Chapter 9. The Keyboard and Pointer

This chapter not only describes how to handle keyboard and pointer events but also describes many other topics related to these two input devices. In particular, it discusses X's use of keysyms as portable symbols for character encoding, keyboard remapping, keyboard and pointer “grabs,” and keyboard and pointer preferences. Internationalized keyboard input is described in Chapter 11.

In Chapter 3, “Basic Window Program” we showed you quite thoroughly how to deal with Expose events. But all we did with pointer and keyboard events was to exit the program. As you can guess, there can be more to it than that. This chapter describes and demonstrates the handling of keyboard and pointer events, describes keyboard and pointer mapping, and describes how to set keyboard preferences. Internationalized keyboard input is described in Chapter 11, Internationalized Text Input, although it depends on many concepts described in this chapter.

The Keyboard

The keyboard is an area like color, where X clients have to be made portable across systems with different physical characteristics. In the case of the keyboard, these variations are in two areas: whether the keyboard provides KeyPress and KeyRelease events or just KeyPress events, and the symbols on the caps of the keys.

Almost all serious workstations provide both KeyPress and KeyRelease events. Some personal computers, however, may not. Therefore, avoid depending on KeyRelease events if you want your client to be portable to the lowest classes of machines.

The second problem is adjusting for variations in the keys available on each keyboard and the codes they generate. We'll start explaining how this problem is solved by describing the contents of a key event.

KeyPress and KeyRelease events are stored in XKeyEvent structures, shown in Example 9-1. Each key event contains the keycode of the key that was pressed and state, a mask which indicates which modifier keys and pointer buttons were being held down just before the event. A modifier key is a key like Shift or Control that can modify the meaning of a key event. In addition to their effect on the processing of other keys, the modifier keys also generate key events with unique keycodes.

Example 9-1. The XKeyEvent structure

typedef struct {
   int type;               /* Of event */
   unsigned long serial;   /* Last request processed by server */
   Bool send_event;        /* True if from a SendEvent request */
   Display *display;       /* Server connection */
   Window window;          /* "event" window reported in */
   Window root;            /* Root window event occurred on */
   Window subwindow;       /* Child window */
   Time time;              /* Milliseconds */
   int x, y;               /* Coordinates in event window */
   int x_root, y_root;     /* Coordinates relative to root */
   unsigned int state;     /* Key or button mask */
   unsigned int keycode;   /* Detail */
   Bool same_screen;       /* Same screen flag */
} XKeyEvent;
typedef XKeyEvent XKeyPressedEvent;
typedef XKeyEvent XKeyReleasedEvent;


The keycode member of XKeyEvent is a number between 8 and 255. The keycode is the same regardless of whether a key is pressed or released. The keycode for each physical key never changes on a particular server, but the key with the same symbol on it on different brands of equipment may generate different keycodes. For portability reasons and because the keycode by itself without the state of the modifier keys does not provide enough information to interpret an event, clients cannot use keycodes by themselves to determine the meaning of key events.

Instead of using the keycode alone, X clients call XLookupString() to translate the key event into a keysym. A keysym is a defined constant that corresponds to the meaning of a key event. For example, the translation of the keycode generated by the “a” key on any system would be XK_a if no other keys were being held and XK_A if the Shift key were being held or if Shift Lock was in effect (all keysyms begin with XK_). The translation of the keycode for the Return key (which is labeled Enter or just with an arrow on some keyboards) would be XK_Return. The Enter key on the keypad, if any, would have the keysym XK_KP_Enter. Example 9-2 shows some keysym definitions. All keysyms are defined in <X11/keysymdef.h>.

Example 9-2. Some sample keysym definitions

#define XK_BackSpace    0xFF08   /* Back space, back char,... */
#define XK_Left         0xFF51   /* Move left, left arrow */
#define XK_Undo         0xFF65   /* Undo, oops */
#define XK_Num_Lock     0xFF7F
#define XK_KP_Multiply  0xFFAA
#define XK_Shift_L      0xFFE1   /* Left shift */
#define XK_space        0x020    /* Space */
#define XK_numbersign   0x023    /* # */
#define XK_3            0x033
#define XK_question     0x03f    /* "?" */
#define XK_A            0x041
#define XK_e            0x065

XLookupString() also provides an ASCII string that corresponds to the keysym or NULL if there is no associated string. By default, all the keys that have ASCII values will have that value as their string. For example, XK_A would have the string “A”, XK_ampersand would have the string “&”, and XK_4 would have the string “4”. XK_Return, XK_Escape, and XK_Delete have ASCII values, but they are not printable. XK_Shift_L (the Shift key on the left side of the keyboard) would not normally have an associated string.

The ASCII value for a particular keysym as returned by XLookupString() can be changed by the client using XRebindKeysym(), and it can be a string of any length, not just a single character. Even though keysyms like XK_F1 (the F1 key) have no default ASCII mapping, they can be given strings. This mapping would apply only to the client that calls XRebindKeysym().

With these introductory comments, we'll move right to the examples that handle keyboard input. Then we'll return to discuss keysyms in more detail and the various keyboard mappings and how they can be changed.

Simple Keyboard Input

Example 9-3 shows the framework of the code for translating a keyboard event into both a keysym and an ASCII string. You will need the keysym to determine what the keystroke means, and the ASCII string if the keystroke is a printable character. If the keystroke is printable, the program would append the ASCII interpretation of the key event to the end of the result string (and display it). If the keystroke is a modifier key being pressed, the event can normally be ignored, since the modifier status of events on other keys is already dealt with by XLookupString(). But XK_Delete or XK_Backspace would indicate that a character should be removed from the string.

The function keys are not initially mapped to ASCII strings and can be ignored, but if the client allows the user to map them to an arbitrary string, it should treat them like any other printable character.

You may notice in Example 9-3 that XLookupString() returns something called an XComposeStatus. Some keyboards provide a Compose key, which is used to type characters not found on the keyboard keys. Its purpose is to make it possible to type characters from other languages without disturbing the normal operation of the keyboard. As it usually works, you press the Compose key followed by some other key to generate characters like é. A table is usually provided which tells you which keys correspond to each foreign character. Processing of multikey sequences using the Compose key is now supported as an input method through the Release 5 internationalization features. So the XComposeStatus argument of XLookupString() is now just a dummy.

Example 9-3. Translating a key event to keysym and ASCII

Display *display;
XEvent event;
char buffer[20];
int bufsize = 20;
KeySym key;
XComposeStatus compose;
int charcount;
   .
   .
   /*  Open display, create window, select, map */
XNextEvent(display, &event);
switch( event.type ) {
   .
   .
   .
case KeyPress:
   charcount = XLookupString(&event, buffer, bufsize, &keysym,
         &compose);
   /* Branch according to keysym, then use buffer
    * if the key is printable */
   break;
case MappingNotify:
   XRefreshKeyboardMapping(&event);
   break;
}


Keysyms for accented vowels, tildes, and most combinations found in Western languages are provided in the LATIN1 set. If you want to display an accented e, for example, the keysym is XK_eacute. If the desired character is not present in the desired font, the client can prepare two or more text items for XDrawText() for displaying the desired overstrike character and use the delta member to move the second character back over the first. XDrawText() is capable of drawing in a different font for each text item, in case the desired accent is in a separate font from the desired character.

Getting a String -- A Dialog Box

Let's say you are porting a nonevent-driven program to X, and you have a routine called get_string that gets an ASCII string from the user. It gets the entire string before returning. But under X, the user might stop typing midway through, pop some other window on top to check some bit of information, then pop the original application back on top. That means you need to handle exposure in the middle of the input string, which, in turn, means you need a function that remembers the string's state so that it can be redrawn in the get_string routine. You also have to be prepared in case the keyboard gets remapped by some other client. Suddenly your tiny subroutine to get a string now must be integrated into the event loop.

Example 9-4 is a modification to the basicwin program described in Chapter 3, “Basic Window Program” that puts up a pop-up dialog box. If the user presses a button in the basicwin window, the application puts up a dialog box, which the user can type into until a carriage return is typed. All the printable characters except Tab are supported, and Delete or Backspace operate as would be expected. The code allows the user to type the string while also handling the other events that might occur. This is done by placing the code for popping up the dialog in the branch for ButtonPress events, placing the code to redraw the dialog string in the branch for Expose events, and placing the code to process key events in the branch for KeyPress events.

Example 9-4. Implementing a dialog box

/* Other include files */
#include <X11/keysym.h>

