3 * Copyright © 2006-2009 Simon Thum simon dot thum at gmx dot de
5 * Permission is hereby granted, free of charge, to any person obtaining a
6 * copy of this software and associated documentation files (the "Software"),
7 * to deal in the Software without restriction, including without limitation
8 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
9 * and/or sell copies of the Software, and to permit persons to whom the
10 * Software is furnished to do so, subject to the following conditions:
12 * The above copyright notice and this permission notice (including the next
13 * paragraph) shall be included in all copies or substantial portions of the
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
19 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
22 * DEALINGS IN THE SOFTWARE.
25 #ifdef HAVE_DIX_CONFIG_H
26 #include <dix-config.h>
32 #include <X11/Xatom.h>
35 #include <xserver-properties.h>
37 /*****************************************************************************
38 * Predictable pointer acceleration
40 * 2006-2009 by Simon Thum (simon [dot] thum [at] gmx de)
42 * Serves 3 complementary functions:
43 * 1) provide a sophisticated ballistic velocity estimate to improve
44 * the relation between velocity (of the device) and acceleration
45 * 2) make arbitrary acceleration profiles possible
46 * 3) decelerate by two means (constant and adaptive) if enabled
48 * Important concepts are the
51 * which selects the basic algorithm
52 * (see devices.c/InitPointerAccelerationScheme)
54 * which returns an acceleration
55 * for a given velocity
57 * The profile can be selected by the user at runtime.
58 * The classic profile is intended to cleanly perform old-style
59 * function selection (threshold =/!= 0)
61 ****************************************************************************/
65 SimpleSmoothProfile(DeviceIntPtr dev
, DeviceVelocityPtr vel
, double velocity
,
66 double threshold
, double acc
);
67 static PointerAccelerationProfileFunc
68 GetAccelerationProfile(DeviceVelocityPtr vel
, int profile_num
);
70 InitializePredictableAccelerationProperties(DeviceIntPtr
,
72 PredictableAccelSchemePtr
);
74 DeletePredictableAccelerationProperties(DeviceIntPtr
,
75 PredictableAccelSchemePtr
);
77 /*#define PTRACCEL_DEBUGGING*/
79 #ifdef PTRACCEL_DEBUGGING
80 #define DebugAccelF(...) ErrorFSigSafe("dix/ptraccel: " __VA_ARGS__)
82 #define DebugAccelF(...) /* */
85 /********************************
87 *******************************/
89 /* some int which is not a profile number */
90 #define PROFILE_UNINITIALIZE (-100)
93 * Init DeviceVelocity struct so it should match the average case
96 InitVelocityData(DeviceVelocityPtr vel
)
98 memset(vel
, 0, sizeof(DeviceVelocityRec
));
100 vel
->corr_mul
= 10.0; /* dots per 10 milisecond should be usable */
101 vel
->const_acceleration
= 1.0; /* no acceleration/deceleration */
102 vel
->reset_time
= 300;
103 vel
->use_softening
= 1;
104 vel
->min_acceleration
= 1.0; /* don't decelerate */
105 vel
->max_rel_diff
= 0.2;
107 vel
->initial_range
= 2;
108 vel
->average_accel
= TRUE
;
109 SetAccelerationProfile(vel
, AccelProfileClassic
);
110 InitTrackers(vel
, 16);
114 * Clean up DeviceVelocityRec
117 FreeVelocityData(DeviceVelocityPtr vel
)
120 SetAccelerationProfile(vel
, PROFILE_UNINITIALIZE
);
124 * Init predictable scheme
127 InitPredictableAccelerationScheme(DeviceIntPtr dev
,
128 ValuatorAccelerationPtr protoScheme
)
130 DeviceVelocityPtr vel
;
131 ValuatorAccelerationRec scheme
;
132 PredictableAccelSchemePtr schemeData
;
134 scheme
= *protoScheme
;
135 vel
= calloc(1, sizeof(DeviceVelocityRec
));
136 schemeData
= calloc(1, sizeof(PredictableAccelSchemeRec
));
137 if (!vel
|| !schemeData
)
139 InitVelocityData(vel
);
140 schemeData
->vel
= vel
;
141 scheme
.accelData
= schemeData
;
142 if (!InitializePredictableAccelerationProperties(dev
, vel
, schemeData
))
144 /* all fine, assign scheme to device */
145 dev
->valuator
->accelScheme
= scheme
;
153 AccelerationDefaultCleanup(DeviceIntPtr dev
)
155 DeviceVelocityPtr vel
= GetDevicePredictableAccelData(dev
);
158 /* the proper guarantee would be that we're not inside of
159 * AccelSchemeProc(), but that seems impossible. Schemes don't get
160 * switched often anyway.
163 dev
->valuator
->accelScheme
.AccelSchemeProc
= NULL
;
164 FreeVelocityData(vel
);
166 DeletePredictableAccelerationProperties(dev
,
167 (PredictableAccelSchemePtr
)
168 dev
->valuator
->accelScheme
.
170 free(dev
->valuator
->accelScheme
.accelData
);
171 dev
->valuator
->accelScheme
.accelData
= NULL
;
176 /*************************
177 * Input property support
178 ************************/
184 AccelSetProfileProperty(DeviceIntPtr dev
, Atom atom
,
185 XIPropertyValuePtr val
, BOOL checkOnly
)
187 DeviceVelocityPtr vel
;
188 int profile
, *ptr
= &profile
;
192 if (atom
!= XIGetKnownProperty(ACCEL_PROP_PROFILE_NUMBER
))
195 vel
= GetDevicePredictableAccelData(dev
);
198 rc
= XIPropToInt(val
, &nelem
, &ptr
);
204 if (GetAccelerationProfile(vel
, profile
) == NULL
)
208 SetAccelerationProfile(vel
, profile
);
214 AccelInitProfileProperty(DeviceIntPtr dev
, DeviceVelocityPtr vel
)
216 int profile
= vel
->statistics
.profile_number
;
217 Atom prop_profile_number
= XIGetKnownProperty(ACCEL_PROP_PROFILE_NUMBER
);
219 XIChangeDeviceProperty(dev
, prop_profile_number
, XA_INTEGER
, 32,
220 PropModeReplace
, 1, &profile
, FALSE
);
221 XISetDevicePropertyDeletable(dev
, prop_profile_number
, FALSE
);
222 return XIRegisterPropertyHandler(dev
, AccelSetProfileProperty
, NULL
, NULL
);
226 * constant deceleration
229 AccelSetDecelProperty(DeviceIntPtr dev
, Atom atom
,
230 XIPropertyValuePtr val
, BOOL checkOnly
)
232 DeviceVelocityPtr vel
;
237 if (atom
!= XIGetKnownProperty(ACCEL_PROP_CONSTANT_DECELERATION
))
240 vel
= GetDevicePredictableAccelData(dev
);
243 rc
= XIPropToFloat(val
, &nelem
, &ptr
);
248 return (v
> 0) ? Success
: BadValue
;
251 vel
->const_acceleration
= 1 / v
;
257 AccelInitDecelProperty(DeviceIntPtr dev
, DeviceVelocityPtr vel
)
259 float fval
= 1.0 / vel
->const_acceleration
;
260 Atom prop_const_decel
=
261 XIGetKnownProperty(ACCEL_PROP_CONSTANT_DECELERATION
);
262 XIChangeDeviceProperty(dev
, prop_const_decel
,
263 XIGetKnownProperty(XATOM_FLOAT
), 32, PropModeReplace
,
265 XISetDevicePropertyDeletable(dev
, prop_const_decel
, FALSE
);
266 return XIRegisterPropertyHandler(dev
, AccelSetDecelProperty
, NULL
, NULL
);
270 * adaptive deceleration
273 AccelSetAdaptDecelProperty(DeviceIntPtr dev
, Atom atom
,
274 XIPropertyValuePtr val
, BOOL checkOnly
)
276 DeviceVelocityPtr veloc
;
281 if (atom
!= XIGetKnownProperty(ACCEL_PROP_ADAPTIVE_DECELERATION
))
284 veloc
= GetDevicePredictableAccelData(dev
);
287 rc
= XIPropToFloat(val
, &nelem
, &ptr
);
292 return (v
>= 1.0f
) ? Success
: BadValue
;
296 veloc
->min_acceleration
= 1 / v
;
302 AccelInitAdaptDecelProperty(DeviceIntPtr dev
, DeviceVelocityPtr vel
)
304 float fval
= 1.0 / vel
->min_acceleration
;
305 Atom prop_adapt_decel
=
306 XIGetKnownProperty(ACCEL_PROP_ADAPTIVE_DECELERATION
);
308 XIChangeDeviceProperty(dev
, prop_adapt_decel
,
309 XIGetKnownProperty(XATOM_FLOAT
), 32, PropModeReplace
,
311 XISetDevicePropertyDeletable(dev
, prop_adapt_decel
, FALSE
);
312 return XIRegisterPropertyHandler(dev
, AccelSetAdaptDecelProperty
, NULL
,
320 AccelSetScaleProperty(DeviceIntPtr dev
, Atom atom
,
321 XIPropertyValuePtr val
, BOOL checkOnly
)
323 DeviceVelocityPtr vel
;
328 if (atom
!= XIGetKnownProperty(ACCEL_PROP_VELOCITY_SCALING
))
331 vel
= GetDevicePredictableAccelData(dev
);
334 rc
= XIPropToFloat(val
, &nelem
, &ptr
);
340 return (v
> 0) ? Success
: BadValue
;
350 AccelInitScaleProperty(DeviceIntPtr dev
, DeviceVelocityPtr vel
)
352 float fval
= vel
->corr_mul
;
353 Atom prop_velo_scale
= XIGetKnownProperty(ACCEL_PROP_VELOCITY_SCALING
);
355 XIChangeDeviceProperty(dev
, prop_velo_scale
,
356 XIGetKnownProperty(XATOM_FLOAT
), 32, PropModeReplace
,
358 XISetDevicePropertyDeletable(dev
, prop_velo_scale
, FALSE
);
359 return XIRegisterPropertyHandler(dev
, AccelSetScaleProperty
, NULL
, NULL
);
363 InitializePredictableAccelerationProperties(DeviceIntPtr dev
,
364 DeviceVelocityPtr vel
,
365 PredictableAccelSchemePtr
368 int num_handlers
= 4;
373 schemeData
->prop_handlers
= calloc(num_handlers
, sizeof(long));
374 if (!schemeData
->prop_handlers
)
376 schemeData
->num_prop_handlers
= num_handlers
;
377 schemeData
->prop_handlers
[0] = AccelInitProfileProperty(dev
, vel
);
378 schemeData
->prop_handlers
[1] = AccelInitDecelProperty(dev
, vel
);
379 schemeData
->prop_handlers
[2] = AccelInitAdaptDecelProperty(dev
, vel
);
380 schemeData
->prop_handlers
[3] = AccelInitScaleProperty(dev
, vel
);
386 DeletePredictableAccelerationProperties(DeviceIntPtr dev
,
387 PredictableAccelSchemePtr scheme
)
389 DeviceVelocityPtr vel
;
393 prop
= XIGetKnownProperty(ACCEL_PROP_VELOCITY_SCALING
);
394 XIDeleteDeviceProperty(dev
, prop
, FALSE
);
395 prop
= XIGetKnownProperty(ACCEL_PROP_ADAPTIVE_DECELERATION
);
396 XIDeleteDeviceProperty(dev
, prop
, FALSE
);
397 prop
= XIGetKnownProperty(ACCEL_PROP_CONSTANT_DECELERATION
);
398 XIDeleteDeviceProperty(dev
, prop
, FALSE
);
399 prop
= XIGetKnownProperty(ACCEL_PROP_PROFILE_NUMBER
);
400 XIDeleteDeviceProperty(dev
, prop
, FALSE
);
402 vel
= GetDevicePredictableAccelData(dev
);
404 for (i
= 0; i
< scheme
->num_prop_handlers
; i
++)
405 if (scheme
->prop_handlers
[i
])
406 XIUnregisterPropertyHandler(dev
, scheme
->prop_handlers
[i
]);
409 free(scheme
->prop_handlers
);
410 scheme
->prop_handlers
= NULL
;
411 scheme
->num_prop_handlers
= 0;
415 /*********************
417 ********************/
420 InitTrackers(DeviceVelocityPtr vel
, int ntracker
)
423 ErrorF("invalid number of trackers\n");
427 vel
->tracker
= (MotionTrackerPtr
) calloc(ntracker
, sizeof(MotionTracker
));
428 vel
->num_tracker
= ntracker
;
444 * return a bit field of possible directions.
445 * There's no reason against widening to more precise directions (<45 degrees),
446 * should it not perform well. All this is needed for is sort out non-linear
447 * motion, so precision isn't paramount. However, one should not flag direction
448 * too narrow, since it would then cut the linear segment to zero size way too
451 * @return A bitmask for N, NE, S, SE, etc. indicating the directions for
455 DoGetDirection(int dx
, int dy
)
459 /* on insignificant mickeys, flag 135 degrees */
460 if (abs(dx
) < 2 && abs(dy
) < 2) {
461 /* first check diagonal cases */
462 if (dx
> 0 && dy
> 0)
464 else if (dx
> 0 && dy
< 0)
466 else if (dx
< 0 && dy
< 0)
468 else if (dx
< 0 && dy
> 0)
470 /* check axis-aligned directions */
480 dir
= UNDEFINED
; /* shouldn't happen */
482 else { /* compute angle and set appropriate flags */
489 * Add 360° to avoid r become negative since C has no well-defined
490 * modulo for such cases. Then divide by 45° to get the octant
492 * 0 <= r <= 1 is [0-45]°
493 * 1 <= r <= 2 is [45-90]°
495 * But we add extra 90° to match up with our N, S, etc. defines up
496 * there, rest stays the same.
498 r
= (r
+ (M_PI
* 2.5)) / (M_PI
/ 4);
499 /* this intends to flag 2 directions (45 degrees),
500 * except on very well-aligned mickeys. */
501 i1
= (int) (r
+ 0.1) % 8;
502 i2
= (int) (r
+ 0.9) % 8;
503 if (i1
< 0 || i1
> 7 || i2
< 0 || i2
> 7)
504 dir
= UNDEFINED
; /* shouldn't happen */
506 dir
= (1 << i1
| 1 << i2
);
511 #define DIRECTION_CACHE_RANGE 5
512 #define DIRECTION_CACHE_SIZE (DIRECTION_CACHE_RANGE*2+1)
514 /* cache DoGetDirection().
515 * To avoid excessive use of direction calculation, cache the values for
516 * [-5..5] for both x/y. Anything outside of that is calcualted on the fly.
518 * @return A bitmask for N, NE, S, SE, etc. indicating the directions for
522 GetDirection(int dx
, int dy
)
524 static int cache
[DIRECTION_CACHE_SIZE
][DIRECTION_CACHE_SIZE
];
527 if (abs(dx
) <= DIRECTION_CACHE_RANGE
&& abs(dy
) <= DIRECTION_CACHE_RANGE
) {
529 dir
= cache
[DIRECTION_CACHE_RANGE
+ dx
][DIRECTION_CACHE_RANGE
+ dy
];
531 dir
= DoGetDirection(dx
, dy
);
532 cache
[DIRECTION_CACHE_RANGE
+ dx
][DIRECTION_CACHE_RANGE
+ dy
] = dir
;
537 dir
= DoGetDirection(dx
, dy
);
543 #undef DIRECTION_CACHE_RANGE
544 #undef DIRECTION_CACHE_SIZE
546 /* convert offset (age) to array index */
547 #define TRACKER_INDEX(s, d) (((s)->num_tracker + (s)->cur_tracker - (d)) % (s)->num_tracker)
548 #define TRACKER(s, d) &(s)->tracker[TRACKER_INDEX(s,d)]
551 * Add the delta motion to each tracker, then reset the latest tracker to
552 * 0/0 and set it as the current one.
555 FeedTrackers(DeviceVelocityPtr vel
, double dx
, double dy
, int cur_t
)
559 for (n
= 0; n
< vel
->num_tracker
; n
++) {
560 vel
->tracker
[n
].dx
+= dx
;
561 vel
->tracker
[n
].dy
+= dy
;
563 n
= (vel
->cur_tracker
+ 1) % vel
->num_tracker
;
564 vel
->tracker
[n
].dx
= 0.0;
565 vel
->tracker
[n
].dy
= 0.0;
566 vel
->tracker
[n
].time
= cur_t
;
567 vel
->tracker
[n
].dir
= GetDirection(dx
, dy
);
568 DebugAccelF("motion [dx: %f dy: %f dir:%d diff: %d]\n",
569 dx
, dy
, vel
->tracker
[n
].dir
,
570 cur_t
- vel
->tracker
[vel
->cur_tracker
].time
);
571 vel
->cur_tracker
= n
;
575 * calc velocity for given tracker, with
577 * This assumes linear motion.
580 CalcTracker(const MotionTracker
* tracker
, int cur_t
)
582 double dist
= sqrt(tracker
->dx
* tracker
->dx
+ tracker
->dy
* tracker
->dy
);
583 int dtime
= cur_t
- tracker
->time
;
588 return 0; /* synonymous for NaN, since we're not C99 */
591 /* find the most plausible velocity. That is, the most distant
592 * (in time) tracker which isn't too old, the movement vector was
593 * in the same octant, and where the velocity is within an
594 * acceptable range to the inital velocity.
596 * @return The tracker's velocity or 0 if the above conditions are unmet
599 QueryTrackers(DeviceVelocityPtr vel
, int cur_t
)
601 int offset
, dir
= UNDEFINED
, used_offset
= -1, age_ms
;
603 /* initial velocity: a low-offset, valid velocity */
604 double initial_velocity
= 0, result
= 0, velocity_diff
;
605 double velocity_factor
= vel
->corr_mul
* vel
->const_acceleration
; /* premultiply */
607 /* loop from current to older data */
608 for (offset
= 1; offset
< vel
->num_tracker
; offset
++) {
609 MotionTracker
*tracker
= TRACKER(vel
, offset
);
610 double tracker_velocity
;
612 age_ms
= cur_t
- tracker
->time
;
614 /* bail out if data is too old and protect from overrun */
615 if (age_ms
>= vel
->reset_time
|| age_ms
< 0) {
616 DebugAccelF("query: tracker too old (reset after %d, age is %d)\n",
617 vel
->reset_time
, age_ms
);
622 * this heuristic avoids using the linear-motion velocity formula
623 * in CalcTracker() on motion that isn't exactly linear. So to get
624 * even more precision we could subdivide as a final step, so possible
625 * non-linearities are accounted for.
628 if (dir
== 0) { /* we've changed octant of movement (e.g. NE → NW) */
629 DebugAccelF("query: no longer linear\n");
630 /* instead of breaking it we might also inspect the partition after,
631 * but actual improvement with this is probably rare. */
635 tracker_velocity
= CalcTracker(tracker
, cur_t
) * velocity_factor
;
637 if ((initial_velocity
== 0 || offset
<= vel
->initial_range
) &&
638 tracker_velocity
!= 0) {
639 /* set initial velocity and result */
640 result
= initial_velocity
= tracker_velocity
;
641 used_offset
= offset
;
643 else if (initial_velocity
!= 0 && tracker_velocity
!= 0) {
644 velocity_diff
= fabs(initial_velocity
- tracker_velocity
);
646 if (velocity_diff
> vel
->max_diff
&&
647 velocity_diff
/ (initial_velocity
+ tracker_velocity
) >=
649 /* we're not in range, quit - it won't get better. */
650 DebugAccelF("query: tracker too different:"
651 " old %2.2f initial %2.2f diff: %2.2f\n",
652 tracker_velocity
, initial_velocity
, velocity_diff
);
655 /* we're in range with the initial velocity,
656 * so this result is likely better
657 * (it contains more information). */
658 result
= tracker_velocity
;
659 used_offset
= offset
;
662 if (offset
== vel
->num_tracker
) {
663 DebugAccelF("query: last tracker in effect\n");
664 used_offset
= vel
->num_tracker
- 1;
666 if (used_offset
>= 0) {
667 #ifdef PTRACCEL_DEBUGGING
668 MotionTracker
*tracker
= TRACKER(vel
, used_offset
);
670 DebugAccelF("result: offset %i [dx: %f dy: %f diff: %i]\n",
671 used_offset
, tracker
->dx
, tracker
->dy
,
672 cur_t
- tracker
->time
);
682 * Perform velocity approximation based on 2D 'mickeys' (mouse motion delta).
683 * return true if non-visible state reset is suggested
686 ProcessVelocityData2D(DeviceVelocityPtr vel
, double dx
, double dy
, int time
)
690 vel
->last_velocity
= vel
->velocity
;
692 FeedTrackers(vel
, dx
, dy
, time
);
694 velocity
= QueryTrackers(vel
, time
);
696 DebugAccelF("velocity is %f\n", velocity
);
698 vel
->velocity
= velocity
;
699 return velocity
== 0;
703 * this flattens significant ( > 1) mickeys a little bit for more steady
704 * constant-velocity response
707 ApplySimpleSoftening(double prev_delta
, double delta
)
709 double result
= delta
;
711 if (delta
< -1.0 || delta
> 1.0) {
712 if (delta
> prev_delta
)
714 else if (delta
< prev_delta
)
721 * Soften the delta based on previous deltas stored in vel.
723 * @param[in,out] fdx Delta X, modified in-place.
724 * @param[in,out] fdx Delta Y, modified in-place.
727 ApplySoftening(DeviceVelocityPtr vel
, double *fdx
, double *fdy
)
729 if (vel
->use_softening
) {
730 *fdx
= ApplySimpleSoftening(vel
->last_dx
, *fdx
);
731 *fdy
= ApplySimpleSoftening(vel
->last_dy
, *fdy
);
736 ApplyConstantDeceleration(DeviceVelocityPtr vel
, double *fdx
, double *fdy
)
738 *fdx
*= vel
->const_acceleration
;
739 *fdy
*= vel
->const_acceleration
;
743 * compute the acceleration for given velocity and enforce min_acceleration
746 BasicComputeAcceleration(DeviceIntPtr dev
,
747 DeviceVelocityPtr vel
,
748 double velocity
, double threshold
, double acc
)
753 result
= vel
->Profile(dev
, vel
, velocity
, threshold
, acc
);
755 /* enforce min_acceleration */
756 if (result
< vel
->min_acceleration
)
757 result
= vel
->min_acceleration
;
762 * Compute acceleration. Takes into account averaging, nv-reset, etc.
763 * If the velocity has changed, an average is taken of 6 velocity factors:
764 * current velocity, last velocity and 4 times the average between the two.
767 ComputeAcceleration(DeviceIntPtr dev
,
768 DeviceVelocityPtr vel
, double threshold
, double acc
)
772 if (vel
->velocity
<= 0) {
773 DebugAccelF("profile skipped\n");
775 * If we have no idea about device velocity, don't pretend it.
780 if (vel
->average_accel
&& vel
->velocity
!= vel
->last_velocity
) {
781 /* use simpson's rule to average acceleration between
782 * current and previous velocity.
783 * Though being the more natural choice, it causes a minor delay
784 * in comparison, so it can be disabled. */
786 BasicComputeAcceleration(dev
, vel
, vel
->velocity
, threshold
, acc
);
788 BasicComputeAcceleration(dev
, vel
, vel
->last_velocity
, threshold
,
791 4.0f
* BasicComputeAcceleration(dev
, vel
,
792 (vel
->last_velocity
+
797 DebugAccelF("profile average [%.2f ... %.2f] is %.3f\n",
798 vel
->velocity
, vel
->last_velocity
, result
);
801 result
= BasicComputeAcceleration(dev
, vel
,
802 vel
->velocity
, threshold
, acc
);
803 DebugAccelF("profile sample [%.2f] is %.3f\n",
804 vel
->velocity
, result
);
810 /*****************************************
811 * Acceleration functions and profiles
812 ****************************************/
815 * Polynomial function similar previous one, but with f(1) = 1
818 PolynomialAccelerationProfile(DeviceIntPtr dev
,
819 DeviceVelocityPtr vel
,
820 double velocity
, double ignored
, double acc
)
822 return pow(velocity
, (acc
- 1.0) * 0.5);
826 * returns acceleration for velocity.
827 * This profile selects the two functions like the old scheme did
830 ClassicProfile(DeviceIntPtr dev
,
831 DeviceVelocityPtr vel
,
832 double velocity
, double threshold
, double acc
)
835 return SimpleSmoothProfile(dev
, vel
, velocity
, threshold
, acc
);
838 return PolynomialAccelerationProfile(dev
, vel
, velocity
, 0, acc
);
844 * This has a completely smooth transition curve, i.e. no jumps in the
847 * This has the expense of overall response dependency on min-acceleration.
848 * In effect, min_acceleration mimics const_acceleration in this profile.
851 PowerProfile(DeviceIntPtr dev
,
852 DeviceVelocityPtr vel
,
853 double velocity
, double threshold
, double acc
)
857 acc
= (acc
- 1.0) * 0.1f
+ 1.0; /* without this, acc of 2 is unuseable */
859 if (velocity
<= threshold
)
860 return vel
->min_acceleration
;
861 vel_dist
= velocity
- threshold
;
862 return (pow(acc
, vel_dist
)) * vel
->min_acceleration
;
866 * just a smooth function in [0..1] -> [0..1]
867 * - point symmetry at 0.5
868 * - f'(0) = f'(1) = 0
869 * - starts faster than a sinoid
870 * - smoothness C1 (Cinf if you dare to ignore endpoints)
873 CalcPenumbralGradient(double x
)
877 return 0.5f
+ (x
* sqrt(1.0 - x
* x
) + asin(x
)) / M_PI
;
881 * acceleration function similar to classic accelerated/unaccelerated,
882 * but with smooth transition in between (and towards zero for adaptive dec.).
885 SimpleSmoothProfile(DeviceIntPtr dev
,
886 DeviceVelocityPtr vel
,
887 double velocity
, double threshold
, double acc
)
890 return CalcPenumbralGradient(0.5 + velocity
* 0.5) * 2.0f
- 1.0f
;
891 if (threshold
< 1.0f
)
893 if (velocity
<= threshold
)
895 velocity
/= threshold
;
899 return 1.0f
+ (CalcPenumbralGradient(velocity
/ acc
) * (acc
- 1.0f
));
903 * This profile uses the first half of the penumbral gradient as a start
904 * and then scales linearly.
907 SmoothLinearProfile(DeviceIntPtr dev
,
908 DeviceVelocityPtr vel
,
909 double velocity
, double threshold
, double acc
)
914 acc
-= 1.0f
; /*this is so acc = 1 is no acceleration */
918 nv
= (velocity
- threshold
) * acc
* 0.5f
;
924 res
= CalcPenumbralGradient(nv
* 0.25f
) * 2.0f
;
928 res
= nv
* 2.0f
/ M_PI
/* steepness of gradient at 0.5 */
929 + 1.0f
; /* gradient crosses 2|1 */
931 res
+= vel
->min_acceleration
;
936 * From 0 to threshold, the response graduates smoothly from min_accel to
937 * acceleration. Beyond threshold it is exactly the specified acceleration.
940 SmoothLimitedProfile(DeviceIntPtr dev
,
941 DeviceVelocityPtr vel
,
942 double velocity
, double threshold
, double acc
)
946 if (velocity
>= threshold
|| threshold
== 0.0f
)
949 velocity
/= threshold
; /* should be [0..1[ now */
951 res
= CalcPenumbralGradient(velocity
) * (acc
- vel
->min_acceleration
);
953 return vel
->min_acceleration
+ res
;
957 LinearProfile(DeviceIntPtr dev
,
958 DeviceVelocityPtr vel
,
959 double velocity
, double threshold
, double acc
)
961 return acc
* velocity
;
965 NoProfile(DeviceIntPtr dev
,
966 DeviceVelocityPtr vel
, double velocity
, double threshold
, double acc
)
971 static PointerAccelerationProfileFunc
972 GetAccelerationProfile(DeviceVelocityPtr vel
, int profile_num
)
974 switch (profile_num
) {
975 case AccelProfileClassic
:
976 return ClassicProfile
;
977 case AccelProfileDeviceSpecific
:
978 return vel
->deviceSpecificProfile
;
979 case AccelProfilePolynomial
:
980 return PolynomialAccelerationProfile
;
981 case AccelProfileSmoothLinear
:
982 return SmoothLinearProfile
;
983 case AccelProfileSimple
:
984 return SimpleSmoothProfile
;
985 case AccelProfilePower
:
987 case AccelProfileLinear
:
988 return LinearProfile
;
989 case AccelProfileSmoothLimited
:
990 return SmoothLimitedProfile
;
991 case AccelProfileNone
:
999 * Set the profile by number.
1000 * Intended to make profiles exchangeable at runtime.
1001 * If you created a profile, give it a number here and in the header to
1002 * make it selectable. In case some profile-specific init is needed, here
1003 * would be a good place, since FreeVelocityData() also calls this with
1004 * PROFILE_UNINITIALIZE.
1006 * returns FALSE if profile number is unavailable, TRUE otherwise.
1009 SetAccelerationProfile(DeviceVelocityPtr vel
, int profile_num
)
1011 PointerAccelerationProfileFunc profile
;
1013 profile
= GetAccelerationProfile(vel
, profile_num
);
1015 if (profile
== NULL
&& profile_num
!= PROFILE_UNINITIALIZE
)
1018 /* Here one could free old profile-private data */
1019 free(vel
->profile_private
);
1020 vel
->profile_private
= NULL
;
1021 /* Here one could init profile-private data */
1022 vel
->Profile
= profile
;
1023 vel
->statistics
.profile_number
= profile_num
;
1027 /**********************************************
1028 * driver interaction
1029 **********************************************/
1032 * device-specific profile
1034 * The device-specific profile is intended as a hook for a driver
1035 * which may want to provide an own acceleration profile.
1036 * It should not rely on profile-private data, instead
1037 * it should do init/uninit in the driver (ie. with DEVICE_INIT and friends).
1038 * Users may override or choose it.
1041 SetDeviceSpecificAccelerationProfile(DeviceVelocityPtr vel
,
1042 PointerAccelerationProfileFunc profile
)
1045 vel
->deviceSpecificProfile
= profile
;
1049 * Use this function to obtain a DeviceVelocityPtr for a device. Will return NULL if
1050 * the predictable acceleration scheme is not in effect.
1053 GetDevicePredictableAccelData(DeviceIntPtr dev
)
1055 BUG_RETURN_VAL(!dev
, NULL
);
1057 if (dev
->valuator
&&
1058 dev
->valuator
->accelScheme
.AccelSchemeProc
==
1059 acceleratePointerPredictable
&&
1060 dev
->valuator
->accelScheme
.accelData
!= NULL
) {
1062 return ((PredictableAccelSchemePtr
)
1063 dev
->valuator
->accelScheme
.accelData
)->vel
;
1068 /********************************
1069 * acceleration schemes
1070 *******************************/
1073 * Modifies valuators in-place.
1074 * This version employs a velocity approximation algorithm to
1075 * enable fine-grained predictable acceleration profiles.
1078 acceleratePointerPredictable(DeviceIntPtr dev
, ValuatorMask
*val
, CARD32 evtime
)
1080 double dx
= 0, dy
= 0;
1081 DeviceVelocityPtr velocitydata
= GetDevicePredictableAccelData(dev
);
1084 if (valuator_mask_num_valuators(val
) == 0 || !velocitydata
)
1087 if (velocitydata
->statistics
.profile_number
== AccelProfileNone
&&
1088 velocitydata
->const_acceleration
== 1.0f
) {
1089 return; /*we're inactive anyway, so skip the whole thing. */
1092 if (valuator_mask_isset(val
, 0)) {
1093 dx
= valuator_mask_get_double(val
, 0);
1096 if (valuator_mask_isset(val
, 1)) {
1097 dy
= valuator_mask_get_double(val
, 1);
1100 if (dx
!= 0.0 || dy
!= 0.0) {
1101 /* reset non-visible state? */
1102 if (ProcessVelocityData2D(velocitydata
, dx
, dy
, evtime
)) {
1106 if (dev
->ptrfeed
&& dev
->ptrfeed
->ctrl
.num
) {
1109 /* invoke acceleration profile to determine acceleration */
1110 mult
= ComputeAcceleration(dev
, velocitydata
,
1111 dev
->ptrfeed
->ctrl
.threshold
,
1112 (double) dev
->ptrfeed
->ctrl
.num
/
1113 (double) dev
->ptrfeed
->ctrl
.den
);
1115 DebugAccelF("mult is %f\n", mult
);
1116 if (mult
!= 1.0f
|| velocitydata
->const_acceleration
!= 1.0f
) {
1117 if (mult
> 1.0f
&& soften
)
1118 ApplySoftening(velocitydata
, &dx
, &dy
);
1119 ApplyConstantDeceleration(velocitydata
, &dx
, &dy
);
1122 valuator_mask_set_double(val
, 0, mult
* dx
);
1124 valuator_mask_set_double(val
, 1, mult
* dy
);
1125 DebugAccelF("delta x:%.3f y:%.3f\n", mult
* dx
, mult
* dy
);
1129 /* remember last motion delta (for softening/slow movement treatment) */
1130 velocitydata
->last_dx
= dx
;
1131 velocitydata
->last_dy
= dy
;
1135 * Originally a part of xf86PostMotionEvent; modifies valuators
1136 * in-place. Retained mostly for embedded scenarios.
1139 acceleratePointerLightweight(DeviceIntPtr dev
,
1140 ValuatorMask
*val
, CARD32 ignored
)
1142 double mult
= 0.0, tmpf
;
1143 double dx
= 0.0, dy
= 0.0;
1145 if (valuator_mask_isset(val
, 0)) {
1146 dx
= valuator_mask_get(val
, 0);
1149 if (valuator_mask_isset(val
, 1)) {
1150 dy
= valuator_mask_get(val
, 1);
1153 if (valuator_mask_num_valuators(val
) == 0)
1156 if (dev
->ptrfeed
&& dev
->ptrfeed
->ctrl
.num
) {
1157 /* modeled from xf86Events.c */
1158 if (dev
->ptrfeed
->ctrl
.threshold
) {
1159 if ((fabs(dx
) + fabs(dy
)) >= dev
->ptrfeed
->ctrl
.threshold
) {
1161 tmpf
= (dx
* (double) (dev
->ptrfeed
->ctrl
.num
)) /
1162 (double) (dev
->ptrfeed
->ctrl
.den
);
1163 valuator_mask_set_double(val
, 0, tmpf
);
1167 tmpf
= (dy
* (double) (dev
->ptrfeed
->ctrl
.num
)) /
1168 (double) (dev
->ptrfeed
->ctrl
.den
);
1169 valuator_mask_set_double(val
, 1, tmpf
);
1174 mult
= pow(dx
* dx
+ dy
* dy
,
1175 ((double) (dev
->ptrfeed
->ctrl
.num
) /
1176 (double) (dev
->ptrfeed
->ctrl
.den
) - 1.0) / 2.0) / 2.0;
1178 valuator_mask_set_double(val
, 0, mult
* dx
);
1180 valuator_mask_set_double(val
, 1, mult
* dy
);