From c4c8bd7d4b7a4c4e7a7a291987441071e31a1ce4 Mon Sep 17 00:00:00 2001 From: LGUG2Z Date: Fri, 10 Sep 2021 12:46:49 -0700 Subject: [PATCH] feat(wm): reconcile monitor state When monitors turn on and off, they do not retain their hmonitor id, therefore this commit introduces an initial attempt to reconcile invalid and valid hmonitors after monitor changes based on the windows that are assigned to them. If a monitor has at least one window, and has been assigned a new hmonitor id, komorebi will look up the current hmonitor of that window's hwnd and update Monitor.id in-place. When reconciling monitors, any monitor marked as invalid will be purged from the window manager state. This commit also applies some of the new clippy lints that come along with the latest nightly release of Rust. resolve #31 --- komorebi-core/src/rect.rs | 13 +---------- komorebi/src/monitor.rs | 10 +++++--- komorebi/src/process_event.rs | 5 ++-- komorebi/src/window_manager.rs | 38 +++++++++++++++++++++++++++++++ komorebi/src/windows_api.rs | 21 +++++++++++------ komorebi/src/windows_callbacks.rs | 19 ++++++++++++++++ komorebic/src/main.rs | 9 ++++---- 7 files changed, 86 insertions(+), 29 deletions(-) diff --git a/komorebi-core/src/rect.rs b/komorebi-core/src/rect.rs index 85f21b89..e2c68f71 100644 --- a/komorebi-core/src/rect.rs +++ b/komorebi-core/src/rect.rs @@ -2,7 +2,7 @@ use serde::Serialize; use bindings::Windows::Win32::Foundation::RECT; -#[derive(Debug, Clone, Copy, Serialize, Eq, PartialEq)] +#[derive(Debug, Default, Clone, Copy, Serialize, Eq, PartialEq)] pub struct Rect { pub left: i32, pub top: i32, @@ -10,17 +10,6 @@ pub struct Rect { pub bottom: i32, } -impl Default for Rect { - fn default() -> Self { - Self { - left: 0, - top: 0, - right: 0, - bottom: 0, - } - } -} - impl From for Rect { fn from(rect: RECT) -> Self { Self { diff --git a/komorebi/src/monitor.rs b/komorebi/src/monitor.rs index f12fb29b..58b49652 100644 --- a/komorebi/src/monitor.rs +++ b/komorebi/src/monitor.rs @@ -6,6 +6,7 @@ use color_eyre::Result; use getset::CopyGetters; use getset::Getters; use getset::MutGetters; +use getset::Setters; use serde::Serialize; use komorebi_core::Rect; @@ -14,9 +15,9 @@ use crate::container::Container; use crate::ring::Ring; use crate::workspace::Workspace; -#[derive(Debug, Clone, Serialize, Getters, CopyGetters, MutGetters)] +#[derive(Debug, Clone, Serialize, Getters, CopyGetters, MutGetters, Setters)] pub struct Monitor { - #[getset(get_copy = "pub")] + #[getset(get_copy = "pub", set = "pub")] id: isize, monitor_size: Rect, #[getset(get = "pub")] @@ -30,11 +31,14 @@ pub struct Monitor { impl_ring_elements!(Monitor, Workspace); pub fn new(id: isize, monitor_size: Rect, work_area_size: Rect) -> Monitor { + let mut workspaces = Ring::default(); + workspaces.elements_mut().push_back(Workspace::default()); + Monitor { id, monitor_size, work_area_size, - workspaces: Ring::default(), + workspaces, workspace_names: HashMap::default(), } } diff --git a/komorebi/src/process_event.rs b/komorebi/src/process_event.rs index 4a493506..4e28ee0f 100644 --- a/komorebi/src/process_event.rs +++ b/komorebi/src/process_event.rs @@ -55,8 +55,9 @@ impl WindowManager { WindowManagerEvent::FocusChange(_, window) | WindowManagerEvent::Show(_, window) | WindowManagerEvent::MoveResizeEnd(_, window) => { - let monitor_idx = self - .monitor_idx_from_window(*window) + self.reconcile_monitors()?; + + let monitor_idx = self.monitor_idx_from_window(*window) .ok_or_else(|| anyhow!("there is no monitor associated with this window, it may have already been destroyed"))?; self.focus_monitor(monitor_idx)?; diff --git a/komorebi/src/window_manager.rs b/komorebi/src/window_manager.rs index 75cb6871..7eb11fbb 100644 --- a/komorebi/src/window_manager.rs +++ b/komorebi/src/window_manager.rs @@ -224,6 +224,44 @@ impl WindowManager { Ok(()) } + #[tracing::instrument(skip(self))] + pub fn reconcile_monitors(&mut self) -> Result<()> { + let valid_hmonitors = WindowsApi::valid_hmonitors()?; + let mut invalid = vec![]; + + for monitor in self.monitors_mut() { + if !valid_hmonitors.contains(&monitor.id()) { + let mut mark_as_invalid = true; + + // If an invalid hmonitor has at least one window in the window manager state, + // we can attempt to update its hmonitor id in-place so that it doesn't get reaped + if let Some(workspace) = monitor.focused_workspace() { + if let Some(container) = workspace.focused_container() { + if let Some(window) = container.focused_window() { + let actual_hmonitor = WindowsApi::monitor_from_window(window.hwnd()); + if actual_hmonitor != monitor.id() { + monitor.set_id(actual_hmonitor); + mark_as_invalid = false; + } + } + } + } + + if mark_as_invalid { + invalid.push(monitor.id()); + } + } + } + + // Remove any invalid monitors from our state + self.monitors_mut().retain(|m| !invalid.contains(&m.id())); + + // Check for and add any new monitors that may have been plugged in + WindowsApi::load_monitor_information(&mut self.monitors)?; + + Ok(()) + } + #[tracing::instrument(skip(self))] pub fn enforce_workspace_rules(&mut self) -> Result<()> { let mut to_move = vec![]; diff --git a/komorebi/src/windows_api.rs b/komorebi/src/windows_api.rs index a688756a..be54d0bd 100644 --- a/komorebi/src/windows_api.rs +++ b/komorebi/src/windows_api.rs @@ -86,7 +86,6 @@ use crate::monitor::Monitor; use crate::ring::Ring; use crate::set_window_position::SetWindowPosition; use crate::windows_callbacks; -use crate::workspace::Workspace; pub enum WindowsResult { Err(E), @@ -166,6 +165,17 @@ impl WindowsApi { })) } + pub fn valid_hmonitors() -> Result> { + let mut monitors: Vec = vec![]; + let monitors_ref: &mut Vec = monitors.as_mut(); + Self::enum_display_monitors( + windows_callbacks::valid_display_monitors, + monitors_ref as *mut Vec as isize, + )?; + + Ok(monitors) + } + pub fn load_monitor_information(monitors: &mut Ring) -> Result<()> { Self::enum_display_monitors( windows_callbacks::enum_display_monitor, @@ -181,9 +191,8 @@ impl WindowsApi { pub fn load_workspace_information(monitors: &mut Ring) -> Result<()> { for monitor in monitors.elements_mut() { - if monitor.workspaces().is_empty() { - let mut workspace = Workspace::default(); - + let monitor_id = monitor.id(); + if let Some(workspace) = monitor.workspaces_mut().front_mut() { // EnumWindows will enumerate through windows on all monitors Self::enum_windows( windows_callbacks::enum_window, @@ -200,7 +209,7 @@ impl WindowsApi { for container in workspace.containers_mut() { for window in container.windows() { - if Self::monitor_from_window(window.hwnd()) != monitor.id() { + if Self::monitor_from_window(window.hwnd()) != monitor_id { windows_on_other_monitors.push(window.hwnd().0); } } @@ -209,8 +218,6 @@ impl WindowsApi { for hwnd in windows_on_other_monitors { workspace.remove_window(hwnd)?; } - - monitor.workspaces_mut().push_back(workspace); } } diff --git a/komorebi/src/windows_callbacks.rs b/komorebi/src/windows_callbacks.rs index fbc7ae4d..cd959918 100644 --- a/komorebi/src/windows_callbacks.rs +++ b/komorebi/src/windows_callbacks.rs @@ -16,6 +16,17 @@ use crate::window_manager_event::WindowManagerEvent; use crate::windows_api::WindowsApi; use crate::winevent_listener::WINEVENT_CALLBACK_CHANNEL; +pub extern "system" fn valid_display_monitors( + hmonitor: HMONITOR, + _: HDC, + _: *mut RECT, + lparam: LPARAM, +) -> BOOL { + let monitors = unsafe { &mut *(lparam.0 as *mut Vec) }; + monitors.push(hmonitor.0); + true.into() +} + pub extern "system" fn enum_display_monitor( hmonitor: HMONITOR, _: HDC, @@ -23,6 +34,14 @@ pub extern "system" fn enum_display_monitor( lparam: LPARAM, ) -> BOOL { let monitors = unsafe { &mut *(lparam.0 as *mut Ring) }; + + // Don't duplicate a monitor that is already being managed + for monitor in monitors.elements() { + if monitor.id() == hmonitor.0 { + return true.into(); + } + } + if let Ok(m) = WindowsApi::monitor(hmonitor) { monitors.elements_mut().push_back(m); } diff --git a/komorebic/src/main.rs b/komorebic/src/main.rs index 401f640b..8e8a5532 100644 --- a/komorebic/src/main.rs +++ b/komorebic/src/main.rs @@ -527,11 +527,10 @@ fn main() -> Result<()> { None }; - let script = if let Some(exec) = exec { - format!("Start-Process '{}' -WindowStyle hidden", exec) - } else { - String::from("Start-Process komorebi -WindowStyle hidden") - }; + let script = exec.map_or_else( + || String::from("Start-Process komorebi -WindowStyle hidden"), + |exec| format!("Start-Process '{}' -WindowStyle hidden", exec), + ); match powershell_script::run(&script, true) { Ok(output) => {