IPC interface (interprocess communication)
==========================================
Michael Stapelberg <michael@i3wm.org>
-July 2012
+August 2012
This document describes how to interface with i3 from a separate process. This
is useful for example to remote-control i3 (to write test cases for example) or
Can be either "normal", "none" or "1pixel", dependending on the
container’s border style.
layout (string)::
- Can be either "default", "stacked", "tabbed", "dockarea" or "output".
+ Can be either "splith", "splitv", "stacked", "tabbed", "dockarea" or
+ "output".
Other values might be possible in the future, should we add new
layouts.
orientation (string)::
Can be either "none" (for non-split containers), "horizontal" or
"vertical".
+ THIS FIELD IS OBSOLETE. It is still present, but your code should not
+ use it. Instead, rely on the layout field.
percent (float)::
The percentage which this container takes in its parent. A value of
+null+ means that the percent property does not make sense for this
i3 User’s Guide
===============
-Michael Stapelberg <michael+i3@stapelberg.de>
-April 2012
+Michael Stapelberg <michael@i3wm.org>
+August 2012
This document contains all the information you need to configure and use the i3
window manager. If it does not, please contact us on IRC (preferred) or post your
A split container can have one of the following layouts:
-default::
+splith/splitv::
Windows are sized so that every window gets an equal amount of space in the
-container.
+container. splith distributes the windows horizontally (windows are right next
+to each other), splitv distributes them vertically (windows are on top of each
+other).
stacking::
Only the focused window in the container is displayed. You get a list of
windows at the top of the container.
The same principle as +stacking+, but the list of windows at the top is only
a single line which is vertically split.
-To switch modes, press +mod+e+ for default, +mod+s+ for stacking and
-+mod+w+ for tabbed.
+To switch modes, press +mod+e+ for splith/splitv (it toggles), +mod+s+ for
+stacking and +mod+w+ for tabbed.
image:modes.png[Container modes]
It is only natural to use so-called +Split Containers+ in order to build a
layout when using a tree as data structure. In i3, every +Container+ has an
-orientation (horizontal, vertical or unspecified). So, in our example with the
-workspace, the default orientation of the workspace +Container+ is horizontal
-(most monitors are widescreen nowadays). If you change the orientation to
-vertical (+mod+v+ in the default config) and *then* open two terminals, i3 will
-configure your windows like this:
+orientation (horizontal, vertical or unspecified) and the orientation depends
+on the layout the container is in (vertical for splitv and stacking, horizontal
+for splith and tabbed). So, in our example with the workspace, the default
+layout of the workspace +Container+ is splith (most monitors are widescreen
+nowadays). If you change the layout to splitv (+mod+l+ in the default config)
+and *then* open two terminals, i3 will configure your windows like this:
image::tree-shot2.png["shot2",title="Vertical Workspace Orientation"]
-An interesting new feature of the tree branch is the ability to split anything:
-Let’s assume you have two terminals on a workspace (with horizontal
-orientation), focus is on the right terminal. Now you want to open another
-terminal window below the current one. If you would just open a new terminal
-window, it would show up to the right due to the horizontal workspace
-orientation. Instead, press +mod+v+ to create a +Vertical Split Container+ (to
+An interesting new feature of i3 since version 4 is the ability to split anything:
+Let’s assume you have two terminals on a workspace (with splith layout, that is
+horizontal orientation), focus is on the right terminal. Now you want to open
+another terminal window below the current one. If you would just open a new
+terminal window, it would show up to the right due to the splith layout.
+Instead, press +mod+v+ to split the container with the splitv layout (to
open a +Horizontal Split Container+, use +mod+h+). Now you can open a new
terminal and it will open below the current one:
=== Splitting containers
The split command makes the current window a split container. Split containers
-can contain multiple windows. Every split container has an orientation, it is
-either split horizontally (a new window gets placed to the right of the current
-one) or vertically (a new window gets placed below the current one).
+can contain multiple windows. Depending on the layout of the split container,
+new windows get placed to the right of the current one (splith) or new windows
+get placed below the current one (splitv).
If you apply this command to a split container with the same orientation,
nothing will happen. If you use a different orientation, the split container’s
-orientation will be changed (if it does not have more than one window).
+orientation will be changed (if it does not have more than one window). Use
++layout toggle split+ to change the layout of any split container from splitv
+to splith or vice-versa.
*Syntax*:
---------------------------
=== Manipulating layout
-Use +layout default+, +layout stacking+ or +layout tabbed+ to change the
-current container layout to default, stacking or tabbed layout, respectively.
+Use +layout toggle split+, +layout stacking+ or +layout tabbed+ to change the
+current container layout to splith/splitv, stacking or tabbed layout,
+respectively.
To make the current window (!) fullscreen, use +fullscreen+, to make
it floating (or tiling again) use +floating enable+ respectively +floating disable+
(or +floating toggle+):
+*Syntax*:
+--------------
+layout <tabbed|stacking>
+layout toggle [split|all]
+--------------
+
*Examples*:
--------------
bindsym mod+s layout stacking
-bindsym mod+l layout default
+bindsym mod+l layout toggle split
bindsym mod+w layout tabbed
+# Toggle between stacking/tabbed/split:
+bindsym mod+x layout toggle
+
+# Toggle between stacking/tabbed/splith/splitv:
+bindsym mod+x layout toggle all
+
# Toggle fullscreen
bindsym mod+f fullscreen
# enter fullscreen mode for the focused container
bindsym Mod1+f fullscreen
-# change container layout (stacked, tabbed, default)
+# change container layout (stacked, tabbed, toggle split)
bindsym Mod1+s layout stacking
bindsym Mod1+w layout tabbed
-bindsym Mod1+e layout default
+bindsym Mod1+e layout toggle split
# toggle tiling / floating
bindsym Mod1+Shift+space floating toggle
# enter fullscreen mode for the focused container
bindcode $mod+41 fullscreen
-# change container layout (stacked, tabbed, default)
+# change container layout (stacked, tabbed, toggle split)
bindcode $mod+39 layout stacking
bindcode $mod+25 layout tabbed
-bindcode $mod+26 layout default
+bindcode $mod+26 layout toggle split
# toggle tiling / floating
bindcode $mod+Shift+65 floating toggle
void cmd_move_direction(I3_CMD, char *direction, char *move_px);
/**
- * Implementation of 'layout default|stacked|stacking|tabbed'.
+ * Implementation of 'layout default|stacked|stacking|tabbed|splitv|splith'.
*
*/
void cmd_layout(I3_CMD, char *layout_str);
+/**
+ * Implementation of 'layout toggle [all|split]'.
+ *
+ */
+void cmd_layout_toggle(I3_CMD, char *toggle_mode);
+
/**
* Implementaiton of 'exit'.
*
*/
void con_set_layout(Con *con, int layout);
+/**
+ * This function toggles the layout of a given container. toggle_mode can be
+ * either 'default' (toggle only between stacked/tabbed/last_split_layout),
+ * 'split' (toggle only between splitv/splith) or 'all' (toggle between all
+ * layouts).
+ *
+ */
+void con_toggle_layout(Con *con, const char *toggle_mode);
+
/**
* Determines the minimum size of the given con by looking at its children (for
* split/stacked/tabbed cons). Will be called when resizing floating cons
*/
struct Con {
bool mapped;
+ /** whether this is a split container or not */
+ bool split;
enum {
CT_ROOT = 0,
CT_OUTPUT = 1,
CT_WORKSPACE = 4,
CT_DOCKAREA = 5
} type;
- orientation_t orientation;
struct Con *parent;
struct Rect rect;
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, L_DOCKAREA = 3, L_OUTPUT = 4 } layout;
+ enum {
+ L_DEFAULT = 0,
+ L_STACKED = 1,
+ L_TABBED = 2,
+ L_DOCKAREA = 3,
+ L_OUTPUT = 4,
+ L_SPLITV = 5,
+ L_SPLITH = 6
+ } layout, last_split_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
border_style = 'normal', 'none', '1pixel', 'toggle'
-> call cmd_border($border_style)
-# layout default|stacked|stacking|tabbed
+# layout default|stacked|stacking|tabbed|splitv|splith
+# layout toggle [split|all]
state LAYOUT:
- layout_mode = 'default', 'stacked', 'stacking', 'tabbed'
+ layout_mode = 'default', 'stacked', 'stacking', 'tabbed', 'splitv', 'splith'
-> call cmd_layout($layout_mode)
+ 'toggle'
+ -> LAYOUT_TOGGLE
+
+# layout toggle [split|all]
+state LAYOUT_TOGGLE:
+ end
+ -> call cmd_layout_toggle($toggle_mode)
+ toggle_mode = 'split', 'all'
+ -> call cmd_layout_toggle($toggle_mode)
# append_layout <path>
state APPEND_LAYOUT:
Con *resize_con = con;
while (resize_con->type != CT_WORKSPACE &&
resize_con->type != CT_FLOATING_CON &&
- resize_con->parent->orientation != orientation)
+ con_orientation(resize_con->parent) != orientation)
resize_con = resize_con->parent;
DLOG("resize_con = %p\n", resize_con);
if (resize_con->type != CT_WORKSPACE &&
resize_con->type != CT_FLOATING_CON &&
- resize_con->parent->orientation == orientation) {
+ con_orientation(resize_con->parent) == orientation) {
first = resize_con;
second = (way == 'n') ? TAILQ_NEXT(first, nodes) : TAILQ_PREV(first, nodes_head, nodes);
if (second == TAILQ_END(&(first->nodes_head))) {
if ((check_con->layout == L_STACKED ||
check_con->layout == L_TABBED ||
- check_con->orientation == HORIZ) &&
+ con_orientation(check_con) == HORIZ) &&
con_num_children(check_con) > 1) {
DLOG("Not handling this resize, this container has > 1 child.\n");
return false;
(strcmp(direction, "left") == 0 || strcmp(direction, "right") == 0 ? HORIZ : VERT);
do {
- if (current->parent->orientation != search_orientation) {
+ if (con_orientation(current->parent) != search_orientation) {
current = current->parent;
continue;
}
percentage = 1.0 / children;
LOG("default percentage = %f\n", percentage);
- orientation_t orientation = current->parent->orientation;
+ orientation_t orientation = con_orientation(current->parent);
if ((orientation == HORIZ &&
(strcmp(direction, "up") == 0 || strcmp(direction, "down") == 0)) ||
while (current->type != CT_WORKSPACE &&
current->type != CT_FLOATING_CON &&
- current->parent->orientation != search_orientation)
+ con_orientation(current->parent) != search_orientation)
current = current->parent;
/* get the default percentage */
double percentage = 1.0 / children;
LOG("default percentage = %f\n", percentage);
- orientation_t orientation = current->parent->orientation;
+ orientation_t orientation = con_orientation(current->parent);
if ((orientation == HORIZ &&
strcmp(direction, "height") == 0) ||
}
/*
- * Implementation of 'layout default|stacked|stacking|tabbed'.
+ * Implementation of 'layout default|stacked|stacking|tabbed|splitv|splith'.
*
*/
void cmd_layout(I3_CMD, char *layout_str) {
if (strcmp(layout_str, "stacking") == 0)
layout_str = "stacked";
- DLOG("changing layout to %s\n", layout_str);
owindow *current;
- int layout = (strcmp(layout_str, "default") == 0 ? L_DEFAULT :
- (strcmp(layout_str, "stacked") == 0 ? L_STACKED :
- L_TABBED));
+ int layout;
+ /* default is a special case which will be handled in con_set_layout(). */
+ if (strcmp(layout_str, "default") == 0)
+ layout = L_DEFAULT;
+ else if (strcmp(layout_str, "stacked") == 0)
+ layout = L_STACKED;
+ else if (strcmp(layout_str, "tabbed") == 0)
+ layout = L_TABBED;
+ else if (strcmp(layout_str, "splitv") == 0)
+ layout = L_SPLITV;
+ else if (strcmp(layout_str, "splith") == 0)
+ layout = L_SPLITH;
+
+ DLOG("changing layout to %s (%d)\n", layout_str, layout);
/* check if the match is empty, not if the result is empty */
if (match_is_empty(current_match))
ysuccess(true);
}
+/*
+ * Implementation of 'layout toggle [all|split]'.
+ *
+ */
+void cmd_layout_toggle(I3_CMD, char *toggle_mode) {
+ owindow *current;
+
+ if (toggle_mode == NULL)
+ toggle_mode = "default";
+
+ DLOG("toggling layout (mode = %s)\n", toggle_mode);
+
+ /* check if the match is empty, not if the result is empty */
+ if (match_is_empty(current_match))
+ con_toggle_layout(focused->parent, toggle_mode);
+ else {
+ TAILQ_FOREACH(current, &owindows, owindows) {
+ DLOG("matching: %p / %s\n", current->con, current->con->name);
+ con_toggle_layout(current->con, toggle_mode);
+ }
+ }
+
+ cmd_output->needs_tree_render = true;
+ // XXX: default reply for now, make this a better reply
+ ysuccess(true);
+}
+
/*
* Implementaiton of 'exit'.
*
void cmd_open(I3_CMD) {
LOG("opening new container\n");
Con *con = tree_open_con(NULL, NULL);
+ con->layout = L_SPLITH;
con_focus(con);
y(map_open);
if (con->type == CT_WORKSPACE)
return false;
- if (con->orientation != NO_ORIENTATION) {
- DLOG("container %p does not accepts windows, orientation != NO_ORIENTATION\n", con);
+ if (con->split) {
+ DLOG("container %p does not accept windows, it is a split container.\n", con);
return false;
}
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)
+ /* Abort when we reach a floating con, or an output con */
+ if (parent &&
+ (parent->type == CT_FLOATING_CON ||
+ parent->type == CT_OUTPUT ||
+ (parent->parent && parent->parent->type == CT_OUTPUT)))
parent = NULL;
if (parent == NULL)
break;
*
*/
int con_orientation(Con *con) {
- /* stacking containers behave like they are in vertical orientation */
- if (con->layout == L_STACKED)
- return VERT;
-
- if (con->layout == L_TABBED)
- return HORIZ;
-
- return con->orientation;
+ switch (con->layout) {
+ case L_SPLITV:
+ /* stacking containers behave like they are in vertical orientation */
+ case L_STACKED:
+ return VERT;
+
+ case L_SPLITH:
+ /* tabbed containers behave like they are in vertical orientation */
+ case L_TABBED:
+ return HORIZ;
+
+ case L_DEFAULT:
+ DLOG("Someone called con_orientation() on a con with L_DEFAULT, this is a bug in the code.\n");
+ assert(false);
+ return HORIZ;
+
+ case L_DOCKAREA:
+ case L_OUTPUT:
+ DLOG("con_orientation() called on dockarea/output (%d) container %p\n", con->layout, con);
+ assert(false);
+ return HORIZ;
+
+ default:
+ DLOG("con_orientation() ran into default\n");
+ assert(false);
+ }
}
/*
Con *new = con_new(NULL, NULL);
new->parent = con;
- /* 2: set the requested layout on the split con */
+ /* 2: Set the requested layout on the split container and mark it as
+ * split. */
new->layout = layout;
-
- /* 3: While the layout is irrelevant in stacked/tabbed mode, it needs
- * to be set. Otherwise, this con will not be interpreted as a split
- * container. */
- if (config.default_orientation == NO_ORIENTATION) {
- new->orientation = (con->rect.height > con->rect.width) ? VERT : HORIZ;
- } else {
- new->orientation = config.default_orientation;
- }
+ new->split = true;
Con *old_focused = TAILQ_FIRST(&(con->focus_head));
if (old_focused == TAILQ_END(&(con->focus_head)))
old_focused = NULL;
- /* 4: move the existing cons of this workspace below the new con */
+ /* 3: move the existing cons of this workspace below the new con */
DLOG("Moving cons\n");
Con *child;
while (!TAILQ_EMPTY(&(con->nodes_head))) {
return;
}
- con->layout = layout;
+ if (layout == L_DEFAULT) {
+ /* Special case: the layout formerly known as "default" (in combination
+ * with an orientation). Since we switched to splith/splitv layouts,
+ * using the "default" layout (which "only" should happen when using
+ * legacy configs) is using the last split layout (either splith or
+ * splitv) in order to still do the same thing.
+ *
+ * Starting from v4.6 though, we will nag users about using "layout
+ * default", and in v4.9 we will remove it entirely (with an
+ * appropriate i3-migrate-config mechanism). */
+ con->layout = con->last_split_layout;
+ } else {
+ /* We fill in last_split_layout when switching to a different layout
+ * since there are many places in the code that don’t use
+ * con_set_layout(). */
+ if (con->layout == L_SPLITH || con->layout == L_SPLITV)
+ con->last_split_layout = con->layout;
+ con->layout = layout;
+ }
+}
+
+/*
+ * This function toggles the layout of a given container. toggle_mode can be
+ * either 'default' (toggle only between stacked/tabbed/last_split_layout),
+ * 'split' (toggle only between splitv/splith) or 'all' (toggle between all
+ * layouts).
+ *
+ */
+void con_toggle_layout(Con *con, const char *toggle_mode) {
+ if (strcmp(toggle_mode, "split") == 0) {
+ /* Toggle between splits. When the current layout is not a split
+ * layout, we just switch back to last_split_layout. Otherwise, we
+ * change to the opposite split layout. */
+ if (con->layout != L_SPLITH && con->layout != L_SPLITV)
+ con_set_layout(con, con->last_split_layout);
+ else {
+ if (con->layout == L_SPLITH)
+ con_set_layout(con, L_SPLITV);
+ else con_set_layout(con, L_SPLITH);
+ }
+ } else {
+ if (con->layout == L_STACKED)
+ con_set_layout(con, L_TABBED);
+ else if (con->layout == L_TABBED) {
+ if (strcmp(toggle_mode, "all") == 0)
+ con_set_layout(con, L_SPLITH);
+ else con_set_layout(con, con->last_split_layout);
+ } else if (con->layout == L_SPLITH || con->layout == L_SPLITV) {
+ if (strcmp(toggle_mode, "all") == 0) {
+ /* When toggling through all modes, we toggle between
+ * splith/splitv, whereas normally we just directly jump to
+ * stacked. */
+ if (con->layout == L_SPLITH)
+ con_set_layout(con, L_SPLITV);
+ else con_set_layout(con, L_STACKED);
+ } else {
+ con_set_layout(con, L_STACKED);
+ }
+ }
+ }
}
/*
/* For horizontal/vertical split containers we sum up the width (h-split)
* or height (v-split) and use the maximum of the height (h-split) or width
* (v-split) as minimum size. */
- if (con->orientation == HORIZ || con->orientation == VERT) {
+ if (con->split) {
uint32_t width = 0, height = 0;
Con *child;
TAILQ_FOREACH(child, &(con->nodes_head), nodes) {
Rect min = con_minimum_size(child);
- if (con->orientation == HORIZ) {
+ if (con->layout == L_SPLITH) {
width += min.width;
height = max(height, min.height);
} else {
return (Rect){ 0, 0, width, height };
}
- ELOG("Unhandled case, type = %d, layout = %d, orientation = %d\n",
- con->type, con->layout, con->orientation);
+ ELOG("Unhandled case, type = %d, layout = %d, split = %d\n",
+ con->type, con->layout, con->split);
assert(false);
}
}
/* 1: If the container is a workspace container, we need to create a new
- * split-container with the same orientation and make that one floating. We
+ * split-container with the same layout and make that one floating. We
* cannot touch the workspace container itself because floating containers
* are children of the workspace. */
if (con->type == CT_WORKSPACE) {
/* TODO: refactor this with src/con.c:con_set_layout */
Con *new = con_new(NULL, NULL);
new->parent = con;
- new->orientation = con->orientation;
+ new->layout = con->layout;
/* since the new container will be set into floating mode directly
* afterwards, we need to copy the workspace rect. */
* otherwise. */
Con *ws = con_get_workspace(con);
nc->parent = ws;
- nc->orientation = NO_ORIENTATION;
+ nc->split = true;
nc->type = CT_FLOATING_CON;
+ nc->layout = L_SPLITH;
/* We insert nc already, even though its rect is not yet calculated. This
* is necessary because otherwise the workspace might be empty (and get
* closed in tree_close()) even though it’s not. */
ystr("type");
y(integer, con->type);
+ /* provided for backwards compatibility only. */
ystr("orientation");
- switch (con->orientation) {
- case NO_ORIENTATION:
- ystr("none");
- break;
- case HORIZ:
+ if (!con->split)
+ ystr("none");
+ else {
+ if (con_orientation(con) == HORIZ)
ystr("horizontal");
- break;
- case VERT:
- ystr("vertical");
- break;
+ else ystr("vertical");
}
ystr("scratchpad_state");
ystr("focused");
y(bool, (con == focused));
+ ystr("split");
+ y(bool, con->split);
+
ystr("layout");
switch (con->layout) {
case L_DEFAULT:
- ystr("default");
+ DLOG("About to dump layout=default, this is a bug in the code.\n");
+ assert(false);
+ break;
+ case L_SPLITV:
+ ystr("splitv");
+ break;
+ case L_SPLITH:
+ ystr("splith");
break;
case L_STACKED:
ystr("stacked");
break;
}
+ ystr("last_split_layout");
+ switch (con->layout) {
+ case L_SPLITV:
+ ystr("splitv");
+ break;
+ default:
+ ystr("splith");
+ break;
+ }
+
ystr("border");
switch (con->border_style) {
case BS_NORMAL:
memcpy(json_node->sticky_group, val, len);
LOG("sticky_group of this container is %s\n", json_node->sticky_group);
} else if (strcasecmp(last_key, "orientation") == 0) {
+ /* Upgrade path from older versions of i3 (doing an inplace restart
+ * to a newer version):
+ * "orientation" is dumped before "layout". Therefore, we store
+ * whether the orientation was horizontal or vertical in the
+ * last_split_layout. When we then encounter layout == "default",
+ * we will use the last_split_layout as layout instead. */
char *buf = NULL;
sasprintf(&buf, "%.*s", (int)len, val);
- if (strcasecmp(buf, "none") == 0)
- json_node->orientation = NO_ORIENTATION;
- else if (strcasecmp(buf, "horizontal") == 0)
- json_node->orientation = HORIZ;
+ if (strcasecmp(buf, "none") == 0 ||
+ strcasecmp(buf, "horizontal") == 0)
+ json_node->last_split_layout = L_SPLITH;
else if (strcasecmp(buf, "vertical") == 0)
- json_node->orientation = VERT;
+ json_node->last_split_layout = L_SPLITV;
else LOG("Unhandled orientation: %s\n", buf);
+
+ /* What used to be an implicit check whether orientation !=
+ * NO_ORIENTATION is now a proper separate flag. */
+ if (strcasecmp(buf, "none") != 0)
+ json_node->split = true;
free(buf);
} else if (strcasecmp(last_key, "border") == 0) {
char *buf = NULL;
char *buf = NULL;
sasprintf(&buf, "%.*s", (int)len, val);
if (strcasecmp(buf, "default") == 0)
- json_node->layout = L_DEFAULT;
+ /* This set above when we read "orientation". */
+ json_node->layout = json_node->last_split_layout;
else if (strcasecmp(buf, "stacked") == 0)
json_node->layout = L_STACKED;
else if (strcasecmp(buf, "tabbed") == 0)
json_node->layout = L_TABBED;
- else if (strcasecmp(buf, "dockarea") == 0)
+ else if (strcasecmp(buf, "dockarea") == 0) {
json_node->layout = L_DOCKAREA;
- else if (strcasecmp(buf, "output") == 0)
+ /* Necessary for migrating from older versions of i3. */
+ json_node->split = false;
+ } else if (strcasecmp(buf, "output") == 0)
json_node->layout = L_OUTPUT;
+ else if (strcasecmp(buf, "splith") == 0)
+ json_node->layout = L_SPLITH;
+ else if (strcasecmp(buf, "splitv") == 0)
+ json_node->layout = L_SPLITV;
else LOG("Unhandled \"layout\": %s\n", buf);
free(buf);
+ } else if (strcasecmp(last_key, "last_split_layout") == 0) {
+ char *buf = NULL;
+ sasprintf(&buf, "%.*s", (int)len, val);
+ if (strcasecmp(buf, "splith") == 0)
+ json_node->last_split_layout = L_SPLITH;
+ else if (strcasecmp(buf, "splitv") == 0)
+ json_node->last_split_layout = L_SPLITV;
+ else LOG("Unhandled \"last_splitlayout\": %s\n", buf);
+ free(buf);
} else if (strcasecmp(last_key, "mark") == 0) {
char *buf = NULL;
sasprintf(&buf, "%.*s", (int)len, val);
to_focus = json_node;
}
+ if (strcasecmp(last_key, "split") == 0)
+ json_node->split = val;
+
if (parsing_swallows) {
if (strcasecmp(last_key, "restart_mode") == 0)
current_swallow->restart_mode = val;
Con *topdock = con_new(NULL, 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);
DLOG("adding main content container\n");
Con *content = con_new(NULL, NULL);
content->type = CT_CON;
+ content->layout = L_SPLITH;
FREE(content->name);
content->name = sstrdup("content");
Con *bottomdock = con_new(NULL, NULL);
bottomdock->type = CT_DOCKAREA;
bottomdock->layout = L_DOCKAREA;
- bottomdock->orientation = VERT;
/* this container swallows dock clients */
match = scalloc(sizeof(Match));
match_init(match);
if (con_num_children(workspace) > 1)
continue;
- workspace->orientation = (output->rect.height > output->rect.width) ? VERT : HORIZ;
- DLOG("Setting workspace [%d,%s]'s orientation to %d.\n", workspace->num, workspace->name, workspace->orientation);
+ workspace->layout = (output->rect.height > output->rect.width) ? L_SPLITV : L_SPLITH;
+ DLOG("Setting workspace [%d,%s]'s layout to %d.\n", workspace->num, workspace->name, workspace->layout);
if ((child = TAILQ_FIRST(&(workspace->nodes_head)))) {
- child->orientation = workspace->orientation;
- DLOG("Setting child [%d,%s]'s orientation to %d.\n", child->num, child->name, child->orientation);
+ child->layout = workspace->layout;
+ DLOG("Setting child [%d,%s]'s layout to %d.\n", child->num, child->name, child->layout);
}
}
}
*/
void render_con(Con *con, bool render_fullscreen) {
int children = con_num_children(con);
- DLOG("Rendering %snode %p / %s / layout %d / children %d / orient %d\n",
+ DLOG("Rendering %snode %p / %s / layout %d / children %d\n",
(render_fullscreen ? "fullscreen " : ""), con, con->name, con->layout,
- children, con->orientation);
+ children);
/* Copy container rect, subtract container border */
/* This is the actually usable space inside this container for clients */
/* precalculate the sizes to be able to correct rounding errors */
int sizes[children];
- if (con->layout == L_DEFAULT && children > 0) {
+ if ((con->layout == L_SPLITH || con->layout == L_SPLITV) && children > 0) {
assert(!TAILQ_EMPTY(&con->nodes_head));
Con *child;
int i = 0, assigned = 0;
- int total = con->orientation == HORIZ ? rect.width : rect.height;
+ int total = con_orientation(con) == HORIZ ? rect.width : rect.height;
TAILQ_FOREACH(child, &(con->nodes_head), nodes) {
double percentage = child->percent > 0.0 ? child->percent : 1.0 / children;
assigned += sizes[i++] = percentage * total;
assert(children > 0);
/* default layout */
- if (con->layout == L_DEFAULT) {
- if (con->orientation == HORIZ) {
+ if (con->layout == L_SPLITH || con->layout == L_SPLITV) {
+ if (con->layout == L_SPLITH) {
child->rect.x = x;
child->rect.y = y;
child->rect.width = sizes[i];
content->type = CT_CON;
FREE(content->name);
content->name = sstrdup("content");
+ content->layout = L_SPLITH;
x_set_name(content, "[i3 con] content __i3");
con_attach(content, __i3, false);
ws->type = CT_WORKSPACE;
ws->num = -1;
ws->name = sstrdup("__i3_scratch");
+ ws->layout = L_SPLITH;
con_attach(ws, content, false);
x_set_name(ws, "[i3 con] workspace __i3_scratch");
ws->fullscreen_mode = CF_OUTPUT;
FREE(croot->name);
croot->name = "root";
croot->type = CT_ROOT;
+ croot->layout = L_SPLITH;
croot->rect = (Rect){
geometry->x,
geometry->y,
/* 3. create the container and attach it to its parent */
Con *new = con_new(con, window);
+ new->layout = L_SPLITH;
/* 4: re-calculate child->percent for each child */
con_fix_percent(con);
/* for a workspace, we just need to change orientation */
if (con->type == CT_WORKSPACE) {
DLOG("Workspace, simply changing orientation to %d\n", orientation);
- con->orientation = orientation;
+ con->layout = (orientation == HORIZ) ? L_SPLITH : L_SPLITV;
return;
}
* child (its split functionality is unused so far), we just change the
* orientation (more intuitive than splitting again) */
if (con_num_children(parent) == 1 &&
- parent->layout == L_DEFAULT) {
- parent->orientation = orientation;
+ (parent->layout == L_SPLITH ||
+ parent->layout == L_SPLITV)) {
+ parent->layout = (orientation == HORIZ) ? L_SPLITH : L_SPLITV;
DLOG("Just changing orientation of existing container\n");
return;
}
TAILQ_REPLACE(&(parent->nodes_head), con, new, nodes);
TAILQ_REPLACE(&(parent->focus_head), con, new, focused);
new->parent = parent;
- new->orientation = orientation;
+ new->layout = (orientation == HORIZ) ? L_SPLITH : L_SPLITV;
+ new->split = true;
/* 3: swap 'percent' (resize factor) */
new->percent = con->percent;
DLOG("Checking if I can flatten con = %p / %s\n", con, con->name);
/* We only consider normal containers without windows */
- if (con->type != CT_CON || con->window != NULL)
+ if (con->type != CT_CON ||
+ parent->layout == L_OUTPUT || /* con == "content" */
+ con->window != NULL)
goto recurse;
/* Ensure it got only one child */
if (child == NULL || TAILQ_NEXT(child, nodes) != NULL)
goto recurse;
+ DLOG("child = %p, con = %p, parent = %p\n", child, con, parent);
+
/* The child must have a different orientation than the con but the same as
* the con’s parent to be redundant */
- if (con->orientation == NO_ORIENTATION ||
- child->orientation == NO_ORIENTATION ||
- con->orientation == child->orientation ||
- child->orientation != parent->orientation)
+ if (con->split ||
+ child->split ||
+ con_orientation(con) == con_orientation(child) ||
+ con_orientation(child) != con_orientation(parent))
goto recurse;
DLOG("Alright, I have to flatten this situation now. Stay calm.\n");
* back-and-forth switching. */
static char *previous_workspace_name = NULL;
+/*
+ * Sets ws->layout to splith/splitv if default_orientation was specified in the
+ * configfile. Otherwise, it uses splith/splitv depending on whether the output
+ * is higher than wide.
+ *
+ */
+static void _workspace_apply_default_orientation(Con *ws) {
+ /* If default_orientation is set to NO_ORIENTATION we determine
+ * orientation depending on output resolution. */
+ if (config.default_orientation == NO_ORIENTATION) {
+ Con *output = con_get_output(ws);
+ ws->layout = (output->rect.height > output->rect.width) ? L_SPLITV : L_SPLITH;
+ DLOG("Auto orientation. Workspace size set to (%d,%d), setting layout to %d.\n",
+ output->rect.width, output->rect.height, ws->layout);
+ } else {
+ ws->layout = (config.default_orientation == HORIZ) ? L_SPLITH : L_SPLITV;
+ }
+}
+
/*
* Returns a pointer to the workspace with the given number (starting at 0),
* creating the workspace if necessary (by allocating the necessary amount of
else workspace->num = parsed_num;
LOG("num = %d\n", workspace->num);
- /* If default_orientation is set to NO_ORIENTATION we
- * determine workspace orientation from workspace size.
- * Otherwise we just set the orientation to default_orientation. */
- if (config.default_orientation == NO_ORIENTATION) {
- workspace->orientation = (output->rect.height > output->rect.width) ? VERT : HORIZ;
- DLOG("Auto orientation. Output resolution set to (%d,%d), setting orientation to %d.\n",
- workspace->rect.width, workspace->rect.height, workspace->orientation);
- } else {
- workspace->orientation = config.default_orientation;
- }
+ workspace->parent = content;
+ _workspace_apply_default_orientation(workspace);
con_attach(workspace, content, false);
ws->fullscreen_mode = CF_OUTPUT;
- /* If default_orientation is set to NO_ORIENTATION we determine
- * orientation depending on output resolution. */
- if (config.default_orientation == NO_ORIENTATION) {
- ws->orientation = (output->rect.height > output->rect.width) ? VERT : HORIZ;
- DLOG("Auto orientation. Workspace size set to (%d,%d), setting orientation to %d.\n",
- output->rect.width, output->rect.height, ws->orientation);
- } else {
- ws->orientation = config.default_orientation;
- }
+ _workspace_apply_default_orientation(ws);
return ws;
}
+
/*
* Returns true if the workspace is currently visible. Especially important for
* multi-monitor environments, as they can have multiple currenlty active
/*
* 'Forces' workspace orientation by moving all cons into a new split-con with
- * the same orientation as the workspace and then changing the workspace
- * orientation.
+ * the same layout as the workspace and then changing the workspace layout.
*
*/
void ws_force_orientation(Con *ws, orientation_t orientation) {
Con *split = con_new(NULL, NULL);
split->parent = ws;
- /* 2: copy layout and orientation from workspace */
+ /* 2: copy layout from workspace */
split->layout = ws->layout;
- split->orientation = ws->orientation;
Con *old_focused = TAILQ_FIRST(&(ws->focus_head));
con_attach(child, split, true);
}
- /* 4: switch workspace orientation */
- ws->orientation = orientation;
+ /* 4: switch workspace layout */
+ ws->layout = (orientation == HORIZ) ? L_SPLITH : L_SPLITV;
/* 5: attach the new split container to the workspace */
DLOG("Attaching new split to ws\n");
/* 1: create a new split container */
Con *new = con_new(NULL, NULL);
new->parent = ws;
+ new->split = true;
/* 2: set the requested layout on the split con */
new->layout = config.default_layout;
- /* 3: While the layout is irrelevant in stacked/tabbed mode, it needs
- * to be set. Otherwise, this con will not be interpreted as a split
- * container. */
- if (config.default_orientation == NO_ORIENTATION) {
- new->orientation = (ws->rect.height > ws->rect.width) ? VERT : HORIZ;
- } else {
- new->orientation = config.default_orientation;
- }
-
/* 4: attach the new split container to the workspace */
DLOG("Attaching new split %p to workspace %p\n", new, ws);
con_attach(new, ws, false);
name => 'root',
orientation => $ignore,
type => 0,
+ split => JSON::XS::false,
id => $ignore,
rect => $ignore,
window_rect => $ignore,
geometry => $ignore,
swallows => $ignore,
percent => undef,
- layout => 'default',
+ layout => 'splith',
floating => 'auto_off',
+ last_split_layout => 'splith',
scratchpad_state => 'none',
focus => $ignore,
focused => JSON::XS::false,
$tmp = fresh_workspace;
$ws = get_ws($tmp);
- is($ws->{orientation}, 'horizontal', 'orientation horizontal by default');
+ is($ws->{layout}, 'splith', 'orientation horizontal by default');
cmd 'split v';
$ws = get_ws($tmp);
- is($ws->{orientation}, 'vertical', 'split v changes workspace orientation');
+ is($ws->{layout}, 'splitv', 'split v changes workspace orientation');
cmd 'open';
cmd 'open';
is(@{$first->{nodes}}, 0, 'first container has no children');
isnt($second->{name}, $old_name, 'second container was replaced');
- is($second->{orientation}, 'horizontal', 'orientation is horizontal');
+ is($second->{layout}, 'splith', 'orientation is horizontal');
is(@{$second->{nodes}}, 2, 'second container has 2 children');
is($second->{nodes}->[0]->{name}, $old_name, 'found old second container');
}
$tmp = fresh_workspace;
$ws = get_ws($tmp);
-is($ws->{orientation}, 'horizontal', 'orientation horizontal by default');
+is($ws->{layout}, 'splith', 'orientation horizontal by default');
cmd 'split v';
$ws = get_ws($tmp);
-is($ws->{orientation}, 'vertical', 'split v changes workspace orientation');
+is($ws->{layout}, 'splitv', 'split v changes workspace orientation');
cmd 'open';
my @content = @{get_ws_content($tmp)};
cmd 'move right';
my $ws = get_ws($tmp);
-is($ws->{orientation}, 'horizontal', 'workspace orientation is horizontal');
+is($ws->{layout}, 'splith', 'workspace layout is splith');
is(@{$ws->{nodes}}, 3, 'all three windows on workspace level');
done_testing;
--- /dev/null
+#!perl
+# vim:ts=4:sw=4:expandtab
+# Verifies that switching between the different layouts works as expected.
+use i3test;
+
+my $tmp = fresh_workspace;
+
+open_window;
+open_window;
+cmd 'split v';
+open_window;
+
+my ($nodes, $focus) = get_ws_content($tmp);
+is($nodes->[1]->{layout}, 'splitv', 'layout is splitv currently');
+
+cmd 'layout stacked';
+($nodes, $focus) = get_ws_content($tmp);
+is($nodes->[1]->{layout}, 'stacked', 'layout now stacked');
+
+cmd 'layout tabbed';
+($nodes, $focus) = get_ws_content($tmp);
+is($nodes->[1]->{layout}, 'tabbed', 'layout now tabbed');
+
+cmd 'layout toggle split';
+($nodes, $focus) = get_ws_content($tmp);
+is($nodes->[1]->{layout}, 'splitv', 'layout now splitv again');
+
+cmd 'layout toggle split';
+($nodes, $focus) = get_ws_content($tmp);
+is($nodes->[1]->{layout}, 'splith', 'layout now splith');
+
+cmd 'layout toggle split';
+($nodes, $focus) = get_ws_content($tmp);
+is($nodes->[1]->{layout}, 'splitv', 'layout now splitv');
+
+cmd 'layout toggle split';
+($nodes, $focus) = get_ws_content($tmp);
+is($nodes->[1]->{layout}, 'splith', 'layout now splith');
+
+cmd 'layout toggle';
+($nodes, $focus) = get_ws_content($tmp);
+is($nodes->[1]->{layout}, 'stacked', 'layout now stacked');
+
+cmd 'layout toggle';
+($nodes, $focus) = get_ws_content($tmp);
+is($nodes->[1]->{layout}, 'tabbed', 'layout now tabbed');
+
+cmd 'layout toggle';
+($nodes, $focus) = get_ws_content($tmp);
+is($nodes->[1]->{layout}, 'splith', 'layout now splith');
+
+cmd 'layout toggle';
+($nodes, $focus) = get_ws_content($tmp);
+is($nodes->[1]->{layout}, 'stacked', 'layout now stacked');
+
+cmd 'layout toggle all';
+($nodes, $focus) = get_ws_content($tmp);
+is($nodes->[1]->{layout}, 'tabbed', 'layout now tabbed');
+
+cmd 'layout toggle all';
+($nodes, $focus) = get_ws_content($tmp);
+is($nodes->[1]->{layout}, 'splith', 'layout now splith');
+
+cmd 'layout toggle all';
+($nodes, $focus) = get_ws_content($tmp);
+is($nodes->[1]->{layout}, 'splitv', 'layout now splitv');
+
+cmd 'layout toggle all';
+($nodes, $focus) = get_ws_content($tmp);
+is($nodes->[1]->{layout}, 'stacked', 'layout now stacked');
+
+cmd 'layout toggle all';
+($nodes, $focus) = get_ws_content($tmp);
+is($nodes->[1]->{layout}, 'tabbed', 'layout now tabbed');
+
+cmd 'layout toggle all';
+($nodes, $focus) = get_ws_content($tmp);
+is($nodes->[1]->{layout}, 'splith', 'layout now splith');
+
+cmd 'layout toggle all';
+($nodes, $focus) = get_ws_content($tmp);
+is($nodes->[1]->{layout}, 'splitv', 'layout now splitv');
+
+done_testing;