diff options
author | Peter Hutterer <peter.hutterer@who-t.net> | 2019-03-20 10:56:51 +1000 |
---|---|---|
committer | Peter Hutterer <peter.hutterer@who-t.net> | 2019-10-17 12:21:41 +1000 |
commit | 63f99230130ae012a71cec90ee0abccb1b43abd3 (patch) | |
tree | d07692d299c13e2bab75d05383c8bfa91524f6f2 | |
parent | 12eb14846a139ae8d4915c338302095a53c69e91 (diff) |
Add a scroll button lock feature
Scroll button locking is an accessibility feature. When enabled, the scroll
button does not need to be held down, the first click holds it logically down,
to be released on the second click of that same button.
This is implemented as simple event filter, so we still get the same behavior
from the emulated logical button, i.e. a physical double click results in a
single logical click of that button provided no scrolling was triggered.
Signed-off-by: Peter Hutterer <peter.hutterer@who-t.net>
-rw-r--r-- | doc/user/scrolling.rst | 7 | ||||
-rw-r--r-- | src/evdev-fallback.c | 5 | ||||
-rw-r--r-- | src/evdev.c | 81 | ||||
-rw-r--r-- | src/evdev.h | 16 | ||||
-rw-r--r-- | src/libinput-private.h | 4 | ||||
-rw-r--r-- | src/libinput.c | 39 | ||||
-rw-r--r-- | src/libinput.h | 75 | ||||
-rw-r--r-- | src/libinput.sym | 6 | ||||
-rw-r--r-- | test/litest.c | 21 | ||||
-rw-r--r-- | test/litest.h | 4 | ||||
-rw-r--r-- | test/test-pointer.c | 509 | ||||
-rw-r--r-- | tools/libinput-debug-events.man | 3 | ||||
-rw-r--r-- | tools/shared.c | 11 | ||||
-rw-r--r-- | tools/shared.h | 5 |
14 files changed, 785 insertions, 1 deletions
diff --git a/doc/user/scrolling.rst b/doc/user/scrolling.rst index c828a3d6..f5cd454c 100644 --- a/doc/user/scrolling.rst +++ b/doc/user/scrolling.rst @@ -117,6 +117,13 @@ the motion events. Cross-device scrolling is not supported but for one exception: libinput's :ref:`t440_support` enables the use of the middle button for button scrolling (even when the touchpad is disabled). +If the scroll button lock is enabled (see +**libinput_device_config_scroll_set_button_lock()**), the button does not +need to be held down. Pressing and releasing the button once enables the +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. + .. _scroll_sources: ------------------------------------------------------------------------------ diff --git a/src/evdev-fallback.c b/src/evdev-fallback.c index 1a9113bc..a40421f0 100644 --- a/src/evdev-fallback.c +++ b/src/evdev-fallback.c @@ -1499,7 +1499,8 @@ fallback_change_scroll_method(struct evdev_device *device) struct fallback_dispatch *dispatch = fallback_dispatch(device->dispatch); if (device->scroll.want_method == device->scroll.method && - device->scroll.want_button == device->scroll.button) + device->scroll.want_button == device->scroll.button && + device->scroll.want_lock_enabled == device->scroll.lock_enabled) return; if (fallback_any_button_down(dispatch, device)) @@ -1507,6 +1508,8 @@ fallback_change_scroll_method(struct evdev_device *device) device->scroll.method = device->scroll.want_method; device->scroll.button = device->scroll.want_button; + device->scroll.lock_enabled = device->scroll.want_lock_enabled; + evdev_set_button_scroll_lock_enabled(device, device->scroll.lock_enabled); } static int diff --git a/src/evdev.c b/src/evdev.c index 60a599e7..20b8dac9 100644 --- a/src/evdev.c +++ b/src/evdev.c @@ -198,6 +198,34 @@ static void evdev_button_scroll_button(struct evdev_device *device, uint64_t time, int is_press) { + /* Where the button lock is enabled, we wrap the buttons into + their own little state machine and filter out the events. + */ + switch (device->scroll.lock_state) { + case BUTTONSCROLL_LOCK_DISABLED: + break; + case BUTTONSCROLL_LOCK_IDLE: + assert(is_press); + device->scroll.lock_state = BUTTONSCROLL_LOCK_FIRSTDOWN; + evdev_log_debug(device, "scroll lock: first down\n"); + break; /* handle event */ + case BUTTONSCROLL_LOCK_FIRSTDOWN: + assert(!is_press); + device->scroll.lock_state = BUTTONSCROLL_LOCK_FIRSTUP; + evdev_log_debug(device, "scroll lock: first up\n"); + return; /* filter release event */ + case BUTTONSCROLL_LOCK_FIRSTUP: + assert(is_press); + device->scroll.lock_state = BUTTONSCROLL_LOCK_SECONDDOWN; + evdev_log_debug(device, "scroll lock: second down\n"); + return; /* filter press event */ + case BUTTONSCROLL_LOCK_SECONDDOWN: + assert(!is_press); + device->scroll.lock_state = BUTTONSCROLL_LOCK_IDLE; + evdev_log_debug(device, "scroll lock: idle\n"); + break; /* handle event */ + } + if (is_press) { enum timer_flags flags = TIMER_FLAG_NONE; @@ -705,6 +733,56 @@ evdev_scroll_get_default_button(struct libinput_device *device) return 0; } +static enum libinput_config_status +evdev_scroll_set_button_lock(struct libinput_device *device, + enum libinput_config_scroll_button_lock_state state) +{ + struct evdev_device *evdev = evdev_device(device); + + switch (state) { + case LIBINPUT_CONFIG_SCROLL_BUTTON_LOCK_DISABLED: + evdev->scroll.want_lock_enabled = false; + break; + case LIBINPUT_CONFIG_SCROLL_BUTTON_LOCK_ENABLED: + evdev->scroll.want_lock_enabled = true; + break; + default: + return LIBINPUT_CONFIG_STATUS_INVALID; + } + + evdev->scroll.change_scroll_method(evdev); + + return LIBINPUT_CONFIG_STATUS_SUCCESS; +} + +static enum libinput_config_scroll_button_lock_state +evdev_scroll_get_button_lock(struct libinput_device *device) +{ + struct evdev_device *evdev = evdev_device(device); + + if (evdev->scroll.lock_state == BUTTONSCROLL_LOCK_DISABLED) + return LIBINPUT_CONFIG_SCROLL_BUTTON_LOCK_DISABLED; + else + return LIBINPUT_CONFIG_SCROLL_BUTTON_LOCK_ENABLED; +} + +static enum libinput_config_scroll_button_lock_state +evdev_scroll_get_default_button_lock(struct libinput_device *device) +{ + return LIBINPUT_CONFIG_SCROLL_BUTTON_LOCK_DISABLED; +} + + +void +evdev_set_button_scroll_lock_enabled(struct evdev_device *device, + bool enabled) +{ + if (enabled) + device->scroll.lock_state = BUTTONSCROLL_LOCK_IDLE; + else + device->scroll.lock_state = BUTTONSCROLL_LOCK_DISABLED; +} + void evdev_init_button_scroll(struct evdev_device *device, void (*change_scroll_method)(struct evdev_device *)) @@ -726,6 +804,9 @@ evdev_init_button_scroll(struct evdev_device *device, device->scroll.config.set_button = evdev_scroll_set_button; device->scroll.config.get_button = evdev_scroll_get_button; device->scroll.config.get_default_button = evdev_scroll_get_default_button; + device->scroll.config.set_button_lock = evdev_scroll_set_button_lock; + device->scroll.config.get_button_lock = evdev_scroll_get_button_lock; + device->scroll.config.get_default_button_lock = evdev_scroll_get_default_button_lock; device->base.config.scroll_method = &device->scroll.config; device->scroll.method = evdev_scroll_get_default_method((struct libinput_device *)device); device->scroll.want_method = device->scroll.method; diff --git a/src/evdev.h b/src/evdev.h index 7de1fea9..0af0ba36 100644 --- a/src/evdev.h +++ b/src/evdev.h @@ -125,6 +125,14 @@ enum evdev_button_scroll_state { BUTTONSCROLL_SCROLLING, /* have sent scroll events */ }; +enum evdev_button_scroll_lock_state { + BUTTONSCROLL_LOCK_DISABLED, + BUTTONSCROLL_LOCK_IDLE, + BUTTONSCROLL_LOCK_FIRSTDOWN, + BUTTONSCROLL_LOCK_FIRSTUP, + BUTTONSCROLL_LOCK_SECONDDOWN, +}; + enum evdev_debounce_state { /** * Initial state, no debounce but monitoring events @@ -224,6 +232,10 @@ struct evdev_device { struct wheel_angle wheel_click_angle; struct wheel_tilt_flags is_tilt; + + enum evdev_button_scroll_lock_state lock_state; + bool want_lock_enabled; + bool lock_enabled; } scroll; struct { @@ -557,6 +569,10 @@ void evdev_init_button_scroll(struct evdev_device *device, void (*change_scroll_method)(struct evdev_device *)); +void +evdev_set_button_scroll_lock_enabled(struct evdev_device *device, + bool enabled); + int evdev_update_key_down_count(struct evdev_device *device, int code, diff --git a/src/libinput-private.h b/src/libinput-private.h index 1950e663..abb47894 100644 --- a/src/libinput-private.h +++ b/src/libinput-private.h @@ -272,6 +272,10 @@ struct libinput_device_config_scroll_method { uint32_t button); uint32_t (*get_button)(struct libinput_device *device); uint32_t (*get_default_button)(struct libinput_device *device); + enum libinput_config_status (*set_button_lock)(struct libinput_device *device, + enum libinput_config_scroll_button_lock_state); + enum libinput_config_scroll_button_lock_state (*get_button_lock)(struct libinput_device *device); + enum libinput_config_scroll_button_lock_state (*get_default_button_lock)(struct libinput_device *device); }; struct libinput_device_config_click_method { diff --git a/src/libinput.c b/src/libinput.c index 6d00a006..9570dc68 100644 --- a/src/libinput.c +++ b/src/libinput.c @@ -4149,6 +4149,45 @@ libinput_device_config_scroll_get_default_button(struct libinput_device *device) return device->config.scroll_method->get_default_button(device); } +LIBINPUT_EXPORT enum libinput_config_status +libinput_device_config_scroll_set_button_lock(struct libinput_device *device, + enum libinput_config_scroll_button_lock_state state) +{ + if ((libinput_device_config_scroll_get_methods(device) & + LIBINPUT_CONFIG_SCROLL_ON_BUTTON_DOWN) == 0) + return LIBINPUT_CONFIG_STATUS_UNSUPPORTED; + + switch (state) { + case LIBINPUT_CONFIG_SCROLL_BUTTON_LOCK_ENABLED: + case LIBINPUT_CONFIG_SCROLL_BUTTON_LOCK_DISABLED: + break; + default: + return LIBINPUT_CONFIG_STATUS_INVALID; + } + + return device->config.scroll_method->set_button_lock(device, state); +} + +LIBINPUT_EXPORT enum libinput_config_scroll_button_lock_state +libinput_device_config_scroll_get_button_lock(struct libinput_device *device) +{ + if ((libinput_device_config_scroll_get_methods(device) & + LIBINPUT_CONFIG_SCROLL_ON_BUTTON_DOWN) == 0) + return LIBINPUT_CONFIG_SCROLL_BUTTON_LOCK_DISABLED; + + return device->config.scroll_method->get_button_lock(device); +} + +LIBINPUT_EXPORT enum libinput_config_scroll_button_lock_state +libinput_device_config_scroll_get_default_button_lock(struct libinput_device *device) +{ + if ((libinput_device_config_scroll_get_methods(device) & + LIBINPUT_CONFIG_SCROLL_ON_BUTTON_DOWN) == 0) + return LIBINPUT_CONFIG_SCROLL_BUTTON_LOCK_DISABLED; + + return device->config.scroll_method->get_default_button_lock(device); +} + LIBINPUT_EXPORT int libinput_device_config_dwt_is_available(struct libinput_device *device) { diff --git a/src/libinput.h b/src/libinput.h index 59463f43..54d40de6 100644 --- a/src/libinput.h +++ b/src/libinput.h @@ -5594,6 +5594,81 @@ libinput_device_config_scroll_get_button(struct libinput_device *device); uint32_t libinput_device_config_scroll_get_default_button(struct libinput_device *device); +enum libinput_config_scroll_button_lock_state { + LIBINPUT_CONFIG_SCROLL_BUTTON_LOCK_DISABLED, + LIBINPUT_CONFIG_SCROLL_BUTTON_LOCK_ENABLED, +}; + +/** + * @ingroup config + * + * Set the scroll button lock. If the state is + * @ref LIBINPUT_CONFIG_SCROLL_BUTTON_LOCK_DISABLED, the button must + * physically be held down for button scrolling to work. + * 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. + * + * @param device The device to configure + * @param state The state to set the scroll button lock to + * + * @return A config status code. Disabling the scroll button lock on + * device that does not support button scrolling always succeeds. + * + * @see libinput_device_config_scroll_set_button + * @see libinput_device_config_scroll_get_button + * @see libinput_device_config_scroll_get_default_button + */ +enum libinput_config_status +libinput_device_config_scroll_set_button_lock(struct libinput_device *device, + enum libinput_config_scroll_button_lock_state state); + +/** + * @ingroup config + * + * Get the current scroll button lock state. + * + * If @ref LIBINPUT_CONFIG_SCROLL_ON_BUTTON_DOWN scroll method is not + * supported, or no button is set, this function returns @ref + * LIBINPUT_CONFIG_SCROLL_BUTTON_LOCK_DISABLED. + * + * @note The return value is independent of the currently selected + * scroll-method. For the scroll button lock to activate, a device must have + * the @ref LIBINPUT_CONFIG_SCROLL_ON_BUTTON_DOWN method enabled, and a + * non-zero button set as scroll button. + * + * @param device The device to configure + * @return The scroll button lock state + * + * @see libinput_device_config_scroll_set_button + * @see libinput_device_config_scroll_set_button_lock + * @see libinput_device_config_scroll_get_button_lock + * @see libinput_device_config_scroll_get_default_button_lock + */ +enum libinput_config_scroll_button_lock_state +libinput_device_config_scroll_get_button_lock(struct libinput_device *device); + +/** + * @ingroup config + * + * Get the default scroll button lock state. + * + * If @ref LIBINPUT_CONFIG_SCROLL_ON_BUTTON_DOWN scroll method is not + * supported, or no button is set, this function returns @ref + * LIBINPUT_CONFIG_SCROLL_BUTTON_LOCK_DISABLED. + * + * @param device The device to configure + * @return The default scroll button lock state + * + * @see libinput_device_config_scroll_set_button + * @see libinput_device_config_scroll_set_button_lock + * @see libinput_device_config_scroll_get_button_lock + * @see libinput_device_config_scroll_get_default_button_lock + */ +enum libinput_config_scroll_button_lock_state +libinput_device_config_scroll_get_default_button_lock(struct libinput_device *device); + /** * @ingroup config * diff --git a/src/libinput.sym b/src/libinput.sym index ef9d9178..1698a066 100644 --- a/src/libinput.sym +++ b/src/libinput.sym @@ -305,3 +305,9 @@ LIBINPUT_1.14 { libinput_event_tablet_tool_size_major_has_changed; libinput_event_tablet_tool_size_minor_has_changed; } LIBINPUT_1.11; + +LIBINPUT_1.15 { + libinput_device_config_scroll_set_button_lock; + libinput_device_config_scroll_get_button_lock; + libinput_device_config_scroll_get_default_button_lock; +} LIBINPUT_1.14; diff --git a/test/litest.c b/test/litest.c index 5b09ec4e..2e17009e 100644 --- a/test/litest.c +++ b/test/litest.c @@ -2496,6 +2496,27 @@ litest_button_scroll(struct litest_device *dev, } void +litest_button_scroll_locked(struct litest_device *dev, + unsigned int button, + double dx, double dy) +{ + struct libinput *li = dev->libinput; + + litest_button_click_debounced(dev, li, button, 1); + litest_button_click_debounced(dev, li, button, 0); + + libinput_dispatch(li); + litest_timeout_buttonscroll(); + libinput_dispatch(li); + + litest_event(dev, EV_REL, REL_X, dx); + litest_event(dev, EV_REL, REL_Y, dy); + litest_event(dev, EV_SYN, SYN_REPORT, 0); + + libinput_dispatch(li); +} + +void litest_keyboard_key(struct litest_device *d, unsigned int key, bool is_press) { struct input_event *ev; diff --git a/test/litest.h b/test/litest.h index 85a0a1f2..fea10f7e 100644 --- a/test/litest.h +++ b/test/litest.h @@ -663,6 +663,10 @@ void litest_button_scroll(struct litest_device *d, unsigned int button, double dx, double dy); +void +litest_button_scroll_locked(struct litest_device *d, + unsigned int button, + double dx, double dy); void litest_keyboard_key(struct litest_device *d, diff --git a/test/test-pointer.c b/test/test-pointer.c index 4ec32f89..f1482642 100644 --- a/test/test-pointer.c +++ b/test/test-pointer.c @@ -1227,6 +1227,503 @@ START_TEST(pointer_scroll_button_device_remove_while_down) } END_TEST +static void +litest_enable_scroll_button_lock(struct litest_device *dev, + unsigned int button) +{ + struct libinput_device *device = dev->libinput_device; + enum libinput_config_status status; + + status = libinput_device_config_scroll_set_method(device, + LIBINPUT_CONFIG_SCROLL_ON_BUTTON_DOWN); + ck_assert_int_eq(status, LIBINPUT_CONFIG_STATUS_SUCCESS); + + status = libinput_device_config_scroll_set_button(device, button); + ck_assert_int_eq(status, LIBINPUT_CONFIG_STATUS_SUCCESS); + + status = libinput_device_config_scroll_set_button_lock(device, + LIBINPUT_CONFIG_SCROLL_BUTTON_LOCK_ENABLED); + ck_assert_int_eq(status, LIBINPUT_CONFIG_STATUS_SUCCESS); +} + +START_TEST(pointer_scroll_button_lock) +{ + 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); + + litest_button_click_debounced(dev, li, BTN_LEFT, true); + litest_button_click_debounced(dev, li, BTN_LEFT, false); + + litest_assert_empty_queue(li); + + litest_timeout_buttonscroll(); + libinput_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); + } + + libinput_dispatch(li); + + litest_button_click_debounced(dev, li, BTN_LEFT, true); + litest_button_click_debounced(dev, li, BTN_LEFT, false); + libinput_dispatch(li); + + litest_assert_scroll(li, LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL, 6); + + litest_assert_empty_queue(li); + + /* back to 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_defaults) +{ + struct litest_device *dev = litest_current_device(); + enum libinput_config_scroll_button_lock_state state; + + state = libinput_device_config_scroll_get_button_lock(dev->libinput_device); + ck_assert_int_eq(state, LIBINPUT_CONFIG_SCROLL_BUTTON_LOCK_DISABLED); + state = libinput_device_config_scroll_get_default_button_lock(dev->libinput_device); + ck_assert_int_eq(state, LIBINPUT_CONFIG_SCROLL_BUTTON_LOCK_DISABLED); +} +END_TEST + +START_TEST(pointer_scroll_button_lock_config) +{ + struct litest_device *dev = litest_current_device(); + enum libinput_config_status status; + enum libinput_config_scroll_button_lock_state state; + + state = libinput_device_config_scroll_get_button_lock(dev->libinput_device); + ck_assert_int_eq(state, LIBINPUT_CONFIG_SCROLL_BUTTON_LOCK_DISABLED); + state = libinput_device_config_scroll_get_default_button_lock(dev->libinput_device); + ck_assert_int_eq(state, LIBINPUT_CONFIG_SCROLL_BUTTON_LOCK_DISABLED); + + status = libinput_device_config_scroll_set_button_lock(dev->libinput_device, + LIBINPUT_CONFIG_SCROLL_BUTTON_LOCK_DISABLED); + ck_assert_int_eq(status, LIBINPUT_CONFIG_STATUS_SUCCESS); + state = libinput_device_config_scroll_get_button_lock(dev->libinput_device); + ck_assert_int_eq(state, LIBINPUT_CONFIG_SCROLL_BUTTON_LOCK_DISABLED); + + + status = libinput_device_config_scroll_set_button_lock(dev->libinput_device, + LIBINPUT_CONFIG_SCROLL_BUTTON_LOCK_ENABLED); + ck_assert_int_eq(status, LIBINPUT_CONFIG_STATUS_SUCCESS); + state = libinput_device_config_scroll_get_button_lock(dev->libinput_device); + ck_assert_int_eq(state, LIBINPUT_CONFIG_SCROLL_BUTTON_LOCK_ENABLED); + + status = libinput_device_config_scroll_set_button_lock(dev->libinput_device, + LIBINPUT_CONFIG_SCROLL_BUTTON_LOCK_ENABLED + 1); + ck_assert_int_eq(status, LIBINPUT_CONFIG_STATUS_INVALID); +} +END_TEST + +START_TEST(pointer_scroll_button_lock_enable_while_down) +{ + struct litest_device *dev = litest_current_device(); + struct libinput *li = dev->libinput; + + litest_disable_middleemu(dev); + litest_drain_events(li); + + litest_button_click_debounced(dev, li, BTN_LEFT, true); + + /* Enable lock while button is down */ + litest_enable_scroll_button_lock(dev, BTN_LEFT); + + litest_assert_button_event(li, BTN_LEFT, LIBINPUT_BUTTON_STATE_PRESSED); + litest_assert_empty_queue(li); + + litest_button_click_debounced(dev, li, BTN_LEFT, false); + litest_assert_button_event(li, BTN_LEFT, LIBINPUT_BUTTON_STATE_RELEASED); + 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); + } + + /* no scrolling yet */ + litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_MOTION); + + /* but on the next button press we scroll lock */ + litest_button_click_debounced(dev, li, BTN_LEFT, true); + litest_button_click_debounced(dev, li, BTN_LEFT, false); + libinput_dispatch(li); + litest_timeout_buttonscroll(); + libinput_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_button_click_debounced(dev, li, BTN_LEFT, true); + litest_button_click_debounced(dev, li, BTN_LEFT, false); + + litest_assert_scroll(li, LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL, 6); + + litest_assert_empty_queue(li); + + /* back to 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_enable_while_down_just_lock) +{ + struct litest_device *dev = litest_current_device(); + struct libinput *li = dev->libinput; + + litest_disable_middleemu(dev); + litest_drain_events(li); + + /* switch method first, but enable lock when we already have a + * button down */ + libinput_device_config_scroll_set_method(dev->libinput_device, + LIBINPUT_CONFIG_SCROLL_ON_BUTTON_DOWN); + libinput_device_config_scroll_set_button(dev->libinput_device, + BTN_LEFT); + + litest_button_click_debounced(dev, li, BTN_LEFT, true); + libinput_device_config_scroll_set_button_lock(dev->libinput_device, + LIBINPUT_CONFIG_SCROLL_BUTTON_LOCK_ENABLED); + + litest_button_click_debounced(dev, li, BTN_LEFT, false); + litest_assert_button_event(li, BTN_LEFT, LIBINPUT_BUTTON_STATE_PRESSED); + litest_assert_button_event(li, BTN_LEFT, LIBINPUT_BUTTON_STATE_RELEASED); + 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); + } + + /* no scrolling yet */ + litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_MOTION); + + /* but on the next button press we scroll lock */ + litest_button_click_debounced(dev, li, BTN_LEFT, true); + litest_button_click_debounced(dev, li, BTN_LEFT, false); + libinput_dispatch(li); + litest_timeout_buttonscroll(); + libinput_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_button_click_debounced(dev, li, BTN_LEFT, true); + litest_button_click_debounced(dev, li, BTN_LEFT, false); + + litest_assert_scroll(li, LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL, 6); + + litest_assert_empty_queue(li); + + /* back to 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_otherbutton) +{ + struct litest_device *dev = litest_current_device(); + struct libinput *li = dev->libinput; + + litest_disable_middleemu(dev); + litest_drain_events(li); + + litest_enable_scroll_button_lock(dev, BTN_LEFT); + + litest_button_click_debounced(dev, li, BTN_LEFT, true); + litest_button_click_debounced(dev, li, BTN_LEFT, false); + litest_assert_empty_queue(li); + litest_timeout_buttonscroll(); + libinput_dispatch(li); + + /* other button passes on normally */ + litest_button_click_debounced(dev, li, BTN_RIGHT, true); + litest_button_click_debounced(dev, li, BTN_RIGHT, false); + litest_assert_button_event(li, BTN_RIGHT, LIBINPUT_BUTTON_STATE_PRESSED); + litest_assert_button_event(li, BTN_RIGHT, LIBINPUT_BUTTON_STATE_RELEASED); + 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_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_AXIS); + + /* other button passes on normally */ + litest_button_click_debounced(dev, li, BTN_RIGHT, true); + litest_button_click_debounced(dev, li, BTN_RIGHT, false); + litest_assert_button_event(li, BTN_RIGHT, LIBINPUT_BUTTON_STATE_PRESSED); + litest_assert_button_event(li, BTN_RIGHT, LIBINPUT_BUTTON_STATE_RELEASED); + + /* stop scroll lock */ + litest_button_click_debounced(dev, li, BTN_LEFT, true); + litest_button_click_debounced(dev, li, BTN_LEFT, false); + litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_AXIS); + + /* other button passes on normally */ + litest_button_click_debounced(dev, li, BTN_RIGHT, true); + litest_button_click_debounced(dev, li, BTN_RIGHT, false); + litest_assert_button_event(li, BTN_RIGHT, LIBINPUT_BUTTON_STATE_PRESSED); + litest_assert_button_event(li, BTN_RIGHT, LIBINPUT_BUTTON_STATE_RELEASED); + + litest_assert_empty_queue(li); +} +END_TEST + +START_TEST(pointer_scroll_button_lock_enable_while_otherbutton_down) +{ + struct litest_device *dev = litest_current_device(); + struct libinput *li = dev->libinput; + + litest_disable_middleemu(dev); + litest_drain_events(li); + + litest_button_click_debounced(dev, li, BTN_RIGHT, true); + litest_timeout_middlebutton(); + litest_drain_events(li); + + /* Enable lock while button is down */ + litest_enable_scroll_button_lock(dev, BTN_LEFT); + + /* We only enable once we go to a neutral state so this still counts + * as normal button event */ + for (int twice = 0; twice < 2; twice++) { + litest_button_click_debounced(dev, li, BTN_LEFT, true); + litest_button_click_debounced(dev, li, BTN_LEFT, false); + litest_assert_button_event(li, BTN_LEFT, LIBINPUT_BUTTON_STATE_PRESSED); + litest_assert_button_event(li, BTN_LEFT, LIBINPUT_BUTTON_STATE_RELEASED); + + 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); + } + + litest_button_click_debounced(dev, li, BTN_RIGHT, false); + litest_assert_button_event(li, BTN_RIGHT, LIBINPUT_BUTTON_STATE_RELEASED); + litest_assert_empty_queue(li); + + /* now we should trigger it */ + litest_button_click_debounced(dev, li, BTN_LEFT, true); + litest_button_click_debounced(dev, li, BTN_LEFT, false); + litest_timeout_buttonscroll(); + 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_button_click_debounced(dev, li, BTN_LEFT, true); + litest_button_click_debounced(dev, li, BTN_LEFT, false); + litest_assert_scroll(li, LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL, 6); + litest_assert_empty_queue(li); + + /* back to 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 + +enum mb_buttonorder { + LLRR, /* left down, left up, r down, r up */ + LRLR, /* left down, right down, left up, right up */ + LRRL, + RRLL, + RLRL, + RLLR, + _MB_BUTTONORDER_COUNT +}; + +START_TEST(pointer_scroll_button_lock_middlebutton) +{ + struct litest_device *dev = litest_current_device(); + struct libinput *li = dev->libinput; + enum mb_buttonorder buttonorder = _i; /* ranged test */ + + if (!libinput_device_config_middle_emulation_is_available(dev->libinput_device)) + return; + + litest_enable_middleemu(dev); + + litest_enable_scroll_button_lock(dev, BTN_LEFT); + litest_drain_events(li); + + /* We expect scroll lock to work only where left and right are never + * held down simultaneously. Everywhere else we expect middle button + * instead. + */ + switch (buttonorder) { + case LLRR: + litest_button_click_debounced(dev, li, BTN_LEFT, true); + litest_button_click_debounced(dev, li, BTN_LEFT, false); + litest_button_click_debounced(dev, li, BTN_RIGHT, true); + litest_button_click_debounced(dev, li, BTN_RIGHT, false); + break; + case LRLR: + litest_button_click_debounced(dev, li, BTN_LEFT, true); + litest_button_click_debounced(dev, li, BTN_RIGHT, true); + litest_button_click_debounced(dev, li, BTN_LEFT, false); + litest_button_click_debounced(dev, li, BTN_RIGHT, false); + break; + case LRRL: + litest_button_click_debounced(dev, li, BTN_LEFT, true); + litest_button_click_debounced(dev, li, BTN_RIGHT, true); + litest_button_click_debounced(dev, li, BTN_RIGHT, false); + litest_button_click_debounced(dev, li, BTN_LEFT, false); + break; + case RRLL: + litest_button_click_debounced(dev, li, BTN_RIGHT, true); + litest_button_click_debounced(dev, li, BTN_RIGHT, false); + litest_button_click_debounced(dev, li, BTN_LEFT, true); + litest_button_click_debounced(dev, li, BTN_LEFT, false); + break; + case RLRL: + litest_button_click_debounced(dev, li, BTN_RIGHT, true); + litest_button_click_debounced(dev, li, BTN_LEFT, true); + litest_button_click_debounced(dev, li, BTN_RIGHT, false); + litest_button_click_debounced(dev, li, BTN_LEFT, false); + break; + case RLLR: + litest_button_click_debounced(dev, li, BTN_RIGHT, true); + litest_button_click_debounced(dev, li, BTN_LEFT, true); + litest_button_click_debounced(dev, li, BTN_LEFT, false); + litest_button_click_debounced(dev, li, BTN_RIGHT, false); + break; + default: + abort(); + } + + libinput_dispatch(li); + litest_timeout_middlebutton(); + litest_timeout_buttonscroll(); + libinput_dispatch(li); + + /* motion events are the same for all of them */ + 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); + } + + libinput_dispatch(li); + + switch (buttonorder) { + case LLRR: + case RRLL: + litest_button_click_debounced(dev, li, BTN_LEFT, true); + litest_button_click_debounced(dev, li, BTN_LEFT, false); + break; + default: + break; + } + + libinput_dispatch(li); + + switch (buttonorder) { + case LLRR: + case RRLL: + litest_assert_button_event(li, BTN_RIGHT, + LIBINPUT_BUTTON_STATE_PRESSED); + litest_assert_button_event(li, BTN_RIGHT, + LIBINPUT_BUTTON_STATE_RELEASED); + litest_assert_scroll(li, LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL, 6); + litest_assert_empty_queue(li); + break; + case LRLR: + case LRRL: + case RLRL: + case RLLR: + litest_assert_button_event(li, BTN_MIDDLE, + LIBINPUT_BUTTON_STATE_PRESSED); + litest_assert_button_event(li, BTN_MIDDLE, + LIBINPUT_BUTTON_STATE_RELEASED); + litest_assert_only_typed_events(li, + LIBINPUT_EVENT_POINTER_MOTION); + break; + default: + abort(); + } + +} +END_TEST + +START_TEST(pointer_scroll_button_lock_doubleclick_nomove) +{ + struct litest_device *dev = litest_current_device(); + struct libinput *li = dev->libinput; + + litest_disable_middleemu(dev); + litest_enable_scroll_button_lock(dev, BTN_LEFT); + litest_drain_events(li); + + /* double click without move in between counts as single click */ + litest_button_click_debounced(dev, li, BTN_LEFT, true); + litest_button_click_debounced(dev, li, BTN_LEFT, false); + litest_assert_empty_queue(li); + litest_button_click_debounced(dev, li, BTN_LEFT, true); + litest_button_click_debounced(dev, li, BTN_LEFT, false); + + litest_assert_button_event(li, BTN_LEFT, LIBINPUT_BUTTON_STATE_PRESSED); + litest_assert_button_event(li, BTN_LEFT, LIBINPUT_BUTTON_STATE_RELEASED); + litest_assert_empty_queue(li); + + /* But a non-scroll button it should work normally */ + litest_button_click_debounced(dev, li, BTN_RIGHT, true); + litest_button_click_debounced(dev, li, BTN_RIGHT, false); + litest_button_click_debounced(dev, li, BTN_RIGHT, true); + litest_button_click_debounced(dev, li, BTN_RIGHT, false); + litest_assert_button_event(li, BTN_RIGHT, LIBINPUT_BUTTON_STATE_PRESSED); + litest_assert_button_event(li, BTN_RIGHT, LIBINPUT_BUTTON_STATE_RELEASED); + litest_assert_button_event(li, BTN_RIGHT, LIBINPUT_BUTTON_STATE_PRESSED); + litest_assert_button_event(li, BTN_RIGHT, LIBINPUT_BUTTON_STATE_RELEASED); + litest_assert_empty_queue(li); + +} +END_TEST + START_TEST(pointer_scroll_nowheel_defaults) { struct litest_device *dev = litest_current_device(); @@ -2691,6 +3188,7 @@ TEST_COLLECTION(pointer) struct range axis_range = {ABS_X, ABS_Y + 1}; struct range compass = {0, 7}; /* cardinal directions */ struct range buttons = {BTN_LEFT, BTN_TASK + 1}; + struct range buttonorder = {0, _MB_BUTTONORDER_COUNT}; litest_add("pointer:motion", pointer_motion_relative, LITEST_RELATIVE, LITEST_POINTINGSTICK); litest_add_for_device("pointer:motion", pointer_motion_relative_zero, LITEST_MOUSE); @@ -2709,6 +3207,17 @@ TEST_COLLECTION(pointer) litest_add("pointer:scroll", pointer_scroll_button_no_event_before_timeout, LITEST_RELATIVE|LITEST_BUTTON, LITEST_ANY); litest_add("pointer:scroll", pointer_scroll_button_middle_emulation, LITEST_RELATIVE|LITEST_BUTTON, LITEST_ANY); litest_add("pointer:scroll", pointer_scroll_button_device_remove_while_down, LITEST_ANY, LITEST_RELATIVE|LITEST_BUTTON); + + litest_add("pointer:scroll", pointer_scroll_button_lock, LITEST_RELATIVE|LITEST_BUTTON, LITEST_ANY); + litest_add("pointer:scroll", pointer_scroll_button_lock_defaults, LITEST_RELATIVE|LITEST_BUTTON, LITEST_ANY); + litest_add("pointer:scroll", pointer_scroll_button_lock_config, LITEST_RELATIVE|LITEST_BUTTON, LITEST_ANY); + litest_add("pointer:scroll", pointer_scroll_button_lock_enable_while_down, LITEST_RELATIVE|LITEST_BUTTON, LITEST_ANY); + litest_add("pointer:scroll", pointer_scroll_button_lock_enable_while_down_just_lock, LITEST_RELATIVE|LITEST_BUTTON, LITEST_ANY); + litest_add("pointer:scroll", pointer_scroll_button_lock_otherbutton, LITEST_RELATIVE|LITEST_BUTTON, LITEST_ANY); + litest_add("pointer:scroll", pointer_scroll_button_lock_enable_while_otherbutton_down, LITEST_RELATIVE|LITEST_BUTTON, LITEST_ANY); + litest_add_ranged("pointer:scroll", pointer_scroll_button_lock_middlebutton, LITEST_RELATIVE|LITEST_BUTTON, LITEST_ANY, &buttonorder); + litest_add("pointer:scroll", pointer_scroll_button_lock_doubleclick_nomove, LITEST_RELATIVE|LITEST_BUTTON, LITEST_ANY); + litest_add("pointer:scroll", pointer_scroll_nowheel_defaults, LITEST_RELATIVE|LITEST_BUTTON, LITEST_WHEEL); litest_add_for_device("pointer:scroll", pointer_scroll_defaults_logitech_marble , LITEST_LOGITECH_TRACKBALL); litest_add("pointer:scroll", pointer_scroll_natural_defaults, LITEST_WHEEL, LITEST_TABLET); diff --git a/tools/libinput-debug-events.man b/tools/libinput-debug-events.man index 29a03c5a..0fc73009 100644 --- a/tools/libinput-debug-events.man +++ b/tools/libinput-debug-events.man @@ -79,6 +79,9 @@ Enable or disable middle button emulation .B \-\-enable\-dwt|\-\-disable\-dwt Enable or disable disable-while-typing .TP 8 +.B \-\-enable\scroll-button-lock|\-\-disable\-scroll-button-lock +Enable or disable the scroll button lock +.TP 8 .B \-\-set\-click\-method=[none|clickfinger|buttonareas] Set the desired click method .TP 8 diff --git a/tools/shared.c b/tools/shared.c index 8541c595..7dba96dc 100644 --- a/tools/shared.c +++ b/tools/shared.c @@ -83,6 +83,7 @@ tools_init_options(struct tools_options *options) options->click_method = -1; options->scroll_method = -1; options->scroll_button = -1; + options->scroll_button_lock = -1; options->speed = 0.0; options->profile = LIBINPUT_CONFIG_ACCEL_PROFILE_NONE; } @@ -198,6 +199,12 @@ tools_parse_option(int option, return 1; } break; + case OPT_SCROLL_BUTTON_LOCK_ENABLE: + options->scroll_button_lock = true; + break; + case OPT_SCROLL_BUTTON_LOCK_DISABLE: + options->scroll_button_lock = false; + break; case OPT_SPEED: if (!optarg) return 1; @@ -407,6 +414,10 @@ tools_device_apply_config(struct libinput_device *device, if (options->scroll_button != -1) libinput_device_config_scroll_set_button(device, options->scroll_button); + if (options->scroll_button_lock != -1) + libinput_device_config_scroll_set_button_lock(device, + options->scroll_button_lock); + if (libinput_device_config_accel_is_available(device)) { libinput_device_config_accel_set_speed(device, diff --git a/tools/shared.h b/tools/shared.h index e2a6d662..e36bfd6e 100644 --- a/tools/shared.h +++ b/tools/shared.h @@ -51,6 +51,8 @@ enum configuration_options { OPT_CLICK_METHOD, OPT_SCROLL_METHOD, OPT_SCROLL_BUTTON, + OPT_SCROLL_BUTTON_LOCK_ENABLE, + OPT_SCROLL_BUTTON_LOCK_DISABLE, OPT_SPEED, OPT_PROFILE, OPT_DISABLE_SENDEVENTS, @@ -73,6 +75,8 @@ enum configuration_options { { "disable-middlebutton", no_argument, 0, OPT_MIDDLEBUTTON_DISABLE }, \ { "enable-dwt", no_argument, 0, OPT_DWT_ENABLE }, \ { "disable-dwt", no_argument, 0, OPT_DWT_DISABLE }, \ + { "enable-scroll-button-lock", no_argument, 0, OPT_SCROLL_BUTTON_LOCK_ENABLE }, \ + { "disable-scroll-button-lock",no_argument, 0, OPT_SCROLL_BUTTON_LOCK_DISABLE }, \ { "set-click-method", required_argument, 0, OPT_CLICK_METHOD }, \ { "set-scroll-method", required_argument, 0, OPT_SCROLL_METHOD }, \ { "set-scroll-button", required_argument, 0, OPT_SCROLL_BUTTON }, \ @@ -100,6 +104,7 @@ struct tools_options { enum libinput_config_scroll_method scroll_method; enum libinput_config_tap_button_map tap_map; int scroll_button; + int scroll_button_lock; double speed; int dwt; enum libinput_config_accel_profile profile; |