Johnson and Reichard are authors of X Window Applications Programming Advanced X Window Applications Programming and Power Programming Motif (all MIS:Press, 1-800-MAN UALS). Johnson can be reached at erc@pai.mn.org via UUCP mail. Reichard's Compuserve address is 73670,3422.
When you look at an X Window screen, usually you see a screen covered with rectangles the windows. These rectangular windows tend to blur together after awhile, since they all look the same. The windows that stand out have odd shapes, such as round clocks or company logos.
Until the X11 Release 4, all windows in the X Window System were rectangles. Unlike the Macintosh or NeWS windowing systems, you couldn't have round clocks and rounded-corner calculators and so on. This article continues our X Window series by showing how to make windows that have odd shapes. You should be familiar with the concepts introduced in the last two installments of our X Window series, since this article concentrates on the SHAPE extension.
The SHAPE Extension
The SHAPE extension is probably the most popular add-on to the X Window System. You can create a window and set the window's shape to anything you can properly describe that fits within the bounding rectangle the window was created under. Extensions (in X terminology) extend the base X Window network protocol to add features. Other extensions (and planned extensions) include 3D PHIGS support (called PEX for the PHIGS Extension to X), video (VEX), and Display Postscript.
The first hurdle you face is finding a system that provides the SHAPE extension, the mechanism that allows X to handle odd-shaped windows (officially called "nonrectangular" windows). If you have a Sun workstation, you could try writing NeWS PostScript code, but that's another matter entirely. Since this article is on X, we'll stick to the SHAPE extension.
Is The SHAPE Extension Available?
Since the SHAPE extension is so popular, almost every Release 4 system has it. You can tell by running the standard X program xdpyinfo. This utility prints out a host of information, much of it confusing, about the X server. What you look for is something like
number of extensions: 4 SHAPE MIT-SHM Multi -Buffering MIT-SUNDRY-NONSTANDARDIf you see "SHAPE," then the extension is there.Running the xdpyinfo program is fine for users, but for robust code, an X Window application needs to determine if the SHAPE extension is available. The function XShapeQueryVersion can be used to check for the SHAPE extension (see Listing1). As in the example, most SHAPE-based programs require the include file <X11/extensions/shape.h>.
To use XShapeQueryVersion, open the connection to the X server. If you've read the earlier articles in our series, this should be old hat. If it isn't, check out an X Window programming book or look up our previous articles in The C Users Journal.
XShapeQueryVersion returns a non-zero status if the SHAPE extension is available.
Once we have checked for the SHAPE extension, the next step is to create a window following the window-creation process described in the last two articles. This window will be a normal rectangular window. The initial size of this window (width and height) becomes the bounding rectangle for whatever shape we select for our window.
Window Shapes
With the SHAPE extension, you can define a new shape for a window. You must describe this shape in a manner that the SHAPE extension can understand. Your can create a shape based on:
a bitmap that is, any bit pattern
a set of rectangles
a region a special X Window structure
or a combination of the above We'll use a set of rectangles, since this is the easiest method. (You can look up Advanced X Window Applications Programming for an example using a bit map).
We need to create a set of rectangles to define the new window shape. To do this, we need to declare an array of X-Rectangle structures.
The XRectangle Structure
The XRectangle structure looks like:
typedef struct { short x, y; unsigned short width, height; } XRectangle;We need to fill in an array of arbitrary rectangles and then call the function XShapeCombineRectangles as in Listing 2.XShapeCombineRectangles is a complex function, so bear with us. First, we pass the X display connection and the window we want to change. Then, we pass which shape we want to set. The choices are:
ShapeBounding, to set the shape of the window itself (this window's border or bounding area)
ShapeClip, to set the shape of the clip area inside of the window In most cases, you'll use ShapeBounding.
The x-offset and y-offset are the offset for the start of the shape from the window's origin (upper left corner). We want to set the whole window to the new shape, so the offsets are zero.
The operation is one of ShapeSet, ShapeUnion, ShapeIntersect, or ShapeSubtract, which define how the rectangles are interpreted. You can combine several shapes to make very complex window shapes. You could take the intersection of one shape with a previous shape using ShapeIntersect and so on. For the example, we just set a new shape using ShapeSet.
Listing 3 shows the code to create a number of rectangles and then set the shape of a window to be those rectangles. The window will effectively have holes areas that are within the window's bounding rectangle but aren't part of the window. Keyboard input in those holes will be sent to whatever window is underneath our window, not to our window.
Trying Out The SHAPE Extension
That's all you really have to do. From now on, the window will have a different shape, a shape made up of rectangles, with empty "gutters" between the rectangles. This task can be more complex, and there are plenty of extra options, but these steps are all you really need. You can now start writing those logo-shaped X clocks or rounded-corner pushbuttons or whatever your heart desires.
In shape1.c (Listing 4) , we've put together a program that uses the X SHAPE extension. If you've been following our X Window series, use the Xlib-based program from the July 1991 C Users Journal ("Introduction to X Window Programming, Part 3: More Xlib Programming") as a starting point. Just type in the changes we made for this issue you'll save a lot of work over typing in the whole program. You can also order CUJ listings on disk. See The C Users Order Form near the center of this magazine.
The program starts by connecting to the default X server (named in the UNIX environment variable DISPLAY). We then create a window, register information with the window manager, load a font, and create a graphics context, just as we did in the last two articles. After creating the window, we check for the presence of the SHAPE extension with XShapeQueryVersion. If the SHAPE extension is supported, then the program creates several rectangles and calls ShapeCombineRectangles. After that, the program enters an event loop, writing a text string containing the SHAPE extension version numbers on every Expose event. Click a mouse button, generating a ButtonPress event in the window to exit the program. Note that the window will only be the interior rectangles (that is, the window does not cover all the area within its boundaries).
Compiling the Example Program
Programs that use the SHAPE extension need to link in the X Window extensions library, usually called Xext (/usr/lib/libXext.a on most systems). You can compile the shape program with a UNIX command something like
cc -o shape1 shape1.c -lXext -lX11For More Information
The official reference for the SHAPE extension is the document titled X11 Nonrectangular Window Shape Extension, that comes with the MIT X Consortium Release 4 of X. Therecugnews is an example SHAPE program in our book, Advanced X Window Applications Programming (MIS: Press, 1990), starting on page 547. We haven't seen any other examples described. You can also look in the demos directory of the X Window sources for xaquarium. Some Athena widget programs, like xmh (a mail program) and xclipboard use rounded-corner button windows when the SHAPE extension is available. If you have these sources you can study these examples as well. We'll cover the Athena widget set in our next X Window installment.
Listing 1
#include <X11/Xlib.h> #include <X11/extensions/shape.h> Display *display; int shape_status; int major_version, minor_version; /* Open display connection... */ /* * Check if we have the SHAPE extension */ shape_status = XShapeQueryVersion( display, &major_version, &minor_version ); if( shape_status != O ) { printf( "SHAPE extension supported ver.%d.%d\n", major_version, minor_version ); /* ... */ } /* End of File */
Listing 2
#define MAX_RECTS 100 /* some arbitrary size */ XRectangle rectangles[ MAX_RECTS + 1 ]; Display *display; Window window; int number_rectangles; int which_kind_of_shape = ShapeBounding; int xoffset, yoffset; int operation = ShapeSet; int ordering = Unsorted; XShapeCombineRectangles( display, window, which_kind_of_shape, xoffset, yoffset, rectangles, number_rectangles, operation, ordering ); /* we haven't sorted the rects */ /* End of File */
Listing 3
#define WIDTH 400 #define HEIGHT 300 #define MAX_RECTS 100 XRectangle rectangles[ MAX_RECTS + 1 ]; Display *display; Window window; int number_rectangles, i; /* * Set up some rectangles */ i = 0; for ( x = 0; x < WIDTH; x += 50 ) { for( y = 0; y < HEIGHT; y += 50 ) { i++; if ( i >= MAX_RECTS ) { i = MAX_RECTS - 1; } rectangles[i].width = 45; rectangles[i].height = 45; rectangles[i].x = x; rectangles[i].y = y; } } /* * Make our window's shape be the set of rectangles */ number_rectangles = i; XShapeCombineRectangles( display, window, ShapeBounding, 0, 0, /* x, y, offsets */ rectangles, number_rectangles, ShapeSet, Unsorted ); /* we haven't sorted the rects */ /* End of File */
Listing 4
/* * shape1.c * An Xlib program using the SHAPE * extension from X11 Release 4. * Written for the C Users Journal. * * Link with the X library, e.g., * * cc -o shape1 shape1.c -lXext -lX11 * * Define SYSV if you have malloc() * declared in stdlib.h. * * 14 Dec 90 */ #include <stdio.h> #ifdef SYSV #include <stdlib.h> #endif #include <X11/Xlib.h> #include <X11/Xutil.h> #include <X11/keysymdef.h> #include <X11/keysym.h> #include <X11/extensions/shape.h> /* * We have a hard-coded size and location */ #define X_LOCATION 10 #define Y_LOCATION 20 #define WIDTH 400 #define HEIGHT 300 #define MAX_STRING_LENGTH 400 #define MAX_RECTS 100 /* * Use xlsfonts to find a font name * that is available on your system. */ /* #define FONT_NAME "fixed" */ #define FONT_NAME "variable" main( argc, argv ) int argc; char *argv[]; { /* main */ Display *display; int screen; Window rootwindow; Window window; XSizeHints *sizehints; XWMHints *wmhints; GC gc; XEvent event; int done; XFontStruct *font; int shape_status, major_version, minor_version; XRectangle rectangles[ MAX_RECTS + 1 ]; int i, number_rectangles, x, y; char message[ MAX_STRING_LENGTH + 4 ]; /* * Connect to an X server. */ display = XOpenDisplay( (char *) NULL ); if ( display == (Display*) NULL ) { fprintf( stderr, "Error opeing in display\n" ); exit( 1 ); } screen = DefaultScreen( display ); rootwindow = RootWindow( display, screen ); /* * Create a window, note reversed * order of colors */ window = XCreateSimpleWindow( display, rootwindow, /* parent */ X_LOCATION, Y_LOCATION, WIDTH, HEIGHT, 1, /* border width */ WhitePixel( display, screen ), BlackPixel( display, screen ) ); /* * Set up Window Manager Hints for keyboard input */ wmhints = (XWMHints *) malloc( sizeof(XWMHints) ); wmhints->flags = InputHint; wmhints->input = True; XSetWMHints(display, window, wmhints ); free( wmhints ); /* * Set up hints about the window */ sizehints = (XSizeHints *) malloc( sizeof(XSizeHints) ); sizehints->x = X_LOCATION; sizehints->y = Y_LOCATION; sizehints->width = WIDTH; sizehints->height = HEIGHT; sizehints->flags = PPosition | PSize; /* * Use XSetWMProperties() in R4 * */ XSetStandardProperties( display, window, "Shape1", /* window name */ "Shape1", /* icon name */ (Pixmap) None, /* Icon pixmap */ argv, argc, sizehints ); free( sizehints ); /* * Ask for Expose, mouse (Button) and keyboard input */ XSelectInput( display, window, ButtonPressMask | ExposureMask | KeyPressMask ); /* * Load up a font. */ font = XLoadQueryFont( display, FONT_NAME ); if ( font == (XFontStruct *) NULL ) { fprintf( stderr, "Error in loading %s font.\n", FONT_NAME ); XCloseDisplay( display ); exit( 1 ); } /* * Create a Graphics Context (GC) for drawing text */ gc = XCreateGC( display, window, 0L, (XGCValues *) NULL ); XSetForeground( display, gc, WhitePixel( display, screen ) ); /* * Set the background color, to, * this time to black, since the * foreground is white. */ XSetBackground( display, gc, BlackPixel( display, screen ) ); /* * Set the GC to draw in the given font. */ XSetFont( display, gc, font->fid ); /* * Check if we have the SHAPE extension */ shape_status = XShapeQueryVersion( display, &major_version, &minor_version ); if( shape_status ) { sprintf( message, "SHAPE extension supported at version %d.%d\n", major_version, minor_version ); /* * Set up some rectangles */ i = 0; for ( x = 0; x < WIDTH; x += 50 ) { for( y = 0; y < HEIGHT; y += 50 ) { i++; if ( i >= MAX_RECTS ) { i = MAX_RECTS - 1; } rectangles[i].width = 45; rectangles[i].height = 45; rectangles[i].x = x; rectangles[i].y = y; } } /* * Make our window's shape be the set of rectangles */ number_rectangles = i; XShapeCombineRectangles( display, window, ShapeBounding, 0, 0, /* x, y, offsets */ rectangles, number_rectangles, ShapeSet, Unsorted ); /* we haven't sorted the rects */ } else { sprintf( message, "Sorry, the SHAPE extension is not available" ); } /* * Make Window appear on the screen */ XMapWindow( display, window ); XFlush( display ); done = False; while( !done ) { XNextEvent( display, &event ); switch( event.type ) { case ButtonPress: XCloseDisplay( display ); exit( 0 ); break; case Expose: /* * Only redraw when all * Expose events are in */ if ( event.xexpose.count == 0 ) { RedrawText( display, window, gc, message ); } break; } } } /* main */ RedrawText( display, window, gc, string ) Display *display; Window window; GC gc; char string[]; { /* RedrawText */ XDrawImageString( display, window, gc, 5, 20, /* location */ string, strlen( string ) ); #define MESSAGE "Click a Mouse Button to Exit" XDrawImageString( display, window, gc, 5, 40, /* location */ MESSAGE, strlen( MESSAGE ) ); XFlush( display ); } /* RedrawText */ /* End of File */