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