/* Other defined constants */
#define MAX_POPUP_STRING_LENGTH 40
#define MAX_MAPPED_STRING_LENGTH 10
/* Global variables display and screen */
void main(argc, argv)
int argc;
char **argv;
{
   /* Declarations from basicwin */
     .
     .
     .
   /* The following are for pop-up window */
   static Window pop_win;
   char buffer[MAX_MAPPED_STRING_LENGTH];
   int bufsize=MAX_MAPPED_STRING_LENGTH;
   int start_x, start_y;
   KeySym keysym;
   XComposeStatus compose;
   int count;
   unsigned int pop_width, pop_height;
   char string[MAX_POPUP_STRING_LENGTH];
   int popped = False;
   int length;
   /* Create main window (win) and select its events */
     .
     .
     .
   XMapWindow(display, win);
   /* Get events, use first to display text and graphics */
   while (1)  {
      XNextEvent(display, &report);
      switch  (report.type) {
      case Expose:
         if (report.xexpose.window == pop_win) {
            /* If pop_win is nonzero, it has been created,
             * and window in Expose is never zero */
            if (popped)
               XDrawString(display, pop_win, gc, start_x,
                  start_y, string, strlen(string));
         }
         else {  /* It's the main window */
            /* Refresh main window as in basicwin */
         }
         break;
      case ConfigureNotify:
         /* Same as in basicwin */
           .
           .
           .
         break;
      case ButtonPress:
         /* Put up pop-up window, create if necessary */
         if (!pop_win) {  /* Create it and pop it */
      /* Determine pop-up box size from font information */
      pop_width = MAX_POPUP_STRING_LENGTH *
            font_info->max_bounds.width + 4;
      pop_height = font_info->max_bounds.ascent +
            font_info->max_bounds.descent + 4;
      pop_win = XCreateSimpleWindow(display, win, x, y,
            pop_width, pop_height, border_width,
            BlackPixel(display, screen),
                WhitePixel(display, screen));
      /* Calculate starting position of string in window */
      start_x = 2;
      start_y = font_info->max_bounds.ascent + 2;
      XSelectInput(display, pop_win, ExposureMask | KeyPressMask);
         }
         /* If window is already mapped, no problem */
         XMapWindow(display, pop_win);
         popped = True;
         break;
      case KeyPress:
         if (report.xkey.window == win) {
            /* Key on main window indicates exit */
            XUnloadFont(display, font_info->fid);
            XFreeGC(display, gc);
            XCloseDisplay(display);
            exit(1);
         }
         else {
            /* Get characters until you encounter a
              * carriage return; deal with backspaces, etc. */
            count = XLookupString(&report, buffer, bufsize,
                  &keysym, &compose);
            /* Now do the right thing with as many
             * keysyms as possible */
            if ((keysym == XK_Return) || (keysym == XK_KP_Enter)
                  || (keysym == XK_Linefeed)) {
               XUnmapWindow(display, pop_win);
               popped = False;
               printf("string is %s\n", string);
               break;
            }
            else if (((keysym >= XK_KP_Space)
                  && (keysym <= XK_KP_9))
                  || ((keysym >= XK_space)
                  && (keysym <= XK_asciitilde))) {
               if ((strlen(string) + strlen (buffer)) >=
                  MAX_POPUP_STRING_LENGTH)
                  XBell(display, 100);
               else
                  strcat(string, buffer);
            }
            else if ((keysym >= XK_Shift_L)
                  && (keysym <= XK_Hyper_R))
               ;/* Do nothing because it's a modifier key */
            else if ((keysym >= XK_F1)
                  && (keysym <= XK_F35))
               if (buffer == NULL)
                  printf("Unmapped function key\n");
               else if ((strlen(string) + strlen (buffer)) >=
                     MAX_POPUP_STRING_LENGTH)
                     XBell(display, 100);
               else
                  strcat(string, buffer);
            else if ((keysym == XK_BackSpace) ||
                  (keysym == XK_Delete)) {
               if ((length = strlen(string)) > 0) {
                  string[length - 1] = NULL;
                  XClearWindow(display, pop_win);
               }
               else
                  XBell(display, 100);
            }
            else {
               printf("keysym %s is not handled\n",
                     XKeysymToString(keysym));
               XBell(display, 100);
            }
            XDrawString(display, pop_win, gc, start_x,
               start_y, string, strlen(string));
            break;
         }
      case MappingNotify:
          XRefreshKeyboardMapping(&report);
         break;
      default:
         /* All events selected by StructureNotifyMask
          * except ConfigureNotify are thrown away here,
          * since nothing is done with them */
         break;
      } /* End switch */
   } /* End while */
}


Example 9-4 takes advantage of the fact that the keysyms are constants arranged in groups with consecutive values. By looking for any keysym in a given range, you do not need to specify every keysym you intend to match.

Notice that the program uses keysyms to match all the keystrokes and then does different things depending on whether the keysym is a normal key, a modifier key, a function key, a delete key, or an enter key. If the key is printable, it copies the ASCII values returned by XLookupString() into the result string.

This program does have some weaknesses. [27] One of them is that it redraws the entire string instead of just the character being changed. Secondly, using XTextItem structures for each character and calling XDrawText() instead of XDrawString() would support Tab characters and functions keys mapped to strings. Since a tab has to be expanded into a number of spaces before being drawn and function keys may be mapped to arbitrary strings, it is difficult to properly implement them with the approach we have used in Example 9-4.

The Keyboard Mappings

As we have said, there are several translations that take place between the pressing of a key and its interpretation within a program. The first, the mapping between physical keys and keycodes, is server-dependent and cannot be modified. A client cannot determine anything about this first mapping, and it is just a fact that certain physical keys generate certain keycodes. The second mapping, keycodes to keysyms, can be modified by clients but is server-wide, so it usually is not modified. The specification of which keycodes are considered modifiers is also part of the second level of mapping, because it affects the mapping of keycodes to keysyms. The third mapping, from keysyms to strings, is local to a client. This is the mapping with which a client can allow the user to map the function keys to strings for convenience of typing.

We are going to describe the mapping between keysyms and strings first, because this is the mapping that applications are most likely to change. Following that, we'll describe what you need to know about the keycode-to-keysym mapping and modifier mapping to write normal applications. These mappings are normally only changed by clients run from the user's startup script that do nothing else, because they change the keyboard mapping for all applications.

After that, Sections 9.1.3.1 and 9.1.3.2 are optional reading. They describe the background and development of keysyms and how to write special purpose programs to change the server-wide mapping of keycodes to keysyms and the modifier mapping. These techniques are not needed in normal applications.

Keysyms to Strings

The default mapping of keysyms to ASCII is defined by the server. The ASCII representation of the keys on the main keyboard are the ASCII codes for the single characters on the caps of the keys. Keysyms that do not have ASCII representations, such as the function keys, initially have mappings to NULL, but they can sometimes be mapped to strings, as we'll describe. However, the modifier keys on some machines cannot be mapped to strings at all.

Any client in which the user is expected to type a large amount of text should support remapping of the function keys to strings. XRebindKeysym() is the only function that can change this string, the one returned by XLookupString(). The string can be any length. This change affects only the client that calls XRebindKeysym().

Example 9-5 is a short code sample that demonstrates how to remap function keys to strings. It binds the string “STOP” to Shift-F1 and “ABORT” to CTRL-Shift-F1. Since keyboards may have two Shift and two Control keys, one on each side, the process has to be done for both. Mapping the function keys combined with modifiers will not work on all servers. (On the Sun sample server, this code results in STOP being generated when F1 is pressed with any modifiers and ABORT never being generated.) However, mapping of unmodified function keys should work on all servers.

Example 9-5. Mapping keys to strings

#include <X11/keysym.h>

  .
  .
Display *display;
KeySym modlist[2];           /* Array of modifier keysyms */
unsigned int string_length;
unsigned int list_length;
  .
  .
/* Open display */
  .
  .
/* Map Shift-F1 to "STOP"  */
string_length = 4;
list_length = 1;
modlist[0] = XK_Shift_R;   /* Do right shift key */
XRebindKeysym(display, XK_F1, modlist, list_length, "STOP",
      string_length);
modlist[0] = XK_Shift_L;   /* Do left shift key */
XRebindKeysym(display, XK_F1, modlist, list_length, "STOP",
      string_length);
/* Map CTRL-Shift-F1 to "ABORT"  */
string_length = 5;
list_length = 2;
/* Both right pressed */
modlist[0] = XK_Shift_R; modlist[1] = XK_Control_R;
XRebindKeysym(display, XK_F1, modlist, list_length, "ABORT",
      string_length);
/* Left Shift, Right Control */
modlist[0] = XK_Shift_L; modlist[1] = XK_Control_R;
XRebindKeysym(display, XK_F1, modlist, list_length, "ABORT",
      string_length);
/* Right Shift, Left Control */
modlist[0] = XK_Shift_R; modlist[1] = XK_Control_L;
XRebindKeysym(display, XK_F1, modlist, list_length, "ABORT",
      string_length);
/* Both left pressed */
modlist[0] = XK_Shift_L; modlist[1] = XK_Control_L;
XRebindKeysym(display, XK_F1, modlist, list_length, "ABORT",
      string_length);

XLookupString() currently uses a linear search to find the keysym corresponding to each key event, and each call to XRebindKeysym() causes XLookupString() to run somewhat slower. This problem is exacerbated if you want a function key (or any other key) to generate the same string with any combination of modifier keys, since this requires 15 or more calls to XRebindKeysym().

The Modifier Keys

A keysym represents the meaning of a certain combination of a key and modifier keys such as Shift and Control. For example, XK_A represents the letter “a” pressed while the Shift key is held down or while Shift Lock is on. As in this example, the keysym depends on what modifier key is being held.

