From b799fd30774b5ae9289481a33f735af8c6047650 Mon Sep 17 00:00:00 2001 From: LGUG2Z Date: Mon, 26 Aug 2024 21:40:07 -0700 Subject: [PATCH] feat(wm): add cross boundary behaviour options This commit introduces a new configuration option, cross_boundary_behaviour, which allows the user to decide if they want Focus and Move operations to operate across Workspace or Monitor boundaries. The default behaviour in komorebi has always been Monitor. Setting this to Workspace will make komorebi act a little like PaperWM, where "komorebic focus left" and "komorebic focus right" will switch to the next or previous workspace respectively if the currently focused window as at either the left or right monitor boundary. resolve #959 --- komorebi/src/core/mod.rs | 10 +++ komorebi/src/monitor.rs | 10 ++- komorebi/src/monitor_reconciliator/mod.rs | 2 +- komorebi/src/process_command.rs | 8 +-- komorebi/src/static_config.rs | 8 +++ komorebi/src/window_manager.rs | 83 ++++++++++++++++++++++- komorebi/src/workspace.rs | 11 ++- 7 files changed, 121 insertions(+), 11 deletions(-) diff --git a/komorebi/src/core/mod.rs b/komorebi/src/core/mod.rs index 1f05edae..4633ae28 100644 --- a/komorebi/src/core/mod.rs +++ b/komorebi/src/core/mod.rs @@ -361,6 +361,16 @@ pub enum MoveBehaviour { NoOp, } +#[derive( + Clone, Copy, Debug, Serialize, Deserialize, Display, EnumString, ValueEnum, JsonSchema, +)] +pub enum CrossBoundaryBehaviour { + /// Attempt to perform actions across a workspace boundary + Workspace, + /// Attempt to perform actions across a monitor boundary + Monitor, +} + #[derive( Copy, Clone, Debug, Serialize, Deserialize, Display, EnumString, ValueEnum, JsonSchema, )] diff --git a/komorebi/src/monitor.rs b/komorebi/src/monitor.rs index 2127a7e2..7aea0283 100644 --- a/komorebi/src/monitor.rs +++ b/komorebi/src/monitor.rs @@ -17,6 +17,7 @@ use crate::core::Rect; use crate::container::Container; use crate::ring::Ring; use crate::workspace::Workspace; +use crate::OperationDirection; #[derive( Debug, @@ -114,7 +115,7 @@ impl Monitor { .ok_or_else(|| anyhow!("there is no workspace"))? }; - workspace.add_container(container); + workspace.add_container_to_back(container); Ok(()) } @@ -149,6 +150,7 @@ impl Monitor { &mut self, target_workspace_idx: usize, follow: bool, + direction: Option, ) -> Result<()> { let workspace = self .focused_workspace_mut() @@ -173,7 +175,11 @@ impl Monitor { Some(workspace) => workspace, }; - target_workspace.add_container(container); + if matches!(direction, Some(OperationDirection::Right)) { + target_workspace.add_container_to_front(container); + } else { + target_workspace.add_container_to_back(container); + } if follow { self.focus_workspace(target_workspace_idx)?; diff --git a/komorebi/src/monitor_reconciliator/mod.rs b/komorebi/src/monitor_reconciliator/mod.rs index 06d99926..c610a8bc 100644 --- a/komorebi/src/monitor_reconciliator/mod.rs +++ b/komorebi/src/monitor_reconciliator/mod.rs @@ -319,7 +319,7 @@ pub fn handle_notifications(wm: Arc>) -> color_eyre::Result // Put the orphaned containers somewhere visible for container in orphaned_containers { - focused_ws.add_container(container); + focused_ws.add_container_to_back(container); } // Gotta reset the focus or the movement will feel "off" diff --git a/komorebi/src/process_command.rs b/komorebi/src/process_command.rs index 24479fa3..60fd9e93 100644 --- a/komorebi/src/process_command.rs +++ b/komorebi/src/process_command.rs @@ -441,7 +441,7 @@ impl WindowManager { self.adjust_workspace_padding(sizing, adjustment)?; } SocketMessage::MoveContainerToWorkspaceNumber(workspace_idx) => { - self.move_container_to_workspace(workspace_idx, true)?; + self.move_container_to_workspace(workspace_idx, true, None)?; } SocketMessage::CycleMoveContainerToWorkspace(direction) => { let focused_monitor = self @@ -457,7 +457,7 @@ impl WindowManager { .ok_or_else(|| anyhow!("there must be at least one workspace"))?, ); - self.move_container_to_workspace(workspace_idx, true)?; + self.move_container_to_workspace(workspace_idx, true, None)?; } SocketMessage::MoveContainerToMonitorNumber(monitor_idx) => { self.move_container_to_monitor(monitor_idx, None, true)?; @@ -475,7 +475,7 @@ impl WindowManager { self.move_container_to_monitor(monitor_idx, None, true)?; } SocketMessage::SendContainerToWorkspaceNumber(workspace_idx) => { - self.move_container_to_workspace(workspace_idx, false)?; + self.move_container_to_workspace(workspace_idx, false, None)?; } SocketMessage::CycleSendContainerToWorkspace(direction) => { let focused_monitor = self @@ -491,7 +491,7 @@ impl WindowManager { .ok_or_else(|| anyhow!("there must be at least one workspace"))?, ); - self.move_container_to_workspace(workspace_idx, false)?; + self.move_container_to_workspace(workspace_idx, false, None)?; } SocketMessage::SendContainerToMonitorNumber(monitor_idx) => { self.move_container_to_monitor(monitor_idx, None, false)?; diff --git a/komorebi/src/static_config.rs b/komorebi/src/static_config.rs index 26c47815..690d66f9 100644 --- a/komorebi/src/static_config.rs +++ b/komorebi/src/static_config.rs @@ -26,6 +26,7 @@ use crate::window_manager::WindowManager; use crate::window_manager_event::WindowManagerEvent; use crate::windows_api::WindowsApi; use crate::workspace::Workspace; +use crate::CrossBoundaryBehaviour; use crate::ANIMATION_DURATION; use crate::ANIMATION_ENABLED; use crate::ANIMATION_FPS; @@ -265,6 +266,9 @@ pub struct StaticConfig { /// Determine what happens when a window is moved across a monitor boundary (default: Swap) #[serde(skip_serializing_if = "Option::is_none")] pub cross_monitor_move_behaviour: Option, + /// Determine what happens when an action is called on a window at a monitor boundary (default: Monitor) + #[serde(skip_serializing_if = "Option::is_none")] + pub cross_boundary_behaviour: Option, /// Determine what happens when commands are sent while an unmanaged window is in the foreground (default: Op) #[serde(skip_serializing_if = "Option::is_none")] pub unmanaged_window_operation_behaviour: Option, @@ -565,6 +569,7 @@ impl From<&WindowManager> for StaticConfig { resize_delta: Option::from(value.resize_delta), window_container_behaviour: Option::from(value.window_container_behaviour), cross_monitor_move_behaviour: Option::from(value.cross_monitor_move_behaviour), + cross_boundary_behaviour: Option::from(value.cross_boundary_behaviour), unmanaged_window_operation_behaviour: Option::from( value.unmanaged_window_operation_behaviour, ), @@ -866,6 +871,9 @@ impl StaticConfig { cross_monitor_move_behaviour: value .cross_monitor_move_behaviour .unwrap_or(MoveBehaviour::Swap), + cross_boundary_behaviour: value + .cross_boundary_behaviour + .unwrap_or(CrossBoundaryBehaviour::Monitor), unmanaged_window_operation_behaviour: value .unmanaged_window_operation_behaviour .unwrap_or(OperationBehaviour::Op), diff --git a/komorebi/src/window_manager.rs b/komorebi/src/window_manager.rs index d05d3f59..f158f081 100644 --- a/komorebi/src/window_manager.rs +++ b/komorebi/src/window_manager.rs @@ -65,6 +65,7 @@ use crate::winevent_listener; use crate::workspace::Workspace; use crate::BorderColours; use crate::Colour; +use crate::CrossBoundaryBehaviour; use crate::Rgb; use crate::WorkspaceRule; use crate::CUSTOM_FFM; @@ -92,6 +93,7 @@ pub struct WindowManager { pub resize_delta: i32, pub window_container_behaviour: WindowContainerBehaviour, pub cross_monitor_move_behaviour: MoveBehaviour, + pub cross_boundary_behaviour: CrossBoundaryBehaviour, pub unmanaged_window_operation_behaviour: OperationBehaviour, pub focus_follows_mouse: Option, pub mouse_follows_focus: bool, @@ -274,6 +276,7 @@ impl WindowManager { work_area_offset: None, window_container_behaviour: WindowContainerBehaviour::Create, cross_monitor_move_behaviour: MoveBehaviour::Swap, + cross_boundary_behaviour: CrossBoundaryBehaviour::Workspace, unmanaged_window_operation_behaviour: OperationBehaviour::Op, resize_delta: 50, focus_follows_mouse: None, @@ -1119,7 +1122,7 @@ impl WindowManager { if focused_monitor_idx == monitor_idx { if let Some(workspace_idx) = workspace_idx { - return self.move_container_to_workspace(workspace_idx, follow); + return self.move_container_to_workspace(workspace_idx, follow, None); } } @@ -1187,7 +1190,12 @@ impl WindowManager { } #[tracing::instrument(skip(self))] - pub fn move_container_to_workspace(&mut self, idx: usize, follow: bool) -> Result<()> { + pub fn move_container_to_workspace( + &mut self, + idx: usize, + follow: bool, + direction: Option, + ) -> Result<()> { self.handle_unmanaged_window_behaviour()?; tracing::info!("moving container"); @@ -1197,7 +1205,7 @@ impl WindowManager { .focused_monitor_mut() .ok_or_else(|| anyhow!("there is no monitor"))?; - monitor.move_container_to_workspace(idx, follow)?; + monitor.move_container_to_workspace(idx, follow, direction)?; monitor.load_focused_workspace(mouse_follows_focus)?; self.update_focused_workspace(mouse_follows_focus, true)?; @@ -1239,6 +1247,7 @@ impl WindowManager { self.handle_unmanaged_window_behaviour()?; let workspace = self.focused_workspace()?; + let workspace_idx = self.focused_workspace_idx()?; tracing::info!("focusing container"); @@ -1250,6 +1259,39 @@ impl WindowManager { let mut cross_monitor_monocle = false; + if new_idx.is_none() + && matches!( + self.cross_boundary_behaviour, + CrossBoundaryBehaviour::Workspace + ) + && matches!( + direction, + OperationDirection::Left | OperationDirection::Right + ) + { + let workspace_count = if let Some(monitor) = self.focused_monitor() { + monitor.workspaces().len() + } else { + 1 + }; + + let next_idx = match direction { + OperationDirection::Left => match workspace_idx { + 0 => workspace_count - 1, + n => n - 1, + }, + OperationDirection::Right => match workspace_idx { + n if n == workspace_count - 1 => 0, + n => n + 1, + }, + _ => workspace_idx, + }; + + self.focus_workspace(next_idx)?; + + return Ok(()); + } + // if there is no container in that direction for this workspace match new_idx { None => { @@ -1292,6 +1334,7 @@ impl WindowManager { self.handle_unmanaged_window_behaviour()?; let workspace = self.focused_workspace()?; + let workspace_idx = self.focused_workspace_idx()?; // removing this messes up the monitor / container / window index somewhere // and results in the wrong window getting moved across the monitor boundary @@ -1305,6 +1348,40 @@ impl WindowManager { let origin_monitor_idx = self.focused_monitor_idx(); let target_container_idx = workspace.new_idx_for_direction(direction); + if target_container_idx.is_none() + && matches!( + self.cross_boundary_behaviour, + CrossBoundaryBehaviour::Workspace + ) + && matches!( + direction, + OperationDirection::Left | OperationDirection::Right + ) + { + let workspace_count = if let Some(monitor) = self.focused_monitor() { + monitor.workspaces().len() + } else { + 1 + }; + + let next_idx = match direction { + OperationDirection::Left => match workspace_idx { + 0 => workspace_count - 1, + n => n - 1, + }, + OperationDirection::Right => match workspace_idx { + n if n == workspace_count - 1 => 0, + n => n + 1, + }, + _ => workspace_idx, + }; + + self.move_container_to_workspace(next_idx, true, Some(direction))?; + self.update_focused_workspace(self.mouse_follows_focus, true)?; + + return Ok(()); + } + match target_container_idx { // If there is nowhere to move on the current workspace, try to move it onto the monitor // in that direction if there is one diff --git a/komorebi/src/workspace.rs b/komorebi/src/workspace.rs index 45bce295..63dc582d 100644 --- a/komorebi/src/workspace.rs +++ b/komorebi/src/workspace.rs @@ -656,11 +656,16 @@ impl Workspace { Ok(()) } - pub fn add_container(&mut self, container: Container) { + pub fn add_container_to_back(&mut self, container: Container) { self.containers_mut().push_back(container); self.focus_last_container(); } + pub fn add_container_to_front(&mut self, container: Container) { + self.containers_mut().push_front(container); + self.focus_first_container(); + } + pub fn insert_container_at_idx(&mut self, idx: usize, container: Container) { self.containers_mut().insert(idx, container); } @@ -1442,4 +1447,8 @@ impl Workspace { fn focus_last_container(&mut self) { self.focus_container(self.containers().len().saturating_sub(1)); } + + fn focus_first_container(&mut self) { + self.focus_container(0); + } }