fix: chrome apps windows would not show in the switcher (closes #5227)

This commit is contained in:
lwouis committed 2026-01-22 22:28:46 +01:00
1 parent be06aef6d7
commit bf3fa6734e
1 file changed
+48 -25
+48 -25
View File
@@ -160,42 +160,65 @@ extension AXUIElement {
func attributes(_ keys: [String]) throws -> AXAttributes { func attributes(_ keys: [String]) throws -> AXAttributes {
var values: CFArray? var values: CFArray?
// without .stopOnError, AXUIElementCopyMultipleAttributeValues always returns an array. it contains placeholder values. try throwIfNotSuccess(AXUIElementCopyMultipleAttributeValues(self, keys as CFArray, [], &values))
// This makes it very hard to know what's real. For example, it can return a MainWindow that's not nil, but has its attributes zero'd let array = values as? [CFTypeRef] ?? []
try throwIfNotSuccess(AXUIElementCopyMultipleAttributeValues(self, keys as CFArray, [.stopOnError], &values))
let array = values as? [Any] ?? []
var result = AXAttributes() var result = AXAttributes()
for (index, key) in keys.enumerated() { for (index, key) in keys.enumerated() {
guard index < array.count else { continue } guard index < array.count else { continue }
let value = array[index] let value = array[index]
switch key { switch key {
case kAXTitleAttribute: result.title = value as? String case kAXTitleAttribute: result.title = castSafely(value)
case kAXRoleAttribute: result.role = value as? String case kAXRoleAttribute: result.role = castSafely(value)
case kAXSubroleAttribute: result.subrole = value as? String case kAXSubroleAttribute: result.subrole = castSafely(value)
case kAXStatusLabelAttribute: result.statusLabel = value as? String case kAXStatusLabelAttribute: result.statusLabel = castSafely(value)
case kAXMinimizedAttribute: result.isMinimized = value as? Bool case kAXMinimizedAttribute: result.isMinimized = castSafely(value)
case kAXFullscreenAttribute: result.isFullscreen = value as? Bool case kAXFullscreenAttribute: result.isFullscreen = castSafely(value)
case kAXIsApplicationRunningAttribute: result.appIsRunning = value as? Bool case kAXIsApplicationRunningAttribute: result.appIsRunning = castSafely(value)
case kAXURLAttribute: result.url = value as? URL case kAXURLAttribute: result.url = castSafely(value)
case kAXParentAttribute: result.parent = unsafeDowncast(value as CFTypeRef, to: AXUIElement.self) case kAXParentAttribute: result.parent = castSafely(value)
case kAXFocusedWindowAttribute: result.focusedWindow = unsafeDowncast(value as CFTypeRef, to: AXUIElement.self) case kAXFocusedWindowAttribute: result.focusedWindow = castSafely(value)
case kAXMainWindowAttribute: result.mainWindow = unsafeDowncast(value as CFTypeRef, to: AXUIElement.self) case kAXMainWindowAttribute: result.mainWindow = castSafely(value)
case kAXCloseButtonAttribute: result.closeButton = unsafeDowncast(value as CFTypeRef, to: AXUIElement.self) case kAXCloseButtonAttribute: result.closeButton = castSafely(value)
case kAXChildrenAttribute: result.children = unsafeDowncast(value as CFTypeRef, to: CFArray.self) as? [AXUIElement] case kAXChildrenAttribute: result.children = castSafely(value)
case kAXWindowsAttribute: result.windows = unsafeDowncast(value as CFTypeRef, to: CFArray.self) as? [AXUIElement] case kAXWindowsAttribute: result.windows = castSafely(value)
case kAXPositionAttribute: result.position = unboxAxValue(value, .cgPoint) case kAXPositionAttribute: result.position = castSafely(value)
case kAXSizeAttribute: result.size = unboxAxValue(value, .cgSize) case kAXSizeAttribute: result.size = castSafely(value)
default: Logger.error { "key:\(key) value:\(value)" } default: Logger.error { "key:\(key) value:\(value)" }
} }
} }
return result return result
} }
func unboxAxValue<T>(_ attributeValue: Any, _ axType: AXValueType, as _: T.Type = T.self) -> T? { func castSafely<T>(_ value: CFTypeRef) -> T? {
let axValue = unsafeDowncast(attributeValue as CFTypeRef, to: AXValue.self) switch CFGetTypeID(value) {
return withUnsafeTemporaryAllocation(of: T.self, capacity: 1) { buffer in case AXValueGetTypeID():
guard AXValueGetValue(axValue, axType, buffer.baseAddress!) else { return nil } let axValue = value as! AXValue
return buffer[0] switch AXValueGetType(axValue) {
case .axError:
// without .stopOnError, AXUIElementCopyMultipleAttributeValues always returns an array. it contains placeholder values.
// This makes it very hard to know what's real. For example, if an app has no MainWindow, it will return .axError. If we cast it to AXUIElement, it will succeed, but the object will have its attributes zero'd
// we have to check for .axError, which we map to nil values
return nil
case .cgSize:
var size = CGSize.zero
AXValueGetValue(axValue, .cgSize, &size)
return size as? T
case .cgPoint:
var point = CGPoint.zero
AXValueGetValue(axValue, .cgPoint, &point)
return point as? T
case let unknownAXValueType:
Logger.error { unknownAXValueType }
return nil
}
case AXUIElementGetTypeID(): return value as? T
case CFArrayGetTypeID(): return value as? T
case CFURLGetTypeID(): return value as? T
case CFStringGetTypeID(): return value as? T
case CFBooleanGetTypeID(): return value as? T
case let unknownCFTypeID:
Logger.error { unknownCFTypeID }
return nil
} }
} }