Tuesday, June 2, 2009

Button mapping in X

X differs between device buttons and logical buttons. The former are handled in the device/driver, the latter are what applies to clients. By default, these are mapped in a 1:1 fashion, with device button 1 (D1) being mapped to logical button 1 (L1), D2 to L2, D3 to L3, etc.

Logical buttons are what the client sees in the end. The first logical button is L1 and there are some standard behaviours:
L1 .. left click
L2 .. middle click
L3 .. right click
L4 .. vert scroll wheel up
L5 .. vert scroll wheel down
L6 .. horiz scroll wheel left
L7 .. horiz scroll wheel right

These are de-facto standard in virtually all clients, with middle click usually resulting in a paste from the current selection. Additionally, some clients (e.g. firefox) map L8 and L9 to back/forward.
All these standard actions are by convention only, there's no defined standard (that I know of) and a client may choose to interpret L4 as left clicks (hint: this is not a smart thing to do).

The meaning of the logical buttons is always the same but the user can re-map device buttons to logical buttons. For example, a left-handed mouse uses a button mapping of D1:L3 and D3:L1 (a mapping of 3 2 1 4 5 6 7 ...). Now, when a button 3 is pressed, the server maps this button to logical button 1 and the client executes a left-click.

In-driver physical button mapping


There's one more stage of button mapping, and that stage happens in the driver. The driver communicates with the server over a defined API and the buttons passed across this API are treated as the device buttons. The driver may chose to map the physical button to a different device button to accommodate for certain hardware features or deficiencies. This mapping is driver-specific and may not exist for all drivers. Evdev has it mainly to accommodate for broken hardware, and synaptics has it for tap-to-click behaviour.

As an example, an evdev button mapping may ask for physical button 1 (P1) being mapped to device button 3. When P1 is pressed on the device, evdev passes D3 to the server which may then map it to L1 if a right-handed mouse is configured.

This method is more commonly used in synaptics for the tap-to-click behaviour. This is a purely in-driver emulation of a button, so the TapButton1/2/3 settings define what device button will be sent on a one/two/three finger tap. By default, TapButton1 is mapped to D1, but one could set it to D4 and scroll by tapping. This mapping is handy for the right-handed synaptics quirk described later.

The button map chain


Starting with server 1.6, devices have a chain of button mappings. Since both the physical device and the core pointer show up as separate devices (run xinput --list -short), they may have different button mappings. Virtually all clients only see the core pointer's events and any device that controls the core pointer is subject to the core pointer's mappings. The button mapping for physical devices that control the core pointer is thus as follows:


physical to device mapping → device to logical mapping → core pointer device to logical → client.


No contemporary configuration tool should change the core pointer's mapping. These tools should be XI aware and modify the configuration for each device.

In the following examples, '→' is used for a standard X:X mapping, and '»' for a X:Y button mapping. Assume Mouse M that controls the core pointer CP. Both have the standard 1 2 3 mapping with no in-driver mapping. The path is thus:


M(P1) → M(D1) → M(L1) → CP(D1) → CP(L1) → client.


If a 3 2 1 left-handed mapping on the core pointer is set up, the path is:

M(P1) → M(D1) → M(L1) → CP(D1) » CP(L3) → client.

Thus, all devices that send events through the core pointer are now left-handed.

On the other hand, if only a single device needs to be left-handed, the core pointer maintains it 1 2 3 mapping and the mouse M gets a 3 2 1 mapping:

M(P1) → M(D1) » M(L3) → CP(D3) → CP(L3) → client.

This is the setup configuration tools should choose if the user requires a left-handed mouse.

If a the same mapping is applied to both CP and M, then this mapping is neutralised:

M(P1) → M(D1) » M(L3) → CP(D3) » CP(L1) → client.

Although both M and CP have a left-handed setup, M effectively acts as a right-handed mouse. Even worse, an application that listens for XI events (e.g. the gimp) would see M as a left-handed mouse while all other applications would see it as a right-handed one. This is the reason why the core pointer mapping should be left alone.

Now, for fun, let's also assume that M has a hardware problem and physical button 4 is actually the left mouse button. So an in-driver mapping from 4 to 1 is needed.

M(P4) » M(D1) » M(L3) → CP(D3) » CP(L1) → client.


These examples above are the most common scenarios the average user may encounter.

Scrollwheel and buttons


As mentioned above, scrollwheels are mapped to the logical buttons 4,5 and 6,7. This has historical reasons, the core protocol only allows for two axes and by the time scrollwheels came about, the protocol was already set in stone. So now they're mapped in-driver to logical buttons instead and most applications and toolkits interpret it correctly. This is not ideal, as mapping wheel events to button events looses information (such as missing out on pointer acceleration for smoother scrolling).

The right-handed synaptics quirk


One common problem is how to configure a right-handed synaptics pad while leaving tap-to-click behaviour in the default configuration. As mentioned above, tap-to-click is an in-driver mapping. Right-handed behaviour is (usually) a global mapping applied by the desktop environment and may even be applied to the core pointer (especially with older, non-XI aware configuration tools). The easiest solution is to map tapping in-driver to an unused button number and then map this button back to the logical buttons as desired. For example, TapButton1 on synaptics device S could be mapped to 10, and D10 mapped to L1.


tap 1 » S(D10) → S(L10) → CP(D10) » CP(L1) → client


Thus, even if S is set up with a 3 2 1 mapping for the first three buttons, tapping still works in a "right-handed" fashion.

Note that in order to map two device buttons to the same logical button, X Input 1.5 is needed (server 1.6).

The tools


For a device button mapping:
XSetDeviceButtonMapping(3)
xinput --set-button-map 1 2 3 4 5 ...

For the core pointer mapping:
XSetPointerMapping(3)
xmodmap -e "pointer = 1 2 3"

For the synaptics mapping:
synclient TapButton1=1

For an evdev button mapping:
Option "ButtonMapping" "1 2 3 ..."


[edits]
mclasen pointed out that the middle button is "primary paste", not "clipboard paste". Text amended to be neutral regarding the paste type.

No comments: