1#include "movement_maker.h" 2 3#include <stdlib.h> 4 5#include <KernelExport.h> 6 7 8#if 1 9# define INFO(x...) dprintf(x) 10#else 11# define INFO(x...) 12#endif 13 14//#define TRACE_MOVEMENT_MAKER 15#ifdef TRACE_MOVEMENT_MAKER 16# define TRACE(x...) dprintf(x) 17#else 18# define TRACE(x...) 19#endif 20 21 22typedef union { 23 float value; 24 /* FIXME: Assumes 32 bit int. */ 25 unsigned int word; 26} ieee_float_shape_type; 27 28/* Get a 32 bit int from a float. */ 29 30#define GET_FLOAT_WORD(i,d) \ 31do { \ 32 ieee_float_shape_type gf_u; \ 33 gf_u.value = (d); \ 34 (i) = gf_u.word; \ 35} while (0) 36 37/* Set a float from a 32 bit int. */ 38 39#define SET_FLOAT_WORD(d,i) \ 40do { \ 41 ieee_float_shape_type sf_u; \ 42 sf_u.word = (i); \ 43 (d) = sf_u.value; \ 44} while (0) 45 46static const float huge = 1.0e30; 47 48float 49floorf(float x) 50{ 51 int32 i0,j0; 52 uint32 i; 53 GET_FLOAT_WORD(i0,x); 54 j0 = ((i0>>23)&0xff)-0x7f; 55 if (j0<23) { 56 if (j0<0) { /* raise inexact if x != 0 */ 57 if (huge+x>(float)0.0) {/* return 0*sign(x) if |x|<1 */ 58 if (i0>=0) {i0=0;} 59 else if ((i0&0x7fffffff)!=0) 60 { i0=0xbf800000;} 61 } 62 } else { 63 i = (0x007fffff)>>j0; 64 if ((i0&i)==0) return x; /* x is integral */ 65 if (huge+x>(float)0.0) { /* raise inexact flag */ 66 if (i0<0) i0 += (0x00800000)>>j0; 67 i0 &= (~i); 68 } 69 } 70 } else { 71 if (j0==0x80) return x+x; /* inf or NaN */ 72 else return x; /* x is integral */ 73 } 74 SET_FLOAT_WORD(x,i0); 75 return x; 76} 77 78 79float 80ceilf(float x) 81{ 82 int32 i0,j0; 83 uint32 i; 84 85 GET_FLOAT_WORD(i0,x); 86 j0 = ((i0>>23)&0xff)-0x7f; 87 if (j0<23) { 88 if (j0<0) { /* raise inexact if x != 0 */ 89 if (huge+x>(float)0.0) {/* return 0*sign(x) if |x|<1 */ 90 if (i0<0) {i0=0x80000000;} 91 else if (i0!=0) { i0=0x3f800000;} 92 } 93 } else { 94 i = (0x007fffff)>>j0; 95 if ((i0&i)==0) return x; /* x is integral */ 96 if (huge+x>(float)0.0) { /* raise inexact flag */ 97 if (i0>0) i0 += (0x00800000)>>j0; 98 i0 &= (~i); 99 } 100 } 101 } else { 102 if (j0==0x80) return x+x; /* inf or NaN */ 103 else return x; /* x is integral */ 104 } 105 SET_FLOAT_WORD(x,i0); 106 return x; 107} 108 109static const float one = 1.0, tiny=1.0e-30; 110 111float 112sqrtf(float x) 113{ 114 float z; 115 int32 sign = (int)0x80000000; 116 int32 ix,s,q,m,t,i; 117 uint32 r; 118 119 GET_FLOAT_WORD(ix,x); 120 121 /* take care of Inf and NaN */ 122 if ((ix&0x7f800000)==0x7f800000) { 123 return x*x+x; /* sqrt(NaN)=NaN, sqrt(+inf)=+inf 124 sqrt(-inf)=sNaN */ 125 } 126 /* take care of zero */ 127 if (ix<=0) { 128 if ((ix&(~sign))==0) return x;/* sqrt(+-0) = +-0 */ 129 else if (ix<0) 130 return (x-x)/(x-x); /* sqrt(-ve) = sNaN */ 131 } 132 /* normalize x */ 133 m = (ix>>23); 134 if (m==0) { /* subnormal x */ 135 for(i=0;(ix&0x00800000)==0;i++) ix<<=1; 136 m -= i-1; 137 } 138 m -= 127; /* unbias exponent */ 139 ix = (ix&0x007fffff)|0x00800000; 140 if (m&1) /* odd m, double x to make it even */ 141 ix += ix; 142 m >>= 1; /* m = [m/2] */ 143 144 /* generate sqrt(x) bit by bit */ 145 ix += ix; 146 q = s = 0; /* q = sqrt(x) */ 147 r = 0x01000000; /* r = moving bit from right to left */ 148 149 while(r!=0) { 150 t = s+r; 151 if (t<=ix) { 152 s = t+r; 153 ix -= t; 154 q += r; 155 } 156 ix += ix; 157 r>>=1; 158 } 159 160 /* use floating add to find out rounding direction */ 161 if (ix!=0) { 162 z = one-tiny; /* trigger inexact flag */ 163 if (z>=one) { 164 z = one+tiny; 165 if (z>one) 166 q += 2; 167 else 168 q += (q&1); 169 } 170 } 171 ix = (q>>1)+0x3f000000; 172 ix += (m <<23); 173 SET_FLOAT_WORD(z,ix); 174 return z; 175} 176 177 178int32 179make_small(float value) 180{ 181 if (value > 0) 182 return floorf(value); 183 else 184 return ceilf(value); 185} 186 187 188 189void 190MovementMaker::SetSettings(touchpad_settings* settings) 191{ 192 fSettings = settings; 193} 194 195 196void 197MovementMaker::SetSpecs(hardware_specs* specs) 198{ 199 fSpecs = specs; 200 201 fAreaWidth = fSpecs->areaEndX - fSpecs->areaStartX; 202 fAreaHeight = fSpecs->areaEndY - fSpecs->areaStartY; 203 204 // calibrated on the synaptics touchpad 205 fSpeed = SYN_WIDTH / fAreaWidth; 206 fSmallMovement = 3 / fSpeed; 207} 208 209 210void 211MovementMaker::StartNewMovment() 212{ 213 if (fSettings->scroll_xstepsize <= 0) 214 fSettings->scroll_xstepsize = 1; 215 if (fSettings->scroll_ystepsize <= 0) 216 fSettings->scroll_ystepsize = 1; 217 218 fMovementMakerStarted = true; 219 scrolling_x = 0; 220 scrolling_y = 0; 221} 222 223 224void 225MovementMaker::GetMovement(uint32 posX, uint32 posY) 226{ 227 _GetRawMovement(posX, posY); 228 229// INFO("SYN: pos: %lu x %lu, delta: %ld x %ld, sums: %ld x %ld\n", 230// posX, posY, xDelta, yDelta, 231// fDeltaSumX, fDeltaSumY); 232 233 xDelta = xDelta; 234 yDelta = yDelta; 235} 236 237 238void 239MovementMaker::GetScrolling(uint32 posX, uint32 posY) 240{ 241 int32 stepsX = 0, stepsY = 0; 242 243 _GetRawMovement(posX, posY); 244 _ComputeAcceleration(fSettings->scroll_acceleration); 245 246 if (fSettings->scroll_xstepsize > 0) { 247 scrolling_x += xDelta; 248 249 stepsX = make_small(scrolling_x / fSettings->scroll_xstepsize); 250 251 scrolling_x -= stepsX * fSettings->scroll_xstepsize; 252 xDelta = stepsX; 253 } else { 254 scrolling_x = 0; 255 xDelta = 0; 256 } 257 if (fSettings->scroll_ystepsize > 0) { 258 scrolling_y += yDelta; 259 260 stepsY = make_small(scrolling_y / fSettings->scroll_ystepsize); 261 262 scrolling_y -= stepsY * fSettings->scroll_ystepsize; 263 yDelta = -1 * stepsY; 264 } else { 265 scrolling_y = 0; 266 yDelta = 0; 267 } 268} 269 270 271void 272MovementMaker::_GetRawMovement(uint32 posX, uint32 posY) 273{ 274 // calibrated on the synaptics touchpad 275 posX = posX * float(SYN_WIDTH) / fAreaWidth; 276 posY = posY * float(SYN_HEIGHT) / fAreaHeight; 277 278 const float acceleration = 0.8; 279 const float translation = 12.0; 280 281 int diff; 282 283 if (fMovementMakerStarted) { 284 fMovementMakerStarted = false; 285 // init delta tracking 286 fPreviousX = posX; 287 fPreviousY = posY; 288 // deltas are automatically reset 289 } 290 291 // accumulate delta and store current pos, reset if pos did not change 292 diff = posX - fPreviousX; 293 // lessen the effect of small diffs 294 if ((diff > -fSmallMovement && diff < -1) 295 || (diff > 1 && diff < fSmallMovement)) { 296 diff /= 2; 297 } 298 if (diff == 0) 299 fDeltaSumX = 0.0; 300 else 301 fDeltaSumX += diff; 302 303 diff = posY - fPreviousY; 304 // lessen the effect of small diffs 305 if ((diff > -fSmallMovement && diff < -1) 306 || (diff > 1 && diff < fSmallMovement)) { 307 diff /= 2; 308 } 309 if (diff == 0) 310 fDeltaSumY = 0.0; 311 else 312 fDeltaSumY += diff; 313 314 fPreviousX = posX; 315 fPreviousY = posY; 316 317 // compute current delta and reset accumulated delta if 318 // abs() is greater than 1 319 xDelta = fDeltaSumX / translation; 320 yDelta = fDeltaSumY / translation; 321 if (xDelta > 1.0) { 322 fDeltaSumX = 0.0; 323 xDelta = 1.0 + (xDelta - 1.0) * acceleration; 324 } else if (xDelta < -1.0) { 325 fDeltaSumX = 0.0; 326 xDelta = -1.0 + (xDelta + 1.0) * acceleration; 327 } 328 329 if (yDelta > 1.0) { 330 fDeltaSumY = 0.0; 331 yDelta = 1.0 + (yDelta - 1.0) * acceleration; 332 } else if (yDelta < -1.0) { 333 fDeltaSumY = 0.0; 334 yDelta = -1.0 + (yDelta + 1.0) * acceleration; 335 } 336 337 xDelta = make_small(xDelta); 338 yDelta = make_small(yDelta); 339} 340 341 342 343void 344MovementMaker::_ComputeAcceleration(int8 accel_factor) 345{ 346 // acceleration 347 float acceleration = 1; 348 if (accel_factor != 0) { 349 acceleration = 1 + sqrtf(xDelta * xDelta 350 + yDelta * yDelta) * accel_factor / 50.0; 351 } 352 353 xDelta = make_small(xDelta * acceleration); 354 yDelta = make_small(yDelta * acceleration); 355} 356 357 358// #pragma mark - 359 360 361#define fTapTimeOUT 200000 362 363 364void 365TouchpadMovement::Init() 366{ 367 fMovementStarted = false; 368 fScrollingStarted = false; 369 fTapStarted = false; 370 fValidEdgeMotion = false; 371 fDoubleClick = false; 372} 373 374 375status_t 376TouchpadMovement::EventToMovement(touch_event *event, mouse_movement *movement) 377{ 378 if (!movement) 379 return B_ERROR; 380 381 movement->xdelta = 0; 382 movement->ydelta = 0; 383 movement->buttons = 0; 384 movement->wheel_ydelta = 0; 385 movement->wheel_xdelta = 0; 386 movement->modifiers = 0; 387 movement->clicks = 0; 388 movement->timestamp = system_time(); 389 390 if ((movement->timestamp - fTapTime) > fTapTimeOUT) { 391 TRACE("TouchpadMovement: tap gesture timed out\n"); 392 fTapStarted = false; 393 if (!fDoubleClick 394 || (movement->timestamp - fTapTime) > 2 * fTapTimeOUT) { 395 fTapClicks = 0; 396 } 397 } 398 399 if (event->buttons & kLeftButton) { 400 fTapClicks = 0; 401 fTapdragStarted = false; 402 fTapStarted = false; 403 fValidEdgeMotion = false; 404 } 405 406 if (event->zPressure >= fSpecs->minPressure 407 && event->zPressure < fSpecs->maxPressure 408 && ((event->wValue >= 4 && event->wValue <= 7) 409 || event->wValue == 0 || event->wValue == 1) 410 && (event->xPosition != 0 || event->yPosition != 0)) { 411 // The touch pad is in touch with at least one finger 412 if (!_CheckScrollingToMovement(event, movement)) 413 _MoveToMovement(event, movement); 414 } else 415 _NoTouchToMovement(event, movement); 416 417 return B_OK; 418} 419 420 421// in pixel per second 422const int32 kEdgeMotionSpeed = 200; 423 424 425bool 426TouchpadMovement::_EdgeMotion(mouse_movement *movement, touch_event *event, 427 bool validStart) 428{ 429 float xdelta = 0; 430 float ydelta = 0; 431 432 bigtime_t time = system_time(); 433 if (fLastEdgeMotion != 0) { 434 xdelta = fRestEdgeMotion + kEdgeMotionSpeed * 435 float(time - fLastEdgeMotion) / (1000 * 1000); 436 fRestEdgeMotion = xdelta - int32(xdelta); 437 ydelta = xdelta; 438 } else { 439 fRestEdgeMotion = 0; 440 } 441 442 bool inXEdge = false; 443 bool inYEdge = false; 444 445 if (event->xPosition < fSpecs->areaStartX + fSpecs->edgeMotionWidth) { 446 inXEdge = true; 447 xdelta *= -1; 448 } else if (event->xPosition > uint16( 449 fSpecs->areaEndX - fSpecs->edgeMotionWidth)) { 450 inXEdge = true; 451 } 452 453 if (event->yPosition < fSpecs->areaStartY + fSpecs->edgeMotionWidth) { 454 inYEdge = true; 455 ydelta *= -1; 456 } else if (event->yPosition > uint16( 457 fSpecs->areaEndY - fSpecs->edgeMotionWidth)) { 458 inYEdge = true; 459 } 460 461 // for a edge motion the drag has to be started in the middle of the pad 462 // TODO: this is difficult to understand simplify the code 463 if (inXEdge && validStart) 464 movement->xdelta = xdelta; 465 if (inYEdge && validStart) 466 movement->ydelta = ydelta; 467 468 if (!inXEdge && !inYEdge) 469 fLastEdgeMotion = 0; 470 else 471 fLastEdgeMotion = time; 472 473 if ((inXEdge || inYEdge) && !validStart) 474 return false; 475 476 return true; 477} 478 479 480/*! If a button has been clicked (movement->buttons must be set accordingly), 481 this function updates the fClickCount, as well as the 482 \a movement's clicks field. 483 Also, it sets the button state from movement->buttons. 484*/ 485void 486TouchpadMovement::UpdateButtons(mouse_movement *movement) 487{ 488 // set click count correctly according to double click timeout 489 if (movement->buttons != 0 && fButtonsState == 0) { 490 if (fClickLastTime + click_speed > movement->timestamp) 491 fClickCount++; 492 else 493 fClickCount = 1; 494 495 fClickLastTime = movement->timestamp; 496 } 497 498 if (movement->buttons != 0) 499 movement->clicks = fClickCount; 500 501 fButtonsState = movement->buttons; 502} 503 504 505void 506TouchpadMovement::_NoTouchToMovement(touch_event *event, 507 mouse_movement *movement) 508{ 509 uint32 buttons = event->buttons; 510 511 TRACE("TouchpadMovement: no touch event\n"); 512 513 fScrollingStarted = false; 514 fMovementStarted = false; 515 fLastEdgeMotion = 0; 516 517 if (fTapdragStarted 518 && (movement->timestamp - fTapTime) < fTapTimeOUT) { 519 buttons = kLeftButton; 520 } 521 522 // if the movement stopped switch off the tap drag when timeout is expired 523 if ((movement->timestamp - fTapTime) > fTapTimeOUT) { 524 fTapdragStarted = false; 525 fValidEdgeMotion = false; 526 TRACE("TouchpadMovement: tap drag gesture timed out\n"); 527 } 528 529 if (abs(fTapDeltaX) > 15 || abs(fTapDeltaY) > 15) { 530 fTapStarted = false; 531 fTapClicks = 0; 532 } 533 534 if (fTapStarted || fDoubleClick) { 535 TRACE("TouchpadMovement: tap gesture\n"); 536 fTapClicks++; 537 538 if (fTapClicks > 1) { 539 TRACE("TouchpadMovement: empty click\n"); 540 buttons = kNoButton; 541 fTapClicks = 0; 542 fDoubleClick = true; 543 } else { 544 buttons = kLeftButton; 545 fTapStarted = false; 546 fTapdragStarted = true; 547 fDoubleClick = false; 548 } 549 } 550 551 movement->buttons = buttons; 552 UpdateButtons(movement); 553} 554 555 556void 557TouchpadMovement::_MoveToMovement(touch_event *event, mouse_movement *movement) 558{ 559 bool isStartOfMovement = false; 560 float pressure = 0; 561 562 TRACE("TouchpadMovement: movement event\n"); 563 if (!fMovementStarted) { 564 isStartOfMovement = true; 565 fMovementStarted = true; 566 StartNewMovment(); 567 } 568 569 GetMovement(event->xPosition, event->yPosition); 570 571 movement->xdelta = xDelta; 572 movement->ydelta = yDelta; 573 574 // tap gesture 575 fTapDeltaX += xDelta; 576 fTapDeltaY += yDelta; 577 578 if (fTapdragStarted) { 579 movement->buttons = kLeftButton; 580 movement->clicks = 0; 581 582 fValidEdgeMotion = _EdgeMotion(movement, event, fValidEdgeMotion); 583 TRACE("TouchpadMovement: tap drag\n"); 584 } else { 585 TRACE("TouchpadMovement: movement set buttons\n"); 586 movement->buttons = event->buttons; 587 } 588 589 // use only a fraction of pressure range, the max pressure seems to be 590 // to high 591 pressure = 20 * (event->zPressure - fSpecs->minPressure) 592 / (fSpecs->realMaxPressure - fSpecs->minPressure); 593 if (!fTapStarted 594 && isStartOfMovement 595 && fSettings->tapgesture_sensibility > 0. 596 && fSettings->tapgesture_sensibility > (20 - pressure)) { 597 TRACE("TouchpadMovement: tap started\n"); 598 fTapStarted = true; 599 fTapTime = system_time(); 600 fTapDeltaX = 0; 601 fTapDeltaY = 0; 602 } 603 604 UpdateButtons(movement); 605} 606 607 608/*! Checks if this is a scrolling event or not, and also actually does the 609 scrolling work if it is. 610 611 \return \c true if this was a scrolling event, \c false if not. 612*/ 613bool 614TouchpadMovement::_CheckScrollingToMovement(touch_event *event, 615 mouse_movement *movement) 616{ 617 bool isSideScrollingV = false; 618 bool isSideScrollingH = false; 619 620 // if a button is pressed don't allow to scroll, we likely be in a drag 621 // action 622 if (fButtonsState != 0) 623 return false; 624 625 if ((fSpecs->areaEndX - fAreaWidth * fSettings->scroll_rightrange 626 < event->xPosition && !fMovementStarted 627 && fSettings->scroll_rightrange > 0.000001) 628 || fSettings->scroll_rightrange > 0.999999) { 629 isSideScrollingV = true; 630 } 631 if ((fSpecs->areaStartY + fAreaHeight * fSettings->scroll_bottomrange 632 > event->yPosition && !fMovementStarted 633 && fSettings->scroll_bottomrange > 0.000001) 634 || fSettings->scroll_bottomrange > 0.999999) { 635 isSideScrollingH = true; 636 } 637 if ((event->wValue == 0 || event->wValue == 1) 638 && fSettings->scroll_twofinger) { 639 // two finger scrolling is enabled 640 isSideScrollingV = true; 641 isSideScrollingH = fSettings->scroll_twofinger_horizontal; 642 } 643 644 if (!isSideScrollingV && !isSideScrollingH) { 645 fScrollingStarted = false; 646 return false; 647 } 648 649 TRACE("TouchpadMovement: scroll event\n"); 650 651 fTapStarted = false; 652 fTapClicks = 0; 653 fTapdragStarted = false; 654 fValidEdgeMotion = false; 655 if (!fScrollingStarted) { 656 fScrollingStarted = true; 657 StartNewMovment(); 658 } 659 GetScrolling(event->xPosition, event->yPosition); 660 movement->wheel_ydelta = yDelta; 661 movement->wheel_xdelta = xDelta; 662 663 if (isSideScrollingV && !isSideScrollingH) 664 movement->wheel_xdelta = 0; 665 else if (isSideScrollingH && !isSideScrollingV) 666 movement->wheel_ydelta = 0; 667 668 fButtonsState = movement->buttons; 669 670 return true; 671} 672