touchpad: implement hold-and-tap

This feature allows a user, as the name suggests, to hold fingers on a
touchpad while tapping with additional fingers to produce the same effect as
without the holding fingers.
The holding fingers may even have moved, pressed the clickpad button (unless
it is still down at the time of the tap), or be participating in a
tap-and-drag, though in the latter case a tap with the same number of fingers
as was used to start the tap-and-drag is ignored, just like a click from
another mouse-like device would be.

Signed-off-by: satrmb <10471-satrmb@users.noreply.gitlab.freedesktop.org>
This commit is contained in:
satrmb 2020-08-02 20:53:16 +02:00
parent 21bf80ab05
commit 355a3e2f64
2 changed files with 194 additions and 69 deletions

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 168 KiB

After

Width:  |  Height:  |  Size: 230 KiB

View file

@ -204,11 +204,12 @@ tp_tap_clear_timer(struct tp_dispatch *tp)
}
static void
tp_tap_kill_all_touches(struct tp_dispatch *tp)
tp_tap_kill_all_touches(struct tp_dispatch *tp,
struct tp_touch *except)
{
struct tp_touch *t;
tp_for_each_touch(tp, t) {
if (t->tap.state == TAP_TOUCH_STATE_TOUCH)
if (t != except && t->tap.state == TAP_TOUCH_STATE_TOUCH)
t->tap.state = TAP_TOUCH_STATE_DEAD;
}
}
@ -238,7 +239,9 @@ tp_drag_idle_handle_event(struct tp_dispatch *tp,
tp->tap.saved_press_time,
1,
LIBINPUT_BUTTON_STATE_PRESSED);
if (tp->tap.drag_enabled) {
/* taps may end with fingers remaining down,
* those should not start a drag */
if (tp->tap.drag_enabled && tp->tap.nfingers_down == 0) {
tp->tap.drag_state = DRAG_STATE_1FGTAP_TAPPED;
tp_tap_set_drag_timer(tp, time, 1);
} else {
@ -253,7 +256,7 @@ tp_drag_idle_handle_event(struct tp_dispatch *tp,
tp->tap.saved_press_time,
2,
LIBINPUT_BUTTON_STATE_PRESSED);
if (tp->tap.drag_enabled) {
if (tp->tap.drag_enabled && tp->tap.nfingers_down == 0) {
tp->tap.drag_state = DRAG_STATE_2FGTAP_TAPPED;
tp_tap_set_drag_timer(tp, time, 2);
} else {
@ -268,8 +271,6 @@ tp_drag_idle_handle_event(struct tp_dispatch *tp,
tp->tap.saved_press_time,
3,
LIBINPUT_BUTTON_STATE_PRESSED);
/* three-finger taps may end with fingers remaining down,
* see gitlab#499; those should not start a drag */
if (tp->tap.drag_enabled && tp->tap.nfingers_down == 0) {
tp->tap.drag_state = DRAG_STATE_3FGTAP_TAPPED;
tp_tap_set_drag_timer(tp, time, 3);
@ -479,9 +480,46 @@ tp_drag_dragging_handle_event(struct tp_dispatch *tp,
}
break;
case TAP_EVENT_1FGTAP:
if (!tp->tap.hold_tap_enabled)
log_drag_bug(tp, t, event);
else if (nfingers_tapped != 1) {
tp_tap_notify(tp,
tp->tap.saved_press_time,
1,
LIBINPUT_BUTTON_STATE_PRESSED);
tp_tap_notify(tp,
tp->tap.saved_release_time,
1,
LIBINPUT_BUTTON_STATE_RELEASED);
}
break;
case TAP_EVENT_2FGTAP:
if (!tp->tap.hold_tap_enabled)
log_drag_bug(tp, t, event);
else if (nfingers_tapped != 2) {
tp_tap_notify(tp,
tp->tap.saved_press_time,
2,
LIBINPUT_BUTTON_STATE_PRESSED);
tp_tap_notify(tp,
tp->tap.saved_release_time,
2,
LIBINPUT_BUTTON_STATE_RELEASED);
}
break;
case TAP_EVENT_3FGTAP:
log_drag_bug(tp, t, event);
if (!tp->tap.hold_tap_enabled)
log_drag_bug(tp, t, event);
else if (nfingers_tapped != 3) {
tp_tap_notify(tp,
tp->tap.saved_press_time,
3,
LIBINPUT_BUTTON_STATE_PRESSED);
tp_tap_notify(tp,
tp->tap.saved_release_time,
3,
LIBINPUT_BUTTON_STATE_RELEASED);
}
break;
}
}
@ -733,6 +771,7 @@ tp_tap_idle_handle_event(struct tp_dispatch *tp,
tp_tap_set_timer(tp, time);
break;
case TAP_EVENT_RELEASE:
log_tap_bug(tp, t, event);
break;
case TAP_EVENT_MOTION:
log_tap_bug(tp, t, event);
@ -749,7 +788,7 @@ tp_tap_idle_handle_event(struct tp_dispatch *tp,
log_tap_bug(tp, t, event);
break;
case TAP_EVENT_PALM:
tp->tap.state = TAP_STATE_IDLE;
log_tap_bug(tp, t, event);
break;
case TAP_EVENT_1FGTAP:
case TAP_EVENT_2FGTAP:
@ -772,15 +811,21 @@ tp_tap_touch_handle_event(struct tp_dispatch *tp,
tp_tap_set_timer(tp, time);
break;
case TAP_EVENT_RELEASE:
tp->tap.saved_release_time = time;
tp_drag_handle_event(tp, t, TAP_EVENT_1FGTAP, time);
tp->tap.state = TAP_STATE_IDLE;
if (t->tap.state == TAP_TOUCH_STATE_TOUCH) {
tp->tap.saved_release_time = time;
tp_drag_handle_event(tp, t, TAP_EVENT_1FGTAP, time);
if (tp->tap.nfingers_down == 0)
tp->tap.state = TAP_STATE_IDLE;
else
tp->tap.state = TAP_STATE_DEAD;
}
break;
case TAP_EVENT_MOTION:
tp->tap.state = TAP_STATE_DEAD;
break;
case TAP_EVENT_TIMEOUT:
if (tp->tap.drag_state == DRAG_STATE_IDLE)
if (!tp->tap.hold_tap_enabled &&
tp->tap.drag_state == DRAG_STATE_IDLE)
tp->tap.state = TAP_STATE_HOLD;
else
tp->tap.state = TAP_STATE_DEAD;
@ -793,14 +838,20 @@ tp_tap_touch_handle_event(struct tp_dispatch *tp,
log_tap_bug(tp, t, event);
break;
case TAP_EVENT_THUMB:
if (tp->tap.drag_state == DRAG_STATE_IDLE) {
if (tp->tap.drag_state == DRAG_STATE_IDLE &&
tp->tap.nfingers_down == 1) {
tp->tap.state = TAP_STATE_IDLE;
t->tap.is_thumb = true;
tp->tap.nfingers_down--;
}
break;
case TAP_EVENT_PALM:
tp->tap.state = TAP_STATE_IDLE;
if (t->tap.state == TAP_TOUCH_STATE_TOUCH) {
if (tp->tap.nfingers_down == 0)
tp->tap.state = TAP_STATE_IDLE;
else
tp->tap.state = TAP_STATE_DEAD;
}
break;
case TAP_EVENT_1FGTAP:
case TAP_EVENT_2FGTAP:
@ -865,15 +916,18 @@ tp_tap_touch2_handle_event(struct tp_dispatch *tp,
tp_tap_set_timer(tp, time);
break;
case TAP_EVENT_RELEASE:
tp->tap.state = TAP_STATE_TOUCH_2_RELEASE;
tp->tap.saved_release_time = time;
tp_tap_set_timer(tp, time);
if (t->tap.state == TAP_TOUCH_STATE_TOUCH) {
tp->tap.state = TAP_STATE_TOUCH_2_RELEASE;
tp->tap.saved_release_time = time;
tp_tap_set_timer(tp, time);
}
break;
case TAP_EVENT_MOTION:
tp->tap.state = TAP_STATE_DEAD;
break;
case TAP_EVENT_TIMEOUT:
if (tp->tap.drag_state == DRAG_STATE_IDLE)
if (!tp->tap.hold_tap_enabled &&
tp->tap.drag_state == DRAG_STATE_IDLE)
tp->tap.state = TAP_STATE_TOUCH_2_HOLD;
else
tp->tap.state = TAP_STATE_DEAD;
@ -888,7 +942,8 @@ tp_tap_touch2_handle_event(struct tp_dispatch *tp,
case TAP_EVENT_THUMB:
break;
case TAP_EVENT_PALM:
tp->tap.state = TAP_STATE_TOUCH;
if (t->tap.state == TAP_TOUCH_STATE_TOUCH)
tp->tap.state = TAP_STATE_TOUCH;
break;
case TAP_EVENT_1FGTAP:
case TAP_EVENT_2FGTAP:
@ -945,19 +1000,29 @@ tp_tap_touch2_release_handle_event(struct tp_dispatch *tp,
switch (event) {
case TAP_EVENT_TOUCH:
tp->tap.state = TAP_STATE_TOUCH_2;
if (tp->tap.hold_tap_enabled) {
tp_tap_kill_all_touches(tp, t);
tp->tap.state = TAP_STATE_TOUCH;
} else
tp->tap.state = TAP_STATE_TOUCH_2;
tp->tap.saved_press_time = time;
tp_tap_set_timer(tp, time);
break;
case TAP_EVENT_RELEASE:
tp_drag_handle_event(tp, t, TAP_EVENT_2FGTAP, time);
tp->tap.state = TAP_STATE_IDLE;
if (t->tap.state == TAP_TOUCH_STATE_TOUCH) {
tp_drag_handle_event(tp, t, TAP_EVENT_2FGTAP, time);
if (tp->tap.nfingers_down == 0)
tp->tap.state = TAP_STATE_IDLE;
else
tp->tap.state = TAP_STATE_DEAD;
}
break;
case TAP_EVENT_MOTION:
tp->tap.state = TAP_STATE_DEAD;
break;
case TAP_EVENT_TIMEOUT:
if (tp->tap.drag_state == DRAG_STATE_IDLE)
if (!tp->tap.hold_tap_enabled &&
tp->tap.drag_state == DRAG_STATE_IDLE)
tp->tap.state = TAP_STATE_HOLD;
else
tp->tap.state = TAP_STATE_DEAD;
@ -971,15 +1036,21 @@ tp_tap_touch2_release_handle_event(struct tp_dispatch *tp,
case TAP_EVENT_THUMB:
break;
case TAP_EVENT_PALM:
/* There's only one saved press time and it's overwritten by
* the last touch down. So in the case of finger down, palm
* down, finger up, palm detected, we use the
* palm touch's press time here instead of the finger's press
* time. The tap timer also gets reset on palm detection.
* Let's wait and see if that's an issue.
*/
tp_drag_handle_event(tp, t, TAP_EVENT_1FGTAP, time);
tp->tap.state = TAP_STATE_IDLE;
if (t->tap.state == TAP_TOUCH_STATE_TOUCH) {
/* There's only one saved press time and it's
* overwritten by the last touch down. So in the case
* of finger down, palm down, finger up, palm detected,
* we use the palm touch's press time here instead of
* the finger's press time. The tap timer also gets
* reset on palm detection. Let's wait and see if that's
* an issue.
*/
tp_drag_handle_event(tp, t, TAP_EVENT_1FGTAP, time);
if (tp->tap.nfingers_down == 0)
tp->tap.state = TAP_STATE_IDLE;
else
tp->tap.state = TAP_STATE_DEAD;
}
break;
case TAP_EVENT_1FGTAP:
case TAP_EVENT_2FGTAP:
@ -1002,21 +1073,24 @@ tp_tap_touch3_handle_event(struct tp_dispatch *tp,
* out of DRAGGING_OR_DOUBLETAP or DRAGLOCK_CONTINUE */
tp_drag_handle_event(tp, t, TAP_EVENT_MOTION, time);
break;
case TAP_EVENT_RELEASE:
if (t->tap.state == TAP_TOUCH_STATE_TOUCH) {
tp->tap.state = TAP_STATE_TOUCH_3_RELEASE;
tp->tap.saved_release_time = time;
tp_tap_set_timer(tp, time);
}
break;
case TAP_EVENT_MOTION:
tp->tap.state = TAP_STATE_DEAD;
break;
case TAP_EVENT_TIMEOUT:
if (tp->tap.drag_state == DRAG_STATE_IDLE)
if (!tp->tap.hold_tap_enabled &&
tp->tap.drag_state == DRAG_STATE_IDLE)
tp->tap.state = TAP_STATE_TOUCH_3_HOLD;
else
tp->tap.state = TAP_STATE_DEAD;
tp_gesture_tap_timeout(tp, time);
break;
case TAP_EVENT_RELEASE:
tp->tap.state = TAP_STATE_TOUCH_3_RELEASE;
tp->tap.saved_release_time = time;
tp_tap_set_timer(tp, time);
break;
case TAP_EVENT_BUTTON:
tp->tap.state = TAP_STATE_BUTTON;
break;
@ -1026,7 +1100,8 @@ tp_tap_touch3_handle_event(struct tp_dispatch *tp,
case TAP_EVENT_THUMB:
break;
case TAP_EVENT_PALM:
tp->tap.state = TAP_STATE_TOUCH_2;
if (t->tap.state == TAP_TOUCH_STATE_TOUCH)
tp->tap.state = TAP_STATE_TOUCH_2;
break;
case TAP_EVENT_1FGTAP:
case TAP_EVENT_2FGTAP:
@ -1081,28 +1156,41 @@ tp_tap_touch3_release_handle_event(struct tp_dispatch *tp,
switch (event) {
case TAP_EVENT_TOUCH:
tp_drag_handle_event(tp, t, TAP_EVENT_3FGTAP, time);
tp->tap.state = TAP_STATE_TOUCH_3;
if (tp->tap.hold_tap_enabled) {
tp_tap_kill_all_touches(tp, t);
tp->tap.state = TAP_STATE_TOUCH;
} else {
tp_drag_handle_event(tp, t, TAP_EVENT_3FGTAP, time);
tp->tap.state = TAP_STATE_TOUCH_3;
}
tp->tap.saved_press_time = time;
tp_tap_set_timer(tp, time);
break;
case TAP_EVENT_RELEASE:
tp->tap.state = TAP_STATE_TOUCH_3_RELEASE_2;
tp_tap_set_timer(tp, time);
if (t->tap.state == TAP_TOUCH_STATE_TOUCH) {
tp->tap.state = TAP_STATE_TOUCH_3_RELEASE_2;
tp_tap_set_timer(tp, time);
}
break;
case TAP_EVENT_MOTION:
tp_drag_handle_event(tp, t, TAP_EVENT_3FGTAP, time);
if (!tp->tap.hold_tap_enabled)
tp_drag_handle_event(tp, t, TAP_EVENT_3FGTAP, time);
tp->tap.state = TAP_STATE_DEAD;
break;
case TAP_EVENT_TIMEOUT:
tp_drag_handle_event(tp, t, TAP_EVENT_3FGTAP, time);
if (tp->tap.drag_state == DRAG_STATE_IDLE)
tp->tap.state = TAP_STATE_TOUCH_2_HOLD;
else
if (tp->tap.hold_tap_enabled)
tp->tap.state = TAP_STATE_DEAD;
else {
tp_drag_handle_event(tp, t, TAP_EVENT_3FGTAP, time);
if (tp->tap.drag_state == DRAG_STATE_IDLE)
tp->tap.state = TAP_STATE_TOUCH_2_HOLD;
else
tp->tap.state = TAP_STATE_DEAD;
}
break;
case TAP_EVENT_BUTTON:
tp_drag_handle_event(tp, t, TAP_EVENT_3FGTAP, time);
if (!tp->tap.hold_tap_enabled)
tp_drag_handle_event(tp, t, TAP_EVENT_3FGTAP, time);
tp->tap.state = TAP_STATE_BUTTON;
break;
case TAP_EVENT_BUTTON_UP:
@ -1111,7 +1199,8 @@ tp_tap_touch3_release_handle_event(struct tp_dispatch *tp,
case TAP_EVENT_THUMB:
break;
case TAP_EVENT_PALM:
tp->tap.state = TAP_STATE_TOUCH_2_RELEASE;
if (t->tap.state == TAP_TOUCH_STATE_TOUCH)
tp->tap.state = TAP_STATE_TOUCH_2_RELEASE;
break;
case TAP_EVENT_1FGTAP:
case TAP_EVENT_2FGTAP:
@ -1129,28 +1218,44 @@ tp_tap_touch3_release2_handle_event(struct tp_dispatch *tp,
switch (event) {
case TAP_EVENT_TOUCH:
tp_drag_handle_event(tp, t, TAP_EVENT_3FGTAP, time);
tp->tap.state = TAP_STATE_TOUCH_2;
if (tp->tap.hold_tap_enabled) {
tp_tap_kill_all_touches(tp, t);
tp->tap.state = TAP_STATE_TOUCH;
} else {
tp_drag_handle_event(tp, t, TAP_EVENT_3FGTAP, time);
tp->tap.state = TAP_STATE_TOUCH_2;
}
tp->tap.saved_press_time = time;
tp_tap_set_timer(tp, time);
break;
case TAP_EVENT_RELEASE:
tp_drag_handle_event(tp, t, TAP_EVENT_3FGTAP, time);
tp->tap.state = TAP_STATE_IDLE;
if (t->tap.state == TAP_TOUCH_STATE_TOUCH) {
tp_drag_handle_event(tp, t, TAP_EVENT_3FGTAP, time);
if (tp->tap.nfingers_down == 0)
tp->tap.state = TAP_STATE_IDLE;
else
tp->tap.state = TAP_STATE_DEAD;
}
break;
case TAP_EVENT_MOTION:
tp_drag_handle_event(tp, t, TAP_EVENT_3FGTAP, time);
if (!tp->tap.hold_tap_enabled)
tp_drag_handle_event(tp, t, TAP_EVENT_3FGTAP, time);
tp->tap.state = TAP_STATE_DEAD;
break;
case TAP_EVENT_TIMEOUT:
tp_drag_handle_event(tp, t, TAP_EVENT_3FGTAP, time);
if (tp->tap.drag_state == DRAG_STATE_IDLE)
tp->tap.state = TAP_STATE_HOLD;
else
if (tp->tap.hold_tap_enabled)
tp->tap.state = TAP_STATE_DEAD;
else {
tp_drag_handle_event(tp, t, TAP_EVENT_3FGTAP, time);
if (tp->tap.drag_state == DRAG_STATE_IDLE)
tp->tap.state = TAP_STATE_HOLD;
else
tp->tap.state = TAP_STATE_DEAD;
}
break;
case TAP_EVENT_BUTTON:
tp_drag_handle_event(tp, t, TAP_EVENT_3FGTAP, time);
if (!tp->tap.hold_tap_enabled)
tp_drag_handle_event(tp, t, TAP_EVENT_3FGTAP, time);
tp->tap.state = TAP_STATE_BUTTON;
break;
case TAP_EVENT_BUTTON_UP:
@ -1159,8 +1264,13 @@ tp_tap_touch3_release2_handle_event(struct tp_dispatch *tp,
case TAP_EVENT_THUMB:
break;
case TAP_EVENT_PALM:
tp_drag_handle_event(tp, t, TAP_EVENT_2FGTAP, time);
tp->tap.state = TAP_STATE_IDLE;
if (t->tap.state == TAP_TOUCH_STATE_TOUCH) {
tp_drag_handle_event(tp, t, TAP_EVENT_2FGTAP, time);
if (tp->tap.nfingers_down == 0)
tp->tap.state = TAP_STATE_IDLE;
else
tp->tap.state = TAP_STATE_DEAD;
}
break;
case TAP_EVENT_1FGTAP:
case TAP_EVENT_2FGTAP:
@ -1211,11 +1321,17 @@ tp_tap_dead_handle_event(struct tp_dispatch *tp,
{
switch (event) {
case TAP_EVENT_TOUCH:
if (tp->tap.hold_tap_enabled) {
tp->tap.state = TAP_STATE_TOUCH;
tp->tap.saved_press_time = time;
tp_tap_set_timer(tp, time);
}
break;
case TAP_EVENT_RELEASE:
if (tp->tap.nfingers_down == 0)
tp->tap.state = TAP_STATE_IDLE;
break;
case TAP_EVENT_TOUCH:
case TAP_EVENT_MOTION:
case TAP_EVENT_TIMEOUT:
break;
@ -1318,7 +1434,7 @@ tp_tap_handle_event(struct tp_dispatch *tp,
if ((tp->tap.state == TAP_STATE_IDLE ||
tp->tap.state == TAP_STATE_BUTTON ||
tp->tap.state == TAP_STATE_DEAD))
tp_tap_kill_all_touches(tp);
tp_tap_kill_all_touches(tp, NULL);
}
static bool
@ -1394,12 +1510,12 @@ tp_tap_handle_state(struct tp_dispatch *tp, uint64_t time)
if (t->palm.state != PALM_NONE) {
assert(!t->tap.is_palm);
t->tap.is_palm = true;
t->tap.state = TAP_TOUCH_STATE_DEAD;
if (t->state != TOUCH_BEGIN) {
assert(tp->tap.nfingers_down > 0);
tp->tap.nfingers_down--;
tp_tap_handle_event(tp, t, TAP_EVENT_PALM, time);
}
t->tap.state = TAP_TOUCH_STATE_DEAD;
} else if (t->state == TOUCH_BEGIN) {
/* The simple version: if a touch is a thumb on
* begin we ignore it. All other thumb touches
@ -1428,8 +1544,10 @@ tp_tap_handle_state(struct tp_dispatch *tp, uint64_t time)
/* Any touch exceeding the threshold turns all
* touches into DEAD */
tp_tap_kill_all_touches(tp);
tp_tap_handle_event(tp, t, TAP_EVENT_MOTION, time);
if (t->tap.state == TAP_TOUCH_STATE_TOUCH) {
tp_tap_kill_all_touches(tp, NULL);
tp_tap_handle_event(tp, t, TAP_EVENT_MOTION, time);
}
}
}
@ -1466,7 +1584,14 @@ tp_tap_handle_state(struct tp_dispatch *tp, uint64_t time)
}
assert(tp->tap.nfingers_down <= tp->nfingers_down);
if (tp->nfingers_down == 0)
if (tp->nfingers_down == 0 ||
tp->tap.state == TAP_STATE_IDLE ||
tp->tap.drag_state == DRAG_STATE_1FGTAP_TAPPED ||
tp->tap.drag_state == DRAG_STATE_2FGTAP_TAPPED ||
tp->tap.drag_state == DRAG_STATE_3FGTAP_TAPPED ||
tp->tap.drag_state == DRAG_STATE_1FGTAP_DRAGLOCK_WAIT ||
tp->tap.drag_state == DRAG_STATE_2FGTAP_DRAGLOCK_WAIT ||
tp->tap.drag_state == DRAG_STATE_3FGTAP_DRAGLOCK_WAIT)
assert(tp->tap.nfingers_down == 0);
return filter_motion;