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) IPC interface (interprocess communication)
========================================== ==========================================
Michael Stapelberg <michael@i3wm.org> Michael Stapelberg <michael@i3wm.org>
July 2012 August 2012
This document describes how to interface with i3 from a separate process. This 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 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 Can be either "normal", "none" or "1pixel", dependending on the
containers border style. containers border style.
layout (string):: 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 Other values might be possible in the future, should we add new
layouts. layouts.
orientation (string):: orientation (string)::
Can be either "none" (for non-split containers), "horizontal" or Can be either "none" (for non-split containers), "horizontal" or
"vertical". "vertical".
THIS FIELD IS OBSOLETE. It is still present, but your code should not
use it. Instead, rely on the layout field.
percent (float):: percent (float)::
The percentage which this container takes in its parent. A value of The percentage which this container takes in its parent. A value of
+null+ means that the percent property does not make sense for this +null+ means that the percent property does not make sense for this

View File

@ -1,7 +1,7 @@
i3 Users Guide i3 Users Guide
=============== ===============
Michael Stapelberg <michael+i3@stapelberg.de> Michael Stapelberg <michael@i3wm.org>
April 2012 August 2012
This document contains all the information you need to configure and use the i3 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 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: 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 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:: stacking::
Only the focused window in the container is displayed. You get a list of Only the focused window in the container is displayed. You get a list of
windows at the top of the container. 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 The same principle as +stacking+, but the list of windows at the top is only
a single line which is vertically split. a single line which is vertically split.
To switch modes, press +mod+e+ for default, +mod+s+ for stacking and To switch modes, press +mod+e+ for splith/splitv (it toggles), +mod+s+ for
+mod+w+ for tabbed. stacking and +mod+w+ for tabbed.
image:modes.png[Container modes] 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 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 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 orientation (horizontal, vertical or unspecified) and the orientation depends
workspace, the default orientation of the workspace +Container+ is horizontal on the layout the container is in (vertical for splitv and stacking, horizontal
(most monitors are widescreen nowadays). If you change the orientation to for splith and tabbed). So, in our example with the workspace, the default
vertical (+mod+v+ in the default config) and *then* open two terminals, i3 will layout of the workspace +Container+ is splith (most monitors are widescreen
configure your windows like this: 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"] image::tree-shot2.png["shot2",title="Vertical Workspace Orientation"]
An interesting new feature of the tree branch is the ability to split anything: 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 horizontal Lets assume you have two terminals on a workspace (with splith layout, that is
orientation), focus is on the right terminal. Now you want to open another horizontal orientation), focus is on the right terminal. Now you want to open
terminal window below the current one. If you would just open a new terminal another terminal window below the current one. If you would just open a new
window, it would show up to the right due to the horizontal workspace terminal window, it would show up to the right due to the splith layout.
orientation. Instead, press +mod+v+ to create a +Vertical Split Container+ (to 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 open a +Horizontal Split Container+, use +mod+h+). Now you can open a new
terminal and it will open below the current one: terminal and it will open below the current one:
@ -1190,13 +1193,15 @@ cursor for 60 seconds.
=== Splitting containers === Splitting containers
The split command makes the current window a split container. Split 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 can contain multiple windows. Depending on the layout of the split container,
either split horizontally (a new window gets placed to the right of the current new windows get placed to the right of the current one (splith) or new windows
one) or vertically (a new window gets placed below the current one). get placed below the current one (splitv).
If you apply this command to a split container with the same orientation, 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 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*: *Syntax*:
--------------------------- ---------------------------
@ -1211,19 +1216,32 @@ bindsym mod+h split horizontal
=== Manipulating layout === Manipulating layout
Use +layout default+, +layout stacking+ or +layout tabbed+ to change the Use +layout toggle split+, +layout stacking+ or +layout tabbed+ to change the
current container layout to default, stacking or tabbed layout, respectively. current container layout to splith/splitv, stacking or tabbed layout,
respectively.
To make the current window (!) fullscreen, use +fullscreen+, to make To make the current window (!) fullscreen, use +fullscreen+, to make
it floating (or tiling again) use +floating enable+ respectively +floating disable+ it floating (or tiling again) use +floating enable+ respectively +floating disable+
(or +floating toggle+): (or +floating toggle+):
*Syntax*:
--------------
layout <tabbed|stacking>
layout toggle [split|all]
--------------
*Examples*: *Examples*:
-------------- --------------
bindsym mod+s layout stacking bindsym mod+s layout stacking
bindsym mod+l layout default bindsym mod+l layout toggle split
bindsym mod+w layout tabbed 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 # Toggle fullscreen
bindsym mod+f fullscreen bindsym mod+f fullscreen

