From 7f89c716891c8e8c01d9289d6ceb668d742864b1 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 20 Feb 2011 23:43:03 +0100 Subject: [PATCH] Implement dock mode, update testsuite Currently, dock clients are only possible at the top. --- include/con.h | 2 + include/data.h | 29 +++++++++--- include/tree.h | 2 +- src/cmdparse.y | 2 +- src/con.c | 14 ++++-- src/ipc.c | 79 ++++++++++++++++---------------- src/manage.c | 14 +++++- src/randr.c | 36 ++++++++++++++- src/render.c | 91 ++++++++++++++++++++++++++++++++++++- src/tree.c | 10 ++-- src/workspace.c | 32 +++++++++---- src/x.c | 2 +- testcases/t/02-fullscreen.t | 10 +++- testcases/t/16-nestedcons.t | 6 ++- testcases/t/lib/i3test.pm | 29 +++++++----- 15 files changed, 277 insertions(+), 81 deletions(-) diff --git a/include/con.h b/include/con.h index 0d4df204..e8944b62 100644 --- a/include/con.h +++ b/include/con.h @@ -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); diff --git a/include/data.h b/include/data.h index 5c4a8852..e8a796cc 100644 --- a/include/data.h +++ b/include/data.h @@ -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 diff --git a/include/tree.h b/include/tree.h index 40d9a541..cdcb4878 100644 --- a/include/tree.h +++ b/include/tree.h @@ -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 diff --git a/src/cmdparse.y b/src/cmdparse.y index d71773fe..33724264 100644 --- a/src/cmdparse.y +++ b/src/cmdparse.y @@ -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); } ; diff --git a/src/con.c b/src/con.c index 8992d132..b1e652a2 100644 --- 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; } diff --git a/src/ipc.c b/src/ipc.c index a0dd64cc..fe1b24a8 100644 --- 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); + } } } diff --git a/src/manage.c b/src/manage.c index a3bad45f..0c0dcae7 100644 --- a/src/manage.c +++ b/src/manage.c @@ -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; diff --git a/src/randr.c b/src/randr.c index e8b044bb..9abe9c7c 100644 --- a/src/randr.c +++ b/src/randr.c @@ -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); diff --git a/src/render.c b/src/render.c index c6ebb5cc..c8ea3184 100644 --- a/src/render.c +++ b/src/render.c @@ -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); diff --git a/src/tree.c b/src/tree.c index c741e65a..3900b862 100644 --- a/src/tree.c +++ b/src/tree.c @@ -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; } diff --git a/src/workspace.c b/src/workspace.c index 4fcdd747..d5875f99 100644 --- a/src/workspace.c +++ b/src/workspace.c @@ -18,18 +18,23 @@ * */ 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 d35c8c6a..5a13b51a 100644 --- 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++; diff --git a/testcases/t/02-fullscreen.t b/testcases/t/02-fullscreen.t index e13bff63..27ae8411 100644 --- a/testcases/t/02-fullscreen.t +++ b/testcases/t/02-fullscreen.t @@ -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'); diff --git a/testcases/t/16-nestedcons.t b/testcases/t/16-nestedcons.t index 365b0054..f40ec68e 100644 --- a/testcases/t/16-nestedcons.t +++ b/testcases/t/16-nestedcons.t @@ -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'); diff --git a/testcases/t/lib/i3test.pm b/testcases/t/lib/i3test.pm index e53bb4db..c1bb7ed3 100644 --- a/testcases/t/lib/i3test.pm +++ b/testcases/t/lib/i3test.pm @@ -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; -- 2.39.5