Site hosted by Angelfire.com: Build your free website today!

Hands-on Projects for the Linux Graphics Subsystem

New book from Christos Karayiannis

Available in Amazon Kindle format at:

amazon.com   amazon.co.uk   amazon.de   amazon.es   amazon.fr   amazon.it

X Replies

In the previous sections we discussed the X Client-Server handshake and the X Protocol requests, the client sends to the X Server. In the current section we have a closer look to the X Server replies to the client requests. As illustrated in the following figure from X Window System core protocol Wikipedia article the X client sends requests to the X server and receives replies. Replies as we see next in the current section, where we discuss the xReply struct, include also error replies and events, that are discussed in section 6.

As we read from the "X Programming Manual" 'A protocol reply is send from the server to Xlib in response to certain requests. Not all requests are answered by replies - only the ones that request information'.

Consider for instance the previous X client program, we used as example in the previous sections:


  /*
   Simple Xlib application drawing a box in a window.
   To Compile: gcc -O2 -Wall -o test test.c -L /usr/X11R6/lib -lX11 -lm
 */
 
 #include<X11/Xlib.h>
 #include<stdio.h>
 #include<stdlib.h> // prevents error for exit on line 18 when compiling with gcc

 int main() {
   Display *d;
   int s;
   Window w;
   XEvent e;
 
                        /* open connection with the server */
   d=XOpenDisplay(NULL);
   if(d==NULL) {
     printf("Cannot open display\n");
     exit(1);
   }
   s=DefaultScreen(d);
 
                        /* create window */
   w=XCreateSimpleWindow(d, RootWindow(d, s), 10, 10, 100, 100, 1,
                         BlackPixel(d, s), WhitePixel(d, s));
 
   // Prosses Window Close Event through event handler so XNextEvent does Not fail
        Atom delWindow = XInternAtom( d, "WM_DELETE_WINDOW", 0 );
        XSetWMProtocols(d , w, &delWindow, 1);
 
                        /* select kind of events we are interested in */
   XSelectInput(d, w, ExposureMask | KeyPressMask);
 
                        /* map (show) the window */
   XMapWindow(d, w);
 
                        /* event loop */
   while(1) {
     XNextEvent(d, &e);
                        /* draw or redraw the window */
     if(e.type==Expose) {
       XFillRectangle(d, w, DefaultGC(d, s), 20, 20, 10, 10);
     }
                        /* exit on key press */
     if(e.type==KeyPress)
       break;
 
     // Handle Windows Close Event
     if(e.type==ClientMessage)
        break;
   }
 
                        /* destroy our window */
   XDestroyWindow(d, w);
 
                        /* close connection to server */
   XCloseDisplay(d);
 
   return 0;
 }

To find which client requests require a reply from the X Server we use the xtrace utility. For more info see section 2.2.

We start xtrace from xterm to monitor our system X activity as:

xtrace -D:9 -d:0 >loglog
Then we run the test program in a second xterm as:
DISPLAY=:9.0 ./test 
We enter Ctrl+C at the first xterm to stop xtrace and we read the data collected by xtrace so far:
more loglog
At the last lines of the xtrace log file we find requests, for instance MapWindow, that does not require a reply and the InternAtom request, that requires and receives a reply:

As we saw in section 2.3 the InternAtom request is prepared by the XInternAtom() Xlib routine, which is called by the X Client program as:

        Atom delWindow = XInternAtom( d, "WM_DELETE_WINDOW", 0 );

XInternAtom() is implemented as:

Atom
XInternAtom (
    Display *dpy,
    const char *name,
    Bool onlyIfExists)
{
    Atom atom;
    unsigned long sig;
    int idx, n;
    xInternAtomReply rep;

    if (!name)
	name = "";
    LockDisplay(dpy);
    if ((atom = _XInternAtom(dpy, name, onlyIfExists, &sig, &idx, &n))) {
	UnlockDisplay(dpy);
	return atom;
    }
    if (dpy->atoms && dpy->atoms->table[idx] == RESERVED)
	dpy->atoms->table[idx] = NULL; /* unreserve slot */
    if (_XReply (dpy, (xReply *)&rep, 0, xTrue)) {
	if ((atom = rep.atom))
	    _XUpdateAtomCache(dpy, name, atom, sig, idx, n);
    }
    UnlockDisplay(dpy);
    SyncHandle();
    return (rep.atom);
} 

There are therefore two steps during the request:

Step 1: the Request

XInternAtom() calls _XInternAtom() to send the request. The latter uses macro GetReq to prepare the X_InternAtom request and macro Data to place it at the 'buffer', the output buffer for the current display:

...
    GetReq(InternAtom, req);
    req->nbytes = n;
    req->onlyIfExists = onlyIfExists;
    req->length += (n+3)>>2;
    Data(dpy, name, n);
...

GetReq is defined as:

