From 57b93a740495ac89808b02c185669209f190fc7d Mon Sep 17 00:00:00 2001 From: ChinhLee <76194645+chinhkrb113@users.noreply.github.com> Date: Sun, 31 May 2026 02:17:16 +0700 Subject: [PATCH] fix(wm): verify foreground state before mutating focus state As a defense-in-depth fix independent of the event-conversion layer, the WindowManagerEvent::FocusChange handler should not update the focused monitor/workspace/container/window state when the incoming window is not the actual OS foreground window. The issue notes that after a monitor switch komorebi focuses "the last window focused from monitor 1" while the popout window on the other monitor still receives input. This is exactly the symptom of komorebi processing a stale/background focus event and re-pointing its selection at monitor 1 even though real focus is elsewhere. Verifying foreground state before mutating focus state prevents the desync regardless of which win-event produced the FocusChange. fix #1679 Signed-off-by: ChinhLee <76194645+chinhkrb113@users.noreply.github.com> --- komorebi/src/process_event.rs | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/komorebi/src/process_event.rs b/komorebi/src/process_event.rs index 49bf5676..315180c9 100644 --- a/komorebi/src/process_event.rs +++ b/komorebi/src/process_event.rs @@ -41,6 +41,30 @@ use crate::windows_api::WindowsApi; use crate::winevent::WinEvent; use crate::workspace::WorkspaceLayer; +fn should_skip_focus_change(foreground_hwnd: Option, window_hwnd: isize) -> bool { + matches!(foreground_hwnd, Some(hwnd) if hwnd != window_hwnd) +} + +#[cfg(test)] +mod focus_change_tests { + use super::should_skip_focus_change; + + #[test] + fn skips_focus_change_from_background_window() { + assert!(should_skip_focus_change(Some(1), 2)); + } + + #[test] + fn allows_focus_change_for_foreground_window() { + assert!(!should_skip_focus_change(Some(1), 1)); + } + + #[test] + fn allows_focus_change_when_foreground_unknown() { + assert!(!should_skip_focus_change(None, 1)); + } +} + #[tracing::instrument] pub fn listen_for_events(wm: Arc>) { let receiver = wm.lock().incoming_events.clone(); @@ -341,6 +365,13 @@ impl WindowManager { already_moved_window_handles.remove(&window.hwnd); } WindowManagerEvent::FocusChange(_, window) => { + if should_skip_focus_change(WindowsApi::foreground_window().ok(), window.hwnd) { + tracing::debug!( + "ignoring stale focus change for hwnd {} as it is not the foreground window", + window.hwnd + ); + return Ok(()); + } // don't want to trigger the full workspace updates when there are no managed // containers - this makes floating windows on empty workspaces go into very // annoying focus change loops which prevents users from interacting with them