touchpad: implement support for three-finger drag

Exposed via new configuration option this enables 3 and 4 finger
dragging on touchpads. When enabled a 3/4 finger swipe
gesture is actually a button down + motion + button up sequence.

If tapping is disabled the drag starts immediately, if tapping is
enabled the drag starts after the tap timeout/motion so we can distinguish
between a tap and a drag.

When fingers are released:
- if two fingers remain -> keep dragging
- if one finger remains -> release drag, switch to pointer motion

When 3/4 fingers are set down immediately after releasing all fingers
the drag continues, similar to the tap drag lock feature. This drag lock
is not currently configurable.

This matches the macos behavior for the same feature.

Part-of: <https://gitlab.freedesktop.org/libinput/libinput/-/merge_requests/1042>
This commit is contained in:
Peter Hutterer 2024-09-06 11:38:35 +10:00 committed by Marge Bot
parent ae86d8b1b6
commit 1d9e307e2b
16 changed files with 1199 additions and 5 deletions

View file

@ -37,6 +37,19 @@ Tapping is usually available on touchpads and the touchpad part of external
graphics tablets. Tapping is usually **not** available on touch screens,
for those devices it is expected to be implemented by the toolkit.
------------------------------------------------------------------------------
Three-finger drag
------------------------------------------------------------------------------
Three-finger drag allows emulates the mouse button down while three fingers
are down on a touchpad without the need to press a physical button or use
:ref:`tapndrag`. See :ref:`drag_3fg` for details on how this feature works.
Three-finger drag is usually available on touchpads and the touchpad part of
external graphics tablets. Three-finger drag is usually **not** available on
touch screens, for those devices it is expected to be implemented by the
toolkit.
------------------------------------------------------------------------------
Send Events Mode
------------------------------------------------------------------------------

40
doc/user/drag-3fg.rst Normal file
View file

@ -0,0 +1,40 @@
.. _drag_3fg:
==============================================================================
Three-finger drag
==============================================================================
Three-finger drag is a feature available on touchpads that emulates logical
button presses if three fingers are moving on the touchpad.
Three-finger drag is independent from :ref:`tapping` though some specific
behaviors may change when both features are enabled. For example, with
tapping *disabled* a three-finger gesture will virtually always be a three-finger
drag. With tapping *enabled* a three finger gesture may be a three finger drag
and a short delay is required to disambiguate between the two.
The exact behavior of three-finger drag is implementation defined and may
subtly change. As a general rule, the following constraints can be expected:
- three fingers down and movement trigger a button down and subsequent motion
events (i.e. a drag)
- releasing one finger while keeping two fingers down will keep the drag
and *not* switch to :ref:`twofinger_scrolling`.
- releasing two fingers while keeping one finger down will end the drag
(and thus release the button) and switch to normal pointer motion
- releasing all three fingers and putting three fingers back on the touchpad
immediately will keep the drag (i.e. behave as if the fingers were
never lifted)
- if tapping is enabled: a three finger tap immediately after a three-finger
drag will *not* tap, the user needs to wait past the timeout to
three-finger tap
- releasing all three fingers and putting one or two fingers back on
the touchpad will end the drag (and thus release the button)
and proceed with pointer motion or two-finger scrolling, if applicable
- if tapping is enabled: a one or two finger tap immediately after a
three-finger drag will trigger a one or two finger tap. The user does
not have to wait past the drag release timeout

View file

@ -22,6 +22,7 @@ to be useful.
scrolling.rst
t440-support.rst
tapping.rst
drag-3fg.rst
tablet-support.rst
switches.rst
touchpad-pressure.rst

View file

