Commit | Line | Data |
---|---|---|
a09e091a JB |
1 | /* |
2 | * | |
3 | * Copyright © 2006-2009 Simon Thum simon dot thum at gmx dot de | |
4 | * | |
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: | |
11 | * | |
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 | |
14 | * Software. | |
15 | * | |
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. | |
23 | */ | |
24 | ||
25 | #ifdef HAVE_DIX_CONFIG_H | |
26 | #include <dix-config.h> | |
27 | #endif | |
28 | ||
29 | #include <math.h> | |
30 | #include <ptrveloc.h> | |
31 | #include <exevents.h> | |
32 | #include <X11/Xatom.h> | |
33 | #include <os.h> | |
34 | ||
35 | #include <xserver-properties.h> | |
36 | ||
37 | /***************************************************************************** | |
38 | * Predictable pointer acceleration | |
39 | * | |
40 | * 2006-2009 by Simon Thum (simon [dot] thum [at] gmx de) | |
41 | * | |
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 | |
47 | * | |
48 | * Important concepts are the | |
49 | * | |
50 | * - Scheme | |
51 | * which selects the basic algorithm | |
52 | * (see devices.c/InitPointerAccelerationScheme) | |
53 | * - Profile | |
54 | * which returns an acceleration | |
55 | * for a given velocity | |
56 | * | |
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) | |
60 | * | |
61 | ****************************************************************************/ | |
62 | ||
63 | /* fwds */ | |
64 | static double | |
65 | SimpleSmoothProfile(DeviceIntPtr dev, DeviceVelocityPtr vel, double velocity, | |
66 | double threshold, double acc); | |
67 | static PointerAccelerationProfileFunc | |
68 | GetAccelerationProfile(DeviceVelocityPtr vel, int profile_num); | |
69 | static BOOL | |
70 | InitializePredictableAccelerationProperties(DeviceIntPtr, | |
71 | DeviceVelocityPtr, | |
72 | PredictableAccelSchemePtr); | |
73 | static BOOL | |
74 | DeletePredictableAccelerationProperties(DeviceIntPtr, | |
75 | PredictableAccelSchemePtr); | |
76 | ||
77 | /*#define PTRACCEL_DEBUGGING*/ | |
78 | ||
79 | #ifdef PTRACCEL_DEBUGGING | |
80 | #define DebugAccelF(...) ErrorFSigSafe("dix/ptraccel: " __VA_ARGS__) | |
81 | #else | |
82 | #define DebugAccelF(...) /* */ | |
83 | #endif | |
84 | ||
85 | /******************************** | |
86 | * Init/Uninit | |
87 | *******************************/ | |
88 | ||
89 | /* some int which is not a profile number */ | |
90 | #define PROFILE_UNINITIALIZE (-100) | |
91 | ||
92 | /** | |
93 | * Init DeviceVelocity struct so it should match the average case | |
94 | */ | |
95 | void | |
96 | InitVelocityData(DeviceVelocityPtr vel) | |
97 | { | |
98 | memset(vel, 0, sizeof(DeviceVelocityRec)); | |
99 | ||
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; | |
106 | vel->max_diff = 1.0; | |
107 | vel->initial_range = 2; | |
108 | vel->average_accel = TRUE; | |
109 | SetAccelerationProfile(vel, AccelProfileClassic); | |
110 | InitTrackers(vel, 16); | |
111 | } | |
112 | ||
113 | /** | |
114 | * Clean up DeviceVelocityRec | |
115 | */ | |
116 | void | |
117 | FreeVelocityData(DeviceVelocityPtr vel) | |
118 | { | |
119 | free(vel->tracker); | |
120 | SetAccelerationProfile(vel, PROFILE_UNINITIALIZE); | |
121 | } | |
122 | ||
123 | /** | |
124 | * Init predictable scheme | |
125 | */ | |
126 | Bool | |
127 | InitPredictableAccelerationScheme(DeviceIntPtr dev, | |
128 | ValuatorAccelerationPtr protoScheme) | |
129 | { | |
130 | DeviceVelocityPtr vel; | |
131 | ValuatorAccelerationRec scheme; | |
132 | PredictableAccelSchemePtr schemeData; | |
133 | ||
134 | scheme = *protoScheme; | |
135 | vel = calloc(1, sizeof(DeviceVelocityRec)); | |
136 | schemeData = calloc(1, sizeof(PredictableAccelSchemeRec)); | |
137 | if (!vel || !schemeData) | |
138 | return FALSE; | |
139 | InitVelocityData(vel); | |
140 | schemeData->vel = vel; | |
141 | scheme.accelData = schemeData; | |
142 | if (!InitializePredictableAccelerationProperties(dev, vel, schemeData)) | |
143 | return FALSE; | |
144 | /* all fine, assign scheme to device */ | |
145 | dev->valuator->accelScheme = scheme; | |
146 | return TRUE; | |
147 | } | |
148 | ||
149 | /** | |
150 | * Uninit scheme | |
151 | */ | |
152 | void | |
153 | AccelerationDefaultCleanup(DeviceIntPtr dev) | |
154 | { | |
155 | DeviceVelocityPtr vel = GetDevicePredictableAccelData(dev); | |
156 | ||
157 | if (vel) { | |
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. | |
161 | */ | |
162 | OsBlockSignals(); | |
163 | dev->valuator->accelScheme.AccelSchemeProc = NULL; | |
164 | FreeVelocityData(vel); | |
165 | free(vel); | |
166 | DeletePredictableAccelerationProperties(dev, | |
167 | (PredictableAccelSchemePtr) | |
168 | dev->valuator->accelScheme. | |
169 | accelData); | |
170 | free(dev->valuator->accelScheme.accelData); | |
171 | dev->valuator->accelScheme.accelData = NULL; | |
172 | OsReleaseSignals(); | |
173 | } | |
174 | } | |
175 | ||
176 | /************************* | |
177 | * Input property support | |
178 | ************************/ | |
179 | ||
180 | /** | |
181 | * choose profile | |
182 | */ | |
183 | static int | |
184 | AccelSetProfileProperty(DeviceIntPtr dev, Atom atom, | |
185 | XIPropertyValuePtr val, BOOL checkOnly) | |
186 | { | |
187 | DeviceVelocityPtr vel; | |
188 | int profile, *ptr = &profile; | |
189 | int rc; | |
190 | int nelem = 1; | |
191 | ||
192 | if (atom != XIGetKnownProperty(ACCEL_PROP_PROFILE_NUMBER)) | |
193 | return Success; | |
194 | ||
195 | vel = GetDevicePredictableAccelData(dev); | |
196 | if (!vel) | |
197 | return BadValue; | |
198 | rc = XIPropToInt(val, &nelem, &ptr); | |
199 | ||
200 | if (checkOnly) { | |
201 | if (rc) | |
202 | return rc; | |
203 | ||
204 | if (GetAccelerationProfile(vel, profile) == NULL) | |
205 | return BadValue; | |
206 | } | |
207 | else | |
208 | SetAccelerationProfile(vel, profile); | |
209 | ||
210 | return Success; | |
211 | } | |
212 | ||
213 | static long | |
214 | AccelInitProfileProperty(DeviceIntPtr dev, DeviceVelocityPtr vel) | |
215 | { | |
216 | int profile = vel->statistics.profile_number; | |
217 | Atom prop_profile_number = XIGetKnownProperty(ACCEL_PROP_PROFILE_NUMBER); | |
218 | ||
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); | |
223 | } | |
224 | ||
225 | /** | |
226 | * constant deceleration | |
227 | */ | |
228 | static int | |
229 | AccelSetDecelProperty(DeviceIntPtr dev, Atom atom, | |
230 | XIPropertyValuePtr val, BOOL checkOnly) | |
231 | { | |
232 | DeviceVelocityPtr vel; | |
233 | float v, *ptr = &v; | |
234 | int rc; | |
235 | int nelem = 1; | |
236 | ||
237 | if (atom != XIGetKnownProperty(ACCEL_PROP_CONSTANT_DECELERATION)) | |
238 | return Success; | |
239 | ||
240 | vel = GetDevicePredictableAccelData(dev); | |
241 | if (!vel) | |
242 | return BadValue; | |
243 | rc = XIPropToFloat(val, &nelem, &ptr); | |
244 | ||
245 | if (checkOnly) { | |
246 | if (rc) | |
247 | return rc; | |
248 | return (v > 0) ? Success : BadValue; | |
249 | } | |
250 | ||
251 | vel->const_acceleration = 1 / v; | |
252 | ||
253 | return Success; | |
254 | } | |
255 | ||
256 | static long | |
257 | AccelInitDecelProperty(DeviceIntPtr dev, DeviceVelocityPtr vel) | |
258 | { | |
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, | |
264 | 1, &fval, FALSE); | |
265 | XISetDevicePropertyDeletable(dev, prop_const_decel, FALSE); | |
266 | return XIRegisterPropertyHandler(dev, AccelSetDecelProperty, NULL, NULL); | |
267 | } | |
268 | ||
269 | /** | |
270 | * adaptive deceleration | |
271 | */ | |
272 | static int | |
273 | AccelSetAdaptDecelProperty(DeviceIntPtr dev, Atom atom, | |
274 | XIPropertyValuePtr val, BOOL checkOnly) | |
275 | { | |
276 | DeviceVelocityPtr veloc; | |
277 | float v, *ptr = &v; | |
278 | int rc; | |
279 | int nelem = 1; | |
280 | ||
281 | if (atom != XIGetKnownProperty(ACCEL_PROP_ADAPTIVE_DECELERATION)) | |
282 | return Success; | |
283 | ||
284 | veloc = GetDevicePredictableAccelData(dev); | |
285 | if (!veloc) | |
286 | return BadValue; | |
287 | rc = XIPropToFloat(val, &nelem, &ptr); | |
288 | ||
289 | if (checkOnly) { | |
290 | if (rc) | |
291 | return rc; | |
292 | return (v >= 1.0f) ? Success : BadValue; | |
293 | } | |
294 | ||
295 | if (v >= 1.0f) | |
296 | veloc->min_acceleration = 1 / v; | |
297 | ||
298 | return Success; | |
299 | } | |
300 | ||
301 | static long | |
302 | AccelInitAdaptDecelProperty(DeviceIntPtr dev, DeviceVelocityPtr vel) | |
303 | { | |
304 | float fval = 1.0 / vel->min_acceleration; | |
305 | Atom prop_adapt_decel = | |
306 | XIGetKnownProperty(ACCEL_PROP_ADAPTIVE_DECELERATION); | |
307 | ||
308 | XIChangeDeviceProperty(dev, prop_adapt_decel, | |
309 | XIGetKnownProperty(XATOM_FLOAT), 32, PropModeReplace, | |
310 | 1, &fval, FALSE); | |
311 | XISetDevicePropertyDeletable(dev, prop_adapt_decel, FALSE); | |
312 | return XIRegisterPropertyHandler(dev, AccelSetAdaptDecelProperty, NULL, | |
313 | NULL); | |
314 | } | |
315 | ||
316 | /** | |
317 | * velocity scaling | |
318 | */ | |
319 | static int | |
320 | AccelSetScaleProperty(DeviceIntPtr dev, Atom atom, | |
321 | XIPropertyValuePtr val, BOOL checkOnly) | |
322 | { | |
323 | DeviceVelocityPtr vel; | |
324 | float v, *ptr = &v; | |
325 | int rc; | |
326 | int nelem = 1; | |
327 | ||
328 | if (atom != XIGetKnownProperty(ACCEL_PROP_VELOCITY_SCALING)) | |
329 | return Success; | |
330 | ||
331 | vel = GetDevicePredictableAccelData(dev); | |
332 | if (!vel) | |
333 | return BadValue; | |
334 | rc = XIPropToFloat(val, &nelem, &ptr); | |
335 | ||
336 | if (checkOnly) { | |
337 | if (rc) | |
338 | return rc; | |
339 | ||
340 | return (v > 0) ? Success : BadValue; | |
341 | } | |
342 | ||
343 | if (v > 0) | |
344 | vel->corr_mul = v; | |
345 | ||
346 | return Success; | |
347 | } | |
348 | ||
349 | static long | |
350 | AccelInitScaleProperty(DeviceIntPtr dev, DeviceVelocityPtr vel) | |
351 | { | |
352 | float fval = vel->corr_mul; | |
353 | Atom prop_velo_scale = XIGetKnownProperty(ACCEL_PROP_VELOCITY_SCALING); | |
354 | ||
355 | XIChangeDeviceProperty(dev, prop_velo_scale, | |
356 | XIGetKnownProperty(XATOM_FLOAT), 32, PropModeReplace, | |
357 | 1, &fval, FALSE); | |
358 | XISetDevicePropertyDeletable(dev, prop_velo_scale, FALSE); | |
359 | return XIRegisterPropertyHandler(dev, AccelSetScaleProperty, NULL, NULL); | |
360 | } | |
361 | ||
362 | static BOOL | |
363 | InitializePredictableAccelerationProperties(DeviceIntPtr dev, | |
364 | DeviceVelocityPtr vel, | |
365 | PredictableAccelSchemePtr | |
366 | schemeData) | |
367 | { | |
368 | int num_handlers = 4; | |
369 | ||
370 | if (!vel) | |
371 | return FALSE; | |
372 | ||
373 | schemeData->prop_handlers = calloc(num_handlers, sizeof(long)); | |
374 | if (!schemeData->prop_handlers) | |
375 | return FALSE; | |
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); | |
381 | ||
382 | return TRUE; | |
383 | } | |
384 | ||
385 | BOOL | |
386 | DeletePredictableAccelerationProperties(DeviceIntPtr dev, | |
387 | PredictableAccelSchemePtr scheme) | |
388 | { | |
389 | DeviceVelocityPtr vel; | |
390 | Atom prop; | |
391 | int i; | |
392 | ||
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); | |
401 | ||
402 | vel = GetDevicePredictableAccelData(dev); | |
403 | if (vel) { | |
404 | for (i = 0; i < scheme->num_prop_handlers; i++) | |
405 | if (scheme->prop_handlers[i]) | |
406 | XIUnregisterPropertyHandler(dev, scheme->prop_handlers[i]); | |
407 | } | |
408 | ||
409 | free(scheme->prop_handlers); | |
410 | scheme->prop_handlers = NULL; | |
411 | scheme->num_prop_handlers = 0; | |
412 | return TRUE; | |
413 | } | |
414 | ||
415 | /********************* | |
416 | * Tracking logic | |
417 | ********************/ | |
418 | ||
419 | void | |
420 | InitTrackers(DeviceVelocityPtr vel, int ntracker) | |
421 | { | |
422 | if (ntracker < 1) { | |
423 | ErrorF("invalid number of trackers\n"); | |
424 | return; | |
425 | } | |
426 | free(vel->tracker); | |
427 | vel->tracker = (MotionTrackerPtr) calloc(ntracker, sizeof(MotionTracker)); | |
428 | vel->num_tracker = ntracker; | |
429 | } | |
430 | ||
431 | enum directions { | |
432 | N = (1 << 0), | |
433 | NE = (1 << 1), | |
434 | E = (1 << 2), | |
435 | SE = (1 << 3), | |
436 | S = (1 << 4), | |
437 | SW = (1 << 5), | |
438 | W = (1 << 6), | |
439 | NW = (1 << 7), | |
440 | UNDEFINED = 0xFF | |
441 | }; | |
442 | ||
443 | /** | |
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 | |
449 | * often. | |
450 | * | |
451 | * @return A bitmask for N, NE, S, SE, etc. indicating the directions for | |
452 | * this movement. | |
453 | */ | |
454 | static int | |
455 | DoGetDirection(int dx, int dy) | |
456 | { | |
457 | int dir = 0; | |
458 | ||
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) | |
463 | dir = E | SE | S; | |
464 | else if (dx > 0 && dy < 0) | |
465 | dir = N | NE | E; | |
466 | else if (dx < 0 && dy < 0) | |
467 | dir = W | NW | N; | |
468 | else if (dx < 0 && dy > 0) | |
469 | dir = W | SW | S; | |
470 | /* check axis-aligned directions */ | |
471 | else if (dx > 0) | |
472 | dir = NE | E | SE; | |
473 | else if (dx < 0) | |
474 | dir = NW | W | SW; | |
475 | else if (dy > 0) | |
476 | dir = SE | S | SW; | |
477 | else if (dy < 0) | |
478 | dir = NE | N | NW; | |
479 | else | |
480 | dir = UNDEFINED; /* shouldn't happen */ | |
481 | } | |
482 | else { /* compute angle and set appropriate flags */ | |
483 | double r; | |
484 | int i1, i2; | |
485 | ||
486 | r = atan2(dy, dx); | |
487 | /* find direction. | |
488 | * | |
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 | |
491 | * number, e.g. | |
492 | * 0 <= r <= 1 is [0-45]° | |
493 | * 1 <= r <= 2 is [45-90]° | |
494 | * etc. | |
495 | * But we add extra 90° to match up with our N, S, etc. defines up | |
496 | * there, rest stays the same. | |
497 | */ | |
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 */ | |
505 | else | |
506 | dir = (1 << i1 | 1 << i2); | |
507 | } | |
508 | return dir; | |
509 | } | |
510 | ||
511 | #define DIRECTION_CACHE_RANGE 5 | |
512 | #define DIRECTION_CACHE_SIZE (DIRECTION_CACHE_RANGE*2+1) | |
513 | ||
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. | |
517 | * | |
518 | * @return A bitmask for N, NE, S, SE, etc. indicating the directions for | |
519 | * this movement. | |
520 | */ | |
521 | static int | |
522 | GetDirection(int dx, int dy) | |
523 | { | |
524 | static int cache[DIRECTION_CACHE_SIZE][DIRECTION_CACHE_SIZE]; | |
525 | int dir; | |
526 | ||
527 | if (abs(dx) <= DIRECTION_CACHE_RANGE && abs(dy) <= DIRECTION_CACHE_RANGE) { | |
528 | /* cacheable */ | |
529 | dir = cache[DIRECTION_CACHE_RANGE + dx][DIRECTION_CACHE_RANGE + dy]; | |
530 | if (dir == 0) { | |
531 | dir = DoGetDirection(dx, dy); | |
532 | cache[DIRECTION_CACHE_RANGE + dx][DIRECTION_CACHE_RANGE + dy] = dir; | |
533 | } | |
534 | } | |
535 | else { | |
536 | /* non-cacheable */ | |
537 | dir = DoGetDirection(dx, dy); | |
538 | } | |
539 | ||
540 | return dir; | |
541 | } | |
542 | ||
543 | #undef DIRECTION_CACHE_RANGE | |
544 | #undef DIRECTION_CACHE_SIZE | |
545 | ||
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)] | |
549 | ||
550 | /** | |
551 | * Add the delta motion to each tracker, then reset the latest tracker to | |
552 | * 0/0 and set it as the current one. | |
553 | */ | |
554 | static inline void | |
555 | FeedTrackers(DeviceVelocityPtr vel, double dx, double dy, int cur_t) | |
556 | { | |
557 | int n; | |
558 | ||
559 | for (n = 0; n < vel->num_tracker; n++) { | |
560 | vel->tracker[n].dx += dx; | |
561 | vel->tracker[n].dy += dy; | |
562 | } | |
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; | |
572 | } | |
573 | ||
574 | /** | |
575 | * calc velocity for given tracker, with | |
576 | * velocity scaling. | |
577 | * This assumes linear motion. | |
578 | */ | |
579 | static double | |
580 | CalcTracker(const MotionTracker * tracker, int cur_t) | |
581 | { | |
582 | double dist = sqrt(tracker->dx * tracker->dx + tracker->dy * tracker->dy); | |
583 | int dtime = cur_t - tracker->time; | |
584 | ||
585 | if (dtime > 0) | |
586 | return dist / dtime; | |
587 | else | |
588 | return 0; /* synonymous for NaN, since we're not C99 */ | |
589 | } | |
590 | ||
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. | |
595 | * | |
596 | * @return The tracker's velocity or 0 if the above conditions are unmet | |
597 | */ | |
598 | static double | |
599 | QueryTrackers(DeviceVelocityPtr vel, int cur_t) | |
600 | { | |
601 | int offset, dir = UNDEFINED, used_offset = -1, age_ms; | |
602 | ||
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 */ | |
606 | ||
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; | |
611 | ||
612 | age_ms = cur_t - tracker->time; | |
613 | ||
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); | |
618 | break; | |
619 | } | |
620 | ||
621 | /* | |
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. | |
626 | */ | |
627 | dir &= tracker->dir; | |
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. */ | |
632 | break; | |
633 | } | |
634 | ||
635 | tracker_velocity = CalcTracker(tracker, cur_t) * velocity_factor; | |
636 | ||
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; | |
642 | } | |
643 | else if (initial_velocity != 0 && tracker_velocity != 0) { | |
644 | velocity_diff = fabs(initial_velocity - tracker_velocity); | |
645 | ||
646 | if (velocity_diff > vel->max_diff && | |
647 | velocity_diff / (initial_velocity + tracker_velocity) >= | |
648 | vel->max_rel_diff) { | |
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); | |
653 | break; | |
654 | } | |
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; | |
660 | } | |
661 | } | |
662 | if (offset == vel->num_tracker) { | |
663 | DebugAccelF("query: last tracker in effect\n"); | |
664 | used_offset = vel->num_tracker - 1; | |
665 | } | |
666 | if (used_offset >= 0) { | |
667 | #ifdef PTRACCEL_DEBUGGING | |
668 | MotionTracker *tracker = TRACKER(vel, used_offset); | |
669 | ||
670 | DebugAccelF("result: offset %i [dx: %f dy: %f diff: %i]\n", | |
671 | used_offset, tracker->dx, tracker->dy, | |
672 | cur_t - tracker->time); | |
673 | #endif | |
674 | } | |
675 | return result; | |
676 | } | |
677 | ||
678 | #undef TRACKER_INDEX | |
679 | #undef TRACKER | |
680 | ||
681 | /** | |
682 | * Perform velocity approximation based on 2D 'mickeys' (mouse motion delta). | |
683 | * return true if non-visible state reset is suggested | |
684 | */ | |
685 | BOOL | |
686 | ProcessVelocityData2D(DeviceVelocityPtr vel, double dx, double dy, int time) | |
687 | { | |
688 | double velocity; | |
689 | ||
690 | vel->last_velocity = vel->velocity; | |
691 | ||
692 | FeedTrackers(vel, dx, dy, time); | |
693 | ||
694 | velocity = QueryTrackers(vel, time); | |
695 | ||
696 | DebugAccelF("velocity is %f\n", velocity); | |
697 | ||
698 | vel->velocity = velocity; | |
699 | return velocity == 0; | |
700 | } | |
701 | ||
702 | /** | |
703 | * this flattens significant ( > 1) mickeys a little bit for more steady | |
704 | * constant-velocity response | |
705 | */ | |
706 | static inline double | |
707 | ApplySimpleSoftening(double prev_delta, double delta) | |
708 | { | |
709 | double result = delta; | |
710 | ||
711 | if (delta < -1.0 || delta > 1.0) { | |
712 | if (delta > prev_delta) | |
713 | result -= 0.5; | |
714 | else if (delta < prev_delta) | |
715 | result += 0.5; | |
716 | } | |
717 | return result; | |
718 | } | |
719 | ||
720 | /** | |
721 | * Soften the delta based on previous deltas stored in vel. | |
722 | * | |
723 | * @param[in,out] fdx Delta X, modified in-place. | |
724 | * @param[in,out] fdx Delta Y, modified in-place. | |
725 | */ | |
726 | static void | |
727 | ApplySoftening(DeviceVelocityPtr vel, double *fdx, double *fdy) | |
728 | { | |
729 | if (vel->use_softening) { | |
730 | *fdx = ApplySimpleSoftening(vel->last_dx, *fdx); | |
731 | *fdy = ApplySimpleSoftening(vel->last_dy, *fdy); | |
732 | } | |
733 | } | |
734 | ||
735 | static void | |
736 | ApplyConstantDeceleration(DeviceVelocityPtr vel, double *fdx, double *fdy) | |
737 | { | |
738 | *fdx *= vel->const_acceleration; | |
739 | *fdy *= vel->const_acceleration; | |
740 | } | |
741 | ||
742 | /* | |
743 | * compute the acceleration for given velocity and enforce min_acceleration | |
744 | */ | |
745 | double | |
746 | BasicComputeAcceleration(DeviceIntPtr dev, | |
747 | DeviceVelocityPtr vel, | |
748 | double velocity, double threshold, double acc) | |
749 | { | |
750 | ||
751 | double result; | |
752 | ||
753 | result = vel->Profile(dev, vel, velocity, threshold, acc); | |
754 | ||
755 | /* enforce min_acceleration */ | |
756 | if (result < vel->min_acceleration) | |
757 | result = vel->min_acceleration; | |
758 | return result; | |
759 | } | |
760 | ||
761 | /** | |
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. | |
765 | */ | |
766 | static double | |
767 | ComputeAcceleration(DeviceIntPtr dev, | |
768 | DeviceVelocityPtr vel, double threshold, double acc) | |
769 | { | |
770 | double result; | |
771 | ||
772 | if (vel->velocity <= 0) { | |
773 | DebugAccelF("profile skipped\n"); | |
774 | /* | |
775 | * If we have no idea about device velocity, don't pretend it. | |
776 | */ | |
777 | return 1; | |
778 | } | |
779 | ||
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. */ | |
785 | result = | |
786 | BasicComputeAcceleration(dev, vel, vel->velocity, threshold, acc); | |
787 | result += | |
788 | BasicComputeAcceleration(dev, vel, vel->last_velocity, threshold, | |
789 | acc); | |
790 | result += | |
791 | 4.0f * BasicComputeAcceleration(dev, vel, | |
792 | (vel->last_velocity + | |
793 | vel->velocity) / 2, | |
794 | threshold, | |
795 | acc); | |
796 | result /= 6.0f; | |
797 | DebugAccelF("profile average [%.2f ... %.2f] is %.3f\n", | |
798 | vel->velocity, vel->last_velocity, result); | |
799 | } | |
800 | else { | |
801 | result = BasicComputeAcceleration(dev, vel, | |
802 | vel->velocity, threshold, acc); | |
803 | DebugAccelF("profile sample [%.2f] is %.3f\n", | |
804 | vel->velocity, result); | |
805 | } | |
806 | ||
807 | return result; | |
808 | } | |
809 | ||
810 | /***************************************** | |
811 | * Acceleration functions and profiles | |
812 | ****************************************/ | |
813 | ||
814 | /** | |
815 | * Polynomial function similar previous one, but with f(1) = 1 | |
816 | */ | |
817 | static double | |
818 | PolynomialAccelerationProfile(DeviceIntPtr dev, | |
819 | DeviceVelocityPtr vel, | |
820 | double velocity, double ignored, double acc) | |
821 | { | |
822 | return pow(velocity, (acc - 1.0) * 0.5); | |
823 | } | |
824 | ||
825 | /** | |
826 | * returns acceleration for velocity. | |
827 | * This profile selects the two functions like the old scheme did | |
828 | */ | |
829 | static double | |
830 | ClassicProfile(DeviceIntPtr dev, | |
831 | DeviceVelocityPtr vel, | |
832 | double velocity, double threshold, double acc) | |
833 | { | |
834 | if (threshold > 0) { | |
835 | return SimpleSmoothProfile(dev, vel, velocity, threshold, acc); | |
836 | } | |
837 | else { | |
838 | return PolynomialAccelerationProfile(dev, vel, velocity, 0, acc); | |
839 | } | |
840 | } | |
841 | ||
842 | /** | |
843 | * Power profile | |
844 | * This has a completely smooth transition curve, i.e. no jumps in the | |
845 | * derivatives. | |
846 | * | |
847 | * This has the expense of overall response dependency on min-acceleration. | |
848 | * In effect, min_acceleration mimics const_acceleration in this profile. | |
849 | */ | |
850 | static double | |
851 | PowerProfile(DeviceIntPtr dev, | |
852 | DeviceVelocityPtr vel, | |
853 | double velocity, double threshold, double acc) | |
854 | { | |
855 | double vel_dist; | |
856 | ||
857 | acc = (acc - 1.0) * 0.1f + 1.0; /* without this, acc of 2 is unuseable */ | |
858 | ||
859 | if (velocity <= threshold) | |
860 | return vel->min_acceleration; | |
861 | vel_dist = velocity - threshold; | |
862 | return (pow(acc, vel_dist)) * vel->min_acceleration; | |
863 | } | |
864 | ||
865 | /** | |
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) | |
871 | */ | |
872 | static inline double | |
873 | CalcPenumbralGradient(double x) | |
874 | { | |
875 | x *= 2.0f; | |
876 | x -= 1.0f; | |
877 | return 0.5f + (x * sqrt(1.0 - x * x) + asin(x)) / M_PI; | |
878 | } | |
879 | ||
880 | /** | |
881 | * acceleration function similar to classic accelerated/unaccelerated, | |
882 | * but with smooth transition in between (and towards zero for adaptive dec.). | |
883 | */ | |
884 | static double | |
885 | SimpleSmoothProfile(DeviceIntPtr dev, | |
886 | DeviceVelocityPtr vel, | |
887 | double velocity, double threshold, double acc) | |
888 | { | |
889 | if (velocity < 1.0f) | |
890 | return CalcPenumbralGradient(0.5 + velocity * 0.5) * 2.0f - 1.0f; | |
891 | if (threshold < 1.0f) | |
892 | threshold = 1.0f; | |
893 | if (velocity <= threshold) | |
894 | return 1; | |
895 | velocity /= threshold; | |
896 | if (velocity >= acc) | |
897 | return acc; | |
898 | else | |
899 | return 1.0f + (CalcPenumbralGradient(velocity / acc) * (acc - 1.0f)); | |
900 | } | |
901 | ||
902 | /** | |
903 | * This profile uses the first half of the penumbral gradient as a start | |
904 | * and then scales linearly. | |
905 | */ | |
906 | static double | |
907 | SmoothLinearProfile(DeviceIntPtr dev, | |
908 | DeviceVelocityPtr vel, | |
909 | double velocity, double threshold, double acc) | |
910 | { | |
911 | double res, nv; | |
912 | ||
913 | if (acc > 1.0f) | |
914 | acc -= 1.0f; /*this is so acc = 1 is no acceleration */ | |
915 | else | |
916 | return 1.0f; | |
917 | ||
918 | nv = (velocity - threshold) * acc * 0.5f; | |
919 | ||
920 | if (nv < 0) { | |
921 | res = 0; | |
922 | } | |
923 | else if (nv < 2) { | |
924 | res = CalcPenumbralGradient(nv * 0.25f) * 2.0f; | |
925 | } | |
926 | else { | |
927 | nv -= 2.0f; | |
928 | res = nv * 2.0f / M_PI /* steepness of gradient at 0.5 */ | |
929 | + 1.0f; /* gradient crosses 2|1 */ | |
930 | } | |
931 | res += vel->min_acceleration; | |
932 | return res; | |
933 | } | |
934 | ||
935 | /** | |
936 | * From 0 to threshold, the response graduates smoothly from min_accel to | |
937 | * acceleration. Beyond threshold it is exactly the specified acceleration. | |
938 | */ | |
939 | static double | |
940 | SmoothLimitedProfile(DeviceIntPtr dev, | |
941 | DeviceVelocityPtr vel, | |
942 | double velocity, double threshold, double acc) | |
943 | { | |
944 | double res; | |
945 | ||
946 | if (velocity >= threshold || threshold == 0.0f) | |
947 | return acc; | |
948 | ||
949 | velocity /= threshold; /* should be [0..1[ now */ | |
950 | ||
951 | res = CalcPenumbralGradient(velocity) * (acc - vel->min_acceleration); | |
952 | ||
953 | return vel->min_acceleration + res; | |
954 | } | |
955 | ||
956 | static double | |
957 | LinearProfile(DeviceIntPtr dev, | |
958 | DeviceVelocityPtr vel, | |
959 | double velocity, double threshold, double acc) | |
960 | { | |
961 | return acc * velocity; | |
962 | } | |
963 | ||
964 | static double | |
965 | NoProfile(DeviceIntPtr dev, | |
966 | DeviceVelocityPtr vel, double velocity, double threshold, double acc) | |
967 | { | |
968 | return 1.0f; | |
969 | } | |
970 | ||
971 | static PointerAccelerationProfileFunc | |
972 | GetAccelerationProfile(DeviceVelocityPtr vel, int profile_num) | |
973 | { | |
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: | |
986 | return PowerProfile; | |
987 | case AccelProfileLinear: | |
988 | return LinearProfile; | |
989 | case AccelProfileSmoothLimited: | |
990 | return SmoothLimitedProfile; | |
991 | case AccelProfileNone: | |
992 | return NoProfile; | |
993 | default: | |
994 | return NULL; | |
995 | } | |
996 | } | |
997 | ||
998 | /** | |
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. | |
1005 | * | |
1006 | * returns FALSE if profile number is unavailable, TRUE otherwise. | |
1007 | */ | |
1008 | int | |
1009 | SetAccelerationProfile(DeviceVelocityPtr vel, int profile_num) | |
1010 | { | |
1011 | PointerAccelerationProfileFunc profile; | |
1012 | ||
1013 | profile = GetAccelerationProfile(vel, profile_num); | |
1014 | ||
1015 | if (profile == NULL && profile_num != PROFILE_UNINITIALIZE) | |
1016 | return FALSE; | |
1017 | ||
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; | |
1024 | return TRUE; | |
1025 | } | |
1026 | ||
1027 | /********************************************** | |
1028 | * driver interaction | |
1029 | **********************************************/ | |
1030 | ||
1031 | /** | |
1032 | * device-specific profile | |
1033 | * | |
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. | |
1039 | */ | |
1040 | void | |
1041 | SetDeviceSpecificAccelerationProfile(DeviceVelocityPtr vel, | |
1042 | PointerAccelerationProfileFunc profile) | |
1043 | { | |
1044 | if (vel) | |
1045 | vel->deviceSpecificProfile = profile; | |
1046 | } | |
1047 | ||
1048 | /** | |
1049 | * Use this function to obtain a DeviceVelocityPtr for a device. Will return NULL if | |
1050 | * the predictable acceleration scheme is not in effect. | |
1051 | */ | |
1052 | DeviceVelocityPtr | |
1053 | GetDevicePredictableAccelData(DeviceIntPtr dev) | |
1054 | { | |
1055 | BUG_RETURN_VAL(!dev, NULL); | |
1056 | ||
1057 | if (dev->valuator && | |
1058 | dev->valuator->accelScheme.AccelSchemeProc == | |
1059 | acceleratePointerPredictable && | |
1060 | dev->valuator->accelScheme.accelData != NULL) { | |
1061 | ||
1062 | return ((PredictableAccelSchemePtr) | |
1063 | dev->valuator->accelScheme.accelData)->vel; | |
1064 | } | |
1065 | return NULL; | |
1066 | } | |
1067 | ||
1068 | /******************************** | |
1069 | * acceleration schemes | |
1070 | *******************************/ | |
1071 | ||
1072 | /** | |
1073 | * Modifies valuators in-place. | |
1074 | * This version employs a velocity approximation algorithm to | |
1075 | * enable fine-grained predictable acceleration profiles. | |
1076 | */ | |
1077 | void | |
1078 | acceleratePointerPredictable(DeviceIntPtr dev, ValuatorMask *val, CARD32 evtime) | |
1079 | { | |
1080 | double dx = 0, dy = 0; | |
1081 | DeviceVelocityPtr velocitydata = GetDevicePredictableAccelData(dev); | |
1082 | Bool soften = TRUE; | |
1083 | ||
1084 | if (valuator_mask_num_valuators(val) == 0 || !velocitydata) | |
1085 | return; | |
1086 | ||
1087 | if (velocitydata->statistics.profile_number == AccelProfileNone && | |
1088 | velocitydata->const_acceleration == 1.0f) { | |
1089 | return; /*we're inactive anyway, so skip the whole thing. */ | |
1090 | } | |
1091 | ||
1092 | if (valuator_mask_isset(val, 0)) { | |
1093 | dx = valuator_mask_get_double(val, 0); | |
1094 | } | |
1095 | ||
1096 | if (valuator_mask_isset(val, 1)) { | |
1097 | dy = valuator_mask_get_double(val, 1); | |
1098 | } | |
1099 | ||
1100 | if (dx != 0.0 || dy != 0.0) { | |
1101 | /* reset non-visible state? */ | |
1102 | if (ProcessVelocityData2D(velocitydata, dx, dy, evtime)) { | |
1103 | soften = FALSE; | |
1104 | } | |
1105 | ||
1106 | if (dev->ptrfeed && dev->ptrfeed->ctrl.num) { | |
1107 | double mult; | |
1108 | ||
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); | |
1114 | ||
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); | |
1120 | ||
1121 | if (dx != 0.0) | |
1122 | valuator_mask_set_double(val, 0, mult * dx); | |
1123 | if (dy != 0.0) | |
1124 | valuator_mask_set_double(val, 1, mult * dy); | |
1125 | DebugAccelF("delta x:%.3f y:%.3f\n", mult * dx, mult * dy); | |
1126 | } | |
1127 | } | |
1128 | } | |
1129 | /* remember last motion delta (for softening/slow movement treatment) */ | |
1130 | velocitydata->last_dx = dx; | |
1131 | velocitydata->last_dy = dy; | |
1132 | } | |
1133 | ||
1134 | /** | |
1135 | * Originally a part of xf86PostMotionEvent; modifies valuators | |
1136 | * in-place. Retained mostly for embedded scenarios. | |
1137 | */ | |
1138 | void | |
1139 | acceleratePointerLightweight(DeviceIntPtr dev, | |
1140 | ValuatorMask *val, CARD32 ignored) | |
1141 | { | |
1142 | double mult = 0.0, tmpf; | |
1143 | double dx = 0.0, dy = 0.0; | |
1144 | ||
1145 | if (valuator_mask_isset(val, 0)) { | |
1146 | dx = valuator_mask_get(val, 0); | |
1147 | } | |
1148 | ||
1149 | if (valuator_mask_isset(val, 1)) { | |
1150 | dy = valuator_mask_get(val, 1); | |
1151 | } | |
1152 | ||
1153 | if (valuator_mask_num_valuators(val) == 0) | |
1154 | return; | |
1155 | ||
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) { | |
1160 | if (dx != 0.0) { | |
1161 | tmpf = (dx * (double) (dev->ptrfeed->ctrl.num)) / | |
1162 | (double) (dev->ptrfeed->ctrl.den); | |
1163 | valuator_mask_set_double(val, 0, tmpf); | |
1164 | } | |
1165 | ||
1166 | if (dy != 0.0) { | |
1167 | tmpf = (dy * (double) (dev->ptrfeed->ctrl.num)) / | |
1168 | (double) (dev->ptrfeed->ctrl.den); | |
1169 | valuator_mask_set_double(val, 1, tmpf); | |
1170 | } | |
1171 | } | |
1172 | } | |
1173 | else { | |
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; | |
1177 | if (dx != 0.0) | |
1178 | valuator_mask_set_double(val, 0, mult * dx); | |
1179 | if (dy != 0.0) | |
1180 | valuator_mask_set_double(val, 1, mult * dy); | |
1181 | } | |
1182 | } | |
1183 | } |