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