diff --git a/include/workspace.h b/include/workspace.h index a7f2d13b..907e959f 100644 --- a/include/workspace.h +++ b/include/workspace.h @@ -174,4 +174,11 @@ void ws_force_orientation(Con *ws, orientation_t orientation); */ Con *workspace_attach_to(Con *ws); +/** + * Creates a new container and re-parents all of children from the given + * workspace into it. + * + * The container inherits the layout from the workspace. + */ +Con *workspace_encapsulate(Con *ws); #endif diff --git a/src/commands.c b/src/commands.c index 0263802f..53532435 100644 --- a/src/commands.c +++ b/src/commands.c @@ -388,7 +388,8 @@ void cmd_move_con_to_workspace(I3_CMD, char *which) { * when criteria was specified but didn't match any window or * when criteria wasn't specified and we don't have any window focused. */ if ((!match_is_empty(current_match) && TAILQ_EMPTY(&owindows)) || - (match_is_empty(current_match) && focused->type == CT_WORKSPACE)) { + (match_is_empty(current_match) && focused->type == CT_WORKSPACE && + con_is_leaf(focused))) { ysuccess(false); return; } @@ -476,9 +477,8 @@ void cmd_move_con_to_workspace_name(I3_CMD, char *name) { ysuccess(false); return; } - - if (match_is_empty(current_match) && focused->type == CT_WORKSPACE) { - ELOG("No window to move, you have focused a workspace.\n"); + else if (match_is_empty(current_match) && focused->type == CT_WORKSPACE && + con_is_leaf(focused)) { ysuccess(false); return; } @@ -512,7 +512,8 @@ void cmd_move_con_to_workspace_number(I3_CMD, char *which) { * when criteria was specified but didn't match any window or * when criteria wasn't specified and we don't have any window focused. */ if ((!match_is_empty(current_match) && TAILQ_EMPTY(&owindows)) || - (match_is_empty(current_match) && focused->type == CT_WORKSPACE)) { + (match_is_empty(current_match) && focused->type == CT_WORKSPACE && + con_is_leaf(focused))) { ysuccess(false); return; } diff --git a/src/con.c b/src/con.c index 1140fe80..5bba8c7c 100644 --- a/src/con.c +++ b/src/con.c @@ -607,11 +607,6 @@ void con_toggle_fullscreen(Con *con, int fullscreen_mode) { * */ void con_move_to_workspace(Con *con, Con *workspace, bool fix_coordinates, bool dont_warp) { - if (con->type == CT_WORKSPACE) { - DLOG("Moving workspaces is not yet implemented.\n"); - return; - } - /* Prevent moving if this would violate the fullscreen focus restrictions. */ if (!con_fullscreen_permits_focusing(workspace)) { LOG("Cannot move out of a fullscreen container"); @@ -629,6 +624,21 @@ void con_move_to_workspace(Con *con, Con *workspace, bool fix_coordinates, bool return; } + if (con->type == CT_WORKSPACE) { + con = workspace_encapsulate(con); + if (con == NULL) { + ELOG("Workspace failed to move its contents into a container!\n"); + return; + } + + /* Re-parent all of the old workspace's floating windows. */ + Con *child; + while (!TAILQ_EMPTY(&(source_ws->floating_head))) { + child = TAILQ_FIRST(&(source_ws->floating_head)); + con_move_to_workspace(child, workspace, true, true); + } + } + /* Save the current workspace. So we can call workspace_show() by the end * of this function. */ Con *current_ws = con_get_workspace(focused); diff --git a/src/workspace.c b/src/workspace.c index 14840e4a..3fdb7db8 100644 --- a/src/workspace.c +++ b/src/workspace.c @@ -830,3 +830,34 @@ Con *workspace_attach_to(Con *ws) { return new; } + +/** + * Creates a new container and re-parents all of children from the given + * workspace into it. + * + * The container inherits the layout from the workspace. + */ +Con *workspace_encapsulate(Con *ws) { + if (TAILQ_EMPTY(&(ws->nodes_head))) { + ELOG("Workspace %p / %s has no children to encapsulate\n", ws, ws->name); + return NULL; + } + + Con *new = con_new(NULL, NULL); + new->parent = ws; + new->layout = ws->layout; + + DLOG("Moving children of workspace %p / %s into container %p\n", + ws, ws->name, new); + + Con *child; + while (!TAILQ_EMPTY(&(ws->nodes_head))) { + child = TAILQ_FIRST(&(ws->nodes_head)); + con_detach(child); + con_attach(child, new, true); + } + + con_attach(new, ws, true); + + return new; +} diff --git a/testcases/t/132-move-workspace.t b/testcases/t/132-move-workspace.t index a4f6b608..730041a6 100644 --- a/testcases/t/132-move-workspace.t +++ b/testcases/t/132-move-workspace.t @@ -198,4 +198,124 @@ cmd 'move workspace number 17'; ok(workspace_exists('17'), 'workspace 17 created by moving'); is(@{get_ws('17')->{nodes}}, 1, 'one node on ws 16'); +################################################################################ +# The following four tests verify the various 'move workspace' commands when +# the selection is itself a workspace. +################################################################################ + +# borrowed from 122-split.t +# recursively sums up all nodes and their children +sub sum_nodes { + my ($nodes) = @_; + + return 0 if !@{$nodes}; + + my @children = (map { @{$_->{nodes}} } @{$nodes}, + map { @{$_->{'floating_nodes'}} } @{$nodes}); + + return @{$nodes} + sum_nodes(\@children); +} + +############################################################ +# move workspace 'next|prev' +############################################################ +$tmp = get_unused_workspace(); +$tmp2 = get_unused_workspace(); + +cmd "workspace $tmp"; +cmd 'open'; +is_num_children($tmp, 1, 'one container on first ws'); + +cmd "workspace $tmp2"; +cmd 'open'; +is_num_children($tmp2, 1, 'one container on second ws'); +cmd 'open'; +is_num_children($tmp2, 2, 'two containers on second ws'); + +cmd 'focus parent'; +cmd 'move workspace prev'; + +is_num_children($tmp, 2, 'two child containers on first ws'); +is(sum_nodes(get_ws_content($tmp)), 4, 'four total containers on first ws'); +is_num_children($tmp2, 0, 'no containers on second ws'); + +############################################################ +# move workspace current +# This is a special case that should be a no-op. +############################################################ +$tmp = fresh_workspace(); + +cmd 'open'; +is_num_children($tmp, 1, 'one container on first ws'); +my $tmpcount = sum_nodes(get_ws_content($tmp)); + +cmd 'focus parent'; +cmd "move workspace $tmp"; + +is(sum_nodes(get_ws_content($tmp)), $tmpcount, 'number of containers in first ws unchanged'); + +############################################################ +# move workspace '' +############################################################ +$tmp2 = get_unused_workspace(); +$tmp = fresh_workspace(); + +cmd 'open'; +is_num_children($tmp, 1, 'one container on first ws'); + +cmd "workspace $tmp2"; +cmd 'open'; +is_num_children($tmp2, 1, 'one container on second ws'); +cmd 'open'; +is_num_children($tmp2, 2, 'two containers on second ws'); + +cmd 'focus parent'; +cmd "move workspace $tmp"; + +is_num_children($tmp, 2, 'two child containers on first ws'); +is(sum_nodes(get_ws_content($tmp)), 4, 'four total containers on first ws'); +is_num_children($tmp2, 0, 'no containers on second ws'); + +############################################################ +# move workspace number '' +############################################################ +cmd 'workspace 18'; +cmd 'open'; +is_num_children('18', 1, 'one container on ws 18'); + +cmd 'workspace 19'; +cmd 'open'; +is_num_children('19', 1, 'one container on ws 19'); +cmd 'open'; +is_num_children('19', 2, 'two containers on ws 19'); + +cmd 'focus parent'; +cmd 'move workspace number 18'; + +is_num_children('18', 2, 'two child containers on ws 18'); +is(sum_nodes(get_ws_content('18')), 4, 'four total containers on ws 18'); +is_num_children('19', 0, 'no containers on ws 19'); + +################################################################### +# move workspace '' with a floating child +################################################################### +$tmp2 = get_unused_workspace(); +$tmp = fresh_workspace(); +cmd 'open'; +cmd 'floating toggle'; +cmd 'open'; +cmd 'floating toggle'; +cmd 'open'; + +$ws = get_ws($tmp); +is_num_children($tmp, 1, 'one container on first workspace'); +is(@{$ws->{floating_nodes}}, 2, 'two floating nodes on first workspace'); + +cmd 'focus parent'; +cmd "move workspace $tmp2"; + +$ws = get_ws($tmp2); +is_num_children($tmp2, 1, 'one container on second workspace'); +is(@{$ws->{floating_nodes}}, 2, 'two floating nodes on second workspace'); + done_testing;