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) => {