]> git.sur5r.net Git - i3/i3/commitdiff
Implement dock mode, update testsuite
authorMichael Stapelberg <michael@stapelberg.de>
Sun, 20 Feb 2011 22:43:03 +0000 (23:43 +0100)
committerMichael Stapelberg <michael@stapelberg.de>
Sun, 20 Feb 2011 22:43:03 +0000 (23:43 +0100)
Currently, dock clients are only possible at the top.

15 files changed:
include/con.h
include/data.h
include/tree.h
src/cmdparse.y
src/con.c
src/ipc.c
src/manage.c
src/randr.c
src/render.c
src/tree.c
src/workspace.c
src/x.c
testcases/t/02-fullscreen.t
testcases/t/16-nestedcons.t
testcases/t/lib/i3test.pm

index 0d4df204d33c5d41c40864fc250fefd17c415d5c..e8944b62875c16de091a3cc8c5d709cdf5633517 100644 (file)
@@ -182,6 +182,8 @@ Rect con_border_style_rect(Con *con);
  * borderless and the only element in the tabbed container, the border is not
  * rendered.
  *
+ * For children of a CT_DOCKAREA, the border style is always none.
+ *
  */
 int con_border_style(Con *con);
 
index 5c4a88526c35e1e8c602f10ff986f51aa17428e1..e8a796cc947a06ad2eea18a52500b26e335a01bc 100644 (file)
@@ -263,23 +263,40 @@ struct Match {
 
     enum { M_USER = 0, M_RESTART } source;
 
-    /* wo das fenster eingefügt werden soll. bei here wird es direkt
-     * diesem Con zugewiesen, also layout saving. bei active ist es
-     * ein assignment, welches an der momentan fokussierten stelle einfügt */
-    enum { M_HERE = 0, M_ACTIVE } insert_where;
+    /* Where the window looking for a match should be inserted:
+     *
+     * M_HERE   = the matched container will be replaced by the window
+     *            (layout saving)
+     * M_ACTIVE = the window will be inserted next to the currently focused
+     *            container below the matched container
+     *            (assignments)
+     * M_BELOW  = the window will be inserted as a child of the matched container
+     *            (dockareas)
+     *
+     */
+    enum { M_HERE = 0, M_ACTIVE, M_BELOW } insert_where;
 
     TAILQ_ENTRY(Match) matches;
 };
 
 struct Con {
     bool mapped;
-    enum { CT_ROOT = 0, CT_OUTPUT = 1, CT_CON = 2, CT_FLOATING_CON = 3, CT_WORKSPACE = 4 } type;
+    enum {
+        CT_ROOT = 0,
+        CT_OUTPUT = 1,
+        CT_CON = 2,
+        CT_FLOATING_CON = 3,
+        CT_WORKSPACE = 4,
+        CT_DOCKAREA = 5
+    } type;
     orientation_t orientation;
     struct Con *parent;
 
     struct Rect rect;
     struct Rect window_rect;
     struct Rect deco_rect;
+    /** the geometry this window requested when getting mapped */
+    struct Rect geometry;
 
     char *name;
 
@@ -332,7 +349,7 @@ struct Con {
     TAILQ_HEAD(swallow_head, Match) swallow_head;
 
     enum { CF_NONE = 0, CF_OUTPUT = 1, CF_GLOBAL = 2 } fullscreen_mode;
-    enum { L_DEFAULT = 0, L_STACKED = 1, L_TABBED = 2 } layout;
+    enum { L_DEFAULT = 0, L_STACKED = 1, L_TABBED = 2, L_DOCKAREA = 3, L_OUTPUT = 4 } layout;
     border_style_t border_style;
     /** floating? (= not in tiling layout) This cannot be simply a bool
      * because we want to keep track of whether the status was set by the
index 40d9a5417714a9c76ec1268b641dce8ca52d8919..cdcb4878694c12014f27d697ae29a343e2898567 100644 (file)
@@ -24,7 +24,7 @@ void tree_init();
  * Opens an empty container in the current container
  *
  */
-Con *tree_open_con(Con *con);
+Con *tree_open_con(Con *con, bool focus_it);
 
 /**
  * Splits (horizontally or vertically) the given container by creating a new
index d71773fe64f25f1d4366265d5ce75f8361484b59..3372426483e68c359622677b4779561cfc96be02 100644 (file)
@@ -403,7 +403,7 @@ open:
     TOK_OPEN
     {
         printf("opening new container\n");
-        Con *con = tree_open_con(NULL);
+        Con *con = tree_open_con(NULL, true);
         asprintf(&json_output, "{\"success\":true, \"id\":%ld}", (long int)con);
     }
     ;
index 8992d13289249d29acd3a896380f6c3729a66005..b1e652a28f8499616e5caf34f1a6b29d0df22224 100644 (file)
--- a/src/con.c
+++ b/src/con.c
@@ -226,7 +226,6 @@ Con *con_get_workspace(Con *con) {
     Con *result = con;
     while (result != NULL && result->type != CT_WORKSPACE)
         result = result->parent;
-    assert(result != NULL);
     return result;
 }
 
@@ -694,6 +693,8 @@ Rect con_border_style_rect(Con *con) {
  * borderless and the only element in the tabbed container, the border is not
  * rendered.
  *
+ * For children of a CT_DOCKAREA, the border style is always none.
+ *
  */
 int con_border_style(Con *con) {
     Con *fs = con_get_fullscreen_con(con->parent);
@@ -708,6 +709,9 @@ int con_border_style(Con *con) {
     if (con->parent->layout == L_TABBED && con->border_style != BS_NORMAL)
         return (con_num_children(con->parent) == 1 ? con->border_style : BS_NORMAL);
 
+    if (con->parent->type == CT_DOCKAREA)
+        return BS_NONE;
+
     return con->border_style;
 }
 
@@ -773,8 +777,12 @@ void con_set_layout(Con *con, int layout) {
 static void con_on_remove_child(Con *con) {
     DLOG("on_remove_child\n");
 
-    /* Nothing to do for workspaces */
-    if (con->type == CT_WORKSPACE || con->type == CT_OUTPUT || con->type == CT_ROOT) {
+    /* Every container 'above' (in the hierarchy) the workspace content should
+     * not be closed when the last child was removed */
+    if (con->type == CT_WORKSPACE ||
+        con->type == CT_OUTPUT ||
+        con->type == CT_ROOT ||
+        con->type == CT_DOCKAREA) {
         DLOG("not handling, type = %d\n", con->type);
         return;
     }
index a0dd64ccbc653304f9bc18b0a420234ca5101afe..fe1b24a8e36ba964b5b4fdcd8dfda50a819d568b 100644 (file)
--- a/src/ipc.c
+++ b/src/ipc.c
@@ -288,44 +288,47 @@ IPC_HANDLER(get_workspaces) {
 
     Con *output;
     TAILQ_FOREACH(output, &(croot->nodes_head), nodes) {
-        Con *ws;
-        TAILQ_FOREACH(ws, &(output->nodes_head), nodes) {
-            assert(ws->type == CT_WORKSPACE);
-            y(map_open);
-
-            ystr("num");
-            if (ws->num == -1)
-                y(null);
-            else y(integer, ws->num);
-
-            ystr("name");
-            ystr(ws->name);
-
-            ystr("visible");
-            y(bool, workspace_is_visible(ws));
-
-            ystr("focused");
-            y(bool, ws == focused_ws);
-
-            ystr("rect");
-            y(map_open);
-            ystr("x");
-            y(integer, ws->rect.x);
-            ystr("y");
-            y(integer, ws->rect.y);
-            ystr("width");
-            y(integer, ws->rect.width);
-            ystr("height");
-            y(integer, ws->rect.height);
-            y(map_close);
-
-            ystr("output");
-            ystr(output->name);
-
-            ystr("urgent");
-            y(bool, ws->urgent);
-
-            y(map_close);
+        Con *child;
+        TAILQ_FOREACH(child, &(output->nodes_head), nodes) {
+            Con *ws;
+            TAILQ_FOREACH(ws, &(child->nodes_head), nodes) {
+                assert(ws->type == CT_WORKSPACE);
+                y(map_open);
+
+                ystr("num");
+                if (ws->num == -1)
+                    y(null);
+                else y(integer, ws->num);
+
+                ystr("name");
+                ystr(ws->name);
+
+                ystr("visible");
+                y(bool, workspace_is_visible(ws));
+
+                ystr("focused");
+                y(bool, ws == focused_ws);
+
+                ystr("rect");
+                y(map_open);
+                ystr("x");
+                y(integer, ws->rect.x);
+                ystr("y");
+                y(integer, ws->rect.y);
+                ystr("width");
+                y(integer, ws->rect.width);
+                ystr("height");
+                y(integer, ws->rect.height);
+                y(map_close);
+
+                ystr("output");
+                ystr(output->name);
+
+                ystr("urgent");
+                y(bool, ws->urgent);
+
+                y(map_close);
+            }
         }
     }
 
index a3bad45f2e72ad3db44e28c5d7e70d1e9bc9edd5..0c0dcae758e1ac3942aea7133e1dd7d836620e52 100644 (file)
@@ -167,8 +167,9 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki
             LOG("using current container, focused = %p, focused->name = %s\n",
                             focused, focused->name);
             nc = focused;
-        } else nc = tree_open_con(NULL);
+        } else nc = tree_open_con(NULL, true);
     } else {
+        /* M_ACTIVE are assignments */
         if (match != NULL && match->insert_where == M_ACTIVE) {
             /* We need to go down the focus stack starting from nc */
             while (TAILQ_FIRST(&(nc->focus_head)) != TAILQ_END(&(nc->focus_head))) {
@@ -178,9 +179,16 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki
             /* We need to open a new con */
             /* TODO: make a difference between match-once containers (directly assign
              * cwindow) and match-multiple (tree_open_con first) */
-            nc = tree_open_con(nc->parent);
+            nc = tree_open_con(nc->parent, true);
+        }
+
+        /* M_BELOW inserts the new window as a child of the one which was
+         * matched (e.g. dock areas) */
+        else if (match != NULL && match->insert_where == M_BELOW) {
+            nc = tree_open_con(nc, !cwindow->dock);
         }
     }
+
     DLOG("new container = %p\n", nc);
     nc->window = cwindow;
     x_reinit(nc);
@@ -208,6 +216,8 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki
          con_by_window_id(cwindow->leader) != NULL))
         want_floating = true;
 
+    nc->geometry = (Rect){ geom->x, geom->y, geom->width, geom->height };
+
     if (want_floating) {
         nc->rect.x = geom->x;
         nc->rect.y = geom->y;
index e8b044bb5fa1a6b222cd3c41b6d596c6b8fa48da..9abe9c7c1d0819a1798b9567956e1d0c8d88cff5 100644 (file)
@@ -250,6 +250,7 @@ void output_init_con(Output *output) {
         FREE(con->name);
         con->name = sstrdup(output->name);
         con->type = CT_OUTPUT;
+        con->layout = L_OUTPUT;
     }
     con->rect = output->rect;
     output->con = con;
@@ -257,12 +258,43 @@ void output_init_con(Output *output) {
     char *name;
     asprintf(&name, "[i3 con] output %s", con->name);
     x_set_name(con, name);
-    free(name);
+    FREE(name);
 
     if (reused) {
         DLOG("Not adding workspace, this was a reused con\n");
         return;
     }
+
+    DLOG("Changing layout, adding top/bottom dockarea\n");
+    Con *topdock = con_new(NULL);
+    topdock->type = CT_DOCKAREA;
+    topdock->layout = L_DOCKAREA;
+    topdock->orientation = VERT;
+    /* this container swallows dock clients */
+    Match *match = scalloc(sizeof(Match));
+    match_init(match);
+    match->dock = true;
+    match->insert_where = M_BELOW;
+    TAILQ_INSERT_TAIL(&(topdock->swallow_head), match, matches);
+
+    topdock->name = sstrdup("topdock");
+
+    asprintf(&name, "[i3 con] top dockarea %s", con->name);
+    x_set_name(topdock, name);
+    FREE(name);
+    DLOG("attaching\n");
+    con_attach(topdock, con, false);
+
+    DLOG("adding main content container\n");
+    Con *content = con_new(NULL);
+    content->type = CT_CON;
+    content->name = sstrdup("content");
+
+    asprintf(&name, "[i3 con] content %s", con->name);
+    x_set_name(content, name);
+    FREE(name);
+    con_attach(content, con, false);
+
     DLOG("Now adding a workspace\n");
 
     /* add a workspace to this output */
@@ -295,7 +327,7 @@ void output_init_con(Output *output) {
         DLOG("result for ws %s / %d: exists = %d\n", ws->name, c, exists);
     }
     ws->num = c;
-    con_attach(ws, con, false);
+    con_attach(ws, content, false);
 
     asprintf(&name, "[i3 con] workspace %s", ws->name);
     x_set_name(ws, name);
index c6ebb5ccb0c4f97dd39731a9f6c6d4faab7925b8..c8ea318452f27f71213e2e20d91409886e0926af 100644 (file)
@@ -8,6 +8,73 @@
  * container (for debugging purposes) */
 static bool show_debug_borders = 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) {
+    Con *child, *dockchild;
+
+    int x = con->rect.x;
+    int y = con->rect.y;
+    int height = con->rect.height;
+    DLOG("Available height: %d\n", height);
+
+    /* First pass: determine the height of all CT_DOCKAREAs (the sum of their
+     * children) and figure out how many pixels we have left for the rest */
+    TAILQ_FOREACH(child, &(con->nodes_head), nodes) {
+        if (child->type != CT_DOCKAREA)
+            continue;
+
+        child->rect.height = 0;
+        TAILQ_FOREACH(dockchild, &(child->nodes_head), nodes)
+            child->rect.height += dockchild->geometry.height;
+        DLOG("This dockarea's height: %d\n", child->rect.height);
+
+        height -= child->rect.height;
+    }
+
+    DLOG("Remaining: %d\n", height);
+
+    /* Second pass: Set the widths/heights */
+    TAILQ_FOREACH(child, &(con->nodes_head), nodes) {
+        if (child->type == CT_CON) {
+            if (height == -1) {
+                DLOG("More than one CT_CON on output container\n");
+                assert(false);
+            }
+            child->rect.x = x;
+            child->rect.y = y;
+            child->rect.width = con->rect.width;
+            child->rect.height = height;
+            height = -1;
+        }
+
+        else if (child->type != CT_DOCKAREA) {
+            DLOG("Child %p of type %d is inside the OUTPUT con\n", child, child->type);
+            assert(false);
+        }
+
+        child->rect.x = x;
+        child->rect.y = y;
+        child->rect.width = con->rect.width;
+
+        child->deco_rect.x = 0;
+        child->deco_rect.y = 0;
+        child->deco_rect.width = 0;
+        child->deco_rect.height = 0;
+
+        y += child->rect.height;
+
+        DLOG("child at (%d, %d) with (%d x %d)\n",
+                child->rect.x, child->rect.y, child->rect.width, child->rect.height);
+        DLOG("x now %d, y now %d\n", x, y);
+        x_raise_con(child);
+        render_con(child, false);
+    }
+}
+
 /*
  * "Renders" the given container (and its children), meaning that all rects are
  * updated correctly. Note that this function does not call any xcb_*
@@ -95,7 +162,7 @@ void render_con(Con *con, bool render_fullscreen) {
     }
 
     /* Check for fullscreen nodes */
-    Con *fullscreen = con_get_fullscreen_con(con);
+    Con *fullscreen = (con->type == CT_OUTPUT ? NULL : con_get_fullscreen_con(con));
     if (fullscreen) {
         DLOG("got fs node: %p\n", fullscreen);
         fullscreen->rect = rect;
@@ -130,6 +197,11 @@ void render_con(Con *con, bool render_fullscreen) {
         }
     }
 
+    if (con->layout == L_OUTPUT) {
+        render_l_output(con);
+    } else {
+
+        /* FIXME: refactor this into separate functions: */
     Con *child;
     TAILQ_FOREACH(child, &(con->nodes_head), nodes) {
 
@@ -202,6 +274,21 @@ void render_con(Con *con, bool render_fullscreen) {
             }
         }
 
+        /* dockarea layout */
+        else if (con->layout == L_DOCKAREA) {
+            DLOG("dockarea con\n");
+            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;
+        }
+
         DLOG("child at (%d, %d) with (%d x %d)\n",
                 child->rect.x, child->rect.y, child->rect.width, child->rect.height);
         DLOG("x now %d, y now %d\n", x, y);
@@ -221,7 +308,9 @@ void render_con(Con *con, bool render_fullscreen) {
             render_con(foc, false);
         }
     }
+    }
 
+    Con *child;
     TAILQ_FOREACH(child, &(con->floating_head), floating_windows) {
         DLOG("render floating:\n");
         DLOG("floating child at (%d,%d) with %d x %d\n", child->rect.x, child->rect.y, child->rect.width, child->rect.height);
index c741e65ac581f1517eac25b6976d2063bcb5bd17..3900b86258948b1cd2ce16b7422dbc26395f377b 100644 (file)
@@ -55,18 +55,21 @@ void tree_init() {
  * Opens an empty container in the current container
  *
  */
-Con *tree_open_con(Con *con) {
+Con *tree_open_con(Con *con, bool focus_it) {
     if (con == NULL) {
         /* every focusable Con has a parent (outputs have parent root) */
         con = focused->parent;
         /* If the parent is an output, we are on a workspace. In this case,
          * the new container needs to be opened as a leaf of the workspace. */
-        if (con->type == CT_OUTPUT)
+        if (con->parent->type == CT_OUTPUT && con->type != CT_DOCKAREA) {
             con = focused;
+        }
+
         /* If the currently focused container is a floating container, we
          * attach the new container to the workspace */
         if (con->type == CT_FLOATING_CON)
             con = con->parent;
+        DLOG("con = %p\n", con);
     }
 
     assert(con != NULL);
@@ -78,7 +81,8 @@ Con *tree_open_con(Con *con) {
     con_fix_percent(con);
 
     /* 5: focus the new container */
-    con_focus(new);
+    if (focus_it)
+        con_focus(new);
 
     return new;
 }
index 4fcdd7473ec46958a74287bc393867b749d222e8..d5875f99cf61a7bcdb9a48af2e67206cc3b71747 100644 (file)
  *
  */
 Con *workspace_get(const char *num) {
-    Con *output, *workspace = NULL, *current;
+    Con *output, *workspace = NULL, *current, *child;
 
     /* TODO: could that look like this in the future?
     GET_MATCHING_NODE(workspace, croot, strcasecmp(current->name, num) != 0);
     */
     TAILQ_FOREACH(output, &(croot->nodes_head), nodes) {
         TAILQ_FOREACH(current, &(output->nodes_head), nodes) {
-            if (strcasecmp(current->name, num) != 0)
+            if (current->type != CT_CON)
                 continue;
 
-            workspace = current;
-            break;
+            TAILQ_FOREACH(child, &(current->nodes_head), nodes) {
+                if (strcasecmp(child->name, num) != 0)
+                    continue;
+
+                workspace = child;
+                break;
+            }
         }
     }
 
@@ -37,7 +42,15 @@ Con *workspace_get(const char *num) {
     if (workspace == NULL) {
         LOG("need to create this one\n");
         output = con_get_output(focused);
-        LOG("got output %p\n", output);
+        Con *child, *content = NULL;
+        TAILQ_FOREACH(child, &(output->nodes_head), nodes) {
+            if (child->type == CT_CON) {
+                content = child;
+                break;
+            }
+        }
+        assert(content != NULL);
+        LOG("got output %p with child %p\n", output, content);
         /* We need to attach this container after setting its type. con_attach
          * will handle CT_WORKSPACEs differently */
         workspace = con_new(NULL);
@@ -60,7 +73,7 @@ Con *workspace_get(const char *num) {
         else workspace->num = parsed_num;
         LOG("num = %d\n", workspace->num);
         workspace->orientation = HORIZ;
-        con_attach(workspace, output, false);
+        con_attach(workspace, content, false);
 
         ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"init\"}");
     }
@@ -214,8 +227,9 @@ void workspace_show(const char *num) {
     /* Check if the the currently focused con is on the same Output as the
      * workspace we chose as 'old'. If not, use the workspace of the currently
      * focused con */
-    if (con_get_workspace(focused)->parent != old->parent)
-        old = con_get_workspace(focused);
+    Con *ws = con_get_workspace(focused);
+    if (ws && ws->parent != old->parent)
+        old = ws;
 
     /* enable fullscreen for the target workspace. If it happens to be the
      * same one we are currently on anyways, we can stop here. */
@@ -228,7 +242,7 @@ void workspace_show(const char *num) {
     LOG("switching to %p\n", workspace);
     Con *next = con_descend_focused(workspace);
 
-    if (TAILQ_EMPTY(&(old->nodes_head)) && TAILQ_EMPTY(&(old->floating_head))) {
+    if (old && TAILQ_EMPTY(&(old->nodes_head)) && TAILQ_EMPTY(&(old->floating_head))) {
         /* check if this workspace is currently visible */
         if (!workspace_is_visible(old)) {
             LOG("Closing old workspace (%p / %s), it is empty\n", old, old->name);
diff --git a/src/x.c b/src/x.c
index d35c8c6a4d35859ee20e7cae9b3d9ced9269c4f4..5a13b51a325ce3c79d832de1a5b81580df383fe3 100644 (file)
--- a/src/x.c
+++ b/src/x.c
@@ -342,7 +342,7 @@ void x_draw_decoration(Con *con) {
             DLOG("il_parent = %p, layout = %d\n", il_parent, il_parent->layout);
             if (il_parent->layout == L_STACKED)
                 indent_level++;
-            if (il_parent->type == CT_WORKSPACE)
+            if (il_parent->type == CT_WORKSPACE || il_parent->type == CT_DOCKAREA || il_parent->type == CT_OUTPUT)
                 break;
             il_parent = il_parent->parent;
             indent_mult++;
index e13bff63457c7c2ac44f243b85f27aff03b3a304..27ae8411fa20a0f0be25ffbbf57f95e12f4a6b73 100644 (file)
@@ -18,7 +18,15 @@ sub fullscreen_windows {
 # get the output of this workspace
 my $tree = $i3->get_tree->recv;
 my @outputs = @{$tree->{nodes}};
-my $output = first { defined(first { $_->{name} eq $tmp } @{$_->{nodes}}) } @outputs;
+my $output;
+for my $o (@outputs) {
+    # get the first CT_CON of each output
+    my $content = first { $_->{type} == 2 } @{$o->{nodes}};
+    if (defined(first { $_->{name} eq $tmp } @{$content->{nodes}})) {
+        $output = $o;
+        last;
+    }
+}
 
 BEGIN {
     use_ok('X11::XCB::Window');
index 365b0054cdb190a60e217ac009431db6054a9f32..f40ec68e5822a0b5c93fa95dea3cd014f5d7402a 100644 (file)
@@ -3,6 +3,7 @@
 
 use i3test tests => 7;
 use List::MoreUtils qw(all none);
+use List::Util qw(first);
 
 my $i3 = i3("/tmp/nestedcons");
 
@@ -41,8 +42,9 @@ ok((all { $_->{type} == 1 } @nodes), 'all nodes are of type CT_OUTPUT');
 ok((none { defined($_->{window}) } @nodes), 'no CT_OUTPUT contains a window');
 ok((all { @{$_->{nodes}} > 0 } @nodes), 'all nodes have at least one leaf (workspace)');
 my @workspaces;
-for my $ws (map { @{$_->{nodes}} } @nodes) {
-    push @workspaces, $ws;
+for my $ws (@nodes) {
+    my $content = first { $_->{type} == 2 } @{$ws->{nodes}};
+    @workspaces = (@workspaces, @{$content->{nodes}});
 }
 
 ok((all { $_->{type} == 4 } @workspaces), 'all workspaces are of type CT_WORKSPACE');
index e53bb4db7669ef410c29ca1c0ad3a64b342e7177..c1bb7ed33d64a4ddb2df1a42f414e02322d66fe3 100644 (file)
@@ -65,10 +65,15 @@ sub open_empty_con {
 
 sub get_workspace_names {
     my $i3 = i3("/tmp/nestedcons");
-    # TODO: use correct command as soon as AnyEvent::i3 is updated
     my $tree = $i3->get_tree->recv;
-    my @workspaces = map { @{$_->{nodes}} } @{$tree->{nodes}};
-    [ map { $_->{name} } @workspaces ]
+    my @outputs = @{$tree->{nodes}};
+    my @cons;
+    for my $output (@outputs) {
+        # get the first CT_CON of each output
+        my $content = first { $_->{type} == 2 } @{$output->{nodes}};
+        @cons = (@cons, @{$content->{nodes}});
+    }
+    [ map { $_->{name} } @cons ]
 }
 
 sub get_unused_workspace {
@@ -82,11 +87,18 @@ sub get_ws {
     my ($name) = @_;
     my $i3 = i3("/tmp/nestedcons");
     my $tree = $i3->get_tree->recv;
-    my @ws = map { @{$_->{nodes}} } @{$tree->{nodes}};
+
+    my @outputs = @{$tree->{nodes}};
+    my @workspaces;
+    for my $output (@outputs) {
+        # get the first CT_CON of each output
+        my $content = first { $_->{type} == 2 } @{$output->{nodes}};
+        @workspaces = (@workspaces, @{$content->{nodes}});
+    }
 
     # as there can only be one workspace with this name, we can safely
     # return the first entry
-    return first { $_->{name} eq $name } @ws;
+    return first { $_->{name} eq $name } @workspaces;
 }
 
 #
@@ -102,12 +114,7 @@ sub get_ws_content {
 
 sub get_focused {
     my ($ws) = @_;
-    my $i3 = i3("/tmp/nestedcons");
-    my $tree = $i3->get_tree->recv;
-
-    my @ws = map { @{$_->{nodes}} } @{$tree->{nodes}};
-    my @cons = grep { $_->{name} eq $ws } @ws;
-    my $con = $cons[0];
+    my $con = get_ws($ws);
 
     my @focused = @{$con->{focus}};
     my $lf;