View File

@ -57,10 +57,10 @@ bindsym Mod1+v split v
# enter fullscreen mode for the focused container # enter fullscreen mode for the focused container
bindsym Mod1+f fullscreen bindsym Mod1+f fullscreen
# change container layout (stacked, tabbed, default) # change container layout (stacked, tabbed, toggle split)
bindsym Mod1+s layout stacking bindsym Mod1+s layout stacking
bindsym Mod1+w layout tabbed bindsym Mod1+w layout tabbed
bindsym Mod1+e layout default bindsym Mod1+e layout toggle split
# toggle tiling / floating # toggle tiling / floating
bindsym Mod1+Shift+space floating toggle bindsym Mod1+Shift+space floating toggle

View File

@ -58,10 +58,10 @@ bindcode $mod+55 split v
# enter fullscreen mode for the focused container # enter fullscreen mode for the focused container
bindcode $mod+41 fullscreen bindcode $mod+41 fullscreen
# change container layout (stacked, tabbed, default) # change container layout (stacked, tabbed, toggle split)
bindcode $mod+39 layout stacking bindcode $mod+39 layout stacking
bindcode $mod+25 layout tabbed bindcode $mod+25 layout tabbed
bindcode $mod+26 layout default bindcode $mod+26 layout toggle split
# toggle tiling / floating # toggle tiling / floating
bindcode $mod+Shift+65 floating toggle 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); 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); 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'. * 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); 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 * 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 * split/stacked/tabbed cons). Will be called when resizing floating cons

View File

