plugin: ignore high-resolution wheel events from disabled wheels

Make sure we drop any potential high-resolution wheel events from a
device that isn't supposed to have them.

Where the device's axes were disabled due to a quirk, re-enabling the
axes means the device's events won't be filtered anymore. Our wheel
emulation plugin thus emulates high-resolution wheel events in addition
to the hardware events.

Fix this by simply filtering out any high-resolution wheel events on any
device that uses this plugin.

Closes #1160

Part-of: <https://gitlab.freedesktop.org/libinput/libinput/-/merge_requests/1279>
This commit is contained in:
Peter Hutterer 2025-07-21 14:00:14 +10:00
parent 6aefc2f166
commit 36b2afae82
5 changed files with 157 additions and 2 deletions

View file

@ -842,6 +842,7 @@ if get_option('tests')
'test/litest-device-magic-trackpad.c', 'test/litest-device-magic-trackpad.c',
'test/litest-device-mouse.c', 'test/litest-device-mouse.c',
'test/litest-device-mouse-wheel-tilt.c', 'test/litest-device-mouse-wheel-tilt.c',
'test/litest-device-mouse-wheel-hires-disabled.c',
'test/litest-device-mouse-ps2.c', 'test/litest-device-mouse-ps2.c',
'test/litest-device-mouse-roccat.c', 'test/litest-device-mouse-roccat.c',
'test/litest-device-mouse-low-dpi.c', 'test/litest-device-mouse-low-dpi.c',

View file

