diff --git a/src/logic/Applications.swift b/src/logic/Applications.swift index 196d2eb7..cd050b62 100644 --- a/src/logic/Applications.swift +++ b/src/logic/Applications.swift @@ -14,11 +14,47 @@ class Applications { } static func manuallyRefreshAllWindows() { + removeZombieWindows() + addMissingWindows() + } + + /// we may not receive a window-created event in some cases: + /// * we can't subscribe to the app + /// * we couldn't subscribe to the app before the window was created + /// * weird cases like apps launching at startup with "restaure windows" + /// this manually queries the system for windows, and keeps our list in-sync with the actual system + static func addMissingWindows() { for app in list { app.manuallyUpdateWindows() } } + /// we may not receive a window-destroyed event in some cases: + /// * Sequoia bug: https://github.com/lwouis/alt-tab-macos/issues/3589 + /// * Logic Pro bug: https://github.com/lwouis/alt-tab-macos/issues/4924 + /// this acts as a garbage-collector for windows, to keep our list in-sync with the actual system + static func removeZombieWindows() { + let wIds = Windows.list.compactMap { $0.cgWindowId } + guard !wIds.isEmpty else { return } + let values = UnsafeMutablePointer.allocate(capacity: wIds.count) + for (i, id) in wIds.enumerated() { + values[i] = UnsafeRawPointer(bitPattern: UInt(id)) + } + let rawIds = CFArrayCreate(kCFAllocatorDefault, values, wIds.count, nil) + let descriptions = (CGWindowListCreateDescriptionFromArray(rawIds) as? [[CFString: Any]]) + let existingWids = descriptions?.compactMap { $0[kCGWindowNumber] } as? [CGWindowID] + guard let existingWids else { return } + let believedAlive = Set(wIds) + let confirmedAlive = Set(existingWids) + let zombies = believedAlive.subtracting(confirmedAlive) + for (index, window) in Windows.list.enumerated().reversed() { + if let wid = window.cgWindowId, zombies.contains(wid) { + Logger.error("----", window.title, window.application.bundleIdentifier) + Windows.removeWindow(index, window.application.pid) + } + } + } + static func addRunningApplications(_ runningApps: [NSRunningApplication]) { runningApps.forEach { let bundleIdentifier = $0.bundleIdentifier diff --git a/src/logic/Windows.swift b/src/logic/Windows.swift index 4d04c0e0..d172dda0 100644 --- a/src/logic/Windows.swift +++ b/src/logic/Windows.swift @@ -131,7 +131,21 @@ class Windows { } } - static func removeAndUpdateFocus(_ window: Window) { + static func removeWindow(_ index: Int, _ pid: pid_t) { + let window = Windows.list[index] + removeAndUpdateFocus(window) + if window.application.addWindowlessWindowIfNeeded() != nil { + Applications.find(pid)?.focusedWindow = nil + } + if Windows.list.count > 0 { + moveFocusedWindowIndexAfterWindowDestroyedInBackground(index) + App.app.refreshOpenUi([], .refreshUiAfterExternalEvent) + } else { + App.app.hideUi() + } + } + + private static func removeAndUpdateFocus(_ window: Window) { let removedWindowOldFocusOrder = window.lastFocusOrder list.removeAll { if $0.lastFocusOrder == removedWindowOldFocusOrder { @@ -245,7 +259,7 @@ class Windows { return targetIndex } - static func moveFocusedWindowIndexAfterWindowDestroyedInBackground(_ index: Int) { + private static func moveFocusedWindowIndexAfterWindowDestroyedInBackground(_ index: Int) { if index < focusedWindowIndex { cycleFocusedWindowIndex(-1) } diff --git a/src/logic/events/AccessibilityEvents.swift b/src/logic/events/AccessibilityEvents.swift index 34f4f695..ed8ed458 100644 --- a/src/logic/events/AccessibilityEvents.swift +++ b/src/logic/events/AccessibilityEvents.swift @@ -150,17 +150,7 @@ class AccessibilityEvents { let wid = try element.cgWindowId() DispatchQueue.main.async { if let index = (Windows.list.firstIndex { $0.isEqualRobust(element, wid) }) { - let window = Windows.list[index] - Windows.removeAndUpdateFocus(window) - if window.application.addWindowlessWindowIfNeeded() != nil { - Applications.find(pid)?.focusedWindow = nil - } - if Windows.list.count > 0 { - Windows.moveFocusedWindowIndexAfterWindowDestroyedInBackground(index) - App.app.refreshOpenUi([], .refreshUiAfterExternalEvent) - } else { - App.app.hideUi() - } + Windows.removeWindow(index, pid) } } }