mirror of
https://gitlab.freedesktop.org/libinput/libinput.git
synced 2026-03-21 07:30:36 +01:00
Fold hold-to-scroll into existing scroll button lock mode
Instead of adding a new ENABLED_HOLD enum value, modify the existing ENABLED lock mode so that hold+scroll+release doesn't engage the lock. Add a 500ms grace period: if the button was held and used to scroll for longer than 500ms, releasing the button does not engage the lock (temporary scroll). If released within 500ms (e.g. shaky hands triggering accidental motion), the lock still engages as before. This fixes the unintuitive behavior where the lock engages even after actively scrolling, without requiring new API surface. Closes: https://gitlab.freedesktop.org/libinput/libinput/-/issues/1259 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> Part-of: <https://gitlab.freedesktop.org/libinput/libinput/-/merge_requests/1435>
This commit is contained in:
parent
43547b461b
commit
8dd25ece10
5 changed files with 164 additions and 1 deletions
|
|
@ -124,6 +124,12 @@ button lock, the button is now considered logically held down. Pressing and
|
|||
releasing the button a second time logically releases the button. While the
|
||||
button is logically held down, motion events are converted to scroll events.
|
||||
|
||||
If the button is held and used to scroll for longer than a short grace
|
||||
period, releasing the button does not engage the lock. This allows
|
||||
hold-to-scroll for short, precise adjustments without accidentally toggling
|
||||
the lock. A quick click or a brief scroll within the grace period still
|
||||
engages the lock as normal.
|
||||
|
||||
.. _scroll_sources:
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
|
|
|
|||
10
src/evdev.c
10
src/evdev.c
|
|
@ -54,6 +54,7 @@
|
|||
|
||||
#define DEFAULT_WHEEL_CLICK_ANGLE 15
|
||||
#define DEFAULT_BUTTON_SCROLL_TIMEOUT usec_from_millis(200)
|
||||
#define SCROLL_BUTTON_LOCK_GRACE_TIMEOUT usec_from_millis(500)
|
||||
|
||||
enum evdev_device_udev_tags {
|
||||
EVDEV_UDEV_TAG_NONE = 0,
|
||||
|
|
@ -228,6 +229,15 @@ evdev_button_scroll_button(struct evdev_device *device, usec_t time, int is_pres
|
|||
break; /* handle event */
|
||||
case BUTTONSCROLL_LOCK_FIRSTDOWN:
|
||||
assert(!is_press);
|
||||
if (device->scroll.button_scroll_state == BUTTONSCROLL_SCROLLING &&
|
||||
usec_cmp(usec_delta(time, device->scroll.button_down_time),
|
||||
SCROLL_BUTTON_LOCK_GRACE_TIMEOUT) >= 0) {
|
||||
/* held + scrolled past grace period: temporary scroll,
|
||||
* no lock engaged */
|
||||
device->scroll.lock_state = BUTTONSCROLL_LOCK_IDLE;
|
||||
evdev_log_debug(device, "scroll lock: temp scroll done\n");
|
||||
break; /* pass release through */
|
||||
}
|
||||
device->scroll.lock_state = BUTTONSCROLL_LOCK_FIRSTUP;
|
||||
evdev_log_debug(device, "scroll lock: first up\n");
|
||||
return; /* filter release event */
|
||||
|
|
|
|||
|
|
@ -6624,7 +6624,9 @@ enum libinput_config_scroll_button_lock_state {
|
|||
* If the state is
|
||||
* @ref LIBINPUT_CONFIG_SCROLL_BUTTON_LOCK_ENABLED, the button is considered
|
||||
* logically down after the first press and release sequence, and logically
|
||||
* up after the second press and release sequence.
|
||||
* up after the second press and release sequence. If the button is held
|
||||
* and used to scroll for longer than a short grace period, releasing the
|
||||
* button does not engage the lock.
|
||||
*
|
||||
* @param device The device to configure
|
||||
* @param state The state to set the scroll button lock to
|
||||
|
|
|
|||
|
|
@ -1359,6 +1359,7 @@ _litest_timeout(struct libinput *li, const char *func, int lineno, int millis);
|
|||
#define litest_timeout_debounce(li_) litest_timeout(li_, 30)
|
||||
#define litest_timeout_softbuttons(li_) litest_timeout(li_, 300)
|
||||
#define litest_timeout_buttonscroll(li_) litest_timeout(li_, 300)
|
||||
#define litest_timeout_scroll_button_lock_grace(li_) litest_timeout(li_, 600)
|
||||
#define litest_timeout_wheel_scroll(li_) litest_timeout(li_, 600)
|
||||
#define litest_timeout_edgescroll(li_) litest_timeout(li_, 300)
|
||||
#define litest_timeout_finger_switch(li_) litest_timeout(li_, 140)
|
||||
|
|
|
|||
|
|
@ -2238,6 +2238,146 @@ START_TEST(pointer_scroll_button_lock_doubleclick_nomove)
|
|||
}
|
||||
END_TEST
|
||||
|
||||
START_TEST(pointer_scroll_button_lock_scroll_releases)
|
||||
{
|
||||
struct litest_device *dev = litest_current_device();
|
||||
struct libinput *li = dev->libinput;
|
||||
|
||||
litest_enable_scroll_button_lock(dev, BTN_LEFT);
|
||||
litest_disable_middleemu(dev);
|
||||
litest_drain_events(li);
|
||||
|
||||
/* Press, wait for buttonscroll timeout, scroll, wait past grace
|
||||
period, release. The lock should NOT engage. */
|
||||
litest_button_click_debounced(dev, li, BTN_LEFT, true);
|
||||
litest_timeout_buttonscroll(li);
|
||||
litest_dispatch(li);
|
||||
|
||||
for (int i = 0; i < 10; i++) {
|
||||
litest_event(dev, EV_REL, REL_X, 1);
|
||||
litest_event(dev, EV_REL, REL_Y, 6);
|
||||
litest_event(dev, EV_SYN, SYN_REPORT, 0);
|
||||
}
|
||||
litest_dispatch(li);
|
||||
litest_assert_only_axis_events(li, LIBINPUT_EVENT_POINTER_SCROLL_CONTINUOUS);
|
||||
|
||||
litest_timeout_scroll_button_lock_grace(li);
|
||||
|
||||
litest_button_click_debounced(dev, li, BTN_LEFT, false);
|
||||
litest_dispatch(li);
|
||||
|
||||
/* Expect scroll stop, no lock */
|
||||
litest_assert_scroll(li, LIBINPUT_EVENT_POINTER_SCROLL_CONTINUOUS, 0, 0);
|
||||
|
||||
/* Subsequent motion should be pointer motion, not scroll */
|
||||
for (int i = 0; i < 10; i++) {
|
||||
litest_event(dev, EV_REL, REL_X, 1);
|
||||
litest_event(dev, EV_REL, REL_Y, 6);
|
||||
litest_event(dev, EV_SYN, SYN_REPORT, 0);
|
||||
}
|
||||
litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_MOTION);
|
||||
}
|
||||
END_TEST
|
||||
|
||||
START_TEST(pointer_scroll_button_lock_scroll_within_grace)
|
||||
{
|
||||
struct litest_device *dev = litest_current_device();
|
||||
struct libinput *li = dev->libinput;
|
||||
|
||||
litest_enable_scroll_button_lock(dev, BTN_LEFT);
|
||||
litest_disable_middleemu(dev);
|
||||
litest_drain_events(li);
|
||||
|
||||
/* Press, wait for buttonscroll timeout, scroll briefly, release
|
||||
immediately (total < 500ms from press). The lock SHOULD engage
|
||||
since we're within the grace period. */
|
||||
litest_button_click_debounced(dev, li, BTN_LEFT, true);
|
||||
litest_timeout_buttonscroll(li);
|
||||
litest_dispatch(li);
|
||||
|
||||
for (int i = 0; i < 3; i++) {
|
||||
litest_event(dev, EV_REL, REL_X, 1);
|
||||
litest_event(dev, EV_REL, REL_Y, 6);
|
||||
litest_event(dev, EV_SYN, SYN_REPORT, 0);
|
||||
}
|
||||
litest_dispatch(li);
|
||||
|
||||
/* Release immediately (still within grace period) */
|
||||
litest_button_click_debounced(dev, li, BTN_LEFT, false);
|
||||
litest_dispatch(li);
|
||||
|
||||
/* Lock should be engaged: drain scroll events from the hold */
|
||||
litest_assert_only_axis_events(li, LIBINPUT_EVENT_POINTER_SCROLL_CONTINUOUS);
|
||||
|
||||
/* Motion while locked should produce scroll events */
|
||||
for (int i = 0; i < 10; i++) {
|
||||
litest_event(dev, EV_REL, REL_X, 1);
|
||||
litest_event(dev, EV_REL, REL_Y, 6);
|
||||
litest_event(dev, EV_SYN, SYN_REPORT, 0);
|
||||
}
|
||||
litest_dispatch(li);
|
||||
litest_assert_only_axis_events(li, LIBINPUT_EVENT_POINTER_SCROLL_CONTINUOUS);
|
||||
|
||||
/* Click again to unlock */
|
||||
litest_button_click_debounced(dev, li, BTN_LEFT, true);
|
||||
litest_button_click_debounced(dev, li, BTN_LEFT, false);
|
||||
litest_dispatch(li);
|
||||
|
||||
litest_assert_scroll(li, LIBINPUT_EVENT_POINTER_SCROLL_CONTINUOUS, 0, 0);
|
||||
|
||||
/* Back to normal motion */
|
||||
for (int i = 0; i < 10; i++) {
|
||||
litest_event(dev, EV_REL, REL_X, 1);
|
||||
litest_event(dev, EV_REL, REL_Y, 6);
|
||||
litest_event(dev, EV_SYN, SYN_REPORT, 0);
|
||||
}
|
||||
litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_MOTION);
|
||||
}
|
||||
END_TEST
|
||||
|
||||
START_TEST(pointer_scroll_button_lock_hold_no_move_still_locks)
|
||||
{
|
||||
struct litest_device *dev = litest_current_device();
|
||||
struct libinput *li = dev->libinput;
|
||||
|
||||
litest_enable_scroll_button_lock(dev, BTN_LEFT);
|
||||
litest_disable_middleemu(dev);
|
||||
litest_drain_events(li);
|
||||
|
||||
/* Press, wait past 500ms, release without moving.
|
||||
button_scroll_state == READY (not SCROLLING), so lock
|
||||
engages as normal click. */
|
||||
litest_button_click_debounced(dev, li, BTN_LEFT, true);
|
||||
litest_timeout_buttonscroll(li);
|
||||
litest_timeout_scroll_button_lock_grace(li);
|
||||
litest_dispatch(li);
|
||||
|
||||
litest_button_click_debounced(dev, li, BTN_LEFT, false);
|
||||
litest_dispatch(li);
|
||||
|
||||
/* Lock should be engaged (release was filtered).
|
||||
The scroll SM was in READY state, so it emits button press/release
|
||||
(since no scrolling occurred), but the lock SM filtered the release.
|
||||
Subsequent motion should produce scroll events. */
|
||||
litest_assert_empty_queue(li);
|
||||
|
||||
for (int i = 0; i < 10; i++) {
|
||||
litest_event(dev, EV_REL, REL_X, 1);
|
||||
litest_event(dev, EV_REL, REL_Y, 6);
|
||||
litest_event(dev, EV_SYN, SYN_REPORT, 0);
|
||||
}
|
||||
litest_dispatch(li);
|
||||
litest_assert_only_axis_events(li, LIBINPUT_EVENT_POINTER_SCROLL_CONTINUOUS);
|
||||
|
||||
/* Click to unlock */
|
||||
litest_button_click_debounced(dev, li, BTN_LEFT, true);
|
||||
litest_button_click_debounced(dev, li, BTN_LEFT, false);
|
||||
litest_dispatch(li);
|
||||
|
||||
litest_assert_scroll(li, LIBINPUT_EVENT_POINTER_SCROLL_CONTINUOUS, 0, 0);
|
||||
}
|
||||
END_TEST
|
||||
|
||||
START_TEST(pointer_scroll_nowheel_defaults)
|
||||
{
|
||||
struct litest_device *dev = litest_current_device();
|
||||
|
|
@ -3816,6 +3956,10 @@ TEST_COLLECTION(pointer)
|
|||
}
|
||||
litest_add(pointer_scroll_button_lock_doubleclick_nomove, LITEST_RELATIVE|LITEST_BUTTON, LITEST_ANY);
|
||||
|
||||
litest_add(pointer_scroll_button_lock_scroll_releases, LITEST_RELATIVE|LITEST_BUTTON, LITEST_ANY);
|
||||
litest_add(pointer_scroll_button_lock_scroll_within_grace, LITEST_RELATIVE|LITEST_BUTTON, LITEST_ANY);
|
||||
litest_add(pointer_scroll_button_lock_hold_no_move_still_locks, LITEST_RELATIVE|LITEST_BUTTON, LITEST_ANY);
|
||||
|
||||
litest_add(pointer_scroll_nowheel_defaults, LITEST_RELATIVE|LITEST_BUTTON, LITEST_WHEEL);
|
||||
litest_add_for_device(pointer_scroll_defaults_logitech_marble , LITEST_LOGITECH_TRACKBALL);
|
||||
litest_add(pointer_scroll_natural_defaults, LITEST_WHEEL, LITEST_TABLET);
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue