fallback: hires scroll heuristics for buggy devices

Some devices might announce support for high-resolution scroll wheel
by enabling REL_WHEEL_HI_RES and/or REL_HWHEEL_HI_RES but never send
a high-resolution scroll event.

When the first low-resolution scroll event is received without any
previous high-resolution event, print a kernel bug warning and start
emulating high-resolution scroll events.

Fix #668

Signed-off-by: José Expósito <jose.exposito89@gmail.com>
This commit is contained in:
José Expósito 2021-09-24 18:08:01 +02:00 committed by Peter Hutterer
parent e0aa946e39
commit 5bda716ebf
6 changed files with 146 additions and 0 deletions

View file

@ -0,0 +1,62 @@
.. _incorrectly_enabled_hires:
==============================================================================
Incorrectly enabled high-resolution scroll
==============================================================================
Some devices might announce support for high-resolution scroll wheel by enabling
``REL_WHEEL_HI_RES`` and/or ``REL_HWHEEL_HI_RES`` but never send a
high-resolution scroll event.
When the first low-resolution scroll event is received without any previous
high-resolution event, libinput prints a bug warning with the text **"device
supports high-resolution scroll but only low-resolution events have been
received"** and a link to this page.
.. note:: This warning will be printed only once
In most cases this is a bug on the device firmware, the kernel driver or in a
software used to create user-space devices through uinput.
Once the bug is detected, libinput will start emulating high-resolution scroll
events.
------------------------------------------------------------------------------
Detecting and fixing the issue
------------------------------------------------------------------------------
Events sent by a buggy device can be shown in the
:ref:`libinput record <libinput-record>` output for the device. Notice that
``REL_WHEEL_HI_RES`` and ``REL_HWHEEL_HI_RES`` are set but only ``REL_WHEEL``
events are sent: ::
# Supported Events:
# Event type 0 (EV_SYN)
# Event type 1 (EV_KEY)
# Event code 272 (BTN_LEFT)
# Event type 2 (EV_REL)
# Event code 0 (REL_X)
# Event code 1 (REL_Y)
# Event code 6 (REL_HWHEEL)
# Event code 8 (REL_WHEEL)
# Event code 11 (REL_WHEEL_HI_RES)
# Event code 12 (REL_HWHEEL_HI_RES)
[...]
quirks:
events:
- evdev:
- [ 0, 0, 2, 8, 1] # EV_REL / REL_WHEEL 1
- [ 0, 0, 0, 0, 0] # ------------ SYN_REPORT (0) ---------- +0ms
- evdev:
- [ 0, 15126, 2, 8, 1] # EV_REL / REL_WHEEL 1
- [ 0, 15126, 0, 0, 0] # ------------ SYN_REPORT (0) ---------- +15ms
- evdev:
- [ 0, 30250, 2, 8, 1] # EV_REL / REL_WHEEL 1
- [ 0, 30250, 0, 0, 0] # ------------ SYN_REPORT (0) ---------- +15ms
The issue can be fixed by adding a quirk to unset the ``REL_WHEEL_HI_RES`` and
``REL_HWHEEL_HI_RES`` event codes: ::
AttrEventCodeDisable=REL_WHEEL_HI_RES;REL_HWHEEL_HI_RES;
Please see :ref:`device-quirks` for details.

View file

