Chapter 15. Other Programming Techniques

As its title implies, this chapter discusses a few orphaned techniques that didn't quite fit in anywhere else. This chapter is important if you want to use one of these techniques, but most readers may just want to skim it.

This chapter covers a few obscure but occasionally necessary programming techniques. The routines and techniques described here will not be needed in most programs.

The end of the chapter contains information about porting and portability.

Reading and Writing Properties

Chapter 12, “Interclient Communication” described many of the usual properties used in communication with the window manager and other clients. Xlib provides convenience routines for reading and writing these properties. But if you establish any other private protocols between two of your applications or between your application and a proprietary window manager, you will need to write your own routines to read and write properties. Example 15-1 is the code for XFetchName() that shows how to read a property containing a string.

Example 15-1. Reading a property

#include "Xatom.h"
Status XFetchName (dpy, w, name)
    register Display *dpy;
    Window w;
    char **name;
{
    Atom actual_type;
    int actual_format;
    unsigned long nitems;
    unsigned long leftover;
    unsigned char *data = NULL;
    if (XGetWindowProperty(dpy, w, XA_WM_NAME, 0L, (long)BUFSIZ,
            False, XA_STRING, &actual_type, &actual_format,
            &nitems, &leftover, &data) != Success) {
        *name = NULL;
        return (0);
    }
    if ( (actual_type == XA_STRING) &&  (actual_format == 8) ) {
        /* The data returned by XGetWindowProperty is guaranteed
         * to contain one extra byte that is null terminated to
         * make retrieving string properties easy */
        *name = (char *)data;
        return(1);
        }
    if (data) Xfree ((char *)data);
    *name = NULL;
    return(0);
}


Using XChangeProperty() is easier than reading a property with XGetWindowProperty(), since there are many fewer arguments. Example 15-2 shows the companion function to XFetchName(), XStoreName().

Example 15-2. Writing a property

XStoreName (dpy, w, name)
    register Display *dpy;
    Window w;
    char *name;
{
    XChangeProperty(dpy, w, XA_WM_NAME, XA_STRING,
            8, PropModeReplace, (unsigned char *)name,
            name ? strlen(name) : 0);
}


Screen Saver

Screen saver routines are provided to control the blanking of the screen when it has been idle for a time. XSetScreenSaver() sets the operation of the screen saver, including:

  • How long the display remains idle before it is blanked.

  • The time between random pattern motions.

  • Whether the application prefers screen blanking or not (regardless of whether the screen is capable of it).

  • Whether exposures are generated when the screen is restored.

XActivateScreenSaver() and XResetScreenSaver() turn the screen saver on and off, respectively, and XForceScreenSaver() can turn it on or off according to a flag. XGetScreenSaver() returns the current settings of the screen saver.

At this writing, the X Consortium is working on an X extension that will support more advanced screen saving abilities.

Host Access and Security

Once an application successfully connects to a server, X does not provide any protection from unauthorized access to individual windows, pixmaps, or other resources. If a program succeeds in connecting with a server and finds out a resource ID, it can manipulate or even destroy the resource.

There are several kinds of security that can prevent connections from being made by clients running on other machines. First, to provide a minimal level of protection, connections are only permitted from machines which are listed on a host access list. This is adequate on single-user workstations but obviously breaks down on machines running more than one server.

In X11R4, per-user control was added with the MIT-MAGIC-COOKIE-1 MIT-MAGIC-COOKIE-1 is not too secure, however, because it passes its secret key (“cookie”) between client and server without encryption.

X11R5 defines, and the MIT release implements, two new mechanisms that can be used for secure access control. XDM-AUTHORIZATION-1 is similar to MIT-MAGIC-COOKIE-1, but uses DES (Data Encryption Standard) encryption to encrypt the authorization data that is passed between client and server. To compile this authorization scheme, you need an implementation of DES in the file mit/lib/Xdmcp/Wraphelp.c. Due to U.S. export regulations, this file may not appear in your distribution. If you do not plan to export the file outside of the U.S., you may legally obtain it over the network from the X Consortium. Ftp to the host export.lcs.mit.edu and see the file pub/R5/xdm-auth/README. Outside the U.S. you may be able to obtain a compatible version of this file from the directory /pub/X11R5 on the machine ftp.psy.uq.oz.au (130.102.32.1). If you have this file, but this security mechanism is not automatically built on your system, you can add the following line to the file mit/config/site.def before building X11R5:

#define HasXdmAuth YES

The other R5 authorization mechanism is named SUN-DES-1, and is based on the public key Sun Secure RPC system included with recent version of SunOS. If your system provides this secure RPC system, then the .cf file for your system in mit/config should define the variable HasSecureRPC, which will cause this security mechanism to be automatically built. The forthcoming (late 1992) X Window System Administrator's Guide from O'Reilly & Associates explains the issues of X security and these X11R5 security mechanisms in detail.

The Host Access List

The initial access control list is read at startup and reset time. The initial set of hosts allowed to open connections consists of:

  • The host the window system is running on.

  • On UNIX-based systems, each host listed in the /etc/X?.hosts file, where ? indicates the number of the server (the number between : and . in the display_name argument to XOpenDisplay() that would connect to the server). This file should consist of host names separated by newlines. DECnet nodes must terminate in “::” to distinguish them from Internet hosts.

If a host is not in the access control list when the access control mechanism is enabled and the host attempts to establish a connection, the server refuses the connection.

You can add, get, or remove hosts with XAddHost(), XAddHosts(), XListHosts(), XRemoveHost(), and XRemoveHosts(). All the host access control functions use the XHostAddress structure. The members in this structure are:

family 

Specifies which address family to use (for example, TCP/IP or DECnet). The family symbols FamilyInternet, FamilyDECnet, and FamilyChaos are defined in <X11/X.h>.

length 

Specifies the length of the address in bytes.

address 

Specifies a pointer to the address.

Example 15-3. The XHostAddress structure

typedef struct {
   int family;      /* For example FamilyInternet */
   int length;      /* Length of address, in bytes */
   char *address;   /* Pointer to where to find the bytes */
} XHostAddress;


For these functions to execute successfully, the client application must run on the same host as the X server and must have permission in the initial authorization at connection setup before calling these functions.

Enabling and Disabling Access Control

Normally the access control list determines whether a client succeeds in connecting to the server. Sometimes it is more convenient (though less safe) to allow a client or any host to have access. In this case, a client running on the same host as the server can call XDisableAccessControl(). Thereafter, the host access list will no longer be used to filter connection requests. To reset the server to its default condition with access control, use XEnableAccessControl(). XSetAccessControl() performs either of these functions according to a flag.

Getting the Window Hierarchy

XQueryTree() lets you get the IDs of the windows in any portion of the window hierarchy with a single call. This is the only way to find out the IDs of windows created by other clients. XQueryTree() gets the root window ID, the parent window ID, and the list of child window IDs given a window. It also returns the number of children.

One possible use of XQueryTree() is to find out whether your application's top-level window has been reparented by the window manager, and it returns the ID of the new parent.

Close Down Mode

Normally all resources associated with a client will be destroyed when the connection between the client and the server closes. This can happen without prior warning to either the server or the client when, for example, the network cable is accidentally pulled out of one of the machines or the machine running the server crashes. Therefore, robust applications need a way of recovering from that occurrence. XSetCloseDownMode() helps implement one method of recovery.

Clients in the default DestroyAll close down mode will have all their resources killed when the connection to the server dies. XSetCloseDownMode() can set two other modes, RetainPermanent and RetainTemporary, which allow client resources to live on for a time. A client may want its resources to live on to assist in the process of recovering from a broken connection with the server, usually caused by a network failure. When next run after the problem has been corrected, the application could somehow determine which resources were its own and continue operating where it left off. The “somehow” is the crux of the problem. The only way we can think of to allow the client to find out the IDs of its resources after the client is resurrected is for the client to save all the resource IDs in a file (or perhaps in a property, but this would not survive a server crash) immediately after they are created. Then upon startup, it can read this information and see if the specified resources still exist. If they do, it can skip creating them.