@ -64,24 +64,43 @@ wheel_plugin_evdev_frame(struct libinput_plugin *libinput_plugin,
size_t nevents; size_t nevents;
struct evdev_event *events = evdev_frame_get_events(frame, &nevents); struct evdev_event *events = evdev_frame_get_events(frame, &nevents);
_unref_(evdev_frame) *filtered_frame = evdev_frame_new(nevents + 2);
for (size_t i = 0; i < nevents; i++) { for (size_t i = 0; i < nevents; i++) {
struct evdev_event *e = &events[i]; struct evdev_event *e = &events[i];
switch (evdev_usage_enum(e->usage)) { switch (evdev_usage_enum(e->usage)) {
case EVDEV_REL_WHEEL_HI_RES:
case EVDEV_REL_HWHEEL_HI_RES:
/* In the uncommon case that our device sends high-res events
* filter those out. This can happen on devices that have the
* highres scroll axes disabled via quirks. The device still
* sends events so when we re-enable the axis in
* wheel_plugin_device_new we get the device events again,
* effectively duplicating the high resolution scroll events.
*/
break;
case EVDEV_REL_WHEEL: case EVDEV_REL_WHEEL:
evdev_frame_append_one(frame, evdev_frame_append(filtered_frame, e, 1);
evdev_frame_append_one(filtered_frame,
evdev_usage_from(EVDEV_REL_WHEEL_HI_RES), evdev_usage_from(EVDEV_REL_WHEEL_HI_RES),
e->value * 120); e->value * 120);
break; break;
case EVDEV_REL_HWHEEL: case EVDEV_REL_HWHEEL:
evdev_frame_append(filtered_frame, e, 1);
evdev_frame_append_one( evdev_frame_append_one(
frame, filtered_frame,
evdev_usage_from(EVDEV_REL_HWHEEL_HI_RES), evdev_usage_from(EVDEV_REL_HWHEEL_HI_RES),
e->value * 120); e->value * 120);
break; break;
default: default:
evdev_frame_append(filtered_frame, e, 1);
break; break;
} }
} }
evdev_frame_set(frame,
evdev_frame_get_events(filtered_frame, NULL),
evdev_frame_get_count(filtered_frame));
} }
static const struct libinput_plugin_interface interface = { static const struct libinput_plugin_interface interface = {

View file

@ -0,0 +1,63 @@
/*
* Copyright © 2013 Red Hat, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice (including the next
* paragraph) shall be included in all copies or substantial portions of the
* Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*/
#include "config.h"
#include "litest-int.h"
#include "litest.h"
static struct input_id input_id = {
.bustype = 0x3,
.vendor = 0x1234,
.product = 0xabcd,
};
/* clang-format off */
static int events[] = {
EV_KEY, BTN_LEFT,
EV_KEY, BTN_RIGHT,
EV_KEY, BTN_MIDDLE,
EV_REL, REL_X,
EV_REL, REL_Y,
EV_REL, REL_WHEEL,
EV_REL, REL_WHEEL_HI_RES,
EV_REL, REL_HWHEEL,
EV_REL, REL_HWHEEL_HI_RES,
-1 , -1,
};
/* clang-format on */
static const char quirk_file[] =
"[litest hires wheel disabled mouse]\n"
"MatchName=litest Mouse with disabled high-res wheels\n"
"AttrEventCode=-REL_WHEEL_HI_RES;-REL_HWHEEL_HI_RES\n";
TEST_DEVICE(LITEST_MOUSE_WHEEL_HIRES_DISABLED,
.features = LITEST_RELATIVE | LITEST_BUTTON | LITEST_WHEEL,
.interface = NULL,
.name = "Mouse with disabled high-res wheels",
.id = &input_id,
.absinfo = NULL,
.events = events,
.quirk_file = quirk_file, )

View file

@ -477,6 +477,7 @@ enum litest_device_type {
LITEST_MOUSE_WHEEL_CLICK_ANGLE, LITEST_MOUSE_WHEEL_CLICK_ANGLE,
LITEST_MOUSE_WHEEL_CLICK_COUNT, LITEST_MOUSE_WHEEL_CLICK_COUNT,
LITEST_MOUSE_WHEEL_TILT, LITEST_MOUSE_WHEEL_TILT,
LITEST_MOUSE_WHEEL_HIRES_DISABLED,
LITEST_MS_NANO_TRANSCEIVER_MOUSE, LITEST_MS_NANO_TRANSCEIVER_MOUSE,
LITEST_SONY_VAIO_KEYS, LITEST_SONY_VAIO_KEYS,
LITEST_SYNAPTICS_TRACKPOINT_BUTTONS, LITEST_SYNAPTICS_TRACKPOINT_BUTTONS,

View file

@ -794,6 +794,9 @@ START_TEST(pointer_scroll_wheel_hires)
test_hi_res_wheel_event(dev, axis, -5 * 120); test_hi_res_wheel_event(dev, axis, -5 * 120);
test_hi_res_wheel_event(dev, axis, 6 * 120); test_hi_res_wheel_event(dev, axis, 6 * 120);
if (dev->which == LITEST_MOUSE_WHEEL_HIRES_DISABLED)
return LITEST_NOT_APPLICABLE;
test_hi_res_wheel_event(dev, axis, 30); test_hi_res_wheel_event(dev, axis, 30);
test_hi_res_wheel_event(dev, axis, -60); test_hi_res_wheel_event(dev, axis, -60);
test_hi_res_wheel_event(dev, axis, -40); test_hi_res_wheel_event(dev, axis, -40);
@ -855,6 +858,64 @@ START_TEST(pointer_scroll_wheel_hires_send_only_lores)
} }
END_TEST END_TEST
START_TEST(pointer_scroll_wheel_hires_disabled)
{
struct litest_device *dev = litest_current_device();
struct libinput *li = dev->libinput;
int direction;
unsigned int lores_code, hires_code;
enum libinput_pointer_axis axis =
litest_test_param_get_i32(test_env->params, "axis");
switch (axis) {
case LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL:
lores_code = REL_WHEEL;
hires_code = REL_WHEEL_HI_RES;
direction = -1;
break;
case LIBINPUT_POINTER_AXIS_SCROLL_HORIZONTAL:
lores_code = REL_HWHEEL;
hires_code = REL_HWHEEL_HI_RES;
direction = 1;
break;
default:
litest_abort_msg("Invalid test axis '%d'", axis);
}
litest_drain_events(li);
litest_log_group("High-res events on this device should be ignored") {
for (size_t i = 0; i < 4; i++) {
litest_event(dev, EV_REL, hires_code, 60);
litest_event(dev, EV_SYN, SYN_REPORT, 0);
}
litest_assert_empty_queue(li);
}
litest_log_group("Only low-res events should be handled") {
for (size_t i = 0; i < 4; i++) {
litest_event(dev, EV_REL, hires_code, 60);
litest_event(dev, EV_REL, lores_code, 1);
litest_event(dev, EV_SYN, SYN_REPORT, 0);
litest_dispatch(li);
litest_drain_events_of_type(li, LIBINPUT_EVENT_POINTER_AXIS);
_destroy_(libinput_event) *ev = libinput_get_event(li);
struct libinput_event_pointer *pev = litest_is_axis_event(
ev,
LIBINPUT_EVENT_POINTER_SCROLL_WHEEL,
axis,
0);
int v120 =
libinput_event_pointer_get_scroll_value_v120(pev, axis);
litest_assert_int_eq(v120, direction * 120);
}
litest_drain_events_of_type(li, LIBINPUT_EVENT_POINTER_AXIS);
litest_assert_empty_queue(li);
}
}
END_TEST
START_TEST(pointer_scroll_wheel_inhibit_small_deltas) START_TEST(pointer_scroll_wheel_inhibit_small_deltas)
{ {
struct litest_device *dev = litest_current_device(); struct litest_device *dev = litest_current_device();
@ -865,6 +926,9 @@ START_TEST(pointer_scroll_wheel_inhibit_small_deltas)
!libevdev_has_event_code(dev->evdev, EV_REL, REL_HWHEEL_HI_RES)) !libevdev_has_event_code(dev->evdev, EV_REL, REL_HWHEEL_HI_RES))
return LITEST_NOT_APPLICABLE; return LITEST_NOT_APPLICABLE;
if (dev->which == LITEST_MOUSE_WHEEL_HIRES_DISABLED)
return LITEST_NOT_APPLICABLE;
litest_drain_events(dev->libinput); litest_drain_events(dev->libinput);
/* A single delta (below the hardcoded threshold 60) is ignored */ /* A single delta (below the hardcoded threshold 60) is ignored */
@ -910,6 +974,9 @@ START_TEST(pointer_scroll_wheel_inhibit_small_deltas_reduce_delta)
!libevdev_has_event_code(dev->evdev, EV_REL, REL_HWHEEL_HI_RES)) !libevdev_has_event_code(dev->evdev, EV_REL, REL_HWHEEL_HI_RES))
return LITEST_NOT_APPLICABLE; return LITEST_NOT_APPLICABLE;
if (dev->which == LITEST_MOUSE_WHEEL_HIRES_DISABLED)
return LITEST_NOT_APPLICABLE;
litest_drain_events(dev->libinput); litest_drain_events(dev->libinput);
/* A single delta (below the hardcoded threshold 30) is ignored */ /* A single delta (below the hardcoded threshold 30) is ignored */
@ -959,6 +1026,9 @@ START_TEST(pointer_scroll_wheel_inhibit_dir_change)
if (!libevdev_has_event_code(dev->evdev, EV_REL, REL_WHEEL_HI_RES)) if (!libevdev_has_event_code(dev->evdev, EV_REL, REL_WHEEL_HI_RES))
return LITEST_NOT_APPLICABLE; return LITEST_NOT_APPLICABLE;
if (dev->which == LITEST_MOUSE_WHEEL_HIRES_DISABLED)
return LITEST_NOT_APPLICABLE;
litest_drain_events(dev->libinput); litest_drain_events(dev->libinput);
/* Scroll one detent and a bit */ /* Scroll one detent and a bit */
@ -3707,6 +3777,7 @@ TEST_COLLECTION(pointer)
litest_with_parameters(params, "axis", 'I', 2, litest_named_i32(LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL, "vertical"), litest_with_parameters(params, "axis", 'I', 2, litest_named_i32(LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL, "vertical"),
litest_named_i32(LIBINPUT_POINTER_AXIS_SCROLL_HORIZONTAL, "horizontal")) { litest_named_i32(LIBINPUT_POINTER_AXIS_SCROLL_HORIZONTAL, "horizontal")) {
litest_add_parametrized(pointer_scroll_wheel_hires_send_only_lores, LITEST_WHEEL, LITEST_TABLET, params); litest_add_parametrized(pointer_scroll_wheel_hires_send_only_lores, LITEST_WHEEL, LITEST_TABLET, params);
litest_add_parametrized_for_device(pointer_scroll_wheel_hires_disabled, LITEST_MOUSE_WHEEL_HIRES_DISABLED, params);
} }
litest_with_parameters(params, "hires-delta", 'u', 3, 5, 15, 20) { litest_with_parameters(params, "hires-delta", 'u', 3, 5, 15, 20) {
litest_add_parametrized(pointer_scroll_wheel_inhibit_small_deltas, LITEST_WHEEL, LITEST_TABLET, params); litest_add_parametrized(pointer_scroll_wheel_inhibit_small_deltas, LITEST_WHEEL, LITEST_TABLET, params);