From dd989fa87b0d56737be97b6aa36bfe80786fbd1e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ingo=20B=C3=BCrk?= Date: Wed, 9 Sep 2015 18:27:34 +0200 Subject: [PATCH 001/187] Refactor out some individual functions in render_con() to make the code more readable. --- include/render.h | 17 ++++ src/render.c | 256 +++++++++++++++++++++++++---------------------- 2 files changed, 154 insertions(+), 119 deletions(-) diff --git a/include/render.h b/include/render.h index 717459e9..d32f0310 100644 --- a/include/render.h +++ b/include/render.h @@ -10,6 +10,23 @@ */ #pragma once +/* This is used to keep a state to pass around when rendering a con in render_con(). */ +typedef struct render_params { + /* A copy of the coordinates of the container which is being rendered. */ + int x; + int y; + + /* The computed height for decorations. */ + int deco_height; + /* Container rect, subtract container border. This is the actually usable space + * inside this container for clients. */ + Rect rect; + /* The number of children of the container which is being rendered. */ + int children; + /* A precalculated list of sizes of each child. */ + int *sizes; +} render_params; + /** * "Renders" the given container (and its children), meaning that all rects are * updated correctly. Note that this function does not call any xcb_* diff --git a/src/render.c b/src/render.c index 7ada19eb..94404347 100644 --- a/src/render.c +++ b/src/render.c @@ -12,6 +12,12 @@ */ #include "all.h" +/* Forward declarations */ +static void render_con_split(Con *con, Con *child, render_params *p, int i); +static void render_con_stacked(Con *con, Con *child, render_params *p, int i); +static void render_con_tabbed(Con *con, Con *child, render_params *p, int i); +static void render_con_dockarea(Con *con, Con *child, render_params *p); + /* change this to 'true' if you want to have additional borders around every * container (for debugging purposes) */ static bool show_debug_borders = false; @@ -124,29 +130,26 @@ static void render_l_output(Con *con) { * */ void render_con(Con *con, bool render_fullscreen) { - int children = con_num_children(con); + render_params params = { + .rect = con->rect, + .x = con->rect.x, + .y = con->rect.y, + .children = con_num_children(con)}; + DLOG("Rendering %snode %p / %s / layout %d / children %d\n", (render_fullscreen ? "fullscreen " : ""), con, con->name, con->layout, - children); - - /* Copy container rect, subtract container border */ - /* This is the actually usable space inside this container for clients */ - Rect rect = con->rect; + params.children); /* Display a border if this is a leaf node. For container nodes, we don’t * draw borders (except when in debug mode) */ if (show_debug_borders) { - rect.x += 2; - rect.y += 2; - rect.width -= 2 * 2; - rect.height -= 2 * 2; + params.rect.x += 2; + params.rect.y += 2; + params.rect.width -= 2 * 2; + params.rect.height -= 2 * 2; } - int x = rect.x; - int y = rect.y; - int i = 0; - con->mapped = true; /* if this container contains a window, set the coordinates */ @@ -207,7 +210,7 @@ void render_con(Con *con, bool render_fullscreen) { fullscreen = con_get_fullscreen_con(con, (con->type == CT_ROOT ? CF_GLOBAL : CF_OUTPUT)); } if (fullscreen) { - fullscreen->rect = rect; + fullscreen->rect = params.rect; x_raise_con(fullscreen); render_con(fullscreen, true); /* Fullscreen containers are either global (underneath the CT_ROOT @@ -222,31 +225,33 @@ void render_con(Con *con, bool render_fullscreen) { } /* find the height for the decorations */ - int deco_height = render_deco_height(); + params.deco_height = render_deco_height(); /* precalculate the sizes to be able to correct rounding errors */ - int sizes[children]; - memset(sizes, 0, children * sizeof(int)); - if ((con->layout == L_SPLITH || con->layout == L_SPLITV) && children > 0) { + int sizes[params.children]; + memset(sizes, 0, params.children * sizeof(int)); + if ((con->layout == L_SPLITH || con->layout == L_SPLITV) && params.children > 0) { assert(!TAILQ_EMPTY(&con->nodes_head)); + Con *child; int i = 0, assigned = 0; - int total = con_orientation(con) == HORIZ ? rect.width : rect.height; + int total = con_orientation(con) == HORIZ ? params.rect.width : params.rect.height; TAILQ_FOREACH(child, &(con->nodes_head), nodes) { - double percentage = child->percent > 0.0 ? child->percent : 1.0 / children; + double percentage = child->percent > 0.0 ? child->percent : 1.0 / params.children; assigned += sizes[i++] = percentage * total; } assert(assigned == total || - (assigned > total && assigned - total <= children * 2) || - (assigned < total && total - assigned <= children * 2)); + (assigned > total && assigned - total <= params.children * 2) || + (assigned < total && total - assigned <= params.children * 2)); int signal = assigned < total ? 1 : -1; while (assigned != total) { - for (i = 0; i < children && assigned != total; ++i) { + for (i = 0; i < params.children && assigned != total; ++i) { sizes[i] += signal; assigned += signal; } } } + params.sizes = sizes; if (con->layout == L_OUTPUT) { /* Skip i3-internal outputs */ @@ -331,104 +336,18 @@ void render_con(Con *con, bool render_fullscreen) { } } else { - /* FIXME: refactor this into separate functions: */ Con *child; TAILQ_FOREACH(child, &(con->nodes_head), nodes) { - assert(children > 0); + assert(params.children > 0); - /* default layout */ if (con->layout == L_SPLITH || con->layout == L_SPLITV) { - if (con->layout == L_SPLITH) { - child->rect.x = x; - child->rect.y = y; - child->rect.width = sizes[i]; - child->rect.height = rect.height; - x += child->rect.width; - } else { - child->rect.x = x; - child->rect.y = y; - child->rect.width = rect.width; - child->rect.height = sizes[i]; - y += child->rect.height; - } - - /* first we have the decoration, if this is a leaf node */ - if (con_is_leaf(child)) { - if (child->border_style == BS_NORMAL) { - /* TODO: make a function for relative coords? */ - child->deco_rect.x = child->rect.x - con->rect.x; - child->deco_rect.y = child->rect.y - con->rect.y; - - child->rect.y += deco_height; - child->rect.height -= deco_height; - - child->deco_rect.width = child->rect.width; - child->deco_rect.height = deco_height; - } else { - child->deco_rect.x = 0; - child->deco_rect.y = 0; - child->deco_rect.width = 0; - child->deco_rect.height = 0; - } - } - } - - /* stacked layout */ - else if (con->layout == L_STACKED) { - child->rect.x = x; - child->rect.y = y; - child->rect.width = rect.width; - child->rect.height = rect.height; - - child->deco_rect.x = x - con->rect.x; - child->deco_rect.y = y - con->rect.y + (i * deco_height); - child->deco_rect.width = child->rect.width; - child->deco_rect.height = deco_height; - - if (children > 1 || (child->border_style != BS_PIXEL && child->border_style != BS_NONE)) { - child->rect.y += (deco_height * children); - child->rect.height -= (deco_height * children); - } - } - - /* tabbed layout */ - else if (con->layout == L_TABBED) { - child->rect.x = x; - child->rect.y = y; - child->rect.width = rect.width; - child->rect.height = rect.height; - - child->deco_rect.width = floor((float)child->rect.width / children); - child->deco_rect.x = x - con->rect.x + i * child->deco_rect.width; - child->deco_rect.y = y - con->rect.y; - - /* Since the tab width may be something like 31,6 px per tab, we - * let the last tab have all the extra space (0,6 * children). */ - if (i == (children - 1)) { - child->deco_rect.width += (child->rect.width - (child->deco_rect.x + child->deco_rect.width)); - } - - if (children > 1 || (child->border_style != BS_PIXEL && child->border_style != BS_NONE)) { - child->rect.y += deco_height; - child->rect.height -= deco_height; - child->deco_rect.height = deco_height; - } else { - child->deco_rect.height = (child->border_style == BS_PIXEL ? 1 : 0); - } - } - - /* dockarea layout */ - else if (con->layout == L_DOCKAREA) { - child->rect.x = x; - child->rect.y = y; - child->rect.width = rect.width; - child->rect.height = child->geometry.height; - - child->deco_rect.x = 0; - child->deco_rect.y = 0; - child->deco_rect.width = 0; - child->deco_rect.height = 0; - y += child->rect.height; + render_con_split(con, child, ¶ms, i); + } else if (con->layout == L_STACKED) { + render_con_stacked(con, child, ¶ms, i); + } else if (con->layout == L_TABBED) { + render_con_tabbed(con, child, ¶ms, i); + } else if (con->layout == L_DOCKAREA) { + render_con_dockarea(con, child, ¶ms); } DLOG("child at (%d, %d) with (%d x %d)\n", @@ -450,7 +369,7 @@ void render_con(Con *con, bool render_fullscreen) { render_con(child, false); } - if (children != 1) + if (params.children != 1) /* Raise the stack con itself. This will put the stack decoration on * top of every stack window. That way, when a new window is opened in * the stack, the old window will not obscure part of the decoration @@ -459,3 +378,102 @@ void render_con(Con *con, bool render_fullscreen) { } } } + +static void render_con_split(Con *con, Con *child, render_params *p, int i) { + assert(con->layout == L_SPLITH || con->layout == L_SPLITV); + + if (con->layout == L_SPLITH) { + child->rect.x = p->x; + child->rect.y = p->y; + child->rect.width = p->sizes[i]; + child->rect.height = p->rect.height; + p->x += child->rect.width; + } else { + child->rect.x = p->x; + child->rect.y = p->y; + child->rect.width = p->rect.width; + child->rect.height = p->sizes[i]; + p->y += child->rect.height; + } + + /* first we have the decoration, if this is a leaf node */ + if (con_is_leaf(child)) { + if (child->border_style == BS_NORMAL) { + /* TODO: make a function for relative coords? */ + child->deco_rect.x = child->rect.x - con->rect.x; + child->deco_rect.y = child->rect.y - con->rect.y; + + child->rect.y += p->deco_height; + child->rect.height -= p->deco_height; + + child->deco_rect.width = child->rect.width; + child->deco_rect.height = p->deco_height; + } else { + child->deco_rect.x = 0; + child->deco_rect.y = 0; + child->deco_rect.width = 0; + child->deco_rect.height = 0; + } + } +} + +static void render_con_stacked(Con *con, Con *child, render_params *p, int i) { + assert(con->layout == L_STACKED); + + child->rect.x = p->x; + child->rect.y = p->y; + child->rect.width = p->rect.width; + child->rect.height = p->rect.height; + + child->deco_rect.x = p->x - con->rect.x; + child->deco_rect.y = p->y - con->rect.y + (i * p->deco_height); + child->deco_rect.width = child->rect.width; + child->deco_rect.height = p->deco_height; + + if (p->children > 1 || (child->border_style != BS_PIXEL && child->border_style != BS_NONE)) { + child->rect.y += (p->deco_height * p->children); + child->rect.height -= (p->deco_height * p->children); + } +} + +static void render_con_tabbed(Con *con, Con *child, render_params *p, int i) { + assert(con->layout == L_TABBED); + + child->rect.x = p->x; + child->rect.y = p->y; + child->rect.width = p->rect.width; + child->rect.height = p->rect.height; + + child->deco_rect.width = floor((float)child->rect.width / p->children); + child->deco_rect.x = p->x - con->rect.x + i * child->deco_rect.width; + child->deco_rect.y = p->y - con->rect.y; + + /* Since the tab width may be something like 31,6 px per tab, we + * let the last tab have all the extra space (0,6 * children). */ + if (i == (p->children - 1)) { + child->deco_rect.width += (child->rect.width - (child->deco_rect.x + child->deco_rect.width)); + } + + if (p->children > 1 || (child->border_style != BS_PIXEL && child->border_style != BS_NONE)) { + child->rect.y += p->deco_height; + child->rect.height -= p->deco_height; + child->deco_rect.height = p->deco_height; + } else { + child->deco_rect.height = (child->border_style == BS_PIXEL ? 1 : 0); + } +} + +static void render_con_dockarea(Con *con, Con *child, render_params *p) { + assert(con->layout == L_DOCKAREA); + + child->rect.x = p->x; + child->rect.y = p->y; + child->rect.width = p->rect.width; + child->rect.height = child->geometry.height; + + child->deco_rect.x = 0; + child->deco_rect.y = 0; + child->deco_rect.width = 0; + child->deco_rect.height = 0; + p->y += child->rect.height; +} From a194cab5231e7e0ec1e40042695d794c2fdd0ee1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ingo=20B=C3=BCrk?= Date: Thu, 10 Sep 2015 20:43:33 +0200 Subject: [PATCH 002/187] Move precalculating the container sizes into a separate function --- src/render.c | 58 ++++++++++++++++++++++++++++++---------------------- 1 file changed, 33 insertions(+), 25 deletions(-) diff --git a/src/render.c b/src/render.c index 94404347..94814220 100644 --- a/src/render.c +++ b/src/render.c @@ -13,6 +13,7 @@ #include "all.h" /* Forward declarations */ +static int *precalculate_sizes(Con *con, render_params *p); static void render_con_split(Con *con, Con *child, render_params *p, int i); static void render_con_stacked(Con *con, Con *child, render_params *p, int i); static void render_con_tabbed(Con *con, Con *child, render_params *p, int i); @@ -228,35 +229,12 @@ void render_con(Con *con, bool render_fullscreen) { params.deco_height = render_deco_height(); /* precalculate the sizes to be able to correct rounding errors */ - int sizes[params.children]; - memset(sizes, 0, params.children * sizeof(int)); - if ((con->layout == L_SPLITH || con->layout == L_SPLITV) && params.children > 0) { - assert(!TAILQ_EMPTY(&con->nodes_head)); - - Con *child; - int i = 0, assigned = 0; - int total = con_orientation(con) == HORIZ ? params.rect.width : params.rect.height; - TAILQ_FOREACH(child, &(con->nodes_head), nodes) { - double percentage = child->percent > 0.0 ? child->percent : 1.0 / params.children; - assigned += sizes[i++] = percentage * total; - } - assert(assigned == total || - (assigned > total && assigned - total <= params.children * 2) || - (assigned < total && total - assigned <= params.children * 2)); - int signal = assigned < total ? 1 : -1; - while (assigned != total) { - for (i = 0; i < params.children && assigned != total; ++i) { - sizes[i] += signal; - assigned += signal; - } - } - } - params.sizes = sizes; + params.sizes = precalculate_sizes(con, ¶ms); if (con->layout == L_OUTPUT) { /* Skip i3-internal outputs */ if (con_is_internal(con)) - return; + goto free_params; render_l_output(con); } else if (con->type == CT_ROOT) { Con *output; @@ -377,6 +355,36 @@ void render_con(Con *con, bool render_fullscreen) { x_raise_con(con); } } + +free_params: + FREE(params.sizes); +} + +static int *precalculate_sizes(Con *con, render_params *p) { + int *sizes = smalloc(p->children * sizeof(int)); + if ((con->layout == L_SPLITH || con->layout == L_SPLITV) && p->children > 0) { + assert(!TAILQ_EMPTY(&con->nodes_head)); + + Con *child; + int i = 0, assigned = 0; + int total = con_orientation(con) == HORIZ ? p->rect.width : p->rect.height; + TAILQ_FOREACH(child, &(con->nodes_head), nodes) { + double percentage = child->percent > 0.0 ? child->percent : 1.0 / p->children; + assigned += sizes[i++] = percentage * total; + } + assert(assigned == total || + (assigned > total && assigned - total <= p->children * 2) || + (assigned < total && total - assigned <= p->children * 2)); + int signal = assigned < total ? 1 : -1; + while (assigned != total) { + for (i = 0; i < p->children && assigned != total; ++i) { + sizes[i] += signal; + assigned += signal; + } + } + } + + return sizes; } static void render_con_split(Con *con, Con *child, render_params *p, int i) { From bb5be480e71dbe776afbebcdc033319f103733bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ingo=20B=C3=BCrk?= Date: Thu, 10 Sep 2015 20:49:14 +0200 Subject: [PATCH 003/187] Move rendering the root node into a separate function. --- src/render.c | 536 ++++++++++++++++++++++++++------------------------- 1 file changed, 270 insertions(+), 266 deletions(-) diff --git a/src/render.c b/src/render.c index 94814220..f8c49c38 100644 --- a/src/render.c +++ b/src/render.c @@ -14,6 +14,8 @@ /* Forward declarations */ static int *precalculate_sizes(Con *con, render_params *p); +static void render_root(Con *con, Con *fullscreen); +static void render_output(Con *con); static void render_con_split(Con *con, Con *child, render_params *p, int i); static void render_con_stacked(Con *con, Con *child, render_params *p, int i); static void render_con_tabbed(Con *con, Con *child, render_params *p, int i); @@ -33,12 +35,279 @@ int render_deco_height(void) { return deco_height; } +/* + * "Renders" the given container (and its children), meaning that all rects are + * updated correctly. Note that this function does not call any xcb_* + * functions, so the changes are completely done in memory only (and + * side-effect free). As soon as you call x_push_changes(), the changes will be + * updated in X11. + * + */ +void render_con(Con *con, bool render_fullscreen) { + render_params params = { + .rect = con->rect, + .x = con->rect.x, + .y = con->rect.y, + .children = con_num_children(con)}; + + DLOG("Rendering %snode %p / %s / layout %d / children %d\n", + (render_fullscreen ? "fullscreen " : ""), con, con->name, con->layout, + params.children); + + /* Display a border if this is a leaf node. For container nodes, we don’t + * draw borders (except when in debug mode) */ + if (show_debug_borders) { + params.rect.x += 2; + params.rect.y += 2; + params.rect.width -= 2 * 2; + params.rect.height -= 2 * 2; + } + + int i = 0; + con->mapped = true; + + /* if this container contains a window, set the coordinates */ + if (con->window) { + /* depending on the border style, the rect of the child window + * needs to be smaller */ + Rect *inset = &(con->window_rect); + *inset = (Rect){0, 0, con->rect.width, con->rect.height}; + if (!render_fullscreen) + *inset = rect_add(*inset, con_border_style_rect(con)); + + /* Obey x11 border */ + inset->width -= (2 * con->border_width); + inset->height -= (2 * con->border_width); + + /* Obey the aspect ratio, if any, unless we are in fullscreen mode. + * + * The spec isn’t explicit on whether the aspect ratio hints should be + * respected during fullscreen mode. Other WMs such as Openbox don’t do + * that, and this post suggests that this is the correct way to do it: + * http://mail.gnome.org/archives/wm-spec-list/2003-May/msg00007.html + * + * Ignoring aspect ratio during fullscreen was necessary to fix MPlayer + * subtitle rendering, see http://bugs.i3wm.org/594 */ + if (!render_fullscreen && con->window->aspect_ratio > 0.0) { + DLOG("aspect_ratio = %f, current width/height are %d/%d\n", + con->window->aspect_ratio, inset->width, inset->height); + double new_height = inset->height + 1; + int new_width = inset->width; + + while (new_height > inset->height) { + new_height = (1.0 / con->window->aspect_ratio) * new_width; + + if (new_height > inset->height) + new_width--; + } + /* Center the window */ + inset->y += ceil(inset->height / 2) - floor((new_height + .5) / 2); + inset->x += ceil(inset->width / 2) - floor(new_width / 2); + + inset->height = new_height + .5; + inset->width = new_width; + } + + /* NB: We used to respect resize increment size hints for tiling + * windows up until commit 0db93d9 here. However, since all terminal + * emulators cope with ignoring the size hints in a better way than we + * can (by providing their fake-transparency or background color), this + * code was removed. See also http://bugs.i3wm.org/540 */ + + DLOG("child will be at %dx%d with size %dx%d\n", inset->x, inset->y, inset->width, inset->height); + } + + /* Check for fullscreen nodes */ + Con *fullscreen = NULL; + if (con->type != CT_OUTPUT) { + fullscreen = con_get_fullscreen_con(con, (con->type == CT_ROOT ? CF_GLOBAL : CF_OUTPUT)); + } + if (fullscreen) { + fullscreen->rect = params.rect; + x_raise_con(fullscreen); + render_con(fullscreen, true); + /* Fullscreen containers are either global (underneath the CT_ROOT + * container) or per-output (underneath the CT_CONTENT container). For + * global fullscreen containers, we cannot abort rendering here yet, + * because the floating windows (with popup_during_fullscreen smart) + * have not yet been rendered (see the CT_ROOT code path below). See + * also http://bugs.i3wm.org/1393 */ + if (con->type != CT_ROOT) { + return; + } + } + + /* find the height for the decorations */ + params.deco_height = render_deco_height(); + + /* precalculate the sizes to be able to correct rounding errors */ + params.sizes = precalculate_sizes(con, ¶ms); + + if (con->layout == L_OUTPUT) { + /* Skip i3-internal outputs */ + if (con_is_internal(con)) + goto free_params; + render_output(con); + } else if (con->type == CT_ROOT) { + render_root(con, fullscreen); + } else { + Con *child; + TAILQ_FOREACH(child, &(con->nodes_head), nodes) { + assert(params.children > 0); + + if (con->layout == L_SPLITH || con->layout == L_SPLITV) { + render_con_split(con, child, ¶ms, i); + } else if (con->layout == L_STACKED) { + render_con_stacked(con, child, ¶ms, i); + } else if (con->layout == L_TABBED) { + render_con_tabbed(con, child, ¶ms, i); + } else if (con->layout == L_DOCKAREA) { + render_con_dockarea(con, child, ¶ms); + } + + DLOG("child at (%d, %d) with (%d x %d)\n", + child->rect.x, child->rect.y, child->rect.width, child->rect.height); + x_raise_con(child); + render_con(child, false); + i++; + } + + /* in a stacking or tabbed container, we ensure the focused client is raised */ + if (con->layout == L_STACKED || con->layout == L_TABBED) { + TAILQ_FOREACH_REVERSE(child, &(con->focus_head), focus_head, focused) + x_raise_con(child); + if ((child = TAILQ_FIRST(&(con->focus_head)))) { + /* By rendering the stacked container again, we handle the case + * that we have a non-leaf-container inside the stack. In that + * case, the children of the non-leaf-container need to be raised + * aswell. */ + render_con(child, false); + } + + if (params.children != 1) + /* Raise the stack con itself. This will put the stack decoration on + * top of every stack window. That way, when a new window is opened in + * the stack, the old window will not obscure part of the decoration + * (it’s unmapped afterwards). */ + x_raise_con(con); + } + } + +free_params: + FREE(params.sizes); +} + +static int *precalculate_sizes(Con *con, render_params *p) { + int *sizes = smalloc(p->children * sizeof(int)); + if ((con->layout == L_SPLITH || con->layout == L_SPLITV) && p->children > 0) { + assert(!TAILQ_EMPTY(&con->nodes_head)); + + Con *child; + int i = 0, assigned = 0; + int total = con_orientation(con) == HORIZ ? p->rect.width : p->rect.height; + TAILQ_FOREACH(child, &(con->nodes_head), nodes) { + double percentage = child->percent > 0.0 ? child->percent : 1.0 / p->children; + assigned += sizes[i++] = percentage * total; + } + assert(assigned == total || + (assigned > total && assigned - total <= p->children * 2) || + (assigned < total && total - assigned <= p->children * 2)); + int signal = assigned < total ? 1 : -1; + while (assigned != total) { + for (i = 0; i < p->children && assigned != total; ++i) { + sizes[i] += signal; + assigned += signal; + } + } + } + + return sizes; +} + +static void render_root(Con *con, Con *fullscreen) { + Con *output; + if (!fullscreen) { + TAILQ_FOREACH(output, &(con->nodes_head), nodes) { + render_con(output, false); + } + } + + /* We need to render floating windows after rendering all outputs’ + * tiling windows because they need to be on top of *every* output at + * all times. This is important when the user places floating + * windows/containers so that they overlap on another output. */ + DLOG("Rendering floating windows:\n"); + TAILQ_FOREACH(output, &(con->nodes_head), nodes) { + if (con_is_internal(output)) + continue; + /* Get the active workspace of that output */ + Con *content = output_get_content(output); + if (!content || TAILQ_EMPTY(&(content->focus_head))) { + DLOG("Skipping this output because it is currently being destroyed.\n"); + continue; + } + Con *workspace = TAILQ_FIRST(&(content->focus_head)); + Con *fullscreen = con_get_fullscreen_con(workspace, CF_OUTPUT); + Con *child; + TAILQ_FOREACH(child, &(workspace->floating_head), floating_windows) { + /* Don’t render floating windows when there is a fullscreen window + * on that workspace. Necessary to make floating fullscreen work + * correctly (ticket #564). */ + /* If there is no fullscreen->window, this cannot be a + * transient window, so we _know_ we need to skip it. This + * happens during restarts where the container already exists, + * but the window was not yet associated. */ + if (fullscreen != NULL && fullscreen->window == NULL) + continue; + if (fullscreen != NULL && fullscreen->window != NULL) { + Con *floating_child = con_descend_focused(child); + Con *transient_con = floating_child; + bool is_transient_for = false; + /* Exception to the above rule: smart + * popup_during_fullscreen handling (popups belonging to + * the fullscreen app will be rendered). */ + while (transient_con != NULL && + transient_con->window != NULL && + transient_con->window->transient_for != XCB_NONE) { + DLOG("transient_con = 0x%08x, transient_con->window->transient_for = 0x%08x, fullscreen_id = 0x%08x\n", + transient_con->window->id, transient_con->window->transient_for, fullscreen->window->id); + if (transient_con->window->transient_for == fullscreen->window->id) { + is_transient_for = true; + break; + } + Con *next_transient = con_by_window_id(transient_con->window->transient_for); + if (next_transient == NULL) + break; + /* Some clients (e.g. x11-ssh-askpass) actually set + * WM_TRANSIENT_FOR to their own window id, so break instead of + * looping endlessly. */ + if (transient_con == next_transient) + break; + transient_con = next_transient; + } + + if (!is_transient_for) + continue; + else { + DLOG("Rendering floating child even though in fullscreen mode: " + "floating->transient_for (0x%08x) --> fullscreen->id (0x%08x)\n", + floating_child->window->transient_for, fullscreen->window->id); + } + } + DLOG("floating child at (%d,%d) with %d x %d\n", + child->rect.x, child->rect.y, child->rect.width, child->rect.height); + x_raise_con(child); + render_con(child, false); + } + } +} + /* * Renders a container with layout L_OUTPUT. In this layout, all CT_DOCKAREAs * get the height of their content and the remaining CT_CON gets the rest. * */ -static void render_l_output(Con *con) { +static void render_output(Con *con) { Con *child, *dockchild; int x = con->rect.x; @@ -122,271 +391,6 @@ static void render_l_output(Con *con) { } } -/* - * "Renders" the given container (and its children), meaning that all rects are - * updated correctly. Note that this function does not call any xcb_* - * functions, so the changes are completely done in memory only (and - * side-effect free). As soon as you call x_push_changes(), the changes will be - * updated in X11. - * - */ -void render_con(Con *con, bool render_fullscreen) { - render_params params = { - .rect = con->rect, - .x = con->rect.x, - .y = con->rect.y, - .children = con_num_children(con)}; - - DLOG("Rendering %snode %p / %s / layout %d / children %d\n", - (render_fullscreen ? "fullscreen " : ""), con, con->name, con->layout, - params.children); - - /* Display a border if this is a leaf node. For container nodes, we don’t - * draw borders (except when in debug mode) */ - if (show_debug_borders) { - params.rect.x += 2; - params.rect.y += 2; - params.rect.width -= 2 * 2; - params.rect.height -= 2 * 2; - } - - int i = 0; - con->mapped = true; - - /* if this container contains a window, set the coordinates */ - if (con->window) { - /* depending on the border style, the rect of the child window - * needs to be smaller */ - Rect *inset = &(con->window_rect); - *inset = (Rect){0, 0, con->rect.width, con->rect.height}; - if (!render_fullscreen) - *inset = rect_add(*inset, con_border_style_rect(con)); - - /* Obey x11 border */ - inset->width -= (2 * con->border_width); - inset->height -= (2 * con->border_width); - - /* Obey the aspect ratio, if any, unless we are in fullscreen mode. - * - * The spec isn’t explicit on whether the aspect ratio hints should be - * respected during fullscreen mode. Other WMs such as Openbox don’t do - * that, and this post suggests that this is the correct way to do it: - * http://mail.gnome.org/archives/wm-spec-list/2003-May/msg00007.html - * - * Ignoring aspect ratio during fullscreen was necessary to fix MPlayer - * subtitle rendering, see http://bugs.i3wm.org/594 */ - if (!render_fullscreen && - con->window->aspect_ratio > 0.0) { - DLOG("aspect_ratio = %f, current width/height are %d/%d\n", - con->window->aspect_ratio, inset->width, inset->height); - double new_height = inset->height + 1; - int new_width = inset->width; - - while (new_height > inset->height) { - new_height = (1.0 / con->window->aspect_ratio) * new_width; - - if (new_height > inset->height) - new_width--; - } - /* Center the window */ - inset->y += ceil(inset->height / 2) - floor((new_height + .5) / 2); - inset->x += ceil(inset->width / 2) - floor(new_width / 2); - - inset->height = new_height + .5; - inset->width = new_width; - } - - /* NB: We used to respect resize increment size hints for tiling - * windows up until commit 0db93d9 here. However, since all terminal - * emulators cope with ignoring the size hints in a better way than we - * can (by providing their fake-transparency or background color), this - * code was removed. See also http://bugs.i3wm.org/540 */ - - DLOG("child will be at %dx%d with size %dx%d\n", inset->x, inset->y, inset->width, inset->height); - } - - /* Check for fullscreen nodes */ - Con *fullscreen = NULL; - if (con->type != CT_OUTPUT) { - fullscreen = con_get_fullscreen_con(con, (con->type == CT_ROOT ? CF_GLOBAL : CF_OUTPUT)); - } - if (fullscreen) { - fullscreen->rect = params.rect; - x_raise_con(fullscreen); - render_con(fullscreen, true); - /* Fullscreen containers are either global (underneath the CT_ROOT - * container) or per-output (underneath the CT_CONTENT container). For - * global fullscreen containers, we cannot abort rendering here yet, - * because the floating windows (with popup_during_fullscreen smart) - * have not yet been rendered (see the CT_ROOT code path below). See - * also http://bugs.i3wm.org/1393 */ - if (con->type != CT_ROOT) { - return; - } - } - - /* find the height for the decorations */ - params.deco_height = render_deco_height(); - - /* precalculate the sizes to be able to correct rounding errors */ - params.sizes = precalculate_sizes(con, ¶ms); - - if (con->layout == L_OUTPUT) { - /* Skip i3-internal outputs */ - if (con_is_internal(con)) - goto free_params; - render_l_output(con); - } else if (con->type == CT_ROOT) { - Con *output; - if (!fullscreen) { - TAILQ_FOREACH(output, &(con->nodes_head), nodes) { - render_con(output, false); - } - } - - /* We need to render floating windows after rendering all outputs’ - * tiling windows because they need to be on top of *every* output at - * all times. This is important when the user places floating - * windows/containers so that they overlap on another output. */ - DLOG("Rendering floating windows:\n"); - TAILQ_FOREACH(output, &(con->nodes_head), nodes) { - if (con_is_internal(output)) - continue; - /* Get the active workspace of that output */ - Con *content = output_get_content(output); - if (!content || TAILQ_EMPTY(&(content->focus_head))) { - DLOG("Skipping this output because it is currently being destroyed.\n"); - continue; - } - Con *workspace = TAILQ_FIRST(&(content->focus_head)); - Con *fullscreen = con_get_fullscreen_con(workspace, CF_OUTPUT); - Con *child; - TAILQ_FOREACH(child, &(workspace->floating_head), floating_windows) { - /* Don’t render floating windows when there is a fullscreen window - * on that workspace. Necessary to make floating fullscreen work - * correctly (ticket #564). */ - /* If there is no fullscreen->window, this cannot be a - * transient window, so we _know_ we need to skip it. This - * happens during restarts where the container already exists, - * but the window was not yet associated. */ - if (fullscreen != NULL && fullscreen->window == NULL) - continue; - if (fullscreen != NULL && fullscreen->window != NULL) { - Con *floating_child = con_descend_focused(child); - Con *transient_con = floating_child; - bool is_transient_for = false; - /* Exception to the above rule: smart - * popup_during_fullscreen handling (popups belonging to - * the fullscreen app will be rendered). */ - while (transient_con != NULL && - transient_con->window != NULL && - transient_con->window->transient_for != XCB_NONE) { - DLOG("transient_con = 0x%08x, transient_con->window->transient_for = 0x%08x, fullscreen_id = 0x%08x\n", - transient_con->window->id, transient_con->window->transient_for, fullscreen->window->id); - if (transient_con->window->transient_for == fullscreen->window->id) { - is_transient_for = true; - break; - } - Con *next_transient = con_by_window_id(transient_con->window->transient_for); - if (next_transient == NULL) - break; - /* Some clients (e.g. x11-ssh-askpass) actually set - * WM_TRANSIENT_FOR to their own window id, so break instead of - * looping endlessly. */ - if (transient_con == next_transient) - break; - transient_con = next_transient; - } - - if (!is_transient_for) - continue; - else { - DLOG("Rendering floating child even though in fullscreen mode: " - "floating->transient_for (0x%08x) --> fullscreen->id (0x%08x)\n", - floating_child->window->transient_for, fullscreen->window->id); - } - } - DLOG("floating child at (%d,%d) with %d x %d\n", - child->rect.x, child->rect.y, child->rect.width, child->rect.height); - x_raise_con(child); - render_con(child, false); - } - } - - } else { - Con *child; - TAILQ_FOREACH(child, &(con->nodes_head), nodes) { - assert(params.children > 0); - - if (con->layout == L_SPLITH || con->layout == L_SPLITV) { - render_con_split(con, child, ¶ms, i); - } else if (con->layout == L_STACKED) { - render_con_stacked(con, child, ¶ms, i); - } else if (con->layout == L_TABBED) { - render_con_tabbed(con, child, ¶ms, i); - } else if (con->layout == L_DOCKAREA) { - render_con_dockarea(con, child, ¶ms); - } - - DLOG("child at (%d, %d) with (%d x %d)\n", - child->rect.x, child->rect.y, child->rect.width, child->rect.height); - x_raise_con(child); - render_con(child, false); - i++; - } - - /* in a stacking or tabbed container, we ensure the focused client is raised */ - if (con->layout == L_STACKED || con->layout == L_TABBED) { - TAILQ_FOREACH_REVERSE(child, &(con->focus_head), focus_head, focused) - x_raise_con(child); - if ((child = TAILQ_FIRST(&(con->focus_head)))) { - /* By rendering the stacked container again, we handle the case - * that we have a non-leaf-container inside the stack. In that - * case, the children of the non-leaf-container need to be raised - * aswell. */ - render_con(child, false); - } - - if (params.children != 1) - /* Raise the stack con itself. This will put the stack decoration on - * top of every stack window. That way, when a new window is opened in - * the stack, the old window will not obscure part of the decoration - * (it’s unmapped afterwards). */ - x_raise_con(con); - } - } - -free_params: - FREE(params.sizes); -} - -static int *precalculate_sizes(Con *con, render_params *p) { - int *sizes = smalloc(p->children * sizeof(int)); - if ((con->layout == L_SPLITH || con->layout == L_SPLITV) && p->children > 0) { - assert(!TAILQ_EMPTY(&con->nodes_head)); - - Con *child; - int i = 0, assigned = 0; - int total = con_orientation(con) == HORIZ ? p->rect.width : p->rect.height; - TAILQ_FOREACH(child, &(con->nodes_head), nodes) { - double percentage = child->percent > 0.0 ? child->percent : 1.0 / p->children; - assigned += sizes[i++] = percentage * total; - } - assert(assigned == total || - (assigned > total && assigned - total <= p->children * 2) || - (assigned < total && total - assigned <= p->children * 2)); - int signal = assigned < total ? 1 : -1; - while (assigned != total) { - for (i = 0; i < p->children && assigned != total; ++i) { - sizes[i] += signal; - assigned += signal; - } - } - } - - return sizes; -} - static void render_con_split(Con *con, Con *child, render_params *p, int i) { assert(con->layout == L_SPLITH || con->layout == L_SPLITV); From 23d5d704ed4c4486a51cef58bb9d1326549a8e1d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ingo=20B=C3=BCrk?= Date: Sun, 27 Sep 2015 16:32:40 +0200 Subject: [PATCH 004/187] Allow the commands parser to use "number" arguments by making the stack typed. --- src/commands_parser.c | 127 ++++++++++++++++++++++++------------------ src/config_parser.c | 51 +---------------- 2 files changed, 76 insertions(+), 102 deletions(-) diff --git a/src/commands_parser.c b/src/commands_parser.c index ffe416f0..d311fdd1 100644 --- a/src/commands_parser.c +++ b/src/commands_parser.c @@ -73,7 +73,14 @@ typedef struct tokenptr { struct stack_entry { /* Just a pointer, not dynamically allocated. */ const char *identifier; - char *str; + enum { + STACK_STR = 0, + STACK_LONG = 1, + } type; + union { + char *str; + long num; + } val; }; /* 10 entries should be enough for everybody. */ @@ -90,7 +97,30 @@ static void push_string(const char *identifier, char *str) { continue; /* Found a free slot, let’s store it here. */ stack[c].identifier = identifier; - stack[c].str = str; + stack[c].val.str = str; + stack[c].type = STACK_STR; + return; + } + + /* When we arrive here, the stack is full. This should not happen and + * means there’s either a bug in this parser or the specification + * contains a command with more than 10 identified tokens. */ + fprintf(stderr, "BUG: commands_parser stack full. This means either a bug " + "in the code, or a new command which contains more than " + "10 identified tokens.\n"); + exit(1); +} + +// TODO move to a common util +static void push_long(const char *identifier, long num) { + for (int c = 0; c < 10; c++) { + if (stack[c].identifier != NULL) { + continue; + } + + stack[c].identifier = identifier; + stack[c].val.num = num; + stack[c].type = STACK_LONG; return; } @@ -105,72 +135,40 @@ static void push_string(const char *identifier, char *str) { // XXX: ideally, this would be const char. need to check if that works with all // called functions. +// TODO move to a common util static char *get_string(const char *identifier) { for (int c = 0; c < 10; c++) { if (stack[c].identifier == NULL) break; if (strcmp(identifier, stack[c].identifier) == 0) - return stack[c].str; + return stack[c].val.str; } return NULL; } +// TODO move to a common util +static long get_long(const char *identifier) { + for (int c = 0; c < 10; c++) { + if (stack[c].identifier == NULL) + break; + if (strcmp(identifier, stack[c].identifier) == 0) + return stack[c].val.num; + } + + return 0; +} + +// TODO move to a common util static void clear_stack(void) { for (int c = 0; c < 10; c++) { - if (stack[c].str != NULL) - free(stack[c].str); + if (stack[c].type == STACK_STR && stack[c].val.str != NULL) + free(stack[c].val.str); stack[c].identifier = NULL; - stack[c].str = NULL; + stack[c].val.str = NULL; + stack[c].val.num = 0; } } -// TODO: remove this if it turns out we don’t need it for testing. -#if 0 -/******************************************************************************* - * A dynamically growing linked list which holds the criteria for the current - * command. - ******************************************************************************/ - -typedef struct criterion { - char *type; - char *value; - - TAILQ_ENTRY(criterion) criteria; -} criterion; - -static TAILQ_HEAD(criteria_head, criterion) criteria = - TAILQ_HEAD_INITIALIZER(criteria); - -/* - * Stores the given type/value in the list of criteria. - * Accepts a pointer as first argument, since it is 'call'ed by the parser. - * - */ -static void push_criterion(void *unused_criteria, const char *type, - const char *value) { - struct criterion *criterion = smalloc(sizeof(struct criterion)); - criterion->type = sstrdup(type); - criterion->value = sstrdup(value); - TAILQ_INSERT_TAIL(&criteria, criterion, criteria); -} - -/* - * Clears the criteria linked list. - * Accepts a pointer as first argument, since it is 'call'ed by the parser. - * - */ -static void clear_criteria(void *unused_criteria) { - struct criterion *criterion; - while (!TAILQ_EMPTY(&criteria)) { - criterion = TAILQ_FIRST(&criteria); - free(criterion->type); - free(criterion->value); - TAILQ_REMOVE(&criteria, criterion, criteria); - free(criterion); - } -} -#endif - /******************************************************************************* * The parser itself. ******************************************************************************/ @@ -316,6 +314,29 @@ CommandResult *parse_command(const char *input, yajl_gen gen) { continue; } + if (strcmp(token->name, "number") == 0) { + /* Handle numbers. We only accept decimal numbers for now. */ + char *end = NULL; + errno = 0; + long int num = strtol(walk, &end, 10); + if ((errno == ERANGE && (num == LONG_MIN || num == LONG_MAX)) || + (errno != 0 && num == 0)) + continue; + + /* No valid numbers found */ + if (end == walk) + continue; + + if (token->identifier != NULL) + push_long(token->identifier, num); + + /* Set walk to the first non-number character */ + walk = end; + next_state(token); + token_handled = true; + break; + } + if (strcmp(token->name, "string") == 0 || strcmp(token->name, "word") == 0) { char *str = parse_string(&walk, (token->name[0] != 's')); diff --git a/src/config_parser.c b/src/config_parser.c index ea00412d..705a3e24 100644 --- a/src/config_parser.c +++ b/src/config_parser.c @@ -122,7 +122,7 @@ static void push_string(const char *identifier, const char *str) { /* When we arrive here, the stack is full. This should not happen and * means there’s either a bug in this parser or the specification * contains a command with more than 10 identified tokens. */ - fprintf(stderr, "BUG: commands_parser stack full. This means either a bug " + fprintf(stderr, "BUG: config_parser stack full. This means either a bug " "in the code, or a new command which contains more than " "10 identified tokens.\n"); exit(1); @@ -142,7 +142,7 @@ static void push_long(const char *identifier, long num) { /* When we arrive here, the stack is full. This should not happen and * means there’s either a bug in this parser or the specification * contains a command with more than 10 identified tokens. */ - fprintf(stderr, "BUG: commands_parser stack full. This means either a bug " + fprintf(stderr, "BUG: config_parser stack full. This means either a bug " "in the code, or a new command which contains more than " "10 identified tokens.\n"); exit(1); @@ -178,53 +178,6 @@ static void clear_stack(void) { } } -// TODO: remove this if it turns out we don’t need it for testing. -#if 0 -/******************************************************************************* - * A dynamically growing linked list which holds the criteria for the current - * command. - ******************************************************************************/ - -typedef struct criterion { - char *type; - char *value; - - TAILQ_ENTRY(criterion) criteria; -} criterion; - -static TAILQ_HEAD(criteria_head, criterion) criteria = - TAILQ_HEAD_INITIALIZER(criteria); - -/* - * Stores the given type/value in the list of criteria. - * Accepts a pointer as first argument, since it is 'call'ed by the parser. - * - */ -static void push_criterion(void *unused_criteria, const char *type, - const char *value) { - struct criterion *criterion = smalloc(sizeof(struct criterion)); - criterion->type = sstrdup(type); - criterion->value = sstrdup(value); - TAILQ_INSERT_TAIL(&criteria, criterion, criteria); -} - -/* - * Clears the criteria linked list. - * Accepts a pointer as first argument, since it is 'call'ed by the parser. - * - */ -static void clear_criteria(void *unused_criteria) { - struct criterion *criterion; - while (!TAILQ_EMPTY(&criteria)) { - criterion = TAILQ_FIRST(&criteria); - free(criterion->type); - free(criterion->value); - TAILQ_REMOVE(&criteria, criterion, criteria); - free(criterion); - } -} -#endif - /******************************************************************************* * The parser itself. ******************************************************************************/ From 0ae9cddc987d653ae7538f60ba4c62a5defc2509 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ingo=20B=C3=BCrk?= Date: Sun, 27 Sep 2015 16:32:54 +0200 Subject: [PATCH 005/187] Migrate the resize command to use typed numbers. --- include/commands.h | 4 ++-- parser-specs/commands.spec | 16 ++++++++-------- src/commands.c | 33 ++++++++++++++------------------- 3 files changed, 24 insertions(+), 29 deletions(-) diff --git a/include/commands.h b/include/commands.h index 80b0efb0..de6c499d 100644 --- a/include/commands.h +++ b/include/commands.h @@ -64,13 +64,13 @@ void cmd_move_con_to_workspace_number(I3_CMD, char *which); * Implementation of 'resize set [px] [px]'. * */ -void cmd_size(I3_CMD, char *cwidth, char *cheight); +void cmd_resize_set(I3_CMD, long cwidth, long cheight); /** * Implementation of 'resize grow|shrink [ px] [or ppt]'. * */ -void cmd_resize(I3_CMD, char *way, char *direction, char *resize_px, char *resize_ppt); +void cmd_resize(I3_CMD, char *way, char *direction, long resize_px, long resize_ppt); /** * Implementation of 'border normal|pixel []', 'border none|1pixel|toggle'. diff --git a/parser-specs/commands.spec b/parser-specs/commands.spec index 5e2bfd8f..c7510914 100644 --- a/parser-specs/commands.spec +++ b/parser-specs/commands.spec @@ -225,10 +225,10 @@ state RESIZE_DIRECTION: -> RESIZE_PX state RESIZE_PX: - resize_px = word + resize_px = number -> RESIZE_TILING end - -> call cmd_resize($way, $direction, "10", "10") + -> call cmd_resize($way, $direction, 10, 10) state RESIZE_TILING: 'px' @@ -236,29 +236,29 @@ state RESIZE_TILING: 'or' -> RESIZE_TILING_OR end - -> call cmd_resize($way, $direction, $resize_px, "10") + -> call cmd_resize($way, $direction, &resize_px, 10) state RESIZE_TILING_OR: - resize_ppt = word + resize_ppt = number -> RESIZE_TILING_FINAL state RESIZE_TILING_FINAL: 'ppt', end - -> call cmd_resize($way, $direction, $resize_px, $resize_ppt) + -> call cmd_resize($way, $direction, &resize_px, &resize_ppt) state RESIZE_SET: - width = word + width = number -> RESIZE_WIDTH state RESIZE_WIDTH: 'px' -> - height = word + height = number -> RESIZE_HEIGHT state RESIZE_HEIGHT: 'px', end - -> call cmd_size($width, $height) + -> call cmd_resize_set(&width, &height) # rename workspace to # rename workspace to diff --git a/src/commands.c b/src/commands.c index a696ad34..ec657a64 100644 --- a/src/commands.c +++ b/src/commands.c @@ -782,15 +782,11 @@ static bool cmd_resize_tiling_width_height(I3_CMD, Con *current, char *way, char * Implementation of 'resize grow|shrink [ px] [or ppt]'. * */ -void cmd_resize(I3_CMD, char *way, char *direction, char *resize_px, char *resize_ppt) { - /* resize [ px] [or ppt] */ - DLOG("resizing in way %s, direction %s, px %s or ppt %s\n", way, direction, resize_px, resize_ppt); - // TODO: We could either handle this in the parser itself as a separate token (and make the stack typed) or we need a better way to convert a string to a number with error checking - int px = atoi(resize_px); - int ppt = atoi(resize_ppt); +void cmd_resize(I3_CMD, char *way, char *direction, long resize_px, long resize_ppt) { + DLOG("resizing in way %s, direction %s, px %ld or ppt %ld\n", way, direction, resize_px, resize_ppt); if (strcmp(way, "shrink") == 0) { - px *= -1; - ppt *= -1; + resize_px *= -1; + resize_ppt *= -1; } HANDLE_EMPTY_MATCH; @@ -805,14 +801,16 @@ void cmd_resize(I3_CMD, char *way, char *direction, char *resize_px, char *resiz Con *floating_con; if ((floating_con = con_inside_floating(current->con))) { - cmd_resize_floating(current_match, cmd_output, way, direction, floating_con, px); + cmd_resize_floating(current_match, cmd_output, way, direction, floating_con, resize_px); } else { if (strcmp(direction, "width") == 0 || strcmp(direction, "height") == 0) { - if (!cmd_resize_tiling_width_height(current_match, cmd_output, current->con, way, direction, ppt)) + if (!cmd_resize_tiling_width_height(current_match, cmd_output, + current->con, way, direction, resize_ppt)) return; } else { - if (!cmd_resize_tiling_direction(current_match, cmd_output, current->con, way, direction, ppt)) + if (!cmd_resize_tiling_direction(current_match, cmd_output, + current->con, way, direction, resize_ppt)) return; } } @@ -827,13 +825,10 @@ void cmd_resize(I3_CMD, char *way, char *direction, char *resize_px, char *resiz * Implementation of 'resize set [px] [px]'. * */ -void cmd_size(I3_CMD, char *cwidth, char *cheight) { - DLOG("resizing to %sx%s px\n", cwidth, cheight); - // TODO: We could either handle this in the parser itself as a separate token (and make the stack typed) or we need a better way to convert a string to a number with error checking - int x = atoi(cwidth); - int y = atoi(cheight); - if (x <= 0 || y <= 0) { - ELOG("Resize failed: dimensions cannot be negative (was %sx%s)\n", cwidth, cheight); +void cmd_resize_set(I3_CMD, long cwidth, long cheight) { + DLOG("resizing to %ldx%ld px\n", cwidth, cheight); + if (cwidth <= 0 || cheight <= 0) { + ELOG("Resize failed: dimensions cannot be negative (was %ldx%ld)\n", cwidth, cheight); return; } @@ -843,7 +838,7 @@ void cmd_size(I3_CMD, char *cwidth, char *cheight) { TAILQ_FOREACH(current, &owindows, owindows) { Con *floating_con; if ((floating_con = con_inside_floating(current->con))) { - floating_resize(floating_con, x, y); + floating_resize(floating_con, cwidth, cheight); } else { ELOG("Resize failed: %p not a floating container\n", current->con); } From c7ca6e1b413484cc7dacffd1a71a682175762b4c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ingo=20B=C3=BCrk?= Date: Sun, 27 Sep 2015 16:59:36 +0200 Subject: [PATCH 006/187] Migrate the move command to use typed numbers. --- include/commands.h | 4 ++-- parser-specs/commands.spec | 14 +++++++------- src/commands.c | 25 ++++++++++--------------- testcases/t/187-commands-parser.t | 2 +- 4 files changed, 20 insertions(+), 25 deletions(-) diff --git a/include/commands.h b/include/commands.h index de6c499d..ec94d371 100644 --- a/include/commands.h +++ b/include/commands.h @@ -214,7 +214,7 @@ void cmd_sticky(I3_CMD, char *action); * Implementation of 'move [ [px]]'. * */ -void cmd_move_direction(I3_CMD, char *direction, char *move_px); +void cmd_move_direction(I3_CMD, char *direction, long move_px); /** * Implementation of 'layout default|stacked|stacking|tabbed|splitv|splith'. @@ -262,7 +262,7 @@ void cmd_focus_output(I3_CMD, char *name); * Implementation of 'move [window|container] [to] [absolute] position [px] [px] * */ -void cmd_move_window_to_position(I3_CMD, char *method, char *x, char *y); +void cmd_move_window_to_position(I3_CMD, char *method, long x, long y); /** * Implementation of 'move [window|container] [to] [absolute] position center diff --git a/parser-specs/commands.spec b/parser-specs/commands.spec index c7510914..b3b5e338 100644 --- a/parser-specs/commands.spec +++ b/parser-specs/commands.spec @@ -320,16 +320,16 @@ state MOVE: -> MOVE_TO_ABSOLUTE_POSITION state MOVE_DIRECTION: - pixels = word + pixels = number -> MOVE_DIRECTION_PX end - -> call cmd_move_direction($direction, "10") + -> call cmd_move_direction($direction, 10) state MOVE_DIRECTION_PX: 'px' - -> call cmd_move_direction($direction, $pixels) + -> call cmd_move_direction($direction, &pixels) end - -> call cmd_move_direction($direction, $pixels) + -> call cmd_move_direction($direction, &pixels) state MOVE_WORKSPACE: 'to ' @@ -370,18 +370,18 @@ state MOVE_TO_POSITION: -> call cmd_move_window_to_center($method) 'mouse', 'cursor', 'pointer' -> call cmd_move_window_to_mouse() - coord_x = word + coord_x = number -> MOVE_TO_POSITION_X state MOVE_TO_POSITION_X: 'px' -> - coord_y = word + coord_y = number -> MOVE_TO_POSITION_Y state MOVE_TO_POSITION_Y: 'px', end - -> call cmd_move_window_to_position($method, $coord_x, $coord_y) + -> call cmd_move_window_to_position($method, &coord_x, &coord_y) # mode state MODE: diff --git a/src/commands.c b/src/commands.c index ec657a64..be975f4c 100644 --- a/src/commands.c +++ b/src/commands.c @@ -1565,28 +1565,25 @@ void cmd_sticky(I3_CMD, char *action) { * Implementation of 'move [ [px]]'. * */ -void cmd_move_direction(I3_CMD, char *direction, char *move_px) { - // TODO: We could either handle this in the parser itself as a separate token (and make the stack typed) or we need a better way to convert a string to a number with error checking - int px = atoi(move_px); - +void cmd_move_direction(I3_CMD, char *direction, long move_px) { owindow *current; HANDLE_EMPTY_MATCH; Con *initially_focused = focused; TAILQ_FOREACH(current, &owindows, owindows) { - DLOG("moving in direction %s, px %s\n", direction, move_px); + DLOG("moving in direction %s, px %ld\n", direction, move_px); if (con_is_floating(current->con)) { - DLOG("floating move with %d pixels\n", px); + DLOG("floating move with %ld pixels\n", move_px); Rect newrect = current->con->parent->rect; if (strcmp(direction, "left") == 0) { - newrect.x -= px; + newrect.x -= move_px; } else if (strcmp(direction, "right") == 0) { - newrect.x += px; + newrect.x += move_px; } else if (strcmp(direction, "up") == 0) { - newrect.y -= px; + newrect.y -= move_px; } else if (strcmp(direction, "down") == 0) { - newrect.y += px; + newrect.y += move_px; } floating_reposition(current->con->parent, newrect); } else { @@ -1788,9 +1785,7 @@ void cmd_focus_output(I3_CMD, char *name) { * Implementation of 'move [window|container] [to] [absolute] position [px] [px] * */ -void cmd_move_window_to_position(I3_CMD, char *method, char *cx, char *cy) { - int x = atoi(cx); - int y = atoi(cy); +void cmd_move_window_to_position(I3_CMD, char *method, long x, long y) { bool has_error = false; owindow *current; @@ -1812,7 +1807,7 @@ void cmd_move_window_to_position(I3_CMD, char *method, char *cx, char *cy) { current->con->parent->rect.x = x; current->con->parent->rect.y = y; - DLOG("moving to absolute position %d %d\n", x, y); + DLOG("moving to absolute position %ld %ld\n", x, y); floating_maybe_reassign_ws(current->con->parent); cmd_output->needs_tree_render = true; } @@ -1820,7 +1815,7 @@ void cmd_move_window_to_position(I3_CMD, char *method, char *cx, char *cy) { if (strcmp(method, "position") == 0) { Rect newrect = current->con->parent->rect; - DLOG("moving to position %d %d\n", x, y); + DLOG("moving to position %ld %ld\n", x, y); newrect.x = x; newrect.y = y; diff --git a/testcases/t/187-commands-parser.t b/testcases/t/187-commands-parser.t index 4f555b29..a56d668e 100644 --- a/testcases/t/187-commands-parser.t +++ b/testcases/t/187-commands-parser.t @@ -216,7 +216,7 @@ is(parser_calls('workspace "foo\\\\\\"bar"'), ################################################################################ is(parser_calls("resize shrink width 10 px or"), - "ERROR: Expected one of these tokens: \n" . + "ERROR: Expected one of these tokens: \n" . "ERROR: Your command: resize shrink width 10 px or\n" . "ERROR: ", "error for resize command with incomplete 'or'-construction ok"); From 6cd6f43d09397f0a5ac32cdeba9681f1166afa76 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ingo=20B=C3=BCrk?= Date: Sun, 27 Sep 2015 23:42:58 +0200 Subject: [PATCH 007/187] Turn "char *" into "const char *" for all command parser functions. --- include/commands.h | 74 ++++++++++++++++++------------------ include/startup.h | 2 +- include/workspace.h | 2 +- src/commands.c | 87 ++++++++++++++++++++++--------------------- src/commands_parser.c | 4 +- src/startup.c | 2 +- src/workspace.c | 2 +- 7 files changed, 86 insertions(+), 87 deletions(-) diff --git a/include/commands.h b/include/commands.h index ec94d371..e0bb2f92 100644 --- a/include/commands.h +++ b/include/commands.h @@ -33,14 +33,14 @@ void cmd_criteria_match_windows(I3_CMD); * specification. * */ -void cmd_criteria_add(I3_CMD, char *ctype, char *cvalue); +void cmd_criteria_add(I3_CMD, const char *ctype, const char *cvalue); /** * Implementation of 'move [window|container] [to] workspace * next|prev|next_on_output|prev_on_output'. * */ -void cmd_move_con_to_workspace(I3_CMD, char *which); +void cmd_move_con_to_workspace(I3_CMD, const char *which); /** * Implementation of 'move [window|container] [to] workspace back_and_forth'. @@ -52,13 +52,13 @@ void cmd_move_con_to_workspace_back_and_forth(I3_CMD); * Implementation of 'move [window|container] [to] workspace '. * */ -void cmd_move_con_to_workspace_name(I3_CMD, char *name); +void cmd_move_con_to_workspace_name(I3_CMD, const char *name); /** * Implementation of 'move [window|container] [to] workspace number '. * */ -void cmd_move_con_to_workspace_number(I3_CMD, char *which); +void cmd_move_con_to_workspace_number(I3_CMD, const char *which); /** * Implementation of 'resize set [px] [px]'. @@ -70,37 +70,37 @@ void cmd_resize_set(I3_CMD, long cwidth, long cheight); * Implementation of 'resize grow|shrink [ px] [or ppt]'. * */ -void cmd_resize(I3_CMD, char *way, char *direction, long resize_px, long resize_ppt); +void cmd_resize(I3_CMD, const char *way, const char *direction, long resize_px, long resize_ppt); /** * Implementation of 'border normal|pixel []', 'border none|1pixel|toggle'. * */ -void cmd_border(I3_CMD, char *border_style_str, char *border_width); +void cmd_border(I3_CMD, const char *border_style_str, const char *border_width); /** * Implementation of 'nop '. * */ -void cmd_nop(I3_CMD, char *comment); +void cmd_nop(I3_CMD, const char *comment); /** * Implementation of 'append_layout '. * */ -void cmd_append_layout(I3_CMD, char *path); +void cmd_append_layout(I3_CMD, const char *path); /** * Implementation of 'workspace next|prev|next_on_output|prev_on_output'. * */ -void cmd_workspace(I3_CMD, char *which); +void cmd_workspace(I3_CMD, const char *which); /** * Implementation of 'workspace number ' * */ -void cmd_workspace_number(I3_CMD, char *which); +void cmd_workspace_number(I3_CMD, const char *which); /** * Implementation of 'workspace back_and_forth'. @@ -112,85 +112,85 @@ void cmd_workspace_back_and_forth(I3_CMD); * Implementation of 'workspace ' * */ -void cmd_workspace_name(I3_CMD, char *name); +void cmd_workspace_name(I3_CMD, const char *name); /** * Implementation of 'mark [--toggle] ' * */ -void cmd_mark(I3_CMD, char *mark, char *toggle); +void cmd_mark(I3_CMD, const char *mark, const char *toggle); /** * Implementation of 'unmark [mark]' * */ -void cmd_unmark(I3_CMD, char *mark); +void cmd_unmark(I3_CMD, const char *mark); /** * Implementation of 'mode '. * */ -void cmd_mode(I3_CMD, char *mode); +void cmd_mode(I3_CMD, const char *mode); /** * Implementation of 'move [window|container] [to] output '. * */ -void cmd_move_con_to_output(I3_CMD, char *name); +void cmd_move_con_to_output(I3_CMD, const char *name); /** * Implementation of 'move [window|container] [to] mark '. * */ -void cmd_move_con_to_mark(I3_CMD, char *mark); +void cmd_move_con_to_mark(I3_CMD, const char *mark); /** * Implementation of 'floating enable|disable|toggle' * */ -void cmd_floating(I3_CMD, char *floating_mode); +void cmd_floating(I3_CMD, const char *floating_mode); /** * Implementation of 'move workspace to [output] '. * */ -void cmd_move_workspace_to_output(I3_CMD, char *name); +void cmd_move_workspace_to_output(I3_CMD, const char *name); /** * Implementation of 'split v|h|vertical|horizontal'. * */ -void cmd_split(I3_CMD, char *direction); +void cmd_split(I3_CMD, const char *direction); /** * Implementation of 'kill [window|client]'. * */ -void cmd_kill(I3_CMD, char *kill_mode_str); +void cmd_kill(I3_CMD, const char *kill_mode_str); /** * Implementation of 'exec [--no-startup-id] '. * */ -void cmd_exec(I3_CMD, char *nosn, char *command); +void cmd_exec(I3_CMD, const char *nosn, const char *command); /** * Implementation of 'focus left|right|up|down'. * */ -void cmd_focus_direction(I3_CMD, char *direction); +void cmd_focus_direction(I3_CMD, const char *direction); /** * Implementation of 'focus tiling|floating|mode_toggle'. * */ -void cmd_focus_window_mode(I3_CMD, char *window_mode); +void cmd_focus_window_mode(I3_CMD, const char *window_mode); /** * Implementation of 'focus parent|child'. * */ -void cmd_focus_level(I3_CMD, char *level); +void cmd_focus_level(I3_CMD, const char *level); /** * Implementation of 'focus'. @@ -202,31 +202,31 @@ void cmd_focus(I3_CMD); * Implementation of 'fullscreen [enable|disable|toggle] [global]'. * */ -void cmd_fullscreen(I3_CMD, char *action, char *fullscreen_mode); +void cmd_fullscreen(I3_CMD, const char *action, const char *fullscreen_mode); /** * Implementation of 'sticky enable|disable|toggle'. * */ -void cmd_sticky(I3_CMD, char *action); +void cmd_sticky(I3_CMD, const char *action); /** * Implementation of 'move [ [px]]'. * */ -void cmd_move_direction(I3_CMD, char *direction, long move_px); +void cmd_move_direction(I3_CMD, const char *direction, long move_px); /** * Implementation of 'layout default|stacked|stacking|tabbed|splitv|splith'. * */ -void cmd_layout(I3_CMD, char *layout_str); +void cmd_layout(I3_CMD, const char *layout_str); /** * Implementation of 'layout toggle [all|split]'. * */ -void cmd_layout_toggle(I3_CMD, char *toggle_mode); +void cmd_layout_toggle(I3_CMD, const char *toggle_mode); /** * Implementation of 'exit'. @@ -256,19 +256,19 @@ void cmd_open(I3_CMD); * Implementation of 'focus output '. * */ -void cmd_focus_output(I3_CMD, char *name); +void cmd_focus_output(I3_CMD, const char *name); /** * Implementation of 'move [window|container] [to] [absolute] position [px] [px] * */ -void cmd_move_window_to_position(I3_CMD, char *method, long x, long y); +void cmd_move_window_to_position(I3_CMD, const char *method, long x, long y); /** * Implementation of 'move [window|container] [to] [absolute] position center * */ -void cmd_move_window_to_center(I3_CMD, char *method); +void cmd_move_window_to_center(I3_CMD, const char *method); /** * Implementation of 'move [window|container] [to] position mouse' @@ -292,28 +292,28 @@ void cmd_scratchpad_show(I3_CMD); * Implementation of 'title_format ' * */ -void cmd_title_format(I3_CMD, char *format); +void cmd_title_format(I3_CMD, const char *format); /** * Implementation of 'rename workspace to ' * */ -void cmd_rename_workspace(I3_CMD, char *old_name, char *new_name); +void cmd_rename_workspace(I3_CMD, const char *old_name, const char *new_name); /** * Implementation of 'bar (hidden_state hide|show|toggle)|(mode dock|hide|invisible|toggle) []' * */ -void cmd_bar(I3_CMD, char *bar_type, char *bar_value, char *bar_id); +void cmd_bar(I3_CMD, const char *bar_type, const char *bar_value, const char *bar_id); /* * Implementation of 'shmlog |toggle|on|off' * */ -void cmd_shmlog(I3_CMD, char *argument); +void cmd_shmlog(I3_CMD, const char *argument); /* * Implementation of 'debuglog toggle|on|off' * */ -void cmd_debuglog(I3_CMD, char *argument); +void cmd_debuglog(I3_CMD, const char *argument); diff --git a/include/startup.h b/include/startup.h index 7d5d2a39..9729cdc2 100644 --- a/include/startup.h +++ b/include/startup.h @@ -48,7 +48,7 @@ void startup_monitor_event(SnMonitorEvent *event, void *userdata); * Renames workspaces that are mentioned in the startup sequences. * */ -void startup_sequence_rename_workspace(char *old_name, char *new_name); +void startup_sequence_rename_workspace(const char *old_name, const char *new_name); /** * Gets the stored startup sequence for the _NET_STARTUP_ID of a given window. diff --git a/include/workspace.h b/include/workspace.h index 21ab18d6..1bee64e0 100644 --- a/include/workspace.h +++ b/include/workspace.h @@ -194,4 +194,4 @@ Con *workspace_encapsulate(Con *ws); * This returns true if and only if moving the workspace was successful. * */ -bool workspace_move_to_output(Con *ws, char *output); +bool workspace_move_to_output(Con *ws, const char *output); diff --git a/src/commands.c b/src/commands.c index be975f4c..95784c77 100644 --- a/src/commands.c +++ b/src/commands.c @@ -83,7 +83,7 @@ static Output *get_output_of_con(Con *con) { * and return true, signaling that no further workspace switching should occur in the calling function. * */ -static bool maybe_back_and_forth(struct CommandResultIR *cmd_output, char *name) { +static bool maybe_back_and_forth(struct CommandResultIR *cmd_output, const char *name) { Con *ws = con_get_workspace(focused); /* If we switched to a different workspace, do nothing */ @@ -315,7 +315,7 @@ void cmd_criteria_match_windows(I3_CMD) { * specification. * */ -void cmd_criteria_add(I3_CMD, char *ctype, char *cvalue) { +void cmd_criteria_add(I3_CMD, const char *ctype, const char *cvalue) { DLOG("ctype=*%s*, cvalue=*%s*\n", ctype, cvalue); if (strcmp(ctype, "class") == 0) { @@ -424,7 +424,7 @@ void cmd_criteria_add(I3_CMD, char *ctype, char *cvalue) { * next|prev|next_on_output|prev_on_output|current'. * */ -void cmd_move_con_to_workspace(I3_CMD, char *which) { +void cmd_move_con_to_workspace(I3_CMD, const char *which) { owindow *current; DLOG("which=%s\n", which); @@ -500,7 +500,7 @@ void cmd_move_con_to_workspace_back_and_forth(I3_CMD) { * Implementation of 'move [window|container] [to] workspace '. * */ -void cmd_move_con_to_workspace_name(I3_CMD, char *name) { +void cmd_move_con_to_workspace_name(I3_CMD, const char *name) { if (strncasecmp(name, "__", strlen("__")) == 0) { LOG("You cannot move containers to i3-internal workspaces (\"%s\").\n", name); ysuccess(false); @@ -544,7 +544,7 @@ void cmd_move_con_to_workspace_name(I3_CMD, char *name) { * Implementation of 'move [window|container] [to] workspace number '. * */ -void cmd_move_con_to_workspace_number(I3_CMD, char *which) { +void cmd_move_con_to_workspace_number(I3_CMD, const char *which) { owindow *current; /* We have nothing to move: @@ -591,7 +591,7 @@ void cmd_move_con_to_workspace_number(I3_CMD, char *which) { ysuccess(true); } -static void cmd_resize_floating(I3_CMD, char *way, char *direction, Con *floating_con, int px) { +static void cmd_resize_floating(I3_CMD, const char *way, const char *direction, Con *floating_con, int px) { LOG("floating resize\n"); Rect old_rect = floating_con->rect; Con *focused_con = con_descend_focused(floating_con); @@ -643,7 +643,7 @@ static void cmd_resize_floating(I3_CMD, char *way, char *direction, Con *floatin floating_con->scratchpad_state = SCRATCHPAD_CHANGED; } -static bool cmd_resize_tiling_direction(I3_CMD, Con *current, char *way, char *direction, int ppt) { +static bool cmd_resize_tiling_direction(I3_CMD, Con *current, const char *way, const char *direction, int ppt) { LOG("tiling resize\n"); Con *second = NULL; Con *first = current; @@ -696,7 +696,7 @@ static bool cmd_resize_tiling_direction(I3_CMD, Con *current, char *way, char *d return true; } -static bool cmd_resize_tiling_width_height(I3_CMD, Con *current, char *way, char *direction, int ppt) { +static bool cmd_resize_tiling_width_height(I3_CMD, Con *current, const char *way, const char *direction, int ppt) { LOG("width/height resize\n"); /* get the appropriate current container (skip stacked/tabbed cons) */ while (current->parent->layout == L_STACKED || @@ -782,7 +782,7 @@ static bool cmd_resize_tiling_width_height(I3_CMD, Con *current, char *way, char * Implementation of 'resize grow|shrink [ px] [or ppt]'. * */ -void cmd_resize(I3_CMD, char *way, char *direction, long resize_px, long resize_ppt) { +void cmd_resize(I3_CMD, const char *way, const char *direction, long resize_px, long resize_ppt) { DLOG("resizing in way %s, direction %s, px %ld or ppt %ld\n", way, direction, resize_px, resize_ppt); if (strcmp(way, "shrink") == 0) { resize_px *= -1; @@ -853,7 +853,7 @@ void cmd_resize_set(I3_CMD, long cwidth, long cheight) { * Implementation of 'border normal|pixel []', 'border none|1pixel|toggle'. * */ -void cmd_border(I3_CMD, char *border_style_str, char *border_width) { +void cmd_border(I3_CMD, const char *border_style_str, const char *border_width) { DLOG("border style should be changed to %s with border width %s\n", border_style_str, border_width); owindow *current; @@ -906,7 +906,7 @@ void cmd_border(I3_CMD, char *border_style_str, char *border_width) { * Implementation of 'nop '. * */ -void cmd_nop(I3_CMD, char *comment) { +void cmd_nop(I3_CMD, const char *comment) { LOG("-------------------------------------------------\n"); LOG(" NOP: %s\n", comment); LOG("-------------------------------------------------\n"); @@ -916,7 +916,8 @@ void cmd_nop(I3_CMD, char *comment) { * Implementation of 'append_layout '. * */ -void cmd_append_layout(I3_CMD, char *path) { +void cmd_append_layout(I3_CMD, const char *cpath) { + char *path = sstrdup(cpath); LOG("Appending layout \"%s\"\n", path); /* Make sure we allow paths like '~/.i3/layout.json' */ @@ -977,7 +978,7 @@ void cmd_append_layout(I3_CMD, char *path) { * Implementation of 'workspace next|prev|next_on_output|prev_on_output'. * */ -void cmd_workspace(I3_CMD, char *which) { +void cmd_workspace(I3_CMD, const char *which) { Con *ws; DLOG("which=%s\n", which); @@ -1013,7 +1014,7 @@ void cmd_workspace(I3_CMD, char *which) { * Implementation of 'workspace number ' * */ -void cmd_workspace_number(I3_CMD, char *which) { +void cmd_workspace_number(I3_CMD, const char *which) { Con *output, *workspace = NULL; if (con_get_fullscreen_con(croot, CF_GLOBAL)) { @@ -1072,7 +1073,7 @@ void cmd_workspace_back_and_forth(I3_CMD) { * Implementation of 'workspace ' * */ -void cmd_workspace_name(I3_CMD, char *name) { +void cmd_workspace_name(I3_CMD, const char *name) { if (strncasecmp(name, "__", strlen("__")) == 0) { LOG("You cannot switch to the i3-internal workspaces (\"%s\").\n", name); ysuccess(false); @@ -1099,7 +1100,7 @@ void cmd_workspace_name(I3_CMD, char *name) { * Implementation of 'mark [--toggle] ' * */ -void cmd_mark(I3_CMD, char *mark, char *toggle) { +void cmd_mark(I3_CMD, const char *mark, const char *toggle) { HANDLE_EMPTY_MATCH; owindow *current = TAILQ_FIRST(&owindows); @@ -1130,7 +1131,7 @@ void cmd_mark(I3_CMD, char *mark, char *toggle) { * Implementation of 'unmark [mark]' * */ -void cmd_unmark(I3_CMD, char *mark) { +void cmd_unmark(I3_CMD, const char *mark) { con_unmark(mark); cmd_output->needs_tree_render = true; @@ -1142,7 +1143,7 @@ void cmd_unmark(I3_CMD, char *mark) { * Implementation of 'mode '. * */ -void cmd_mode(I3_CMD, char *mode) { +void cmd_mode(I3_CMD, const char *mode) { DLOG("mode=%s\n", mode); switch_mode(mode); @@ -1154,7 +1155,7 @@ void cmd_mode(I3_CMD, char *mode) { * Implementation of 'move [window|container] [to] output '. * */ -void cmd_move_con_to_output(I3_CMD, char *name) { +void cmd_move_con_to_output(I3_CMD, const char *name) { DLOG("Should move window to output \"%s\".\n", name); HANDLE_EMPTY_MATCH; @@ -1192,7 +1193,7 @@ void cmd_move_con_to_output(I3_CMD, char *name) { * Implementation of 'move [container|window] [to] mark '. * */ -void cmd_move_con_to_mark(I3_CMD, char *mark) { +void cmd_move_con_to_mark(I3_CMD, const char *mark) { DLOG("moving window to mark \"%s\"\n", mark); HANDLE_EMPTY_MATCH; @@ -1212,7 +1213,7 @@ void cmd_move_con_to_mark(I3_CMD, char *mark) { * Implementation of 'floating enable|disable|toggle' * */ -void cmd_floating(I3_CMD, char *floating_mode) { +void cmd_floating(I3_CMD, const char *floating_mode) { owindow *current; DLOG("floating_mode=%s\n", floating_mode); @@ -1243,7 +1244,7 @@ void cmd_floating(I3_CMD, char *floating_mode) { * Implementation of 'move workspace to [output] '. * */ -void cmd_move_workspace_to_output(I3_CMD, char *name) { +void cmd_move_workspace_to_output(I3_CMD, const char *name) { DLOG("should move workspace to output %s\n", name); HANDLE_EMPTY_MATCH; @@ -1268,7 +1269,7 @@ void cmd_move_workspace_to_output(I3_CMD, char *name) { * Implementation of 'split v|h|vertical|horizontal'. * */ -void cmd_split(I3_CMD, char *direction) { +void cmd_split(I3_CMD, const char *direction) { owindow *current; /* TODO: use matches */ LOG("splitting in direction %c\n", direction[0]); @@ -1290,7 +1291,7 @@ void cmd_split(I3_CMD, char *direction) { * Implementation of 'kill [window|client]'. * */ -void cmd_kill(I3_CMD, char *kill_mode_str) { +void cmd_kill(I3_CMD, const char *kill_mode_str) { if (kill_mode_str == NULL) kill_mode_str = "window"; owindow *current; @@ -1327,7 +1328,7 @@ void cmd_kill(I3_CMD, char *kill_mode_str) { * Implementation of 'exec [--no-startup-id] '. * */ -void cmd_exec(I3_CMD, char *nosn, char *command) { +void cmd_exec(I3_CMD, const char *nosn, const char *command) { bool no_startup_id = (nosn != NULL); DLOG("should execute %s, no_startup_id = %d\n", command, no_startup_id); @@ -1341,7 +1342,7 @@ void cmd_exec(I3_CMD, char *nosn, char *command) { * Implementation of 'focus left|right|up|down'. * */ -void cmd_focus_direction(I3_CMD, char *direction) { +void cmd_focus_direction(I3_CMD, const char *direction) { DLOG("direction = *%s*\n", direction); if (strcmp(direction, "left") == 0) @@ -1367,7 +1368,7 @@ void cmd_focus_direction(I3_CMD, char *direction) { * Implementation of 'focus tiling|floating|mode_toggle'. * */ -void cmd_focus_window_mode(I3_CMD, char *window_mode) { +void cmd_focus_window_mode(I3_CMD, const char *window_mode) { DLOG("window_mode = %s\n", window_mode); Con *ws = con_get_workspace(focused); @@ -1398,7 +1399,7 @@ void cmd_focus_window_mode(I3_CMD, char *window_mode) { * Implementation of 'focus parent|child'. * */ -void cmd_focus_level(I3_CMD, char *level) { +void cmd_focus_level(I3_CMD, const char *level) { DLOG("level = %s\n", level); bool success = false; @@ -1502,7 +1503,7 @@ void cmd_focus(I3_CMD) { * 'fullscreen disable' * */ -void cmd_fullscreen(I3_CMD, char *action, char *fullscreen_mode) { +void cmd_fullscreen(I3_CMD, const char *action, const char *fullscreen_mode) { fullscreen_mode_t mode = strcmp(fullscreen_mode, "global") == 0 ? CF_GLOBAL : CF_OUTPUT; DLOG("%s fullscreen, mode = %s\n", action, fullscreen_mode); owindow *current; @@ -1529,7 +1530,7 @@ void cmd_fullscreen(I3_CMD, char *action, char *fullscreen_mode) { * Implementation of 'sticky enable|disable|toggle'. * */ -void cmd_sticky(I3_CMD, char *action) { +void cmd_sticky(I3_CMD, const char *action) { DLOG("%s sticky on window\n", action); HANDLE_EMPTY_MATCH; @@ -1565,7 +1566,7 @@ void cmd_sticky(I3_CMD, char *action) { * Implementation of 'move [ [px]]'. * */ -void cmd_move_direction(I3_CMD, char *direction, long move_px) { +void cmd_move_direction(I3_CMD, const char *direction, long move_px) { owindow *current; HANDLE_EMPTY_MATCH; @@ -1604,7 +1605,7 @@ void cmd_move_direction(I3_CMD, char *direction, long move_px) { * Implementation of 'layout default|stacked|stacking|tabbed|splitv|splith'. * */ -void cmd_layout(I3_CMD, char *layout_str) { +void cmd_layout(I3_CMD, const char *layout_str) { if (strcmp(layout_str, "stacking") == 0) layout_str = "stacked"; owindow *current; @@ -1646,7 +1647,7 @@ void cmd_layout(I3_CMD, char *layout_str) { * Implementation of 'layout toggle [all|split]'. * */ -void cmd_layout_toggle(I3_CMD, char *toggle_mode) { +void cmd_layout_toggle(I3_CMD, const char *toggle_mode) { owindow *current; if (toggle_mode == NULL) @@ -1743,7 +1744,7 @@ void cmd_open(I3_CMD) { * Implementation of 'focus output '. * */ -void cmd_focus_output(I3_CMD, char *name) { +void cmd_focus_output(I3_CMD, const char *name) { owindow *current; DLOG("name = %s\n", name); @@ -1785,7 +1786,7 @@ void cmd_focus_output(I3_CMD, char *name) { * Implementation of 'move [window|container] [to] [absolute] position [px] [px] * */ -void cmd_move_window_to_position(I3_CMD, char *method, long x, long y) { +void cmd_move_window_to_position(I3_CMD, const char *method, long x, long y) { bool has_error = false; owindow *current; @@ -1832,7 +1833,7 @@ void cmd_move_window_to_position(I3_CMD, char *method, long x, long y) { * Implementation of 'move [window|container] [to] [absolute] position center * */ -void cmd_move_window_to_center(I3_CMD, char *method) { +void cmd_move_window_to_center(I3_CMD, const char *method) { if (!con_is_floating(focused)) { ELOG("Cannot change position. The window/container is not floating\n"); yerror("Cannot change position. The window/container is not floating."); @@ -1928,7 +1929,7 @@ void cmd_scratchpad_show(I3_CMD) { * Implementation of 'title_format ' * */ -void cmd_title_format(I3_CMD, char *format) { +void cmd_title_format(I3_CMD, const char *format) { DLOG("setting title_format to \"%s\"\n", format); HANDLE_EMPTY_MATCH; @@ -1965,7 +1966,7 @@ void cmd_title_format(I3_CMD, char *format) { * Implementation of 'rename workspace [] to ' * */ -void cmd_rename_workspace(I3_CMD, char *old_name, char *new_name) { +void cmd_rename_workspace(I3_CMD, const char *old_name, const char *new_name) { if (strncasecmp(new_name, "__", strlen("__")) == 0) { LOG("Cannot rename workspace to \"%s\": names starting with __ are i3-internal.\n", new_name); ysuccess(false); @@ -2050,7 +2051,7 @@ void cmd_rename_workspace(I3_CMD, char *old_name, char *new_name) { * Implementation of 'bar mode dock|hide|invisible|toggle []' * */ -bool cmd_bar_mode(char *bar_mode, char *bar_id) { +bool cmd_bar_mode(const char *bar_mode, const char *bar_id) { int mode = M_DOCK; bool toggle = false; if (strcmp(bar_mode, "dock") == 0) @@ -2095,7 +2096,7 @@ bool cmd_bar_mode(char *bar_mode, char *bar_id) { * Implementation of 'bar hidden_state hide|show|toggle []' * */ -bool cmd_bar_hidden_state(char *bar_hidden_state, char *bar_id) { +bool cmd_bar_hidden_state(const char *bar_hidden_state, const char *bar_id) { int hidden_state = S_SHOW; bool toggle = false; if (strcmp(bar_hidden_state, "hide") == 0) @@ -2138,7 +2139,7 @@ bool cmd_bar_hidden_state(char *bar_hidden_state, char *bar_id) { * Implementation of 'bar (hidden_state hide|show|toggle)|(mode dock|hide|invisible|toggle) []' * */ -void cmd_bar(I3_CMD, char *bar_type, char *bar_value, char *bar_id) { +void cmd_bar(I3_CMD, const char *bar_type, const char *bar_value, const char *bar_id) { bool ret; if (strcmp(bar_type, "mode") == 0) ret = cmd_bar_mode(bar_value, bar_id); @@ -2160,7 +2161,7 @@ void cmd_bar(I3_CMD, char *bar_type, char *bar_value, char *bar_id) { * Implementation of 'shmlog |toggle|on|off' * */ -void cmd_shmlog(I3_CMD, char *argument) { +void cmd_shmlog(I3_CMD, const char *argument) { if (!strcmp(argument, "toggle")) /* Toggle shm log, if size is not 0. If it is 0, set it to default. */ shmlog_size = shmlog_size ? -shmlog_size : default_shmlog_size; @@ -2191,7 +2192,7 @@ void cmd_shmlog(I3_CMD, char *argument) { * Implementation of 'debuglog toggle|on|off' * */ -void cmd_debuglog(I3_CMD, char *argument) { +void cmd_debuglog(I3_CMD, const char *argument) { bool logging = get_debug_logging(); if (!strcmp(argument, "toggle")) { LOG("%s debug logging\n", logging ? "Disabling" : "Enabling"); diff --git a/src/commands_parser.c b/src/commands_parser.c index d311fdd1..cdd35e45 100644 --- a/src/commands_parser.c +++ b/src/commands_parser.c @@ -133,10 +133,8 @@ static void push_long(const char *identifier, long num) { exit(1); } -// XXX: ideally, this would be const char. need to check if that works with all -// called functions. // TODO move to a common util -static char *get_string(const char *identifier) { +static const char *get_string(const char *identifier) { for (int c = 0; c < 10; c++) { if (stack[c].identifier == NULL) break; diff --git a/src/startup.c b/src/startup.c index 400d3192..b7950c20 100644 --- a/src/startup.c +++ b/src/startup.c @@ -261,7 +261,7 @@ void startup_monitor_event(SnMonitorEvent *event, void *userdata) { * Renames workspaces that are mentioned in the startup sequences. * */ -void startup_sequence_rename_workspace(char *old_name, char *new_name) { +void startup_sequence_rename_workspace(const char *old_name, const char *new_name) { struct Startup_Sequence *current; TAILQ_FOREACH(current, &startup_sequences, sequences) { if (strcmp(current->workspace, old_name) != 0) diff --git a/src/workspace.c b/src/workspace.c index d3a97453..e7a09c70 100644 --- a/src/workspace.c +++ b/src/workspace.c @@ -918,7 +918,7 @@ Con *workspace_encapsulate(Con *ws) { * Move the given workspace to the specified output. * This returns true if and only if moving the workspace was successful. */ -bool workspace_move_to_output(Con *ws, char *name) { +bool workspace_move_to_output(Con *ws, const char *name) { LOG("Trying to move workspace %p / %s to output \"%s\".\n", ws, ws->name, name); Con *current_output_con = con_get_output(ws); From 2453feb4e7a8b7b5725d7ca4a5d960f8569806f0 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Wed, 30 Sep 2015 09:03:34 +0200 Subject: [PATCH 008/187] debian: update changelog --- debian/changelog | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/debian/changelog b/debian/changelog index 65b7fc10..1cb2e681 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,8 +1,14 @@ -i3-wm (4.10.5-1) unstable; urgency=medium +i3-wm (4.11.1-1) unstable; urgency=medium * UNRELEASED - -- Michael Stapelberg Tue, 08 Sep 2015 09:25:45 +0200 + -- Michael Stapelberg Wed, 30 Sep 2015 09:03:26 +0200 + +i3-wm (4.11-1) unstable; urgency=medium + + * New upstream release. + + -- Michael Stapelberg Wed, 30 Sep 2015 08:50:13 +0200 i3-wm (4.10.4-1) unstable; urgency=medium From 2c4fd56069cbfba9f49bda5fb6f6c37355c3fa1f Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Wed, 30 Sep 2015 09:06:34 +0200 Subject: [PATCH 009/187] update release.sh for 4.11 release --- release.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/release.sh b/release.sh index 33b8dcd6..b3e84610 100755 --- a/release.sh +++ b/release.sh @@ -1,9 +1,9 @@ #!/bin/zsh # This script is used to prepare a new release of i3. -export RELEASE_VERSION="4.10.4" -export PREVIOUS_VERSION="4.10.3" -export RELEASE_BRANCH="master" +export RELEASE_VERSION="4.11" +export PREVIOUS_VERSION="4.10.4" +export RELEASE_BRANCH="next" if [ ! -e "../i3.github.io" ] then From fc48a297ed0f5dd974b6177f8f4c643db3da3652 Mon Sep 17 00:00:00 2001 From: hwangcc23 Date: Fri, 25 Sep 2015 22:20:28 +0800 Subject: [PATCH 010/187] Check duplicated bindings after translating keysym 1). See the issue #1926. For example, the second keybinding is not detected as a duplicate: bindcode Mod4+24 sticky toggle bindsym Mod4+q focus parent 2). To fix it, check duplicated bindings when translating the keysym to keycodes. --- include/config_parser.h | 5 ++++ src/bindings.c | 22 +++++++++++++++-- src/config_parser.c | 52 +++++++++++++++++++++++------------------ 3 files changed, 54 insertions(+), 25 deletions(-) diff --git a/include/config_parser.h b/include/config_parser.h index 28c28e48..9c23a11d 100644 --- a/include/config_parser.h +++ b/include/config_parser.h @@ -31,6 +31,11 @@ struct ConfigResultIR { struct ConfigResultIR *parse_config(const char *input, struct context *context); +/** + * launch nagbar to indicate errors in the configuration file. + */ +void start_config_error_nagbar(const char *configpath, bool has_errors); + /** * Parses the given file by first replacing the variables, then calling * parse_config and launching i3-nagbar if use_nagbar is true. diff --git a/src/bindings.c b/src/bindings.c index 32aac05a..086d230a 100644 --- a/src/bindings.c +++ b/src/bindings.c @@ -319,6 +319,7 @@ void translate_keysyms(void) { return; } + bool has_errors = false; Binding *bind; TAILQ_FOREACH(bind, bindings, bindings) { if (bind->input_type == B_MOUSE) { @@ -389,6 +390,21 @@ void translate_keysyms(void) { sasprintf(&tmp, "%s %d", keycodes, bind->translated_to[n]); free(keycodes); keycodes = tmp; + + /* check for duplicate bindings */ + Binding *check; + TAILQ_FOREACH(check, bindings, bindings) { + if (check == bind) + continue; + if (check->symbol != NULL) + continue; + if (check->keycode != bind->translated_to[n] || + check->event_state_mask != bind->event_state_mask || + check->release != bind->release) + continue; + has_errors = true; + ELOG("Duplicate keybinding in config file:\n keysym = %s, keycode = %d, state_mask = 0x%x\n", bind->symbol, check->keycode, bind->event_state_mask); + } } DLOG("state=0x%x, cfg=\"%s\", sym=0x%x → keycodes%s (%d)\n", bind->event_state_mask, bind->symbol, keysym, keycodes, bind->number_keycodes); @@ -396,6 +412,10 @@ void translate_keysyms(void) { } xkb_state_unref(dummy_state); + + if (has_errors) { + start_config_error_nagbar(current_configpath, true); + } } /* @@ -514,8 +534,6 @@ void check_for_duplicate_bindings(struct context *context) { /* Check if one is using keysym while the other is using bindsym. * If so, skip. */ - /* XXX: It should be checked at a later place (when translating the - * keysym to keycodes) if there are any duplicates */ if ((bind->symbol == NULL && current->symbol != NULL) || (bind->symbol != NULL && current->symbol == NULL)) continue; diff --git a/src/config_parser.c b/src/config_parser.c index ea00412d..2b4572b0 100644 --- a/src/config_parser.c +++ b/src/config_parser.c @@ -830,6 +830,34 @@ static char *migrate_config(char *input, off_t size) { return converted; } +/** + * Launch nagbar to indicate errors in the configuration file. + */ +void start_config_error_nagbar(const char *configpath, bool has_errors) { + char *editaction, *pageraction; + sasprintf(&editaction, "i3-sensible-editor \"%s\" && i3-msg reload\n", configpath); + sasprintf(&pageraction, "i3-sensible-pager \"%s\"\n", errorfilename); + char *argv[] = { + NULL, /* will be replaced by the executable path */ + "-f", + (config.font.pattern ? config.font.pattern : "fixed"), + "-t", + (has_errors ? "error" : "warning"), + "-m", + (has_errors ? "You have an error in your i3 config file!" : "Your config is outdated. Please fix the warnings to make sure everything works."), + "-b", + "edit config", + editaction, + (errorfilename ? "-b" : NULL), + (has_errors ? "show errors" : "show warnings"), + pageraction, + NULL}; + + start_nagbar(&config_error_nagbar_pid, argv); + free(editaction); + free(pageraction); +} + /* * Parses the given file by first replacing the variables, then calling * parse_config and possibly launching i3-nagbar. @@ -1005,29 +1033,7 @@ bool parse_file(const char *f, bool use_nagbar) { if (version == 3) ELOG("Please convert your configfile first, then fix any remaining errors (see above).\n"); - char *editaction, - *pageraction; - sasprintf(&editaction, "i3-sensible-editor \"%s\" && i3-msg reload\n", f); - sasprintf(&pageraction, "i3-sensible-pager \"%s\"\n", errorfilename); - char *argv[] = { - NULL, /* will be replaced by the executable path */ - "-f", - (config.font.pattern ? config.font.pattern : "fixed"), - "-t", - (context->has_errors ? "error" : "warning"), - "-m", - (context->has_errors ? "You have an error in your i3 config file!" : "Your config is outdated. Please fix the warnings to make sure everything works."), - "-b", - "edit config", - editaction, - (errorfilename ? "-b" : NULL), - (context->has_errors ? "show errors" : "show warnings"), - pageraction, - NULL}; - - start_nagbar(&config_error_nagbar_pid, argv); - free(editaction); - free(pageraction); + start_config_error_nagbar(f, context->has_errors); } bool has_errors = context->has_errors; From 7a66d55df5e40c45838fd6b3a82fd9ce4f399742 Mon Sep 17 00:00:00 2001 From: Jakob Haufe Date: Thu, 1 Oct 2015 21:34:16 +0200 Subject: [PATCH 011/187] Fix formatting of description list --- docs/userguide | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/userguide b/docs/userguide index 13dae4fe..b077eb66 100644 --- a/docs/userguide +++ b/docs/userguide @@ -2134,10 +2134,10 @@ and the following placeholders which will be replaced: +%title+:: The X11 window title (_NET_WM_NAME or WM_NAME as fallback). -+%class+: ++%class+:: The X11 window class (second part of WM_CLASS). This corresponds to the +class+ criterion, see <>. -+%instance+: ++%instance+:: The X11 window instance (first part of WM_CLASS). This corresponds to the +instance+ criterion, see <>. From b744c5e6c6605059d8485f60e62dc587de17f14e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ingo=20B=C3=BCrk?= Date: Sun, 27 Sep 2015 09:42:26 +0200 Subject: [PATCH 012/187] Refactor parsing of matches to avoid code duplication. --- include/match.h | 6 +++ src/commands.c | 102 +------------------------------------ src/config_directives.c | 104 +------------------------------------- src/match.c | 109 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 117 insertions(+), 204 deletions(-) diff --git a/include/match.h b/include/match.h index dbd9bb79..64a4f22b 100644 --- a/include/match.h +++ b/include/match.h @@ -45,3 +45,9 @@ bool match_matches_window(Match *match, i3Window *window); * */ void match_free(Match *match); + +/** + * Interprets a ctype=cvalue pair and adds it to the given match specification. + * + */ +void match_parse_property(Match *match, const char *ctype, const char *cvalue); diff --git a/src/commands.c b/src/commands.c index 95784c77..c9746ba4 100644 --- a/src/commands.c +++ b/src/commands.c @@ -316,107 +316,7 @@ void cmd_criteria_match_windows(I3_CMD) { * */ void cmd_criteria_add(I3_CMD, const char *ctype, const char *cvalue) { - DLOG("ctype=*%s*, cvalue=*%s*\n", ctype, cvalue); - - if (strcmp(ctype, "class") == 0) { - current_match->class = regex_new(cvalue); - return; - } - - if (strcmp(ctype, "instance") == 0) { - current_match->instance = regex_new(cvalue); - return; - } - - if (strcmp(ctype, "window_role") == 0) { - current_match->window_role = regex_new(cvalue); - return; - } - - if (strcmp(ctype, "con_id") == 0) { - char *end; - long parsed = strtol(cvalue, &end, 0); - if (parsed == LONG_MIN || - parsed == LONG_MAX || - parsed < 0 || - (end && *end != '\0')) { - ELOG("Could not parse con id \"%s\"\n", cvalue); - } else { - current_match->con_id = (Con *)parsed; - DLOG("id as int = %p\n", current_match->con_id); - } - return; - } - - if (strcmp(ctype, "id") == 0) { - char *end; - long parsed = strtol(cvalue, &end, 0); - if (parsed == LONG_MIN || - parsed == LONG_MAX || - parsed < 0 || - (end && *end != '\0')) { - ELOG("Could not parse window id \"%s\"\n", cvalue); - } else { - current_match->id = parsed; - DLOG("window id as int = %d\n", current_match->id); - } - return; - } - - if (strcmp(ctype, "window_type") == 0) { - if (strcasecmp(cvalue, "normal") == 0) - current_match->window_type = A__NET_WM_WINDOW_TYPE_NORMAL; - else if (strcasecmp(cvalue, "dialog") == 0) - current_match->window_type = A__NET_WM_WINDOW_TYPE_DIALOG; - else if (strcasecmp(cvalue, "utility") == 0) - current_match->window_type = A__NET_WM_WINDOW_TYPE_UTILITY; - else if (strcasecmp(cvalue, "toolbar") == 0) - current_match->window_type = A__NET_WM_WINDOW_TYPE_TOOLBAR; - else if (strcasecmp(cvalue, "splash") == 0) - current_match->window_type = A__NET_WM_WINDOW_TYPE_SPLASH; - else if (strcasecmp(cvalue, "menu") == 0) - current_match->window_type = A__NET_WM_WINDOW_TYPE_MENU; - else if (strcasecmp(cvalue, "dropdown_menu") == 0) - current_match->window_type = A__NET_WM_WINDOW_TYPE_DROPDOWN_MENU; - else if (strcasecmp(cvalue, "popup_menu") == 0) - current_match->window_type = A__NET_WM_WINDOW_TYPE_POPUP_MENU; - else if (strcasecmp(cvalue, "tooltip") == 0) - current_match->window_type = A__NET_WM_WINDOW_TYPE_TOOLTIP; - else - ELOG("unknown window_type value \"%s\"\n", cvalue); - - return; - } - - if (strcmp(ctype, "con_mark") == 0) { - current_match->mark = regex_new(cvalue); - return; - } - - if (strcmp(ctype, "title") == 0) { - current_match->title = regex_new(cvalue); - return; - } - - if (strcmp(ctype, "urgent") == 0) { - if (strcasecmp(cvalue, "latest") == 0 || - strcasecmp(cvalue, "newest") == 0 || - strcasecmp(cvalue, "recent") == 0 || - strcasecmp(cvalue, "last") == 0) { - current_match->urgent = U_LATEST; - } else if (strcasecmp(cvalue, "oldest") == 0 || - strcasecmp(cvalue, "first") == 0) { - current_match->urgent = U_OLDEST; - } - return; - } - - if (strcmp(ctype, "workspace") == 0) { - current_match->workspace = regex_new(cvalue); - return; - } - - ELOG("Unknown criterion: %s\n", ctype); + match_parse_property(current_match, ctype, cvalue); } /* diff --git a/src/config_directives.c b/src/config_directives.c index d772387d..cd0432fe 100644 --- a/src/config_directives.c +++ b/src/config_directives.c @@ -42,111 +42,9 @@ CFGFUN(criteria_pop_state) { * */ CFGFUN(criteria_add, const char *ctype, const char *cvalue) { - DLOG("ctype=*%s*, cvalue=*%s*\n", ctype, cvalue); - - if (strcmp(ctype, "class") == 0) { - current_match->class = regex_new(cvalue); - return; - } - - if (strcmp(ctype, "instance") == 0) { - current_match->instance = regex_new(cvalue); - return; - } - - if (strcmp(ctype, "window_role") == 0) { - current_match->window_role = regex_new(cvalue); - return; - } - - if (strcmp(ctype, "con_id") == 0) { - char *end; - long parsed = strtol(cvalue, &end, 10); - if (parsed == LONG_MIN || - parsed == LONG_MAX || - parsed < 0 || - (end && *end != '\0')) { - ELOG("Could not parse con id \"%s\"\n", cvalue); - } else { - current_match->con_id = (Con *)parsed; - DLOG("id as int = %p\n", current_match->con_id); - } - return; - } - - if (strcmp(ctype, "id") == 0) { - char *end; - long parsed = strtol(cvalue, &end, 10); - if (parsed == LONG_MIN || - parsed == LONG_MAX || - parsed < 0 || - (end && *end != '\0')) { - ELOG("Could not parse window id \"%s\"\n", cvalue); - } else { - current_match->id = parsed; - DLOG("window id as int = %d\n", current_match->id); - } - return; - } - - if (strcmp(ctype, "window_type") == 0) { - if (strcasecmp(cvalue, "normal") == 0) - current_match->window_type = A__NET_WM_WINDOW_TYPE_NORMAL; - else if (strcasecmp(cvalue, "dialog") == 0) - current_match->window_type = A__NET_WM_WINDOW_TYPE_DIALOG; - else if (strcasecmp(cvalue, "utility") == 0) - current_match->window_type = A__NET_WM_WINDOW_TYPE_UTILITY; - else if (strcasecmp(cvalue, "toolbar") == 0) - current_match->window_type = A__NET_WM_WINDOW_TYPE_TOOLBAR; - else if (strcasecmp(cvalue, "splash") == 0) - current_match->window_type = A__NET_WM_WINDOW_TYPE_SPLASH; - else if (strcasecmp(cvalue, "menu") == 0) - current_match->window_type = A__NET_WM_WINDOW_TYPE_MENU; - else if (strcasecmp(cvalue, "dropdown_menu") == 0) - current_match->window_type = A__NET_WM_WINDOW_TYPE_DROPDOWN_MENU; - else if (strcasecmp(cvalue, "popup_menu") == 0) - current_match->window_type = A__NET_WM_WINDOW_TYPE_POPUP_MENU; - else if (strcasecmp(cvalue, "tooltip") == 0) - current_match->window_type = A__NET_WM_WINDOW_TYPE_TOOLTIP; - else - ELOG("unknown window_type value \"%s\"\n", cvalue); - - return; - } - - if (strcmp(ctype, "con_mark") == 0) { - current_match->mark = regex_new(cvalue); - return; - } - - if (strcmp(ctype, "title") == 0) { - current_match->title = regex_new(cvalue); - return; - } - - if (strcmp(ctype, "urgent") == 0) { - if (strcasecmp(cvalue, "latest") == 0 || - strcasecmp(cvalue, "newest") == 0 || - strcasecmp(cvalue, "recent") == 0 || - strcasecmp(cvalue, "last") == 0) { - current_match->urgent = U_LATEST; - } else if (strcasecmp(cvalue, "oldest") == 0 || - strcasecmp(cvalue, "first") == 0) { - current_match->urgent = U_OLDEST; - } - return; - } - - if (strcmp(ctype, "workspace") == 0) { - current_match->workspace = regex_new(cvalue); - return; - } - - ELOG("Unknown criterion: %s\n", ctype); + match_parse_property(current_match, ctype, cvalue); } -/* TODO: refactor the above criteria code into a single file (with src/commands.c). */ - /******************************************************************************* * Utility functions ******************************************************************************/ diff --git a/src/match.c b/src/match.c index 20af38f6..67054dae 100644 --- a/src/match.c +++ b/src/match.c @@ -254,3 +254,112 @@ void match_free(Match *match) { FREE(match->mark); FREE(match->window_role); } + +/* + * Interprets a ctype=cvalue pair and adds it to the given match specification. + * + */ +void match_parse_property(Match *match, const char *ctype, const char *cvalue) { + assert(match != NULL); + DLOG("ctype=*%s*, cvalue=*%s*\n", ctype, cvalue); + + if (strcmp(ctype, "class") == 0) { + match->class = regex_new(cvalue); + return; + } + + if (strcmp(ctype, "instance") == 0) { + match->instance = regex_new(cvalue); + return; + } + + if (strcmp(ctype, "window_role") == 0) { + match->window_role = regex_new(cvalue); + return; + } + + if (strcmp(ctype, "con_id") == 0) { + char *end; + long parsed = strtol(cvalue, &end, 10); + if (parsed == LONG_MIN || + parsed == LONG_MAX || + parsed < 0 || + (end && *end != '\0')) { + ELOG("Could not parse con id \"%s\"\n", cvalue); + } else { + match->con_id = (Con *)parsed; + DLOG("id as int = %p\n", match->con_id); + } + return; + } + + if (strcmp(ctype, "id") == 0) { + char *end; + long parsed = strtol(cvalue, &end, 10); + if (parsed == LONG_MIN || + parsed == LONG_MAX || + parsed < 0 || + (end && *end != '\0')) { + ELOG("Could not parse window id \"%s\"\n", cvalue); + } else { + match->id = parsed; + DLOG("window id as int = %d\n", match->id); + } + return; + } + + if (strcmp(ctype, "window_type") == 0) { + if (strcasecmp(cvalue, "normal") == 0) + match->window_type = A__NET_WM_WINDOW_TYPE_NORMAL; + else if (strcasecmp(cvalue, "dialog") == 0) + match->window_type = A__NET_WM_WINDOW_TYPE_DIALOG; + else if (strcasecmp(cvalue, "utility") == 0) + match->window_type = A__NET_WM_WINDOW_TYPE_UTILITY; + else if (strcasecmp(cvalue, "toolbar") == 0) + match->window_type = A__NET_WM_WINDOW_TYPE_TOOLBAR; + else if (strcasecmp(cvalue, "splash") == 0) + match->window_type = A__NET_WM_WINDOW_TYPE_SPLASH; + else if (strcasecmp(cvalue, "menu") == 0) + match->window_type = A__NET_WM_WINDOW_TYPE_MENU; + else if (strcasecmp(cvalue, "dropdown_menu") == 0) + match->window_type = A__NET_WM_WINDOW_TYPE_DROPDOWN_MENU; + else if (strcasecmp(cvalue, "popup_menu") == 0) + match->window_type = A__NET_WM_WINDOW_TYPE_POPUP_MENU; + else if (strcasecmp(cvalue, "tooltip") == 0) + match->window_type = A__NET_WM_WINDOW_TYPE_TOOLTIP; + else + ELOG("unknown window_type value \"%s\"\n", cvalue); + + return; + } + + if (strcmp(ctype, "con_mark") == 0) { + match->mark = regex_new(cvalue); + return; + } + + if (strcmp(ctype, "title") == 0) { + match->title = regex_new(cvalue); + return; + } + + if (strcmp(ctype, "urgent") == 0) { + if (strcasecmp(cvalue, "latest") == 0 || + strcasecmp(cvalue, "newest") == 0 || + strcasecmp(cvalue, "recent") == 0 || + strcasecmp(cvalue, "last") == 0) { + match->urgent = U_LATEST; + } else if (strcasecmp(cvalue, "oldest") == 0 || + strcasecmp(cvalue, "first") == 0) { + match->urgent = U_OLDEST; + } + return; + } + + if (strcmp(ctype, "workspace") == 0) { + match->workspace = regex_new(cvalue); + return; + } + + ELOG("Unknown criterion: %s\n", ctype); +} From 410c5da7cf0e4f4b635f22d770d24de64626981a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ingo=20B=C3=BCrk?= Date: Sat, 5 Sep 2015 23:38:15 +0200 Subject: [PATCH 013/187] Use cairo for all drawing operations in i3bar. fixes #1878 --- i3bar/include/cairo_util.h | 65 +++++++ i3bar/include/common.h | 1 + i3bar/include/outputs.h | 9 +- i3bar/include/xcb.h | 6 + i3bar/src/cairo_util.c | 71 +++++++ i3bar/src/outputs.c | 3 +- i3bar/src/xcb.c | 374 ++++++++++++++----------------------- libi3/font.c | 2 +- 8 files changed, 288 insertions(+), 243 deletions(-) create mode 100644 i3bar/include/cairo_util.h create mode 100644 i3bar/src/cairo_util.c diff --git a/i3bar/include/cairo_util.h b/i3bar/include/cairo_util.h new file mode 100644 index 00000000..749beec8 --- /dev/null +++ b/i3bar/include/cairo_util.h @@ -0,0 +1,65 @@ +/* + * vim:ts=4:sw=4:expandtab + * + * © 2015 Ingo Bürk and contributors (see also: LICENSE) + * + * cairo_util.h: Utility for operations using cairo. + * + */ +#pragma once + +#include + +/* Represents a color split by color channel. */ +typedef struct color_t { + double red; + double green; + double blue; + + /* For compatibility, we also store the colorpixel for now. */ + uint32_t colorpixel; +} color_t; + +/* A wrapper grouping an XCB drawable and both a graphics context + * and the corresponding cairo objects representing it. */ +typedef struct surface_t { + /* The drawable which is being represented. */ + xcb_drawable_t id; + + // TODO remove this once i3 uses solely cairo for drawing operations + /* A classic XCB graphics context. This should not be used for + * drawing operations. */ + xcb_gcontext_t gc; + + /* A cairo surface representing the drawable. */ + cairo_surface_t *surface; + + /* The cairo object representing the drawale. In general, + * this is what one should use for any drawing operation. */ + cairo_t *cr; +} surface_t; + +/** + * Initialize the cairo surface to represent the given drawable. + * + */ +void cairo_surface_init(surface_t *surface, xcb_drawable_t drawable, int width, int height); + +/** + * Destroys the surface. + * + */ +void cairo_surface_free(surface_t *surface); + +/** + * Parses the given color in hex format to an internal color representation. + * Note that the input must begin with a hash sign, e.g., "#3fbc59". + * + */ +color_t cairo_hex_to_color(const char *color); + +/** + * Set the given color as the source color on the surface. + * + */ +void cairo_set_source_color(surface_t *surface, color_t color); diff --git a/i3bar/include/common.h b/i3bar/include/common.h index 90da9388..50d1c7b9 100644 --- a/i3bar/include/common.h +++ b/i3bar/include/common.h @@ -80,3 +80,4 @@ TAILQ_HEAD(statusline_head, status_block) statusline_head; #include "config.h" #include "libi3.h" #include "parse_json_header.h" +#include "cairo_util.h" diff --git a/i3bar/include/outputs.h b/i3bar/include/outputs.h index e6605e1f..7ad3ed50 100644 --- a/i3bar/include/outputs.h +++ b/i3bar/include/outputs.h @@ -10,8 +10,10 @@ #pragma once #include +#include #include "common.h" +#include "cairo_util.h" typedef struct i3_output i3_output; @@ -44,9 +46,10 @@ struct i3_output { int ws; /* The number of the currently visible ws */ rect rect; /* The rect (relative to the root window) */ - xcb_window_t bar; /* The id of the bar of the output */ - xcb_pixmap_t buffer; /* An extra pixmap for double-buffering */ - xcb_gcontext_t bargc; /* The graphical context of the bar */ + /* Off-screen buffer for preliminary rendering. */ + surface_t buffer; + /* The actual window on which we draw. */ + surface_t bar; struct ws_head* workspaces; /* The workspaces on this output */ struct tc_head* trayclients; /* The tray clients on this output */ diff --git a/i3bar/include/xcb.h b/i3bar/include/xcb.h index 835105e7..3746204c 100644 --- a/i3bar/include/xcb.h +++ b/i3bar/include/xcb.h @@ -24,6 +24,12 @@ #define XEMBED_MAPPED (1 << 0) #define XEMBED_EMBEDDED_NOTIFY 0 +xcb_connection_t *xcb_connection; + +/* 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); + struct xcb_color_strings_t { char *bar_fg; char *bar_bg; diff --git a/i3bar/src/cairo_util.c b/i3bar/src/cairo_util.c new file mode 100644 index 00000000..aff763c3 --- /dev/null +++ b/i3bar/src/cairo_util.c @@ -0,0 +1,71 @@ +/* + * vim:ts=4:sw=4:expandtab + * + * © 2015 Ingo Bürk and contributors (see also: LICENSE) + * + * cairo_util.c: Utility for operations using cairo. + * + */ +#include +#include +#include +#include +#include + +#include "common.h" +#include "libi3.h" + +xcb_connection_t *xcb_connection; +xcb_screen_t *root_screen; + +/* + * Initialize the cairo surface to represent the given drawable. + * + */ +void cairo_surface_init(surface_t *surface, xcb_drawable_t drawable, int width, int height) { + surface->id = drawable; + + surface->gc = xcb_generate_id(xcb_connection); + xcb_void_cookie_t gc_cookie = xcb_create_gc_checked(xcb_connection, surface->gc, surface->id, 0, NULL); + if (xcb_request_failed(gc_cookie, "Could not create graphical context")) + exit(EXIT_FAILURE); + + surface->surface = cairo_xcb_surface_create(xcb_connection, surface->id, get_visualtype(root_screen), width, height); + surface->cr = cairo_create(surface->surface); +} + +/* + * Destroys the surface. + * + */ +void cairo_surface_free(surface_t *surface) { + xcb_free_gc(xcb_connection, surface->gc); + cairo_surface_destroy(surface->surface); + cairo_destroy(surface->cr); +} + +/* + * Parses the given color in hex format to an internal color representation. + * Note that the input must begin with a hash sign, e.g., "#3fbc59". + * + */ +color_t cairo_hex_to_color(const char *color) { + char groups[3][3] = { + {color[1], color[2], '\0'}, + {color[3], color[4], '\0'}, + {color[5], color[6], '\0'}}; + + return (color_t){ + .red = strtol(groups[0], NULL, 16) / 255.0, + .green = strtol(groups[1], NULL, 16) / 255.0, + .blue = strtol(groups[2], NULL, 16) / 255.0, + .colorpixel = get_colorpixel(color)}; +} + +/* + * Set the given color as the source color on the surface. + * + */ +void cairo_set_source_color(surface_t *surface, color_t color) { + cairo_set_source_rgb(surface->cr, color.red, color.green, color.blue); +} diff --git a/i3bar/src/outputs.c b/i3bar/src/outputs.c index b49ff53f..fd2b1363 100644 --- a/i3bar/src/outputs.c +++ b/i3bar/src/outputs.c @@ -151,7 +151,8 @@ static int outputs_start_map_cb(void *params_) { new_output->name = NULL; new_output->ws = 0, memset(&new_output->rect, 0, sizeof(rect)); - new_output->bar = XCB_NONE; + memset(&new_output->bar, 0, sizeof(surface_t)); + memset(&new_output->buffer, 0, sizeof(surface_t)); new_output->workspaces = smalloc(sizeof(struct ws_head)); TAILQ_INIT(new_output->workspaces); diff --git a/i3bar/src/xcb.c b/i3bar/src/xcb.c index f90bbcee..f5d065f0 100644 --- a/i3bar/src/xcb.c +++ b/i3bar/src/xcb.c @@ -30,6 +30,7 @@ #include #include #include +#include #include "common.h" #include "libi3.h" @@ -72,9 +73,7 @@ int mod_pressed = 0; /* Because the statusline is the same on all outputs, we have * global buffer to render it on */ -xcb_gcontext_t statusline_ctx; -xcb_gcontext_t statusline_clear; -xcb_pixmap_t statusline_pm; +surface_t statusline_surface; uint32_t statusline_width; /* Event watchers, to interact with the user */ @@ -91,24 +90,24 @@ bool activated_mode = false; /* The parsed colors */ struct xcb_colors_t { - uint32_t bar_fg; - uint32_t bar_bg; - uint32_t sep_fg; - uint32_t active_ws_fg; - uint32_t active_ws_bg; - uint32_t active_ws_border; - uint32_t inactive_ws_fg; - uint32_t inactive_ws_bg; - uint32_t inactive_ws_border; - uint32_t urgent_ws_bg; - uint32_t urgent_ws_fg; - uint32_t urgent_ws_border; - uint32_t focus_ws_bg; - uint32_t focus_ws_fg; - uint32_t focus_ws_border; - uint32_t binding_mode_bg; - uint32_t binding_mode_fg; - uint32_t binding_mode_border; + color_t bar_fg; + color_t bar_bg; + color_t sep_fg; + color_t active_ws_fg; + color_t active_ws_bg; + color_t active_ws_border; + color_t inactive_ws_fg; + color_t inactive_ws_bg; + color_t inactive_ws_border; + color_t urgent_ws_bg; + color_t urgent_ws_fg; + color_t urgent_ws_border; + color_t focus_ws_bg; + color_t focus_ws_fg; + color_t focus_ws_border; + color_t binding_mode_bg; + color_t binding_mode_fg; + color_t binding_mode_border; }; struct xcb_colors_t colors; @@ -131,8 +130,6 @@ static const int tray_loff_px = 2; /* Vertical offset between the bar and a separator */ static const int sep_voff_px = 4; -/* 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) { xcb_generic_error_t *err; if ((err = xcb_request_check(xcb_connection, cookie)) != NULL) { @@ -173,17 +170,14 @@ static void draw_separator(uint32_t x, struct status_block *block) { uint32_t center_x = x - sep_offset; if (config.separator_symbol == NULL) { /* Draw a classic one pixel, vertical separator. */ - 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[]){{center_x, logical_px(sep_voff_px)}, - {center_x, bar_height - logical_px(sep_voff_px)}}); + cairo_set_source_color(&statusline_surface, colors.sep_fg); + cairo_rectangle(statusline_surface.cr, center_x, logical_px(sep_voff_px), logical_px(1), bar_height - 2 * logical_px(sep_voff_px)); + cairo_fill(statusline_surface.cr); } else { /* Draw a custom separator. */ uint32_t separator_x = MAX(x - block->sep_block_width, center_x - separator_symbol_width / 2); - set_font_colors(statusline_ctx, colors.sep_fg, colors.bar_bg); - draw_text(config.separator_symbol, statusline_pm, statusline_ctx, + set_font_colors(statusline_surface.gc, colors.sep_fg.colorpixel, colors.bar_bg.colorpixel); + draw_text(config.separator_symbol, statusline_surface.id, statusline_surface.gc, separator_x, logical_px(ws_voff_px), x - separator_x); } } @@ -245,37 +239,31 @@ void refresh_statusline(bool use_short_text) { realloc_sl_buffer(); /* Clear the statusline pixmap. */ - 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); + cairo_set_source_color(&statusline_surface, colors.bar_bg); + cairo_rectangle(statusline_surface.cr, 0, 0, MAX(root_screen->width_in_pixels, statusline_width), bar_height); + cairo_fill(statusline_surface.cr); /* Draw the text of each block. */ uint32_t x = 0; TAILQ_FOREACH(block, &statusline_head, blocks) { if (i3string_get_num_bytes(block->full_text) == 0) continue; - uint32_t fg_color; + color_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; - /* 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); + cairo_set_source_color(&statusline_surface, colors.urgent_ws_bg); + cairo_rectangle(statusline_surface.cr, x - logical_px(2), logical_px(1), block->width + logical_px(4), bar_height - logical_px(2)); + cairo_fill(statusline_surface.cr); } else { - fg_color = (block->color ? get_colorpixel(block->color) : colors.bar_fg); + fg_color = (block->color ? cairo_hex_to_color(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); + set_font_colors(statusline_surface.gc, fg_color.colorpixel, colors.bar_bg.colorpixel); + draw_text(block->full_text, statusline_surface.id, statusline_surface.gc, 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 this is not the last block, draw a separator. */ @@ -297,7 +285,7 @@ void hide_bars(void) { if (!walk->active) { continue; } - xcb_unmap_window(xcb_connection, walk->bar); + xcb_unmap_window(xcb_connection, walk->bar.id); } stop_child(); } @@ -319,7 +307,7 @@ void unhide_bars(void) { cont_child(); SLIST_FOREACH(walk, outputs, slist) { - if (walk->bar == XCB_NONE) { + if (walk->bar.id == XCB_NONE) { continue; } mask = XCB_CONFIG_WINDOW_X | @@ -337,14 +325,14 @@ void unhide_bars(void) { values[4] = XCB_STACK_MODE_ABOVE; 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, + walk->bar.id, mask, values); if (xcb_request_failed(cookie, "Could not reconfigure window")) { exit(EXIT_FAILURE); } - xcb_map_window(xcb_connection, walk->bar); + xcb_map_window(xcb_connection, walk->bar.id); } } @@ -353,9 +341,9 @@ void unhide_bars(void) { * */ void init_colors(const struct xcb_color_strings_t *new_colors) { -#define PARSE_COLOR(name, def) \ - do { \ - colors.name = get_colorpixel(new_colors->name ? new_colors->name : def); \ +#define PARSE_COLOR(name, def) \ + do { \ + colors.name = cairo_hex_to_color(new_colors->name ? new_colors->name : def); \ } while (0) PARSE_COLOR(bar_fg, "#FFFFFF"); PARSE_COLOR(bar_bg, "#000000"); @@ -374,9 +362,9 @@ void init_colors(const struct xcb_color_strings_t *new_colors) { PARSE_COLOR(focus_ws_border, "#4c7899"); #undef PARSE_COLOR -#define PARSE_COLOR_FALLBACK(name, fallback) \ - do { \ - colors.name = new_colors->name ? get_colorpixel(new_colors->name) : colors.fallback; \ +#define PARSE_COLOR_FALLBACK(name, fallback) \ + do { \ + colors.name = new_colors->name ? cairo_hex_to_color(new_colors->name) : colors.fallback; \ } while (0) /* For the binding mode indicator colors, we don't hardcode a default. @@ -401,7 +389,7 @@ void handle_button(xcb_button_press_event_t *event) { i3_output *walk; xcb_window_t bar = event->event; SLIST_FOREACH(walk, outputs, slist) { - if (walk->bar == bar) { + if (walk->bar.id == bar) { break; } } @@ -564,7 +552,7 @@ static void handle_visibility_notify(xcb_visibility_notify_event_t *event) { if (!output->active) { continue; } - if (output->bar == event->window) { + if (output->bar.id == event->window) { if (output->visible == visible) { return; } @@ -713,7 +701,7 @@ static void handle_client_message(xcb_client_message_event_t *event) { } xcb_reparent_window(xcb_connection, client, - output->bar, + output->bar.id, output->rect.w - icon_size - logical_px(config.tray_padding), logical_px(config.tray_padding)); /* We reconfigure the window to use a reasonable size. The systray @@ -738,7 +726,7 @@ static void handle_client_message(xcb_client_message_event_t *event) { ev->format = 32; ev->data.data32[0] = XCB_CURRENT_TIME; ev->data.data32[1] = atoms[XEMBED_EMBEDDED_NOTIFY]; - ev->data.data32[2] = output->bar; + ev->data.data32[2] = output->bar.id; ev->data.data32[3] = xe_version; xcb_send_event(xcb_connection, 0, @@ -1120,30 +1108,14 @@ char *init_xcb_early() { /* We draw the statusline to a seperate pixmap, because it looks the same on all bars and * this way, we can choose to crop it */ - uint32_t mask = XCB_GC_FOREGROUND; - uint32_t vals[] = {colors.bar_bg, colors.bar_bg}; - - statusline_clear = xcb_generate_id(xcb_connection); - xcb_void_cookie_t clear_ctx_cookie = xcb_create_gc_checked(xcb_connection, - statusline_clear, - xcb_root, - mask, - vals); - - statusline_ctx = xcb_generate_id(xcb_connection); - xcb_void_cookie_t sl_ctx_cookie = xcb_create_gc_checked(xcb_connection, - statusline_ctx, - xcb_root, - 0, - NULL); - - statusline_pm = xcb_generate_id(xcb_connection); + xcb_pixmap_t statusline_id = xcb_generate_id(xcb_connection); xcb_void_cookie_t sl_pm_cookie = xcb_create_pixmap_checked(xcb_connection, root_screen->root_depth, - statusline_pm, + statusline_id, xcb_root, root_screen->width_in_pixels, root_screen->height_in_pixels); + cairo_surface_init(&statusline_surface, statusline_id, root_screen->width_in_pixels, root_screen->height_in_pixels); /* The various watchers to communicate with xcb */ xcb_io = smalloc(sizeof(ev_io)); @@ -1163,11 +1135,8 @@ char *init_xcb_early() { char *path = root_atom_contents("I3_SOCKET_PATH", xcb_connection, screen); - if (xcb_request_failed(sl_pm_cookie, "Could not allocate statusline buffer") || - xcb_request_failed(clear_ctx_cookie, "Could not allocate statusline buffer clearcontext") || - xcb_request_failed(sl_ctx_cookie, "Could not allocate statusline buffer context")) { + if (xcb_request_failed(sl_pm_cookie, "Could not allocate statusline buffer")) exit(EXIT_FAILURE); - } return path; } @@ -1483,13 +1452,13 @@ void destroy_window(i3_output *output) { if (output == NULL) { return; } - if (output->bar == XCB_NONE) { + if (output->bar.id == XCB_NONE) { return; } kick_tray_clients(output); - xcb_destroy_window(xcb_connection, output->bar); - output->bar = XCB_NONE; + xcb_destroy_window(xcb_connection, output->bar.id); + output->bar.id = XCB_NONE; } /* @@ -1499,40 +1468,20 @@ void destroy_window(i3_output *output) { void realloc_sl_buffer(void) { DLOG("Re-allocating statusline buffer, statusline_width = %d, root_screen->width_in_pixels = %d\n", statusline_width, root_screen->width_in_pixels); - xcb_free_pixmap(xcb_connection, statusline_pm); - statusline_pm = xcb_generate_id(xcb_connection); + xcb_free_pixmap(xcb_connection, statusline_surface.id); + cairo_surface_free(&statusline_surface); + + xcb_pixmap_t statusline_id = xcb_generate_id(xcb_connection); xcb_void_cookie_t sl_pm_cookie = xcb_create_pixmap_checked(xcb_connection, root_screen->root_depth, - statusline_pm, + statusline_id, xcb_root, MAX(root_screen->width_in_pixels, statusline_width), bar_height); + cairo_surface_init(&statusline_surface, statusline_id, root_screen->width_in_pixels, root_screen->height_in_pixels); - uint32_t mask = XCB_GC_FOREGROUND; - uint32_t vals[2] = {colors.bar_bg, colors.bar_bg}; - xcb_free_gc(xcb_connection, statusline_clear); - statusline_clear = xcb_generate_id(xcb_connection); - xcb_void_cookie_t clear_ctx_cookie = xcb_create_gc_checked(xcb_connection, - statusline_clear, - xcb_root, - mask, - vals); - - mask |= XCB_GC_BACKGROUND; - vals[0] = colors.bar_fg; - xcb_free_gc(xcb_connection, statusline_ctx); - statusline_ctx = xcb_generate_id(xcb_connection); - xcb_void_cookie_t sl_ctx_cookie = xcb_create_gc_checked(xcb_connection, - statusline_ctx, - xcb_root, - mask, - vals); - - if (xcb_request_failed(sl_pm_cookie, "Could not allocate statusline buffer") || - xcb_request_failed(clear_ctx_cookie, "Could not allocate statusline buffer clearcontext") || - xcb_request_failed(sl_ctx_cookie, "Could not allocate statusline buffer context")) { + if (xcb_request_failed(sl_pm_cookie, "Could not allocate statusline buffer")) exit(EXIT_FAILURE); - } } /* Strut partial tells i3 where to reserve space for i3bar. This is determined @@ -1571,7 +1520,7 @@ xcb_void_cookie_t config_strut_partial(i3_output *output) { } return xcb_change_property(xcb_connection, XCB_PROP_MODE_REPLACE, - output->bar, + output->bar.id, atoms[_NET_WM_STRUT_PARTIAL], XCB_ATOM_CARDINAL, 32, @@ -1597,14 +1546,14 @@ void reconfig_windows(bool redraw_bars) { destroy_window(walk); continue; } - if (walk->bar == XCB_NONE) { + if (walk->bar.id == XCB_NONE) { DLOG("Creating window for output %s\n", walk->name); - walk->bar = xcb_generate_id(xcb_connection); - walk->buffer = xcb_generate_id(xcb_connection); + xcb_window_t bar_id = xcb_generate_id(xcb_connection); + xcb_pixmap_t buffer_id = xcb_generate_id(xcb_connection); mask = XCB_CW_BACK_PIXEL | XCB_CW_OVERRIDE_REDIRECT | XCB_CW_EVENT_MASK; /* Black background */ - values[0] = colors.bar_bg; + values[0] = colors.bar_bg.colorpixel; /* If hide_on_modifier is set to hide or invisible mode, i3 is not supposed to manage our bar windows */ values[1] = (config.hide_on_modifier == M_DOCK ? 0 : 1); /* We enable the following EventMask fields: @@ -1624,7 +1573,7 @@ void reconfig_windows(bool redraw_bars) { } xcb_void_cookie_t win_cookie = xcb_create_window_checked(xcb_connection, root_screen->root_depth, - walk->bar, + bar_id, xcb_root, walk->rect.x, walk->rect.y + walk->rect.h - bar_height, walk->rect.w, bar_height, @@ -1637,8 +1586,8 @@ void reconfig_windows(bool redraw_bars) { /* The double-buffer we use to render stuff off-screen */ xcb_void_cookie_t pm_cookie = xcb_create_pixmap_checked(xcb_connection, root_screen->root_depth, - walk->buffer, - walk->bar, + buffer_id, + bar_id, walk->rect.w, bar_height); @@ -1646,7 +1595,7 @@ void reconfig_windows(bool redraw_bars) { xcb_void_cookie_t class_cookie; class_cookie = xcb_change_property(xcb_connection, XCB_PROP_MODE_REPLACE, - walk->bar, + bar_id, XCB_ATOM_WM_CLASS, XCB_ATOM_STRING, 8, @@ -1658,7 +1607,7 @@ void reconfig_windows(bool redraw_bars) { xcb_void_cookie_t name_cookie; name_cookie = xcb_change_property(xcb_connection, XCB_PROP_MODE_REPLACE, - walk->bar, + bar_id, XCB_ATOM_WM_NAME, XCB_ATOM_STRING, 8, @@ -1670,28 +1619,22 @@ void reconfig_windows(bool redraw_bars) { * this one */ xcb_void_cookie_t dock_cookie = xcb_change_property(xcb_connection, XCB_PROP_MODE_REPLACE, - walk->bar, + bar_id, atoms[_NET_WM_WINDOW_TYPE], XCB_ATOM_ATOM, 32, 1, (unsigned char *)&atoms[_NET_WM_WINDOW_TYPE_DOCK]); - xcb_void_cookie_t strut_cookie = config_strut_partial(walk); + cairo_surface_init(&walk->bar, bar_id, walk->rect.w, bar_height); + cairo_surface_init(&walk->buffer, buffer_id, walk->rect.w, bar_height); - /* We also want a graphics context for the bars (it defines the properties - * with which we draw to them) */ - walk->bargc = xcb_generate_id(xcb_connection); - xcb_void_cookie_t gc_cookie = xcb_create_gc_checked(xcb_connection, - walk->bargc, - walk->bar, - 0, - NULL); + xcb_void_cookie_t strut_cookie = config_strut_partial(walk); /* We finally map the bar (display it on screen), unless the modifier-switch is on */ xcb_void_cookie_t map_cookie; if (config.hide_on_modifier == M_DOCK) { - map_cookie = xcb_map_window_checked(xcb_connection, walk->bar); + map_cookie = xcb_map_window_checked(xcb_connection, bar_id); } if (xcb_request_failed(win_cookie, "Could not create window") || @@ -1700,7 +1643,6 @@ void reconfig_windows(bool redraw_bars) { xcb_request_failed(class_cookie, "Could not set WM_CLASS") || xcb_request_failed(name_cookie, "Could not set WM_NAME") || xcb_request_failed(strut_cookie, "Could not set strut") || - xcb_request_failed(gc_cookie, "Could not create graphical context") || ((config.hide_on_modifier == M_DOCK) && xcb_request_failed(map_cookie, "Could not map window"))) { exit(EXIT_FAILURE); } @@ -1741,11 +1683,11 @@ void reconfig_windows(bool redraw_bars) { xcb_void_cookie_t strut_cookie = config_strut_partial(walk); DLOG("Destroying buffer for output %s\n", walk->name); - xcb_free_pixmap(xcb_connection, walk->buffer); + xcb_free_pixmap(xcb_connection, walk->buffer.id); 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, + walk->bar.id, mask, values); @@ -1753,25 +1695,30 @@ void reconfig_windows(bool redraw_bars) { 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]); xcb_void_cookie_t chg_cookie = xcb_change_window_attributes(xcb_connection, - walk->bar, + walk->bar.id, mask, values); DLOG("Recreating buffer for output %s\n", walk->name); xcb_void_cookie_t pm_cookie = xcb_create_pixmap_checked(xcb_connection, root_screen->root_depth, - walk->buffer, - walk->bar, + walk->buffer.id, + walk->bar.id, walk->rect.w, bar_height); + cairo_surface_free(&(walk->bar)); + cairo_surface_free(&(walk->buffer)); + cairo_surface_init(&(walk->bar), walk->bar.id, walk->rect.w, bar_height); + cairo_surface_init(&(walk->buffer), walk->buffer.id, walk->rect.w, bar_height); + xcb_void_cookie_t map_cookie, umap_cookie; if (redraw_bars) { /* Unmap the window, and draw it again when in dock mode */ - umap_cookie = xcb_unmap_window_checked(xcb_connection, walk->bar); + umap_cookie = xcb_unmap_window_checked(xcb_connection, walk->bar.id); if (config.hide_on_modifier == M_DOCK) { cont_child(); - map_cookie = xcb_map_window_checked(xcb_connection, walk->bar); + map_cookie = xcb_map_window_checked(xcb_connection, walk->bar.id); } else { stop_child(); } @@ -1815,31 +1762,23 @@ void draw_bars(bool unhide) { DLOG("Output %s inactive, skipping...\n", outputs_walk->name); continue; } - if (outputs_walk->bar == XCB_NONE) { + if (outputs_walk->bar.id == XCB_NONE) { /* Oh shit, an active output without an own bar. Create it now! */ reconfig_windows(false); } /* First things first: clear the backbuffer */ - uint32_t color = colors.bar_bg; - xcb_change_gc(xcb_connection, - outputs_walk->bargc, - XCB_GC_FOREGROUND, - &color); - xcb_rectangle_t rect = {0, 0, outputs_walk->rect.w, bar_height}; - xcb_poly_fill_rectangle(xcb_connection, - outputs_walk->buffer, - outputs_walk->bargc, - 1, - &rect); + cairo_set_source_color(&(outputs_walk->buffer), colors.bar_bg); + cairo_rectangle(outputs_walk->buffer.cr, 0, 0, outputs_walk->rect.w, bar_height); + cairo_fill(outputs_walk->buffer.cr); 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), 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; + color_t fg_color = colors.inactive_ws_fg; + color_t bg_color = colors.inactive_ws_bg; + color_t border_color = colors.inactive_ws_border; if (ws_walk->visible) { if (!ws_walk->focused) { fg_color = colors.active_ws_fg; @@ -1858,37 +1797,21 @@ void draw_bars(bool unhide) { border_color = colors.urgent_ws_border; unhide = true; } - uint32_t mask = XCB_GC_FOREGROUND | XCB_GC_BACKGROUND; - uint32_t vals_border[] = {border_color, border_color}; - xcb_change_gc(xcb_connection, - outputs_walk->bargc, - mask, - vals_border); - xcb_rectangle_t rect_border = {workspace_width, - logical_px(1), - 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, - 1, - &rect_border); - uint32_t vals[] = {bg_color, bg_color}; - xcb_change_gc(xcb_connection, - outputs_walk->bargc, - mask, - vals); - xcb_rectangle_t rect = {workspace_width + logical_px(1), - 2 * logical_px(1), - 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, - 1, - &rect); - set_font_colors(outputs_walk->bargc, fg_color, bg_color); - draw_text(ws_walk->name, outputs_walk->buffer, outputs_walk->bargc, + + cairo_set_source_color(&(outputs_walk->buffer), border_color); + cairo_rectangle(outputs_walk->buffer.cr, workspace_width, logical_px(1), + 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)); + cairo_fill(outputs_walk->buffer.cr); + + cairo_set_source_color(&(outputs_walk->buffer), bg_color); + cairo_rectangle(outputs_walk->buffer.cr, workspace_width + logical_px(1), 2 * logical_px(1), + ws_walk->name_width + 2 * logical_px(ws_hoff_px), + font.height + 2 * logical_px(ws_voff_px) - 4 * logical_px(1)); + cairo_fill(outputs_walk->buffer.cr); + + set_font_colors(outputs_walk->buffer.gc, fg_color.colorpixel, bg_color.colorpixel); + draw_text(ws_walk->name, outputs_walk->buffer.id, outputs_walk->buffer.gc, workspace_width + logical_px(ws_hoff_px) + logical_px(1), logical_px(ws_voff_px), ws_walk->name_width); @@ -1902,44 +1825,25 @@ void draw_bars(bool unhide) { if (binding.name && !config.disable_binding_mode_indicator) { workspace_width += logical_px(ws_spacing_px); - uint32_t fg_color = colors.binding_mode_fg; - uint32_t bg_color = colors.binding_mode_bg; - uint32_t mask = XCB_GC_FOREGROUND | XCB_GC_BACKGROUND; + color_t fg_color = colors.binding_mode_fg; + color_t bg_color = colors.binding_mode_bg; - uint32_t vals_border[] = {colors.binding_mode_border, colors.binding_mode_border}; - xcb_change_gc(xcb_connection, - outputs_walk->bargc, - mask, - vals_border); - 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, - 1, - &rect_border); + cairo_set_source_color(&(outputs_walk->buffer), colors.binding_mode_border); + cairo_rectangle(outputs_walk->buffer.cr, 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)); + cairo_fill(outputs_walk->buffer.cr); - uint32_t vals[] = {bg_color, bg_color}; - xcb_change_gc(xcb_connection, - outputs_walk->bargc, - mask, - vals); - 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, - 1, - &rect); + cairo_set_source_color(&(outputs_walk->buffer), bg_color); + cairo_rectangle(outputs_walk->buffer.cr, 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)); + cairo_fill(outputs_walk->buffer.cr); - set_font_colors(outputs_walk->bargc, fg_color, bg_color); + set_font_colors(outputs_walk->buffer.gc, fg_color.colorpixel, bg_color.colorpixel); draw_text(binding.name, - outputs_walk->buffer, - outputs_walk->bargc, + outputs_walk->buffer.id, + outputs_walk->buffer.gc, workspace_width + logical_px(ws_hoff_px) + logical_px(1), logical_px(ws_voff_px), binding.width); @@ -1969,13 +1873,12 @@ void draw_bars(bool unhide) { * statusline, we just have to copy the relevant parts to the relevant * position */ int visible_statusline_width = MIN(statusline_width, max_statusline_width); - 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); + int x_src = (int16_t)(statusline_width - visible_statusline_width); + int x_dest = (int16_t)(outputs_walk->rect.w - tray_width - logical_px(sb_hoff_px) - visible_statusline_width); + + cairo_set_source_surface(outputs_walk->buffer.cr, statusline_surface.surface, x_dest - x_src, 0); + cairo_rectangle(outputs_walk->buffer.cr, x_dest, 0, (int16_t)visible_statusline_width, (int16_t)bar_height); + cairo_fill(outputs_walk->buffer.cr); } workspace_width = 0; @@ -2003,14 +1906,9 @@ void redraw_bars(void) { if (!outputs_walk->active) { continue; } - xcb_copy_area(xcb_connection, - outputs_walk->buffer, - outputs_walk->bar, - outputs_walk->bargc, - 0, 0, - 0, 0, - outputs_walk->rect.w, - outputs_walk->rect.h); + cairo_set_source_surface(outputs_walk->bar.cr, outputs_walk->buffer.surface, 0, 0); + cairo_rectangle(outputs_walk->bar.cr, 0, 0, outputs_walk->rect.w, outputs_walk->rect.h); + cairo_fill(outputs_walk->bar.cr); xcb_flush(xcb_connection); } } diff --git a/libi3/font.c b/libi3/font.c index b502b52c..b8c31b73 100644 --- a/libi3/font.c +++ b/libi3/font.c @@ -12,8 +12,8 @@ #include #include -#if PANGO_SUPPORT #include +#if PANGO_SUPPORT #include #endif From 1c4100ce5d8f9a7edc46f80f8a20ca50c6d97f8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ingo=20B=C3=BCrk?= Date: Mon, 5 Oct 2015 12:58:05 +0200 Subject: [PATCH 014/187] Use 32-bit visuals for i3bar when possible and allow RGBA colors. This patch creates all necessary windows for i3bar with 32-bit visuals if available. It also introduces the possibility to define RGBA colors (next to RGB colors), which allows the user to set the opacity of any color. This requires running a compositor. With this patch we also start supporting _NET_SYSTEM_TRAY_VISUAL, which is necessary for the tray icons so they create the tray window with the correct depth and visual. --- i3-input/main.c | 4 +- i3-nagbar/main.c | 4 +- i3bar/include/cairo_util.h | 1 + i3bar/src/cairo_util.c | 21 +++++-- i3bar/src/xcb.c | 121 ++++++++++++++++++++++++++++--------- include/libi3.h | 4 +- libi3/font.c | 17 +++--- src/restore_layout.c | 4 +- src/sighandler.c | 2 +- src/x.c | 4 +- 10 files changed, 132 insertions(+), 50 deletions(-) diff --git a/i3-input/main.c b/i3-input/main.c index cf3884e9..4e1be78b 100644 --- a/i3-input/main.c +++ b/i3-input/main.c @@ -141,12 +141,12 @@ static int handle_expose(void *data, xcb_connection_t *conn, xcb_expose_event_t /* draw the prompt … */ if (prompt != NULL) { - draw_text(prompt, pixmap, pixmap_gc, logical_px(4), logical_px(4), logical_px(492)); + draw_text(prompt, pixmap, pixmap_gc, NULL, logical_px(4), logical_px(4), logical_px(492)); } /* … and the text */ if (input_position > 0) { i3String *input = i3string_from_ucs2(glyphs_ucs, input_position); - draw_text(input, pixmap, pixmap_gc, prompt_offset + logical_px(4), logical_px(4), logical_px(492)); + draw_text(input, pixmap, pixmap_gc, NULL, prompt_offset + logical_px(4), logical_px(4), logical_px(492)); i3string_free(input); } diff --git a/i3-nagbar/main.c b/i3-nagbar/main.c index d86cd69a..d6ace221 100644 --- a/i3-nagbar/main.c +++ b/i3-nagbar/main.c @@ -196,7 +196,7 @@ static int handle_expose(xcb_connection_t *conn, xcb_expose_event_t *event) { /* restore font color */ set_font_colors(pixmap_gc, color_text, color_background); - draw_text(prompt, pixmap, pixmap_gc, + draw_text(prompt, pixmap, pixmap_gc, NULL, logical_px(4) + logical_px(4), logical_px(4) + logical_px(4), rect.width - logical_px(4) - logical_px(4)); @@ -264,7 +264,7 @@ static int handle_expose(xcb_connection_t *conn, xcb_expose_event_t *event) { values[1] = color_button_background; set_font_colors(pixmap_gc, color_text, color_button_background); /* the x term seems to set left/right padding */ - draw_text(buttons[c].label, pixmap, pixmap_gc, + draw_text(buttons[c].label, pixmap, pixmap_gc, NULL, y - w - line_width + logical_px(6), logical_px(4) + logical_px(3), rect.width - y + w + line_width - logical_px(6)); diff --git a/i3bar/include/cairo_util.h b/i3bar/include/cairo_util.h index 749beec8..390e6a1c 100644 --- a/i3bar/include/cairo_util.h +++ b/i3bar/include/cairo_util.h @@ -15,6 +15,7 @@ typedef struct color_t { double red; double green; double blue; + double alpha; /* For compatibility, we also store the colorpixel for now. */ uint32_t colorpixel; diff --git a/i3bar/src/cairo_util.c b/i3bar/src/cairo_util.c index aff763c3..288969c9 100644 --- a/i3bar/src/cairo_util.c +++ b/i3bar/src/cairo_util.c @@ -8,6 +8,7 @@ */ #include #include +#include #include #include #include @@ -16,7 +17,7 @@ #include "libi3.h" xcb_connection_t *xcb_connection; -xcb_screen_t *root_screen; +xcb_visualtype_t *visual_type; /* * Initialize the cairo surface to represent the given drawable. @@ -30,7 +31,7 @@ void cairo_surface_init(surface_t *surface, xcb_drawable_t drawable, int width, if (xcb_request_failed(gc_cookie, "Could not create graphical context")) exit(EXIT_FAILURE); - surface->surface = cairo_xcb_surface_create(xcb_connection, surface->id, get_visualtype(root_screen), width, height); + surface->surface = cairo_xcb_surface_create(xcb_connection, surface->id, visual_type, width, height); surface->cr = cairo_create(surface->surface); } @@ -50,15 +51,25 @@ void cairo_surface_free(surface_t *surface) { * */ color_t cairo_hex_to_color(const char *color) { - char groups[3][3] = { + char alpha[2]; + if (strlen(color) == strlen("#rrggbbaa")) { + alpha[0] = color[7]; + alpha[1] = color[8]; + } else { + alpha[0] = alpha[1] = 'F'; + } + + char groups[4][3] = { {color[1], color[2], '\0'}, {color[3], color[4], '\0'}, - {color[5], color[6], '\0'}}; + {color[5], color[6], '\0'}, + {alpha[0], alpha[1], '\0'}}; return (color_t){ .red = strtol(groups[0], NULL, 16) / 255.0, .green = strtol(groups[1], NULL, 16) / 255.0, .blue = strtol(groups[2], NULL, 16) / 255.0, + .alpha = strtol(groups[3], NULL, 16) / 255.0, .colorpixel = get_colorpixel(color)}; } @@ -67,5 +78,5 @@ color_t cairo_hex_to_color(const char *color) { * */ void cairo_set_source_color(surface_t *surface, color_t color) { - cairo_set_source_rgb(surface->cr, color.red, color.green, color.blue); + cairo_set_source_rgba(surface->cr, color.red, color.green, color.blue, color.alpha); } diff --git a/i3bar/src/xcb.c b/i3bar/src/xcb.c index f5d065f0..443e30ea 100644 --- a/i3bar/src/xcb.c +++ b/i3bar/src/xcb.c @@ -64,6 +64,10 @@ static i3Font font; /* Icon size (based on font size) */ int icon_size; +xcb_visualtype_t *visual_type; +uint8_t depth; +xcb_colormap_t colormap; + /* Overall height of the bar (based on font size) */ int bar_height; @@ -170,14 +174,17 @@ static void draw_separator(uint32_t x, struct status_block *block) { uint32_t center_x = x - sep_offset; if (config.separator_symbol == NULL) { /* Draw a classic one pixel, vertical separator. */ + cairo_save(statusline_surface.cr); + cairo_set_operator(statusline_surface.cr, CAIRO_OPERATOR_SOURCE); cairo_set_source_color(&statusline_surface, colors.sep_fg); cairo_rectangle(statusline_surface.cr, center_x, logical_px(sep_voff_px), logical_px(1), bar_height - 2 * logical_px(sep_voff_px)); cairo_fill(statusline_surface.cr); + cairo_restore(statusline_surface.cr); } else { /* Draw a custom separator. */ uint32_t separator_x = MAX(x - block->sep_block_width, center_x - separator_symbol_width / 2); set_font_colors(statusline_surface.gc, colors.sep_fg.colorpixel, colors.bar_bg.colorpixel); - draw_text(config.separator_symbol, statusline_surface.id, statusline_surface.gc, + draw_text(config.separator_symbol, statusline_surface.id, statusline_surface.gc, visual_type, separator_x, logical_px(ws_voff_px), x - separator_x); } } @@ -239,9 +246,11 @@ void refresh_statusline(bool use_short_text) { realloc_sl_buffer(); /* Clear the statusline pixmap. */ + cairo_save(statusline_surface.cr); cairo_set_source_color(&statusline_surface, colors.bar_bg); - cairo_rectangle(statusline_surface.cr, 0, 0, MAX(root_screen->width_in_pixels, statusline_width), bar_height); - cairo_fill(statusline_surface.cr); + cairo_set_operator(statusline_surface.cr, CAIRO_OPERATOR_SOURCE); + cairo_paint(statusline_surface.cr); + cairo_restore(statusline_surface.cr); /* Draw the text of each block. */ uint32_t x = 0; @@ -263,7 +272,8 @@ void refresh_statusline(bool use_short_text) { } set_font_colors(statusline_surface.gc, fg_color.colorpixel, colors.bar_bg.colorpixel); - draw_text(block->full_text, statusline_surface.id, statusline_surface.gc, x + block->x_offset, logical_px(ws_voff_px), block->width); + draw_text(block->full_text, statusline_surface.id, statusline_surface.gc, visual_type, + 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 this is not the last block, draw a separator. */ @@ -699,11 +709,15 @@ static void handle_client_message(xcb_client_message_event_t *event) { ELOG("No output found\n"); return; } - xcb_reparent_window(xcb_connection, - client, - output->bar.id, - output->rect.w - icon_size - logical_px(config.tray_padding), - logical_px(config.tray_padding)); + + xcb_void_cookie_t rcookie = xcb_reparent_window(xcb_connection, + client, + output->bar.id, + output->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; + /* We reconfigure the window to use a reasonable size. The systray * specification explicitly says: * Tray icons may be assigned any size by the system tray, and @@ -1106,11 +1120,29 @@ char *init_xcb_early() { root_screen = xcb_aux_get_screen(xcb_connection, screen); xcb_root = root_screen->root; + depth = root_screen->root_depth; + colormap = root_screen->default_colormap; + visual_type = xcb_aux_find_visual_by_attrs(root_screen, -1, 32); + if (visual_type) { + depth = xcb_aux_get_depth_of_visual(root_screen, visual_type->visual_id); + colormap = xcb_generate_id(xcb_connection); + xcb_void_cookie_t cm_cookie = xcb_create_colormap_checked(xcb_connection, + XCB_COLORMAP_ALLOC_NONE, + colormap, + xcb_root, + visual_type->visual_id); + if (xcb_request_failed(cm_cookie, "Could not allocate colormap")) { + exit(EXIT_FAILURE); + } + } else { + visual_type = get_visualtype(root_screen); + } + /* We draw the statusline to a seperate pixmap, because it looks the same on all bars and * this way, we can choose to crop it */ xcb_pixmap_t statusline_id = xcb_generate_id(xcb_connection); xcb_void_cookie_t sl_pm_cookie = xcb_create_pixmap_checked(xcb_connection, - root_screen->root_depth, + depth, statusline_id, xcb_root, root_screen->width_in_pixels, @@ -1248,17 +1280,17 @@ void init_tray(void) { /* tray support: we need a window to own the selection */ selwin = xcb_generate_id(xcb_connection); - uint32_t selmask = XCB_CW_OVERRIDE_REDIRECT; - uint32_t selval[] = {1}; + uint32_t selmask = XCB_CW_BACK_PIXEL | XCB_CW_BORDER_PIXEL | XCB_CW_OVERRIDE_REDIRECT | XCB_CW_COLORMAP; + uint32_t selval[] = {root_screen->black_pixel, root_screen->black_pixel, 1, colormap}; xcb_create_window(xcb_connection, - root_screen->root_depth, + depth, selwin, xcb_root, -1, -1, 1, 1, 0, XCB_WINDOW_CLASS_INPUT_OUTPUT, - root_screen->root_visual, + visual_type->visual_id, selmask, selval); @@ -1272,6 +1304,14 @@ void init_tray(void) { 32, 1, &orientation); + xcb_change_property(xcb_connection, + XCB_PROP_MODE_REPLACE, + selwin, + atoms[_NET_SYSTEM_TRAY_VISUAL], + XCB_ATOM_VISUALID, + 32, + 1, + &visual_type->visual_id); init_tray_colors(); @@ -1473,7 +1513,7 @@ void realloc_sl_buffer(void) { xcb_pixmap_t statusline_id = xcb_generate_id(xcb_connection); xcb_void_cookie_t sl_pm_cookie = xcb_create_pixmap_checked(xcb_connection, - root_screen->root_depth, + depth, statusline_id, xcb_root, MAX(root_screen->width_in_pixels, statusline_width), @@ -1551,41 +1591,44 @@ 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); - mask = XCB_CW_BACK_PIXEL | XCB_CW_OVERRIDE_REDIRECT | XCB_CW_EVENT_MASK; - /* Black background */ + mask = XCB_CW_BACK_PIXEL | XCB_CW_BORDER_PIXEL | XCB_CW_OVERRIDE_REDIRECT | XCB_CW_EVENT_MASK | XCB_CW_COLORMAP; + values[0] = colors.bar_bg.colorpixel; + values[1] = root_screen->black_pixel; /* If hide_on_modifier is set to hide or invisible mode, i3 is not supposed to manage our bar windows */ - values[1] = (config.hide_on_modifier == M_DOCK ? 0 : 1); + values[2] = (config.hide_on_modifier == M_DOCK ? 0 : 1); /* We enable the following EventMask fields: * EXPOSURE, to get expose events (we have to re-draw then) * SUBSTRUCTURE_REDIRECT, to get ConfigureRequests when the tray * child windows use ConfigureWindow * BUTTON_PRESS, to handle clicks on the workspace buttons * */ - values[2] = XCB_EVENT_MASK_EXPOSURE | + values[3] = XCB_EVENT_MASK_EXPOSURE | 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; + values[3] |= XCB_EVENT_MASK_VISIBILITY_CHANGE; walk->visible = true; } + values[4] = colormap; + xcb_void_cookie_t win_cookie = xcb_create_window_checked(xcb_connection, - root_screen->root_depth, + depth, bar_id, xcb_root, walk->rect.x, walk->rect.y + walk->rect.h - bar_height, walk->rect.w, bar_height, 0, XCB_WINDOW_CLASS_INPUT_OUTPUT, - root_screen->root_visual, + visual_type->visual_id, mask, values); /* The double-buffer we use to render stuff off-screen */ xcb_void_cookie_t pm_cookie = xcb_create_pixmap_checked(xcb_connection, - root_screen->root_depth, + depth, buffer_id, bar_id, walk->rect.w, @@ -1701,7 +1744,7 @@ void reconfig_windows(bool redraw_bars) { DLOG("Recreating buffer for output %s\n", walk->name); xcb_void_cookie_t pm_cookie = xcb_create_pixmap_checked(xcb_connection, - root_screen->root_depth, + depth, walk->buffer.id, walk->bar.id, walk->rect.w, @@ -1766,10 +1809,13 @@ void draw_bars(bool unhide) { /* Oh shit, an active output without an own bar. Create it now! */ reconfig_windows(false); } + /* First things first: clear the backbuffer */ + cairo_save(outputs_walk->buffer.cr); cairo_set_source_color(&(outputs_walk->buffer), colors.bar_bg); - cairo_rectangle(outputs_walk->buffer.cr, 0, 0, outputs_walk->rect.w, bar_height); - cairo_fill(outputs_walk->buffer.cr); + cairo_set_operator(outputs_walk->buffer.cr, CAIRO_OPERATOR_SOURCE); + cairo_paint(outputs_walk->buffer.cr); + cairo_restore(outputs_walk->buffer.cr); if (!config.disable_ws) { i3_ws *ws_walk; @@ -1798,20 +1844,27 @@ void draw_bars(bool unhide) { unhide = true; } + cairo_save(outputs_walk->buffer.cr); + cairo_set_operator(outputs_walk->buffer.cr, CAIRO_OPERATOR_SOURCE); + + /* Draw the border of the button. */ cairo_set_source_color(&(outputs_walk->buffer), border_color); cairo_rectangle(outputs_walk->buffer.cr, workspace_width, logical_px(1), 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)); cairo_fill(outputs_walk->buffer.cr); + /* Draw the inside of the button. */ cairo_set_source_color(&(outputs_walk->buffer), bg_color); cairo_rectangle(outputs_walk->buffer.cr, workspace_width + logical_px(1), 2 * logical_px(1), ws_walk->name_width + 2 * logical_px(ws_hoff_px), font.height + 2 * logical_px(ws_voff_px) - 4 * logical_px(1)); cairo_fill(outputs_walk->buffer.cr); + cairo_restore(outputs_walk->buffer.cr); + set_font_colors(outputs_walk->buffer.gc, fg_color.colorpixel, bg_color.colorpixel); - draw_text(ws_walk->name, outputs_walk->buffer.id, outputs_walk->buffer.gc, + draw_text(ws_walk->name, outputs_walk->buffer.id, outputs_walk->buffer.gc, visual_type, workspace_width + logical_px(ws_hoff_px) + logical_px(1), logical_px(ws_voff_px), ws_walk->name_width); @@ -1828,6 +1881,9 @@ void draw_bars(bool unhide) { color_t fg_color = colors.binding_mode_fg; color_t bg_color = colors.binding_mode_bg; + cairo_save(outputs_walk->buffer.cr); + cairo_set_operator(outputs_walk->buffer.cr, CAIRO_OPERATOR_SOURCE); + cairo_set_source_color(&(outputs_walk->buffer), colors.binding_mode_border); cairo_rectangle(outputs_walk->buffer.cr, workspace_width, logical_px(1), binding.width + 2 * logical_px(ws_hoff_px) + 2 * logical_px(1), @@ -1840,10 +1896,13 @@ void draw_bars(bool unhide) { font.height + 2 * logical_px(ws_voff_px) - 4 * logical_px(1)); cairo_fill(outputs_walk->buffer.cr); + cairo_restore(outputs_walk->buffer.cr); + set_font_colors(outputs_walk->buffer.gc, fg_color.colorpixel, bg_color.colorpixel); draw_text(binding.name, outputs_walk->buffer.id, outputs_walk->buffer.gc, + visual_type, workspace_width + logical_px(ws_hoff_px) + logical_px(1), logical_px(ws_voff_px), binding.width); @@ -1876,9 +1935,12 @@ void draw_bars(bool unhide) { int x_src = (int16_t)(statusline_width - visible_statusline_width); int x_dest = (int16_t)(outputs_walk->rect.w - tray_width - logical_px(sb_hoff_px) - visible_statusline_width); + cairo_save(outputs_walk->buffer.cr); + cairo_set_operator(outputs_walk->buffer.cr, CAIRO_OPERATOR_SOURCE); cairo_set_source_surface(outputs_walk->buffer.cr, statusline_surface.surface, x_dest - x_src, 0); cairo_rectangle(outputs_walk->buffer.cr, x_dest, 0, (int16_t)visible_statusline_width, (int16_t)bar_height); cairo_fill(outputs_walk->buffer.cr); + cairo_restore(outputs_walk->buffer.cr); } workspace_width = 0; @@ -1906,9 +1968,14 @@ void redraw_bars(void) { if (!outputs_walk->active) { continue; } + + cairo_save(outputs_walk->bar.cr); + cairo_set_operator(outputs_walk->bar.cr, CAIRO_OPERATOR_SOURCE); cairo_set_source_surface(outputs_walk->bar.cr, outputs_walk->buffer.surface, 0, 0); cairo_rectangle(outputs_walk->bar.cr, 0, 0, outputs_walk->rect.w, outputs_walk->rect.h); cairo_fill(outputs_walk->bar.cr); + cairo_restore(outputs_walk->bar.cr); + xcb_flush(xcb_connection); } } diff --git a/include/libi3.h b/include/libi3.h index 9e7ef133..c1e109ef 100644 --- a/include/libi3.h +++ b/include/libi3.h @@ -402,8 +402,8 @@ bool font_is_pango(void); * Text must be specified as an i3String. * */ -void draw_text(i3String *text, xcb_drawable_t drawable, - xcb_gcontext_t gc, int x, int y, int max_width); +void draw_text(i3String *text, xcb_drawable_t drawable, xcb_gcontext_t gc, + xcb_visualtype_t *visual, int x, int y, int max_width); /** * ASCII version of draw_text to print static strings. diff --git a/libi3/font.c b/libi3/font.c index b8c31b73..8bdf3d60 100644 --- a/libi3/font.c +++ b/libi3/font.c @@ -102,12 +102,12 @@ static bool load_pango_font(i3Font *font, const char *desc) { * */ static void draw_text_pango(const char *text, size_t text_len, - xcb_drawable_t drawable, int x, int y, + xcb_drawable_t drawable, xcb_visualtype_t *visual, int x, int y, int max_width, bool is_markup) { /* Create the Pango layout */ /* root_visual_type is cached in load_pango_font */ cairo_surface_t *surface = cairo_xcb_surface_create(conn, drawable, - root_visual_type, x + max_width, y + savedFont->height); + visual, x + max_width, y + savedFont->height); cairo_t *cr = cairo_create(surface); PangoLayout *layout = create_layout_with_dpi(cr); gint height; @@ -391,9 +391,12 @@ static void draw_text_xcb(const xcb_char2b_t *text, size_t text_len, xcb_drawabl * Text must be specified as an i3String. * */ -void draw_text(i3String *text, xcb_drawable_t drawable, - xcb_gcontext_t gc, int x, int y, int max_width) { +void draw_text(i3String *text, xcb_drawable_t drawable, xcb_gcontext_t gc, + xcb_visualtype_t *visual, int x, int y, int max_width) { assert(savedFont != NULL); + if (visual == NULL) { + visual = root_visual_type; + } switch (savedFont->type) { case FONT_TYPE_NONE: @@ -407,7 +410,7 @@ void draw_text(i3String *text, xcb_drawable_t drawable, case FONT_TYPE_PANGO: /* Render the text using Pango */ draw_text_pango(i3string_as_utf8(text), i3string_get_num_bytes(text), - drawable, x, y, max_width, i3string_is_markup(text)); + drawable, visual, x, y, max_width, i3string_is_markup(text)); return; #endif default: @@ -432,7 +435,7 @@ void draw_text_ascii(const char *text, xcb_drawable_t drawable, if (text_len > 255) { /* The text is too long to draw it directly to X */ i3String *str = i3string_from_utf8(text); - draw_text(str, drawable, gc, x, y, max_width); + draw_text(str, drawable, gc, NULL, x, y, max_width); i3string_free(str); } else { /* X11 coordinates for fonts start at the baseline */ @@ -446,7 +449,7 @@ void draw_text_ascii(const char *text, xcb_drawable_t drawable, case FONT_TYPE_PANGO: /* Render the text using Pango */ draw_text_pango(text, strlen(text), - drawable, x, y, max_width, false); + drawable, root_visual_type, x, y, max_width, false); return; #endif default: diff --git a/src/restore_layout.c b/src/restore_layout.c index 439d23cc..70eed523 100644 --- a/src/restore_layout.c +++ b/src/restore_layout.c @@ -161,7 +161,7 @@ static void update_placeholder_contents(placeholder_state *state) { DLOG("con %p (placeholder 0x%08x) line %d: %s\n", state->con, state->window, n, serialized); i3String *str = i3string_from_utf8(serialized); - draw_text(str, state->pixmap, state->gc, 2, (n * (config.font.height + 2)) + 2, state->rect.width - 2); + draw_text(str, state->pixmap, state->gc, NULL, 2, (n * (config.font.height + 2)) + 2, state->rect.width - 2); i3string_free(str); n++; free(serialized); @@ -172,7 +172,7 @@ static void update_placeholder_contents(placeholder_state *state) { int text_width = predict_text_width(line); int x = (state->rect.width / 2) - (text_width / 2); int y = (state->rect.height / 2) - (config.font.height / 2); - draw_text(line, state->pixmap, state->gc, x, y, text_width); + draw_text(line, state->pixmap, state->gc, NULL, x, y, text_width); i3string_free(line); xcb_flush(conn); xcb_aux_sync(conn); diff --git a/src/sighandler.c b/src/sighandler.c index ceaa4842..555f5e55 100644 --- a/src/sighandler.c +++ b/src/sighandler.c @@ -154,7 +154,7 @@ static int sig_draw_window(xcb_window_t win, int width, int height, int font_hei if (i == backtrace_string_index) set_font_colors(pixmap_gc, get_colorpixel(bt_colour), get_colorpixel("#000000")); - draw_text(crash_text_i3strings[i], pixmap, pixmap_gc, + draw_text(crash_text_i3strings[i], pixmap, pixmap_gc, NULL, 8, 5 + i * font_height, width - 16); /* and reset the colour again for other lines */ diff --git a/src/x.c b/src/x.c index 9b9ba6aa..d312666b 100644 --- a/src/x.c +++ b/src/x.c @@ -552,7 +552,7 @@ void x_draw_decoration(Con *con) { FREE(formatted_mark); mark_width = predict_text_width(mark); - draw_text(mark, parent->pixmap, parent->pm_gc, + draw_text(mark, parent->pixmap, parent->pm_gc, NULL, con->deco_rect.x + con->deco_rect.width - mark_width - logical_px(2), con->deco_rect.y + text_offset_y, mark_width); @@ -561,7 +561,7 @@ void x_draw_decoration(Con *con) { i3String *title = win->title_format == NULL ? win->name : window_parse_title_format(win); draw_text(title, - parent->pixmap, parent->pm_gc, + parent->pixmap, parent->pm_gc, NULL, con->deco_rect.x + logical_px(2) + indent_px, con->deco_rect.y + text_offset_y, con->deco_rect.width - logical_px(2) - indent_px - mark_width - logical_px(2)); if (win->title_format != NULL) From a5d4c7c9ab2759676dfe4623eb8848941f38ff7e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ingo=20B=C3=BCrk?= Date: Wed, 7 Oct 2015 11:03:18 +0200 Subject: [PATCH 015/187] Allow text drawing to use the alpha channel. We pass alpha channel information to the current text drawing code and use it if it is available. The previous behavior of using full opacity for RGB format colors is preserved. --- libi3/font.c | 5 ++++- libi3/get_colorpixel.c | 22 ++++++++++++++++------ 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/libi3/font.c b/libi3/font.c index 8bdf3d60..e14bb080 100644 --- a/libi3/font.c +++ b/libi3/font.c @@ -29,6 +29,7 @@ static xcb_visualtype_t *root_visual_type; static double pango_font_red; static double pango_font_green; static double pango_font_blue; +static double pango_font_alpha; /* Necessary to track whether the dpi changes and trigger a LOG() message, * which is more easily visible to users. */ @@ -123,7 +124,8 @@ static void draw_text_pango(const char *text, size_t text_len, pango_layout_set_text(layout, text, text_len); /* Do the drawing */ - cairo_set_source_rgb(cr, pango_font_red, pango_font_green, pango_font_blue); + cairo_set_source_rgba(cr, pango_font_red, pango_font_green, pango_font_blue, pango_font_alpha); + cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE); pango_cairo_update_layout(cr, layout); pango_layout_get_pixel_size(layout, NULL, &height); /* Center the piece of text vertically if its height is smaller than the @@ -332,6 +334,7 @@ void set_font_colors(xcb_gcontext_t gc, uint32_t foreground, uint32_t background pango_font_red = ((foreground >> 16) & 0xff) / 255.0; pango_font_green = ((foreground >> 8) & 0xff) / 255.0; pango_font_blue = (foreground & 0xff) / 255.0; + pango_font_alpha = ((foreground >> 24) & 0xff) / 255.0; break; #endif default: diff --git a/libi3/get_colorpixel.c b/libi3/get_colorpixel.c index 44ad295d..3a62a8e4 100644 --- a/libi3/get_colorpixel.c +++ b/libi3/get_colorpixel.c @@ -7,6 +7,7 @@ */ #include #include +#include #include "libi3.h" @@ -25,14 +26,23 @@ * */ uint32_t get_colorpixel(const char *hex) { - char strgroups[3][3] = {{hex[1], hex[2], '\0'}, - {hex[3], hex[4], '\0'}, - {hex[5], hex[6], '\0'}}; + char alpha[2]; + if (strlen(hex) == strlen("#rrggbbaa")) { + alpha[0] = hex[7]; + alpha[1] = hex[8]; + } else { + alpha[0] = alpha[1] = 'F'; + } + + char strgroups[4][3] = { + {hex[1], hex[2], '\0'}, + {hex[3], hex[4], '\0'}, + {hex[5], hex[6], '\0'}, + {alpha[0], alpha[1], '\0'}}; uint8_t r = strtol(strgroups[0], NULL, 16); uint8_t g = strtol(strgroups[1], NULL, 16); uint8_t b = strtol(strgroups[2], NULL, 16); + uint8_t a = strtol(strgroups[3], NULL, 16); - /* We set the first 8 bits high to have 100% opacity in case of a 32 bit - * color depth visual. */ - return (0xFF << 24) | (r << 16 | g << 8 | b); + return (a << 24) | (r << 16 | g << 8 | b); } From ff0aeddede9ae0c91c5e970ab4c8b2aa18476c3a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ingo=20B=C3=BCrk?= Date: Thu, 8 Oct 2015 12:16:25 +0200 Subject: [PATCH 016/187] When drawing text, mark the surface as dirty. Since libi3 currently creates its own cairo surface for drawing text, we need to mark our own surface as dirty to force cairo to invalidate its cache. Otherwise, this will result in graphical glitches such as the text not showing up at all. This wrapper can be removed in the future when libi3 is adapted to reuse the same cairo surface as we do for all other drawing operations. --- i3bar/include/cairo_util.h | 8 ++++++ i3bar/src/cairo_util.c | 13 ++++++++++ i3bar/src/xcb.c | 53 +++++++++++++++++++++----------------- 3 files changed, 50 insertions(+), 24 deletions(-) diff --git a/i3bar/include/cairo_util.h b/i3bar/include/cairo_util.h index 390e6a1c..de3fa805 100644 --- a/i3bar/include/cairo_util.h +++ b/i3bar/include/cairo_util.h @@ -64,3 +64,11 @@ color_t cairo_hex_to_color(const char *color); * */ void cairo_set_source_color(surface_t *surface, color_t color); + +/** + * Draw the given text using libi3. + * This function also marks the surface dirty which is needed if other means of + * drawing are used. This will be the case when using XCB to draw text. + * + */ +void cairo_draw_text(i3String *text, surface_t *surface, color_t fg_color, color_t bg_color, int x, int y, int max_width); diff --git a/i3bar/src/cairo_util.c b/i3bar/src/cairo_util.c index 288969c9..75a99d2f 100644 --- a/i3bar/src/cairo_util.c +++ b/i3bar/src/cairo_util.c @@ -80,3 +80,16 @@ color_t cairo_hex_to_color(const char *color) { void cairo_set_source_color(surface_t *surface, color_t color) { cairo_set_source_rgba(surface->cr, color.red, color.green, color.blue, color.alpha); } + +/** + * Draw the given text using libi3. + * This function also marks the surface dirty which is needed if other means of + * drawing are used. This will be the case when using XCB to draw text. + * + */ +void cairo_draw_text(i3String *text, surface_t *surface, color_t fg_color, color_t bg_color, int x, int y, int max_width) { + set_font_colors(surface->gc, fg_color.colorpixel, bg_color.colorpixel); + draw_text(text, surface->id, surface->gc, visual_type, x, y, max_width); + + cairo_surface_mark_dirty(surface->surface); +} diff --git a/i3bar/src/xcb.c b/i3bar/src/xcb.c index 443e30ea..8f9c94b0 100644 --- a/i3bar/src/xcb.c +++ b/i3bar/src/xcb.c @@ -179,13 +179,13 @@ static void draw_separator(uint32_t x, struct status_block *block) { cairo_set_source_color(&statusline_surface, colors.sep_fg); cairo_rectangle(statusline_surface.cr, center_x, logical_px(sep_voff_px), logical_px(1), bar_height - 2 * logical_px(sep_voff_px)); cairo_fill(statusline_surface.cr); + cairo_surface_flush(statusline_surface.surface); cairo_restore(statusline_surface.cr); } else { /* Draw a custom separator. */ uint32_t separator_x = MAX(x - block->sep_block_width, center_x - separator_symbol_width / 2); - set_font_colors(statusline_surface.gc, colors.sep_fg.colorpixel, colors.bar_bg.colorpixel); - draw_text(config.separator_symbol, statusline_surface.id, statusline_surface.gc, visual_type, - separator_x, logical_px(ws_voff_px), x - separator_x); + cairo_draw_text(config.separator_symbol, &statusline_surface, colors.sep_fg, colors.bar_bg, + separator_x, logical_px(ws_voff_px), x - separator_x); } } @@ -250,6 +250,7 @@ void refresh_statusline(bool use_short_text) { cairo_set_source_color(&statusline_surface, colors.bar_bg); cairo_set_operator(statusline_surface.cr, CAIRO_OPERATOR_SOURCE); cairo_paint(statusline_surface.cr); + cairo_surface_flush(statusline_surface.surface); cairo_restore(statusline_surface.cr); /* Draw the text of each block. */ @@ -267,13 +268,13 @@ void refresh_statusline(bool use_short_text) { cairo_set_source_color(&statusline_surface, colors.urgent_ws_bg); cairo_rectangle(statusline_surface.cr, x - logical_px(2), logical_px(1), block->width + logical_px(4), bar_height - logical_px(2)); cairo_fill(statusline_surface.cr); + cairo_surface_flush(statusline_surface.surface); } else { fg_color = (block->color ? cairo_hex_to_color(block->color) : colors.bar_fg); } - set_font_colors(statusline_surface.gc, fg_color.colorpixel, colors.bar_bg.colorpixel); - draw_text(block->full_text, statusline_surface.id, statusline_surface.gc, visual_type, - x + block->x_offset, logical_px(ws_voff_px), block->width); + cairo_draw_text(block->full_text, &statusline_surface, fg_color, colors.bar_bg, + 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 this is not the last block, draw a separator. */ @@ -1815,6 +1816,7 @@ void draw_bars(bool unhide) { cairo_set_source_color(&(outputs_walk->buffer), colors.bar_bg); cairo_set_operator(outputs_walk->buffer.cr, CAIRO_OPERATOR_SOURCE); cairo_paint(outputs_walk->buffer.cr); + cairo_surface_flush(outputs_walk->buffer.surface); cairo_restore(outputs_walk->buffer.cr); if (!config.disable_ws) { @@ -1844,30 +1846,32 @@ void draw_bars(bool unhide) { unhide = true; } + /* Draw the border of the button. */ cairo_save(outputs_walk->buffer.cr); cairo_set_operator(outputs_walk->buffer.cr, CAIRO_OPERATOR_SOURCE); - - /* Draw the border of the button. */ cairo_set_source_color(&(outputs_walk->buffer), border_color); cairo_rectangle(outputs_walk->buffer.cr, workspace_width, logical_px(1), 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)); cairo_fill(outputs_walk->buffer.cr); + cairo_surface_flush(outputs_walk->buffer.surface); + cairo_restore(outputs_walk->buffer.cr); /* Draw the inside of the button. */ + cairo_save(outputs_walk->buffer.cr); + cairo_set_operator(outputs_walk->buffer.cr, CAIRO_OPERATOR_SOURCE); cairo_set_source_color(&(outputs_walk->buffer), bg_color); cairo_rectangle(outputs_walk->buffer.cr, workspace_width + logical_px(1), 2 * logical_px(1), ws_walk->name_width + 2 * logical_px(ws_hoff_px), font.height + 2 * logical_px(ws_voff_px) - 4 * logical_px(1)); cairo_fill(outputs_walk->buffer.cr); - + cairo_surface_flush(outputs_walk->buffer.surface); cairo_restore(outputs_walk->buffer.cr); - set_font_colors(outputs_walk->buffer.gc, fg_color.colorpixel, bg_color.colorpixel); - draw_text(ws_walk->name, outputs_walk->buffer.id, outputs_walk->buffer.gc, visual_type, - workspace_width + logical_px(ws_hoff_px) + logical_px(1), - logical_px(ws_voff_px), - ws_walk->name_width); + cairo_draw_text(ws_walk->name, &(outputs_walk->buffer), fg_color, bg_color, + 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) @@ -1883,29 +1887,28 @@ void draw_bars(bool unhide) { cairo_save(outputs_walk->buffer.cr); cairo_set_operator(outputs_walk->buffer.cr, CAIRO_OPERATOR_SOURCE); - cairo_set_source_color(&(outputs_walk->buffer), colors.binding_mode_border); cairo_rectangle(outputs_walk->buffer.cr, 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)); cairo_fill(outputs_walk->buffer.cr); + cairo_surface_flush(outputs_walk->buffer.surface); + cairo_restore(outputs_walk->buffer.cr); + cairo_save(outputs_walk->buffer.cr); + cairo_set_operator(outputs_walk->buffer.cr, CAIRO_OPERATOR_SOURCE); cairo_set_source_color(&(outputs_walk->buffer), bg_color); cairo_rectangle(outputs_walk->buffer.cr, 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)); cairo_fill(outputs_walk->buffer.cr); - + cairo_surface_flush(outputs_walk->buffer.surface); cairo_restore(outputs_walk->buffer.cr); - set_font_colors(outputs_walk->buffer.gc, fg_color.colorpixel, bg_color.colorpixel); - draw_text(binding.name, - outputs_walk->buffer.id, - outputs_walk->buffer.gc, - visual_type, - workspace_width + logical_px(ws_hoff_px) + logical_px(1), - logical_px(ws_voff_px), - binding.width); + cairo_draw_text(binding.name, &(outputs_walk->buffer), fg_color, bg_color, + 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; @@ -1940,6 +1943,7 @@ void draw_bars(bool unhide) { cairo_set_source_surface(outputs_walk->buffer.cr, statusline_surface.surface, x_dest - x_src, 0); cairo_rectangle(outputs_walk->buffer.cr, x_dest, 0, (int16_t)visible_statusline_width, (int16_t)bar_height); cairo_fill(outputs_walk->buffer.cr); + cairo_surface_flush(outputs_walk->buffer.surface); cairo_restore(outputs_walk->buffer.cr); } @@ -1974,6 +1978,7 @@ void redraw_bars(void) { cairo_set_source_surface(outputs_walk->bar.cr, outputs_walk->buffer.surface, 0, 0); cairo_rectangle(outputs_walk->bar.cr, 0, 0, outputs_walk->rect.w, outputs_walk->rect.h); cairo_fill(outputs_walk->bar.cr); + cairo_surface_flush(outputs_walk->bar.surface); cairo_restore(outputs_walk->bar.cr); xcb_flush(xcb_connection); From d300a076606be3d5b74d42a51c312ff2f7791f6d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ingo=20B=C3=BCrk?= Date: Thu, 8 Oct 2015 12:31:56 +0200 Subject: [PATCH 017/187] Refactor cairo drawing of rectangles into utility functions. --- i3bar/include/cairo_util.h | 17 +++++++ i3bar/src/cairo_util.c | 50 ++++++++++++++++++++ i3bar/src/xcb.c | 96 ++++++++++++++------------------------ 3 files changed, 101 insertions(+), 62 deletions(-) diff --git a/i3bar/include/cairo_util.h b/i3bar/include/cairo_util.h index de3fa805..ce1d3180 100644 --- a/i3bar/include/cairo_util.h +++ b/i3bar/include/cairo_util.h @@ -72,3 +72,20 @@ void cairo_set_source_color(surface_t *surface, color_t color); * */ void cairo_draw_text(i3String *text, surface_t *surface, color_t fg_color, color_t bg_color, int x, int y, int max_width); + +/** + * Draws a filled rectangle. + * This function is a convenience wrapper and takes care of flushing the + * surface as well as restoring the cairo state. + * Note that the drawing is done using CAIRO_OPERATOR_SOURCE. + * + */ +void cairo_draw_rectangle(surface_t *surface, color_t color, double x, double y, double w, double h); + +/** + * Copies a surface onto another surface. + * Note that the drawing is done using CAIRO_OPERATOR_SOURCE. + * + */ +void cairo_copy_surface(surface_t *src, surface_t *dest, double src_x, double src_y, + double dest_x, double dest_y, double dest_w, double dest_h); diff --git a/i3bar/src/cairo_util.c b/i3bar/src/cairo_util.c index 75a99d2f..e1728786 100644 --- a/i3bar/src/cairo_util.c +++ b/i3bar/src/cairo_util.c @@ -93,3 +93,53 @@ void cairo_draw_text(i3String *text, surface_t *surface, color_t fg_color, color cairo_surface_mark_dirty(surface->surface); } + +/** + * Draws a filled rectangle. + * This function is a convenience wrapper and takes care of flushing the + * surface as well as restoring the cairo state. + * Note that the drawing is done using CAIRO_OPERATOR_SOURCE. + * + */ +void cairo_draw_rectangle(surface_t *surface, color_t color, double x, double y, double w, double h) { + cairo_save(surface->cr); + + /* Using the SOURCE operator will copy both color and alpha information directly + * onto the surface rather than blending it. This is a bit more efficient and + * allows better color control for the user when using opacity. */ + cairo_set_operator(surface->cr, CAIRO_OPERATOR_SOURCE); + cairo_set_source_color(surface, color); + + cairo_rectangle(surface->cr, x, y, w, h); + cairo_fill(surface->cr); + + /* Make sure we flush the surface for any text drawing operations that could follow. + * Since we support drawing text via XCB, we need this. */ + cairo_surface_flush(surface->surface); + + cairo_restore(surface->cr); +} + +/** + * Copies a surface onto another surface. + * Note that the drawing is done using CAIRO_OPERATOR_SOURCE. + * + */ +void cairo_copy_surface(surface_t *src, surface_t *dest, double src_x, double src_y, + double dest_x, double dest_y, double dest_w, double dest_h) { + cairo_save(dest->cr); + + /* Using the SOURCE operator will copy both color and alpha information directly + * onto the surface rather than blending it. This is a bit more efficient and + * allows better color control for the user when using opacity. */ + cairo_set_operator(dest->cr, CAIRO_OPERATOR_SOURCE); + cairo_set_source_surface(dest->cr, src->surface, src_x, src_y); + + cairo_rectangle(dest->cr, dest_x, dest_y, dest_w, dest_h); + cairo_fill(dest->cr); + + /* Make sure we flush the surface for any text drawing operations that could follow. + * Since we support drawing text via XCB, we need this. */ + cairo_surface_flush(dest->surface); + cairo_restore(dest->cr); +} diff --git a/i3bar/src/xcb.c b/i3bar/src/xcb.c index 8f9c94b0..2cd3bd6e 100644 --- a/i3bar/src/xcb.c +++ b/i3bar/src/xcb.c @@ -174,13 +174,11 @@ static void draw_separator(uint32_t x, struct status_block *block) { uint32_t center_x = x - sep_offset; if (config.separator_symbol == NULL) { /* Draw a classic one pixel, vertical separator. */ - cairo_save(statusline_surface.cr); - cairo_set_operator(statusline_surface.cr, CAIRO_OPERATOR_SOURCE); - cairo_set_source_color(&statusline_surface, colors.sep_fg); - cairo_rectangle(statusline_surface.cr, center_x, logical_px(sep_voff_px), logical_px(1), bar_height - 2 * logical_px(sep_voff_px)); - cairo_fill(statusline_surface.cr); - cairo_surface_flush(statusline_surface.surface); - cairo_restore(statusline_surface.cr); + cairo_draw_rectangle(&statusline_surface, colors.sep_fg, + center_x, + logical_px(sep_voff_px), + logical_px(1), + bar_height - 2 * logical_px(sep_voff_px)); } else { /* Draw a custom separator. */ uint32_t separator_x = MAX(x - block->sep_block_width, center_x - separator_symbol_width / 2); @@ -265,10 +263,11 @@ void refresh_statusline(bool use_short_text) { fg_color = colors.urgent_ws_fg; /* Draw the background */ - cairo_set_source_color(&statusline_surface, colors.urgent_ws_bg); - cairo_rectangle(statusline_surface.cr, x - logical_px(2), logical_px(1), block->width + logical_px(4), bar_height - logical_px(2)); - cairo_fill(statusline_surface.cr); - cairo_surface_flush(statusline_surface.surface); + cairo_draw_rectangle(&statusline_surface, colors.urgent_ws_bg, + x - logical_px(2), + logical_px(1), + block->width + logical_px(4), + bar_height - logical_px(2)); } else { fg_color = (block->color ? cairo_hex_to_color(block->color) : colors.bar_fg); } @@ -1847,26 +1846,18 @@ void draw_bars(bool unhide) { } /* Draw the border of the button. */ - cairo_save(outputs_walk->buffer.cr); - cairo_set_operator(outputs_walk->buffer.cr, CAIRO_OPERATOR_SOURCE); - cairo_set_source_color(&(outputs_walk->buffer), border_color); - cairo_rectangle(outputs_walk->buffer.cr, workspace_width, logical_px(1), - 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)); - cairo_fill(outputs_walk->buffer.cr); - cairo_surface_flush(outputs_walk->buffer.surface); - cairo_restore(outputs_walk->buffer.cr); + cairo_draw_rectangle(&(outputs_walk->buffer), border_color, + workspace_width, + logical_px(1), + 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)); /* Draw the inside of the button. */ - cairo_save(outputs_walk->buffer.cr); - cairo_set_operator(outputs_walk->buffer.cr, CAIRO_OPERATOR_SOURCE); - cairo_set_source_color(&(outputs_walk->buffer), bg_color); - cairo_rectangle(outputs_walk->buffer.cr, workspace_width + logical_px(1), 2 * logical_px(1), - ws_walk->name_width + 2 * logical_px(ws_hoff_px), - font.height + 2 * logical_px(ws_voff_px) - 4 * logical_px(1)); - cairo_fill(outputs_walk->buffer.cr); - cairo_surface_flush(outputs_walk->buffer.surface); - cairo_restore(outputs_walk->buffer.cr); + cairo_draw_rectangle(&(outputs_walk->buffer), bg_color, + workspace_width + logical_px(1), + 2 * logical_px(1), + ws_walk->name_width + 2 * logical_px(ws_hoff_px), + font.height + 2 * logical_px(ws_voff_px) - 4 * logical_px(1)); cairo_draw_text(ws_walk->name, &(outputs_walk->buffer), fg_color, bg_color, workspace_width + logical_px(ws_hoff_px) + logical_px(1), @@ -1885,25 +1876,17 @@ void draw_bars(bool unhide) { color_t fg_color = colors.binding_mode_fg; color_t bg_color = colors.binding_mode_bg; - cairo_save(outputs_walk->buffer.cr); - cairo_set_operator(outputs_walk->buffer.cr, CAIRO_OPERATOR_SOURCE); - cairo_set_source_color(&(outputs_walk->buffer), colors.binding_mode_border); - cairo_rectangle(outputs_walk->buffer.cr, 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)); - cairo_fill(outputs_walk->buffer.cr); - cairo_surface_flush(outputs_walk->buffer.surface); - cairo_restore(outputs_walk->buffer.cr); + cairo_draw_rectangle(&(outputs_walk->buffer), colors.binding_mode_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)); - cairo_save(outputs_walk->buffer.cr); - cairo_set_operator(outputs_walk->buffer.cr, CAIRO_OPERATOR_SOURCE); - cairo_set_source_color(&(outputs_walk->buffer), bg_color); - cairo_rectangle(outputs_walk->buffer.cr, 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)); - cairo_fill(outputs_walk->buffer.cr); - cairo_surface_flush(outputs_walk->buffer.surface); - cairo_restore(outputs_walk->buffer.cr); + cairo_draw_rectangle(&(outputs_walk->buffer), bg_color, + 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)); cairo_draw_text(binding.name, &(outputs_walk->buffer), fg_color, bg_color, workspace_width + logical_px(ws_hoff_px) + logical_px(1), @@ -1938,13 +1921,8 @@ void draw_bars(bool unhide) { int x_src = (int16_t)(statusline_width - visible_statusline_width); int x_dest = (int16_t)(outputs_walk->rect.w - tray_width - logical_px(sb_hoff_px) - visible_statusline_width); - cairo_save(outputs_walk->buffer.cr); - cairo_set_operator(outputs_walk->buffer.cr, CAIRO_OPERATOR_SOURCE); - cairo_set_source_surface(outputs_walk->buffer.cr, statusline_surface.surface, x_dest - x_src, 0); - cairo_rectangle(outputs_walk->buffer.cr, x_dest, 0, (int16_t)visible_statusline_width, (int16_t)bar_height); - cairo_fill(outputs_walk->buffer.cr); - cairo_surface_flush(outputs_walk->buffer.surface); - cairo_restore(outputs_walk->buffer.cr); + cairo_copy_surface(&statusline_surface, &(outputs_walk->buffer), x_dest - x_src, 0, + x_dest, 0, (int16_t)visible_statusline_width, (int16_t)bar_height); } workspace_width = 0; @@ -1973,14 +1951,8 @@ void redraw_bars(void) { continue; } - cairo_save(outputs_walk->bar.cr); - cairo_set_operator(outputs_walk->bar.cr, CAIRO_OPERATOR_SOURCE); - cairo_set_source_surface(outputs_walk->bar.cr, outputs_walk->buffer.surface, 0, 0); - cairo_rectangle(outputs_walk->bar.cr, 0, 0, outputs_walk->rect.w, outputs_walk->rect.h); - cairo_fill(outputs_walk->bar.cr); - cairo_surface_flush(outputs_walk->bar.surface); - cairo_restore(outputs_walk->bar.cr); - + cairo_copy_surface(&(outputs_walk->buffer), &(outputs_walk->bar), 0, 0, + 0, 0, outputs_walk->rect.w, outputs_walk->rect.h); xcb_flush(xcb_connection); } } From 73289a7394d55832fa8cd28ace165728b929dee0 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 11 Oct 2015 00:12:38 +0200 Subject: [PATCH 018/187] travis: install clang-format-3.5 from llvm repository MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Ubuntu utopic disappeared from archive.ubuntu.com, it’s EOL. --- .travis.yml | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index f90e0ebd..dd1fb156 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,12 +2,20 @@ language: c compiler: - gcc - clang +addons: + apt: + sources: + - llvm-toolchain-precise-3.5 + # ubuntu-toolchain-r-test contains libstdc++6 >= 4.8 which libllvm3.5 needs. + - ubuntu-toolchain-r-test + packages: + - clang-format-3.5 + - libllvm3.5 before_install: # The travis VMs run on Ubuntu 12.04 which is very old and a huge pain to get # into a state where we can build a recent version of i3 :(. - "echo 'deb http://archive.ubuntu.com/ubuntu/ trusty main universe' | sudo tee /etc/apt/sources.list.d/trusty.list" - "echo 'APT::Default-Release \"precise\";' | sudo tee /etc/apt/apt.conf.d/default-release" - - "echo 'deb http://archive.ubuntu.com/ubuntu/ utopic main universe' | sudo tee /etc/apt/sources.list.d/utopic.list" - "echo 'Package: libc6' > /tmp/pin" - "echo 'Pin: release n=trusty' >> /tmp/pin" @@ -33,8 +41,6 @@ before_install: - sudo apt-get update - sudo apt-get install -t trusty libc6 libc6-dev - sudo apt-get install --no-install-recommends devscripts equivs xdotool - - sudo apt-get install -t utopic clang-format-3.5 - - clang-format-3.5 --version install: - sudo mk-build-deps --install --remove --tool 'apt-get --no-install-recommends' debian/control # Install as many dependencies as possible via apt because cpanm is not very reliable/easy to debug. From 21c0c2084322e5de338effcd44dce591f483ad0f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ingo=20B=C3=BCrk?= Date: Sat, 10 Oct 2015 21:27:23 +0200 Subject: [PATCH 019/187] Remove support for 32-bit visuals and RGBA colors. fixes #1984 --- i3bar/include/cairo_util.h | 1 - i3bar/src/cairo_util.c | 16 +++------------- i3bar/src/xcb.c | 16 +--------------- libi3/font.c | 4 +--- libi3/get_colorpixel.c | 16 +++------------- 5 files changed, 8 insertions(+), 45 deletions(-) diff --git a/i3bar/include/cairo_util.h b/i3bar/include/cairo_util.h index ce1d3180..37eaa6e2 100644 --- a/i3bar/include/cairo_util.h +++ b/i3bar/include/cairo_util.h @@ -15,7 +15,6 @@ typedef struct color_t { double red; double green; double blue; - double alpha; /* For compatibility, we also store the colorpixel for now. */ uint32_t colorpixel; diff --git a/i3bar/src/cairo_util.c b/i3bar/src/cairo_util.c index e1728786..52181aee 100644 --- a/i3bar/src/cairo_util.c +++ b/i3bar/src/cairo_util.c @@ -51,25 +51,15 @@ void cairo_surface_free(surface_t *surface) { * */ color_t cairo_hex_to_color(const char *color) { - char alpha[2]; - if (strlen(color) == strlen("#rrggbbaa")) { - alpha[0] = color[7]; - alpha[1] = color[8]; - } else { - alpha[0] = alpha[1] = 'F'; - } - - char groups[4][3] = { + char groups[3][3] = { {color[1], color[2], '\0'}, {color[3], color[4], '\0'}, - {color[5], color[6], '\0'}, - {alpha[0], alpha[1], '\0'}}; + {color[5], color[6], '\0'}}; return (color_t){ .red = strtol(groups[0], NULL, 16) / 255.0, .green = strtol(groups[1], NULL, 16) / 255.0, .blue = strtol(groups[2], NULL, 16) / 255.0, - .alpha = strtol(groups[3], NULL, 16) / 255.0, .colorpixel = get_colorpixel(color)}; } @@ -78,7 +68,7 @@ color_t cairo_hex_to_color(const char *color) { * */ void cairo_set_source_color(surface_t *surface, color_t color) { - cairo_set_source_rgba(surface->cr, color.red, color.green, color.blue, color.alpha); + cairo_set_source_rgb(surface->cr, color.red, color.green, color.blue); } /** diff --git a/i3bar/src/xcb.c b/i3bar/src/xcb.c index 2cd3bd6e..ac9b50f9 100644 --- a/i3bar/src/xcb.c +++ b/i3bar/src/xcb.c @@ -1122,21 +1122,7 @@ char *init_xcb_early() { depth = root_screen->root_depth; colormap = root_screen->default_colormap; - visual_type = xcb_aux_find_visual_by_attrs(root_screen, -1, 32); - if (visual_type) { - depth = xcb_aux_get_depth_of_visual(root_screen, visual_type->visual_id); - colormap = xcb_generate_id(xcb_connection); - xcb_void_cookie_t cm_cookie = xcb_create_colormap_checked(xcb_connection, - XCB_COLORMAP_ALLOC_NONE, - colormap, - xcb_root, - visual_type->visual_id); - if (xcb_request_failed(cm_cookie, "Could not allocate colormap")) { - exit(EXIT_FAILURE); - } - } else { - visual_type = get_visualtype(root_screen); - } + visual_type = get_visualtype(root_screen); /* We draw the statusline to a seperate pixmap, because it looks the same on all bars and * this way, we can choose to crop it */ diff --git a/libi3/font.c b/libi3/font.c index e14bb080..9e808a89 100644 --- a/libi3/font.c +++ b/libi3/font.c @@ -29,7 +29,6 @@ static xcb_visualtype_t *root_visual_type; static double pango_font_red; static double pango_font_green; static double pango_font_blue; -static double pango_font_alpha; /* Necessary to track whether the dpi changes and trigger a LOG() message, * which is more easily visible to users. */ @@ -124,8 +123,8 @@ static void draw_text_pango(const char *text, size_t text_len, pango_layout_set_text(layout, text, text_len); /* Do the drawing */ - cairo_set_source_rgba(cr, pango_font_red, pango_font_green, pango_font_blue, pango_font_alpha); cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE); + cairo_set_source_rgb(cr, pango_font_red, pango_font_green, pango_font_blue); pango_cairo_update_layout(cr, layout); pango_layout_get_pixel_size(layout, NULL, &height); /* Center the piece of text vertically if its height is smaller than the @@ -334,7 +333,6 @@ void set_font_colors(xcb_gcontext_t gc, uint32_t foreground, uint32_t background pango_font_red = ((foreground >> 16) & 0xff) / 255.0; pango_font_green = ((foreground >> 8) & 0xff) / 255.0; pango_font_blue = (foreground & 0xff) / 255.0; - pango_font_alpha = ((foreground >> 24) & 0xff) / 255.0; break; #endif default: diff --git a/libi3/get_colorpixel.c b/libi3/get_colorpixel.c index 3a62a8e4..f81ea6c2 100644 --- a/libi3/get_colorpixel.c +++ b/libi3/get_colorpixel.c @@ -26,23 +26,13 @@ * */ uint32_t get_colorpixel(const char *hex) { - char alpha[2]; - if (strlen(hex) == strlen("#rrggbbaa")) { - alpha[0] = hex[7]; - alpha[1] = hex[8]; - } else { - alpha[0] = alpha[1] = 'F'; - } - - char strgroups[4][3] = { + char strgroups[3][3] = { {hex[1], hex[2], '\0'}, {hex[3], hex[4], '\0'}, - {hex[5], hex[6], '\0'}, - {alpha[0], alpha[1], '\0'}}; + {hex[5], hex[6], '\0'}}; uint8_t r = strtol(strgroups[0], NULL, 16); uint8_t g = strtol(strgroups[1], NULL, 16); uint8_t b = strtol(strgroups[2], NULL, 16); - uint8_t a = strtol(strgroups[3], NULL, 16); - return (a << 24) | (r << 16 | g << 8 | b); + return (0xFF << 24) | (r << 16 | g << 8 | b); } From 54486ca498e07c5c97818c05714184617642a6b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ingo=20B=C3=BCrk?= Date: Sun, 11 Oct 2015 17:05:30 +0200 Subject: [PATCH 020/187] Fix documentation for no_focus. fixes #1986 --- docs/userguide | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/userguide b/docs/userguide index b077eb66..c4c93796 100644 --- a/docs/userguide +++ b/docs/userguide @@ -604,7 +604,7 @@ The valid criteria are the same as those for commands, see <>. [[no_focus]] When a new window appears, it will be focused. The +no_focus+ directive allows preventing -this from happening and can be used in combination with <>. +this from happening and must be used in combination with <>. Note that this does not apply to all cases, e.g., when feeding data into a running application causing it to request being focused. To configure the behavior in such cases, refer to From 321bba224a72966d25f5ba3f09d3ea946a4394a6 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 11 Oct 2015 20:23:07 +0200 Subject: [PATCH 021/187] Bugfix: add keymap fall back (_XKB_RULES_NAMES, then defaults) fixes #1983 --- src/bindings.c | 111 ++++++++++++++++++++++++++++++++++++++++++++++--- src/main.c | 2 + 2 files changed, 107 insertions(+), 6 deletions(-) diff --git a/src/bindings.c b/src/bindings.c index 086d230a..eac63a59 100644 --- a/src/bindings.c +++ b/src/bindings.c @@ -644,6 +644,77 @@ CommandResult *run_binding(Binding *bind, Con *con) { return result; } +static int fill_rmlvo_from_root(struct xkb_rule_names *xkb_names) { + xcb_intern_atom_reply_t *atom_reply; + size_t content_max_words = 256; + + xcb_window_t root = root_screen->root; + + atom_reply = xcb_intern_atom_reply( + conn, xcb_intern_atom(conn, 0, strlen("_XKB_RULES_NAMES"), "_XKB_RULES_NAMES"), NULL); + if (atom_reply == NULL) + return -1; + + xcb_get_property_cookie_t prop_cookie; + xcb_get_property_reply_t *prop_reply; + prop_cookie = xcb_get_property_unchecked(conn, false, root, atom_reply->atom, + XCB_GET_PROPERTY_TYPE_ANY, 0, content_max_words); + prop_reply = xcb_get_property_reply(conn, prop_cookie, NULL); + if (prop_reply == NULL) { + free(atom_reply); + return -1; + } + if (xcb_get_property_value_length(prop_reply) > 0 && prop_reply->bytes_after > 0) { + /* We received an incomplete value. Ask again but with a properly + * adjusted size. */ + content_max_words += ceil(prop_reply->bytes_after / 4.0); + /* Repeat the request, with adjusted size */ + free(prop_reply); + prop_cookie = xcb_get_property_unchecked(conn, false, root, atom_reply->atom, + XCB_GET_PROPERTY_TYPE_ANY, 0, content_max_words); + prop_reply = xcb_get_property_reply(conn, prop_cookie, NULL); + if (prop_reply == NULL) { + free(atom_reply); + return -1; + } + } + if (xcb_get_property_value_length(prop_reply) == 0) { + free(atom_reply); + free(prop_reply); + return -1; + } + + const char *walk = (const char *)xcb_get_property_value(prop_reply); + int remaining = xcb_get_property_value_length(prop_reply); + for (int i = 0; i < 5 && remaining > 0; i++) { + const int len = strnlen(walk, remaining); + remaining -= len; + switch (i) { + case 0: + asprintf((char **)&(xkb_names->rules), "%.*s", len, walk); + break; + case 1: + asprintf((char **)&(xkb_names->model), "%.*s", len, walk); + break; + case 2: + asprintf((char **)&(xkb_names->layout), "%.*s", len, walk); + break; + case 3: + asprintf((char **)&(xkb_names->variant), "%.*s", len, walk); + break; + case 4: + asprintf((char **)&(xkb_names->options), "%.*s", len, walk); + break; + } + DLOG("component %d of _XKB_RULES_NAMES is \"%.*s\"\n", i, len, walk); + walk += (len + 1); + } + + free(atom_reply); + free(prop_reply); + return 0; +} + /* * Loads the XKB keymap from the X11 server and feeds it to xkbcommon. * @@ -656,12 +727,40 @@ bool load_keymap(void) { } } - struct xkb_keymap *new_keymap; - const int32_t device_id = xkb_x11_get_core_keyboard_device_id(conn); - DLOG("device_id = %d\n", device_id); - if ((new_keymap = xkb_x11_keymap_new_from_device(xkb_context, conn, device_id, 0)) == NULL) { - ELOG("xkb_x11_keymap_new_from_device failed\n"); - return false; + struct xkb_keymap *new_keymap = NULL; + int32_t device_id; + if (xkb_supported && (device_id = xkb_x11_get_core_keyboard_device_id(conn)) > -1) { + if ((new_keymap = xkb_x11_keymap_new_from_device(xkb_context, conn, device_id, 0)) == NULL) { + ELOG("xkb_x11_keymap_new_from_device failed\n"); + return false; + } + } else { + /* Likely there is no XKB support on this server, possibly because it + * is a VNC server. */ + LOG("No XKB / core keyboard device? Assembling keymap from local RMLVO.\n"); + struct xkb_rule_names names = { + .rules = NULL, + .model = NULL, + .layout = NULL, + .variant = NULL, + .options = NULL}; + if (fill_rmlvo_from_root(&names) == -1) { + ELOG("Could not get _XKB_RULES_NAMES atom from root window, falling back to defaults.\n"); + if ((new_keymap = xkb_keymap_new_from_names(xkb_context, &names, 0)) == NULL) { + ELOG("xkb_keymap_new_from_names(NULL) failed\n"); + return false; + } + } + new_keymap = xkb_keymap_new_from_names(xkb_context, &names, 0); + free((char *)names.rules); + free((char *)names.model); + free((char *)names.layout); + free((char *)names.variant); + free((char *)names.options); + if (new_keymap == NULL) { + ELOG("xkb_keymap_new_from_names(RMLVO) failed\n"); + return false; + } } xkb_keymap_unref(xkb_keymap); xkb_keymap = new_keymap; diff --git a/src/main.c b/src/main.c index 0dc25936..563fb00c 100644 --- a/src/main.c +++ b/src/main.c @@ -87,6 +87,7 @@ struct ws_assignments_head ws_assignments = TAILQ_HEAD_INITIALIZER(ws_assignment /* We hope that those are supported and set them to true */ bool xcursor_supported = true; +bool xkb_supported = true; /* * This callback is only a dummy, see xcb_prepare_cb and xcb_check_cb. @@ -543,6 +544,7 @@ int main(int argc, char *argv[]) { const xcb_query_extension_reply_t *extreply; extreply = xcb_get_extension_data(conn, &xcb_xkb_id); + xkb_supported = extreply->present; if (!extreply->present) { DLOG("xkb is not present on this server\n"); } else { From d24964ff6ab582879b4e82411b05e30f493852a8 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 11 Oct 2015 20:42:52 +0200 Subject: [PATCH 022/187] Use sasprintf() --- src/bindings.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/bindings.c b/src/bindings.c index eac63a59..c0e01030 100644 --- a/src/bindings.c +++ b/src/bindings.c @@ -691,19 +691,19 @@ static int fill_rmlvo_from_root(struct xkb_rule_names *xkb_names) { remaining -= len; switch (i) { case 0: - asprintf((char **)&(xkb_names->rules), "%.*s", len, walk); + sasprintf((char **)&(xkb_names->rules), "%.*s", len, walk); break; case 1: - asprintf((char **)&(xkb_names->model), "%.*s", len, walk); + sasprintf((char **)&(xkb_names->model), "%.*s", len, walk); break; case 2: - asprintf((char **)&(xkb_names->layout), "%.*s", len, walk); + sasprintf((char **)&(xkb_names->layout), "%.*s", len, walk); break; case 3: - asprintf((char **)&(xkb_names->variant), "%.*s", len, walk); + sasprintf((char **)&(xkb_names->variant), "%.*s", len, walk); break; case 4: - asprintf((char **)&(xkb_names->options), "%.*s", len, walk); + sasprintf((char **)&(xkb_names->options), "%.*s", len, walk); break; } DLOG("component %d of _XKB_RULES_NAMES is \"%.*s\"\n", i, len, walk); From f7907c11d702fe5cc658f6aec0fdd4c2d1074cdb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ingo=20B=C3=BCrk?= Date: Mon, 12 Oct 2015 12:56:19 +0200 Subject: [PATCH 023/187] Suppress no_focus for first window on a workspace. With this patch, the no_focus directive will be ignored if the to-be-opened window is the first on its workspace as there's no reason the user would not want to focus it in this case. This improves usability when, for example, using a tabbed workspace_layout. fixes #1987 --- docs/userguide | 4 ++++ src/manage.c | 18 ++++++++++++++---- testcases/t/242-no-focus.t | 30 ++++++++++++++++++++++++++++-- 3 files changed, 46 insertions(+), 6 deletions(-) diff --git a/docs/userguide b/docs/userguide index c4c93796..007ef69f 100644 --- a/docs/userguide +++ b/docs/userguide @@ -610,6 +610,10 @@ Note that this does not apply to all cases, e.g., when feeding data into a runni causing it to request being focused. To configure the behavior in such cases, refer to <>. ++no_focus+ will also be ignored for the first window on a workspace as there shouldn't be +a reason to not focus the window in this case. This allows for better usability in +combination with +workspace_layout+. + *Syntax*: ------------------- no_focus diff --git a/src/manage.c b/src/manage.c index e3769670..5cfe490e 100644 --- a/src/manage.c +++ b/src/manage.c @@ -524,13 +524,23 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki /* Send an event about window creation */ ipc_send_window_event("new", nc); + if (set_focus && assignment_for(cwindow, A_NO_FOCUS) != NULL) { + /* The first window on a workspace should always be focused. We have to + * compare with == 1 because the container has already been inserted at + * this point. */ + if (con_num_children(ws) == 1) { + DLOG("This is the first window on this workspace, ignoring no_focus.\n"); + } else { + DLOG("no_focus was set for con = %p, not setting focus.\n", nc); + set_focus = false; + } + } + /* Defer setting focus after the 'new' event has been sent to ensure the * proper window event sequence. */ if (set_focus && !nc->window->doesnt_accept_focus && nc->mapped) { - if (assignment_for(cwindow, A_NO_FOCUS) == NULL) { - DLOG("Now setting focus.\n"); - con_focus(nc); - } + DLOG("Now setting focus.\n"); + con_focus(nc); } tree_render(); diff --git a/testcases/t/242-no-focus.t b/testcases/t/242-no-focus.t index 143ae5cf..0a7f5c93 100644 --- a/testcases/t/242-no-focus.t +++ b/testcases/t/242-no-focus.t @@ -30,13 +30,15 @@ font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1 EOT $pid = launch_with_config($config); - + $ws = fresh_workspace; $first = open_window; $focused = get_focused($ws); $second = open_window; +sync_with_i3; isnt(get_focused($ws), $focused, 'focus has changed'); +is($x->input_focus, $second->id, 'input focus has changed'); exit_gracefully($pid); @@ -53,13 +55,37 @@ no_focus [instance=notme] EOT $pid = launch_with_config($config); - + $ws = fresh_workspace; $first = open_window; $focused = get_focused($ws); $second = open_window(wm_class => 'notme'); +sync_with_i3; is(get_focused($ws), $focused, 'focus has not changed'); +is($x->input_focus, $first->id, 'input focus has not changed'); + +exit_gracefully($pid); + +##################################################################### +## 3: no_focus doesn't affect the first window opened on a workspace +##################################################################### + +$config = < 'focusme'); + +sync_with_i3; +is($x->input_focus, $first->id, 'input focus has changed'); exit_gracefully($pid); From 82dc747396e5a6814c3bcd66f065c04e68253448 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ingo=20B=C3=BCrk?= Date: Mon, 12 Oct 2015 23:43:47 +0200 Subject: [PATCH 024/187] Make pango markup in mode names optional with a flag. This introduces the flag "--pango" on the mode config directive to explicitly enable pango markup for mode names. Not setting this will cause the mode name to be rendered as is. This fixes a regression in 4.11 where mode names containing characters such as '<' would break user's configs as they didn't escape these characters. fixes #1992 --- docs/ipc | 8 ++++-- i3bar/src/mode.c | 49 +++++++++++++++++++++++++++++---- include/bindings.h | 2 +- include/config.h | 1 + include/config_directives.h | 2 +- parser-specs/config.spec | 4 ++- src/bindings.c | 10 ++++--- src/config_directives.c | 8 ++++-- testcases/t/201-config-parser.t | 4 +-- 9 files changed, 68 insertions(+), 20 deletions(-) diff --git a/docs/ipc b/docs/ipc index 5113d79b..1813e53a 100644 --- a/docs/ipc +++ b/docs/ipc @@ -717,11 +717,15 @@ This event consists of a single serialized map containing a property This event consists of a single serialized map containing a property +change (string)+ which holds the name of current mode in use. The name is the same as specified in config when creating a mode. The default -mode is simply named default. +mode is simply named default. It contains a second property, +pango_markup+, which +defines whether pango markup shall be used for displaying this mode. *Example:* --------------------------- -{ "change": "default" } +{ + "change": "default", + "pango_markup": true +} --------------------------- === window event diff --git a/i3bar/src/mode.c b/i3bar/src/mode.c index 7f7537af..4b27b110 100644 --- a/i3bar/src/mode.c +++ b/i3bar/src/mode.c @@ -20,6 +20,8 @@ struct mode_json_params { char *json; char *cur_key; + char *name; + bool pango_markup; mode *mode; }; @@ -31,17 +33,35 @@ static int mode_string_cb(void *params_, const unsigned char *val, size_t len) { struct mode_json_params *params = (struct mode_json_params *)params_; if (!strcmp(params->cur_key, "change")) { - /* Save the name */ - params->mode->name = i3string_from_markup_with_length((const char *)val, len); - /* Save its rendered width */ - params->mode->width = predict_text_width(params->mode->name); + char *copy = smalloc(sizeof(const unsigned char) * (len + 1)); + strncpy(copy, (const char *)val, len); + copy[len] = '\0'; - DLOG("Got mode change: %s\n", i3string_as_utf8(params->mode->name)); + params->name = copy; FREE(params->cur_key); - return 1; } + FREE(params->cur_key); + return 0; +} + +/* + * Parse a boolean. + * + */ +static int mode_boolean_cb(void *params_, int val) { + struct mode_json_params *params = (struct mode_json_params *)params_; + + if (strcmp(params->cur_key, "pango_markup") == 0) { + DLOG("Setting pango_markup to %d.\n", val); + params->pango_markup = val; + + FREE(params->cur_key); + return 1; + } + + FREE(params->cur_key); return 0; } @@ -62,10 +82,27 @@ static int mode_map_key_cb(void *params_, const unsigned char *keyVal, size_t ke return 1; } +static int mode_end_map_cb(void *params_) { + struct mode_json_params *params = (struct mode_json_params *)params_; + + /* Save the name */ + params->mode->name = i3string_from_utf8(params->name); + i3string_set_markup(params->mode->name, params->pango_markup); + /* Save its rendered width */ + params->mode->width = predict_text_width(params->mode->name); + + DLOG("Got mode change: %s\n", i3string_as_utf8(params->mode->name)); + FREE(params->cur_key); + + return 1; +} + /* A datastructure to pass all these callbacks to yajl */ static yajl_callbacks mode_callbacks = { .yajl_string = mode_string_cb, + .yajl_boolean = mode_boolean_cb, .yajl_map_key = mode_map_key_cb, + .yajl_end_map = mode_end_map_cb, }; /* diff --git a/include/bindings.h b/include/bindings.h index 88b4a6cc..e9e5dac9 100644 --- a/include/bindings.h +++ b/include/bindings.h @@ -25,7 +25,7 @@ const char *DEFAULT_BINDING_MODE; */ Binding *configure_binding(const char *bindtype, const char *modifiers, const char *input_code, const char *release, const char *border, const char *whole_window, - const char *command, const char *mode); + const char *command, const char *mode, bool pango_markup); /** * Grab the bound keys (tell X to send us keypress events for those keycodes) diff --git a/include/config.h b/include/config.h index fd6fe6c0..6312d3d2 100644 --- a/include/config.h +++ b/include/config.h @@ -77,6 +77,7 @@ struct Variable { */ struct Mode { char *name; + bool pango_markup; struct bindings_head *bindings; SLIST_ENTRY(Mode) modes; diff --git a/include/config_directives.h b/include/config_directives.h index c0c70bb4..97b8b424 100644 --- a/include/config_directives.h +++ b/include/config_directives.h @@ -66,7 +66,7 @@ CFGFUN(new_window, const char *windowtype, const char *border, const long width) CFGFUN(workspace, const char *workspace, const char *output); CFGFUN(binding, const char *bindtype, const char *modifiers, const char *key, const char *release, const char *border, const char *whole_window, const char *command); -CFGFUN(enter_mode, const char *mode); +CFGFUN(enter_mode, const char *pango_markup, const char *mode); CFGFUN(mode_binding, const char *bindtype, const char *modifiers, const char *key, const char *release, const char *border, const char *whole_window, const char *command); CFGFUN(bar_font, const char *font); diff --git a/parser-specs/config.spec b/parser-specs/config.spec index b9542c8c..2170ace3 100644 --- a/parser-specs/config.spec +++ b/parser-specs/config.spec @@ -326,8 +326,10 @@ state BINDCOMMAND: ################################################################################ state MODENAME: + pango_markup = '--pango_markup' + -> modename = word - -> call cfg_enter_mode($modename); MODEBRACE + -> call cfg_enter_mode($pango_markup, $modename); MODEBRACE state MODEBRACE: end diff --git a/src/bindings.c b/src/bindings.c index c0e01030..b5f31901 100644 --- a/src/bindings.c +++ b/src/bindings.c @@ -27,7 +27,7 @@ const char *DEFAULT_BINDING_MODE = "default"; * the list of modes. * */ -static struct Mode *mode_from_name(const char *name) { +static struct Mode *mode_from_name(const char *name, bool pango_markup) { struct Mode *mode; /* Try to find the mode in the list of modes and return it */ @@ -39,6 +39,7 @@ static struct Mode *mode_from_name(const char *name) { /* If the mode was not found, create a new one */ mode = scalloc(1, sizeof(struct Mode)); mode->name = sstrdup(name); + mode->pango_markup = pango_markup; mode->bindings = scalloc(1, sizeof(struct bindings_head)); TAILQ_INIT(mode->bindings); SLIST_INSERT_HEAD(&modes, mode, modes); @@ -54,7 +55,7 @@ static struct Mode *mode_from_name(const char *name) { */ Binding *configure_binding(const char *bindtype, const char *modifiers, const char *input_code, const char *release, const char *border, const char *whole_window, - const char *command, const char *modename) { + const char *command, const char *modename, bool pango_markup) { Binding *new_binding = scalloc(1, sizeof(Binding)); DLOG("bindtype %s, modifiers %s, input code %s, release %s\n", bindtype, modifiers, input_code, release); new_binding->release = (release != NULL ? B_UPON_KEYRELEASE : B_UPON_KEYPRESS); @@ -91,7 +92,7 @@ Binding *configure_binding(const char *bindtype, const char *modifiers, const ch if (group_bits_set > 1) ELOG("Keybinding has more than one Group specified, but your X server is always in precisely one group. The keybinding can never trigger.\n"); - struct Mode *mode = mode_from_name(modename); + struct Mode *mode = mode_from_name(modename, pango_markup); TAILQ_INSERT_TAIL(mode->bindings, new_binding, bindings); return new_binding; @@ -437,7 +438,8 @@ void switch_mode(const char *new_mode) { grab_all_keys(conn); char *event_msg; - sasprintf(&event_msg, "{\"change\":\"%s\"}", mode->name); + sasprintf(&event_msg, "{\"change\":\"%s\", \"pango_markup\":%s}", + mode->name, (mode->pango_markup ? "true" : "false")); ipc_send_event("mode", I3_IPC_EVENT_MODE, event_msg); FREE(event_msg); diff --git a/src/config_directives.c b/src/config_directives.c index cd0432fe..99b70db8 100644 --- a/src/config_directives.c +++ b/src/config_directives.c @@ -108,7 +108,7 @@ CFGFUN(font, const char *font) { } CFGFUN(binding, const char *bindtype, const char *modifiers, const char *key, const char *release, const char *border, const char *whole_window, const char *command) { - configure_binding(bindtype, modifiers, key, release, border, whole_window, command, DEFAULT_BINDING_MODE); + configure_binding(bindtype, modifiers, key, release, border, whole_window, command, DEFAULT_BINDING_MODE, false); } /******************************************************************************* @@ -116,12 +116,13 @@ CFGFUN(binding, const char *bindtype, const char *modifiers, const char *key, co ******************************************************************************/ static char *current_mode; +static bool current_mode_pango_markup; CFGFUN(mode_binding, const char *bindtype, const char *modifiers, const char *key, const char *release, const char *border, const char *whole_window, const char *command) { - configure_binding(bindtype, modifiers, key, release, border, whole_window, command, current_mode); + configure_binding(bindtype, modifiers, key, release, border, whole_window, command, current_mode, current_mode_pango_markup); } -CFGFUN(enter_mode, const char *modename) { +CFGFUN(enter_mode, const char *pango_markup, const char *modename) { if (strcasecmp(modename, DEFAULT_BINDING_MODE) == 0) { ELOG("You cannot use the name %s for your mode\n", DEFAULT_BINDING_MODE); exit(1); @@ -129,6 +130,7 @@ CFGFUN(enter_mode, const char *modename) { DLOG("\t now in mode %s\n", modename); FREE(current_mode); current_mode = sstrdup(modename); + current_mode_pango_markup = (pango_markup != NULL); } CFGFUN(exec, const char *exectype, const char *no_startup_id, const char *command) { diff --git a/testcases/t/201-config-parser.t b/testcases/t/201-config-parser.t index a2b0a3a9..7b3a181e 100644 --- a/testcases/t/201-config-parser.t +++ b/testcases/t/201-config-parser.t @@ -53,7 +53,7 @@ mode "meh" { EOT my $expected = <<'EOT'; -cfg_enter_mode(meh) +cfg_enter_mode((null), meh) cfg_mode_binding(bindsym, Mod1,Shift, x, (null), (null), (null), resize grow) cfg_mode_binding(bindcode, Mod1, 44, (null), (null), (null), resize shrink) cfg_mode_binding(bindsym, Mod1, x, --release, (null), (null), exec foo) @@ -627,7 +627,7 @@ mode "yo" { EOT $expected = <<'EOT'; -cfg_enter_mode(yo) +cfg_enter_mode((null), yo) cfg_mode_binding(bindsym, (null), x, (null), (null), (null), resize shrink left) ERROR: CONFIG: Expected one of these tokens: , '#', 'set', 'bindsym', 'bindcode', 'bind', '}' ERROR: CONFIG: (in file ) From fec61791e1b26ecb7fedcd86c0c4b68634dd4169 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ingo=20B=C3=BCrk?= Date: Tue, 13 Oct 2015 09:29:50 +0200 Subject: [PATCH 025/187] Rename is_markup to pango_markup. --- i3bar/include/common.h | 2 +- i3bar/src/child.c | 8 ++++---- include/libi3.h | 2 +- libi3/font.c | 8 ++++---- libi3/string.c | 14 +++++++------- src/window.c | 10 +++++----- 6 files changed, 22 insertions(+), 22 deletions(-) diff --git a/i3bar/include/common.h b/i3bar/include/common.h index 50d1c7b9..f0b53814 100644 --- a/i3bar/include/common.h +++ b/i3bar/include/common.h @@ -49,7 +49,7 @@ struct status_block { bool urgent; bool no_separator; - bool is_markup; + bool pango_markup; /* The amount of pixels necessary to render a separater after the block. */ uint32_t sep_block_width; diff --git a/i3bar/src/child.c b/i3bar/src/child.c index cfc96d5f..e73a2920 100644 --- a/i3bar/src/child.c +++ b/i3bar/src/child.c @@ -206,7 +206,7 @@ static int stdin_string(void *context, const unsigned char *val, size_t len) { return 1; } if (strcasecmp(ctx->last_map_key, "markup") == 0) { - ctx->block.is_markup = (len == strlen("pango") && !strncasecmp((const char *)val, "pango", strlen("pango"))); + ctx->block.pango_markup = (len == strlen("pango") && !strncasecmp((const char *)val, "pango", strlen("pango"))); return 1; } if (strcasecmp(ctx->last_map_key, "align") == 0) { @@ -275,15 +275,15 @@ static int stdin_end_map(void *context) { if (new_block->min_width_str) { i3String *text = i3string_from_utf8(new_block->min_width_str); - i3string_set_markup(text, new_block->is_markup); + i3string_set_markup(text, new_block->pango_markup); new_block->min_width = (uint32_t)predict_text_width(text); i3string_free(text); } - i3string_set_markup(new_block->full_text, new_block->is_markup); + i3string_set_markup(new_block->full_text, new_block->pango_markup); if (new_block->short_text != NULL) - i3string_set_markup(new_block->short_text, new_block->is_markup); + i3string_set_markup(new_block->short_text, new_block->pango_markup); TAILQ_INSERT_TAIL(&statusline_buffer, new_block, blocks); return 1; diff --git a/include/libi3.h b/include/libi3.h index c1e109ef..75d3639b 100644 --- a/include/libi3.h +++ b/include/libi3.h @@ -243,7 +243,7 @@ bool i3string_is_markup(i3String *str); /** * Set whether the i3String should use Pango markup. */ -void i3string_set_markup(i3String *str, bool is_markup); +void i3string_set_markup(i3String *str, bool pango_markup); /** * Escape pango markup characters in the given string. diff --git a/libi3/font.c b/libi3/font.c index 9e808a89..c90f8be0 100644 --- a/libi3/font.c +++ b/libi3/font.c @@ -103,7 +103,7 @@ static bool load_pango_font(i3Font *font, const char *desc) { */ static void draw_text_pango(const char *text, size_t text_len, xcb_drawable_t drawable, xcb_visualtype_t *visual, int x, int y, - int max_width, bool is_markup) { + int max_width, bool pango_markup) { /* Create the Pango layout */ /* root_visual_type is cached in load_pango_font */ cairo_surface_t *surface = cairo_xcb_surface_create(conn, drawable, @@ -117,7 +117,7 @@ static void draw_text_pango(const char *text, size_t text_len, pango_layout_set_wrap(layout, PANGO_WRAP_CHAR); pango_layout_set_ellipsize(layout, PANGO_ELLIPSIZE_END); - if (is_markup) + if (pango_markup) pango_layout_set_markup(layout, text, text_len); else pango_layout_set_text(layout, text, text_len); @@ -143,7 +143,7 @@ static void draw_text_pango(const char *text, size_t text_len, * Calculate the text width using Pango rendering. * */ -static int predict_text_width_pango(const char *text, size_t text_len, bool is_markup) { +static int predict_text_width_pango(const char *text, size_t text_len, bool pango_markup) { /* Create a dummy Pango layout */ /* root_visual_type is cached in load_pango_font */ cairo_surface_t *surface = cairo_xcb_surface_create(conn, root_screen->root, root_visual_type, 1, 1); @@ -154,7 +154,7 @@ static int predict_text_width_pango(const char *text, size_t text_len, bool is_m gint width; pango_layout_set_font_description(layout, savedFont->specific.pango_desc); - if (is_markup) + if (pango_markup) pango_layout_set_markup(layout, text, text_len); else pango_layout_set_text(layout, text, text_len); diff --git a/libi3/string.c b/libi3/string.c index 7741fde0..328b41c0 100644 --- a/libi3/string.c +++ b/libi3/string.c @@ -24,7 +24,7 @@ struct _i3String { xcb_char2b_t *ucs2; size_t num_glyphs; size_t num_bytes; - bool is_markup; + bool pango_markup; }; /* @@ -52,7 +52,7 @@ i3String *i3string_from_markup(const char *from_markup) { i3String *str = i3string_from_utf8(from_markup); /* Set the markup flag */ - str->is_markup = true; + str->pango_markup = true; return str; } @@ -86,7 +86,7 @@ i3String *i3string_from_markup_with_length(const char *from_markup, size_t num_b i3String *str = i3string_from_utf8_with_length(from_markup, num_bytes); /* set the markup flag */ - str->is_markup = true; + str->pango_markup = true; return str; } @@ -118,7 +118,7 @@ i3String *i3string_from_ucs2(const xcb_char2b_t *from_ucs2, size_t num_glyphs) { */ i3String *i3string_copy(i3String *str) { i3String *copy = i3string_from_utf8(i3string_as_utf8(str)); - copy->is_markup = str->is_markup; + copy->pango_markup = str->pango_markup; return copy; } @@ -178,14 +178,14 @@ size_t i3string_get_num_bytes(i3String *str) { * Whether the given i3String is in Pango markup. */ bool i3string_is_markup(i3String *str) { - return str->is_markup; + return str->pango_markup; } /* * Set whether the i3String should use Pango markup. */ -void i3string_set_markup(i3String *str, bool is_markup) { - str->is_markup = is_markup; +void i3string_set_markup(i3String *str, bool pango_markup) { + str->pango_markup = pango_markup; } /* diff --git a/src/window.c b/src/window.c index 5898333f..eba15632 100644 --- a/src/window.c +++ b/src/window.c @@ -342,7 +342,7 @@ void window_update_motif_hints(i3Window *win, xcb_get_property_reply_t *prop, bo i3String *window_parse_title_format(i3Window *win) { /* We need to ensure that we only escape the window title if pango * is used by the current font. */ - const bool is_markup = font_is_pango(); + const bool pango_markup = font_is_pango(); char *format = win->title_format; if (format == NULL) @@ -359,19 +359,19 @@ i3String *window_parse_title_format(i3Window *win) { for (char *walk = format; *walk != '\0'; walk++) { if (STARTS_WITH(walk, "%title")) { if (escaped_title == NULL) - escaped_title = win->name == NULL ? "" : i3string_as_utf8(is_markup ? i3string_escape_markup(win->name) : win->name); + escaped_title = win->name == NULL ? "" : i3string_as_utf8(pango_markup ? i3string_escape_markup(win->name) : win->name); buffer_len = buffer_len - strlen("%title") + strlen(escaped_title); walk += strlen("%title") - 1; } else if (STARTS_WITH(walk, "%class")) { if (escaped_class == NULL) - escaped_class = is_markup ? g_markup_escape_text(win->class_class, -1) : win->class_class; + escaped_class = pango_markup ? g_markup_escape_text(win->class_class, -1) : win->class_class; buffer_len = buffer_len - strlen("%class") + strlen(escaped_class); walk += strlen("%class") - 1; } else if (STARTS_WITH(walk, "%instance")) { if (escaped_instance == NULL) - escaped_instance = is_markup ? g_markup_escape_text(win->class_instance, -1) : win->class_instance; + escaped_instance = pango_markup ? g_markup_escape_text(win->class_instance, -1) : win->class_instance; buffer_len = buffer_len - strlen("%instance") + strlen(escaped_instance); walk += strlen("%instance") - 1; @@ -403,6 +403,6 @@ i3String *window_parse_title_format(i3Window *win) { *outwalk = '\0'; i3String *formatted = i3string_from_utf8(buffer); - i3string_set_markup(formatted, is_markup); + i3string_set_markup(formatted, pango_markup); return formatted; } From 54dbbe2f06134b32c03af38db7ec0aa7d3d0b74e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ingo=20B=C3=BCrk?= Date: Tue, 13 Oct 2015 11:08:10 +0200 Subject: [PATCH 026/187] Flush cairo surface after drawing text. This is necessary to avoid a bug where a cairo assertion fails because no snapshot is available. fixes #1989 --- i3bar/src/cairo_util.c | 5 +++++ libi3/font.c | 2 ++ 2 files changed, 7 insertions(+) diff --git a/i3bar/src/cairo_util.c b/i3bar/src/cairo_util.c index 52181aee..6a2ed0ac 100644 --- a/i3bar/src/cairo_util.c +++ b/i3bar/src/cairo_util.c @@ -78,9 +78,13 @@ void cairo_set_source_color(surface_t *surface, color_t color) { * */ void cairo_draw_text(i3String *text, surface_t *surface, color_t fg_color, color_t bg_color, int x, int y, int max_width) { + /* Flush any changes before we draw the text as this might use XCB directly. */ + cairo_surface_flush(surface->surface); + set_font_colors(surface->gc, fg_color.colorpixel, bg_color.colorpixel); draw_text(text, surface->id, surface->gc, visual_type, x, y, max_width); + /* Notify cairo that we (possibly) used another way to draw on the surface. */ cairo_surface_mark_dirty(surface->surface); } @@ -130,6 +134,7 @@ void cairo_copy_surface(surface_t *src, surface_t *dest, double src_x, double sr /* Make sure we flush the surface for any text drawing operations that could follow. * Since we support drawing text via XCB, we need this. */ + cairo_surface_flush(src->surface); cairo_surface_flush(dest->surface); cairo_restore(dest->cr); } diff --git a/libi3/font.c b/libi3/font.c index 9e808a89..b578bf8b 100644 --- a/libi3/font.c +++ b/libi3/font.c @@ -133,6 +133,8 @@ static void draw_text_pango(const char *text, size_t text_len, cairo_move_to(cr, x, y - yoffset); pango_cairo_show_layout(cr, layout); + cairo_surface_flush(surface); + /* Free resources */ g_object_unref(layout); cairo_destroy(cr); From 8178910f168c927f1f3b4c94531fd32f38743fb0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ingo=20B=C3=BCrk?= Date: Wed, 14 Oct 2015 18:57:16 +0200 Subject: [PATCH 027/187] Introduce a macro to flush a cairo surface twice. Flushing the surface twice is necessary due to a cairo bug. For context, refer to #1989 and https://bugs.freedesktop.org/show_bug.cgi?id=92455. --- i3bar/include/cairo_util.h | 8 ++++++++ i3bar/src/cairo_util.c | 8 ++++---- i3bar/src/xcb.c | 4 ++-- libi3/font.c | 3 +++ 4 files changed, 17 insertions(+), 6 deletions(-) diff --git a/i3bar/include/cairo_util.h b/i3bar/include/cairo_util.h index 37eaa6e2..3e4ebec4 100644 --- a/i3bar/include/cairo_util.h +++ b/i3bar/include/cairo_util.h @@ -10,6 +10,14 @@ #include +/* We need to flush cairo surfaces twice to avoid an assertion bug. See #1989 + * and https://bugs.freedesktop.org/show_bug.cgi?id=92455. */ +#define CAIRO_SURFACE_FLUSH(surface) \ + do { \ + cairo_surface_flush(surface); \ + cairo_surface_flush(surface); \ + } while (0) + /* Represents a color split by color channel. */ typedef struct color_t { double red; diff --git a/i3bar/src/cairo_util.c b/i3bar/src/cairo_util.c index 6a2ed0ac..98c129b9 100644 --- a/i3bar/src/cairo_util.c +++ b/i3bar/src/cairo_util.c @@ -79,7 +79,7 @@ void cairo_set_source_color(surface_t *surface, color_t color) { */ void cairo_draw_text(i3String *text, surface_t *surface, color_t fg_color, color_t bg_color, int x, int y, int max_width) { /* Flush any changes before we draw the text as this might use XCB directly. */ - cairo_surface_flush(surface->surface); + CAIRO_SURFACE_FLUSH(surface->surface); set_font_colors(surface->gc, fg_color.colorpixel, bg_color.colorpixel); draw_text(text, surface->id, surface->gc, visual_type, x, y, max_width); @@ -109,7 +109,7 @@ void cairo_draw_rectangle(surface_t *surface, color_t color, double x, double y, /* Make sure we flush the surface for any text drawing operations that could follow. * Since we support drawing text via XCB, we need this. */ - cairo_surface_flush(surface->surface); + CAIRO_SURFACE_FLUSH(surface->surface); cairo_restore(surface->cr); } @@ -134,7 +134,7 @@ void cairo_copy_surface(surface_t *src, surface_t *dest, double src_x, double sr /* Make sure we flush the surface for any text drawing operations that could follow. * Since we support drawing text via XCB, we need this. */ - cairo_surface_flush(src->surface); - cairo_surface_flush(dest->surface); + CAIRO_SURFACE_FLUSH(src->surface); + CAIRO_SURFACE_FLUSH(dest->surface); cairo_restore(dest->cr); } diff --git a/i3bar/src/xcb.c b/i3bar/src/xcb.c index ac9b50f9..aa7b816c 100644 --- a/i3bar/src/xcb.c +++ b/i3bar/src/xcb.c @@ -248,7 +248,7 @@ void refresh_statusline(bool use_short_text) { cairo_set_source_color(&statusline_surface, colors.bar_bg); cairo_set_operator(statusline_surface.cr, CAIRO_OPERATOR_SOURCE); cairo_paint(statusline_surface.cr); - cairo_surface_flush(statusline_surface.surface); + CAIRO_SURFACE_FLUSH(statusline_surface.surface); cairo_restore(statusline_surface.cr); /* Draw the text of each block. */ @@ -1801,7 +1801,7 @@ void draw_bars(bool unhide) { cairo_set_source_color(&(outputs_walk->buffer), colors.bar_bg); cairo_set_operator(outputs_walk->buffer.cr, CAIRO_OPERATOR_SOURCE); cairo_paint(outputs_walk->buffer.cr); - cairo_surface_flush(outputs_walk->buffer.surface); + CAIRO_SURFACE_FLUSH(outputs_walk->buffer.surface); cairo_restore(outputs_walk->buffer.cr); if (!config.disable_ws) { diff --git a/libi3/font.c b/libi3/font.c index b578bf8b..c1e95a9e 100644 --- a/libi3/font.c +++ b/libi3/font.c @@ -133,6 +133,9 @@ static void draw_text_pango(const char *text, size_t text_len, cairo_move_to(cr, x, y - yoffset); pango_cairo_show_layout(cr, layout); + /* We need to flush cairo surfaces twice to avoid an assertion bug. See #1989 + * and https://bugs.freedesktop.org/show_bug.cgi?id=92455. */ + cairo_surface_flush(surface); cairo_surface_flush(surface); /* Free resources */ From 46bcc55f6f67cae6c52f2e93e549d803761a8e51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ingo=20B=C3=BCrk?= Date: Wed, 14 Oct 2015 19:03:05 +0200 Subject: [PATCH 028/187] Extract cairo_clear_surface. This commit refactors the i3bar drawing code to also define an abstraction function for clearing a surface. This is needed to fully abstract i3bar/xcb.c's drawing code so that we can introduce a switch to easily exchange the underlying drawing mechanism. --- i3bar/include/cairo_util.h | 7 +++++++ i3bar/src/cairo_util.c | 23 +++++++++++++++++++++++ i3bar/src/xcb.c | 14 ++------------ 3 files changed, 32 insertions(+), 12 deletions(-) diff --git a/i3bar/include/cairo_util.h b/i3bar/include/cairo_util.h index 3e4ebec4..fe2d06ff 100644 --- a/i3bar/include/cairo_util.h +++ b/i3bar/include/cairo_util.h @@ -89,6 +89,13 @@ void cairo_draw_text(i3String *text, surface_t *surface, color_t fg_color, color */ void cairo_draw_rectangle(surface_t *surface, color_t color, double x, double y, double w, double h); +/** + * Clears a surface with the given color. + * Note that the drawing is done using CAIRO_OPERATOR_SOURCE. + * + */ +void cairo_clear_surface(surface_t *surface, color_t color); + /** * Copies a surface onto another surface. * Note that the drawing is done using CAIRO_OPERATOR_SOURCE. diff --git a/i3bar/src/cairo_util.c b/i3bar/src/cairo_util.c index 98c129b9..2cec76d6 100644 --- a/i3bar/src/cairo_util.c +++ b/i3bar/src/cairo_util.c @@ -114,6 +114,29 @@ void cairo_draw_rectangle(surface_t *surface, color_t color, double x, double y, cairo_restore(surface->cr); } +/** + * Clears a surface with the given color. + * Note that the drawing is done using CAIRO_OPERATOR_SOURCE. + * + */ +void cairo_clear_surface(surface_t *surface, color_t color) { + cairo_save(surface->cr); + + /* Using the SOURCE operator will copy both color and alpha information directly + * onto the surface rather than blending it. This is a bit more efficient and + * allows better color control for the user when using opacity. */ + cairo_set_operator(surface->cr, CAIRO_OPERATOR_SOURCE); + cairo_set_source_color(surface, color); + + cairo_paint(surface->cr); + + /* Make sure we flush the surface for any text drawing operations that could follow. + * Since we support drawing text via XCB, we need this. */ + CAIRO_SURFACE_FLUSH(surface->surface); + + cairo_restore(surface->cr); +} + /** * Copies a surface onto another surface. * Note that the drawing is done using CAIRO_OPERATOR_SOURCE. diff --git a/i3bar/src/xcb.c b/i3bar/src/xcb.c index aa7b816c..3b3836c1 100644 --- a/i3bar/src/xcb.c +++ b/i3bar/src/xcb.c @@ -244,12 +244,7 @@ void refresh_statusline(bool use_short_text) { realloc_sl_buffer(); /* Clear the statusline pixmap. */ - cairo_save(statusline_surface.cr); - cairo_set_source_color(&statusline_surface, colors.bar_bg); - cairo_set_operator(statusline_surface.cr, CAIRO_OPERATOR_SOURCE); - cairo_paint(statusline_surface.cr); - CAIRO_SURFACE_FLUSH(statusline_surface.surface); - cairo_restore(statusline_surface.cr); + cairo_clear_surface(&statusline_surface, colors.bar_bg); /* Draw the text of each block. */ uint32_t x = 0; @@ -1797,12 +1792,7 @@ void draw_bars(bool unhide) { } /* First things first: clear the backbuffer */ - cairo_save(outputs_walk->buffer.cr); - cairo_set_source_color(&(outputs_walk->buffer), colors.bar_bg); - cairo_set_operator(outputs_walk->buffer.cr, CAIRO_OPERATOR_SOURCE); - cairo_paint(outputs_walk->buffer.cr); - CAIRO_SURFACE_FLUSH(outputs_walk->buffer.surface); - cairo_restore(outputs_walk->buffer.cr); + cairo_clear_surface(&(outputs_walk->buffer), colors.bar_bg); if (!config.disable_ws) { i3_ws *ws_walk; From 02468296c42cf3d0fd972596f4ebeebc8f37741a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ingo=20B=C3=BCrk?= Date: Wed, 14 Oct 2015 20:49:52 +0200 Subject: [PATCH 029/187] Introduce switch for the drawing backend This commit restores the old XCB drawing code paths while keeping the cairo drawing available via a compile-time switch (I3BAR_CAIRO). This is necessary as cairo currently has a bug that breaks i3bar for users without the RENDER extension, which might be the case, e.g., for VNC users. For more context, see #1989 and the discussions about its fix. Once the cairo fix is available in a stable release, i3 can depend on that version and remove the XCB drawing code paths. fixes #1989 --- i3bar/include/common.h | 2 +- i3bar/include/{cairo_util.h => draw_util.h} | 42 +++---- i3bar/include/outputs.h | 2 +- i3bar/src/{cairo_util.c => draw_util.c} | 74 ++++++++--- i3bar/src/xcb.c | 129 ++++++++++---------- libi3/font.c | 5 - 6 files changed, 142 insertions(+), 112 deletions(-) rename i3bar/include/{cairo_util.h => draw_util.h} (59%) rename i3bar/src/{cairo_util.c => draw_util.c} (66%) diff --git a/i3bar/include/common.h b/i3bar/include/common.h index 50d1c7b9..131412c1 100644 --- a/i3bar/include/common.h +++ b/i3bar/include/common.h @@ -80,4 +80,4 @@ TAILQ_HEAD(statusline_head, status_block) statusline_head; #include "config.h" #include "libi3.h" #include "parse_json_header.h" -#include "cairo_util.h" +#include "draw_util.h" diff --git a/i3bar/include/cairo_util.h b/i3bar/include/draw_util.h similarity index 59% rename from i3bar/include/cairo_util.h rename to i3bar/include/draw_util.h index fe2d06ff..9bac72ca 100644 --- a/i3bar/include/cairo_util.h +++ b/i3bar/include/draw_util.h @@ -3,13 +3,16 @@ * * © 2015 Ingo Bürk and contributors (see also: LICENSE) * - * cairo_util.h: Utility for operations using cairo. + * draw.h: Utility for drawing. * */ #pragma once +#ifdef I3BAR_CAIRO #include +#endif +#ifdef I3BAR_CAIRO /* We need to flush cairo surfaces twice to avoid an assertion bug. See #1989 * and https://bugs.freedesktop.org/show_bug.cgi?id=92455. */ #define CAIRO_SURFACE_FLUSH(surface) \ @@ -17,6 +20,7 @@ cairo_surface_flush(surface); \ cairo_surface_flush(surface); \ } while (0) +#endif /* Represents a color split by color channel. */ typedef struct color_t { @@ -34,43 +38,40 @@ typedef struct surface_t { /* The drawable which is being represented. */ xcb_drawable_t id; - // TODO remove this once i3 uses solely cairo for drawing operations - /* A classic XCB graphics context. This should not be used for - * drawing operations. */ + /* A classic XCB graphics context. */ xcb_gcontext_t gc; + int width; + int height; + +#ifdef I3BAR_CAIRO /* A cairo surface representing the drawable. */ cairo_surface_t *surface; /* The cairo object representing the drawale. In general, * this is what one should use for any drawing operation. */ cairo_t *cr; +#endif } surface_t; /** - * Initialize the cairo surface to represent the given drawable. + * Initialize the surface to represent the given drawable. * */ -void cairo_surface_init(surface_t *surface, xcb_drawable_t drawable, int width, int height); +void draw_util_surface_init(surface_t *surface, xcb_drawable_t drawable, int width, int height); /** * Destroys the surface. * */ -void cairo_surface_free(surface_t *surface); +void draw_util_surface_free(surface_t *surface); /** * Parses the given color in hex format to an internal color representation. * Note that the input must begin with a hash sign, e.g., "#3fbc59". * */ -color_t cairo_hex_to_color(const char *color); - -/** - * Set the given color as the source color on the surface. - * - */ -void cairo_set_source_color(surface_t *surface, color_t color); +color_t draw_util_hex_to_color(const char *color); /** * Draw the given text using libi3. @@ -78,28 +79,25 @@ void cairo_set_source_color(surface_t *surface, color_t color); * drawing are used. This will be the case when using XCB to draw text. * */ -void cairo_draw_text(i3String *text, surface_t *surface, color_t fg_color, color_t bg_color, int x, int y, int max_width); +void draw_util_text(i3String *text, surface_t *surface, color_t fg_color, color_t bg_color, int x, int y, int max_width); /** * Draws a filled rectangle. * This function is a convenience wrapper and takes care of flushing the * surface as well as restoring the cairo state. - * Note that the drawing is done using CAIRO_OPERATOR_SOURCE. * */ -void cairo_draw_rectangle(surface_t *surface, color_t color, double x, double y, double w, double h); +void draw_util_rectangle(surface_t *surface, color_t color, double x, double y, double w, double h); /** * Clears a surface with the given color. - * Note that the drawing is done using CAIRO_OPERATOR_SOURCE. * */ -void cairo_clear_surface(surface_t *surface, color_t color); +void draw_util_clear_surface(surface_t *surface, color_t color); /** * Copies a surface onto another surface. - * Note that the drawing is done using CAIRO_OPERATOR_SOURCE. * */ -void cairo_copy_surface(surface_t *src, surface_t *dest, double src_x, double src_y, - double dest_x, double dest_y, double dest_w, double dest_h); +void draw_util_copy_surface(surface_t *src, surface_t *dest, double src_x, double src_y, + double dest_x, double dest_y, double width, double height); diff --git a/i3bar/include/outputs.h b/i3bar/include/outputs.h index 7ad3ed50..87cc023e 100644 --- a/i3bar/include/outputs.h +++ b/i3bar/include/outputs.h @@ -13,7 +13,7 @@ #include #include "common.h" -#include "cairo_util.h" +#include "draw_util.h" typedef struct i3_output i3_output; diff --git a/i3bar/src/cairo_util.c b/i3bar/src/draw_util.c similarity index 66% rename from i3bar/src/cairo_util.c rename to i3bar/src/draw_util.c index 2cec76d6..e1facf39 100644 --- a/i3bar/src/cairo_util.c +++ b/i3bar/src/draw_util.c @@ -3,7 +3,7 @@ * * © 2015 Ingo Bürk and contributors (see also: LICENSE) * - * cairo_util.c: Utility for operations using cairo. + * draw.c: Utility for drawing. * */ #include @@ -11,7 +11,9 @@ #include #include #include +#ifdef I3BAR_CAIRO #include +#endif #include "common.h" #include "libi3.h" @@ -19,30 +21,39 @@ xcb_connection_t *xcb_connection; xcb_visualtype_t *visual_type; +/* Forward declarations */ +static void draw_util_set_source_color(surface_t *surface, color_t color); + /* - * Initialize the cairo surface to represent the given drawable. + * Initialize the surface to represent the given drawable. * */ -void cairo_surface_init(surface_t *surface, xcb_drawable_t drawable, int width, int height) { +void draw_util_surface_init(surface_t *surface, xcb_drawable_t drawable, int width, int height) { surface->id = drawable; + surface->width = width; + surface->height = height; surface->gc = xcb_generate_id(xcb_connection); xcb_void_cookie_t gc_cookie = xcb_create_gc_checked(xcb_connection, surface->gc, surface->id, 0, NULL); if (xcb_request_failed(gc_cookie, "Could not create graphical context")) exit(EXIT_FAILURE); +#ifdef I3BAR_CAIRO surface->surface = cairo_xcb_surface_create(xcb_connection, surface->id, visual_type, width, height); surface->cr = cairo_create(surface->surface); +#endif } /* * Destroys the surface. * */ -void cairo_surface_free(surface_t *surface) { +void draw_util_surface_free(surface_t *surface) { xcb_free_gc(xcb_connection, surface->gc); +#ifdef I3BAR_CAIRO cairo_surface_destroy(surface->surface); cairo_destroy(surface->cr); +#endif } /* @@ -50,7 +61,7 @@ void cairo_surface_free(surface_t *surface) { * Note that the input must begin with a hash sign, e.g., "#3fbc59". * */ -color_t cairo_hex_to_color(const char *color) { +color_t draw_util_hex_to_color(const char *color) { char groups[3][3] = { {color[1], color[2], '\0'}, {color[3], color[4], '\0'}, @@ -67,8 +78,14 @@ color_t cairo_hex_to_color(const char *color) { * Set the given color as the source color on the surface. * */ -void cairo_set_source_color(surface_t *surface, color_t color) { +static void draw_util_set_source_color(surface_t *surface, color_t color) { +#ifdef I3BAR_CAIRO cairo_set_source_rgb(surface->cr, color.red, color.green, color.blue); +#else + uint32_t colorpixel = color.colorpixel; + xcb_change_gc(xcb_connection, surface->gc, XCB_GC_FOREGROUND | XCB_GC_BACKGROUND, + (uint32_t[]){colorpixel, colorpixel}); +#endif } /** @@ -77,32 +94,36 @@ void cairo_set_source_color(surface_t *surface, color_t color) { * drawing are used. This will be the case when using XCB to draw text. * */ -void cairo_draw_text(i3String *text, surface_t *surface, color_t fg_color, color_t bg_color, int x, int y, int max_width) { +void draw_util_text(i3String *text, surface_t *surface, color_t fg_color, color_t bg_color, int x, int y, int max_width) { +#ifdef I3BAR_CAIRO /* Flush any changes before we draw the text as this might use XCB directly. */ CAIRO_SURFACE_FLUSH(surface->surface); +#endif set_font_colors(surface->gc, fg_color.colorpixel, bg_color.colorpixel); draw_text(text, surface->id, surface->gc, visual_type, x, y, max_width); +#ifdef I3BAR_CAIRO /* Notify cairo that we (possibly) used another way to draw on the surface. */ cairo_surface_mark_dirty(surface->surface); +#endif } /** * Draws a filled rectangle. * This function is a convenience wrapper and takes care of flushing the * surface as well as restoring the cairo state. - * Note that the drawing is done using CAIRO_OPERATOR_SOURCE. * */ -void cairo_draw_rectangle(surface_t *surface, color_t color, double x, double y, double w, double h) { +void draw_util_rectangle(surface_t *surface, color_t color, double x, double y, double w, double h) { +#ifdef I3BAR_CAIRO cairo_save(surface->cr); /* Using the SOURCE operator will copy both color and alpha information directly * onto the surface rather than blending it. This is a bit more efficient and * allows better color control for the user when using opacity. */ cairo_set_operator(surface->cr, CAIRO_OPERATOR_SOURCE); - cairo_set_source_color(surface, color); + draw_util_set_source_color(surface, color); cairo_rectangle(surface->cr, x, y, w, h); cairo_fill(surface->cr); @@ -112,21 +133,27 @@ void cairo_draw_rectangle(surface_t *surface, color_t color, double x, double y, CAIRO_SURFACE_FLUSH(surface->surface); cairo_restore(surface->cr); +#else + draw_util_set_source_color(surface, color); + + xcb_rectangle_t rect = {x, y, w, h}; + xcb_poly_fill_rectangle(xcb_connection, surface->id, surface->gc, 1, &rect); +#endif } /** * Clears a surface with the given color. - * Note that the drawing is done using CAIRO_OPERATOR_SOURCE. * */ -void cairo_clear_surface(surface_t *surface, color_t color) { +void draw_util_clear_surface(surface_t *surface, color_t color) { +#ifdef I3BAR_CAIRO cairo_save(surface->cr); /* Using the SOURCE operator will copy both color and alpha information directly * onto the surface rather than blending it. This is a bit more efficient and * allows better color control for the user when using opacity. */ cairo_set_operator(surface->cr, CAIRO_OPERATOR_SOURCE); - cairo_set_source_color(surface, color); + draw_util_set_source_color(surface, color); cairo_paint(surface->cr); @@ -135,29 +162,40 @@ void cairo_clear_surface(surface_t *surface, color_t color) { CAIRO_SURFACE_FLUSH(surface->surface); cairo_restore(surface->cr); +#else + draw_util_set_source_color(surface, color); + + xcb_rectangle_t rect = {0, 0, surface->width, surface->height}; + xcb_poly_fill_rectangle(xcb_connection, surface->id, surface->gc, 1, &rect); +#endif } /** * Copies a surface onto another surface. - * Note that the drawing is done using CAIRO_OPERATOR_SOURCE. * */ -void cairo_copy_surface(surface_t *src, surface_t *dest, double src_x, double src_y, - double dest_x, double dest_y, double dest_w, double dest_h) { +void draw_util_copy_surface(surface_t *src, surface_t *dest, double src_x, double src_y, + double dest_x, double dest_y, double width, double height) { +#ifdef I3BAR_CAIRO cairo_save(dest->cr); /* Using the SOURCE operator will copy both color and alpha information directly * onto the surface rather than blending it. This is a bit more efficient and * allows better color control for the user when using opacity. */ cairo_set_operator(dest->cr, CAIRO_OPERATOR_SOURCE); - cairo_set_source_surface(dest->cr, src->surface, src_x, src_y); + cairo_set_source_surface(dest->cr, src->surface, dest_x - src_x, src_y); - cairo_rectangle(dest->cr, dest_x, dest_y, dest_w, dest_h); + cairo_rectangle(dest->cr, dest_x, dest_y, width, height); cairo_fill(dest->cr); /* Make sure we flush the surface for any text drawing operations that could follow. * Since we support drawing text via XCB, we need this. */ CAIRO_SURFACE_FLUSH(src->surface); CAIRO_SURFACE_FLUSH(dest->surface); + cairo_restore(dest->cr); +#else + xcb_copy_area(xcb_connection, src->id, dest->id, dest->gc, (int16_t)src_x, (int16_t)src_y, + (int16_t)dest_x, (int16_t)dest_y, (uint16_t)width, (uint16_t)height); +#endif } diff --git a/i3bar/src/xcb.c b/i3bar/src/xcb.c index 3b3836c1..efef7365 100644 --- a/i3bar/src/xcb.c +++ b/i3bar/src/xcb.c @@ -30,7 +30,6 @@ #include #include #include -#include #include "common.h" #include "libi3.h" @@ -174,16 +173,16 @@ static void draw_separator(uint32_t x, struct status_block *block) { uint32_t center_x = x - sep_offset; if (config.separator_symbol == NULL) { /* Draw a classic one pixel, vertical separator. */ - cairo_draw_rectangle(&statusline_surface, colors.sep_fg, - center_x, - logical_px(sep_voff_px), - logical_px(1), - bar_height - 2 * logical_px(sep_voff_px)); + draw_util_rectangle(&statusline_surface, colors.sep_fg, + center_x, + logical_px(sep_voff_px), + logical_px(1), + bar_height - 2 * logical_px(sep_voff_px)); } else { /* Draw a custom separator. */ uint32_t separator_x = MAX(x - block->sep_block_width, center_x - separator_symbol_width / 2); - cairo_draw_text(config.separator_symbol, &statusline_surface, colors.sep_fg, colors.bar_bg, - separator_x, logical_px(ws_voff_px), x - separator_x); + draw_util_text(config.separator_symbol, &statusline_surface, colors.sep_fg, colors.bar_bg, + separator_x, logical_px(ws_voff_px), x - separator_x); } } @@ -244,7 +243,7 @@ void refresh_statusline(bool use_short_text) { realloc_sl_buffer(); /* Clear the statusline pixmap. */ - cairo_clear_surface(&statusline_surface, colors.bar_bg); + draw_util_clear_surface(&statusline_surface, colors.bar_bg); /* Draw the text of each block. */ uint32_t x = 0; @@ -258,17 +257,17 @@ void refresh_statusline(bool use_short_text) { fg_color = colors.urgent_ws_fg; /* Draw the background */ - cairo_draw_rectangle(&statusline_surface, colors.urgent_ws_bg, - x - logical_px(2), - logical_px(1), - block->width + logical_px(4), - bar_height - logical_px(2)); + draw_util_rectangle(&statusline_surface, colors.urgent_ws_bg, + x - logical_px(2), + logical_px(1), + block->width + logical_px(4), + bar_height - logical_px(2)); } else { - fg_color = (block->color ? cairo_hex_to_color(block->color) : colors.bar_fg); + fg_color = (block->color ? draw_util_hex_to_color(block->color) : colors.bar_fg); } - cairo_draw_text(block->full_text, &statusline_surface, fg_color, colors.bar_bg, - x + block->x_offset, logical_px(ws_voff_px), block->width); + draw_util_text(block->full_text, &statusline_surface, fg_color, colors.bar_bg, + 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 this is not the last block, draw a separator. */ @@ -346,9 +345,9 @@ void unhide_bars(void) { * */ void init_colors(const struct xcb_color_strings_t *new_colors) { -#define PARSE_COLOR(name, def) \ - do { \ - colors.name = cairo_hex_to_color(new_colors->name ? new_colors->name : def); \ +#define PARSE_COLOR(name, def) \ + do { \ + colors.name = draw_util_hex_to_color(new_colors->name ? new_colors->name : def); \ } while (0) PARSE_COLOR(bar_fg, "#FFFFFF"); PARSE_COLOR(bar_bg, "#000000"); @@ -367,9 +366,9 @@ void init_colors(const struct xcb_color_strings_t *new_colors) { PARSE_COLOR(focus_ws_border, "#4c7899"); #undef PARSE_COLOR -#define PARSE_COLOR_FALLBACK(name, fallback) \ - do { \ - colors.name = new_colors->name ? cairo_hex_to_color(new_colors->name) : colors.fallback; \ +#define PARSE_COLOR_FALLBACK(name, fallback) \ + do { \ + colors.name = new_colors->name ? draw_util_hex_to_color(new_colors->name) : colors.fallback; \ } while (0) /* For the binding mode indicator colors, we don't hardcode a default. @@ -1128,7 +1127,7 @@ char *init_xcb_early() { xcb_root, root_screen->width_in_pixels, root_screen->height_in_pixels); - cairo_surface_init(&statusline_surface, statusline_id, root_screen->width_in_pixels, root_screen->height_in_pixels); + draw_util_surface_init(&statusline_surface, statusline_id, root_screen->width_in_pixels, root_screen->height_in_pixels); /* The various watchers to communicate with xcb */ xcb_io = smalloc(sizeof(ev_io)); @@ -1490,7 +1489,7 @@ void realloc_sl_buffer(void) { DLOG("Re-allocating statusline buffer, statusline_width = %d, root_screen->width_in_pixels = %d\n", statusline_width, root_screen->width_in_pixels); xcb_free_pixmap(xcb_connection, statusline_surface.id); - cairo_surface_free(&statusline_surface); + draw_util_surface_free(&statusline_surface); xcb_pixmap_t statusline_id = xcb_generate_id(xcb_connection); xcb_void_cookie_t sl_pm_cookie = xcb_create_pixmap_checked(xcb_connection, @@ -1499,7 +1498,7 @@ void realloc_sl_buffer(void) { xcb_root, MAX(root_screen->width_in_pixels, statusline_width), bar_height); - cairo_surface_init(&statusline_surface, statusline_id, root_screen->width_in_pixels, root_screen->height_in_pixels); + draw_util_surface_init(&statusline_surface, statusline_id, root_screen->width_in_pixels, root_screen->height_in_pixels); if (xcb_request_failed(sl_pm_cookie, "Could not allocate statusline buffer")) exit(EXIT_FAILURE); @@ -1650,8 +1649,8 @@ void reconfig_windows(bool redraw_bars) { 1, (unsigned char *)&atoms[_NET_WM_WINDOW_TYPE_DOCK]); - cairo_surface_init(&walk->bar, bar_id, walk->rect.w, bar_height); - cairo_surface_init(&walk->buffer, buffer_id, walk->rect.w, bar_height); + 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); xcb_void_cookie_t strut_cookie = config_strut_partial(walk); @@ -1731,10 +1730,10 @@ void reconfig_windows(bool redraw_bars) { walk->rect.w, bar_height); - cairo_surface_free(&(walk->bar)); - cairo_surface_free(&(walk->buffer)); - cairo_surface_init(&(walk->bar), walk->bar.id, walk->rect.w, bar_height); - cairo_surface_init(&(walk->buffer), walk->buffer.id, walk->rect.w, bar_height); + draw_util_surface_free(&(walk->bar)); + draw_util_surface_free(&(walk->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); xcb_void_cookie_t map_cookie, umap_cookie; if (redraw_bars) { @@ -1792,7 +1791,7 @@ void draw_bars(bool unhide) { } /* First things first: clear the backbuffer */ - cairo_clear_surface(&(outputs_walk->buffer), colors.bar_bg); + draw_util_clear_surface(&(outputs_walk->buffer), colors.bar_bg); if (!config.disable_ws) { i3_ws *ws_walk; @@ -1822,23 +1821,23 @@ void draw_bars(bool unhide) { } /* Draw the border of the button. */ - cairo_draw_rectangle(&(outputs_walk->buffer), border_color, - workspace_width, - logical_px(1), - 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)); + draw_util_rectangle(&(outputs_walk->buffer), border_color, + workspace_width, + logical_px(1), + 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)); /* Draw the inside of the button. */ - cairo_draw_rectangle(&(outputs_walk->buffer), bg_color, - workspace_width + logical_px(1), - 2 * logical_px(1), - ws_walk->name_width + 2 * logical_px(ws_hoff_px), - font.height + 2 * logical_px(ws_voff_px) - 4 * logical_px(1)); + draw_util_rectangle(&(outputs_walk->buffer), bg_color, + workspace_width + logical_px(1), + 2 * logical_px(1), + ws_walk->name_width + 2 * logical_px(ws_hoff_px), + font.height + 2 * logical_px(ws_voff_px) - 4 * logical_px(1)); - cairo_draw_text(ws_walk->name, &(outputs_walk->buffer), fg_color, bg_color, - workspace_width + logical_px(ws_hoff_px) + logical_px(1), - logical_px(ws_voff_px), - ws_walk->name_width); + draw_util_text(ws_walk->name, &(outputs_walk->buffer), fg_color, bg_color, + 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) @@ -1852,22 +1851,22 @@ void draw_bars(bool unhide) { color_t fg_color = colors.binding_mode_fg; color_t bg_color = colors.binding_mode_bg; - cairo_draw_rectangle(&(outputs_walk->buffer), colors.binding_mode_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)); + draw_util_rectangle(&(outputs_walk->buffer), colors.binding_mode_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)); - cairo_draw_rectangle(&(outputs_walk->buffer), bg_color, - 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)); + draw_util_rectangle(&(outputs_walk->buffer), bg_color, + 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)); - cairo_draw_text(binding.name, &(outputs_walk->buffer), fg_color, bg_color, - workspace_width + logical_px(ws_hoff_px) + logical_px(1), - logical_px(ws_voff_px), - binding.width); + draw_util_text(binding.name, &(outputs_walk->buffer), fg_color, bg_color, + 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; @@ -1897,8 +1896,8 @@ void draw_bars(bool unhide) { int x_src = (int16_t)(statusline_width - visible_statusline_width); int x_dest = (int16_t)(outputs_walk->rect.w - tray_width - logical_px(sb_hoff_px) - visible_statusline_width); - cairo_copy_surface(&statusline_surface, &(outputs_walk->buffer), x_dest - x_src, 0, - x_dest, 0, (int16_t)visible_statusline_width, (int16_t)bar_height); + draw_util_copy_surface(&statusline_surface, &(outputs_walk->buffer), x_src, 0, + x_dest, 0, (int16_t)visible_statusline_width, (int16_t)bar_height); } workspace_width = 0; @@ -1927,8 +1926,8 @@ void redraw_bars(void) { continue; } - cairo_copy_surface(&(outputs_walk->buffer), &(outputs_walk->bar), 0, 0, - 0, 0, outputs_walk->rect.w, outputs_walk->rect.h); + draw_util_copy_surface(&(outputs_walk->buffer), &(outputs_walk->bar), 0, 0, + 0, 0, outputs_walk->rect.w, outputs_walk->rect.h); xcb_flush(xcb_connection); } } diff --git a/libi3/font.c b/libi3/font.c index c1e95a9e..9e808a89 100644 --- a/libi3/font.c +++ b/libi3/font.c @@ -133,11 +133,6 @@ static void draw_text_pango(const char *text, size_t text_len, cairo_move_to(cr, x, y - yoffset); pango_cairo_show_layout(cr, layout); - /* We need to flush cairo surfaces twice to avoid an assertion bug. See #1989 - * and https://bugs.freedesktop.org/show_bug.cgi?id=92455. */ - cairo_surface_flush(surface); - cairo_surface_flush(surface); - /* Free resources */ g_object_unref(layout); cairo_destroy(cr); From dd400ff74bbf709445473dd99b3c0b8314e63b41 Mon Sep 17 00:00:00 2001 From: hwangcc23 Date: Fri, 16 Oct 2015 23:17:41 +0800 Subject: [PATCH 030/187] Revise workspace next/prev See the issue #1798 (http://github.com/i3/i3/issues/1798). +workspace_next+ as-is cycles through either numbered or named workspaces, but when it reaches the last numbered/named workspace, it only looks for named workspaces. This commit changes it: look for named workspaces after exhausting numbered ones, but also for numbered ones after exhausting named ones. Also add a test case 528-workspace-next-prev.t (numbered workspaces and named workspaces on 2 outputs) for testing this. --- docs/userguide | 4 + src/workspace.c | 135 ++++++++++++-------------- testcases/t/528-workspace-next-prev.t | 127 ++++++++++++++++++++++++ 3 files changed, 193 insertions(+), 73 deletions(-) create mode 100644 testcases/t/528-workspace-next-prev.t diff --git a/docs/userguide b/docs/userguide index 007ef69f..c98c25f9 100644 --- a/docs/userguide +++ b/docs/userguide @@ -1849,6 +1849,10 @@ container to workspace next+, +move container to workspace prev+ to move a container to the next/previous workspace and +move container to workspace current+ (the last one makes sense only when used with criteria). ++workspace next+ cycles through either numbered or named workspaces. But when it +reaches the last numbered/named workspace, it looks for named workspaces after +exhausting numbered ones and looks for numbered ones after exhausting named ones. + See <> for how to move a container/workspace to a different RandR output. diff --git a/src/workspace.c b/src/workspace.c index e7a09c70..dc0a596d 100644 --- a/src/workspace.c +++ b/src/workspace.c @@ -506,12 +506,33 @@ void workspace_show_by_name(const char *num) { */ Con *workspace_next(void) { Con *current = con_get_workspace(focused); - Con *next = NULL; + Con *next = NULL, *first = NULL, *first_opposite = NULL; Con *output; if (current->num == -1) { /* If currently a named workspace, find next named workspace. */ - next = TAILQ_NEXT(current, nodes); + if ((next = TAILQ_NEXT(current, nodes)) != NULL) + return next; + bool found_current = false; + TAILQ_FOREACH(output, &(croot->nodes_head), nodes) { + /* Skip outputs starting with __, they are internal. */ + if (con_is_internal(output)) + continue; + NODES_FOREACH(output_get_content(output)) { + if (child->type != CT_WORKSPACE) + continue; + if (!first) + first = child; + if (!first_opposite && child->num != -1) + first_opposite = child; + if (child == current) { + found_current = true; + } else if (child->num == -1 && found_current) { + next = child; + return next; + } + } + } } else { /* If currently a numbered workspace, find next numbered workspace. */ TAILQ_FOREACH(output, &(croot->nodes_head), nodes) { @@ -521,6 +542,10 @@ Con *workspace_next(void) { NODES_FOREACH(output_get_content(output)) { if (child->type != CT_WORKSPACE) continue; + if (!first) + first = child; + if (!first_opposite && child->num == -1) + first_opposite = child; if (child->num == -1) break; /* Need to check child against current and next because we are @@ -532,41 +557,9 @@ Con *workspace_next(void) { } } - /* Find next named workspace. */ - if (!next) { - bool found_current = false; - TAILQ_FOREACH(output, &(croot->nodes_head), nodes) { - /* Skip outputs starting with __, they are internal. */ - if (con_is_internal(output)) - continue; - NODES_FOREACH(output_get_content(output)) { - if (child->type != CT_WORKSPACE) - continue; - if (child == current) { - found_current = 1; - } else if (child->num == -1 && (current->num != -1 || found_current)) { - next = child; - goto workspace_next_end; - } - } - } - } + if (!next) + next = first_opposite ? first_opposite : first; - /* Find first workspace. */ - if (!next) { - TAILQ_FOREACH(output, &(croot->nodes_head), nodes) { - /* Skip outputs starting with __, they are internal. */ - if (con_is_internal(output)) - continue; - NODES_FOREACH(output_get_content(output)) { - if (child->type != CT_WORKSPACE) - continue; - if (!next || (child->num != -1 && child->num < next->num)) - next = child; - } - } - } -workspace_next_end: return next; } @@ -576,7 +569,7 @@ workspace_next_end: */ Con *workspace_prev(void) { Con *current = con_get_workspace(focused); - Con *prev = NULL; + Con *prev = NULL, *first_opposite = NULL, *last = NULL; Con *output; if (current->num == -1) { @@ -584,6 +577,28 @@ Con *workspace_prev(void) { prev = TAILQ_PREV(current, nodes_head, nodes); if (prev && prev->num != -1) prev = NULL; + if (!prev) { + bool found_current = false; + TAILQ_FOREACH_REVERSE(output, &(croot->nodes_head), nodes_head, nodes) { + /* Skip outputs starting with __, they are internal. */ + if (con_is_internal(output)) + continue; + NODES_FOREACH_REVERSE(output_get_content(output)) { + if (child->type != CT_WORKSPACE) + continue; + if (!last) + last = child; + if (!first_opposite && child->num != -1) + first_opposite = child; + if (child == current) { + found_current = true; + } else if (child->num == -1 && found_current) { + prev = child; + goto workspace_prev_end; + } + } + } + } } else { /* If numbered workspace, find previous numbered workspace. */ TAILQ_FOREACH_REVERSE(output, &(croot->nodes_head), nodes_head, nodes) { @@ -591,7 +606,13 @@ Con *workspace_prev(void) { if (con_is_internal(output)) continue; NODES_FOREACH_REVERSE(output_get_content(output)) { - if (child->type != CT_WORKSPACE || child->num == -1) + if (child->type != CT_WORKSPACE) + continue; + if (!last) + last = child; + if (!first_opposite && child->num == -1) + first_opposite = child; + if (child->num == -1) continue; /* Need to check child against current and previous because we * are traversing multiple lists and thus are not guaranteed @@ -602,40 +623,8 @@ Con *workspace_prev(void) { } } - /* Find previous named workspace. */ - if (!prev) { - bool found_current = false; - TAILQ_FOREACH_REVERSE(output, &(croot->nodes_head), nodes_head, nodes) { - /* Skip outputs starting with __, they are internal. */ - if (con_is_internal(output)) - continue; - NODES_FOREACH_REVERSE(output_get_content(output)) { - if (child->type != CT_WORKSPACE) - continue; - if (child == current) { - found_current = true; - } else if (child->num == -1 && (current->num != -1 || found_current)) { - prev = child; - goto workspace_prev_end; - } - } - } - } - - /* Find last workspace. */ - if (!prev) { - TAILQ_FOREACH_REVERSE(output, &(croot->nodes_head), nodes_head, nodes) { - /* Skip outputs starting with __, they are internal. */ - if (con_is_internal(output)) - continue; - NODES_FOREACH_REVERSE(output_get_content(output)) { - if (child->type != CT_WORKSPACE) - continue; - if (!prev || child->num > prev->num) - prev = child; - } - } - } + if (!prev) + prev = first_opposite ? first_opposite : last; workspace_prev_end: return prev; @@ -675,7 +664,7 @@ Con *workspace_next_on_output(void) { if (child->type != CT_WORKSPACE) continue; if (child == current) { - found_current = 1; + found_current = true; } else if (child->num == -1 && (current->num != -1 || found_current)) { next = child; goto workspace_next_on_output_end; diff --git a/testcases/t/528-workspace-next-prev.t b/testcases/t/528-workspace-next-prev.t new file mode 100644 index 00000000..79e83a07 --- /dev/null +++ b/testcases/t/528-workspace-next-prev.t @@ -0,0 +1,127 @@ +#!perl +# vim:ts=4:sw=4:expandtab +# +# Please read the following documents before working on tests: +# • http://build.i3wm.org/docs/testsuite.html +# (or docs/testsuite) +# +# • http://build.i3wm.org/docs/lib-i3test.html +# (alternatively: perldoc ./testcases/lib/i3test.pm) +# +# • http://build.i3wm.org/docs/ipc.html +# (or docs/ipc) +# +# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# (unless you are already familiar with Perl) +# +# Tests whether 'workspace next' works correctly. +# +use List::Util qw(first); +use i3test i3_autostart => 0; + +sub assert_next { + my ($expected) = @_; + + cmd 'workspace next'; + # We need to sync after changing focus to a different output to wait for the + # EnterNotify to be processed, otherwise it will be processed at some point + # later in time and mess up our subsequent tests. + sync_with_i3; + + is(focused_ws, $expected, "workspace $expected focused"); +} + + +my $config = <root->warp_pointer(0, 0); +sync_with_i3; + +cmd 'workspace A'; +# ensure workspace A stays open +open_window; + +cmd 'workspace B'; +# ensure workspace B stays open +open_window; + +cmd 'workspace D'; +# ensure workspace D stays open +open_window; + +cmd 'workspace E'; +# ensure workspace E stays open +open_window; + +cmd 'focus output right'; + +cmd 'workspace 1'; +# ensure workspace 1 stays open +open_window; + +cmd 'workspace 2'; +# ensure workspace 2 stays open +open_window; + +cmd 'workspace 3'; +# ensure workspace 3 stays open +open_window; + +cmd 'workspace 4'; +# ensure workspace 4 stays open +open_window; + +cmd 'workspace 5'; +# ensure workspace 5 stays open +open_window; + +cmd 'workspace C'; +# ensure workspace C stays open +open_window; + +cmd 'workspace F'; +# ensure workspace F stays open +open_window; + +cmd 'focus output right'; + +################################################################################ +# Use workspace next and verify the correct order. +################################################################################ + +# The current order should be: +# output 1: A, B, D, E +# output 2: 1, 2, 3, 4, 5, C, F + +cmd 'workspace A'; +is(focused_ws, 'A', 'back on workspace A'); + +assert_next('B'); +assert_next('D'); +assert_next('E'); +assert_next('C'); +assert_next('F'); +assert_next('1'); +assert_next('2'); +assert_next('3'); +assert_next('4'); +assert_next('5'); +assert_next('A'); +assert_next('B'); + +exit_gracefully($pid); + +done_testing; From d56264e00185023d433a913ef468428d345a3c7a Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 16 Oct 2015 19:42:20 +0200 Subject: [PATCH 031/187] =?UTF-8?q?man:=20add=20=E2=80=9Cfloating=20window?= =?UTF-8?q?=E2=80=9D=20to=20terminology=20(Thanks=20frederik)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- man/i3.man | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/man/i3.man b/man/i3.man index 203b42ee..16302e08 100644 --- a/man/i3.man +++ b/man/i3.man @@ -77,6 +77,12 @@ i3 keeps your layout in a tree data structure. Window:: An X11 window, like the Firefox browser window or a terminal emulator. +Floating Window:: +A window which "floats" on top of other windows. This style is used by i3 to +display X11 windows with type "dialog", such as the "Print" or "Open File" +dialog boxes in many GUI applications. Use of floating windows can be +fine-tuned with the for_window command (see HTML userguide). + Split container:: A split container contains multiple other split containers or windows. + From a172168e61e37a1c4415f4abfd5ece6c5293dadf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ingo=20B=C3=BCrk?= Date: Fri, 16 Oct 2015 21:07:16 +0200 Subject: [PATCH 032/187] Fix moving windows to a marked workspace by mark. When a window is moved to a mark and the marked container is a workspace, we can skip any other logic and just call con_move_to_workspace directly. fixes #2003 --- src/con.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/con.c b/src/con.c index d1148301..5b0cc6c8 100644 --- a/src/con.c +++ b/src/con.c @@ -1020,6 +1020,12 @@ bool con_move_to_mark(Con *con, const char *mark) { return true; } + if (con->type == CT_WORKSPACE) { + DLOG("target container is a workspace, simply moving the container there.\n"); + con_move_to_workspace(con, target, true, false, false); + return true; + } + /* For split containers, we use the currently focused container within it. * This allows setting marks on, e.g., tabbed containers which will move * con to a new tab behind the focused tab. */ From 19a16f3ce7bd83158b2789572b685a56f8efbb49 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ingo=20B=C3=BCrk?= Date: Fri, 16 Oct 2015 21:18:23 +0200 Subject: [PATCH 033/187] Log X11 errors in i3bar. This commit introduces X11 error logging similar to the way we already do in i3. fixes #1998 --- i3bar/src/xcb.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/i3bar/src/xcb.c b/i3bar/src/xcb.c index efef7365..63b9863a 100644 --- a/i3bar/src/xcb.c +++ b/i3bar/src/xcb.c @@ -987,6 +987,13 @@ void xcb_chk_cb(struct ev_loop *loop, ev_check *watcher, int revents) { } while ((event = xcb_poll_for_event(xcb_connection)) != NULL) { + if (event->response_type == 0) { + xcb_generic_error_t *error = (xcb_generic_error_t *)event; + DLOG("Received X11 error, sequence 0x%x, error_code = %d\n", error->sequence, error->error_code); + free(event); + continue; + } + int type = (event->response_type & ~0x80); if (type == xkb_base && xkb_base > -1) { From 0750b450b2289351c927c0bda97f718f8386e4b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ingo=20B=C3=BCrk?= Date: Sat, 17 Oct 2015 22:14:48 +0200 Subject: [PATCH 034/187] Use sasprintf() instead of alloc'ing and strncpy() in i3bar. resolves #1995 --- i3bar/src/child.c | 15 +++------------ i3bar/src/config.c | 5 +---- i3bar/src/mode.c | 12 ++---------- i3bar/src/outputs.c | 21 ++++++--------------- i3bar/src/workspaces.c | 16 +++++----------- 5 files changed, 17 insertions(+), 52 deletions(-) diff --git a/i3bar/src/child.c b/i3bar/src/child.c index e73a2920..2b10be49 100644 --- a/i3bar/src/child.c +++ b/i3bar/src/child.c @@ -220,24 +220,15 @@ static int stdin_string(void *context, const unsigned char *val, size_t len) { return 1; } if (strcasecmp(ctx->last_map_key, "min_width") == 0) { - char *copy = (char *)smalloc(len + 1); - strncpy(copy, (const char *)val, len); - copy[len] = 0; - ctx->block.min_width_str = copy; + sasprintf(&(ctx->block.min_width_str), "%.*s", len, val); return 1; } if (strcasecmp(ctx->last_map_key, "name") == 0) { - char *copy = (char *)smalloc(len + 1); - strncpy(copy, (const char *)val, len); - copy[len] = 0; - ctx->block.name = copy; + sasprintf(&(ctx->block.name), "%.*s", len, val); return 1; } if (strcasecmp(ctx->last_map_key, "instance") == 0) { - char *copy = (char *)smalloc(len + 1); - strncpy(copy, (const char *)val, len); - copy[len] = 0; - ctx->block.instance = copy; + sasprintf(&(ctx->block.instance), "%.*s", len, val); return 1; } diff --git a/i3bar/src/config.c b/i3bar/src/config.c index 0e2dd05a..f3412719 100644 --- a/i3bar/src/config.c +++ b/i3bar/src/config.c @@ -30,10 +30,7 @@ static bool parsing_bindings; */ static int config_map_key_cb(void *params_, const unsigned char *keyVal, size_t keyLen) { FREE(cur_key); - - cur_key = smalloc(sizeof(unsigned char) * (keyLen + 1)); - strncpy(cur_key, (const char *)keyVal, keyLen); - cur_key[keyLen] = '\0'; + sasprintf(&(cur_key), "%.*s", keyLen, keyVal); if (strcmp(cur_key, "bindings") == 0) parsing_bindings = true; diff --git a/i3bar/src/mode.c b/i3bar/src/mode.c index 4b27b110..d6767786 100644 --- a/i3bar/src/mode.c +++ b/i3bar/src/mode.c @@ -33,11 +33,7 @@ static int mode_string_cb(void *params_, const unsigned char *val, size_t len) { struct mode_json_params *params = (struct mode_json_params *)params_; if (!strcmp(params->cur_key, "change")) { - char *copy = smalloc(sizeof(const unsigned char) * (len + 1)); - strncpy(copy, (const char *)val, len); - copy[len] = '\0'; - - params->name = copy; + sasprintf(&(params->name), "%.*s", len, val); FREE(params->cur_key); return 1; } @@ -74,11 +70,7 @@ static int mode_boolean_cb(void *params_, int val) { static int mode_map_key_cb(void *params_, const unsigned char *keyVal, size_t keyLen) { struct mode_json_params *params = (struct mode_json_params *)params_; FREE(params->cur_key); - - params->cur_key = smalloc(sizeof(unsigned char) * (keyLen + 1)); - strncpy(params->cur_key, (const char *)keyVal, keyLen); - params->cur_key[keyLen] = '\0'; - + sasprintf(&(params->cur_key), "%.*s", keyLen, keyVal); return 1; } diff --git a/i3bar/src/outputs.c b/i3bar/src/outputs.c index fd2b1363..d0d175ca 100644 --- a/i3bar/src/outputs.c +++ b/i3bar/src/outputs.c @@ -108,9 +108,8 @@ static int outputs_string_cb(void *params_, const unsigned char *val, size_t len struct outputs_json_params *params = (struct outputs_json_params *)params_; if (!strcmp(params->cur_key, "current_workspace")) { - char *copy = smalloc(sizeof(const unsigned char) * (len + 1)); - strncpy(copy, (const char *)val, len); - copy[len] = '\0'; + char *copy = NULL; + sasprintf(©, "%.*s", len, val); char *end; errno = 0; @@ -118,7 +117,8 @@ static int outputs_string_cb(void *params_, const unsigned char *val, size_t len if (errno == 0 && (end && *end == '\0')) params->outputs_walk->ws = parsed_num; - free(copy); + + FREE(copy); FREE(params->cur_key); return 1; } @@ -127,14 +127,9 @@ static int outputs_string_cb(void *params_, const unsigned char *val, size_t len return 0; } - char *name = smalloc(sizeof(const unsigned char) * (len + 1)); - strncpy(name, (const char *)val, len); - name[len] = '\0'; - - params->outputs_walk->name = name; + sasprintf(&(params->outputs_walk->name), "%.*s", len, val); FREE(params->cur_key); - return 1; } @@ -228,11 +223,7 @@ static int outputs_end_map_cb(void *params_) { static int outputs_map_key_cb(void *params_, const unsigned char *keyVal, size_t keyLen) { struct outputs_json_params *params = (struct outputs_json_params *)params_; FREE(params->cur_key); - - params->cur_key = smalloc(sizeof(unsigned char) * (keyLen + 1)); - strncpy(params->cur_key, (const char *)keyVal, keyLen); - params->cur_key[keyLen] = '\0'; - + sasprintf(&(params->cur_key), "%.*s", keyLen, keyVal); return 1; } diff --git a/i3bar/src/workspaces.c b/i3bar/src/workspaces.c index 961a41f5..77b351e8 100644 --- a/i3bar/src/workspaces.c +++ b/i3bar/src/workspaces.c @@ -102,8 +102,6 @@ static int workspaces_integer_cb(void *params_, long long val) { static int workspaces_string_cb(void *params_, const unsigned char *val, size_t len) { struct workspaces_json_params *params = (struct workspaces_json_params *)params_; - char *output_name; - if (!strcmp(params->cur_key, "name")) { const char *ws_name = (const char *)val; params->workspaces_walk->canonical_name = sstrndup(ws_name, len); @@ -147,11 +145,11 @@ static int workspaces_string_cb(void *params_, const unsigned char *val, size_t if (!strcmp(params->cur_key, "output")) { /* We add the ws to the TAILQ of the output, it belongs to */ - output_name = smalloc(sizeof(const unsigned char) * (len + 1)); - strncpy(output_name, (const char *)val, len); - output_name[len] = '\0'; + char *output_name = NULL; + sasprintf(&output_name, "%.*s", len, val); + i3_output *target = get_output_by_name(output_name); - if (target) { + if (target != NULL) { params->workspaces_walk->output = target; TAILQ_INSERT_TAIL(params->workspaces_walk->output->workspaces, @@ -201,11 +199,7 @@ static int workspaces_start_map_cb(void *params_) { static int workspaces_map_key_cb(void *params_, const unsigned char *keyVal, size_t keyLen) { struct workspaces_json_params *params = (struct workspaces_json_params *)params_; FREE(params->cur_key); - - params->cur_key = smalloc(sizeof(unsigned char) * (keyLen + 1)); - strncpy(params->cur_key, (const char *)keyVal, keyLen); - params->cur_key[keyLen] = '\0'; - + sasprintf(&(params->cur_key), "%.*s", keyLen, keyVal); return 1; } From 52d306db2418b7ea3b09f450d75bf71886b4be19 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ingo=20B=C3=BCrk?= Date: Sat, 17 Oct 2015 19:59:45 +0200 Subject: [PATCH 035/187] Add proper documentation for binding modes. fixes #1996 --- docs/userguide | 106 ++++++++++++++++++++++++++++++++----------------- 1 file changed, 70 insertions(+), 36 deletions(-) diff --git a/docs/userguide b/docs/userguide index c98c25f9..92348fb5 100644 --- a/docs/userguide +++ b/docs/userguide @@ -149,8 +149,10 @@ it does not yet exist. The easiest way to resize a container is by using the mouse: Grab the border and move it to the wanted size. -See <> for how to configure i3 to be able to resize -columns/rows with your keyboard. +You can also use <> to define a mode for resizing via the +keyboard. To see an example for this, look at the +https://github.com/i3/i3/blob/next/i3.config.keycodes[default config] provided +by i3. === Restarting i3 inplace @@ -177,7 +179,8 @@ around. By grabbing the borders and moving them you can resize the window. You can also do that by using the <>. Another way to resize floating windows using the mouse is to right-click on the titlebar and drag. -For resizing floating windows with your keyboard, see <>. +For resizing floating windows with your keyboard, see the resizing binding mode +provided by the i3 https://github.com/i3/i3/blob/next/i3.config.keycodes[default config]. Floating windows are always on top of tiling windows. @@ -441,6 +444,59 @@ bindsym button9 move left bindsym button8 move right -------------------------------- +[[binding_modes]] + +=== Binding modes + +You can have multiple sets of bindings by using different binding modes. When +you switch to another binding mode, all bindings from the current mode are +released and only the bindings defined in the new mode are valid for as long as +you stay in that binding mode. The only predefined binding mode is +default+, +which is the mode i3 starts out with and to which all bindings not defined in a +specific binding mode belong. + +Working with binding modes consists of two parts: defining a binding mode and +switching to it. For these purposes, there are one config directive and one +command, both of which are called +mode+. The directive is used to define the +bindings belonging to a certain binding mode, while the command will switch to +the specified mode. + +It is recommended to use binding modes in combination with <> in +order to make maintenance easier. Below is an example of how to use a binding +mode. + +Note that it is advisable to define bindings for switching back to the default +mode. + +Note that it is possible to use <> for binding modes, but you +need to enable it explicitly by passing the +--pango_markup+ flag to the mode +definition. + +*Syntax*: +---------------------------- +# config directive +mode [--pango_markup] + +# command +mode +---------------------------- + +*Example*: +------------------------------------------------------------------------ +# Press $mod+o followed by either f, t, Esc or Return to launch firefox, +# thunderbird or return to the default mode, respectively. +set $mode_launcher Launch: [f]irefox [t]hunderbird +bindsym $mod+o mode "$mode_launcher" + +mode "$mode_launcher" { + bindsym f exec firefox + bindsym t exec thunderbird + + bindsym Esc mode "default" + bindsym Return mode "default" +} +------------------------------------------------------------------------ + [[floating_modifier]] === The floating modifier @@ -624,6 +680,8 @@ no_focus no_focus [window_role="pop-up"] ------------------------------- +[[variables]] + === Variables As you learned in the section about keyboard bindings, you will have @@ -1455,8 +1513,8 @@ bar { Specifies whether the current binding mode indicator should be shown or not. This is useful if you want to hide the workspace buttons but still be able -to see the current binding mode indicator. -For an example of a +mode+ definition, see <>. +to see the current binding mode indicator. See <> to learn what +modes are and how to use them. The default is to show the mode indicator. @@ -2023,38 +2081,12 @@ many percentage points a *tiling container* should be grown or shrunk (the default is 10 percentage points). Note that +resize set+ will only work for floating containers. -I recommend using the resize command inside a so called +mode+: +It is recommended to define bindings for resizing in a dedicated binding mode. +See <> and the example in the i3 +https://github.com/i3/i3/blob/next/i3.config.keycodes[default config] for more +context. -.Example: Configuration file, defining a mode for resizing ----------------------------------------------------------------------- -mode "resize" { - # These bindings trigger as soon as you enter the resize mode - - # Pressing left will shrink the window’s width. - # Pressing right will grow the window’s width. - # Pressing up will shrink the window’s height. - # Pressing down will grow the window’s height. - bindsym j resize shrink width 10 px or 10 ppt - bindsym k resize grow height 10 px or 10 ppt - bindsym l resize shrink height 10 px or 10 ppt - bindsym semicolon resize grow width 10 px or 10 ppt - - # same bindings, but for the arrow keys - bindsym Left resize shrink width 10 px or 10 ppt - bindsym Down resize grow height 10 px or 10 ppt - bindsym Up resize shrink height 10 px or 10 ppt - bindsym Right resize grow width 10 px or 10 ppt - - # back to normal: Enter or Escape - bindsym Return mode "default" - bindsym Escape mode "default" -} - -# Enter resize mode -bindsym $mod+r mode "resize" ----------------------------------------------------------------------- - -*Example 2 - setting urxvt size to 640x480:* +*Example*: ------------------------------------------------ for_window [class="urxvt"] resize set 640 480 ------------------------------------------------ @@ -2132,6 +2164,8 @@ Alternatively, if you do not want to mess with +i3-input+, you could create seperate bindings for a specific set of labels and then only use those labels. /////////////////////////////////////////////////////////////////// +[[pango_markup]] + === Window title format By default, i3 will simply print the X11 window title. Using +title_format+, From 3e3a2f054950696a5ee824d722f824e2a0c4d937 Mon Sep 17 00:00:00 2001 From: Adaephon-GH Date: Mon, 19 Oct 2015 07:39:59 +0200 Subject: [PATCH 036/187] Fix erroneous headline for moving to mark The headline indicated that it would be possible to move containers and *workspaces* to marks but the following text clearly shows that it should state containers and *windows*. --- docs/userguide | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/userguide b/docs/userguide index 92348fb5..01a02e12 100644 --- a/docs/userguide +++ b/docs/userguide @@ -2039,7 +2039,7 @@ bindsym $mod+x move workspace to output right bindsym $mod+x move container to output VGA1 -------------------------------------------------------- -=== Moving containers/workspaces to marks +=== Moving containers/windows to marks To move a container to another container with a specific mark (see <>), you can use the following command. From 7b502cf44cabd4d01dcadaae3ecb8b43be97730d Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 19 Oct 2015 18:52:47 +0200 Subject: [PATCH 037/187] Fix memleak in translate_keysyms --- src/bindings.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/bindings.c b/src/bindings.c index b5f31901..b32972ac 100644 --- a/src/bindings.c +++ b/src/bindings.c @@ -413,6 +413,7 @@ void translate_keysyms(void) { } xkb_state_unref(dummy_state); + xkb_state_unref(dummy_state_no_shift); if (has_errors) { start_config_error_nagbar(current_configpath, true); From 8125b54e09aa2ec06367245d8132241a8ee682ae Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 19 Oct 2015 19:11:53 +0200 Subject: [PATCH 038/187] fix a memory leak in handle_get_bar_config --- src/ipc.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/ipc.c b/src/ipc.c index 1a8d28ed..c884cc8e 100644 --- a/src/ipc.c +++ b/src/ipc.c @@ -894,8 +894,8 @@ IPC_HANDLER(get_bar_config) { /* To get a properly terminated buffer, we copy * message_size bytes out of the buffer */ - char *bar_id = scalloc(message_size + 1, 1); - strncpy(bar_id, (const char *)message, message_size); + char *bar_id = NULL; + sasprintf(&bar_id, "%.*s", message_size, message); LOG("IPC: looking for config for bar ID \"%s\"\n", bar_id); Barconfig *current, *config = NULL; TAILQ_FOREACH(current, &barconfigs, configs) { @@ -905,6 +905,7 @@ IPC_HANDLER(get_bar_config) { config = current; break; } + free(bar_id); if (!config) { /* If we did not find a config for the given ID, the reply will contain From 29490c2336aaa0d10f27bdad82ef9b212190d086 Mon Sep 17 00:00:00 2001 From: Adaephon-GH Date: Wed, 21 Oct 2015 13:08:21 +0200 Subject: [PATCH 039/187] Quote __focused__ to prevent parsing by asciidoc Text between two "__" is rendered as italics by asciidoc. This affects the display of the new __focused__ special value for criteria. --- docs/userguide | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/userguide b/docs/userguide index 01a02e12..2447b5b2 100644 --- a/docs/userguide +++ b/docs/userguide @@ -1642,15 +1642,15 @@ The criteria which are currently implemented are: class:: Compares the window class (the second part of WM_CLASS). Use the - special value +__focused__+ to match all windows having the same window + special value +\_\_focused__+ to match all windows having the same window class as the currently focused window. instance:: Compares the window instance (the first part of WM_CLASS). Use the - special value +__focused__+ to match all windows having the same window + special value +\_\_focused__+ to match all windows having the same window instance as the currently focused window. window_role:: Compares the window role (WM_WINDOW_ROLE). Use the special value - +__focused__+ to match all windows having the same window role as the + +\_\_focused__+ to match all windows having the same window role as the currently focused window. window_type:: Compare the window type (_NET_WM_WINDOW_TYPE). Possible values are @@ -1659,8 +1659,8 @@ window_type:: id:: Compares the X11 window ID, which you can get via +xwininfo+ for example. title:: - Compares the X11 window title (_NET_WM_NAME or WM_NAME as fallback). - Use the special value +__focused__+ to match all windows having the + Compares the X11 window title (\_NET_WM_NAME or WM_NAME as fallback). + Use the special value +\_\_focused__+ to match all windows having the same window title as the currently focused window. urgent:: Compares the urgent state of the window. Can be "latest" or "oldest". @@ -1668,7 +1668,7 @@ urgent:: (The following aliases are also available: newest, last, recent, first) workspace:: Compares the workspace name of the workspace the window belongs to. Use - the special value +__focused__+ to match all windows in the currently + the special value +\_\_focused__+ to match all windows in the currently focused workspace. con_mark:: Compares the mark set for this container, see <>. From 103cef7b2bcf50b129bc0f1a84df398c6cdb3acd Mon Sep 17 00:00:00 2001 From: Adaephon-GH Date: Wed, 21 Oct 2015 13:35:52 +0200 Subject: [PATCH 040/187] Improve placement of explicit IDs for headings In some cases the IDs of section titles was placed after the section title. With that in the rendered HTML the ID was placed on the paragraph and not on the heading. This led to heading not being shown when the corresponding link was clicked. --- docs/userguide | 40 ++++++++++------------------------------ 1 file changed, 10 insertions(+), 30 deletions(-) diff --git a/docs/userguide b/docs/userguide index 2447b5b2..68011a62 100644 --- a/docs/userguide +++ b/docs/userguide @@ -205,9 +205,8 @@ like this: image::tree-layout2.png["layout2",float="right"] image::tree-shot4.png["shot4",title="Two terminals on standard workspace"] -=== Orientation and Split Containers - [[OrientationSplit]] +=== Orientation and Split Containers It is only natural to use so-called +Split Containers+ in order to build a layout when using a tree as data structure. In i3, every +Container+ has an @@ -309,7 +308,6 @@ a # and can only be used at the beginning of a line: ------------------- [[fonts]] - === Fonts i3 has support for both X core fonts and FreeType fonts (through Pango) to @@ -342,7 +340,6 @@ font pango:Terminus 11px -------------------------------------------------------------- [[keybindings]] - === Keyboard bindings A keyboard binding makes i3 execute a command (see below) upon pressing a @@ -407,7 +404,6 @@ corresponding group. For backwards compatibility, the group “Mode_switch” is alias for Group2. [[mousebindings]] - === Mouse bindings A mouse binding makes i3 execute a command upon pressing a specific mouse @@ -445,7 +441,6 @@ bindsym button8 move right -------------------------------- [[binding_modes]] - === Binding modes You can have multiple sets of bindings by using different binding modes. When @@ -498,7 +493,6 @@ mode "$mode_launcher" { ------------------------------------------------------------------------ [[floating_modifier]] - === The floating modifier To move floating windows with your mouse, you can either grab their titlebar @@ -626,9 +620,8 @@ hide_edge_borders none|vertical|horizontal|both hide_edge_borders vertical ---------------------- -=== Arbitrary commands for specific windows (for_window) - [[for_window]] +=== Arbitrary commands for specific windows (for_window) With the +for_window+ command, you can let i3 execute any command when it encounters a specific window. This can be used to set windows to floating or to @@ -655,9 +648,8 @@ for_window [title="x200: ~/work"] floating enable The valid criteria are the same as those for commands, see <>. -=== Don't focus window upon opening - [[no_focus]] +=== Don't focus window upon opening When a new window appears, it will be focused. The +no_focus+ directive allows preventing this from happening and must be used in combination with <>. @@ -681,7 +673,6 @@ no_focus [window_role="pop-up"] ------------------------------- [[variables]] - === Variables As you learned in the section about keyboard bindings, you will have @@ -707,9 +698,8 @@ absolutely no plans to change this. If you need a more dynamic configuration you should create a little script which generates a configuration file and run it before starting i3 (for example in your +~/.xsession+ file). -=== Automatically putting clients on specific workspaces - [[assign_workspace]] +=== Automatically putting clients on specific workspaces To automatically make a specific window show up on a specific workspace, you can use an *assignment*. You can match windows by using any criteria, @@ -814,7 +804,6 @@ exec --no-startup-id urxvt The flag --no-startup-id is explained in <>. [[workspace_screen]] - === Automatically putting workspaces on specific screens If you assign clients to workspaces, it might be handy to put the @@ -1073,9 +1062,8 @@ force_display_urgency_hint ms force_display_urgency_hint 500 ms --------------------------------- -=== Focus on window activation - [[focus_on_window_activation]] +=== Focus on window activation If a window is activated, e.g., via +google-chrome www.google.com+, it may request to take focus. Since this may not preferable, different reactions can be configured. @@ -1100,6 +1088,7 @@ focus:: none:: The window will neither be focused, nor be marked urgent. +[[show_marks]] === Drawing marks on window decoration If activated, marks on windows are drawn in their window decoration. However, @@ -1119,7 +1108,6 @@ show_marks yes -------------- [[line_continuation]] - === Line continuation Config files support line continuation, meaning when you end a line in a @@ -1681,7 +1669,6 @@ actually regular expressions (PCRE). See +pcresyntax(3)+ or +perldoc perlre+ for information on how to use them. [[exec]] - === Executing applications (exec) What good is a window manager if you can’t actually start any applications? @@ -1779,7 +1766,6 @@ bindsym $mod+t floating toggle -------------- [[_focusing_moving_containers]] - === Focusing containers To change focus, you can use the +focus+ command. The following options are @@ -2015,9 +2001,8 @@ bindsym $mod+r exec i3-input -F 'rename workspace to "%s"' -P 'New name: ' See <> for how to move a container/workspace to a different RandR output. -=== Moving containers/workspaces to RandR outputs - [[move_to_outputs]] +=== Moving containers/workspaces to RandR outputs To move a container to another RandR output (addressed by names like +LVDS1+ or +VGA1+) or to a RandR output identified by a specific direction (like +left+, @@ -2060,7 +2045,6 @@ for_window [instance="tabme"] move window to mark target -------------------------------------------------------- [[resizingconfig]] - === Resizing containers/windows If you want to resize containers/windows using your keyboard, you can use the @@ -2112,9 +2096,8 @@ with criteria for that. bindsym $mod+a [class="urxvt" title="VIM"] focus ------------------------------------------------ -=== VIM-like marks (mark/goto) - [[vim_like_marks]] +=== VIM-like marks (mark/goto) This feature is like the jump feature: It allows you to directly jump to a specific window (this means switching to the appropriate workspace and setting @@ -2133,7 +2116,7 @@ The additional +--toggle+ option will remove the mark if the window already has this mark, add it if the window has none or replace the current mark if it has another mark. -Refer to +show_marks+ if you don't want marks to be shown in the window decoration. +Refer to <> if you don't want marks to be shown in the window decoration. *Syntax*: ------------------------------ @@ -2165,7 +2148,6 @@ seperate bindings for a specific set of labels and then only use those labels. /////////////////////////////////////////////////////////////////// [[pango_markup]] - === Window title format By default, i3 will simply print the X11 window title. Using +title_format+, @@ -2231,7 +2213,6 @@ bindsym $mod+u border none ---------------------------------------------- [[shmlog]] - === Enabling shared memory logging As described in http://i3wm.org/docs/debugging.html, i3 can log to a shared @@ -2380,7 +2361,6 @@ bindsym $mod+Shift+b bar mode invisible bar-1 ------------------------------------------------ [[multi_monitor]] - == Multiple monitors As you can see in the goal list on the website, i3 was specifically developed @@ -2510,6 +2490,7 @@ position the window either at the top or at the bottom of the screen, depending on which hint the application sets. With i3bar, you can configure its position, see <>. +[[presentations]] === Giving presentations (multi-monitor) When giving a presentation, you typically want the audience to see what you see @@ -2518,7 +2499,6 @@ simple). For more complex presentations, you might want to have some notes which only you can see on your screen, while the audience can only see the slides. -[[presentations]] ==== Case 1: everybody gets the same output This is the simple case. You connect your computer to the video projector, turn on both (computer and video projector) and configure your X server to From 68c10f72571f096065cfbc44407f3f951e167d81 Mon Sep 17 00:00:00 2001 From: Adaephon-GH Date: Wed, 21 Oct 2015 13:58:45 +0200 Subject: [PATCH 041/187] Make rendering of key bindings more consistent - Render key names and key bindings verbatim if they could be used like that in the configuration (no special format for "colloquial" names: Alt, Windows, ...) - Use only lower case letters for key bindings --- docs/userguide | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/docs/userguide b/docs/userguide index 68011a62..a94896cc 100644 --- a/docs/userguide +++ b/docs/userguide @@ -33,15 +33,15 @@ above, just decline i3-config-wizard’s offer and base your config on == Using i3 Throughout this guide, the keyword +$mod+ will be used to refer to the -configured modifier. This is the Alt key (Mod1) by default, with the Windows -key (Mod4) being a popular alternative. +configured modifier. This is the Alt key (+Mod1+) by default, with the Windows +key (+Mod4+) being a popular alternative. === Opening terminals and moving around One very basic operation is opening a new terminal. By default, the keybinding -for this is $mod+Enter, that is Alt+Enter in the default configuration. By -pressing $mod+Enter, a new terminal will be opened. It will fill the whole -space available on your screen. +for this is +$mod+Enter+, that is Alt+Enter (+Mod1+Enter+) in the default +configuration. By pressing +$mod+Enter+, a new terminal will be opened. It +will fill the whole space available on your screen. image:single_terminal.png[Single terminal] @@ -55,9 +55,9 @@ image:two_terminals.png[Two terminals] To move the focus between the two terminals, you can use the direction keys which you may know from the editor +vi+. However, in i3, your homerow is used for these keys (in +vi+, the keys are shifted to the left by one for -compatibility with most keyboard layouts). Therefore, +$mod+J+ is left, +$mod+K+ -is down, +$mod+L+ is up and `$mod+;` is right. So, to switch between the -terminals, use +$mod+K+ or +$mod+L+. Of course, you can also use the arrow keys. +compatibility with most keyboard layouts). Therefore, +$mod+j+ is left, +$mod+k+ +is down, +$mod+l+ is up and `$mod+;` is right. So, to switch between the +terminals, use +$mod+k+ or +$mod+l+. Of course, you can also use the arrow keys. At the moment, your workspace is split (it contains two terminals) in a specific direction (horizontal by default). Every window can be split @@ -114,7 +114,7 @@ create a keybinding for starting the application directly. See the section === Closing windows If an application does not provide a mechanism for closing (most applications -provide a menu, the escape key or a shortcut like +Control+W+ to close), you +provide a menu, the escape key or a shortcut like +Control+w+ to close), you can press +$mod+Shift+q+ to kill a window. For applications which support the WM_DELETE protocol, this will correctly close the application (saving any modifications or doing other cleanup). If the application doesn’t support @@ -290,7 +290,7 @@ with a text editor. On first start (and on all following starts, unless you have a configuration file), i3 will offer you to create a configuration file. You can tell the -wizard to use either Alt (Mod1) or Windows (Mod4) as modifier in the config +wizard to use either Alt (+Mod1+) or Windows (+Mod4+) as modifier in the config file. Also, the created config file will use the key symbols of your current keyboard layout. To start the wizard, use the command +i3-config-wizard+. Please note that you must not have +~/.i3/config+, otherwise the wizard will From 41db90952222b5420bd642423fffc305d4c61cb3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ingo=20B=C3=BCrk?= Date: Mon, 19 Oct 2015 21:17:35 +0200 Subject: [PATCH 042/187] Activate root output if RandR request fails. fixes #2011 --- src/randr.c | 36 +++++++++++++++++++----------------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/src/randr.c b/src/randr.c index e4522c4d..81a33e62 100644 --- a/src/randr.c +++ b/src/randr.c @@ -604,7 +604,6 @@ void randr_query_outputs(void) { Output *output, *other, *first; xcb_randr_get_output_primary_cookie_t pcookie; xcb_randr_get_screen_resources_current_cookie_t rcookie; - resources_reply *res; /* timestamp of the configuration so that we get consistent replies to all * requests (if the configuration changes between our different calls) */ @@ -621,28 +620,31 @@ void randr_query_outputs(void) { ELOG("Could not get RandR primary output\n"); else DLOG("primary output is %08x\n", primary->output); - if ((res = xcb_randr_get_screen_resources_current_reply(conn, rcookie, NULL)) == NULL) - return; - cts = res->config_timestamp; + resources_reply *res = xcb_randr_get_screen_resources_current_reply(conn, rcookie, NULL); + if (res == NULL) { + ELOG("Could not query screen resources.\n"); + } else { + cts = res->config_timestamp; - int len = xcb_randr_get_screen_resources_current_outputs_length(res); - randr_outputs = xcb_randr_get_screen_resources_current_outputs(res); + int len = xcb_randr_get_screen_resources_current_outputs_length(res); + randr_outputs = xcb_randr_get_screen_resources_current_outputs(res); - /* Request information for each output */ - xcb_randr_get_output_info_cookie_t ocookie[len]; - for (int i = 0; i < len; i++) - ocookie[i] = xcb_randr_get_output_info(conn, randr_outputs[i], cts); + /* Request information for each output */ + xcb_randr_get_output_info_cookie_t ocookie[len]; + for (int i = 0; i < len; i++) + ocookie[i] = xcb_randr_get_output_info(conn, randr_outputs[i], cts); - /* Loop through all outputs available for this X11 screen */ - for (int i = 0; i < len; i++) { - xcb_randr_get_output_info_reply_t *output; + /* Loop through all outputs available for this X11 screen */ + for (int i = 0; i < len; i++) { + xcb_randr_get_output_info_reply_t *output; - if ((output = xcb_randr_get_output_info_reply(conn, ocookie[i], NULL)) == NULL) - continue; + if ((output = xcb_randr_get_output_info_reply(conn, ocookie[i], NULL)) == NULL) + continue; - handle_output(conn, randr_outputs[i], output, cts, res); - free(output); + handle_output(conn, randr_outputs[i], output, cts, res); + free(output); + } } /* If there's no randr output, enable the output covering the root window. */ From 9bb2f038ab6229a53588771af400d5a8e0ba61ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ingo=20B=C3=BCrk?= Date: Mon, 19 Oct 2015 18:10:20 +0200 Subject: [PATCH 043/187] Allow multiple marks on windows. This patch allows multiple marks to be set on a single window. The restriction that a mark may only be on one window at a time is still upheld as this is necessary for commands like "move window to mark" to make sense. relates to #2014 --- docs/userguide | 4 +- include/con.h | 6 ++ include/data.h | 11 ++- src/commands.c | 14 +++- src/con.c | 63 ++++++++++----- src/ipc.c | 22 ++++-- src/load_layout.c | 23 ++++-- src/x.c | 38 ++++++--- testcases/t/165-for_window.t | 6 +- testcases/t/210-mark-unmark.t | 23 ++---- testcases/t/235-wm-class-change-handler.t | 2 +- testcases/t/255-multiple-marks.t | 96 +++++++++++++++++++++++ 12 files changed, 238 insertions(+), 70 deletions(-) create mode 100644 testcases/t/255-multiple-marks.t diff --git a/docs/userguide b/docs/userguide index a94896cc..f80f19f9 100644 --- a/docs/userguide +++ b/docs/userguide @@ -1659,7 +1659,9 @@ workspace:: the special value +\_\_focused__+ to match all windows in the currently focused workspace. con_mark:: - Compares the mark set for this container, see <>. + Compares the marks set for this container, see <>. A + match is made if any of the container's marks matches the specified + mark. con_id:: Compares the i3-internal container ID, which you can get via the IPC interface. Handy for scripting. diff --git a/include/con.h b/include/con.h index 16ea6cfa..df94c1ae 100644 --- a/include/con.h +++ b/include/con.h @@ -146,6 +146,12 @@ Con *con_by_frame_id(xcb_window_t frame); */ Con *con_by_mark(const char *mark); +/** + * Returns true if and only if the given containers holds the mark. + * + */ +bool con_has_mark(Con *con, const char *mark); + /** * Toggles the mark on a container. * If the container already has this mark, the mark is removed. diff --git a/include/data.h b/include/data.h index 58e4a00d..ee7f82c0 100644 --- a/include/data.h +++ b/include/data.h @@ -46,6 +46,7 @@ typedef struct Con Con; typedef struct Match Match; typedef struct Assignment Assignment; typedef struct Window i3Window; +typedef struct mark_t mark_t; /****************************************************************************** * Helper types @@ -523,6 +524,12 @@ typedef enum { CF_NONE = 0, CF_OUTPUT = 1, CF_GLOBAL = 2 } fullscreen_mode_t; +struct mark_t { + char *name; + + TAILQ_ENTRY(mark_t) marks; +}; + /** * A 'Con' represents everything from the X11 root window down to a single X11 window. * @@ -575,8 +582,8 @@ struct Con { * displayed on whichever of the containers is currently visible */ char *sticky_group; - /* user-definable mark to jump to this container later */ - char *mark; + /* user-definable marks to jump to this container later */ + TAILQ_HEAD(marks_head, mark_t) marks_head; /* cached to decide whether a redraw is needed */ bool mark_changed; diff --git a/src/commands.c b/src/commands.c index c9746ba4..ab9a4135 100644 --- a/src/commands.c +++ b/src/commands.c @@ -290,10 +290,16 @@ void cmd_criteria_match_windows(I3_CMD) { DLOG("doesnt match\n"); free(current); } - } else if (current_match->mark != NULL && current->con->mark != NULL && - regex_matches(current_match->mark, current->con->mark)) { - DLOG("match by mark\n"); - TAILQ_INSERT_TAIL(&owindows, current, owindows); + } else if (current_match->mark != NULL && !TAILQ_EMPTY(&(current->con->marks_head))) { + mark_t *mark; + TAILQ_FOREACH(mark, &(current->con->marks_head), marks) { + if (!regex_matches(current_match->mark, mark->name)) + continue; + + DLOG("match by mark\n"); + TAILQ_INSERT_TAIL(&owindows, current, owindows); + break; + } } else { if (current->con->window && match_matches_window(current_match, current->con->window)) { DLOG("matches window!\n"); diff --git a/src/con.c b/src/con.c index 5b0cc6c8..bf7dd545 100644 --- a/src/con.c +++ b/src/con.c @@ -55,6 +55,7 @@ Con *con_new_skeleton(Con *parent, i3Window *window) { TAILQ_INIT(&(new->nodes_head)); TAILQ_INIT(&(new->focus_head)); TAILQ_INIT(&(new->swallow_head)); + TAILQ_INIT(&(new->marks_head)); if (parent != NULL) con_attach(new, parent, false); @@ -512,13 +513,27 @@ Con *con_by_frame_id(xcb_window_t frame) { Con *con_by_mark(const char *mark) { Con *con; TAILQ_FOREACH(con, &all_cons, all_cons) { - if (con->mark != NULL && strcmp(con->mark, mark) == 0) + if (con_has_mark(con, mark)) return con; } return NULL; } +/* + * Returns true if and only if the given containers holds the mark. + * + */ +bool con_has_mark(Con *con, const char *mark) { + mark_t *current; + TAILQ_FOREACH(current, &(con->marks_head), marks) { + if (strcmp(current->name, mark) == 0) + return true; + } + + return false; +} + /* * Toggles the mark on a container. * If the container already has this mark, the mark is removed. @@ -529,7 +544,7 @@ void con_mark_toggle(Con *con, const char *mark) { assert(con != NULL); DLOG("Toggling mark \"%s\" on con = %p.\n", mark, con); - if (con->mark != NULL && strcmp(con->mark, mark) == 0) { + if (con_has_mark(con, mark)) { con_unmark(mark); } else { con_mark(con, mark); @@ -544,22 +559,13 @@ void con_mark(Con *con, const char *mark) { assert(con != NULL); DLOG("Setting mark \"%s\" on con = %p.\n", mark, con); - FREE(con->mark); - con->mark = sstrdup(mark); + con_unmark(mark); + + mark_t *new = scalloc(1, sizeof(mark_t)); + new->name = sstrdup(mark); + TAILQ_INSERT_TAIL(&(con->marks_head), new, marks); + con->mark_changed = true; - - DLOG("Clearing the mark from all other windows.\n"); - Con *other; - TAILQ_FOREACH(other, &all_cons, all_cons) { - /* Skip the window we actually handled since we took care of it already. */ - if (con == other) - continue; - - if (other->mark != NULL && strcmp(other->mark, mark) == 0) { - FREE(other->mark); - other->mark_changed = true; - } - } } /* @@ -572,10 +578,17 @@ void con_unmark(const char *mark) { if (mark == NULL) { DLOG("Unmarking all containers.\n"); TAILQ_FOREACH(con, &all_cons, all_cons) { - if (con->mark == NULL) + if (TAILQ_EMPTY(&(con->marks_head))) continue; - FREE(con->mark); + mark_t *current; + while (!TAILQ_EMPTY(&(con->marks_head))) { + current = TAILQ_FIRST(&(con->marks_head)); + FREE(current->name); + TAILQ_REMOVE(&(con->marks_head), current, marks); + FREE(current); + } + con->mark_changed = true; } } else { @@ -587,8 +600,18 @@ void con_unmark(const char *mark) { } DLOG("Found mark on con = %p. Removing it now.\n", con); - FREE(con->mark); con->mark_changed = true; + + mark_t *current; + TAILQ_FOREACH(current, &(con->marks_head), marks) { + if (strcmp(current->name, mark) != 0) + continue; + + FREE(current->name); + TAILQ_REMOVE(&(con->marks_head), current, marks); + FREE(current); + break; + } } } diff --git a/src/ipc.c b/src/ipc.c index c884cc8e..68cc417a 100644 --- a/src/ipc.c +++ b/src/ipc.c @@ -275,9 +275,16 @@ void dump_node(yajl_gen gen, struct Con *con, bool inplace_restart) { ystr("urgent"); y(bool, con->urgent); - if (con->mark != NULL) { - ystr("mark"); - ystr(con->mark); + if (!TAILQ_EMPTY(&(con->marks_head))) { + ystr("marks"); + y(array_open); + + mark_t *mark; + TAILQ_FOREACH(mark, &(con->marks_head), marks) { + ystr(mark->name); + } + + y(array_close); } ystr("focused"); @@ -819,9 +826,12 @@ IPC_HANDLER(get_marks) { y(array_open); Con *con; - TAILQ_FOREACH(con, &all_cons, all_cons) - if (con->mark != NULL) - ystr(con->mark); + TAILQ_FOREACH(con, &all_cons, all_cons) { + mark_t *mark; + TAILQ_FOREACH(mark, &(con->marks_head), marks) { + ystr(mark->name); + } + } y(array_close); diff --git a/src/load_layout.c b/src/load_layout.c index 4a67e6b1..68c4f4a2 100644 --- a/src/load_layout.c +++ b/src/load_layout.c @@ -28,6 +28,7 @@ static bool parsing_deco_rect; static bool parsing_window_rect; static bool parsing_geometry; static bool parsing_focus; +static bool parsing_marks; struct Match *current_swallow; /* This list is used for reordering the focus stack after parsing the 'focus' @@ -159,12 +160,16 @@ static int json_end_map(void *ctx) { static int json_end_array(void *ctx) { LOG("end of array\n"); - if (!parsing_swallows && !parsing_focus) { + if (!parsing_swallows && !parsing_focus && !parsing_marks) { con_fix_percent(json_node); } if (parsing_swallows) { parsing_swallows = false; } + if (parsing_marks) { + parsing_marks = false; + } + if (parsing_focus) { /* Clear the list of focus mappings */ struct focus_mapping *mapping; @@ -214,6 +219,9 @@ static int json_key(void *ctx, const unsigned char *val, size_t len) { if (strcasecmp(last_key, "focus") == 0) parsing_focus = true; + if (strcasecmp(last_key, "marks") == 0) + parsing_marks = true; + return 1; } @@ -234,6 +242,11 @@ static int json_string(void *ctx, const unsigned char *val, size_t len) { ELOG("swallow key %s unknown\n", last_key); } free(sval); + } else if (parsing_marks) { + char *mark; + sasprintf(&mark, "%.*s", (int)len, val); + + con_mark(json_node, mark); } else { if (strcasecmp(last_key, "name") == 0) { json_node->name = scalloc(len + 1, 1); @@ -336,13 +349,12 @@ static int json_string(void *ctx, const unsigned char *val, size_t len) { LOG("Unhandled \"last_splitlayout\": %s\n", buf); free(buf); } else if (strcasecmp(last_key, "mark") == 0) { + DLOG("Found deprecated key \"mark\".\n"); + char *buf = NULL; sasprintf(&buf, "%.*s", (int)len, val); - /* We unmark any containers using this mark to avoid duplicates. */ - con_unmark(buf); - - json_node->mark = buf; + con_mark(json_node, buf); } else if (strcasecmp(last_key, "floating") == 0) { char *buf = NULL; sasprintf(&buf, "%.*s", (int)len, val); @@ -589,6 +601,7 @@ void tree_append_json(Con *con, const char *filename, char **errormsg) { parsing_window_rect = false; parsing_geometry = false; parsing_focus = false; + parsing_marks = false; setlocale(LC_NUMERIC, "C"); stat = yajl_parse(hand, (const unsigned char *)buf, n); if (stat != yajl_status_ok) { diff --git a/src/x.c b/src/x.c index d312666b..094a33f0 100644 --- a/src/x.c +++ b/src/x.c @@ -545,18 +545,34 @@ void x_draw_decoration(Con *con) { int indent_px = (indent_level * 5) * indent_mult; int mark_width = 0; - if (config.show_marks && con->mark != NULL && (con->mark)[0] != '_') { - char *formatted_mark; - sasprintf(&formatted_mark, "[%s]", con->mark); - i3String *mark = i3string_from_utf8(formatted_mark); + if (config.show_marks && !TAILQ_EMPTY(&(con->marks_head))) { + char *formatted_mark = sstrdup(""); + bool had_visible_mark = false; + + mark_t *mark; + TAILQ_FOREACH(mark, &(con->marks_head), marks) { + if (mark->name[0] == '_') + continue; + had_visible_mark = true; + + char *buf; + sasprintf(&buf, "%s[%s]", formatted_mark, mark->name); + free(formatted_mark); + formatted_mark = buf; + } + + if (had_visible_mark) { + i3String *mark = i3string_from_utf8(formatted_mark); + mark_width = predict_text_width(mark); + + draw_text(mark, parent->pixmap, parent->pm_gc, NULL, + con->deco_rect.x + con->deco_rect.width - mark_width - logical_px(2), + con->deco_rect.y + text_offset_y, mark_width); + + I3STRING_FREE(mark); + } + FREE(formatted_mark); - mark_width = predict_text_width(mark); - - draw_text(mark, parent->pixmap, parent->pm_gc, NULL, - con->deco_rect.x + con->deco_rect.width - mark_width - logical_px(2), - con->deco_rect.y + text_offset_y, mark_width); - - I3STRING_FREE(mark); } i3String *title = win->title_format == NULL ? win->name : window_parse_title_format(win); diff --git a/testcases/t/165-for_window.t b/testcases/t/165-for_window.t index 985a7bfd..476bcc9f 100644 --- a/testcases/t/165-for_window.t +++ b/testcases/t/165-for_window.t @@ -398,7 +398,7 @@ EOT my @nodes = @{get_ws($tmp)->{floating_nodes}}; cmp_ok(@nodes, '==', 1, 'one floating container on this workspace'); - is($nodes[0]->{nodes}[0]->{mark}, 'branded', "mark set (window_type = $atom)"); + is_deeply($nodes[0]->{nodes}[0]->{marks}, [ 'branded' ], "mark set (window_type = $atom)"); exit_gracefully($pid); @@ -431,7 +431,7 @@ EOT my @nodes = @{get_ws($tmp)->{floating_nodes}}; cmp_ok(@nodes, '==', 1, 'one floating container on this workspace'); - is($nodes[0]->{nodes}[0]->{mark}, 'branded', "mark set (window_type = $atom)"); + is_deeply($nodes[0]->{nodes}[0]->{marks}, [ 'branded' ], "mark set (window_type = $atom)"); exit_gracefully($pid); @@ -454,7 +454,7 @@ $window = open_window; @nodes = @{get_ws('trigger')->{floating_nodes}}; cmp_ok(@nodes, '==', 1, 'one floating container on this workspace'); -is($nodes[0]->{nodes}[0]->{mark}, 'triggered', "mark set for workspace criterion"); +is_deeply($nodes[0]->{nodes}[0]->{marks}, [ 'triggered' ], "mark set for workspace criterion"); exit_gracefully($pid); diff --git a/testcases/t/210-mark-unmark.t b/testcases/t/210-mark-unmark.t index 93b26d94..39fc0904 100644 --- a/testcases/t/210-mark-unmark.t +++ b/testcases/t/210-mark-unmark.t @@ -28,7 +28,7 @@ sub get_mark_for_window_on_workspace { my ($ws, $con) = @_; my $current = first { $_->{window} == $con->{id} } @{get_ws_content($ws)}; - return $current->{mark}; + return $current->{marks}; } ############################################################## @@ -41,7 +41,6 @@ cmd 'split h'; is_deeply(get_marks(), [], 'no marks set yet'); - ############################################################## # 2: mark a con, check that it's marked, unmark it, check that ############################################################## @@ -98,7 +97,7 @@ cmd 'mark important'; cmd 'focus left'; cmd 'mark important'; -is(get_mark_for_window_on_workspace($tmp, $first), 'important', 'first container now has the mark'); +is_deeply(get_mark_for_window_on_workspace($tmp, $first), [ 'important' ], 'first container now has the mark'); ok(!get_mark_for_window_on_workspace($tmp, $second), 'second container lost the mark'); ############################################################## @@ -116,20 +115,10 @@ ok(!get_mark_for_window_on_workspace($tmp, $con), 'container no longer has the m $con = open_window; cmd 'mark --toggle important'; -is(get_mark_for_window_on_workspace($tmp, $con), 'important', 'container now has the mark'); +is_deeply(get_mark_for_window_on_workspace($tmp, $con), [ 'important' ], 'container now has the mark'); ############################################################## -# 7: mark a con, toggle a different mark, check it is marked -# with the new mark -############################################################## - -$con = open_window; -cmd 'mark boring'; -cmd 'mark --toggle important'; -is(get_mark_for_window_on_workspace($tmp, $con), 'important', 'container has the most recent mark'); - -############################################################## -# 8: mark a con, toggle the mark on another con, +# 7: mark a con, toggle the mark on another con, # check only the latter has the mark ############################################################## @@ -140,11 +129,11 @@ cmd 'mark important'; cmd 'focus left'; cmd 'mark --toggle important'; -is(get_mark_for_window_on_workspace($tmp, $first), 'important', 'left container has the mark now'); +is_deeply(get_mark_for_window_on_workspace($tmp, $first), [ 'important' ], 'left container has the mark now'); ok(!get_mark_for_window_on_workspace($tmp, $second), 'second containr no longer has the mark'); ############################################################## -# 9: try to mark two cons with the same mark and check that +# 8: try to mark two cons with the same mark and check that # it fails ############################################################## diff --git a/testcases/t/235-wm-class-change-handler.t b/testcases/t/235-wm-class-change-handler.t index 3685b30c..ce237b57 100644 --- a/testcases/t/235-wm-class-change-handler.t +++ b/testcases/t/235-wm-class-change-handler.t @@ -63,7 +63,7 @@ is($con->{window_properties}->{instance}, 'special', # The mark `special_class_mark` is added in a `for_window` assignment in the # config for testing purposes -is($con->{mark}, 'special_class_mark', +is_deeply($con->{marks}, [ 'special_class_mark' ], 'A `for_window` assignment should run for a match when the window changes class'); change_window_class($win, "abcdefghijklmnopqrstuv\0abcd", 24); diff --git a/testcases/t/255-multiple-marks.t b/testcases/t/255-multiple-marks.t new file mode 100644 index 00000000..d6d86e23 --- /dev/null +++ b/testcases/t/255-multiple-marks.t @@ -0,0 +1,96 @@ +#!perl +# vim:ts=4:sw=4:expandtab +# +# Please read the following documents before working on tests: +# • http://build.i3wm.org/docs/testsuite.html +# (or docs/testsuite) +# +# • http://build.i3wm.org/docs/lib-i3test.html +# (alternatively: perldoc ./testcases/lib/i3test.pm) +# +# • http://build.i3wm.org/docs/ipc.html +# (or docs/ipc) +# +# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# (unless you are already familiar with Perl) +# +# Tests for mark/unmark with multiple marks on a single window. +# Ticket: #2014 +use i3test; +use List::Util qw(first); + +my ($ws, $con, $first, $second); + +sub get_marks { + return i3(get_socket_path())->get_marks->recv; +} + +sub get_mark_for_window_on_workspace { + my ($ws, $con) = @_; + + my $current = first { $_->{window} == $con->{id} } @{get_ws_content($ws)}; + return $current->{marks}; +} + +############################################################################### +# Verify that multiple marks can be set on a window. +############################################################################### + +$ws = fresh_workspace; +$con = open_window; +cmd 'mark A'; +cmd 'mark B'; + +is_deeply(sort(get_marks()), [ 'A', 'B' ], 'both marks exist'); +is_deeply(get_mark_for_window_on_workspace($ws, $con), [ 'A', 'B' ], 'both marks are on the same window'); + +cmd 'unmark'; + +############################################################################### +# Verify that toggling a mark can affect only the specified mark. +############################################################################### + +$ws = fresh_workspace; +$con = open_window; +cmd 'mark A'; + +cmd 'mark --toggle B'; +is_deeply(get_mark_for_window_on_workspace($ws, $con), [ 'A', 'B' ], 'both marks are on the same window'); +cmd 'mark --toggle B'; +is_deeply(get_mark_for_window_on_workspace($ws, $con), [ 'A' ], 'only mark B has been removed'); + +cmd 'unmark'; + +############################################################################### +# Verify that unmarking a mark leaves other marks on the same window intact. +############################################################################### + +$ws = fresh_workspace; +$con = open_window; +cmd 'mark A'; +cmd 'mark B'; +cmd 'mark C'; + +cmd 'unmark B'; +is_deeply(get_mark_for_window_on_workspace($ws, $con), [ 'A', 'C' ], 'only mark B has been removed'); + +cmd 'unmark'; + +############################################################################### +# Verify that matching via mark works on windows with multiple marks. +############################################################################### + +$ws = fresh_workspace; +$con = open_window; +cmd 'mark A'; +cmd 'mark B'; +open_window; + +cmd '[con_mark=B] mark C'; +is_deeply(get_mark_for_window_on_workspace($ws, $con), [ 'A', 'B', 'C' ], 'matching on a mark works with multiple marks'); + +cmd 'unmark'; + +############################################################################### + +done_testing; From 7a77c5f0bb988ec2fff52e231590fff93a4ee6b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ingo=20B=C3=BCrk?= Date: Mon, 19 Oct 2015 18:31:21 +0200 Subject: [PATCH 044/187] Introduce "--add" for marking windows. In order to keep compatibility to before allowing multiple marks on a window, we introduce a flag "--add" that must be set to put more than one mark on a window. The default, which is also available as "--replace", keeps the old behavior of overwriting a mark when setting a new one. fixes #2014 --- docs/userguide | 14 +++++++++----- include/commands.h | 4 ++-- include/con.h | 4 ++-- include/data.h | 3 +++ parser-specs/commands.spec | 6 ++++-- src/commands.c | 10 ++++++---- src/con.c | 14 +++++++++++--- src/load_layout.c | 4 ++-- testcases/t/210-mark-unmark.t | 14 ++++++++++++-- testcases/t/255-multiple-marks.t | 20 ++++++++++---------- 10 files changed, 61 insertions(+), 32 deletions(-) diff --git a/docs/userguide b/docs/userguide index f80f19f9..e07e544d 100644 --- a/docs/userguide +++ b/docs/userguide @@ -2115,17 +2115,21 @@ for this purpose: It lets you input a command and sends the command to i3. It can also prefix this command and display a custom prompt for the input dialog. The additional +--toggle+ option will remove the mark if the window already has -this mark, add it if the window has none or replace the current mark if it has -another mark. +this mark or add it otherwise. Note that you may need to use this in +combination with +--add+ (see below) as any other marks will otherwise be +removed. + +By default, a window can only have one mark. You can use the +--add+ flag to +put more than one mark on a window. Refer to <> if you don't want marks to be shown in the window decoration. *Syntax*: ------------------------------- -mark [--toggle] +---------------------------------------------- +mark [--add|--replace] [--toggle] [con_mark="identifier"] focus unmark ------------------------------- +---------------------------------------------- *Example (in a terminal)*: ------------------------------ diff --git a/include/commands.h b/include/commands.h index e0bb2f92..d3485f15 100644 --- a/include/commands.h +++ b/include/commands.h @@ -115,10 +115,10 @@ void cmd_workspace_back_and_forth(I3_CMD); void cmd_workspace_name(I3_CMD, const char *name); /** - * Implementation of 'mark [--toggle] ' + * Implementation of 'mark [--add|--replace] [--toggle] ' * */ -void cmd_mark(I3_CMD, const char *mark, const char *toggle); +void cmd_mark(I3_CMD, const char *mark, const char *mode, const char *toggle); /** * Implementation of 'unmark [mark]' diff --git a/include/con.h b/include/con.h index df94c1ae..b448b8c2 100644 --- a/include/con.h +++ b/include/con.h @@ -158,13 +158,13 @@ bool con_has_mark(Con *con, const char *mark); * Otherwise, the mark is assigned to the container. * */ -void con_mark_toggle(Con *con, const char *mark); +void con_mark_toggle(Con *con, const char *mark, mark_mode_t mode); /** * Assigns a mark to the container. * */ -void con_mark(Con *con, const char *mark); +void con_mark(Con *con, const char *mark, mark_mode_t mode); /** * If mark is NULL, this removes all existing marks. diff --git a/include/data.h b/include/data.h index ee7f82c0..3a752c2e 100644 --- a/include/data.h +++ b/include/data.h @@ -75,6 +75,9 @@ typedef enum { ADJ_NONE = 0, ADJ_UPPER_SCREEN_EDGE = (1 << 2), ADJ_LOWER_SCREEN_EDGE = (1 << 4) } adjacent_t; +typedef enum { MM_REPLACE, + MM_ADD } mark_mode_t; + /** * Container layouts. See Con::layout. */ diff --git a/parser-specs/commands.spec b/parser-specs/commands.spec index b3b5e338..475dc4bd 100644 --- a/parser-specs/commands.spec +++ b/parser-specs/commands.spec @@ -199,12 +199,14 @@ state FLOATING: floating = 'enable', 'disable', 'toggle' -> call cmd_floating($floating) -# mark [--toggle] +# mark [--add|--replace] [--toggle] state MARK: + mode = '--add', '--replace' + -> toggle = '--toggle' -> mark = string - -> call cmd_mark($mark, $toggle) + -> call cmd_mark($mark, $mode, $toggle) # unmark [mark] state UNMARK: diff --git a/src/commands.c b/src/commands.c index ab9a4135..a9b98c53 100644 --- a/src/commands.c +++ b/src/commands.c @@ -1003,10 +1003,10 @@ void cmd_workspace_name(I3_CMD, const char *name) { } /* - * Implementation of 'mark [--toggle] ' + * Implementation of 'mark [--add|--replace] [--toggle] ' * */ -void cmd_mark(I3_CMD, const char *mark, const char *toggle) { +void cmd_mark(I3_CMD, const char *mark, const char *mode, const char *toggle) { HANDLE_EMPTY_MATCH; owindow *current = TAILQ_FIRST(&owindows); @@ -1022,10 +1022,12 @@ void cmd_mark(I3_CMD, const char *mark, const char *toggle) { } DLOG("matching: %p / %s\n", current->con, current->con->name); + + mark_mode_t mark_mode = (mode == NULL || strcmp(mode, "--replace") == 0) ? MM_REPLACE : MM_ADD; if (toggle != NULL) { - con_mark_toggle(current->con, mark); + con_mark_toggle(current->con, mark, mark_mode); } else { - con_mark(current->con, mark); + con_mark(current->con, mark, mark_mode); } cmd_output->needs_tree_render = true; diff --git a/src/con.c b/src/con.c index bf7dd545..9238acad 100644 --- a/src/con.c +++ b/src/con.c @@ -540,14 +540,14 @@ bool con_has_mark(Con *con, const char *mark) { * Otherwise, the mark is assigned to the container. * */ -void con_mark_toggle(Con *con, const char *mark) { +void con_mark_toggle(Con *con, const char *mark, mark_mode_t mode) { assert(con != NULL); DLOG("Toggling mark \"%s\" on con = %p.\n", mark, con); if (con_has_mark(con, mark)) { con_unmark(mark); } else { - con_mark(con, mark); + con_mark(con, mark, mode); } } @@ -555,11 +555,19 @@ void con_mark_toggle(Con *con, const char *mark) { * Assigns a mark to the container. * */ -void con_mark(Con *con, const char *mark) { +void con_mark(Con *con, const char *mark, mark_mode_t mode) { assert(con != NULL); DLOG("Setting mark \"%s\" on con = %p.\n", mark, con); con_unmark(mark); + if (mode == MM_REPLACE) { + DLOG("Removing all existing marks on con = %p.\n", con); + + mark_t *current; + TAILQ_FOREACH(current, &(con->marks_head), marks) { + con_unmark(current->name); + } + } mark_t *new = scalloc(1, sizeof(mark_t)); new->name = sstrdup(mark); diff --git a/src/load_layout.c b/src/load_layout.c index 68c4f4a2..dc84c607 100644 --- a/src/load_layout.c +++ b/src/load_layout.c @@ -246,7 +246,7 @@ static int json_string(void *ctx, const unsigned char *val, size_t len) { char *mark; sasprintf(&mark, "%.*s", (int)len, val); - con_mark(json_node, mark); + con_mark(json_node, mark, MM_ADD); } else { if (strcasecmp(last_key, "name") == 0) { json_node->name = scalloc(len + 1, 1); @@ -354,7 +354,7 @@ static int json_string(void *ctx, const unsigned char *val, size_t len) { char *buf = NULL; sasprintf(&buf, "%.*s", (int)len, val); - con_mark(json_node, buf); + con_mark(json_node, buf, MM_REPLACE); } else if (strcasecmp(last_key, "floating") == 0) { char *buf = NULL; sasprintf(&buf, "%.*s", (int)len, val); diff --git a/testcases/t/210-mark-unmark.t b/testcases/t/210-mark-unmark.t index 39fc0904..446d5465 100644 --- a/testcases/t/210-mark-unmark.t +++ b/testcases/t/210-mark-unmark.t @@ -118,7 +118,17 @@ cmd 'mark --toggle important'; is_deeply(get_mark_for_window_on_workspace($tmp, $con), [ 'important' ], 'container now has the mark'); ############################################################## -# 7: mark a con, toggle the mark on another con, +# 7: mark a con, toggle a different mark, check it is marked +# with the new mark +############################################################## + +$con = open_window; +cmd 'mark boring'; +cmd 'mark --replace --toggle important'; +is_deeply(get_mark_for_window_on_workspace($tmp, $con), [ 'important' ], 'container has the most recent mark'); + +############################################################## +# 8: mark a con, toggle the mark on another con, # check only the latter has the mark ############################################################## @@ -133,7 +143,7 @@ is_deeply(get_mark_for_window_on_workspace($tmp, $first), [ 'important' ], 'left ok(!get_mark_for_window_on_workspace($tmp, $second), 'second containr no longer has the mark'); ############################################################## -# 8: try to mark two cons with the same mark and check that +# 9: try to mark two cons with the same mark and check that # it fails ############################################################## diff --git a/testcases/t/255-multiple-marks.t b/testcases/t/255-multiple-marks.t index d6d86e23..925e39db 100644 --- a/testcases/t/255-multiple-marks.t +++ b/testcases/t/255-multiple-marks.t @@ -38,8 +38,8 @@ sub get_mark_for_window_on_workspace { $ws = fresh_workspace; $con = open_window; -cmd 'mark A'; -cmd 'mark B'; +cmd 'mark --add A'; +cmd 'mark --add B'; is_deeply(sort(get_marks()), [ 'A', 'B' ], 'both marks exist'); is_deeply(get_mark_for_window_on_workspace($ws, $con), [ 'A', 'B' ], 'both marks are on the same window'); @@ -54,9 +54,9 @@ $ws = fresh_workspace; $con = open_window; cmd 'mark A'; -cmd 'mark --toggle B'; +cmd 'mark --add --toggle B'; is_deeply(get_mark_for_window_on_workspace($ws, $con), [ 'A', 'B' ], 'both marks are on the same window'); -cmd 'mark --toggle B'; +cmd 'mark --add --toggle B'; is_deeply(get_mark_for_window_on_workspace($ws, $con), [ 'A' ], 'only mark B has been removed'); cmd 'unmark'; @@ -67,9 +67,9 @@ cmd 'unmark'; $ws = fresh_workspace; $con = open_window; -cmd 'mark A'; -cmd 'mark B'; -cmd 'mark C'; +cmd 'mark --add A'; +cmd 'mark --add B'; +cmd 'mark --add C'; cmd 'unmark B'; is_deeply(get_mark_for_window_on_workspace($ws, $con), [ 'A', 'C' ], 'only mark B has been removed'); @@ -82,11 +82,11 @@ cmd 'unmark'; $ws = fresh_workspace; $con = open_window; -cmd 'mark A'; -cmd 'mark B'; +cmd 'mark --add A'; +cmd 'mark --add B'; open_window; -cmd '[con_mark=B] mark C'; +cmd '[con_mark=B] mark --add C'; is_deeply(get_mark_for_window_on_workspace($ws, $con), [ 'A', 'B', 'C' ], 'matching on a mark works with multiple marks'); cmd 'unmark'; From 9537ada5ad8ec1f43a3dbdf69d92cc6b0dd0ac0b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ingo=20B=C3=BCrk?= Date: Tue, 20 Oct 2015 08:14:42 +0200 Subject: [PATCH 045/187] Make 'unmark' aware of matched windows. This patch allows using 'unmark' on matched windows. The old behavior of applying it to all windows if no criteria were specified is kept. relates to #2014 --- docs/userguide | 18 ++++++++++----- include/con.h | 8 ++++--- src/commands.c | 9 +++++++- src/con.c | 59 +++++++++++++++++++++++++++----------------------- 4 files changed, 58 insertions(+), 36 deletions(-) diff --git a/docs/userguide b/docs/userguide index e07e544d..21caae6e 100644 --- a/docs/userguide +++ b/docs/userguide @@ -2132,11 +2132,19 @@ unmark ---------------------------------------------- *Example (in a terminal)*: ------------------------------- -$ i3-msg mark irssi -$ i3-msg '[con_mark="irssi"] focus' -$ i3-msg unmark irssi ------------------------------- +--------------------------------------------------------- +# marks the focused container +mark irssi + +# focus the container with the mark "irssi" +'[con_mark="irssi"] focus' + +# remove the mark "irssi" from whichever container has it +unmark irssi + +# remove all marks on all firefox windows +[class="(?i)firefox"] unmark +--------------------------------------------------------- /////////////////////////////////////////////////////////////////// TODO: make i3-input replace %s diff --git a/include/con.h b/include/con.h index b448b8c2..1c8e341b 100644 --- a/include/con.h +++ b/include/con.h @@ -166,12 +166,14 @@ void con_mark_toggle(Con *con, const char *mark, mark_mode_t mode); */ void con_mark(Con *con, const char *mark, mark_mode_t mode); -/** - * If mark is NULL, this removes all existing marks. +/* + * Removes marks from containers. + * If con is NULL, all containers are considered. + * If name is NULL, this removes all existing marks. * Otherwise, it will only remove the given mark (if it is present). * */ -void con_unmark(const char *mark); +void con_unmark(Con *con, const char *name); /** * Returns the first container below 'con' which wants to swallow this window diff --git a/src/commands.c b/src/commands.c index a9b98c53..f1cc969b 100644 --- a/src/commands.c +++ b/src/commands.c @@ -1040,7 +1040,14 @@ void cmd_mark(I3_CMD, const char *mark, const char *mode, const char *toggle) { * */ void cmd_unmark(I3_CMD, const char *mark) { - con_unmark(mark); + if (match_is_empty(current_match)) { + con_unmark(NULL, mark); + } else { + owindow *current; + TAILQ_FOREACH(current, &owindows, owindows) { + con_unmark(current->con, mark); + } + } cmd_output->needs_tree_render = true; // XXX: default reply for now, make this a better reply diff --git a/src/con.c b/src/con.c index 9238acad..cebe0a7e 100644 --- a/src/con.c +++ b/src/con.c @@ -545,7 +545,7 @@ void con_mark_toggle(Con *con, const char *mark, mark_mode_t mode) { DLOG("Toggling mark \"%s\" on con = %p.\n", mark, con); if (con_has_mark(con, mark)) { - con_unmark(mark); + con_unmark(con, mark); } else { con_mark(con, mark, mode); } @@ -559,13 +559,13 @@ void con_mark(Con *con, const char *mark, mark_mode_t mode) { assert(con != NULL); DLOG("Setting mark \"%s\" on con = %p.\n", mark, con); - con_unmark(mark); + con_unmark(NULL, mark); if (mode == MM_REPLACE) { DLOG("Removing all existing marks on con = %p.\n", con); mark_t *current; TAILQ_FOREACH(current, &(con->marks_head), marks) { - con_unmark(current->name); + con_unmark(con, current->name); } } @@ -577,47 +577,52 @@ void con_mark(Con *con, const char *mark, mark_mode_t mode) { } /* - * If mark is NULL, this removes all existing marks. + * Removes marks from containers. + * If con is NULL, all containers are considered. + * If name is NULL, this removes all existing marks. * Otherwise, it will only remove the given mark (if it is present). * */ -void con_unmark(const char *mark) { - Con *con; - if (mark == NULL) { +void con_unmark(Con *con, const char *name) { + Con *current; + if (name == NULL) { DLOG("Unmarking all containers.\n"); - TAILQ_FOREACH(con, &all_cons, all_cons) { - if (TAILQ_EMPTY(&(con->marks_head))) + TAILQ_FOREACH(current, &all_cons, all_cons) { + if (con != NULL && current != con) continue; - mark_t *current; - while (!TAILQ_EMPTY(&(con->marks_head))) { - current = TAILQ_FIRST(&(con->marks_head)); - FREE(current->name); - TAILQ_REMOVE(&(con->marks_head), current, marks); - FREE(current); + if (TAILQ_EMPTY(&(current->marks_head))) + continue; + + mark_t *mark; + while (!TAILQ_EMPTY(&(current->marks_head))) { + mark = TAILQ_FIRST(&(current->marks_head)); + FREE(mark->name); + TAILQ_REMOVE(&(current->marks_head), mark, marks); + FREE(mark); } - con->mark_changed = true; + current->mark_changed = true; } } else { - DLOG("Removing mark \"%s\".\n", mark); - con = con_by_mark(mark); - if (con == NULL) { + DLOG("Removing mark \"%s\".\n", name); + current = (con == NULL) ? con_by_mark(name) : con; + if (current == NULL) { DLOG("No container found with this mark, so there is nothing to do.\n"); return; } - DLOG("Found mark on con = %p. Removing it now.\n", con); - con->mark_changed = true; + DLOG("Found mark on con = %p. Removing it now.\n", current); + current->mark_changed = true; - mark_t *current; - TAILQ_FOREACH(current, &(con->marks_head), marks) { - if (strcmp(current->name, mark) != 0) + mark_t *mark; + TAILQ_FOREACH(mark, &(current->marks_head), marks) { + if (strcmp(mark->name, name) != 0) continue; - FREE(current->name); - TAILQ_REMOVE(&(con->marks_head), current, marks); - FREE(current); + FREE(mark->name); + TAILQ_REMOVE(&(current->marks_head), mark, marks); + FREE(mark); break; } } From 60158d31a29d9f70f8e950015f6c8105ac2f1fda Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ingo=20B=C3=BCrk?= Date: Thu, 22 Oct 2015 15:38:23 +0200 Subject: [PATCH 046/187] Introduce special value __focused__ for criterion con_id. This allows matching with [con_id=__focused__] unmark for commands that do not default to operating on the focused window if no criteria have been specified (such as unmark). relates to #2014 --- docs/userguide | 3 ++- src/match.c | 5 +++++ testcases/t/255-multiple-marks.t | 21 +++++++++++++++++++++ 3 files changed, 28 insertions(+), 1 deletion(-) diff --git a/docs/userguide b/docs/userguide index 21caae6e..0d63269f 100644 --- a/docs/userguide +++ b/docs/userguide @@ -1664,7 +1664,8 @@ con_mark:: mark. con_id:: Compares the i3-internal container ID, which you can get via the IPC - interface. Handy for scripting. + interface. Handy for scripting. Use the special value +\_\_focused__+ + to match only the currently focused window. The criteria +class+, +instance+, +role+, +title+, +workspace+ and +mark+ are actually regular expressions (PCRE). See +pcresyntax(3)+ or +perldoc perlre+ for diff --git a/src/match.c b/src/match.c index 67054dae..8da3c1ea 100644 --- a/src/match.c +++ b/src/match.c @@ -279,6 +279,11 @@ void match_parse_property(Match *match, const char *ctype, const char *cvalue) { } if (strcmp(ctype, "con_id") == 0) { + if (strcmp(cvalue, "__focused__") == 0) { + match->con_id = focused; + return; + } + char *end; long parsed = strtol(cvalue, &end, 10); if (parsed == LONG_MIN || diff --git a/testcases/t/255-multiple-marks.t b/testcases/t/255-multiple-marks.t index 925e39db..2269f0d8 100644 --- a/testcases/t/255-multiple-marks.t +++ b/testcases/t/255-multiple-marks.t @@ -91,6 +91,27 @@ is_deeply(get_mark_for_window_on_workspace($ws, $con), [ 'A', 'B', 'C' ], 'match cmd 'unmark'; +############################################################################### +# Verify that "unmark" can be matched on the focused window. +############################################################################### + +$ws = fresh_workspace; +$con = open_window; +cmd 'mark --add A'; +cmd 'mark --add B'; +open_window; +cmd 'mark --add C'; +cmd 'mark --add D'; + +is_deeply(sort(get_marks()), [ 'A', 'B', 'C', 'D' ], 'all marks exist'); + +cmd '[con_id=__focused__] unmark'; + +is_deeply(sort(get_marks()), [ 'A', 'B' ], 'marks on the unfocused window still exist'); +is_deeply(get_mark_for_window_on_workspace($ws, $con), [ 'A', 'B' ], 'matching on con_id=__focused__ works for unmark'); + +cmd 'unmark'; + ############################################################################### done_testing; From a0add1ba7342bf17672fbfd2cdb3f87390c69b0b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ingo=20B=C3=BCrk?= Date: Thu, 22 Oct 2015 16:11:08 +0200 Subject: [PATCH 047/187] Added background and border keys to the i3bar protocol. This patch adds two new status block keys, background and border, which define the respective colors for the status block. If not specified, the current behavior is kept, e.g., no background / border will be drawn. If the status block is marked urgent, the urgent color is prioritized. fixes #2022 --- docs/i3bar-protocol | 6 ++++++ i3bar/include/common.h | 2 ++ i3bar/src/child.c | 10 +++++++++ i3bar/src/xcb.c | 46 +++++++++++++++++++++++++++++++----------- 4 files changed, 52 insertions(+), 12 deletions(-) diff --git a/docs/i3bar-protocol b/docs/i3bar-protocol index 6cb04bf6..3ae14453 100644 --- a/docs/i3bar-protocol +++ b/docs/i3bar-protocol @@ -136,6 +136,10 @@ color:: when it is associated. Colors are specified in hex (like in HTML), starting with a leading hash sign. For example, +#ff0000+ means red. +background:: + Overrides the background color for this particular block. +border:: + Overrides the border color for this particular block. min_width:: The minimum width (in pixels) of the block. If the content of the +full_text+ key take less space than the specified min_width, the block @@ -207,6 +211,8 @@ An example of a block which uses all possible entries follows: "full_text": "E: 10.0.0.1 (1000 Mbit/s)", "short_text": "10.0.0.1", "color": "#00ff00", + "background": "#1c1c1c", + "border": "#ee0000", "min_width": 300, "align": "right", "urgent": false, diff --git a/i3bar/include/common.h b/i3bar/include/common.h index 4d2dbd35..7ab3441f 100644 --- a/i3bar/include/common.h +++ b/i3bar/include/common.h @@ -38,6 +38,8 @@ struct status_block { i3String *short_text; char *color; + char *background; + char *border; /* min_width can be specified either as a numeric value (in pixels) or as a * string. For strings, we set min_width to the measured text width of diff --git a/i3bar/src/child.c b/i3bar/src/child.c index 2b10be49..3570dde9 100644 --- a/i3bar/src/child.c +++ b/i3bar/src/child.c @@ -75,6 +75,8 @@ static void clear_statusline(struct statusline_head *head, bool free_resources) FREE(first->name); FREE(first->instance); FREE(first->min_width_str); + FREE(first->background); + FREE(first->border); } TAILQ_REMOVE(head, first, blocks); @@ -205,6 +207,14 @@ static int stdin_string(void *context, const unsigned char *val, size_t len) { sasprintf(&(ctx->block.color), "%.*s", len, val); return 1; } + if (strcasecmp(ctx->last_map_key, "background") == 0) { + sasprintf(&(ctx->block.background), "%.*s", len, val); + return 1; + } + if (strcasecmp(ctx->last_map_key, "border") == 0) { + sasprintf(&(ctx->block.border), "%.*s", len, val); + return 1; + } if (strcasecmp(ctx->last_map_key, "markup") == 0) { ctx->block.pango_markup = (len == strlen("pango") && !strncasecmp((const char *)val, "pango", strlen("pango"))); return 1; diff --git a/i3bar/src/xcb.c b/i3bar/src/xcb.c index 63b9863a..989b06c7 100644 --- a/i3bar/src/xcb.c +++ b/i3bar/src/xcb.c @@ -208,6 +208,9 @@ void refresh_statusline(bool use_short_text) { continue; block->width = predict_text_width(block->full_text); + /* Add padding for the border if we have to draw it. */ + if (block->border) + block->width += logical_px(2); /* Compute offset and append for text aligment in min_width. */ if (block->min_width <= block->width) { @@ -250,24 +253,43 @@ void refresh_statusline(bool use_short_text) { TAILQ_FOREACH(block, &statusline_head, blocks) { if (i3string_get_num_bytes(block->full_text) == 0) continue; - color_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; + color_t fg_color = (block->color ? draw_util_hex_to_color(block->color) : colors.bar_fg); + int border_width = (block->border) ? logical_px(1) : 0; + if (block->border || block->background || block->urgent) { + if (block->urgent) + fg_color = colors.urgent_ws_fg; - /* Draw the background */ - draw_util_rectangle(&statusline_surface, colors.urgent_ws_bg, - x - logical_px(2), - logical_px(1), - block->width + logical_px(4), + /* Let's determine the colors first. */ + color_t border_color = colors.bar_bg; + color_t bg_color = colors.bar_bg; + if (block->urgent) { + border_color = colors.urgent_ws_border; + bg_color = colors.urgent_ws_bg; + } else { + if (block->border) + border_color = draw_util_hex_to_color(block->border); + + if (block->background) + bg_color = draw_util_hex_to_color(block->background); + } + + /* Draw the border. */ + draw_util_rectangle(&statusline_surface, border_color, + x, logical_px(1), + block->width + block->x_offset + block->x_append, bar_height - logical_px(2)); - } else { - fg_color = (block->color ? draw_util_hex_to_color(block->color) : colors.bar_fg); + + /* Draw the background. */ + draw_util_rectangle(&statusline_surface, bg_color, + x + border_width, + logical_px(1) + border_width, + block->width + block->x_offset + block->x_append - 2 * border_width, + bar_height - 2 * border_width - logical_px(2)); } draw_util_text(block->full_text, &statusline_surface, fg_color, colors.bar_bg, - x + block->x_offset, logical_px(ws_voff_px), block->width); + x + block->x_offset + border_width, logical_px(ws_voff_px), block->width - 2 * border_width); x += block->width + block->sep_block_width + block->x_offset + block->x_append; /* If this is not the last block, draw a separator. */ From f67678157630702e46d99e82fa549d0829e131db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ingo=20B=C3=BCrk?= Date: Fri, 23 Oct 2015 00:38:11 +0200 Subject: [PATCH 048/187] Remove broken and unused debug borders. --- src/render.c | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/src/render.c b/src/render.c index f8c49c38..9fa40f03 100644 --- a/src/render.c +++ b/src/render.c @@ -21,10 +21,6 @@ static void render_con_stacked(Con *con, Con *child, render_params *p, int i); static void render_con_tabbed(Con *con, Con *child, render_params *p, int i); static void render_con_dockarea(Con *con, Con *child, render_params *p); -/* change this to 'true' if you want to have additional borders around every - * container (for debugging purposes) */ -static bool show_debug_borders = false; - /* * Returns the height for the decorations */ @@ -54,15 +50,6 @@ void render_con(Con *con, bool render_fullscreen) { (render_fullscreen ? "fullscreen " : ""), con, con->name, con->layout, params.children); - /* Display a border if this is a leaf node. For container nodes, we don’t - * draw borders (except when in debug mode) */ - if (show_debug_borders) { - params.rect.x += 2; - params.rect.y += 2; - params.rect.width -= 2 * 2; - params.rect.height -= 2 * 2; - } - int i = 0; con->mapped = true; From 7270206e24d53dfc927e7a965dec30b66b7c7ff5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ingo=20B=C3=BCrk?= Date: Fri, 23 Oct 2015 23:36:37 +0200 Subject: [PATCH 049/187] Added --no-auto-back-and-forth to workspace commands. This patch introduces the --no-auto-back-and-forth flag to both of workspace --no-auto-back-and-forth workspace --no-auto-back-and-forth number This flag will only have an effect if the back_and_forth feature is enabled. If passed, the feature will be ignored for this particular call only. fixes #2028 --- docs/userguide | 11 +++-- include/commands.h | 8 ++-- parser-specs/commands.spec | 10 +++-- src/commands.c | 15 ++++--- testcases/t/187-commands-parser.t | 16 +++---- testcases/t/256-no-auto-back-and-forth.t | 57 ++++++++++++++++++++++++ 6 files changed, 91 insertions(+), 26 deletions(-) create mode 100644 testcases/t/256-no-auto-back-and-forth.t diff --git a/docs/userguide b/docs/userguide index 0d63269f..e419fcec 100644 --- a/docs/userguide +++ b/docs/userguide @@ -1884,8 +1884,11 @@ for_window [instance=notepad] sticky enable === Changing (named) workspaces/moving to workspaces To change to a specific workspace, use the +workspace+ command, followed by the -number or name of the workspace. To move containers to specific workspaces, use -+move container to workspace+. +number or name of the workspace. Pass the optional flag ++--no-auto-back-and-forth+ to disable <> for this specific call +only. + +To move containers to specific workspaces, use +move container to workspace+. You can also switch to the next and previous workspace with the commands +workspace next+ and +workspace prev+, which is handy, for example, if you have @@ -1916,8 +1919,8 @@ workspace using +move container to workspace back_and_forth+. ----------------------------------- workspace next|prev|next_on_output|prev_on_output workspace back_and_forth -workspace -workspace number +workspace [--no-auto-back-and-forth] +workspace [--no-auto-back-and-forth] number move [window|container] [to] workspace move [window|container] [to] workspace number diff --git a/include/commands.h b/include/commands.h index d3485f15..9201a60c 100644 --- a/include/commands.h +++ b/include/commands.h @@ -97,10 +97,10 @@ void cmd_append_layout(I3_CMD, const char *path); void cmd_workspace(I3_CMD, const char *which); /** - * Implementation of 'workspace number ' + * Implementation of 'workspace [--no-auto-back-and-forth] number ' * */ -void cmd_workspace_number(I3_CMD, const char *which); +void cmd_workspace_number(I3_CMD, const char *which, const char *no_auto_back_and_forth); /** * Implementation of 'workspace back_and_forth'. @@ -109,10 +109,10 @@ void cmd_workspace_number(I3_CMD, const char *which); void cmd_workspace_back_and_forth(I3_CMD); /** - * Implementation of 'workspace ' + * Implementation of 'workspace [--no-auto-back-and-forth] ' * */ -void cmd_workspace_name(I3_CMD, const char *name); +void cmd_workspace_name(I3_CMD, const char *name, const char *no_auto_back_and_forth); /** * Implementation of 'mark [--add|--replace] [--toggle] ' diff --git a/parser-specs/commands.spec b/parser-specs/commands.spec index 475dc4bd..68a2667f 100644 --- a/parser-specs/commands.spec +++ b/parser-specs/commands.spec @@ -117,9 +117,11 @@ state APPEND_LAYOUT: # workspace next|prev|next_on_output|prev_on_output # workspace back_and_forth -# workspace -# workspace number +# workspace [--no-auto-back-and-forth] +# workspace [--no-auto-back-and-forth] number state WORKSPACE: + no_auto_back_and_forth = '--no-auto-back-and-forth' + -> direction = 'next_on_output', 'prev_on_output', 'next', 'prev' -> call cmd_workspace($direction) 'back_and_forth' @@ -127,11 +129,11 @@ state WORKSPACE: 'number' -> WORKSPACE_NUMBER workspace = string - -> call cmd_workspace_name($workspace) + -> call cmd_workspace_name($workspace, $no_auto_back_and_forth) state WORKSPACE_NUMBER: workspace = string - -> call cmd_workspace_number($workspace) + -> call cmd_workspace_number($workspace, $no_auto_back_and_forth) # focus left|right|up|down # focus output diff --git a/src/commands.c b/src/commands.c index f1cc969b..f4436d1b 100644 --- a/src/commands.c +++ b/src/commands.c @@ -917,10 +917,11 @@ void cmd_workspace(I3_CMD, const char *which) { } /* - * Implementation of 'workspace number ' + * Implementation of 'workspace [--no-auto-back-and-forth] number ' * */ -void cmd_workspace_number(I3_CMD, const char *which) { +void cmd_workspace_number(I3_CMD, const char *which, const char *_no_auto_back_and_forth) { + const bool no_auto_back_and_forth = (_no_auto_back_and_forth != NULL); Con *output, *workspace = NULL; if (con_get_fullscreen_con(croot, CF_GLOBAL)) { @@ -948,7 +949,7 @@ void cmd_workspace_number(I3_CMD, const char *which) { cmd_output->needs_tree_render = true; return; } - if (maybe_back_and_forth(cmd_output, workspace->name)) + if (!no_auto_back_and_forth && maybe_back_and_forth(cmd_output, workspace->name)) return; workspace_show(workspace); @@ -976,10 +977,12 @@ void cmd_workspace_back_and_forth(I3_CMD) { } /* - * Implementation of 'workspace ' + * Implementation of 'workspace [--no-auto-back-and-forth] ' * */ -void cmd_workspace_name(I3_CMD, const char *name) { +void cmd_workspace_name(I3_CMD, const char *name, const char *_no_auto_back_and_forth) { + const bool no_auto_back_and_forth = (_no_auto_back_and_forth != NULL); + if (strncasecmp(name, "__", strlen("__")) == 0) { LOG("You cannot switch to the i3-internal workspaces (\"%s\").\n", name); ysuccess(false); @@ -993,7 +996,7 @@ void cmd_workspace_name(I3_CMD, const char *name) { } DLOG("should switch to workspace %s\n", name); - if (maybe_back_and_forth(cmd_output, name)) + if (!no_auto_back_and_forth && maybe_back_and_forth(cmd_output, name)) return; workspace_show_by_name(name); diff --git a/testcases/t/187-commands-parser.t b/testcases/t/187-commands-parser.t index a56d668e..5c0cc99f 100644 --- a/testcases/t/187-commands-parser.t +++ b/testcases/t/187-commands-parser.t @@ -131,11 +131,11 @@ is(parser_calls('[con_mark="yay"] focus'), # commands being parsed due to the configuration, people might send IPC # commands with leading or trailing newlines. is(parser_calls("workspace test\n"), - 'cmd_workspace_name(test)', + 'cmd_workspace_name(test, (null))', 'trailing whitespace stripped off ok'); is(parser_calls("\nworkspace test"), - 'cmd_workspace_name(test)', + 'cmd_workspace_name(test, (null))', 'trailing whitespace stripped off ok'); ################################################################################ @@ -187,27 +187,27 @@ is(parser_calls('move something to somewhere'), ################################################################################ is(parser_calls('workspace "foo"'), - 'cmd_workspace_name(foo)', + 'cmd_workspace_name(foo, (null))', 'Command with simple double quotes ok'); is(parser_calls('workspace "foo'), - 'cmd_workspace_name(foo)', + 'cmd_workspace_name(foo, (null))', 'Command without ending double quotes ok'); is(parser_calls('workspace "foo \"bar"'), - 'cmd_workspace_name(foo "bar)', + 'cmd_workspace_name(foo "bar, (null))', 'Command with escaped double quotes ok'); is(parser_calls('workspace "foo \\'), - 'cmd_workspace_name(foo \\)', + 'cmd_workspace_name(foo \\, (null))', 'Command with single backslash in the end ok'); is(parser_calls('workspace "foo\\\\bar"'), - 'cmd_workspace_name(foo\\bar)', + 'cmd_workspace_name(foo\\bar, (null))', 'Command with escaped backslashes ok'); is(parser_calls('workspace "foo\\\\\\"bar"'), - 'cmd_workspace_name(foo\\"bar)', + 'cmd_workspace_name(foo\\"bar, (null))', 'Command with escaped double quotes after escaped backslashes ok'); ################################################################################ diff --git a/testcases/t/256-no-auto-back-and-forth.t b/testcases/t/256-no-auto-back-and-forth.t new file mode 100644 index 00000000..770754d1 --- /dev/null +++ b/testcases/t/256-no-auto-back-and-forth.t @@ -0,0 +1,57 @@ +#!perl +# vim:ts=4:sw=4:expandtab +# +# Please read the following documents before working on tests: +# • http://build.i3wm.org/docs/testsuite.html +# (or docs/testsuite) +# +# • http://build.i3wm.org/docs/lib-i3test.html +# (alternatively: perldoc ./testcases/lib/i3test.pm) +# +# • http://build.i3wm.org/docs/ipc.html +# (or docs/ipc) +# +# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# (unless you are already familiar with Perl) +# +# Test for the --no-auto-back-and-forth flag. +# Ticket: #2028 +use i3test; + +my ($first, $second, $third); +$first = "1:first"; +$second = "2:second"; +$third = "3:third"; + +############################################################################### +# Switching to another workspace when passing --no-auto-back-and-forth works +# as if the flag wasn't set. +############################################################################### + +cmd qq|workspace "$first"|; +ok(get_ws($first)->{focused}, 'first workspace is focused'); + +cmd qq|workspace --no-auto-back-and-forth "$second"|; +ok(get_ws($second)->{focused}, 'second workspace is focused'); + +cmd qq|workspace --no-auto-back-and-forth number "$third"|; +ok(get_ws($third)->{focused}, 'third workspace is focused'); + +############################################################################### +# Switching to the focused workspace when passing --no-auto-back-and-forth +# is a no-op. +############################################################################### + +cmd qq|workspace "$second"|; +cmd qq|workspace "$first"|; +ok(get_ws($first)->{focused}, 'first workspace is focused'); + +cmd qq|workspace --no-auto-back-and-forth "$first"|; +ok(get_ws($first)->{focused}, 'first workspace is still focused'); + +cmd qq|workspace --no-auto-back-and-forth number "$first"|; +ok(get_ws($first)->{focused}, 'first workspace is still focused'); + +############################################################################### + +done_testing; From 10b1b394784ba4a2d84b24aad11368730e170663 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ingo=20B=C3=BCrk?= Date: Sun, 25 Oct 2015 13:03:56 +0100 Subject: [PATCH 050/187] Make resize grow|shrink width|height work for a nested split in the same direction. Suppose a horizontally oriented workspace has two windows open. Issuing "split h" on the right window creates a nested horizontal container. If we now resize in "width" direction, the resize doesn't work because it will only operate on the current parent, which is a container with only one child, so the resize command exits. This is unexpected behavior from a user point of view. Hence, with this patch, we ensure that we don't just go up the tree until we find a parent with the correct orientation, but also keep going if that parent has only a single child. fixes #2015 --- src/commands.c | 2 +- testcases/t/141-resize.t | 15 +++++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/src/commands.c b/src/commands.c index f1cc969b..234d1df9 100644 --- a/src/commands.c +++ b/src/commands.c @@ -615,7 +615,7 @@ static bool cmd_resize_tiling_width_height(I3_CMD, Con *current, const char *way while (current->type != CT_WORKSPACE && current->type != CT_FLOATING_CON && - con_orientation(current->parent) != search_orientation) + (con_orientation(current->parent) != search_orientation || con_num_children(current->parent) == 1)) current = current->parent; /* get the default percentage */ diff --git a/testcases/t/141-resize.t b/testcases/t/141-resize.t index c5e61a32..44ad9bbf 100644 --- a/testcases/t/141-resize.t +++ b/testcases/t/141-resize.t @@ -141,6 +141,21 @@ cmp_float($nodes->[1]->{percent}, 0.166666666666667, 'second window got 16%'); cmp_float($nodes->[2]->{percent}, 0.166666666666667, 'third window got 16%'); cmp_float($nodes->[3]->{percent}, 0.50, 'fourth window got 50%'); +################################################################################ +# Check that the resize grow/shrink width/height syntax works if a nested split +# was set on the container, but no sibling has been opened yet. See #2015. +################################################################################ + +$tmp = fresh_workspace; +$left = open_window; +$right = open_window; + +cmd 'split h'; +cmd 'resize grow width 10px or 25 ppt'; + +($nodes, $focus) = get_ws_content($tmp); +cmp_float($nodes->[0]->{percent}, 0.25, 'left window got 25%'); +cmp_float($nodes->[1]->{percent}, 0.75, 'right window got 75%'); ############################################################ # checks that resizing floating windows works From ac4ac941819b25243ede6420c8fa8f56b9e3c399 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ingo=20B=C3=BCrk?= Date: Sun, 25 Oct 2015 14:25:55 +0100 Subject: [PATCH 051/187] Fixed logging statement. Assignments don't necessarily represent workspace assignments, but could also be used, e.g., for no_focus. Hence, there's no point in logging dest.workspace for all assignments. --- src/assignments.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/assignments.c b/src/assignments.c index babe890e..9de50e12 100644 --- a/src/assignments.c +++ b/src/assignments.c @@ -76,7 +76,7 @@ Assignment *assignment_for(i3Window *window, int type) { if ((type != A_ANY && (assignment->type & type) == 0) || !match_matches_window(&(assignment->match), window)) continue; - DLOG("got a matching assignment (to %s)\n", assignment->dest.workspace); + DLOG("got a matching assignment\n"); return assignment; } From ad10a366d6c922ce172b7a72e46fa844f02bcfcb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ingo=20B=C3=BCrk?= Date: Sun, 25 Oct 2015 14:27:08 +0100 Subject: [PATCH 052/187] Mark assignment as run before executing it. We need to store the information that an assignment was run for a window before actually executing the command. Otherwise, if the command causes a change that causes assignments to be run again, the window might be matched again, causing an infinite loop and hence i3 to freeze or crash. --- src/assignments.c | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/assignments.c b/src/assignments.c index 9de50e12..6c563357 100644 --- a/src/assignments.c +++ b/src/assignments.c @@ -40,6 +40,13 @@ void run_assignments(i3Window *window) { if (skip) continue; + /* Store that we ran this assignment to not execute it again. We have + * to do this before running the actual command to prevent infinite + * loops. */ + window->nr_assignments++; + window->ran_assignments = srealloc(window->ran_assignments, sizeof(Assignment *) * window->nr_assignments); + window->ran_assignments[window->nr_assignments - 1] = current; + DLOG("matching assignment, would do:\n"); if (current->type == A_COMMAND) { DLOG("execute command %s\n", current->dest.command); @@ -53,11 +60,6 @@ void run_assignments(i3Window *window) { command_result_free(result); } - - /* Store that we ran this assignment to not execute it again */ - window->nr_assignments++; - window->ran_assignments = srealloc(window->ran_assignments, sizeof(Assignment *) * window->nr_assignments); - window->ran_assignments[window->nr_assignments - 1] = current; } /* If any of the commands required re-rendering, we will do that now. */ From 071f11f9b9c010e9522d2a461b8f3e2f091f0abf Mon Sep 17 00:00:00 2001 From: David Simon Date: Mon, 26 Oct 2015 11:27:09 -0400 Subject: [PATCH 053/187] Use separate buffers for i3bar statusline for each workspace, track short and long renders separately, fixes #1824 --- i3bar/include/common.h | 16 ++- i3bar/include/outputs.h | 8 +- i3bar/src/ipc.c | 2 - i3bar/src/outputs.c | 6 + i3bar/src/xcb.c | 259 ++++++++++++++++++++-------------------- 5 files changed, 156 insertions(+), 135 deletions(-) diff --git a/i3bar/include/common.h b/i3bar/include/common.h index 7ab3441f..aa706bbe 100644 --- a/i3bar/include/common.h +++ b/i3bar/include/common.h @@ -31,6 +31,14 @@ typedef enum { ALIGN_RIGHT } blockalign_t; +/* This data structure describes the way a status block should be rendered. These + * variables are updated each time the statusline is re-rendered. */ +struct status_block_render_desc { + uint32_t width; + uint32_t x_offset; + uint32_t x_append; +}; + /* This data structure represents one JSON dictionary, multiple of these make * up one status line. */ struct status_block { @@ -56,11 +64,9 @@ struct status_block { /* The amount of pixels necessary to render a separater after the block. */ uint32_t sep_block_width; - /* The amount of pixels necessary to render this block. These variables are - * only temporarily used in refresh_statusline(). */ - uint32_t width; - uint32_t x_offset; - uint32_t x_append; + /* Continuously-updated information on how to render this status block. */ + struct status_block_render_desc full_render; + struct status_block_render_desc short_render; /* Optional */ char *name; diff --git a/i3bar/include/outputs.h b/i3bar/include/outputs.h index 87cc023e..ec09e764 100644 --- a/i3bar/include/outputs.h +++ b/i3bar/include/outputs.h @@ -46,8 +46,14 @@ struct i3_output { int ws; /* The number of the currently visible ws */ rect rect; /* The rect (relative to the root window) */ - /* Off-screen buffer for preliminary rendering. */ + /* Off-screen buffer for preliminary rendering of the bar. */ surface_t buffer; + /* Off-screen buffer for pre-rendering the statusline, separated to make clipping easier. */ + surface_t statusline_buffer; + /* How much of statusline_buffer's horizontal space was used on last statusline render. */ + int statusline_width; + /* Whether statusline block short texts where used on last statusline render. */ + bool statusline_short_text; /* The actual window on which we draw. */ surface_t bar; diff --git a/i3bar/src/ipc.c b/i3bar/src/ipc.c index eb48afea..1214954d 100644 --- a/i3bar/src/ipc.c +++ b/i3bar/src/ipc.c @@ -63,7 +63,6 @@ void got_output_reply(char *reply) { DLOG("Parsing outputs JSON...\n"); parse_outputs_json(reply); DLOG("Reconfiguring windows...\n"); - realloc_sl_buffer(); reconfig_windows(false); i3_output *o_walk; @@ -175,7 +174,6 @@ void got_bar_config_update(char *event) { /* update fonts and colors */ init_xcb_late(config.fontname); init_colors(&(config.colors)); - realloc_sl_buffer(); draw_bars(false); } diff --git a/i3bar/src/outputs.c b/i3bar/src/outputs.c index d0d175ca..df3bc69b 100644 --- a/i3bar/src/outputs.c +++ b/i3bar/src/outputs.c @@ -144,10 +144,16 @@ static int outputs_start_map_cb(void *params_) { if (params->cur_key == NULL) { new_output = smalloc(sizeof(i3_output)); new_output->name = NULL; + new_output->active = false; + new_output->primary = false; + new_output->visible = false; new_output->ws = 0, + new_output->statusline_width = 0; + new_output->statusline_short_text = false; memset(&new_output->rect, 0, sizeof(rect)); memset(&new_output->bar, 0, sizeof(surface_t)); memset(&new_output->buffer, 0, sizeof(surface_t)); + memset(&new_output->statusline_buffer, 0, sizeof(surface_t)); new_output->workspaces = smalloc(sizeof(struct ws_head)); TAILQ_INIT(new_output->workspaces); diff --git a/i3bar/src/xcb.c b/i3bar/src/xcb.c index 989b06c7..841570e2 100644 --- a/i3bar/src/xcb.c +++ b/i3bar/src/xcb.c @@ -74,11 +74,6 @@ int bar_height; int xkb_base; int mod_pressed = 0; -/* Because the statusline is the same on all outputs, we have - * global buffer to render it on */ -surface_t statusline_surface; -uint32_t statusline_width; - /* Event watchers, to interact with the user */ ev_prepare *xcb_prep; ev_check *xcb_chk; @@ -165,7 +160,7 @@ int get_tray_width(struct tc_head *trayclients) { * Draws a separator for the given block if necessary. * */ -static void draw_separator(uint32_t x, struct status_block *block) { +static void draw_separator(i3_output *output, uint32_t x, struct status_block *block) { uint32_t sep_offset = get_sep_offset(block); if (TAILQ_NEXT(block, blocks) == NULL || sep_offset == 0) return; @@ -173,7 +168,7 @@ static void draw_separator(uint32_t x, struct status_block *block) { uint32_t center_x = x - sep_offset; if (config.separator_symbol == NULL) { /* Draw a classic one pixel, vertical separator. */ - draw_util_rectangle(&statusline_surface, colors.sep_fg, + draw_util_rectangle(&output->statusline_buffer, colors.sep_fg, center_x, logical_px(sep_voff_px), logical_px(1), @@ -181,119 +176,138 @@ static void draw_separator(uint32_t x, struct status_block *block) { } 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, &statusline_surface, colors.sep_fg, colors.bar_bg, + draw_util_text(config.separator_symbol, &output->statusline_buffer, colors.sep_fg, colors.bar_bg, separator_x, logical_px(ws_voff_px), x - separator_x); } } -/* - * Redraws the statusline to the buffer - * - */ -void refresh_statusline(bool use_short_text) { +uint32_t predict_statusline_length(bool use_short_text) { + uint32_t width = 0; struct status_block *block; - uint32_t old_statusline_width = statusline_width; - statusline_width = 0; - - /* Predict the text width of all blocks (in pixels). */ TAILQ_FOREACH(block, &statusline_head, blocks) { - /* Try to use the shorter text if necessary and possible. */ + i3String *text = block->full_text; + struct status_block_render_desc *render = &block->full_render; if (use_short_text && block->short_text != NULL) { - I3STRING_FREE(block->full_text); - block->full_text = i3string_copy(block->short_text); + text = block->short_text; + render = &block->short_render; } - if (i3string_get_num_bytes(block->full_text) == 0) + if (i3string_get_num_bytes(text) == 0) continue; - block->width = predict_text_width(block->full_text); - /* Add padding for the border if we have to draw it. */ + render->width = predict_text_width(text); if (block->border) - block->width += logical_px(2); + render->width += logical_px(2); /* Compute offset and append for text aligment in min_width. */ - if (block->min_width <= block->width) { - block->x_offset = 0; - block->x_append = 0; + if (block->min_width <= render->width) { + render->x_offset = 0; + render->x_append = 0; } else { - uint32_t padding_width = block->min_width - block->width; + uint32_t padding_width = block->min_width - render->width; switch (block->align) { case ALIGN_LEFT: - block->x_append = padding_width; + render->x_append = padding_width; break; case ALIGN_RIGHT: - block->x_offset = padding_width; + render->x_offset = padding_width; break; case ALIGN_CENTER: - block->x_offset = padding_width / 2; - block->x_append = padding_width / 2 + padding_width % 2; + render->x_offset = padding_width / 2; + render->x_append = padding_width / 2 + padding_width % 2; break; } } + width += render->width + render->x_offset + render->x_append; + /* If this is not the last block, add some pixels for a separator. */ if (TAILQ_NEXT(block, blocks) != NULL) - statusline_width += block->sep_block_width; - - statusline_width += block->width + block->x_offset + block->x_append; + width += block->sep_block_width; } - /* If the statusline is bigger than our screen we need to make sure that - * the pixmap provides enough space, so re-allocate if the width grew */ - if (statusline_width > root_screen->width_in_pixels && - statusline_width > old_statusline_width) - realloc_sl_buffer(); + return width; +} - /* Clear the statusline pixmap. */ - draw_util_clear_surface(&statusline_surface, colors.bar_bg); +/* + * Redraws the statusline to the output's statusline_buffer + */ +void draw_statusline(i3_output *output, uint32_t clip_left, bool use_short_text) { + struct status_block *block; - /* Draw the text of each block. */ - uint32_t x = 0; + draw_util_clear_surface(&output->statusline_buffer, colors.bar_bg); + + /* 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 + * number INT_MAX-75, which is way outside the surface dimensions. Drawing + * to that x position is a no-op which XCB and Cairo safely ignore. Once x moves + * up by 75 and goes past INT_MAX, it will wrap around again to 0, and we start + * actually rendering content to the surface. */ + uint32_t x = 0 - clip_left; + + /* Draw the text of each block */ TAILQ_FOREACH(block, &statusline_head, blocks) { - if (i3string_get_num_bytes(block->full_text) == 0) + i3String *text = block->full_text; + struct status_block_render_desc *render = &block->full_render; + if (use_short_text && block->short_text != NULL) { + text = block->short_text; + render = &block->short_render; + } + + if (i3string_get_num_bytes(text) == 0) continue; - color_t fg_color = (block->color ? draw_util_hex_to_color(block->color) : colors.bar_fg); - int border_width = (block->border) ? logical_px(1) : 0; - if (block->border || block->background || block->urgent) { - if (block->urgent) - fg_color = colors.urgent_ws_fg; + color_t fg_color; + if (block->urgent) { + fg_color = colors.urgent_ws_fg; + } else if (block->color) { + fg_color = draw_util_hex_to_color(block->color); + } else { + fg_color = colors.bar_fg; + } + color_t bg_color = colors.bar_bg; + + 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 bg_color = colors.bar_bg; if (block->urgent) { border_color = colors.urgent_ws_border; bg_color = colors.urgent_ws_bg; } else { if (block->border) border_color = draw_util_hex_to_color(block->border); - if (block->background) bg_color = draw_util_hex_to_color(block->background); } /* Draw the border. */ - draw_util_rectangle(&statusline_surface, border_color, + draw_util_rectangle(&output->statusline_buffer, border_color, x, logical_px(1), - block->width + block->x_offset + block->x_append, + full_render_width, bar_height - logical_px(2)); /* Draw the background. */ - draw_util_rectangle(&statusline_surface, bg_color, + draw_util_rectangle(&output->statusline_buffer, bg_color, x + border_width, logical_px(1) + border_width, - block->width + block->x_offset + block->x_append - 2 * border_width, + full_render_width - 2 * border_width, bar_height - 2 * border_width - logical_px(2)); } - draw_util_text(block->full_text, &statusline_surface, fg_color, colors.bar_bg, - x + block->x_offset + border_width, logical_px(ws_voff_px), block->width - 2 * border_width); - x += block->width + block->sep_block_width + block->x_offset + block->x_append; + draw_util_text(text, &output->statusline_buffer, fg_color, bg_color, + x + render->x_offset + border_width, logical_px(ws_voff_px), + render->width - 2 * border_width); + x += full_render_width; /* If this is not the last block, draw a separator. */ - draw_separator(x, block); + if (TAILQ_NEXT(block, blocks) != NULL) { + x += block->sep_block_width; + draw_separator(output, x, block); + } } } @@ -426,7 +440,6 @@ void handle_button(xcb_button_press_event_t *event) { } int32_t x = event->event_x >= 0 ? event->event_x : 0; - int32_t original_x = x; DLOG("Got button %d\n", event->detail); @@ -449,21 +462,28 @@ void handle_button(xcb_button_press_event_t *event) { * 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(sb_hoff_px); + int offset = walk->rect.w - walk->statusline_width - tray_width - logical_px(sb_hoff_px); + int32_t statusline_x = x - offset; - x = original_x - offset; - if (x >= 0 && (size_t)x < statusline_width) { + if (statusline_x >= 0 && statusline_x < walk->statusline_width) { struct status_block *block; int sep_offset_remainder = 0; TAILQ_FOREACH(block, &statusline_head, blocks) { - if (i3string_get_num_bytes(block->full_text) == 0) + i3String *text = block->full_text; + struct status_block_render_desc *render = &block->full_render; + if (walk->statusline_short_text && block->short_text != NULL) { + text = block->short_text; + render = &block->short_render; + } + + if (i3string_get_num_bytes(text) == 0) continue; last_block_x = block_x; - block_x += block->width + block->x_offset + block->x_append + get_sep_offset(block) + sep_offset_remainder; + block_x += render->width + render->x_offset + render->x_append + get_sep_offset(block) + sep_offset_remainder; - if (x <= block_x && x >= last_block_x) { + 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); return; } @@ -471,7 +491,6 @@ void handle_button(xcb_button_press_event_t *event) { sep_offset_remainder = block->sep_block_width - get_sep_offset(block); } } - x = original_x; } /* If a custom command was specified for this mouse button, it overrides @@ -1147,17 +1166,6 @@ char *init_xcb_early() { colormap = root_screen->default_colormap; visual_type = get_visualtype(root_screen); - /* We draw the statusline to a seperate pixmap, because it looks the same on all bars and - * this way, we can choose to crop it */ - xcb_pixmap_t statusline_id = xcb_generate_id(xcb_connection); - xcb_void_cookie_t sl_pm_cookie = xcb_create_pixmap_checked(xcb_connection, - depth, - statusline_id, - xcb_root, - root_screen->width_in_pixels, - root_screen->height_in_pixels); - draw_util_surface_init(&statusline_surface, statusline_id, root_screen->width_in_pixels, root_screen->height_in_pixels); - /* The various watchers to communicate with xcb */ xcb_io = smalloc(sizeof(ev_io)); xcb_prep = smalloc(sizeof(ev_prepare)); @@ -1176,9 +1184,6 @@ char *init_xcb_early() { char *path = root_atom_contents("I3_SOCKET_PATH", xcb_connection, screen); - if (xcb_request_failed(sl_pm_cookie, "Could not allocate statusline buffer")) - exit(EXIT_FAILURE); - return path; } @@ -1510,29 +1515,6 @@ void destroy_window(i3_output *output) { output->bar.id = XCB_NONE; } -/* - * Reallocate the statusline buffer - * - */ -void realloc_sl_buffer(void) { - DLOG("Re-allocating statusline buffer, statusline_width = %d, root_screen->width_in_pixels = %d\n", - statusline_width, root_screen->width_in_pixels); - xcb_free_pixmap(xcb_connection, statusline_surface.id); - draw_util_surface_free(&statusline_surface); - - xcb_pixmap_t statusline_id = xcb_generate_id(xcb_connection); - xcb_void_cookie_t sl_pm_cookie = xcb_create_pixmap_checked(xcb_connection, - depth, - statusline_id, - xcb_root, - MAX(root_screen->width_in_pixels, statusline_width), - bar_height); - draw_util_surface_init(&statusline_surface, statusline_id, root_screen->width_in_pixels, root_screen->height_in_pixels); - - if (xcb_request_failed(sl_pm_cookie, "Could not allocate statusline buffer")) - exit(EXIT_FAILURE); -} - /* 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) { @@ -1600,6 +1582,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; values[0] = colors.bar_bg.colorpixel; @@ -1643,6 +1626,14 @@ void reconfig_windows(bool redraw_bars) { walk->rect.w, bar_height); + /* The double-buffer we use to render the statusline before copying to buffer */ + xcb_void_cookie_t slpm_cookie = xcb_create_pixmap_checked(xcb_connection, + depth, + statusline_buffer_id, + bar_id, + walk->rect.w, + bar_height); + /* Set the WM_CLASS and WM_NAME (we don't need UTF-8) atoms */ xcb_void_cookie_t class_cookie; class_cookie = xcb_change_property(xcb_connection, @@ -1680,6 +1671,7 @@ void reconfig_windows(bool redraw_bars) { 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); xcb_void_cookie_t strut_cookie = config_strut_partial(walk); @@ -1691,6 +1683,7 @@ void reconfig_windows(bool redraw_bars) { if (xcb_request_failed(win_cookie, "Could not create window") || xcb_request_failed(pm_cookie, "Could not create pixmap") || + xcb_request_failed(slpm_cookie, "Could not create statusline pixmap") || xcb_request_failed(dock_cookie, "Could not set dock mode") || xcb_request_failed(class_cookie, "Could not set WM_CLASS") || xcb_request_failed(name_cookie, "Could not set WM_NAME") || @@ -1737,6 +1730,9 @@ void reconfig_windows(bool redraw_bars) { DLOG("Destroying buffer for output %s\n", walk->name); xcb_free_pixmap(xcb_connection, walk->buffer.id); + DLOG("Destroying statusline buffer for output %s\n", walk->name); + xcb_free_pixmap(xcb_connection, walk->statusline_buffer.id); + 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.id, @@ -1759,10 +1755,20 @@ void reconfig_windows(bool redraw_bars) { walk->rect.w, bar_height); + DLOG("Recreating statusline buffer for output %s\n", walk->name); + xcb_void_cookie_t slpm_cookie = xcb_create_pixmap_checked(xcb_connection, + depth, + walk->statusline_buffer.id, + walk->bar.id, + 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); xcb_void_cookie_t map_cookie, umap_cookie; if (redraw_bars) { @@ -1787,6 +1793,7 @@ void reconfig_windows(bool redraw_bars) { if (xcb_request_failed(cfg_cookie, "Could not reconfigure window") || xcb_request_failed(chg_cookie, "Could not change window") || xcb_request_failed(pm_cookie, "Could not create pixmap") || + xcb_request_failed(slpm_cookie, "Could not create statusline pixmap") || xcb_request_failed(strut_cookie, "Could not set strut") || (redraw_bars && (xcb_request_failed(umap_cookie, "Could not unmap window") || (config.hide_on_modifier == M_DOCK && xcb_request_failed(map_cookie, "Could not map window"))))) { @@ -1802,14 +1809,14 @@ void reconfig_windows(bool redraw_bars) { */ void draw_bars(bool unhide) { DLOG("Drawing bars...\n"); - int workspace_width = 0; - /* Is the currently-rendered statusline using short_text items? */ - bool rendered_statusline_is_short = false; - refresh_statusline(false); + uint32_t full_statusline_width = predict_statusline_length(false); + uint32_t short_statusline_width = predict_statusline_length(true); i3_output *outputs_walk; SLIST_FOREACH(outputs_walk, outputs, slist) { + int workspace_width = 0; + if (!outputs_walk->active) { DLOG("Output %s inactive, skipping...\n", outputs_walk->name); continue; @@ -1906,30 +1913,28 @@ void draw_bars(bool unhide) { 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 clip_left = 0; + uint32_t statusline_width = full_statusline_width; + bool use_short_text = false; - /* If the statusline is too long, try to use short texts. */ if (statusline_width > max_statusline_width) { - /* If the currently rendered statusline is long, render a short status line */ - refresh_statusline(true); - rendered_statusline_is_short = true; - } else if (rendered_statusline_is_short) { - /* If the currently rendered statusline is short, render a long status line */ - refresh_statusline(false); - rendered_statusline_is_short = false; + statusline_width = short_statusline_width; + use_short_text = true; + if (statusline_width > max_statusline_width) { + clip_left = statusline_width - max_statusline_width; + } } - /* Luckily we already prepared a seperate pixmap containing the rendered - * statusline, we just have to copy the relevant parts to the relevant - * position */ - int visible_statusline_width = MIN(statusline_width, max_statusline_width); - int x_src = (int16_t)(statusline_width - visible_statusline_width); - int x_dest = (int16_t)(outputs_walk->rect.w - tray_width - logical_px(sb_hoff_px) - visible_statusline_width); + 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; - draw_util_copy_surface(&statusline_surface, &(outputs_walk->buffer), x_src, 0, - x_dest, 0, (int16_t)visible_statusline_width, (int16_t)bar_height); + draw_statusline(outputs_walk, clip_left, 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); + + outputs_walk->statusline_width = statusline_width; + outputs_walk->statusline_short_text = use_short_text; } - - workspace_width = 0; } /* Assure the bar is hidden/unhidden according to the specified hidden_state and mode */ From 3d6c76eb9340e2e541f2f7e3d54a5fb5458daa12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ingo=20B=C3=BCrk?= Date: Mon, 26 Oct 2015 18:16:21 +0100 Subject: [PATCH 054/187] Fix crash when trying to split and float a dock container. Since splitting a docking container was allowed and successful, the check to prevent floating it fails to work. This causes a crash because the workspace of the container cannot be determined as the dockarea is higher up in the tree than the workspace it belongs to. This patch extends to sanity check to nested dock containers when trying to float a container and also disallows manually splitting a docked container or changing its layout. fixes #2034 --- include/con.h | 6 ++++++ src/commands.c | 34 +++++++++++++++++++--------------- src/con.c | 14 ++++++++++++++ src/floating.c | 2 +- 4 files changed, 40 insertions(+), 16 deletions(-) diff --git a/include/con.h b/include/con.h index 1c8e341b..9ed9508a 100644 --- a/include/con.h +++ b/include/con.h @@ -112,6 +112,12 @@ bool con_is_internal(Con *con); */ bool con_is_floating(Con *con); +/** + * Returns true if the container is a docked container. + * + */ +bool con_is_docked(Con *con); + /** * Checks if the given container is either floating or inside some floating * container. It returns the FLOATING_CON container. diff --git a/src/commands.c b/src/commands.c index 7aab6422..7f196bdc 100644 --- a/src/commands.c +++ b/src/commands.c @@ -1188,16 +1188,18 @@ void cmd_move_workspace_to_output(I3_CMD, const char *name) { * */ void cmd_split(I3_CMD, const char *direction) { + HANDLE_EMPTY_MATCH; + owindow *current; - /* TODO: use matches */ LOG("splitting in direction %c\n", direction[0]); - if (match_is_empty(current_match)) - tree_split(focused, (direction[0] == 'v' ? VERT : HORIZ)); - else { - TAILQ_FOREACH(current, &owindows, owindows) { - DLOG("matching: %p / %s\n", current->con, current->con->name); - tree_split(current->con, (direction[0] == 'v' ? VERT : HORIZ)); + TAILQ_FOREACH(current, &owindows, owindows) { + if (con_is_docked(current->con)) { + ELOG("Cannot split a docked container, skipping.\n"); + continue; } + + DLOG("matching: %p / %s\n", current->con, current->con->name); + tree_split(current->con, (direction[0] == 'v' ? VERT : HORIZ)); } cmd_output->needs_tree_render = true; @@ -1524,9 +1526,10 @@ void cmd_move_direction(I3_CMD, const char *direction, long move_px) { * */ void cmd_layout(I3_CMD, const char *layout_str) { + HANDLE_EMPTY_MATCH; + if (strcmp(layout_str, "stacking") == 0) layout_str = "stacked"; - owindow *current; layout_t layout; /* default is a special case which will be handled in con_set_layout(). */ if (strcmp(layout_str, "default") == 0) @@ -1546,14 +1549,15 @@ void cmd_layout(I3_CMD, const char *layout_str) { DLOG("changing layout to %s (%d)\n", layout_str, layout); - /* check if the match is empty, not if the result is empty */ - if (match_is_empty(current_match)) - con_set_layout(focused, layout); - else { - TAILQ_FOREACH(current, &owindows, owindows) { - DLOG("matching: %p / %s\n", current->con, current->con->name); - con_set_layout(current->con, layout); + owindow *current; + TAILQ_FOREACH(current, &owindows, owindows) { + if (con_is_docked(current->con)) { + ELOG("cannot change layout of a docked container, skipping it.\n"); + continue; } + + DLOG("matching: %p / %s\n", current->con, current->con->name); + con_set_layout(current->con, layout); } cmd_output->needs_tree_render = true; diff --git a/src/con.c b/src/con.c index cebe0a7e..bd002d35 100644 --- a/src/con.c +++ b/src/con.c @@ -448,6 +448,20 @@ bool con_is_floating(Con *con) { return (con->floating >= FLOATING_AUTO_ON); } +/* + * Returns true if the container is a docked container. + * + */ +bool con_is_docked(Con *con) { + if (con->parent == NULL) + return false; + + if (con->parent->type == CT_DOCKAREA) + return true; + + return con_is_docked(con->parent); +} + /* * Checks if the given container is either floating or inside some floating * container. It returns the FLOATING_CON container. diff --git a/src/floating.c b/src/floating.c index d7a33067..77bc9e17 100644 --- a/src/floating.c +++ b/src/floating.c @@ -108,7 +108,7 @@ void floating_check_size(Con *floating_con) { void floating_enable(Con *con, bool automatic) { bool set_focus = (con == focused); - if (con->parent && con->parent->type == CT_DOCKAREA) { + if (con_is_docked(con)) { LOG("Container is a dock window, not enabling floating mode.\n"); return; } From 57a7ff301f7bdab85e1ad5ef331f87c209f49f2b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ingo=20B=C3=BCrk?= Date: Mon, 26 Oct 2015 22:38:06 +0100 Subject: [PATCH 055/187] Add --no-auto-back-and-forth for moving windows. This patch extends the previously introduced flag --no-auto-back-and-forth to also apply to move window to workspace move window to workspace number relates to #2028 --- docs/userguide | 8 ++--- include/commands.h | 8 ++--- parser-specs/commands.spec | 6 ++-- src/commands.c | 16 +++++---- testcases/t/187-commands-parser.t | 22 ++++++------ testcases/t/256-no-auto-back-and-forth.t | 43 +++++++++++++++++++++++- 6 files changed, 75 insertions(+), 28 deletions(-) diff --git a/docs/userguide b/docs/userguide index e419fcec..e866dfb1 100644 --- a/docs/userguide +++ b/docs/userguide @@ -1916,16 +1916,16 @@ back_and_forth+; likewise, you can move containers to the previously focused workspace using +move container to workspace back_and_forth+. *Syntax*: ------------------------------------ +-------------------------------------------------------------------------------- workspace next|prev|next_on_output|prev_on_output workspace back_and_forth workspace [--no-auto-back-and-forth] workspace [--no-auto-back-and-forth] number -move [window|container] [to] workspace -move [window|container] [to] workspace number +move [--no-auto-back-and-forth] [window|container] [to] workspace +move [--no-auto-back-and-forth] [window|container] [to] workspace number move [window|container] [to] workspace prev|next|current ------------------------------------ +-------------------------------------------------------------------------------- *Examples*: ------------------------- diff --git a/include/commands.h b/include/commands.h index 9201a60c..5e8dd05c 100644 --- a/include/commands.h +++ b/include/commands.h @@ -49,16 +49,16 @@ void cmd_move_con_to_workspace(I3_CMD, const char *which); void cmd_move_con_to_workspace_back_and_forth(I3_CMD); /** - * Implementation of 'move [window|container] [to] workspace '. + * Implementation of 'move [--no-auto-back-and-forth] [window|container] [to] workspace '. * */ -void cmd_move_con_to_workspace_name(I3_CMD, const char *name); +void cmd_move_con_to_workspace_name(I3_CMD, const char *name, const char *no_auto_back_and_forth); /** - * Implementation of 'move [window|container] [to] workspace number '. + * Implementation of 'move [--no-auto-back-and-forth] [window|container] [to] workspace number '. * */ -void cmd_move_con_to_workspace_number(I3_CMD, const char *which); +void cmd_move_con_to_workspace_number(I3_CMD, const char *which, const char *no_auto_back_and_forth); /** * Implementation of 'resize set [px] [px]'. diff --git a/parser-specs/commands.spec b/parser-specs/commands.spec index 68a2667f..98812132 100644 --- a/parser-specs/commands.spec +++ b/parser-specs/commands.spec @@ -308,6 +308,8 @@ state MOVE: -> 'to' -> + no_auto_back_and_forth = '--no-auto-back-and-forth' + -> 'workspace' -> MOVE_WORKSPACE 'output' @@ -345,11 +347,11 @@ state MOVE_WORKSPACE: 'number' -> MOVE_WORKSPACE_NUMBER workspace = string - -> call cmd_move_con_to_workspace_name($workspace) + -> call cmd_move_con_to_workspace_name($workspace, $no_auto_back_and_forth) state MOVE_WORKSPACE_NUMBER: number = string - -> call cmd_move_con_to_workspace_number($number) + -> call cmd_move_con_to_workspace_number($number, $no_auto_back_and_forth) state MOVE_TO_OUTPUT: output = string diff --git a/src/commands.c b/src/commands.c index 7aab6422..d6c0b788 100644 --- a/src/commands.c +++ b/src/commands.c @@ -403,16 +403,17 @@ void cmd_move_con_to_workspace_back_and_forth(I3_CMD) { } /* - * Implementation of 'move [window|container] [to] workspace '. + * Implementation of 'move [--no-auto-back-and-forth] [window|container] [to] workspace '. * */ -void cmd_move_con_to_workspace_name(I3_CMD, const char *name) { +void cmd_move_con_to_workspace_name(I3_CMD, const char *name, const char *_no_auto_back_and_forth) { if (strncasecmp(name, "__", strlen("__")) == 0) { LOG("You cannot move containers to i3-internal workspaces (\"%s\").\n", name); ysuccess(false); return; } + const bool no_auto_back_and_forth = (_no_auto_back_and_forth != NULL); owindow *current; /* We have nothing to move: @@ -432,7 +433,8 @@ void cmd_move_con_to_workspace_name(I3_CMD, const char *name) { /* get the workspace */ Con *ws = workspace_get(name, NULL); - ws = maybe_auto_back_and_forth_workspace(ws); + if (!no_auto_back_and_forth) + ws = maybe_auto_back_and_forth_workspace(ws); HANDLE_EMPTY_MATCH; @@ -447,10 +449,11 @@ void cmd_move_con_to_workspace_name(I3_CMD, const char *name) { } /* - * Implementation of 'move [window|container] [to] workspace number '. + * Implementation of 'move [--no-auto-back-and-forth] [window|container] [to] workspace number '. * */ -void cmd_move_con_to_workspace_number(I3_CMD, const char *which) { +void cmd_move_con_to_workspace_number(I3_CMD, const char *which, const char *_no_auto_back_and_forth) { + const bool no_auto_back_and_forth = (_no_auto_back_and_forth != NULL); owindow *current; /* We have nothing to move: @@ -483,7 +486,8 @@ void cmd_move_con_to_workspace_number(I3_CMD, const char *which) { workspace = workspace_get(which, NULL); } - workspace = maybe_auto_back_and_forth_workspace(workspace); + if (!no_auto_back_and_forth) + workspace = maybe_auto_back_and_forth_workspace(workspace); HANDLE_EMPTY_MATCH; diff --git a/testcases/t/187-commands-parser.t b/testcases/t/187-commands-parser.t index 5c0cc99f..e3481b0a 100644 --- a/testcases/t/187-commands-parser.t +++ b/testcases/t/187-commands-parser.t @@ -44,7 +44,7 @@ sub parser_calls { # The first call has only a single command, the following ones are consolidated # for performance. is(parser_calls('move workspace 3'), - 'cmd_move_con_to_workspace_name(3)', + 'cmd_move_con_to_workspace_name(3, (null))', 'single number (move workspace 3) ok'); is(parser_calls( @@ -57,19 +57,19 @@ is(parser_calls( 'move workspace 3: foobar; ' . 'move workspace "3: foobar"; ' . 'move workspace "3: foobar, baz"; '), - "cmd_move_con_to_workspace_name(3)\n" . - "cmd_move_con_to_workspace_name(3)\n" . - "cmd_move_con_to_workspace_name(3)\n" . - "cmd_move_con_to_workspace_name(foobar)\n" . - "cmd_move_con_to_workspace_name(torrent)\n" . + "cmd_move_con_to_workspace_name(3, (null))\n" . + "cmd_move_con_to_workspace_name(3, (null))\n" . + "cmd_move_con_to_workspace_name(3, (null))\n" . + "cmd_move_con_to_workspace_name(foobar, (null))\n" . + "cmd_move_con_to_workspace_name(torrent, (null))\n" . "cmd_move_workspace_to_output(LVDS1)\n" . - "cmd_move_con_to_workspace_name(3: foobar)\n" . - "cmd_move_con_to_workspace_name(3: foobar)\n" . - "cmd_move_con_to_workspace_name(3: foobar, baz)", + "cmd_move_con_to_workspace_name(3: foobar, (null))\n" . + "cmd_move_con_to_workspace_name(3: foobar, (null))\n" . + "cmd_move_con_to_workspace_name(3: foobar, baz, (null))", 'move ok'); is(parser_calls('move workspace 3: foobar, nop foo'), - "cmd_move_con_to_workspace_name(3: foobar)\n" . + "cmd_move_con_to_workspace_name(3: foobar, (null))\n" . "cmd_nop(foo)", 'multiple ops (move workspace 3: foobar, nop foo) ok'); @@ -177,7 +177,7 @@ is(parser_calls('unknown_literal'), 'error for unknown literal ok'); is(parser_calls('move something to somewhere'), - "ERROR: Expected one of these tokens: 'window', 'container', 'to', 'workspace', 'output', 'mark', 'scratchpad', 'left', 'right', 'up', 'down', 'position', 'absolute'\n" . + "ERROR: Expected one of these tokens: 'window', 'container', 'to', '--no-auto-back-and-forth', 'workspace', 'output', 'mark', 'scratchpad', 'left', 'right', 'up', 'down', 'position', 'absolute'\n" . "ERROR: Your command: move something to somewhere\n" . "ERROR: ^^^^^^^^^^^^^^^^^^^^^^", 'error for unknown literal ok'); diff --git a/testcases/t/256-no-auto-back-and-forth.t b/testcases/t/256-no-auto-back-and-forth.t index 770754d1..859b76f1 100644 --- a/testcases/t/256-no-auto-back-and-forth.t +++ b/testcases/t/256-no-auto-back-and-forth.t @@ -18,7 +18,7 @@ # Ticket: #2028 use i3test; -my ($first, $second, $third); +my ($first, $second, $third, $con); $first = "1:first"; $second = "2:second"; $third = "3:third"; @@ -52,6 +52,47 @@ ok(get_ws($first)->{focused}, 'first workspace is still focused'); cmd qq|workspace --no-auto-back-and-forth number "$first"|; ok(get_ws($first)->{focused}, 'first workspace is still focused'); +############################################################################### +# Moving a window to another workspace when passing --no-auto-back-and-forth +# works as if the flag wasn't set. +############################################################################### + +cmd qq|workspace "$third"|; +cmd qq|workspace "$second"|; +cmd qq|workspace "$first"|; +$con = open_window; +cmd 'mark mywindow'; + +cmd qq|move --no-auto-back-and-forth window to workspace "$second"|; +is(@{get_ws($second)->{nodes}}, 1, 'window was moved to second workspace'); +cmd qq|[con_mark=mywindow] move window to workspace "$first"|; + +cmd qq|move --no-auto-back-and-forth window to workspace number "$third"|; +is(@{get_ws($third)->{nodes}}, 1, 'window was moved to third workspace'); +cmd qq|[con_mark=mywindow] move window to workspace "$first"|; + +cmd '[con_mark=mywindow] kill'; + +############################################################################### +# Moving a window to the same workspace when passing --no-auto-back-and-forth +# is a no-op. +############################################################################### + +cmd qq|workspace "$second"|; +cmd qq|workspace "$first"|; +$con = open_window; +cmd 'mark mywindow'; + +cmd qq|move --no-auto-back-and-forth window to workspace "$first"|; +is(@{get_ws($first)->{nodes}}, 1, 'window is still on first workspace'); +cmd qq|[con_mark=mywindow] move window to workspace "$first"|; + +cmd qq|move --no-auto-back-and-forth window to workspace number "$first"|; +is(@{get_ws($first)->{nodes}}, 1, 'window is still on first workspace'); +cmd qq|[con_mark=mywindow] move window to workspace "$first"|; + +cmd '[con_mark=mywindow] kill'; + ############################################################################### done_testing; From dc05d905c1ab0978ca98b3e15ff96d18df55c182 Mon Sep 17 00:00:00 2001 From: David Simon Date: Mon, 26 Oct 2015 16:55:01 -0400 Subject: [PATCH 056/187] Optionally change i3bar color on focused output, implements #2020 --- docs/ipc | 8 ++++++++ docs/userguide | 9 +++++++++ i3bar/include/outputs.h | 6 ++++++ i3bar/include/xcb.h | 3 +++ i3bar/src/config.c | 6 ++++++ i3bar/src/outputs.c | 14 +++++++++++++ i3bar/src/xcb.c | 38 ++++++++++++++++++++++++++---------- include/config.h | 4 ++++ parser-specs/config.spec | 2 +- src/config.c | 4 ++++ src/config_directives.c | 8 +++++++- src/ipc.c | 3 +++ testcases/t/177-bar-config.t | 6 ++++++ 13 files changed, 99 insertions(+), 12 deletions(-) diff --git a/docs/ipc b/docs/ipc index 1813e53a..5231c5ec 100644 --- a/docs/ipc +++ b/docs/ipc @@ -520,6 +520,14 @@ statusline:: Text color to be used for the statusline. separator:: Text color to be used for the separator. +focused_background:: + Background color of the bar on the currently focused monitor output. +focused_statusline:: + Text color to be used for the statusline on the currently focused + monitor output. +focused_separator:: + Text color to be used for the separator on the currently focused + monitor output. focused_workspace_text/focused_workspace_bg/focused_workspace_border:: Text/background/border color for a workspace button when the workspace has focus. diff --git a/docs/userguide b/docs/userguide index e866dfb1..ca39757a 100644 --- a/docs/userguide +++ b/docs/userguide @@ -1529,6 +1529,15 @@ statusline:: Text color to be used for the statusline. separator:: Text color to be used for the separator. +focused_background:: + Background color of the bar on the currently focused monitor output. If + not used, the color will be taken from +background+. +focused_statusline:: + Text color to be used for the statusline on the currently focused + monitor output. If not used, the color will be taken from +statusline+. +focused_separator:: + Text color to be used for the separator on the currently focused + monitor output. If not used, the color will be taken from +separator+. focused_workspace:: Border, background and text color for a workspace button when the workspace has focus. diff --git a/i3bar/include/outputs.h b/i3bar/include/outputs.h index ec09e764..08adefd9 100644 --- a/i3bar/include/outputs.h +++ b/i3bar/include/outputs.h @@ -38,6 +38,12 @@ void init_outputs(void); */ i3_output* get_output_by_name(char* name); +/* + * Returns true if the output has the currently focused workspace + * + */ +bool output_has_focus(i3_output* output); + struct i3_output { char* name; /* Name of the output */ bool active; /* If the output is active */ diff --git a/i3bar/include/xcb.h b/i3bar/include/xcb.h index 3746204c..0a9bd7e4 100644 --- a/i3bar/include/xcb.h +++ b/i3bar/include/xcb.h @@ -34,6 +34,9 @@ struct xcb_color_strings_t { char *bar_fg; char *bar_bg; char *sep_fg; + char *focus_bar_fg; + char *focus_bar_bg; + char *focus_sep_fg; char *active_ws_fg; char *active_ws_bg; char *active_ws_border; diff --git a/i3bar/src/config.c b/i3bar/src/config.c index f3412719..6476d15f 100644 --- a/i3bar/src/config.c +++ b/i3bar/src/config.c @@ -214,6 +214,9 @@ static int config_string_cb(void *params_, const unsigned char *val, size_t _len COLOR(statusline, bar_fg); COLOR(background, bar_bg); COLOR(separator, sep_fg); + COLOR(focused_statusline, focus_bar_fg); + COLOR(focused_background, focus_bar_bg); + COLOR(focused_separator, focus_sep_fg); COLOR(focused_workspace_border, focus_ws_border); COLOR(focused_workspace_bg, focus_ws_bg); COLOR(focused_workspace_text, focus_ws_fg); @@ -343,6 +346,9 @@ void free_colors(struct xcb_color_strings_t *colors) { FREE_COLOR(bar_fg); FREE_COLOR(bar_bg); FREE_COLOR(sep_fg); + FREE_COLOR(focus_bar_fg); + FREE_COLOR(focus_bar_bg); + FREE_COLOR(focus_sep_fg); FREE_COLOR(active_ws_fg); FREE_COLOR(active_ws_bg); FREE_COLOR(active_ws_border); diff --git a/i3bar/src/outputs.c b/i3bar/src/outputs.c index df3bc69b..841a7565 100644 --- a/i3bar/src/outputs.c +++ b/i3bar/src/outputs.c @@ -302,3 +302,17 @@ i3_output *get_output_by_name(char *name) { return walk; } + +/* + * Returns true if the output has the currently focused workspace + * + */ +bool output_has_focus(i3_output *output) { + i3_ws *ws_walk; + TAILQ_FOREACH(ws_walk, output->workspaces, tailq) { + if (ws_walk->focused) { + return true; + } + } + return false; +} diff --git a/i3bar/src/xcb.c b/i3bar/src/xcb.c index 841570e2..20206c8b 100644 --- a/i3bar/src/xcb.c +++ b/i3bar/src/xcb.c @@ -91,6 +91,9 @@ 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; @@ -160,7 +163,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 +174,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,7 +182,7 @@ 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); } } @@ -233,10 +239,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) { +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 +270,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 +315,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); } } } @@ -412,6 +421,12 @@ 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(); @@ -1826,8 +1841,11 @@ 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; @@ -1928,7 +1946,7 @@ 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; - 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); diff --git a/include/config.h b/include/config.h index 6312d3d2..1c4ccce6 100644 --- a/include/config.h +++ b/include/config.h @@ -323,6 +323,10 @@ struct Barconfig { char *statusline; char *separator; + char *focused_background; + char *focused_statusline; + char *focused_separator; + char *focused_workspace_border; char *focused_workspace_bg; char *focused_workspace_text; diff --git a/parser-specs/config.spec b/parser-specs/config.spec index 2170ace3..882e81fb 100644 --- a/parser-specs/config.spec +++ b/parser-specs/config.spec @@ -520,7 +520,7 @@ state BAR_COLORS: end -> '#' -> BAR_COLORS_IGNORE_LINE 'set' -> BAR_COLORS_IGNORE_LINE - colorclass = 'background', 'statusline', 'separator' + colorclass = 'background', 'statusline', 'separator', 'focused_background', 'focused_statusline', 'focused_separator' -> BAR_COLORS_SINGLE colorclass = 'focused_workspace', 'active_workspace', 'inactive_workspace', 'urgent_workspace', 'binding_mode' -> BAR_COLORS_BORDER diff --git a/src/config.c b/src/config.c index d8db85e6..c1c31e30 100644 --- a/src/config.c +++ b/src/config.c @@ -118,6 +118,10 @@ void load_configuration(xcb_connection_t *conn, const char *override_configpath, FREE(barconfig->font); FREE(barconfig->colors.background); FREE(barconfig->colors.statusline); + FREE(barconfig->colors.separator); + FREE(barconfig->colors.focused_background); + FREE(barconfig->colors.focused_statusline); + FREE(barconfig->colors.focused_separator); FREE(barconfig->colors.focused_workspace_border); FREE(barconfig->colors.focused_workspace_bg); FREE(barconfig->colors.focused_workspace_text); diff --git a/src/config_directives.c b/src/config_directives.c index 99b70db8..960dee5b 100644 --- a/src/config_directives.c +++ b/src/config_directives.c @@ -533,8 +533,14 @@ CFGFUN(bar_color_single, const char *colorclass, const char *color) { current_bar.colors.background = sstrdup(color); else if (strcmp(colorclass, "separator") == 0) current_bar.colors.separator = sstrdup(color); - else + else if (strcmp(colorclass, "statusline") == 0) current_bar.colors.statusline = sstrdup(color); + else if (strcmp(colorclass, "focused_background") == 0) + current_bar.colors.focused_background = sstrdup(color); + else if (strcmp(colorclass, "focused_separator") == 0) + current_bar.colors.focused_separator = sstrdup(color); + else + current_bar.colors.focused_statusline = sstrdup(color); } CFGFUN(bar_status_command, const char *command) { diff --git a/src/ipc.c b/src/ipc.c index 68cc417a..8e448c7c 100644 --- a/src/ipc.c +++ b/src/ipc.c @@ -663,6 +663,9 @@ static void dump_bar_config(yajl_gen gen, Barconfig *config) { YSTR_IF_SET(background); YSTR_IF_SET(statusline); YSTR_IF_SET(separator); + YSTR_IF_SET(focused_background); + YSTR_IF_SET(focused_statusline); + YSTR_IF_SET(focused_separator); YSTR_IF_SET(focused_workspace_border); YSTR_IF_SET(focused_workspace_bg); YSTR_IF_SET(focused_workspace_text); diff --git a/testcases/t/177-bar-config.t b/testcases/t/177-bar-config.t index cc4826c1..8ef94e57 100644 --- a/testcases/t/177-bar-config.t +++ b/testcases/t/177-bar-config.t @@ -109,6 +109,9 @@ bar { colors { background #ff0000 statusline #00ff00 + focused_background #cc0000 + focused_statusline #cccc00 + focused_separator #0000cc focused_workspace #4c7899 #285577 #ffffff active_workspace #333333 #222222 #888888 @@ -143,6 +146,9 @@ is_deeply($bar_config->{colors}, { background => '#ff0000', statusline => '#00ff00', + focused_background => '#cc0000', + focused_statusline=> '#cccc00', + focused_separator => '#0000cc', focused_workspace_border => '#4c7899', focused_workspace_text => '#ffffff', focused_workspace_bg => '#285577', From 27db61f504458bf7afe813154d7eb6d7782ed06f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ingo=20B=C3=BCrk?= Date: Wed, 28 Oct 2015 14:39:23 +0100 Subject: [PATCH 057/187] Fix multiple memory leaks with regular expressions. --- src/config_directives.c | 1 + src/manage.c | 1 + src/match.c | 16 +++++++--------- src/regex.c | 1 + 4 files changed, 10 insertions(+), 9 deletions(-) diff --git a/src/config_directives.c b/src/config_directives.c index 960dee5b..20b880ed 100644 --- a/src/config_directives.c +++ b/src/config_directives.c @@ -29,6 +29,7 @@ CFGFUN(criteria_init, int _state) { criteria_next_state = _state; DLOG("Initializing criteria, current_match = %p, state = %d\n", current_match, _state); + match_free(current_match); match_init(current_match); } diff --git a/src/manage.c b/src/manage.c index 5cfe490e..0dec2844 100644 --- a/src/manage.c +++ b/src/manage.c @@ -294,6 +294,7 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki if (match != NULL && match->insert_where != M_BELOW) { DLOG("Removing match %p from container %p\n", match, nc); TAILQ_REMOVE(&(nc->swallow_head), match, matches); + match_free(match); } } diff --git a/src/match.c b/src/match.c index 8da3c1ea..1cdd478f 100644 --- a/src/match.c +++ b/src/match.c @@ -238,21 +238,13 @@ bool match_matches_window(Match *match, i3Window *window) { * */ void match_free(Match *match) { - /* First step: free the regex fields / patterns */ regex_free(match->title); regex_free(match->application); regex_free(match->class); regex_free(match->instance); regex_free(match->mark); regex_free(match->window_role); - - /* Second step: free the regex helper struct itself */ - FREE(match->title); - FREE(match->application); - FREE(match->class); - FREE(match->instance); - FREE(match->mark); - FREE(match->window_role); + regex_free(match->workspace); } /* @@ -264,16 +256,19 @@ void match_parse_property(Match *match, const char *ctype, const char *cvalue) { DLOG("ctype=*%s*, cvalue=*%s*\n", ctype, cvalue); if (strcmp(ctype, "class") == 0) { + regex_free(match->class); match->class = regex_new(cvalue); return; } if (strcmp(ctype, "instance") == 0) { + regex_free(match->instance); match->instance = regex_new(cvalue); return; } if (strcmp(ctype, "window_role") == 0) { + regex_free(match->window_role); match->window_role = regex_new(cvalue); return; } @@ -339,11 +334,13 @@ void match_parse_property(Match *match, const char *ctype, const char *cvalue) { } if (strcmp(ctype, "con_mark") == 0) { + regex_free(match->mark); match->mark = regex_new(cvalue); return; } if (strcmp(ctype, "title") == 0) { + regex_free(match->title); match->title = regex_new(cvalue); return; } @@ -362,6 +359,7 @@ void match_parse_property(Match *match, const char *ctype, const char *cvalue) { } if (strcmp(ctype, "workspace") == 0) { + regex_free(match->workspace); match->workspace = regex_new(cvalue); return; } diff --git a/src/regex.c b/src/regex.c index 913519be..24846981 100644 --- a/src/regex.c +++ b/src/regex.c @@ -64,6 +64,7 @@ void regex_free(struct regex *regex) { FREE(regex->pattern); FREE(regex->regex); FREE(regex->extra); + FREE(regex); } /* From 0e5180cae9e9295678e3f053042b559e82cb8c98 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Wed, 28 Oct 2015 21:42:37 +0100 Subject: [PATCH 058/187] Bugfix: correctly compare modifier mask when identifying keybindings fixes #2002 --- src/bindings.c | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/bindings.c b/src/bindings.c index b32972ac..ed8775a8 100644 --- a/src/bindings.c +++ b/src/bindings.c @@ -166,15 +166,23 @@ static Binding *get_binding(i3_event_state_mask_t state_filtered, bool is_releas } TAILQ_FOREACH(bind, bindings, bindings) { + bool state_matches; + if (bind->event_state_mask == 0) { + /* Verify no modifiers are pressed. A bitwise AND would lead to + * false positives, see issue #2002. */ + state_matches = (state_filtered == 0); + } else { + state_matches = ((state_filtered & bind->event_state_mask) == bind->event_state_mask); + } + DLOG("binding with event_state_mask 0x%x, state_filtered 0x%x, match: %s\n", - bind->event_state_mask, state_filtered, - ((state_filtered & bind->event_state_mask) == bind->event_state_mask) ? "yes" : "no"); + bind->event_state_mask, state_filtered, (state_matches ? "yes" : "no")); /* First compare the state_filtered (unless this is a * B_UPON_KEYRELEASE_IGNORE_MODS binding and this is a KeyRelease * event) */ if (bind->input_type != input_type) continue; - if ((state_filtered & bind->event_state_mask) != bind->event_state_mask && + if (!state_matches && (bind->release != B_UPON_KEYRELEASE_IGNORE_MODS || !is_release)) continue; From 9692c1498bcde3489f80b484dbd847a75dfd0405 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Thu, 29 Oct 2015 08:47:36 +0100 Subject: [PATCH 059/187] Bugfix: set group mask 1 by default, correctly compare modifiers fixes #2002 --- src/bindings.c | 4 ++-- src/config_directives.c | 22 +++++++++++++++++----- 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/src/bindings.c b/src/bindings.c index ed8775a8..7ea087e6 100644 --- a/src/bindings.c +++ b/src/bindings.c @@ -167,10 +167,10 @@ static Binding *get_binding(i3_event_state_mask_t state_filtered, bool is_releas TAILQ_FOREACH(bind, bindings, bindings) { bool state_matches; - if (bind->event_state_mask == 0) { + if ((bind->event_state_mask & 0xFFFF) == 0) { /* Verify no modifiers are pressed. A bitwise AND would lead to * false positives, see issue #2002. */ - state_matches = (state_filtered == 0); + state_matches = (state_filtered == bind->event_state_mask); } else { state_matches = ((state_filtered & bind->event_state_mask) == bind->event_state_mask); } diff --git a/src/config_directives.c b/src/config_directives.c index 20b880ed..fb6ae94c 100644 --- a/src/config_directives.c +++ b/src/config_directives.c @@ -67,8 +67,9 @@ i3_event_state_mask_t event_state_from_str(const char *str) { /* It might be better to use strtok() here, but the simpler strstr() should * do for now. */ i3_event_state_mask_t result = 0; + int group_bits_set = 0; if (str == NULL) - return result; + return (I3_XKB_GROUP_MASK_1 << 16); if (strstr(str, "Mod1") != NULL) result |= XCB_KEY_BUT_MASK_MOD_1; if (strstr(str, "Mod2") != NULL) @@ -85,15 +86,26 @@ i3_event_state_mask_t event_state_from_str(const char *str) { if (strstr(str, "Shift") != NULL) result |= XCB_KEY_BUT_MASK_SHIFT; - if (strstr(str, "Group1") != NULL) + if (strstr(str, "Group1") != NULL) { result |= (I3_XKB_GROUP_MASK_1 << 16); + group_bits_set++; + } if (strstr(str, "Group2") != NULL || - strstr(str, "Mode_switch") != NULL) + strstr(str, "Mode_switch") != NULL) { result |= (I3_XKB_GROUP_MASK_2 << 16); - if (strstr(str, "Group3") != NULL) + group_bits_set++; + } + if (strstr(str, "Group3") != NULL) { result |= (I3_XKB_GROUP_MASK_3 << 16); - if (strstr(str, "Group4") != NULL) + group_bits_set++; + } + if (strstr(str, "Group4") != NULL) { result |= (I3_XKB_GROUP_MASK_4 << 16); + group_bits_set++; + } + if (group_bits_set == 0) { + result |= (I3_XKB_GROUP_MASK_1 << 16); + } return result; } From beb89e64b46266853a55770685e82f22081dffcb Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 30 Oct 2015 08:38:53 +0100 Subject: [PATCH 060/187] Bugfix: ignore XKB group bits in floating_modifier fixes #2046 --- src/click.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/click.c b/src/click.c index 92a1a1fa..c895666d 100644 --- a/src/click.c +++ b/src/click.c @@ -356,7 +356,7 @@ int handle_button_press(xcb_button_press_event_t *event) { last_timestamp = event->time; - const uint32_t mod = config.floating_modifier; + const uint32_t mod = (config.floating_modifier & 0xFFFF); const bool mod_pressed = (mod != 0 && (event->state & mod) == mod); DLOG("floating_mod = %d, detail = %d\n", mod_pressed, event->detail); if ((con = con_by_window_id(event->event))) From bc250b26a06be744f98912d309504deec9ca1c8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ingo=20B=C3=BCrk?= Date: Fri, 16 Oct 2015 11:34:19 +0200 Subject: [PATCH 061/187] Allow multiple tray_output directives. This patch introduces the possibility to specify the tray_output directive multiple times. All values will be used by i3bar, in the order they are given. This way, a single bar configuration can be used for several machines with internal output names "eDP1" and "LVDS-0" by specifying tray_output for both. Any external output (e.g., "DP-0") will still not receive the tray. The same effect can be achieved by using "primary", but forces the user to couple the tray display to the primary output which may not be desirable behavior. relates to #555 --- docs/userguide | 5 ++ i3bar/include/config.h | 8 ++- i3bar/src/config.c | 27 +++++-- i3bar/src/xcb.c | 94 +++++++++++++++++------- include/config.h | 13 +++- src/config.c | 7 +- src/config_directives.c | 135 +++++++++++++++++------------------ src/ipc.c | 14 +++- testcases/t/177-bar-config.t | 4 +- 9 files changed, 200 insertions(+), 107 deletions(-) diff --git a/docs/userguide b/docs/userguide index ca39757a..e596aeae 100644 --- a/docs/userguide +++ b/docs/userguide @@ -1369,6 +1369,11 @@ NetworkManager, VLC, Pidgin, etc. can place little icons. You can configure on which output (monitor) the icons should be displayed or you can turn off the functionality entirely. +You can use mutliple +tray_output+ directives in your config to specify a list +of outputs on which you want the tray to appear. The first available output in +that list as defined by the order of the directives will be used for the tray +output. + *Syntax*: --------------------------------- tray_output none|primary| diff --git a/i3bar/include/config.h b/i3bar/include/config.h index 1ce5dfa6..2a059046 100644 --- a/i3bar/include/config.h +++ b/i3bar/include/config.h @@ -29,6 +29,12 @@ typedef struct binding_t { TAILQ_ENTRY(binding_t) bindings; } binding_t; +typedef struct tray_output_t { + char *output; + + TAILQ_ENTRY(tray_output_t) tray_outputs; +} tray_output_t; + typedef struct config_t { int modifier; TAILQ_HEAD(bindings_head, binding_t) bindings; @@ -42,7 +48,7 @@ typedef struct config_t { char *command; char *fontname; i3String *separator_symbol; - char *tray_output; + TAILQ_HEAD(tray_outputs_head, tray_output_t) tray_outputs; int tray_padding; int num_outputs; char **outputs; diff --git a/i3bar/src/config.c b/i3bar/src/config.c index 6476d15f..bc13f3d9 100644 --- a/i3bar/src/config.c +++ b/i3bar/src/config.c @@ -21,6 +21,7 @@ static char *cur_key; static bool parsing_bindings; +static bool parsing_tray_outputs; /* * Parse a key. @@ -32,14 +33,20 @@ static int config_map_key_cb(void *params_, const unsigned char *keyVal, size_t FREE(cur_key); sasprintf(&(cur_key), "%.*s", keyLen, keyVal); - if (strcmp(cur_key, "bindings") == 0) + if (strcmp(cur_key, "bindings") == 0) { parsing_bindings = true; + } + + if (strcmp(cur_key, "tray_outputs") == 0) { + parsing_tray_outputs = true; + } return 1; } static int config_end_array_cb(void *params_) { parsing_bindings = false; + parsing_tray_outputs = false; return 1; } @@ -90,6 +97,14 @@ static int config_string_cb(void *params_, const unsigned char *val, size_t _len return 0; } + if (parsing_tray_outputs) { + DLOG("Adding tray_output = %.*s to the list.\n", len, val); + tray_output_t *tray_output = scalloc(1, sizeof(tray_output_t)); + sasprintf(&(tray_output->output), "%.*s", len, val); + TAILQ_INSERT_TAIL(&(config.tray_outputs), tray_output, tray_outputs); + return 1; + } + if (!strcmp(cur_key, "mode")) { DLOG("mode = %.*s, len = %d\n", len, val, len); config.hide_on_modifier = (len == 4 && !strncmp((const char *)val, "dock", strlen("dock")) ? M_DOCK @@ -195,10 +210,13 @@ static int config_string_cb(void *params_, const unsigned char *val, size_t _len return 1; } + /* We keep the old single tray_output working for users who only restart i3bar + * after updating. */ if (!strcmp(cur_key, "tray_output")) { - DLOG("tray_output %.*s\n", len, val); - FREE(config.tray_output); - sasprintf(&config.tray_output, "%.*s", len, val); + DLOG("Found deprecated key tray_output %.*s.\n", len, val); + tray_output_t *tray_output = scalloc(1, sizeof(tray_output_t)); + sasprintf(&(tray_output->output), "%.*s", len, val); + TAILQ_INSERT_TAIL(&(config.tray_outputs), tray_output, tray_outputs); return 1; } @@ -317,6 +335,7 @@ void parse_config_json(char *json) { handle = yajl_alloc(&outputs_callbacks, NULL, NULL); TAILQ_INIT(&(config.bindings)); + TAILQ_INIT(&(config.tray_outputs)); state = yajl_parse(handle, (const unsigned char *)json, strlen(json)); diff --git a/i3bar/src/xcb.c b/i3bar/src/xcb.c index 20206c8b..92b0d1ab 100644 --- a/i3bar/src/xcb.c +++ b/i3bar/src/xcb.c @@ -728,25 +728,50 @@ 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)) + i3_output *output = NULL; + i3_output *walk = NULL; + tray_output_t *tray_output = NULL; + /* We need to iterate through the tray_output assignments first in + * order to prioritize them. Otherwise, if this bar manages two + * outputs and both are assigned as tray_output as well, the first + * output in our list would receive the tray rather than the first + * one defined via tray_output. */ + TAILQ_FOREACH(tray_output, &(config.tray_outputs), tray_outputs) { + SLIST_FOREACH(walk, outputs, slist) { + if (!walk->active) continue; + + if (strcasecmp(walk->name, tray_output->output) == 0) { + DLOG("Found tray_output assignment for output %s.\n", walk->name); + output = walk; + break; + } + + if (walk->primary && strcasecmp("primary", tray_output->output) == 0) { + DLOG("Found tray_output assignment on primary output %s.\n", walk->name); + output = walk; + break; + } } - DLOG("using output %s\n", walk->name); - output = walk; - break; + /* If we found an output, we're done. */ + if (output != NULL) + break; } + + /* Check whether any "tray_output primary" was defined for this bar. */ + bool contains_primary = false; + TAILQ_FOREACH(tray_output, &(config.tray_outputs), tray_outputs) { + if (strcasecmp("primary", tray_output->output) == 0) { + contains_primary = true; + 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) { + * configured, we fall back to the first available output. We do the + * same if no tray_output was specified. */ + if (output == NULL && (contains_primary || TAILQ_EMPTY(&(config.tray_outputs)))) { SLIST_FOREACH(walk, outputs, slist) { if (!walk->active) continue; @@ -1707,20 +1732,37 @@ 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; + /* Unless "tray_output none" was specified, we need to initialize the tray. */ + const char *first = (TAILQ_EMPTY(&(config.tray_outputs))) ? SLIST_FIRST(outputs)->name : TAILQ_FIRST(&(config.tray_outputs))->output; + if (!tray_configured && strcasecmp(first, "none") != 0) { + /* We do a sanity check here to ensure that this i3bar instance actually handles + * the output on which the tray should appear. For example, + * consider tray_output == [VGA-1], but output == [HDMI-1]. */ + + /* If no tray_output was specified, we go ahead and initialize the tray as + * we will be using the first available output. */ + if (TAILQ_EMPTY(&(config.tray_outputs))) + init_tray(); + + /* 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) { + i3_output *output; + bool found = false; + SLIST_FOREACH(output, outputs, slist) { + if (strcasecmp(output->name, tray_output->output) == 0 || + (strcasecmp(tray_output->output, "primary") == 0 && output->primary)) { + found = true; + init_tray(); + break; + } } + + if (found) + break; } + tray_configured = true; } } else { diff --git a/include/config.h b/include/config.h index 1c4ccce6..5b98ce6e 100644 --- a/include/config.h +++ b/include/config.h @@ -248,9 +248,10 @@ struct Barconfig { * simplicity (since we store just strings). */ char **outputs; - /** Output on which the tray should be shown. The special value of 'no' - * disables the tray (it’s enabled by default). */ - char *tray_output; + /* List of outputs on which the tray is allowed to be shown, in order. + * The special value "none" disables it (per default, it will be shown) and + * the special value "primary" enabled it on the primary output. */ + TAILQ_HEAD(tray_outputs_head, tray_output_t) tray_outputs; /* Padding around the tray icons. */ int tray_padding; @@ -366,6 +367,12 @@ struct Barbinding { TAILQ_ENTRY(Barbinding) bindings; }; +struct tray_output_t { + char *output; + + TAILQ_ENTRY(tray_output_t) tray_outputs; +}; + /** * Finds the configuration file to use (either the one specified by * override_configpath), the user’s one or the system default) and calls diff --git a/src/config.c b/src/config.c index c1c31e30..89c1d09a 100644 --- a/src/config.c +++ b/src/config.c @@ -110,8 +110,13 @@ void load_configuration(xcb_connection_t *conn, const char *override_configpath, FREE(barconfig->id); for (int c = 0; c < barconfig->num_outputs; c++) free(barconfig->outputs[c]); + while (!TAILQ_EMPTY(&(barconfig->tray_outputs))) { + struct tray_output_t *tray_output = TAILQ_FIRST(&(barconfig->tray_outputs)); + FREE(tray_output->output); + TAILQ_REMOVE(&(barconfig->tray_outputs), tray_output, tray_outputs); + FREE(tray_output); + } FREE(barconfig->outputs); - FREE(barconfig->tray_output); FREE(barconfig->socket_path); FREE(barconfig->status_command); FREE(barconfig->i3bar_command); diff --git a/src/config_directives.c b/src/config_directives.c index fb6ae94c..419d663a 100644 --- a/src/config_directives.c +++ b/src/config_directives.c @@ -399,57 +399,57 @@ CFGFUN(no_focus) { * Bar configuration (i3bar) ******************************************************************************/ -static Barconfig current_bar; +static Barconfig *current_bar; CFGFUN(bar_font, const char *font) { - FREE(current_bar.font); - current_bar.font = sstrdup(font); + FREE(current_bar->font); + current_bar->font = sstrdup(font); } CFGFUN(bar_separator_symbol, const char *separator) { - FREE(current_bar.separator_symbol); - current_bar.separator_symbol = sstrdup(separator); + FREE(current_bar->separator_symbol); + current_bar->separator_symbol = sstrdup(separator); } CFGFUN(bar_mode, const char *mode) { - current_bar.mode = (strcmp(mode, "dock") == 0 ? M_DOCK : (strcmp(mode, "hide") == 0 ? M_HIDE : M_INVISIBLE)); + current_bar->mode = (strcmp(mode, "dock") == 0 ? M_DOCK : (strcmp(mode, "hide") == 0 ? M_HIDE : M_INVISIBLE)); } CFGFUN(bar_hidden_state, const char *hidden_state) { - current_bar.hidden_state = (strcmp(hidden_state, "hide") == 0 ? S_HIDE : S_SHOW); + current_bar->hidden_state = (strcmp(hidden_state, "hide") == 0 ? S_HIDE : S_SHOW); } CFGFUN(bar_id, const char *bar_id) { - current_bar.id = sstrdup(bar_id); + current_bar->id = sstrdup(bar_id); } CFGFUN(bar_output, const char *output) { - int new_outputs = current_bar.num_outputs + 1; - current_bar.outputs = srealloc(current_bar.outputs, sizeof(char *) * new_outputs); - current_bar.outputs[current_bar.num_outputs] = sstrdup(output); - current_bar.num_outputs = new_outputs; + int new_outputs = current_bar->num_outputs + 1; + current_bar->outputs = srealloc(current_bar->outputs, sizeof(char *) * new_outputs); + current_bar->outputs[current_bar->num_outputs] = sstrdup(output); + current_bar->num_outputs = new_outputs; } CFGFUN(bar_verbose, const char *verbose) { - current_bar.verbose = eval_boolstr(verbose); + current_bar->verbose = eval_boolstr(verbose); } CFGFUN(bar_modifier, const char *modifier) { if (strcmp(modifier, "Mod1") == 0) - current_bar.modifier = M_MOD1; + current_bar->modifier = M_MOD1; else if (strcmp(modifier, "Mod2") == 0) - current_bar.modifier = M_MOD2; + current_bar->modifier = M_MOD2; else if (strcmp(modifier, "Mod3") == 0) - current_bar.modifier = M_MOD3; + current_bar->modifier = M_MOD3; else if (strcmp(modifier, "Mod4") == 0) - current_bar.modifier = M_MOD4; + current_bar->modifier = M_MOD4; else if (strcmp(modifier, "Mod5") == 0) - current_bar.modifier = M_MOD5; + current_bar->modifier = M_MOD5; else if (strcmp(modifier, "Control") == 0 || strcmp(modifier, "Ctrl") == 0) - current_bar.modifier = M_CONTROL; + current_bar->modifier = M_CONTROL; else if (strcmp(modifier, "Shift") == 0) - current_bar.modifier = M_SHIFT; + current_bar->modifier = M_SHIFT; } static void bar_configure_binding(const char *button, const char *command) { @@ -465,7 +465,7 @@ static void bar_configure_binding(const char *button, const char *command) { } struct Barbinding *current; - TAILQ_FOREACH(current, &(current_bar.bar_bindings), bindings) { + TAILQ_FOREACH(current, &(current_bar->bar_bindings), bindings) { if (current->input_code == input_code) { ELOG("command for button %s was already specified, ignoring.\n", button); return; @@ -475,7 +475,7 @@ static void bar_configure_binding(const char *button, const char *command) { struct Barbinding *new_binding = scalloc(1, sizeof(struct Barbinding)); new_binding->input_code = input_code; new_binding->command = sstrdup(command); - TAILQ_INSERT_TAIL(&(current_bar.bar_bindings), new_binding, bindings); + TAILQ_INSERT_TAIL(&(current_bar->bar_bindings), new_binding, bindings); } CFGFUN(bar_wheel_up_cmd, const char *command) { @@ -493,29 +493,29 @@ CFGFUN(bar_bindsym, const char *button, const char *command) { } CFGFUN(bar_position, const char *position) { - current_bar.position = (strcmp(position, "top") == 0 ? P_TOP : P_BOTTOM); + current_bar->position = (strcmp(position, "top") == 0 ? P_TOP : P_BOTTOM); } CFGFUN(bar_i3bar_command, const char *i3bar_command) { - FREE(current_bar.i3bar_command); - current_bar.i3bar_command = sstrdup(i3bar_command); + FREE(current_bar->i3bar_command); + current_bar->i3bar_command = sstrdup(i3bar_command); } CFGFUN(bar_color, const char *colorclass, const char *border, const char *background, const char *text) { -#define APPLY_COLORS(classname) \ - do { \ - if (strcmp(colorclass, #classname) == 0) { \ - if (text != NULL) { \ - /* New syntax: border, background, text */ \ - current_bar.colors.classname##_border = sstrdup(border); \ - current_bar.colors.classname##_bg = sstrdup(background); \ - current_bar.colors.classname##_text = sstrdup(text); \ - } else { \ - /* Old syntax: text, background */ \ - current_bar.colors.classname##_bg = sstrdup(background); \ - current_bar.colors.classname##_text = sstrdup(border); \ - } \ - } \ +#define APPLY_COLORS(classname) \ + do { \ + if (strcmp(colorclass, #classname) == 0) { \ + if (text != NULL) { \ + /* New syntax: border, background, text */ \ + current_bar->colors.classname##_border = sstrdup(border); \ + current_bar->colors.classname##_bg = sstrdup(background); \ + current_bar->colors.classname##_text = sstrdup(text); \ + } else { \ + /* Old syntax: text, background */ \ + current_bar->colors.classname##_bg = sstrdup(background); \ + current_bar->colors.classname##_text = sstrdup(border); \ + } \ + } \ } while (0) APPLY_COLORS(focused_workspace); @@ -528,73 +528,72 @@ CFGFUN(bar_color, const char *colorclass, const char *border, const char *backgr } CFGFUN(bar_socket_path, const char *socket_path) { - FREE(current_bar.socket_path); - current_bar.socket_path = sstrdup(socket_path); + FREE(current_bar->socket_path); + current_bar->socket_path = sstrdup(socket_path); } CFGFUN(bar_tray_output, const char *output) { - FREE(current_bar.tray_output); - current_bar.tray_output = sstrdup(output); + struct tray_output_t *tray_output = scalloc(1, sizeof(struct tray_output_t)); + tray_output->output = sstrdup(output); + TAILQ_INSERT_TAIL(&(current_bar->tray_outputs), tray_output, tray_outputs); } CFGFUN(bar_tray_padding, const long padding_px) { - current_bar.tray_padding = padding_px; + current_bar->tray_padding = padding_px; } CFGFUN(bar_color_single, const char *colorclass, const char *color) { if (strcmp(colorclass, "background") == 0) - current_bar.colors.background = sstrdup(color); + current_bar->colors.background = sstrdup(color); else if (strcmp(colorclass, "separator") == 0) - current_bar.colors.separator = sstrdup(color); + current_bar->colors.separator = sstrdup(color); else if (strcmp(colorclass, "statusline") == 0) - current_bar.colors.statusline = sstrdup(color); + current_bar->colors.statusline = sstrdup(color); else if (strcmp(colorclass, "focused_background") == 0) - current_bar.colors.focused_background = sstrdup(color); + current_bar->colors.focused_background = sstrdup(color); else if (strcmp(colorclass, "focused_separator") == 0) - current_bar.colors.focused_separator = sstrdup(color); + current_bar->colors.focused_separator = sstrdup(color); else - current_bar.colors.focused_statusline = sstrdup(color); + current_bar->colors.focused_statusline = sstrdup(color); } CFGFUN(bar_status_command, const char *command) { - FREE(current_bar.status_command); - current_bar.status_command = sstrdup(command); + FREE(current_bar->status_command); + current_bar->status_command = sstrdup(command); } CFGFUN(bar_binding_mode_indicator, const char *value) { - current_bar.hide_binding_mode_indicator = !eval_boolstr(value); + current_bar->hide_binding_mode_indicator = !eval_boolstr(value); } CFGFUN(bar_workspace_buttons, const char *value) { - current_bar.hide_workspace_buttons = !eval_boolstr(value); + current_bar->hide_workspace_buttons = !eval_boolstr(value); } CFGFUN(bar_strip_workspace_numbers, const char *value) { - current_bar.strip_workspace_numbers = eval_boolstr(value); + current_bar->strip_workspace_numbers = eval_boolstr(value); } CFGFUN(bar_start) { - TAILQ_INIT(&(current_bar.bar_bindings)); - current_bar.tray_padding = 2; + current_bar = scalloc(1, sizeof(struct Barconfig)); + TAILQ_INIT(&(current_bar->bar_bindings)); + TAILQ_INIT(&(current_bar->tray_outputs)); + current_bar->tray_padding = 2; } CFGFUN(bar_finish) { DLOG("\t new bar configuration finished, saving.\n"); /* Generate a unique ID for this bar if not already configured */ - if (!current_bar.id) - sasprintf(¤t_bar.id, "bar-%d", config.number_barconfigs); + if (current_bar->id == NULL) + sasprintf(¤t_bar->id, "bar-%d", config.number_barconfigs); config.number_barconfigs++; /* If no font was explicitly set, we use the i3 font as default */ - if (!current_bar.font && font_pattern) - current_bar.font = sstrdup(font_pattern); + if (current_bar->font == NULL && font_pattern != NULL) + current_bar->font = sstrdup(font_pattern); - /* Copy the current (static) structure into a dynamically allocated - * one, then cleanup our static one. */ - Barconfig *bar_config = scalloc(1, sizeof(Barconfig)); - memcpy(bar_config, ¤t_bar, sizeof(Barconfig)); - TAILQ_INSERT_TAIL(&barconfigs, bar_config, configs); - - memset(¤t_bar, '\0', sizeof(Barconfig)); + TAILQ_INSERT_TAIL(&barconfigs, current_bar, configs); + /* Simply reset the pointer, but don't free the resources. */ + current_bar = NULL; } diff --git a/src/ipc.c b/src/ipc.c index 8e448c7c..276a737a 100644 --- a/src/ipc.c +++ b/src/ipc.c @@ -551,6 +551,18 @@ static void dump_bar_config(yajl_gen gen, Barconfig *config) { y(array_close); } + if (!TAILQ_EMPTY(&(config->tray_outputs))) { + ystr("tray_outputs"); + y(array_open); + + struct tray_output_t *tray_output; + TAILQ_FOREACH(tray_output, &(config->tray_outputs), tray_outputs) { + ystr(tray_output->output); + } + + y(array_close); + } + #define YSTR_IF_SET(name) \ do { \ if (config->name) { \ @@ -559,8 +571,6 @@ static void dump_bar_config(yajl_gen gen, Barconfig *config) { } \ } while (0) - YSTR_IF_SET(tray_output); - ystr("tray_padding"); y(integer, config->tray_padding); diff --git a/testcases/t/177-bar-config.t b/testcases/t/177-bar-config.t index 8ef94e57..956b0caa 100644 --- a/testcases/t/177-bar-config.t +++ b/testcases/t/177-bar-config.t @@ -138,7 +138,7 @@ ok(!$bar_config->{binding_mode_indicator}, 'mode indicator disabled'); is($bar_config->{mode}, 'dock', 'dock mode'); is($bar_config->{position}, 'top', 'position top'); is_deeply($bar_config->{outputs}, [ 'HDMI1', 'HDMI2' ], 'outputs ok'); -is($bar_config->{tray_output}, 'HDMI2', 'tray_output ok'); +is_deeply($bar_config->{tray_outputs}, [ 'LVDS1', 'HDMI2' ], 'tray_output ok'); is($bar_config->{tray_padding}, 0, 'tray_padding ok'); is($bar_config->{font}, 'Terminus', 'font ok'); is($bar_config->{socket_path}, '/tmp/foobar', 'socket_path ok'); @@ -294,7 +294,7 @@ ok($bar_config->{binding_mode_indicator}, 'mode indicator enabled'); is($bar_config->{mode}, 'dock', 'dock mode'); is($bar_config->{position}, 'top', 'position top'); is_deeply($bar_config->{outputs}, [ 'HDMI1', 'HDMI2' ], 'outputs ok'); -is($bar_config->{tray_output}, 'HDMI2', 'tray_output ok'); +is_deeply($bar_config->{tray_outputs}, [ 'LVDS1', 'HDMI2' ], 'tray_output ok'); is($bar_config->{font}, 'Terminus', 'font ok'); is($bar_config->{socket_path}, '/tmp/foobar', 'socket_path ok'); is_deeply($bar_config->{colors}, From 5b9fb51b379a940a8d2139ea2df2bc150ed354c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ingo=20B=C3=BCrk?= Date: Fri, 30 Oct 2015 13:10:41 -0400 Subject: [PATCH 062/187] Fix memory leaks in modes and bar bindings. --- src/config.c | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/src/config.c b/src/config.c index 89c1d09a..f146b906 100644 --- a/src/config.c +++ b/src/config.c @@ -75,20 +75,20 @@ void load_configuration(xcb_connection_t *conn, const char *override_configpath, ungrab_all_keys(conn); struct Mode *mode; - Binding *bind; while (!SLIST_EMPTY(&modes)) { mode = SLIST_FIRST(&modes); FREE(mode->name); /* Clear the old binding list */ - bindings = mode->bindings; - while (!TAILQ_EMPTY(bindings)) { - bind = TAILQ_FIRST(bindings); - TAILQ_REMOVE(bindings, bind, bindings); + while (!TAILQ_EMPTY(mode->bindings)) { + Binding *bind = TAILQ_FIRST(mode->bindings); + TAILQ_REMOVE(mode->bindings, bind, bindings); binding_free(bind); } - FREE(bindings); + FREE(mode->bindings); + SLIST_REMOVE(&modes, mode, Mode, modes); + FREE(mode); } struct Assignment *assign; @@ -110,12 +110,21 @@ void load_configuration(xcb_connection_t *conn, const char *override_configpath, FREE(barconfig->id); for (int c = 0; c < barconfig->num_outputs; c++) free(barconfig->outputs[c]); + + while (!TAILQ_EMPTY(&(barconfig->bar_bindings))) { + struct Barbinding *binding = TAILQ_FIRST(&(barconfig->bar_bindings)); + FREE(binding->command); + TAILQ_REMOVE(&(barconfig->bar_bindings), binding, bindings); + FREE(binding); + } + while (!TAILQ_EMPTY(&(barconfig->tray_outputs))) { struct tray_output_t *tray_output = TAILQ_FIRST(&(barconfig->tray_outputs)); FREE(tray_output->output); TAILQ_REMOVE(&(barconfig->tray_outputs), tray_output, tray_outputs); FREE(tray_output); } + FREE(barconfig->outputs); FREE(barconfig->socket_path); FREE(barconfig->status_command); From 18b3f09970cf89a76e0d740740b0539eb8be02a7 Mon Sep 17 00:00:00 2001 From: hwangcc23 Date: Mon, 2 Nov 2015 22:12:44 +0800 Subject: [PATCH 063/187] Fix config validation fail when no new line from end of file 1. i3 config validation failed when the new line is missing from the end of file. The error was: "ERROR: Your line continuation is too long, it exceeds 4096 bytes". It is wrong to assume that there is always a '\n' at the end of each line in the config file. (Not for the last line.) Fix it via adding a end-of-file check. 2. See the issue #2051. (https://github.com/i3/i3/issues/2051) --- src/config_parser.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/config_parser.c b/src/config_parser.c index 0f3d33ec..27cdeb93 100644 --- a/src/config_parser.c +++ b/src/config_parser.c @@ -843,7 +843,7 @@ bool parse_file(const char *f, bool use_nagbar) { break; die("Could not read configuration file\n"); } - if (buffer[strlen(buffer) - 1] != '\n') { + if (buffer[strlen(buffer) - 1] != '\n' && !feof(fstr)) { ELOG("Your line continuation is too long, it exceeds %zd bytes\n", sizeof(buffer)); } continuation = strstr(buffer, "\\\n"); From f2209a873c47d651c2e87b4ee4ebf203e99fa6ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ingo=20B=C3=BCrk?= Date: Mon, 2 Nov 2015 09:21:43 -0500 Subject: [PATCH 064/187] Remove autostart commands after they have been executed. As there is no need to keep autostart commands in memory, we can safely remove them as soon as they have been executed. As we previously didn't clear them at all during config reloads, this also fixes a memory leak. Note that neither autostarts nor autostarts_always is executed during config reloads, so removing them from memory is fine as an i3 restart will cause them to be parsed again. fixes #2044 --- src/main.c | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/src/main.c b/src/main.c index 563fb00c..59c00146 100644 --- a/src/main.c +++ b/src/main.c @@ -819,18 +819,28 @@ int main(int argc, char *argv[]) { /* Autostarting exec-lines */ if (autostart) { - struct Autostart *exec; - TAILQ_FOREACH(exec, &autostarts, autostarts) { + while (!TAILQ_EMPTY(&autostarts)) { + struct Autostart *exec = TAILQ_FIRST(&autostarts); + LOG("auto-starting %s\n", exec->command); start_application(exec->command, exec->no_startup_id); + + FREE(exec->command); + TAILQ_REMOVE(&autostarts, exec, autostarts); + FREE(exec); } } /* Autostarting exec_always-lines */ - struct Autostart *exec_always; - TAILQ_FOREACH(exec_always, &autostarts_always, autostarts_always) { + while (!TAILQ_EMPTY(&autostarts_always)) { + struct Autostart *exec_always = TAILQ_FIRST(&autostarts_always); + LOG("auto-starting (always!) %s\n", exec_always->command); start_application(exec_always->command, exec_always->no_startup_id); + + FREE(exec_always->command); + TAILQ_REMOVE(&autostarts_always, exec_always, autostarts_always); + FREE(exec_always); } /* Start i3bar processes for all configured bars */ From 1d6827a0fd50d38ff8e518d5e01117257023ced0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ingo=20B=C3=BCrk?= Date: Mon, 2 Nov 2015 09:11:21 -0500 Subject: [PATCH 065/187] Raise dependency to cairo 1.14.4. With the recent cairo bugfix, we can now switch to using cairo for rendering i3bar per default by raising the minimum version of cairo to 1.14.4, if this cairo version is available. resolves #2048 --- DEPENDS | 2 +- common.mk | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/DEPENDS b/DEPENDS index 0882f147..fad11da5 100644 --- a/DEPENDS +++ b/DEPENDS @@ -24,7 +24,7 @@ │ PCRE │ 8.12 │ 8.35 │ http://www.pcre.org/ │ │ libsn¹ │ 0.10 │ 0.12 │ http://freedesktop.org/wiki/Software/startup-notification │ pango │ 1.30.0 | 1.36.8 │ http://www.pango.org/ │ -│ cairo │ 1.12.2 │ 1.14.0 │ http://cairographics.org/ │ +│ cairo │ 1.14.4 │ 1.14.4 │ http://cairographics.org/ │ └──────────────┴────────┴────────┴────────────────────────────────────────┘ ¹ libsn = libstartup-notification ² Pod::Simple is a Perl module required for converting the testsuite diff --git a/common.mk b/common.mk index d2875042..60be830e 100644 --- a/common.mk +++ b/common.mk @@ -141,6 +141,9 @@ LIBSN_LIBS := $(call ldflags_for_lib, libstartup-notification-1.0,startup-noti PANGO_CFLAGS := $(call cflags_for_lib, cairo) PANGO_CFLAGS += $(call cflags_for_lib, pangocairo) I3_CPPFLAGS += -DPANGO_SUPPORT=1 +ifeq ($(shell $(PKG_CONFIG) --atleast-version=1.14.4 cairo 2>/dev/null && echo 1),1) +I3_CPPFLAGS += -DI3BAR_CAIRO=1 +endif PANGO_LIBS := $(call ldflags_for_lib, cairo) PANGO_LIBS += $(call ldflags_for_lib, pangocairo) From 38fcaf3fa8ecd87d74646caedcc0fc5b2607cd08 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ingo=20B=C3=BCrk?= Date: Sat, 7 Nov 2015 14:28:45 -0500 Subject: [PATCH 066/187] Rename I3BAR_CAIRO to CAIRO_SUPPORT. --- common.mk | 2 +- i3bar/include/draw_util.h | 6 +++--- i3bar/src/draw_util.c | 18 +++++++++--------- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/common.mk b/common.mk index 60be830e..eb324f23 100644 --- a/common.mk +++ b/common.mk @@ -142,7 +142,7 @@ PANGO_CFLAGS := $(call cflags_for_lib, cairo) PANGO_CFLAGS += $(call cflags_for_lib, pangocairo) I3_CPPFLAGS += -DPANGO_SUPPORT=1 ifeq ($(shell $(PKG_CONFIG) --atleast-version=1.14.4 cairo 2>/dev/null && echo 1),1) -I3_CPPFLAGS += -DI3BAR_CAIRO=1 +I3_CPPFLAGS += -DCAIRO_SUPPORT=1 endif PANGO_LIBS := $(call ldflags_for_lib, cairo) PANGO_LIBS += $(call ldflags_for_lib, pangocairo) diff --git a/i3bar/include/draw_util.h b/i3bar/include/draw_util.h index 9bac72ca..f2e12ec5 100644 --- a/i3bar/include/draw_util.h +++ b/i3bar/include/draw_util.h @@ -8,11 +8,11 @@ */ #pragma once -#ifdef I3BAR_CAIRO +#ifdef CAIRO_SUPPORT #include #endif -#ifdef I3BAR_CAIRO +#ifdef CAIRO_SUPPORT /* We need to flush cairo surfaces twice to avoid an assertion bug. See #1989 * and https://bugs.freedesktop.org/show_bug.cgi?id=92455. */ #define CAIRO_SURFACE_FLUSH(surface) \ @@ -44,7 +44,7 @@ typedef struct surface_t { int width; int height; -#ifdef I3BAR_CAIRO +#ifdef CAIRO_SUPPORT /* A cairo surface representing the drawable. */ cairo_surface_t *surface; diff --git a/i3bar/src/draw_util.c b/i3bar/src/draw_util.c index e1facf39..86435351 100644 --- a/i3bar/src/draw_util.c +++ b/i3bar/src/draw_util.c @@ -11,7 +11,7 @@ #include #include #include -#ifdef I3BAR_CAIRO +#ifdef CAIRO_SUPPORT #include #endif @@ -38,7 +38,7 @@ void draw_util_surface_init(surface_t *surface, xcb_drawable_t drawable, int wid if (xcb_request_failed(gc_cookie, "Could not create graphical context")) exit(EXIT_FAILURE); -#ifdef I3BAR_CAIRO +#ifdef CAIRO_SUPPORT surface->surface = cairo_xcb_surface_create(xcb_connection, surface->id, visual_type, width, height); surface->cr = cairo_create(surface->surface); #endif @@ -50,7 +50,7 @@ void draw_util_surface_init(surface_t *surface, xcb_drawable_t drawable, int wid */ void draw_util_surface_free(surface_t *surface) { xcb_free_gc(xcb_connection, surface->gc); -#ifdef I3BAR_CAIRO +#ifdef CAIRO_SUPPORT cairo_surface_destroy(surface->surface); cairo_destroy(surface->cr); #endif @@ -79,7 +79,7 @@ color_t draw_util_hex_to_color(const char *color) { * */ static void draw_util_set_source_color(surface_t *surface, color_t color) { -#ifdef I3BAR_CAIRO +#ifdef CAIRO_SUPPORT cairo_set_source_rgb(surface->cr, color.red, color.green, color.blue); #else uint32_t colorpixel = color.colorpixel; @@ -95,7 +95,7 @@ static void draw_util_set_source_color(surface_t *surface, color_t color) { * */ void draw_util_text(i3String *text, surface_t *surface, color_t fg_color, color_t bg_color, int x, int y, int max_width) { -#ifdef I3BAR_CAIRO +#ifdef CAIRO_SUPPORT /* Flush any changes before we draw the text as this might use XCB directly. */ CAIRO_SURFACE_FLUSH(surface->surface); #endif @@ -103,7 +103,7 @@ void draw_util_text(i3String *text, surface_t *surface, color_t fg_color, color_ set_font_colors(surface->gc, fg_color.colorpixel, bg_color.colorpixel); draw_text(text, surface->id, surface->gc, visual_type, x, y, max_width); -#ifdef I3BAR_CAIRO +#ifdef CAIRO_SUPPORT /* Notify cairo that we (possibly) used another way to draw on the surface. */ cairo_surface_mark_dirty(surface->surface); #endif @@ -116,7 +116,7 @@ void draw_util_text(i3String *text, surface_t *surface, color_t fg_color, color_ * */ void draw_util_rectangle(surface_t *surface, color_t color, double x, double y, double w, double h) { -#ifdef I3BAR_CAIRO +#ifdef CAIRO_SUPPORT cairo_save(surface->cr); /* Using the SOURCE operator will copy both color and alpha information directly @@ -146,7 +146,7 @@ void draw_util_rectangle(surface_t *surface, color_t color, double x, double y, * */ void draw_util_clear_surface(surface_t *surface, color_t color) { -#ifdef I3BAR_CAIRO +#ifdef CAIRO_SUPPORT cairo_save(surface->cr); /* Using the SOURCE operator will copy both color and alpha information directly @@ -176,7 +176,7 @@ void draw_util_clear_surface(surface_t *surface, color_t color) { */ void draw_util_copy_surface(surface_t *src, surface_t *dest, double src_x, double src_y, double dest_x, double dest_y, double width, double height) { -#ifdef I3BAR_CAIRO +#ifdef CAIRO_SUPPORT cairo_save(dest->cr); /* Using the SOURCE operator will copy both color and alpha information directly From 66882bf445970588c6c7a6a2c2e08a38ad0b9536 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ingo=20B=C3=BCrk?= Date: Wed, 11 Nov 2015 20:21:26 +0100 Subject: [PATCH 067/187] Extract function to grab buttons when managing a window. We refactor the button grabbing into a function to allow the next patch both to - conditionally grab different sets of buttons - grab the buttons again when reloading the config. relates to #2049 --- include/xcb.h | 6 ++++++ src/manage.c | 7 +------ src/xcb.c | 11 +++++++++++ 3 files changed, 18 insertions(+), 6 deletions(-) diff --git a/include/xcb.h b/include/xcb.h index 2c87a19c..27d8986f 100644 --- a/include/xcb.h +++ b/include/xcb.h @@ -160,3 +160,9 @@ void xcb_add_property_atom(xcb_connection_t *conn, xcb_window_t window, xcb_atom * */ void xcb_remove_property_atom(xcb_connection_t *conn, xcb_window_t window, xcb_atom_t property, xcb_atom_t atom); + +/** + * Grab the specified buttons on a window when managing it. + * + */ +void xcb_grab_buttons(xcb_connection_t *conn, xcb_window_t window, uint8_t* buttons); diff --git a/src/manage.c b/src/manage.c index 0dec2844..033d55e1 100644 --- a/src/manage.c +++ b/src/manage.c @@ -168,12 +168,7 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki cwindow->id = window; cwindow->depth = get_visual_depth(attr->visual); - /* We need to grab buttons 1-3 for click-to-focus and buttons 1-5 - * to allow for mouse bindings using --whole-window to work correctly. */ - xcb_grab_button(conn, false, window, XCB_EVENT_MASK_BUTTON_PRESS, - XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_ASYNC, root, XCB_NONE, - XCB_BUTTON_INDEX_ANY, - XCB_BUTTON_MASK_ANY /* don’t filter for any modifiers */); + xcb_grab_buttons(conn, window, (uint8_t[]) {XCB_BUTTON_INDEX_ANY}); /* update as much information as possible so far (some replies may be NULL) */ window_update_class(cwindow, xcb_get_property_reply(conn, class_cookie, NULL), true); diff --git a/src/xcb.c b/src/xcb.c index f98115f5..cafc29ca 100644 --- a/src/xcb.c +++ b/src/xcb.c @@ -319,3 +319,14 @@ release_grab: FREE(reply); xcb_ungrab_server(conn); } + +/* + * Grab the specified buttons on a window when managing it. + * + */ +void xcb_grab_buttons(xcb_connection_t *conn, xcb_window_t window, uint8_t* buttons) { + for (int i = 0; i < sizeof(buttons) / sizeof(uint8_t); i++) { + xcb_grab_button(conn, false, window, XCB_EVENT_MASK_BUTTON_PRESS, XCB_GRAB_MODE_SYNC, + XCB_GRAB_MODE_ASYNC, root, XCB_NONE, buttons[i], XCB_BUTTON_MASK_ANY); + } +} From 029d78c0bf771dd98f80699dcc16b3e962895455 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ingo=20B=C3=BCrk?= Date: Wed, 11 Nov 2015 20:40:25 +0100 Subject: [PATCH 068/187] Only grab scrollwheel buttons if necessary. With this patch, we only grab the scrollwheel buttons (4 and 5) when managing a window if a whole window key binding exists for these buttons. This allows both of these usecases: - Bindings to scrollwheel buttons using --whole-window (see #1701). - Scrolling in a window without focusing it if no such binding exists (see #2049). Furthermore, we drop all button grabs and regrab them after a config reload in order to reevaluate the new bindings correctly. fixes #2049 --- include/bindings.h | 16 +++++++++++++++ include/xcb.h | 2 +- src/bindings.c | 51 ++++++++++++++++++++++++++++++++++++++++++++++ src/config.c | 1 + src/manage.c | 2 +- src/xcb.c | 15 ++++++++++++-- 6 files changed, 83 insertions(+), 4 deletions(-) diff --git a/include/bindings.h b/include/bindings.h index e9e5dac9..da81de24 100644 --- a/include/bindings.h +++ b/include/bindings.h @@ -33,6 +33,13 @@ Binding *configure_binding(const char *bindtype, const char *modifiers, const ch */ void grab_all_keys(xcb_connection_t *conn); +/** + * Release the button grabs on all managed windows and regrab them, + * reevaluating which buttons need to be grabbed. + * + */ +void regrab_all_buttons(xcb_connection_t *conn); + /** * Returns a pointer to the Binding that matches the given xcb event or NULL if * no such binding exists. @@ -95,3 +102,12 @@ CommandResult *run_binding(Binding *bind, Con *con); * */ bool load_keymap(void); + +/** + * Returns true if the current config has any binding to a scroll wheel button + * (4 or 5) which is a whole-window binding. + * We need this to figure out whether we should grab all buttons or just 1-3 + * when managing a window. See #2049. + * + */ +bool bindings_should_grab_scrollwheel_buttons(void); diff --git a/include/xcb.h b/include/xcb.h index 27d8986f..c1f989bd 100644 --- a/include/xcb.h +++ b/include/xcb.h @@ -165,4 +165,4 @@ void xcb_remove_property_atom(xcb_connection_t *conn, xcb_window_t window, xcb_a * Grab the specified buttons on a window when managing it. * */ -void xcb_grab_buttons(xcb_connection_t *conn, xcb_window_t window, uint8_t* buttons); +void xcb_grab_buttons(xcb_connection_t *conn, xcb_window_t window, bool bind_scrollwheel); diff --git a/src/bindings.c b/src/bindings.c index 7ea087e6..471e4783 100644 --- a/src/bindings.c +++ b/src/bindings.c @@ -146,6 +146,27 @@ void grab_all_keys(xcb_connection_t *conn) { } } +/* + * Release the button grabs on all managed windows and regrab them, + * reevaluating which buttons need to be grabbed. + * + */ +void regrab_all_buttons(xcb_connection_t *conn) { + bool grab_scrollwheel = bindings_should_grab_scrollwheel_buttons(); + xcb_grab_server(conn); + + Con *con; + TAILQ_FOREACH(con, &all_cons, all_cons) { + if (con->window == NULL) + continue; + + xcb_ungrab_button(conn, XCB_BUTTON_INDEX_ANY, con->window->id, XCB_BUTTON_MASK_ANY); + xcb_grab_buttons(conn, con->window->id, grab_scrollwheel); + } + + xcb_ungrab_server(conn); +} + /* * Returns a pointer to the Binding with the specified modifiers and * keycode or NULL if no such binding exists. @@ -778,3 +799,33 @@ bool load_keymap(void) { return true; } + +/* + * Returns true if the current config has any binding to a scroll wheel button + * (4 or 5) which is a whole-window binding. + * We need this to figure out whether we should grab all buttons or just 1-3 + * when managing a window. See #2049. + * + */ +bool bindings_should_grab_scrollwheel_buttons(void) { + Binding *bind; + TAILQ_FOREACH(bind, bindings, bindings) { + /* We are only interested in whole window mouse bindings. */ + if (bind->input_type != B_MOUSE || !bind->whole_window) + continue; + + char *endptr; + long button = strtol(bind->symbol + (sizeof("button") - 1), &endptr, 10); + if (button == LONG_MAX || button == LONG_MIN || button < 0 || *endptr != '\0' || endptr == bind->symbol) { + ELOG("Could not parse button number, skipping this binding. Please report this bug in i3.\n"); + continue; + } + + /* If the binding is for either scrollwheel button, we need to grab everything. */ + if (button == 4 || button == 5) { + return true; + } + } + + return false; +} diff --git a/src/config.c b/src/config.c index f146b906..fac4e265 100644 --- a/src/config.c +++ b/src/config.c @@ -229,6 +229,7 @@ void load_configuration(xcb_connection_t *conn, const char *override_configpath, if (reload) { translate_keysyms(); grab_all_keys(conn); + regrab_all_buttons(conn); } if (config.font.type == FONT_TYPE_NONE) { diff --git a/src/manage.c b/src/manage.c index 033d55e1..98051ec3 100644 --- a/src/manage.c +++ b/src/manage.c @@ -168,7 +168,7 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki cwindow->id = window; cwindow->depth = get_visual_depth(attr->visual); - xcb_grab_buttons(conn, window, (uint8_t[]) {XCB_BUTTON_INDEX_ANY}); + xcb_grab_buttons(conn, window, bindings_should_grab_scrollwheel_buttons()); /* update as much information as possible so far (some replies may be NULL) */ window_update_class(cwindow, xcb_get_property_reply(conn, class_cookie, NULL), true); diff --git a/src/xcb.c b/src/xcb.c index cafc29ca..90d591c7 100644 --- a/src/xcb.c +++ b/src/xcb.c @@ -324,8 +324,19 @@ release_grab: * Grab the specified buttons on a window when managing it. * */ -void xcb_grab_buttons(xcb_connection_t *conn, xcb_window_t window, uint8_t* buttons) { - for (int i = 0; i < sizeof(buttons) / sizeof(uint8_t); i++) { +void xcb_grab_buttons(xcb_connection_t *conn, xcb_window_t window, bool bind_scrollwheel) { + uint8_t buttons[3]; + int num = 0; + + if (bind_scrollwheel) { + buttons[num++] = XCB_BUTTON_INDEX_ANY; + } else { + buttons[num++] = XCB_BUTTON_INDEX_1; + buttons[num++] = XCB_BUTTON_INDEX_2; + buttons[num++] = XCB_BUTTON_INDEX_3; + } + + for (int i = 0; i < num; i++) { xcb_grab_button(conn, false, window, XCB_EVENT_MASK_BUTTON_PRESS, XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_ASYNC, root, XCB_NONE, buttons[i], XCB_BUTTON_MASK_ANY); } From e48c4cd25700fac9bf1eb85c3ffec5f43348a90d Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 15 Nov 2015 18:00:48 +0100 Subject: [PATCH 069/187] Revert "Bugfix: set group mask 1 by default, correctly compare modifiers" This reverts commit 9692c1498bcde3489f80b484dbd847a75dfd0405. That commit accidentally defaulted to group mask 1, but the default should be to match any group mask, so that having multiple layouts loaded at the same time works. fixes #2062 --- src/bindings.c | 22 ++++++++++++++++------ src/config_directives.c | 22 +++++----------------- 2 files changed, 21 insertions(+), 23 deletions(-) diff --git a/src/bindings.c b/src/bindings.c index 7ea087e6..49f61590 100644 --- a/src/bindings.c +++ b/src/bindings.c @@ -165,18 +165,28 @@ static Binding *get_binding(i3_event_state_mask_t state_filtered, bool is_releas } } + const uint32_t xkb_group_state = (state_filtered & 0xFFFF0000); + const uint32_t modifiers_state = (state_filtered & 0x0000FFFF); TAILQ_FOREACH(bind, bindings, bindings) { - bool state_matches; - if ((bind->event_state_mask & 0xFFFF) == 0) { + const uint32_t xkb_group_mask = (bind->event_state_mask & 0xFFFF0000); + /* modifiers_mask is a special case: a value of 0 does not mean “match all”, + * but rather “match exactly when no modifiers are present”. */ + const uint32_t modifiers_mask = (bind->event_state_mask & 0x0000FFFF); + const bool groups_match = ((xkb_group_state & xkb_group_mask) == xkb_group_mask); + bool mods_match; + if (modifiers_mask == 0) { /* Verify no modifiers are pressed. A bitwise AND would lead to * false positives, see issue #2002. */ - state_matches = (state_filtered == bind->event_state_mask); + mods_match = (modifiers_state == 0); } else { - state_matches = ((state_filtered & bind->event_state_mask) == bind->event_state_mask); + mods_match = ((modifiers_state & modifiers_mask) == modifiers_mask); } + const bool state_matches = (groups_match && mods_match); - DLOG("binding with event_state_mask 0x%x, state_filtered 0x%x, match: %s\n", - bind->event_state_mask, state_filtered, (state_matches ? "yes" : "no")); + DLOG("binding groups_match = %s, mods_match = %s, state_matches = %s\n", + (groups_match ? "yes" : "no"), + (mods_match ? "yes" : "no"), + (state_matches ? "yes" : "no")); /* First compare the state_filtered (unless this is a * B_UPON_KEYRELEASE_IGNORE_MODS binding and this is a KeyRelease * event) */ diff --git a/src/config_directives.c b/src/config_directives.c index 419d663a..ba267427 100644 --- a/src/config_directives.c +++ b/src/config_directives.c @@ -67,9 +67,8 @@ i3_event_state_mask_t event_state_from_str(const char *str) { /* It might be better to use strtok() here, but the simpler strstr() should * do for now. */ i3_event_state_mask_t result = 0; - int group_bits_set = 0; if (str == NULL) - return (I3_XKB_GROUP_MASK_1 << 16); + return result; if (strstr(str, "Mod1") != NULL) result |= XCB_KEY_BUT_MASK_MOD_1; if (strstr(str, "Mod2") != NULL) @@ -86,26 +85,15 @@ i3_event_state_mask_t event_state_from_str(const char *str) { if (strstr(str, "Shift") != NULL) result |= XCB_KEY_BUT_MASK_SHIFT; - if (strstr(str, "Group1") != NULL) { + if (strstr(str, "Group1") != NULL) result |= (I3_XKB_GROUP_MASK_1 << 16); - group_bits_set++; - } if (strstr(str, "Group2") != NULL || - strstr(str, "Mode_switch") != NULL) { + strstr(str, "Mode_switch") != NULL) result |= (I3_XKB_GROUP_MASK_2 << 16); - group_bits_set++; - } - if (strstr(str, "Group3") != NULL) { + if (strstr(str, "Group3") != NULL) result |= (I3_XKB_GROUP_MASK_3 << 16); - group_bits_set++; - } - if (strstr(str, "Group4") != NULL) { + if (strstr(str, "Group4") != NULL) result |= (I3_XKB_GROUP_MASK_4 << 16); - group_bits_set++; - } - if (group_bits_set == 0) { - result |= (I3_XKB_GROUP_MASK_1 << 16); - } return result; } From 6a7f9370dbff7c9147622c4a3f364b98125a08b2 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 16 Nov 2015 09:25:53 +0100 Subject: [PATCH 070/187] Add i3test::XTEST, add 2 test cases for key bindings --- .travis.yml | 2 +- testcases/lib/i3test/XTEST.pm | 258 +++++++++++++++++++++ testcases/t/257-keypress-group1-fallback.t | 96 ++++++++ testcases/t/258-keypress-release.t | 69 ++++++ 4 files changed, 424 insertions(+), 1 deletion(-) create mode 100644 testcases/lib/i3test/XTEST.pm create mode 100644 testcases/t/257-keypress-group1-fallback.t create mode 100644 testcases/t/258-keypress-release.t diff --git a/.travis.yml b/.travis.yml index dd1fb156..c4d221ae 100644 --- a/.travis.yml +++ b/.travis.yml @@ -44,7 +44,7 @@ before_install: install: - sudo mk-build-deps --install --remove --tool 'apt-get --no-install-recommends' debian/control # Install as many dependencies as possible via apt because cpanm is not very reliable/easy to debug. - - sudo apt-get install --no-install-recommends libanyevent-perl libanyevent-i3-perl libextutils-pkgconfig-perl xcb-proto cpanminus xvfb xserver-xephyr xauth libinline-perl libxml-simple-perl libmouse-perl libmousex-nativetraits-perl libextutils-depends-perl perl-modules libtest-deep-perl libtest-exception-perl libxml-parser-perl libtest-simple-perl libtest-fatal-perl libdata-dump-perl libtest-differences-perl libxml-tokeparser-perl libtest-use-ok-perl libipc-run-perl + - sudo apt-get install --no-install-recommends libanyevent-perl libanyevent-i3-perl libextutils-pkgconfig-perl xcb-proto cpanminus xvfb xserver-xephyr xauth libinline-perl libxml-simple-perl libmouse-perl libmousex-nativetraits-perl libextutils-depends-perl perl-modules libtest-deep-perl libtest-exception-perl libxml-parser-perl libtest-simple-perl libtest-fatal-perl libdata-dump-perl libtest-differences-perl libxml-tokeparser-perl libtest-use-ok-perl libipc-run-perl libxcb-xtest0-dev - sudo /bin/sh -c 'cpanm -n -v X11::XCB || true' - sudo /bin/sh -c 'cpanm -n -v AnyEvent::I3 || true' script: diff --git a/testcases/lib/i3test/XTEST.pm b/testcases/lib/i3test/XTEST.pm new file mode 100644 index 00000000..065c8a35 --- /dev/null +++ b/testcases/lib/i3test/XTEST.pm @@ -0,0 +1,258 @@ +package i3test::XTEST; +# vim:ts=4:sw=4:expandtab + +use strict; +use warnings; +use v5.10; + +use i3test i3_autostart => 0; +use AnyEvent::I3; +use ExtUtils::PkgConfig; + +use Exporter (); +our @EXPORT = qw( + inlinec_connect + set_xkb_group + xtest_key_press + xtest_key_release + listen_for_binding + start_binding_capture + binding_events +); + +=encoding utf-8 + +=head1 NAME + +i3test::XTEST - Inline::C wrappers for xcb-xtest and xcb-xkb + +=cut + +# We need to use libxcb-xkb because xdotool cannot trigger ISO_Next_Group +# anymore: it contains code to set the XKB group to 1 and then restore the +# previous group, effectively rendering any keys that switch groups +# ineffective. +my %sn_config; +BEGIN { + %sn_config = ExtUtils::PkgConfig->find('xcb-xkb xcb-xtest'); +} + +use Inline C => Config => LIBS => $sn_config{libs}, CCFLAGS => $sn_config{cflags}; +use Inline C => <<'END_OF_C_CODE'; +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +static xcb_connection_t *conn = NULL; + +bool inlinec_connect() { + int screen; + + if ((conn = xcb_connect(NULL, &screen)) == NULL || + xcb_connection_has_error(conn)) { + fprintf(stderr, "Could not connect to X11\n"); + return false; + } + + if (!xcb_get_extension_data(conn, &xcb_xkb_id)->present) { + fprintf(stderr, "XKB not present\n"); + return false; + } + + if (!xcb_get_extension_data(conn, &xcb_test_id)->present) { + fprintf(stderr, "XTEST not present\n"); + return false; + } + + xcb_generic_error_t *err = NULL; + xcb_xkb_use_extension_reply_t *usereply; + usereply = xcb_xkb_use_extension_reply( + conn, xcb_xkb_use_extension(conn, XCB_XKB_MAJOR_VERSION, XCB_XKB_MINOR_VERSION), &err); + if (err != NULL || usereply == NULL) { + fprintf(stderr, "xcb_xkb_use_extension() failed\n"); + return false; + } + free(usereply); + + return true; +} + +// NOTE: while |group| should be a uint8_t, Inline::C will not define the +// function unless we use an int. +bool set_xkb_group(int group) { + xcb_generic_error_t *err = NULL; + // Needs libxcb ≥ 1.11 so that we have the following bug fix: + // http://cgit.freedesktop.org/xcb/proto/commit/src/xkb.xml?id=8d7ee5b6ba4cf343f7df70372a3e1f85b82aeed7 + xcb_void_cookie_t cookie = xcb_xkb_latch_lock_state_checked( + conn, + XCB_XKB_ID_USE_CORE_KBD, /* deviceSpec */ + 0, /* affectModLocks */ + 0, /* modLocks */ + 1, /* lockGroup */ + group, /* groupLock */ + 0, /* affectModLatches */ + 0, /* latchGroup */ + 0); /* groupLatch */ + if ((err = xcb_request_check(conn, cookie)) != NULL) { + fprintf(stderr, "X error code %d\n", err->error_code); + return false; + } + return true; +} + +bool xtest_key(int type, int detail) { + xcb_generic_error_t *err; + xcb_void_cookie_t cookie; + + cookie = xcb_test_fake_input_checked( + conn, + type, /* type */ + detail, /* detail */ + XCB_CURRENT_TIME, /* time */ + XCB_NONE, /* root */ + 0, /* rootX */ + 0, /* rootY */ + XCB_NONE); /* deviceid */ + if ((err = xcb_request_check(conn, cookie)) != NULL) { + fprintf(stderr, "X error code %d\n", err->error_code); + return false; + } + + return true; +} + +bool xtest_key_press(int detail) { + return xtest_key(XCB_KEY_PRESS, detail); +} + +bool xtest_key_release(int detail) { + return xtest_key(XCB_KEY_RELEASE, detail); +} + +END_OF_C_CODE + +sub import { + my ($class, %args) = @_; + ok(inlinec_connect(), 'Connect to X11, verify XKB and XTEST are present (via Inline::C)'); + goto \&Exporter::import; +} + +=head1 EXPORT + +=cut + +my $i3; +our @binding_events; + +=head2 start_binding_capture() + +Captures all binding events sent by i3 in the C<@binding_events> symbol, so +that you can verify the correct number of binding events was generated. + + my $pid = launch_with_config($config); + start_binding_capture; + # … + sync_with_i3; + is(scalar @i3test::XTEST::binding_events, 2, 'Received exactly 2 binding events'); + +=cut + +sub start_binding_capture { + # Store a copy of each binding event so that we can count the expected + # events in test cases. + $i3 = i3(get_socket_path()); + $i3->connect()->recv; + $i3->subscribe({ + binding => sub { + my ($event) = @_; + @binding_events = (@binding_events, $event); + }, + })->recv; +} + +=head2 listen_for_binding($cb) + +Helper function to evaluate whether sending KeyPress/KeyRelease events via +XTEST triggers an i3 key binding or not (with a timeout of 0.5s). Expects key +bindings to be configured in the form “bindsym nop ”, e.g. +“bindsym Mod4+Return nop Mod4+Return”. + + is(listen_for_binding( + sub { + xtest_key_press(133); # Super_L + xtest_key_press(36); # Return + xtest_key_release(36); # Return + xtest_key_release(133); # Super_L + }, + ), + 'Mod4+Return', + 'triggered the "Mod4+Return" keybinding'); + +=cut + +sub listen_for_binding { + my ($cb) = @_; + my $triggered = AnyEvent->condvar; + my $i3 = i3(get_socket_path()); + $i3->connect()->recv; + $i3->subscribe({ + binding => sub { + my ($event) = @_; + return unless $event->{change} eq 'run'; + # We look at the command (which is “nop ”) because that is + # easier than re-assembling the string representation of + # $event->{binding}. + $triggered->send($event->{binding}->{command}); + }, + })->recv; + + my $t; + $t = AnyEvent->timer( + after => 0.5, + cb => sub { + $triggered->send('timeout'); + } + ); + + $cb->(); + + my $recv = $triggered->recv; + $recv =~ s/^nop //g; + return $recv; +} + +=head2 set_xkb_group($group) + +Changes the current XKB group from the default of 1 to C<$group>, which must be +one of 1, 2, 3, 4. + +Returns false when there was an X11 error changing the group, true otherwise. + +=head2 xtest_key_press($detail) + +Sends a KeyPress event via XTEST, with the specified C<$detail>, i.e. key code. +Use C to find key codes. + +Returns false when there was an X11 error changing the group, true otherwise. + +=head2 xtest_key_release($detail) + +Sends a KeyRelease event via XTEST, with the specified C<$detail>, i.e. key code. +Use C to find key codes. + +Returns false when there was an X11 error changing the group, true otherwise. + +=head1 AUTHOR + +Michael Stapelberg + +=cut + +1 diff --git a/testcases/t/257-keypress-group1-fallback.t b/testcases/t/257-keypress-group1-fallback.t new file mode 100644 index 00000000..212dfd15 --- /dev/null +++ b/testcases/t/257-keypress-group1-fallback.t @@ -0,0 +1,96 @@ +#!perl +# vim:ts=4:sw=4:expandtab +# +# Please read the following documents before working on tests: +# • http://build.i3wm.org/docs/testsuite.html +# (or docs/testsuite) +# +# • http://build.i3wm.org/docs/lib-i3test.html +# (alternatively: perldoc ./testcases/lib/i3test.pm) +# +# • http://build.i3wm.org/docs/ipc.html +# (or docs/ipc) +# +# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# (unless you are already familiar with Perl) +# +# Verifies that when using multiple keyboard layouts at the same time, bindings +# without a specified XKB group will work in all XKB groups. +# Ticket: #2062 +# Bug still in: 4.11-103-gc8d51b4 +# Bug introduced with commit 0e5180cae9e9295678e3f053042b559e82cb8c98 +use i3test i3_autostart => 0; +use i3test::XTEST; +use ExtUtils::PkgConfig; + +SKIP: { + skip "libxcb-xkb too old (need >= 1.11)", 1 unless + ExtUtils::PkgConfig->atleast_version('xcb-xkb', '1.11'); + skip "setxkbmap not found", 1 if + system(q|setxkbmap -print >/dev/null|) != 0; + +my $config = < 0; +use i3test::XTEST; +use ExtUtils::PkgConfig; + +SKIP: { + skip "libxcb-xkb too old (need >= 1.11)", 1 unless + ExtUtils::PkgConfig->atleast_version('xcb-xkb', '1.11'); + +my $config = < Date: Thu, 19 Nov 2015 13:32:35 +0100 Subject: [PATCH 071/187] Use https if possible git:// and http:// do not protect the integrity of the accessed data. Therefore use https instead. --- release.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/release.sh b/release.sh index b3e84610..766f4d30 100755 --- a/release.sh +++ b/release.sh @@ -8,7 +8,7 @@ export RELEASE_BRANCH="next" if [ ! -e "../i3.github.io" ] then echo "../i3.github.io does not exist." - echo "Use git clone git://github.com/i3/i3.github.io" + echo "Use git clone https://github.com/i3/i3.github.io" exit 1 fi @@ -41,7 +41,7 @@ STARTDIR=$PWD TMPDIR=$(mktemp -d) cd $TMPDIR -if ! wget http://i3wm.org/downloads/i3-${PREVIOUS_VERSION}.tar.bz2; then +if ! wget https://i3wm.org/downloads/i3-${PREVIOUS_VERSION}.tar.bz2; then echo "Could not download i3-${PREVIOUS_VERSION}.tar.bz2 (required for comparing files)." exit 1 fi From 8016cce44755d3b37ce66e0fd8bec560b3c71178 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ingo=20B=C3=BCrk?= Date: Sat, 21 Nov 2015 21:52:43 +0100 Subject: [PATCH 072/187] Document rect, window_rect and deco_rect. --- include/data.h | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/include/data.h b/include/data.h index 3a752c2e..e5a76462 100644 --- a/include/data.h +++ b/include/data.h @@ -572,8 +572,14 @@ struct Con { struct Con *parent; + /* The position and size for this con. These coordinates are absolute. Note + * that the rect of a container does not include the decoration. */ struct Rect rect; + /* The position and size of the actual client window. These coordinates are + * relative to the container's rect. */ struct Rect window_rect; + /* The position and size of the container's decoration. These coordinates + * are relative to the container's parent's rect. */ struct Rect deco_rect; /** the geometry this window requested when getting mapped */ struct Rect geometry; From 00881bb156dfe339358d122301ed959fb0545dad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ingo=20B=C3=BCrk?= Date: Sat, 21 Nov 2015 00:19:49 +0100 Subject: [PATCH 073/187] Support _NET_WM_USER_TIME. With this patch, we support the special value "0" for _NET_WM_USER_TIME on a window upon managing it, which indicates that the window shall not be focused. fixes #2064 --- include/atoms.xmacro | 1 + src/ewmh.c | 2 +- src/manage.c | 20 +++++++- testcases/t/259-net-wm-user-time.t | 74 ++++++++++++++++++++++++++++++ 4 files changed, 95 insertions(+), 2 deletions(-) create mode 100644 testcases/t/259-net-wm-user-time.t diff --git a/include/atoms.xmacro b/include/atoms.xmacro index f856559c..8798528a 100644 --- a/include/atoms.xmacro +++ b/include/atoms.xmacro @@ -30,6 +30,7 @@ xmacro(_NET_DESKTOP_NAMES) xmacro(_NET_DESKTOP_VIEWPORT) xmacro(_NET_ACTIVE_WINDOW) xmacro(_NET_CLOSE_WINDOW) +xmacro(_NET_WM_USER_TIME) xmacro(_NET_STARTUP_ID) xmacro(_NET_WORKAREA) xmacro(WM_PROTOCOLS) diff --git a/src/ewmh.c b/src/ewmh.c index b2260d64..a5c90175 100644 --- a/src/ewmh.c +++ b/src/ewmh.c @@ -263,7 +263,7 @@ void ewmh_setup_hints(void) { /* I’m not entirely sure if we need to keep _NET_WM_NAME on root. */ xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root, A__NET_WM_NAME, A_UTF8_STRING, 8, strlen("i3"), "i3"); - /* only send the first 31 atoms (last one is _NET_CLOSE_WINDOW) increment that number when adding supported atoms */ + /* only send the first 32 atoms (last one is _NET_CLOSE_WINDOW) increment that number when adding supported atoms */ xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root, A__NET_SUPPORTED, XCB_ATOM_ATOM, 32, /* number of atoms */ 32, supported_atoms); /* We need to map this window to be able to set the input focus to it if no other window is available to be focused. */ diff --git a/src/manage.c b/src/manage.c index 98051ec3..87d19ff8 100644 --- a/src/manage.c +++ b/src/manage.c @@ -90,7 +90,7 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki utf8_title_cookie, title_cookie, class_cookie, leader_cookie, transient_cookie, role_cookie, startup_id_cookie, wm_hints_cookie, - wm_normal_hints_cookie, motif_wm_hints_cookie; + wm_normal_hints_cookie, motif_wm_hints_cookie, wm_user_time_cookie; geomc = xcb_get_geometry(conn, d); @@ -161,6 +161,7 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki wm_hints_cookie = xcb_icccm_get_wm_hints(conn, window); wm_normal_hints_cookie = xcb_icccm_get_wm_normal_hints(conn, window); motif_wm_hints_cookie = GET_PROPERTY(A__MOTIF_WM_HINTS, 5 * sizeof(uint64_t)); + wm_user_time_cookie = GET_PROPERTY(A__NET_WM_USER_TIME, UINT32_MAX); DLOG("Managing window 0x%08x\n", window); @@ -532,6 +533,23 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki } } + if (set_focus) { + DLOG("Checking con = %p for _NET_WM_USER_TIME.\n", nc); + + uint32_t *wm_user_time; + xcb_get_property_reply_t *wm_user_time_reply = xcb_get_property_reply(conn, wm_user_time_cookie, NULL); + if (wm_user_time_reply != NULL && xcb_get_property_value_length(wm_user_time_reply) != 0 && + (wm_user_time = xcb_get_property_value(wm_user_time_reply)) && + wm_user_time[0] == 0) { + DLOG("_NET_WM_USER_TIME set to 0, not focusing con = %p.\n", nc); + set_focus = false; + } + + FREE(wm_user_time_reply); + } else { + xcb_discard_reply(conn, wm_user_time_cookie.sequence); + } + /* Defer setting focus after the 'new' event has been sent to ensure the * proper window event sequence. */ if (set_focus && !nc->window->doesnt_accept_focus && nc->mapped) { diff --git a/testcases/t/259-net-wm-user-time.t b/testcases/t/259-net-wm-user-time.t new file mode 100644 index 00000000..530b1167 --- /dev/null +++ b/testcases/t/259-net-wm-user-time.t @@ -0,0 +1,74 @@ +#!perl +# vim:ts=4:sw=4:expandtab +# +# Please read the following documents before working on tests: +# • http://build.i3wm.org/docs/testsuite.html +# (or docs/testsuite) +# +# • http://build.i3wm.org/docs/lib-i3test.html +# (alternatively: perldoc ./testcases/lib/i3test.pm) +# +# • http://build.i3wm.org/docs/ipc.html +# (or docs/ipc) +# +# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# (unless you are already familiar with Perl) +# +# Test for _NET_WM_USER_TIME. +# Ticket: #2064 +use i3test; +use X11::XCB 'PROP_MODE_REPLACE'; + +my ($ws, $other, $con); + +sub open_window_with_user_time { + my $wm_user_time = shift; + + my $window = open_window( + before_map => sub { + my ($window) = @_; + + my $atomname = $x->atom(name => '_NET_WM_USER_TIME'); + my $atomtype = $x->atom(name => 'CARDINAL'); + $x->change_property( + PROP_MODE_REPLACE, + $window->id, + $atomname->id, + $atomtype->id, + 32, + 1, + pack('L1', $wm_user_time), + ); + }, + ); + + return $window; +} + +##################################################################### +# 1: if _NET_WM_USER_TIME is set to 0, the window is not focused +# initially. +##################################################################### + +$ws = fresh_workspace; + +open_window; +$other = get_focused($ws); +open_window_with_user_time(0); + +is(get_focused($ws), $other, 'new window is not focused'); + +##################################################################### +# 2: if _NET_WM_USER_TIME is set to something other than 0, the +# window is focused anyway. +##################################################################### + +$ws = fresh_workspace; + +open_window; +$other = get_focused($ws); +open_window_with_user_time(42); + +isnt(get_focused($ws), $other, 'new window is focused'); + +done_testing; From 8f90b8448b016da41bef32baf0a53aabe3f294b3 Mon Sep 17 00:00:00 2001 From: Tony Crisci Date: Sun, 22 Nov 2015 13:32:21 -0500 Subject: [PATCH 074/187] Bug: parse con_id base 16 Mouse bindings that target the window that was clicked send the command to the parser with `con_id` of the clicked window serialized base 16 for compatability with FreeBSD. See 7c2842e for explaination. Set base to 0 for strtol to handle base 16 numbers for that reason. This allows mouse bindings that target specific windows to work correctly. Without this change, the focused window is always targetted rather than the window that was actually clicked. Regression introduced in b744c5e. --- src/match.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/match.c b/src/match.c index 1cdd478f..950e0fe3 100644 --- a/src/match.c +++ b/src/match.c @@ -280,7 +280,7 @@ void match_parse_property(Match *match, const char *ctype, const char *cvalue) { } char *end; - long parsed = strtol(cvalue, &end, 10); + long parsed = strtol(cvalue, &end, 0); if (parsed == LONG_MIN || parsed == LONG_MAX || parsed < 0 || From 3e11c66aebab5947cfdec7ac18cb1a5c707f6896 Mon Sep 17 00:00:00 2001 From: Till Maas Date: Sun, 22 Nov 2015 19:57:46 +0100 Subject: [PATCH 075/187] Fix contact information fixes #2077 --- PACKAGE-MAINTAINER | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PACKAGE-MAINTAINER b/PACKAGE-MAINTAINER index 269ce0fd..22e10997 100644 --- a/PACKAGE-MAINTAINER +++ b/PACKAGE-MAINTAINER @@ -40,7 +40,7 @@ start of i3 (it will automatically exit if it finds a config file). If you have any questions, ideas, hints, problems or whatever, please do not hesitate to contact me. I will help you out. Just drop me an E-Mail (find the -address at http://michael.stapelberg.de/Kontakt, scroll down to bottom), +address at https://michael.stapelberg.de/Impressum/, scroll down to bottom), contact me using the same address in jabber or ask on our IRC channel: (#i3 on irc.twice-irc.de). From 90d94298fa388e58a0f54607f0ab7a46108e84e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ingo=20B=C3=BCrk?= Date: Wed, 11 Nov 2015 23:39:15 +0100 Subject: [PATCH 076/187] Move draw_util.c to libi3. In order to prepare for using cairo for rendering i3 decorations, we need to make the draw_util.c from i3bar available via libi3 such that both i3bar and i3 can use it. relates to #1278 --- i3bar/include/common.h | 1 - i3bar/include/draw_util.h | 103 ------------------------------- i3bar/include/outputs.h | 1 - include/libi3.h | 93 ++++++++++++++++++++++++++++ {i3bar/src => libi3}/draw_util.c | 7 ++- 5 files changed, 98 insertions(+), 107 deletions(-) delete mode 100644 i3bar/include/draw_util.h rename {i3bar/src => libi3}/draw_util.c (97%) diff --git a/i3bar/include/common.h b/i3bar/include/common.h index aa706bbe..0d46ab6a 100644 --- a/i3bar/include/common.h +++ b/i3bar/include/common.h @@ -88,4 +88,3 @@ TAILQ_HEAD(statusline_head, status_block) statusline_head; #include "config.h" #include "libi3.h" #include "parse_json_header.h" -#include "draw_util.h" diff --git a/i3bar/include/draw_util.h b/i3bar/include/draw_util.h deleted file mode 100644 index f2e12ec5..00000000 --- a/i3bar/include/draw_util.h +++ /dev/null @@ -1,103 +0,0 @@ -/* - * vim:ts=4:sw=4:expandtab - * - * © 2015 Ingo Bürk and contributors (see also: LICENSE) - * - * draw.h: Utility for drawing. - * - */ -#pragma once - -#ifdef CAIRO_SUPPORT -#include -#endif - -#ifdef CAIRO_SUPPORT -/* We need to flush cairo surfaces twice to avoid an assertion bug. See #1989 - * and https://bugs.freedesktop.org/show_bug.cgi?id=92455. */ -#define CAIRO_SURFACE_FLUSH(surface) \ - do { \ - cairo_surface_flush(surface); \ - cairo_surface_flush(surface); \ - } while (0) -#endif - -/* Represents a color split by color channel. */ -typedef struct color_t { - double red; - double green; - double blue; - - /* For compatibility, we also store the colorpixel for now. */ - uint32_t colorpixel; -} color_t; - -/* A wrapper grouping an XCB drawable and both a graphics context - * and the corresponding cairo objects representing it. */ -typedef struct surface_t { - /* The drawable which is being represented. */ - xcb_drawable_t id; - - /* A classic XCB graphics context. */ - xcb_gcontext_t gc; - - int width; - int height; - -#ifdef CAIRO_SUPPORT - /* A cairo surface representing the drawable. */ - cairo_surface_t *surface; - - /* The cairo object representing the drawale. In general, - * this is what one should use for any drawing operation. */ - cairo_t *cr; -#endif -} surface_t; - -/** - * Initialize the surface to represent the given drawable. - * - */ -void draw_util_surface_init(surface_t *surface, xcb_drawable_t drawable, int width, int height); - -/** - * Destroys the surface. - * - */ -void draw_util_surface_free(surface_t *surface); - -/** - * Parses the given color in hex format to an internal color representation. - * Note that the input must begin with a hash sign, e.g., "#3fbc59". - * - */ -color_t draw_util_hex_to_color(const char *color); - -/** - * Draw the given text using libi3. - * This function also marks the surface dirty which is needed if other means of - * drawing are used. This will be the case when using XCB to draw text. - * - */ -void draw_util_text(i3String *text, surface_t *surface, color_t fg_color, color_t bg_color, int x, int y, int max_width); - -/** - * Draws a filled rectangle. - * This function is a convenience wrapper and takes care of flushing the - * surface as well as restoring the cairo state. - * - */ -void draw_util_rectangle(surface_t *surface, color_t color, double x, double y, double w, double h); - -/** - * Clears a surface with the given color. - * - */ -void draw_util_clear_surface(surface_t *surface, color_t color); - -/** - * Copies a surface onto another surface. - * - */ -void draw_util_copy_surface(surface_t *src, surface_t *dest, double src_x, double src_y, - double dest_x, double dest_y, double width, double height); diff --git a/i3bar/include/outputs.h b/i3bar/include/outputs.h index 08adefd9..63cfca7f 100644 --- a/i3bar/include/outputs.h +++ b/i3bar/include/outputs.h @@ -13,7 +13,6 @@ #include #include "common.h" -#include "draw_util.h" typedef struct i3_output i3_output; diff --git a/include/libi3.h b/include/libi3.h index 75d3639b..15dffdb7 100644 --- a/include/libi3.h +++ b/include/libi3.h @@ -20,6 +20,9 @@ #if PANGO_SUPPORT #include #endif +#ifdef CAIRO_SUPPORT +#include +#endif #define DEFAULT_DIR_MODE (S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH) @@ -480,3 +483,93 @@ char *get_config_path(const char *override_configpath, bool use_system_paths); */ int mkdirp(const char *path, mode_t mode); #endif + +#ifdef CAIRO_SUPPORT +/* We need to flush cairo surfaces twice to avoid an assertion bug. See #1989 + * and https://bugs.freedesktop.org/show_bug.cgi?id=92455. */ +#define CAIRO_SURFACE_FLUSH(surface) \ + do { \ + cairo_surface_flush(surface); \ + cairo_surface_flush(surface); \ + } while (0) +#endif + +/* Represents a color split by color channel. */ +typedef struct color_t { + double red; + double green; + double blue; + + /* The colorpixel we use for direct XCB calls. */ + uint32_t colorpixel; +} color_t; + +/* A wrapper grouping an XCB drawable and both a graphics context + * and the corresponding cairo objects representing it. */ +typedef struct surface_t { + /* The drawable which is being represented. */ + xcb_drawable_t id; + + /* A classic XCB graphics context. */ + xcb_gcontext_t gc; + + int width; + int height; + +#ifdef CAIRO_SUPPORT + /* A cairo surface representing the drawable. */ + cairo_surface_t *surface; + + /* The cairo object representing the drawable. In general, + * this is what one should use for any drawing operation. */ + cairo_t *cr; +#endif +} surface_t; + +/** + * Initialize the surface to represent the given drawable. + * + */ +void draw_util_surface_init(surface_t *surface, xcb_drawable_t drawable, int width, int height); + +/** + * Destroys the surface. + * + */ +void draw_util_surface_free(surface_t *surface); + +/** + * Parses the given color in hex format to an internal color representation. + * Note that the input must begin with a hash sign, e.g., "#3fbc59". + * + */ +color_t draw_util_hex_to_color(const char *color); + +/** + * Draw the given text using libi3. + * This function also marks the surface dirty which is needed if other means of + * drawing are used. This will be the case when using XCB to draw text. + * + */ +void draw_util_text(i3String *text, surface_t *surface, color_t fg_color, color_t bg_color, int x, int y, int max_width); + +/** + * Draws a filled rectangle. + * This function is a convenience wrapper and takes care of flushing the + * surface as well as restoring the cairo state. + * + */ +void draw_util_rectangle(surface_t *surface, color_t color, double x, double y, double w, double h); + +/** + * Clears a surface with the given color. + * + */ +void draw_util_clear_surface(surface_t *surface, color_t color); + +/** + * Copies a surface onto another surface. + * + */ +void draw_util_copy_surface(surface_t *src, surface_t *dest, double src_x, double src_y, + double dest_x, double dest_y, double width, double height); diff --git a/i3bar/src/draw_util.c b/libi3/draw_util.c similarity index 97% rename from i3bar/src/draw_util.c rename to libi3/draw_util.c index 86435351..04dc0ce5 100644 --- a/i3bar/src/draw_util.c +++ b/libi3/draw_util.c @@ -15,7 +15,6 @@ #include #endif -#include "common.h" #include "libi3.h" xcb_connection_t *xcb_connection; @@ -35,8 +34,12 @@ void draw_util_surface_init(surface_t *surface, xcb_drawable_t drawable, int wid surface->gc = xcb_generate_id(xcb_connection); xcb_void_cookie_t gc_cookie = xcb_create_gc_checked(xcb_connection, surface->gc, surface->id, 0, NULL); - if (xcb_request_failed(gc_cookie, "Could not create graphical context")) + + xcb_generic_error_t *error = xcb_request_check(xcb_connection, gc_cookie); + if (error != NULL) { + ELOG("Could not create graphical context. Error code: %d\n", error->error_code); exit(EXIT_FAILURE); + } #ifdef CAIRO_SUPPORT surface->surface = cairo_xcb_surface_create(xcb_connection, surface->id, visual_type, width, height); From d9bbfb7b351f13a3a86e43f69fc7d2e4a18582f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ingo=20B=C3=BCrk?= Date: Sun, 15 Nov 2015 17:25:12 +0100 Subject: [PATCH 077/187] Fix draw_util_copy_surface. This patch fixes a bug when copying one surface to another. Since it only exposes itself when used with non-trivial source coordinates, it didn't surface before when only used for i3bar. relates to #1278 --- libi3/draw_util.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libi3/draw_util.c b/libi3/draw_util.c index 04dc0ce5..90b1f26f 100644 --- a/libi3/draw_util.c +++ b/libi3/draw_util.c @@ -186,7 +186,7 @@ void draw_util_copy_surface(surface_t *src, surface_t *dest, double src_x, doubl * onto the surface rather than blending it. This is a bit more efficient and * allows better color control for the user when using opacity. */ cairo_set_operator(dest->cr, CAIRO_OPERATOR_SOURCE); - cairo_set_source_surface(dest->cr, src->surface, dest_x - src_x, src_y); + cairo_set_source_surface(dest->cr, src->surface, dest_x - src_x, dest_y - src_y); cairo_rectangle(dest->cr, dest_x, dest_y, width, height); cairo_fill(dest->cr); From b66504988366b123ebaa5e45160d584495c1f9a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ingo=20B=C3=BCrk?= Date: Mon, 16 Nov 2015 21:26:06 +0100 Subject: [PATCH 078/187] Migrate i3 rendering to cairo. This patch migrates all decoration rendering of i3 to cairo. Using the compile switch CAIRO_SUPPORT, rendering can be switched back to the previous XCB behavior, just like with the previous migration to cairo in i3bar. This patch also fixes a bug in draw_util.c where copying one surface to another would use incorrect coordinates if the source coordinates are not 0, 0. Furthermore, this patch implicitly fixes some minor issues in the decoration rendering which would be ignored previously due to the fact that errors would only show up in the event queue, but not cause the rendering code path to crash. One example is zero-height pixmaps which are not allowed. Using cairo, these would cause i3 to instantly segfault, so this patch avoids this. Lastly, this patch annotates other issues found but not fixed in this patch using TODO comments, e.g., the zero-height check not working correctly and the comment that it should probably work the same way for zero-width pixmaps. relates to #1278 --- i3bar/src/xcb.c | 40 +++--- include/data.h | 7 +- include/libi3.h | 21 +++- include/xcb.h | 1 + libi3/draw_util.c | 66 ++++++---- src/con.c | 2 +- src/handlers.c | 11 +- src/main.c | 2 + src/manage.c | 2 +- src/x.c | 315 ++++++++++++++++++++++++---------------------- src/xcb.c | 16 +++ 11 files changed, 276 insertions(+), 207 deletions(-) diff --git a/i3bar/src/xcb.c b/i3bar/src/xcb.c index 92b0d1ab..c6c9846b 100644 --- a/i3bar/src/xcb.c +++ b/i3bar/src/xcb.c @@ -174,7 +174,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, sep_fg, + draw_util_rectangle(xcb_connection, &output->statusline_buffer, sep_fg, center_x, logical_px(sep_voff_px), logical_px(1), @@ -243,7 +243,7 @@ void draw_statusline(i3_output *output, uint32_t clip_left, bool use_focus_color struct status_block *block; color_t bar_color = (use_focus_colors ? colors.focus_bar_bg : colors.bar_bg); - draw_util_clear_surface(&output->statusline_buffer, bar_color); + draw_util_clear_surface(xcb_connection, &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 @@ -294,13 +294,13 @@ void draw_statusline(i3_output *output, uint32_t clip_left, bool use_focus_color } /* Draw the border. */ - draw_util_rectangle(&output->statusline_buffer, border_color, + draw_util_rectangle(xcb_connection, &output->statusline_buffer, border_color, x, logical_px(1), full_render_width, bar_height - logical_px(2)); /* Draw the background. */ - draw_util_rectangle(&output->statusline_buffer, bg_color, + draw_util_rectangle(xcb_connection, &output->statusline_buffer, bg_color, x + border_width, logical_px(1) + border_width, full_render_width - 2 * border_width, @@ -1709,9 +1709,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); @@ -1820,12 +1820,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) { @@ -1886,7 +1886,7 @@ void draw_bars(bool unhide) { bool use_focus_colors = output_has_focus(outputs_walk); /* First things first: clear the backbuffer */ - draw_util_clear_surface(&(outputs_walk->buffer), + draw_util_clear_surface(xcb_connection, &(outputs_walk->buffer), (use_focus_colors ? colors.focus_bar_bg : colors.bar_bg)); if (!config.disable_ws) { @@ -1917,14 +1917,14 @@ void draw_bars(bool unhide) { } /* Draw the border of the button. */ - draw_util_rectangle(&(outputs_walk->buffer), border_color, + draw_util_rectangle(xcb_connection, &(outputs_walk->buffer), border_color, workspace_width, logical_px(1), 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)); /* Draw the inside of the button. */ - draw_util_rectangle(&(outputs_walk->buffer), bg_color, + draw_util_rectangle(xcb_connection, &(outputs_walk->buffer), bg_color, workspace_width + logical_px(1), 2 * logical_px(1), ws_walk->name_width + 2 * logical_px(ws_hoff_px), @@ -1947,13 +1947,13 @@ void draw_bars(bool unhide) { color_t fg_color = colors.binding_mode_fg; color_t bg_color = colors.binding_mode_bg; - draw_util_rectangle(&(outputs_walk->buffer), colors.binding_mode_border, + draw_util_rectangle(xcb_connection, &(outputs_walk->buffer), colors.binding_mode_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)); - draw_util_rectangle(&(outputs_walk->buffer), bg_color, + draw_util_rectangle(xcb_connection, &(outputs_walk->buffer), bg_color, workspace_width + logical_px(1), 2 * logical_px(1), binding.width + 2 * logical_px(ws_hoff_px), @@ -1989,7 +1989,7 @@ void draw_bars(bool unhide) { int x_dest = outputs_walk->rect.w - tray_width - logical_px(sb_hoff_px) - visible_statusline_width; 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, + draw_util_copy_surface(xcb_connection, &outputs_walk->statusline_buffer, &outputs_walk->buffer, 0, 0, x_dest, 0, visible_statusline_width, (int16_t)bar_height); outputs_walk->statusline_width = statusline_width; @@ -2020,7 +2020,7 @@ void redraw_bars(void) { continue; } - draw_util_copy_surface(&(outputs_walk->buffer), &(outputs_walk->bar), 0, 0, + draw_util_copy_surface(xcb_connection, &(outputs_walk->buffer), &(outputs_walk->bar), 0, 0, 0, 0, outputs_walk->rect.w, outputs_walk->rect.h); xcb_flush(xcb_connection); } diff --git a/include/data.h b/include/data.h index e5a76462..78a42ff4 100644 --- a/include/data.h +++ b/include/data.h @@ -551,11 +551,10 @@ struct Con { * change. */ uint8_t ignore_unmap; - /* ids/pixmap/graphics context for the frame window */ + /* The surface used for the frame window. */ + surface_t frame; + surface_t frame_buffer; bool pixmap_recreated; - xcb_window_t frame; - xcb_pixmap_t pixmap; - xcb_gcontext_t pm_gc; enum { CT_ROOT = 0, diff --git a/include/libi3.h b/include/libi3.h index 15dffdb7..0cb2532f 100644 --- a/include/libi3.h +++ b/include/libi3.h @@ -513,6 +513,8 @@ typedef struct surface_t { /* A classic XCB graphics context. */ xcb_gcontext_t gc; + xcb_visualtype_t *visual_type; + int width; int height; @@ -530,13 +532,20 @@ typedef struct surface_t { * Initialize the surface to represent the given drawable. * */ -void draw_util_surface_init(surface_t *surface, xcb_drawable_t drawable, int width, int height); +void draw_util_surface_init(xcb_connection_t *conn, surface_t *surface, xcb_drawable_t drawable, + xcb_visualtype_t *visual, int width, int height); + +/** + * Resize the surface to the given size. + * + */ +void draw_util_surface_set_size(surface_t *surface, int width, int height); /** * Destroys the surface. * */ -void draw_util_surface_free(surface_t *surface); +void draw_util_surface_free(xcb_connection_t *conn, surface_t *surface); /** * Parses the given color in hex format to an internal color representation. @@ -545,6 +554,8 @@ void draw_util_surface_free(surface_t *surface); */ color_t draw_util_hex_to_color(const char *color); +color_t draw_util_colorpixel_to_color(uint32_t colorpixel); + /** * Draw the given text using libi3. * This function also marks the surface dirty which is needed if other means of @@ -559,17 +570,17 @@ void draw_util_text(i3String *text, surface_t *surface, color_t fg_color, color_ * surface as well as restoring the cairo state. * */ -void draw_util_rectangle(surface_t *surface, color_t color, double x, double y, double w, double h); +void draw_util_rectangle(xcb_connection_t *conn, surface_t *surface, color_t color, double x, double y, double w, double h); /** * Clears a surface with the given color. * */ -void draw_util_clear_surface(surface_t *surface, color_t color); +void draw_util_clear_surface(xcb_connection_t *conn, surface_t *surface, color_t color); /** * Copies a surface onto another surface. * */ -void draw_util_copy_surface(surface_t *src, surface_t *dest, double src_x, double src_y, +void draw_util_copy_surface(xcb_connection_t *conn, surface_t *src, surface_t *dest, double src_x, double src_y, double dest_x, double dest_y, double width, double height); diff --git a/include/xcb.h b/include/xcb.h index c1f989bd..7fae41f5 100644 --- a/include/xcb.h +++ b/include/xcb.h @@ -139,6 +139,7 @@ void xcb_set_root_cursor(int cursor); * */ uint16_t get_visual_depth(xcb_visualid_t visual_id); +xcb_visualtype_t *get_visualtype_by_id(xcb_visualid_t visual_id); /** * Get visualid with specified depth diff --git a/libi3/draw_util.c b/libi3/draw_util.c index 90b1f26f..e7e7c09c 100644 --- a/libi3/draw_util.c +++ b/libi3/draw_util.c @@ -17,32 +17,34 @@ #include "libi3.h" -xcb_connection_t *xcb_connection; +/* The default visual_type to use if none is specified when creating the surface. Must be defined globally. */ xcb_visualtype_t *visual_type; /* Forward declarations */ -static void draw_util_set_source_color(surface_t *surface, color_t color); +static void draw_util_set_source_color(xcb_connection_t *conn, surface_t *surface, color_t color); /* * Initialize the surface to represent the given drawable. * */ -void draw_util_surface_init(surface_t *surface, xcb_drawable_t drawable, int width, int height) { +void draw_util_surface_init(xcb_connection_t *conn, surface_t *surface, xcb_drawable_t drawable, + xcb_visualtype_t *visual, int width, int height) { surface->id = drawable; + surface->visual_type = (visual == NULL) ? visual_type : visual; surface->width = width; surface->height = height; - surface->gc = xcb_generate_id(xcb_connection); - xcb_void_cookie_t gc_cookie = xcb_create_gc_checked(xcb_connection, surface->gc, surface->id, 0, NULL); + surface->gc = xcb_generate_id(conn); + xcb_void_cookie_t gc_cookie = xcb_create_gc_checked(conn, surface->gc, surface->id, 0, NULL); - xcb_generic_error_t *error = xcb_request_check(xcb_connection, gc_cookie); + xcb_generic_error_t *error = xcb_request_check(conn, gc_cookie); if (error != NULL) { ELOG("Could not create graphical context. Error code: %d\n", error->error_code); exit(EXIT_FAILURE); } #ifdef CAIRO_SUPPORT - surface->surface = cairo_xcb_surface_create(xcb_connection, surface->id, visual_type, width, height); + surface->surface = cairo_xcb_surface_create(conn, surface->id, surface->visual_type, width, height); surface->cr = cairo_create(surface->surface); #endif } @@ -51,14 +53,26 @@ void draw_util_surface_init(surface_t *surface, xcb_drawable_t drawable, int wid * Destroys the surface. * */ -void draw_util_surface_free(surface_t *surface) { - xcb_free_gc(xcb_connection, surface->gc); +void draw_util_surface_free(xcb_connection_t *conn, surface_t *surface) { + xcb_free_gc(conn, surface->gc); #ifdef CAIRO_SUPPORT cairo_surface_destroy(surface->surface); cairo_destroy(surface->cr); #endif } +/* + * Resize the surface to the given size. + * + */ +void draw_util_surface_set_size(surface_t *surface, int width, int height) { + surface->width = width; + surface->height = height; +#ifdef CAIRO_SUPPORT + cairo_xcb_surface_set_size(surface->surface, width, height); +#endif +} + /* * Parses the given color in hex format to an internal color representation. * Note that the input must begin with a hash sign, e.g., "#3fbc59". @@ -77,16 +91,24 @@ color_t draw_util_hex_to_color(const char *color) { .colorpixel = get_colorpixel(color)}; } +color_t draw_util_colorpixel_to_color(uint32_t colorpixel) { + return (color_t){ + .red = ((colorpixel >> 16) & 0xFF) / 255.0, + .green = ((colorpixel >> 8) & 0xFF) / 255.0, + .blue = (colorpixel & 0xFF) / 255.0, + .colorpixel = colorpixel}; +} + /* * Set the given color as the source color on the surface. * */ -static void draw_util_set_source_color(surface_t *surface, color_t color) { +static void draw_util_set_source_color(xcb_connection_t *conn, surface_t *surface, color_t color) { #ifdef CAIRO_SUPPORT cairo_set_source_rgb(surface->cr, color.red, color.green, color.blue); #else uint32_t colorpixel = color.colorpixel; - xcb_change_gc(xcb_connection, surface->gc, XCB_GC_FOREGROUND | XCB_GC_BACKGROUND, + xcb_change_gc(conn, surface->gc, XCB_GC_FOREGROUND | XCB_GC_BACKGROUND, (uint32_t[]){colorpixel, colorpixel}); #endif } @@ -104,7 +126,7 @@ void draw_util_text(i3String *text, surface_t *surface, color_t fg_color, color_ #endif set_font_colors(surface->gc, fg_color.colorpixel, bg_color.colorpixel); - draw_text(text, surface->id, surface->gc, visual_type, x, y, max_width); + draw_text(text, surface->id, surface->gc, surface->visual_type, x, y, max_width); #ifdef CAIRO_SUPPORT /* Notify cairo that we (possibly) used another way to draw on the surface. */ @@ -118,7 +140,7 @@ void draw_util_text(i3String *text, surface_t *surface, color_t fg_color, color_ * surface as well as restoring the cairo state. * */ -void draw_util_rectangle(surface_t *surface, color_t color, double x, double y, double w, double h) { +void draw_util_rectangle(xcb_connection_t *conn, surface_t *surface, color_t color, double x, double y, double w, double h) { #ifdef CAIRO_SUPPORT cairo_save(surface->cr); @@ -126,7 +148,7 @@ void draw_util_rectangle(surface_t *surface, color_t color, double x, double y, * onto the surface rather than blending it. This is a bit more efficient and * allows better color control for the user when using opacity. */ cairo_set_operator(surface->cr, CAIRO_OPERATOR_SOURCE); - draw_util_set_source_color(surface, color); + draw_util_set_source_color(conn, surface, color); cairo_rectangle(surface->cr, x, y, w, h); cairo_fill(surface->cr); @@ -137,10 +159,10 @@ void draw_util_rectangle(surface_t *surface, color_t color, double x, double y, cairo_restore(surface->cr); #else - draw_util_set_source_color(surface, color); + draw_util_set_source_color(conn, surface, color); xcb_rectangle_t rect = {x, y, w, h}; - xcb_poly_fill_rectangle(xcb_connection, surface->id, surface->gc, 1, &rect); + xcb_poly_fill_rectangle(conn, surface->id, surface->gc, 1, &rect); #endif } @@ -148,7 +170,7 @@ void draw_util_rectangle(surface_t *surface, color_t color, double x, double y, * Clears a surface with the given color. * */ -void draw_util_clear_surface(surface_t *surface, color_t color) { +void draw_util_clear_surface(xcb_connection_t *conn, surface_t *surface, color_t color) { #ifdef CAIRO_SUPPORT cairo_save(surface->cr); @@ -156,7 +178,7 @@ void draw_util_clear_surface(surface_t *surface, color_t color) { * onto the surface rather than blending it. This is a bit more efficient and * allows better color control for the user when using opacity. */ cairo_set_operator(surface->cr, CAIRO_OPERATOR_SOURCE); - draw_util_set_source_color(surface, color); + draw_util_set_source_color(conn, surface, color); cairo_paint(surface->cr); @@ -166,10 +188,10 @@ void draw_util_clear_surface(surface_t *surface, color_t color) { cairo_restore(surface->cr); #else - draw_util_set_source_color(surface, color); + draw_util_set_source_color(conn, surface, color); xcb_rectangle_t rect = {0, 0, surface->width, surface->height}; - xcb_poly_fill_rectangle(xcb_connection, surface->id, surface->gc, 1, &rect); + xcb_poly_fill_rectangle(conn, surface->id, surface->gc, 1, &rect); #endif } @@ -177,7 +199,7 @@ void draw_util_clear_surface(surface_t *surface, color_t color) { * Copies a surface onto another surface. * */ -void draw_util_copy_surface(surface_t *src, surface_t *dest, double src_x, double src_y, +void draw_util_copy_surface(xcb_connection_t *conn, surface_t *src, surface_t *dest, double src_x, double src_y, double dest_x, double dest_y, double width, double height) { #ifdef CAIRO_SUPPORT cairo_save(dest->cr); @@ -198,7 +220,7 @@ void draw_util_copy_surface(surface_t *src, surface_t *dest, double src_x, doubl cairo_restore(dest->cr); #else - xcb_copy_area(xcb_connection, src->id, dest->id, dest->gc, (int16_t)src_x, (int16_t)src_y, + xcb_copy_area(conn, src->id, dest->id, dest->gc, (int16_t)src_x, (int16_t)src_y, (int16_t)dest_x, (int16_t)dest_y, (uint16_t)width, (uint16_t)height); #endif } diff --git a/src/con.c b/src/con.c index bd002d35..03fd0ee6 100644 --- a/src/con.c +++ b/src/con.c @@ -514,7 +514,7 @@ Con *con_by_window_id(xcb_window_t window) { Con *con_by_frame_id(xcb_window_t frame) { Con *con; TAILQ_FOREACH(con, &all_cons, all_cons) - if (con->frame == frame) + if (con->frame.id == frame) return con; return NULL; } diff --git a/src/handlers.c b/src/handlers.c index e3ddf701..6f08d25c 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -656,15 +656,14 @@ static void handle_expose_event(xcb_expose_event_t *event) { return; } - /* Since we render to our pixmap on every change anyways, expose events + /* Since we render to our surface on every change anyways, expose events * only tell us that the X server lost (parts of) the window contents. We - * can handle that by copying the appropriate part from our pixmap to the + * can handle that by copying the appropriate part from our surface to the * window. */ - xcb_copy_area(conn, parent->pixmap, parent->frame, parent->pm_gc, - event->x, event->y, event->x, event->y, - event->width, event->height); + draw_util_copy_surface(conn, &(parent->frame_buffer), &(parent->frame), + event->x, event->y, event->x, event->y, + event->width, event->height); xcb_flush(conn); - return; } diff --git a/src/main.c b/src/main.c index 59c00146..1e0ec4a5 100644 --- a/src/main.c +++ b/src/main.c @@ -60,6 +60,7 @@ xcb_window_t root; * otherwise the root window’s default (usually 24 bit TrueColor). */ uint8_t root_depth; xcb_visualid_t visual_id; +xcb_visualtype_t *visual_type; xcb_colormap_t colormap; struct ev_loop *main_loop; @@ -487,6 +488,7 @@ int main(int argc, char *argv[]) { * transparency) and use it if so. */ root_depth = root_screen->root_depth; visual_id = root_screen->root_visual; + visual_type = get_visualtype(root_screen); colormap = root_screen->default_colormap; DLOG("root_depth = %d, visual_id = 0x%08x.\n", root_depth, visual_id); diff --git a/src/manage.c b/src/manage.c index 87d19ff8..05ac15f0 100644 --- a/src/manage.c +++ b/src/manage.c @@ -473,7 +473,7 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki values[0] = XCB_NONE; xcb_change_window_attributes(conn, window, XCB_CW_EVENT_MASK, values); - xcb_void_cookie_t rcookie = xcb_reparent_window_checked(conn, window, nc->frame, 0, 0); + xcb_void_cookie_t rcookie = xcb_reparent_window_checked(conn, window, nc->frame.id, 0, 0); if (xcb_request_check(conn, rcookie) != NULL) { LOG("Could not reparent the window, aborting\n"); goto geom_out; diff --git a/src/x.c b/src/x.c index 094a33f0..86a0c797 100644 --- a/src/x.c +++ b/src/x.c @@ -144,10 +144,11 @@ void x_con_init(Con *con, uint16_t depth) { } Rect dims = {-15, -15, 10, 10}; - con->frame = create_window(conn, dims, depth, visual, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCURSOR_CURSOR_POINTER, false, mask, values); + xcb_window_t frame_id = create_window(conn, dims, depth, visual, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCURSOR_CURSOR_POINTER, false, mask, values); + draw_util_surface_init(conn, &(con->frame), frame_id, get_visualtype_by_id(visual), dims.width, dims.height); xcb_change_property(conn, XCB_PROP_MODE_REPLACE, - con->frame, + con->frame.id, XCB_ATOM_WM_CLASS, XCB_ATOM_STRING, 8, @@ -158,7 +159,7 @@ void x_con_init(Con *con, uint16_t depth) { xcb_free_colormap(conn, win_colormap); struct con_state *state = scalloc(1, sizeof(struct con_state)); - state->id = con->frame; + state->id = con->frame.id; state->mapped = false; state->initial = true; DLOG("Adding window 0x%08x to lists\n", state->id); @@ -177,7 +178,7 @@ void x_con_init(Con *con, uint16_t depth) { void x_reinit(Con *con) { struct con_state *state; - if ((state = state_for_frame(con->frame)) == NULL) { + if ((state = state_for_frame(con->frame.id)) == NULL) { ELOG("window state not found\n"); return; } @@ -196,13 +197,13 @@ void x_reinit(Con *con) { */ void x_reparent_child(Con *con, Con *old) { struct con_state *state; - if ((state = state_for_frame(con->frame)) == NULL) { + if ((state = state_for_frame(con->frame.id)) == NULL) { ELOG("window state for con not found\n"); return; } state->need_reparent = true; - state->old_frame = old->frame; + state->old_frame = old->frame.id; } /* @@ -212,12 +213,12 @@ void x_reparent_child(Con *con, Con *old) { void x_move_win(Con *src, Con *dest) { struct con_state *state_src, *state_dest; - if ((state_src = state_for_frame(src->frame)) == NULL) { + if ((state_src = state_for_frame(src->frame.id)) == NULL) { ELOG("window state for src not found\n"); return; } - if ((state_dest = state_for_frame(dest->frame)) == NULL) { + if ((state_dest = state_for_frame(dest->frame.id)) == NULL) { ELOG("window state for dest not found\n"); return; } @@ -239,10 +240,11 @@ void x_move_win(Con *src, Con *dest) { void x_con_kill(Con *con) { con_state *state; - xcb_destroy_window(conn, con->frame); - xcb_free_pixmap(conn, con->pixmap); - xcb_free_gc(conn, con->pm_gc); - state = state_for_frame(con->frame); + draw_util_surface_free(conn, &(con->frame)); + draw_util_surface_free(conn, &(con->frame_buffer)); + xcb_destroy_window(conn, con->frame.id); + xcb_free_pixmap(conn, con->frame_buffer.id); + state = state_for_frame(con->frame.id); CIRCLEQ_REMOVE(&state_head, state, state); CIRCLEQ_REMOVE(&old_state_head, state, old_state); TAILQ_REMOVE(&initial_mapping_head, state, initial_mapping_order); @@ -312,6 +314,54 @@ void x_window_kill(xcb_window_t window, kill_window_t kill_window) { free(event); } +static void x_draw_decoration_border(Con *con, struct deco_render_params *p) { + assert(con->parent != NULL); + + Rect *dr = &(con->deco_rect); + adjacent_t borders_to_hide = con_adjacent_borders(con) & config.hide_edge_borders; + int deco_diff_l = borders_to_hide & ADJ_LEFT_SCREEN_EDGE ? 0 : con->current_border_width; + int deco_diff_r = borders_to_hide & ADJ_RIGHT_SCREEN_EDGE ? 0 : con->current_border_width; + if (con->parent->layout == L_TABBED || + (con->parent->layout == L_STACKED && TAILQ_NEXT(con, nodes) != NULL)) { + deco_diff_l = 0; + deco_diff_r = 0; + } + + draw_util_rectangle(conn, &(con->parent->frame_buffer), draw_util_colorpixel_to_color(p->color->border), + dr->x, dr->y, dr->width, 1); + + draw_util_rectangle(conn, &(con->parent->frame_buffer), draw_util_colorpixel_to_color(p->color->border), + dr->x + deco_diff_l, dr->y + dr->height - 1, dr->width - (deco_diff_l + deco_diff_r), 1); +} + +static void x_draw_decoration_after_title(Con *con, struct deco_render_params *p) { + assert(con->parent != NULL); + + Rect *dr = &(con->deco_rect); + Rect br = con_border_style_rect(con); + + /* Redraw the right border to cut off any text that went past it. + * This is necessary when the text was drawn using XCB since cutting text off + * automatically does not work there. For pango rendering, this isn't necessary. */ + draw_util_rectangle(conn, &(con->parent->frame_buffer), draw_util_colorpixel_to_color(p->color->background), + dr->x + dr->width + br.width, dr->y, -br.width, dr->height); + + /* Draw a 1px separator line before and after every tab, so that tabs can + * be easily distinguished. */ + if (con->parent->layout == L_TABBED) { + /* Left side */ + draw_util_rectangle(conn, &(con->parent->frame_buffer), draw_util_colorpixel_to_color(p->color->border), + dr->x, dr->y, 1, dr->height); + + /* Right side */ + draw_util_rectangle(conn, &(con->parent->frame_buffer), draw_util_colorpixel_to_color(p->color->border), + dr->x + dr->width - 1, dr->y, 1, dr->height); + } + + /* Redraw the border. */ + x_draw_decoration_border(con, p); +} + /* * Draws the decoration of the given container onto its parent. * @@ -343,7 +393,7 @@ void x_draw_decoration(Con *con) { /* Skip containers whose pixmap has not yet been created (can happen when * decoration rendering happens recursively for a window for which * x_push_node() was not yet called) */ - if (leaf && con->pixmap == XCB_NONE) + if (leaf && con->frame_buffer.id == XCB_NONE) return; /* 1: build deco_params and compare with cache */ @@ -395,29 +445,20 @@ void x_draw_decoration(Con *con) { con->pixmap_recreated = false; con->mark_changed = false; - /* 2: draw the client.background, but only for the parts around the client_rect */ + /* 2: draw the client.background, but only for the parts around the window_rect */ if (con->window != NULL) { - xcb_rectangle_t background[] = { - /* top area */ - {0, 0, r->width, w->y}, - /* bottom area */ - {0, (w->y + w->height), r->width, r->height - (w->y + w->height)}, - /* left area */ - {0, 0, w->x, r->height}, - /* right area */ - {w->x + w->width, 0, r->width - (w->x + w->width), r->height}}; -#if 0 - for (int i = 0; i < 4; i++) - DLOG("rect is (%d, %d) with %d x %d\n", - background[i].x, - background[i].y, - background[i].width, - background[i].height - ); -#endif - - xcb_change_gc(conn, con->pm_gc, XCB_GC_FOREGROUND, (uint32_t[]){config.client.background}); - xcb_poly_fill_rectangle(conn, con->pixmap, con->pm_gc, sizeof(background) / sizeof(xcb_rectangle_t), background); + /* top area */ + draw_util_rectangle(conn, &(con->frame_buffer), draw_util_colorpixel_to_color(config.client.background), + 0, 0, r->width, w->y); + /* bottom area */ + draw_util_rectangle(conn, &(con->frame_buffer), draw_util_colorpixel_to_color(config.client.background), + 0, w->y + w->height, r->width, r->height - (w->y + w->height)); + /* left area */ + draw_util_rectangle(conn, &(con->frame_buffer), draw_util_colorpixel_to_color(config.client.background), + 0, 0, w->x, r->height); + /* right area */ + draw_util_rectangle(conn, &(con->frame_buffer), draw_util_colorpixel_to_color(config.client.background), + w->x + w->width, 0, r->width - (w->x + w->width), r->height); } /* 3: draw a rectangle in border color around the client */ @@ -433,27 +474,26 @@ void x_draw_decoration(Con *con) { DLOG("window_rect spans (%d, %d) with %d x %d\n", con->window_rect.x, con->window_rect.y, con->window_rect.width, con->window_rect.height); #endif - /* These rectangles represents the border around the child window + /* These rectangles represent the border around the child window * (left, bottom and right part). We don’t just fill the whole * rectangle because some childs are not freely resizable and we want * their background color to "shine through". */ - xcb_change_gc(conn, con->pm_gc, XCB_GC_FOREGROUND, (uint32_t[]){p->color->background}); if (!(borders_to_hide & ADJ_LEFT_SCREEN_EDGE)) { - xcb_rectangle_t leftline = {0, 0, br.x, r->height}; - xcb_poly_fill_rectangle(conn, con->pixmap, con->pm_gc, 1, &leftline); + draw_util_rectangle(conn, &(con->frame_buffer), draw_util_colorpixel_to_color(p->color->background), + 0, 0, br.x, r->height); } if (!(borders_to_hide & ADJ_RIGHT_SCREEN_EDGE)) { - xcb_rectangle_t rightline = {r->width + (br.width + br.x), 0, -(br.width + br.x), r->height}; - xcb_poly_fill_rectangle(conn, con->pixmap, con->pm_gc, 1, &rightline); + draw_util_rectangle(conn, &(con->frame_buffer), draw_util_colorpixel_to_color(p->color->background), + r->width + (br.width + br.x), 0, -(br.width + br.x), r->height); } if (!(borders_to_hide & ADJ_LOWER_SCREEN_EDGE)) { - xcb_rectangle_t bottomline = {br.x, r->height + (br.height + br.y), r->width + br.width, -(br.height + br.y)}; - xcb_poly_fill_rectangle(conn, con->pixmap, con->pm_gc, 1, &bottomline); + draw_util_rectangle(conn, &(con->frame_buffer), draw_util_colorpixel_to_color(p->color->background), + br.x, r->height + (br.height + br.y), r->width + br.width, -(br.height + br.y)); } - /* 1pixel border needs an additional line at the top */ + /* pixel border needs an additional line at the top */ if (p->border_style == BS_PIXEL && !(borders_to_hide & ADJ_UPPER_SCREEN_EDGE)) { - xcb_rectangle_t topline = {br.x, 0, r->width + br.width, br.y}; - xcb_poly_fill_rectangle(conn, con->pixmap, con->pm_gc, 1, &topline); + draw_util_rectangle(conn, &(con->frame_buffer), draw_util_colorpixel_to_color(p->color->background), + br.x, 0, r->width + br.width, br.y); } /* Highlight the side of the border at which the next window will be @@ -463,13 +503,13 @@ void x_draw_decoration(Con *con) { if (TAILQ_NEXT(con, nodes) == NULL && TAILQ_PREV(con, nodes_head, nodes) == NULL && con->parent->type != CT_FLOATING_CON) { - xcb_change_gc(conn, con->pm_gc, XCB_GC_FOREGROUND, (uint32_t[]){p->color->indicator}); - if (p->parent_layout == L_SPLITH) - xcb_poly_fill_rectangle(conn, con->pixmap, con->pm_gc, 1, (xcb_rectangle_t[]){ - {r->width + (br.width + br.x), br.y, -(br.width + br.x), r->height + br.height}}); - else if (p->parent_layout == L_SPLITV) - xcb_poly_fill_rectangle(conn, con->pixmap, con->pm_gc, 1, (xcb_rectangle_t[]){ - {br.x, r->height + (br.height + br.y), r->width + br.width, -(br.height + br.y)}}); + if (p->parent_layout == L_SPLITH) { + draw_util_rectangle(conn, &(con->frame_buffer), draw_util_colorpixel_to_color(p->color->indicator), + r->width + (br.width + br.x), br.y, -(br.width + br.x), r->height + br.height); + } else if (p->parent_layout == L_SPLITV) { + draw_util_rectangle(conn, &(con->frame_buffer), draw_util_colorpixel_to_color(p->color->indicator), + br.x, r->height + (br.height + br.y), r->width + br.width, -(br.height + br.y)); + } } } @@ -478,31 +518,19 @@ void x_draw_decoration(Con *con) { if (p->border_style != BS_NORMAL) goto copy_pixmaps; + /* If the parent hasn't been set up yet, skip the decoratin rendering + * for now. */ + if (parent->frame_buffer.id == XCB_NONE) + goto copy_pixmaps; + /* 4: paint the bar */ - xcb_change_gc(conn, parent->pm_gc, XCB_GC_FOREGROUND, (uint32_t[]){p->color->background}); - xcb_rectangle_t drect = {con->deco_rect.x, con->deco_rect.y, con->deco_rect.width, con->deco_rect.height}; - xcb_poly_fill_rectangle(conn, parent->pixmap, parent->pm_gc, 1, &drect); + draw_util_rectangle(conn, &(parent->frame_buffer), draw_util_colorpixel_to_color(p->color->background), + con->deco_rect.x, con->deco_rect.y, con->deco_rect.width, con->deco_rect.height); /* 5: draw two unconnected horizontal lines in border color */ - xcb_change_gc(conn, parent->pm_gc, XCB_GC_FOREGROUND, (uint32_t[]){p->color->border}); - Rect *dr = &(con->deco_rect); - adjacent_t borders_to_hide = con_adjacent_borders(con) & config.hide_edge_borders; - int deco_diff_l = borders_to_hide & ADJ_LEFT_SCREEN_EDGE ? 0 : con->current_border_width; - int deco_diff_r = borders_to_hide & ADJ_RIGHT_SCREEN_EDGE ? 0 : con->current_border_width; - if (parent->layout == L_TABBED || - (parent->layout == L_STACKED && TAILQ_NEXT(con, nodes) != NULL)) { - deco_diff_l = 0; - deco_diff_r = 0; - } - xcb_segment_t segments[] = { - {dr->x, dr->y, - dr->x + dr->width - 1, dr->y}, - {dr->x + deco_diff_l, dr->y + dr->height - 1, - dr->x - deco_diff_r + dr->width - 1, dr->y + dr->height - 1}}; - xcb_poly_segment(conn, parent->pixmap, parent->pm_gc, 2, segments); + x_draw_decoration_border(con, p); /* 6: draw the title */ - set_font_colors(parent->pm_gc, p->color->text, p->color->background); int text_offset_y = (con->deco_rect.height - config.font.height) / 2; struct Window *win = con->window; @@ -510,16 +538,18 @@ void x_draw_decoration(Con *con) { /* we have a split container which gets a representation * of its children as title */ - char *title; + char *_title; char *tree = con_get_tree_representation(con); - sasprintf(&title, "i3: %s", tree); + sasprintf(&_title, "i3: %s", tree); free(tree); - draw_text_ascii(title, - parent->pixmap, parent->pm_gc, - con->deco_rect.x + 2, con->deco_rect.y + text_offset_y, - con->deco_rect.width - 2); - free(title); + i3String *title = i3string_from_utf8(_title); + draw_util_text(title, &(parent->frame_buffer), + draw_util_colorpixel_to_color(p->color->text), draw_util_colorpixel_to_color(p->color->background), + con->deco_rect.x + 2, con->deco_rect.y + text_offset_y, + con->deco_rect.width - 2); + FREE(_title); + I3STRING_FREE(title); goto after_title; } @@ -565,9 +595,10 @@ void x_draw_decoration(Con *con) { i3String *mark = i3string_from_utf8(formatted_mark); mark_width = predict_text_width(mark); - draw_text(mark, parent->pixmap, parent->pm_gc, NULL, - con->deco_rect.x + con->deco_rect.width - mark_width - logical_px(2), - con->deco_rect.y + text_offset_y, mark_width); + draw_util_text(mark, &(parent->frame_buffer), + draw_util_colorpixel_to_color(p->color->text), draw_util_colorpixel_to_color(p->color->background), + con->deco_rect.x + con->deco_rect.width - mark_width - logical_px(2), + con->deco_rect.y + text_offset_y, mark_width); I3STRING_FREE(mark); } @@ -576,41 +607,17 @@ void x_draw_decoration(Con *con) { } i3String *title = win->title_format == NULL ? win->name : window_parse_title_format(win); - draw_text(title, - parent->pixmap, parent->pm_gc, NULL, - con->deco_rect.x + logical_px(2) + indent_px, con->deco_rect.y + text_offset_y, - con->deco_rect.width - logical_px(2) - indent_px - mark_width - logical_px(2)); + draw_util_text(title, &(parent->frame_buffer), + draw_util_colorpixel_to_color(p->color->text), draw_util_colorpixel_to_color(p->color->background), + con->deco_rect.x + logical_px(2) + indent_px, con->deco_rect.y + text_offset_y, + con->deco_rect.width - logical_px(2) - indent_px - mark_width - logical_px(2)); if (win->title_format != NULL) I3STRING_FREE(title); after_title: - /* Since we don’t clip the text at all, it might in some cases be painted - * on the border pixels on the right side of a window. Therefore, we draw - * the right border again after rendering the text (and the unconnected - * lines in border color). */ - - /* Draw a 1px separator line before and after every tab, so that tabs can - * be easily distinguished. */ - if (parent->layout == L_TABBED) { - xcb_change_gc(conn, parent->pm_gc, XCB_GC_FOREGROUND, (uint32_t[]){p->color->border}); - } else { - xcb_change_gc(conn, parent->pm_gc, XCB_GC_FOREGROUND, (uint32_t[]){p->color->background}); - } - xcb_poly_line(conn, XCB_COORD_MODE_ORIGIN, parent->pixmap, parent->pm_gc, 6, - (xcb_point_t[]){ - {dr->x + dr->width, dr->y}, - {dr->x + dr->width, dr->y + dr->height}, - {dr->x + dr->width - 1, dr->y}, - {dr->x + dr->width - 1, dr->y + dr->height}, - {dr->x, dr->y + dr->height}, - {dr->x, dr->y}, - }); - - xcb_change_gc(conn, parent->pm_gc, XCB_GC_FOREGROUND, (uint32_t[]){p->color->border}); - xcb_poly_segment(conn, parent->pixmap, parent->pm_gc, 2, segments); - + x_draw_decoration_after_title(con, p); copy_pixmaps: - xcb_copy_area(conn, con->pixmap, con->frame, con->pm_gc, 0, 0, 0, 0, con->rect.width, con->rect.height); + draw_util_copy_surface(conn, &(con->frame_buffer), &(con->frame), 0, 0, 0, 0, con->rect.width, con->rect.height); } /* @@ -623,7 +630,7 @@ void x_deco_recurse(Con *con) { Con *current; bool leaf = TAILQ_EMPTY(&(con->nodes_head)) && TAILQ_EMPTY(&(con->floating_head)); - con_state *state = state_for_frame(con->frame); + con_state *state = state_for_frame(con->frame.id); if (!leaf) { TAILQ_FOREACH(current, &(con->nodes_head), nodes) @@ -632,8 +639,9 @@ void x_deco_recurse(Con *con) { TAILQ_FOREACH(current, &(con->floating_head), floating_windows) x_deco_recurse(current); - if (state->mapped) - xcb_copy_area(conn, con->pixmap, con->frame, con->pm_gc, 0, 0, 0, 0, con->rect.width, con->rect.height); + if (state->mapped) { + draw_util_copy_surface(conn, &(con->frame_buffer), &(con->frame), 0, 0, 0, 0, con->rect.width, con->rect.height); + } } if ((con->type != CT_ROOT && con->type != CT_OUTPUT) && @@ -650,7 +658,7 @@ static void set_hidden_state(Con *con) { return; } - con_state *state = state_for_frame(con->frame); + con_state *state = state_for_frame(con->frame.id); bool should_be_hidden = con_is_hidden(con); if (should_be_hidden == state->is_hidden) return; @@ -678,12 +686,12 @@ void x_push_node(Con *con) { Rect rect = con->rect; //DLOG("Pushing changes for node %p / %s\n", con, con->name); - state = state_for_frame(con->frame); + state = state_for_frame(con->frame.id); if (state->name != NULL) { DLOG("pushing name %s for con %p\n", state->name, con); - xcb_change_property(conn, XCB_PROP_MODE_REPLACE, con->frame, + xcb_change_property(conn, XCB_PROP_MODE_REPLACE, con->frame.id, XCB_ATOM_WM_NAME, XCB_ATOM_STRING, 8, strlen(state->name), state->name); FREE(state->name); } @@ -716,7 +724,7 @@ void x_push_node(Con *con) { xcb_change_window_attributes(conn, state->old_frame, XCB_CW_EVENT_MASK, values); xcb_change_window_attributes(conn, con->window->id, XCB_CW_EVENT_MASK, values); - xcb_reparent_window(conn, con->window->id, con->frame, 0, 0); + xcb_reparent_window(conn, con->window->id, con->frame.id, 0, 0); values[0] = FRAME_EVENT_MASK; xcb_change_window_attributes(conn, state->old_frame, XCB_CW_EVENT_MASK, values); @@ -741,8 +749,8 @@ void x_push_node(Con *con) { bool fake_notify = false; /* Set new position if rect changed (and if height > 0) or if the pixmap * needs to be recreated */ - if ((is_pixmap_needed && con->pixmap == XCB_NONE) || (memcmp(&(state->rect), &rect, sizeof(Rect)) != 0 && - rect.height > 0)) { + if ((is_pixmap_needed && con->frame_buffer.id == XCB_NONE) || (memcmp(&(state->rect), &rect, sizeof(Rect)) != 0 && + rect.height > 0)) { /* We first create the new pixmap, then render to it, set it as the * background and only afterwards change the window size. This reduces * flickering. */ @@ -755,38 +763,47 @@ void x_push_node(Con *con) { /* Check if the container has an unneeded pixmap left over from * previously having a border or titlebar. */ - if (!is_pixmap_needed && con->pixmap != XCB_NONE) { - xcb_free_pixmap(conn, con->pixmap); - con->pixmap = XCB_NONE; + if (!is_pixmap_needed && con->frame_buffer.id != XCB_NONE) { + draw_util_surface_free(conn, &(con->frame_buffer)); + xcb_free_pixmap(conn, con->frame_buffer.id); + con->frame_buffer.id = XCB_NONE; } - if (is_pixmap_needed && (has_rect_changed || con->pixmap == XCB_NONE)) { - if (con->pixmap == 0) { - con->pixmap = xcb_generate_id(conn); - con->pm_gc = xcb_generate_id(conn); + if (is_pixmap_needed && (has_rect_changed || con->frame_buffer.id == XCB_NONE)) { + if (con->frame_buffer.id == XCB_NONE) { + con->frame_buffer.id = xcb_generate_id(conn); } else { - xcb_free_pixmap(conn, con->pixmap); - xcb_free_gc(conn, con->pm_gc); + draw_util_surface_free(conn, &(con->frame_buffer)); + xcb_free_pixmap(conn, con->frame_buffer.id); } uint16_t win_depth = root_depth; if (con->window) win_depth = con->window->depth; - xcb_create_pixmap(conn, win_depth, con->pixmap, con->frame, rect.width, rect.height); + /* Ensure we have valid dimensions for our surface. */ + // TODO This is probably a bug in the condition above as we should never enter this path + // for height == 0. Also, we should probably handle width == 0 the same way. + int width = MAX(rect.width, 1); + int height = MAX(rect.height, 1); + + xcb_create_pixmap_checked(conn, win_depth, con->frame_buffer.id, con->frame.id, width, height); + draw_util_surface_init(conn, &(con->frame_buffer), con->frame_buffer.id, + get_visualtype_by_id(get_visualid_by_depth(win_depth)), width, height); /* For the graphics context, we disable GraphicsExposure events. * Those will be sent when a CopyArea request cannot be fulfilled * properly due to parts of the source being unmapped or otherwise * unavailable. Since we always copy from pixmaps to windows, this * is not a concern for us. */ - uint32_t values[] = {0}; - xcb_create_gc(conn, con->pm_gc, con->pixmap, XCB_GC_GRAPHICS_EXPOSURES, values); + xcb_change_gc(conn, con->frame_buffer.gc, XCB_GC_GRAPHICS_EXPOSURES, (uint32_t[]){0}); + draw_util_surface_set_size(&(con->frame), width, height); con->pixmap_recreated = true; /* Don’t render the decoration for windows inside a stack which are * not visible right now */ + // TODO Should this work the same way for L_TABBED? if (!con->parent || con->parent->layout != L_STACKED || TAILQ_FIRST(&(con->parent->focus_head)) == con) @@ -802,9 +819,10 @@ void x_push_node(Con *con) { * window get lost when resizing it, therefore we want to provide it as * fast as possible) */ xcb_flush(conn); - xcb_set_window_rect(conn, con->frame, rect); - if (con->pixmap != XCB_NONE) - xcb_copy_area(conn, con->pixmap, con->frame, con->pm_gc, 0, 0, 0, 0, con->rect.width, con->rect.height); + xcb_set_window_rect(conn, con->frame.id, rect); + if (con->frame_buffer.id != XCB_NONE) { + draw_util_copy_surface(conn, &(con->frame_buffer), &(con->frame), 0, 0, 0, 0, con->rect.width, con->rect.height); + } xcb_flush(conn); memcpy(&(state->rect), &rect, sizeof(Rect)); @@ -848,17 +866,18 @@ void x_push_node(Con *con) { state->child_mapped = true; } - cookie = xcb_map_window(conn, con->frame); + cookie = xcb_map_window(conn, con->frame.id); values[0] = FRAME_EVENT_MASK; - xcb_change_window_attributes(conn, con->frame, XCB_CW_EVENT_MASK, values); + xcb_change_window_attributes(conn, con->frame.id, XCB_CW_EVENT_MASK, values); /* copy the pixmap contents to the frame window immediately after mapping */ - if (con->pixmap != XCB_NONE) - xcb_copy_area(conn, con->pixmap, con->frame, con->pm_gc, 0, 0, 0, 0, con->rect.width, con->rect.height); + if (con->frame_buffer.id != XCB_NONE) { + draw_util_copy_surface(conn, &(con->frame_buffer), &(con->frame), 0, 0, 0, 0, con->rect.width, con->rect.height); + } xcb_flush(conn); - DLOG("mapping container %08x (serial %d)\n", con->frame, cookie.sequence); + DLOG("mapping container %08x (serial %d)\n", con->frame.id, cookie.sequence); state->mapped = con->mapped; } @@ -892,7 +911,7 @@ static void x_push_node_unmaps(Con *con) { con_state *state; //DLOG("Pushing changes (with unmaps) for node %p / %s\n", con, con->name); - state = state_for_frame(con->frame); + state = state_for_frame(con->frame.id); /* map/unmap if map state changed, also ensure that the child window * is changed if we are mapped *and* in initial state (meaning the @@ -906,14 +925,14 @@ static void x_push_node_unmaps(Con *con) { A_WM_STATE, A_WM_STATE, 32, 2, data); } - cookie = xcb_unmap_window(conn, con->frame); + cookie = xcb_unmap_window(conn, con->frame.id); DLOG("unmapping container %p / %s (serial %d)\n", con, con->name, cookie.sequence); /* we need to increase ignore_unmap for this container (if it * contains a window) and for every window "under" this one which * contains a window */ if (con->window != NULL) { con->ignore_unmap++; - DLOG("ignore_unmap for con %p (frame 0x%08x) now %d\n", con, con->frame, con->ignore_unmap); + DLOG("ignore_unmap for con %p (frame 0x%08x) now %d\n", con, con->frame.id, con->ignore_unmap); } state->mapped = con->mapped; } @@ -1067,7 +1086,7 @@ void x_push_changes(Con *con) { x_deco_recurse(con); - xcb_window_t to_focus = focused->frame; + xcb_window_t to_focus = focused->frame.id; if (focused->window != NULL) to_focus = focused->window->id; @@ -1161,7 +1180,7 @@ void x_push_changes(Con *con) { */ void x_raise_con(Con *con) { con_state *state; - state = state_for_frame(con->frame); + state = state_for_frame(con->frame.id); //DLOG("raising in new stack: %p / %s / %s / xid %08x\n", con, con->name, con->window ? con->window->name_json : "", state->id); CIRCLEQ_REMOVE(&state_head, state, state); @@ -1177,7 +1196,7 @@ void x_raise_con(Con *con) { void x_set_name(Con *con, const char *name) { struct con_state *state; - if ((state = state_for_frame(con->frame)) == NULL) { + if ((state = state_for_frame(con->frame.id)) == NULL) { ELOG("window state not found\n"); return; } diff --git a/src/xcb.c b/src/xcb.c index 90d591c7..60fd4212 100644 --- a/src/xcb.c +++ b/src/xcb.c @@ -252,6 +252,22 @@ uint16_t get_visual_depth(xcb_visualid_t visual_id) { } return 0; } +xcb_visualtype_t *get_visualtype_by_id(xcb_visualid_t visual_id) { + xcb_depth_iterator_t depth_iter; + + depth_iter = xcb_screen_allowed_depths_iterator(root_screen); + for (; depth_iter.rem; xcb_depth_next(&depth_iter)) { + xcb_visualtype_iterator_t visual_iter; + + visual_iter = xcb_depth_visuals_iterator(depth_iter.data); + for (; visual_iter.rem; xcb_visualtype_next(&visual_iter)) { + if (visual_id == visual_iter.data->visual_id) { + return visual_iter.data; + } + } + } + return 0; +} /* * Get visualid with specified depth From fdeb4e0c368c8354fd539c9fae98a2ddfc077e72 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ingo=20B=C3=BCrk?= Date: Mon, 16 Nov 2015 21:28:33 +0100 Subject: [PATCH 079/187] Skip drawing for uninitialized surfaces. We return early from drawing functions if the surface to draw to is not initialized properly. There is no immediate need to do so, at least no crashes have been observed, but it mirrors the previous behavior a bit more closely. Furthermore, i3 should not crash due to not being able to make some rendering call, so this provides some stability. relates to #1278 --- libi3/draw_util.c | 19 +++++++++++++++++++ src/x.c | 2 +- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/libi3/draw_util.c b/libi3/draw_util.c index e7e7c09c..816a81df 100644 --- a/libi3/draw_util.c +++ b/libi3/draw_util.c @@ -23,6 +23,14 @@ xcb_visualtype_t *visual_type; /* Forward declarations */ static void draw_util_set_source_color(xcb_connection_t *conn, surface_t *surface, color_t color); +#define RETURN_UNLESS_SURFACE_INITIALIZED(surface) \ + do { \ + if ((surface)->id == XCB_NONE) { \ + ELOG("Surface %p is not initialized, skipping drawing.\n", surface); \ + return; \ + } \ + } while (0) + /* * Initialize the surface to represent the given drawable. * @@ -104,6 +112,8 @@ color_t draw_util_colorpixel_to_color(uint32_t colorpixel) { * */ static void draw_util_set_source_color(xcb_connection_t *conn, surface_t *surface, color_t color) { + RETURN_UNLESS_SURFACE_INITIALIZED(surface); + #ifdef CAIRO_SUPPORT cairo_set_source_rgb(surface->cr, color.red, color.green, color.blue); #else @@ -120,6 +130,8 @@ static void draw_util_set_source_color(xcb_connection_t *conn, surface_t *surfac * */ void draw_util_text(i3String *text, surface_t *surface, color_t fg_color, color_t bg_color, int x, int y, int max_width) { + RETURN_UNLESS_SURFACE_INITIALIZED(surface); + #ifdef CAIRO_SUPPORT /* Flush any changes before we draw the text as this might use XCB directly. */ CAIRO_SURFACE_FLUSH(surface->surface); @@ -141,6 +153,8 @@ void draw_util_text(i3String *text, surface_t *surface, color_t fg_color, color_ * */ void draw_util_rectangle(xcb_connection_t *conn, surface_t *surface, color_t color, double x, double y, double w, double h) { + RETURN_UNLESS_SURFACE_INITIALIZED(surface); + #ifdef CAIRO_SUPPORT cairo_save(surface->cr); @@ -171,6 +185,8 @@ void draw_util_rectangle(xcb_connection_t *conn, surface_t *surface, color_t col * */ void draw_util_clear_surface(xcb_connection_t *conn, surface_t *surface, color_t color) { + RETURN_UNLESS_SURFACE_INITIALIZED(surface); + #ifdef CAIRO_SUPPORT cairo_save(surface->cr); @@ -201,6 +217,9 @@ void draw_util_clear_surface(xcb_connection_t *conn, surface_t *surface, color_t */ void draw_util_copy_surface(xcb_connection_t *conn, surface_t *src, surface_t *dest, double src_x, double src_y, double dest_x, double dest_y, double width, double height) { + RETURN_UNLESS_SURFACE_INITIALIZED(src); + RETURN_UNLESS_SURFACE_INITIALIZED(dest); + #ifdef CAIRO_SUPPORT cairo_save(dest->cr); diff --git a/src/x.c b/src/x.c index 86a0c797..49325af4 100644 --- a/src/x.c +++ b/src/x.c @@ -518,7 +518,7 @@ void x_draw_decoration(Con *con) { if (p->border_style != BS_NORMAL) goto copy_pixmaps; - /* If the parent hasn't been set up yet, skip the decoratin rendering + /* If the parent hasn't been set up yet, skip the decoration rendering * for now. */ if (parent->frame_buffer.id == XCB_NONE) goto copy_pixmaps; From a4afd1b642af8d0b6382024e823ddc305a26883d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ingo=20B=C3=BCrk?= Date: Mon, 16 Nov 2015 23:03:39 +0100 Subject: [PATCH 080/187] Parse colors as color_t instead of colorpixel. With this patch we remove the temporary draw_util_colorpixel_to_color function we introduced previously by parsing the colors as color_t to begin with. relates to #1278 --- include/config.h | 10 +++++----- include/data.h | 2 +- include/libi3.h | 2 -- libi3/draw_util.c | 8 -------- src/config.c | 10 +++++----- src/config_directives.c | 22 +++++++++++----------- src/resize.c | 2 +- src/restore_layout.c | 6 +++--- src/x.c | 38 +++++++++++++++++++------------------- 9 files changed, 45 insertions(+), 55 deletions(-) diff --git a/include/config.h b/include/config.h index 5b98ce6e..f9badf10 100644 --- a/include/config.h +++ b/include/config.h @@ -50,10 +50,10 @@ struct context { * */ struct Colortriple { - uint32_t border; - uint32_t background; - uint32_t text; - uint32_t indicator; + color_t border; + color_t background; + color_t text; + color_t indicator; }; /** @@ -202,7 +202,7 @@ struct Config { /* Color codes are stored here */ struct config_client { - uint32_t background; + color_t background; struct Colortriple focused; struct Colortriple focused_inactive; struct Colortriple unfocused; diff --git a/include/data.h b/include/data.h index 78a42ff4..636092d2 100644 --- a/include/data.h +++ b/include/data.h @@ -179,7 +179,7 @@ struct deco_render_params { struct width_height con_rect; struct width_height con_window_rect; Rect con_deco_rect; - uint32_t background; + color_t background; layout_t parent_layout; bool con_is_leaf; }; diff --git a/include/libi3.h b/include/libi3.h index 0cb2532f..02988837 100644 --- a/include/libi3.h +++ b/include/libi3.h @@ -554,8 +554,6 @@ void draw_util_surface_free(xcb_connection_t *conn, surface_t *surface); */ color_t draw_util_hex_to_color(const char *color); -color_t draw_util_colorpixel_to_color(uint32_t colorpixel); - /** * Draw the given text using libi3. * This function also marks the surface dirty which is needed if other means of diff --git a/libi3/draw_util.c b/libi3/draw_util.c index 816a81df..4015ba92 100644 --- a/libi3/draw_util.c +++ b/libi3/draw_util.c @@ -99,14 +99,6 @@ color_t draw_util_hex_to_color(const char *color) { .colorpixel = get_colorpixel(color)}; } -color_t draw_util_colorpixel_to_color(uint32_t colorpixel) { - return (color_t){ - .red = ((colorpixel >> 16) & 0xFF) / 255.0, - .green = ((colorpixel >> 8) & 0xFF) / 255.0, - .blue = (colorpixel & 0xFF) / 255.0, - .colorpixel = colorpixel}; -} - /* * Set the given color as the source color on the surface. * diff --git a/src/config.c b/src/config.c index fac4e265..833ea6b6 100644 --- a/src/config.c +++ b/src/config.c @@ -191,13 +191,13 @@ void load_configuration(xcb_connection_t *conn, const char *override_configpath, /* Initialize default colors */ #define INIT_COLOR(x, cborder, cbackground, ctext, cindicator) \ do { \ - x.border = get_colorpixel(cborder); \ - x.background = get_colorpixel(cbackground); \ - x.text = get_colorpixel(ctext); \ - x.indicator = get_colorpixel(cindicator); \ + x.border = draw_util_hex_to_color(cborder); \ + x.background = draw_util_hex_to_color(cbackground); \ + x.text = draw_util_hex_to_color(ctext); \ + x.indicator = draw_util_hex_to_color(cindicator); \ } while (0) - config.client.background = get_colorpixel("#000000"); + config.client.background = draw_util_hex_to_color("#000000"); INIT_COLOR(config.client.focused, "#4c7899", "#285577", "#ffffff", "#2e9ef4"); INIT_COLOR(config.client.focused_inactive, "#333333", "#5f676a", "#ffffff", "#484e50"); INIT_COLOR(config.client.unfocused, "#333333", "#222222", "#888888", "#292d2e"); diff --git a/src/config_directives.c b/src/config_directives.c index ba267427..0d32f008 100644 --- a/src/config_directives.c +++ b/src/config_directives.c @@ -332,20 +332,20 @@ CFGFUN(popup_during_fullscreen, const char *value) { CFGFUN(color_single, const char *colorclass, const char *color) { /* used for client.background only currently */ - config.client.background = get_colorpixel(color); + config.client.background = draw_util_hex_to_color(color); } CFGFUN(color, const char *colorclass, const char *border, const char *background, const char *text, const char *indicator) { -#define APPLY_COLORS(classname) \ - do { \ - if (strcmp(colorclass, "client." #classname) == 0) { \ - config.client.classname.border = get_colorpixel(border); \ - config.client.classname.background = get_colorpixel(background); \ - config.client.classname.text = get_colorpixel(text); \ - if (indicator != NULL) { \ - config.client.classname.indicator = get_colorpixel(indicator); \ - } \ - } \ +#define APPLY_COLORS(classname) \ + do { \ + if (strcmp(colorclass, "client." #classname) == 0) { \ + config.client.classname.border = draw_util_hex_to_color(border); \ + config.client.classname.background = draw_util_hex_to_color(background); \ + config.client.classname.text = draw_util_hex_to_color(text); \ + if (indicator != NULL) { \ + config.client.classname.indicator = draw_util_hex_to_color(indicator); \ + } \ + } \ } while (0) APPLY_COLORS(focused_inactive); diff --git a/src/resize.c b/src/resize.c index 05fe5055..31a78354 100644 --- a/src/resize.c +++ b/src/resize.c @@ -146,7 +146,7 @@ int resize_graphical_handler(Con *first, Con *second, orientation_t orientation, } mask = XCB_CW_BACK_PIXEL; - values[0] = config.client.focused.border; + values[0] = config.client.focused.border.colorpixel; mask |= XCB_CW_OVERRIDE_REDIRECT; values[1] = 1; diff --git a/src/restore_layout.c b/src/restore_layout.c index 70eed523..5ca4cff5 100644 --- a/src/restore_layout.c +++ b/src/restore_layout.c @@ -125,7 +125,7 @@ void restore_connect(void) { static void update_placeholder_contents(placeholder_state *state) { xcb_change_gc(restore_conn, state->gc, XCB_GC_FOREGROUND, - (uint32_t[]){config.client.placeholder.background}); + (uint32_t[]){config.client.placeholder.background.colorpixel}); xcb_poly_fill_rectangle(restore_conn, state->pixmap, state->gc, 1, (xcb_rectangle_t[]){{0, 0, state->rect.width, state->rect.height}}); @@ -133,7 +133,7 @@ static void update_placeholder_contents(placeholder_state *state) { xcb_flush(restore_conn); xcb_aux_sync(restore_conn); - set_font_colors(state->gc, config.client.placeholder.text, config.client.placeholder.background); + set_font_colors(state->gc, config.client.placeholder.text.colorpixel, config.client.placeholder.background.colorpixel); Match *swallows; int n = 0; @@ -193,7 +193,7 @@ static void open_placeholder_window(Con *con) { true, XCB_CW_BACK_PIXEL | XCB_CW_EVENT_MASK, (uint32_t[]){ - config.client.placeholder.background, + config.client.placeholder.background.colorpixel, XCB_EVENT_MASK_EXPOSURE | XCB_EVENT_MASK_STRUCTURE_NOTIFY, }); /* Make i3 not focus this window. */ diff --git a/src/x.c b/src/x.c index 49325af4..63cc4b48 100644 --- a/src/x.c +++ b/src/x.c @@ -327,10 +327,10 @@ static void x_draw_decoration_border(Con *con, struct deco_render_params *p) { deco_diff_r = 0; } - draw_util_rectangle(conn, &(con->parent->frame_buffer), draw_util_colorpixel_to_color(p->color->border), + draw_util_rectangle(conn, &(con->parent->frame_buffer), p->color->border, dr->x, dr->y, dr->width, 1); - draw_util_rectangle(conn, &(con->parent->frame_buffer), draw_util_colorpixel_to_color(p->color->border), + draw_util_rectangle(conn, &(con->parent->frame_buffer), p->color->border, dr->x + deco_diff_l, dr->y + dr->height - 1, dr->width - (deco_diff_l + deco_diff_r), 1); } @@ -343,18 +343,18 @@ static void x_draw_decoration_after_title(Con *con, struct deco_render_params *p /* Redraw the right border to cut off any text that went past it. * This is necessary when the text was drawn using XCB since cutting text off * automatically does not work there. For pango rendering, this isn't necessary. */ - draw_util_rectangle(conn, &(con->parent->frame_buffer), draw_util_colorpixel_to_color(p->color->background), + draw_util_rectangle(conn, &(con->parent->frame_buffer), p->color->background, dr->x + dr->width + br.width, dr->y, -br.width, dr->height); /* Draw a 1px separator line before and after every tab, so that tabs can * be easily distinguished. */ if (con->parent->layout == L_TABBED) { /* Left side */ - draw_util_rectangle(conn, &(con->parent->frame_buffer), draw_util_colorpixel_to_color(p->color->border), + draw_util_rectangle(conn, &(con->parent->frame_buffer), p->color->border, dr->x, dr->y, 1, dr->height); /* Right side */ - draw_util_rectangle(conn, &(con->parent->frame_buffer), draw_util_colorpixel_to_color(p->color->border), + draw_util_rectangle(conn, &(con->parent->frame_buffer), p->color->border, dr->x + dr->width - 1, dr->y, 1, dr->height); } @@ -448,16 +448,16 @@ void x_draw_decoration(Con *con) { /* 2: draw the client.background, but only for the parts around the window_rect */ if (con->window != NULL) { /* top area */ - draw_util_rectangle(conn, &(con->frame_buffer), draw_util_colorpixel_to_color(config.client.background), + draw_util_rectangle(conn, &(con->frame_buffer), config.client.background, 0, 0, r->width, w->y); /* bottom area */ - draw_util_rectangle(conn, &(con->frame_buffer), draw_util_colorpixel_to_color(config.client.background), + draw_util_rectangle(conn, &(con->frame_buffer), config.client.background, 0, w->y + w->height, r->width, r->height - (w->y + w->height)); /* left area */ - draw_util_rectangle(conn, &(con->frame_buffer), draw_util_colorpixel_to_color(config.client.background), + draw_util_rectangle(conn, &(con->frame_buffer), config.client.background, 0, 0, w->x, r->height); /* right area */ - draw_util_rectangle(conn, &(con->frame_buffer), draw_util_colorpixel_to_color(config.client.background), + draw_util_rectangle(conn, &(con->frame_buffer), config.client.background, w->x + w->width, 0, r->width - (w->x + w->width), r->height); } @@ -479,20 +479,20 @@ void x_draw_decoration(Con *con) { * rectangle because some childs are not freely resizable and we want * their background color to "shine through". */ if (!(borders_to_hide & ADJ_LEFT_SCREEN_EDGE)) { - draw_util_rectangle(conn, &(con->frame_buffer), draw_util_colorpixel_to_color(p->color->background), + draw_util_rectangle(conn, &(con->frame_buffer), p->color->background, 0, 0, br.x, r->height); } if (!(borders_to_hide & ADJ_RIGHT_SCREEN_EDGE)) { - draw_util_rectangle(conn, &(con->frame_buffer), draw_util_colorpixel_to_color(p->color->background), + draw_util_rectangle(conn, &(con->frame_buffer), p->color->background, r->width + (br.width + br.x), 0, -(br.width + br.x), r->height); } if (!(borders_to_hide & ADJ_LOWER_SCREEN_EDGE)) { - draw_util_rectangle(conn, &(con->frame_buffer), draw_util_colorpixel_to_color(p->color->background), + draw_util_rectangle(conn, &(con->frame_buffer), p->color->background, br.x, r->height + (br.height + br.y), r->width + br.width, -(br.height + br.y)); } /* pixel border needs an additional line at the top */ if (p->border_style == BS_PIXEL && !(borders_to_hide & ADJ_UPPER_SCREEN_EDGE)) { - draw_util_rectangle(conn, &(con->frame_buffer), draw_util_colorpixel_to_color(p->color->background), + draw_util_rectangle(conn, &(con->frame_buffer), p->color->background, br.x, 0, r->width + br.width, br.y); } @@ -504,10 +504,10 @@ void x_draw_decoration(Con *con) { TAILQ_PREV(con, nodes_head, nodes) == NULL && con->parent->type != CT_FLOATING_CON) { if (p->parent_layout == L_SPLITH) { - draw_util_rectangle(conn, &(con->frame_buffer), draw_util_colorpixel_to_color(p->color->indicator), + draw_util_rectangle(conn, &(con->frame_buffer), p->color->indicator, r->width + (br.width + br.x), br.y, -(br.width + br.x), r->height + br.height); } else if (p->parent_layout == L_SPLITV) { - draw_util_rectangle(conn, &(con->frame_buffer), draw_util_colorpixel_to_color(p->color->indicator), + draw_util_rectangle(conn, &(con->frame_buffer), p->color->indicator, br.x, r->height + (br.height + br.y), r->width + br.width, -(br.height + br.y)); } } @@ -524,7 +524,7 @@ void x_draw_decoration(Con *con) { goto copy_pixmaps; /* 4: paint the bar */ - draw_util_rectangle(conn, &(parent->frame_buffer), draw_util_colorpixel_to_color(p->color->background), + draw_util_rectangle(conn, &(parent->frame_buffer), p->color->background, con->deco_rect.x, con->deco_rect.y, con->deco_rect.width, con->deco_rect.height); /* 5: draw two unconnected horizontal lines in border color */ @@ -545,7 +545,7 @@ void x_draw_decoration(Con *con) { i3String *title = i3string_from_utf8(_title); draw_util_text(title, &(parent->frame_buffer), - draw_util_colorpixel_to_color(p->color->text), draw_util_colorpixel_to_color(p->color->background), + p->color->text, p->color->background, con->deco_rect.x + 2, con->deco_rect.y + text_offset_y, con->deco_rect.width - 2); FREE(_title); @@ -596,7 +596,7 @@ void x_draw_decoration(Con *con) { mark_width = predict_text_width(mark); draw_util_text(mark, &(parent->frame_buffer), - draw_util_colorpixel_to_color(p->color->text), draw_util_colorpixel_to_color(p->color->background), + p->color->text, p->color->background, con->deco_rect.x + con->deco_rect.width - mark_width - logical_px(2), con->deco_rect.y + text_offset_y, mark_width); @@ -608,7 +608,7 @@ void x_draw_decoration(Con *con) { i3String *title = win->title_format == NULL ? win->name : window_parse_title_format(win); draw_util_text(title, &(parent->frame_buffer), - draw_util_colorpixel_to_color(p->color->text), draw_util_colorpixel_to_color(p->color->background), + p->color->text, p->color->background, con->deco_rect.x + logical_px(2) + indent_px, con->deco_rect.y + text_offset_y, con->deco_rect.width - logical_px(2) - indent_px - mark_width - logical_px(2)); if (win->title_format != NULL) From d2126027cef86a3a6cf3e22ec551156636e4d9b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ingo=20B=C3=BCrk?= Date: Mon, 16 Nov 2015 23:40:41 +0100 Subject: [PATCH 081/187] Don't create a pixmap for CT_ROOT and CT_OUTPUT containers. Such containers never require a pixmap anyway. In particular for the __i3 output (used for the scratchpad workspace), this would cause an allocation error anyway because it can be very big -- so big, in fact, that X cannot allocate the pixmap for it. Until now, this error was silently ignored due to the fact that we did not create the pixmap checked (and asserted its success), but with cairo this would cause a crash because we'd try to create a surface for a pixmap which doesn't exist. relates to #1278 --- src/x.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/x.c b/src/x.c index 63cc4b48..17970fb8 100644 --- a/src/x.c +++ b/src/x.c @@ -746,6 +746,12 @@ void x_push_node(Con *con) { con->parent->layout == L_STACKED || con->parent->layout == L_TABBED); + /* The root con and output cons will never require a pixmap. In particular for the + * __i3 output, this will likely not work anyway because it might be ridiculously + * large, causing an XCB_ALLOC error. */ + if (con->type == CT_ROOT || con->type == CT_OUTPUT) + is_pixmap_needed = false; + bool fake_notify = false; /* Set new position if rect changed (and if height > 0) or if the pixmap * needs to be recreated */ From 780cb8d15d07febbb9be47cb1bc1201072b31838 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ingo=20B=C3=BCrk?= Date: Tue, 17 Nov 2015 12:50:06 +0100 Subject: [PATCH 082/187] Use 32-bit visual by default if available. With this patch, we use 32-bit visuals per default whenever it is available. Otherwise, we fall back to the actual root window's depth, which will typically be 24-bit. Before this patch, we already used 32-bit depth for containers with a window that uses 32-bit. However, this means that we didn't use 32-bit for split parent containers on which decoration is drawn. For 32-bit windows using transparency, this caused a graphical glitch because the decoration pixmap behind it would show through. This behavior is fixed with this change. relates to #1278 --- include/libi3.h | 3 +++ include/xcb.h | 5 ++++ libi3/draw_util.c | 18 ++++++++++--- src/con.c | 2 +- src/main.c | 28 ++++++++++++++------ src/x.c | 65 +++++++++++++++++++++-------------------------- src/xcb.c | 5 ++++ 7 files changed, 77 insertions(+), 49 deletions(-) diff --git a/include/libi3.h b/include/libi3.h index 02988837..08b01402 100644 --- a/include/libi3.h +++ b/include/libi3.h @@ -499,11 +499,14 @@ typedef struct color_t { double red; double green; double blue; + double alpha; /* The colorpixel we use for direct XCB calls. */ uint32_t colorpixel; } color_t; +#define COLOR_TRANSPARENT ((color_t){.red = 0.0, .green = 0.0, .blue = 0.0, .colorpixel = 0}) + /* A wrapper grouping an XCB drawable and both a graphics context * and the corresponding cairo objects representing it. */ typedef struct surface_t { diff --git a/include/xcb.h b/include/xcb.h index 7fae41f5..86019c5d 100644 --- a/include/xcb.h +++ b/include/xcb.h @@ -139,6 +139,11 @@ void xcb_set_root_cursor(int cursor); * */ uint16_t get_visual_depth(xcb_visualid_t visual_id); + +/** + * Get visual type specified by visualid + * + */ xcb_visualtype_t *get_visualtype_by_id(xcb_visualid_t visual_id); /** diff --git a/libi3/draw_util.c b/libi3/draw_util.c index 4015ba92..f6c53865 100644 --- a/libi3/draw_util.c +++ b/libi3/draw_util.c @@ -38,7 +38,7 @@ static void draw_util_set_source_color(xcb_connection_t *conn, surface_t *surfac void draw_util_surface_init(xcb_connection_t *conn, surface_t *surface, xcb_drawable_t drawable, xcb_visualtype_t *visual, int width, int height) { surface->id = drawable; - surface->visual_type = (visual == NULL) ? visual_type : visual; + surface->visual_type = ((visual == NULL) ? visual_type : visual); surface->width = width; surface->height = height; @@ -87,15 +87,25 @@ void draw_util_surface_set_size(surface_t *surface, int width, int height) { * */ color_t draw_util_hex_to_color(const char *color) { - char groups[3][3] = { + char alpha[2]; + if (strlen(color) == strlen("#rrggbbaa")) { + alpha[0] = color[7]; + alpha[1] = color[8]; + } else { + alpha[0] = alpha[1] = 'F'; + } + + char groups[4][3] = { {color[1], color[2], '\0'}, {color[3], color[4], '\0'}, - {color[5], color[6], '\0'}}; + {color[5], color[6], '\0'}, + {alpha[0], alpha[1], '\0'}}; return (color_t){ .red = strtol(groups[0], NULL, 16) / 255.0, .green = strtol(groups[1], NULL, 16) / 255.0, .blue = strtol(groups[2], NULL, 16) / 255.0, + .alpha = strtol(groups[3], NULL, 16) / 255.0, .colorpixel = get_colorpixel(color)}; } @@ -107,7 +117,7 @@ static void draw_util_set_source_color(xcb_connection_t *conn, surface_t *surfac RETURN_UNLESS_SURFACE_INITIALIZED(surface); #ifdef CAIRO_SUPPORT - cairo_set_source_rgb(surface->cr, color.red, color.green, color.blue); + cairo_set_source_rgba(surface->cr, color.red, color.green, color.blue, color.alpha); #else uint32_t colorpixel = color.colorpixel; xcb_change_gc(conn, surface->gc, XCB_GC_FOREGROUND | XCB_GC_BACKGROUND, diff --git a/src/con.c b/src/con.c index 03fd0ee6..64dbec69 100644 --- a/src/con.c +++ b/src/con.c @@ -47,7 +47,7 @@ Con *con_new_skeleton(Con *parent, i3Window *window) { new->depth = window->depth; new->window->aspect_ratio = 0.0; } else { - new->depth = XCB_COPY_FROM_PARENT; + new->depth = root_depth; } DLOG("opening window\n"); diff --git a/src/main.c b/src/main.c index 1e0ec4a5..9c67b42a 100644 --- a/src/main.c +++ b/src/main.c @@ -59,7 +59,6 @@ xcb_window_t root; * pixmaps. Will use 32 bit depth and an appropriate visual, if available, * otherwise the root window’s default (usually 24 bit TrueColor). */ uint8_t root_depth; -xcb_visualid_t visual_id; xcb_visualtype_t *visual_type; xcb_colormap_t colormap; @@ -482,16 +481,29 @@ int main(int argc, char *argv[]) { #include "atoms.xmacro" #undef xmacro - /* By default, we use the same depth and visual as the root window, which - * usually is TrueColor (24 bit depth) and the corresponding visual. - * However, we also check if a 32 bit depth and visual are available (for - * transparency) and use it if so. */ root_depth = root_screen->root_depth; - visual_id = root_screen->root_visual; - visual_type = get_visualtype(root_screen); colormap = root_screen->default_colormap; + visual_type = xcb_aux_find_visual_by_attrs(root_screen, -1, 32); + if (visual_type != NULL) { + root_depth = xcb_aux_get_depth_of_visual(root_screen, visual_type->visual_id); + colormap = xcb_generate_id(conn); - DLOG("root_depth = %d, visual_id = 0x%08x.\n", root_depth, visual_id); + xcb_void_cookie_t cm_cookie = xcb_create_colormap_checked(conn, + XCB_COLORMAP_ALLOC_NONE, + colormap, + root, + visual_type->visual_id); + + xcb_generic_error_t *error = xcb_request_check(conn, cm_cookie); + if (error != NULL) { + ELOG("Could not create colormap. Error code: %d\n", error->error_code); + exit(EXIT_FAILURE); + } + } else { + visual_type = get_visualtype(root_screen); + } + + DLOG("root_depth = %d, visual_id = 0x%08x.\n", root_depth, visual_type->visual_id); DLOG("root_screen->height_in_pixels = %d, root_screen->height_in_millimeters = %d, dpi = %d\n", root_screen->height_in_pixels, root_screen->height_in_millimeters, (int)((double)root_screen->height_in_pixels * 25.4 / (double)root_screen->height_in_millimeters)); diff --git a/src/x.c b/src/x.c index 17970fb8..f1f971e9 100644 --- a/src/x.c +++ b/src/x.c @@ -101,47 +101,32 @@ void x_con_init(Con *con, uint16_t depth) { uint32_t mask = 0; uint32_t values[5]; - xcb_visualid_t visual = XCB_COPY_FROM_PARENT; - xcb_colormap_t win_colormap = XCB_NONE; - if (depth != root_depth && depth != XCB_COPY_FROM_PARENT) { - /* For custom visuals, we need to create a colormap before creating - * this window. It will be freed directly after creating the window. */ - visual = get_visualid_by_depth(depth); - win_colormap = xcb_generate_id(conn); - xcb_create_colormap_checked(conn, XCB_COLORMAP_ALLOC_NONE, win_colormap, root, visual); + /* For custom visuals, we need to create a colormap before creating + * this window. It will be freed directly after creating the window. */ + xcb_visualid_t visual = get_visualid_by_depth(depth); + xcb_colormap_t win_colormap = xcb_generate_id(conn); + xcb_create_colormap_checked(conn, XCB_COLORMAP_ALLOC_NONE, win_colormap, root, visual); - /* We explicitly set a background color and border color (even though we - * don’t even have a border) because the X11 server requires us to when - * using 32 bit color depths, see - * http://stackoverflow.com/questions/3645632 */ - mask |= XCB_CW_BACK_PIXEL; - values[0] = root_screen->black_pixel; + /* We explicitly set a background color and border color (even though we + * don’t even have a border) because the X11 server requires us to when + * using 32 bit color depths, see + * http://stackoverflow.com/questions/3645632 */ + mask |= XCB_CW_BACK_PIXEL; + values[0] = root_screen->black_pixel; - mask |= XCB_CW_BORDER_PIXEL; - values[1] = root_screen->black_pixel; + mask |= XCB_CW_BORDER_PIXEL; + values[1] = root_screen->black_pixel; - /* our own frames should not be managed */ - mask |= XCB_CW_OVERRIDE_REDIRECT; - values[2] = 1; + /* our own frames should not be managed */ + mask |= XCB_CW_OVERRIDE_REDIRECT; + values[2] = 1; - /* see include/xcb.h for the FRAME_EVENT_MASK */ - mask |= XCB_CW_EVENT_MASK; - values[3] = FRAME_EVENT_MASK & ~XCB_EVENT_MASK_ENTER_WINDOW; + /* see include/xcb.h for the FRAME_EVENT_MASK */ + mask |= XCB_CW_EVENT_MASK; + values[3] = FRAME_EVENT_MASK & ~XCB_EVENT_MASK_ENTER_WINDOW; - mask |= XCB_CW_COLORMAP; - values[4] = win_colormap; - } else { - /* our own frames should not be managed */ - mask = XCB_CW_OVERRIDE_REDIRECT; - values[0] = 1; - - /* see include/xcb.h for the FRAME_EVENT_MASK */ - mask |= XCB_CW_EVENT_MASK; - values[1] = FRAME_EVENT_MASK & ~XCB_EVENT_MASK_ENTER_WINDOW; - - mask |= XCB_CW_COLORMAP; - values[2] = colormap; - } + mask |= XCB_CW_COLORMAP; + values[4] = win_colormap; Rect dims = {-15, -15, 10, 10}; xcb_window_t frame_id = create_window(conn, dims, depth, visual, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCURSOR_CURSOR_POINTER, false, mask, values); @@ -523,6 +508,14 @@ void x_draw_decoration(Con *con) { if (parent->frame_buffer.id == XCB_NONE) goto copy_pixmaps; + /* For the first child, we clear the parent pixmap to ensure there's no + * garbage left on there. This is important to avoid tearing when using + * transparency. */ + if (con == TAILQ_FIRST(&(con->parent->nodes_head))) { + draw_util_clear_surface(conn, &(con->parent->frame_buffer), COLOR_TRANSPARENT); + FREE(con->parent->deco_render_params); + } + /* 4: paint the bar */ draw_util_rectangle(conn, &(parent->frame_buffer), p->color->background, con->deco_rect.x, con->deco_rect.y, con->deco_rect.width, con->deco_rect.height); diff --git a/src/xcb.c b/src/xcb.c index 60fd4212..07f9281a 100644 --- a/src/xcb.c +++ b/src/xcb.c @@ -252,6 +252,11 @@ uint16_t get_visual_depth(xcb_visualid_t visual_id) { } return 0; } + +/* + * Get visual type specified by visualid + * + */ xcb_visualtype_t *get_visualtype_by_id(xcb_visualid_t visual_id) { xcb_depth_iterator_t depth_iter; From bf442ff5de277537dca40ee194bec984eeaa33c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ingo=20B=C3=BCrk?= Date: Wed, 18 Nov 2015 15:32:45 +0100 Subject: [PATCH 083/187] Make freeing surfaces idempotent If a window with border is set to "border none" and then closed, we would call cairo_destroy / cairo_surface_destroy twice, causing an assertion failure in cairo as the objects already had zero references the second time. We fix this by explicitly setting these objects to NULL. relates to #1278 --- libi3/draw_util.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/libi3/draw_util.c b/libi3/draw_util.c index f6c53865..e33a9ecb 100644 --- a/libi3/draw_util.c +++ b/libi3/draw_util.c @@ -66,6 +66,12 @@ void draw_util_surface_free(xcb_connection_t *conn, surface_t *surface) { #ifdef CAIRO_SUPPORT cairo_surface_destroy(surface->surface); cairo_destroy(surface->cr); + + /* We need to explicitly set these to NULL to avoid assertion errors in + * cairo when calling this multiple times. This can happen, for example, + * when setting the border of a window to none and then closing it. */ + surface->surface = NULL; + surface->cr = NULL; #endif } From a2b20c8d9a57c636e4bffcfc206402c678abf4ce Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 30 Nov 2015 21:08:00 +0100 Subject: [PATCH 084/187] travis: call git fetch --unshallow, so that git describe works --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index c4d221ae..6d788926 100644 --- a/.travis.yml +++ b/.travis.yml @@ -48,6 +48,7 @@ install: - sudo /bin/sh -c 'cpanm -n -v X11::XCB || true' - sudo /bin/sh -c 'cpanm -n -v AnyEvent::I3 || true' script: + - if [ -a .git/shallow ]; then git fetch --unshallow; fi - CFLAGS="-Wformat -Wformat-security -Wextra -Wno-unused-parameter -Werror" make -j - (cd testcases && xvfb-run ./complete-run.pl --parallel=1 || (cat latest/complete-run.log; false)) - clang-format-3.5 -i $(find . -name "*.[ch]" | tr '\n' ' ') && git diff --exit-code || (echo 'Code was not formatted using clang-format!'; false) From a5b2c91c93c0f8a1b51b09563efc554a7cce87f0 Mon Sep 17 00:00:00 2001 From: Tobias Bucher Date: Tue, 24 Nov 2015 13:15:31 +0000 Subject: [PATCH 085/187] Quote the variables in i3-sensible-* correctly Previously, the variables $EDITOR, $PAGER, $TERMINAL and $VISUAL got shell-expanded twice before executing them. --- i3-sensible-editor | 6 +++--- i3-sensible-pager | 6 +++--- i3-sensible-terminal | 6 +++--- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/i3-sensible-editor b/i3-sensible-editor index 4e7456b7..861615f3 100755 --- a/i3-sensible-editor +++ b/i3-sensible-editor @@ -9,8 +9,8 @@ # mechanism to find the preferred editor # Hopefully one of these is installed (no flamewars about preference please!): -for editor in $VISUAL $EDITOR nano vim vi emacs pico qe mg jed gedit mc-edit; do - if command -v $editor > /dev/null 2>&1; then - exec $editor "$@" +for editor in "$VISUAL" "$EDITOR" nano vim vi emacs pico qe mg jed gedit mc-edit; do + if command -v "$editor" > /dev/null 2>&1; then + exec "$editor" "$@" fi done diff --git a/i3-sensible-pager b/i3-sensible-pager index ce71686b..386e2988 100755 --- a/i3-sensible-pager +++ b/i3-sensible-pager @@ -11,8 +11,8 @@ # Hopefully one of these is installed (no flamewars about preference please!): # We don't use 'more' because it will exit if the file is too short. # Worst case scenario we'll open the file in your editor. -for pager in $PAGER less most w3m pg i3-sensible-editor; do - if command -v $pager > /dev/null 2>&1; then - exec $pager "$@" +for pager in "$PAGER" less most w3m pg i3-sensible-editor; do + if command -v "$pager" > /dev/null 2>&1; then + exec "$pager" "$@" fi done diff --git a/i3-sensible-terminal b/i3-sensible-terminal index c80e5ee2..bb66f02b 100755 --- a/i3-sensible-terminal +++ b/i3-sensible-terminal @@ -8,9 +8,9 @@ # We welcome patches that add distribution-specific mechanisms to find the # preferred terminal emulator. On Debian, there is the x-terminal-emulator # symlink for example. -for terminal in $TERMINAL x-terminal-emulator urxvt rxvt terminator Eterm aterm xterm gnome-terminal roxterm xfce4-terminal termite lxterminal mate-terminal terminology; do - if command -v $terminal > /dev/null 2>&1; then - exec $terminal "$@" +for terminal in "$TERMINAL" x-terminal-emulator urxvt rxvt terminator Eterm aterm xterm gnome-terminal roxterm xfce4-terminal termite lxterminal mate-terminal terminology; do + if command -v "$terminal" > /dev/null 2>&1; then + exec "$terminal" "$@" fi done From caeb193a6c54de2a464029e8186ea48874f56bcf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ingo=20B=C3=BCrk?= Date: Thu, 3 Dec 2015 13:24:39 +0100 Subject: [PATCH 086/187] Allow "move position center" to operate on matched windows Moving windows to the center previously did not consider command criteria. We now operate on matched windows as for other commands. fixes #2090 --- src/commands.c | 46 ++++++++++++++++++++++++++++++---------------- 1 file changed, 30 insertions(+), 16 deletions(-) diff --git a/src/commands.c b/src/commands.c index 2b6e7dfe..5b28d03e 100644 --- a/src/commands.c +++ b/src/commands.c @@ -1760,29 +1760,43 @@ void cmd_move_window_to_position(I3_CMD, const char *method, long x, long y) { * */ void cmd_move_window_to_center(I3_CMD, const char *method) { - if (!con_is_floating(focused)) { - ELOG("Cannot change position. The window/container is not floating\n"); - yerror("Cannot change position. The window/container is not floating."); - return; - } + bool has_error = false; + HANDLE_EMPTY_MATCH; - if (strcmp(method, "absolute") == 0) { - DLOG("moving to absolute center\n"); - floating_center(focused->parent, croot->rect); + owindow *current; + TAILQ_FOREACH(current, &owindows, owindows) { + Con *floating_con = con_inside_floating(current->con); + if (floating_con == NULL) { + ELOG("con %p / %s is not floating, cannot move it to the center.\n", + current->con, current->con->name); - floating_maybe_reassign_ws(focused->parent); - cmd_output->needs_tree_render = true; - } + if (!has_error) { + yerror("Cannot change position of a window/container because it is not floating."); + has_error = true; + } - if (strcmp(method, "position") == 0) { - DLOG("moving to center\n"); - floating_center(focused->parent, con_get_workspace(focused)->rect); + continue; + } - cmd_output->needs_tree_render = true; + if (strcmp(method, "absolute") == 0) { + DLOG("moving to absolute center\n"); + floating_center(floating_con, croot->rect); + + floating_maybe_reassign_ws(floating_con); + cmd_output->needs_tree_render = true; + } + + if (strcmp(method, "position") == 0) { + DLOG("moving to center\n"); + floating_center(floating_con, con_get_workspace(floating_con)->rect); + + cmd_output->needs_tree_render = true; + } } // XXX: default reply for now, make this a better reply - ysuccess(true); + if (!has_error) + ysuccess(true); } /* From 256007442f41cd9c461e62337d8d37bdf3407903 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ingo=20B=C3=BCrk?= Date: Thu, 3 Dec 2015 18:57:02 +0100 Subject: [PATCH 087/187] Cast unsigned to signed before comparison The values of a Rect are unsigned, but can contain signed values. Using MAX when the value is negative causes incorrect behavior and makes the result stay negative, which is what we wanted to avoid here in the first place. Fix by properly casting the value for the comparison. fixes #2094 --- src/x.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/x.c b/src/x.c index f1f971e9..faa892aa 100644 --- a/src/x.c +++ b/src/x.c @@ -783,8 +783,8 @@ void x_push_node(Con *con) { /* Ensure we have valid dimensions for our surface. */ // TODO This is probably a bug in the condition above as we should never enter this path // for height == 0. Also, we should probably handle width == 0 the same way. - int width = MAX(rect.width, 1); - int height = MAX(rect.height, 1); + int width = MAX((int32_t)rect.width, 1); + int height = MAX((int32_t)rect.height, 1); xcb_create_pixmap_checked(conn, win_depth, con->frame_buffer.id, con->frame.id, width, height); draw_util_surface_init(conn, &(con->frame_buffer), con->frame_buffer.id, From 35a4e22f4a94d06d54a2cfca0932bb3906d897f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ingo=20B=C3=BCrk?= Date: Thu, 3 Dec 2015 18:59:35 +0100 Subject: [PATCH 088/187] Fail gracefully when the gc cannot be created We now only log an error but do not exit when creating the graphics context fails. While, if this happens, rendering will likely be wrong, this is still better than terminating the user's session entirely due to a rendering problem, potentially causing data loss. relates to #2094 --- libi3/draw_util.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/libi3/draw_util.c b/libi3/draw_util.c index e33a9ecb..fa538d1a 100644 --- a/libi3/draw_util.c +++ b/libi3/draw_util.c @@ -47,8 +47,7 @@ void draw_util_surface_init(xcb_connection_t *conn, surface_t *surface, xcb_draw xcb_generic_error_t *error = xcb_request_check(conn, gc_cookie); if (error != NULL) { - ELOG("Could not create graphical context. Error code: %d\n", error->error_code); - exit(EXIT_FAILURE); + ELOG("Could not create graphical context. Error code: %d. Please report this bug.\n", error->error_code); } #ifdef CAIRO_SUPPORT From 8d36f78b8e85f2a292c24411a481dc0a02c7498d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ingo=20B=C3=BCrk?= Date: Wed, 9 Dec 2015 13:39:08 +0100 Subject: [PATCH 089/187] Reject invalid match criteria with an error. Previously, using a command like [con_id=foo] kill would kill the currently focused window because while an error for not being able to parse the con_id was logged, no further action was taken, which caused the criterion to be ignored. In this case, the fallback behavior of using the focused window took over. For con_id, id and window_type we now reject incorrect values with an error and abort the command. fixes #2091 --- include/data.h | 3 +++ src/commands.c | 14 ++++++++++++++ src/match.c | 25 +++++++++++++++---------- testcases/t/260-invalid-criteria.t | 27 +++++++++++++++++++++++++++ 4 files changed, 59 insertions(+), 10 deletions(-) create mode 100644 testcases/t/260-invalid-criteria.t diff --git a/include/data.h b/include/data.h index 636092d2..b122dbfd 100644 --- a/include/data.h +++ b/include/data.h @@ -436,6 +436,9 @@ struct Window { * */ struct Match { + /* Set if a criterion was specified incorrectly. */ + char *error; + struct regex *title; struct regex *application; struct regex *class; diff --git a/src/commands.c b/src/commands.c index 5b28d03e..78b1e993 100644 --- a/src/commands.c +++ b/src/commands.c @@ -42,6 +42,16 @@ } \ } while (0) +/** If an error occured during parsing of the criteria, we want to exit instead + * of relying on fallback behavior. See #2091. */ +#define HANDLE_INVALID_MATCH \ + do { \ + if (current_match->error != NULL) { \ + yerror("Invalid match: %s", current_match->error); \ + return; \ + } \ + } while (0) + /** When the command did not include match criteria (!), we use the currently * focused container. Do not confuse this case with a command which included * criteria but which did not match any windows. This macro has to be called in @@ -49,6 +59,8 @@ */ #define HANDLE_EMPTY_MATCH \ do { \ + HANDLE_INVALID_MATCH; \ + \ if (match_is_empty(current_match)) { \ owindow *ow = smalloc(sizeof(owindow)); \ ow->con = focused; \ @@ -1233,6 +1245,8 @@ void cmd_kill(I3_CMD, const char *kill_mode_str) { return; } + HANDLE_INVALID_MATCH; + /* check if the match is empty, not if the result is empty */ if (match_is_empty(current_match)) tree_close_con(kill_mode); diff --git a/src/match.c b/src/match.c index 950e0fe3..caeb909f 100644 --- a/src/match.c +++ b/src/match.c @@ -238,6 +238,7 @@ bool match_matches_window(Match *match, i3Window *window) { * */ void match_free(Match *match) { + FREE(match->error); regex_free(match->title); regex_free(match->application); regex_free(match->class); @@ -286,6 +287,7 @@ void match_parse_property(Match *match, const char *ctype, const char *cvalue) { parsed < 0 || (end && *end != '\0')) { ELOG("Could not parse con id \"%s\"\n", cvalue); + match->error = sstrdup("invalid con_id"); } else { match->con_id = (Con *)parsed; DLOG("id as int = %p\n", match->con_id); @@ -301,6 +303,7 @@ void match_parse_property(Match *match, const char *ctype, const char *cvalue) { parsed < 0 || (end && *end != '\0')) { ELOG("Could not parse window id \"%s\"\n", cvalue); + match->error = sstrdup("invalid id"); } else { match->id = parsed; DLOG("window id as int = %d\n", match->id); @@ -309,26 +312,28 @@ void match_parse_property(Match *match, const char *ctype, const char *cvalue) { } if (strcmp(ctype, "window_type") == 0) { - if (strcasecmp(cvalue, "normal") == 0) + if (strcasecmp(cvalue, "normal") == 0) { match->window_type = A__NET_WM_WINDOW_TYPE_NORMAL; - else if (strcasecmp(cvalue, "dialog") == 0) + } else if (strcasecmp(cvalue, "dialog") == 0) { match->window_type = A__NET_WM_WINDOW_TYPE_DIALOG; - else if (strcasecmp(cvalue, "utility") == 0) + } else if (strcasecmp(cvalue, "utility") == 0) { match->window_type = A__NET_WM_WINDOW_TYPE_UTILITY; - else if (strcasecmp(cvalue, "toolbar") == 0) + } else if (strcasecmp(cvalue, "toolbar") == 0) { match->window_type = A__NET_WM_WINDOW_TYPE_TOOLBAR; - else if (strcasecmp(cvalue, "splash") == 0) + } else if (strcasecmp(cvalue, "splash") == 0) { match->window_type = A__NET_WM_WINDOW_TYPE_SPLASH; - else if (strcasecmp(cvalue, "menu") == 0) + } else if (strcasecmp(cvalue, "menu") == 0) { match->window_type = A__NET_WM_WINDOW_TYPE_MENU; - else if (strcasecmp(cvalue, "dropdown_menu") == 0) + } else if (strcasecmp(cvalue, "dropdown_menu") == 0) { match->window_type = A__NET_WM_WINDOW_TYPE_DROPDOWN_MENU; - else if (strcasecmp(cvalue, "popup_menu") == 0) + } else if (strcasecmp(cvalue, "popup_menu") == 0) { match->window_type = A__NET_WM_WINDOW_TYPE_POPUP_MENU; - else if (strcasecmp(cvalue, "tooltip") == 0) + } else if (strcasecmp(cvalue, "tooltip") == 0) { match->window_type = A__NET_WM_WINDOW_TYPE_TOOLTIP; - else + } else { ELOG("unknown window_type value \"%s\"\n", cvalue); + match->error = sstrdup("unknown window_type value"); + } return; } diff --git a/testcases/t/260-invalid-criteria.t b/testcases/t/260-invalid-criteria.t new file mode 100644 index 00000000..4c1830f9 --- /dev/null +++ b/testcases/t/260-invalid-criteria.t @@ -0,0 +1,27 @@ +#!perl +# vim:ts=4:sw=4:expandtab +# +# Please read the following documents before working on tests: +# • http://build.i3wm.org/docs/testsuite.html +# (or docs/testsuite) +# +# • http://build.i3wm.org/docs/lib-i3test.html +# (alternatively: perldoc ./testcases/lib/i3test.pm) +# +# • http://build.i3wm.org/docs/ipc.html +# (or docs/ipc) +# +# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# (unless you are already familiar with Perl) +# +# Ticket: #2091 +use i3test; + +my $ws = fresh_workspace; +open_window; + +my $result = cmd '[con_id=foobar] kill'; +is($result->[0]->{success}, 0, 'command was unsuccessful'); +is($result->[0]->{error}, 'Invalid match: invalid con_id', 'correct error is returned'); + +done_testing; From 624b33fd79043bfa9804bac63c22f46d1e27c181 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Wed, 9 Dec 2015 23:07:18 +0100 Subject: [PATCH 090/187] travis: install git 1.9.1 from trusty for git fetch --unshallow --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 6d788926..d1d1932b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -39,7 +39,7 @@ before_install: - sudo cp /tmp/pin /etc/apt/preferences.d/trustypin - sudo apt-get update - - sudo apt-get install -t trusty libc6 libc6-dev + - sudo apt-get install -t trusty libc6 libc6-dev git - sudo apt-get install --no-install-recommends devscripts equivs xdotool install: - sudo mk-build-deps --install --remove --tool 'apt-get --no-install-recommends' debian/control From e3a5c004d36d2773678610a8610f65045fba2664 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ingo=20B=C3=BCrk?= Date: Fri, 11 Dec 2015 09:00:20 +0100 Subject: [PATCH 091/187] Reject empty swallow definitions Empty swallow definitions don't make sense and can lead to crashes, for that reason we reject them. fixes #2099 --- src/load_layout.c | 20 ++++++++++++++++++- .../t/216-layout-restore-split-swallows.t | 6 ------ testcases/t/234-layout-restore-output.t | 8 ++++---- 3 files changed, 23 insertions(+), 11 deletions(-) diff --git a/src/load_layout.c b/src/load_layout.c index dc84c607..d9acd1ba 100644 --- a/src/load_layout.c +++ b/src/load_layout.c @@ -30,6 +30,7 @@ static bool parsing_geometry; static bool parsing_focus; static bool parsing_marks; struct Match *current_swallow; +static bool swallow_is_empty; /* This list is used for reordering the focus stack after parsing the 'focus' * array. */ @@ -48,6 +49,7 @@ static int json_start_map(void *ctx) { current_swallow = smalloc(sizeof(Match)); match_init(current_swallow); TAILQ_INSERT_TAIL(&(json_node->swallow_head), current_swallow, matches); + swallow_is_empty = true; } else { if (!parsing_rect && !parsing_deco_rect && !parsing_window_rect && !parsing_geometry) { if (last_key && strcasecmp(last_key, "floating_nodes") == 0) { @@ -151,6 +153,13 @@ static int json_end_map(void *ctx) { json_node = json_node->parent; } + if (parsing_swallows && swallow_is_empty) { + /* We parsed an empty swallow definition. This is an invalid layout + * definition, hence we reject it. */ + ELOG("Layout file is invalid: found an empty swallow definition.\n"); + return 0; + } + parsing_rect = false; parsing_deco_rect = false; parsing_window_rect = false; @@ -232,12 +241,16 @@ static int json_string(void *ctx, const unsigned char *val, size_t len) { sasprintf(&sval, "%.*s", len, val); if (strcasecmp(last_key, "class") == 0) { current_swallow->class = regex_new(sval); + swallow_is_empty = false; } else if (strcasecmp(last_key, "instance") == 0) { current_swallow->instance = regex_new(sval); + swallow_is_empty = false; } else if (strcasecmp(last_key, "window_role") == 0) { current_swallow->window_role = regex_new(sval); + swallow_is_empty = false; } else if (strcasecmp(last_key, "title") == 0) { current_swallow->title = regex_new(sval); + swallow_is_empty = false; } else { ELOG("swallow key %s unknown\n", last_key); } @@ -433,12 +446,15 @@ static int json_int(void *ctx, long long val) { if (parsing_swallows) { if (strcasecmp(last_key, "id") == 0) { current_swallow->id = val; + swallow_is_empty = false; } if (strcasecmp(last_key, "dock") == 0) { current_swallow->dock = val; + swallow_is_empty = false; } if (strcasecmp(last_key, "insert_where") == 0) { current_swallow->insert_where = val; + swallow_is_empty = false; } } @@ -455,8 +471,10 @@ static int json_bool(void *ctx, int val) { json_node->sticky = val; if (parsing_swallows) { - if (strcasecmp(last_key, "restart_mode") == 0) + if (strcasecmp(last_key, "restart_mode") == 0) { current_swallow->restart_mode = val; + swallow_is_empty = false; + } } return 1; diff --git a/testcases/t/216-layout-restore-split-swallows.t b/testcases/t/216-layout-restore-split-swallows.t index 2e2028a2..c064b5d1 100644 --- a/testcases/t/216-layout-restore-split-swallows.t +++ b/testcases/t/216-layout-restore-split-swallows.t @@ -54,9 +54,6 @@ print $fh <<'EOT'; "floating": "auto_off", "layout": "splitv", "percent": 0.883854166666667, - "swallows": [ - {} - ], "type": "con", "nodes": [ { @@ -65,9 +62,6 @@ print $fh <<'EOT'; "floating": "auto_off", "layout": "splitv", "percent": 1, - "swallows": [ - {} - ], "type": "con", "nodes": [ { diff --git a/testcases/t/234-layout-restore-output.t b/testcases/t/234-layout-restore-output.t index bc90131d..5a1f3763 100644 --- a/testcases/t/234-layout-restore-output.t +++ b/testcases/t/234-layout-restore-output.t @@ -54,7 +54,7 @@ print $fh <<'EOT'; "percent": 1, "swallows": [ { - // "class": "^URxvt$", + "class": "^URxvt$" // "instance": "^urxvt$", // "title": "^vals\\@w00t\\:\\ \\~$" } @@ -119,7 +119,7 @@ print $fh <<'EOT'; "percent": 1, "swallows": [ { - // "class": "^URxvt$", + "class": "^URxvt$" // "instance": "^urxvt$", // "title": "^vals\\@w00t\\:\\ \\~$" } @@ -165,7 +165,7 @@ print $fh <<'EOT'; "percent": 1, "swallows": [ { - // "class": "^URxvt$", + "class": "^URxvt$" // "instance": "^urxvt$", // "title": "^vals\\@w00t\\:\\ \\~$" } @@ -213,7 +213,7 @@ print $fh <<'EOT'; "percent": 1, "swallows": [ { - // "class": "^URxvt$", + "class": "^URxvt$" // "instance": "^urxvt$", // "title": "^vals\\@w00t\\:\\ \\~$" } From 6f12f029f4fcfac384bb58d4f426f8dab3840f72 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ingo=20B=C3=BCrk?= Date: Sat, 12 Dec 2015 15:45:59 -0500 Subject: [PATCH 092/187] Allow mouse bindings to run on the root window. Previously, mouse bindings could only be run when a window was present, by using --whole-window. Such bindings would not work on empty workspaces. However, this is a valid usecase for bindings like bindsym $mod+button4 workspace prev bindsym $mod+button5 workspace next Hence, we need to grab the root window as well and run bindings on it. fixes #2097 --- src/bindings.c | 4 ++++ src/click.c | 15 +++++++++++++++ 2 files changed, 19 insertions(+) diff --git a/src/bindings.c b/src/bindings.c index b9d994ea..16235a1e 100644 --- a/src/bindings.c +++ b/src/bindings.c @@ -164,6 +164,10 @@ void regrab_all_buttons(xcb_connection_t *conn) { xcb_grab_buttons(conn, con->window->id, grab_scrollwheel); } + /* Also grab the root window to allow bindings to work on there as well. */ + xcb_ungrab_button(conn, XCB_BUTTON_INDEX_ANY, root, XCB_BUTTON_MASK_ANY); + xcb_grab_buttons(conn, root, grab_scrollwheel); + xcb_ungrab_server(conn); } diff --git a/src/click.c b/src/click.c index c895666d..66a271c2 100644 --- a/src/click.c +++ b/src/click.c @@ -363,6 +363,21 @@ int handle_button_press(xcb_button_press_event_t *event) { return route_click(con, event, mod_pressed, CLICK_INSIDE); if (!(con = con_by_frame_id(event->event))) { + /* Run bindings on the root window as well, see #2097. We only run it + * if --whole-window was set as that's the equivalent for a normal + * window. */ + if (event->event == root) { + Binding *bind = get_binding_from_xcb_event((xcb_generic_event_t *)event); + if (bind != NULL && bind->whole_window) { + CommandResult *result = run_binding(bind, NULL); + if (result->needs_tree_render) { + tree_render(); + } + + command_result_free(result); + } + } + /* If the root window is clicked, find the relevant output from the * click coordinates and focus the output's active workspace. */ if (event->event == root && event->response_type == XCB_BUTTON_PRESS) { From 905bca3531c8774d2e34e35a285979bc33e46166 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ingo=20B=C3=BCrk?= Date: Tue, 15 Dec 2015 07:21:11 -0500 Subject: [PATCH 093/187] Avoid rendering the tree twice in a row The callee already renders the tree if necessary, so despite the documentation of the function, doing it again on the caller side is unnecessary. --- src/bindings.c | 4 ++-- src/click.c | 8 -------- src/key_press.c | 4 ---- 3 files changed, 2 insertions(+), 14 deletions(-) diff --git a/src/bindings.c b/src/bindings.c index 16235a1e..3463f831 100644 --- a/src/bindings.c +++ b/src/bindings.c @@ -643,8 +643,8 @@ void binding_free(Binding *bind) { /* * Runs the given binding and handles parse errors. If con is passed, it will * execute the command binding with that container selected by criteria. - * Returns a CommandResult for running the binding's command. Caller should - * render tree if needs_tree_render is true. Free with command_result_free(). + * Returns a CommandResult for running the binding's command. Free with + * command_result_free(). * */ CommandResult *run_binding(Binding *bind, Con *con) { diff --git a/src/click.c b/src/click.c index 66a271c2..a670120f 100644 --- a/src/click.c +++ b/src/click.c @@ -198,11 +198,7 @@ static int route_click(Con *con, xcb_button_press_event_t *event, const bool mod xcb_allow_events(conn, XCB_ALLOW_ASYNC_POINTER, event->time); xcb_flush(conn); - if (result->needs_tree_render) - tree_render(); - command_result_free(result); - return 0; } } @@ -370,10 +366,6 @@ int handle_button_press(xcb_button_press_event_t *event) { Binding *bind = get_binding_from_xcb_event((xcb_generic_event_t *)event); if (bind != NULL && bind->whole_window) { CommandResult *result = run_binding(bind, NULL); - if (result->needs_tree_render) { - tree_render(); - } - command_result_free(result); } } diff --git a/src/key_press.c b/src/key_press.c index aa6d8150..6760e35b 100644 --- a/src/key_press.c +++ b/src/key_press.c @@ -31,9 +31,5 @@ void handle_key_press(xcb_key_press_event_t *event) { return; CommandResult *result = run_binding(bind, NULL); - - if (result->needs_tree_render) - tree_render(); - command_result_free(result); } From 33f017daa9f6c48e0d9c5bcb6135b47a0b32bf76 Mon Sep 17 00:00:00 2001 From: Florian Merkel Date: Mon, 7 Dec 2015 12:34:24 +0100 Subject: [PATCH 094/187] Support matching _NET_WM_WINDOW_TYPE_NOTIFICATION This commit fixes #1969 by adding support for matching a window's type against _NET_WM_WINDOW_TYPE_NOTIFICATION. The userguide and tests were updated to reflect this change. --- docs/userguide | 2 +- include/atoms.xmacro | 1 + src/match.c | 2 ++ src/xcb.c | 3 ++- testcases/t/165-for_window.t | 3 ++- testcases/t/232-cmd-move-criteria.t | 3 ++- 6 files changed, 10 insertions(+), 4 deletions(-) diff --git a/docs/userguide b/docs/userguide index e596aeae..69f7da92 100644 --- a/docs/userguide +++ b/docs/userguide @@ -1657,7 +1657,7 @@ window_role:: window_type:: Compare the window type (_NET_WM_WINDOW_TYPE). Possible values are +normal+, +dialog+, +utility+, +toolbar+, +splash+, +menu+, +dropdown_menu+, - +popup_menu+ and +tooltip+. + +popup_menu+, +tooltip+ and +notification+. id:: Compares the X11 window ID, which you can get via +xwininfo+ for example. title:: diff --git a/include/atoms.xmacro b/include/atoms.xmacro index 8798528a..139b6efb 100644 --- a/include/atoms.xmacro +++ b/include/atoms.xmacro @@ -20,6 +20,7 @@ xmacro(_NET_WM_WINDOW_TYPE_MENU) xmacro(_NET_WM_WINDOW_TYPE_DROPDOWN_MENU) xmacro(_NET_WM_WINDOW_TYPE_POPUP_MENU) xmacro(_NET_WM_WINDOW_TYPE_TOOLTIP) +xmacro(_NET_WM_WINDOW_TYPE_NOTIFICATION) xmacro(_NET_WM_DESKTOP) xmacro(_NET_WM_STRUT_PARTIAL) xmacro(_NET_CLIENT_LIST) diff --git a/src/match.c b/src/match.c index caeb909f..bbb239ac 100644 --- a/src/match.c +++ b/src/match.c @@ -330,6 +330,8 @@ void match_parse_property(Match *match, const char *ctype, const char *cvalue) { match->window_type = A__NET_WM_WINDOW_TYPE_POPUP_MENU; } else if (strcasecmp(cvalue, "tooltip") == 0) { match->window_type = A__NET_WM_WINDOW_TYPE_TOOLTIP; + } else if (strcasecmp(cvalue, "notification") == 0) { + match->window_type = A__NET_WM_WINDOW_TYPE_NOTIFICATION; } else { ELOG("unknown window_type value \"%s\"\n", cvalue); match->error = sstrdup("unknown window_type value"); diff --git a/src/xcb.c b/src/xcb.c index 07f9281a..9d181cfa 100644 --- a/src/xcb.c +++ b/src/xcb.c @@ -175,7 +175,8 @@ xcb_atom_t xcb_get_preferred_window_type(xcb_get_property_reply_t *reply) { atoms[i] == A__NET_WM_WINDOW_TYPE_MENU || atoms[i] == A__NET_WM_WINDOW_TYPE_DROPDOWN_MENU || atoms[i] == A__NET_WM_WINDOW_TYPE_POPUP_MENU || - atoms[i] == A__NET_WM_WINDOW_TYPE_TOOLTIP) { + atoms[i] == A__NET_WM_WINDOW_TYPE_TOOLTIP || + atoms[i] == A__NET_WM_WINDOW_TYPE_NOTIFICATION) { return atoms[i]; } } diff --git a/testcases/t/165-for_window.t b/testcases/t/165-for_window.t index 476bcc9f..025fe21c 100644 --- a/testcases/t/165-for_window.t +++ b/testcases/t/165-for_window.t @@ -380,7 +380,8 @@ my %window_types = ( 'menu' => '_NET_WM_WINDOW_TYPE_MENU', 'dropdown_menu' => '_NET_WM_WINDOW_TYPE_DROPDOWN_MENU', 'popup_menu' => '_NET_WM_WINDOW_TYPE_POPUP_MENU', - 'tooltip' => '_NET_WM_WINDOW_TYPE_TOOLTIP' + 'tooltip' => '_NET_WM_WINDOW_TYPE_TOOLTIP', + 'notification' => '_NET_WM_WINDOW_TYPE_NOTIFICATION' ); while (my ($window_type, $atom) = each %window_types) { diff --git a/testcases/t/232-cmd-move-criteria.t b/testcases/t/232-cmd-move-criteria.t index c023bca4..787f72b3 100644 --- a/testcases/t/232-cmd-move-criteria.t +++ b/testcases/t/232-cmd-move-criteria.t @@ -54,7 +54,8 @@ my %window_types = ( 'menu' => '_NET_WM_WINDOW_TYPE_MENU', 'dropdown_menu' => '_NET_WM_WINDOW_TYPE_DROPDOWN_MENU', 'popup_menu' => '_NET_WM_WINDOW_TYPE_POPUP_MENU', - 'tooltip' => '_NET_WM_WINDOW_TYPE_TOOLTIP' + 'tooltip' => '_NET_WM_WINDOW_TYPE_TOOLTIP', + 'notification' => '_NET_WM_WINDOW_TYPE_NOTIFICATION' ); while (my ($window_type, $atom) = each %window_types) { From bba18453af27ea00d0fedcbbd05f77d327d8bd89 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ingo=20B=C3=BCrk?= Date: Tue, 15 Dec 2015 07:38:56 -0500 Subject: [PATCH 095/187] Match on all criteria even if con_id or con_mark are given. Previously, if a match specification contained the con_id or con_mark criterion, all other criteria were ignored. However, a user may want to specify one of those two unique identifiers and still specify others as well, for example to match the currently focused window, but only if it has a certain WM_CLASS: [con_id=__focused__ class=special] kill We now check all specified criteria. fixes #2111 --- src/commands.c | 48 +++++++++++++---- src/match.c | 22 ++++++-- .../261-match-con_id-con_mark-combinations.t | 51 +++++++++++++++++++ 3 files changed, 107 insertions(+), 14 deletions(-) create mode 100644 testcases/t/261-match-con_id-con_mark-combinations.t diff --git a/src/commands.c b/src/commands.c index 78b1e993..d4b2d51c 100644 --- a/src/commands.c +++ b/src/commands.c @@ -294,33 +294,61 @@ void cmd_criteria_match_windows(I3_CMD) { next = TAILQ_NEXT(next, owindows); DLOG("checking if con %p / %s matches\n", current->con, current->con->name); + + /* We use this flag to prevent matching on window-less containers if + * only window-specific criteria were specified. */ + bool accept_match = false; + if (current_match->con_id != NULL) { + accept_match = true; + if (current_match->con_id == current->con) { - DLOG("matches container!\n"); - TAILQ_INSERT_TAIL(&owindows, current, owindows); + DLOG("con_id matched.\n"); } else { - DLOG("doesnt match\n"); - free(current); + DLOG("con_id does not match.\n"); + FREE(current); + continue; } - } else if (current_match->mark != NULL && !TAILQ_EMPTY(&(current->con->marks_head))) { + } + + if (current_match->mark != NULL && !TAILQ_EMPTY(&(current->con->marks_head))) { + accept_match = true; + bool matched_by_mark = false; + mark_t *mark; TAILQ_FOREACH(mark, &(current->con->marks_head), marks) { if (!regex_matches(current_match->mark, mark->name)) continue; DLOG("match by mark\n"); - TAILQ_INSERT_TAIL(&owindows, current, owindows); + matched_by_mark = true; break; } - } else { - if (current->con->window && match_matches_window(current_match, current->con->window)) { + + if (!matched_by_mark) { + DLOG("mark does not match.\n"); + FREE(current); + continue; + } + } + + if (current->con->window != NULL) { + if (match_matches_window(current_match, current->con->window)) { DLOG("matches window!\n"); - TAILQ_INSERT_TAIL(&owindows, current, owindows); + accept_match = true; } else { DLOG("doesnt match\n"); - free(current); + FREE(current); + continue; } } + + if (accept_match) { + TAILQ_INSERT_TAIL(&owindows, current, owindows); + } else { + FREE(current); + continue; + } } TAILQ_FOREACH(current, &owindows, owindows) { diff --git a/src/match.c b/src/match.c index caeb909f..a680ae70 100644 --- a/src/match.c +++ b/src/match.c @@ -223,11 +223,25 @@ bool match_matches_window(Match *match, i3Window *window) { } } - /* We don’t check the mark because this function is not even called when - * the mark would have matched - it is checked in cmdparse.y itself */ if (match->mark != NULL) { - LOG("mark does not match\n"); - return false; + if ((con = con_by_window_id(window->id)) == NULL) + return false; + + bool matched = false; + mark_t *mark; + TAILQ_FOREACH(mark, &(con->marks_head), marks) { + if (regex_matches(match->mark, mark->name)) { + matched = true; + break; + } + } + + if (matched) { + LOG("mark matches\n"); + } else { + LOG("mark does not match\n"); + return false; + } } return true; diff --git a/testcases/t/261-match-con_id-con_mark-combinations.t b/testcases/t/261-match-con_id-con_mark-combinations.t new file mode 100644 index 00000000..b255558e --- /dev/null +++ b/testcases/t/261-match-con_id-con_mark-combinations.t @@ -0,0 +1,51 @@ +#!perl +# vim:ts=4:sw=4:expandtab +# +# Please read the following documents before working on tests: +# • http://build.i3wm.org/docs/testsuite.html +# (or docs/testsuite) +# +# • http://build.i3wm.org/docs/lib-i3test.html +# (alternatively: perldoc ./testcases/lib/i3test.pm) +# +# • http://build.i3wm.org/docs/ipc.html +# (or docs/ipc) +# +# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# (unless you are already familiar with Perl) +# +# Ticket: #2111 +use i3test; + +my ($ws); + +############################################################################### +# Verify that con_id can be combined with other criteria +############################################################################### + +$ws = fresh_workspace; +open_window(wm_class => 'matchme'); + +cmd '[con_id=__focused__ class=doesnotmatch] kill'; +is(@{get_ws($ws)->{nodes}}, 1, 'window was not killed'); + +cmd '[con_id=__focused__ class=matchme] kill'; +is(@{get_ws($ws)->{nodes}}, 0, 'window was killed'); + +############################################################################### +# Verify that con_mark can be combined with other criteria +############################################################################### + +$ws = fresh_workspace; +open_window(wm_class => 'matchme'); +cmd 'mark marked'; + +cmd '[con_mark=marked class=doesnotmatch] kill'; +is(@{get_ws($ws)->{nodes}}, 1, 'window was not killed'); + +cmd '[con_mark=marked class=matchme] kill'; +is(@{get_ws($ws)->{nodes}}, 0, 'window was killed'); + +############################################################################### + +done_testing; From 68e3cb8ab79267e8ee0faf0a4e2d60e9dde0e1b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ingo=20B=C3=BCrk?= Date: Thu, 17 Dec 2015 09:43:34 -0500 Subject: [PATCH 096/187] Don't force base 10 for parsing id. --- src/match.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/match.c b/src/match.c index bbb239ac..ca64df69 100644 --- a/src/match.c +++ b/src/match.c @@ -297,7 +297,7 @@ void match_parse_property(Match *match, const char *ctype, const char *cvalue) { if (strcmp(ctype, "id") == 0) { char *end; - long parsed = strtol(cvalue, &end, 10); + long parsed = strtol(cvalue, &end, 0); if (parsed == LONG_MIN || parsed == LONG_MAX || parsed < 0 || From b5693d6fb33ad29337e4187a2db4a2618ea8fb4c Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 20 Dec 2015 13:12:37 +0100 Subject: [PATCH 097/187] userguide: faq has moved to reddit --- docs/userguide | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/userguide b/docs/userguide index 69f7da92..799d7731 100644 --- a/docs/userguide +++ b/docs/userguide @@ -4,8 +4,9 @@ Michael Stapelberg March 2013 This document contains all the information you need to configure and use the i3 -window manager. If it does not, please check http://faq.i3wm.org/ first, then -contact us on IRC (preferred) or post your question(s) on the mailing list. +window manager. If it does not, please check https://www.reddit.com/r/i3wm/ +first, then contact us on IRC (preferred) or post your question(s) on the +mailing list. == Default keybindings From 019ae6d06cc7a8df5b34f60c2685955ba3bc3c48 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Tue, 22 Dec 2015 22:33:37 +0100 Subject: [PATCH 098/187] =?UTF-8?q?Bugfix:=20don=E2=80=99t=20remove=20Subs?= =?UTF-8?q?tructureRedirect=20event=20mask=20temporarily?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This fixes race conditions, for example when i3bar gets reconfigured after the available outputs change. In that specific case, i3bar sends a ConfigureWindow request (see https://github.com/i3/i3/blob/b5693d6fb33ad29337e4187a2db4a2618ea8fb4c/i3bar/src/xcb.c#L376) which normally is turned into a ConfigureRequest that i3 largely ignores, only the dock client’s height is considered (see https://github.com/i3/i3/blob/b5693d6fb33ad29337e4187a2db4a2618ea8fb4c/src/handlers.c#L390). Turning ConfigureWindow into ConfigureRequest is only done when the SubstructureRedirect event mask is set, and because we temporarily removed _all_ events from our mask, the ConfigureWindow request went through unmodified. This in turn lead to the i3bar client window (not its decoration frame) being positioned at e.g. y=1304, whereas dock client windows should always end up at x=0 y=0 within their decoration frame. The result of the i3bar client window being out of the visible space was either a black i3bar or graphics corruption. This also fixes issue #1904, I think. I couldn’t reproduce issue #1904 specifically, but when i3bar is in the misconfigured state, it will receive a VisibilityNotify event, telling i3bar that it is obscured. This would explain why i3bar sent a SIGSTOP in issue #1904. fixes #1904 --- src/x.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/x.c b/src/x.c index faa892aa..51b66c5d 100644 --- a/src/x.c +++ b/src/x.c @@ -984,7 +984,10 @@ void x_push_changes(Con *con) { DLOG("-- PUSHING WINDOW STACK --\n"); //DLOG("Disabling EnterNotify\n"); - uint32_t values[1] = {XCB_NONE}; + /* We need to keep SubstructureRedirect around, otherwise clients can send + * ConfigureWindow requests and get them applied directly instead of having + * them become ConfigureRequests that i3 handles. */ + uint32_t values[1] = {XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT}; CIRCLEQ_FOREACH_REVERSE(state, &state_head, state) { if (state->mapped) xcb_change_window_attributes(conn, state->id, XCB_CW_EVENT_MASK, values); From 0ebb4b200578b668be8437be581f8a807202be4e Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 25 Dec 2015 16:37:44 +0100 Subject: [PATCH 099/187] make: should be XCB_CURSOR, not XCURSOR MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit After all, we’re not using libXcursor, but libxcb-cursor :). --- common.mk | 8 ++++---- src/i3.mk | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/common.mk b/common.mk index eb324f23..878aeac1 100644 --- a/common.mk +++ b/common.mk @@ -109,15 +109,15 @@ XCB_WM_LIBS := $(call ldflags_for_lib, xcb-icccm,xcb-icccm) XCB_WM_LIBS += $(call ldflags_for_lib, xcb-xinerama,xcb-xinerama) XCB_WM_LIBS += $(call ldflags_for_lib, xcb-randr,xcb-randr) +# XCB cursor +XCB_CURSOR_CFLAGS := $(call cflags_for_lib, xcb-cursor) +XCB_CURSOR_LIBS := $(call ldflags_for_lib, xcb-cursor,xcb-cursor) + XKB_COMMON_CFLAGS := $(call cflags_for_lib, xkbcommon,xkbcommon) XKB_COMMON_LIBS := $(call ldflags_for_lib, xkbcommon,xkbcommon) XKB_COMMON_X11_CFLAGS := $(call cflags_for_lib, xkbcommon-x11,xkbcommon-x11) XKB_COMMON_X11_LIBS := $(call ldflags_for_lib, xkbcommon-x11,xkbcommon-x11) -# Xcursor -XCURSOR_CFLAGS := $(call cflags_for_lib, xcb-cursor) -XCURSOR_LIBS := $(call ldflags_for_lib, xcb-cursor,xcb-cursor) - # yajl YAJL_CFLAGS := $(call cflags_for_lib, yajl) YAJL_LIBS := $(call ldflags_for_lib, yajl,yajl) diff --git a/src/i3.mk b/src/i3.mk index 8472106e..ed8e3ae9 100644 --- a/src/i3.mk +++ b/src/i3.mk @@ -5,8 +5,8 @@ CLEAN_TARGETS += clean-i3 i3_SOURCES := $(filter-out $(i3_SOURCES_GENERATED),$(wildcard src/*.c)) i3_HEADERS_CMDPARSER := $(wildcard include/GENERATED_*.h) i3_HEADERS := $(filter-out $(i3_HEADERS_CMDPARSER),$(wildcard include/*.h)) -i3_CFLAGS = $(XKB_COMMON_CFLAGS) $(XKB_COMMON_X11_CFLAGS) $(XCB_CFLAGS) $(XCB_KBD_CFLAGS) $(XCB_WM_CFLAGS) $(XCURSOR_CFLAGS) $(PANGO_CFLAGS) $(YAJL_CFLAGS) $(LIBEV_CFLAGS) $(PCRE_CFLAGS) $(LIBSN_CFLAGS) -i3_LIBS = $(XKB_COMMON_LIBS) $(XKB_COMMON_X11_LIBS) $(XCB_LIBS) $(XCB_XKB_LIBS) $(XCB_KBD_LIBS) $(XCB_WM_LIBS) $(XCURSOR_LIBS) $(PANGO_LIBS) $(YAJL_LIBS) $(LIBEV_LIBS) $(PCRE_LIBS) $(LIBSN_LIBS) -lm -lpthread +i3_CFLAGS = $(XKB_COMMON_CFLAGS) $(XKB_COMMON_X11_CFLAGS) $(XCB_CFLAGS) $(XCB_KBD_CFLAGS) $(XCB_WM_CFLAGS) $(XCB_CURSOR_CFLAGS) $(PANGO_CFLAGS) $(YAJL_CFLAGS) $(LIBEV_CFLAGS) $(PCRE_CFLAGS) $(LIBSN_CFLAGS) +i3_LIBS = $(XKB_COMMON_LIBS) $(XKB_COMMON_X11_LIBS) $(XCB_LIBS) $(XCB_XKB_LIBS) $(XCB_KBD_LIBS) $(XCB_WM_LIBS) $(XCB_CURSOR_LIBS) $(PANGO_LIBS) $(YAJL_LIBS) $(LIBEV_LIBS) $(PCRE_LIBS) $(LIBSN_LIBS) -lm -lpthread # When using clang, we use pre-compiled headers to speed up the build. With # gcc, this actually makes the build slower. From b1f1da432df47806be3b7e61951eed195ca70fc9 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 25 Dec 2015 16:38:11 +0100 Subject: [PATCH 100/187] i3bar: explicitly set cursor using libxcursor if available Even if the X11 root window cursor is not set up correctly for some reason, with this fix, users should at least see the correct cursor when the pointer is over i3bar. see issue #2114 --- i3bar/i3bar.mk | 4 ++-- i3bar/src/xcb.c | 30 ++++++++++++++++++++++++++++-- 2 files changed, 30 insertions(+), 4 deletions(-) diff --git a/i3bar/i3bar.mk b/i3bar/i3bar.mk index 737b0b69..5aed1902 100644 --- a/i3bar/i3bar.mk +++ b/i3bar/i3bar.mk @@ -4,8 +4,8 @@ CLEAN_TARGETS += clean-i3bar i3bar_SOURCES := $(wildcard i3bar/src/*.c) i3bar_HEADERS := $(wildcard i3bar/include/*.h) -i3bar_CFLAGS = $(XCB_CFLAGS) $(PANGO_CFLAGS) $(YAJL_CFLAGS) $(LIBEV_CFLAGS) -i3bar_LIBS = $(XCB_LIBS) $(PANGO_LIBS) $(YAJL_LIBS) $(LIBEV_LIBS) $(XCB_XKB_LIBS) +i3bar_CFLAGS = $(XCB_CFLAGS) $(XCB_CURSOR_CFLAGS) $(PANGO_CFLAGS) $(YAJL_CFLAGS) $(LIBEV_CFLAGS) +i3bar_LIBS = $(XCB_LIBS) $(XCB_CURSOR_LIBS) $(PANGO_LIBS) $(YAJL_LIBS) $(LIBEV_LIBS) $(XCB_XKB_LIBS) i3bar_OBJECTS := $(i3bar_SOURCES:.c=.o) diff --git a/i3bar/src/xcb.c b/i3bar/src/xcb.c index c6c9846b..5e21e118 100644 --- a/i3bar/src/xcb.c +++ b/i3bar/src/xcb.c @@ -11,6 +11,7 @@ #include #include #include +#include #ifdef XCB_COMPAT #include "xcb_compat.h" @@ -34,6 +35,10 @@ #include "common.h" #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 +54,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; @@ -1206,6 +1212,24 @@ 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)); @@ -1462,6 +1486,7 @@ void clean_xcb(void) { FREE_SLIST(outputs, i3_output); FREE(outputs); + xcb_free_cursor(xcb_connection, cursor); xcb_flush(xcb_connection); xcb_aux_sync(xcb_connection); xcb_disconnect(xcb_connection); @@ -1605,7 +1630,7 @@ xcb_void_cookie_t config_strut_partial(i3_output *output) { */ void reconfig_windows(bool redraw_bars) { uint32_t mask; - uint32_t values[5]; + uint32_t values[6]; static bool tray_configured = false; i3_output *walk; @@ -1623,7 +1648,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; @@ -1645,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, From 3919dd8daaafa54612483ed7b566560aa1fa3e6e Mon Sep 17 00:00:00 2001 From: Kyle Kneitinger Date: Sat, 26 Dec 2015 23:29:26 -0800 Subject: [PATCH 101/187] userguide: clarify quoting of exec commands --- docs/userguide | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/docs/userguide b/docs/userguide index 799d7731..89791bf7 100644 --- a/docs/userguide +++ b/docs/userguide @@ -785,7 +785,7 @@ keyword. These commands will be run in order. See <> for details on the special meaning of +;+ (semicolon) and +,+ (comma): they chain commands together in i3, so you need to use quoted -strings if they appear in your command. +strings (as shown in <>) if they appear in your command. *Syntax*: --------------------------------------- @@ -1696,7 +1696,7 @@ searched in your +$PATH+. See <> for details on the special meaning of +;+ (semicolon) and +,+ (comma): they chain commands together in i3, so you need to use quoted -strings if they appear in your command. +strings (as shown in <>) if they appear in your command. *Syntax*: -------------------------------- @@ -1720,6 +1720,27 @@ launching. So, if an application is not startup-notification aware (most GTK and Qt using applications seem to be, though), you will end up with a watch cursor for 60 seconds. +[[exec_quoting]] +If the command to be executed contains a +;+ (semicolon) and/or a +,+ (comma), +the entire command must be quoted. For example, to have a keybinding for the +shell command +notify-send Hello, i3+, you would add an entry to your +configuration file like this: + +*Example*: +------------------------------ +# Execute a command with a comma in it +bindsym $mod+p exec "notify-send Hello, i3" +------------------------------ + +If however a command with a comma and/or semicolon itself requires quotes, you +must escape the internal quotation marks with double backslashes, like this: + +*Example*: +------------------------------ +# Execute a command with a comma, semicolon and internal quotes +bindsym $mod+p exec "notify-send \\"Hello, i3; from $USER\\"" +------------------------------ + === Splitting containers The split command makes the current window a split container. Split containers From 61a8bc81ecf16182089e07d9c532c4d0e75e1873 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ingo=20B=C3=BCrk?= Date: Sun, 27 Dec 2015 18:08:30 -0500 Subject: [PATCH 102/187] Move urgent flag before killing the parent. We need to move the urgent flag when moving a container across workspaces before calling on_remove_child on the parent in order to avoid a crash. fixes #2128 --- src/con.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/con.c b/src/con.c index 64dbec69..66bba864 100644 --- a/src/con.c +++ b/src/con.c @@ -1040,14 +1040,14 @@ static bool _con_move_to_con(Con *con, Con *target, bool behind_focused, bool fi startup_sequence_delete(sequence); } - CALL(parent, on_remove_child); - /* 9. If the container was marked urgent, move the urgency hint. */ if (urgent) { workspace_update_urgent_flag(source_ws); con_set_urgency(con, true); } + CALL(parent, on_remove_child); + ipc_send_window_event("move", con); return true; } From 0dd71674dea30076f6cda6029f624386acfe015f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ingo=20B=C3=BCrk?= Date: Sun, 27 Dec 2015 20:58:35 -0500 Subject: [PATCH 103/187] Rename tree_close() to tree_close_internal(). It should be clear for callers of this function that this is an internal function that skips certain validations which might be important. Therefore we make it clear that this is an internal function by renaming it. relates to #1761 --- include/con.h | 2 +- include/data.h | 2 +- include/tree.h | 6 +++--- src/commands.c | 2 +- src/con.c | 6 +++--- src/floating.c | 8 ++++---- src/handlers.c | 4 ++-- src/randr.c | 4 ++-- src/tree.c | 18 +++++++++--------- src/workspace.c | 4 ++-- 10 files changed, 28 insertions(+), 28 deletions(-) diff --git a/include/con.h b/include/con.h index 9ed9508a..655fde7f 100644 --- a/include/con.h +++ b/include/con.h @@ -278,7 +278,7 @@ orientation_t con_orientation(Con *con); /** * Returns the container which will be focused next when the given container - * is not available anymore. Called in tree_close and con_move_to_workspace + * is not available anymore. Called in tree_close_internal and con_move_to_workspace * to properly restore focus. * */ diff --git a/include/data.h b/include/data.h index b122dbfd..9ccc2c2e 100644 --- a/include/data.h +++ b/include/data.h @@ -62,7 +62,7 @@ typedef enum { BS_NORMAL = 0, BS_NONE = 1, BS_PIXEL = 2 } border_style_t; -/** parameter to specify whether tree_close() and x_window_kill() should kill +/** parameter to specify whether tree_close_internal() and x_window_kill() should kill * only this specific window or the whole X11 client */ typedef enum { DONT_KILL_WINDOW = 0, KILL_WINDOW = 1, diff --git a/include/tree.h b/include/tree.h index af3309e9..4a640446 100644 --- a/include/tree.h +++ b/include/tree.h @@ -57,7 +57,7 @@ bool level_down(void); void tree_render(void); /** - * Closes the current container using tree_close(). + * Closes the current container using tree_close_internal(). * */ void tree_close_con(kill_window_t kill_window); @@ -78,11 +78,11 @@ void tree_next(char way, orientation_t orientation); * recursively while deleting a containers children. * * The force_set_focus flag is specified in the case of killing a floating - * window: tree_close() will be invoked for the CT_FLOATINGCON (the parent + * window: tree_close_internal() will be invoked for the CT_FLOATINGCON (the parent * container) and focus should be set there. * */ -bool tree_close(Con *con, kill_window_t kill_window, bool dont_kill_parent, bool force_set_focus); +bool tree_close_internal(Con *con, kill_window_t kill_window, bool dont_kill_parent, bool force_set_focus); /** * Loads tree from ~/.i3/_restart.json (used for in-place restarts). diff --git a/src/commands.c b/src/commands.c index d4b2d51c..eb1834e7 100644 --- a/src/commands.c +++ b/src/commands.c @@ -1281,7 +1281,7 @@ void cmd_kill(I3_CMD, const char *kill_mode_str) { else { TAILQ_FOREACH(current, &owindows, owindows) { DLOG("matching: %p / %s\n", current->con, current->con->name); - tree_close(current->con, kill_mode, false, false); + tree_close_internal(current->con, kill_mode, false, false); } } diff --git a/src/con.c b/src/con.c index 64dbec69..f0353f97 100644 --- a/src/con.c +++ b/src/con.c @@ -1162,7 +1162,7 @@ orientation_t con_orientation(Con *con) { /* * Returns the container which will be focused next when the given container - * is not available anymore. Called in tree_close and con_move_to_workspace + * is not available anymore. Called in tree_close_internal and con_move_to_workspace * to properly restore focus. * */ @@ -1678,7 +1678,7 @@ static void con_on_remove_child(Con *con) { if (TAILQ_EMPTY(&(con->focus_head)) && !workspace_is_visible(con)) { LOG("Closing old workspace (%p / %s), it is empty\n", con, con->name); yajl_gen gen = ipc_marshal_workspace_event("empty", con, NULL); - tree_close(con, DONT_KILL_WINDOW, false, false); + tree_close_internal(con, DONT_KILL_WINDOW, false, false); const unsigned char *payload; ylength length; @@ -1699,7 +1699,7 @@ static void con_on_remove_child(Con *con) { int children = con_num_children(con); if (children == 0) { DLOG("Container empty, closing\n"); - tree_close(con, DONT_KILL_WINDOW, false, false); + tree_close_internal(con, DONT_KILL_WINDOW, false, false); return; } } diff --git a/src/floating.c b/src/floating.c index 77bc9e17..3aa42364 100644 --- a/src/floating.c +++ b/src/floating.c @@ -162,7 +162,7 @@ void floating_enable(Con *con, bool automatic) { } /* 1: detach the container from its parent */ - /* TODO: refactor this with tree_close() */ + /* TODO: refactor this with tree_close_internal() */ TAILQ_REMOVE(&(con->parent->nodes_head), con, nodes); TAILQ_REMOVE(&(con->parent->focus_head), con, focused); @@ -180,7 +180,7 @@ void floating_enable(Con *con, bool automatic) { nc->layout = L_SPLITH; /* We insert nc already, even though its rect is not yet calculated. This * is necessary because otherwise the workspace might be empty (and get - * closed in tree_close()) even though it’s not. */ + * closed in tree_close_internal()) even though it’s not. */ TAILQ_INSERT_TAIL(&(ws->floating_head), nc, floating_windows); TAILQ_INSERT_TAIL(&(ws->focus_head), nc, focused); @@ -188,7 +188,7 @@ void floating_enable(Con *con, bool automatic) { if ((con->parent->type == CT_CON || con->parent->type == CT_FLOATING_CON) && con_num_children(con->parent) == 0) { DLOG("Old container empty after setting this child to floating, closing\n"); - tree_close(con->parent, DONT_KILL_WINDOW, false, false); + tree_close_internal(con->parent, DONT_KILL_WINDOW, false, false); } char *name; @@ -333,7 +333,7 @@ void floating_disable(Con *con, bool automatic) { /* 2: kill parent container */ TAILQ_REMOVE(&(con->parent->parent->floating_head), con->parent, floating_windows); TAILQ_REMOVE(&(con->parent->parent->focus_head), con->parent, focused); - tree_close(con->parent, DONT_KILL_WINDOW, true, false); + tree_close_internal(con->parent, DONT_KILL_WINDOW, true, false); /* 3: re-attach to the parent of the currently focused con on the workspace * this floating con was on */ diff --git a/src/handlers.c b/src/handlers.c index 6f08d25c..6cbc54f2 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -503,7 +503,7 @@ static void handle_unmap_notify_event(xcb_unmap_notify_event_t *event) { goto ignore_end; } - tree_close(con, DONT_KILL_WINDOW, false, false); + tree_close_internal(con, DONT_KILL_WINDOW, false, false); tree_render(); ignore_end: @@ -878,7 +878,7 @@ static void handle_client_message(xcb_client_message_event_t *event) { if (event->data.data32[0]) last_timestamp = event->data.data32[0]; - tree_close(con, KILL_WINDOW, false, false); + tree_close_internal(con, KILL_WINDOW, false, false); tree_render(); } else { DLOG("Couldn't find con for _NET_CLOSE_WINDOW request. (window = %d)\n", event->window); diff --git a/src/randr.c b/src/randr.c index 81a33e62..6753f8a6 100644 --- a/src/randr.c +++ b/src/randr.c @@ -741,7 +741,7 @@ void randr_query_outputs(void) { if (current != next && TAILQ_EMPTY(&(current->focus_head))) { /* the workspace is empty and not focused, get rid of it */ DLOG("Getting rid of current = %p / %s (empty, unfocused)\n", current, current->name); - tree_close(current, DONT_KILL_WINDOW, false, false); + tree_close_internal(current, DONT_KILL_WINDOW, false, false); continue; } DLOG("Detaching current = %p / %s\n", current, current->name); @@ -783,7 +783,7 @@ void randr_query_outputs(void) { } DLOG("destroying disappearing con %p\n", output->con); - tree_close(output->con, DONT_KILL_WINDOW, true, false); + tree_close_internal(output->con, DONT_KILL_WINDOW, true, false); DLOG("Done. Should be fine now\n"); output->con = NULL; } diff --git a/src/tree.c b/src/tree.c index 1d06d874..e296a981 100644 --- a/src/tree.c +++ b/src/tree.c @@ -185,11 +185,11 @@ static bool _is_con_mapped(Con *con) { * recursively while deleting a containers children. * * The force_set_focus flag is specified in the case of killing a floating - * window: tree_close() will be invoked for the CT_FLOATINGCON (the parent + * window: tree_close_internal() will be invoked for the CT_FLOATINGCON (the parent * container) and focus should be set there. * */ -bool tree_close(Con *con, kill_window_t kill_window, bool dont_kill_parent, bool force_set_focus) { +bool tree_close_internal(Con *con, kill_window_t kill_window, bool dont_kill_parent, bool force_set_focus) { bool was_mapped = con->mapped; Con *parent = con->parent; @@ -219,7 +219,7 @@ bool tree_close(Con *con, kill_window_t kill_window, bool dont_kill_parent, bool for (child = TAILQ_FIRST(&(con->nodes_head)); child;) { nextchild = TAILQ_NEXT(child, nodes); DLOG("killing child=%p\n", child); - if (!tree_close(child, kill_window, true, false)) + if (!tree_close_internal(child, kill_window, true, false)) abort_kill = true; child = nextchild; } @@ -310,7 +310,7 @@ bool tree_close(Con *con, kill_window_t kill_window, bool dont_kill_parent, bool * underlying container, see ticket #660. * * Rendering has to be avoided when dont_kill_parent is set (when - * tree_close calls itself recursively) because the tree is in a + * tree_close_internal calls itself recursively) because the tree is in a * non-renderable state during that time. */ if (!dont_kill_parent) tree_render(); @@ -320,7 +320,7 @@ bool tree_close(Con *con, kill_window_t kill_window, bool dont_kill_parent, bool if (con_is_floating(con)) { DLOG("Container was floating, killing floating container\n"); - tree_close(parent, DONT_KILL_WINDOW, false, (con == focused)); + tree_close_internal(parent, DONT_KILL_WINDOW, false, (con == focused)); DLOG("parent container killed\n"); } @@ -363,7 +363,7 @@ bool tree_close(Con *con, kill_window_t kill_window, bool dont_kill_parent, bool } /* - * Closes the current container using tree_close(). + * Closes the current container using tree_close_internal(). * */ void tree_close_con(kill_window_t kill_window) { @@ -379,7 +379,7 @@ void tree_close_con(kill_window_t kill_window) { for (child = TAILQ_FIRST(&(focused->focus_head)); child;) { nextchild = TAILQ_NEXT(child, focused); DLOG("killing child=%p\n", child); - tree_close(child, kill_window, false, false); + tree_close_internal(child, kill_window, false, false); child = nextchild; } @@ -387,7 +387,7 @@ void tree_close_con(kill_window_t kill_window) { } /* Kill con */ - tree_close(focused, kill_window, false, false); + tree_close_internal(focused, kill_window, false, false); } /* @@ -773,7 +773,7 @@ void tree_flatten(Con *con) { /* 4: close the redundant cons */ DLOG("closing redundant cons\n"); - tree_close(con, DONT_KILL_WINDOW, true, false); + tree_close_internal(con, DONT_KILL_WINDOW, true, false); /* Well, we got to abort the recursion here because we destroyed the * container. However, if tree_flatten() is called sufficiently often, diff --git a/src/workspace.c b/src/workspace.c index dc0a596d..923bfc83 100644 --- a/src/workspace.c +++ b/src/workspace.c @@ -442,7 +442,7 @@ static void _workspace_show(Con *workspace) { DLOG("old = %p / %s\n", old, (old ? old->name : "(null)")); /* Close old workspace if necessary. This must be done *after* doing - * urgency handling, because tree_close() will do a con_focus() on the next + * urgency handling, because tree_close_internal() will do a con_focus() on the next * client, which will clear the urgency flag too early. Also, there is no * way for con_focus() to know about when to clear urgency immediately and * when to defer it. */ @@ -451,7 +451,7 @@ static void _workspace_show(Con *workspace) { if (!workspace_is_visible(old)) { LOG("Closing old workspace (%p / %s), it is empty\n", old, old->name); yajl_gen gen = ipc_marshal_workspace_event("empty", old, NULL); - tree_close(old, DONT_KILL_WINDOW, false, false); + tree_close_internal(old, DONT_KILL_WINDOW, false, false); const unsigned char *payload; ylength length; From 19c273a2adde5e6248566b7bd13b5cee80f84b34 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ingo=20B=C3=BCrk?= Date: Sun, 27 Dec 2015 22:11:51 -0500 Subject: [PATCH 104/187] Validate matched containers for "kill" command correctly. We now execute the validations when "kill" is executed even if match criteria are used. This prevents users from killing workspace containers, which instead kills all clients (as before when not using criteria). fixes #1761 --- include/con.h | 6 ++++ include/tree.h | 6 ---- src/commands.c | 14 +++------ src/con.c | 30 +++++++++++++++++++ src/tree.c | 28 ----------------- .../261-match-con_id-con_mark-combinations.t | 4 +++ 6 files changed, 44 insertions(+), 44 deletions(-) diff --git a/include/con.h b/include/con.h index 655fde7f..db5fcfbf 100644 --- a/include/con.h +++ b/include/con.h @@ -30,6 +30,12 @@ Con *con_new(Con *parent, i3Window *window); */ void con_focus(Con *con); +/** + * Closes the given container. + * + */ +void con_close(Con *con, kill_window_t kill_window); + /** * Returns true when this node is a leaf node (has no children) * diff --git a/include/tree.h b/include/tree.h index 4a640446..c64c173d 100644 --- a/include/tree.h +++ b/include/tree.h @@ -56,12 +56,6 @@ bool level_down(void); */ void tree_render(void); -/** - * Closes the current container using tree_close_internal(). - * - */ -void tree_close_con(kill_window_t kill_window); - /** * Changes focus in the given way (next/previous) and given orientation * (horizontal/vertical). diff --git a/src/commands.c b/src/commands.c index eb1834e7..579b5e0e 100644 --- a/src/commands.c +++ b/src/commands.c @@ -1258,7 +1258,6 @@ void cmd_split(I3_CMD, const char *direction) { void cmd_kill(I3_CMD, const char *kill_mode_str) { if (kill_mode_str == NULL) kill_mode_str = "window"; - owindow *current; DLOG("kill_mode=%s\n", kill_mode_str); @@ -1273,16 +1272,11 @@ void cmd_kill(I3_CMD, const char *kill_mode_str) { return; } - HANDLE_INVALID_MATCH; + HANDLE_EMPTY_MATCH; - /* check if the match is empty, not if the result is empty */ - if (match_is_empty(current_match)) - tree_close_con(kill_mode); - else { - TAILQ_FOREACH(current, &owindows, owindows) { - DLOG("matching: %p / %s\n", current->con, current->con->name); - tree_close_internal(current->con, kill_mode, false, false); - } + owindow *current; + TAILQ_FOREACH(current, &owindows, owindows) { + con_close(current->con, kill_mode); } cmd_output->needs_tree_render = true; diff --git a/src/con.c b/src/con.c index f0353f97..591419e4 100644 --- a/src/con.c +++ b/src/con.c @@ -220,6 +220,36 @@ void con_focus(Con *con) { } } +/* + * Closes the given container. + * + */ +void con_close(Con *con, kill_window_t kill_window) { + assert(con != NULL); + DLOG("Closing con = %p.\n", con); + + /* We never close output or root containers. */ + if (con->type == CT_OUTPUT || con->type == CT_ROOT) { + DLOG("con = %p is of type %d, not closing anything.\n", con, con->type); + return; + } + + if (con->type == CT_WORKSPACE) { + DLOG("con = %p is a workspace, closing all children instead.\n", con); + Con *child, *nextchild; + for (child = TAILQ_FIRST(&(con->focus_head)); child;) { + nextchild = TAILQ_NEXT(child, focused); + DLOG("killing child = %p.\n", child); + tree_close_internal(child, kill_window, false, false); + child = nextchild; + } + + return; + } + + tree_close_internal(con, kill_window, false, false); +} + /* * Returns true when this node is a leaf node (has no children) * diff --git a/src/tree.c b/src/tree.c index e296a981..5642c5c7 100644 --- a/src/tree.c +++ b/src/tree.c @@ -362,34 +362,6 @@ bool tree_close_internal(Con *con, kill_window_t kill_window, bool dont_kill_par return true; } -/* - * Closes the current container using tree_close_internal(). - * - */ -void tree_close_con(kill_window_t kill_window) { - assert(focused != NULL); - - /* There *should* be no possibility to focus outputs / root container */ - assert(focused->type != CT_OUTPUT); - assert(focused->type != CT_ROOT); - - if (focused->type == CT_WORKSPACE) { - DLOG("Workspaces cannot be close, closing all children instead\n"); - Con *child, *nextchild; - for (child = TAILQ_FIRST(&(focused->focus_head)); child;) { - nextchild = TAILQ_NEXT(child, focused); - DLOG("killing child=%p\n", child); - tree_close_internal(child, kill_window, false, false); - child = nextchild; - } - - return; - } - - /* Kill con */ - tree_close_internal(focused, kill_window, false, false); -} - /* * Splits (horizontally or vertically) the given container by creating a new * container which contains the old one and the future ones. diff --git a/testcases/t/261-match-con_id-con_mark-combinations.t b/testcases/t/261-match-con_id-con_mark-combinations.t index b255558e..61ff1ff0 100644 --- a/testcases/t/261-match-con_id-con_mark-combinations.t +++ b/testcases/t/261-match-con_id-con_mark-combinations.t @@ -27,9 +27,11 @@ $ws = fresh_workspace; open_window(wm_class => 'matchme'); cmd '[con_id=__focused__ class=doesnotmatch] kill'; +sync_with_i3; is(@{get_ws($ws)->{nodes}}, 1, 'window was not killed'); cmd '[con_id=__focused__ class=matchme] kill'; +sync_with_i3; is(@{get_ws($ws)->{nodes}}, 0, 'window was killed'); ############################################################################### @@ -41,9 +43,11 @@ open_window(wm_class => 'matchme'); cmd 'mark marked'; cmd '[con_mark=marked class=doesnotmatch] kill'; +sync_with_i3; is(@{get_ws($ws)->{nodes}}, 1, 'window was not killed'); cmd '[con_mark=marked class=matchme] kill'; +sync_with_i3; is(@{get_ws($ws)->{nodes}}, 0, 'window was killed'); ############################################################################### From 194da2d7cfb90ee23ad8bdce6ca3e20e2f27abc1 Mon Sep 17 00:00:00 2001 From: Franz Thoma Date: Mon, 28 Dec 2015 19:38:17 +0100 Subject: [PATCH 105/187] Comply with documentation: 'workspace' token in 'assign' command MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit According to the User's Guide [1], an `assign` command allows a `workspace` token after the selector, as an alternative or in addition to the unicode arrow `→`. In reality, however, the `workspace` token is not recognized. Example: assign [class="Firefox"] workspace "1: Browser" should assign Firefox windows to workspace `1: Browser`, but the the browser window appears on a new workspace called `workspace "1: Browser"` instead. With this fix, both `→` and `workspace` are recognized (and ignored) after the selector. [1] https://i3wm.org/docs/userguide.html#_automatically_putting_clients_on_specific_workspaces --- parser-specs/config.spec | 2 ++ 1 file changed, 2 insertions(+) diff --git a/parser-specs/config.spec b/parser-specs/config.spec index 882e81fb..9dad79c0 100644 --- a/parser-specs/config.spec +++ b/parser-specs/config.spec @@ -146,6 +146,8 @@ state ASSIGN: state ASSIGN_WORKSPACE: '→' -> + 'workspace' + -> workspace = string -> call cfg_assign($workspace) From 19fd6817af1e070cedde6d94d557e0944f8fa44d Mon Sep 17 00:00:00 2001 From: Alex Auvolat Date: Tue, 29 Dec 2015 12:47:12 +0100 Subject: [PATCH 106/187] Refactor extern definition of conn and root_screen --- i3bar/include/xcb.h | 2 -- include/i3.h | 2 -- include/libi3.h | 7 +++++++ libi3/dpi.c | 2 -- libi3/font.c | 3 --- libi3/get_mod_mask.c | 2 -- src/floating.c | 2 -- src/resize.c | 2 -- 8 files changed, 7 insertions(+), 15 deletions(-) diff --git a/i3bar/include/xcb.h b/i3bar/include/xcb.h index 0a9bd7e4..03bfc51e 100644 --- a/i3bar/include/xcb.h +++ b/i3bar/include/xcb.h @@ -24,8 +24,6 @@ #define XEMBED_MAPPED (1 << 0) #define XEMBED_EMBEDDED_NOTIFY 0 -xcb_connection_t *xcb_connection; - /* 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); diff --git a/include/i3.h b/include/i3.h index 2a431486..254c4d23 100644 --- a/include/i3.h +++ b/include/i3.h @@ -35,7 +35,6 @@ extern struct rlimit original_rlimit_core; extern bool debug_build; /** The number of file descriptors passed via socket activation. */ extern int listen_fds; -extern xcb_connection_t *conn; extern int conn_screen; /** * The EWMH support window that is used to indicate that an EWMH-compliant @@ -61,7 +60,6 @@ extern TAILQ_HEAD(autostarts_always_head, Autostart) autostarts_always; extern TAILQ_HEAD(ws_assignments_head, Workspace_Assignment) ws_assignments; extern TAILQ_HEAD(assignments_head, Assignment) assignments; extern SLIST_HEAD(stack_wins_head, Stack_Window) stack_wins; -extern xcb_screen_t *root_screen; /* Color depth, visual id and colormap to use when creating windows and * pixmaps. Will use 32 bit depth and an appropriate visual, if available, diff --git a/include/libi3.h b/include/libi3.h index 08b01402..90ed8989 100644 --- a/include/libi3.h +++ b/include/libi3.h @@ -26,6 +26,13 @@ #define DEFAULT_DIR_MODE (S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH) +/** + * XCB connection and root screen + * + */ +extern xcb_connection_t *conn; +extern xcb_screen_t *root_screen; + /** * Opaque data structure for storing strings. * diff --git a/libi3/dpi.c b/libi3/dpi.c index a347b08f..897e6e40 100644 --- a/libi3/dpi.c +++ b/libi3/dpi.c @@ -8,8 +8,6 @@ #include "libi3.h" #include -extern xcb_screen_t *root_screen; - /* * Convert a logical amount of pixels (e.g. 2 pixels on a “standard” 96 DPI * screen) to a corresponding amount of physical pixels on a standard or retina diff --git a/libi3/font.c b/libi3/font.c index c90f8be0..c09a7fb1 100644 --- a/libi3/font.c +++ b/libi3/font.c @@ -19,9 +19,6 @@ #include "libi3.h" -extern xcb_connection_t *conn; -extern xcb_screen_t *root_screen; - static const i3Font *savedFont = NULL; #if PANGO_SUPPORT diff --git a/libi3/get_mod_mask.c b/libi3/get_mod_mask.c index 3b6976ad..ac71e6b2 100644 --- a/libi3/get_mod_mask.c +++ b/libi3/get_mod_mask.c @@ -12,8 +12,6 @@ #include "libi3.h" -extern xcb_connection_t *conn; - /* * All-in-one function which returns the modifier mask (XCB_MOD_MASK_*) for the * given keysymbol, for example for XCB_NUM_LOCK (usually configured to mod2). diff --git a/src/floating.c b/src/floating.c index 3aa42364..2b8a88d1 100644 --- a/src/floating.c +++ b/src/floating.c @@ -11,8 +11,6 @@ */ #include "all.h" -extern xcb_connection_t *conn; - /* * Calculates sum of heights and sum of widths of all currently active outputs * diff --git a/src/resize.c b/src/resize.c index 31a78354..5d9eb2eb 100644 --- a/src/resize.c +++ b/src/resize.c @@ -11,8 +11,6 @@ */ #include "all.h" -extern xcb_connection_t *conn; - /* * This is an ugly data structure which we need because there is no standard * way of having nested functions (only available as a gcc extension at the From c6a4e4519fb000fe835486bcb02f3253e4316a3b Mon Sep 17 00:00:00 2001 From: Alex Auvolat Date: Mon, 28 Dec 2015 12:43:53 +0100 Subject: [PATCH 107/187] Correct color management for pango fonts Corrects the cases where the colorpixel is not 0xRRGGBB : we have to use the full color_t struct to describe font colors, as Pango expects RGB values and not an XCB colorpixel value. --- i3-config-wizard/main.c | 14 ++++++------- i3-input/main.c | 2 +- i3-nagbar/main.c | 46 ++++++++++++++++++++--------------------- include/libi3.h | 28 ++++++++++++------------- libi3/draw_util.c | 2 +- libi3/font.c | 10 ++++----- src/restore_layout.c | 2 +- src/sighandler.c | 6 +++--- 8 files changed, 55 insertions(+), 55 deletions(-) diff --git a/i3-config-wizard/main.c b/i3-config-wizard/main.c index bd12cd81..284f15fa 100644 --- a/i3-config-wizard/main.c +++ b/i3-config-wizard/main.c @@ -479,7 +479,7 @@ static int handle_expose() { if (current_step == STEP_WELCOME) { /* restore font color */ - set_font_colors(pixmap_gc, get_colorpixel("#FFFFFF"), get_colorpixel("#000000")); + set_font_colors(pixmap_gc, draw_util_hex_to_color("#FFFFFF"), draw_util_hex_to_color("#000000")); txt(logical_px(10), 2, "You have not configured i3 yet."); txt(logical_px(10), 3, "Do you want me to generate a config at"); @@ -493,16 +493,16 @@ static int handle_expose() { txt(logical_px(85), 8, "No, I will use the defaults"); /* green */ - set_font_colors(pixmap_gc, get_colorpixel("#00FF00"), get_colorpixel("#000000")); + set_font_colors(pixmap_gc, draw_util_hex_to_color("#00FF00"), draw_util_hex_to_color("#000000")); txt(logical_px(25), 6, ""); /* red */ - set_font_colors(pixmap_gc, get_colorpixel("#FF0000"), get_colorpixel("#000000")); + set_font_colors(pixmap_gc, draw_util_hex_to_color("#FF0000"), draw_util_hex_to_color("#000000")); txt(logical_px(31), 8, ""); } if (current_step == STEP_GENERATE) { - set_font_colors(pixmap_gc, get_colorpixel("#FFFFFF"), get_colorpixel("#000000")); + set_font_colors(pixmap_gc, draw_util_hex_to_color("#FFFFFF"), draw_util_hex_to_color("#000000")); txt(logical_px(10), 2, "Please choose either:"); txt(logical_px(85), 4, "Win as default modifier"); @@ -519,7 +519,7 @@ static int handle_expose() { /* the selected modifier */ set_font(&bold_font); - set_font_colors(pixmap_gc, get_colorpixel("#FFFFFF"), get_colorpixel("#000000")); + set_font_colors(pixmap_gc, draw_util_hex_to_color("#FFFFFF"), draw_util_hex_to_color("#000000")); if (modifier == MOD_Mod4) txt(logical_px(10), 4, "-> "); else @@ -527,11 +527,11 @@ static int handle_expose() { /* green */ set_font(&font); - set_font_colors(pixmap_gc, get_colorpixel("#00FF00"), get_colorpixel("#000000")); + set_font_colors(pixmap_gc, draw_util_hex_to_color("#00FF00"), draw_util_hex_to_color("#000000")); txt(logical_px(25), 9, ""); /* red */ - set_font_colors(pixmap_gc, get_colorpixel("#FF0000"), get_colorpixel("#000000")); + set_font_colors(pixmap_gc, draw_util_hex_to_color("#FF0000"), draw_util_hex_to_color("#000000")); txt(logical_px(31), 10, ""); } diff --git a/i3-input/main.c b/i3-input/main.c index 4e1be78b..e196891c 100644 --- a/i3-input/main.c +++ b/i3-input/main.c @@ -137,7 +137,7 @@ static int handle_expose(void *data, xcb_connection_t *conn, xcb_expose_event_t xcb_poly_fill_rectangle(conn, pixmap, pixmap_gc, 1, &inner); /* restore font color */ - set_font_colors(pixmap_gc, get_colorpixel("#FFFFFF"), get_colorpixel("#000000")); + set_font_colors(pixmap_gc, draw_util_hex_to_color("#FFFFFF"), draw_util_hex_to_color("#000000")); /* draw the prompt … */ if (prompt != NULL) { diff --git a/i3-nagbar/main.c b/i3-nagbar/main.c index d6ace221..cbbe9ef8 100644 --- a/i3-nagbar/main.c +++ b/i3-nagbar/main.c @@ -51,11 +51,11 @@ static button_t *buttons; static int buttoncnt; /* Result of get_colorpixel() for the various colors. */ -static uint32_t color_background; /* background of the bar */ -static uint32_t color_button_background; /* background for buttons */ -static uint32_t color_border; /* color of the button border */ -static uint32_t color_border_bottom; /* color of the bottom border */ -static uint32_t color_text; /* color of the text */ +static color_t color_background; /* background of the bar */ +static color_t color_button_background; /* background for buttons */ +static color_t color_border; /* color of the button border */ +static color_t color_border_bottom; /* color of the bottom border */ +static color_t color_text; /* color of the text */ xcb_window_t root; xcb_connection_t *conn; @@ -191,7 +191,7 @@ static void handle_button_release(xcb_connection_t *conn, xcb_button_release_eve */ static int handle_expose(xcb_connection_t *conn, xcb_expose_event_t *event) { /* re-draw the background */ - xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND, (uint32_t[]){color_background}); + xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND, (uint32_t[]){color_background.colorpixel}); xcb_poly_fill_rectangle(conn, pixmap, pixmap_gc, 1, &rect); /* restore font color */ @@ -210,14 +210,14 @@ static int handle_expose(xcb_connection_t *conn, xcb_expose_event_t *event) { w += logical_px(8); int y = rect.width; uint32_t values[3]; - values[0] = color_button_background; + values[0] = color_button_background.colorpixel; values[1] = line_width; xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND | XCB_GC_LINE_WIDTH, values); xcb_rectangle_t close = {y - w - (2 * line_width), 0, w + (2 * line_width), rect.height}; xcb_poly_fill_rectangle(conn, pixmap, pixmap_gc, 1, &close); - xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND, (uint32_t[]){color_border}); + xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND, (uint32_t[]){color_border.colorpixel}); xcb_point_t points[] = { {y - w - (2 * line_width), line_width / 2}, {y - (line_width / 2), line_width / 2}, @@ -245,11 +245,11 @@ static int handle_expose(xcb_connection_t *conn, xcb_expose_event_t *event) { /* account for left/right padding, which seems to be set to 12px (total) below */ w += logical_px(12); y -= logical_px(30); - xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND, (uint32_t[]){color_button_background}); + xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND, (uint32_t[]){color_button_background.colorpixel}); close = (xcb_rectangle_t){y - w - (2 * line_width), logical_px(2), w + (2 * line_width), rect.height - logical_px(6)}; xcb_poly_fill_rectangle(conn, pixmap, pixmap_gc, 1, &close); - xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND, (uint32_t[]){color_border}); + xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND, (uint32_t[]){color_border.colorpixel}); buttons[c].x = y - w - (2 * line_width); buttons[c].width = w; xcb_point_t points2[] = { @@ -260,8 +260,8 @@ static int handle_expose(xcb_connection_t *conn, xcb_expose_event_t *event) { {y - w - (2 * line_width), (line_width / 2) + logical_px(2)}}; xcb_poly_line(conn, XCB_COORD_MODE_ORIGIN, pixmap, pixmap_gc, 5, points2); - values[0] = color_text; - values[1] = color_button_background; + values[0] = color_text.colorpixel; + values[1] = color_button_background.colorpixel; set_font_colors(pixmap_gc, color_text, color_button_background); /* the x term seems to set left/right padding */ draw_text(buttons[c].label, pixmap, pixmap_gc, NULL, @@ -274,7 +274,7 @@ static int handle_expose(xcb_connection_t *conn, xcb_expose_event_t *event) { /* border line at the bottom */ line_width = logical_px(2); - values[0] = color_border_bottom; + values[0] = color_border_bottom.colorpixel; values[1] = line_width; xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND | XCB_GC_LINE_WIDTH, values); xcb_point_t bottom[] = { @@ -448,18 +448,18 @@ int main(int argc, char *argv[]) { if (bar_type == TYPE_ERROR) { /* Red theme for error messages */ - color_button_background = get_colorpixel("#680a0a"); - color_background = get_colorpixel("#900000"); - color_text = get_colorpixel("#ffffff"); - color_border = get_colorpixel("#d92424"); - color_border_bottom = get_colorpixel("#470909"); + color_button_background = draw_util_hex_to_color("#680a0a"); + color_background = draw_util_hex_to_color("#900000"); + color_text = draw_util_hex_to_color("#ffffff"); + color_border = draw_util_hex_to_color("#d92424"); + color_border_bottom = draw_util_hex_to_color("#470909"); } else { /* Yellowish theme for warnings */ - color_button_background = get_colorpixel("#ffc100"); - color_background = get_colorpixel("#ffa8000"); - color_text = get_colorpixel("#000000"); - color_border = get_colorpixel("#ab7100"); - color_border_bottom = get_colorpixel("#ab7100"); + color_button_background = draw_util_hex_to_color("#ffc100"); + color_background = draw_util_hex_to_color("#ffa8000"); + color_text = draw_util_hex_to_color("#000000"); + color_border = draw_util_hex_to_color("#ab7100"); + color_border_bottom = draw_util_hex_to_color("#ab7100"); } font = load_font(pattern, true); diff --git a/include/libi3.h b/include/libi3.h index 90ed8989..0c69f1c0 100644 --- a/include/libi3.h +++ b/include/libi3.h @@ -392,11 +392,24 @@ char *convert_ucs2_to_utf8(xcb_char2b_t *text, size_t num_glyphs); */ xcb_char2b_t *convert_utf8_to_ucs2(char *input, size_t *real_strlen); +/* Represents a color split by color channel. */ +typedef struct color_t { + double red; + double green; + double blue; + double alpha; + + /* The colorpixel we use for direct XCB calls. */ + uint32_t colorpixel; +} color_t; + +#define COLOR_TRANSPARENT ((color_t){.red = 0.0, .green = 0.0, .blue = 0.0, .colorpixel = 0}) + /** * Defines the colors to be used for the forthcoming draw_text calls. * */ -void set_font_colors(xcb_gcontext_t gc, uint32_t foreground, uint32_t background); +void set_font_colors(xcb_gcontext_t gc, color_t foreground, color_t background); /** * Returns true if and only if the current font is a pango font. @@ -501,19 +514,6 @@ int mkdirp(const char *path, mode_t mode); } while (0) #endif -/* Represents a color split by color channel. */ -typedef struct color_t { - double red; - double green; - double blue; - double alpha; - - /* The colorpixel we use for direct XCB calls. */ - uint32_t colorpixel; -} color_t; - -#define COLOR_TRANSPARENT ((color_t){.red = 0.0, .green = 0.0, .blue = 0.0, .colorpixel = 0}) - /* A wrapper grouping an XCB drawable and both a graphics context * and the corresponding cairo objects representing it. */ typedef struct surface_t { diff --git a/libi3/draw_util.c b/libi3/draw_util.c index fa538d1a..dcd881c2 100644 --- a/libi3/draw_util.c +++ b/libi3/draw_util.c @@ -144,7 +144,7 @@ void draw_util_text(i3String *text, surface_t *surface, color_t fg_color, color_ CAIRO_SURFACE_FLUSH(surface->surface); #endif - set_font_colors(surface->gc, fg_color.colorpixel, bg_color.colorpixel); + set_font_colors(surface->gc, fg_color, bg_color); draw_text(text, surface->id, surface->gc, surface->visual_type, x, y, max_width); #ifdef CAIRO_SUPPORT diff --git a/libi3/font.c b/libi3/font.c index c09a7fb1..9e72849a 100644 --- a/libi3/font.c +++ b/libi3/font.c @@ -310,7 +310,7 @@ void free_font(void) { * Defines the colors to be used for the forthcoming draw_text calls. * */ -void set_font_colors(xcb_gcontext_t gc, uint32_t foreground, uint32_t background) { +void set_font_colors(xcb_gcontext_t gc, color_t foreground, color_t background) { assert(savedFont != NULL); switch (savedFont->type) { @@ -320,16 +320,16 @@ void set_font_colors(xcb_gcontext_t gc, uint32_t foreground, uint32_t background case FONT_TYPE_XCB: { /* Change the font and colors in the GC */ uint32_t mask = XCB_GC_FOREGROUND | XCB_GC_BACKGROUND | XCB_GC_FONT; - uint32_t values[] = {foreground, background, savedFont->specific.xcb.id}; + uint32_t values[] = {foreground.colorpixel, background.colorpixel, savedFont->specific.xcb.id}; xcb_change_gc(conn, gc, mask, values); break; } #if PANGO_SUPPORT case FONT_TYPE_PANGO: /* Save the foreground font */ - pango_font_red = ((foreground >> 16) & 0xff) / 255.0; - pango_font_green = ((foreground >> 8) & 0xff) / 255.0; - pango_font_blue = (foreground & 0xff) / 255.0; + pango_font_red = foreground.red; + pango_font_green = foreground.green; + pango_font_blue = foreground.blue; break; #endif default: diff --git a/src/restore_layout.c b/src/restore_layout.c index 5ca4cff5..af77b11d 100644 --- a/src/restore_layout.c +++ b/src/restore_layout.c @@ -133,7 +133,7 @@ static void update_placeholder_contents(placeholder_state *state) { xcb_flush(restore_conn); xcb_aux_sync(restore_conn); - set_font_colors(state->gc, config.client.placeholder.text.colorpixel, config.client.placeholder.background.colorpixel); + set_font_colors(state->gc, config.client.placeholder.text, config.client.placeholder.background); Match *swallows; int n = 0; diff --git a/src/sighandler.c b/src/sighandler.c index 555f5e55..80d2fae2 100644 --- a/src/sighandler.c +++ b/src/sighandler.c @@ -141,7 +141,7 @@ static int sig_draw_window(xcb_window_t win, int width, int height, int font_hei xcb_poly_fill_rectangle(conn, pixmap, pixmap_gc, 1, &inner); /* restore font color */ - set_font_colors(pixmap_gc, get_colorpixel("#FFFFFF"), get_colorpixel("#000000")); + set_font_colors(pixmap_gc, draw_util_hex_to_color("#FFFFFF"), draw_util_hex_to_color("#000000")); char *bt_colour = "#FFFFFF"; if (backtrace_done < 0) @@ -152,14 +152,14 @@ static int sig_draw_window(xcb_window_t win, int width, int height, int font_hei for (int i = 0; crash_text_i3strings[i] != NULL; ++i) { /* fix the colour for the backtrace line when it finished */ if (i == backtrace_string_index) - set_font_colors(pixmap_gc, get_colorpixel(bt_colour), get_colorpixel("#000000")); + set_font_colors(pixmap_gc, draw_util_hex_to_color(bt_colour), draw_util_hex_to_color("#000000")); draw_text(crash_text_i3strings[i], pixmap, pixmap_gc, NULL, 8, 5 + i * font_height, width - 16); /* and reset the colour again for other lines */ if (i == backtrace_string_index) - set_font_colors(pixmap_gc, get_colorpixel("#FFFFFF"), get_colorpixel("#000000")); + set_font_colors(pixmap_gc, draw_util_hex_to_color("#FFFFFF"), draw_util_hex_to_color("#000000")); } /* Copy the contents of the pixmap to the real window */ From 287a0b4c3cc24a18f5063b7d5743f0b85d6852a1 Mon Sep 17 00:00:00 2001 From: Alex Auvolat Date: Mon, 28 Dec 2015 12:58:32 +0100 Subject: [PATCH 108/187] get_colorpixel support for non-true color displays Re-introduce fully-fledged get_colorpixel function, which enables arbitrary color depths for the display. The previous code is kept as an optimization for the case of a true color display, where a X11 roundtrip is unnecessary. --- libi3/get_colorpixel.c | 59 +++++++++++++++++++++++++++++++++++++----- 1 file changed, 52 insertions(+), 7 deletions(-) diff --git a/libi3/get_colorpixel.c b/libi3/get_colorpixel.c index f81ea6c2..8820938f 100644 --- a/libi3/get_colorpixel.c +++ b/libi3/get_colorpixel.c @@ -9,21 +9,28 @@ #include #include +#include "queue.h" #include "libi3.h" +struct Colorpixel { + char *hex; + uint32_t pixel; + + SLIST_ENTRY(Colorpixel) + colorpixels; +}; + +SLIST_HEAD(colorpixel_head, Colorpixel) +colorpixels; + /* - * Returns the colorpixel to use for the given hex color (think of HTML). Only - * works for true-color (vast majority of cases) at the moment, avoiding a - * roundtrip to X11. + * Returns the colorpixel to use for the given hex color (think of HTML). * * The hex_color has to start with #, for example #FF00FF. * * NOTE that get_colorpixel() does _NOT_ check the given color code for validity. * This has to be done by the caller. * - * NOTE that this function may in the future rely on a global xcb_connection_t - * variable called 'conn' to be present. - * */ uint32_t get_colorpixel(const char *hex) { char strgroups[3][3] = { @@ -34,5 +41,43 @@ uint32_t get_colorpixel(const char *hex) { uint8_t g = strtol(strgroups[1], NULL, 16); uint8_t b = strtol(strgroups[2], NULL, 16); - return (0xFF << 24) | (r << 16 | g << 8 | b); + /* Shortcut: if our screen is true color, no need to do a roundtrip to X11 */ + if (root_screen->root_depth == 24 || root_screen->root_depth == 32) { + return (0xFF << 24) | (r << 16 | g << 8 | b); + } + + /* Lookup this colorpixel in the cache */ + struct Colorpixel *colorpixel; + SLIST_FOREACH(colorpixel, &(colorpixels), colorpixels) { + if (strcmp(colorpixel->hex, hex) == 0) + return colorpixel->pixel; + } + +#define RGB_8_TO_16(i) (65535 * ((i)&0xFF) / 255) + int r16 = RGB_8_TO_16(r); + int g16 = RGB_8_TO_16(g); + int b16 = RGB_8_TO_16(b); + + xcb_alloc_color_reply_t *reply; + + reply = xcb_alloc_color_reply(conn, xcb_alloc_color(conn, root_screen->default_colormap, + r16, g16, b16), + NULL); + + if (!reply) { + LOG("Could not allocate color\n"); + exit(1); + } + + uint32_t pixel = reply->pixel; + free(reply); + + /* Store the result in the cache */ + struct Colorpixel *cache_pixel = scalloc(1, sizeof(struct Colorpixel)); + cache_pixel->hex = sstrdup(hex); + cache_pixel->pixel = pixel; + + SLIST_INSERT_HEAD(&(colorpixels), cache_pixel, colorpixels); + + return pixel; } From 981c64667c73d22c446a01ab97224017c541f754 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Wed, 30 Dec 2015 15:59:53 +0100 Subject: [PATCH 109/187] docs/ipc: add https://github.com/drmgc/i3ipcpp --- docs/ipc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/ipc b/docs/ipc index 5231c5ec..dc4e6947 100644 --- a/docs/ipc +++ b/docs/ipc @@ -826,6 +826,8 @@ know): C:: * i3 includes a headerfile +i3/ipc.h+ which provides you all constants. * https://github.com/acrisci/i3ipc-glib +C++:: + * https://github.com/drmgc/i3ipcpp Go:: * https://github.com/proxypoke/i3ipc JavaScript:: From c099381632ba37a81cce603204038cef6e13f024 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ingo=20B=C3=BCrk?= Date: Sun, 27 Dec 2015 19:03:31 -0500 Subject: [PATCH 110/187] Added test for #2097. fixes #2115 --- testcases/lib/i3test/XTEST.pm | 36 +++++++++++++++--- testcases/t/262-root-window-mouse-binding.t | 42 +++++++++++++++++++++ 2 files changed, 73 insertions(+), 5 deletions(-) create mode 100644 testcases/t/262-root-window-mouse-binding.t diff --git a/testcases/lib/i3test/XTEST.pm b/testcases/lib/i3test/XTEST.pm index 065c8a35..9ec083b1 100644 --- a/testcases/lib/i3test/XTEST.pm +++ b/testcases/lib/i3test/XTEST.pm @@ -15,6 +15,8 @@ our @EXPORT = qw( set_xkb_group xtest_key_press xtest_key_release + xtest_button_press + xtest_button_release listen_for_binding start_binding_capture binding_events @@ -107,7 +109,7 @@ bool set_xkb_group(int group) { return true; } -bool xtest_key(int type, int detail) { +bool xtest_input(int type, int detail, int x, int y) { xcb_generic_error_t *err; xcb_void_cookie_t cookie; @@ -117,8 +119,8 @@ bool xtest_key(int type, int detail) { detail, /* detail */ XCB_CURRENT_TIME, /* time */ XCB_NONE, /* root */ - 0, /* rootX */ - 0, /* rootY */ + x, /* rootX */ + y, /* rootY */ XCB_NONE); /* deviceid */ if ((err = xcb_request_check(conn, cookie)) != NULL) { fprintf(stderr, "X error code %d\n", err->error_code); @@ -128,6 +130,10 @@ bool xtest_key(int type, int detail) { return true; } +bool xtest_key(int type, int detail) { + return xtest_input(type, detail, 0, 0); +} + bool xtest_key_press(int detail) { return xtest_key(XCB_KEY_PRESS, detail); } @@ -136,6 +142,14 @@ bool xtest_key_release(int detail) { return xtest_key(XCB_KEY_RELEASE, detail); } +bool xtest_button_press(int button, int x, int y) { + return xtest_input(XCB_BUTTON_PRESS, button, x, y); +} + +bool xtest_button_release(int button, int x, int y) { + return xtest_input(XCB_BUTTON_RELEASE, button, x, y); +} + END_OF_C_CODE sub import { @@ -240,14 +254,26 @@ Returns false when there was an X11 error changing the group, true otherwise. Sends a KeyPress event via XTEST, with the specified C<$detail>, i.e. key code. Use C to find key codes. -Returns false when there was an X11 error changing the group, true otherwise. +Returns false when there was an X11 error, true otherwise. =head2 xtest_key_release($detail) Sends a KeyRelease event via XTEST, with the specified C<$detail>, i.e. key code. Use C to find key codes. -Returns false when there was an X11 error changing the group, true otherwise. +Returns false when there was an X11 error, true otherwise. + +=head2 xtest_button_press($button, $x, $y) + +Sends a ButtonPress event via XTEST, with the specified C<$button>. + +Returns false when there was an X11 error, true otherwise. + +=head2 xtest_button_release($button, $x, $y) + +Sends a ButtonRelease event via XTEST, with the specified C<$button>. + +Returns false when there was an X11 error, true otherwise. =head1 AUTHOR diff --git a/testcases/t/262-root-window-mouse-binding.t b/testcases/t/262-root-window-mouse-binding.t new file mode 100644 index 00000000..c8fd89ef --- /dev/null +++ b/testcases/t/262-root-window-mouse-binding.t @@ -0,0 +1,42 @@ +#!perl +# vim:ts=4:sw=4:expandtab +# +# Please read the following documents before working on tests: +# • http://build.i3wm.org/docs/testsuite.html +# (or docs/testsuite) +# +# • http://build.i3wm.org/docs/lib-i3test.html +# (alternatively: perldoc ./testcases/lib/i3test.pm) +# +# • http://build.i3wm.org/docs/ipc.html +# (or docs/ipc) +# +# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# (unless you are already familiar with Perl) +# +# Verifies that mouse bindings work on the root window if +# --whole-window is set. +# Ticket: #2115 +use i3test i3_autostart => 0; +use i3test::XTEST; + +my $config = < Date: Sun, 3 Jan 2016 21:48:39 -0500 Subject: [PATCH 111/187] Migrate "xdotool click" in tests to XTEST. --- testcases/t/525-i3bar-mouse-bindings.t | 88 +++++++++++++------------- 1 file changed, 44 insertions(+), 44 deletions(-) diff --git a/testcases/t/525-i3bar-mouse-bindings.t b/testcases/t/525-i3bar-mouse-bindings.t index eabcad7a..b76ccaf2 100644 --- a/testcases/t/525-i3bar-mouse-bindings.t +++ b/testcases/t/525-i3bar-mouse-bindings.t @@ -17,6 +17,7 @@ # Ensures that mouse bindings on the i3bar work correctly. # Ticket: #1695 use i3test i3_autostart => 0; +use i3test::XTEST; my ($cv, $timer); sub reset_test { @@ -41,17 +42,13 @@ bar { } EOT -SKIP: { - qx(command -v xdotool 2> /dev/null); - skip 'xdotool is required for this test', 1 if $?; +my $pid = launch_with_config($config); +my $i3 = i3(get_socket_path()); +$i3->connect()->recv; +my $ws = fresh_workspace; - my $pid = launch_with_config($config); - my $i3 = i3(get_socket_path()); - $i3->connect()->recv; - my $ws = fresh_workspace; - - reset_test; - $i3->subscribe({ +reset_test; +$i3->subscribe({ window => sub { my ($event) = @_; if ($event->{change} eq 'focus') { @@ -60,45 +57,48 @@ SKIP: { }, })->recv; - my $left = open_window; - my $right = open_window; - sync_with_i3; - my $con = $cv->recv; - is($con->{window}, $right->{id}, 'focus is initially on the right container'); - reset_test; +my $left = open_window; +my $right = open_window; +sync_with_i3; +my $con = $cv->recv; +is($con->{window}, $right->{id}, 'focus is initially on the right container'); +reset_test; - qx(xdotool mousemove 3 3 click 1); - sync_with_i3; - $con = $cv->recv; - is($con->{window}, $left->{id}, 'button 1 moves focus left'); - reset_test; +xtest_button_press(1, 3, 3); +xtest_button_release(1, 3, 3); +sync_with_i3; +$con = $cv->recv; +is($con->{window}, $left->{id}, 'button 1 moves focus left'); +reset_test; - qx(xdotool mousemove 3 3 click 2); - sync_with_i3; - $con = $cv->recv; - is($con->{window}, $right->{id}, 'button 2 moves focus right'); - reset_test; +xtest_button_press(2, 3, 3); +xtest_button_release(2, 3, 3); +sync_with_i3; +$con = $cv->recv; +is($con->{window}, $right->{id}, 'button 2 moves focus right'); +reset_test; - qx(xdotool mousemove 3 3 click 3); - sync_with_i3; - $con = $cv->recv; - is($con->{window}, $left->{id}, 'button 3 moves focus left'); - reset_test; +xtest_button_press(3, 3, 3); +xtest_button_release(3, 3, 3); +sync_with_i3; +$con = $cv->recv; +is($con->{window}, $left->{id}, 'button 3 moves focus left'); +reset_test; - qx(xdotool mousemove 3 3 click 4); - sync_with_i3; - $con = $cv->recv; - is($con->{window}, $right->{id}, 'button 4 moves focus right'); - reset_test; +xtest_button_press(4, 3, 3); +xtest_button_release(4, 3, 3); +sync_with_i3; +$con = $cv->recv; +is($con->{window}, $right->{id}, 'button 4 moves focus right'); +reset_test; - qx(xdotool mousemove 3 3 click 5); - sync_with_i3; - $con = $cv->recv; - is($con->{window}, $left->{id}, 'button 5 moves focus left'); - reset_test; +xtest_button_press(5, 3, 3); +xtest_button_release(5, 3, 3); +sync_with_i3; +$con = $cv->recv; +is($con->{window}, $left->{id}, 'button 5 moves focus left'); +reset_test; - exit_gracefully($pid); - -} +exit_gracefully($pid); done_testing; From a9f31b9dc98f4f9cada46c48b5caa97de8bef34b Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 4 Jan 2016 09:26:45 +0100 Subject: [PATCH 112/187] i3-nagbar: explicitly set cursor using libxcursor if available See commit b1f1da432 for context. fixes #2114 --- i3-nagbar/i3-nagbar.mk | 4 ++-- i3-nagbar/main.c | 29 +++++++++++++++++++++++++++-- 2 files changed, 29 insertions(+), 4 deletions(-) diff --git a/i3-nagbar/i3-nagbar.mk b/i3-nagbar/i3-nagbar.mk index aba3c09a..b10e389e 100644 --- a/i3-nagbar/i3-nagbar.mk +++ b/i3-nagbar/i3-nagbar.mk @@ -4,8 +4,8 @@ CLEAN_TARGETS += clean-i3-nagbar i3_nagbar_SOURCES := $(wildcard i3-nagbar/*.c) i3_nagbar_HEADERS := $(wildcard i3-nagbar/*.h) -i3_nagbar_CFLAGS = $(XCB_CFLAGS) $(XCB_WM_CFLAGS) $(PANGO_CFLAGS) -i3_nagbar_LIBS = $(XCB_LIBS) $(XCB_WM_LIBS) $(PANGO_LIBS) +i3_nagbar_CFLAGS = $(XCB_CFLAGS) $(XCB_CURSOR_CFLAGS) $(XCB_WM_CFLAGS) $(PANGO_CFLAGS) +i3_nagbar_LIBS = $(XCB_LIBS) $(XCB_CURSOR_LIBS) $(XCB_WM_LIBS) $(PANGO_LIBS) i3_nagbar_OBJECTS := $(i3_nagbar_SOURCES:.c=.o) diff --git a/i3-nagbar/main.c b/i3-nagbar/main.c index cbbe9ef8..38740774 100644 --- a/i3-nagbar/main.c +++ b/i3-nagbar/main.c @@ -28,10 +28,15 @@ #include #include #include +#include #include "libi3.h" #include "i3-nagbar.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 + static char *argv0 = NULL; typedef struct { @@ -467,6 +472,25 @@ int main(int argc, char *argv[]) { xcb_rectangle_t win_pos = get_window_position(); + xcb_cursor_t cursor; + 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(conn); + i3Font cursor_font = load_font("cursor", false); + xcb_create_glyph_cursor( + conn, + 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); + } + /* Open an input window */ win = xcb_generate_id(conn); @@ -479,13 +503,14 @@ int main(int argc, char *argv[]) { 0, /* x11 border = 0, we draw our own */ XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_WINDOW_CLASS_COPY_FROM_PARENT, /* copy visual from parent */ - XCB_CW_BACK_PIXEL | XCB_CW_EVENT_MASK, + XCB_CW_BACK_PIXEL | XCB_CW_EVENT_MASK | XCB_CW_CURSOR, (uint32_t[]){ 0, /* back pixel: black */ XCB_EVENT_MASK_EXPOSURE | XCB_EVENT_MASK_STRUCTURE_NOTIFY | XCB_EVENT_MASK_BUTTON_PRESS | - XCB_EVENT_MASK_BUTTON_RELEASE}); + XCB_EVENT_MASK_BUTTON_RELEASE, + cursor}); /* Map the window (make it visible) */ xcb_map_window(conn, win); From 8bfd06c3dd6e15b031c4a1bca82499e449f5121c Mon Sep 17 00:00:00 2001 From: Johannes Lange Date: Mon, 4 Jan 2016 17:31:47 +0100 Subject: [PATCH 113/187] added "toggle" option to "split" command as requested in #1814 --- docs/userguide | 20 ++++++++++++-------- include/commands.h | 2 +- parser-specs/commands.spec | 4 ++-- src/commands.c | 19 +++++++++++++++++-- testcases/t/122-split.t | 30 ++++++++++++++++++++++++++++++ 5 files changed, 62 insertions(+), 13 deletions(-) diff --git a/docs/userguide b/docs/userguide index 89791bf7..59833ed5 100644 --- a/docs/userguide +++ b/docs/userguide @@ -1750,20 +1750,24 @@ get placed below the current one (splitv). If you apply this command to a split container with the same orientation, nothing will happen. If you use a different orientation, the split container’s -orientation will be changed (if it does not have more than one window). Use -+layout toggle split+ to change the layout of any split container from splitv -to splith or vice-versa. +orientation will be changed (if it does not have more than one window). +The +toggle+ option will toggle the orientation of the split container if it +contains a single window. Otherwise it makes the current window a split +container with opposite orientation compared to the parent container. +Use +layout toggle split+ to change the layout of any split container from +splitv to splith or vice-versa. *Syntax*: -------------------------- -split vertical|horizontal -------------------------- +-------------------------------- +split vertical|horizontal|toggle +-------------------------------- *Example*: ------------------------------- +------------------------------- bindsym $mod+v split vertical bindsym $mod+h split horizontal ------------------------------- +bindsym $mod+t split toggle +------------------------------- === Manipulating layout diff --git a/include/commands.h b/include/commands.h index 5e8dd05c..968bfbef 100644 --- a/include/commands.h +++ b/include/commands.h @@ -157,7 +157,7 @@ void cmd_floating(I3_CMD, const char *floating_mode); void cmd_move_workspace_to_output(I3_CMD, const char *name); /** - * Implementation of 'split v|h|vertical|horizontal'. + * Implementation of 'split v|h|t|vertical|horizontal|toggle'. * */ void cmd_split(I3_CMD, const char *direction); diff --git a/parser-specs/commands.spec b/parser-specs/commands.spec index 98812132..e6480a35 100644 --- a/parser-specs/commands.spec +++ b/parser-specs/commands.spec @@ -191,9 +191,9 @@ state STICKY: action = 'enable', 'disable', 'toggle' -> call cmd_sticky($action) -# split v|h|vertical|horizontal +# split v|h|t|vertical|horizontal|toggle state SPLIT: - direction = 'horizontal', 'vertical', 'v', 'h' + direction = 'horizontal', 'vertical', 'toggle', 'v', 'h', 't' -> call cmd_split($direction) # floating enable|disable|toggle diff --git a/src/commands.c b/src/commands.c index 579b5e0e..e9e5c7dd 100644 --- a/src/commands.c +++ b/src/commands.c @@ -1228,7 +1228,7 @@ void cmd_move_workspace_to_output(I3_CMD, const char *name) { } /* - * Implementation of 'split v|h|vertical|horizontal'. + * Implementation of 'split v|h|t|vertical|horizontal|toggle'. * */ void cmd_split(I3_CMD, const char *direction) { @@ -1243,7 +1243,22 @@ void cmd_split(I3_CMD, const char *direction) { } DLOG("matching: %p / %s\n", current->con, current->con->name); - tree_split(current->con, (direction[0] == 'v' ? VERT : HORIZ)); + if (direction[0] == 't') { + layout_t current_layout; + if (current->con->type == CT_WORKSPACE) { + current_layout = current->con->layout; + } else { + current_layout = current->con->parent->layout; + } + /* toggling split orientation */ + if (current_layout == L_SPLITH) { + tree_split(current->con, VERT); + } else { + tree_split(current->con, HORIZ); + } + } else { + tree_split(current->con, (direction[0] == 'v' ? VERT : HORIZ)); + } } cmd_output->needs_tree_render = true; diff --git a/testcases/t/122-split.t b/testcases/t/122-split.t index e9d06938..6fd9b0e7 100644 --- a/testcases/t/122-split.t +++ b/testcases/t/122-split.t @@ -178,4 +178,34 @@ is(@{$content}, 2, 'two containers on workspace'); is(@{$fst->{nodes}}, 2, 'first child has two children'); is(@{$snd->{nodes}}, 0, 'second child has no children'); +###################################################################### +# Test split toggle +###################################################################### + +$tmp = fresh_workspace; +cmd 'split h'; +cmd 'split toggle'; +$ws = get_ws($tmp); +is($ws->{layout}, 'splitv', 'toggled workspace split'); + +$tmp = fresh_workspace; +cmd 'split h'; +cmd 'split toggle'; +cmd 'split toggle'; +$ws = get_ws($tmp); +is($ws->{layout}, 'splith', 'toggled workspace back and forth'); + +cmd 'open'; +cmd 'open'; +cmd 'split toggle'; + +$content = get_ws_content($tmp); +my $second = $content->[1]; +is($second->{layout}, 'splitv', 'toggled container orientation to vertical'); + +cmd 'split toggle'; +$content = get_ws_content($tmp); +$second = $content->[1]; +is($second->{layout}, 'splith', 'toggled container orientation back to horizontal'); + done_testing; From 6fefe836d465f2b1f045a73335c71403d7cc568f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ingo=20B=C3=BCrk?= Date: Mon, 4 Jan 2016 18:39:40 -0500 Subject: [PATCH 114/187] Fix memory leaks in title_format. This fixes three memory leaks that were found during the implementation of #2120 so that they can be fixed in a bugfix release. relates to #2143 --- include/util.h | 7 +++++++ src/util.c | 14 ++++++++++++++ src/window.c | 38 +++++++++++++++++++++++--------------- 3 files changed, 44 insertions(+), 15 deletions(-) diff --git a/include/util.h b/include/util.h index 01f732ca..2667df89 100644 --- a/include/util.h +++ b/include/util.h @@ -130,6 +130,13 @@ void *memmem(const void *l, size_t l_len, const void *s, size_t s_len); #endif +/** + * Escapes the given string if a pango font is currently used. + * If the string has to be escaped, the input string will be free'd. + * + */ +char *pango_escape_markup(char *input); + /** * Starts an i3-nagbar instance with the given parameters. Takes care of * handling SIGCHLD and killing i3-nagbar when i3 exits. diff --git a/src/util.c b/src/util.c index 7a73011d..67dc5c92 100644 --- a/src/util.c +++ b/src/util.c @@ -332,6 +332,20 @@ void *memmem(const void *l, size_t l_len, const void *s, size_t s_len) { #endif +/* + * Escapes the given string if a pango font is currently used. + * If the string has to be escaped, the input string will be free'd. + * + */ +char *pango_escape_markup(char *input) { + if (!font_is_pango()) + return input; + + char *escaped = g_markup_escape_text(input, -1); + FREE(input); + return escaped; +} + /* * Handler which will be called when we get a SIGCHLD for the nagbar, meaning * it exited (or could not be started, depending on the exit code). diff --git a/src/window.c b/src/window.c index eba15632..f787e078 100644 --- a/src/window.c +++ b/src/window.c @@ -66,8 +66,12 @@ void window_update_name(i3Window *win, xcb_get_property_reply_t *prop, bool befo i3string_free(win->name); win->name = i3string_from_utf8_with_length(xcb_get_property_value(prop), xcb_get_property_value_length(prop)); - if (win->title_format != NULL) - ewmh_update_visible_name(win->id, i3string_as_utf8(window_parse_title_format(win))); + + if (win->title_format != NULL) { + i3String *name = window_parse_title_format(win); + ewmh_update_visible_name(win->id, i3string_as_utf8(name)); + I3STRING_FREE(name); + } win->name_x_changed = true; LOG("_NET_WM_NAME changed to \"%s\"\n", i3string_as_utf8(win->name)); @@ -106,8 +110,11 @@ void window_update_name_legacy(i3Window *win, xcb_get_property_reply_t *prop, bo i3string_free(win->name); win->name = i3string_from_utf8_with_length(xcb_get_property_value(prop), xcb_get_property_value_length(prop)); - if (win->title_format != NULL) - ewmh_update_visible_name(win->id, i3string_as_utf8(window_parse_title_format(win))); + if (win->title_format != NULL) { + i3String *name = window_parse_title_format(win); + ewmh_update_visible_name(win->id, i3string_as_utf8(name)); + I3STRING_FREE(name); + } LOG("WM_NAME changed to \"%s\"\n", i3string_as_utf8(win->name)); LOG("Using legacy window title. Note that in order to get Unicode window " @@ -340,18 +347,14 @@ void window_update_motif_hints(i3Window *win, xcb_get_property_reply_t *prop, bo * */ i3String *window_parse_title_format(i3Window *win) { - /* We need to ensure that we only escape the window title if pango - * is used by the current font. */ - const bool pango_markup = font_is_pango(); - char *format = win->title_format; if (format == NULL) return i3string_copy(win->name); /* We initialize these lazily so we only escape them if really necessary. */ - const char *escaped_title = NULL; - const char *escaped_class = NULL; - const char *escaped_instance = NULL; + char *escaped_title = NULL; + char *escaped_class = NULL; + char *escaped_instance = NULL; /* We have to first iterate over the string to see how much buffer space * we need to allocate. */ @@ -359,19 +362,19 @@ i3String *window_parse_title_format(i3Window *win) { for (char *walk = format; *walk != '\0'; walk++) { if (STARTS_WITH(walk, "%title")) { if (escaped_title == NULL) - escaped_title = win->name == NULL ? "" : i3string_as_utf8(pango_markup ? i3string_escape_markup(win->name) : win->name); + escaped_title = pango_escape_markup(sstrdup((win->name == NULL) ? "" : i3string_as_utf8(win->name))); buffer_len = buffer_len - strlen("%title") + strlen(escaped_title); walk += strlen("%title") - 1; } else if (STARTS_WITH(walk, "%class")) { if (escaped_class == NULL) - escaped_class = pango_markup ? g_markup_escape_text(win->class_class, -1) : win->class_class; + escaped_class = pango_escape_markup(sstrdup((win->class_class == NULL) ? "" : win->class_class)); buffer_len = buffer_len - strlen("%class") + strlen(escaped_class); walk += strlen("%class") - 1; } else if (STARTS_WITH(walk, "%instance")) { if (escaped_instance == NULL) - escaped_instance = pango_markup ? g_markup_escape_text(win->class_instance, -1) : win->class_instance; + escaped_instance = pango_escape_markup(sstrdup((win->class_instance == NULL) ? "" : win->class_instance)); buffer_len = buffer_len - strlen("%instance") + strlen(escaped_instance); walk += strlen("%instance") - 1; @@ -403,6 +406,11 @@ i3String *window_parse_title_format(i3Window *win) { *outwalk = '\0'; i3String *formatted = i3string_from_utf8(buffer); - i3string_set_markup(formatted, pango_markup); + i3string_set_markup(formatted, font_is_pango()); + + FREE(escaped_title); + FREE(escaped_class); + FREE(escaped_instance); + return formatted; } From 034a12acad203750583cf95da9e7e97fe6610797 Mon Sep 17 00:00:00 2001 From: Lauri Tirkkonen Date: Mon, 28 Dec 2015 14:37:42 +0200 Subject: [PATCH 115/187] add decoration_border color for the actual client borders see https://github.com/i3/i3/pull/2136 --- docs/userguide | 22 +++++++++++----------- include/config.h | 1 + include/config_directives.h | 2 +- parser-specs/config.spec | 10 ++++++++-- src/config.c | 13 +++++++------ src/config_directives.c | 25 ++++++++++++++----------- src/x.c | 25 ++++++++++++++----------- testcases/t/201-config-parser.t | 18 +++++++++--------- 8 files changed, 65 insertions(+), 51 deletions(-) diff --git a/docs/userguide b/docs/userguide index 59833ed5..9bcabaa7 100644 --- a/docs/userguide +++ b/docs/userguide @@ -836,9 +836,9 @@ workspace "2: vim" output VGA1 You can change all colors which i3 uses to draw the window decorations. *Syntax*: ------------------------------------------------------- - ------------------------------------------------------- +------------------------------------------------------------------------- + +------------------------------------------------------------------------- Where colorclass can be one of: @@ -864,19 +864,19 @@ Colors are in HTML hex format (#rrggbb), see the following example: *Examples (default colors)*: --------------------------------------------------------- -# class border backgr. text indicator -client.focused #4c7899 #285577 #ffffff #2e9ef4 -client.focused_inactive #333333 #5f676a #ffffff #484e50 -client.unfocused #333333 #222222 #888888 #292d2e -client.urgent #2f343a #900000 #ffffff #900000 -client.placeholder #000000 #0c0c0c #ffffff #000000 +# class border backgr. text indicator decoration_border +client.focused #4c7899 #285577 #ffffff #2e9ef4 #285577 +client.focused_inactive #333333 #5f676a #ffffff #484e50 #5f676a +client.unfocused #333333 #222222 #888888 #292d2e #222222 +client.urgent #2f343a #900000 #ffffff #900000 #900000 +client.placeholder #000000 #0c0c0c #ffffff #000000 #0c0c0c client.background #ffffff --------------------------------------------------------- Note that for the window decorations, the color around the child window is the -background color, and the border color is only the two thin lines at the top of -the window. +"decoration_border", and "border" color is only the two thin lines around the +titlebar. The indicator color is used for indicating where a new window will be opened. For horizontal split containers, the right border will be painted in indicator diff --git a/include/config.h b/include/config.h index f9badf10..f4b1efd5 100644 --- a/include/config.h +++ b/include/config.h @@ -54,6 +54,7 @@ struct Colortriple { color_t background; color_t text; color_t indicator; + color_t decoration_border; }; /** diff --git a/include/config_directives.h b/include/config_directives.h index 97b8b424..fd8a4208 100644 --- a/include/config_directives.h +++ b/include/config_directives.h @@ -59,7 +59,7 @@ CFGFUN(no_focus); CFGFUN(ipc_socket, const char *path); CFGFUN(restart_state, const char *path); CFGFUN(popup_during_fullscreen, const char *value); -CFGFUN(color, const char *colorclass, const char *border, const char *background, const char *text, const char *indicator); +CFGFUN(color, const char *colorclass, const char *border, const char *background, const char *text, const char *indicator, const char *decoration_border); CFGFUN(color_single, const char *colorclass, const char *color); CFGFUN(floating_modifier, const char *modifiers); CFGFUN(new_window, const char *windowtype, const char *border, const long width); diff --git a/parser-specs/config.spec b/parser-specs/config.spec index 9dad79c0..1e1b52e2 100644 --- a/parser-specs/config.spec +++ b/parser-specs/config.spec @@ -282,9 +282,15 @@ state COLOR_TEXT: state COLOR_INDICATOR: indicator = word - -> call cfg_color($colorclass, $border, $background, $text, $indicator) + -> COLOR_DECORATION_BORDER end - -> call cfg_color($colorclass, $border, $background, $text, NULL) + -> call cfg_color($colorclass, $border, $background, $text, NULL, NULL) + +state COLOR_DECORATION_BORDER: + decoration_border = word + -> call cfg_color($colorclass, $border, $background, $text, $indicator, $decoration_border) + end + -> call cfg_color($colorclass, $border, $background, $text, $indicator, NULL) # [--no-startup-id] command state EXEC: diff --git a/src/config.c b/src/config.c index 833ea6b6..ef632128 100644 --- a/src/config.c +++ b/src/config.c @@ -189,12 +189,13 @@ void load_configuration(xcb_connection_t *conn, const char *override_configpath, memset(&config, 0, sizeof(config)); /* Initialize default colors */ -#define INIT_COLOR(x, cborder, cbackground, ctext, cindicator) \ - do { \ - x.border = draw_util_hex_to_color(cborder); \ - x.background = draw_util_hex_to_color(cbackground); \ - x.text = draw_util_hex_to_color(ctext); \ - x.indicator = draw_util_hex_to_color(cindicator); \ +#define INIT_COLOR(x, cborder, cbackground, ctext, cindicator) \ + do { \ + x.border = draw_util_hex_to_color(cborder); \ + x.background = draw_util_hex_to_color(cbackground); \ + x.text = draw_util_hex_to_color(ctext); \ + x.indicator = draw_util_hex_to_color(cindicator); \ + x.decoration_border = draw_util_hex_to_color(cbackground); \ } while (0) config.client.background = draw_util_hex_to_color("#000000"); diff --git a/src/config_directives.c b/src/config_directives.c index 0d32f008..fcc48094 100644 --- a/src/config_directives.c +++ b/src/config_directives.c @@ -335,17 +335,20 @@ CFGFUN(color_single, const char *colorclass, const char *color) { config.client.background = draw_util_hex_to_color(color); } -CFGFUN(color, const char *colorclass, const char *border, const char *background, const char *text, const char *indicator) { -#define APPLY_COLORS(classname) \ - do { \ - if (strcmp(colorclass, "client." #classname) == 0) { \ - config.client.classname.border = draw_util_hex_to_color(border); \ - config.client.classname.background = draw_util_hex_to_color(background); \ - config.client.classname.text = draw_util_hex_to_color(text); \ - if (indicator != NULL) { \ - config.client.classname.indicator = draw_util_hex_to_color(indicator); \ - } \ - } \ +CFGFUN(color, const char *colorclass, const char *border, const char *background, const char *text, const char *indicator, const char *decoration_border) { +#define APPLY_COLORS(classname) \ + do { \ + if (strcmp(colorclass, "client." #classname) == 0) { \ + config.client.classname.border = draw_util_hex_to_color(border); \ + config.client.classname.background = draw_util_hex_to_color(background); \ + config.client.classname.text = draw_util_hex_to_color(text); \ + if (indicator != NULL) { \ + config.client.classname.indicator = draw_util_hex_to_color(indicator); \ + } \ + if (decoration_border != NULL) { \ + config.client.classname.decoration_border = draw_util_hex_to_color(decoration_border); \ + } \ + } \ } while (0) APPLY_COLORS(focused_inactive); diff --git a/src/x.c b/src/x.c index 51b66c5d..bef0e22a 100644 --- a/src/x.c +++ b/src/x.c @@ -299,7 +299,7 @@ void x_window_kill(xcb_window_t window, kill_window_t kill_window) { free(event); } -static void x_draw_decoration_border(Con *con, struct deco_render_params *p) { +static void x_draw_title_border(Con *con, struct deco_render_params *p) { assert(con->parent != NULL); Rect *dr = &(con->deco_rect); @@ -344,7 +344,7 @@ static void x_draw_decoration_after_title(Con *con, struct deco_render_params *p } /* Redraw the border. */ - x_draw_decoration_border(con, p); + x_draw_title_border(con, p); } /* @@ -464,21 +464,24 @@ void x_draw_decoration(Con *con) { * rectangle because some childs are not freely resizable and we want * their background color to "shine through". */ if (!(borders_to_hide & ADJ_LEFT_SCREEN_EDGE)) { - draw_util_rectangle(conn, &(con->frame_buffer), p->color->background, - 0, 0, br.x, r->height); + draw_util_rectangle(conn, &(con->frame_buffer), p->color->decoration_border, 0, 0, br.x, r->height); } if (!(borders_to_hide & ADJ_RIGHT_SCREEN_EDGE)) { - draw_util_rectangle(conn, &(con->frame_buffer), p->color->background, - r->width + (br.width + br.x), 0, -(br.width + br.x), r->height); + draw_util_rectangle(conn, &(con->frame_buffer), + p->color->decoration_border, r->width + (br.width + br.x), 0, + -(br.width + br.x), r->height); } if (!(borders_to_hide & ADJ_LOWER_SCREEN_EDGE)) { - draw_util_rectangle(conn, &(con->frame_buffer), p->color->background, - br.x, r->height + (br.height + br.y), r->width + br.width, -(br.height + br.y)); + draw_util_rectangle(conn, &(con->frame_buffer), + p->color->decoration_border, br.x, r->height + (br.height + + br.y), + r->width + br.width, -(br.height + br.y)); } /* pixel border needs an additional line at the top */ if (p->border_style == BS_PIXEL && !(borders_to_hide & ADJ_UPPER_SCREEN_EDGE)) { - draw_util_rectangle(conn, &(con->frame_buffer), p->color->background, - br.x, 0, r->width + br.width, br.y); + draw_util_rectangle(conn, &(con->frame_buffer), + p->color->decoration_border, br.x, 0, r->width + br.width, + br.y); } /* Highlight the side of the border at which the next window will be @@ -521,7 +524,7 @@ void x_draw_decoration(Con *con) { con->deco_rect.x, con->deco_rect.y, con->deco_rect.width, con->deco_rect.height); /* 5: draw two unconnected horizontal lines in border color */ - x_draw_decoration_border(con, p); + x_draw_title_border(con, p); /* 6: draw the title */ int text_offset_y = (con->deco_rect.height - config.font.height) / 2; diff --git a/testcases/t/201-config-parser.t b/testcases/t/201-config-parser.t index 7b3a181e..e8835005 100644 --- a/testcases/t/201-config-parser.t +++ b/testcases/t/201-config-parser.t @@ -412,19 +412,19 @@ is(parser_calls($config), ################################################################################ $config = <<'EOT'; -client.focused #4c7899 #285577 #ffffff #2e9ef4 +client.focused #4c7899 #285577 #ffffff #2e9ef4 #b34d4c client.focused_inactive #333333 #5f676a #ffffff #484e50 client.unfocused #333333 #222222 #888888 #292d2e -client.urgent #2f343a #900000 #ffffff #900000 +client.urgent #2f343a #900000 #ffffff #900000 #c00000 client.placeholder #000000 #0c0c0c #ffffff #000000 EOT $expected = <<'EOT'; -cfg_color(client.focused, #4c7899, #285577, #ffffff, #2e9ef4) -cfg_color(client.focused_inactive, #333333, #5f676a, #ffffff, #484e50) -cfg_color(client.unfocused, #333333, #222222, #888888, #292d2e) -cfg_color(client.urgent, #2f343a, #900000, #ffffff, #900000) -cfg_color(client.placeholder, #000000, #0c0c0c, #ffffff, #000000) +cfg_color(client.focused, #4c7899, #285577, #ffffff, #2e9ef4, #b34d4c) +cfg_color(client.focused_inactive, #333333, #5f676a, #ffffff, #484e50, NULL) +cfg_color(client.unfocused, #333333, #222222, #888888, #292d2e, NULL) +cfg_color(client.urgent, #2f343a, #900000, #ffffff, #900000, #c00000) +cfg_color(client.placeholder, #000000, #0c0c0c, #ffffff, #000000, NULL) EOT is(parser_calls($config), @@ -449,7 +449,7 @@ ERROR: CONFIG: (in file ) ERROR: CONFIG: Line 1: hide_edge_border both ERROR: CONFIG: ^^^^^^^^^^^^^^^^^^^^^ ERROR: CONFIG: Line 2: client.focused #4c7899 #285577 #ffffff #2e9ef4 -cfg_color(client.focused, #4c7899, #285577, #ffffff, #2e9ef4) +cfg_color(client.focused, #4c7899, #285577, #ffffff, #2e9ef4, NULL) EOT $expected = $expected_all_tokens . $expected_end; @@ -469,7 +469,7 @@ ERROR: CONFIG: (in file ) ERROR: CONFIG: Line 1: hide_edge_borders FOOBAR ERROR: CONFIG: ^^^^^^ ERROR: CONFIG: Line 2: client.focused #4c7899 #285577 #ffffff #2e9ef4 -cfg_color(client.focused, #4c7899, #285577, #ffffff, #2e9ef4) +cfg_color(client.focused, #4c7899, #285577, #ffffff, #2e9ef4, NULL) EOT is(parser_calls($config), From 1f660a4cc401ecbeb4cc5244fb71b8285f9f4b65 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ingo=20B=C3=BCrk?= Date: Tue, 29 Dec 2015 12:01:51 -0500 Subject: [PATCH 116/187] Move title_format from window to container. This patch moves the title_format information from windows to containers. Furthermore, it allows correctly setting it on window-less containers and displays the title accordingly for split containers. We now also dump and read title_format in GET_TREE / during restarts. fixes #2120 --- docs/userguide | 5 ++- i3bar/include/util.h | 2 +- include/con.h | 6 +++ include/data.h | 5 ++- include/libi3.h | 14 ++++++ include/util.h | 2 +- include/window.h | 7 --- libi3/format_placeholders.c | 67 +++++++++++++++++++++++++++++ src/commands.c | 30 +++++++------ src/con.c | 44 +++++++++++++++++++ src/ipc.c | 5 +++ src/load_layout.c | 3 ++ src/util.c | 1 + src/window.c | 85 +++---------------------------------- src/x.c | 25 ++++++----- 15 files changed, 188 insertions(+), 113 deletions(-) create mode 100644 libi3/format_placeholders.c diff --git a/docs/userguide b/docs/userguide index 59833ed5..05e10da4 100644 --- a/docs/userguide +++ b/docs/userguide @@ -2215,7 +2215,10 @@ https://developer.gnome.org/pango/stable/PangoMarkupFormat.html[Pango markup] and the following placeholders which will be replaced: +%title+:: - The X11 window title (_NET_WM_NAME or WM_NAME as fallback). + For normal windows, this is the X11 window title (_NET_WM_NAME or WM_NAME + as fallback). When used on containers without a window (e.g., a split + container inside a tabbed/stacked layout), this will be the tree + representation of the container (e.g., "H[xterm xterm]"). +%class+:: The X11 window class (second part of WM_CLASS). This corresponds to the +class+ criterion, see <>. diff --git a/i3bar/include/util.h b/i3bar/include/util.h index ba08cf76..dfbfd6bf 100644 --- a/i3bar/include/util.h +++ b/i3bar/include/util.h @@ -15,7 +15,7 @@ #undef MIN #define MIN(x, y) ((x) < (y) ? (x) : (y)) -#define STARTS_WITH(string, len, needle) ((len >= strlen(needle)) && strncasecmp(string, needle, strlen(needle)) == 0) +#define STARTS_WITH(string, len, needle) (((len) >= strlen((needle))) && strncasecmp((string), (needle), strlen((needle))) == 0) /* Securely free p */ #define FREE(p) \ diff --git a/include/con.h b/include/con.h index db5fcfbf..5b48120b 100644 --- a/include/con.h +++ b/include/con.h @@ -433,3 +433,9 @@ char *con_get_tree_representation(Con *con); * */ void con_force_split_parents_redraw(Con *con); + +/** + * Returns the window title considering the current title format. + * + */ +i3String *con_parse_title_format(Con *con); diff --git a/include/data.h b/include/data.h index 9ccc2c2e..959068e0 100644 --- a/include/data.h +++ b/include/data.h @@ -376,8 +376,6 @@ struct Window { /** The name of the window. */ i3String *name; - /** The format with which the window's name should be displayed. */ - char *title_format; /** The WM_WINDOW_ROLE of this window (for example, the pidgin buddy window * sets "buddy list"). Useful to match specific windows in assignments or @@ -588,6 +586,9 @@ struct Con { char *name; + /** The format with which the window's name should be displayed. */ + char *title_format; + /* a sticky-group is an identifier which bundles several containers to a * group. The contents are shared between all of them, that is they are * displayed on whichever of the containers is currently visible */ diff --git a/include/libi3.h b/include/libi3.h index 0c69f1c0..9f6eff2b 100644 --- a/include/libi3.h +++ b/include/libi3.h @@ -504,6 +504,20 @@ char *get_config_path(const char *override_configpath, bool use_system_paths); int mkdirp(const char *path, mode_t mode); #endif +/** Helper structure for usage in format_placeholders(). */ +typedef struct placeholder_t { + /* The placeholder to be replaced, e.g., "%title". */ + char *name; + /* The value this placeholder should be replaced with. */ + char *value; +} placeholder_t; + +/** + * Replaces occurences of the defined placeholders in the format string. + * + */ +char *format_placeholders(char *format, placeholder_t *placeholders, int num); + #ifdef CAIRO_SUPPORT /* We need to flush cairo surfaces twice to avoid an assertion bug. See #1989 * and https://bugs.freedesktop.org/show_bug.cgi?id=92455. */ diff --git a/include/util.h b/include/util.h index 2667df89..28655178 100644 --- a/include/util.h +++ b/include/util.h @@ -20,7 +20,7 @@ if (pointer == NULL) \ die(__VA_ARGS__); \ } -#define STARTS_WITH(string, needle) (strncasecmp(string, needle, strlen(needle)) == 0) +#define STARTS_WITH(string, needle) (strncasecmp((string), (needle), strlen((needle))) == 0) #define CIRCLEQ_NEXT_OR_NULL(head, elm, field) (CIRCLEQ_NEXT(elm, field) != CIRCLEQ_END(head) ? CIRCLEQ_NEXT(elm, field) : NULL) #define CIRCLEQ_PREV_OR_NULL(head, elm, field) (CIRCLEQ_PREV(elm, field) != CIRCLEQ_END(head) ? CIRCLEQ_PREV(elm, field) : NULL) #define FOR_TABLE(workspace) \ diff --git a/include/window.h b/include/window.h index 395c9883..7a248277 100644 --- a/include/window.h +++ b/include/window.h @@ -81,10 +81,3 @@ void window_update_hints(i3Window *win, xcb_get_property_reply_t *prop, bool *ur * */ void window_update_motif_hints(i3Window *win, xcb_get_property_reply_t *prop, border_style_t *motif_border_style); - -/** - * Returns the window title considering the current title format. - * If no format is set, this will simply return the window's name. - * - */ -i3String *window_parse_title_format(i3Window *win); diff --git a/libi3/format_placeholders.c b/libi3/format_placeholders.c new file mode 100644 index 00000000..825cab5c --- /dev/null +++ b/libi3/format_placeholders.c @@ -0,0 +1,67 @@ +/* + * vim:ts=4:sw=4:expandtab + * + * i3 - an improved dynamic tiling window manager + * © 2009 Michael Stapelberg and contributors (see also: LICENSE) + * + */ +#include +#include +#include + +#include "libi3.h" + +#ifndef STARTS_WITH +#define STARTS_WITH(string, needle) (strncasecmp((string), (needle), strlen((needle))) == 0) +#endif + +/* + * Replaces occurences of the defined placeholders in the format string. + * + */ +char *format_placeholders(char *format, placeholder_t *placeholders, int num) { + if (format == NULL) + return NULL; + + /* We have to first iterate over the string to see how much buffer space + * we need to allocate. */ + int buffer_len = strlen(format) + 1; + for (char *walk = format; *walk != '\0'; walk++) { + for (int i = 0; i < num; i++) { + if (!STARTS_WITH(walk, placeholders[i].name)) + continue; + + buffer_len = buffer_len - strlen(placeholders[i].name) + strlen(placeholders[i].value); + walk += strlen(placeholders[i].name) - 1; + break; + } + } + + /* Now we can parse the format string. */ + char buffer[buffer_len]; + char *outwalk = buffer; + for (char *walk = format; *walk != '\0'; walk++) { + if (*walk != '%') { + *(outwalk++) = *walk; + continue; + } + + bool matched = false; + for (int i = 0; i < num; i++) { + if (!STARTS_WITH(walk, placeholders[i].name)) { + continue; + } + + matched = true; + outwalk += sprintf(outwalk, "%s", placeholders[i].value); + walk += strlen(placeholders[i].name) - 1; + break; + } + + if (!matched) + *(outwalk++) = *walk; + } + + *outwalk = '\0'; + return sstrdup(buffer); +} diff --git a/src/commands.c b/src/commands.c index e9e5c7dd..03ae0ae0 100644 --- a/src/commands.c +++ b/src/commands.c @@ -1926,27 +1926,33 @@ void cmd_title_format(I3_CMD, const char *format) { owindow *current; TAILQ_FOREACH(current, &owindows, owindows) { - if (current->con->window == NULL) - continue; - DLOG("setting title_format for %p / %s\n", current->con, current->con->name); - FREE(current->con->window->title_format); + FREE(current->con->title_format); /* If we only display the title without anything else, we can skip the parsing step, * so we remove the title format altogether. */ if (strcasecmp(format, "%title") != 0) { - current->con->window->title_format = sstrdup(format); + current->con->title_format = sstrdup(format); - i3String *formatted_title = window_parse_title_format(current->con->window); - ewmh_update_visible_name(current->con->window->id, i3string_as_utf8(formatted_title)); - I3STRING_FREE(formatted_title); + if (current->con->window != NULL) { + i3String *formatted_title = con_parse_title_format(current->con); + ewmh_update_visible_name(current->con->window->id, i3string_as_utf8(formatted_title)); + I3STRING_FREE(formatted_title); + } } else { - /* We can remove _NET_WM_VISIBLE_NAME since we don't display a custom title. */ - ewmh_update_visible_name(current->con->window->id, NULL); + if (current->con->window != NULL) { + /* We can remove _NET_WM_VISIBLE_NAME since we don't display a custom title. */ + ewmh_update_visible_name(current->con->window->id, NULL); + } } - /* Make sure the window title is redrawn immediately. */ - current->con->window->name_x_changed = true; + if (current->con->window != NULL) { + /* Make sure the window title is redrawn immediately. */ + current->con->window->name_x_changed = true; + } else { + /* For windowless containers we also need to force the redrawing. */ + FREE(current->con->deco_render_params); + } } cmd_output->needs_tree_render = true; diff --git a/src/con.c b/src/con.c index 24809efb..ccc8445e 100644 --- a/src/con.c +++ b/src/con.c @@ -2000,3 +2000,47 @@ char *con_get_tree_representation(Con *con) { return complete_buf; } + +/* + * Returns the container's title considering the current title format. + * + */ +i3String *con_parse_title_format(Con *con) { + assert(con->title_format != NULL); + + i3Window *win = con->window; + + /* We need to ensure that we only escape the window title if pango + * is used by the current font. */ + const bool pango_markup = font_is_pango(); + + char *title; + char *class; + char *instance; + if (win == NULL) { + title = pango_escape_markup(con_get_tree_representation(con)); + class = sstrdup("i3-frame"); + instance = sstrdup("i3-frame"); + } else { + title = pango_escape_markup(sstrdup((win->name == NULL) ? "" : i3string_as_utf8(win->name))); + class = pango_escape_markup(sstrdup((win->class_class == NULL) ? "" : win->class_class)); + instance = pango_escape_markup(sstrdup((win->class_instance == NULL) ? "" : win->class_instance)); + } + + placeholder_t placeholders[] = { + {.name = "%title", .value = title}, + {.name = "%class", .value = class}, + {.name = "%instance", .value = instance}}; + const size_t num = sizeof(placeholders) / sizeof(placeholder_t); + + char *formatted_str = format_placeholders(con->title_format, &placeholders[0], num); + i3String *formatted = i3string_from_utf8(formatted_str); + i3string_set_markup(formatted, pango_markup); + FREE(formatted_str); + + for (size_t i = 0; i < num; i++) { + FREE(placeholders[i].value); + } + + return formatted; +} diff --git a/src/ipc.c b/src/ipc.c index 276a737a..00b468fb 100644 --- a/src/ipc.c +++ b/src/ipc.c @@ -372,6 +372,11 @@ void dump_node(yajl_gen gen, struct Con *con, bool inplace_restart) { else y(null); + if (con->title_format != NULL) { + ystr("title_format"); + ystr(con->title_format); + } + if (con->type == CT_WORKSPACE) { ystr("num"); y(integer, con->num); diff --git a/src/load_layout.c b/src/load_layout.c index d9acd1ba..9856d078 100644 --- a/src/load_layout.c +++ b/src/load_layout.c @@ -264,6 +264,9 @@ static int json_string(void *ctx, const unsigned char *val, size_t len) { if (strcasecmp(last_key, "name") == 0) { json_node->name = scalloc(len + 1, 1); memcpy(json_node->name, val, len); + } else if (strcasecmp(last_key, "title_format") == 0) { + json_node->title_format = scalloc(len + 1, 1); + memcpy(json_node->title_format, val, len); } else if (strcasecmp(last_key, "sticky_group") == 0) { json_node->sticky_group = scalloc(len + 1, 1); memcpy(json_node->sticky_group, val, len); diff --git a/src/util.c b/src/util.c index 67dc5c92..35ce8b19 100644 --- a/src/util.c +++ b/src/util.c @@ -343,6 +343,7 @@ char *pango_escape_markup(char *input) { char *escaped = g_markup_escape_text(input, -1); FREE(input); + return escaped; } diff --git a/src/window.c b/src/window.c index f787e078..a86f77a2 100644 --- a/src/window.c +++ b/src/window.c @@ -67,8 +67,9 @@ void window_update_name(i3Window *win, xcb_get_property_reply_t *prop, bool befo win->name = i3string_from_utf8_with_length(xcb_get_property_value(prop), xcb_get_property_value_length(prop)); - if (win->title_format != NULL) { - i3String *name = window_parse_title_format(win); + Con *con = con_by_window_id(win->id); + if (con != NULL && con->title_format != NULL) { + i3String *name = con_parse_title_format(con); ewmh_update_visible_name(win->id, i3string_as_utf8(name)); I3STRING_FREE(name); } @@ -110,8 +111,10 @@ void window_update_name_legacy(i3Window *win, xcb_get_property_reply_t *prop, bo i3string_free(win->name); win->name = i3string_from_utf8_with_length(xcb_get_property_value(prop), xcb_get_property_value_length(prop)); - if (win->title_format != NULL) { - i3String *name = window_parse_title_format(win); + + Con *con = con_by_window_id(win->id); + if (con != NULL && con->title_format != NULL) { + i3String *name = con_parse_title_format(con); ewmh_update_visible_name(win->id, i3string_as_utf8(name)); I3STRING_FREE(name); } @@ -340,77 +343,3 @@ void window_update_motif_hints(i3Window *win, xcb_get_property_reply_t *prop, bo #undef MWM_DECOR_BORDER #undef MWM_DECOR_TITLE } - -/* - * Returns the window title considering the current title format. - * If no format is set, this will simply return the window's name. - * - */ -i3String *window_parse_title_format(i3Window *win) { - char *format = win->title_format; - if (format == NULL) - return i3string_copy(win->name); - - /* We initialize these lazily so we only escape them if really necessary. */ - char *escaped_title = NULL; - char *escaped_class = NULL; - char *escaped_instance = NULL; - - /* We have to first iterate over the string to see how much buffer space - * we need to allocate. */ - int buffer_len = strlen(format) + 1; - for (char *walk = format; *walk != '\0'; walk++) { - if (STARTS_WITH(walk, "%title")) { - if (escaped_title == NULL) - escaped_title = pango_escape_markup(sstrdup((win->name == NULL) ? "" : i3string_as_utf8(win->name))); - - buffer_len = buffer_len - strlen("%title") + strlen(escaped_title); - walk += strlen("%title") - 1; - } else if (STARTS_WITH(walk, "%class")) { - if (escaped_class == NULL) - escaped_class = pango_escape_markup(sstrdup((win->class_class == NULL) ? "" : win->class_class)); - - buffer_len = buffer_len - strlen("%class") + strlen(escaped_class); - walk += strlen("%class") - 1; - } else if (STARTS_WITH(walk, "%instance")) { - if (escaped_instance == NULL) - escaped_instance = pango_escape_markup(sstrdup((win->class_instance == NULL) ? "" : win->class_instance)); - - buffer_len = buffer_len - strlen("%instance") + strlen(escaped_instance); - walk += strlen("%instance") - 1; - } - } - - /* Now we can parse the format string. */ - char buffer[buffer_len]; - char *outwalk = buffer; - for (char *walk = format; *walk != '\0'; walk++) { - if (*walk != '%') { - *(outwalk++) = *walk; - continue; - } - - if (STARTS_WITH(walk + 1, "title")) { - outwalk += sprintf(outwalk, "%s", escaped_title); - walk += strlen("title"); - } else if (STARTS_WITH(walk + 1, "class")) { - outwalk += sprintf(outwalk, "%s", escaped_class); - walk += strlen("class"); - } else if (STARTS_WITH(walk + 1, "instance")) { - outwalk += sprintf(outwalk, "%s", escaped_instance); - walk += strlen("instance"); - } else { - *(outwalk++) = *walk; - } - } - *outwalk = '\0'; - - i3String *formatted = i3string_from_utf8(buffer); - i3string_set_markup(formatted, font_is_pango()); - - FREE(escaped_title); - FREE(escaped_class); - FREE(escaped_instance); - - return formatted; -} diff --git a/src/x.c b/src/x.c index 51b66c5d..c4cae170 100644 --- a/src/x.c +++ b/src/x.c @@ -528,20 +528,23 @@ void x_draw_decoration(Con *con) { struct Window *win = con->window; if (win == NULL) { - /* we have a split container which gets a representation - * of its children as title - */ - char *_title; - char *tree = con_get_tree_representation(con); - sasprintf(&_title, "i3: %s", tree); - free(tree); + i3String *title; + if (con->title_format == NULL) { + char *_title; + char *tree = con_get_tree_representation(con); + sasprintf(&_title, "i3: %s", tree); + free(tree); + + title = i3string_from_utf8(_title); + FREE(_title); + } else { + title = con_parse_title_format(con); + } - i3String *title = i3string_from_utf8(_title); draw_util_text(title, &(parent->frame_buffer), p->color->text, p->color->background, con->deco_rect.x + 2, con->deco_rect.y + text_offset_y, con->deco_rect.width - 2); - FREE(_title); I3STRING_FREE(title); goto after_title; @@ -599,12 +602,12 @@ void x_draw_decoration(Con *con) { FREE(formatted_mark); } - i3String *title = win->title_format == NULL ? win->name : window_parse_title_format(win); + i3String *title = con->title_format == NULL ? win->name : con_parse_title_format(con); draw_util_text(title, &(parent->frame_buffer), p->color->text, p->color->background, con->deco_rect.x + logical_px(2) + indent_px, con->deco_rect.y + text_offset_y, con->deco_rect.width - logical_px(2) - indent_px - mark_width - logical_px(2)); - if (win->title_format != NULL) + if (con->title_format != NULL) I3STRING_FREE(title); after_title: From b9b1a60b5d696e3eef041216a120bb3eb62827a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ingo=20B=C3=BCrk?= Date: Sat, 2 Jan 2016 19:11:55 -0500 Subject: [PATCH 117/187] Fix segfault when calling "i3 -C". Commit 287a0b4 introduced a segfault when validating the i3 config as the root_screen will not be set in this case, causing a null pointer dereference. fixes #2144 --- libi3/get_colorpixel.c | 2 +- testcases/complete-run.pl | 5 +-- testcases/lib/SocketActivation.pm | 18 +++++++++-- testcases/lib/StartXServer.pm | 15 ++++----- testcases/lib/i3test.pm | 26 ++++++++++++++++ testcases/lib/i3test/Util.pm | 47 +++++++++++++++++++++++++++++ testcases/t/171-config-migrate.t | 7 ----- testcases/t/262-config-validation.t | 39 ++++++++++++++++++++++++ 8 files changed, 138 insertions(+), 21 deletions(-) create mode 100644 testcases/lib/i3test/Util.pm create mode 100644 testcases/t/262-config-validation.t diff --git a/libi3/get_colorpixel.c b/libi3/get_colorpixel.c index 8820938f..a0a9e345 100644 --- a/libi3/get_colorpixel.c +++ b/libi3/get_colorpixel.c @@ -42,7 +42,7 @@ uint32_t get_colorpixel(const char *hex) { uint8_t b = strtol(strgroups[2], NULL, 16); /* Shortcut: if our screen is true color, no need to do a roundtrip to X11 */ - if (root_screen->root_depth == 24 || root_screen->root_depth == 32) { + if (root_screen == NULL || root_screen->root_depth == 24 || root_screen->root_depth == 32) { return (0xFF << 24) | (r << 16 | g << 8 | b); } diff --git a/testcases/complete-run.pl b/testcases/complete-run.pl index c1244e08..4657a2ec 100755 --- a/testcases/complete-run.pl +++ b/testcases/complete-run.pl @@ -30,6 +30,7 @@ BEGIN { # these are shipped with the testsuite use lib $dirname . 'lib'; +use i3test::Util qw(slurp); use StartXServer; use StatusLine; use TestWorker; @@ -156,7 +157,7 @@ for my $display (@displays) { # Read previous timing information, if available. We will be able to roughly # predict the test duration and schedule a good order for the tests. -my $timingsjson = StartXServer::slurp('.last_run_timings.json'); +my $timingsjson = slurp('.last_run_timings.json') if -e '.last_run_timings.json'; %timings = %{decode_json($timingsjson)} if length($timingsjson) > 0; # Re-order the files so that those which took the longest time in the previous @@ -245,7 +246,7 @@ printf("\t%s with %.2f seconds\n", $_, $timings{$_}) if ($numtests == 1) { say ''; say 'Test output:'; - say StartXServer::slurp($logfile); + say slurp($logfile); } END { cleanup() } diff --git a/testcases/lib/SocketActivation.pm b/testcases/lib/SocketActivation.pm index 83ca9255..b58707a4 100644 --- a/testcases/lib/SocketActivation.pm +++ b/testcases/lib/SocketActivation.pm @@ -96,8 +96,13 @@ sub activate_i3 { # the interactive signalhandler to make it crash immediately instead. # Also disable logging to SHM since we redirect the logs anyways. # Force Xinerama because we use Xdmx for multi-monitor tests. - my $i3cmd = abs_path("../i3") . q| -V -d all --disable-signalhandler| . - q| --shmlog-size=0 --force-xinerama|; + my $i3cmd = abs_path("../i3") . q| --shmlog-size=0 --disable-signalhandler --force-xinerama|; + if (!$args{validate_config}) { + # We only set logging if i3 is actually started, but not if we only + # validate the config file. This is to keep logging to a minimum as + # such a test will likely want to inspect the log file. + $i3cmd .= q| -V -d all|; + } # For convenience: my $outdir = $args{outdir}; @@ -107,6 +112,10 @@ sub activate_i3 { $i3cmd .= ' -L ' . abs_path('restart-state.golden'); } + if ($args{validate_config}) { + $i3cmd .= ' -C'; + } + if ($args{valgrind}) { $i3cmd = qq|valgrind --log-file="$outdir/valgrind-for-$test.log" | . @@ -149,6 +158,11 @@ sub activate_i3 { # descriptor on the listening socket. $socket->close; + if ($args{validate_config}) { + $args{cv}->send(1); + return $pid; + } + # We now connect (will succeed immediately) and send a request afterwards. # As soon as the reply is there, i3 is considered ready. my $cl = IO::Socket::UNIX->new(Peer => $args{unix_socket_path}); diff --git a/testcases/lib/StartXServer.pm b/testcases/lib/StartXServer.pm index 032f58c6..86a37d92 100644 --- a/testcases/lib/StartXServer.pm +++ b/testcases/lib/StartXServer.pm @@ -5,6 +5,7 @@ use strict; use warnings; use Exporter 'import'; use Time::HiRes qw(sleep); +use i3test::Util qw(slurp); use v5.10; our @EXPORT = qw(start_xserver); @@ -12,13 +13,6 @@ our @EXPORT = qw(start_xserver); my @pids; my $x_socketpath = '/tmp/.X11-unix/X'; -# reads in a whole file -sub slurp { - open(my $fh, '<', shift) or return ''; - local $/; - <$fh>; -} - # forks an X server process sub fork_xserver { my $keep_xserver_output = shift; @@ -86,8 +80,11 @@ sub start_xserver { # Yeah, I know it’s non-standard, but Perl’s POSIX module doesn’t have # _SC_NPROCESSORS_CONF. - my $cpuinfo = slurp('/proc/cpuinfo'); - my $num_cores = scalar grep { /model name/ } split("\n", $cpuinfo); + my $num_cores; + if (-e '/proc/cpuinfo') { + my $cpuinfo = slurp('/proc/cpuinfo'); + $num_cores = scalar grep { /model name/ } split("\n", $cpuinfo); + } # If /proc/cpuinfo does not exist, we fall back to 2 cores. $num_cores ||= 2; diff --git a/testcases/lib/i3test.pm b/testcases/lib/i3test.pm index ac1a26ca..98486122 100644 --- a/testcases/lib/i3test.pm +++ b/testcases/lib/i3test.pm @@ -13,6 +13,7 @@ use Time::HiRes qw(sleep); use Cwd qw(abs_path); use Scalar::Util qw(blessed); use SocketActivation; +use i3test::Util qw(slurp); use v5.10; @@ -39,6 +40,7 @@ our @EXPORT = qw( focused_ws get_socket_path launch_with_config + get_i3_log wait_for_event wait_for_map wait_for_unmap @@ -827,6 +829,7 @@ sub launch_with_config { $tmp_socket_path = "/tmp/nested-$ENV{DISPLAY}"; $args{dont_create_temp_dir} //= 0; + $args{validate_config} //= 0; my ($fh, $tmpfile) = tempfile("i3-cfg-for-$ENV{TESTNAME}-XXXXX", UNLINK => 1); @@ -857,8 +860,21 @@ sub launch_with_config { restart => $ENV{RESTART}, cv => $cv, dont_create_temp_dir => $args{dont_create_temp_dir}, + validate_config => $args{validate_config}, ); + # If we called i3 with -C, we wait for it to exit and then return as + # there's nothing else we need to do. + if ($args{validate_config}) { + $cv->recv; + waitpid $i3_pid, 0; + + # We need this since exit_gracefully will not be called in this case. + undef $i3_pid; + + return ${^CHILD_ERROR_NATIVE}; + } + # force update of the cached socket path in lib/i3test # as soon as i3 has started $cv->cb(sub { get_socket_path(0) }); @@ -871,6 +887,16 @@ sub launch_with_config { return $i3_pid; } +=head2 get_i3_log + +Returns the content of the log file for the current test. + +=cut +sub get_i3_log { + my $logfile = "$ENV{OUTDIR}/i3-log-for-$ENV{TESTNAME}"; + return slurp($logfile); +} + =head1 AUTHOR Michael Stapelberg diff --git a/testcases/lib/i3test/Util.pm b/testcases/lib/i3test/Util.pm new file mode 100644 index 00000000..74913681 --- /dev/null +++ b/testcases/lib/i3test/Util.pm @@ -0,0 +1,47 @@ +package i3test::Util; +# vim:ts=4:sw=4:expandtab + +use strict; +use warnings; +use v5.10; + +use Exporter qw(import); +our @EXPORT = qw( + slurp +); + +=encoding utf-8 + +=head1 NAME + +i3test::Util - General utility functions + +=cut + +=head1 EXPORT + +=cut + +=head2 slurp($fn) + +Reads the entire file specified in the arguments and returns the content. + +=cut +sub slurp { + my ($file) = @_; + my $content = do { + local $/ = undef; + open my $fh, "<", $file or die "could not open $file: $!"; + <$fh>; + }; + + return $content; +} + +=head1 AUTHOR + +Michael Stapelberg + +=cut + +1 diff --git a/testcases/t/171-config-migrate.t b/testcases/t/171-config-migrate.t index d8f9d289..d098ae58 100644 --- a/testcases/t/171-config-migrate.t +++ b/testcases/t/171-config-migrate.t @@ -22,13 +22,6 @@ use Cwd qw(abs_path); use File::Temp qw(tempfile tempdir); use v5.10; -# reads in a whole file -sub slurp { - open my $fh, '<', shift; - local $/; - <$fh>; -} - sub migrate_config { my ($config) = @_; diff --git a/testcases/t/262-config-validation.t b/testcases/t/262-config-validation.t new file mode 100644 index 00000000..3a3e42dc --- /dev/null +++ b/testcases/t/262-config-validation.t @@ -0,0 +1,39 @@ +#!perl +# vim:ts=4:sw=4:expandtab +# +# Please read the following documents before working on tests: +# • http://build.i3wm.org/docs/testsuite.html +# (or docs/testsuite) +# +# • http://build.i3wm.org/docs/lib-i3test.html +# (alternatively: perldoc ./testcases/lib/i3test.pm) +# +# • http://build.i3wm.org/docs/ipc.html +# (or docs/ipc) +# +# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# (unless you are already familiar with Perl) +# +# Ensures that calling i3 with the -C switch works (smoke test). +# Ticket: #2144 +use i3test i3_autostart => 0; +use POSIX ":sys_wait_h"; +use Time::HiRes qw(sleep); + +my $config = < 1); +isnt($exit_code, 0, 'i3 exited with an error code'); + +my $log = get_i3_log; + +# We don't care so much about the output (there are tests for this), but rather +# that we got correct output at all instead of, e.g., a segfault. +ok($log =~ /Expected one of these tokens/, 'an invalid config token was found'); + +done_testing; From bcee585e17b3968a1967fc3d55e984fc909d355b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ingo=20B=C3=BCrk?= Date: Wed, 6 Jan 2016 09:19:42 -0500 Subject: [PATCH 118/187] Use correct fallback color for decoration_border. The newly introduced decoration_border color incorrectly uses the default value for "background" as a fallback when not specified. Instead, it should use the user-specified "background" as a fallback. fixes #2149 --- src/config_directives.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/config_directives.c b/src/config_directives.c index fcc48094..85cea4cf 100644 --- a/src/config_directives.c +++ b/src/config_directives.c @@ -347,6 +347,8 @@ CFGFUN(color, const char *colorclass, const char *border, const char *background } \ if (decoration_border != NULL) { \ config.client.classname.decoration_border = draw_util_hex_to_color(decoration_border); \ + } else { \ + config.client.classname.decoration_border = config.client.classname.background; \ } \ } \ } while (0) From 54738f6ec89db5b8ed78bdf1f30927281fdd473e Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 8 Jan 2016 19:56:32 +0100 Subject: [PATCH 119/187] bindings.h: mark DEFAULT_BINDING_MODE as external This prevents the following linker warning (only when compiling with -fsanitize=address): /usr/bin/ld: Warning: size of symbol `DEFAULT_BINDING_MODE' changed from 8 in src/resize.o to 64 in src/bindings.o --- include/bindings.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/bindings.h b/include/bindings.h index da81de24..9b14e988 100644 --- a/include/bindings.h +++ b/include/bindings.h @@ -15,7 +15,7 @@ extern pid_t command_error_nagbar_pid; * The name of the default mode. * */ -const char *DEFAULT_BINDING_MODE; +extern const char *DEFAULT_BINDING_MODE; /** * Adds a binding from config parameters given as strings and returns a From d155496915d6417bb1cfd2cbc8a47dcd969b641a Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 8 Jan 2016 20:15:34 +0100 Subject: [PATCH 120/187] root_atom_contents: properly clean up in all cases --- libi3/root_atom_contents.c | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/libi3/root_atom_contents.c b/libi3/root_atom_contents.c index d91f1e15..8d26c307 100644 --- a/libi3/root_atom_contents.c +++ b/libi3/root_atom_contents.c @@ -31,14 +31,15 @@ char *root_atom_contents(const char *atomname, xcb_connection_t *provided_conn, int screen) { xcb_intern_atom_cookie_t atom_cookie; xcb_intern_atom_reply_t *atom_reply; - char *content; + char *content = NULL; size_t content_max_words = 256; xcb_connection_t *conn = provided_conn; if (provided_conn == NULL && ((conn = xcb_connect(NULL, &screen)) == NULL || - xcb_connection_has_error(conn))) + xcb_connection_has_error(conn))) { return NULL; + } atom_cookie = xcb_intern_atom(conn, 0, strlen(atomname), atomname); @@ -46,8 +47,9 @@ char *root_atom_contents(const char *atomname, xcb_connection_t *provided_conn, xcb_window_t root = root_screen->root; atom_reply = xcb_intern_atom_reply(conn, atom_cookie, NULL); - if (atom_reply == NULL) - return NULL; + if (atom_reply == NULL) { + goto out_conn; + } xcb_get_property_cookie_t prop_cookie; xcb_get_property_reply_t *prop_reply; @@ -55,8 +57,7 @@ char *root_atom_contents(const char *atomname, xcb_connection_t *provided_conn, XCB_GET_PROPERTY_TYPE_ANY, 0, content_max_words); prop_reply = xcb_get_property_reply(conn, prop_cookie, NULL); if (prop_reply == NULL) { - free(atom_reply); - return NULL; + goto out_atom; } if (xcb_get_property_value_length(prop_reply) > 0 && prop_reply->bytes_after > 0) { /* We received an incomplete value. Ask again but with a properly @@ -68,14 +69,11 @@ char *root_atom_contents(const char *atomname, xcb_connection_t *provided_conn, XCB_GET_PROPERTY_TYPE_ANY, 0, content_max_words); prop_reply = xcb_get_property_reply(conn, prop_cookie, NULL); if (prop_reply == NULL) { - free(atom_reply); - return NULL; + goto out_atom; } } if (xcb_get_property_value_length(prop_reply) == 0) { - free(atom_reply); - free(prop_reply); - return NULL; + goto out; } if (prop_reply->type == XCB_ATOM_CARDINAL) { /* We treat a CARDINAL as a >= 32-bit unsigned int. The only CARDINAL @@ -85,9 +83,13 @@ char *root_atom_contents(const char *atomname, xcb_connection_t *provided_conn, sasprintf(&content, "%.*s", xcb_get_property_value_length(prop_reply), (char *)xcb_get_property_value(prop_reply)); } + +out: + free(prop_reply); +out_atom: + free(atom_reply); +out_conn: if (provided_conn == NULL) xcb_disconnect(conn); - free(atom_reply); - free(prop_reply); return content; } From 55bc2ae6a97b5678d7c63149465a7120f577b6f0 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 8 Jan 2016 20:17:34 +0100 Subject: [PATCH 121/187] i3-dump-log: explicitly free shmname Reduces memory usage and makes LeakSanitizer more quiet. --- i3-dump-log/main.c | 1 + 1 file changed, 1 insertion(+) diff --git a/i3-dump-log/main.c b/i3-dump-log/main.c index 137554a4..9d4eefc7 100644 --- a/i3-dump-log/main.c +++ b/i3-dump-log/main.c @@ -138,6 +138,7 @@ int main(int argc, char *argv[]) { if (verbose) printf("next_write = %d, last_wrap = %d, logbuffer_size = %d, shmname = %s\n", header->offset_next_write, header->offset_last_wrap, header->size, shmname); + free(shmname); walk = logbuffer + header->offset_next_write; /* We first need to print old content in case there was at least one From 0d1aad0af427b999d7242ce41a532c9dd7ee1c69 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 8 Jan 2016 20:21:54 +0100 Subject: [PATCH 122/187] rename workspace: fix heap-use-after-free --- src/commands.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/commands.c b/src/commands.c index 03ae0ae0..0c6ff127 100644 --- a/src/commands.c +++ b/src/commands.c @@ -2001,6 +2001,8 @@ void cmd_rename_workspace(I3_CMD, const char *old_name, const char *new_name) { } /* Change the name and try to parse it as a number. */ + /* old_name might refer to workspace->name, so copy it before free()ing */ + char *old_name_copy = sstrdup(old_name); FREE(workspace->name); workspace->name = sstrdup(new_name); @@ -2041,7 +2043,8 @@ void cmd_rename_workspace(I3_CMD, const char *old_name, const char *new_name) { ewmh_update_desktop_viewport(); ewmh_update_current_desktop(); - startup_sequence_rename_workspace(old_name, new_name); + startup_sequence_rename_workspace(old_name_copy, new_name); + free(old_name_copy); } /* From 08976f7a2a836a81731c45f6e8bbb8b8738d0a05 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 8 Jan 2016 20:41:09 +0100 Subject: [PATCH 123/187] con_mark: fix heap-use-after-free --- src/con.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/con.c b/src/con.c index ccc8445e..51b2a3f6 100644 --- a/src/con.c +++ b/src/con.c @@ -608,7 +608,8 @@ void con_mark(Con *con, const char *mark, mark_mode_t mode) { DLOG("Removing all existing marks on con = %p.\n", con); mark_t *current; - TAILQ_FOREACH(current, &(con->marks_head), marks) { + while (!TAILQ_EMPTY(&(con->marks_head))) { + current = TAILQ_FIRST(&(con->marks_head)); con_unmark(con, current->name); } } From 287ebcc206789747af4f8117e9ceb330b5a54959 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 8 Jan 2016 21:24:12 +0100 Subject: [PATCH 124/187] ipc: fix cosmetic memory leak when shutting down --- src/ipc.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/ipc.c b/src/ipc.c index 00b468fb..55eacb99 100644 --- a/src/ipc.c +++ b/src/ipc.c @@ -71,6 +71,9 @@ void ipc_shutdown(void) { current = TAILQ_FIRST(&all_clients); shutdown(current->fd, SHUT_RDWR); close(current->fd); + for (int i = 0; i < current->num_events; i++) + free(current->events[i]); + free(current->events); TAILQ_REMOVE(&all_clients, current, clients); free(current); } From de035cab6d0e50cb77448d730c14647668e3a575 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 8 Jan 2016 21:24:21 +0100 Subject: [PATCH 125/187] ipc: fix memory leak when clients with subscriptions disconnect --- src/ipc.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/ipc.c b/src/ipc.c index 55eacb99..f46e7179 100644 --- a/src/ipc.c +++ b/src/ipc.c @@ -1082,6 +1082,7 @@ static void ipc_receive_message(EV_P_ struct ev_io *w, int revents) { for (int i = 0; i < current->num_events; i++) free(current->events[i]); + free(current->events); /* We can call TAILQ_REMOVE because we break out of the * TAILQ_FOREACH afterwards */ TAILQ_REMOVE(&all_clients, current, clients); From 9eba061ed30367a887df2c5b7b5bb54e2cdca71f Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 9 Jan 2016 12:07:47 +0100 Subject: [PATCH 126/187] fix memory leak: free(pointerreply); --- src/main.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main.c b/src/main.c index 9c67b42a..78835e08 100644 --- a/src/main.c +++ b/src/main.c @@ -674,6 +674,7 @@ int main(int argc, char *argv[]) { } con_focus(con_descend_focused(output_get_content(output->con))); + free(pointerreply); } tree_render(); From 9dde0b9b18817e3aeb6104f2954998557f07c8ec Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 9 Jan 2016 12:30:15 +0100 Subject: [PATCH 127/187] i3bar: fix memory leak in socket path --- i3bar/src/main.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/i3bar/src/main.c b/i3bar/src/main.c index 32425319..3b5d7546 100644 --- a/i3bar/src/main.c +++ b/i3bar/src/main.c @@ -137,6 +137,8 @@ int main(int argc, char **argv) { if (socket_path == NULL) { socket_path = atom_sock_path; + } else { + free(atom_sock_path); } if (socket_path == NULL) { @@ -149,6 +151,7 @@ int main(int argc, char **argv) { /* Request the bar configuration. When it arrives, we fill the config array. */ i3_send_msg(I3_IPC_MESSAGE_TYPE_GET_BAR_CONFIG, config.bar_id); } + free(socket_path); /* We listen to SIGTERM/QUIT/INT and try to exit cleanly, by stopping the main loop. * We only need those watchers on the stack, so putting them on the stack saves us From f3a5796a8b6146792e648b315682a627f63847bf Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 9 Jan 2016 12:41:02 +0100 Subject: [PATCH 128/187] i3test::XTEST: free errors and connections with errors --- testcases/lib/i3test/XTEST.pm | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/testcases/lib/i3test/XTEST.pm b/testcases/lib/i3test/XTEST.pm index 9ec083b1..92adde42 100644 --- a/testcases/lib/i3test/XTEST.pm +++ b/testcases/lib/i3test/XTEST.pm @@ -59,6 +59,9 @@ bool inlinec_connect() { if ((conn = xcb_connect(NULL, &screen)) == NULL || xcb_connection_has_error(conn)) { + if (conn != NULL) { + xcb_disconnect(conn); + } fprintf(stderr, "Could not connect to X11\n"); return false; } @@ -79,6 +82,7 @@ bool inlinec_connect() { conn, xcb_xkb_use_extension(conn, XCB_XKB_MAJOR_VERSION, XCB_XKB_MINOR_VERSION), &err); if (err != NULL || usereply == NULL) { fprintf(stderr, "xcb_xkb_use_extension() failed\n"); + free(err); return false; } free(usereply); @@ -104,6 +108,7 @@ bool set_xkb_group(int group) { 0); /* groupLatch */ if ((err = xcb_request_check(conn, cookie)) != NULL) { fprintf(stderr, "X error code %d\n", err->error_code); + free(err); return false; } return true; @@ -124,6 +129,7 @@ bool xtest_input(int type, int detail, int x, int y) { XCB_NONE); /* deviceid */ if ((err = xcb_request_check(conn, cookie)) != NULL) { fprintf(stderr, "X error code %d\n", err->error_code); + free(err); return false; } From 338cb693dcd98235eb2106617f7b7b1f0e9622c6 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 9 Jan 2016 12:47:00 +0100 Subject: [PATCH 129/187] t/525-i3bar-mouse-bindings: wait for i3bar to appear before testing Otherwise, this test is flaky when i3bar takes a while to start. --- testcases/t/525-i3bar-mouse-bindings.t | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/testcases/t/525-i3bar-mouse-bindings.t b/testcases/t/525-i3bar-mouse-bindings.t index b76ccaf2..39690291 100644 --- a/testcases/t/525-i3bar-mouse-bindings.t +++ b/testcases/t/525-i3bar-mouse-bindings.t @@ -54,13 +54,22 @@ $i3->subscribe({ if ($event->{change} eq 'focus') { $cv->send($event->{container}); } + if ($event->{change} eq 'new') { + if (defined($event->{container}->{window_properties}->{class}) && + $event->{container}->{window_properties}->{class} eq 'i3bar') { + $cv->send($event->{container}); + } + } }, })->recv; +my $con = $cv->recv; +ok($con, 'i3bar appeared'); + my $left = open_window; my $right = open_window; sync_with_i3; -my $con = $cv->recv; +$con = $cv->recv; is($con->{window}, $right->{id}, 'focus is initially on the right container'); reset_test; From 6f53dc01ef8d4840672d58ab75fd4b933a744c03 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 9 Jan 2016 12:50:29 +0100 Subject: [PATCH 130/187] tree_restore(): fix memory leak --- src/tree.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/tree.c b/src/tree.c index 5642c5c7..5a6129ac 100644 --- a/src/tree.c +++ b/src/tree.c @@ -84,6 +84,7 @@ bool tree_restore(const char *path, xcb_get_geometry_reply_t *geometry) { focused = croot; tree_append_json(focused, globbed, NULL); + free(globbed); DLOG("appended tree, using new root\n"); croot = TAILQ_FIRST(&(croot->nodes_head)); From 71476b03e3f5010c5e3b8efe2b9ea42f9d82e2b5 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 9 Jan 2016 13:10:54 +0100 Subject: [PATCH 131/187] log.c: fix memory leak --- src/log.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/log.c b/src/log.c index 856330b6..b7ad072f 100644 --- a/src/log.c +++ b/src/log.c @@ -172,6 +172,7 @@ void open_logbuffer(void) { void close_logbuffer(void) { close(logbuffer_shm); shm_unlink(shmlogname); + free(shmlogname); logbuffer = NULL; shmlogname = ""; } From ada71471c13ec8e4abe7c5739bc641746fd5dc42 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 9 Jan 2016 13:13:57 +0100 Subject: [PATCH 132/187] x.c: fix memory leak --- src/x.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/x.c b/src/x.c index a5aca55b..a14c25fa 100644 --- a/src/x.c +++ b/src/x.c @@ -1080,6 +1080,8 @@ void x_push_changes(Con *con) { xcb_warp_pointer(conn, XCB_NONE, root, 0, 0, 0, 0, mid_x, mid_y); xcb_change_window_attributes(conn, root, XCB_CW_EVENT_MASK, (uint32_t[]){ROOT_EVENT_MASK}); } + + free(pointerreply); } warp_to = NULL; } From 79594398e2e042a466c3c0b20fb31d544b6c5d2b Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 9 Jan 2016 13:39:00 +0100 Subject: [PATCH 133/187] commands.c: fix memory leak --- src/commands.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/commands.c b/src/commands.c index 0c6ff127..515b5776 100644 --- a/src/commands.c +++ b/src/commands.c @@ -62,6 +62,11 @@ HANDLE_INVALID_MATCH; \ \ if (match_is_empty(current_match)) { \ + while (!TAILQ_EMPTY(&owindows)) { \ + owindow *ow = TAILQ_FIRST(&owindows); \ + TAILQ_REMOVE(&owindows, ow, owindows); \ + free(ow); \ + } \ owindow *ow = smalloc(sizeof(owindow)); \ ow->con = focused; \ TAILQ_INIT(&owindows); \ From e7f147618062401e8d16ea2a953354d34329bcf3 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 9 Jan 2016 13:46:49 +0100 Subject: [PATCH 134/187] reload: fix memory leak --- include/config.h | 2 +- src/config.c | 4 ++++ src/config_directives.c | 3 +++ 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/include/config.h b/include/config.h index f4b1efd5..d5465c3f 100644 --- a/include/config.h +++ b/include/config.h @@ -94,7 +94,7 @@ struct Config { i3Font font; char *ipc_socket_path; - const char *restart_state_path; + char *restart_state_path; layout_t default_layout; int container_stack_limit; diff --git a/src/config.c b/src/config.c index ef632128..a1111463 100644 --- a/src/config.c +++ b/src/config.c @@ -169,6 +169,10 @@ void load_configuration(xcb_connection_t *conn, const char *override_configpath, /* Get rid of the current font */ free_font(); + + free(config.ipc_socket_path); + free(config.restart_state_path); + free(config.fake_outputs); } SLIST_INIT(&modes); diff --git a/src/config_directives.c b/src/config_directives.c index 85cea4cf..0d4b3d31 100644 --- a/src/config_directives.c +++ b/src/config_directives.c @@ -261,6 +261,7 @@ CFGFUN(workspace_back_and_forth, const char *value) { } CFGFUN(fake_outputs, const char *outputs) { + free(config.fake_outputs); config.fake_outputs = sstrdup(outputs); } @@ -313,10 +314,12 @@ CFGFUN(workspace, const char *workspace, const char *output) { } CFGFUN(ipc_socket, const char *path) { + free(config.ipc_socket_path); config.ipc_socket_path = sstrdup(path); } CFGFUN(restart_state, const char *path) { + free(config.restart_state_path); config.restart_state_path = sstrdup(path); } From feef3ea78a773374706f0c6535f96ae2ab812457 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 9 Jan 2016 13:54:37 +0100 Subject: [PATCH 135/187] window.c: fix memory leak --- src/window.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/window.c b/src/window.c index a86f77a2..bd4e2719 100644 --- a/src/window.c +++ b/src/window.c @@ -244,6 +244,7 @@ void window_update_role(i3Window *win, xcb_get_property_reply_t *prop, bool befo */ void window_update_type(i3Window *window, xcb_get_property_reply_t *reply) { xcb_atom_t new_type = xcb_get_preferred_window_type(reply); + free(reply); if (new_type == XCB_NONE) { DLOG("cannot read _NET_WM_WINDOW_TYPE from window.\n"); return; From f904511b7ec51fc6e8adcf97fef2a7d39049a965 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 9 Jan 2016 14:05:05 +0100 Subject: [PATCH 136/187] con_get_tree_representation: fix memory leak --- src/con.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/con.c b/src/con.c index 51b2a3f6..a3a2f2e3 100644 --- a/src/con.c +++ b/src/con.c @@ -1992,6 +1992,7 @@ char *con_get_tree_representation(Con *con) { (TAILQ_FIRST(&(con->nodes_head)) == child ? "" : " "), child_txt); free(buf); buf = tmp_buf; + free(child_txt); } /* 3) close the brackets */ From 414be6290d83ebed13d0c5a869fa5a3b0a54ddcd Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 9 Jan 2016 14:17:47 +0100 Subject: [PATCH 137/187] tree_append_json: fix memory leaks --- src/load_layout.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/load_layout.c b/src/load_layout.c index 9856d078..33435236 100644 --- a/src/load_layout.c +++ b/src/load_layout.c @@ -640,8 +640,11 @@ void tree_append_json(Con *con, const char *filename, char **errormsg) { setlocale(LC_NUMERIC, ""); yajl_complete_parse(hand); + yajl_free(hand); + yajl_gen_free(g); fclose(f); + free(buf); if (to_focus) con_focus(to_focus); } From 436e4c17b30ab36535d0bb02f25906a8d8f3954e Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 9 Jan 2016 14:18:16 +0100 Subject: [PATCH 138/187] free container matches when destroying containers --- src/tree.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/tree.c b/src/tree.c index 5a6129ac..5ff426ca 100644 --- a/src/tree.c +++ b/src/tree.c @@ -328,6 +328,12 @@ bool tree_close_internal(Con *con, kill_window_t kill_window, bool dont_kill_par free(con->name); FREE(con->deco_render_params); TAILQ_REMOVE(&all_cons, con, all_cons); + while (!TAILQ_EMPTY(&(con->swallow_head))) { + Match *match = TAILQ_FIRST(&(con->swallow_head)); + TAILQ_REMOVE(&(con->swallow_head), match, matches); + match_free(match); + free(match); + } free(con); /* in the case of floating windows, we already focused another container From 8d4bf6c57723b46179a3b202cfc1660e8f864405 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 9 Jan 2016 14:19:00 +0100 Subject: [PATCH 139/187] free dynamically allocated matches --- src/load_layout.c | 1 + src/manage.c | 1 + 2 files changed, 2 insertions(+) diff --git a/src/load_layout.c b/src/load_layout.c index 33435236..173e573b 100644 --- a/src/load_layout.c +++ b/src/load_layout.c @@ -87,6 +87,7 @@ static int json_end_map(void *ctx) { Match *match = TAILQ_FIRST(&(json_node->swallow_head)); TAILQ_REMOVE(&(json_node->swallow_head), match, matches); match_free(match); + free(match); } } diff --git a/src/manage.c b/src/manage.c index 05ac15f0..d6a8c6d3 100644 --- a/src/manage.c +++ b/src/manage.c @@ -304,6 +304,7 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki Match *first = TAILQ_FIRST(&(nc->swallow_head)); TAILQ_REMOVE(&(nc->swallow_head), first, matches); match_free(first); + free(first); } } } From 8d917497c2973bcf85b223777a9e294eab4e9a7a Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 9 Jan 2016 16:34:41 +0100 Subject: [PATCH 140/187] manage.c: fix memory leak --- src/manage.c | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/manage.c b/src/manage.c index d6a8c6d3..8e68c51e 100644 --- a/src/manage.c +++ b/src/manage.c @@ -242,6 +242,7 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki /* See if any container swallows this new window */ nc = con_for_window(search_at, cwindow, &match); + const bool match_from_restart_mode = (match && match->restart_mode); if (nc == NULL) { /* If not, check if it is assigned to a specific workspace */ if ((assignment = assignment_for(cwindow, A_TO_WORKSPACE))) { @@ -291,6 +292,7 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki DLOG("Removing match %p from container %p\n", match, nc); TAILQ_REMOVE(&(nc->swallow_head), match, matches); match_free(match); + FREE(match); } } @@ -344,14 +346,17 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki Con *target_output = con_get_output(ws); if (workspace_is_visible(ws) && current_output == target_output) { - if (!match || !match->restart_mode) { + if (!match_from_restart_mode) { set_focus = true; - } else + } else { DLOG("not focusing, matched with restart_mode == true\n"); - } else + } + } else { DLOG("workspace not visible, not focusing\n"); - } else + } + } else { DLOG("dock, not focusing\n"); + } } else { DLOG("fs = %p, ws = %p, not focusing\n", fs, ws); /* Insert the new container in focus stack *after* the currently From f511cc61c18a8c73a58e26dd8d5b89a323e2aac5 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 9 Jan 2016 16:47:48 +0100 Subject: [PATCH 141/187] fix memory leak when swallowing windows --- include/window.h | 6 ++++++ src/manage.c | 3 +++ src/tree.c | 7 ++----- src/window.c | 12 ++++++++++++ 4 files changed, 23 insertions(+), 5 deletions(-) diff --git a/include/window.h b/include/window.h index 7a248277..d0b97f1d 100644 --- a/include/window.h +++ b/include/window.h @@ -9,6 +9,12 @@ */ #pragma once +/** + * Frees an i3Window and all its members. + * + */ +void window_free(i3Window *win); + /** * Updates the WM_CLASS (consisting of the class and instance) for the * given window. diff --git a/src/manage.c b/src/manage.c index 8e68c51e..2bcb47f3 100644 --- a/src/manage.c +++ b/src/manage.c @@ -310,6 +310,9 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki } } } + if (nc->window != cwindow && nc->window != NULL) { + window_free(nc->window); + } nc->window = cwindow; x_reinit(nc); diff --git a/src/tree.c b/src/tree.c index 5ff426ca..0c301209 100644 --- a/src/tree.c +++ b/src/tree.c @@ -266,11 +266,8 @@ bool tree_close_internal(Con *con, kill_window_t kill_window, bool dont_kill_par add_ignore_event(cookie.sequence, 0); } ipc_send_window_event("close", con); - FREE(con->window->class_class); - FREE(con->window->class_instance); - i3string_free(con->window->name); - FREE(con->window->ran_assignments); - FREE(con->window); + window_free(con->window); + con->window = NULL; } Con *ws = con_get_workspace(con); diff --git a/src/window.c b/src/window.c index bd4e2719..d6136c78 100644 --- a/src/window.c +++ b/src/window.c @@ -11,6 +11,18 @@ */ #include "all.h" +/* + * Frees an i3Window and all its members. + * + */ +void window_free(i3Window *win) { + FREE(win->class_class); + FREE(win->class_instance); + i3string_free(win->name); + FREE(win->ran_assignments); + FREE(win); +} + /* * Updates the WM_CLASS (consisting of the class and instance) for the * given window. From 5ca659853afade8a1a830b916dc6ca3c1a85aaed Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 9 Jan 2016 17:03:29 +0100 Subject: [PATCH 142/187] i3bar: fix memory leak --- i3bar/src/xcb.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/i3bar/src/xcb.c b/i3bar/src/xcb.c index 5e21e118..60821167 100644 --- a/i3bar/src/xcb.c +++ b/i3bar/src/xcb.c @@ -1425,6 +1425,8 @@ void init_tray(void) { return; } + free(selreply); + send_tray_clientmessage(); } From 8bfc651dd1589bb3d5a2ec59f90ad44585746c46 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 9 Jan 2016 17:03:36 +0100 Subject: [PATCH 143/187] i3bar: free font when exiting --- i3bar/src/xcb.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/i3bar/src/xcb.c b/i3bar/src/xcb.c index 60821167..590515a7 100644 --- a/i3bar/src/xcb.c +++ b/i3bar/src/xcb.c @@ -1488,6 +1488,8 @@ void clean_xcb(void) { FREE_SLIST(outputs, i3_output); FREE(outputs); + free_font(); + xcb_free_cursor(xcb_connection, cursor); xcb_flush(xcb_connection); xcb_aux_sync(xcb_connection); From 17c55792c6c20df0085c79fa82d03322aa04a377 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 9 Jan 2016 17:05:40 +0100 Subject: [PATCH 144/187] fix memory leak: use xcb_disconnect() instead of free() --- src/restore_layout.c | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/restore_layout.c b/src/restore_layout.c index af77b11d..4b9dc4ae 100644 --- a/src/restore_layout.c +++ b/src/restore_layout.c @@ -98,7 +98,11 @@ void restore_connect(void) { free(state); } - free(restore_conn); + /* xcb_disconnect leaks memory in libxcb versions earlier than 1.11, + * but it’s the right function to call. See + * http://cgit.freedesktop.org/xcb/libxcb/commit/src/xcb_conn.c?id=4dcbfd77b + */ + xcb_disconnect(restore_conn); free(xcb_watcher); free(xcb_check); free(xcb_prepare); @@ -106,8 +110,12 @@ void restore_connect(void) { int screen; restore_conn = xcb_connect(NULL, &screen); - if (restore_conn == NULL || xcb_connection_has_error(restore_conn)) + if (restore_conn == NULL || xcb_connection_has_error(restore_conn)) { + if (restore_conn != NULL) { + xcb_disconnect(restore_conn); + } errx(EXIT_FAILURE, "Cannot open display\n"); + } xcb_watcher = scalloc(1, sizeof(struct ev_io)); xcb_check = scalloc(1, sizeof(struct ev_check)); From 9b4efdc1943c3ad3493c77c07eb32f15390c04a5 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 9 Jan 2016 17:07:23 +0100 Subject: [PATCH 145/187] font: free errors --- libi3/font.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/libi3/font.c b/libi3/font.c index 9e72849a..5a2504fa 100644 --- a/libi3/font.c +++ b/libi3/font.c @@ -224,6 +224,7 @@ i3Font load_font(const char *pattern, const bool fallback) { info_cookie = xcb_query_font(conn, font.specific.xcb.id); /* Check if we managed to open 'fixed' */ + free(error); error = xcb_request_check(conn, font_cookie); /* Fall back to '-misc-*' if opening 'fixed' fails. */ @@ -234,12 +235,16 @@ i3Font load_font(const char *pattern, const bool fallback) { strlen(pattern), pattern); info_cookie = xcb_query_font(conn, font.specific.xcb.id); + free(error); if ((error = xcb_request_check(conn, font_cookie)) != NULL) errx(EXIT_FAILURE, "Could open neither requested font nor fallbacks " "(fixed or -misc-*): X11 error %d", error->error_code); } } + if (error != NULL) { + free(error); + } font.pattern = sstrdup(pattern); LOG("Using X font %s\n", pattern); From 5a36d090c7e341326399a3f6174e243a941a3029 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 9 Jan 2016 17:15:33 +0100 Subject: [PATCH 146/187] common.mk: add ASAN flag for AddressSanitizer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit See https://github.com/google/sanitizers/wiki for details. Compile with “make ASAN=1” to enable. --- common.mk | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/common.mk b/common.mk index 878aeac1..4fe8f2b0 100644 --- a/common.mk +++ b/common.mk @@ -1,5 +1,6 @@ UNAME=$(shell uname) DEBUG=1 +ASAN=0 INSTALL=install LN=ln PKG_CONFIG=pkg-config @@ -42,6 +43,11 @@ else CFLAGS ?= -pipe -O2 -freorder-blocks-and-partition endif +ifeq ($(ASAN),1) +CFLAGS += -fsanitize=address -DI3_ASAN_ENABLED +LDFLAGS += -fsanitize=address +endif + # Default LDFLAGS that users should be able to override LDFLAGS ?= $(as_needed_LDFLAG) From cb3cdb602a08432d8b972d06bef832e056119ad9 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 9 Jan 2016 17:18:05 +0100 Subject: [PATCH 147/187] ASAN: trigger leak before exiting MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This disables the default leak-check-on-exit behavior which reports a bunch of leaks that are only leaks while exiting, at which point they don’t matter, because the operating system will clean up the memory our process used. --- i3bar/src/ipc.c | 6 ++++++ i3bar/src/xcb.c | 7 +++++++ src/commands.c | 7 +++++++ src/restore_layout.c | 7 +++++++ 4 files changed, 27 insertions(+) diff --git a/i3bar/src/ipc.c b/i3bar/src/ipc.c index 1214954d..34898663 100644 --- a/i3bar/src/ipc.c +++ b/i3bar/src/ipc.c @@ -17,6 +17,9 @@ #include #include #include +#ifdef I3_ASAN_ENABLED +#include +#endif #include "common.h" @@ -212,6 +215,9 @@ void got_data(struct ev_loop *loop, ev_io *watcher, int events) { /* EOF received. Since i3 will restart i3bar instances as appropriate, * we exit here. */ DLOG("EOF received, exiting...\n"); +#ifdef I3_ASAN_ENABLED + __lsan_do_leak_check(); +#endif clean_xcb(); exit(EXIT_SUCCESS); } diff --git a/i3bar/src/xcb.c b/i3bar/src/xcb.c index 590515a7..0b9f094b 100644 --- a/i3bar/src/xcb.c +++ b/i3bar/src/xcb.c @@ -32,6 +32,10 @@ #include #include +#ifdef I3_ASAN_ENABLED +#include +#endif + #include "common.h" #include "libi3.h" @@ -1070,6 +1074,9 @@ void xcb_chk_cb(struct ev_loop *loop, ev_check *watcher, int revents) { 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); } diff --git a/src/commands.c b/src/commands.c index 515b5776..525a30fc 100644 --- a/src/commands.c +++ b/src/commands.c @@ -12,6 +12,10 @@ #include #include +#ifdef I3_ASAN_ENABLED +#include +#endif + #include "all.h" #include "shmlog.h" @@ -1658,6 +1662,9 @@ void cmd_layout_toggle(I3_CMD, const char *toggle_mode) { */ void cmd_exit(I3_CMD) { LOG("Exiting due to user command.\n"); +#ifdef I3_ASAN_ENABLED + __lsan_do_leak_check(); +#endif ipc_shutdown(); unlink(config.ipc_socket_path); xcb_disconnect(conn); diff --git a/src/restore_layout.c b/src/restore_layout.c index 4b9dc4ae..7e1b78ae 100644 --- a/src/restore_layout.c +++ b/src/restore_layout.c @@ -13,6 +13,10 @@ */ #include "all.h" +#ifdef I3_ASAN_ENABLED +#include +#endif + typedef struct placeholder_state { /** The X11 placeholder window. */ xcb_window_t window; @@ -114,6 +118,9 @@ void restore_connect(void) { if (restore_conn != NULL) { xcb_disconnect(restore_conn); } +#ifdef I3_ASAN_ENABLED + __lsan_do_leak_check(); +#endif errx(EXIT_FAILURE, "Cannot open display\n"); } From 196e63e1bf8e7b9826dbc57ea0f93a1e043c0a4f Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 9 Jan 2016 17:19:12 +0100 Subject: [PATCH 148/187] testcases: report tests with AddressSanitizer/LeakSanitizer reports --- testcases/complete-run.pl | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/testcases/complete-run.pl b/testcases/complete-run.pl index 4657a2ec..2d61e993 100755 --- a/testcases/complete-run.pl +++ b/testcases/complete-run.pl @@ -262,6 +262,20 @@ if ($options{coverage}) { } } +# Report logfiles that match “(Leak|Address)Sanitizer:”. +my @logs_with_leaks; +for my $log (<$outdir/i3-log-for-*>) { + if (slurp($log) =~ /(Leak|Address)Sanitizer:/) { + push @logs_with_leaks, $log; + } +} +if (scalar @logs_with_leaks > 0) { + say "\nThe following test logfiles contain AddressSanitizer or LeakSanitizer reports:"; + for my $log (sort @logs_with_leaks) { + say "\t$log"; + } +} + exit ($aggregator->failed > 0); # From f14a94031c6b33a11715bfc4e0273cc05d981ae6 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 9 Jan 2016 17:22:45 +0100 Subject: [PATCH 149/187] tests: switch to xft fonts See issue #2155 for details. --- testcases/t/245-move-position-mouse.t | 8 ++++---- testcases/t/254-move-to-output-with-criteria.t | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/testcases/t/245-move-position-mouse.t b/testcases/t/245-move-position-mouse.t index ae049271..3268e582 100644 --- a/testcases/t/245-move-position-mouse.t +++ b/testcases/t/245-move-position-mouse.t @@ -38,7 +38,7 @@ my $root_rect = $x->root->rect; $config = < 0; my $config = < Date: Sat, 9 Jan 2016 16:47:33 -0500 Subject: [PATCH 150/187] Rename decoration_border to child_border. fixes #2154 --- docs/userguide | 14 +++++++------- include/config.h | 2 +- include/config_directives.h | 2 +- parser-specs/config.spec | 8 ++++---- src/config.c | 14 +++++++------- src/config_directives.c | 32 ++++++++++++++++---------------- src/x.c | 10 ++++------ 7 files changed, 40 insertions(+), 42 deletions(-) diff --git a/docs/userguide b/docs/userguide index e8dee103..55568b65 100644 --- a/docs/userguide +++ b/docs/userguide @@ -836,9 +836,9 @@ workspace "2: vim" output VGA1 You can change all colors which i3 uses to draw the window decorations. *Syntax*: -------------------------------------------------------------------------- - -------------------------------------------------------------------------- +-------------------------------------------------------------------- + +-------------------------------------------------------------------- Where colorclass can be one of: @@ -863,8 +863,8 @@ client.background:: Colors are in HTML hex format (#rrggbb), see the following example: *Examples (default colors)*: ---------------------------------------------------------- -# class border backgr. text indicator decoration_border +---------------------------------------------------------------------- +# class border backgr. text indicator child_border client.focused #4c7899 #285577 #ffffff #2e9ef4 #285577 client.focused_inactive #333333 #5f676a #ffffff #484e50 #5f676a client.unfocused #333333 #222222 #888888 #292d2e #222222 @@ -872,10 +872,10 @@ client.urgent #2f343a #900000 #ffffff #900000 #900000 client.placeholder #000000 #0c0c0c #ffffff #000000 #0c0c0c client.background #ffffff ---------------------------------------------------------- +---------------------------------------------------------------------- Note that for the window decorations, the color around the child window is the -"decoration_border", and "border" color is only the two thin lines around the +"child_border", and "border" color is only the two thin lines around the titlebar. The indicator color is used for indicating where a new window will be opened. diff --git a/include/config.h b/include/config.h index d5465c3f..acdd2c89 100644 --- a/include/config.h +++ b/include/config.h @@ -54,7 +54,7 @@ struct Colortriple { color_t background; color_t text; color_t indicator; - color_t decoration_border; + color_t child_border; }; /** diff --git a/include/config_directives.h b/include/config_directives.h index fd8a4208..bcbea111 100644 --- a/include/config_directives.h +++ b/include/config_directives.h @@ -59,7 +59,7 @@ CFGFUN(no_focus); CFGFUN(ipc_socket, const char *path); CFGFUN(restart_state, const char *path); CFGFUN(popup_during_fullscreen, const char *value); -CFGFUN(color, const char *colorclass, const char *border, const char *background, const char *text, const char *indicator, const char *decoration_border); +CFGFUN(color, const char *colorclass, const char *border, const char *background, const char *text, const char *indicator, const char *child_border); CFGFUN(color_single, const char *colorclass, const char *color); CFGFUN(floating_modifier, const char *modifiers); CFGFUN(new_window, const char *windowtype, const char *border, const long width); diff --git a/parser-specs/config.spec b/parser-specs/config.spec index 1e1b52e2..f5275028 100644 --- a/parser-specs/config.spec +++ b/parser-specs/config.spec @@ -282,13 +282,13 @@ state COLOR_TEXT: state COLOR_INDICATOR: indicator = word - -> COLOR_DECORATION_BORDER + -> COLOR_CHILD_BORDER end -> call cfg_color($colorclass, $border, $background, $text, NULL, NULL) -state COLOR_DECORATION_BORDER: - decoration_border = word - -> call cfg_color($colorclass, $border, $background, $text, $indicator, $decoration_border) +state COLOR_CHILD_BORDER: + child_border = word + -> call cfg_color($colorclass, $border, $background, $text, $indicator, $child_border) end -> call cfg_color($colorclass, $border, $background, $text, $indicator, NULL) diff --git a/src/config.c b/src/config.c index a1111463..fb2feda8 100644 --- a/src/config.c +++ b/src/config.c @@ -193,13 +193,13 @@ void load_configuration(xcb_connection_t *conn, const char *override_configpath, memset(&config, 0, sizeof(config)); /* Initialize default colors */ -#define INIT_COLOR(x, cborder, cbackground, ctext, cindicator) \ - do { \ - x.border = draw_util_hex_to_color(cborder); \ - x.background = draw_util_hex_to_color(cbackground); \ - x.text = draw_util_hex_to_color(ctext); \ - x.indicator = draw_util_hex_to_color(cindicator); \ - x.decoration_border = draw_util_hex_to_color(cbackground); \ +#define INIT_COLOR(x, cborder, cbackground, ctext, cindicator) \ + do { \ + x.border = draw_util_hex_to_color(cborder); \ + x.background = draw_util_hex_to_color(cbackground); \ + x.text = draw_util_hex_to_color(ctext); \ + x.indicator = draw_util_hex_to_color(cindicator); \ + x.child_border = draw_util_hex_to_color(cbackground); \ } while (0) config.client.background = draw_util_hex_to_color("#000000"); diff --git a/src/config_directives.c b/src/config_directives.c index 0d4b3d31..e92ef1d9 100644 --- a/src/config_directives.c +++ b/src/config_directives.c @@ -338,22 +338,22 @@ CFGFUN(color_single, const char *colorclass, const char *color) { config.client.background = draw_util_hex_to_color(color); } -CFGFUN(color, const char *colorclass, const char *border, const char *background, const char *text, const char *indicator, const char *decoration_border) { -#define APPLY_COLORS(classname) \ - do { \ - if (strcmp(colorclass, "client." #classname) == 0) { \ - config.client.classname.border = draw_util_hex_to_color(border); \ - config.client.classname.background = draw_util_hex_to_color(background); \ - config.client.classname.text = draw_util_hex_to_color(text); \ - if (indicator != NULL) { \ - config.client.classname.indicator = draw_util_hex_to_color(indicator); \ - } \ - if (decoration_border != NULL) { \ - config.client.classname.decoration_border = draw_util_hex_to_color(decoration_border); \ - } else { \ - config.client.classname.decoration_border = config.client.classname.background; \ - } \ - } \ +CFGFUN(color, const char *colorclass, const char *border, const char *background, const char *text, const char *indicator, const char *child_border) { +#define APPLY_COLORS(classname) \ + do { \ + if (strcmp(colorclass, "client." #classname) == 0) { \ + config.client.classname.border = draw_util_hex_to_color(border); \ + config.client.classname.background = draw_util_hex_to_color(background); \ + config.client.classname.text = draw_util_hex_to_color(text); \ + if (indicator != NULL) { \ + config.client.classname.indicator = draw_util_hex_to_color(indicator); \ + } \ + if (child_border != NULL) { \ + config.client.classname.child_border = draw_util_hex_to_color(child_border); \ + } else { \ + config.client.classname.child_border = config.client.classname.background; \ + } \ + } \ } while (0) APPLY_COLORS(focused_inactive); diff --git a/src/x.c b/src/x.c index a14c25fa..f44bc37a 100644 --- a/src/x.c +++ b/src/x.c @@ -464,24 +464,22 @@ void x_draw_decoration(Con *con) { * rectangle because some childs are not freely resizable and we want * their background color to "shine through". */ if (!(borders_to_hide & ADJ_LEFT_SCREEN_EDGE)) { - draw_util_rectangle(conn, &(con->frame_buffer), p->color->decoration_border, 0, 0, br.x, r->height); + draw_util_rectangle(conn, &(con->frame_buffer), p->color->child_border, 0, 0, br.x, r->height); } if (!(borders_to_hide & ADJ_RIGHT_SCREEN_EDGE)) { draw_util_rectangle(conn, &(con->frame_buffer), - p->color->decoration_border, r->width + (br.width + br.x), 0, + p->color->child_border, r->width + (br.width + br.x), 0, -(br.width + br.x), r->height); } if (!(borders_to_hide & ADJ_LOWER_SCREEN_EDGE)) { draw_util_rectangle(conn, &(con->frame_buffer), - p->color->decoration_border, br.x, r->height + (br.height + - br.y), + p->color->child_border, br.x, r->height + (br.height + br.y), r->width + br.width, -(br.height + br.y)); } /* pixel border needs an additional line at the top */ if (p->border_style == BS_PIXEL && !(borders_to_hide & ADJ_UPPER_SCREEN_EDGE)) { draw_util_rectangle(conn, &(con->frame_buffer), - p->color->decoration_border, br.x, 0, r->width + br.width, - br.y); + p->color->child_border, br.x, 0, r->width + br.width, br.y); } /* Highlight the side of the border at which the next window will be From dd33cd36dd0d28f0b60fbc0366bb468c645e9e55 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 11 Jan 2016 20:59:26 +0100 Subject: [PATCH 151/187] travis: build with AddressSanitizer enabled This requires us to use a more recent compiler. --- .travis.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index d1d1932b..c6ccf58d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,6 +11,8 @@ addons: packages: - clang-format-3.5 - libllvm3.5 + - clang-3.5 + - gcc-5 before_install: # The travis VMs run on Ubuntu 12.04 which is very old and a huge pain to get # into a state where we can build a recent version of i3 :(. @@ -49,7 +51,9 @@ install: - sudo /bin/sh -c 'cpanm -n -v AnyEvent::I3 || true' script: - if [ -a .git/shallow ]; then git fetch --unshallow; fi - - CFLAGS="-Wformat -Wformat-security -Wextra -Wno-unused-parameter -Werror" make -j + - if [ "$CC" = "clang" ]; then export CC="clang-3.5"; fi + - if [ "$CC" = "gcc" ]; then export CC="gcc-5"; fi + - CFLAGS="-Wformat -Wformat-security -Wextra -Wno-unused-parameter -Werror" make -j ASAN=1 - (cd testcases && xvfb-run ./complete-run.pl --parallel=1 || (cat latest/complete-run.log; false)) - clang-format-3.5 -i $(find . -name "*.[ch]" | tr '\n' ' ') && git diff --exit-code || (echo 'Code was not formatted using clang-format!'; false) - | From 9a4dbf366bcfce43e50185f05ed8c3ad85ac8ba1 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 11 Jan 2016 21:18:11 +0100 Subject: [PATCH 152/187] travis: use trusty (simpler config, faster builds) --- .travis.yml | 38 +++++--------------------------------- 1 file changed, 5 insertions(+), 33 deletions(-) diff --git a/.travis.yml b/.travis.yml index c6ccf58d..54ef5cae 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,3 +1,5 @@ +sudo: required +dist: trusty language: c compiler: - gcc @@ -5,8 +7,6 @@ compiler: addons: apt: sources: - - llvm-toolchain-precise-3.5 - # ubuntu-toolchain-r-test contains libstdc++6 >= 4.8 which libllvm3.5 needs. - ubuntu-toolchain-r-test packages: - clang-format-3.5 @@ -14,39 +14,11 @@ addons: - clang-3.5 - gcc-5 before_install: - # The travis VMs run on Ubuntu 12.04 which is very old and a huge pain to get - # into a state where we can build a recent version of i3 :(. - - "echo 'deb http://archive.ubuntu.com/ubuntu/ trusty main universe' | sudo tee /etc/apt/sources.list.d/trusty.list" - - "echo 'APT::Default-Release \"precise\";' | sudo tee /etc/apt/apt.conf.d/default-release" - - - "echo 'Package: libc6' > /tmp/pin" - - "echo 'Pin: release n=trusty' >> /tmp/pin" - - "echo 'Pin-Priority: 999' >> /tmp/pin" - - "echo '' >> /tmp/pin" - - - "echo 'Package: libxkbcommon*' >> /tmp/pin" - - "echo 'Pin: release n=trusty' >> /tmp/pin" - - "echo 'Pin-Priority: 999' >> /tmp/pin" - - "echo '' >> /tmp/pin" - - - "echo 'Package: libyajl*' >> /tmp/pin" - - "echo 'Pin: release n=trusty' >> /tmp/pin" - - "echo 'Pin-Priority: 999' >> /tmp/pin" - - "echo '' >> /tmp/pin" - - - "echo 'Package: libxcb-image*' >> /tmp/pin" - - "echo 'Pin: release n=trusty' >> /tmp/pin" - - "echo 'Pin-Priority: 999' >> /tmp/pin" - - "echo '' >> /tmp/pin" - - - sudo cp /tmp/pin /etc/apt/preferences.d/trustypin - - sudo apt-get update - - sudo apt-get install -t trusty libc6 libc6-dev git - - sudo apt-get install --no-install-recommends devscripts equivs xdotool + - sudo DEBIAN_FRONTEND=noninteractive apt-get install -yq --no-install-suggests --no-install-recommends devscripts equivs xdotool install: - - sudo mk-build-deps --install --remove --tool 'apt-get --no-install-recommends' debian/control + - sudo DEBIAN_FRONTEND=noninteractive mk-build-deps --install --remove --tool 'apt-get -yq --no-install-suggests --no-install-recommends' debian/control # Install as many dependencies as possible via apt because cpanm is not very reliable/easy to debug. - - sudo apt-get install --no-install-recommends libanyevent-perl libanyevent-i3-perl libextutils-pkgconfig-perl xcb-proto cpanminus xvfb xserver-xephyr xauth libinline-perl libxml-simple-perl libmouse-perl libmousex-nativetraits-perl libextutils-depends-perl perl-modules libtest-deep-perl libtest-exception-perl libxml-parser-perl libtest-simple-perl libtest-fatal-perl libdata-dump-perl libtest-differences-perl libxml-tokeparser-perl libtest-use-ok-perl libipc-run-perl libxcb-xtest0-dev + - sudo DEBIAN_FRONTEND=noninteractive apt-get install -yq --no-install-suggests --no-install-recommends libanyevent-perl libanyevent-i3-perl libextutils-pkgconfig-perl xcb-proto cpanminus xvfb xserver-xephyr xauth libinline-perl libxml-simple-perl libmouse-perl libmousex-nativetraits-perl libextutils-depends-perl perl-modules libtest-deep-perl libtest-exception-perl libxml-parser-perl libtest-simple-perl libtest-fatal-perl libdata-dump-perl libtest-differences-perl libxml-tokeparser-perl libtest-use-ok-perl libipc-run-perl libxcb-xtest0-dev - sudo /bin/sh -c 'cpanm -n -v X11::XCB || true' - sudo /bin/sh -c 'cpanm -n -v AnyEvent::I3 || true' script: From e41dee32daecc4ceb2809c03ec5d34e536201baa Mon Sep 17 00:00:00 2001 From: Prayag Verma Date: Sun, 17 Jan 2016 16:25:54 +0530 Subject: [PATCH 153/187] Remove copyright year range from License --- LICENSE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE b/LICENSE index 05f078e2..6354f065 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright © 2009-2011, Michael Stapelberg and contributors +Copyright © 2009, Michael Stapelberg and contributors All rights reserved. Redistribution and use in source and binary forms, with or without From a1d1f456a1fbf44bfa5c6a9d041023ac498b0c46 Mon Sep 17 00:00:00 2001 From: Theo Buehler Date: Thu, 14 Jan 2016 10:06:34 +0100 Subject: [PATCH 154/187] Add pledge(2) support for OpenBSD pledges for i3: "stdio rpath unix" for talking to the i3 socket usually in /tmp "proc exec" for executing programs "wpath cpath" are needed for the restart-in-place functionality To make this work, @semarie pointed out that it is sufficient to ensure that we get physical_mem_bytes only once, namely in init_logging(). pledges for i3-msg: "stdio rpath unix" are needed for talking to the i3-socket pledges for i3-nagbar "rpath getpw" to find the home directory "wpath cpath" to write the script "proc exec" to execute the script --- i3-msg/main.c | 4 ++++ i3-nagbar/main.c | 5 +++++ src/log.c | 21 ++++++++++++--------- src/main.c | 5 +++++ 4 files changed, 26 insertions(+), 9 deletions(-) diff --git a/i3-msg/main.c b/i3-msg/main.c index e4a0628c..36691cae 100644 --- a/i3-msg/main.c +++ b/i3-msg/main.c @@ -119,6 +119,10 @@ static yajl_callbacks reply_callbacks = { }; int main(int argc, char *argv[]) { +#if defined(__OpenBSD__) + if (pledge("stdio rpath unix", NULL) == -1) + err(EXIT_FAILURE, "pledge"); +#endif char *env_socket_path = getenv("I3SOCK"); if (env_socket_path) socket_path = sstrdup(env_socket_path); diff --git a/i3-nagbar/main.c b/i3-nagbar/main.c index 38740774..674fcb7d 100644 --- a/i3-nagbar/main.c +++ b/i3-nagbar/main.c @@ -470,6 +470,11 @@ int main(int argc, char *argv[]) { font = load_font(pattern, true); set_font(&font); +#if defined(__OpenBSD__) + if (pledge("stdio rpath wpath cpath getpw proc exec", NULL) == -1) + err(EXIT_FAILURE, "pledge"); +#endif + xcb_rectangle_t win_pos = get_window_position(); xcb_cursor_t cursor; diff --git a/src/log.c b/src/log.c index b7ad072f..e8a08b53 100644 --- a/src/log.c +++ b/src/log.c @@ -58,6 +58,8 @@ static char *loglastwrap; static int logbuffer_size; /* File descriptor for shm_open. */ static int logbuffer_shm; +/* Size (in bytes) of physical memory */ +static long long physical_mem_bytes; /* * Writes the offsets for the next write and for the last wrap to the @@ -89,6 +91,16 @@ void init_logging(void) { } } } + if (physical_mem_bytes == 0) { +#if defined(__APPLE__) + int mib[2] = {CTL_HW, HW_MEMSIZE}; + size_t length = sizeof(long long); + sysctl(mib, 2, &physical_mem_bytes, &length, NULL, 0); +#else + physical_mem_bytes = (long long)sysconf(_SC_PHYS_PAGES) * + sysconf(_SC_PAGESIZE); +#endif + } /* Start SHM logging if shmlog_size is > 0. shmlog_size is SHMLOG_SIZE by * default on development versions, and 0 on release versions. If it is * not > 0, the user has turned it off, so let's close the logbuffer. */ @@ -108,15 +120,6 @@ void open_logbuffer(void) { * For 512 MiB of RAM this will lead to a 5 MiB log buffer. * At the moment (2011-12-10), no testcase leads to an i3 log * of more than ~ 600 KiB. */ - long long physical_mem_bytes; -#if defined(__APPLE__) - int mib[2] = {CTL_HW, HW_MEMSIZE}; - size_t length = sizeof(long long); - sysctl(mib, 2, &physical_mem_bytes, &length, NULL, 0); -#else - physical_mem_bytes = (long long)sysconf(_SC_PHYS_PAGES) * - sysconf(_SC_PAGESIZE); -#endif logbuffer_size = min(physical_mem_bytes * 0.01, shmlog_size); #if defined(__FreeBSD__) sasprintf(&shmlogname, "/tmp/i3-log-%d", getpid()); diff --git a/src/main.c b/src/main.c index 78835e08..b2ce17d8 100644 --- a/src/main.c +++ b/src/main.c @@ -802,6 +802,11 @@ int main(int argc, char *argv[]) { xcb_free_pixmap(conn, pixmap); } +#if defined(__OpenBSD__) + if (pledge("stdio rpath wpath cpath proc exec unix", NULL) == -1) + err(EXIT_FAILURE, "pledge"); +#endif + struct sigaction action; action.sa_sigaction = handle_signal; From 328035fb7e98630862ae8b43088631f62b807c77 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ingo=20B=C3=BCrk?= Date: Mon, 11 Jan 2016 20:53:26 +0100 Subject: [PATCH 155/187] Handle the EWMH atom _NET_WM_DESKTOP. We already claim _NET_WM_DESKTOP support in _NET_SUPPORTED since around 2009, but haven't actually done anything with it. However, especially pagers like gnome-panel rely on this property to be updated and many tools, like GTK, want to use the corresponding client messages to make a window sticky, move it around etc. This patch implements full support according to the EWMH spec. This means: * We set the property on all windows when managing it. * We keep the property updated on all windows at all times. * We read and respect the property upon managing a window if it was set before mapping the window. * We react to client messages for it. * We remove the property on withdrawn windows. Note that the special value 0xFFFFFFFF, according to the spec, means that the window shall be shown on all workspaces. We do this by making it sticky and float it. This shows it on all workspaces at least on the output it is on. Furthermore, the spec gives us the freedom to ignore _NET_WM_DESKTOP when managing a window if we have good reason to. In our case, we give window swallowing a higher priority since the user would likely expect that and we want to keep placeholder windows only around for as long as we have to. However, we do prioritize this property over, for example, startup notifications. fixes #2153 fixes #1507 fixes #938 --- include/data.h | 3 + include/ewmh.h | 25 ++ include/workspace.h | 8 + src/commands.c | 2 + src/con.c | 1 + src/ewmh.c | 144 ++++++- src/handlers.c | 72 ++-- src/manage.c | 46 ++- src/move.c | 3 + src/workspace.c | 2 + testcases/t/253-multiple-net-wm-state-atoms.t | 1 - testcases/t/529-net-wm-desktop.t | 350 ++++++++++++++++++ 12 files changed, 609 insertions(+), 48 deletions(-) create mode 100644 testcases/t/529-net-wm-desktop.t diff --git a/include/data.h b/include/data.h index 959068e0..3a059e7b 100644 --- a/include/data.h +++ b/include/data.h @@ -398,6 +398,9 @@ struct Window { /** The _NET_WM_WINDOW_TYPE for this window. */ xcb_atom_t window_type; + /** The _NET_WM_DESKTOP for this window. */ + uint32_t wm_desktop; + /** Whether the window says it is a dock window */ enum { W_NODOCK = 0, W_DOCK_TOP = 1, diff --git a/include/ewmh.h b/include/ewmh.h index 7ed9b544..2a55ab9f 100644 --- a/include/ewmh.h +++ b/include/ewmh.h @@ -36,6 +36,13 @@ void ewmh_update_desktop_names(void); */ void ewmh_update_desktop_viewport(void); +/** + * Updates _NET_WM_DESKTOP for all windows. + * A request will only be made if the cached value differs from the calculated value. + * + */ +void ewmh_update_wm_desktop(void); + /** * Updates _NET_ACTIVE_WINDOW with the currently focused window. * @@ -96,3 +103,21 @@ void ewmh_setup_hints(void); * */ void ewmh_update_workarea(void); + +/** + * Returns the workspace container as enumerated by the EWMH desktop model. + * Returns NULL if no workspace could be found for the index. + * + * This is the reverse of ewmh_get_workspace_index. + * + */ +Con *ewmh_get_workspace_by_index(uint32_t idx); + +/** + * Returns the EWMH desktop index for the workspace the given container is on. + * Returns NET_WM_DESKTOP_NONE if the desktop index cannot be determined. + * + * This is the reverse of ewmh_get_workspace_by_index. + * + */ +uint32_t ewmh_get_workspace_index(Con *con); diff --git a/include/workspace.h b/include/workspace.h index 1bee64e0..0ff5cd30 100644 --- a/include/workspace.h +++ b/include/workspace.h @@ -14,6 +14,14 @@ #include "tree.h" #include "randr.h" +/* We use NET_WM_DESKTOP_NONE for cases where we cannot determine the EWMH + * desktop index for a window. We cannot use a negative value like -1 since we + * need to use uint32_t as we actually need the full range of it. This is + * technically dangerous, but it's safe to assume that we will never have more + * than 4294967279 workspaces open at a time. */ +#define NET_WM_DESKTOP_NONE 0xFFFFFFF0 +#define NET_WM_DESKTOP_ALL 0xFFFFFFFF + /** * Returns a pointer to the workspace with the given number (starting at 0), * creating the workspace if necessary (by allocating the necessary amount of diff --git a/src/commands.c b/src/commands.c index 525a30fc..482560b8 100644 --- a/src/commands.c +++ b/src/commands.c @@ -1542,6 +1542,8 @@ void cmd_sticky(I3_CMD, const char *action) { * sure it gets pushed to the front now. */ output_push_sticky_windows(focused); + ewmh_update_wm_desktop(); + cmd_output->needs_tree_render = true; ysuccess(true); } diff --git a/src/con.c b/src/con.c index a3a2f2e3..cd17f9e5 100644 --- a/src/con.c +++ b/src/con.c @@ -1080,6 +1080,7 @@ static bool _con_move_to_con(Con *con, Con *target, bool behind_focused, bool fi CALL(parent, on_remove_child); ipc_send_window_event("move", con); + ewmh_update_wm_desktop(); return true; } diff --git a/src/ewmh.c b/src/ewmh.c index a5c90175..05f4d3cd 100644 --- a/src/ewmh.c +++ b/src/ewmh.c @@ -21,24 +21,9 @@ xcb_window_t ewmh_window; * */ void ewmh_update_current_desktop(void) { - Con *focused_ws = con_get_workspace(focused); - Con *output; - uint32_t idx = 0; - /* We count to get the index of this workspace because named workspaces - * don’t have the ->num property */ - TAILQ_FOREACH(output, &(croot->nodes_head), nodes) { - Con *ws; - TAILQ_FOREACH(ws, &(output_get_content(output)->nodes_head), nodes) { - if (STARTS_WITH(ws->name, "__")) - continue; - - if (ws == focused_ws) { - xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root, - A__NET_CURRENT_DESKTOP, XCB_ATOM_CARDINAL, 32, 1, &idx); - return; - } - ++idx; - } + const uint32_t idx = ewmh_get_workspace_index(focused); + if (idx != NET_WM_DESKTOP_NONE) { + xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root, A__NET_CURRENT_DESKTOP, XCB_ATOM_CARDINAL, 32, 1, &idx); } } @@ -138,6 +123,71 @@ void ewmh_update_desktop_viewport(void) { A__NET_DESKTOP_VIEWPORT, XCB_ATOM_CARDINAL, 32, current_position, &viewports); } +static void ewmh_update_wm_desktop_recursively(Con *con, const uint32_t desktop) { + /* Recursively call this to descend through the entire subtree. */ + Con *child; + TAILQ_FOREACH(child, &(con->nodes_head), nodes) { + ewmh_update_wm_desktop_recursively(child, desktop); + } + /* If con is a workspace, we also need to go through the floating windows on it. */ + if (con->type == CT_WORKSPACE) { + TAILQ_FOREACH(child, &(con->floating_head), floating_windows) { + ewmh_update_wm_desktop_recursively(child, desktop); + } + } + + if (!con_has_managed_window(con)) + return; + + const xcb_window_t window = con->window->id; + + uint32_t wm_desktop = desktop; + /* Sticky windows are only actually sticky when they are floating or inside + * a floating container. This is technically still slightly wrong, since + * sticky windows will only be on all workspaces on this output, but we + * ignore multi-monitor situations for this since the spec isn't too + * precise on this anyway. */ + if (con_is_sticky(con) && con_is_floating(con)) { + wm_desktop = NET_WM_DESKTOP_ALL; + } + + /* If this is the cached value, we don't need to do anything. */ + if (con->window->wm_desktop == wm_desktop) + return; + con->window->wm_desktop = wm_desktop; + + if (wm_desktop != NET_WM_DESKTOP_NONE) { + DLOG("Setting _NET_WM_DESKTOP = %d for window 0x%08x.\n", wm_desktop, window); + xcb_change_property(conn, XCB_PROP_MODE_REPLACE, window, A__NET_WM_DESKTOP, XCB_ATOM_CARDINAL, 32, 1, &wm_desktop); + } else { + /* If we can't determine the workspace index, delete the property. We'd + * rather not set it than lie. */ + ELOG("Failed to determine the proper EWMH desktop index for window 0x%08x, deleting _NET_WM_DESKTOP.\n", window); + xcb_delete_property(conn, window, A__NET_WM_DESKTOP); + } +} + +/* + * Updates _NET_WM_DESKTOP for all windows. + * A request will only be made if the cached value differs from the calculated value. + * + */ +void ewmh_update_wm_desktop(void) { + uint32_t desktop = 0; + + Con *output; + TAILQ_FOREACH(output, &(croot->nodes_head), nodes) { + Con *workspace; + TAILQ_FOREACH(workspace, &(output_get_content(output)->nodes_head), nodes) { + if (con_is_internal(workspace)) + continue; + + ewmh_update_wm_desktop_recursively(workspace, desktop); + ++desktop; + } + } +} + /* * Updates _NET_ACTIVE_WINDOW with the currently focused window. * @@ -270,3 +320,61 @@ void ewmh_setup_hints(void) { xcb_map_window(conn, ewmh_window); xcb_configure_window(conn, ewmh_window, XCB_CONFIG_WINDOW_STACK_MODE, (uint32_t[]){XCB_STACK_MODE_BELOW}); } + +/* + * Returns the workspace container as enumerated by the EWMH desktop model. + * Returns NULL if no workspace could be found for the index. + * + * This is the reverse of ewmh_get_workspace_index. + * + */ +Con *ewmh_get_workspace_by_index(uint32_t idx) { + if (idx == NET_WM_DESKTOP_NONE) + return NULL; + + uint32_t current_index = 0; + + Con *output; + TAILQ_FOREACH(output, &(croot->nodes_head), nodes) { + Con *workspace; + TAILQ_FOREACH(workspace, &(output_get_content(output)->nodes_head), nodes) { + if (con_is_internal(workspace)) + continue; + + if (current_index == idx) + return workspace; + + ++current_index; + } + } + + return NULL; +} + +/* + * Returns the EWMH desktop index for the workspace the given container is on. + * Returns NET_WM_DESKTOP_NONE if the desktop index cannot be determined. + * + * This is the reverse of ewmh_get_workspace_by_index. + * + */ +uint32_t ewmh_get_workspace_index(Con *con) { + uint32_t index = 0; + + Con *workspace = con_get_workspace(con); + Con *output; + TAILQ_FOREACH(output, &(croot->nodes_head), nodes) { + Con *current; + TAILQ_FOREACH(current, &(output_get_content(output)->nodes_head), nodes) { + if (con_is_internal(current)) + continue; + + if (current == workspace) + return index; + + ++index; + } + } + + return NET_WM_DESKTOP_NONE; +} diff --git a/src/handlers.c b/src/handlers.c index 6cbc54f2..50e218c8 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -503,6 +503,9 @@ static void handle_unmap_notify_event(xcb_unmap_notify_event_t *event) { goto ignore_end; } + /* Since we close the container, we need to unset _NET_WM_DESKTOP according to the spec. */ + xcb_delete_property(conn, event->window, A__NET_WM_DESKTOP); + tree_close_internal(con, DONT_KILL_WINDOW, false, false); tree_render(); @@ -735,7 +738,9 @@ static void handle_client_message(xcb_client_message_event_t *event) { con->sticky = !con->sticky; DLOG("New sticky status for con = %p is %i.\n", con, con->sticky); + ewmh_update_sticky(con->window->id, con->sticky); output_push_sticky_windows(focused); + ewmh_update_wm_desktop(); } tree_render(); @@ -839,32 +844,48 @@ static void handle_client_message(xcb_client_message_event_t *event) { * a request to focus the given workspace. See * http://standards.freedesktop.org/wm-spec/latest/ar01s03.html#idm140251368135008 * */ - Con *output; - uint32_t idx = 0; DLOG("Request to change current desktop to index %d\n", event->data.data32[0]); - - TAILQ_FOREACH(output, &(croot->nodes_head), nodes) { - Con *ws; - TAILQ_FOREACH(ws, &(output_get_content(output)->nodes_head), nodes) { - if (STARTS_WITH(ws->name, "__")) - continue; - - if (idx == event->data.data32[0]) { - /* data32[1] is a timestamp used to prevent focus race conditions */ - if (event->data.data32[1]) - last_timestamp = event->data.data32[1]; - - DLOG("Handling request to focus workspace %s\n", ws->name); - - workspace_show(ws); - tree_render(); - - return; - } - - ++idx; - } + Con *ws = ewmh_get_workspace_by_index(event->data.data32[0]); + if (ws == NULL) { + ELOG("Could not determine workspace for this index, ignoring request.\n"); + return; } + + DLOG("Handling request to focus workspace %s\n", ws->name); + workspace_show(ws); + tree_render(); + } else if (event->type == A__NET_WM_DESKTOP) { + uint32_t index = event->data.data32[0]; + DLOG("Request to move window %d to EWMH desktop index %d\n", event->window, index); + + Con *con = con_by_window_id(event->window); + if (con == NULL) { + DLOG("Couldn't find con for window %d, ignoring the request.\n", event->window); + return; + } + + if (index == NET_WM_DESKTOP_ALL) { + /* The window is requesting to be visible on all workspaces, so + * let's float it and make it sticky. */ + DLOG("The window was requested to be visible on all workspaces, making it sticky and floating.\n"); + + floating_enable(con, false); + + con->sticky = true; + ewmh_update_sticky(con->window->id, true); + output_push_sticky_windows(focused); + } else { + Con *ws = ewmh_get_workspace_by_index(index); + if (ws == NULL) { + ELOG("Could not determine workspace for this index, ignoring request.\n"); + return; + } + + con_move_to_workspace(con, ws, false, false, true); + } + + tree_render(); + ewmh_update_wm_desktop(); } else if (event->type == A__NET_CLOSE_WINDOW) { /* * Pagers wanting to close a window MUST send a _NET_CLOSE_WINDOW @@ -915,8 +936,7 @@ static void handle_client_message(xcb_client_message_event_t *event) { break; } } else { - DLOG("unhandled clientmessage\n"); - return; + DLOG("Skipping client message for unhandled type %d\n", event->type); } } diff --git a/src/manage.c b/src/manage.c index 2bcb47f3..93272f1b 100644 --- a/src/manage.c +++ b/src/manage.c @@ -90,7 +90,7 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki utf8_title_cookie, title_cookie, class_cookie, leader_cookie, transient_cookie, role_cookie, startup_id_cookie, wm_hints_cookie, - wm_normal_hints_cookie, motif_wm_hints_cookie, wm_user_time_cookie; + wm_normal_hints_cookie, motif_wm_hints_cookie, wm_user_time_cookie, wm_desktop_cookie; geomc = xcb_get_geometry(conn, d); @@ -162,6 +162,7 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki wm_normal_hints_cookie = xcb_icccm_get_wm_normal_hints(conn, window); motif_wm_hints_cookie = GET_PROPERTY(A__MOTIF_WM_HINTS, 5 * sizeof(uint64_t)); wm_user_time_cookie = GET_PROPERTY(A__NET_WM_USER_TIME, UINT32_MAX); + wm_desktop_cookie = GET_PROPERTY(A__NET_WM_DESKTOP, UINT32_MAX); DLOG("Managing window 0x%08x\n", window); @@ -194,6 +195,16 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki char *startup_ws = startup_workspace_for_window(cwindow, startup_id_reply); DLOG("startup workspace = %s\n", startup_ws); + /* Get _NET_WM_DESKTOP if it was set. */ + xcb_get_property_reply_t *wm_desktop_reply; + wm_desktop_reply = xcb_get_property_reply(conn, wm_desktop_cookie, NULL); + cwindow->wm_desktop = NET_WM_DESKTOP_NONE; + if (wm_desktop_reply != NULL && xcb_get_property_value_length(wm_desktop_reply) != 0) { + uint32_t *wm_desktops = xcb_get_property_value(wm_desktop_reply); + cwindow->wm_desktop = (int32_t)wm_desktops[0]; + } + FREE(wm_desktop_reply); + /* check if the window needs WM_TAKE_FOCUS */ cwindow->needs_take_focus = window_supports_protocol(cwindow->id, A_WM_TAKE_FOCUS); @@ -244,6 +255,8 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki nc = con_for_window(search_at, cwindow, &match); const bool match_from_restart_mode = (match && match->restart_mode); if (nc == NULL) { + Con *wm_desktop_ws = NULL; + /* If not, check if it is assigned to a specific workspace */ if ((assignment = assignment_for(cwindow, A_TO_WORKSPACE))) { DLOG("Assignment matches (%p)\n", match); @@ -258,9 +271,23 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki /* set the urgency hint on the window if the workspace is not visible */ if (!workspace_is_visible(assigned_ws)) urgency_hint = true; + } else if (cwindow->wm_desktop != NET_WM_DESKTOP_NONE && + cwindow->wm_desktop != NET_WM_DESKTOP_ALL && + (wm_desktop_ws = ewmh_get_workspace_by_index(cwindow->wm_desktop)) != NULL) { + /* If _NET_WM_DESKTOP is set to a specific desktop, we open it + * there. Note that we ignore the special value 0xFFFFFFFF here + * since such a window will be made sticky anyway. */ + + DLOG("Using workspace %p / %s because _NET_WM_DESKTOP = %d.\n", + wm_desktop_ws, wm_desktop_ws->name, cwindow->wm_desktop); + + nc = con_descend_tiling_focused(wm_desktop_ws); + if (nc->type == CT_WORKSPACE) + nc = tree_open_con(nc, cwindow); + else + nc = tree_open_con(nc->parent, cwindow); } else if (startup_ws) { - /* If it’s not assigned, but was started on a specific workspace, - * we want to open it there */ + /* If it was started on a specific workspace, we want to open it there. */ DLOG("Using workspace on which this application was started (%s)\n", startup_ws); nc = con_descend_tiling_focused(workspace_get(startup_ws, NULL)); DLOG("focused on ws %s: %p / %s\n", startup_ws, nc, nc->name); @@ -393,6 +420,12 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki if (xcb_reply_contains_atom(state_reply, A__NET_WM_STATE_STICKY)) nc->sticky = true; + if (cwindow->wm_desktop == NET_WM_DESKTOP_ALL) { + DLOG("This window has _NET_WM_DESKTOP = 0xFFFFFFFF. Will float it and make it sticky.\n"); + nc->sticky = true; + want_floating = true; + } + FREE(state_reply); FREE(type_reply); @@ -574,6 +607,13 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki * needs to be on the final workspace first. */ con_set_urgency(nc, urgency_hint); + /* Update _NET_WM_DESKTOP. We invalidate the cached value first to force an update. */ + cwindow->wm_desktop = NET_WM_DESKTOP_NONE; + ewmh_update_wm_desktop(); + + /* If a sticky window was mapped onto another workspace, make sure to pop it to the front. */ + output_push_sticky_windows(focused); + geom_out: free(geom); out: diff --git a/src/move.c b/src/move.c index bd228a1c..87f78ee3 100644 --- a/src/move.c +++ b/src/move.c @@ -206,6 +206,7 @@ void tree_move(Con *con, int direction) { DLOG("Swapped.\n"); ipc_send_window_event("move", con); + ewmh_update_wm_desktop(); return; } @@ -214,6 +215,7 @@ void tree_move(Con *con, int direction) { * try to move it to a workspace on a different output */ move_to_output_directed(con, direction); ipc_send_window_event("move", con); + ewmh_update_wm_desktop(); return; } @@ -274,4 +276,5 @@ end: tree_flatten(croot); ipc_send_window_event("move", con); + ewmh_update_wm_desktop(); } diff --git a/src/workspace.c b/src/workspace.c index 923bfc83..ba19cb5f 100644 --- a/src/workspace.c +++ b/src/workspace.c @@ -101,6 +101,7 @@ Con *workspace_get(const char *num, bool *created) { ewmh_update_number_of_desktops(); ewmh_update_desktop_names(); ewmh_update_desktop_viewport(); + ewmh_update_wm_desktop(); if (created != NULL) *created = true; } else if (created != NULL) { @@ -463,6 +464,7 @@ static void _workspace_show(Con *workspace) { ewmh_update_number_of_desktops(); ewmh_update_desktop_names(); ewmh_update_desktop_viewport(); + ewmh_update_wm_desktop(); } } diff --git a/testcases/t/253-multiple-net-wm-state-atoms.t b/testcases/t/253-multiple-net-wm-state-atoms.t index 0ce7f9fd..bbd6c521 100644 --- a/testcases/t/253-multiple-net-wm-state-atoms.t +++ b/testcases/t/253-multiple-net-wm-state-atoms.t @@ -20,7 +20,6 @@ use X11::XCB qw(:all); sub get_wm_state { sync_with_i3; - my $atom = $x->atom(name => '_NET_WM_STATE_HIDDEN'); my ($con) = @_; my $cookie = $x->get_property( diff --git a/testcases/t/529-net-wm-desktop.t b/testcases/t/529-net-wm-desktop.t new file mode 100644 index 00000000..f6a3b218 --- /dev/null +++ b/testcases/t/529-net-wm-desktop.t @@ -0,0 +1,350 @@ +#!perl +# vim:ts=4:sw=4:expandtab +# +# Please read the following documents before working on tests: +# • http://build.i3wm.org/docs/testsuite.html +# (or docs/testsuite) +# +# • http://build.i3wm.org/docs/lib-i3test.html +# (alternatively: perldoc ./testcases/lib/i3test.pm) +# +# • http://build.i3wm.org/docs/ipc.html +# (or docs/ipc) +# +# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# (unless you are already familiar with Perl) +# +# Tests for _NET_WM_DESKTOP. +# Ticket: #2153 +use i3test i3_autostart => 0; +use X11::XCB qw(:all); + +############################################################################### + +sub get_net_wm_desktop { + sync_with_i3; + + my ($con) = @_; + my $cookie = $x->get_property( + 0, + $con->{id}, + $x->atom(name => '_NET_WM_DESKTOP')->id, + $x->atom(name => 'CARDINAL')->id, + 0, + 1 + ); + + my $reply = $x->get_property_reply($cookie->{sequence}); + return undef if $reply->{length} != 1; + + return unpack("L", $reply->{value}); +} + +sub send_net_wm_desktop { + my ($con, $idx) = @_; + my $msg = pack "CCSLLLLLL", + X11::XCB::CLIENT_MESSAGE, 32, 0, + $con->{id}, + $x->atom(name => '_NET_WM_DESKTOP')->id, + $idx, 0, 0, 0, 0; + + $x->send_event(0, $x->get_root_window(), X11::XCB::EVENT_MASK_SUBSTRUCTURE_REDIRECT, $msg); + sync_with_i3; +} + +sub open_window_with_net_wm_desktop { + my $idx = shift; + my $window = open_window( + before_map => sub { + my ($window) = @_; + $x->change_property( + PROP_MODE_REPLACE, + $window->id, + $x->atom(name => '_NET_WM_DESKTOP')->id, + $x->atom(name => 'CARDINAL')->id, + 32, 1, + pack('L', $idx), + ); + }, + ); + + return $window; +} + +# We need to kill all windows in between tests since they survive the i3 restart +# and will interfere with the following tests. +sub kill_windows { + sync_with_i3; + cmd '[title="Window.*"] kill'; +} + +############################################################################### + +my ($config, $config_mm, $pid, $con); + +$config = <{floating_nodes}}, 1, 'The window is floating'); +ok(get_ws('0')->{floating_nodes}->[0]->{nodes}->[0]->{sticky}, 'The window is sticky'); + +kill_windows; +exit_gracefully($pid); + +############################################################################### +# _NET_WM_DESKTOP is updated when the window is moved to another workspace +# on the same output. +############################################################################### + +$pid = launch_with_config($config); + +cmd 'workspace 0'; +open_window; +cmd 'workspace 1'; +open_window; +cmd 'workspace 0'; +$con = open_window; + +cmd 'move window to workspace 1'; + +is(get_net_wm_desktop($con), 1, '_NET_WM_DESKTOP is updated when moving the window'); + +kill_windows; +exit_gracefully($pid); + +############################################################################### +# _NET_WM_DESKTOP is updated when the floating window is moved to another +# workspace on the same output. +############################################################################### + +$pid = launch_with_config($config); + +cmd 'workspace 0'; +open_window; +cmd 'workspace 1'; +open_window; +cmd 'workspace 0'; +$con = open_window; +cmd 'floating enable'; + +cmd 'move window to workspace 1'; + +is(get_net_wm_desktop($con), 1, '_NET_WM_DESKTOP is updated when moving the window'); + +kill_windows; +exit_gracefully($pid); + +############################################################################### +# _NET_WM_DESKTOP is updated when the window is moved to another workspace +# on another output. +############################################################################### + +$pid = launch_with_config($config_mm); + +cmd 'workspace 0'; +open_window; +cmd 'workspace 10'; +open_window; +cmd 'workspace 0'; +$con = open_window; + +cmd 'move window to workspace 10'; + +is(get_net_wm_desktop($con), 1, '_NET_WM_DESKTOP is updated when moving the window'); + +kill_windows; +exit_gracefully($pid); + +############################################################################### +# _NET_WM_DESKTOP is removed when the window is withdrawn. +############################################################################### + +$pid = launch_with_config($config); + +$con = open_window; +is(get_net_wm_desktop($con), 0, '_NET_WM_DESKTOP is set (sanity check)'); + +$con->unmap; +wait_for_unmap($con); + +is(get_net_wm_desktop($con), undef, '_NET_WM_DESKTOP is removed'); + +kill_windows; +exit_gracefully($pid); + +############################################################################### +# A _NET_WM_DESKTOP client message sent to the root window moves a window +# to the correct workspace. +############################################################################### + +$pid = launch_with_config($config); + +cmd 'workspace 0'; +open_window; +cmd 'workspace 1'; +open_window; +cmd 'workspace 0'; + +$con = open_window; +is_num_children('0', 2, 'The window is on workspace 0'); + +send_net_wm_desktop($con, 1); + +is_num_children('0', 1, 'The window is no longer on workspace 0'); +is_num_children('1', 2, 'The window is now on workspace 1'); +is(get_net_wm_desktop($con), 1, '_NET_WM_DESKTOP is updated'); + +kill_windows; +exit_gracefully($pid); + +############################################################################### +# A _NET_WM_DESKTOP client message sent to the root window can make a window +# sticky. +############################################################################### + +$pid = launch_with_config($config); + +cmd 'workspace 0'; +$con = open_window; + +send_net_wm_desktop($con, 0xFFFFFFFF); + +is(get_net_wm_desktop($con), 0xFFFFFFFF, '_NET_WM_DESKTOP is updated'); +is(@{get_ws('0')->{floating_nodes}}, 1, 'The window is floating'); +ok(get_ws('0')->{floating_nodes}->[0]->{nodes}->[0]->{sticky}, 'The window is sticky'); + +kill_windows; +exit_gracefully($pid); + +############################################################################### +# _NET_WM_DESKTOP is updated when a new workspace with a lower number is +# opened and closed. +############################################################################### + +$pid = launch_with_config($config); + +cmd 'workspace 1'; +$con = open_window; +is(get_net_wm_desktop($con), 0, '_NET_WM_DESKTOP is set sanity check)'); + +cmd 'workspace 0'; +is(get_net_wm_desktop($con), 1, '_NET_WM_DESKTOP is updated'); + +kill_windows; +exit_gracefully($pid); + +############################################################################### +# _NET_WM_DESKTOP is updated when a window is made sticky by command. +############################################################################### + +$pid = launch_with_config($config); + +cmd 'workspace 0'; +$con = open_window; +cmd 'floating enable'; +is(get_net_wm_desktop($con), 0, '_NET_WM_DESKTOP is set sanity check)'); + +cmd 'sticky enable'; +is(get_net_wm_desktop($con), 0xFFFFFFFF, '_NET_WM_DESKTOP is updated'); + +kill_windows; +exit_gracefully($pid); + +############################################################################### +# _NET_WM_DESKTOP is updated when a window is made sticky by client message. +############################################################################### + +$pid = launch_with_config($config); + +cmd 'workspace 0'; +$con = open_window; +cmd 'floating enable'; +is(get_net_wm_desktop($con), 0, '_NET_WM_DESKTOP is set sanity check)'); + +my $msg = pack "CCSLLLLLL", + X11::XCB::CLIENT_MESSAGE, 32, 0, + $con->{id}, + $x->atom(name => '_NET_WM_STATE')->id, + 1, + $x->atom(name => '_NET_WM_STATE_STICKY')->id, + 0, 0, 0; + +$x->send_event(0, $x->get_root_window(), X11::XCB::EVENT_MASK_SUBSTRUCTURE_REDIRECT, $msg); +sync_with_i3; + +is(get_net_wm_desktop($con), 0xFFFFFFFF, '_NET_WM_DESKTOP is updated'); + +kill_windows; +exit_gracefully($pid); + +############################################################################### + +done_testing; From 95b60b170e0b3cd25e03fef57e1968d9782f66b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ingo=20B=C3=BCrk?= Date: Wed, 20 Jan 2016 22:28:20 +0100 Subject: [PATCH 156/187] Don't grab the buttons on the root window. We don't actually need to grab the buttons to fix #2097, but doing so will cause a freeze due to unreleased events. We partially revert 6f12f02 which avoids the freeze, but doesn't break functionality. relates to #2097 fixes #2168 --- src/bindings.c | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/bindings.c b/src/bindings.c index 3463f831..351a5862 100644 --- a/src/bindings.c +++ b/src/bindings.c @@ -164,10 +164,6 @@ void regrab_all_buttons(xcb_connection_t *conn) { xcb_grab_buttons(conn, con->window->id, grab_scrollwheel); } - /* Also grab the root window to allow bindings to work on there as well. */ - xcb_ungrab_button(conn, XCB_BUTTON_INDEX_ANY, root, XCB_BUTTON_MASK_ANY); - xcb_grab_buttons(conn, root, grab_scrollwheel); - xcb_ungrab_server(conn); } From 3c0ba081dfa00747a31e2355f683186cda1b8638 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ingo=20B=C3=BCrk?= Date: Fri, 22 Jan 2016 20:32:51 +0100 Subject: [PATCH 157/187] Don't ignore focus on _NET_WM_DESKTOP client messages. Ignoring the focus leads to a broken focus if the pointer is, e.g., over i3bar at the point in time the window is moved by the client message. It also causes i3bar's workspace display to break. Thanks to GermainZ for reporting. --- src/handlers.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/handlers.c b/src/handlers.c index 50e218c8..7e116ce7 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -881,7 +881,7 @@ static void handle_client_message(xcb_client_message_event_t *event) { return; } - con_move_to_workspace(con, ws, false, false, true); + con_move_to_workspace(con, ws, true, false, false); } tree_render(); From 1041572769566375e4a91dc27e7c95caf1129201 Mon Sep 17 00:00:00 2001 From: David Coppa Date: Tue, 26 Jan 2016 09:55:24 +0100 Subject: [PATCH 158/187] Remove commented-out code. --- src/commands.c | 96 -------------------------------------------------- 1 file changed, 96 deletions(-) diff --git a/src/commands.c b/src/commands.c index 482560b8..9f5af8c2 100644 --- a/src/commands.c +++ b/src/commands.c @@ -142,102 +142,6 @@ static Con *maybe_auto_back_and_forth_workspace(Con *workspace) { return workspace; } -// This code is commented out because we might recycle it for popping up error -// messages on parser errors. -#if 0 -static pid_t migration_pid = -1; - -/* - * Handler which will be called when we get a SIGCHLD for the nagbar, meaning - * it exited (or could not be started, depending on the exit code). - * - */ -static void nagbar_exited(EV_P_ ev_child *watcher, int revents) { - ev_child_stop(EV_A_ watcher); - if (!WIFEXITED(watcher->rstatus)) { - fprintf(stderr, "ERROR: i3-nagbar did not exit normally.\n"); - return; - } - - int exitcode = WEXITSTATUS(watcher->rstatus); - printf("i3-nagbar process exited with status %d\n", exitcode); - if (exitcode == 2) { - fprintf(stderr, "ERROR: i3-nagbar could not be found. Is it correctly installed on your system?\n"); - } - - migration_pid = -1; -} - -/* We need ev >= 4 for the following code. Since it is not *that* important (it - * only makes sure that there are no i3-nagbar instances left behind) we still - * support old systems with libev 3. */ -#if EV_VERSION_MAJOR >= 4 -/* - * Cleanup handler. Will be called when i3 exits. Kills i3-nagbar with signal - * SIGKILL (9) to make sure there are no left-over i3-nagbar processes. - * - */ -static void nagbar_cleanup(EV_P_ ev_cleanup *watcher, int revent) { - if (migration_pid != -1) { - LOG("Sending SIGKILL (9) to i3-nagbar with PID %d\n", migration_pid); - kill(migration_pid, SIGKILL); - } -} -#endif - -void cmd_MIGRATION_start_nagbar(void) { - if (migration_pid != -1) { - fprintf(stderr, "i3-nagbar already running.\n"); - return; - } - fprintf(stderr, "Starting i3-nagbar, command parsing differs from expected output.\n"); - ELOG("Please report this on IRC or in the bugtracker. Make sure to include the full debug level logfile:\n"); - ELOG("i3-dump-log | gzip -9c > /tmp/i3.log.gz\n"); - ELOG("FYI: Your i3 version is " I3_VERSION "\n"); - migration_pid = fork(); - if (migration_pid == -1) { - warn("Could not fork()"); - return; - } - - /* child */ - if (migration_pid == 0) { - char *pageraction; - sasprintf(&pageraction, "i3-sensible-terminal -e i3-sensible-pager \"%s\"", errorfilename); - char *argv[] = { - NULL, /* will be replaced by the executable path */ - "-t", - "error", - "-m", - "You found a parsing error. Please, please, please, report it!", - "-b", - "show errors", - pageraction, - NULL - }; - exec_i3_utility("i3-nagbar", argv); - } - - /* parent */ - /* install a child watcher */ - ev_child *child = smalloc(sizeof(ev_child)); - ev_child_init(child, &nagbar_exited, migration_pid, 0); - ev_child_start(main_loop, child); - -/* We need ev >= 4 for the following code. Since it is not *that* important (it - * only makes sure that there are no i3-nagbar instances left behind) we still - * support old systems with libev 3. */ -#if EV_VERSION_MAJOR >= 4 - /* install a cleanup watcher (will be called when i3 exits and i3-nagbar is - * still running) */ - ev_cleanup *cleanup = smalloc(sizeof(ev_cleanup)); - ev_cleanup_init(cleanup, nagbar_cleanup); - ev_cleanup_start(main_loop, cleanup); -#endif -} - -#endif - /******************************************************************************* * Criteria functions. ******************************************************************************/ From 7a25d3f1492ca3db1009bc48a96db2d2d512b34c Mon Sep 17 00:00:00 2001 From: David Coppa Date: Tue, 26 Jan 2016 09:57:59 +0100 Subject: [PATCH 159/187] Fix i3-nagbar example. --- man/i3-nagbar.man | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/man/i3-nagbar.man b/man/i3-nagbar.man index 9e6619fa..77fdd80b 100644 --- a/man/i3-nagbar.man +++ b/man/i3-nagbar.man @@ -44,7 +44,7 @@ after modifying the configuration file. ------------------------------------------------ i3-nagbar -m 'You have an error in your i3 config file!' \ --b 'edit config' 'xterm $EDITOR ~/.i3/config' +-b 'edit config' 'i3-sensible-editor ~/.config/i3/config' ------------------------------------------------ == SEE ALSO From 1c7b25f25afef292a82de27e1f6bae6524f8e5f6 Mon Sep 17 00:00:00 2001 From: Johannes Lange Date: Sat, 30 Jan 2016 13:40:45 +0100 Subject: [PATCH 160/187] changed old "cfgparse.y" reference to "config_parser.c" --- docs/hacking-howto | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/hacking-howto b/docs/hacking-howto index d6e2b67e..247a1791 100644 --- a/docs/hacking-howto +++ b/docs/hacking-howto @@ -117,7 +117,7 @@ containers, searching containers, getting specific properties from containers, src/config.c:: Contains all functions handling the configuration file (calling the parser -(src/cfgparse.y) with the correct path, switching key bindings mode). +src/config_parser.c) with the correct path, switching key bindings mode). src/debug.c:: Contains debugging functions to print unhandled X events. From 8f2d066dc6440f94d896127e23633263fdb2b041 Mon Sep 17 00:00:00 2001 From: Nick Lanham Date: Sun, 31 Jan 2016 12:21:47 -0800 Subject: [PATCH 161/187] Fix issue #2184. don't send atom, but raw code XEMBED messages aren't atoms, but just codes, so i3bar should just send XEMBED_EMBEDDED_NOTIFY rather than indexing the atom array. --- i3bar/src/xcb.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/i3bar/src/xcb.c b/i3bar/src/xcb.c index 0b9f094b..30111aab 100644 --- a/i3bar/src/xcb.c +++ b/i3bar/src/xcb.c @@ -824,7 +824,7 @@ 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[1] = XEMBED_EMBEDDED_NOTIFY; ev->data.data32[2] = output->bar.id; ev->data.data32[3] = xe_version; xcb_send_event(xcb_connection, From bb4e1c8cd162b5142753f431ea9251efe0a9c2c2 Mon Sep 17 00:00:00 2001 From: Brian Millar Date: Mon, 1 Feb 2016 01:18:41 +0000 Subject: [PATCH 162/187] Added Neovim to i3-sensible-editor --- i3-sensible-editor | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/i3-sensible-editor b/i3-sensible-editor index 861615f3..b93893a1 100755 --- a/i3-sensible-editor +++ b/i3-sensible-editor @@ -9,7 +9,7 @@ # mechanism to find the preferred editor # Hopefully one of these is installed (no flamewars about preference please!): -for editor in "$VISUAL" "$EDITOR" nano vim vi emacs pico qe mg jed gedit mc-edit; do +for editor in "$VISUAL" "$EDITOR" nano nvim vim vi emacs pico qe mg jed gedit mc-edit; do if command -v "$editor" > /dev/null 2>&1; then exec "$editor" "$@" fi From 0c57abe8e57966f4d2c1f115a453d0054df9e704 Mon Sep 17 00:00:00 2001 From: Johannes Lange Date: Wed, 3 Feb 2016 19:32:52 +0100 Subject: [PATCH 163/187] "hiding vertical borders" -> "hiding outer borders" This option can be used to hide horizontal or horizontal+vertical borders since 57effd6 --- docs/userguide | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/userguide b/docs/userguide index 55568b65..2b4491ac 100644 --- a/docs/userguide +++ b/docs/userguide @@ -605,9 +605,10 @@ new_window pixel 3 --------------------- -=== Hiding vertical borders +[[_hiding_vertical_borders]] +=== Hiding borders adjacent to the screen edges -You can hide vertical borders adjacent to the screen edges using +You can hide container borders adjacent to the screen edges using +hide_edge_borders+. This is useful if you are using scrollbars, or do not want to waste even two pixels in displayspace. Default is none. From fbfbdb8e124480bc90bbd6a8b59c1692c4ebd531 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 1 Feb 2016 09:42:55 +0100 Subject: [PATCH 164/187] travis: check spelling of binaries and manpages, use docker We now build a docker base container based on debian sid (where the very latest packages are available). That base container is updated once a month, or whenever travis-build.Dockerfile or debian/control change, but re-used for subsequent travis runs. While the initial build might take up to 15 minutes, subsequent builds typically run in a minute or two. All the different steps that we run on travis are now factored into separate scripts in the travis/ directory. Switching to docker should also help with issue #2174. --- .travis.yml | 56 ++++++++--------------------- generate-command-parser.pl | 2 +- i3-input/main.c | 4 +-- include/libi3.h | 2 +- libi3/format_placeholders.c | 2 +- man/i3-input.man | 2 +- man/i3-msg.man | 2 +- src/commands.c | 2 +- src/config_parser.c | 2 +- travis-build.Dockerfile | 29 +++++++++++++++ travis/check-formatting.sh | 2 ++ travis/check-safe-wrappers.sh | 19 ++++++++++ travis/check-spelling.pl | 64 +++++++++++++++++++++++++++++++++ travis/docker-build-and-push.sh | 11 ++++++ travis/ha.sh | 7 ++++ travis/run-tests.sh | 8 +++++ 16 files changed, 164 insertions(+), 50 deletions(-) create mode 100644 travis-build.Dockerfile create mode 100755 travis/check-formatting.sh create mode 100755 travis/check-safe-wrappers.sh create mode 100755 travis/check-spelling.pl create mode 100755 travis/docker-build-and-push.sh create mode 100755 travis/ha.sh create mode 100755 travis/run-tests.sh diff --git a/.travis.yml b/.travis.yml index 54ef5cae..aa574d09 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,49 +1,23 @@ sudo: required dist: trusty +services: + - docker language: c compiler: - gcc - clang -addons: - apt: - sources: - - ubuntu-toolchain-r-test - packages: - - clang-format-3.5 - - libllvm3.5 - - clang-3.5 - - gcc-5 -before_install: - - sudo DEBIAN_FRONTEND=noninteractive apt-get install -yq --no-install-suggests --no-install-recommends devscripts equivs xdotool +env: + global: + - BASENAME="i3wm/travis-base:$(date +'%Y-%m')-$(./travis/ha.sh)" + - secure: "B5IICA8MPx/FKaB50rTPqL8P1NU+Q0yuWl+lElL4+a9xSyLikfm3NzUPHoVwx8lNw2AVK6br7p0OmF7vMFjqAgrgc1cajTtEae5uFRKNUrWLpXM046YgNEYLLIHsQOjInxE+S4O6EFVzsUqsu8aeo2Xhq4sm4iUocG7e5isYgYo=" # DOCKER_PASS + - secure: "EIvrq8PG7lRjidppG0RCv4F0X4GP3DT9F5+ixVuGPfhK/hZT3jYC2AVY9G+NnUcXVwQEpW92rlqpftQ/qZ13FoyWokC8ZyoyD06fr5FPCfoFF3OczZwAJzZYkObI/hE9+/hXcylx/Os6N4INd2My1ntGk3JPsWL9riopod5EjSg=" # DOCKER_EMAIL + - secure: "hvhBunS4xXTgnIOsk/BPT7I7FrJhvVwCSt5PfxxvMqNaztOJI9BuK7ZrZ5Cy38KyHwlh3VHAH5AaCygJcPauoSQCV3bpnlbaWn3ruq2F0Q697Q5uNf73liXzyUqb9/Zvfvge4y4WWOhP5tVz1C6ZBe/NfhU7pqKLMA+6ads+99c=" # DOCKER_USER install: - - sudo DEBIAN_FRONTEND=noninteractive mk-build-deps --install --remove --tool 'apt-get -yq --no-install-suggests --no-install-recommends' debian/control - # Install as many dependencies as possible via apt because cpanm is not very reliable/easy to debug. - - sudo DEBIAN_FRONTEND=noninteractive apt-get install -yq --no-install-suggests --no-install-recommends libanyevent-perl libanyevent-i3-perl libextutils-pkgconfig-perl xcb-proto cpanminus xvfb xserver-xephyr xauth libinline-perl libxml-simple-perl libmouse-perl libmousex-nativetraits-perl libextutils-depends-perl perl-modules libtest-deep-perl libtest-exception-perl libxml-parser-perl libtest-simple-perl libtest-fatal-perl libdata-dump-perl libtest-differences-perl libxml-tokeparser-perl libtest-use-ok-perl libipc-run-perl libxcb-xtest0-dev - - sudo /bin/sh -c 'cpanm -n -v X11::XCB || true' - - sudo /bin/sh -c 'cpanm -n -v AnyEvent::I3 || true' -script: - if [ -a .git/shallow ]; then git fetch --unshallow; fi - - if [ "$CC" = "clang" ]; then export CC="clang-3.5"; fi - - if [ "$CC" = "gcc" ]; then export CC="gcc-5"; fi - - CFLAGS="-Wformat -Wformat-security -Wextra -Wno-unused-parameter -Werror" make -j ASAN=1 - - (cd testcases && xvfb-run ./complete-run.pl --parallel=1 || (cat latest/complete-run.log; false)) - - clang-format-3.5 -i $(find . -name "*.[ch]" | tr '\n' ' ') && git diff --exit-code || (echo 'Code was not formatted using clang-format!'; false) - - | - funcs='malloc|calloc|realloc|strdup|strndup|asprintf|write' - cstring='"([^"\\]|\\.)*"' - cchar="'[^\\\\]'|'\\\\.[^']*'" - regex="^([^'\"]|${cstring}|${cchar})*\<(${funcs})\>" - detected=0 - while IFS= read -r file; do - if { cpp -w -fpreprocessed "$file" || exit "$?"; } | grep -E -- "$regex"; then - echo "^ $file calls a function that has a safe counterpart." - detected=1 - fi - done << EOF - $(find -name '*.c' -not -name safewrappers.c -not -name strndup.c) - EOF - if [ "$detected" -ne 0 ]; then - echo - echo "Calls of functions that have safe counterparts were detected." - exit 1 - fi + - docker pull ${BASENAME} || ./travis/docker-build-and-push.sh +script: + - docker run -v $PWD:/usr/src/i3/ -w /usr/src/i3 ${BASENAME} ./travis/check-safe-wrappers.sh + - docker run -v $PWD:/usr/src/i3/ -w /usr/src/i3 ${BASENAME} ./travis/check-formatting.sh + - docker run -v $PWD:/usr/src/i3/ -w /usr/src/i3 -e CC -e CFLAGS="-Wformat -Wformat-security -Wextra -Wno-unused-parameter -Werror" ${BASENAME} make all mans -j ASAN=1 + - docker run -v $PWD:/usr/src/i3/ -w /usr/src/i3 ${BASENAME} ./travis/check-spelling.pl + - docker run -v $PWD:/usr/src/i3/ -w /usr/src/i3 ${BASENAME} ./travis/run-tests.sh diff --git a/generate-command-parser.pl b/generate-command-parser.pl index c0a9a4d4..6208945d 100755 --- a/generate-command-parser.pl +++ b/generate-command-parser.pl @@ -109,7 +109,7 @@ for my $line (@lines) { # Second step: Generate the enum values for all states. # It is important to keep the order the same, so we store the keys once. -# We sort descendingly by length to be able to replace occurences of the state +# We sort descendingly by length to be able to replace occurrences of the state # name even when one state’s name is included in another one’s (like FOR_WINDOW # is in FOR_WINDOW_COMMAND). my @keys = sort { (length($b) <=> length($a)) or ($a cmp $b) } keys %states; diff --git a/i3-input/main.c b/i3-input/main.c index e196891c..64a089dd 100644 --- a/i3-input/main.c +++ b/i3-input/main.c @@ -176,14 +176,14 @@ static int handle_key_release(void *ignored, xcb_connection_t *conn, xcb_key_rel static void finish_input() { char *command = (char *)concat_strings(glyphs_utf8, input_position); - /* count the occurences of %s in the string */ + /* count the occurrences of %s in the string */ int c; int len = strlen(format); int cnt = 0; for (c = 0; c < (len - 1); c++) if (format[c] == '%' && format[c + 1] == 's') cnt++; - printf("occurences = %d\n", cnt); + printf("occurrences = %d\n", cnt); /* allocate space for the output */ int inputlen = strlen(command); diff --git a/include/libi3.h b/include/libi3.h index 9f6eff2b..4c722671 100644 --- a/include/libi3.h +++ b/include/libi3.h @@ -513,7 +513,7 @@ typedef struct placeholder_t { } placeholder_t; /** - * Replaces occurences of the defined placeholders in the format string. + * Replaces occurrences of the defined placeholders in the format string. * */ char *format_placeholders(char *format, placeholder_t *placeholders, int num); diff --git a/libi3/format_placeholders.c b/libi3/format_placeholders.c index 825cab5c..c9cbbea4 100644 --- a/libi3/format_placeholders.c +++ b/libi3/format_placeholders.c @@ -16,7 +16,7 @@ #endif /* - * Replaces occurences of the defined placeholders in the format string. + * Replaces occurrences of the defined placeholders in the format string. * */ char *format_placeholders(char *format, placeholder_t *placeholders, int num) { diff --git a/man/i3-input.man b/man/i3-input.man index b67a1403..07a91783 100644 --- a/man/i3-input.man +++ b/man/i3-input.man @@ -26,7 +26,7 @@ Specify the path to the i3 IPC socket (it should not be necessary to use this option, i3-input will figure out the path on its own). -F :: -Every occurence of "%s" in the string is replaced by the user input, +Every occurrence of "%s" in the string is replaced by the user input, and the result is sent to i3 as a command. Default value is "%s". -l :: diff --git a/man/i3-msg.man b/man/i3-msg.man index 911fc995..e0c70c44 100644 --- a/man/i3-msg.man +++ b/man/i3-msg.man @@ -89,7 +89,7 @@ i3-msg -t get_tree === I3SOCK If no ipc-socket is specified on the commandline, this variable is used -to determine the path, at wich the unix domain socket is expected, on which +to determine the path, at which the unix domain socket is expected, on which to connect to i3. == SEE ALSO diff --git a/src/commands.c b/src/commands.c index 9f5af8c2..1612a5ef 100644 --- a/src/commands.c +++ b/src/commands.c @@ -250,7 +250,7 @@ void cmd_criteria_match_windows(I3_CMD) { DLOG("matches window!\n"); accept_match = true; } else { - DLOG("doesnt match\n"); + DLOG("doesn't match\n"); FREE(current); continue; } diff --git a/src/config_parser.c b/src/config_parser.c index 27cdeb93..e97a37e1 100644 --- a/src/config_parser.c +++ b/src/config_parser.c @@ -910,7 +910,7 @@ bool parse_file(const char *f, bool use_nagbar) { FREE(bufcopy); /* Then, allocate a new buffer and copy the file over to the new one, - * but replace occurences of our variables */ + * but replace occurrences of our variables */ char *walk = buf, *destwalk; char *new = smalloc(stbuf.st_size + extra_bytes + 1); destwalk = new; diff --git a/travis-build.Dockerfile b/travis-build.Dockerfile new file mode 100644 index 00000000..0861b26a --- /dev/null +++ b/travis-build.Dockerfile @@ -0,0 +1,29 @@ +# vim:ft=Dockerfile +FROM debian:sid + +RUN echo force-unsafe-io > /etc/dpkg/dpkg.cfg.d/docker-apt-speedup +# Paper over occasional network flakiness of some mirrors. +RUN echo 'APT::Acquire::Retries "5";' > /etc/apt/apt.conf.d/80retry + +# NOTE: I tried exclusively using gce_debian_mirror.storage.googleapis.com +# instead of httpredir.debian.org, but the results (Fetched 123 MB in 36s (3357 +# kB/s)) are not any better than httpredir.debian.org (Fetched 123 MB in 34s +# (3608 kB/s)). Hence, let’s stick with httpredir.debian.org (default) for now. + +# Install mk-build-deps (for installing the i3 build dependencies), +# clang and clang-format-3.5 (for checking formatting and building with clang), +# lintian (for checking spelling errors), +# test suite dependencies (for running tests) +RUN apt-get update && \ + DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ + dpkg-dev devscripts git equivs \ + clang clang-format-3.5 \ + lintian \ + libanyevent-perl libanyevent-i3-perl libextutils-pkgconfig-perl xcb-proto cpanminus xvfb xserver-xephyr xauth libinline-perl libinline-c-perl libxml-simple-perl libmouse-perl libmousex-nativetraits-perl libextutils-depends-perl perl-modules libtest-deep-perl libtest-exception-perl libxml-parser-perl libtest-simple-perl libtest-fatal-perl libdata-dump-perl libtest-differences-perl libxml-tokeparser-perl libipc-run-perl libxcb-xtest0-dev libx11-xcb-perl libanyevent-i3-perl && \ + rm -rf /var/lib/apt/lists/* + +# Install i3 build dependencies. +COPY debian/control /usr/src/i3-debian-packaging/control +RUN apt-get update && \ + DEBIAN_FRONTEND=noninteractive mk-build-deps --install --remove --tool 'apt-get --no-install-recommends -y' /usr/src/i3-debian-packaging/control && \ + rm -rf /var/lib/apt/lists/* diff --git a/travis/check-formatting.sh b/travis/check-formatting.sh new file mode 100755 index 00000000..abdb1168 --- /dev/null +++ b/travis/check-formatting.sh @@ -0,0 +1,2 @@ +#!/bin/sh +clang-format-3.5 -i $(find . -name "*.[ch]" | tr '\n' ' ') && git diff --exit-code || (echo 'Code was not formatted using clang-format!'; false) diff --git a/travis/check-safe-wrappers.sh b/travis/check-safe-wrappers.sh new file mode 100755 index 00000000..f6911ca2 --- /dev/null +++ b/travis/check-safe-wrappers.sh @@ -0,0 +1,19 @@ +#!/bin/sh +funcs='malloc|calloc|realloc|strdup|strndup|asprintf|write' +cstring='"([^"\\]|\\.)*"' +cchar="'[^\\\\]'|'\\\\.[^']*'" +regex="^([^'\"]|${cstring}|${cchar})*\<(${funcs})\>" +detected=0 +while IFS= read -r file; do + if { cpp -w -fpreprocessed "$file" || exit "$?"; } | grep -E -- "$regex"; then + echo "^ $file calls a function that has a safe counterpart." + detected=1 + fi +done << EOF +$(find -name '*.c' -not -name safewrappers.c -not -name strndup.c) +EOF +if [ "$detected" -ne 0 ]; then + echo + echo "Calls of functions that have safe counterparts were detected." + exit 1 +fi diff --git a/travis/check-spelling.pl b/travis/check-spelling.pl new file mode 100755 index 00000000..71feec31 --- /dev/null +++ b/travis/check-spelling.pl @@ -0,0 +1,64 @@ +#!/usr/bin/env perl +# vim:ts=4:sw=4:expandtab +# +# © 2016 Michael Stapelberg +# +# Checks for spelling errors in binaries and manpages (to be run by continuous +# integration to point out spelling errors before accepting contributions). + +use strict; +use warnings; +use v5.10; +use autodie; +use lib 'testcases/lib'; +use i3test::Util qw(slurp); +use Lintian::Check qw(check_spelling); + +# Lintian complains if we don’t set a vendor. +use Lintian::Data; +use Lintian::Profile; +Lintian::Data->set_vendor( + Lintian::Profile->new('debian', ['/usr/share/lintian'], {})); + +my $exitcode = 0; + +# Whitelist for spelling errors in manpages, in case the spell checker has +# false-positives. +my $binary_spelling_exceptions = { + #'exmaple' => 1, # Example for how to add entries to this whitelist. + 'betwen' => 1, # asan_flags.inc contains this spelling error. +}; +my @binaries = qw( + i3 + i3-config-wizard/i3-config-wizard + i3-dump-log/i3-dump-log + i3-input/i3-input + i3-msg/i3-msg + i3-nagbar/i3-nagbar + i3bar/i3bar +); +for my $binary (@binaries) { + check_spelling(slurp($binary), $binary_spelling_exceptions, sub { + my ($current, $fixed) = @_; + say STDERR qq|Binary "$binary" contains a spelling error: "$current" should be "$fixed"|; + $exitcode = 1; + }); +} + +# Whitelist for spelling errors in manpages, in case the spell checker has +# false-positives. +my $manpage_spelling_exceptions = { +}; + +for my $name (glob('man/*.1')) { + for my $line (split(/\n/, slurp($name))) { + next if $line =~ /^\.\\\"/o; + check_spelling($line, $manpage_spelling_exceptions, sub { + my ($current, $fixed) = @_; + say STDERR qq|Manpage "$name" contains a spelling error: "$current" should be "$fixed"|; + $exitcode = 1; + }); + } +} + +exit $exitcode; diff --git a/travis/docker-build-and-push.sh b/travis/docker-build-and-push.sh new file mode 100755 index 00000000..7dfd3392 --- /dev/null +++ b/travis/docker-build-and-push.sh @@ -0,0 +1,11 @@ +#!/bin/sh + +set -e + +# .dockerignore is created on demand so that release.sh and other scripts are +# not influenced by our travis setup. +echo .git > .dockerignore + +docker build --pull --no-cache --rm -t=${BASENAME} -f travis-build.Dockerfile . +docker login -e ${DOCKER_EMAIL} -u ${DOCKER_USER} -p ${DOCKER_PASS} +docker push ${BASENAME} diff --git a/travis/ha.sh b/travis/ha.sh new file mode 100755 index 00000000..688755c3 --- /dev/null +++ b/travis/ha.sh @@ -0,0 +1,7 @@ +#!/bin/sh +# Returns a hash to be used as version number suffix for the i3/travis-base +# docker container. The hash is over all files which influence what gets +# installed in the container, so that any changes in what needs to be installed +# will result in a cache invalidation. + +cat debian/control travis-build.Dockerfile | sha256sum | dd bs=1 count=8 status=none diff --git a/travis/run-tests.sh b/travis/run-tests.sh new file mode 100755 index 00000000..87c5dbb7 --- /dev/null +++ b/travis/run-tests.sh @@ -0,0 +1,8 @@ +#!/bin/sh +cd testcases +# Try running the tests in parallel so that the common case (tests pass) is +# quick, but fall back to running them in sequence to make debugging easier. +if ! xvfb-run ./complete-run.pl +then + xvfb-run ./complete-run.pl --parallel=1 || (cat latest/complete-run.log; false) +fi From ad95038c5e1d99bd32e32d6f52be0272a15c6b05 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ingo=20B=C3=BCrk?= Date: Sun, 7 Feb 2016 12:08:50 +0100 Subject: [PATCH 165/187] Remove _NET_WM_STATE on withdrawn windows. According to the spec, _NET_WM_STATE must be removed when the window is withdrawn, much like _NET_WM_DESKTOP. fixes #2165 --- src/handlers.c | 4 +++- testcases/t/253-multiple-net-wm-state-atoms.t | 16 +++++++++++++++- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/src/handlers.c b/src/handlers.c index 7e116ce7..2991d7c3 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -503,8 +503,10 @@ static void handle_unmap_notify_event(xcb_unmap_notify_event_t *event) { goto ignore_end; } - /* Since we close the container, we need to unset _NET_WM_DESKTOP according to the spec. */ + /* Since we close the container, we need to unset _NET_WM_DESKTOP and + * _NET_WM_STATE according to the spec. */ xcb_delete_property(conn, event->window, A__NET_WM_DESKTOP); + xcb_delete_property(conn, event->window, A__NET_WM_STATE); tree_close_internal(con, DONT_KILL_WINDOW, false, false); tree_render(); diff --git a/testcases/t/253-multiple-net-wm-state-atoms.t b/testcases/t/253-multiple-net-wm-state-atoms.t index bbd6c521..bfa8b46c 100644 --- a/testcases/t/253-multiple-net-wm-state-atoms.t +++ b/testcases/t/253-multiple-net-wm-state-atoms.t @@ -33,7 +33,7 @@ sub get_wm_state { my $reply = $x->get_property_reply($cookie->{sequence}); my $len = $reply->{length}; - return 0 if $len == 0; + return undef if $len == 0; my @atoms = unpack("L$len", $reply->{value}); return \@atoms; @@ -64,6 +64,20 @@ cmd 'sticky enable'; cmd 'fullscreen disable'; is_deeply(get_wm_state($window), [ $wm_state_sticky ], 'only _NET_WM_STATE_STICKY is set'); +############################################################################### +# _NET_WM_STATE is removed when the window is withdrawn. +############################################################################### + +fresh_workspace; +$window = open_window; +cmd 'sticky enable'; +is_deeply(get_wm_state($window), [ $wm_state_sticky ], 'sanity check: _NET_WM_STATE_STICKY is set'); + +$window->unmap; +wait_for_unmap($window); + +is(get_wm_state($window), undef, '_NET_WM_STATE is removed'); + ########################################################################## done_testing; From 5601096c5e9791d9275de076944d8b1565049484 Mon Sep 17 00:00:00 2001 From: norrland Date: Sun, 7 Feb 2016 14:40:00 +0100 Subject: [PATCH 166/187] Update i3-sensible-terminal --- i3-sensible-terminal | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/i3-sensible-terminal b/i3-sensible-terminal index bb66f02b..5a012cd4 100755 --- a/i3-sensible-terminal +++ b/i3-sensible-terminal @@ -8,7 +8,7 @@ # We welcome patches that add distribution-specific mechanisms to find the # preferred terminal emulator. On Debian, there is the x-terminal-emulator # symlink for example. -for terminal in "$TERMINAL" x-terminal-emulator urxvt rxvt terminator Eterm aterm xterm gnome-terminal roxterm xfce4-terminal termite lxterminal mate-terminal terminology; do +for terminal in "$TERMINAL" x-terminal-emulator urxvt rxvt termit terminator Eterm aterm xterm gnome-terminal roxterm xfce4-terminal termite lxterminal mate-terminal terminology; do if command -v "$terminal" > /dev/null 2>&1; then exec "$terminal" "$@" fi From 6cbff6bfd401742f5b6672f567425b8bc49b1935 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ingo=20B=C3=BCrk?= Date: Tue, 9 Feb 2016 21:03:44 +0100 Subject: [PATCH 167/187] Ensure that the "border" command uses logical pixels. Until now, only the config directive for borders (new_window, new_float) respected the DPI setting (using logical_px). This patch makes sure we also do so for runtime "border" commands. fixes #2202 --- docs/userguide | 4 ++++ include/commands.h | 2 +- parser-specs/commands.spec | 10 +++++----- src/commands.c | 34 +++++++++++++++------------------- 4 files changed, 25 insertions(+), 25 deletions(-) diff --git a/docs/userguide b/docs/userguide index 2b4491ac..e67a8c09 100644 --- a/docs/userguide +++ b/docs/userguide @@ -2255,6 +2255,10 @@ and +border none+ to make the client borderless. There is also +border toggle+ which will toggle the different border styles. +Note that "pixel" refers to logical pixel. On HiDPI displays, a logical pixel +may be represented by multiple physical pixels, so +pixel 1+ might not +necessarily translate into a single pixel row wide border. + *Syntax*: ----------------------------------------------- border normal|pixel [] diff --git a/include/commands.h b/include/commands.h index 968bfbef..9f83cb21 100644 --- a/include/commands.h +++ b/include/commands.h @@ -76,7 +76,7 @@ void cmd_resize(I3_CMD, const char *way, const char *direction, long resize_px, * Implementation of 'border normal|pixel []', 'border none|1pixel|toggle'. * */ -void cmd_border(I3_CMD, const char *border_style_str, const char *border_width); +void cmd_border(I3_CMD, const char *border_style_str, long border_width); /** * Implementation of 'nop '. diff --git a/parser-specs/commands.spec b/parser-specs/commands.spec index e6480a35..ec7fbabf 100644 --- a/parser-specs/commands.spec +++ b/parser-specs/commands.spec @@ -86,15 +86,15 @@ state BORDER: border_style = 'normal', 'pixel' -> BORDER_WIDTH border_style = 'none', 'toggle' - -> call cmd_border($border_style, "0") + -> call cmd_border($border_style, 0) border_style = '1pixel' - -> call cmd_border($border_style, "1") + -> call cmd_border($border_style, 1) state BORDER_WIDTH: end - -> call cmd_border($border_style, "2") - border_width = word - -> call cmd_border($border_style, $border_width) + -> call cmd_border($border_style, 2) + border_width = number + -> call cmd_border($border_style, &border_width) # layout default|stacked|stacking|tabbed|splitv|splith # layout toggle [split|all] diff --git a/src/commands.c b/src/commands.c index 1612a5ef..af72f5bb 100644 --- a/src/commands.c +++ b/src/commands.c @@ -716,8 +716,8 @@ void cmd_resize_set(I3_CMD, long cwidth, long cheight) { * Implementation of 'border normal|pixel []', 'border none|1pixel|toggle'. * */ -void cmd_border(I3_CMD, const char *border_style_str, const char *border_width) { - DLOG("border style should be changed to %s with border width %s\n", border_style_str, border_width); +void cmd_border(I3_CMD, const char *border_style_str, long border_width) { + DLOG("border style should be changed to %s with border width %ld\n", border_style_str, border_width); owindow *current; HANDLE_EMPTY_MATCH; @@ -725,39 +725,35 @@ void cmd_border(I3_CMD, const char *border_style_str, const char *border_width) TAILQ_FOREACH(current, &owindows, owindows) { DLOG("matching: %p / %s\n", current->con, current->con->name); int border_style = current->con->border_style; - char *end; - int tmp_border_width = -1; - tmp_border_width = strtol(border_width, &end, 10); - if (end == border_width) { - /* no valid digits found */ - tmp_border_width = -1; - } + int con_border_width = border_width; + if (strcmp(border_style_str, "toggle") == 0) { border_style++; border_style %= 3; if (border_style == BS_NORMAL) - tmp_border_width = 2; + con_border_width = 2; else if (border_style == BS_NONE) - tmp_border_width = 0; + con_border_width = 0; else if (border_style == BS_PIXEL) - tmp_border_width = 1; + con_border_width = 1; } else { - if (strcmp(border_style_str, "normal") == 0) + if (strcmp(border_style_str, "normal") == 0) { border_style = BS_NORMAL; - else if (strcmp(border_style_str, "pixel") == 0) + } else if (strcmp(border_style_str, "pixel") == 0) { border_style = BS_PIXEL; - else if (strcmp(border_style_str, "1pixel") == 0) { + } else if (strcmp(border_style_str, "1pixel") == 0) { border_style = BS_PIXEL; - tmp_border_width = 1; - } else if (strcmp(border_style_str, "none") == 0) + con_border_width = 1; + } else if (strcmp(border_style_str, "none") == 0) { border_style = BS_NONE; - else { + } else { ELOG("BUG: called with border_style=%s\n", border_style_str); ysuccess(false); return; } } - con_set_border_style(current->con, border_style, tmp_border_width); + + con_set_border_style(current->con, border_style, logical_px(con_border_width)); } cmd_output->needs_tree_render = true; From 658e5597d2f864e64cacc1182428d26aea108981 Mon Sep 17 00:00:00 2001 From: Roman Blanco Date: Tue, 9 Feb 2016 22:33:37 +0100 Subject: [PATCH 168/187] Added st to i3-sensible-terminal --- i3-sensible-terminal | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/i3-sensible-terminal b/i3-sensible-terminal index 5a012cd4..a1ada248 100755 --- a/i3-sensible-terminal +++ b/i3-sensible-terminal @@ -8,7 +8,7 @@ # We welcome patches that add distribution-specific mechanisms to find the # preferred terminal emulator. On Debian, there is the x-terminal-emulator # symlink for example. -for terminal in "$TERMINAL" x-terminal-emulator urxvt rxvt termit terminator Eterm aterm xterm gnome-terminal roxterm xfce4-terminal termite lxterminal mate-terminal terminology; do +for terminal in "$TERMINAL" x-terminal-emulator urxvt rxvt termit terminator Eterm aterm xterm gnome-terminal roxterm xfce4-terminal termite lxterminal mate-terminal terminology st; do if command -v "$terminal" > /dev/null 2>&1; then exec "$terminal" "$@" fi From 018922dcc4e6cf224e3c80cf3ea74be9b8434e76 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ingo=20B=C3=BCrk?= Date: Thu, 11 Feb 2016 19:57:32 +0100 Subject: [PATCH 169/187] Allow "modifier none" in i3bar to disable the modifier. This allows "modifier none" (and "modifier off") for the bar config in order to disable the modifier key altogether. This is useful for users who use a different approach to hiding / showing the bar, e.g., a custom keybind that involved multiple keys or scripts. fixes #2208 --- docs/userguide | 5 +++-- i3bar/src/config.c | 11 ++++++----- i3bar/src/xcb.c | 2 +- parser-specs/config.spec | 2 +- src/config_directives.c | 4 ++++ src/ipc.c | 8 +++----- 6 files changed, 18 insertions(+), 14 deletions(-) diff --git a/docs/userguide b/docs/userguide index e67a8c09..f3f80c96 100644 --- a/docs/userguide +++ b/docs/userguide @@ -1238,7 +1238,7 @@ the windows key). The default value for the hidden_state is hide. ------------------------- mode dock|hide|invisible hidden_state hide|show -modifier +modifier |none ------------------------ *Example*: @@ -1250,7 +1250,8 @@ bar { } ---------------- -Available modifiers are Mod1-Mod5, Shift, Control (see +xmodmap(1)+). +Available modifiers are Mod1-Mod5, Shift, Control (see +xmodmap(1)+). You can +also use "none" if you don't want any modifier to trigger this behavior. === Mouse button commands diff --git a/i3bar/src/config.c b/i3bar/src/config.c index bc13f3d9..5c23bc78 100644 --- a/i3bar/src/config.c +++ b/i3bar/src/config.c @@ -121,6 +121,11 @@ static int config_string_cb(void *params_, const unsigned char *val, size_t _len if (!strcmp(cur_key, "modifier")) { DLOG("modifier = %.*s\n", len, val); + if (len == 4 && !strncmp((const char *)val, "none", strlen("none"))) { + config.modifier = XCB_NONE; + return 1; + } + if (len == 5 && !strncmp((const char *)val, "shift", strlen("shift"))) { config.modifier = ShiftMask; return 1; @@ -140,16 +145,12 @@ static int config_string_cb(void *params_, const unsigned char *val, size_t _len case '3': config.modifier = Mod3Mask; return 1; - /* - case '4': - config.modifier = Mod4Mask; - return 1; - */ case '5': config.modifier = Mod5Mask; return 1; } } + config.modifier = Mod4Mask; return 1; } diff --git a/i3bar/src/xcb.c b/i3bar/src/xcb.c index 30111aab..d2aa28e9 100644 --- a/i3bar/src/xcb.c +++ b/i3bar/src/xcb.c @@ -1094,7 +1094,7 @@ 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) { + if (state->xkbType == XCB_XKB_STATE_NOTIFY && config.modifier != XCB_NONE) { int modstate = state->mods & config.modifier; #define DLOGMOD(modmask, status) \ diff --git a/parser-specs/config.spec b/parser-specs/config.spec index f5275028..ef3bc2e0 100644 --- a/parser-specs/config.spec +++ b/parser-specs/config.spec @@ -453,7 +453,7 @@ state BAR_ID: -> call cfg_bar_id($bar_id); BAR state BAR_MODIFIER: - modifier = 'Mod1', 'Mod2', 'Mod3', 'Mod4', 'Mod5', 'Control', 'Ctrl', 'Shift' + modifier = 'Mod1', 'Mod2', 'Mod3', 'Mod4', 'Mod5', 'Control', 'Ctrl', 'Shift', 'none', 'off' -> call cfg_bar_modifier($modifier); BAR state BAR_WHEEL_UP_CMD: diff --git a/src/config_directives.c b/src/config_directives.c index e92ef1d9..ec99321a 100644 --- a/src/config_directives.c +++ b/src/config_directives.c @@ -446,6 +446,9 @@ CFGFUN(bar_modifier, const char *modifier) { current_bar->modifier = M_CONTROL; else if (strcmp(modifier, "Shift") == 0) current_bar->modifier = M_SHIFT; + else if (strcmp(modifier, "none") == 0 || + strcmp(modifier, "off") == 0) + current_bar->modifier = M_NONE; } static void bar_configure_binding(const char *button, const char *command) { @@ -575,6 +578,7 @@ CFGFUN(bar_start) { TAILQ_INIT(&(current_bar->bar_bindings)); TAILQ_INIT(&(current_bar->tray_outputs)); current_bar->tray_padding = 2; + current_bar->modifier = M_MOD4; } CFGFUN(bar_finish) { diff --git a/src/ipc.c b/src/ipc.c index f46e7179..566fe52a 100644 --- a/src/ipc.c +++ b/src/ipc.c @@ -611,6 +611,9 @@ static void dump_bar_config(yajl_gen gen, Barconfig *config) { ystr("modifier"); switch (config->modifier) { + case M_NONE: + ystr("none"); + break; case M_CONTROL: ystr("ctrl"); break; @@ -626,11 +629,6 @@ static void dump_bar_config(yajl_gen gen, Barconfig *config) { case M_MOD3: ystr("Mod3"); break; - /* - case M_MOD4: - ystr("Mod4"); - break; - */ case M_MOD5: ystr("Mod5"); break; From 9431ef16f652384c7a3fd85272d9692dc9061773 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ingo=20B=C3=BCrk?= Date: Thu, 11 Feb 2016 20:54:02 +0100 Subject: [PATCH 170/187] Don't allow floating workspaces anymore. This is essentially a revert of daf00a9 which introduced a mechanism to float workspace containers by wrapping all children into a new container and floating it. This caused a bug and there's no good reason (anymore) to allow doing this in the first place as we don't support floating nested containers. fixes #2166 --- src/floating.c | 42 ++------------------------- testcases/t/155-floating-split-size.t | 5 ++++ 2 files changed, 7 insertions(+), 40 deletions(-) diff --git a/src/floating.c b/src/floating.c index 2b8a88d1..8ac818ac 100644 --- a/src/floating.c +++ b/src/floating.c @@ -116,47 +116,9 @@ void floating_enable(Con *con, bool automatic) { return; } - /* 1: If the container is a workspace container, we need to create a new - * split-container with the same layout and make that one floating. We - * cannot touch the workspace container itself because floating containers - * are children of the workspace. */ if (con->type == CT_WORKSPACE) { - LOG("This is a workspace, creating new container around content\n"); - if (con_num_children(con) == 0) { - LOG("Workspace is empty, aborting\n"); - return; - } - /* TODO: refactor this with src/con.c:con_set_layout */ - Con *new = con_new(NULL, NULL); - new->parent = con; - new->layout = con->layout; - - /* since the new container will be set into floating mode directly - * afterwards, we need to copy the workspace rect. */ - memcpy(&(new->rect), &(con->rect), sizeof(Rect)); - - Con *old_focused = TAILQ_FIRST(&(con->focus_head)); - if (old_focused == TAILQ_END(&(con->focus_head))) - old_focused = NULL; - - /* 4: move the existing cons of this workspace below the new con */ - DLOG("Moving cons\n"); - Con *child; - while (!TAILQ_EMPTY(&(con->nodes_head))) { - child = TAILQ_FIRST(&(con->nodes_head)); - con_detach(child); - con_attach(child, new, true); - } - - /* 4: attach the new split container to the workspace */ - DLOG("Attaching new split to ws\n"); - con_attach(new, con, false); - - if (old_focused) - con_focus(old_focused); - - con = new; - set_focus = false; + LOG("Container is a workspace, not enabling floating mode.\n"); + return; } /* 1: detach the container from its parent */ diff --git a/testcases/t/155-floating-split-size.t b/testcases/t/155-floating-split-size.t index 7475f9c7..604b661e 100644 --- a/testcases/t/155-floating-split-size.t +++ b/testcases/t/155-floating-split-size.t @@ -21,6 +21,9 @@ use i3test; my $tmp = fresh_workspace; +open_window; +cmd 'split v'; + ##################################################################### # open a window with 200x80 ##################################################################### @@ -30,6 +33,8 @@ my $first = open_window({ background_color => '#FF0000', }); +cmd 'split h'; + ##################################################################### # Open a second window with 300x90 ##################################################################### From 9c595a1a28ddd739dece5fcc77ba798e89b3bc87 Mon Sep 17 00:00:00 2001 From: bendem Date: Sun, 14 Feb 2016 13:28:14 +0100 Subject: [PATCH 171/187] Reflect changes to i3-sensible-terminal in its manpage --- man/i3-sensible-terminal.man | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/man/i3-sensible-terminal.man b/man/i3-sensible-terminal.man index 6fa91ac8..b231f808 100644 --- a/man/i3-sensible-terminal.man +++ b/man/i3-sensible-terminal.man @@ -25,6 +25,7 @@ It tries to start one of the following (in that order): * x-terminal-emulator (only present on Debian and derivatives) * urxvt * rxvt +* termit * terminator * Eterm * aterm @@ -32,6 +33,11 @@ It tries to start one of the following (in that order): * gnome-terminal * roxterm * xfce4-terminal +* termite +* lxterminal +* mate-terminal +* terminology +* st Please don’t complain about the order: If the user has any preference, they will have $TERMINAL set or modified their i3 configuration file. From f63701ed098414a61bf4d78d443194f94092b5ff Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 15 Feb 2016 08:34:03 +0100 Subject: [PATCH 172/187] Allow workspace renames which change case fixes #2211 --- src/commands.c | 4 +++- testcases/t/117-workspace.t | 5 +++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/commands.c b/src/commands.c index af72f5bb..0faf2775 100644 --- a/src/commands.c +++ b/src/commands.c @@ -1909,7 +1909,9 @@ void cmd_rename_workspace(I3_CMD, const char *old_name, const char *new_name) { GREP_FIRST(check_dest, output_get_content(output), !strcasecmp(child->name, new_name)); - if (check_dest != NULL) { + /* If check_dest == workspace, the user might be changing the case of the + * workspace, or it might just be a no-op. */ + if (check_dest != NULL && check_dest != workspace) { yerror("New workspace \"%s\" already exists", new_name); return; } diff --git a/testcases/t/117-workspace.t b/testcases/t/117-workspace.t index 04d9b9dd..01d51cc0 100644 --- a/testcases/t/117-workspace.t +++ b/testcases/t/117-workspace.t @@ -288,5 +288,10 @@ ok(!$result->[0]->{success}, 'renaming workspace to an already existing one fail $result = cmd 'rename workspace notexistant to bleh'; ok(!$result->[0]->{success}, 'renaming workspace which does not exist failed'); +# 8: change case +ok(!workspace_exists('11: BAR'), 'workspace 11: BAR does not exist yet'); +$result = cmd 'rename workspace "11: bar" to "11: BAR"'; +ok($result->[0]->{success}, 'renaming workspace from 11: bar to 11: BAR worked'); +ok(workspace_exists('11: BAR'), 'workspace 11: BAR now exists'); done_testing; From fe5e7b0c81855a18dc3a4556e2964764bb6cbd2b Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 19 Feb 2016 18:41:30 +0100 Subject: [PATCH 173/187] move CONTRIBUTING.md to .github/ directory See https://github.com/blog/2111-issue-and-pull-request-templates --- CONTRIBUTING.md => .github/CONTRIBUTING.md | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename CONTRIBUTING.md => .github/CONTRIBUTING.md (100%) diff --git a/CONTRIBUTING.md b/.github/CONTRIBUTING.md similarity index 100% rename from CONTRIBUTING.md rename to .github/CONTRIBUTING.md From d0ff8ac212c03f5ee97b6439c5f4a7a87ba4c49b Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 19 Feb 2016 18:48:49 +0100 Subject: [PATCH 174/187] Add issue template MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This is a mix of what our old new-ticket-form used to suggest: http://code.stapelberg.de/git/i3-new-ticket/tree/templates/index.html …and what CONTRIBUTING.md already entailed. See https://github.com/blog/2111-issue-and-pull-request-templates --- .github/ISSUE_TEMPLATE.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE.md diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md new file mode 100644 index 00000000..d2254676 --- /dev/null +++ b/.github/ISSUE_TEMPLATE.md @@ -0,0 +1,18 @@ +Output of `i3 --moreversion 2>&- || i3 --version`: + +_REPLACE: i3 version output_ + +URL to a logfile as per http://i3wm.org/docs/debugging.html: + +_REPLACE: URL to logfile_ + +**What I did:** + +_REPLACE: e.g. "I’m pressing Alt+j (focus left)"_ + +**What I saw:** + +_REPLACE: e.g. "i3 changed focus to the window ABOVE the current window"_ + +**What I expected instead:** +_REPLACE: e.g. "Focus should be on the window to the left"_ From 3e1b2694095d27ef1b0789e4268e0e0507b8d6db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ingo=20B=C3=BCrk?= Date: Sun, 21 Feb 2016 14:12:02 +0100 Subject: [PATCH 175/187] Revert "Add 'tray_output primary' to the default config" This reverts commit e2e7b70d002cac2031cb65d6f5f197c9583913d6. relates to #2220 --- i3.config | 1 - i3.config.keycodes | 1 - 2 files changed, 2 deletions(-) diff --git a/i3.config b/i3.config index b2d7fac8..f7722d36 100644 --- a/i3.config +++ b/i3.config @@ -165,7 +165,6 @@ bindsym Mod1+r mode "resize" # finds out, if available) bar { status_command i3status - tray_output primary } ####################################################################### diff --git a/i3.config.keycodes b/i3.config.keycodes index e606d347..0c978d0b 100644 --- a/i3.config.keycodes +++ b/i3.config.keycodes @@ -152,5 +152,4 @@ bindcode $mod+27 mode "resize" # finds out, if available) bar { status_command i3status - tray_output primary } From 320591ac196df95f6087ad0f8374d68e9f243d1e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ingo=20B=C3=BCrk?= Date: Sun, 21 Feb 2016 14:13:58 +0100 Subject: [PATCH 176/187] Remove unreachable fallback code for tray_output primary. This commit removes the code for falling back to the first available output for the system tray if 'tray_output primary' has been specified but there is no primary output (managed by this bar). This fallback behavior was broken/unreachable because the tray will never be initialized in this situation in the first place. Having this dead code lead to a wrong assumption in #1855 and hence to commit e2e7b70d002cac2031cb65d6f5f197c9583913d6, which makes the system tray not show up for many users when first installing i3. Thanks to @rtlanceroad for reporting this issue. fixes #2220 --- i3bar/src/xcb.c | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/i3bar/src/xcb.c b/i3bar/src/xcb.c index d2aa28e9..1c2134f4 100644 --- a/i3bar/src/xcb.c +++ b/i3bar/src/xcb.c @@ -769,19 +769,9 @@ static void handle_client_message(xcb_client_message_event_t *event) { break; } - /* Check whether any "tray_output primary" was defined for this bar. */ - bool contains_primary = false; - TAILQ_FOREACH(tray_output, &(config.tray_outputs), tray_outputs) { - if (strcasecmp("primary", tray_output->output) == 0) { - contains_primary = true; - break; - } - } - - /* In case of tray_output == primary and there is no primary output - * configured, we fall back to the first available output. We do the - * same if no tray_output was specified. */ - if (output == NULL && (contains_primary || TAILQ_EMPTY(&(config.tray_outputs)))) { + /* If no tray_output has been specified, we fall back to the first + * available output. */ + if (output == NULL && TAILQ_EMPTY(&(config.tray_outputs))) { SLIST_FOREACH(walk, outputs, slist) { if (!walk->active) continue; @@ -790,6 +780,7 @@ static void handle_client_message(xcb_client_message_event_t *event) { break; } } + if (output == NULL) { ELOG("No output found\n"); return; From b8109c3a59ff07486d719c95a68bb78edbc66ec0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ingo=20B=C3=BCrk?= Date: Sun, 21 Feb 2016 14:26:13 +0100 Subject: [PATCH 177/187] Document tray initialization better. This commit removes an unnecessary fallback to the first output's name as this name ("first") will only be used to see whether "tray_output none" has been specified, anyway. We also add documentation that clearly states when we want to initialize the tray and when we don't want to do the same. relates to #2220 --- i3bar/src/xcb.c | 31 +++++++++++++++++++++++++------ 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/i3bar/src/xcb.c b/i3bar/src/xcb.c index 1c2134f4..496035c2 100644 --- a/i3bar/src/xcb.c +++ b/i3bar/src/xcb.c @@ -1761,16 +1761,35 @@ void reconfig_windows(bool redraw_bars) { } /* Unless "tray_output none" was specified, we need to initialize the tray. */ - const char *first = (TAILQ_EMPTY(&(config.tray_outputs))) ? SLIST_FIRST(outputs)->name : TAILQ_FIRST(&(config.tray_outputs))->output; - if (!tray_configured && strcasecmp(first, "none") != 0) { - /* We do a sanity check here to ensure that this i3bar instance actually handles - * the output on which the tray should appear. For example, - * consider tray_output == [VGA-1], but output == [HDMI-1]. */ + bool no_tray = false; + if (!(TAILQ_EMPTY(&(config.tray_outputs)))) { + no_tray = strcasecmp(TAILQ_FIRST(&(config.tray_outputs))->output, "none") == 0; + } + /* + * There are three scenarios in which we need to initialize the tray: + * 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 + * initialize the tray are: + * 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. + */ + if (!tray_configured && !no_tray) { /* If no tray_output was specified, we go ahead and initialize the tray as * we will be using the first available output. */ - if (TAILQ_EMPTY(&(config.tray_outputs))) + if (TAILQ_EMPTY(&(config.tray_outputs))) { init_tray(); + } /* 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. */ From fdbbae56b2b5f8049380b9456c01c3966b3b18f2 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Tue, 23 Feb 2016 21:47:00 +0100 Subject: [PATCH 178/187] split atoms.xmacro into 2 files to obtain _NET_SUPPORTED count MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Manually updating a magic number doesn’t work in the long run. With this change, the number of atoms contained in include/atoms_NET_SUPPORTED.xmacro is used. fixes #2230 --- include/atoms.xmacro | 53 ++---------------------------- include/atoms_NET_SUPPORTED.xmacro | 33 +++++++++++++++++++ include/atoms_rest.xmacro | 18 ++++++++++ src/ewmh.c | 5 ++- 4 files changed, 55 insertions(+), 54 deletions(-) create mode 100644 include/atoms_NET_SUPPORTED.xmacro create mode 100644 include/atoms_rest.xmacro diff --git a/include/atoms.xmacro b/include/atoms.xmacro index 139b6efb..730e569a 100644 --- a/include/atoms.xmacro +++ b/include/atoms.xmacro @@ -1,51 +1,2 @@ -xmacro(_NET_SUPPORTED) -xmacro(_NET_SUPPORTING_WM_CHECK) -xmacro(_NET_WM_NAME) -xmacro(_NET_WM_VISIBLE_NAME) -xmacro(_NET_WM_MOVERESIZE) -xmacro(_NET_WM_STATE_STICKY) -xmacro(_NET_WM_STATE_FULLSCREEN) -xmacro(_NET_WM_STATE_DEMANDS_ATTENTION) -xmacro(_NET_WM_STATE_MODAL) -xmacro(_NET_WM_STATE_HIDDEN) -xmacro(_NET_WM_STATE) -xmacro(_NET_WM_WINDOW_TYPE) -xmacro(_NET_WM_WINDOW_TYPE_NORMAL) -xmacro(_NET_WM_WINDOW_TYPE_DOCK) -xmacro(_NET_WM_WINDOW_TYPE_DIALOG) -xmacro(_NET_WM_WINDOW_TYPE_UTILITY) -xmacro(_NET_WM_WINDOW_TYPE_TOOLBAR) -xmacro(_NET_WM_WINDOW_TYPE_SPLASH) -xmacro(_NET_WM_WINDOW_TYPE_MENU) -xmacro(_NET_WM_WINDOW_TYPE_DROPDOWN_MENU) -xmacro(_NET_WM_WINDOW_TYPE_POPUP_MENU) -xmacro(_NET_WM_WINDOW_TYPE_TOOLTIP) -xmacro(_NET_WM_WINDOW_TYPE_NOTIFICATION) -xmacro(_NET_WM_DESKTOP) -xmacro(_NET_WM_STRUT_PARTIAL) -xmacro(_NET_CLIENT_LIST) -xmacro(_NET_CLIENT_LIST_STACKING) -xmacro(_NET_CURRENT_DESKTOP) -xmacro(_NET_NUMBER_OF_DESKTOPS) -xmacro(_NET_DESKTOP_NAMES) -xmacro(_NET_DESKTOP_VIEWPORT) -xmacro(_NET_ACTIVE_WINDOW) -xmacro(_NET_CLOSE_WINDOW) -xmacro(_NET_WM_USER_TIME) -xmacro(_NET_STARTUP_ID) -xmacro(_NET_WORKAREA) -xmacro(WM_PROTOCOLS) -xmacro(WM_DELETE_WINDOW) -xmacro(UTF8_STRING) -xmacro(WM_STATE) -xmacro(WM_CLIENT_LEADER) -xmacro(WM_TAKE_FOCUS) -xmacro(WM_WINDOW_ROLE) -xmacro(I3_SOCKET_PATH) -xmacro(I3_CONFIG_PATH) -xmacro(I3_SYNC) -xmacro(I3_SHMLOG_PATH) -xmacro(I3_PID) -xmacro(_NET_REQUEST_FRAME_EXTENTS) -xmacro(_NET_FRAME_EXTENTS) -xmacro(_MOTIF_WM_HINTS) +#include "atoms_NET_SUPPORTED.xmacro" +#include "atoms_rest.xmacro" diff --git a/include/atoms_NET_SUPPORTED.xmacro b/include/atoms_NET_SUPPORTED.xmacro new file mode 100644 index 00000000..1358e0f1 --- /dev/null +++ b/include/atoms_NET_SUPPORTED.xmacro @@ -0,0 +1,33 @@ +xmacro(_NET_SUPPORTED) +xmacro(_NET_SUPPORTING_WM_CHECK) +xmacro(_NET_WM_NAME) +xmacro(_NET_WM_VISIBLE_NAME) +xmacro(_NET_WM_MOVERESIZE) +xmacro(_NET_WM_STATE_STICKY) +xmacro(_NET_WM_STATE_FULLSCREEN) +xmacro(_NET_WM_STATE_DEMANDS_ATTENTION) +xmacro(_NET_WM_STATE_MODAL) +xmacro(_NET_WM_STATE_HIDDEN) +xmacro(_NET_WM_STATE) +xmacro(_NET_WM_WINDOW_TYPE) +xmacro(_NET_WM_WINDOW_TYPE_NORMAL) +xmacro(_NET_WM_WINDOW_TYPE_DOCK) +xmacro(_NET_WM_WINDOW_TYPE_DIALOG) +xmacro(_NET_WM_WINDOW_TYPE_UTILITY) +xmacro(_NET_WM_WINDOW_TYPE_TOOLBAR) +xmacro(_NET_WM_WINDOW_TYPE_SPLASH) +xmacro(_NET_WM_WINDOW_TYPE_MENU) +xmacro(_NET_WM_WINDOW_TYPE_DROPDOWN_MENU) +xmacro(_NET_WM_WINDOW_TYPE_POPUP_MENU) +xmacro(_NET_WM_WINDOW_TYPE_TOOLTIP) +xmacro(_NET_WM_WINDOW_TYPE_NOTIFICATION) +xmacro(_NET_WM_DESKTOP) +xmacro(_NET_WM_STRUT_PARTIAL) +xmacro(_NET_CLIENT_LIST) +xmacro(_NET_CLIENT_LIST_STACKING) +xmacro(_NET_CURRENT_DESKTOP) +xmacro(_NET_NUMBER_OF_DESKTOPS) +xmacro(_NET_DESKTOP_NAMES) +xmacro(_NET_DESKTOP_VIEWPORT) +xmacro(_NET_ACTIVE_WINDOW) +xmacro(_NET_CLOSE_WINDOW) diff --git a/include/atoms_rest.xmacro b/include/atoms_rest.xmacro new file mode 100644 index 00000000..12cb63fd --- /dev/null +++ b/include/atoms_rest.xmacro @@ -0,0 +1,18 @@ +xmacro(_NET_WM_USER_TIME) +xmacro(_NET_STARTUP_ID) +xmacro(_NET_WORKAREA) +xmacro(WM_PROTOCOLS) +xmacro(WM_DELETE_WINDOW) +xmacro(UTF8_STRING) +xmacro(WM_STATE) +xmacro(WM_CLIENT_LEADER) +xmacro(WM_TAKE_FOCUS) +xmacro(WM_WINDOW_ROLE) +xmacro(I3_SOCKET_PATH) +xmacro(I3_CONFIG_PATH) +xmacro(I3_SYNC) +xmacro(I3_SHMLOG_PATH) +xmacro(I3_PID) +xmacro(_NET_REQUEST_FRAME_EXTENTS) +xmacro(_NET_FRAME_EXTENTS) +xmacro(_MOTIF_WM_HINTS) diff --git a/src/ewmh.c b/src/ewmh.c index 05f4d3cd..c4ae844e 100644 --- a/src/ewmh.c +++ b/src/ewmh.c @@ -284,7 +284,7 @@ void ewmh_update_sticky(xcb_window_t window, bool sticky) { void ewmh_setup_hints(void) { xcb_atom_t supported_atoms[] = { #define xmacro(atom) A_##atom, -#include "atoms.xmacro" +#include "atoms_NET_SUPPORTED.xmacro" #undef xmacro }; @@ -313,8 +313,7 @@ void ewmh_setup_hints(void) { /* I’m not entirely sure if we need to keep _NET_WM_NAME on root. */ xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root, A__NET_WM_NAME, A_UTF8_STRING, 8, strlen("i3"), "i3"); - /* only send the first 32 atoms (last one is _NET_CLOSE_WINDOW) increment that number when adding supported atoms */ - xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root, A__NET_SUPPORTED, XCB_ATOM_ATOM, 32, /* number of atoms */ 32, supported_atoms); + xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root, A__NET_SUPPORTED, XCB_ATOM_ATOM, 32, /* number of atoms */ sizeof(supported_atoms) / sizeof(xcb_atom_t), supported_atoms); /* We need to map this window to be able to set the input focus to it if no other window is available to be focused. */ xcb_map_window(conn, ewmh_window); From 712c6d65ff01ea73731f69963d1bbc9cd35d8cbf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ingo=20B=C3=BCrk?= Date: Tue, 23 Feb 2016 20:12:45 +0100 Subject: [PATCH 179/187] Introduce I3_FLOATING_WINDOW This patch introduces a proprietary atom I3_FLOATING_WINDOW which will be set and maintained for floating windows and removed on tiling containers. This allows users to select on this atom, e.g., in their compositor configuration or in utility scripts (without using the IPC). fixes #2223 --- include/atoms_rest.xmacro | 1 + src/floating.c | 37 ++++++++++-- testcases/t/263-i3-floating-window-atom.t | 70 +++++++++++++++++++++++ 3 files changed, 104 insertions(+), 4 deletions(-) create mode 100644 testcases/t/263-i3-floating-window-atom.t diff --git a/include/atoms_rest.xmacro b/include/atoms_rest.xmacro index 12cb63fd..d461dc08 100644 --- a/include/atoms_rest.xmacro +++ b/include/atoms_rest.xmacro @@ -13,6 +13,7 @@ xmacro(I3_CONFIG_PATH) xmacro(I3_SYNC) xmacro(I3_SHMLOG_PATH) xmacro(I3_PID) +xmacro(I3_FLOATING_WINDOW) xmacro(_NET_REQUEST_FRAME_EXTENTS) xmacro(_NET_FRAME_EXTENTS) xmacro(_MOTIF_WM_HINTS) diff --git a/src/floating.c b/src/floating.c index 8ac818ac..231577fd 100644 --- a/src/floating.c +++ b/src/floating.c @@ -29,6 +29,34 @@ static Rect total_outputs_dimensions(void) { return outputs_dimensions; } +/* + * Updates I3_FLOATING_WINDOW by either setting or removing it on the con and + * all its children. + * + */ +static void floating_set_hint_atom(Con *con, bool floating) { + if (!con_is_leaf(con)) { + Con *child; + TAILQ_FOREACH(child, &(con->nodes_head), nodes) { + floating_set_hint_atom(child, floating); + } + } + + if (con->window == NULL) { + return; + } + + if (floating) { + uint32_t val = 1; + xcb_change_property(conn, XCB_PROP_MODE_REPLACE, con->window->id, + A_I3_FLOATING_WINDOW, XCB_ATOM_CARDINAL, 32, 1, &val); + } else { + xcb_delete_property(conn, con->window->id, A_I3_FLOATING_WINDOW); + } + + xcb_flush(conn); +} + /** * Called when a floating window is created or resized. * This function resizes the window if its size is higher or lower than the @@ -260,19 +288,19 @@ void floating_enable(Con *con, bool automatic) { /* Check if we need to re-assign it to a different workspace because of its * coordinates and exit if that was done successfully. */ if (floating_maybe_reassign_ws(nc)) { - ipc_send_window_event("floating", con); - return; + goto done; } /* Sanitize coordinates: Check if they are on any output */ if (get_output_containing(nc->rect.x, nc->rect.y) != NULL) { - ipc_send_window_event("floating", con); - return; + goto done; } ELOG("No output found at destination coordinates, centering floating window on current ws\n"); floating_center(nc, ws->rect); +done: + floating_set_hint_atom(nc, true); ipc_send_window_event("floating", con); } @@ -318,6 +346,7 @@ void floating_disable(Con *con, bool automatic) { if (set_focus) con_focus(con); + floating_set_hint_atom(con, false); ipc_send_window_event("floating", con); } diff --git a/testcases/t/263-i3-floating-window-atom.t b/testcases/t/263-i3-floating-window-atom.t new file mode 100644 index 00000000..43b69ccb --- /dev/null +++ b/testcases/t/263-i3-floating-window-atom.t @@ -0,0 +1,70 @@ +#!perl +# vim:ts=4:sw=4:expandtab +# +# Please read the following documents before working on tests: +# • http://build.i3wm.org/docs/testsuite.html +# (or docs/testsuite) +# +# • http://build.i3wm.org/docs/lib-i3test.html +# (alternatively: perldoc ./testcases/lib/i3test.pm) +# +# • http://build.i3wm.org/docs/ipc.html +# (or docs/ipc) +# +# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# (unless you are already familiar with Perl) +# +# Tests for our proprietary atom I3_FLOATING_WINDOW to allow +# identifying floating windows. +# Ticket: #2223 +use i3test; +use X11::XCB qw(:all); + +my ($con); + +sub has_i3_floating_window { + sync_with_i3; + + my ($con) = @_; + my $cookie = $x->get_property( + 0, + $con->{id}, + $x->atom(name => 'I3_FLOATING_WINDOW')->id, + $x->atom(name => 'CARDINAL')->id, + 0, + 1 + ); + + my $reply = $x->get_property_reply($cookie->{sequence}); + return 0 if $reply->{length} != 1; + + return unpack("L", $reply->{value}); +} + +############################################################################### +# Toggling floating on a container adds / removes I3_FLOATING_WINDOW. +############################################################################### + +fresh_workspace; + +$con = open_window; +is(has_i3_floating_window($con), 0, 'I3_FLOATING_WINDOW is not set'); + +cmd 'floating enable'; +is(has_i3_floating_window($con), 1, 'I3_FLOATING_WINDOW is set'); + +cmd 'floating disable'; +is(has_i3_floating_window($con), 0, 'I3_FLOATING_WINDOW is not set'); + +############################################################################### +# A window that is floated when managed has I3_FLOATING_WINDOW set. +############################################################################### +# +fresh_workspace; + +$con = open_floating_window; +is(has_i3_floating_window($con), 1, 'I3_FLOATING_WINDOW is set'); + +############################################################################### + +done_testing; From a9d8184c732ef5af7b6c13f5b65176de8e02fbfd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ingo=20B=C3=BCrk?= Date: Thu, 25 Feb 2016 19:12:09 +0100 Subject: [PATCH 180/187] Determine focused workspace correctly when moving workspace to output. This patch correctly determines the previously focused workspace on the target output when moving a workspace to another output. Before, we used nodes_head for this, which will not actually return the previously focused workspace, but just the first workspace on that output. Hence, we now use focus_head instead. This bug was introduced all the way back in 1e143fea when the feature of moving workspaces to another output was first implemented. fixes #2229 --- src/workspace.c | 2 +- testcases/t/530-bug-2229.t | 50 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+), 1 deletion(-) create mode 100644 testcases/t/530-bug-2229.t diff --git a/src/workspace.c b/src/workspace.c index ba19cb5f..f8d15ba1 100644 --- a/src/workspace.c +++ b/src/workspace.c @@ -932,7 +932,7 @@ bool workspace_move_to_output(Con *ws, const char *name) { Con *content = output_get_content(output->con); LOG("got output %p with content %p\n", output, content); - Con *previously_visible_ws = TAILQ_FIRST(&(content->nodes_head)); + Con *previously_visible_ws = TAILQ_FIRST(&(content->focus_head)); LOG("Previously visible workspace = %p / %s\n", previously_visible_ws, previously_visible_ws->name); bool workspace_was_visible = workspace_is_visible(ws); diff --git a/testcases/t/530-bug-2229.t b/testcases/t/530-bug-2229.t new file mode 100644 index 00000000..cfe61dec --- /dev/null +++ b/testcases/t/530-bug-2229.t @@ -0,0 +1,50 @@ +#!perl +# vim:ts=4:sw=4:expandtab +# +# Please read the following documents before working on tests: +# • http://build.i3wm.org/docs/testsuite.html +# (or docs/testsuite) +# +# • http://build.i3wm.org/docs/lib-i3test.html +# (alternatively: perldoc ./testcases/lib/i3test.pm) +# +# • http://build.i3wm.org/docs/ipc.html +# (or docs/ipc) +# +# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# (unless you are already familiar with Perl) +# +# Ticket: #2229 +# Bug still in: 4.11-262-geb631ce +use i3test i3_autostart => 0; + +my $config = <get_workspaces->recv; +my @ws_names = map { $_->{name} } @$get_ws; +ok(!('3' ~~ @ws_names), 'workspace 3 has been closed'); + +exit_gracefully($pid); + +done_testing; From ad702dff1374bcb69ba5d4326512c588c5860e89 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ingo=20B=C3=BCrk?= Date: Thu, 25 Feb 2016 19:28:48 +0100 Subject: [PATCH 181/187] Revert to default binding mode before reloading the config. If a user reloads the config while in some binding mode, the binding mode will revert to the default, but no event will ever be fired, causing a broken i3bar mode display. This patch explicitly reverts to the default binding mode before reloading the config. We reload rather than switch to the binding mode after having reloaded the config because there's no guarantee that mode will even still exist. fixes #2228 --- src/config.c | 5 ++ .../t/263-config-reload-reverts-bind-mode.t | 56 +++++++++++++++++++ 2 files changed, 61 insertions(+) create mode 100644 testcases/t/263-config-reload-reverts-bind-mode.t diff --git a/src/config.c b/src/config.c index fb2feda8..9028a881 100644 --- a/src/config.c +++ b/src/config.c @@ -71,6 +71,11 @@ bool parse_configuration(const char *override_configpath, bool use_nagbar) { */ void load_configuration(xcb_connection_t *conn, const char *override_configpath, bool reload) { if (reload) { + /* If we are currently in a binding mode, we first revert to the + * default since we have no guarantee that the current mode will even + * still exist after parsing the config again. See #2228. */ + switch_mode("default"); + /* First ungrab the keys */ ungrab_all_keys(conn); diff --git a/testcases/t/263-config-reload-reverts-bind-mode.t b/testcases/t/263-config-reload-reverts-bind-mode.t new file mode 100644 index 00000000..d72d3d94 --- /dev/null +++ b/testcases/t/263-config-reload-reverts-bind-mode.t @@ -0,0 +1,56 @@ +#!perl +# vim:ts=4:sw=4:expandtab +# +# Please read the following documents before working on tests: +# • http://build.i3wm.org/docs/testsuite.html +# (or docs/testsuite) +# +# • http://build.i3wm.org/docs/lib-i3test.html +# (alternatively: perldoc ./testcases/lib/i3test.pm) +# +# • http://build.i3wm.org/docs/ipc.html +# (or docs/ipc) +# +# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# (unless you are already familiar with Perl) +# +# Verifies that reloading the config reverts to the default +# binding mode. +# Ticket: #2228 +# Bug still in: 4.11-262-geb631ce +use i3test i3_autostart => 0; + +my $config = <connect->recv; + +my $cv = AnyEvent->condvar; +$i3->subscribe({ + mode => sub { + my ($event) = @_; + $cv->send($event->{change} eq 'default'); + } +})->recv; + +cmd 'reload'; + +# Timeout after 0.5s +my $t; +$t = AnyEvent->timer(after => 0.5, cb => sub { $cv->send(0); }); + +ok($cv->recv, 'Mode event received'); + +exit_gracefully($pid); + +done_testing; From ad36e32764e6d07c0d48a75196a77a0a20211a70 Mon Sep 17 00:00:00 2001 From: Marcin Szewczyk Date: Wed, 2 Mar 2016 22:00:40 +0100 Subject: [PATCH 182/187] window_update_motif_hints(): uint32_t for fields, fixes #2238 xcb_get_property_value() returns 32-bit property fields --- src/window.c | 27 +++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/src/window.c b/src/window.c index d6136c78..d10811f5 100644 --- a/src/window.c +++ b/src/window.c @@ -320,6 +320,9 @@ void window_update_motif_hints(i3Window *win, xcb_get_property_reply_t *prop, bo * https://people.gnome.org/~tthurman/docs/metacity/xprops_8h-source.html * http://stackoverflow.com/questions/13787553/detect-if-a-x11-window-has-decorations */ +#define MWM_HINTS_FLAGS_FIELD 0 +#define MWM_HINTS_DECORATIONS_FIELD 2 + #define MWM_HINTS_DECORATIONS (1 << 1) #define MWM_DECOR_ALL (1 << 0) #define MWM_DECOR_BORDER (1 << 1) @@ -333,17 +336,23 @@ void window_update_motif_hints(i3Window *win, xcb_get_property_reply_t *prop, bo return; } - /* The property consists of an array of 5 uint64_t's. The first value is a bit - * mask of what properties the hint will specify. We are only interested in - * MWM_HINTS_DECORATIONS because it indicates that the second value of the + /* The property consists of an array of 5 uint32_t's. The first value is a + * bit mask of what properties the hint will specify. We are only interested + * in MWM_HINTS_DECORATIONS because it indicates that the third value of the * array tells us which decorations the window should have, each flag being - * a particular decoration. */ - uint64_t *motif_hints = (uint64_t *)xcb_get_property_value(prop); + * a particular decoration. Notice that X11 (Xlib) often mentions 32-bit + * fields which in reality are implemented using unsigned long variables + * (64-bits long on amd64 for example). On the other hand, + * xcb_get_property_value() behaves strictly according to documentation, + * i.e. returns 32-bit data fields. */ + uint32_t *motif_hints = (uint32_t *)xcb_get_property_value(prop); - if (motif_border_style != NULL && motif_hints[0] & MWM_HINTS_DECORATIONS) { - if (motif_hints[1] & MWM_DECOR_ALL || motif_hints[1] & MWM_DECOR_TITLE) + if (motif_border_style != NULL && + motif_hints[MWM_HINTS_FLAGS_FIELD] & MWM_HINTS_DECORATIONS) { + if (motif_hints[MWM_HINTS_DECORATIONS_FIELD] & MWM_DECOR_ALL || + motif_hints[MWM_HINTS_DECORATIONS_FIELD] & MWM_DECOR_TITLE) *motif_border_style = BS_NORMAL; - else if (motif_hints[1] & MWM_DECOR_BORDER) + else if (motif_hints[MWM_HINTS_DECORATIONS_FIELD] & MWM_DECOR_BORDER) *motif_border_style = BS_PIXEL; else *motif_border_style = BS_NONE; @@ -351,6 +360,8 @@ void window_update_motif_hints(i3Window *win, xcb_get_property_reply_t *prop, bo FREE(prop); +#undef MWM_HINTS_FLAGS_FIELD +#undef MWM_HINTS_DECORATIONS_FIELD #undef MWM_HINTS_DECORATIONS #undef MWM_DECOR_ALL #undef MWM_DECOR_BORDER From 601108437b4e56e1c31c1aa709a393713915c7fe Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 6 Mar 2016 14:45:46 +0100 Subject: [PATCH 183/187] debian: update copyright to machine-readable copyright --- debian/copyright | 65 ++++++++++++++++++++++++++++-------------------- 1 file changed, 38 insertions(+), 27 deletions(-) diff --git a/debian/copyright b/debian/copyright index 46a3d791..f86d850f 100644 --- a/debian/copyright +++ b/debian/copyright @@ -1,30 +1,41 @@ -This Debian package is based on a tarball downloaded from -http://i3wm.org/ +Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ +Upstream-Name: i3 +Upstream-Contact: Michael Stapelberg +Source: https://i3wm.org/ -Copyright: (C) 2009-2011 Michael Stapelberg -All rights reserved. +Files: * +Copyright: 2009 Michael Stapelberg +License: BSD-3-clause -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: +Files: debian/* +Copyright: 2009 Michael Stapelberg +License: BSD-3-clause -* Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - -* Redistributions in binary form must reproduce the above copyright notice, this - list of conditions and the following disclaimer in the documentation and/or other - materials provided with the distribution. - -* Neither the name of Michael Stapelberg, i3 nor the names of its contributors - may be used to endorse or promote products derived from this software without - specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY -EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT -SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, -INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED -TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR -BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN -ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH -DAMAGE. +License: BSD-3-clause + Copyright: © 2009 Michael Stapelberg + All rights reserved. + . + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + . + * Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + . + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + . + * Neither the name of Michael Stapelberg, i3 nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + . + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. From 3ee78284604f0481887e08588051a0284b10f8f1 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 6 Mar 2016 14:46:12 +0100 Subject: [PATCH 184/187] debian/compat: bump debhelper compat level to 9 --- debian/compat | 2 +- debian/control | 2 +- debian/i3-wm.install | 2 ++ debian/rules | 42 ++++++------------------------------------ 4 files changed, 10 insertions(+), 38 deletions(-) create mode 100644 debian/i3-wm.install diff --git a/debian/compat b/debian/compat index 7f8f011e..ec635144 100644 --- a/debian/compat +++ b/debian/compat @@ -1 +1 @@ -7 +9 diff --git a/debian/control b/debian/control index 52fb1538..53ed78bf 100644 --- a/debian/control +++ b/debian/control @@ -2,7 +2,7 @@ Source: i3-wm Section: x11 Priority: extra Maintainer: Michael Stapelberg -Build-Depends: debhelper (>= 7.0.50~), +Build-Depends: debhelper (>= 9), libx11-dev, libxcb-util0-dev (>= 0.3.8), libxcb-keysyms1-dev, diff --git a/debian/i3-wm.install b/debian/i3-wm.install new file mode 100644 index 00000000..a75e2371 --- /dev/null +++ b/debian/i3-wm.install @@ -0,0 +1,2 @@ +etc +usr diff --git a/debian/rules b/debian/rules index 7036b90d..843655ee 100755 --- a/debian/rules +++ b/debian/rules @@ -1,47 +1,17 @@ #!/usr/bin/make -f # vi: ts=8 sw=8 noet -DPKG_EXPORT_BUILDFLAGS = 1 --include /usr/share/dpkg/buildflags.mk - -ifneq (,$(filter parallel=%,$(DEB_BUILD_OPTIONS))) - NUMJOBS = $(patsubst parallel=%,%,$(filter parallel=%,$(DEB_BUILD_OPTIONS))) - MAKEFLAGS += -j$(NUMJOBS) -endif - -build: build-arch build-indep -build-arch: build-stamp -build-indep: build-stamp -build-stamp: - dh build - touch build-stamp - -clean: - dh clean - -install: build install-stamp -install-stamp: - dh install - touch install-stamp - -binary-arch: install - dh binary-arch - -binary-indep: install - dh binary-indep - -binary: binary-arch binary-indep +export V:=1 +export DEB_BUILD_MAINT_OPTIONS = hardening=+all override_dh_auto_build: - $(MAKE) - $(MAKE) -C man - $(MAKE) -C docs + dh_auto_build -- all docs mans override_dh_installchangelogs: dh_installchangelogs RELEASE-NOTES-* -override_dh_install: - $(MAKE) DESTDIR=$(CURDIR)/debian/i3-wm/ install - override_dh_strip: dh_strip --dbg-package=i3-wm-dbg + +%: + dh $@ --parallel From 0d33f70ac76a08e19a0657709c2a6436dc27c100 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 6 Mar 2016 14:48:42 +0100 Subject: [PATCH 185/187] debian/control: bump standards-version to 3.9.7 --- debian/control | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/debian/control b/debian/control index 53ed78bf..577bb4d4 100644 --- a/debian/control +++ b/debian/control @@ -24,7 +24,7 @@ Build-Depends: debhelper (>= 9), libcairo2-dev, libpango1.0-dev, libpod-simple-perl -Standards-Version: 3.9.5 +Standards-Version: 3.9.7 Homepage: http://i3wm.org/ Package: i3 From 8d226693110cac0c437afdea777d6c80ac013e21 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 6 Mar 2016 14:57:28 +0100 Subject: [PATCH 186/187] debian/watch: verify signature, use https --- debian/upstream/signing-key.asc | 56 +++++++++++++++++++++++++++++++++ debian/watch | 3 +- 2 files changed, 58 insertions(+), 1 deletion(-) create mode 100644 debian/upstream/signing-key.asc diff --git a/debian/upstream/signing-key.asc b/debian/upstream/signing-key.asc new file mode 100644 index 00000000..d0ef661b --- /dev/null +++ b/debian/upstream/signing-key.asc @@ -0,0 +1,56 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- +Version: GnuPG v1 + +mQINBEoVG4cBEADX2160pBoUf2vSWKbUa8soEMscBFjmb/NajCxwX/BlD1sVNyDm +twZ74CNPS7X5GgNQoXCzkm7v18zOpON69/pwQ0C4T4P+dvewaDzi2+4/bZsXSor1 +mA3C9lHcKDbpH7jHkN2AbMnY3Z4LD46LA1qfCISAAKtx1h4peBF6Xhu743dKXrBa +zg/TEJwWIWSyPKgIhur95yebD/Tws+gWlOfBKkF1v1PA+5sPmC8LyK5Rd1n9Sg1D +j//4sWl8A4EwM4QUzSliZME775klV4mOBGbsTnhNjCymgDiXVNjoWdEIHoNfDsut +E2czgSwsSrSPls/Kl1KuHyBiOWi4dl6MFaypcuSNEVNi5K+oJ7gmX/sy/TlF5Ofw +KoBEPrcvulVT8aAM3azMfb/Fgo+GcEEYljV1yvSg7jSjCHxXgMyh/yMfZcPkwajp +fNE5D7WAXgygpolM9dLIOBemDJxwWr0G7uhXNv3vSHpuUheb2REaJJwWHw1IuCmn +gigD5mebQWRSmbEl66ygOFkps9FEq6KSmbHkj7dIrSVmK5DtQRRI5fMPI+E+atul +Lnpgm/R2p2yvPKoS/pr9mwvKIf9F5C20wm1iAaGW1pTDSIl2y2ZpzcJIyS+jhyCX +3d6D7FNEFlI2p9Tnbt9aE04ASLlZFGjxNWweU8zAkNOr1MyPTiWrYtsCtwARAQAB +tCpNaWNoYWVsIFN0YXBlbGJlcmcgPG1pY2hhZWxAc3RhcGVsYmVyZy5kZT6JAkAE +EwEKACoCGy8CHgECF4AFCwkIBwMFFQoJCAsFFgIDAQACGQEFAlNdKVoFCRKuD04A +CgkQTnFg7UrI7h1HYw//R7WBr/MrqevbaB6Uh7Koy3rN1GqXXY7L4kQAO1XSrmC9 +IQ/giwg7+655tDWq4cAjefiBWRv0I1WWqZwdgUGwfhzW20DBx2sPkGKZ29pcvU/k +LuMyWs49o2lcsb4cQqgDpH/uzi22fc4BhO91o/uZYOAXrrSlLuzkCa1SDCRymwdw +lIXIXktROd+r6Fpc1FAinOQgn5BQjf7gbSZSlqBLeYZdR+qSxZWufrhsVUy03nVx +mF9hc/aTFNYZHHHh0yFzYfBKisqsuwJW94uW80xw17HoBMSb10eNGEq5xWqh4Owu +8heJePlcoh2F5JnO6cFWoz1bHCZGjeeIm0OdPJXTLDQdcA5Hy4K3ADidqW0y+Iza +Wbs3TpLprLw91LaPcwzZf+vzRgsQCwPKODjhcetEaYGIKweCkNQRQCW6wEl7kRAw +/eG1wdn4YfEcnCz4ye1MW67au3omvBy41BNmGb20rEc9JIQ37HhAJy5MwuguuO1Z +xZWyu3fV39YLwvsa8EYFPb/DOUTmSCBCyvfTOCEC94Vl2kcPXicIpaRnCFZNqVEJ +FAMKY/tSVjPsBEXTFx3aiX3am4CCtc8R95z2DrYtW5UU/yA5o6lDnfRX6Smdl620 +kTM/gFEgAI8+x56XsWJ/CnG//EbgKMy8u2u5y7x1SdpZFxLf722EryF0yPJt+im0 +OU1pY2hhZWwgU3RhcGVsYmVyZyAoUkVOVC1BLUdVUlUpIDxtaWNoYWVsQHJlbnQt +YS1ndXJ1LmRlPokCPQQTAQoAJwIbLwIeAQIXgAULCQgHAwUVCgkICwUWAgMBAAUC +U10pWgUJEq4PTgAKCRBOcWDtSsjuHUTREACcBXnZwinxZ8S0DOl2OR7qm8ao4U/n +h71tkJX9klnXYY8KQD54tuGjYjCA+UvTOX7c0Rzj3WijgyRxefmOhPQzkk/zUheH +bsaYSbj2mCvA/IkMRe6G0+wyFU5ydssLVApx/+bwdL3CiIoFPwyHMgWPjYuijIts +UMbq7jtnF26l7O0GSY5uHSUQb7caz+Mu0CcF95h3oxRxHVAhHIMtwzkilbjbshEf +nxDH5L0s2hT0amkEB2jw3US2v+YrThk0ZQPoB+tgNLL2Li7yAuwbaEaK37aDTtkX +NdFiAwcOHrLhlD0SnNya4nEVwgnCu5B9f+OwalPq3a0+G394L+a+XHWWwXc6MlFz +WhzAMeE1uFJfbIIGEL/Q3URBbhIUf0xsZEagsjNExgYtJY5XJRitgyxPwAuxusia +VhfTmbr3Mr5yu7QEt1oACq3j0bNr7hzcPk+ckHYbsSvuoo21Ef5vhQetXpAzrot/ ++62c7i1xcAvY7MVBT2f/7BC4cYvLXALhcvLAabzOcD8lBPEIBkxgJj42EckEzFzC +c9s5htWdhWYIAIIdIOxZejxfRiTvujg5CaJo+Fg/BL4TTlBgjU7ASzMQBfos3cII +F8O9Yc+W04uMyG9QPqk//rIUFdKgUyd4tzXkQrJs7Jom7duxJPpr5dDMLqTwINL5 +LBBKw/lpA/nAkLQqTWljaGFlbCBTdGFwZWxiZXJnIDxzdGFwZWxiZXJnQGRlYmlh +bi5vcmc+iQI9BBMBCgAnAhsvBQsJCAcDBRUKCQgLBRYCAwEAAh4BAheABQJTXSla +BQkSrg9OAAoJEE5xYO1KyO4dGBkP/353sh8feSyxJNXMdgAfe+okYE4B9iWS2zRy +GAEGKyliaMWDJLRhT4ln1glr05pFoy52Hxe4+NBpSYuZ9NV390HRmPxbFaUvHs74 +HPkuABxy2XKH94IS1nrb+fleR00w0rLEin4aW3mtwKDHCJtNUW0/DNtC7Uz028SL +9TftaqJrsf+paTFJkZF0ShJ2A6XFxwlvBiVz4f2UDEi7GQuAaYI5V5ZosWIAxdYG +631tKf1EU9YtjPYdBk3YF4Q44AhVbe478Ji24Of0G/l+dGFRzbdARNayBwegJoMu +djXQrPfR1jNVSaw65/AinbbtadlULSTrvhoD6ommGeU6fQS3WtNBbF4T5SuNRBeK +QhWuLSOllmjWfERaj6omg8GXd1rctIFnUhT/dl6bmhooMPRUW1DNxVzqd7UFnqXE ++CaJ7cGfI/9/MITTG32QpjkVeowOcuoZ+qLoOu7dBbagDaEn8T6yFCLACyf9LGBm +MC9bNSGsMRCFV33N/PbVcgf6M0z46d0ysPgGiR5MX1O6CQkJGrwolfA5sK3VIsyp +PaCCHpAuPdEiWB57WleURqCIGWV2ccElyA4M4auDGt1SSNl0NfWu8vde6wNIPir/ +DBqh1265QLrLS239UT0u5hYE5hkfiKP2dzgWKh9NT9xm2Dyjjv/PaqWlVedvsVIp +VRx/oRxr +=1n/p +-----END PGP PUBLIC KEY BLOCK----- diff --git a/debian/watch b/debian/watch index ab71a19e..f434b2e6 100644 --- a/debian/watch +++ b/debian/watch @@ -1,2 +1,3 @@ version=3 -http://i3wm.org/downloads/ /downloads/i3-(.*)\.tar\.bz2 +opts=pgpsigurlmangle=s/$/.asc/ \ + https://i3wm.org/downloads/ /downloads/i3-(.*)\.tar\.bz2 From c9f0bc174d0746c925443d49bcc07727b631e335 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 6 Mar 2016 16:17:15 +0100 Subject: [PATCH 187/187] release i3 4.12 --- RELEASE-NOTES-4.11 | 115 --------------------------------------- RELEASE-NOTES-4.12 | 132 +++++++++++++++++++++++++++++++++++++++++++++ man/asciidoc.conf | 2 +- 3 files changed, 133 insertions(+), 116 deletions(-) delete mode 100644 RELEASE-NOTES-4.11 create mode 100644 RELEASE-NOTES-4.12 diff --git a/RELEASE-NOTES-4.11 b/RELEASE-NOTES-4.11 deleted file mode 100644 index 2cbadda9..00000000 --- a/RELEASE-NOTES-4.11 +++ /dev/null @@ -1,115 +0,0 @@ - - ┌────────────────────────────┐ - │ Release notes for i3 v4.11 │ - └────────────────────────────┘ - -This is i3 v4.11. This version is considered stable. All users of i3 are -strongly encouraged to upgrade. - -Aside from plenty of new features, there are several changes that might result -in behavioral changes, depending on your specific configuration and -environment. As usual, we tried hard to keep the current behavior, and when we -changed behavior, we strongly believe it’s for the better. - -Keyboard binding handling has been made more correct, for details see: -https://github.com/i3/i3/commit/bf3cd41b5ddf1e757515ab5fbf811be56e5f69cc - - ┌────────────────────────────┐ - │ Changes in i3 v4.11 │ - └────────────────────────────┘ - - • docs/debugging: provide instructions on how to debug i3bar - • docs/debugging: added a note about sensitive data - • docs/userguide: add a note to both “exec”s about semicolon and comma - • docs/userguide: quoted strings need to be used, escaping isn’t possible - • docs/userguide: make syntax of syntax descriptions consistent - • docs/userguide: recommend “exec exec” for correct signal handling - • docs/userguide: explain i3-config-wizard’s behavior - • i3-nagbar: open on the primary screen - • i3-config-wizard: respect XDG config directories - • i3-input: position i3-input at window with input focus - • i3bar: use a reasonable default sep_block_width if a separator_symbol is given - • i3bar: add binding mode indicator - • i3bar: add bindsym command (deprecates wheel_{up,down}_cmd) - • i3bar: make tray padding configurable - • makefiles: respect EXEC_PREFIX and PKG_CONFIG - • added a --toggle switch to mark: “mark [--toggle] ” - • added “focus_on_window_activation” directive - • added “no_focus” directive - • added “move [container|window] [to] mark ” command - • added “move [window|container] [to] position mouse|cursor|pointer” command - • added “title_format” command - • added “resize set [width] [height]” command - • added “sticky” command (for floating containers) - • added “workspace” criterion - • added “window_type” criterion - • make center coordinates relative to current workspace - • draw marks in window decoration (configure with show_marks) - • only mark a window if only one window is matched - • make floating window mouse handling consistent with tiled windows - • add a --border flag to enable mouse binds to trigger on border click - • set the _NET_WM_STATE_HIDDEN atom on windows that are currently not visible - due to being in the non-focused tab of a stacked or tabbed container - • ignore InputHint when not in WM_HINTS - • display which config is used in i3 --moreversion - • support config file line continuation - • use WM_SIZE_HINTS when present to set the geometry of floating windows - • add “tray_output primary” to the default config - • use libxkbcommon for translating keysyms, support all XKB groups - • support special value “__focused__” in criteria - • support _NET_WM_VISIBLE_NAME - • make sure borders are never counted as adjacent to the edge for floating - containers - • support moving dock clients to another output - • let “focus” report success depending on whether a window was matched - • handle _NET_WM_STATE_STICKY (for floating containers) - • make “debuglog on” command persist over restarts - • randr: use root window in case of no randr outputs - • set proper WM_CLASS on frame windows - - ┌────────────────────────────┐ - │ Bugfixes │ - └────────────────────────────┘ - - • i3bar: only detect clicks within the statusline width - • i3bar: fix flickering shortened status bar on other output(s) - • i3bar: send custom-defined command upon click on the non-statusline part of - i3bar even if workspace_buttons is set to “no”. - • i3-config-wizard: Make window size and click coordinates dependent on font - • i3-save-tree: retain “rect” for floating cons - • move urgency hint when moving container - • fix percents when attaching a window to a ws creates a new split con - • cope with non-null-terminated x class properties - • get workspace name when renaming current workspace - • allow single-child non-default layout cons to be moved between outputs - • allow --whole-window right after 'bindsym' within binding modes - • remove windows from the save set when unmapping (fixes problems with e.g. - owncloud when restarting i3) - • serialize con_id with %p in run_binding() - • initialize workspace rect to the output's upon creation - • mkdirp: do not throw an error if directory exists - • grab all buttons when managing a window to also allow 'bindsym - --whole-window button4 …' to work correctly - • properly clear the urgency hint when set by i3 - • layout restore: load floating containers correctly - • layout restore: remove remaining criteria when swallowing window - • layout restore: When appending a layout containing a marked container, make - sure that any other containers with the same mark are unmarked during - insertion of the new container. - • use the EWMH support window rather than the root window as an input focus fallback - • use the focused container to determine the target window_mode when using - floating mode_toggle - - ┌────────────────────────────┐ - │ Thanks! │ - └────────────────────────────┘ - -Thanks for testing, bugfixes, discussions and everything I forgot go out to: - - Andrzej Pronobis, Chris West (Faux), Deiz, Felix C. Stegerman, Georgiy Tugai, - hwangcc23, Ingo Bürk, Kacper Kowalik (Xarthisius), lasers, lambithal, Michael - Hofmann, Michael Tipton, Micha Rosenbaum, Nikita Mikhailov, Nils Schneider, - PopeLevi, rr-, shdown, Simon Nagl, Theo Buehler, Thomas Anderson, Tim Creech, - Tony Crisci - --- Michael Stapelberg, 2015-09-30 diff --git a/RELEASE-NOTES-4.12 b/RELEASE-NOTES-4.12 new file mode 100644 index 00000000..57cd9553 --- /dev/null +++ b/RELEASE-NOTES-4.12 @@ -0,0 +1,132 @@ + + ┌────────────────────────────┐ + │ Release notes for i3 v4.12 │ + └────────────────────────────┘ + +This is i3 v4.12. This version is considered stable. All users of i3 are +strongly encouraged to upgrade. + +If cairo ≥ 1.14.4 is available, i3 and i3bar will use cairo for rendering +(instead of raw X11 drawing primitives). While this is currently optional, +having cairo ≥ 1.14.4 will be a hard requirement in future release. + +This release contains a good number of detail improvements and fixes. + + ┌────────────────────────────┐ + │ Changes in i3 v4.12 │ + └────────────────────────────┘ + + • use https instead of git/http, update contact information, add GPG key + • docs/hacking-howto: fix old cfgparse.y reference to config_parser.c + • docs/ipc: added link to i3ipcpp (C++ library) + • docs/userguide: clarify no_focus documentation + • docs/userguide: add documentation for binding modes + • docs/userguide: fix rendering of __focused__ + • docs/userguide: improve placement of explicit IDs for headings + • docs/userguide: make rendering of key bindings more consistent + • docs/userguide: clarify quoting of “exec” commands + • man/i3-nagbar: fix example invocation + • man/i3: add “floating window” to terminology + • i3-sensible-*: quote variables correctly + • i3-sensible-editor: add neovim + • i3-sensible-terminal: add termit, st + • i3bar: use cairo for all drawing operations + • i3bar: support per-statusblock border and background colors + • i3bar: support different bar background colors depending on whether the bar + is on the focused output or not + • i3bar: multiple tray_output directives on the same bar are now supported + • i3bar: support disabling the modifier by specifying “modifier none” + • use cairo for all drawing operations + • fix a number of memory leaks, thanks to AddressSanitizer + • no_focus is now suppressed for the first window of a workspace + • “workspace next/prev” now looks for numbered workspaces after reaching the + last workspace (it used to incorrectly only look at named workspaces) + • multiple marks can now be set on a single window (but a mark can still only + be present on one window at a time) + • the “unmark” command now supports criteria + • the “con_id” criterion now supports the special value __focused__ + • the “workspace” command now supports the --no-auto-back-and-forth parameter + • the “move window to workspace” command now supports the + --no-auto-back-and-forth parameter + • the “resize grow|shrink width|height” command now works for a nested split + in the same direction + • support _NET_WM_USER_TIME’s special 0 value, indicating that a window + should not be focused + • use 32-bit visual by default if available. This reduces graphical glitches + when using transparency (which is still not officially supported) + • the “move position center” command now supports criteria + • specifying invalid match criteria now results in an error instead of + blindly applying the operation to the currently focused window + • allow mouse bindings to run on the root window + • support matching _NET_WM_WINDOW_TYPE_NOTIFICATION in criteria + • all criteria are now matched, even when con_id or con_mark are given (used + to be a special case) + • allow the “id” criterion to be specified in any base recognized by + strtol(), not only base 10 + • non-true color displays are now supported again (e.g. the Raspberry Pi) + • the “split” command now has a “toggle” option + • the additional color class “decoration_border” was added + • title_format is now stored on containers instead of windows, allowing the + use of title_format on split containers + • On OpenBSD, i3 now uses pledge(2) + • support _NET_WM_DESKTOP (for pager applications like gnome-panel) + • floating workspaces are no longer available (they were not supported for a + while now) + • floating windows now carry the I3_FLOATING_WINDOW atom so that tools like + compositors can be configured to match on floating windows + + ┌────────────────────────────┐ + │ Bugfixes │ + └────────────────────────────┘ + + • i3bar: display short text only on the monitor(s) on which it is necessary + • i3bar: explicitly set cursor using libxcb-cursor if available + • i3bar: fix XEMBED messages + • i3-nagbar: explicitly set cursor using libxcb-cursor if available + • duplicated keybindings are now also detected when one uses bindcode but the + other(s) use(s) bindsym + • keymap fallback for servers without XKB (e.g. TightVNC) has been added + • using pango markup in mode names is now optional, fixing a regression in i3 + v4.11 where modes which contained characters such as “<” would break. + • moving windows to a workspace by specifying a mark now works + • the root output is now used when any RandR request fails (for x2go) + • assignments are now marked as run before executing them, preventing endless + loops/crashes when assignments cause another assignment evaluation + • splitting/floating a dock container no longer crashes i3 + • correctly compare modifier mask when identifying keybindings (fixes + bindings which use --release) + • no longer fail config validation when there is no newline at the end of + the config file + • scrollwheel buttons are now only grabbed when necessary, allowing the use + of “bindsym button*” or scrolling in windows without focusing them (in case + no “bindsym button*” is present) + • parse con_id in base 16 (affected FreeBSD only) + • fix crash when opening a large number of windows + • reject empty swallow definitions to avoid crashes + • don’t remove SubstructureRedirect event mask temporarily (fixes i3bar + stopping after system suspend) + • move urgent flag before killing the parent to avoid a crash + • correctly validate “kill” command to avoid crashing when “kill” is invoked + on workspace containers + • actually accept the documented “workspace” token as an alternative to “→” + in assign statements + • remove _NET_WM_STATE on withdrawn windows to comply with the spec + • the “border” command now uses logical pixels (relevant for hi-dpi displays) + • “tray_output primary” does not properly fall back and hence was removed + from the default config again + • correctly determine focused workspace when moving workspace to output + • revert to default binding mode before reloading the config file + • correctly interpret _MOTIF_WM_HINTS (endianness-dependent) + + ┌────────────────────────────┐ + │ Thanks! │ + └────────────────────────────┘ + +Thanks for testing, bugfixes, discussions and everything I forgot go out to: + + Adaephon, Airblader, Alexis211, bendem, botovq, brianmillar, DavidMikeSimon, + dcoppa, Florian Merkel, fmthoma, frederik, hwangcc23, jolange, Juuso + Lapinlampi, kneitinger, lotheac, nicklan, norrland, pra85, romanblanco, + sur5r, tbu-, tyll, wodny + +-- Michael Stapelberg, 2016-03-06 diff --git a/man/asciidoc.conf b/man/asciidoc.conf index 6cbcebe8..11e76501 100644 --- a/man/asciidoc.conf +++ b/man/asciidoc.conf @@ -7,7 +7,7 @@ template::[header-declarations] {mantitle} {manvolnum} i3 -4.11 +4.12 i3 Manual