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