ptrveloc.c revision 35c4bbdf
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 */ 64static double 65SimpleSmoothProfile(DeviceIntPtr dev, DeviceVelocityPtr vel, double velocity, 66 double threshold, double acc); 67static PointerAccelerationProfileFunc 68GetAccelerationProfile(DeviceVelocityPtr vel, int profile_num); 69static BOOL 70InitializePredictableAccelerationProperties(DeviceIntPtr, 71 DeviceVelocityPtr, 72 PredictableAccelSchemePtr); 73static BOOL 74DeletePredictableAccelerationProperties(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 */ 95void 96InitVelocityData(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 */ 116void 117FreeVelocityData(DeviceVelocityPtr vel) 118{ 119 free(vel->tracker); 120 SetAccelerationProfile(vel, PROFILE_UNINITIALIZE); 121} 122 123/** 124 * Init predictable scheme 125 */ 126Bool 127InitPredictableAccelerationScheme(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 free(vel); 139 free(schemeData); 140 return FALSE; 141 } 142 InitVelocityData(vel); 143 schemeData->vel = vel; 144 scheme.accelData = schemeData; 145 if (!InitializePredictableAccelerationProperties(dev, vel, schemeData)) { 146 free(vel); 147 free(schemeData); 148 return FALSE; 149 } 150 /* all fine, assign scheme to device */ 151 dev->valuator->accelScheme = scheme; 152 return TRUE; 153} 154 155/** 156 * Uninit scheme 157 */ 158void 159AccelerationDefaultCleanup(DeviceIntPtr dev) 160{ 161 DeviceVelocityPtr vel = GetDevicePredictableAccelData(dev); 162 163 if (vel) { 164 /* the proper guarantee would be that we're not inside of 165 * AccelSchemeProc(), but that seems impossible. Schemes don't get 166 * switched often anyway. 167 */ 168 OsBlockSignals(); 169 dev->valuator->accelScheme.AccelSchemeProc = NULL; 170 FreeVelocityData(vel); 171 free(vel); 172 DeletePredictableAccelerationProperties(dev, 173 (PredictableAccelSchemePtr) 174 dev->valuator->accelScheme. 175 accelData); 176 free(dev->valuator->accelScheme.accelData); 177 dev->valuator->accelScheme.accelData = NULL; 178 OsReleaseSignals(); 179 } 180} 181 182/************************* 183 * Input property support 184 ************************/ 185 186/** 187 * choose profile 188 */ 189static int 190AccelSetProfileProperty(DeviceIntPtr dev, Atom atom, 191 XIPropertyValuePtr val, BOOL checkOnly) 192{ 193 DeviceVelocityPtr vel; 194 int profile, *ptr = &profile; 195 int rc; 196 int nelem = 1; 197 198 if (atom != XIGetKnownProperty(ACCEL_PROP_PROFILE_NUMBER)) 199 return Success; 200 201 vel = GetDevicePredictableAccelData(dev); 202 if (!vel) 203 return BadValue; 204 rc = XIPropToInt(val, &nelem, &ptr); 205 206 if (checkOnly) { 207 if (rc) 208 return rc; 209 210 if (GetAccelerationProfile(vel, profile) == NULL) 211 return BadValue; 212 } 213 else 214 SetAccelerationProfile(vel, profile); 215 216 return Success; 217} 218 219static long 220AccelInitProfileProperty(DeviceIntPtr dev, DeviceVelocityPtr vel) 221{ 222 int profile = vel->statistics.profile_number; 223 Atom prop_profile_number = XIGetKnownProperty(ACCEL_PROP_PROFILE_NUMBER); 224 225 XIChangeDeviceProperty(dev, prop_profile_number, XA_INTEGER, 32, 226 PropModeReplace, 1, &profile, FALSE); 227 XISetDevicePropertyDeletable(dev, prop_profile_number, FALSE); 228 return XIRegisterPropertyHandler(dev, AccelSetProfileProperty, NULL, NULL); 229} 230 231/** 232 * constant deceleration 233 */ 234static int 235AccelSetDecelProperty(DeviceIntPtr dev, Atom atom, 236 XIPropertyValuePtr val, BOOL checkOnly) 237{ 238 DeviceVelocityPtr vel; 239 float v, *ptr = &v; 240 int rc; 241 int nelem = 1; 242 243 if (atom != XIGetKnownProperty(ACCEL_PROP_CONSTANT_DECELERATION)) 244 return Success; 245 246 vel = GetDevicePredictableAccelData(dev); 247 if (!vel) 248 return BadValue; 249 rc = XIPropToFloat(val, &nelem, &ptr); 250 251 if (checkOnly) { 252 if (rc) 253 return rc; 254 return (v > 0) ? Success : BadValue; 255 } 256 257 vel->const_acceleration = 1 / v; 258 259 return Success; 260} 261 262static long 263AccelInitDecelProperty(DeviceIntPtr dev, DeviceVelocityPtr vel) 264{ 265 float fval = 1.0 / vel->const_acceleration; 266 Atom prop_const_decel = 267 XIGetKnownProperty(ACCEL_PROP_CONSTANT_DECELERATION); 268 XIChangeDeviceProperty(dev, prop_const_decel, 269 XIGetKnownProperty(XATOM_FLOAT), 32, PropModeReplace, 270 1, &fval, FALSE); 271 XISetDevicePropertyDeletable(dev, prop_const_decel, FALSE); 272 return XIRegisterPropertyHandler(dev, AccelSetDecelProperty, NULL, NULL); 273} 274 275/** 276 * adaptive deceleration 277 */ 278static int 279AccelSetAdaptDecelProperty(DeviceIntPtr dev, Atom atom, 280 XIPropertyValuePtr val, BOOL checkOnly) 281{ 282 DeviceVelocityPtr veloc; 283 float v, *ptr = &v; 284 int rc; 285 int nelem = 1; 286 287 if (atom != XIGetKnownProperty(ACCEL_PROP_ADAPTIVE_DECELERATION)) 288 return Success; 289 290 veloc = GetDevicePredictableAccelData(dev); 291 if (!veloc) 292 return BadValue; 293 rc = XIPropToFloat(val, &nelem, &ptr); 294 295 if (checkOnly) { 296 if (rc) 297 return rc; 298 return (v >= 1.0f) ? Success : BadValue; 299 } 300 301 if (v >= 1.0f) 302 veloc->min_acceleration = 1 / v; 303 304 return Success; 305} 306 307static long 308AccelInitAdaptDecelProperty(DeviceIntPtr dev, DeviceVelocityPtr vel) 309{ 310 float fval = 1.0 / vel->min_acceleration; 311 Atom prop_adapt_decel = 312 XIGetKnownProperty(ACCEL_PROP_ADAPTIVE_DECELERATION); 313 314 XIChangeDeviceProperty(dev, prop_adapt_decel, 315 XIGetKnownProperty(XATOM_FLOAT), 32, PropModeReplace, 316 1, &fval, FALSE); 317 XISetDevicePropertyDeletable(dev, prop_adapt_decel, FALSE); 318 return XIRegisterPropertyHandler(dev, AccelSetAdaptDecelProperty, NULL, 319 NULL); 320} 321 322/** 323 * velocity scaling 324 */ 325static int 326AccelSetScaleProperty(DeviceIntPtr dev, Atom atom, 327 XIPropertyValuePtr val, BOOL checkOnly) 328{ 329 DeviceVelocityPtr vel; 330 float v, *ptr = &v; 331 int rc; 332 int nelem = 1; 333 334 if (atom != XIGetKnownProperty(ACCEL_PROP_VELOCITY_SCALING)) 335 return Success; 336 337 vel = GetDevicePredictableAccelData(dev); 338 if (!vel) 339 return BadValue; 340 rc = XIPropToFloat(val, &nelem, &ptr); 341 342 if (checkOnly) { 343 if (rc) 344 return rc; 345 346 return (v > 0) ? Success : BadValue; 347 } 348 349 if (v > 0) 350 vel->corr_mul = v; 351 352 return Success; 353} 354 355static long 356AccelInitScaleProperty(DeviceIntPtr dev, DeviceVelocityPtr vel) 357{ 358 float fval = vel->corr_mul; 359 Atom prop_velo_scale = XIGetKnownProperty(ACCEL_PROP_VELOCITY_SCALING); 360 361 XIChangeDeviceProperty(dev, prop_velo_scale, 362 XIGetKnownProperty(XATOM_FLOAT), 32, PropModeReplace, 363 1, &fval, FALSE); 364 XISetDevicePropertyDeletable(dev, prop_velo_scale, FALSE); 365 return XIRegisterPropertyHandler(dev, AccelSetScaleProperty, NULL, NULL); 366} 367 368static BOOL 369InitializePredictableAccelerationProperties(DeviceIntPtr dev, 370 DeviceVelocityPtr vel, 371 PredictableAccelSchemePtr 372 schemeData) 373{ 374 int num_handlers = 4; 375 376 if (!vel) 377 return FALSE; 378 379 schemeData->prop_handlers = calloc(num_handlers, sizeof(long)); 380 if (!schemeData->prop_handlers) 381 return FALSE; 382 schemeData->num_prop_handlers = num_handlers; 383 schemeData->prop_handlers[0] = AccelInitProfileProperty(dev, vel); 384 schemeData->prop_handlers[1] = AccelInitDecelProperty(dev, vel); 385 schemeData->prop_handlers[2] = AccelInitAdaptDecelProperty(dev, vel); 386 schemeData->prop_handlers[3] = AccelInitScaleProperty(dev, vel); 387 388 return TRUE; 389} 390 391BOOL 392DeletePredictableAccelerationProperties(DeviceIntPtr dev, 393 PredictableAccelSchemePtr scheme) 394{ 395 DeviceVelocityPtr vel; 396 Atom prop; 397 int i; 398 399 prop = XIGetKnownProperty(ACCEL_PROP_VELOCITY_SCALING); 400 XIDeleteDeviceProperty(dev, prop, FALSE); 401 prop = XIGetKnownProperty(ACCEL_PROP_ADAPTIVE_DECELERATION); 402 XIDeleteDeviceProperty(dev, prop, FALSE); 403 prop = XIGetKnownProperty(ACCEL_PROP_CONSTANT_DECELERATION); 404 XIDeleteDeviceProperty(dev, prop, FALSE); 405 prop = XIGetKnownProperty(ACCEL_PROP_PROFILE_NUMBER); 406 XIDeleteDeviceProperty(dev, prop, FALSE); 407 408 vel = GetDevicePredictableAccelData(dev); 409 if (vel) { 410 for (i = 0; i < scheme->num_prop_handlers; i++) 411 if (scheme->prop_handlers[i]) 412 XIUnregisterPropertyHandler(dev, scheme->prop_handlers[i]); 413 } 414 415 free(scheme->prop_handlers); 416 scheme->prop_handlers = NULL; 417 scheme->num_prop_handlers = 0; 418 return TRUE; 419} 420 421/********************* 422 * Tracking logic 423 ********************/ 424 425void 426InitTrackers(DeviceVelocityPtr vel, int ntracker) 427{ 428 if (ntracker < 1) { 429 ErrorF("invalid number of trackers\n"); 430 return; 431 } 432 free(vel->tracker); 433 vel->tracker = (MotionTrackerPtr) calloc(ntracker, sizeof(MotionTracker)); 434 vel->num_tracker = ntracker; 435} 436 437enum directions { 438 N = (1 << 0), 439 NE = (1 << 1), 440 E = (1 << 2), 441 SE = (1 << 3), 442 S = (1 << 4), 443 SW = (1 << 5), 444 W = (1 << 6), 445 NW = (1 << 7), 446 UNDEFINED = 0xFF 447}; 448 449/** 450 * return a bit field of possible directions. 451 * There's no reason against widening to more precise directions (<45 degrees), 452 * should it not perform well. All this is needed for is sort out non-linear 453 * motion, so precision isn't paramount. However, one should not flag direction 454 * too narrow, since it would then cut the linear segment to zero size way too 455 * often. 456 * 457 * @return A bitmask for N, NE, S, SE, etc. indicating the directions for 458 * this movement. 459 */ 460static int 461DoGetDirection(int dx, int dy) 462{ 463 int dir = 0; 464 465 /* on insignificant mickeys, flag 135 degrees */ 466 if (abs(dx) < 2 && abs(dy) < 2) { 467 /* first check diagonal cases */ 468 if (dx > 0 && dy > 0) 469 dir = E | SE | S; 470 else if (dx > 0 && dy < 0) 471 dir = N | NE | E; 472 else if (dx < 0 && dy < 0) 473 dir = W | NW | N; 474 else if (dx < 0 && dy > 0) 475 dir = W | SW | S; 476 /* check axis-aligned directions */ 477 else if (dx > 0) 478 dir = NE | E | SE; 479 else if (dx < 0) 480 dir = NW | W | SW; 481 else if (dy > 0) 482 dir = SE | S | SW; 483 else if (dy < 0) 484 dir = NE | N | NW; 485 else 486 dir = UNDEFINED; /* shouldn't happen */ 487 } 488 else { /* compute angle and set appropriate flags */ 489 double r; 490 int i1, i2; 491 492 r = atan2(dy, dx); 493 /* find direction. 494 * 495 * Add 360° to avoid r become negative since C has no well-defined 496 * modulo for such cases. Then divide by 45° to get the octant 497 * number, e.g. 498 * 0 <= r <= 1 is [0-45]° 499 * 1 <= r <= 2 is [45-90]° 500 * etc. 501 * But we add extra 90° to match up with our N, S, etc. defines up 502 * there, rest stays the same. 503 */ 504 r = (r + (M_PI * 2.5)) / (M_PI / 4); 505 /* this intends to flag 2 directions (45 degrees), 506 * except on very well-aligned mickeys. */ 507 i1 = (int) (r + 0.1) % 8; 508 i2 = (int) (r + 0.9) % 8; 509 if (i1 < 0 || i1 > 7 || i2 < 0 || i2 > 7) 510 dir = UNDEFINED; /* shouldn't happen */ 511 else 512 dir = (1 << i1 | 1 << i2); 513 } 514 return dir; 515} 516 517#define DIRECTION_CACHE_RANGE 5 518#define DIRECTION_CACHE_SIZE (DIRECTION_CACHE_RANGE*2+1) 519 520/* cache DoGetDirection(). 521 * To avoid excessive use of direction calculation, cache the values for 522 * [-5..5] for both x/y. Anything outside of that is calcualted on the fly. 523 * 524 * @return A bitmask for N, NE, S, SE, etc. indicating the directions for 525 * this movement. 526 */ 527static int 528GetDirection(int dx, int dy) 529{ 530 static int cache[DIRECTION_CACHE_SIZE][DIRECTION_CACHE_SIZE]; 531 int dir; 532 533 if (abs(dx) <= DIRECTION_CACHE_RANGE && abs(dy) <= DIRECTION_CACHE_RANGE) { 534 /* cacheable */ 535 dir = cache[DIRECTION_CACHE_RANGE + dx][DIRECTION_CACHE_RANGE + dy]; 536 if (dir == 0) { 537 dir = DoGetDirection(dx, dy); 538 cache[DIRECTION_CACHE_RANGE + dx][DIRECTION_CACHE_RANGE + dy] = dir; 539 } 540 } 541 else { 542 /* non-cacheable */ 543 dir = DoGetDirection(dx, dy); 544 } 545 546 return dir; 547} 548 549#undef DIRECTION_CACHE_RANGE 550#undef DIRECTION_CACHE_SIZE 551 552/* convert offset (age) to array index */ 553#define TRACKER_INDEX(s, d) (((s)->num_tracker + (s)->cur_tracker - (d)) % (s)->num_tracker) 554#define TRACKER(s, d) &(s)->tracker[TRACKER_INDEX(s,d)] 555 556/** 557 * Add the delta motion to each tracker, then reset the latest tracker to 558 * 0/0 and set it as the current one. 559 */ 560static inline void 561FeedTrackers(DeviceVelocityPtr vel, double dx, double dy, int cur_t) 562{ 563 int n; 564 565 for (n = 0; n < vel->num_tracker; n++) { 566 vel->tracker[n].dx += dx; 567 vel->tracker[n].dy += dy; 568 } 569 n = (vel->cur_tracker + 1) % vel->num_tracker; 570 vel->tracker[n].dx = 0.0; 571 vel->tracker[n].dy = 0.0; 572 vel->tracker[n].time = cur_t; 573 vel->tracker[n].dir = GetDirection(dx, dy); 574 DebugAccelF("motion [dx: %f dy: %f dir:%d diff: %d]\n", 575 dx, dy, vel->tracker[n].dir, 576 cur_t - vel->tracker[vel->cur_tracker].time); 577 vel->cur_tracker = n; 578} 579 580/** 581 * calc velocity for given tracker, with 582 * velocity scaling. 583 * This assumes linear motion. 584 */ 585static double 586CalcTracker(const MotionTracker * tracker, int cur_t) 587{ 588 double dist = sqrt(tracker->dx * tracker->dx + tracker->dy * tracker->dy); 589 int dtime = cur_t - tracker->time; 590 591 if (dtime > 0) 592 return dist / dtime; 593 else 594 return 0; /* synonymous for NaN, since we're not C99 */ 595} 596 597/* find the most plausible velocity. That is, the most distant 598 * (in time) tracker which isn't too old, the movement vector was 599 * in the same octant, and where the velocity is within an 600 * acceptable range to the inital velocity. 601 * 602 * @return The tracker's velocity or 0 if the above conditions are unmet 603 */ 604static double 605QueryTrackers(DeviceVelocityPtr vel, int cur_t) 606{ 607 int offset, dir = UNDEFINED, used_offset = -1, age_ms; 608 609 /* initial velocity: a low-offset, valid velocity */ 610 double initial_velocity = 0, result = 0, velocity_diff; 611 double velocity_factor = vel->corr_mul * vel->const_acceleration; /* premultiply */ 612 613 /* loop from current to older data */ 614 for (offset = 1; offset < vel->num_tracker; offset++) { 615 MotionTracker *tracker = TRACKER(vel, offset); 616 double tracker_velocity; 617 618 age_ms = cur_t - tracker->time; 619 620 /* bail out if data is too old and protect from overrun */ 621 if (age_ms >= vel->reset_time || age_ms < 0) { 622 DebugAccelF("query: tracker too old (reset after %d, age is %d)\n", 623 vel->reset_time, age_ms); 624 break; 625 } 626 627 /* 628 * this heuristic avoids using the linear-motion velocity formula 629 * in CalcTracker() on motion that isn't exactly linear. So to get 630 * even more precision we could subdivide as a final step, so possible 631 * non-linearities are accounted for. 632 */ 633 dir &= tracker->dir; 634 if (dir == 0) { /* we've changed octant of movement (e.g. NE → NW) */ 635 DebugAccelF("query: no longer linear\n"); 636 /* instead of breaking it we might also inspect the partition after, 637 * but actual improvement with this is probably rare. */ 638 break; 639 } 640 641 tracker_velocity = CalcTracker(tracker, cur_t) * velocity_factor; 642 643 if ((initial_velocity == 0 || offset <= vel->initial_range) && 644 tracker_velocity != 0) { 645 /* set initial velocity and result */ 646 result = initial_velocity = tracker_velocity; 647 used_offset = offset; 648 } 649 else if (initial_velocity != 0 && tracker_velocity != 0) { 650 velocity_diff = fabs(initial_velocity - tracker_velocity); 651 652 if (velocity_diff > vel->max_diff && 653 velocity_diff / (initial_velocity + tracker_velocity) >= 654 vel->max_rel_diff) { 655 /* we're not in range, quit - it won't get better. */ 656 DebugAccelF("query: tracker too different:" 657 " old %2.2f initial %2.2f diff: %2.2f\n", 658 tracker_velocity, initial_velocity, velocity_diff); 659 break; 660 } 661 /* we're in range with the initial velocity, 662 * so this result is likely better 663 * (it contains more information). */ 664 result = tracker_velocity; 665 used_offset = offset; 666 } 667 } 668 if (offset == vel->num_tracker) { 669 DebugAccelF("query: last tracker in effect\n"); 670 used_offset = vel->num_tracker - 1; 671 } 672 if (used_offset >= 0) { 673#ifdef PTRACCEL_DEBUGGING 674 MotionTracker *tracker = TRACKER(vel, used_offset); 675 676 DebugAccelF("result: offset %i [dx: %f dy: %f diff: %i]\n", 677 used_offset, tracker->dx, tracker->dy, 678 cur_t - tracker->time); 679#endif 680 } 681 return result; 682} 683 684#undef TRACKER_INDEX 685#undef TRACKER 686 687/** 688 * Perform velocity approximation based on 2D 'mickeys' (mouse motion delta). 689 * return true if non-visible state reset is suggested 690 */ 691BOOL 692ProcessVelocityData2D(DeviceVelocityPtr vel, double dx, double dy, int time) 693{ 694 double velocity; 695 696 vel->last_velocity = vel->velocity; 697 698 FeedTrackers(vel, dx, dy, time); 699 700 velocity = QueryTrackers(vel, time); 701 702 DebugAccelF("velocity is %f\n", velocity); 703 704 vel->velocity = velocity; 705 return velocity == 0; 706} 707 708/** 709 * this flattens significant ( > 1) mickeys a little bit for more steady 710 * constant-velocity response 711 */ 712static inline double 713ApplySimpleSoftening(double prev_delta, double delta) 714{ 715 double result = delta; 716 717 if (delta < -1.0 || delta > 1.0) { 718 if (delta > prev_delta) 719 result -= 0.5; 720 else if (delta < prev_delta) 721 result += 0.5; 722 } 723 return result; 724} 725 726/** 727 * Soften the delta based on previous deltas stored in vel. 728 * 729 * @param[in,out] fdx Delta X, modified in-place. 730 * @param[in,out] fdx Delta Y, modified in-place. 731 */ 732static void 733ApplySoftening(DeviceVelocityPtr vel, double *fdx, double *fdy) 734{ 735 if (vel->use_softening) { 736 *fdx = ApplySimpleSoftening(vel->last_dx, *fdx); 737 *fdy = ApplySimpleSoftening(vel->last_dy, *fdy); 738 } 739} 740 741static void 742ApplyConstantDeceleration(DeviceVelocityPtr vel, double *fdx, double *fdy) 743{ 744 *fdx *= vel->const_acceleration; 745 *fdy *= vel->const_acceleration; 746} 747 748/* 749 * compute the acceleration for given velocity and enforce min_acceleration 750 */ 751double 752BasicComputeAcceleration(DeviceIntPtr dev, 753 DeviceVelocityPtr vel, 754 double velocity, double threshold, double acc) 755{ 756 757 double result; 758 759 result = vel->Profile(dev, vel, velocity, threshold, acc); 760 761 /* enforce min_acceleration */ 762 if (result < vel->min_acceleration) 763 result = vel->min_acceleration; 764 return result; 765} 766 767/** 768 * Compute acceleration. Takes into account averaging, nv-reset, etc. 769 * If the velocity has changed, an average is taken of 6 velocity factors: 770 * current velocity, last velocity and 4 times the average between the two. 771 */ 772static double 773ComputeAcceleration(DeviceIntPtr dev, 774 DeviceVelocityPtr vel, double threshold, double acc) 775{ 776 double result; 777 778 if (vel->velocity <= 0) { 779 DebugAccelF("profile skipped\n"); 780 /* 781 * If we have no idea about device velocity, don't pretend it. 782 */ 783 return 1; 784 } 785 786 if (vel->average_accel && vel->velocity != vel->last_velocity) { 787 /* use simpson's rule to average acceleration between 788 * current and previous velocity. 789 * Though being the more natural choice, it causes a minor delay 790 * in comparison, so it can be disabled. */ 791 result = 792 BasicComputeAcceleration(dev, vel, vel->velocity, threshold, acc); 793 result += 794 BasicComputeAcceleration(dev, vel, vel->last_velocity, threshold, 795 acc); 796 result += 797 4.0f * BasicComputeAcceleration(dev, vel, 798 (vel->last_velocity + 799 vel->velocity) / 2, 800 threshold, 801 acc); 802 result /= 6.0f; 803 DebugAccelF("profile average [%.2f ... %.2f] is %.3f\n", 804 vel->velocity, vel->last_velocity, result); 805 } 806 else { 807 result = BasicComputeAcceleration(dev, vel, 808 vel->velocity, threshold, acc); 809 DebugAccelF("profile sample [%.2f] is %.3f\n", 810 vel->velocity, result); 811 } 812 813 return result; 814} 815 816/***************************************** 817 * Acceleration functions and profiles 818 ****************************************/ 819 820/** 821 * Polynomial function similar previous one, but with f(1) = 1 822 */ 823static double 824PolynomialAccelerationProfile(DeviceIntPtr dev, 825 DeviceVelocityPtr vel, 826 double velocity, double ignored, double acc) 827{ 828 return pow(velocity, (acc - 1.0) * 0.5); 829} 830 831/** 832 * returns acceleration for velocity. 833 * This profile selects the two functions like the old scheme did 834 */ 835static double 836ClassicProfile(DeviceIntPtr dev, 837 DeviceVelocityPtr vel, 838 double velocity, double threshold, double acc) 839{ 840 if (threshold > 0) { 841 return SimpleSmoothProfile(dev, vel, velocity, threshold, acc); 842 } 843 else { 844 return PolynomialAccelerationProfile(dev, vel, velocity, 0, acc); 845 } 846} 847 848/** 849 * Power profile 850 * This has a completely smooth transition curve, i.e. no jumps in the 851 * derivatives. 852 * 853 * This has the expense of overall response dependency on min-acceleration. 854 * In effect, min_acceleration mimics const_acceleration in this profile. 855 */ 856static double 857PowerProfile(DeviceIntPtr dev, 858 DeviceVelocityPtr vel, 859 double velocity, double threshold, double acc) 860{ 861 double vel_dist; 862 863 acc = (acc - 1.0) * 0.1f + 1.0; /* without this, acc of 2 is unuseable */ 864 865 if (velocity <= threshold) 866 return vel->min_acceleration; 867 vel_dist = velocity - threshold; 868 return (pow(acc, vel_dist)) * vel->min_acceleration; 869} 870 871/** 872 * just a smooth function in [0..1] -> [0..1] 873 * - point symmetry at 0.5 874 * - f'(0) = f'(1) = 0 875 * - starts faster than a sinoid 876 * - smoothness C1 (Cinf if you dare to ignore endpoints) 877 */ 878static inline double 879CalcPenumbralGradient(double x) 880{ 881 x *= 2.0f; 882 x -= 1.0f; 883 return 0.5f + (x * sqrt(1.0 - x * x) + asin(x)) / M_PI; 884} 885 886/** 887 * acceleration function similar to classic accelerated/unaccelerated, 888 * but with smooth transition in between (and towards zero for adaptive dec.). 889 */ 890static double 891SimpleSmoothProfile(DeviceIntPtr dev, 892 DeviceVelocityPtr vel, 893 double velocity, double threshold, double acc) 894{ 895 if (velocity < 1.0f) 896 return CalcPenumbralGradient(0.5 + velocity * 0.5) * 2.0f - 1.0f; 897 if (threshold < 1.0f) 898 threshold = 1.0f; 899 if (velocity <= threshold) 900 return 1; 901 velocity /= threshold; 902 if (velocity >= acc) 903 return acc; 904 else 905 return 1.0f + (CalcPenumbralGradient(velocity / acc) * (acc - 1.0f)); 906} 907 908/** 909 * This profile uses the first half of the penumbral gradient as a start 910 * and then scales linearly. 911 */ 912static double 913SmoothLinearProfile(DeviceIntPtr dev, 914 DeviceVelocityPtr vel, 915 double velocity, double threshold, double acc) 916{ 917 double res, nv; 918 919 if (acc > 1.0f) 920 acc -= 1.0f; /*this is so acc = 1 is no acceleration */ 921 else 922 return 1.0f; 923 924 nv = (velocity - threshold) * acc * 0.5f; 925 926 if (nv < 0) { 927 res = 0; 928 } 929 else if (nv < 2) { 930 res = CalcPenumbralGradient(nv * 0.25f) * 2.0f; 931 } 932 else { 933 nv -= 2.0f; 934 res = nv * 2.0f / M_PI /* steepness of gradient at 0.5 */ 935 + 1.0f; /* gradient crosses 2|1 */ 936 } 937 res += vel->min_acceleration; 938 return res; 939} 940 941/** 942 * From 0 to threshold, the response graduates smoothly from min_accel to 943 * acceleration. Beyond threshold it is exactly the specified acceleration. 944 */ 945static double 946SmoothLimitedProfile(DeviceIntPtr dev, 947 DeviceVelocityPtr vel, 948 double velocity, double threshold, double acc) 949{ 950 double res; 951 952 if (velocity >= threshold || threshold == 0.0f) 953 return acc; 954 955 velocity /= threshold; /* should be [0..1[ now */ 956 957 res = CalcPenumbralGradient(velocity) * (acc - vel->min_acceleration); 958 959 return vel->min_acceleration + res; 960} 961 962static double 963LinearProfile(DeviceIntPtr dev, 964 DeviceVelocityPtr vel, 965 double velocity, double threshold, double acc) 966{ 967 return acc * velocity; 968} 969 970static double 971NoProfile(DeviceIntPtr dev, 972 DeviceVelocityPtr vel, double velocity, double threshold, double acc) 973{ 974 return 1.0f; 975} 976 977static PointerAccelerationProfileFunc 978GetAccelerationProfile(DeviceVelocityPtr vel, int profile_num) 979{ 980 switch (profile_num) { 981 case AccelProfileClassic: 982 return ClassicProfile; 983 case AccelProfileDeviceSpecific: 984 return vel->deviceSpecificProfile; 985 case AccelProfilePolynomial: 986 return PolynomialAccelerationProfile; 987 case AccelProfileSmoothLinear: 988 return SmoothLinearProfile; 989 case AccelProfileSimple: 990 return SimpleSmoothProfile; 991 case AccelProfilePower: 992 return PowerProfile; 993 case AccelProfileLinear: 994 return LinearProfile; 995 case AccelProfileSmoothLimited: 996 return SmoothLimitedProfile; 997 case AccelProfileNone: 998 return NoProfile; 999 default: 1000 return NULL; 1001 } 1002} 1003 1004/** 1005 * Set the profile by number. 1006 * Intended to make profiles exchangeable at runtime. 1007 * If you created a profile, give it a number here and in the header to 1008 * make it selectable. In case some profile-specific init is needed, here 1009 * would be a good place, since FreeVelocityData() also calls this with 1010 * PROFILE_UNINITIALIZE. 1011 * 1012 * returns FALSE if profile number is unavailable, TRUE otherwise. 1013 */ 1014int 1015SetAccelerationProfile(DeviceVelocityPtr vel, int profile_num) 1016{ 1017 PointerAccelerationProfileFunc profile; 1018 1019 profile = GetAccelerationProfile(vel, profile_num); 1020 1021 if (profile == NULL && profile_num != PROFILE_UNINITIALIZE) 1022 return FALSE; 1023 1024 /* Here one could free old profile-private data */ 1025 free(vel->profile_private); 1026 vel->profile_private = NULL; 1027 /* Here one could init profile-private data */ 1028 vel->Profile = profile; 1029 vel->statistics.profile_number = profile_num; 1030 return TRUE; 1031} 1032 1033/********************************************** 1034 * driver interaction 1035 **********************************************/ 1036 1037/** 1038 * device-specific profile 1039 * 1040 * The device-specific profile is intended as a hook for a driver 1041 * which may want to provide an own acceleration profile. 1042 * It should not rely on profile-private data, instead 1043 * it should do init/uninit in the driver (ie. with DEVICE_INIT and friends). 1044 * Users may override or choose it. 1045 */ 1046void 1047SetDeviceSpecificAccelerationProfile(DeviceVelocityPtr vel, 1048 PointerAccelerationProfileFunc profile) 1049{ 1050 if (vel) 1051 vel->deviceSpecificProfile = profile; 1052} 1053 1054/** 1055 * Use this function to obtain a DeviceVelocityPtr for a device. Will return NULL if 1056 * the predictable acceleration scheme is not in effect. 1057 */ 1058DeviceVelocityPtr 1059GetDevicePredictableAccelData(DeviceIntPtr dev) 1060{ 1061 BUG_RETURN_VAL(!dev, NULL); 1062 1063 if (dev->valuator && 1064 dev->valuator->accelScheme.AccelSchemeProc == 1065 acceleratePointerPredictable && 1066 dev->valuator->accelScheme.accelData != NULL) { 1067 1068 return ((PredictableAccelSchemePtr) 1069 dev->valuator->accelScheme.accelData)->vel; 1070 } 1071 return NULL; 1072} 1073 1074/******************************** 1075 * acceleration schemes 1076 *******************************/ 1077 1078/** 1079 * Modifies valuators in-place. 1080 * This version employs a velocity approximation algorithm to 1081 * enable fine-grained predictable acceleration profiles. 1082 */ 1083void 1084acceleratePointerPredictable(DeviceIntPtr dev, ValuatorMask *val, CARD32 evtime) 1085{ 1086 double dx = 0, dy = 0; 1087 DeviceVelocityPtr velocitydata = GetDevicePredictableAccelData(dev); 1088 Bool soften = TRUE; 1089 1090 if (valuator_mask_num_valuators(val) == 0 || !velocitydata) 1091 return; 1092 1093 if (velocitydata->statistics.profile_number == AccelProfileNone && 1094 velocitydata->const_acceleration == 1.0f) { 1095 return; /*we're inactive anyway, so skip the whole thing. */ 1096 } 1097 1098 if (valuator_mask_isset(val, 0)) { 1099 dx = valuator_mask_get_double(val, 0); 1100 } 1101 1102 if (valuator_mask_isset(val, 1)) { 1103 dy = valuator_mask_get_double(val, 1); 1104 } 1105 1106 if (dx != 0.0 || dy != 0.0) { 1107 /* reset non-visible state? */ 1108 if (ProcessVelocityData2D(velocitydata, dx, dy, evtime)) { 1109 soften = FALSE; 1110 } 1111 1112 if (dev->ptrfeed && dev->ptrfeed->ctrl.num) { 1113 double mult; 1114 1115 /* invoke acceleration profile to determine acceleration */ 1116 mult = ComputeAcceleration(dev, velocitydata, 1117 dev->ptrfeed->ctrl.threshold, 1118 (double) dev->ptrfeed->ctrl.num / 1119 (double) dev->ptrfeed->ctrl.den); 1120 1121 DebugAccelF("mult is %f\n", mult); 1122 if (mult != 1.0f || velocitydata->const_acceleration != 1.0f) { 1123 if (mult > 1.0f && soften) 1124 ApplySoftening(velocitydata, &dx, &dy); 1125 ApplyConstantDeceleration(velocitydata, &dx, &dy); 1126 1127 if (dx != 0.0) 1128 valuator_mask_set_double(val, 0, mult * dx); 1129 if (dy != 0.0) 1130 valuator_mask_set_double(val, 1, mult * dy); 1131 DebugAccelF("delta x:%.3f y:%.3f\n", mult * dx, mult * dy); 1132 } 1133 } 1134 } 1135 /* remember last motion delta (for softening/slow movement treatment) */ 1136 velocitydata->last_dx = dx; 1137 velocitydata->last_dy = dy; 1138} 1139 1140/** 1141 * Originally a part of xf86PostMotionEvent; modifies valuators 1142 * in-place. Retained mostly for embedded scenarios. 1143 */ 1144void 1145acceleratePointerLightweight(DeviceIntPtr dev, 1146 ValuatorMask *val, CARD32 ignored) 1147{ 1148 double mult = 0.0, tmpf; 1149 double dx = 0.0, dy = 0.0; 1150 1151 if (valuator_mask_isset(val, 0)) { 1152 dx = valuator_mask_get(val, 0); 1153 } 1154 1155 if (valuator_mask_isset(val, 1)) { 1156 dy = valuator_mask_get(val, 1); 1157 } 1158 1159 if (valuator_mask_num_valuators(val) == 0) 1160 return; 1161 1162 if (dev->ptrfeed && dev->ptrfeed->ctrl.num) { 1163 /* modeled from xf86Events.c */ 1164 if (dev->ptrfeed->ctrl.threshold) { 1165 if ((fabs(dx) + fabs(dy)) >= dev->ptrfeed->ctrl.threshold) { 1166 if (dx != 0.0) { 1167 tmpf = (dx * (double) (dev->ptrfeed->ctrl.num)) / 1168 (double) (dev->ptrfeed->ctrl.den); 1169 valuator_mask_set_double(val, 0, tmpf); 1170 } 1171 1172 if (dy != 0.0) { 1173 tmpf = (dy * (double) (dev->ptrfeed->ctrl.num)) / 1174 (double) (dev->ptrfeed->ctrl.den); 1175 valuator_mask_set_double(val, 1, tmpf); 1176 } 1177 } 1178 } 1179 else { 1180 mult = pow(dx * dx + dy * dy, 1181 ((double) (dev->ptrfeed->ctrl.num) / 1182 (double) (dev->ptrfeed->ctrl.den) - 1.0) / 2.0) / 2.0; 1183 if (dx != 0.0) 1184 valuator_mask_set_double(val, 0, mult * dx); 1185 if (dy != 0.0) 1186 valuator_mask_set_double(val, 1, mult * dy); 1187 } 1188 } 1189} 1190