Wednesday, July 15, 2009

XI2 recipes, Part 4

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 three parts, I covered how to get and manipulate the device hierarchy, how to select for events and how to get extended device information. In this part, I will cover the common input events and the data they include.

All XI2 events are cookie events and must be retrieved with XGetEventData.


XIDeviceEvent


The XIDeviceEvent is the default event for button and key press and releases and motion events.
The event types that produce an XIDeviceEvent are XI_Motion, XI_ButtonPress, XI_ButtonRelease, XI_KeyPress and XI_KeyRelease. Note that proximity events do not exist in XI2. A device that supports proximity should instead provide another axis and send valuator events on this axis.

The event itself is (reasonably) close to the core events, so most fields may be familiar in one way or another. It is a GenericEvent, so the type is always GenericEvent (35), extension is the X Input extension's opcode and evtype is the actual type of the event: XI_KeyPress, XI_KeyRelease, XI_ButtonPress, XI_ButtonRelease, XI_Motion. XI2 does not have proximity events like XI1 has, on the basis that if a device supports proximity, it should just provide an axis that reports proximity data (even if that axis has a range of 0-1).

Each event provides the device the event came from and the actual source the event originated from. So for applications that listen to master device events, deviceid is the id of the master device, and sourceid is the id of the physical device that just got moved/clicked/typed.


static void print_deviceevent(XIDeviceEvent* event)
{
printf(" device: %d (%d)\n", event->deviceid, event->sourceid);
printf(" detail: %d\n", event->detail);
if (event->flags & XIKeyRepeat)
printf(" event is a key repeat.\n");

...
}


For button events, detail is the button number (after mapping applies of course). For key events, detail is the keycode. XI2 supports 32-bit keycodes, btw. For motion events, detail is 0. The flags field is a combination of various flags that apply for this event. Right now, the only defined flag is XIKeyRepeat for XI_KeyPress events. If this flag is set, the event is the result of an in-server key repeat instead of a physical key press (waiting for daniels to send me the patch for the server).

Each event includes root-absolute and window-relative coordinates with subpixel precision. For example, if you have your mouse slowed down by constant deceleration, you'll see the pointer's X coordinate move from 100.0 to 100.25, 100.5, 100.75, 101, etc. The same happens with devices that have their own coordinate range (except that that bit is missing in the server right now.).

XIDeviceEvents contain the button, modifier and valuator state in four different structs:

XIButtonState buttons;
XIValuatorState valuators;
XIModifierState mods;
XIGroupState group;


The buttons include the button state for each button on this device. Since we don't have any restrictions on the number of buttons in the protocol, the mask looks like this:


typedef struct {
int mask_len;
unsigned char *mask;
} XIButtonState;


The mask_len specifies the length of the actual mask in bytes. The bit for button N is defined as (1 << N), if it is set then the button is currently logically down. Quite similar is the valuator state, except that the mask specifies which valuators are provided in the values array.


typedef struct {
int mask_len;
unsigned char *mask;
double *values;
} XIValuatorState;


Again, mask_len is in bytes and for each bit set in mask, one double represents the current value of this valuator in this event. These coordinates are always in the device-specific coordinate system (screen coordinates for relative devices). To give you an example, if mask_len is 1 and bits 0 and 5 are set in mask, then values is an array size 2, with the values for axis 0 and axis 5.

Finally, the event contains the state of the modifier keys and the current XKB group info.

typedef struct
{
int base;
int latched;
int locked;
int effective;
} XIModifierState;

typedef XIModifierState XIGroupState;


The base modifiers are the ones currently pressed, latched the ones pressed until a key is pressed that's configured to unlatch it (e.g. some shift-capslock interactions have this behaviour) and finally locked modifiers are the ones permanently active until unlocked (default capslock behaviour in the US layout). The effective modifiers are a bitwise OR of the three above - which is essentially equivalent to the modifiers state supplied in the core protocol events.
The group state is a bit more complicated, since the effective group it is the arithmetic sum of all 3 after the group overflow handling is taken into account. The meaning of base, latched and locked is essentially the same otherwise.

