Wednesday, July 22, 2009

XI2 recipes, Part 5

This post is part of a mini-series of various recipes on how to deal with the new functionality in XI2. The examples here are merely snippets, full example programs to summarize each part are available here.

In the first four parts, I covered how to get and manipulate the device hierarchy, how to select for events, how to get extended device information and the common event types. In this part, I will focus on grabs.

What are grabs?


Grabs ensure event delivery to a particular client and to this client only. A common application of grabs are drop-down and popup menus. If such a menu is displayed, the pointer is grabbed to ensure that the next click is delivered to the client displaying the popup. The client can then either perform an action or undisplay the popup.

Two different types of grabs exist: active grabs ("grab here and now") and passive grabs ("grab whenever a button/key is pressed"). Both types of grabs may be synchronous or asynchronous. Asynchronous grabs essentially do just the above - deliver all future events to the grabbing client only (in relation to the grab window). Synchronous grabs stop event reporting after an event reported to the grabbing client and it is up to the client what happens next (see below). I recommend reading the XGrabPointer and XAllowEvents man pages for more detail, XI2 grabs work essentially in the same manner.

Active grabs


Devices may be actively grabbed with the XIGrabDevice() call. This call is quite similar to the core protocol's XGrabPointer, so I encourage you to read the matching man page for more detail than provides here.


XIGrabDevice(Display* dpy, int deviceid, Window grab_window, Time time,
Cursor cursor, int grab_mode, int paired_device_mode,
Bool owner_events, XIEventMask *mask)

The device may be a master or slave device that is currently not grabbed, the window must be viewable and time must be CurrentTime or later for the grab to succeed. If the device is a master pointer device, the cursor specified in cursor is displayed for the duration of the grab. Otherwise, the cursor argument is ignored.
Grab mode is either synchronous or asynchronous and applies to the device. If the device is a master device, the paired_device_mode applies to the paired master device. Otherwise, this argument is ignored.
The mask itself is a simple event mask, specifying the events to be delivered to the grabbing client. Depending on owner_events, events are reported only if selected by the mask (if owner_events is false) or are reported if selected by the client on the window (if owner_events is true).

The matching call to release an active grab is XIUngrabDevice(). If a synchronous grab is issued on a device, the client must use XIAllowEvents() to control further event delivery - just as with synchronous core grabs.

Noteworthy about XIGrabDevice is that the deviceid must specify a valid device. The fake deviceids of XIAllDevices or XIAllMasterDevices will result in a BadDevice error.

Passive grabs on buttons and keys


Devices may be passively grabbed on button presses and key presses, similar to the core protocol passive grabs.
Let's have a look at button presses. Passive button grabs ensure that the next time the specified button is pressed, a grab is activated on the device and the client receives the event until the button is released again. This happens automatically provided the matching modifiers are down.
The API to set a button grab is similar to the one from the core protocol with one notable exception. Button grabs can be registered for multiple modifier combinations in one go:


int XIGrabButton( Display *display, int deviceid,
int button, Window grab_window,
Cursor cursor, int grab_mode,
int paired_device_mode, Bool owner_events,
XIEventMask *mask, int num_modifiers,
XIGrabModifiers *modifiers_inout);


The first couple of parameters are identical to the active grab calls. The num_modifiers parameter specifies the length of the modifiers_inout array. The modifiers_inout itself specifies the modifiers states when this grab should be activated. So instead of one request per button+keycode combination, a client can submit all modifier combinations in one go. If any of these combinations fails, the server returns it in the modifiers_inout array.


typedef struct
{
int modifiers;
int status;
} XIGrabModifiers;


The client sets modifiers to the modifiers that should activate the grab, status is ignored by the server. The server returns the number of modifier combinations that could not be set and their status value. The modifiers themselves are always the effective modifiers. No difference between latched, locked, etc. is made.

In pseudo-code, setting a button grab looks something like this.


int nmodifiers = 2;
XIGrabModifiers modifiers[nmodifiers];

modifers[0].modifiers = ShiftMask;
modifiers[1].modifiers = ShiftMask | Mod1Mask;

if ((nmodifiers = XIGrabButton(..., nmodifiers, modifiers)) {
for (i = 0; i <nmodifiers; i++)
printf("Modifiers %x failed with %d\n", modifiers[i].modifiers, modifiers[i].status);



The API for grabbing keycodes is essentially the same as for button grabs.


int XIGrabKeycode( Display *display, int deviceid,
int keycode, Window grab_window,
int grab_mode, int paired_device_mode,
Bool owner_events, XIEventMask *mask,
int num_modifiers, XIGrabModifiers *modifiers_inout);


Both button and keycode grabs can be removed with XIUngrabButton and XIUngrabKeycode.

The fake deviceids of XIAllDevices and XIAllMasterDevices are permitted for passive button and key grabs.

Passive grabs on enter/focus events



Grabbing on enter and focus events is a new feature of XI2. Similar in principle to button presses except that the device is grabbed when the pointer or the keyboard focus is set to the specified window.


int XIGrabEnter( Display *display, int deviceid,
Window grab_window, Cursor cursor,
int grab_mode, int paired_device_mode,
Bool owner_events, XIEventMask *mask,
int num_modifiers,
XIGrabModifiers *modifiers_inout);

int XIGrabFocusIn ( ...)


The parameters are the same for both (XIGrabFocusIn doesn't take a cursor parameter) and they are the same as for button/keysym grabs anyway. When an enter and focus grabs activates, the grabbing client receives an additional XI_Enter of XI_FocusIn event with detail XINotifyPassiveGrab (provided it is set on the window or in the grab mask). To avoid inconsistencies with the core protocol, this event is sent after the enter event is sent to underlying window.

As an example, consider the following pseudo code:


Window win = create_window();

int nmodifiers = 1;
XIGrabModifiers modifiers[nmodifiers];
modifiers[0].modifiers = 0;

if ((nmodifiers = XIGrabEnter(display, 2, win, ..., num_modifiers, modifiers))
handle_errors();

while(1)
{
XEvent ev;
XNextEvent(dpy, &ev);
if (XGetEventData(dpy, &ev.xcookie) &&
ev.xcookie.extension == xi_opcode &&
ev.xcookie.evtype == XI_Enter)
{
XIEnterEvent *enter = ev.xcookie.data;

if (enter->detail == XINotifyPassiveGrab)
printf("we have grabbed the device\n");
}
XFreeEventData(dpy, &ev);
}


Provided that nmodifiers is 0 after the call to XIGrabEnter, each time the pointer moves into the window win the device will be grabbed (provided no modifiers are logically down). Once the pointer leaves the window again, the device is automatically ungrabbed.

Passive enter/focus grabs can be removed with the XIUngrabEnter and XIUngrabFocusIn calls.

The fake deviceids of XIAllDevices and XIAllMasterDevices are permitted for passive button and key grabs.

No comments: