diff --git a/komorebi/src/process_event.rs b/komorebi/src/process_event.rs index 315180c98..8c3797bd0 100644 --- a/komorebi/src/process_event.rs +++ b/komorebi/src/process_event.rs @@ -45,6 +45,16 @@ fn should_skip_focus_change(foreground_hwnd: Option, window_hwnd: isize) matches!(foreground_hwnd, Some(hwnd) if hwnd != window_hwnd) } +/// After a managed window is destroyed, Windows may promote no successor, leaving +/// the foreground null with no FocusChange to reconcile from. The selection then +/// never regains focus: every border renders unfocused and keystrokes fall through +/// to nothing until the user refocuses by hand. Refocus only in that case, otherwise +/// foreground successor is left to FocusChange so we don't fight the OS or desync +/// focus. +fn should_refocus_after_destroy(foreground_hwnd: Option) -> bool { + matches!(foreground_hwnd, None | Some(0)) +} + #[cfg(test)] mod focus_change_tests { use super::should_skip_focus_change; @@ -65,6 +75,26 @@ mod focus_change_tests { } } +#[cfg(test)] +mod destroy_focus_tests { + use super::should_refocus_after_destroy; + + #[test] + fn refocuses_when_foreground_is_null() { + assert!(should_refocus_after_destroy(Some(0))); + } + + #[test] + fn refocuses_when_foreground_is_unavailable() { + assert!(should_refocus_after_destroy(None)); + } + + #[test] + fn defers_when_a_successor_took_the_foreground() { + assert!(!should_refocus_after_destroy(Some(12345))); + } +} + #[tracing::instrument] pub fn listen_for_events(wm: Arc>) { let receiver = wm.lock().incoming_events.clone(); @@ -282,7 +312,13 @@ impl WindowManager { WindowManagerEvent::Destroy(_, window) | WindowManagerEvent::Unmanage(window) => { if self.focused_workspace()?.contains_window(window.hwnd) { self.focused_workspace_mut()?.remove_window(window.hwnd)?; - self.update_focused_workspace(false, false)?; + + // With refocus true, update_focused_workspace focuses the surviving + // selection (maximized/monocle/tiling); with refocus false it only + // re-tiles. + let refocus = + should_refocus_after_destroy(WindowsApi::foreground_window().ok()); + self.update_focused_workspace(refocus, refocus)?; let mut already_moved_window_handles = self.already_moved_window_handles.lock();