xref: /openbsd-src/sys/dev/wscons/wstpad.c (revision 93ab69426ace0f2c718831e73925aa538b22c5b4)
1*93ab6942Smvs /* $OpenBSD: wstpad.c,v 1.34 2024/03/25 13:01:49 mvs Exp $ */
231a33b6eSbru 
39ac761feSbru /*
49ac761feSbru  * Copyright (c) 2015, 2016 Ulf Brosziewski
59ac761feSbru  *
69ac761feSbru  * Permission to use, copy, modify, and distribute this software for any
79ac761feSbru  * purpose with or without fee is hereby granted, provided that the above
89ac761feSbru  * copyright notice and this permission notice appear in all copies.
99ac761feSbru  *
109ac761feSbru  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
119ac761feSbru  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
129ac761feSbru  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
139ac761feSbru  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
149ac761feSbru  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
159ac761feSbru  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
169ac761feSbru  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
179ac761feSbru  */
189ac761feSbru 
199ac761feSbru /*
209ac761feSbru  * touchpad input processing
219ac761feSbru  */
229ac761feSbru 
239ac761feSbru #include <sys/param.h>
249ac761feSbru #include <sys/kernel.h>
259ac761feSbru #include <sys/malloc.h>
269ac761feSbru #include <sys/proc.h>
279ac761feSbru #include <sys/systm.h>
2831a33b6eSbru #include <sys/signalvar.h>
2931a33b6eSbru #include <sys/timeout.h>
309ac761feSbru 
319ac761feSbru #include <dev/wscons/wsconsio.h>
329ac761feSbru #include <dev/wscons/wsmousevar.h>
339ac761feSbru #include <dev/wscons/wseventvar.h>
349ac761feSbru #include <dev/wscons/wsmouseinput.h>
359ac761feSbru 
36b1ba6288Sbru #define BTNMASK(n)		((n) > 0 && (n) <= 32 ? 1 << ((n) - 1) : 0)
37b1ba6288Sbru 
38b1ba6288Sbru #define LEFTBTN			BTNMASK(1)
39b1ba6288Sbru #define MIDDLEBTN		BTNMASK(2)
40b1ba6288Sbru #define RIGHTBTN		BTNMASK(3)
419ac761feSbru 
429ac761feSbru #define PRIMARYBTN LEFTBTN
439ac761feSbru 
449ac761feSbru #define PRIMARYBTN_CLICKED(tp) ((tp)->btns_sync & PRIMARYBTN & (tp)->btns)
459ac761feSbru #define PRIMARYBTN_RELEASED(tp) ((tp)->btns_sync & PRIMARYBTN & ~(tp)->btns)
469ac761feSbru 
479ac761feSbru #define IS_MT(tp) ((tp)->features & WSTPAD_MT)
489ac761feSbru #define DISABLE(tp) ((tp)->features & WSTPAD_DISABLE)
499ac761feSbru 
508de0db29Sbru /*
518de0db29Sbru  * Ratios to the height or width of the touchpad surface, in
528de0db29Sbru  * [*.12] fixed-point format:
538de0db29Sbru  */
548de0db29Sbru #define V_EDGE_RATIO_DEFAULT	205
558de0db29Sbru #define B_EDGE_RATIO_DEFAULT	410
568de0db29Sbru #define T_EDGE_RATIO_DEFAULT	512
578de0db29Sbru #define CENTER_RATIO_DEFAULT	512
589ac761feSbru 
5931a33b6eSbru #define TAP_MAXTIME_DEFAULT	180
6031a33b6eSbru #define TAP_CLICKTIME_DEFAULT	180
6131a33b6eSbru #define TAP_LOCKTIME_DEFAULT	0
62b1ba6288Sbru #define TAP_BTNMAP_SIZE		3
6331a33b6eSbru 
6431a33b6eSbru #define CLICKDELAY_MS		20
659ac761feSbru #define FREEZE_MS		100
66d8696265Sbru #define MATCHINTERVAL_MS	45
67d8696265Sbru #define STOPINTERVAL_MS		55
689ac761feSbru 
69c06ae661Sbru #define MAG_LOW			(10 << 12)
70c06ae661Sbru #define MAG_MEDIUM		(18 << 12)
71c06ae661Sbru 
729ac761feSbru enum tpad_handlers {
739ac761feSbru 	SOFTBUTTON_HDLR,
749ac761feSbru 	TOPBUTTON_HDLR,
7531a33b6eSbru 	TAP_HDLR,
769ac761feSbru 	F2SCROLL_HDLR,
779ac761feSbru 	EDGESCROLL_HDLR,
789ac761feSbru 	CLICK_HDLR,
799ac761feSbru };
809ac761feSbru 
8131a33b6eSbru enum tap_state {
8231a33b6eSbru 	TAP_DETECT,
8331a33b6eSbru 	TAP_IGNORE,
8431a33b6eSbru 	TAP_LIFTED,
8531a33b6eSbru 	TAP_LOCKED,
8615aff9aaSbru 	TAP_LOCKED_DRAG,
8731a33b6eSbru };
8831a33b6eSbru 
899ac761feSbru enum tpad_cmd {
909ac761feSbru 	CLEAR_MOTION_DELTAS,
919ac761feSbru 	SOFTBUTTON_DOWN,
929ac761feSbru 	SOFTBUTTON_UP,
9315aff9aaSbru 	TAPBUTTON_SYNC,
9431a33b6eSbru 	TAPBUTTON_DOWN,
9531a33b6eSbru 	TAPBUTTON_UP,
969ac761feSbru 	VSCROLL,
979ac761feSbru 	HSCROLL,
989ac761feSbru };
999ac761feSbru 
1009ac761feSbru /*
1019ac761feSbru  * tpad_touch.flags:
1029ac761feSbru  */
1039ac761feSbru #define L_EDGE			(1 << 0)
1049ac761feSbru #define R_EDGE			(1 << 1)
1059ac761feSbru #define T_EDGE			(1 << 2)
1069ac761feSbru #define B_EDGE			(1 << 3)
107c06ae661Sbru #define THUMB 			(1 << 4)
1089ac761feSbru 
1099ac761feSbru #define EDGES (L_EDGE | R_EDGE | T_EDGE | B_EDGE)
1109ac761feSbru 
1111f56cd93Sbru /*
1121f56cd93Sbru  * A touch is "centered" if it does not start and remain at the top
1131f56cd93Sbru  * edge or one of the vertical edges.  Two-finger scrolling and tapping
1141f56cd93Sbru  * require that at least one touch is centered.
1151f56cd93Sbru  */
1161f56cd93Sbru #define CENTERED(t) (((t)->flags & (L_EDGE | R_EDGE | T_EDGE)) == 0)
1171f56cd93Sbru 
1189ac761feSbru enum touchstates {
1199ac761feSbru 	TOUCH_NONE,
1209ac761feSbru 	TOUCH_BEGIN,
1219ac761feSbru 	TOUCH_UPDATE,
1229ac761feSbru 	TOUCH_END,
1239ac761feSbru };
1249ac761feSbru 
1259ac761feSbru struct tpad_touch {
1269ac761feSbru 	u_int flags;
1279ac761feSbru 	enum touchstates state;
1289ac761feSbru 	int x;
1299ac761feSbru 	int y;
1309ac761feSbru 	int dir;
131d8696265Sbru 	struct timespec start;
132d8696265Sbru 	struct timespec match;
133c06ae661Sbru 	struct position *pos;
1349ac761feSbru 	struct {
1359ac761feSbru 		int x;
1369ac761feSbru 		int y;
1379ac761feSbru 		struct timespec time;
1389ac761feSbru 	} orig;
1399ac761feSbru };
1409ac761feSbru 
1419ac761feSbru /*
1429ac761feSbru  * wstpad.features
1439ac761feSbru  */
14457251937Smpi #define WSTPAD_SOFTBUTTONS	(1 << 0)
14557251937Smpi #define WSTPAD_SOFTMBTN		(1 << 1)
14657251937Smpi #define WSTPAD_TOPBUTTONS	(1 << 2)
14757251937Smpi #define WSTPAD_TWOFINGERSCROLL	(1 << 3)
14857251937Smpi #define WSTPAD_EDGESCROLL	(1 << 4)
14957251937Smpi #define WSTPAD_HORIZSCROLL	(1 << 5)
15057251937Smpi #define WSTPAD_SWAPSIDES	(1 << 6)
15157251937Smpi #define WSTPAD_DISABLE		(1 << 7)
1529310c18aSbru #define WSTPAD_MTBUTTONS	(1 << 8)
1539ac761feSbru 
154dcec61dcSmiod #define WSTPAD_MT		(1U << 31)
1559ac761feSbru 
1569ac761feSbru 
1579ac761feSbru struct wstpad {
1589ac761feSbru 	u_int features;
1599ac761feSbru 	u_int handlers;
1609ac761feSbru 
1619ac761feSbru 	/*
1629ac761feSbru 	 * t always points into the tpad_touches array, which has at
1639ac761feSbru 	 * least one element. If there is more than one, t selects
1649ac761feSbru 	 * the pointer-controlling touch.
1659ac761feSbru 	 */
1669ac761feSbru 	struct tpad_touch *t;
1679ac761feSbru 	struct tpad_touch *tpad_touches;
1689ac761feSbru 
1699ac761feSbru 	u_int mtcycle;
1701f56cd93Sbru 	u_int ignore;
1719ac761feSbru 
1729ac761feSbru 	int contacts;
1739ac761feSbru 	int prev_contacts;
1749ac761feSbru 	u_int btns;
1759ac761feSbru 	u_int btns_sync;
1769ac761feSbru 	int ratio;
1779ac761feSbru 
1789ac761feSbru 	struct timespec time;
1799ac761feSbru 
1809ac761feSbru 	u_int freeze;
1819ac761feSbru 	struct timespec freeze_ts;
1829ac761feSbru 
1839ac761feSbru 	/* edge coordinates */
1849ac761feSbru 	struct {
1859ac761feSbru 		int left;
1869ac761feSbru 		int right;
1879ac761feSbru 		int top;
1889ac761feSbru 		int bottom;
1899ac761feSbru 		int center;
1909ac761feSbru 		int center_left;
1919ac761feSbru 		int center_right;
192c06ae661Sbru 		int low;
1939ac761feSbru 	} edge;
1949ac761feSbru 
1959ac761feSbru 	struct {
1969ac761feSbru 		/* ratios to the surface width or height */
1979ac761feSbru 		int left_edge;
1989ac761feSbru 		int right_edge;
1999ac761feSbru 		int top_edge;
2009ac761feSbru 		int bottom_edge;
2019ac761feSbru 		int center_width;
2029ac761feSbru 		/* two-finger contacts */
2039ac761feSbru 		int f2pressure;
2049ac761feSbru 		int f2width;
2059310c18aSbru 		/* MTBUTTONS: distance limit for two-finger clicks */
2069310c18aSbru 		int mtbtn_maxdist;
2079ac761feSbru 	} params;
2089ac761feSbru 
2099ac761feSbru 	/* handler state and configuration: */
2109ac761feSbru 
2119ac761feSbru 	u_int softbutton;
2129ac761feSbru 	u_int sbtnswap;
2139ac761feSbru 
2149ac761feSbru 	struct {
21531a33b6eSbru 		enum tap_state state;
21631a33b6eSbru 		int contacts;
21715aff9aaSbru 		int valid;
21815aff9aaSbru 		u_int pending;
21931a33b6eSbru 		u_int button;
22015aff9aaSbru 		int masked;
22131a33b6eSbru 		int maxdist;
22231a33b6eSbru 		struct timeout to;
22331a33b6eSbru 		/* parameters: */
22431a33b6eSbru 		struct timespec maxtime;
22531a33b6eSbru 		int clicktime;
22631a33b6eSbru 		int locktime;
227b1ba6288Sbru 		u_int btnmap[TAP_BTNMAP_SIZE];
22831a33b6eSbru 	} tap;
22931a33b6eSbru 
23031a33b6eSbru 	struct {
2319ac761feSbru 		int dz;
2329ac761feSbru 		int dw;
2339ac761feSbru 		int hdist;
2349ac761feSbru 		int vdist;
23588c4a543Sbru 		int mag;
2369ac761feSbru 	} scroll;
2379ac761feSbru };
2389ac761feSbru 
239d8696265Sbru static const struct timespec match_interval =
240d8696265Sbru     { .tv_sec = 0, .tv_nsec = MATCHINTERVAL_MS * 1000000 };
241d8696265Sbru 
242d8696265Sbru static const struct timespec stop_interval =
243d8696265Sbru     { .tv_sec = 0, .tv_nsec = STOPINTERVAL_MS * 1000000 };
244d8696265Sbru 
2459ac761feSbru /*
2469ac761feSbru  * Coordinates in the wstpad struct are "normalized" device coordinates,
2479ac761feSbru  * the orientation is left-to-right and upward.
2489ac761feSbru  */
2491f4c8d79Smpi static inline int
normalize_abs(struct axis_filter * filter,int val)2509ac761feSbru normalize_abs(struct axis_filter *filter, int val)
2519ac761feSbru {
2529ac761feSbru 	return (filter->inv ? filter->inv - val : val);
2539ac761feSbru }
2549ac761feSbru 
2551f4c8d79Smpi static inline int
normalize_rel(struct axis_filter * filter,int val)2569ac761feSbru normalize_rel(struct axis_filter *filter, int val)
2579ac761feSbru {
2589ac761feSbru 	return (filter->inv ? -val : val);
2599ac761feSbru }
2609ac761feSbru 
2619ac761feSbru /*
2629ac761feSbru  * Directions of motion are represented by numbers in the range 0 - 11,
2639ac761feSbru  * corresponding to clockwise counted circle sectors:
2649ac761feSbru  *
2659ac761feSbru  *              11 | 0
2669ac761feSbru  *           10    |    1
2679ac761feSbru  *          9      |      2
2689ac761feSbru  *          -------+-------
2699ac761feSbru  *          8      |      3
2709ac761feSbru  *            7    |    4
2719ac761feSbru  *               6 | 5
2729ac761feSbru  *
2739ac761feSbru  */
2749ac761feSbru /* Tangent constants in [*.12] fixed-point format: */
2759ac761feSbru #define TAN_DEG_60 7094
2769ac761feSbru #define TAN_DEG_30 2365
2779ac761feSbru 
2789ac761feSbru #define NORTH(d) ((d) == 0 || (d) == 11)
2799ac761feSbru #define SOUTH(d) ((d) == 5 || (d) == 6)
2809ac761feSbru #define EAST(d) ((d) == 2 || (d) == 3)
2819ac761feSbru #define WEST(d) ((d) == 8 || (d) == 9)
2829ac761feSbru 
2831f4c8d79Smpi static inline int
direction(int dx,int dy,int ratio)2849ac761feSbru direction(int dx, int dy, int ratio)
2859ac761feSbru {
2869ac761feSbru 	int rdy, dir = -1;
2879ac761feSbru 
2889ac761feSbru 	if (dx || dy) {
2899ac761feSbru 		rdy = abs(dy) * ratio;
2909ac761feSbru 		if (abs(dx) * TAN_DEG_60 < rdy)
2919ac761feSbru 			dir = 0;
2929ac761feSbru 		else if (abs(dx) * TAN_DEG_30 < rdy)
2939ac761feSbru 			dir = 1;
2949ac761feSbru 		else
2959ac761feSbru 			dir = 2;
2969ac761feSbru 		if ((dx < 0) != (dy < 0))
2979ac761feSbru 			dir = 5 - dir;
2989ac761feSbru 		if (dx < 0)
2999ac761feSbru 			dir += 6;
3009ac761feSbru 	}
3019ac761feSbru 	return dir;
3029ac761feSbru }
3039ac761feSbru 
3041f4c8d79Smpi static inline int
dircmp(int dir1,int dir2)3059ac761feSbru dircmp(int dir1, int dir2)
3069ac761feSbru {
3079ac761feSbru 	int diff = abs(dir1 - dir2);
3089ac761feSbru 	return (diff <= 6 ? diff : 12 - diff);
3099ac761feSbru }
3109ac761feSbru 
311d8696265Sbru /*
312d8696265Sbru  * Update direction and timespec attributes for a touch.  They are used to
313d8696265Sbru  * determine whether it is moving - or resting - stably.
314d8696265Sbru  *
315d8696265Sbru  * The callers pass touches from the current frame and the touches that are
316d8696265Sbru  * no longer present in the update cycle to this function.  Even though this
317d8696265Sbru  * ensures that pairs of zero deltas do not result from stale coordinates,
318d8696265Sbru  * zero deltas do not reset the state immediately.  A short time span - the
319d8696265Sbru  * "stop interval" - must pass before the state is cleared, which is
320d8696265Sbru  * necessary because some touchpads report intermediate stops when a touch
321d8696265Sbru  * is moving very slowly.
322d8696265Sbru  */
3239ac761feSbru void
wstpad_set_direction(struct wstpad * tp,struct tpad_touch * t,int dx,int dy)3241be64207Sbru wstpad_set_direction(struct wstpad *tp, struct tpad_touch *t, int dx, int dy)
3259ac761feSbru {
3269ac761feSbru 	int dir;
327d8696265Sbru 	struct timespec ts;
3289ac761feSbru 
3299ac761feSbru 	if (t->state != TOUCH_UPDATE) {
3309ac761feSbru 		t->dir = -1;
331d8696265Sbru 		memcpy(&t->start, &tp->time, sizeof(struct timespec));
332d8696265Sbru 		return;
333d8696265Sbru 	}
334d8696265Sbru 
3351be64207Sbru 	dir = direction(dx, dy, tp->ratio);
3361be64207Sbru 	if (dir >= 0) {
337d8696265Sbru 		if (t->dir < 0 || dircmp(dir, t->dir) > 1) {
338d8696265Sbru 			memcpy(&t->start, &tp->time, sizeof(struct timespec));
339d8696265Sbru 		}
3409ac761feSbru 		t->dir = dir;
341d8696265Sbru 		memcpy(&t->match, &tp->time, sizeof(struct timespec));
3421be64207Sbru 	} else if (t->dir >= 0) {
343d8696265Sbru 		timespecsub(&tp->time, &t->match, &ts);
344d8696265Sbru 		if (timespeccmp(&ts, &stop_interval, >=)) {
3451be64207Sbru 			t->dir = -1;
346d8696265Sbru 			memcpy(&t->start, &t->match, sizeof(struct timespec));
3471be64207Sbru 		}
3481be64207Sbru 	}
3499ac761feSbru }
350d8696265Sbru 
351c06ae661Sbru /*
352c06ae661Sbru  * Make a rough, but quick estimation of the speed of a touch.  Its
353c06ae661Sbru  * distance to the previous position is scaled by factors derived
354c06ae661Sbru  * from the average update rate and the deceleration parameter
355c06ae661Sbru  * (filter.dclr).  The unit of the result is:
356c06ae661Sbru  *         (filter.dclr / 100) device units per millisecond
357c06ae661Sbru  *
358c06ae661Sbru  * Magnitudes are returned in [*.12] fixed-point format.  For purposes
359c06ae661Sbru  * of filtering, they are divided into medium and high speeds
360c06ae661Sbru  * (> MAG_MEDIUM), low speeds, and very low speeds (< MAG_LOW).
361c06ae661Sbru  *
362c06ae661Sbru  * The scale factors are not affected if deceleration is turned off.
363c06ae661Sbru  */
364c06ae661Sbru static inline int
magnitude(struct wsmouseinput * input,int dx,int dy)365c06ae661Sbru magnitude(struct wsmouseinput *input, int dx, int dy)
366c06ae661Sbru {
367c06ae661Sbru 	int h, v;
368c06ae661Sbru 
369c06ae661Sbru 	h = abs(dx) * input->filter.h.mag_scale;
370c06ae661Sbru 	v = abs(dy) * input->filter.v.mag_scale;
371c06ae661Sbru 	/* Return an "alpha-max-plus-beta-min" approximation: */
372c06ae661Sbru 	return (h >= v ? h + 3 * v / 8 : v + 3 * h / 8);
373c06ae661Sbru }
374c06ae661Sbru 
375c06ae661Sbru /*
376c06ae661Sbru  * Treat a touch as stable if it is moving at a medium or high speed,
377c06ae661Sbru  * if it is moving continuously, or if it has stopped for a certain
378c06ae661Sbru  * time span.
379c06ae661Sbru  */
380d8696265Sbru int
wstpad_is_stable(struct wsmouseinput * input,struct tpad_touch * t)381c06ae661Sbru wstpad_is_stable(struct wsmouseinput *input, struct tpad_touch *t)
382d8696265Sbru {
383d8696265Sbru 	struct timespec ts;
384d8696265Sbru 
385c06ae661Sbru 	if (t->dir >= 0) {
386c06ae661Sbru 		if (magnitude(input, t->pos->dx, t->pos->dy) > MAG_MEDIUM)
387c06ae661Sbru 			return (1);
388d8696265Sbru 		timespecsub(&t->match, &t->start, &ts);
389c06ae661Sbru 	} else {
390c06ae661Sbru 		timespecsub(&input->tp->time, &t->start, &ts);
391c06ae661Sbru 	}
392d8696265Sbru 
393d8696265Sbru 	return (timespeccmp(&ts, &match_interval, >=));
3949ac761feSbru }
3959ac761feSbru 
3969ac761feSbru /*
397757db27eSbru  * If a touch starts in an edge area, pointer movement will be
398757db27eSbru  * suppressed as long as it stays in that area.
3999ac761feSbru  */
4001f4c8d79Smpi static inline u_int
edge_flags(struct wstpad * tp,int x,int y)4019ac761feSbru edge_flags(struct wstpad *tp, int x, int y)
4029ac761feSbru {
4039ac761feSbru 	u_int flags = 0;
4049ac761feSbru 
4059ac761feSbru 	if (x < tp->edge.left)
4069ac761feSbru 		flags |= L_EDGE;
4079ac761feSbru 	else if (x >= tp->edge.right)
4089ac761feSbru 		flags |= R_EDGE;
4099ac761feSbru 	if (y < tp->edge.bottom)
4109ac761feSbru 		flags |= B_EDGE;
4119ac761feSbru 	else if (y >= tp->edge.top)
4129ac761feSbru 		flags |= T_EDGE;
4139ac761feSbru 
4149ac761feSbru 	return (flags);
4159ac761feSbru }
4169ac761feSbru 
4171f4c8d79Smpi static inline struct tpad_touch *
get_2nd_touch(struct wsmouseinput * input)4189ac761feSbru get_2nd_touch(struct wsmouseinput *input)
4199ac761feSbru {
4209ac761feSbru 	struct wstpad *tp = input->tp;
4219ac761feSbru 	int slot;
4229ac761feSbru 
4239ac761feSbru 	if (IS_MT(tp)) {
4241f56cd93Sbru 		slot = ffs(input->mt.touches & ~(input->mt.ptr | tp->ignore));
4259ac761feSbru 		if (slot)
4269ac761feSbru 			return &tp->tpad_touches[--slot];
4279ac761feSbru 	}
4289ac761feSbru 	return NULL;
4299ac761feSbru }
4309ac761feSbru 
4319ac761feSbru /* Suppress pointer motion for a short period of time. */
4321f4c8d79Smpi static inline void
set_freeze_ts(struct wstpad * tp,int sec,int ms)4339ac761feSbru set_freeze_ts(struct wstpad *tp, int sec, int ms)
4349ac761feSbru {
4359ac761feSbru 	tp->freeze_ts.tv_sec = sec;
4369ac761feSbru 	tp->freeze_ts.tv_nsec = ms * 1000000;
4379ac761feSbru 	timespecadd(&tp->time, &tp->freeze_ts, &tp->freeze_ts);
4389ac761feSbru }
4399ac761feSbru 
4409ac761feSbru 
4411be64207Sbru /* Return TRUE if two-finger- or edge-scrolling would be valid. */
44288c4a543Sbru int
wstpad_scroll_coords(struct wsmouseinput * input,int * dx,int * dy)44388c4a543Sbru wstpad_scroll_coords(struct wsmouseinput *input, int *dx, int *dy)
4449ac761feSbru {
445c06ae661Sbru 	struct wstpad *tp = input->tp;
446c06ae661Sbru 
4479ac761feSbru 	if (tp->contacts != tp->prev_contacts || tp->btns || tp->btns_sync) {
448584b8e02Sbru 		tp->scroll.dz = 0;
449584b8e02Sbru 		tp->scroll.dw = 0;
4509ac761feSbru 		return (0);
4519ac761feSbru 	}
45288c4a543Sbru 	if ((input->motion.sync & SYNC_POSITION) == 0)
45388c4a543Sbru 		return (0);
4541be64207Sbru 	/*
455d8696265Sbru 	 * Try to exclude accidental scroll events by checking whether the
456d8696265Sbru 	 * pointer-controlling touch is stable.  The check, which may cause
457d8696265Sbru 	 * a short delay, is only applied initially, a touch that stops and
458d8696265Sbru 	 * resumes scrolling is not affected.
4591be64207Sbru 	 */
46088c4a543Sbru 	if (tp->scroll.dz || tp->scroll.dw || wstpad_is_stable(input, tp->t)) {
46188c4a543Sbru 		*dx = normalize_rel(&input->filter.h, input->motion.pos.dx);
46288c4a543Sbru 		*dy = normalize_rel(&input->filter.v, input->motion.pos.dy);
46388c4a543Sbru 		return (*dx || *dy);
46488c4a543Sbru 	}
465c06ae661Sbru 
466c06ae661Sbru 	return (0);
4679ac761feSbru }
4689ac761feSbru 
4699ac761feSbru void
wstpad_scroll(struct wstpad * tp,int dx,int dy,int mag,u_int * cmds)47088c4a543Sbru wstpad_scroll(struct wstpad *tp, int dx, int dy, int mag, u_int *cmds)
4719ac761feSbru {
47288c4a543Sbru 	int dz, dw, n = 1;
4739ac761feSbru 
47488c4a543Sbru 	/*
47588c4a543Sbru 	 * The function applies strong deceleration, but only to input with
47688c4a543Sbru 	 * very low speeds.  A higher threshold might make applications
47788c4a543Sbru 	 * without support for precision scrolling appear unresponsive.
47888c4a543Sbru 	 */
47988c4a543Sbru 	mag = tp->scroll.mag = imin(MAG_MEDIUM,
48088c4a543Sbru 	    (mag + 3 * tp->scroll.mag) / 4);
48188c4a543Sbru 	if (mag < MAG_LOW)
48288c4a543Sbru 		n = (MAG_LOW - mag) / 4096 + 1;
483584b8e02Sbru 
48488c4a543Sbru 	if (dy && tp->scroll.vdist) {
48588c4a543Sbru 		if (tp->scroll.dw) {
48688c4a543Sbru 			/*
48788c4a543Sbru 			 * Before switching the axis, wstpad_scroll_coords()
48888c4a543Sbru 			 * should check again whether the movement is stable.
48988c4a543Sbru 			 */
49088c4a543Sbru 			tp->scroll.dw = 0;
49188c4a543Sbru 			return;
492584b8e02Sbru 		}
49388c4a543Sbru 		dz = -dy * 4096 / (tp->scroll.vdist * n);
49488c4a543Sbru 		if (tp->scroll.dz) {
49513a73e99Sbru 			if ((dy < 0) != (tp->scroll.dz > 0))
49688c4a543Sbru 				tp->scroll.dz = -tp->scroll.dz;
49788c4a543Sbru 			dz = (dz + 3 * tp->scroll.dz) / 4;
49888c4a543Sbru 		}
49988c4a543Sbru 		if (dz) {
50088c4a543Sbru 			tp->scroll.dz = dz;
5019ac761feSbru 			*cmds |= 1 << VSCROLL;
5029ac761feSbru 		}
50388c4a543Sbru 
50488c4a543Sbru 	} else if (dx && tp->scroll.hdist) {
50588c4a543Sbru 		if (tp->scroll.dz) {
50688c4a543Sbru 			tp->scroll.dz = 0;
50788c4a543Sbru 			return;
508584b8e02Sbru 		}
50988c4a543Sbru 		dw = dx * 4096 / (tp->scroll.hdist * n);
51013a73e99Sbru 		if (tp->scroll.dw) {
51113a73e99Sbru 			if ((dx > 0) != (tp->scroll.dw > 0))
51213a73e99Sbru 				tp->scroll.dw = -tp->scroll.dw;
51388c4a543Sbru 			dw = (dw + 3 * tp->scroll.dw) / 4;
51413a73e99Sbru 		}
51588c4a543Sbru 		if (dw) {
51688c4a543Sbru 			tp->scroll.dw = dw;
5179ac761feSbru 			*cmds |= 1 << HSCROLL;
5189ac761feSbru 		}
5199ac761feSbru 	}
5209ac761feSbru }
5219ac761feSbru 
5229ac761feSbru void
wstpad_f2scroll(struct wsmouseinput * input,u_int * cmds)5239ac761feSbru wstpad_f2scroll(struct wsmouseinput *input, u_int *cmds)
5249ac761feSbru {
5259ac761feSbru 	struct wstpad *tp = input->tp;
5269ac761feSbru 	struct tpad_touch *t2;
5271f56cd93Sbru 	int dir, dx, dy, centered;
5289ac761feSbru 
5291f56cd93Sbru 	if (tp->ignore == 0) {
5301f56cd93Sbru 		if (tp->contacts != 2)
5311f56cd93Sbru 			return;
5321f56cd93Sbru 	} else if (tp->contacts != 3 || (tp->ignore == input->mt.ptr)) {
5331f56cd93Sbru 		return;
5341f56cd93Sbru 	}
5351f56cd93Sbru 
53688c4a543Sbru 	if (!wstpad_scroll_coords(input, &dx, &dy))
5379ac761feSbru 		return;
5389ac761feSbru 
5399ac761feSbru 	dir = tp->t->dir;
54088c4a543Sbru 	if (!(NORTH(dir) || SOUTH(dir)))
54188c4a543Sbru 		dy = 0;
54288c4a543Sbru 	if (!(EAST(dir) || WEST(dir)))
54388c4a543Sbru 		dx = 0;
5449ac761feSbru 
545584b8e02Sbru 	if (dx || dy) {
5461f56cd93Sbru 		centered = CENTERED(tp->t);
547584b8e02Sbru 		if (IS_MT(tp)) {
5489ac761feSbru 			t2 = get_2nd_touch(input);
549584b8e02Sbru 			if (t2 == NULL)
5509ac761feSbru 				return;
5519ac761feSbru 			dir = t2->dir;
5529ac761feSbru 			if ((dy > 0 && !NORTH(dir)) || (dy < 0 && !SOUTH(dir)))
5539ac761feSbru 				return;
5549ac761feSbru 			if ((dx > 0 && !EAST(dir)) || (dx < 0 && !WEST(dir)))
5559ac761feSbru 				return;
556c06ae661Sbru 			if (!wstpad_is_stable(input, t2) &&
5571be64207Sbru 			    !(tp->scroll.dz || tp->scroll.dw))
5581be4be95Sbru 				return;
5591f56cd93Sbru 			centered |= CENTERED(t2);
5609ac761feSbru 		}
5611f56cd93Sbru 		if (centered) {
56288c4a543Sbru 			wstpad_scroll(tp, dx, dy,
56388c4a543Sbru 			    magnitude(input, dx, dy), cmds);
5649ac761feSbru 			set_freeze_ts(tp, 0, FREEZE_MS);
5659ac761feSbru 		}
566584b8e02Sbru 	}
5671f56cd93Sbru }
5689ac761feSbru 
5699ac761feSbru void
wstpad_edgescroll(struct wsmouseinput * input,u_int * cmds)5709ac761feSbru wstpad_edgescroll(struct wsmouseinput *input, u_int *cmds)
5719ac761feSbru {
5729ac761feSbru 	struct wstpad *tp = input->tp;
5739ac761feSbru 	struct tpad_touch *t = tp->t;
5741ad0085eSbru 	u_int v_edge, b_edge;
5751ad0085eSbru 	int dx, dy;
5769ac761feSbru 
57788c4a543Sbru 	if (!wstpad_scroll_coords(input, &dx, &dy) || tp->contacts != 1)
5789ac761feSbru 		return;
5799ac761feSbru 
5801ad0085eSbru 	v_edge = (tp->features & WSTPAD_SWAPSIDES) ? L_EDGE : R_EDGE;
5811ad0085eSbru 	b_edge = (tp->features & WSTPAD_HORIZSCROLL) ? B_EDGE : 0;
5821ad0085eSbru 
58388c4a543Sbru 	if ((t->flags & v_edge) == 0)
58488c4a543Sbru 		dy = 0;
58588c4a543Sbru 	if ((t->flags & b_edge) == 0)
58688c4a543Sbru 		dx = 0;
5871ad0085eSbru 
5889ac761feSbru 	if (dx || dy)
58988c4a543Sbru 		wstpad_scroll(tp, dx, dy, magnitude(input, dx, dy), cmds);
5909ac761feSbru }
5919ac761feSbru 
5921f4c8d79Smpi static inline u_int
sbtn(struct wstpad * tp,int x,int y)5939ac761feSbru sbtn(struct wstpad *tp, int x, int y)
5949ac761feSbru {
5959ac761feSbru 	if (y >= tp->edge.bottom)
5969ac761feSbru 		return (0);
5979ac761feSbru 	if ((tp->features & WSTPAD_SOFTMBTN)
5989ac761feSbru 	    && x >= tp->edge.center_left
5999ac761feSbru 	    && x < tp->edge.center_right)
6009ac761feSbru 		return (MIDDLEBTN);
6019ac761feSbru 	return ((x < tp->edge.center ? LEFTBTN : RIGHTBTN) ^ tp->sbtnswap);
6029ac761feSbru }
6039ac761feSbru 
6041f4c8d79Smpi static inline u_int
top_sbtn(struct wstpad * tp,int x,int y)6059ac761feSbru top_sbtn(struct wstpad *tp, int x, int y)
6069ac761feSbru {
6079ac761feSbru 	if (y < tp->edge.top)
6089ac761feSbru 		return (0);
6099ac761feSbru 	if (x < tp->edge.center_left)
6109ac761feSbru 		return (LEFTBTN ^ tp->sbtnswap);
6119ac761feSbru 	return (x > tp->edge.center_right
6129ac761feSbru 	    ? (RIGHTBTN ^ tp->sbtnswap) : MIDDLEBTN);
6139ac761feSbru }
6149ac761feSbru 
6159ac761feSbru u_int
wstpad_get_sbtn(struct wsmouseinput * input,int top)6169ac761feSbru wstpad_get_sbtn(struct wsmouseinput *input, int top)
6179ac761feSbru {
6189ac761feSbru 	struct wstpad *tp = input->tp;
6199ac761feSbru 	struct tpad_touch *t = tp->t;
6209ac761feSbru 	u_int btn;
6219ac761feSbru 
6229ac761feSbru 	btn = 0;
6239ac761feSbru 	if (tp->contacts) {
6249ac761feSbru 		btn = top ? top_sbtn(tp, t->x, t->y) : sbtn(tp, t->x, t->y);
6259ac761feSbru 		/*
6269ac761feSbru 		 * If there is no middle-button area, but contacts in both
6279ac761feSbru 		 * halves of the edge zone, generate a middle-button event:
6289ac761feSbru 		 */
6299ac761feSbru 		if (btn && IS_MT(tp) && tp->contacts == 2
6309ac761feSbru 		    && !top && !(tp->features & WSTPAD_SOFTMBTN)) {
6319ac761feSbru 			if ((t = get_2nd_touch(input)) != NULL)
6329ac761feSbru 				btn |= sbtn(tp, t->x, t->y);
6339ac761feSbru 			if (btn == (LEFTBTN | RIGHTBTN))
6349ac761feSbru 				btn = MIDDLEBTN;
6359ac761feSbru 		}
6369ac761feSbru 	}
6379ac761feSbru 	return (btn != PRIMARYBTN ? btn : 0);
6389ac761feSbru }
6399ac761feSbru 
6409310c18aSbru int
wstpad_mtbtn_contacts(struct wsmouseinput * input)6419310c18aSbru wstpad_mtbtn_contacts(struct wsmouseinput *input)
6429310c18aSbru {
6439310c18aSbru 	struct wstpad *tp = input->tp;
6449310c18aSbru 	struct tpad_touch *t;
6459310c18aSbru 	int dx, dy, dist, limit;
6469310c18aSbru 
6479310c18aSbru 	if (tp->ignore != 0)
6489310c18aSbru 		return (tp->contacts - 1);
6499310c18aSbru 
6509310c18aSbru 	if (tp->contacts == 2 && (t = get_2nd_touch(input)) != NULL) {
6519310c18aSbru 		dx = abs(t->x - tp->t->x) << 12;
6529310c18aSbru 		dy = abs(t->y - tp->t->y) * tp->ratio;
6539310c18aSbru 		dist = (dx >= dy ? dx + 3 * dy / 8 : dy + 3 * dx / 8);
6549310c18aSbru 		limit = tp->params.mtbtn_maxdist << 12;
6559310c18aSbru 		if (input->mt.ptr_mask != 0)
6569310c18aSbru 			limit = limit * 2 / 3;
6579310c18aSbru 		if (dist > limit)
6589310c18aSbru 			return (1);
6599310c18aSbru 	}
6609310c18aSbru 	return (tp->contacts);
6619310c18aSbru }
6629310c18aSbru 
6639310c18aSbru u_int
wstpad_get_mtbtn(struct wsmouseinput * input)6649310c18aSbru wstpad_get_mtbtn(struct wsmouseinput *input)
6659310c18aSbru {
6669310c18aSbru 	int contacts = wstpad_mtbtn_contacts(input);
6679310c18aSbru 	return (contacts == 2 ? RIGHTBTN : (contacts == 3 ? MIDDLEBTN : 0));
6689310c18aSbru }
6699310c18aSbru 
6709310c18aSbru 
6719ac761feSbru void
wstpad_softbuttons(struct wsmouseinput * input,u_int * cmds,int hdlr)6729ac761feSbru wstpad_softbuttons(struct wsmouseinput *input, u_int *cmds, int hdlr)
6739ac761feSbru {
6749ac761feSbru 	struct wstpad *tp = input->tp;
6759ac761feSbru 	int top = (hdlr == TOPBUTTON_HDLR);
6769ac761feSbru 
6779ac761feSbru 	if (tp->softbutton && PRIMARYBTN_RELEASED(tp)) {
6789ac761feSbru 		*cmds |= 1 << SOFTBUTTON_UP;
6799ac761feSbru 		return;
6809ac761feSbru 	}
6819ac761feSbru 
6829ac761feSbru 	if (tp->softbutton == 0 && PRIMARYBTN_CLICKED(tp)) {
6839310c18aSbru 		tp->softbutton = ((tp->features & WSTPAD_MTBUTTONS)
6849310c18aSbru 		    ? wstpad_get_mtbtn(input) : wstpad_get_sbtn(input, top));
6859ac761feSbru 		if (tp->softbutton)
6869ac761feSbru 			*cmds |= 1 << SOFTBUTTON_DOWN;
6879ac761feSbru 	}
6889ac761feSbru }
6899ac761feSbru 
69015aff9aaSbru /* Check whether the duration of t is within the tap limit. */
69131a33b6eSbru int
wstpad_is_tap(struct wstpad * tp,struct tpad_touch * t)69231a33b6eSbru wstpad_is_tap(struct wstpad *tp, struct tpad_touch *t)
69331a33b6eSbru {
69431a33b6eSbru 	struct timespec ts;
6954c234c18Sbru 
6964c234c18Sbru 	timespecsub(&tp->time, &t->orig.time, &ts);
6974c234c18Sbru 	return (timespeccmp(&ts, &tp->tap.maxtime, <));
6984c234c18Sbru }
6994c234c18Sbru 
7004c234c18Sbru /*
7014c234c18Sbru  * At least one MT touch must remain close to its origin and end
7024c234c18Sbru  * in the main area.  The same conditions apply to one-finger taps
7034c234c18Sbru  * on single-touch devices.
7044c234c18Sbru  */
7054c234c18Sbru void
wstpad_tap_filter(struct wstpad * tp,struct tpad_touch * t)7064c234c18Sbru wstpad_tap_filter(struct wstpad *tp, struct tpad_touch *t)
7074c234c18Sbru {
70831a33b6eSbru 	int dx, dy, dist = 0;
70931a33b6eSbru 
7104c234c18Sbru 	if (IS_MT(tp) || tp->tap.contacts == 1) {
71131a33b6eSbru 		dx = abs(t->x - t->orig.x) << 12;
71231a33b6eSbru 		dy = abs(t->y - t->orig.y) * tp->ratio;
71331a33b6eSbru 		dist = (dx >= dy ? dx + 3 * dy / 8 : dy + 3 * dx / 8);
71431a33b6eSbru 	}
71515aff9aaSbru 	tp->tap.valid = (CENTERED(t) && dist <= (tp->tap.maxdist << 12));
71631a33b6eSbru }
7174c234c18Sbru 
71831a33b6eSbru 
71931a33b6eSbru /*
7201f56cd93Sbru  * Return the oldest touch in the TOUCH_END state, or NULL.
7211f56cd93Sbru  */
7221f56cd93Sbru struct tpad_touch *
wstpad_tap_touch(struct wsmouseinput * input)7231f56cd93Sbru wstpad_tap_touch(struct wsmouseinput *input)
7241f56cd93Sbru {
7251f56cd93Sbru 	struct wstpad *tp = input->tp;
7261f56cd93Sbru 	struct tpad_touch *s, *t = NULL;
7271f56cd93Sbru 	u_int lifted;
7281f56cd93Sbru 	int slot;
7291f56cd93Sbru 
7301f56cd93Sbru 	if (IS_MT(tp)) {
7311f56cd93Sbru 		lifted = (input->mt.sync[MTS_TOUCH] & ~input->mt.touches);
7321f56cd93Sbru 		FOREACHBIT(lifted, slot) {
7331f56cd93Sbru 			s = &tp->tpad_touches[slot];
73415aff9aaSbru 			if (tp->tap.state == TAP_DETECT && !tp->tap.valid)
7354c234c18Sbru 				wstpad_tap_filter(tp, s);
7361f56cd93Sbru 			if (t == NULL || timespeccmp(&t->orig.time,
7371f56cd93Sbru 			    &s->orig.time, >))
7381f56cd93Sbru 				t = s;
7391f56cd93Sbru 		}
7401f56cd93Sbru 	} else {
7411f56cd93Sbru 		if (tp->t->state == TOUCH_END) {
7421f56cd93Sbru 			t = tp->t;
74315aff9aaSbru 			if (tp->tap.state == TAP_DETECT && !tp->tap.valid)
7444c234c18Sbru 				wstpad_tap_filter(tp, t);
7451f56cd93Sbru 		}
7461f56cd93Sbru 	}
7471f56cd93Sbru 
7481f56cd93Sbru 	return (t);
7491f56cd93Sbru }
7501f56cd93Sbru 
75115aff9aaSbru /* Determine the "tap button", keep track of whether a touch is masked. */
75215aff9aaSbru u_int
wstpad_tap_button(struct wstpad * tp)75315aff9aaSbru wstpad_tap_button(struct wstpad *tp)
75415aff9aaSbru {
75515aff9aaSbru 	int n = tp->tap.contacts - tp->contacts - 1;
75615aff9aaSbru 
75715aff9aaSbru 	tp->tap.masked = tp->contacts;
75815aff9aaSbru 
75915aff9aaSbru 	return (n >= 0 && n < TAP_BTNMAP_SIZE ? tp->tap.btnmap[n] : 0);
76015aff9aaSbru }
76115aff9aaSbru 
7621f56cd93Sbru /*
76315aff9aaSbru  * In the hold/drag state, do not mask touches if no masking was involved
76415aff9aaSbru  * in the preceding tap gesture.
7651f56cd93Sbru  */
7661f56cd93Sbru static inline int
tap_unmask(struct wstpad * tp)76715aff9aaSbru tap_unmask(struct wstpad *tp)
7681f56cd93Sbru {
76915aff9aaSbru 	return ((tp->tap.button || tp->tap.pending) && tp->tap.masked == 0);
7701f56cd93Sbru }
7711f56cd93Sbru 
7721f56cd93Sbru /*
77315aff9aaSbru  * In the default configuration, this handler maps one-, two-, and
77415aff9aaSbru  * three-finger taps to left-button, right-button, and middle-button
77515aff9aaSbru  * events, respectively.  Setting the LOCKTIME parameter enables
77631a33b6eSbru  * "locked drags", which are finished by a timeout or a tap-to-end
77731a33b6eSbru  * gesture.
77831a33b6eSbru  */
77931a33b6eSbru void
wstpad_tap(struct wsmouseinput * input,u_int * cmds)78031a33b6eSbru wstpad_tap(struct wsmouseinput *input, u_int *cmds)
78131a33b6eSbru {
78231a33b6eSbru 	struct wstpad *tp = input->tp;
7831f56cd93Sbru 	struct tpad_touch *t;
78415aff9aaSbru 	int contacts, is_tap, slot, err = 0;
78531a33b6eSbru 
78615aff9aaSbru 	/* Synchronize the button states, if necessary. */
78715aff9aaSbru 	if (input->btn.sync)
78815aff9aaSbru 		*cmds |= 1 << TAPBUTTON_SYNC;
78915aff9aaSbru 
79031a33b6eSbru 	/*
79115aff9aaSbru 	 * It is possible to produce a click within the tap timeout.
79215aff9aaSbru 	 * Wait for a new touch before generating new button events.
79331a33b6eSbru 	 */
79415aff9aaSbru 	if (PRIMARYBTN_RELEASED(tp))
7951f56cd93Sbru 		tp->tap.contacts = 0;
79615aff9aaSbru 
79715aff9aaSbru 	/* Reset the detection state whenever a new touch starts. */
79815aff9aaSbru 	if (tp->contacts > tp->prev_contacts || (IS_MT(tp) &&
79915aff9aaSbru 	    (input->mt.touches & input->mt.sync[MTS_TOUCH]))) {
80015aff9aaSbru 		tp->tap.contacts = tp->contacts;
80115aff9aaSbru 		tp->tap.valid = 0;
80231a33b6eSbru 	}
80331a33b6eSbru 
8041f56cd93Sbru 	/*
80515aff9aaSbru 	 * The filtered number of active touches excludes a masked
80615aff9aaSbru 	 * touch if its duration exceeds the tap limit.
8071f56cd93Sbru 	 */
80815aff9aaSbru 	contacts = tp->contacts;
80915aff9aaSbru 	if ((slot = ffs(input->mt.ptr_mask) - 1) >= 0
81015aff9aaSbru 	    && !wstpad_is_tap(tp, &tp->tpad_touches[slot])
81115aff9aaSbru 	    && !tap_unmask(tp)) {
81215aff9aaSbru 		contacts--;
81315aff9aaSbru 	}
8141f56cd93Sbru 
81531a33b6eSbru 	switch (tp->tap.state) {
81631a33b6eSbru 	case TAP_DETECT:
81715aff9aaSbru 		/* Find the oldest touch in the TOUCH_END state. */
81815aff9aaSbru 		t = wstpad_tap_touch(input);
8191f56cd93Sbru 		if (t) {
82015aff9aaSbru 			is_tap = wstpad_is_tap(tp, t);
82115aff9aaSbru 			if (is_tap && contacts == 0) {
82215aff9aaSbru 				if (tp->tap.button)
82331a33b6eSbru 					*cmds |= 1 << TAPBUTTON_UP;
82415aff9aaSbru 				tp->tap.pending = (tp->tap.valid
82515aff9aaSbru 				    ? wstpad_tap_button(tp) : 0);
82615aff9aaSbru 				if (tp->tap.pending) {
82731a33b6eSbru 					tp->tap.state = TAP_LIFTED;
8281f56cd93Sbru 					err = !timeout_add_msec(&tp->tap.to,
8291f56cd93Sbru 					    CLICKDELAY_MS);
83015aff9aaSbru 				}
83115aff9aaSbru 			} else if (!is_tap && tp->tap.locktime == 0) {
83215aff9aaSbru 				if (contacts == 0 && tp->tap.button)
83315aff9aaSbru 					*cmds |= 1 << TAPBUTTON_UP;
83415aff9aaSbru 				else if (contacts)
83515aff9aaSbru 					tp->tap.state = TAP_IGNORE;
83615aff9aaSbru 			} else if (!is_tap && tp->tap.button) {
83715aff9aaSbru 				if (contacts == 0) {
83815aff9aaSbru 					tp->tap.state = TAP_LOCKED;
83915aff9aaSbru 					err = !timeout_add_msec(&tp->tap.to,
84015aff9aaSbru 					    tp->tap.locktime);
84115aff9aaSbru 				} else {
84215aff9aaSbru 					tp->tap.state = TAP_LOCKED_DRAG;
84315aff9aaSbru 				}
84415aff9aaSbru 			}
84515aff9aaSbru 		}
84615aff9aaSbru 		break;
84715aff9aaSbru 	case TAP_IGNORE:
84815aff9aaSbru 		if (contacts == 0) {
84915aff9aaSbru 			tp->tap.state = TAP_DETECT;
85015aff9aaSbru 			if (tp->tap.button)
85115aff9aaSbru 				*cmds |= 1 << TAPBUTTON_UP;
85215aff9aaSbru 		}
85315aff9aaSbru 		break;
85415aff9aaSbru 	case TAP_LIFTED:
85515aff9aaSbru 		if (contacts) {
85615aff9aaSbru 			timeout_del(&tp->tap.to);
85715aff9aaSbru 			tp->tap.state = TAP_DETECT;
85815aff9aaSbru 			if (tp->tap.pending)
85915aff9aaSbru 				*cmds |= 1 << TAPBUTTON_DOWN;
86015aff9aaSbru 		}
86115aff9aaSbru 		break;
86215aff9aaSbru 	case TAP_LOCKED:
86315aff9aaSbru 		if (contacts) {
86415aff9aaSbru 			timeout_del(&tp->tap.to);
86515aff9aaSbru 			tp->tap.state = TAP_LOCKED_DRAG;
86615aff9aaSbru 		}
86715aff9aaSbru 		break;
86815aff9aaSbru 	case TAP_LOCKED_DRAG:
86915aff9aaSbru 		if (contacts == 0) {
87015aff9aaSbru 			t = wstpad_tap_touch(input);
87115aff9aaSbru 			if (t && wstpad_is_tap(tp, t)) {
87215aff9aaSbru 				/* "tap-to-end" */
87331a33b6eSbru 				*cmds |= 1 << TAPBUTTON_UP;
87431a33b6eSbru 				tp->tap.state = TAP_DETECT;
87531a33b6eSbru 			} else {
87631a33b6eSbru 				tp->tap.state = TAP_LOCKED;
8771f56cd93Sbru 				err = !timeout_add_msec(&tp->tap.to,
8781f56cd93Sbru 				    tp->tap.locktime);
87931a33b6eSbru 			}
8801f56cd93Sbru 		}
88131a33b6eSbru 		break;
88231a33b6eSbru 	}
88331a33b6eSbru 
88431a33b6eSbru 	if (err) { /* Did timeout_add fail? */
88515aff9aaSbru 		input->sbtn.buttons &= ~tp->tap.button;
88615aff9aaSbru 		input->sbtn.sync |= tp->tap.button;
88715aff9aaSbru 		tp->tap.pending = 0;
88815aff9aaSbru 		tp->tap.button = 0;
88931a33b6eSbru 		tp->tap.state = TAP_DETECT;
89031a33b6eSbru 	}
89131a33b6eSbru }
89231a33b6eSbru 
89315aff9aaSbru int
wstpad_tap_sync(struct wsmouseinput * input)89415aff9aaSbru wstpad_tap_sync(struct wsmouseinput *input) {
89515aff9aaSbru 	struct wstpad *tp = input->tp;
89615aff9aaSbru 
89715aff9aaSbru 	return ((tp->tap.button & (input->btn.buttons | tp->softbutton)) == 0
89815aff9aaSbru 	    || (tp->tap.button == PRIMARYBTN && tp->softbutton));
89915aff9aaSbru }
90015aff9aaSbru 
90131a33b6eSbru void
wstpad_tap_timeout(void * p)90231a33b6eSbru wstpad_tap_timeout(void *p)
90331a33b6eSbru {
90431a33b6eSbru 	struct wsmouseinput *input = p;
90531a33b6eSbru 	struct wstpad *tp = input->tp;
90631a33b6eSbru 	struct evq_access evq;
90731a33b6eSbru 	u_int btn;
90815aff9aaSbru 	int s, ev;
90931a33b6eSbru 
91031a33b6eSbru 	s = spltty();
91131a33b6eSbru 	evq.evar = *input->evar;
91215aff9aaSbru 	if (evq.evar != NULL && tp != NULL) {
91315aff9aaSbru 		ev = 0;
91415aff9aaSbru 		if (tp->tap.pending) {
91515aff9aaSbru 			tp->tap.button = tp->tap.pending;
91615aff9aaSbru 			tp->tap.pending = 0;
91715aff9aaSbru 			input->sbtn.buttons |= tp->tap.button;
91815aff9aaSbru 			timeout_add_msec(&tp->tap.to, tp->tap.clicktime);
91915aff9aaSbru 			if (wstpad_tap_sync(input)) {
92015aff9aaSbru 				ev = BTN_DOWN_EV;
92131a33b6eSbru 				btn = ffs(tp->tap.button) - 1;
92215aff9aaSbru 			}
92315aff9aaSbru 		} else {
92415aff9aaSbru 			if (wstpad_tap_sync(input)) {
92515aff9aaSbru 				ev = BTN_UP_EV;
92615aff9aaSbru 				btn = ffs(tp->tap.button) - 1;
92715aff9aaSbru 			}
92815aff9aaSbru 			if (tp->tap.button != tp->softbutton)
92915aff9aaSbru 				input->sbtn.buttons &= ~tp->tap.button;
93015aff9aaSbru 			tp->tap.button = 0;
93115aff9aaSbru 			tp->tap.state = TAP_DETECT;
93215aff9aaSbru 		}
93315aff9aaSbru 		if (ev) {
934*93ab6942Smvs 			evq.put = evq.evar->ws_put;
93531a33b6eSbru 			evq.result = EVQ_RESULT_NONE;
936dc7e9ee1Sbru 			getnanotime(&evq.ts);
93715aff9aaSbru 			wsmouse_evq_put(&evq, ev, btn);
93831a33b6eSbru 			wsmouse_evq_put(&evq, SYNC_EV, 0);
93931a33b6eSbru 			if (evq.result == EVQ_RESULT_SUCCESS) {
940dc7e9ee1Sbru 				if (input->flags & LOG_EVENTS) {
941dc7e9ee1Sbru 					wsmouse_log_events(input, &evq);
942dc7e9ee1Sbru 				}
943*93ab6942Smvs 				evq.evar->ws_put = evq.put;
94431a33b6eSbru 				WSEVENT_WAKEUP(evq.evar);
94531a33b6eSbru 			} else {
94631a33b6eSbru 				input->sbtn.sync |= tp->tap.button;
94731a33b6eSbru 			}
94831a33b6eSbru 		}
94915aff9aaSbru 	}
95031a33b6eSbru 	splx(s);
95131a33b6eSbru }
95231a33b6eSbru 
9539ac761feSbru /*
9549ac761feSbru  * Suppress accidental pointer movements after a click on a clickpad.
9559ac761feSbru  */
9569ac761feSbru void
wstpad_click(struct wsmouseinput * input)9579ac761feSbru wstpad_click(struct wsmouseinput *input)
9589ac761feSbru {
9599ac761feSbru 	struct wstpad *tp = input->tp;
9609ac761feSbru 
9619ac761feSbru 	if (tp->contacts == 1 &&
9629ac761feSbru 	    (PRIMARYBTN_CLICKED(tp) || PRIMARYBTN_RELEASED(tp)))
9639ac761feSbru 		set_freeze_ts(tp, 0, FREEZE_MS);
9649ac761feSbru }
9659ac761feSbru 
96615aff9aaSbru /* Translate the "command" bits into the sync-state of wsmouse. */
9679ac761feSbru void
wstpad_cmds(struct wsmouseinput * input,u_int cmds)96815aff9aaSbru wstpad_cmds(struct wsmouseinput *input, u_int cmds)
9699ac761feSbru {
9709ac761feSbru 	struct wstpad *tp = input->tp;
9719ac761feSbru 	int n;
9729ac761feSbru 
9739ac761feSbru 	FOREACHBIT(cmds, n) {
9749ac761feSbru 		switch (n) {
9759ac761feSbru 		case CLEAR_MOTION_DELTAS:
9769ac761feSbru 			input->motion.dx = input->motion.dy = 0;
9779ac761feSbru 			if (input->motion.dz == 0 && input->motion.dw == 0)
9789ac761feSbru 				input->motion.sync &= ~SYNC_DELTAS;
9799ac761feSbru 			continue;
9809ac761feSbru 		case SOFTBUTTON_DOWN:
9819ac761feSbru 			input->btn.sync &= ~PRIMARYBTN;
98215aff9aaSbru 			input->sbtn.buttons |= tp->softbutton;
98315aff9aaSbru 			if (tp->softbutton != tp->tap.button)
98415aff9aaSbru 				input->sbtn.sync |= tp->softbutton;
9859ac761feSbru 			continue;
9869ac761feSbru 		case SOFTBUTTON_UP:
9879ac761feSbru 			input->btn.sync &= ~PRIMARYBTN;
98815aff9aaSbru 			if (tp->softbutton != tp->tap.button) {
98915aff9aaSbru 				input->sbtn.buttons &= ~tp->softbutton;
99015aff9aaSbru 				input->sbtn.sync |= tp->softbutton;
99115aff9aaSbru 			}
9929ac761feSbru 			tp->softbutton = 0;
9939ac761feSbru 			continue;
99415aff9aaSbru 		case TAPBUTTON_SYNC:
99515aff9aaSbru 			if (tp->tap.button)
99615aff9aaSbru 				input->btn.sync &= ~tp->tap.button;
99715aff9aaSbru 			continue;
99831a33b6eSbru 		case TAPBUTTON_DOWN:
99915aff9aaSbru 			tp->tap.button = tp->tap.pending;
100015aff9aaSbru 			tp->tap.pending = 0;
100115aff9aaSbru 			input->sbtn.buttons |= tp->tap.button;
100215aff9aaSbru 			if (wstpad_tap_sync(input))
100315aff9aaSbru 				input->sbtn.sync |= tp->tap.button;
100431a33b6eSbru 			continue;
100531a33b6eSbru 		case TAPBUTTON_UP:
100615aff9aaSbru 			if (tp->tap.button != tp->softbutton)
100715aff9aaSbru 				input->sbtn.buttons &= ~tp->tap.button;
100815aff9aaSbru 			if (wstpad_tap_sync(input))
100915aff9aaSbru 				input->sbtn.sync |= tp->tap.button;
101015aff9aaSbru 			tp->tap.button = 0;
101131a33b6eSbru 			continue;
10129ac761feSbru 		case HSCROLL:
10139ac761feSbru 			input->motion.dw = tp->scroll.dw;
10149ac761feSbru 			input->motion.sync |= SYNC_DELTAS;
10159ac761feSbru 			continue;
10169ac761feSbru 		case VSCROLL:
10179ac761feSbru 			input->motion.dz = tp->scroll.dz;
10189ac761feSbru 			input->motion.sync |= SYNC_DELTAS;
10199ac761feSbru 			continue;
10209ac761feSbru 		default:
10219ac761feSbru 			printf("[wstpad] invalid cmd %d\n", n);
10229ac761feSbru 			break;
10239ac761feSbru 		}
10249ac761feSbru 	}
10259ac761feSbru }
10269ac761feSbru 
10279ac761feSbru 
10289ac761feSbru /*
10299ac761feSbru  * Set the state of touches that have ended. TOUCH_END is a transitional
10309ac761feSbru  * state and will be changed to TOUCH_NONE before process_input() returns.
10319ac761feSbru  */
10321f4c8d79Smpi static inline void
clear_touchstates(struct wsmouseinput * input,enum touchstates state)10339ac761feSbru clear_touchstates(struct wsmouseinput *input, enum touchstates state)
10349ac761feSbru {
10359ac761feSbru 	u_int touches;
10369ac761feSbru 	int slot;
10379ac761feSbru 
10389ac761feSbru 	touches = input->mt.sync[MTS_TOUCH] & ~input->mt.touches;
10399ac761feSbru 	FOREACHBIT(touches, slot)
10409ac761feSbru 		input->tp->tpad_touches[slot].state = state;
10419ac761feSbru }
10429ac761feSbru 
10439ac761feSbru void
wstpad_mt_inputs(struct wsmouseinput * input)10449ac761feSbru wstpad_mt_inputs(struct wsmouseinput *input)
10459ac761feSbru {
10469ac761feSbru 	struct wstpad *tp = input->tp;
10479ac761feSbru 	struct tpad_touch *t;
10489ac761feSbru 	int slot, dx, dy;
10499ac761feSbru 	u_int touches, inactive;
10509ac761feSbru 
10519ac761feSbru 	/* TOUCH_BEGIN */
10529ac761feSbru 	touches = input->mt.touches & input->mt.sync[MTS_TOUCH];
10539ac761feSbru 	FOREACHBIT(touches, slot) {
10549ac761feSbru 		t = &tp->tpad_touches[slot];
10559ac761feSbru 		t->state = TOUCH_BEGIN;
10561a6243b7Sbru 		t->x = normalize_abs(&input->filter.h, t->pos->x);
10571a6243b7Sbru 		t->y = normalize_abs(&input->filter.v, t->pos->y);
10589ac761feSbru 		t->orig.x = t->x;
10599ac761feSbru 		t->orig.y = t->y;
10609ac761feSbru 		memcpy(&t->orig.time, &tp->time, sizeof(struct timespec));
10619ac761feSbru 		t->flags = edge_flags(tp, t->x, t->y);
10621be64207Sbru 		wstpad_set_direction(tp, t, 0, 0);
10639ac761feSbru 	}
10649ac761feSbru 
10659ac761feSbru 	/* TOUCH_UPDATE */
10669ac761feSbru 	touches = input->mt.touches & input->mt.frame;
10679ac761feSbru 	if (touches & tp->mtcycle) {
10689ac761feSbru 		/*
10699ac761feSbru 		 * Slot data may be synchronized separately, in any order,
10701f56cd93Sbru 		 * or not at all if there is no delta.  Identify the touches
10711f56cd93Sbru 		 * without deltas.
10729ac761feSbru 		 */
10739ac761feSbru 		inactive = input->mt.touches & ~tp->mtcycle;
10749ac761feSbru 		tp->mtcycle = touches;
10759ac761feSbru 	} else {
10769ac761feSbru 		inactive = 0;
10779ac761feSbru 		tp->mtcycle |= touches;
10789ac761feSbru 	}
10799ac761feSbru 	touches = input->mt.touches & ~input->mt.sync[MTS_TOUCH];
10809ac761feSbru 	FOREACHBIT(touches, slot) {
10819ac761feSbru 		t = &tp->tpad_touches[slot];
10829ac761feSbru 		t->state = TOUCH_UPDATE;
10839ac761feSbru 		if ((1 << slot) & input->mt.frame) {
10841a6243b7Sbru 			dx = normalize_abs(&input->filter.h, t->pos->x) - t->x;
10859ac761feSbru 			t->x += dx;
10861a6243b7Sbru 			dy = normalize_abs(&input->filter.v, t->pos->y) - t->y;
10879ac761feSbru 			t->y += dy;
10889ac761feSbru 			t->flags &= (~EDGES | edge_flags(tp, t->x, t->y));
10891a6243b7Sbru 			if (wsmouse_hysteresis(input, t->pos))
1090d8696265Sbru 				dx = dy = 0;
10911be64207Sbru 			wstpad_set_direction(tp, t, dx, dy);
10929ac761feSbru 		} else if ((1 << slot) & inactive) {
10931be64207Sbru 			wstpad_set_direction(tp, t, 0, 0);
10949ac761feSbru 		}
10959ac761feSbru 	}
10969ac761feSbru 
10979ac761feSbru 	clear_touchstates(input, TOUCH_END);
10989ac761feSbru }
10999ac761feSbru 
1100c06ae661Sbru /*
1101c06ae661Sbru  * Identify "thumb" contacts in the bottom area.  The identification
1102c06ae661Sbru  * has three stages:
1103c06ae661Sbru  * 1. If exactly one of two or more touches is in the bottom area, it
1104c06ae661Sbru  * is masked, which means it does not receive pointer control as long
1105c06ae661Sbru  * as there are alternatives.  Once set, the mask will only be cleared
1106c06ae661Sbru  * when the touch is released.
1107c06ae661Sbru  * Tap detection ignores a masked touch if it does not participate in
1108c06ae661Sbru  * a tap gesture.
1109c06ae661Sbru  * 2. If the pointer-controlling touch is moving stably while a masked
1110c06ae661Sbru  * touch in the bottom area is resting, or only moving minimally, the
1111c06ae661Sbru  * pointer mask is copied to tp->ignore.  In this stage, the masked
1112c06ae661Sbru  * touch does not block pointer movement, and it is ignored by
1113c06ae661Sbru  * wstpad_f2scroll().
1114c06ae661Sbru  * Decisions are made more or less immediately, there may be errors
1115c06ae661Sbru  * in edge cases.  If a fast or long upward movement is detected,
1116c06ae661Sbru  * tp->ignore is cleared.  There is no other transition from stage 2
1117c06ae661Sbru  * to scrolling, or vice versa, for a pair of touches.
1118c06ae661Sbru  * 3. If tp->ignore is set and the touch is resting, it is marked as
1119c06ae661Sbru  * thumb, and it will be ignored until it ends.
1120c06ae661Sbru  */
11219ac761feSbru void
wstpad_mt_masks(struct wsmouseinput * input)11221f56cd93Sbru wstpad_mt_masks(struct wsmouseinput *input)
11231f56cd93Sbru {
11241f56cd93Sbru 	struct wstpad *tp = input->tp;
11251f56cd93Sbru 	struct tpad_touch *t;
1126c06ae661Sbru 	struct position *pos;
11271f56cd93Sbru 	u_int mask;
1128d8696265Sbru 	int slot;
11291f56cd93Sbru 
11301f56cd93Sbru 	tp->ignore &= input->mt.touches;
11311f56cd93Sbru 
1132c06ae661Sbru 	if (tp->contacts < 2)
11331f56cd93Sbru 		return;
11341f56cd93Sbru 
1135c06ae661Sbru 	if (tp->ignore) {
1136c06ae661Sbru 		slot = ffs(tp->ignore) - 1;
1137c06ae661Sbru 		t = &tp->tpad_touches[slot];
1138c06ae661Sbru 		if (t->flags & THUMB)
1139c06ae661Sbru 			return;
1140c06ae661Sbru 		if (t->dir < 0 && wstpad_is_stable(input, t)) {
1141c06ae661Sbru 			t->flags |= THUMB;
1142c06ae661Sbru 			return;
1143c06ae661Sbru 		}
1144c06ae661Sbru 		/* The edge.low area is a bit larger than the bottom area. */
1145c06ae661Sbru 		if (t->y >= tp->edge.low || (NORTH(t->dir) &&
1146c06ae661Sbru 		    magnitude(input, t->pos->dx, t->pos->dy) >= MAG_MEDIUM))
1147c06ae661Sbru 			tp->ignore = 0;
1148c06ae661Sbru 		return;
1149c06ae661Sbru 	}
1150c06ae661Sbru 
11511f56cd93Sbru 	if (input->mt.ptr_mask == 0) {
11521f56cd93Sbru 		mask = ~0;
11531f56cd93Sbru 		FOREACHBIT(input->mt.touches, slot) {
11541f56cd93Sbru 			t = &tp->tpad_touches[slot];
11551f56cd93Sbru 			if (t->flags & B_EDGE) {
11561f56cd93Sbru 				mask &= (1 << slot);
11571f56cd93Sbru 				input->mt.ptr_mask = mask;
11581f56cd93Sbru 			}
11591f56cd93Sbru 		}
11601f56cd93Sbru 	}
11611f56cd93Sbru 
1162c06ae661Sbru 	if ((input->mt.ptr_mask & ~input->mt.ptr)
1163c06ae661Sbru 	    && !(tp->scroll.dz || tp->scroll.dw)
1164c06ae661Sbru 	    && tp->t->dir >= 0
1165c06ae661Sbru 	    && wstpad_is_stable(input, tp->t)) {
1166d8696265Sbru 
11671f56cd93Sbru 		slot = ffs(input->mt.ptr_mask) - 1;
11681f56cd93Sbru 		t = &tp->tpad_touches[slot];
1169d8696265Sbru 
1170c06ae661Sbru 		if (t->y >= tp->edge.low)
1171c06ae661Sbru 			return;
1172c06ae661Sbru 
1173c06ae661Sbru 		if (!wstpad_is_stable(input, t))
1174c06ae661Sbru 			return;
1175c06ae661Sbru 
1176c06ae661Sbru 		/* Default hysteresis limits are low.  Make a strict check. */
1177c06ae661Sbru 		pos = tp->t->pos;
1178c06ae661Sbru 		if (abs(pos->acc_dx) < 3 * input->filter.h.hysteresis
1179c06ae661Sbru 		    && abs(pos->acc_dy) < 3 * input->filter.v.hysteresis)
1180c06ae661Sbru 			return;
1181c06ae661Sbru 
1182c06ae661Sbru 		if (t->dir >= 0) {
1183c06ae661Sbru 			/* Treat t as thumb if it is slow while tp->t is fast. */
1184c06ae661Sbru 			if (magnitude(input, t->pos->dx, t->pos->dy) > MAG_LOW
1185c06ae661Sbru 			    || magnitude(input, pos->dx, pos->dy) < MAG_MEDIUM)
1186c06ae661Sbru 				return;
1187c06ae661Sbru 		}
1188c06ae661Sbru 
11891f56cd93Sbru 		tp->ignore = input->mt.ptr_mask;
11901f56cd93Sbru 	}
11911f56cd93Sbru }
11921f56cd93Sbru 
11931f56cd93Sbru void
wstpad_touch_inputs(struct wsmouseinput * input)11949ac761feSbru wstpad_touch_inputs(struct wsmouseinput *input)
11959ac761feSbru {
11969ac761feSbru 	struct wstpad *tp = input->tp;
11979ac761feSbru 	struct tpad_touch *t;
119888c4a543Sbru 	int slot, x, y, dx, dy;
11991ad0085eSbru 
12009ac761feSbru 	tp->btns = input->btn.buttons;
12019ac761feSbru 	tp->btns_sync = input->btn.sync;
12029ac761feSbru 
12039ac761feSbru 	tp->prev_contacts = tp->contacts;
12049ac761feSbru 	tp->contacts = input->touch.contacts;
12059ac761feSbru 
12069ac761feSbru 	if (tp->contacts == 1 &&
12079ac761feSbru 	    ((tp->params.f2width &&
12089ac761feSbru 	    input->touch.width >= tp->params.f2width)
12099ac761feSbru 	    || (tp->params.f2pressure &&
12109ac761feSbru 	    input->touch.pressure >= tp->params.f2pressure)))
12119ac761feSbru 		tp->contacts = 2;
12129ac761feSbru 
12139ac761feSbru 	if (IS_MT(tp)) {
12149ac761feSbru 		wstpad_mt_inputs(input);
12159ac761feSbru 		if (input->mt.ptr) {
12169ac761feSbru 			slot = ffs(input->mt.ptr) - 1;
12179ac761feSbru 			tp->t = &tp->tpad_touches[slot];
12189ac761feSbru 		}
12191f56cd93Sbru 		wstpad_mt_masks(input);
12209ac761feSbru 	} else {
12219ac761feSbru 		t = tp->t;
12229ac761feSbru 		if (tp->contacts)
12239ac761feSbru 			t->state = (tp->prev_contacts ?
12249ac761feSbru 			    TOUCH_UPDATE : TOUCH_BEGIN);
12259ac761feSbru 		else
12269ac761feSbru 			t->state = (tp->prev_contacts ?
12279ac761feSbru 			    TOUCH_END : TOUCH_NONE);
12289ac761feSbru 
122988c4a543Sbru 		dx = dy = 0;
123088c4a543Sbru 		x = normalize_abs(&input->filter.h, t->pos->x);
123188c4a543Sbru 		y = normalize_abs(&input->filter.v, t->pos->y);
12329ac761feSbru 		if (t->state == TOUCH_BEGIN) {
123388c4a543Sbru 			t->x = t->orig.x = x;
123488c4a543Sbru 			t->y = t->orig.y = y;
12359ac761feSbru 			memcpy(&t->orig.time, &tp->time,
12369ac761feSbru 			    sizeof(struct timespec));
123788c4a543Sbru 			t->flags = edge_flags(tp, x, y);
123888c4a543Sbru 		} else if (input->motion.sync & SYNC_POSITION) {
123988c4a543Sbru 			if (!wsmouse_hysteresis(input, t->pos)) {
124088c4a543Sbru 				dx = x - t->x;
124188c4a543Sbru 				dy = y - t->y;
12429ac761feSbru 			}
124388c4a543Sbru 			t->x = x;
124488c4a543Sbru 			t->y = y;
124588c4a543Sbru 			t->flags &= (~EDGES | edge_flags(tp, x, y));
124688c4a543Sbru 		}
124788c4a543Sbru 		wstpad_set_direction(tp, t, dx, dy);
12489ac761feSbru 	}
12499ac761feSbru }
12509ac761feSbru 
12511f56cd93Sbru static inline int
t2_ignore(struct wsmouseinput * input)12521f56cd93Sbru t2_ignore(struct wsmouseinput *input)
12531f56cd93Sbru {
12541f56cd93Sbru 	/*
12551f56cd93Sbru 	 * If there are two touches, do not block pointer movement if they
12561f56cd93Sbru 	 * perform a click-and-drag action, or if the second touch is
12571f56cd93Sbru 	 * resting in the bottom area.
12581f56cd93Sbru 	 */
12591f56cd93Sbru 	return (input->tp->contacts == 2 && ((input->tp->btns & PRIMARYBTN)
12601f56cd93Sbru 	    || (input->tp->ignore & ~input->mt.ptr)));
12611f56cd93Sbru }
12621f56cd93Sbru 
12639ac761feSbru void
wstpad_process_input(struct wsmouseinput * input,struct evq_access * evq)12649ac761feSbru wstpad_process_input(struct wsmouseinput *input, struct evq_access *evq)
12659ac761feSbru {
12669ac761feSbru 	struct wstpad *tp = input->tp;
12679ac761feSbru 	u_int handlers, hdlr, cmds;
12689ac761feSbru 
12699ac761feSbru 	memcpy(&tp->time, &evq->ts, sizeof(struct timespec));
12709ac761feSbru 	wstpad_touch_inputs(input);
12719ac761feSbru 
12729ac761feSbru 	cmds = 0;
12739ac761feSbru 	handlers = tp->handlers;
12749ac761feSbru 	if (DISABLE(tp))
1275757db27eSbru 		handlers &= ((1 << TOPBUTTON_HDLR) | (1 << SOFTBUTTON_HDLR));
12769ac761feSbru 
12779ac761feSbru 	FOREACHBIT(handlers, hdlr) {
12789ac761feSbru 		switch (hdlr) {
12799ac761feSbru 		case SOFTBUTTON_HDLR:
12809ac761feSbru 		case TOPBUTTON_HDLR:
12819ac761feSbru 			wstpad_softbuttons(input, &cmds, hdlr);
12829ac761feSbru 			continue;
128331a33b6eSbru 		case TAP_HDLR:
128431a33b6eSbru 			wstpad_tap(input, &cmds);
128531a33b6eSbru 			continue;
12869ac761feSbru 		case F2SCROLL_HDLR:
12879ac761feSbru 			wstpad_f2scroll(input, &cmds);
12889ac761feSbru 			continue;
12899ac761feSbru 		case EDGESCROLL_HDLR:
12909ac761feSbru 			wstpad_edgescroll(input, &cmds);
12919ac761feSbru 			continue;
12929ac761feSbru 		case CLICK_HDLR:
12939ac761feSbru 			wstpad_click(input);
12949ac761feSbru 			continue;
12959ac761feSbru 		}
12969ac761feSbru 	}
12979ac761feSbru 
12981f56cd93Sbru 	/* Check whether pointer movement should be blocked. */
12991ad0085eSbru 	if (input->motion.dx || input->motion.dy) {
13009ac761feSbru 		if (DISABLE(tp)
13019ac761feSbru 		    || (tp->t->flags & tp->freeze)
13029ac761feSbru 		    || timespeccmp(&tp->time, &tp->freeze_ts, <)
13031f56cd93Sbru 		    || (tp->contacts > 1 && !t2_ignore(input))) {
13049ac761feSbru 
13059ac761feSbru 			cmds |= 1 << CLEAR_MOTION_DELTAS;
13069ac761feSbru 		}
13079ac761feSbru 	}
13089ac761feSbru 
130915aff9aaSbru 	wstpad_cmds(input, cmds);
13109ac761feSbru 
13119ac761feSbru 	if (IS_MT(tp))
13129ac761feSbru 		clear_touchstates(input, TOUCH_NONE);
13139ac761feSbru }
13149ac761feSbru 
13159ac761feSbru /*
13169ac761feSbru  * Try to determine the average interval between two updates. Various
13179ac761feSbru  * conditions are checked in order to ensure that only valid samples enter
13189ac761feSbru  * into the calculation. Above all, it is restricted to motion events
1319c72e6d6bSfcambus  * occurring when there is only one contact. MT devices may need more than
13209ac761feSbru  * one packet to transmit their state if there are multiple touches, and
13219ac761feSbru  * the update frequency may be higher in this case.
13229ac761feSbru  */
13239ac761feSbru void
wstpad_track_interval(struct wsmouseinput * input,struct timespec * time)13249ac761feSbru wstpad_track_interval(struct wsmouseinput *input, struct timespec *time)
13259ac761feSbru {
13269ac761feSbru 	static const struct timespec limit = { 0, 30 * 1000000L };
13279ac761feSbru 	struct timespec ts;
13289ac761feSbru 	int samples;
13299ac761feSbru 
13309ac761feSbru 	if (input->motion.sync == 0
13319ac761feSbru 	    || (input->touch.sync & SYNC_CONTACTS)
13329ac761feSbru 	    || (input->touch.contacts > 1)) {
13339ac761feSbru 		input->intv.track = 0;
13349ac761feSbru 		return;
13359ac761feSbru 	}
13369ac761feSbru 	if (input->intv.track) {
13379ac761feSbru 		timespecsub(time, &input->intv.ts, &ts);
13389ac761feSbru 		if (timespeccmp(&ts, &limit, <)) {
13399ac761feSbru 			/* The unit of the sum is 4096 nanoseconds. */
13409ac761feSbru 			input->intv.sum += ts.tv_nsec >> 12;
13419ac761feSbru 			samples = ++input->intv.samples;
13429ac761feSbru 			/*
13439ac761feSbru 			 * Make the first calculation quickly and later
13449ac761feSbru 			 * a more reliable one:
13459ac761feSbru 			 */
13469ac761feSbru 			if (samples == 8) {
13479ac761feSbru 				input->intv.avg = input->intv.sum << 9;
13489ac761feSbru 				wstpad_init_deceleration(input);
13499ac761feSbru 			} else if (samples == 128) {
13509ac761feSbru 				input->intv.avg = input->intv.sum << 5;
13519ac761feSbru 				wstpad_init_deceleration(input);
13529ac761feSbru 				input->intv.samples = 0;
13539ac761feSbru 				input->intv.sum = 0;
13549ac761feSbru 				input->flags &= ~TRACK_INTERVAL;
13559ac761feSbru 			}
13569ac761feSbru 		}
13579ac761feSbru 	}
13589ac761feSbru 	memcpy(&input->intv.ts, time, sizeof(struct timespec));
13599ac761feSbru 	input->intv.track = 1;
13609ac761feSbru }
13619ac761feSbru 
1362c61b9928Sbru 
13639ac761feSbru 
13649ac761feSbru /*
13659ac761feSbru  * The default acceleration options of X don't work convincingly with
13669ac761feSbru  * touchpads (the synaptics driver installs its own "acceleration
13679ac761feSbru  * profile" and callback function). As a preliminary workaround, this
1368c06ae661Sbru  * filter applies a simple deceleration scheme to small deltas, based
1369c06ae661Sbru  * on the "magnitude" of the delta pair. A magnitude of 8 corresponds,
13709ac761feSbru  * roughly, to a speed of (filter.dclr / 12.5) device units per milli-
13719ac761feSbru  * second. If its magnitude is smaller than 7 a delta will be downscaled
13729ac761feSbru  * by the factor 2/8, deltas with magnitudes from 7 to 11 by factors
13739ac761feSbru  * ranging from 3/8 to 7/8.
13749ac761feSbru  */
1375c61b9928Sbru int
wstpad_decelerate(struct wsmouseinput * input,int * dx,int * dy)13769ac761feSbru wstpad_decelerate(struct wsmouseinput *input, int *dx, int *dy)
13779ac761feSbru {
1378c06ae661Sbru 	int mag, n, h, v;
1379c06ae661Sbru 
1380c06ae661Sbru 	mag = magnitude(input, *dx, *dy);
13819ac761feSbru 
13829ac761feSbru 	/* Don't change deceleration levels abruptly. */
13839ac761feSbru 	mag = (mag + 7 * input->filter.mag) / 8;
13849ac761feSbru 	/* Don't use arbitrarily high values. */
13859ac761feSbru 	input->filter.mag = imin(mag, 24 << 12);
13869ac761feSbru 
13879ac761feSbru 	n = imax((mag >> 12) - 4, 2);
13889ac761feSbru 	if (n < 8) {
13899ac761feSbru 		/* Scale by (n / 8). */
13909ac761feSbru 		h = *dx * n + input->filter.h.dclr_rmdr;
13919ac761feSbru 		v = *dy * n + input->filter.v.dclr_rmdr;
13929ac761feSbru 		input->filter.h.dclr_rmdr = (h >= 0 ? h & 7 : -(-h & 7));
13939ac761feSbru 		input->filter.v.dclr_rmdr = (v >= 0 ? v & 7 : -(-v & 7));
13949ac761feSbru 		*dx = h / 8;
13959ac761feSbru 		*dy = v / 8;
1396c61b9928Sbru 		return (1);
1397c61b9928Sbru 	}
1398c61b9928Sbru 	return (0);
1399c61b9928Sbru }
1400c61b9928Sbru 
1401c61b9928Sbru void
wstpad_filter(struct wsmouseinput * input)1402509ce00cSbru wstpad_filter(struct wsmouseinput *input)
1403c61b9928Sbru {
1404c61b9928Sbru 	struct axis_filter *h = &input->filter.h;
1405c61b9928Sbru 	struct axis_filter *v = &input->filter.v;
1406509ce00cSbru 	struct position *pos = &input->motion.pos;
1407c61b9928Sbru 	int strength = input->filter.mode & 7;
1408509ce00cSbru 	int dx, dy;
1409c61b9928Sbru 
1410509ce00cSbru 	if (!(input->motion.sync & SYNC_POSITION)
1411509ce00cSbru 	    || (h->dmax && (abs(pos->dx) > h->dmax))
1412c06ae661Sbru 	    || (v->dmax && (abs(pos->dy) > v->dmax))) {
1413c06ae661Sbru 		dx = dy = 0;
1414c06ae661Sbru 	} else {
1415509ce00cSbru 		dx = pos->dx;
1416509ce00cSbru 		dy = pos->dy;
1417c06ae661Sbru 	}
1418509ce00cSbru 
141989ee92dcSbru 	if (wsmouse_hysteresis(input, pos))
1420509ce00cSbru 		dx = dy = 0;
1421c61b9928Sbru 
1422509ce00cSbru 	if (input->filter.dclr && wstpad_decelerate(input, &dx, &dy))
1423c61b9928Sbru 		/* Strong smoothing may hamper the precision at low speeds. */
1424c61b9928Sbru 		strength = imin(strength, 2);
1425c61b9928Sbru 
1426c61b9928Sbru 	if (strength) {
1427509ce00cSbru 		if ((input->touch.sync & SYNC_CONTACTS)
1428509ce00cSbru 		    || input->mt.ptr != input->mt.prev_ptr) {
1429509ce00cSbru 			h->avg = v->avg = 0;
14309ac761feSbru 		}
1431509ce00cSbru 		/* Use a weighted decaying average for smoothing. */
1432509ce00cSbru 		dx = dx * (8 - strength) + h->avg * strength + h->avg_rmdr;
1433509ce00cSbru 		dy = dy * (8 - strength) + v->avg * strength + v->avg_rmdr;
1434509ce00cSbru 		h->avg_rmdr = (dx >= 0 ? dx & 7 : -(-dx & 7));
1435509ce00cSbru 		v->avg_rmdr = (dy >= 0 ? dy & 7 : -(-dy & 7));
1436509ce00cSbru 		dx = h->avg = dx / 8;
1437509ce00cSbru 		dy = v->avg = dy / 8;
1438509ce00cSbru 	}
1439509ce00cSbru 
1440509ce00cSbru 	input->motion.dx = dx;
1441509ce00cSbru 	input->motion.dy = dy;
14429ac761feSbru }
14439ac761feSbru 
1444c61b9928Sbru 
14459ac761feSbru /*
1446509ce00cSbru  * Compatibility-mode conversions. wstpad_filter transforms and filters
14479ac761feSbru  * the coordinate inputs, extended functionality is provided by
14489ac761feSbru  * wstpad_process_input.
14499ac761feSbru  */
14509ac761feSbru void
wstpad_compat_convert(struct wsmouseinput * input,struct evq_access * evq)14519ac761feSbru wstpad_compat_convert(struct wsmouseinput *input, struct evq_access *evq)
14529ac761feSbru {
14539ac761feSbru 	if (input->flags & TRACK_INTERVAL)
14549ac761feSbru 		wstpad_track_interval(input, &evq->ts);
14559ac761feSbru 
1456509ce00cSbru 	wstpad_filter(input);
14579ac761feSbru 
1458509ce00cSbru 	if ((input->motion.dx || input->motion.dy)
1459509ce00cSbru 	    && !(input->motion.sync & SYNC_DELTAS)) {
14609ac761feSbru 		input->motion.dz = input->motion.dw = 0;
14619ac761feSbru 		input->motion.sync |= SYNC_DELTAS;
14629ac761feSbru 	}
14639ac761feSbru 
14649ac761feSbru 	if (input->tp != NULL)
14659ac761feSbru 		wstpad_process_input(input, evq);
14669ac761feSbru 
14679ac761feSbru 	input->motion.sync &= ~SYNC_POSITION;
14689ac761feSbru 	input->touch.sync = 0;
14699ac761feSbru }
14709ac761feSbru 
14719ac761feSbru int
wstpad_init(struct wsmouseinput * input)14729ac761feSbru wstpad_init(struct wsmouseinput *input)
14739ac761feSbru {
14749ac761feSbru 	struct wstpad *tp = input->tp;
14751a6243b7Sbru 	int i, slots;
14769ac761feSbru 
14779ac761feSbru 	if (tp != NULL)
14789ac761feSbru 		return (0);
14799ac761feSbru 
14809ac761feSbru 	input->tp = tp = malloc(sizeof(struct wstpad),
14819ac761feSbru 	    M_DEVBUF, M_WAITOK | M_ZERO);
14829ac761feSbru 	if (tp == NULL)
14839ac761feSbru 		return (-1);
14849ac761feSbru 
14859ac761feSbru 	slots = imax(input->mt.num_slots, 1);
14869ac761feSbru 	tp->tpad_touches = malloc(slots * sizeof(struct tpad_touch),
14879ac761feSbru 	    M_DEVBUF, M_WAITOK | M_ZERO);
14889ac761feSbru 	if (tp->tpad_touches == NULL) {
14899ac761feSbru 		free(tp, M_DEVBUF, sizeof(struct wstpad));
14909ac761feSbru 		return (-1);
14919ac761feSbru 	}
14929ac761feSbru 
14939ac761feSbru 	tp->t = &tp->tpad_touches[0];
14941a6243b7Sbru 	if (input->mt.num_slots) {
14959ac761feSbru 		tp->features |= WSTPAD_MT;
14961a6243b7Sbru 		for (i = 0; i < input->mt.num_slots; i++)
14971a6243b7Sbru 			tp->tpad_touches[i].pos = &input->mt.slots[i].pos;
14981a6243b7Sbru 	} else {
14991a6243b7Sbru 		tp->t->pos = &input->motion.pos;
15001a6243b7Sbru 	}
15019ac761feSbru 
150231a33b6eSbru 	timeout_set(&tp->tap.to, wstpad_tap_timeout, input);
150331a33b6eSbru 
15049ac761feSbru 	tp->ratio = input->filter.ratio;
15059ac761feSbru 
15069ac761feSbru 	return (0);
15079ac761feSbru }
15089ac761feSbru 
15099ac761feSbru /*
15109ac761feSbru  * Integer square root (Halleck's method)
15119ac761feSbru  *
15129ac761feSbru  * An adaption of code from John B. Halleck (from
15139ac761feSbru  * http://www.cc.utah.edu/~nahaj/factoring/code.html). This version is
15149ac761feSbru  * used and published under the OpenBSD license terms with his permission.
15159ac761feSbru  *
15169ac761feSbru  * Cf. also Martin Guy's "Square root by abacus" method.
15179ac761feSbru  */
15181f4c8d79Smpi static inline u_int
isqrt(u_int n)15199ac761feSbru isqrt(u_int n)
15209ac761feSbru {
15219ac761feSbru 	u_int root, sqbit;
15229ac761feSbru 
15239ac761feSbru 	root = 0;
15249ac761feSbru 	sqbit = 1 << (sizeof(u_int) * 8 - 2);
15259ac761feSbru 	while (sqbit) {
15269ac761feSbru 		if (n >= (sqbit | root)) {
15279ac761feSbru 			n -= (sqbit | root);
15289ac761feSbru 			root = (root >> 1) | sqbit;
15299ac761feSbru 		} else {
15309ac761feSbru 			root >>= 1;
15319ac761feSbru 		}
15329ac761feSbru 		sqbit >>= 2;
15339ac761feSbru 	}
15349ac761feSbru 	return (root);
15359ac761feSbru }
15369ac761feSbru 
15379ac761feSbru void
wstpad_init_deceleration(struct wsmouseinput * input)15389ac761feSbru wstpad_init_deceleration(struct wsmouseinput *input)
15399ac761feSbru {
15409ac761feSbru 	int n, dclr;
15419ac761feSbru 
15429ac761feSbru 	if ((dclr = input->filter.dclr) == 0)
15439ac761feSbru 		return;
15449ac761feSbru 
15459ac761feSbru 	dclr = imax(dclr, 4);
15469ac761feSbru 
15479ac761feSbru 	/*
15489ac761feSbru 	 * For a standard update rate of about 80Hz, (dclr) units
15499ac761feSbru 	 * will be mapped to a magnitude of 8. If the average rate
15509ac761feSbru 	 * is significantly higher or lower, adjust the coefficient
15519ac761feSbru 	 * accordingly:
15529ac761feSbru 	 */
15539ac761feSbru 	if (input->intv.avg == 0) {
15549ac761feSbru 		n = 8;
15559ac761feSbru 	} else {
15569ac761feSbru 		n = 8 * 13000000 / input->intv.avg;
15579ac761feSbru 		n = imax(imin(n, 32), 4);
15589ac761feSbru 	}
15599ac761feSbru 	input->filter.h.mag_scale = (n << 12) / dclr;
15609ac761feSbru 	input->filter.v.mag_scale = (input->filter.ratio ?
15619ac761feSbru 	    n * input->filter.ratio : n << 12) / dclr;
15629ac761feSbru 	input->filter.h.dclr_rmdr = 0;
15639ac761feSbru 	input->filter.v.dclr_rmdr = 0;
15649ac761feSbru 	input->flags |= TRACK_INTERVAL;
15659ac761feSbru }
15669ac761feSbru 
15679ac761feSbru int
wstpad_configure(struct wsmouseinput * input)15689ac761feSbru wstpad_configure(struct wsmouseinput *input)
15699ac761feSbru {
15709ac761feSbru 	struct wstpad *tp;
1571b1ba6288Sbru 	int width, height, diag, offset, h_res, v_res, h_unit, v_unit, i;
15729ac761feSbru 
15739ac761feSbru 	width = abs(input->hw.x_max - input->hw.x_min);
15749ac761feSbru 	height = abs(input->hw.y_max - input->hw.y_min);
15759ac761feSbru 	if (width == 0 || height == 0)
15769ac761feSbru 		return (-1);	/* We can't do anything. */
15779ac761feSbru 
15789ac761feSbru 	if (input->tp == NULL && wstpad_init(input))
15799ac761feSbru 		return (-1);
15809ac761feSbru 	tp = input->tp;
15819ac761feSbru 
15829ac761feSbru 	if (!(input->flags & CONFIGURED)) {
1583397249a6Sbru 		/*
1584397249a6Sbru 		 * The filter parameters are derived from the length of the
1585397249a6Sbru 		 * diagonal in device units, with some magic constants which
1586397249a6Sbru 		 * are partly adapted from libinput or synaptics code, or are
1587397249a6Sbru 		 * based on tests and guess work.  The absolute resolution
1588397249a6Sbru 		 * values might not be reliable, but if they are present the
1589397249a6Sbru 	         * settings are adapted to the ratio.
1590397249a6Sbru 		 */
1591397249a6Sbru 		h_res = input->hw.h_res;
1592397249a6Sbru 		v_res = input->hw.v_res;
1593397249a6Sbru 		if (h_res == 0 || v_res == 0)
1594397249a6Sbru 			h_res = v_res = 1;
1595397249a6Sbru 		diag = isqrt(width * width + height * height);
15969ac761feSbru 		input->filter.h.scale = (imin(920, diag) << 12) / diag;
1597397249a6Sbru 		input->filter.v.scale = input->filter.h.scale * h_res / v_res;
1598397249a6Sbru 		h_unit = imax(diag / 280, 3);
1599397249a6Sbru 		v_unit = imax((h_unit * v_res + h_res / 2) / h_res, 3);
16009ac761feSbru 		input->filter.h.hysteresis = h_unit;
16019ac761feSbru 		input->filter.v.hysteresis = v_unit;
1602c61b9928Sbru 		input->filter.mode = FILTER_MODE_DEFAULT;
16039ac761feSbru 		input->filter.dclr = h_unit - h_unit / 5;
16049ac761feSbru 		wstpad_init_deceleration(input);
16059ac761feSbru 
16069ac761feSbru 		tp->features &= (WSTPAD_MT | WSTPAD_DISABLE);
16071ad0085eSbru 
16081ad0085eSbru 		if (input->hw.contacts_max != 1)
16091ad0085eSbru 			tp->features |= WSTPAD_TWOFINGERSCROLL;
16109ac761feSbru 		else
16119ac761feSbru 			tp->features |= WSTPAD_EDGESCROLL;
16121ad0085eSbru 
16131ad0085eSbru 		if (input->hw.hw_type == WSMOUSEHW_CLICKPAD) {
16148de0db29Sbru 			if (input->hw.type == WSMOUSE_TYPE_SYNAP_SBTN) {
16158de0db29Sbru 				tp->features |= WSTPAD_TOPBUTTONS;
16161ad0085eSbru 			} else {
16171ad0085eSbru 				tp->features |= WSTPAD_SOFTBUTTONS;
16181ad0085eSbru 				tp->features |= WSTPAD_SOFTMBTN;
16191ad0085eSbru 			}
16208de0db29Sbru 		}
1621757db27eSbru 
16228de0db29Sbru 		tp->params.left_edge = V_EDGE_RATIO_DEFAULT;
16238de0db29Sbru 		tp->params.right_edge = V_EDGE_RATIO_DEFAULT;
16241ad0085eSbru 		tp->params.bottom_edge = ((tp->features & WSTPAD_SOFTBUTTONS)
16251ad0085eSbru 		    ? B_EDGE_RATIO_DEFAULT : 0);
16261ad0085eSbru 		tp->params.top_edge = ((tp->features & WSTPAD_TOPBUTTONS)
16271ad0085eSbru 		    ? T_EDGE_RATIO_DEFAULT : 0);
16281ad0085eSbru 		tp->params.center_width = CENTER_RATIO_DEFAULT;
16291ad0085eSbru 
1630757db27eSbru 		tp->tap.maxtime.tv_nsec = TAP_MAXTIME_DEFAULT * 1000000;
1631757db27eSbru 		tp->tap.clicktime = TAP_CLICKTIME_DEFAULT;
1632757db27eSbru 		tp->tap.locktime = TAP_LOCKTIME_DEFAULT;
1633757db27eSbru 
1634584b8e02Sbru 		tp->scroll.hdist = 4 * h_unit;
1635584b8e02Sbru 		tp->scroll.vdist = 4 * v_unit;
16368de0db29Sbru 		tp->tap.maxdist = 4 * h_unit;
16379310c18aSbru 
16389310c18aSbru 		if (IS_MT(tp) && h_res > 1 && v_res > 1 &&
16399310c18aSbru 		    input->hw.hw_type == WSMOUSEHW_CLICKPAD &&
16409310c18aSbru 		    (width + h_res / 2) / h_res > 100 &&
16419310c18aSbru 		    (height + v_res / 2) / v_res > 60) {
16429310c18aSbru 			tp->params.mtbtn_maxdist = h_res * 35;
16439310c18aSbru 		} else {
16449310c18aSbru 			tp->params.mtbtn_maxdist = -1; /* not available */
16459310c18aSbru 		}
16469ac761feSbru 	}
16479ac761feSbru 
16480f19fc80Sbru 	/* A touch with a flag set in this mask does not move the pointer. */
16498de0db29Sbru 	tp->freeze = EDGES;
16500f19fc80Sbru 
16511ad0085eSbru 	offset = width * tp->params.left_edge / 4096;
1652509ce00cSbru 	tp->edge.left = (offset ? input->hw.x_min + offset : INT_MIN);
16531ad0085eSbru 	offset = width * tp->params.right_edge / 4096;
1654509ce00cSbru 	tp->edge.right = (offset ? input->hw.x_max - offset : INT_MAX);
16551ad0085eSbru 	offset = height * tp->params.bottom_edge / 4096;
1656509ce00cSbru 	tp->edge.bottom = (offset ? input->hw.y_min + offset : INT_MIN);
1657c06ae661Sbru 	tp->edge.low = tp->edge.bottom + offset / 2;
16581ad0085eSbru 	offset = height * tp->params.top_edge / 4096;
1659509ce00cSbru 	tp->edge.top = (offset ? input->hw.y_max - offset : INT_MAX);
1660727566e0Sbru 
16611ad0085eSbru 	offset = width * abs(tp->params.center_width) / 8192;
1662c06ae661Sbru 	tp->edge.center = input->hw.x_min + width / 2;
16631ad0085eSbru 	tp->edge.center_left = tp->edge.center - offset;
16641ad0085eSbru 	tp->edge.center_right = tp->edge.center + offset;
16659ac761feSbru 
16669310c18aSbru 	/*
16679310c18aSbru 	 * Make the MTBUTTONS configuration consistent.  A non-negative 'maxdist'
16689310c18aSbru 	 * value makes the feature visible in wsconsctl.  0-values are replaced
16699310c18aSbru 	 * by a default (one fourth of the length of the touchpad diagonal).
16709310c18aSbru 	 */
16719310c18aSbru 	if (tp->params.mtbtn_maxdist < 0) {
16729310c18aSbru 		tp->features &= ~WSTPAD_MTBUTTONS;
16739310c18aSbru 	} else if (tp->params.mtbtn_maxdist == 0) {
16749310c18aSbru 		diag = isqrt(width * width + height * height);
16759310c18aSbru 		tp->params.mtbtn_maxdist = diag / 4;
16769310c18aSbru 	}
16779310c18aSbru 
16789ac761feSbru 	tp->handlers = 0;
16799ac761feSbru 
16809310c18aSbru 	if (tp->features & (WSTPAD_SOFTBUTTONS | WSTPAD_MTBUTTONS))
16819ac761feSbru 		tp->handlers |= 1 << SOFTBUTTON_HDLR;
1682757db27eSbru 	if (tp->features & WSTPAD_TOPBUTTONS)
16839ac761feSbru 		tp->handlers |= 1 << TOPBUTTON_HDLR;
1684757db27eSbru 	if (tp->features & WSTPAD_TWOFINGERSCROLL)
16859ac761feSbru 		tp->handlers |= 1 << F2SCROLL_HDLR;
1686757db27eSbru 	else if (tp->features & WSTPAD_EDGESCROLL)
16879ac761feSbru 		tp->handlers |= 1 << EDGESCROLL_HDLR;
16889ac761feSbru 
1689b1ba6288Sbru 	for (i = 0; i < TAP_BTNMAP_SIZE; i++) {
1690b1ba6288Sbru 		if (tp->tap.btnmap[i] == 0)
1691b1ba6288Sbru 			continue;
1692b1ba6288Sbru 
169331a33b6eSbru 		tp->tap.clicktime = imin(imax(tp->tap.clicktime, 80), 350);
169431a33b6eSbru 		if (tp->tap.locktime)
169531a33b6eSbru 			tp->tap.locktime =
169631a33b6eSbru 			    imin(imax(tp->tap.locktime, 150), 5000);
169731a33b6eSbru 		tp->handlers |= 1 << TAP_HDLR;
1698b1ba6288Sbru 		break;
169931a33b6eSbru 	}
170031a33b6eSbru 
17019ac761feSbru 	if (input->hw.hw_type == WSMOUSEHW_CLICKPAD)
17029ac761feSbru 		tp->handlers |= 1 << CLICK_HDLR;
17039ac761feSbru 
17049ac761feSbru 	tp->sbtnswap = ((tp->features & WSTPAD_SWAPSIDES)
17059ac761feSbru 	    ? (LEFTBTN | RIGHTBTN) : 0);
17069ac761feSbru 
17079ac761feSbru 	return (0);
17089ac761feSbru }
17099ac761feSbru 
17109ac761feSbru void
wstpad_reset(struct wsmouseinput * input)17119ac761feSbru wstpad_reset(struct wsmouseinput *input)
17129ac761feSbru {
171331a33b6eSbru 	struct wstpad *tp = input->tp;
171431a33b6eSbru 
171531a33b6eSbru 	if (tp != NULL) {
171631a33b6eSbru 		timeout_del(&tp->tap.to);
171731a33b6eSbru 		tp->tap.state = TAP_DETECT;
171831a33b6eSbru 	}
171931a33b6eSbru 
17209ac761feSbru 	if (input->sbtn.buttons) {
17219ac761feSbru 		input->sbtn.sync = input->sbtn.buttons;
17229ac761feSbru 		input->sbtn.buttons = 0;
17239ac761feSbru 	}
17249ac761feSbru }
17259ac761feSbru 
17261ab10031Sbru void
wstpad_cleanup(struct wsmouseinput * input)17271ab10031Sbru wstpad_cleanup(struct wsmouseinput *input)
17281ab10031Sbru {
17291ab10031Sbru 	struct wstpad *tp = input->tp;
17301ab10031Sbru 	int slots;
17311ab10031Sbru 
17321ab10031Sbru 	timeout_del(&tp->tap.to);
17331ab10031Sbru 	slots = imax(input->mt.num_slots, 1);
17341ab10031Sbru 	free(tp->tpad_touches, M_DEVBUF, slots * sizeof(struct tpad_touch));
17351ab10031Sbru 	free(tp, M_DEVBUF, sizeof(struct wstpad));
17361ab10031Sbru 	input->tp = NULL;
17371ab10031Sbru }
17381ab10031Sbru 
17399ac761feSbru int
wstpad_set_param(struct wsmouseinput * input,int key,int val)174057251937Smpi wstpad_set_param(struct wsmouseinput *input, int key, int val)
17419ac761feSbru {
17429ac761feSbru 	struct wstpad *tp = input->tp;
17439ac761feSbru 	u_int flag;
17449ac761feSbru 
17459ac761feSbru 	if (tp == NULL)
174657251937Smpi 		return (EINVAL);
17479ac761feSbru 
174857251937Smpi 	switch (key) {
17499310c18aSbru 	case WSMOUSECFG_SOFTBUTTONS ... WSMOUSECFG_MTBUTTONS:
175057251937Smpi 		switch (key) {
175157251937Smpi 		case WSMOUSECFG_SOFTBUTTONS:
175257251937Smpi 			flag = WSTPAD_SOFTBUTTONS;
175357251937Smpi 			break;
175457251937Smpi 		case WSMOUSECFG_SOFTMBTN:
175557251937Smpi 			flag = WSTPAD_SOFTMBTN;
175657251937Smpi 			break;
175757251937Smpi 		case WSMOUSECFG_TOPBUTTONS:
175857251937Smpi 			flag = WSTPAD_TOPBUTTONS;
175957251937Smpi 			break;
176057251937Smpi 		case WSMOUSECFG_TWOFINGERSCROLL:
176157251937Smpi 			flag = WSTPAD_TWOFINGERSCROLL;
176257251937Smpi 			break;
176357251937Smpi 		case WSMOUSECFG_EDGESCROLL:
176457251937Smpi 			flag = WSTPAD_EDGESCROLL;
176557251937Smpi 			break;
176657251937Smpi 		case WSMOUSECFG_HORIZSCROLL:
176757251937Smpi 			flag = WSTPAD_HORIZSCROLL;
176857251937Smpi 			break;
176957251937Smpi 		case WSMOUSECFG_SWAPSIDES:
177057251937Smpi 			flag = WSTPAD_SWAPSIDES;
177157251937Smpi 			break;
177257251937Smpi 		case WSMOUSECFG_DISABLE:
177357251937Smpi 			flag = WSTPAD_DISABLE;
177457251937Smpi 			break;
17759310c18aSbru 		case WSMOUSECFG_MTBUTTONS:
17769310c18aSbru 			flag = WSTPAD_MTBUTTONS;
17779310c18aSbru 			break;
177857251937Smpi 		}
17799ac761feSbru 		if (val)
17809ac761feSbru 			tp->features |= flag;
17819ac761feSbru 		else
17829ac761feSbru 			tp->features &= ~flag;
178357251937Smpi 		break;
178457251937Smpi 	case WSMOUSECFG_LEFT_EDGE:
178557251937Smpi 		tp->params.left_edge = val;
178657251937Smpi 		break;
178757251937Smpi 	case WSMOUSECFG_RIGHT_EDGE:
178857251937Smpi 		tp->params.right_edge = val;
178957251937Smpi 		break;
179057251937Smpi 	case WSMOUSECFG_TOP_EDGE:
179157251937Smpi 		tp->params.top_edge = val;
179257251937Smpi 		break;
179357251937Smpi 	case WSMOUSECFG_BOTTOM_EDGE:
179457251937Smpi 		tp->params.bottom_edge = val;
179557251937Smpi 		break;
179657251937Smpi 	case WSMOUSECFG_CENTERWIDTH:
179757251937Smpi 		tp->params.center_width = val;
179857251937Smpi 		break;
179957251937Smpi 	case WSMOUSECFG_HORIZSCROLLDIST:
180057251937Smpi 		tp->scroll.hdist = val;
180157251937Smpi 		break;
180257251937Smpi 	case WSMOUSECFG_VERTSCROLLDIST:
180357251937Smpi 		tp->scroll.vdist = val;
180457251937Smpi 		break;
180557251937Smpi 	case WSMOUSECFG_F2WIDTH:
180657251937Smpi 		tp->params.f2width = val;
180757251937Smpi 		break;
180857251937Smpi 	case WSMOUSECFG_F2PRESSURE:
180957251937Smpi 		tp->params.f2pressure = val;
181057251937Smpi 		break;
181131a33b6eSbru 	case WSMOUSECFG_TAP_MAXTIME:
181231a33b6eSbru 		tp->tap.maxtime.tv_nsec = imin(val, 999) * 1000000;
181331a33b6eSbru 		break;
181431a33b6eSbru 	case WSMOUSECFG_TAP_CLICKTIME:
181531a33b6eSbru 		tp->tap.clicktime = val;
181631a33b6eSbru 		break;
181731a33b6eSbru 	case WSMOUSECFG_TAP_LOCKTIME:
181831a33b6eSbru 		tp->tap.locktime = val;
181931a33b6eSbru 		break;
1820b1ba6288Sbru 	case WSMOUSECFG_TAP_ONE_BTNMAP:
1821b1ba6288Sbru 		tp->tap.btnmap[0] = BTNMASK(val);
1822b1ba6288Sbru 		break;
1823b1ba6288Sbru 	case WSMOUSECFG_TAP_TWO_BTNMAP:
1824b1ba6288Sbru 		tp->tap.btnmap[1] = BTNMASK(val);
1825b1ba6288Sbru 		break;
1826b1ba6288Sbru 	case WSMOUSECFG_TAP_THREE_BTNMAP:
1827b1ba6288Sbru 		tp->tap.btnmap[2] = BTNMASK(val);
1828b1ba6288Sbru 		break;
18299310c18aSbru 	case WSMOUSECFG_MTBTN_MAXDIST:
18309310c18aSbru 		if (IS_MT(tp))
18319310c18aSbru 			tp->params.mtbtn_maxdist = val;
18329310c18aSbru 		break;
183357251937Smpi 	default:
183457251937Smpi 		return (ENOTSUP);
18359ac761feSbru 	}
183657251937Smpi 
183757251937Smpi 	return (0);
18389ac761feSbru }
18399ac761feSbru 
18409ac761feSbru int
wstpad_get_param(struct wsmouseinput * input,int key,int * pval)184157251937Smpi wstpad_get_param(struct wsmouseinput *input, int key, int *pval)
18429ac761feSbru {
18439ac761feSbru 	struct wstpad *tp = input->tp;
18449ac761feSbru 	u_int flag;
18459ac761feSbru 
18469ac761feSbru 	if (tp == NULL)
184757251937Smpi 		return (EINVAL);
184857251937Smpi 
184957251937Smpi 	switch (key) {
18509310c18aSbru 	case WSMOUSECFG_SOFTBUTTONS ... WSMOUSECFG_MTBUTTONS:
185157251937Smpi 		switch (key) {
185257251937Smpi 		case WSMOUSECFG_SOFTBUTTONS:
185357251937Smpi 			flag = WSTPAD_SOFTBUTTONS;
185457251937Smpi 			break;
185557251937Smpi 		case WSMOUSECFG_SOFTMBTN:
185657251937Smpi 			flag = WSTPAD_SOFTMBTN;
185757251937Smpi 			break;
185857251937Smpi 		case WSMOUSECFG_TOPBUTTONS:
185957251937Smpi 			flag = WSTPAD_TOPBUTTONS;
186057251937Smpi 			break;
186157251937Smpi 		case WSMOUSECFG_TWOFINGERSCROLL:
186257251937Smpi 			flag = WSTPAD_TWOFINGERSCROLL;
186357251937Smpi 			break;
186457251937Smpi 		case WSMOUSECFG_EDGESCROLL:
186557251937Smpi 			flag = WSTPAD_EDGESCROLL;
186657251937Smpi 			break;
186757251937Smpi 		case WSMOUSECFG_HORIZSCROLL:
186857251937Smpi 			flag = WSTPAD_HORIZSCROLL;
186957251937Smpi 			break;
187057251937Smpi 		case WSMOUSECFG_SWAPSIDES:
187157251937Smpi 			flag = WSTPAD_SWAPSIDES;
187257251937Smpi 			break;
187357251937Smpi 		case WSMOUSECFG_DISABLE:
187457251937Smpi 			flag = WSTPAD_DISABLE;
187557251937Smpi 			break;
18769310c18aSbru 		case WSMOUSECFG_MTBUTTONS:
18779310c18aSbru 			flag = WSTPAD_MTBUTTONS;
18789310c18aSbru 			break;
18799ac761feSbru 		}
188057251937Smpi 		*pval = !!(tp->features & flag);
188157251937Smpi 		break;
188257251937Smpi 	case WSMOUSECFG_LEFT_EDGE:
188357251937Smpi 		*pval = tp->params.left_edge;
188457251937Smpi 		break;
188557251937Smpi 	case WSMOUSECFG_RIGHT_EDGE:
188657251937Smpi 		*pval = tp->params.right_edge;
188757251937Smpi 		break;
188857251937Smpi 	case WSMOUSECFG_TOP_EDGE:
188957251937Smpi 		*pval = tp->params.top_edge;
189057251937Smpi 		break;
189157251937Smpi 	case WSMOUSECFG_BOTTOM_EDGE:
189257251937Smpi 		*pval = tp->params.bottom_edge;
189357251937Smpi 		break;
189457251937Smpi 	case WSMOUSECFG_CENTERWIDTH:
189557251937Smpi 		*pval = tp->params.center_width;
189657251937Smpi 		break;
189757251937Smpi 	case WSMOUSECFG_HORIZSCROLLDIST:
189857251937Smpi 		*pval = tp->scroll.hdist;
189957251937Smpi 		break;
190057251937Smpi 	case WSMOUSECFG_VERTSCROLLDIST:
190157251937Smpi 		*pval = tp->scroll.vdist;
190257251937Smpi 		break;
190357251937Smpi 	case WSMOUSECFG_F2WIDTH:
190457251937Smpi 		*pval = tp->params.f2width;
190557251937Smpi 		break;
190657251937Smpi 	case WSMOUSECFG_F2PRESSURE:
190757251937Smpi 		*pval = tp->params.f2pressure;
190857251937Smpi 		break;
190931a33b6eSbru 	case WSMOUSECFG_TAP_MAXTIME:
191031a33b6eSbru 		*pval = tp->tap.maxtime.tv_nsec / 1000000;
191131a33b6eSbru 		break;
191231a33b6eSbru 	case WSMOUSECFG_TAP_CLICKTIME:
191331a33b6eSbru 		*pval = tp->tap.clicktime;
191431a33b6eSbru 		break;
191531a33b6eSbru 	case WSMOUSECFG_TAP_LOCKTIME:
191631a33b6eSbru 		*pval = tp->tap.locktime;
191731a33b6eSbru 		break;
1918b1ba6288Sbru 	case WSMOUSECFG_TAP_ONE_BTNMAP:
1919b1ba6288Sbru 		*pval = ffs(tp->tap.btnmap[0]);
1920b1ba6288Sbru 		break;
1921b1ba6288Sbru 	case WSMOUSECFG_TAP_TWO_BTNMAP:
1922b1ba6288Sbru 		*pval = ffs(tp->tap.btnmap[1]);
1923b1ba6288Sbru 		break;
1924b1ba6288Sbru 	case WSMOUSECFG_TAP_THREE_BTNMAP:
1925b1ba6288Sbru 		*pval = ffs(tp->tap.btnmap[2]);
1926b1ba6288Sbru 		break;
19279310c18aSbru 	case WSMOUSECFG_MTBTN_MAXDIST:
19289310c18aSbru 		*pval = tp->params.mtbtn_maxdist;
19299310c18aSbru 		break;
193057251937Smpi 	default:
193157251937Smpi 		return (ENOTSUP);
19329ac761feSbru 	}
19339ac761feSbru 
19349ac761feSbru 	return (0);
19359ac761feSbru }
1936