filter: differentiate scroll wheel from button scrolling to fix wheel speed

Commit 94b7836456 ("filter: support accelerating high-resolution
scroll wheel events") introduced a regression where high-res scroll
wheel events were incorrectly normalized by DPI. Mice with non-default
DPI (e.g., Logitech G502 at 2400 DPI) had their scroll wheel speed
reduced by the DPI ratio (1000/2400), resulting in 2-3x slower
scrolling.

The "noop" filter functions were actually performing DPI normalization
or applying a constant acceleration factor, which is appropriate for
button scrolling but incorrect for scroll wheels that have their own
units.

Add a filter_scroll_type enum (CONTINOUS, WHEEL, FINGER to match the
public events) passed through the filter_scroll interface. Update all
filter implementations to skip acceleration and normalization for wheel
events while maintaining existing behavior for button scrolling and
touchpad scrolling.

The custom acceleration profile continues to accelerate high-res wheel
events as designed.

Fixes: 94b7836456 ("filter: support accelerating high-resolution scroll wheel events")

Closes: #1212

Signed-off-by: Yinon Burgansky <yinonburgansky@gmail.com>
Part-of: <https://gitlab.freedesktop.org/libinput/libinput/-/merge_requests/1363>
This commit is contained in:
Yinon Burgansky 2025-11-10 14:57:24 +02:00 committed by Peter Hutterer
parent f6caae2289
commit a12dc6eba3
14 changed files with 233 additions and 47 deletions

View file

@ -117,7 +117,8 @@ post_button_scroll(struct evdev_device *device,
filter_dispatch_scroll(device->pointer.filter,
&raw,
device,
time);
time,
FILTER_SCROLL_TYPE_CONTINUOUS);
evdev_post_scroll(device,
time,
LIBINPUT_POINTER_AXIS_SOURCE_CONTINUOUS,
@ -221,7 +222,8 @@ fallback_flush_wheels(struct fallback_dispatch *dispatch,
filter_dispatch_scroll(device->pointer.filter,
&raw,
device,
time);
time,
FILTER_SCROLL_TYPE_WHEEL);
evdev_post_scroll(device,
time,
LIBINPUT_POINTER_AXIS_SOURCE_CONTINUOUS,
@ -245,7 +247,8 @@ fallback_flush_wheels(struct fallback_dispatch *dispatch,
? filter_dispatch_scroll(device->pointer.filter,
&v120_unaccelerated,
device,
time)
time,
FILTER_SCROLL_TYPE_WHEEL)
: (const struct normalized_coords){
.x = v120_unaccelerated.x,
.y = v120_unaccelerated.y

View file

@ -107,7 +107,11 @@ tp_filter_scroll(struct tp_dispatch *tp,
/* Convert to device units with x/y in the same resolution */
raw = tp_scale_to_xaxis(tp, *unaccelerated);
return filter_dispatch_scroll(tp->device->pointer.filter, &raw, tp, time);
return filter_dispatch_scroll(tp->device->pointer.filter,
&raw,
tp,
time,
FILTER_SCROLL_TYPE_FINGER);
}
static inline void

View file

@ -400,7 +400,8 @@ static struct normalized_coords
custom_accelerator_filter_scroll(struct motion_filter *filter,
const struct device_float_coords *unaccelerated,
void *data,
uint64_t time)
uint64_t time,
enum filter_scroll_type type)
{
return custom_accelerator_filter(LIBINPUT_ACCEL_TYPE_SCROLL,
filter,

View file

@ -62,7 +62,7 @@ accelerator_filter_flat(struct motion_filter *filter,
}
static struct normalized_coords
accelerator_filter_noop_flat(struct motion_filter *filter,
accelerator_filter_constant_flat(struct motion_filter *filter,
const struct device_float_coords *unaccelerated,
void *data,
uint64_t time)
@ -81,6 +81,27 @@ accelerator_filter_noop_flat(struct motion_filter *filter,
return accelerator_filter_flat(filter, unaccelerated, data, time);
}
static struct normalized_coords
accelerator_filter_scroll_flat(struct motion_filter *filter,
const struct device_float_coords *unaccelerated,
void *data,
uint64_t time,
enum filter_scroll_type type)
{
/* Scroll wheels were not historically accelerated and have different
* units than button scrolling. Maintain the status quo and do not
* accelerate wheel events.
*/
if (type == FILTER_SCROLL_TYPE_WHEEL) {
return (struct normalized_coords){
.x = unaccelerated->x,
.y = unaccelerated->y,
};
}
return accelerator_filter_constant_flat(filter, unaccelerated, data, time);
}
static bool
accelerator_set_speed_flat(struct motion_filter *filter, double speed_adjustment)
{
@ -112,8 +133,8 @@ accelerator_destroy_flat(struct motion_filter *filter)
static const struct motion_filter_interface accelerator_interface_flat = {
.type = LIBINPUT_CONFIG_ACCEL_PROFILE_FLAT,
.filter = accelerator_filter_flat,
.filter_constant = accelerator_filter_noop_flat,
.filter_scroll = accelerator_filter_noop_flat,
.filter_constant = accelerator_filter_constant_flat,
.filter_scroll = accelerator_filter_scroll_flat,
.restart = NULL,
.destroy = accelerator_destroy_flat,
.set_speed = accelerator_set_speed_flat,

View file

@ -145,7 +145,7 @@ accelerator_filter_low_dpi(struct motion_filter *filter,
}
static struct normalized_coords
accelerator_filter_noop(struct motion_filter *filter,
accelerator_filter_constant(struct motion_filter *filter,
const struct device_float_coords *unaccelerated,
void *data,
uint64_t time)
@ -157,6 +157,16 @@ accelerator_filter_noop(struct motion_filter *filter,
return normalized;
}
static struct normalized_coords
accelerator_filter_scroll(struct motion_filter *filter,
const struct device_float_coords *unaccelerated,
void *data,
uint64_t time,
enum filter_scroll_type type)
{
return accelerator_filter_constant(filter, unaccelerated, data, time);
}
static void
accelerator_restart(struct motion_filter *filter, void *data, uint64_t time)
{
@ -205,8 +215,8 @@ accelerator_set_speed(struct motion_filter *filter, double speed_adjustment)
static const struct motion_filter_interface accelerator_interface_low_dpi = {
.type = LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE,
.filter = accelerator_filter_low_dpi,
.filter_constant = accelerator_filter_noop,
.filter_scroll = accelerator_filter_noop,
.filter_constant = accelerator_filter_constant,
.filter_scroll = accelerator_filter_scroll,
.restart = accelerator_restart,
.destroy = accelerator_destroy,
.set_speed = accelerator_set_speed,

View file

@ -133,7 +133,7 @@ accelerator_filter_linear(struct motion_filter *filter,
* motion
*/
static struct normalized_coords
accelerator_filter_noop(struct motion_filter *filter,
accelerator_filter_constant(struct motion_filter *filter,
const struct device_float_coords *unaccelerated,
void *data,
uint64_t time)
@ -143,6 +143,27 @@ accelerator_filter_noop(struct motion_filter *filter,
return normalize_for_dpi(unaccelerated, accel->dpi);
}
static struct normalized_coords
accelerator_filter_scroll(struct motion_filter *filter,
const struct device_float_coords *unaccelerated,
void *data,
uint64_t time,
enum filter_scroll_type type)
{
/* Scroll wheels were not historically accelerated and have different
* units than button scrolling. Maintain the status quo and do not
* accelerate wheel events.
*/
if (type == FILTER_SCROLL_TYPE_WHEEL) {
return (struct normalized_coords){
.x = unaccelerated->x,
.y = unaccelerated->y,
};
}
return accelerator_filter_constant(filter, unaccelerated, data, time);
}
static void
accelerator_restart(struct motion_filter *filter, void *data, uint64_t time)
{
@ -260,8 +281,8 @@ pointer_accel_profile_linear(struct motion_filter *filter,
static const struct motion_filter_interface accelerator_interface = {
.type = LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE,
.filter = accelerator_filter_linear,
.filter_constant = accelerator_filter_noop,
.filter_scroll = accelerator_filter_noop,
.filter_constant = accelerator_filter_constant,
.filter_scroll = accelerator_filter_scroll,
.restart = accelerator_restart,
.destroy = accelerator_destroy,
.set_speed = accelerator_set_speed,

View file

@ -44,7 +44,8 @@ struct motion_filter_interface {
struct motion_filter *filter,
const struct device_float_coords *unaccelerated,
void *data,
uint64_t time);
uint64_t time,
enum filter_scroll_type type);
void (*restart)(struct motion_filter *filter, void *data, uint64_t time);
void (*destroy)(struct motion_filter *filter);
bool (*set_speed)(struct motion_filter *filter, double speed_adjustment);

View file

@ -65,7 +65,8 @@ accelerator_filter_touchpad_flat(struct motion_filter *filter,
}
static struct normalized_coords
accelerator_filter_noop_touchpad_flat(struct motion_filter *filter,
accelerator_filter_constant_touchpad_flat(
struct motion_filter *filter,
const struct device_float_coords *unaccelerated,
void *data,
uint64_t time)
@ -84,6 +85,30 @@ accelerator_filter_noop_touchpad_flat(struct motion_filter *filter,
return accelerator_filter_touchpad_flat(filter, unaccelerated, data, time);
}
static struct normalized_coords
accelerator_filter_scroll_touchpad_flat(struct motion_filter *filter,
const struct device_float_coords *unaccelerated,
void *data,
uint64_t time,
enum filter_scroll_type type)
{
/* Scroll wheels were not historically accelerated and have different
* units than button scrolling. Maintain the status quo and do not
* accelerate wheel events.
*/
if (type == FILTER_SCROLL_TYPE_WHEEL) {
return (struct normalized_coords){
.x = unaccelerated->x,
.y = unaccelerated->y,
};
}
return accelerator_filter_constant_touchpad_flat(filter,
unaccelerated,
data,
time);
}
static bool
accelerator_set_speed_touchpad_flat(struct motion_filter *filter,
double speed_adjustment)
@ -111,8 +136,8 @@ accelerator_destroy_touchpad_flat(struct motion_filter *filter)
static const struct motion_filter_interface accelerator_interface_touchpad_flat = {
.type = LIBINPUT_CONFIG_ACCEL_PROFILE_FLAT,
.filter = accelerator_filter_touchpad_flat,
.filter_constant = accelerator_filter_noop_touchpad_flat,
.filter_scroll = accelerator_filter_noop_touchpad_flat,
.filter_constant = accelerator_filter_constant_touchpad_flat,
.filter_scroll = accelerator_filter_scroll_touchpad_flat,
.restart = NULL,
.destroy = accelerator_destroy_touchpad_flat,
.set_speed = accelerator_set_speed_touchpad_flat,

View file

@ -185,6 +185,27 @@ accelerator_filter_constant_x230(struct motion_filter *filter,
return normalized;
}
static struct normalized_coords
accelerator_filter_scroll_x230(struct motion_filter *filter,
const struct device_float_coords *unaccelerated,
void *data,
uint64_t time,
enum filter_scroll_type type)
{
/* Scroll wheels were not historically accelerated and have different
* units than button scrolling. Maintain the status quo and do not
* accelerate wheel events.
*/
if (type == FILTER_SCROLL_TYPE_WHEEL) {
return (struct normalized_coords){
.x = unaccelerated->x,
.y = unaccelerated->y,
};
}
return accelerator_filter_constant_x230(filter, unaccelerated, data, time);
}
static void
accelerator_restart_x230(struct motion_filter *filter, void *data, uint64_t time)
{
@ -285,7 +306,7 @@ static const struct motion_filter_interface accelerator_interface_x230 = {
.type = LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE,
.filter = accelerator_filter_x230,
.filter_constant = accelerator_filter_constant_x230,
.filter_scroll = accelerator_filter_constant_x230,
.filter_scroll = accelerator_filter_scroll_x230,
.restart = accelerator_restart_x230,
.destroy = accelerator_destroy_x230,
.set_speed = accelerator_set_speed_x230,

View file

@ -162,6 +162,27 @@ touchpad_constant_filter(struct motion_filter *filter,
return normalized;
}
static struct normalized_coords
touchpad_scroll_filter(struct motion_filter *filter,
const struct device_float_coords *unaccelerated,
void *data,
uint64_t time,
enum filter_scroll_type type)
{
/* Scroll wheels were not historically accelerated and have different
* units than button scrolling. Maintain the status quo and do not
* accelerate wheel events.
*/
if (type == FILTER_SCROLL_TYPE_WHEEL) {
return (struct normalized_coords){
.x = unaccelerated->x,
.y = unaccelerated->y,
};
}
return touchpad_constant_filter(filter, unaccelerated, data, time);
}
static void
touchpad_accelerator_restart(struct motion_filter *filter, void *data, uint64_t time)
{
@ -272,7 +293,7 @@ static const struct motion_filter_interface accelerator_interface_touchpad = {
.type = LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE,
.filter = accelerator_filter_touchpad,
.filter_constant = touchpad_constant_filter,
.filter_scroll = touchpad_constant_filter,
.filter_scroll = touchpad_scroll_filter,
.restart = touchpad_accelerator_restart,
.destroy = touchpad_accelerator_destroy,
.set_speed = touchpad_accelerator_set_speed,

View file

@ -60,7 +60,7 @@ trackpoint_flat_filter(struct motion_filter *filter,
}
static struct normalized_coords
trackpoint_flat_filter_noop(struct motion_filter *filter,
trackpoint_flat_filter_constant(struct motion_filter *filter,
const struct device_float_coords *unaccelerated,
void *data,
uint64_t time)
@ -79,6 +79,27 @@ trackpoint_flat_filter_noop(struct motion_filter *filter,
return trackpoint_flat_filter(filter, unaccelerated, data, time);
}
static struct normalized_coords
trackpoint_flat_filter_scroll(struct motion_filter *filter,
const struct device_float_coords *unaccelerated,
void *data,
uint64_t time,
enum filter_scroll_type type)
{
/* Scroll wheels were not historically accelerated and have different
* units than button scrolling. Maintain the status quo and do not
* accelerate wheel events.
*/
if (type == FILTER_SCROLL_TYPE_WHEEL) {
return (struct normalized_coords){
.x = unaccelerated->x,
.y = unaccelerated->y,
};
}
return trackpoint_flat_filter_constant(filter, unaccelerated, data, time);
}
/* Maps the [-1, 1] speed setting into a constant acceleration
* range. This isn't a linear scale, we keep 0 as the 'optimized'
* mid-point and scale down to 0 for setting -1 and up to 5 for
@ -128,8 +149,8 @@ trackpoint_flat_destroy(struct motion_filter *filter)
static struct motion_filter_interface accelerator_interface_flat = {
.type = LIBINPUT_CONFIG_ACCEL_PROFILE_FLAT,
.filter = trackpoint_flat_filter,
.filter_constant = trackpoint_flat_filter_noop,
.filter_scroll = trackpoint_flat_filter_noop,
.filter_constant = trackpoint_flat_filter_constant,
.filter_scroll = trackpoint_flat_filter_scroll,
.restart = NULL,
.destroy = trackpoint_flat_destroy,
.set_speed = trackpoint_flat_set_speed,

View file

@ -97,7 +97,7 @@ trackpoint_accelerator_filter(struct motion_filter *filter,
}
static struct normalized_coords
trackpoint_accelerator_filter_noop(struct motion_filter *filter,
trackpoint_accelerator_filter_constant(struct motion_filter *filter,
const struct device_float_coords *unaccelerated,
void *data,
uint64_t time)
@ -112,6 +112,30 @@ trackpoint_accelerator_filter_noop(struct motion_filter *filter,
return coords;
}
static struct normalized_coords
trackpoint_accelerator_filter_scroll(struct motion_filter *filter,
const struct device_float_coords *unaccelerated,
void *data,
uint64_t time,
enum filter_scroll_type type)
{
/* Scroll wheels were not historically accelerated and have different
* units than button scrolling. Maintain the status quo and do not
* accelerate wheel events.
*/
if (type == FILTER_SCROLL_TYPE_WHEEL) {
return (struct normalized_coords){
.x = unaccelerated->x,
.y = unaccelerated->y,
};
}
return trackpoint_accelerator_filter_constant(filter,
unaccelerated,
data,
time);
}
/* Maps the [-1, 1] speed setting into a constant acceleration
* range. This isn't a linear scale, we keep 0 as the 'optimized'
* mid-point and scale down to 0 for setting -1 and up to 5 for
@ -170,8 +194,8 @@ trackpoint_accelerator_destroy(struct motion_filter *filter)
static const struct motion_filter_interface accelerator_interface_trackpoint = {
.type = LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE,
.filter = trackpoint_accelerator_filter,
.filter_constant = trackpoint_accelerator_filter_noop,
.filter_scroll = trackpoint_accelerator_filter_noop,
.filter_constant = trackpoint_accelerator_filter_constant,
.filter_scroll = trackpoint_accelerator_filter_scroll,
.restart = trackpoint_accelerator_restart,
.destroy = trackpoint_accelerator_destroy,
.set_speed = trackpoint_accelerator_set_speed,

View file

@ -59,9 +59,14 @@ struct normalized_coords
filter_dispatch_scroll(struct motion_filter *filter,
const struct device_float_coords *unaccelerated,
void *data,
uint64_t time)
uint64_t time,
enum filter_scroll_type type)
{
return filter->interface->filter_scroll(filter, unaccelerated, data, time);
return filter->interface->filter_scroll(filter,
unaccelerated,
data,
time,
type);
}
void

View file

@ -34,6 +34,12 @@
struct motion_filter;
enum filter_scroll_type {
FILTER_SCROLL_TYPE_CONTINUOUS,
FILTER_SCROLL_TYPE_WHEEL,
FILTER_SCROLL_TYPE_FINGER,
};
/**
* Accelerate the given coordinates.
* Takes a set of unaccelerated deltas and accelerates them based on the
@ -98,6 +104,7 @@ filter_dispatch_constant(struct motion_filter *filter,
* originally provided resolution.
* @param data Custom data
* @param time The time of the delta
* @param type The type of scroll event
*
* @see filter_dispatch
*/
@ -105,7 +112,8 @@ struct normalized_coords
filter_dispatch_scroll(struct motion_filter *filter,
const struct device_float_coords *unaccelerated,
void *data,
uint64_t time);
uint64_t time,
enum filter_scroll_type type);
void
filter_restart(struct motion_filter *filter, void *data, uint64_t time);