A dying connection between the server and client raises other problems, too. Even if a client's resources are put on life support, there is no longer any “brain” behind them. The user's instructions will go unanswered, and there will be no visible warning on the screen that the client is no longer connected. The window manager or some other program, if running on the same machine as the server, could conceivably detect this situation and print a message. However, this kind of functionality in a window manager has not been demonstrated up to now. Otherwise, the user can only be warned that the connection could die and that this would cause the window to freeze (if the client's resources were preserved; the window would disappear if the close down mode had not been set). The user could then restart the client from an xterm window to reactivate the window.

XKillClient() can kill resources that remain alive after the connection closes. It can kill resources associated with a single client by specifying any resource ID associated with that client, or it can kill all resources of all clients that terminated with mode RetainTemporary if given the argument AllTemporary. XKillClient() might be used by the window manager or conceivably by a separate client to save space in the server by cleaning up resources after clients die that have requested that their resources be kept alive. This should not be done unless the user agrees with it, because it could upset an application's attempt to recover from a broken connection with the server.

Connection Close Operations

When the connection between the X server and a client is closed, either by a call to XCloseDisplay() or by an exiting process, the X server performs these automatic operations:

  • Disowns all selections made by the program.

  • Releases all passive grabs made by the program.

  • Performs an XUngrabPointer() and XUngrabKeyboard() if the client application had actively grabbed the pointer or the keyboard.

  • Performs an XUngrabServer() if the client had grabbed the server.

  • Marks all resources (including colormap entries) allocated by the client application as permanent or temporary, according to whether the close down mode is RetainPermanent or RetainTemporary (see “Close Down Mode”).

The X server performs these operations when the close down mode is DestroyAll:

  • The save-set is a list of other client's windows, referred to as save-set windows (see “Window Save-set” in Chapter 16 for a complete description of save-sets). If any window in the client's save-set is an inferior of a window created by the client, the X server reparents the save-set window to the closest ancestor so that the save-set window is not an inferior of a window created by the client.

  • Performs an XMapWindow() request on the save-set window if the save-set window is unmapped. The X server does this even if the save-set window was not an inferior of a window created by the client.

  • Destroys all windows created by the client, after examining each window in the client's save-set.

  • Performs the appropriate free request on all nonwindow resources (Bitmap, Colormap, Cursor, Font, GC, and Pixmap) created by the client.

Additional processing occurs when the last connection to the X server closes with close down mode DestroyAll. The X server:

  • Resets its state, as if it had just been started. The X server destroys all lingering resources from clients that have terminated in RetainPermanent or RetainTemporary mode.

  • Deletes all but the predefined atom IDs.

  • Deletes all properties on all root windows.

  • Resets all device attributes (key click, bell volume, acceleration) and the access control list.

  • Restores the standard root tiles, cursors, default pointing device, and default font path.

  • Restores the keyboard focus to PointerRoot.

Data Management

Xlib provides two ways to help you manage data within an application: the context manager and association tables. The former saves you the trouble of creating arrays and dynamically allocating memory for data to be used only within your application. The latter is a different way of doing the same thing, maintained for backwards compatibility with X10. We will describe only the context manager. If you are interested in investigating association tables, see Appendix B, X10 Compatibility.

The Context Manager

Four routines are provided to let you associate data with a window locally in Xlib, rather than in the server as in properties. The context manager routines store and retrieve untyped data according to the display, a window ID, and an assigned context ID. The display argument to the context manager routine (returned from XOpenDisplay()) is used as an additional dimension to the array, not as a pointer to the display structure. No requests to the server are made.

First, you call XUniqueContext() to obtain an ID for a particular type of information you want to assign to windows. XUniqueContext() just provides a unique integer ID every time you call it (you can also make up your own if you wish). This ID indicates to the application what type of information is stored, but none of the calls require you to specify the data type. Then use XSaveContext() to store information into the context manager and XFindContext() to read it. If you plan to rewrite a particular piece of data corresponding to a window ID and context ID, it is better in terms of time and space to erase the current entry with XDeleteContext() before calling XSaveContext() again. XDeleteContext() does not make the context ID invalid.

If you have many different pieces of data of the same type, such as an array, that must be associated with each window, you have the option of packing it in a single chunk of data and storing it by context or creating a different context ID for each member of the array. The context ID indicates the meaning of the data (how you interpret it), not necessarily the C language type.

The After Function

Every Xlib function that generates a protocol request calls an after function just before it returns. This function is normally NoOp, but the program may specify the name of any function using XSetAfterFunction().

Coordinate Transformation

XTranslateCoordinates() translates coordinates relative to one window into the coordinates relative to a second and determines whether the resulting position relative to the second window is in a subwindow of the second window.

Because the window-based coordinate system is so convenient, this function is rarely needed. Since XTranslateCoordinates() makes a round-trip request, it cannot be used heavily to port to X programs that use global coordinates.

ANSI-C and POSIX Portability

The MIT Release 5 X distribution is compliant with ANSI-C and POSIX standards, and portable across a wide variety of platforms. While the goal of the ANSI-C and POSIX standards is portability, many systems do not implement these standards, or do not implement them fully, so the MIT R5 distribution defines new header files that attempt to mask the differences between systems. The header files are <X11/Xfuncproto.h>, <X11/Xfuncs.h>, <X11/Xosdefs.h>, and <X11/Xos.h>. None of these files are part of the official R5 standard, so they may not be shipped with your system. But they can be very useful in writing portable applications, so we have included them with the code from this book, which you can get as described in the Preface.[57]

<X11/Xosdefs.h>

The file <X11/Xosdefs.h> defines symbols that describe a system's support for ANSI-C and POSIX. Symbols that describe a system's support for other standards may be added in the future. It defines two new symbols, X_NOT_STDC_ENV and X_NOT_POSIX, for systems that do not have the ANSI-C and POSIX header files, respectively. When standard header files exist, your code should include them. On systems which do not have them, however, attempting to include them would cause a compilation error. The symbols in <X11/Xosdefs.h> allow you to write code that takes the right action in either situation. Note that X_NOT_STDC_ENV is different from __STDC__, which simply indicates whether or not the compiler supports ANSI-C.

An example of using X_NOT_STDC_ENV might be to know when the system declares getenv:

#ifndef X_NOT_STDC_ENV
#include <stdlib.h>

#else
extern char *getenv();
#endif

It is convention in the R5 code from MIT is to put the standard case first using #ifndef.

Lack of the symbol X_NOT_STDC_ENV does not mean that the system has <stdarg.h>. This header file is part of ANSI-C, but the X Consortium found it more useful to check for it separately because many systems have all the ANSI-C files except this one. The symbol __STDC__ is used to control inclusion of this file.

X_NOT_POSIX means the system does not have POSIX.1 header files. Lack of this symbol does not mean that the POSIX environment is the default. You may still have to define _POSIX_SOURCE before including the header file to get POSIX definitions.

An example of using X_NOT_POSIX might be to determine what return type would be declared for getuid in <pwd.h>:

#include <pwd.h>

#ifndef X_NOT_POSIX
    uid_t uid;
#else
    int uid;
    extern int getuid();
#endif
    uid = getuid();

Note that both X_NOT_STDC_ENV and X_NOT_POSIX, when declared, state a noncompliance. This was chosen so that porting to a new, standard platform would be easier. Only non-standard platforms need to add themselves to <X11/Xosdefs.h> to turn on the appropriate symbols.

Not all systems for which the X Consortium leaves these symbols undefined strictly adhere to the relevant standards. Thus you will sometimes see checks for a specific operating system near a check for one of the Xosdefs.h symbols. The X Consortium found it most useful to label systems as conforming even if they had some holes in their compliance. Presumably these holes will become fewer as time goes on.

<X11/Xosdefs.h> is automatically included by the header <X11/Xos.h>.

<X11/Xos.h>

This header file portably defines some of the most commonly used operating system and C library functions, and masks some of the most common system incompatibilities. It should be used instead of <string.h>, <strings.h>, <sys/types.h>, <sys/file.h>, <fcntl.h>, <sys/time.h>, and <unistd.h>. Most of these are POSIX standard header files, but are not yet universal. <X11/Xos.h> defines any of the four functions index, rindex, strchr, and strrchr, which are not defined by the host operating system. It defines gettimeofday and time as well as all the standard string functions. It also defines the type caddr_t, and the constants used by the open system call (O_RDONLY, O_RDWR, etc.) and the constants used by the fcntl system call (R_OK, W_OK, etc.).

Unfortunately, there is not a header file for declaring malloc correctly, and it can be a bit tricky. The MIT R5 distribution uses lines like the following (from mit/lib/Xt/Alloc.c) to declare malloc and related functions:

#ifndef X_NOT_STDC_ENV
#include <stdlib.h>

#else
    char *malloc(), *realloc(), *calloc();
#endif
#if defined(macII) && !defined(__STDC__)
    char *malloc(), *realloc(), *calloc();
#endif /* macII */

Note that because index may be a macro declared in this header, you should be sure to avoid this identifier in variable and structure field names.

<X11/Xfuncs.h>

This new header file provides definitions for the BSD functions bcopy, bzero, and bcmp. These are not standard functions, but are widely used in the X source code. Including this header file allows them to be used portably.

<X11/Xfuncproto.h>

This file contains definitions for writing function declarations in a way that is portable between ANSI-C compilers that support function prototypes and pre-ANSI-C compilers that do not support or only partially support function prototypes.

For external header files that might get used from C++, you should wrap all of your function declarations like this:

_XFUNCPROTOBEGIN...

...function declarations...

_XFUNCPROTOEND...

When in doubt, assume that the header file might get used from C++.

A typical function declaration uses NeedFunctionPrototypes, like this:

extern Atom XInternAtom(
#if NeedFunctionPrototypes
       Display*               /* display */,
       _Xconst char*          /* atom_name */,
       Bool                   /* only_if_exists */
#endif
);

If there are const parameters, [58] use the symbol _Xconst instead, as above. This symbol will be defined only if the compiler supports const parameters. If it is plausible to pass a string constant to a char* parameter, then it is a good idea to declare the parameter with _Xconst, so that literals can be passed in C++.

If there are nested function prototypes, use NeedNestedPrototypes:

extern Bool XCheckIfEvent(
#if NeedFunctionPrototypes
       Display*                  /* display */,
       XEvent*                   /* event_return */,
       Bool (*) (
#if NeedNestedPrototypes
       Display*                  /* display */,
       XEvent*                   /* event */,
       XPointer                  /* arg */
#endif
   )   /* predicate */,
   XPointer   /* arg */
#endif
);

If there is a variable argument list, use NeedVarargsPrototypes:

extern char *XGetIMValues(
#if NeedVarargsPrototypes
    XIM /* im */, ...
#endif
);

If you have parameter types in library functions that will widen (be silently cast to a larger type) in traditional C, then you should use NeedWidePrototypes so that functions compiled with an ANSI-C compiler may be called from code compiled with a traditional C compiler, and vice versa.

extern XModifierKeymap *XDeleteModifiermapEntry(
#if NeedFunctionPrototypes
    XModifierKeymap*    /* modmap */,
#if NeedWidePrototypes
    unsigned int        /* keycode_entry */,
#else
    KeyCode             /* keycode_entry */,
#endif
    int                 /* modifier */
#endif
);

If you use _Xconst, NeedNestedPrototypes, NeedVarargsPrototypes, or NeedWidePrototypes, then your function implementation also has to have a function prototype. For example:

#if NeedFunctionPrototypes
Atom XInternAtom (
    Display *dpy,
    _Xconst char *name,
    Bool onlyIfExists)
#else
Atom XInternAtom (dpy, name, onlyIfExists)
    Display *dpy;
    char *name;
    Bool onlyIfExists;
#endif
{
    ...
}

Actually, whenever you use a function prototype in a header file, you should use a function prototype in the implementation, as required by ANSI-C.

Other Symbols

Do not use the names class, new, or index as variables or structure members. The names class and new are reserved words in C++, and you may find your header files used by a C++ program someday. Depending on the system, index can be defined as a macro in <X11/Xos.h>; this rules out any other use of that name.

The following system-specific symbols are commonly used in X sources where OS dependencies intrude:

USG     Based on System V Release 2.
SYSV    Based on System V Release 3.
SVR4    System V Release 4.

For other system-specific symbols, look at the StandardDefines parameters in the mit/config/*.cf files.

Porting Programs to X

Any program that runs on an ASCII terminal can be run directly under the terminal emulator xterm. The only problem is how to deal with changing the size of the window while the application is running. The application may read the termcap definition to determine the original window size. Look at the X application resize, which makes changes to TERMCAP. The resize reference page (see Volume Three) suggests a couple of C shell aliases for commands to resize xterm windows.

If you have a Berkeley 4.3-compatible tty driver, xterm sets the tty driver's row and column attributes when its top-level window is resized. vi and more and several other programs also look at those attributes when figuring out the terminal size. Also, xterm will send a SIGWINCH signal to the controlling process, which, if it is vi or more, will understand this signal and change its own notion of screen size, repainting the window in the process. This is the best way to deal with window resizing under xterm.

Graphics programs face a more difficult porting path. They must be rewritten to use the X library. It is a good idea to use a toolkit rather than trying to write completely in Xlib.

Programs written for single-user systems such as PCs will be a little more difficult, since they must be converted to respond to events instead of asking for one type of input at a time. They must also be modified to work in a multitasking environment.

Byte order is another traditionally thorny issue in porting. Byte order refers to the order in which bytes of data are stored in memory. There are actually four ways for two-byte data to be ordered, since the direction of each byte has two variations and the position of the most significant byte is also variable.

For X pixmaps, byte order is defined by the server and clients with different native byte ordering must swap bytes as necessary. For all other parts of the protocol, the byte order is defined by the client and the server swaps bytes as necessary.

Programming for Multiple X Releases

In R5 and later, Xlib defines the symbol XlibSpecificationRelease with the release number as the value (i.e., 5). This can be used to allow an application to successfully compile with more than one release of Xlib (assuming of course it depends only on features present in the releases which it will be compiled). Example 15-4 shows a code segment into which R4, R5 and R6 code could be inserted:

Example 15-4. Using the XlibSpecificationRelease symbol

#ifdef XlibSpecificationRelease
    if (XlibSpecificationRelease == 5)
      ;/* R5 */
    else if (XlibSpecificationRelease == 6)
      ;/* R6 */
    else
      ;/* R7 or error */
#elseif
   /* R4 */
#endif


Using Extensions to X

An extension is a set of routines and capabilities that a hardware vendor has provided for use on a particular machine, in addition to the standard X library.

Extensions to X are not second-class citizens, and there should be very little to distinguish the use of an extension from that of the core protocol. The only difference is that the application should check to make sure the extension exists and then query the extension to find out the major opcode, additional event types, and additional error types so that the extension can be integrated properly. If the extensions have been written properly so that they initialize themselves when first called, they should be usable just like other X library functions.

XListExtensions() returns a list of all extensions supported by the server. Once the name of the desired extension is known, XQueryExtension() should be called to get specific information about the extension. XFreeExtensionList() should then be used to free the memory allocated by XListExtensions().

The standard extensions as of this writing are the Shape extension, which supports non-rectangular windows, the X Input extension, which supports input devices other than the single mouse and keyboard normally connected to an X server, and PEX, a 3-D graphics extension. All extensions are optional, however. Only the Shape extension is available in virtually all X servers as of this writing.



[57] The sections below have been adapted from the X Consortium R5 Release Notes.

[58] The const keyword is new in ANSI-C. It indicates that a particular variable or function argument will not be changed. A compiler may be able to perform special optimizations on const parameters.