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);