@ -145,6 +145,7 @@ src_rst = files(
'contributing.rst',
'device-configuration-via-udev.rst',
'device-quirks.rst',
'drag-3fg.rst',
'faqs.rst',
'gestures.rst',
'incorrectly-enabled-hires.rst',

View file

@ -55,6 +55,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_RELEASE_TIMEOUT,
};
/*****************************************
@ -81,6 +83,9 @@ 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_START);
CASE_RETURN_STRING(GESTURE_STATE_3FG_DRAG);
CASE_RETURN_STRING(GESTURE_STATE_3FG_DRAG_RELEASED);
}
return NULL;
}
@ -101,6 +106,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_RELEASE_TIMEOUT);
}
return NULL;
}
@ -313,6 +320,16 @@ tp_gesture_init_pinch(struct tp_dispatch *tp)
tp->gesture.prev_scale = 1.0;
}
static inline void
tp_gesture_init_3fg_drag(struct tp_dispatch *tp, uint64_t time)
{
}
static inline void
tp_gesture_stop_3fg_drag(struct tp_dispatch *tp, uint64_t time)
{
}
static void
tp_gesture_set_scroll_buildup(struct tp_dispatch *tp)
{
@ -540,8 +557,16 @@ tp_gesture_handle_event_on_state_none(struct tp_dispatch *tp,
libinput_timer_cancel(&tp->gesture.hold_timer);
break;
case GESTURE_EVENT_FINGER_DETECTED:
tp_gesture_set_hold_timer(tp, time);
tp->gesture.state = GESTURE_STATE_UNKNOWN;
/* Note: this makes 3fg drag more responsive but disables
* 3fg pinch/hold. Those are niche enough to not worry about
* for now.
*/
if (!tp->tap.enabled && tp->drag_3fg.nfingers == tp->gesture.finger_count) {
tp->gesture.state = GESTURE_STATE_3FG_DRAG_START;
} else {
tp_gesture_set_hold_timer(tp, time);
tp->gesture.state = GESTURE_STATE_UNKNOWN;
}
break;
case GESTURE_EVENT_HOLD_TIMEOUT:
case GESTURE_EVENT_TAP_TIMEOUT:
@ -556,6 +581,8 @@ 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_RELEASE_TIMEOUT:
log_gesture_bug(tp, event);
break;
}
@ -600,8 +627,14 @@ 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:
libinput_timer_cancel(&tp->gesture.hold_timer);
tp_gesture_init_3fg_drag(tp, time);
tp->gesture.state = GESTURE_STATE_3FG_DRAG_START;
break;
case GESTURE_EVENT_HOLD_AND_MOTION_START:
case GESTURE_EVENT_FINGER_DETECTED:
case GESTURE_EVENT_3FG_DRAG_RELEASE_TIMEOUT:
log_gesture_bug(tp, event);
break;
}
@ -649,9 +682,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:
tp_gesture_cancel(tp, time);
tp_gesture_init_3fg_drag(tp, time);
tp->gesture.state = GESTURE_STATE_3FG_DRAG_START;
break;
case GESTURE_EVENT_HOLD_TIMEOUT:
case GESTURE_EVENT_TAP_TIMEOUT:
case GESTURE_EVENT_FINGER_DETECTED:
case GESTURE_EVENT_3FG_DRAG_RELEASE_TIMEOUT:
log_gesture_bug(tp, event);
break;
}
@ -689,6 +728,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_RELEASE_TIMEOUT:
log_gesture_bug(tp, event);
break;
}
@ -732,6 +773,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_RELEASE_TIMEOUT:
log_gesture_bug(tp, event);
break;
}
@ -763,6 +806,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_RELEASE_TIMEOUT:
log_gesture_bug(tp, event);
break;
}
@ -796,6 +841,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_RELEASE_TIMEOUT:
log_gesture_bug(tp, event);
break;
}
@ -822,6 +869,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_RELEASE_TIMEOUT:
log_gesture_bug(tp, event);
break;
}
@ -858,6 +907,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_RELEASE_TIMEOUT:
log_gesture_bug(tp, event);
break;
}
@ -885,6 +936,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_RELEASE_TIMEOUT:
log_gesture_bug(tp, event);
break;
}
@ -921,11 +974,159 @@ 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_RELEASE_TIMEOUT:
log_gesture_bug(tp, event);
break;
}
}
static void
tp_gesture_handle_event_on_state_3fg_drag_start(struct tp_dispatch *tp,
enum gesture_event event,
uint64_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_START:
case GESTURE_EVENT_3FG_DRAG_RELEASE_TIMEOUT:
log_gesture_bug(tp, event);
break;
}
}
static void
tp_gesture_set_3fg_drag_timer(struct tp_dispatch *tp, uint64_t time)
{
tp->gesture.drag_3fg_release_time = time;
libinput_timer_set(&tp->gesture.drag_3fg_timer, time + ms2us(700));
}
static void
tp_gesture_handle_event_on_state_3fg_drag(struct tp_dispatch *tp,
enum gesture_event event,
uint64_t time)
{
switch(event) {
case GESTURE_EVENT_RESET:
log_gesture_bug(tp, event);
break;
case GESTURE_EVENT_CANCEL:
/* If the gesture is cancelled we release the button immediately */
evdev_pointer_notify_button(tp->device,
tp->gesture.drag_3fg_release_time,
BTN_LEFT,
LIBINPUT_BUTTON_STATE_RELEASED);
tp->gesture.state = GESTURE_STATE_NONE;
break;
case GESTURE_EVENT_END:
/* If the gesture ends we start the timer so we
* can keep dragging */
tp_gesture_set_3fg_drag_timer(tp, time);
tp->gesture.state = GESTURE_STATE_3FG_DRAG_RELEASED;
break;
case GESTURE_EVENT_FINGER_SWITCH_TIMEOUT:
if (tp->gesture.finger_count_pending < 2) {
evdev_pointer_notify_button(tp->device,
tp->gesture.drag_3fg_release_time,
BTN_LEFT,
LIBINPUT_BUTTON_STATE_RELEASED);
tp->gesture.state = GESTURE_STATE_NONE;
}
break;
case GESTURE_EVENT_TAP_TIMEOUT:
break;
case GESTURE_EVENT_HOLD_AND_MOTION_START:
case GESTURE_EVENT_FINGER_DETECTED:
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_START:
case GESTURE_EVENT_3FG_DRAG_RELEASE_TIMEOUT:
log_gesture_bug(tp, event);
break;
}
}
static void
tp_gesture_handle_event_on_state_3fg_drag_released(struct tp_dispatch *tp,
enum gesture_event event,
uint64_t time)
{
switch(event) {
case GESTURE_EVENT_RESET:
log_gesture_bug(tp, event);
break;
case GESTURE_EVENT_END:
case GESTURE_EVENT_CANCEL:
case GESTURE_EVENT_3FG_DRAG_RELEASE_TIMEOUT:
tp_gesture_stop_3fg_drag(tp, time);
libinput_timer_cancel(&tp->gesture.drag_3fg_timer);
libinput_timer_cancel(&tp->gesture.finger_count_switch_timer);
evdev_pointer_notify_button(tp->device,
tp->gesture.drag_3fg_release_time,
BTN_LEFT,
LIBINPUT_BUTTON_STATE_RELEASED);
tp->gesture.state = GESTURE_STATE_NONE;
break;
case GESTURE_EVENT_FINGER_SWITCH_TIMEOUT:
case GESTURE_EVENT_TAP_TIMEOUT:
if (tp->gesture.finger_count_pending == tp->drag_3fg.nfingers) {
libinput_timer_cancel(&tp->gesture.drag_3fg_timer);
tp->gesture.state = GESTURE_STATE_3FG_DRAG;
}
break;
case GESTURE_EVENT_FINGER_DETECTED:
break;
case GESTURE_EVENT_POINTER_MOTION_START:
tp_gesture_stop_3fg_drag(tp, time);
libinput_timer_cancel(&tp->gesture.drag_3fg_timer);
evdev_pointer_notify_button(tp->device,
tp->gesture.drag_3fg_release_time,
BTN_LEFT,
LIBINPUT_BUTTON_STATE_RELEASED);
tp->gesture.state = GESTURE_STATE_POINTER_MOTION;
break;
case GESTURE_EVENT_HOLD_AND_MOTION_START:
case GESTURE_EVENT_HOLD_TIMEOUT:
log_gesture_bug(tp, event);
break;
/* Anything that's detected as gesture in this state
* will be continue the current 3fg drag gesture */
case GESTURE_EVENT_SCROLL_START:
libinput_timer_cancel(&tp->gesture.drag_3fg_timer);
evdev_pointer_notify_button(tp->device,
tp->gesture.drag_3fg_release_time,
BTN_LEFT,
LIBINPUT_BUTTON_STATE_RELEASED);
tp->gesture.state = GESTURE_STATE_SCROLL_START;
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;
}
}
static void
tp_gesture_handle_event(struct tp_dispatch *tp,
enum gesture_event event,
@ -969,6 +1170,15 @@ tp_gesture_handle_event(struct tp_dispatch *tp,
case GESTURE_STATE_SWIPE:
tp_gesture_handle_event_on_state_swipe(tp, event, time);
break;
case GESTURE_STATE_3FG_DRAG_START:
tp_gesture_handle_event_on_state_3fg_drag_start(tp, event, time);
break;
case GESTURE_STATE_3FG_DRAG:
tp_gesture_handle_event_on_state_3fg_drag(tp, event, time);
break;
case GESTURE_STATE_3FG_DRAG_RELEASED:
tp_gesture_handle_event_on_state_3fg_drag_released(tp, event, time);
break;
}
if (oldstate != tp->gesture.state) {
@ -1002,6 +1212,14 @@ tp_gesture_tap_timeout(struct tp_dispatch *tp, uint64_t time)
tp_gesture_handle_event(tp, GESTURE_EVENT_TAP_TIMEOUT, time);
}
static void
tp_gesture_3fg_drag_timeout(uint64_t now, void *data)
{
struct tp_dispatch *tp = data;
tp_gesture_handle_event(tp, GESTURE_EVENT_3FG_DRAG_RELEASE_TIMEOUT, now);
}
static void
tp_gesture_detect_motion_gestures(struct tp_dispatch *tp, uint64_t time)
{
@ -1044,10 +1262,13 @@ tp_gesture_detect_motion_gestures(struct tp_dispatch *tp, uint64_t time)
}
/* If we have more fingers than slots, we don't know where the
* fingers are. Default to swipe */
* fingers are. Default to swipe/3fg drag */
if (tp->gesture.enabled && tp->gesture.finger_count > 2 &&
tp->gesture.finger_count > tp->num_slots) {
tp_gesture_handle_event(tp, GESTURE_EVENT_SWIPE_START, time);
if (tp->drag_3fg.nfingers == tp->gesture.finger_count)
tp_gesture_handle_event(tp, GESTURE_EVENT_3FG_DRAG_START, time);
else
tp_gesture_handle_event(tp, GESTURE_EVENT_SWIPE_START, time);
return;
}
@ -1073,6 +1294,8 @@ tp_gesture_detect_motion_gestures(struct tp_dispatch *tp, uint64_t time)
time > (tp->gesture.initial_time + DEFAULT_GESTURE_SWIPE_TIMEOUT)) {
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);
else
tp_gesture_handle_event(tp, GESTURE_EVENT_SWIPE_START, time);
@ -1154,6 +1377,11 @@ tp_gesture_detect_motion_gestures(struct tp_dispatch *tp, uint64_t time)
return;
}
if (tp->drag_3fg.nfingers == tp->gesture.finger_count) {
tp_gesture_handle_event(tp, GESTURE_EVENT_3FG_DRAG_START, time);
return;
}
if (tp->gesture.enabled) {
tp_gesture_handle_event(tp, GESTURE_EVENT_SWIPE_START, time);
return;
@ -1468,6 +1696,40 @@ tp_gesture_handle_state_pinch(struct tp_dispatch *tp, uint64_t time)
tp->gesture.prev_scale = scale;
}
static void
tp_gesture_handle_state_3fg_drag_start(struct tp_dispatch *tp, uint64_t time)
{
evdev_pointer_notify_button(tp->device,
time,
BTN_LEFT,
LIBINPUT_BUTTON_STATE_PRESSED);
/* FIXME: immediately send a motion event? */
tp->gesture.state = GESTURE_STATE_3FG_DRAG;
}
static void
tp_gesture_handle_state_3fg_drag(struct tp_dispatch *tp, uint64_t time)
{
if (!(tp->queued & TOUCHPAD_EVENT_MOTION))
return;
struct device_float_coords raw = tp_get_average_touches_delta(tp);
struct normalized_coords delta = tp_filter_motion(tp, &raw, time);
if (!normalized_is_zero(delta) || !device_float_is_zero(raw)) {
if (tp->queued & TOUCHPAD_EVENT_MOTION)
tp_gesture_post_pointer_motion(tp, time);
}
}
static void
tp_gesture_handle_state_3fg_drag_released(struct tp_dispatch *tp,
uint64_t time,
bool ignore_motion)
{
tp_gesture_detect_motion_gestures(tp, time);
}
static void
tp_gesture_handle_state(struct tp_dispatch *tp, uint64_t time,
bool ignore_motion)
@ -1529,6 +1791,18 @@ tp_gesture_handle_state(struct tp_dispatch *tp, uint64_t time,
tp_gesture_handle_state_pinch_start(tp, time);
REMEMBER_TRANSITION(transition_state, tp->gesture.state);
}
if (tp->gesture.state == GESTURE_STATE_3FG_DRAG) {
tp_gesture_handle_state_3fg_drag(tp, time);
REMEMBER_TRANSITION(transition_state, tp->gesture.state);
}
if (tp->gesture.state == GESTURE_STATE_3FG_DRAG_START) {
tp_gesture_handle_state_3fg_drag_start(tp, time);
REMEMBER_TRANSITION(transition_state, tp->gesture.state);
}
if (tp->gesture.state == GESTURE_STATE_3FG_DRAG_RELEASED) {
tp_gesture_handle_state_3fg_drag_released(tp, time, ignore_motion);
REMEMBER_TRANSITION(transition_state, tp->gesture.state);
}
#undef REMEMBER_TRANSITION
@ -1628,6 +1902,7 @@ tp_gesture_end(struct tp_dispatch *tp, uint64_t time, enum gesture_cancelled can
case GESTURE_STATE_SCROLL_START:
case GESTURE_STATE_PINCH_START:
case GESTURE_STATE_SWIPE_START:
case GESTURE_STATE_3FG_DRAG_START:
tp_gesture_handle_event(tp, GESTURE_EVENT_RESET, time);
break;
case GESTURE_STATE_HOLD:
@ -1636,6 +1911,8 @@ tp_gesture_end(struct tp_dispatch *tp, uint64_t time, enum gesture_cancelled can
case GESTURE_STATE_SCROLL:
case GESTURE_STATE_PINCH:
case GESTURE_STATE_SWIPE:
case GESTURE_STATE_3FG_DRAG:
case GESTURE_STATE_3FG_DRAG_RELEASED:
switch (cancelled) {
case CANCEL_GESTURE:
tp_gesture_handle_event(tp, GESTURE_EVENT_CANCEL, time);
@ -1657,12 +1934,14 @@ tp_gesture_cancel(struct tp_dispatch *tp, uint64_t time)
void
tp_gesture_cancel_motion_gestures(struct tp_dispatch *tp, uint64_t time)
{
switch (tp->gesture.state) {
case GESTURE_STATE_NONE:
case GESTURE_STATE_UNKNOWN:
case GESTURE_STATE_SCROLL_START:
case GESTURE_STATE_PINCH_START:
case GESTURE_STATE_SWIPE_START:
case GESTURE_STATE_3FG_DRAG_START:
break;
case GESTURE_STATE_HOLD:
break;
@ -1671,8 +1950,13 @@ tp_gesture_cancel_motion_gestures(struct tp_dispatch *tp, uint64_t time)
case GESTURE_STATE_SCROLL:
case GESTURE_STATE_PINCH:
case GESTURE_STATE_SWIPE:
evdev_log_debug(tp->device, "Cancelling motion gestures\n");
tp_gesture_cancel(tp, time);
break;
case GESTURE_STATE_3FG_DRAG:
break;
case GESTURE_STATE_3FG_DRAG_RELEASED:
break;
}
}
@ -1711,6 +1995,9 @@ tp_gesture_debounce_finger_changes(struct tp_dispatch *tp)
case GESTURE_STATE_SCROLL:
case GESTURE_STATE_PINCH:
case GESTURE_STATE_SWIPE:
case GESTURE_STATE_3FG_DRAG_START:
case GESTURE_STATE_3FG_DRAG_RELEASED:
case GESTURE_STATE_3FG_DRAG:
return true;
}
@ -1751,7 +2038,7 @@ tp_gesture_update_finger_state(struct tp_dispatch *tp, uint64_t time)
} else if (active_touches != tp->gesture.finger_count_pending) {
tp->gesture.finger_count_pending = active_touches;
libinput_timer_set(&tp->gesture.finger_count_switch_timer,
time + DEFAULT_GESTURE_SWITCH_TIMEOUT);
time + DEFAULT_GESTURE_SWITCH_TIMEOUT);
}
} else {
tp->gesture.finger_count_pending = 0;
@ -1800,6 +2087,94 @@ tp_gesture_get_hold_default(struct libinput_device *device)
LIBINPUT_CONFIG_HOLD_DISABLED;
}
static int
tp_3fg_drag_count(struct libinput_device *device)
{
struct evdev_dispatch *dispatch = evdev_device(device)->dispatch;
struct tp_dispatch *tp = tp_dispatch(dispatch);
/* If we can't to gestures we can't do 3fg drag */
if (!tp_gesture_are_gestures_enabled(tp))
return 0;
/* For now return the number of MT slots until we need to figure out
* if we can implement this on a 2-finger BTN_TOOL_TRIPLETAP device */
return tp->num_slots;
}
static enum libinput_config_status
tp_3fg_drag_set_enabled(struct libinput_device *device,
enum libinput_config_3fg_drag_state enabled)
{
struct evdev_dispatch *dispatch = evdev_device(device)->dispatch;
struct tp_dispatch *tp = tp_dispatch(dispatch);
if (tp_3fg_drag_count(device) < 3)
return LIBINPUT_CONFIG_STATUS_UNSUPPORTED;
switch (enabled) {
case LIBINPUT_CONFIG_3FG_DRAG_DISABLED:
tp->drag_3fg.want_nfingers = 0;
break;
case LIBINPUT_CONFIG_3FG_DRAG_ENABLED_3FG:
tp->drag_3fg.want_nfingers = 3;
break;
case LIBINPUT_CONFIG_3FG_DRAG_ENABLED_4FG:
tp->drag_3fg.want_nfingers = 4;
break;
}
tp_3fg_drag_apply_config(evdev_device(device));
return LIBINPUT_CONFIG_STATUS_SUCCESS;
}
static enum libinput_config_3fg_drag_state
tp_3fg_drag_get_enabled(struct libinput_device *device)
{
struct evdev_dispatch *dispatch = evdev_device(device)->dispatch;
struct tp_dispatch *tp = tp_dispatch(dispatch);
switch (tp->drag_3fg.want_nfingers) {
case 3:
return LIBINPUT_CONFIG_3FG_DRAG_ENABLED_3FG;
case 4:
return LIBINPUT_CONFIG_3FG_DRAG_ENABLED_4FG;
}
return LIBINPUT_CONFIG_3FG_DRAG_DISABLED;
}
static enum libinput_config_3fg_drag_state
tp_3fg_drag_default(struct tp_dispatch *tp)
{
return LIBINPUT_CONFIG_3FG_DRAG_DISABLED;
}
static enum libinput_config_3fg_drag_state
tp_3fg_drag_get_default_enabled(struct libinput_device *device)
{
struct evdev_dispatch *dispatch = evdev_device(device)->dispatch;
struct tp_dispatch *tp = tp_dispatch(dispatch);
return tp_3fg_drag_default(tp);
}
void
tp_3fg_drag_apply_config(struct evdev_device *device)
{
struct tp_dispatch *tp = (struct tp_dispatch *)device->dispatch;
if (tp->drag_3fg.want_nfingers == tp->drag_3fg.nfingers)
return;
if (tp->nfingers_down)
return;
tp->drag_3fg.nfingers = tp->drag_3fg.want_nfingers;
evdev_log_debug(device, "touchpad-3fg-drag: drag is now for %zd fingers\n", tp->drag_3fg.nfingers);
}
void
tp_init_gesture(struct tp_dispatch *tp)
{
@ -1810,6 +2185,25 @@ tp_init_gesture(struct tp_dispatch *tp)
tp->gesture.config.get_hold_default = tp_gesture_get_hold_default;
tp->device->base.config.gesture = &tp->gesture.config;
tp->drag_3fg.config.count = tp_3fg_drag_count;
tp->drag_3fg.config.set_enabled = tp_3fg_drag_set_enabled;
tp->drag_3fg.config.get_enabled = tp_3fg_drag_get_enabled;
tp->drag_3fg.config.get_default = tp_3fg_drag_get_default_enabled;
tp->device->base.config.drag_3fg = &tp->drag_3fg.config;
switch (tp_3fg_drag_default(tp)) {
case LIBINPUT_CONFIG_3FG_DRAG_DISABLED:
tp->drag_3fg.nfingers = 0;
break;
case LIBINPUT_CONFIG_3FG_DRAG_ENABLED_3FG:
tp->drag_3fg.nfingers = 3;
break;
case LIBINPUT_CONFIG_3FG_DRAG_ENABLED_4FG:
tp->drag_3fg.nfingers = 4;
break;
}
tp->drag_3fg.want_nfingers = tp->drag_3fg.nfingers;
/* two-finger scrolling is always enabled, this flag just
* decides whether we detect pinch. semi-mt devices are too
* unreliable to do pinch gestures. */
@ -1835,6 +2229,14 @@ tp_init_gesture(struct tp_dispatch *tp)
tp_libinput_context(tp),
timer_name,
tp_gesture_hold_timeout, tp);
snprintf(timer_name,
sizeof(timer_name),
"%s drag_3fg",
evdev_device_get_sysname(tp->device));
libinput_timer_init(&tp->gesture.drag_3fg_timer,
tp_libinput_context(tp),
timer_name,
tp_gesture_3fg_drag_timeout, tp);
}
void
@ -1842,4 +2244,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);
}

