Inconsistencies in (wacom) tablet stylus button mapping on Wayland
Affected version
- Ubuntu 22.04
- libmutter-10-0 version 42.0-1ubuntu1
- gnome-shell 42.0-1ubuntu1
- Affects only Wayland
Bug summary
There are a number of inconsistencies in how additional buttons on tablet stylus devices are mapped, leading to, differences in the default mapping between Xorg and Wayland, inverted mappings between Wayland and XWayland in the same session, swapping of top/bottom button mappings in the gnome settings and inability to assign the "back" and "forward" actions when using GTK apps.
Some of the these problems should probably be fixed in GTK (and/or maybe XWayland, depending on what we consider the correct mapping), but since there's certainly also a number of issues in mutter, I'm reporting this together here, to hopefully get an overall idea on what the correct mapping should be (and to allow a more global discussion first, rather than focusing on specific issues).
Mapping of BTN_STYLUS/BTN_STYLUS2/BTN_STYLUS3
A big part of the issue here seems to be that there is no obvious (self-documenting) meaning of the BTN_STYLUS (bottom button) and BTN_STYLUS2 (upper button) reported by libinput. In particular, Xorg and XWayland use one interpretation, GDK uses another, and mutter mixes up both. BTN_STYLUS3 seems to be consistenly used as the "back" button (except for what I think is a bug in xf86-input-libinput).
For this discussion, I'm going to assume that the mapping used by Xorg (BTN_STYLUS = middle, BTN_STYLUS2 = right) is the correct one for historical reasons.
Problems
Briefly, these are the problems I noticed (mostly from reviewing the code):
-
GTK uses a deviating mapping for the buttons in its
tablet_tool_handle_button
function (which handles the waylandzwp_tablet_tool_v2::button event). It mapsBTN_STYLUS
toGDK_BUTTON_SECONDARY
(right) andBTN_STYLUS2
toGDK_BUTTON_MIDDLE
. When no custom button mappings are configured in mutter (more on that below), mutter (throughmeta_seat_impl_notify_button_in_impl()
andbroadcast_button()
) just passes libinput button numbers on without changes, so this explains why the default mappings is wrong in GTK apps. Also, XWayland implements the reverse (IMHO correct) mapping (in itstablet_tool_button_state()
function), which was even more confusing (since fixing the mapping for GTK actually broke it for XWayland again). This has been broken in GTK since introduction in commit fb32f11e3.Suggested fix: Reverse the button mapping in GTK.
-
Custom button mappings are applied to the wrong buttons when running the native backend. The problem here is in the
meta_seat_impl_notify_button_in_impl()
function, which uses the wrong mapping (BTN_STYLUS
toCLUTTER_BUTTON_SECONDARY
andBTN_STYLUS2
toCLUTTER_BUTTON_MIDDLE
). Since the rest of the custom mapping code (see detail below) does use the correct mapping, this means that the configured action for the lower button (primary) is applied to the upper button, and vice versa.I guess this problem might have been caused because of button ordering differences, libinput uses left/right/middle while xorg uses left/middle/right (which has previously already [caused an issue for mouse button numbering] (99a7406d). This issue has existed since tablet support was introduced in clutter
Suggested fix: Reverse the button mapping in
meta_seat_impl_notify_button_in_impl()
. -
In the same function as the previous problem, it actually seems weird to me that the custom mapping is handled by first translating BTN_STYLUSx constants into CLUTTER_BUTTON_x constants, and then translating those back into BTN_x constants based on the configured mapping. Since the mapping is configured (in the UI) as a mapping of the lower/primary and upper/secondary buttons, it would make a lot more sense to me if the mapping code would just take the original libinput BTN_STYLUSx value and map that. Maybe this mapping was historically handled by code that was also used by the X11 backend, which only has X button numbers available, but now all of the relevant code stays within the native backend, so no objections in changing this.
Suggested fix: Do mapping based on the original libinput button number, by passing
button
instead ofbutton_nr
to themeta_input_device_tool_native_set_button_code_in_impl()
function and usingBTN_STYLUSx
constants instead ofCLUTTER_BUTTON_
constants when setting up the hash map in themeta_input_settings_native_set_stylus_button_map()
function (since the map and all related functions just operate on auint32_t button
, there are no other changes needed AFAICS). -
In the same function as the previous problems, it actually seems a bit weird that translation of the libinput button to Xorg
button_nr
happens before the custom mapping is applied, so always uses the default mapping. I initially thought that (if the previous problem is fixed),button_nr
could maybe be removed entirely (why deal with X button numbers when there is no X involved), but it seems thatbutton_nr
is still used for other things (it is stored in the clutter eventbutton.button
member, as well as in theseat_impl->button_state
bitmask. I am not familiar enough with the code to understand what these are used for (if at all - maybe they're effectively unused in the wayland case?) and whether these need to be the default mapping, or whether it would make sense to have the custom mapping applied to them.Suggested fix: I'm not sure. Moving the
button_nr
mapping to below themeta_input_device_tool_native_set_button_code_in_impl()
custom mapping seems obvious, but I cannot oversee the consequences. -
When an action of "Back" or "Forward" is configured for the pen buttons, these send
BTN_BACK
/BTN_FORWARD
(see theaction_to_evcode
function), which GTK does not handle in itstablet_tool_handle_button
function, it expectsBTN_STYLUS3
for the back button and does not support the forward button.Suggested fix: GTK should probably be prepared to handle not just
BTN_STYLUSx
for tablet tools, but any regular mouse button as well (i.e. reuse the sameBTN_x
to X button number mapping used for regular pointer devices). This should give maximum flexibility to the compositor for remapping keys. However, there is something weird with howBTN_BACK
andBTN_FORWARD
is defined, so this might not be sufficient. See discussion below. -
In the same function as the previous problem, it is actually weird that the middle/right actions are mapped to
BTN_STYLUSx
tablet buttons, while back/forward are mapped toBTN_BACK
/BTN_FORWARD
. Since there is no stylus-equivalent for forward, this is a practical solution, but I wonder if middle and right actions shouldn't actually be mapped toBTN_MIDDLE
andBTN_RIGHT
?Suggested fix: I'm not sure, see discussion below.
-
In the
meta_seat_impl_notify_button_in_impl()
function, I think additional tablet buttons are mapped wrongly. In the non-tablet case, these are mapped asbutton_nr = button - (BTN_LEFT - 1) + 4
which is a bit weirdly written (I guess the "4" here refers to the 4 scroll "buttons" that are skipped), but achieves the desired result: Additional buttons (afterBTN_MIDDLE
) are mapped to 8 and beyond (i.e.BTN_MIDDLE + 1
is correctly mapped to 8). However, for tablet buttons, this works differently: the additional buttons (i.e.BTN_TOOL_PEN
and onwards) are mapped to 4 and beyond. I'm not sure if this difference is intentional (or if these additional buttons are at all relevant), though. I do wonder if the non-tablet code would have been written likebutton_nr = 8 + button - (BTN_MIDDLE + 1)
, if the tablet code would have been written differently when it was introduced by @carlosg.Suggested fix: I'm not exactly sure, I guess it depends on how
button_nr
is actually used elsewhere. Maybe it does not even matter and it just needs unique button numbers without assigning any meaning to them?
Detail on mappings used by Xorg
Historically, the default mapping for these buttons is decided by the xf86-input-wacom driver, combined with the kernel wacom driver. I suspect (but haven't checked) that the kernel just numbers the extra buttons 1/2/3 (kernel button 0 is the tip). Then xf86-input-wacom assigns X mouse buttons to them: 2/3/8 (middle, right, back). There's probably a bit more code involved, but that does not seem relevant here now.
The xf86-input-libinput driver has copied this mapping: BTN_STYLUS becomes button 2 (middle) and BTN_STYLUS2 becomes button 3 (right). Note that BTN_STYLUS3 is not actually handled explicitly here, so I think that will end up being mapped to button 62 (8 + BTN_STYLUS3 - BTN_SIDE = 8 + 0x149- 0x113 = 62, see kernel source). Edit: I've reported this as a bug: https://gitlab.freedesktop.org/xorg/driver/xf86-input-libinput/-/issues/50
I've verified this in an Xorg session with xev, trying both drivers and indeed see lower button = 2, uppper button is 3. I could not verify the third button mapping inconsistency, since I only have two buttons. I've also used libinput debug-events
to check that the lower button is indeed BTN_STYLUS and the upper button BTN_STYLUS2.
For now, I'm going to assume that the xf86-input-wacom mapping (middle, right, back) from bottom to top is the most "correct" one.
Detail on problem 2: Mutter button remapping
Mutter allows remapping these buttons, by specifying "button-action", "secondary-button-action" and "tertiary-button-action" for the stylus under the org/gnome/desktop/peripherals/stylus/
dconf hierarchy. These are loaded from dconf by the generic update_stylus_buttonmap
function and then passed to backend-specific backends for processing.
Running on Xorg, the primary ("button-action") mapping is applied to button 2 (the bottom button), the secondary mapping to button 3 (the upper button) and the tertiary mapping to button 8 (the third button, if any). In the the meta_input_settings_x11_set_stylus_button_map
function.
Running on Wayland, the mapping is also applied correctly by the meta_input_settings_native_set_stylus_button_map
function, but as stated above, the mapping of libinput buttons to clutter buttons is reversed, so this code gets the wrong button numbers passed and applies the wrong action to the wrong button.
Discussion on problem 5: How to handle back/forward buttons/actions
There is actually a weirdness in how the "back" button is mapped. In Xorg, apparently the first button after the three normal buttons is mapped to button "8" and is intended to mean "back" (and I presume 9 is forward), but in libinput, that first button is actually BTN_SIDE
, with BTN_BACK
being a couple of values higher (and would be mapped to X button 11, not 8). And in fact, on my (ancient) Logitech mouse that has two side buttons (marked with arrays, so I guess they are intended to be "back" and "forward") actually produce BTN_SIDE
and BTN_EXTRA
events (which are passed verbatim by mutter, and translated to button 8 and 9 by either XWayland or the GTK Wayland code, so they do actually work as back/forward). This makes me wonder: Are applications actually prepared to handle BTN_BACK
and BTN_FORWARD
at all? Looking in the kernel source, it seems that there are a couple of devices that do actually report BTN_BACK
/ BTN_FORWARD
(some specific logitech mouse, some synaptics touchpads with extra buttons, some wacom pad buttons, some logitech PS/2++ mice). I'm not quite sure whose responsibility it is to actually interpret these buttons, it seems that at least for GTK it is now implied that BTN_SIDE
and BTN_EXTRA
really mean back and forward.
In GTK, for regular pointer devices, BTN_BACK
/ BTN_FORWARD
are mapped to buttons 11 and 10 respectively. If applications are actually handling these as forward/back, I guess the suggested fix for this problem in GTK is sufficient.
If applications do not handle button 11 and 10, I wonder if Mutter should maybe just send BTN_SIDE
and BTN_EXTRA
instead of BTN_BACK
and BTN_FORWARD
(which is lying to clients and relying on legacy behavior, but probably the easiest fix). Alternatively, GTK could maybe translate BTN_BACK
and BTN_FORWARD
into button 8 and 9, meaning GTK lies to its apps, but given X11 button 8/9 have been understood to mean back/forward, it might be less of a lie. Finally, (GTK) apps could be made aware that button 11 and 10 (also) mean forward/back, which is probably the most work (and ends up hardcoding button mappings in a lot of applications, but I guess that's sort of how Wayland was designed), but has the side effect of also making these buttons work when running GTK apps on X and XWayland.
Edit: Turns out XWayland actually already handles this by simply treating e.g. BTN_SIDE
and BTN_BACK
(and also BTN_STYLUS3
) as button 8. Not sure what this would mean for Mutter/GTK, though.
Discussion on problem 6: What buttons should be sent to clients?
Currently, clients get sent the stylus buttons constants BTN_STYLUS
/BTN_STYLUS2
/BTN_STYLUS3
when no custom mappings are configured. Custom mappings result in the BTN_STYLUS
/BTN_STYLUS2
/BTN_BACK
/BTN_FORWARD
(for the middle/right/back/forward actions respectively) being sent to clients. This seems a bit inconsistent, and it would seem obvious to just always send mouse buttons (i.e. BTN_MIDDLE
, BTN_RIGHT
). As an added advantage, this removes any ambiguity for clients about how to handle a button (no need to decide what the equivalent mouse button for e.g. BTN_STYLUS
is).
The downside is that some applications might actually be interested in the original button namings. I.e. I can imagine some drawing applications that know about how a stylus works, and allow configuring actions for the lower and upper buttons (though I checked inkscape which doesn't do this, and GIMP which might allow this but not on Wayland, it seems). If sending mouse button numbers, those applications would have to translate them back into stylus button numbers and map those. Even worse, when you configure Gnome with different actions for the stylus buttons (swap middle and right, or maybe even use back/forward), the in-app configuration might end up with the buttons swapped, or some buttons not configurable at all...
I wonder if it would make sense to support both of these usecases side-by-side by extending the wayland tablet protocol to pass both the original BTN_STYLUSx
button without any mapping applied, and also the mouse button that the button is mapped to as a separate argument. Then applications can either decide to use the real button when they care about that, or the mapped button if they just want to use the tablet pointer as a mouse alternative.
This actually also touches upon a different question, about whether applications that do not really care about tablets should need to implement the tablet protocol at all, or for them the stylus should just move the primary wm_pointer object. If so, you could also argue that the custom button mapping is maybe applied only the wm_pointer events, and the zwp_tablet_tool_v2 clients always get the original unmapped buttons (but I guess this is tricky, because e.g. GTK now implements the tablet_tool protocol even for applications that do not care about tablets...). In any case, this is really a different (and big) discussion that is probably best for elsewhere (edit: I created #2226 (closed) for this).
A related thing to look at here, is the handling of left-handed mice. This is essentially also a remapping of buttons. And where X11 did this elegantly by having a "Primary" and "Secondary" button (with the X server deciding which is left and right and applications not caring), the kernel and libinput (rightfully) use explicit BTN_LEFT
and BTN_RIGHT
values, matching the hardware directly. However, because the wayland protocol uses these constants verbatim, it is in sort of a pickle, since to implement a left-handed mouse, it must either tell all clients that they should swap these buttons (which becomes a mess), or it should lie to the clients about the button was pressed (which is indeed what it does now, but it lets libinput handles this through its libinput_device_config_left_handed_set()
function). So to some degree, I guess lying to clients is already an acceptable approach for pragmatic reasons.
Steps to reproduce
- Open an Gnome wayland session, attach a (wacom) tablet device with a pen (ideally two or three button).
Problem 1: Inconstency between GTK apps via Wayland and via XWayland
- Open some app using X, e.g.
GDK_BACKEND=x11 firefox
and verify that right-click is indeed the top button as expected - Open some GTK app using Wayland, e.g.
GDK_BACKEND=wayland firefox
and see that right-click is now the bottom button
Problem 2: the custom mapping being applied to the wrong button
Note that, because of problem 1, reproducing this is not immediately obvious: If you want to swap middle and right buttons, and you configure them in the obvious way in the gnome settings, they will be applied to the wrong button, but GTK then swaps middle vs right again, so it appears to have worked correctly. To really see this happening in practice:
- Open gnome settings, goto the wacom tablet page, leave the top button configured to "default" and set the lower button to "middle"
- Open some GDK app: you have now two right-click buttons (right instead of middle because of problem 1).
- Open some X app: you now have two middle-click buttons
Problem 5: Inability to configure the back button in GTK apps via Wayland
- Open the gnome settings, go to the wacom tablet, select the right pen with the arrows top right (I had two for some reason, only the second one worked), configure the "Back" action for either button.
- Open some app using X, e.g.
GDK_BACKEND=x11 firefox
and verify that the button indeed triggers a "back" action. Optionally also use xev to verify this sends button 8 events. - Open some app using Wayland, e.g.
GDK_BACKEND=wayland firefox
and see that the button is not handled at all.
Other implementations
Weston does not seem to implement the tablet protocol at all. It also does not seem to handle LIBINPUT_EVENT_TABLET_TOOL_*
events, so I suspect it simply does not support tablets at all.
Sway supports tablet input and the tablet protocol, but also implements a fallback: It tracks whether surfaces can accept tablet_v2 events and if not, lets the stylus control the regular pointer instead of using the tablet_v2 protocol. When using the tablet_v2 protocol, the button number is just passed verbatim, but when controlling the regular pointer, Sway seems to simply let any additional button send a BTN_RIGHT
event, though there is a comment to implement customized button mapping. The placement of that comment could suggest that their intent might be to make button remapping possible for regular pointer events, but always report the original buttons in the tablet protocol events (not entirely sure, though).