A Vivisection of LibGII
Author : | Éric Faurot |
Revision : | 1.8 |
Revision date : | 2004-11-15 |
Introduction
LibGII (General Input Interface Library) is a flexible library for handling input from a variety of sources, including keyboards, mice, joysticks, etc.
LibGII is based on the concept of input streams, which virtualize access to the underlying input drivers. Events from various input devices are abstracted into easy-to-use structures. LibGII also allows the application to join streams together, receiving input from an arbitrary combination of devices.
LibGII consists of a main library (libgii.so) and a multitude of dynamic drivers. The library then loads the necessary "drivers" for the requested input.
LibGII is the result of splitting event functionality from LibGGI in order to further abstract it. Thus its design is deliberately similar to LibGGI, which uses LibGII for input (and LibGG, a library of LibGGI/LibGII common functions) internally.
The following document is an attempt to give a detailed view of the reasons behind LibGII's design, and its internal structures and mechanisms. All the info reported here result from the author's reading and (hopefully) understanding of the code. If something is wrong, incomplete, or ambiguous, feel free to notify the author.
The GII event model
Event semantic
As GII aims to fully abstract input events on a computer system, it must provide the developer with a general event model that will encompass all kinds of event sematic found either in hardware or software input devices. GII defines the following semantics, or categories into one of which any event may be classified:
- Key event
generated by a keyboard device where keys are bound to a code, symbol, or character, and may be pressed and released, possibly with modifiers (SHIFT, CTRL).
- Pointer motion event
generated by devices that give a position on several axis and reflect the motion of a pointing device. Typically a mouse, with at least 2 axes or dimensions.
- Button event
devices (typically pointer devices) may have a set of buttons which can be, just like keys, pressed and released. However, unlike keys, buttons are not bound to a code, symbol, or character.
- Exposure event
This event is specific to visuals and is not directly mapped from a physical device. It is sent to an application when part of the screen was hidden for any reason and it becomes visible again. A part of it might need to be repainted.
- Valuator event
These events are sent by any kind of captor that measures physical quantities. Typically, these are used to monitor sets of thermometers in the fission core of nuclear power plants... (not :-) Valuator events are meant for high sampling rates of many variables, but are also useful with any device more complicated than a wheel mouse.
Command and Information event
This family of events is used to exchange information with devices or drivers, and also to issue them commands. It is useful to query or set states on a software-configurable device.
Most applications involving user interaction should be happy with these.
Event types
Along with the semantic of an event, there are different kind of actual event that may occur. A key can be pressed or released, for example, or a pointing device will notify movement, either in relative or absolute position. Thus, for each event category, a set of possible events is defined.
- evInformation
More on these later.
- evExpose
More on this later.
- evKeyPress, evKeyRelease, evKeyRepeat
These events belong to the key event group. They are delivered when a key on a device has changed state.
- evPtrRelative, evPtrAbsolute
These events report pointer (mice, etc.) motion in terms of the x, y, z coordinates and the wheel values of the device. The motion described may be relative (offset from the current location) or absolute (a specific location on the 'screen'), depending on whether the event is of type evPtrRelative or evPtrAbsolute, respectively.
LibGII itself does not attempt to interpolate or keep track of the current pointer position. (This is the responsibility of either the application itself or LibGII filter library.) However, in some situations LibGII may simultaneously report both relative and absolute pointer motion for the same input.
- evPtrButtonPress, evPtrButtonRelease
They belong to the Button event category.
- evValRelative, evValAbsolute
More on those later.
The number of event types is limited to 32, so that they can be grouped in masks on a 32bits int. Only 12 are used right now.
Event data
Events carry two sets of data, common event data which all events have, and type-specific data.
Common fields are:
- size : the size of this event in bytes (limit 256). Note that the event structure size is constant. This value only gives the number of bytes relevant for this particular event.
- type: the type of the event (one of the ev* decribed above).
- origin: the origin of the event (see below).
- target: the target of the command.
- error: if an error occured (?? people will want to know more about this - me too)
- time: A non-monotonic timestamp indicating when the event was generated.
Event specific data:
- PtrButton
the number of the button that was pressed, or released, depending on the event type.
- PtrMovement
the movement in x, y, z and wheel, either absolute or relative.
- Key
the key symbol, value and number, along with the current active modifiers (ALT, SHIFT, CTRL,...). The developer doesn't have to keep track of the state of these modifiers himself.
- Valuator
the readings of some or all of the quantities monitored by the measuring device. At most 32 quantities with contiguous indices may be reported. Devices reporting more than 32 values will generate several events with an index offset set accordingly.
- Command/Information
a code identifying the command or event and specific data, if any.
A closer view on input sources
Input source
In GII, an input source is an abstract link to one or more event source(s). What we call an event source is a single instance of a GII module that is able to generate events by some means. Such a module is in most cases a software driver for a system component, laying between the application with the GII event model and the system specific event source with its particular model. For example, the linux keyboard module can provide a key input source to any GGI application by knowing two things:
- how to invoke the linux keyboard subsystem (/dev/kbd...)
- how to map its events into the GII world.
An input source manages an event queue, where events generated by event sources are... well... queued! The application just has to read or poll the event sources to get the events one after another. (Polling should be done at regular intervals by the application to ensure that the event source is properly processed.)
In the GII input source abstraction, an event source is atomic in the sense that it refers to a single module instance, usually wrapping a whole subsystem. But this subsystem can be fairly complex and subdivided into many software or hardware components. Let us consider for example linux-evdev LibGII module. Ultimately, all events in the linux system will be accessed through the evdev interface. So the single linux-evdev module intance would pass us all events occuring on the system. LibGII inputs are written such that this single access point can address multiple distinct devices that might need to be identified individually.
This leads us to the notion of device. Devices are the building blocks for event sources, A single event source may be composed of several devices. For example, the module wrapping the xlib events has two devices, the XWin Keyboard and the XWin Mouse. The first will generate key events, while the second will report both the pointer movement and button state -- however both kinds of events will enter the application on the same LibGII handle.
Understanding origin
Simple applications usually read events from an input source without caring much about the actual event sources. They just need to be notify about mouse motion and key strokes. LibGII gets more mileage out of this trivial programming paradigm by allowing multiple handles to be merged into a single handle. So, even if two devices use entirely different LibGII modules, they may be made to appear to an application to be part of the same unified input handle.
However, there are cases where the application has to know a bit more about the exact composition of an input source. Suppose that a user has two mice which are connected to the same input source, typically a touchpad and a serial mouse. The application can simply listen to pointer events, regardless of their origin, so that the user can control a single pointer with any of the two mice. In fact the application itself doesn't even notice that the user has two mice. This is hidden in the input source.
But the two mice can also be used to control two separate characters at the same time in a multiplayer game. So, they need to be differentiated somehow. A solution is to separate the input sources for each player. Problem: you have to poll two input sources in the application, which tends to complicate the application's codebase. A more elegant approach (LibGII's approach) is to know which device generated the event, and act accordingly.
When a module is instantiated, it is given a unique identifier in the current execution context. This identifier, called the origin, is used by the application to refer to a specific event source. If the same module is instantiated multiple times, each instance will have different origin.
This origin number refers to the first device in the input source. A unique origin is also assigned to each of the device instances in the event source. In the current implementation, the origin of the n-th device is the origin of the event source plus n, with a comfortable limit of 256 devices. The first device's origin can be seen as a mask for event sources. They are always multiples of 256. When an event is delivered to the application, the origin field can be checked to know both the device it came from, and the event source that contains that device.
The origin is also used to communicate with an event source, for example to use force feedback in a joystick or steering wheel.
Masks
An event mask is a set of bit flags identifying which categories of events the application.
Event masks serve two purposes. The first is to eliminate excess traffic/system load. For example as one possible worst case, when you have an input source which is separated from your application by a wide area network -- if you are only interested in the keyboard events, you can stop the mouse events from being sent from the far end in the first place. Another worst case is when you have a VERY talkative device on a slow CPU.
The second is as a convenience, to allow the application to access only the events which it wants at a particular moment, so, for example, an application can ask for any keyboard events without having to process mouse movement event first. Note that setting the event mask on an event source does not guarantee that the application will never find such an event in the event queue... there are various corner cases where unwanted events may pop up from time to time. An application should always ask for exactly the events which it wants to process.
Filters
The GII filter is a powerful way to interact on the event queue, without the event sources or the application being aware of it. A filter is actually a module, just like a normal event source, but it does not generate events from the external world. Instead it can intercept events as they are queued and perform any kind of modification, including eating the event so it is not passed to any other filters or to the application.
How can it be used? For example you can invert the directions of your friend's mouse at no cost, or remap your keyboard. An application requires a mouse and you don't have one: just put a filter that intercept keystrokes, and generate fake pointer movements on Ctrl-Shift-<arrows>. More generally it can be used to reconfigure somehow the input of an application that was a bit too strict about the way it handles inputs, e.g. a game with an annoyingly large joystick dead-zone.
Other examples are a filter that saves events to a file (for later playback) or another one that repeats events on network socket to allow remote monitoring by a third party.
A real case study
Santa brought us the latest Santa Claus simulator. A great game indeed. And it uses GII, which makes it even better.
At this point, two scenarii.
We are a hardcore gamer, inclined to get the best out of the Santa simulator. Consequently, we choosed the special pack including the software, and the latest "Ultimate All-in-One Santa AirFight Controller - When Xmas delivery is serious business". Let us admire this beauty:
- 3-axis stick up/down left/right roto, with 4 buttons and force feedback, for precise droppings in extrem conditions.
- 4 throttles with 2 buttons on each, for a better control of each reactor (or deer).
- An sticker for your car, a field in which GII is irrelevant.
All these features are used in the game, so there is a one-to-one match between them and a game control. This module would generate pointer motion and button events, just like any joystick. So it could be used transparently as a simple joystick by any other application.
But wait, there is more! It's called the second scenario.
We're a casual gamer. This simulator is probably fun, but the pack was definitely overkill.
However, we have an old joystick and a couple of wheel mice. Hey, these wheels would surely make pretty handy throttles! Let's plug that all together, set the GII_INPUT to reflect our new configuration and run our simulator.
Now the application scans through the input source. The user goes to the configuration menu where the app displays all available devices with the type of event they generate, and on the other side all the controls used in the game. The user can associate devices to controls, provided that they have the common semantic: axis are axis, and buttons are buttons. But the mapping is up to the user. So he can disable force feedback, assign the second mouse wheel to reactor 3 and 4, etc... You get the idea.
Conclusion: The application was designed to map arbitrary event sources to controls in the game, allowing any device to be used for these controls. It didn't have to be specially designed around the Santa Controller, but can make use of all its features. Now the inventive user can come up with a messy set of input devices that will cooperate wonderfully on the application.
The application is really not cooperative? So be it. A custom filter module will do the job.
Using GII in an application
Using LibGII as a standalone event library
While LibGGI relies on LibGII, the latter can be used independently as an event library for a non graphic application, or with another graphic subsystem. LibGGI automatically set LibGII up at initialization, but if LibGGI is not used, LibGII has to be initialized by the programmer.
The minimal GII program will look like this:
#include <ggi/gii.h> int main(int ac, char **av) { if ( giiInit() < 0 ) exit(1); giiExit(); return 0; }
Most LibGII functions return 0 to indicate success, and a negative gii-error(3) code to indicate errors. Return codes greater than 0 are usually additional hints or other non-negative integer data.
A list of error codes and descriptions can be found in the gii-error(3) manual.
When using LibGGI, an input source is automatically bound to the visual, with default devices opened. All the LibGII functions have a LibGGI equivalent which takes a ggi_visual_t instead of a gii_input_t. Of course, other inputs can still be opened manually.
Opening
Stay tuned.
Merging and Splitting
When an input or filter module is successfully opened (with the giiOpen(3) function), it creates an input source. In the case of a filter, this input source will hardly provides any event, since the only time a filter can generate an event is when an other event is queued on the input source.
As we said earlier, an input source can be composed of several event generators. This is achieved by merging input sources together with the giiJoinInputs function. This input source will recieve all events generated by the inputs and filters it contains, and pass them through the filters. This allow an application to read all its events from a single source, regardless of the number and variety of devices it is actually bound to.
Splitting (with the giiSplitInputs function) is necessary when you want to drop a single source. A new input source is created, bound only to the chosen source, which is removed from the former input. This input can then be closed.
Note that the ggi interface does not allow to split inputs associated to a visual directly. Additional inputs may be joined, but to close individual inputs, you have to use the ggiDetachInput function. This function removes all inputs from the visual and returns a gii_input on which all gii function apply. Event functions will not work then on the visual itself.
Event Management - the Lost Art of Polling
Stay tuned.
Writing an input module
Before we describe the module writing process, it is worth taking a closer look at how GII works internally.
Event queue mechanism
Each gii_input maintains a set of event queues, each queue corresponding to a single event type. All the events of this type will be queued in that queue. These queues use circular buffers to store events. When an event is generated by a device, it is queued appropriately by the library. Having a separate queue for each event type prevents events of one type from pushing events of another type out of the event queue when it fills up -- so your mouse events don't pile up and delete your keyboard events.
When an application reads an event, these queues are scanned and the next event, chronologically, is removed and returned to the caller. It works more or less like a fifo, written to by input modules and read by applications.
Note that no dynamic memory is ever allocated throughout the process. Input modules will be given the event structures to fill when they need to report an event, and the user must provide its own event structure to read one. Actually, dynamic allocation occurs, but only when the queues are created, which happens only when the first event of a kind must be queued and the corresponding queue doesn't exist. This cuts down on the actual runtime processing requirements of event sources.
Writing a module
The first thing you need to do before writing a module is to define what devices are exposed to GII and what events they generate.
A good separation between a device features is important to allow applications to have precise handling of there input sources. For example, it is a good idea to define two devices for a 2-stick joystick, so that the application can make a distinction between them and address them individually. Remember that the origin of a device is the smallest unit an application can address, so of both sticks have the same origin (they are the same gii device) and send the same axis data (x, y, rotation and throttle), the application will not be able to know which one was moved.
On the other hand, excessive device definitions are not a good thing either. One could imagine that a mouse input module would define one device for the axes and one for the buttons, but it makes it more difficult for a sophistciated application as it has to make a link between the buttons and movement device for normal pointer operations.
When you define your devices, think of them not only as event providers, but also as event recievers. Things like joystick calibration, force feeback notification, etc. imply that events will need to be targeted at devices, so in our two-joystick example if there are two separate force feedback mechanisms this is more reason to identify them with two separate device origins.
Defining your devices
Once you know which devices we need for your module, you'll have to fill in a gii_cmddata_getdevinfo for each of them:
typedef struct { char longname[75]; char shortname[5]; gii_event_mask can_generate; uint32 num_buttons; /* Maximum number of buttons. */ uint32 num_axes; /* Maximum number of axes. */ } gii_cmddata_getdevinfo;
This structure will be sent to the application in a information event on demand or when the module state changes.
The longname and shortname have a limited size, and must terminate with