Monday, November 22, 2010

A High-Level Overview of Grabs

One of the most common topics coming up in X input is grabs. This is a high-level overview of input device grabs and how they affect input event processing. Note that the examples here are simplified, grabs are probably the most complicated topic in input processing.

By default, X input events are delivered to all clients that registered for a specific event on a window. The basic premise of a grab is that it affects event delivery to deliver events exclusively to one client only.
There are three types of grabs, two classes of grabs, and two modes for a grab. The three types are:

  • active grabs

  • passive grabs

  • implicit passive grab



The two classes are core grabs and device grabs and the two modes are synchronous and asynchronous.
A grab comes in a combination of type + class + mode, so a grab may be an "active synchronous device grab".

Grab Types


Let's look at the difference between types first.

Active Grabs


An active grab is a direct result of a client request. The client requests "Grab this device and deliver its events exclusively to me". Future events are then delivered to this client until the client "ungrabs" the device again.
From a UI point-of-view, active grabs are commonly used when events must go to one specific application, regardless of the pointer or keyboard focus. One example are popup menus, where you want the next click to either activate a field or remove the window when clicking outside of the popup menu.

Passive Grabs


A passive grab is a promise by the X server to grab the device next time a button or key is pressed. The client essentially requests "grab the button when it is on window W and Alt and Shift are down". This request is stored and in the future when the pointer moves into window W, the specified modifiers are down and the user presses the button, the passive grab is activated (so now we have an "activated passive grab") and events are delivered exclusively to the client that requested the passive grab. Once the button or key is released, the passive grab is released again (and events are sent to all applicable clients). Unlike active grabs, passive grabs will activate repeatedly whenever the conditions are met. Ungrabbing a passive grab means to remove the conditions that trigger the activation of a passive grab.

XI2 added enter/leave and focus in/out passive grabs in addition to the already common button and key grabs. So a passive grab may activate when a pointer enters the window now.

Implicit Passive Grabs


These are a special type of passive grabs that are not under the direct influence of clients. Whenever a button press event is delivered normally to a client (i.e. no passive grab activated), this client gets an implicit passive grab on the device to ensure that future events, most notably the release event, is also delivered to the same client. Implicit passive grabs only come in synchronous mode, more on that later.


Grab Classes


In the core X protocol, only pointer and keyboard grabs are defined. With the addition of the X Input Extension, the device grabs were added as well. The only real difference is that with the latter a client explicitly specifies which device to grab. In the case of a core grab, the client simply says "grab the pointer" or "grab the keyboard", in the case of a device grab, the client must specify a valid device to grab. The class of a grab affects the events being sent to the client. A client with a core grab cannot receive X Input events from this device and vice versa.
With the addition of MPX and XI2 to the server, core grabs now work according to the ClientPointer principle. Since one of the requirements for MPX/XI2 was to not interfere with core applications, the behaviour of the two classes of grabs differs slightly.


  • A core grab is a promise that the device will only send events to the grabbing client, and that no other device may interact with the client while the grab is active.

  • A device grab is a promise that the device will only send events to the grabbing client but other devices may still send events to this client too.



The basic assumption here is that core applications do not know about the existence of multiple devices, but XI-aware applications do. Other than that, the two classes behave mostly identical.

Grab Modes


Grabs, with the exception of implicit passive grabs, may come as synchronous or asynchronous grabs. Asynchronous are simple: the event stream is processed by the X server and sent to the grabbing client immediately. During synchronous grabs, the server will queue up events internally and ask the client how to continue for each event. On a synchronous passive button grab for example, the client will receive the first event (the button press) but no more. It can then decide what to do and instruct the server how to proceed with the events. One common instruction is to "thaw" the device, i.e. release the event stream and continue in asynchronous mode. The other common instruction is to "replay" the event. This is a common operation for window managers which put a grab on mouse buttons as it allows them to reshuffle windows before the event is seen by an application. The list of actions is essentially this:


  1. Button press occurs on window W.

  2. Synchronised passive grab for window manager WM activates.

  3. WM receives button event.

  4. WM brings window W to the foreground.

  5. WM requests to "replay" the event.

  6. X server deactivates passive grab.

  7. The same button press event is now sent to W and the clients listening for button events on W.



Misc Other Info


If a client requests an active grab, you are at the mercy of this client. If the client decides to not ungrab (for whatever reason, usually a bug), then you will not be able to use the device on any other application. This is what's commonly referred to as "stuck grab". The only way to get out of this at the moment is to find the offending client and kill it. All grabs for a client are automatically terminated when a client exits.

A passive grab may be converted into an active grab. If a client already has a passive grab and requests an active grab on the same device, this active passive grab is converted to an active grab and thus does not terminate automatically anymore. The client then has to send an ungrab request to the server. This is a common operation for popup menus, a client has a synchronised passive grab that is converted into an asynchronous active grab once the passive grab activates. It only works passive → active though, an active grab cannot be converted into a passive grab.

