1 /* 2 * Copyright 2008-2011, Clemens Zeidler <haiku@clemens-zeidler.de> 3 * Copyright 2022, Haiku, Inc. All rights reserved. 4 * Distributed under the terms of the MIT License. 5 */ 6 #include "movement_maker.h" 7 8 #include <stdlib.h> 9 #include <math.h> 10 11 #include <KernelExport.h> 12 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 22 // magic constants 23 #define SYN_WIDTH (4100) 24 #define SYN_HEIGHT (3140) 25 26 27 static int32 28 make_small(float value) 29 { 30 if (value > 0) 31 return (int32)floorf(value); 32 else 33 return (int32)ceilf(value); 34 } 35 36 37 void 38 MovementMaker::SetSettings(const touchpad_settings& settings) 39 { 40 fSettings = settings; 41 } 42 43 44 void 45 MovementMaker::SetSpecs(const touchpad_specs& specs) 46 { 47 fSpecs = specs; 48 49 fAreaWidth = fSpecs.areaEndX - fSpecs.areaStartX; 50 fAreaHeight = fSpecs.areaEndY - fSpecs.areaStartY; 51 52 // calibrated on the synaptics touchpad 53 fSpeed = SYN_WIDTH / fAreaWidth; 54 fSmallMovement = 3 / fSpeed; 55 } 56 57 58 void 59 MovementMaker::StartNewMovment() 60 { 61 if (fSettings.scroll_xstepsize <= 0) 62 fSettings.scroll_xstepsize = 1; 63 if (fSettings.scroll_ystepsize <= 0) 64 fSettings.scroll_ystepsize = 1; 65 66 fMovementMakerStarted = true; 67 scrolling_x = 0; 68 scrolling_y = 0; 69 } 70 71 72 void 73 MovementMaker::GetMovement(uint32 posX, uint32 posY) 74 { 75 _GetRawMovement(posX, posY); 76 } 77 78 79 void 80 MovementMaker::GetScrolling(uint32 posX, uint32 posY) 81 { 82 int32 stepsX = 0, stepsY = 0; 83 84 _GetRawMovement(posX, posY); 85 _ComputeAcceleration(fSettings.scroll_acceleration); 86 87 if (fSettings.scroll_xstepsize > 0) { 88 scrolling_x += xDelta; 89 90 stepsX = make_small(scrolling_x / fSettings.scroll_xstepsize); 91 92 scrolling_x -= stepsX * fSettings.scroll_xstepsize; 93 xDelta = stepsX; 94 } else { 95 scrolling_x = 0; 96 xDelta = 0; 97 } 98 if (fSettings.scroll_ystepsize > 0) { 99 scrolling_y += yDelta; 100 101 stepsY = make_small(scrolling_y / fSettings.scroll_ystepsize); 102 103 scrolling_y -= stepsY * fSettings.scroll_ystepsize; 104 yDelta = -1 * stepsY; 105 } else { 106 scrolling_y = 0; 107 yDelta = 0; 108 } 109 } 110 111 112 void 113 MovementMaker::_GetRawMovement(uint32 posX, uint32 posY) 114 { 115 // calibrated on the synaptics touchpad 116 posX = posX * SYN_WIDTH / fAreaWidth; 117 posY = posY * SYN_HEIGHT / fAreaHeight; 118 119 const float acceleration = 0.8; 120 const float translation = 12.0; 121 122 int diff; 123 124 if (fMovementMakerStarted) { 125 fMovementMakerStarted = false; 126 // init delta tracking 127 fPreviousX = posX; 128 fPreviousY = posY; 129 // deltas are automatically reset 130 } 131 132 // accumulate delta and store current pos, reset if pos did not change 133 diff = posX - fPreviousX; 134 // lessen the effect of small diffs 135 if ((diff > -fSmallMovement && diff < -1) 136 || (diff > 1 && diff < fSmallMovement)) { 137 diff /= 2; 138 } 139 if (diff == 0) 140 fDeltaSumX = 0; 141 else 142 fDeltaSumX += diff; 143 144 diff = posY - fPreviousY; 145 // lessen the effect of small diffs 146 if ((diff > -fSmallMovement && diff < -1) 147 || (diff > 1 && diff < fSmallMovement)) { 148 diff /= 2; 149 } 150 if (diff == 0) 151 fDeltaSumY = 0; 152 else 153 fDeltaSumY += diff; 154 155 fPreviousX = posX; 156 fPreviousY = posY; 157 158 // compute current delta and reset accumulated delta if 159 // abs() is greater than 1 160 xDelta = fDeltaSumX / translation; 161 yDelta = fDeltaSumY / translation; 162 if (xDelta > 1.0) { 163 fDeltaSumX = 0.0; 164 xDelta = 1.0 + (xDelta - 1.0) * acceleration; 165 } else if (xDelta < -1.0) { 166 fDeltaSumX = 0.0; 167 xDelta = -1.0 + (xDelta + 1.0) * acceleration; 168 } 169 170 if (yDelta > 1.0) { 171 fDeltaSumY = 0.0; 172 yDelta = 1.0 + (yDelta - 1.0) * acceleration; 173 } else if (yDelta < -1.0) { 174 fDeltaSumY = 0.0; 175 yDelta = -1.0 + (yDelta + 1.0) * acceleration; 176 } 177 178 xDelta = make_small(xDelta); 179 yDelta = make_small(yDelta); 180 } 181 182 183 void 184 MovementMaker::_ComputeAcceleration(int8 accel_factor) 185 { 186 // acceleration 187 float acceleration = 1; 188 if (accel_factor != 0) { 189 acceleration = 1 + sqrtf(xDelta * xDelta 190 + yDelta * yDelta) * accel_factor / 50.0; 191 } 192 193 xDelta = make_small(xDelta * acceleration); 194 yDelta = make_small(yDelta * acceleration); 195 } 196 197 198 // #pragma mark - 199 200 201 #define fTapTimeOUT 200000 202 203 204 TouchpadMovement::TouchpadMovement() 205 { 206 fMovementStarted = false; 207 fScrollingStarted = false; 208 fTapStarted = false; 209 fValidEdgeMotion = false; 210 fDoubleClick = false; 211 } 212 213 214 status_t 215 TouchpadMovement::EventToMovement(const touchpad_movement* event, mouse_movement* movement, 216 bigtime_t& repeatTimeout) 217 { 218 if (!movement) 219 return B_ERROR; 220 221 movement->xdelta = 0; 222 movement->ydelta = 0; 223 movement->buttons = 0; 224 movement->wheel_ydelta = 0; 225 movement->wheel_xdelta = 0; 226 movement->modifiers = 0; 227 movement->clicks = 0; 228 movement->timestamp = system_time(); 229 230 if ((movement->timestamp - fTapTime) > fTapTimeOUT) { 231 if (fTapStarted) 232 TRACE("TouchpadMovement: tap gesture timed out\n"); 233 fTapStarted = false; 234 if (!fDoubleClick 235 || (movement->timestamp - fTapTime) > 2 * fTapTimeOUT) { 236 fTapClicks = 0; 237 } 238 } 239 240 if (event->buttons & kLeftButton) { 241 fTapClicks = 0; 242 fTapdragStarted = false; 243 fTapStarted = false; 244 fValidEdgeMotion = false; 245 } 246 247 if (event->zPressure >= fSpecs.minPressure 248 && event->zPressure < fSpecs.maxPressure 249 && ((event->fingerWidth >= 4 && event->fingerWidth <= 7) 250 || event->fingerWidth == 0 || event->fingerWidth == 1) 251 && (event->xPosition != 0 || event->yPosition != 0)) { 252 // The touch pad is in touch with at least one finger 253 if (!_CheckScrollingToMovement(event, movement)) 254 _MoveToMovement(event, movement); 255 } else 256 _NoTouchToMovement(event, movement); 257 258 259 if (fTapdragStarted || fValidEdgeMotion) { 260 // We want the current event to be repeated in 50ms if no other 261 // events occur in the interim. 262 repeatTimeout = 1000 * 50; 263 } else 264 repeatTimeout = B_INFINITE_TIMEOUT; 265 266 return B_OK; 267 } 268 269 270 // in pixel per second 271 const int32 kEdgeMotionSpeed = 200; 272 273 274 bool 275 TouchpadMovement::_EdgeMotion(const touchpad_movement *event, mouse_movement *movement, 276 bool validStart) 277 { 278 float xdelta = 0; 279 float ydelta = 0; 280 281 bigtime_t time = system_time(); 282 if (fLastEdgeMotion != 0) { 283 xdelta = fRestEdgeMotion + kEdgeMotionSpeed * 284 float(time - fLastEdgeMotion) / (1000 * 1000); 285 fRestEdgeMotion = xdelta - int32(xdelta); 286 ydelta = xdelta; 287 } else { 288 fRestEdgeMotion = 0; 289 } 290 291 bool inXEdge = false; 292 bool inYEdge = false; 293 294 if (int32(event->xPosition) < fSpecs.areaStartX + fSpecs.edgeMotionWidth) { 295 inXEdge = true; 296 xdelta *= -1; 297 } else if (event->xPosition > uint16( 298 fSpecs.areaEndX - fSpecs.edgeMotionWidth)) { 299 inXEdge = true; 300 } 301 302 if (int32(event->yPosition) < fSpecs.areaStartY + fSpecs.edgeMotionWidth) { 303 inYEdge = true; 304 ydelta *= -1; 305 } else if (event->yPosition > uint16( 306 fSpecs.areaEndY - fSpecs.edgeMotionWidth)) { 307 inYEdge = true; 308 } 309 310 // for a edge motion the drag has to be started in the middle of the pad 311 // TODO: this is difficult to understand simplify the code 312 if (inXEdge && validStart) 313 movement->xdelta = make_small(xdelta); 314 if (inYEdge && validStart) 315 movement->ydelta = make_small(ydelta); 316 317 if (!inXEdge && !inYEdge) 318 fLastEdgeMotion = 0; 319 else 320 fLastEdgeMotion = time; 321 322 if ((inXEdge || inYEdge) && !validStart) 323 return false; 324 325 return true; 326 } 327 328 329 /*! If a button has been clicked (movement->buttons must be set accordingly), 330 this function updates the fClickCount, as well as the 331 \a movement's clicks field. 332 Also, it sets the button state from movement->buttons. 333 */ 334 void 335 TouchpadMovement::_UpdateButtons(mouse_movement *movement) 336 { 337 // set click count correctly according to double click timeout 338 if (movement->buttons != 0 && fButtonsState == 0) { 339 if (fClickLastTime + click_speed > movement->timestamp) 340 fClickCount++; 341 else 342 fClickCount = 1; 343 344 fClickLastTime = movement->timestamp; 345 } 346 347 if (movement->buttons != 0) 348 movement->clicks = fClickCount; 349 350 fButtonsState = movement->buttons; 351 } 352 353 354 void 355 TouchpadMovement::_NoTouchToMovement(const touchpad_movement *event, 356 mouse_movement *movement) 357 { 358 uint32 buttons = event->buttons; 359 360 if (fMovementStarted) 361 TRACE("TouchpadMovement: no touch event\n"); 362 363 fScrollingStarted = false; 364 fMovementStarted = false; 365 fLastEdgeMotion = 0; 366 367 if (fTapdragStarted 368 && (movement->timestamp - fTapTime) < fTapTimeOUT) { 369 buttons = kLeftButton; 370 } 371 372 // if the movement stopped switch off the tap drag when timeout is expired 373 if ((movement->timestamp - fTapTime) > fTapTimeOUT) { 374 if (fTapdragStarted) 375 TRACE("TouchpadMovement: tap drag gesture timed out\n"); 376 fTapdragStarted = false; 377 fValidEdgeMotion = false; 378 } 379 380 if (abs(fTapDeltaX) > 15 || abs(fTapDeltaY) > 15) { 381 fTapStarted = false; 382 fTapClicks = 0; 383 } 384 385 if (fTapStarted || fDoubleClick) { 386 TRACE("TouchpadMovement: tap gesture\n"); 387 fTapClicks++; 388 389 if (fTapClicks > 1) { 390 TRACE("TouchpadMovement: empty click\n"); 391 buttons = kNoButton; 392 fTapClicks = 0; 393 fDoubleClick = true; 394 } else { 395 buttons = kLeftButton; 396 fTapStarted = false; 397 fTapdragStarted = true; 398 fDoubleClick = false; 399 } 400 } 401 402 movement->buttons = buttons; 403 _UpdateButtons(movement); 404 } 405 406 407 void 408 TouchpadMovement::_MoveToMovement(const touchpad_movement *event, mouse_movement *movement) 409 { 410 bool isStartOfMovement = false; 411 float pressure = 0; 412 413 TRACE("TouchpadMovement: movement event\n"); 414 if (!fMovementStarted) { 415 isStartOfMovement = true; 416 fMovementStarted = true; 417 StartNewMovment(); 418 } 419 420 GetMovement(event->xPosition, event->yPosition); 421 422 movement->xdelta = make_small(xDelta); 423 movement->ydelta = make_small(yDelta); 424 425 // tap gesture 426 fTapDeltaX += make_small(xDelta); 427 fTapDeltaY += make_small(yDelta); 428 429 if (fTapdragStarted) { 430 movement->buttons = kLeftButton; 431 movement->clicks = 0; 432 433 fValidEdgeMotion = _EdgeMotion(event, movement, fValidEdgeMotion); 434 TRACE("TouchpadMovement: tap drag\n"); 435 } else { 436 TRACE("TouchpadMovement: movement set buttons\n"); 437 movement->buttons = event->buttons; 438 } 439 440 // use only a fraction of pressure range, the max pressure seems to be 441 // to high 442 pressure = 20 * (event->zPressure - fSpecs.minPressure) 443 / (fSpecs.realMaxPressure - fSpecs.minPressure); 444 if (!fTapStarted 445 && isStartOfMovement 446 && fSettings.tapgesture_sensibility > 0. 447 && fSettings.tapgesture_sensibility > (20 - pressure)) { 448 TRACE("TouchpadMovement: tap started\n"); 449 fTapStarted = true; 450 fTapTime = system_time(); 451 fTapDeltaX = 0; 452 fTapDeltaY = 0; 453 } 454 455 _UpdateButtons(movement); 456 } 457 458 459 /*! Checks if this is a scrolling event or not, and also actually does the 460 scrolling work if it is. 461 462 \return \c true if this was a scrolling event, \c false if not. 463 */ 464 bool 465 TouchpadMovement::_CheckScrollingToMovement(const touchpad_movement *event, 466 mouse_movement *movement) 467 { 468 bool isSideScrollingV = false; 469 bool isSideScrollingH = false; 470 471 // if a button is pressed don't allow to scroll, we likely be in a drag 472 // action 473 if (fButtonsState != 0) 474 return false; 475 476 if ((fSpecs.areaEndX - fAreaWidth * fSettings.scroll_rightrange 477 < event->xPosition && !fMovementStarted 478 && fSettings.scroll_rightrange > 0.000001) 479 || fSettings.scroll_rightrange > 0.999999) { 480 isSideScrollingV = true; 481 } 482 if ((fSpecs.areaStartY + fAreaHeight * fSettings.scroll_bottomrange 483 > event->yPosition && !fMovementStarted 484 && fSettings.scroll_bottomrange > 0.000001) 485 || fSettings.scroll_bottomrange > 0.999999) { 486 isSideScrollingH = true; 487 } 488 if ((event->fingerWidth == 0 || event->fingerWidth == 1) 489 && fSettings.scroll_twofinger) { 490 // two finger scrolling is enabled 491 isSideScrollingV = true; 492 isSideScrollingH = fSettings.scroll_twofinger_horizontal; 493 } 494 495 if (!isSideScrollingV && !isSideScrollingH) { 496 fScrollingStarted = false; 497 return false; 498 } 499 500 TRACE("TouchpadMovement: scroll event\n"); 501 502 fTapStarted = false; 503 fTapClicks = 0; 504 fTapdragStarted = false; 505 fValidEdgeMotion = false; 506 if (!fScrollingStarted) { 507 fScrollingStarted = true; 508 StartNewMovment(); 509 } 510 GetScrolling(event->xPosition, event->yPosition); 511 movement->wheel_ydelta = make_small(yDelta); 512 movement->wheel_xdelta = make_small(xDelta); 513 514 if (isSideScrollingV && !isSideScrollingH) 515 movement->wheel_xdelta = 0; 516 else if (isSideScrollingH && !isSideScrollingV) 517 movement->wheel_ydelta = 0; 518 519 fButtonsState = movement->buttons; 520 521 return true; 522 } 523