diff --git a/doc/touchpad-tap-state-machine.svg b/doc/touchpad-tap-state-machine.svg index f3086d28..3a29658f 100644 --- a/doc/touchpad-tap-state-machine.svg +++ b/doc/touchpad-tap-state-machine.svg @@ -1,3 +1,4 @@ + -
(this section exists for [n] = 1, [n] = 2, and [n] = 3)
(this section exists for [n] = 1, [n] = 2, and [n] = 3)
IDLETOUCHfirstfinger downfinger upbutton 1presstimeoutmove > thresholdsecondfinger downTOUCH_2secondfinger upbutton 2pressmove > thresholdtimeoutbutton 1releasebutton 2release[n]FGTAP_TAPPEDtimeoutfirstfinger down[n]FGTAP_DRAGGINGfirstfinger upbutton [n]releaseIDLEthirdfinger downTOUCH_3button 3pressbutton 3releasemove > thresholdIDLEtimeoutfirstfinger upIDLEfourthfinger down[n]FGTAP_DRAGGING_OR_DOUBLETAPtimeoutfirstfinger upbutton [n]releasesecondfinger downmove > thresholdHOLDfirstfinger upsecondfinger downTOUCH_2_HOLDsecondfinger upthirdfinger downTOUCH_3_HOLDfourthfinger downDEADany finger upfourthfinger upany finger upany finger upIDLEif fingercount == 0[n]FGTAP_DRAGGING_2secondfinger downthirdfinger downphysbuttonpressphysbuttonpressbutton [n]release[n]FGTAP_DRAGGING_WAITtimeoutfirstfinger downTOUCH_TOUCHTOUCH_IDLETOUCH_DEADTOUCH_DEADTOUCH_IDLETOUCH_TOUCHTOUCH_IDLETOUCH_IDLETOUCH_TOUCHthat fingerTOUCH_IDLETOUCH_DEADthat fingerTOUCH_IDLETOUCH_TOUCHTOUCH_IDLETOUCH_TOUCHTOUCH_DEADTOUCH_IDLETOUCH_TOUCHTOUCH_TOUCHTOUCH_IDLETOUCH_TOUCHthat fingerTOUCH_IDLETOUCH_DEADTOUCH_DEADTOUCH_DEADTOUCH_DEADTOUCH_DEADTOUCH_DEADTOUCH_DEADTOUCH_DEADTOUCH_DEAD[n]FGTAP_DRAGGING_OR_TAPfirstfinger uptimeoutmove > thresholdTOUCH_IDLEdrag lockenabled?
no
no
yes
yes
thumbTOUCH_DEADTOUCH_2_RELEASEsecondfinger uptimeoutmove > thresholdfirstfinger downTOUCH_IDLEfirstfinger upsecondfinger downTOUCH_DEADTOUCH_DEADdragdisabled?
yes
yes
palmeither fingerpalmremaining fingerpalmany fingerpalmthat fingerTOUCH_DEADthat fingerTOUCH_DEADpalmany fingerpalmthat fingerTOUCH_DEADTOUCH_DEADpalmTOUCH_DEADany fingerpalmthat fingerTOUCH_DEADeither fingerpalmthat fingerTOUCH_DEADpalmTOUCH_DEADany fingerpalmthat fingerTOUCH_DEADTOUCH_DEADmove > thresholdmove > thresholdbutton 1press
[n] = 1
[n] = 1
[n] = 1
[n] = 1
no
no
button [n]releaseIDLEdragdisabled?
[n] = 2
[n] = 2
no
no
yes
yes
dragdisabled?
yes
yes
no
no
[n] = 3
[n] = 3
TOUCH_3_RELEASEtimeoutTOUCH_DEADTOUCH_DEADeither fingerpalmthat fingerTOUCH_DEADmove > thresholdthirdfinger downTOUCH_TOUCHTOUCH_3_RELEASE_2remainingfinger upthat fingerTOUCH_IDLEeither finger upthat fingerTOUCH_IDLEsecondfinger downremaining fingerpalmTOUCH_DEADtimeoutfirstfinger upbutton 3pressbutton 3releasebutton 3pressbutton 3releaseeither finger upthat fingerTOUCH_IDLEsecondfinger downbutton [n]releasebutton 3pressbutton 3releaseTOUCH_DEADbutton 3pressbutton 3releasebutton 3pressbutton 3release
Viewer does not support full SVG 1.1
+
(this section exists for [n] = 1, [n] = 2, and [n] = 3)
(this section exists for [n] = 1, [n] = 2, and [n] = 3)
IDLETOUCHfirstfinger downfinger upbutton 1presstimeoutmove > thresholdsecondfinger downTOUCH_2secondfinger upbutton 2pressmove > thresholdtimeoutbutton 1releasebutton 2release[n]FGTAP_TAPPEDtimeoutfirstfinger down[n]FGTAP_DRAGGINGfirstfinger upbutton [n]releaseIDLEthirdfinger downTOUCH_3button 3pressbutton 3releasemove > thresholdIDLEtimeoutfirstfinger upIDLEfourthfinger down[n]FGTAP_DRAGGING_OR_DOUBLETAPtimeoutfirstfinger upbutton [n]releasesecondfinger downmove > thresholdHOLDfirstfinger upsecondfinger downTOUCH_2_HOLDsecondfinger upthirdfinger downTOUCH_3_HOLDfourthfinger downDEADany finger upfourthfinger upany finger upany finger upIDLEif fingercount == 0[n]FGTAP_DRAGGING_2secondfinger downthirdfinger downphysbuttonpressphysbuttonpressbutton [n]release[n]FGTAP_DRAGGING_WAITtimeoutfirstfinger downTOUCH_TOUCHTOUCH_IDLETOUCH_DEADTOUCH_DEADTOUCH_IDLETOUCH_TOUCHTOUCH_IDLETOUCH_IDLETOUCH_TOUCHthat fingerTOUCH_IDLETOUCH_DEADthat fingerTOUCH_IDLETOUCH_TOUCHTOUCH_IDLETOUCH_TOUCHTOUCH_DEADTOUCH_IDLETOUCH_TOUCHTOUCH_TOUCHTOUCH_IDLETOUCH_TOUCHthat fingerTOUCH_IDLETOUCH_DEADTOUCH_DEADTOUCH_DEADTOUCH_DEADTOUCH_DEADTOUCH_DEADTOUCH_DEADTOUCH_DEADTOUCH_DEAD[n]FGTAP_DRAGGING_OR_TAPfirstfinger uptimeoutmove > thresholdTOUCH_IDLEdrag lockenabled?
no
no
yes
yes
thumbTOUCH_DEADTOUCH_2_RELEASEsecondfinger uptimeoutmove > thresholdfirstfinger downTOUCH_IDLEfirstfinger upsecondfinger downTOUCH_DEADTOUCH_DEADdragdisabled?
yes
yes
palmeither fingerpalmremaining fingerpalmany fingerpalmthat fingerTOUCH_DEADthat fingerTOUCH_DEADpalmany fingerpalmthat fingerTOUCH_DEADTOUCH_DEADpalmTOUCH_DEADany fingerpalmthat fingerTOUCH_DEADeither fingerpalmthat fingerTOUCH_DEADpalmTOUCH_DEADany fingerpalmthat fingerTOUCH_DEADTOUCH_DEADmove > thresholdmove > thresholdbutton 1press
[n] = 1
[n] = 1
[n] = 1
[n] = 1
no
no
button [n]releaseIDLEdragdisabled?
[n] = 2
[n] = 2
no
no
yes
yes
dragdisabled?
yes
yes
no
no
[n] = 3
[n] = 3
TOUCH_3_RELEASEtimeoutTOUCH_DEADTOUCH_DEADeither fingerpalmthat fingerTOUCH_DEADmove > thresholdthirdfinger downTOUCH_TOUCHTOUCH_3_RELEASE_2remainingfinger upthat fingerTOUCH_IDLEeither finger upthat fingerTOUCH_IDLEsecondfinger downremaining fingerpalmTOUCH_DEADtimeoutfirstfinger upbutton 3pressbutton 3releasebutton 3pressbutton 3releaseeither finger upthat fingerTOUCH_IDLEsecondfinger downbutton [n]releasebutton 3pressbutton 3releaseTOUCH_DEADbutton 3pressbutton 3releasebutton 3pressbutton 3release
no
no
yes
yes
fingerat edge?
Text is not SVG - cannot display
diff --git a/doc/user/tapping.rst b/doc/user/tapping.rst index 0cdcd3a3..7f7fb0d5 100644 --- a/doc/user/tapping.rst +++ b/doc/user/tapping.rst @@ -86,6 +86,12 @@ useful on small touchpads or with slow pointer acceleration. If drag lock is enabled, the release of the mouse buttons after the finger release (e) is triggered by a timeout (if in timeout mode) or by tapping again (f). +libinput also supports an "auto drag-lock" feature: if drag lock is **disabled** +but the dragging finger is released at the very edge of the touchpad, +a drag lock automatically activates for a short timeout. This allows a user to +quickly reset the finger to elsewhere on the touchpad and continue the dragging +motion. If the finger is released elsewhere, no drag lock activates. + If two fingers are supported by the hardware, a second finger can be used to drag while the first is held in-place. diff --git a/src/evdev-mt-touchpad-tap.c b/src/evdev-mt-touchpad-tap.c index 596ba944..565f2914 100644 --- a/src/evdev-mt-touchpad-tap.c +++ b/src/evdev-mt-touchpad-tap.c @@ -178,6 +178,13 @@ tp_tap_clear_timer(struct tp_dispatch *tp) libinput_timer_cancel(&tp->tap.timer); } +static bool +tp_touch_near_any_edge(struct tp_dispatch *tp, struct tp_touch *t) +{ + return (t->point.x < tp->tap.edges.left || t->point.x > tp->tap.edges.right || + t->point.y < tp->tap.edges.top || t->point.y > tp->tap.edges.bottom); +} + static void tp_tap_move_to_dead(struct tp_dispatch *tp, struct tp_touch *t) { @@ -812,6 +819,7 @@ tp_tap_dragging_handle_event(struct tp_dispatch *tp, usec_t time, int nfingers_tapped) { + bool at_edge = false; switch (event) { case TAP_EVENT_TOUCH: { @@ -825,7 +833,8 @@ tp_tap_dragging_handle_event(struct tp_dispatch *tp, break; } case TAP_EVENT_RELEASE: - if (tp->tap.drag_lock != LIBINPUT_CONFIG_DRAG_LOCK_DISABLED) { + if (tp->tap.drag_lock != LIBINPUT_CONFIG_DRAG_LOCK_DISABLED || + (at_edge = tp_touch_near_any_edge(tp, t))) { enum tp_tap_state dest[3] = { TAP_STATE_1FGTAP_DRAGGING_WAIT, TAP_STATE_2FGTAP_DRAGGING_WAIT, @@ -833,8 +842,9 @@ tp_tap_dragging_handle_event(struct tp_dispatch *tp, }; assert(nfingers_tapped >= 1 && nfingers_tapped <= 3); tp->tap.state = dest[nfingers_tapped - 1]; - if (tp->tap.drag_lock == - LIBINPUT_CONFIG_DRAG_LOCK_ENABLED_TIMEOUT) + if (at_edge || + tp->tap.drag_lock == + LIBINPUT_CONFIG_DRAG_LOCK_ENABLED_TIMEOUT) tp_tap_set_draglock_timer(tp, time); } else { tp_tap_notify(tp, @@ -1579,6 +1589,20 @@ tp_init_tap(struct tp_dispatch *tp) tp->tap.drag_enabled = tp_drag_default(tp->device); tp->tap.drag_lock = tp_drag_lock_default(tp->device); + struct evdev_device *device = tp->device; + + const struct input_absinfo *absx = device->abs.absinfo_x; + const struct input_absinfo *absy = device->abs.absinfo_y; + assert(absx && absy); + + struct phys_coords mm = { 5.0, 5.0 }; + struct device_coords edge_margin = evdev_device_mm_to_units(device, &mm); + + tp->tap.edges.left = edge_margin.x; + tp->tap.edges.right = (absx->maximum - edge_margin.x + absx->minimum); + tp->tap.edges.top = edge_margin.y; + tp->tap.edges.bottom = (absy->maximum - edge_margin.y + absy->minimum); + snprintf(timer_name, sizeof(timer_name), "%s tap", diff --git a/src/evdev-mt-touchpad.h b/src/evdev-mt-touchpad.h index 7e85baf7..544cf467 100644 --- a/src/evdev-mt-touchpad.h +++ b/src/evdev-mt-touchpad.h @@ -450,6 +450,11 @@ struct tp_dispatch { unsigned int nfingers_down; /* number of fingers down for tapping (excl. thumb/palm) */ + + /* Edges for auto drag-lock, in device coordinates */ + struct { + int left, right, top, bottom; + } edges; } tap; struct { diff --git a/test/test-touchpad-tap.c b/test/test-touchpad-tap.c index b96a80c3..7d444120 100644 --- a/test/test-touchpad-tap.c +++ b/test/test-touchpad-tap.c @@ -1549,6 +1549,182 @@ START_TEST(touchpad_tap_n_drag_draglock_sticky) } END_TEST +enum edge { TOP, BOTTOM, LEFT, RIGHT }; + +START_TEST(touchpad_tap_n_drag_auto_draglock_edge) +{ + /* Test: tap-and-drag with drag-lock disabled. When a finger is + * released at a touchpad edge, auto drag-lock activates. + */ + struct litest_device *dev = litest_current_device(); + struct libinput *li = dev->libinput; + enum edge edge = litest_test_param_get_i32(test_env->params, "edge"); + int nfingers = litest_test_param_get_i32(test_env->params, "fingers"); + + if (nfingers > litest_slot_count(dev)) + return LITEST_NOT_APPLICABLE; + + litest_enable_tap(dev->libinput_device); + litest_disable_drag_lock(dev->libinput_device); + litest_disable_hold_gestures(dev->libinput_device); + litest_drain_events(li); + + unsigned int button = 0; + switch (nfingers) { + case 1: + button = BTN_LEFT; + break; + case 2: + button = BTN_RIGHT; + break; + case 3: + button = BTN_MIDDLE; + break; + } + + /* tap and drag */ + switch (nfingers) { + case 3: + litest_touch_down(dev, 2, 60, 30); + _fallthrough_; + case 2: + litest_touch_down(dev, 1, 50, 30); + _fallthrough_; + case 1: + litest_touch_down(dev, 0, 40, 30); + break; + } + switch (nfingers) { + case 3: + litest_touch_up(dev, 2); + _fallthrough_; + case 2: + litest_touch_up(dev, 1); + _fallthrough_; + case 1: + litest_touch_up(dev, 0); + break; + } + + /* 1fg back down starts the drag */ + litest_touch_down(dev, 0, 50, 50); + litest_timeout_tap(li); + litest_assert_button_event(li, button, LIBINPUT_BUTTON_STATE_PRESSED); + litest_assert_empty_queue(li); + + int x = 50, y = 50; + switch (edge) { + case TOP: + y = 0; + break; + case BOTTOM: + y = 99; + break; + case LEFT: + x = 0; + break; + case RIGHT: + x = 99; + break; + } + litest_touch_move_to(dev, 0, 50, 50, x, y, 20); + litest_drain_events(li); + litest_touch_up(dev, 0); + + /* auto drag-lock should be active - button still pressed */ + litest_assert_empty_queue(li); + + /* put finger down again within timeout */ + litest_touch_down(dev, 0, 50, 50); + litest_touch_move_to(dev, 0, 50, 50, 60, 60, 10); + litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_MOTION); + litest_drain_events(li); + + /* release normally (not at edge) */ + litest_touch_up(dev, 0); + litest_timeout_tapndrag(li); + litest_assert_button_event(li, button, LIBINPUT_BUTTON_STATE_RELEASED); + + litest_assert_empty_queue(li); +} +END_TEST + +START_TEST(touchpad_tap_n_drag_auto_draglock_timeout) +{ + /* Test: auto drag-lock times out if finger not replaced */ + struct litest_device *dev = litest_current_device(); + struct libinput *li = dev->libinput; + + litest_enable_tap(dev->libinput_device); + litest_disable_drag_lock(dev->libinput_device); + litest_disable_hold_gestures(dev->libinput_device); + litest_drain_events(li); + + /* Tap */ + litest_touch_down(dev, 0, 50, 50); + litest_touch_up(dev, 0); + + /* Start drag */ + litest_touch_down(dev, 0, 50, 50); + litest_timeout_tap(li); + litest_assert_button_event(li, BTN_LEFT, LIBINPUT_BUTTON_STATE_PRESSED); + litest_assert_empty_queue(li); + + /* Move to left edge and release */ + litest_touch_move_to(dev, 0, 50, 50, 1, 50, 20); + litest_drain_events(li); + litest_touch_up(dev, 0); + + /* Auto drag-lock active - button still pressed */ + litest_assert_empty_queue(li); + + litest_timeout_tapndrag(li); + libinput_dispatch(li); + litest_assert_button_event(li, BTN_LEFT, LIBINPUT_BUTTON_STATE_RELEASED); + + litest_assert_empty_queue(li); +} +END_TEST + +START_TEST(touchpad_tap_n_drag_auto_draglock_disabled_when_draglock_enabled_via_config) +{ + struct litest_device *dev = litest_current_device(); + struct libinput *li = dev->libinput; + + litest_enable_tap(dev->libinput_device); + /* sticky draglock because it's not timing sensitive */ + litest_enable_drag_lock_sticky(dev->libinput_device); + litest_disable_hold_gestures(dev->libinput_device); + litest_drain_events(li); + + /* tap and drag */ + litest_touch_down(dev, 0, 50, 50); + litest_touch_up(dev, 0); + + litest_touch_down(dev, 0, 50, 50); + litest_timeout_tap(li); + litest_assert_button_event(li, BTN_LEFT, LIBINPUT_BUTTON_STATE_PRESSED); + litest_assert_empty_queue(li); + + /* release at edge */ + litest_touch_move_to(dev, 0, 50, 50, 99, 50, 20); + litest_drain_events(li); + litest_touch_up(dev, 0); + + litest_assert_empty_queue(li); + + /* no auto release after timeout */ + litest_timeout_tapndrag(li); + litest_assert_empty_queue(li); + + /* tap to end */ + litest_touch_down(dev, 0, 50, 50); + litest_touch_up(dev, 0); + litest_assert_button_event(li, BTN_LEFT, LIBINPUT_BUTTON_STATE_RELEASED); + litest_assert_empty_queue(li); +} +END_TEST + START_TEST(touchpad_tap_n_drag_2fg) { /* Test: tap with 1-3 fingers (multiple times), then a 1fg move @@ -5502,7 +5678,21 @@ TEST_COLLECTION(touchpad_tap_drag) litest_add_parametrized(touchpad_tap_n_drag_draglock, LITEST_TOUCHPAD, LITEST_ANY, params); litest_add_parametrized(touchpad_tap_n_drag_draglock_timeout, LITEST_TOUCHPAD, LITEST_ANY, params); litest_add_parametrized(touchpad_tap_n_drag_draglock_sticky, LITEST_TOUCHPAD, LITEST_ANY, params); + } + litest_with_parameters(params, + "fingers", 'i', 3, 1, 2, 3, + "edge", 'I', 4, + litest_named_i32(LEFT), + litest_named_i32(RIGHT), + litest_named_i32(TOP), + litest_named_i32(BOTTOM)) { + litest_add_parametrized(touchpad_tap_n_drag_auto_draglock_edge, LITEST_TOUCHPAD, LITEST_ANY, params); + } + litest_add(touchpad_tap_n_drag_auto_draglock_timeout, LITEST_TOUCHPAD, LITEST_ANY); + litest_add(touchpad_tap_n_drag_auto_draglock_disabled_when_draglock_enabled_via_config, LITEST_TOUCHPAD, LITEST_ANY); + + litest_with_parameters(params, "fingers", 'i', 3, 1, 2, 3) { litest_add_parametrized(touchpad_drag_disabled, LITEST_TOUCHPAD, LITEST_ANY, params); litest_add_parametrized(touchpad_drag_disabled_immediate, LITEST_TOUCHPAD, LITEST_ANY, params); }