From bf8dcd14087f0d8b9670ad50ef6cc7c7fd759c3d Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Sat, 29 Nov 2025 19:38:16 +0100 Subject: [PATCH 1/4] tree: allow moving focus between containers on different outputs --- src/config/handler.rs | 2 +- src/state.rs | 6 +++++- src/tree/container.rs | 32 ++++++++++++++++++++++++++------ 3 files changed, 32 insertions(+), 8 deletions(-) diff --git a/src/config/handler.rs b/src/config/handler.rs index fa2da799..492eb358 100644 --- a/src/config/handler.rs +++ b/src/config/handler.rs @@ -1607,7 +1607,7 @@ impl ConfigProxyHandler { let source_output = self.get_output_node(connector)?; let connector = self .state - .find_connector_in_direction(&source_output, direction.into()) + .find_output_in_direction(&source_output, direction.into()) .map(|o| Connector(o.global.connector.id.raw() as u64)) .unwrap_or(Connector(0)); self.respond(Response::GetConnectorInDirection { connector }); diff --git a/src/state.rs b/src/state.rs index 23ccbd18..4a95b242 100644 --- a/src/state.rs +++ b/src/state.rs @@ -1560,11 +1560,15 @@ impl State { } } - pub fn find_connector_in_direction( + pub fn find_output_in_direction( &self, source_output: &OutputNode, direction: Direction, ) -> Option> { + if source_output.is_dummy { + return None; + } + let outputs = self.root.outputs.lock(); let ref_box = source_output.global.pos.get(); diff --git a/src/tree/container.rs b/src/tree/container.rs index c2df4a3b..e4a6652d 100644 --- a/src/tree/container.rs +++ b/src/tree/container.rs @@ -976,6 +976,17 @@ impl ContainerNode { .and_then(|p| p.node_into_container()) } + fn find_neighboring_output(&self, direction: Direction) -> Option> { + if self.toplevel_data.parent.is_none() { + return None; + } + if self.toplevel_data.float.is_some() { + return None; + } + self.state + .find_output_in_direction(&self.workspace.get().output.get(), direction) + } + pub fn move_focus_from_child( self: Rc, seat: &Rc, @@ -997,10 +1008,21 @@ impl ContainerNode { ContainerSplit::Vertical => matches!(direction, Direction::Up | Direction::Down), } }; - if !in_line { - if let Some(c) = self.parent_container() { - c.move_focus_from_child(seat, self.deref(), direction); + let focus_in_parent = || { + if let Some(parent) = self.toplevel_data.parent.get() { + if let Some(c) = parent.node_into_container() { + c.move_focus_from_child(seat, self.deref(), direction); + } else if let Some(output) = self.find_neighboring_output(direction) + && let Some(ws) = output.workspace.get() + && let Some(c) = ws.container.get() + && c.node_visible() + { + c.node_do_focus(seat, direction); + } } + }; + if !in_line { + focus_in_parent(); return; } let prev = match direction { @@ -1017,9 +1039,7 @@ impl ContainerNode { let sibling = match sibling { Some(s) => s, None => { - if let Some(c) = self.parent_container() { - c.move_focus_from_child(seat, self.deref(), direction); - } + focus_in_parent(); return; } }; From 31fb4397cc745a65e3451cdcfc81895d37e23fae Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Sat, 29 Nov 2025 20:30:53 +0100 Subject: [PATCH 2/4] tree: allow moving children between containers on different outputs --- src/tree/container.rs | 44 ++++++++++++++++++++++++++++++++++++------- 1 file changed, 37 insertions(+), 7 deletions(-) diff --git a/src/tree/container.rs b/src/tree/container.rs index e4a6652d..d9a8c80e 100644 --- a/src/tree/container.rs +++ b/src/tree/container.rs @@ -20,7 +20,8 @@ use { ContainingNode, Direction, FindTreeResult, FindTreeUsecase, FloatNode, FoundNode, Node, NodeId, NodeLayerLink, NodeLocation, OutputNode, TddType, TileDragDestination, ToplevelData, ToplevelNode, ToplevelNodeBase, ToplevelType, WorkspaceNode, - default_tile_drag_bounds, toplevel_set_floating, walker::NodeVisitor, + default_tile_drag_bounds, toplevel_set_floating, toplevel_set_workspace, + walker::NodeVisitor, }, utils::{ asyncevent::AsyncEvent, @@ -1052,16 +1053,42 @@ impl ContainerNode { // pub fn move_child(self: Rc, child: Rc, direction: Direction) { + let move_to_neighboring_output = |child: Rc| { + let Some(output) = self.find_neighboring_output(direction) else { + return; + }; + let ws = output.ensure_workspace(); + let mut foci = SmallVec::new(); + let move_foci = !ws.container_visible(); + if move_foci { + collect_kb_foci2(child.clone(), &mut foci); + } + if let Some(c) = ws.container.get() { + self.clone().cnode_remove_child2(&*child, true); + c.insert_child(child, direction); + } else { + toplevel_set_workspace(&self.state, child, &ws); + } + if move_foci { + for seat in foci { + ws.clone().node_do_focus(&seat, Direction::Unspecified); + } + } + }; + // CASE 1: This is the only child of the container. Replace the container by the child. if self.num_children.get() == 1 { if let Some(parent) = self.toplevel_data.parent.get() && !self.toplevel_data.is_fullscreen.get() - && parent.cnode_accepts_child(&*child) { - parent.cnode_replace_child(self.deref(), child.clone()); - self.toplevel_data.parent.take(); - self.child_nodes.borrow_mut().clear(); - self.tl_destroy(); + if parent.cnode_accepts_child(&*child) { + parent.cnode_replace_child(self.deref(), child.clone()); + self.toplevel_data.parent.take(); + self.child_nodes.borrow_mut().clear(); + self.tl_destroy(); + } else { + move_to_neighboring_output(child); + } } return; } @@ -1112,7 +1139,10 @@ impl ContainerNode { } let parent = match parent_opt { Some(p) => p, - _ => return, + _ => { + move_to_neighboring_output(child); + return; + } }; self.cnode_remove_child2(&*child, true); match prev { From 0ee76fc09fc1e22a8dc11b72347e3ceabd8d1f5b Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Tue, 2 Dec 2025 12:29:53 +0100 Subject: [PATCH 3/4] tree: allow moving focus from/to fullscreen windows --- src/ifs/wl_seat.rs | 12 ++++++++++-- src/tree/container.rs | 8 ++------ src/tree/output.rs | 15 +++++++++++++++ 3 files changed, 27 insertions(+), 8 deletions(-) diff --git a/src/ifs/wl_seat.rs b/src/ifs/wl_seat.rs index 508dcf64..d20d6137 100644 --- a/src/ifs/wl_seat.rs +++ b/src/ifs/wl_seat.rs @@ -730,8 +730,16 @@ impl WlSeatGlobal { }; if direction == Direction::Down && tl.node_is_container() { tl.node_do_focus(self, direction); - } else if let Some(p) = tl.tl_data().parent.get() { - if let Some(c) = p.node_into_container() { + } else { + let data = tl.tl_data(); + if data.is_fullscreen.get() + && let Some(output) = data.output_opt() + && let Some(target) = self.state.find_output_in_direction(&output, direction) + { + target.take_keyboard_navigation_focus(self, direction); + } else if let Some(p) = data.parent.get() + && let Some(c) = p.node_into_container() + { c.move_focus_from_child(self, tl.deref(), direction); } } diff --git a/src/tree/container.rs b/src/tree/container.rs index d9a8c80e..7aab26a2 100644 --- a/src/tree/container.rs +++ b/src/tree/container.rs @@ -1013,12 +1013,8 @@ impl ContainerNode { if let Some(parent) = self.toplevel_data.parent.get() { if let Some(c) = parent.node_into_container() { c.move_focus_from_child(seat, self.deref(), direction); - } else if let Some(output) = self.find_neighboring_output(direction) - && let Some(ws) = output.workspace.get() - && let Some(c) = ws.container.get() - && c.node_visible() - { - c.node_do_focus(seat, direction); + } else if let Some(output) = self.find_neighboring_output(direction) { + output.take_keyboard_navigation_focus(seat, direction); } } }; diff --git a/src/tree/output.rs b/src/tree/output.rs index 9802ada5..74640db3 100644 --- a/src/tree/output.rs +++ b/src/tree/output.rs @@ -1488,6 +1488,21 @@ impl OutputNode { } } } + + pub fn take_keyboard_navigation_focus(&self, seat: &Rc, direction: Direction) { + let Some(ws) = self.workspace.get() else { + return; + }; + if let Some(fs) = ws.fullscreen.get() { + if fs.node_visible() { + fs.node_do_focus(seat, direction); + } + } else if let Some(c) = ws.container.get() { + if c.node_visible() { + c.node_do_focus(seat, direction); + } + } + } } pub struct OutputTitle { From 2a8d5d1ab6da6a24244a21d18a22ab2433a71f4f Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Tue, 2 Dec 2025 12:34:18 +0100 Subject: [PATCH 4/4] tree: allow moving fullscren windows with the keyboard --- src/ifs/wl_seat.rs | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/ifs/wl_seat.rs b/src/ifs/wl_seat.rs index d20d6137..119d16d9 100644 --- a/src/ifs/wl_seat.rs +++ b/src/ifs/wl_seat.rs @@ -747,8 +747,17 @@ impl WlSeatGlobal { pub fn move_focused(self: &Rc, direction: Direction) { let kb_node = self.keyboard_node.get(); - if let Some(tl) = kb_node.node_toplevel() - && let Some(parent) = tl.tl_data().parent.get() + let Some(tl) = kb_node.node_toplevel() else { + return; + }; + let data = tl.tl_data(); + if data.is_fullscreen.get() + && let Some(output) = data.output_opt() + && let Some(target) = self.state.find_output_in_direction(&output, direction) + { + let ws = target.ensure_workspace(); + toplevel_set_workspace(&self.state, tl, &ws); + } else if let Some(parent) = data.parent.get() && let Some(c) = parent.node_into_container() { c.move_child(tl, direction);