Linux GUI Systems - Study Journal

Day 1: 30 Jul 2012

Here's a program to open a window, sleep one second, and then exit.

/* test1.c */

#include <unistd.h>

#include <xcb/xcb.h>

int
main(void)
{
	xcb_connection_t *c;
	xcb_screen_t *screen;
	xcb_window_t win;
	c = xcb_connect(NULL, NULL);
	screen = xcb_setup_roots_iterator(xcb_get_setup(c)).data;
	win = xcb_generate_id(c);
	xcb_create_window(
		c, XCB_COPY_FROM_PARENT, win, screen->root,
		0, 0, 150, 150, 10,
		XCB_WINDOW_CLASS_INPUT_OUTPUT,
		screen->root_visual, 0, NULL);
	xcb_map_window(c, win);
	xcb_flush(c);
	sleep(1);
	return 0;
}

Use the following command to compile and link. Of course, you'll need to make sure you have XCB installed.

cc -o test1 test1.c -lxcb

Day 2: 31 Jul 2012

Today, I'll write some notes about yesterday's program. But first, here's a list of resources I'm working from:

Let's start with the types.

These types are all very simple. Some of them are instances of idioms that are pervasive in XCB. For instance, we see integer identifiers being used to refer to server entities like windows; we see the first of many iterator types; and we see the first of many cookie types.

The actions taken are also fairly self-explanatory. Defaults are used to connect to the display and access attributes of the first screen. The window is created by allocating an id for it, sending a creation request, and finally mapping it. There is a buffer flush and a one-second sleep.

The window creation process reveals some interesting properties: (1) the client is responsible for allocating identifiers, (2) window creation does not automatically make the window visible on screen, (3) the client makes a window visible by "mapping" it.

The parameters provided to the window creation command were not particularly interesting—they were just some sane parameters to get the job done.

Finally, note that there was no error handling and we completely ignored the cookies returned by xcb_create_window and xcb_map_window. Let's write the program again but try some error handling and see what we can do with those cookies.

/* test2.c */

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

#include <xcb/xcb.h>

void
panic(const char *m)
{
	fprintf(stderr, "%s\n", m);
	exit(1);
}

int
main(void)
{
	xcb_connection_t *c;
	const xcb_setup_t *setup;
	xcb_screen_t *screen;
	xcb_window_t win;
	xcb_void_cookie_t cookies[2];
	xcb_generic_error_t *err;
	c = xcb_connect(NULL, NULL);
	if (xcb_connection_has_error(c))
		panic("failed to connect");
	setup = xcb_get_setup(c);
	if (setup->roots_len < 1)
		panic("no roots");
	screen = xcb_setup_roots_iterator(setup).data;
	win = xcb_generate_id(c);
	cookies[0] = xcb_create_window_checked(
		c, XCB_COPY_FROM_PARENT, win, screen->root,
		0, 0, 150, 150, 10,
		XCB_WINDOW_CLASS_INPUT_OUTPUT,
		screen->root_visual, 0, NULL);
	cookies[1] = xcb_map_window_checked(c, win);
	if (xcb_flush(c) <= 0)
		panic("failed to flush buffer");
	err = xcb_request_check(c, cookies[0]);
	if (err != NULL)
		panic("failed in create_window request");
	err = xcb_request_check(c, cookies[1]);
	if (err != NULL)
		panic("failed in map_window request");
	sleep(1);
	return 0;
}

Error handling remains a little bit unclear. It's something I'll have to revisit later.

Day 3: 2 Aug 2012

Before moving into some more interesting features, there are some details from the last program that I want to clear up.

The first is the behavior related to connection errors. Here are a few of the questions that come to mind:

The documentation in xcb.h doesn't say anything about returning NULL but I was suspicious about it and ended up looking into the XCB source. I found that xcb_connection_t is a struct whose first field is an error code. So instead of using NULL, the library seems to be written to always work with good pointers—with the proviso that the pointed-to object may be unusable. The one thing you can safely do with any connection object is find out if it has any errors.

In fact, it seems that XCB functions are written to check for connection errors so that the user can call any function without first verifying the connection—instead responding to errors as they are signalled during operation. My conclusion from all this is that the appropriate places to look for connection errors with xcb_connection_has_error are probably right after a connection attempt and any time after an XCB function fails.

Here are the connection errors:

The next set of questions arose when I revised the program to use the cookies to check for errors using xcb_request_check. As it turns out, there are a number of patterns for the messaging that happens between client and server. Now seems like a good time to look at each pattern in turn.

From what I've seen so far, the units of communication can be partitioned into three categories: requests, responses, and events. Requests can be further divided into those that always give rise to a response and those that never give rise to a response. The latter generate a xcb_void_cookie_t when sent. Requests that have associated replies also have related functions—per request-type—for using a cookie to receive the reply. A further distinction is that request errors can be detected using one of xcb_request_check and xcb_{request-name}_reply or request errors can be detected as they arrive along with events. The client chooses the error mode at request-time by appending _checked or _unchecked to the request call. The _checked form is used with requests that don't have replies. The _unchecked form is used with requests that do have replies.

With this diversion into errors and communication modes done with, I'm hoping to try out some event handling for the next entry.

Day 4: 3 Aug 2012

The following program shows live counters for all event types. It uses the terminus font. Make sure you choose another font if you don't have terminus.

/* test3.c */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <xcb/xcb.h>

#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0]))
#define FONT "-*-terminus-medium-r-*-*-16-*-*-*-*-*-*-*"

static xcb_connection_t *conn;
static xcb_window_t win;
static xcb_gcontext_t foreground;
static uint32_t foreground_color = 9398012;
static uint32_t background_color = 0;
static xcb_font_t font;

static struct {
	const char *name;
	int nr;
} event_counters[] = {
	[XCB_KEY_PRESS] = { "key_press", 0 },
	[XCB_KEY_RELEASE] = { "key_release", 0 },
	[XCB_BUTTON_PRESS] = { "button_press", 0 },
	[XCB_BUTTON_RELEASE] = { "button_release", 0 },
	[XCB_MOTION_NOTIFY] = { "motion_notify", 0 },
	[XCB_ENTER_NOTIFY] = { "enter_notify", 0 },
	[XCB_LEAVE_NOTIFY] = { "leave_notify", 0 },
	[XCB_FOCUS_IN] = { "focus_in", 0 },
	[XCB_FOCUS_OUT] = { "focus_out", 0 },
	[XCB_KEYMAP_NOTIFY] = { "keymap_notify", 0 },
	[XCB_EXPOSE] = { "expose", 0 },
	[XCB_GRAPHICS_EXPOSURE] = { "graphics_exposure", 0 },
	[XCB_NO_EXPOSURE] = { "no_exposure", 0 },
	[XCB_VISIBILITY_NOTIFY] = { "visibility_notify", 0 },
	[XCB_CREATE_NOTIFY] = { "create_notify", 0 },
	[XCB_DESTROY_NOTIFY] = { "destroy_notify", 0 },
	[XCB_UNMAP_NOTIFY] = { "unmap_notify", 0 },
	[XCB_MAP_NOTIFY] = { "map_notify", 0 },
	[XCB_MAP_REQUEST] = { "map_request", 0 },
	[XCB_REPARENT_NOTIFY] = { "reparent_notify", 0 },
	[XCB_CONFIGURE_NOTIFY] = { "configure_notify", 0 },
	[XCB_CONFIGURE_REQUEST] = { "configure_request", 0 },
	[XCB_GRAVITY_NOTIFY] = { "gravity_notify", 0 },
	[XCB_RESIZE_REQUEST] = { "resize_request", 0 },
	[XCB_CIRCULATE_NOTIFY] = { "circulate_notify", 0 },
	[XCB_CIRCULATE_REQUEST] = { "circulate_request", 0 },
	[XCB_PROPERTY_NOTIFY] = { "property_notify", 0 },
	[XCB_SELECTION_CLEAR] = { "selection_clear", 0 },
	[XCB_SELECTION_REQUEST] = { "selection_request", 0 },
	[XCB_SELECTION_NOTIFY] = { "selection_notify", 0 },
	[XCB_COLORMAP_NOTIFY] = { "colormap_notify", 0 },
	[XCB_CLIENT_MESSAGE] = { "client_message", 0 },
	[XCB_MAPPING_NOTIFY] = { "mapping_notify", 0 },
};

static void
panic(const char *m)
{
	fprintf(stderr, "%s\n", m);
	exit(1);
}

static const char *
connection_error_message(int err)
{
	switch (err) {
	case XCB_CONN_ERROR:			return "IO error";
	case XCB_CONN_CLOSED_EXT_NOTSUPPORTED:	return "extension not supported";
	case XCB_CONN_CLOSED_MEM_INSUFFICIENT:	return "insufficient memory";
	case XCB_CONN_CLOSED_REQ_LEN_EXCEED:	return "request length exceeded";
	case XCB_CONN_CLOSED_PARSE_ERR:		return "parse error";
	}
	return "unknown";
}

static void
open_connection(void)
{
	int err;
	conn = xcb_connect(NULL, NULL);
	err = xcb_connection_has_error(conn);
	if (err)
		panic(connection_error_message(err));
}

static void
open_font(void)
{
	font = xcb_generate_id(conn);
	xcb_open_font(conn, font, sizeof(FONT)-1, FONT);
}

static void
create_foreground(void)
{
	uint32_t mask =
		XCB_GC_FOREGROUND | XCB_GC_BACKGROUND | XCB_GC_FONT |
		XCB_GC_GRAPHICS_EXPOSURES;
	uint32_t values[] = {
		foreground_color,
		background_color,
		font,
		0,
	};
	foreground = xcb_generate_id(conn);
	xcb_create_gc(conn, foreground, win, mask, values);
}

#define ALL_EVENTS \
    XCB_EVENT_MASK_KEY_PRESS \
    | XCB_EVENT_MASK_KEY_RELEASE \
    | XCB_EVENT_MASK_BUTTON_PRESS \
    | XCB_EVENT_MASK_BUTTON_RELEASE \
    | XCB_EVENT_MASK_ENTER_WINDOW \
    | XCB_EVENT_MASK_LEAVE_WINDOW \
    | XCB_EVENT_MASK_POINTER_MOTION \
    | XCB_EVENT_MASK_POINTER_MOTION_HINT \
    | XCB_EVENT_MASK_BUTTON_1_MOTION \
    | XCB_EVENT_MASK_BUTTON_2_MOTION \
    | XCB_EVENT_MASK_BUTTON_3_MOTION \
    | XCB_EVENT_MASK_BUTTON_4_MOTION \
    | XCB_EVENT_MASK_BUTTON_5_MOTION \
    | XCB_EVENT_MASK_BUTTON_MOTION \
    | XCB_EVENT_MASK_KEYMAP_STATE \
    | XCB_EVENT_MASK_EXPOSURE \
    | XCB_EVENT_MASK_VISIBILITY_CHANGE \
    | XCB_EVENT_MASK_STRUCTURE_NOTIFY \
    | XCB_EVENT_MASK_RESIZE_REDIRECT \
    | XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY \
    | XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT \
    | XCB_EVENT_MASK_FOCUS_CHANGE \
    | XCB_EVENT_MASK_PROPERTY_CHANGE \
    | XCB_EVENT_MASK_COLOR_MAP_CHANGE \
    | XCB_EVENT_MASK_OWNER_GRAB_BUTTON


static void
create_window(void)
{
	const xcb_setup_t *setup;
	xcb_screen_t *screen;
	uint32_t mask = XCB_CW_BACK_PIXEL | XCB_CW_EVENT_MASK;
	uint32_t values[] = { background_color, ALL_EVENTS };
	static const char name[] = "test3";
	setup = xcb_get_setup(conn);
	if (setup->roots_len < 1)
		panic("no roots");
	screen = xcb_setup_roots_iterator(setup).data;
	win = xcb_generate_id(conn);
	xcb_create_window(
		conn, XCB_COPY_FROM_PARENT, win, screen->root,
		0, 0, 400, 640, 10,
		XCB_WINDOW_CLASS_INPUT_OUTPUT,
		screen->root_visual, mask, values);
	xcb_change_property(conn, XCB_PROP_MODE_REPLACE, win,
		XCB_ATOM_WM_NAME, XCB_ATOM_STRING, 8,
		sizeof(name)-1, name);
	xcb_map_window(conn, win);
	if (xcb_flush(conn) <= 0)
		panic("failed to flush buffer");
}

