From e58e542c438172f3edc1f3ef8979e3d7301277e3 Mon Sep 17 00:00:00 2001 From: Vladimir Panteleev Date: Sat, 27 Dec 2025 11:22:00 +0000 Subject: [PATCH] fix(move): match i3 behavior for move-to-mark on split targets MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When moving a container to a mark on a workspace or split container, match i3's con_move_to_target behavior: descend to the focused child and add the container as a sibling (making it a direct child of the parent), rather than adding it as a child of the focused split. This fixes two cases: - N_WORKSPACE: moving to a mark on a workspace now adds the container as a direct child of the workspace - N_CONTAINER: moving to a mark on a split container now adds the container as a sibling of the focused child, not as a nested child This enables the "move to parent" use case where a container nested in a split can be reparented to be a direct child of its grandparent. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- sway/commands/move.c | 57 ++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 53 insertions(+), 4 deletions(-) diff --git a/sway/commands/move.c b/sway/commands/move.c index 7705200c4..09f94c9f3 100644 --- a/sway/commands/move.c +++ b/sway/commands/move.c @@ -566,8 +566,33 @@ static struct cmd_results *cmd_move_container(bool no_auto_back_and_forth, root_scratchpad_show(container); } switch (destination->type) { - case N_WORKSPACE: - container_move_to_workspace(container, destination->sway_workspace); + case N_WORKSPACE: { + struct sway_workspace *dest_ws = destination->sway_workspace; + // Match i3's con_move_to_target behavior: + // - Empty workspace: move directly to workspace + // - Non-empty workspace: descend to focused child, add as sibling + // (i3's _con_move_to_con goes up to parent, making con a sibling) + if (workspace_is_empty(dest_ws)) { + container_move_to_workspace(container, dest_ws); + } else { + struct sway_node *focus = + seat_get_focus_inactive(seat, destination); + if (focus && focus->type == N_CONTAINER) { + struct sway_container *focus_con = focus->sway_container; + // Match i3: add as sibling of focused child, not as child of it + if (container != focus_con && + !container_has_ancestor(focus_con, container)) { + container_detach(container); + container->pending.width = container->pending.height = 0; + container->width_fraction = container->height_fraction = 0; + container_add_sibling(focus_con, container, 1); + if (container->view) { + ipc_event_window(container, "move"); + } + } + } + } + } break; case N_OUTPUT: { struct sway_output *output = destination->sway_output; @@ -579,8 +604,32 @@ static struct cmd_results *cmd_move_container(bool no_auto_back_and_forth, container_move_to_workspace(container, ws); } break; - case N_CONTAINER: - container_move_to_container(container, destination->sway_container); + case N_CONTAINER: { + struct sway_container *dest_con = destination->sway_container; + // Match i3's con_move_to_target behavior: + // If destination is a split (not a leaf), descend to focused child + // and add as sibling of that child + if (!dest_con->view) { + struct sway_node *focus = + seat_get_focus_inactive(seat, destination); + if (focus && focus->type == N_CONTAINER) { + struct sway_container *focus_con = focus->sway_container; + // Add as sibling of focused child + if (container != focus_con && + !container_has_ancestor(focus_con, container)) { + container_detach(container); + container->pending.width = container->pending.height = 0; + container->width_fraction = container->height_fraction = 0; + container_add_sibling(focus_con, container, 1); + if (container->view) { + ipc_event_window(container, "move"); + } + } + } + } else { + container_move_to_container(container, dest_con); + } + } break; case N_ROOT: break;