Merge pull request #1024 from willakat/master

Add Awesome/Monad style automatic layouts to Sway
master
Drew DeVault 8 years ago committed by GitHub
commit 81102e8eac

@ -76,7 +76,7 @@ int list_seq_find(list_t *list, int compare(const void *item, const void *data),
return -1;
}
static void list_swap(list_t *list, int src, int dest) {
void list_swap(list_t *list, int src, int dest) {
void *tmp = list->items[src];
list->items[src] = list->items[dest];
list->items[dest] = tmp;

@ -0,0 +1,63 @@
#
# Replicate some of Awesome's default layout manipulation configuration for Sway
#
# Differences:
# - Layout switching doesn't use the spacebar (i.e. i3/Sway behavior to switch to/from floating windows)
# and uses the 'A' key instead (as in auto)
# - Resizing windows uses i3/Sway's more versatile Mod4+r
# - no tags
# - no Maximize/Minize, alternatives to Maximize would be to switch to Stacked/Tabbed layouts
# via Mod4+w or Mod4+s.
# - kill focused client is available on Mod4+Shift+q (instead of Mod4+Shift+c, which maps to Sway's
# config reload)
# - probably many more ...
# Awesome-style container traversal using Vim-like binding
set $next j
set $prev k
#
# Moving around:
#
# Move your focus around
bindsym $mod+$next focus next
bindsym $mod+$prev focus prev
# _move_ the focused window with the same, but add Shift
bindsym $mod+Shift+$next move next
bindsym $mod+Shift+$prev move prev
#
# Layout:
#
workspace_layout auto left
# This is usually bound to $mod+space, but this works well in practice by keeping
# all the layout switching keys grouped together.
bindsym $mod+a layout auto next
bindsym $mod+Shift+a layout auto prev
# Promote a child to master position in an auto layout
bindsym $mod+Control+Return move first
# Increase/decrease number of master elements in auto layout
bindsym $mod+Shift+h layout auto master inc 1
bindsym $mod+Shift+l layout auto master inc -1
# Increase/decrease number of slave element groups in auto layout
bindsym $mod+Control+h layout auto ncol inc 1
bindsym $mod+Control+l layout auto ncol inc -1
#
# Resizing containers:
# Again, not really the way Awesome works well, but in spirit with i3/Sway and it works well.
#
mode "resize" {
bindsym Left resize shrink width 20 px
bindsym Down resize grow height 20 px
bindsym Up resize shrink height 20 px
bindsym Right resize grow width 20 px
}
bindsym $mod+r mode "resize"
new_window pixel 1

@ -22,4 +22,6 @@ void list_qsort(list_t *list, int compare(const void *left, const void *right));
int list_seq_find(list_t *list, int compare(const void *item, const void *cmp_to), const void *cmp_to);
// stable sort since qsort is not guaranteed to be stable
void list_stable_sort(list_t *list, int compare(const void *a, const void *b));
// swap two elements in a list
void list_swap(list_t *list, int src, int dest);
#endif

@ -37,6 +37,16 @@ enum swayc_layouts {
L_STACKED,
L_TABBED,
L_FLOATING, /**< A psuedo-container, removed from the tree, to hold floating windows */
/* Awesome/Monad style auto layouts */
L_AUTO_LEFT,
L_AUTO_RIGHT,
L_AUTO_TOP,
L_AUTO_BOTTOM,
L_AUTO_FIRST = L_AUTO_LEFT,
L_AUTO_LAST = L_AUTO_BOTTOM,
// Keep last
L_LAYOUTS,
};
@ -144,6 +154,16 @@ struct sway_container {
struct wlc_geometry title_bar_geometry;
struct wlc_geometry actual_geometry;
int border_thickness;
/**
* Number of master views in auto layouts.
*/
size_t nb_master;
/**
* Number of slave groups (e.g. columns) in auto layouts.
*/
size_t nb_slave_groups;
};
enum visibility_mask {

@ -6,7 +6,10 @@ enum movement_direction {
MOVE_UP,
MOVE_DOWN,
MOVE_PARENT,
MOVE_CHILD
MOVE_CHILD,
MOVE_NEXT,
MOVE_PREV,
MOVE_FIRST
};
#include "container.h"
@ -40,4 +43,3 @@ extern bool suspend_workspace_cleanup;
bool move_focus(enum movement_direction direction);
#endif

@ -75,4 +75,11 @@ void swayc_log(log_importance_t verbosity, swayc_t *cont, const char* format, ..
*/
enum swayc_layouts default_layout(swayc_t *output);
bool is_auto_layout(enum swayc_layouts layout);
int auto_group_start_index(const swayc_t *container, int index);
int auto_group_end_index(const swayc_t *container, int index);
size_t auto_group_count(const swayc_t *container);
size_t auto_group_index(const swayc_t *container, int index);
bool auto_group_bounds(const swayc_t *container, size_t group_index, int *start, int *end);
#endif

@ -46,6 +46,10 @@ struct cmd_results *cmd_focus(int argc, char **argv) {
move_focus(MOVE_PARENT);
} else if (strcasecmp(argv[0], "child") == 0) {
move_focus(MOVE_CHILD);
} else if (strcasecmp(argv[0], "next") == 0) {
move_focus(MOVE_NEXT);
} else if (strcasecmp(argv[0], "prev") == 0) {
move_focus(MOVE_PREV);
} else if (strcasecmp(argv[0], "mode_toggle") == 0) {
int i;
swayc_t *workspace = swayc_active_workspace();

@ -3,6 +3,11 @@
#include "sway/container.h"
#include "sway/layout.h"
/**
* handle "layout auto" command group
*/
static struct cmd_results *cmd_layout_auto(swayc_t *container, int argc, char **argv);
struct cmd_results *cmd_layout(int argc, char **argv) {
struct cmd_results *error = NULL;
if (config->reading) return cmd_results_new(CMD_FAILURE, "layout", "Can't be used in config file.");
@ -49,11 +54,14 @@ struct cmd_results *cmd_layout(int argc, char **argv) {
} else if (strcasecmp(argv[0], "splitv") == 0) {
swayc_change_layout(parent, L_VERT);
} else if (strcasecmp(argv[0], "toggle") == 0 && argc == 2 && strcasecmp(argv[1], "split") == 0) {
if (parent->layout == L_HORIZ && (parent->workspace_layout == L_NONE || parent->workspace_layout == L_HORIZ)) {
if (parent->layout == L_HORIZ && (parent->workspace_layout == L_NONE
|| parent->workspace_layout == L_HORIZ)) {
swayc_change_layout(parent, L_VERT);
} else {
swayc_change_layout(parent, L_HORIZ);
}
} else if (strcasecmp(argv[0], "auto") == 0) {
return cmd_layout_auto(parent, argc, argv);
}
}
@ -64,3 +72,120 @@ struct cmd_results *cmd_layout(int argc, char **argv) {
return cmd_results_new(CMD_SUCCESS, NULL, NULL);
}
static struct cmd_results *cmd_layout_auto(swayc_t *container, int argc, char **argv) {
// called after checking that argv[0] is auto, so just continue parsing from there
struct cmd_results *error = NULL;
const char *cmd_name = "layout auto";
const char *set_inc_cmd_name = "layout auto [master|ncol] [set|inc]";
const char *err_msg = "Allowed arguments are <right|left|top|bottom|next|prev|master|ncol>";
bool need_layout_update = false;
enum swayc_layouts old_layout = container->layout;
enum swayc_layouts layout = old_layout;
if (strcasecmp(argv[1], "left") == 0) {
layout = L_AUTO_LEFT;
} else if (strcasecmp(argv[1], "right") == 0) {
layout = L_AUTO_RIGHT;
} else if (strcasecmp(argv[1], "top") == 0) {
layout = L_AUTO_TOP;
} else if (strcasecmp(argv[1], "bottom") == 0) {
layout = L_AUTO_BOTTOM;
} else if (strcasecmp(argv[1], "next") == 0) {
if (is_auto_layout(container->layout) && container->layout < L_AUTO_LAST) {
layout = container->layout + 1;
} else {
layout = L_AUTO_FIRST;
}
} else if (strcasecmp(argv[1], "prev") == 0) {
if (is_auto_layout(container->layout) && container->layout > L_AUTO_FIRST) {
layout = container->layout - 1;
} else {
layout = L_AUTO_LAST;
}
} else {
bool is_nmaster;
bool is_set;
if (strcasecmp(argv[1], "master") == 0) {
is_nmaster = true;
} else if (strcasecmp(argv[1], "ncol") == 0) {
is_nmaster = false;
} else {
return cmd_results_new(CMD_INVALID, cmd_name, "Invalid %s command. %s",
cmd_name, err_msg);
}
if ((error = checkarg(argc, "auto <master|ncol>", EXPECTED_EQUAL_TO, 4))) {
return error;
}
if (strcasecmp(argv[2], "set") == 0) {
is_set = true;
} else if (strcasecmp(argv[2], "inc") == 0) {
is_set = false;
} else {
return cmd_results_new(CMD_INVALID, set_inc_cmd_name, "Invalid %s command. %s, "
"Argument must be on of <set|inc>",
set_inc_cmd_name);
}
char *end;
int n = (int)strtol(argv[3], &end, 10);
if (*end) {
return cmd_results_new(CMD_INVALID, set_inc_cmd_name, "Invalid %s command "
"(argument must be an integer)", set_inc_cmd_name);
}
if (is_auto_layout(container->layout)) {
int inc = 0; /* difference between current master/ncol and requested value */
if (is_nmaster) {
if (is_set) {
if (n < 0) {
return cmd_results_new(CMD_INVALID, set_inc_cmd_name, "Invalid %s command "
"(master must be >= 0)", set_inc_cmd_name);
}
inc = n - (int)container->nb_master;
} else { /* inc command */
if ((int)container->nb_master + n >= 0) {
inc = n;
}
}
if (inc) {
for (int i = container->nb_master;
i >= 0 && i < container->children->length
&& i != (int)container->nb_master + inc;) {
((swayc_t *)container->children->items[i])->height = -1;
((swayc_t *)container->children->items[i])->width = -1;
i += inc > 0 ? 1 : -1;
}
container->nb_master += inc;
need_layout_update = true;
}
} else { /* ncol modification */
if (is_set) {
if (n <= 0) {
return cmd_results_new(CMD_INVALID, set_inc_cmd_name, "Invalid %s command "
"(ncol must be > 0)", set_inc_cmd_name);
}
inc = n - (int)container->nb_slave_groups;
} else { /* inc command */
if ((int)container->nb_slave_groups + n > 0) {
inc = n;
}
}
if (inc) {
container->nb_slave_groups += inc;
need_layout_update = true;
}
}
}
}
if (layout != old_layout) {
swayc_change_layout(container, layout);
update_layout_geometry(container, old_layout);
need_layout_update = true;
}
if (need_layout_update) {
update_geometry(container);
arrange_windows(container, container->width, container->height);
}
return cmd_results_new(CMD_SUCCESS, NULL, NULL);
}

@ -13,7 +13,7 @@ struct cmd_results *cmd_move(int argc, char **argv) {
if ((error = checkarg(argc, "move", EXPECTED_AT_LEAST, 1))) {
return error;
}
const char* expected_syntax = "Expected 'move <left|right|up|down>' or "
const char* expected_syntax = "Expected 'move <left|right|up|down|next|prev|first>' or "
"'move <container|window> to workspace <name>' or "
"'move <container|window|workspace> to output <name|direction>' or "
"'move position mouse'";
@ -27,6 +27,12 @@ struct cmd_results *cmd_move(int argc, char **argv) {
move_container(view, MOVE_UP);
} else if (strcasecmp(argv[0], "down") == 0) {
move_container(view, MOVE_DOWN);
} else if (strcasecmp(argv[0], "next") == 0) {
move_container(view, MOVE_NEXT);
} else if (strcasecmp(argv[0], "prev") == 0) {
move_container(view, MOVE_PREV);
} else if (strcasecmp(argv[0], "first") == 0) {
move_container(view, MOVE_FIRST);
} else if (strcasecmp(argv[0], "container") == 0 || strcasecmp(argv[0], "window") == 0) {
// "move container ...
if ((error = checkarg(argc, "move container/window", EXPECTED_AT_LEAST, 4))) {

@ -63,224 +63,135 @@ static bool resize_floating(int amount, bool use_width) {
}
static bool resize_tiled(int amount, bool use_width) {
swayc_t *parent = get_focused_view(swayc_active_workspace());
swayc_t *focused = parent;
swayc_t *sibling;
if (!parent) {
return true;
}
// Find the closest parent container which has siblings of the proper layout.
// Then apply the resize to all of them.
int i;
if (use_width) {
int lnumber = 0;
int rnumber = 0;
while (parent->parent) {
if (parent->parent->layout == L_HORIZ && parent->parent->children) {
for (i = 0; i < parent->parent->children->length; i++) {
sibling = parent->parent->children->items[i];
if (sibling->x != focused->x) {
if (sibling->x < parent->x) {
lnumber++;
} else if (sibling->x > parent->x) {
rnumber++;
}
}
}
if (rnumber || lnumber) {
break;
}
swayc_t *container = get_focused_view(swayc_active_workspace());
swayc_t *parent = container->parent;
int idx_focused = 0;
bool use_major = false;
size_t nb_before = 0;
size_t nb_after = 0;
// 1. Identify a container ancestor that will allow the focused child to grow in the requested
// direction.
while (container->parent) {
parent = container->parent;
if ((parent->children && parent->children->length > 1)
&& (is_auto_layout(parent->layout)
|| (use_width ? parent->layout == L_HORIZ : parent->layout == L_VERT))) {
// check if container has siblings that can provide/absorb the space needed for
// the resize operation.
use_major = use_width
? parent->layout == L_AUTO_LEFT || parent->layout == L_AUTO_RIGHT
: parent->layout == L_AUTO_TOP || parent->layout == L_AUTO_BOTTOM;
// Note: use_major will be false for L_HORIZ and L_VERT
idx_focused = index_child(container);
if (idx_focused < 0) {
sway_log(L_ERROR, "Something weird is happening, child container not "
"present in its parent's children list.");
continue;
}
parent = parent->parent;
}
if (parent == &root_container) {
return true;
}
sway_log(L_DEBUG, "Found the proper parent: %p. It has %d l conts, and %d r conts", parent->parent, lnumber, rnumber);
//TODO: Ensure rounding is done in such a way that there are NO pixel leaks
bool valid = true;
for (i = 0; i < parent->parent->children->length; i++) {
sibling = parent->parent->children->items[i];
if (sibling->x != focused->x) {
if (sibling->x < parent->x) {
double pixels = -1 * amount;
pixels /= lnumber;
if (rnumber) {
if ((sibling->width + pixels/2) < min_sane_w) {
valid = false;
break;
}
} else {
if ((sibling->width + pixels) < min_sane_w) {
valid = false;
break;
}
}
} else if (sibling->x > parent->x) {
double pixels = -1 * amount;
pixels /= rnumber;
if (lnumber) {
if ((sibling->width + pixels/2) < min_sane_w) {
valid = false;
break;
}
} else {
if ((sibling->width + pixels) < min_sane_w) {
valid = false;
break;
}
}
}
if (use_major) {
nb_before = auto_group_index(parent, idx_focused);
nb_after = auto_group_count(parent) - nb_before - 1;
} else {
double pixels = amount;
if (parent->width + pixels < min_sane_w) {
valid = false;
break;
}
nb_before = idx_focused - auto_group_start_index(parent, idx_focused);
nb_after = auto_group_end_index(parent, idx_focused) - idx_focused - 1;
sway_log(L_DEBUG, "+++ focused: %d, start: %d, end: %d, before: %d, after: %d",
idx_focused,
(int)auto_group_start_index(parent, idx_focused),
(int)auto_group_end_index(parent, idx_focused),
(int)nb_before, (int)nb_after);
}
}
if (valid) {
for (i = 0; i < parent->parent->children->length; i++) {
sibling = parent->parent->children->items[i];
if (sibling->x != focused->x) {
if (sibling->x < parent->x) {
double pixels = -1 * amount;
pixels /= lnumber;
if (rnumber) {
recursive_resize(sibling, pixels/2, WLC_RESIZE_EDGE_RIGHT);
} else {
recursive_resize(sibling, pixels, WLC_RESIZE_EDGE_RIGHT);
}
} else if (sibling->x > parent->x) {
double pixels = -1 * amount;
pixels /= rnumber;
if (lnumber) {
recursive_resize(sibling, pixels/2, WLC_RESIZE_EDGE_LEFT);
} else {
recursive_resize(sibling, pixels, WLC_RESIZE_EDGE_LEFT);
}
}
} else {
if (rnumber != 0 && lnumber != 0) {
double pixels = amount;
pixels /= 2;
recursive_resize(parent, pixels, WLC_RESIZE_EDGE_LEFT);
recursive_resize(parent, pixels, WLC_RESIZE_EDGE_RIGHT);
} else if (rnumber) {
recursive_resize(parent, amount, WLC_RESIZE_EDGE_RIGHT);
} else if (lnumber) {
recursive_resize(parent, amount, WLC_RESIZE_EDGE_LEFT);
}
}
if (nb_before || nb_after) {
break;
}
// Recursive resize does not handle positions, let arrange_windows
// take care of that.
arrange_windows(swayc_active_workspace(), -1, -1);
}
container = parent; /* continue up the tree to the next ancestor */
}
if (parent == &root_container) {
return true;
} else {
int tnumber = 0;
int bnumber = 0;
while (parent->parent) {
if (parent->parent->layout == L_VERT) {
for (i = 0; i < parent->parent->children->length; i++) {
sibling = parent->parent->children->items[i];
if (sibling->y != focused->y) {
if (sibling->y < parent->y) {
bnumber++;
} else if (sibling->y > parent->y) {
tnumber++;
}
}
}
if (bnumber || tnumber) {
break;
}
}
sway_log(L_DEBUG, "Found the proper parent: %p. It has %zu before conts, "
"and %zu after conts", parent, nb_before, nb_after);
// 2. Ensure that the resize operation will not make one of the resized containers drop
// below the "sane" size threshold.
bool valid = true;
swayc_t *focused = parent->children->items[idx_focused];
int start = use_major ? 0 : auto_group_start_index(parent, idx_focused);
int end = use_major ? parent->children->length : auto_group_end_index(parent, idx_focused);
sway_log(L_DEBUG, "Check children of container %p [%d,%d[", container, start, end);
for (int i = start; i < end; ) {
swayc_t *sibling = parent->children->items[i];
double pixels = amount;
bool is_before = use_width ? sibling->x < focused->x : sibling->y < focused->y;
bool is_after = use_width ? sibling->x > focused->x : sibling->y > focused->y;
if (is_before || is_after) {
pixels = -pixels;
pixels /= is_before ? nb_before : nb_after;
if (nb_after != 0 && nb_before != 0) {
pixels /= 2;
}
parent = parent->parent;
}
if (parent->parent == NULL || parent->parent->children == NULL) {
return true;
sway_log(L_DEBUG, "Check container %p: width %g vs %d, height %g vs %d", sibling, sibling->width + pixels, min_sane_w, sibling->height + pixels, min_sane_h);
if (use_width ?
sibling->width + pixels < min_sane_w :
sibling->height + pixels < min_sane_h) {
valid = false;
sway_log(L_DEBUG, "Container size no longer sane");
break;
}
sway_log(L_DEBUG, "Found the proper parent: %p. It has %d b conts, and %d t conts", parent->parent, bnumber, tnumber);
//TODO: Ensure rounding is done in such a way that there are NO pixel leaks
bool valid = true;
for (i = 0; i < parent->parent->children->length; i++) {
sibling = parent->parent->children->items[i];
if (sibling->y != focused->y) {
if (sibling->y < parent->y) {
double pixels = -1 * amount;
pixels /= bnumber;
if (tnumber) {
if ((sibling->height + pixels/2) < min_sane_h) {
valid = false;
break;
}
} else {
if ((sibling->height + pixels) < min_sane_h) {
valid = false;
break;
}
}
} else if (sibling->y > parent->y) {
double pixels = -1 * amount;
pixels /= tnumber;
if (bnumber) {
if ((sibling->height + pixels/2) < min_sane_h) {
valid = false;
break;
}
} else {
if ((sibling->height + pixels) < min_sane_h) {
valid = false;
break;
}
i = use_major ? auto_group_end_index(parent, i) : (i + 1);
sway_log(L_DEBUG, "+++++ check %i", i);
}
// 3. Apply the size change
if (valid) {
for (int i = start; i < end; ) {
int next_i = use_major ? auto_group_end_index(parent, i) : (i + 1);
swayc_t *sibling = parent->children->items[i];
double pixels = amount;
bool is_before = use_width ? sibling->x < focused->x : sibling->y < focused->y;
bool is_after = use_width ? sibling->x > focused->x : sibling->y > focused->y;
if (is_before || is_after) {
pixels = -pixels;
pixels /= is_before ? nb_before : nb_after;
if (nb_after != 0 && nb_before != 0) {
pixels /= 2;
}
sway_log(L_DEBUG, "%p: %s", sibling, is_before ? "before" : "after");
if (use_major) {
for (int j = i; j < next_i; ++j) {
recursive_resize(parent->children->items[j], pixels,
use_width ?
(is_before ? WLC_RESIZE_EDGE_RIGHT : WLC_RESIZE_EDGE_LEFT) :
(is_before ? WLC_RESIZE_EDGE_BOTTOM : WLC_RESIZE_EDGE_TOP));
}
} else {
recursive_resize(sibling, pixels,
use_width ?
(is_before ? WLC_RESIZE_EDGE_RIGHT : WLC_RESIZE_EDGE_LEFT) :
(is_before ? WLC_RESIZE_EDGE_BOTTOM : WLC_RESIZE_EDGE_TOP));
}
} else {
double pixels = amount;
if (parent->height + pixels < min_sane_h) {
valid = false;
break;
}
}
}
if (valid) {
for (i = 0; i < parent->parent->children->length; i++) {
sibling = parent->parent->children->items[i];
if (sibling->y != focused->y) {
if (sibling->y < parent->y) {
double pixels = -1 * amount;
pixels /= bnumber;
if (tnumber) {
recursive_resize(sibling, pixels/2, WLC_RESIZE_EDGE_BOTTOM);
} else {
recursive_resize(sibling, pixels, WLC_RESIZE_EDGE_BOTTOM);
}
} else if (sibling->x > parent->x) {
double pixels = -1 * amount;
pixels /= tnumber;
if (bnumber) {
recursive_resize(sibling, pixels/2, WLC_RESIZE_EDGE_TOP);
} else {
recursive_resize(sibling, pixels, WLC_RESIZE_EDGE_TOP);
}
if (use_major) {
for (int j = i; j < next_i; ++j) {
recursive_resize(parent->children->items[j], pixels / 2,
use_width ? WLC_RESIZE_EDGE_LEFT : WLC_RESIZE_EDGE_TOP);
recursive_resize(parent->children->items[j], pixels / 2,
use_width ? WLC_RESIZE_EDGE_RIGHT : WLC_RESIZE_EDGE_BOTTOM);
}
} else {
if (bnumber != 0 && tnumber != 0) {
double pixels = amount/2;
recursive_resize(parent, pixels, WLC_RESIZE_EDGE_TOP);
recursive_resize(parent, pixels, WLC_RESIZE_EDGE_BOTTOM);
} else if (tnumber) {
recursive_resize(parent, amount, WLC_RESIZE_EDGE_TOP);
} else if (bnumber) {
recursive_resize(parent, amount, WLC_RESIZE_EDGE_BOTTOM);
}
recursive_resize(sibling, pixels / 2,
use_width ? WLC_RESIZE_EDGE_LEFT : WLC_RESIZE_EDGE_TOP);
recursive_resize(sibling, pixels / 2,
use_width ? WLC_RESIZE_EDGE_RIGHT : WLC_RESIZE_EDGE_BOTTOM);
}
}
arrange_windows(swayc_active_workspace(), -1, -1);
i = next_i;
}
return true;
// Recursive resize does not handle positions, let arrange_windows
// take care of that.
arrange_windows(swayc_active_workspace(), -1, -1);
}
return true;
}

@ -3,7 +3,7 @@
struct cmd_results *cmd_workspace_layout(int argc, char **argv) {
struct cmd_results *error = NULL;
if ((error = checkarg(argc, "workspace_layout", EXPECTED_EQUAL_TO, 1))) {
if ((error = checkarg(argc, "workspace_layout", EXPECTED_AT_LEAST, 1))) {
return error;
}
@ -13,8 +13,27 @@ struct cmd_results *cmd_workspace_layout(int argc, char **argv) {
config->default_layout = L_STACKED;
} else if (strcasecmp(argv[0], "tabbed") == 0) {
config->default_layout = L_TABBED;
} else if (strcasecmp(argv[0], "auto") == 0) {
if (argc == 1) {
config->default_layout = L_AUTO_FIRST;
} else {
if ((error = checkarg(argc, "workspace_layout auto", EXPECTED_EQUAL_TO, 2))) {
return error;
}
if (strcasecmp(argv[0], "left") == 0) {
config->default_layout = L_AUTO_LEFT;
} else if (strcasecmp(argv[0], "right") == 0) {
config->default_layout = L_AUTO_RIGHT;
} else if (strcasecmp(argv[0], "top") == 0) {
config->default_layout = L_AUTO_TOP;
} else if (strcasecmp(argv[0], "bottom") == 0) {
config->default_layout = L_AUTO_BOTTOM;
} else {
return cmd_results_new(CMD_INVALID, "workspace_layout auto", "Expected 'workspace_layout auto <left|right|top|bottom>'");
}
}
} else {
return cmd_results_new(CMD_INVALID, "workspace_layout", "Expected 'workspace_layout <default|stacking|tabbed>'");
return cmd_results_new(CMD_INVALID, "workspace_layout", "Expected 'workspace_layout <default|stacking|tabbed|auto|auto left|auto right|auto top|auto bottom>'");
}
return cmd_results_new(CMD_SUCCESS, NULL, NULL);
}

@ -32,6 +32,8 @@ static swayc_t *new_swayc(enum swayc_types type) {
c->layout = L_NONE;
c->workspace_layout = L_NONE;
c->type = type;
c->nb_master = 1;
c->nb_slave_groups = 1;
if (type != C_VIEW) {
c->children = create_list();
}
@ -960,11 +962,29 @@ swayc_t *swayc_tabbed_stacked_parent(swayc_t *con) {
}
swayc_t *swayc_change_layout(swayc_t *container, enum swayc_layouts layout) {
// if layout change modifies the auto layout's major axis, swap width and height
// to preserve current ratios.
if (is_auto_layout(layout) && is_auto_layout(container->layout)) {
enum swayc_layouts prev_major =
container->layout == L_AUTO_LEFT || container->layout == L_AUTO_RIGHT
? L_HORIZ : L_VERT;
enum swayc_layouts new_major =
layout == L_AUTO_LEFT || layout == L_AUTO_RIGHT
? L_HORIZ : L_VERT;
if (new_major != prev_major) {
for (int i = 0; i < container->children->length; ++i) {
swayc_t *child = container->children->items[i];
double h = child->height;
child->height = child->width;
child->width = h;
}
}
}
if (container->type == C_WORKSPACE) {
container->workspace_layout = layout;
if (layout == L_HORIZ || layout == L_VERT) {
container->layout = layout;
}
if (layout == L_HORIZ || layout == L_VERT || is_auto_layout(layout)) {
container->layout = layout;
}
} else {
container->layout = layout;
}

@ -38,6 +38,10 @@ static void container_log(const swayc_t *c, int depth) {
c->layout == L_STACKED ? "Stack":
c->layout == L_TABBED ? "Tab":
c->layout == L_FLOATING ? "Float":
c->layout == L_AUTO_LEFT ? "A_lft":
c->layout == L_AUTO_RIGHT ? "A_rgt":
c->layout == L_AUTO_TOP ? "A_top":
c->layout == L_AUTO_BOTTOM ? "A_bot":
"Unknown");
fprintf(stderr, "w:%4.f|h:%4.f|", c->width, c->height);
fprintf(stderr, "x:%4.f|y:%4.f|", c->x, c->y);

@ -71,6 +71,14 @@ void add_child(swayc_t *parent, swayc_t *child) {
}
}
static double *get_height(swayc_t *cont) {
return &cont->height;
}
static double *get_width(swayc_t *cont) {
return &cont->width;
}
void insert_child(swayc_t *parent, swayc_t *child, int index) {
if (index > parent->children->length) {
index = parent->children->length;
@ -86,7 +94,44 @@ void insert_child(swayc_t *parent, swayc_t *child, int index) {
if (parent->type == C_WORKSPACE && child->type == C_VIEW && (parent->workspace_layout == L_TABBED || parent->workspace_layout == L_STACKED)) {
child = new_container(child, parent->workspace_layout);
}
if (is_auto_layout(parent->layout)) {
/* go through each group, adjust the size of the first child of each group */
double *(*get_maj_dim)(swayc_t *cont);
double *(*get_min_dim)(swayc_t *cont);
if (parent->layout == L_AUTO_LEFT || parent->layout == L_AUTO_RIGHT) {
get_maj_dim = get_width;
get_min_dim = get_height;
} else {
get_maj_dim = get_height;
get_min_dim = get_width;
}
for (int i = index; i < parent->children->length;) {
int start = auto_group_start_index(parent, i);
int end = auto_group_end_index(parent, i);
swayc_t *first = parent->children->items[start];
if (start + 1 < parent->children->length) {
/* preserve the group's dimension along major axis */
*get_maj_dim(first) = *get_maj_dim(parent->children->items[start + 1]);
} else {
/* new group, let the apply_layout handle it */
first->height = first->width = 0;
break;
}
double remaining = *get_min_dim(parent);
for (int j = end - 1; j > start; --j) {
swayc_t *sibling = parent->children->items[j];
if (sibling == child) {
/* the inserted child won't yet have its minor
dimension set */
remaining -= *get_min_dim(parent) / (end - start);
} else {
remaining -= *get_min_dim(sibling);
}
}
*get_min_dim(first) = remaining;
i = end;
}
}
}
void add_floating(swayc_t *ws, swayc_t *child) {
@ -118,7 +163,11 @@ swayc_t *add_sibling(swayc_t *fixed, swayc_t *active) {
list_add(parent->floating, active);
} else {
int i = index_child(fixed);
list_insert(parent->children, i + 1, active);
if (is_auto_layout(parent->layout)) {
list_add(parent->children, active);
} else {
list_insert(parent->children, i + 1, active);
}
}
}
active->parent = parent;
@ -181,6 +230,42 @@ swayc_t *remove_child(swayc_t *child) {
break;
}
}
if (is_auto_layout(parent->layout) && parent->children->length) {
/* go through each group, adjust the size of the last child of each group */
double *(*get_maj_dim)(swayc_t *cont);
double *(*get_min_dim)(swayc_t *cont);
if (parent->layout == L_AUTO_LEFT || parent->layout == L_AUTO_RIGHT) {
get_maj_dim = get_width;
get_min_dim = get_height;
} else {
get_maj_dim = get_height;
get_min_dim = get_width;
}
for (int j = parent->children->length - 1; j >= i;) {
int start = auto_group_start_index(parent, j);
int end = auto_group_end_index(parent, j);
swayc_t *first = parent->children->items[start];
if (i == start) {
/* removed element was first child in the current group,
use its size along the major axis */
*get_maj_dim(first) = *get_maj_dim(child);
} else if (start > i) {
/* preserve the group's dimension along major axis */
*get_maj_dim(first) = *get_maj_dim(parent->children->items[start - 1]);
}
if (end != parent->children->length) {
double remaining = *get_min_dim(parent);
for (int k = start; k < end - 1; ++k) {
swayc_t *sibling = parent->children->items[k];
remaining -= *get_min_dim(sibling);
}
/* last element of the group gets remaining size, elements
that don't change groups keep their ratio */
*get_min_dim((swayc_t *) parent->children->items[end - 1]) = remaining;
} /* else last group, let apply_layout handle it */
j = start - 1;
}
}
}
// Set focused to new container
if (parent->focused == child) {
@ -246,20 +331,51 @@ void swap_geometry(swayc_t *a, swayc_t *b) {
b->height = h;
}
static void swap_children(swayc_t *container, int a, int b) {
if (a >= 0 && b >= 0 && a < container->children->length
&& b < container->children->length
&& a != b) {
swayc_t *pa = (swayc_t *)container->children->items[a];
swayc_t *pb = (swayc_t *)container->children->items[b];
container->children->items[a] = container->children->items[b];
container->children->items[b] = pa;
if (is_auto_layout(container->layout)) {
size_t ga = auto_group_index(container, a);
size_t gb = auto_group_index(container, b);
if (ga != gb) {
swap_geometry(pa, pb);
}
}
}
}
void move_container(swayc_t *container, enum movement_direction dir) {
enum swayc_layouts layout;
if (container->is_floating
|| (container->type != C_VIEW && container->type != C_CONTAINER)) {
enum swayc_layouts layout = L_NONE;
swayc_t *parent = container->parent;
if (container->is_floating || (container->type != C_VIEW && container->type != C_CONTAINER)) {
return;
}
if (dir == MOVE_UP || dir == MOVE_DOWN) {
layout = L_VERT;
} else if (dir == MOVE_LEFT || dir == MOVE_RIGHT) {
layout = L_HORIZ;
} else {
} else if (dir == MOVE_FIRST) {
// swap first child in auto layout with currently focused child
if (is_auto_layout(parent->layout)) {
int focused_idx = index_child(container);
swayc_t *first = parent->children->items[0];
if (focused_idx > 0) {
list_swap(parent->children, 0, focused_idx);
swap_geometry(first, container);
}
arrange_windows(parent->parent, -1, -1);
ipc_event_window(container, "move");
set_focused_container_for(parent->parent, container);
}
return;
} else if (! (dir == MOVE_NEXT || dir == MOVE_PREV)) {
return;
}
swayc_t *parent = container->parent;
swayc_t *child = container;
bool ascended = false;
@ -279,19 +395,30 @@ void move_container(swayc_t *container, enum movement_direction dir) {
sway_log(L_DEBUG, "container:%p, parent:%p, child %p,",
container,parent,child);
if (parent->layout == layout
|| (layout == L_NONE && parent->type == C_CONTAINER) /* accept any layout for next/prev direction */
|| (parent->layout == L_TABBED && layout == L_HORIZ)
|| (parent->layout == L_STACKED && layout == L_VERT)) {
|| (parent->layout == L_STACKED && layout == L_VERT)
|| is_auto_layout(parent->layout)) {
int diff;
// If it has ascended (parent has moved up), no container is removed
// so insert it at index, or index+1.
// if it has not, the moved container is removed, so it needs to be
// inserted at index-1, or index+1
if (ascended) {
diff = dir == MOVE_LEFT || dir == MOVE_UP ? 0 : 1;
diff = dir == MOVE_LEFT || dir == MOVE_UP || dir == MOVE_PREV ? 0 : 1;
} else {
diff = dir == MOVE_LEFT || dir == MOVE_UP ? -1 : 1;
diff = dir == MOVE_LEFT || dir == MOVE_UP || dir == MOVE_PREV ? -1 : 1;
}
int idx = index_child(child);
int desired = idx + diff;
if (dir == MOVE_NEXT || dir == MOVE_PREV) {
// Next/Prev always wrap.
if (desired < 0) {
desired += parent->children->length;
} else if (desired >= parent->children->length) {
desired = 0;
}
}
int desired = index_child(child) + diff;
// when it has ascended, legal insertion position is 0:len
// when it has not, legal insertion position is 0:len-1
if (desired >= 0 && desired - ascended < parent->children->length) {
@ -304,7 +431,8 @@ void move_container(swayc_t *container, enum movement_direction dir) {
// insert it next to focused container
if (parent->layout == layout
|| (parent->layout == L_TABBED && layout == L_HORIZ)
|| (parent->layout == L_STACKED && layout == L_VERT)) {
|| (parent->layout == L_STACKED && layout == L_VERT)
|| is_auto_layout(parent->layout)) {
desired = (diff < 0) * parent->children->length;
} else {
desired = index_child(child->focused) + 1;
@ -313,15 +441,19 @@ void move_container(swayc_t *container, enum movement_direction dir) {
container->width = container->height = 0;
}
}
swayc_t *old_parent = remove_child(container);
insert_child(parent, container, desired);
destroy_container(old_parent);
sway_log(L_DEBUG,"Moving to %p %d", parent, desired);
if (container->parent == parent) {
swap_children(parent, idx, desired);
} else {
swayc_t *old_parent = remove_child(container);
insert_child(parent, container, desired);
destroy_container(old_parent);
sway_log(L_DEBUG,"Moving to %p %d", parent, desired);
}
break;
}
}
// Change parent layout if we need to
if (parent->children->length == 1 && parent->layout != layout) {
if (parent->children->length == 1 && parent->layout != layout && layout != L_NONE) {
/* swayc_change_layout(parent, layout); */
parent->layout = layout;
continue;
@ -776,6 +908,26 @@ void update_geometry(swayc_t *container) {
}
}
/**
* Layout application prototypes
*/
static void apply_horiz_layout(swayc_t *container, const double x,
const double y, const double width,
const double height, const int start,
const int end);
static void apply_vert_layout(swayc_t *container, const double x,
const double y, const double width,
const double height, const int start,
const int end);
static void apply_tabbed_or_stacked_layout(swayc_t *container, double x,
double y, double width,
double height);
static void apply_auto_layout(swayc_t *container, const double x, const double y,
const double width, const double height,
enum swayc_layouts group_layout,
bool master_first);
static void arrange_windows_r(swayc_t *container, double width, double height) {
int i;
if (width == -1 || height == -1) {
@ -783,14 +935,15 @@ static void arrange_windows_r(swayc_t *container, double width, double height) {
width = container->width;
height = container->height;
}
// pixels are indivisable. if we don't round the pixels, then the view
// pixels are indivisible. if we don't round the pixels, then the view
// calculations will be off (e.g. 50.5 + 50.5 = 101, but in reality it's
// 50 + 50 = 100). doing it here cascades properly to all width/height/x/y.
width = floor(width);
height = floor(height);
sway_log(L_DEBUG, "Arranging layout for %p %s %fx%f+%f,%f", container,
container->name, container->width, container->height, container->x, container->y);
container->name, container->width, container->height, container->x,
container->y);
double x = 0, y = 0;
switch (container->type) {
@ -902,135 +1055,298 @@ static void arrange_windows_r(swayc_t *container, double width, double height) {
break;
}
double scale = 0;
switch (container->layout) {
case L_HORIZ:
default:
// Calculate total width
for (i = 0; i < container->children->length; ++i) {
double *old_width = &((swayc_t *)container->children->items[i])->width;
if (*old_width <= 0) {
if (container->children->length > 1) {
*old_width = width / (container->children->length - 1);
} else {
*old_width = width;
}
}
scale += *old_width;
}
// Resize windows
if (scale > 0.1) {
scale = width / scale;
sway_log(L_DEBUG, "Arranging %p horizontally", container);
swayc_t *focused = NULL;
for (i = 0; i < container->children->length; ++i) {
swayc_t *child = container->children->items[i];
sway_log(L_DEBUG, "Calculating arrangement for %p:%d (will scale %f by %f)", child, child->type, width, scale);
child->x = x;
child->y = y;
if (child == container->focused) {
focused = child;
}
apply_horiz_layout(container, x, y, width, height, 0,
container->children->length);
break;
case L_VERT:
apply_vert_layout(container, x, y, width, height, 0,
container->children->length);
break;
case L_TABBED:
case L_STACKED:
apply_tabbed_or_stacked_layout(container, x, y, width, height);
break;
case L_AUTO_LEFT:
apply_auto_layout(container, x, y, width, height, L_VERT, true);
break;
case L_AUTO_RIGHT:
apply_auto_layout(container, x, y, width, height, L_VERT, false);
break;
case L_AUTO_TOP:
apply_auto_layout(container, x, y, width, height, L_HORIZ, true);
break;
case L_AUTO_BOTTOM:
apply_auto_layout(container, x, y, width, height, L_HORIZ, false);
break;
}
if (i == container->children->length - 1) {
double remaining_width = container->x + width - x;
arrange_windows_r(child, remaining_width, height);
} else {
arrange_windows_r(child, child->width * scale, height);
// Arrage floating layouts for workspaces last
if (container->type == C_WORKSPACE) {
for (int i = 0; i < container->floating->length; ++i) {
swayc_t *view = container->floating->items[i];
if (view->type == C_VIEW) {
update_geometry(view);
sway_log(L_DEBUG, "Set floating view to %.f x %.f @ %.f, %.f",
view->width, view->height, view->x, view->y);
if (swayc_is_fullscreen(view)) {
wlc_view_bring_to_front(view->handle);
} else if (!container->focused ||
!swayc_is_fullscreen(container->focused)) {
wlc_view_bring_to_front(view->handle);
}
x += child->width;
}
}
}
}
// update focused view border last because it may
// depend on the title bar geometry of its siblings.
if (focused && container->children->length > 1) {
update_container_border(focused);
void apply_horiz_layout(swayc_t *container, const double x, const double y,
const double width, const double height,
const int start, const int end) {
double scale = 0;
// Calculate total width
for (int i = start; i < end; ++i) {
double *old_width = &((swayc_t *)container->children->items[i])->width;
if (*old_width <= 0) {
if (end - start > 1) {
*old_width = width / (end - start - 1);
} else {
*old_width = width;
}
}
break;
case L_VERT:
// Calculate total height
for (i = 0; i < container->children->length; ++i) {
double *old_height = &((swayc_t *)container->children->items[i])->height;
if (*old_height <= 0) {
if (container->children->length > 1) {
*old_height = height / (container->children->length - 1);
} else {
*old_height = height;
}
scale += *old_width;
}
scale = width / scale;
// Resize windows
double child_x = x;
if (scale > 0.1) {
sway_log(L_DEBUG, "Arranging %p horizontally", container);
swayc_t *focused = NULL;
for (int i = start; i < end; ++i) {
swayc_t *child = container->children->items[i];
sway_log(L_DEBUG,
"Calculating arrangement for %p:%d (will scale %f by %f)", child,
child->type, width, scale);
child->x = child_x;
child->y = y;
if (child == container->focused) {
focused = child;
}
scale += *old_height;
}
// Resize
if (scale > 0.1) {
scale = height / scale;
sway_log(L_DEBUG, "Arranging %p vertically", container);
swayc_t *focused = NULL;
for (i = 0; i < container->children->length; ++i) {
swayc_t *child = container->children->items[i];
sway_log(L_DEBUG, "Calculating arrangement for %p:%d (will scale %f by %f)", child, child->type, height, scale);
child->x = x;
child->y = y;
if (child == container->focused) {
focused = child;
}
if (i == container->children->length - 1) {
double remaining_height = container->y + height - y;
arrange_windows_r(child, width, remaining_height);
} else {
arrange_windows_r(child, width, child->height * scale);
}
y += child->height;
if (i == end - 1) {
double remaining_width = x + width - child_x;
arrange_windows_r(child, remaining_width, height);
} else {
arrange_windows_r(child, child->width * scale, height);
}
child_x += child->width;
}
// update focused view border last because it may
// depend on the title bar geometry of its siblings.
if (focused && container->children->length > 1) {
update_container_border(focused);
}
}
}
// update focused view border last because it may
// depend on the title bar geometry of its siblings.
if (focused && container->children->length > 1) {
update_container_border(focused);
void apply_vert_layout(swayc_t *container, const double x, const double y,
const double width, const double height, const int start,
const int end) {
int i;
double scale = 0;
// Calculate total height
for (i = start; i < end; ++i) {
double *old_height = &((swayc_t *)container->children->items[i])->height;
if (*old_height <= 0) {
if (end - start > 1) {
*old_height = height / (end - start - 1);
} else {
*old_height = height;
}
}
break;
case L_TABBED:
case L_STACKED:
{
swayc_t *focused = NULL;
for (i = 0; i < container->children->length; ++i) {
swayc_t *child = container->children->items[i];
child->x = x;
child->y = y;
if (child == container->focused) {
focused = child;
} else {
arrange_windows_r(child, width, height);
}
scale += *old_height;
}
scale = height / scale;
// Resize
double child_y = y;
if (scale > 0.1) {
sway_log(L_DEBUG, "Arranging %p vertically", container);
swayc_t *focused = NULL;
for (i = start; i < end; ++i) {
swayc_t *child = container->children->items[i];
sway_log(L_DEBUG,
"Calculating arrangement for %p:%d (will scale %f by %f)", child,
child->type, height, scale);
child->x = x;
child->y = child_y;
if (child == container->focused) {
focused = child;
}
if (focused) {
arrange_windows_r(focused, width, height);
if (i == end - 1) {
double remaining_height = y + height - child_y;
arrange_windows_r(child, width, remaining_height);
} else {
arrange_windows_r(child, width, child->height * scale);
}
break;
child_y += child->height;
}
// update focused view border last because it may
// depend on the title bar geometry of its siblings.
if (focused && container->children->length > 1) {
update_container_border(focused);
}
}
}
// Arrage floating layouts for workspaces last
if (container->type == C_WORKSPACE) {
for (i = 0; i < container->floating->length; ++i) {
swayc_t *view = container->floating->items[i];
if (view->type == C_VIEW) {
update_geometry(view);
sway_log(L_DEBUG, "Set floating view to %.f x %.f @ %.f, %.f", view->width,
view->height, view->x, view->y);
if (swayc_is_fullscreen(view)) {
wlc_view_bring_to_front(view->handle);
} else if (!container->focused
|| !swayc_is_fullscreen(container->focused)) {
wlc_view_bring_to_front(view->handle);
void apply_tabbed_or_stacked_layout(swayc_t *container, double x, double y,
double width, double height) {
int i;
swayc_t *focused = NULL;
for (i = 0; i < container->children->length; ++i) {
swayc_t *child = container->children->items[i];
child->x = x;
child->y = y;
if (child == container->focused) {
focused = child;
} else {
arrange_windows_r(child, width, height);
}
}
if (focused) {
arrange_windows_r(focused, width, height);
}
}
void apply_auto_layout(swayc_t *container, const double x, const double y,
const double width, const double height,
enum swayc_layouts group_layout,
bool master_first) {
// Auto layout "container" in width x height @ x, y
// using "group_layout" for each of the groups in the container.
// There is one "master" group, plus container->nb_slave_groups.
// Each group is layed out side by side following the "major" axis.
// The direction of the layout used for groups is the "minor" axis.
// Example:
//
// ---- major axis -->
// +---------+-----------+
// | | | |
// | master | slave 1 | |
// | +-----------+ | minor axis (direction of group_layout)
// | | | |
// | | slave 2 | V
// +---------+-----------+
//
// container with three children (one master and two slaves) and
// a single slave group (containing slave 1 and 2). The master
// group and slave group are layed out using L_VERT.
size_t nb_groups = auto_group_count(container);
// the target dimension of the container along the "major" axis, each
// group in the container will be layed out using "group_layout" along
// the "minor" axis.
double dim_maj;
double pos_maj;
// x and y coords for the next group to be laid out.
const double *group_x, *group_y;
// pos of the next group to layout along the major axis
double pos;
// size of the next group along the major axis.
double group_dim;
// height and width of next group to be laid out.
const double *group_h, *group_w;
switch (group_layout) {
default:
sway_log(L_DEBUG, "Unknown layout type (%d) used in %s()",
group_layout, __func__);
/* fall through */
case L_VERT:
dim_maj = width;
pos_maj = x;
group_x = &pos;
group_y = &y;
group_w = &group_dim;
group_h = &height;
break;
case L_HORIZ:
dim_maj = height;
pos_maj = y;
group_x = &x;
group_y = &pos;
group_w = &width;
group_h = &group_dim;
break;
}
/* Determine the dimension of each of the groups in the layout.
* Dimension will be width for a VERT layout and height for a HORIZ
* layout. */
double old_group_dim[nb_groups];
double old_dim = 0;
for (size_t group = 0; group < nb_groups; ++group) {
int idx;
if (auto_group_bounds(container, group, &idx, NULL)) {
swayc_t *child = container->children->items[idx];
double *dim = group_layout == L_HORIZ ? &child->height : &child->width;
if (*dim <= 0) {
// New child with uninitialized dimension
*dim = dim_maj;
if (nb_groups > 1) {
// child gets a dimension proportional to existing groups,
// it will be later scaled based on to the available size
// in the major axis.
*dim /= (nb_groups - 1);
}
}
old_dim += *dim;
old_group_dim[group] = *dim;
}
}
double scale = dim_maj / old_dim;
/* Apply layout to each group */
pos = pos_maj;
for (size_t group = 0; group < nb_groups; ++group) {
int start, end; // index of first (inclusive) and last (exclusive) child in the group
if (auto_group_bounds(container, group, &start, &end)) {
// adjusted size of the group
group_dim = old_group_dim[group] * scale;
if (group == nb_groups - 1) {
group_dim = pos_maj + dim_maj - pos; // remaining width
}
sway_log(L_DEBUG, "Arranging container %p column %zu, children [%d,%d[ (%fx%f+%f,%f)",
container, group, start, end, *group_w, *group_h, *group_x, *group_y);
switch (group_layout) {
default:
case L_VERT:
apply_vert_layout(container, *group_x, *group_y, *group_w, *group_h, start, end);
break;
case L_HORIZ:
apply_horiz_layout(container, *group_x, *group_y, *group_w, *group_h, start, end);
break;
}
/* update position for next group */
pos += group_dim;
}
}
}
@ -1106,6 +1422,21 @@ swayc_t *get_swayc_in_direction_under(swayc_t *container, enum movement_directio
return parent;
}
}
if (dir == MOVE_PREV || dir == MOVE_NEXT) {
int focused_idx = index_child(container);
if (focused_idx == -1) {
return NULL;
} else {
int desired = (focused_idx + (dir == MOVE_NEXT ? 1 : -1)) %
parent->children->length;
if (desired < 0) {
desired += parent->children->length;
}
return parent->children->items[desired];
}
}
// If moving to an adjacent output we need a starting position (since this
// output might border to multiple outputs).
struct wlc_point abs_pos;
@ -1128,7 +1459,8 @@ swayc_t *get_swayc_in_direction_under(swayc_t *container, enum movement_directio
while (true) {
// Test if we can even make a difference here
bool can_move = false;
int diff = 0;
int desired;
int idx = index_child(container);
if (parent->type == C_ROOT) {
swayc_t *output = swayc_adjacent_output(container, dir, &abs_pos, true);
if (!output || output == container) {
@ -1137,21 +1469,36 @@ swayc_t *get_swayc_in_direction_under(swayc_t *container, enum movement_directio
sway_log(L_DEBUG, "Moving between outputs");
return get_swayc_in_output_direction(output, dir);
} else {
if (dir == MOVE_LEFT || dir == MOVE_RIGHT) {
if (parent->layout == L_HORIZ || parent->layout == L_TABBED) {
can_move = true;
diff = dir == MOVE_LEFT ? -1 : 1;
if (is_auto_layout(parent->layout)) {
bool is_major = parent->layout == L_AUTO_LEFT || parent->layout == L_AUTO_RIGHT
? dir == MOVE_LEFT || dir == MOVE_RIGHT
: dir == MOVE_DOWN || dir == MOVE_UP;
size_t gidx = auto_group_index(parent, idx);
if (is_major) {
size_t desired_grp = gidx + (dir == MOVE_RIGHT || dir == MOVE_DOWN ? 1 : -1);
can_move = auto_group_bounds(parent, desired_grp, &desired, NULL);
} else {
desired = idx + (dir == MOVE_RIGHT || dir == MOVE_DOWN ? 1 : -1);
int start, end;
can_move = auto_group_bounds(parent, gidx, &start, &end)
&& desired >= start && desired < end;
}
} else {
if (parent->layout == L_VERT || parent->layout == L_STACKED) {
can_move = true;
diff = dir == MOVE_UP ? -1 : 1;
if (dir == MOVE_LEFT || dir == MOVE_RIGHT) {
if (parent->layout == L_HORIZ || parent->layout == L_TABBED) {
can_move = true;
desired = idx + (dir == MOVE_LEFT ? -1 : 1);
}
} else {
if (parent->layout == L_VERT || parent->layout == L_STACKED) {
can_move = true;
desired = idx + (dir == MOVE_UP ? -1 : 1);
}
}
}
}
if (can_move) {
int desired = index_child(container) + diff;
if (container->is_floating) {
if (desired < 0) {
wrap_candidate = parent->floating->items[parent->floating->length-1];
@ -1178,6 +1525,8 @@ swayc_t *get_swayc_in_direction_under(swayc_t *container, enum movement_directio
}
}
} else {
sway_log(L_DEBUG, "%s cont %d-%p dir %i sibling %d: %p", __func__,
idx, container, dir, desired, parent->children->items[desired]);
return parent->children->items[desired];
}
}
@ -1233,3 +1582,167 @@ enum swayc_layouts default_layout(swayc_t *output) {
return L_VERT;
}
}
bool is_auto_layout(enum swayc_layouts layout) {
return (layout >= L_AUTO_FIRST) && (layout <= L_AUTO_LAST);
}
/**
* Return the number of master elements in a container
*/
static inline size_t auto_master_count(const swayc_t *container) {
sway_assert(container->children->length >= 0, "Container %p has (negative) children %d",
container, container->children->length);
return MIN(container->nb_master, (size_t)container->children->length);
}
/**
* Return the number of children in the slave groups. This corresponds to the children
* that are not members of the master group.
*/
static inline size_t auto_slave_count(const swayc_t *container) {
return container->children->length - auto_master_count(container);
}
/**
* Return the number of slave groups in the container.
*/
size_t auto_slave_group_count(const swayc_t *container) {
return MIN(container->nb_slave_groups, auto_slave_count(container));
}
/**
* Return the combined number of master and slave groups in the container.
*/
size_t auto_group_count(const swayc_t *container) {
return auto_slave_group_count(container)
+ (container->children->length && container->nb_master ? 1 : 0);
}
/**
* given the index of a container's child, return the index of the first child of the group
* which index is a member of.
*/
int auto_group_start_index(const swayc_t *container, int index) {
if (index < 0 || ! is_auto_layout(container->layout)
|| (size_t)index < container->nb_master) {
return 0;
} else {
size_t nb_slaves = auto_slave_count(container);
size_t nb_slave_grp = auto_slave_group_count(container);
size_t grp_sz = nb_slaves / nb_slave_grp;
size_t remainder = nb_slaves % nb_slave_grp;
int idx2 = (nb_slave_grp - remainder) * grp_sz + container->nb_master;
int start_idx;
if (index < idx2) {
start_idx = ((index - container->nb_master) / grp_sz) * grp_sz + container->nb_master;
} else {
start_idx = idx2 + ((index - idx2) / (grp_sz + 1)) * (grp_sz + 1);
}
return MIN(start_idx, container->children->length);
}
}
/**
* given the index of a container's child, return the index of the first child of the group
* that follows the one which index is a member of.
* This makes the function usable to walk through the groups in a container.
*/
int auto_group_end_index(const swayc_t *container, int index) {
if (index < 0 || ! is_auto_layout(container->layout)) {
return container->children->length;
} else {
int nxt_idx;
if ((size_t)index < container->nb_master) {
nxt_idx = auto_master_count(container);
} else {
size_t nb_slaves = auto_slave_count(container);
size_t nb_slave_grp = auto_slave_group_count(container);
size_t grp_sz = nb_slaves / nb_slave_grp;
size_t remainder = nb_slaves % nb_slave_grp;
int idx2 = (nb_slave_grp - remainder) * grp_sz + container->nb_master;
if (index < idx2) {
nxt_idx = ((index - container->nb_master) / grp_sz + 1) * grp_sz + container->nb_master;
} else {
nxt_idx = idx2 + ((index - idx2) / (grp_sz + 1) + 1) * (grp_sz + 1);
}
}
return MIN(nxt_idx, container->children->length);
}
}
/**
* return the index of the Group containing <index>th child of <container>.
* The index is the order of the group along the container's major axis (starting at 0).
*/
size_t auto_group_index(const swayc_t *container, int index) {
if (index < 0) {
return 0;
}
bool master_first = (container->layout == L_AUTO_LEFT || container->layout == L_AUTO_TOP);
size_t nb_slaves = auto_slave_count(container);
if ((size_t)index < container->nb_master) {
if (master_first || nb_slaves <= 0) {
return 0;
} else {
return auto_slave_group_count(container);
}
} else {
size_t nb_slave_grp = auto_slave_group_count(container);
size_t grp_sz = nb_slaves / nb_slave_grp;
size_t remainder = nb_slaves % nb_slave_grp;
int idx2 = (nb_slave_grp - remainder) * grp_sz + container->nb_master;
size_t grp_idx;
if (index < idx2) {
grp_idx = (index - container->nb_master) / grp_sz;
} else {
grp_idx = (nb_slave_grp - remainder) + (index - idx2) / (grp_sz + 1) ;
}
return grp_idx + (master_first && container-> nb_master ? 1 : 0);
}
}
/**
* Return the first index (inclusive) and last index (exclusive) of the elements of a group in
* an auto layout.
* If the bounds of the given group can be calculated, they are returned in the start/end
* parameters (int pointers) and the return value will be true.
* The indexes are passed by reference and can be NULL.
*/
bool auto_group_bounds(const swayc_t *container, size_t group_index, int *start, int *end) {
size_t nb_grp = auto_group_count(container);
if (group_index >= nb_grp) {
return false;
}
bool master_first = (container->layout == L_AUTO_LEFT || container->layout == L_AUTO_TOP);
size_t nb_master = auto_master_count(container);
size_t nb_slave_grp = auto_slave_group_count(container);
int g_start, g_end;
if (nb_master && (master_first ? group_index == 0 : group_index == nb_grp - 1)) {
g_start = 0;
g_end = nb_master;
} else {
size_t nb_slaves = auto_slave_count(container);
size_t grp_sz = nb_slaves / nb_slave_grp;
size_t remainder = nb_slaves % nb_slave_grp;
size_t g0 = master_first && container->nb_master ? 1 : 0;
size_t g1 = g0 + nb_slave_grp - remainder;
if (group_index < g1) {
g_start = container->nb_master + (group_index - g0) * grp_sz;
g_end = g_start + grp_sz;
} else {
size_t g2 = group_index - g1;
g_start = container->nb_master
+ (nb_slave_grp - remainder) * grp_sz
+ g2 * (grp_sz + 1);
g_end = g_start + grp_sz + 1;
}
}
if (start) {
*start = g_start;
}
if (end) {
*end = g_end;
}
return true;
}

@ -39,7 +39,7 @@ The following commands may only be used in the configuration file.
**set** <name> <value>::
Sets variable $name to _value_. You can use the new variable in the arguments
of future commands.
**orientation** <horizontal|vertical|auto>::
Sets the default container layout for tiled containers.
@ -62,11 +62,13 @@ They are expected to be used with **bindsym** or at runtime through **swaymsg**(
Make focused view floating, non-floating, or the opposite of what it is now.
**focus** <direction>::
Direction may be one of _up_, _down_, _left_, _right_, _parent_, or _child_.
The directional focus commands will move the focus in that direction. The parent
focus command will change the focus to the parent of the currently focused
container, which is useful, for example, to open a sibling of the parent
container, or to move the entire container around.
Direction may be one of _up_, _down_, _left_, _right_, _next_, _prev_,
_parent_, or _child_. The directional focus commands will move the focus
in that direction. The _next_ and _prev_ directions will focus the next,
respectively previous, element in the current container. The parent
focus command will change the focus to the parent of the currently
focused container, which is useful, for example, to open a sibling of
the parent container, or to move the entire container around.
**focus** output <direction|name>::
Direction may be one of _up_, _down_, _left_, _right_. The directional focus
@ -81,10 +83,28 @@ They are expected to be used with **bindsym** or at runtime through **swaymsg**(
**layout** <mode>::
Sets the layout mode of the focused container. _mode_ can be one of _splith_,
_splitv_, _toggle split_, _stacking_ or _tabbed_.
_splitv_, _toggle split_, _stacking_, _tabbed_.
**layout** auto <mode>::
Sets layout to one of the auto modes, i.e. one of _left_, right_, _top_,
or _bottom_.
**layout** auto <next|prev>::
Cycles between available auto layouts.
**layout** auto [master|ncol] [inc|set] <n>::
Modify the number of master elements, respectively slave columns, in the
focused container. <n> can be a positive or negative integer. These commands
only have an effect if the focused container uses one of the "auto" layouts.
**layout** toggle split::
Cycles between available split layouts.
**move** <left|right|up|down>::
Moves the focused container _left_, _right_, _up_, or _down_.
**move** <left|right|up|down|next|prev|first>::
Moves the focused container _left_, _right_, _up_, or _down_. Moving to _prev_
or _next_ swaps the container with its sibling in the same container. Move
_first_ exchanges the focused element in an auto layout with the first
element, i.e. promotes the focused element to master position.
**move** <container|window> to workspace <name>::
Moves the focused container to the workspace identified by _name_.
@ -360,8 +380,8 @@ The default colors are:
switch to workspace 2, then invoke the "workspace 2" command again, you
will be returned to workspace 1. Defaults to _no_.
**workspace_layout** <default|stacking|tabbed>::
Specifies the start layout for new workspaces.
**workspace_layout** <default|stacking|tabbed|auto|auto left|auto right|auto
top|auto bottom>:: Specifies the start layout for new workspaces.
**include** <path>::
Includes a sub config file by _path_. _path_ can be either a full path or a

Loading…
Cancel
Save