The implementation of the tapping mechanism in wsmouse(4) has a bug
concerning the transitions into the hold/drag state, see
https://marc.info/...
for details. The patch proposed in that message is obsolete. I've
been made aware that there is another problem, the transition only
works with left-button taps.
This patch merges tap detection and the handling of hold/drag states,
which is a cleaner and more generic approach. It fixes the problems
mentioned above, and it permits a better synchronization of button
states when tap inputs and the use of hardware buttons are mixed.
OK?
Index: dev/wscons/wstpad.c
===================================================================
RCS file: /cvs/src/sys/dev/wscons/wstpad.c,v
retrieving revision 1.30
diff -u -p -r1.30 wstpad.c
--- dev/wscons/wstpad.c 24 Mar 2021 18:28:24 -0000 1.30
+++ dev/wscons/wstpad.c 2 May 2022 22:17:57 -0000
@@ -82,18 +82,17 @@ enum tap_state {
TAP_DETECT,
TAP_IGNORE,
TAP_LIFTED,
- TAP_2ND_TOUCH,
TAP_LOCKED,
- TAP_NTH_TOUCH,
+ TAP_LOCKED_DRAG,
};
enum tpad_cmd {
CLEAR_MOTION_DELTAS,
SOFTBUTTON_DOWN,
SOFTBUTTON_UP,
+ TAPBUTTON_SYNC,
TAPBUTTON_DOWN,
TAPBUTTON_UP,
- TAPBUTTON_DOUBLECLK,
VSCROLL,
HSCROLL,
};
@@ -212,8 +211,10 @@ struct wstpad {
struct {
enum tap_state state;
int contacts;
- int centered;
+ int valid;
+ u_int pending;
u_int button;
+ int masked;
int maxdist;
struct timeout to;
/* parameters: */
@@ -651,6 +652,7 @@ wstpad_softbuttons(struct wsmouseinput *
}
}
+/* Check whether the duration of t is within the tap limit. */
int
wstpad_is_tap(struct wstpad *tp, struct tpad_touch *t)
{
@@ -675,7 +677,7 @@ wstpad_tap_filter(struct wstpad *tp, str
dy = abs(t->y - t->orig.y) * tp->ratio;
dist = (dx >= dy ? dx + 3 * dy / 8 : dy + 3 * dx / 8);
}
- tp->tap.centered = (CENTERED(t) && dist <= (tp->tap.maxdist << 12));
+ tp->tap.valid = (CENTERED(t) && dist <= (tp->tap.maxdist << 12));
}
@@ -694,7 +696,7 @@ wstpad_tap_touch(struct wsmouseinput *in
lifted = (input->mt.sync[MTS_TOUCH] & ~input->mt.touches);
FOREACHBIT(lifted, slot) {
s = &tp->tpad_touches[slot];
- if (tp->tap.state == TAP_DETECT && !tp->tap.centered)
+ if (tp->tap.state == TAP_DETECT && !tp->tap.valid)
wstpad_tap_filter(tp, s);
if (t == NULL || timespeccmp(&t->orig.time,
&s->orig.time, >))
@@ -703,7 +705,7 @@ wstpad_tap_touch(struct wsmouseinput *in
} else {
if (tp->t->state == TOUCH_END) {
t = tp->t;
- if (tp->tap.state == TAP_DETECT && !tp->tap.centered)
+ if (tp->tap.state == TAP_DETECT && !tp->tap.valid)
wstpad_tap_filter(tp, t);
}
}
@@ -711,30 +713,31 @@ wstpad_tap_touch(struct wsmouseinput *in
return (t);
}
-/*
- * If each contact in a sequence of contacts that overlap in time
- * is a tap, a button event may be generated when the number of
- * contacts drops to zero, or to one if there is a masked touch.
- */
-static inline int
-tap_finished(struct wstpad *tp, int nmasked)
+/* Determine the "tap button", keep track of whether a touch is masked. */
+static inline u_int
+tap_button(struct wstpad *tp)
{
- return (tp->contacts == nmasked
- && (nmasked == 0 || !wstpad_is_tap(tp, tp->t)));
+ int n = tp->tap.contacts - tp->contacts - 1;
+
+ tp->tap.masked = tp->contacts;
+
+ return (n >= 0 && n < TAP_BTNMAP_SIZE ? tp->tap.btnmap[n] : 0);
}
-static inline u_int
-tap_btn(struct wstpad *tp, int nmasked)
+/*
+ * In the hold/drag state, do not mask touches if no masking was involved
+ * in the preceding tap gesture.
+ */
+static inline int
+tap_unmask(struct wstpad *tp)
{
- int n = tp->tap.contacts - nmasked;
-
- return (n <= TAP_BTNMAP_SIZE ? tp->tap.btnmap[n - 1] : 0);
+ return ((tp->tap.button || tp->tap.pending) && tp->tap.masked == 0);
}
/*
- * This handler supports one-, two-, and three-finger-taps, which
- * are mapped to left-button, right-button and middle-button events,
- * respectively; moreover, it supports tap-and-drag operations with
+ * In the default configuration, this handler maps one-, two-, and
+ * three-finger taps to left-button, right-button, and middle-button
+ * events, respectively. Setting the LOCKTIME parameter enables
* "locked drags", which are finished by a timeout or a tap-to-end
* gesture.
*/
@@ -743,140 +746,111 @@ wstpad_tap(struct wsmouseinput *input, u
{
struct wstpad *tp = input->tp;
struct tpad_touch *t;
- int nmasked, err = 0;
+ int contacts, is_tap, slot, err = 0;
- if (tp->btns) {
- /*
- * Don't process tapping while hardware buttons are being
- * pressed. If the handler is not in its initial state,
- * release the "tap button".
- */
- if (tp->tap.state > TAP_IGNORE) {
- timeout_del(&tp->tap.to);
- *cmds |= 1 << TAPBUTTON_UP;
- }
- /*
- * It might be possible to produce a click within the tap
- * timeout; ignore the current touch.
- */
- tp->tap.state = TAP_IGNORE;
- tp->tap.contacts = 0;
- tp->tap.centered = 0;
- }
+ /* Synchronize the button states, if necessary. */
+ if (input->btn.sync)
+ *cmds |= 1 << TAPBUTTON_SYNC;
/*
- * If a touch from the bottom area is masked, reduce the
- * contact counts and ignore it.
+ * It is possible to produce a click within the tap timeout.
+ * Wait for a new touch before generating new button events.
*/
- nmasked = (input->mt.ptr_mask ? 1 : 0);
+ if (PRIMARYBTN_RELEASED(tp))
+ tp->tap.contacts = 0;
+
+ /* Reset the detection state whenever a new touch starts. */
+ if (tp->contacts > tp->prev_contacts || (IS_MT(tp) &&
+ (input->mt.touches & input->mt.sync[MTS_TOUCH]))) {
+ tp->tap.contacts = tp->contacts;
+ tp->tap.valid = 0;
+ }
/*
- * Only touches in the TOUCH_END state are relevant here.
- * t is NULL if no touch has been lifted.
+ * The filtered number of active touches excludes a masked
+ * touch if its duration exceeds the tap limit.
*/
- t = wstpad_tap_touch(input);
+ contacts = tp->contacts;
+ if ((slot = ffs(input->mt.ptr_mask) - 1) >= 0
+ && !wstpad_is_tap(tp, &tp->tpad_touches[slot])
+ && !tap_unmask(tp)) {
+ contacts--;
+ }
switch (tp->tap.state) {
case TAP_DETECT:
- if (tp->contacts > tp->tap.contacts)
- tp->tap.contacts = tp->contacts;
-
+ /* Find the oldest touch in the TOUCH_END state. */
+ t = wstpad_tap_touch(input);
if (t) {
- if (wstpad_is_tap(tp, t)) {
- if (tap_finished(tp, nmasked)) {
- if (tp->tap.centered) {
- tp->tap.state = TAP_LIFTED;
- tp->tap.button =
- tap_btn(tp, nmasked);
- }
- tp->tap.contacts = 0;
- tp->tap.centered = 0;
+ is_tap = wstpad_is_tap(tp, t);
+ if (is_tap && contacts == 0) {
+ if (tp->tap.button)
+ *cmds |= 1 << TAPBUTTON_UP;
+ tp->tap.pending = (tp->tap.valid
+ ? tap_button(tp) : 0);
+ if (tp->tap.pending) {
+ tp->tap.state = TAP_LIFTED;
+ err = !timeout_add_msec(&tp->tap.to,
+ CLICKDELAY_MS);
}
- } else {
- if (tp->contacts > nmasked)
+ } else if (!is_tap && tp->tap.locktime == 0) {
+ if (contacts == 0 && tp->tap.button)
+ *cmds |= 1 << TAPBUTTON_UP;
+ else if (contacts)
tp->tap.state = TAP_IGNORE;
- tp->tap.contacts = 0;
- tp->tap.centered = 0;
- }
- if (tp->tap.state == TAP_LIFTED) {
- if (tp->tap.button != 0) {
- *cmds |= 1 << TAPBUTTON_DOWN;
+ } else if (!is_tap && tp->tap.button) {
+ if (contacts == 0) {
+ tp->tap.state = TAP_LOCKED;
err = !timeout_add_msec(&tp->tap.to,
- tp->tap.clicktime);
+ tp->tap.locktime);
} else {
- tp->tap.state = TAP_DETECT;
+ tp->tap.state = TAP_LOCKED_DRAG;
}
}
}
break;
-
case TAP_IGNORE:
- if (tp->contacts == nmasked)
+ if (contacts == 0) {
tp->tap.state = TAP_DETECT;
- break;
- case TAP_LIFTED:
- if (tp->contacts > nmasked) {
- timeout_del(&tp->tap.to);
- if (tp->tap.button == LEFTBTN) {
- tp->tap.state = TAP_2ND_TOUCH;
- } else {
+ if (tp->tap.button)
*cmds |= 1 << TAPBUTTON_UP;
- tp->tap.state = TAP_DETECT;
- }
}
break;
- case TAP_2ND_TOUCH:
- if (t) {
- if (wstpad_is_tap(tp, t)) {
- *cmds |= 1 << TAPBUTTON_DOUBLECLK;
- tp->tap.state = TAP_LIFTED;
- err = !timeout_add_msec(&tp->tap.to,
- CLICKDELAY_MS);
- } else if (tp->contacts == nmasked) {
- if (tp->tap.locktime == 0) {
- *cmds |= 1 << TAPBUTTON_UP;
- tp->tap.state = TAP_DETECT;
- } else {
- tp->tap.state = TAP_LOCKED;
- err = !timeout_add_msec(&tp->tap.to,
- tp->tap.locktime);
- }
- }
- } else if (tp->contacts != nmasked + 1) {
- *cmds |= 1 << TAPBUTTON_UP;
+ case TAP_LIFTED:
+ if (contacts) {
+ timeout_del(&tp->tap.to);
tp->tap.state = TAP_DETECT;
+ if (tp->tap.pending)
+ *cmds |= 1 << TAPBUTTON_DOWN;
}
break;
case TAP_LOCKED:
- if (tp->contacts > nmasked) {
+ if (contacts) {
timeout_del(&tp->tap.to);
- tp->tap.state = TAP_NTH_TOUCH;
+ tp->tap.state = TAP_LOCKED_DRAG;
}
break;
- case TAP_NTH_TOUCH:
- if (t) {
- if (wstpad_is_tap(tp, t)) {
+ case TAP_LOCKED_DRAG:
+ if (contacts == 0) {
+ t = wstpad_tap_touch(input);
+ if (t && wstpad_is_tap(tp, t)) {
/* "tap-to-end" */
*cmds |= 1 << TAPBUTTON_UP;
tp->tap.state = TAP_DETECT;
- } else if (tp->contacts == nmasked) {
+ } else {
tp->tap.state = TAP_LOCKED;
err = !timeout_add_msec(&tp->tap.to,
tp->tap.locktime);
}
- } else if (tp->contacts != nmasked + 1) {
- *cmds |= 1 << TAPBUTTON_UP;
- tp->tap.state = TAP_DETECT;
}
break;
}
if (err) { /* Did timeout_add fail? */
- if (tp->tap.state == TAP_LIFTED)
- *cmds &= ~(1 << TAPBUTTON_DOWN);
- else
- *cmds |= 1 << TAPBUTTON_UP;
-
+ input->sbtn.buttons &= ~tp->tap.button;
+ input->sbtn.sync |= tp->tap.button;
+ tp->tap.pending = 0;
+ tp->tap.button = 0;
tp->tap.state = TAP_DETECT;
}
}
@@ -888,19 +862,31 @@ wstpad_tap_timeout(void *p)
struct wstpad *tp = input->tp;
struct evq_access evq;
u_int btn;
- int s;
+ int s, ev;
s = spltty();
evq.evar = *input->evar;
if (evq.evar != NULL && tp != NULL &&
(tp->tap.state == TAP_LIFTED || tp->tap.state == TAP_LOCKED)) {
- tp->tap.state = TAP_DETECT;
- input->sbtn.buttons &= ~tp->tap.button;
- btn = ffs(tp->tap.button) - 1;
+
+ if (tp->tap.pending) {
+ tp->tap.button = tp->tap.pending;
+ tp->tap.pending = 0;
+ ev = BTN_DOWN_EV;
+ btn = ffs(tp->tap.button) - 1;
+ input->sbtn.buttons |= tp->tap.button;
+ timeout_add_msec(&tp->tap.to, tp->tap.clicktime);
+ } else {
+ ev = BTN_UP_EV;
+ btn = ffs(tp->tap.button) - 1;
+ input->sbtn.buttons &= ~tp->tap.button;
+ tp->tap.button = 0;
+ tp->tap.state = TAP_DETECT;
+ }
evq.put = evq.evar->put;
evq.result = EVQ_RESULT_NONE;
getnanotime(&evq.ts);
- wsmouse_evq_put(&evq, BTN_UP_EV, btn);
+ wsmouse_evq_put(&evq, ev, btn);
wsmouse_evq_put(&evq, SYNC_EV, 0);
if (evq.result == EVQ_RESULT_SUCCESS) {
if (input->flags & LOG_EVENTS) {
@@ -928,15 +914,12 @@ wstpad_click(struct wsmouseinput *input)
set_freeze_ts(tp, 0, FREEZE_MS);
}
-/*
- * Translate the "command" bits into the sync-state of wsmouse, or into
- * wscons events.
- */
+/* Translate the "command" bits into the sync-state of wsmouse. */
void
-wstpad_cmds(struct wsmouseinput *input, struct evq_access *evq, u_int cmds)
+wstpad_cmds(struct wsmouseinput *input, u_int cmds)
{
struct wstpad *tp = input->tp;
- u_int btn, sbtns_dn = 0, sbtns_up = 0;
+ u_int tapbtn, sbtns_dn = 0, sbtns_up = 0;
int n;
FOREACHBIT(cmds, n) {
@@ -955,24 +938,24 @@ wstpad_cmds(struct wsmouseinput *input,
sbtns_up |= tp->softbutton;
tp->softbutton = 0;
continue;
+ case TAPBUTTON_SYNC:
+ if ((tapbtn = (tp->tap.button | tp->tap.pending))) {
+ input->btn.sync &= ~tapbtn;
+ sbtns_up &= ~tapbtn;
+ sbtns_dn &= ~tapbtn;
+ }
+ continue;
case TAPBUTTON_DOWN:
+ tp->tap.button = tp->tap.pending;
+ tp->tap.pending = 0;
sbtns_dn |= tp->tap.button;
continue;
case TAPBUTTON_UP:
- sbtns_up |= tp->tap.button;
- continue;
- case TAPBUTTON_DOUBLECLK:
- /*
- * We cannot add the final BTN_UP event here, a
- * delay is required. This is the reason why the
- * tap handler returns from the 2ND_TOUCH state
- * into the LIFTED state with a short timeout
- * (CLICKDELAY_MS).
- */
- btn = ffs(PRIMARYBTN) - 1;
- wsmouse_evq_put(evq, BTN_UP_EV, btn);
- wsmouse_evq_put(evq, SYNC_EV, 0);
- wsmouse_evq_put(evq, BTN_DOWN_EV, btn);
+ if ((tp->tap.button & input->btn.buttons) == 0
+ && tp->tap.button != tp->softbutton) {
+ sbtns_up |= tp->tap.button;
+ }
+ tp->tap.button = 0;
continue;
case HSCROLL:
input->motion.dw = tp->scroll.dw;
@@ -1276,7 +1259,7 @@ wstpad_process_input(struct wsmouseinput
}
}
- wstpad_cmds(input, evq, cmds);
+ wstpad_cmds(input, cmds);
if (IS_MT(tp))
clear_touchstates(input, TOUCH_NONE);