Currently, dock clients are only possible at the top.
* 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);
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;
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
* 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
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);
}
;
Con *result = con;
while (result != NULL && result->type != CT_WORKSPACE)
result = result->parent;
- assert(result != NULL);
return result;
}
* 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);
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;
}
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;
}
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);
+ }
}
}
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))) {
/* 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);
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;
FREE(con->name);
con->name = sstrdup(output->name);
con->type = CT_OUTPUT;
+ con->layout = L_OUTPUT;
}
con->rect = output->rect;
output->con = con;
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 */
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);
* 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_*
}
/* 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;
}
}
+ 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) {
}
}
+ /* 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);
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);
* 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);
con_fix_percent(con);
/* 5: focus the new container */
- con_focus(new);
+ if (focus_it)
+ con_focus(new);
return new;
}
*
*/
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;
+ }
}
}
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);
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\"}");
}
/* 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. */
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);
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++;
# 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');
use i3test tests => 7;
use List::MoreUtils qw(all none);
+use List::Util qw(first);
my $i3 = i3("/tmp/nestedcons");
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');
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 {
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;
}
#
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;