@ -50,6 +50,7 @@ src_404s = [
[ 'faqs.rst', 'faq.html'],
[ 'features.rst', 'features.html'],
[ 'gestures.rst', 'gestures.html'],
[ 'incorrectly-enabled-hires.rst', 'incorrectly-enabled-hires.html'],
[ 'middle-button-emulation.rst', 'middle_button_emulation.html'],
[ 'normalization-of-relative-motion.rst', 'motion_normalization.html'],
[ 'palm-detection.rst', 'palm_detection.html'],
@ -143,6 +144,7 @@ src_rst = files(
'device-quirks.rst',
'faqs.rst',
'gestures.rst',
'incorrectly-enabled-hires.rst',
'middle-button-emulation.rst',
'normalization-of-relative-motion.rst',
'palm-detection.rst',

View file

@ -14,3 +14,4 @@ Troubleshooting
touchpad-pressure-debugging.rst
trackpoint-configuration.rst
tablet-debugging.rst
incorrectly-enabled-hires.rst

View file

@ -235,6 +235,18 @@ fallback_flush_wheels(struct fallback_dispatch *dispatch,
if (!(device->seat_caps & EVDEV_DEVICE_POINTER))
return;
if (!dispatch->wheel.emulate_hi_res_wheel &&
!dispatch->wheel.hi_res_event_received &&
(dispatch->wheel.lo_res.x != 0 || dispatch->wheel.lo_res.y != 0)) {
evdev_log_bug_kernel(device,
"device supports high-resolution scroll but only low-resolution events have been received.\n"
"See %s/incorrectly-enabled-hires.html for details\n",
HTTP_DOC_LINK);
dispatch->wheel.emulate_hi_res_wheel = true;
dispatch->wheel.hi_res.x = dispatch->wheel.lo_res.x * 120;
dispatch->wheel.hi_res.y = dispatch->wheel.lo_res.y * 120;
}
if (dispatch->wheel.is_inhibited) {
dispatch->wheel.hi_res.x = 0;
dispatch->wheel.hi_res.y = 0;
@ -897,10 +909,12 @@ fallback_process_relative(struct fallback_dispatch *dispatch,
break;
case REL_WHEEL_HI_RES:
dispatch->wheel.hi_res.y += e->value;
dispatch->wheel.hi_res_event_received = true;
dispatch->pending_event |= EVDEV_WHEEL;
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;
break;
}

View file

@ -102,6 +102,7 @@ struct fallback_dispatch {
struct device_coords hi_res;
bool emulate_hi_res_wheel;
bool is_inhibited;
bool hi_res_event_received;
} wheel;
struct {

View file

@ -803,6 +803,70 @@ START_TEST(pointer_scroll_wheel_hires)
}
END_TEST
START_TEST(pointer_scroll_wheel_hires_send_only_lores_vertical)
{
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);
litest_set_log_handler_bug(li);
litest_event(dev, EV_REL, REL_WHEEL, 1);
litest_event(dev, EV_SYN, SYN_REPORT, 0);
libinput_dispatch(li);
test_high_and_low_wheel_events_value(dev, REL_WHEEL, -120);
litest_event(dev, EV_REL, REL_WHEEL, -1);
litest_event(dev, EV_SYN, SYN_REPORT, 0);
libinput_dispatch(li);
test_high_and_low_wheel_events_value(dev, REL_WHEEL, 120);
litest_event(dev, EV_REL, REL_HWHEEL, 1);
litest_event(dev, EV_SYN, SYN_REPORT, 0);
libinput_dispatch(li);
test_high_and_low_wheel_events_value(dev, REL_HWHEEL, 120);
litest_assert_empty_queue(li);
litest_restore_log_handler(li);
}
END_TEST
START_TEST(pointer_scroll_wheel_hires_send_only_lores_horizontal)
{
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);
litest_set_log_handler_bug(li);
litest_event(dev, EV_REL, REL_HWHEEL, 2);
litest_event(dev, EV_SYN, SYN_REPORT, 0);
libinput_dispatch(li);
test_high_and_low_wheel_events_value(dev, REL_HWHEEL, 240);
litest_event(dev, EV_REL, REL_WHEEL, -1);
litest_event(dev, EV_SYN, SYN_REPORT, 0);
libinput_dispatch(li);
test_high_and_low_wheel_events_value(dev, REL_WHEEL, 120);
litest_event(dev, EV_REL, REL_HWHEEL, 1);
litest_event(dev, EV_SYN, SYN_REPORT, 0);
libinput_dispatch(li);
test_high_and_low_wheel_events_value(dev, REL_HWHEEL, 120);
litest_assert_empty_queue(li);
litest_restore_log_handler(li);
}
END_TEST
START_TEST(pointer_scroll_natural_defaults)
{
struct litest_device *dev = litest_current_device();
@ -3429,6 +3493,8 @@ TEST_COLLECTION(pointer)
litest_add_for_device(pointer_scroll_wheel_pressed_noscroll, LITEST_MOUSE);
litest_add_for_device(pointer_scroll_hi_res_wheel_pressed_noscroll, LITEST_MOUSE);
litest_add(pointer_scroll_wheel_hires, LITEST_WHEEL, LITEST_TABLET);
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_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);