Welcome to a cute little Xlib introduction and tutorial. Before diving into this tutorial, take some time to learn about X. If you don't want to go hunting for your own sources, just read these next few paragraphs.

The X Window System is the de facto standard for graphical environments in Unix, and has been for the past 20 years or so. X itself is actually a network model and protocol, and also provides APIs (Such as Xlib) for the protocol.

X is indeed a network model; in fact, it is a client-server model. In X, a 'server' is one or more screens controlled by a single piece of server software, such as xfree86 or a commercial X server. A client is a program that connects to this server in order to use one or more of the screens, whether on the same machine, or in another room in the same building, or on one of Jupiter's frozen moons. Thus, your screen (if you are using Unix) can potentially be used to run any program on any computer on Earth, if it implements the X protocol. (This is a general statement; I can't actually bring up goatse.cx in a browser on your computer from where I sit, unless you've done something painfully stupid in terms of security.)

Of course, it would be a pain in the ass for each programmer to implement the X protocol in his own programs, creating each packet by hand and managing networking; thus, Xlib is used. Xlib is a C API for low level use of the X protocol. (Actually, you technically aren't using Xlib if you use something like python-xlib (tutorial pending), since Xlib isn't used to make it!)

Setup: Stuff you need

First, you will need a computer running an X Window System server with a C compiler (such as gcc) and Xlib, which is packaged with xfree86. Any stock Linux system will do, but a lot of campuses have Unix labs as well.

Second, you will need an Xlib reference of some sort. Techincally, you could just use what I've given you here, but playing with the examples may require a few more nuts and bolts. I recommend you get the official Xlib specification in Postscript, listed in the sources below. It can be viewed with the gv program, available for Unix systems. Also, I would recommend skimming the tutorials mentioned below if you find mine confusing.

Third, you will need your wits about you. These Xlibs are alive.

Whoa-ho! Just kidding. It's not scary. (Roll human effigy film clip)

Example the first: Connection, information

This is a minimal Xlib program. It does nothing graphical.

#include <stdio.h> // Load stdio for printf #include <assert.h> // Assert() is fluffy #include <X11/Xlib.h> // Load Xlib int main() { int depth; Display *display; char *display_string; assert(display = XOpenDisplay(0)); // Open the default X display depth = DefaultDepth(display, DefaultScreen(display)); // Get the default depth of our display display_string = DisplayString(display); printf("Default depth: %d", depth); // Print the depth printf("Display string: %s", display_string); // Print the display string }

Lesson one: Xlib programs don't have to draw windows to be useful

This is a small, trivial program for printing a little bit of interesting information about your display. Specifically, it will print the default depth and the display string. This program does nothing graphical; none the less, it uses Xlib and communicates with the X server. In fact, the window manager you use is probably such a program.

You may need to mess with compiler options to compile this. I used "gcc -L/usr/X11R6/lib -lX11". YMMV. Compiler options for the next two programs are the same.

A line by line breakdown is hardly necessary, with the comments and all. Here is a brief list of the probable unfamiliar material:

Display is the type of an X display. There will be a few memorable new types, mostly structs, but some unions.

XOpenDisplay(0) opens the default display. It can also be sent a string to open a specific display, e.g. one specified at the command line.

DefaultScreen, DefaultDepth, DisplayString: These three macros generate useful information about the display given as the first argument. DefaultDepth takes a screen as the second argument, thus the use of DefaultScreen.

Example the second: First window

We are operating very much through defaults; it will be that way throughout, but is not a great practice in real life. Accepting a display name and screen from the command line is very common among programs like xterm xclock , etc.

Here is a another program that has primarily educational value, but actually displays something: #include <stdio.h> #include <stdlib.h> #include <assert.h> #include <X11/Xlib.h> struct color_struct { unsigned long black, white; } ; #define SCREEN (DefaultScreen(display)) #define ROOT_WINDOW (DefaultRootWindow(display)) int main(){ int depth; char *display_name; struct color_struct *color = malloc( sizeof(struct color_struct)); Display *display; Window main_window; display = XOpenDisplay(0); assert(display); printf("Default display opened

"); printf("Size: %dx%d

", XDisplayWidth(display, SCREEN), XDisplayHeight(display, SCREEN)); // Figure it out, or look it up color->black = XBlackPixel(display, SCREEN); // Black and white are the only color->white = XWhitePixel(display, SCREEN); // trivial colors in Xlib main_window = XCreateSimpleWindow(display, ROOT_WINDOW, 0, 0, 200, 100, 0, color->black, color->black); // Create a window at 0, 0, that is 200x100 and has a black background assert(main_window); // Make sure it happened printf("Main window created

"); // Hurrah XMapWindow(display, main_window); // We have to 'map' the window to make it visible XFlush(display); // XFlush sends the commands to the server (Network model!) while(1); // Stick around }

Lesson two: Create window, map window, and don't forget to flush

This program is not nearly as trivial as the last one. I have thrown in some real C to make things more readable. The color_struct struct is just used to store some integers; we might want to expand on it later. Those macros are to simplify the more complicated lines.

In Xlib, you have to both create a window and "map" it to make it visible. Mapping allows for things like minimization. Once we create and map the window, we must flush the commands, i.e. the commands are not sent to the server until XFlush is called. This does not apply to things like DisplayName, etc.

The color model is too complicated to address here. There are only two colors that are trivially easy to acquire, so we will make use of them.

By the way, in order to close this program, you will have to kill it yourself; you can do this with xkill or by closing the window and killing the program in the terminal.

Example 3: Eventful drawings?

#include <stdio.h> #include <stdlib.h> #include <assert.h> #include <X11/Xlib.h> struct color_struct { unsigned long black, white; } ; #define SCREEN (DefaultScreen(display)) #define ROOT_WINDOW (DefaultRootWindow(display)) int main(){ int depth; struct color_struct *color = malloc( sizeof(struct color_struct)); Display *display; Window main_window, sub_window; GC main_gc; // We need a graphics context for drawing stuff XEvent event; // We need one of these so we can recieve mouse events display = XOpenDisplay(0); color->black = XBlackPixel(display, SCREEN); color->white = XWhitePixel(display, SCREEN); main_window = XCreateSimpleWindow(display, ROOT_WINDOW, 0, 0, 200, 100, 0, color->black, color->black); main_gc = XDefaultGC(display, 0); // Create the GC for drawing commands sub_window = XCreateSimpleWindow(display, main_window, 10, 10, 50, 50, 0, color->black, color->white); // Yes, we are creating a new window inside the main one. This is // how widgets and so forth are made. XMapWindow(display, sub_window); XMapWindow(display, main_window); XFlush(display); // Map both windows, flush commands to the server XSelectInput(display, main_window, ButtonPressMask); XSelectInput(display, sub_window, ButtonPressMask); // This means we want the mouse click input for both windows XSetForeground(display, main_gc, color->white); // This is so we're drawing in white while(1) { XNextEvent(display, &event); // This gets the next event from the server // We look at the event's type, and then the internal event // which might be xkey or xfocus or xmap or any other number of things. if( event.type == ButtonPress ) { if( event.xbutton.window == main_window ) { XDrawLine(display, main_window, main_gc, 70, 20, 150, 40); XFlush(display); } else if( event.xbutton.window == sub_window ) { XCloseDisplay(display); exit(0); } } } }

Lesson 3: It's windows all the way down

I have removed the asserts for brevity; you may add them back in to taste.

This last example is likely to leave your head spinning if you try to take it in all in one go. This program takes advantage of two new features: The ability to nest windows, and the ability to create simple graphics. All the program does is display a line if you click in the main window and close if you click in the sub window.

The first issue to address is the sub window. It's a funny thing about the X Window System; it's all windows. There are no buttons or text boxes or titlebars in the X protocol; all those are in fact windows nested inside each other! So in essence, what we have done is implement a simple button which responds to a click by closing the program; you should be able to spot the relevant lines. By nesting windows, you can achieve visual seperation, as well as having seperate containers for text and button-type things and so forth. There is no new command introduced; we use the same simple window command and map command as with the first window; in fact, we were just drawing the first window inside another window, the "root window". (Is the light coming on?)

The next issue is the events; there is a lot to talk about here. First of all, events can be any of the events defined in the protocol; here, we are only interested in the mouse events. The server does not know which events to send us; we tell it we would like the button press event using the XSelectInput function. If we wanted, we could request multiple events by ORing them together.

Next, we must find out if we've gotten any events; our old friend, the infinite loop, does this for us. XNextEvent sets the pointer it is handed (&event) to the next event in the queue. We can then find out the type of the event (Well, yes, we do in this case know it's a mouse event), and the details of the event (In this case, just the window) and act on those accordingly. Note that XEvent is a union of every possible event type; you have to access the specific event struct as a member of the union.

Last of all, I should mention the graphics. I have not bothered with text here, for the sake of simplicity. In order to use graphics, we must first create a graphics context (GC). You might typically use the XCreateGC function, but I have chosen to just use the default. Then we use the XDrawLine function to draw a line, using the display, window, context, and coordinates as arguments. There are more complicated drawing functions for text and so forth. Note also that in one of the tutorials sourced at the bottom, the author says that you need to wait for the map notify event so that your drawing will show up; you will need to do this if you need to have something displayed immediately, rather than after an arbitrary amount of time. I omitted that for space and simplicity.

I hope you've enjoyed the show! By the way, if you were looking for the 'cruftiness' in Xlib, you'll have to look further. I'm afraid my examples just don't bring out the cruft.

Also, there's good Xlib code out there for the reading; I recommend running and reading evilwm and anything else that seems appropriate. (xkill, xnethack, whatever)

If you have suggestions or comments, let me know. if I have done anything grossly wrong, I will change it, and if I have done anything grossly right, I would like to know. Don't look here for changes, but if you really want someothing covered here ask me and I might add it. (After I learn it, that is.)

Sources: