From 696d3c71f365af985d24b76344b3818d744ba365 Mon Sep 17 00:00:00 2001
From: satrmb
<10471-satrmb_true-email-is-private_contact-via-web@gitlab.freedesktop.org>
Date: Fri, 7 Aug 2020 17:52:20 +0200
Subject: [PATCH] touchpad: if hold-and-tap is on, treat half-released taps as
lesser taps
The expectation is that the user means the remaining fingers of the tap
to be holding fingers which just happened to be put down at roughly
the same time as the released tapping fingers.
This necessitates handling a tap with four or more fingers, because only
three of them might get lifted, making it actually a three-finger tap
and at least one more holding finger.
Though not strictly necessary, these states can also be reached
if hold-and-tap is off: it may catch a many-finger-tap to end drag-lock,
or a three-finger-tap joined by a palm, as demonstrated in the
previously existing test case touchpad_tap_palm_on_touch_4.
Signed-off-by: satrmb <10471-satrmb@users.noreply.gitlab.freedesktop.org>
---
doc/touchpad-tap-state-machine.svg | 2 +-
src/evdev-mt-touchpad-tap.c | 486 +++++++++++++++++++++++++++--
src/evdev-mt-touchpad.h | 5 +
test/test-touchpad-tap.c | 8 +
4 files changed, 468 insertions(+), 33 deletions(-)
diff --git a/doc/touchpad-tap-state-machine.svg b/doc/touchpad-tap-state-machine.svg
index 45b90d4d..6887559f 100644
--- a/doc/touchpad-tap-state-machine.svg
+++ b/doc/touchpad-tap-state-machine.svg
@@ -1,3 +1,3 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/src/evdev-mt-touchpad-tap.c b/src/evdev-mt-touchpad-tap.c
index c819127d..8fa527b0 100644
--- a/src/evdev-mt-touchpad-tap.c
+++ b/src/evdev-mt-touchpad-tap.c
@@ -72,6 +72,11 @@ tap_state_to_str(enum tp_tap_state state)
CASE_RETURN_STRING(TAP_STATE_TOUCH_3_HOLD);
CASE_RETURN_STRING(TAP_STATE_TOUCH_3_RELEASE);
CASE_RETURN_STRING(TAP_STATE_TOUCH_3_RELEASE_2);
+ CASE_RETURN_STRING(TAP_STATE_TOUCH_4_PLUS);
+ CASE_RETURN_STRING(TAP_STATE_TOUCH_4_PLUS_HOLD);
+ CASE_RETURN_STRING(TAP_STATE_TOUCH_4_PLUS_RELEASE);
+ CASE_RETURN_STRING(TAP_STATE_TOUCH_4_PLUS_RELEASE_2);
+ CASE_RETURN_STRING(TAP_STATE_TOUCH_4_PLUS_RELEASE_3);
CASE_RETURN_STRING(TAP_STATE_BUTTON);
CASE_RETURN_STRING(TAP_STATE_DEAD);
}
@@ -240,8 +245,10 @@ tp_drag_idle_handle_event(struct tp_dispatch *tp,
1,
LIBINPUT_BUTTON_STATE_PRESSED);
/* taps may end with fingers remaining down,
- * those should not start a drag */
- if (tp->tap.drag_enabled && tp->tap.nfingers_down == 0) {
+ * those should not start a drag;
+ * also, the touch in the process of being released
+ * is still counted for nfingers_down */
+ if (tp->tap.drag_enabled && tp->tap.nfingers_down == 1) {
tp->tap.drag_state = DRAG_STATE_1FGTAP_TAPPED;
tp_tap_set_drag_timer(tp, time, 1);
} else {
@@ -256,7 +263,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 && tp->tap.nfingers_down == 0) {
+ if (tp->tap.drag_enabled && tp->tap.nfingers_down == 1) {
tp->tap.drag_state = DRAG_STATE_2FGTAP_TAPPED;
tp_tap_set_drag_timer(tp, time, 2);
} else {
@@ -271,7 +278,7 @@ tp_drag_idle_handle_event(struct tp_dispatch *tp,
tp->tap.saved_press_time,
3,
LIBINPUT_BUTTON_STATE_PRESSED);
- if (tp->tap.drag_enabled && tp->tap.nfingers_down == 0) {
+ if (tp->tap.drag_enabled && tp->tap.nfingers_down == 1) {
tp->tap.drag_state = DRAG_STATE_3FGTAP_TAPPED;
tp_tap_set_drag_timer(tp, time, 3);
} else {
@@ -434,7 +441,7 @@ tp_drag_dragging_handle_event(struct tp_dispatch *tp,
case TAP_EVENT_TOUCH:
break;
case TAP_EVENT_RELEASE:
- if (tp->tap.nfingers_down == 0) {
+ if (tp->tap.nfingers_down == 1) {
if (tp->tap.drag_lock != LIBINPUT_CONFIG_DRAG_LOCK_DISABLED) {
enum tp_drag_state dest[3] = {
DRAG_STATE_1FGTAP_DRAGLOCK_WAIT,
@@ -471,7 +478,7 @@ tp_drag_dragging_handle_event(struct tp_dispatch *tp,
case TAP_EVENT_THUMB:
break;
case TAP_EVENT_PALM:
- if (tp->tap.nfingers_down == 0) {
+ if (tp->tap.nfingers_down == 1) {
tp_tap_notify(tp,
time,
nfingers_tapped,
@@ -814,7 +821,7 @@ tp_tap_touch_handle_event(struct tp_dispatch *tp,
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)
+ if (tp->tap.nfingers_down == 1)
tp->tap.state = TAP_STATE_IDLE;
else
tp->tap.state = TAP_STATE_DEAD;
@@ -847,7 +854,7 @@ tp_tap_touch_handle_event(struct tp_dispatch *tp,
break;
case TAP_EVENT_PALM:
if (t->tap.state == TAP_TOUCH_STATE_TOUCH) {
- if (tp->tap.nfingers_down == 0)
+ if (tp->tap.nfingers_down == 1)
tp->tap.state = TAP_STATE_IDLE;
else
tp->tap.state = TAP_STATE_DEAD;
@@ -1001,6 +1008,7 @@ tp_tap_touch2_release_handle_event(struct tp_dispatch *tp,
switch (event) {
case TAP_EVENT_TOUCH:
if (tp->tap.hold_tap_enabled) {
+ tp_drag_handle_event(tp, t, TAP_EVENT_1FGTAP, time);
tp_tap_kill_all_touches(tp, t);
tp->tap.state = TAP_STATE_TOUCH;
} else
@@ -1011,23 +1019,29 @@ tp_tap_touch2_release_handle_event(struct tp_dispatch *tp,
case TAP_EVENT_RELEASE:
if (t->tap.state == TAP_TOUCH_STATE_TOUCH) {
tp_drag_handle_event(tp, t, TAP_EVENT_2FGTAP, time);
- if (tp->tap.nfingers_down == 0)
+ if (tp->tap.nfingers_down == 1)
tp->tap.state = TAP_STATE_IDLE;
else
tp->tap.state = TAP_STATE_DEAD;
}
break;
case TAP_EVENT_MOTION:
+ if (tp->tap.hold_tap_enabled)
+ tp_drag_handle_event(tp, t, TAP_EVENT_1FGTAP, time);
tp->tap.state = TAP_STATE_DEAD;
break;
case TAP_EVENT_TIMEOUT:
- if (!tp->tap.hold_tap_enabled &&
- tp->tap.drag_state == DRAG_STATE_IDLE)
+ if (tp->tap.hold_tap_enabled) {
+ tp_drag_handle_event(tp, t, TAP_EVENT_1FGTAP, time);
+ tp->tap.state = TAP_STATE_DEAD;
+ } else 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:
+ if (tp->tap.hold_tap_enabled)
+ tp_drag_handle_event(tp, t, TAP_EVENT_1FGTAP, time);
tp->tap.state = TAP_STATE_BUTTON;
break;
case TAP_EVENT_BUTTON_UP:
@@ -1046,7 +1060,7 @@ tp_tap_touch2_release_handle_event(struct tp_dispatch *tp,
* an issue.
*/
tp_drag_handle_event(tp, t, TAP_EVENT_1FGTAP, time);
- if (tp->tap.nfingers_down == 0)
+ if (tp->tap.nfingers_down == 1)
tp->tap.state = TAP_STATE_IDLE;
else
tp->tap.state = TAP_STATE_DEAD;
@@ -1068,10 +1082,9 @@ tp_tap_touch3_handle_event(struct tp_dispatch *tp,
switch (event) {
case TAP_EVENT_TOUCH:
- tp->tap.state = TAP_STATE_DEAD;
- /* this cannot be a tap anymore, get the dragging state machine
- * out of DRAGGING_OR_DOUBLETAP or DRAGLOCK_CONTINUE */
- tp_drag_handle_event(tp, t, TAP_EVENT_MOTION, time);
+ tp->tap.state = TAP_STATE_TOUCH_4_PLUS;
+ tp->tap.saved_press_time = time;
+ tp_tap_set_timer(tp, time);
break;
case TAP_EVENT_RELEASE:
if (t->tap.state == TAP_TOUCH_STATE_TOUCH) {
@@ -1119,7 +1132,11 @@ tp_tap_touch3_hold_handle_event(struct tp_dispatch *tp,
switch (event) {
case TAP_EVENT_TOUCH:
- tp->tap.state = TAP_STATE_DEAD;
+ /* Don't even try to revive the previous three touches
+ * for a tap like in the other hold states, because any tap
+ * with more than three fingers can only end drag-lock.
+ * If we got here the timeout for that has already elapsed. */
+ tp->tap.state = TAP_STATE_TOUCH_4_PLUS_HOLD;
break;
case TAP_EVENT_RELEASE:
tp->tap.state = TAP_STATE_TOUCH_2_HOLD;
@@ -1157,6 +1174,7 @@ tp_tap_touch3_release_handle_event(struct tp_dispatch *tp,
switch (event) {
case TAP_EVENT_TOUCH:
if (tp->tap.hold_tap_enabled) {
+ tp_drag_handle_event(tp, t, TAP_EVENT_1FGTAP, time);
tp_tap_kill_all_touches(tp, t);
tp->tap.state = TAP_STATE_TOUCH;
} else {
@@ -1173,14 +1191,17 @@ tp_tap_touch3_release_handle_event(struct tp_dispatch *tp,
}
break;
case TAP_EVENT_MOTION:
- if (!tp->tap.hold_tap_enabled)
+ if (tp->tap.hold_tap_enabled)
+ tp_drag_handle_event(tp, t, TAP_EVENT_1FGTAP, time);
+ else
tp_drag_handle_event(tp, t, TAP_EVENT_3FGTAP, time);
tp->tap.state = TAP_STATE_DEAD;
break;
case TAP_EVENT_TIMEOUT:
- if (tp->tap.hold_tap_enabled)
+ if (tp->tap.hold_tap_enabled) {
+ tp_drag_handle_event(tp, t, TAP_EVENT_1FGTAP, time);
tp->tap.state = TAP_STATE_DEAD;
- else {
+ } 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;
@@ -1189,7 +1210,9 @@ tp_tap_touch3_release_handle_event(struct tp_dispatch *tp,
}
break;
case TAP_EVENT_BUTTON:
- if (!tp->tap.hold_tap_enabled)
+ if (tp->tap.hold_tap_enabled)
+ tp_drag_handle_event(tp, t, TAP_EVENT_1FGTAP, time);
+ else
tp_drag_handle_event(tp, t, TAP_EVENT_3FGTAP, time);
tp->tap.state = TAP_STATE_BUTTON;
break;
@@ -1219,6 +1242,7 @@ tp_tap_touch3_release2_handle_event(struct tp_dispatch *tp,
switch (event) {
case TAP_EVENT_TOUCH:
if (tp->tap.hold_tap_enabled) {
+ tp_drag_handle_event(tp, t, TAP_EVENT_2FGTAP, time);
tp_tap_kill_all_touches(tp, t);
tp->tap.state = TAP_STATE_TOUCH;
} else {
@@ -1231,21 +1255,24 @@ tp_tap_touch3_release2_handle_event(struct tp_dispatch *tp,
case TAP_EVENT_RELEASE:
if (t->tap.state == TAP_TOUCH_STATE_TOUCH) {
tp_drag_handle_event(tp, t, TAP_EVENT_3FGTAP, time);
- if (tp->tap.nfingers_down == 0)
+ if (tp->tap.nfingers_down == 1)
tp->tap.state = TAP_STATE_IDLE;
else
tp->tap.state = TAP_STATE_DEAD;
}
break;
case TAP_EVENT_MOTION:
- if (!tp->tap.hold_tap_enabled)
+ if (tp->tap.hold_tap_enabled)
+ tp_drag_handle_event(tp, t, TAP_EVENT_2FGTAP, time);
+ else
tp_drag_handle_event(tp, t, TAP_EVENT_3FGTAP, time);
tp->tap.state = TAP_STATE_DEAD;
break;
case TAP_EVENT_TIMEOUT:
- if (tp->tap.hold_tap_enabled)
+ if (tp->tap.hold_tap_enabled) {
+ tp_drag_handle_event(tp, t, TAP_EVENT_2FGTAP, time);
tp->tap.state = TAP_STATE_DEAD;
- else {
+ } 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;
@@ -1254,7 +1281,9 @@ tp_tap_touch3_release2_handle_event(struct tp_dispatch *tp,
}
break;
case TAP_EVENT_BUTTON:
- if (!tp->tap.hold_tap_enabled)
+ if (tp->tap.hold_tap_enabled)
+ tp_drag_handle_event(tp, t, TAP_EVENT_2FGTAP, time);
+ else
tp_drag_handle_event(tp, t, TAP_EVENT_3FGTAP, time);
tp->tap.state = TAP_STATE_BUTTON;
break;
@@ -1266,7 +1295,7 @@ tp_tap_touch3_release2_handle_event(struct tp_dispatch *tp,
case TAP_EVENT_PALM:
if (t->tap.state == TAP_TOUCH_STATE_TOUCH) {
tp_drag_handle_event(tp, t, TAP_EVENT_2FGTAP, time);
- if (tp->tap.nfingers_down == 0)
+ if (tp->tap.nfingers_down == 1)
tp->tap.state = TAP_STATE_IDLE;
else
tp->tap.state = TAP_STATE_DEAD;
@@ -1280,6 +1309,384 @@ tp_tap_touch3_release2_handle_event(struct tp_dispatch *tp,
}
}
+static int
+tp_count_alive_touches(struct tp_dispatch *tp)
+{
+ /* In the TOUCH_4_PLUS group of states we lose track of the exact
+ * number of fingers down which are still alive for tapping.
+ * There are several relatively unlikely events that may bring us back
+ * into the states corresponding to a smaller number of fingers,
+ * most prominently touches turning into palms.
+ * In these cases we need to figure out how many remaining
+ * alive touches there are. */
+ struct tp_touch *t;
+ int nfingers = 0;
+
+ /* When hold-and-tap is disabled, either all or no fingers are tapping,
+ * and the latter case occurs in the DEAD and BUTTON states only
+ * which don't have the need to ask for alive touches,
+ * so we have the count stored as the number of fingers. */
+ if (!tp->tap.hold_tap_enabled)
+ return tp->tap.nfingers_down;
+
+ /* If the setting is enabled however, tapping can happen with only some
+ * of the fingers down, i.e. there may be to-be-ignored holding touches,
+ * so we need to actually count the tapping ones. */
+ tp_for_each_touch(tp, t) {
+ if (t->tap.state == TAP_TOUCH_STATE_TOUCH)
+ nfingers++;
+ }
+ return nfingers;
+}
+
+static void
+tp_tap_touch4plus_handle_event(struct tp_dispatch *tp,
+ struct tp_touch *t,
+ enum tap_event event, uint64_t time)
+{
+
+ switch (event) {
+ case TAP_EVENT_TOUCH:
+ tp->tap.saved_press_time = time;
+ tp_tap_set_timer(tp, time);
+ break;
+ case TAP_EVENT_RELEASE:
+ if (t->tap.state == TAP_TOUCH_STATE_TOUCH) {
+ tp->tap.state = TAP_STATE_TOUCH_4_PLUS_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.hold_tap_enabled &&
+ tp->tap.drag_state == DRAG_STATE_IDLE)
+ tp->tap.state = TAP_STATE_TOUCH_4_PLUS_HOLD;
+ else
+ tp->tap.state = TAP_STATE_DEAD;
+ break;
+ case TAP_EVENT_BUTTON:
+ tp->tap.state = TAP_STATE_BUTTON;
+ break;
+ case TAP_EVENT_BUTTON_UP:
+ log_tap_bug(tp, t, event);
+ break;
+ case TAP_EVENT_THUMB:
+ break;
+ case TAP_EVENT_PALM:
+ if (t->tap.state == TAP_TOUCH_STATE_TOUCH) {
+ int remaining = tp_count_alive_touches(tp) - 1;
+ assert(remaining >= 3);
+ if (remaining == 3)
+ tp->tap.state = TAP_STATE_TOUCH_3;
+ }
+ break;
+ case TAP_EVENT_1FGTAP:
+ case TAP_EVENT_2FGTAP:
+ case TAP_EVENT_3FGTAP:
+ log_tap_bug(tp, t, event);
+ break;
+ }
+}
+
+static void
+tp_tap_touch4plus_hold_handle_event(struct tp_dispatch *tp,
+ struct tp_touch *t,
+ enum tap_event event, uint64_t time)
+{
+
+ switch (event) {
+ case TAP_EVENT_TOUCH:
+ break;
+ case TAP_EVENT_RELEASE: {
+ int remaining = tp_count_alive_touches(tp) - 1;
+ assert(remaining >= 3);
+ if (remaining == 3)
+ tp->tap.state = TAP_STATE_TOUCH_3_HOLD;
+ break;
+ }
+ case TAP_EVENT_MOTION:
+ tp->tap.state = TAP_STATE_DEAD;
+ break;
+ case TAP_EVENT_TIMEOUT:
+ break;
+ case TAP_EVENT_BUTTON:
+ tp->tap.state = TAP_STATE_BUTTON;
+ break;
+ case TAP_EVENT_BUTTON_UP:
+ log_tap_bug(tp, t, event);
+ break;
+ case TAP_EVENT_THUMB:
+ break;
+ case TAP_EVENT_PALM: {
+ int remaining = tp_count_alive_touches(tp) - 1;
+ assert(remaining >= 3);
+ if (remaining == 3)
+ tp->tap.state = TAP_STATE_TOUCH_3_HOLD;
+ break;
+ }
+ case TAP_EVENT_1FGTAP:
+ case TAP_EVENT_2FGTAP:
+ case TAP_EVENT_3FGTAP:
+ log_tap_bug(tp, t, event);
+ break;
+ }
+}
+
+static void
+tp_tap_touch4plus_release_handle_event(struct tp_dispatch *tp,
+ struct tp_touch *t,
+ enum tap_event event, uint64_t time)
+{
+
+ switch (event) {
+ case TAP_EVENT_TOUCH:
+ if (tp->tap.hold_tap_enabled) {
+ tp_drag_handle_event(tp, t, TAP_EVENT_1FGTAP, time);
+ tp_tap_kill_all_touches(tp, t);
+ tp->tap.state = TAP_STATE_TOUCH;
+ } else
+ tp->tap.state = TAP_STATE_TOUCH_4_PLUS;
+ tp->tap.saved_press_time = time;
+ tp_tap_set_timer(tp, time);
+ break;
+ case TAP_EVENT_RELEASE:
+ if (t->tap.state == TAP_TOUCH_STATE_TOUCH) {
+ tp->tap.state = TAP_STATE_TOUCH_4_PLUS_RELEASE_2;
+ tp_tap_set_timer(tp, time);
+ }
+ break;
+ case TAP_EVENT_MOTION:
+ if (tp->tap.hold_tap_enabled)
+ tp_drag_handle_event(tp, t, TAP_EVENT_1FGTAP, time);
+ tp->tap.state = TAP_STATE_DEAD;
+ break;
+ case TAP_EVENT_TIMEOUT:
+ if (tp->tap.hold_tap_enabled) {
+ tp_drag_handle_event(tp, t, TAP_EVENT_1FGTAP, time);
+ tp->tap.state = TAP_STATE_DEAD;
+ } else {
+ if (tp->tap.drag_state == DRAG_STATE_IDLE) {
+ int remaining = tp_count_alive_touches(tp);
+ assert(remaining >= 3);
+ if (remaining == 3)
+ tp->tap.state = TAP_STATE_TOUCH_3_HOLD;
+ else
+ tp->tap.state = TAP_STATE_TOUCH_4_PLUS_HOLD;
+ } else
+ tp->tap.state = TAP_STATE_DEAD;
+ }
+ break;
+ case TAP_EVENT_BUTTON:
+ if (tp->tap.hold_tap_enabled)
+ tp_drag_handle_event(tp, t, TAP_EVENT_1FGTAP, time);
+ tp->tap.state = TAP_STATE_BUTTON;
+ break;
+ case TAP_EVENT_BUTTON_UP:
+ log_tap_bug(tp, t, event);
+ break;
+ case TAP_EVENT_THUMB:
+ break;
+ case TAP_EVENT_PALM:
+ if (t->tap.state == TAP_TOUCH_STATE_TOUCH) {
+ int remaining = tp_count_alive_touches(tp) - 1;
+ assert(remaining >= 2);
+ if (remaining == 2)
+ tp->tap.state = TAP_STATE_TOUCH_3_RELEASE;
+ }
+ break;
+ case TAP_EVENT_1FGTAP:
+ case TAP_EVENT_2FGTAP:
+ case TAP_EVENT_3FGTAP:
+ log_tap_bug(tp, t, event);
+ break;
+ }
+}
+
+static void
+tp_tap_touch4plus_release2_handle_event(struct tp_dispatch *tp,
+ struct tp_touch *t,
+ enum tap_event event, uint64_t time)
+{
+
+ switch (event) {
+ case TAP_EVENT_TOUCH:
+ if (tp->tap.hold_tap_enabled) {
+ tp_drag_handle_event(tp, t, TAP_EVENT_2FGTAP, time);
+ tp_tap_kill_all_touches(tp, t);
+ tp->tap.state = TAP_STATE_TOUCH;
+ } else {
+ int remaining = tp_count_alive_touches(tp);
+ assert(remaining >= 3);
+ if (remaining == 3)
+ tp->tap.state = TAP_STATE_TOUCH_3;
+ else
+ tp->tap.state = TAP_STATE_TOUCH_4_PLUS;
+ }
+ tp->tap.saved_press_time = time;
+ tp_tap_set_timer(tp, time);
+ break;
+ case TAP_EVENT_RELEASE:
+ if (t->tap.state == TAP_TOUCH_STATE_TOUCH) {
+ tp->tap.state = TAP_STATE_TOUCH_4_PLUS_RELEASE_3;
+ tp_tap_set_timer(tp, time);
+ }
+ break;
+ case TAP_EVENT_MOTION:
+ if (tp->tap.hold_tap_enabled)
+ tp_drag_handle_event(tp, t, TAP_EVENT_2FGTAP, time);
+ tp->tap.state = TAP_STATE_DEAD;
+ break;
+ case TAP_EVENT_TIMEOUT:
+ if (tp->tap.hold_tap_enabled) {
+ tp_drag_handle_event(tp, t, TAP_EVENT_2FGTAP, time);
+ tp->tap.state = TAP_STATE_DEAD;
+ } else {
+ if (tp->tap.drag_state == DRAG_STATE_IDLE) {
+ int remaining = min(tp_count_alive_touches(tp),
+ 4);
+ enum tp_tap_state dest[3] = {
+ TAP_STATE_TOUCH_2_HOLD,
+ TAP_STATE_TOUCH_3_HOLD,
+ TAP_STATE_TOUCH_4_PLUS_HOLD,
+ };
+ assert(remaining >= 2);
+ tp->tap.state = dest[remaining - 2];
+ } else
+ tp->tap.state = TAP_STATE_DEAD;
+ }
+ break;
+ case TAP_EVENT_BUTTON:
+ if (tp->tap.hold_tap_enabled)
+ tp_drag_handle_event(tp, t, TAP_EVENT_2FGTAP, time);
+ tp->tap.state = TAP_STATE_BUTTON;
+ break;
+ case TAP_EVENT_BUTTON_UP:
+ log_tap_bug(tp, t, event);
+ break;
+ case TAP_EVENT_THUMB:
+ break;
+ case TAP_EVENT_PALM:
+ if (t->tap.state == TAP_TOUCH_STATE_TOUCH) {
+ int remaining = tp_count_alive_touches(tp) - 1;
+ assert(remaining >= 1);
+ if (remaining == 1)
+ tp->tap.state = TAP_STATE_TOUCH_3_RELEASE_2;
+ }
+ break;
+ case TAP_EVENT_1FGTAP:
+ case TAP_EVENT_2FGTAP:
+ case TAP_EVENT_3FGTAP:
+ log_tap_bug(tp, t, event);
+ break;
+ }
+}
+
+static void
+tp_tap_touch4plus_release3_handle_event(struct tp_dispatch *tp,
+ struct tp_touch *t,
+ enum tap_event event, uint64_t time)
+{
+
+ switch (event) {
+ case TAP_EVENT_TOUCH:
+ if (tp->tap.hold_tap_enabled) {
+ tp_drag_handle_event(tp, t, TAP_EVENT_3FGTAP, time);
+ tp_tap_kill_all_touches(tp, t);
+ tp->tap.state = TAP_STATE_TOUCH;
+ } else {
+ int remaining = min(tp_count_alive_touches(tp),
+ 4);
+ enum tp_tap_state dest[3] = {
+ TAP_STATE_TOUCH_2,
+ TAP_STATE_TOUCH_3,
+ TAP_STATE_TOUCH_4_PLUS,
+ };
+ assert(remaining >= 2);
+ tp->tap.state = dest[remaining - 2];
+ }
+ tp->tap.saved_press_time = time;
+ tp_tap_set_timer(tp, time);
+ break;
+ case TAP_EVENT_RELEASE:
+ if (t->tap.state == TAP_TOUCH_STATE_TOUCH) {
+ /* four-plus-finger taps are to be ignored,
+ * except for the purpose of stopping drag-lock.
+ * We also don't care about remaining fingers
+ * of this tap anymore */
+ if (tp->tap.nfingers_down == 1)
+ tp->tap.state = TAP_STATE_IDLE;
+ else
+ tp->tap.state = TAP_STATE_DEAD;
+ if (tp->tap.drag_state == DRAG_STATE_1FGTAP_DRAGLOCK_CONTINUE ||
+ tp->tap.drag_state == DRAG_STATE_2FGTAP_DRAGLOCK_CONTINUE ||
+ tp->tap.drag_state == DRAG_STATE_3FGTAP_DRAGLOCK_CONTINUE) {
+ /* all taps have the same result here,
+ * a one-finger tap does the trick without
+ * introducing another event just for this */
+ tp_drag_handle_event(tp, t, TAP_EVENT_1FGTAP,
+ time);
+ }
+ }
+ break;
+ case TAP_EVENT_MOTION:
+ 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:
+ if (tp->tap.hold_tap_enabled) {
+ tp_drag_handle_event(tp, t, TAP_EVENT_3FGTAP, time);
+ tp->tap.state = TAP_STATE_DEAD;
+ } else {
+ if (tp->tap.drag_state == DRAG_STATE_IDLE) {
+ int remaining = min(tp_count_alive_touches(tp),
+ 4);
+ enum tp_tap_state dest[4] = {
+ TAP_STATE_HOLD,
+ TAP_STATE_TOUCH_2_HOLD,
+ TAP_STATE_TOUCH_3_HOLD,
+ TAP_STATE_TOUCH_4_PLUS_HOLD,
+ };
+ assert(remaining >= 1);
+ tp->tap.state = dest[remaining - 1];
+ } else
+ tp->tap.state = TAP_STATE_DEAD;
+ }
+ break;
+ case TAP_EVENT_BUTTON:
+ 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:
+ log_tap_bug(tp, t, event);
+ break;
+ case TAP_EVENT_THUMB:
+ break;
+ case TAP_EVENT_PALM:
+ if (t->tap.state == TAP_TOUCH_STATE_TOUCH) {
+ int remaining = tp_count_alive_touches(tp) - 1;
+ if (remaining == 0) {
+ tp_drag_handle_event(tp, t, TAP_EVENT_3FGTAP,
+ time);
+ if (tp->tap.nfingers_down == 1)
+ tp->tap.state = TAP_STATE_IDLE;
+ else
+ tp->tap.state = TAP_STATE_DEAD;
+ }
+ }
+ break;
+ case TAP_EVENT_1FGTAP:
+ case TAP_EVENT_2FGTAP:
+ case TAP_EVENT_3FGTAP:
+ log_tap_bug(tp, t, event);
+ break;
+ }
+}
+
static void
tp_tap_button_handle_event(struct tp_dispatch *tp,
struct tp_touch *t,
@@ -1329,7 +1736,7 @@ tp_tap_dead_handle_event(struct tp_dispatch *tp,
}
break;
case TAP_EVENT_RELEASE:
- if (tp->tap.nfingers_down == 0)
+ if (tp->tap.nfingers_down == 1)
tp->tap.state = TAP_STATE_IDLE;
break;
case TAP_EVENT_MOTION:
@@ -1344,7 +1751,7 @@ tp_tap_dead_handle_event(struct tp_dispatch *tp,
case TAP_EVENT_THUMB:
break;
case TAP_EVENT_PALM:
- if (tp->tap.nfingers_down == 0)
+ if (tp->tap.nfingers_down == 1)
tp->tap.state = TAP_STATE_IDLE;
break;
case TAP_EVENT_1FGTAP:
@@ -1396,6 +1803,21 @@ tp_tap_handle_event(struct tp_dispatch *tp,
case TAP_STATE_TOUCH_3_RELEASE_2:
tp_tap_touch3_release2_handle_event(tp, t, event, time);
break;
+ case TAP_STATE_TOUCH_4_PLUS:
+ tp_tap_touch4plus_handle_event(tp, t, event, time);
+ break;
+ case TAP_STATE_TOUCH_4_PLUS_HOLD:
+ tp_tap_touch4plus_hold_handle_event(tp, t, event, time);
+ break;
+ case TAP_STATE_TOUCH_4_PLUS_RELEASE:
+ tp_tap_touch4plus_release_handle_event(tp, t, event, time);
+ break;
+ case TAP_STATE_TOUCH_4_PLUS_RELEASE_2:
+ tp_tap_touch4plus_release2_handle_event(tp, t, event, time);
+ break;
+ case TAP_STATE_TOUCH_4_PLUS_RELEASE_3:
+ tp_tap_touch4plus_release3_handle_event(tp, t, event, time);
+ break;
case TAP_STATE_BUTTON:
tp_tap_button_handle_event(tp, t, event, time);
break;
@@ -1511,9 +1933,9 @@ tp_tap_handle_state(struct tp_dispatch *tp, uint64_t time)
assert(!t->tap.is_palm);
t->tap.is_palm = true;
if (t->state != TOUCH_BEGIN) {
- assert(tp->tap.nfingers_down > 0);
- tp->tap.nfingers_down--;
+ assert(tp->tap.nfingers_down >= 1);
tp_tap_handle_event(tp, t, TAP_EVENT_PALM, time);
+ tp->tap.nfingers_down--;
}
t->tap.state = TAP_TOUCH_STATE_DEAD;
} else if (t->state == TOUCH_BEGIN) {
@@ -1532,8 +1954,8 @@ tp_tap_handle_state(struct tp_dispatch *tp, uint64_t time)
} else if (t->state == TOUCH_END) {
if (t->was_down) {
assert(tp->tap.nfingers_down >= 1);
- tp->tap.nfingers_down--;
tp_tap_handle_event(tp, t, TAP_EVENT_RELEASE, time);
+ tp->tap.nfingers_down--;
}
t->tap.state = TAP_TOUCH_STATE_IDLE;
} else if (tp->tap.state != TAP_STATE_IDLE &&
diff --git a/src/evdev-mt-touchpad.h b/src/evdev-mt-touchpad.h
index 3e7f7d2c..1c601886 100644
--- a/src/evdev-mt-touchpad.h
+++ b/src/evdev-mt-touchpad.h
@@ -113,6 +113,11 @@ enum tp_tap_state {
TAP_STATE_TOUCH_3_HOLD,
TAP_STATE_TOUCH_3_RELEASE,
TAP_STATE_TOUCH_3_RELEASE_2,
+ TAP_STATE_TOUCH_4_PLUS,
+ TAP_STATE_TOUCH_4_PLUS_HOLD,
+ TAP_STATE_TOUCH_4_PLUS_RELEASE,
+ TAP_STATE_TOUCH_4_PLUS_RELEASE_2,
+ TAP_STATE_TOUCH_4_PLUS_RELEASE_3,
TAP_STATE_BUTTON, /**< clickpad button pressed */
TAP_STATE_DEAD, /**< clickpad button released, or finger moved */
};
diff --git a/test/test-touchpad-tap.c b/test/test-touchpad-tap.c
index 90f27c5c..591ea273 100644
--- a/test/test-touchpad-tap.c
+++ b/test/test-touchpad-tap.c
@@ -5146,6 +5146,14 @@ START_TEST(touchpad_tap_palm_on_touch_4)
litest_touch_up(dev, (this + 2) % 4);
litest_touch_up(dev, (this + 3) % 4);
+ litest_assert_button_event(li,
+ BTN_MIDDLE,
+ LIBINPUT_BUTTON_STATE_PRESSED);
+ litest_timeout_tap();
+ litest_assert_button_event(li,
+ BTN_MIDDLE,
+ LIBINPUT_BUTTON_STATE_RELEASED);
+
litest_assert_empty_queue(li);
}
END_TEST