Although Shift is present on all keyboards and Control on most, the remaining modifier keys are not standardized. There may be Meta, Hyper, Super, Left, Right, or Alternate keys. X, however, has a fixed set of logical modifiers, listed in the first column of Table 9-1. Each of these logical modifier symbols corresponds to a bit in the state member of the XKeyEvent structure. On each keyboard, there is a mapping between the physical modifier keys and these logical modifiers. Table 9-1 also shows the keysyms of the keys that are by default mapped to the logical modifiers on a Sun-3 system and the corresponding keycodes for that system. You can use the xmodmap command without arguments to find out the default modifier mapping on any system.

Table 9-1. Logical Modifiers and a Typical Modifier Key Mapping

Logical Modifier

Default Keycodes of Modifier Keys (Sun-3)

Modifier Keysym (Sun-3)

ShiftMask

(0x6a), (0x75)

XK_Shift_L , XK_Shift_R

ShiftLockMask

(0x7e)

XK_Caps_Lock

ControlMask

(0x53)

XK_Control_L

Mod1Mask

(0x7f), (0x81)

XK_Meta_L , XK_Meta_R

Mod2Mask

(unmapped)

Mod3Mask

(unmapped)

Mod4Mask

(unmapped)

Mod5Mask

(unmapped)

Each keycode may have a list of keysyms, one for every logical modifier. Each list, of varying length, conveys the set of meanings for the key with each of the modifier keys pressed. This array of keysyms for each keycode is initially defined by the server. In most cases, only two keysyms are defined for the keys that represent single printable characters and only one for the rest.

Keycodes to Keysyms

Clients can change the mapping of keycodes to keysyms (with XChangeKeyboardMapping()), but they rarely do because this mapping is global to the server. This change would affect every client operating on the server. Every client would receive a MappingNotify event (regardless of whether they selected it or whether they actually use keyboard input) and must then get a new keysym table from the server with XRefreshKeyboardMapping(). (This table is stored in the Display structure and is used by XLookupString() and the other routines that return keysyms.) XRefreshKeyboardMapping() works by erasing the copy of the keyboard and/or modifier mappings that are present in the Display structure (the keysyms and modifiermap members). The next time that an Xlib call is made that requires either of these mappings, a request is made to the server, the new mappings are transferred to Xlib, and the pointers in the Display structure are reset to the new mapping data. Subsequent calls to access this data use the Display structure instead of querying the server.

One of few applications that might change the mapping between keycodes and keysyms would be an application that converted between QWERTY and DVORAK keyboard layout. These are the nicknames for two different layouts for the alphabetic characters on English language keyboards. The QWERTY keyboard in common use was originally designed to be slow enough so that mechanical typesetting machine operators would not be able to type fast enough to jam their machines. The DVORAK keyboard, on the other hand, was designed to place the most common letters in the English language under the home row of keys and is much faster.

Let's say a user wanted to use the DVORAK layout instead of the default, which is QWERTY. This application would not even need to create a window, but it would change the mapping of keycodes to keysyms with XChangeKeyboardMapping(). The user could then move the keycaps around on the keyboard or label them somehow. Except for calling XRefreshKeyboardMapping(), other applications would operate as usual. From then on, while the server was running, all applications would work properly with the DVORAK layout.

Background on Keysyms

Keysyms are a concept developed especially for X. It may help you to understand them better to read about how they were designed. But this is optional reading, and you can skip to “The Pointer” if you do not plan to write programs that change the mapping of keycodes to keysyms.

The keysyms are defined in two include files, <X11/keysym.h> and <X11/keysymdef.h>. Together these files define several sets of keysyms for different languages and purposes. There are sets for Latin, Greek, Cyrillic, Arabic, and so on, intended to allow for internationalization of programs. There are also sets for publishing and technical purposes, because these fields have their own “languages.” <X11/keysym.h> defines which character sets are active, and <X11/keysymdef.h> defines the symbols in all the sets. Only <X11/keysym.h> needs to be included in an application because it includes <X11/keysymdef.h>.

By default, the enabled sets of defined keysyms include the ISO Latin character sets (1-4), a set of Greek characters, and a set of miscellaneous symbols common on keyboards (Return, Help, Tab, and so on). These are sufficient for making an application work in any Western language. Symbols for Katakana, Arabic, Cyrillic, Technical, Special, Publishing, APL, and Hebrew character sets are defined in <X11/keysymdef.h> but are not enabled in <X11/keysym.h> and may not be available on all servers. This is because some C compilers have a limit to the number of allowable defined symbols.

Many of the keysym sets share keysyms with sets earlier in the <X11/keysymdef.h> include file. For example, there is only one XK_space keysym because a space is common to all languages. XK_space is in LATIN1 so that it is always available. The LATIN2 and LATIN3 sets are quite short because they share most of their symbols with the previous sets.

The Design of Keysyms

English language keyboards tend to be quite standard in the alphanumeric keys, but they differ radically in the miscellaneous function keys. Many function keys are left over from early timesharing days or are designed for a specific application. Keyboard layouts from large manufacturers tend to have lots of keys for every conceivable purpose, whereas small workstation manufacturers often have keys that are solely for support of some unique function.

There are two ways of thinking about how to define keysyms given such a situation: the Engraving approach and the Common approach.

The Engraving approach is to create a keysym for every unique key engraving. This is effectively taking the union of all key engravings on all keyboards. For example, some keyboards label function keys across the top as F1 through Fn, others label them as PF1 through PFn. These would be different keys under the Engraving approach. Likewise, Lock would differ from Shift Lock, which is different from the up-arrow symbol that has the effect of changing lower case to upper case. There are lots of other aliases such as Del, DEL, Delete, Remove, and so forth. The Engraving approach makes it easy to decide if a new entry should be added to the keysym set: if it does not exactly match an existing one, then a new one is created. One estimate is that there would be on the order of 300 to 500 miscellaneous keysyms using this approach, not counting foreign translations and variations.

The Common approach tries to capture all of the keys present on a number of common keyboards, folding likely aliases into the same keysym. For example, Del, DEL, and Delete are all merged into a single keysym. Vendors would be expected to augment the keysym set (using the vendor-specific encoding space) to include all of their unique keys that were not included in the standard set. Each vendor decides which of its keys map into the standard keysyms. It is more difficult to implement this approach, since a judgement is required whether a sufficient set of keyboards implement an engraving to justify making it a keysym in the standard set and which engravings should be merged into a single keysym. Under this scheme, there are an estimated 100 to 150 keysyms for an English language keyboard.

While neither scheme is perfect, the Common approach has been selected because it makes it easier to write a portable application. Having the Delete functionality merged into a single keysym allows an application to implement a deletion function and expect reasonable bindings on a wide set of workstations. Under the Common approach, application writers are still free to look for and interpret vendor-specific keysyms, but because they are in an extended set, application developers should be more conscious that they are writing applications in a nonportable fashion.

Conventions for Keysym Meaning

For each keycode, the server defines a list of keysyms, corresponding to the key pressed while various modifier keys are being held. There are conventions for the meanings of the first two keysyms in the list. The first keysym in the list for a particular key should be construed as the symbol corresponding to a KeyPress when no modifier keys are down. The second keysym in the list, if present, usually should be construed as the symbol when the Shift or Shift Lock modifier keys are down. However, if there is only one keysym for a particular keycode, if it is alphabetic, and if case distinction is relevant for it, then the appropriate case should be based on the Shift and Lock modifiers. For example, if the single keysym is an uppercase A, you have to use the state member of XKeyEvent to determine if the Shift key is held. XLookupString() should translate the event into the correct ASCII string anyway.

X does not suggest an interpretation of the keysyms beyond the first two and does not define any spatial geometry of the symbols on the key by their order in the keysym list. This is because the list of modifier keys varies widely between keyboards. However, when programming, it should be safe to assume that the third member in the keysym list would correspond to the key pressed with the next most common modifier available on the keyboard, which might be Control.

For keyboards with both left-side and right-side modifier keys (for example, Shift keys on each side that generate different keycodes), the bit in the state member in the event structure defines the OR of the keys. If electronically distinguishable, these keys can have separate keycodes and up/down events generated and your program can track their individual states manually.

Changing the Server-wide Keyboard Mappings

Both the keycode-to-keysym mapping and the modifier mapping affect all clients when they are changed by any client. That is why normal applications will not change them. Special purpose programs, however, can be written to change these mappings, usually to be run from a user's startup script. These sections describe how to write such programs. If you do not plan to write one, you can skip ahead to “Other Keyboard-handling Routines”

Changing the Keycode-to-Keysym Mapping

XChangeKeyboardMapping() changes the current mapping of the specified range of keycodes to keysyms.

Example 9-6 shows a simple program called mapkey that changes the keyboard mapping for all the applications running on the server. This application takes pairs of arguments that are keysyms and maps the keycode associated with the first keysym to the second keysym. In other words, you could use it to map the F1 key to be Escape and Home to be a Control key on the right side of the keyboard by typing the following:

$  mapkey F1 Escape Home Control_R

Use mapkey with care, because it is easy to disable a server by remapping an alphanumeric key. Such a remapping cannot be reversed except by restarting the server.

