diff --git a/src/evdev-mt-touchpad-gestures.c b/src/evdev-mt-touchpad-gestures.c index 0d5fcedd..4202802e 100644 --- a/src/evdev-mt-touchpad-gestures.c +++ b/src/evdev-mt-touchpad-gestures.c @@ -28,6 +28,7 @@ #include "evdev-mt-touchpad.h" +#define QUICK_GESTURE_HOLD_TIMEOUT ms2us(40) #define DEFAULT_GESTURE_HOLD_TIMEOUT ms2us(180) #define DEFAULT_GESTURE_SWITCH_TIMEOUT ms2us(100) #define DEFAULT_GESTURE_SWIPE_TIMEOUT ms2us(150) @@ -476,6 +477,17 @@ log_gesture_bug(struct tp_dispatch *tp, enum gesture_event event) gesture_state_to_str(tp->gesture.state)); } +static bool +tp_gesture_is_quick_hold(struct tp_dispatch *tp) +{ + /* When 1 or 2 fingers are used to hold, always use a "quick" hold to + * make the hold to stop kinetic scrolling user interaction feel more + * natural. + */ + return (tp->gesture.finger_count == 1) || + (tp->gesture.finger_count == 2); +} + static bool tp_gesture_use_hold_timer(struct tp_dispatch *tp) { @@ -483,6 +495,10 @@ tp_gesture_use_hold_timer(struct tp_dispatch *tp) if (!tp->tap.enabled) return true; + /* Always use the timer if it is a quick hold */ + if (tp_gesture_is_quick_hold(tp)) + return true; + /* If the number of fingers on the touchpad exceeds the number of * allowed fingers to tap, use the timer. */ @@ -510,12 +526,17 @@ tp_gesture_use_hold_timer(struct tp_dispatch *tp) static void tp_gesture_set_hold_timer(struct tp_dispatch *tp, uint64_t time) { + uint64_t timeout; + if (!tp->gesture.hold_enabled) return; if (tp_gesture_use_hold_timer(tp)) { - libinput_timer_set(&tp->gesture.hold_timer, - time + DEFAULT_GESTURE_HOLD_TIMEOUT); + timeout = tp_gesture_is_quick_hold(tp) ? + QUICK_GESTURE_HOLD_TIMEOUT : + DEFAULT_GESTURE_HOLD_TIMEOUT; + + libinput_timer_set(&tp->gesture.hold_timer, time + timeout); } } @@ -750,6 +771,10 @@ static void tp_gesture_hold_timeout(uint64_t now, void *data) { struct tp_dispatch *tp = data; + + if (tp_tap_dragging_or_double_tapping(tp) || tp_tap_dragging(tp)) + return; + tp_gesture_handle_event(tp, GESTURE_EVENT_HOLD_TIMEOUT, now); } @@ -759,7 +784,8 @@ tp_gesture_tap_timeout(struct tp_dispatch *tp, uint64_t time) if (!tp->gesture.hold_enabled) return; - tp_gesture_handle_event(tp, GESTURE_EVENT_HOLD_TIMEOUT, time); + if (!tp_gesture_is_quick_hold(tp)) + tp_gesture_handle_event(tp, GESTURE_EVENT_HOLD_TIMEOUT, time); } static void @@ -1263,6 +1289,13 @@ tp_gesture_cancel(struct tp_dispatch *tp, uint64_t time) tp_gesture_end(tp, time, true); } +void +tp_gesture_cancel_motion_gestures(struct tp_dispatch *tp, uint64_t time) +{ + if (tp->gesture.started && tp->gesture.state != GESTURE_STATE_HOLD) + tp_gesture_end(tp, time, true); +} + void tp_gesture_stop(struct tp_dispatch *tp, uint64_t time) { diff --git a/src/evdev-mt-touchpad-tap.c b/src/evdev-mt-touchpad-tap.c index cbd51036..7c4349a6 100644 --- a/src/evdev-mt-touchpad-tap.c +++ b/src/evdev-mt-touchpad-tap.c @@ -1656,3 +1656,16 @@ tp_tap_dragging(const struct tp_dispatch *tp) return false; } } + +bool +tp_tap_dragging_or_double_tapping(const struct tp_dispatch *tp) +{ + switch (tp->tap.state) { + case TAP_STATE_1FGTAP_DRAGGING_OR_DOUBLETAP: + case TAP_STATE_2FGTAP_DRAGGING_OR_DOUBLETAP: + case TAP_STATE_3FGTAP_DRAGGING_OR_DOUBLETAP: + return true; + default: + return false; + } +} diff --git a/src/evdev-mt-touchpad.c b/src/evdev-mt-touchpad.c index dbb8fd95..c97452a8 100644 --- a/src/evdev-mt-touchpad.c +++ b/src/evdev-mt-touchpad.c @@ -1874,7 +1874,7 @@ tp_post_events(struct tp_dispatch *tp, uint64_t time) if (ignore_motion) { tp_edge_scroll_stop_events(tp, time); - tp_gesture_cancel(tp, time); + tp_gesture_cancel_motion_gestures(tp, time); tp_gesture_post_events(tp, time, true); return; } diff --git a/src/evdev-mt-touchpad.h b/src/evdev-mt-touchpad.h index 8286085f..c44c7514 100644 --- a/src/evdev-mt-touchpad.h +++ b/src/evdev-mt-touchpad.h @@ -669,6 +669,9 @@ tp_tap_resume(struct tp_dispatch *tp, uint64_t time); bool tp_tap_dragging(const struct tp_dispatch *tp); +bool +tp_tap_dragging_or_double_tapping(const struct tp_dispatch *tp); + void tp_edge_scroll_init(struct tp_dispatch *tp, struct evdev_device *device); @@ -703,6 +706,9 @@ tp_gesture_stop(struct tp_dispatch *tp, uint64_t time); void tp_gesture_cancel(struct tp_dispatch *tp, uint64_t time); +void +tp_gesture_cancel_motion_gestures(struct tp_dispatch *tp, uint64_t time); + void tp_gesture_handle_state(struct tp_dispatch *tp, uint64_t time); diff --git a/test/litest.c b/test/litest.c index 120cbd7a..344d1749 100644 --- a/test/litest.c +++ b/test/litest.c @@ -4149,6 +4149,12 @@ litest_timeout_gesture_hold(void) msleep(300); } +void +litest_timeout_gesture_quick_hold(void) +{ + msleep(60); +} + void litest_timeout_trackpoint(void) { diff --git a/test/litest.h b/test/litest.h index e4e69bb7..888fb4a6 100644 --- a/test/litest.h +++ b/test/litest.h @@ -896,6 +896,9 @@ litest_timeout_gesture_scroll(void); void litest_timeout_gesture_hold(void); +void +litest_timeout_gesture_quick_hold(void); + void litest_timeout_trackpoint(void); diff --git a/test/test-gestures.c b/test/test-gestures.c index e488aab2..7f8701fd 100644 --- a/test/test-gestures.c +++ b/test/test-gestures.c @@ -1574,10 +1574,144 @@ START_TEST(gestures_hold_then_3fg_buttonarea_scroll) } END_TEST +START_TEST(gestures_hold_once_on_double_tap) +{ + struct litest_device *dev = litest_current_device(); + struct libinput *li = dev->libinput; + + if (!libinput_device_has_capability(dev->libinput_device, + LIBINPUT_DEVICE_CAP_GESTURE)) + return; + + litest_enable_tap(dev->libinput_device); + litest_drain_events(li); + + /* First tap, a hold gesture must be generated */ + litest_touch_down(dev, 0, 50, 50); + libinput_dispatch(li); + litest_timeout_gesture_quick_hold(); + litest_touch_up(dev, 0); + libinput_dispatch(li); + + litest_assert_gesture_event(li, + LIBINPUT_EVENT_GESTURE_HOLD_BEGIN, + 1); + litest_assert_gesture_event(li, + LIBINPUT_EVENT_GESTURE_HOLD_END, + 1); + litest_assert_button_event(li, BTN_LEFT, + LIBINPUT_BUTTON_STATE_PRESSED); + litest_assert_button_event(li, BTN_LEFT, + LIBINPUT_BUTTON_STATE_RELEASED); + litest_assert_empty_queue(li); + + /* Double tap, don't generate an extra hold gesture */ + litest_touch_down(dev, 0, 50, 50); + litest_touch_up(dev, 0); + libinput_dispatch(li); + litest_timeout_gesture_quick_hold(); + + litest_assert_button_event(li, BTN_LEFT, + LIBINPUT_BUTTON_STATE_PRESSED); + litest_assert_button_event(li, BTN_LEFT, + LIBINPUT_BUTTON_STATE_RELEASED); + + litest_assert_empty_queue(li); +} +END_TEST + +START_TEST(gestures_hold_once_tap_n_drag) +{ + struct litest_device *dev = litest_current_device(); + struct libinput *li = dev->libinput; + int nfingers = _i; /* ranged test */ + unsigned int button = 0; + + if (nfingers > litest_slot_count(dev)) + return; + + if (!libinput_device_has_capability(dev->libinput_device, + LIBINPUT_DEVICE_CAP_GESTURE)) + return; + + litest_enable_tap(dev->libinput_device); + litest_disable_drag_lock(dev->libinput_device); + litest_drain_events(li); + + switch (nfingers) { + case 1: + button = BTN_LEFT; + break; + case 2: + button = BTN_RIGHT; + break; + case 3: + button = BTN_MIDDLE; + break; + default: + abort(); + } + + switch (nfingers) { + case 3: + litest_touch_down(dev, 2, 60, 30); + /* fallthrough */ + case 2: + litest_touch_down(dev, 1, 50, 30); + /* fallthrough */ + case 1: + litest_touch_down(dev, 0, 40, 30); + break; + } + libinput_dispatch(li); + litest_timeout_gesture_quick_hold(); + + switch (nfingers) { + case 3: + litest_touch_up(dev, 2); + /* fallthrough */ + case 2: + litest_touch_up(dev, 1); + /* fallthrough */ + case 1: + litest_touch_up(dev, 0); + break; + } + libinput_dispatch(li); + + /* "Quick" hold gestures are only generated when using 1 or 2 fingers */ + if (nfingers == 1 || nfingers == 2) { + litest_assert_gesture_event(li, + LIBINPUT_EVENT_GESTURE_HOLD_BEGIN, + nfingers); + litest_assert_gesture_event(li, + LIBINPUT_EVENT_GESTURE_HOLD_END, + nfingers); + } + + /* Tap and drag, don't generate an extra hold gesture */ + litest_touch_down(dev, 0, 50, 50); + litest_touch_move_to(dev, 0, 50, 50, 80, 80, 20); + libinput_dispatch(li); + + litest_assert_button_event(li, button, + LIBINPUT_BUTTON_STATE_PRESSED); + litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_MOTION); + + litest_touch_up(dev, 0); + libinput_dispatch(li); + + litest_assert_button_event(li, button, + LIBINPUT_BUTTON_STATE_RELEASED); + litest_assert_empty_queue(li); +} +END_TEST + TEST_COLLECTION(gestures) { struct range cardinals = { N, N + NCARDINALS }; struct range range_hold = { 1, 5 }; + struct range range_multifinger_tap = {1, 4}; litest_add(gestures_cap, LITEST_TOUCHPAD, LITEST_SINGLE_TOUCH); litest_add(gestures_nocap, LITEST_ANY, LITEST_TOUCHPAD); @@ -1615,6 +1749,9 @@ TEST_COLLECTION(gestures) litest_add_ranged(gestures_hold_then_spread, LITEST_TOUCHPAD, LITEST_SINGLE_TOUCH, &cardinals); litest_add(gestures_hold_then_3fg_buttonarea_scroll, LITEST_CLICKPAD, LITEST_SINGLE_TOUCH); + litest_add(gestures_hold_once_on_double_tap, LITEST_TOUCHPAD, LITEST_SINGLE_TOUCH); + litest_add_ranged(gestures_hold_once_tap_n_drag, LITEST_TOUCHPAD, LITEST_ANY, &range_multifinger_tap); + /* Timing-sensitive test, valgrind is too slow */ if (!RUNNING_ON_VALGRIND) litest_add(gestures_swipe_3fg_unaccel, LITEST_TOUCHPAD, LITEST_SINGLE_TOUCH);