Basic Graphics Programming With The Xlib Library

发表于:2007-07-04来源:作者:点击数: 标签:
Copy from http://users.actcom.co.il/~choo/lupg/tutorials/xlib-programming/xlib-programming.html This document is copyright (c) 1999-2002 by guy keren. Preface The Client And Server Model Of The X Window System GUI programming - the Asynchr

Copy from  http://users.actcom.co.il/~choo/lupg/tutorials/xlib-programming/xlib-programming.html
This document is copyright (c) 1999-2002 by guy keren.

  1. Preface
  2. The Client And Server Model Of The X Window System
  3. GUI programming - the Asynchronous Programming Model
  4. Basic Xlib Notions
    1. The X Display
    2. The GC - Graphics Context
    3. Object Handles
    4. Memory Allocation For Xlib Structures
    5. Events
  5. Compiling Xlib-Based Programs
  6. Opening And Closing The Connection To An X Server
  7. Checking Basic Information About A Display
  8. Creating A Basic Window - Our "hello world" Program
  9. Drawing In A Window
    1. Allocating A Graphics Context (GC)
    2. Drawing Primitives - Point, Line, Box, Circle...
  10. X Events
    1. Registering For Event Types Using Event Masks
    2. Receiving Events - Writing The Events Loop
    3. Expose Events
    4. Getting User Input
      1. Mouse Button Click And Release Events
      2. Mouse Movement Events
      3. Mouse Pointer Enter And Leave Events
      4. The Keyboard Focus
      5. Keyboard Press And Release Events
    5. X Events - A Complete Example
  11. Handling Text And Fonts
    1. The Font Structure
    2. Loading A Font
    3. Assigning A Font To A Graphics Context
    4. Drawing Text In A Window
  12. Windows Hierarchy
    1. Root, Parent And Child Windows
    2. Events Propagation
  13. Interacting With The Window Manager
    1. Window Properties
    2. Setting The Window Name And Icon Name
    3. Setting Preferred Window Size(s)
    4. Setting Miscellaneous Window Manager Hints
    5. Setting An Application's Icon
  14. Simple Window Operations
    1. Mapping And UN-Mapping A Window
    2. Moving A Window Around The Screen
    3. Resizing A Window
    4. Changing Windows Stacking Order - Raise And Lower
    5. Iconifying And De-Iconifying A Window
    6. Getting Info About A Window
  15. Using Colors To Paint The Rainbow
    1. Color Maps
    2. Allocating And Freeing Color Maps
    3. Allocating And Freeing A Color Entry
    4. Drawing With A Color
  16. X Bitmaps And Pixmaps
    1. What Is An X Bitmap? An X Pixmap?
    2. Loading A Bitmap From A File
    3. Drawing A Bitmap In A Window
    4. Creating A Pixmap
    5. Drawing A Pixmap In A Window
    6. Freeing A Pixmap
  17. Messing With The Mouse Cursor
    1. Creating And Destroying A Mouse Cursor
    2. Setting A Window's Mouse Cursor