Example 9-6. An application for server-wide keymapping

#include <stdio.h>

#include <X11/Xlib.h>

#include <X11/Xutil.h>

#include <X11/Xatom.h>

#include <X11/keysym.h>

main(argc, argv)
int argc;
char **argv;
{
   KeySym old, new;
   int old_code;
   Display *display;
   if (!(display = XOpenDisplay(""))) {
      fprintf(stderr,"Cannot open display '%s'\n",
               XDisplayName(""));
      exit(1);
   }
   argv++, argc--;
   if (argc & 0x1) {
      fprintf(stderr,"Usage:  Keysymfrom Keysymto Keysymfrom \
               Keysymto ...\n");
      exit(1);
   }
   while (argc > 1) {
      old = XStringToKeysym(*argv++);
      new = XStringToKeysym(*argv++);
      argc--, argc--;
      old_code = XKeysymToKeycode(display, old);
      XChangeKeyboardMapping(display, old_code, 1, &new, 1);
   }
   XFlush(display);
   XCloseDisplay(display);
   exit(0);
}

The application in Example 9-6 could be rewritten on a larger scale to change a keyboard from QWERTY to DVORAK layout as described in “Keycodes to Keysyms” Since the keycodes are server-dependent, the QWERTY-to-DVORAK conversion program would not be portable between machines unless it used XGetKeyboardMapping() to get the current mapping of keycodes to keysyms and then remapped them.

XGetKeyboardMapping() returns an array of keysyms that represent the current mapping of the specified range of keycodes.

Changing Modifier Mapping

X allows you to control which physical keys are considered modifier keys. Normal applications will not do this. The modifier mapping might be changed for a left-handed user if, by default, there was only one Control key on the left side of the keyboard and the user preferred to have a Control key on the right side. In that case, a conveniently placed key on the right side could be mapped to a logical Control key. Like keycode-to-keysym remapping, this would typically be done by a special purpose application run from the user's startup script.

While modifier keys generate KeyPress and KeyRelease events like other keys, modifier keys are the only keys reported in the state member of every key, button, motion, or border crossing event structure. The state member is a mask that indicates which logical modifiers were pressed when the event occurred. Each bit in state is represented by a constant such as ControlMask. state is used by XLookupString() to generate the correct keysym from a key event. Note that the state member of events other than key, button, motion, and border crossing events does not have the meaning described here.

XInsertModifiermapEntry() and XDeleteModifiermapEntry() provide the easiest ways by far to add or delete a few keycodes for a modifier.

Using XInsertModifiermapEntry() and XDeleteModifiermapEntry() is straightforward. You get the current modifier mapping stored in an XModifierKeymap structure with a call to XGetModifierMapping(). You specify this structure, a keycode, and one of the eight modifier symbols as the three arguments to XInsertModifiermapEntry() or XDeleteModifiermapEntry(). Both routines return a new XModifierKeymap structure suitable for calling XSetModifierMapping(). You should add or delete all the keycodes you intend to change before calling XSetModifierMapping().

You should not need to understand how the modifiers are stored to use the procedure described above for adding or deleting keycodes.

XSetModifierMapping() is the routine that actually changes the mapping. As such, it is when calling XSetModifierMapping() that any errors appear, even though they are usually caused by an invalid XModifierKeymap structure that was set earlier.

These are the requirements for the XModifierKeymap structure specified to XSetModifierMapping():

  • Zero keycodes are ignored.

  • No keycode may appear twice anywhere in the map (otherwise, a BadValue error is generated).

  • All nonzero keycodes must be in the range specified by min_keycode and max_keycode in the Display structure (else a BadValue error).

  • A server can impose restrictions on how modifiers can be changed. For example, certain keys may not generate up transitions in hardware or multiple modifier keys may not be supported. If a restriction is violated, then the status reply is MappingFailed, and none of the modifiers are changed.

If the new keycodes specified for a modifier differ from those currently defined and any (current or new) keys for that modifier are in the down state, then the status reply is MappingBusy, and none of the modifiers are changed.

XSetModifierMapping() generates a MappingNotify event on a MappingSuccess status.

When finished mapping the keyboard, you can free the XModifierKeymap structures by calling XFreeModifiermap().

Other Keyboard-handling Routines

Several routines in addition to XLookupString() provide ways to translate key events. None of these routines are commonly needed in applications.

You might think that XKeysymToString() and XStringToKeysym() describe the mapping between keysyms and strings, but they don't. XKeysymToString() does not return the same string as is placed in the buffer argument of XLookupString(), when XKeysymToString() is given the keysym that XLookupString() returns. XKeysymToString() changes the symbol form of a keysym (XK_Return), which is a number, into a string form of the symbol (“Return”), and XStringToKeysym() does the reverse. XKeysymToString()(XK_F1) would return “F1” regardless of what string is currently mapping to the F1 key. Only XLookupString() returns the string mapped to a particular keysym with XRebindKeysym().

XKeycodeToKeysym() and XKeysymToKeycode() make the mapping between single keycodes and keysyms more accessible. (XLookupKeysym() actually takes a key event, extracts the keycode, and calls XKeycodeToKeysym().) For XKeycodeToKeysym() and XLookupKeysym(), you must specify which keysym you want from the list for the keycode, with the index argument. Remember that the list of keysyms for each keycode represents the key with various combinations of modifier keys pressed. The meaning of the keysym list beyond the first two (unmodified, Shift or Shift Lock) is not defined. Therefore, the index values of 0 and 1 are the most commonly used.

The Pointer

The pointer generates events as it moves, as it crosses window borders, and as its buttons are pressed. It provides position information that can define a path in the two-dimensional space of the screen, tell you which window the pointer is in and allow the user to “point and click,” generating input without using the keyboard. In fact, the pointer is the most unique feature of a window system.

This section describes how to track the pointer and how to handle the pointer buttons. Border crossing events are discussed in “Border Crossing and Keyboard Focus Change Events” because they must be handled in concert with keyboard focus change events.

Tracking Pointer Motion

There are three ways of handling pointer motion events:

  • Getting all motion events. The program simply receives and processes every motion event. This option is suitable for applications that require all movements to be reported, no matter how small. Since many motion events are generated and reporting the processing of the events may lag behind the pointer, this approach is not suitable for applications that require the most current information about pointer position.

  • Getting hints and querying pointer position. This method greatly reduces the number of motion events sent but requires that XQueryPointer() be called to get the current pointer position. This option is suitable for applications that require only the final position of the mouse after each movement.

  • Reading the motion history buffer. After checking that the buffer exists, call XGetMotionEvents() when you want the array of events occurring between two specified times. This option is not available on all servers, but it is suitable for detailed pointer position reporting. Its advantage over getting all motion events is that the list of pointer positions in the motion history buffer can be used for undoing or responding to exposure events in drawing applications.

Let's look at each of these methods in detail.

Getting All Motion Events

The most obvious way to handle motion events is to get all motion events. The only complication is that you must keep the processing of each event to a minimum so that the feedback loop to the user is reasonably fast.

Example 9-7 shows another modification to basicwin, the program described in Chapter 3, “Basic Window Program” It creates a child window of the top-level window of the application and allows the user to draw into it by moving the pointer with any button held down.

Example 9-7. Getting all motion events

/* Global declarations of display and screen */
  .
  .
  .
#define BUF_SIZE 2000
void main(argc, argv)
int argc;
char **argv;
{
   /* Declarations from basicwin */
     .
     .
     .
   Window wint;
   int xpositions[BUF_SIZE], ypositions[BUF_SIZE];
   int i;
   int count = 0;
   Bool buffer_filled = False;
   /* Open display and create window win */
     .
     .
     .
   wint = XCreateSimpleWindow(display, win, 20, 20, 50, 50,
         border_width, BlackPixel(display, screen),
         WhitePixel(display,screen));
   XSelectInput(display, wint, ExposureMask | PointerMotionMask);
   XMapWindow(display, wint);
     .
     .
     .
   /* Select events for and map win */
   while (1)  {
      XNextEvent(display, &report);
      switch  (report.type) {
      case MotionNotify:
         printf("got a motion event\n");
         xpositions[count] = report.xmotion.x;
         ypositions[count] = report.xmotion.y;
         XDrawPoint(display, wint, gc,
               report.xmotion.x, report.xmotion.y);
         /* The following implements a fast ring buffer
          * when count reaches buffer size */
         if (count <= BUF_SIZE)
            count++;
         else {
            count = 0;
            buffer_filled = True;
         }
         break;
      case Expose:
         printf("got expose event\n");
         if (report.xexpose.count != 0)
            break;
         if (report.xexpose.window == wint) {
            /* This redraws the right number of points;
             * if the ring buffer is not yet filled,
             * it draws count points; otherwise, it
             * draws all the points */
            for (i=0 ; i < (buffer_filled ?
                  BUF_SIZE : count) ; i++)
               XDrawPoint(display, wint, gc, xpositions[i],
                     ypositions[i]);
         }
         else {
            if (window_size == SMALL)
               TooSmall(win, gc, font_info);
            else {
               /* Place text in window */
               draw_text(win, gc, font_info, width, height);
               /* Place graphics in window */
               draw_graphics(win, gc, width, height);
            }
         }
      break;
      /* Other event types handled same as basicwin */
        .
        .
        .
      } /* End switch */
   } /* End while */
}

