mirror of
https://github.com/LGUG2Z/komorebi.git
synced 2026-08-07 04:38:36 +02:00
fix(wm): refocus selection on destroy when foreground goes null
Reactive events pass trigger_focus=false so komorebi follows the OS foreground and reconciles via the FocusChange handler once Windows promotes a successor window. When the destroyed window was the foreground and Windows promotes nothing, the foreground becomes null, no FocusChange arrives, and komorebi's selection never regains OS focus, leaving every border unfocused until the user refocuses by hand. Add a narrow fallback in the Destroy/Unmanage handler: when the OS foreground is null after removal, focus the current selection. Any non-null foreground is left to FocusChange, so the OS is not fought and the should_skip_focus_change desync cannot occur.
This commit is contained in:
@@ -45,6 +45,16 @@ fn should_skip_focus_change(foreground_hwnd: Option<isize>, 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<isize>) -> 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<Mutex<WindowManager>>) {
|
||||
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();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user