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