ENOSUCHBLOG

Programming, philosophy, pedaling.


Who Needs Toolkits When You Have Xlib?

Mar 14, 2015

Tags: programming

Disclaimer: You shouldn't write large graphical programs for X without a toolkit. This post will go over some of the smaller things that can be done via Xlib with a minimal amount of pain and confusion. For large programs, working directly with Xlib becomes hairy and painful very quickly.

Dating back to 1984, the X Window System venerable member of the UNIX world, predating just about every popular UNIX or UNIX-like system in common use today. Already well established and frozen as version 11 (hence "X11") well before the initial release of Linux in 1991, X is a relic of an earlier time in computing, complete with a codebase widely panned for doing too much, too little, and failing to do anything well in the process.

A fair deal of this criticism is deserved; X wasn't designed with personal single-user desktops in mind and, as a result, it has accumulated quite a bit of cruft over the years in the form of various confusing standards that attempt to bolt modern paradigms onto an aging model. Of course there are other things that are pretty neat about X, like its network-agnostic client-server model and the fact that a modern platform can seamlessly switch between X window managers in a matter of seconds per the user's wishes.

But I digress.

I'm not writing this to discuss X's pros and cons, as those have been covered in great detail over its 30+ years of existence. Instead, I am here to cover a very specific situation that I (and others, no doubt) often find myself in: making the X server do <thing> in my program without using a massive toolkit or framework.

To that end, I'm going to outline (and provide examples) for the following common graphical operations, all with mostly simple solutions in plain old Xlib-backed C:

Why? Because we shouldn't need to use large frameworks for simple things (even when they aren't so simple, thanks to X), and because it's good to be comfortable with the underlying library that controls your entire graphical experience.

Creating a basic X Window

Before we take a look at the source code, let's run through what it actually takes to draw a plain, white window with Xlib: In order:

  1. Open a connection to the X server with XOpenDisplay and obtain a Display *.
  2. Get the default screen of the opened display with DefaultScreen.
  3. Actually create the Window structure with a call to XCreateSimpleWindow.
  4. Map the Window to the Display and flush it all for displaying.
  5. Use an event loop or sleep to keep the program alive, and clean up before exiting.

That's a lot of steps for a pretty simple task, and we haven't even included proper error checking or any actual content!

Here's the actual code:

#include <X11/Xlib.h>

include <stdio.h>

include <stdlib.h>

int main(void) { int x, y, width, height, border; Display *disp; int screen; Window wind;

/* try experimenting with different top-left coordinates */
x = y = 0;

/* try experimenting with different widths and heights */
width = height = 500;

/* try experimenting with different border widths */
border = 0;

if (!(disp = XOpenDisplay(NULL)))
{
    fprintf(stderr, &quot;Could not open an X display.\n&quot;);
    return EXIT_FAILURE;
}

/* get the default screen of the now-opened display */
screen = DefaultScreen(disp);

wind = XCreateSimpleWindow(disp, RootWindow(disp, screen),
        x, y, /* the upper left start coordinates */
        width, height, /* the dimensions of the window */
        0, /* the size of window borders - 0 for none */
        BlackPixel(disp, screen), WhitePixel(disp, screen));

/* map our new window to the display and flush it */
XMapWindow(disp, wind);
XFlush(disp);

/* sleep for a few seconds before cleaning up and exiting */
sleep(5);

/* destroy the window and close the display to avoid memory leaks */
XDestroyWindow(disp, wind);
XCloseDisplay(disp);

return EXIT_SUCCESS;

}

When compiled as follows:

$ gcc basic_window.c -o basic_window -lX11

We should get a binary, basic_window, that produces this when executed:

A plain Window.

Impressive, huh?

Constraining an X Window

Our plain window is fine and dandy, but what happens if a user tries to resize it beyond the size of our (non-resizable) window contents?

Luckily, Xlib provides the XSizeHints structure and XSetWMNormalHints function, allowing us to set the maximum, minimum, and default sizes in pixels of any Window. With a few small modifications, our basic_window.c from before is now properly constrained:

#include <X11/Xlib.h>

include <X11/Xutil.h> /* for XSizeHints */

include <stdio.h>

include <stdlib.h>

define MIN 200

define MAX 800

int main(void) { int x, y, width, height, border; Display *disp; int screen; Window wind; XSizeHints *wind_size_hints;

/* try experimenting with different top-left coordinates */
x = y = 0;

/* try experimenting with different widths and heights */
width = height = 500;

/* try experimenting with different border widths */
border = 0;

if (!(disp = XOpenDisplay(NULL)))
{
    fprintf(stderr, &quot;Could not open an X display.\n&quot;);
    return EXIT_FAILURE;
}

/* get the default screen of the now-opened display */
screen = DefaultScreen(disp);

wind = XCreateSimpleWindow(disp, RootWindow(disp, screen),
        x, y, /* the upper left start coordinates */
        width, height, /* the dimensions of the window */
        0, /* the size of window borders - 0 for none */
        BlackPixel(disp, screen), WhitePixel(disp, screen));

if (!(wind_size_hints = XAllocSizeHints()))
{
    fprintf(stderr, &quot;Could not allocate memory for size hints.\n&quot;);
    return EXIT_FAILURE;
}

/*  define which constraints we want to use.
    in this case, we&#39;re using all three: initial, minimum, and maximum sizes
*/
wind_size_hints-&gt;flags = PSize | PMinSize | PMaxSize;

/* set the minimum allowed dimensions to MIN x MIN */
wind_size_hints-&gt;min_width = MIN;
wind_size_hints-&gt;min_height = MIN;

/* set the initial dimensions to width x height */
wind_size_hints-&gt;width = width;
wind_size_hints-&gt;height = height;

/* set the maximum allowed dimensions to MAX x MAX */
wind_size_hints-&gt;max_width = MAX;
wind_size_hints-&gt;max_height = MAX;

/* apply the size hints to the window */
XSetWMNormalHints(disp, wind, wind_size_hints);

/* we can free our size hints after applying them */
XFree(wind_size_hints);

/* map our new window to the display and flush it */
XMapWindow(disp, wind);
XFlush(disp);

/* sleep for a few seconds before cleaning up and exiting */
sleep(15);

/* destroy the window and close the display to avoid memory leaks */
XDestroyWindow(disp, wind);
XCloseDisplay(disp);

return EXIT_SUCCESS;

}

Compiled just as above, this produces them same plain window, but with dimensions no smaller than 200x200 and no larger than 800x800 after resizing.

These gists are starting to get pretty long, so let's wrap our window creation code into one big function that takes a Display * and all other necessary arguments and returns a usable, flushed Window. You can find this function here.

Changing the name of an X Window

Now that we have a create_window function that does all the ugly Window creation and constraint stuff for us, we can think about actually naming the window (another prime example of something that should be done inside the XCreateSimpleWindow function).

Luckily, setting a Window's name is a relatively simple matter, and can be isolated in a function we'll call name_window:

/*   name_window
    Name the given window "name" on the given display.

Arguments:
Display *disp - a pointer to the X display
Window wind - the window being named
const char *name - the name being given to the window

*/ void name_window(Display *disp, Window wind, const char *name) { XTextProperty name_prop;

name_prop.value = (unsigned char *) name;
name_prop.encoding = XA_STRING; /* found in X11/Xatom.h */
name_prop.format = 8; /* our format is 8 bits, ASCII encoded */
name_prop.nitems = strlen(name); /* specify the number of characters in name */

XSetWMName(disp, wind, &amp;name_prop);

}

Sticking an X Window to the front of the screen

For some programs, it makes sense for a given window to be constantly visible and never covered up by new windows from other programs. Urgent dialog boxes are the most common example of this, but the principle can be applied to just about any window that needs to be visible (nearly) 100% of the time.

Because the concept of window "frontness" is purely decided by the window manager and not the X server itself, Xlib must rely on standards like the Extended Window Manager Hints to suggest changes in visibility to the window manager running above it. Luckily for us, just about all of the common window managers implement EWMH, allowing us to construct the following function:

/*   set_window_front
    On EWMH-compliant window managers, sets the _NET_WM_STATE of the given
    window to _NET_WM_STATE_ABOVE to make it stay on top of all others besides
    _NET_WM_STATE_FULLSCREEN. If the window manager is not EWMH-compliant, 
    nothing is done.

Arguments:
Display *disp - a pointer to the X display
Window wind - the window being moved to the front

*/ void set_window_front(Display *disp, Window wind) { Atom wm_state, wm_state_above; XEvent event;

if ((wm_state = XInternAtom(disp, &quot;_NET_WM_STATE&quot;, False)) != None)
{
    if ((wm_state_above = XInternAtom(disp, &quot;_NET_WM_STATE_ABOVE&quot;, False))
        != None)
    {
        /* sending a ClientMessage */
        event.xclient.type = ClientMessage;

        /* value unimportant in this case */
        event.xclient.serial = 0;

        /* coming from a SendEvent request, so True */
        event.xclient.send_event = True;

        /* the event originates from disp */
        event.xclient.display = disp;

        /* the window whose state will be modified */
        event.xclient.window = wind;

        /* the component Atom being modified in the window */
        event.xclient.message_type = wm_state;

        /* specifies that data.l will be used */
        event.xclient.format = 32;

        /* 1 is _NET_WM_STATE_ADD */
        event.xclient.data.l[0] = 1;

        /* the atom being added */
        event.xclient.data.l[1] = wm_state_above;

        /* unused */
        event.xclient.data.l[2] = 0;
        event.xclient.data.l[3] = 0;
        event.xclient.data.l[4] = 0;

        /* actually send the event */
        XSendEvent(disp, DefaultRootWindow(disp), False,
            SubstructureRedirectMask | SubstructureNotifyMask, &amp;event);
    }
}

}

On non-EWMH compliant window managers, therefore, this should fail gracefully.

Tying it all together

This post quickly exploded in size thanks to the large examples, so I decided to cut a large part of it out. Specifically, the topics of graphic contexts and bitmaps were removed entirely. To see a relatively clean example of everything in this post, as well as X BitMaps and basic graphics, please take a look at xnelson, which uses all the the principles above (as well as X BitMaps) to stick a simple window to the front of the screen with nothing but Xlib.

Conclusions

So, what have we accomplished?

We've proven that it's possible, with some pain, to perform common graphical operations with raw Xlib routines. Of course, many of these routines aren't useful by themselves - we need to add images, text, menus, and so forth. For that, I highly recommend some kind of library, even if it's just Xaw or the more full-featured Qt and GTK+.

In the meantime, I'll be thinking up new ways of masochistically implementing more simple tasks with as few dependencies as possible.

- William