From 31a33b6ee0d04db7a946074e01f2bf7aa6a53cce Mon Sep 17 00:00:00 2001 From: bru Date: Tue, 6 Jun 2017 19:47:22 +0000 Subject: [PATCH] Add support for tap gestures. --- sys/dev/wscons/wsconsio.h | 10 +- sys/dev/wscons/wstpad.c | 293 +++++++++++++++++++++++++++++++++++++- 2 files changed, 298 insertions(+), 5 deletions(-) diff --git a/sys/dev/wscons/wsconsio.h b/sys/dev/wscons/wsconsio.h index 7094d523b4e..121ef0d7727 100644 --- a/sys/dev/wscons/wsconsio.h +++ b/sys/dev/wscons/wsconsio.h @@ -1,4 +1,4 @@ -/* $OpenBSD: wsconsio.h,v 1.80 2017/05/08 20:55:29 bru Exp $ */ +/* $OpenBSD: wsconsio.h,v 1.81 2017/06/06 19:47:22 bru Exp $ */ /* $NetBSD: wsconsio.h,v 1.74 2005/04/28 07:15:44 martin Exp $ */ /* @@ -318,6 +318,7 @@ enum wsmousecfg { WSMOUSECFG_SWAPSIDES, /* invert soft-button/scroll areas */ WSMOUSECFG_DISABLE, /* disable all output except for clicks in the top-button area */ + WSMOUSECFG_TAPPING, /* enable tapping */ /* * Touchpad options @@ -331,8 +332,13 @@ enum wsmousecfg { WSMOUSECFG_VERTSCROLLDIST, /* distance mapped to a scroll event */ WSMOUSECFG_F2WIDTH, /* width limit for single touches */ WSMOUSECFG_F2PRESSURE, /* pressure limit for single touches */ + WSMOUSECFG_TAP_MAXTIME, /* max. duration of tap contacts (ms) */ + WSMOUSECFG_TAP_CLICKTIME, /* time between the end of a tap and + the button-up-event (ms) */ + WSMOUSECFG_TAP_LOCKTIME, /* time between a tap-and-drag action + and the button-up-event (ms) */ }; -#define WSMOUSECFG_MAX 32 /* max size of param array per ioctl */ +#define WSMOUSECFG_MAX 36 /* max size of param array per ioctl */ struct wsmouse_param { enum wsmousecfg key; diff --git a/sys/dev/wscons/wstpad.c b/sys/dev/wscons/wstpad.c index 64533693e21..829f118f0f7 100644 --- a/sys/dev/wscons/wstpad.c +++ b/sys/dev/wscons/wstpad.c @@ -1,3 +1,5 @@ +/* $OpenBSD: wstpad.c,v 1.6 2017/06/06 19:47:22 bru Exp $ */ + /* * Copyright (c) 2015, 2016 Ulf Brosziewski * @@ -23,6 +25,8 @@ #include #include #include +#include +#include #include #include @@ -44,20 +48,38 @@ #define EDGERATIO_DEFAULT (4096 / 16) #define CENTERWIDTH_DEFAULT (4096 / 8) +#define TAP_MAXTIME_DEFAULT 180 +#define TAP_CLICKTIME_DEFAULT 180 +#define TAP_LOCKTIME_DEFAULT 0 + +#define CLICKDELAY_MS 20 #define FREEZE_MS 100 enum tpad_handlers { SOFTBUTTON_HDLR, TOPBUTTON_HDLR, + TAP_HDLR, F2SCROLL_HDLR, EDGESCROLL_HDLR, CLICK_HDLR, }; +enum tap_state { + TAP_DETECT, + TAP_IGNORE, + TAP_LIFTED, + TAP_2ND_TOUCH, + TAP_LOCKED, + TAP_NTH_TOUCH, +}; + enum tpad_cmd { CLEAR_MOTION_DELTAS, SOFTBUTTON_DOWN, SOFTBUTTON_UP, + TAPBUTTON_DOWN, + TAPBUTTON_UP, + TAPBUTTON_DOUBLECLK, VSCROLL, HSCROLL, }; @@ -104,6 +126,7 @@ struct tpad_touch { #define WSTPAD_HORIZSCROLL (1 << 5) #define WSTPAD_SWAPSIDES (1 << 6) #define WSTPAD_DISABLE (1 << 7) +#define WSTPAD_TAPPING (1 << 8) #define WSTPAD_MT (1 << 31) @@ -168,6 +191,18 @@ struct wstpad { u_int softbutton; u_int sbtnswap; + struct { + enum tap_state state; + int contacts; + u_int button; + int maxdist; + struct timeout to; + /* parameters: */ + struct timespec maxtime; + int clicktime; + int locktime; + } tap; + struct { int acc_dx; int acc_dy; @@ -474,6 +509,191 @@ wstpad_softbuttons(struct wsmouseinput *input, u_int *cmds, int hdlr) } } +int +wstpad_is_tap(struct wstpad *tp, struct tpad_touch *t) +{ + struct timespec ts; + int dx, dy, dist = 0; + + if (t->flags & EDGES) + /* No tapping in the edge areas. */ + return (0); + + /* + * No distance limit applies if there has been more than one contact + * on a single-touch device. We cannot use (t->x - t->orig.x) in this + * case. Accumulated deltas might be an alternative, but some + * touchpads provide unreliable coordinates at the start or end of a + * multi-finger touch. + */ + if (IS_MT(tp) || tp->tap.contacts < 2) { + dx = abs(t->x - t->orig.x) << 12; + dy = abs(t->y - t->orig.y) * tp->ratio; + dist = (dx >= dy ? dx + 3 * dy / 8 : dy + 3 * dx / 8); + } + if (dist <= (tp->tap.maxdist << 12)) { + timespecsub(&tp->time, &t->orig.time, &ts); + return (timespeccmp(&ts, &tp->tap.maxtime, <)); + } + return (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 + * "locked drags", which are finished by a timeout or a tap-to-end + * gesture. + * Please note that if the touch t is derived from MT input it is the + * pointer-controlling touch. If the state of that touch is not + * TOUCH_UPDATE then no other touch will be in this state. + */ +void +wstpad_tap(struct wsmouseinput *input, u_int *cmds) +{ + struct wstpad *tp = input->tp; + struct tpad_touch *t = tp->t; + int err = 0; + + if (tp->btns) { + /* + * Don't process tapping while hardware buttons are being + * pressed. If the handler is not in its initial state, + * the "tap button" will be released. + */ + 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; + } + + switch (tp->tap.state) { + case TAP_DETECT: + if (t->state == TOUCH_END) { + if (wstpad_is_tap(tp, t)) { + tp->tap.button = (tp->tap.contacts == 2 + ? RIGHTBTN : (tp->tap.contacts == 3 + ? MIDDLEBTN : LEFTBTN)); + *cmds |= 1 << TAPBUTTON_DOWN; + tp->tap.state = TAP_LIFTED; + err = !timeout_add_msec( + &tp->tap.to, tp->tap.clicktime); + } + tp->tap.contacts = 0; + } else if (tp->contacts != tp->tap.contacts) { + /* Check t before pointer-control might change. */ + if (!wstpad_is_tap(tp, t)) { + tp->tap.state = TAP_IGNORE; + tp->tap.contacts = 0; + } else if (tp->contacts > tp->tap.contacts) { + tp->tap.contacts = tp->contacts; + } + } + break; + case TAP_IGNORE: + if (t->state != TOUCH_UPDATE) + tp->tap.state = TAP_DETECT; + break; + case TAP_LIFTED: + if (t->state >= TOUCH_BEGIN) { + timeout_del(&tp->tap.to); + if (tp->tap.button == LEFTBTN) { + tp->tap.state = TAP_2ND_TOUCH; + } else { + *cmds |= 1 << TAPBUTTON_UP; + tp->tap.state = TAP_DETECT; + } + } + break; + case TAP_2ND_TOUCH: + if (t->state == TOUCH_END) { + 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->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 > 1) { + *cmds |= 1 << TAPBUTTON_UP; + tp->tap.state = TAP_DETECT; + } + break; + case TAP_LOCKED: + if (t->state >= TOUCH_BEGIN) { + timeout_del(&tp->tap.to); + tp->tap.state = TAP_NTH_TOUCH; + } + break; + case TAP_NTH_TOUCH: + if (t->state == TOUCH_END) { + if (wstpad_is_tap(tp, t)) { + /* "tap-to-end" */ + *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 > 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; + + tp->tap.state = TAP_DETECT; + } +} + +void +wstpad_tap_timeout(void *p) +{ + struct wsmouseinput *input = p; + struct wstpad *tp = input->tp; + struct evq_access evq; + u_int btn; + int s; + + 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; + evq.put = evq.evar->put; + evq.result = EVQ_RESULT_NONE; + wsmouse_evq_put(&evq, BTN_UP_EV, btn); + wsmouse_evq_put(&evq, SYNC_EV, 0); + if (evq.result == EVQ_RESULT_SUCCESS) { + evq.evar->put = evq.put; + WSEVENT_WAKEUP(evq.evar); + } else { + input->sbtn.sync |= tp->tap.button; + } + } + splx(s); +} + /* * Suppress accidental pointer movements after a click on a clickpad. */ @@ -495,7 +715,7 @@ void wstpad_cmds(struct wsmouseinput *input, struct evq_access *evq, u_int cmds) { struct wstpad *tp = input->tp; - u_int sbtns_dn = 0, sbtns_up = 0; + u_int btn, sbtns_dn = 0, sbtns_up = 0; int n; FOREACHBIT(cmds, n) { @@ -514,6 +734,25 @@ wstpad_cmds(struct wsmouseinput *input, struct evq_access *evq, u_int cmds) sbtns_up |= tp->softbutton; tp->softbutton = 0; continue; + case TAPBUTTON_DOWN: + 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); + continue; case HSCROLL: input->motion.dw = tp->scroll.dw; input->motion.sync |= SYNC_DELTAS; @@ -684,6 +923,9 @@ wstpad_process_input(struct wsmouseinput *input, struct evq_access *evq) case TOPBUTTON_HDLR: wstpad_softbuttons(input, &cmds, hdlr); continue; + case TAP_HDLR: + wstpad_tap(input, &cmds); + continue; case F2SCROLL_HDLR: wstpad_f2scroll(input, &cmds); continue; @@ -952,6 +1194,8 @@ wstpad_init(struct wsmouseinput *input) if (input->mt.num_slots) tp->features |= WSTPAD_MT; + timeout_set(&tp->tap.to, wstpad_tap_timeout, input); + tp->ratio = input->filter.ratio; return (0); @@ -1059,6 +1303,9 @@ wstpad_configure(struct wsmouseinput *input) tp->params.left_edge = EDGERATIO_DEFAULT; tp->params.right_edge = EDGERATIO_DEFAULT; tp->params.center_width = CENTERWIDTH_DEFAULT; + tp->tap.maxtime.tv_nsec = TAP_MAXTIME_DEFAULT * 1000000; + tp->tap.clicktime = TAP_CLICKTIME_DEFAULT; + tp->tap.locktime = TAP_LOCKTIME_DEFAULT; tp->features &= (WSTPAD_MT | WSTPAD_DISABLE); if (input->hw.hw_type == WSMOUSEHW_TOUCHPAD) @@ -1071,6 +1318,7 @@ wstpad_configure(struct wsmouseinput *input) } tp->scroll.hdist = 5 * h_unit; tp->scroll.vdist = 5 * v_unit; + tp->tap.maxdist = 3 * h_unit; } tp->edge.left = input->hw.x_min + @@ -1108,6 +1356,14 @@ wstpad_configure(struct wsmouseinput *input) ((tp->features & WSTPAD_SWAPSIDES) ? L_EDGE : R_EDGE); } + if (tp->features & WSTPAD_TAPPING) { + tp->tap.clicktime = imin(imax(tp->tap.clicktime, 80), 350); + if (tp->tap.locktime) + tp->tap.locktime = + imin(imax(tp->tap.locktime, 150), 5000); + tp->handlers |= 1 << TAP_HDLR; + } + if (input->hw.hw_type == WSMOUSEHW_CLICKPAD) tp->handlers |= 1 << CLICK_HDLR; @@ -1120,6 +1376,13 @@ wstpad_configure(struct wsmouseinput *input) void wstpad_reset(struct wsmouseinput *input) { + struct wstpad *tp = input->tp; + + if (tp != NULL) { + timeout_del(&tp->tap.to); + tp->tap.state = TAP_DETECT; + } + if (input->sbtn.buttons) { input->sbtn.sync = input->sbtn.buttons; input->sbtn.buttons = 0; @@ -1136,7 +1399,7 @@ wstpad_set_param(struct wsmouseinput *input, int key, int val) return (EINVAL); switch (key) { - case WSMOUSECFG_SOFTBUTTONS ... WSMOUSECFG_DISABLE: + case WSMOUSECFG_SOFTBUTTONS ... WSMOUSECFG_TAPPING: switch (key) { case WSMOUSECFG_SOFTBUTTONS: flag = WSTPAD_SOFTBUTTONS; @@ -1162,6 +1425,9 @@ wstpad_set_param(struct wsmouseinput *input, int key, int val) case WSMOUSECFG_DISABLE: flag = WSTPAD_DISABLE; break; + case WSMOUSECFG_TAPPING: + flag = WSTPAD_TAPPING; + break; } if (val) tp->features |= flag; @@ -1195,6 +1461,15 @@ wstpad_set_param(struct wsmouseinput *input, int key, int val) case WSMOUSECFG_F2PRESSURE: tp->params.f2pressure = val; break; + case WSMOUSECFG_TAP_MAXTIME: + tp->tap.maxtime.tv_nsec = imin(val, 999) * 1000000; + break; + case WSMOUSECFG_TAP_CLICKTIME: + tp->tap.clicktime = val; + break; + case WSMOUSECFG_TAP_LOCKTIME: + tp->tap.locktime = val; + break; default: return (ENOTSUP); } @@ -1212,7 +1487,7 @@ wstpad_get_param(struct wsmouseinput *input, int key, int *pval) return (EINVAL); switch (key) { - case WSMOUSECFG_SOFTBUTTONS ... WSMOUSECFG_DISABLE: + case WSMOUSECFG_SOFTBUTTONS ... WSMOUSECFG_TAPPING: switch (key) { case WSMOUSECFG_SOFTBUTTONS: flag = WSTPAD_SOFTBUTTONS; @@ -1238,6 +1513,9 @@ wstpad_get_param(struct wsmouseinput *input, int key, int *pval) case WSMOUSECFG_DISABLE: flag = WSTPAD_DISABLE; break; + case WSMOUSECFG_TAPPING: + flag = WSTPAD_TAPPING; + break; } *pval = !!(tp->features & flag); break; @@ -1268,6 +1546,15 @@ wstpad_get_param(struct wsmouseinput *input, int key, int *pval) case WSMOUSECFG_F2PRESSURE: *pval = tp->params.f2pressure; break; + case WSMOUSECFG_TAP_MAXTIME: + *pval = tp->tap.maxtime.tv_nsec / 1000000; + break; + case WSMOUSECFG_TAP_CLICKTIME: + *pval = tp->tap.clicktime; + break; + case WSMOUSECFG_TAP_LOCKTIME: + *pval = tp->tap.locktime; + break; default: return (ENOTSUP); } -- 2.20.1