Repository: YaLTeR/niri
Pull request #1332: layout: add focus flag to move-window-to-workspace
Author: login: ghost
Created: 2025-03-24T02:44:39Z | Merged: 2025-03-29T06:40:08Z



Diff:
diff --git a/niri-config/src/lib.rs b/niri-config/src/lib.rs
index db379746d7..6ae66aebb3 100644
--- a/niri-config/src/lib.rs
+++ b/niri-config/src/lib.rs
@@ -1592,11 +1592,15 @@ pub enum Action {
     FocusWorkspacePrevious,
     MoveWindowToWorkspaceDown,
     MoveWindowToWorkspaceUp,
-    MoveWindowToWorkspace(#[knuffel(argument)] WorkspaceReference),
+    MoveWindowToWorkspace(
+        #[knuffel(argument)] WorkspaceReference,
+        #[knuffel(property(name = "focus"), default = true)] bool,
+    ),
     #[knuffel(skip)]
     MoveWindowToWorkspaceById {
         window_id: u64,
         reference: WorkspaceReference,
+        focus: bool,
     },
     MoveColumnToWorkspaceDown,
     MoveColumnToWorkspaceUp,
@@ -1817,13 +1821,16 @@ impl From<niri_ipc::Action> for Action {
             niri_ipc::Action::MoveWindowToWorkspace {
                 window_id: None,
                 reference,
-            } => Self::MoveWindowToWorkspace(WorkspaceReference::from(reference)),
+                focus,
+            } => Self::MoveWindowToWorkspace(WorkspaceReference::from(reference), focus),
             niri_ipc::Action::MoveWindowToWorkspace {
                 window_id: Some(window_id),
                 reference,
+                focus,
             } => Self::MoveWindowToWorkspaceById {
                 window_id,
                 reference: WorkspaceReference::from(reference),
+                focus,
             },
             niri_ipc::Action::MoveColumnToWorkspaceDown {} => Self::MoveColumnToWorkspaceDown,
             niri_ipc::Action::MoveColumnToWorkspaceUp {} => Self::MoveColumnToWorkspaceUp,
diff --git a/niri-ipc/src/lib.rs b/niri-ipc/src/lib.rs
index 591c1bf685..fb90dbe784 100644
--- a/niri-ipc/src/lib.rs
+++ b/niri-ipc/src/lib.rs
@@ -426,6 +426,14 @@ pub enum Action {
         /// Reference (index or name) of the workspace to move the window to.
         #[cfg_attr(feature = "clap", arg())]
         reference: WorkspaceReferenceArg,
+
+        /// Whether the focus should follow the moved window.
+        ///
+        /// If `true` (the default) and the window to move is focused, the focus will follow the
+        /// window to the new workspace. If `false`, the focus will remain on the original
+        /// workspace.
+        #[cfg_attr(feature = "clap", arg(long, action = clap::ArgAction::Set, default_value_t = true))]
+        focus: bool,
     },
     /// Move the focused column to the workspace below.
     MoveColumnToWorkspaceDown {},
diff --git a/src/input/mod.rs b/src/input/mod.rs
index 2744561c20..961df54a75 100644
--- a/src/input/mod.rs
+++ b/src/input/mod.rs
@@ -40,7 +40,7 @@ use self::move_grab::MoveGrab;
 use self::resize_grab::ResizeGrab;
 use self::spatial_movement_grab::SpatialMovementGrab;
 use crate::layout::scrolling::ScrollDirection;
-use crate::layout::LayoutElement as _;
+use crate::layout::{ActivateWindow, LayoutElement as _};
 use crate::niri::{CastTarget, State};
 use crate::ui::screenshot_ui::ScreenshotUi;
 use crate::utils::spawning::spawn;
@@ -1072,7 +1072,7 @@ impl State {
                 // FIXME: granular
                 self.niri.queue_redraw_all();
             }
-            Action::MoveWindowToWorkspace(reference) => {
+            Action::MoveWindowToWorkspace(reference, focus) => {
                 if let Some((mut output, index)) =
                     self.niri.find_output_and_workspace_index(reference)
                 {
@@ -1091,7 +1091,12 @@ impl State {
                             self.move_cursor_to_output(&output);
                         }
                     } else {
-                        self.niri.layout.move_to_workspace(None, index);
+                        let activate = if focus {
+                            ActivateWindow::Smart
+                        } else {
+                            ActivateWindow::No
+                        };
+                        self.niri.layout.move_to_workspace(None, index, activate);
                         self.maybe_warp_cursor_to_focus();
                     }
 
@@ -1102,6 +1107,7 @@ impl State {
             Action::MoveWindowToWorkspaceById {
                 window_id: id,
                 reference,
+                focus,
             } => {
                 let window = self.niri.layout.windows().find(|(_, m)| m.id().get() == id);
                 let window = window.map(|(_, m)| m.window.clone());
@@ -1130,7 +1136,14 @@ impl State {
                                 }
                             }
                         } else {
-                            self.niri.layout.move_to_workspace(Some(&window), index);
+                            let activate = if focus {
+                                ActivateWindow::Smart
+                            } else {
+                                ActivateWindow::No
+                            };
+                            self.niri
+                                .layout
+                                .move_to_workspace(Some(&window), index, activate);
 
                             // If we focused the target window.
                             let new_focus = self.niri.layout.focus();
diff --git a/src/layout/mod.rs b/src/layout/mod.rs
index 64e6877c57..62ece8c022 100644
--- a/src/layout/mod.rs
+++ b/src/layout/mod.rs
@@ -2120,7 +2120,12 @@ impl<W: LayoutElement> Layout<W> {
         monitor.move_to_workspace_down();
     }
 
-    pub fn move_to_workspace(&mut self, window: Option<&W::Id>, idx: usize) {
+    pub fn move_to_workspace(
+        &mut self,
+        window: Option<&W::Id>,
+        idx: usize,
+        activate: ActivateWindow,
+    ) {
         if let Some(InteractiveMoveState::Moving(move_)) = &mut self.interactive_move {
             if window.is_none() || window == Some(move_.tile.window().id()) {
                 return;
@@ -2143,7 +2148,7 @@ impl<W: LayoutElement> Layout<W> {
             };
             monitor
         };
-        monitor.move_to_workspace(window, idx);
+        monitor.move_to_workspace(window, idx, activate);
     }
 
     pub fn move_column_to_workspace_up(&mut self) {
diff --git a/src/layout/monitor.rs b/src/layout/monitor.rs
index 851bffbd5d..b087960613 100644
--- a/src/layout/monitor.rs
+++ b/src/layout/monitor.rs
@@ -474,7 +474,12 @@ impl<W: LayoutElement> Monitor<W> {
         );
     }
 
-    pub fn move_to_workspace(&mut self, window: Option<&W::Id>, idx: usize) {
+    pub fn move_to_workspace(
+        &mut self,
+        window: Option<&W::Id>,
+        idx: usize,
+        activate: ActivateWindow,
+    ) {
         let source_workspace_idx = if let Some(window) = window {
             self.workspaces
                 .iter()
@@ -490,14 +495,11 @@ impl<W: LayoutElement> Monitor<W> {
         }
         let new_id = self.workspaces[new_idx].id();
 
-        let activate = window.map_or(true, |win| {
-            self.active_window().map(|win| win.id()) == Some(win)
+        let activate = activate.map_smart(|| {
+            window.map_or(true, |win| {
+                self.active_window().map(|win| win.id()) == Some(win)
+            })
         });
-        let activate = if activate {
-            ActivateWindow::Yes
-        } else {
-            ActivateWindow::No
-        };
 
         let workspace = &mut self.workspaces[source_workspace_idx];
         let transaction = Transaction::new();
@@ -515,7 +517,11 @@ impl<W: LayoutElement> Monitor<W> {
                 id: new_id,
                 column_idx: None,
             },
-            activate,
+            if activate {
+                ActivateWindow::Yes
+            } else {
+                ActivateWindow::No
+            },
             removed.width,
             removed.is_full_width,
             removed.is_floating,
@@ -578,7 +584,7 @@ impl<W: LayoutElement> Monitor<W> {
 
         let workspace = &mut self.workspaces[source_workspace_idx];
         if workspace.floating_is_active() {
-            self.move_to_workspace(None, idx);
+            self.move_to_workspace(None, idx, ActivateWindow::Smart);
             return;
         }
 
diff --git a/src/layout/tests.rs b/src/layout/tests.rs
index f7e5c759c3..727f53f5db 100644
--- a/src/layout/tests.rs
+++ b/src/layout/tests.rs
@@ -1063,7 +1063,7 @@ impl Op {
                 workspace_idx,
             } => {
                 let window_id = window_id.filter(|id| layout.has_window(id));
-                layout.move_to_workspace(window_id.as_ref(), workspace_idx);
+                layout.move_to_workspace(window_id.as_ref(), workspace_idx, ActivateWindow::Smart);
             }
             Op::MoveColumnToWorkspaceDown => layout.move_column_to_workspace_down(),
             Op::MoveColumnToWorkspaceUp => layout.move_column_to_workspace_up(),


Reviews:
user: login: YaLTeR
state: CHANGES_REQUESTED
author_association: MEMBER
submitted_at: 2025-03-26T05:20:55Z
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-26T06:59:44Z
user: login: YaLTeR
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-26T17:04:26Z

Review comments:
diff_hunk: @@ -426,6 +426,11 @@ pub enum Action {
         /// Reference (index or name) of the workspace to move the window to.
         #[cfg_attr(feature = "clap", arg())]
         reference: WorkspaceReferenceArg,
+
+        /// When set to false, move the window to the workspace without switching
+        /// focus to that workspace. The focus remains on the current workspace.
+        #[cfg_attr(feature = "clap", arg(long))]
+        focus: Option<bool>,
path: niri-ipc/src/lib.rs
user: login: YaLTeR
body: Should be just a `bool` that defaults to `true`.
created_at: 2025-03-26T05:14:54Z
author_association: MEMBER
diff_hunk: @@ -1091,8 +1091,10 @@ impl State {
                             self.move_cursor_to_output(&output);
                         }
                     } else {
-                        self.niri.layout.move_to_workspace(None, index);
-                        self.maybe_warp_cursor_to_focus();
+                        self.niri.layout.move_to_workspace(None, index, focus);
+                        if focus {
+                            self.maybe_warp_cursor_to_focus();
path: src/input/mod.rs
user: login: YaLTeR
body: This should remain unconditional because the cursor should still warp to the newly focused window after this one moves away.
created_at: 2025-03-26T05:15:37Z
author_association: MEMBER
diff_hunk: @@ -1130,12 +1133,16 @@ impl State {
                                 }
                             }
                         } else {
-                            self.niri.layout.move_to_workspace(Some(&window), index);
+                            self.niri
+                                .layout
+                                .move_to_workspace(Some(&window), index, focus);
 
                             // If we focused the target window.
-                            let new_focus = self.niri.layout.focus();
-                            if new_focus.is_some_and(|win| win.window == window) {
-                                self.maybe_warp_cursor_to_focus();
+                            if focus {
path: src/input/mod.rs
user: login: YaLTeR
body: Probably unnecessary extra condition
created_at: 2025-03-26T05:17:00Z
author_association: MEMBER
diff_hunk: @@ -490,9 +490,16 @@ impl<W: LayoutElement> Monitor<W> {
         }
         let new_id = self.workspaces[new_idx].id();
 
-        let activate = window.map_or(true, |win| {
-            self.active_window().map(|win| win.id()) == Some(win)
-        });
+        // Whether to activate the window based on focus parameter and whether the window
+        // is currently focused.
+        let activate = if focus {
path: src/layout/monitor.rs
user: login: YaLTeR
body: Could you replace `focus: bool` with `activate: ActivateWindow` then use `map_smart()` for this check? Pass `Smart` for the current behavior, and `No` for new behavior
created_at: 2025-03-26T05:19:13Z
author_association: MEMBER
diff_hunk: @@ -426,6 +426,11 @@ pub enum Action {
         /// Reference (index or name) of the workspace to move the window to.
         #[cfg_attr(feature = "clap", arg())]
         reference: WorkspaceReferenceArg,
+
+        /// When set to false, move the window to the workspace without switching
+        /// focus to that workspace. The focus remains on the current workspace.
+        #[cfg_attr(feature = "clap", arg(long))]
+        focus: Option<bool>,
path: niri-ipc/src/lib.rs
body: what?
created_at: 2025-03-26T06:59:44Z
author_association: NONE
diff_hunk: @@ -426,6 +426,11 @@ pub enum Action {
         /// Reference (index or name) of the workspace to move the window to.
         #[cfg_attr(feature = "clap", arg())]
         reference: WorkspaceReferenceArg,
+
+        /// When set to false, move the window to the workspace without switching
+        /// focus to that workspace. The focus remains on the current workspace.
+        #[cfg_attr(feature = "clap", arg(long))]
+        focus: Option<bool>,
path: niri-ipc/src/lib.rs
user: login: YaLTeR
body: ```rs
#[cfg_attr(feature = "clap", arg(long, action = clap::ArgAction::Set, default_value_t = true))]
focus: bool,
```
created_at: 2025-03-26T17:02:04Z
author_association: MEMBER
diff_hunk: @@ -578,7 +584,7 @@ impl<W: LayoutElement> Monitor<W> {
 
         let workspace = &mut self.workspaces[source_workspace_idx];
         if workspace.floating_is_active() {
-            self.move_to_workspace(None, idx);
+            self.move_to_workspace(None, idx, ActivateWindow::Yes);
path: src/layout/monitor.rs
user: login: YaLTeR
body: ```suggestion
            self.move_to_workspace(None, idx, ActivateWindow::Smart);
```
created_at: 2025-03-26T17:04:06Z
author_association: MEMBER

PR comments:
user: login: YaLTeR
created_at: 2025-03-24T04:45:29Z
body: I don't really like the name "silent", it's unclear in the window management context. Maybe something like `--focus false` or `--no-focus`? This is similar to how there's a window rule for `open-focused false`.
author_association: MEMBER
user: login: ghost
created_at: 2025-03-24T05:10:30Z
body: > I don't really like the name "silent", it's unclear in the window management context. Maybe something like `--focus false` or `--no-focus`? This is similar to how there's a window rule for `open-focused false`.

changed to focus
author_association: NONE
user: login: ghost
created_at: 2025-03-24T06:00:20Z
body: should be good now, did some minor touchup
author_association: NONE
user: login: YaLTeR
created_at: 2025-03-29T06:55:21Z
body: Thanks
author_association: MEMBER
user: login: YaLTeR
created_at: 2025-03-29T08:12:46Z
body: So I somehow missed this but moving to a workspace on a different output does not honor the focus flag
author_association: MEMBER
user: login: YaLTeR
created_at: 2025-03-29T08:20:50Z
body: Fixed in d91499486e505bba1e1764e1e055ce1ec5eda7fb
author_association: MEMBER