Writing a Mouse-Sensitive Application

Terminal-oriented programs tend to have an unwieldy interface, while writing X-Windows applications is difficult. By using the gpm client library you can easily turn a text-oriented program into an easy-to-port mouse-sensitive application.

The Linux text console is more than a bare terminal. One of its most important features is the availability of a mouse device. In addition to supporting selection, the mouse can be used to interact with user programs. If your computer runs the gpm server your programs can easily benefit from mouse availability under both the Linux console and xterm, and run without complaint under other environments.

Figure 1. An application using libgpm

The main difference between a conventional text program and a mouse-sensitive application is the way they process input—while the former reads from stdin and writes to stdout, the latter must multiplex input from different sources—we can call it an “event-driven” application. I will consistently use “program” to refer to a stdin-driven process and “application” to refer to an event-driven one.

The gpm client library is meant to allow programmers to easily turn a program into an application by changing only a few lines of original source code. Alternatively, it offers complete support to developers designing an application from scratch. Portability is a major issue here, because you can be tempted to build a nice full-featured application for the Linux console, which reveals itself as completely unusable when you remotely log in to your PC from within an xterm or a bare vt100. Some care must be taken to avoid this, since a networked Linux computer can easily be used from a tty which has nothing to do with its own console.

The internal structure of a console application is represented in Figure 1, which outlines what changes required in the original program so it can respond to mouse events. As you can see, all mouse support code could be hidden in a separate module, and mouse-related code in the main body of the source is limited to the following calls:

  • Gpm_Open() The open function should be called before reading any input. It is used to connect to the daemon's socket and performs all the setup needed to get back events from the gpm daemon.

  • Gpm_Getc() Any call to getc() and to getch() should be replaced by the Gpm_-prefixed function. The replacement code manages multiple inputs and dispatches mouse events as needed—more on this later.

  • Gpm_Close() Before exit()ing, the mouse connection should be closed. This call may be omitted, though it isn't nice to do so.

When writing portable code, these few modifications could be masked out as suggested in the code fragment below. Its role is to define function names which are independent of mouse availability. Such preprocessor-specific code would better reside in a header file, to avoid ugly #ifdefs in the actual source. The approach of choice is to hide Gpm_Open in local_mouse_init, because setting up is more than a function call; conversely, local_mouse_close is a syntactic place holder.

Any other code referring to the mouse can be put in a different source file from the general application code. A correct Makefile (possibly through autoconf) can easily choose which files need to be compiled and which preprocessor defines are needed, without cluttering the code with #ifdef/#endif.

#ifdef CFG_MOUSE<\n>
#    define local_wgetch(w) Gpm_Wgetch(w)<\n>
     extern int local_mouse_init(void);<\n>
#    define local_mouse_close() Gpm_Close()<\n>
#    define local_wgetch(w) wgetch(w)<\n>
#    define local_mouse_init()  /* nothing */<\n>
#    define local_mouse_close() /* nothing */<\n>
Connecting through Gpm_Open

Choosing a good connection with your mouse device is tricky. The problem is getting the best event resolution while avoiding excessive of context switches. Some simple applications need to be told of only button-press events and can leave cursor-drawing to the server program; more complex applications, on the contrary, might want to be told of any single movement of the mouse, as well as button-press and button-release.

The Gpm_Open function gets as an argument a structure identifying the type of connection requested. The type of connection in turn is characterized by event masks—bitmaps identifying event types. With gpm, two mask are required—the mask of events you want to get and the mask of events you want to be handled in the default way.

The double mask is useful, because the default way is known. In particular, since you know that mouse motions cause the cursor to be drawn, you may often leave motion events to the default management, thus relieving your application of most of the work of handling the mouse.

In addition to event masks, the connecting application must specify two “modifier sets”, that is, sets of keyboard modifiers, such as shift, control, meta (alt) and so on. Within the gpm server, keyboard modifiers are used to multiplex applications on a single console. It is handy to be able to paste selected text in a mouse-sensitive application, while an application taking complete control of the user's pointer would irritate most of the customers.

Each gpm client is asked to specify a “minimum set” and a “maximum set”. The client specifically asks not to be informed about mouse events with less than the minimum set or more than the maximum set of attached modifiers. The minimum set will be 0 for most clients. The gpm-root menu drawer is a client with non-0 minimum mask. This gives selection mouse-only events when there is no other client. Thus, when running Emacs, you can use the Emacs mouse facility (by loading the library t-mouse.el, within the gpm distribution) and have access to selection and gpm-root; the lisp package accepts mouse-only and alt-mouse, the gpm-root server gets ctrl-mouse, and the internal selection mechanism gets any other events. Within this scheme, selection is a catch-all, as if it had an infinite maximum-modifiers mask.

Gpm_Open, then, keeps a stack of connection masks, so you can reopen the connection to modify your mask and get back to the previous behaviour on the next Gpm_Close invocation. This feature can be used to either increase or decrease the amount of events you get. Emacs, for example, disables event reporting in this way when it is stopped, to allow you to use selection normally with your shell. An application drawing a menu, by contrast, can only reopen the connection to get motion events while the menu is kept down. This stack-like feature is managed in the client library, so that misbehaving applications can't lock up the server.