gestures: add quick hold implementation
authorJosé Expósito <jose.exposito89@gmail.com>
Thu, 27 May 2021 17:20:37 +0000 (19:20 +0200)
committerPeter Hutterer <peter.hutterer@who-t.net>
Wed, 9 Jun 2021 01:18:58 +0000 (01:18 +0000)
When 1 or 2 fingers are used to hold, use a faster timer to make the
"hold to stop kinetic scrolling" user interaction feel more immediate.

Also handle double tap and tap and drag interations to send only one
hold gesture instead of two.

Holding with 3 or 4 fingers remains the same to try to avoid callers
missusing hold gestures to build their own tap implementation.

Signed-off-by: José Expósito <jose.exposito89@gmail.com>
src/evdev-mt-touchpad-gestures.c
src/evdev-mt-touchpad-tap.c
src/evdev-mt-touchpad.c
src/evdev-mt-touchpad.h
test/litest.c
test/litest.h
test/test-gestures.c

index 0d5fcedd58452a66ae6673da4efd981f2459df8a..4202802e3767886a875e15ae5cd8315f0497ec06 100644 (file)
@@ -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)
 {
index cbd510368f9eafd693a0430460da90e5c4798182..7c4349a62e73a63bafbd51b69b3b671391897b64 100644 (file)
@@ -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;
+       }
+}
index dbb8fd953a392e42d8dd1718fa6b221caec44144..c97452a88d945854ae3e16ed9cb37db96a1417b9 100644 (file)
@@ -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;
        }
index 8286085f6c5b00fc6571edf0b4db61184aefdf06..c44c7514b76b8d9a71b6f6bc7fa36cd6583602f3 100644 (file)
@@ -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);
 
index 120cbd7a1bd541c738ed7e24bb60509e3b41555b..344d17494ef5200d22f7b550204c8febd7e51856 100644 (file)
@@ -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)
 {
index e4e69bb7c618550e9191b4d1dacfd9845cc958df..888fb4a6a48a66ece320ba334136939ff27c8903 100644 (file)
@@ -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);
 
index e488aab2ed4943eabcac1b468fe6a023e127de61..7f8701fd380fd8666547dfdf3a222ec0cd85ee85 100644 (file)
@@ -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);