]> git.sur5r.net Git - i3/i3/blobdiff - i3bar/src/xcb.c
i3bar: introduce get_tray_width() function to reduce duplication
[i3/i3] / i3bar / src / xcb.c
index 5bbec13e6c5602a6370d0c0d369ed4082228da98..0f01fe23b907f9807ef79e6b630f80a65f66568d 100644 (file)
@@ -106,6 +106,14 @@ struct xcb_colors_t {
 };
 struct xcb_colors_t colors;
 
+const static int ws_hoff_px = 4,    /* Horizontal offset between a workspace label and button borders */
+                 ws_voff_px = 3,    /* Vertical offset between a workspace label and button borders */
+                 ws_spacing_px = 1, /* Offset between two workspace buttons */
+                 sb_hoff_px = 4,    /* Offset between the statusline and 1) workspace buttons on the left
+                                     *                                   2) the tray or screen edge on the right */
+                 tray_loff_px = 2,  /* Additional offset between the tray and the statusline, if the tray is not empty */
+                 sep_voff_px = 4;   /* Vertical offset between the bar and a separator */
+
 /* We define xcb_request_failed as a macro to include the relevant line-number */
 #define xcb_request_failed(cookie, err_msg) _xcb_request_failed(cookie, err_msg, __LINE__)
 int _xcb_request_failed(xcb_void_cookie_t cookie, char *err_msg, int line) {
@@ -117,6 +125,25 @@ 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) {
+    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) {
+    trayclient *trayclient;
+    int tray_width = 0;
+    TAILQ_FOREACH_REVERSE(trayclient, trayclients, tc_head, tailq) {
+        if (!trayclient->mapped)
+            continue;
+        tray_width += font.height + logical_px(2);
+    }
+    if (tray_width > 0)
+        tray_width += logical_px(tray_loff_px);
+    return tray_width;
+}
+
 /*
  * Redraws the statusline to the buffer
  *
@@ -148,15 +175,15 @@ void refresh_statusline(void) {
                     block->x_offset = padding_width;
                     break;
                 case ALIGN_CENTER:
-                    block->x_offset = padding_width / logical_px(2);
-                    block->x_append = padding_width / logical_px(2) + padding_width % logical_px(2);
+                    block->x_offset = padding_width / 2;
+                    block->x_append = padding_width / 2 + padding_width % 2;
                     break;
             }
         }
 
         /* If this is not the last block, add some pixels for a separator. */
         if (TAILQ_NEXT(block, blocks) != NULL)
-            block->width += block->sep_block_width;
+            statusline_width += block->sep_block_width;
 
         statusline_width += block->width + block->x_offset + block->x_append;
     }
