fix: closing a window would remove the thumbnail (closes #5324)

This commit is contained in:
lwouis committed 2026-02-24 20:48:23 +01:00
1 parent db34a7eb8f
commit 451bbbe035
2 files changed
+15 -12

No files matched your search

+6 -6
View File
@@ -33,14 +33,15 @@ extension AXUIElement {
}
/// if the window server is busy, it may not reply to AX calls. We retry right before the call times-out and returns a bogus value
static func retryAxCallUntilTimeout(file: String = #file, function: String = #function, line: Int = #line, context: String = "", after: DispatchTime? = nil, pid: pid_t? = nil, wid: CGWindowID? = nil, retriesQueue: Bool = false, startTimeInNanoseconds: UInt64 = DispatchTime.now().uptimeNanoseconds, callType: AXCallType, block: @escaping () throws -> Void) {
let closure = { retryAxCallUntilTimeout_(file: file, function: function, line: line, context: context, after: after, pid: pid, wid: wid, retriesQueue: retriesQueue, startTimeInNanoseconds: startTimeInNanoseconds, callType: callType, block: block) }
static func retryAxCallUntilTimeout(file: String = #file, function: String = #function, line: Int = #line, context: String = "", after: DispatchTime? = nil, pid: pid_t? = nil, wid: CGWindowID? = nil, retriesQueue: Bool = false, startTimeInNanoseconds: UInt64 = DispatchTime.now().uptimeNanoseconds, isWindowDestroyedEvent: Bool = false, callType: AXCallType, block: @escaping () throws -> Void) {
let closure = { retryAxCallUntilTimeout_(file: file, function: function, line: line, context: context, after: after, pid: pid, wid: wid, retriesQueue: retriesQueue, startTimeInNanoseconds: startTimeInNanoseconds, isWindowDestroyedEvent: isWindowDestroyedEvent, callType: callType, block: block) }
let queue = (callType == .updateAppWindows ? BackgroundWork.axCallsManualDiscoveryQueue
: (retriesQueue ? BackgroundWork.axCallsRetriesQueue
: BackgroundWork.axCallsFirstAttemptQueue))!
if callType == .updateWindowFromAxEvent || callType == .updateWindowFromManualDiscovery {
guard let wid else { Logger.error { (callType, context, file, function, line) }; return }
if throttleOrProceed(key: "\(callType.rawValue)-wid-\(wid)", queue: queue, closure: closure) { return }
// we don't throttle windowDestroyedEvent as these are key events to process
if !isWindowDestroyedEvent && throttleOrProceed(key: "\(callType.rawValue)-wid-\(wid)", queue: queue, closure: closure) { return }
}
if callType == .updateApp || callType == .updateAppWindows || callType == .updateAppFocusedWindow {
guard let pid else { Logger.error { (callType, context, file, function, line) }; return }
@@ -53,7 +54,7 @@ extension AXUIElement {
}
}
private static func retryAxCallUntilTimeout_(file: String, function: String, line: Int, context: String, after: DispatchTime?, pid: pid_t?, wid: CGWindowID?, retriesQueue: Bool, startTimeInNanoseconds: UInt64, callType: AXCallType, block: @escaping () throws -> Void) {
private static func retryAxCallUntilTimeout_(file: String, function: String, line: Int, context: String, after: DispatchTime?, pid: pid_t?, wid: CGWindowID?, retriesQueue: Bool, startTimeInNanoseconds: UInt64, isWindowDestroyedEvent: Bool, callType: AXCallType, block: @escaping () throws -> Void) {
// attempt the AX call
if (try? block()) != nil {
return
@@ -66,7 +67,7 @@ extension AXUIElement {
}
// retry
Logger.debug { "(pid:\(pid) wid:\(wid)) \(logFromContext(file, function, line, context, callType))" }
retryAxCallUntilTimeout(file: file, function: function, line: line, context: context, after: .now() + humanPerceptionDelay, pid: pid, wid: wid, retriesQueue: true, startTimeInNanoseconds: startTimeInNanoseconds, callType: callType, block: block)
retryAxCallUntilTimeout(file: file, function: function, line: line, context: context, after: .now() + humanPerceptionDelay, pid: pid, wid: wid, retriesQueue: true, startTimeInNanoseconds: startTimeInNanoseconds, isWindowDestroyedEvent: isWindowDestroyedEvent, callType: callType, block: block)
}
private static func logFromContext(_ file: String, _ function: String, _ line: Int, _ context: String, _ callType: AXCallType) -> String {
@@ -82,7 +83,6 @@ extension AXUIElement {
if elapsed < windowNs {
if !state.tailScheduled {
map[key] = ThrottleState(time: state.time, tailScheduled: true)
let remaining = windowNs - elapsed
queue.addOperationAfter(deadline: .now() + .nanoseconds(Int(remaining))) {
throttleMap.withLock { $0[key] = nil }
+9 -6
View File
@@ -19,8 +19,8 @@ class AccessibilityEvents {
try handleEventApp(type, pid, element)
}
} else {
let wid = try element.cgWindowId()
AXUIElement.retryAxCallUntilTimeout(context: "(pid:\(pid))", pid: pid, wid: wid, callType: .updateWindowFromAxEvent) {
let wid = (try? element.cgWindowId()) ?? 0
AXUIElement.retryAxCallUntilTimeout(context: "(pid:\(pid))", pid: pid, wid: wid, isWindowDestroyedEvent: type == kAXUIElementDestroyedNotification, callType: .updateWindowFromAxEvent) {
try handleEventWindow(type, wid, pid, element)
}
}
@@ -76,15 +76,18 @@ class AccessibilityEvents {
static func handleEventWindow(_ type: String, _ wid: CGWindowID, _ pid: pid_t, _ element: AXUIElement) throws {
guard wid != 0 || type == kAXUIElementDestroyedNotification,
wid != TilesPanel.shared.windowNumber else { return } // don't process events for the thumbnails panel
if type == kAXUIElementDestroyedNotification {
DispatchQueue.main.async {
Logger.info { "\(type) wid:\(wid) pid:\(pid)" }
windowDestroyed(element, pid, wid)
}
return
}
let level = wid.level()
let a = try element.attributes([kAXTitleAttribute, kAXSubroleAttribute, kAXRoleAttribute, kAXSizeAttribute, kAXPositionAttribute, kAXFullscreenAttribute, kAXMinimizedAttribute])
DispatchQueue.main.async {
guard let app = Applications.findOrCreate(pid, false) else { return }
Logger.info { "\(type) wid:\(wid) app:\(app.debugId)" }
if type == kAXUIElementDestroyedNotification {
windowDestroyed(element, pid, wid)
return
}
let findOrCreate = Windows.findOrCreate(element, wid, app, level, a.title, a.subrole, a.role, a.size, a.position, a.isFullscreen, a.isMinimized)
guard let window = findOrCreate.0 else {
// we don't know this window, but it got focused, so let's update app.focusedWindow with nil