diff options
author | José Expósito <jose.exposito89@gmail.com> | 2021-09-30 17:50:26 +0200 |
---|---|---|
committer | José Expósito <jose.exposito89@gmail.com> | 2021-11-08 18:00:46 +0100 |
commit | d21f1ab7abb792a5bd467ea120e0742fe03f6bb5 (patch) | |
tree | 5c91ba7251c18c2b7fdf4f5e60f4d2926730a835 | |
parent | b6a944bb80a9bb886bf38508e8a2b85be22dda57 (diff) | |
download | libinput-d21f1ab7abb792a5bd467ea120e0742fe03f6bb5.tar.gz |
wheel: accumulate scroll when direction changes
Most mice with high-resolution support have a mechanism in place to
adjust the wheel to a detent. When scrolling, it is possible to stop
between two detents and this mechanism could generate a small amount of
scroll in the oposite direction.
Track the scroll direction in the wheel state machine and reset it when
the direction changes to avoid this issue.
Signed-off-by: José Expósito <jose.exposito89@gmail.com>
-rw-r--r-- | src/evdev-fallback.h | 9 | ||||
-rw-r--r-- | src/evdev-wheel.c | 39 | ||||
-rw-r--r-- | test/test-pointer.c | 41 |
3 files changed, 88 insertions, 1 deletions
diff --git a/src/evdev-fallback.h b/src/evdev-fallback.h index ad41cd3b..0c2f65cb 100644 --- a/src/evdev-fallback.h +++ b/src/evdev-fallback.h @@ -66,6 +66,14 @@ enum wheel_state { WHEEL_STATE_SCROLLING, }; +enum wheel_direction { + WHEEL_DIR_UNKNOW, + WHEEL_DIR_VPOS, + WHEEL_DIR_VNEG, + WHEEL_DIR_HPOS, + WHEEL_DIR_HNEG, +}; + struct mt_slot { bool dirty; enum mt_slot_state state; @@ -111,6 +119,7 @@ struct fallback_dispatch { bool emulate_hi_res_wheel; bool hi_res_event_received; struct libinput_timer scroll_timer; + enum wheel_direction dir; } wheel; struct { diff --git a/src/evdev-wheel.c b/src/evdev-wheel.c index 215cd2db..5ba0c638 100644 --- a/src/evdev-wheel.c +++ b/src/evdev-wheel.c @@ -39,6 +39,7 @@ enum wheel_event { WHEEL_EVENT_SCROLL_ACCUMULATED, WHEEL_EVENT_SCROLL, WHEEL_EVENT_SCROLL_TIMEOUT, + WHEEL_EVENT_SCROLL_DIR_CHANGED, }; static inline const char * @@ -62,6 +63,7 @@ wheel_event_to_str(enum wheel_event event) CASE_RETURN_STRING(WHEEL_EVENT_SCROLL_ACCUMULATED); CASE_RETURN_STRING(WHEEL_EVENT_SCROLL); CASE_RETURN_STRING(WHEEL_EVENT_SCROLL_TIMEOUT); + CASE_RETURN_STRING(WHEEL_EVENT_SCROLL_DIR_CHANGED); } return NULL; } @@ -100,6 +102,8 @@ wheel_handle_event_on_state_none(struct fallback_dispatch *dispatch, case WHEEL_EVENT_SCROLL: dispatch->wheel.state = WHEEL_STATE_ACCUMULATING_SCROLL; break; + case WHEEL_EVENT_SCROLL_DIR_CHANGED: + break; case WHEEL_EVENT_RELEASE: case WHEEL_EVENT_SCROLL_ACCUMULATED: case WHEEL_EVENT_SCROLL_TIMEOUT: @@ -118,6 +122,7 @@ wheel_handle_event_on_state_pressed(struct fallback_dispatch *dispatch, dispatch->wheel.state = WHEEL_STATE_NONE; break; case WHEEL_EVENT_SCROLL: + case WHEEL_EVENT_SCROLL_DIR_CHANGED: /* Ignore scroll while the wheel is pressed */ break; case WHEEL_EVENT_PRESS: @@ -144,6 +149,9 @@ wheel_handle_event_on_state_accumulating_scroll(struct fallback_dispatch *dispat case WHEEL_EVENT_SCROLL: /* Ignore scroll while accumulating deltas */ break; + case WHEEL_EVENT_SCROLL_DIR_CHANGED: + dispatch->wheel.state = WHEEL_STATE_NONE; + break; case WHEEL_EVENT_RELEASE: case WHEEL_EVENT_SCROLL_TIMEOUT: log_wheel_bug(dispatch, event); @@ -168,6 +176,10 @@ wheel_handle_event_on_state_scrolling(struct fallback_dispatch *dispatch, case WHEEL_EVENT_SCROLL_TIMEOUT: dispatch->wheel.state = WHEEL_STATE_NONE; break; + case WHEEL_EVENT_SCROLL_DIR_CHANGED: + wheel_cancel_scroll_timer(dispatch); + dispatch->wheel.state = WHEEL_STATE_NONE; + break; case WHEEL_EVENT_RELEASE: case WHEEL_EVENT_SCROLL_ACCUMULATED: log_wheel_bug(dispatch, event); @@ -330,6 +342,30 @@ wheel_handle_state_scrolling(struct fallback_dispatch *dispatch, wheel_flush_scroll(dispatch, device, time); } +static void +wheel_handle_direction_change(struct fallback_dispatch *dispatch, + struct input_event *e, + uint64_t time) +{ + enum wheel_direction new_dir = WHEEL_DIR_UNKNOW; + + switch (e->code) { + case REL_WHEEL_HI_RES: + new_dir = (e->value > 0) ? WHEEL_DIR_VPOS : WHEEL_DIR_VNEG; + break; + case REL_HWHEEL_HI_RES: + new_dir = (e->value > 0) ? WHEEL_DIR_HPOS : WHEEL_DIR_HNEG; + break; + } + + if (new_dir != WHEEL_DIR_UNKNOW && new_dir != dispatch->wheel.dir) { + dispatch->wheel.dir = new_dir; + wheel_handle_event(dispatch, + WHEEL_EVENT_SCROLL_DIR_CHANGED, + time); + } +} + void fallback_wheel_process_relative(struct fallback_dispatch *dispatch, struct evdev_device *device, @@ -354,12 +390,14 @@ fallback_wheel_process_relative(struct fallback_dispatch *dispatch, dispatch->wheel.hi_res.y += e->value; dispatch->wheel.hi_res_event_received = true; dispatch->pending_event |= EVDEV_WHEEL; + wheel_handle_direction_change(dispatch, e, time); wheel_handle_event(dispatch, WHEEL_EVENT_SCROLL, time); break; case REL_HWHEEL_HI_RES: dispatch->wheel.hi_res.x += e->value; dispatch->wheel.hi_res_event_received = true; dispatch->pending_event |= EVDEV_WHEEL; + wheel_handle_direction_change(dispatch, e, time); wheel_handle_event(dispatch, WHEEL_EVENT_SCROLL, time); break; } @@ -442,6 +480,7 @@ fallback_init_wheel(struct fallback_dispatch *dispatch, char timer_name[64]; dispatch->wheel.state = WHEEL_STATE_NONE; + dispatch->wheel.dir = WHEEL_DIR_UNKNOW; /* On kernel < 5.0 we need to emulate high-resolution wheel scroll events */ diff --git a/test/test-pointer.c b/test/test-pointer.c index 77a63963..4e383858 100644 --- a/test/test-pointer.c +++ b/test/test-pointer.c @@ -796,8 +796,8 @@ START_TEST(pointer_scroll_wheel_hires) test_hi_res_wheel_event(dev, axis, 6 * 120); test_hi_res_wheel_event(dev, axis, 30); - test_hi_res_wheel_event(dev, axis, -40); test_hi_res_wheel_event(dev, axis, -60); + test_hi_res_wheel_event(dev, axis, -40); test_hi_res_wheel_event(dev, axis, 180); } } @@ -914,6 +914,44 @@ START_TEST(pointer_scroll_wheel_inhibit_small_deltas) } END_TEST +START_TEST(pointer_scroll_wheel_inhibit_dir_change) +{ + 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 one detent and a bit */ + litest_event(dev, EV_REL, REL_WHEEL_HI_RES, 120); + 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, -150); + + /* Scroll below the threshold in the oposite direction should be ignored */ + litest_event(dev, EV_REL, REL_WHEEL_HI_RES, -30); + litest_event(dev, EV_SYN, SYN_REPORT, 0); + libinput_dispatch(li); + litest_assert_empty_queue(li); + + /* But should be triggered if the scroll continues in the same direction */ + litest_event(dev, EV_REL, REL_WHEEL_HI_RES, -120); + litest_event(dev, EV_SYN, SYN_REPORT, 0); + libinput_dispatch(li); + test_high_and_low_wheel_events_value(dev, REL_WHEEL_HI_RES, 150); + + /* Scroll above the threshold in the same dir should be triggered */ + litest_event(dev, EV_REL, REL_WHEEL_HI_RES, 80); + litest_event(dev, EV_SYN, SYN_REPORT, 0); + libinput_dispatch(li); + test_high_and_low_wheel_events_value(dev, REL_WHEEL_HI_RES, -80); +} +END_TEST + START_TEST(pointer_scroll_natural_defaults) { struct litest_device *dev = litest_current_device(); @@ -3543,6 +3581,7 @@ TEST_COLLECTION(pointer) 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_wheel_inhibit_dir_change, 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); |