From 8ffe6f78b7c2a41d1bbe7768c595ca7bf5e41570 Mon Sep 17 00:00:00 2001 From: LGUG2Z Date: Thu, 19 Aug 2021 13:01:09 -0700 Subject: [PATCH] feat(wm): forcibly manage and unmanage windows Added commands to forcibly manage and unmanage windows if they don't get picked up for tiling automatically. This commit adds support for running those operations on the currently focused window, but if there is a need to specify a hwnd to operate on, that could be added pretty easily too in the future, though I'd like to keep the complexity of looking up and passing hwnds to a command out of the CLI if possible. This commit also fixes an issue with restoring floating windows. I'm not sure what happened, but at some point, for me at least, WindowsApi::top_visible_window started returning explorer.exe all the time, so I've switched this out for WindowsApi::foreground_window. I have a feeling I was using TopWindow before, thinking it was GetForegroundWindow, which it isn't, and it wasn't reliable, so I created the top_visible_window abstraction on top of it, which also turned out to be unreliable. Anyway, it's working now. I think the next step will be to create a manage-rule command to compliment the float-rule command which users can use to handle edge cases with their apps in their configuration. re #16 --- komorebi-core/src/lib.rs | 2 ++ komorebi/src/process_command.rs | 6 +++++ komorebi/src/process_event.rs | 11 ++++++++-- komorebi/src/window.rs | 15 +++++++++++++ komorebi/src/window_manager.rs | 33 ++++++++++++++++------------ komorebi/src/window_manager_event.rs | 12 +++++++++- komorebi/src/windows_api.rs | 8 +++++++ komorebi/src/workspace.rs | 2 +- komorebic/src/main.rs | 10 +++++++++ 9 files changed, 81 insertions(+), 18 deletions(-) diff --git a/komorebi-core/src/lib.rs b/komorebi-core/src/lib.rs index 0d566e49..6f785edd 100644 --- a/komorebi-core/src/lib.rs +++ b/komorebi-core/src/lib.rs @@ -34,6 +34,8 @@ pub enum SocketMessage { ToggleMonocle, ToggleMaximize, // Current Workspace Commands + ManageFocusedWindow, + UnmanageFocusedWindow, AdjustContainerPadding(Sizing, i32), AdjustWorkspacePadding(Sizing, i32), ChangeLayout(Layout), diff --git a/komorebi/src/process_command.rs b/komorebi/src/process_command.rs index c6fd01e3..46146e99 100644 --- a/komorebi/src/process_command.rs +++ b/komorebi/src/process_command.rs @@ -221,6 +221,12 @@ impl WindowManager { } ApplicationIdentifier::Title => {} }, + SocketMessage::ManageFocusedWindow => { + self.manage_focused_window()?; + } + SocketMessage::UnmanageFocusedWindow => { + self.unmanage_focused_window()?; + } } tracing::info!("processed"); diff --git a/komorebi/src/process_event.rs b/komorebi/src/process_event.rs index c4316f82..db97c080 100644 --- a/komorebi/src/process_event.rs +++ b/komorebi/src/process_event.rs @@ -101,7 +101,9 @@ impl WindowManager { } match event { - WindowManagerEvent::Minimize(_, window) | WindowManagerEvent::Destroy(_, window) => { + WindowManagerEvent::Minimize(_, window) + | WindowManagerEvent::Destroy(_, window) + | WindowManagerEvent::Unmanage(window) => { self.focused_workspace_mut()?.remove_window(window.hwnd)?; self.update_focused_workspace(false)?; } @@ -152,7 +154,7 @@ impl WindowManager { self.focused_workspace_mut()? .focus_container_by_window(window.hwnd)?; } - WindowManagerEvent::Show(_, window) => { + WindowManagerEvent::Show(_, window) | WindowManagerEvent::Manage(window) => { let mut switch_to = None; for (i, monitors) in self.monitors().iter().enumerate() { for (j, workspace) in monitors.workspaces().iter().enumerate() { @@ -296,6 +298,11 @@ impl WindowManager { WindowManagerEvent::MouseCapture(..) => {} }; + // If we unmanaged a window, it shouldn't be immediately hidden behind managed windows + if let WindowManagerEvent::Unmanage(window) = event { + window.center(&self.focused_monitor_work_area()?)?; + } + tracing::trace!("updating list of known hwnds"); let mut known_hwnds = vec![]; for monitor in self.monitors() { diff --git a/komorebi/src/window.rs b/komorebi/src/window.rs index c5ccd9fc..5c30b940 100644 --- a/komorebi/src/window.rs +++ b/komorebi/src/window.rs @@ -71,6 +71,21 @@ impl Window { HWND(self.hwnd) } + pub fn center(&mut self, work_area: &Rect) -> Result<()> { + let half_width = work_area.right / 2; + let half_weight = work_area.bottom / 2; + + self.set_position( + &Rect { + left: work_area.left + ((work_area.right - half_width) / 2), + top: work_area.top + ((work_area.bottom - half_weight) / 2), + right: half_width, + bottom: half_weight, + }, + true, + ) + } + pub fn set_position(&mut self, layout: &Rect, top: bool) -> Result<()> { // NOTE: This is how the border variable below was calculated; every time this code was // run on any window in any position, the generated border was always the same, so I am diff --git a/komorebi/src/window_manager.rs b/komorebi/src/window_manager.rs index f80e2d8c..8530aafc 100644 --- a/komorebi/src/window_manager.rs +++ b/komorebi/src/window_manager.rs @@ -28,6 +28,7 @@ use crate::ring::Ring; use crate::window::Window; use crate::window_manager_event::WindowManagerEvent; use crate::windows_api::WindowsApi; +use crate::winevent_listener::WINEVENT_CALLBACK_CHANNEL; use crate::workspace::Workspace; use crate::FLOAT_CLASSES; use crate::FLOAT_EXES; @@ -334,6 +335,20 @@ impl WindowManager { Ok(()) } + #[tracing::instrument(skip(self))] + pub fn manage_focused_window(&mut self) -> Result<()> { + let hwnd = WindowsApi::foreground_window()?; + let event = WindowManagerEvent::Manage(Window { hwnd }); + Ok(WINEVENT_CALLBACK_CHANNEL.lock().0.send(event)?) + } + + #[tracing::instrument(skip(self))] + pub fn unmanage_focused_window(&mut self) -> Result<()> { + let hwnd = WindowsApi::foreground_window()?; + let event = WindowManagerEvent::Unmanage(Window { hwnd }); + Ok(WINEVENT_CALLBACK_CHANNEL.lock().0.send(event)?) + } + #[tracing::instrument(skip(self))] pub fn update_focused_workspace(&mut self, mouse_follows_focus: bool) -> Result<()> { tracing::info!("updating"); @@ -616,7 +631,7 @@ impl WindowManager { #[tracing::instrument(skip(self))] pub fn toggle_float(&mut self) -> Result<()> { - let hwnd = WindowsApi::top_visible_window()?; + let hwnd = WindowsApi::foreground_window()?; let workspace = self.focused_workspace_mut()?; let mut is_floating_window = false; @@ -629,11 +644,11 @@ impl WindowManager { if is_floating_window { self.unfloat_window()?; - self.update_focused_workspace(true) } else { self.float_window()?; - self.update_focused_workspace(false) } + + self.update_focused_workspace(is_floating_window) } #[tracing::instrument(skip(self))] @@ -650,17 +665,7 @@ impl WindowManager { .last_mut() .context("there is no floating window")?; - let half_width = work_area.right / 2; - let half_weight = work_area.bottom / 2; - - let center = Rect { - left: work_area.left + ((work_area.right - half_width) / 2), - top: work_area.top + ((work_area.bottom - half_weight) / 2), - right: half_width, - bottom: half_weight, - }; - - window.set_position(¢er, true)?; + window.center(&work_area)?; window.focus()?; Ok(()) diff --git a/komorebi/src/window_manager_event.rs b/komorebi/src/window_manager_event.rs index 14e721de..e97c5d78 100644 --- a/komorebi/src/window_manager_event.rs +++ b/komorebi/src/window_manager_event.rs @@ -14,11 +14,19 @@ pub enum WindowManagerEvent { Show(WinEvent, Window), MoveResizeEnd(WinEvent, Window), MouseCapture(WinEvent, Window), + Manage(Window), + Unmanage(Window), } impl Display for WindowManagerEvent { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { match self { + WindowManagerEvent::Manage(window) => { + write!(f, "Manage (Window: {})", window) + } + WindowManagerEvent::Unmanage(window) => { + write!(f, "Unmanage (Window: {})", window) + } WindowManagerEvent::Destroy(winevent, window) => { write!(f, "Destroy (WinEvent: {}, Window: {})", winevent, window) } @@ -65,7 +73,9 @@ impl WindowManagerEvent { | WindowManagerEvent::Minimize(_, window) | WindowManagerEvent::Show(_, window) | WindowManagerEvent::MoveResizeEnd(_, window) - | WindowManagerEvent::MouseCapture(_, window) => window, + | WindowManagerEvent::MouseCapture(_, window) + | WindowManagerEvent::Manage(window) + | WindowManagerEvent::Unmanage(window) => window, } } diff --git a/komorebi/src/windows_api.rs b/komorebi/src/windows_api.rs index 5db45071..1966518d 100644 --- a/komorebi/src/windows_api.rs +++ b/komorebi/src/windows_api.rs @@ -42,6 +42,7 @@ use bindings::Windows::Win32::UI::WindowsAndMessaging::AllowSetForegroundWindow; use bindings::Windows::Win32::UI::WindowsAndMessaging::EnumWindows; use bindings::Windows::Win32::UI::WindowsAndMessaging::GetCursorPos; use bindings::Windows::Win32::UI::WindowsAndMessaging::GetDesktopWindow; +use bindings::Windows::Win32::UI::WindowsAndMessaging::GetForegroundWindow; use bindings::Windows::Win32::UI::WindowsAndMessaging::GetTopWindow; use bindings::Windows::Win32::UI::WindowsAndMessaging::GetWindow; use bindings::Windows::Win32::UI::WindowsAndMessaging::GetWindowLongPtrW; @@ -264,6 +265,10 @@ impl WindowsApi { Self::show_window(hwnd, SW_MAXIMIZE); } + pub fn foreground_window() -> Result { + Result::from(WindowsResult::from(unsafe { GetForegroundWindow() })) + } + pub fn set_foreground_window(hwnd: HWND) -> Result<()> { match WindowsResult::from(unsafe { SetForegroundWindow(hwnd) }) { WindowsResult::Ok(_) => Ok(()), @@ -280,6 +285,7 @@ impl WindowsApi { } } + #[allow(dead_code)] pub fn top_window() -> Result { Result::from(WindowsResult::from(unsafe { GetTopWindow(HWND::NULL).0 })) } @@ -288,12 +294,14 @@ impl WindowsApi { Result::from(WindowsResult::from(unsafe { GetDesktopWindow() })) } + #[allow(dead_code)] pub fn next_window(hwnd: HWND) -> Result { Result::from(WindowsResult::from(unsafe { GetWindow(hwnd, GW_HWNDNEXT).0 })) } + #[allow(dead_code)] pub fn top_visible_window() -> Result { let hwnd = Self::top_window()?; let mut next_hwnd = hwnd; diff --git a/komorebi/src/workspace.rs b/komorebi/src/workspace.rs index 9bc6bd32..a399ee3f 100644 --- a/komorebi/src/workspace.rs +++ b/komorebi/src/workspace.rs @@ -678,7 +678,7 @@ impl Workspace { } pub fn remove_focused_floating_window(&mut self) -> Option { - let hwnd = WindowsApi::top_visible_window().ok()?; + let hwnd = WindowsApi::foreground_window().ok()?; let mut idx = None; for (i, window) in self.floating_windows.iter().enumerate() { diff --git a/komorebic/src/main.rs b/komorebic/src/main.rs index 47fb98cd..2701688f 100644 --- a/komorebic/src/main.rs +++ b/komorebic/src/main.rs @@ -269,6 +269,10 @@ enum SubCommand { ToggleMaximize, /// Restore all hidden windows (debugging command) RestoreWindows, + /// Force komorebi to manage the focused window + Manage, + /// Unmanage a window that was forcibly managed + Unmanage, /// Reload ~/komorebi.ahk (if it exists) ReloadConfiguration, /// Toggle the automatic reloading of ~/komorebi.ahk (if it exists) @@ -543,6 +547,12 @@ fn main() -> Result<()> { .as_bytes()?, )?; } + SubCommand::Manage => { + send_message(&*SocketMessage::ManageFocusedWindow.as_bytes()?)?; + } + SubCommand::Unmanage => { + send_message(&*SocketMessage::UnmanageFocusedWindow.as_bytes()?)?; + } } Ok(())