]> git.sur5r.net Git - i3/i3/blobdiff - i3bar/src/xcb.c
Add "modifiers" to events sent by i3bar
[i3/i3] / i3bar / src / xcb.c
index 841570e260fa3c29dc91f5bf31111e2a146ea6f9..31ae08f05c01d9c87d8fb0488a9c83aa1a25cffe 100644 (file)
@@ -7,14 +7,13 @@
  * xcb.c: Communicating with X
  *
  */
+#include "common.h"
+
 #include <xcb/xcb.h>
 #include <xcb/xkb.h>
 #include <xcb/xproto.h>
 #include <xcb/xcb_aux.h>
-
-#ifdef XCB_COMPAT
-#include "xcb_compat.h"
-#endif
+#include <xcb/xcb_cursor.h>
 
 #include <stdio.h>
 #include <stdlib.h>
 #include <X11/XKBlib.h>
 #include <X11/extensions/XKB.h>
 
-#include "common.h"
+#ifdef I3_ASAN_ENABLED
+#include <sanitizer/lsan_interface.h>
+#endif
+
 #include "libi3.h"
 
+/** This is the equivalent of XC_left_ptr. I’m not sure why xcb doesn’t have a
+ * constant for that. */
+#define XCB_CURSOR_LEFT_PTR 68
+
 /* We save the atoms in an easy to access array, indexed by an enum */
 enum {
 #define ATOM_DO(name) name,
@@ -49,6 +55,7 @@ xcb_connection_t *xcb_connection;
 int screen;
 xcb_screen_t *root_screen;
 xcb_window_t xcb_root;
+static xcb_cursor_t cursor;
 
 /* selection window for tray support */
 static xcb_window_t selwin = XCB_NONE;
@@ -72,11 +79,10 @@ int bar_height;
 
 /* These are only relevant for XKB, which we only need for grabbing modifiers */
 int xkb_base;
-int mod_pressed = 0;
+bool mod_pressed = 0;
 
 /* Event watchers, to interact with the user */
 ev_prepare *xcb_prep;
-ev_check *xcb_chk;
 ev_io *xcb_io;
 ev_io *xkb_io;
 
@@ -86,11 +92,17 @@ static mode binding;
 /* Indicates whether a new binding mode was recently activated */
 bool activated_mode = false;
 
+/* The output in which the tray should be displayed. */
+static i3_output *output_for_tray;
+
 /* The parsed colors */
 struct xcb_colors_t {
     color_t bar_fg;
     color_t bar_bg;
     color_t sep_fg;
+    color_t focus_bar_fg;
+    color_t focus_bar_bg;
+    color_t focus_sep_fg;
     color_t active_ws_fg;
     color_t active_ws_bg;
     color_t active_ws_border;
@@ -137,13 +149,13 @@ int _xcb_request_failed(xcb_void_cookie_t cookie, char *err_msg, int line) {
     return 0;
 }
 
-uint32_t get_sep_offset(struct status_block *block) {
+static uint32_t get_sep_offset(struct status_block *block) {
     if (!block->no_separator && block->sep_block_width > 0)
         return block->sep_block_width / 2 + block->sep_block_width % 2;
     return 0;
 }
 
-int get_tray_width(struct tc_head *trayclients) {
+static int get_tray_width(struct tc_head *trayclients) {
     trayclient *trayclient;
     int tray_width = 0;
     TAILQ_FOREACH_REVERSE(trayclient, trayclients, tc_head, tailq) {
@@ -160,7 +172,10 @@ int get_tray_width(struct tc_head *trayclients) {
  * Draws a separator for the given block if necessary.
  *
  */
-static void draw_separator(i3_output *output, uint32_t x, struct status_block *block) {
+static void draw_separator(i3_output *output, uint32_t x, struct status_block *block, bool use_focus_colors) {
+    color_t sep_fg = (use_focus_colors ? colors.focus_sep_fg : colors.sep_fg);
+    color_t bar_bg = (use_focus_colors ? colors.focus_bar_bg : colors.bar_bg);
+
     uint32_t sep_offset = get_sep_offset(block);
     if (TAILQ_NEXT(block, blocks) == NULL || sep_offset == 0)
         return;
@@ -168,7 +183,7 @@ static void draw_separator(i3_output *output, uint32_t x, struct status_block *b
     uint32_t center_x = x - sep_offset;
     if (config.separator_symbol == NULL) {
         /* Draw a classic one pixel, vertical separator. */
-        draw_util_rectangle(&output->statusline_buffer, colors.sep_fg,
+        draw_util_rectangle(&output->statusline_buffer, sep_fg,
                             center_x,
                             logical_px(sep_voff_px),
                             logical_px(1),
@@ -176,12 +191,12 @@ static void draw_separator(i3_output *output, uint32_t x, struct status_block *b
     } else {
         /* Draw a custom separator. */
         uint32_t separator_x = MAX(x - block->sep_block_width, center_x - separator_symbol_width / 2);
-        draw_util_text(config.separator_symbol, &output->statusline_buffer, colors.sep_fg, colors.bar_bg,
+        draw_util_text(config.separator_symbol, &output->statusline_buffer, sep_fg, bar_bg,
                        separator_x, logical_px(ws_voff_px), x - separator_x);
     }
 }
 
-uint32_t predict_statusline_length(bool use_short_text) {
+static uint32_t predict_statusline_length(bool use_short_text) {
     uint32_t width = 0;
     struct status_block *block;
 
@@ -233,10 +248,11 @@ uint32_t predict_statusline_length(bool use_short_text) {
 /*
  * Redraws the statusline to the output's statusline_buffer
  */
-void draw_statusline(i3_output *output, uint32_t clip_left, bool use_short_text) {
+static void draw_statusline(i3_output *output, uint32_t clip_left, bool use_focus_colors, bool use_short_text) {
     struct status_block *block;
 
-    draw_util_clear_surface(&output->statusline_buffer, colors.bar_bg);
+    color_t bar_color = (use_focus_colors ? colors.focus_bar_bg : colors.bar_bg);
+    draw_util_clear_surface(&output->statusline_buffer, bar_color);
 
     /* Use unsigned integer wraparound to clip off the left side.
      * For example, if clip_left is 75, then x will start at the very large
@@ -263,17 +279,19 @@ void draw_statusline(i3_output *output, uint32_t clip_left, bool use_short_text)
             fg_color = colors.urgent_ws_fg;
         } else if (block->color) {
             fg_color = draw_util_hex_to_color(block->color);
+        } else if (use_focus_colors) {
+            fg_color = colors.focus_bar_fg;
         } else {
             fg_color = colors.bar_fg;
         }
 
-        color_t bg_color = colors.bar_bg;
+        color_t bg_color = bar_color;
 
         int border_width = (block->border) ? logical_px(1) : 0;
         int full_render_width = render->width + render->x_offset + render->x_append;
         if (block->border || block->background || block->urgent) {
             /* Let's determine the colors first. */
-            color_t border_color = colors.bar_bg;
+            color_t border_color = bar_color;
             if (block->urgent) {
                 border_color = colors.urgent_ws_border;
                 bg_color = colors.urgent_ws_bg;
@@ -306,7 +324,7 @@ void draw_statusline(i3_output *output, uint32_t clip_left, bool use_short_text)
         /* If this is not the last block, draw a separator. */
         if (TAILQ_NEXT(block, blocks) != NULL) {
             x += block->sep_block_width;
-            draw_separator(output, x, block);
+            draw_separator(output, x, block, use_focus_colors);
         }
     }
 }
@@ -315,7 +333,7 @@ void draw_statusline(i3_output *output, uint32_t clip_left, bool use_short_text)
  * Hides all bars (unmaps them)
  *
  */
-void hide_bars(void) {
+static void hide_bars(void) {
     if ((config.hide_on_modifier == M_DOCK) || (config.hidden_state == S_SHOW && config.hide_on_modifier == M_HIDE)) {
         return;
     }
@@ -334,7 +352,7 @@ void hide_bars(void) {
  * Unhides all bars (maps them)
  *
  */
-void unhide_bars(void) {
+static void unhide_bars(void) {
     if (config.hide_on_modifier != M_HIDE) {
         return;
     }
@@ -412,19 +430,37 @@ void init_colors(const struct xcb_color_strings_t *new_colors) {
     PARSE_COLOR_FALLBACK(binding_mode_fg, urgent_ws_fg);
     PARSE_COLOR_FALLBACK(binding_mode_bg, urgent_ws_bg);
     PARSE_COLOR_FALLBACK(binding_mode_border, urgent_ws_border);
+
+    /* Similarly, for unspecified focused bar colors, we fall back to the
+     * regular bar colors. */
+    PARSE_COLOR_FALLBACK(focus_bar_fg, bar_fg);
+    PARSE_COLOR_FALLBACK(focus_bar_bg, bar_bg);
+    PARSE_COLOR_FALLBACK(focus_sep_fg, sep_fg);
 #undef PARSE_COLOR_FALLBACK
 
     init_tray_colors();
     xcb_flush(xcb_connection);
 }
 
+static bool execute_custom_command(xcb_keycode_t input_code, bool event_is_release) {
+    binding_t *binding;
+    TAILQ_FOREACH(binding, &(config.bindings), bindings) {
+        if ((binding->input_code != input_code) || (binding->release != event_is_release))
+            continue;
+
+        i3_send_msg(I3_IPC_MESSAGE_TYPE_RUN_COMMAND, binding->command);
+        return true;
+    }
+    return false;
+}
+
 /*
  * Handle a button press event (i.e. a mouse click on one of our bars).
- * We determine, whether the click occured on a workspace button or if the scroll-
+ * We determine, whether the click occurred on a workspace button or if the scroll-
  * wheel was used and change the workspace appropriately
  *
  */
-void handle_button(xcb_button_press_event_t *event) {
+static void handle_button(xcb_button_press_event_t *event) {
     /* Determine, which bar was clicked */
     i3_output *walk;
     xcb_window_t bar = event->event;
@@ -439,10 +475,16 @@ void handle_button(xcb_button_press_event_t *event) {
         return;
     }
 
-    int32_t x = event->event_x >= 0 ? event->event_x : 0;
-
     DLOG("Got button %d\n", event->detail);
 
+    /* During button release events, only check for custom commands. */
+    const bool event_is_release = (event->response_type & ~0x80) == XCB_BUTTON_RELEASE;
+    if (event_is_release) {
+        execute_custom_command(event->detail, event_is_release);
+        return;
+    }
+
+    int32_t x = event->event_x >= 0 ? event->event_x : 0;
     int workspace_width = 0;
     i3_ws *cur_ws = NULL, *clicked_ws = NULL, *ws_walk;
 
@@ -461,13 +503,12 @@ void handle_button(xcb_button_press_event_t *event) {
         /* If the child asked for click events,
          * check if a status block has been clicked. */
         int tray_width = get_tray_width(walk->trayclients);
-        int block_x = 0, last_block_x;
-        int offset = walk->rect.w - walk->statusline_width - tray_width - logical_px(sb_hoff_px);
+        int last_block_x = 0;
+        int offset = walk->rect.w - walk->statusline_width - tray_width - logical_px((tray_width > 0) * sb_hoff_px);
         int32_t statusline_x = x - offset;
 
         if (statusline_x >= 0 && statusline_x < walk->statusline_width) {
             struct status_block *block;
-            int sep_offset_remainder = 0;
 
             TAILQ_FOREACH(block, &statusline_head, blocks) {
                 i3String *text = block->full_text;
@@ -480,27 +521,22 @@ void handle_button(xcb_button_press_event_t *event) {
                 if (i3string_get_num_bytes(text) == 0)
                     continue;
 
-                last_block_x = block_x;
-                block_x += render->width + render->x_offset + render->x_append + get_sep_offset(block) + sep_offset_remainder;
-
-                if (statusline_x <= block_x && statusline_x >= last_block_x) {
-                    send_block_clicked(event->detail, block->name, block->instance, event->root_x, event->root_y);
+                const int relative_x = statusline_x - last_block_x;
+                if (relative_x >= 0 && (uint32_t)relative_x <= render->width) {
+                    send_block_clicked(event->detail, block->name, block->instance,
+                                       event->root_x, event->root_y, relative_x, event->event_y, render->width, bar_height,
+                                       event->state);
                     return;
                 }
 
-                sep_offset_remainder = block->sep_block_width - get_sep_offset(block);
+                last_block_x += render->width + render->x_append + render->x_offset + block->sep_block_width;
             }
         }
     }
 
     /* If a custom command was specified for this mouse button, it overrides
      * the default behavior. */
-    binding_t *binding;
-    TAILQ_FOREACH(binding, &(config.bindings), bindings) {
-        if (binding->input_code != event->detail)
-            continue;
-
-        i3_send_msg(I3_IPC_MESSAGE_TYPE_COMMAND, binding->command);
+    if (execute_custom_command(event->detail, event_is_release)) {
         return;
     }
 
@@ -509,7 +545,8 @@ void handle_button(xcb_button_press_event_t *event) {
         return;
     }
     switch (event->detail) {
-        case 4:
+        case XCB_BUTTON_SCROLL_UP:
+        case XCB_BUTTON_SCROLL_LEFT:
             /* Mouse wheel up. We select the previous ws, if any.
              * If there is no more workspace, don’t even send the workspace
              * command, otherwise (with workspace auto_back_and_forth) we’d end
@@ -519,7 +556,8 @@ void handle_button(xcb_button_press_event_t *event) {
 
             cur_ws = TAILQ_PREV(cur_ws, ws_head, tailq);
             break;
-        case 5:
+        case XCB_BUTTON_SCROLL_DOWN:
+        case XCB_BUTTON_SCROLL_RIGHT:
             /* Mouse wheel down. We select the next ws, if any.
              * If there is no more workspace, don’t even send the workspace
              * command, otherwise (with workspace auto_back_and_forth) we’d end
@@ -567,7 +605,7 @@ void handle_button(xcb_button_press_event_t *event) {
 
     const size_t len = namelen + strlen("workspace \"\"") + 1;
     char *buffer = scalloc(len + num_quotes, 1);
-    strncpy(buffer, "workspace \"", strlen("workspace \""));
+    memcpy(buffer, "workspace \"", strlen("workspace \""));
     size_t inpos, outpos;
     for (inpos = 0, outpos = strlen("workspace \"");
          inpos < namelen;
@@ -579,7 +617,7 @@ void handle_button(xcb_button_press_event_t *event) {
         buffer[outpos] = utf8_name[inpos];
     }
     buffer[outpos] = '"';
-    i3_send_msg(I3_IPC_MESSAGE_TYPE_COMMAND, buffer);
+    i3_send_msg(I3_IPC_MESSAGE_TYPE_RUN_COMMAND, buffer);
     free(buffer);
 }
 
@@ -654,8 +692,17 @@ static void configure_trayclients(void) {
  *
  */
 static void handle_client_message(xcb_client_message_event_t *event) {
-    if (event->type == atoms[_NET_SYSTEM_TRAY_OPCODE] &&
-        event->format == 32) {
+    if (event->type == atoms[I3_SYNC]) {
+        xcb_window_t window = event->data.data32[0];
+        uint32_t rnd = event->data.data32[1];
+        /* Forward the request to i3 via the IPC interface so that all pending
+         * IPC messages are guaranteed to be handled. */
+        char *payload = NULL;
+        sasprintf(&payload, "{\"rnd\":%d, \"window\":%d}", rnd, window);
+        i3_send_msg(I3_IPC_MESSAGE_TYPE_SYNC, payload);
+        free(payload);
+    } else if (event->type == atoms[_NET_SYSTEM_TRAY_OPCODE] &&
+               event->format == 32) {
         DLOG("_NET_SYSTEM_TRAY_OPCODE received\n");
         /* event->data.data32[0] is the timestamp */
         uint32_t op = event->data.data32[1];
@@ -664,15 +711,17 @@ static void handle_client_message(xcb_client_message_event_t *event) {
         if (op == SYSTEM_TRAY_REQUEST_DOCK) {
             xcb_window_t client = event->data.data32[2];
 
-            /* Listen for PropertyNotify events to get the most recent value of
-             * the XEMBED_MAPPED atom, also listen for UnmapNotify events */
             mask = XCB_CW_EVENT_MASK;
-            values[0] = XCB_EVENT_MASK_PROPERTY_CHANGE |
-                        XCB_EVENT_MASK_STRUCTURE_NOTIFY;
-            xcb_change_window_attributes(xcb_connection,
-                                         client,
-                                         mask,
-                                         values);
+
+            /* Needed to get the most recent value of XEMBED_MAPPED. */
+            values[0] = XCB_EVENT_MASK_PROPERTY_CHANGE;
+            /* Needed for UnmapNotify events. */
+            values[0] |= XCB_EVENT_MASK_STRUCTURE_NOTIFY;
+            /* Needed because some tray applications (e.g., VLC) use
+             * override_redirect which causes no ConfigureRequest to be sent. */
+            values[0] |= XCB_EVENT_MASK_RESIZE_REDIRECT;
+
+            xcb_change_window_attributes(xcb_connection, client, mask, values);
 
             /* Request the _XEMBED_INFO property. The XEMBED specification
              * (which is referred by the tray specification) says this *has* to
@@ -713,42 +762,16 @@ static void handle_client_message(xcb_client_message_event_t *event) {
             }
 
             DLOG("X window %08x requested docking\n", client);
-            i3_output *walk, *output = NULL;
-            SLIST_FOREACH(walk, outputs, slist) {
-                if (!walk->active)
-                    continue;
-                if (config.tray_output) {
-                    if ((strcasecmp(walk->name, config.tray_output) != 0) &&
-                        (!walk->primary || strcasecmp("primary", config.tray_output) != 0))
-                        continue;
-                }
 
-                DLOG("using output %s\n", walk->name);
-                output = walk;
-                break;
-            }
-            /* In case of tray_output == primary and there is no primary output
-             * configured, we fall back to the first available output. */
-            if (output == NULL &&
-                config.tray_output &&
-                strcasecmp("primary", config.tray_output) == 0) {
-                SLIST_FOREACH(walk, outputs, slist) {
-                    if (!walk->active)
-                        continue;
-                    DLOG("Falling back to output %s because no primary output is configured\n", walk->name);
-                    output = walk;
-                    break;
-                }
-            }
-            if (output == NULL) {
-                ELOG("No output found\n");
+            if (output_for_tray == NULL) {
+                ELOG("No output found for tray\n");
                 return;
             }
 
             xcb_void_cookie_t rcookie = xcb_reparent_window(xcb_connection,
                                                             client,
-                                                            output->bar.id,
-                                                            output->rect.w - icon_size - logical_px(config.tray_padding),
+                                                            output_for_tray->bar.id,
+                                                            output_for_tray->rect.w - icon_size - logical_px(config.tray_padding),
                                                             logical_px(config.tray_padding));
             if (xcb_request_failed(rcookie, "Could not reparent window. Maybe it is using an incorrect depth/visual?"))
                 return;
@@ -774,8 +797,8 @@ static void handle_client_message(xcb_client_message_event_t *event) {
             ev->type = atoms[_XEMBED];
             ev->format = 32;
             ev->data.data32[0] = XCB_CURRENT_TIME;
-            ev->data.data32[1] = atoms[XEMBED_EMBEDDED_NOTIFY];
-            ev->data.data32[2] = output->bar.id;
+            ev->data.data32[1] = XEMBED_EMBEDDED_NOTIFY;
+            ev->data.data32[2] = output_for_tray->bar.id;
             ev->data.data32[3] = xe_version;
             xcb_send_event(xcb_connection,
                            0,
@@ -795,7 +818,7 @@ static void handle_client_message(xcb_client_message_event_t *event) {
             tc->win = client;
             tc->xe_version = xe_version;
             tc->mapped = false;
-            TAILQ_INSERT_TAIL(output->trayclients, tc, tailq);
+            TAILQ_INSERT_TAIL(output_for_tray->trayclients, tc, tailq);
 
             if (map_it) {
                 DLOG("Mapping dock client\n");
@@ -817,7 +840,7 @@ static void handle_client_message(xcb_client_message_event_t *event) {
  * client to finish the protocol. After this event is received, there is no
  * further interaction with the tray client.
  *
- * See: http://standards.freedesktop.org/xembed-spec/xembed-spec-latest.html
+ * See: https://standards.freedesktop.org/xembed-spec/xembed-spec-latest.html
  *
  */
 static void handle_destroy_notify(xcb_destroy_notify_event_t *event) {
@@ -830,11 +853,13 @@ static void handle_destroy_notify(xcb_destroy_notify_event_t *event) {
         DLOG("checking output %s\n", walk->name);
         trayclient *trayclient;
         TAILQ_FOREACH(trayclient, walk->trayclients, tailq) {
-            if (trayclient->win != event->window)
+            if (trayclient->win != event->window) {
                 continue;
+            }
 
             DLOG("Removing tray client with window ID %08x\n", event->window);
             TAILQ_REMOVE(walk->trayclients, trayclient, tailq);
+            FREE(trayclient);
 
             /* Trigger an update, we now have more space for the statusline */
             configure_trayclients();
@@ -967,13 +992,11 @@ static void handle_property_notify(xcb_property_notify_event_t *event) {
 }
 
 /*
- * Handle ConfigureRequests by denying them and sending the client a
- * ConfigureNotify with its actual size.
+ * If a tray client attempts to change its size we deny the request and respond
+ * by telling it its actual size.
  *
  */
-static void handle_configure_request(xcb_configure_request_event_t *event) {
-    DLOG("ConfigureRequest for window = %08x\n", event->window);
-
+static void handle_configuration_change(xcb_window_t window) {
     trayclient *trayclient;
     i3_output *output;
     SLIST_FOREACH(output, outputs, slist) {
@@ -986,7 +1009,7 @@ static void handle_configure_request(xcb_configure_request_event_t *event) {
                 continue;
             clients++;
 
-            if (trayclient->win != event->window)
+            if (trayclient->win != window)
                 continue;
 
             xcb_rectangle_t rect;
@@ -996,7 +1019,7 @@ static void handle_configure_request(xcb_configure_request_event_t *event) {
             rect.height = icon_size;
 
             DLOG("This is a tray window. x = %d\n", rect.x);
-            fake_configure_notify(xcb_connection, rect, event->window, 0);
+            fake_configure_notify(xcb_connection, rect, window, 0);
             return;
         }
     }
@@ -1004,26 +1027,29 @@ static void handle_configure_request(xcb_configure_request_event_t *event) {
     DLOG("WARNING: Could not find corresponding tray window.\n");
 }
 
-/*
- * This function is called immediately before the main loop locks. We flush xcb
- * then (and only then)
- *
- */
-void xcb_prep_cb(struct ev_loop *loop, ev_prepare *watcher, int revents) {
-    xcb_flush(xcb_connection);
+static void handle_configure_request(xcb_configure_request_event_t *event) {
+    DLOG("ConfigureRequest for window = %08x\n", event->window);
+    handle_configuration_change(event->window);
+}
+
+static void handle_resize_request(xcb_resize_request_event_t *event) {
+    DLOG("ResizeRequest for window = %08x\n", event->window);
+    handle_configuration_change(event->window);
 }
 
 /*
- * This function is called immediately after the main loop locks, so when one
- * of the watchers registered an event.
- * We check whether an X-Event arrived and handle it.
+ * This function is called immediately before the main loop locks. We check for
+ * events from X11, handle them, then flush our outgoing queue.
  *
  */
-void xcb_chk_cb(struct ev_loop *loop, ev_check *watcher, int revents) {
+static void xcb_prep_cb(struct ev_loop *loop, ev_prepare *watcher, int revents) {
     xcb_generic_event_t *event;
 
     if (xcb_connection_has_error(xcb_connection)) {
         ELOG("X11 connection was closed unexpectedly - maybe your X server terminated / crashed?\n");
+#ifdef I3_ASAN_ENABLED
+        __lsan_do_leak_check();
+#endif
         exit(1);
     }
 
@@ -1041,49 +1067,18 @@ void xcb_chk_cb(struct ev_loop *loop, ev_check *watcher, int revents) {
             DLOG("received an xkb event\n");
 
             xcb_xkb_state_notify_event_t *state = (xcb_xkb_state_notify_event_t *)event;
-            if (state->xkbType == XCB_XKB_STATE_NOTIFY) {
-                int modstate = state->mods & config.modifier;
-
-#define DLOGMOD(modmask, status)                        \
-    do {                                                \
-        switch (modmask) {                              \
-            case ShiftMask:                             \
-                DLOG("ShiftMask got " #status "!\n");   \
-                break;                                  \
-            case ControlMask:                           \
-                DLOG("ControlMask got " #status "!\n"); \
-                break;                                  \
-            case Mod1Mask:                              \
-                DLOG("Mod1Mask got " #status "!\n");    \
-                break;                                  \
-            case Mod2Mask:                              \
-                DLOG("Mod2Mask got " #status "!\n");    \
-                break;                                  \
-            case Mod3Mask:                              \
-                DLOG("Mod3Mask got " #status "!\n");    \
-                break;                                  \
-            case Mod4Mask:                              \
-                DLOG("Mod4Mask got " #status "!\n");    \
-                break;                                  \
-            case Mod5Mask:                              \
-                DLOG("Mod5Mask got " #status "!\n");    \
-                break;                                  \
-        }                                               \
-    } while (0)
-
-                if (modstate != mod_pressed) {
-                    if (modstate == 0) {
-                        DLOGMOD(config.modifier, released);
-                        if (!activated_mode)
-                            hide_bars();
-                    } else {
-                        DLOGMOD(config.modifier, pressed);
+            const uint32_t mod = (config.modifier & 0xFFFF);
+            const bool new_mod_pressed = (mod != 0 && (state->mods & mod) == mod);
+            if (new_mod_pressed != mod_pressed) {
+                mod_pressed = new_mod_pressed;
+                if (state->xkbType == XCB_XKB_STATE_NOTIFY && config.modifier != XCB_NONE) {
+                    if (mod_pressed) {
                         activated_mode = false;
                         unhide_bars();
+                    } else if (!activated_mode) {
+                        hide_bars();
                     }
-                    mod_pressed = modstate;
                 }
-#undef DLOGMOD
             }
 
             free(event);
@@ -1096,9 +1091,13 @@ void xcb_chk_cb(struct ev_loop *loop, ev_check *watcher, int revents) {
                 handle_visibility_notify((xcb_visibility_notify_event_t *)event);
                 break;
             case XCB_EXPOSE:
-                /* Expose-events happen, when the window needs to be redrawn */
-                redraw_bars();
+                if (((xcb_expose_event_t *)event)->count == 0) {
+                    /* Expose-events happen, when the window needs to be redrawn */
+                    redraw_bars();
+                }
+
                 break;
+            case XCB_BUTTON_RELEASE:
             case XCB_BUTTON_PRESS:
                 /* Button press events are mouse buttons clicked on one of our bars */
                 handle_button((xcb_button_press_event_t *)event);
@@ -1127,9 +1126,15 @@ void xcb_chk_cb(struct ev_loop *loop, ev_check *watcher, int revents) {
                 /* ConfigureRequest, sent by a tray child */
                 handle_configure_request((xcb_configure_request_event_t *)event);
                 break;
+            case XCB_RESIZE_REQUEST:
+                /* ResizeRequest sent by a tray child using override_redirect. */
+                handle_resize_request((xcb_resize_request_event_t *)event);
+                break;
         }
         free(event);
     }
+
+    xcb_flush(xcb_connection);
 }
 
 /*
@@ -1137,7 +1142,7 @@ void xcb_chk_cb(struct ev_loop *loop, ev_check *watcher, int revents) {
  * are triggered
  *
  */
-void xcb_io_cb(struct ev_loop *loop, ev_io *watcher, int revents) {
+static void xcb_io_cb(struct ev_loop *loop, ev_io *watcher, int revents) {
 }
 
 /*
@@ -1145,7 +1150,7 @@ void xcb_io_cb(struct ev_loop *loop, ev_io *watcher, int revents) {
  * depend on 'config'.
  *
  */
-char *init_xcb_early() {
+char *init_xcb_early(void) {
     /* FIXME: xcb_connect leaks memory */
     xcb_connection = xcb_connect(NULL, &screen);
     if (xcb_connection_has_error(xcb_connection)) {
@@ -1166,18 +1171,33 @@ char *init_xcb_early() {
     colormap = root_screen->default_colormap;
     visual_type = get_visualtype(root_screen);
 
+    xcb_cursor_context_t *cursor_ctx;
+    if (xcb_cursor_context_new(conn, root_screen, &cursor_ctx) == 0) {
+        cursor = xcb_cursor_load_cursor(cursor_ctx, "left_ptr");
+        xcb_cursor_context_free(cursor_ctx);
+    } else {
+        cursor = xcb_generate_id(xcb_connection);
+        i3Font cursor_font = load_font("cursor", false);
+        xcb_create_glyph_cursor(
+            xcb_connection,
+            cursor,
+            cursor_font.specific.xcb.id,
+            cursor_font.specific.xcb.id,
+            XCB_CURSOR_LEFT_PTR,
+            XCB_CURSOR_LEFT_PTR + 1,
+            0, 0, 0,
+            65535, 65535, 65535);
+    }
+
     /* The various watchers to communicate with xcb */
     xcb_io = smalloc(sizeof(ev_io));
     xcb_prep = smalloc(sizeof(ev_prepare));
-    xcb_chk = smalloc(sizeof(ev_check));
 
     ev_io_init(xcb_io, &xcb_io_cb, xcb_get_file_descriptor(xcb_connection), EV_READ);
     ev_prepare_init(xcb_prep, &xcb_prep_cb);
-    ev_check_init(xcb_chk, &xcb_chk_cb);
 
     ev_io_start(main_loop, xcb_io);
     ev_prepare_start(main_loop, xcb_prep);
-    ev_check_start(main_loop, xcb_chk);
 
     /* Now we get the atoms and save them in a nice data structure */
     get_atoms();
@@ -1193,7 +1213,7 @@ char *init_xcb_early() {
  * in xcb.
  *
  */
-void register_xkb_keyevents() {
+static void register_xkb_keyevents(void) {
     const xcb_query_extension_reply_t *extreply;
     extreply = xcb_get_extension_data(conn, &xcb_xkb_id);
     if (!extreply->present) {
@@ -1217,7 +1237,7 @@ void register_xkb_keyevents() {
  * Deregister from xkb keyevents.
  *
  */
-void deregister_xkb_keyevents() {
+static void deregister_xkb_keyevents(void) {
     xcb_xkb_select_events(conn,
                           XCB_XKB_ID_USE_CORE_KBD,
                           0,
@@ -1283,7 +1303,7 @@ static void send_tray_clientmessage(void) {
  * atom. Afterwards, tray clients will send ClientMessages to our window.
  *
  */
-void init_tray(void) {
+static void init_tray(void) {
     DLOG("Initializing system tray functionality\n");
     /* request the tray manager atom for the X11 display we are running on */
     char atomname[strlen("_NET_SYSTEM_TRAY_S") + 11];
@@ -1361,6 +1381,8 @@ void init_tray(void) {
         return;
     }
 
+    free(selreply);
+
     send_tray_clientmessage();
 }
 
@@ -1411,26 +1433,18 @@ void init_tray_colors(void) {
  *
  */
 void clean_xcb(void) {
-    i3_output *o_walk;
-    free_workspaces();
-    SLIST_FOREACH(o_walk, outputs, slist) {
-        destroy_window(o_walk);
-        FREE(o_walk->trayclients);
-        FREE(o_walk->workspaces);
-        FREE(o_walk->name);
-    }
-    FREE_SLIST(outputs, i3_output);
-    FREE(outputs);
+    free_outputs();
 
+    free_font();
+
+    xcb_free_cursor(xcb_connection, cursor);
     xcb_flush(xcb_connection);
     xcb_aux_sync(xcb_connection);
     xcb_disconnect(xcb_connection);
 
-    ev_check_stop(main_loop, xcb_chk);
     ev_prepare_stop(main_loop, xcb_prep);
     ev_io_stop(main_loop, xcb_io);
 
-    FREE(xcb_chk);
     FREE(xcb_prep);
     FREE(xcb_io);
 }
@@ -1481,6 +1495,7 @@ void kick_tray_clients(i3_output *output) {
         /* We remove the trayclient right here. We might receive an UnmapNotify
          * event afterwards, but better safe than sorry. */
         TAILQ_REMOVE(output->trayclients, trayclient, tailq);
+        FREE(trayclient);
     }
 
     /* Fake a DestroyNotify so that Qt re-adds tray icons.
@@ -1517,7 +1532,7 @@ void destroy_window(i3_output *output) {
 
 /* Strut partial tells i3 where to reserve space for i3bar. This is determined
  * by the `position` bar config directive. */
-xcb_void_cookie_t config_strut_partial(i3_output *output) {
+static xcb_void_cookie_t config_strut_partial(i3_output *output) {
     /* A local struct to save the strut_partial property */
     struct {
         uint32_t left;
@@ -1559,14 +1574,63 @@ xcb_void_cookie_t config_strut_partial(i3_output *output) {
                                &strut_partial);
 }
 
+/*
+ * Returns the output which should hold the tray, if one exists.
+ *
+ * An output is returned in these scenarios:
+ *   1. A specific output was listed in tray_outputs which is also in the list
+ *   of outputs managed by this bar.
+ *   2. No tray_output directive was specified. In this case, we use the first
+ *   available output.
+ *   3. 'tray_output primary' was specified. In this case we use the primary
+ *   output.
+ *
+ * Three scenarios in which we specifically don't want to use a tray:
+ *   1. 'tray_output none' was specified.
+ *   2. A specific output was listed as a tray_output, but is not one of the
+ *   outputs managed by this bar. For example, consider tray_outputs == [VGA-1],
+ *   but outputs == [HDMI-1].
+ *   3. 'tray_output primary' was specified and no output in the list is
+ *   primary.
+ */
+static i3_output *get_tray_output(void) {
+    i3_output *output = NULL;
+    if (TAILQ_EMPTY(&(config.tray_outputs))) {
+        /* No tray_output specified, use first active output. */
+        SLIST_FOREACH(output, outputs, slist) {
+            if (output->active) {
+                return output;
+            }
+        }
+        return NULL;
+    } else if (strcasecmp(TAILQ_FIRST(&(config.tray_outputs))->output, "none") == 0) {
+        /* Check for "tray_output none" */
+        return NULL;
+    }
+
+    /* If one or more tray_output assignments were specified, we ensure that at
+     * least one of them is actually an output managed by this instance. */
+    tray_output_t *tray_output;
+    TAILQ_FOREACH(tray_output, &(config.tray_outputs), tray_outputs) {
+        SLIST_FOREACH(output, outputs, slist) {
+            if (output->active &&
+                (strcasecmp(output->name, tray_output->output) == 0 ||
+                 (strcasecmp(tray_output->output, "primary") == 0 && output->primary))) {
+                return output;
+            }
+        }
+    }
+
+    return NULL;
+}
+
 /*
  * Reconfigure all bars and create new bars for recently activated outputs
  *
  */
 void reconfig_windows(bool redraw_bars) {
     uint32_t mask;
-    uint32_t values[5];
-    static bool tray_configured = false;
+    uint32_t values[6];
 
     i3_output *walk;
     SLIST_FOREACH(walk, outputs, slist) {
@@ -1583,7 +1647,7 @@ void reconfig_windows(bool redraw_bars) {
             xcb_window_t bar_id = xcb_generate_id(xcb_connection);
             xcb_pixmap_t buffer_id = xcb_generate_id(xcb_connection);
             xcb_pixmap_t statusline_buffer_id = xcb_generate_id(xcb_connection);
-            mask = XCB_CW_BACK_PIXEL | XCB_CW_BORDER_PIXEL | XCB_CW_OVERRIDE_REDIRECT | XCB_CW_EVENT_MASK | XCB_CW_COLORMAP;
+            mask = XCB_CW_BACK_PIXEL | XCB_CW_BORDER_PIXEL | XCB_CW_OVERRIDE_REDIRECT | XCB_CW_EVENT_MASK | XCB_CW_COLORMAP | XCB_CW_CURSOR;
 
             values[0] = colors.bar_bg.colorpixel;
             values[1] = root_screen->black_pixel;
@@ -1597,7 +1661,8 @@ void reconfig_windows(bool redraw_bars) {
              * */
             values[3] = XCB_EVENT_MASK_EXPOSURE |
                         XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT |
-                        XCB_EVENT_MASK_BUTTON_PRESS;
+                        XCB_EVENT_MASK_BUTTON_PRESS |
+                        XCB_EVENT_MASK_BUTTON_RELEASE;
             if (config.hide_on_modifier == M_DOCK) {
                 /* If the bar is normally visible, catch visibility change events to suspend
                  * the status process when the bar is obscured by full-screened windows.  */
@@ -1605,6 +1670,7 @@ void reconfig_windows(bool redraw_bars) {
                 walk->visible = true;
             }
             values[4] = colormap;
+            values[5] = cursor;
 
             xcb_void_cookie_t win_cookie = xcb_create_window_checked(xcb_connection,
                                                                      depth,
@@ -1669,9 +1735,9 @@ void reconfig_windows(bool redraw_bars) {
                                                                 1,
                                                                 (unsigned char *)&atoms[_NET_WM_WINDOW_TYPE_DOCK]);
 
-            draw_util_surface_init(&walk->bar, bar_id, walk->rect.w, bar_height);
-            draw_util_surface_init(&walk->buffer, buffer_id, walk->rect.w, bar_height);
-            draw_util_surface_init(&walk->statusline_buffer, statusline_buffer_id, walk->rect.w, bar_height);
+            draw_util_surface_init(xcb_connection, &walk->bar, bar_id, NULL, walk->rect.w, bar_height);
+            draw_util_surface_init(xcb_connection, &walk->buffer, buffer_id, NULL, walk->rect.w, bar_height);
+            draw_util_surface_init(xcb_connection, &walk->statusline_buffer, statusline_buffer_id, NULL, walk->rect.w, bar_height);
 
             xcb_void_cookie_t strut_cookie = config_strut_partial(walk);
 
@@ -1692,22 +1758,6 @@ void reconfig_windows(bool redraw_bars) {
                 exit(EXIT_FAILURE);
             }
 
-            const char *tray_output = (config.tray_output ? config.tray_output : SLIST_FIRST(outputs)->name);
-            if (!tray_configured && strcasecmp(tray_output, "none") != 0) {
-                /* Configuration sanity check: ensure this i3bar instance handles the output on
-                 * which the tray should appear (e.g. don’t initialize a tray if tray_output ==
-                 * VGA-1 but output == [HDMI-1]).
-                 */
-                i3_output *output;
-                SLIST_FOREACH(output, outputs, slist) {
-                    if (strcasecmp(output->name, tray_output) == 0 ||
-                        (strcasecmp(tray_output, "primary") == 0 && output->primary)) {
-                        init_tray();
-                        break;
-                    }
-                }
-                tray_configured = true;
-            }
         } else {
             /* We already have a bar, so we just reconfigure it */
             mask = XCB_CONFIG_WINDOW_X |
@@ -1763,12 +1813,12 @@ void reconfig_windows(bool redraw_bars) {
                                                                       walk->rect.w,
                                                                       bar_height);
 
-            draw_util_surface_free(&(walk->bar));
-            draw_util_surface_free(&(walk->buffer));
-            draw_util_surface_free(&(walk->statusline_buffer));
-            draw_util_surface_init(&(walk->bar), walk->bar.id, walk->rect.w, bar_height);
-            draw_util_surface_init(&(walk->buffer), walk->buffer.id, walk->rect.w, bar_height);
-            draw_util_surface_init(&(walk->statusline_buffer), walk->statusline_buffer.id, walk->rect.w, bar_height);
+            draw_util_surface_free(xcb_connection, &(walk->bar));
+            draw_util_surface_free(xcb_connection, &(walk->buffer));
+            draw_util_surface_free(xcb_connection, &(walk->statusline_buffer));
+            draw_util_surface_init(xcb_connection, &(walk->bar), walk->bar.id, NULL, walk->rect.w, bar_height);
+            draw_util_surface_init(xcb_connection, &(walk->buffer), walk->buffer.id, NULL, walk->rect.w, bar_height);
+            draw_util_surface_init(xcb_connection, &(walk->statusline_buffer), walk->statusline_buffer.id, NULL, walk->rect.w, bar_height);
 
             xcb_void_cookie_t map_cookie, umap_cookie;
             if (redraw_bars) {
@@ -1801,6 +1851,19 @@ void reconfig_windows(bool redraw_bars) {
             }
         }
     }
+
+    /* Finally, check if we want to initialize the tray or destroy the selection
+     * window. The result of get_tray_output() is cached. */
+    output_for_tray = get_tray_output();
+    if (output_for_tray) {
+        if (selwin == XCB_NONE) {
+            init_tray();
+        }
+    } else if (selwin != XCB_NONE) {
+        DLOG("Destroying tray selection window\n");
+        xcb_destroy_window(xcb_connection, selwin);
+        selwin = XCB_NONE;
+    }
 }
 
 /*
@@ -1826,8 +1889,10 @@ void draw_bars(bool unhide) {
             reconfig_windows(false);
         }
 
+        bool use_focus_colors = output_has_focus(outputs_walk);
+
         /* First things first: clear the backbuffer */
-        draw_util_clear_surface(&(outputs_walk->buffer), colors.bar_bg);
+        draw_util_clear_surface(&(outputs_walk->buffer), (use_focus_colors ? colors.focus_bar_bg : colors.bar_bg));
 
         if (!config.disable_ws) {
             i3_ws *ws_walk;
@@ -1912,7 +1977,8 @@ void draw_bars(bool unhide) {
             DLOG("Printing statusline!\n");
 
             int tray_width = get_tray_width(outputs_walk->trayclients);
-            uint32_t max_statusline_width = outputs_walk->rect.w - workspace_width - tray_width - 2 * logical_px(sb_hoff_px);
+            uint32_t hoff = logical_px(((workspace_width > 0) + (tray_width > 0)) * sb_hoff_px);
+            uint32_t max_statusline_width = outputs_walk->rect.w - workspace_width - tray_width - hoff;
             uint32_t clip_left = 0;
             uint32_t statusline_width = full_statusline_width;
             bool use_short_text = false;
@@ -1926,9 +1992,9 @@ void draw_bars(bool unhide) {
             }
 
             int16_t visible_statusline_width = MIN(statusline_width, max_statusline_width);
-            int x_dest = outputs_walk->rect.w - tray_width - logical_px(sb_hoff_px) - visible_statusline_width;
+            int x_dest = outputs_walk->rect.w - tray_width - logical_px((tray_width > 0) * sb_hoff_px) - visible_statusline_width;
 
-            draw_statusline(outputs_walk, clip_left, use_short_text);
+            draw_statusline(outputs_walk, clip_left, use_focus_colors, use_short_text);
             draw_util_copy_surface(&outputs_walk->statusline_buffer, &outputs_walk->buffer, 0, 0,
                                    x_dest, 0, visible_statusline_width, (int16_t)bar_height);
 
@@ -1974,5 +2040,4 @@ void set_current_mode(struct mode *current) {
     I3STRING_FREE(binding.name);
     binding = *current;
     activated_mode = binding.name != NULL;
-    return;
 }