diff --git a/komorebi/src/core/mod.rs b/komorebi/src/core/mod.rs index c08cb4b70..7cbe78363 100644 --- a/komorebi/src/core/mod.rs +++ b/komorebi/src/core/mod.rs @@ -77,6 +77,7 @@ pub enum SocketMessage { ToggleMonocle, ToggleMaximize, ToggleWindowContainerBehaviour, + ToggleFloatOverride, WindowHidingBehaviour(HidingBehaviour), ToggleCrossMonitorMoveBehaviour, CrossMonitorMoveBehaviour(MoveBehaviour), @@ -90,6 +91,8 @@ pub enum SocketMessage { CycleLayout(CycleDirection), ChangeLayoutCustom(PathBuf), FlipLayout(Axis), + ToggleWorkspaceWindowContainerBehaviour, + ToggleWorkspaceFloatOverride, // Monitor and Workspace Commands MonitorIndexPreference(usize, i32, i32, i32, i32), DisplayIndexPreference(usize, String), @@ -343,10 +346,23 @@ pub enum FocusFollowsMouseImplementation { } #[derive( - Clone, Copy, Debug, Serialize, Deserialize, Display, EnumString, ValueEnum, JsonSchema, + Clone, Copy, Debug, Default, Serialize, Deserialize, JsonSchema, PartialEq, +)] +pub struct WindowManagementBehaviour { + /// The current WindowContainerBehaviour to be used + pub current_behaviour: WindowContainerBehaviour, + /// Override of `current_behaviour` to open new windows as floating windows + /// that can be later toggled to tiled, when false it will default to + /// `current_behaviour` again. + pub float_override: bool, +} + +#[derive( + Clone, Copy, Debug, Default, Serialize, Deserialize, Display, EnumString, ValueEnum, JsonSchema, PartialEq )] pub enum WindowContainerBehaviour { /// Create a new container for each new window + #[default] Create, /// Append new windows to the focused window container Append, diff --git a/komorebi/src/process_command.rs b/komorebi/src/process_command.rs index 09fa303ab..ac0aa2425 100644 --- a/komorebi/src/process_command.rs +++ b/komorebi/src/process_command.rs @@ -1346,15 +1346,42 @@ impl WindowManager { self.resize_delta = delta; } SocketMessage::ToggleWindowContainerBehaviour => { - match self.window_container_behaviour { + match self.window_management_behaviour.current_behaviour { WindowContainerBehaviour::Create => { - self.window_container_behaviour = WindowContainerBehaviour::Append; + self.window_management_behaviour.current_behaviour = WindowContainerBehaviour::Append; } WindowContainerBehaviour::Append => { - self.window_container_behaviour = WindowContainerBehaviour::Create; + self.window_management_behaviour.current_behaviour = WindowContainerBehaviour::Create; } } } + SocketMessage::ToggleFloatOverride => { + self.window_management_behaviour.float_override = !self.window_management_behaviour.float_override; + } + SocketMessage::ToggleWorkspaceWindowContainerBehaviour => { + let current_global_behaviour = self.window_management_behaviour.current_behaviour; + if let Some(behaviour) = self.focused_workspace_mut()?.window_container_behaviour_mut() { + match behaviour { + WindowContainerBehaviour::Create => *behaviour = WindowContainerBehaviour::Append, + WindowContainerBehaviour::Append => *behaviour = WindowContainerBehaviour::Create, + } + } else { + self.focused_workspace_mut()?.set_window_container_behaviour( + Some(match current_global_behaviour { + WindowContainerBehaviour::Create => WindowContainerBehaviour::Append, + WindowContainerBehaviour::Append => WindowContainerBehaviour::Create, + }) + ); + }; + } + SocketMessage::ToggleWorkspaceFloatOverride => { + let current_global_override = self.window_management_behaviour.float_override; + if let Some(float_override) = self.focused_workspace_mut()?.float_override_mut() { + *float_override = !*float_override; + } else { + self.focused_workspace_mut()?.set_float_override(Some(!current_global_override)); + }; + } SocketMessage::WindowHidingBehaviour(behaviour) => { let mut hiding_behaviour = HIDING_BEHAVIOUR.lock(); *hiding_behaviour = behaviour; diff --git a/komorebi/src/process_event.rs b/komorebi/src/process_event.rs index 30a8a952c..46fd9e038 100644 --- a/komorebi/src/process_event.rs +++ b/komorebi/src/process_event.rs @@ -333,8 +333,8 @@ impl WindowManager { } if proceed { - let behaviour = - self.window_container_behaviour(focused_monitor_idx, focused_workspace_idx); + let mut behaviour = + self.window_management_behaviour(focused_monitor_idx, focused_workspace_idx); let workspace = self.focused_workspace_mut()?; let workspace_contains_window = workspace.contains_window(window.hwnd); let monocle_container = workspace.monocle_container().clone(); @@ -360,11 +360,13 @@ impl WindowManager { } } - if should_float && !matches!(event, WindowManagerEvent::Manage(_)) { + behaviour.float_override = behaviour.float_override || (should_float && !matches!(event, WindowManagerEvent::Manage(_))); + + if behaviour.float_override { workspace.floating_windows_mut().push(window); - self.update_focused_workspace(false, true)?; + self.update_focused_workspace(false, false)?; } else { - match behaviour { + match behaviour.current_behaviour { WindowContainerBehaviour::Create => { workspace.new_container_for_window(window); self.update_focused_workspace(false, false)?; @@ -375,7 +377,6 @@ impl WindowManager { .ok_or_else(|| anyhow!("there is no focused container"))? .add_window(window); self.update_focused_workspace(true, false)?; - stackbar_manager::send_notification(); } } @@ -431,8 +432,8 @@ impl WindowManager { let focused_monitor_idx = self.focused_monitor_idx(); let focused_workspace_idx = self.focused_workspace_idx().unwrap_or_default(); - let window_container_behaviour = - self.window_container_behaviour(focused_monitor_idx, focused_workspace_idx); + let window_management_behaviour = + self.window_management_behaviour(focused_monitor_idx, focused_workspace_idx); let workspace = self.focused_workspace_mut()?; let focused_container_idx = workspace.focused_container_idx(); @@ -550,8 +551,11 @@ impl WindowManager { } // Here we handle a simple move on the same monitor which is treated as // a container swap + } else if window_management_behaviour.float_override { + workspace.floating_windows_mut().push(window); + self.update_focused_workspace(false, false)?; } else { - match window_container_behaviour { + match window_management_behaviour.current_behaviour { WindowContainerBehaviour::Create => { match workspace.container_idx_from_current_point() { Some(target_idx) => { diff --git a/komorebi/src/static_config.rs b/komorebi/src/static_config.rs index 2114bc6cd..1e9ee0ad7 100644 --- a/komorebi/src/static_config.rs +++ b/komorebi/src/static_config.rs @@ -68,6 +68,7 @@ use crate::core::OperationBehaviour; use crate::core::Rect; use crate::core::SocketMessage; use crate::core::WindowContainerBehaviour; +use crate::core::WindowManagementBehaviour; use color_eyre::Result; use crossbeam_channel::Receiver; use hotwatch::EventKind; @@ -130,6 +131,13 @@ pub struct WorkspaceConfig { /// Apply this monitor's window-based work area offset (default: true) #[serde(skip_serializing_if = "Option::is_none")] pub apply_window_based_work_area_offset: Option, + /// Determine what happens when a new window is opened (default: Create) + #[serde(skip_serializing_if = "Option::is_none")] + pub window_container_behaviour: Option, + /// Enable or disable float override, which makes it so every new window opens in floating mode + /// (default: false) + #[serde(skip_serializing_if = "Option::is_none")] + pub float_override: Option, } impl From<&Workspace> for WorkspaceConfig { @@ -182,6 +190,8 @@ impl From<&Workspace> for WorkspaceConfig { initial_workspace_rules: None, workspace_rules: None, apply_window_based_work_area_offset: Some(value.apply_window_based_work_area_offset()), + window_container_behaviour: *value.window_container_behaviour(), + float_override: *value.float_override(), } } } @@ -235,6 +245,10 @@ pub struct StaticConfig { /// Determine what happens when a new window is opened (default: Create) #[serde(skip_serializing_if = "Option::is_none")] pub window_container_behaviour: Option, + /// Enable or disable float override, which makes it so every new window opens in floating mode + /// (default: false) + #[serde(skip_serializing_if = "Option::is_none")] + pub float_override: Option, /// 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, @@ -520,7 +534,8 @@ impl From<&WindowManager> for StaticConfig { Self { invisible_borders: None, resize_delta: Option::from(value.resize_delta), - window_container_behaviour: Option::from(value.window_container_behaviour), + window_container_behaviour: Option::from(value.window_management_behaviour.current_behaviour), + float_override: Option::from(value.window_management_behaviour.float_override), 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( @@ -1031,9 +1046,10 @@ impl StaticConfig { is_paused: false, virtual_desktop_id: current_virtual_desktop(), work_area_offset: value.global_work_area_offset, - window_container_behaviour: value - .window_container_behaviour - .unwrap_or(WindowContainerBehaviour::Create), + window_management_behaviour: WindowManagementBehaviour { + current_behaviour: value.window_container_behaviour.unwrap_or(WindowContainerBehaviour::Create), + float_override: value.float_override.unwrap_or_default(), + }, cross_monitor_move_behaviour: value .cross_monitor_move_behaviour .unwrap_or(MoveBehaviour::Swap), @@ -1208,7 +1224,11 @@ impl StaticConfig { } if let Some(val) = value.window_container_behaviour { - wm.window_container_behaviour = val; + wm.window_management_behaviour.current_behaviour = val; + } + + if let Some(val) = value.float_override { + wm.window_management_behaviour.float_override = val; } if let Some(val) = value.cross_monitor_move_behaviour { diff --git a/komorebi/src/window_manager.rs b/komorebi/src/window_manager.rs index 92f222a69..4631ea7cc 100644 --- a/komorebi/src/window_manager.rs +++ b/komorebi/src/window_manager.rs @@ -39,6 +39,7 @@ use crate::core::Rect; use crate::core::Sizing; use crate::core::StackbarLabel; use crate::core::WindowContainerBehaviour; +use crate::core::WindowManagementBehaviour; use crate::border_manager; use crate::border_manager::STYLE; @@ -92,7 +93,7 @@ pub struct WindowManager { pub is_paused: bool, pub work_area_offset: Option, pub resize_delta: i32, - pub window_container_behaviour: WindowContainerBehaviour, + pub window_management_behaviour: WindowManagementBehaviour, pub cross_monitor_move_behaviour: MoveBehaviour, pub cross_boundary_behaviour: CrossBoundaryBehaviour, pub unmanaged_window_operation_behaviour: OperationBehaviour, @@ -112,6 +113,7 @@ pub struct State { pub is_paused: bool, pub resize_delta: i32, pub new_window_behaviour: WindowContainerBehaviour, + pub float_override: bool, pub cross_monitor_move_behaviour: MoveBehaviour, pub unmanaged_window_operation_behaviour: OperationBehaviour, pub work_area_offset: Option, @@ -215,7 +217,8 @@ impl From<&WindowManager> for State { is_paused: wm.is_paused, work_area_offset: wm.work_area_offset, resize_delta: wm.resize_delta, - new_window_behaviour: wm.window_container_behaviour, + new_window_behaviour: wm.window_management_behaviour.current_behaviour, + float_override: wm.window_management_behaviour.float_override, cross_monitor_move_behaviour: wm.cross_monitor_move_behaviour, focus_follows_mouse: wm.focus_follows_mouse, mouse_follows_focus: wm.mouse_follows_focus, @@ -275,7 +278,7 @@ impl WindowManager { is_paused: false, virtual_desktop_id: current_virtual_desktop(), work_area_offset: None, - window_container_behaviour: WindowContainerBehaviour::Create, + window_management_behaviour: WindowManagementBehaviour::default(), cross_monitor_move_behaviour: MoveBehaviour::Swap, cross_boundary_behaviour: CrossBoundaryBehaviour::Workspace, unmanaged_window_operation_behaviour: OperationBehaviour::Op, @@ -308,22 +311,44 @@ impl WindowManager { StaticConfig::reload(pathbuf, self) } - pub fn window_container_behaviour( + pub fn window_management_behaviour( &self, monitor_idx: usize, workspace_idx: usize, - ) -> WindowContainerBehaviour { + ) -> WindowManagementBehaviour { if let Some(monitor) = self.monitors().get(monitor_idx) { if let Some(workspace) = monitor.workspaces().get(workspace_idx) { - return if workspace.containers().is_empty() { + let current_behaviour = if let Some(behaviour) = workspace.window_container_behaviour() { + if workspace.containers().is_empty() && matches!(behaviour, WindowContainerBehaviour::Append) { + // You can't append to an empty workspace + WindowContainerBehaviour::Create + } else { + *behaviour + } + } else if workspace.containers().is_empty() && matches!(self.window_management_behaviour.current_behaviour, WindowContainerBehaviour::Append) { + // You can't append to an empty workspace WindowContainerBehaviour::Create } else { - self.window_container_behaviour + self.window_management_behaviour.current_behaviour + }; + + let float_override = if let Some(float_override) = workspace.float_override() { + *float_override + } else { + self.window_management_behaviour.float_override + }; + + return WindowManagementBehaviour { + current_behaviour, + float_override }; } } - WindowContainerBehaviour::Create + WindowManagementBehaviour { + current_behaviour: WindowContainerBehaviour::Create, + float_override: self.window_management_behaviour.float_override, + } } #[tracing::instrument(skip(self))] @@ -846,6 +871,8 @@ impl WindowManager { && self.focused_workspace()?.maximized_window().is_none() // and we don't have a monocle container && self.focused_workspace()?.monocle_container().is_none() + // and we don't have any floating windows that should show on top + && self.focused_workspace()?.floating_windows().is_empty() { if let Ok(window) = self.focused_window_mut() { if trigger_focus { diff --git a/komorebi/src/workspace.rs b/komorebi/src/workspace.rs index 39a846e91..a7392159d 100644 --- a/komorebi/src/workspace.rs +++ b/komorebi/src/workspace.rs @@ -30,6 +30,7 @@ use crate::static_config::WorkspaceConfig; use crate::window::Window; use crate::window::WindowDetails; use crate::windows_api::WindowsApi; +use crate::WindowContainerBehaviour; use crate::DEFAULT_CONTAINER_PADDING; use crate::DEFAULT_WORKSPACE_PADDING; use crate::INITIAL_CONFIGURATION_LOADED; @@ -83,6 +84,10 @@ pub struct Workspace { tile: bool, #[getset(get_copy = "pub", set = "pub")] apply_window_based_work_area_offset: bool, + #[getset(get = "pub", get_mut = "pub", set = "pub")] + window_container_behaviour: Option, + #[getset(get = "pub", get_mut = "pub", set = "pub")] + float_override: Option, } impl_ring_elements!(Workspace, Container); @@ -106,6 +111,8 @@ impl Default for Workspace { resize_dimensions: vec![], tile: true, apply_window_based_work_area_offset: true, + window_container_behaviour: None, + float_override: None, } } } @@ -162,6 +169,14 @@ impl Workspace { config.apply_window_based_work_area_offset.unwrap_or(true), ); + if config.window_container_behaviour.is_some() { + self.set_window_container_behaviour(config.window_container_behaviour); + } + + if config.float_override.is_some() { + self.set_float_override(config.float_override); + } + Ok(()) } @@ -217,10 +232,6 @@ impl Workspace { container.restore(); } - for container in self.containers_mut() { - container.restore(); - } - if let Some(container) = self.focused_container_mut() { container.focus_window(container.focused_window_idx()); } diff --git a/komorebic/src/main.rs b/komorebic/src/main.rs index 0079f43fc..f805de6d2 100644 --- a/komorebic/src/main.rs +++ b/komorebic/src/main.rs @@ -1167,6 +1167,16 @@ enum SubCommand { WorkspaceName(WorkspaceName), /// Toggle the behaviour for new windows (stacking or dynamic tiling) ToggleWindowContainerBehaviour, + /// Enable or disable float override, which makes it so every new window opens in floating mode + ToggleFloatOverride, + /// Toggle the behaviour for new windows (stacking or dynamic tiling) for currently focused + /// workspace. If there was no behaviour set for the workspace previously it takes the opposite + /// of the global value. + ToggleWorkspaceWindowContainerBehaviour, + /// Enable or disable float override, which makes it so every new window opens in floating + /// mode, for the currently focused workspace. If there was no override value set for the + /// workspace previously it takes the opposite of the global value. + ToggleWorkspaceFloatOverride, /// Toggle window tiling on the focused workspace TogglePause, /// Toggle window tiling on the focused workspace @@ -2470,6 +2480,15 @@ Stop-Process -Name:komorebi -ErrorAction SilentlyContinue SubCommand::ToggleWindowContainerBehaviour => { send_message(&SocketMessage::ToggleWindowContainerBehaviour)?; } + SubCommand::ToggleFloatOverride => { + send_message(&SocketMessage::ToggleFloatOverride)?; + } + SubCommand::ToggleWorkspaceWindowContainerBehaviour => { + send_message(&SocketMessage::ToggleWorkspaceWindowContainerBehaviour)?; + } + SubCommand::ToggleWorkspaceFloatOverride => { + send_message(&SocketMessage::ToggleWorkspaceFloatOverride)?; + } SubCommand::WindowHidingBehaviour(arg) => { send_message(&SocketMessage::WindowHidingBehaviour(arg.hiding_behaviour))?; }