The program keeps a record of the points drawn so that they can be redrawn in case of an Expose event. The event record is a ring buffer so that the latest BUF_SIZE pointer positions are always maintained.

The program requires that one or more of the pointer buttons must be held down while drawing. (Most drawing applications require a button to be held, because otherwise it is impossible to move the pointer into a different application without drawing a trail of points to the edge of the window.) Therefore, drawing applications normally select ButtonMotionMask.

It would be quite easy to extend this program by giving each button a different meaning. Drawing with button 1 could mean drawing in black, button 2 could mean drawing in white, and button 3 could mean toggling the previous state of the drawn pixels. The only change necessary to implement this would be code that changes the foreground pixel value and logical operation in a GC or creates three GCs with these variations. The routine would determine which button was pressed from the state member of the event structure and determine what to do if more than one button was pressed.

Using Pointer Motion Hints

If you do not need a record of every point the pointer has passed through but only its current position, using motion hints is the most efficient method of handling pointer motion events. This method could be used for dragging in menus or scrollbars, in a window manager when it moves the outlines of windows, or in a drawing application in a line drawing mode. We'll demonstrate the technique in a line drawing application.

To use this method, select PointerMotionHintMask in addition to the specific event masks you desire. PointerMotionHintMask is a modifier; it does not select events by itself.

Example 9-8 demonstrates how to read pointer events with PointerMotionHintMask selected. The code shown in the example draws lines between the series of points the user specifies with button clicks. The ButtonPress event indicates the beginning of a line, MotionNotify events allow the application to draw a temporary line to the current pointer position, and ButtonRelease events indicate that the line should be drawn permanently between the points indicated by the ButtonPress and ButtonRelease events.

Example 9-8. Using pointer motion hints

/* Declare global variables display and screen */
  .
  .
  .
void main(argc, argv)
int argc;
char **argv;
{
   /* Declarations from basicwin */
     .
     .
     .
   int root_x, root_y;
   Window root, child;
   unsigned int keys_buttons;
   Window wint;
   XPoint points[BUF_SIZE];
   int index = 1;
   int pos_x, pos_y;
   int prev_x, prev_y;
   GC gcx;
   wint = XCreateSimpleWindow(display, win, 20, 20, 50, 50,
         border_width, BlackPixel(display, screen),
         WhitePixel(display,screen));
   XSelectInput(display, wint, ExposureMask | ButtonPressMask
         | ButtonReleaseMask | ButtonMotionMask
         | PointerMotionHintMask);
   gcx = XCreateGC(display, win, 0, NULL);
   XSetFunction(display, gcx, GXxor);
   XSetForeground(display, gcx, BlackPixel(display, screen));
   XMapWindow(display, wint);
   while (1)  {
      XNextEvent(display, &report);
      switch  (report.type) {
      case ButtonPress:
         points[index].x = report.xbutton.x;
         points[index].y = report.xbutton.y;
         break;
      case ButtonRelease:
         index++;
         points[index].x = report.xbutton.x;
         points[index].y = report.xbutton.y;
         break;
      case MotionNotify:
         printf("got a motion event\n");
         while (XCheckMaskEvent(display,
               ButtonMotionMask, &report));
         if (!XQueryPointer(display, report.xmotion.window,
               &root, &child, &root_x, &root_y,
               &pos_x, &pos_y, &keys_buttons))
            /* Pointer is on other screen */
            break;
         /* Undraw previous line, only if not first */
         if (index != 1)
            XDrawLine(display, wint, gcx, points[index].x,
                  points[index].y, prev_x, prev_y);
         /* Draw current line */
         XDrawLine(display, wint, gcx, points[index].x,
               points[index].y, pos_x, pos_y);
         prev_x = pos_x;
         prev_y = pos_y;
         break;
      case Expose:
         printf("got expose event\n");
         if (report.xexpose.window == wint) {
            while (XCheckTypedWindowEvent(display,
                  wint, Expose, &report));
            XSetFunction(display, gcx, GXcopy);
            XDrawLines(display, wint, gcx, points,
                  index, CoordModeOrigin);
            XSetFunction(display, gcx, GXxor);
         }
         else {
            /* Same code as basicwin */
         }
         break;
      } /* End switch */
   } /* End while */
}

In some applications, you do not need to track pointer motion events to know where the pointer is at particular times. The pointer position is given in ButtonPress, ButtonRelease, KeyPress, KeyRelease, EnterNotify, and LeaveNotify events. You can use any of these events to locate objects in a window.

Motion History

If the motion history buffer exists on the server (XDisplayMotionBufferSize() (display) > 0), all selected motion events are placed in a list of XTimeCoord structures. There is no macro for accessing this member of the display structure. You specify the desired range of times to XGetMotionEvents(), and it returns a pointer to a list of XTimeCoord structures, representing all the pointer positions during the range of times. The reported pointer positions may be in finer detail than would be reported by MotionNotify events.

In the MIT sample distribution of Xlib, motion history buffers were first implemented in Release 5. In any case, they are not a required part of a server implementation. Therefore, an application that uses motion history should also support the all-motion-events approach for use on servers that do not have the buffer.

The XTimeCoord structure is shown in Example 9-9.

Example 9-9. The XTimeCoord structure

typedef struct _XTimeCoord {
   short x,y;  /* Position relative to root window */
   Time time;
} XTimeCoord;


Example 9-10 shows another version of the program used to demonstrate getting all motion events.

Example 9-10. Reading the motion history buffer

/* Global declarations of display and screen */
  .
  .
  .
#define BUF_SIZE 2000
void main(argc, argv)
int argc;
char **argv;
{
   /* Declarations from basicwin */
     .
     .
     .
   Window wint;
   int xpositions[BUF_SIZE], ypositions[BUF_SIZE];
   int i;
   int count = 0;
   Bool buffer_filled = False;
   /* Open display and create window win */
     .
     .
     .
   if (XDisplayMotionBufferSize(display) <= 0)
      {
      printf("%s: motion history buffer not provided on server",
            argv(0));
      exit(-1);  /* Or use all events method instead */
      }
   wint = XCreateSimpleWindow(display, win, 20, 20, 50, 50,
         border_width, BlackPixel(display, screen),
         WhitePixel(display,screen));
   XSelectInput(display, wint, ExposureMask | ButtonMotionMask
         | PointerMotionHintMask);
   XMapWindow(display, wint);
     .
     .
     .
   /* Select events for and map win */
   while (1)  {
      XNextEvent(display, &report);
      switch  (report.type) {
      case MotionNotify:
                printf("got a motion event\n");
         while (XCheckTypedEvent(display, MotionNotify, &report));
         start = prevtime;
         stop = report.xmotion.time;
         xytimelist = XGetMotionEvents(display, window, start,
               stop, &nevents);
         for (i=0;i<nevents;i++)
            XDrawPoint(display, window, gc, xytimelist[i]->x,
                  xytimelist[i]->y);
         break;
      case Expose:
         printf("got expose event\n");
         if (report.xexpose.window == wint) {
            while (XCheckTypedWindowEvent(display,
                  wint, Expose, &report));
            xytimelist = XGetMotionEvents(display, window,
                  0, CurrentTime, &nevents);
            for (i=0 ; i < nevents ; i++)
               XDrawPoint(display, window, gc, xytimelist[i]->x,
                     xytimelist[i]->y);
         }
         else {
            while (XCheckTypedWindowEvent(display,
                  win, Expose, &report));
            if (window_size == SMALL)
               TooSmall(win, gc, font_info);
            else {
               /* Place text in window */
               draw_text(win, gc, font_info, width, height);
               /* Place graphics in window */
               draw_graphics(win, gc, width, height);
            }
         }
         break;
      /* Other event types handled same as basicwin */
        .
        .
        .
      } /* End switch */
   } /* End while */
}


Handling Pointer Button Events

The examples of tracking pointer motion in “Tracking Pointer Motion” use the buttons to some extent, but they do not tell you the whole story. There is the subject of automatic button grabs, and there are issues involved in making each button perform a different function. Let's tackle grabs first.

When a pointer button is pressed, an active grab is triggered automatically (as described in “Keyboard and Pointer Grabbing” in Chapter 8 an active grab means that all button events before the matching ButtonRelease event on the same button always goes to the same application, or sometimes the same window, as the ButtonPress). The automatic grab does not take place if an active grab already exists or a passive grab on the present key and button combination exists for some higher level window in the hierarchy than the window in which the ButtonPress occurred.

The OwnerGrabButtonMask that you can specify in calls to XSelectInput() controls the distribution of the ButtonRelease event (and any other pointer events that occur between the ButtonPress and ButtonRelease). If OwnerGrabButtonMask is selected, the ButtonRelease event will be sent to whichever window in the application the pointer is in when the event occurs. If the pointer is outside the application or if OwnerGrabButtonMask is not selected, the event is sent to the window in which the ButtonPress occurred.

