From d7c8dc5aad9e5b538aa8a18ea300737e12b869e3 Mon Sep 17 00:00:00 2001 From: ShlomoCode <78599753+ShlomoCode@users.noreply.github.com> Date: Mon, 5 Aug 2024 04:20:13 +0300 Subject: [PATCH] refactor: use AXUIElement extension --- DockDoor.xcodeproj/project.pbxproj | 4 + DockDoor/Extensions/AXUIElement.swift | 118 ++++++++++ DockDoor/Utilities/PrivateApis.swift | 4 + .../WindowManipulationObservers.swift | 34 --- DockDoor/Utilities/WindowUtil.swift | 202 +++++++----------- 5 files changed, 202 insertions(+), 160 deletions(-) create mode 100644 DockDoor/Extensions/AXUIElement.swift diff --git a/DockDoor.xcodeproj/project.pbxproj b/DockDoor.xcodeproj/project.pbxproj index a903f8be..d5c95aae 100644 --- a/DockDoor.xcodeproj/project.pbxproj +++ b/DockDoor.xcodeproj/project.pbxproj @@ -10,6 +10,7 @@ 0531F8D22C3CC04600327808 /* AppearanceSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0531F8D12C3CC04600327808 /* AppearanceSettingsView.swift */; }; 0596C2C32C3E060D00DCABEF /* PrivateApis.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0596C2C22C3E060D00DCABEF /* PrivateApis.swift */; }; 05A25B912C3EF28E002AC594 /* ColorHex.swift in Sources */ = {isa = PBXBuildFile; fileRef = 05A25B902C3EF28E002AC594 /* ColorHex.swift */; }; + 05C0C7182C60629C000ADAC6 /* AXUIElement.swift in Sources */ = {isa = PBXBuildFile; fileRef = 05C0C7172C60629C000ADAC6 /* AXUIElement.swift */; }; 3A105FD62C1BED660015EC66 /* BlurView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A105FD52C1BED660015EC66 /* BlurView.swift */; }; 3A105FD92C1C049E0015EC66 /* dockStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A105FD82C1C049E0015EC66 /* dockStyle.swift */; }; 3A105FDD2C1C0EE20015EC66 /* DynStack.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A105FDC2C1C0EE20015EC66 /* DynStack.swift */; }; @@ -57,6 +58,7 @@ 0531F8D12C3CC04600327808 /* AppearanceSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppearanceSettingsView.swift; sourceTree = ""; }; 0596C2C22C3E060D00DCABEF /* PrivateApis.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrivateApis.swift; sourceTree = ""; }; 05A25B902C3EF28E002AC594 /* ColorHex.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ColorHex.swift; sourceTree = ""; }; + 05C0C7172C60629C000ADAC6 /* AXUIElement.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AXUIElement.swift; sourceTree = ""; }; 3A105FD52C1BED660015EC66 /* BlurView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlurView.swift; sourceTree = ""; }; 3A105FD82C1C049E0015EC66 /* dockStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = dockStyle.swift; sourceTree = ""; }; 3A105FDC2C1C0EE20015EC66 /* DynStack.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DynStack.swift; sourceTree = ""; }; @@ -131,6 +133,7 @@ 3A105FD72C1BF11D0015EC66 /* Extensions */ = { isa = PBXGroup; children = ( + 05C0C7172C60629C000ADAC6 /* AXUIElement.swift */, 05A25B902C3EF28E002AC594 /* ColorHex.swift */, 3A105FD82C1C049E0015EC66 /* dockStyle.swift */, ); @@ -330,6 +333,7 @@ BBF4139A2C40A64A00AA6733 /* FluidGradient.swift in Sources */, BB7CA33D2C31F1B00012E303 /* WindowManipulationObservers.swift in Sources */, BB157B802C0E8E6700997315 /* MessageUtil.swift in Sources */, + 05C0C7182C60629C000ADAC6 /* AXUIElement.swift in Sources */, 3A105FD62C1BED660015EC66 /* BlurView.swift in Sources */, BB1CBD5D2C1BCA4F003969BC /* Misc Utils.swift in Sources */, 05A25B912C3EF28E002AC594 /* ColorHex.swift in Sources */, diff --git a/DockDoor/Extensions/AXUIElement.swift b/DockDoor/Extensions/AXUIElement.swift new file mode 100644 index 00000000..42dfec9f --- /dev/null +++ b/DockDoor/Extensions/AXUIElement.swift @@ -0,0 +1,118 @@ +import Cocoa +import ApplicationServices.HIServices.AXUIElement +import ApplicationServices.HIServices.AXValue +import ApplicationServices.HIServices.AXError +import ApplicationServices.HIServices.AXRoleConstants +import ApplicationServices.HIServices.AXAttributeConstants +import ApplicationServices.HIServices.AXActionConstants + +extension AXUIElement { + func axCallWhichCanThrow(_ result: AXError, _ successValue: inout T) throws -> T? { + switch result { + case .success: return successValue + // .cannotComplete can happen if the app is unresponsive; we throw in that case to retry until the call succeeds + case .cannotComplete: throw AxError.runtimeError + // for other errors it's pointless to retry + default: return nil + } + } + + func cgWindowId() throws -> CGWindowID? { + var id = CGWindowID(0) + return try axCallWhichCanThrow(_AXUIElementGetWindow(self, &id), &id) + } + + func pid() throws -> pid_t? { + var pid = pid_t(0) + return try axCallWhichCanThrow(AXUIElementGetPid(self, &pid), &pid) + } + + func attribute(_ key: String, _ _: T.Type) throws -> T? { + var value: AnyObject? + return try axCallWhichCanThrow(AXUIElementCopyAttributeValue(self, key as CFString, &value), &value) as? T + } + + private func value(_ key: String, _ target: T, _ type: AXValueType) throws -> T? { + if let a = try attribute(key, AXValue.self) { + var value = target + AXValueGetValue(a, type, &value) + return value + } + return nil + } + + func position() throws -> CGPoint? { + return try value(kAXPositionAttribute, CGPoint.zero, .cgPoint) + } + + func size() throws -> CGSize? { + return try value(kAXSizeAttribute, CGSize.zero, .cgSize) + } + + func title() throws -> String? { + return try attribute(kAXTitleAttribute, String.self) + } + + func parent() throws -> AXUIElement? { + return try attribute(kAXParentAttribute, AXUIElement.self) + } + + func children() throws -> [AXUIElement]? { + return try attribute(kAXChildrenAttribute, [AXUIElement].self) + } + + func windows() throws -> [AXUIElement]? { + return try attribute(kAXWindowsAttribute, [AXUIElement].self) + } + + func isMinimized() throws -> Bool { + return try attribute(kAXMinimizedAttribute, Bool.self) == true + } + + func isFullscreen() throws -> Bool { + return try attribute(kAXFullscreenAttribute, Bool.self) == true + } + + func focusedWindow() throws -> AXUIElement? { + return try attribute(kAXFocusedWindowAttribute, AXUIElement.self) + } + + func role() throws -> String? { + return try attribute(kAXRoleAttribute, String.self) + } + + func subrole() throws -> String? { + return try attribute(kAXSubroleAttribute, String.self) + } + + func appIsRunning() throws -> Bool? { + return try attribute(kAXIsApplicationRunningAttribute, Bool.self) + } + + func closeButton() throws -> AXUIElement? { + return try attribute(kAXCloseButtonAttribute, AXUIElement.self) + } + + func subscribeToNotification(_ axObserver: AXObserver, _ notification: String, _ callback: (() -> Void)? = nil) throws { + let result = AXObserverAddNotification(axObserver, self, notification as CFString, nil) + if result == .success || result == .notificationAlreadyRegistered { + callback?() + } else if result != .notificationUnsupported && result != .notImplemented { + throw AxError.runtimeError + } + } + + func setAttribute(_ key: String, _ value: Any) throws { + var unused: Void = () + try axCallWhichCanThrow(AXUIElementSetAttributeValue(self, key as CFString, value as CFTypeRef), &unused) + } + + func performAction(_ action: String) throws { + var unused: Void = () + try axCallWhichCanThrow(AXUIElementPerformAction(self, action as CFString), &unused) + } +} + +enum AxError: Error { + case runtimeError +} diff --git a/DockDoor/Utilities/PrivateApis.swift b/DockDoor/Utilities/PrivateApis.swift index 1f72c7a5..7921b1a1 100644 --- a/DockDoor/Utilities/PrivateApis.swift +++ b/DockDoor/Utilities/PrivateApis.swift @@ -11,3 +11,7 @@ import Cocoa // * macOS 10.10+ @_silgen_name("_AXUIElementGetWindow") @discardableResult func _AXUIElementGetWindow(_ axUiElement: AXUIElement, _ wid: inout CGWindowID) -> AXError + +// for some reason, these attributes are missing from AXAttributeConstants +let kAXFullscreenAttribute = "AXFullScreen" +let kAXStatusLabelAttribute = "AXStatusLabel" diff --git a/DockDoor/Utilities/WindowManipulationObservers.swift b/DockDoor/Utilities/WindowManipulationObservers.swift index b5dfa766..4a902e89 100644 --- a/DockDoor/Utilities/WindowManipulationObservers.swift +++ b/DockDoor/Utilities/WindowManipulationObservers.swift @@ -163,37 +163,3 @@ func axObserverCallback(observer: AXObserver, element: AXUIElement, notification } } } - -func printTitle(of element: AXUIElement) { - var title: AnyObject? - let result = AXUIElementCopyAttributeValue(element, kAXTitleAttribute as CFString, &title) - - if result == .success, let titleString = title as? String { - print("Title: \(titleString)") - } else { - print("Unable to retrieve title") - } -} - -enum AxError: Error { - case runtimeError -} - -func axCallWhichCanThrow(_ result: AXError, _ successValue: inout T) throws -> T? { - switch result { - case .success: - print(successValue) - return successValue - case .cannotComplete: - print("error") - throw AxError.runtimeError - default: - return nil - } -} - -func cgWindowId(appElement: AXUIElement) throws -> CGWindowID? { - var id: CGWindowID = 0 - let result = _AXUIElementGetWindow(appElement, &id) - return try axCallWhichCanThrow(result, &id) -} diff --git a/DockDoor/Utilities/WindowUtil.swift b/DockDoor/Utilities/WindowUtil.swift index 0714526a..3a156372 100644 --- a/DockDoor/Utilities/WindowUtil.swift +++ b/DockDoor/Utilities/WindowUtil.swift @@ -21,6 +21,7 @@ struct WindowInfo: Identifiable, Hashable { var windowName: String? var image: CGImage? var axElement: AXUIElement + var appAxElement: AXUIElement var closeButton: AXUIElement? var isMinimized: Bool var isHidden: Bool @@ -142,59 +143,38 @@ final class WindowUtil { return nil } - static func createAXUIElement(for pid: pid_t) -> AXUIElement { - return AXUIElementCreateApplication(pid) - } - - static func getAXWindows(for appRef: AXUIElement) -> [AXUIElement]? { - var windowList: AnyObject? - let result = AXUIElementCopyAttributeValue(appRef, kAXWindowsAttribute as CFString, &windowList) - return result == .success ? windowList as? [AXUIElement] : nil - } - static func isElementValid(_ element: AXUIElement) -> Bool { - var role: AnyObject? - let result = AXUIElementCopyAttributeValue(element, kAXRoleAttribute as CFString, &role) - return result == .success + do { + let _ = try element.role() + return true + } catch { + return false + } } static func findWindow(matchingWindow window: SCWindow, in axWindows: [AXUIElement]) -> AXUIElement? { for axWindow in axWindows { - var cgWindowId: CGWindowID = 0 - let windowIDStatus = _AXUIElementGetWindow(axWindow, &cgWindowId) - if windowIDStatus == .success && window.windowID == cgWindowId { + if let cgWindowId = try? axWindow.cgWindowId(), window.windowID == cgWindowId { return axWindow } + + // Fallback metohd + // TODO: May never be needed - var axTitle: CFTypeRef? - AXUIElementCopyAttributeValue(axWindow, kAXTitleAttribute as CFString, &axTitle) - let axTitleString = (axTitle as? String) ?? "" - - var axPosition: CFTypeRef? - AXUIElementCopyAttributeValue(axWindow, kAXPositionAttribute as CFString, &axPosition) - let axPositionValue = axPosition as? CGPoint - - var axSize: CFTypeRef? - AXUIElementCopyAttributeValue(axWindow, kAXSizeAttribute as CFString, &axSize) - let axSizeValue = axSize as? CGSize - - if let windowTitle = window.title, isFuzzyMatch(windowTitle: windowTitle, axTitleString: axTitleString) { + if let windowTitle = window.title, let axTitle = try? axWindow.title(), isFuzzyMatch(windowTitle: windowTitle, axTitleString: axTitle) { return axWindow } - if let axPositionValue = axPositionValue, - let axSizeValue = axSizeValue, - axPositionValue != .zero, - axSizeValue != .zero { + if let axPosition = try? axWindow.position(), let axSize = try? axWindow.size(), axPosition != .zero, axSize != .zero { let positionThreshold: CGFloat = 10 let sizeThreshold: CGFloat = 10 - let positionMatch = abs(axPositionValue.x - window.frame.origin.x) <= positionThreshold && - abs(axPositionValue.y - window.frame.origin.y) <= positionThreshold + let positionMatch = abs(axPosition.x - window.frame.origin.x) <= positionThreshold && + abs(axPosition.y - window.frame.origin.y) <= positionThreshold - let sizeMatch = abs(axSizeValue.width - window.frame.size.width) <= sizeThreshold && - abs(axSizeValue.height - window.frame.size.height) <= sizeThreshold + let sizeMatch = abs(axSize.width - window.frame.size.width) <= sizeThreshold && + abs(axSize.height - window.frame.size.height) <= sizeThreshold if positionMatch && sizeMatch { return axWindow @@ -215,17 +195,6 @@ final class WindowUtil { return matchPercentage >= 0.90 || matchPercentage.isNaN || axTitleString.lowercased().contains(windowTitle.lowercased()) } - static func getCloseButton(for windowRef: AXUIElement) -> AXUIElement? { - var closeButton: AnyObject? - let result = AXUIElementCopyAttributeValue(windowRef, kAXCloseButtonAttribute as CFString, &closeButton) - - guard result == .success, let closeButtonElement = closeButton else { - return nil - } - - return (closeButtonElement as! AXUIElement) - } - static func getRunningApplication(named applicationName: String) -> NSRunningApplication? { return NSWorkspace.shared.runningApplications.first { applicationName.contains($0.localizedName ?? "") || ($0.localizedName?.contains(applicationName) ?? false) @@ -245,33 +214,30 @@ final class WindowUtil { app.unhide() } - let minimizeResult = AXUIElementSetAttributeValue(windowInfo.axElement, kAXMinimizedAttribute as CFString, kCFBooleanFalse) - - if minimizeResult != .success { - print("Error un-minimizing window: \(minimizeResult.rawValue)") - } else { + do { + try windowInfo.axElement.setAttribute(kAXMinimizedAttribute, false) NSRunningApplication(processIdentifier: windowInfo.pid)?.activate() focusOnSpecificWindow(windowInfo: windowInfo) + } catch { + print("Error un-minimizing window") } } else { - let minimizeResult = AXUIElementSetAttributeValue(windowInfo.axElement, kAXMinimizedAttribute as CFString, kCFBooleanTrue) - - if minimizeResult != .success { - print("Error minimizing window: \(minimizeResult.rawValue)") + do { + try windowInfo.axElement.setAttribute(kAXMinimizedAttribute, true) + } catch { + print("Error minimizing window") } } updateWindowDateTime(windowInfo) } static func toggleHidden(windowInfo: WindowInfo) { - let appElement = AXUIElementCreateApplication(windowInfo.pid) - let newHiddenState = !windowInfo.isHidden - let setResult = AXUIElementSetAttributeValue(appElement, kAXHiddenAttribute as CFString, newHiddenState as CFTypeRef) - - if setResult != .success { - print("Error toggling hidden state of application: \(setResult.rawValue)") + do { + try windowInfo.appAxElement.setAttribute(kAXHiddenAttribute, newHiddenState) + } catch { + print("Error toggling hidden state of application") return } @@ -283,21 +249,15 @@ final class WindowUtil { } static func focusOnSpecificWindow(windowInfo: WindowInfo) { - let appElement = AXUIElementCreateApplication(windowInfo.pid) - var windowsRef: CFTypeRef? - let result = AXUIElementCopyAttributeValue(appElement, kAXWindowsAttribute as CFString, &windowsRef) - - guard result == .success, let windows = windowsRef as? [AXUIElement] else { + guard let windows = try? windowInfo.appAxElement.windows() else { print("Failed to get windows for the application") return } for window in windows { - var titleRef: CFTypeRef? - AXUIElementCopyAttributeValue(window, kAXTitleAttribute as CFString, &titleRef) - if let title = titleRef as? String, isFuzzyMatch(windowTitle: windowInfo.windowName ?? "", axTitleString: title) { - AXUIElementPerformAction(window, kAXRaiseAction as CFString) - AXUIElementSetAttributeValue(window, kAXFocusedAttribute as CFString, kCFBooleanTrue) + if let title = try? window.title(), isFuzzyMatch(windowTitle: windowInfo.windowName ?? "", axTitleString: title) { + try? window.performAction(kAXRaiseAction) + try? window.setAttribute(kAXFocusedAttribute, true) return } } @@ -306,33 +266,39 @@ final class WindowUtil { } static func toggleFullScreen(windowInfo: WindowInfo) { - let kAXFullscreenAttribute = "AXFullScreen" as CFString - var isCurrentlyInFullScreen: CFTypeRef? - - let currentState = AXUIElementCopyAttributeValue(windowInfo.axElement, kAXFullscreenAttribute, &isCurrentlyInFullScreen) - if currentState == .success { - if let isFullScreen = isCurrentlyInFullScreen as? Bool { - AXUIElementSetAttributeValue(windowInfo.axElement, kAXFullscreenAttribute, !isFullScreen as CFBoolean) + if let isCurrentlyInFullScreen = try? windowInfo.axElement.isFullscreen() { + do { + try windowInfo.axElement.setAttribute(kAXFullscreenAttribute, !isCurrentlyInFullScreen) + } catch { + print("Fialed to toggle full screen") } + } else { + print("Failed to determine current full screen state") } } static func bringWindowToFront(windowInfo: WindowInfo) { - let raiseResult = AXUIElementPerformAction(windowInfo.axElement, kAXRaiseAction as CFString) - let focusResult = AXUIElementSetAttributeValue(windowInfo.axElement, kAXFocusedAttribute as CFString, kCFBooleanTrue) - AXUIElementSetAttributeValue(windowInfo.axElement, kAXFrontmostAttribute as CFString, kCFBooleanTrue) - let activateResult = NSRunningApplication(processIdentifier: windowInfo.pid)?.activate() - updateWindowDateTime(windowInfo) - - if activateResult != true || raiseResult != .success || focusResult != .success { - let fallbackActivateResult = NSRunningApplication(processIdentifier: windowInfo.pid)?.activate(options: [.activateAllWindows]) - let fallbackResult = AXUIElementSetAttributeValue(windowInfo.axElement, kAXFrontmostAttribute as CFString, kCFBooleanTrue) + do { + try windowInfo.axElement.performAction(kAXRaiseAction) + try windowInfo.axElement.setAttribute(kAXFocusedAttribute, true) + try? windowInfo.axElement.setAttribute(kAXFrontmostAttribute, true) + if let application = NSRunningApplication(processIdentifier: windowInfo.pid) { + if !application.activate(options: [.activateAllWindows]) { + throw NSError(domain: "FailedToActivate", code: 1, userInfo: [NSLocalizedDescriptionKey: "Failed to activate application with PID \(windowInfo.pid)"]) + } + } else { + throw NSError(domain: "ApplicationNotFound", code: 2, userInfo: [NSLocalizedDescriptionKey: "No running application found with PID \(windowInfo.pid)"]) + } - if fallbackActivateResult != true || fallbackResult != .success { - removeWindowFromDesktopSpaceCache(with: windowInfo.id, in: windowInfo.bundleID) + updateWindowDateTime(windowInfo) + } catch { + guard NSRunningApplication(processIdentifier: windowInfo.pid)?.activate(options: [.activateAllWindows]) == true, (try? windowInfo.axElement.setAttribute(kAXFrontmostAttribute, true)) != nil else { print("Failed to bring window to front with fallback attempts.") + return } + } + removeWindowFromDesktopSpaceCache(with: windowInfo.id, in: windowInfo.bundleID) } static func updateWindowDateTime(_ windowInfo: WindowInfo) { @@ -352,16 +318,10 @@ final class WindowUtil { if windowSet.isEmpty { return } - let application = createAXUIElement(for: pid) - var value: AnyObject? - let result = AXUIElementCopyAttributeValue(application, kAXWindowsAttribute as CFString, &value) - if result == .success, let windows = value as? [AXUIElement] { + let appElement = AXUIElementCreateApplication(pid) + if let windows = try? appElement.windows() { for window in windows { - var cgWindowId: CGWindowID = 0 - let windowIDStatus = _AXUIElementGetWindow(window, &cgWindowId) - if windowIDStatus == .success, - let index = windowSet.firstIndex(where: { $0.id == cgWindowId }) - { + if let cgWindowId = try? window.cgWindowId(), let index = windowSet.firstIndex(where: { $0.id == cgWindowId }) { var updatedWindow = windowSet[index] updatedWindow.lastUsed = Date() windowSet.remove(at: index) @@ -374,16 +334,16 @@ final class WindowUtil { } static func closeWindow(windowInfo: WindowInfo) { - guard let closeButton = windowInfo.closeButton else { + guard windowInfo.closeButton != nil else { print("Error: closeButton is nil.") return } - let closeResult = AXUIElementPerformAction(closeButton, kAXPressAction as CFString) - removeWindowFromDesktopSpaceCache(with: windowInfo.id, in: windowInfo.bundleID) - - if closeResult != .success { - print("Error closing window: \(closeResult.rawValue)") + do { + try windowInfo.closeButton?.performAction(kAXPressAction) + removeWindowFromDesktopSpaceCache(with: windowInfo.id, in: windowInfo.bundleID) + } catch { + print("Error closing window") return } } @@ -403,12 +363,6 @@ final class WindowUtil { } } - static func getAXAttribute(element: AXUIElement, attribute: CFString) -> T? { - var value: CFTypeRef? - let result = AXUIElementCopyAttributeValue(element, attribute, &value) - return result == .success ? value as? T : nil - } - // MARK: - Active Window Handling static func activeWindows(for applicationName: String) async throws -> [WindowInfo] { @@ -495,9 +449,9 @@ final class WindowUtil { } let pid = owningApplication.processID - let appRef = createAXUIElement(for: pid) + let appElement = AXUIElementCreateApplication(pid) - guard let axWindows = getAXWindows(for: appRef), !axWindows.isEmpty else { + guard let axWindows = try? appElement.windows(), !axWindows.isEmpty else { return nil } @@ -505,7 +459,7 @@ final class WindowUtil { return nil } - let closeButton = getCloseButton(for: windowRef) + let closeButton = try? windowRef.closeButton() var windowInfo = WindowInfo(id: windowID, window: window, @@ -515,10 +469,12 @@ final class WindowUtil { windowName: window.title, image: nil, axElement: windowRef, + appAxElement: AXUIElementCreateApplication(pid), closeButton: closeButton, isMinimized: false, isHidden: false, - lastUsed: Date()) + lastUsed: Date() + ) do { windowInfo.image = try await captureWindowImage(window: window) @@ -576,18 +532,12 @@ final class WindowUtil { static func updateStatusOfWindowCache(pid: pid_t, bundleID: String, isParentAppHidden: Bool) { let appElement = AXUIElementCreateApplication(pid) - var value: AnyObject? - let result = AXUIElementCopyAttributeValue(appElement, kAXWindowsAttribute as CFString, &value) - - if result == .success, let windows = value as? [AXUIElement] { + if let windows = try? appElement.windows() { desktopSpaceWindowCacheManager.updateCache(bundleId: bundleID) { cachedWindows in for window in windows { - var cgWindowId: CGWindowID = 0 - let windowIDStatus = _AXUIElementGetWindow(window, &cgWindowId) - - if windowIDStatus == .success { - let isMinimized: Bool = getAXAttribute(element: window, attribute: kAXMinimizedAttribute as CFString) ?? false - + if let cgWindowId = try? window.cgWindowId() { + let isMinimized: Bool = (try? window.isMinimized()) ?? false + cachedWindows = Set(cachedWindows.map { windowInfo in var updatedWindow = windowInfo if windowInfo.id == cgWindowId {