Enter/leave and focus events


One of the biggest deficiencies of XI1 is the lack of enter/leave events for extended devices. XI2 provides both and they are essentially the same as the device events above with three extra fields: mode, focus and same_screen. Both enter/leave events and focus events are the same as core events squashed in an XI2 format. I recommend reading the core protocol spec for these events, it's much more verbose (and eloquent) than this blog.

Unsurprisingly, enter/leave events are generated separately for each device. While the core protocol has a quite funky model to ensure that applications aren't confused when multiple pointers exit or leave a window, the XI2 events are sent for each pointer, regardless of how many devices are currently in the window.

Focus events are simply typedefs of the enter events, so there's nothing exciting there.

Property events


Property events have stayed the same, except that they use the XGenericEvent (and cookie) format now. Property events contain the property that changed, the deviceid and a field detailing what actually changed on this property (one of XIPropertyDeleted, XIPropertyCreated, XIPropertyModified).

Raw events


Raw events are something new. Normal input events are heavily processed by the server (clipped, accelerated, mapped to absolute, etc.). Raw events are essentially a container to forward the data the server works with (e.g. the data passed up by the driver) and thus do not contain state other than the new information. The three interesting fields are detail, valuators and raw_values.


typedef struct {
...
int detail;
XIValuatorState valuators;
double *raw_values;
} XIRawEvent;


That the detail for button events is the unmapped button number or the key code. Possible evtypes for raw events are XI_RawMotion, XI_RawKeyPress, XI_RawKeyRelease, XI_RawButtonPress and XI_RawButtonRelease.

Valuator information works in the same manner as in XIDeviceEvents and contains the transformed (i.e. accelerated) valuators as used in the server. The raw_values array provides the untransformed values as they were passed up from the driver. This is useful for applications that need to provide their own acceleration code (e.g. games).
For example, the following bit shows the acceleration applied on each axis:


void print_rawmotion(XIRawEvent *event)
{
int i;
double *raw_valuator = event->raw_values,
*valuator = event->valuators.values;

for (i = 0; i < event->valuators.mask_len * 8; i++) {
if (XIMaskIsSet(event->valuators.mask, i)) {
printf("Acceleration on valuator %d: %f\n",
i, *valuator - *raw_valuator);
valuator++;
raw_valuator++;
}
}
}



Since raw events do not have target windows they are delivered exclusively to all root windows. Thus, a client that registers for raw events on a standard client window will receive a BadValue from XISelectEvents(). Like normal events however, if a client has a grab on the device, then the event is delivered only to the grabbing client.

4 comments:

spoo said...

On the x.org XI2 page it lists "Support for relative motion events". I thought that information might be through valuators, but none of the valuators for my master mouse provided that information. Is there some simple way to do this?

Also, with standard XEvents I could use XCheckTypedEvent/XCheckIfEvent to drop redundant events. I assume that I need to use something like XCheckIfEvent if I want to do xcookie.type comparisons, but XCheckIfEvent can't handle this for XI2 because it disallows X function calls in the comparator function, which I assume precludes XGetEventData. How would you recommend dealing with excessive XI2 motion events and such?

Peter Hutterer said...

spoo:
Unfortunately, we don't have all the libX11/... calls in place yet to provide complete replacements. So right now, there is no XI2 equivalent for XCheckIfEvent.

spoo said...

Ah, okay, thanks. I ended up just going through XPending worth of events before actually handling anything.

I hate to be a bother, but do you know of anywhere that documents the relative motion events stuff? I've seen references, but despite my googling I haven't been able to find a method for obtaining this data.

Peter Hutterer said...

spoo: please ask for things like that on the xorg-devel mailing list. My blog isn't archived as well.