# Depend on the object files of all source-files in src/*.c and on all header files
AUTOGENERATED:=src/cfgparse.tab.c src/cfgparse.yy.c src/cmdparse.tab.c src/cmdparse.yy.c
-FILES:=src/ipc.c src/main.c src/log.c src/util.c src/tree.c src/xcb.c src/manage.c src/workspace.c src/x.c src/floating.c src/click.c src/config.c src/handlers.c src/randr.c src/xinerama.c src/con.c src/load_layout.c src/render.c src/window.c src/match.c src/xcursor.c src/resize.c src/sighandler.c
+FILES:=src/ipc.c src/main.c src/log.c src/util.c src/tree.c src/xcb.c src/manage.c src/workspace.c src/x.c src/floating.c src/click.c src/config.c src/handlers.c src/randr.c src/xinerama.c src/con.c src/load_layout.c src/render.c src/window.c src/match.c src/xcursor.c src/resize.c src/sighandler.c src/move.c
FILES:=$(FILES:.c=.o)
HEADERS:=$(filter-out include/loglevels.h,$(wildcard include/*.h))
bindsym Mod1+Up prev v
# Move
-bindsym Mod1+Shift+n move before h
-bindsym Mod1+Shift+r move before v
-bindsym Mod1+Shift+t move after v
-bindsym Mod1+Shift+d move after h
+bindsym Mod1+Shift+n move left
+bindsym Mod1+Shift+r move down
+bindsym Mod1+Shift+t move up
+bindsym Mod1+Shift+d move right
# alternatively, you can use the cursor keys:
bindsym Mod1+Shift+Left move before h
#include "xcursor.h"
#include "resize.h"
#include "sighandler.h"
+#include "move.h"
#endif
*/
Con *con_get_workspace(Con *con);
+
+Con *con_parent_with_orientation(Con *con, orientation_t orientation);
+
/**
* Returns the first fullscreen node below this node.
*
--- /dev/null
+/*
+ * vim:ts=4:sw=4:expandtab
+ */
+
+#ifndef _MOVE_H
+#define _MOVE_H
+
+/**
+ * Moves the current container in the given direction (TOK_LEFT, TOK_RIGHT,
+ * TOK_UP, TOK_DOWN from cmdparse.l)
+ *
+ */
+void tree_move(int direction);
+
+#endif
*/
void tree_next(char way, orientation_t orientation);
-/**
- * Moves the current container in the given way (next/previous) and given
- * orientation (horizontal/vertical).
- *
- */
-void tree_move(char way, orientation_t orientation);
-
/**
* Closes the given container including all children
*
*/
void workspace_update_urgent_flag(Con *ws);
+void ws_force_orientation(Con *ws, orientation_t orientation);
+
#endif
down { return TOK_DOWN; }
left { return TOK_LEFT; }
right { return TOK_RIGHT; }
-before { return TOK_BEFORE; }
-after { return TOK_AFTER; }
resize { return TOK_RESIZE; }
shrink { return TOK_SHRINK; }
grow { return TOK_GROW; }
%token TOK_DOWN "down"
%token TOK_LEFT "left"
%token TOK_RIGHT "right"
-%token TOK_AFTER "after"
-%token TOK_BEFORE "before"
%token TOK_RESTORE "restore"
%token TOK_MARK "mark"
%token TOK_RESIZE "resize"
;
move:
- TOK_MOVE WHITESPACE before_after WHITESPACE direction
+ TOK_MOVE WHITESPACE direction
{
- printf("moving: %s and %c\n", ($<number>3 == TOK_BEFORE ? "before" : "after"), $<chr>5);
- /* TODO: change API for the next call, we need to convert in both directions while ideally
- * we should not need any of both */
- tree_move(($<number>3 == TOK_BEFORE ? 'p' : 'n'), ($<chr>5 == 'v' ? VERT : HORIZ));
+ printf("moving in direction %d\n", $<number>3);
+ tree_move($<number>3);
}
| TOK_MOVE WHITESPACE TOK_WORKSPACE WHITESPACE STR
{
}
;
-before_after:
- TOK_BEFORE { $<number>$ = TOK_BEFORE; }
- | TOK_AFTER { $<number>$ = TOK_AFTER; }
- ;
-
restore:
TOK_RESTORE WHITESPACE STR
{
return result;
}
+Con *con_parent_with_orientation(Con *con, orientation_t orientation) {
+ DLOG("Searching for parent of Con %p with orientation %d\n", con, orientation);
+ Con *parent = con->parent;
+ if (parent->type == CT_FLOATING_CON)
+ return NULL;
+ while (con_orientation(parent) != orientation) {
+ DLOG("Need to go one level further up\n");
+ parent = parent->parent;
+ /* Abort when we reach a floating con */
+ if (parent && parent->type == CT_FLOATING_CON)
+ parent = NULL;
+ if (parent == NULL)
+ break;
+ }
+ DLOG("Result: %p\n", parent);
+ return parent;
+}
+
/*
* helper data structure for the breadth-first-search in
* con_get_fullscreen_con()
}
static void con_on_remove_child(Con *con) {
+ DLOG("on_remove_child\n");
+
/* Nothing to do for workspaces */
- if (con->type == CT_WORKSPACE)
+ if (con->type == CT_WORKSPACE || con->type == CT_OUTPUT || con->type == CT_ROOT) {
+ DLOG("not handling, type = %d\n", con->type);
return;
+ }
/* TODO: check if this container would swallow any other client and
* don’t close it automatically. */
- DLOG("on_remove_child\n");
- if (con_num_children(con) == 0) {
+ int children = con_num_children(con);
+ if (children == 0) {
DLOG("Container empty, closing\n");
tree_close(con, false, false);
+ return;
+ }
+
+ /* If we did not close the container, check if we have only a single child left */
+ if (children == 1) {
+ Con *child = TAILQ_FIRST(&(con->nodes_head));
+ Con *parent = con->parent;
+ DLOG("Container has only one child, replacing con %p with child %p\n", con, child);
+
+ /* TODO: refactor it into con_swap */
+ TAILQ_REPLACE(&(parent->nodes_head), con, child, nodes);
+ TAILQ_REPLACE(&(parent->focus_head), con, child, focused);
+ if (focused == con)
+ focused = child;
+ child->parent = parent;
+ child->percent = 0.0;
+ con_fix_percent(parent);
+
+ con->parent = NULL;
+ x_con_kill(con);
+ free(con->name);
+ TAILQ_REMOVE(&all_cons, con, all_cons);
+ free(con);
+
+ return;
}
}
--- /dev/null
+/*
+ * vim:ts=4:sw=4:expandtab
+ */
+
+#include "all.h"
+#include "cmdparse.tab.h"
+
+typedef enum { BEFORE, AFTER } position_t;
+
+/*
+ * This function detaches 'con' from its parent and inserts it either before or
+ * after 'target'.
+ *
+ */
+static void insert_con_into(Con *con, Con *target, position_t position) {
+ Con *parent = target->parent;
+ /* We need to preserve the old con->parent. While it might still be used to
+ * insert the entry before/after it, we call the on_remove_child callback
+ * afterwards which might then close the con if it is empty. */
+ Con *old_parent = con->parent;
+
+ con_detach(con);
+ con_fix_percent(con->parent);
+
+ con->parent = parent;
+
+ if (position == BEFORE) {
+ TAILQ_INSERT_BEFORE(target, con, nodes);
+ TAILQ_INSERT_HEAD(&(parent->focus_head), con, focused);
+ } else if (position == AFTER) {
+ TAILQ_INSERT_AFTER(&(parent->nodes_head), target, con, nodes);
+ TAILQ_INSERT_HEAD(&(parent->focus_head), con, focused);
+ }
+
+ /* Pretend the con was just opened with regards to size percent values.
+ * Since the con is moved to a completely different con, the old value
+ * does not make sense anyways. */
+ con->percent = 0.0;
+ con_fix_percent(parent);
+
+ CALL(old_parent, on_remove_child);
+}
+
+/*
+ * This function detaches 'con' from its parent and inserts it at the given
+ * workspace.
+ *
+ */
+static void attach_to_workspace(Con *con, Con *ws) {
+ con_detach(con);
+ con_fix_percent(con->parent);
+
+ CALL(con->parent, on_remove_child);
+
+ con->parent = ws;
+
+ TAILQ_INSERT_TAIL(&(ws->nodes_head), con, nodes);
+ TAILQ_INSERT_TAIL(&(ws->focus_head), con, focused);
+
+ /* Pretend the con was just opened with regards to size percent values.
+ * Since the con is moved to a completely different con, the old value
+ * does not make sense anyways. */
+ con->percent = 0.0;
+ con_fix_percent(ws);
+}
+
+/*
+ * Moves the current container in the given direction (TOK_LEFT, TOK_RIGHT,
+ * TOK_UP, TOK_DOWN from cmdparse.l)
+ *
+ */
+void tree_move(int direction) {
+ DLOG("Moving in direction %d\n", direction);
+ /* 1: get the first parent with the same orientation */
+ Con *con = focused;
+
+ if (con->type == CT_WORKSPACE) {
+ DLOG("Not moving workspace\n");
+ return;
+ }
+
+ if (con->parent->type == CT_WORKSPACE && con_num_children(con->parent) == 1) {
+ DLOG("This is the only con on this workspace, not doing anything\n");
+ return;
+ }
+
+ orientation_t o = (direction == TOK_LEFT || direction == TOK_RIGHT ? HORIZ : VERT);
+
+ Con *same_orientation = con_parent_with_orientation(con, o);
+ /* There is no parent container with the same orientation */
+ if (!same_orientation) {
+ if (con_is_floating(con)) {
+ /* this is a floating con, we just disable floating */
+ floating_disable(con, true);
+ return;
+ }
+ if (con_inside_floating(con)) {
+ /* 'con' should be moved out of a floating container */
+ DLOG("Inside floating, moving to workspace\n");
+ attach_to_workspace(con, con_get_workspace(con));
+ goto end;
+ }
+ DLOG("Force-changing orientation\n");
+ ws_force_orientation(con_get_workspace(con), o);
+ same_orientation = con_parent_with_orientation(con, o);
+ }
+
+ /* easy case: the move is within this container */
+ if (same_orientation == con->parent) {
+ DLOG("We are in the same container\n");
+ Con *swap;
+ /* TODO: TAILQ_SWAP? */
+ if (direction == TOK_LEFT || direction == TOK_UP) {
+ if (!(swap = TAILQ_PREV(con, nodes_head, nodes)))
+ return;
+
+ if (!con_is_leaf(swap)) {
+ insert_con_into(con, con_descend_focused(swap), AFTER);
+ goto end;
+ }
+
+ /* the container right of the current one is a normal one. */
+ con_detach(con);
+ TAILQ_INSERT_BEFORE(swap, con, nodes);
+ TAILQ_INSERT_HEAD(&(swap->parent->focus_head), con, focused);
+ } else {
+ if (!(swap = TAILQ_NEXT(con, nodes)))
+ return;
+
+ if (!con_is_leaf(swap)) {
+ insert_con_into(con, con_descend_focused(swap), AFTER);
+ goto end;
+ }
+
+ con_detach(con);
+ TAILQ_INSERT_AFTER(&(swap->parent->nodes_head), swap, con, nodes);
+ TAILQ_INSERT_HEAD(&(swap->parent->focus_head), con, focused);
+ }
+ DLOG("Swapped.\n");
+ return;
+ }
+
+ /* this time, we have to move to another container */
+ /* This is the container *above* 'con' which is inside 'same_orientation' */
+ Con *above = con;
+ while (above->parent != same_orientation)
+ above = above->parent;
+
+ DLOG("above = %p\n", above);
+ Con *next;
+ position_t position;
+ if (direction == TOK_UP || direction == TOK_LEFT) {
+ position = BEFORE;
+ next = TAILQ_PREV(above, nodes_head, nodes);
+ } else if (direction == TOK_DOWN || direction == TOK_RIGHT) {
+ position = AFTER;
+ next = TAILQ_NEXT(above, nodes);
+ }
+
+ /* special case: there is a split container in the direction we are moving
+ * to, so descend and append */
+ if (next && !con_is_leaf(next))
+ insert_con_into(con, con_descend_focused(next), AFTER);
+ else
+ insert_con_into(con, above, position);
+
+end:
+ /* We need to call con_focus() to fix the focus stack "above" the container
+ * we just inserted the focused container into (otherwise, the parent
+ * container(s) would still point to the old container(s)). */
+ con_focus(con);
+
+ tree_flatten(croot);
+}
con_focus(con_descend_focused(next));
}
-/*
- * Moves the current container in the given way (next/previous) and given
- * orientation (horizontal/vertical).
- *
- */
-void tree_move(char way, orientation_t orientation) {
- /* 1: get the first parent with the same orientation */
- Con *con = focused;
- Con *parent = con->parent;
- Con *old_parent = parent;
- if (con->type == CT_WORKSPACE)
- return;
- DLOG("con = %p / %s\n", con, con->name);
- bool level_changed = false;
- while (con_orientation(parent) != orientation) {
- DLOG("need to go one level further up\n");
- /* If the current parent is an output, we are at a workspace
- * and the orientation still does not match. In this case, we split the
- * workspace to have the same look & feel as in older i3 releases. */
- if (parent->type == CT_WORKSPACE) {
- DLOG("Arrived at workspace (%p / %s)\n", parent, parent->name);
- /* In case of moving a window out of a floating con, there might be
- * not a single tiling container. Makes no sense to split then, so
- * just use the workspace as target */
- if (TAILQ_EMPTY(&(parent->nodes_head)))
- break;
-
- /* Check if there are any other cons at all. If not, there is no
- * point in creating a new split con and changing workspace
- * orientation. Instead, the operation is a no-op. */
- Con *child;
- bool other_container = false;
- TAILQ_FOREACH(child, &(parent->nodes_head), nodes)
- if (child != con)
- other_container = true;
-
- if (!other_container) {
- DLOG("No other container found, we are not creating this split container.\n");
- return;
- }
-
- /* 1: create a new split container */
- Con *new = con_new(NULL);
- new->parent = parent;
-
- /* 2: copy layout and orientation from workspace */
- new->layout = parent->layout;
- new->orientation = parent->orientation;
-
- Con *old_focused = TAILQ_FIRST(&(parent->focus_head));
- if (old_focused == TAILQ_END(&(parent->focus_head)))
- old_focused = NULL;
-
- /* 3: move the existing cons of this workspace below the new con */
- DLOG("Moving cons\n");
- while (!TAILQ_EMPTY(&(parent->nodes_head))) {
- child = TAILQ_FIRST(&(parent->nodes_head));
- con_detach(child);
- con_attach(child, new, true);
- }
-
- /* 4: switch workspace orientation */
- parent->orientation = orientation;
-
- /* 5: attach the new split container to the workspace */
- DLOG("Attaching new split to ws\n");
- con_attach(new, parent, false);
-
- /* 6: fix the percentages */
- con_fix_percent(parent);
-
- if (old_focused)
- con_focus(old_focused);
-
- level_changed = true;
-
- break;
- }
- parent = parent->parent;
- level_changed = true;
- }
- /* If we have no tiling cons (when moving a window out of a floating con to
- * an otherwise empty workspace for example), we just attach the window to
- * the workspace. */
- bool fix_percent = false;
- if (TAILQ_EMPTY(&(parent->nodes_head))) {
- con_detach(con);
- con_fix_percent(con->parent);
- con->parent = parent;
- fix_percent = true;
-
- TAILQ_INSERT_HEAD(&(parent->nodes_head), con, nodes);
- TAILQ_INSERT_HEAD(&(parent->focus_head), con, focused);
- } else {
- Con *current = NULL, *loop;
- /* Get the first tiling container in focus stack */
- TAILQ_FOREACH(loop, &(parent->focus_head), focused) {
- if (loop->type == CT_FLOATING_CON)
- continue;
- current = loop;
- break;
- }
- assert(current != TAILQ_END(&(parent->focus_head)));
-
- /* 2: chose next (or previous) */
- Con *next = current;
- if (way == 'n') {
- LOG("i would insert it after %p / %s\n", next, next->name);
-
- /* Have a look at the next container: If there is no next container or
- * if it is a leaf node, we move the con one left to it. However,
- * for split containers, we descend into it. */
- next = TAILQ_NEXT(next, nodes);
- if (next == TAILQ_END(&(next->parent->nodes_head))) {
- if (con == current)
- return;
- next = current;
- } else {
- if (level_changed && con_is_leaf(next)) {
- next = current;
- } else {
- /* if this is a split container, we need to go down */
- next = con_descend_focused(next);
- }
- }
-
- con_detach(con);
- if (con->parent != next->parent) {
- con_fix_percent(con->parent);
- con->parent = next->parent;
- fix_percent = true;
- }
-
- CALL(con->parent, on_remove_child);
-
- TAILQ_INSERT_AFTER(&(next->parent->nodes_head), next, con, nodes);
- TAILQ_INSERT_HEAD(&(next->parent->focus_head), con, focused);
- /* TODO: don’t influence focus handling? */
- } else {
- LOG("i would insert it before %p / %s\n", current, current->name);
- bool gone_down = false;
- next = TAILQ_PREV(next, nodes_head, nodes);
- if (next == TAILQ_END(&(next->parent->nodes_head))) {
- if (con == current) {
- DLOG("Cannot move, no other container in that direction\n");
- return;
- }
- next = current;
- } else {
- if (level_changed && con_is_leaf(next)) {
- next = current;
- } else {
- /* if this is a split container, we need to go down */
- while (!TAILQ_EMPTY(&(next->focus_head))) {
- gone_down = true;
- next = TAILQ_FIRST(&(next->focus_head));
- }
- }
- }
-
- DLOG("detaching con = %p / %s, next = %p / %s\n",
- con, con->name, next, next->name);
- con_detach(con);
- if (con->parent != next->parent) {
- DLOG("different parents. new parent = %p / %s\n", next->parent, next->parent->name);
- con_fix_percent(con->parent);
- con->parent = next->parent;
- fix_percent = true;
- }
-
- /* After going down in the tree, we insert the container *after*
- * the currently focused one even though the command used "before".
- * This is to keep the user experience clear, since the before/after
- * only signifies the direction of the movement on top-level */
- if (gone_down)
- TAILQ_INSERT_AFTER(&(next->parent->nodes_head), next, con, nodes);
- else TAILQ_INSERT_BEFORE(next, con, nodes);
- TAILQ_INSERT_HEAD(&(next->parent->focus_head), con, focused);
- /* TODO: don’t influence focus handling? */
- }
- }
-
- /* fix the percentages in the container we moved to */
- if (fix_percent) {
- int children = con_num_children(con->parent);
- if (children == 1) {
- con->percent = 1.0;
- } else {
- con->percent = 1.0 / (children - 1);
- con_fix_percent(con->parent);
- }
- }
-
- /* We need to call con_focus() to fix the focus stack "above" the container
- * we just inserted the focused container into (otherwise, the parent
- * container(s) would still point to the old container(s)). */
- con_focus(con);
-
- /* fix the percentages in the container we moved from */
- if (level_changed)
- con_fix_percent(old_parent);
-
- CALL(old_parent, on_remove_child);
-
- tree_flatten(croot);
-}
-
/*
* tree_flatten() removes pairs of redundant split containers, e.g.:
* [workspace, horizontal]
if (old_flag != ws->urgent)
ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"urgent\"}");
}
+
+void ws_force_orientation(Con *ws, orientation_t orientation) {
+ /* 1: create a new split container */
+ Con *split = con_new(NULL);
+ split->parent = ws;
+
+ /* 2: copy layout and orientation from workspace */
+ split->layout = ws->layout;
+ split->orientation = ws->orientation;
+
+ Con *old_focused = TAILQ_FIRST(&(ws->focus_head));
+
+ /* 3: move the existing cons of this workspace below the new con */
+ DLOG("Moving cons\n");
+ while (!TAILQ_EMPTY(&(ws->nodes_head))) {
+ Con *child = TAILQ_FIRST(&(ws->nodes_head));
+ con_detach(child);
+ con_attach(child, split, true);
+ }
+
+ /* 4: switch workspace orientation */
+ ws->orientation = orientation;
+
+ /* 5: attach the new split container to the workspace */
+ DLOG("Attaching new split to ws\n");
+ con_attach(split, ws, false);
+
+ /* 6: fix the percentages */
+ con_fix_percent(ws);
+
+ if (old_focused)
+ con_focus(old_focused);
+}
my $i3 = i3("/tmp/nestedcons");
my $tmp = get_unused_workspace();
-$i3->command("workspace $tmp")->recv;
+cmd "workspace $tmp";
######################################################################
# 1) move a container which cannot be moved
######################################################################
-$i3->command('open')->recv;
+cmd 'open';
my $old_content = get_ws_content($tmp);
is(@{$old_content}, 1, 'one container on this workspace');
is($content->[0]->{id}, $first, 'first container unmodified');
# Move the second container before the first one (→ swap them)
-$i3->command('move before h')->recv;
+$i3->command('move left')->recv;
$content = get_ws_content($tmp);
is($content->[0]->{id}, $second, 'first container modified');
# We should not be able to move any further
-$i3->command('move before h')->recv;
+$i3->command('move left')->recv;
$content = get_ws_content($tmp);
is($content->[0]->{id}, $second, 'first container unmodified');
# Now move in the other direction
-$i3->command('move after h')->recv;
+$i3->command('move right')->recv;
$content = get_ws_content($tmp);
is($content->[0]->{id}, $first, 'first container modified');
# We should not be able to move any further
-$i3->command('move after h')->recv;
+$i3->command('move right')->recv;
$content = get_ws_content($tmp);
is($content->[0]->{id}, $first, 'first container unmodified');
is(@{$content}, 3, 'three containers on this workspace');
my $third = $content->[2]->{id};
-$i3->command('move before h')->recv;
+$i3->command('move left')->recv;
$content = get_ws_content($tmp);
is(@{$content}, 2, 'only two containers on this workspace');
my $nodes = $content->[1]->{nodes};
# move it inside the split container
######################################################################
-$i3->command('move before v')->recv;
+$i3->command('move up')->recv;
$nodes = get_ws_content($tmp)->[1]->{nodes};
is($nodes->[0]->{id}, $third, 'third container on top');
is($nodes->[1]->{id}, $second, 'second container on bottom');
# move it outside again
-$i3->command('move before h')->recv;
+$i3->command('move left')->recv;
$content = get_ws_content($tmp);
is(@{$content}, 3, 'three nodes on this workspace');
-$i3->command('move after h')->recv;
+# due to automatic flattening/cleanup, the remaining split container
+# will be replaced by the con itself, so we will still have 3 nodes
+$i3->command('move right')->recv;
$content = get_ws_content($tmp);
-is(@{$content}, 2, 'two nodes on this workspace');
+is(@{$content}, 3, 'two nodes on this workspace');
######################################################################
# 4) We create two v-split containers on the workspace, then we move
######################################################################
my $otmp = get_unused_workspace();
-$i3->command("workspace $otmp")->recv;
+cmd "workspace $otmp";
$i3->command("open")->recv;
$i3->command("open")->recv;
$i3->command("prev h")->recv;
$i3->command("split v")->recv;
$i3->command("open")->recv;
-$i3->command("move after h")->recv;
+$i3->command("move right")->recv;
$i3->command("prev h")->recv;
-$i3->command("move after h")->recv;
+$i3->command("move right")->recv;
$content = get_ws_content($otmp);
is(@{$content}, 1, 'only one nodes on this workspace');