An Xlib Client Program

Listing 27.1 shows the listing of the include file that is used by the source modules xeg.c and events.c.

Code Listing 27.1. xeg.h—Common Include File for xeg.c and events.c
1:   /* xeg.h */
2:
3:   #include <stdio.h>
4:   #include <strings.h>
5:   #include <X11/Xlib.h>
6:   #include <X11/Xutil.h>
7:
8:   typedef unsigned long Ulong;
9:
10:  #define B1      1           /* Left button */
11:  #define B2      2           /* Middle button */
12:  #define B3      4           /* Right button */
13:
14:  extern Display *disp;       /* Display */
15:  extern int scr;             /* Screen */
16:
17:  extern Ulong bg;            /* Background color */
18:  extern Ulong fg;            /* Foreground color */
19:
20:  extern Ulong wht;           /* White */
21:  extern Ulong blk;           /* Black */
22:
23:  extern Ulong red;           /* red */
24:  extern Ulong green;         /* green */
25:  extern Ulong blue;          /* blue */
26:
27:  extern Window xwin;         /* Drawing window */
28:
29:  extern void event_loop(void);
30:
31:  /* End xeg.h */

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.

Code Listing 27.2. xeg.c—The main() Function of the Xlib Client Program
1:   /* xeg.c */
2:
3:   #include "xeg.h"
4:
5:   Display *disp;              /* Display */
6:   int scr;                    /* Screen */
7:
8:   Ulong bg;                   /* Background color */
9:   Ulong fg;                   /* Foreground color */
10:
11:  Ulong wht;                  /* White */
12:  Ulong blk;                  /* Black */
13:
14:  Ulong red;                  /* red */
15:  Ulong green;                /* green */
16:  Ulong blue;                 /* blue */
17:
18:  Window xwin;                /* Drawing window */
19:
20:  int
21:  main(int argc,char **argv) {
22:      Colormap cmap;          /* Color map */
23:      XColor approx;          /* Approximate color */
24:      XColor exact;           /* Precise color */
25:      XSizeHints hint;        /* Initial size hints */
26:
27:      /*
28:       * Open display (connection to X Server) :
29:       */
30:      if ( !(disp = XOpenDisplay(NULL)) ) {
31:          fprintf(stderr,"Cannot open display: check DISPLAY variable
");
32:          exit(1);
33:      }
34:
35:      scr = DefaultScreen(disp);  /* Obtain default screen */
36:      cmap = DefaultColormap(disp,scr);
37:
38:      /*
39:       * Obtain color information :
40:       */
41:      XAllocNamedColor(disp,cmap,"red",&exact,&approx);
42:      red = approx.pixel;
43:
44:      XAllocNamedColor(disp,cmap,"green",&exact,&approx);
45:      green = approx.pixel;
46:
47:      XAllocNamedColor(disp,cmap,"blue",&exact,&approx);
48:      blue = approx.pixel;
49:
50:      /*
51:       * Get black and white pixel values :
52:       */
53:      wht = WhitePixel(disp,scr); /* White pixel */
54:      blk = BlackPixel(disp,scr); /* Black pixel */
55:
56:      /*
57:       * Choose colors for foreground and background :
58:       */
59:      fg = wht;                   /* use white foreground */
60:      bg = blk;                   /* use black background */
61:
62:      /*
63:       * Set Hint Information for Window placement :
64:       */
65:      hint.x = 100;               /* Start x position */
66:      hint.y = 150;               /* Start y position */
67:      hint.width = 550;           /* Suggested width */
68:      hint.height = 400;          /* Suggested height */
69:      hint.flags = PPosition | PSize; /* pgm specified position, size */
70:
71:      /*
72:       * Create a window to draw in :
73:       */
74:      xwin = XCreateSimpleWindow(
75:          disp,                   /* Display to use */
76:          DefaultRootWindow(disp),/* Parent window */
77:          hint.x, hint.y,         /* Start position */
78:          hint.width, hint.height,/* Window Size */
79:          7,                      /* Border width */
80:          fg,                     /* Foreground color */
81:          bg);                    /* Background color */
82:
83:      /*
84:       * Specify the window and icon names :
85:       */
86:      XSetStandardProperties(
87:          disp,                   /* X Server connection */
88:          xwin,                   /* Window */
89:          "xegwin",               /* Window name */
90:          "xeg.c",                /* icon name */
91:          None,                   /* pixmap for icon */
92:          argv,argc,              /* argument values */
93:          &hint);                 /* sizing hints */
94:
95:      /*
96:       * Map the window, and ensure it is the topmost
97:       * window :
98:       */
99:      XMapRaised(disp,xwin);
100:
101:     /*
102:      * Process the event loop :
103:      */
104:     event_loop();
105:
106:     /*
107:      * Cleanup :
108:      */
109:     XDestroyWindow(disp,xwin);          /* Release and destroy window */
110:     XCloseDisplay(disp);                /* Close connection to X Server */
111:
112:     return 0;
113: }

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:

  1. 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.

  2. 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.

  3. A color map is obtained (line 36). X Window graphics operations used in this program require the use of a color map. A color map is associated with a specific screen and server connection (scr and disp, respectively).

  4. The color red is allocated in the color map cmap (line 41). The approximate value for red is used in line 42, since the actual color is not critical for this application.

  5. The colors green and blue are allocated in lines 44–48. Again, approximate colors are acceptable to this application.

  6. Pixel values for white and black are determined and assigned to the variables wht and blk, respectively (lines 53 and 54). These colors will be used to establish default foreground and background colors.

  7. Pixel values for foreground and background are established in variables fg and bg (lines 59 and 60).

  8. 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.

  9. 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.

  10. A call to XSetStandardProperties(3X11) (lines 86–93) is made to specify the window's name, its icon name, a pixmap for the icon if any, resource setting arguments (from the command line), and sizing hints.

  11. Function XMapRaised(3X11) is called in line 99 to cause the created window to be mapped (displayed). Until this point, the X Window server has just kept notes about the window specified by xwin.

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.

Figure 27.5. The startup X Window created by client program ./xeg.


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.

Figure 27.6. The X Window with xeg.c hand drawn with 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. A starburst drawn in the X Window with Shift+click.


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.

Code Listing 27.3. events.c—The Event-Processing Loop
1:   /* events.c */
2:
3:   #include "xeg.h"
4:
5:   /*
6:    * The X Window Event Loop :
7:    */
8:   void
9:   event_loop(void) {
10:      int x0, y0;             /* Prior position */
11:      GC gc;                  /* Graphics context */
12:      XEvent evt;             /* X Event */
13:      char kbuf[8];           /* Key conv buffer */
14:      KeySym key;             /* Key symbol */
15:      int kcount;             /* Key count */
16:      int b = 0;              /* Buttons Pressed */
17:      int star = False;       /* Draw stars when True */
18:      Bool quit = False;      /* Quit event loop when True */
19:
20:      /*
21:       * Choose the XEvents that we want to process :
22:       */
23:      XSelectInput(disp,xwin,
24:          KeyPressMask | ExposureMask |
25:          ButtonPressMask | ButtonReleaseMask |
26:          Button1MotionMask | Button2MotionMask | Button3MotionMask);
27:
28:      /*
29:       * Create a Graphics Context :
30:       */
31:      gc = XCreateGC(disp,xwin,0,0);
32:      XSetBackground(disp,gc,bg); /* Set background color of gc */
33:      XSetForeground(disp,gc,fg); /* Set foreground color of gc */
34:
35:      /*
36:       * Process X Events :
37:       */
38:      while ( quit != True ) {
39:          /*
40:           * Fetch an X Event :
41:           */
42:          XNextEvent(disp,&evt);
43:
44:          /*
45:           * Process the X Event :
46:           */
47:          switch ( evt.type ) {
48:
49:          case Expose :
50:              /*
51:               * Window has been exposed :
52:               */
53:              if ( evt.xexpose.count == 0 )
54:                  XDrawImageString(evt.xexpose.display,
55:                      evt.xexpose.window,
56:                      gc,
57:                      105, 65,
58:                      "xeg.c", 5);
59:              break;
60:
61:          case ButtonPress :
62:              /*
63:               * A button has been pressed:
64:               *
65:               * Set the bit corresponding to the mouse button that
66:               * is pressed :
67:               */
68:              switch ( evt.xbutton.button ) {
69:              case Button1 :
70:                  b |= B1;
71:                  break;
72:              case Button2 :
73:                  b |= B2;
74:                  break;
75:              default :
76:                  b |= B3;
77:              }
78:
79:              if ( evt.xbutton.state & ShiftMask )
80:                  star = True;
81:              else
82:                  star = False;
83:
84:              /*
85:               * Save the current position :
86:               */
87:              x0 = evt.xbutton.x;
88:              y0 = evt.xbutton.y;
89:
90:              /*
91:               * Establish the drawing color based upon the leftmost
92:               * mouse button that is pressed :
93:               */
94:              if ( b & B1 )
95:                  fg = red;
96:              else if ( b & B2 )
97:                  fg = green;
98:              else
99:                  fg = blue;
100:
101:             XSetForeground(disp,gc,fg); /* Set foreground color of gc */
102:             break;
103:
104:         case ButtonRelease :
105:             /*
106:              * A button has been released :
107:              *
108:              * Unset the bit corresponding to the released color :
109:              */
110:             switch ( evt.xbutton.button ) {
111:             case Button1 :
112:                 b &= ~B1;
113:                 break;
114:             case Button2 :
115:                 b &= ~B2;
116:                 break;
117:             default :
118:                 b &= ~B3;
119:             }
120:
121:             /*
122:              * Set the color based upon the leftmost mouse button :
123:              */
124:             if ( b & B1 )
125:                 fg = red;
126:             else if ( b & B2 )
127:                 fg = green;
128:             else
129:                 fg = blue;
130:             XSetForeground(disp,gc,fg); /* Set foreground color of gc */
131:             break;
132:
133:         case MotionNotify :
134:             /*
135:              * Motion with a button down :
136:              *
137:              * Draw a line from the last know position, to the current :
138:              */
139:             XDrawLine(disp,xwin,gc,x0,y0,evt.xmotion.x,evt.xmotion.y);
140:
141:             /*
142:              * When drawing lines, we must save the last position that
143:              * we have drawn a line segment to :
144:              */
145:             if ( star == False ) {
146:                 x0 = evt.xmotion.x;     /* Save x for next line segment */
147:                 y0 = evt.xmotion.y;     /* Save y for next line segment */
148:             }
149:             break;
150:
151:         case MappingNotify :
152:             XRefreshKeyboardMapping(&evt.xmapping);
153:             break;
154:
155:         case KeyPress :
156:             /*
157:              * A key was pressed; check for 'q'to quit :
158:              */
159:             kcount = XLookupString(&evt.xkey,kbuf,sizeof kbuf,&key,0);
160:             if ( kcount == 1 && kbuf[0] == 'q')
161:                 quit = True;
162:         }
163:     }
164:
165:     XFreeGC(disp,gc);                   /* Release graphics context */
166: }

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:

KeyPressMask Key press events
ExposureMask Window expose events
ButtonPressMask Mouse button press events
ButtonReleaseMask Mouse button release events
Button1MotionMask Pointer motion events when button 1 is down
Button2MotionMask Pointer motion events when button 2 is down
Button3MotionMask Pointer motion events when button 3 is down

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.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.135.196.172