diff --git a/src/evdev-fallback.c b/src/evdev-fallback.c index 19ae4a0a..28d5de99 100644 --- a/src/evdev-fallback.c +++ b/src/evdev-fallback.c @@ -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); diff --git a/src/evdev-fallback.h b/src/evdev-fallback.h index 2338f4a7..ad41cd3b 100644 --- a/src/evdev-fallback.h +++ b/src/evdev-fallback.h @@ -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 { diff --git a/src/evdev-wheel.c b/src/evdev-wheel.c index acdbf861..215cd2db 100644 --- a/src/evdev-wheel.c +++ b/src/evdev-wheel.c @@ -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); } diff --git a/test/litest.c b/test/litest.c index 187dd345..69d5154b 100644 --- a/test/litest.c +++ b/test/litest.c @@ -4299,6 +4299,12 @@ litest_timeout_finger_switch(void) msleep(120); } +void +litest_timeout_wheel_scroll(void) +{ + msleep(600); +} + void litest_timeout_edgescroll(void) { diff --git a/test/litest.h b/test/litest.h index b6ffbf4e..990161b9 100644 --- a/test/litest.h +++ b/test/litest.h @@ -894,6 +894,9 @@ litest_timeout_softbuttons(void); void litest_timeout_buttonscroll(void); +void +litest_timeout_wheel_scroll(void); + void litest_timeout_edgescroll(void); diff --git a/test/test-pointer.c b/test/test-pointer.c index 218fe074..77a63963 100644 --- a/test/test-pointer.c +++ b/test/test-pointer.c @@ -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);