vfs: check userland buffers before reading them.
[haiku.git] / src / add-ons / kernel / bus_managers / ps2 / movement_maker.cpp
blobce5fc0bd66d606ba76583d505626b825ccee2fc8
1 #include "movement_maker.h"
3 #include <stdlib.h>
5 #include <KernelExport.h>
8 #if 1
9 # define INFO(x...) dprintf(x)
10 #else
11 # define INFO(x...)
12 #endif
14 //#define TRACE_MOVEMENT_MAKER
15 #ifdef TRACE_MOVEMENT_MAKER
16 # define TRACE(x...) dprintf(x)
17 #else
18 # define TRACE(x...)
19 #endif
22 typedef union {
23 float value;
24 /* FIXME: Assumes 32 bit int. */
25 unsigned int word;
26 } ieee_float_shape_type;
28 /* Get a 32 bit int from a float. */
30 #define GET_FLOAT_WORD(i,d) \
31 do { \
32 ieee_float_shape_type gf_u; \
33 gf_u.value = (d); \
34 (i) = gf_u.word; \
35 } while (0)
37 /* Set a float from a 32 bit int. */
39 #define SET_FLOAT_WORD(d,i) \
40 do { \
41 ieee_float_shape_type sf_u; \
42 sf_u.word = (i); \
43 (d) = sf_u.value; \
44 } while (0)
46 static const float huge = 1.0e30;
48 float
49 floorf(float x)
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;}
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);
70 } else {
71 if (j0==0x80) return x+x; /* inf or NaN */
72 else return x; /* x is integral */
74 SET_FLOAT_WORD(x,i0);
75 return x;
79 float
80 ceilf(float x)
82 int32 i0,j0;
83 uint32 i;
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;}
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);
101 } else {
102 if (j0==0x80) return x+x; /* inf or NaN */
103 else return x; /* x is integral */
105 SET_FLOAT_WORD(x,i0);
106 return x;
109 static const float one = 1.0, tiny=1.0e-30;
111 float
112 sqrtf(float x)
114 float z;
115 int32 sign = (int)0x80000000;
116 int32 ix,s,q,m,t,i;
117 uint32 r;
119 GET_FLOAT_WORD(ix,x);
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 */
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 */
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;
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] */
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 */
149 while(r!=0) {
150 t = s+r;
151 if (t<=ix) {
152 s = t+r;
153 ix -= t;
154 q += r;
156 ix += ix;
157 r>>=1;
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);
171 ix = (q>>1)+0x3f000000;
172 ix += (m <<23);
173 SET_FLOAT_WORD(z,ix);
174 return z;
178 static int32
179 make_small(float value)
181 if (value > 0)
182 return (int32)floorf(value);
183 else
184 return (int32)ceilf(value);
189 void
190 MovementMaker::SetSettings(touchpad_settings* settings)
192 fSettings = settings;
196 void
197 MovementMaker::SetSpecs(hardware_specs* specs)
199 fSpecs = specs;
201 fAreaWidth = fSpecs->areaEndX - fSpecs->areaStartX;
202 fAreaHeight = fSpecs->areaEndY - fSpecs->areaStartY;
204 // calibrated on the synaptics touchpad
205 fSpeed = SYN_WIDTH / fAreaWidth;
206 fSmallMovement = 3 / fSpeed;
210 void
211 MovementMaker::StartNewMovment()
213 if (fSettings->scroll_xstepsize <= 0)
214 fSettings->scroll_xstepsize = 1;
215 if (fSettings->scroll_ystepsize <= 0)
216 fSettings->scroll_ystepsize = 1;
218 fMovementMakerStarted = true;
219 scrolling_x = 0;
220 scrolling_y = 0;
224 void
225 MovementMaker::GetMovement(uint32 posX, uint32 posY)
227 _GetRawMovement(posX, posY);
229 // INFO("SYN: pos: %lu x %lu, delta: %ld x %ld, sums: %ld x %ld\n",
230 // posX, posY, xDelta, yDelta,
231 // fDeltaSumX, fDeltaSumY);
233 xDelta = xDelta;
234 yDelta = yDelta;
238 void
239 MovementMaker::GetScrolling(uint32 posX, uint32 posY)
241 int32 stepsX = 0, stepsY = 0;
243 _GetRawMovement(posX, posY);
244 _ComputeAcceleration(fSettings->scroll_acceleration);
246 if (fSettings->scroll_xstepsize > 0) {
247 scrolling_x += xDelta;
249 stepsX = make_small(scrolling_x / fSettings->scroll_xstepsize);
251 scrolling_x -= stepsX * fSettings->scroll_xstepsize;
252 xDelta = stepsX;
253 } else {
254 scrolling_x = 0;
255 xDelta = 0;
257 if (fSettings->scroll_ystepsize > 0) {
258 scrolling_y += yDelta;
260 stepsY = make_small(scrolling_y / fSettings->scroll_ystepsize);
262 scrolling_y -= stepsY * fSettings->scroll_ystepsize;
263 yDelta = -1 * stepsY;
264 } else {
265 scrolling_y = 0;
266 yDelta = 0;
271 void
272 MovementMaker::_GetRawMovement(uint32 posX, uint32 posY)
274 // calibrated on the synaptics touchpad
275 posX = posX * SYN_WIDTH / fAreaWidth;
276 posY = posY * SYN_HEIGHT / fAreaHeight;
278 const float acceleration = 0.8;
279 const float translation = 12.0;
281 int diff;
283 if (fMovementMakerStarted) {
284 fMovementMakerStarted = false;
285 // init delta tracking
286 fPreviousX = posX;
287 fPreviousY = posY;
288 // deltas are automatically reset
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;
298 if (diff == 0)
299 fDeltaSumX = 0;
300 else
301 fDeltaSumX += diff;
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;
309 if (diff == 0)
310 fDeltaSumY = 0;
311 else
312 fDeltaSumY += diff;
314 fPreviousX = posX;
315 fPreviousY = posY;
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;
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;
337 xDelta = make_small(xDelta);
338 yDelta = make_small(yDelta);
343 void
344 MovementMaker::_ComputeAcceleration(int8 accel_factor)
346 // acceleration
347 float acceleration = 1;
348 if (accel_factor != 0) {
349 acceleration = 1 + sqrtf(xDelta * xDelta
350 + yDelta * yDelta) * accel_factor / 50.0;
353 xDelta = make_small(xDelta * acceleration);
354 yDelta = make_small(yDelta * acceleration);
358 // #pragma mark -
361 #define fTapTimeOUT 200000
364 void
365 TouchpadMovement::Init()
367 fMovementStarted = false;
368 fScrollingStarted = false;
369 fTapStarted = false;
370 fValidEdgeMotion = false;
371 fDoubleClick = false;
375 status_t
376 TouchpadMovement::EventToMovement(touch_event *event, mouse_movement *movement)
378 if (!movement)
379 return B_ERROR;
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();
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;
399 if (event->buttons & kLeftButton) {
400 fTapClicks = 0;
401 fTapdragStarted = false;
402 fTapStarted = false;
403 fValidEdgeMotion = false;
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);
417 return B_OK;
421 // in pixel per second
422 const int32 kEdgeMotionSpeed = 200;
425 bool
426 TouchpadMovement::_EdgeMotion(mouse_movement *movement, touch_event *event,
427 bool validStart)
429 float xdelta = 0;
430 float ydelta = 0;
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;
442 bool inXEdge = false;
443 bool inYEdge = false;
445 if (int32(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;
453 if (int32(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;
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 = make_small(xdelta);
465 if (inYEdge && validStart)
466 movement->ydelta = make_small(ydelta);
468 if (!inXEdge && !inYEdge)
469 fLastEdgeMotion = 0;
470 else
471 fLastEdgeMotion = time;
473 if ((inXEdge || inYEdge) && !validStart)
474 return false;
476 return true;
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.
485 void
486 TouchpadMovement::UpdateButtons(mouse_movement *movement)
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;
495 fClickLastTime = movement->timestamp;
498 if (movement->buttons != 0)
499 movement->clicks = fClickCount;
501 fButtonsState = movement->buttons;
505 void
506 TouchpadMovement::_NoTouchToMovement(touch_event *event,
507 mouse_movement *movement)
509 uint32 buttons = event->buttons;
511 TRACE("TouchpadMovement: no touch event\n");
513 fScrollingStarted = false;
514 fMovementStarted = false;
515 fLastEdgeMotion = 0;
517 if (fTapdragStarted
518 && (movement->timestamp - fTapTime) < fTapTimeOUT) {
519 buttons = kLeftButton;
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");
529 if (abs(fTapDeltaX) > 15 || abs(fTapDeltaY) > 15) {
530 fTapStarted = false;
531 fTapClicks = 0;
534 if (fTapStarted || fDoubleClick) {
535 TRACE("TouchpadMovement: tap gesture\n");
536 fTapClicks++;
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;
551 movement->buttons = buttons;
552 UpdateButtons(movement);
556 void
557 TouchpadMovement::_MoveToMovement(touch_event *event, mouse_movement *movement)
559 bool isStartOfMovement = false;
560 float pressure = 0;
562 TRACE("TouchpadMovement: movement event\n");
563 if (!fMovementStarted) {
564 isStartOfMovement = true;
565 fMovementStarted = true;
566 StartNewMovment();
569 GetMovement(event->xPosition, event->yPosition);
571 movement->xdelta = make_small(xDelta);
572 movement->ydelta = make_small(yDelta);
574 // tap gesture
575 fTapDeltaX += make_small(xDelta);
576 fTapDeltaY += make_small(yDelta);
578 if (fTapdragStarted) {
579 movement->buttons = kLeftButton;
580 movement->clicks = 0;
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;
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;
604 UpdateButtons(movement);
608 /*! Checks if this is a scrolling event or not, and also actually does the
609 scrolling work if it is.
611 \return \c true if this was a scrolling event, \c false if not.
613 bool
614 TouchpadMovement::_CheckScrollingToMovement(touch_event *event,
615 mouse_movement *movement)
617 bool isSideScrollingV = false;
618 bool isSideScrollingH = false;
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;
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;
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;
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;
644 if (!isSideScrollingV && !isSideScrollingH) {
645 fScrollingStarted = false;
646 return false;
649 TRACE("TouchpadMovement: scroll event\n");
651 fTapStarted = false;
652 fTapClicks = 0;
653 fTapdragStarted = false;
654 fValidEdgeMotion = false;
655 if (!fScrollingStarted) {
656 fScrollingStarted = true;
657 StartNewMovment();
659 GetScrolling(event->xPosition, event->yPosition);
660 movement->wheel_ydelta = make_small(yDelta);
661 movement->wheel_xdelta = make_small(xDelta);
663 if (isSideScrollingV && !isSideScrollingH)
664 movement->wheel_xdelta = 0;
665 else if (isSideScrollingH && !isSideScrollingV)
666 movement->wheel_ydelta = 0;
668 fButtonsState = movement->buttons;
670 return true;