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:

  1. how to invoke the linux keyboard subsystem (/dev/kbd...)
  2. 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.

  1. 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.

  2. 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 0. can_generate is a mask defining what kind of event this device is able to generate. If the device doesn't have buttons nor axes, these numbers must be set to 0, or GII_NUM_UNKNOWN if the numbers are not known.

If the device is a valuator, or can generate valuator events, a gii_cmddata_getvalinfo must also be defined:

typedef struct {
      
      uint32          number;         /* Number of the valuator */

      char            longname[75];
      char            shortname[5];

      gii_valrange    range;
      gii_phystype    phystype;

      sint32  SI_add,SI_mul,SI_div,SI_shift;

} gii_cmddata_getvalinfo;

typedef struct gii_valrange {
      sint32          min, center, max;
} gii_valrange;

The SI_* values are used to compute the floating point value of the reported values with the formula:

SI = (float)(SI_add+value[n])*(float)SI_mul/(float)SI_div*pow(2.0,SI_shift)

All these structures can be declared static in the module source.

Defining private data

Your module will most probably want to associate private data with an input, for example a file from which data is read to produce events, such as a serial port reading mouse data. The gii input structure has a void * priv field that the module is free to use for its specific needs.

Defining special commands

If your command doesn't carry additional data it must be OR'ed with GII_CMDFLAG_NODATA. Commands that are private to a specific module must also have the GII_CMDFLAG_PRIVATE flag set.

Module interface

The module may define a number of functions that will be called by the GII library. They are not all required, but a typical module should at least define GIIclose, GIIeventpoll and GIIeventsend.

int GIIclose(struct gii_input *inp);

This function is used when the device is closed. All you have to do is to perform any required cleanups like free privately allocated memory. It is not supposed to fail so the return value doesn't mean much and will not be checked. Anyway, it is a good practice to return 0 on normal operation.

int GIIhandler(struct gii_input *inp, gii_event *event);

If a module defines this functions, it is a filter. Before being queued, all events will be passed to this function. It is free to do whatever it wants, like altering the events, or posting other events. If this function doesn't return 0, then the event is eaten and will not be queued. Note that it will also not be delivered to remaining filters.

int GIIseteventmask(struct gii_input *inp, gii_event_mask evm);

A module may define this function if special internal operations may or must be performed to optimize event generation in case all event types are not needed, or new functionality must be invoked. As for GIIclose, the return value is not kept. Nonetheless you should return 0 by default or any gii-error(3) code. This should set the cureventmask field of the input if necessary.

gii_event_mask GIIgeteventmask(struct gii_input *inp);

If the event source mask is subject to change dynamically (plug and play devices on an USB bus) this function can be defined to let GII poll the current event mask.

int GIIsendevent(struct gii_input *inp, gii_event *event);

Event are sent to modules via this functions. The basic behaviour is to send to correct device or valuator information if required, or perform any module specific operation, like disabling a device, applying a force feedback, etc... When returning the device information, the event is evCommand and the code is GII_CMDCODE_GETDEVINFO.

int GIIgetselectfdset(struct gii_input *inp, fd_set *readfds);

If the module must wait on a file descriptor from which it will generate events, this function must be defined to let GII know about these descriptors. The expected behaviour is to set readfds to the fd (see select(2)) on which the source will be waiting, and return the highest of these descriptors.

gii_event_mask GIIeventpoll(struct gii_input *inp, void *arg);

When this function is called, the module must queue any pending event. It returns the mask of queued events. This function is must not be blocking.

int GIIdlinit(gii_input *inp, const char *args, void *argptr);

Whereas all previous functions are usually defined static this one must be named as is and will be looked for by the libgii dynamic module loader code. It will be called when a new instance of a module is opened and bound to a new gii_input.

The args string carries additional parameters that the module might want to use to alter its behaviour or configuration. For example, the input-tcp module needs a host and a port.

The argptr argument might be used to communicate special binary data to the module. This is the case for the X11 input module which needs a hook to the xwindow it is listening on.

