wheel: fix Lenovo Scrollpoint quirk
authorJosé Expósito <jose.exposito89@gmail.com>
Mon, 6 Jun 2022 18:24:16 +0000 (20:24 +0200)
committerJosé Expósito <jose.exposito89@gmail.com>
Tue, 7 Jun 2022 17:55:42 +0000 (19:55 +0200)
The IBM/Lenovo Scrollpoint mouse features a trackpoint-like stick that
sends a great amount of scroll deltas.

In order to handle the device, a quirk is in place to normalize the
scroll events as they were relative motion.

However, when high-resolution scroll was implemented, we started
normalizing the hi-res events instead of the lo-res events by mistake.

Fix the quirk by normalizing the right deltas.

Fixes: 6bb02aaf307a ("High-resolution scroll wheel support")
Signed-off-by: José Expósito <jose.exposito89@gmail.com>
Tested-by: Peter Ganzhorn <peter.ganzhorn@gmail.com>
meson.build
src/evdev-wheel.c
test/litest-device-lenovo-scrollpoint.c [new file with mode: 0644]
test/litest.h
test/test-pointer.c

index afbcc7a950d36b5d2f9fef71e8aa47d7e8dd8bca..b8b92710bd063fe127f8a7408263219ca9ac2d2a 100644 (file)
@@ -758,6 +758,7 @@ if get_option('tests')
                'test/litest-device-keyboard-razer-blackwidow.c',
                'test/litest-device-keyboard-razer-blade-stealth.c',
                'test/litest-device-keyboard-razer-blade-stealth-videoswitch.c',
+               'test/litest-device-lenovo-scrollpoint.c',
                'test/litest-device-lid-switch.c',
                'test/litest-device-lid-switch-surface3.c',
                'test/litest-device-logitech-media-keyboard-elite.c',
index 69984c9b1bc6b18cea6f5ddc3eb73118701a33d3..7fcf658d5d3c7fa7b98a5e7327b6ec28cfb22fc7 100644 (file)
@@ -187,17 +187,22 @@ wheel_flush_scroll(struct fallback_dispatch *dispatch,
        struct discrete_coords discrete = { 0.0, 0.0 };
        struct wheel_v120 v120 = { 0.0, 0.0 };
 
+       /* This mouse has a trackstick instead of a mouse wheel and sends
+        * trackstick data via REL_WHEEL. Normalize it like normal x/y coordinates.
+        */
        if (device->model_flags & EVDEV_MODEL_LENOVO_SCROLLPOINT) {
                struct normalized_coords unaccel = { 0.0, 0.0 };
 
-               dispatch->wheel.hi_res.y *= -1;
-               fallback_normalize_delta(device, &dispatch->wheel.hi_res, &unaccel);
+               dispatch->wheel.lo_res.y *= -1;
+               fallback_normalize_delta(device, &dispatch->wheel.lo_res, &unaccel);
                evdev_post_scroll(device,
                                  time,
                                  LIBINPUT_POINTER_AXIS_SOURCE_CONTINUOUS,
                                  &unaccel);
                dispatch->wheel.hi_res.x = 0;
                dispatch->wheel.hi_res.y = 0;
+               dispatch->wheel.lo_res.x = 0;
+               dispatch->wheel.lo_res.y = 0;
 
                return;
        }
diff --git a/test/litest-device-lenovo-scrollpoint.c b/test/litest-device-lenovo-scrollpoint.c
new file mode 100644 (file)
index 0000000..a61db92
--- /dev/null
@@ -0,0 +1,60 @@
+/*
+ * 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.h"
+#include "litest-int.h"
+
+static struct input_id input_id = {
+       .bustype = 0x3,
+       .vendor = 0x04b3,
+       .product = 0x3109,
+};
+
+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,
+       -1 , -1,
+};
+
+/* Note: device is not tagged with LITEST_WHEEL to avoid running the
+ * "standard" wheel tests. Device has a custom wheel
+ * behavior that is tested directly.
+ */
+TEST_DEVICE("lenovo-scrollpoint",
+       .type = LITEST_LENOVO_SCROLLPOINT,
+       .features = LITEST_RELATIVE | LITEST_BUTTON,
+       .interface = NULL,
+
+       .name = "HID 04b3:3109",
+       .id = &input_id,
+       .absinfo = NULL,
+       .events = events,
+)
index 1b1daa902d4c538150c9bc386a7678f869a2a158..7ac54f3e17e4ac37ec6bc37c61adb4ac1c4fc189 100644 (file)
@@ -322,6 +322,7 @@ enum litest_device_type {
        LITEST_GENERIC_PRESSUREPAD,
        LITEST_WACOM_ISDV4_524C_PEN,
        LITEST_MOUSE_FORMAT_STRING,
+       LITEST_LENOVO_SCROLLPOINT,
 };
 
 #define LITEST_DEVICELESS      -2
index 0fac63878c9aa9ec943a41e6b4b99bfb68a52c4c..7fbec60d2fa68ffa3fa39cbc9234eda80e89ed4f 100644 (file)
@@ -900,6 +900,73 @@ START_TEST(pointer_scroll_wheel_inhibit_dir_change)
 }
 END_TEST
 
