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