OwnerGrabButtonMask should be selected when an application wants to know in which window ButtonRelease events occur. This information is useful when you require that both the ButtonPress and the matching ButtonRelease events occur in the same window in order for an operation to be executed. In practice, it does not hurt to select OwnerGrabButtonMask even if you do not need the response it provides. If you do not select OwnerGrabButtonMask, any changes you try to make to the event mask of the grabbing window before the ButtonRelease will not take effect.

The automatic grabs affect only the window to which button events are sent. To be more precise, they affect the value of the window member in the button event structures in the application's event queue. And for the event to be placed on the queue in the first place, it must have been selected on the window specified in the window member.

Now let's talk about distinguishing which pointer button was pressed. Two members of the XButtonEvent structure contain information about the button state. The button member specifies the button that changed state to trigger the event. The state member gives the state of all the buttons and modifier keys just before the event. You will need the state member only if you require that certain key or button combinations be pressed to trigger an operation.

Especially if you require that the same button must be pressed and released in a certain window, be sure to account for the case where, for example, button 1 is pressed, then buttons 2 and 3 are pressed and released (perhaps repeatedly) or pressed and held, before button 1 is again released. You must be careful if you structure your code as shown in Example 9-11 to handle ButtonPress and ButtonRelease events in pairs. [28] There is no case for ButtonRelease in the example. Instead the code for ButtonPress looks for the matching ButtonRelease event. The matching ButtonRelease might not be the next button event, so intervening events must be dealt with. This problem appears only if you are trying to distinguish the button that was pressed.

Example 9-11. Accepting button events in pairs

case ButtonPress:
   /* Draw pane in white on black */
   paint_pane(event.xbutton.window, panes, gc, rgc,
         font_info, BLACK);
   /* Keep track of which button was pressed */
   button = event.xbutton.button;
   /* Keep track of which window press occurred in */
   inverted_pane = event.xbutton.window;
   /* Get the matching ButtonRelease on same button */
   while (1) {
      /* Get rid of presses on other buttons */
      while (XCheckTypedEvent(display, ButtonPress,
            &event));
      /* Wait for release; if on correct button, exit */
      XMaskEvent(display, ButtonReleaseMask, &event);
      if (event.xbutton.button == button)
         break;
   }
   /* All events are sent to the grabbing window
    * regardless of whether this is True or False,
    * because owner_events only affects the
    * distribution of events when the pointer is
    * within this application's windows; we don't
    * expect it to be for a window manager */
   owner_events = True;
   /* We don't want pointer or keyboard events
    * frozen in the server */
   pointer_mode = GrabModeAsync;
   keyboard_mode = GrabModeAsync;
   /* We don't want to confine the cursor */
   confine_to = None;
   GrabPointer(display, menuwin, owner_events,
         ButtonPressMask | ButtonReleaseMask,
         pointer_mode, keyboard_mode,
         confine_to, hand_cursor, CurrentTime);
   /* If press and release occurred in same window,
    * do command; if not, do nothing */
   if (inverted_pane == event.xbutton.window)
      {
      /* Convert window ID to window array index  */
      for (winindex = 0; inverted_pane !=
            panes[winindex]; winindex++)
         ;
      switch (winindex)
         {
      case 0:
         raise_lower(display, screen,
               RAISE);
         break;
        .
        .
        .
      case 9: /* Exit */
         XSetInputFocus(display,
            RootWindow(display,screen),
            RevertToPointerRoot,
            CurrentTime);
         /* Turn all icons back into windows */
         /* Must clear focus highlights */
         XClearWindow(display, RootWindow(display, screen));
         /* Need to change focus border width back here */
         XFlush(display);
         XCloseDisplay(display);
         exit(1);
      default:
         (void) fprintf(stderr,
               "Something went wrong\n");
         break;
         } /* End switch */
      } /* End if */
   /* Invert back here (logical function is GXcopy) */
   paint_pane(event.xexpose.window, panes, gc, rgc,
         font_info, WHITE);
   inverted_pane = NONE;
   draw_focus_frame();
   XUngrabPointer(display, CurrentTime);
   XFlush(display);
   break;
case DestroyNotify:
  .
  .
  .


Changing the Pointer Button Mapping

Some applications may allow the user to modify the mapping between the physical pointer buttons and the logical buttons that are reported when a button is pressed. In other words, if physical button 1 were mapped to logical button 3, then when either button 3 or button 1 were pressed, it would appear to all applications that only button 3 was pressed.

There are five logical buttons, but the number of physical buttons may range from one up to and perhaps greater than five. Mapping the pointer buttons might be done, for example, to simulate buttons 4 and 5 on a system with a three-button mouse. However, while physical buttons 1 and 2 were mapped to logical 4 and 5, no buttons would be mapped to logical 1 and 2. Therefore, there would have to be a way of toggling between the modes, perhaps using a function key.

The mapping of pointer buttons is analogous to the mapping between keycodes and keysyms in that it is global to the server and affects all clients. However, since the translation of a pointer event takes place in the server, unlike key event processing routines that use information stored in Xlib whenever possible, no routine is necessary to update the pointer mapping like XRefreshKeyboardMapping() updates the keyboard mapping.

XGetPointerMapping() returns the current mapping between physical and logical pointer buttons. XSetPointerMapping() sets this mapping.

Moving the Pointer

The XWarpPointer() routine moves the pointer to a relative or global position. Its use should be minimized and constrained to particular predictable circumstances, because it often confuses the user.

XWarpPointer() has various features for moving only in certain situations. See the reference page in Volume Two, for details.

Warping the pointer generates MotionNotify and border crossing events just as if the user moved the pointer.

Border Crossing and Keyboard Focus Change Events

LeaveNotify and EnterNotify events are generated when the pointer crosses a window border. If the window manager is of the real-estate-driven variety (as is uwm), you might be tempted to assume that a LeaveNotify event indicates that the window will not receive keyboard input until it receives a matching EnterNotify. However, this assumption is not true if the user has been allowed to set a keyboard focus window. It is also not true if the window manager is of the listener variety (see Chapters 1, Introduction, 12, Interclient Communication, and 16, Window Management). Ideally, you should be prepared to deal with either type of window manager.

Pointer input can only be delivered to a window when the pointer is inside the window (unless the window grabs the pointer). Therefore, an application that depends on pointer input can expect to be idle when the pointer leaves the window and to be active again when the pointer enters. Notice that keyboard input can be diverted with the keyboard focus or grabs, while pointer input can only be diverted by grabs.

FocusIn and FocusOut events occur when the keyboard focus window changes (when some client calls XSetInputFocus()). By using focus events together with the border crossing events, an application should be able to determine whether or not it can get keyboard input. If it cannot get keyboard input, it may change its behavior somewhat. If it polls for keyboard input to allow for interrupts, it can stop polling. If it normally highlights a window when the pointer enters it, it should not do so if the keyboard focus is not the root window.

In general, to determine if it will get keyboard input, an application should first check FocusIn and FocusOut events. If the focus window is the root window, then the application should check LeaveNotify and EnterNotify to see if keyboard events are possible.

Additional focus change and border crossing events are generated when the origin and destination of the focus or pointer crossing do not have a parent-child relationship. These events are called virtual crossing events. See Appendix E, Event Reference, for a description of when these events are generated and how to distinguish them from normal crossing events.

Example 9-12 shows the code that would be used to monitor whether the application will receive keyboard input. When keyboard_active is True in this code, the application could highlight its main window.

Example 9-12. Monitoring whether keyboard input will be available

Bool keyboard_active;
Bool focus;
/* Open display, create window, select input */
/* Select input before setting keyboard focus, if application does */
while (1)  {
   XNextEvent(display, &report);
   switch  (report.type) {
     .
     .
     .
   case EnterNotify:
      printf("enter\n");
      /* Make sure focus is an ancestor */
      (report.xcrossing.focus) ?
            (keyboard_active = True)
            : (keyboard_active = False);
      break;
   case LeaveNotify:
      printf("leave\n");
      /* We get input only if we have the focus */
      (focus) ? (keyboard_active = True)
            : (keyboard_active = False);
      break;
   case FocusIn:
      /* We get keyboard input for sure */
      printf("focus in\n");
      focus = True;
      keyboard_active = True;
      break;
   case FocusOut:
      /* We lost focus, get no keyboard input */
      printf("focus out\n");
      focus = False;
      keyboard_active = False;
      break;
     .
     .
     .
   }
} /* End while */

Example 9-12 could be used as a basis for code that highlights a portion of an application when it can get keyboard input. It would be in active mode when the keyboard_active flag is True. When an EnterNotify event is received, the focus member of the event structure is checked to see that the focus window is an ancestor of the window in question. If so, keyboard_active is True. When a LeaveNotify event is received, keyboard_active is True only if the application has the focus. On FocusIn events, keyboard_active is True, and a flag (focus) is set to indicate whether the keyboard will be active after LeaveNotify events.

The KeymapNotify Event

The KeymapNotify event, when selected, always follows on the queue immediately after a FocusIn or EnterNotify event. Its purpose is to allow the application to easily determine which combination of keys were pressed when the focus was transferred to the window or the pointer entered it. The KeymapNotify event contains a keyboard vector, which is a 32-element array of type char, in which each bit represents a key. For a given key, its keycode is its position in the keyboard vector.

