Johnson and Reichard are authors of X Window Applications Programming, Advanced X Window Applications Programming, and Power Programming Motif (all MIS: Press, 800-MANUALS). Johnson can be reached at erc@pai.mn.org via UUCP mail. Reichard's Compuserve address is 73670, 3422.
The X Window System is a graphical network-based windowing system that runs under UNIX, VMS, AmigaDOS and, to some extent, MS-DOS. With the companion C program, we introduce here the lowest level of X Window programming using the X library. The X library and the X Window System are extremely complex. Our goal here is to describe enough of the X library for you to start writing X programs.
The X Library
The X library is a library of C functions that allow you to design windowed graphics applications. The X library has functions to create windows, such as XCreateSimpleWindow, and draw lines, such as XDrawLine. Under the hood, the X library, or Xlib, translates commands such as XDrawLine into the underlying X network protocol.This X network protocol allows you to display X programs across a network. For example, you can run an X program on a Sun SPARCStation and display its windows on a 386 clone. X can separate client applications (the programs you'll write) from the X server (the program that actually draws the dots on a screen). The X Window usage of client and server is reversed from the norm. With X, the X server runs on the machine on your desk and draws the dots on your monitor. X client programs can also run on the machine on your desk, or they can run on any host that is connected over a network.
Opening And Closing A Display
Because X is based on networking and separates the client applications from the server, your first X call must connect you with an X server. The Xlib function is XOpenDisplay. See Listing 1. Almost every X program needs to include the file /usr/include/X11/Xlib.h, which contains necessary definitions for the X library. Most X programs also include /usr/include/X11/Xutil.h, which contains more definitions.You need to pass the name of a display to XOpenDisplay. The display name is the name of the connecting X server. If you have a DISPLAY environment variable set correctly, then you can pass NULL to XOpenDisplay, which is what we'll do in our sample program. There are many rules for setting display names, but the basic idea is simple. A display name consists of three parts:
In most cases, both display and screen numbers are zero for the first (and usually only) X server and the first screen. Thus, a machine named nokomis would have an X display name of:
- the network host name of the machine
- the display number (which of the possibly many X servers on the machine we're connecting with)
- the screen number (if your workstation has multiple screens for a single keyboard and mouse).
nokomis:0.0Note the colon and the period. If you don't have a DISPLAY environment variable set (or have it set incorrectly), consult the X Window user guide that came with your system. There are so many variants for display names that there isn't enough space to go into them all here.XOpenDisplay returns a pointer to a Display structure. You won't access fields of this structure directly there are a number of Xlib functions to access the Display structure.
When you're done with an X program, your program should call XCloseDisplay to shut down the connection to the server:
Display *display; XCloseDisplay( display );After you call XCloseDisplay, don't use the Display pointer again because it is no longer valid.
Creating A Window
Once you open a connection to an X server, your next step is creating a window. The easiest method is to use XCreateSimpleWindow. See Listing 2. XCreateSimpleWindow returns a window ID that you'll need in most drawing functions. You can easily determine the x and y location (X places the origin in the upper left corner) or the width and height of the window, but determining the parent window and the colors is more difficult.All X screens have a root window, which is effectively the screen background. When you first create a window, you'll want to use the root window as a parent. If you then create subwindows, you'll want to use your window as a parent. To get the root window, you can use the macros RootWindow and DefaultScreen.
Display *display; int screen; Window rootwindow; screen = DefaultScreen( display ); rootwindow = RootWindow( display, screen );DefaultScreen returns the default screen (usually the only screen) for the connected X server. RootWindow returns the root window for the given display (X server) and screen on the display.
Determining Colors
X models color in a number of different ways, all of which are complex. For simplicity, our sample program will use monochrome black-and-white. You can get color values for black-and-white by using the macros BlackPixel and WhitePixel:
Display *display; int screen; unsigned long border_color, background_color; border_color = BlackPixel ( display, screen ); background_color = WhitePixel ( display, screen );The Window Manager
Once you've created a window, you need to tell the window manager about the window. The window manager is an X Window program that places title bars on windows and controls how these windows are placed on the screen and resized. Unlike Macintosh or Microsoft Windows, X allows the choice of window managers (if any). Three of the most common window managers are the Motif window manager (mwm), the Open Look window manager (olwm) and the Tab window manager (formerly Tom's window manager, twm).In order for the window manager to make the correct decisions about what to do with your new window, you must send it some information. Your program is supposed to put together and send off quite a lot of information, but for this beginning program, we'll concentrate on a few basics. When you start writing more Xlib applications, you should consult the Inter-Client Communications Conventions Manual, or ICCCM, which describes how a well-behaved X Window program should act.
For now, we'll use the Xlib function XSetStandardProperties. See Listing 3. XSetStandardProperties provides the window manager and a session manager (if they are present) with information about your window. The window name usually goes on the title bar. The icon name goes with an icon (if your window is iconified). The icon bitmap is an optional bitmap to use for your window's icon. (In our sample program, we'll pass the X Window constant None for the icon bitmap.)
The command-line parameters, argc and argv, are passed to XSetStandardProperties. Note the reversed order argv comes first when calling XSetStandardProperties.
The XSizeHints structure contains information about the size and location of your window. It also contains increments, and maximum and minimum values to help control how your window is resized. Unfortunately, the XSizeHints structure is defined differently between Release 3 (and earlier releases) and Release 4 of X. (X11 Release 4 is the most current version of X, but many vendors have not yet upgraded. Differences between releases can cause problems.) The sample program works fine under both Release 3 and Release 4 we're not doing anything fancy. Listing 4 shows the XSizeHints structure.
In Release 4, a new function dynamically allocates space for an XSizeHints structure, XAllocSizeHints:
XSizeHints *sizehints; sizehints = XAllocSizeHints();We use malloc in our sample program, which works in both Releases 3 and 4 of X.The flags field of the XSizeHints structure contains bit masks that tell the X library which of the many fields in the XSizeHints structure you filled in. (You don't have to fill in all of the fields.) In our sample program, we'll just fill in the position (x, y) and the size (width, height). If you're running under Release 4, those four fields are considered obsolete. Use the base_width and base_height fields for the size instead. (You should fill in enough information to be compatible with both Release 3 and Release 4.)
The flag bits for the position and size are PPosition and PSize. The leading P tells the window manager that our program chose the position and size. If the user had passed the position and size as command-line parameters, the flags should be USPosition and USSize instead. Use an OR operation to combine the flags for the fields you set. For now, we'll just use PPosition and PSize. See Listing 5.
In Release 4, XSetStandard Properties is considered obsolete. It still exists in the Release 4 Xlib, but you should use the new XSetWMProperties. If your system doesn't have this function, you're out of luck.
Asking For Input
The X server will only send your window the type of events you ask for. If your program doesn't use the keyboard at all and you don't ask for keyboard events, you will never see them.Because most programs do want events, you should call XSelectInput to ask for the event types your program wants. (We'll describe more on events later in this article.)
Display *display; Window window; long event_mask; XSelectInput( display, window, event_mask );The event mask is a set of bits indicating what event types you want. OR together the individual bits. For example, to ask for Expose and ButtonPress events, the event_mask would look like:
event_mask = ButtonPressMask | ExposureMask;Making The Window Appear
When you create a window in X, nothing appears. Creating a window in X allocates data structures for the window in both your program and the X server. To make the window appear on the screen, you must use the function XMapWindow.
Display *display; Window window; XMapWindow( display, window );For performance reasons, the X library caches a number of X protocol requests and then sends out the requests in a batch. This cuts down on the number of relatively slow network packets. The X library normally hides all this from you, except for one rule. After you've called a number of Xlib functions and want to see the results of these calls, use the XFlush function.
Display *display; XFlush( display );XFlush flushes the cache of network requests to the server. By planning where you call XFlush, you can improve the performance of your programs. If you don't see the results of your drawing commands (which we'll go into later), it may be because you forgot to call XFlush.If you're following along in the sample program, you should now be seeing the new window appear on the screen.
What Are Events?
The next step is using events. Events are messages sent by the X server to your (X client) programs. The user generates events by pressing a key or clicking the mouse. Events are also generated when the X server determines that your program needs to redraw all or part of its window.X applications should be event_driven. That is, X programs should loop, getting the next event from the X server and then acting on the event. This event loop defines most of what the program does. If you've ever programmed for the Macintosh or Microsoft Windows, you'll understand this.
Getting The Next Event
There are a number of Xlib functions for getting events. The easiest is XNextEvent.
Display *display; XEvent *event; XNextEvent( display, event );XNextEvent blocks awaiting the next X window event to come down the pike from the X server. The XEvent structure is a union of a number of different types of events. Each element in the union has fields that make sense to its particular type of event.
typedef union _XEvent { int type; XAnyEvent xany; XButtonEvent xbutton; ... XExposeEvent xexpose; ... } XEvent;In the sample program, we'll handle two types of events ButtonPress and Expose.
Button Press Events
A ButtonPress event occurs whenever the user depresses a button on the pointer (usually the mouse, but it could be a trackball or something more exotic). A ButtonRelease event occurs when the user lets up on a mouse button. Both events use the same structure. See Listing 6.For our purposes, the important fields are the x, y location, which specifies where the mouse was in the window, and the button field. This field will be set to one of:
Button1 Button2 Button3 Button4 Button5This indicates which button was pressed. Normally, on a three-button mouse, Button1 is the leftmost mouse button, Button2 the center button, and Button3 the rightmost button.Your application will get ButtonPress events only when the mouse cursor is actually inside your window and the user depresses a mouse button. Mouse and keyboard events go to the window in which the mouse pointer resides (although some window managers may modify this).
Expose Events
In any windowing system that allows for overlapping windows, your application's window may be partially (or totally) covered by other windows. When those other windows are moved off your window, an area on your window needs to be refreshed. There's always been a debate about who is responsible for refreshing this area the window system or should your program? In X, the usual answer is your program.Unlike other windowing systems, like AmigaDOS, the X Window System doesn't guarantee that what you draw into a window will stay there. In fact, with X, you can assume the opposite. At any given moment, all X programs need to be able to redraw all or part of their windows.
When you need to redraw part of your window, the X server will send an Expose event. See Listing 7. Each Expose event pertains to a single rectangle, given by the location of its origin (x, y the upper left corner of the rectangle) and its size (width, height). When you get such an event, your program is supposed to redraw the contents of that rectangular area.
Expose events usually come in groups. That is, your program will receive a number of Expose events, each with one rectangular part of the full exposed area (which may be odd-shaped). Your program can then redraw the given rectangle. Or it can wait until all the Expose events arrive for a batch and then redraw either the whole window (the easy way) or just that part of the window that was exposed. To know when a whole batch has arrived, check the count field. The count field will be zero (0) when the last Expose event of a batch has arrived.
Creating A Graphics Context
When an Expose event arrives, your program will need to redraw part of its window. To draw with the X library, you must first create a graphics context or GC. A graphics context contains information about a virtual pen the pen size, the pen color, whether lines are to be drawn dashed, and so on. Use XCreateGC to create a GC. See Listing 8.You can also create a graphics context to draw into a pixmap (essentially an off-screen window). Just replace the window ID with a pixmap ID. The same goes for most of the drawing functions in Xlib, including the ones we'll describe in this article.
There are a host of options for the mask and xgcvalues parameters, none of which are needed for our simple drawing purposes. In our sample program, we'll create a GC more like:
gc = XCreateGC( display, window, OL, (XGCValues *) NULL );Once we've created a graphics context, we need to set up the foreground drawing color. (The background drawing color is used only for text.) XSetForeground sets the foreground drawing color of a graphics context.
Display *display; GC gc; unsigned long foreground_color; XSetForeground( display, gc, foreground_color );We need to call both XCreateGC and XSetForeground before we draw. Remember to eventually call XFlush to actually send out these requests to the X server.
Drawing Lines And Rectangles
You can draw lines by using the Xlib function XDrawLine. See Listing 9. The graphics context controls the drawing color, pen size, dashing and so on.You can use XDrawRectangle to draw the outline of a rectangle.
Display *display; Window window; GC gc; int x, y; * upper left corner */ unsigned int width, height; /* size of rectangle */ XDrawRectangle ( display, window, gc, x, y, width, height );Listing 10 shows how you can fill a rectangle with XFillRectangle. (If the Xlib function is XDrawSomething, for instance XDrawRectangle, an outline of the Something shape is drawn. If the function is XFillSomething, then a filled object is drawn.)
A Sample Program
We've put together a sample program based on what we've described so far. The code is in the file xlib1.c, shown in Listing 11. First, the sample program sets up a display connection to an X server with XOpenDisplay. The program creates a window and a graphics context. Then XMapWindow puts the window visibly on the screen and XFlush sends out all requests to the X server.The program then goes into an event loop. If the user clicks a mouse button in our window, then we quit the program. If the X server sends an Expose event, our program waits until the last event of a batch arrives (when the count field is zero). When this happens, the Redraw function redraws a simple picture (a few lines and rectangles).
Note that we never draw anything into the window when we first create it. In X, there is no initial drawing. Always wait until an Expose event arrives and then draw even the first time. When the window first appears, don't worry, the X server will send our program an Expose event, one that looks the same as all other Expose events. This can be a tricky part of the X library.
Look over the code and try out the program. When you run it, the window it creates should look like Figure 1
Compiling And Linking
To compile and link the xlib1 program, you need to link in the X library, usually /usr/lib/libX11.a. A common UNIX command for this is:
cc -o xlib1 xlib1.c -lX11You may have to use the -I option to specify where the X11 include files are located, and the -L option to specify the X11 library location.If your system uses the include file stdlib.h to predeclare malloc, define SYSV, as in
cc -DSYSV -o xlib1 xlib1.c -lX11On some 386 versions of UNIX, you may need to link in the networking libraries as well (since the X Window System is a network-based windowing system). On SCO's Open Desktop 1.0, you'll need a command like:
cc -o xlib1 -DLAI_TCP -Di386 \ -DSYSV Dsysv xlib1.c -lX11 \ -ltlisock -lsocket -lnsl_sOn Interactive's 386/ix (depending on the version), you'll need a command like:
cc -o xlib1 xlib1.c -lX11 -linetThe X manuals that came with your system should have more information on compiling X programs.