]> git.sur5r.net Git - i3/i3/blob - i3bar/src/xcb.c
Merge pull request #2025 from Airblader/feature-2022
[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 Axel Wagner and contributors (see also: LICENSE)
6  *
7  * xcb.c: Communicating with X
8  *
9  */
10 #include <xcb/xcb.h>
11 #include <xcb/xkb.h>
12 #include <xcb/xproto.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 /* selection window for tray support */
54 static xcb_window_t selwin = XCB_NONE;
55 static xcb_intern_atom_reply_t *tray_reply = NULL;
56
57 /* This is needed for integration with libi3 */
58 xcb_connection_t *conn;
59
60 /* The font we'll use */
61 static i3Font font;
62
63 /* Icon size (based on font size) */
64 int icon_size;
65
66 xcb_visualtype_t *visual_type;
67 uint8_t depth;
68 xcb_colormap_t colormap;
69
70 /* Overall height of the bar (based on font size) */
71 int bar_height;
72
73 /* These are only relevant for XKB, which we only need for grabbing modifiers */
74 int xkb_base;
75 int mod_pressed = 0;
76
77 /* Because the statusline is the same on all outputs, we have
78  * global buffer to render it on */
79 surface_t statusline_surface;
80 uint32_t statusline_width;
81
82 /* Event watchers, to interact with the user */
83 ev_prepare *xcb_prep;
84 ev_check *xcb_chk;
85 ev_io *xcb_io;
86 ev_io *xkb_io;
87
88 /* The name of current binding mode */
89 static mode binding;
90
91 /* Indicates whether a new binding mode was recently activated */
92 bool activated_mode = false;
93
94 /* The parsed colors */
95 struct xcb_colors_t {
96     color_t bar_fg;
97     color_t bar_bg;
98     color_t sep_fg;
99     color_t active_ws_fg;
100     color_t active_ws_bg;
101     color_t active_ws_border;
102     color_t inactive_ws_fg;
103     color_t inactive_ws_bg;
104     color_t inactive_ws_border;
105     color_t urgent_ws_bg;
106     color_t urgent_ws_fg;
107     color_t urgent_ws_border;
108     color_t focus_ws_bg;
109     color_t focus_ws_fg;
110     color_t focus_ws_border;
111     color_t binding_mode_bg;
112     color_t binding_mode_fg;
113     color_t binding_mode_border;
114 };
115 struct xcb_colors_t colors;
116
117 /* Horizontal offset between a workspace label and button borders */
118 static const int ws_hoff_px = 4;
119
120 /* Vertical offset between a workspace label and button borders */
121 static const int ws_voff_px = 3;
122
123 /* Offset between two workspace buttons */
124 static const int ws_spacing_px = 1;
125
126 /* Offset between the statusline and 1) workspace buttons on the left
127  *                                   2) the tray or screen edge on the right */
128 static const int sb_hoff_px = 4;
129
130 /* Additional offset between the tray and the statusline, if the tray is not empty */
131 static const int tray_loff_px = 2;
132
133 /* Vertical offset between the bar and a separator */
134 static const int sep_voff_px = 4;
135
136 int _xcb_request_failed(xcb_void_cookie_t cookie, char *err_msg, int line) {
137     xcb_generic_error_t *err;
138     if ((err = xcb_request_check(xcb_connection, cookie)) != NULL) {
139         fprintf(stderr, "[%s:%d] ERROR: %s. X Error Code: %d\n", __FILE__, line, err_msg, err->error_code);
140         return err->error_code;
141     }
142     return 0;
143 }
144
145 uint32_t get_sep_offset(struct status_block *block) {
146     if (!block->no_separator && block->sep_block_width > 0)
147         return block->sep_block_width / 2 + block->sep_block_width % 2;
148     return 0;
149 }
150
151 int get_tray_width(struct tc_head *trayclients) {
152     trayclient *trayclient;
153     int tray_width = 0;
154     TAILQ_FOREACH_REVERSE(trayclient, trayclients, tc_head, tailq) {
155         if (!trayclient->mapped)
156             continue;
157         tray_width += icon_size + logical_px(config.tray_padding);
158     }
159     if (tray_width > 0)
160         tray_width += logical_px(tray_loff_px);
161     return tray_width;
162 }
163
164 /*
165  * Draws a separator for the given block if necessary.
166  *
167  */
168 static void draw_separator(uint32_t x, struct status_block *block) {
169     uint32_t sep_offset = get_sep_offset(block);
170     if (TAILQ_NEXT(block, blocks) == NULL || sep_offset == 0)
171         return;
172
173     uint32_t center_x = x - sep_offset;
174     if (config.separator_symbol == NULL) {
175         /* Draw a classic one pixel, vertical separator. */
176         draw_util_rectangle(&statusline_surface, colors.sep_fg,
177                             center_x,
178                             logical_px(sep_voff_px),
179                             logical_px(1),
180                             bar_height - 2 * logical_px(sep_voff_px));
181     } else {
182         /* Draw a custom separator. */
183         uint32_t separator_x = MAX(x - block->sep_block_width, center_x - separator_symbol_width / 2);
184         draw_util_text(config.separator_symbol, &statusline_surface, colors.sep_fg, colors.bar_bg,
185                        separator_x, logical_px(ws_voff_px), x - separator_x);
186     }
187 }
188
189 /*
190  * Redraws the statusline to the buffer
191  *
192  */
193 void refresh_statusline(bool use_short_text) {
194     struct status_block *block;
195
196     uint32_t old_statusline_width = statusline_width;
197     statusline_width = 0;
198
199     /* Predict the text width of all blocks (in pixels). */
200     TAILQ_FOREACH(block, &statusline_head, blocks) {
201         /* Try to use the shorter text if necessary and possible. */
202         if (use_short_text && block->short_text != NULL) {
203             I3STRING_FREE(block->full_text);
204             block->full_text = i3string_copy(block->short_text);
205         }
206
207         if (i3string_get_num_bytes(block->full_text) == 0)
208             continue;
209
210         block->width = predict_text_width(block->full_text);
211         /* Add padding for the border if we have to draw it. */
212         if (block->border)
213             block->width += logical_px(2);
214
215         /* Compute offset and append for text aligment in min_width. */
216         if (block->min_width <= block->width) {
217             block->x_offset = 0;
218             block->x_append = 0;
219         } else {
220             uint32_t padding_width = block->min_width - block->width;
221             switch (block->align) {
222                 case ALIGN_LEFT:
223                     block->x_append = padding_width;
224                     break;
225                 case ALIGN_RIGHT:
226                     block->x_offset = padding_width;
227                     break;
228                 case ALIGN_CENTER:
229                     block->x_offset = padding_width / 2;
230                     block->x_append = padding_width / 2 + padding_width % 2;
231                     break;
232             }
233         }
234
235         /* If this is not the last block, add some pixels for a separator. */
236         if (TAILQ_NEXT(block, blocks) != NULL)
237             statusline_width += block->sep_block_width;
238
239         statusline_width += block->width + block->x_offset + block->x_append;
240     }
241
242     /* If the statusline is bigger than our screen we need to make sure that
243      * the pixmap provides enough space, so re-allocate if the width grew */
244     if (statusline_width > root_screen->width_in_pixels &&
245         statusline_width > old_statusline_width)
246         realloc_sl_buffer();
247
248     /* Clear the statusline pixmap. */
249     draw_util_clear_surface(&statusline_surface, colors.bar_bg);
250
251     /* Draw the text of each block. */
252     uint32_t x = 0;
253     TAILQ_FOREACH(block, &statusline_head, blocks) {
254         if (i3string_get_num_bytes(block->full_text) == 0)
255             continue;
256
257         color_t fg_color = (block->color ? draw_util_hex_to_color(block->color) : colors.bar_fg);
258         int border_width = (block->border) ? logical_px(1) : 0;
259         if (block->border || block->background || block->urgent) {
260             if (block->urgent)
261                 fg_color = colors.urgent_ws_fg;
262
263             /* Let's determine the colors first. */
264             color_t border_color = colors.bar_bg;
265             color_t bg_color = colors.bar_bg;
266             if (block->urgent) {
267                 border_color = colors.urgent_ws_border;
268                 bg_color = colors.urgent_ws_bg;
269             } else {
270                 if (block->border)
271                     border_color = draw_util_hex_to_color(block->border);
272
273                 if (block->background)
274                     bg_color = draw_util_hex_to_color(block->background);
275             }
276
277             /* Draw the border. */
278             draw_util_rectangle(&statusline_surface, border_color,
279                                 x, logical_px(1),
280                                 block->width + block->x_offset + block->x_append,
281                                 bar_height - logical_px(2));
282
283             /* Draw the background. */
284             draw_util_rectangle(&statusline_surface, bg_color,
285                                 x + border_width,
286                                 logical_px(1) + border_width,
287                                 block->width + block->x_offset + block->x_append - 2 * border_width,
288                                 bar_height - 2 * border_width - logical_px(2));
289         }
290
291         draw_util_text(block->full_text, &statusline_surface, fg_color, colors.bar_bg,
292                        x + block->x_offset + border_width, logical_px(ws_voff_px), block->width - 2 * border_width);
293         x += block->width + block->sep_block_width + block->x_offset + block->x_append;
294
295         /* If this is not the last block, draw a separator. */
296         draw_separator(x, block);
297     }
298 }
299
300 /*
301  * Hides all bars (unmaps them)
302  *
303  */
304 void hide_bars(void) {
305     if ((config.hide_on_modifier == M_DOCK) || (config.hidden_state == S_SHOW && config.hide_on_modifier == M_HIDE)) {
306         return;
307     }
308
309     i3_output *walk;
310     SLIST_FOREACH(walk, outputs, slist) {
311         if (!walk->active) {
312             continue;
313         }
314         xcb_unmap_window(xcb_connection, walk->bar.id);
315     }
316     stop_child();
317 }
318
319 /*
320  * Unhides all bars (maps them)
321  *
322  */
323 void unhide_bars(void) {
324     if (config.hide_on_modifier != M_HIDE) {
325         return;
326     }
327
328     i3_output *walk;
329     xcb_void_cookie_t cookie;
330     uint32_t mask;
331     uint32_t values[5];
332
333     cont_child();
334
335     SLIST_FOREACH(walk, outputs, slist) {
336         if (walk->bar.id == XCB_NONE) {
337             continue;
338         }
339         mask = XCB_CONFIG_WINDOW_X |
340                XCB_CONFIG_WINDOW_Y |
341                XCB_CONFIG_WINDOW_WIDTH |
342                XCB_CONFIG_WINDOW_HEIGHT |
343                XCB_CONFIG_WINDOW_STACK_MODE;
344         values[0] = walk->rect.x;
345         if (config.position == POS_TOP)
346             values[1] = walk->rect.y;
347         else
348             values[1] = walk->rect.y + walk->rect.h - bar_height;
349         values[2] = walk->rect.w;
350         values[3] = bar_height;
351         values[4] = XCB_STACK_MODE_ABOVE;
352         DLOG("Reconfiguring window for output %s to %d,%d\n", walk->name, values[0], values[1]);
353         cookie = xcb_configure_window_checked(xcb_connection,
354                                               walk->bar.id,
355                                               mask,
356                                               values);
357
358         if (xcb_request_failed(cookie, "Could not reconfigure window")) {
359             exit(EXIT_FAILURE);
360         }
361         xcb_map_window(xcb_connection, walk->bar.id);
362     }
363 }
364
365 /*
366  * Parse the colors into a format that we can use
367  *
368  */
369 void init_colors(const struct xcb_color_strings_t *new_colors) {
370 #define PARSE_COLOR(name, def)                                                           \
371     do {                                                                                 \
372         colors.name = draw_util_hex_to_color(new_colors->name ? new_colors->name : def); \
373     } while (0)
374     PARSE_COLOR(bar_fg, "#FFFFFF");
375     PARSE_COLOR(bar_bg, "#000000");
376     PARSE_COLOR(sep_fg, "#666666");
377     PARSE_COLOR(active_ws_fg, "#FFFFFF");
378     PARSE_COLOR(active_ws_bg, "#333333");
379     PARSE_COLOR(active_ws_border, "#333333");
380     PARSE_COLOR(inactive_ws_fg, "#888888");
381     PARSE_COLOR(inactive_ws_bg, "#222222");
382     PARSE_COLOR(inactive_ws_border, "#333333");
383     PARSE_COLOR(urgent_ws_fg, "#FFFFFF");
384     PARSE_COLOR(urgent_ws_bg, "#900000");
385     PARSE_COLOR(urgent_ws_border, "#2f343a");
386     PARSE_COLOR(focus_ws_fg, "#FFFFFF");
387     PARSE_COLOR(focus_ws_bg, "#285577");
388     PARSE_COLOR(focus_ws_border, "#4c7899");
389 #undef PARSE_COLOR
390
391 #define PARSE_COLOR_FALLBACK(name, fallback)                                                         \
392     do {                                                                                             \
393         colors.name = new_colors->name ? draw_util_hex_to_color(new_colors->name) : colors.fallback; \
394     } while (0)
395
396     /* For the binding mode indicator colors, we don't hardcode a default.
397      * Instead, we fall back to urgent_ws_* colors. */
398     PARSE_COLOR_FALLBACK(binding_mode_fg, urgent_ws_fg);
399     PARSE_COLOR_FALLBACK(binding_mode_bg, urgent_ws_bg);
400     PARSE_COLOR_FALLBACK(binding_mode_border, urgent_ws_border);
401 #undef PARSE_COLOR_FALLBACK
402
403     init_tray_colors();
404     xcb_flush(xcb_connection);
405 }
406
407 /*
408  * Handle a button press event (i.e. a mouse click on one of our bars).
409  * We determine, whether the click occured on a workspace button or if the scroll-
410  * wheel was used and change the workspace appropriately
411  *
412  */
413 void handle_button(xcb_button_press_event_t *event) {
414     /* Determine, which bar was clicked */
415     i3_output *walk;
416     xcb_window_t bar = event->event;
417     SLIST_FOREACH(walk, outputs, slist) {
418         if (walk->bar.id == bar) {
419             break;
420         }
421     }
422
423     if (walk == NULL) {
424         DLOG("Unknown bar clicked!\n");
425         return;
426     }
427
428     int32_t x = event->event_x >= 0 ? event->event_x : 0;
429     int32_t original_x = x;
430
431     DLOG("Got button %d\n", event->detail);
432
433     int workspace_width = 0;
434     i3_ws *cur_ws = NULL, *clicked_ws = NULL, *ws_walk;
435
436     TAILQ_FOREACH(ws_walk, walk->workspaces, tailq) {
437         int w = 2 * logical_px(ws_hoff_px) + 2 * logical_px(1) + ws_walk->name_width;
438         if (x >= workspace_width && x <= workspace_width + w)
439             clicked_ws = ws_walk;
440         if (ws_walk->visible)
441             cur_ws = ws_walk;
442         workspace_width += w;
443         if (TAILQ_NEXT(ws_walk, tailq) != NULL)
444             workspace_width += logical_px(ws_spacing_px);
445     }
446
447     if (x > workspace_width && child_want_click_events()) {
448         /* If the child asked for click events,
449          * check if a status block has been clicked. */
450         int tray_width = get_tray_width(walk->trayclients);
451         int block_x = 0, last_block_x;
452         int offset = walk->rect.w - statusline_width - tray_width - logical_px(sb_hoff_px);
453
454         x = original_x - offset;
455         if (x >= 0 && (size_t)x < statusline_width) {
456             struct status_block *block;
457             int sep_offset_remainder = 0;
458
459             TAILQ_FOREACH(block, &statusline_head, blocks) {
460                 if (i3string_get_num_bytes(block->full_text) == 0)
461                     continue;
462
463                 last_block_x = block_x;
464                 block_x += block->width + block->x_offset + block->x_append + get_sep_offset(block) + sep_offset_remainder;
465
466                 if (x <= block_x && x >= last_block_x) {
467                     send_block_clicked(event->detail, block->name, block->instance, event->root_x, event->root_y);
468                     return;
469                 }
470
471                 sep_offset_remainder = block->sep_block_width - get_sep_offset(block);
472             }
473         }
474         x = original_x;
475     }
476
477     /* If a custom command was specified for this mouse button, it overrides
478      * the default behavior. */
479     binding_t *binding;
480     TAILQ_FOREACH(binding, &(config.bindings), bindings) {
481         if (binding->input_code != event->detail)
482             continue;
483
484         i3_send_msg(I3_IPC_MESSAGE_TYPE_COMMAND, binding->command);
485         return;
486     }
487
488     if (cur_ws == NULL) {
489         DLOG("No workspace active?\n");
490         return;
491     }
492     switch (event->detail) {
493         case 4:
494             /* Mouse wheel up. We select the previous ws, if any.
495              * If there is no more workspace, don’t even send the workspace
496              * command, otherwise (with workspace auto_back_and_forth) we’d end
497              * up on the wrong workspace. */
498             if (cur_ws == TAILQ_FIRST(walk->workspaces))
499                 return;
500
501             cur_ws = TAILQ_PREV(cur_ws, ws_head, tailq);
502             break;
503         case 5:
504             /* Mouse wheel down. We select the next ws, if any.
505              * If there is no more workspace, don’t even send the workspace
506              * command, otherwise (with workspace auto_back_and_forth) we’d end
507              * up on the wrong workspace. */
508             if (cur_ws == TAILQ_LAST(walk->workspaces, ws_head))
509                 return;
510
511             cur_ws = TAILQ_NEXT(cur_ws, tailq);
512             break;
513         case 1:
514             cur_ws = clicked_ws;
515
516             /* if no workspace was clicked, focus our currently visible
517              * workspace if it is not already focused */
518             if (cur_ws == NULL) {
519                 TAILQ_FOREACH(cur_ws, walk->workspaces, tailq) {
520                     if (cur_ws->visible && !cur_ws->focused)
521                         break;
522                 }
523             }
524
525             /* if there is nothing to focus, we are done */
526             if (cur_ws == NULL)
527                 return;
528
529             break;
530         default:
531             return;
532     }
533
534     /* To properly handle workspace names with double quotes in them, we need
535      * to escape the double quotes. Unfortunately, that’s rather ugly in C: We
536      * first count the number of double quotes, then we allocate a large enough
537      * buffer, then we copy character by character. */
538     int num_quotes = 0;
539     size_t namelen = 0;
540     const char *utf8_name = cur_ws->canonical_name;
541     for (const char *walk = utf8_name; *walk != '\0'; walk++) {
542         if (*walk == '"' || *walk == '\\')
543             num_quotes++;
544         /* While we’re looping through the name anyway, we can save one
545          * strlen(). */
546         namelen++;
547     }
548
549     const size_t len = namelen + strlen("workspace \"\"") + 1;
550     char *buffer = scalloc(len + num_quotes, 1);
551     strncpy(buffer, "workspace \"", strlen("workspace \""));
552     size_t inpos, outpos;
553     for (inpos = 0, outpos = strlen("workspace \"");
554          inpos < namelen;
555          inpos++, outpos++) {
556         if (utf8_name[inpos] == '"' || utf8_name[inpos] == '\\') {
557             buffer[outpos] = '\\';
558             outpos++;
559         }
560         buffer[outpos] = utf8_name[inpos];
561     }
562     buffer[outpos] = '"';
563     i3_send_msg(I3_IPC_MESSAGE_TYPE_COMMAND, buffer);
564     free(buffer);
565 }
566
567 /*
568  * Handle visibility notifications: when none of the bars are visible, e.g.
569  * if windows are in fullscreen on each output, suspend the child process.
570  *
571  */
572 static void handle_visibility_notify(xcb_visibility_notify_event_t *event) {
573     bool visible = (event->state != XCB_VISIBILITY_FULLY_OBSCURED);
574     int num_visible = 0;
575     i3_output *output;
576
577     SLIST_FOREACH(output, outputs, slist) {
578         if (!output->active) {
579             continue;
580         }
581         if (output->bar.id == event->window) {
582             if (output->visible == visible) {
583                 return;
584             }
585             output->visible = visible;
586         }
587         num_visible += output->visible;
588     }
589
590     if (num_visible == 0) {
591         stop_child();
592     } else if (num_visible == visible) {
593         /* Wake the child only when transitioning from 0 to 1 visible bar.
594          * We cannot transition from 0 to 2 or more visible bars at once since
595          * visibility events are delivered to each window separately */
596         cont_child();
597     }
598 }
599
600 /*
601  * Adjusts the size of the tray window and alignment of the tray clients by
602  * configuring their respective x coordinates. To be called when mapping or
603  * unmapping a tray client window.
604  *
605  */
606 static void configure_trayclients(void) {
607     trayclient *trayclient;
608     i3_output *output;
609     SLIST_FOREACH(output, outputs, slist) {
610         if (!output->active)
611             continue;
612
613         int clients = 0;
614         TAILQ_FOREACH_REVERSE(trayclient, output->trayclients, tc_head, tailq) {
615             if (!trayclient->mapped)
616                 continue;
617             clients++;
618
619             DLOG("Configuring tray window %08x to x=%d\n",
620                  trayclient->win, output->rect.w - (clients * (icon_size + logical_px(config.tray_padding))));
621             uint32_t x = output->rect.w - (clients * (icon_size + logical_px(config.tray_padding)));
622             xcb_configure_window(xcb_connection,
623                                  trayclient->win,
624                                  XCB_CONFIG_WINDOW_X,
625                                  &x);
626         }
627     }
628 }
629
630 /*
631  * Handles ClientMessages (messages sent from another client directly to us).
632  *
633  * At the moment, only the tray window will receive client messages. All
634  * supported client messages currently are _NET_SYSTEM_TRAY_OPCODE.
635  *
636  */
637 static void handle_client_message(xcb_client_message_event_t *event) {
638     if (event->type == atoms[_NET_SYSTEM_TRAY_OPCODE] &&
639         event->format == 32) {
640         DLOG("_NET_SYSTEM_TRAY_OPCODE received\n");
641         /* event->data.data32[0] is the timestamp */
642         uint32_t op = event->data.data32[1];
643         uint32_t mask;
644         uint32_t values[2];
645         if (op == SYSTEM_TRAY_REQUEST_DOCK) {
646             xcb_window_t client = event->data.data32[2];
647
648             /* Listen for PropertyNotify events to get the most recent value of
649              * the XEMBED_MAPPED atom, also listen for UnmapNotify events */
650             mask = XCB_CW_EVENT_MASK;
651             values[0] = XCB_EVENT_MASK_PROPERTY_CHANGE |
652                         XCB_EVENT_MASK_STRUCTURE_NOTIFY;
653             xcb_change_window_attributes(xcb_connection,
654                                          client,
655                                          mask,
656                                          values);
657
658             /* Request the _XEMBED_INFO property. The XEMBED specification
659              * (which is referred by the tray specification) says this *has* to
660              * be set, but VLC does not set it… */
661             bool map_it = true;
662             int xe_version = 1;
663             xcb_get_property_cookie_t xembedc;
664             xcb_generic_error_t *error;
665             xembedc = xcb_get_property(xcb_connection,
666                                        0,
667                                        client,
668                                        atoms[_XEMBED_INFO],
669                                        XCB_GET_PROPERTY_TYPE_ANY,
670                                        0,
671                                        2 * 32);
672
673             xcb_get_property_reply_t *xembedr = xcb_get_property_reply(xcb_connection,
674                                                                        xembedc,
675                                                                        &error);
676             if (error != NULL) {
677                 ELOG("Error getting _XEMBED_INFO property: error_code %d\n",
678                      error->error_code);
679                 free(error);
680                 return;
681             }
682             if (xembedr != NULL && xembedr->length != 0) {
683                 DLOG("xembed format = %d, len = %d\n", xembedr->format, xembedr->length);
684                 uint32_t *xembed = xcb_get_property_value(xembedr);
685                 DLOG("xembed version = %d\n", xembed[0]);
686                 DLOG("xembed flags = %d\n", xembed[1]);
687                 map_it = ((xembed[1] & XEMBED_MAPPED) == XEMBED_MAPPED);
688                 xe_version = xembed[0];
689                 if (xe_version > 1)
690                     xe_version = 1;
691                 free(xembedr);
692             } else {
693                 ELOG("Window %08x violates the XEMBED protocol, _XEMBED_INFO not set\n", client);
694             }
695
696             DLOG("X window %08x requested docking\n", client);
697             i3_output *walk, *output = NULL;
698             SLIST_FOREACH(walk, outputs, slist) {
699                 if (!walk->active)
700                     continue;
701                 if (config.tray_output) {
702                     if ((strcasecmp(walk->name, config.tray_output) != 0) &&
703                         (!walk->primary || strcasecmp("primary", config.tray_output) != 0))
704                         continue;
705                 }
706
707                 DLOG("using output %s\n", walk->name);
708                 output = walk;
709                 break;
710             }
711             /* In case of tray_output == primary and there is no primary output
712              * configured, we fall back to the first available output. */
713             if (output == NULL &&
714                 config.tray_output &&
715                 strcasecmp("primary", config.tray_output) == 0) {
716                 SLIST_FOREACH(walk, outputs, slist) {
717                     if (!walk->active)
718                         continue;
719                     DLOG("Falling back to output %s because no primary output is configured\n", walk->name);
720                     output = walk;
721                     break;
722                 }
723             }
724             if (output == NULL) {
725                 ELOG("No output found\n");
726                 return;
727             }
728
729             xcb_void_cookie_t rcookie = xcb_reparent_window(xcb_connection,
730                                                             client,
731                                                             output->bar.id,
732                                                             output->rect.w - icon_size - logical_px(config.tray_padding),
733                                                             logical_px(config.tray_padding));
734             if (xcb_request_failed(rcookie, "Could not reparent window. Maybe it is using an incorrect depth/visual?"))
735                 return;
736
737             /* We reconfigure the window to use a reasonable size. The systray
738              * specification explicitly says:
739              *   Tray icons may be assigned any size by the system tray, and
740              *   should do their best to cope with any size effectively
741              */
742             mask = XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT;
743             values[0] = icon_size;
744             values[1] = icon_size;
745             xcb_configure_window(xcb_connection,
746                                  client,
747                                  mask,
748                                  values);
749
750             /* send the XEMBED_EMBEDDED_NOTIFY message */
751             void *event = scalloc(32, 1);
752             xcb_client_message_event_t *ev = event;
753             ev->response_type = XCB_CLIENT_MESSAGE;
754             ev->window = client;
755             ev->type = atoms[_XEMBED];
756             ev->format = 32;
757             ev->data.data32[0] = XCB_CURRENT_TIME;
758             ev->data.data32[1] = atoms[XEMBED_EMBEDDED_NOTIFY];
759             ev->data.data32[2] = output->bar.id;
760             ev->data.data32[3] = xe_version;
761             xcb_send_event(xcb_connection,
762                            0,
763                            client,
764                            XCB_EVENT_MASK_NO_EVENT,
765                            (char *)ev);
766             free(event);
767
768             /* Put the client inside the save set. Upon termination (whether
769              * killed or normal exit does not matter) of i3bar, these clients
770              * will be correctly reparented to their most closest living
771              * ancestor. Without this, tray icons might die when i3bar
772              * exits/crashes. */
773             xcb_change_save_set(xcb_connection, XCB_SET_MODE_INSERT, client);
774
775             trayclient *tc = smalloc(sizeof(trayclient));
776             tc->win = client;
777             tc->xe_version = xe_version;
778             tc->mapped = false;
779             TAILQ_INSERT_TAIL(output->trayclients, tc, tailq);
780
781             if (map_it) {
782                 DLOG("Mapping dock client\n");
783                 xcb_map_window(xcb_connection, client);
784             } else {
785                 DLOG("Not mapping dock client yet\n");
786             }
787             /* Trigger an update to copy the statusline text to the appropriate
788              * position */
789             configure_trayclients();
790             draw_bars(false);
791         }
792     }
793 }
794
795 /*
796  * Handles DestroyNotify events by removing the tray client from the data
797  * structure. According to the XEmbed protocol, this is one way for a tray
798  * client to finish the protocol. After this event is received, there is no
799  * further interaction with the tray client.
800  *
801  * See: http://standards.freedesktop.org/xembed-spec/xembed-spec-latest.html
802  *
803  */
804 static void handle_destroy_notify(xcb_destroy_notify_event_t *event) {
805     DLOG("DestroyNotify for window = %08x, event = %08x\n", event->window, event->event);
806
807     i3_output *walk;
808     SLIST_FOREACH(walk, outputs, slist) {
809         if (!walk->active)
810             continue;
811         DLOG("checking output %s\n", walk->name);
812         trayclient *trayclient;
813         TAILQ_FOREACH(trayclient, walk->trayclients, tailq) {
814             if (trayclient->win != event->window)
815                 continue;
816
817             DLOG("Removing tray client with window ID %08x\n", event->window);
818             TAILQ_REMOVE(walk->trayclients, trayclient, tailq);
819
820             /* Trigger an update, we now have more space for the statusline */
821             configure_trayclients();
822             draw_bars(false);
823             return;
824         }
825     }
826 }
827
828 /*
829  * Handles MapNotify events. These events happen when a tray client shows its
830  * window. We respond by realigning the tray clients.
831  *
832  */
833 static void handle_map_notify(xcb_map_notify_event_t *event) {
834     DLOG("MapNotify for window = %08x, event = %08x\n", event->window, event->event);
835
836     i3_output *walk;
837     SLIST_FOREACH(walk, outputs, slist) {
838         if (!walk->active)
839             continue;
840         DLOG("checking output %s\n", walk->name);
841         trayclient *trayclient;
842         TAILQ_FOREACH(trayclient, walk->trayclients, tailq) {
843             if (trayclient->win != event->window)
844                 continue;
845
846             DLOG("Tray client mapped (window ID %08x). Adjusting tray.\n", event->window);
847             trayclient->mapped = true;
848
849             /* Trigger an update, we now have more space for the statusline */
850             configure_trayclients();
851             draw_bars(false);
852             return;
853         }
854     }
855 }
856 /*
857  * Handles UnmapNotify events. These events happen when a tray client hides its
858  * window. We respond by realigning the tray clients.
859  *
860  */
861 static void handle_unmap_notify(xcb_unmap_notify_event_t *event) {
862     DLOG("UnmapNotify for window = %08x, event = %08x\n", event->window, event->event);
863
864     i3_output *walk;
865     SLIST_FOREACH(walk, outputs, slist) {
866         if (!walk->active)
867             continue;
868         DLOG("checking output %s\n", walk->name);
869         trayclient *trayclient;
870         TAILQ_FOREACH(trayclient, walk->trayclients, tailq) {
871             if (trayclient->win != event->window)
872                 continue;
873
874             DLOG("Tray client unmapped (window ID %08x). Adjusting tray.\n", event->window);
875             trayclient->mapped = false;
876
877             /* Trigger an update, we now have more space for the statusline */
878             configure_trayclients();
879             draw_bars(false);
880             return;
881         }
882     }
883 }
884
885 /*
886  * Handle PropertyNotify messages. Currently only the _XEMBED_INFO property is
887  * handled, which tells us whether a dock client should be mapped or unmapped.
888  *
889  */
890 static void handle_property_notify(xcb_property_notify_event_t *event) {
891     DLOG("PropertyNotify\n");
892     if (event->atom == atoms[_XEMBED_INFO] &&
893         event->state == XCB_PROPERTY_NEW_VALUE) {
894         DLOG("xembed_info updated\n");
895         trayclient *trayclient = NULL, *walk;
896         i3_output *o_walk;
897         SLIST_FOREACH(o_walk, outputs, slist) {
898             if (!o_walk->active)
899                 continue;
900
901             TAILQ_FOREACH(walk, o_walk->trayclients, tailq) {
902                 if (walk->win != event->window)
903                     continue;
904                 trayclient = walk;
905                 break;
906             }
907
908             if (trayclient)
909                 break;
910         }
911         if (!trayclient) {
912             ELOG("PropertyNotify received for unknown window %08x\n",
913                  event->window);
914             return;
915         }
916         xcb_get_property_cookie_t xembedc;
917         xembedc = xcb_get_property_unchecked(xcb_connection,
918                                              0,
919                                              trayclient->win,
920                                              atoms[_XEMBED_INFO],
921                                              XCB_GET_PROPERTY_TYPE_ANY,
922                                              0,
923                                              2 * 32);
924
925         xcb_get_property_reply_t *xembedr = xcb_get_property_reply(xcb_connection,
926                                                                    xembedc,
927                                                                    NULL);
928         if (xembedr == NULL || xembedr->length == 0) {
929             DLOG("xembed_info unset\n");
930             return;
931         }
932
933         DLOG("xembed format = %d, len = %d\n", xembedr->format, xembedr->length);
934         uint32_t *xembed = xcb_get_property_value(xembedr);
935         DLOG("xembed version = %d\n", xembed[0]);
936         DLOG("xembed flags = %d\n", xembed[1]);
937         bool map_it = ((xembed[1] & XEMBED_MAPPED) == XEMBED_MAPPED);
938         DLOG("map state now %d\n", map_it);
939         if (trayclient->mapped && !map_it) {
940             /* need to unmap the window */
941             xcb_unmap_window(xcb_connection, trayclient->win);
942         } else if (!trayclient->mapped && map_it) {
943             /* need to map the window */
944             xcb_map_window(xcb_connection, trayclient->win);
945         }
946         free(xembedr);
947     }
948 }
949
950 /*
951  * Handle ConfigureRequests by denying them and sending the client a
952  * ConfigureNotify with its actual size.
953  *
954  */
955 static void handle_configure_request(xcb_configure_request_event_t *event) {
956     DLOG("ConfigureRequest for window = %08x\n", event->window);
957
958     trayclient *trayclient;
959     i3_output *output;
960     SLIST_FOREACH(output, outputs, slist) {
961         if (!output->active)
962             continue;
963
964         int clients = 0;
965         TAILQ_FOREACH_REVERSE(trayclient, output->trayclients, tc_head, tailq) {
966             if (!trayclient->mapped)
967                 continue;
968             clients++;
969
970             if (trayclient->win != event->window)
971                 continue;
972
973             xcb_rectangle_t rect;
974             rect.x = output->rect.w - (clients * (icon_size + logical_px(config.tray_padding)));
975             rect.y = logical_px(config.tray_padding);
976             rect.width = icon_size;
977             rect.height = icon_size;
978
979             DLOG("This is a tray window. x = %d\n", rect.x);
980             fake_configure_notify(xcb_connection, rect, event->window, 0);
981             return;
982         }
983     }
984
985     DLOG("WARNING: Could not find corresponding tray window.\n");
986 }
987
988 /*
989  * This function is called immediately before the main loop locks. We flush xcb
990  * then (and only then)
991  *
992  */
993 void xcb_prep_cb(struct ev_loop *loop, ev_prepare *watcher, int revents) {
994     xcb_flush(xcb_connection);
995 }
996
997 /*
998  * This function is called immediately after the main loop locks, so when one
999  * of the watchers registered an event.
1000  * We check whether an X-Event arrived and handle it.
1001  *
1002  */
1003 void xcb_chk_cb(struct ev_loop *loop, ev_check *watcher, int revents) {
1004     xcb_generic_event_t *event;
1005
1006     if (xcb_connection_has_error(xcb_connection)) {
1007         ELOG("X11 connection was closed unexpectedly - maybe your X server terminated / crashed?\n");
1008         exit(1);
1009     }
1010
1011     while ((event = xcb_poll_for_event(xcb_connection)) != NULL) {
1012         if (event->response_type == 0) {
1013             xcb_generic_error_t *error = (xcb_generic_error_t *)event;
1014             DLOG("Received X11 error, sequence 0x%x, error_code = %d\n", error->sequence, error->error_code);
1015             free(event);
1016             continue;
1017         }
1018
1019         int type = (event->response_type & ~0x80);
1020
1021         if (type == xkb_base && xkb_base > -1) {
1022             DLOG("received an xkb event\n");
1023
1024             xcb_xkb_state_notify_event_t *state = (xcb_xkb_state_notify_event_t *)event;
1025             if (state->xkbType == XCB_XKB_STATE_NOTIFY) {
1026                 int modstate = state->mods & config.modifier;
1027
1028 #define DLOGMOD(modmask, status)                        \
1029     do {                                                \
1030         switch (modmask) {                              \
1031             case ShiftMask:                             \
1032                 DLOG("ShiftMask got " #status "!\n");   \
1033                 break;                                  \
1034             case ControlMask:                           \
1035                 DLOG("ControlMask got " #status "!\n"); \
1036                 break;                                  \
1037             case Mod1Mask:                              \
1038                 DLOG("Mod1Mask got " #status "!\n");    \
1039                 break;                                  \
1040             case Mod2Mask:                              \
1041                 DLOG("Mod2Mask got " #status "!\n");    \
1042                 break;                                  \
1043             case Mod3Mask:                              \
1044                 DLOG("Mod3Mask got " #status "!\n");    \
1045                 break;                                  \
1046             case Mod4Mask:                              \
1047                 DLOG("Mod4Mask got " #status "!\n");    \
1048                 break;                                  \
1049             case Mod5Mask:                              \
1050                 DLOG("Mod5Mask got " #status "!\n");    \
1051                 break;                                  \
1052         }                                               \
1053     } while (0)
1054
1055                 if (modstate != mod_pressed) {
1056                     if (modstate == 0) {
1057                         DLOGMOD(config.modifier, released);
1058                         if (!activated_mode)
1059                             hide_bars();
1060                     } else {
1061                         DLOGMOD(config.modifier, pressed);
1062                         activated_mode = false;
1063                         unhide_bars();
1064                     }
1065                     mod_pressed = modstate;
1066                 }
1067 #undef DLOGMOD
1068             }
1069
1070             free(event);
1071             continue;
1072         }
1073
1074         switch (type) {
1075             case XCB_VISIBILITY_NOTIFY:
1076                 /* Visibility change: a bar is [un]obscured by other window */
1077                 handle_visibility_notify((xcb_visibility_notify_event_t *)event);
1078                 break;
1079             case XCB_EXPOSE:
1080                 /* Expose-events happen, when the window needs to be redrawn */
1081                 redraw_bars();
1082                 break;
1083             case XCB_BUTTON_PRESS:
1084                 /* Button press events are mouse buttons clicked on one of our bars */
1085                 handle_button((xcb_button_press_event_t *)event);
1086                 break;
1087             case XCB_CLIENT_MESSAGE:
1088                 /* Client messages are used for client-to-client communication, for
1089                  * example system tray widgets talk to us directly via client messages. */
1090                 handle_client_message((xcb_client_message_event_t *)event);
1091                 break;
1092             case XCB_DESTROY_NOTIFY:
1093                 /* DestroyNotify signifies the end of the XEmbed protocol */
1094                 handle_destroy_notify((xcb_destroy_notify_event_t *)event);
1095                 break;
1096             case XCB_UNMAP_NOTIFY:
1097                 /* UnmapNotify is received when a tray client hides its window. */
1098                 handle_unmap_notify((xcb_unmap_notify_event_t *)event);
1099                 break;
1100             case XCB_MAP_NOTIFY:
1101                 handle_map_notify((xcb_map_notify_event_t *)event);
1102                 break;
1103             case XCB_PROPERTY_NOTIFY:
1104                 /* PropertyNotify */
1105                 handle_property_notify((xcb_property_notify_event_t *)event);
1106                 break;
1107             case XCB_CONFIGURE_REQUEST:
1108                 /* ConfigureRequest, sent by a tray child */
1109                 handle_configure_request((xcb_configure_request_event_t *)event);
1110                 break;
1111         }
1112         free(event);
1113     }
1114 }
1115
1116 /*
1117  * Dummy callback. We only need this, so that the prepare and check watchers
1118  * are triggered
1119  *
1120  */
1121 void xcb_io_cb(struct ev_loop *loop, ev_io *watcher, int revents) {
1122 }
1123
1124 /*
1125  * Early initialization of the connection to X11: Everything which does not
1126  * depend on 'config'.
1127  *
1128  */
1129 char *init_xcb_early() {
1130     /* FIXME: xcb_connect leaks memory */
1131     xcb_connection = xcb_connect(NULL, &screen);
1132     if (xcb_connection_has_error(xcb_connection)) {
1133         ELOG("Cannot open display\n");
1134         exit(EXIT_FAILURE);
1135     }
1136     conn = xcb_connection;
1137     DLOG("Connected to xcb\n");
1138
1139 /* We have to request the atoms we need */
1140 #define ATOM_DO(name) atom_cookies[name] = xcb_intern_atom(xcb_connection, 0, strlen(#name), #name);
1141 #include "xcb_atoms.def"
1142
1143     root_screen = xcb_aux_get_screen(xcb_connection, screen);
1144     xcb_root = root_screen->root;
1145
1146     depth = root_screen->root_depth;
1147     colormap = root_screen->default_colormap;
1148     visual_type = get_visualtype(root_screen);
1149
1150     /* We draw the statusline to a seperate pixmap, because it looks the same on all bars and
1151      * this way, we can choose to crop it */
1152     xcb_pixmap_t statusline_id = xcb_generate_id(xcb_connection);
1153     xcb_void_cookie_t sl_pm_cookie = xcb_create_pixmap_checked(xcb_connection,
1154                                                                depth,
1155                                                                statusline_id,
1156                                                                xcb_root,
1157                                                                root_screen->width_in_pixels,
1158                                                                root_screen->height_in_pixels);
1159     draw_util_surface_init(&statusline_surface, statusline_id, root_screen->width_in_pixels, root_screen->height_in_pixels);
1160
1161     /* The various watchers to communicate with xcb */
1162     xcb_io = smalloc(sizeof(ev_io));
1163     xcb_prep = smalloc(sizeof(ev_prepare));
1164     xcb_chk = smalloc(sizeof(ev_check));
1165
1166     ev_io_init(xcb_io, &xcb_io_cb, xcb_get_file_descriptor(xcb_connection), EV_READ);
1167     ev_prepare_init(xcb_prep, &xcb_prep_cb);
1168     ev_check_init(xcb_chk, &xcb_chk_cb);
1169
1170     ev_io_start(main_loop, xcb_io);
1171     ev_prepare_start(main_loop, xcb_prep);
1172     ev_check_start(main_loop, xcb_chk);
1173
1174     /* Now we get the atoms and save them in a nice data structure */
1175     get_atoms();
1176
1177     char *path = root_atom_contents("I3_SOCKET_PATH", xcb_connection, screen);
1178
1179     if (xcb_request_failed(sl_pm_cookie, "Could not allocate statusline buffer"))
1180         exit(EXIT_FAILURE);
1181
1182     return path;
1183 }
1184
1185 /*
1186  * Register for xkb keyevents. To grab modifiers without blocking other applications from receiving key events
1187  * involving that modifier, we sadly have to use xkb which is not yet fully supported
1188  * in xcb.
1189  *
1190  */
1191 void register_xkb_keyevents() {
1192     const xcb_query_extension_reply_t *extreply;
1193     extreply = xcb_get_extension_data(conn, &xcb_xkb_id);
1194     if (!extreply->present) {
1195         ELOG("xkb is not present on this server\n");
1196         exit(EXIT_FAILURE);
1197     }
1198     DLOG("initializing xcb-xkb\n");
1199     xcb_xkb_use_extension(conn, XCB_XKB_MAJOR_VERSION, XCB_XKB_MINOR_VERSION);
1200     xcb_xkb_select_events(conn,
1201                           XCB_XKB_ID_USE_CORE_KBD,
1202                           XCB_XKB_EVENT_TYPE_STATE_NOTIFY,
1203                           0,
1204                           XCB_XKB_EVENT_TYPE_STATE_NOTIFY,
1205                           0xff,
1206                           0xff,
1207                           NULL);
1208     xkb_base = extreply->first_event;
1209 }
1210
1211 /*
1212  * Deregister from xkb keyevents.
1213  *
1214  */
1215 void deregister_xkb_keyevents() {
1216     xcb_xkb_select_events(conn,
1217                           XCB_XKB_ID_USE_CORE_KBD,
1218                           0,
1219                           0,
1220                           0,
1221                           0xff,
1222                           0xff,
1223                           NULL);
1224 }
1225
1226 /*
1227  * Initialization which depends on 'config' being usable. Called after the
1228  * configuration has arrived.
1229  *
1230  */
1231 void init_xcb_late(char *fontname) {
1232     if (fontname == NULL)
1233         fontname = "-misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1";
1234
1235     /* Load the font */
1236     font = load_font(fontname, true);
1237     set_font(&font);
1238     DLOG("Calculated font height: %d\n", font.height);
1239     bar_height = font.height + 2 * logical_px(ws_voff_px);
1240     icon_size = bar_height - 2 * logical_px(config.tray_padding);
1241
1242     if (config.separator_symbol)
1243         separator_symbol_width = predict_text_width(config.separator_symbol);
1244
1245     xcb_flush(xcb_connection);
1246
1247     if (config.hide_on_modifier == M_HIDE)
1248         register_xkb_keyevents();
1249 }
1250
1251 /*
1252  * Inform clients waiting for a new _NET_SYSTEM_TRAY that we took the
1253  * selection.
1254  *
1255  */
1256 static void send_tray_clientmessage(void) {
1257     uint8_t buffer[32] = {0};
1258     xcb_client_message_event_t *ev = (xcb_client_message_event_t *)buffer;
1259
1260     ev->response_type = XCB_CLIENT_MESSAGE;
1261     ev->window = xcb_root;
1262     ev->type = atoms[MANAGER];
1263     ev->format = 32;
1264     ev->data.data32[0] = XCB_CURRENT_TIME;
1265     ev->data.data32[1] = tray_reply->atom;
1266     ev->data.data32[2] = selwin;
1267
1268     xcb_send_event(xcb_connection,
1269                    0,
1270                    xcb_root,
1271                    0xFFFFFF,
1272                    (char *)buffer);
1273 }
1274
1275 /*
1276  * Initializes tray support by requesting the appropriate _NET_SYSTEM_TRAY atom
1277  * for the X11 display we are running on, then acquiring the selection for this
1278  * atom. Afterwards, tray clients will send ClientMessages to our window.
1279  *
1280  */
1281 void init_tray(void) {
1282     DLOG("Initializing system tray functionality\n");
1283     /* request the tray manager atom for the X11 display we are running on */
1284     char atomname[strlen("_NET_SYSTEM_TRAY_S") + 11];
1285     snprintf(atomname, strlen("_NET_SYSTEM_TRAY_S") + 11, "_NET_SYSTEM_TRAY_S%d", screen);
1286     xcb_intern_atom_cookie_t tray_cookie;
1287     if (tray_reply == NULL)
1288         tray_cookie = xcb_intern_atom(xcb_connection, 0, strlen(atomname), atomname);
1289
1290     /* tray support: we need a window to own the selection */
1291     selwin = xcb_generate_id(xcb_connection);
1292     uint32_t selmask = XCB_CW_BACK_PIXEL | XCB_CW_BORDER_PIXEL | XCB_CW_OVERRIDE_REDIRECT | XCB_CW_COLORMAP;
1293     uint32_t selval[] = {root_screen->black_pixel, root_screen->black_pixel, 1, colormap};
1294     xcb_create_window(xcb_connection,
1295                       depth,
1296                       selwin,
1297                       xcb_root,
1298                       -1, -1,
1299                       1, 1,
1300                       0,
1301                       XCB_WINDOW_CLASS_INPUT_OUTPUT,
1302                       visual_type->visual_id,
1303                       selmask,
1304                       selval);
1305
1306     uint32_t orientation = _NET_SYSTEM_TRAY_ORIENTATION_HORZ;
1307     /* set the atoms */
1308     xcb_change_property(xcb_connection,
1309                         XCB_PROP_MODE_REPLACE,
1310                         selwin,
1311                         atoms[_NET_SYSTEM_TRAY_ORIENTATION],
1312                         XCB_ATOM_CARDINAL,
1313                         32,
1314                         1,
1315                         &orientation);
1316     xcb_change_property(xcb_connection,
1317                         XCB_PROP_MODE_REPLACE,
1318                         selwin,
1319                         atoms[_NET_SYSTEM_TRAY_VISUAL],
1320                         XCB_ATOM_VISUALID,
1321                         32,
1322                         1,
1323                         &visual_type->visual_id);
1324
1325     init_tray_colors();
1326
1327     if (tray_reply == NULL) {
1328         if (!(tray_reply = xcb_intern_atom_reply(xcb_connection, tray_cookie, NULL))) {
1329             ELOG("Could not get atom %s\n", atomname);
1330             exit(EXIT_FAILURE);
1331         }
1332     }
1333
1334     xcb_set_selection_owner(xcb_connection,
1335                             selwin,
1336                             tray_reply->atom,
1337                             XCB_CURRENT_TIME);
1338
1339     /* Verify that we have the selection */
1340     xcb_get_selection_owner_cookie_t selcookie;
1341     xcb_get_selection_owner_reply_t *selreply;
1342
1343     selcookie = xcb_get_selection_owner(xcb_connection, tray_reply->atom);
1344     if (!(selreply = xcb_get_selection_owner_reply(xcb_connection, selcookie, NULL))) {
1345         ELOG("Could not get selection owner for %s\n", atomname);
1346         exit(EXIT_FAILURE);
1347     }
1348
1349     if (selreply->owner != selwin) {
1350         ELOG("Could not set the %s selection. "
1351              "Maybe another tray is already running?\n",
1352              atomname);
1353         /* NOTE that this error is not fatal. We just can’t provide tray
1354          * functionality */
1355         free(selreply);
1356         return;
1357     }
1358
1359     send_tray_clientmessage();
1360 }
1361
1362 /*
1363  * We need to set the _NET_SYSTEM_TRAY_COLORS atom on the tray selection window
1364  * to make GTK+ 3 applets with symbolic icons visible. If the colors are unset,
1365  * they assume a light background.
1366  * See also https://bugzilla.gnome.org/show_bug.cgi?id=679591
1367  *
1368  */
1369 void init_tray_colors(void) {
1370     /* Convert colors.bar_fg (#rrggbb) to 16-bit RGB */
1371     const char *bar_fg = (config.colors.bar_fg ? config.colors.bar_fg : "#FFFFFF");
1372
1373     DLOG("Setting bar_fg = %s as _NET_SYSTEM_TRAY_COLORS\n", bar_fg);
1374
1375     char strgroups[3][3] = {{bar_fg[1], bar_fg[2], '\0'},
1376                             {bar_fg[3], bar_fg[4], '\0'},
1377                             {bar_fg[5], bar_fg[6], '\0'}};
1378     const uint8_t r = strtol(strgroups[0], NULL, 16);
1379     const uint8_t g = strtol(strgroups[1], NULL, 16);
1380     const uint8_t b = strtol(strgroups[2], NULL, 16);
1381
1382     const uint16_t r16 = ((uint16_t)r << 8) | r;
1383     const uint16_t g16 = ((uint16_t)g << 8) | g;
1384     const uint16_t b16 = ((uint16_t)b << 8) | b;
1385
1386     const uint32_t tray_colors[12] = {
1387         r16, g16, b16, /* foreground color */
1388         r16, g16, b16, /* error color */
1389         r16, g16, b16, /* warning color */
1390         r16, g16, b16, /* success color */
1391     };
1392
1393     xcb_change_property(xcb_connection,
1394                         XCB_PROP_MODE_REPLACE,
1395                         selwin,
1396                         atoms[_NET_SYSTEM_TRAY_COLORS],
1397                         XCB_ATOM_CARDINAL,
1398                         32,
1399                         12,
1400                         tray_colors);
1401 }
1402
1403 /*
1404  * Cleanup the xcb stuff.
1405  * Called once, before the program terminates.
1406  *
1407  */
1408 void clean_xcb(void) {
1409     i3_output *o_walk;
1410     free_workspaces();
1411     SLIST_FOREACH(o_walk, outputs, slist) {
1412         destroy_window(o_walk);
1413         FREE(o_walk->trayclients);
1414         FREE(o_walk->workspaces);
1415         FREE(o_walk->name);
1416     }
1417     FREE_SLIST(outputs, i3_output);
1418     FREE(outputs);
1419
1420     xcb_flush(xcb_connection);
1421     xcb_aux_sync(xcb_connection);
1422     xcb_disconnect(xcb_connection);
1423
1424     ev_check_stop(main_loop, xcb_chk);
1425     ev_prepare_stop(main_loop, xcb_prep);
1426     ev_io_stop(main_loop, xcb_io);
1427
1428     FREE(xcb_chk);
1429     FREE(xcb_prep);
1430     FREE(xcb_io);
1431 }
1432
1433 /*
1434  * Get the earlier requested atoms and save them in the prepared data structure
1435  *
1436  */
1437 void get_atoms(void) {
1438     xcb_intern_atom_reply_t *reply;
1439 #define ATOM_DO(name)                                                        \
1440     reply = xcb_intern_atom_reply(xcb_connection, atom_cookies[name], NULL); \
1441     if (reply == NULL) {                                                     \
1442         ELOG("Could not get atom %s\n", #name);                              \
1443         exit(EXIT_FAILURE);                                                  \
1444     }                                                                        \
1445     atoms[name] = reply->atom;                                               \
1446     free(reply);
1447
1448 #include "xcb_atoms.def"
1449     DLOG("Got atoms\n");
1450 }
1451
1452 /*
1453  * Reparents all tray clients of the specified output to the root window. This
1454  * is either used when shutting down, when an output appears (xrandr --output
1455  * VGA1 --off) or when the primary output changes.
1456  *
1457  * Applications using the tray will start the protocol from the beginning again
1458  * afterwards.
1459  *
1460  */
1461 void kick_tray_clients(i3_output *output) {
1462     if (TAILQ_EMPTY(output->trayclients))
1463         return;
1464
1465     trayclient *trayclient;
1466     while (!TAILQ_EMPTY(output->trayclients)) {
1467         trayclient = TAILQ_FIRST(output->trayclients);
1468         /* Unmap, then reparent (to root) the tray client windows */
1469         xcb_unmap_window(xcb_connection, trayclient->win);
1470         xcb_reparent_window(xcb_connection,
1471                             trayclient->win,
1472                             xcb_root,
1473                             0,
1474                             0);
1475
1476         /* We remove the trayclient right here. We might receive an UnmapNotify
1477          * event afterwards, but better safe than sorry. */
1478         TAILQ_REMOVE(output->trayclients, trayclient, tailq);
1479     }
1480
1481     /* Fake a DestroyNotify so that Qt re-adds tray icons.
1482      * We cannot actually destroy the window because then Qt will not restore
1483      * its event mask on the new window. */
1484     uint8_t buffer[32] = {0};
1485     xcb_destroy_notify_event_t *event = (xcb_destroy_notify_event_t *)buffer;
1486
1487     event->response_type = XCB_DESTROY_NOTIFY;
1488     event->event = selwin;
1489     event->window = selwin;
1490
1491     xcb_send_event(conn, false, selwin, XCB_EVENT_MASK_STRUCTURE_NOTIFY, (char *)event);
1492
1493     send_tray_clientmessage();
1494 }
1495
1496 /*
1497  * Destroy the bar of the specified output
1498  *
1499  */
1500 void destroy_window(i3_output *output) {
1501     if (output == NULL) {
1502         return;
1503     }
1504     if (output->bar.id == XCB_NONE) {
1505         return;
1506     }
1507
1508     kick_tray_clients(output);
1509     xcb_destroy_window(xcb_connection, output->bar.id);
1510     output->bar.id = XCB_NONE;
1511 }
1512
1513 /*
1514  * Reallocate the statusline buffer
1515  *
1516  */
1517 void realloc_sl_buffer(void) {
1518     DLOG("Re-allocating statusline buffer, statusline_width = %d, root_screen->width_in_pixels = %d\n",
1519          statusline_width, root_screen->width_in_pixels);
1520     xcb_free_pixmap(xcb_connection, statusline_surface.id);
1521     draw_util_surface_free(&statusline_surface);
1522
1523     xcb_pixmap_t statusline_id = xcb_generate_id(xcb_connection);
1524     xcb_void_cookie_t sl_pm_cookie = xcb_create_pixmap_checked(xcb_connection,
1525                                                                depth,
1526                                                                statusline_id,
1527                                                                xcb_root,
1528                                                                MAX(root_screen->width_in_pixels, statusline_width),
1529                                                                bar_height);
1530     draw_util_surface_init(&statusline_surface, statusline_id, root_screen->width_in_pixels, root_screen->height_in_pixels);
1531
1532     if (xcb_request_failed(sl_pm_cookie, "Could not allocate statusline buffer"))
1533         exit(EXIT_FAILURE);
1534 }
1535
1536 /* Strut partial tells i3 where to reserve space for i3bar. This is determined
1537  * by the `position` bar config directive. */
1538 xcb_void_cookie_t config_strut_partial(i3_output *output) {
1539     /* A local struct to save the strut_partial property */
1540     struct {
1541         uint32_t left;
1542         uint32_t right;
1543         uint32_t top;
1544         uint32_t bottom;
1545         uint32_t left_start_y;
1546         uint32_t left_end_y;
1547         uint32_t right_start_y;
1548         uint32_t right_end_y;
1549         uint32_t top_start_x;
1550         uint32_t top_end_x;
1551         uint32_t bottom_start_x;
1552         uint32_t bottom_end_x;
1553     } __attribute__((__packed__)) strut_partial;
1554     memset(&strut_partial, 0, sizeof(strut_partial));
1555
1556     switch (config.position) {
1557         case POS_NONE:
1558             break;
1559         case POS_TOP:
1560             strut_partial.top = bar_height;
1561             strut_partial.top_start_x = output->rect.x;
1562             strut_partial.top_end_x = output->rect.x + output->rect.w;
1563             break;
1564         case POS_BOT:
1565             strut_partial.bottom = bar_height;
1566             strut_partial.bottom_start_x = output->rect.x;
1567             strut_partial.bottom_end_x = output->rect.x + output->rect.w;
1568             break;
1569     }
1570     return xcb_change_property(xcb_connection,
1571                                XCB_PROP_MODE_REPLACE,
1572                                output->bar.id,
1573                                atoms[_NET_WM_STRUT_PARTIAL],
1574                                XCB_ATOM_CARDINAL,
1575                                32,
1576                                12,
1577                                &strut_partial);
1578 }
1579
1580 /*
1581  * Reconfigure all bars and create new bars for recently activated outputs
1582  *
1583  */
1584 void reconfig_windows(bool redraw_bars) {
1585     uint32_t mask;
1586     uint32_t values[5];
1587     static bool tray_configured = false;
1588
1589     i3_output *walk;
1590     SLIST_FOREACH(walk, outputs, slist) {
1591         if (!walk->active) {
1592             /* If an output is not active, we destroy its bar */
1593             /* FIXME: Maybe we rather want to unmap? */
1594             DLOG("Destroying window for output %s\n", walk->name);
1595             destroy_window(walk);
1596             continue;
1597         }
1598         if (walk->bar.id == XCB_NONE) {
1599             DLOG("Creating window for output %s\n", walk->name);
1600
1601             xcb_window_t bar_id = xcb_generate_id(xcb_connection);
1602             xcb_pixmap_t buffer_id = xcb_generate_id(xcb_connection);
1603             mask = XCB_CW_BACK_PIXEL | XCB_CW_BORDER_PIXEL | XCB_CW_OVERRIDE_REDIRECT | XCB_CW_EVENT_MASK | XCB_CW_COLORMAP;
1604
1605             values[0] = colors.bar_bg.colorpixel;
1606             values[1] = root_screen->black_pixel;
1607             /* If hide_on_modifier is set to hide or invisible mode, i3 is not supposed to manage our bar windows */
1608             values[2] = (config.hide_on_modifier == M_DOCK ? 0 : 1);
1609             /* We enable the following EventMask fields:
1610              * EXPOSURE, to get expose events (we have to re-draw then)
1611              * SUBSTRUCTURE_REDIRECT, to get ConfigureRequests when the tray
1612              *                        child windows use ConfigureWindow
1613              * BUTTON_PRESS, to handle clicks on the workspace buttons
1614              * */
1615             values[3] = XCB_EVENT_MASK_EXPOSURE |
1616                         XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT |
1617                         XCB_EVENT_MASK_BUTTON_PRESS;
1618             if (config.hide_on_modifier == M_DOCK) {
1619                 /* If the bar is normally visible, catch visibility change events to suspend
1620                  * the status process when the bar is obscured by full-screened windows.  */
1621                 values[3] |= XCB_EVENT_MASK_VISIBILITY_CHANGE;
1622                 walk->visible = true;
1623             }
1624             values[4] = colormap;
1625
1626             xcb_void_cookie_t win_cookie = xcb_create_window_checked(xcb_connection,
1627                                                                      depth,
1628                                                                      bar_id,
1629                                                                      xcb_root,
1630                                                                      walk->rect.x, walk->rect.y + walk->rect.h - bar_height,
1631                                                                      walk->rect.w, bar_height,
1632                                                                      0,
1633                                                                      XCB_WINDOW_CLASS_INPUT_OUTPUT,
1634                                                                      visual_type->visual_id,
1635                                                                      mask,
1636                                                                      values);
1637
1638             /* The double-buffer we use to render stuff off-screen */
1639             xcb_void_cookie_t pm_cookie = xcb_create_pixmap_checked(xcb_connection,
1640                                                                     depth,
1641                                                                     buffer_id,
1642                                                                     bar_id,
1643                                                                     walk->rect.w,
1644                                                                     bar_height);
1645
1646             /* Set the WM_CLASS and WM_NAME (we don't need UTF-8) atoms */
1647             xcb_void_cookie_t class_cookie;
1648             class_cookie = xcb_change_property(xcb_connection,
1649                                                XCB_PROP_MODE_REPLACE,
1650                                                bar_id,
1651                                                XCB_ATOM_WM_CLASS,
1652                                                XCB_ATOM_STRING,
1653                                                8,
1654                                                (strlen("i3bar") + 1) * 2,
1655                                                "i3bar\0i3bar\0");
1656
1657             char *name;
1658             sasprintf(&name, "i3bar for output %s", walk->name);
1659             xcb_void_cookie_t name_cookie;
1660             name_cookie = xcb_change_property(xcb_connection,
1661                                               XCB_PROP_MODE_REPLACE,
1662                                               bar_id,
1663                                               XCB_ATOM_WM_NAME,
1664                                               XCB_ATOM_STRING,
1665                                               8,
1666                                               strlen(name),
1667                                               name);
1668             free(name);
1669
1670             /* We want dock windows (for now). When override_redirect is set, i3 is ignoring
1671              * this one */
1672             xcb_void_cookie_t dock_cookie = xcb_change_property(xcb_connection,
1673                                                                 XCB_PROP_MODE_REPLACE,
1674                                                                 bar_id,
1675                                                                 atoms[_NET_WM_WINDOW_TYPE],
1676                                                                 XCB_ATOM_ATOM,
1677                                                                 32,
1678                                                                 1,
1679                                                                 (unsigned char *)&atoms[_NET_WM_WINDOW_TYPE_DOCK]);
1680
1681             draw_util_surface_init(&walk->bar, bar_id, walk->rect.w, bar_height);
1682             draw_util_surface_init(&walk->buffer, buffer_id, walk->rect.w, bar_height);
1683
1684             xcb_void_cookie_t strut_cookie = config_strut_partial(walk);
1685
1686             /* We finally map the bar (display it on screen), unless the modifier-switch is on */
1687             xcb_void_cookie_t map_cookie;
1688             if (config.hide_on_modifier == M_DOCK) {
1689                 map_cookie = xcb_map_window_checked(xcb_connection, bar_id);
1690             }
1691
1692             if (xcb_request_failed(win_cookie, "Could not create window") ||
1693                 xcb_request_failed(pm_cookie, "Could not create pixmap") ||
1694                 xcb_request_failed(dock_cookie, "Could not set dock mode") ||
1695                 xcb_request_failed(class_cookie, "Could not set WM_CLASS") ||
1696                 xcb_request_failed(name_cookie, "Could not set WM_NAME") ||
1697                 xcb_request_failed(strut_cookie, "Could not set strut") ||
1698                 ((config.hide_on_modifier == M_DOCK) && xcb_request_failed(map_cookie, "Could not map window"))) {
1699                 exit(EXIT_FAILURE);
1700             }
1701
1702             const char *tray_output = (config.tray_output ? config.tray_output : SLIST_FIRST(outputs)->name);
1703             if (!tray_configured && strcasecmp(tray_output, "none") != 0) {
1704                 /* Configuration sanity check: ensure this i3bar instance handles the output on
1705                  * which the tray should appear (e.g. don’t initialize a tray if tray_output ==
1706                  * VGA-1 but output == [HDMI-1]).
1707                  */
1708                 i3_output *output;
1709                 SLIST_FOREACH(output, outputs, slist) {
1710                     if (strcasecmp(output->name, tray_output) == 0 ||
1711                         (strcasecmp(tray_output, "primary") == 0 && output->primary)) {
1712                         init_tray();
1713                         break;
1714                     }
1715                 }
1716                 tray_configured = true;
1717             }
1718         } else {
1719             /* We already have a bar, so we just reconfigure it */
1720             mask = XCB_CONFIG_WINDOW_X |
1721                    XCB_CONFIG_WINDOW_Y |
1722                    XCB_CONFIG_WINDOW_WIDTH |
1723                    XCB_CONFIG_WINDOW_HEIGHT |
1724                    XCB_CONFIG_WINDOW_STACK_MODE;
1725             values[0] = walk->rect.x;
1726             if (config.position == POS_TOP)
1727                 values[1] = walk->rect.y;
1728             else
1729                 values[1] = walk->rect.y + walk->rect.h - bar_height;
1730             values[2] = walk->rect.w;
1731             values[3] = bar_height;
1732             values[4] = XCB_STACK_MODE_ABOVE;
1733
1734             DLOG("Reconfiguring strut partial property for output %s\n", walk->name);
1735             xcb_void_cookie_t strut_cookie = config_strut_partial(walk);
1736
1737             DLOG("Destroying buffer for output %s\n", walk->name);
1738             xcb_free_pixmap(xcb_connection, walk->buffer.id);
1739
1740             DLOG("Reconfiguring window for output %s to %d,%d\n", walk->name, values[0], values[1]);
1741             xcb_void_cookie_t cfg_cookie = xcb_configure_window_checked(xcb_connection,
1742                                                                         walk->bar.id,
1743                                                                         mask,
1744                                                                         values);
1745
1746             mask = XCB_CW_OVERRIDE_REDIRECT;
1747             values[0] = (config.hide_on_modifier == M_DOCK ? 0 : 1);
1748             DLOG("Changing window attribute override_redirect for output %s to %d\n", walk->name, values[0]);
1749             xcb_void_cookie_t chg_cookie = xcb_change_window_attributes(xcb_connection,
1750                                                                         walk->bar.id,
1751                                                                         mask,
1752                                                                         values);
1753
1754             DLOG("Recreating buffer for output %s\n", walk->name);
1755             xcb_void_cookie_t pm_cookie = xcb_create_pixmap_checked(xcb_connection,
1756                                                                     depth,
1757                                                                     walk->buffer.id,
1758                                                                     walk->bar.id,
1759                                                                     walk->rect.w,
1760                                                                     bar_height);
1761
1762             draw_util_surface_free(&(walk->bar));
1763             draw_util_surface_free(&(walk->buffer));
1764             draw_util_surface_init(&(walk->bar), walk->bar.id, walk->rect.w, bar_height);
1765             draw_util_surface_init(&(walk->buffer), walk->buffer.id, walk->rect.w, bar_height);
1766
1767             xcb_void_cookie_t map_cookie, umap_cookie;
1768             if (redraw_bars) {
1769                 /* Unmap the window, and draw it again when in dock mode */
1770                 umap_cookie = xcb_unmap_window_checked(xcb_connection, walk->bar.id);
1771                 if (config.hide_on_modifier == M_DOCK) {
1772                     cont_child();
1773                     map_cookie = xcb_map_window_checked(xcb_connection, walk->bar.id);
1774                 } else {
1775                     stop_child();
1776                 }
1777
1778                 if (config.hide_on_modifier == M_HIDE) {
1779                     /* Switching to hide mode, register for keyevents */
1780                     register_xkb_keyevents();
1781                 } else {
1782                     /* Switching to dock/invisible mode, deregister from keyevents */
1783                     deregister_xkb_keyevents();
1784                 }
1785             }
1786
1787             if (xcb_request_failed(cfg_cookie, "Could not reconfigure window") ||
1788                 xcb_request_failed(chg_cookie, "Could not change window") ||
1789                 xcb_request_failed(pm_cookie, "Could not create pixmap") ||
1790                 xcb_request_failed(strut_cookie, "Could not set strut") ||
1791                 (redraw_bars && (xcb_request_failed(umap_cookie, "Could not unmap window") ||
1792                                  (config.hide_on_modifier == M_DOCK && xcb_request_failed(map_cookie, "Could not map window"))))) {
1793                 exit(EXIT_FAILURE);
1794             }
1795         }
1796     }
1797 }
1798
1799 /*
1800  * Render the bars, with buttons and statusline
1801  *
1802  */
1803 void draw_bars(bool unhide) {
1804     DLOG("Drawing bars...\n");
1805     int workspace_width = 0;
1806     /* Is the currently-rendered statusline using short_text items? */
1807     bool rendered_statusline_is_short = false;
1808
1809     refresh_statusline(false);
1810
1811     i3_output *outputs_walk;
1812     SLIST_FOREACH(outputs_walk, outputs, slist) {
1813         if (!outputs_walk->active) {
1814             DLOG("Output %s inactive, skipping...\n", outputs_walk->name);
1815             continue;
1816         }
1817         if (outputs_walk->bar.id == XCB_NONE) {
1818             /* Oh shit, an active output without an own bar. Create it now! */
1819             reconfig_windows(false);
1820         }
1821
1822         /* First things first: clear the backbuffer */
1823         draw_util_clear_surface(&(outputs_walk->buffer), colors.bar_bg);
1824
1825         if (!config.disable_ws) {
1826             i3_ws *ws_walk;
1827             TAILQ_FOREACH(ws_walk, outputs_walk->workspaces, tailq) {
1828                 DLOG("Drawing button for WS %s at x = %d, len = %d\n",
1829                      i3string_as_utf8(ws_walk->name), workspace_width, ws_walk->name_width);
1830                 color_t fg_color = colors.inactive_ws_fg;
1831                 color_t bg_color = colors.inactive_ws_bg;
1832                 color_t border_color = colors.inactive_ws_border;
1833                 if (ws_walk->visible) {
1834                     if (!ws_walk->focused) {
1835                         fg_color = colors.active_ws_fg;
1836                         bg_color = colors.active_ws_bg;
1837                         border_color = colors.active_ws_border;
1838                     } else {
1839                         fg_color = colors.focus_ws_fg;
1840                         bg_color = colors.focus_ws_bg;
1841                         border_color = colors.focus_ws_border;
1842                     }
1843                 }
1844                 if (ws_walk->urgent) {
1845                     DLOG("WS %s is urgent!\n", i3string_as_utf8(ws_walk->name));
1846                     fg_color = colors.urgent_ws_fg;
1847                     bg_color = colors.urgent_ws_bg;
1848                     border_color = colors.urgent_ws_border;
1849                     unhide = true;
1850                 }
1851
1852                 /* Draw the border of the button. */
1853                 draw_util_rectangle(&(outputs_walk->buffer), border_color,
1854                                     workspace_width,
1855                                     logical_px(1),
1856                                     ws_walk->name_width + 2 * logical_px(ws_hoff_px) + 2 * logical_px(1),
1857                                     font.height + 2 * logical_px(ws_voff_px) - 2 * logical_px(1));
1858
1859                 /* Draw the inside of the button. */
1860                 draw_util_rectangle(&(outputs_walk->buffer), bg_color,
1861                                     workspace_width + logical_px(1),
1862                                     2 * logical_px(1),
1863                                     ws_walk->name_width + 2 * logical_px(ws_hoff_px),
1864                                     font.height + 2 * logical_px(ws_voff_px) - 4 * logical_px(1));
1865
1866                 draw_util_text(ws_walk->name, &(outputs_walk->buffer), fg_color, bg_color,
1867                                workspace_width + logical_px(ws_hoff_px) + logical_px(1),
1868                                logical_px(ws_voff_px),
1869                                ws_walk->name_width);
1870
1871                 workspace_width += 2 * logical_px(ws_hoff_px) + 2 * logical_px(1) + ws_walk->name_width;
1872                 if (TAILQ_NEXT(ws_walk, tailq) != NULL)
1873                     workspace_width += logical_px(ws_spacing_px);
1874             }
1875         }
1876
1877         if (binding.name && !config.disable_binding_mode_indicator) {
1878             workspace_width += logical_px(ws_spacing_px);
1879
1880             color_t fg_color = colors.binding_mode_fg;
1881             color_t bg_color = colors.binding_mode_bg;
1882
1883             draw_util_rectangle(&(outputs_walk->buffer), colors.binding_mode_border,
1884                                 workspace_width,
1885                                 logical_px(1),
1886                                 binding.width + 2 * logical_px(ws_hoff_px) + 2 * logical_px(1),
1887                                 font.height + 2 * logical_px(ws_voff_px) - 2 * logical_px(1));
1888
1889             draw_util_rectangle(&(outputs_walk->buffer), bg_color,
1890                                 workspace_width + logical_px(1),
1891                                 2 * logical_px(1),
1892                                 binding.width + 2 * logical_px(ws_hoff_px),
1893                                 font.height + 2 * logical_px(ws_voff_px) - 4 * logical_px(1));
1894
1895             draw_util_text(binding.name, &(outputs_walk->buffer), fg_color, bg_color,
1896                            workspace_width + logical_px(ws_hoff_px) + logical_px(1),
1897                            logical_px(ws_voff_px),
1898                            binding.width);
1899
1900             unhide = true;
1901             workspace_width += 2 * logical_px(ws_hoff_px) + 2 * logical_px(1) + binding.width;
1902         }
1903
1904         if (!TAILQ_EMPTY(&statusline_head)) {
1905             DLOG("Printing statusline!\n");
1906
1907             int tray_width = get_tray_width(outputs_walk->trayclients);
1908             uint32_t max_statusline_width = outputs_walk->rect.w - workspace_width - tray_width - 2 * logical_px(sb_hoff_px);
1909
1910             /* If the statusline is too long, try to use short texts. */
1911             if (statusline_width > max_statusline_width) {
1912                 /* If the currently rendered statusline is long, render a short status line */
1913                 refresh_statusline(true);
1914                 rendered_statusline_is_short = true;
1915             } else if (rendered_statusline_is_short) {
1916                 /* If the currently rendered statusline is short, render a long status line */
1917                 refresh_statusline(false);
1918                 rendered_statusline_is_short = false;
1919             }
1920
1921             /* Luckily we already prepared a seperate pixmap containing the rendered
1922              * statusline, we just have to copy the relevant parts to the relevant
1923              * position */
1924             int visible_statusline_width = MIN(statusline_width, max_statusline_width);
1925             int x_src = (int16_t)(statusline_width - visible_statusline_width);
1926             int x_dest = (int16_t)(outputs_walk->rect.w - tray_width - logical_px(sb_hoff_px) - visible_statusline_width);
1927
1928             draw_util_copy_surface(&statusline_surface, &(outputs_walk->buffer), x_src, 0,
1929                                    x_dest, 0, (int16_t)visible_statusline_width, (int16_t)bar_height);
1930         }
1931
1932         workspace_width = 0;
1933     }
1934
1935     /* Assure the bar is hidden/unhidden according to the specified hidden_state and mode */
1936     if (mod_pressed ||
1937         config.hidden_state == S_SHOW ||
1938         unhide) {
1939         unhide_bars();
1940     } else if (config.hide_on_modifier == M_HIDE) {
1941         hide_bars();
1942     }
1943
1944     redraw_bars();
1945 }
1946
1947 /*
1948  * Redraw the bars, i.e. simply copy the buffer to the barwindow
1949  *
1950  */
1951 void redraw_bars(void) {
1952     i3_output *outputs_walk;
1953     SLIST_FOREACH(outputs_walk, outputs, slist) {
1954         if (!outputs_walk->active) {
1955             continue;
1956         }
1957
1958         draw_util_copy_surface(&(outputs_walk->buffer), &(outputs_walk->bar), 0, 0,
1959                                0, 0, outputs_walk->rect.w, outputs_walk->rect.h);
1960         xcb_flush(xcb_connection);
1961     }
1962 }
1963
1964 /*
1965  * Set the current binding mode
1966  *
1967  */
1968 void set_current_mode(struct mode *current) {
1969     I3STRING_FREE(binding.name);
1970     binding = *current;
1971     activated_mode = binding.name != NULL;
1972     return;
1973 }