mirror of
https://gitlab.freedesktop.org/libinput/libinput.git
synced 2026-03-21 13:20:42 +01:00
Merge branch 'wip/auto-drag-lock' into 'main'
touchpad: support automatic drag-lock when releasing at the edge See merge request libinput/libinput!1447
This commit is contained in:
commit
210a2be499
5 changed files with 241 additions and 16 deletions
File diff suppressed because one or more lines are too long
|
Before Width: | Height: | Size: 181 KiB After Width: | Height: | Size: 489 KiB |
|
|
@ -59,13 +59,16 @@ tap-and-drag enabled by default.
|
|||
middle-click drag, tap with three fingers followed by a
|
||||
single-finger drag.
|
||||
|
||||
Also optional is a feature called "drag lock". With drag lock disabled, lifting
|
||||
the finger will stop any drag process. When enabled, the drag
|
||||
process continues even after lifting a finger but can be ended
|
||||
with an additional tap. If timeout-based drag-locks are enabled
|
||||
the drag process will also automatically end once the finger has
|
||||
been lifted for an implementation-specific timeout. Drag lock can be
|
||||
enabled and disabled with **libinput_device_config_tap_set_drag_lock_enabled()**.
|
||||
Also optional is a feature called "drag lock". With drag lock **disabled**,
|
||||
lifting the finger will stop any drag process. When **enabled**, the drag
|
||||
process continues even after lifting a finger, allowing the user to
|
||||
reset the finger position and keep moving without releasing the drag.
|
||||
|
||||
libinput supports two variations of this drag lock: "sticky" and "timeout".
|
||||
In sticky mode, the drag lock must be ended with an explicit additional tap.
|
||||
In timeout mode, the drag lock ends automatically if no finger was put back on
|
||||
the touchpad within a timeout. Drag lock can be enabled and disabled with
|
||||
**libinput_device_config_tap_set_drag_lock_enabled()**.
|
||||
Note that drag lock only applies if tap-and-drag is enabled.
|
||||
|
||||
.. figure:: tap-n-drag.svg
|
||||
|
|
@ -81,11 +84,13 @@ position can be reset by lifting and quickly setting it down again on the
|
|||
touchpad (d). This will be interpreted as continuing move and is especially
|
||||
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. To release the button immediately,
|
||||
simply tap again (f).
|
||||
release (e) is triggered by a timeout (if in timeout mode) or by tapping again (f).
|
||||
|
||||
If drag lock is enabled in sticky mode there is no timeout after
|
||||
releasing a finger and an extra tap is required to release the button.
|
||||
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.
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -1123,7 +1123,7 @@ START_TEST(touchpad_tap_n_drag)
|
|||
break;
|
||||
}
|
||||
litest_touch_down(dev, 0, 50, 50);
|
||||
litest_touch_move_to(dev, 0, 50, 50, 80, 80, 20);
|
||||
litest_touch_move_to(dev, 0, 50, 50, 70, 70, 20);
|
||||
|
||||
litest_dispatch(li);
|
||||
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue