Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 37 additions & 1 deletion komorebi/src/process_event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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();
Expand Down Expand Up @@ -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();

Expand Down