The XQueryKeymap() function also returns this keyboard vector. Keyboard vectors are always independent of all the keyboard mapping and reading functions, since the bits in the vector correspond to keycodes that cannot be changed. This way of reading the keyboard is just like reading the pointer buttons. It can be useful for applications that treat the keyboard not as characters but, for example, as piano keys or drum pads.

Since XQueryKeymap() makes a routine trip request, reading the keyboard this way could not achieve the same performance when operating over a network as the same program implemented using events.

Grabbing the Keyboard and Pointer

There are times when a program might want to bypass the normal keyboard or pointer event propagation path in order to get input independent of the position of the pointer. This is the purpose of grabbing the keyboard and pointer. There are routines to grab the keyboard (XGrabKeyboard()) or the pointer (XGrabPointer()), or to arrange that they become grabbed when a certain combination of keys and/or buttons is pressed (XGrabButton(), XGrabKey()). There are corresponding calls to ungrab (XUngrabButton(), XUngrabKey(), XUngrabKeyboard(), XUngrabPointer()), and there is one call to change the characteristics of a pointer grab (XChangeActivePointerGrab()).

One of the most common situations where grabbing takes place is with button events. Most applications want both a ButtonPress and a ButtonRelease, so that they can compare the two positions. Since this is such a common desire, the server automatically grabs the pointer between the ButtonPress and ButtonRelease if both are selected, so that you do not have to make an explicit grab.

One reason for grabbing a device is so that you can handle a series of events contiguously without fear of intervening events. But when you grab a device, no other application can receive input from that device. Therefore, it is something to do only when absolutely necessary.

The routines that grab take several arguments that tailor the input response in these ways:

  • When the pointer is grabbed, the cursor may be confined to any window (the confine_to argument).

  • The distribution of events to windows within the application can be modified by the owner_events argument. If owner_events is True, then the grabbed events will be sent to the window within the application that the pointer indicates. If owner_events is False or the pointer is outside the applications, then the events are always sent only to window specified by window.

  • A window called the grab_window is specified. All events that occur outside the calling application's windows are reported to the grab window. All events within the application's windows will be sent to the grab window if the owner_events argument is False, or they will be reported normally within the application (to the window indicated by the pointer or propagating from that window if it did not select the event) if owner_events is True.

  • For events that occur outside the calling application's windows, and events that occur inside when owner_events is False, the event_mask argument specifies which types of events are selected for the grab window. This event_mask overrules the existing event_mask for the grab window unless owner_events is True.

  • Event processing for either keyboard or pointer events or both may be halted altogether during the grab until a releasing XAllowEvents() call is invoked by setting the pointer_mode or keyboard_mode arguments to GrabModeSync.

  • The cursor argument specifies a particular Cursor to be displayed while the grab is active. This cursor indicates to the user that input is going to the grabbing window, since the cursor will not change when moved across the screen as it normally would.

  • Grabbing calls may specify a time when the grab should take place (the time argument).

You can change several of the conditions of an active pointer grab, namely the event_mask, cursor, and time, using XChangeActivePointerGrab().

XGrabKey() and XGrabButton() arrange for a grab to take place when a certain combination of keys or buttons is pressed. After one of these routines is called, a passive grab is said to be in effect, until the specified keys and buttons are pressed. At that time, the grab is active and is indistinguishable from a grab generated by a call to XGrabKeyboard() or XGrabPointer(). After a passive grab, an active pointer grab will take effect when the following four conditions are met:

  • The specified button is pressed when an optional set of modifier keyboard keys is pressed and no other keys or buttons are pressed.

  • The pointer is contained in the grab window specified in the grabbing call.

  • The cursor-confining window (specified in the confine_to argument of XGrabPointer() or XGrabButton()) must be visible, if one is specified.

  • These conditions are not satisfied by any ancestor.

Grabbing the keyboard is similar to setting the keyboard focus window, but grabbing is more flexible, since there are more arguments to modify the effect. Focus changes and keyboard grabs and ungrabs all generate the same FocusIn and FocusOut events.

If pointer grabs and ungrabs cause the pointer to move in or out of a window, they generate EnterNotify and LeaveNotify events.

The XAllowEvents() routine is used only when the pointer_mode or keyboard_mode in previous grabbing calls were set to GrabModeSync. Under these conditions, the server queues any events that occur (but does not send them to the Xlib event queues for each application), and the keyboard or pointer is considered “frozen.” XAllowEvents() releases the events that are queued in the server for the frozen device. After the call, the device is still frozen, and the server again queues any events that occur on that device until the next XAllowEvents() or Ungrab* call. In effect, XAllowEvents() allows events to come in a batch through the network to the event queues for each application in Xlib.

The pointer modes have no effect on the processing of keyboard events and vice versa.

Both a pointer grab and a keyboard grab may be active at the same time by the same or different clients. If a device is frozen on behalf of either grab, no event processing is performed for the device. It is possible for a single device to be frozen by both grabs. In this case, the freeze must be released on behalf of both grabs before events can again be processed.

Implementing Type-ahead for Information Entry

Normally, the keyboard input focus, which is the window to which all keyboard input is sent, is controlled by the window manager. However, the window manager only gives the keyboard input focus to top-level windows. So essentially the window manager gives the keyboard focus to one application at a time.

Order entry applications need to move the keyboard focus from subwindow to subwindow within the application. Many of them interpret the Tab key as a command to move to the next information entry field. To do this reliably while allowing type-ahead, they must use the keyboard focus in combination with grabs. Here's why, as written by Wayne Dyksen of Purdue University.[29] It begins with a little more about synchronous and asynchronous grabs, which you need to understand to follow the rest.

As “raw” events occur on devices, the Server processes them and sends them to clients. For example, given a raw event, the Server must determine to which window the event is to be sent; that is, the Server must determine the value of the window member of the event structure. The parameters pointer_mode and keyboard_mode arguments of XGrabButton(), XGrabPointer(), XGrabKey(), and XGrabKeyboard() control the processing of raw events during a grab; they can have either of the values GrabModeAsync or GrabModeSync.

If the value GrabModeAsync is used, then event processing for the grabbed device is asynchronous, as usual. That is, the Server processes and sends all grabbed events to the grabbing client as soon as they occur. Note that all ungrabbed events (e.g., Expose) are processed and sent normally.

If GrabModeSync is used, then, when the grab occurs, the Server records raw device events in an internal queue, but it temporarily stops processing and sending them to the grabbing client. The Server resumes raw event processing when the grabbing client sends either an XAllowEvents() request or an ungrab request.

Using GrabModeSync is often referred to as freezing the keyboard or pointer. This term is the source of some confusion since using GrabModeSync does not freeze (lockup) the pointer or keyboard themselves in any intuitive sense. One would guess that if the pointer or keyboard were “frozen,” then using them would have no effect. In fact, use of the pointer and keyboard during a freeze continues to generate raw events which are recorded (but not processed or sent) by the Server. For example, the pointer cursor continues to move on the screen. Using GrabModeSync does not freeze the physical pointer or keyboard themselves, but rather it freezes the raw pointer or keyboard events at the Server. The raw events (not the devices) are eventually thawed (processed and sent) when the freezing client sends either an XAllowEvents() request or an ungrab request.

Consider the simplified forms fill-in application illustrated in Figure 9-1. Recall that the user gets from one blank to the next (from one window to the next) by using a special key (say Next). Each blank in the form is implemented by a separate X window. The client changes the keyboard input focus (via XSetInputFocus()) to the next blank (window) each time it receives a Next KeyPress event.

Figure 9-1. Simplified forms fill-in application

Suppose first that a client were to attempt to implement this forms fill-in application by having the “form” window (the parent of the blanks) asynchronously grab the Next key. When the form window receives a Next KeyPress event, it issues an XSetInputFocus() request, changing the keyboard input focus to the next blank.

Consider the possible scenario of events when a user types “D-y-k-s-e-n-Next-W-a-y-n-e,” as illustrated in Figure 9-2. Each Snapshot shows the events and requests in queues at a moment in time; events as they are first generated (“Raw Events”), events as the server determines the window to which they should be delivered (“Cooked Events”), and requests made by the client in response to the arrival of these events. “Raw Events” are physical device events which the Server has queued and must process; for example, “s --> ?” indicates that the “s” key has been pressed and that the Server must decide to which window the KeyPress event is to be sent. “Cooked Events” are events which the Server has processed and is about to dispatch; for example, “D --> Last” indicates that the “D” key has been pressed and that the event is to be sent to the “Last Name” window. “Requests” are requests which have come from the client in response to events; for example, “D --> Last” indicates that the client has requested the Server to draw a “D” in the “Last Name” window.

Figure 9-2. Possible scenario of events using an asynchronous key grab

