mirror of
https://gitlab.freedesktop.org/libinput/libinput.git
synced 2026-03-23 15:30:36 +01:00
touchpad: support automatic drag-lock when releasing at the edge
If drag-lock is disabled but we're in a tap-and-drag state and the finger is released near the edge (within 5mm), enable automatic drag lock for 400ms. This allows a user to quickly reset the finger and continue with the drag. The 400ms is a randomly guessed timeout - if you're using tap-and-drag without draglock, finger dexterity should be high enough that resetting the single finger can be done quickly but it's also short enough to not make the occasional delayed button be painful in day-to-day use.
This commit is contained in:
parent
ea3c247e66
commit
2248c3eab6
5 changed files with 230 additions and 4 deletions
File diff suppressed because one or more lines are too long
|
Before Width: | Height: | Size: 181 KiB After Width: | Height: | Size: 489 KiB |
|
|
@ -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.
|
||||
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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