#if !defined(UNIXCPP) || defined(ANSICPP)
#define GetReq(name, req) \
        WORD64ALIGN\
	if ((dpy->bufptr + SIZEOF(x##name##Req)) > dpy->bufmax)\
		_XFlush(dpy);\
	req = (x##name##Req *)(dpy->last_req = dpy->bufptr);\
	req->reqType = X_##name;\
	req->length = (SIZEOF(x##name##Req))>>2;\
	dpy->bufptr += SIZEOF(x##name##Req);\
	dpy->request++
. . . 

X_InternAtom becomes the request type since the first argument passed to GetReq is InternAtom. It is defined in Xproto.h as:

#define X_InternAtom                   16

xInternAtomReq, the request struct is defined in Xproto.h as:

typedef struct {    /* followed by padded string */
    CARD8 reqType;
    BOOL onlyIfExists;
    CARD16 length B16;
    CARD16 nbytes  B16;    /* number of bytes in string */
    CARD16 pad B16;
} xInternAtomReq;

Macro Data is defined as:

/*
 * Data - Place data in the buffer and pad the end to provide
 * 32 bit word alignment.  Transmit if the buffer fills.
 *
 * "dpy" is a pointer to a Display.
 * "data" is a pinter to a data buffer.
 * "len" is the length of the data buffer.
 */
#ifndef DataRoutineIsProcedure
#define Data(dpy, data, len) {\
	if (dpy->bufptr + (len) <= dpy->bufmax) {\
		memcpy(dpy->bufptr, data, (int)len);\
		dpy->bufptr += ((len) + 3) & ~3;\
	} else\
		_XSend(dpy, data, len);\
	}
#endif /* DataRoutineIsProcedure */

As we read from the comment Data places the request at 'buffer', at position indicated by bufptr (see section 1.3 ), if it finds adequate space between bufptr and bufmax (the max location buffer can possibly exceed). In that case the request is buffered to be sent with other requests. Otherwise, if no adequate space remains, Data use _XSend() to send instantly the data. As we read from the routine's comment:

 * _XSend - Flush the buffer and send the client data. 

Also from 'How Xlib is Implemented' text we read:

The XSend function uses the writev system call to write both the buffer and the extra data in one system call, without further copying.

Step 2: the Reply

XInternAtom() calls then _XReply() to handle the reply to the InternAtom request. As we read from the Xlib Reference:

"If the protocol request has a reply, then call _XReply after you have finished dealing with all the fixed and variable length arguments. _XReply flushes the output buffer and waits for an xReply packet to arrive. If any events arrive in the meantime, _XReply places them in the queue for later use."

_XReply() is implemented in XlibInt.c. It has the prototype:

/*
 * _XReply - Wait for a reply packet and copy its contents into the
 * specified rep.  Meanwhile we must handle error and event packets that
 * we may encounter.
 */
Status
_XReply (
    register Display *dpy,
    register xReply *rep,
    int extra,		/* number of 32-bit words expected after the reply */
    Bool discard)	/* should I discard data following "extra" words? */

rep is a pointer to xReply, which is defined in Xproto.h as:

/* XReply is the union of all the replies above whose "fixed part"
fits in 32 bytes.  It does NOT include GetWindowAttributesReply,
QueryFontReply, QueryKeymapReply, or GetKeyboardControlReply 
ListFontsWithInfoReply */

typedef union {
    xGenericReply generic;
    xGetGeometryReply geom;
    xQueryTreeReply tree;
    xInternAtomReply atom;
    xGetAtomNameReply atomName;
    xGetPropertyReply property;
    xListPropertiesReply listProperties;
    xGetSelectionOwnerReply selection;
    xGrabPointerReply grabPointer;
    xGrabKeyboardReply grabKeyboard;
    xQueryPointerReply pointer;
    xGetMotionEventsReply motionEvents;
    xTranslateCoordsReply coords;
    xGetInputFocusReply inputFocus;
    xQueryTextExtentsReply textExtents;
    xListFontsReply fonts;
    xGetFontPathReply fontPath;
    xGetImageReply image;
    xListInstalledColormapsReply colormaps;
    xAllocColorReply allocColor;
    xAllocNamedColorReply allocNamedColor;
    xAllocColorCellsReply colorCells;
    xAllocColorPlanesReply colorPlanes;
    xQueryColorsReply colors;
    xLookupColorReply lookupColor;
    xQueryBestSizeReply bestSize;
    xQueryExtensionReply extension;
    xListExtensionsReply extensions;
    xSetModifierMappingReply setModifierMapping;
    xGetModifierMappingReply getModifierMapping;
    xSetPointerMappingReply setPointerMapping;
    xGetKeyboardMappingReply getKeyboardMapping;
    xGetPointerMappingReply getPointerMapping;
    xGetPointerControlReply pointerControl;
    xGetScreenSaverReply screenSaver;
    xListHostsReply hosts;
    xError error;
    xEvent event;
} xReply;

As we see from the xReply definition the error and the event are treated as replies from the X Server to the client. We also highlight the xInternAtomReply, which is the reply that XInternAtom() asks for, since in this routine rep is of type xInternAtomReply. The latter is defined in Xproto.h as:

typedef struct {
    BYTE type;  /* X_Reply */
    BYTE pad1;
    CARD16 sequenceNumber B16;
    CARD32 length B32; /* 0 */
    Atom atom B32;
    CARD32 pad2 B32;
    CARD32 pad3 B32;
    CARD32 pad4 B32;
    CARD32 pad5 B32;
    CARD32 pad6 B32;
    } xInternAtomReply;

In xReply highlight also the xGenericReply, which is used to demultiplex the various reply types. As we see the size of most of the reply types, including error and event, is 32 bytes. Some other replies have extra bytes. We also see a lot of padding that is required for the efficient placed of the struct along with other request structs in memory, in the case of 64-bit (and certainly 32-bit) systems.

_XReply() commentary

_XReply() is implemented in XlibInt.c as:

/*
 * _XReply - Wait for a reply packet and copy its contents into the
 * specified rep.  Meanwhile we must handle error and event packets that
 * we may encounter.
 */
Status
_XReply (
    register Display *dpy,
    register xReply *rep,
    int extra,		/* number of 32-bit words expected after the reply */
    Bool discard)	/* should I discard data following "extra" words? */
{
    /* Pull out the serial number now, so that (currently illegal) requests
     * generated by an error handler don't confuse us.
     */
    unsigned long cur_request = dpy->request;

. . .

    _XFlush(dpy);

. . .

   for (;;) {

. . .
	    (void) _XRead(dpy, (char *)rep, (long)SIZEOF(xReply));

. . .

	switch ((int)rep->generic.type) {

	    case X_Reply:
	        /* Reply received.  Fast update for synchronous replies,
		 * but deal with multiple outstanding replies.
		 */
	        if (rep->generic.sequenceNumber == (cur_request & 0xffff))
		    dpy->last_request_read = cur_request;
		else {
		    int pend = SIZEOF(xReply);
		    if (_XAsyncReply(dpy, rep, (char *)rep, &pend, False)
			!= (char *)rep)
			continue;
		}
		if (extra <= rep->generic.length) {
. . .
		    return 1;
		}

. . .

    	    case X_Error:
	    	{
. . .
		} /* case X_Error */
		break;

. . .

	    default:
		_XEnq(dpy, (xEvent *) rep);
. . .
		break;
	    }
	}
} 

_XRead() is used to place the 32 bytes that SIZEOF(xReply) indicates to rep.

As we mentioned previously the generic type of the reply is used to demultiplex the reply. The reply is treated initially as generic and then the generic.type is examined. Three cases are examined according to the type of the generic reply type: reply, error and event (the default in the switch statement). X_Reply and X_Error are defined in Xproto.h as:

/* Reply codes */

#define X_Reply		1		/* Normal reply */
#define X_Error		0		/* Error */

In the case of X_Reply first the code looks for a 'synchronous reply', a reply for the current request. In that case the sequenceNumber field of the reply is compared with the last 16 bits (logical AND operation with 0xffff) of cur_request, which previously took the value of the request field of the display. This field as we see in the Display struct (see section 1.5) is the 'sequence number of last request'. If the reply number matches the last request number it is a reply to the last request, a 'synchronous' reply.

	        if (rep->generic.sequenceNumber == (cur_request & 0xffff))
		    dpy->last_request_read = cur_request;

In that case the reply, which is placed at the memory location pointed by rep is further processed by the routine that called _XReply(), in our case XInternAtom():

    if (_XReply (dpy, (xReply *)&rep, 0, xTrue)) {
	if ((atom = rep.atom))
	    _XUpdateAtomCache(dpy, name, atom, sig, idx, n);
    }

If the reply is not 'synchronous' and it is a reply to an older request the _XAsyncReply() is called to handle the reply.

As we read from the XSCOPE paper:

Of the 120 different X11 requests, only 40 generate replies. Since the communication between client and server is asynchronous, it is possible for the client to make several reply-generating requests without waiting for the replies.

To identify the reply packets (or the cause of any error packets), the client must record the sequence number of each request (request 1 is the first request after the initial setup communication). Each reply (or error) contains the sequence number of the request which caused it.

XInternAtom() request, does not register an asynchronous request handler. Other requests, for instance XGetWindowAttributes() do register a request to the current Display, opened by the X Client. Consider the following code from XGetWindowAttributes():

    async.next = dpy->async_handlers;
    async.handler = _XWAttrsHandler;
    async.data = (XPointer)&async_state;
    dpy->async_handlers = &async;

With the previous part of code _XWAttrsHandler() is placed at the list of asynchronous handler of the current X Client's Display.

On success, in both cases synchronous and asynchronous, TRUE is returned to the caller of _XReply():

		    return 1;

In our case the caller was XInternAtom() that called _XReply() as:

    if (_XReply (dpy, (xReply *)&rep, 0, xTrue)) {

If 1 (TRUE) is returned the previously 'if' condition in XInternAtom() holds true.

Errors are also handled by _XReply in the case of X_Error type.

The default case of the switch instruction is dedicated to the events. In that case _XEnq() is called to "Place event packets on the display's queue" as we read from this routine's comment.

REFERENCES:
How Xlib is Implemented
Xlib Reference Manual