Consider now what might happen if a user were to type “D-y-k-s-e-n-Next-W-a-y-n-e.” When the event “Next --> Form” illustrated in Snapshot 2 is received by the “Form” window, the client issues the request “Focus --> First” shown in Snapshot 3. Unfortunately, while the client is processing the Next KeyPress event, the Server is asynchronously processing further keyboard events. Thus, until the Server actually receives and processes the “Focus --> First” request, it continues to dispatch KeyPress events to the “Last Name” window. This is illustrated in Snapshot 3 where the Server is dispatching “W-a-y” to the “Last Name” window. Since the events “W-a-y” are sent to the “Last Name” window, the client issues the requests shown in Snapshot 4 to draw “W-a-y” in the “Last Name” window. Eventually, the Server does receive and process the “Focus --> First” request. Snapshot 4 and 5 show “n-e” being sent to and drawn in the “First Name” window. The above scenario produces the incorrect result illustrated in Figure 9-3.

Figure 9-3. Possible incorrect forms fill-in result using an asynchronous key grab

Note that the actual forms fill-in result using an asynchronous grab varies depending on the time between KeyPress events. In fact, the form would produce the correct result if a user were to type “D-y-k-s-e-n-Next” followed by a sufficiently long pause followed by “W-a-y-n-e.”

Consider again the above example, only this time suppose that the client synchronously grabs the Next key (before any key events are processed). The sequence of events that occur in response to the user typing “D-y-k-s-e-n-Next-W-a-y-n-e” is illustrated in Figure 9-4.

Figure 9-4. Scenario of events using a synchronous key grab

As soon as the Server sees the raw Next KeyPress event, it initiates a synchronous grab. Snapshot 3 shows that the Server is continuing to record raw KeyPress events, but it has stopped “cooking” them. The KeyPress events “W-a-y-n-e” have accumulated in the Server's “Raw Events” queue. After receiving the Next KeyPress event, the client sends a request to the Server to change the focus to the “First Name” window (“Focus --> First”) followed by a request to start cooking events (“Allow Events”). Because of the synchronous grab, the client knows that the Server receives and processes the “Focus --> First” request before it processes the raw event “W --> ?.” The desired result is illustrated in Figure 9-5.

Figure 9-5. Correct forms fill-in result using a synchronous key grab

To summarize, the solution to this form of type-ahead problem is to use GrabModeSync as the keyboard_mode argument of the passive grab on the Tab key. Then call XAllowEvents() in response the arrival of the Tab key event that signals the change in windows. This synchronizes the change of keyboard focus from one window to another and assures that the events go to the intended window.

Keyboard Preferences

Xlib provides routines to control beep pitch and volume, key click, Shift-Lock mode, mouse acceleration, keyboard lights and keyboard auto-repeat. Not all servers will actually be able to control all of these parameters.

There are five routines that deal with the keyboard and pointer preferences. XGetKeyboardControl() and XChangeKeyboardControl() are the primary routines for getting or setting all these preferences at once. XAutoRepeatOff() and XAutoRepeatOn() set the global keyboard auto-repeat status but are not able to control the auto-repeat of individual keys as XChangeKeyboardControl() can.

Setting Keyboard Preferences

XChangeKeyboardControl() uses the standard X method of changing internal structure members. The values argument to XChangeKeyboardControl() specifies the structure containing the desired values; the value_mask argument specifies which members in the structure specified in values should replace the current settings. See the reference page for XChangeKeyboardControl() in Volume Two, for a list of the mask symbols.

Example 9-13 shows the XKeyboardControl() structure.

Example 9-13. The XKeyboardControl() structure

typedef struct {
   int key_click_percent;
   int bell_percent;
   int bell_pitch;
   int bell_duration;
   int led;
   int led_mode;           /* LedModeOn or LedModeOff */
   int key;
   int auto_repeat_mode;   /* AutoRepeatModeOff, AutoRepeatModeOn,
                            * AutoRepeatModeDefault */
} XKeyboardControl;

The following list describes each member of the XKeyboardControl() structure:

  • key_click_percent sets the volume for key clicks between 0 (off) and 100 (loud), inclusive.

  • bell_percent sets the base volume for the bell (or beep) between 0 (off) and 100 (loud), inclusive.

  • bell_pitch sets the pitch (specified in Hz) of the bell.

  • bell_duration sets the duration (specified in milliseconds) of the bell.

  • led_mode controls whether the keyboard LEDs are to be used. If led is not specified and led_mode is LedModeOn, the states of all the lights are changed. If led_mode is LedModeOff, then the states of the lights are not changed. If led is specified, the light specified in led is turned on if led_mode is LedModeOn or turned off if led_mode is LedModeOff.

  • led is a number between 1 and 32, inclusive, which specifies which light is turned on or off, depending on led_mode.

  • auto_repeat_mode specifies how to handle auto-repeat when a key is held down. If only auto_repeat_mode is specified, then the global auto-repeat mode for the entire keyboard is changed, without affecting the per-key settings. If the auto_repeat_mode is AutoRepeatModeOn, the keys that are set to auto-repeat will do so. If it is set to AutoRepeatModeOff, no keys will repeat. If it is set to AutoRepeatModeDefault, all the keys or the specified key will operate in the default mode for the server. Normally the default mode is for all nonmodal keys to repeat (everything except Shift Lock and similar keys). The auto_repeat_mode can also be set using the XAutoRepeatOff() and XAutoRepeatOn() routines. None of the other members of the XKeyboardControl() structure have convenience routines for setting them.

  • key specifies the keycode of a key whose auto-repeat status will be changed to the setting specified by auto_repeat_mode. If this value is specified, auto_repeat_mode affects only the key specified in key. This is the only way to change the mode of a single key.

Setting any of bell_duration, bell_percent, bell_pitch, or key_click_percent to -1 restores the default value for that member.

The initial state of many of these parameters may be determined by command line arguments to the X server. On systems that operate only under the X Window System, the server is executed automatically by xdm during the boot procedure, and the defaults may have been modified in one of the xdm configuration files.

Table 9-2 shows the ranges for each member when no command line arguments are specified for the server. The defaults when these values are not set are server-dependent.

Table 9-2. Keyboard Preference Settings -- Ranges

Parameter

Range

key_click_percent

0 to 100

bell_percent

0 to 100

bell_pitch

hertz (20 to 20K)

bell_duration

milliseconds

led

1 to 32

led_mode

LedModeOff , LedModeOn

key

8 to 255

auto_repeat_mode

AutoRepeatModeDefault , AutoRepeatModeOff , AutoRepeatModeOn


Getting Keyboard Preferences

To obtain the current state of the user preferences, use XGetKeyboardControl(). This routine returns an XKeyboardState() structure, as shown in Example 9-14.

Example 9-14. The XKeyboardState() structure

typedef struct {
   int key_click_percent;
   int bell_percent;
   unsigned int bell_pitch, bell_duration;
   unsigned long led_mask;
   int global_auto_repeat;
   char auto_repeats[32];
} XKeyboardState;


Except for led_mask, global_auto_repeat, and auto_repeats, these members have the same range of possible values listed in Table 9-2.

The led_mask member is not directly analogous to any member of XKeyboardControl(). Each bit set to 1 in led_mask indicates a lit LED. The least significant bit of led_mask corresponds to LED one.

The global_auto_repeat member is either AutoRepeatModeOff or AutoRepeatModeOn. It reports the state of the parameter set by the auto_repeat_mode member of XKeyboardControl().

The auto_repeats member is a key vector like the one in KeymapNotify events and returned by XQueryKeymap(). Each bit set to 1 in auto_repeats indicates that auto-repeat is enabled for the corresponding key. The vector is represented as 32 bytes. Byte N (from 0) contains the bits for keycodes 8N to 8N+7, with the least significant bit in the byte representing keycode 8N. Every key on the keyboard is represented by a bit in the vector.

Pointer Preferences

XChangePointerControl() sets the parameters that control pointer acceleration, and XGetPointerControl() gets them. Pointer acceleration is a feature that allows the user to move the cursor more quickly across the screen. If pointer acceleration is active, when the pointer moves more than a certain threshold amount in a single movement, the cursor will move a multiple of the amount the physical pointer moved. The effect of acceleration is that you can have detailed control over the pointer for fine work and, by flicking the wrist, you can also move quickly to the far reaches of the screen.

XChangePointerControl() takes three arguments (in addition to the ubiquitous display): accel_numerator, accel_denominator, and threshold.

The accel_numerator and accel_denominator arguments make up a fraction that determines the multiple used to determine how many pixels to move the cursor based on how much the physical pointer moved. The threshold argument specifies how many pixels the physical pointer must have moved for acceleration to take effect.

X Input Extension

As of Release 5, the X Input extension is now a standard way to get input from devices other than keyboard and mouse (such as trackballs and tablets), or from multiple such devices. However, not many servers currently support the extension. For more information on the X Input extension, see The X Resource, Issue 4, or the forthcoming volume Extensions and Utilities.



[27] We could say we left it this way because it is simpler, and it is, but that is not why we wrote it this way. We did not realize the other way would be better until the program was already done. We will leave it as an exercise for you to modify it as described!

[28] This code is an excerpt from winman, the simple window manager described in Chapter 16, “Window Management”

[29] This is an excerpt of the paper “Controlling Event Delivery with Grabs and Keyboard Focus,” by Wayne Dyksen, that appeared in The X Resource, Issue 2.