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