@ -423,6 +423,8 @@ struct Assignment {
*/ */
struct Con { struct Con {
bool mapped; bool mapped;
/** whether this is a split container or not */
bool split;
enum { enum {
CT_ROOT = 0, CT_ROOT = 0,
CT_OUTPUT = 1, CT_OUTPUT = 1,
@ -431,7 +433,6 @@ struct Con {
CT_WORKSPACE = 4, CT_WORKSPACE = 4,
CT_DOCKAREA = 5 CT_DOCKAREA = 5
} type; } type;
orientation_t orientation;
struct Con *parent; struct Con *parent;
struct Rect rect; struct Rect rect;
@ -496,7 +497,15 @@ struct Con {
TAILQ_HEAD(swallow_head, Match) swallow_head; TAILQ_HEAD(swallow_head, Match) swallow_head;
enum { CF_NONE = 0, CF_OUTPUT = 1, CF_GLOBAL = 2 } fullscreen_mode; 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; border_style_t border_style;
/** floating? (= not in tiling layout) This cannot be simply a bool /** 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 * 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' border_style = 'normal', 'none', '1pixel', 'toggle'
-> call cmd_border($border_style) -> call cmd_border($border_style)
# layout default|stacked|stacking|tabbed # layout default|stacked|stacking|tabbed|splitv|splith
# layout toggle [split|all]
state LAYOUT: state LAYOUT:
layout_mode = 'default', 'stacked', 'stacking', 'tabbed' layout_mode = 'default', 'stacked', 'stacking', 'tabbed', 'splitv', 'splith'
-> call cmd_layout($layout_mode) -> 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> # append_layout <path>
state APPEND_LAYOUT: 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; Con *resize_con = con;
while (resize_con->type != CT_WORKSPACE && while (resize_con->type != CT_WORKSPACE &&
resize_con->type != CT_FLOATING_CON && resize_con->type != CT_FLOATING_CON &&
resize_con->parent->orientation != orientation) con_orientation(resize_con->parent) != orientation)
resize_con = resize_con->parent; resize_con = resize_con->parent;
DLOG("resize_con = %p\n", resize_con); DLOG("resize_con = %p\n", resize_con);
if (resize_con->type != CT_WORKSPACE && if (resize_con->type != CT_WORKSPACE &&
resize_con->type != CT_FLOATING_CON && resize_con->type != CT_FLOATING_CON &&
resize_con->parent->orientation == orientation) { con_orientation(resize_con->parent) == orientation) {
first = resize_con; first = resize_con;
second = (way == 'n') ? TAILQ_NEXT(first, nodes) : TAILQ_PREV(first, nodes_head, nodes); second = (way == 'n') ? TAILQ_NEXT(first, nodes) : TAILQ_PREV(first, nodes_head, nodes);
if (second == TAILQ_END(&(first->nodes_head))) { 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 || if ((check_con->layout == L_STACKED ||
check_con->layout == L_TABBED || check_con->layout == L_TABBED ||
check_con->orientation == HORIZ) && con_orientation(check_con) == HORIZ) &&
con_num_children(check_con) > 1) { con_num_children(check_con) > 1) {
DLOG("Not handling this resize, this container has > 1 child.\n"); DLOG("Not handling this resize, this container has > 1 child.\n");
return false; 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); (strcmp(direction, "left") == 0 || strcmp(direction, "right") == 0 ? HORIZ : VERT);
do { do {
if (current->parent->orientation != search_orientation) { if (con_orientation(current->parent) != search_orientation) {
current = current->parent; current = current->parent;
continue; continue;
} }
@ -541,7 +541,7 @@ static bool cmd_resize_tiling_direction(I3_CMD, char *way, char *direction, int
percentage = 1.0 / children; percentage = 1.0 / children;
LOG("default percentage = %f\n", percentage); LOG("default percentage = %f\n", percentage);
orientation_t orientation = current->parent->orientation; orientation_t orientation = con_orientation(current->parent);
if ((orientation == HORIZ && if ((orientation == HORIZ &&
(strcmp(direction, "up") == 0 || strcmp(direction, "down") == 0)) || (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 && while (current->type != CT_WORKSPACE &&
current->type != CT_FLOATING_CON && current->type != CT_FLOATING_CON &&
current->parent->orientation != search_orientation) con_orientation(current->parent) != search_orientation)
current = current->parent; current = current->parent;
/* get the default percentage */ /* 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; double percentage = 1.0 / children;
LOG("default percentage = %f\n", percentage); LOG("default percentage = %f\n", percentage);
orientation_t orientation = current->parent->orientation; orientation_t orientation = con_orientation(current->parent);
if ((orientation == HORIZ && if ((orientation == HORIZ &&
strcmp(direction, "height") == 0) || 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) { void cmd_layout(I3_CMD, char *layout_str) {
if (strcmp(layout_str, "stacking") == 0) if (strcmp(layout_str, "stacking") == 0)
layout_str = "stacked"; layout_str = "stacked";
DLOG("changing layout to %s\n", layout_str);
owindow *current; owindow *current;
int layout = (strcmp(layout_str, "default") == 0 ? L_DEFAULT : int layout;
(strcmp(layout_str, "stacked") == 0 ? L_STACKED : /* default is a special case which will be handled in con_set_layout(). */
L_TABBED)); 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 */ /* check if the match is empty, not if the result is empty */
if (match_is_empty(current_match)) if (match_is_empty(current_match))
@ -1424,6 +1434,33 @@ void cmd_layout(I3_CMD, char *layout_str) {
ysuccess(true); 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'. * Implementaiton of 'exit'.
* *
@ -1472,6 +1509,7 @@ void cmd_restart(I3_CMD) {
void cmd_open(I3_CMD) { void cmd_open(I3_CMD) {
LOG("opening new container\n"); LOG("opening new container\n");
Con *con = tree_open_con(NULL, NULL); Con *con = tree_open_con(NULL, NULL);
con->layout = L_SPLITH;
con_focus(con); con_focus(con);
y(map_open); y(map_open);

117
src/con.c
View File

@ -217,8 +217,8 @@ bool con_accepts_window(Con *con) {
if (con->type == CT_WORKSPACE) if (con->type == CT_WORKSPACE)
return false; return false;
if (con->orientation != NO_ORIENTATION) { if (con->split) {
DLOG("container %p does not accepts windows, orientation != NO_ORIENTATION\n", con); DLOG("container %p does not accept windows, it is a split container.\n", con);
return false; return false;
} }
@ -265,8 +265,11 @@ Con *con_parent_with_orientation(Con *con, orientation_t orientation) {
while (con_orientation(parent) != orientation) { while (con_orientation(parent) != orientation) {
DLOG("Need to go one level further up\n"); DLOG("Need to go one level further up\n");
parent = parent->parent; parent = parent->parent;
/* Abort when we reach a floating con */ /* Abort when we reach a floating con, or an output con */
if (parent && parent->type == CT_FLOATING_CON) if (parent &&
(parent->type == CT_FLOATING_CON ||
parent->type == CT_OUTPUT ||
(parent->parent && parent->parent->type == CT_OUTPUT)))
parent = NULL; parent = NULL;
if (parent == NULL) if (parent == NULL)
break; break;
@ -697,14 +700,32 @@ void con_move_to_workspace(Con *con, Con *workspace, bool fix_coordinates, bool
* *
*/ */
int con_orientation(Con *con) { int con_orientation(Con *con) {
switch (con->layout) {
case L_SPLITV:
/* stacking containers behave like they are in vertical orientation */ /* stacking containers behave like they are in vertical orientation */
if (con->layout == L_STACKED) case L_STACKED:
return VERT; 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 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); Con *new = con_new(NULL, NULL);
new->parent = con; 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; new->layout = layout;
new->split = true;
/* 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;
}
Con *old_focused = TAILQ_FIRST(&(con->focus_head)); Con *old_focused = TAILQ_FIRST(&(con->focus_head));
if (old_focused == TAILQ_END(&(con->focus_head))) if (old_focused == TAILQ_END(&(con->focus_head)))
old_focused = NULL; 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"); DLOG("Moving cons\n");
Con *child; Con *child;
while (!TAILQ_EMPTY(&(con->nodes_head))) { while (!TAILQ_EMPTY(&(con->nodes_head))) {
@ -1054,7 +1068,66 @@ void con_set_layout(Con *con, int layout) {
return; 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; 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);
}
}
}
} }
/* /*
@ -1131,12 +1204,12 @@ Rect con_minimum_size(Con *con) {
/* For horizontal/vertical split containers we sum up the width (h-split) /* 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 * or height (v-split) and use the maximum of the height (h-split) or width
* (v-split) as minimum size. */ * (v-split) as minimum size. */
if (con->orientation == HORIZ || con->orientation == VERT) { if (con->split) {
uint32_t width = 0, height = 0; uint32_t width = 0, height = 0;
Con *child; Con *child;
TAILQ_FOREACH(child, &(con->nodes_head), nodes) { TAILQ_FOREACH(child, &(con->nodes_head), nodes) {
Rect min = con_minimum_size(child); Rect min = con_minimum_size(child);
if (con->orientation == HORIZ) { if (con->layout == L_SPLITH) {
width += min.width; width += min.width;
height = max(height, min.height); height = max(height, min.height);
} else { } else {
@ -1148,8 +1221,8 @@ Rect con_minimum_size(Con *con) {
return (Rect){ 0, 0, width, height }; return (Rect){ 0, 0, width, height };
} }
ELOG("Unhandled case, type = %d, layout = %d, orientation = %d\n", ELOG("Unhandled case, type = %d, layout = %d, split = %d\n",
con->type, con->layout, con->orientation); con->type, con->layout, con->split);
assert(false); 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 /* 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 * cannot touch the workspace container itself because floating containers
* are children of the workspace. */ * are children of the workspace. */
if (con->type == CT_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 */ /* TODO: refactor this with src/con.c:con_set_layout */
Con *new = con_new(NULL, NULL); Con *new = con_new(NULL, NULL);
new->parent = con; new->parent = con;
new->orientation = con->orientation; new->layout = con->layout;
/* since the new container will be set into floating mode directly /* since the new container will be set into floating mode directly
* afterwards, we need to copy the workspace rect. */ * afterwards, we need to copy the workspace rect. */
@ -97,8 +97,9 @@ void floating_enable(Con *con, bool automatic) {
* otherwise. */ * otherwise. */
Con *ws = con_get_workspace(con); Con *ws = con_get_workspace(con);
nc->parent = ws; nc->parent = ws;
nc->orientation = NO_ORIENTATION; nc->split = true;
nc->type = CT_FLOATING_CON; nc->type = CT_FLOATING_CON;
nc->layout = L_SPLITH;
/* We insert nc already, even though its rect is not yet calculated. This /* We insert nc already, even though its rect is not yet calculated. This
* is necessary because otherwise the workspace might be empty (and get * is necessary because otherwise the workspace might be empty (and get
* closed in tree_close()) even though its not. */ * 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"); ystr("type");
y(integer, con->type); y(integer, con->type);
/* provided for backwards compatibility only. */
ystr("orientation"); ystr("orientation");
switch (con->orientation) { if (!con->split)
case NO_ORIENTATION:
ystr("none"); ystr("none");
break; else {
case HORIZ: if (con_orientation(con) == HORIZ)
ystr("horizontal"); ystr("horizontal");
break; else ystr("vertical");
case VERT:
ystr("vertical");
break;
} }
ystr("scratchpad_state"); ystr("scratchpad_state");
@ -203,10 +200,20 @@ void dump_node(yajl_gen gen, struct Con *con, bool inplace_restart) {
ystr("focused"); ystr("focused");
y(bool, (con == focused)); y(bool, (con == focused));
ystr("split");
y(bool, con->split);
ystr("layout"); ystr("layout");
switch (con->layout) { switch (con->layout) {
case L_DEFAULT: 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; break;
case L_STACKED: case L_STACKED:
ystr("stacked"); ystr("stacked");
@ -222,6 +229,16 @@ void dump_node(yajl_gen gen, struct Con *con, bool inplace_restart) {
break; break;
} }
ystr("last_split_layout");
switch (con->layout) {
case L_SPLITV:
ystr("splitv");
break;
default:
ystr("splith");
break;
}
ystr("border"); ystr("border");
switch (con->border_style) { switch (con->border_style) {
case BS_NORMAL: 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); memcpy(json_node->sticky_group, val, len);
LOG("sticky_group of this container is %s\n", json_node->sticky_group); LOG("sticky_group of this container is %s\n", json_node->sticky_group);
} else if (strcasecmp(last_key, "orientation") == 0) { } 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; char *buf = NULL;
sasprintf(&buf, "%.*s", (int)len, val); sasprintf(&buf, "%.*s", (int)len, val);
if (strcasecmp(buf, "none") == 0) if (strcasecmp(buf, "none") == 0 ||
json_node->orientation = NO_ORIENTATION; strcasecmp(buf, "horizontal") == 0)
else if (strcasecmp(buf, "horizontal") == 0) json_node->last_split_layout = L_SPLITH;
json_node->orientation = HORIZ;
else if (strcasecmp(buf, "vertical") == 0) else if (strcasecmp(buf, "vertical") == 0)
json_node->orientation = VERT; json_node->last_split_layout = L_SPLITV;
else LOG("Unhandled orientation: %s\n", buf); 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); free(buf);
} else if (strcasecmp(last_key, "border") == 0) { } else if (strcasecmp(last_key, "border") == 0) {
char *buf = NULL; char *buf = NULL;
@ -181,17 +191,33 @@ static int json_string(void *ctx, const unsigned char *val, unsigned int len) {
char *buf = NULL; char *buf = NULL;
sasprintf(&buf, "%.*s", (int)len, val); sasprintf(&buf, "%.*s", (int)len, val);
if (strcasecmp(buf, "default") == 0) 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) else if (strcasecmp(buf, "stacked") == 0)
json_node->layout = L_STACKED; json_node->layout = L_STACKED;
else if (strcasecmp(buf, "tabbed") == 0) else if (strcasecmp(buf, "tabbed") == 0)
json_node->layout = L_TABBED; json_node->layout = L_TABBED;
else if (strcasecmp(buf, "dockarea") == 0) else if (strcasecmp(buf, "dockarea") == 0) {
json_node->layout = L_DOCKAREA; 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; 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); else LOG("Unhandled \"layout\": %s\n", buf);
free(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) { } else if (strcasecmp(last_key, "mark") == 0) {
char *buf = NULL; char *buf = NULL;
sasprintf(&buf, "%.*s", (int)len, val); sasprintf(&buf, "%.*s", (int)len, val);
@ -288,6 +314,9 @@ static int json_bool(void *ctx, int val) {
to_focus = json_node; to_focus = json_node;
} }
if (strcasecmp(last_key, "split") == 0)
json_node->split = val;
if (parsing_swallows) { if (parsing_swallows) {
if (strcasecmp(last_key, "restart_mode") == 0) if (strcasecmp(last_key, "restart_mode") == 0)
current_swallow->restart_mode = val; current_swallow->restart_mode = val;

View File

@ -256,7 +256,6 @@ void output_init_con(Output *output) {
Con *topdock = con_new(NULL, NULL); Con *topdock = con_new(NULL, NULL);
topdock->type = CT_DOCKAREA; topdock->type = CT_DOCKAREA;
topdock->layout = L_DOCKAREA; topdock->layout = L_DOCKAREA;
topdock->orientation = VERT;
/* this container swallows dock clients */ /* this container swallows dock clients */
Match *match = scalloc(sizeof(Match)); Match *match = scalloc(sizeof(Match));
match_init(match); match_init(match);
@ -278,6 +277,7 @@ void output_init_con(Output *output) {
DLOG("adding main content container\n"); DLOG("adding main content container\n");
Con *content = con_new(NULL, NULL); Con *content = con_new(NULL, NULL);
content->type = CT_CON; content->type = CT_CON;
content->layout = L_SPLITH;
FREE(content->name); FREE(content->name);
content->name = sstrdup("content"); content->name = sstrdup("content");
@ -290,7 +290,6 @@ void output_init_con(Output *output) {
Con *bottomdock = con_new(NULL, NULL); Con *bottomdock = con_new(NULL, NULL);
bottomdock->type = CT_DOCKAREA; bottomdock->type = CT_DOCKAREA;
bottomdock->layout = L_DOCKAREA; bottomdock->layout = L_DOCKAREA;
bottomdock->orientation = VERT;
/* this container swallows dock clients */ /* this container swallows dock clients */
match = scalloc(sizeof(Match)); match = scalloc(sizeof(Match));
match_init(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) if (con_num_children(workspace) > 1)
continue; continue;
workspace->orientation = (output->rect.height > output->rect.width) ? VERT : HORIZ; workspace->layout = (output->rect.height > output->rect.width) ? L_SPLITV : L_SPLITH;
DLOG("Setting workspace [%d,%s]'s orientation to %d.\n", workspace->num, workspace->name, workspace->orientation); DLOG("Setting workspace [%d,%s]'s layout to %d.\n", workspace->num, workspace->name, workspace->layout);
if ((child = TAILQ_FIRST(&(workspace->nodes_head)))) { if ((child = TAILQ_FIRST(&(workspace->nodes_head)))) {
child->orientation = workspace->orientation; child->layout = workspace->layout;
DLOG("Setting child [%d,%s]'s orientation to %d.\n", child->num, child->name, child->orientation); 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) { void render_con(Con *con, bool render_fullscreen) {
int children = con_num_children(con); 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, (render_fullscreen ? "fullscreen " : ""), con, con->name, con->layout,
children, con->orientation); children);
/* Copy container rect, subtract container border */ /* Copy container rect, subtract container border */
/* This is the actually usable space inside this container for clients */ /* 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 */ /* precalculate the sizes to be able to correct rounding errors */
int sizes[children]; 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)); assert(!TAILQ_EMPTY(&con->nodes_head));
Con *child; Con *child;
int i = 0, assigned = 0; 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) { TAILQ_FOREACH(child, &(con->nodes_head), nodes) {
double percentage = child->percent > 0.0 ? child->percent : 1.0 / children; double percentage = child->percent > 0.0 ? child->percent : 1.0 / children;
assigned += sizes[i++] = percentage * total; assigned += sizes[i++] = percentage * total;
@ -289,8 +289,8 @@ void render_con(Con *con, bool render_fullscreen) {
assert(children > 0); assert(children > 0);
/* default layout */ /* default layout */
if (con->layout == L_DEFAULT) { if (con->layout == L_SPLITH || con->layout == L_SPLITV) {
if (con->orientation == HORIZ) { if (con->layout == L_SPLITH) {
child->rect.x = x; child->rect.x = x;
child->rect.y = y; child->rect.y = y;
child->rect.width = sizes[i]; child->rect.width = sizes[i];

View File

@ -39,6 +39,7 @@ static Con *_create___i3(void) {
content->type = CT_CON; content->type = CT_CON;
FREE(content->name); FREE(content->name);
content->name = sstrdup("content"); content->name = sstrdup("content");
content->layout = L_SPLITH;
x_set_name(content, "[i3 con] content __i3"); x_set_name(content, "[i3 con] content __i3");
con_attach(content, __i3, false); con_attach(content, __i3, false);
@ -48,6 +49,7 @@ static Con *_create___i3(void) {
ws->type = CT_WORKSPACE; ws->type = CT_WORKSPACE;
ws->num = -1; ws->num = -1;
ws->name = sstrdup("__i3_scratch"); ws->name = sstrdup("__i3_scratch");
ws->layout = L_SPLITH;
con_attach(ws, content, false); con_attach(ws, content, false);
x_set_name(ws, "[i3 con] workspace __i3_scratch"); x_set_name(ws, "[i3 con] workspace __i3_scratch");
ws->fullscreen_mode = CF_OUTPUT; ws->fullscreen_mode = CF_OUTPUT;
@ -112,6 +114,7 @@ void tree_init(xcb_get_geometry_reply_t *geometry) {
FREE(croot->name); FREE(croot->name);
croot->name = "root"; croot->name = "root";
croot->type = CT_ROOT; croot->type = CT_ROOT;
croot->layout = L_SPLITH;
croot->rect = (Rect){ croot->rect = (Rect){
geometry->x, geometry->x,
geometry->y, geometry->y,
@ -151,6 +154,7 @@ Con *tree_open_con(Con *con, i3Window *window) {
/* 3. create the container and attach it to its parent */ /* 3. create the container and attach it to its parent */
Con *new = con_new(con, window); Con *new = con_new(con, window);
new->layout = L_SPLITH;
/* 4: re-calculate child->percent for each child */ /* 4: re-calculate child->percent for each child */
con_fix_percent(con); 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 */ /* for a workspace, we just need to change orientation */
if (con->type == CT_WORKSPACE) { if (con->type == CT_WORKSPACE) {
DLOG("Workspace, simply changing orientation to %d\n", orientation); DLOG("Workspace, simply changing orientation to %d\n", orientation);
con->orientation = orientation; con->layout = (orientation == HORIZ) ? L_SPLITH : L_SPLITV;
return; 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 * child (its split functionality is unused so far), we just change the
* orientation (more intuitive than splitting again) */ * orientation (more intuitive than splitting again) */
if (con_num_children(parent) == 1 && if (con_num_children(parent) == 1 &&
parent->layout == L_DEFAULT) { (parent->layout == L_SPLITH ||
parent->orientation = orientation; parent->layout == L_SPLITV)) {
parent->layout = (orientation == HORIZ) ? L_SPLITH : L_SPLITV;
DLOG("Just changing orientation of existing container\n"); DLOG("Just changing orientation of existing container\n");
return; return;
} }
@ -364,7 +369,8 @@ void tree_split(Con *con, orientation_t orientation) {
TAILQ_REPLACE(&(parent->nodes_head), con, new, nodes); TAILQ_REPLACE(&(parent->nodes_head), con, new, nodes);
TAILQ_REPLACE(&(parent->focus_head), con, new, focused); TAILQ_REPLACE(&(parent->focus_head), con, new, focused);
new->parent = parent; new->parent = parent;
new->orientation = orientation; new->layout = (orientation == HORIZ) ? L_SPLITH : L_SPLITV;
new->split = true;
/* 3: swap 'percent' (resize factor) */ /* 3: swap 'percent' (resize factor) */
new->percent = con->percent; 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); DLOG("Checking if I can flatten con = %p / %s\n", con, con->name);
/* We only consider normal containers without windows */ /* 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; goto recurse;
/* Ensure it got only one child */ /* Ensure it got only one child */
@ -602,12 +610,14 @@ void tree_flatten(Con *con) {
if (child == NULL || TAILQ_NEXT(child, nodes) != NULL) if (child == NULL || TAILQ_NEXT(child, nodes) != NULL)
goto recurse; 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 child must have a different orientation than the con but the same as
* the cons parent to be redundant */ * the cons parent to be redundant */
if (con->orientation == NO_ORIENTATION || if (con->split ||
child->orientation == NO_ORIENTATION || child->split ||
con->orientation == child->orientation || con_orientation(con) == con_orientation(child) ||
child->orientation != parent->orientation) con_orientation(child) != con_orientation(parent))
goto recurse; goto recurse;
DLOG("Alright, I have to flatten this situation now. Stay calm.\n"); DLOG("Alright, I have to flatten this situation now. Stay calm.\n");

View File

@ -14,6 +14,25 @@
* back-and-forth switching. */ * back-and-forth switching. */
static char *previous_workspace_name = NULL; 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), * Returns a pointer to the workspace with the given number (starting at 0),
* creating the workspace if necessary (by allocating the necessary amount of * 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; else workspace->num = parsed_num;
LOG("num = %d\n", workspace->num); LOG("num = %d\n", workspace->num);
/* If default_orientation is set to NO_ORIENTATION we workspace->parent = content;
* determine workspace orientation from workspace size. _workspace_apply_default_orientation(workspace);
* 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;
}
con_attach(workspace, content, false); con_attach(workspace, content, false);
@ -198,19 +209,12 @@ Con *create_workspace_on_output(Output *output, Con *content) {
ws->fullscreen_mode = CF_OUTPUT; ws->fullscreen_mode = CF_OUTPUT;
/* If default_orientation is set to NO_ORIENTATION we determine _workspace_apply_default_orientation(ws);
* 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;
}
return ws; return ws;
} }
/* /*
* Returns true if the workspace is currently visible. Especially important for * Returns true if the workspace is currently visible. Especially important for
* multi-monitor environments, as they can have multiple currenlty active * 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 * 'Forces' workspace orientation by moving all cons into a new split-con with
* the same orientation as the workspace and then changing the workspace * the same layout as the workspace and then changing the workspace layout.
* orientation.
* *
*/ */
void ws_force_orientation(Con *ws, orientation_t orientation) { 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); Con *split = con_new(NULL, NULL);
split->parent = ws; split->parent = ws;
/* 2: copy layout and orientation from workspace */ /* 2: copy layout from workspace */
split->layout = ws->layout; split->layout = ws->layout;
split->orientation = ws->orientation;
Con *old_focused = TAILQ_FIRST(&(ws->focus_head)); 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); con_attach(child, split, true);
} }
/* 4: switch workspace orientation */ /* 4: switch workspace layout */
ws->orientation = orientation; ws->layout = (orientation == HORIZ) ? L_SPLITH : L_SPLITV;
/* 5: attach the new split container to the workspace */ /* 5: attach the new split container to the workspace */
DLOG("Attaching new split to ws\n"); DLOG("Attaching new split to ws\n");
@ -745,19 +747,11 @@ Con *workspace_attach_to(Con *ws) {
/* 1: create a new split container */ /* 1: create a new split container */
Con *new = con_new(NULL, NULL); Con *new = con_new(NULL, NULL);
new->parent = ws; new->parent = ws;
new->split = true;
/* 2: set the requested layout on the split con */ /* 2: set the requested layout on the split con */
new->layout = config.default_layout; 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 */ /* 4: attach the new split container to the workspace */
DLOG("Attaching new split %p to workspace %p\n", new, ws); DLOG("Attaching new split %p to workspace %p\n", new, ws);
con_attach(new, ws, false); con_attach(new, ws, false);

View File

@ -39,14 +39,16 @@ my $expected = {
name => 'root', name => 'root',
orientation => $ignore, orientation => $ignore,
type => 0, type => 0,
split => JSON::XS::false,
id => $ignore, id => $ignore,
rect => $ignore, rect => $ignore,
window_rect => $ignore, window_rect => $ignore,
geometry => $ignore, geometry => $ignore,
swallows => $ignore, swallows => $ignore,
percent => undef, percent => undef,
layout => 'default', layout => 'splith',
floating => 'auto_off', floating => 'auto_off',
last_split_layout => 'splith',
scratchpad_state => 'none', scratchpad_state => 'none',
focus => $ignore, focus => $ignore,
focused => JSON::XS::false, focused => JSON::XS::false,

View File

@ -19,10 +19,10 @@ sub verify_split_layout {
$tmp = fresh_workspace; $tmp = fresh_workspace;
$ws = get_ws($tmp); $ws = get_ws($tmp);
is($ws->{orientation}, 'horizontal', 'orientation horizontal by default'); is($ws->{layout}, 'splith', 'orientation horizontal by default');
cmd 'split v'; cmd 'split v';
$ws = get_ws($tmp); $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';
cmd 'open'; cmd 'open';
@ -47,7 +47,7 @@ sub verify_split_layout {
is(@{$first->{nodes}}, 0, 'first container has no children'); is(@{$first->{nodes}}, 0, 'first container has no children');
isnt($second->{name}, $old_name, 'second container was replaced'); 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}}, 2, 'second container has 2 children');
is($second->{nodes}->[0]->{name}, $old_name, 'found old second container'); 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; $tmp = fresh_workspace;
$ws = get_ws($tmp); $ws = get_ws($tmp);
is($ws->{orientation}, 'horizontal', 'orientation horizontal by default'); is($ws->{layout}, 'splith', 'orientation horizontal by default');
cmd 'split v'; cmd 'split v';
$ws = get_ws($tmp); $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';
my @content = @{get_ws_content($tmp)}; my @content = @{get_ws_content($tmp)};

View File

@ -22,7 +22,7 @@ cmd 'move up';
cmd 'move right'; cmd 'move right';
my $ws = get_ws($tmp); 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'); is(@{$ws->{nodes}}, 3, 'all three windows on workspace level');
done_testing; 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;