diff --git a/doc/user/configuration.rst b/doc/user/configuration.rst index 3b00767b..83ac706a 100644 --- a/doc/user/configuration.rst +++ b/doc/user/configuration.rst @@ -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 ------------------------------------------------------------------------------ diff --git a/doc/user/drag-3fg.rst b/doc/user/drag-3fg.rst new file mode 100644 index 00000000..d9ce0c4e --- /dev/null +++ b/doc/user/drag-3fg.rst @@ -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 diff --git a/doc/user/features.rst b/doc/user/features.rst index b9e45c0b..0553a056 100644 --- a/doc/user/features.rst +++ b/doc/user/features.rst @@ -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 diff --git a/doc/user/meson.build b/doc/user/meson.build index 49ef2d50..bad2503a 100644 --- a/doc/user/meson.build +++ b/doc/user/meson.build @@ -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', diff --git a/src/evdev-mt-touchpad-gestures.c b/src/evdev-mt-touchpad-gestures.c index 6ec9f697..6b378205 100644 --- a/src/evdev-mt-touchpad-gestures.c +++ b/src/evdev-mt-touchpad-gestures.c @@ -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); } diff --git a/src/evdev-mt-touchpad.c b/src/evdev-mt-touchpad.c index d92b9eb0..909e8f0c 100644 --- a/src/evdev-mt-touchpad.c +++ b/src/evdev-mt-touchpad.c @@ -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); } diff --git a/src/evdev-mt-touchpad.h b/src/evdev-mt-touchpad.h index 48d1b113..34dd576d 100644 --- a/src/evdev-mt-touchpad.h +++ b/src/evdev-mt-touchpad.h @@ -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 diff --git a/src/libinput-private.h b/src/libinput-private.h index baca85aa..266a61e6 100644 --- a/src/libinput-private.h +++ b/src/libinput-private.h @@ -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 { diff --git a/src/libinput.c b/src/libinput.c index 1b5da805..6efa896f 100644 --- a/src/libinput.c +++ b/src/libinput.c @@ -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) { diff --git a/src/libinput.h b/src/libinput.h index 945bf6f9..9a0ad73e 100644 --- a/src/libinput.h +++ b/src/libinput.h @@ -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 * diff --git a/src/libinput.sym b/src/libinput.sym index 2f3a7eee..68c8651f 100644 --- a/src/libinput.sym +++ b/src/libinput.sym @@ -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; diff --git a/test/litest.c b/test/litest.c index a3c1868c..32e18d4f 100644 --- a/test/litest.c +++ b/test/litest.c @@ -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) { diff --git a/test/litest.h b/test/litest.h index 380c9e44..5f4a5f09 100644 --- a/test/litest.h +++ b/test/litest.h @@ -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) { diff --git a/test/test-gestures.c b/test/test-gestures.c index e50a946a..ff775366 100644 --- a/test/test-gestures.c +++ b/test/test-gestures.c @@ -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); diff --git a/tools/shared.c b/tools/shared.c index 4d7fae6e..13306f9d 100644 --- a/tools/shared.c +++ b/tools/shared.c @@ -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 diff --git a/tools/shared.h b/tools/shared.h index cadb36e3..d7090bc7 100644 --- a/tools/shared.h +++ b/tools/shared.h @@ -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);