All grabs are based on to a window and the event coordinates in the subsequent events are relative to that window's origin. During a grab, the pointer or keyboard is regarded as inside this window (even when outside the window's boundaries) and thus enter/leave and focus in/out events are sent as if the device had left the previous window. These event will have a flag set to notify other clients that the leave or focus out event was caused by a grab.

If you think all this is confusing, look at the code that implements this :)
Also note that this post skips on a number of details and contains some amount of handwaving and sticking fingers in your ears and going "lalalala".


[edit] fix typo, on synchronous button grabs the client receives only the first event.

Thursday, November 18, 2010

How to ignore configuration errors

Two common errors turning up in the log files are "(EE) ioctl EVIOCGNAME failed: Inappropriate ioctl for device" and "(--) SynPS/2 Synaptics TouchPad: no supported touchpad found".

One example for the former is:

[ 59.837] (II) config/udev: Adding input device ADS7846 Touchscreen (/dev/input/event3)
[ 59.837] (**) ADS7846 Touchscreen: Applying InputClass "default"
[ 59.841] (**) ADS7846 Touchscreen: Applying InputClass "evdev touchscreen "
[ 59.842] (**) ADS7846 Touchscreen: always reports core events
[ 59.842] (**) ADS7846 Touchscreen: Device: "/dev/input/event3"
[ 59.842] (--) ADS7846 Touchscreen: Found absolute axes
[ 59.842] (--) ADS7846 Touchscreen: Found x and y absolute axes
[ 59.842] (--) ADS7846 Touchscreen: Found absolute touchscreen
[ 59.843] (II) ADS7846 Touchscreen: Configuring as touchscreen
[ 59.843] (**) ADS7846 Touchscreen: YAxisMapping: buttons 4 and 5
[ 59.843] (**) ADS7846 Touchscreen: EmulateWheelButton: 4, EmulateWheelIner0
[ 59.845] (II) XINPUT: Adding extended input device "ADS7846 Touchscreen" ()
[ 59.851] (II) ADS7846 Touchscreen: initialized for absolute axes.

--------------------- divider added for clarity ----------------

[ 59.854] (II) config/udev: Adding input device ADS7846 Touchscreen (/dev/input/mouse0)
[ 59.854] (**) ADS7846 Touchscreen: Applying InputClass "default"
[ 59.855] (**) ADS7846 Touchscreen: always reports core events
[ 59.855] (**) ADS7846 Touchscreen: Device: "/dev/input/mouse0"
[ 59.855] (EE) ioctl EVIOCGNAME failed: Inappropriate ioctl for device
[ 59.856] (II) UnloadModule: "evdev"
[ 59.856] (EE) PreInit returned NULL for "ADS7846 Touchscreen"


One example for the latter:


[ 494.925] (II) config/udev: Adding input device SynPS/2 Synaptics TouchPad (/dev/input/event6)
[ 494.925] (**) SynPS/2 Synaptics TouchPad: Applying InputClass "evdev touchpad catchall"
[ 494.925] (**) SynPS/2 Synaptics TouchPad: Applying InputClass "touchpad catchall"
[ 494.925] (II) LoadModule: "synaptics"
[ 494.927] (II) Loading /usr/lib/xorg/modules/input/synaptics_drv.so
[ 494.927] (II) Module synaptics: vendor="X.Org Foundation"
[ 494.927] compiled for 1.9.0, module version = 1.3.99
[ 494.928] Module class: X.Org XInput Driver
[ 494.928] ABI class: X.Org XInput driver, version 11.0
[ 494.928] (II) Synaptics touchpad driver version 1.3.99
[ 494.928] (**) Option "Device" "/dev/input/event6"
[ 495.282] (--) SynPS/2 Synaptics TouchPad: x-axis range 1472 - 5682
[ 495.282] (--) SynPS/2 Synaptics TouchPad: y-axis range 1408 - 5076
[ 495.282] (--) SynPS/2 Synaptics TouchPad: pressure range 0 - 255
[ 495.282] (--) SynPS/2 Synaptics TouchPad: finger width range 0 - 0
[ 495.283] (--) SynPS/2 Synaptics TouchPad: buttons: left right
[ 495.283] (--) SynPS/2 Synaptics TouchPad: invalid finger width range. defaulting to 0 - 16
[ 495.522] (--) SynPS/2 Synaptics TouchPad: touchpad found
[ 495.522] (**) SynPS/2 Synaptics TouchPad: always reports core events
[ 495.682] (II) XINPUT: Adding extended input device "SynPS/2 Synaptics TouchPad" (type: TOUCHPAD)
[ 495.683] (**) SynPS/2 Synaptics TouchPad: (accel) MinSpeed is now constant deceleration 2.5
[ 495.683] (**) SynPS/2 Synaptics TouchPad: MaxSpeed is now 1.75
[ 495.683] (**) SynPS/2 Synaptics TouchPad: AccelFactor is now 0.036
[ 495.683] (**) SynPS/2 Synaptics TouchPad: (accel) keeping acceleration scheme 1
[ 495.683] (**) SynPS/2 Synaptics TouchPad: (accel) acceleration profile 1
[ 495.683] (**) SynPS/2 Synaptics TouchPad: (accel) acceleration factor: 2.000
[ 495.683] (**) SynPS/2 Synaptics TouchPad: (accel) acceleration threshold: 4
[ 495.842] (--) SynPS/2 Synaptics TouchPad: touchpad found

