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.


Dylan McCall said...

Thanks for this great article, Peter!
You mention stuck grabs, which is an issue I (and I am sure many others) like to grumble about :)

I'm curious if anything is happening in that space, or where that can go in the future. Do you know why it is how it is right now?

Unknown said...

You didn't mention server grabs which are a whole other beast and definitely Pure Evil™.

Or maybe that was intentional and I just blew it...

Great write-up though :)

Peter Hutterer said...

I didn't intentionally skip server grabs, I simply forgot about them since technically they aren't related to input processing.

They simply pause the server, with all the side effects that entails, so I didn't bother describing them. I fail to see a good case in input event processing where halting the server is a good idea :)

Anonymous said...

"On an asynchronous passive button grab for example, the client will receive the first event (the button press) but no more."

I think you meant "On a synchronous passive button grab" (in the middle of the grab modes paragraph).

Anonymous said...

"On an asynchronous 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."

Isn't this supposed to be "On a synchronous passive button grab ..."?

Unknown said...

BTW, are there any plans to resurrect "disable active grabs" functionality?