Implement dock mode, update testsuite

Currently, dock clients are only possible at the top.
next
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
* rendered.
*
* For children of a CT_DOCKAREA, the border style is always none.
*
*/
int con_border_style(Con *con);

View 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

View 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

View 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);
}
;

View File

@ -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;
}

View File

@ -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);
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("num");
if (ws->num == -1)
y(null);
else y(integer, ws->num);
ystr("name");
ystr(ws->name);
ystr("name");
ystr(ws->name);
ystr("visible");
y(bool, workspace_is_visible(ws));
ystr("visible");
y(bool, workspace_is_visible(ws));
ystr("focused");
y(bool, ws == focused_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("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("output");
ystr(output->name);
ystr("urgent");
y(bool, ws->urgent);
ystr("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",
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;

View 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);

View 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);

View 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;
}

View File

@ -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);

View File

@ -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++;

View 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');

View 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');

View 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;