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 <peter.hutterer@who-t.net>
Part-of: <https://gitlab.freedesktop.org/libinput/libinput/-/merge_requests/1410>
This commit is contained in:
Peter Hutterer 2026-01-15 15:15:12 +10:00 committed by Marge Bot
parent 5b7b8f1bb2
commit fe1d44637f
5 changed files with 358 additions and 37 deletions

View file

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

View file

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

View file

@ -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 {

View file

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

View file

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