]> git.sur5r.net Git - i3/i3/blob - i3bar/src/xcb.c
Merge branch 'master' into next
[i3/i3] / i3bar / src / xcb.c
1 /*
2  * vim:ts=4:sw=4:expandtab
3  *
4  * i3bar - an xcb-based status- and ws-bar for i3
5  * © 2010-2011 Axel Wagner and contributors (see also: LICENSE)
6  *
7  * xcb.c: Communicating with X
8  *
9  */
10 #include <xcb/xcb.h>
11 #include <xcb/xproto.h>
12 #include <xcb/xcb_atom.h>
13 #include <xcb/xcb_aux.h>
14
15 #ifdef XCB_COMPAT
16 #include "xcb_compat.h"
17 #endif
18
19 #include <stdio.h>
20 #include <stdlib.h>
21 #include <unistd.h>
22 #include <fcntl.h>
23 #include <string.h>
24 #include <i3/ipc.h>
25 #include <ev.h>
26 #include <errno.h>
27 #include <limits.h>
28 #include <err.h>
29
30 #include <X11/Xlib.h>
31 #include <X11/XKBlib.h>
32 #include <X11/extensions/XKB.h>
33
34 #include "common.h"
35 #include "libi3.h"
36
37 /* We save the Atoms in an easy to access array, indexed by an enum */
38 enum {
39     #define ATOM_DO(name) name,
40     #include "xcb_atoms.def"
41     NUM_ATOMS
42 };
43
44 xcb_intern_atom_cookie_t atom_cookies[NUM_ATOMS];
45 xcb_atom_t               atoms[NUM_ATOMS];
46
47 /* Variables, that are the same for all functions at all times */
48 xcb_connection_t *xcb_connection;
49 int              screen;
50 xcb_screen_t     *xcb_screen;
51 xcb_window_t     xcb_root;
52
53 /* This is needed for integration with libi3 */
54 xcb_connection_t *conn;
55
56 /* The font we'll use */
57 static i3Font font;
58
59 /* These are only relevant for XKB, which we only need for grabbing modifiers */
60 Display          *xkb_dpy;
61 int              xkb_event_base;
62 int              mod_pressed = 0;
63
64 /* Because the statusline is the same on all outputs, we have
65  * global buffer to render it on */
66 xcb_gcontext_t   statusline_ctx;
67 xcb_gcontext_t   statusline_clear;
68 xcb_pixmap_t     statusline_pm;
69 uint32_t         statusline_width;
70
71 /* Event-Watchers, to interact with the user */
72 ev_prepare *xcb_prep;
73 ev_check   *xcb_chk;
74 ev_io      *xcb_io;
75 ev_io      *xkb_io;
76
77 /* The parsed colors */
78 struct xcb_colors_t {
79     uint32_t bar_fg;
80     uint32_t bar_bg;
81     uint32_t active_ws_fg;
82     uint32_t active_ws_bg;
83     uint32_t active_ws_border;
84     uint32_t inactive_ws_fg;
85     uint32_t inactive_ws_bg;
86     uint32_t inactive_ws_border;
87     uint32_t urgent_ws_bg;
88     uint32_t urgent_ws_fg;
89     uint32_t urgent_ws_border;
90     uint32_t focus_ws_bg;
91     uint32_t focus_ws_fg;
92     uint32_t focus_ws_border;
93 };
94 struct xcb_colors_t colors;
95
96 /* We define xcb_request_failed as a macro to include the relevant line-number */
97 #define xcb_request_failed(cookie, err_msg) _xcb_request_failed(cookie, err_msg, __LINE__)
98 int _xcb_request_failed(xcb_void_cookie_t cookie, char *err_msg, int line) {
99     xcb_generic_error_t *err;
100     if ((err = xcb_request_check(xcb_connection, cookie)) != NULL) {
101         fprintf(stderr, "[%s:%d] ERROR: %s. X Error Code: %d\n", __FILE__, line, err_msg, err->error_code);
102         return err->error_code;
103     }
104     return 0;
105 }
106
107 /*
108  * Redraws the statusline to the buffer
109  *
110  */
111 void refresh_statusline() {
112     struct status_block *block;
113
114     uint32_t old_statusline_width = statusline_width;
115     statusline_width = 0;
116
117     /* Convert all blocks from UTF-8 to UCS-2 and predict the text width (in
118      * pixels). */
119     TAILQ_FOREACH(block, &statusline_head, blocks) {
120         block->ucs2_full_text = (xcb_char2b_t*)convert_utf8_to_ucs2(block->full_text, &(block->glyph_count_full_text));
121         block->width = predict_text_width((char*)block->ucs2_full_text, block->glyph_count_full_text, true);
122         /* If this is not the last block, add some pixels for a separator. */
123         if (TAILQ_NEXT(block, blocks) != NULL)
124             block->width += 9;
125         statusline_width += block->width;
126     }
127
128     /* If the statusline is bigger than our screen we need to make sure that
129      * the pixmap provides enough space, so re-allocate if the width grew */
130     if (statusline_width > xcb_screen->width_in_pixels &&
131         statusline_width > old_statusline_width)
132         realloc_sl_buffer();
133
134     /* Clear the statusline pixmap. */
135     xcb_rectangle_t rect = { 0, 0, xcb_screen->width_in_pixels, font.height };
136     xcb_poly_fill_rectangle(xcb_connection, statusline_pm, statusline_clear, 1, &rect);
137
138     /* Draw the text of each block. */
139     uint32_t x = 0;
140     TAILQ_FOREACH(block, &statusline_head, blocks) {
141         uint32_t colorpixel = (block->color ? get_colorpixel(block->color) : colors.bar_fg);
142         set_font_colors(statusline_ctx, colorpixel, colors.bar_bg);
143         draw_text((char*)block->ucs2_full_text, block->glyph_count_full_text,
144                   true, statusline_pm, statusline_ctx, x, 0, block->width);
145         x += block->width;
146
147         if (TAILQ_NEXT(block, blocks) != NULL) {
148             /* This is not the last block, draw a separator. */
149             set_font_colors(statusline_ctx, get_colorpixel("#666666"), colors.bar_bg);
150             xcb_poly_line(xcb_connection, XCB_COORD_MODE_ORIGIN, statusline_pm,
151                           statusline_ctx, 2,
152                           (xcb_point_t[]){ { x - 5, 2 }, { x - 5, font.height - 2 } });
153         }
154
155         FREE(block->ucs2_full_text);
156     }
157 }
158
159 /*
160  * Hides all bars (unmaps them)
161  *
162  */
163 void hide_bars() {
164     if (!config.hide_on_modifier) {
165         return;
166     }
167
168     i3_output *walk;
169     SLIST_FOREACH(walk, outputs, slist) {
170         if (!walk->active) {
171             continue;
172         }
173         xcb_unmap_window(xcb_connection, walk->bar);
174     }
175     stop_child();
176 }
177
178 /*
179  * Unhides all bars (maps them)
180  *
181  */
182 void unhide_bars() {
183     if (!config.hide_on_modifier) {
184         return;
185     }
186
187     i3_output           *walk;
188     xcb_void_cookie_t   cookie;
189     uint32_t            mask;
190     uint32_t            values[5];
191
192     cont_child();
193
194     SLIST_FOREACH(walk, outputs, slist) {
195         if (walk->bar == XCB_NONE) {
196             continue;
197         }
198         mask = XCB_CONFIG_WINDOW_X |
199                XCB_CONFIG_WINDOW_Y |
200                XCB_CONFIG_WINDOW_WIDTH |
201                XCB_CONFIG_WINDOW_HEIGHT |
202                XCB_CONFIG_WINDOW_STACK_MODE;
203         values[0] = walk->rect.x;
204         if (config.position == POS_TOP)
205             values[1] = walk->rect.y;
206         else values[1] = walk->rect.y + walk->rect.h - font.height - 6;
207         values[2] = walk->rect.w;
208         values[3] = font.height + 6;
209         values[4] = XCB_STACK_MODE_ABOVE;
210         DLOG("Reconfiguring Window for output %s to %d,%d\n", walk->name, values[0], values[1]);
211         cookie = xcb_configure_window_checked(xcb_connection,
212                                               walk->bar,
213                                               mask,
214                                               values);
215
216         if (xcb_request_failed(cookie, "Could not reconfigure window")) {
217             exit(EXIT_FAILURE);
218         }
219         xcb_map_window(xcb_connection, walk->bar);
220     }
221 }
222
223 /*
224  * Parse the colors into a format that we can use
225  *
226  */
227 void init_colors(const struct xcb_color_strings_t *new_colors) {
228 #define PARSE_COLOR(name, def) \
229     do { \
230         colors.name = get_colorpixel(new_colors->name ? new_colors->name : def); \
231     } while  (0)
232     PARSE_COLOR(bar_fg, "#FFFFFF");
233     PARSE_COLOR(bar_bg, "#000000");
234     PARSE_COLOR(active_ws_fg, "#FFFFFF");
235     PARSE_COLOR(active_ws_bg, "#333333");
236     PARSE_COLOR(active_ws_border, "#333333");
237     PARSE_COLOR(inactive_ws_fg, "#888888");
238     PARSE_COLOR(inactive_ws_bg, "#222222");
239     PARSE_COLOR(inactive_ws_border, "#333333");
240     PARSE_COLOR(urgent_ws_fg, "#FFFFFF");
241     PARSE_COLOR(urgent_ws_bg, "#900000");
242     PARSE_COLOR(urgent_ws_border, "#2f343a");
243     PARSE_COLOR(focus_ws_fg, "#FFFFFF");
244     PARSE_COLOR(focus_ws_bg, "#285577");
245     PARSE_COLOR(focus_ws_border, "#4c7899");
246 #undef PARSE_COLOR
247 }
248
249 /*
250  * Handle a button-press-event (i.e. a mouse click on one of our bars).
251  * We determine, whether the click occured on a ws-button or if the scroll-
252  * wheel was used and change the workspace appropriately
253  *
254  */
255 void handle_button(xcb_button_press_event_t *event) {
256     i3_ws *cur_ws;
257
258     /* Determine, which bar was clicked */
259     i3_output *walk;
260     xcb_window_t bar = event->event;
261     SLIST_FOREACH(walk, outputs, slist) {
262         if (walk->bar == bar) {
263             break;
264         }
265     }
266
267     if (walk == NULL) {
268         DLOG("Unknown Bar klicked!\n");
269         return;
270     }
271
272     /* TODO: Move this to extern get_ws_for_output() */
273     TAILQ_FOREACH(cur_ws, walk->workspaces, tailq) {
274         if (cur_ws->visible) {
275             break;
276         }
277     }
278
279     if (cur_ws == NULL) {
280         DLOG("No Workspace active?\n");
281         return;
282     }
283
284     int32_t x = event->event_x;
285
286     DLOG("Got Button %d\n", event->detail);
287
288     switch (event->detail) {
289         case 1:
290             /* Left Mousbutton. We determine, which button was clicked
291              * and set cur_ws accordingly */
292             TAILQ_FOREACH(cur_ws, walk->workspaces, tailq) {
293                 DLOG("x = %d\n", x);
294                 if (x >= 0 && x < cur_ws->name_width + 10) {
295                     break;
296                 }
297                 x -= cur_ws->name_width + 11;
298             }
299             if (cur_ws == NULL) {
300                 return;
301             }
302             break;
303         case 4:
304             /* Mouse wheel down. We select the next ws */
305             if (cur_ws == TAILQ_FIRST(walk->workspaces)) {
306                 cur_ws = TAILQ_LAST(walk->workspaces, ws_head);
307             } else {
308                 cur_ws = TAILQ_PREV(cur_ws, ws_head, tailq);
309             }
310             break;
311         case 5:
312             /* Mouse wheel up. We select the previos ws */
313             if (cur_ws == TAILQ_LAST(walk->workspaces, ws_head)) {
314                 cur_ws = TAILQ_FIRST(walk->workspaces);
315             } else {
316                 cur_ws = TAILQ_NEXT(cur_ws, tailq);
317             }
318             break;
319     }
320
321     /* To properly handle workspace names with double quotes in them, we need
322      * to escape the double quotes. Unfortunately, that’s rather ugly in C: We
323      * first count the number of double quotes, then we allocate a large enough
324      * buffer, then we copy character by character. */
325     int num_quotes = 0;
326     size_t namelen = 0;
327     for (char *walk = cur_ws->name; *walk != '\0'; walk++) {
328         if (*walk == '"')
329             num_quotes++;
330         /* While we’re looping through the name anyway, we can save one
331          * strlen(). */
332         namelen++;
333     }
334
335     const size_t len = namelen + strlen("workspace \"\"") + 1;
336     char *buffer = scalloc(len+num_quotes);
337     strncpy(buffer, "workspace \"", strlen("workspace \""));
338     int inpos, outpos;
339     for (inpos = 0, outpos = strlen("workspace \"");
340          inpos < namelen;
341          inpos++, outpos++) {
342         if (cur_ws->name[inpos] == '"') {
343             buffer[outpos] = '\\';
344             outpos++;
345         }
346         buffer[outpos] = cur_ws->name[inpos];
347     }
348     buffer[outpos] = '"';
349     i3_send_msg(I3_IPC_MESSAGE_TYPE_COMMAND, buffer);
350     free(buffer);
351 }
352
353 /*
354  * Configures the x coordinate of all trayclients. To be called after adding a
355  * new tray client or removing an old one.
356  *
357  */
358 static void configure_trayclients() {
359     trayclient *trayclient;
360     i3_output *output;
361     SLIST_FOREACH(output, outputs, slist) {
362         if (!output->active)
363             continue;
364
365         int clients = 0;
366         TAILQ_FOREACH_REVERSE(trayclient, output->trayclients, tc_head, tailq) {
367             if (!trayclient->mapped)
368                 continue;
369             clients++;
370
371             DLOG("Configuring tray window %08x to x=%d\n",
372                  trayclient->win, output->rect.w - (clients * (font.height + 2)));
373             uint32_t x = output->rect.w - (clients * (font.height + 2));
374             xcb_configure_window(xcb_connection,
375                                  trayclient->win,
376                                  XCB_CONFIG_WINDOW_X,
377                                  &x);
378         }
379     }
380 }
381
382 /*
383  * Handles ClientMessages (messages sent from another client directly to us).
384  *
385  * At the moment, only the tray window will receive client messages. All
386  * supported client messages currently are _NET_SYSTEM_TRAY_OPCODE.
387  *
388  */
389 static void handle_client_message(xcb_client_message_event_t* event) {
390     if (event->type == atoms[_NET_SYSTEM_TRAY_OPCODE] &&
391         event->format == 32) {
392         DLOG("_NET_SYSTEM_TRAY_OPCODE received\n");
393         /* event->data.data32[0] is the timestamp */
394         uint32_t op = event->data.data32[1];
395         uint32_t mask;
396         uint32_t values[2];
397         if (op == SYSTEM_TRAY_REQUEST_DOCK) {
398             xcb_window_t client = event->data.data32[2];
399
400             /* Listen for PropertyNotify events to get the most recent value of
401              * the XEMBED_MAPPED atom, also listen for UnmapNotify events */
402             mask = XCB_CW_EVENT_MASK;
403             values[0] = XCB_EVENT_MASK_PROPERTY_CHANGE |
404                         XCB_EVENT_MASK_STRUCTURE_NOTIFY;
405             xcb_change_window_attributes(xcb_connection,
406                                          client,
407                                          mask,
408                                          values);
409
410             /* Request the _XEMBED_INFO property. The XEMBED specification
411              * (which is referred by the tray specification) says this *has* to
412              * be set, but VLC does not set it… */
413             bool map_it = true;
414             int xe_version = 1;
415             xcb_get_property_cookie_t xembedc;
416             xcb_generic_error_t *error;
417             xembedc = xcb_get_property(xcb_connection,
418                                        0,
419                                        client,
420                                        atoms[_XEMBED_INFO],
421                                        XCB_GET_PROPERTY_TYPE_ANY,
422                                        0,
423                                        2 * 32);
424
425             xcb_get_property_reply_t *xembedr = xcb_get_property_reply(xcb_connection,
426                                                                        xembedc,
427                                                                        &error);
428             if (error != NULL) {
429                 ELOG("Error getting _XEMBED_INFO property: error_code %d\n",
430                      error->error_code);
431                 free(error);
432                 return;
433             }
434             if (xembedr != NULL && xembedr->length != 0) {
435                 DLOG("xembed format = %d, len = %d\n", xembedr->format, xembedr->length);
436                 uint32_t *xembed = xcb_get_property_value(xembedr);
437                 DLOG("xembed version = %d\n", xembed[0]);
438                 DLOG("xembed flags = %d\n", xembed[1]);
439                 map_it = ((xembed[1] & XEMBED_MAPPED) == XEMBED_MAPPED);
440                 xe_version = xembed[0];
441                 if (xe_version > 1)
442                     xe_version = 1;
443                 free(xembedr);
444             } else {
445                 ELOG("Window %08x violates the XEMBED protocol, _XEMBED_INFO not set\n", client);
446             }
447
448             DLOG("X window %08x requested docking\n", client);
449             i3_output *walk, *output = NULL;
450             SLIST_FOREACH(walk, outputs, slist) {
451                 if (!walk->active)
452                     continue;
453                 if (config.tray_output &&
454                     strcasecmp(walk->name, config.tray_output) != 0)
455                     continue;
456                 DLOG("using output %s\n", walk->name);
457                 output = walk;
458             }
459             if (output == NULL) {
460                 ELOG("No output found\n");
461                 return;
462             }
463             xcb_reparent_window(xcb_connection,
464                                 client,
465                                 output->bar,
466                                 output->rect.w - font.height - 2,
467                                 2);
468             /* We reconfigure the window to use a reasonable size. The systray
469              * specification explicitly says:
470              *   Tray icons may be assigned any size by the system tray, and
471              *   should do their best to cope with any size effectively
472              */
473             mask = XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT;
474             values[0] = font.height;
475             values[1] = font.height;
476             xcb_configure_window(xcb_connection,
477                                  client,
478                                  mask,
479                                  values);
480
481             /* send the XEMBED_EMBEDDED_NOTIFY message */
482             void *event = scalloc(32);
483             xcb_client_message_event_t *ev = event;
484             ev->response_type = XCB_CLIENT_MESSAGE;
485             ev->window = client;
486             ev->type = atoms[_XEMBED];
487             ev->format = 32;
488             ev->data.data32[0] = XCB_CURRENT_TIME;
489             ev->data.data32[1] = atoms[XEMBED_EMBEDDED_NOTIFY];
490             ev->data.data32[2] = output->bar;
491             ev->data.data32[3] = xe_version;
492             xcb_send_event(xcb_connection,
493                            0,
494                            client,
495                            XCB_EVENT_MASK_NO_EVENT,
496                            (char*)ev);
497             free(event);
498
499             /* Put the client inside the save set. Upon termination (whether
500              * killed or normal exit does not matter) of i3bar, these clients
501              * will be correctly reparented to their most closest living
502              * ancestor. Without this, tray icons might die when i3bar
503              * exits/crashes. */
504             xcb_change_save_set(xcb_connection, XCB_SET_MODE_INSERT, client);
505
506             if (map_it) {
507                 DLOG("Mapping dock client\n");
508                 xcb_map_window(xcb_connection, client);
509             } else {
510                 DLOG("Not mapping dock client yet\n");
511             }
512             trayclient *tc = smalloc(sizeof(trayclient));
513             tc->win = client;
514             tc->mapped = map_it;
515             tc->xe_version = xe_version;
516             TAILQ_INSERT_TAIL(output->trayclients, tc, tailq);
517
518             /* Trigger an update to copy the statusline text to the appropriate
519              * position */
520             configure_trayclients();
521             draw_bars();
522         }
523     }
524 }
525
526 /*
527  * Handles UnmapNotify events. These events happen when a tray window unmaps
528  * itself. We then update our data structure
529  *
530  */
531 static void handle_unmap_notify(xcb_unmap_notify_event_t* event) {
532     DLOG("UnmapNotify for window = %08x, event = %08x\n", event->window, event->event);
533
534     i3_output *walk;
535     SLIST_FOREACH(walk, outputs, slist) {
536         if (!walk->active)
537             continue;
538         DLOG("checking output %s\n", walk->name);
539         trayclient *trayclient;
540         TAILQ_FOREACH(trayclient, walk->trayclients, tailq) {
541             if (trayclient->win != event->window)
542                 continue;
543
544             DLOG("Removing tray client with window ID %08x\n", event->window);
545             TAILQ_REMOVE(walk->trayclients, trayclient, tailq);
546
547             /* Trigger an update, we now have more space for the statusline */
548             configure_trayclients();
549             draw_bars();
550             return;
551         }
552     }
553 }
554
555 /*
556  * Handle PropertyNotify messages. Currently only the _XEMBED_INFO property is
557  * handled, which tells us whether a dock client should be mapped or unmapped.
558  *
559  */
560 static void handle_property_notify(xcb_property_notify_event_t *event) {
561     DLOG("PropertyNotify\n");
562     if (event->atom == atoms[_XEMBED_INFO] &&
563         event->state == XCB_PROPERTY_NEW_VALUE) {
564         DLOG("xembed_info updated\n");
565         trayclient *trayclient = NULL, *walk;
566         i3_output *o_walk;
567         SLIST_FOREACH(o_walk, outputs, slist) {
568             if (!o_walk->active)
569                 continue;
570
571             TAILQ_FOREACH(walk, o_walk->trayclients, tailq) {
572                 if (walk->win != event->window)
573                     continue;
574                 trayclient = walk;
575                 break;
576             }
577
578             if (trayclient)
579                 break;
580         }
581         if (!trayclient) {
582             ELOG("PropertyNotify received for unknown window %08x\n",
583                  event->window);
584             return;
585         }
586         xcb_get_property_cookie_t xembedc;
587         xembedc = xcb_get_property_unchecked(xcb_connection,
588                                              0,
589                                              trayclient->win,
590                                              atoms[_XEMBED_INFO],
591                                              XCB_GET_PROPERTY_TYPE_ANY,
592                                              0,
593                                              2 * 32);
594
595         xcb_get_property_reply_t *xembedr = xcb_get_property_reply(xcb_connection,
596                                                                    xembedc,
597                                                                    NULL);
598         if (xembedr == NULL || xembedr->length == 0) {
599             DLOG("xembed_info unset\n");
600             return;
601         }
602
603         DLOG("xembed format = %d, len = %d\n", xembedr->format, xembedr->length);
604         uint32_t *xembed = xcb_get_property_value(xembedr);
605         DLOG("xembed version = %d\n", xembed[0]);
606         DLOG("xembed flags = %d\n", xembed[1]);
607         bool map_it = ((xembed[1] & XEMBED_MAPPED) == XEMBED_MAPPED);
608         DLOG("map-state now %d\n", map_it);
609         if (trayclient->mapped && !map_it) {
610             /* need to unmap the window */
611             xcb_unmap_window(xcb_connection, trayclient->win);
612             trayclient->mapped = map_it;
613             configure_trayclients();
614             draw_bars();
615         } else if (!trayclient->mapped && map_it) {
616             /* need to map the window */
617             xcb_map_window(xcb_connection, trayclient->win);
618             trayclient->mapped = map_it;
619             configure_trayclients();
620             draw_bars();
621         }
622         free(xembedr);
623     }
624 }
625
626 /*
627  * Handle ConfigureRequests by denying them and sending the client a
628  * ConfigureNotify with its actual size.
629  *
630  */
631 static void handle_configure_request(xcb_configure_request_event_t *event) {
632     DLOG("ConfigureRequest for window = %08x\n", event->window);
633
634     trayclient *trayclient;
635     i3_output *output;
636     SLIST_FOREACH(output, outputs, slist) {
637         if (!output->active)
638             continue;
639
640         int clients = 0;
641         TAILQ_FOREACH_REVERSE(trayclient, output->trayclients, tc_head, tailq) {
642             if (!trayclient->mapped)
643                 continue;
644             clients++;
645
646             if (trayclient->win != event->window)
647                 continue;
648
649             xcb_rectangle_t rect;
650             rect.x = output->rect.w - (clients * (font.height + 2));
651             rect.y = 2;
652             rect.width = font.height;
653             rect.height = font.height;
654
655             DLOG("This is a tray window. x = %d\n", rect.x);
656             fake_configure_notify(xcb_connection, rect, event->window, 0);
657             return;
658         }
659     }
660
661     DLOG("WARNING: Could not find corresponding tray window.\n");
662 }
663
664 /*
665  * This function is called immediately before the main loop locks. We flush xcb
666  * then (and only then)
667  *
668  */
669 void xcb_prep_cb(struct ev_loop *loop, ev_prepare *watcher, int revents) {
670     xcb_flush(xcb_connection);
671 }
672
673 /*
674  * This function is called immediately after the main loop locks, so when one
675  * of the watchers registered an event.
676  * We check whether an X-Event arrived and handle it.
677  *
678  */
679 void xcb_chk_cb(struct ev_loop *loop, ev_check *watcher, int revents) {
680     xcb_generic_event_t *event;
681
682     if (xcb_connection_has_error(xcb_connection)) {
683         ELOG("X11 connection was closed unexpectedly - maybe your X server terminated / crashed?\n");
684         exit(1);
685     }
686
687     while ((event = xcb_poll_for_event(xcb_connection)) != NULL) {
688         switch (event->response_type & ~0x80) {
689             case XCB_EXPOSE:
690                 /* Expose-events happen, when the window needs to be redrawn */
691                 redraw_bars();
692                 break;
693             case XCB_BUTTON_PRESS:
694                 /* Button-press-events are mouse-buttons clicked on one of our bars */
695                 handle_button((xcb_button_press_event_t*) event);
696                 break;
697             case XCB_CLIENT_MESSAGE:
698                 /* Client messages are used for client-to-client communication, for
699                  * example system tray widgets talk to us directly via client messages. */
700                 handle_client_message((xcb_client_message_event_t*) event);
701                 break;
702             case XCB_UNMAP_NOTIFY:
703             case XCB_DESTROY_NOTIFY:
704                 /* UnmapNotifies are received when a tray window unmaps itself */
705                 handle_unmap_notify((xcb_unmap_notify_event_t*) event);
706                 break;
707             case XCB_PROPERTY_NOTIFY:
708                 /* PropertyNotify */
709                 handle_property_notify((xcb_property_notify_event_t*) event);
710                 break;
711             case XCB_CONFIGURE_REQUEST:
712                 /* ConfigureRequest, sent by a tray child */
713                 handle_configure_request((xcb_configure_request_event_t*) event);
714                 break;
715         }
716         free(event);
717     }
718 }
719
720 /*
721  * Dummy Callback. We only need this, so that the Prepare- and Check-Watchers
722  * are triggered
723  *
724  */
725 void xcb_io_cb(struct ev_loop *loop, ev_io *watcher, int revents) {
726 }
727
728 /*
729  * We need to bind to the modifier per XKB. Sadly, XCB does not implement this
730  *
731  */
732 void xkb_io_cb(struct ev_loop *loop, ev_io *watcher, int revents) {
733     XkbEvent ev;
734     int modstate = 0;
735
736     DLOG("Got XKB-Event!\n");
737
738     while (XPending(xkb_dpy)) {
739         XNextEvent(xkb_dpy, (XEvent*)&ev);
740
741         if (ev.type != xkb_event_base) {
742             ELOG("No Xkb-Event!\n");
743             continue;
744         }
745
746         if (ev.any.xkb_type != XkbStateNotify) {
747             ELOG("No State Notify!\n");
748             continue;
749         }
750
751         unsigned int mods = ev.state.mods;
752         modstate = mods & config.modifier;
753     }
754
755 #define DLOGMOD(modmask, status, barfunc) \
756     do { \
757         switch (modmask) { \
758             case ShiftMask: \
759                 DLOG("ShiftMask got " #status "!\n"); \
760                 break; \
761             case ControlMask: \
762                 DLOG("ControlMask got " #status "!\n"); \
763                 break; \
764             case Mod1Mask: \
765                 DLOG("Mod1Mask got " #status "!\n"); \
766                 break; \
767             case Mod2Mask: \
768                 DLOG("Mod2Mask got " #status "!\n"); \
769                 break; \
770             case Mod3Mask: \
771                 DLOG("Mod3Mask got " #status "!\n"); \
772                 break; \
773             case Mod4Mask: \
774                 DLOG("Mod4Mask got " #status "!\n"); \
775                 break; \
776             case Mod5Mask: \
777                 DLOG("Mod5Mask got " #status "!\n"); \
778                 break; \
779         } \
780         barfunc(); \
781     } while (0)
782
783     if (modstate != mod_pressed) {
784         if (modstate == 0) {
785             DLOGMOD(config.modifier, released, hide_bars);
786         } else {
787             DLOGMOD(config.modifier, pressed, unhide_bars);
788         }
789         mod_pressed = modstate;
790     }
791
792 #undef DLOGMOD
793 }
794
795 /*
796  * Early initialization of the connection to X11: Everything which does not
797  * depend on 'config'.
798  *
799  */
800 char *init_xcb_early() {
801     /* FIXME: xcb_connect leaks Memory */
802     xcb_connection = xcb_connect(NULL, &screen);
803     if (xcb_connection_has_error(xcb_connection)) {
804         ELOG("Cannot open display\n");
805         exit(EXIT_FAILURE);
806     }
807     conn = xcb_connection;
808     DLOG("Connected to xcb\n");
809
810     /* We have to request the atoms we need */
811     #define ATOM_DO(name) atom_cookies[name] = xcb_intern_atom(xcb_connection, 0, strlen(#name), #name);
812     #include "xcb_atoms.def"
813
814     xcb_screen = xcb_aux_get_screen(xcb_connection, screen);
815     xcb_root = xcb_screen->root;
816
817     /* We draw the statusline to a seperate pixmap, because it looks the same on all bars and
818      * this way, we can choose to crop it */
819     uint32_t mask = XCB_GC_FOREGROUND;
820     uint32_t vals[] = { colors.bar_bg, colors.bar_bg };
821
822     statusline_clear = xcb_generate_id(xcb_connection);
823     xcb_void_cookie_t clear_ctx_cookie = xcb_create_gc_checked(xcb_connection,
824                                                                statusline_clear,
825                                                                xcb_root,
826                                                                mask,
827                                                                vals);
828
829     statusline_ctx = xcb_generate_id(xcb_connection);
830     xcb_void_cookie_t sl_ctx_cookie = xcb_create_gc_checked(xcb_connection,
831                                                             statusline_ctx,
832                                                             xcb_root,
833                                                             0,
834                                                             NULL);
835
836     statusline_pm = xcb_generate_id(xcb_connection);
837     xcb_void_cookie_t sl_pm_cookie = xcb_create_pixmap_checked(xcb_connection,
838                                                                xcb_screen->root_depth,
839                                                                statusline_pm,
840                                                                xcb_root,
841                                                                xcb_screen->width_in_pixels,
842                                                                xcb_screen->height_in_pixels);
843
844
845     /* The various Watchers to communicate with xcb */
846     xcb_io = smalloc(sizeof(ev_io));
847     xcb_prep = smalloc(sizeof(ev_prepare));
848     xcb_chk = smalloc(sizeof(ev_check));
849
850     ev_io_init(xcb_io, &xcb_io_cb, xcb_get_file_descriptor(xcb_connection), EV_READ);
851     ev_prepare_init(xcb_prep, &xcb_prep_cb);
852     ev_check_init(xcb_chk, &xcb_chk_cb);
853
854     ev_io_start(main_loop, xcb_io);
855     ev_prepare_start(main_loop, xcb_prep);
856     ev_check_start(main_loop, xcb_chk);
857
858     /* Now we get the atoms and save them in a nice data structure */
859     get_atoms();
860
861     xcb_get_property_cookie_t path_cookie;
862     path_cookie = xcb_get_property_unchecked(xcb_connection,
863                                    0,
864                                    xcb_root,
865                                    atoms[I3_SOCKET_PATH],
866                                    XCB_GET_PROPERTY_TYPE_ANY,
867                                    0, PATH_MAX);
868
869     /* We check, if i3 set its socket-path */
870     xcb_get_property_reply_t *path_reply = xcb_get_property_reply(xcb_connection,
871                                                                   path_cookie,
872                                                                   NULL);
873     char *path = NULL;
874     if (path_reply) {
875         int len = xcb_get_property_value_length(path_reply);
876         if (len != 0) {
877             path = strndup(xcb_get_property_value(path_reply), len);
878         }
879     }
880
881
882     if (xcb_request_failed(sl_pm_cookie, "Could not allocate statusline-buffer") ||
883         xcb_request_failed(clear_ctx_cookie, "Could not allocate statusline-buffer-clearcontext") ||
884         xcb_request_failed(sl_ctx_cookie, "Could not allocate statusline-buffer-context")) {
885         exit(EXIT_FAILURE);
886     }
887
888     return path;
889 }
890
891 /*
892  * Initialization which depends on 'config' being usable. Called after the
893  * configuration has arrived.
894  *
895  */
896 void init_xcb_late(char *fontname) {
897     if (fontname == NULL)
898         fontname = "-misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1";
899
900     /* Load the font */
901     font = load_font(fontname, true);
902     set_font(&font);
903     DLOG("Calculated Font-height: %d\n", font.height);
904
905     xcb_flush(xcb_connection);
906
907     /* To grab modifiers without blocking other applications from receiving key-events
908      * involving that modifier, we sadly have to use xkb which is not yet fully supported
909      * in xcb */
910     if (config.hide_on_modifier) {
911         int xkb_major, xkb_minor, xkb_errbase, xkb_err;
912         xkb_major = XkbMajorVersion;
913         xkb_minor = XkbMinorVersion;
914
915         xkb_dpy = XkbOpenDisplay(NULL,
916                                  &xkb_event_base,
917                                  &xkb_errbase,
918                                  &xkb_major,
919                                  &xkb_minor,
920                                  &xkb_err);
921
922         if (xkb_dpy == NULL) {
923             ELOG("No XKB!\n");
924             exit(EXIT_FAILURE);
925         }
926
927         if (fcntl(ConnectionNumber(xkb_dpy), F_SETFD, FD_CLOEXEC) == -1) {
928             ELOG("Could not set FD_CLOEXEC on xkbdpy: %s\n", strerror(errno));
929             exit(EXIT_FAILURE);
930         }
931
932         int i1;
933         if (!XkbQueryExtension(xkb_dpy, &i1, &xkb_event_base, &xkb_errbase, &xkb_major, &xkb_minor)) {
934             ELOG("XKB not supported by X-server!\n");
935             exit(EXIT_FAILURE);
936         }
937
938         if (!XkbSelectEvents(xkb_dpy, XkbUseCoreKbd, XkbStateNotifyMask, XkbStateNotifyMask)) {
939             ELOG("Could not grab Key!\n");
940             exit(EXIT_FAILURE);
941         }
942
943         xkb_io = smalloc(sizeof(ev_io));
944         ev_io_init(xkb_io, &xkb_io_cb, ConnectionNumber(xkb_dpy), EV_READ);
945         ev_io_start(main_loop, xkb_io);
946         XFlush(xkb_dpy);
947     }
948 }
949
950 /*
951  * Initializes tray support by requesting the appropriate _NET_SYSTEM_TRAY atom
952  * for the X11 display we are running on, then acquiring the selection for this
953  * atom. Afterwards, tray clients will send ClientMessages to our window.
954  *
955  */
956 void init_tray() {
957     DLOG("Initializing system tray functionality\n");
958     /* request the tray manager atom for the X11 display we are running on */
959     char atomname[strlen("_NET_SYSTEM_TRAY_S") + 11];
960     snprintf(atomname, strlen("_NET_SYSTEM_TRAY_S") + 11, "_NET_SYSTEM_TRAY_S%d", screen);
961     xcb_intern_atom_cookie_t tray_cookie;
962     xcb_intern_atom_reply_t *tray_reply;
963     tray_cookie = xcb_intern_atom(xcb_connection, 0, strlen(atomname), atomname);
964
965     /* tray support: we need a window to own the selection */
966     xcb_window_t selwin = xcb_generate_id(xcb_connection);
967     uint32_t selmask = XCB_CW_OVERRIDE_REDIRECT;
968     uint32_t selval[] = { 1 };
969     xcb_create_window(xcb_connection,
970                       xcb_screen->root_depth,
971                       selwin,
972                       xcb_root,
973                       -1, -1,
974                       1, 1,
975                       1,
976                       XCB_WINDOW_CLASS_INPUT_OUTPUT,
977                       xcb_screen->root_visual,
978                       selmask,
979                       selval);
980
981     uint32_t orientation = _NET_SYSTEM_TRAY_ORIENTATION_HORZ;
982     /* set the atoms */
983     xcb_change_property(xcb_connection,
984                         XCB_PROP_MODE_REPLACE,
985                         selwin,
986                         atoms[_NET_SYSTEM_TRAY_ORIENTATION],
987                         XCB_ATOM_CARDINAL,
988                         32,
989                         1,
990                         &orientation);
991
992     if (!(tray_reply = xcb_intern_atom_reply(xcb_connection, tray_cookie, NULL))) {
993         ELOG("Could not get atom %s\n", atomname);
994         exit(EXIT_FAILURE);
995     }
996
997     xcb_set_selection_owner(xcb_connection,
998                             selwin,
999                             tray_reply->atom,
1000                             XCB_CURRENT_TIME);
1001
1002     /* Verify that we have the selection */
1003     xcb_get_selection_owner_cookie_t selcookie;
1004     xcb_get_selection_owner_reply_t *selreply;
1005
1006     selcookie = xcb_get_selection_owner(xcb_connection, tray_reply->atom);
1007     if (!(selreply = xcb_get_selection_owner_reply(xcb_connection, selcookie, NULL))) {
1008         ELOG("Could not get selection owner for %s\n", atomname);
1009         exit(EXIT_FAILURE);
1010     }
1011
1012     if (selreply->owner != selwin) {
1013         ELOG("Could not set the %s selection. " \
1014              "Maybe another tray is already running?\n", atomname);
1015         /* NOTE that this error is not fatal. We just can’t provide tray
1016          * functionality */
1017         free(selreply);
1018         return;
1019     }
1020
1021     /* Inform clients waiting for a new _NET_SYSTEM_TRAY that we are here */
1022     void *event = scalloc(32);
1023     xcb_client_message_event_t *ev = event;
1024     ev->response_type = XCB_CLIENT_MESSAGE;
1025     ev->window = xcb_root;
1026     ev->type = atoms[MANAGER];
1027     ev->format = 32;
1028     ev->data.data32[0] = XCB_CURRENT_TIME;
1029     ev->data.data32[1] = tray_reply->atom;
1030     ev->data.data32[2] = selwin;
1031     xcb_send_event(xcb_connection,
1032                    0,
1033                    xcb_root,
1034                    XCB_EVENT_MASK_STRUCTURE_NOTIFY,
1035                    (char*)ev);
1036     free(event);
1037     free(tray_reply);
1038 }
1039
1040 /*
1041  * Cleanup the xcb-stuff.
1042  * Called once, before the program terminates.
1043  *
1044  */
1045 void clean_xcb() {
1046     i3_output *o_walk;
1047     trayclient *trayclient;
1048     free_workspaces();
1049     SLIST_FOREACH(o_walk, outputs, slist) {
1050         TAILQ_FOREACH(trayclient, o_walk->trayclients, tailq) {
1051             /* Unmap, then reparent (to root) the tray client windows */
1052             xcb_unmap_window(xcb_connection, trayclient->win);
1053             xcb_reparent_window(xcb_connection,
1054                                 trayclient->win,
1055                                 xcb_root,
1056                                 0,
1057                                 0);
1058         }
1059         destroy_window(o_walk);
1060         FREE(o_walk->trayclients);
1061         FREE(o_walk->workspaces);
1062         FREE(o_walk->name);
1063     }
1064     FREE_SLIST(outputs, i3_output);
1065     FREE(outputs);
1066
1067     xcb_flush(xcb_connection);
1068     xcb_disconnect(xcb_connection);
1069
1070     ev_check_stop(main_loop, xcb_chk);
1071     ev_prepare_stop(main_loop, xcb_prep);
1072     ev_io_stop(main_loop, xcb_io);
1073
1074     FREE(xcb_chk);
1075     FREE(xcb_prep);
1076     FREE(xcb_io);
1077 }
1078
1079 /*
1080  * Get the earlier requested atoms and save them in the prepared data structure
1081  *
1082  */
1083 void get_atoms() {
1084     xcb_intern_atom_reply_t *reply;
1085     #define ATOM_DO(name) reply = xcb_intern_atom_reply(xcb_connection, atom_cookies[name], NULL); \
1086         if (reply == NULL) { \
1087             ELOG("Could not get atom %s\n", #name); \
1088             exit(EXIT_FAILURE); \
1089         } \
1090         atoms[name] = reply->atom; \
1091         free(reply);
1092
1093     #include "xcb_atoms.def"
1094     DLOG("Got Atoms\n");
1095 }
1096
1097 /*
1098  * Destroy the bar of the specified output
1099  *
1100  */
1101 void destroy_window(i3_output *output) {
1102     if (output == NULL) {
1103         return;
1104     }
1105     if (output->bar == XCB_NONE) {
1106         return;
1107     }
1108     xcb_destroy_window(xcb_connection, output->bar);
1109     output->bar = XCB_NONE;
1110 }
1111
1112 /*
1113  * Reallocate the statusline-buffer
1114  *
1115  */
1116 void realloc_sl_buffer() {
1117     DLOG("Re-allocating statusline-buffer, statusline_width = %d, xcb_screen->width_in_pixels = %d\n",
1118          statusline_width, xcb_screen->width_in_pixels);
1119     xcb_free_pixmap(xcb_connection, statusline_pm);
1120     statusline_pm = xcb_generate_id(xcb_connection);
1121     xcb_void_cookie_t sl_pm_cookie = xcb_create_pixmap_checked(xcb_connection,
1122                                                                xcb_screen->root_depth,
1123                                                                statusline_pm,
1124                                                                xcb_root,
1125                                                                MAX(xcb_screen->width_in_pixels, statusline_width),
1126                                                                xcb_screen->height_in_pixels);
1127
1128     uint32_t mask = XCB_GC_FOREGROUND;
1129     uint32_t vals[2] = { colors.bar_bg, colors.bar_bg };
1130     xcb_free_gc(xcb_connection, statusline_clear);
1131     statusline_clear = xcb_generate_id(xcb_connection);
1132     xcb_void_cookie_t clear_ctx_cookie = xcb_create_gc_checked(xcb_connection,
1133                                                                statusline_clear,
1134                                                                xcb_root,
1135                                                                mask,
1136                                                                vals);
1137
1138     mask |= XCB_GC_BACKGROUND;
1139     vals[0] = colors.bar_fg;
1140     statusline_ctx = xcb_generate_id(xcb_connection);
1141     xcb_free_gc(xcb_connection, statusline_ctx);
1142     xcb_void_cookie_t sl_ctx_cookie = xcb_create_gc_checked(xcb_connection,
1143                                                             statusline_ctx,
1144                                                             xcb_root,
1145                                                             mask,
1146                                                             vals);
1147
1148     if (xcb_request_failed(sl_pm_cookie, "Could not allocate statusline-buffer") ||
1149         xcb_request_failed(clear_ctx_cookie, "Could not allocate statusline-buffer-clearcontext") ||
1150         xcb_request_failed(sl_ctx_cookie, "Could not allocate statusline-buffer-context")) {
1151         exit(EXIT_FAILURE);
1152     }
1153
1154 }
1155
1156 /*
1157  * Reconfigure all bars and create new bars for recently activated outputs
1158  *
1159  */
1160 void reconfig_windows() {
1161     uint32_t mask;
1162     uint32_t values[5];
1163     static bool tray_configured = false;
1164
1165     i3_output *walk;
1166     SLIST_FOREACH(walk, outputs, slist) {
1167         if (!walk->active) {
1168             /* If an output is not active, we destroy its bar */
1169             /* FIXME: Maybe we rather want to unmap? */
1170             DLOG("Destroying window for output %s\n", walk->name);
1171             destroy_window(walk);
1172             continue;
1173         }
1174         if (walk->bar == XCB_NONE) {
1175             DLOG("Creating Window for output %s\n", walk->name);
1176
1177             walk->bar = xcb_generate_id(xcb_connection);
1178             walk->buffer = xcb_generate_id(xcb_connection);
1179             mask = XCB_CW_BACK_PIXEL | XCB_CW_OVERRIDE_REDIRECT | XCB_CW_EVENT_MASK;
1180             /* Black background */
1181             values[0] = colors.bar_bg;
1182             /* If hide_on_modifier is set, i3 is not supposed to manage our bar-windows */
1183             values[1] = config.hide_on_modifier;
1184             /* We enable the following EventMask fields:
1185              * EXPOSURE, to get expose events (we have to re-draw then)
1186              * SUBSTRUCTURE_REDIRECT, to get ConfigureRequests when the tray
1187              *                        child windows use ConfigureWindow
1188              * BUTTON_PRESS, to handle clicks on the workspace buttons
1189              * */
1190             values[2] = XCB_EVENT_MASK_EXPOSURE |
1191                         XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT;
1192             if (!config.disable_ws) {
1193                 values[2] |= XCB_EVENT_MASK_BUTTON_PRESS;
1194             }
1195             xcb_void_cookie_t win_cookie = xcb_create_window_checked(xcb_connection,
1196                                                                      xcb_screen->root_depth,
1197                                                                      walk->bar,
1198                                                                      xcb_root,
1199                                                                      walk->rect.x, walk->rect.y + walk->rect.h - font.height - 6,
1200                                                                      walk->rect.w, font.height + 6,
1201                                                                      1,
1202                                                                      XCB_WINDOW_CLASS_INPUT_OUTPUT,
1203                                                                      xcb_screen->root_visual,
1204                                                                      mask,
1205                                                                      values);
1206
1207             /* The double-buffer we use to render stuff off-screen */
1208             xcb_void_cookie_t pm_cookie = xcb_create_pixmap_checked(xcb_connection,
1209                                                                     xcb_screen->root_depth,
1210                                                                     walk->buffer,
1211                                                                     walk->bar,
1212                                                                     walk->rect.w,
1213                                                                     walk->rect.h);
1214
1215             /* Set the WM_CLASS and WM_NAME (we don't need UTF-8) atoms */
1216             xcb_void_cookie_t class_cookie;
1217             class_cookie = xcb_change_property(xcb_connection,
1218                                                XCB_PROP_MODE_REPLACE,
1219                                                walk->bar,
1220                                                XCB_ATOM_WM_CLASS,
1221                                                XCB_ATOM_STRING,
1222                                                8,
1223                                                (strlen("i3bar") + 1) * 2,
1224                                                "i3bar\0i3bar\0");
1225
1226             char *name;
1227             if (asprintf(&name, "i3bar for output %s", walk->name) == -1)
1228                 err(EXIT_FAILURE, "asprintf()");
1229             xcb_void_cookie_t name_cookie;
1230             name_cookie = xcb_change_property(xcb_connection,
1231                                               XCB_PROP_MODE_REPLACE,
1232                                               walk->bar,
1233                                               XCB_ATOM_WM_NAME,
1234                                               XCB_ATOM_STRING,
1235                                               8,
1236                                               strlen(name),
1237                                               name);
1238             free(name);
1239
1240             /* We want dock-windows (for now). When override_redirect is set, i3 is ignoring
1241              * this one */
1242             xcb_void_cookie_t dock_cookie = xcb_change_property(xcb_connection,
1243                                                                 XCB_PROP_MODE_REPLACE,
1244                                                                 walk->bar,
1245                                                                 atoms[_NET_WM_WINDOW_TYPE],
1246                                                                 XCB_ATOM_ATOM,
1247                                                                 32,
1248                                                                 1,
1249                                                                 (unsigned char*) &atoms[_NET_WM_WINDOW_TYPE_DOCK]);
1250
1251             /* We need to tell i3, where to reserve space for i3bar */
1252             /* left, right, top, bottom, left_start_y, left_end_y,
1253              * right_start_y, right_end_y, top_start_x, top_end_x, bottom_start_x,
1254              * bottom_end_x */
1255             /* A local struct to save the strut_partial property */
1256             struct {
1257                 uint32_t left;
1258                 uint32_t right;
1259                 uint32_t top;
1260                 uint32_t bottom;
1261                 uint32_t left_start_y;
1262                 uint32_t left_end_y;
1263                 uint32_t right_start_y;
1264                 uint32_t right_end_y;
1265                 uint32_t top_start_x;
1266                 uint32_t top_end_x;
1267                 uint32_t bottom_start_x;
1268                 uint32_t bottom_end_x;
1269             } __attribute__((__packed__)) strut_partial = {0,};
1270             switch (config.position) {
1271                 case POS_NONE:
1272                     break;
1273                 case POS_TOP:
1274                     strut_partial.top = font.height + 6;
1275                     strut_partial.top_start_x = walk->rect.x;
1276                     strut_partial.top_end_x = walk->rect.x + walk->rect.w;
1277                     break;
1278                 case POS_BOT:
1279                     strut_partial.bottom = font.height + 6;
1280                     strut_partial.bottom_start_x = walk->rect.x;
1281                     strut_partial.bottom_end_x = walk->rect.x + walk->rect.w;
1282                     break;
1283             }
1284             xcb_void_cookie_t strut_cookie = xcb_change_property(xcb_connection,
1285                                                                  XCB_PROP_MODE_REPLACE,
1286                                                                  walk->bar,
1287                                                                  atoms[_NET_WM_STRUT_PARTIAL],
1288                                                                  XCB_ATOM_CARDINAL,
1289                                                                  32,
1290                                                                  12,
1291                                                                  &strut_partial);
1292
1293             /* We also want a graphics-context for the bars (it defines the properties
1294              * with which we draw to them) */
1295             walk->bargc = xcb_generate_id(xcb_connection);
1296             xcb_void_cookie_t gc_cookie = xcb_create_gc_checked(xcb_connection,
1297                                                                 walk->bargc,
1298                                                                 walk->bar,
1299                                                                 0,
1300                                                                 NULL);
1301
1302             /* We finally map the bar (display it on screen), unless the modifier-switch is on */
1303             xcb_void_cookie_t map_cookie;
1304             if (!config.hide_on_modifier) {
1305                 map_cookie = xcb_map_window_checked(xcb_connection, walk->bar);
1306             }
1307
1308             if (xcb_request_failed(win_cookie,   "Could not create window") ||
1309                 xcb_request_failed(pm_cookie,    "Could not create pixmap") ||
1310                 xcb_request_failed(dock_cookie,  "Could not set dock mode") ||
1311                 xcb_request_failed(class_cookie, "Could not set WM_CLASS")  ||
1312                 xcb_request_failed(name_cookie,  "Could not set WM_NAME")   ||
1313                 xcb_request_failed(strut_cookie, "Could not set strut")     ||
1314                 xcb_request_failed(gc_cookie,    "Could not create graphical context") ||
1315                 (!config.hide_on_modifier && xcb_request_failed(map_cookie, "Could not map window"))) {
1316                 exit(EXIT_FAILURE);
1317             }
1318
1319             if (!tray_configured &&
1320                 (!config.tray_output ||
1321                  strcasecmp("none", config.tray_output) != 0)) {
1322                 init_tray();
1323                 tray_configured = true;
1324             }
1325         } else {
1326             /* We already have a bar, so we just reconfigure it */
1327             mask = XCB_CONFIG_WINDOW_X |
1328                    XCB_CONFIG_WINDOW_Y |
1329                    XCB_CONFIG_WINDOW_WIDTH |
1330                    XCB_CONFIG_WINDOW_HEIGHT |
1331                    XCB_CONFIG_WINDOW_STACK_MODE;
1332             values[0] = walk->rect.x;
1333             values[1] = walk->rect.y + walk->rect.h - font.height - 6;
1334             values[2] = walk->rect.w;
1335             values[3] = font.height + 6;
1336             values[4] = XCB_STACK_MODE_ABOVE;
1337
1338             DLOG("Destroying buffer for output %s", walk->name);
1339             xcb_free_pixmap(xcb_connection, walk->buffer);
1340
1341             DLOG("Reconfiguring Window for output %s to %d,%d\n", walk->name, values[0], values[1]);
1342             xcb_void_cookie_t cfg_cookie = xcb_configure_window_checked(xcb_connection,
1343                                                                         walk->bar,
1344                                                                         mask,
1345                                                                         values);
1346
1347             DLOG("Recreating buffer for output %s", walk->name);
1348             xcb_void_cookie_t pm_cookie = xcb_create_pixmap_checked(xcb_connection,
1349                                                                     xcb_screen->root_depth,
1350                                                                     walk->buffer,
1351                                                                     walk->bar,
1352                                                                     walk->rect.w,
1353                                                                     walk->rect.h);
1354
1355             if (xcb_request_failed(cfg_cookie, "Could not reconfigure window")) {
1356                 exit(EXIT_FAILURE);
1357             }
1358             if (xcb_request_failed(pm_cookie,  "Could not create pixmap")) {
1359                 exit(EXIT_FAILURE);
1360             }
1361         }
1362     }
1363 }
1364
1365 /*
1366  * Render the bars, with buttons and statusline
1367  *
1368  */
1369 void draw_bars() {
1370     DLOG("Drawing Bars...\n");
1371     int i = 0;
1372
1373     refresh_statusline();
1374
1375     i3_output *outputs_walk;
1376     SLIST_FOREACH(outputs_walk, outputs, slist) {
1377         if (!outputs_walk->active) {
1378             DLOG("Output %s inactive, skipping...\n", outputs_walk->name);
1379             continue;
1380         }
1381         if (outputs_walk->bar == XCB_NONE) {
1382             /* Oh shit, an active output without an own bar. Create it now! */
1383             reconfig_windows();
1384         }
1385         /* First things first: clear the backbuffer */
1386         uint32_t color = colors.bar_bg;
1387         xcb_change_gc(xcb_connection,
1388                       outputs_walk->bargc,
1389                       XCB_GC_FOREGROUND,
1390                       &color);
1391         xcb_rectangle_t rect = { 0, 0, outputs_walk->rect.w, font.height + 6 };
1392         xcb_poly_fill_rectangle(xcb_connection,
1393                                 outputs_walk->buffer,
1394                                 outputs_walk->bargc,
1395                                 1,
1396                                 &rect);
1397
1398         if (!TAILQ_EMPTY(&statusline_head)) {
1399             DLOG("Printing statusline!\n");
1400
1401             /* Luckily we already prepared a seperate pixmap containing the rendered
1402              * statusline, we just have to copy the relevant parts to the relevant
1403              * position */
1404             trayclient *trayclient;
1405             int traypx = 0;
1406             TAILQ_FOREACH(trayclient, outputs_walk->trayclients, tailq) {
1407                 if (!trayclient->mapped)
1408                     continue;
1409                 /* We assume the tray icons are quadratic (we use the font
1410                  * *height* as *width* of the icons) because we configured them
1411                  * like this. */
1412                 traypx += font.height + 2;
1413             }
1414             /* Add 2px of padding if there are any tray icons */
1415             if (traypx > 0)
1416                 traypx += 2;
1417             xcb_copy_area(xcb_connection,
1418                           statusline_pm,
1419                           outputs_walk->buffer,
1420                           outputs_walk->bargc,
1421                           MAX(0, (int16_t)(statusline_width - outputs_walk->rect.w + 4)), 0,
1422                           MAX(0, (int16_t)(outputs_walk->rect.w - statusline_width - traypx - 4)), 3,
1423                           MIN(outputs_walk->rect.w - traypx - 4, statusline_width), font.height);
1424         }
1425
1426         if (config.disable_ws) {
1427             continue;
1428         }
1429
1430         i3_ws *ws_walk;
1431         TAILQ_FOREACH(ws_walk, outputs_walk->workspaces, tailq) {
1432             DLOG("Drawing Button for WS %s at x = %d, len = %d\n", ws_walk->name, i, ws_walk->name_width);
1433             uint32_t fg_color = colors.inactive_ws_fg;
1434             uint32_t bg_color = colors.inactive_ws_bg;
1435             uint32_t border_color = colors.inactive_ws_border;
1436             if (ws_walk->visible) {
1437                 if (!ws_walk->focused) {
1438                     fg_color = colors.active_ws_fg;
1439                     bg_color = colors.active_ws_bg;
1440                     border_color = colors.active_ws_border;
1441                 } else {
1442                     fg_color = colors.focus_ws_fg;
1443                     bg_color = colors.focus_ws_bg;
1444                     border_color = colors.focus_ws_border;
1445                 }
1446             }
1447             if (ws_walk->urgent) {
1448                 DLOG("WS %s is urgent!\n", ws_walk->name);
1449                 fg_color = colors.urgent_ws_fg;
1450                 bg_color = colors.urgent_ws_bg;
1451                 border_color = colors.urgent_ws_border;
1452                 /* The urgent-hint should get noticed, so we unhide the bars shortly */
1453                 unhide_bars();
1454             }
1455             uint32_t mask = XCB_GC_FOREGROUND | XCB_GC_BACKGROUND;
1456             uint32_t vals_border[] = { border_color, border_color };
1457             xcb_change_gc(xcb_connection,
1458                           outputs_walk->bargc,
1459                           mask,
1460                           vals_border);
1461             xcb_rectangle_t rect_border = { i, 0, ws_walk->name_width + 10, font.height + 4 };
1462             xcb_poly_fill_rectangle(xcb_connection,
1463                                     outputs_walk->buffer,
1464                                     outputs_walk->bargc,
1465                                     1,
1466                                     &rect_border);
1467             uint32_t vals[] = { bg_color, bg_color };
1468             xcb_change_gc(xcb_connection,
1469                           outputs_walk->bargc,
1470                           mask,
1471                           vals);
1472             xcb_rectangle_t rect = { i + 1, 1, ws_walk->name_width + 8, font.height + 2 };
1473             xcb_poly_fill_rectangle(xcb_connection,
1474                                     outputs_walk->buffer,
1475                                     outputs_walk->bargc,
1476                                     1,
1477                                     &rect);
1478             set_font_colors(outputs_walk->bargc, fg_color, bg_color);
1479             draw_text((char*)ws_walk->ucs2_name, ws_walk->name_glyphs, true,
1480                     outputs_walk->buffer, outputs_walk->bargc, i + 5, 2, ws_walk->name_width);
1481             i += 10 + ws_walk->name_width + 1;
1482         }
1483
1484         i = 0;
1485     }
1486
1487     redraw_bars();
1488 }
1489
1490 /*
1491  * Redraw the bars, i.e. simply copy the buffer to the barwindow
1492  *
1493  */
1494 void redraw_bars() {
1495     i3_output *outputs_walk;
1496     SLIST_FOREACH(outputs_walk, outputs, slist) {
1497         if (!outputs_walk->active) {
1498             continue;
1499         }
1500         xcb_copy_area(xcb_connection,
1501                       outputs_walk->buffer,
1502                       outputs_walk->bar,
1503                       outputs_walk->bargc,
1504                       0, 0,
1505                       0, 0,
1506                       outputs_walk->rect.w,
1507                       outputs_walk->rect.h);
1508         xcb_flush(xcb_connection);
1509     }
1510 }