From bb05e0d1b5623d240b4e07f9a481fedfaeebd2cb Mon Sep 17 00:00:00 2001 From: Peter Hutterer Date: Fri, 4 Jul 2025 11:03:22 +1000 Subject: [PATCH] plugin/wheel: don't accumulate for low HID resolution multipliers Accumulating (and partially discarding) scroll wheel events serves to debounce a scroll wheel and provide for more fluid scrolling where scroll wheels can send events going in the wrong direction. This is unlikely to happen on devices with low resolution multipliers (i.e. where some significant physical movement by the wheel is required to trigger events) so let's make it contingent on devices more likely to have flaky wheels. The magic threshold picked is 30 (HID resolution multiplier of 4) as a guess. The resolution multiplier isn't accessible in userspace so we have to heuristically get to it - typical interaction with a mouse will have that value set within the first two, three scroll wheel events though. Closes #1150 Part-of: --- src/libinput-plugin-mouse-wheel.c | 33 +++++++++++++++++++++++++------ test/test-pointer.c | 6 +++--- 2 files changed, 30 insertions(+), 9 deletions(-) diff --git a/src/libinput-plugin-mouse-wheel.c b/src/libinput-plugin-mouse-wheel.c index 6d8f4547..8285cfad 100644 --- a/src/libinput-plugin-mouse-wheel.c +++ b/src/libinput-plugin-mouse-wheel.c @@ -36,6 +36,7 @@ #include "libinput-plugin.h" #include "libinput-util.h" +#define ACC_V120_TRIGGER_THRESHOLD 30 /* 1/4 of a wheel detent */ #define ACC_V120_THRESHOLD 59 #define WHEEL_SCROLL_TIMEOUT ms2us(500) @@ -60,6 +61,12 @@ enum wheel_event { WHEEL_EVENT_SCROLL_DIR_CHANGED, }; +enum ignore_strategy { + MAYBE, /* use heuristics but don't yet accumulate */ + ACCUMULATE, /* accumulate scroll wheel events */ + PASSTHROUGH, /* do not accumulate, pass through */ +}; + struct plugin_device { struct list link; struct plugin_data *parent; @@ -71,7 +78,7 @@ struct plugin_device { bool hi_res_event_received; struct libinput_plugin_timer *scroll_timer; enum wheel_direction dir; - bool ignore_small_hi_res_movements; + enum ignore_strategy ignore_small_hi_res_movements; int min_movement; struct ratelimit hires_warning_limit; @@ -139,7 +146,7 @@ wheel_handle_event_on_state_none(struct plugin_device *pd, { switch (event) { case WHEEL_EVENT_SCROLL: - pd->state = pd->ignore_small_hi_res_movements + pd->state = pd->ignore_small_hi_res_movements == ACCUMULATE ? WHEEL_STATE_ACCUMULATING_SCROLL : WHEEL_STATE_SCROLLING; break; @@ -334,6 +341,19 @@ wheel_handle_direction_change(struct plugin_device *pd, } } +static inline void +wheel_update_strategy(struct plugin_device *pd, int32_t value) +{ + pd->min_movement = min(pd->min_movement, abs(value)); + + /* Only if a wheel sends movements less than the trigger threshold + * activate the accumulation and debouncing of scroll directions, etc. + */ + if (pd->ignore_small_hi_res_movements == MAYBE && + pd->min_movement < ACC_V120_TRIGGER_THRESHOLD) + pd->ignore_small_hi_res_movements = ACCUMULATE; +} + static void wheel_process_relative(struct plugin_device *pd, struct evdev_event *e, uint64_t time) { @@ -349,14 +369,14 @@ wheel_process_relative(struct plugin_device *pd, struct evdev_event *e, uint64_t case EVDEV_REL_WHEEL_HI_RES: pd->hi_res.y += e->value; pd->hi_res_event_received = true; - pd->min_movement = min(pd->min_movement, abs(e->value)); + wheel_update_strategy(pd, e->value); wheel_handle_direction_change(pd, e, time); wheel_handle_event(pd, WHEEL_EVENT_SCROLL, time); break; case EVDEV_REL_HWHEEL_HI_RES: pd->hi_res.x += e->value; pd->hi_res_event_received = true; - pd->min_movement = min(pd->min_movement, abs(e->value)); + wheel_update_strategy(pd, e->value); wheel_handle_direction_change(pd, e, time); wheel_handle_event(pd, WHEEL_EVENT_SCROLL, time); break; @@ -414,11 +434,12 @@ wheel_plugin_device_create(struct libinput_plugin *libinput_plugin, pd->device = libinput_device_ref(device); pd->state = WHEEL_STATE_NONE; pd->dir = WHEEL_DIR_UNKNOW; - pd->ignore_small_hi_res_movements = !evdev_device_is_virtual(evdev); + pd->ignore_small_hi_res_movements = + evdev_device_is_virtual(evdev) ? PASSTHROUGH : MAYBE; pd->min_movement = ACC_V120_THRESHOLD; ratelimit_init(&pd->hires_warning_limit, s2us(24 * 60 * 60), 1); - if (pd->ignore_small_hi_res_movements) { + if (pd->ignore_small_hi_res_movements != PASSTHROUGH) { pd->scroll_timer = libinput_plugin_timer_new(libinput_plugin, libinput_device_get_sysname(device), diff --git a/test/test-pointer.c b/test/test-pointer.c index 725f4043..1f6b4902 100644 --- a/test/test-pointer.c +++ b/test/test-pointer.c @@ -896,8 +896,8 @@ START_TEST(pointer_scroll_wheel_inhibit_small_deltas_reduce_delta) litest_drain_events(dev->libinput); - /* A single delta (below the hardcoded threshold 60) is ignored */ - litest_event(dev, EV_REL, REL_WHEEL_HI_RES, 50); + /* A single delta (below the hardcoded threshold 30) is ignored */ + litest_event(dev, EV_REL, REL_WHEEL_HI_RES, 29); litest_event(dev, EV_SYN, SYN_REPORT, 0); litest_dispatch(li); litest_assert_empty_queue(li); @@ -907,7 +907,7 @@ START_TEST(pointer_scroll_wheel_inhibit_small_deltas_reduce_delta) litest_event(dev, EV_SYN, SYN_REPORT, 0); litest_dispatch(li); - test_high_and_low_wheel_events_value(dev, REL_WHEEL_HI_RES, -55); + test_high_and_low_wheel_events_value(dev, REL_WHEEL_HI_RES, -34); litest_timeout_wheel_scroll(li);