summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--doc/user/scrolling.rst7
-rw-r--r--src/evdev-fallback.c5
-rw-r--r--src/evdev.c81
-rw-r--r--src/evdev.h16
-rw-r--r--src/libinput-private.h4
-rw-r--r--src/libinput.c39
-rw-r--r--src/libinput.h75
-rw-r--r--src/libinput.sym6
-rw-r--r--test/litest.c21
-rw-r--r--test/litest.h4
-rw-r--r--test/test-pointer.c509
-rw-r--r--tools/libinput-debug-events.man3
-rw-r--r--tools/shared.c11
-rw-r--r--tools/shared.h5
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;