touchpad: time the 3fg fast swipe timeout from the initial contact

We differ between a fast 3fg swipe and a 3fg drag based on whether we
move 5mm within 80ms of contact. Alas, the code started the timeout once
we had enough motion, not on initial contact.

Three fingers down, then resting for >80ms, then moving 5mm within the
subsequent 80ms would thus trigger a fast swipe because the timer wasn't
set until sufficient movement happened. Fix this by setting the timer
based on the initial touch point's time. This requires potentially
setting a negative timer to avoid duplicating parts of the state
machine.

Closes #1266

Fixes: fe1d44637f ("touchpad: add support for fast swipe when 3fg drag is enabled")
(cherry picked from commit fdd43a4fcd)

Part-of: <https://gitlab.freedesktop.org/libinput/libinput/-/merge_requests/1470>
This commit is contained in:
Peter Hutterer 2026-04-15 13:26:07 +10:00
parent 174a3550e6
commit 493a830651
2 changed files with 82 additions and 2 deletions

View file

@ -585,8 +585,28 @@ tp_gesture_handle_event_on_state_none(struct tp_dispatch *tp,
static void
tp_gesture_set_3fg_drag_3fg_or_swipe_timer(struct tp_dispatch *tp, usec_t time)
{
libinput_timer_set(&tp->gesture.drag_3fg_or_swipe_timer,
usec_add(time, DRAG_3FG_OR_SWIPE_TIMEOUT));
usec_t expire = usec_add(tp->gesture.initial_time, DRAG_3FG_OR_SWIPE_TIMEOUT);
/* This is a hack to avoid the state machine getting even more complicated.
* For a slow drag/fast swipe we want the time from the *initial* touch point,
* not the time from when we realised the fingers are moving. IOW
* putting 3fg down, resting for 80ms and then moving fast must trigger
* a drag, not a swipe.
*
* In theory we should set the timer in the NONE/UNKNOWN states but that would
* require a whole parallel set of states like
* NONE_BUT_TIMEOUT_FOR_FAST_STATE_EXPIRED. Let's not do that, instead we set a
* negative timer and let the normal state proceed. Either we moved by the
* threshold already (in which case we shouldn't ever get here anyway) or
* we didn't in which case the neg timer will do the right thing too when it
* fires.
*/
if (usec_cmp(expire, time) < 0)
libinput_timer_set_flags(&tp->gesture.drag_3fg_or_swipe_timer,
expire,
TIMER_FLAG_ALLOW_NEGATIVE);
else
libinput_timer_set(&tp->gesture.drag_3fg_or_swipe_timer, expire);
}
static void

View file

@ -2161,6 +2161,65 @@ START_TEST(gestures_3fg_drag_fast_swipe)
}
END_TEST
START_TEST(gestures_3fg_drag_slow_start)
{
struct litest_device *dev = litest_current_device();
struct libinput *li = dev->libinput;
uint32_t finger_count = litest_test_param_get_u32(test_env->params, "fingers");
bool tap_enabled = litest_test_param_get_bool(test_env->params, "tap-enabled");
if (litest_slot_count(dev) < 3)
return LITEST_NOT_APPLICABLE;
if (libinput_device_config_3fg_drag_get_finger_count(dev->libinput_device) <
(int)finger_count)
return LITEST_NOT_APPLICABLE;
litest_enable_3fg_drag(dev->libinput_device, finger_count);
if (tap_enabled)
litest_enable_tap(dev->libinput_device);
else
litest_disable_tap(dev->libinput_device);
litest_drain_events(li);
/* Fingers are set down, then we do nothing, then we swipe fast. Because
* we did nothing at first even fast movement must become a 3fg drag, not
* a swipe.
*/
double y = 30.0;
for (uint32_t i = 0; i < finger_count; i++)
litest_touch_down(dev, i, 10 + i, y);
litest_checkpoint("Waiting past timeout before fast finger move");
litest_timeout_3fg_drag_or_swipe(li);
/* Now move fast */
while (y < 60.0) {
y += 2;
for (uint32_t i = 0; i < finger_count; i++) {
litest_touch_move(dev, i, 10 + i, y);
}
litest_dispatch(li);
}
drain_cancelled_swipe_gesture(li);
litest_checkpoint("Expecting button press for 3fg drag");
litest_assert_button_event(li, BTN_LEFT, LIBINPUT_BUTTON_STATE_PRESSED);
litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_MOTION);
for (uint32_t i = 0; i < finger_count; i++)
litest_touch_up(dev, i);
litest_dispatch(li);
litest_assert_empty_queue(li);
litest_timeout_3fg_drag(li);
litest_assert_button_event(li, BTN_LEFT, LIBINPUT_BUTTON_STATE_RELEASED);
}
END_TEST
TEST_COLLECTION(gestures)
{
/* clang-format off */
@ -2247,6 +2306,7 @@ TEST_COLLECTION(gestures)
"fingers", 'u', 2, 3, 4,
"tap-enabled", 'b') {
litest_add_parametrized(gestures_3fg_drag_fast_swipe, LITEST_TOUCHPAD, LITEST_SINGLE_TOUCH, params);
litest_add_parametrized(gestures_3fg_drag_slow_start, LITEST_TOUCHPAD, LITEST_SINGLE_TOUCH, params);
}
/* Timing-sensitive test, valgrind is too slow */