Basic Graphics Programming With The Xlib Library

  1. Preface
  2. The Client And Server Model Of The X Window System
  3. GUI programming - the Asynchronous Programming Model
  4. Basic Xlib Notions
    1. The X Display
    2. The GC - Graphics Context
    3. Object Handles
    4. Memory Allocation For Xlib Structures
    5. Events
  5. Compiling Xlib-Based Programs
  6. Opening And Closing The Connection To An X Server
  7. Checking Basic Information About A Display
  8. Creating A Basic Window - Our "hello world" Program
  9. Drawing In A Window
    1. Allocating A Graphics Context (GC)
    2. Drawing Primitives - Point, Line, Box, Circle...
  10. X Events
    1. Registering For Event Types Using Event Masks
    2. Receiving Events - Writing The Events Loop
    3. Expose Events
    4. Getting User Input
      1. Mouse Button Click And Release Events
      2. Mouse Movement Events
      3. Mouse Pointer Enter And Leave Events
      4. The Keyboard Focus
      5. Keyboard Press And Release Events
    5. X Events - A Complete Example
  11. Handling Text And Fonts
    1. The Font Structure
    2. Loading A Font
    3. Assigning A Font To A Graphics Context
    4. Drawing Text In A Window
  12. Windows Hierarchy
    1. Root, Parent And Child Windows
    2. Events Propagation
  13. Interacting With The Window Manager
    1. Window Properties
    2. Setting The Window Name And Icon Name
    3. Setting Preferred Window Size(s)
    4. Setting Miscellaneous Window Manager Hints
    5. Setting An Application's Icon
  14. Simple Window Operations
    1. Mapping And UN-Mapping A Window
    2. Moving A Window Around The Screen
    3. Resizing A Window
    4. Changing Windows Stacking Order - Raise And Lower
    5. Iconifying And De-Iconifying A Window
    6. Getting Info About A Window
  15. Using Colors To Paint The Rainbow
    1. Color Maps
    2. Allocating And Freeing Color Maps
    3. Allocating And Freeing A Color Entry
    4. Drawing With A Color
  16. X Bitmaps And Pixmaps
    1. What Is An X Bitmap? An X Pixmap?
    2. Loading A Bitmap From A File
    3. Drawing A Bitmap In A Window
    4. Creating A Pixmap
    5. Drawing A Pixmap In A Window
    6. Freeing A Pixmap
  17. Messing With The Mouse Cursor
    1. Creating And Destroying A Mouse Cursor
    2. Setting A Window's Mouse Cursor

Preface

This tutorial is the first in a series of "would-be" tutorials about graphicalprogramming in the X window environment. By itself, it is useless. A realX programmer usually uses a much higher level of abstraction, such asusing Motif (or its free version, lesstiff), GTK, QT and similar libraries.However, we need to start somewhere. More than this, knowing how things workdown below is never a bad idea.

After reading this tutorial, one would be able to write very simple graphicalprograms, but not programs with a descent user interface. For such programs,one of the previously mentioned libraries would be used.


The Client And Server Model Of The X Window System

The X window system was developed with one major goal - flexibility. The ideawas that the way things look is one thing, but the way things work is anothermatter. Thus, the lower levels provide the tools required to draw windows,handle user input, allow drawing graphics using colors (or black and whitescreens), etc. To this point, a decision was made to separate the system intotwo parts. A client that decides what to do, and a server that actually drawson the screen and reads user input in order to send it to the clientfor processing.

This model is the complete opposite of what one is used to when dealing withclients and servers. In our case, the user seats near the machine controlledby the server, while the client might be running on a remote machine. The servercontrols the screen, mouse and keyboard. A client may connect to the server,request that it draws a window (or several windows), and ask the server to sendit any input the user sends to these windows. Thus, several clients mayconnect to a single X server - one might be running an email software,one running a WWW browser, etc. When input it sent by the user to somewindow, the server sends a message to the client controlling this windowfor processing. The client decides what to do with this input, and sendsthe server requests for drawing in the window.

The whole session is carriedout using the X message protocol. This protocol was originally carriedover the TCP/IP protocol suite, allowing the client to run on any machineconnected to the same network that the server is. Later on the X servers wereextended to allow clients running on the local machine more optimized accessto the server (note that an X protocol message may be several hundreds ofKB in size), such as using shared memory, or using Unix domain sockets (amethod for creating a logical channel on a Unix system between two processes).


GUI programming - the Asynchronous Programming Model

Unlike conventional computer programs, that carry some serial nature,a GUI program usually uses an asynchronous programming model, also knownas "event-driven programming". This means that that program mostly sits idle,waiting for events sent by the X server, and then acts upon these events.An event may say "The user pressed the 1st button mouse in spot x,y", or"the window you control needs to be redrawn". In order for the program tobe responsive to the user input, as well as to refresh requests, it needs tohandle each event in a rather short period of time (e.g. less than 200milliseconds, as a rule of thumb).

This also implies that the program may notperform operations that might take a long time while handling an event(such as opening a network connection to some remote server, or connectingto a database server, or even performing a long file copy operation). Instead,it needs to perform all these operations in an asynchronous manner. This may bedone by using various asynchronous models to perform the longish operations,or by performing them in a different process or thread.

So the way a GUI program looks is something like that:

  1. Perform initialization routines.
  2. Connect to the X server.
  3. Perform X-related initialization.
  4. While not finished:
    1. Receive the next event from the X server.
    2. handle the event, possibly sending various drawing requests to the X server.
    3. If the event was a quit message, exit the loop.
  5. Close down the connection to the X server.
  6. Perform cleanup operations.

Basic Xlib Notions

In order to eliminate the needs of programs to actually implement the Xprotocol layer, a library called 'Xlib' was created. This library givesa program a very low-level access to any X server. Since the protocol isstandardized, A client using any implementation of Xlib may talk with anyX server. This might look trivial these days, but back at the days of usingcharacter mode terminals and proprietary methods of drawing graphicson screens, this looked like a major break-through. In fact, you'll noticethe big hype going around thin-clients, windows terminal servers, etc.They are implementing today what the X protocol enabled in the late 80's.On the other hand, the X universe is playing a catch-up game regardingCUA (common user access, a notion made by IBM to refer to the usage ofa common look and feel for all programs in order to ease the lives ofthe users). Not having a common look and feel was a philosophy of thecreators of the X window system. Obviously, it had some drawbacks thatare evident today.


The X Display

The major notion of using Xlib is the X display. This is a structurerepresenting the connection we have open with a given X server. It hidesa queue of messages coming from the server, and a queue of pending requests thatour client intends to send to the server. In Xlib, this structure is named'Display'. When we open a connection to an X server, the library returnsa pointer to such a structure. Later, we supply this pointer to any Xlibfunction that should send messages to the X server or receive messages fromthis server.


The GC - Graphics Context

When we perform various drawing operations (graphics, text, etc), we mayspecify various options for controlling how the data will be drawn - whatforeground and background colors to use, how line edges will be connected,what font to use when drawing some text, etc). In order to avoid the needto supply zillions of parameters to each drawing function, a graphicalcontext structure, of type 'GC' is used. We set the various drawing optionsin this structure, and then pass a pointer to this structure to any drawingroutines. This is rather handy, as we often needs to perform several drawingrequests with the same options. Thus, we would initialize a graphicalcontext, set the desired options, and pass this GC structure to alldrawing functions.


Object Handles

When various objects are created for us by the X server - such as windows,drawing areas and cursors - the relevant function returns a handle. Thisis some identifier for the object that actually resides in the X server'smemory - not in our application's memory. We can later manipulate thisobject by supplying this handle to various Xlib functions. The server keepsa mapping between these handles and the actual objects it manages. Xlibprovides various type definitions for these objects (Window, Cursor, Colormapand so on), which are all eventually mapped to simple integers. We shouldstill use these type names when defining variables that hold handles -for portability reasons.


Memory Allocation For Xlib Structures

Various structure types are used in Xlib's interface. Some of themare allocated directly by the user. Others are allocated using specific Xlibfunctions. This allows the library to initialize properly these structures.This is very handy, since these structures tend to contain a lot of variables,making it rather tedious for the poor programmer to initialize. Remember - Xlibtries to be as flexible as possible, and this means it is also as complex asit can get. Having default values will enable a beginner X programmerto use the library, without interfering with the ability of a more experiencedprogrammer to tweak with these zillions of options.

As for freeing memory, this is done in one of two ways. In cases where weallocated the memory - we free it in the same manner (i.e. usefree() to free memory allocated using malloc()).In case we used some Xlib function to allocate it, or we used some Xlib querymethod that returns dynamically allocated memory - we will use theXFree() function to free this memory block.

Events

A structure of type 'XEvent' is used to pass events received from the Xserver. Xlib supports a large amount of event types. The XEvent structurecontains the type of event received, as well as the data associated withthe event (e.g. position on the screen where the event was generated, mousebutton associated with the event, region of screen associated with a 'redraw'event, etc). The way to read the event's data depends on the event type.Thus, an XEvent structure contains a C language union of all possible eventtypes (if you're not sure what C unions are, it is time to check yourproffered C language manual...). Thus, we could have an XExpose event,an XButton event, an XMotion event, etc.


Compiling Xlib-Based Programs

Compiling Xlib-Based programs requires linking them with the Xlib library.This is done using a compilation command like this:


cc prog.c -o prog -lX11
If the compiler complains that it cannot find the X11 library, try addinga '-L' flag, like this:

cc prog.c -o prog -L/usr/X11/lib -lX11
or perhaps this (for a system with release 6 of X11):

cc prog.c -o prog -L/usr/X11R6/lib -lX11
On SunOs 4 systems, the X libraries are placed in /usr/openwin/lib:

cc prog.c -o prog -L/usr/openwin/lib -lX11
and so on...

Opening And Closing The Connection To An X Server

An X program first needs to open the connection to the X server. When we dothat, we need to specify the address of the host running the X server,as well as the display number. The X window system can support several displaysall connected to the same machine. However, usually there is only one suchdisplay, which is display number '0'. If we wanted to connect to the localdisplay (i.e. the display of the machine on which our client program runs),we could specify the display as ":0". To connect to the first display ofa machine whose address is "simey", we could use the address "simey:0".Here is how the connection is opened:



#include <X11/Xlib.h> /* defines common Xlib functions and structs. */
.
.
/* this variable will contain the pointer to the Display structure */
/* returned when opening a connection. */
Display* display;

/* open the connection to the display "simey:0". */
display = XOpenDisplay("simey:0");
if (display == NULL) {
fprintf(stderr, "Cannot connect to X server %sn", "simey:0");
exit (-1);
}

Note that is common for X programs to check if the environment variable'DISPLAY' is defined, and if it is, use its contents as the parameter to theXOpenDisplay() function.

When the program finished its business and needs to close the connectionthe X server, it does something like this:


XCloseDisplay(display);

This would cause all windows created by the program (if any are left) tobe automatically closed by the server, and any resources stored on theserver on behalf of the clients - to be freed. Note that this does notcause our client program to terminate - we could use the normalexit() function to do that.

Checking Basic Information About A Display

Once we opened a connection to an X server, we should check some basicinformation about it: what screens it has, what is the size (width and height)of the screen, how many colors it supports (black and white? grey scale?256 colors? more?), and so on. We will show a code snippet that makes fewof these checks, with comments explaining each function as it is being used.We assume that 'display' is a pointer to a 'Display' structure, as returned bya previous call to XOpenDisplay().



/* this variable will be used to store the "default" screen of the */
/* X server. usually an X server has only one screen, so we're only */
/* interested in that screen. */
int screen_num;

/* these variables will store the size of the screen, in pixels. */
int screen_width;
int screen_height;

/* this variable will be used to store the ID of the root window of our */
/* screen. Each screen always has a root window that covers the whole */
/* screen, and always exists. */
Window root_window;

/* these variables will be used to store the IDs of the black and white */
/* colors of the given screen. More on this will be explained later. */
unsigned long white_pixel;
unsigned long black_pixel;

/* check the number of the default screen for our X server. */
screen_num = DefaultScreen(display);

/* find the width of the default screen of our X server, in pixels. */
screen_width = DisplayWidth(display, screen_num);

/* find the height of the default screen of our X server, in pixels. */
screen_height = DisplayHeight(display, screen_num);

/* find the ID of the root window of the screen. */
root_window = RootWindow(display, screen_num);

/* find the value of a white pixel on this screen. */
white_pixel = WhitePixel(display, screen_num);

/* find the value of a black pixel on this screen. */
black_pixel = BlackPixel(display, screen_num);

There are various other macros to get more information about the screen,that you can find in any Xlib reference. There are also function equivalentsfor some of these macros (e.g. XWhitePixel, which does the same as WhitePixel).

Creating A Basic Window - Our "hello world" Program

After we got some basic information about our screen, we can get to creatingour first window. Xlib supplies several functions for creating new windows,one of which is XCreateSimpleWindow(). This function getsquite a few parameters determining the window's size, its position, and so on.Here is a complete list of these parameters:

Display* display
Pointer to the Display structure.
Window parent
The ID of an existing window that should be the parent of the new window.
int x
X Position of the top-left corner of the window (given as number of pixels from the left of the screen).
int y
Y Position of the top-left corner of the window (given as number of pixels from the top of the screen).
unsigned int width
Width of the new window, in pixels.
unsigned int height
Height of the new window, in pixels.
unsigned int border_width
Width of the window's border, in pixels.
unsigned long border
Color to be used to paint the window's border.
unsigned long background
Color to be used to paint the window's background.
Lets create a simple window, whose width is 1/3 of the screen's width,height is 1/3 of the screen's height, background color is white, bordercolor is black, and border width is 2 pixels. The window will be placedat the top-left corner of the screen.

/* this variable will store the ID of the newly created window. */
Window win;

/* these variables will store the window's width and height. */
int win_width;
int win_height;

/* these variables will store the window's location. */
int win_x;
int win_y;

/* calculate the window's width and height. */
win_width = DisplayWidth(display, screen_num) / 3;
win_height = DisplayHeight(display, screen_num) / 3;

/* position of the window is top-left corner - 0,0. */
win_x = win_y = 0;

/* create the window, as specified earlier. */
win = XCreateSimpleWindow(display,
RootWindow(display, screen_num),
win_x, win_y,
win_width, win_height,
win_border_width, BlackPixel(display, screen_num),
WhitePixel(display, screen_num));

The fact that we created the window does not mean it will be drawn on screen.By default, newly created windows are not mapped on the screen - they areinvisible. In order to make our window visible, we use theXMapWindow() function, as follows:

XMapWindow(display, win);
To see all the code we have gathered so far, take a look at thesimple-window.c program. You'll see two morefunction not explained so far - XFlush() and XSync().The XFlush() function flushes all pending requests to the Xserver - much like the fflush() function is used to flashstandard output. The XSync() function also flushes all pendingrequests to the X server, and then waits until the X server finishes processingthese requests. In a normal program this will not be necessary (you'll seewhy when we get to write a normal X program), but for now we put it there.Try compiling the program either with or without these function calls to seethe difference in its behavior.

Drawing In A Window

Drawing in a window can be done using various graphical functions - drawingpixels, lines, circles, rectangles, etc. In order to draw in a window,we first need to define various general drawing parameters - what line widthto use, which color to draw with, etc. This is done using a graphicalcontext (GC).


Allocating A Graphics Context (GC)

As we said, a graphical context defines several attributes to be usedwith the various drawing functions. For this, we define a graphicalcontext. We can use more than one graphical context with a single window,in order to draw in multiple styles (different colors, different linewidths, etc.). Allocating a new GC is done using the XCreateGC()function, as follows (in this code fragment, we assume "display" is a pointerto a Display structure, and "win" is the ID of a previously created window):



/* this variable will contain the handle to the returned graphics context. */
GC gc;

/* these variables are used to specify various attributes for the GC. */
/* initial values for the GC. */
XGCValues values = CapButt | JoinBevel;
/* which values in 'values' to check when creating the GC. */
unsigned long valuemask = GCCapStyle | GCJoinStyle;

/* create a new graphical context. */
gc = XCreateGC(display, win, valuemask, &values);
if (gc < 0) {
fprintf(stderr, "XCreateGC: n");
}

Note should be taken regarding the roles of "valuemask" and "values".Since a graphics context has zillions of attributes, and since often wedon't want to define few of them, we need to be able to tell theXCreateGC() which attributes we want to set. This is whatthe "valuemask" variable is for. We then use the "values" variable to specifyactual values for the attributes we defined in the "valuesmask". Thus, foreach constant used in "values", we'll use the matching constant in "valuesmask".In this case, we defined a graphics context with two attributes:
  1. When drawing a multiple-part line, the lines should be joined in a 'Bevelian' style.
  2. A line's end-point will be drawn straight (as opposed to ending the line in a round shape, if its width is more than 1 pixel wide).
The rest of the attributes of this GC will be set to their default values.

Once we created a graphics context, we can use it in drawing functions.We can also modify its parameters using various functions. Here area few examples:



/* change the foreground color of this GC to white. */
XSetForeground(display, gc, WhitePixel(display, screen_num));

/* change the background color of this GC to black. */
XSetBackground(display, gc, BlackPixel(display, screen_num));

/* change the fill style of this GC to 'solid'. */
XSetFillStyle(display, gc, FillSolid);

/* change the line drawing attributes of this GC to the given values. */
/* the parameters are: Display structure, GC, line width (in pixels), */
/* line drawing style, cap (line's end) drawing style, and lines */
/* join style. */
XSetLineAttributes(display, gc, 2, LineSolid, CapRound, JoinRound);

for complete information on the various attributes available in a graphicscontext, refer to the manual page of XCreateGC(). We will usejust a few simple attributes in our tutorial, to avoid over-complicating it.

Drawing Primitives - Point, Line, Box, Circle...

After we have created a GC, we can draw on a window using thisGC, with a set of Xlib functions, collectively called "drawing primitives".Without much fuss, lets see how they are used. We assume that "gc" is apreviously initialized GC, and that 'win' contains the handleof a previously created window.



/* draw a pixel at position '5,60' (line 5, column 60) of the given window. */
XDrawPoint(display, win, gc, 5, 5);

/* draw a line between point '20,20' and point '40,100' of the window. */
XDrawLine(display, win, gc, 20, 20, 40, 100);

/* draw an arc whose center is at position 'x,y', its width (if it was a */
/* full ellipse) is 'w', and height is 'h'. Start the arc at angle 'angle1' */
/* (angle 0 is the hour '3' on a clock, and positive numbers go */
/* counter-clockwise. the angles are in units of 1/64 of a degree (so 360*64 */
/* is 360 degrees). */
int x = 30, y = 40;
int h = 15, w = 45;
int angle1 = 0, angle2 = 2.109;
XDrawArc(display, win, gc, x-(w/2), y-(h/2), w, h, angle1, angle2);

/* now use the XDrawArc() function to draw a circle whose diameter */
/* is 15 pixels, and whose center is at location '50,100'. */
XDrawArc(display, win, gc, 50-(15/2), 100-(15/2), 15, 15, 0, 360*64);

/* the XDrawLines() function draws a set of consecutive lines, whose */
/* edges are given in an array of XPoint structures. */
/* The following block will draw a triangle. We use a block here, since */
/* the C language allows defining new variables only in the beginning of */
/* a block. */
{
/* this array contains the pixels to be used as the line's end-points. */
XPoint points[] = {
{0, 0},
{15, 15},
{0, 15},
{0, 0}
};
/* and this is the number of pixels in the array. The number of drawn */
/* lines will be 'npoints - 1'. */
int npoints = sizeof(points)/sizeof(XPoint);

/* draw a small triangle at the top-left corner of the window. */
/* the triangle is made of a set of consecutive lines, whose */
/* end-point pixels are specified in the 'points' array. */
XDrawLines(display, win, gc, points, npoints, CoordModeOrigin);
}

/* draw a rectangle whose top-left corner is at '120,150', its width is */
/* 50 pixels, and height is 60 pixels. */
XDrawRectangle(display, win, gc, 120, 150, 50, 60);

/* draw a filled rectangle of the same size as above, to the left of the */
/* previous rectangle. note that this rectangle is one pixel smaller than */
/* the previous line, since 'XFillRectangle()' assumes it is filling up */
/* an already drawn rectangle. This may be used to draw a rectangle using */
/* one color, and later to fill it using another color. */
XFillRectangle(display, win, gc, 60, 150, 50, 60);


Hopefully, you got the point by now. We will mention a few more functionsthat may be used in a similar fashion. For example, XFillArc()takes the same parameters as XDrawArc(), but draws only theinside of this arc (like XFillRectangle() does to a rectangledrawn using the XDrawRectangle() function). There is also anXFillPolygon() function that fills the inside of a polygon.It takes almost the same parameters as XDrawLines(). However,if the last point in the array has a different location than the first pointin the array, the XFillPolygon() function automatically addsanother "virtual" lines, connecting these two points. Another differencebetween the two functions, is that XFillPolygon() takesan additional parameters, shape, that is used to help the X server optimizeits operation. You can read about it in your manual pages. There are alsoplural versions for these functions, namely XFillArcs()and XFillRectangles().

The source code for a program doing these drawings is found in thefile simple-drawing.c.

X Events

In an Xlib program, everything is driven by events. Event painting on thescreen is sometimes done as a response to an event - an "expose" event. Ifpart of a program's window that was hidden, gets exposed (e.g. the windowwas raised above other windows), the X server will send an "expose" event tolet the program know it should repaint that part of the window. User input(key presses, mouse movement, etc.) is also received as a set of events.


Registering For Event Types Using Event Masks

After a program creates a window (or several windows), it should tell the Xserver what types of events it wishes to receive for this window. By default,no events are sent to the program. It may register for various mouse (alsocalled "pointer") events, keyboard events, expose events and so on. Thisis done for optimizing the server-to-client connection (i.e. why senda program (that might even be running at the other side of the globe) anevent it is not interested in?).

In Xlib, we use the XSelectInput() function to register forevents. This function accepts 3 parameters - the display structure, an IDof a window, and a mask of the event types it wishes to get. The window IDparameter allows us to register for receiving different types of events fordifferent windows. Here is how we register for "expose" events for a windowwhose ID is 'win':


XSelectInput(display, win, ExposureMask);
ExposureMask is a constant defined in the "X.h" header file.If we wanted to register to several event types, we can logically "or"them, as follows:

XSelectInput(display, win, ExposureMask | ButtonPressMask);
This registers for "expose" events as well as for mouse button presses insidethe given window. You should note that a mask may represent several eventsub-types.

Note: A common bug programmers do is adding code to handle new event types intheir program, while forgetting to add the masks for these events in thecall to XSelectInput(). Such a programmer then could sit downfor hours debugging his program, wondering "why doesn't my program noticethat i released the button??", only to find that they registered for buttonpress events, but not for button release events.


Receiving Events - Writing The Events Loop

After we have registered for the event types we are interested in, we need toenter a loop of receiving events and handling them. There are various waysto write such a loop, but the basic loop looks like this:



/* this structure will contain the event's data, once received. */
XEvent an_event;

/* enter an "endless" loop of handling events. */
while (1) {
XNextEvent(display, &an_event);
switch (an_event.type) {
case Expose:
/* handle this event type... */
.
.
break;
default: /* unknown event type - ignore it. */
break;
}
}

The XNextEvent() function fetches the next event coming fromthe X server. If no event is waiting, it blocks until one is received. Whenit returns, the event's data is placed in the XEvent variablegiven to the function as the second parameter. After that, the "type" field ofthis variable specifies what type of event we got. Expose isthe event type that tells us there is a part of the window that needs to beredrawn. After we handle this event, we go back and wait for the nextevent to process. Obviously, we will need to give the user some way ofterminating the program. This is usually done by handling a special "quit"event, as we'll soon see.

Expose Events

The "expose" event is one of the most basic events an application mayreceive. It will be sent to us in one of several cases:

  • A window that covered part of our window has moved away, exposing part (or all) of our window.
  • Our window was raised above other windows.
  • Our window mapped for the first time.
  • Our window was de-iconified.
You should note the implicit assumption hidden here - the contents of our windowis lost when it is being obscured (covered) by other windows. One may wonderwhy the X server does not save this contents. The answer is - to save memory.After all, the number of windows on a display at a given time may be verylarge, and storing the contents of all of them might require a lot ofmemory (for instance, a 256 color bitmap covering 400 pixels by 400 pixelstakes 160KB of memory to store. Now think about 20 windows, some much largerthan this size). Actually, there is a way to tell the X server to store thecontents of a window in special cases, as we will see later.

When we get an "expose" event, we should take the event's data from the"xexpose" member of the XEvent structure (in our codeexample we refer to it as "an_event.xexpose"). It contains severalinteresting fields:

count
Number of other expose events waiting in the server's events queue. This may be useful if we got several expose events in a row - we will usually avoid redrawing the window until we get the last of them (i.e. until count is 0).
Window window
The ID of the window this expose event was sent for (in case our application registered for events on several windows).
int x, y
The x and y coordinates (in pixels) from the top-left of the window, of the window's region that needs to be redrawn.
int width, height
The width and height (in pixels) of the window's region that needs to be redraw.
In our demo programs, we will tend to ignore the region supplied, and simplyre-draw all the window. However, this is very inefficient, and we will tryto demonstrate some techniques for drawing only the relevant section ofscreen later on.

As an example, here is how we will draw a line across our window, wheneverwe receive "expose" events. Assume this 'case' is part of the event loop'sswitch command.



case Expose:
/* if we have several other expose events waiting, don't redraw. */
/* we will do the redrawing when we receive the last of them. */
if (an_event.xexpose.count > 0)
break;
/* ok, now draw the line... */
XDrawLine(display, win, gc, 0, 100, 400, 100);
break;


Getting User Input

User input traditionally comes from two sources - the mouse and the keyboard.Various event types exist to notify us of user input - a key being pressed onthe keyboard, a key being released on the keyboard, the mouse moving over ourwindow, the mouse entering (or leaving) our window and so on.


Mouse Button Click And Release Events

The first event type we'll deal with is a mouse button-press (or buttonrelease) event in our window. In order to register to such an event type, wewould add one (or more) of the following masks to the event types we specifyfor the XSelectInput() function:

ButtonPressMask
Notify us of any button that was pressed in one of our windows.
ButtonReleaseMask
Notify us of any button that was released over one of our windows.

The event types to be checked for in our event-loop switch, are anyof the following:

ButtonPress
A button was pressed over one of our windows.
ButtonRelease
A button was released over one of our windows.

The event structure for these event types is accessed as "an_event.xbutton",and contains the following interesting fields:

Window window
The ID of the window this button event was sent for (in case our application registered for events on several windows).
int x, y
The x and y coordinates (in pixels) from the top-left of the window, of the mouse pointer, during the click.
int button
The number of mouse button that was clicked. May be a value such as Button1, Button2, Button3.
Time time
time (in millisecond) the event took place in. May be used to calculate "double-click" situations by an application (e.g. if the mouse button was clicked two times in a duration shorter than a given amount, assume this was a double-click).

As an example, here is how we will draw a black pixel at the mouse position,whenever we receive "button press" events, with the 1st mouse button, and erasethat pixel (i.e. draw a white pixel) when the 2nd mouse button is pressed.We assume the existence of two GCs, gc_draw with foreground color set toblack, and gc_erase, with foreground color set to white.
Assume that the following 'case' is part of the event loop'sswitch command.



case ButtonPress:
/* store the mouse button coordinates in 'int' variables. */
/* also store the ID of the window on which the mouse was */
/* pressed. */
x = an_event.xbutton.x;
y = an_event.xbutton.y;
the_win = an_event.xbutton.window;

/* check which mouse button was pressed, and act accordingly. */
switch (an_event.xbutton.button) {
case Button1:
/* draw a pixel at the mouse position. */
XDrawPoint(display, the_win, gc_draw, x, y);
break;
case Button2:
/* erase a pixel at the mouse position. */
XDrawPoint(display, the_win, gc_erase, x, y);
break;
default: /* probably 3rd button - just ignore this event. */
break;
}
break;


Mouse Movement Events

Similar to mouse button press and release events, we also can be notifiedof various mouse movement events. These can be split into two families.One is of mouse pointer movement while no buttons are pressed, and thesecond is a mouse pointer motion while one (or more) of the buttons arepressed (this is sometimes called "a mouse drag operation", or just"dragging"). The following event masks may be added in the call toXSelectInput() for our application to be notified of suchevents:

PointerMotionMask
Events of the pointer moving in one of the windows controlled by our application, while no mouse button is held pressed.
ButtonMotionMask
Events of the pointer moving while one (or more) of the mouse buttons is held pressed.
Button1MotionMask
Same as ButtonMotionMask, but only when the 1st mouse button is held pressed.
Button2MotionMask, Button3MotionMask, Button4MotionMask, Button5MotionMask
Likewise, for 2nd mouse button, or 3rd, 4th or 5th.

The event types to be checked for in our event-loop switch, are anyof the following:

MotionNotify
The mouse pointer moved in one of the windows for which we requested to be notified of such events.

The event structure for these event types is accessed as "an_event.xbutton",and contains the following interesting fields:

Window window
The ID of the window this mouse motion event was sent for (in case our application registered for events on several windows).
int x, y
The x and y coordinates (in pixels) from the top-left of the window, of the mouse pointer, when the event was generated.
unsigned int state
A mask of the buttons (or keys) held down during this event - if any. This field is a bitwise OR of any of the following:
  • Button1Mask
  • Button2Mask
  • Button3Mask

    原文转自:http://www.ltesting.net