mirror of
https://gitlab.freedesktop.org/libinput/libinput.git
synced 2025-12-20 16:10:06 +01:00
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:
parent
92827de624
commit
9b024c6928
7 changed files with 202 additions and 4 deletions
|
|
@ -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)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue