]> git.sur5r.net Git - i3/i3/blob - src/mainx.c
Don’t rely on libxcb-wm any longer, as it got removed in libxcb 0.3.4
[i3/i3] / src / mainx.c
1 /*
2  * vim:ts=8:expandtab
3  *
4  * i3 - an improved dynamic tiling window manager
5  *
6  * © 2009 Michael Stapelberg and contributors
7  *
8  * See file LICENSE for license information.
9  *
10  */
11 #include <stdio.h>
12 #include <assert.h>
13 #include <stdlib.h>
14 #include <string.h>
15 #include <sys/types.h>
16 #include <unistd.h>
17 #include <stdbool.h>
18 #include <assert.h>
19 #include <limits.h>
20 #include <locale.h>
21
22 #include <X11/XKBlib.h>
23 #include <X11/extensions/XKB.h>
24
25 #include <xcb/xcb.h>
26 #include <xcb/xcb_atom.h>
27 #include <xcb/xcb_aux.h>
28 #include <xcb/xcb_event.h>
29 #include <xcb/xcb_property.h>
30 #include <xcb/xcb_keysyms.h>
31 #include <xcb/xcb_icccm.h>
32 #include <xcb/xinerama.h>
33
34 #include "config.h"
35 #include "data.h"
36 #include "debug.h"
37 #include "handlers.h"
38 #include "i3.h"
39 #include "layout.h"
40 #include "queue.h"
41 #include "table.h"
42 #include "util.h"
43 #include "xcb.h"
44 #include "xinerama.h"
45
46 /* This is the path to i3, copied from argv[0] when starting up */
47 char *application_path;
48
49 /* This is our connection to X11 for use with XKB */
50 Display *xkbdpy;
51
52 /* The list of key bindings */
53 struct bindings_head bindings = TAILQ_HEAD_INITIALIZER(bindings);
54
55 /* This is a list of Stack_Windows, global, for easier/faster access on expose events */
56 struct stack_wins_head stack_wins = SLIST_HEAD_INITIALIZER(stack_wins);
57
58 /* The event handlers need to be global because they are accessed by our custom event handler
59    in handle_button_press(), needed for graphical resizing */
60 xcb_event_handlers_t evenths;
61 xcb_atom_t atoms[NUM_ATOMS];
62
63 int num_screens = 0;
64
65 /*
66  * Do some sanity checks and then reparent the window.
67  *
68  */
69 void manage_window(xcb_property_handlers_t *prophs, xcb_connection_t *conn, xcb_window_t window, window_attributes_t wa) {
70         LOG("managing window.\n");
71         xcb_drawable_t d = { window };
72         xcb_get_geometry_cookie_t geomc;
73         xcb_get_geometry_reply_t *geom;
74         xcb_get_window_attributes_reply_t *attr = 0;
75
76         if (wa.tag == TAG_COOKIE) {
77                 /* Check if the window is mapped (it could be not mapped when intializing and
78                    calling manage_window() for every window) */
79                 if ((attr = xcb_get_window_attributes_reply(conn, wa.u.cookie, 0)) == NULL)
80                         return;
81
82                 if (attr->map_state != XCB_MAP_STATE_VIEWABLE)
83                         goto out;
84
85                 wa.tag = TAG_VALUE;
86                 wa.u.override_redirect = attr->override_redirect;
87         }
88
89         /* Don’t manage clients with the override_redirect flag */
90         if (wa.u.override_redirect)
91                 goto out;
92
93         /* Check if the window is already managed */
94         if (table_get(&by_child, window))
95                 goto out;
96
97         /* Get the initial geometry (position, size, …) */
98         geomc = xcb_get_geometry(conn, d);
99         if (!attr) {
100                 wa.tag = TAG_COOKIE;
101                 wa.u.cookie = xcb_get_window_attributes(conn, window);
102                 attr = xcb_get_window_attributes_reply(conn, wa.u.cookie, 0);
103         }
104         geom = xcb_get_geometry_reply(conn, geomc, 0);
105         if (attr && geom) {
106                 reparent_window(conn, window, attr->visual, geom->root, geom->depth,
107                                 geom->x, geom->y, geom->width, geom->height);
108                 xcb_property_changed(prophs, XCB_PROPERTY_NEW_VALUE, window, WM_NAME);
109                 xcb_property_changed(prophs, XCB_PROPERTY_NEW_VALUE, window, WM_NORMAL_HINTS);
110                 xcb_property_changed(prophs, XCB_PROPERTY_NEW_VALUE, window, atoms[_NET_WM_NAME]);
111         }
112
113         free(geom);
114 out:
115         free(attr);
116         return;
117 }
118
119 /*
120  * reparent_window() gets called when a new window was opened and becomes a child of the root
121  * window, or it gets called by us when we manage the already existing windows at startup.
122  *
123  * Essentially, this is the point where we take over control.
124  *
125  */
126 void reparent_window(xcb_connection_t *conn, xcb_window_t child,
127                      xcb_visualid_t visual, xcb_window_t root, uint8_t depth,
128                      int16_t x, int16_t y, uint16_t width, uint16_t height) {
129
130         xcb_get_property_cookie_t wm_type_cookie, strut_cookie, state_cookie;
131         uint32_t mask = 0;
132         uint32_t values[3];
133
134         /* We are interested in property changes */
135         mask = XCB_CW_EVENT_MASK;
136         values[0] = CHILD_EVENT_MASK;
137         xcb_change_window_attributes(conn, child, mask, values);
138
139         /* Map the window first to avoid flickering */
140         xcb_map_window(conn, child);
141
142         /* Place requests for properties ASAP */
143         wm_type_cookie = xcb_get_any_property_unchecked(conn, false, child, atoms[_NET_WM_WINDOW_TYPE], UINT32_MAX);
144         strut_cookie = xcb_get_any_property_unchecked(conn, false, child, atoms[_NET_WM_STRUT_PARTIAL], UINT32_MAX);
145         state_cookie = xcb_get_any_property_unchecked(conn, false, child, atoms[_NET_WM_STATE], UINT32_MAX);
146
147         Client *new = table_get(&by_child, child);
148
149         /* Events for already managed windows should already be filtered in manage_window() */
150         assert(new == NULL);
151
152         LOG("reparenting new client\n");
153         new = calloc(sizeof(Client), 1);
154         new->force_reconfigure = true;
155
156         /* Update the data structures */
157         Client *old_focused = CUR_CELL->currently_focused;
158
159         new->container = CUR_CELL;
160         new->workspace = new->container->workspace;
161
162         new->frame = xcb_generate_id(conn);
163         new->child = child;
164         new->rect.width = width;
165         new->rect.height = height;
166
167         mask = 0;
168
169         /* Don’t generate events for our new window, it should *not* be managed */
170         mask |= XCB_CW_OVERRIDE_REDIRECT;
171         values[0] = 1;
172
173         /* We want to know when… */
174         mask |= XCB_CW_EVENT_MASK;
175         values[1] = FRAME_EVENT_MASK;
176
177         LOG("Reparenting 0x%08x under 0x%08x.\n", child, new->frame);
178
179         i3Font *font = load_font(conn, config.font);
180         width = min(width, c_ws->rect.x + c_ws->rect.width);
181         height = min(height, c_ws->rect.y + c_ws->rect.height);
182
183         Rect framerect = {x, y,
184                           width + 2 + 2,                  /* 2 px border at each side */
185                           height + 2 + 2 + font->height}; /* 2 px border plus font’s height */
186
187         /* Yo dawg, I heard you like windows, so I create a window around your window… */
188         new->frame = create_window(conn, framerect, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_CURSOR_LEFT_PTR, mask, values);
189
190         /* Put the client inside the save set. Upon termination (whether killed or normal exit
191            does not matter) of the window manager, these clients will be correctly reparented
192            to their most closest living ancestor (= cleanup) */
193         xcb_change_save_set(conn, XCB_SET_MODE_INSERT, child);
194
195         /* Generate a graphics context for the titlebar */
196         new->titlegc = xcb_generate_id(conn);
197         xcb_create_gc(conn, new->titlegc, new->frame, 0, 0);
198
199         /* Moves the original window into the new frame we've created for it */
200         new->awaiting_useless_unmap = true;
201         xcb_void_cookie_t cookie = xcb_reparent_window_checked(conn, child, new->frame, 0, font->height);
202         if (xcb_request_check(conn, cookie) != NULL) {
203                 LOG("Could not reparent the window, aborting\n");
204                 xcb_destroy_window(conn, new->frame);
205                 free(new);
206                 return;
207         }
208
209         /* Put our data structure (Client) into the table */
210         table_put(&by_parent, new->frame, new);
211         table_put(&by_child, child, new);
212
213         /* We need to grab the mouse buttons for click to focus */
214         xcb_grab_button(conn, false, child, XCB_EVENT_MASK_BUTTON_PRESS,
215                         XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_ASYNC, root, XCB_NONE,
216                         1 /* left mouse button */,
217                         XCB_BUTTON_MASK_ANY /* don’t filter for any modifiers */);
218
219         /* Get _NET_WM_WINDOW_TYPE (to see if it’s a dock) */
220         xcb_atom_t *atom;
221         xcb_get_property_reply_t *preply = xcb_get_property_reply(conn, wm_type_cookie, NULL);
222         if (preply != NULL && preply->value_len > 0 && (atom = xcb_get_property_value(preply))) {
223                 for (int i = 0; i < xcb_get_property_value_length(preply); i++)
224                         if (atom[i] == atoms[_NET_WM_WINDOW_TYPE_DOCK]) {
225                                 LOG("Window is a dock.\n");
226                                 new->dock = true;
227                                 new->titlebar_position = TITLEBAR_OFF;
228                                 new->force_reconfigure = true;
229                                 new->container = NULL;
230                                 SLIST_INSERT_HEAD(&(c_ws->screen->dock_clients), new, dock_clients);
231                         }
232         }
233
234         if (new->dock) {
235                 /* Get _NET_WM_STRUT_PARTIAL to determine the client’s requested height */
236                 uint32_t *strut;
237                 preply = xcb_get_property_reply(conn, strut_cookie, NULL);
238                 if (preply != NULL && preply->value_len > 0 && (strut = xcb_get_property_value(preply))) {
239                         /* We only use a subset of the provided values, namely the reserved space at the top/bottom
240                            of the screen. This is because the only possibility for bars is at to be at the top/bottom
241                            with maximum horizontal size.
242                            TODO: bars at the top */
243                         new->desired_height = strut[3];
244                         if (new->desired_height == 0) {
245                                 LOG("Client wanted to be 0 pixels high, using the window's height (%d)\n", height);
246                                 new->desired_height = height;
247                         }
248                         LOG("the client wants to be %d pixels high\n", new->desired_height);
249                 } else {
250                         LOG("The client didn't specify space to reserve at the screen edge, using its height (%d)\n", height);
251                         new->desired_height = height;
252                 }
253         }
254
255         /* Focus the new window if we’re not in fullscreen mode and if it is not a dock window */
256         if (CUR_CELL->workspace->fullscreen_client == NULL) {
257                 if (!new->dock) {
258                         CUR_CELL->currently_focused = new;
259                         xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, new->child, XCB_CURRENT_TIME);
260                 }
261         } else {
262                 /* If we are in fullscreen, we should lower the window to not be annoying */
263                 uint32_t values[] = { XCB_STACK_MODE_BELOW };
264                 xcb_configure_window(conn, new->frame, XCB_CONFIG_WINDOW_STACK_MODE, values);
265         }
266
267         /* Insert into the currently active container, if it’s not a dock window */
268         if (!new->dock) {
269                 /* Insert after the old active client, if existing. If it does not exist, the
270                    container is empty and it does not matter, where we insert it */
271                 if (old_focused != NULL && !old_focused->dock)
272                         CIRCLEQ_INSERT_AFTER(&(CUR_CELL->clients), old_focused, new, clients);
273                 else CIRCLEQ_INSERT_TAIL(&(CUR_CELL->clients), new, clients);
274
275                 SLIST_INSERT_HEAD(&(new->container->workspace->focus_stack), new, focus_clients);
276         }
277
278         /* Check if the window already got the fullscreen hint set */
279         xcb_atom_t *state;
280         if ((preply = xcb_get_property_reply(conn, state_cookie, NULL)) != NULL &&
281             (state = xcb_get_property_value(preply)) != NULL)
282                 /* Check all set _NET_WM_STATEs */
283                 for (int i = 0; i < xcb_get_property_value_length(preply); i++)
284                         if (state[i] == atoms[_NET_WM_STATE_FULLSCREEN]) {
285                                 /* If the window got the fullscreen state, we just toggle fullscreen
286                                    and don’t event bother to redraw the layout – that would not change
287                                    anything anyways */
288                                 toggle_fullscreen(conn, new);
289                                 return;
290                         }
291
292         render_layout(conn);
293 }
294
295 /*
296  * Go through all existing windows (if the window manager is restarted) and manage them
297  *
298  */
299 void manage_existing_windows(xcb_connection_t *conn, xcb_property_handlers_t *prophs, xcb_window_t root) {
300         xcb_query_tree_reply_t *reply;
301         int i, len;
302         xcb_window_t *children;
303         xcb_get_window_attributes_cookie_t *cookies;
304
305         /* Get the tree of windows whose parent is the root window (= all) */
306         if ((reply = xcb_query_tree_reply(conn, xcb_query_tree(conn, root), 0)) == NULL)
307                 return;
308
309         len = xcb_query_tree_children_length(reply);
310         cookies = smalloc(len * sizeof(*cookies));
311
312         /* Request the window attributes for every window */
313         children = xcb_query_tree_children(reply);
314         for(i = 0; i < len; ++i)
315                 cookies[i] = xcb_get_window_attributes(conn, children[i]);
316
317         /* Call manage_window with the attributes for every window */
318         for(i = 0; i < len; ++i) {
319                 window_attributes_t wa = { TAG_COOKIE, { cookies[i] } };
320                 manage_window(prophs, conn, children[i], wa);
321         }
322
323         free(reply);
324         free(cookies);
325 }
326
327 int main(int argc, char *argv[], char *env[]) {
328         int i, screens, opt;
329         char *override_configpath = NULL;
330         xcb_connection_t *conn;
331         xcb_property_handlers_t prophs;
332         xcb_window_t root;
333         xcb_intern_atom_cookie_t atom_cookies[NUM_ATOMS];
334
335         setlocale(LC_ALL, "");
336
337         /* Disable output buffering to make redirects in .xsession actually useful for debugging */
338         if (!isatty(fileno(stdout)))
339                 setbuf(stdout, NULL);
340
341         application_path = sstrdup(argv[0]);
342
343         while ((opt = getopt(argc, argv, "c:v")) != -1) {
344                 switch (opt) {
345                         case 'c':
346                                 override_configpath = sstrdup(optarg);
347                                 break;
348                         case 'v':
349                                 printf("i3 version " I3_VERSION " © 2009 Michael Stapelberg and contributors\n");
350                                 exit(EXIT_SUCCESS);
351                         default:
352                                 fprintf(stderr, "Usage: %s [-c configfile]\n", argv[0]);
353                                 exit(EXIT_FAILURE);
354                 }
355         }
356
357         LOG("i3 version " I3_VERSION " starting\n");
358
359         /* Initialize the table data structures for each workspace */
360         init_table();
361
362         memset(&evenths, 0, sizeof(xcb_event_handlers_t));
363         memset(&prophs, 0, sizeof(xcb_property_handlers_t));
364
365         load_configuration(override_configpath);
366
367         conn = xcb_connect(NULL, &screens);
368
369         /* Place requests for the atoms we need as soon as possible */
370         #define REQUEST_ATOM(name) atom_cookies[name] = xcb_intern_atom(conn, 0, strlen(#name), #name);
371
372         REQUEST_ATOM(_NET_SUPPORTED);
373         REQUEST_ATOM(_NET_WM_STATE_FULLSCREEN);
374         REQUEST_ATOM(_NET_SUPPORTING_WM_CHECK);
375         REQUEST_ATOM(_NET_WM_NAME);
376         REQUEST_ATOM(_NET_WM_STATE);
377         REQUEST_ATOM(_NET_WM_WINDOW_TYPE);
378         REQUEST_ATOM(_NET_WM_DESKTOP);
379         REQUEST_ATOM(_NET_WM_WINDOW_TYPE_DOCK);
380         REQUEST_ATOM(_NET_WM_STRUT_PARTIAL);
381         REQUEST_ATOM(WM_PROTOCOLS);
382         REQUEST_ATOM(WM_DELETE_WINDOW);
383         REQUEST_ATOM(UTF8_STRING);
384
385         /* TODO: this has to be more beautiful somewhen */
386         int major, minor, error;
387
388         major = XkbMajorVersion;
389         minor = XkbMinorVersion;
390
391         int evBase, errBase;
392
393         if ((xkbdpy = XkbOpenDisplay(getenv("DISPLAY"), &evBase, &errBase, &major, &minor, &error)) == NULL) {
394                 fprintf(stderr, "XkbOpenDisplay() failed\n");
395                 return 1;
396         }
397
398         int i1;
399         if (!XkbQueryExtension(xkbdpy,&i1,&evBase,&errBase,&major,&minor)) {
400                 fprintf(stderr, "XKB not supported by X-server\n");
401                 return 1;
402         }
403         /* end of ugliness */
404
405         xcb_event_handlers_init(conn, &evenths);
406
407         /* DEBUG: Trap all events and print them */
408         for (i = 2; i < 128; ++i)
409                 xcb_event_set_handler(&evenths, i, handle_event, 0);
410
411         for (i = 0; i < 256; ++i)
412                 xcb_event_set_error_handler(&evenths, i, (xcb_generic_error_handler_t)handle_event, 0);
413
414         /* Expose = an Application should redraw itself, in this case it’s our titlebars. */
415         xcb_event_set_expose_handler(&evenths, handle_expose_event, NULL);
416
417         /* Key presses/releases are pretty obvious, I think */
418         xcb_event_set_key_press_handler(&evenths, handle_key_press, NULL);
419         xcb_event_set_key_release_handler(&evenths, handle_key_release, NULL);
420
421         /* Enter window = user moved his mouse over the window */
422         xcb_event_set_enter_notify_handler(&evenths, handle_enter_notify, NULL);
423
424         /* Button press = user pushed a mouse button over one of our windows */
425         xcb_event_set_button_press_handler(&evenths, handle_button_press, NULL);
426
427         /* Map notify = there is a new window */
428         xcb_event_set_map_request_handler(&evenths, handle_map_request, &prophs);
429
430         /* Unmap notify = window disappeared. When sent from a client, we don’t manage
431            it any longer. Usually, the client destroys the window shortly afterwards. */
432         xcb_event_set_unmap_notify_handler(&evenths, handle_unmap_notify_event, NULL);
433
434         /* Configure notify = window’s configuration (geometry, stacking, …). We only need
435            it to set up ignore the following enter_notify events */
436         xcb_event_set_configure_notify_handler(&evenths, handle_configure_event, NULL);
437
438         /* Configure request = window tried to change size on its own */
439         xcb_event_set_configure_request_handler(&evenths, handle_configure_request, NULL);
440
441         /* Client message are sent to the root window. The only interesting client message
442            for us is _NET_WM_STATE, we honour _NET_WM_STATE_FULLSCREEN */
443         xcb_event_set_client_message_handler(&evenths, handle_client_message, NULL);
444
445         /* Initialize the property handlers */
446         xcb_property_handlers_init(&prophs, &evenths);
447
448         /* Watch size hints (to obey correct aspect ratio) */
449         xcb_property_set_handler(&prophs, WM_NORMAL_HINTS, UINT_MAX, handle_normal_hints, NULL);
450
451         /* Get the root window and set the event mask */
452         root = xcb_aux_get_screen(conn, screens)->root;
453
454         uint32_t mask = XCB_CW_EVENT_MASK;
455         uint32_t values[] = { XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT |
456                               XCB_EVENT_MASK_STRUCTURE_NOTIFY |         /* when the user adds a screen (e.g. video
457                                                                            projector), the root window gets a
458                                                                            ConfigureNotify */
459                               XCB_EVENT_MASK_PROPERTY_CHANGE |
460                               XCB_EVENT_MASK_ENTER_WINDOW };
461         xcb_change_window_attributes(conn, root, mask, values);
462
463         /* Setup NetWM atoms */
464         #define GET_ATOM(name) { \
465                 xcb_intern_atom_reply_t *reply = xcb_intern_atom_reply(conn, atom_cookies[name], NULL); \
466                 if (!reply) { \
467                         LOG("Could not get atom " #name "\n"); \
468                         exit(-1); \
469                 } \
470                 atoms[name] = reply->atom; \
471                 free(reply); \
472         }
473
474         GET_ATOM(_NET_SUPPORTED);
475         GET_ATOM(_NET_WM_STATE_FULLSCREEN);
476         GET_ATOM(_NET_SUPPORTING_WM_CHECK);
477         GET_ATOM(_NET_WM_NAME);
478         GET_ATOM(_NET_WM_STATE);
479         GET_ATOM(_NET_WM_WINDOW_TYPE);
480         GET_ATOM(_NET_WM_DESKTOP);
481         GET_ATOM(_NET_WM_WINDOW_TYPE_DOCK);
482         GET_ATOM(_NET_WM_STRUT_PARTIAL);
483         GET_ATOM(WM_PROTOCOLS);
484         GET_ATOM(WM_DELETE_WINDOW);
485         GET_ATOM(UTF8_STRING);
486
487         xcb_property_set_handler(&prophs, atoms[_NET_WM_WINDOW_TYPE], UINT_MAX, handle_window_type, NULL);
488         /* TODO: In order to comply with EWMH, we have to watch _NET_WM_STRUT_PARTIAL */
489
490         /* Watch _NET_WM_NAME (= title of the window in UTF-8) property */
491         xcb_property_set_handler(&prophs, atoms[_NET_WM_NAME], 128, handle_windowname_change, NULL);
492
493         /* Watch WM_NAME (= title of the window in compound text) property for legacy applications */
494         xcb_watch_wm_name(&prophs, 128, handle_windowname_change_legacy, NULL);
495
496         /* Set up the atoms we support */
497         check_error(conn, xcb_change_property_checked(conn, XCB_PROP_MODE_REPLACE, root, atoms[_NET_SUPPORTED],
498                        ATOM, 32, 7, atoms), "Could not set _NET_SUPPORTED");
499         /* Set up the window manager’s name */
500         xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root, atoms[_NET_SUPPORTING_WM_CHECK], WINDOW, 32, 1, &root);
501         xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root, atoms[_NET_WM_NAME], atoms[UTF8_STRING], 8, strlen("i3"), "i3");
502
503         xcb_get_numlock_mask(conn);
504
505         /* Grab the bound keys */
506         Binding *bind;
507         TAILQ_FOREACH(bind, &bindings, bindings) {
508                 LOG("Grabbing %d\n", bind->keycode);
509                 if (bind->mods & BIND_MODE_SWITCH)
510                         xcb_grab_key(conn, 0, root, 0, bind->keycode, XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_SYNC);
511                 else {
512                         /* Grab the key in all combinations */
513                         #define GRAB_KEY(modifier) xcb_grab_key(conn, 0, root, modifier, bind->keycode, \
514                                                                 XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_ASYNC)
515                         GRAB_KEY(bind->mods);
516                         GRAB_KEY(bind->mods | xcb_numlock_mask);
517                         GRAB_KEY(bind->mods | xcb_numlock_mask | XCB_MOD_MASK_LOCK);
518                 }
519         }
520
521         /* check for Xinerama */
522         LOG("Checking for Xinerama...\n");
523         initialize_xinerama(conn);
524
525         xcb_flush(conn);
526
527         manage_existing_windows(conn, &prophs, root);
528
529         /* Get pointer position to see on which screen we’re starting */
530         xcb_query_pointer_reply_t *reply;
531         if ((reply = xcb_query_pointer_reply(conn, xcb_query_pointer(conn, root), NULL)) == NULL) {
532                 LOG("Could not get pointer position\n");
533                 return 1;
534         }
535
536         i3Screen *screen = get_screen_containing(reply->root_x, reply->root_y);
537         if (screen == NULL) {
538                 LOG("ERROR: No screen at %d x %d\n", reply->root_x, reply->root_y);
539                 return 0;
540         }
541         if (screen->current_workspace != 0) {
542                 LOG("Ok, I need to go to the other workspace\n");
543                 c_ws = &workspaces[screen->current_workspace];
544         }
545
546         /* Enter xcb’s event handler */
547         xcb_event_wait_for_event_loop(&evenths);
548
549         /* not reached */
550         return 0;
551 }