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>
This commit is contained in:
Peter Hutterer 2019-03-20 10:56:51 +10:00
parent 12eb14846a
commit 63f9923013
14 changed files with 785 additions and 1 deletions

View file

@ -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:
------------------------------------------------------------------------------

View file

@ -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

View file

@ -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;

View file

@ -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,

View file

@ -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 {

View file

@ -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)
{

View file

@ -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
*

View file

@ -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;

View file

@ -2495,6 +2495,27 @@ litest_button_scroll(struct litest_device *dev,
libinput_dispatch(li);
}
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)
{

View file

@ -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,

View file

@ -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);

View file

@ -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

View file

@ -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,

View file

@ -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;