diff --git a/niri-config/src/lib.rs b/niri-config/src/lib.rs index c692844b9..1f4acb008 100644 --- a/niri-config/src/lib.rs +++ b/niri-config/src/lib.rs @@ -1434,7 +1434,10 @@ pub enum Action { #[knuffel(skip)] FullscreenWindowById(u64), #[knuffel(skip)] - FocusWindow(u64), + FocusWindow { + id: u64, + no_mouse_warp: bool, + }, FocusWindowInColumn(#[knuffel(argument)] u8), FocusWindowPrevious, FocusColumnLeft, @@ -1485,9 +1488,9 @@ pub enum Action { CenterWindow, #[knuffel(skip)] CenterWindowById(u64), - FocusWorkspaceDown, - FocusWorkspaceUp, - FocusWorkspace(#[knuffel(argument)] WorkspaceReference), + FocusWorkspaceDown(FocusWorkspaceArgument), + FocusWorkspaceUp(FocusWorkspaceArgument), + FocusWorkspace(FocusWorkspaceWithReference), FocusWorkspacePrevious, MoveWindowToWorkspaceDown, MoveWindowToWorkspaceUp, @@ -1596,6 +1599,20 @@ pub enum Action { ToggleWindowRuleOpacityById(u64), } +#[derive(knuffel::Decode, Debug, Clone, PartialEq)] +pub struct FocusWorkspaceArgument { + #[knuffel(argument, default)] + pub no_mouse_warp: bool, +} + +#[derive(knuffel::Decode, Debug, Clone, PartialEq)] +pub struct FocusWorkspaceWithReference { + #[knuffel(argument)] + pub reference: WorkspaceReference, + #[knuffel(argument, default)] + pub no_mouse_warp: bool, +} + impl From for Action { fn from(value: niri_ipc::Action) -> Self { match value { @@ -1620,7 +1637,9 @@ impl From for Action { niri_ipc::Action::CloseWindow { id: Some(id) } => Self::CloseWindowById(id), niri_ipc::Action::FullscreenWindow { id: None } => Self::FullscreenWindow, niri_ipc::Action::FullscreenWindow { id: Some(id) } => Self::FullscreenWindowById(id), - niri_ipc::Action::FocusWindow { id } => Self::FocusWindow(id), + niri_ipc::Action::FocusWindow { id, no_mouse_warp } => { + Self::FocusWindow { id, no_mouse_warp } + } niri_ipc::Action::FocusWindowInColumn { index } => Self::FocusWindowInColumn(index), niri_ipc::Action::FocusWindowPrevious {} => Self::FocusWindowPrevious, niri_ipc::Action::FocusColumnLeft {} => Self::FocusColumnLeft, @@ -1682,11 +1701,19 @@ impl From for Action { niri_ipc::Action::CenterColumn {} => Self::CenterColumn, niri_ipc::Action::CenterWindow { id: None } => Self::CenterWindow, niri_ipc::Action::CenterWindow { id: Some(id) } => Self::CenterWindowById(id), - niri_ipc::Action::FocusWorkspaceDown {} => Self::FocusWorkspaceDown, - niri_ipc::Action::FocusWorkspaceUp {} => Self::FocusWorkspaceUp, - niri_ipc::Action::FocusWorkspace { reference } => { - Self::FocusWorkspace(WorkspaceReference::from(reference)) + niri_ipc::Action::FocusWorkspaceDown { no_mouse_warp } => { + Self::FocusWorkspaceDown(FocusWorkspaceArgument { no_mouse_warp }) + } + niri_ipc::Action::FocusWorkspaceUp { no_mouse_warp } => { + Self::FocusWorkspaceUp(FocusWorkspaceArgument { no_mouse_warp }) } + niri_ipc::Action::FocusWorkspace { + reference, + no_mouse_warp, + } => Self::FocusWorkspace(FocusWorkspaceWithReference { + reference: WorkspaceReference::from(reference), + no_mouse_warp, + }), niri_ipc::Action::FocusWorkspacePrevious {} => Self::FocusWorkspacePrevious, niri_ipc::Action::MoveWindowToWorkspaceDown {} => Self::MoveWindowToWorkspaceDown, niri_ipc::Action::MoveWindowToWorkspaceUp {} => Self::MoveWindowToWorkspaceUp, @@ -4149,7 +4176,7 @@ mod tests { trigger: Trigger::Keysym(Keysym::_1), modifiers: Modifiers::COMPOSITOR, }, - action: Action::FocusWorkspace(WorkspaceReference::Index(1)), + action: Action::FocusWorkspace( FocusWorkspaceWithReference{reference: WorkspaceReference::Index(1), no_mouse_warp: false}), repeat: true, cooldown: None, allow_when_locked: false, @@ -4161,9 +4188,9 @@ mod tests { trigger: Trigger::Keysym(Keysym::_1), modifiers: Modifiers::COMPOSITOR | Modifiers::SHIFT, }, - action: Action::FocusWorkspace(WorkspaceReference::Name( + action: Action::FocusWorkspace(FocusWorkspaceWithReference{reference: WorkspaceReference::Name( "workspace-1".to_string(), - )), + ), no_mouse_warp: false}), repeat: true, cooldown: None, allow_when_locked: false, @@ -4187,7 +4214,7 @@ mod tests { trigger: Trigger::WheelScrollDown, modifiers: Modifiers::COMPOSITOR, }, - action: Action::FocusWorkspaceDown, + action: Action::FocusWorkspaceDown(FocusWorkspaceArgument{no_mouse_warp: false}), repeat: true, cooldown: Some(Duration::from_millis(150)), allow_when_locked: false, diff --git a/niri-ipc/src/lib.rs b/niri-ipc/src/lib.rs index 3a585eb34..1bd85ba74 100644 --- a/niri-ipc/src/lib.rs +++ b/niri-ipc/src/lib.rs @@ -214,6 +214,9 @@ pub enum Action { /// Id of the window to focus. #[cfg_attr(feature = "clap", arg(long))] id: u64, + /// Disable moving the cursor to the newly focused window or output. + #[cfg_attr(feature = "clap", arg(long))] + no_mouse_warp: bool, }, /// Focus a window in the focused column by index. FocusWindowInColumn { @@ -344,14 +347,25 @@ pub enum Action { id: Option, }, /// Focus the workspace below. - FocusWorkspaceDown {}, + FocusWorkspaceDown { + /// Disable moving the cursor to the newly focused window or output. + #[cfg_attr(feature = "clap", arg(long))] + no_mouse_warp: bool, + }, /// Focus the workspace above. - FocusWorkspaceUp {}, + FocusWorkspaceUp { + /// Disable moving the cursor to the newly focused window or output. + #[cfg_attr(feature = "clap", arg(long))] + no_mouse_warp: bool, + }, /// Focus a workspace by reference (index or name). FocusWorkspace { /// Reference (index or name) of the workspace to focus. #[cfg_attr(feature = "clap", arg())] reference: WorkspaceReferenceArg, + /// Disable moving the cursor to the newly focused window or output. + #[cfg_attr(feature = "clap", arg(long))] + no_mouse_warp: bool, }, /// Focus the previous workspace. FocusWorkspacePrevious {}, diff --git a/src/input/mod.rs b/src/input/mod.rs index 6c3ec8124..23f397519 100644 --- a/src/input/mod.rs +++ b/src/input/mod.rs @@ -679,11 +679,15 @@ impl State { self.niri.queue_redraw_all(); } } - Action::FocusWindow(id) => { + Action::FocusWindow { id, no_mouse_warp } => { let window = self.niri.layout.windows().find(|(_, m)| m.id().get() == id); let window = window.map(|(_, m)| m.window.clone()); if let Some(window) = window { - self.focus_window(&window); + if !no_mouse_warp { + self.focus_window(&window); + return; + } + self.focus_window_without_moving_cursor(&window); } } Action::FocusWindowInColumn(index) => { @@ -1149,21 +1153,28 @@ impl State { self.niri.queue_redraw_all(); } } - Action::FocusWorkspaceDown => { + Action::FocusWorkspaceDown(niri_config::FocusWorkspaceArgument { no_mouse_warp }) => { self.niri.layout.switch_workspace_down(); - self.maybe_warp_cursor_to_focus(); + if !no_mouse_warp { + self.maybe_warp_cursor_to_focus(); + } self.niri.layer_shell_on_demand_focus = None; // FIXME: granular self.niri.queue_redraw_all(); } - Action::FocusWorkspaceUp => { + Action::FocusWorkspaceUp(niri_config::FocusWorkspaceArgument { no_mouse_warp }) => { self.niri.layout.switch_workspace_up(); - self.maybe_warp_cursor_to_focus(); + if !no_mouse_warp { + self.maybe_warp_cursor_to_focus(); + } self.niri.layer_shell_on_demand_focus = None; // FIXME: granular self.niri.queue_redraw_all(); } - Action::FocusWorkspace(reference) => { + Action::FocusWorkspace(niri_config::FocusWorkspaceWithReference { + reference, + no_mouse_warp, + }) => { if let Some((mut output, index)) = self.niri.find_output_and_workspace_index(reference) { @@ -1176,7 +1187,7 @@ impl State { if let Some(output) = output { self.niri.layout.focus_output(&output); self.niri.layout.switch_workspace(index); - if !self.maybe_warp_cursor_to_focus_centered() { + if !no_mouse_warp && !self.maybe_warp_cursor_to_focus_centered() { self.move_cursor_to_output(&output); } } else { @@ -1186,7 +1197,9 @@ impl State { } else { self.niri.layout.switch_workspace(index); } - self.maybe_warp_cursor_to_focus(); + if !no_mouse_warp { + self.maybe_warp_cursor_to_focus(); + } } self.niri.layer_shell_on_demand_focus = None; diff --git a/src/niri.rs b/src/niri.rs index d0df938e8..fe533ea14 100644 --- a/src/niri.rs +++ b/src/niri.rs @@ -775,6 +775,13 @@ impl State { self.niri.queue_redraw_all(); } + /// Focus a specific window without moving the cursor. + pub fn focus_window_without_moving_cursor(&mut self, window: &Window) { + self.niri.layout.activate_window(window); + // FIXME: granular + self.niri.queue_redraw_all(); + } + pub fn maybe_warp_cursor_to_focus(&mut self) -> bool { if !self.niri.config.borrow().input.warp_mouse_to_focus { return false; diff --git a/src/ui/hotkey_overlay.rs b/src/ui/hotkey_overlay.rs index c8165ede9..fa0bb645e 100644 --- a/src/ui/hotkey_overlay.rs +++ b/src/ui/hotkey_overlay.rs @@ -210,8 +210,12 @@ fn render( &Action::FocusColumnRight, &Action::MoveColumnLeft, &Action::MoveColumnRight, - &Action::FocusWorkspaceDown, - &Action::FocusWorkspaceUp, + &Action::FocusWorkspaceDown(niri_config::FocusWorkspaceArgument { + no_mouse_warp: false, + }), + &Action::FocusWorkspaceUp(niri_config::FocusWorkspaceArgument { + no_mouse_warp: false, + }), ]); // Prefer move-column-to-workspace-down, but fall back to move-window-to-workspace-down. @@ -422,8 +426,8 @@ fn action_name(action: &Action) -> String { Action::FocusColumnRight => String::from("Focus Column to the Right"), Action::MoveColumnLeft => String::from("Move Column Left"), Action::MoveColumnRight => String::from("Move Column Right"), - Action::FocusWorkspaceDown => String::from("Switch Workspace Down"), - Action::FocusWorkspaceUp => String::from("Switch Workspace Up"), + Action::FocusWorkspaceDown { .. } => String::from("Switch Workspace Down"), + Action::FocusWorkspaceUp { .. } => String::from("Switch Workspace Up"), Action::MoveColumnToWorkspaceDown => String::from("Move Column to Workspace Down"), Action::MoveColumnToWorkspaceUp => String::from("Move Column to Workspace Up"), Action::MoveWindowToWorkspaceDown => String::from("Move Window to Workspace Down"),