xserver-multidpi/dix/ptrveloc.c
Keith Packard 9838b7032e Introduce a consistent coding style
This is strictly the application of the script 'x-indent-all.sh'
from util/modular. Compared to the patch that Daniel posted in
January, I've added a few indent flags:

	-bap
	-psl
	-T PrivatePtr
	-T pmWait
	-T _XFUNCPROTOBEGIN
	-T _XFUNCPROTOEND
	-T _X_EXPORT

The typedefs were needed to make the output of sdksyms.sh match the
previous output, otherwise, the code is formatted badly enough that
sdksyms.sh generates incorrect output.

The generated code was compared with the previous version and found to
be essentially identical -- "assert" line numbers and BUILD_TIME were
the only differences found.

The comparison was done with this script:

dir1=$1
dir2=$2

for dir in $dir1 $dir2; do
	(cd $dir && find . -name '*.o' | while read file; do
		dir=`dirname $file`
		base=`basename $file .o`
		dump=$dir/$base.dump
		objdump -d $file > $dump
	done)
done

find $dir1 -name '*.dump' | while read dump; do
	otherdump=`echo $dump | sed "s;$dir1;$dir2;"`
	diff -u $dump $otherdump
done

Signed-off-by: Keith Packard <keithp@keithp.com>
Acked-by: Daniel Stone <daniel@fooishbar.org>
Acked-by: Alan Coopersmith <alan.coopersmith@oracle.com>
2012-03-21 13:54:42 -07:00

1187 lines
35 KiB
C

/*
*
* 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 <dix-config.h>
#endif
#include <math.h>
#include <ptrveloc.h>
#include <exevents.h>
#include <X11/Xatom.h>
#include <os.h>
#include <xserver-properties.h>
/*****************************************************************************
* 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 */
int
SetAccelerationProfile(DeviceVelocityPtr vel, int profile_num);
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 ErrorF
#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 milisecond 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)
return FALSE;
InitVelocityData(vel);
schemeData->vel = vel;
scheme.accelData = schemeData;
if (!InitializePredictableAccelerationProperties(dev, vel, 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.
*/
OsBlockSignals();
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;
OsReleaseSignals();
}
}
/*************************
* 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 >= 1.0f) ? Success : BadValue;
}
if (v >= 1.0f)
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("(dix ptracc) 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 calcualted 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("(dix prtacc) motion [dx: %i dy: %i dir:%i diff: %i]\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 inital 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("(dix prtacc) query: tracker too old\n");
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("(dix prtacc) 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("(dix prtacc) 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("(dix prtacc) 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("(dix prtacc) result: offset %i [dx: %i dy: %i 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);
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_acceleartion
*/
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("(dix ptracc) 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.0f * BasicComputeAcceleration(dev, vel,
(vel->last_velocity +
vel->velocity) / 2, threshold,
acc);
result /= 6.0f;
DebugAccelF("(dix ptracc) profile average [%.2f ... %.2f] is %.3f\n",
vel->velocity, vel->last_velocity, result);
}
else {
result = BasicComputeAcceleration(dev, vel,
vel->velocity, threshold, acc);
DebugAccelF("(dix ptracc) profile sample [%.2f] is %.3f\n",
vel->velocity, res);
}
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.1f + 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.0f;
x -= 1.0f;
return 0.5f + (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.0f)
acc -= 1.0f; /*this is so acc = 1 is no acceleration */
else
return 1.0f;
nv = (velocity - threshold) * acc * 0.5f;
if (nv < 0) {
res = 0;
}
else if (nv < 2) {
res = CalcPenumbralGradient(nv * 0.25f) * 2.0f;
}
else {
nv -= 2.0f;
res = nv * 2.0f / M_PI /* steepness of gradient at 0.5 */
+ 1.0f; /* 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.0f)
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.0f;
}
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)
{
/*sanity check */
if (!dev) {
ErrorF("[dix] accel: DeviceIntPtr was NULL");
return 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.0f) {
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);
if (mult != 1.0f || velocitydata->const_acceleration != 1.0f) {
if (mult > 1.0f && 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("pos (%i | %i) 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);
}
}
}