tablet: add API for relative dials

Some tablets such as those in the XP-PEN PRO series use "dials" which
are actually scrollwheels and emit EV_REL events. These should not be
emulated as rings (which are absolute) so we must expose them as a new
tablet event.

Adds LIBINPUT_EVENT_TABLET_PAD_DIAL that work largely identical as our
high-resolution wheel events (i.e. the values are in multiples or
fractions of of 120). Currently supports two dials.

This is a lot of copy/paste from the ring axes because the interface is
virtually identical. The main difference is that dials give us a v120
value in the same manner as our scroll axes.

Notes:
- REL_DIAL is mutually exclusive with REL_WHEEL, we assume the kernel
  doesn't (at this point) give us devices with both. If this changes for
  devices with three dials (wheel + hwheel + dial) we need to add code
  for that.
- REL_DIAL does not have a high-resolution axis and we assume that any
  device with REL_WHEEL_HI_RES will also have REL_HWHEEL_HI_RES (if the
  second wheel exists).
- With dials being REL_DIAL or REL_WHEEL there is no possibility of
  detecting a finger release (the kernel does not route EV_RELs with a
  value of zero). Unless this is implemented via a side-channel - and it
  doesn't look like any hardware that supports dials does that - we
  cannot forward any information here. So unlike absolute rings we
  cannot provide a source information here.

Closes #600

Co-authored-by: Peter Hutterer <peter.hutterer@who-t.net>
Part-of: <https://gitlab.freedesktop.org/libinput/libinput/-/merge_requests/967>
This commit is contained in:
Joshua Goins 2024-01-30 14:43:59 +10:00 committed by Marge Bot
parent d487ca36a4
commit beca998122
19 changed files with 735 additions and 12 deletions

View file

@ -757,6 +757,7 @@ if get_option('tests')
'test/litest-device-generic-singletouch.c',
'test/litest-device-gpio-keys.c',
'test/litest-device-huion-pentablet.c',
'test/litest-device-huion-q620m-dial.c',
'test/litest-device-hp-wmi-hotkeys.c',
'test/litest-device-ignored-mouse.c',
'test/litest-device-keyboard.c',
@ -792,7 +793,9 @@ if get_option('tests')
'test/litest-device-synaptics-t440.c',
'test/litest-device-synaptics-x1-carbon-3rd.c',
'test/litest-device-synaptics-phantomclicks.c',
'test/litest-device-tablet-doubledial.c',
'test/litest-device-tablet-mode-switch.c',
'test/litest-device-tablet-rel-dial.c',
'test/litest-device-thinkpad-extrabuttons.c',
'test/litest-device-trackpoint.c',
'test/litest-device-touch-screen.c',

View file

