Fix negative value range for the dial to ring-mapping (RHEL-104952)

Resolves: RHEL-104952
This commit is contained in:
Peter Hutterer 2025-07-25 14:08:24 +10:00
parent 57458bd0ae
commit fedc85ddb7
2 changed files with 253 additions and 28 deletions

View File

@ -1,4 +1,4 @@
From 52bd7e03558c2a5f2356e34cc1fdc9486712d378 Mon Sep 17 00:00:00 2001
From 6b3b71d0ddd27207ef4c52c72080ba10b672fa5a Mon Sep 17 00:00:00 2001
From: Peter Hutterer <peter.hutterer@who-t.net>
Date: Tue, 22 Jul 2025 14:22:34 +1000
Subject: [PATCH] RHEL: map dials to rings on the Intuos Pro 3rd Gen devices
@ -14,13 +14,30 @@ remembered but we only get so much data from a dial.
Signed-off-by: Peter Hutterer <peter.hutterer@who-t.net>
---
quirks/30-vendor-wacom.quirks | 42 +++++++++++++++++++++
src/evdev-tablet-pad.c | 71 ++++++++++++++++++++++++++++++-----
src/evdev-tablet-pad.h | 5 +++
src/quirks.c | 1 +
src/quirks.h | 1 +
5 files changed, 110 insertions(+), 10 deletions(-)
meson.build | 1 +
quirks/30-vendor-wacom.quirks | 42 +++++++++
src/evdev-tablet-pad.c | 89 ++++++++++++++++---
src/evdev-tablet-pad.h | 5 ++
src/quirks.c | 1 +
src/quirks.h | 1 +
test/litest-device-wacom-intuos-pro-3rd-pad.c | 87 ++++++++++++++++++
test/litest.h | 1 +
test/test-tablet.c | 59 ++++++++++++
9 files changed, 276 insertions(+), 10 deletions(-)
create mode 100644 test/litest-device-wacom-intuos-pro-3rd-pad.c
diff --git a/meson.build b/meson.build
index 3f0583e5879f..f7fbe8222eea 100644
--- a/meson.build
+++ b/meson.build
@@ -822,6 +822,7 @@ if get_option('tests')
'test/litest-device-wacom-cintiq-pro-16-pen.c',
'test/litest-device-wacom-ekr.c',
'test/litest-device-wacom-hid4800-pen.c',
+ 'test/litest-device-wacom-intuos-pro-3rd-pad.c',
'test/litest-device-wacom-intuos3-pad.c',
'test/litest-device-wacom-intuos5-finger.c',
'test/litest-device-wacom-intuos5-pad.c',
diff --git a/quirks/30-vendor-wacom.quirks b/quirks/30-vendor-wacom.quirks
index 51211684c821..79c860efca39 100644
--- a/quirks/30-vendor-wacom.quirks
@ -72,10 +89,41 @@ index 51211684c821..79c860efca39 100644
+MatchProduct=0x03F5
+ModelWacomIntuosPro3rd=1
diff --git a/src/evdev-tablet-pad.c b/src/evdev-tablet-pad.c
index 90614c5dae91..f7b2117c3a56 100644
index 90614c5dae91..64d0eae2b603 100644
--- a/src/evdev-tablet-pad.c
+++ b/src/evdev-tablet-pad.c
@@ -335,7 +335,7 @@ pad_check_notify_axes(struct pad_dispatch *pad,
@@ -317,6 +317,30 @@ pad_strip_get_mode_group(struct pad_dispatch *pad,
return NULL;
}
+static double
+pad_ring_to_dial(double *current_value, double delta)
+{
+ /* The dial value is in v120 range but needs to be mapped to the 0..360
+ * degrees that the ring provides.
+ *
+ * Let's say one wheel detent (v120) is 15 degrees, this gives us 24
+ * lores wheel clicks to go the full 360 degrees circle.
+ * If we have 24 clicks per 360 that means our max value is 120 * 24
+ * after which we wrap around.
+ */
+ const int dial_degrees = 15;
+ const int detents_per_360 = 360 / dial_degrees;
+ const int wrap_threshold = 120 * detents_per_360;
+
+ double abs_value = *current_value + delta;
+ abs_value = fmod(abs_value + wrap_threshold, wrap_threshold);
+ *current_value = abs_value;
+
+ double degrees = abs_value * dial_degrees / 120;
+
+ return degrees;
+}
+
static void
pad_check_notify_axes(struct pad_dispatch *pad,
struct evdev_device *device,
@@ -335,7 +359,7 @@ pad_check_notify_axes(struct pad_dispatch *pad,
/* Unlike the ring axis we don't get an event when we release
* so we can't set a source */
@ -84,7 +132,7 @@ index 90614c5dae91..f7b2117c3a56 100644
group = pad_dial_get_mode_group(pad, 0);
tablet_pad_notify_dial(base,
time,
@@ -344,7 +344,7 @@ pad_check_notify_axes(struct pad_dispatch *pad,
@@ -344,7 +368,7 @@ pad_check_notify_axes(struct pad_dispatch *pad,
group);
}
@ -93,40 +141,34 @@ index 90614c5dae91..f7b2117c3a56 100644
group = pad_dial_get_mode_group(pad, 1);
tablet_pad_notify_dial(base,
time,
@@ -353,6 +353,40 @@ pad_check_notify_axes(struct pad_dispatch *pad,
@@ -353,6 +377,34 @@ pad_check_notify_axes(struct pad_dispatch *pad,
group);
}
+ if (pad->changed_axes & PAD_AXIS_DIAL1 && pad->dials.map_to_ring) {
+ pad->dials.abs_dial1_value += pad->dials.dial1;
+
+ /* Map one dial detent (in v120) to 15 degrees */
+ double value = fmod(pad->dials.abs_dial1_value / 8, 360);
+ double degrees = pad_ring_to_dial(&pad->dials.abs_dial1_value, pad->dials.dial1);
+ if (device->left_handed.enabled)
+ value = fmod(value + 180, 360);
+ degrees = fmod(degrees + 180, 360);
+
+ group = pad_ring_get_mode_group(pad, 0);
+ tablet_pad_notify_ring(base,
+ time,
+ 0,
+ value,
+ degrees,
+ LIBINPUT_TABLET_PAD_RING_SOURCE_UNKNOWN,
+ group);
+ }
+
+ if (pad->changed_axes & PAD_AXIS_DIAL2 && pad->dials.map_to_ring) {
+ pad->dials.abs_dial2_value += pad->dials.dial2;
+
+ /* Map one dial detent (in v120) to 15 degrees */
+ double value = fmod(pad->dials.abs_dial2_value / 8, 360);
+ double degrees = pad_ring_to_dial(&pad->dials.abs_dial2_value, pad->dials.dial2);
+ if (device->left_handed.enabled)
+ value = fmod(value + 180, 360);
+ degrees = fmod(degrees + 180, 360);
+
+ group = pad_ring_get_mode_group(pad, 1);
+ tablet_pad_notify_ring(base,
+ time,
+ 1,
+ value,
+ degrees,
+ LIBINPUT_TABLET_PAD_RING_SOURCE_UNKNOWN,
+ group);
+ }
@ -134,7 +176,7 @@ index 90614c5dae91..f7b2117c3a56 100644
if (pad->changed_axes & PAD_AXIS_RING1) {
value = pad_handle_ring(pad, device, ABS_WHEEL);
if (send_finger_up)
@@ -780,6 +814,9 @@ pad_init(struct pad_dispatch *pad, struct evdev_device *device)
@@ -780,6 +832,9 @@ pad_init(struct pad_dispatch *pad, struct evdev_device *device)
pad->status = PAD_NONE;
pad->changed_axes = PAD_AXIS_NONE;
@ -144,7 +186,7 @@ index 90614c5dae91..f7b2117c3a56 100644
/* We expect the kernel to either give us both axes as hires or neither.
* Getting one is a kernel bug we don't need to care about */
pad->dials.has_hires_dial = libevdev_has_event_code(device->evdev, EV_REL, REL_WHEEL_HI_RES) ||
@@ -886,18 +923,15 @@ evdev_device_tablet_pad_get_num_buttons(struct evdev_device *device)
@@ -886,18 +941,15 @@ evdev_device_tablet_pad_get_num_buttons(struct evdev_device *device)
return pad->nbuttons;
}
@ -168,7 +210,7 @@ index 90614c5dae91..f7b2117c3a56 100644
EV_REL,
REL_HWHEEL))
ndials++;
@@ -906,6 +940,19 @@ evdev_device_tablet_pad_get_num_dials(struct evdev_device *device)
@@ -906,6 +958,19 @@ evdev_device_tablet_pad_get_num_dials(struct evdev_device *device)
return ndials;
}
@ -188,7 +230,7 @@ index 90614c5dae91..f7b2117c3a56 100644
int
evdev_device_tablet_pad_get_num_rings(struct evdev_device *device)
{
@@ -914,6 +961,10 @@ evdev_device_tablet_pad_get_num_rings(struct evdev_device *device)
@@ -914,6 +979,10 @@ evdev_device_tablet_pad_get_num_rings(struct evdev_device *device)
if (!(device->seat_caps & EVDEV_DEVICE_TABLET_PAD))
return -1;
@ -239,6 +281,186 @@ index 340d04635b9f..ff0ac94e424f 100644
QUIRK_MODEL_PRESSURE_PAD,
QUIRK_MODEL_TOUCHPAD_PHANTOM_CLICKS,
diff --git a/test/litest-device-wacom-intuos-pro-3rd-pad.c b/test/litest-device-wacom-intuos-pro-3rd-pad.c
new file mode 100644
index 000000000000..d5e54d46db6b
--- /dev/null
+++ b/test/litest-device-wacom-intuos-pro-3rd-pad.c
@@ -0,0 +1,87 @@
+/*
+ * Copyright © 2016 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_event down[] = {
+ { .type = -1, .code = -1 },
+};
+
+static struct input_event move[] = {
+ { .type = -1, .code = -1 },
+};
+
+static struct litest_device_interface interface = {
+ .touch_down_events = down,
+ .touch_move_events = move,
+};
+
+static struct input_absinfo absinfo[] = {
+ { ABS_X, 0, 1, 0, 0, 0 },
+ { ABS_Y, 0, 1, 0, 0, 0 },
+ { ABS_MISC, 0, 0, 0, 0, 0 },
+ { .value = -1 },
+};
+
+static struct input_id input_id = {
+ .bustype = 0x3,
+ .vendor = 0x56a,
+ .product = 0x3f9,
+};
+
+static int events[] = {
+ EV_KEY, BTN_0,
+ EV_KEY, BTN_1,
+ EV_KEY, BTN_2,
+ EV_KEY, BTN_3,
+ EV_KEY, BTN_4,
+ EV_KEY, BTN_5,
+ EV_KEY, BTN_6,
+ EV_KEY, BTN_7,
+ EV_KEY, BTN_8,
+ EV_KEY, BTN_9,
+ EV_KEY, BTN_STYLUS,
+ EV_REL, REL_WHEEL,
+ EV_REL, REL_HWHEEL,
+ EV_REL, REL_WHEEL_HI_RES,
+ EV_REL, REL_HWHEEL_HI_RES,
+ -1, -1,
+};
+
+TEST_DEVICE("wacom-intuos3-pad",
+ .type = LITEST_WACOM_INTUOS_PRO_3RD_PAD,
+ .features = LITEST_TABLET_PAD, /* RHEL: not LITEST_DIAL because it's special */
+ .interface = &interface,
+
+ .name = "Wacom Intuos Pro L Pad",
+ .id = &input_id,
+ .events = events,
+ .absinfo = absinfo,
+ .udev_properties = {
+ { "ID_INPUT_TABLET_PAD", "1" },
+ { NULL },
+ },
+)
diff --git a/test/litest.h b/test/litest.h
index 6108be50a4c1..93bea19dcd3e 100644
--- a/test/litest.h
+++ b/test/litest.h
@@ -352,6 +352,7 @@ enum litest_device_type {
LITEST_WACOM_EKR,
LITEST_WACOM_HID4800_PEN,
LITEST_WACOM_INTUOS,
+ LITEST_WACOM_INTUOS_PRO_3RD_PAD,
LITEST_WACOM_INTUOS3_PAD,
LITEST_WACOM_INTUOS5_PAD,
LITEST_WACOM_ISDV4,
diff --git a/test/test-tablet.c b/test/test-tablet.c
index b652dcfd5345..ae88c844be91 100644
--- a/test/test-tablet.c
+++ b/test/test-tablet.c
@@ -6560,6 +6560,62 @@ START_TEST(tablet_smoothing)
}
END_TEST
+START_TEST(tablet_dial_to_ring)
+{
+ struct litest_device *dev = litest_current_device();
+ struct libinput *li = dev->libinput;
+ struct libinput_event *event;
+ struct libinput_event_tablet_pad *pev;
+
+ /* The range is a bitmask of ring, direction, left-handed */
+ int testcase = _i; /* ranged test */
+ unsigned int ring = testcase & 0x1;
+ int direction = testcase & 0x2 ? -1 : 1;
+ bool left_handed = !!(testcase & 0x4);
+
+ litest_assert_int_lt(testcase, 8);
+
+ const int degrees_per_detent = 15;
+
+ litest_assert_int_eq(libinput_device_tablet_pad_get_num_dials(dev->libinput_device), 0);
+ litest_assert_int_eq(libinput_device_tablet_pad_get_num_rings(dev->libinput_device), 2);
+
+ /* The dial-to-ring affected devices do not support left-handed so let's
+ * make sure setting left-handed a) doesn't work and b) the setting
+ * doesn't affect anything.
+ */
+ enum libinput_config_status rc = libinput_device_config_left_handed_set(dev->libinput_device, left_handed);
+ litest_assert_int_eq(rc, (unsigned)LIBINPUT_CONFIG_STATUS_UNSUPPORTED);
+
+ litest_drain_events(li);
+
+ double expected = 0.0;
+ for (size_t count = 0; count < 36; count++) { /* 24 per 360 so let's do 1.5 rotations */
+ if (ring == 0) {
+ /* REL_WHEEL is inverted to expectations */
+ litest_event(dev, EV_REL, REL_WHEEL, 1 * -direction);
+ litest_event(dev, EV_REL, REL_WHEEL_HI_RES, 120 * -direction);
+ litest_event(dev, EV_SYN, SYN_REPORT, 0);
+ } else {
+ litest_event(dev, EV_REL, REL_HWHEEL, 1 * direction);
+ litest_event(dev, EV_REL, REL_HWHEEL_HI_RES, 120 * direction);
+ litest_event(dev, EV_SYN, SYN_REPORT, 0);
+ }
+
+ libinput_dispatch(li);
+
+ expected += 360 + direction * degrees_per_detent;
+ expected = fmod(expected, 360);
+
+ event = libinput_get_event(li);
+ pev = litest_is_pad_ring_event(event, ring, LIBINPUT_TABLET_PAD_RING_SOURCE_UNKNOWN);
+ double value = libinput_event_tablet_pad_get_ring_position(pev);
+ litest_assert_double_eq(value, expected);
+ libinput_event_destroy(event);
+ }
+}
+END_TEST
+
TEST_COLLECTION(tablet)
{
struct range with_timeout = { 0, 2 };
@@ -6704,4 +6760,7 @@ TEST_COLLECTION(tablet)
litest_add_ranged_for_device(huion_static_btn_tool_pen_disable_quirk_on_prox_out, LITEST_HUION_TABLET, &with_timeout);
litest_add_for_device(tablet_smoothing, LITEST_WACOM_HID4800_PEN);
+
+ struct range dial_ring_cases = { 0, 8 };
+ litest_add_ranged_for_device(tablet_dial_to_ring, LITEST_WACOM_INTUOS_PRO_3RD_PAD, &dial_ring_cases);
}
--
2.50.1

View File

@ -5,7 +5,7 @@
Name: libinput
Version: 1.26.1
Release: 3%{?gitdate:.%{gitdate}git%{gitversion}}%{?dist}
Release: 4%{?gitdate:.%{gitdate}git%{gitversion}}%{?dist}
Summary: Input device library
# SPDX
@ -160,6 +160,9 @@ intended to be run by users.
%changelog
* Fri Jul 25 2025 Peter Hutterer <peter.hutterer@redhat.com> - 1.26.1-4
- Fix negative value range for the dial to ring-mapping (RHEL-104952)
* Wed Jul 23 2025 Peter Hutterer <peter.hutterer@redhat.com> - 1.26.1-3
- Support Wacom Intuos Pro 3rd by mapping the dial to a ring (RHEL-104952)