Implement dock mode, update testsuite

Currently, dock clients are only possible at the top.
This commit is contained in:
Michael Stapelberg 2011-02-20 23:43:03 +01:00
parent 481ae6ccf2
commit 7f89c71689
15 changed files with 269 additions and 73 deletions

View 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 * borderless and the only element in the tabbed container, the border is not
* rendered. * rendered.
* *
* For children of a CT_DOCKAREA, the border style is always none.
*
*/ */
int con_border_style(Con *con); int con_border_style(Con *con);

View File

@ -263,23 +263,40 @@ struct Match {
enum { M_USER = 0, M_RESTART } source; enum { M_USER = 0, M_RESTART } source;
/* wo das fenster eingefügt werden soll. bei here wird es direkt /* Where the window looking for a match should be inserted:
* diesem Con zugewiesen, also layout saving. bei active ist es *
* ein assignment, welches an der momentan fokussierten stelle einfügt */ * M_HERE = the matched container will be replaced by the window
enum { M_HERE = 0, M_ACTIVE } insert_where; * (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; TAILQ_ENTRY(Match) matches;
}; };
struct Con { struct Con {
bool mapped; 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; orientation_t orientation;
struct Con *parent; struct Con *parent;
struct Rect rect; struct Rect rect;
struct Rect window_rect; struct Rect window_rect;
struct Rect deco_rect; struct Rect deco_rect;
/** the geometry this window requested when getting mapped */
struct Rect geometry;
char *name; char *name;
@ -332,7 +349,7 @@ struct Con {
TAILQ_HEAD(swallow_head, Match) swallow_head; TAILQ_HEAD(swallow_head, Match) swallow_head;
enum { CF_NONE = 0, CF_OUTPUT = 1, CF_GLOBAL = 2 } fullscreen_mode; 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; border_style_t border_style;
/** floating? (= not in tiling layout) This cannot be simply a bool /** 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 * because we want to keep track of whether the status was set by the

View File

@ -24,7 +24,7 @@ void tree_init();
* Opens an empty container in the current container * 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 * Splits (horizontally or vertically) the given container by creating a new

View File

@ -403,7 +403,7 @@ open:
TOK_OPEN TOK_OPEN
{ {
printf("opening new container\n"); 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); asprintf(&json_output, "{\"success\":true, \"id\":%ld}", (long int)con);
} }
; ;

View File

@ -226,7 +226,6 @@ Con *con_get_workspace(Con *con) {
Con *result = con; Con *result = con;
while (result != NULL && result->type != CT_WORKSPACE) while (result != NULL && result->type != CT_WORKSPACE)
result = result->parent; result = result->parent;
assert(result != NULL);
return result; 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 * borderless and the only element in the tabbed container, the border is not
* rendered. * rendered.
* *
* For children of a CT_DOCKAREA, the border style is always none.
*
*/ */
int con_border_style(Con *con) { int con_border_style(Con *con) {
Con *fs = con_get_fullscreen_con(con->parent); 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) if (con->parent->layout == L_TABBED && con->border_style != BS_NORMAL)
return (con_num_children(con->parent) == 1 ? 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; return con->border_style;
} }
@ -773,8 +777,12 @@ void con_set_layout(Con *con, int layout) {
static void con_on_remove_child(Con *con) { static void con_on_remove_child(Con *con) {
DLOG("on_remove_child\n"); DLOG("on_remove_child\n");
/* Nothing to do for workspaces */ /* Every container 'above' (in the hierarchy) the workspace content should
if (con->type == CT_WORKSPACE || con->type == CT_OUTPUT || con->type == CT_ROOT) { * 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); DLOG("not handling, type = %d\n", con->type);
return; return;
} }

View File

@ -288,44 +288,47 @@ IPC_HANDLER(get_workspaces) {
Con *output; Con *output;
TAILQ_FOREACH(output, &(croot->nodes_head), nodes) { TAILQ_FOREACH(output, &(croot->nodes_head), nodes) {
Con *ws; Con *child;
TAILQ_FOREACH(ws, &(output->nodes_head), nodes) { TAILQ_FOREACH(child, &(output->nodes_head), nodes) {
assert(ws->type == CT_WORKSPACE); Con *ws;
y(map_open); TAILQ_FOREACH(ws, &(child->nodes_head), nodes) {
assert(ws->type == CT_WORKSPACE);
y(map_open);
ystr("num"); ystr("num");
if (ws->num == -1) if (ws->num == -1)
y(null); y(null);
else y(integer, ws->num); else y(integer, ws->num);
ystr("name"); ystr("name");
ystr(ws->name); ystr(ws->name);
ystr("visible"); ystr("visible");
y(bool, workspace_is_visible(ws)); y(bool, workspace_is_visible(ws));
ystr("focused"); ystr("focused");
y(bool, ws == focused_ws); y(bool, ws == focused_ws);
ystr("rect"); ystr("rect");
y(map_open); y(map_open);
ystr("x"); ystr("x");
y(integer, ws->rect.x); y(integer, ws->rect.x);
ystr("y"); ystr("y");
y(integer, ws->rect.y); y(integer, ws->rect.y);
ystr("width"); ystr("width");
y(integer, ws->rect.width); y(integer, ws->rect.width);
ystr("height"); ystr("height");
y(integer, ws->rect.height); y(integer, ws->rect.height);
y(map_close); y(map_close);
ystr("output"); ystr("output");
ystr(output->name); ystr(output->name);
ystr("urgent"); ystr("urgent");
y(bool, ws->urgent); y(bool, ws->urgent);
y(map_close); y(map_close);
}
} }
} }

View 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", LOG("using current container, focused = %p, focused->name = %s\n",
focused, focused->name); focused, focused->name);
nc = focused; nc = focused;
} else nc = tree_open_con(NULL); } else nc = tree_open_con(NULL, true);
} else { } else {
/* M_ACTIVE are assignments */
if (match != NULL && match->insert_where == M_ACTIVE) { if (match != NULL && match->insert_where == M_ACTIVE) {
/* We need to go down the focus stack starting from nc */ /* We need to go down the focus stack starting from nc */
while (TAILQ_FIRST(&(nc->focus_head)) != TAILQ_END(&(nc->focus_head))) { 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 */ /* We need to open a new con */
/* TODO: make a difference between match-once containers (directly assign /* TODO: make a difference between match-once containers (directly assign
* cwindow) and match-multiple (tree_open_con first) */ * 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); DLOG("new container = %p\n", nc);
nc->window = cwindow; nc->window = cwindow;
x_reinit(nc); 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)) con_by_window_id(cwindow->leader) != NULL))
want_floating = true; want_floating = true;
nc->geometry = (Rect){ geom->x, geom->y, geom->width, geom->height };
if (want_floating) { if (want_floating) {
nc->rect.x = geom->x; nc->rect.x = geom->x;
nc->rect.y = geom->y; nc->rect.y = geom->y;

View File

@ -250,6 +250,7 @@ void output_init_con(Output *output) {
FREE(con->name); FREE(con->name);
con->name = sstrdup(output->name); con->name = sstrdup(output->name);
con->type = CT_OUTPUT; con->type = CT_OUTPUT;
con->layout = L_OUTPUT;
} }
con->rect = output->rect; con->rect = output->rect;
output->con = con; output->con = con;
@ -257,12 +258,43 @@ void output_init_con(Output *output) {
char *name; char *name;
asprintf(&name, "[i3 con] output %s", con->name); asprintf(&name, "[i3 con] output %s", con->name);
x_set_name(con, name); x_set_name(con, name);
free(name); FREE(name);
if (reused) { if (reused) {
DLOG("Not adding workspace, this was a reused con\n"); DLOG("Not adding workspace, this was a reused con\n");
return; 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"); DLOG("Now adding a workspace\n");
/* add a workspace to this output */ /* 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); DLOG("result for ws %s / %d: exists = %d\n", ws->name, c, exists);
} }
ws->num = c; ws->num = c;
con_attach(ws, con, false); con_attach(ws, content, false);
asprintf(&name, "[i3 con] workspace %s", ws->name); asprintf(&name, "[i3 con] workspace %s", ws->name);
x_set_name(ws, name); x_set_name(ws, name);

View File

@ -8,6 +8,73 @@
* container (for debugging purposes) */ * container (for debugging purposes) */
static bool show_debug_borders = false; 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 * "Renders" the given container (and its children), meaning that all rects are
* updated correctly. Note that this function does not call any xcb_* * 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 */ /* 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) { if (fullscreen) {
DLOG("got fs node: %p\n", fullscreen); DLOG("got fs node: %p\n", fullscreen);
fullscreen->rect = rect; 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; Con *child;
TAILQ_FOREACH(child, &(con->nodes_head), nodes) { 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", DLOG("child at (%d, %d) with (%d x %d)\n",
child->rect.x, child->rect.y, child->rect.width, child->rect.height); child->rect.x, child->rect.y, child->rect.width, child->rect.height);
DLOG("x now %d, y now %d\n", x, y); 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); render_con(foc, false);
} }
} }
}
Con *child;
TAILQ_FOREACH(child, &(con->floating_head), floating_windows) { TAILQ_FOREACH(child, &(con->floating_head), floating_windows) {
DLOG("render floating:\n"); 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); DLOG("floating child at (%d,%d) with %d x %d\n", child->rect.x, child->rect.y, child->rect.width, child->rect.height);

View File

@ -55,18 +55,21 @@ void tree_init() {
* Opens an empty container in the current container * 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) { if (con == NULL) {
/* every focusable Con has a parent (outputs have parent root) */ /* every focusable Con has a parent (outputs have parent root) */
con = focused->parent; con = focused->parent;
/* If the parent is an output, we are on a workspace. In this case, /* 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. */ * 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; con = focused;
}
/* If the currently focused container is a floating container, we /* If the currently focused container is a floating container, we
* attach the new container to the workspace */ * attach the new container to the workspace */
if (con->type == CT_FLOATING_CON) if (con->type == CT_FLOATING_CON)
con = con->parent; con = con->parent;
DLOG("con = %p\n", con);
} }
assert(con != NULL); assert(con != NULL);
@ -78,7 +81,8 @@ Con *tree_open_con(Con *con) {
con_fix_percent(con); con_fix_percent(con);
/* 5: focus the new container */ /* 5: focus the new container */
con_focus(new); if (focus_it)
con_focus(new);
return new; return new;
} }

View File

@ -18,18 +18,23 @@
* *
*/ */
Con *workspace_get(const char *num) { 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? /* TODO: could that look like this in the future?
GET_MATCHING_NODE(workspace, croot, strcasecmp(current->name, num) != 0); GET_MATCHING_NODE(workspace, croot, strcasecmp(current->name, num) != 0);
*/ */
TAILQ_FOREACH(output, &(croot->nodes_head), nodes) { TAILQ_FOREACH(output, &(croot->nodes_head), nodes) {
TAILQ_FOREACH(current, &(output->nodes_head), nodes) { TAILQ_FOREACH(current, &(output->nodes_head), nodes) {
if (strcasecmp(current->name, num) != 0) if (current->type != CT_CON)
continue; continue;
workspace = current; TAILQ_FOREACH(child, &(current->nodes_head), nodes) {
break; if (strcasecmp(child->name, num) != 0)
continue;
workspace = child;
break;
}
} }
} }
@ -37,7 +42,15 @@ Con *workspace_get(const char *num) {
if (workspace == NULL) { if (workspace == NULL) {
LOG("need to create this one\n"); LOG("need to create this one\n");
output = con_get_output(focused); 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 /* We need to attach this container after setting its type. con_attach
* will handle CT_WORKSPACEs differently */ * will handle CT_WORKSPACEs differently */
workspace = con_new(NULL); workspace = con_new(NULL);
@ -60,7 +73,7 @@ Con *workspace_get(const char *num) {
else workspace->num = parsed_num; else workspace->num = parsed_num;
LOG("num = %d\n", workspace->num); LOG("num = %d\n", workspace->num);
workspace->orientation = HORIZ; workspace->orientation = HORIZ;
con_attach(workspace, output, false); con_attach(workspace, content, false);
ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"init\"}"); 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 /* 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 * workspace we chose as 'old'. If not, use the workspace of the currently
* focused con */ * focused con */
if (con_get_workspace(focused)->parent != old->parent) Con *ws = con_get_workspace(focused);
old = con_get_workspace(focused); if (ws && ws->parent != old->parent)
old = ws;
/* enable fullscreen for the target workspace. If it happens to be the /* enable fullscreen for the target workspace. If it happens to be the
* same one we are currently on anyways, we can stop here. */ * 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); LOG("switching to %p\n", workspace);
Con *next = con_descend_focused(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 */ /* check if this workspace is currently visible */
if (!workspace_is_visible(old)) { if (!workspace_is_visible(old)) {
LOG("Closing old workspace (%p / %s), it is empty\n", old, old->name); LOG("Closing old workspace (%p / %s), it is empty\n", old, old->name);

View File

@ -342,7 +342,7 @@ void x_draw_decoration(Con *con) {
DLOG("il_parent = %p, layout = %d\n", il_parent, il_parent->layout); DLOG("il_parent = %p, layout = %d\n", il_parent, il_parent->layout);
if (il_parent->layout == L_STACKED) if (il_parent->layout == L_STACKED)
indent_level++; 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; break;
il_parent = il_parent->parent; il_parent = il_parent->parent;
indent_mult++; indent_mult++;

View File

@ -18,7 +18,15 @@ sub fullscreen_windows {
# get the output of this workspace # get the output of this workspace
my $tree = $i3->get_tree->recv; my $tree = $i3->get_tree->recv;
my @outputs = @{$tree->{nodes}}; 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 { BEGIN {
use_ok('X11::XCB::Window'); use_ok('X11::XCB::Window');

View File

@ -3,6 +3,7 @@
use i3test tests => 7; use i3test tests => 7;
use List::MoreUtils qw(all none); use List::MoreUtils qw(all none);
use List::Util qw(first);
my $i3 = i3("/tmp/nestedcons"); 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((none { defined($_->{window}) } @nodes), 'no CT_OUTPUT contains a window');
ok((all { @{$_->{nodes}} > 0 } @nodes), 'all nodes have at least one leaf (workspace)'); ok((all { @{$_->{nodes}} > 0 } @nodes), 'all nodes have at least one leaf (workspace)');
my @workspaces; my @workspaces;
for my $ws (map { @{$_->{nodes}} } @nodes) { for my $ws (@nodes) {
push @workspaces, $ws; my $content = first { $_->{type} == 2 } @{$ws->{nodes}};
@workspaces = (@workspaces, @{$content->{nodes}});
} }
ok((all { $_->{type} == 4 } @workspaces), 'all workspaces are of type CT_WORKSPACE'); ok((all { $_->{type} == 4 } @workspaces), 'all workspaces are of type CT_WORKSPACE');

View File

@ -65,10 +65,15 @@ sub open_empty_con {
sub get_workspace_names { sub get_workspace_names {
my $i3 = i3("/tmp/nestedcons"); my $i3 = i3("/tmp/nestedcons");
# TODO: use correct command as soon as AnyEvent::i3 is updated
my $tree = $i3->get_tree->recv; my $tree = $i3->get_tree->recv;
my @workspaces = map { @{$_->{nodes}} } @{$tree->{nodes}}; my @outputs = @{$tree->{nodes}};
[ map { $_->{name} } @workspaces ] 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 { sub get_unused_workspace {
@ -82,11 +87,18 @@ sub get_ws {
my ($name) = @_; my ($name) = @_;
my $i3 = i3("/tmp/nestedcons"); my $i3 = i3("/tmp/nestedcons");
my $tree = $i3->get_tree->recv; 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 # as there can only be one workspace with this name, we can safely
# return the first entry # 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 { sub get_focused {
my ($ws) = @_; my ($ws) = @_;
my $i3 = i3("/tmp/nestedcons"); my $con = get_ws($ws);
my $tree = $i3->get_tree->recv;
my @ws = map { @{$_->{nodes}} } @{$tree->{nodes}};
my @cons = grep { $_->{name} eq $ws } @ws;
my $con = $cons[0];
my @focused = @{$con->{focus}}; my @focused = @{$con->{focus}};
my $lf; my $lf;