From ac444563af791f2c9961e88d01f2356e1e634f39 Mon Sep 17 00:00:00 2001 From: benjo Date: Wed, 18 Feb 2026 00:43:10 +0100 Subject: [PATCH] feat: implement bringing windows to workspaces --- rift.default.toml | 1 + src/actor/reactor/events/command.rs | 10 +++ src/actor/wm_controller.rs | 27 ++++++++ src/layout_engine/engine.rs | 103 ++++++++++++++++++++++++++++ 4 files changed, 141 insertions(+) diff --git a/rift.default.toml b/rift.default.toml index 5e996f6a..a883ec69 100644 --- a/rift.default.toml +++ b/rift.default.toml @@ -356,6 +356,7 @@ comb1 = "Alt + Shift" # - prev_workspace = true|false (optional skip-empty override) # - switch_to_workspace = N # - move_window_to_workspace = N / move_window_to_workspace = { workspace = N, window_id = 123 } (optional window id) +# - bring_window_to_workspace = N (moves window and switches workspace at the same time) # - create_workspace # - switch_to_last_workspace # - set_workspace_layout = { mode = "traditional"|"bsp"|"master_stack"|"scrolling" } (active workspace) diff --git a/src/actor/reactor/events/command.rs b/src/actor/reactor/events/command.rs index 57e3c953..12080c3c 100644 --- a/src/actor/reactor/events/command.rs +++ b/src/actor/reactor/events/command.rs @@ -98,6 +98,16 @@ impl CommandEventHandler { EventResponse::default() } } + LayoutCommand::BringWindowToWorkspace { .. } => { + if let Some(space) = command_space { + reactor + .layout_manager + .layout_engine + .handle_virtual_workspace_command(space, &cmd) + } else { + EventResponse::default() + } + } _ => { let (visible_spaces, visible_space_centers) = reactor.visible_spaces_for_layout(false); diff --git a/src/actor/wm_controller.rs b/src/actor/wm_controller.rs index 39efd058..d7a12b6c 100644 --- a/src/actor/wm_controller.rs +++ b/src/actor/wm_controller.rs @@ -68,6 +68,7 @@ pub enum WmCmd { PrevWorkspace, SwitchToWorkspace(WorkspaceSelector), MoveWindowToWorkspace(WorkspaceSelector), + BringWindowToWorkspace(WorkspaceSelector), CreateWorkspace, SwitchToLastWorkspace, @@ -359,6 +360,32 @@ impl WmController { ); } } + Command(Wm(BringWindowToWorkspace(ws_sel))) => { + let maybe_index: Option = match &ws_sel { + WorkspaceSelector::Index(i) => Some(*i), + WorkspaceSelector::Name(name) => self + .config + .config + .virtual_workspaces + .workspace_names + .iter() + .position(|n| n == name), + }; + + if let Some(workspace_index) = maybe_index { + self.events_tx.send(reactor::Event::Command(reactor::Command::Layout( + layout::LayoutCommand::BringWindowToWorkspace { + workspace: workspace_index, + window_id: None, + }, + ))); + } else { + tracing::warn!( + "Hotkey requested bring window to workspace {:?} but it could not be resolved; ignoring", + ws_sel + ); + } + } Command(Wm(CreateWorkspace)) => { self.events_tx.send(reactor::Event::Command(reactor::Command::Layout( layout::LayoutCommand::CreateWorkspace, diff --git a/src/layout_engine/engine.rs b/src/layout_engine/engine.rs index 735dda90..8bc20e32 100644 --- a/src/layout_engine/engine.rs +++ b/src/layout_engine/engine.rs @@ -71,6 +71,10 @@ pub enum LayoutCommand { workspace: usize, window_id: Option, }, + BringWindowToWorkspace { + workspace: usize, + window_id: Option, + }, SetWorkspaceLayout { workspace: Option, mode: LayoutMode, @@ -1327,6 +1331,7 @@ impl LayoutEngine { | LayoutCommand::PrevWorkspace(_) | LayoutCommand::SwitchToWorkspace(_) | LayoutCommand::MoveWindowToWorkspace { .. } + | LayoutCommand::BringWindowToWorkspace { .. } | LayoutCommand::SetWorkspaceLayout { .. } | LayoutCommand::CreateWorkspace | LayoutCommand::SwitchToLastWorkspace => EventResponse::default(), @@ -2059,6 +2064,104 @@ impl LayoutEngine { self.broadcast_windows_changed(op_space); EventResponse::default() } + LayoutCommand::BringWindowToWorkspace { + workspace: workspace_index, + window_id: maybe_id, + } => { + let focused_window = if let Some(spec_u32) = maybe_id { + match self.virtual_workspace_manager.find_window_by_idx(space, *spec_u32) { + Some(w) => w, + None => return EventResponse::default(), + } + } else { + match self.focused_window { + Some(wid) => wid, + None => return EventResponse::default(), + } + }; + + let inferred_space = self.space_with_window(focused_window); + let op_space = if inferred_space == Some(space) { + space + } else { + inferred_space.unwrap_or(space) + }; + + let workspaces = self.virtual_workspace_manager_mut().list_workspaces(op_space); + let Some((target_workspace_id, _)) = workspaces.get(*workspace_index) else { + return EventResponse::default(); + }; + let target_workspace_id = *target_workspace_id; + + let Some(current_workspace_id) = + self.virtual_workspace_manager.workspace_for_window(op_space, focused_window) + else { + return EventResponse::default(); + }; + + if current_workspace_id == target_workspace_id { + return EventResponse::default(); + } + + let is_floating = self.floating.is_floating(focused_window); + + if is_floating { + self.floating.remove_active_for_window(focused_window); + } else { + self.remove_window_from_all_tiling_trees(focused_window); + } + + let assigned = self.virtual_workspace_manager.assign_window_to_workspace( + op_space, + focused_window, + target_workspace_id, + ); + if !assigned { + if is_floating { + self.floating.add_active(op_space, focused_window.pid, focused_window); + } else if let Some(prev_layout) = + self.workspace_layouts.active(op_space, current_workspace_id) + { + self.workspace_tree_mut(current_workspace_id) + .add_window_after_selection(prev_layout, focused_window); + } + return EventResponse::default(); + } + + if !is_floating { + if let Some(target_layout) = + self.workspace_layouts.active(op_space, target_workspace_id) + { + self.workspace_tree_mut(target_workspace_id) + .add_window_after_selection(target_layout, focused_window); + } + } + + let active_workspace = self.virtual_workspace_manager.active_workspace(op_space); + + if Some(target_workspace_id) == active_workspace { + if is_floating { + self.floating.add_active(op_space, focused_window.pid, focused_window); + } + return EventResponse { + focus_window: Some(focused_window), + raise_windows: vec![], + boundary_hit: None, + }; + } + + self.virtual_workspace_manager.set_last_focused_window( + op_space, + target_workspace_id, + Some(focused_window), + ); + self.virtual_workspace_manager + .set_active_workspace(op_space, target_workspace_id); + + self.broadcast_windows_changed(op_space); + self.broadcast_workspace_changed(op_space); + return self.refocus_workspace(op_space, target_workspace_id); + } LayoutCommand::CreateWorkspace => { match self.virtual_workspace_manager.create_workspace(space, None) { Ok(_workspace_id) => {