diff --git a/README.md b/README.md index f3660c9d..26913ae7 100644 --- a/README.md +++ b/README.md @@ -475,6 +475,7 @@ unmanage Unmanage a window that was forcibly m reload-configuration Reload ~/komorebi.ahk (if it exists) watch-configuration Enable or disable watching of ~/komorebi.ahk (if it exists) window-hiding-behaviour Set the window behaviour when switching workspaces / cycling stacks +unmanaged-window-operation-behaviour Set the operation behaviour when the focused window is not managed float-rule Add a rule to always float the specified application manage-rule Add a rule to always manage the specified application workspace-rule Add a rule to associate an application with a workspace @@ -487,6 +488,8 @@ toggle-focus-follows-mouse Toggle focus follows mouse for the op mouse-follows-focus Enable or disable mouse follows focus on all workspaces toggle-mouse-follows-focus Toggle mouse follows focus on all workspaces ahk-library Generate a library of AutoHotKey helper functions +ahk-app-specific-configuration Generate common app-specific configurations and fixes to use in komorebi.ahk +format-app-specific-configuration Format a YAML file for use with the 'ahk-app-specific-configuration' command notification-schema Generate a JSON Schema of subscription notifications help Print this message or the help of the given subcommand(s) ``` diff --git a/komorebi-core/src/lib.rs b/komorebi-core/src/lib.rs index 2ed4cbf9..7f7a41cc 100644 --- a/komorebi-core/src/lib.rs +++ b/komorebi-core/src/lib.rs @@ -57,6 +57,7 @@ pub enum SocketMessage { ToggleMaximize, ToggleWindowContainerBehaviour, WindowHidingBehaviour(HidingBehaviour), + UnmanagedWindowOperationBehaviour(OperationBehaviour), // Current Workspace Commands ManageFocusedWindow, UnmanageFocusedWindow, @@ -167,6 +168,13 @@ pub enum HidingBehaviour { Minimize, } +#[derive(Clone, Copy, Debug, Serialize, Deserialize, Display, EnumString, ArgEnum, JsonSchema)] +#[strum(serialize_all = "snake_case")] +pub enum OperationBehaviour { + Op, + NoOp, +} + #[derive(Clone, Copy, Debug, Serialize, Deserialize, Display, EnumString, ArgEnum, JsonSchema)] #[strum(serialize_all = "snake_case")] pub enum Sizing { diff --git a/komorebi/src/process_command.rs b/komorebi/src/process_command.rs index a8ceffd0..064d571b 100644 --- a/komorebi/src/process_command.rs +++ b/komorebi/src/process_command.rs @@ -681,6 +681,9 @@ impl WindowManager { let mut hiding_behaviour = HIDING_BEHAVIOUR.lock(); *hiding_behaviour = behaviour; } + SocketMessage::UnmanagedWindowOperationBehaviour(behaviour) => { + self.unmanaged_window_operation_behaviour = behaviour; + } SocketMessage::NotificationSchema => { let notification = schema_for!(Notification); let schema = serde_json::to_string_pretty(¬ification)?; diff --git a/komorebi/src/window_manager.rs b/komorebi/src/window_manager.rs index f849c571..3e0fd19b 100644 --- a/komorebi/src/window_manager.rs +++ b/komorebi/src/window_manager.rs @@ -22,6 +22,7 @@ use komorebi_core::CycleDirection; use komorebi_core::DefaultLayout; use komorebi_core::FocusFollowsMouseImplementation; use komorebi_core::Layout; +use komorebi_core::OperationBehaviour; use komorebi_core::OperationDirection; use komorebi_core::Rect; use komorebi_core::Sizing; @@ -56,6 +57,7 @@ pub struct WindowManager { pub work_area_offset: Option, pub resize_delta: i32, pub window_container_behaviour: WindowContainerBehaviour, + pub unmanaged_window_operation_behaviour: OperationBehaviour, pub focus_follows_mouse: Option, pub mouse_follows_focus: bool, pub hotwatch: Hotwatch, @@ -172,6 +174,7 @@ impl WindowManager { virtual_desktop_id: current_virtual_desktop(), work_area_offset: None, window_container_behaviour: WindowContainerBehaviour::Create, + unmanaged_window_operation_behaviour: OperationBehaviour::Op, resize_delta: 50, focus_follows_mouse: None, mouse_follows_focus: true, @@ -785,6 +788,21 @@ impl WindowManager { } } + #[tracing::instrument(skip(self))] + fn handle_unmanaged_window_behaviour(&self) -> Result<()> { + if let OperationBehaviour::NoOp = self.unmanaged_window_operation_behaviour { + let workspace = self.focused_workspace()?; + let focused_hwnd = WindowsApi::foreground_window()?; + if !workspace.contains_managed_window(focused_hwnd) { + return Err(anyhow!( + "ignoring commands while active window is not managed by komorebi" + )); + } + } + + Ok(()) + } + #[tracing::instrument(skip(self))] pub fn move_container_to_monitor( &mut self, @@ -792,6 +810,8 @@ impl WindowManager { workspace_idx: Option, follow: bool, ) -> Result<()> { + self.handle_unmanaged_window_behaviour()?; + tracing::info!("moving container"); let invisible_borders = self.invisible_borders; @@ -835,6 +855,8 @@ impl WindowManager { #[tracing::instrument(skip(self))] pub fn move_container_to_workspace(&mut self, idx: usize, follow: bool) -> Result<()> { + self.handle_unmanaged_window_behaviour()?; + tracing::info!("moving container"); let mouse_follows_focus = self.mouse_follows_focus; @@ -879,7 +901,10 @@ impl WindowManager { #[tracing::instrument(skip(self))] pub fn focus_container_in_direction(&mut self, direction: OperationDirection) -> Result<()> { + self.handle_unmanaged_window_behaviour()?; + tracing::info!("focusing container"); + let workspace = self.focused_workspace_mut()?; let new_idx = workspace @@ -894,6 +919,8 @@ impl WindowManager { #[tracing::instrument(skip(self))] pub fn move_container_in_direction(&mut self, direction: OperationDirection) -> Result<()> { + self.handle_unmanaged_window_behaviour()?; + tracing::info!("moving container"); let workspace = self.focused_workspace_mut()?; @@ -910,6 +937,8 @@ impl WindowManager { #[tracing::instrument(skip(self))] pub fn focus_container_in_cycle_direction(&mut self, direction: CycleDirection) -> Result<()> { + self.handle_unmanaged_window_behaviour()?; + tracing::info!("focusing container"); let mut maximize_next = false; let mut monocle_next = false; @@ -945,6 +974,8 @@ impl WindowManager { #[tracing::instrument(skip(self))] pub fn move_container_in_cycle_direction(&mut self, direction: CycleDirection) -> Result<()> { + self.handle_unmanaged_window_behaviour()?; + tracing::info!("moving container"); let workspace = self.focused_workspace_mut()?; @@ -961,6 +992,8 @@ impl WindowManager { #[tracing::instrument(skip(self))] pub fn cycle_container_window_in_direction(&mut self, direction: CycleDirection) -> Result<()> { + self.handle_unmanaged_window_behaviour()?; + tracing::info!("cycling container windows"); let container = self.focused_container_mut()?; @@ -983,6 +1016,8 @@ impl WindowManager { #[tracing::instrument(skip(self))] pub fn add_window_to_container(&mut self, direction: OperationDirection) -> Result<()> { + self.handle_unmanaged_window_behaviour()?; + tracing::info!("adding window to container"); let workspace = self.focused_workspace_mut()?; @@ -1019,6 +1054,8 @@ impl WindowManager { #[tracing::instrument(skip(self))] pub fn promote_container_to_front(&mut self) -> Result<()> { + self.handle_unmanaged_window_behaviour()?; + tracing::info!("promoting container"); let workspace = self.focused_workspace_mut()?; @@ -1028,6 +1065,8 @@ impl WindowManager { #[tracing::instrument(skip(self))] pub fn remove_window_from_container(&mut self) -> Result<()> { + self.handle_unmanaged_window_behaviour()?; + tracing::info!("removing window"); if self.focused_container()?.windows().len() == 1 { @@ -1100,6 +1139,8 @@ impl WindowManager { #[tracing::instrument(skip(self))] pub fn toggle_monocle(&mut self) -> Result<()> { + self.handle_unmanaged_window_behaviour()?; + let workspace = self.focused_workspace_mut()?; match workspace.monocle_container() { @@ -1128,6 +1169,8 @@ impl WindowManager { #[tracing::instrument(skip(self))] pub fn toggle_maximize(&mut self) -> Result<()> { + self.handle_unmanaged_window_behaviour()?; + let workspace = self.focused_workspace_mut()?; match workspace.maximized_window() { diff --git a/komorebi/src/workspace.rs b/komorebi/src/workspace.rs index 89870baa..34899877 100644 --- a/komorebi/src/workspace.rs +++ b/komorebi/src/workspace.rs @@ -334,6 +334,28 @@ impl Workspace { None } + pub fn contains_managed_window(&self, hwnd: isize) -> bool { + for container in self.containers() { + if container.contains_window(hwnd) { + return true; + } + } + + if let Some(window) = self.maximized_window() { + if hwnd == window.hwnd { + return true; + } + } + + if let Some(container) = self.monocle_container() { + if container.contains_window(hwnd) { + return true; + } + } + + false + } + pub fn contains_window(&self, hwnd: isize) -> bool { for container in self.containers() { if container.contains_window(hwnd) { diff --git a/komorebic.lib.sample.ahk b/komorebic.lib.sample.ahk index d62d43f0..8424799a 100644 --- a/komorebic.lib.sample.ahk +++ b/komorebic.lib.sample.ahk @@ -256,6 +256,10 @@ WindowHidingBehaviour(hiding_behaviour) { Run, komorebic.exe window-hiding-behaviour %hiding_behaviour%, , Hide } +UnmanagedWindowOperationBehaviour(operation_behaviour) { + Run, komorebic.exe unmanaged-window-operation-behaviour %operation_behaviour%, , Hide +} + FloatRule(identifier, id) { Run, komorebic.exe float-rule %identifier% "%id%", , Hide } diff --git a/komorebic/src/main.rs b/komorebic/src/main.rs index a0ec0ef8..556a3195 100644 --- a/komorebic/src/main.rs +++ b/komorebic/src/main.rs @@ -36,6 +36,7 @@ use komorebi_core::CycleDirection; use komorebi_core::DefaultLayout; use komorebi_core::FocusFollowsMouseImplementation; use komorebi_core::HidingBehaviour; +use komorebi_core::OperationBehaviour; use komorebi_core::OperationDirection; use komorebi_core::Rect; use komorebi_core::Sizing; @@ -114,6 +115,7 @@ gen_enum_subcommand_args! { MouseFollowsFocus: BooleanState, Query: StateQuery, WindowHidingBehaviour: HidingBehaviour, + UnmanagedWindowOperationBehaviour: OperationBehaviour, } macro_rules! gen_target_subcommand_args { @@ -622,6 +624,9 @@ enum SubCommand { /// Set the window behaviour when switching workspaces / cycling stacks #[clap(arg_required_else_help = true)] WindowHidingBehaviour(WindowHidingBehaviour), + /// Set the operation behaviour when the focused window is not managed + #[clap(arg_required_else_help = true)] + UnmanagedWindowOperationBehaviour(UnmanagedWindowOperationBehaviour), /// Add a rule to always float the specified application #[clap(arg_required_else_help = true)] FloatRule(FloatRule), @@ -1162,6 +1167,12 @@ fn main() -> Result<()> { SubCommand::WindowHidingBehaviour(arg) => { send_message(&*SocketMessage::WindowHidingBehaviour(arg.hiding_behaviour).as_bytes()?)?; } + SubCommand::UnmanagedWindowOperationBehaviour(arg) => { + send_message( + &*SocketMessage::UnmanagedWindowOperationBehaviour(arg.operation_behaviour) + .as_bytes()?, + )?; + } SubCommand::AhkAppSpecificConfiguration(arg) => { let content = fs::read_to_string(resolve_windows_path(&arg.path)?)?; let lines = if let Some(override_path) = arg.override_path {