gestures: add quick hold implementation

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>
This commit is contained in:
José Expósito 2021-05-27 19:20:37 +02:00 committed by Peter Hutterer
parent 92827de624
commit 9b024c6928
7 changed files with 202 additions and 4 deletions

View file

@ -28,6 +28,7 @@
#include "evdev-mt-touchpad.h" #include "evdev-mt-touchpad.h"
#define QUICK_GESTURE_HOLD_TIMEOUT ms2us(40)
#define DEFAULT_GESTURE_HOLD_TIMEOUT ms2us(180) #define DEFAULT_GESTURE_HOLD_TIMEOUT ms2us(180)
#define DEFAULT_GESTURE_SWITCH_TIMEOUT ms2us(100) #define DEFAULT_GESTURE_SWITCH_TIMEOUT ms2us(100)
#define DEFAULT_GESTURE_SWIPE_TIMEOUT ms2us(150) #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)); 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 static bool
tp_gesture_use_hold_timer(struct tp_dispatch *tp) 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) if (!tp->tap.enabled)
return true; 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 /* If the number of fingers on the touchpad exceeds the number of
* allowed fingers to tap, use the timer. * allowed fingers to tap, use the timer.
*/ */
@ -510,12 +526,17 @@ tp_gesture_use_hold_timer(struct tp_dispatch *tp)
static void static void
tp_gesture_set_hold_timer(struct tp_dispatch *tp, uint64_t time) tp_gesture_set_hold_timer(struct tp_dispatch *tp, uint64_t time)
{ {
uint64_t timeout;
if (!tp->gesture.hold_enabled) if (!tp->gesture.hold_enabled)
return; return;
if (tp_gesture_use_hold_timer(tp)) { if (tp_gesture_use_hold_timer(tp)) {
libinput_timer_set(&tp->gesture.hold_timer, timeout = tp_gesture_is_quick_hold(tp) ?
time + DEFAULT_GESTURE_HOLD_TIMEOUT); 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) tp_gesture_hold_timeout(uint64_t now, void *data)
{ {
struct tp_dispatch *tp = 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); 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) if (!tp->gesture.hold_enabled)
return; 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 static void
@ -1263,6 +1289,13 @@ tp_gesture_cancel(struct tp_dispatch *tp, uint64_t time)
tp_gesture_end(tp, time, true); 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 void
tp_gesture_stop(struct tp_dispatch *tp, uint64_t time) tp_gesture_stop(struct tp_dispatch *tp, uint64_t time)
{ {

View file

@ -1656,3 +1656,16 @@ tp_tap_dragging(const struct tp_dispatch *tp)
return false; 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;
}
}

View file

@ -1874,7 +1874,7 @@ tp_post_events(struct tp_dispatch *tp, uint64_t time)
if (ignore_motion) { if (ignore_motion) {
tp_edge_scroll_stop_events(tp, time); 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); tp_gesture_post_events(tp, time, true);
return; return;
} }

View file

@ -669,6 +669,9 @@ tp_tap_resume(struct tp_dispatch *tp, uint64_t time);
bool bool
tp_tap_dragging(const struct tp_dispatch *tp); tp_tap_dragging(const struct tp_dispatch *tp);
bool
tp_tap_dragging_or_double_tapping(const struct tp_dispatch *tp);
void void
tp_edge_scroll_init(struct tp_dispatch *tp, struct evdev_device *device); 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 void
tp_gesture_cancel(struct tp_dispatch *tp, uint64_t time); tp_gesture_cancel(struct tp_dispatch *tp, uint64_t time);
void
tp_gesture_cancel_motion_gestures(struct tp_dispatch *tp, uint64_t time);
void void
tp_gesture_handle_state(struct tp_dispatch *tp, uint64_t time); tp_gesture_handle_state(struct tp_dispatch *tp, uint64_t time);

View file

@ -4149,6 +4149,12 @@ litest_timeout_gesture_hold(void)
msleep(300); msleep(300);
} }
void
litest_timeout_gesture_quick_hold(void)
{
msleep(60);
}
void void
litest_timeout_trackpoint(void) litest_timeout_trackpoint(void)
{ {

View file

@ -896,6 +896,9 @@ litest_timeout_gesture_scroll(void);
void void
litest_timeout_gesture_hold(void); litest_timeout_gesture_hold(void);
void
litest_timeout_gesture_quick_hold(void);
void void
litest_timeout_trackpoint(void); litest_timeout_trackpoint(void);

View file

@ -1574,10 +1574,144 @@ START_TEST(gestures_hold_then_3fg_buttonarea_scroll)
} }
END_TEST 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) TEST_COLLECTION(gestures)
{ {
struct range cardinals = { N, N + NCARDINALS }; struct range cardinals = { N, N + NCARDINALS };
struct range range_hold = { 1, 5 }; 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_cap, LITEST_TOUCHPAD, LITEST_SINGLE_TOUCH);
litest_add(gestures_nocap, LITEST_ANY, LITEST_TOUCHPAD); 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_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_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 */ /* Timing-sensitive test, valgrind is too slow */
if (!RUNNING_ON_VALGRIND) if (!RUNNING_ON_VALGRIND)
litest_add(gestures_swipe_3fg_unaccel, LITEST_TOUCHPAD, LITEST_SINGLE_TOUCH); litest_add(gestures_swipe_3fg_unaccel, LITEST_TOUCHPAD, LITEST_SINGLE_TOUCH);