From e51268a39eeffd56d016a8d25dc98a40ff045a9c Mon Sep 17 00:00:00 2001 From: Andreas Stührk Date: Sat, 2 Dec 2023 23:57:01 +0100 Subject: Add actions to move the active workspace to another monitor --- niri-config/src/lib.rs | 4 +++ resources/default-config.kdl | 4 +++ src/input.rs | 24 +++++++++++++ src/layout/mod.rs | 82 ++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 114 insertions(+) diff --git a/niri-config/src/lib.rs b/niri-config/src/lib.rs index 98d2097e..736f976e 100644 --- a/niri-config/src/lib.rs +++ b/niri-config/src/lib.rs @@ -490,6 +490,10 @@ pub enum Action { SetColumnWidth(#[knuffel(argument, str)] SizeChange), SwitchLayout(#[knuffel(argument)] LayoutAction), ShowHotkeyOverlay, + MoveWorkspaceToMonitorLeft, + MoveWorkspaceToMonitorRight, + MoveWorkspaceToMonitorDown, + MoveWorkspaceToMonitorUp, } #[derive(Debug, Clone, Copy, PartialEq)] diff --git a/resources/default-config.kdl b/resources/default-config.kdl index 2de9b25a..6765e9ba 100644 --- a/resources/default-config.kdl +++ b/resources/default-config.kdl @@ -275,6 +275,10 @@ binds { // Mod+Shift+Ctrl+Left { move-window-to-monitor-left; } // ... + // And you can also move a whole workspace to another monitor: + // Mod+Shift+Ctrl+Left { move-workspace-to-monitor-left; } + // ... + Mod+Page_Down { focus-workspace-down; } Mod+Page_Up { focus-workspace-up; } Mod+U { focus-workspace-down; } diff --git a/src/input.rs b/src/input.rs index 0b671b4d..a357eac1 100644 --- a/src/input.rs +++ b/src/input.rs @@ -609,6 +609,30 @@ impl State { self.niri.queue_redraw_all(); } } + Action::MoveWorkspaceToMonitorLeft => { + if let Some(output) = self.niri.output_left() { + self.niri.layout.move_workspace_to_output(&output); + self.move_cursor_to_output(&output); + } + } + Action::MoveWorkspaceToMonitorRight => { + if let Some(output) = self.niri.output_right() { + self.niri.layout.move_workspace_to_output(&output); + self.move_cursor_to_output(&output); + } + } + Action::MoveWorkspaceToMonitorDown => { + if let Some(output) = self.niri.output_down() { + self.niri.layout.move_workspace_to_output(&output); + self.move_cursor_to_output(&output); + } + } + Action::MoveWorkspaceToMonitorUp => { + if let Some(output) = self.niri.output_up() { + self.niri.layout.move_workspace_to_output(&output); + self.move_cursor_to_output(&output); + } + } } } diff --git a/src/layout/mod.rs b/src/layout/mod.rs index bedc0979..5482b29c 100644 --- a/src/layout/mod.rs +++ b/src/layout/mod.rs @@ -1335,6 +1335,42 @@ impl Layout { } } + pub fn move_workspace_to_output(&mut self, output: &Output) { + let MonitorSet::Normal { + monitors, + active_monitor_idx, + .. + } = &mut self.monitor_set + else { + return; + }; + + let current = &mut monitors[*active_monitor_idx]; + if current.active_workspace_idx == current.workspaces.len() - 1 { + // Insert a new empty workspace. + let ws = Workspace::new(current.output.clone(), current.options.clone()); + current.workspaces.push(ws); + } + let mut ws = current.workspaces.remove(current.active_workspace_idx); + current.active_workspace_idx = current.active_workspace_idx.saturating_sub(1); + current.workspace_switch = None; + current.clean_up_workspaces(); + + ws.set_output(Some(output.clone())); + ws.original_output = OutputId::new(output); + + let target_idx = monitors + .iter() + .position(|mon| &mon.output == output) + .unwrap(); + let target = &mut monitors[target_idx]; + target.workspaces.insert(target.active_workspace_idx, ws); + target.workspace_switch = None; + target.clean_up_workspaces(); + + *active_monitor_idx = target_idx; + } + pub fn set_fullscreen(&mut self, window: &W, is_fullscreen: bool) { match &mut self.monitor_set { MonitorSet::Normal { monitors, .. } => { @@ -1737,6 +1773,7 @@ mod tests { SetColumnWidth(#[proptest(strategy = "arbitrary_size_change()")] SizeChange), SetWindowHeight(#[proptest(strategy = "arbitrary_size_change()")] SizeChange), Communicate(#[proptest(strategy = "1..=5usize")] usize), + MoveWorkspaceToOutput(#[proptest(strategy = "1..=5u8")] u8), } impl Op { @@ -1910,6 +1947,14 @@ mod tests { layout.update_window(&win); } } + Op::MoveWorkspaceToOutput(id) => { + let name = format!("output{id}"); + let Some(output) = layout.outputs().find(|o| o.name() == name).cloned() else { + return; + }; + + layout.move_workspace_to_output(&output); + } } } } @@ -1996,6 +2041,7 @@ mod tests { Op::MoveWindowDownOrToWorkspaceDown, Op::MoveWindowUp, Op::MoveWindowUpOrToWorkspaceUp, + Op::MoveWorkspaceToOutput(1), ]; for third in every_op { @@ -2410,6 +2456,42 @@ mod tests { check_ops(&ops); } + #[test] + fn move_workspace_to_output() { + let ops = [ + Op::AddOutput(1), + Op::AddOutput(2), + Op::FocusOutput(1), + Op::AddWindow { + id: 0, + bbox: Rectangle::from_loc_and_size((0, 0), (100, 200)), + min_max_size: Default::default(), + }, + Op::MoveWorkspaceToOutput(2), + ]; + + let mut layout = Layout::default(); + for op in ops { + op.apply(&mut layout); + } + + let MonitorSet::Normal { + monitors, + active_monitor_idx, + .. + } = layout.monitor_set + else { + unreachable!() + }; + + assert_eq!(active_monitor_idx, 1); + assert_eq!(monitors[0].workspaces.len(), 1); + assert!(!monitors[0].workspaces[0].has_windows()); + assert_eq!(monitors[1].active_workspace_idx, 0); + assert_eq!(monitors[1].workspaces.len(), 2); + assert!(monitors[1].workspaces[0].has_windows()); + } + fn arbitrary_spacing() -> impl Strategy { // Give equal weight to: // - 0: the element is disabled -- cgit