The touchpad input driver of wsmouse(4) uses a flawed shortcut for
handling double taps, which has the effect that tap-and-drag inputs with
multiple taps before the final contact almost never work correctly when
the number of the taps is even:
    https://marc.info/?l=openbsd-misc&m=164986426617019&w=2

After each second tap, the driver generates a button-up and a button-down
event at once, and issues a second button-up event almost immediately
afterwards in order to make that work.  It doesn't wait for another touch
within the proper wait interval (WSMOUSECG_TAP_CLICKTIME).

With this patch, the driver serializes the events properly - with an
additional timeout, if necessary.  A new "tap state" keeps the input
handler and the timeout function in sync.

Tests and OKs would be welcome.


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 15 Apr 2022 17:27:35 -0000
@@ -61,7 +61,7 @@
 #define TAP_LOCKTIME_DEFAULT   0
 #define TAP_BTNMAP_SIZE                3

-#define CLICKDELAY_MS          20
+#define CLICKDELAY_MS          10
 #define FREEZE_MS              100
 #define MATCHINTERVAL_MS       45
 #define STOPINTERVAL_MS                55
@@ -83,6 +83,7 @@ enum tap_state {
        TAP_IGNORE,
        TAP_LIFTED,
        TAP_2ND_TOUCH,
+       TAP_REPEATED,
        TAP_LOCKED,
        TAP_NTH_TOUCH,
 };
@@ -93,7 +94,6 @@ enum tpad_cmd {
        SOFTBUTTON_UP,
        TAPBUTTON_DOWN,
        TAPBUTTON_UP,
-       TAPBUTTON_DOUBLECLK,
        VSCROLL,
        HSCROLL,
 };
@@ -828,8 +828,8 @@ wstpad_tap(struct wsmouseinput *input, u
        case TAP_2ND_TOUCH:
                if (t) {
                        if (wstpad_is_tap(tp, t)) {
-                               *cmds |= 1 << TAPBUTTON_DOUBLECLK;
-                               tp->tap.state = TAP_LIFTED;
+                               *cmds |= 1 << TAPBUTTON_UP;
+                               tp->tap.state = TAP_REPEATED;
                                err = !timeout_add_msec(&tp->tap.to,
                                    CLICKDELAY_MS);
                        } else if (tp->contacts == nmasked) {
@@ -847,6 +847,14 @@ wstpad_tap(struct wsmouseinput *input, u
                        tp->tap.state = TAP_DETECT;
                }
                break;
+       case TAP_REPEATED:
+               if (tp->contacts > nmasked) {
+                       timeout_del(&tp->tap.to);
+                       tp->tap.state = TAP_2ND_TOUCH;
+                       if ((input->sbtn.buttons & tp->tap.button) == 0)
+                               *cmds |= 1 << TAPBUTTON_DOWN;
+               }
+               break;
        case TAP_LOCKED:
                if (tp->contacts > nmasked) {
                        timeout_del(&tp->tap.to);
@@ -888,19 +896,28 @@ 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;
+       if (evq.evar != NULL && tp != NULL && (tp->tap.state == TAP_LIFTED
+           || tp->tap.state == TAP_REPEATED || tp->tap.state == TAP_LOCKED)) {
+
+               if (tp->tap.state != TAP_REPEATED
+                   || (input->sbtn.buttons & tp->tap.button)) {
+                       ev = BTN_UP_EV;
+                       input->sbtn.buttons &= ~tp->tap.button;
+                       tp->tap.state = TAP_DETECT;
+               } else {
+                       ev = BTN_DOWN_EV;
+                       input->sbtn.buttons |= tp->tap.button;
+                       timeout_add_msec(&tp->tap.to, tp->tap.clicktime);
+               }
                btn = ffs(tp->tap.button) - 1;
                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 +945,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 sbtns_dn = 0, sbtns_up = 0;
        int n;

        FOREACHBIT(cmds, n) {
@@ -961,19 +975,6 @@ wstpad_cmds(struct wsmouseinput *input,
                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);
-                       continue;
                case HSCROLL:
                        input->motion.dw = tp->scroll.dw;
                        input->motion.sync |= SYNC_DELTAS;
@@ -1276,7 +1277,7 @@ wstpad_process_input(struct wsmouseinput
                }
        }

-       wstpad_cmds(input, evq, cmds);
+       wstpad_cmds(input, cmds);

        if (IS_MT(tp))
                clear_touchstates(input, TOUCH_NONE);

Reply via email to