diff options
-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; |