Listing 27.1 shows the listing of the include file that is used by the source modules xeg.c and events.c.
The file includes the usual <stdio.h> and <strings.h> definitions to define the macro NULL and strerror(3), respectively. It should be noted that one of the great features of UNIX graphics programming is that you can send output to standard output, in addition to graphics on the X Window server. This often assists greatly in debugging efforts.
The include file <X11/Xlib.h> (line 5) is required to define a number of Xlib functions and macros. Include file <X11/Xutil.h> (line 6) is needed to define the type XSizeHints, which is used in this example program.
The typedef Ulong is declared in line 8 for programming convenience, since the type unsigned long is used frequently. Macros B1, B2, and B3 are mouse button bits that define bits 0, 1, and 2, respectively, where 0 is the least significant bit. These macros are used in the event processing loop.
The remainder of the include file (lines 14–27) defines global values that are initialized by the main() program.
Listing 27.2 shows the source listing for the main() program.
The main() program takes care of the initialization and cleanup for the X Window demonstration. Much of this initialization is common to most X Window programs. The overall steps used by the main program are as follows:
Open the display on the X Window server (lines 30–33). This call creates a socket and connects to the X Window server, which may be a local or remote hosted server.
Select the default screen (line 35). X Window servers are capable of supporting more than one display screen. Here the application simply chooses the default screen.
The colors green and blue are allocated in lines 44–48. Again, approximate colors are acceptable to this application.
Pixel values for foreground and background are established in variables fg and bg (lines 59 and 60).
This program establishes "hint" information about where the window should be created (lines 65–69). Line 69 indicates that the program wants to select the position and size of the window.
A simple drawing window is created in lines 74–81. Argument disp specifies the connection to the server. Note that it is possible for a program to establish connections to multiple X Window servers.
Once those steps have been accomplished, it is possible to invoke the function event_loop() that is in source module events.c. When the function event_loop() returns, however, this indicates that it is time for this client program to terminate. Termination consists of destroying the window that was created (xwin) and closing the connection to the X Window server (disp). The main() program then terminates at the return statement in line 112.
A number of important X Window concepts have been glossed over here to get you to the most important aspect of this chapter, which is the event-processing loop. However, even with a rudimentary understanding, you could clone other X Window graphics program clients from this main program. As your understanding grows, you can expand upon the code presented here.
The feature piece of this chapter is the event-processing loop contained within the source module events.c. Before examining the code for it, compile and try the program to see what it is supposed to do. The following shows a compile session under FreeBSD:
$ make
cc -c -Wall -I/usr/X11R6/include xeg.c
cc -c -Wall -I/usr/X11R6/include events.c
cc -o xeg xeg.o events.o -L/usr/X11R6/lib -lX11
$
It is often necessary to indicate where the include files and the X Window libraries are. If you compile this program on a different UNIX platform, you may need to adjust the options -I/usr/X11R6/include and -L/usr/X11R6/lib to point to where your include and library files are.
Normally, you start the program and place it into the background when you are using an xterm(1) session. This allows you to continue using the xterm(1) window for other things while your client program runs:
$ ./xeg &
$
Soon after the program starts, you should see a window like that shown in Figure 27.5.
A black background window should be created with the white-lettered message xeg.c. Using the mouse now, it is possible to draw in different colors. To exit the window, press the lowercase q key to quit (the window must have the focus for the q key to work).
Using the left, middle, or right mouse button, you can draw in the window with the colors red, green, and blue, respectively. If you have a two-button mouse and middle button emulation enabled, press the right and left buttons simultaneously to get the color green. Figure 27.6 shows the author's attempt to write xeg.c on the window using the mouse.
One other feature of this program is activated with the Shift+click of the mouse. When the Shift key is held down, a different drawing technique causes a starburst effect, as shown in Figure 27.7.
Figure 27.7 shows the mouse starting at the 2 o'clock position and circling around to 8 o'clock, while holding down the Shift key and mouse button at the same time. The way this is accomplished is explained when the code in Listing 27.3 is discussed.
Before X Window events are processed in the event loop, a call to XSelectInput(3X11) is performed to select the events that are of interest (lines 23–26). disp and xwin specify the connection and the window to modify. The events selected are the following:
Since drawing is required, a graphics context is needed to draw with. This specifies the attributes of the drawing pen, such as the foreground and background colors. Line 31 creates a graphics context with a call to XCreateGC(3X11). Line 32 selects the background color of the context by calling XSetBackground(3X11). A similar call to XSetForeground(3X11) is made in line 33 to set the foreground color of the graphics context. You will recall that the main() program established pixel values of white in variable fg and black in bg.
The event loop itself begins with the while statement in line 38 and ends at line 163. Bool variable quit is initialized as False in line 18. Consequently, the while loop continues until quit changes to True.
The function call that drives this event loop is the function XNextEvent(3X11) in line 42. The function synopsis for the function is as follows:
#include <X11/Xlib.h> XNextEvent(display, event_return) Display *display; XEvent *event_return;
Notice that the X Window function is defined in the older C function syntax. This is due to the early start that X Window development had. For compatibility with older software, it has not made the change to the ANSI C function prototypes.
The argument display provides the information about the connection to the X Window server (specifically the socket). Argument event_return is used to return the event information that has been received.
If there are no events to process, XNextEvent(3X11) forces any buffered server requests to be written to the server. Execution is suspended within the function until an interesting event arrives (those events that are not masked out). Once an interesting event is received, the event information is copied to the area pointed to by the event_return argument, and the function returns to the caller.
The definition of the XEvent data type is a large union of event structures. The following synopsis is a subset of the full XEvent definition:
typedef union _XEvent { int type; /* Event type */ XAnyEvent xany; /* Common event members */ XKeyEvent xkey; /* Key events */ XButtonEvent xbutton; /* Mouse button events */ XMotionEvent xmotion; /* Mouse motion events */ XExposeEvent xexpose; /* Window expose events */ XMappingEvent xmapping; /* Key/Button mapping change events */ /* etc. */ } XEvent;
The XEvent type definition is a union of the many member types within it. The most basic member of all is the member type, which identifies the type of the event that is being described.
The member xany defines a number of additional members that are common to almost any event:
typedef struct { int type; /* Event type */ unsigned long serial; /* # of last request processed by server */ Bool send_event; /* true if from a SendEvent request */ Display *display; /* Display the event was read from */ Window window; /* window event was requested in event mask */ } XAnyEvent;
In the XAnyEvent structure definition, you see that the type of the event is included first in the structure. Each X Window request has a serial number assigned to it, and the event indicates the event number in the serial member. The member send_event is True when an event is artificially sent to a window with a function such as XSendEvent(3X11). When this value is False, the event came from the X Window server. The display and window members identify the X Window server connection and the participating window.
The other XEvent union members will be discussed as the code is examined. When an event is received, the switch statement on line 47 dispatches the execution of the program to the correct case statement to process it.
The X Window server makes no guarantee that it will preserve a window when it is obscured. Consequently, when a window is uncovered or made viewable for the first time, one or more Expose event is generated. This permits the client program to restore the image in the newly exposed areas of the window.
Expose events often occur as regions of the full window. Clients that can take advantage of the efficiency achieved by restoring only small portions of an exposed window can do so with these events. For simpler client programs, the entire window must be refreshed instead.
The illustrated demo program simply draws a string of text xeg.c on the new window (lines 54–58). This is done in response to the Expose event, starting with the case statement on line 49. No attempt to restore the current drawing is performed. Consequently, you will find that when you obscure the xeg window and re-expose it, you will only find the text xeg.c redrawn. All other drawn information will be lost.
The synopsis of the XExposeEvent structure is as follows:
typedef struct { int type; unsigned long serial; Bool send_event; Display *display; Window window; int x; /* Upper left x of region */ int y; /* Upper left y of region */ int width /* Width of region */ int height; /* Height of region */ int count; /* # of subsequent Expose events */ } XExposeEvent;
In addition to the members described by the union member XAnyEvent, the XExposeEvent type defines members x, y, width, and height. The x and y members describe the upper-left corner of the region of the window. The width and height members describe the width and height of the region that has been exposed and needs redrawing. The last member count describes how many subsequent Expose events follow.
If your client program is unable to redraw the exposed areas of the window region by region, then all Expose events where count is greater than zero should be ignored. Eventually, the count value will be decremented to zero in a subsequent event, indicating that no more Expose events remain for this window. Simple programs should therefore redraw the entire window only when this count reaches zero. Otherwise, needless repetition of the redraw operations will be performed. Since the demonstration program has been kept simple, it draws xeg.c only when this count reaches zero (line 53).
The case statement on line 61 handles the ButtonPress events. The type definition for XButtonEvent is as follows:
typedef struct { int type; unsigned long serial; Bool send_event; Display *display; Window window; Window root; /* root window that the event occurred on */ Window subwindow; /* child window */ Time time; /* milliseconds */ int x, y; /* pointer x, y coordinates in event window */ int x_root, y_root; /* coordinates relative to root */ unsigned int state; /* key or button mask */ unsigned int button; /* detail */ Bool same_screen;/* same screen flag */ } XButtonEvent;
Member button is consulted in the switch statement on line 68. Depending upon whether Button1, Button2, or any other button has been pressed, bits are set in variable b (lines 70, 73, or 76). Depending on the bits set in b, a color is chosen in lines 94–99 for the foreground. The graphics context is modified to use this color in line 101 with XSetForeground(3X11).
However, member state of this event indicates other important things such as whether the Shift key was pressed at the time of the mouse button press. If the Shift key is pressed at the time of the button down event (line 79), the variable star is set to True. Otherwise, normal drawing is performed when star is set to False in line 82 (more about this later).
Lines 87 and 88 save the coordinates of the mouse when the button was pressed. These coordinates will be required later to draw a line when the mouse moves with the button held down.
When the mouse button is released, event ButtonRelease is processed by the case statement in line 104. The switch statement in lines 110–119 removes the bit that corresponds to the mouse button in variable b. Again, the color is modified by changing the fg variable in lines 124–129. The graphics context gc is then modified in line 130 to reflect this new choice in foreground color.
As the mouse moves with a button held down, MotionNotify events are delivered (line 133). The XMotionEvent type definition is given in the following synopsis:
typedef struct { int type; unsigned long serial; Bool send_event; Display *display; Window window; Window root; /* root window that the event occurred on */ Window subwindow; /* child window */ Time time; /* milliseconds */ int x, y; /* pointer x, y coordinates in event window */ int x_root, y_root; /* coordinates relative to root */ unsigned int state; /* key or button mask */ char is_hint; /* detail */ Bool same_screen;/* same screen flag */ } XMotionEvent;
The xeg program simply draws a line from the last saved x0 and y0 positions to the new location specified in the XMotionEvent structure members x and y (line 139). This is performed using the XDrawLine(3X11) function, using the color attributes assigned to the graphics context gc.
For normal drawing (no Shift key), the current mouse coordinates are then saved at lines 146 and 147. The next MotionNotify then causes the next line to be drawn from the previous mouse position to the current, effectively drawing a line as a pen would.
When the Shift key is pressed, the coordinates in lines 146 and 147 are not saved. This causes lines to always be drawn from the original button press coordinate to the present mouse coordinate. This gives the starburst effect as the mouse is moved around.
As a bit of housekeeping, MappingNotify events are processed by a call to XRefreshKeyboardMapping(3X11). The X Window system allows keyboard keys and mouse buttons to be remapped differently according to the user's preference. To support this flexibility, a client program can pass the XMappingEvent structure directly to XRefreshKeyboardMapping(3X11). It will then handle any necessary mapping changes for you.
The case statement in line 155 intercepts the KeyPress event. The XKeyEvent member xkey holds an untranslated key symbol reference. The call to XLookupString(3X11) causes this key to be translated into ASCII form in the supplied buffer kbuf[]. The length of the translated key is returned.
When the key translates to an ASCII q in line 160, the variable quit is set to True to allow the program to exit the event-processing loop. Upon exiting the loop, the graphics context that was created earlier is freed in line 165 by calling XFreeGC(3X11).
That concludes the code walk-through for this demonstration program. This simple drawing program has demonstrated event-driven programming and has also provided you with a taste of how X Window programming is performed.
As an exercise, you are encouraged to improve upon this program. Complete the program by adding code to keep track of all drawing commands performed within the window. Then, when the Expose events occur, it should be possible to re-create the lost artwork. Another method is to learn about the XCreatePixmap(3X11) function. The drawn image can be maintained in a pixmap, and then the window regions can be refreshed from it when Expose events occur.
3.135.196.172