Features


X Window Programming

Part 3: More Xlib Programming

Eric F. Johnson and Kevin Reichard


Johnson and Reichard are authors of X Window Applications Programming, Advanced X Window Applications Programming, and Power Programming Motif, Henry-Holt. Johnson can be reached at erc@pai.mn.org via UUCP mail. Reichard's Compuserve address is 73670, 3422.

The last article on X Window Programming introduced the X library and included a sample program that showed how to create a window and draw basic shapes such as lines and rectangles. This article continues where the last one left off. It introduces text, fonts, and keyboard input in the X library. (If you haven't read "Introduction to X Window Programming, Part 2: The X Library" in CUJ, May 1991, you may want to do so before continuing with this article.)

Fonts And Text

Text in the X Window System is drawn with bitmap fonts. Although some vendors have added Display PostScript or other proprietary extensions, the basic X fonts are all bitmap fonts. This situation may change, but for now fonts in X cannot be scaled. Consequently, you must load in a font for each typeface and point size you intend to use.

You can load a font with the XLoadQueryFont function. You cannot use a font until you have successfully loaded it. See Listing 1.

The Display pointer in Listing 1 represents the connection to an X server. The pointer is returned by XOpenDisplay. (See Part 2 of this series for more information on this topic.) The font_name should be the name of a valid font that is installed on your system. The X Window program xlsfonts will list all the installed fonts for your system. Some common fonts are fixed and variable. XLoadQueryFont returns a pointer to an XFontStruct structure, or NULL on failure.

The XFontStruct structure contains a number of fields. Only three of these fields will be really important for your programs. See Listing 2.

The three fields are ascent (the height of the tallest character), descent (the height downward of the lowest character), and fid (a font ID number that is used with a graphics context).

Setting A Graphics Context

Part 2 in our series showed XCreateGC, an Xlib function that creates a graphics context. The graphics context, or GC, controls the pen for drawing. With text, the GC also controls what font is used when drawing text. After loading a font with XLoadQueryFont, you need to set a graphics context to draw with your newly-loaded font. The Xlib function XSetFont handles this task.

Display *display;
GC gc;
Font font_id;
XSetFont( display, gc, font_id );
Because we have a pointer to an XFontStruct structure, we can get the font ID from the fid field. So in our code, we'll set a GC to use a given font with

Display *display;
GC gc;
XFontStruct *font;

XSetFont( display, gc, font->fid );
We're set up to draw text.

Drawing Text

Several Xlib functions can draw text. These include functions to draw 16-bit character text, such as text for Asian languages. Unfortunately, the X Window System is weak at handling text in multiple languages, such as combined English and Japanese. The X Consortium is improving this area for Release 5. In our examples, we will use 8-bit characters, which are enough for most western languages. We hope that Release 5 will accommodate international applications.

The two main functions for drawing text are XDrawlmageString and XDrawString. Both take the same parameters, as shown in Listing 3. Both functions draw length number of characters from string at the given x, y location, using the color and font set up in the graphics context, gc. XDrawString draws the foreground part of the text — just the letters. XDrawImageString also draws in the background dots around a character cell. It is useful for terminal-emulation software. We'll use XDrawImageString in our sample program.

We set the background color for a graphics context using XSetBackground, as shown in Listing 4. To avoid describing the very complex ways to model color in X, we'll use the macros BlackPixel and WhitePixel for our colors, as we explained in Part 2.

Text Height And Width

We can find out how tall text is by using the ascent and descent fields of the XFontStruct structure.

XFontStruct *font;
int   height;

height = font->ascent + font->descent;
This won't tell us how high an individual character is, but it does tell us how much vertical space to leave for a line of text.

To determine how many pixels wide a given text string is, we use the Xlib function XTextWidth().

XFontStruct  *font;
char         *string;
int          string_length;
int          text_width;

text_width = XTextWidth( font, string, string_length );
Because some fonts are proportional and some are fixedwidth, we must pass a string to XTextWidth. XTextWidth returns how many pixels wide that string will be when drawn in the given font.

Asking For Keyboard Input

Because our test program wants keyboard input, we must hint that fact to the window manager, using the XWMHints structure. If we don't set an input hint, then our program may never see keyboard events. See Listing 5.

Because this structure may change size in the future, use XAllocWMHints to allocate memory for it, if you have X11 Release 4. If not, use malloc.

XWMHints *wmhints;

wmhints = (XWMHints *)malloc( sizeof(XWMHints) );
Use free to free up the memory when done.

free( wmhints );
Set the input field to True, along with a bit mask-flag indicating that the input field is set:

wmhints->input = True;
wmhints->flags = InputHint;
Next, pass these hints to the windows manger using the X function XSetWMHints.

Display *display;
Window window;
XWMHints *wmhints;

XSetWMHints( display, window, wmhints );
Keyboard input in X generates KeyPress events. For each key that is pressed, the program will receive a KeyPress event, but only if the program asked the X server to send KeyPress events.

Part 2 in this series described the X-SelectInput function, which asks the X server to send certain types of events to our program. In Part 2, we only asked for ButtonPress (presses on a mouse button) and Expose (requests to redraw part of our window) events. The code in Listing 6 will also ask for KeyPress events.

The KeyPress Event

When a KeyPress event arrives, its type will be, obviously, KeyPress. Pressing a key on the keyboard generates a KeyPress event. Releasing the key generates a KeyRelease event. Both events have the same structure.

Listing 7 shows the event structure for the KeyPress event.

The most important field in the X-KeyEvent structure is the keycode. The keycode is a number that represents what key was actually pressed on the keyboard. The keycode is similar to the scan code on a PC keyboard.

Unfortunately, the keycode is inherently nonportable. Each X vendor uses a private keyboard and set of keycodes. Because the X Window System is supposed to provide portability, we must convert a keycode into something more meaningful and portable, preferably into text. The Xlib function XLookupString converts a KeyPress event into a KeySym (which we'll cover later in this article) and a text string. See Listing 8.

XLookupString tries to convert the KeyPress event. It gives back two useful pieces of information: a string (in ISO Latin-1, which is close enough to US ASCII) and a KeySym.

If the user typed in an "a" key, the string will return an "a" (although some systems don't terminate the string, which is why in our program we always put in a null character to terminate the string returned from XLookupString). The KeySym will also equal 'a' (which is the number 97).

We'll need to allocate space for the string, the KeySym and the XComposeStatus struct, because these values are returned.

KeySyms

KeySyms are symbolic key IDs used in the X Window System to provide portability between different keyboards. KeySyms are defined for letters (A to Z and a to z), function keys, arrows, and just about every key on a keyboard. KeySyms are defined in two files: X11/keysymdef.h and X11/keysym.h. Any Xlib program that uses these symbols should include those two files, along with X11/Xlib.h and X11/Xutil.h.

#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/keysymdef.h>
#include <X11/keysym.h>
The KeySym symbols defined in those files all begin with XK_. For example, the capital A KeySym is defined as

#define XK_A  0x041
And the lowercase a as

#define XK_a   0x061
Other special characters include XK_BackSpace, XK_Delete, XK_Tab, XK_Escape, XK_Return, XK_Up, XK_Home, and XK_Help.

Function Keys

There are KeySyms for most function keys, with names like XK_F1 for the F1 function key and XK_F8 for the F8 function key. Some keyboards, such as older Sun keyboards, have left and right function keys as well, with KeySyms such as XK_L4 (Left 4) and XK_R6 (Right 6). Although the KeySyms are portable, determining which keys to use is sometimes hard because not all keyboards have function keys.

Special Shift Keys

In DOS, the Alt key is often used for keyboard shortcuts, such as Alt-Q to quit. In X Window parlance, this is the Meta key. We prefer the term Meta, because this key is not always labeled Alt. On Hewlett-Packard workstations, for example, the Meta key is labeled Extend Char. On Sun Type 4 keyboards, the Alt key is not the Meta key. The diamond-shaped key next to the Alt key is the default Meta key. Many keyboards even have a left and a right Meta key.

You test a Meta key combination by examining the state field of the XKeyEvent struct shown in Listing 9.

You can also test for the shift key with

XKeyEvent event;

if ( event.state & ShiftMask )
   {
   /* Shift key down */
   }
Note that XLookupString takes care of normal shifted characters, such as A and E, so you only need to test the shift key for combinations such as shift-right arrow.

Checking For Keyboard Input

Because keyboard input generates X events, the same code that checks for other events also checks for KeyPress events. As in the program in Part 2, we'll use the Xlib function XNextEvent to wait for the next event from the X server. See Listing 10.

With KeyPress events, we need to determine if the event is a plain alphanumeric key or some form of special key like arrows or function keys. We can generally test if the KeySym returned by XLookupString is between 32 (ASCII space) and 126 (ASCII Tilde). If so, then we have an alphanumeric keyboard entry. Note that users in other countries will need to modify this test. See Listing 11.

Clearing The Window

One other Xlib function, XClearWindow, clears out a window to its background color (which we set to white).

Display *display;
Window window;

XClearWindow( display, window );
XClearWindow offers a simple way to blank out our window.

The Sample Program

For this month's sample program, we added keyboard input and text to our program from Part 2. If you're typing this in by hand, you can speed up the process by using the program in Part 2 and then just inserting the differing lines.

Our program, xlib2.c in Listing 12 first opens a connection to an X server and then creates a window. We create a graphics context to draw in and then load in a font, setting the graphics context to draw with our font. (If the font name we used does not work with your system, use the xlsfonts program to determine a font name that is acceptable.)

The xlib2 program asks the X server to provide Expose, ButtonPress, and KeyPress events. On an Expose event, we redraw a global string. On a ButtonPress event the program quits after calling XCloseDisplay.

On KeyPress events, we add the new key (if it is alphanumeric) to a global string and then update the text in the window for our new key. If the user presses the Escape key, we clear out our string. If the user presses a Delete or Backspace, we wipe out the last character in the global string, again updating the display. There are more efficient ways of doing this task, but our goal is to illustrate X Window programming.

Try out the program and look carefully over the sample code. This will give you the most information as you try your own Xlib programs. The code is in Listing 12.

When you run it, the window it creates should look like

{IMPORT C:\\XWINDOW\\XLIB2.TIF \
   \* mergeformat|}

Compiling And Linking The Sample Program

To compile and link the xlib2 program, you must link in the X library, usually /usr/lib/libX11.a. A common UNIX command for this is

cc -o xlib2 xlib2.c -lX11
You may need to use the -I option to specify where the X11 include files are located, and the -L option to specify where the X11 library is located.

If your system uses the include file stdlib.h to pre-declare malloc, define SYSV, as in

cc -DSYSV -o xlib2 xlib2.c -lX11
On some 386 versions of UNIX, you may need to link in the networking libraries as well (since the X Window System is a networking windowing system). On SCO's Open Desktop 1.0, you'll need a command like

cc -oxlib2 -DLAI_TCP -Di386 \
   DSYSV -Dsysv xlib2.c \
   -lX11 -ltlisock -lsocket -lnsl_s
On Interactive's 386/ix (depending on the version), you'll need a command like

cc -o xlib2 xlib2.c -lX11 -linet

Conclusion

This briefly introduces Xlib programming, Unfortunately, we could not show you everything about the X library. We've tried to provide a good starting point for learning X programming.

The next installment of our X Window programming series introduces the SHAPE extension to make odd-shaped windows and other features new to Release 4 of the X Window System.