diff --git a/komorebi/src/border_manager/border.rs b/komorebi/src/border_manager/border.rs index db7ee180..7360ee95 100644 --- a/komorebi/src/border_manager/border.rs +++ b/komorebi/src/border_manager/border.rs @@ -539,6 +539,17 @@ impl Border { } let reference_hwnd = (*border_pointer).tracking_hwnd; + + // The window is hidden while a ghost stands in for it, and + // is not where the ghost is, which is where the border + // belongs: the animation moves it. + if crate::border_manager::is_ghosted(reference_hwnd) { + tracing::trace!( + "border: left the border of hwnd {reference_hwnd} to its ghost's animation" + ); + return LRESULT(0); + } + let rect = WindowsApi::window_rect(reference_hwnd).unwrap_or_default(); apply_tracked_rect(border_pointer, rect); LRESULT(0) diff --git a/komorebi/src/border_manager/mod.rs b/komorebi/src/border_manager/mod.rs index 55af0638..9f527119 100644 --- a/komorebi/src/border_manager/mod.rs +++ b/komorebi/src/border_manager/mod.rs @@ -23,6 +23,7 @@ use parking_lot::Mutex; use serde::Deserialize; use serde::Serialize; use std::collections::HashMap; +use std::collections::HashSet; use std::collections::hash_map::Entry; use std::ops::Deref; use std::sync::Arc; @@ -74,6 +75,7 @@ impl Deref for RenderTarget { } } +#[derive(Debug)] pub enum Notification { Update(Option), ForceUpdate, @@ -105,6 +107,27 @@ fn event_rx() -> Receiver { channel().1.clone() } +lazy_static! { + /// The windows that a ghost is standing in for, which are hidden and not + /// where the ghost is. The animation moves their borders. + static ref GHOSTED: Mutex> = Mutex::new(HashSet::new()); +} + +/// Say that a ghost is, or has stopped being, what is seen of a window: from +/// then until then the border of the window follows the animation of the ghost +/// (see `animate_to`) and not the window, which is not where it is seen. +pub fn set_ghosted(hwnd: isize, ghosted: bool) { + if ghosted { + GHOSTED.lock().insert(hwnd); + } else { + GHOSTED.lock().remove(&hwnd); + } +} + +pub(crate) fn is_ghosted(hwnd: isize) -> bool { + GHOSTED.lock().contains(&hwnd) +} + pub fn window_border(hwnd: isize) -> Option { let id = WINDOWS_BORDERS.lock().get(&hwnd)?.clone(); BORDER_STATE.lock().get(&id).map(|b| BorderInfo { @@ -457,7 +480,14 @@ pub fn handle_notifications(wm: Arc>) -> color_eyre::Result // Update the border's monitor idx in case it changed border.monitor_idx = Some(monitor_idx); - let rect = WindowsApi::window_rect(focused_window_hwnd)?; + let rect = if !new_border && is_ghosted(focused_window_hwnd) { + tracing::trace!( + "border: left the border of hwnd {focused_window_hwnd} to its ghost's animation" + ); + border.window_rect + } else { + WindowsApi::window_rect(focused_window_hwnd)? + }; border.window_rect = rect; if new_border { @@ -615,11 +645,18 @@ pub fn handle_notifications(wm: Arc>) -> color_eyre::Result // avoid getting into a thread restart loop if we try to look up // rect info for a window that has been destroyed by the time // we get here - let rect = match WindowsApi::window_rect(focused_window_hwnd) { - Ok(rect) => rect, - Err(_) => { - remove_border(&c.id, &mut borders, &mut windows_borders)?; - continue 'containers; + let rect = if !new_border && is_ghosted(focused_window_hwnd) { + tracing::trace!( + "border: left the border of hwnd {focused_window_hwnd} to its ghost's animation" + ); + border.window_rect + } else { + match WindowsApi::window_rect(focused_window_hwnd) { + Ok(rect) => rect, + Err(_) => { + remove_border(&c.id, &mut borders, &mut windows_borders)?; + continue 'containers; + } } }; border.window_rect = rect; @@ -709,7 +746,15 @@ fn handle_floating_borders( // Update the border's monitor idx in case it changed border.monitor_idx = Some(monitor_idx); - let rect = WindowsApi::window_rect(window.hwnd)?; + let rect = if !new_border && is_ghosted(window.hwnd) { + tracing::trace!( + "border: left the border of hwnd {} to its ghost's animation", + window.hwnd + ); + border.window_rect + } else { + WindowsApi::window_rect(window.hwnd)? + }; border.window_rect = rect; let should_invalidate = @@ -870,3 +915,29 @@ impl From for isize { } } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn a_window_is_ghosted_from_when_it_is_said_to_be_until_it_is_not() { + // a window handle nothing else in the tests uses + let hwnd = 0x7e57_0001; + + assert!(!is_ghosted(hwnd)); + + set_ghosted(hwnd, true); + assert!(is_ghosted(hwnd)); + assert!(!is_ghosted(hwnd + 1)); + + // saying it twice does not make it take two to undo + set_ghosted(hwnd, true); + set_ghosted(hwnd, false); + assert!(!is_ghosted(hwnd)); + + // and undoing what was never done is fine + set_ghosted(hwnd, false); + assert!(!is_ghosted(hwnd)); + } +} diff --git a/komorebi/src/window.rs b/komorebi/src/window.rs index 6661d6ac..f3600b6c 100644 --- a/komorebi/src/window.rs +++ b/komorebi/src/window.rs @@ -338,6 +338,7 @@ impl RenderDispatcher for MovementRenderDispatcher { match GhostWindow::create(self.hwnd, self.start_rect) { Ok(ghost) => { *self.ghost.lock() = Some(ghost); + border_manager::set_ghosted(self.hwnd, true); // DwmSetWindowAttribute(DWMWA_CLOAK) is rejected with // E_ACCESSDENIED for foreign HWNDs; the undocumented @@ -428,6 +429,7 @@ impl RenderDispatcher for MovementRenderDispatcher { ghost.fade_out(self.target_rect); } + border_manager::set_ghosted(self.hwnd, false); let _ = ghost.dispose(); self.finalise_managers(); @@ -455,6 +457,7 @@ impl RenderDispatcher for MovementRenderDispatcher { self.uncloak(); } + border_manager::set_ghosted(self.hwnd, false); if let Some(ghost) = self.ghost.lock().take() { let _ = ghost.dispose(); }