mirror of
https://gitlab.freedesktop.org/libinput/libinput.git
synced 2026-03-21 16:50:42 +01:00
Merge branch 'scroll-button-lock-hold' into 'main'
Fold hold-to-scroll into existing scroll button lock mode Closes #1259 See merge request libinput/libinput!1435
This commit is contained in:
commit
0136c6bb94
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