HID: lenovo: Detect quirk-free fw on cptkbd and stop applying workaround
authorMikhail Khvainitski <me@khvoinitsky.org>
Sat, 23 Sep 2023 22:58:30 +0000 (01:58 +0300)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Tue, 28 Nov 2023 17:19:43 +0000 (17:19 +0000)
[ Upstream commit 46a0a2c96f0f47628190f122c2e3d879e590bcbe ]

Built-in firmware of cptkbd handles scrolling by itself (when middle
button is pressed) but with issues: it does not support horizontal and
hi-res scrolling and upon middle button release it sends middle button
click even if there was a scrolling event. Commit 3cb5ff0220e3 ("HID:
lenovo: Hide middle-button press until release") workarounds last
issue but it's impossible to workaround scrolling-related issues
without firmware modification.

Likely, Dennis Schneider has reverse engineered the firmware and
provided an instruction on how to patch it [1]. However,
aforementioned workaround prevents userspace (libinput) from knowing
exact moment when middle button has been pressed down and performing
"On-Button scrolling". This commit detects correctly-behaving patched
firmware if cursor movement events has been received during middle
button being pressed and stops applying workaround for this device.

Link: https://hohlerde.org/rauch/en/elektronik/projekte/tpkbd-fix/
Signed-off-by: Mikhail Khvainitski <me@khvoinitsky.org>
Signed-off-by: Jiri Kosina <jkosina@suse.cz>
Signed-off-by: Sasha Levin <sashal@kernel.org>
drivers/hid/hid-lenovo.c

index 44763c0..9c11813 100644 (file)
@@ -51,7 +51,12 @@ struct lenovo_drvdata {
        int select_right;
        int sensitivity;
        int press_speed;
-       u8 middlebutton_state; /* 0:Up, 1:Down (undecided), 2:Scrolling */
+       /* 0: Up
+        * 1: Down (undecided)
+        * 2: Scrolling
+        * 3: Patched firmware, disable workaround
+        */
+       u8 middlebutton_state;
        bool fn_lock;
 };
 
@@ -668,31 +673,48 @@ static int lenovo_event_cptkbd(struct hid_device *hdev,
 {
        struct lenovo_drvdata *cptkbd_data = hid_get_drvdata(hdev);
 
-       /* "wheel" scroll events */
-       if (usage->type == EV_REL && (usage->code == REL_WHEEL ||
-                       usage->code == REL_HWHEEL)) {
-               /* Scroll events disable middle-click event */
-               cptkbd_data->middlebutton_state = 2;
-               return 0;
-       }
+       if (cptkbd_data->middlebutton_state != 3) {
+               /* REL_X and REL_Y events during middle button pressed
+                * are only possible on patched, bug-free firmware
+                * so set middlebutton_state to 3
+                * to never apply workaround anymore
+                */
+               if (cptkbd_data->middlebutton_state == 1 &&
+                               usage->type == EV_REL &&
+                               (usage->code == REL_X || usage->code == REL_Y)) {
+                       cptkbd_data->middlebutton_state = 3;
+                       /* send middle button press which was hold before */
+                       input_event(field->hidinput->input,
+                               EV_KEY, BTN_MIDDLE, 1);
+                       input_sync(field->hidinput->input);
+               }
 
-       /* Middle click events */
-       if (usage->type == EV_KEY && usage->code == BTN_MIDDLE) {
-               if (value == 1) {
-                       cptkbd_data->middlebutton_state = 1;
-               } else if (value == 0) {
-                       if (cptkbd_data->middlebutton_state == 1) {
-                               /* No scrolling inbetween, send middle-click */
-                               input_event(field->hidinput->input,
-                                       EV_KEY, BTN_MIDDLE, 1);
-                               input_sync(field->hidinput->input);
-                               input_event(field->hidinput->input,
-                                       EV_KEY, BTN_MIDDLE, 0);
-                               input_sync(field->hidinput->input);
+               /* "wheel" scroll events */
+               if (usage->type == EV_REL && (usage->code == REL_WHEEL ||
+                               usage->code == REL_HWHEEL)) {
+                       /* Scroll events disable middle-click event */
+                       cptkbd_data->middlebutton_state = 2;
+                       return 0;
+               }
+
+               /* Middle click events */
+               if (usage->type == EV_KEY && usage->code == BTN_MIDDLE) {
+                       if (value == 1) {
+                               cptkbd_data->middlebutton_state = 1;
+                       } else if (value == 0) {
+                               if (cptkbd_data->middlebutton_state == 1) {
+                                       /* No scrolling inbetween, send middle-click */
+                                       input_event(field->hidinput->input,
+                                               EV_KEY, BTN_MIDDLE, 1);
+                                       input_sync(field->hidinput->input);
+                                       input_event(field->hidinput->input,
+                                               EV_KEY, BTN_MIDDLE, 0);
+                                       input_sync(field->hidinput->input);
+                               }
+                               cptkbd_data->middlebutton_state = 0;
                        }
-                       cptkbd_data->middlebutton_state = 0;
+                       return 1;
                }
-               return 1;
        }
 
        if (usage->type == EV_KEY && usage->code == KEY_FN_ESC && value == 1) {