[comp.windows.x] R4 Hello World

glennw@crevasse.WV.TEK.COM (Glenn Widener;;61-049;;orca;) (02/07/90)

There have been several requests for a "Hello World" program updated for
R4.  Here is the one I presented at my ICCCM tutorial at the MIT X
conference.  Yes, it does run!  It is a bit more than just a minimal Hello
World program; it demonstrates how to create client icon windows and
bitmaps, and how to handle Client Messages, using as an example a new
"client closedown" protocol that I have submitted to the Consortium for
review.

For more information, find the notes from my talk, or better yet, attend
the next edition of the ICCCM talk at Xhibition '90.
-----
/*
 * Copyright 1989 by the Massachusetts Institute of Technology, 
 * Cambridge, Massachusetts, and Tektronix, Inc. Beaverton, Oregon.
 *
 * Permission to use, copy, modify, distribute, and sell this software and
 * its documentation for any purpose is hereby granted without fee, provided
 * that the above copyright notice appear in all copies and that both that
 * copyright notice and this permission notice appear in supporting
 * documentation, and that the names of Tektronix or M.I.T. not be used in
 * advertising or publicity pertaining to distribution of the software
 * without specific, written prior permission.  Tektronix and M.I.T. make no
 * representations about the suitability of this software for any purpose.
 * It is provided "as is" without express or implied warranty.
 *
 * TEKTRONIX AND M.I.T. DISCLAIM ALL WARRANTIES WITH REGARD TO THIS
 * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS,
 * IN NO EVENT SHALL TEKTRONIX OR M.I.T. BE LIABLE FOR ANY SPECIAL, INDIRECT
 * OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF
 * USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
 * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 * PERFORMANCE OF THIS SOFTWARE.
 *
 * Author:  Glenn Widener, Tektronix, Inc.
 *          P.O. Box 1000
 *          Wilsonville, OR, 97070
 *          glennw@orca.wv.tek.com
 *  Based on the r3 Hello World program by David Rosenthal.
 *
 *	NAME
 *		xhw_r4.c - an R4 Xlib Hello World program
 *
 *	DESCRIPTION
 *		This program is an update of the Hello World supplied by
 *		David Rosenthal on the R3 MIT X Consortium tape.  
 *		That program had no copyright; I have added the standard MIT
 *	        release copyright.
 *
 */

#include <stdio.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/Xatom.h>

#define STRING	"Hello, world"
/* icon names should be as short as possible */
#define ICON_NAME	"Hello!"
#define BORDER	1
#define FONT	"fixed"
#define ICON_FILE		"smile"
#define ARG_ICON_FILE		"icon"
#define	ARG_FONT		"font"
#define	ARG_BORDER_COLOR	"bordercolor"
#define	ARG_FOREGROUND		"foreground"
#define	ARG_BACKGROUND		"background"
#define ARG_BORDER		"border"
#define	ARG_GEOMETRY		"geometry"
#define DEFAULT_GEOMETRY	""

char *ProgramName;
XWMHints    *xwmh;		/* WM Hints struct */
XSizeHints  *xsh;		/* Size hints for window manager */
XClassHint  *class_hint;	/* Class name hints for window manager */
Display	    *dpy;		/* X server connection */

void exit();

usage ()
{
    fprintf (stderr, 
	     "usage:  %s [-display host:dpy] [-geometry geom]\n", ProgramName);
    exit (1);
}

main(argc,argv)
    int argc;
    char **argv;
{
    Window      win;		/* Window ID */
    GC          gc;		/* GC to draw with */
    char       *fontName;	/* Name of font for string */
    XFontStruct *fontstruct;	/* Font descriptor */
    unsigned long fth, pad;	/* Font size parameters */
    unsigned long fg, bg, bd;	/* Pixel values */
    unsigned long bw;		/* Border width */
    char       *tempstr;	/* Temporary string */
    XColor      color;		/* Temporary color */
    Colormap    cmap;		/* Color map to use */
    XGCValues   gcv;		/* Struct for creating GC */
    XEvent      event;		/* Event received */
    char       *geomSpec = NULL;/* Window geometry string */
    XSetWindowAttributes xswa;	/* Temporary Set Window Attribute struct */
    int		junk;		/* ignore return values */
    unsigned int ujunk;		/* ignore return values */
    Window	wjunk;		/* ignore return values */
    unsigned int width, height;	/* of icon */
    unsigned int depth;		/* of root window/icon window */
    char	*icon_file;	/* file to get icon bitmap data from */
    char	*displayname = NULL;
    Atom	ClosedownAtom;	/* to ask for WM_CLOSEDOWN */
    Atom	WmProtocolsAtom;/* to read WM_CLOSEDOWN event */
    int         bitmask;	/* for ParseGeometry flags from XWMGeometry */
    int		i;
    Pixmap	icon_win_pixmap;/* for icon window */
    XTextProperty windowName, iconName;   /* ICCCM text */

    /* parse arguments for basic X args */

    ProgramName = argv[0];
    for (i = 1; i < argc; i++) {
	char *arg = argv[i];

	if (arg[0] == '-') {
	    switch (arg[1]) {
	      case 'd':			/* -display host:dpy */
		if (++i >= argc) usage ();
		displayname = argv[i];
		continue;
	      case 'g':			/* -geometry geom */
		if (++i >= argc) usage ();
		geomSpec = argv[i];
		continue;
	      default:
		usage ();
		/* doesn't return */
	    }
	} else 
	  usage ();
    }
    
    /*
     * Open the display using the $DISPLAY environment variable to locate
     * the X server.  See Section 2.1.
     */
    if ((dpy = XOpenDisplay(displayname)) == NULL) {
	fprintf(stderr, "%s: can't open %s\n", argv[0], 
	        XDisplayName(displayname));
	exit(1);
    }

    /*
     * Load the font and icon bitmap to use.  See Sections 10.2 & 6.5.1
     */
    if ((icon_file = XGetDefault(dpy, argv[0], ARG_ICON_FILE)) == NULL) {
	icon_file = ICON_FILE;
    }
    if ((fontName = XGetDefault(dpy, argv[0], ARG_FONT)) == NULL) {
	fontName = FONT;
    }
    if ((fontstruct = XLoadQueryFont(dpy, fontName)) == NULL) {
	fprintf(stderr, "%s: display %s doesn't know font %s\n",
		argv[0], DisplayString(dpy), fontName);
	exit(1);
    }
    fth = fontstruct->max_bounds.ascent + fontstruct->max_bounds.descent;

    /*
     * Select colors for the border,  the window background,  and the
     * foreground.  We use the default colormap to allocate the colors in.
     * Note that the border color will typically be overridden by the wm.
     * See Sections 2.2.1, 5.1.2, & 10.4.
     */
    cmap = DefaultColormap(dpy, DefaultScreen(dpy));
    if ((tempstr = XGetDefault(dpy, argv[0], ARG_BORDER_COLOR)) == NULL ||
	XParseColor(dpy, cmap, tempstr, &color) == 0 ||
	XAllocColor(dpy, cmap, &color) == 0) {
	bd = WhitePixel(dpy, DefaultScreen(dpy));
    }
    else {
	bd = color.pixel;
    }
    if ((tempstr = XGetDefault(dpy, argv[0], ARG_BACKGROUND)) == NULL ||
	XParseColor(dpy, cmap, tempstr, &color) == 0 ||
	XAllocColor(dpy, cmap, &color) == 0) {
	bg = BlackPixel(dpy, DefaultScreen(dpy));
    }
    else {
	bg = color.pixel;
    }
    if ((tempstr = XGetDefault(dpy, argv[0], ARG_FOREGROUND)) == NULL ||
	XParseColor(dpy, cmap, tempstr, &color) == 0 ||
	XAllocColor(dpy, cmap, &color) == 0) {
	fg = WhitePixel(dpy, DefaultScreen(dpy));
    }
    else {
	fg = color.pixel;
    }
    /*
     * Set the border width of the window,  and the gap between the text
     * and the edge of the window, "pad".
     */
    pad = BORDER;
    if ((tempstr = XGetDefault(dpy, argv[0], ARG_BORDER)) == NULL)
	bw = 1;
    else
	bw = atoi(tempstr);

    /*
     * Deal with providing the window with an initial position & size.
     * Fill out the XSizeHints struct to inform the window manager. See
     * Sections 9.1.7 & 10.3.
     */
    xsh = XAllocSizeHints();
    /* Ok, ok, so technically we should be checking for out of memory... */
    
    /*
     * For a program default size, fit the window to the text and locate it in
     * the center of the screen.  Make the minimum size such that the text is
     * always visible.
     */
    xsh->flags = (PPosition | PSize | PMinSize);
    xsh->height =
    xsh->min_height = fth + pad * 2;
    xsh->width = 
    xsh->min_width = XTextWidth(fontstruct, STRING, strlen(STRING)) + pad * 2;
    xsh->x = (DisplayWidth(dpy, DefaultScreen(dpy)) - xsh->width) / 2;
    xsh->y = (DisplayHeight(dpy, DefaultScreen(dpy)) - xsh->height) / 2;

    if (geomSpec == NULL)
	geomSpec = XGetDefault(dpy, argv[0], ARG_GEOMETRY);

    bitmask = XWMGeometry(dpy, DefaultScreen(dpy), geomSpec, 
			  DEFAULT_GEOMETRY, bw, xsh,
			  &(xsh->x), &(xsh->y),
			  &(xsh->width), &(xsh->height), &(xsh->win_gravity));
    /* XWMGeomtry does not set anything in the size hints structure */
    if (bitmask & (XValue | YValue)) {
        xsh->flags |= USPosition;
    }
    if (bitmask & (WidthValue | HeightValue)) {
        xsh->flags |= USSize;
    }

    /*
     * Create the Window with the information in the XSizeHints, the
     * border width,  and the border & background pixels. See Section 3.3.
     */
    win = XCreateSimpleWindow(dpy, DefaultRootWindow(dpy),
			      xsh->x, xsh->y, xsh->width, xsh->height,
			      bw, bd, bg);

    /*
     * Create the GC for writing the text and copying the icon pixmap.  See
     * Section 5.3.
     */
    gcv.font = fontstruct->fid;
    gcv.foreground = fg;
    gcv.background = bg;
    gc = XCreateGC(dpy, win, (GCFont | GCForeground | GCBackground), &gcv);

    /*
     * Set the standard properties for the window managers. See Section
     * 9.1.13.
     */

    /*
     * This structure forms the WM_HINTS property of the window, letting the
     * window manager know how to handle this window.  It may grow, so it is
     * dynamically allocated.  See Section 9.1.6 of the Xlib manual.
     */
    xwmh = XAllocWMHints();
    xwmh->flags = (InputHint|StateHint);
    xwmh->input = False;
    xwmh->initial_state = NormalState;
    /* unset fields are initialized to zero (they are ignored anyway) */

    if (XReadBitmapFile(dpy, win, icon_file, &width, &height,
		        &xwmh->icon_pixmap, &junk, &junk) == BitmapSuccess) {
	/*
	 * Create the icon window the size of the bitmap; ignore the
	 * WM_ICON_SIZE_HINTS and let the wm resize the window if it has to
	 * (the pixmap gets tiled in this case).  Do this cheap, to avoid
	 * handling exposure; use the window background to draw the desired
	 * pixmap.  Pixmap and window depth and visual are those of the root
	 * window.  The background pixmap must be of the depth of the window;
	 * fastest to let the server generate the bits from the depth one
	 * bitmap.  Border will be set by the WM, so usually this border
	 * pixel/width has no effect.  Window position is irrelevant.
	 */

	XGetGeometry(dpy, DefaultRootWindow(dpy), &wjunk, &junk, &junk,
			      &ujunk, &ujunk, &ujunk, &depth);
	icon_win_pixmap = XCreatePixmap(dpy, DefaultRootWindow(dpy),
				        width, height, depth);
	XCopyPlane(dpy, xwmh->icon_pixmap, icon_win_pixmap, gc, 0, 0,
		   width, height, 0, 0, 1);
	xswa.background_pixmap = icon_win_pixmap;
	xswa.border_pixel = bd;
	xwmh->icon_window = XCreateWindow(dpy, DefaultRootWindow(dpy),
			      0, 0, width, height, bw, CopyFromParent,
			      InputOutput, CopyFromParent,
			      (CWBackPixmap | CWBorderPixel),
			      &xswa);
	/* server keeps a copy of the pixmap */
	XFreePixmap(dpy, icon_win_pixmap);

	xwmh->flags |= (IconPixmapHint|IconWindowHint|IconPositionHint);
	/* for fun, center the icon on the screen (perhaps, approximately) */
	xwmh->icon_x = DisplayWidth(dpy, DefaultScreen(dpy)) >> 1;
	xwmh->icon_y = DisplayHeight(dpy, DefaultScreen(dpy)) >> 1;
    }
    else
	fprintf(stderr, "%s: Can't open bitmap file '%s' for icon\n",
		argv[0], icon_file);

    /*
     * Set the WM_CLASS property for the window managers. See Section
     * 9.1.8.
     */
    class_hint = XAllocClassHint();
    class_hint->res_name = argv[0];
    /* We set the program class name consistent with the XGetDefault code,
	   which is itself silly, and needs to be fixed... */
    class_hint->res_class = "Program";

    /*
     * XSetWMProperties() uses the new XTextProperty structure.  See Section
     * 9.1.2.
     */
    windowName.value = (unsigned char *) STRING;
    iconName.value = (unsigned char *) ICON_NAME;
    windowName.encoding = iconName.encoding = XA_STRING;
    windowName.format = iconName.format = 8;
    /* Note - nitems does not include a NULL terminator */
    windowName.nitems = strlen(STRING);
    iconName.nitems = strlen(ICON_NAME);

    XSetWMProperties(dpy, win, &windowName, &iconName, argv, argc, xsh, xwmh,
		     class_hint);

    /*
     * We don't really have to be informed of a connection close, but doing so
     * prevents dangling network resources and/or spurious error messages.
     * Note - WM_CLOSEDOWN is not yet part of the ICCCM!
     */
    ClosedownAtom = XInternAtom(dpy, "WM_CLOSEDOWN", False);
    WmProtocolsAtom = XInternAtom(dpy, "WM_PROTOCOLS", False);
    XSetWMProtocols(dpy, win, &ClosedownAtom, 1);

    /*
     * Ensure that the window's colormap field points to the default
     * colormap,  so that the window manager knows the correct colormap to
     * use for the window.  See Section 3.2.9. Also,  set the window's Bit
     * Gravity to reduce Expose events.
     */
    xswa.colormap = DefaultColormap(dpy, DefaultScreen(dpy));
    xswa.bit_gravity = CenterGravity;
    XChangeWindowAttributes(dpy, win, (CWColormap | CWBitGravity), &xswa);

    /*
     * Specify the event types we're interested in - only Exposures.  See
     * Sections 8.5 & 8.4.5.1
     */
    XSelectInput(dpy, win, ExposureMask);

    /*
     * Map the window to make it visible.  See Section 3.5.
     */
    XMapWindow(dpy, win);

    /*
     * Loop forever,  examining each event.
     */
    while (1) {
	/*
	 * Get the next event
	 */
	XNextEvent(dpy, &event);

	/*
	 * On the last of each group of Expose events,  repaint the entire
	 * window.  See Section 8.4.5.1.
	 */
	if (event.type == Expose && event.xexpose.count == 0) {
	    int         x, y;

	    /*
	     * Remove any other pending Expose events from the queue to
	     * avoid multiple repaints. See Section 8.7.
	     */
	    while (XCheckTypedEvent(dpy, Expose, &event));

	    /*
	     * Find out how big the window is now,  so that we can center
	     * the text in it.
	     */
	    if (XGetGeometry(dpy, win, &wjunk, &junk, &junk, 
			     &width, &height, &ujunk, &ujunk) == 0)
		break;
	    x = (width - XTextWidth(fontstruct, STRING, strlen(STRING))) / 2;
	    y = (height + fontstruct->max_bounds.ascent
		 - fontstruct->max_bounds.descent) / 2;

	    /*
	     * Fill the window with the background color,  and then paint
	     * the centered string.
	     */
	    XClearWindow(dpy, win);
	    XDrawString(dpy, win, gc, x, y, STRING, strlen(STRING));
	}
	else if ((event.type == ClientMessage) &&
		 (event.xclient.message_type == WmProtocolsAtom) &&
		 (event.xclient.data.l[0] == ClosedownAtom)) {
	    Cleanup();
	}
    }
    Cleanup();
}

Cleanup()
{
    /* just to be truly kosher */
    XFree(xwmh);
    XFree(class_hint);
    XFree(xsh);
    /* enough kosher is enough, close the display to free server resources */
    XCloseDisplay(dpy);
    exit(1);
}
Glenn Widener
Tektronix, Inc.
(UPS) 2660 SW Parkway
(US Mail) PO Box 1000