diff options
author | Joshua Goins <josh@redstrate.com> | 2024-01-30 14:43:59 +1000 |
---|---|---|
committer | Marge Bot <emma+marge@anholt.net> | 2024-02-20 02:49:05 +0000 |
commit | beca998122089f55e79071a1d9ea77208a25f4c9 (patch) | |
tree | 565fcef8a83471f5f6ea27f259522c24fabc939d | |
parent | d487ca36a4730d41c71a293b910bd0f48e58eac0 (diff) |
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>
-rw-r--r-- | meson.build | 3 | ||||
-rw-r--r-- | src/evdev-tablet-pad-leds.c | 2 | ||||
-rw-r--r-- | src/evdev-tablet-pad.c | 115 | ||||
-rw-r--r-- | src/evdev-tablet-pad.h | 8 | ||||
-rw-r--r-- | src/evdev.h | 3 | ||||
-rw-r--r-- | src/libinput-private.h | 8 | ||||
-rw-r--r-- | src/libinput.c | 79 | ||||
-rw-r--r-- | src/libinput.h | 85 | ||||
-rw-r--r-- | src/libinput.sym | 4 | ||||
-rw-r--r-- | test/litest-device-huion-q620m-dial.c | 76 | ||||
-rw-r--r-- | test/litest-device-tablet-doubledial.c | 81 | ||||
-rw-r--r-- | test/litest-device-tablet-rel-dial.c | 78 | ||||
-rw-r--r-- | test/litest-int.h | 8 | ||||
-rw-r--r-- | test/litest.c | 43 | ||||
-rw-r--r-- | test/litest.h | 7 | ||||
-rw-r--r-- | test/test-pad.c | 86 | ||||
-rw-r--r-- | tools/libinput-debug-events.c | 21 | ||||
-rw-r--r-- | tools/libinput-debug-gui.c | 33 | ||||
-rw-r--r-- | tools/libinput-debug-gui.man | 7 |
19 files changed, 735 insertions, 12 deletions
diff --git a/meson.build b/meson.build index b4c85c15..26356838 100644 --- a/meson.build +++ b/meson.build @@ -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', diff --git a/src/evdev-tablet-pad-leds.c b/src/evdev-tablet-pad-leds.c index ad8e6fa6..7419bb5b 100644 --- a/src/evdev-tablet-pad-leds.c +++ b/src/evdev-tablet-pad-leds.c @@ -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); diff --git a/src/evdev-tablet-pad.c b/src/evdev-tablet-pad.c index de3d7cbe..59f63cb5 100644 --- a/src/evdev-tablet-pad.c +++ b/src/evdev-tablet-pad.c @@ -98,6 +98,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, struct input_event *e, @@ -217,6 +261,22 @@ pad_handle_strip(struct pad_dispatch *pad, } 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) @@ -783,6 +878,26 @@ evdev_device_tablet_pad_get_num_buttons(struct evdev_device *device) } 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) { int nrings = 0; diff --git a/src/evdev-tablet-pad.h b/src/evdev-tablet-pad.h index 3fc0f796..fe7aa8b7 100644 --- a/src/evdev-tablet-pad.h +++ b/src/evdev-tablet-pad.h @@ -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 { @@ -74,6 +76,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; } sendevents; diff --git a/src/evdev.h b/src/evdev.h index 0f0087a3..7e2567be 100644 --- a/src/evdev.h +++ b/src/evdev.h @@ -519,6 +519,9 @@ 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); int diff --git a/src/libinput-private.h b/src/libinput-private.h index b218b390..de1d7d64 100644 --- a/src/libinput-private.h +++ b/src/libinput-private.h @@ -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, diff --git a/src/libinput.c b/src/libinput.c index 42be3f92..0b42db2e 100644 --- a/src/libinput.c +++ b/src/libinput.c @@ -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); @@ -233,6 +234,10 @@ struct libinput_event_tablet_pad { enum libinput_key_state state; } key; struct { + double v120; + int number; + } dial; + struct { enum libinput_tablet_pad_ring_axis_source source; double position; int number; @@ -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: @@ -2910,6 +2917,34 @@ tablet_pad_notify_button(struct libinput_device *device, } 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, unsigned int number, @@ -3376,6 +3411,12 @@ libinput_device_tablet_pad_get_num_buttons(struct libinput_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) { return evdev_device_tablet_pad_get_num_rings((struct evdev_device *)device); @@ -3432,6 +3473,17 @@ libinput_tablet_pad_mode_group_has_button(struct libinput_tablet_pad_mode_group } 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) { @@ -3590,6 +3642,28 @@ libinput_event_tablet_tool_get_base_event(struct libinput_event_tablet_tool *eve } 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) { require_event_type(libinput_event_get_context(&event->base), @@ -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); diff --git a/src/libinput.h b/src/libinput.h index 67445946..0c14098a 100644 --- a/src/libinput.h +++ b/src/libinput.h @@ -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. * @@ -542,6 +544,22 @@ libinput_tablet_pad_mode_group_has_button(struct libinput_tablet_pad_mode_group /** * @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 + * * Devices without mode switching capabilities return true for every ring. * * @param group A previously obtained mode group @@ -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, @@ -3317,6 +3343,44 @@ 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 + * * Returns the mode the button, ring, or strip that triggered this event is * in, at the time of the event. * @@ -4380,6 +4444,23 @@ 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 + * * Return the number of rings a device with the @ref * LIBINPUT_DEVICE_CAP_TABLET_PAD capability provides. * diff --git a/src/libinput.sym b/src/libinput.sym index 78739059..f48e39e2 100644 --- a/src/libinput.sym +++ b/src/libinput.sym @@ -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; diff --git a/test/litest-device-huion-q620m-dial.c b/test/litest-device-huion-q620m-dial.c new file mode 100644 index 00000000..e76313c3 --- /dev/null +++ b/test/litest-device-huion-q620m-dial.c @@ -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 }, + }, +) diff --git a/test/litest-device-tablet-doubledial.c b/test/litest-device-tablet-doubledial.c new file mode 100644 index 00000000..34e4fd26 --- /dev/null +++ b/test/litest-device-tablet-doubledial.c @@ -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 }, + }, +) diff --git a/test/litest-device-tablet-rel-dial.c b/test/litest-device-tablet-rel-dial.c new file mode 100644 index 00000000..c32b4c47 --- /dev/null +++ b/test/litest-device-tablet-rel-dial.c @@ -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 }, + }, +) diff --git a/test/litest-int.h b/test/litest-int.h index 9226f574..8b76e7ee 100644 --- a/test/litest-int.h +++ b/test/litest-int.h @@ -139,6 +139,14 @@ struct litest_device_interface { /** * 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 */ struct input_event *pad_strip_start_events; diff --git a/test/litest.c b/test/litest.c index 0ff9d1e3..792b0a54 100644 --- a/test/litest.c +++ b/test/litest.c @@ -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; } @@ -3921,6 +3943,23 @@ litest_is_pad_button_event(struct libinput_event *event, } 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, enum libinput_tablet_pad_ring_axis_source source) diff --git a/test/litest.h b/test/litest.h index 672e6ef5..bdb217b2 100644 --- a/test/litest.h +++ b/test/litest.h @@ -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); diff --git a/test/test-pad.c b/test/test-pad.c index 2aeb735b..1ee12534 100644 --- a/test/test-pad.c +++ b/test/test-pad.c @@ -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); diff --git a/tools/libinput-debug-events.c b/tools/libinput-debug-events.c index c4877c62..6230b18f 100644 --- a/tools/libinput-debug-events.c +++ b/tools/libinput-debug-events.c @@ -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; @@ -820,6 +823,21 @@ print_tablet_pad_key_event(struct libinput_event *ev) } 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) { struct libinput_event_switch *sw = libinput_event_get_switch_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; diff --git a/tools/libinput-debug-gui.c b/tools/libinput-debug-gui.c index 619828cc..a1300c64 100644 --- a/tools/libinput-debug-gui.c +++ b/tools/libinput-debug-gui.c @@ -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: diff --git a/tools/libinput-debug-gui.man b/tools/libinput-debug-gui.man index 7cfe64bb..6865ea93 100644 --- a/tools/libinput-debug-gui.man +++ b/tools/libinput-debug-gui.man @@ -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 |