Merge branch '2026-thumb-regression-fixes' into 'main'

Draft: touchpad: restore thumb rejection/suppression

Closes #1191

See merge request libinput/libinput!1439
This commit is contained in:
Matt Mayfield 2026-03-11 01:53:59 -04:00
commit 62bf4bb1cd
2 changed files with 176 additions and 3 deletions

View file

@ -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);
}
}

View file

@ -5459,6 +5459,149 @@ 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_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();
@ -7169,6 +7312,8 @@ 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(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);