static void
init(void)
{
	open_connection();
	create_window();
	open_font();
	create_foreground();
}

static void
show_status(void)
{
	static char buf[ARRAY_SIZE(event_counters)][64];
	int i, line_nr = 1, len;
	for (i = 0; i < ARRAY_SIZE(event_counters); i++) {
		if (event_counters[i].name == NULL)
			continue;
		len = snprintf(buf[i], sizeof(buf[i]), "%s: %d",
			event_counters[i].name, event_counters[i].nr);
		xcb_image_text_8(conn, len, win, foreground,
			10, 16 * line_nr, buf[i]);
		line_nr++;
	}
	xcb_flush(conn);
}

static void
handle_event(xcb_generic_event_t *e)
{
	event_counters[e->response_type].nr++;
	show_status();
}

static void
handle_error(xcb_generic_error_t *err)
{
	fprintf(stderr, "got error %d\n", err->error_code);
}

static void
interact(void)
{
	xcb_generic_event_t *e;
	for (;;) {
		e = xcb_wait_for_event(conn);
		if (e == NULL) {
			int err;
			err = xcb_connection_has_error(conn);
			if (err)
				panic(connection_error_message(err));
			panic("unexpected NULL event");
		}
		if (e->response_type)
			handle_event(e);
		else
			handle_error((xcb_generic_error_t *)e);
		free(e);
	}
}

int
main(void)
{
	init();
	interact();
	return 0;
}

Day 5: 4 Aug 2012

Today, I tried to get yesterday's program to resize its window as requested by the window manager. I see resize_request events arriving at appropriate times and they carry appropriate width and height parameters but when I send the configure_window request for the new dimensions, nothing seems to happen. I'll have to revisit this problem later.

Day 6: 8 Aug 2012

Having hit some limits of the set of resources I was working from, I decided to look for some more reading materials. I have a number of web articles I might read through but I'm expecting to spend most of my time digging through the following technical tomes:

I've scanned through these very briefly. It was interesting to read through the historical account at the beginning of The Complete Reference. There are links to MIT, Project Argus, CLU, LISP, Chaosnet--it's all from before I started programming but it's always fun to connect some of the dots.

Another interesting historical note: X11 turns 25 years old next month!

I've started reworking my previous experiment with the initial goal of just dumping more detail about each of the events as they come in. One surprise today was that the response_type field of event structures is not just an event identifier. It also carries a bit to indicate whether the event was generated from a SendEvent request. So to get the event identifier, you should use the XCB_EVENT_RESPONSE_TYPE() macro from xcb_event.h. Also in that header, there are declarations of functions for getting names of event, error, and request identifiers. So I've started using those to shorten my code.

Tomorrow, I'll finish the code to dump detailed event information. Then I think it will be time to figure out what conventions I need to follow in order to get the window manager to cooperate with my experimental clients. In particular, I want to reach the point where my windows move and resize properly in response to the standard window manager commands.

Day 7: 9 Aug 2012

I got more event logging work done today—tedious but useful. I also read through most of the section of the ICCCM that deals with client-to-window-manager communication. I finally found the subsection that gives the trick for dealing with resize events: 4.2.9. Because I am listening for all events—including ResizeRequest—I need to set the override_redirect flag on the window, send the resize request, and finally clear the override_redirect flag. Presumably, the resize would be handled transparently by the window manager if I weren't listening for ResizeRequest events. I haven't tested that though.

It has been a fairly successful day but I'm realizing as I read through the ICCCM and glance at the EWMH that there are many many ways to mess up these details. It's going to take some time to get comfortable with it all.

One last thing before I sign off for today. Here's the list of atoms that I could find mentioned in dwm version 6.0:

Day 8: 11 Aug 2012

I decided to take a break from all the reading to make a little prototype.

The prototype displays a list of labeled histograms. Each histogram is tiny so that I can pack a bunch of them on the screen at once. I often have a number of quantities that I want to monitor as they change over days and weeks. The tool I have in mind is something I might consult each day to see at a glance how things are going.

The program reads the data from some files at start-up and draws the histograms by using xcb_poly_fill_rectangle to draw each bar. The event loop doesn't have much to do since the program is not interactive. I just use it to watch for map, unmap, and visibility-change events.

Aside: I realized that I can use browser-style, three-byte RGB hex colors. That's convenient.

Here's what it looks like:

Here's the code:

/* test5.c */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <xcb/xcb.h>
#include <xcb/xcb_event.h>
#include <xcb/xcb_ewmh.h>

#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0]))
#define FONT "-*-terminus-medium-r-*-*-16-*-*-*-*-*-*-*"

static xcb_connection_t *conn;
static xcb_window_t win;
static xcb_gcontext_t foreground;
static uint32_t foreground_color = 0x8f7ad1;
static uint32_t background_color = 0x39106d;
static xcb_font_t font;
static xcb_ewmh_connection_t ewmh;
static int is_mapped = 0;
static int is_visible = 0;

static void
panic(const char *m)
{
	fprintf(stderr, "%s\n", m);
	exit(1);
}

static void
log_error(xcb_generic_error_t *err)
{
	if (err == NULL)
		return;
	fprintf(stderr, "error (request: %s): %s\n",
		xcb_event_get_request_label(err->major_code),
		xcb_event_get_error_label(err->error_code));
}

static const char *
connection_error_message(int err)
{
	switch (err) {
	case XCB_CONN_ERROR:			return "IO error";
	case XCB_CONN_CLOSED_EXT_NOTSUPPORTED:	return "extension not supported";
	case XCB_CONN_CLOSED_MEM_INSUFFICIENT:	return "insufficient memory";
	case XCB_CONN_CLOSED_REQ_LEN_EXCEED:	return "request length exceeded";
	case XCB_CONN_CLOSED_PARSE_ERR:		return "parse error";
	}
	return "unknown";
}

static void
open_connection(void)
{
	int err;
	conn = xcb_connect(NULL, NULL);
	err = xcb_connection_has_error(conn);
	if (err)
		panic(connection_error_message(err));
}

static void
ewmh_init(void)
{
	xcb_intern_atom_cookie_t *cookies;
	xcb_generic_error_t *err;
	cookies = xcb_ewmh_init_atoms(conn, &ewmh);
	if (!xcb_ewmh_init_atoms_replies(&ewmh, cookies, &err)) {
		log_error(err);
		panic("failed to init EWMH");
	}
}

static void
open_font(void)
{
	font = xcb_generate_id(conn);
	xcb_open_font(conn, font, sizeof(FONT)-1, FONT);
}

static void
create_foreground(void)
{
	uint32_t mask =
		XCB_GC_FOREGROUND | XCB_GC_BACKGROUND | XCB_GC_FONT |
		XCB_GC_GRAPHICS_EXPOSURES;
	uint32_t values[] = {
		foreground_color,
		background_color,
		font,
		0,
	};
	foreground = xcb_generate_id(conn);
	xcb_create_gc(conn, foreground, win, mask, values);
}

#define EVENT_MASK \
	XCB_EVENT_MASK_VISIBILITY_CHANGE \
	| XCB_EVENT_MASK_STRUCTURE_NOTIFY

static void
create_window(void)
{
	const xcb_setup_t *setup;
	xcb_screen_t *screen;
	uint32_t mask = XCB_CW_BACK_PIXEL | XCB_CW_EVENT_MASK;
	uint32_t values[] = { background_color, EVENT_MASK };
	static const char name[] = "test5";
	setup = xcb_get_setup(conn);
	if (setup->roots_len < 1)
		panic("no roots");
	screen = xcb_setup_roots_iterator(setup).data;
	win = xcb_generate_id(conn);
	xcb_create_window(
		conn, XCB_COPY_FROM_PARENT, win, screen->root,
		0, 0, 800, 640, 10,
		XCB_WINDOW_CLASS_INPUT_OUTPUT,
		screen->root_visual, mask, values);
	xcb_change_property(conn, XCB_PROP_MODE_REPLACE, win,
		XCB_ATOM_WM_NAME, XCB_ATOM_STRING, 8,
		sizeof(name)-1, name);
	xcb_change_property(conn, XCB_PROP_MODE_REPLACE, win,
		ewmh._NET_WM_NAME, XCB_ATOM_STRING, 8,
		sizeof(name)-1, name);
	xcb_map_window(conn, win);
	if (xcb_flush(conn) <= 0)
		panic("failed to flush buffer");
}

