From 1ed9243c9517e8533cdfb4d4d963ab9f651704b0 Mon Sep 17 00:00:00 2001 From: Kai Azim <68963405+MrKai77@users.noreply.github.com> Date: Wed, 6 Sep 2023 21:12:09 -0600 Subject: [PATCH] =?UTF-8?q?=E2=9A=A1=EF=B8=8F=20Improvements=20to=20`AXUIE?= =?UTF-8?q?lement`=20extensions?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Also use NSAccessibility --- Loop/Extensions/AXUIElement+Extensions.swift | 57 +++++++++++++++----- Loop/Window/Window.swift | 56 ++++++++----------- Loop/Window/WindowEngine.swift | 8 +-- 3 files changed, 69 insertions(+), 52 deletions(-) diff --git a/Loop/Extensions/AXUIElement+Extensions.swift b/Loop/Extensions/AXUIElement+Extensions.swift index af522ed1..31ebe33f 100644 --- a/Loop/Extensions/AXUIElement+Extensions.swift +++ b/Loop/Extensions/AXUIElement+Extensions.swift @@ -8,24 +8,49 @@ import SwiftUI extension AXUIElement { - func getValue(attribute: String) -> CFTypeRef? { - var ref: CFTypeRef? - let error = AXUIElementCopyAttributeValue(self, attribute as CFString, &ref) - if error == .success { - return ref + func getValue(_ attribute: NSAccessibility.Attribute) -> AnyObject? { + var value: AnyObject? + let result = AXUIElementCopyAttributeValue(self, attribute as CFString, &value) + if result == .success { + return value } - return .none + return nil + } + + @discardableResult + func setValue(_ attribute: NSAccessibility.Attribute, value: AnyObject) -> Bool { + let result = AXUIElementSetAttributeValue(self, attribute as CFString, value) + return result == .success + } + + @discardableResult + func setValue(_ attribute: NSAccessibility.Attribute, value: Bool) -> Bool { + return setValue(attribute, value: value as CFBoolean) } - func setValue(attribute: String, value: CFTypeRef) -> Bool { - let error = AXUIElementSetAttributeValue(self, attribute as CFString, value) - return error == .success + @discardableResult + func setValue(_ attribute: NSAccessibility.Attribute, value: CGPoint) -> Bool { + guard let axValue = AXValue.from(value: value, type: .cgPoint) else { return false } + return self.setValue(attribute, value: axValue) + } + + @discardableResult + func setValue(_ attribute: NSAccessibility.Attribute, value: CGSize) -> Bool { + guard let axValue = AXValue.from(value: value, type: .cgSize) else { return false } + return self.setValue(attribute, value: axValue) } func performAction(_ action: String) { AXUIElementPerformAction(self, action as CFString) } + func getElementAtPosition(_ position: CGPoint) -> AXUIElement? { + var element: AXUIElement? + let result = AXUIElementCopyElementAtPosition(self, Float(position.x), Float(position.y), &element) + guard result == .success else { return nil } + return element + } + // Only used when experimenting func getAttributeNames() -> [String]? { var ref: CFArray? @@ -35,11 +60,15 @@ extension AXUIElement { } return nil } +} - func getElementAtPosition(_ position: CGPoint) -> AXUIElement? { - var element: AXUIElement? - let result = AXUIElementCopyElementAtPosition(self, Float(position.x), Float(position.y), &element) - guard result == .success else { return nil } - return element +extension NSAccessibility.Attribute { + static let fullScreen: NSAccessibility.Attribute = NSAccessibility.Attribute(rawValue: "AXFullScreen") +} + +extension AXValue { + static func from(value: Any, type: AXValueType) -> AXValue? { + var value = value + return AXValueCreate(type, &value) } } diff --git a/Loop/Window/Window.swift b/Loop/Window/Window.swift index fd1d64c1..d9ab10c4 100644 --- a/Loop/Window/Window.swift +++ b/Loop/Window/Window.swift @@ -8,78 +8,70 @@ import SwiftUI class Window { - private let kAXFullscreenAttribute = "AXFullScreen" +// private let kAXFullscreenAttribute = "AXFullScreen" let axWindow: AXUIElement init?(window: AXUIElement) { self.axWindow = window - if role != kAXWindowRole, - subrole != kAXStandardWindowSubrole { + if role != .window, + subrole != .standardWindow { return nil } } convenience init?(pid: pid_t) { let element = AXUIElementCreateApplication(pid) - guard let window = element.getValue(attribute: kAXFocusedWindowAttribute) else { return nil } + guard let window = element.getValue(.focusedWindow) else { return nil } // swiftlint:disable force_cast self.init(window: window as! AXUIElement) // swiftlint:enable force_cast } - var role: String? { - return self.axWindow.getValue(attribute: kAXRoleAttribute) as? String + var role: NSAccessibility.Role? { + guard let value = self.axWindow.getValue(.role) as? String else { return nil } + return NSAccessibility.Role(rawValue: value) } - var subrole: String? { - return self.axWindow.getValue(attribute: kAXSubroleAttribute) as? String + var subrole: NSAccessibility.Subrole? { + guard let value = self.axWindow.getValue(.subrole) as? String else { return nil } + return NSAccessibility.Subrole(rawValue: value) } var isFullscreen: Bool { - let result = self.axWindow.getValue(attribute: kAXFullscreenAttribute) as? NSNumber + let result = self.axWindow.getValue(.fullScreen) as? NSNumber return result?.boolValue ?? false } @discardableResult func setFullscreen(_ state: Bool) -> Bool { - return self.axWindow.setValue( - attribute: kAXFullscreenAttribute, - value: state ? kCFBooleanTrue : kCFBooleanFalse - ) + return self.axWindow.setValue(.fullScreen, value: state) } var isMinimized: Bool { - let result = self.axWindow.getValue(attribute: kAXMinimizedAttribute) as? NSNumber + let result = self.axWindow.getValue(.minimized) as? NSNumber return result?.boolValue ?? false } @discardableResult func setMinimized(_ state: Bool) -> Bool { - return self.axWindow.setValue( - attribute: kAXMinimizedAttribute, - value: state ? kCFBooleanTrue : kCFBooleanFalse - ) + return self.axWindow.setValue(.minimized, value: state) } - var origin: CGPoint { + var position: CGPoint { var point: CGPoint = .zero - guard let value = self.axWindow.getValue(attribute: kAXPositionAttribute) else { return point } + guard let value = self.axWindow.getValue(.position) else { return point } // swiftlint:disable force_cast AXValueGetValue(value as! AXValue, .cgPoint, &point) // Convert to CGPoint // swiftlint:enable force_cast return point } @discardableResult - func setOrigin(_ origin: CGPoint) -> Bool { - var position = origin - if let value = AXValueCreate(AXValueType.cgPoint, &position) { - return self.axWindow.setValue(attribute: kAXPositionAttribute, value: value) - } - return false + func setPosition(_ position: CGPoint) -> Bool { + return self.axWindow.setValue(.position, value: position) } var size: CGSize { var size: CGSize = .zero - guard let value = self.axWindow.getValue(attribute: kAXSizeAttribute) else { return size } + guard let value = self.axWindow.getValue(.size) else { return size } // swiftlint:disable force_cast AXValueGetValue(value as! AXValue, .cgSize, &size) // Convert to CGSize // swiftlint:enable force_cast @@ -87,15 +79,11 @@ class Window { } @discardableResult func setSize(_ size: CGSize) -> Bool { - var size = size - if let value = AXValueCreate(AXValueType.cgSize, &size) { - return self.axWindow.setValue(attribute: kAXSizeAttribute, value: value) - } - return false + return self.axWindow.setValue(.size, value: size) } var frame: CGRect { - return CGRect(origin: self.origin, size: self.size) + return CGRect(origin: self.position, size: self.size) } func setFrame(_ rect: CGRect, animate: Bool = false) { @@ -103,7 +91,7 @@ class Window { let animation = WindowTransformAnimation(rect, window: self) animation.startInBackground() } else { - self.setOrigin(rect.origin) + self.setPosition(rect.origin) self.setSize(rect.size) } } diff --git a/Loop/Window/WindowEngine.swift b/Loop/Window/WindowEngine.swift index fcda88c5..13c33695 100644 --- a/Loop/Window/WindowEngine.swift +++ b/Loop/Window/WindowEngine.swift @@ -56,9 +56,9 @@ struct WindowEngine { #if DEBUG print("===== NEW WINDOW =====") - print("Frontmost app: \(app)") - print("kAXWindowRole: \(window.role ?? "N/A")") - print("kAXStandardWindowSubrole: \(window.subrole ?? "N/A")") + print("Frontmost window: \(window.axWindow)") + print("kAXWindowRole: \(window.role?.rawValue ?? "N/A")") + print("kAXStandardWindowSubrole: \(window.subrole?.rawValue ?? "N/A")") #endif return window @@ -166,7 +166,7 @@ struct WindowEngine { fixedWindowFrame.origin.y = screenFrame.maxY - fixedWindowFrame.height - Defaults[.windowPadding] } - window.setOrigin(fixedWindowFrame.origin) + window.setPosition(fixedWindowFrame.origin) } }