@ -517,6 +517,7 @@ pad_init_leds_from_libwacom(struct pad_dispatch *pad,
pad_init_mode_rings(pad, wacom);
pad_init_mode_strips(pad, wacom);
/* Note: libwacom doesn't do dials yet */
out:
if (wacom)
@ -546,6 +547,7 @@ pad_init_fallback_group(struct pad_dispatch *pad)
group->base.button_mask = -1;
group->base.strip_mask = -1;
group->base.ring_mask = -1;
group->base.dial_mask = -1;
group->base.toggle_button_mask = 0;
list_insert(&pad->modes.mode_group_list, &group->base.link);

View file

@ -97,6 +97,50 @@ pad_button_set_down(struct pad_dispatch *pad,
}
}
static void
pad_process_relative(struct pad_dispatch *pad,
struct evdev_device *device,
struct input_event *e,
uint64_t time)
{
switch (e->code) {
case REL_DIAL:
pad->dials.dial1 = e->value * 120;
pad->changed_axes |= PAD_AXIS_DIAL1;
pad_set_status(pad, PAD_AXES_UPDATED);
break;
case REL_WHEEL:
if (!pad->dials.has_hires_dial) {
pad->dials.dial1 = e->value * 120;
pad->changed_axes |= PAD_AXIS_DIAL1;
pad_set_status(pad, PAD_AXES_UPDATED);
}
break;
case REL_HWHEEL:
if (!pad->dials.has_hires_dial) {
pad->dials.dial2 = e->value * 120;
pad->changed_axes |= PAD_AXIS_DIAL2;
pad_set_status(pad, PAD_AXES_UPDATED);
}
break;
case REL_WHEEL_HI_RES:
pad->dials.dial1 = e->value;
pad->changed_axes |= PAD_AXIS_DIAL1;
pad_set_status(pad, PAD_AXES_UPDATED);
break;
case REL_HWHEEL_HI_RES:
pad->dials.dial2 = e->value * 120;
pad->changed_axes |= PAD_AXIS_DIAL2;
pad_set_status(pad, PAD_AXES_UPDATED);
break;
default:
evdev_log_info(device,
"Unhandled EV_REL event code %#x\n",
e->code);
break;
}
}
static void
pad_process_absolute(struct pad_dispatch *pad,
struct evdev_device *device,
@ -216,6 +260,22 @@ pad_handle_strip(struct pad_dispatch *pad,
return pos;
}
static inline struct libinput_tablet_pad_mode_group *
pad_dial_get_mode_group(struct pad_dispatch *pad,
unsigned int dial)
{
struct libinput_tablet_pad_mode_group *group;
list_for_each(group, &pad->modes.mode_group_list, link) {
if (libinput_tablet_pad_mode_group_has_dial(group, dial))
return group;
}
assert(!"Unable to find dial mode group");
return NULL;
}
static inline struct libinput_tablet_pad_mode_group *
pad_ring_get_mode_group(struct pad_dispatch *pad,
unsigned int ring)
@ -264,6 +324,26 @@ pad_check_notify_axes(struct pad_dispatch *pad,
libevdev_get_event_value(device->evdev, EV_ABS, ABS_MISC) == 0)
send_finger_up = true;
/* Unlike the ring axis we don't get an event when we release
* so we can't set a source */
if (pad->changed_axes & PAD_AXIS_DIAL1) {
group = pad_dial_get_mode_group(pad, 0);
tablet_pad_notify_dial(base,
time,
0,
pad->dials.dial1,
group);
}
if (pad->changed_axes & PAD_AXIS_DIAL2) {
group = pad_dial_get_mode_group(pad, 1);
tablet_pad_notify_dial(base,
time,
1,
pad->dials.dial2,
group);
}
if (pad->changed_axes & PAD_AXIS_RING1) {
value = pad_handle_ring(pad, device, ABS_WHEEL);
if (send_finger_up)
@ -473,6 +553,8 @@ pad_flush(struct pad_dispatch *pad,
memcpy(&pad->prev_button_state,
&pad->button_state,
sizeof(pad->button_state));
pad->dials.dial1 = 0;
pad->dials.dial2 = 0;
}
static void
@ -484,6 +566,9 @@ pad_process(struct evdev_dispatch *dispatch,
struct pad_dispatch *pad = pad_dispatch(dispatch);
switch (e->type) {
case EV_REL:
pad_process_relative(pad, device, e, time);
break;
case EV_ABS:
pad_process_absolute(pad, device, e, time);
break;
@ -686,6 +771,16 @@ pad_init(struct pad_dispatch *pad, struct evdev_device *device)
pad->status = PAD_NONE;
pad->changed_axes = PAD_AXIS_NONE;
/* 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) ||
libevdev_has_event_code(device->evdev, EV_REL, REL_HWHEEL_HI_RES);
if (libevdev_has_event_code(device->evdev, EV_REL, REL_WHEEL) &&
libevdev_has_event_code(device->evdev, EV_REL, REL_DIAL)) {
log_bug_libinput(pad_libinput_context(pad), "Unsupported combination REL_DIAL and REL_WHEEL\n");
}
pad_init_buttons(pad, device);
pad_init_left_handed(device);
if (pad_init_leds(pad, device) != 0)
@ -782,6 +877,26 @@ evdev_device_tablet_pad_get_num_buttons(struct evdev_device *device)
return pad->nbuttons;
}
int
evdev_device_tablet_pad_get_num_dials(struct evdev_device *device)
{
int ndials = 0;
if (!(device->seat_caps & EVDEV_DEVICE_TABLET_PAD))
return -1;
if (libevdev_has_event_code(device->evdev, EV_REL, REL_WHEEL) ||
libevdev_has_event_code(device->evdev, EV_REL, REL_DIAL)) {
ndials++;
if (libevdev_has_event_code(device->evdev,
EV_REL,
REL_HWHEEL))
ndials++;
}
return ndials;
}
int
evdev_device_tablet_pad_get_num_rings(struct evdev_device *device)
{

View file

@ -41,6 +41,8 @@ enum pad_axes {
PAD_AXIS_RING2 = bit(1),
PAD_AXIS_STRIP1 = bit(2),
PAD_AXIS_STRIP2 = bit(3),
PAD_AXIS_DIAL1 = bit(4),
PAD_AXIS_DIAL2 = bit(5),
};
struct button_state {
@ -73,6 +75,12 @@ struct pad_dispatch {
bool have_abs_misc_terminator;
struct {
bool has_hires_dial;
double dial1;
double dial2;
} dials;
struct {
struct libinput_device_config_send_events config;
enum libinput_config_send_events_mode current_mode;

View file

@ -518,6 +518,9 @@ evdev_device_tablet_pad_has_key(struct evdev_device *device,
int
evdev_device_tablet_pad_get_num_buttons(struct evdev_device *device);
int
evdev_device_tablet_pad_get_num_dials(struct evdev_device *device);
int
evdev_device_tablet_pad_get_num_rings(struct evdev_device *device);

View file

@ -503,6 +503,7 @@ struct libinput_tablet_pad_mode_group {
uint32_t button_mask;
uint32_t ring_mask;
uint32_t strip_mask;
uint32_t dial_mask;
uint32_t toggle_button_mask;
@ -797,6 +798,13 @@ tablet_pad_notify_button(struct libinput_device *device,
enum libinput_button_state state,
struct libinput_tablet_pad_mode_group *group);
void
tablet_pad_notify_dial(struct libinput_device *device,
uint64_t time,
unsigned int number,
double value,
struct libinput_tablet_pad_mode_group *group);
void
tablet_pad_notify_ring(struct libinput_device *device,
uint64_t time,
unsigned int number,

View file

@ -104,6 +104,7 @@ event_type_to_str(enum libinput_event_type type)
CASE_RETURN_STRING(LIBINPUT_EVENT_TABLET_PAD_RING);
CASE_RETURN_STRING(LIBINPUT_EVENT_TABLET_PAD_STRIP);
CASE_RETURN_STRING(LIBINPUT_EVENT_TABLET_PAD_KEY);
CASE_RETURN_STRING(LIBINPUT_EVENT_TABLET_PAD_DIAL);
CASE_RETURN_STRING(LIBINPUT_EVENT_GESTURE_SWIPE_BEGIN);
CASE_RETURN_STRING(LIBINPUT_EVENT_GESTURE_SWIPE_UPDATE);
CASE_RETURN_STRING(LIBINPUT_EVENT_GESTURE_SWIPE_END);
@ -232,6 +233,10 @@ struct libinput_event_tablet_pad {
uint32_t code;
enum libinput_key_state state;
} key;
struct {
double v120;
int number;
} dial;
struct {
enum libinput_tablet_pad_ring_axis_source source;
double position;
@ -443,6 +448,7 @@ libinput_event_get_tablet_pad_event(struct libinput_event *event)
event->type,
NULL,
LIBINPUT_EVENT_TABLET_PAD_RING,
LIBINPUT_EVENT_TABLET_PAD_DIAL,
LIBINPUT_EVENT_TABLET_PAD_STRIP,
LIBINPUT_EVENT_TABLET_PAD_BUTTON,
LIBINPUT_EVENT_TABLET_PAD_KEY);
@ -2024,6 +2030,7 @@ libinput_event_destroy(struct libinput_event *event)
libinput_event_get_tablet_tool_event(event));
break;
case LIBINPUT_EVENT_TABLET_PAD_RING:
case LIBINPUT_EVENT_TABLET_PAD_DIAL:
case LIBINPUT_EVENT_TABLET_PAD_STRIP:
case LIBINPUT_EVENT_TABLET_PAD_BUTTON:
case LIBINPUT_EVENT_TABLET_PAD_KEY:
@ -2909,6 +2916,34 @@ tablet_pad_notify_button(struct libinput_device *device,
&button_event->base);
}
void
tablet_pad_notify_dial(struct libinput_device *device,
uint64_t time,
unsigned int number,
double value,
struct libinput_tablet_pad_mode_group *group)
{
struct libinput_event_tablet_pad *dial_event;
unsigned int mode;
dial_event = zalloc(sizeof *dial_event);
mode = libinput_tablet_pad_mode_group_get_mode(group);
*dial_event = (struct libinput_event_tablet_pad) {
.time = time,
.dial.number = number,
.dial.v120 = value,
.mode_group = libinput_tablet_pad_mode_group_ref(group),
.mode = mode,
};
post_device_event(device,
time,
LIBINPUT_EVENT_TABLET_PAD_DIAL,
&dial_event->base);
}
void
tablet_pad_notify_ring(struct libinput_device *device,
uint64_t time,
@ -3375,6 +3410,12 @@ libinput_device_tablet_pad_get_num_buttons(struct libinput_device *device)
return evdev_device_tablet_pad_get_num_buttons((struct evdev_device *)device);
}
LIBINPUT_EXPORT int
libinput_device_tablet_pad_get_num_dials(struct libinput_device *device)
{
return evdev_device_tablet_pad_get_num_dials((struct evdev_device *)device);
}
LIBINPUT_EXPORT int
libinput_device_tablet_pad_get_num_rings(struct libinput_device *device)
{
@ -3431,6 +3472,17 @@ libinput_tablet_pad_mode_group_has_button(struct libinput_tablet_pad_mode_group
return !!(group->button_mask & bit(button));
}
LIBINPUT_EXPORT int
libinput_tablet_pad_mode_group_has_dial(struct libinput_tablet_pad_mode_group *group,
unsigned int dial)
{
if ((int)dial >=
libinput_device_tablet_pad_get_num_dials(group->device))
return 0;
return !!(group->dial_mask & bit(dial));
}
LIBINPUT_EXPORT int
libinput_tablet_pad_mode_group_has_ring(struct libinput_tablet_pad_mode_group *group,
unsigned int ring)
@ -3589,6 +3641,28 @@ libinput_event_tablet_tool_get_base_event(struct libinput_event_tablet_tool *eve
return &event->base;
}
LIBINPUT_EXPORT double
libinput_event_tablet_pad_get_dial_delta_v120(struct libinput_event_tablet_pad *event)
{
require_event_type(libinput_event_get_context(&event->base),
event->base.type,
0.0,
LIBINPUT_EVENT_TABLET_PAD_DIAL);
return event->dial.v120;
}
LIBINPUT_EXPORT unsigned int
libinput_event_tablet_pad_get_dial_number(struct libinput_event_tablet_pad *event)
{
require_event_type(libinput_event_get_context(&event->base),
event->base.type,
0,
LIBINPUT_EVENT_TABLET_PAD_DIAL);
return event->dial.number;
}
LIBINPUT_EXPORT double
libinput_event_tablet_pad_get_ring_position(struct libinput_event_tablet_pad *event)
{
@ -3706,6 +3780,7 @@ libinput_event_tablet_pad_get_mode(struct libinput_event_tablet_pad *event)
event->base.type,
0,
LIBINPUT_EVENT_TABLET_PAD_RING,
LIBINPUT_EVENT_TABLET_PAD_DIAL,
LIBINPUT_EVENT_TABLET_PAD_STRIP,
LIBINPUT_EVENT_TABLET_PAD_BUTTON);
@ -3719,6 +3794,7 @@ libinput_event_tablet_pad_get_mode_group(struct libinput_event_tablet_pad *event
event->base.type,
NULL,
LIBINPUT_EVENT_TABLET_PAD_RING,
LIBINPUT_EVENT_TABLET_PAD_DIAL,
LIBINPUT_EVENT_TABLET_PAD_STRIP,
LIBINPUT_EVENT_TABLET_PAD_BUTTON);
@ -3732,6 +3808,7 @@ libinput_event_tablet_pad_get_time(struct libinput_event_tablet_pad *event)
event->base.type,
0,
LIBINPUT_EVENT_TABLET_PAD_RING,
LIBINPUT_EVENT_TABLET_PAD_DIAL,
LIBINPUT_EVENT_TABLET_PAD_STRIP,
LIBINPUT_EVENT_TABLET_PAD_BUTTON,
LIBINPUT_EVENT_TABLET_PAD_KEY);
@ -3746,6 +3823,7 @@ libinput_event_tablet_pad_get_time_usec(struct libinput_event_tablet_pad *event)
event->base.type,
0,
LIBINPUT_EVENT_TABLET_PAD_RING,
LIBINPUT_EVENT_TABLET_PAD_DIAL,
LIBINPUT_EVENT_TABLET_PAD_STRIP,
LIBINPUT_EVENT_TABLET_PAD_BUTTON,
LIBINPUT_EVENT_TABLET_PAD_KEY);
@ -3760,6 +3838,7 @@ libinput_event_tablet_pad_get_base_event(struct libinput_event_tablet_pad *event
event->base.type,
NULL,
LIBINPUT_EVENT_TABLET_PAD_RING,
LIBINPUT_EVENT_TABLET_PAD_DIAL,
LIBINPUT_EVENT_TABLET_PAD_STRIP,
LIBINPUT_EVENT_TABLET_PAD_BUTTON,
LIBINPUT_EVENT_TABLET_PAD_KEY);

View file

@ -164,7 +164,8 @@ struct libinput_event_tablet_tool;
*
* Tablet pad event representing a button press, or ring/strip update on
* the tablet pad itself. Valid event types for this event are @ref
* LIBINPUT_EVENT_TABLET_PAD_BUTTON, @ref LIBINPUT_EVENT_TABLET_PAD_RING and
* LIBINPUT_EVENT_TABLET_PAD_BUTTON, @ref LIBINPUT_EVENT_TABLET_PAD_DIAL,
* @ref LIBINPUT_EVENT_TABLET_PAD_RING and
* @ref LIBINPUT_EVENT_TABLET_PAD_STRIP.
*
* @since 1.3
@ -429,7 +430,8 @@ struct libinput_tablet_pad_mode_group;
* the Wacom Cintiq 22HD provide two mode groups. If multiple mode groups
* are available, a caller should use
* libinput_tablet_pad_mode_group_has_button(),
* libinput_tablet_pad_mode_group_has_ring() and
* libinput_tablet_pad_mode_group_has_ring(),
* libinput_tablet_pad_mode_group_has_dial() and
* libinput_tablet_pad_mode_group_has_strip() to associate each button,
* ring and strip with the correct mode group.
*
@ -539,6 +541,22 @@ int
libinput_tablet_pad_mode_group_has_button(struct libinput_tablet_pad_mode_group *group,
unsigned int button);
/**
* @ingroup tablet_pad_modes
*
* Devices without mode switching capabilities return true for every dial.
*
* @param group A previously obtained mode group
* @param dial A dial index, starting at 0
* @return true if the given dial index is part of this mode group or
* false otherwise
*
* @since 1.26
*/
int
libinput_tablet_pad_mode_group_has_dial(struct libinput_tablet_pad_mode_group *group,
unsigned int dial);
/**
* @ingroup tablet_pad_modes
*
@ -968,6 +986,14 @@ enum libinput_event_type {
*/
LIBINPUT_EVENT_TABLET_PAD_KEY,
/**
* A status change on a tablet dial with the @ref
* LIBINPUT_DEVICE_CAP_TABLET_PAD capability.
*
* @since 1.26
*/
LIBINPUT_EVENT_TABLET_PAD_DIAL,
LIBINPUT_EVENT_GESTURE_SWIPE_BEGIN = 800,
LIBINPUT_EVENT_GESTURE_SWIPE_UPDATE,
LIBINPUT_EVENT_GESTURE_SWIPE_END,
@ -3314,6 +3340,44 @@ libinput_event_tablet_pad_get_key(struct libinput_event_tablet_pad *event);
enum libinput_key_state
libinput_event_tablet_pad_get_key_state(struct libinput_event_tablet_pad *event);
/**
* @ingroup event_tablet_pad
*
* Returns the delta change of the dial, in multiples or fractions of 120, with
* each multiple of 120 indicating one logical wheel event.
* See libinput_event_pointer_get_scroll_value_v120() for more details.
*
* @note It is an application bug to call this function for events other than
* @ref LIBINPUT_EVENT_TABLET_PAD_DIAL. For other events, this function
* returns 0.
*
* @param event The libinput tablet pad event
* @return The delta of the the axis
*
* @since 1.26
*/
double
libinput_event_tablet_pad_get_dial_delta_v120(struct libinput_event_tablet_pad *event);
/**
* @ingroup event_tablet_pad
*
* Returns the number of the dial that has changed state, with 0 being the
* first dial. On tablets with only one dial, this function always returns
* 0.
*
* @note It is an application bug to call this function for events other than
* @ref LIBINPUT_EVENT_TABLET_PAD_DIAL. For other events, this function
* returns 0.
*
* @param event The libinput tablet pad event
* @return The index of the dial that changed state
*
* @since 1.26
*/
unsigned int
libinput_event_tablet_pad_get_dial_number(struct libinput_event_tablet_pad *event);
/**
* @ingroup event_tablet_pad
*
@ -4377,6 +4441,23 @@ libinput_device_switch_has_switch(struct libinput_device *device,
int
libinput_device_tablet_pad_get_num_buttons(struct libinput_device *device);
/**
* @ingroup device
*
* Return the number of dials a device with the @ref
* LIBINPUT_DEVICE_CAP_TABLET_PAD capability provides.
*
* @param device A current input device
*
* @return The number of dials or 0 if the device has no dials. -1 on error.
*
* @see libinput_event_tablet_pad_get_dial_number
*
* @since 1.26
*/
int
libinput_device_tablet_pad_get_num_dials(struct libinput_device *device);
/**
* @ingroup device
*

View file

@ -335,6 +335,10 @@ LIBINPUT_1.23 {
} LIBINPUT_1.21;
LIBINPUT_1.26 {
libinput_device_tablet_pad_get_num_dials;
libinput_event_tablet_pad_get_dial_delta_v120;
libinput_event_tablet_pad_get_dial_number;
libinput_tablet_pad_mode_group_has_dial;
libinput_tablet_tool_config_pressure_range_is_available;
libinput_tablet_tool_config_pressure_range_set;
libinput_tablet_tool_config_pressure_range_get_minimum;

View file

@ -0,0 +1,76 @@
/*
* Copyright © 2024 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, 255, 0, 0, 0 },
{ .value = -1 },
};
static struct input_id input_id = {
.bustype = 0x3,
.vendor = 0x256c,
.product = 0x006d,
};
static int events[] = {
EV_KEY, BTN_0,
EV_REL, REL_WHEEL,
EV_REL, REL_WHEEL_HI_RES,
-1, -1,
};
/* Device from https://gitlab.freedesktop.org/libinput/libinput/-/issues/600 */
TEST_DEVICE("huion-q620m-dial-pad",
.type = LITEST_HUION_Q620M_DIAL,
.features = LITEST_TABLET_PAD | LITEST_DIAL,
.interface = &interface,
.name = "HUION Huion Tablet_Q620M Dial",
.id = &input_id,
.events = events,
.absinfo = absinfo,
.udev_properties = {
{ "ID_INPUT_TABLET_PAD", "1" },
{ NULL },
},
)

View file

@ -0,0 +1,81 @@
/*
* Copyright © 2024 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 = 0x123,
.product = 0x678,
};
static int events[] = {
EV_KEY, BTN_0,
EV_KEY, BTN_1,
EV_KEY, BTN_2,
EV_KEY, BTN_3,
EV_KEY, BTN_STYLUS,
EV_REL, REL_WHEEL,
EV_REL, REL_WHEEL_HI_RES,
EV_REL, REL_HWHEEL,
EV_REL, REL_HWHEEL_HI_RES,
-1, -1,
};
TEST_DEVICE("tablet-doubledial-pad",
.type = LITEST_TABLET_DOUBLEDIAL_PAD,
.features = LITEST_TABLET_PAD | LITEST_DIAL,
.interface = &interface,
.name = "Generic Double Dial Pad",
.id = &input_id,
.events = events,
.absinfo = absinfo,
.udev_properties = {
{ "ID_INPUT_TABLET_PAD", "1" },
{ NULL },
},
)

View file

@ -0,0 +1,78 @@
/*
* Copyright © 2024 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 = 0x123,
.product = 0x456,
};
static int events[] = {
EV_KEY, BTN_0,
EV_KEY, BTN_1,
EV_KEY, BTN_2,
EV_KEY, BTN_3,
EV_KEY, BTN_STYLUS,
EV_REL, REL_DIAL,
-1, -1,
};
TEST_DEVICE("tablet-rel-dial-pad",
.type = LITEST_TABLET_REL_DIAL_PAD,
.features = LITEST_TABLET_PAD | LITEST_DIAL,
.interface = &interface,
.name = "Generic Rel DialPad",
.id = &input_id,
.events = events,
.absinfo = absinfo,
.udev_properties = {
{ "ID_INPUT_TABLET_PAD", "1" },
{ NULL },
},
)

View file

@ -137,6 +137,14 @@ struct litest_device_interface {
struct input_event *pad_ring_change_events;
struct input_event *pad_ring_end_events;
/**
* Pad events, LITEST_AUTO_ASSIGN is allowed on event values
* for ABS_WHEEL
*/
struct input_event *pad_dial_start_events;
struct input_event *pad_dial_change_events;
struct input_event *pad_dial_end_events;
/**
* Pad events, LITEST_AUTO_ASSIGN is allowed on event values
* for ABS_RX

View file

@ -2901,10 +2901,23 @@ auto_assign_pad_value(struct litest_device *dev,
{
const struct input_absinfo *abs;
if (ev->value != LITEST_AUTO_ASSIGN ||
ev->type != EV_ABS)
if (ev->value != LITEST_AUTO_ASSIGN)
return value;
if (ev->type == EV_REL) {
switch (ev->code) {
case REL_WHEEL:
case REL_HWHEEL:
case REL_DIAL:
assert (fmod(value, 120.0) == 0.0); /* Fractions not supported yet */
return value/120.0;
default:
return value;
}
} else if (ev->type != EV_ABS) {
return value;
}
abs = libevdev_get_abs_info(dev->evdev, ev->code);
litest_assert_notnull(abs);
@ -3213,6 +3226,9 @@ litest_event_type_str(enum libinput_event_type type)
case LIBINPUT_EVENT_TABLET_PAD_KEY:
str = "TABLET PAD KEY";
break;
case LIBINPUT_EVENT_TABLET_PAD_DIAL:
str = "TABLET PAD DIAL";
break;
case LIBINPUT_EVENT_SWITCH_TOGGLE:
str = "SWITCH TOGGLE";
break;
@ -3314,6 +3330,12 @@ litest_print_event(struct libinput_event *event)
libinput_event_tablet_pad_get_ring_position(pad),
libinput_event_tablet_pad_get_ring_source(pad));
break;
case LIBINPUT_EVENT_TABLET_PAD_DIAL:
pad = libinput_event_get_tablet_pad_event(event);
fprintf(stderr, "dial %d delta %.2f",
libinput_event_tablet_pad_get_dial_number(pad),
libinput_event_tablet_pad_get_dial_delta_v120(pad));
break;
default:
break;
}
@ -3920,6 +3942,23 @@ litest_is_pad_button_event(struct libinput_event *event,
return p;
}
struct libinput_event_tablet_pad *
litest_is_pad_dial_event(struct libinput_event *event,
unsigned int number)
{
struct libinput_event_tablet_pad *p;
enum libinput_event_type type = LIBINPUT_EVENT_TABLET_PAD_DIAL;
litest_assert_ptr_notnull(event);
litest_assert_event_type(event, type);
p = libinput_event_get_tablet_pad_event(event);
litest_assert_int_eq(libinput_event_tablet_pad_get_dial_number(p),
number);
return p;
}
struct libinput_event_tablet_pad *
litest_is_pad_ring_event(struct libinput_event *event,
unsigned int number,

View file

@ -315,6 +315,9 @@ enum litest_device_type {
/* Tablets */
LITEST_ELAN_TABLET,
LITEST_HUION_TABLET,
LITEST_HUION_Q620M_DIAL,
LITEST_TABLET_DOUBLEDIAL_PAD,
LITEST_TABLET_REL_DIAL_PAD,
LITEST_QEMU_TABLET,
LITEST_UCLOGIC_TABLET,
LITEST_WACOM_BAMBOO,
@ -380,6 +383,7 @@ enum litest_device_type {
#define LITEST_TOTEM bit(31)
#define LITEST_FORCED_PROXOUT bit(32)
#define LITEST_PRECALIBRATED bit(33)
#define LITEST_DIAL bit(34)
/* this is a semi-mt device, so we keep track of the touches that the tests
* send and modify them so that the first touch is always slot 0 and sends
@ -812,6 +816,9 @@ litest_is_pad_button_event(struct libinput_event *event,
unsigned int button,
enum libinput_button_state state);
struct libinput_event_tablet_pad *
litest_is_pad_dial_event(struct libinput_event *event,
unsigned int number);
struct libinput_event_tablet_pad *
litest_is_pad_ring_event(struct libinput_event *event,
unsigned int number,
enum libinput_tablet_pad_ring_axis_source source);

View file

@ -486,6 +486,82 @@ START_TEST(pad_ring_finger_up)
}
END_TEST
START_TEST(pad_has_dial)
{
struct litest_device *dev = litest_current_device();
struct libinput_device *device = dev->libinput_device;
int ndials;
int expected_ndials = 1;
if (libevdev_has_event_code(dev->evdev, EV_REL, REL_HWHEEL))
expected_ndials = 2;
ndials = libinput_device_tablet_pad_get_num_dials(device);
ck_assert_int_ge(ndials, expected_ndials);
}
END_TEST
START_TEST(pad_dial_low_res)
{
struct litest_device *dev = litest_current_device();
struct libinput *li = dev->libinput;
unsigned int code = 0;
if (libevdev_has_event_code(dev->evdev, EV_REL, REL_WHEEL))
code = REL_WHEEL;
if (libevdev_has_event_code(dev->evdev, EV_REL, REL_DIAL))
code = REL_DIAL;
litest_drain_events(li);
for (int i = 0; i < 10; i++) {
int direction = -1 + 2 * i % 2;
litest_event(dev, EV_REL, code, direction);
if (code == REL_WHEEL)
litest_event(dev, EV_REL, REL_WHEEL_HI_RES, direction * 120);
litest_event(dev, EV_SYN, SYN_REPORT, 0);
libinput_dispatch(li);
struct libinput_event *ev = libinput_get_event(li);
struct libinput_event_tablet_pad *pev = litest_is_pad_dial_event(ev, 0);
double v120 = libinput_event_tablet_pad_get_dial_delta_v120(pev);
ck_assert_double_ge(v120, 120.0 * direction);
libinput_event_destroy(ev);
}
}
END_TEST
START_TEST(pad_dial_hi_res)
{
struct litest_device *dev = litest_current_device();
struct libinput *li = dev->libinput;
const int increment = 30;
int accumulated = 0;
if (!libevdev_has_event_code(dev->evdev, EV_REL, REL_WHEEL_HI_RES))
return;
litest_drain_events(li);
for (int i = 0; i < 10; i++) {
litest_event(dev, EV_REL, REL_WHEEL_HI_RES, increment);
accumulated += increment;
if (accumulated % 120 == 0)
litest_event(dev, EV_REL, REL_WHEEL, 1);
litest_event(dev, EV_SYN, SYN_REPORT, 0);
libinput_dispatch(li);
struct libinput_event *ev = libinput_get_event(li);
struct libinput_event_tablet_pad *pev = litest_is_pad_dial_event(ev, 0);
double v120 = libinput_event_tablet_pad_get_dial_delta_v120(pev);
ck_assert_double_ge(v120, increment);
libinput_event_destroy(ev);
}
}
END_TEST
START_TEST(pad_has_strip)
{
struct litest_device *dev = litest_current_device();
@ -994,10 +1070,12 @@ TEST_COLLECTION(tablet_pad)
litest_add(pad_time, LITEST_TABLET_PAD, LITEST_ANY);
litest_add(pad_num_buttons, LITEST_TABLET_PAD, LITEST_ANY);
litest_add(pad_num_buttons_libwacom, LITEST_TABLET_PAD, LITEST_ANY);
/* None of our dial devices have libwacom entries */
litest_add(pad_num_buttons_libwacom, LITEST_TABLET_PAD, LITEST_DIAL);
litest_add(pad_button_intuos, LITEST_TABLET_PAD, LITEST_ANY);
litest_add(pad_button_bamboo, LITEST_TABLET_PAD, LITEST_ANY);
litest_add(pad_button_libwacom, LITEST_TABLET_PAD, LITEST_ANY);
/* None of our dial devices have libwacom entries */
litest_add(pad_button_libwacom, LITEST_TABLET_PAD, LITEST_DIAL);
litest_add(pad_button_mode_groups, LITEST_TABLET_PAD, LITEST_ANY);
litest_add(pad_has_ring, LITEST_RING, LITEST_ANY);
@ -1008,6 +1086,10 @@ TEST_COLLECTION(tablet_pad)
litest_add(pad_strip, LITEST_STRIP, LITEST_ANY);
litest_add(pad_strip_finger_up, LITEST_STRIP, LITEST_ANY);
litest_add(pad_has_dial, LITEST_DIAL, LITEST_ANY);
litest_add(pad_dial_low_res, LITEST_DIAL, LITEST_ANY);
litest_add(pad_dial_hi_res, LITEST_DIAL, LITEST_ANY);
litest_add_for_device(pad_left_handed_default, LITEST_WACOM_INTUOS5_PAD);
litest_add_for_device(pad_no_left_handed, LITEST_WACOM_INTUOS3_PAD);
litest_add_for_device(pad_left_handed_ring, LITEST_WACOM_INTUOS5_PAD);

View file

@ -158,6 +158,9 @@ print_event_header(struct libinput_event *ev)
case LIBINPUT_EVENT_TABLET_PAD_KEY:
type = "TABLET_PAD_KEY";
break;
case LIBINPUT_EVENT_TABLET_PAD_DIAL:
type = "TABLET_PAD_DIAL";
break;
case LIBINPUT_EVENT_SWITCH_TOGGLE:
type = "SWITCH_TOGGLE";
break;
@ -819,6 +822,21 @@ print_tablet_pad_key_event(struct libinput_event *ev)
state == LIBINPUT_KEY_STATE_PRESSED ? "pressed" : "released");
}
static void
print_tablet_pad_dial_event(struct libinput_event *ev)
{
struct libinput_event_tablet_pad *p = libinput_event_get_tablet_pad_event(ev);
unsigned int mode;
print_event_time(libinput_event_tablet_pad_get_time(p));
mode = libinput_event_tablet_pad_get_mode(p);
printq("dial %d delta %.2f (mode %d)\n",
libinput_event_tablet_pad_get_dial_number(p),
libinput_event_tablet_pad_get_dial_delta_v120(p),
mode);
}
static void
print_switch_event(struct libinput_event *ev)
{
@ -943,6 +961,9 @@ handle_and_print_events(struct libinput *li)
case LIBINPUT_EVENT_TABLET_PAD_KEY:
print_tablet_pad_key_event(ev);
break;
case LIBINPUT_EVENT_TABLET_PAD_DIAL:
print_tablet_pad_dial_event(ev);
break;
case LIBINPUT_EVENT_SWITCH_TOGGLE:
print_switch_event(ev);
break;

View file

@ -190,6 +190,10 @@ struct window {
double position;
int number;
} strip;
struct {
double position;
int number;
} dial;
} pad;
struct {
@ -720,12 +724,12 @@ draw_pad(struct window *w, cairo_t *cr)
ry = w->height/2 + 100;
cairo_save(cr);
/* outer ring */
/* outer ring (for ring) */
cairo_set_source_rgb(cr, .7, .7, .0);
cairo_arc(cr, rx, ry, 50, 0, 2 * M_PI);
cairo_fill(cr);
/* inner ring */
/* inner ring (for dial) */
cairo_set_source_rgb(cr, 1., 1., 1.);
cairo_arc(cr, rx, ry, 30, 0, 2 * M_PI);
cairo_fill(cr);
@ -743,7 +747,20 @@ draw_pad(struct window *w, cairo_t *cr)
snprintf(number, sizeof(number), "%d", w->pad.ring.number);
cairo_set_source_rgb(cr, .0, .0, .0);
draw_text(cr, number, rx, ry);
}
if (w->pad.dial.position != -1) {
const int degrees_per_click = 15.0;
double degrees = fmod(w->pad.dial.position/120 * degrees_per_click, 360);
pos = (degrees + 270) * M_PI/180.0;
cairo_set_source_rgb(cr, .0, .0, .0);
cairo_set_line_width(cr, 20);
cairo_arc(cr, rx, ry, 20, pos - M_PI/12 , pos + M_PI/12);
cairo_stroke(cr);
snprintf(number, sizeof(number), "%d", w->pad.dial.number);
cairo_set_source_rgb(cr, .0, .0, .0);
draw_text(cr, number, rx, ry);
}
cairo_restore(cr);
@ -1160,6 +1177,7 @@ window_init(struct window *w)
w->pad.ring.position = -1;
w->pad.strip.position = -1;
w->pad.dial.position = -1;
}
static void
@ -1726,7 +1744,7 @@ handle_event_tablet_pad(struct libinput_event *ev, struct window *w)
"Pad 0", "Pad 1", "Pad 2", "Pad 3", "Pad 4", "Pad 5",
"Pad 6", "Pad 7", "Pad 8", "Pad 9", "Pad >= 10"
};
double position;
double position, delta;
double number;
switch (libinput_event_get_type(ev)) {
@ -1748,6 +1766,14 @@ handle_event_tablet_pad(struct libinput_event *ev, struct window *w)
w->pad.strip.number = number;
w->pad.strip.position = position;
break;
case LIBINPUT_EVENT_TABLET_PAD_DIAL:
delta = libinput_event_tablet_pad_get_dial_delta_v120(p);
number = libinput_event_tablet_pad_get_dial_number(p);
if (w->pad.dial.number != number)
w->pad.dial.position = -delta;
w->pad.dial.number = number;
w->pad.dial.position += delta;
break;
default:
abort();
}
@ -1825,6 +1851,7 @@ handle_event_libinput(GIOChannel *source, GIOCondition condition, gpointer data)
case LIBINPUT_EVENT_TABLET_PAD_BUTTON:
case LIBINPUT_EVENT_TABLET_PAD_RING:
case LIBINPUT_EVENT_TABLET_PAD_STRIP:
case LIBINPUT_EVENT_TABLET_PAD_DIAL:
handle_event_tablet_pad(ev, w);
break;
case LIBINPUT_EVENT_TABLET_PAD_KEY:

View file

@ -91,9 +91,10 @@ displayed on press.
.TP 8
.B Tablet pads
Button events are displayed in the bottom-most button oblong, with the name
of the button displayed on press. Ring and strip events are displayed in the
yellow 'IO' symbol, with the position and the number of the ring/strip
filled in when events are available.
of the button displayed on press. Dials, ring and strip events are displayed in
the yellow 'IO' symbol, with the position of the ring or strip or the
delta of the dial filled in when events are available. The number of the dial,
ring or strip is displayed when an event is available.
.TP 8
.B Kernel events
Left of the center is a blue ring to debug kernel relative events (REL_X and