The function basically works this way:

  1. allocate a private structure
  2. perform any subsystem initialisation (opening a special file,...)
  3. register all devices
  4. fill the input structure fields, callbacks and flags
  5. return 0 if everything is ok

The fields that a module can initialize are:

the callback functions

All the callbacks described above can be set. Only set the ones you want to override since they are all set to safe defaults. This is especially true for GIIseteventmask, GIIgeteventmask and GIIgetselectfd. These are actually only necessary to give the module a chance to dynamically invoke or dismiss devices dynamically.

targetcan

a mask of events the target can produce. If it is not the default value is 0, which means that the soure will never be polled.

flags

The only(?) flag currently defined is GII_FLAGS_HASPOLLED. If it is set, it means that the input can't wait for events on a file descriptor so select(2) will not work with this input since a call will never be interrupted. In this case, GII has to emulate blocking call to giiReadEvent or giiPollEvent by polling these devices regularly using small timeouts. The default value is 10ms but can be changed at compile time. Note that this is generally quite bad for overall system performance since a lot of CPU cycles will be wasted polling devices.

fdset, maxfd

the set of file descriptor on which the source is waiting and the highest of these descriptor. You don't necessarily have to define the GIIgetselectfd function. You can simply set these values and if they change, simply call _giiUpdateCache to notify GII. The default callback will be just fine. Remember that calling _giiUpdateCache will wake up all other modules in the input, so do it only when necessary.

libgii intraface

LibGII provides a few functions for module developers to access the internal implementation, that they we'll have to call in their callbacks.

void _giiEventBlank(gii_event *ev, int size);
int  _giiEvQueueAdd(gii_input *inp, gii_event *ev);

_giiEventBlank zeroes an event structure of the given size. The time is set to the current time, the origin to GII_EV_ORIGIN_NONE and the target to GII_EV_TARGET_ALL.

_giiEvQueueAdd post the given event to the input queue.

These functions are used in the poll callback or when an information request is submitted to the sendevent callback.

uint32 _giiRegisterDevice(gii_input *inp,
                          gii_cmddata_getdevinfo  *dev,
                          gii_cmddata_getvalinfo  *val);

int    _giiUnregisterDevice(gii_input *inp, uint32 origin);

These functions are used to define the devices of a module instance. For each device defined as we've seen before, there should be a call _giiRegisterDevice in the GGIdlinit function. The parameters must refer to static structures in the module code which should not be altered later on.

There is no need to unregister devices on close. This is done automatically by libgii. In fact _giiUnregisterDevice will only be used in cases where a device is dynamically disabled during the execution of a program, for example when recieving a special command event. Most modules will never use it.

_giiRegisterDevice returns the origin given to this device by GGI. It is usually a good idea to keep track of this result since it will most probably be needed to know which device corresponds to the target found in command events sent to the module.

void _giiUpdateCache(gii_input *inp);</term>

GII caches information about the event sources found in an input, like flags, event masks and more importantly the set of file descriptors on which modules are waiting. If for any reasons, one of these changes for a source, it must call _giiUpdateCache to force libgii to rescan all these values. Note that this will call the GIIgeteventmask and GIIgetselectfdset functions for the modules that define them. So do not call _giiUpdateCache in either of those functions.

This is usually used in the poll function if a file is closed and will not be read anymore. In that case the read fdset should be changed.

A few notes/questions on GII to the maintainers/developers

  • Should their be a way to send events to all the devices of a single source?

    My suggestion is to reserve 0x00 or 0xff in the origin for this. right now, origin 0x00 is not assigned to a device. This could be seen as addressing the whole source (e.g. for querying devices, rather than broadcasting to all sources)

  • The poll interval could be modified via an envvar. Actuallly many things could be controlled by envvar, like the size of the event queues.
  • What exactly is the GIIgetfdset suppposed to be there for? The same question holds for GIIgeteventmask.
  • How to have gii send a default response to devinfo and devval command? an internal helper could be defined.
  • I think GII_EV_TARGET_ALL should pass the event to all module but not put it on the queue.
  • GII_CMDCODE_GETDEVINFO doesn't carry data on request but also doesn't have the GII_CMDFLAG_NODATA bit set. Is it a sane exception?