Re: [PATCH 3/3] HID: wacom: report ABS_TWIST alongside ABS_Z for pen twist
From: Jason Gerecke
Date: Fri Nov 13 2026 - 18:29:58 EST
On Mon, Nov 9, 2026 at 6:21 PM Siarhei Vishniakou <svv@xxxxxxxxxx> wrote:
>
> Historically, Wacom tablets reported stylus barrel rotation (twist)
> via ABS_Z because a dedicated evdev axis did not exist. Now that we
> have this axis, report pen twist on ABS_TWIST in addition to ABS_Z.
>
> Keep reporting ABS_Z to maintain backward compatibility for existing
> applications that support Wacom devices.
>
> Signed-off-by: Siarhei Vishniakou <svv@xxxxxxxxxx>
> ---
> drivers/hid/wacom_wac.c | 35 +++++++++++++++++++++++++++++------
> 1 file changed, 29 insertions(+), 6 deletions(-)
>
> diff --git a/drivers/hid/wacom_wac.c b/drivers/hid/wacom_wac.c
> index 614f2adab563..7676bc26a027 100644
> --- a/drivers/hid/wacom_wac.c
> +++ b/drivers/hid/wacom_wac.c
> @@ -802,8 +802,10 @@ static void wacom_exit_report(struct wacom_wac *wacom)
> input_report_key(input, BTN_STYLUS2, 0);
> input_report_key(input, BTN_TOUCH, 0);
> input_report_abs(input, ABS_WHEEL, 0);
> - if (features->type >= INTUOS3S)
> + if (features->type >= INTUOS3S) {
> input_report_abs(input, ABS_Z, 0);
> + input_report_abs(input, ABS_TWIST, 0);
> + }
> }
> input_report_key(input, wacom->tool[idx], 0);
> input_report_abs(input, ABS_MISC, 0); /* reset tool id */
> @@ -981,6 +983,7 @@ static int wacom_intuos_general(struct wacom_wac *wacom)
> t = (data[7] & 0x20) ? ((t > 900) ? ((t-1) / 2 - 1350) :
> ((t-1) / 2 + 450)) : (450 - t / 2) ;
> input_report_abs(input, ABS_Z, t);
> + input_report_abs(input, ABS_TWIST, t);
> } else {
> /* 4D mouse 2nd packet */
> t = (data[6] << 3) | ((data[7] >> 5) & 7);
> @@ -1411,6 +1414,7 @@ static void wacom_intuos_pro2_bt_pen(struct wacom_wac *wacom)
> input_report_abs(pen_input, ABS_TILT_Y,
> (signed char)frame[8]);
> input_report_abs(pen_input, ABS_Z, rotation);
> + input_report_abs(pen_input, ABS_TWIST, rotation);
> input_report_abs(pen_input, ABS_WHEEL,
> get_unaligned_le16(&frame[11]));
> }
> @@ -1935,7 +1939,7 @@ static void wacom_map_usage(struct input_dev *input, struct hid_usage *usage,
> int resolution;
>
> if (equivalent_usage == HID_DG_TWIST) {
> - resolution_code = ABS_RZ;
> + resolution_code = ABS_TWIST;
> }
Requesting a comment here just to make sure that anyone that is only
casually reading doesn't think this code is dead. Setting *some* kind
of radial axis here (be it ABS_RZ or ABS_TWIST) is critical to
ensuring HID properly calculates the resolution for ABS_Z, and that
might not be obvious when you see HID_DG_TWIST forcing the
resolution_code to ABS_TWIST.
>
> resolution = hidinput_calc_abs_res(field, resolution_code);
> @@ -2332,6 +2336,7 @@ static void wacom_wac_pen_usage_mapping(struct hid_device *hdev,
> break;
> case HID_DG_TWIST:
Also requesting a comment for this case that explains that the usage
here is mapped twice for backwards-compatibility, and that the order
matters since the latter call will override `usage->type` and
`usage->code`, preventing wacom_wac_pen_event from automatically
emitting an ABS_Z event.
> wacom_map_usage(input, usage, field, EV_ABS, ABS_Z, 0);
> + wacom_map_usage(input, usage, field, EV_ABS, ABS_TWIST, 0);
> break;
> case HID_DG_ERASER:
> input_set_capability(input, EV_KEY, BTN_TOOL_RUBBER);
> @@ -2522,10 +2527,21 @@ static void wacom_wac_pen_event(struct hid_device *hdev, struct hid_field *field
> return;
>
> /* send pen events only when the pen is in range */
> - if (wacom_wac->hid_data.inrange_state)
> - input_event(input, usage->type, usage->code, value);
> - else if (wacom_wac->shared->stylus_in_proximity && !wacom_wac->hid_data.sense_state)
> - input_event(input, usage->type, usage->code, 0);
> + if (!wacom_wac->hid_data.inrange_state) {
> + if (!wacom_wac->shared->stylus_in_proximity ||
> + wacom_wac->hid_data.sense_state)
> + return;
> + value = 0;
> + }
> +
> + input_event(input, usage->type, usage->code, value);
> + /*
> + * ABS_Z was used to report twist data, because ABS_TWIST was not yet
> + * defined at the time. For compatibility purposes, also report twist
> + * via ABS_Z.
> + */
I think this comment could be a little more clear, perhaps referencing
the double-mapping in wacom_wac_pen_usage_mapping (and how we need to
send data out of both ABS_TWIST and ABS_Z but that `usage->type` and
`usage->code` can only be set to one of those two)
Jason (she/they)
---
Now instead of four in the eights place /
you’ve got three, ‘Cause you added one /
(That is to say, eight) to the two, /
But you can’t take seven from three, /
So you look at the sixty-fours....
> + if (equivalent_usage == HID_DG_TWIST)
> + input_report_abs(input, ABS_Z, value);
> }
>
> static void wacom_wac_pen_pre_report(struct hid_device *hdev,
> @@ -3741,6 +3757,7 @@ void wacom_setup_device_quirks(struct wacom *wacom)
> __clear_bit(BTN_TOOL_PENCIL, wacom_wac->pen_input->keybit);
> __clear_bit(BTN_TOOL_AIRBRUSH, wacom_wac->pen_input->keybit);
> __clear_bit(ABS_Z, wacom_wac->pen_input->absbit);
> + __clear_bit(ABS_TWIST, wacom_wac->pen_input->absbit);
> __clear_bit(ABS_DISTANCE, wacom_wac->pen_input->absbit);
> __clear_bit(ABS_TILT_X, wacom_wac->pen_input->absbit);
> __clear_bit(ABS_TILT_Y, wacom_wac->pen_input->absbit);
> @@ -3823,6 +3840,8 @@ int wacom_setup_pen_input_capabilities(struct input_dev *input_dev,
> case CINTIQ_COMPANION_2:
> input_set_abs_params(input_dev, ABS_Z, -900, 899, 0, 0);
> input_abs_set_res(input_dev, ABS_Z, 287);
> + input_set_abs_params(input_dev, ABS_TWIST, -900, 899, 0, 0);
> + input_abs_set_res(input_dev, ABS_TWIST, 287);
> wacom_setup_cintiq(wacom_wac);
> break;
>
> @@ -3835,6 +3854,8 @@ int wacom_setup_pen_input_capabilities(struct input_dev *input_dev,
> case INTUOS4S:
> input_set_abs_params(input_dev, ABS_Z, -900, 899, 0, 0);
> input_abs_set_res(input_dev, ABS_Z, 287);
> + input_set_abs_params(input_dev, ABS_TWIST, -900, 899, 0, 0);
> + input_abs_set_res(input_dev, ABS_TWIST, 287);
> fallthrough;
>
> case INTUOS:
> @@ -3855,6 +3876,8 @@ int wacom_setup_pen_input_capabilities(struct input_dev *input_dev,
>
> input_set_abs_params(input_dev, ABS_Z, -900, 899, 0, 0);
> input_abs_set_res(input_dev, ABS_Z, 287);
> + input_set_abs_params(input_dev, ABS_TWIST, -900, 899, 0, 0);
> + input_abs_set_res(input_dev, ABS_TWIST, 287);
>
> wacom_setup_intuos(wacom_wac);
> break;
> --
> 2.56.0.455.g6c5a503b38-goog
>
>