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: <https://gitlab.freedesktop.org/libinput/libinput/-/merge_requests/1261>
This commit is contained in:
Peter Hutterer 2025-07-04 11:03:22 +10:00 committed by Marge Bot
parent ca6b82841c
commit bb05e0d1b5
2 changed files with 30 additions and 9 deletions

View file

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

View file

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