struct sparkline {
	const char *name;
	int data[10];
};

#define MAX_SPARKLINES 32
static struct sparkline sparklines[MAX_SPARKLINES];
static int nr_sparklines;

static void
load_sparkline(const char *name)
{
	static const char *dirname = "./test5-data/";
	char *fullname;
	FILE *file;
	struct sparkline *s;
	int i;
	if (nr_sparklines >= MAX_SPARKLINES)
		return;
	fullname = malloc(sizeof(dirname) + strlen(name));
	strcpy(fullname, dirname);
	strcat(fullname, name);
	file = fopen(fullname, "r");
	s = &sparklines[nr_sparklines];
	s->name = name;
	for (i = 0; i < 10; i++) {
		fscanf(file, "%d", &s->data[i]);
	}
	nr_sparklines++;
}

static void
load_data(void)
{
	load_sparkline("wins");
	load_sparkline("losses");
}

static void
init(void)
{
	open_connection();
	ewmh_init();
	create_window();
	open_font();
	create_foreground();
	load_data();
}

static void
handle_visibility_notify_event(xcb_visibility_notify_event_t *e)
{
	if (e->state == XCB_VISIBILITY_FULLY_OBSCURED)
		is_visible = 0;
	else
		is_visible = 1;
}

static void
handle_map_notify_event(xcb_map_notify_event_t *e)
{
	is_mapped = 1;
}

static void
handle_unmap_notify_event(xcb_unmap_notify_event_t *e)
{
	is_mapped = 0;
}

static void
show_sparkline(int line_nr, const struct sparkline *s)
{
	static char buf[64];
	int len, i;
	xcb_rectangle_t *r, rects[10];
	strcpy(buf, s->name);
	strcat(buf, ":");
	len = strlen(buf);
	memset(buf + len, ' ', ARRAY_SIZE(buf) - len);
	xcb_image_text_8(conn, 24, win, foreground,
		10, 16 * line_nr, buf);
	for (i = 0; i < 10; i++) {
		r = &rects[i];
		r->x = 80 + 4 * i;
		r->y = 16 * line_nr - 2 * (s->data[i] + 1);
		r->width = 2;
		r->height = 2 * (s->data[i] + 1);
	}
	xcb_poly_fill_rectangle(conn, win, foreground, 10, rects);
}

static void
draw(void)
{
	int i, line_nr;
	for (i = 0, line_nr = 1; i < nr_sparklines; i++, line_nr++)
		show_sparkline(line_nr, &sparklines[i]);
	if (xcb_flush(conn) <= 0)
		panic("failed to flush buffer");
}

static void
handle_event(xcb_generic_event_t *e)
{
	int type = XCB_EVENT_RESPONSE_TYPE(e);
	switch (type) {
	case XCB_VISIBILITY_NOTIFY:
		handle_visibility_notify_event(
			(xcb_visibility_notify_event_t *)e);
		break;
	case XCB_MAP_NOTIFY:
		handle_map_notify_event((xcb_map_notify_event_t *)e);
		break;
	case XCB_UNMAP_NOTIFY:
		handle_unmap_notify_event((xcb_unmap_notify_event_t *)e);
		break;
	}
	if (is_mapped && is_visible)
		draw();
}

static void
handle_error(xcb_generic_error_t *err)
{
	log_error(err);
}

static void
interact(void)
{
	xcb_generic_event_t *e;
	for (;;) {
		e = xcb_wait_for_event(conn);
		if (e == NULL) {
			int err;
			err = xcb_connection_has_error(conn);
			if (err)
				panic(connection_error_message(err));
			panic("unexpected NULL event");
		}
		if (e->response_type)
			handle_event(e);
		else
			handle_error((xcb_generic_error_t *)e);
		free(e);
	}
}

int
main(void)
{
	init();
	interact();
	return 0;
}

Day 9: 14 Aug 2012

Today I went through some more of the ICCCM and EWMH details. I found that much of it can reasonably be ignored for my own current purposes. The only properties I find worth using at this point are WM_NAME and WM_PROTOCOLS. I use WM_PROTOCOLS to register for WM_DELETE_WINDOW messages. Without it, closing a window tends to close the connection, which I didn't like because such behavior is indistinguishable from a connection error.

Here's some of the code related to WM_PROTOCOLS:

/* snip1.c */

#include <xcb/xcb_icccm.h>

static void
icccm_init(void)
{
	static const char name[] = "test6";
	xcb_atom_t protocols[] = {
		intern("WM_DELETE_WINDOW"),
	};
	xcb_icccm_set_wm_name(conn, win, XCB_ATOM_STRING, 8,
		sizeof(name)-1, name);
	xcb_icccm_set_wm_protocols(conn, win, intern("WM_PROTOCOLS"),
		ARRAY_SIZE(protocols), protocols);
}

static void
handle_client_message(xcb_client_message_event_t *e)
{
	if (e->type != intern("WM_PROTOCOLS"))
		return;
	if (e->data.data32[0] == intern("WM_DELETE_WINDOW"))
		exit(0);
}

I feel now that I've covered enough of the basics that I can begin to tackle some more interesting basic clients. The project I have in mind entails writing a terminal emulator and a window manager. I don't see this as a large project because:

Day 10: 15 Aug 2012

I worked on porting my old terminal emulator today. The encoding of key events is not as straightforward as I had expected. So I'll have to return to that later. On the other hand, one thing that worked out nicely was the multiplexing. The terminal reads and writes on one file descriptor to communicate with the child process and must concurrently deal with X events. I'm happiest when libraries let me treat everything as a file and use the poll system call to work with all channels uniformly. Luckily, the XCB library provides a way to work in that style. The following snippet shows how to get the file descriptor for the X connection using xcb_get_file_descriptor and also the—very rough—polling loop that I have so far. The pseudoterminal IO goes through a pair of ring buffers—one for each direction. Whenever the X connection is readable, I use xcb_poll_for_event to handle each available event.

/* snip2.c */

static int x_fd;
static int tty_fd;

static Ring ttyoutbuf;

static void
open_connection(void)
{
	int err;
	conn = xcb_connect(NULL, NULL);
	err = xcb_connection_has_error(conn);
	if (err)
		panic(connection_error_message(err));
	x_fd = xcb_get_file_descriptor(conn);
}

static void
init(void)
{
	// Not shown. Opens the X connection and forks the shell process,
	// among other things.
}

static void
handle_event(xcb_generic_event_t *e)
{
	// Not shown. May write into ttyoutbuf.
}

static void
poll_x(void)
{
	xcb_generic_event_t *e;
	for (;;) {
		e = xcb_poll_for_event(conn);
		if (e == NULL) {
			if (xcb_connection_has_error(conn))
				panic("X IO error");
			return;
		}
		if (e->response_type)
			handle_event(e);
		else
			handle_error((xcb_generic_error_t *)e);
		free(e);
	}
}

static void
loop(void)
{
	struct pollfd fds[] = {
		{ .fd = x_fd, .events = POLLIN },
		{ .fd = tty_fd, .events = POLLIN },
	};
	int n, eof;
	Ring inbuf;
	ringinit(&inbuf, malloc(512), 512); // TODO leak ok?
	for (;;) {
		if (ringsize(&ttyoutbuf) > 0)
			fds[1].events |= POLLOUT;
		else
			fds[1].events &= ~POLLOUT;
		n = poll(fds, 2, -1);
		if (n == -1) {
			if (errno == EAGAIN)
				continue;
			if (errno == EINTR) {
				// TODO what goes here?
				continue;
			}
			panic("poll: %m");
		}
		if (fds[0].revents & POLLIN)
			poll_x();
		if (fds[1].revents & POLLIN) {
			ringread(&inbuf, tty_fd, &eof);
			if (eof)
				exit(0);
			if (ringsize(&inbuf) > 0) {
				parse(&inbuf);
				if (is_mapped && is_visible)
					draw();
			}
		}
		if (fds[1].revents & POLLOUT)
			ringwrite(&ttyoutbuf, tty_fd);
	}
}

int
main(void)
{
	init();
	loop();
	return 0;
}