View file

@ -1910,6 +1910,7 @@ tp_handle_state(struct tp_dispatch *tp,
tp_clickpad_middlebutton_apply_config(tp->device);
tp_apply_rotation(tp->device);
tp_3fg_drag_apply_config(tp->device);
}
LIBINPUT_UNUSED
@ -2023,6 +2024,7 @@ tp_interface_destroy(struct evdev_dispatch *dispatch)
libinput_timer_destroy(&tp->tap.timer);
libinput_timer_destroy(&tp->gesture.finger_count_switch_timer);
libinput_timer_destroy(&tp->gesture.hold_timer);
libinput_timer_destroy(&tp->gesture.drag_3fg_timer);
free(tp->touches);
free(tp);
}

View file

@ -166,6 +166,9 @@ enum tp_gesture_state {
GESTURE_STATE_PINCH,
GESTURE_STATE_SWIPE_START,
GESTURE_STATE_SWIPE,
GESTURE_STATE_3FG_DRAG_START,
GESTURE_STATE_3FG_DRAG,
GESTURE_STATE_3FG_DRAG_RELEASED,
};
enum tp_thumb_state {
@ -365,6 +368,9 @@ struct tp_dispatch {
struct device_float_coords center;
struct libinput_timer hold_timer;
bool hold_enabled;
struct libinput_timer drag_3fg_timer;
uint64_t drag_3fg_release_time;
} gesture;
struct {
@ -442,6 +448,12 @@ struct tp_dispatch {
unsigned int nfingers_down; /* number of fingers down for tapping (excl. thumb/palm) */
} tap;
struct {
struct libinput_device_config_3fg_drag config;
size_t nfingers;
size_t want_nfingers;
} drag_3fg;
struct {
struct libinput_device_config_dwtp config;
bool dwtp_enabled;
@ -775,4 +787,7 @@ tp_init_thumb(struct tp_dispatch *tp);
struct tp_touch*
tp_thumb_get_touch(struct tp_dispatch *tp);
void
tp_3fg_drag_apply_config(struct evdev_device *device);
#endif

View file

@ -218,6 +218,15 @@ struct libinput_device_config_tap {
enum libinput_config_drag_lock_state (*get_default_draglock_enabled)(struct libinput_device *device);
};
struct libinput_device_config_3fg_drag {
int (*count)(struct libinput_device *device);
enum libinput_config_status (*set_enabled)(struct libinput_device *device,
enum libinput_config_3fg_drag_state enable);
enum libinput_config_3fg_drag_state (*get_enabled)(struct libinput_device *device);
enum libinput_config_3fg_drag_state (*get_default)(struct libinput_device *device);
};
struct libinput_device_config_calibration {
int (*has_matrix)(struct libinput_device *device);
enum libinput_config_status (*set_matrix)(struct libinput_device *device,
@ -411,6 +420,7 @@ struct libinput_device_config {
struct libinput_device_config_dwtp *dwtp;
struct libinput_device_config_rotation *rotation;
struct libinput_device_config_gesture *gesture;
struct libinput_device_config_3fg_drag *drag_3fg;
};
struct libinput_device_group {

View file

@ -4118,6 +4118,48 @@ libinput_device_config_tap_get_default_drag_lock_enabled(struct libinput_device
return device->config.tap->get_default_draglock_enabled(device);
}
LIBINPUT_EXPORT int
libinput_device_config_3fg_drag_get_finger_count(struct libinput_device *device)
{
return device->config.drag_3fg ? device->config.drag_3fg->count(device) : 0;
}
LIBINPUT_EXPORT enum libinput_config_status
libinput_device_config_3fg_drag_set_enabled(struct libinput_device *device,
enum libinput_config_3fg_drag_state enable)
{
if (libinput_device_config_3fg_drag_get_finger_count(device) < 3)
return LIBINPUT_CONFIG_STATUS_UNSUPPORTED;
switch (enable) {
case LIBINPUT_CONFIG_3FG_DRAG_DISABLED:
case LIBINPUT_CONFIG_3FG_DRAG_ENABLED_3FG:
case LIBINPUT_CONFIG_3FG_DRAG_ENABLED_4FG:
return device->config.drag_3fg->set_enabled(device, enable);
break;
}
return LIBINPUT_CONFIG_STATUS_INVALID;
}
LIBINPUT_EXPORT enum libinput_config_3fg_drag_state
libinput_device_config_3fg_drag_get_enabled(struct libinput_device *device)
{
if (libinput_device_config_3fg_drag_get_finger_count(device) < 3)
return LIBINPUT_CONFIG_3FG_DRAG_DISABLED;
return device->config.drag_3fg->get_enabled(device);
}
LIBINPUT_EXPORT enum libinput_config_3fg_drag_state
libinput_device_config_3fg_drag_get_default_enabled(struct libinput_device *device)
{
if (libinput_device_config_3fg_drag_get_finger_count(device) < 3)
return LIBINPUT_CONFIG_3FG_DRAG_DISABLED;
return device->config.drag_3fg->get_default(device);
}
LIBINPUT_EXPORT int
libinput_device_config_calibration_has_matrix(struct libinput_device *device)
{

View file

@ -5024,6 +5024,107 @@ libinput_device_config_tap_get_drag_lock_enabled(struct libinput_device *device)
enum libinput_config_drag_lock_state
libinput_device_config_tap_get_default_drag_lock_enabled(struct libinput_device *device);
/**
* @ingroup config
*
* A config status to distinguish or set 3-finger dragging on a device.
*
* @since 1.27
*/
enum libinput_config_3fg_drag_state {
/**
* Drag is to be disabled, or is
* currently disabled.
*/
LIBINPUT_CONFIG_3FG_DRAG_DISABLED,
/**
* Drag is to be enabled for 3 fingers, or is
* currently enabled
*/
LIBINPUT_CONFIG_3FG_DRAG_ENABLED_3FG,
/**
* Drag is to be enabled for 4 fingers, or is
* currently enabled
*/
LIBINPUT_CONFIG_3FG_DRAG_ENABLED_4FG,
};
/**
* @ingroup config
*
* Returns the maximum number of fingers available for 3-finger dragging.
*
* @param device The device to check
*
* @see libinput_device_config_3fg_drag_set_enabled
* @see libinput_device_config_3fg_drag_get_enabled
* @see libinput_device_config_3fg_drag_get_default_enabled
*
* @since 1.27
*/
int
libinput_device_config_3fg_drag_get_finger_count(struct libinput_device *device);
/**
* @ingroup config
*
* Enable or disable 3-finger drag on this device. When enabled, three fingers
* down will result in a button down event, subsequent finger motion triggers
* a drag. The button is released shortly after all fingers are logically up.
* See the libinput documentation for more details.
*
* @param device The device to configure
* @param enable @ref LIBINPUT_CONFIG_DRAG_ENABLED to enable, @ref
* LIBINPUT_CONFIG_DRAG_DISABLED to disable 3-finger drag
*
* @see libinput_device_config_3fg_drag_is_available
* @see libinput_device_config_3fg_drag_get_enabled
* @see libinput_device_config_3fg_drag_get_default_enabled
*
* @since 1.27
*/
enum libinput_config_status
libinput_device_config_3fg_drag_set_enabled(struct libinput_device *device,
enum libinput_config_3fg_drag_state enable);
/**
* @ingroup config
*
* Return whether 3-finger drag is enabled or disabled on this device.
*
* @param device The device to check
* @retval LIBINPUT_CONFIG_DRAG_ENABLED if 3-finger drag is enabled
* @retval LIBINPUT_CONFIG_DRAG_DISABLED if 3-finger drag is
* disabled
*
* @see libinput_device_config_3fg_drag_is_available
* @see libinput_device_config_3fg_drag_set_enabled
* @see libinput_device_config_3fg_drag_get_default_enabled
*
* @since 1.27
*/
enum libinput_config_3fg_drag_state
libinput_device_config_3fg_drag_get_enabled(struct libinput_device *device);
/**
* @ingroup config
*
* Return whether 3-finger drag is enabled or disabled by default on this device.
*
* @param device The device to check
* @retval LIBINPUT_CONFIG_DRAG_ENABLED if 3-finger drag is enabled
* @retval LIBINPUT_CONFIG_DRAG_DISABLED if 3-finger drag is
* disabled
*
* @see libinput_device_config_3fg_drag_is_available
* @see libinput_device_config_3fg_drag_set_enabled
* @see libinput_device_config_3fg_drag_get_enabled
*
* @since 1.27
*/
enum libinput_config_3fg_drag_state
libinput_device_config_3fg_drag_get_default_enabled(struct libinput_device *device);
/**
* @ingroup config
*

View file

@ -357,3 +357,10 @@ LIBINPUT_1.27 {
libinput_device_config_area_get_rectangle;
libinput_device_config_area_get_default_rectangle;
} LIBINPUT_1.26;
LIBINPUT_1.28 {
libinput_device_config_3fg_drag_get_finger_count;
libinput_device_config_3fg_drag_set_enabled;
libinput_device_config_3fg_drag_get_enabled;
libinput_device_config_3fg_drag_get_default_enabled;
} LIBINPUT_1.27;

View file

@ -4914,6 +4914,12 @@ litest_timeout_hysteresis(void)
msleep(90);
}
void
litest_timeout_3fg_drag(void)
{
msleep(800);
}
void
litest_push_event_frame(struct litest_device *dev)
{

View file

@ -1236,6 +1236,9 @@ litest_timeout_touch_arbitration(void);
void
litest_timeout_hysteresis(void);
void
litest_timeout_3fg_drag(void);
void
litest_push_event_frame(struct litest_device *dev);
@ -1268,6 +1271,29 @@ litest_semi_mt_touch_up(struct litest_device *d,
struct litest_semi_mt *semi_mt,
unsigned int slot);
static inline
void litest_enable_3fg_drag(struct libinput_device *device,
unsigned int nfingers)
{
enum libinput_config_3fg_drag_state enabled;
switch (nfingers) {
case 3:
enabled = LIBINPUT_CONFIG_3FG_DRAG_ENABLED_3FG;
break;
case 4:
enabled = LIBINPUT_CONFIG_3FG_DRAG_ENABLED_4FG;
break;
default:
litest_abort_msg("Invalid finger count");
break;
}
enum libinput_config_status status =
libinput_device_config_3fg_drag_set_enabled(device, enabled);
litest_assert_enum_eq(status, LIBINPUT_CONFIG_STATUS_SUCCESS);
}
static inline void
litest_enable_tap(struct libinput_device *device)
{

View file

@ -1782,11 +1782,491 @@ START_TEST(gestures_hold_and_motion_after_timeout)
}
END_TEST
START_TEST(gestures_3fg_drag)
{
struct litest_device *dev = litest_current_device();
struct libinput *li = dev->libinput;
uint32_t finger_count;
bool tap_enabled;
litest_test_param_fetch(test_env->params,
"fingers", 'u', &finger_count,
"tap-enabled", 'b', &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);
litest_dispatch(li);
litest_drain_events_of_type(li, LIBINPUT_EVENT_GESTURE_HOLD_BEGIN, LIBINPUT_EVENT_GESTURE_HOLD_END);
if (tap_enabled) {
litest_checkpoint("Expecting no immediate button press as tapping is enabled");
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);
}
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);
}
if (tap_enabled) {
litest_checkpoint("Expecting late button press as tapping is enabled");
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++)
litest_touch_up(dev, i);
litest_dispatch(li);
litest_assert_empty_queue(li);
litest_timeout_3fg_drag();
litest_dispatch(li);
litest_assert_button_event(li, BTN_LEFT, LIBINPUT_BUTTON_STATE_RELEASED);
}
END_TEST
START_TEST(gestures_3fg_drag_lock_resume_3fg_motion)
{
struct litest_device *dev = litest_current_device();
struct libinput *li = dev->libinput;
uint32_t finger_count;
bool tap_enabled;
bool wait_for_timeout;
litest_test_param_fetch(test_env->params,
"fingers", 'u', &finger_count,
"tap-enabled", 'b', &tap_enabled,
"wait", 'b', &wait_for_timeout);
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);
litest_checkpoint("Putting three fingers down + movement)");
double y = 30.0;
for (uint32_t i = 0; i < finger_count; i++)
litest_touch_down(dev, i, 10 + i, y);
litest_dispatch(li);
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_assert_button_event(li, BTN_LEFT, LIBINPUT_BUTTON_STATE_PRESSED);
litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_MOTION);
litest_checkpoint("Releasing all fingers");
for (uint32_t i = 0; i < finger_count; i++)
litest_touch_up(dev, i);
litest_dispatch(li);
litest_assert_empty_queue(li);
litest_checkpoint("Putting three fingers down (no movement)");
y = 30.0;
for (uint32_t i = 0; i < finger_count; i++)
litest_touch_down(dev, i, 10 + i, y);
litest_dispatch(li);
litest_assert_empty_queue(li);
litest_dispatch(li);
litest_checkpoint("Waiting past finger switch timeout");
litest_timeout_finger_switch();
litest_dispatch(li);
if (wait_for_timeout) {
litest_checkpoint("Waiting past tap/3fg drag timeout");
litest_timeout_3fg_drag();
litest_dispatch(li);
litest_assert_empty_queue(li);
}
litest_checkpoint("Moving three fingers");
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_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_MOTION);
litest_checkpoint("Releasing three fingers");
for (uint32_t i = 0; i < finger_count; i++)
litest_touch_up(dev, i);
litest_dispatch(li);
litest_assert_empty_queue(li);
litest_timeout_3fg_drag();
litest_dispatch(li);
litest_assert_button_event(li, BTN_LEFT, LIBINPUT_BUTTON_STATE_RELEASED);
}
END_TEST
START_TEST(gestures_3fg_drag_lock_resume_3fg_release_no_motion)
{
struct litest_device *dev = litest_current_device();
struct libinput *li = dev->libinput;
uint32_t finger_count;
bool tap_enabled;
bool wait_for_timeout;
litest_test_param_fetch(test_env->params,
"fingers", 'u', &finger_count,
"tap-enabled", 'b', &tap_enabled,
"wait", 'b', &wait_for_timeout);
/* tap-enabled for 4fg finger count doesn't make a difference */
bool expect_tap = finger_count <= 3 && tap_enabled && !wait_for_timeout;
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);
litest_checkpoint("Putting 3 fingers down with motion for drag");
double y = 30.0;
for (uint32_t i = 0; i < finger_count; i++)
litest_touch_down(dev, i, 10 + i, y);
litest_dispatch(li);
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_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++)
litest_touch_up(dev, i);
litest_dispatch(li);
litest_assert_empty_queue(li);
litest_checkpoint("Putting 3 fingers down again (no motion)");
y = 30.0;
for (uint32_t i = 0; i < finger_count; i++)
litest_touch_down(dev, i, 10 + i, y);
litest_dispatch(li);
litest_assert_empty_queue(li);
litest_checkpoint("Waiting past finger switch timeout");
litest_timeout_finger_switch();
litest_dispatch(li);
if (wait_for_timeout) {
litest_checkpoint("Waiting past tap/3fg drag timeout");
litest_timeout_3fg_drag();
litest_dispatch(li);
litest_assert_empty_queue(li);
}
litest_checkpoint("Releasing three fingers");
for (uint32_t i = 0; i < finger_count; i++)
litest_touch_up(dev, i);
litest_dispatch(li);
if (expect_tap) {
/* If we're not waiting and tapping is enabled, this is
* the equivalent of a 3fg tap within the drag timeout */
litest_checkpoint("Expecting 3fg drag release");
litest_assert_button_event(li, BTN_LEFT, LIBINPUT_BUTTON_STATE_RELEASED);
litest_checkpoint("Expecting 3fg tap");
litest_assert_button_event(li, BTN_MIDDLE, LIBINPUT_BUTTON_STATE_PRESSED);
litest_assert_button_event(li, BTN_MIDDLE, LIBINPUT_BUTTON_STATE_RELEASED);
}
litest_assert_empty_queue(li);
litest_timeout_3fg_drag();
litest_dispatch(li);
if (!expect_tap)
litest_assert_button_event(li, BTN_LEFT, LIBINPUT_BUTTON_STATE_RELEASED);
litest_assert_empty_queue(li);
}
END_TEST
START_TEST(gestures_3fg_drag_lock_resume_1fg_motion)
{
struct litest_device *dev = litest_current_device();
struct libinput *li = dev->libinput;
uint32_t finger_count;
bool tap_enabled;
litest_test_param_fetch(test_env->params,
"fingers", 'u', &finger_count,
"tap-enabled", 'b', &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);
litest_checkpoint("Putting 3 fingers down + motion to trigger 3fg drag");
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_assert_button_event(li, BTN_LEFT, LIBINPUT_BUTTON_STATE_PRESSED);
litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_MOTION);
litest_checkpoint("Releasing 3 fingers");
for (uint32_t i = 0; i < finger_count; i++)
litest_touch_up(dev, i);
litest_dispatch(li);
litest_assert_empty_queue(li);
litest_checkpoint("Putting 1 finger down and moving it");
/* fingers are up, now let's put one finger down and move it */
y = 30.0;
litest_touch_down(dev, 0, 10, y);
litest_dispatch(li);
litest_assert_empty_queue(li);
/* We need to wait until the gesture code accepts this is one finger only */
litest_timeout_finger_switch();
litest_dispatch(li);
while (y < 60.0) {
y += 2;
litest_touch_move(dev, 0, 10, y);
litest_dispatch(li);
}
litest_checkpoint("Expecting drag button release and motion");
litest_assert_button_event(li, BTN_LEFT, LIBINPUT_BUTTON_STATE_RELEASED);
litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_MOTION);
litest_touch_up(dev, 0);
litest_dispatch(li);
litest_assert_empty_queue(li);
litest_timeout_3fg_drag();
litest_dispatch(li);
}
END_TEST
START_TEST(gestures_3fg_drag_lock_resume_2fg_scroll)
{
struct litest_device *dev = litest_current_device();
struct libinput *li = dev->libinput;
uint32_t finger_count;
bool tap_enabled;
litest_test_param_fetch(test_env->params,
"fingers", 'u', &finger_count,
"tap-enabled", 'b', &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);
litest_checkpoint("Putting 3 fingers down + motion to trigger 3fg drag");
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_assert_button_event(li, BTN_LEFT, LIBINPUT_BUTTON_STATE_PRESSED);
litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_MOTION);
litest_checkpoint("Releasing 3 fingers");
for (uint32_t i = 0; i < finger_count; i++)
litest_touch_up(dev, i);
litest_dispatch(li);
litest_assert_empty_queue(li);
litest_checkpoint("Putting 2 fingers down and moving them");
y = 30.0;
litest_touch_down(dev, 0, 10, y);
litest_touch_down(dev, 1, 20, y);
litest_dispatch(li);
litest_assert_empty_queue(li);
litest_timeout_finger_switch();
litest_dispatch(li);
while (y < 60.0) {
y += 2;
litest_touch_move(dev, 0, 10, y);
litest_touch_move(dev, 1, 20, y);
litest_dispatch(li);
}
litest_checkpoint("Expecting drag button release and scroll");
litest_assert_button_event(li, BTN_LEFT, LIBINPUT_BUTTON_STATE_RELEASED);
litest_assert_only_axis_events(li, LIBINPUT_EVENT_POINTER_SCROLL_FINGER);
litest_touch_up(dev, 0);
litest_dispatch(li);
litest_assert_empty_queue(li);
litest_timeout_3fg_drag();
litest_dispatch(li);
}
END_TEST
START_TEST(gestures_3fg_drag_lock_resume_1fg_tap)
{
struct litest_device *dev = litest_current_device();
struct libinput *li = dev->libinput;
int finger_count = _i; /* ranged test */
if (litest_slot_count(dev) < 3)
return LITEST_NOT_APPLICABLE;
if (libinput_device_config_3fg_drag_get_finger_count(dev->libinput_device) < finger_count)
return LITEST_NOT_APPLICABLE;
litest_enable_3fg_drag(dev->libinput_device, finger_count);
litest_enable_tap(dev->libinput_device);
litest_drain_events(li);
litest_checkpoint("Putting 3 fingers down for 3fg drag");
double y = 30.0;
for (int i = 0; i < finger_count; i++)
litest_touch_down(dev, i, 10 + i, y);
litest_dispatch(li);
while (y < 60.0) {
y += 2;
for (int 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,
-1);
litest_assert_button_event(li, BTN_LEFT, LIBINPUT_BUTTON_STATE_PRESSED);
litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_MOTION);
litest_checkpoint("Releasing 3 fingers");
for (int i = 0; i < finger_count; i++)
litest_touch_up(dev, i);
litest_dispatch(li);
litest_assert_empty_queue(li);
litest_checkpoint("Tapping with 1 finger");
/* fingers are up, now let's tap with one finger */
y = 30.0;
litest_touch_down(dev, 0, 10, y);
litest_dispatch(li);
litest_assert_empty_queue(li);
litest_touch_up(dev, 0);
litest_dispatch(li);
litest_timeout_tap();
litest_dispatch(li);
litest_checkpoint("Expecting drag release followed by 1fg tap");
/* 3fg drag lock must be cancelled */
litest_assert_button_event(li, BTN_LEFT, LIBINPUT_BUTTON_STATE_RELEASED);
/* And a 1fg tap */
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);
litest_timeout_3fg_drag();
litest_dispatch(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};
struct range range_3fg_drag = { 3, 5 };
litest_add(gestures_cap, LITEST_TOUCHPAD, LITEST_SINGLE_TOUCH);
litest_add(gestures_nocap, LITEST_ANY, LITEST_TOUCHPAD);
@ -1830,6 +2310,32 @@ TEST_COLLECTION(gestures)
litest_add(gestures_hold_and_motion_before_timeout, LITEST_TOUCHPAD, LITEST_SINGLE_TOUCH);
litest_add(gestures_hold_and_motion_after_timeout, LITEST_TOUCHPAD, LITEST_SINGLE_TOUCH);
{
struct litest_parameters *params = litest_parameters_new("fingers", 'u', 2, 3, 4,
"tap-enabled", 'b');
litest_add_parametrized(gestures_3fg_drag, LITEST_TOUCHPAD, LITEST_SINGLE_TOUCH, params);
litest_parameters_unref(params);
}
{
struct litest_parameters *params = litest_parameters_new("fingers", 'u', 2, 3, 4,
"tap-enabled", 'b',
"wait", 'b');
litest_add_parametrized(gestures_3fg_drag_lock_resume_3fg_motion, LITEST_TOUCHPAD, LITEST_SINGLE_TOUCH, params);
litest_add_parametrized(gestures_3fg_drag_lock_resume_3fg_release_no_motion, LITEST_TOUCHPAD, LITEST_SINGLE_TOUCH, params);
litest_parameters_unref(params);
}
{
struct litest_parameters *params = litest_parameters_new("fingers", 'u', 2, 3, 4,
"tap-enabled", 'b');
litest_add_parametrized(gestures_3fg_drag_lock_resume_1fg_motion, LITEST_TOUCHPAD, LITEST_SINGLE_TOUCH, params);
litest_add_parametrized(gestures_3fg_drag_lock_resume_2fg_scroll, LITEST_TOUCHPAD, LITEST_SINGLE_TOUCH, params);
litest_parameters_unref(params);
}
litest_add_ranged(gestures_3fg_drag_lock_resume_1fg_tap, LITEST_TOUCHPAD, LITEST_SINGLE_TOUCH, &range_3fg_drag);
/* Timing-sensitive test, valgrind is too slow */
if (!RUNNING_ON_VALGRIND)
litest_add(gestures_swipe_3fg_unaccel, LITEST_TOUCHPAD, LITEST_SINGLE_TOUCH);

View file

@ -411,6 +411,21 @@ tools_parse_option(int option,
options->area.y2 = y2;
break;
}
case OPT_3FG_DRAG:
if (!optarg)
return 1;
if (streq(optarg, "3fg"))
options->drag_3fg = LIBINPUT_CONFIG_3FG_DRAG_ENABLED_3FG;
else if (streq(optarg, "4fg"))
options->drag_3fg = LIBINPUT_CONFIG_3FG_DRAG_ENABLED_4FG;
else if (streq(optarg, "disabled"))
options->drag_3fg = LIBINPUT_CONFIG_3FG_DRAG_DISABLED;
else {
fprintf(stderr, "Invalid --enable-3fg-drag\n"
"Valid options: 3fg|4fg|disabled\n");
return 1;
}
break;
}
return 0;
}
@ -629,6 +644,9 @@ tools_device_apply_config(struct libinput_device *device,
if (libinput_device_config_area_has_rectangle(device))
libinput_device_config_area_set_rectangle(device, &options->area);
if (libinput_device_config_3fg_drag_get_finger_count(device) >= 3)
libinput_device_config_3fg_drag_set_enabled(device, options->drag_3fg);
}
void

View file

@ -69,6 +69,7 @@ enum configuration_options {
OPT_PRESSURE_RANGE,
OPT_CALIBRATION,
OPT_AREA,
OPT_3FG_DRAG,
};
#define CONFIGURATION_OPTIONS \
@ -91,6 +92,7 @@ enum configuration_options {
{ "disable-dwtp", no_argument, 0, OPT_DWTP_DISABLE }, \
{ "enable-scroll-button-lock", no_argument, 0, OPT_SCROLL_BUTTON_LOCK_ENABLE }, \
{ "disable-scroll-button-lock",no_argument, 0, OPT_SCROLL_BUTTON_LOCK_DISABLE }, \
{ "enable-3fg-drag", required_argument, 0, OPT_3FG_DRAG }, \
{ "set-click-method", required_argument, 0, OPT_CLICK_METHOD }, \
{ "set-clickfinger-map", required_argument, 0, OPT_CLICKFINGER_MAP }, \
{ "set-scroll-method", required_argument, 0, OPT_SCROLL_METHOD }, \
@ -141,6 +143,7 @@ struct tools_options {
double pressure_range[2];
float calibration[6];
struct libinput_config_area_rectangle area;
enum libinput_config_3fg_drag_state drag_3fg;
};
void tools_init_options(struct tools_options *options);