/* * * Copyright © 2006-2009 Simon Thum simon dot thum at gmx dot de * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice (including the next * paragraph) shall be included in all copies or substantial portions of the * Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ #ifdef HAVE_DIX_CONFIG_H #include #endif #include #include #include #include #include #include /***************************************************************************** * Predictable pointer acceleration * * 2006-2009 by Simon Thum (simon [dot] thum [at] gmx de) * * Serves 3 complementary functions: * 1) provide a sophisticated ballistic velocity estimate to improve * the relation between velocity (of the device) and acceleration * 2) make arbitrary acceleration profiles possible * 3) decelerate by two means (constant and adaptive) if enabled * * Important concepts are the * * - Scheme * which selects the basic algorithm * (see devices.c/InitPointerAccelerationScheme) * - Profile * which returns an acceleration * for a given velocity * * The profile can be selected by the user at runtime. * The classic profile is intended to cleanly perform old-style * function selection (threshold =/!= 0) * ****************************************************************************/ /* fwds */ static double SimpleSmoothProfile(DeviceIntPtr dev, DeviceVelocityPtr vel, double velocity, double threshold, double acc); static PointerAccelerationProfileFunc GetAccelerationProfile(DeviceVelocityPtr vel, int profile_num); static BOOL InitializePredictableAccelerationProperties(DeviceIntPtr, DeviceVelocityPtr, PredictableAccelSchemePtr); static BOOL DeletePredictableAccelerationProperties(DeviceIntPtr, PredictableAccelSchemePtr); /*#define PTRACCEL_DEBUGGING*/ #ifdef PTRACCEL_DEBUGGING #define DebugAccelF(...) ErrorFSigSafe("dix/ptraccel: " __VA_ARGS__) #else #define DebugAccelF(...) /* */ #endif /******************************** * Init/Uninit *******************************/ /* some int which is not a profile number */ #define PROFILE_UNINITIALIZE (-100) /** * Init DeviceVelocity struct so it should match the average case */ void InitVelocityData(DeviceVelocityPtr vel) { memset(vel, 0, sizeof(DeviceVelocityRec)); vel->corr_mul = 10.0; /* dots per 10 millisecond should be usable */ vel->const_acceleration = 1.0; /* no acceleration/deceleration */ vel->reset_time = 300; vel->use_softening = 1; vel->min_acceleration = 1.0; /* don't decelerate */ vel->max_rel_diff = 0.2; vel->max_diff = 1.0; vel->initial_range = 2; vel->average_accel = TRUE; SetAccelerationProfile(vel, AccelProfileClassic); InitTrackers(vel, 16); } /** * Clean up DeviceVelocityRec */ void FreeVelocityData(DeviceVelocityPtr vel) { free(vel->tracker); SetAccelerationProfile(vel, PROFILE_UNINITIALIZE); } /** * Init predictable scheme */ Bool InitPredictableAccelerationScheme(DeviceIntPtr dev, ValuatorAccelerationPtr protoScheme) { DeviceVelocityPtr vel; ValuatorAccelerationRec scheme; PredictableAccelSchemePtr schemeData; scheme = *protoScheme; vel = calloc(1, sizeof(DeviceVelocityRec)); schemeData = calloc(1, sizeof(PredictableAccelSchemeRec)); if (!vel || !schemeData) { free(vel); free(schemeData); return FALSE; } InitVelocityData(vel); schemeData->vel = vel; scheme.accelData = schemeData; if (!InitializePredictableAccelerationProperties(dev, vel, schemeData)) { FreeVelocityData(vel); free(vel); free(schemeData); return FALSE; } /* all fine, assign scheme to device */ dev->valuator->accelScheme = scheme; return TRUE; } /** * Uninit scheme */ void AccelerationDefaultCleanup(DeviceIntPtr dev) { DeviceVelocityPtr vel = GetDevicePredictableAccelData(dev); if (vel) { /* the proper guarantee would be that we're not inside of * AccelSchemeProc(), but that seems impossible. Schemes don't get * switched often anyway. */ input_lock(); dev->valuator->accelScheme.AccelSchemeProc = NULL; FreeVelocityData(vel); free(vel); DeletePredictableAccelerationProperties(dev, (PredictableAccelSchemePtr) dev->valuator->accelScheme. accelData); free(dev->valuator->accelScheme.accelData); dev->valuator->accelScheme.accelData = NULL; input_unlock(); } } /************************* * Input property support ************************/ /** * choose profile */ static int AccelSetProfileProperty(DeviceIntPtr dev, Atom atom, XIPropertyValuePtr val, BOOL checkOnly) { DeviceVelocityPtr vel; int profile, *ptr = &profile; int rc; int nelem = 1; if (atom != XIGetKnownProperty(ACCEL_PROP_PROFILE_NUMBER)) return Success; vel = GetDevicePredictableAccelData(dev); if (!vel) return BadValue; rc = XIPropToInt(val, &nelem, &ptr); if (checkOnly) { if (rc) return rc; if (GetAccelerationProfile(vel, profile) == NULL) return BadValue; } else SetAccelerationProfile(vel, profile); return Success; } static long AccelInitProfileProperty(DeviceIntPtr dev, DeviceVelocityPtr vel) { int profile = vel->statistics.profile_number; Atom prop_profile_number = XIGetKnownProperty(ACCEL_PROP_PROFILE_NUMBER); XIChangeDeviceProperty(dev, prop_profile_number, XA_INTEGER, 32, PropModeReplace, 1, &profile, FALSE); XISetDevicePropertyDeletable(dev, prop_profile_number, FALSE); return XIRegisterPropertyHandler(dev, AccelSetProfileProperty, NULL, NULL); } /** * constant deceleration */ static int AccelSetDecelProperty(DeviceIntPtr dev, Atom atom, XIPropertyValuePtr val, BOOL checkOnly) { DeviceVelocityPtr vel; float v, *ptr = &v; int rc; int nelem = 1; if (atom != XIGetKnownProperty(ACCEL_PROP_CONSTANT_DECELERATION)) return Success; vel = GetDevicePredictableAccelData(dev); if (!vel) return BadValue; rc = XIPropToFloat(val, &nelem, &ptr); if (checkOnly) { if (rc) return rc; return (v > 0) ? Success : BadValue; } vel->const_acceleration = 1 / v; return Success; } static long AccelInitDecelProperty(DeviceIntPtr dev, DeviceVelocityPtr vel) { float fval = 1.0 / vel->const_acceleration; Atom prop_const_decel = XIGetKnownProperty(ACCEL_PROP_CONSTANT_DECELERATION); XIChangeDeviceProperty(dev, prop_const_decel, XIGetKnownProperty(XATOM_FLOAT), 32, PropModeReplace, 1, &fval, FALSE); XISetDevicePropertyDeletable(dev, prop_const_decel, FALSE); return XIRegisterPropertyHandler(dev, AccelSetDecelProperty, NULL, NULL); } /** * adaptive deceleration */ static int AccelSetAdaptDecelProperty(DeviceIntPtr dev, Atom atom, XIPropertyValuePtr val, BOOL checkOnly) { DeviceVelocityPtr veloc; float v, *ptr = &v; int rc; int nelem = 1; if (atom != XIGetKnownProperty(ACCEL_PROP_ADAPTIVE_DECELERATION)) return Success; veloc = GetDevicePredictableAccelData(dev); if (!veloc) return BadValue; rc = XIPropToFloat(val, &nelem, &ptr); if (checkOnly) { if (rc) return rc; return (v >= 1.0f) ? Success : BadValue; } if (v >= 1.0f) veloc->min_acceleration = 1 / v; return Success; } static long AccelInitAdaptDecelProperty(DeviceIntPtr dev, DeviceVelocityPtr vel) { float fval = 1.0 / vel->min_acceleration; Atom prop_adapt_decel = XIGetKnownProperty(ACCEL_PROP_ADAPTIVE_DECELERATION); XIChangeDeviceProperty(dev, prop_adapt_decel, XIGetKnownProperty(XATOM_FLOAT), 32, PropModeReplace, 1, &fval, FALSE); XISetDevicePropertyDeletable(dev, prop_adapt_decel, FALSE); return XIRegisterPropertyHandler(dev, AccelSetAdaptDecelProperty, NULL, NULL); } /** * velocity scaling */ static int AccelSetScaleProperty(DeviceIntPtr dev, Atom atom, XIPropertyValuePtr val, BOOL checkOnly) { DeviceVelocityPtr vel; float v, *ptr = &v; int rc; int nelem = 1; if (atom != XIGetKnownProperty(ACCEL_PROP_VELOCITY_SCALING)) return Success; vel = GetDevicePredictableAccelData(dev); if (!vel) return BadValue; rc = XIPropToFloat(val, &nelem, &ptr); if (checkOnly) { if (rc) return rc; return (v > 0) ? Success : BadValue; } if (v > 0) vel->corr_mul = v; return Success; } static long AccelInitScaleProperty(DeviceIntPtr dev, DeviceVelocityPtr vel) { float fval = vel->corr_mul; Atom prop_velo_scale = XIGetKnownProperty(ACCEL_PROP_VELOCITY_SCALING); XIChangeDeviceProperty(dev, prop_velo_scale, XIGetKnownProperty(XATOM_FLOAT), 32, PropModeReplace, 1, &fval, FALSE); XISetDevicePropertyDeletable(dev, prop_velo_scale, FALSE); return XIRegisterPropertyHandler(dev, AccelSetScaleProperty, NULL, NULL); } static BOOL InitializePredictableAccelerationProperties(DeviceIntPtr dev, DeviceVelocityPtr vel, PredictableAccelSchemePtr schemeData) { int num_handlers = 4; if (!vel) return FALSE; schemeData->prop_handlers = calloc(num_handlers, sizeof(long)); if (!schemeData->prop_handlers) return FALSE; schemeData->num_prop_handlers = num_handlers; schemeData->prop_handlers[0] = AccelInitProfileProperty(dev, vel); schemeData->prop_handlers[1] = AccelInitDecelProperty(dev, vel); schemeData->prop_handlers[2] = AccelInitAdaptDecelProperty(dev, vel); schemeData->prop_handlers[3] = AccelInitScaleProperty(dev, vel); return TRUE; } BOOL DeletePredictableAccelerationProperties(DeviceIntPtr dev, PredictableAccelSchemePtr scheme) { DeviceVelocityPtr vel; Atom prop; int i; prop = XIGetKnownProperty(ACCEL_PROP_VELOCITY_SCALING); XIDeleteDeviceProperty(dev, prop, FALSE); prop = XIGetKnownProperty(ACCEL_PROP_ADAPTIVE_DECELERATION); XIDeleteDeviceProperty(dev, prop, FALSE); prop = XIGetKnownProperty(ACCEL_PROP_CONSTANT_DECELERATION); XIDeleteDeviceProperty(dev, prop, FALSE); prop = XIGetKnownProperty(ACCEL_PROP_PROFILE_NUMBER); XIDeleteDeviceProperty(dev, prop, FALSE); vel = GetDevicePredictableAccelData(dev); if (vel) { for (i = 0; i < scheme->num_prop_handlers; i++) if (scheme->prop_handlers[i]) XIUnregisterPropertyHandler(dev, scheme->prop_handlers[i]); } free(scheme->prop_handlers); scheme->prop_handlers = NULL; scheme->num_prop_handlers = 0; return TRUE; } /********************* * Tracking logic ********************/ void InitTrackers(DeviceVelocityPtr vel, int ntracker) { if (ntracker < 1) { ErrorF("invalid number of trackers\n"); return; } free(vel->tracker); vel->tracker = (MotionTrackerPtr) calloc(ntracker, sizeof(MotionTracker)); vel->num_tracker = ntracker; } enum directions { N = (1 << 0), NE = (1 << 1), E = (1 << 2), SE = (1 << 3), S = (1 << 4), SW = (1 << 5), W = (1 << 6), NW = (1 << 7), UNDEFINED = 0xFF }; /** * return a bit field of possible directions. * There's no reason against widening to more precise directions (<45 degrees), * should it not perform well. All this is needed for is sort out non-linear * motion, so precision isn't paramount. However, one should not flag direction * too narrow, since it would then cut the linear segment to zero size way too * often. * * @return A bitmask for N, NE, S, SE, etc. indicating the directions for * this movement. */ static int DoGetDirection(int dx, int dy) { int dir = 0; /* on insignificant mickeys, flag 135 degrees */ if (abs(dx) < 2 && abs(dy) < 2) { /* first check diagonal cases */ if (dx > 0 && dy > 0) dir = E | SE | S; else if (dx > 0 && dy < 0) dir = N | NE | E; else if (dx < 0 && dy < 0) dir = W | NW | N; else if (dx < 0 && dy > 0) dir = W | SW | S; /* check axis-aligned directions */ else if (dx > 0) dir = NE | E | SE; else if (dx < 0) dir = NW | W | SW; else if (dy > 0) dir = SE | S | SW; else if (dy < 0) dir = NE | N | NW; else dir = UNDEFINED; /* shouldn't happen */ } else { /* compute angle and set appropriate flags */ double r; int i1, i2; r = atan2(dy, dx); /* find direction. * * Add 360° to avoid r become negative since C has no well-defined * modulo for such cases. Then divide by 45° to get the octant * number, e.g. * 0 <= r <= 1 is [0-45]° * 1 <= r <= 2 is [45-90]° * etc. * But we add extra 90° to match up with our N, S, etc. defines up * there, rest stays the same. */ r = (r + (M_PI * 2.5)) / (M_PI / 4); /* this intends to flag 2 directions (45 degrees), * except on very well-aligned mickeys. */ i1 = (int) (r + 0.1) % 8; i2 = (int) (r + 0.9) % 8; if (i1 < 0 || i1 > 7 || i2 < 0 || i2 > 7) dir = UNDEFINED; /* shouldn't happen */ else dir = (1 << i1 | 1 << i2); } return dir; } #define DIRECTION_CACHE_RANGE 5 #define DIRECTION_CACHE_SIZE (DIRECTION_CACHE_RANGE*2+1) /* cache DoGetDirection(). * To avoid excessive use of direction calculation, cache the values for * [-5..5] for both x/y. Anything outside of that is calculated on the fly. * * @return A bitmask for N, NE, S, SE, etc. indicating the directions for * this movement. */ static int GetDirection(int dx, int dy) { static int cache[DIRECTION_CACHE_SIZE][DIRECTION_CACHE_SIZE]; int dir; if (abs(dx) <= DIRECTION_CACHE_RANGE && abs(dy) <= DIRECTION_CACHE_RANGE) { /* cacheable */ dir = cache[DIRECTION_CACHE_RANGE + dx][DIRECTION_CACHE_RANGE + dy]; if (dir == 0) { dir = DoGetDirection(dx, dy); cache[DIRECTION_CACHE_RANGE + dx][DIRECTION_CACHE_RANGE + dy] = dir; } } else { /* non-cacheable */ dir = DoGetDirection(dx, dy); } return dir; } #undef DIRECTION_CACHE_RANGE #undef DIRECTION_CACHE_SIZE /* convert offset (age) to array index */ #define TRACKER_INDEX(s, d) (((s)->num_tracker + (s)->cur_tracker - (d)) % (s)->num_tracker) #define TRACKER(s, d) &(s)->tracker[TRACKER_INDEX(s,d)] /** * Add the delta motion to each tracker, then reset the latest tracker to * 0/0 and set it as the current one. */ static inline void FeedTrackers(DeviceVelocityPtr vel, double dx, double dy, int cur_t) { int n; for (n = 0; n < vel->num_tracker; n++) { vel->tracker[n].dx += dx; vel->tracker[n].dy += dy; } n = (vel->cur_tracker + 1) % vel->num_tracker; vel->tracker[n].dx = 0.0; vel->tracker[n].dy = 0.0; vel->tracker[n].time = cur_t; vel->tracker[n].dir = GetDirection(dx, dy); DebugAccelF("motion [dx: %f dy: %f dir:%d diff: %d]\n", dx, dy, vel->tracker[n].dir, cur_t - vel->tracker[vel->cur_tracker].time); vel->cur_tracker = n; } /** * calc velocity for given tracker, with * velocity scaling. * This assumes linear motion. */ static double CalcTracker(const MotionTracker * tracker, int cur_t) { double dist = sqrt(tracker->dx * tracker->dx + tracker->dy * tracker->dy); int dtime = cur_t - tracker->time; if (dtime > 0) return dist / dtime; else return 0; /* synonymous for NaN, since we're not C99 */ } /* find the most plausible velocity. That is, the most distant * (in time) tracker which isn't too old, the movement vector was * in the same octant, and where the velocity is within an * acceptable range to the initial velocity. * * @return The tracker's velocity or 0 if the above conditions are unmet */ static double QueryTrackers(DeviceVelocityPtr vel, int cur_t) { int offset, dir = UNDEFINED, used_offset = -1, age_ms; /* initial velocity: a low-offset, valid velocity */ double initial_velocity = 0, result = 0, velocity_diff; double velocity_factor = vel->corr_mul * vel->const_acceleration; /* premultiply */ /* loop from current to older data */ for (offset = 1; offset < vel->num_tracker; offset++) { MotionTracker *tracker = TRACKER(vel, offset); double tracker_velocity; age_ms = cur_t - tracker->time; /* bail out if data is too old and protect from overrun */ if (age_ms >= vel->reset_time || age_ms < 0) { DebugAccelF("query: tracker too old (reset after %d, age is %d)\n", vel->reset_time, age_ms); break; } /* * this heuristic avoids using the linear-motion velocity formula * in CalcTracker() on motion that isn't exactly linear. So to get * even more precision we could subdivide as a final step, so possible * non-linearities are accounted for. */ dir &= tracker->dir; if (dir == 0) { /* we've changed octant of movement (e.g. NE → NW) */ DebugAccelF("query: no longer linear\n"); /* instead of breaking it we might also inspect the partition after, * but actual improvement with this is probably rare. */ break; } tracker_velocity = CalcTracker(tracker, cur_t) * velocity_factor; if ((initial_velocity == 0 || offset <= vel->initial_range) && tracker_velocity != 0) { /* set initial velocity and result */ result = initial_velocity = tracker_velocity; used_offset = offset; } else if (initial_velocity != 0 && tracker_velocity != 0) { velocity_diff = fabs(initial_velocity - tracker_velocity); if (velocity_diff > vel->max_diff && velocity_diff / (initial_velocity + tracker_velocity) >= vel->max_rel_diff) { /* we're not in range, quit - it won't get better. */ DebugAccelF("query: tracker too different:" " old %2.2f initial %2.2f diff: %2.2f\n", tracker_velocity, initial_velocity, velocity_diff); break; } /* we're in range with the initial velocity, * so this result is likely better * (it contains more information). */ result = tracker_velocity; used_offset = offset; } } if (offset == vel->num_tracker) { DebugAccelF("query: last tracker in effect\n"); used_offset = vel->num_tracker - 1; } if (used_offset >= 0) { #ifdef PTRACCEL_DEBUGGING MotionTracker *tracker = TRACKER(vel, used_offset); DebugAccelF("result: offset %i [dx: %f dy: %f diff: %i]\n", used_offset, tracker->dx, tracker->dy, cur_t - tracker->time); #endif } return result; } #undef TRACKER_INDEX #undef TRACKER /** * Perform velocity approximation based on 2D 'mickeys' (mouse motion delta). * return true if non-visible state reset is suggested */ BOOL ProcessVelocityData2D(DeviceVelocityPtr vel, double dx, double dy, int time) { double velocity; vel->last_velocity = vel->velocity; FeedTrackers(vel, dx, dy, time); velocity = QueryTrackers(vel, time); DebugAccelF("velocity is %f\n", velocity); vel->velocity = velocity; return velocity == 0; } /** * this flattens significant ( > 1) mickeys a little bit for more steady * constant-velocity response */ static inline double ApplySimpleSoftening(double prev_delta, double delta) { double result = delta; if (delta < -1.0 || delta > 1.0) { if (delta > prev_delta) result -= 0.5; else if (delta < prev_delta) result += 0.5; } return result; } /** * Soften the delta based on previous deltas stored in vel. * * @param[in,out] fdx Delta X, modified in-place. * @param[in,out] fdx Delta Y, modified in-place. */ static void ApplySoftening(DeviceVelocityPtr vel, double *fdx, double *fdy) { if (vel->use_softening) { *fdx = ApplySimpleSoftening(vel->last_dx, *fdx); *fdy = ApplySimpleSoftening(vel->last_dy, *fdy); } } static void ApplyConstantDeceleration(DeviceVelocityPtr vel, double *fdx, double *fdy) { *fdx *= vel->const_acceleration; *fdy *= vel->const_acceleration; } /* * compute the acceleration for given velocity and enforce min_acceleration */ double BasicComputeAcceleration(DeviceIntPtr dev, DeviceVelocityPtr vel, double velocity, double threshold, double acc) { double result; result = vel->Profile(dev, vel, velocity, threshold, acc); /* enforce min_acceleration */ if (result < vel->min_acceleration) result = vel->min_acceleration; return result; } /** * Compute acceleration. Takes into account averaging, nv-reset, etc. * If the velocity has changed, an average is taken of 6 velocity factors: * current velocity, last velocity and 4 times the average between the two. */ static double ComputeAcceleration(DeviceIntPtr dev, DeviceVelocityPtr vel, double threshold, double acc) { double result; if (vel->velocity <= 0) { DebugAccelF("profile skipped\n"); /* * If we have no idea about device velocity, don't pretend it. */ return 1; } if (vel->average_accel && vel->velocity != vel->last_velocity) { /* use simpson's rule to average acceleration between * current and previous velocity. * Though being the more natural choice, it causes a minor delay * in comparison, so it can be disabled. */ result = BasicComputeAcceleration(dev, vel, vel->velocity, threshold, acc); result += BasicComputeAcceleration(dev, vel, vel->last_velocity, threshold, acc); result += 4.0 * BasicComputeAcceleration(dev, vel, (vel->last_velocity + vel->velocity) / 2, threshold, acc); result /= 6.0; DebugAccelF("profile average [%.2f ... %.2f] is %.3f\n", vel->velocity, vel->last_velocity, result); } else { result = BasicComputeAcceleration(dev, vel, vel->velocity, threshold, acc); DebugAccelF("profile sample [%.2f] is %.3f\n", vel->velocity, result); } return result; } /***************************************** * Acceleration functions and profiles ****************************************/ /** * Polynomial function similar previous one, but with f(1) = 1 */ static double PolynomialAccelerationProfile(DeviceIntPtr dev, DeviceVelocityPtr vel, double velocity, double ignored, double acc) { return pow(velocity, (acc - 1.0) * 0.5); } /** * returns acceleration for velocity. * This profile selects the two functions like the old scheme did */ static double ClassicProfile(DeviceIntPtr dev, DeviceVelocityPtr vel, double velocity, double threshold, double acc) { if (threshold > 0) { return SimpleSmoothProfile(dev, vel, velocity, threshold, acc); } else { return PolynomialAccelerationProfile(dev, vel, velocity, 0, acc); } } /** * Power profile * This has a completely smooth transition curve, i.e. no jumps in the * derivatives. * * This has the expense of overall response dependency on min-acceleration. * In effect, min_acceleration mimics const_acceleration in this profile. */ static double PowerProfile(DeviceIntPtr dev, DeviceVelocityPtr vel, double velocity, double threshold, double acc) { double vel_dist; acc = (acc - 1.0) * 0.1 + 1.0; /* without this, acc of 2 is unuseable */ if (velocity <= threshold) return vel->min_acceleration; vel_dist = velocity - threshold; return (pow(acc, vel_dist)) * vel->min_acceleration; } /** * just a smooth function in [0..1] -> [0..1] * - point symmetry at 0.5 * - f'(0) = f'(1) = 0 * - starts faster than a sinoid * - smoothness C1 (Cinf if you dare to ignore endpoints) */ static inline double CalcPenumbralGradient(double x) { x *= 2.0; x -= 1.0; return 0.5 + (x * sqrt(1.0 - x * x) + asin(x)) / M_PI; } /** * acceleration function similar to classic accelerated/unaccelerated, * but with smooth transition in between (and towards zero for adaptive dec.). */ static double SimpleSmoothProfile(DeviceIntPtr dev, DeviceVelocityPtr vel, double velocity, double threshold, double acc) { if (velocity < 1.0f) return CalcPenumbralGradient(0.5 + velocity * 0.5) * 2.0f - 1.0f; if (threshold < 1.0f) threshold = 1.0f; if (velocity <= threshold) return 1; velocity /= threshold; if (velocity >= acc) return acc; else return 1.0f + (CalcPenumbralGradient(velocity / acc) * (acc - 1.0f)); } /** * This profile uses the first half of the penumbral gradient as a start * and then scales linearly. */ static double SmoothLinearProfile(DeviceIntPtr dev, DeviceVelocityPtr vel, double velocity, double threshold, double acc) { double res, nv; if (acc > 1.0) acc -= 1.0; /*this is so acc = 1 is no acceleration */ else return 1.0; nv = (velocity - threshold) * acc * 0.5; if (nv < 0) { res = 0; } else if (nv < 2) { res = CalcPenumbralGradient(nv * 0.25) * 2.0; } else { nv -= 2.0; res = nv * 2.0 / M_PI /* steepness of gradient at 0.5 */ + 1.0; /* gradient crosses 2|1 */ } res += vel->min_acceleration; return res; } /** * From 0 to threshold, the response graduates smoothly from min_accel to * acceleration. Beyond threshold it is exactly the specified acceleration. */ static double SmoothLimitedProfile(DeviceIntPtr dev, DeviceVelocityPtr vel, double velocity, double threshold, double acc) { double res; if (velocity >= threshold || threshold == 0.0) return acc; velocity /= threshold; /* should be [0..1[ now */ res = CalcPenumbralGradient(velocity) * (acc - vel->min_acceleration); return vel->min_acceleration + res; } static double LinearProfile(DeviceIntPtr dev, DeviceVelocityPtr vel, double velocity, double threshold, double acc) { return acc * velocity; } static double NoProfile(DeviceIntPtr dev, DeviceVelocityPtr vel, double velocity, double threshold, double acc) { return 1.0; } static PointerAccelerationProfileFunc GetAccelerationProfile(DeviceVelocityPtr vel, int profile_num) { switch (profile_num) { case AccelProfileClassic: return ClassicProfile; case AccelProfileDeviceSpecific: return vel->deviceSpecificProfile; case AccelProfilePolynomial: return PolynomialAccelerationProfile; case AccelProfileSmoothLinear: return SmoothLinearProfile; case AccelProfileSimple: return SimpleSmoothProfile; case AccelProfilePower: return PowerProfile; case AccelProfileLinear: return LinearProfile; case AccelProfileSmoothLimited: return SmoothLimitedProfile; case AccelProfileNone: return NoProfile; default: return NULL; } } /** * Set the profile by number. * Intended to make profiles exchangeable at runtime. * If you created a profile, give it a number here and in the header to * make it selectable. In case some profile-specific init is needed, here * would be a good place, since FreeVelocityData() also calls this with * PROFILE_UNINITIALIZE. * * returns FALSE if profile number is unavailable, TRUE otherwise. */ int SetAccelerationProfile(DeviceVelocityPtr vel, int profile_num) { PointerAccelerationProfileFunc profile; profile = GetAccelerationProfile(vel, profile_num); if (profile == NULL && profile_num != PROFILE_UNINITIALIZE) return FALSE; /* Here one could free old profile-private data */ free(vel->profile_private); vel->profile_private = NULL; /* Here one could init profile-private data */ vel->Profile = profile; vel->statistics.profile_number = profile_num; return TRUE; } /********************************************** * driver interaction **********************************************/ /** * device-specific profile * * The device-specific profile is intended as a hook for a driver * which may want to provide an own acceleration profile. * It should not rely on profile-private data, instead * it should do init/uninit in the driver (ie. with DEVICE_INIT and friends). * Users may override or choose it. */ void SetDeviceSpecificAccelerationProfile(DeviceVelocityPtr vel, PointerAccelerationProfileFunc profile) { if (vel) vel->deviceSpecificProfile = profile; } /** * Use this function to obtain a DeviceVelocityPtr for a device. Will return NULL if * the predictable acceleration scheme is not in effect. */ DeviceVelocityPtr GetDevicePredictableAccelData(DeviceIntPtr dev) { BUG_RETURN_VAL(!dev, NULL); if (dev->valuator && dev->valuator->accelScheme.AccelSchemeProc == acceleratePointerPredictable && dev->valuator->accelScheme.accelData != NULL) { return ((PredictableAccelSchemePtr) dev->valuator->accelScheme.accelData)->vel; } return NULL; } /******************************** * acceleration schemes *******************************/ /** * Modifies valuators in-place. * This version employs a velocity approximation algorithm to * enable fine-grained predictable acceleration profiles. */ void acceleratePointerPredictable(DeviceIntPtr dev, ValuatorMask *val, CARD32 evtime) { double dx = 0, dy = 0; DeviceVelocityPtr velocitydata = GetDevicePredictableAccelData(dev); Bool soften = TRUE; if (valuator_mask_num_valuators(val) == 0 || !velocitydata) return; if (velocitydata->statistics.profile_number == AccelProfileNone && velocitydata->const_acceleration == 1.0) { return; /*we're inactive anyway, so skip the whole thing. */ } if (valuator_mask_isset(val, 0)) { dx = valuator_mask_get_double(val, 0); } if (valuator_mask_isset(val, 1)) { dy = valuator_mask_get_double(val, 1); } if (dx != 0.0 || dy != 0.0) { /* reset non-visible state? */ if (ProcessVelocityData2D(velocitydata, dx, dy, evtime)) { soften = FALSE; } if (dev->ptrfeed && dev->ptrfeed->ctrl.num) { double mult; /* invoke acceleration profile to determine acceleration */ mult = ComputeAcceleration(dev, velocitydata, dev->ptrfeed->ctrl.threshold, (double) dev->ptrfeed->ctrl.num / (double) dev->ptrfeed->ctrl.den); DebugAccelF("mult is %f\n", mult); if (mult != 1.0 || velocitydata->const_acceleration != 1.0) { if (mult > 1.0 && soften) ApplySoftening(velocitydata, &dx, &dy); ApplyConstantDeceleration(velocitydata, &dx, &dy); if (dx != 0.0) valuator_mask_set_double(val, 0, mult * dx); if (dy != 0.0) valuator_mask_set_double(val, 1, mult * dy); DebugAccelF("delta x:%.3f y:%.3f\n", mult * dx, mult * dy); } } } /* remember last motion delta (for softening/slow movement treatment) */ velocitydata->last_dx = dx; velocitydata->last_dy = dy; } /** * Originally a part of xf86PostMotionEvent; modifies valuators * in-place. Retained mostly for embedded scenarios. */ void acceleratePointerLightweight(DeviceIntPtr dev, ValuatorMask *val, CARD32 ignored) { double mult = 0.0, tmpf; double dx = 0.0, dy = 0.0; if (valuator_mask_isset(val, 0)) { dx = valuator_mask_get(val, 0); } if (valuator_mask_isset(val, 1)) { dy = valuator_mask_get(val, 1); } if (valuator_mask_num_valuators(val) == 0) return; if (dev->ptrfeed && dev->ptrfeed->ctrl.num) { /* modeled from xf86Events.c */ if (dev->ptrfeed->ctrl.threshold) { if ((fabs(dx) + fabs(dy)) >= dev->ptrfeed->ctrl.threshold) { if (dx != 0.0) { tmpf = (dx * (double) (dev->ptrfeed->ctrl.num)) / (double) (dev->ptrfeed->ctrl.den); valuator_mask_set_double(val, 0, tmpf); } if (dy != 0.0) { tmpf = (dy * (double) (dev->ptrfeed->ctrl.num)) / (double) (dev->ptrfeed->ctrl.den); valuator_mask_set_double(val, 1, tmpf); } } } else { mult = pow(dx * dx + dy * dy, ((double) (dev->ptrfeed->ctrl.num) / (double) (dev->ptrfeed->ctrl.den) - 1.0) / 2.0) / 2.0; if (dx != 0.0) valuator_mask_set_double(val, 0, mult * dx); if (dy != 0.0) valuator_mask_set_double(val, 1, mult * dy); } } }