From 5274f14b2403455e261649fcaa47ff529d8b366b Mon Sep 17 00:00:00 2001 From: Matt Mayfield Date: Sat, 7 Mar 2026 19:33:18 -0500 Subject: [PATCH 1/3] touchpad: restore thumb rejection/suppression closes #1191 --- src/evdev-mt-touchpad-thumb.c | 34 +++++++++++++++++++++++++++++++--- 1 file changed, 31 insertions(+), 3 deletions(-) diff --git a/src/evdev-mt-touchpad-thumb.c b/src/evdev-mt-touchpad-thumb.c index 876fe801..1fbc6e04 100644 --- a/src/evdev-mt-touchpad-thumb.c +++ b/src/evdev-mt-touchpad-thumb.c @@ -192,6 +192,34 @@ tp_thumb_revive(struct tp_dispatch *tp, struct tp_touch *t) tp_thumb_set_state(tp, t, THUMB_STATE_REVIVED); } +/* Intelligently reset thumb state... Unlike tp_thumb_lift(), this + * pays attention to suppression/revival/jailed states so that a thumb + * can't be revived more than once. + */ +static void +tp_thumb_reevaluate(struct tp_dispatch *tp) +{ + switch (tp->thumb.state) { + case THUMB_STATE_FINGER: + break; + case THUMB_STATE_JAILED: + case THUMB_STATE_PINCH: + tp->thumb.state = THUMB_STATE_FINGER; + tp->thumb.index = UINT_MAX; + break; + case THUMB_STATE_SUPPRESSED: { + struct tp_touch *thumb = tp_thumb_get_touch(tp); + if (thumb) + tp_thumb_revive(tp, thumb); + break; + } + case THUMB_STATE_REVIVED: + case THUMB_STATE_REVIVED_JAILED: + case THUMB_STATE_DEAD: + break; + } +} + void tp_thumb_update_touch(struct tp_dispatch *tp, struct tp_touch *t, usec_t time) { @@ -326,7 +354,7 @@ tp_thumb_update_multifinger(struct tp_dispatch *tp) */ if (mm.x < SCROLL_MM_X && mm.y < SCROLL_MM_Y) { - tp_thumb_lift(tp); + tp_thumb_reevaluate(tp); return; } @@ -341,7 +369,7 @@ tp_thumb_update_multifinger(struct tp_dispatch *tp) usec_t delta = usec_delta(newest->initial_time, oldest->initial_time); if (usec_cmp(delta, THUMB_TIMEOUT) < 0 && first->point.y < tp->thumb.lower_thumb_line) { - tp_thumb_lift(tp); + tp_thumb_reevaluate(tp); return; } } @@ -357,7 +385,7 @@ tp_thumb_update_multifinger(struct tp_dispatch *tp) else tp_thumb_suppress(tp, first); } else { - tp_thumb_lift(tp); + tp_thumb_reevaluate(tp); } } From 1e232e3f915d70f52a7c8737df512b5bbc822133 Mon Sep 17 00:00:00 2001 From: Matt Mayfield Date: Sat, 7 Mar 2026 20:07:05 -0500 Subject: [PATCH 2/3] test: ensure thumb can't be inappropriately re-revived --- test/test-touchpad.c | 70 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) diff --git a/test/test-touchpad.c b/test/test-touchpad.c index 9884da4b..8982fdb3 100644 --- a/test/test-touchpad.c +++ b/test/test-touchpad.c @@ -5459,6 +5459,75 @@ START_TEST(touchpad_thumb_no_doublethumb) } END_TEST +START_TEST(touchpad_thumb_no_double_revive) +{ + struct litest_device *dev = litest_current_device(); + struct libinput *li = dev->libinput; + + if (!has_thumb_detect(dev)) + return LITEST_NOT_APPLICABLE; + + litest_disable_tap(dev->libinput_device); + litest_disable_hold_gestures(dev->libinput_device); + + if (litest_has_clickfinger(dev)) + litest_enable_clickfinger(dev); + + litest_drain_events(li); + + /* 1. Thumb rests in thumb area, starts as JAILED */ + litest_touch_down(dev, 0, 50, 99); + litest_touch_move_to(dev, 0, 50, 99, 51, 99, 10); + litest_assert_empty_queue(li); + + /* 2. Finger touches far from thumb, makes a small movement + * Thumb becomes SUPPRESSED + */ + msleep(200); + litest_dispatch(li); + litest_touch_down(dev, 1, 50, 0); + litest_dispatch(li); + litest_touch_move_to(dev, 1, 50, 0, 51, 1, 10); + litest_dispatch(li); + litest_drain_events(li); + + /* 3. Lift the finger. Thumb is still on TP + */ + litest_touch_up(dev, 1); + litest_dispatch(li); + msleep(200); + + /* 4. Move the thumb significantly so it becomes REVIVED. We should + * now see pointer motion events. + */ + litest_touch_move_to(dev, 0, 51, 99, 80, 70, 20); + litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_MOTION); + + /* 5. A second finger touches & moves far away again. + * Thumb should become DEAD. + */ + msleep(200); + litest_dispatch(li); + litest_touch_down(dev, 1, 50, 0); + litest_dispatch(li); + litest_touch_move_to(dev, 1, 50, 0, 80, 1, 8); + litest_dispatch(li); + litest_drain_events(li); + + /* 6. Lift the second finger */ + litest_touch_up(dev, 1); + litest_drain_events(li); + + /* 7. Move the thumb again. It must now be DEAD! + * Should not show any motion + */ + litest_touch_move_to(dev, 0, 80, 70, 50, 30, 30); + litest_assert_empty_queue(li); + + litest_touch_up(dev, 0); +} +END_TEST + START_TEST(touchpad_tool_tripletap_touch_count) { struct litest_device *dev = litest_current_device(); @@ -7169,6 +7238,7 @@ TEST_COLLECTION(touchpad) litest_add(touchpad_thumb_area_clickfinger, LITEST_CLICKPAD, LITEST_ANY); litest_add(touchpad_thumb_area_btnarea, LITEST_CLICKPAD, LITEST_ANY); litest_add(touchpad_thumb_no_doublethumb, LITEST_CLICKPAD, LITEST_ANY); + litest_add(touchpad_thumb_no_double_revive, LITEST_CLICKPAD, LITEST_SINGLE_TOUCH|LITEST_SEMI_MT); litest_add_for_device(touchpad_tool_tripletap_touch_count, LITEST_SYNAPTICS_TOPBUTTONPAD); litest_add_for_device(touchpad_tool_tripletap_touch_count_late, LITEST_SYNAPTICS_TOPBUTTONPAD); From c17790d29bf5aad4399cba9d924e083ce5637ca7 Mon Sep 17 00:00:00 2001 From: Matt Mayfield Date: Sun, 8 Mar 2026 09:48:04 -0400 Subject: [PATCH 3/3] test: ghost touch test, based on HW recording --- test/test-touchpad.c | 75 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) diff --git a/test/test-touchpad.c b/test/test-touchpad.c index 8982fdb3..81cd0bc7 100644 --- a/test/test-touchpad.c +++ b/test/test-touchpad.c @@ -5528,6 +5528,80 @@ START_TEST(touchpad_thumb_no_double_revive) } END_TEST +START_TEST(touchpad_thumb_no_revive_after_ghost_touch) +{ + struct litest_device *dev = litest_current_device(); + struct libinput *li = dev->libinput; + + if (!has_thumb_detect(dev)) + return LITEST_NOT_APPLICABLE; + + litest_disable_tap(dev->libinput_device); + litest_disable_hold_gestures(dev->libinput_device); + + if (litest_has_clickfinger(dev)) + litest_enable_clickfinger(dev); + + litest_drain_events(li); + + /* 1. Thumb rests in thumb area at bottom center */ + litest_touch_down(dev, 0, 45, 97); + litest_touch_move_to(dev, 0, 45, 97, 45, 97, 10); + litest_assert_empty_queue(li); + + /* 2. Finger touches far above thumb and scrolls up */ + msleep(200); + litest_dispatch(li); + litest_touch_down(dev, 1, 46, 41); + litest_dispatch(li); + litest_touch_move_to(dev, 1, 46, 41, 49, 14, 15); + litest_dispatch(li); + litest_drain_events(li); + + /* 3. Lift the scroll finger */ + litest_touch_up(dev, 1); + litest_dispatch(li); + msleep(340); + + /* 4. Move the thumb significantly — it should become REVIVED */ + litest_touch_move_to(dev, 0, 45, 97, 50, 83, 20); + litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_MOTION); + + /* 5. Ghost touch: a single-frame phantom contact very close to + * the thumb, as seen in real hardware recording. The ghost + * appears while the thumb is actively moving and disappears + * within one frame. + */ + litest_touch_down(dev, 1, 52, 87); + litest_dispatch(li); + litest_touch_up(dev, 1); + litest_dispatch(li); + litest_drain_events(li); + + /* 6. Real second finger arrives and scrolls */ + msleep(234); + litest_dispatch(li); + litest_touch_down(dev, 1, 53, 44); + litest_dispatch(li); + litest_touch_move_to(dev, 1, 53, 44, 51, 11, 15); + litest_dispatch(li); + litest_drain_events(li); + + /* 7. Lift the real second finger */ + litest_touch_up(dev, 1); + litest_drain_events(li); + + /* 8. Move the thumb again. After the ghost touch triggered a + * spurious state reset and a real second finger followed, + * the thumb must be DEAD and produce no motion. + */ + litest_touch_move_to(dev, 0, 50, 83, 60, 55, 30); + litest_assert_empty_queue(li); + + litest_touch_up(dev, 0); +} +END_TEST + START_TEST(touchpad_tool_tripletap_touch_count) { struct litest_device *dev = litest_current_device(); @@ -7239,6 +7313,7 @@ TEST_COLLECTION(touchpad) litest_add(touchpad_thumb_area_btnarea, LITEST_CLICKPAD, LITEST_ANY); litest_add(touchpad_thumb_no_doublethumb, LITEST_CLICKPAD, LITEST_ANY); litest_add(touchpad_thumb_no_double_revive, LITEST_CLICKPAD, LITEST_SINGLE_TOUCH|LITEST_SEMI_MT); + litest_add(touchpad_thumb_no_revive_after_ghost_touch, LITEST_CLICKPAD, LITEST_SINGLE_TOUCH|LITEST_SEMI_MT); litest_add_for_device(touchpad_tool_tripletap_touch_count, LITEST_SYNAPTICS_TOPBUTTONPAD); litest_add_for_device(touchpad_tool_tripletap_touch_count_late, LITEST_SYNAPTICS_TOPBUTTONPAD);