Skip to content

Commit

Permalink
⚡️ Improvements to AXUIElement extensions
Browse files Browse the repository at this point in the history
Also use NSAccessibility
  • Loading branch information
MrKai77 committed Sep 7, 2023
1 parent 610ed77 commit 1ed9243
Show file tree
Hide file tree
Showing 3 changed files with 69 additions and 52 deletions.
57 changes: 43 additions & 14 deletions Loop/Extensions/AXUIElement+Extensions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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?
Expand All @@ -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)
}
}
56 changes: 22 additions & 34 deletions Loop/Window/Window.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,102 +8,90 @@
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
return size
}
@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) {
if animate {
let animation = WindowTransformAnimation(rect, window: self)
animation.startInBackground()
} else {
self.setOrigin(rect.origin)
self.setPosition(rect.origin)
self.setSize(rect.size)
}
}
Expand Down
8 changes: 4 additions & 4 deletions Loop/Window/WindowEngine.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -166,7 +166,7 @@ struct WindowEngine {
fixedWindowFrame.origin.y = screenFrame.maxY - fixedWindowFrame.height - Defaults[.windowPadding]
}

window.setOrigin(fixedWindowFrame.origin)
window.setPosition(fixedWindowFrame.origin)
}
}

Expand Down

0 comments on commit 1ed9243

Please sign in to comment.