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:
- A tutorial on XCB
- Header file /usr/include/xcb/xcb.h
- Header file /usr/include/xcb/xproto.h
- X Window System Protocol by Robert W. Scheifler
- The mcwm project
- The dwm project
Let's start with the types.
xcb_connection_t
is an opaque type with a predictable rolexcb_setup_t
(returned byxcb_get_setup
) is a struct of server attributesxcb_screen_t
is a struct of screen attributesxcb_window_t
is an alias for a 32-bit unsigned integer idxcb_screen_iterator_t
(returned byxcb_setup_roots_iterator
) is a small struct for accessing the list of screensxcb_visualid_t
(the type of screen->root_visual) is another 32-bit unsigned integer idxcb_void_cookie_t
(returned byxcb_create_window
) is just a sequence number for distinguishing messages
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:
- Can
xcb_connect
returnNULL
? - What kinds of errors can arise?
- When can these errors arise?
- When is it appropriate to call
xcb_connection_has_errors
?
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:
XCB_CONN_ERROR
XCB_CONN_CLOSED_EXT_NOTSUPPORTED
XCB_CONN_CLOSED_MEM_INSUFFICIENT
XCB_CONN_CLOSED_REQ_LEN_EXCEED
XCB_CONN_CLOSED_PARSE_ERR
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:
- X Window System: The Complete Reference
- ICCCM
- EWMH
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:
_NET_ACTIVE_WINDOW
_NET_SUPPORTED
_NET_WM_NAME
_NET_WM_STATE
_NET_WM_STATE_FULLSCREEN
_NET_WM_WINDOW_TYPE
_NET_WM_WINDOW_TYPE_DIALOG
WM_DELETE_WINDOW
WM_PROTOCOLS
WM_STATE
WM_TAKE_FOCUS
XA_WM_HINTS
XA_WM_NAME
XA_WM_NORMAL_HINTS
XA_WM_TRANSIENT_FOR
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:
- The terminal emulator doesn't have to emulate any particular standard terminal. I'll use terminfo to export a small but reasonable terminal interface. And I already have a prototype emulator to start from that was part of an old SDL project.
- The window manager only has to emulate the tiling, single-monitor subset of dwm and only has to correctly host the new terminal emulator.
- Neither needs antialiased fonts or nontrivial graphics.
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; }