@@ -168,7 +195,7 @@ void refresh_statusline(void) {
         realloc_sl_buffer();
 
     /* Clear the statusline pixmap. */
-    xcb_rectangle_t rect = {0, 0, root_screen->width_in_pixels, font.height + logical_px(5)};
+    xcb_rectangle_t rect = {0, 0, MAX(root_screen->width_in_pixels, statusline_width), bar_height};
     xcb_poly_fill_rectangle(xcb_connection, statusline_pm, statusline_clear, 1, &rect);
 
     /* Draw the text of each block. */
@@ -176,22 +203,41 @@ void refresh_statusline(void) {
     TAILQ_FOREACH(block, &statusline_head, blocks) {
         if (i3string_get_num_bytes(block->full_text) == 0)
             continue;
+        uint32_t fg_color;
+
+        /* If this block is urgent, draw it with the defined color and border. */
+        if (block->urgent) {
+            fg_color = colors.urgent_ws_fg;
+
+            uint32_t mask = XCB_GC_FOREGROUND | XCB_GC_BACKGROUND;
 
-        uint32_t colorpixel = (block->color ? get_colorpixel(block->color) : colors.bar_fg);
-        set_font_colors(statusline_ctx, colorpixel, colors.bar_bg);
-        draw_text(block->full_text, statusline_pm, statusline_ctx, x + block->x_offset, 1, block->width);
-        x += block->width + block->x_offset + block->x_append;
+            /* Draw the background */
+            uint32_t bg_color = colors.urgent_ws_bg;
+            uint32_t bg_values[] = {bg_color, bg_color};
+            xcb_change_gc(xcb_connection, statusline_ctx, mask, bg_values);
+
+            /* The urgent background “overshoots” by 2 px so that the text that
+             * is printed onto it will not be look so cut off. */
+            xcb_rectangle_t bg_rect = {x - logical_px(2), logical_px(1), block->width + logical_px(4), bar_height - logical_px(2)};
+            xcb_poly_fill_rectangle(xcb_connection, statusline_pm, statusline_ctx, 1, &bg_rect);
+        } else {
+            fg_color = (block->color ? get_colorpixel(block->color) : colors.bar_fg);
+        }
+
+        set_font_colors(statusline_ctx, fg_color, colors.bar_bg);
+        draw_text(block->full_text, statusline_pm, statusline_ctx, x + block->x_offset, logical_px(ws_voff_px), block->width);
+        x += block->width + block->sep_block_width + block->x_offset + block->x_append;
 
-        if (TAILQ_NEXT(block, blocks) != NULL && !block->no_separator && block->sep_block_width > 0) {
+        uint32_t sep_offset = get_sep_offset(block);
+        if (TAILQ_NEXT(block, blocks) != NULL && sep_offset > 0) {
             /* This is not the last block, draw a separator. */
-            uint32_t sep_offset = block->sep_block_width / 2 + block->sep_block_width % 2;
             uint32_t mask = XCB_GC_FOREGROUND | XCB_GC_BACKGROUND | XCB_GC_LINE_WIDTH;
             uint32_t values[] = {colors.sep_fg, colors.bar_bg, logical_px(1)};
             xcb_change_gc(xcb_connection, statusline_ctx, mask, values);
             xcb_poly_line(xcb_connection, XCB_COORD_MODE_ORIGIN, statusline_pm,
                           statusline_ctx, 2,
-                          (xcb_point_t[]) {{x - sep_offset, 2},
-                                           {x - sep_offset, font.height - 2}});
+                          (xcb_point_t[]){{x - sep_offset, logical_px(sep_voff_px)},
+                                          {x - sep_offset, bar_height - logical_px(sep_voff_px)}});
         }
     }
 }
@@ -248,7 +294,7 @@ void unhide_bars(void) {
         values[2] = walk->rect.w;
         values[3] = bar_height;
         values[4] = XCB_STACK_MODE_ABOVE;
-        DLOG("Reconfiguring Window for output %s to %d,%d\n", walk->name, values[0], values[1]);
+        DLOG("Reconfiguring window for output %s to %d,%d\n", walk->name, values[0], values[1]);
         cookie = xcb_configure_window_checked(xcb_connection,
                                               walk->bar,
                                               mask,
@@ -298,8 +344,6 @@ void init_colors(const struct xcb_color_strings_t *new_colors) {
  *
  */
 void handle_button(xcb_button_press_event_t *event) {
-    i3_ws *cur_ws;
-
     /* Determine, which bar was clicked */
     i3_output *walk;
     xcb_window_t bar = event->event;
@@ -310,60 +354,64 @@ void handle_button(xcb_button_press_event_t *event) {
     }
 
     if (walk == NULL) {
-        DLOG("Unknown Bar klicked!\n");
-        return;
-    }
-
-    /* TODO: Move this to extern get_ws_for_output() */
-    TAILQ_FOREACH(cur_ws, walk->workspaces, tailq) {
-        if (cur_ws->visible) {
-            break;
-        }
-    }
-
-    if (cur_ws == NULL) {
-        DLOG("No Workspace active?\n");
+        DLOG("Unknown bar clicked!\n");
         return;
     }
 
     int32_t x = event->event_x >= 0 ? event->event_x : 0;
     int32_t original_x = x;
 
-    DLOG("Got Button %d\n", event->detail);
+    DLOG("Got button %d\n", event->detail);
 
-    if (child_want_click_events()) {
-        /* If the child asked for click events,
-         * check if a status block has been clicked. */
+    int workspace_width = 0;
+    i3_ws *cur_ws = NULL, *clicked_ws = NULL, *ws_walk;
 
-        /* First calculate width of tray area */
-        trayclient *trayclient;
-        int tray_width = 0;
-        TAILQ_FOREACH_REVERSE(trayclient, walk->trayclients, tc_head, tailq) {
-            if (!trayclient->mapped)
-                continue;
-            tray_width += (font.height + logical_px(2));
-        }
+    TAILQ_FOREACH(ws_walk, walk->workspaces, tailq) {
+        int w = 2*logical_px(ws_hoff_px) + 2*logical_px(1) + ws_walk->name_width;
+        if (x >= workspace_width && x <= workspace_width + w)
+            clicked_ws = ws_walk;
+        if (ws_walk->visible)
+            cur_ws = ws_walk;
+        workspace_width += w;
+        if (TAILQ_NEXT(ws_walk, tailq) != NULL)
+            workspace_width += logical_px(ws_spacing_px);
+    }
 
+    if (x > workspace_width && child_want_click_events()) {
+        /* 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 - (statusline_width + tray_width)) - logical_px(10);
+        int offset = walk->rect.w - statusline_width - tray_width - logical_px(sb_hoff_px);
 
         x = original_x - offset;
         if (x >= 0) {
             struct status_block *block;
+            int sep_offset_remainder = 0;
 
             TAILQ_FOREACH(block, &statusline_head, blocks) {
+                if (i3string_get_num_bytes(block->full_text) == 0)
+                    continue;
+
                 last_block_x = block_x;
-                block_x += block->width + block->x_offset + block->x_append;
+                block_x += block->width + block->x_offset + block->x_append + get_sep_offset(block) + sep_offset_remainder;
 
                 if (x <= block_x && x >= last_block_x) {
                     send_block_clicked(event->detail, block->name, block->instance, event->root_x, event->root_y);
                     return;
                 }
+
+                sep_offset_remainder = block->sep_block_width - get_sep_offset(block);
             }
         }
         x = original_x;
     }
 
+    if (cur_ws == NULL) {
+        DLOG("No workspace active?\n");
+        return;
+    }
+
     switch (event->detail) {
         case 4:
             /* Mouse wheel up. We select the previous ws, if any.
@@ -402,17 +450,10 @@ void handle_button(xcb_button_press_event_t *event) {
             cur_ws = TAILQ_NEXT(cur_ws, tailq);
             break;
         case 1:
-            /* Check if this event regards a workspace button */
-            TAILQ_FOREACH(cur_ws, walk->workspaces, tailq) {
-                DLOG("x = %d\n", x);
-                if (x >= 0 && x < cur_ws->name_width + logical_px(10)) {
-                    break;
-                }
-                x -= cur_ws->name_width + logical_px(11);
-            }
+            cur_ws = clicked_ws;
 
-            /* Otherwise, focus our currently visible workspace if it is not
-             * already focused */
+            /* if no workspace was clicked, focus our currently visible
+             * workspace if it is not already focused */
             if (cur_ws == NULL) {
                 TAILQ_FOREACH(cur_ws, walk->workspaces, tailq) {
                     if (cur_ws->visible && !cur_ws->focused)
@@ -462,6 +503,39 @@ void handle_button(xcb_button_press_event_t *event) {
     free(buffer);
 }
 
+/*
+ * Handle visibility notifications: when none of the bars are visible, e.g.
+ * if windows are in full-screen on each output, suspend the child process.
+ *
+ */
+static void handle_visibility_notify(xcb_visibility_notify_event_t *event) {
+    bool visible = (event->state != XCB_VISIBILITY_FULLY_OBSCURED);
+    int num_visible = 0;
+    i3_output *output;
+
+    SLIST_FOREACH(output, outputs, slist) {
+        if (!output->active) {
+            continue;
+        }
+        if (output->bar == event->window) {
+            if (output->visible == visible) {
+                return;
+            }
+            output->visible = visible;
+        }
+        num_visible += output->visible;
+    }
+
+    if (num_visible == 0) {
+        stop_child();
+    } else if (num_visible == visible) {
+        /* Wake the child only when transitioning from 0 to 1 visible bar.
+         * We cannot transition from 0 to 2 or more visible bars at once since
+         * visibility events are delivered to each window separately */
+        cont_child();
+    }
+}
+
 /*
  * Adjusts the size of the tray window and alignment of the tray clients by
  * configuring their respective x coordinates. To be called when mapping or
@@ -926,6 +1000,10 @@ void xcb_chk_cb(struct ev_loop *loop, ev_check *watcher, int revents) {
         }
 
         switch (type) {
+            case XCB_VISIBILITY_NOTIFY:
+                /* Visibility change: a bar is [un]obscured by other window */
+                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();
@@ -1100,8 +1178,8 @@ void init_xcb_late(char *fontname) {
     /* Load the font */
     font = load_font(fontname, true);
     set_font(&font);
-    DLOG("Calculated Font-height: %d\n", font.height);
-    bar_height = font.height + logical_px(6);
+    DLOG("Calculated font height: %d\n", font.height);
+    bar_height = font.height + 2*logical_px(ws_voff_px);
 
     xcb_flush(xcb_connection);
 
@@ -1271,6 +1349,7 @@ void clean_xcb(void) {
     FREE(outputs);
 
     xcb_flush(xcb_connection);
+    xcb_aux_sync(xcb_connection);
     xcb_disconnect(xcb_connection);
 
     ev_check_stop(main_loop, xcb_chk);
@@ -1298,7 +1377,7 @@ void get_atoms(void) {
     free(reply);
 
 #include "xcb_atoms.def"
-    DLOG("Got Atoms\n");
+    DLOG("Got atoms\n");
 }
 
 /*
@@ -1424,7 +1503,7 @@ void reconfig_windows(bool redraw_bars) {
             continue;
         }
         if (walk->bar == XCB_NONE) {
-            DLOG("Creating Window for output %s\n", walk->name);
+            DLOG("Creating window for output %s\n", walk->name);
 
             walk->bar = xcb_generate_id(xcb_connection);
             walk->buffer = xcb_generate_id(xcb_connection);
@@ -1440,9 +1519,13 @@ void reconfig_windows(bool redraw_bars) {
              * BUTTON_PRESS, to handle clicks on the workspace buttons
              * */
             values[2] = XCB_EVENT_MASK_EXPOSURE |
-                        XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT;
-            if (!config.disable_ws) {
-                values[2] |= XCB_EVENT_MASK_BUTTON_PRESS;
+                        XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT |
+                        XCB_EVENT_MASK_BUTTON_PRESS;
+            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.  */
+                values[2] |= XCB_EVENT_MASK_VISIBILITY_CHANGE;
+                walk->visible = true;
             }
             xcb_void_cookie_t win_cookie = xcb_create_window_checked(xcb_connection,
                                                                      root_screen->root_depth,
@@ -1602,7 +1685,7 @@ void reconfig_windows(bool redraw_bars) {
             DLOG("Destroying buffer for output %s\n", walk->name);
             xcb_free_pixmap(xcb_connection, walk->buffer);
 
-            DLOG("Reconfiguring Window for output %s to %d,%d\n", walk->name, values[0], values[1]);
+            DLOG("Reconfiguring window for output %s to %d,%d\n", walk->name, values[0], values[1]);
             xcb_void_cookie_t cfg_cookie = xcb_configure_window_checked(xcb_connection,
                                                                         walk->bar,
                                                                         mask,
@@ -1610,7 +1693,7 @@ void reconfig_windows(bool redraw_bars) {
 
             mask = XCB_CW_OVERRIDE_REDIRECT;
             values[0] = (config.hide_on_modifier == M_DOCK ? 0 : 1);
-            DLOG("Changing Window attribute override_redirect for output %s to %d\n", walk->name, values[0]);
+            DLOG("Changing window attribute override_redirect for output %s to %d\n", walk->name, values[0]);
             xcb_void_cookie_t chg_cookie = xcb_change_window_attributes(xcb_connection,
                                                                         walk->bar,
                                                                         mask,
@@ -1660,8 +1743,8 @@ void reconfig_windows(bool redraw_bars) {
  *
  */
 void draw_bars(bool unhide) {
-    DLOG("Drawing Bars...\n");
-    int i = 0;
+    DLOG("Drawing bars...\n");
+    int workspace_width = 0;
 
     refresh_statusline();
 
@@ -1688,39 +1771,11 @@ void draw_bars(bool unhide) {
                                 1,
                                 &rect);
 
-        if (!TAILQ_EMPTY(&statusline_head)) {
-            DLOG("Printing statusline!\n");
-
-            /* Luckily we already prepared a seperate pixmap containing the rendered
-             * statusline, we just have to copy the relevant parts to the relevant
-             * position */
-            trayclient *trayclient;
-            int traypx = 0;
-            TAILQ_FOREACH(trayclient, outputs_walk->trayclients, tailq) {
-                if (!trayclient->mapped)
-                    continue;
-                /* We assume the tray icons are quadratic (we use the font
-                 * *height* as *width* of the icons) because we configured them
-                 * like this. */
-                traypx += font.height + 2;
-            }
-            /* Add 2px of padding if there are any tray icons */
-            if (traypx > 0)
-                traypx += 2;
-            xcb_copy_area(xcb_connection,
-                          statusline_pm,
-                          outputs_walk->buffer,
-                          outputs_walk->bargc,
-                          MAX(0, (int16_t)(statusline_width - outputs_walk->rect.w + 4)), 0,
-                          MAX(0, (int16_t)(outputs_walk->rect.w - statusline_width - traypx - 4)), 3,
-                          MIN(outputs_walk->rect.w - traypx - 4, (int)statusline_width), font.height + 2);
-        }
-
         if (!config.disable_ws) {
             i3_ws *ws_walk;
             TAILQ_FOREACH(ws_walk, outputs_walk->workspaces, tailq) {
-                DLOG("Drawing Button for WS %s at x = %d, len = %d\n",
-                     i3string_as_utf8(ws_walk->name), i, ws_walk->name_width);
+                DLOG("Drawing button for WS %s at x = %d, len = %d\n",
+                     i3string_as_utf8(ws_walk->name), workspace_width, ws_walk->name_width);
                 uint32_t fg_color = colors.inactive_ws_fg;
                 uint32_t bg_color = colors.inactive_ws_bg;
                 uint32_t border_color = colors.inactive_ws_border;
@@ -1748,10 +1803,10 @@ void draw_bars(bool unhide) {
                               outputs_walk->bargc,
                               mask,
                               vals_border);
-                xcb_rectangle_t rect_border = {i,
+                xcb_rectangle_t rect_border = {workspace_width,
                                                logical_px(1),
-                                               ws_walk->name_width + logical_px(10),
-                                               font.height + logical_px(4)};
+                                               ws_walk->name_width + 2*logical_px(ws_hoff_px) + 2*logical_px(1),
+                                               font.height + 2*logical_px(ws_voff_px) - 2*logical_px(1)};
                 xcb_poly_fill_rectangle(xcb_connection,
                                         outputs_walk->buffer,
                                         outputs_walk->bargc,
@@ -1762,10 +1817,10 @@ void draw_bars(bool unhide) {
                               outputs_walk->bargc,
                               mask,
                               vals);
-                xcb_rectangle_t rect = {i + logical_px(1),
+                xcb_rectangle_t rect = {workspace_width + logical_px(1),
                                         2 * logical_px(1),
-                                        ws_walk->name_width + logical_px(8),
-                                        font.height + logical_px(2)};
+                                        ws_walk->name_width + 2*logical_px(ws_hoff_px),
+                                        font.height + 2*logical_px(ws_voff_px) - 4*logical_px(1)};
                 xcb_poly_fill_rectangle(xcb_connection,
                                         outputs_walk->buffer,
                                         outputs_walk->bargc,
@@ -1773,12 +1828,19 @@ void draw_bars(bool unhide) {
                                         &rect);
                 set_font_colors(outputs_walk->bargc, fg_color, bg_color);
                 draw_text(ws_walk->name, outputs_walk->buffer, outputs_walk->bargc,
-                          i + logical_px(5), 3 * logical_px(1), ws_walk->name_width);
-                i += logical_px(10) + ws_walk->name_width + logical_px(1);
+                          workspace_width + logical_px(ws_hoff_px) + logical_px(1),
+                          logical_px(ws_voff_px),
+                          ws_walk->name_width);
+
+                workspace_width += 2*logical_px(ws_hoff_px) + 2*logical_px(1) + ws_walk->name_width;
+                if (TAILQ_NEXT(ws_walk, tailq) != NULL)
+                    workspace_width += logical_px(ws_spacing_px);
             }
         }
 
         if (binding.name && !config.disable_binding_mode_indicator) {
+            workspace_width += logical_px(ws_spacing_px);
+
             uint32_t fg_color = colors.urgent_ws_fg;
             uint32_t bg_color = colors.urgent_ws_bg;
             uint32_t mask = XCB_GC_FOREGROUND | XCB_GC_BACKGROUND;
@@ -1788,7 +1850,10 @@ void draw_bars(bool unhide) {
                           outputs_walk->bargc,
                           mask,
                           vals_border);
-            xcb_rectangle_t rect_border = {i, 1, binding.width + 10, font.height + 4};
+            xcb_rectangle_t rect_border = {workspace_width,
+                                           logical_px(1),
+                                           binding.width + 2*logical_px(ws_hoff_px) + 2*logical_px(1),
+                                           font.height + 2*logical_px(ws_voff_px) - 2*logical_px(1)};
             xcb_poly_fill_rectangle(xcb_connection,
                                     outputs_walk->buffer,
                                     outputs_walk->bargc,
@@ -1800,7 +1865,10 @@ void draw_bars(bool unhide) {
                           outputs_walk->bargc,
                           mask,
                           vals);
-            xcb_rectangle_t rect = {i + 1, 2, binding.width + 8, font.height + 2};
+            xcb_rectangle_t rect = {workspace_width + logical_px(1),
+                                    2*logical_px(1),
+                                    binding.width + 2*logical_px(ws_hoff_px),
+                                    font.height + 2*logical_px(ws_voff_px) - 4*logical_px(1)};
             xcb_poly_fill_rectangle(xcb_connection,
                                     outputs_walk->buffer,
                                     outputs_walk->bargc,
@@ -1808,12 +1876,38 @@ void draw_bars(bool unhide) {
                                     &rect);
 
             set_font_colors(outputs_walk->bargc, fg_color, bg_color);
-            draw_text(binding.name, outputs_walk->buffer, outputs_walk->bargc, i + 5, 3, binding.width);
+            draw_text(binding.name,
+                      outputs_walk->buffer,
+                      outputs_walk->bargc,
+                      workspace_width + logical_px(ws_hoff_px) + logical_px(1),
+                      logical_px(ws_voff_px),
+                      binding.width);
 
             unhide = true;
+            workspace_width += 2*logical_px(ws_hoff_px) + 2*logical_px(1) + binding.width;
+        }
+
+        if (!TAILQ_EMPTY(&statusline_head)) {
+            DLOG("Printing statusline!\n");
+
+            /* Luckily we already prepared a seperate pixmap containing the rendered
+             * statusline, we just have to copy the relevant parts to the relevant
+             * position */
+            int tray_width = get_tray_width(outputs_walk->trayclients);
+
+            int visible_statusline_width = MIN(statusline_width,
+                                               outputs_walk->rect.w - workspace_width - tray_width - 2*logical_px(sb_hoff_px));
+
+            xcb_copy_area(xcb_connection,
+                          statusline_pm,
+                          outputs_walk->buffer,
+                          outputs_walk->bargc,
+                          (int16_t)(statusline_width - visible_statusline_width), 0,
+                          (int16_t)(outputs_walk->rect.w - tray_width - logical_px(sb_hoff_px) - visible_statusline_width), 0,
+                          (int16_t)visible_statusline_width, (int16_t)bar_height);
         }
 
-        i = 0;
+        workspace_width = 0;
     }
 
     /* Assure the bar is hidden/unhidden according to the specified hidden_state and mode */