Introduce splith/splitv layouts, remove orientation

With this commit, the "default" layout is replaced by the splith and
splitv layouts. splith is equivalent to default with orientation
horizontal and splitv is equivalent to default with orientation
vertical.

The "split h" and "split v" commands continue to work as before, they
split the current container and you will end up in a split container
with layout splith (after "split h") or splitv (after "split v").

To change a splith container into a splitv container, use either "layout
splitv" or "layout toggle split". The latter command is used in the
default config as mod+l (previously "layout default"). In case you have
"layout default" in your config file, it is recommended to just replace
it by "layout toggle split", which will work as "layout default" did
before when pressing it once, but toggle between horizontal/vertical
when pressing it repeatedly.

The rationale behind this commit is that it’s cleaner to have all
parameters that influence how windows are rendered in the layout itself
rather than having a special parameter in combination with only one
layout. This enables us to change existing split containers in all cases
without breaking existing features (see ticket #464). Also, users should
feel more confident about whether they are actually splitting or just
changing an existing split container now.

As a nice side-effect, this commit brings back the "layout toggle"
feature we once had in i3 version 3 (see the userguide).

AFAIK, it is safe to use in-place restart to upgrade into versions
after this commit (switching to an older version will break your layout,
though).

Fixes #464
This commit is contained in:
Michael Stapelberg 2012-08-04 03:04:00 +02:00
parent 077e021e26
commit de94f6da1a
22 changed files with 458 additions and 156 deletions

View File

@ -1,7 +1,7 @@
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
@ -270,12 +270,15 @@ border (string)::
Can be either "normal", "none" or "1pixel", dependending on the
containers 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

View File

@ -1,7 +1,7 @@
i3 Users 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
@ -68,9 +68,11 @@ To split a window vertically, press +mod+v+. To split it horizontally, press
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.
@ -78,8 +80,8 @@ tabbed::
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]
@ -196,20 +198,21 @@ image::tree-shot4.png["shot4",title="Two terminals on standard workspace"]
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:
Lets 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:
Lets 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:
@ -1190,13 +1193,15 @@ cursor for 60 seconds.
=== 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 containers
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*:
---------------------------
@ -1211,19 +1216,32 @@ bindsym mod+h split horizontal
=== 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

View File

@ -57,10 +57,10 @@ bindsym Mod1+v split v
# 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

View File

@ -58,10 +58,10 @@ bindcode $mod+55 split v
# 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

View File

@ -200,11 +200,17 @@ void cmd_fullscreen(I3_CMD, char *fullscreen_mode);
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'.
*

View File

@ -248,6 +248,15 @@ void con_set_border_style(Con *con, int border_style);
*/
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

View File

@ -423,6 +423,8 @@ struct Assignment {
*/
struct Con {
bool mapped;
/** whether this is a split container or not */
bool split;
enum {
CT_ROOT = 0,
CT_OUTPUT = 1,
@ -431,7 +433,6 @@ struct Con {
CT_WORKSPACE = 4,
CT_DOCKAREA = 5
} type;
orientation_t orientation;
struct Con *parent;
struct Rect rect;
@ -496,7 +497,15 @@ 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, 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

View File

@ -66,10 +66,20 @@ state BORDER:
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:

View File

@ -35,13 +35,13 @@ static bool tiling_resize_for_border(Con *con, border_t border, xcb_button_press
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))) {
@ -145,7 +145,7 @@ static bool tiling_resize(Con *con, xcb_button_press_event_t *event, const click
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;

View File

@ -530,7 +530,7 @@ static bool cmd_resize_tiling_direction(I3_CMD, char *way, char *direction, int
(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;
}
@ -541,7 +541,7 @@ static bool cmd_resize_tiling_direction(I3_CMD, char *way, char *direction, int
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)) ||
@ -612,7 +612,7 @@ static bool cmd_resize_tiling_width_height(I3_CMD, char *way, char *direction, i
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 */
@ -621,7 +621,7 @@ static bool cmd_resize_tiling_width_height(I3_CMD, char *way, char *direction, i
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) ||
@ -1397,17 +1397,27 @@ 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) {
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))
@ -1424,6 +1434,33 @@ void cmd_layout(I3_CMD, char *layout_str) {
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'.
*
@ -1472,6 +1509,7 @@ void cmd_restart(I3_CMD) {
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);

117
src/con.c
View File

@ -217,8 +217,8 @@ bool con_accepts_window(Con *con) {
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;
}
@ -265,8 +265,11 @@ Con *con_parent_with_orientation(Con *con, orientation_t orientation) {
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;
@ -697,14 +700,32 @@ void con_move_to_workspace(Con *con, Con *workspace, bool fix_coordinates, bool
*
*/
int con_orientation(Con *con) {
switch (con->layout) {
case L_SPLITV:
/* stacking containers behave like they are in vertical orientation */
if (con->layout == L_STACKED)
case L_STACKED:
return VERT;
if (con->layout == L_TABBED)
case L_SPLITH:
/* tabbed containers behave like they are in vertical orientation */
case L_TABBED:
return HORIZ;
return con->orientation;
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);
}
}
/*
@ -1017,23 +1038,16 @@ void con_set_layout(Con *con, int layout) {
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))) {
@ -1054,8 +1068,67 @@ void con_set_layout(Con *con, int layout) {
return;
}
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 dont 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);
}
}
}
}
/*
* Callback which will be called when removing a child from the given con.
@ -1131,12 +1204,12 @@ Rect con_minimum_size(Con *con) {
/* 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 {
@ -1148,8 +1221,8 @@ Rect con_minimum_size(Con *con) {
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);
}

View File

@ -40,7 +40,7 @@ void floating_enable(Con *con, bool automatic) {
}
/* 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) {
@ -52,7 +52,7 @@ void floating_enable(Con *con, bool automatic) {
/* 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. */
@ -97,8 +97,9 @@ void floating_enable(Con *con, bool automatic) {
* 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 its not. */

View File

@ -161,17 +161,14 @@ void dump_node(yajl_gen gen, struct Con *con, bool inplace_restart) {
ystr("type");
y(integer, con->type);
/* provided for backwards compatibility only. */
ystr("orientation");
switch (con->orientation) {
case NO_ORIENTATION:
if (!con->split)
ystr("none");
break;
case HORIZ:
else {
if (con_orientation(con) == HORIZ)
ystr("horizontal");
break;
case VERT:
ystr("vertical");
break;
else ystr("vertical");
}
ystr("scratchpad_state");
@ -203,10 +200,20 @@ void dump_node(yajl_gen gen, struct Con *con, bool inplace_restart) {
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");
@ -222,6 +229,16 @@ void dump_node(yajl_gen gen, struct Con *con, bool inplace_restart) {
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:

View File

@ -156,15 +156,25 @@ static int json_string(void *ctx, const unsigned char *val, unsigned int len) {
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;
@ -181,17 +191,33 @@ static int json_string(void *ctx, const unsigned char *val, unsigned int len) {
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);
@ -288,6 +314,9 @@ static int json_bool(void *ctx, int 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;

View File

@ -256,7 +256,6 @@ void output_init_con(Output *output) {
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);
@ -278,6 +277,7 @@ void output_init_con(Output *output) {
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");
@ -290,7 +290,6 @@ void output_init_con(Output *output) {
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);
@ -447,11 +446,11 @@ static void output_change_mode(xcb_connection_t *conn, Output *output) {
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);
}
}
}

View File

@ -106,9 +106,9 @@ static void render_l_output(Con *con) {
*/
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 */
@ -208,11 +208,11 @@ void render_con(Con *con, bool render_fullscreen) {
/* 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;
@ -289,8 +289,8 @@ void render_con(Con *con, bool render_fullscreen) {
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];

View File

@ -39,6 +39,7 @@ static Con *_create___i3(void) {
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);
@ -48,6 +49,7 @@ static Con *_create___i3(void) {
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;
@ -112,6 +114,7 @@ void tree_init(xcb_get_geometry_reply_t *geometry) {
FREE(croot->name);
croot->name = "root";
croot->type = CT_ROOT;
croot->layout = L_SPLITH;
croot->rect = (Rect){
geometry->x,
geometry->y,
@ -151,6 +154,7 @@ Con *tree_open_con(Con *con, i3Window *window) {
/* 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);
@ -337,7 +341,7 @@ void tree_split(Con *con, orientation_t orientation) {
/* 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;
}
@ -351,8 +355,9 @@ void tree_split(Con *con, orientation_t orientation) {
* 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;
}
@ -364,7 +369,8 @@ void tree_split(Con *con, orientation_t orientation) {
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;
@ -594,7 +600,9 @@ void tree_flatten(Con *con) {
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 */
@ -602,12 +610,14 @@ void tree_flatten(Con *con) {
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 cons 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");

View File

@ -14,6 +14,25 @@
* 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
@ -64,16 +83,8 @@ Con *workspace_get(const char *num, bool *created) {
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);
@ -198,19 +209,12 @@ Con *create_workspace_on_output(Output *output, Con *content) {
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
@ -686,8 +690,7 @@ void workspace_update_urgent_flag(Con *ws) {
/*
* '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) {
@ -695,9 +698,8 @@ 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));
@ -709,8 +711,8 @@ void ws_force_orientation(Con *ws, orientation_t orientation) {
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");
@ -745,19 +747,11 @@ Con *workspace_attach_to(Con *ws) {
/* 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);

View File

@ -39,14 +39,16 @@ my $expected = {
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,

View File

@ -19,10 +19,10 @@ sub verify_split_layout {
$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';
@ -47,7 +47,7 @@ sub verify_split_layout {
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');
}
@ -66,10 +66,10 @@ verify_split_layout(split_command => 'split horizontal');
$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)};

View File

@ -22,7 +22,7 @@ cmd 'move up';
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;

84
testcases/t/192-layout.t Normal file
View File

@ -0,0 +1,84 @@
#!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;