ptrveloc.c revision 35c4bbdf
14642e01fSmrg/*
24642e01fSmrg *
36747b715Smrg * Copyright © 2006-2009 Simon Thum             simon dot thum at gmx dot de
44642e01fSmrg *
54642e01fSmrg * Permission is hereby granted, free of charge, to any person obtaining a
64642e01fSmrg * copy of this software and associated documentation files (the "Software"),
74642e01fSmrg * to deal in the Software without restriction, including without limitation
84642e01fSmrg * the rights to use, copy, modify, merge, publish, distribute, sublicense,
94642e01fSmrg * and/or sell copies of the Software, and to permit persons to whom the
104642e01fSmrg * Software is furnished to do so, subject to the following conditions:
114642e01fSmrg *
124642e01fSmrg * The above copyright notice and this permission notice (including the next
134642e01fSmrg * paragraph) shall be included in all copies or substantial portions of the
144642e01fSmrg * Software.
154642e01fSmrg *
164642e01fSmrg * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
174642e01fSmrg * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
184642e01fSmrg * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
194642e01fSmrg * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
204642e01fSmrg * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
214642e01fSmrg * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
224642e01fSmrg * DEALINGS IN THE SOFTWARE.
234642e01fSmrg */
244642e01fSmrg
254642e01fSmrg#ifdef HAVE_DIX_CONFIG_H
264642e01fSmrg#include <dix-config.h>
274642e01fSmrg#endif
284642e01fSmrg
294642e01fSmrg#include <math.h>
304642e01fSmrg#include <ptrveloc.h>
316747b715Smrg#include <exevents.h>
326747b715Smrg#include <X11/Xatom.h>
3335c4bbdfSmrg#include <os.h>
346747b715Smrg
356747b715Smrg#include <xserver-properties.h>
364642e01fSmrg
374642e01fSmrg/*****************************************************************************
384642e01fSmrg * Predictable pointer acceleration
394642e01fSmrg *
406747b715Smrg * 2006-2009 by Simon Thum (simon [dot] thum [at] gmx de)
414642e01fSmrg *
424642e01fSmrg * Serves 3 complementary functions:
434642e01fSmrg * 1) provide a sophisticated ballistic velocity estimate to improve
444642e01fSmrg *    the relation between velocity (of the device) and acceleration
454642e01fSmrg * 2) make arbitrary acceleration profiles possible
464642e01fSmrg * 3) decelerate by two means (constant and adaptive) if enabled
474642e01fSmrg *
484642e01fSmrg * Important concepts are the
494642e01fSmrg *
504642e01fSmrg * - Scheme
514642e01fSmrg *      which selects the basic algorithm
524642e01fSmrg *      (see devices.c/InitPointerAccelerationScheme)
534642e01fSmrg * - Profile
544642e01fSmrg *      which returns an acceleration
554642e01fSmrg *      for a given velocity
564642e01fSmrg *
576747b715Smrg *  The profile can be selected by the user at runtime.
586747b715Smrg *  The classic profile is intended to cleanly perform old-style
594642e01fSmrg *  function selection (threshold =/!= 0)
604642e01fSmrg *
614642e01fSmrg ****************************************************************************/
624642e01fSmrg
634642e01fSmrg/* fwds */
6435c4bbdfSmrgstatic double
6535c4bbdfSmrgSimpleSmoothProfile(DeviceIntPtr dev, DeviceVelocityPtr vel, double velocity,
6635c4bbdfSmrg                    double threshold, double acc);
676747b715Smrgstatic PointerAccelerationProfileFunc
686747b715SmrgGetAccelerationProfile(DeviceVelocityPtr vel, int profile_num);
6935c4bbdfSmrgstatic BOOL
7035c4bbdfSmrgInitializePredictableAccelerationProperties(DeviceIntPtr,
7135c4bbdfSmrg                                            DeviceVelocityPtr,
7235c4bbdfSmrg                                            PredictableAccelSchemePtr);
7335c4bbdfSmrgstatic BOOL
7435c4bbdfSmrgDeletePredictableAccelerationProperties(DeviceIntPtr,
7535c4bbdfSmrg                                        PredictableAccelSchemePtr);
764642e01fSmrg
774642e01fSmrg/*#define PTRACCEL_DEBUGGING*/
784642e01fSmrg
794642e01fSmrg#ifdef PTRACCEL_DEBUGGING
8035c4bbdfSmrg#define DebugAccelF(...) ErrorFSigSafe("dix/ptraccel: " __VA_ARGS__)
814642e01fSmrg#else
8235c4bbdfSmrg#define DebugAccelF(...)        /* */
834642e01fSmrg#endif
844642e01fSmrg
854642e01fSmrg/********************************
866747b715Smrg *  Init/Uninit
874642e01fSmrg *******************************/
884642e01fSmrg
896747b715Smrg/* some int which is not a profile number */
906747b715Smrg#define PROFILE_UNINITIALIZE (-100)
916747b715Smrg
924642e01fSmrg/**
9335c4bbdfSmrg * Init DeviceVelocity struct so it should match the average case
944642e01fSmrg */
954642e01fSmrgvoid
966747b715SmrgInitVelocityData(DeviceVelocityPtr vel)
974642e01fSmrg{
986747b715Smrg    memset(vel, 0, sizeof(DeviceVelocityRec));
996747b715Smrg
10035c4bbdfSmrg    vel->corr_mul = 10.0;       /* dots per 10 milisecond should be usable */
10135c4bbdfSmrg    vel->const_acceleration = 1.0;      /* no acceleration/deceleration  */
1026747b715Smrg    vel->reset_time = 300;
1036747b715Smrg    vel->use_softening = 1;
10435c4bbdfSmrg    vel->min_acceleration = 1.0;        /* don't decelerate */
1056747b715Smrg    vel->max_rel_diff = 0.2;
1066747b715Smrg    vel->max_diff = 1.0;
1076747b715Smrg    vel->initial_range = 2;
1086747b715Smrg    vel->average_accel = TRUE;
1096747b715Smrg    SetAccelerationProfile(vel, AccelProfileClassic);
1106747b715Smrg    InitTrackers(vel, 16);
1114642e01fSmrg}
1124642e01fSmrg
1134642e01fSmrg/**
11435c4bbdfSmrg * Clean up DeviceVelocityRec
1154642e01fSmrg */
1166747b715Smrgvoid
11735c4bbdfSmrgFreeVelocityData(DeviceVelocityPtr vel)
11835c4bbdfSmrg{
1196747b715Smrg    free(vel->tracker);
1206747b715Smrg    SetAccelerationProfile(vel, PROFILE_UNINITIALIZE);
1214642e01fSmrg}
1224642e01fSmrg
12335c4bbdfSmrg/**
12435c4bbdfSmrg * Init predictable scheme
12535c4bbdfSmrg */
12635c4bbdfSmrgBool
12735c4bbdfSmrgInitPredictableAccelerationScheme(DeviceIntPtr dev,
12835c4bbdfSmrg                                  ValuatorAccelerationPtr protoScheme)
12935c4bbdfSmrg{
13035c4bbdfSmrg    DeviceVelocityPtr vel;
13135c4bbdfSmrg    ValuatorAccelerationRec scheme;
13235c4bbdfSmrg    PredictableAccelSchemePtr schemeData;
13335c4bbdfSmrg
13435c4bbdfSmrg    scheme = *protoScheme;
13535c4bbdfSmrg    vel = calloc(1, sizeof(DeviceVelocityRec));
13635c4bbdfSmrg    schemeData = calloc(1, sizeof(PredictableAccelSchemeRec));
13735c4bbdfSmrg    if (!vel || !schemeData) {
13835c4bbdfSmrg        free(vel);
13935c4bbdfSmrg        free(schemeData);
14035c4bbdfSmrg        return FALSE;
14135c4bbdfSmrg    }
14235c4bbdfSmrg    InitVelocityData(vel);
14335c4bbdfSmrg    schemeData->vel = vel;
14435c4bbdfSmrg    scheme.accelData = schemeData;
14535c4bbdfSmrg    if (!InitializePredictableAccelerationProperties(dev, vel, schemeData)) {
14635c4bbdfSmrg        free(vel);
14735c4bbdfSmrg        free(schemeData);
14835c4bbdfSmrg        return FALSE;
14935c4bbdfSmrg    }
15035c4bbdfSmrg    /* all fine, assign scheme to device */
15135c4bbdfSmrg    dev->valuator->accelScheme = scheme;
15235c4bbdfSmrg    return TRUE;
15335c4bbdfSmrg}
1544642e01fSmrg
15535c4bbdfSmrg/**
15635c4bbdfSmrg *  Uninit scheme
1574642e01fSmrg */
1584642e01fSmrgvoid
1596747b715SmrgAccelerationDefaultCleanup(DeviceIntPtr dev)
1604642e01fSmrg{
16135c4bbdfSmrg    DeviceVelocityPtr vel = GetDevicePredictableAccelData(dev);
16235c4bbdfSmrg
16335c4bbdfSmrg    if (vel) {
16435c4bbdfSmrg        /* the proper guarantee would be that we're not inside of
16535c4bbdfSmrg         * AccelSchemeProc(), but that seems impossible. Schemes don't get
16635c4bbdfSmrg         * switched often anyway.
16735c4bbdfSmrg         */
16835c4bbdfSmrg        OsBlockSignals();
1696747b715Smrg        dev->valuator->accelScheme.AccelSchemeProc = NULL;
17035c4bbdfSmrg        FreeVelocityData(vel);
17135c4bbdfSmrg        free(vel);
17235c4bbdfSmrg        DeletePredictableAccelerationProperties(dev,
17335c4bbdfSmrg                                                (PredictableAccelSchemePtr)
17435c4bbdfSmrg                                                dev->valuator->accelScheme.
17535c4bbdfSmrg                                                accelData);
1766747b715Smrg        free(dev->valuator->accelScheme.accelData);
1776747b715Smrg        dev->valuator->accelScheme.accelData = NULL;
17835c4bbdfSmrg        OsReleaseSignals();
1794642e01fSmrg    }
1804642e01fSmrg}
1814642e01fSmrg
1826747b715Smrg/*************************
1836747b715Smrg * Input property support
1846747b715Smrg ************************/
1854642e01fSmrg
1864642e01fSmrg/**
1876747b715Smrg * choose profile
1886747b715Smrg */
1896747b715Smrgstatic int
1906747b715SmrgAccelSetProfileProperty(DeviceIntPtr dev, Atom atom,
1916747b715Smrg                        XIPropertyValuePtr val, BOOL checkOnly)
1924642e01fSmrg{
1936747b715Smrg    DeviceVelocityPtr vel;
1946747b715Smrg    int profile, *ptr = &profile;
1956747b715Smrg    int rc;
1966747b715Smrg    int nelem = 1;
1976747b715Smrg
1986747b715Smrg    if (atom != XIGetKnownProperty(ACCEL_PROP_PROFILE_NUMBER))
1996747b715Smrg        return Success;
2006747b715Smrg
2016747b715Smrg    vel = GetDevicePredictableAccelData(dev);
2026747b715Smrg    if (!vel)
2036747b715Smrg        return BadValue;
2046747b715Smrg    rc = XIPropToInt(val, &nelem, &ptr);
2056747b715Smrg
20635c4bbdfSmrg    if (checkOnly) {
2076747b715Smrg        if (rc)
2086747b715Smrg            return rc;
2096747b715Smrg
2106747b715Smrg        if (GetAccelerationProfile(vel, profile) == NULL)
2116747b715Smrg            return BadValue;
21235c4bbdfSmrg    }
21335c4bbdfSmrg    else
21435c4bbdfSmrg        SetAccelerationProfile(vel, profile);
2156747b715Smrg
2166747b715Smrg    return Success;
2174642e01fSmrg}
2184642e01fSmrg
2196747b715Smrgstatic long
2206747b715SmrgAccelInitProfileProperty(DeviceIntPtr dev, DeviceVelocityPtr vel)
2214642e01fSmrg{
2226747b715Smrg    int profile = vel->statistics.profile_number;
2236747b715Smrg    Atom prop_profile_number = XIGetKnownProperty(ACCEL_PROP_PROFILE_NUMBER);
2244642e01fSmrg
2256747b715Smrg    XIChangeDeviceProperty(dev, prop_profile_number, XA_INTEGER, 32,
2266747b715Smrg                           PropModeReplace, 1, &profile, FALSE);
2276747b715Smrg    XISetDevicePropertyDeletable(dev, prop_profile_number, FALSE);
2286747b715Smrg    return XIRegisterPropertyHandler(dev, AccelSetProfileProperty, NULL, NULL);
2294642e01fSmrg}
2304642e01fSmrg
2316747b715Smrg/**
2326747b715Smrg * constant deceleration
2336747b715Smrg */
2346747b715Smrgstatic int
2356747b715SmrgAccelSetDecelProperty(DeviceIntPtr dev, Atom atom,
2366747b715Smrg                      XIPropertyValuePtr val, BOOL checkOnly)
2374642e01fSmrg{
2386747b715Smrg    DeviceVelocityPtr vel;
2396747b715Smrg    float v, *ptr = &v;
2406747b715Smrg    int rc;
2416747b715Smrg    int nelem = 1;
2426747b715Smrg
2436747b715Smrg    if (atom != XIGetKnownProperty(ACCEL_PROP_CONSTANT_DECELERATION))
2446747b715Smrg        return Success;
2456747b715Smrg
2466747b715Smrg    vel = GetDevicePredictableAccelData(dev);
2476747b715Smrg    if (!vel)
2486747b715Smrg        return BadValue;
2496747b715Smrg    rc = XIPropToFloat(val, &nelem, &ptr);
2506747b715Smrg
25135c4bbdfSmrg    if (checkOnly) {
2526747b715Smrg        if (rc)
2536747b715Smrg            return rc;
25435c4bbdfSmrg        return (v > 0) ? Success : BadValue;
2554642e01fSmrg    }
2566747b715Smrg
25735c4bbdfSmrg    vel->const_acceleration = 1 / v;
2586747b715Smrg
2596747b715Smrg    return Success;
2606747b715Smrg}
2616747b715Smrg
2626747b715Smrgstatic long
2636747b715SmrgAccelInitDecelProperty(DeviceIntPtr dev, DeviceVelocityPtr vel)
2646747b715Smrg{
26535c4bbdfSmrg    float fval = 1.0 / vel->const_acceleration;
26635c4bbdfSmrg    Atom prop_const_decel =
26735c4bbdfSmrg        XIGetKnownProperty(ACCEL_PROP_CONSTANT_DECELERATION);
2686747b715Smrg    XIChangeDeviceProperty(dev, prop_const_decel,
26935c4bbdfSmrg                           XIGetKnownProperty(XATOM_FLOAT), 32, PropModeReplace,
27035c4bbdfSmrg                           1, &fval, FALSE);
2716747b715Smrg    XISetDevicePropertyDeletable(dev, prop_const_decel, FALSE);
2726747b715Smrg    return XIRegisterPropertyHandler(dev, AccelSetDecelProperty, NULL, NULL);
2734642e01fSmrg}
2744642e01fSmrg
2754642e01fSmrg/**
2766747b715Smrg * adaptive deceleration
2774642e01fSmrg */
2786747b715Smrgstatic int
2796747b715SmrgAccelSetAdaptDecelProperty(DeviceIntPtr dev, Atom atom,
2806747b715Smrg                           XIPropertyValuePtr val, BOOL checkOnly)
2814642e01fSmrg{
2826747b715Smrg    DeviceVelocityPtr veloc;
2836747b715Smrg    float v, *ptr = &v;
2846747b715Smrg    int rc;
2856747b715Smrg    int nelem = 1;
2866747b715Smrg
2876747b715Smrg    if (atom != XIGetKnownProperty(ACCEL_PROP_ADAPTIVE_DECELERATION))
2886747b715Smrg        return Success;
2896747b715Smrg
2906747b715Smrg    veloc = GetDevicePredictableAccelData(dev);
2916747b715Smrg    if (!veloc)
2926747b715Smrg        return BadValue;
2936747b715Smrg    rc = XIPropToFloat(val, &nelem, &ptr);
2946747b715Smrg
29535c4bbdfSmrg    if (checkOnly) {
2966747b715Smrg        if (rc)
2976747b715Smrg            return rc;
29835c4bbdfSmrg        return (v >= 1.0f) ? Success : BadValue;
2994642e01fSmrg    }
3006747b715Smrg
30135c4bbdfSmrg    if (v >= 1.0f)
30235c4bbdfSmrg        veloc->min_acceleration = 1 / v;
3036747b715Smrg
3046747b715Smrg    return Success;
3054642e01fSmrg}
3064642e01fSmrg
3076747b715Smrgstatic long
3086747b715SmrgAccelInitAdaptDecelProperty(DeviceIntPtr dev, DeviceVelocityPtr vel)
3096747b715Smrg{
31035c4bbdfSmrg    float fval = 1.0 / vel->min_acceleration;
31135c4bbdfSmrg    Atom prop_adapt_decel =
31235c4bbdfSmrg        XIGetKnownProperty(ACCEL_PROP_ADAPTIVE_DECELERATION);
3134642e01fSmrg
31435c4bbdfSmrg    XIChangeDeviceProperty(dev, prop_adapt_decel,
31535c4bbdfSmrg                           XIGetKnownProperty(XATOM_FLOAT), 32, PropModeReplace,
31635c4bbdfSmrg                           1, &fval, FALSE);
3176747b715Smrg    XISetDevicePropertyDeletable(dev, prop_adapt_decel, FALSE);
31835c4bbdfSmrg    return XIRegisterPropertyHandler(dev, AccelSetAdaptDecelProperty, NULL,
31935c4bbdfSmrg                                     NULL);
3206747b715Smrg}
3216747b715Smrg
3226747b715Smrg/**
3236747b715Smrg * velocity scaling
3246747b715Smrg */
3256747b715Smrgstatic int
3266747b715SmrgAccelSetScaleProperty(DeviceIntPtr dev, Atom atom,
3276747b715Smrg                      XIPropertyValuePtr val, BOOL checkOnly)
3284642e01fSmrg{
3296747b715Smrg    DeviceVelocityPtr vel;
3306747b715Smrg    float v, *ptr = &v;
3316747b715Smrg    int rc;
3326747b715Smrg    int nelem = 1;
3336747b715Smrg
3346747b715Smrg    if (atom != XIGetKnownProperty(ACCEL_PROP_VELOCITY_SCALING))
3356747b715Smrg        return Success;
3364642e01fSmrg
3376747b715Smrg    vel = GetDevicePredictableAccelData(dev);
3386747b715Smrg    if (!vel)
3396747b715Smrg        return BadValue;
3406747b715Smrg    rc = XIPropToFloat(val, &nelem, &ptr);
3416747b715Smrg
34235c4bbdfSmrg    if (checkOnly) {
3436747b715Smrg        if (rc)
3446747b715Smrg            return rc;
3456747b715Smrg
3466747b715Smrg        return (v > 0) ? Success : BadValue;
3474642e01fSmrg    }
3486747b715Smrg
34935c4bbdfSmrg    if (v > 0)
35035c4bbdfSmrg        vel->corr_mul = v;
3516747b715Smrg
3526747b715Smrg    return Success;
3534642e01fSmrg}
3544642e01fSmrg
3556747b715Smrgstatic long
3566747b715SmrgAccelInitScaleProperty(DeviceIntPtr dev, DeviceVelocityPtr vel)
3576747b715Smrg{
3586747b715Smrg    float fval = vel->corr_mul;
3596747b715Smrg    Atom prop_velo_scale = XIGetKnownProperty(ACCEL_PROP_VELOCITY_SCALING);
3604642e01fSmrg
36135c4bbdfSmrg    XIChangeDeviceProperty(dev, prop_velo_scale,
36235c4bbdfSmrg                           XIGetKnownProperty(XATOM_FLOAT), 32, PropModeReplace,
36335c4bbdfSmrg                           1, &fval, FALSE);
3646747b715Smrg    XISetDevicePropertyDeletable(dev, prop_velo_scale, FALSE);
3656747b715Smrg    return XIRegisterPropertyHandler(dev, AccelSetScaleProperty, NULL, NULL);
3664642e01fSmrg}
3674642e01fSmrg
36835c4bbdfSmrgstatic BOOL
36935c4bbdfSmrgInitializePredictableAccelerationProperties(DeviceIntPtr dev,
37035c4bbdfSmrg                                            DeviceVelocityPtr vel,
37135c4bbdfSmrg                                            PredictableAccelSchemePtr
37235c4bbdfSmrg                                            schemeData)
3734642e01fSmrg{
37435c4bbdfSmrg    int num_handlers = 4;
3754642e01fSmrg
37635c4bbdfSmrg    if (!vel)
37735c4bbdfSmrg        return FALSE;
3784642e01fSmrg
37935c4bbdfSmrg    schemeData->prop_handlers = calloc(num_handlers, sizeof(long));
38035c4bbdfSmrg    if (!schemeData->prop_handlers)
38135c4bbdfSmrg        return FALSE;
38235c4bbdfSmrg    schemeData->num_prop_handlers = num_handlers;
38335c4bbdfSmrg    schemeData->prop_handlers[0] = AccelInitProfileProperty(dev, vel);
38435c4bbdfSmrg    schemeData->prop_handlers[1] = AccelInitDecelProperty(dev, vel);
38535c4bbdfSmrg    schemeData->prop_handlers[2] = AccelInitAdaptDecelProperty(dev, vel);
38635c4bbdfSmrg    schemeData->prop_handlers[3] = AccelInitScaleProperty(dev, vel);
3874642e01fSmrg
3886747b715Smrg    return TRUE;
3896747b715Smrg}
3904642e01fSmrg
3916747b715SmrgBOOL
39235c4bbdfSmrgDeletePredictableAccelerationProperties(DeviceIntPtr dev,
39335c4bbdfSmrg                                        PredictableAccelSchemePtr scheme)
3946747b715Smrg{
39535c4bbdfSmrg    DeviceVelocityPtr vel;
3966747b715Smrg    Atom prop;
3976747b715Smrg    int i;
3986747b715Smrg
3996747b715Smrg    prop = XIGetKnownProperty(ACCEL_PROP_VELOCITY_SCALING);
4006747b715Smrg    XIDeleteDeviceProperty(dev, prop, FALSE);
4016747b715Smrg    prop = XIGetKnownProperty(ACCEL_PROP_ADAPTIVE_DECELERATION);
4026747b715Smrg    XIDeleteDeviceProperty(dev, prop, FALSE);
4036747b715Smrg    prop = XIGetKnownProperty(ACCEL_PROP_CONSTANT_DECELERATION);
4046747b715Smrg    XIDeleteDeviceProperty(dev, prop, FALSE);
4056747b715Smrg    prop = XIGetKnownProperty(ACCEL_PROP_PROFILE_NUMBER);
4066747b715Smrg    XIDeleteDeviceProperty(dev, prop, FALSE);
4076747b715Smrg
4086747b715Smrg    vel = GetDevicePredictableAccelData(dev);
40935c4bbdfSmrg    if (vel) {
41035c4bbdfSmrg        for (i = 0; i < scheme->num_prop_handlers; i++)
41135c4bbdfSmrg            if (scheme->prop_handlers[i])
41235c4bbdfSmrg                XIUnregisterPropertyHandler(dev, scheme->prop_handlers[i]);
41335c4bbdfSmrg    }
4144642e01fSmrg
41535c4bbdfSmrg    free(scheme->prop_handlers);
41635c4bbdfSmrg    scheme->prop_handlers = NULL;
41735c4bbdfSmrg    scheme->num_prop_handlers = 0;
4186747b715Smrg    return TRUE;
4194642e01fSmrg}
4204642e01fSmrg
4216747b715Smrg/*********************
4226747b715Smrg * Tracking logic
4236747b715Smrg ********************/
4244642e01fSmrg
4256747b715Smrgvoid
4266747b715SmrgInitTrackers(DeviceVelocityPtr vel, int ntracker)
4276747b715Smrg{
42835c4bbdfSmrg    if (ntracker < 1) {
42935c4bbdfSmrg        ErrorF("invalid number of trackers\n");
43035c4bbdfSmrg        return;
4314642e01fSmrg    }
4326747b715Smrg    free(vel->tracker);
43335c4bbdfSmrg    vel->tracker = (MotionTrackerPtr) calloc(ntracker, sizeof(MotionTracker));
4346747b715Smrg    vel->num_tracker = ntracker;
4354642e01fSmrg}
4364642e01fSmrg
43735c4bbdfSmrgenum directions {
43835c4bbdfSmrg    N = (1 << 0),
43935c4bbdfSmrg    NE = (1 << 1),
44035c4bbdfSmrg    E = (1 << 2),
44135c4bbdfSmrg    SE = (1 << 3),
44235c4bbdfSmrg    S = (1 << 4),
44335c4bbdfSmrg    SW = (1 << 5),
44435c4bbdfSmrg    W = (1 << 6),
44535c4bbdfSmrg    NW = (1 << 7),
44635c4bbdfSmrg    UNDEFINED = 0xFF
44735c4bbdfSmrg};
44835c4bbdfSmrg
4494642e01fSmrg/**
4506747b715Smrg * return a bit field of possible directions.
4516747b715Smrg * There's no reason against widening to more precise directions (<45 degrees),
4526747b715Smrg * should it not perform well. All this is needed for is sort out non-linear
4536747b715Smrg * motion, so precision isn't paramount. However, one should not flag direction
4546747b715Smrg * too narrow, since it would then cut the linear segment to zero size way too
4556747b715Smrg * often.
45635c4bbdfSmrg *
45735c4bbdfSmrg * @return A bitmask for N, NE, S, SE, etc. indicating the directions for
45835c4bbdfSmrg * this movement.
4594642e01fSmrg */
4606747b715Smrgstatic int
46135c4bbdfSmrgDoGetDirection(int dx, int dy)
46235c4bbdfSmrg{
46335c4bbdfSmrg    int dir = 0;
46435c4bbdfSmrg
4656747b715Smrg    /* on insignificant mickeys, flag 135 degrees */
46635c4bbdfSmrg    if (abs(dx) < 2 && abs(dy) < 2) {
46735c4bbdfSmrg        /* first check diagonal cases */
46835c4bbdfSmrg        if (dx > 0 && dy > 0)
46935c4bbdfSmrg            dir = E | SE | S;
47035c4bbdfSmrg        else if (dx > 0 && dy < 0)
47135c4bbdfSmrg            dir = N | NE | E;
47235c4bbdfSmrg        else if (dx < 0 && dy < 0)
47335c4bbdfSmrg            dir = W | NW | N;
47435c4bbdfSmrg        else if (dx < 0 && dy > 0)
47535c4bbdfSmrg            dir = W | SW | S;
4766747b715Smrg        /* check axis-aligned directions */
47735c4bbdfSmrg        else if (dx > 0)
47835c4bbdfSmrg            dir = NE | E | SE;
47935c4bbdfSmrg        else if (dx < 0)
48035c4bbdfSmrg            dir = NW | W | SW;
48135c4bbdfSmrg        else if (dy > 0)
48235c4bbdfSmrg            dir = SE | S | SW;
48335c4bbdfSmrg        else if (dy < 0)
48435c4bbdfSmrg            dir = NE | N | NW;
48535c4bbdfSmrg        else
48635c4bbdfSmrg            dir = UNDEFINED;    /* shouldn't happen */
4874642e01fSmrg    }
48835c4bbdfSmrg    else {                      /* compute angle and set appropriate flags */
48935c4bbdfSmrg        double r;
49035c4bbdfSmrg        int i1, i2;
49135c4bbdfSmrg
49235c4bbdfSmrg        r = atan2(dy, dx);
49335c4bbdfSmrg        /* find direction.
49435c4bbdfSmrg         *
49535c4bbdfSmrg         * Add 360° to avoid r become negative since C has no well-defined
49635c4bbdfSmrg         * modulo for such cases. Then divide by 45° to get the octant
49735c4bbdfSmrg         * number,  e.g.
49835c4bbdfSmrg         *          0 <= r <= 1 is [0-45]°
49935c4bbdfSmrg         *          1 <= r <= 2 is [45-90]°
50035c4bbdfSmrg         *          etc.
50135c4bbdfSmrg         * But we add extra 90° to match up with our N, S, etc. defines up
50235c4bbdfSmrg         * there, rest stays the same.
50335c4bbdfSmrg         */
50435c4bbdfSmrg        r = (r + (M_PI * 2.5)) / (M_PI / 4);
50535c4bbdfSmrg        /* this intends to flag 2 directions (45 degrees),
50635c4bbdfSmrg         * except on very well-aligned mickeys. */
50735c4bbdfSmrg        i1 = (int) (r + 0.1) % 8;
50835c4bbdfSmrg        i2 = (int) (r + 0.9) % 8;
50935c4bbdfSmrg        if (i1 < 0 || i1 > 7 || i2 < 0 || i2 > 7)
51035c4bbdfSmrg            dir = UNDEFINED;    /* shouldn't happen */
51135c4bbdfSmrg        else
51235c4bbdfSmrg            dir = (1 << i1 | 1 << i2);
51335c4bbdfSmrg    }
51435c4bbdfSmrg    return dir;
5156747b715Smrg}
5164642e01fSmrg
5176747b715Smrg#define DIRECTION_CACHE_RANGE 5
5186747b715Smrg#define DIRECTION_CACHE_SIZE (DIRECTION_CACHE_RANGE*2+1)
5196747b715Smrg
52035c4bbdfSmrg/* cache DoGetDirection().
52135c4bbdfSmrg * To avoid excessive use of direction calculation, cache the values for
52235c4bbdfSmrg * [-5..5] for both x/y. Anything outside of that is calcualted on the fly.
52335c4bbdfSmrg *
52435c4bbdfSmrg * @return A bitmask for N, NE, S, SE, etc. indicating the directions for
52535c4bbdfSmrg * this movement.
52635c4bbdfSmrg */
5276747b715Smrgstatic int
52835c4bbdfSmrgGetDirection(int dx, int dy)
52935c4bbdfSmrg{
5306747b715Smrg    static int cache[DIRECTION_CACHE_SIZE][DIRECTION_CACHE_SIZE];
53135c4bbdfSmrg    int dir;
53235c4bbdfSmrg
53335c4bbdfSmrg    if (abs(dx) <= DIRECTION_CACHE_RANGE && abs(dy) <= DIRECTION_CACHE_RANGE) {
53435c4bbdfSmrg        /* cacheable */
53535c4bbdfSmrg        dir = cache[DIRECTION_CACHE_RANGE + dx][DIRECTION_CACHE_RANGE + dy];
53635c4bbdfSmrg        if (dir == 0) {
53735c4bbdfSmrg            dir = DoGetDirection(dx, dy);
53835c4bbdfSmrg            cache[DIRECTION_CACHE_RANGE + dx][DIRECTION_CACHE_RANGE + dy] = dir;
53935c4bbdfSmrg        }
5404642e01fSmrg    }
54135c4bbdfSmrg    else {
54235c4bbdfSmrg        /* non-cacheable */
54335c4bbdfSmrg        dir = DoGetDirection(dx, dy);
54435c4bbdfSmrg    }
54535c4bbdfSmrg
54635c4bbdfSmrg    return dir;
5476747b715Smrg}
5484642e01fSmrg
5496747b715Smrg#undef DIRECTION_CACHE_RANGE
5506747b715Smrg#undef DIRECTION_CACHE_SIZE
5514642e01fSmrg
5526747b715Smrg/* convert offset (age) to array index */
5536747b715Smrg#define TRACKER_INDEX(s, d) (((s)->num_tracker + (s)->cur_tracker - (d)) % (s)->num_tracker)
55435c4bbdfSmrg#define TRACKER(s, d) &(s)->tracker[TRACKER_INDEX(s,d)]
5556747b715Smrg
55635c4bbdfSmrg/**
55735c4bbdfSmrg * Add the delta motion to each tracker, then reset the latest tracker to
55835c4bbdfSmrg * 0/0 and set it as the current one.
55935c4bbdfSmrg */
5606747b715Smrgstatic inline void
56135c4bbdfSmrgFeedTrackers(DeviceVelocityPtr vel, double dx, double dy, int cur_t)
5626747b715Smrg{
5636747b715Smrg    int n;
56435c4bbdfSmrg
56535c4bbdfSmrg    for (n = 0; n < vel->num_tracker; n++) {
56635c4bbdfSmrg        vel->tracker[n].dx += dx;
56735c4bbdfSmrg        vel->tracker[n].dy += dy;
5684642e01fSmrg    }
5696747b715Smrg    n = (vel->cur_tracker + 1) % vel->num_tracker;
57035c4bbdfSmrg    vel->tracker[n].dx = 0.0;
57135c4bbdfSmrg    vel->tracker[n].dy = 0.0;
5726747b715Smrg    vel->tracker[n].time = cur_t;
5736747b715Smrg    vel->tracker[n].dir = GetDirection(dx, dy);
57435c4bbdfSmrg    DebugAccelF("motion [dx: %f dy: %f dir:%d diff: %d]\n",
5756747b715Smrg                dx, dy, vel->tracker[n].dir,
5766747b715Smrg                cur_t - vel->tracker[vel->cur_tracker].time);
5776747b715Smrg    vel->cur_tracker = n;
5786747b715Smrg}
5796747b715Smrg
5806747b715Smrg/**
5816747b715Smrg * calc velocity for given tracker, with
5826747b715Smrg * velocity scaling.
5836747b715Smrg * This assumes linear motion.
5846747b715Smrg */
58535c4bbdfSmrgstatic double
58635c4bbdfSmrgCalcTracker(const MotionTracker * tracker, int cur_t)
58735c4bbdfSmrg{
58835c4bbdfSmrg    double dist = sqrt(tracker->dx * tracker->dx + tracker->dy * tracker->dy);
58935c4bbdfSmrg    int dtime = cur_t - tracker->time;
59035c4bbdfSmrg
59135c4bbdfSmrg    if (dtime > 0)
59235c4bbdfSmrg        return dist / dtime;
5936747b715Smrg    else
59435c4bbdfSmrg        return 0;               /* synonymous for NaN, since we're not C99 */
5956747b715Smrg}
5966747b715Smrg
5976747b715Smrg/* find the most plausible velocity. That is, the most distant
59835c4bbdfSmrg * (in time) tracker which isn't too old, the movement vector was
59935c4bbdfSmrg * in the same octant, and where the velocity is within an
60035c4bbdfSmrg * acceptable range to the inital velocity.
6016747b715Smrg *
60235c4bbdfSmrg * @return The tracker's velocity or 0 if the above conditions are unmet
6036747b715Smrg */
60435c4bbdfSmrgstatic double
60535c4bbdfSmrgQueryTrackers(DeviceVelocityPtr vel, int cur_t)
60635c4bbdfSmrg{
60735c4bbdfSmrg    int offset, dir = UNDEFINED, used_offset = -1, age_ms;
60835c4bbdfSmrg
6096747b715Smrg    /* initial velocity: a low-offset, valid velocity */
61035c4bbdfSmrg    double initial_velocity = 0, result = 0, velocity_diff;
61135c4bbdfSmrg    double velocity_factor = vel->corr_mul * vel->const_acceleration;   /* premultiply */
61235c4bbdfSmrg
6136747b715Smrg    /* loop from current to older data */
61435c4bbdfSmrg    for (offset = 1; offset < vel->num_tracker; offset++) {
61535c4bbdfSmrg        MotionTracker *tracker = TRACKER(vel, offset);
61635c4bbdfSmrg        double tracker_velocity;
61735c4bbdfSmrg
61835c4bbdfSmrg        age_ms = cur_t - tracker->time;
61935c4bbdfSmrg
62035c4bbdfSmrg        /* bail out if data is too old and protect from overrun */
62135c4bbdfSmrg        if (age_ms >= vel->reset_time || age_ms < 0) {
62235c4bbdfSmrg            DebugAccelF("query: tracker too old (reset after %d, age is %d)\n",
62335c4bbdfSmrg                        vel->reset_time, age_ms);
62435c4bbdfSmrg            break;
62535c4bbdfSmrg        }
62635c4bbdfSmrg
62735c4bbdfSmrg        /*
62835c4bbdfSmrg         * this heuristic avoids using the linear-motion velocity formula
62935c4bbdfSmrg         * in CalcTracker() on motion that isn't exactly linear. So to get
63035c4bbdfSmrg         * even more precision we could subdivide as a final step, so possible
63135c4bbdfSmrg         * non-linearities are accounted for.
63235c4bbdfSmrg         */
63335c4bbdfSmrg        dir &= tracker->dir;
63435c4bbdfSmrg        if (dir == 0) {         /* we've changed octant of movement (e.g. NE → NW) */
63535c4bbdfSmrg            DebugAccelF("query: no longer linear\n");
63635c4bbdfSmrg            /* instead of breaking it we might also inspect the partition after,
63735c4bbdfSmrg             * but actual improvement with this is probably rare. */
63835c4bbdfSmrg            break;
63935c4bbdfSmrg        }
64035c4bbdfSmrg
64135c4bbdfSmrg        tracker_velocity = CalcTracker(tracker, cur_t) * velocity_factor;
64235c4bbdfSmrg
64335c4bbdfSmrg        if ((initial_velocity == 0 || offset <= vel->initial_range) &&
64435c4bbdfSmrg            tracker_velocity != 0) {
64535c4bbdfSmrg            /* set initial velocity and result */
64635c4bbdfSmrg            result = initial_velocity = tracker_velocity;
64735c4bbdfSmrg            used_offset = offset;
64835c4bbdfSmrg        }
64935c4bbdfSmrg        else if (initial_velocity != 0 && tracker_velocity != 0) {
65035c4bbdfSmrg            velocity_diff = fabs(initial_velocity - tracker_velocity);
65135c4bbdfSmrg
65235c4bbdfSmrg            if (velocity_diff > vel->max_diff &&
65335c4bbdfSmrg                velocity_diff / (initial_velocity + tracker_velocity) >=
65435c4bbdfSmrg                vel->max_rel_diff) {
65535c4bbdfSmrg                /* we're not in range, quit - it won't get better. */
65635c4bbdfSmrg                DebugAccelF("query: tracker too different:"
65735c4bbdfSmrg                            " old %2.2f initial %2.2f diff: %2.2f\n",
65835c4bbdfSmrg                            tracker_velocity, initial_velocity, velocity_diff);
65935c4bbdfSmrg                break;
66035c4bbdfSmrg            }
66135c4bbdfSmrg            /* we're in range with the initial velocity,
66235c4bbdfSmrg             * so this result is likely better
66335c4bbdfSmrg             * (it contains more information). */
66435c4bbdfSmrg            result = tracker_velocity;
66535c4bbdfSmrg            used_offset = offset;
66635c4bbdfSmrg        }
6676747b715Smrg    }
66835c4bbdfSmrg    if (offset == vel->num_tracker) {
66935c4bbdfSmrg        DebugAccelF("query: last tracker in effect\n");
67035c4bbdfSmrg        used_offset = vel->num_tracker - 1;
6716747b715Smrg    }
67235c4bbdfSmrg    if (used_offset >= 0) {
67335c4bbdfSmrg#ifdef PTRACCEL_DEBUGGING
67435c4bbdfSmrg        MotionTracker *tracker = TRACKER(vel, used_offset);
67535c4bbdfSmrg
67635c4bbdfSmrg        DebugAccelF("result: offset %i [dx: %f dy: %f diff: %i]\n",
67735c4bbdfSmrg                    used_offset, tracker->dx, tracker->dy,
67835c4bbdfSmrg                    cur_t - tracker->time);
67935c4bbdfSmrg#endif
6804642e01fSmrg    }
68135c4bbdfSmrg    return result;
6826747b715Smrg}
6834642e01fSmrg
6846747b715Smrg#undef TRACKER_INDEX
68535c4bbdfSmrg#undef TRACKER
6864642e01fSmrg
6876747b715Smrg/**
6886747b715Smrg * Perform velocity approximation based on 2D 'mickeys' (mouse motion delta).
6896747b715Smrg * return true if non-visible state reset is suggested
6906747b715Smrg */
69135c4bbdfSmrgBOOL
69235c4bbdfSmrgProcessVelocityData2D(DeviceVelocityPtr vel, double dx, double dy, int time)
6936747b715Smrg{
69435c4bbdfSmrg    double velocity;
6954642e01fSmrg
6966747b715Smrg    vel->last_velocity = vel->velocity;
6976747b715Smrg
6986747b715Smrg    FeedTrackers(vel, dx, dy, time);
6994642e01fSmrg
7006747b715Smrg    velocity = QueryTrackers(vel, time);
7016747b715Smrg
70235c4bbdfSmrg    DebugAccelF("velocity is %f\n", velocity);
70335c4bbdfSmrg
7046747b715Smrg    vel->velocity = velocity;
7056747b715Smrg    return velocity == 0;
7066747b715Smrg}
7074642e01fSmrg
7084642e01fSmrg/**
7094642e01fSmrg * this flattens significant ( > 1) mickeys a little bit for more steady
7104642e01fSmrg * constant-velocity response
7114642e01fSmrg */
71235c4bbdfSmrgstatic inline double
71335c4bbdfSmrgApplySimpleSoftening(double prev_delta, double delta)
7144642e01fSmrg{
71535c4bbdfSmrg    double result = delta;
7164642e01fSmrg
71735c4bbdfSmrg    if (delta < -1.0 || delta > 1.0) {
71835c4bbdfSmrg        if (delta > prev_delta)
71935c4bbdfSmrg            result -= 0.5;
72035c4bbdfSmrg        else if (delta < prev_delta)
72135c4bbdfSmrg            result += 0.5;
72235c4bbdfSmrg    }
72335c4bbdfSmrg    return result;
72435c4bbdfSmrg}
7254642e01fSmrg
72635c4bbdfSmrg/**
72735c4bbdfSmrg * Soften the delta based on previous deltas stored in vel.
72835c4bbdfSmrg *
72935c4bbdfSmrg * @param[in,out] fdx Delta X, modified in-place.
73035c4bbdfSmrg * @param[in,out] fdx Delta Y, modified in-place.
73135c4bbdfSmrg */
7324642e01fSmrgstatic void
73335c4bbdfSmrgApplySoftening(DeviceVelocityPtr vel, double *fdx, double *fdy)
7344642e01fSmrg{
73535c4bbdfSmrg    if (vel->use_softening) {
73635c4bbdfSmrg        *fdx = ApplySimpleSoftening(vel->last_dx, *fdx);
73735c4bbdfSmrg        *fdy = ApplySimpleSoftening(vel->last_dy, *fdy);
7384642e01fSmrg    }
73935c4bbdfSmrg}
7404642e01fSmrg
74135c4bbdfSmrgstatic void
74235c4bbdfSmrgApplyConstantDeceleration(DeviceVelocityPtr vel, double *fdx, double *fdy)
74335c4bbdfSmrg{
7446747b715Smrg    *fdx *= vel->const_acceleration;
7456747b715Smrg    *fdy *= vel->const_acceleration;
7464642e01fSmrg}
7474642e01fSmrg
7484642e01fSmrg/*
74935c4bbdfSmrg * compute the acceleration for given velocity and enforce min_acceleration
7504642e01fSmrg */
75135c4bbdfSmrgdouble
75235c4bbdfSmrgBasicComputeAcceleration(DeviceIntPtr dev,
75335c4bbdfSmrg                         DeviceVelocityPtr vel,
75435c4bbdfSmrg                         double velocity, double threshold, double acc)
75535c4bbdfSmrg{
75635c4bbdfSmrg
75735c4bbdfSmrg    double result;
75835c4bbdfSmrg
7596747b715Smrg    result = vel->Profile(dev, vel, velocity, threshold, acc);
7604642e01fSmrg
7614642e01fSmrg    /* enforce min_acceleration */
7626747b715Smrg    if (result < vel->min_acceleration)
76335c4bbdfSmrg        result = vel->min_acceleration;
7644642e01fSmrg    return result;
7654642e01fSmrg}
7664642e01fSmrg
7674642e01fSmrg/**
7684642e01fSmrg * Compute acceleration. Takes into account averaging, nv-reset, etc.
76935c4bbdfSmrg * If the velocity has changed, an average is taken of 6 velocity factors:
77035c4bbdfSmrg * current velocity, last velocity and 4 times the average between the two.
7714642e01fSmrg */
77235c4bbdfSmrgstatic double
77335c4bbdfSmrgComputeAcceleration(DeviceIntPtr dev,
77435c4bbdfSmrg                    DeviceVelocityPtr vel, double threshold, double acc)
77535c4bbdfSmrg{
77635c4bbdfSmrg    double result;
77735c4bbdfSmrg
77835c4bbdfSmrg    if (vel->velocity <= 0) {
77935c4bbdfSmrg        DebugAccelF("profile skipped\n");
7804642e01fSmrg        /*
7816747b715Smrg         * If we have no idea about device velocity, don't pretend it.
7824642e01fSmrg         */
78335c4bbdfSmrg        return 1;
7844642e01fSmrg    }
7854642e01fSmrg
78635c4bbdfSmrg    if (vel->average_accel && vel->velocity != vel->last_velocity) {
78735c4bbdfSmrg        /* use simpson's rule to average acceleration between
78835c4bbdfSmrg         * current and previous velocity.
78935c4bbdfSmrg         * Though being the more natural choice, it causes a minor delay
79035c4bbdfSmrg         * in comparison, so it can be disabled. */
79135c4bbdfSmrg        result =
79235c4bbdfSmrg            BasicComputeAcceleration(dev, vel, vel->velocity, threshold, acc);
79335c4bbdfSmrg        result +=
79435c4bbdfSmrg            BasicComputeAcceleration(dev, vel, vel->last_velocity, threshold,
79535c4bbdfSmrg                                     acc);
79635c4bbdfSmrg        result +=
79735c4bbdfSmrg            4.0f * BasicComputeAcceleration(dev, vel,
79835c4bbdfSmrg                                            (vel->last_velocity +
79935c4bbdfSmrg                                             vel->velocity) / 2,
80035c4bbdfSmrg                                            threshold,
80135c4bbdfSmrg                                            acc);
80235c4bbdfSmrg        result /= 6.0f;
80335c4bbdfSmrg        DebugAccelF("profile average [%.2f ... %.2f] is %.3f\n",
80435c4bbdfSmrg                    vel->velocity, vel->last_velocity, result);
80535c4bbdfSmrg    }
80635c4bbdfSmrg    else {
80735c4bbdfSmrg        result = BasicComputeAcceleration(dev, vel,
80835c4bbdfSmrg                                          vel->velocity, threshold, acc);
80935c4bbdfSmrg        DebugAccelF("profile sample [%.2f] is %.3f\n",
81035c4bbdfSmrg                    vel->velocity, result);
8114642e01fSmrg    }
8124642e01fSmrg
81335c4bbdfSmrg    return result;
81435c4bbdfSmrg}
8154642e01fSmrg
8164642e01fSmrg/*****************************************
8174642e01fSmrg *  Acceleration functions and profiles
8184642e01fSmrg ****************************************/
8194642e01fSmrg
8204642e01fSmrg/**
8214642e01fSmrg * Polynomial function similar previous one, but with f(1) = 1
8224642e01fSmrg */
82335c4bbdfSmrgstatic double
82435c4bbdfSmrgPolynomialAccelerationProfile(DeviceIntPtr dev,
82535c4bbdfSmrg                              DeviceVelocityPtr vel,
82635c4bbdfSmrg                              double velocity, double ignored, double acc)
8274642e01fSmrg{
82835c4bbdfSmrg    return pow(velocity, (acc - 1.0) * 0.5);
8294642e01fSmrg}
8304642e01fSmrg
8314642e01fSmrg/**
8324642e01fSmrg * returns acceleration for velocity.
8334642e01fSmrg * This profile selects the two functions like the old scheme did
8344642e01fSmrg */
83535c4bbdfSmrgstatic double
83635c4bbdfSmrgClassicProfile(DeviceIntPtr dev,
83735c4bbdfSmrg               DeviceVelocityPtr vel,
83835c4bbdfSmrg               double velocity, double threshold, double acc)
8394642e01fSmrg{
8406747b715Smrg    if (threshold > 0) {
84135c4bbdfSmrg        return SimpleSmoothProfile(dev, vel, velocity, threshold, acc);
84235c4bbdfSmrg    }
84335c4bbdfSmrg    else {
84435c4bbdfSmrg        return PolynomialAccelerationProfile(dev, vel, velocity, 0, acc);
8454642e01fSmrg    }
8464642e01fSmrg}
8474642e01fSmrg
8484642e01fSmrg/**
8494642e01fSmrg * Power profile
8504642e01fSmrg * This has a completely smooth transition curve, i.e. no jumps in the
8514642e01fSmrg * derivatives.
8524642e01fSmrg *
8534642e01fSmrg * This has the expense of overall response dependency on min-acceleration.
8544642e01fSmrg * In effect, min_acceleration mimics const_acceleration in this profile.
8554642e01fSmrg */
85635c4bbdfSmrgstatic double
85735c4bbdfSmrgPowerProfile(DeviceIntPtr dev,
85835c4bbdfSmrg             DeviceVelocityPtr vel,
85935c4bbdfSmrg             double velocity, double threshold, double acc)
8604642e01fSmrg{
86135c4bbdfSmrg    double vel_dist;
8624642e01fSmrg
86335c4bbdfSmrg    acc = (acc - 1.0) * 0.1f + 1.0;     /* without this, acc of 2 is unuseable */
8644642e01fSmrg
8654642e01fSmrg    if (velocity <= threshold)
8666747b715Smrg        return vel->min_acceleration;
8674642e01fSmrg    vel_dist = velocity - threshold;
8686747b715Smrg    return (pow(acc, vel_dist)) * vel->min_acceleration;
8694642e01fSmrg}
8704642e01fSmrg
8714642e01fSmrg/**
8724642e01fSmrg * just a smooth function in [0..1] -> [0..1]
8734642e01fSmrg *  - point symmetry at 0.5
8744642e01fSmrg *  - f'(0) = f'(1) = 0
8754642e01fSmrg *  - starts faster than a sinoid
8764642e01fSmrg *  - smoothness C1 (Cinf if you dare to ignore endpoints)
8774642e01fSmrg */
87835c4bbdfSmrgstatic inline double
87935c4bbdfSmrgCalcPenumbralGradient(double x)
88035c4bbdfSmrg{
8814642e01fSmrg    x *= 2.0f;
8824642e01fSmrg    x -= 1.0f;
88335c4bbdfSmrg    return 0.5f + (x * sqrt(1.0 - x * x) + asin(x)) / M_PI;
8844642e01fSmrg}
8854642e01fSmrg
8864642e01fSmrg/**
8874642e01fSmrg * acceleration function similar to classic accelerated/unaccelerated,
8884642e01fSmrg * but with smooth transition in between (and towards zero for adaptive dec.).
8894642e01fSmrg */
89035c4bbdfSmrgstatic double
89135c4bbdfSmrgSimpleSmoothProfile(DeviceIntPtr dev,
89235c4bbdfSmrg                    DeviceVelocityPtr vel,
89335c4bbdfSmrg                    double velocity, double threshold, double acc)
8944642e01fSmrg{
89535c4bbdfSmrg    if (velocity < 1.0f)
89635c4bbdfSmrg        return CalcPenumbralGradient(0.5 + velocity * 0.5) * 2.0f - 1.0f;
89735c4bbdfSmrg    if (threshold < 1.0f)
8984642e01fSmrg        threshold = 1.0f;
8994642e01fSmrg    if (velocity <= threshold)
9004642e01fSmrg        return 1;
9014642e01fSmrg    velocity /= threshold;
9024642e01fSmrg    if (velocity >= acc)
9034642e01fSmrg        return acc;
9044642e01fSmrg    else
90535c4bbdfSmrg        return 1.0f + (CalcPenumbralGradient(velocity / acc) * (acc - 1.0f));
9064642e01fSmrg}
9074642e01fSmrg
9084642e01fSmrg/**
9094642e01fSmrg * This profile uses the first half of the penumbral gradient as a start
9104642e01fSmrg * and then scales linearly.
9114642e01fSmrg */
91235c4bbdfSmrgstatic double
91335c4bbdfSmrgSmoothLinearProfile(DeviceIntPtr dev,
91435c4bbdfSmrg                    DeviceVelocityPtr vel,
91535c4bbdfSmrg                    double velocity, double threshold, double acc)
9164642e01fSmrg{
91735c4bbdfSmrg    double res, nv;
9184642e01fSmrg
91935c4bbdfSmrg    if (acc > 1.0f)
92035c4bbdfSmrg        acc -= 1.0f;            /*this is so acc = 1 is no acceleration */
9214642e01fSmrg    else
9224642e01fSmrg        return 1.0f;
9234642e01fSmrg
9244642e01fSmrg    nv = (velocity - threshold) * acc * 0.5f;
9254642e01fSmrg
92635c4bbdfSmrg    if (nv < 0) {
9274642e01fSmrg        res = 0;
92835c4bbdfSmrg    }
92935c4bbdfSmrg    else if (nv < 2) {
93035c4bbdfSmrg        res = CalcPenumbralGradient(nv * 0.25f) * 2.0f;
93135c4bbdfSmrg    }
93235c4bbdfSmrg    else {
9334642e01fSmrg        nv -= 2.0f;
9344642e01fSmrg        res = nv * 2.0f / M_PI  /* steepness of gradient at 0.5 */
93535c4bbdfSmrg            + 1.0f;             /* gradient crosses 2|1 */
9364642e01fSmrg    }
9376747b715Smrg    res += vel->min_acceleration;
9384642e01fSmrg    return res;
9394642e01fSmrg}
9404642e01fSmrg
9416747b715Smrg/**
9426747b715Smrg * From 0 to threshold, the response graduates smoothly from min_accel to
9436747b715Smrg * acceleration. Beyond threshold it is exactly the specified acceleration.
9446747b715Smrg */
94535c4bbdfSmrgstatic double
94635c4bbdfSmrgSmoothLimitedProfile(DeviceIntPtr dev,
94735c4bbdfSmrg                     DeviceVelocityPtr vel,
94835c4bbdfSmrg                     double velocity, double threshold, double acc)
9496747b715Smrg{
95035c4bbdfSmrg    double res;
9516747b715Smrg
95235c4bbdfSmrg    if (velocity >= threshold || threshold == 0.0f)
95335c4bbdfSmrg        return acc;
9546747b715Smrg
95535c4bbdfSmrg    velocity /= threshold;      /* should be [0..1[ now */
9566747b715Smrg
9576747b715Smrg    res = CalcPenumbralGradient(velocity) * (acc - vel->min_acceleration);
9586747b715Smrg
9596747b715Smrg    return vel->min_acceleration + res;
9606747b715Smrg}
9616747b715Smrg
96235c4bbdfSmrgstatic double
96335c4bbdfSmrgLinearProfile(DeviceIntPtr dev,
96435c4bbdfSmrg              DeviceVelocityPtr vel,
96535c4bbdfSmrg              double velocity, double threshold, double acc)
9664642e01fSmrg{
9674642e01fSmrg    return acc * velocity;
9684642e01fSmrg}
9694642e01fSmrg
97035c4bbdfSmrgstatic double
97135c4bbdfSmrgNoProfile(DeviceIntPtr dev,
97235c4bbdfSmrg          DeviceVelocityPtr vel, double velocity, double threshold, double acc)
9736747b715Smrg{
9746747b715Smrg    return 1.0f;
9756747b715Smrg}
9764642e01fSmrg
9776747b715Smrgstatic PointerAccelerationProfileFunc
97835c4bbdfSmrgGetAccelerationProfile(DeviceVelocityPtr vel, int profile_num)
9794642e01fSmrg{
98035c4bbdfSmrg    switch (profile_num) {
98135c4bbdfSmrg    case AccelProfileClassic:
98235c4bbdfSmrg        return ClassicProfile;
98335c4bbdfSmrg    case AccelProfileDeviceSpecific:
98435c4bbdfSmrg        return vel->deviceSpecificProfile;
98535c4bbdfSmrg    case AccelProfilePolynomial:
98635c4bbdfSmrg        return PolynomialAccelerationProfile;
98735c4bbdfSmrg    case AccelProfileSmoothLinear:
98835c4bbdfSmrg        return SmoothLinearProfile;
98935c4bbdfSmrg    case AccelProfileSimple:
99035c4bbdfSmrg        return SimpleSmoothProfile;
99135c4bbdfSmrg    case AccelProfilePower:
99235c4bbdfSmrg        return PowerProfile;
99335c4bbdfSmrg    case AccelProfileLinear:
99435c4bbdfSmrg        return LinearProfile;
99535c4bbdfSmrg    case AccelProfileSmoothLimited:
99635c4bbdfSmrg        return SmoothLimitedProfile;
99735c4bbdfSmrg    case AccelProfileNone:
99835c4bbdfSmrg        return NoProfile;
99935c4bbdfSmrg    default:
100035c4bbdfSmrg        return NULL;
10014642e01fSmrg    }
10026747b715Smrg}
10036747b715Smrg
10046747b715Smrg/**
10056747b715Smrg * Set the profile by number.
10066747b715Smrg * Intended to make profiles exchangeable at runtime.
10076747b715Smrg * If you created a profile, give it a number here and in the header to
10086747b715Smrg * make it selectable. In case some profile-specific init is needed, here
10096747b715Smrg * would be a good place, since FreeVelocityData() also calls this with
10106747b715Smrg * PROFILE_UNINITIALIZE.
10116747b715Smrg *
10126747b715Smrg * returns FALSE if profile number is unavailable, TRUE otherwise.
10136747b715Smrg */
10146747b715Smrgint
101535c4bbdfSmrgSetAccelerationProfile(DeviceVelocityPtr vel, int profile_num)
10166747b715Smrg{
10176747b715Smrg    PointerAccelerationProfileFunc profile;
101835c4bbdfSmrg
10196747b715Smrg    profile = GetAccelerationProfile(vel, profile_num);
10206747b715Smrg
102135c4bbdfSmrg    if (profile == NULL && profile_num != PROFILE_UNINITIALIZE)
102235c4bbdfSmrg        return FALSE;
10236747b715Smrg
10249ace9065Smrg    /* Here one could free old profile-private data */
10259ace9065Smrg    free(vel->profile_private);
10269ace9065Smrg    vel->profile_private = NULL;
10274642e01fSmrg    /* Here one could init profile-private data */
10286747b715Smrg    vel->Profile = profile;
10296747b715Smrg    vel->statistics.profile_number = profile_num;
10304642e01fSmrg    return TRUE;
10314642e01fSmrg}
10324642e01fSmrg
10334642e01fSmrg/**********************************************
10344642e01fSmrg * driver interaction
10354642e01fSmrg **********************************************/
10364642e01fSmrg
10374642e01fSmrg/**
10384642e01fSmrg * device-specific profile
10394642e01fSmrg *
10404642e01fSmrg * The device-specific profile is intended as a hook for a driver
10414642e01fSmrg * which may want to provide an own acceleration profile.
10424642e01fSmrg * It should not rely on profile-private data, instead
10434642e01fSmrg * it should do init/uninit in the driver (ie. with DEVICE_INIT and friends).
10444642e01fSmrg * Users may override or choose it.
10454642e01fSmrg */
10466747b715Smrgvoid
104735c4bbdfSmrgSetDeviceSpecificAccelerationProfile(DeviceVelocityPtr vel,
104835c4bbdfSmrg                                     PointerAccelerationProfileFunc profile)
10494642e01fSmrg{
105035c4bbdfSmrg    if (vel)
105135c4bbdfSmrg        vel->deviceSpecificProfile = profile;
10524642e01fSmrg}
10534642e01fSmrg
10544642e01fSmrg/**
10554642e01fSmrg * Use this function to obtain a DeviceVelocityPtr for a device. Will return NULL if
10564642e01fSmrg * the predictable acceleration scheme is not in effect.
10574642e01fSmrg */
10586747b715SmrgDeviceVelocityPtr
105935c4bbdfSmrgGetDevicePredictableAccelData(DeviceIntPtr dev)
10604642e01fSmrg{
106135c4bbdfSmrg    BUG_RETURN_VAL(!dev, NULL);
10624642e01fSmrg
106335c4bbdfSmrg    if (dev->valuator &&
106435c4bbdfSmrg        dev->valuator->accelScheme.AccelSchemeProc ==
106535c4bbdfSmrg        acceleratePointerPredictable &&
106635c4bbdfSmrg        dev->valuator->accelScheme.accelData != NULL) {
106735c4bbdfSmrg
106835c4bbdfSmrg        return ((PredictableAccelSchemePtr)
106935c4bbdfSmrg                dev->valuator->accelScheme.accelData)->vel;
10704642e01fSmrg    }
10714642e01fSmrg    return NULL;
10724642e01fSmrg}
10734642e01fSmrg
10744642e01fSmrg/********************************
10754642e01fSmrg *  acceleration schemes
10764642e01fSmrg *******************************/
10774642e01fSmrg
10784642e01fSmrg/**
10794642e01fSmrg * Modifies valuators in-place.
10804642e01fSmrg * This version employs a velocity approximation algorithm to
10814642e01fSmrg * enable fine-grained predictable acceleration profiles.
10824642e01fSmrg */
10834642e01fSmrgvoid
108435c4bbdfSmrgacceleratePointerPredictable(DeviceIntPtr dev, ValuatorMask *val, CARD32 evtime)
10854642e01fSmrg{
108635c4bbdfSmrg    double dx = 0, dy = 0;
108735c4bbdfSmrg    DeviceVelocityPtr velocitydata = GetDevicePredictableAccelData(dev);
10886747b715Smrg    Bool soften = TRUE;
10894642e01fSmrg
109035c4bbdfSmrg    if (valuator_mask_num_valuators(val) == 0 || !velocitydata)
10914642e01fSmrg        return;
10924642e01fSmrg
10936747b715Smrg    if (velocitydata->statistics.profile_number == AccelProfileNone &&
109435c4bbdfSmrg        velocitydata->const_acceleration == 1.0f) {
109535c4bbdfSmrg        return;                 /*we're inactive anyway, so skip the whole thing. */
10966747b715Smrg    }
10976747b715Smrg
109835c4bbdfSmrg    if (valuator_mask_isset(val, 0)) {
109935c4bbdfSmrg        dx = valuator_mask_get_double(val, 0);
11004642e01fSmrg    }
110135c4bbdfSmrg
110235c4bbdfSmrg    if (valuator_mask_isset(val, 1)) {
110335c4bbdfSmrg        dy = valuator_mask_get_double(val, 1);
11044642e01fSmrg    }
11054642e01fSmrg
110635c4bbdfSmrg    if (dx != 0.0 || dy != 0.0) {
11076747b715Smrg        /* reset non-visible state? */
110835c4bbdfSmrg        if (ProcessVelocityData2D(velocitydata, dx, dy, evtime)) {
11096747b715Smrg            soften = FALSE;
11104642e01fSmrg        }
11114642e01fSmrg
11126747b715Smrg        if (dev->ptrfeed && dev->ptrfeed->ctrl.num) {
111335c4bbdfSmrg            double mult;
111435c4bbdfSmrg
11154642e01fSmrg            /* invoke acceleration profile to determine acceleration */
111635c4bbdfSmrg            mult = ComputeAcceleration(dev, velocitydata,
111735c4bbdfSmrg                                       dev->ptrfeed->ctrl.threshold,
111835c4bbdfSmrg                                       (double) dev->ptrfeed->ctrl.num /
111935c4bbdfSmrg                                       (double) dev->ptrfeed->ctrl.den);
112035c4bbdfSmrg
112135c4bbdfSmrg            DebugAccelF("mult is %f\n", mult);
112235c4bbdfSmrg            if (mult != 1.0f || velocitydata->const_acceleration != 1.0f) {
112335c4bbdfSmrg                if (mult > 1.0f && soften)
112435c4bbdfSmrg                    ApplySoftening(velocitydata, &dx, &dy);
112535c4bbdfSmrg                ApplyConstantDeceleration(velocitydata, &dx, &dy);
112635c4bbdfSmrg
112735c4bbdfSmrg                if (dx != 0.0)
112835c4bbdfSmrg                    valuator_mask_set_double(val, 0, mult * dx);
112935c4bbdfSmrg                if (dy != 0.0)
113035c4bbdfSmrg                    valuator_mask_set_double(val, 1, mult * dy);
113135c4bbdfSmrg                DebugAccelF("delta x:%.3f y:%.3f\n", mult * dx, mult * dy);
11324642e01fSmrg            }
11334642e01fSmrg        }
11344642e01fSmrg    }
11354642e01fSmrg    /* remember last motion delta (for softening/slow movement treatment) */
11364642e01fSmrg    velocitydata->last_dx = dx;
11374642e01fSmrg    velocitydata->last_dy = dy;
11384642e01fSmrg}
11394642e01fSmrg
11404642e01fSmrg/**
11414642e01fSmrg * Originally a part of xf86PostMotionEvent; modifies valuators
11424642e01fSmrg * in-place. Retained mostly for embedded scenarios.
11434642e01fSmrg */
11444642e01fSmrgvoid
114535c4bbdfSmrgacceleratePointerLightweight(DeviceIntPtr dev,
114635c4bbdfSmrg                             ValuatorMask *val, CARD32 ignored)
11474642e01fSmrg{
114835c4bbdfSmrg    double mult = 0.0, tmpf;
114935c4bbdfSmrg    double dx = 0.0, dy = 0.0;
11504642e01fSmrg
115135c4bbdfSmrg    if (valuator_mask_isset(val, 0)) {
115235c4bbdfSmrg        dx = valuator_mask_get(val, 0);
11534642e01fSmrg    }
115435c4bbdfSmrg
115535c4bbdfSmrg    if (valuator_mask_isset(val, 1)) {
115635c4bbdfSmrg        dy = valuator_mask_get(val, 1);
11574642e01fSmrg    }
11584642e01fSmrg
115935c4bbdfSmrg    if (valuator_mask_num_valuators(val) == 0)
11604642e01fSmrg        return;
11614642e01fSmrg
11626747b715Smrg    if (dev->ptrfeed && dev->ptrfeed->ctrl.num) {
11634642e01fSmrg        /* modeled from xf86Events.c */
11646747b715Smrg        if (dev->ptrfeed->ctrl.threshold) {
116535c4bbdfSmrg            if ((fabs(dx) + fabs(dy)) >= dev->ptrfeed->ctrl.threshold) {
116635c4bbdfSmrg                if (dx != 0.0) {
116735c4bbdfSmrg                    tmpf = (dx * (double) (dev->ptrfeed->ctrl.num)) /
116835c4bbdfSmrg                        (double) (dev->ptrfeed->ctrl.den);
116935c4bbdfSmrg                    valuator_mask_set_double(val, 0, tmpf);
11704642e01fSmrg                }
11714642e01fSmrg
117235c4bbdfSmrg                if (dy != 0.0) {
117335c4bbdfSmrg                    tmpf = (dy * (double) (dev->ptrfeed->ctrl.num)) /
117435c4bbdfSmrg                        (double) (dev->ptrfeed->ctrl.den);
117535c4bbdfSmrg                    valuator_mask_set_double(val, 1, tmpf);
11764642e01fSmrg                }
11774642e01fSmrg            }
11784642e01fSmrg        }
11794642e01fSmrg        else {
118035c4bbdfSmrg            mult = pow(dx * dx + dy * dy,
118135c4bbdfSmrg                       ((double) (dev->ptrfeed->ctrl.num) /
118235c4bbdfSmrg                        (double) (dev->ptrfeed->ctrl.den) - 1.0) / 2.0) / 2.0;
118335c4bbdfSmrg            if (dx != 0.0)
118435c4bbdfSmrg                valuator_mask_set_double(val, 0, mult * dx);
118535c4bbdfSmrg            if (dy != 0.0)
118635c4bbdfSmrg                valuator_mask_set_double(val, 1, mult * dy);
11874642e01fSmrg        }
11884642e01fSmrg    }
11894642e01fSmrg}
1190