+START_TEST(pointer_scroll_wheel_lenovo_scrollpoint)
+{
+       struct litest_device *dev = litest_current_device();
+       struct libinput *li = dev->libinput;
+       struct libinput_event *event;
+       struct libinput_event_pointer *ptrev;
+       double v;
+
+       litest_drain_events(dev->libinput);
+
+       /* Lenovo ScrollPoint has a trackstick instead of a wheel, data sent
+        * via REL_WHEEL is close to x/y coordinate space.
+        */
+       litest_event(dev, EV_REL, REL_WHEEL, 30);
+       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+       litest_event(dev, EV_REL, REL_WHEEL, -60);
+       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+       libinput_dispatch(li);
+
+       /* Hi-res scroll event first */
+       event = libinput_get_event(li);
+       litest_assert(litest_is_high_res_axis_event(event));
+       ptrev = litest_is_axis_event(event,
+                                    LIBINPUT_EVENT_POINTER_SCROLL_CONTINUOUS,
+                                    LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL,
+                                    LIBINPUT_POINTER_AXIS_SOURCE_CONTINUOUS);
+
+       v = libinput_event_pointer_get_scroll_value(ptrev, LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL);
+       litest_assert_double_eq(v, -30.0);
+       libinput_event_destroy(event);
+
+       /* legacy lo-res scroll event */
+       event = libinput_get_event(li);
+       litest_assert(!litest_is_high_res_axis_event(event));
+       ptrev = litest_is_axis_event(event,
+                                    LIBINPUT_EVENT_POINTER_SCROLL_CONTINUOUS,
+                                    LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL,
+                                    LIBINPUT_POINTER_AXIS_SOURCE_CONTINUOUS);
+       v = libinput_event_pointer_get_axis_value(ptrev, LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL);
+       litest_assert_double_eq(v, -30.0);
+       libinput_event_destroy(event);
+
+       /* Hi-res scroll event first */
+       event = libinput_get_event(li);
+       ptrev = litest_is_axis_event(event,
+                                    LIBINPUT_EVENT_POINTER_SCROLL_CONTINUOUS,
+                                    LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL,
+                                    LIBINPUT_POINTER_AXIS_SOURCE_CONTINUOUS);
+
+       v = libinput_event_pointer_get_scroll_value(ptrev, LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL);
+       litest_assert_double_eq(v, 60.0);
+       libinput_event_destroy(event);
+
+       /* legacy lo-res scroll event */
+       event = libinput_get_event(li);
+       litest_assert(!litest_is_high_res_axis_event(event));
+       ptrev = litest_is_axis_event(event,
+                                    LIBINPUT_EVENT_POINTER_SCROLL_CONTINUOUS,
+                                    LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL,
+                                    LIBINPUT_POINTER_AXIS_SOURCE_CONTINUOUS);
+       v = libinput_event_pointer_get_axis_value(ptrev, LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL);
+       litest_assert_double_eq(v, 60.0);
+       libinput_event_destroy(event);
+
+}
+END_TEST
+
 START_TEST(pointer_scroll_natural_defaults)
 {
        struct litest_device *dev = litest_current_device();
@@ -1964,7 +2031,8 @@ START_TEST(pointer_scroll_nowheel_defaults)
 
        /* button scrolling is only enabled if there is a
           middle button present */
-       if (libinput_device_pointer_has_button(device, BTN_MIDDLE))
+       if (libinput_device_pointer_has_button(device, BTN_MIDDLE) &&
+           dev->which != LITEST_LENOVO_SCROLLPOINT)
                expected = LIBINPUT_CONFIG_SCROLL_ON_BUTTON_DOWN;
        else
                expected = LIBINPUT_CONFIG_SCROLL_NO_SCROLL;
@@ -3528,6 +3596,7 @@ TEST_COLLECTION(pointer)
        litest_add(pointer_scroll_wheel_hires_send_only_lores_horizontal, LITEST_WHEEL, LITEST_TABLET);
        litest_add(pointer_scroll_wheel_inhibit_small_deltas, LITEST_WHEEL, LITEST_TABLET);
        litest_add(pointer_scroll_wheel_inhibit_dir_change, LITEST_WHEEL, LITEST_TABLET);
+       litest_add_for_device(pointer_scroll_wheel_lenovo_scrollpoint, LITEST_LENOVO_SCROLLPOINT);
        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);