wheel: ignore initial small scroll deltas

Mice with high-resolution support can generate deltas when the finger is
put on the wheel or when the user tries to click the wheel.

To avoid sending involuntary scroll events, add an extra state the the
wheel state machine to accumulate scroll deltas.
While the accumulated scroll is lower than a certain threshold, ignore
them until the threshold is reached.

Since no finish event is sent by the mouse, reset the state machine
after a period of scroll inactivity.

Signed-off-by: José Expósito <jose.exposito89@gmail.com>
This commit is contained in:
José Expósito 2021-09-29 18:32:54 +02:00
parent 08245c778a
commit b6a944bb80
6 changed files with 161 additions and 1 deletions

View file

@ -1109,6 +1109,7 @@ fallback_interface_remove(struct evdev_dispatch *evdev_dispatch)
struct fallback_dispatch *dispatch = fallback_dispatch(evdev_dispatch);
struct evdev_paired_keyboard *kbd;
libinput_timer_cancel(&dispatch->wheel.scroll_timer);
libinput_timer_cancel(&dispatch->debounce.timer);
libinput_timer_cancel(&dispatch->debounce.timer_short);
libinput_timer_cancel(&dispatch->arbitration.arbitration_timer);
@ -1222,6 +1223,7 @@ fallback_interface_destroy(struct evdev_dispatch *evdev_dispatch)
{
struct fallback_dispatch *dispatch = fallback_dispatch(evdev_dispatch);
libinput_timer_destroy(&dispatch->wheel.scroll_timer);
libinput_timer_destroy(&dispatch->arbitration.arbitration_timer);
libinput_timer_destroy(&dispatch->debounce.timer);
libinput_timer_destroy(&dispatch->debounce.timer_short);

View file

@ -62,6 +62,7 @@ enum palm_state {
enum wheel_state {
WHEEL_STATE_NONE,
WHEEL_STATE_PRESSED,
WHEEL_STATE_ACCUMULATING_SCROLL,
WHEEL_STATE_SCROLLING,
};
@ -109,6 +110,7 @@ struct fallback_dispatch {
struct device_coords hi_res;
bool emulate_hi_res_wheel;
bool hi_res_event_received;
struct libinput_timer scroll_timer;
} wheel;
struct {

View file

@ -30,10 +30,15 @@
#include "evdev-fallback.h"
#include "util-input-event.h"
#define ACC_V120_THRESHOLD 60
#define WHEEL_SCROLL_TIMEOUT ms2us(500)
enum wheel_event {
WHEEL_EVENT_PRESS,
WHEEL_EVENT_RELEASE,
WHEEL_EVENT_SCROLL_ACCUMULATED,
WHEEL_EVENT_SCROLL,
WHEEL_EVENT_SCROLL_TIMEOUT,
};
static inline const char *
@ -42,6 +47,7 @@ wheel_state_to_str(enum wheel_state state)
switch(state) {
CASE_RETURN_STRING(WHEEL_STATE_NONE);
CASE_RETURN_STRING(WHEEL_STATE_PRESSED);
CASE_RETURN_STRING(WHEEL_STATE_ACCUMULATING_SCROLL);
CASE_RETURN_STRING(WHEEL_STATE_SCROLLING);
}
return NULL;
@ -53,7 +59,9 @@ wheel_event_to_str(enum wheel_event event)
switch(event) {
CASE_RETURN_STRING(WHEEL_EVENT_PRESS);
CASE_RETURN_STRING(WHEEL_EVENT_RELEASE);
CASE_RETURN_STRING(WHEEL_EVENT_SCROLL_ACCUMULATED);
CASE_RETURN_STRING(WHEEL_EVENT_SCROLL);
CASE_RETURN_STRING(WHEEL_EVENT_SCROLL_TIMEOUT);
}
return NULL;
}
@ -67,6 +75,19 @@ log_wheel_bug(struct fallback_dispatch *dispatch, enum wheel_event event)
wheel_state_to_str(dispatch->wheel.state));
}
static inline void
wheel_set_scroll_timer(struct fallback_dispatch *dispatch, uint64_t time)
{
libinput_timer_set(&dispatch->wheel.scroll_timer,
time + WHEEL_SCROLL_TIMEOUT);
}
static inline void
wheel_cancel_scroll_timer(struct fallback_dispatch *dispatch)
{
libinput_timer_cancel(&dispatch->wheel.scroll_timer);
}
static void
wheel_handle_event_on_state_none(struct fallback_dispatch *dispatch,
enum wheel_event event,
@ -77,9 +98,11 @@ wheel_handle_event_on_state_none(struct fallback_dispatch *dispatch,
dispatch->wheel.state = WHEEL_STATE_PRESSED;
break;
case WHEEL_EVENT_SCROLL:
dispatch->wheel.state = WHEEL_STATE_SCROLLING;
dispatch->wheel.state = WHEEL_STATE_ACCUMULATING_SCROLL;
break;
case WHEEL_EVENT_RELEASE:
case WHEEL_EVENT_SCROLL_ACCUMULATED:
case WHEEL_EVENT_SCROLL_TIMEOUT:
log_wheel_bug(dispatch, event);
break;
}
@ -98,6 +121,31 @@ wheel_handle_event_on_state_pressed(struct fallback_dispatch *dispatch,
/* Ignore scroll while the wheel is pressed */
break;
case WHEEL_EVENT_PRESS:
case WHEEL_EVENT_SCROLL_ACCUMULATED:
case WHEEL_EVENT_SCROLL_TIMEOUT:
log_wheel_bug(dispatch, event);
break;
}
}
static void
wheel_handle_event_on_state_accumulating_scroll(struct fallback_dispatch *dispatch,
enum wheel_event event,
uint64_t time)
{
switch (event) {
case WHEEL_EVENT_PRESS:
dispatch->wheel.state = WHEEL_STATE_PRESSED;
break;
case WHEEL_EVENT_SCROLL_ACCUMULATED:
dispatch->wheel.state = WHEEL_STATE_SCROLLING;
wheel_set_scroll_timer(dispatch, time);
break;
case WHEEL_EVENT_SCROLL:
/* Ignore scroll while accumulating deltas */
break;
case WHEEL_EVENT_RELEASE:
case WHEEL_EVENT_SCROLL_TIMEOUT:
log_wheel_bug(dispatch, event);
break;
}
@ -111,10 +159,17 @@ wheel_handle_event_on_state_scrolling(struct fallback_dispatch *dispatch,
switch (event) {
case WHEEL_EVENT_PRESS:
dispatch->wheel.state = WHEEL_STATE_PRESSED;
wheel_cancel_scroll_timer(dispatch);
break;
case WHEEL_EVENT_SCROLL:
wheel_cancel_scroll_timer(dispatch);
wheel_set_scroll_timer(dispatch, time);
break;
case WHEEL_EVENT_SCROLL_TIMEOUT:
dispatch->wheel.state = WHEEL_STATE_NONE;
break;
case WHEEL_EVENT_RELEASE:
case WHEEL_EVENT_SCROLL_ACCUMULATED:
log_wheel_bug(dispatch, event);
break;
}
@ -134,6 +189,11 @@ wheel_handle_event(struct fallback_dispatch *dispatch,
case WHEEL_STATE_PRESSED:
wheel_handle_event_on_state_pressed(dispatch, event, time);
break;
case WHEEL_STATE_ACCUMULATING_SCROLL:
wheel_handle_event_on_state_accumulating_scroll(dispatch,
event,
time);
break;
case WHEEL_STATE_SCROLLING:
wheel_handle_event_on_state_scrolling(dispatch, event, time);
break;
@ -248,6 +308,20 @@ wheel_handle_state_pressed(struct fallback_dispatch *dispatch,
dispatch->wheel.lo_res.y = 0;
}
static void
wheel_handle_state_accumulating_scroll(struct fallback_dispatch *dispatch,
struct evdev_device *device,
uint64_t time)
{
if (abs(dispatch->wheel.hi_res.x) >= ACC_V120_THRESHOLD ||
abs(dispatch->wheel.hi_res.y) >= ACC_V120_THRESHOLD) {
wheel_handle_event(dispatch,
WHEEL_EVENT_SCROLL_ACCUMULATED,
time);
wheel_flush_scroll(dispatch, device, time);
}
}
static void
wheel_handle_state_scrolling(struct fallback_dispatch *dispatch,
struct evdev_device *device,
@ -342,16 +416,31 @@ fallback_wheel_handle_state(struct fallback_dispatch *dispatch,
case WHEEL_STATE_PRESSED:
wheel_handle_state_pressed(dispatch, device, time);
break;
case WHEEL_STATE_ACCUMULATING_SCROLL:
wheel_handle_state_accumulating_scroll(dispatch, device, time);
break;
case WHEEL_STATE_SCROLLING:
wheel_handle_state_scrolling(dispatch, device, time);
break;
}
}
static void
wheel_init_scroll_timer(uint64_t now, void *data)
{
struct evdev_device *device = data;
struct fallback_dispatch *dispatch =
fallback_dispatch(device->dispatch);
wheel_handle_event(dispatch, WHEEL_EVENT_SCROLL_TIMEOUT, now);
}
void
fallback_init_wheel(struct fallback_dispatch *dispatch,
struct evdev_device *device)
{
char timer_name[64];
dispatch->wheel.state = WHEEL_STATE_NONE;
/* On kernel < 5.0 we need to emulate high-resolution
@ -369,4 +458,14 @@ fallback_init_wheel(struct fallback_dispatch *dispatch,
EV_REL,
REL_HWHEEL_HI_RES)))
dispatch->wheel.emulate_hi_res_wheel = true;
snprintf(timer_name,
sizeof(timer_name),
"%s wheel scroll",
evdev_device_get_sysname(device));
libinput_timer_init(&dispatch->wheel.scroll_timer,
evdev_libinput_context(device),
timer_name,
wheel_init_scroll_timer,
device);
}

View file

@ -4299,6 +4299,12 @@ litest_timeout_finger_switch(void)
msleep(120);
}
void
litest_timeout_wheel_scroll(void)
{
msleep(600);
}
void
litest_timeout_edgescroll(void)
{

View file

@ -894,6 +894,9 @@ litest_timeout_softbuttons(void);
void
litest_timeout_buttonscroll(void);
void
litest_timeout_wheel_scroll(void);
void
litest_timeout_edgescroll(void);

View file

@ -867,6 +867,53 @@ START_TEST(pointer_scroll_wheel_hires_send_only_lores_horizontal)
}
END_TEST
START_TEST(pointer_scroll_wheel_inhibit_small_deltas)
{
struct litest_device *dev = litest_current_device();
struct libinput *li = dev->libinput;
if (!libevdev_has_event_code(dev->evdev, EV_REL, REL_WHEEL_HI_RES) &&
!libevdev_has_event_code(dev->evdev, EV_REL, REL_HWHEEL_HI_RES))
return;
litest_drain_events(dev->libinput);
/* Scroll deltas bellow the threshold (60) must be ignored */
litest_event(dev, EV_REL, REL_WHEEL_HI_RES, 15);
litest_event(dev, EV_REL, REL_WHEEL_HI_RES, 15);
litest_event(dev, EV_SYN, SYN_REPORT, 0);
libinput_dispatch(li);
litest_assert_empty_queue(li);
/* The accumulated scroll is 30, add 30 to trigger scroll */
litest_event(dev, EV_REL, REL_WHEEL_HI_RES, 30);
litest_event(dev, EV_SYN, SYN_REPORT, 0);
libinput_dispatch(li);
test_high_and_low_wheel_events_value(dev, REL_WHEEL_HI_RES, -60);
/* Once the threshold is reached, small scroll deltas are reported */
litest_event(dev, EV_REL, REL_WHEEL_HI_RES, 5);
litest_event(dev, EV_SYN, SYN_REPORT, 0);
libinput_dispatch(li);
test_high_and_low_wheel_events_value(dev, REL_WHEEL_HI_RES, -5);
/* When the scroll timeout is triggered, ignore small deltas again */
litest_timeout_wheel_scroll();
litest_event(dev, EV_REL, REL_WHEEL_HI_RES, -15);
litest_event(dev, EV_REL, REL_WHEEL_HI_RES, -15);
litest_event(dev, EV_SYN, SYN_REPORT, 0);
libinput_dispatch(li);
litest_assert_empty_queue(li);
litest_event(dev, EV_REL, REL_HWHEEL_HI_RES, 15);
litest_event(dev, EV_REL, REL_HWHEEL_HI_RES, 15);
litest_event(dev, EV_SYN, SYN_REPORT, 0);
libinput_dispatch(li);
litest_assert_empty_queue(li);
}
END_TEST
START_TEST(pointer_scroll_natural_defaults)
{
struct litest_device *dev = litest_current_device();
@ -3495,6 +3542,7 @@ TEST_COLLECTION(pointer)
litest_add(pointer_scroll_wheel_hires, LITEST_WHEEL, LITEST_TABLET);
litest_add(pointer_scroll_wheel_hires_send_only_lores_vertical, LITEST_WHEEL, LITEST_TABLET);
litest_add(pointer_scroll_wheel_hires_send_only_lores_horizontal, LITEST_WHEEL, LITEST_TABLET);
litest_add(pointer_scroll_wheel_inhibit_small_deltas, LITEST_WHEEL, LITEST_TABLET);
litest_add(pointer_scroll_button, LITEST_RELATIVE|LITEST_BUTTON, LITEST_ANY);
litest_add(pointer_scroll_button_noscroll, LITEST_ABSOLUTE|LITEST_BUTTON, LITEST_RELATIVE);
litest_add(pointer_scroll_button_noscroll, LITEST_ANY, LITEST_RELATIVE|LITEST_BUTTON);