From fe1d44637f67866270723efe8b888e9c0f0f6c8e Mon Sep 17 00:00:00 2001 From: Peter Hutterer Date: Thu, 15 Jan 2026 15:15:12 +1000 Subject: [PATCH] touchpad: add support for fast swipe when 3fg drag is enabled This adds a movement threshold (5mm) and a timeout (80ms) to the 3fg drag gesture. On 3fg down with 3fg drag enabled we immediately send a GESTURE_SWIPE event. After the timeout expires we check the movement of the fingers - if it is below the threshold cancel the swipe and hold a button down (i.e. a 3fg drag). Otherwise, continue with this being a swipe. This allows for swipe gestures to be used while 3fg drag is enabled. Above applies the same way for 4fg with 4fg drag enabled. Thresholds selected using the "yeah, that seems about alright" method, intentionally quite low because we assume that users that enable 3fg drag prefer 3fg dragging over swipe. Signed-off-by: Peter Hutterer Part-of: --- src/evdev-mt-touchpad-gestures.c | 252 ++++++++++++++++++++++++++++--- src/evdev-mt-touchpad.c | 1 + src/evdev-mt-touchpad.h | 4 + test/litest.h | 1 + test/test-gestures.c | 137 +++++++++++++++-- 5 files changed, 358 insertions(+), 37 deletions(-) diff --git a/src/evdev-mt-touchpad-gestures.c b/src/evdev-mt-touchpad-gestures.c index 03a7a496..50956207 100644 --- a/src/evdev-mt-touchpad-gestures.c +++ b/src/evdev-mt-touchpad-gestures.c @@ -38,9 +38,11 @@ enum gesture_cancelled { #define DEFAULT_GESTURE_SWITCH_TIMEOUT usec_from_millis(100) #define DEFAULT_GESTURE_SWIPE_TIMEOUT usec_from_millis(150) #define DEFAULT_GESTURE_PINCH_TIMEOUT usec_from_millis(300) +#define DRAG_3FG_OR_SWIPE_TIMEOUT usec_from_millis(80) #define HOLD_AND_MOTION_THRESHOLD 0.5 /* mm */ #define PINCH_DISAMBIGUATION_MOVE_THRESHOLD 1.5 /* mm */ +#define DRAG_3FG_OR_SWIPE_MOVE_THRESHOLD 5 /* mm */ enum gesture_event { GESTURE_EVENT_RESET, @@ -55,7 +57,8 @@ enum gesture_event { GESTURE_EVENT_SCROLL_START, GESTURE_EVENT_SWIPE_START, GESTURE_EVENT_PINCH_START, - GESTURE_EVENT_3FG_DRAG_START, + GESTURE_EVENT_3FG_DRAG_OR_SWIPE_START, + GESTURE_EVENT_3FG_DRAG_OR_SWIPE_TIMEOUT, GESTURE_EVENT_3FG_DRAG_RELEASE_TIMEOUT, }; @@ -83,6 +86,8 @@ gesture_state_to_str(enum tp_gesture_state state) CASE_RETURN_STRING(GESTURE_STATE_PINCH); CASE_RETURN_STRING(GESTURE_STATE_SWIPE_START); CASE_RETURN_STRING(GESTURE_STATE_SWIPE); + CASE_RETURN_STRING(GESTURE_STATE_3FG_DRAG_OR_SWIPE_START); + CASE_RETURN_STRING(GESTURE_STATE_3FG_DRAG_OR_SWIPE); CASE_RETURN_STRING(GESTURE_STATE_3FG_DRAG_START); CASE_RETURN_STRING(GESTURE_STATE_3FG_DRAG); CASE_RETURN_STRING(GESTURE_STATE_3FG_DRAG_RELEASED); @@ -106,7 +111,8 @@ gesture_event_to_str(enum gesture_event event) CASE_RETURN_STRING(GESTURE_EVENT_SCROLL_START); CASE_RETURN_STRING(GESTURE_EVENT_SWIPE_START); CASE_RETURN_STRING(GESTURE_EVENT_PINCH_START); - CASE_RETURN_STRING(GESTURE_EVENT_3FG_DRAG_START); + CASE_RETURN_STRING(GESTURE_EVENT_3FG_DRAG_OR_SWIPE_START); + CASE_RETURN_STRING(GESTURE_EVENT_3FG_DRAG_OR_SWIPE_TIMEOUT); CASE_RETURN_STRING(GESTURE_EVENT_3FG_DRAG_RELEASE_TIMEOUT); } return NULL; @@ -549,7 +555,7 @@ tp_gesture_handle_event_on_state_none(struct tp_dispatch *tp, */ if (!tp->tap.enabled && tp->drag_3fg.nfingers == tp->gesture.finger_count) { - tp->gesture.state = GESTURE_STATE_3FG_DRAG_START; + tp->gesture.state = GESTURE_STATE_3FG_DRAG_OR_SWIPE_START; } else { tp_gesture_set_hold_timer(tp, time); tp->gesture.state = GESTURE_STATE_UNKNOWN; @@ -568,13 +574,21 @@ tp_gesture_handle_event_on_state_none(struct tp_dispatch *tp, case GESTURE_EVENT_HOLD_AND_MOTION_START: case GESTURE_EVENT_SWIPE_START: case GESTURE_EVENT_PINCH_START: - case GESTURE_EVENT_3FG_DRAG_START: + case GESTURE_EVENT_3FG_DRAG_OR_SWIPE_START: + case GESTURE_EVENT_3FG_DRAG_OR_SWIPE_TIMEOUT: case GESTURE_EVENT_3FG_DRAG_RELEASE_TIMEOUT: log_gesture_bug(tp, event); break; } } +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)); +} + static void tp_gesture_handle_event_on_state_unknown(struct tp_dispatch *tp, enum gesture_event event, @@ -615,12 +629,13 @@ tp_gesture_handle_event_on_state_unknown(struct tp_dispatch *tp, tp_gesture_init_pinch(tp); tp->gesture.state = GESTURE_STATE_PINCH_START; break; - case GESTURE_EVENT_3FG_DRAG_START: + case GESTURE_EVENT_3FG_DRAG_OR_SWIPE_START: libinput_timer_cancel(&tp->gesture.hold_timer); - tp->gesture.state = GESTURE_STATE_3FG_DRAG_START; + tp->gesture.state = GESTURE_STATE_3FG_DRAG_OR_SWIPE_START; break; case GESTURE_EVENT_HOLD_AND_MOTION_START: case GESTURE_EVENT_FINGER_DETECTED: + case GESTURE_EVENT_3FG_DRAG_OR_SWIPE_TIMEOUT: case GESTURE_EVENT_3FG_DRAG_RELEASE_TIMEOUT: log_gesture_bug(tp, event); break; @@ -671,13 +686,15 @@ tp_gesture_handle_event_on_state_hold(struct tp_dispatch *tp, tp_gesture_init_pinch(tp); tp->gesture.state = GESTURE_STATE_PINCH_START; break; - case GESTURE_EVENT_3FG_DRAG_START: + case GESTURE_EVENT_3FG_DRAG_OR_SWIPE_START: + libinput_timer_cancel(&tp->gesture.hold_timer); tp_gesture_cancel(tp, time); - tp->gesture.state = GESTURE_STATE_3FG_DRAG_START; + tp->gesture.state = GESTURE_STATE_3FG_DRAG_OR_SWIPE_START; break; case GESTURE_EVENT_HOLD_TIMEOUT: case GESTURE_EVENT_TAP_TIMEOUT: case GESTURE_EVENT_FINGER_DETECTED: + case GESTURE_EVENT_3FG_DRAG_OR_SWIPE_TIMEOUT: case GESTURE_EVENT_3FG_DRAG_RELEASE_TIMEOUT: log_gesture_bug(tp, event); break; @@ -718,7 +735,8 @@ tp_gesture_handle_event_on_state_hold_and_motion(struct tp_dispatch *tp, case GESTURE_EVENT_SCROLL_START: case GESTURE_EVENT_SWIPE_START: case GESTURE_EVENT_PINCH_START: - case GESTURE_EVENT_3FG_DRAG_START: + case GESTURE_EVENT_3FG_DRAG_OR_SWIPE_START: + case GESTURE_EVENT_3FG_DRAG_OR_SWIPE_TIMEOUT: case GESTURE_EVENT_3FG_DRAG_RELEASE_TIMEOUT: log_gesture_bug(tp, event); break; @@ -764,7 +782,8 @@ tp_gesture_handle_event_on_state_pointer_motion(struct tp_dispatch *tp, case GESTURE_EVENT_SCROLL_START: case GESTURE_EVENT_SWIPE_START: case GESTURE_EVENT_PINCH_START: - case GESTURE_EVENT_3FG_DRAG_START: + case GESTURE_EVENT_3FG_DRAG_OR_SWIPE_START: + case GESTURE_EVENT_3FG_DRAG_OR_SWIPE_TIMEOUT: case GESTURE_EVENT_3FG_DRAG_RELEASE_TIMEOUT: log_gesture_bug(tp, event); break; @@ -797,7 +816,8 @@ tp_gesture_handle_event_on_state_scroll_start(struct tp_dispatch *tp, case GESTURE_EVENT_POINTER_MOTION_START: case GESTURE_EVENT_SCROLL_START: case GESTURE_EVENT_SWIPE_START: - case GESTURE_EVENT_3FG_DRAG_START: + case GESTURE_EVENT_3FG_DRAG_OR_SWIPE_START: + case GESTURE_EVENT_3FG_DRAG_OR_SWIPE_TIMEOUT: case GESTURE_EVENT_3FG_DRAG_RELEASE_TIMEOUT: log_gesture_bug(tp, event); break; @@ -832,7 +852,8 @@ tp_gesture_handle_event_on_state_scroll(struct tp_dispatch *tp, case GESTURE_EVENT_POINTER_MOTION_START: case GESTURE_EVENT_SCROLL_START: case GESTURE_EVENT_SWIPE_START: - case GESTURE_EVENT_3FG_DRAG_START: + case GESTURE_EVENT_3FG_DRAG_OR_SWIPE_START: + case GESTURE_EVENT_3FG_DRAG_OR_SWIPE_TIMEOUT: case GESTURE_EVENT_3FG_DRAG_RELEASE_TIMEOUT: log_gesture_bug(tp, event); break; @@ -860,7 +881,8 @@ tp_gesture_handle_event_on_state_pinch_start(struct tp_dispatch *tp, case GESTURE_EVENT_SCROLL_START: case GESTURE_EVENT_SWIPE_START: case GESTURE_EVENT_PINCH_START: - case GESTURE_EVENT_3FG_DRAG_START: + case GESTURE_EVENT_3FG_DRAG_OR_SWIPE_START: + case GESTURE_EVENT_3FG_DRAG_OR_SWIPE_TIMEOUT: case GESTURE_EVENT_3FG_DRAG_RELEASE_TIMEOUT: log_gesture_bug(tp, event); break; @@ -899,7 +921,8 @@ tp_gesture_handle_event_on_state_pinch(struct tp_dispatch *tp, case GESTURE_EVENT_SCROLL_START: case GESTURE_EVENT_SWIPE_START: case GESTURE_EVENT_PINCH_START: - case GESTURE_EVENT_3FG_DRAG_START: + case GESTURE_EVENT_3FG_DRAG_OR_SWIPE_START: + case GESTURE_EVENT_3FG_DRAG_OR_SWIPE_TIMEOUT: case GESTURE_EVENT_3FG_DRAG_RELEASE_TIMEOUT: log_gesture_bug(tp, event); break; @@ -928,7 +951,8 @@ tp_gesture_handle_event_on_state_swipe_start(struct tp_dispatch *tp, case GESTURE_EVENT_SCROLL_START: case GESTURE_EVENT_SWIPE_START: case GESTURE_EVENT_PINCH_START: - case GESTURE_EVENT_3FG_DRAG_START: + case GESTURE_EVENT_3FG_DRAG_OR_SWIPE_START: + case GESTURE_EVENT_3FG_DRAG_OR_SWIPE_TIMEOUT: case GESTURE_EVENT_3FG_DRAG_RELEASE_TIMEOUT: log_gesture_bug(tp, event); break; @@ -966,13 +990,101 @@ tp_gesture_handle_event_on_state_swipe(struct tp_dispatch *tp, case GESTURE_EVENT_SCROLL_START: case GESTURE_EVENT_SWIPE_START: case GESTURE_EVENT_PINCH_START: - case GESTURE_EVENT_3FG_DRAG_START: + case GESTURE_EVENT_3FG_DRAG_OR_SWIPE_START: + case GESTURE_EVENT_3FG_DRAG_OR_SWIPE_TIMEOUT: case GESTURE_EVENT_3FG_DRAG_RELEASE_TIMEOUT: log_gesture_bug(tp, event); break; } } +static void +tp_gesture_handle_event_on_state_3fg_drag_or_swipe_start(struct tp_dispatch *tp, + enum gesture_event event, + usec_t time) +{ + switch (event) { + case GESTURE_EVENT_RESET: + case GESTURE_EVENT_END: + case GESTURE_EVENT_CANCEL: + libinput_timer_cancel(&tp->gesture.hold_timer); + tp->gesture.state = GESTURE_STATE_NONE; + break; + case GESTURE_EVENT_FINGER_SWITCH_TIMEOUT: + break; + case GESTURE_EVENT_HOLD_AND_MOTION_START: + case GESTURE_EVENT_FINGER_DETECTED: + case GESTURE_EVENT_TAP_TIMEOUT: + case GESTURE_EVENT_HOLD_TIMEOUT: + case GESTURE_EVENT_POINTER_MOTION_START: + case GESTURE_EVENT_SCROLL_START: + case GESTURE_EVENT_SWIPE_START: + case GESTURE_EVENT_PINCH_START: + case GESTURE_EVENT_3FG_DRAG_OR_SWIPE_START: + case GESTURE_EVENT_3FG_DRAG_OR_SWIPE_TIMEOUT: + case GESTURE_EVENT_3FG_DRAG_RELEASE_TIMEOUT: + log_gesture_bug(tp, event); + break; + } +} + +static void +tp_gesture_handle_event_on_state_3fg_drag_or_swipe(struct tp_dispatch *tp, + enum gesture_event event, + usec_t time) +{ + struct tp_touch *first = tp->gesture.touches[0], + *second = tp->gesture.touches[1]; + struct phys_coords first_moved, second_moved; + double first_mm, second_mm; + + switch (event) { + case GESTURE_EVENT_RESET: + libinput_timer_cancel(&tp->gesture.hold_timer); + tp->gesture.state = GESTURE_STATE_NONE; + break; + case GESTURE_EVENT_END: + case GESTURE_EVENT_CANCEL: { + bool cancelled = event == GESTURE_EVENT_CANCEL; + gesture_notify_swipe_end(&tp->device->base, + time, + tp->gesture.finger_count, + cancelled); + tp->gesture.state = GESTURE_STATE_NONE; + break; + } + case GESTURE_EVENT_FINGER_SWITCH_TIMEOUT: + break; + case GESTURE_EVENT_HOLD_AND_MOTION_START: + case GESTURE_EVENT_FINGER_DETECTED: + case GESTURE_EVENT_TAP_TIMEOUT: + case GESTURE_EVENT_HOLD_TIMEOUT: + case GESTURE_EVENT_POINTER_MOTION_START: + case GESTURE_EVENT_SCROLL_START: + case GESTURE_EVENT_SWIPE_START: + case GESTURE_EVENT_PINCH_START: + case GESTURE_EVENT_3FG_DRAG_OR_SWIPE_START: + case GESTURE_EVENT_3FG_DRAG_RELEASE_TIMEOUT: + log_gesture_bug(tp, event); + break; + case GESTURE_EVENT_3FG_DRAG_OR_SWIPE_TIMEOUT: + libinput_timer_cancel(&tp->gesture.drag_3fg_or_swipe_timer); + + first_moved = tp_gesture_mm_moved(tp, first); + second_moved = tp_gesture_mm_moved(tp, second); + first_mm = hypot(first_moved.x, first_moved.y); + second_mm = hypot(second_moved.x, second_moved.y); + if ((first_mm + second_mm) / 2.0 >= DRAG_3FG_OR_SWIPE_MOVE_THRESHOLD) { + tp->gesture.state = GESTURE_STATE_SWIPE; + } else { + /* Cancel the swipe */ + tp_gesture_cancel(tp, time); + tp->gesture.state = GESTURE_STATE_3FG_DRAG_START; + } + break; + } +} + static void tp_gesture_handle_event_on_state_3fg_drag_start(struct tp_dispatch *tp, enum gesture_event event, @@ -995,7 +1107,8 @@ tp_gesture_handle_event_on_state_3fg_drag_start(struct tp_dispatch *tp, case GESTURE_EVENT_SCROLL_START: case GESTURE_EVENT_SWIPE_START: case GESTURE_EVENT_PINCH_START: - case GESTURE_EVENT_3FG_DRAG_START: + case GESTURE_EVENT_3FG_DRAG_OR_SWIPE_START: + case GESTURE_EVENT_3FG_DRAG_OR_SWIPE_TIMEOUT: case GESTURE_EVENT_3FG_DRAG_RELEASE_TIMEOUT: log_gesture_bug(tp, event); break; @@ -1050,7 +1163,8 @@ tp_gesture_handle_event_on_state_3fg_drag(struct tp_dispatch *tp, case GESTURE_EVENT_SCROLL_START: case GESTURE_EVENT_SWIPE_START: case GESTURE_EVENT_PINCH_START: - case GESTURE_EVENT_3FG_DRAG_START: + case GESTURE_EVENT_3FG_DRAG_OR_SWIPE_START: + case GESTURE_EVENT_3FG_DRAG_OR_SWIPE_TIMEOUT: case GESTURE_EVENT_3FG_DRAG_RELEASE_TIMEOUT: log_gesture_bug(tp, event); break; @@ -1110,10 +1224,13 @@ tp_gesture_handle_event_on_state_3fg_drag_released(struct tp_dispatch *tp, break; case GESTURE_EVENT_SWIPE_START: case GESTURE_EVENT_PINCH_START: - case GESTURE_EVENT_3FG_DRAG_START: libinput_timer_cancel(&tp->gesture.drag_3fg_timer); tp->gesture.state = GESTURE_STATE_3FG_DRAG; break; + case GESTURE_EVENT_3FG_DRAG_OR_SWIPE_START: + case GESTURE_EVENT_3FG_DRAG_OR_SWIPE_TIMEOUT: + log_gesture_bug(tp, event); + break; } } @@ -1158,6 +1275,14 @@ tp_gesture_handle_event(struct tp_dispatch *tp, enum gesture_event event, usec_t case GESTURE_STATE_SWIPE: tp_gesture_handle_event_on_state_swipe(tp, event, time); break; + case GESTURE_STATE_3FG_DRAG_OR_SWIPE_START: + tp_gesture_handle_event_on_state_3fg_drag_or_swipe_start(tp, + event, + time); + break; + case GESTURE_STATE_3FG_DRAG_OR_SWIPE: + tp_gesture_handle_event_on_state_3fg_drag_or_swipe(tp, event, time); + break; case GESTURE_STATE_3FG_DRAG_START: tp_gesture_handle_event_on_state_3fg_drag_start(tp, event, time); break; @@ -1208,6 +1333,14 @@ tp_gesture_3fg_drag_timeout(usec_t now, void *data) tp_gesture_handle_event(tp, GESTURE_EVENT_3FG_DRAG_RELEASE_TIMEOUT, now); } +static void +tp_gesture_3fg_drag_or_swipe_timeout(usec_t now, void *data) +{ + struct tp_dispatch *tp = data; + + tp_gesture_handle_event(tp, GESTURE_EVENT_3FG_DRAG_OR_SWIPE_TIMEOUT, now); +} + static void tp_gesture_detect_motion_gestures(struct tp_dispatch *tp, usec_t time) { @@ -1250,7 +1383,9 @@ tp_gesture_detect_motion_gestures(struct tp_dispatch *tp, usec_t time) if (tp->gesture.enabled && tp->gesture.finger_count > 2 && tp->gesture.finger_count > tp->num_slots) { if (tp->drag_3fg.nfingers == tp->gesture.finger_count) - tp_gesture_handle_event(tp, GESTURE_EVENT_3FG_DRAG_START, time); + tp_gesture_handle_event(tp, + GESTURE_EVENT_3FG_DRAG_OR_SWIPE_START, + time); else tp_gesture_handle_event(tp, GESTURE_EVENT_SWIPE_START, time); return; @@ -1280,7 +1415,9 @@ tp_gesture_detect_motion_gestures(struct tp_dispatch *tp, usec_t time) if (tp->gesture.finger_count == 2) tp_gesture_handle_event(tp, GESTURE_EVENT_SCROLL_START, time); else if (tp->drag_3fg.nfingers == tp->gesture.finger_count) - tp_gesture_handle_event(tp, GESTURE_EVENT_3FG_DRAG_START, time); + tp_gesture_handle_event(tp, + GESTURE_EVENT_3FG_DRAG_OR_SWIPE_START, + time); else tp_gesture_handle_event(tp, GESTURE_EVENT_SWIPE_START, time); @@ -1288,10 +1425,12 @@ tp_gesture_detect_motion_gestures(struct tp_dispatch *tp, usec_t time) } /* If 3fg dragging touches are within a 60x10mm box, start - * dragging immediately */ + * dragging (or swiping) immediately */ if (tp->gesture.finger_count == tp->drag_3fg.nfingers && distance_mm.x < 60.0 && distance_mm.y < 10.0) { - tp_gesture_handle_event(tp, GESTURE_EVENT_3FG_DRAG_START, time); + tp_gesture_handle_event(tp, + GESTURE_EVENT_3FG_DRAG_OR_SWIPE_START, + time); return; } @@ -1369,7 +1508,9 @@ tp_gesture_detect_motion_gestures(struct tp_dispatch *tp, usec_t time) } if (tp->drag_3fg.nfingers == tp->gesture.finger_count) { - tp_gesture_handle_event(tp, GESTURE_EVENT_3FG_DRAG_START, time); + tp_gesture_handle_event(tp, + GESTURE_EVENT_3FG_DRAG_OR_SWIPE_START, + time); return; } @@ -1724,11 +1865,45 @@ tp_gesture_handle_state_3fg_drag_released(struct tp_dispatch *tp, tp_gesture_detect_motion_gestures(tp, time); } +static void +tp_gesture_handle_state_3fg_drag_or_swipe(struct tp_dispatch *tp, usec_t time) +{ + struct device_float_coords raw; + struct normalized_coords delta, unaccel; + + raw = tp_get_average_touches_delta(tp); + delta = tp_filter_motion(tp, &raw, time); + + if (!normalized_is_zero(delta) || !device_float_is_zero(raw)) { + unaccel = tp_filter_motion_unaccelerated(tp, &raw, time); + gesture_notify_swipe(&tp->device->base, + time, + LIBINPUT_EVENT_GESTURE_SWIPE_UPDATE, + tp->gesture.finger_count, + &delta, + &unaccel); + } +} + +static void +tp_gesture_handle_state_3fg_drag_or_swipe_start(struct tp_dispatch *tp, usec_t time) +{ + const struct normalized_coords zero = { 0.0, 0.0 }; + gesture_notify_swipe(&tp->device->base, + time, + LIBINPUT_EVENT_GESTURE_SWIPE_BEGIN, + tp->gesture.finger_count, + &zero, + &zero); + tp->gesture.state = GESTURE_STATE_3FG_DRAG_OR_SWIPE; + tp_gesture_set_3fg_drag_3fg_or_swipe_timer(tp, time); +} + static void tp_gesture_handle_state(struct tp_dispatch *tp, usec_t time, bool ignore_motion) { enum tp_gesture_state oldstate = tp->gesture.state; - enum tp_gesture_state transitions[16] = { 0 }; + enum tp_gesture_state transitions[18] = { 0 }; enum tp_gesture_state *transition_state = transitions; #define REMEMBER_TRANSITION(_ts, _state) { \ @@ -1796,6 +1971,14 @@ tp_gesture_handle_state(struct tp_dispatch *tp, usec_t time, bool ignore_motion) tp_gesture_handle_state_3fg_drag_released(tp, time, ignore_motion); REMEMBER_TRANSITION(transition_state, tp->gesture.state); } + if (tp->gesture.state == GESTURE_STATE_3FG_DRAG_OR_SWIPE) { + tp_gesture_handle_state_3fg_drag_or_swipe(tp, time); + REMEMBER_TRANSITION(transition_state, tp->gesture.state); + } + if (tp->gesture.state == GESTURE_STATE_3FG_DRAG_OR_SWIPE_START) { + tp_gesture_handle_state_3fg_drag_or_swipe_start(tp, time); + REMEMBER_TRANSITION(transition_state, tp->gesture.state); + } #undef REMEMBER_TRANSITION @@ -1893,6 +2076,7 @@ tp_gesture_end(struct tp_dispatch *tp, usec_t time, enum gesture_cancelled cance case GESTURE_STATE_PINCH_START: case GESTURE_STATE_SWIPE_START: case GESTURE_STATE_3FG_DRAG_START: + case GESTURE_STATE_3FG_DRAG_OR_SWIPE_START: tp_gesture_handle_event(tp, GESTURE_EVENT_RESET, time); break; case GESTURE_STATE_HOLD: @@ -1903,6 +2087,7 @@ tp_gesture_end(struct tp_dispatch *tp, usec_t time, enum gesture_cancelled cance case GESTURE_STATE_SWIPE: case GESTURE_STATE_3FG_DRAG: case GESTURE_STATE_3FG_DRAG_RELEASED: + case GESTURE_STATE_3FG_DRAG_OR_SWIPE: switch (cancelled) { case CANCEL_GESTURE: tp_gesture_handle_event(tp, GESTURE_EVENT_CANCEL, time); @@ -1947,6 +2132,11 @@ tp_gesture_cancel_motion_gestures(struct tp_dispatch *tp, usec_t time) break; case GESTURE_STATE_3FG_DRAG_RELEASED: break; + case GESTURE_STATE_3FG_DRAG_OR_SWIPE: + case GESTURE_STATE_3FG_DRAG_OR_SWIPE_START: + evdev_log_debug(tp->device, "Cancelling motion gestures\n"); + tp_gesture_cancel(tp, time); + break; } } @@ -1988,6 +2178,8 @@ tp_gesture_debounce_finger_changes(struct tp_dispatch *tp) case GESTURE_STATE_3FG_DRAG_START: case GESTURE_STATE_3FG_DRAG_RELEASED: case GESTURE_STATE_3FG_DRAG: + case GESTURE_STATE_3FG_DRAG_OR_SWIPE: + case GESTURE_STATE_3FG_DRAG_OR_SWIPE_START: return true; } @@ -2230,6 +2422,15 @@ tp_init_gesture(struct tp_dispatch *tp) timer_name, tp_gesture_3fg_drag_timeout, tp); + snprintf(timer_name, + sizeof(timer_name), + "%s drag_or_swipe", + evdev_device_get_sysname(tp->device)); + libinput_timer_init(&tp->gesture.drag_3fg_or_swipe_timer, + tp_libinput_context(tp), + timer_name, + tp_gesture_3fg_drag_or_swipe_timeout, + tp); } void @@ -2238,4 +2439,5 @@ tp_remove_gesture(struct tp_dispatch *tp) libinput_timer_cancel(&tp->gesture.finger_count_switch_timer); libinput_timer_cancel(&tp->gesture.hold_timer); libinput_timer_cancel(&tp->gesture.drag_3fg_timer); + libinput_timer_cancel(&tp->gesture.drag_3fg_or_swipe_timer); } diff --git a/src/evdev-mt-touchpad.c b/src/evdev-mt-touchpad.c index e1090ff4..3db9eea4 100644 --- a/src/evdev-mt-touchpad.c +++ b/src/evdev-mt-touchpad.c @@ -1975,6 +1975,7 @@ tp_interface_destroy(struct evdev_dispatch *dispatch) libinput_timer_destroy(&tp->gesture.finger_count_switch_timer); libinput_timer_destroy(&tp->gesture.hold_timer); libinput_timer_destroy(&tp->gesture.drag_3fg_timer); + libinput_timer_destroy(&tp->gesture.drag_3fg_or_swipe_timer); free(tp->touches); free(tp); } diff --git a/src/evdev-mt-touchpad.h b/src/evdev-mt-touchpad.h index b9c7aa7c..7e85baf7 100644 --- a/src/evdev-mt-touchpad.h +++ b/src/evdev-mt-touchpad.h @@ -166,6 +166,8 @@ enum tp_gesture_state { GESTURE_STATE_PINCH, GESTURE_STATE_SWIPE_START, GESTURE_STATE_SWIPE, + GESTURE_STATE_3FG_DRAG_OR_SWIPE_START, + GESTURE_STATE_3FG_DRAG_OR_SWIPE, GESTURE_STATE_3FG_DRAG_START, GESTURE_STATE_3FG_DRAG, GESTURE_STATE_3FG_DRAG_RELEASED, @@ -371,6 +373,8 @@ struct tp_dispatch { struct libinput_timer drag_3fg_timer; usec_t drag_3fg_release_time; + + struct libinput_timer drag_3fg_or_swipe_timer; } gesture; struct { diff --git a/test/litest.h b/test/litest.h index 2f2ebfb0..6d5faec0 100644 --- a/test/litest.h +++ b/test/litest.h @@ -1371,6 +1371,7 @@ _litest_timeout(struct libinput *li, const char *func, int lineno, int millis); #define litest_timeout_tablet_proxout(li_) litest_timeout(li_, 170) #define litest_timeout_touch_arbitration(li_) litest_timeout(li_, 100) #define litest_timeout_hysteresis(li_) litest_timeout(li_, 90) +#define litest_timeout_3fg_drag_or_swipe(li_) litest_timeout(li_, 90) #define litest_timeout_3fg_drag(li_) litest_timeout(li_, 800) #define litest_timeout_eraser_button(li_) litest_timeout(li_, 50) diff --git a/test/test-gestures.c b/test/test-gestures.c index e5f87cc1..cb53be68 100644 --- a/test/test-gestures.c +++ b/test/test-gestures.c @@ -1587,6 +1587,18 @@ START_TEST(gestures_hold_and_motion_after_timeout) } END_TEST +static void +drain_cancelled_swipe_gesture(struct libinput *li) +{ + litest_drain_events_of_type(li, + LIBINPUT_EVENT_GESTURE_SWIPE_BEGIN, + LIBINPUT_EVENT_GESTURE_SWIPE_UPDATE); + + _destroy_(libinput_event) *end = libinput_get_event(li); + auto gev = litest_is_gesture_event(end, LIBINPUT_EVENT_GESTURE_SWIPE_END, -1); + litest_assert(libinput_event_gesture_get_cancelled(gev)); +} + START_TEST(gestures_3fg_drag) { struct litest_device *dev = litest_current_device(); @@ -1626,21 +1638,25 @@ START_TEST(gestures_3fg_drag) litest_assert_empty_queue(li); } else { litest_checkpoint( - "Expecting immediate button press as tapping is disabled"); - litest_assert_button_event(li, BTN_LEFT, LIBINPUT_BUTTON_STATE_PRESSED); + "Expecting immediate swipe begin tapping is disabled"); + litest_assert_gesture_event(li, + LIBINPUT_EVENT_GESTURE_SWIPE_BEGIN, + finger_count); } while (y < 60.0) { y += 2; - for (uint32_t i = 0; i < finger_count; i++) + for (uint32_t i = 0; i < finger_count; i++) { litest_touch_move(dev, i, 10 + i, y); + if (i == + 0) /* Wait after the first movement to escape the swipe */ + litest_timeout_3fg_drag_or_swipe(li); + } litest_dispatch(li); } - if (tap_enabled) { - litest_checkpoint("Expecting late button press as tapping is enabled"); - litest_assert_button_event(li, BTN_LEFT, LIBINPUT_BUTTON_STATE_PRESSED); - } + drain_cancelled_swipe_gesture(li); + 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++) @@ -1688,10 +1704,15 @@ START_TEST(gestures_3fg_drag_lock_resume_3fg_motion) while (y < 60.0) { y += 2; - for (uint32_t i = 0; i < finger_count; i++) + for (uint32_t i = 0; i < finger_count; i++) { litest_touch_move(dev, i, 10 + i, y); + if (i == + 0) /* Wait after the first movement to escape the swipe */ + litest_timeout_3fg_drag_or_swipe(li); + } litest_dispatch(li); } + drain_cancelled_swipe_gesture(li); litest_assert_button_event(li, BTN_LEFT, LIBINPUT_BUTTON_STATE_PRESSED); litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_MOTION); @@ -1777,10 +1798,15 @@ START_TEST(gestures_3fg_drag_lock_resume_3fg_release_no_motion) litest_dispatch(li); while (y < 60.0) { y += 2; - for (uint32_t i = 0; i < finger_count; i++) + for (uint32_t i = 0; i < finger_count; i++) { litest_touch_move(dev, i, 10 + i, y); + if (i == + 0) /* Wait after the first movement to escape the swipe */ + litest_timeout_3fg_drag_or_swipe(li); + } litest_dispatch(li); } + drain_cancelled_swipe_gesture(li); litest_assert_button_event(li, BTN_LEFT, LIBINPUT_BUTTON_STATE_PRESSED); litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_MOTION); @@ -1870,10 +1896,15 @@ START_TEST(gestures_3fg_drag_lock_resume_1fg_motion) while (y < 60.0) { y += 2; - for (uint32_t i = 0; i < finger_count; i++) + for (uint32_t i = 0; i < finger_count; i++) { litest_touch_move(dev, i, 10 + i, y); + if (i == + 0) /* Wait after the first movement to escape the swipe */ + litest_timeout_3fg_drag_or_swipe(li); + } litest_dispatch(li); } + drain_cancelled_swipe_gesture(li); litest_assert_button_event(li, BTN_LEFT, LIBINPUT_BUTTON_STATE_PRESSED); litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_MOTION); @@ -1943,10 +1974,15 @@ START_TEST(gestures_3fg_drag_lock_resume_2fg_scroll) while (y < 60.0) { y += 2; - for (uint32_t i = 0; i < finger_count; i++) + for (uint32_t i = 0; i < finger_count; i++) { litest_touch_move(dev, i, 10 + i, y); + if (i == + 0) /* Wait after the first movement to escape the swipe */ + litest_timeout_3fg_drag_or_swipe(li); + } litest_dispatch(li); } + drain_cancelled_swipe_gesture(li); litest_assert_button_event(li, BTN_LEFT, LIBINPUT_BUTTON_STATE_PRESSED); litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_MOTION); @@ -2012,14 +2048,19 @@ START_TEST(gestures_3fg_drag_lock_resume_1fg_tap) while (y < 60.0) { y += 2; - for (int i = 0; i < finger_count; i++) + for (int i = 0; i < finger_count; i++) { litest_touch_move(dev, i, 10 + i, y); + if (i == + 0) /* Wait after the first movement to escape the swipe */ + litest_timeout_3fg_drag_or_swipe(li); + } litest_dispatch(li); } litest_drain_events_of_type(li, LIBINPUT_EVENT_GESTURE_HOLD_BEGIN, LIBINPUT_EVENT_GESTURE_HOLD_END, -1); + drain_cancelled_swipe_gesture(li); litest_assert_button_event(li, BTN_LEFT, LIBINPUT_BUTTON_STATE_PRESSED); litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_MOTION); @@ -2054,6 +2095,72 @@ START_TEST(gestures_3fg_drag_lock_resume_1fg_tap) } END_TEST +START_TEST(gestures_3fg_drag_fast_swipe) +{ + 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); + + double y = 30.0; + for (uint32_t i = 0; i < finger_count; i++) + litest_touch_down(dev, i, 10 + i, y); + + 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); + } + + litest_drain_events_of_type(li, + LIBINPUT_EVENT_GESTURE_HOLD_BEGIN, + LIBINPUT_EVENT_GESTURE_HOLD_END); + + auto begin = libinput_get_event(li); + litest_is_gesture_event(begin, + LIBINPUT_EVENT_GESTURE_SWIPE_BEGIN, + finger_count); + libinput_event_destroy(begin); + + struct libinput_event *update; + while ((update = libinput_get_event(li))) { + litest_is_gesture_event(update, + LIBINPUT_EVENT_GESTURE_SWIPE_UPDATE, + finger_count); + libinput_event_destroy(update); + } + + for (uint32_t i = 0; i < finger_count; i++) + litest_touch_up(dev, i); + + litest_dispatch(li); + + auto end = libinput_get_event(li); + auto gev = litest_is_gesture_event(end, + LIBINPUT_EVENT_GESTURE_SWIPE_END, + finger_count); + litest_assert(!libinput_event_gesture_get_cancelled(gev)); + libinput_event_destroy(end); +} +END_TEST + TEST_COLLECTION(gestures) { /* clang-format off */ @@ -2136,6 +2243,12 @@ TEST_COLLECTION(gestures) litest_add_parametrized(gestures_3fg_drag_lock_resume_1fg_tap, LITEST_TOUCHPAD, LITEST_SINGLE_TOUCH, params); } + litest_with_parameters(params, + "fingers", 'u', 2, 3, 4, + "tap-enabled", 'b') { + litest_add_parametrized(gestures_3fg_drag_fast_swipe, LITEST_TOUCHPAD, LITEST_SINGLE_TOUCH, params); + } + /* Timing-sensitive test, valgrind is too slow */ if (!RUNNING_ON_VALGRIND) litest_add(gestures_swipe_3fg_unaccel, LITEST_TOUCHPAD, LITEST_SINGLE_TOUCH);