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); + } }