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
100ed6184dfSmrg    vel->corr_mul = 10.0;       /* dots per 10 millisecond 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)) {
14658cf2af7Smrg        FreeVelocityData(vel);
14735c4bbdfSmrg        free(vel);
14835c4bbdfSmrg        free(schemeData);
14935c4bbdfSmrg        return FALSE;
15035c4bbdfSmrg    }
15135c4bbdfSmrg    /* all fine, assign scheme to device */
15235c4bbdfSmrg    dev->valuator->accelScheme = scheme;
15335c4bbdfSmrg    return TRUE;
15435c4bbdfSmrg}
1554642e01fSmrg
15635c4bbdfSmrg/**
15735c4bbdfSmrg *  Uninit scheme
1584642e01fSmrg */
1594642e01fSmrgvoid
1606747b715SmrgAccelerationDefaultCleanup(DeviceIntPtr dev)
1614642e01fSmrg{
16235c4bbdfSmrg    DeviceVelocityPtr vel = GetDevicePredictableAccelData(dev);
16335c4bbdfSmrg
16435c4bbdfSmrg    if (vel) {
16535c4bbdfSmrg        /* the proper guarantee would be that we're not inside of
16635c4bbdfSmrg         * AccelSchemeProc(), but that seems impossible. Schemes don't get
16735c4bbdfSmrg         * switched often anyway.
16835c4bbdfSmrg         */
1691b5d61b8Smrg        input_lock();
1706747b715Smrg        dev->valuator->accelScheme.AccelSchemeProc = NULL;
17135c4bbdfSmrg        FreeVelocityData(vel);
17235c4bbdfSmrg        free(vel);
17335c4bbdfSmrg        DeletePredictableAccelerationProperties(dev,
17435c4bbdfSmrg                                                (PredictableAccelSchemePtr)
17535c4bbdfSmrg                                                dev->valuator->accelScheme.
17635c4bbdfSmrg                                                accelData);
1776747b715Smrg        free(dev->valuator->accelScheme.accelData);
1786747b715Smrg        dev->valuator->accelScheme.accelData = NULL;
1791b5d61b8Smrg        input_unlock();
1804642e01fSmrg    }
1814642e01fSmrg}
1824642e01fSmrg
1836747b715Smrg/*************************
1846747b715Smrg * Input property support
1856747b715Smrg ************************/
1864642e01fSmrg
1874642e01fSmrg/**
1886747b715Smrg * choose profile
1896747b715Smrg */
1906747b715Smrgstatic int
1916747b715SmrgAccelSetProfileProperty(DeviceIntPtr dev, Atom atom,
1926747b715Smrg                        XIPropertyValuePtr val, BOOL checkOnly)
1934642e01fSmrg{
1946747b715Smrg    DeviceVelocityPtr vel;
1956747b715Smrg    int profile, *ptr = &profile;
1966747b715Smrg    int rc;
1976747b715Smrg    int nelem = 1;
1986747b715Smrg
1996747b715Smrg    if (atom != XIGetKnownProperty(ACCEL_PROP_PROFILE_NUMBER))
2006747b715Smrg        return Success;
2016747b715Smrg
2026747b715Smrg    vel = GetDevicePredictableAccelData(dev);
2036747b715Smrg    if (!vel)
2046747b715Smrg        return BadValue;
2056747b715Smrg    rc = XIPropToInt(val, &nelem, &ptr);
2066747b715Smrg
20735c4bbdfSmrg    if (checkOnly) {
2086747b715Smrg        if (rc)
2096747b715Smrg            return rc;
2106747b715Smrg
2116747b715Smrg        if (GetAccelerationProfile(vel, profile) == NULL)
2126747b715Smrg            return BadValue;
21335c4bbdfSmrg    }
21435c4bbdfSmrg    else
21535c4bbdfSmrg        SetAccelerationProfile(vel, profile);
2166747b715Smrg
2176747b715Smrg    return Success;
2184642e01fSmrg}
2194642e01fSmrg
2206747b715Smrgstatic long
2216747b715SmrgAccelInitProfileProperty(DeviceIntPtr dev, DeviceVelocityPtr vel)
2224642e01fSmrg{
2236747b715Smrg    int profile = vel->statistics.profile_number;
2246747b715Smrg    Atom prop_profile_number = XIGetKnownProperty(ACCEL_PROP_PROFILE_NUMBER);
2254642e01fSmrg
2266747b715Smrg    XIChangeDeviceProperty(dev, prop_profile_number, XA_INTEGER, 32,
2276747b715Smrg                           PropModeReplace, 1, &profile, FALSE);
2286747b715Smrg    XISetDevicePropertyDeletable(dev, prop_profile_number, FALSE);
2296747b715Smrg    return XIRegisterPropertyHandler(dev, AccelSetProfileProperty, NULL, NULL);
2304642e01fSmrg}
2314642e01fSmrg
2326747b715Smrg/**
2336747b715Smrg * constant deceleration
2346747b715Smrg */
2356747b715Smrgstatic int
2366747b715SmrgAccelSetDecelProperty(DeviceIntPtr dev, Atom atom,
2376747b715Smrg                      XIPropertyValuePtr val, BOOL checkOnly)
2384642e01fSmrg{
2396747b715Smrg    DeviceVelocityPtr vel;
2406747b715Smrg    float v, *ptr = &v;
2416747b715Smrg    int rc;
2426747b715Smrg    int nelem = 1;
2436747b715Smrg
2446747b715Smrg    if (atom != XIGetKnownProperty(ACCEL_PROP_CONSTANT_DECELERATION))
2456747b715Smrg        return Success;
2466747b715Smrg
2476747b715Smrg    vel = GetDevicePredictableAccelData(dev);
2486747b715Smrg    if (!vel)
2496747b715Smrg        return BadValue;
2506747b715Smrg    rc = XIPropToFloat(val, &nelem, &ptr);
2516747b715Smrg
25235c4bbdfSmrg    if (checkOnly) {
2536747b715Smrg        if (rc)
2546747b715Smrg            return rc;
25535c4bbdfSmrg        return (v > 0) ? Success : BadValue;
2564642e01fSmrg    }
2576747b715Smrg
25835c4bbdfSmrg    vel->const_acceleration = 1 / v;
2596747b715Smrg
2606747b715Smrg    return Success;
2616747b715Smrg}
2626747b715Smrg
2636747b715Smrgstatic long
2646747b715SmrgAccelInitDecelProperty(DeviceIntPtr dev, DeviceVelocityPtr vel)
2656747b715Smrg{
26635c4bbdfSmrg    float fval = 1.0 / vel->const_acceleration;
26735c4bbdfSmrg    Atom prop_const_decel =
26835c4bbdfSmrg        XIGetKnownProperty(ACCEL_PROP_CONSTANT_DECELERATION);
2696747b715Smrg    XIChangeDeviceProperty(dev, prop_const_decel,
27035c4bbdfSmrg                           XIGetKnownProperty(XATOM_FLOAT), 32, PropModeReplace,
27135c4bbdfSmrg                           1, &fval, FALSE);
2726747b715Smrg    XISetDevicePropertyDeletable(dev, prop_const_decel, FALSE);
2736747b715Smrg    return XIRegisterPropertyHandler(dev, AccelSetDecelProperty, NULL, NULL);
2744642e01fSmrg}
2754642e01fSmrg
2764642e01fSmrg/**
2776747b715Smrg * adaptive deceleration
2784642e01fSmrg */
2796747b715Smrgstatic int
2806747b715SmrgAccelSetAdaptDecelProperty(DeviceIntPtr dev, Atom atom,
2816747b715Smrg                           XIPropertyValuePtr val, BOOL checkOnly)
2824642e01fSmrg{
2836747b715Smrg    DeviceVelocityPtr veloc;
2846747b715Smrg    float v, *ptr = &v;
2856747b715Smrg    int rc;
2866747b715Smrg    int nelem = 1;
2876747b715Smrg
2886747b715Smrg    if (atom != XIGetKnownProperty(ACCEL_PROP_ADAPTIVE_DECELERATION))
2896747b715Smrg        return Success;
2906747b715Smrg
2916747b715Smrg    veloc = GetDevicePredictableAccelData(dev);
2926747b715Smrg    if (!veloc)
2936747b715Smrg        return BadValue;
2946747b715Smrg    rc = XIPropToFloat(val, &nelem, &ptr);
2956747b715Smrg
29635c4bbdfSmrg    if (checkOnly) {
2976747b715Smrg        if (rc)
2986747b715Smrg            return rc;
29935c4bbdfSmrg        return (v >= 1.0f) ? Success : BadValue;
3004642e01fSmrg    }
3016747b715Smrg
30235c4bbdfSmrg    if (v >= 1.0f)
30335c4bbdfSmrg        veloc->min_acceleration = 1 / v;
3046747b715Smrg
3056747b715Smrg    return Success;
3064642e01fSmrg}
3074642e01fSmrg
3086747b715Smrgstatic long
3096747b715SmrgAccelInitAdaptDecelProperty(DeviceIntPtr dev, DeviceVelocityPtr vel)
3106747b715Smrg{
31135c4bbdfSmrg    float fval = 1.0 / vel->min_acceleration;
31235c4bbdfSmrg    Atom prop_adapt_decel =
31335c4bbdfSmrg        XIGetKnownProperty(ACCEL_PROP_ADAPTIVE_DECELERATION);
3144642e01fSmrg
31535c4bbdfSmrg    XIChangeDeviceProperty(dev, prop_adapt_decel,
31635c4bbdfSmrg                           XIGetKnownProperty(XATOM_FLOAT), 32, PropModeReplace,
31735c4bbdfSmrg                           1, &fval, FALSE);
3186747b715Smrg    XISetDevicePropertyDeletable(dev, prop_adapt_decel, FALSE);
31935c4bbdfSmrg    return XIRegisterPropertyHandler(dev, AccelSetAdaptDecelProperty, NULL,
32035c4bbdfSmrg                                     NULL);
3216747b715Smrg}
3226747b715Smrg
3236747b715Smrg/**
3246747b715Smrg * velocity scaling
3256747b715Smrg */
3266747b715Smrgstatic int
3276747b715SmrgAccelSetScaleProperty(DeviceIntPtr dev, Atom atom,
3286747b715Smrg                      XIPropertyValuePtr val, BOOL checkOnly)
3294642e01fSmrg{
3306747b715Smrg    DeviceVelocityPtr vel;
3316747b715Smrg    float v, *ptr = &v;
3326747b715Smrg    int rc;
3336747b715Smrg    int nelem = 1;
3346747b715Smrg
3356747b715Smrg    if (atom != XIGetKnownProperty(ACCEL_PROP_VELOCITY_SCALING))
3366747b715Smrg        return Success;
3374642e01fSmrg
3386747b715Smrg    vel = GetDevicePredictableAccelData(dev);
3396747b715Smrg    if (!vel)
3406747b715Smrg        return BadValue;
3416747b715Smrg    rc = XIPropToFloat(val, &nelem, &ptr);
3426747b715Smrg
34335c4bbdfSmrg    if (checkOnly) {
3446747b715Smrg        if (rc)
3456747b715Smrg            return rc;
3466747b715Smrg
3476747b715Smrg        return (v > 0) ? Success : BadValue;
3484642e01fSmrg    }
3496747b715Smrg
35035c4bbdfSmrg    if (v > 0)
35135c4bbdfSmrg        vel->corr_mul = v;
3526747b715Smrg
3536747b715Smrg    return Success;
3544642e01fSmrg}
3554642e01fSmrg
3566747b715Smrgstatic long
3576747b715SmrgAccelInitScaleProperty(DeviceIntPtr dev, DeviceVelocityPtr vel)
3586747b715Smrg{
3596747b715Smrg    float fval = vel->corr_mul;
3606747b715Smrg    Atom prop_velo_scale = XIGetKnownProperty(ACCEL_PROP_VELOCITY_SCALING);
3614642e01fSmrg
36235c4bbdfSmrg    XIChangeDeviceProperty(dev, prop_velo_scale,
36335c4bbdfSmrg                           XIGetKnownProperty(XATOM_FLOAT), 32, PropModeReplace,
36435c4bbdfSmrg                           1, &fval, FALSE);
3656747b715Smrg    XISetDevicePropertyDeletable(dev, prop_velo_scale, FALSE);
3666747b715Smrg    return XIRegisterPropertyHandler(dev, AccelSetScaleProperty, NULL, NULL);
3674642e01fSmrg}
3684642e01fSmrg
36935c4bbdfSmrgstatic BOOL
37035c4bbdfSmrgInitializePredictableAccelerationProperties(DeviceIntPtr dev,
37135c4bbdfSmrg                                            DeviceVelocityPtr vel,
37235c4bbdfSmrg                                            PredictableAccelSchemePtr
37335c4bbdfSmrg                                            schemeData)
3744642e01fSmrg{
37535c4bbdfSmrg    int num_handlers = 4;
3764642e01fSmrg
37735c4bbdfSmrg    if (!vel)
37835c4bbdfSmrg        return FALSE;
3794642e01fSmrg
38035c4bbdfSmrg    schemeData->prop_handlers = calloc(num_handlers, sizeof(long));
38135c4bbdfSmrg    if (!schemeData->prop_handlers)
38235c4bbdfSmrg        return FALSE;
38335c4bbdfSmrg    schemeData->num_prop_handlers = num_handlers;
38435c4bbdfSmrg    schemeData->prop_handlers[0] = AccelInitProfileProperty(dev, vel);
38535c4bbdfSmrg    schemeData->prop_handlers[1] = AccelInitDecelProperty(dev, vel);
38635c4bbdfSmrg    schemeData->prop_handlers[2] = AccelInitAdaptDecelProperty(dev, vel);
38735c4bbdfSmrg    schemeData->prop_handlers[3] = AccelInitScaleProperty(dev, vel);
3884642e01fSmrg
3896747b715Smrg    return TRUE;
3906747b715Smrg}
3914642e01fSmrg
3926747b715SmrgBOOL
39335c4bbdfSmrgDeletePredictableAccelerationProperties(DeviceIntPtr dev,
39435c4bbdfSmrg                                        PredictableAccelSchemePtr scheme)
3956747b715Smrg{
39635c4bbdfSmrg    DeviceVelocityPtr vel;
3976747b715Smrg    Atom prop;
3986747b715Smrg    int i;
3996747b715Smrg
4006747b715Smrg    prop = XIGetKnownProperty(ACCEL_PROP_VELOCITY_SCALING);
4016747b715Smrg    XIDeleteDeviceProperty(dev, prop, FALSE);
4026747b715Smrg    prop = XIGetKnownProperty(ACCEL_PROP_ADAPTIVE_DECELERATION);
4036747b715Smrg    XIDeleteDeviceProperty(dev, prop, FALSE);
4046747b715Smrg    prop = XIGetKnownProperty(ACCEL_PROP_CONSTANT_DECELERATION);
4056747b715Smrg    XIDeleteDeviceProperty(dev, prop, FALSE);
4066747b715Smrg    prop = XIGetKnownProperty(ACCEL_PROP_PROFILE_NUMBER);
4076747b715Smrg    XIDeleteDeviceProperty(dev, prop, FALSE);
4086747b715Smrg
4096747b715Smrg    vel = GetDevicePredictableAccelData(dev);
41035c4bbdfSmrg    if (vel) {
41135c4bbdfSmrg        for (i = 0; i < scheme->num_prop_handlers; i++)
41235c4bbdfSmrg            if (scheme->prop_handlers[i])
41335c4bbdfSmrg                XIUnregisterPropertyHandler(dev, scheme->prop_handlers[i]);
41435c4bbdfSmrg    }
4154642e01fSmrg
41635c4bbdfSmrg    free(scheme->prop_handlers);
41735c4bbdfSmrg    scheme->prop_handlers = NULL;
41835c4bbdfSmrg    scheme->num_prop_handlers = 0;
4196747b715Smrg    return TRUE;
4204642e01fSmrg}
4214642e01fSmrg
4226747b715Smrg/*********************
4236747b715Smrg * Tracking logic
4246747b715Smrg ********************/
4254642e01fSmrg
4266747b715Smrgvoid
4276747b715SmrgInitTrackers(DeviceVelocityPtr vel, int ntracker)
4286747b715Smrg{
42935c4bbdfSmrg    if (ntracker < 1) {
43035c4bbdfSmrg        ErrorF("invalid number of trackers\n");
43135c4bbdfSmrg        return;
4324642e01fSmrg    }
4336747b715Smrg    free(vel->tracker);
43435c4bbdfSmrg    vel->tracker = (MotionTrackerPtr) calloc(ntracker, sizeof(MotionTracker));
4356747b715Smrg    vel->num_tracker = ntracker;
4364642e01fSmrg}
4374642e01fSmrg
43835c4bbdfSmrgenum directions {
43935c4bbdfSmrg    N = (1 << 0),
44035c4bbdfSmrg    NE = (1 << 1),
44135c4bbdfSmrg    E = (1 << 2),
44235c4bbdfSmrg    SE = (1 << 3),
44335c4bbdfSmrg    S = (1 << 4),
44435c4bbdfSmrg    SW = (1 << 5),
44535c4bbdfSmrg    W = (1 << 6),
44635c4bbdfSmrg    NW = (1 << 7),
44735c4bbdfSmrg    UNDEFINED = 0xFF
44835c4bbdfSmrg};
44935c4bbdfSmrg
4504642e01fSmrg/**
4516747b715Smrg * return a bit field of possible directions.
4526747b715Smrg * There's no reason against widening to more precise directions (<45 degrees),
4536747b715Smrg * should it not perform well. All this is needed for is sort out non-linear
4546747b715Smrg * motion, so precision isn't paramount. However, one should not flag direction
4556747b715Smrg * too narrow, since it would then cut the linear segment to zero size way too
4566747b715Smrg * often.
45735c4bbdfSmrg *
45835c4bbdfSmrg * @return A bitmask for N, NE, S, SE, etc. indicating the directions for
45935c4bbdfSmrg * this movement.
4604642e01fSmrg */
4616747b715Smrgstatic int
46235c4bbdfSmrgDoGetDirection(int dx, int dy)
46335c4bbdfSmrg{
46435c4bbdfSmrg    int dir = 0;
46535c4bbdfSmrg
4666747b715Smrg    /* on insignificant mickeys, flag 135 degrees */
46735c4bbdfSmrg    if (abs(dx) < 2 && abs(dy) < 2) {
46835c4bbdfSmrg        /* first check diagonal cases */
46935c4bbdfSmrg        if (dx > 0 && dy > 0)
47035c4bbdfSmrg            dir = E | SE | S;
47135c4bbdfSmrg        else if (dx > 0 && dy < 0)
47235c4bbdfSmrg            dir = N | NE | E;
47335c4bbdfSmrg        else if (dx < 0 && dy < 0)
47435c4bbdfSmrg            dir = W | NW | N;
47535c4bbdfSmrg        else if (dx < 0 && dy > 0)
47635c4bbdfSmrg            dir = W | SW | S;
4776747b715Smrg        /* check axis-aligned directions */
47835c4bbdfSmrg        else if (dx > 0)
47935c4bbdfSmrg            dir = NE | E | SE;
48035c4bbdfSmrg        else if (dx < 0)
48135c4bbdfSmrg            dir = NW | W | SW;
48235c4bbdfSmrg        else if (dy > 0)
48335c4bbdfSmrg            dir = SE | S | SW;
48435c4bbdfSmrg        else if (dy < 0)
48535c4bbdfSmrg            dir = NE | N | NW;
48635c4bbdfSmrg        else
48735c4bbdfSmrg            dir = UNDEFINED;    /* shouldn't happen */
4884642e01fSmrg    }
48935c4bbdfSmrg    else {                      /* compute angle and set appropriate flags */
49035c4bbdfSmrg        double r;
49135c4bbdfSmrg        int i1, i2;
49235c4bbdfSmrg
49335c4bbdfSmrg        r = atan2(dy, dx);
49435c4bbdfSmrg        /* find direction.
49535c4bbdfSmrg         *
49635c4bbdfSmrg         * Add 360° to avoid r become negative since C has no well-defined
49735c4bbdfSmrg         * modulo for such cases. Then divide by 45° to get the octant
49835c4bbdfSmrg         * number,  e.g.
49935c4bbdfSmrg         *          0 <= r <= 1 is [0-45]°
50035c4bbdfSmrg         *          1 <= r <= 2 is [45-90]°
50135c4bbdfSmrg         *          etc.
50235c4bbdfSmrg         * But we add extra 90° to match up with our N, S, etc. defines up
50335c4bbdfSmrg         * there, rest stays the same.
50435c4bbdfSmrg         */
50535c4bbdfSmrg        r = (r + (M_PI * 2.5)) / (M_PI / 4);
50635c4bbdfSmrg        /* this intends to flag 2 directions (45 degrees),
50735c4bbdfSmrg         * except on very well-aligned mickeys. */
50835c4bbdfSmrg        i1 = (int) (r + 0.1) % 8;
50935c4bbdfSmrg        i2 = (int) (r + 0.9) % 8;
51035c4bbdfSmrg        if (i1 < 0 || i1 > 7 || i2 < 0 || i2 > 7)
51135c4bbdfSmrg            dir = UNDEFINED;    /* shouldn't happen */
51235c4bbdfSmrg        else
51335c4bbdfSmrg            dir = (1 << i1 | 1 << i2);
51435c4bbdfSmrg    }
51535c4bbdfSmrg    return dir;
5166747b715Smrg}
5174642e01fSmrg
5186747b715Smrg#define DIRECTION_CACHE_RANGE 5
5196747b715Smrg#define DIRECTION_CACHE_SIZE (DIRECTION_CACHE_RANGE*2+1)
5206747b715Smrg
52135c4bbdfSmrg/* cache DoGetDirection().
52235c4bbdfSmrg * To avoid excessive use of direction calculation, cache the values for
523ed6184dfSmrg * [-5..5] for both x/y. Anything outside of that is calculated on the fly.
52435c4bbdfSmrg *
52535c4bbdfSmrg * @return A bitmask for N, NE, S, SE, etc. indicating the directions for
52635c4bbdfSmrg * this movement.
52735c4bbdfSmrg */
5286747b715Smrgstatic int
52935c4bbdfSmrgGetDirection(int dx, int dy)
53035c4bbdfSmrg{
5316747b715Smrg    static int cache[DIRECTION_CACHE_SIZE][DIRECTION_CACHE_SIZE];
53235c4bbdfSmrg    int dir;
53335c4bbdfSmrg
53435c4bbdfSmrg    if (abs(dx) <= DIRECTION_CACHE_RANGE && abs(dy) <= DIRECTION_CACHE_RANGE) {
53535c4bbdfSmrg        /* cacheable */
53635c4bbdfSmrg        dir = cache[DIRECTION_CACHE_RANGE + dx][DIRECTION_CACHE_RANGE + dy];
53735c4bbdfSmrg        if (dir == 0) {
53835c4bbdfSmrg            dir = DoGetDirection(dx, dy);
53935c4bbdfSmrg            cache[DIRECTION_CACHE_RANGE + dx][DIRECTION_CACHE_RANGE + dy] = dir;
54035c4bbdfSmrg        }
5414642e01fSmrg    }
54235c4bbdfSmrg    else {
54335c4bbdfSmrg        /* non-cacheable */
54435c4bbdfSmrg        dir = DoGetDirection(dx, dy);
54535c4bbdfSmrg    }
54635c4bbdfSmrg
54735c4bbdfSmrg    return dir;
5486747b715Smrg}
5494642e01fSmrg
5506747b715Smrg#undef DIRECTION_CACHE_RANGE
5516747b715Smrg#undef DIRECTION_CACHE_SIZE
5524642e01fSmrg
5536747b715Smrg/* convert offset (age) to array index */
5546747b715Smrg#define TRACKER_INDEX(s, d) (((s)->num_tracker + (s)->cur_tracker - (d)) % (s)->num_tracker)
55535c4bbdfSmrg#define TRACKER(s, d) &(s)->tracker[TRACKER_INDEX(s,d)]
5566747b715Smrg
55735c4bbdfSmrg/**
55835c4bbdfSmrg * Add the delta motion to each tracker, then reset the latest tracker to
55935c4bbdfSmrg * 0/0 and set it as the current one.
56035c4bbdfSmrg */
5616747b715Smrgstatic inline void
56235c4bbdfSmrgFeedTrackers(DeviceVelocityPtr vel, double dx, double dy, int cur_t)
5636747b715Smrg{
5646747b715Smrg    int n;
56535c4bbdfSmrg
56635c4bbdfSmrg    for (n = 0; n < vel->num_tracker; n++) {
56735c4bbdfSmrg        vel->tracker[n].dx += dx;
56835c4bbdfSmrg        vel->tracker[n].dy += dy;
5694642e01fSmrg    }
5706747b715Smrg    n = (vel->cur_tracker + 1) % vel->num_tracker;
57135c4bbdfSmrg    vel->tracker[n].dx = 0.0;
57235c4bbdfSmrg    vel->tracker[n].dy = 0.0;
5736747b715Smrg    vel->tracker[n].time = cur_t;
5746747b715Smrg    vel->tracker[n].dir = GetDirection(dx, dy);
57535c4bbdfSmrg    DebugAccelF("motion [dx: %f dy: %f dir:%d diff: %d]\n",
5766747b715Smrg                dx, dy, vel->tracker[n].dir,
5776747b715Smrg                cur_t - vel->tracker[vel->cur_tracker].time);
5786747b715Smrg    vel->cur_tracker = n;
5796747b715Smrg}
5806747b715Smrg
5816747b715Smrg/**
5826747b715Smrg * calc velocity for given tracker, with
5836747b715Smrg * velocity scaling.
5846747b715Smrg * This assumes linear motion.
5856747b715Smrg */
58635c4bbdfSmrgstatic double
58735c4bbdfSmrgCalcTracker(const MotionTracker * tracker, int cur_t)
58835c4bbdfSmrg{
58935c4bbdfSmrg    double dist = sqrt(tracker->dx * tracker->dx + tracker->dy * tracker->dy);
59035c4bbdfSmrg    int dtime = cur_t - tracker->time;
59135c4bbdfSmrg
59235c4bbdfSmrg    if (dtime > 0)
59335c4bbdfSmrg        return dist / dtime;
5946747b715Smrg    else
59535c4bbdfSmrg        return 0;               /* synonymous for NaN, since we're not C99 */
5966747b715Smrg}
5976747b715Smrg
5986747b715Smrg/* find the most plausible velocity. That is, the most distant
59935c4bbdfSmrg * (in time) tracker which isn't too old, the movement vector was
60035c4bbdfSmrg * in the same octant, and where the velocity is within an
601ed6184dfSmrg * acceptable range to the initial velocity.
6026747b715Smrg *
60335c4bbdfSmrg * @return The tracker's velocity or 0 if the above conditions are unmet
6046747b715Smrg */
60535c4bbdfSmrgstatic double
60635c4bbdfSmrgQueryTrackers(DeviceVelocityPtr vel, int cur_t)
60735c4bbdfSmrg{
60835c4bbdfSmrg    int offset, dir = UNDEFINED, used_offset = -1, age_ms;
60935c4bbdfSmrg
6106747b715Smrg    /* initial velocity: a low-offset, valid velocity */
61135c4bbdfSmrg    double initial_velocity = 0, result = 0, velocity_diff;
61235c4bbdfSmrg    double velocity_factor = vel->corr_mul * vel->const_acceleration;   /* premultiply */
61335c4bbdfSmrg
6146747b715Smrg    /* loop from current to older data */
61535c4bbdfSmrg    for (offset = 1; offset < vel->num_tracker; offset++) {
61635c4bbdfSmrg        MotionTracker *tracker = TRACKER(vel, offset);
61735c4bbdfSmrg        double tracker_velocity;
61835c4bbdfSmrg
61935c4bbdfSmrg        age_ms = cur_t - tracker->time;
62035c4bbdfSmrg
62135c4bbdfSmrg        /* bail out if data is too old and protect from overrun */
62235c4bbdfSmrg        if (age_ms >= vel->reset_time || age_ms < 0) {
62335c4bbdfSmrg            DebugAccelF("query: tracker too old (reset after %d, age is %d)\n",
62435c4bbdfSmrg                        vel->reset_time, age_ms);
62535c4bbdfSmrg            break;
62635c4bbdfSmrg        }
62735c4bbdfSmrg
62835c4bbdfSmrg        /*
62935c4bbdfSmrg         * this heuristic avoids using the linear-motion velocity formula
63035c4bbdfSmrg         * in CalcTracker() on motion that isn't exactly linear. So to get
63135c4bbdfSmrg         * even more precision we could subdivide as a final step, so possible
63235c4bbdfSmrg         * non-linearities are accounted for.
63335c4bbdfSmrg         */
63435c4bbdfSmrg        dir &= tracker->dir;
63535c4bbdfSmrg        if (dir == 0) {         /* we've changed octant of movement (e.g. NE → NW) */
63635c4bbdfSmrg            DebugAccelF("query: no longer linear\n");
63735c4bbdfSmrg            /* instead of breaking it we might also inspect the partition after,
63835c4bbdfSmrg             * but actual improvement with this is probably rare. */
63935c4bbdfSmrg            break;
64035c4bbdfSmrg        }
64135c4bbdfSmrg
64235c4bbdfSmrg        tracker_velocity = CalcTracker(tracker, cur_t) * velocity_factor;
64335c4bbdfSmrg
64435c4bbdfSmrg        if ((initial_velocity == 0 || offset <= vel->initial_range) &&
64535c4bbdfSmrg            tracker_velocity != 0) {
64635c4bbdfSmrg            /* set initial velocity and result */
64735c4bbdfSmrg            result = initial_velocity = tracker_velocity;
64835c4bbdfSmrg            used_offset = offset;
64935c4bbdfSmrg        }
65035c4bbdfSmrg        else if (initial_velocity != 0 && tracker_velocity != 0) {
65135c4bbdfSmrg            velocity_diff = fabs(initial_velocity - tracker_velocity);
65235c4bbdfSmrg
65335c4bbdfSmrg            if (velocity_diff > vel->max_diff &&
65435c4bbdfSmrg                velocity_diff / (initial_velocity + tracker_velocity) >=
65535c4bbdfSmrg                vel->max_rel_diff) {
65635c4bbdfSmrg                /* we're not in range, quit - it won't get better. */
65735c4bbdfSmrg                DebugAccelF("query: tracker too different:"
65835c4bbdfSmrg                            " old %2.2f initial %2.2f diff: %2.2f\n",
65935c4bbdfSmrg                            tracker_velocity, initial_velocity, velocity_diff);
66035c4bbdfSmrg                break;
66135c4bbdfSmrg            }
66235c4bbdfSmrg            /* we're in range with the initial velocity,
66335c4bbdfSmrg             * so this result is likely better
66435c4bbdfSmrg             * (it contains more information). */
66535c4bbdfSmrg            result = tracker_velocity;
66635c4bbdfSmrg            used_offset = offset;
66735c4bbdfSmrg        }
6686747b715Smrg    }
66935c4bbdfSmrg    if (offset == vel->num_tracker) {
67035c4bbdfSmrg        DebugAccelF("query: last tracker in effect\n");
67135c4bbdfSmrg        used_offset = vel->num_tracker - 1;
6726747b715Smrg    }
67335c4bbdfSmrg    if (used_offset >= 0) {
67435c4bbdfSmrg#ifdef PTRACCEL_DEBUGGING
67535c4bbdfSmrg        MotionTracker *tracker = TRACKER(vel, used_offset);
67635c4bbdfSmrg
67735c4bbdfSmrg        DebugAccelF("result: offset %i [dx: %f dy: %f diff: %i]\n",
67835c4bbdfSmrg                    used_offset, tracker->dx, tracker->dy,
67935c4bbdfSmrg                    cur_t - tracker->time);
68035c4bbdfSmrg#endif
6814642e01fSmrg    }
68235c4bbdfSmrg    return result;
6836747b715Smrg}
6844642e01fSmrg
6856747b715Smrg#undef TRACKER_INDEX
68635c4bbdfSmrg#undef TRACKER
6874642e01fSmrg
6886747b715Smrg/**
6896747b715Smrg * Perform velocity approximation based on 2D 'mickeys' (mouse motion delta).
6906747b715Smrg * return true if non-visible state reset is suggested
6916747b715Smrg */
69235c4bbdfSmrgBOOL
69335c4bbdfSmrgProcessVelocityData2D(DeviceVelocityPtr vel, double dx, double dy, int time)
6946747b715Smrg{
69535c4bbdfSmrg    double velocity;
6964642e01fSmrg
6976747b715Smrg    vel->last_velocity = vel->velocity;
6986747b715Smrg
6996747b715Smrg    FeedTrackers(vel, dx, dy, time);
7004642e01fSmrg
7016747b715Smrg    velocity = QueryTrackers(vel, time);
7026747b715Smrg
70335c4bbdfSmrg    DebugAccelF("velocity is %f\n", velocity);
70435c4bbdfSmrg
7056747b715Smrg    vel->velocity = velocity;
7066747b715Smrg    return velocity == 0;
7076747b715Smrg}
7084642e01fSmrg
7094642e01fSmrg/**
7104642e01fSmrg * this flattens significant ( > 1) mickeys a little bit for more steady
7114642e01fSmrg * constant-velocity response
7124642e01fSmrg */
71335c4bbdfSmrgstatic inline double
71435c4bbdfSmrgApplySimpleSoftening(double prev_delta, double delta)
7154642e01fSmrg{
71635c4bbdfSmrg    double result = delta;
7174642e01fSmrg
71835c4bbdfSmrg    if (delta < -1.0 || delta > 1.0) {
71935c4bbdfSmrg        if (delta > prev_delta)
72035c4bbdfSmrg            result -= 0.5;
72135c4bbdfSmrg        else if (delta < prev_delta)
72235c4bbdfSmrg            result += 0.5;
72335c4bbdfSmrg    }
72435c4bbdfSmrg    return result;
72535c4bbdfSmrg}
7264642e01fSmrg
72735c4bbdfSmrg/**
72835c4bbdfSmrg * Soften the delta based on previous deltas stored in vel.
72935c4bbdfSmrg *
73035c4bbdfSmrg * @param[in,out] fdx Delta X, modified in-place.
73135c4bbdfSmrg * @param[in,out] fdx Delta Y, modified in-place.
73235c4bbdfSmrg */
7334642e01fSmrgstatic void
73435c4bbdfSmrgApplySoftening(DeviceVelocityPtr vel, double *fdx, double *fdy)
7354642e01fSmrg{
73635c4bbdfSmrg    if (vel->use_softening) {
73735c4bbdfSmrg        *fdx = ApplySimpleSoftening(vel->last_dx, *fdx);
73835c4bbdfSmrg        *fdy = ApplySimpleSoftening(vel->last_dy, *fdy);
7394642e01fSmrg    }
74035c4bbdfSmrg}
7414642e01fSmrg
74235c4bbdfSmrgstatic void
74335c4bbdfSmrgApplyConstantDeceleration(DeviceVelocityPtr vel, double *fdx, double *fdy)
74435c4bbdfSmrg{
7456747b715Smrg    *fdx *= vel->const_acceleration;
7466747b715Smrg    *fdy *= vel->const_acceleration;
7474642e01fSmrg}
7484642e01fSmrg
7494642e01fSmrg/*
75035c4bbdfSmrg * compute the acceleration for given velocity and enforce min_acceleration
7514642e01fSmrg */
75235c4bbdfSmrgdouble
75335c4bbdfSmrgBasicComputeAcceleration(DeviceIntPtr dev,
75435c4bbdfSmrg                         DeviceVelocityPtr vel,
75535c4bbdfSmrg                         double velocity, double threshold, double acc)
75635c4bbdfSmrg{
75735c4bbdfSmrg
75835c4bbdfSmrg    double result;
75935c4bbdfSmrg
7606747b715Smrg    result = vel->Profile(dev, vel, velocity, threshold, acc);
7614642e01fSmrg
7624642e01fSmrg    /* enforce min_acceleration */
7636747b715Smrg    if (result < vel->min_acceleration)
76435c4bbdfSmrg        result = vel->min_acceleration;
7654642e01fSmrg    return result;
7664642e01fSmrg}
7674642e01fSmrg
7684642e01fSmrg/**
7694642e01fSmrg * Compute acceleration. Takes into account averaging, nv-reset, etc.
77035c4bbdfSmrg * If the velocity has changed, an average is taken of 6 velocity factors:
77135c4bbdfSmrg * current velocity, last velocity and 4 times the average between the two.
7724642e01fSmrg */
77335c4bbdfSmrgstatic double
77435c4bbdfSmrgComputeAcceleration(DeviceIntPtr dev,
77535c4bbdfSmrg                    DeviceVelocityPtr vel, double threshold, double acc)
77635c4bbdfSmrg{
77735c4bbdfSmrg    double result;
77835c4bbdfSmrg
77935c4bbdfSmrg    if (vel->velocity <= 0) {
78035c4bbdfSmrg        DebugAccelF("profile skipped\n");
7814642e01fSmrg        /*
7826747b715Smrg         * If we have no idea about device velocity, don't pretend it.
7834642e01fSmrg         */
78435c4bbdfSmrg        return 1;
7854642e01fSmrg    }
7864642e01fSmrg
78735c4bbdfSmrg    if (vel->average_accel && vel->velocity != vel->last_velocity) {
78835c4bbdfSmrg        /* use simpson's rule to average acceleration between
78935c4bbdfSmrg         * current and previous velocity.
79035c4bbdfSmrg         * Though being the more natural choice, it causes a minor delay
79135c4bbdfSmrg         * in comparison, so it can be disabled. */
79235c4bbdfSmrg        result =
79335c4bbdfSmrg            BasicComputeAcceleration(dev, vel, vel->velocity, threshold, acc);
79435c4bbdfSmrg        result +=
79535c4bbdfSmrg            BasicComputeAcceleration(dev, vel, vel->last_velocity, threshold,
79635c4bbdfSmrg                                     acc);
79735c4bbdfSmrg        result +=
7981b5d61b8Smrg            4.0 * BasicComputeAcceleration(dev, vel,
79935c4bbdfSmrg                                            (vel->last_velocity +
80035c4bbdfSmrg                                             vel->velocity) / 2,
80135c4bbdfSmrg                                            threshold,
80235c4bbdfSmrg                                            acc);
8031b5d61b8Smrg        result /= 6.0;
80435c4bbdfSmrg        DebugAccelF("profile average [%.2f ... %.2f] is %.3f\n",
80535c4bbdfSmrg                    vel->velocity, vel->last_velocity, result);
80635c4bbdfSmrg    }
80735c4bbdfSmrg    else {
80835c4bbdfSmrg        result = BasicComputeAcceleration(dev, vel,
80935c4bbdfSmrg                                          vel->velocity, threshold, acc);
81035c4bbdfSmrg        DebugAccelF("profile sample [%.2f] is %.3f\n",
81135c4bbdfSmrg                    vel->velocity, result);
8124642e01fSmrg    }
8134642e01fSmrg
81435c4bbdfSmrg    return result;
81535c4bbdfSmrg}
8164642e01fSmrg
8174642e01fSmrg/*****************************************
8184642e01fSmrg *  Acceleration functions and profiles
8194642e01fSmrg ****************************************/
8204642e01fSmrg
8214642e01fSmrg/**
8224642e01fSmrg * Polynomial function similar previous one, but with f(1) = 1
8234642e01fSmrg */
82435c4bbdfSmrgstatic double
82535c4bbdfSmrgPolynomialAccelerationProfile(DeviceIntPtr dev,
82635c4bbdfSmrg                              DeviceVelocityPtr vel,
82735c4bbdfSmrg                              double velocity, double ignored, double acc)
8284642e01fSmrg{
82935c4bbdfSmrg    return pow(velocity, (acc - 1.0) * 0.5);
8304642e01fSmrg}
8314642e01fSmrg
8324642e01fSmrg/**
8334642e01fSmrg * returns acceleration for velocity.
8344642e01fSmrg * This profile selects the two functions like the old scheme did
8354642e01fSmrg */
83635c4bbdfSmrgstatic double
83735c4bbdfSmrgClassicProfile(DeviceIntPtr dev,
83835c4bbdfSmrg               DeviceVelocityPtr vel,
83935c4bbdfSmrg               double velocity, double threshold, double acc)
8404642e01fSmrg{
8416747b715Smrg    if (threshold > 0) {
84235c4bbdfSmrg        return SimpleSmoothProfile(dev, vel, velocity, threshold, acc);
84335c4bbdfSmrg    }
84435c4bbdfSmrg    else {
84535c4bbdfSmrg        return PolynomialAccelerationProfile(dev, vel, velocity, 0, acc);
8464642e01fSmrg    }
8474642e01fSmrg}
8484642e01fSmrg
8494642e01fSmrg/**
8504642e01fSmrg * Power profile
8514642e01fSmrg * This has a completely smooth transition curve, i.e. no jumps in the
8524642e01fSmrg * derivatives.
8534642e01fSmrg *
8544642e01fSmrg * This has the expense of overall response dependency on min-acceleration.
8554642e01fSmrg * In effect, min_acceleration mimics const_acceleration in this profile.
8564642e01fSmrg */
85735c4bbdfSmrgstatic double
85835c4bbdfSmrgPowerProfile(DeviceIntPtr dev,
85935c4bbdfSmrg             DeviceVelocityPtr vel,
86035c4bbdfSmrg             double velocity, double threshold, double acc)
8614642e01fSmrg{
86235c4bbdfSmrg    double vel_dist;
8634642e01fSmrg
8641b5d61b8Smrg    acc = (acc - 1.0) * 0.1 + 1.0;     /* without this, acc of 2 is unuseable */
8654642e01fSmrg
8664642e01fSmrg    if (velocity <= threshold)
8676747b715Smrg        return vel->min_acceleration;
8684642e01fSmrg    vel_dist = velocity - threshold;
8696747b715Smrg    return (pow(acc, vel_dist)) * vel->min_acceleration;
8704642e01fSmrg}
8714642e01fSmrg
8724642e01fSmrg/**
8734642e01fSmrg * just a smooth function in [0..1] -> [0..1]
8744642e01fSmrg *  - point symmetry at 0.5
8754642e01fSmrg *  - f'(0) = f'(1) = 0
8764642e01fSmrg *  - starts faster than a sinoid
8774642e01fSmrg *  - smoothness C1 (Cinf if you dare to ignore endpoints)
8784642e01fSmrg */
87935c4bbdfSmrgstatic inline double
88035c4bbdfSmrgCalcPenumbralGradient(double x)
88135c4bbdfSmrg{
8821b5d61b8Smrg    x *= 2.0;
8831b5d61b8Smrg    x -= 1.0;
8841b5d61b8Smrg    return 0.5 + (x * sqrt(1.0 - x * x) + asin(x)) / M_PI;
8854642e01fSmrg}
8864642e01fSmrg
8874642e01fSmrg/**
8884642e01fSmrg * acceleration function similar to classic accelerated/unaccelerated,
8894642e01fSmrg * but with smooth transition in between (and towards zero for adaptive dec.).
8904642e01fSmrg */
89135c4bbdfSmrgstatic double
89235c4bbdfSmrgSimpleSmoothProfile(DeviceIntPtr dev,
89335c4bbdfSmrg                    DeviceVelocityPtr vel,
89435c4bbdfSmrg                    double velocity, double threshold, double acc)
8954642e01fSmrg{
89635c4bbdfSmrg    if (velocity < 1.0f)
89735c4bbdfSmrg        return CalcPenumbralGradient(0.5 + velocity * 0.5) * 2.0f - 1.0f;
89835c4bbdfSmrg    if (threshold < 1.0f)
8994642e01fSmrg        threshold = 1.0f;
9004642e01fSmrg    if (velocity <= threshold)
9014642e01fSmrg        return 1;
9024642e01fSmrg    velocity /= threshold;
9034642e01fSmrg    if (velocity >= acc)
9044642e01fSmrg        return acc;
9054642e01fSmrg    else
90635c4bbdfSmrg        return 1.0f + (CalcPenumbralGradient(velocity / acc) * (acc - 1.0f));
9074642e01fSmrg}
9084642e01fSmrg
9094642e01fSmrg/**
9104642e01fSmrg * This profile uses the first half of the penumbral gradient as a start
9114642e01fSmrg * and then scales linearly.
9124642e01fSmrg */
91335c4bbdfSmrgstatic double
91435c4bbdfSmrgSmoothLinearProfile(DeviceIntPtr dev,
91535c4bbdfSmrg                    DeviceVelocityPtr vel,
91635c4bbdfSmrg                    double velocity, double threshold, double acc)
9174642e01fSmrg{
91835c4bbdfSmrg    double res, nv;
9194642e01fSmrg
9201b5d61b8Smrg    if (acc > 1.0)
9211b5d61b8Smrg        acc -= 1.0;            /*this is so acc = 1 is no acceleration */
9224642e01fSmrg    else
9231b5d61b8Smrg        return 1.0;
9244642e01fSmrg
9251b5d61b8Smrg    nv = (velocity - threshold) * acc * 0.5;
9264642e01fSmrg
92735c4bbdfSmrg    if (nv < 0) {
9284642e01fSmrg        res = 0;
92935c4bbdfSmrg    }
93035c4bbdfSmrg    else if (nv < 2) {
9311b5d61b8Smrg        res = CalcPenumbralGradient(nv * 0.25) * 2.0;
93235c4bbdfSmrg    }
93335c4bbdfSmrg    else {
9341b5d61b8Smrg        nv -= 2.0;
9351b5d61b8Smrg        res = nv * 2.0 / M_PI  /* steepness of gradient at 0.5 */
9361b5d61b8Smrg            + 1.0;             /* gradient crosses 2|1 */
9374642e01fSmrg    }
9386747b715Smrg    res += vel->min_acceleration;
9394642e01fSmrg    return res;
9404642e01fSmrg}
9414642e01fSmrg
9426747b715Smrg/**
9436747b715Smrg * From 0 to threshold, the response graduates smoothly from min_accel to
9446747b715Smrg * acceleration. Beyond threshold it is exactly the specified acceleration.
9456747b715Smrg */
94635c4bbdfSmrgstatic double
94735c4bbdfSmrgSmoothLimitedProfile(DeviceIntPtr dev,
94835c4bbdfSmrg                     DeviceVelocityPtr vel,
94935c4bbdfSmrg                     double velocity, double threshold, double acc)
9506747b715Smrg{
95135c4bbdfSmrg    double res;
9526747b715Smrg
9531b5d61b8Smrg    if (velocity >= threshold || threshold == 0.0)
95435c4bbdfSmrg        return acc;
9556747b715Smrg
95635c4bbdfSmrg    velocity /= threshold;      /* should be [0..1[ now */
9576747b715Smrg
9586747b715Smrg    res = CalcPenumbralGradient(velocity) * (acc - vel->min_acceleration);
9596747b715Smrg
9606747b715Smrg    return vel->min_acceleration + res;
9616747b715Smrg}
9626747b715Smrg
96335c4bbdfSmrgstatic double
96435c4bbdfSmrgLinearProfile(DeviceIntPtr dev,
96535c4bbdfSmrg              DeviceVelocityPtr vel,
96635c4bbdfSmrg              double velocity, double threshold, double acc)
9674642e01fSmrg{
9684642e01fSmrg    return acc * velocity;
9694642e01fSmrg}
9704642e01fSmrg
97135c4bbdfSmrgstatic double
97235c4bbdfSmrgNoProfile(DeviceIntPtr dev,
97335c4bbdfSmrg          DeviceVelocityPtr vel, double velocity, double threshold, double acc)
9746747b715Smrg{
9751b5d61b8Smrg    return 1.0;
9766747b715Smrg}
9774642e01fSmrg
9786747b715Smrgstatic PointerAccelerationProfileFunc
97935c4bbdfSmrgGetAccelerationProfile(DeviceVelocityPtr vel, int profile_num)
9804642e01fSmrg{
98135c4bbdfSmrg    switch (profile_num) {
98235c4bbdfSmrg    case AccelProfileClassic:
98335c4bbdfSmrg        return ClassicProfile;
98435c4bbdfSmrg    case AccelProfileDeviceSpecific:
98535c4bbdfSmrg        return vel->deviceSpecificProfile;
98635c4bbdfSmrg    case AccelProfilePolynomial:
98735c4bbdfSmrg        return PolynomialAccelerationProfile;
98835c4bbdfSmrg    case AccelProfileSmoothLinear:
98935c4bbdfSmrg        return SmoothLinearProfile;
99035c4bbdfSmrg    case AccelProfileSimple:
99135c4bbdfSmrg        return SimpleSmoothProfile;
99235c4bbdfSmrg    case AccelProfilePower:
99335c4bbdfSmrg        return PowerProfile;
99435c4bbdfSmrg    case AccelProfileLinear:
99535c4bbdfSmrg        return LinearProfile;
99635c4bbdfSmrg    case AccelProfileSmoothLimited:
99735c4bbdfSmrg        return SmoothLimitedProfile;
99835c4bbdfSmrg    case AccelProfileNone:
99935c4bbdfSmrg        return NoProfile;
100035c4bbdfSmrg    default:
100135c4bbdfSmrg        return NULL;
10024642e01fSmrg    }
10036747b715Smrg}
10046747b715Smrg
10056747b715Smrg/**
10066747b715Smrg * Set the profile by number.
10076747b715Smrg * Intended to make profiles exchangeable at runtime.
10086747b715Smrg * If you created a profile, give it a number here and in the header to
10096747b715Smrg * make it selectable. In case some profile-specific init is needed, here
10106747b715Smrg * would be a good place, since FreeVelocityData() also calls this with
10116747b715Smrg * PROFILE_UNINITIALIZE.
10126747b715Smrg *
10136747b715Smrg * returns FALSE if profile number is unavailable, TRUE otherwise.
10146747b715Smrg */
10156747b715Smrgint
101635c4bbdfSmrgSetAccelerationProfile(DeviceVelocityPtr vel, int profile_num)
10176747b715Smrg{
10186747b715Smrg    PointerAccelerationProfileFunc profile;
101935c4bbdfSmrg
10206747b715Smrg    profile = GetAccelerationProfile(vel, profile_num);
10216747b715Smrg
102235c4bbdfSmrg    if (profile == NULL && profile_num != PROFILE_UNINITIALIZE)
102335c4bbdfSmrg        return FALSE;
10246747b715Smrg
10259ace9065Smrg    /* Here one could free old profile-private data */
10269ace9065Smrg    free(vel->profile_private);
10279ace9065Smrg    vel->profile_private = NULL;
10284642e01fSmrg    /* Here one could init profile-private data */
10296747b715Smrg    vel->Profile = profile;
10306747b715Smrg    vel->statistics.profile_number = profile_num;
10314642e01fSmrg    return TRUE;
10324642e01fSmrg}
10334642e01fSmrg
10344642e01fSmrg/**********************************************
10354642e01fSmrg * driver interaction
10364642e01fSmrg **********************************************/
10374642e01fSmrg
10384642e01fSmrg/**
10394642e01fSmrg * device-specific profile
10404642e01fSmrg *
10414642e01fSmrg * The device-specific profile is intended as a hook for a driver
10424642e01fSmrg * which may want to provide an own acceleration profile.
10434642e01fSmrg * It should not rely on profile-private data, instead
10444642e01fSmrg * it should do init/uninit in the driver (ie. with DEVICE_INIT and friends).
10454642e01fSmrg * Users may override or choose it.
10464642e01fSmrg */
10476747b715Smrgvoid
104835c4bbdfSmrgSetDeviceSpecificAccelerationProfile(DeviceVelocityPtr vel,
104935c4bbdfSmrg                                     PointerAccelerationProfileFunc profile)
10504642e01fSmrg{
105135c4bbdfSmrg    if (vel)
105235c4bbdfSmrg        vel->deviceSpecificProfile = profile;
10534642e01fSmrg}
10544642e01fSmrg
10554642e01fSmrg/**
10564642e01fSmrg * Use this function to obtain a DeviceVelocityPtr for a device. Will return NULL if
10574642e01fSmrg * the predictable acceleration scheme is not in effect.
10584642e01fSmrg */
10596747b715SmrgDeviceVelocityPtr
106035c4bbdfSmrgGetDevicePredictableAccelData(DeviceIntPtr dev)
10614642e01fSmrg{
106235c4bbdfSmrg    BUG_RETURN_VAL(!dev, NULL);
10634642e01fSmrg
106435c4bbdfSmrg    if (dev->valuator &&
106535c4bbdfSmrg        dev->valuator->accelScheme.AccelSchemeProc ==
106635c4bbdfSmrg        acceleratePointerPredictable &&
106735c4bbdfSmrg        dev->valuator->accelScheme.accelData != NULL) {
106835c4bbdfSmrg
106935c4bbdfSmrg        return ((PredictableAccelSchemePtr)
107035c4bbdfSmrg                dev->valuator->accelScheme.accelData)->vel;
10714642e01fSmrg    }
10724642e01fSmrg    return NULL;
10734642e01fSmrg}
10744642e01fSmrg
10754642e01fSmrg/********************************
10764642e01fSmrg *  acceleration schemes
10774642e01fSmrg *******************************/
10784642e01fSmrg
10794642e01fSmrg/**
10804642e01fSmrg * Modifies valuators in-place.
10814642e01fSmrg * This version employs a velocity approximation algorithm to
10824642e01fSmrg * enable fine-grained predictable acceleration profiles.
10834642e01fSmrg */
10844642e01fSmrgvoid
108535c4bbdfSmrgacceleratePointerPredictable(DeviceIntPtr dev, ValuatorMask *val, CARD32 evtime)
10864642e01fSmrg{
108735c4bbdfSmrg    double dx = 0, dy = 0;
108835c4bbdfSmrg    DeviceVelocityPtr velocitydata = GetDevicePredictableAccelData(dev);
10896747b715Smrg    Bool soften = TRUE;
10904642e01fSmrg
109135c4bbdfSmrg    if (valuator_mask_num_valuators(val) == 0 || !velocitydata)
10924642e01fSmrg        return;
10934642e01fSmrg
10946747b715Smrg    if (velocitydata->statistics.profile_number == AccelProfileNone &&
10951b5d61b8Smrg        velocitydata->const_acceleration == 1.0) {
109635c4bbdfSmrg        return;                 /*we're inactive anyway, so skip the whole thing. */
10976747b715Smrg    }
10986747b715Smrg
109935c4bbdfSmrg    if (valuator_mask_isset(val, 0)) {
110035c4bbdfSmrg        dx = valuator_mask_get_double(val, 0);
11014642e01fSmrg    }
110235c4bbdfSmrg
110335c4bbdfSmrg    if (valuator_mask_isset(val, 1)) {
110435c4bbdfSmrg        dy = valuator_mask_get_double(val, 1);
11054642e01fSmrg    }
11064642e01fSmrg
110735c4bbdfSmrg    if (dx != 0.0 || dy != 0.0) {
11086747b715Smrg        /* reset non-visible state? */
110935c4bbdfSmrg        if (ProcessVelocityData2D(velocitydata, dx, dy, evtime)) {
11106747b715Smrg            soften = FALSE;
11114642e01fSmrg        }
11124642e01fSmrg
11136747b715Smrg        if (dev->ptrfeed && dev->ptrfeed->ctrl.num) {
111435c4bbdfSmrg            double mult;
111535c4bbdfSmrg
11164642e01fSmrg            /* invoke acceleration profile to determine acceleration */
111735c4bbdfSmrg            mult = ComputeAcceleration(dev, velocitydata,
111835c4bbdfSmrg                                       dev->ptrfeed->ctrl.threshold,
111935c4bbdfSmrg                                       (double) dev->ptrfeed->ctrl.num /
112035c4bbdfSmrg                                       (double) dev->ptrfeed->ctrl.den);
112135c4bbdfSmrg
112235c4bbdfSmrg            DebugAccelF("mult is %f\n", mult);
11231b5d61b8Smrg            if (mult != 1.0 || velocitydata->const_acceleration != 1.0) {
11241b5d61b8Smrg                if (mult > 1.0 && soften)
112535c4bbdfSmrg                    ApplySoftening(velocitydata, &dx, &dy);
112635c4bbdfSmrg                ApplyConstantDeceleration(velocitydata, &dx, &dy);
112735c4bbdfSmrg
112835c4bbdfSmrg                if (dx != 0.0)
112935c4bbdfSmrg                    valuator_mask_set_double(val, 0, mult * dx);
113035c4bbdfSmrg                if (dy != 0.0)
113135c4bbdfSmrg                    valuator_mask_set_double(val, 1, mult * dy);
113235c4bbdfSmrg                DebugAccelF("delta x:%.3f y:%.3f\n", mult * dx, mult * dy);
11334642e01fSmrg            }
11344642e01fSmrg        }
11354642e01fSmrg    }
11364642e01fSmrg    /* remember last motion delta (for softening/slow movement treatment) */
11374642e01fSmrg    velocitydata->last_dx = dx;
11384642e01fSmrg    velocitydata->last_dy = dy;
11394642e01fSmrg}
11404642e01fSmrg
11414642e01fSmrg/**
11424642e01fSmrg * Originally a part of xf86PostMotionEvent; modifies valuators
11434642e01fSmrg * in-place. Retained mostly for embedded scenarios.
11444642e01fSmrg */
11454642e01fSmrgvoid
114635c4bbdfSmrgacceleratePointerLightweight(DeviceIntPtr dev,
114735c4bbdfSmrg                             ValuatorMask *val, CARD32 ignored)
11484642e01fSmrg{
114935c4bbdfSmrg    double mult = 0.0, tmpf;
115035c4bbdfSmrg    double dx = 0.0, dy = 0.0;
11514642e01fSmrg
115235c4bbdfSmrg    if (valuator_mask_isset(val, 0)) {
115335c4bbdfSmrg        dx = valuator_mask_get(val, 0);
11544642e01fSmrg    }
115535c4bbdfSmrg
115635c4bbdfSmrg    if (valuator_mask_isset(val, 1)) {
115735c4bbdfSmrg        dy = valuator_mask_get(val, 1);
11584642e01fSmrg    }
11594642e01fSmrg
116035c4bbdfSmrg    if (valuator_mask_num_valuators(val) == 0)
11614642e01fSmrg        return;
11624642e01fSmrg
11636747b715Smrg    if (dev->ptrfeed && dev->ptrfeed->ctrl.num) {
11644642e01fSmrg        /* modeled from xf86Events.c */
11656747b715Smrg        if (dev->ptrfeed->ctrl.threshold) {
116635c4bbdfSmrg            if ((fabs(dx) + fabs(dy)) >= dev->ptrfeed->ctrl.threshold) {
116735c4bbdfSmrg                if (dx != 0.0) {
116835c4bbdfSmrg                    tmpf = (dx * (double) (dev->ptrfeed->ctrl.num)) /
116935c4bbdfSmrg                        (double) (dev->ptrfeed->ctrl.den);
117035c4bbdfSmrg                    valuator_mask_set_double(val, 0, tmpf);
11714642e01fSmrg                }
11724642e01fSmrg
117335c4bbdfSmrg                if (dy != 0.0) {
117435c4bbdfSmrg                    tmpf = (dy * (double) (dev->ptrfeed->ctrl.num)) /
117535c4bbdfSmrg                        (double) (dev->ptrfeed->ctrl.den);
117635c4bbdfSmrg                    valuator_mask_set_double(val, 1, tmpf);
11774642e01fSmrg                }
11784642e01fSmrg            }
11794642e01fSmrg        }
11804642e01fSmrg        else {
118135c4bbdfSmrg            mult = pow(dx * dx + dy * dy,
118235c4bbdfSmrg                       ((double) (dev->ptrfeed->ctrl.num) /
118335c4bbdfSmrg                        (double) (dev->ptrfeed->ctrl.den) - 1.0) / 2.0) / 2.0;
118435c4bbdfSmrg            if (dx != 0.0)
118535c4bbdfSmrg                valuator_mask_set_double(val, 0, mult * dx);
118635c4bbdfSmrg            if (dy != 0.0)
118735c4bbdfSmrg                valuator_mask_set_double(val, 1, mult * dy);
11884642e01fSmrg        }
11894642e01fSmrg    }
11904642e01fSmrg}
1191