diff --git a/niri-config/src/binds.rs b/niri-config/src/binds.rs index 5b0efb97d2..06478dc804 100644 --- a/niri-config/src/binds.rs +++ b/niri-config/src/binds.rs @@ -180,6 +180,8 @@ pub enum Action { FocusWindowUpOrColumnRight, FocusWindowOrWorkspaceDown, FocusWindowOrWorkspaceUp, + FocusWindowOrWorkspaceOrMonitorDown, + FocusWindowOrWorkspaceOrMonitorUp, FocusWindowTop, FocusWindowBottom, FocusWindowDownOrTop, @@ -460,6 +462,12 @@ impl From for Action { niri_ipc::Action::FocusWindowUpOrColumnRight {} => Self::FocusWindowUpOrColumnRight, niri_ipc::Action::FocusWindowOrWorkspaceDown {} => Self::FocusWindowOrWorkspaceDown, niri_ipc::Action::FocusWindowOrWorkspaceUp {} => Self::FocusWindowOrWorkspaceUp, + niri_ipc::Action::FocusWindowOrWorkspaceOrMonitorDown {} => { + Self::FocusWindowOrWorkspaceOrMonitorDown + } + niri_ipc::Action::FocusWindowOrWorkspaceOrMonitorUp {} => { + Self::FocusWindowOrWorkspaceOrMonitorUp + } niri_ipc::Action::FocusWindowTop {} => Self::FocusWindowTop, niri_ipc::Action::FocusWindowBottom {} => Self::FocusWindowBottom, niri_ipc::Action::FocusWindowDownOrTop {} => Self::FocusWindowDownOrTop, diff --git a/niri-ipc/src/lib.rs b/niri-ipc/src/lib.rs index 508456333d..0b5ca02f03 100644 --- a/niri-ipc/src/lib.rs +++ b/niri-ipc/src/lib.rs @@ -378,6 +378,10 @@ pub enum Action { FocusWindowOrWorkspaceDown {}, /// Focus the window or the workspace above. FocusWindowOrWorkspaceUp {}, + /// Focus the window, the workspace below, or the monitor below. + FocusWindowOrWorkspaceOrMonitorDown {}, + /// Focus the window, the workspace above, or the monitor above. + FocusWindowOrWorkspaceOrMonitorUp {}, /// Focus the topmost window. FocusWindowTop {}, /// Focus the bottommost window. diff --git a/src/input/mod.rs b/src/input/mod.rs index ab31df9403..76638d285f 100644 --- a/src/input/mod.rs +++ b/src/input/mod.rs @@ -1265,6 +1265,40 @@ impl State { // FIXME: granular self.niri.queue_redraw_all(); } + Action::FocusWindowOrWorkspaceOrMonitorDown => { + if !self.niri.layout.focus_window_or_workspace_or_monitor_down() { + // Fall back to monitor below + if let Some(output) = self.niri.output_down() { + self.niri.layout.focus_output(&output); + if !self.maybe_warp_cursor_to_focus_centered() { + self.move_cursor_to_output(&output); + } + self.niri.layer_shell_on_demand_focus = None; + } + } else { + self.maybe_warp_cursor_to_focus(); + self.niri.layer_shell_on_demand_focus = None; + } + // FIXME: granular + self.niri.queue_redraw_all(); + } + Action::FocusWindowOrWorkspaceOrMonitorUp => { + if !self.niri.layout.focus_window_or_workspace_or_monitor_up() { + // Fall back to monitor above + if let Some(output) = self.niri.output_up() { + self.niri.layout.focus_output(&output); + if !self.maybe_warp_cursor_to_focus_centered() { + self.move_cursor_to_output(&output); + } + self.niri.layer_shell_on_demand_focus = None; + } + } else { + self.maybe_warp_cursor_to_focus(); + self.niri.layer_shell_on_demand_focus = None; + } + // FIXME: granular + self.niri.queue_redraw_all(); + } Action::FocusWindowTop => { self.niri.layout.focus_window_top(); self.maybe_warp_cursor_to_focus(); diff --git a/src/layout/mod.rs b/src/layout/mod.rs index 010fe65b7c..1a59f47201 100644 --- a/src/layout/mod.rs +++ b/src/layout/mod.rs @@ -2032,6 +2032,20 @@ impl Layout { monitor.focus_window_or_workspace_up(); } + pub fn focus_window_or_workspace_or_monitor_down(&mut self) -> bool { + let Some(monitor) = self.active_monitor() else { + return false; + }; + monitor.focus_window_or_workspace_or_monitor_down() + } + + pub fn focus_window_or_workspace_or_monitor_up(&mut self) -> bool { + let Some(monitor) = self.active_monitor() else { + return false; + }; + monitor.focus_window_or_workspace_or_monitor_up() + } + pub fn focus_window_top(&mut self) { let Some(workspace) = self.active_workspace_mut() else { return; diff --git a/src/layout/monitor.rs b/src/layout/monitor.rs index 420dad6706..86a504a897 100644 --- a/src/layout/monitor.rs +++ b/src/layout/monitor.rs @@ -781,6 +781,20 @@ impl Monitor { } } + pub fn focus_window_or_workspace_or_monitor_down(&mut self) -> bool { + if self.active_workspace().focus_down() { + return true; + } + self.switch_workspace_down() + } + + pub fn focus_window_or_workspace_or_monitor_up(&mut self) -> bool { + if self.active_workspace().focus_up() { + return true; + } + self.switch_workspace_up() + } + pub fn move_to_workspace_up(&mut self, focus: bool) { let source_workspace_idx = self.active_workspace_idx; @@ -974,7 +988,7 @@ impl Monitor { self.add_column(new_idx, column, activate); } - pub fn switch_workspace_up(&mut self) { + pub fn switch_workspace_up(&mut self) -> bool { let new_idx = match &self.workspace_switch { // During a DnD scroll, select the prev apparent workspace. Some(WorkspaceSwitch::Gesture(gesture)) if gesture.dnd_last_event_time.is_some() => { @@ -985,10 +999,12 @@ impl Monitor { _ => self.active_workspace_idx.saturating_sub(1), }; + let changed = new_idx != self.active_workspace_idx; self.activate_workspace(new_idx); + changed } - pub fn switch_workspace_down(&mut self) { + pub fn switch_workspace_down(&mut self) -> bool { let new_idx = match &self.workspace_switch { // During a DnD scroll, select the next apparent workspace. Some(WorkspaceSwitch::Gesture(gesture)) if gesture.dnd_last_event_time.is_some() => { @@ -999,7 +1015,9 @@ impl Monitor { _ => min(self.active_workspace_idx + 1, self.workspaces.len() - 1), }; + let changed = new_idx != self.active_workspace_idx; self.activate_workspace(new_idx); + changed } fn previous_workspace_idx(&self) -> Option { diff --git a/src/layout/tests.rs b/src/layout/tests.rs index e22e860e5d..b7e3c0b9dd 100644 --- a/src/layout/tests.rs +++ b/src/layout/tests.rs @@ -475,6 +475,8 @@ enum Op { FocusWindowUpOrColumnRight, FocusWindowOrWorkspaceDown, FocusWindowOrWorkspaceUp, + FocusWindowOrWorkspaceOrMonitorDown, + FocusWindowOrWorkspaceOrMonitorUp, FocusWindow(#[proptest(strategy = "1..=5usize")] usize), FocusWindowInColumn(#[proptest(strategy = "1..=5u8")] u8), FocusWindowTop, @@ -1123,6 +1125,8 @@ impl Op { Op::FocusWindowUpOrColumnRight => layout.focus_up_or_right(), Op::FocusWindowOrWorkspaceDown => layout.focus_window_or_workspace_down(), Op::FocusWindowOrWorkspaceUp => layout.focus_window_or_workspace_up(), + Op::FocusWindowOrWorkspaceOrMonitorDown => layout.focus_window_or_workspace_or_monitor_down(), + Op::FocusWindowOrWorkspaceOrMonitorUp => layout.focus_window_or_workspace_or_monitor_up(), Op::FocusWindow(id) => layout.activate_window(&id), Op::FocusWindowInColumn(index) => layout.focus_window_in_column(index), Op::FocusWindowTop => layout.focus_window_top(), @@ -1706,10 +1710,12 @@ fn operations_dont_panic() { Op::FocusWindowUpOrColumnLeft, Op::FocusWindowUpOrColumnRight, Op::FocusWindowOrWorkspaceUp, + Op::FocusWindowOrWorkspaceOrMonitorUp, Op::FocusWindowDown, Op::FocusWindowDownOrColumnLeft, Op::FocusWindowDownOrColumnRight, Op::FocusWindowOrWorkspaceDown, + Op::FocusWindowOrWorkspaceOrMonitorDown, Op::MoveColumnLeft, Op::MoveColumnRight, Op::MoveColumnLeftOrToMonitorLeft(0), @@ -1880,10 +1886,12 @@ fn operations_from_starting_state_dont_panic() { Op::FocusWindowUpOrColumnLeft, Op::FocusWindowUpOrColumnRight, Op::FocusWindowOrWorkspaceUp, + Op::FocusWindowOrWorkspaceOrMonitorUp, Op::FocusWindowDown, Op::FocusWindowDownOrColumnLeft, Op::FocusWindowDownOrColumnRight, Op::FocusWindowOrWorkspaceDown, + Op::FocusWindowOrWorkspaceOrMonitorDown, Op::MoveColumnLeft, Op::MoveColumnRight, Op::MoveColumnLeftOrToMonitorLeft(0),