--------------------- divider added for clarity ----------------

[ 495.843] (II) config/udev: Adding input device SynPS/2 Synaptics TouchPad (/dev/input/mouse0)
[ 495.843] (**) SynPS/2 Synaptics TouchPad: Applying InputClass "touchpad catchall"
[ 495.844] (II) Synaptics touchpad driver version 1.3.99
[ 495.844] (**) Option "Device" "/dev/input/mouse0"
[ 495.922] (--) SynPS/2 Synaptics TouchPad: invalid x-axis range. defaulting to 1615 - 5685
[ 495.922] (--) SynPS/2 Synaptics TouchPad: invalid y-axis range. defaulting to 1729 - 4171
[ 495.922] (--) SynPS/2 Synaptics TouchPad: invalid pressure range. defaulting to 0 - 256
[ 495.922] (--) SynPS/2 Synaptics TouchPad: invalid finger width range. defaulting to 0 - 16
[ 495.943] (EE) Query no Synaptics: 6003C8
[ 495.943] (--) SynPS/2 Synaptics TouchPad: no supported touchpad found
[ 495.943] (EE) SynPS/2 Synaptics TouchPad Unable to query/initialize Synaptics hardware.
[ 495.944] (EE) PreInit returned NULL for "SynPS/2 Synaptics TouchPad"


In both cases, you'll notice that the device gets added twice. Once with a /dev/input/eventX device path, and once with a /dev/input/mouseX path. The first one succeeds and that results in your device being added to the X server's device list. The second one fails because both evdev and synaptics try to use an ioctl that is only available on event devices. The errors do not indicate a driver bug but a misconfiguration. The local configuration needs to be adjusted to avoid non-event devices being assigned to evdev or synaptics.

Look at the synaptics xorg.conf.d snippet we ship in Fedora:

Section "InputClass"
Identifier "touchpad catchall"
Driver "synaptics"
MatchIsTouchpad "on"
MatchDevicePath "/dev/input/event*"
EndSection


That last line is important. It only tries to assign the synaptics driver to event devices, not to mouse devices. Once you've added that line (or poked your distro maintainer to do it for you), the errors will go away. Alternatively, you could just ignore the errors and not file a bug for it :)

Note that the synaptics upstream snippet does not contain this line. Synaptics supports more than just Linux so we cannot add this upstream.

Monday, November 15, 2010

XKB mouse emulation removed from default keymap

XKB has a feature referred to as "MouseKeys" or in the case of xkeyboard-config "PointerKeys". Once enabled, the keypad controls the pointer movement and can be used to emulate button events. The default keymap hat the PointerKeys toggle on Shift+NumLock.

Like so many other things, it is a low level feature that has virtually zero desktop integration. The only way to find out whether it is on or not is to hit a key and see the mouse cursor move. That of course requires you to already know about the feature.

If you've accidentally enabled it the only thing you may notice is that your keypad stopped working. Worse, with a nasty bug we had in servers 1.7 and later (fixed now), the mouse buttons would get stuck when PointerKeys was used intermixed with physical mouse button presses. I've received a fair number of bug reports and other complaints about this feature that I finally decided to remove it from the default options. Sergey has pushed the patch to the xkeyboard-config repository over the weekend, so expect that in xkeyboard-config 2.1 and later, PointerKeys will need to be explicitly enabled.

You can do so with the XKB option "keypad:pointerkeys", or by ticking the "Toggle PointerKeys with Shift + NumLock" checkbox in your desktop keyboard configuration UI.

Like the changed zapping behaviour and the middle button emulation default (both of which have completely failed to bring the world to an end, despite claims to the contrary), this is only a changed default. The feature is still there and can be re-enabled on-the-fly or permanently if needed.

Thursday, November 4, 2010

Adding Reviewed-by tags to patches

I merge a lot of patches and (thankfully!) I get a lot of Reviewed-by, Acked-by, etc. comments on both my patches and others that I end up merging. Copying those is painful and quiet annoying. Somewhen last year, I stumbled across snipMate which allows you to add autocompletion snippets to vim. My $HOME/.vim/snippets/gitcommit file now looks like this:


snippet ack
Acked-by: ${1}
snippet rev
Reviewed-by: ${1}
snippet sob
Signed-off-by: ${1}
snippet tested
Tested-by: ${1}
snippet me
Peter Hutterer <peter.hutterer@...>


and the names of the common patch reviewers. So whenever I comment on a patch or I amend a commit after receiving reviews, just typing "rev<space>name alias<space>" is enough to add the Reviewed-by: line.

snipMate is quite powerful but so far I haven't used it much beyond the above. But even just that saved me numerous hours.