Skip to content

Commit

Permalink
🔀 Merge pull request #443 from MrKai77/improved-AX-handling
Browse files Browse the repository at this point in the history
✨ Improve handling of Accessibility API
  • Loading branch information
MrKai77 authored Jun 27, 2024
2 parents 5b68714 + 524c73a commit e56c564
Show file tree
Hide file tree
Showing 14 changed files with 600 additions and 205 deletions.
8 changes: 4 additions & 4 deletions Loop.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
A80397D22A93287C006D2796 /* MenuBarExtraAccess in Frameworks */ = {isa = PBXBuildFile; productRef = A80397D12A93287C006D2796 /* MenuBarExtraAccess */; settings = {ATTRIBUTES = (Required, ); }; };
A80397D42A932993006D2796 /* MenuBarIconView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A80397D32A932993006D2796 /* MenuBarIconView.swift */; };
A8055EC22AFEDE0B00459D13 /* Keycorder.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8055EC12AFEDE0B00459D13 /* Keycorder.swift */; };
A8063A732B19891900EAB3D9 /* grid.metal in Sources */ = {isa = PBXBuildFile; fileRef = A8063A722B19891900EAB3D9 /* grid.metal */; };
A80900D52AA3F9F30085C63B /* VisualEffectView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A80900D32AA3F9F20085C63B /* VisualEffectView.swift */; };
A80D49BB2BAE479900493B67 /* WindowAction+Port.swift in Sources */ = {isa = PBXBuildFile; fileRef = A80D49BA2BAE479900493B67 /* WindowAction+Port.swift */; };
A81989062AC8EDB300EFF7A1 /* MenuBarHeaderText.swift in Sources */ = {isa = PBXBuildFile; fileRef = A81989052AC8EDB300EFF7A1 /* MenuBarHeaderText.swift */; };
Expand Down Expand Up @@ -50,6 +49,7 @@
A85CB5852ACFA5F700BF63E6 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = A85CB5842ACFA5F700BF63E6 /* AppDelegate.swift */; };
A85DDBDA2C1693D4008C103D /* WindowDirection+Snapping.swift in Sources */ = {isa = PBXBuildFile; fileRef = A85DDBD92C1693D4008C103D /* WindowDirection+Snapping.swift */; };
A864F4682AA660CD00579738 /* WindowDragManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = A864F4672AA660CD00579738 /* WindowDragManager.swift */; };
A867C20E2C26522B005831BC /* Observer.swift in Sources */ = {isa = PBXBuildFile; fileRef = A867C20D2C26522B005831BC /* Observer.swift */; };
A86949862A8F2BB70051AAAF /* CGKeyCode+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = A86949852A8F2BB60051AAAF /* CGKeyCode+Extensions.swift */; };
A869C1A12B38C6E600AD1A84 /* StageManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = A869C1A02B38C6E600AD1A84 /* StageManager.swift */; };
A86B97AD2AB79E2500099D7F /* ShakeEffect.swift in Sources */ = {isa = PBXBuildFile; fileRef = A86B97AC2AB79E2500099D7F /* ShakeEffect.swift */; };
Expand Down Expand Up @@ -101,7 +101,6 @@
A80397D32A932993006D2796 /* MenuBarIconView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MenuBarIconView.swift; sourceTree = "<group>"; };
A80521312A84878200BF7E22 /* Config.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Config.xcconfig; sourceTree = "<group>"; };
A8055EC12AFEDE0B00459D13 /* Keycorder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Keycorder.swift; sourceTree = "<group>"; };
A8063A722B19891900EAB3D9 /* grid.metal */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.metal; path = grid.metal; sourceTree = "<group>"; };
A80900D32AA3F9F20085C63B /* VisualEffectView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VisualEffectView.swift; sourceTree = "<group>"; };
A80AB7E22C276E0300BE0360 /* en-GB */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "en-GB"; path = "en-GB.lproj/InfoPlist.strings"; sourceTree = "<group>"; };
A80D49BA2BAE479900493B67 /* WindowAction+Port.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WindowAction+Port.swift"; sourceTree = "<group>"; };
Expand Down Expand Up @@ -137,6 +136,7 @@
A85CB5842ACFA5F700BF63E6 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
A85DDBD92C1693D4008C103D /* WindowDirection+Snapping.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WindowDirection+Snapping.swift"; sourceTree = "<group>"; };
A864F4672AA660CD00579738 /* WindowDragManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WindowDragManager.swift; sourceTree = "<group>"; };
A867C20D2C26522B005831BC /* Observer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Observer.swift; sourceTree = "<group>"; };
A86949852A8F2BB60051AAAF /* CGKeyCode+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CGKeyCode+Extensions.swift"; sourceTree = "<group>"; };
A869C1A02B38C6E600AD1A84 /* StageManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StageManager.swift; sourceTree = "<group>"; };
A86AFD7529888B29008F4892 /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = "<group>"; };
Expand Down Expand Up @@ -220,7 +220,7 @@
A86B97AC2AB79E2500099D7F /* ShakeEffect.swift */,
A8D6D3002B6C894C0061B11F /* PaddingModel.swift */,
A8D6D3042B6C92F20061B11F /* WallpaperView.swift */,
A8063A722B19891900EAB3D9 /* grid.metal */,
A867C20D2C26522B005831BC /* Observer.swift */,
);
path = Utilities;
sourceTree = "<group>";
Expand Down Expand Up @@ -587,6 +587,7 @@
A8D4327B2C13ED3C007BE4F2 /* Icon.swift in Sources */,
A86B97AD2AB79E2500099D7F /* ShakeEffect.swift in Sources */,
A8D6D3032B6C8D750061B11F /* PaddingPreviewView.swift in Sources */,
A867C20E2C26522B005831BC /* Observer.swift in Sources */,
A82740982AB00FCE00B9BDC5 /* Color+Extensions.swift in Sources */,
A82B1AF62BD35C8500E2F3F9 /* BehaviorConfiguration.swift in Sources */,
A869C1A12B38C6E600AD1A84 /* StageManager.swift in Sources */,
Expand Down Expand Up @@ -638,7 +639,6 @@
A81989082AC8F2AE00EFF7A1 /* MenuBarResizeButton.swift in Sources */,
A87DDD152B50A6A400A32C76 /* ScreenManager.swift in Sources */,
A86949862A8F2BB70051AAAF /* CGKeyCode+Extensions.swift in Sources */,
A8063A732B19891900EAB3D9 /* grid.metal in Sources */,
A8F0125B2AEDD7660017307F /* WindowAction.swift in Sources */,
A8BC77792C0EB4DD008E2EDA /* AppDelegate+UNNotifications.swift in Sources */,
A80397D42A932993006D2796 /* MenuBarIconView.swift in Sources */,
Expand Down
2 changes: 1 addition & 1 deletion Loop/AppDelegate+UNNotifications.swift
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ extension AppDelegate: UNUserNotificationCenterDelegate {
}

if let error {
print(error)
print(error.localizedDescription)
}
}
}
Expand Down
144 changes: 107 additions & 37 deletions Loop/Extensions/AXUIElement+Extensions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,46 +10,41 @@ import SwiftUI
extension AXUIElement {
static let systemWide = AXUIElementCreateSystemWide()

func getValue(_ attribute: NSAccessibility.Attribute) -> AnyObject? {
func getValue<T>(_ attribute: NSAccessibility.Attribute) throws -> T? {
var value: AnyObject?
let result = AXUIElementCopyAttributeValue(self, attribute as CFString, &value)
if result == .success {
return value
let error = AXUIElementCopyAttributeValue(self, attribute as CFString, &value)

if error == .noValue || error == .attributeUnsupported {
return nil
}
return nil
}

@discardableResult
func setValue(_ attribute: NSAccessibility.Attribute, value: AnyObject) -> Bool {
let result = AXUIElementSetAttributeValue(self, attribute as CFString, value)
return result == .success
}
guard error == .success else {
throw error
}

@discardableResult
func setValue(_ attribute: NSAccessibility.Attribute, value: Bool) -> Bool {
setValue(attribute, value: value as CFBoolean)
}
guard let unpackedValue = (unpackAXValue(value!) as? T) else {
throw AXError.illegalArgument
}

@discardableResult
func setValue(_ attribute: NSAccessibility.Attribute, value: CGPoint) -> Bool {
guard let axValue = AXValue.from(value: value, type: .cgPoint) else { return false }
return setValue(attribute, value: axValue)
return unpackedValue
}

@discardableResult
func setValue(_ attribute: NSAccessibility.Attribute, value: CGSize) -> Bool {
guard let axValue = AXValue.from(value: value, type: .cgSize) else { return false }
return setValue(attribute, value: axValue)
}
func setValue(_ attribute: NSAccessibility.Attribute, value: Any) throws {
let error = AXUIElementSetAttributeValue(self, attribute as CFString, packAXValue(value))

func performAction(_ action: String) {
AXUIElementPerformAction(self, action as CFString)
guard error == .success else {
throw error
}
}

func getElementAtPosition(_ position: CGPoint) -> AXUIElement? {
func getElementAtPosition(_ position: CGPoint) throws -> AXUIElement? {
var element: AXUIElement?
let result = AXUIElementCopyElementAtPosition(self, Float(position.x), Float(position.y), &element)
guard result == .success else { return nil }
let error = AXUIElementCopyElementAtPosition(self, Float(position.x), Float(position.y), &element)

guard error == .success else {
throw error
}

return element
}

Expand All @@ -62,18 +57,93 @@ extension AXUIElement {
}
return nil
}

private func packAXValue(_ value: Any) -> AnyObject {
switch value {
case let val as Window:
val.axWindow
case let val as Bool:
val as CFBoolean
case var val as CFRange:
AXValueCreate(AXValueType(rawValue: kAXValueCFRangeType)!, &val)!
case var val as CGPoint:
AXValueCreate(AXValueType(rawValue: kAXValueCGPointType)!, &val)!
case var val as CGRect:
AXValueCreate(AXValueType(rawValue: kAXValueCGRectType)!, &val)!
case var val as CGSize:
AXValueCreate(AXValueType(rawValue: kAXValueCGSizeType)!, &val)!
default:
value as AnyObject
}
}

private func unpackAXValue(_ value: AnyObject) -> Any {
switch CFGetTypeID(value) {
case AXUIElementGetTypeID():
return value as! AXUIElement
case AXValueGetTypeID():
let type = AXValueGetType(value as! AXValue)
switch type {
case .axError:
var result: AXError = .success
let success = AXValueGetValue(value as! AXValue, type, &result)
assert(success)
return result
case .cfRange:
var result = CFRange()
let success = AXValueGetValue(value as! AXValue, type, &result)
assert(success)
return result
case .cgPoint:
var result = CGPoint.zero
let success = AXValueGetValue(value as! AXValue, type, &result)
assert(success)
return result
case .cgRect:
var result = CGRect.zero
let success = AXValueGetValue(value as! AXValue, type, &result)
assert(success)
return result
case .cgSize:
var result = CGSize.zero
let success = AXValueGetValue(value as! AXValue, type, &result)
assert(success)
return result
default:
return value
}
default:
return value
}
}

func getPID() throws -> pid_t? {
var pid: pid_t = 0
let error = AXUIElementGetPid(self, &pid)

guard error == .success else {
throw error
}

return pid
}

func getWindowID() throws -> CGWindowID {
var id: CGWindowID = 0
let error = _AXUIElementGetWindow(self, &id)

guard error == .success else {
throw error
}

return id
}
}

extension AXError: Swift.Error {}

extension NSAccessibility.Attribute {
static let fullScreen: NSAccessibility.Attribute = .init(rawValue: "AXFullScreen")
static let enhancedUserInterface = NSAccessibility.Attribute(rawValue: "AXEnhancedUserInterface")
static let windowIds = NSAccessibility.Attribute(rawValue: "AXWindowsIDs")
}

extension AXValue {
static func from(value: Any, type: AXValueType) -> AXValue? {
withUnsafePointer(to: value) { ptr in
AXValueCreate(type, ptr)
}
}
}
2 changes: 1 addition & 1 deletion Loop/Extensions/UNNotification+Extensions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ extension UNNotificationAttachment {
)
return imageAttachment
} catch {
print("error \(error)")
print("error \(error.localizedDescription)")
}

return nil
Expand Down
12 changes: 8 additions & 4 deletions Loop/Luminare/Settings/Behavior/BehaviorConfiguration.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,14 @@ class BehaviorConfigurationModel: ObservableObject {
didSet {
Defaults[.launchAtLogin] = launchAtLogin

if launchAtLogin {
try? SMAppService().register()
} else {
try? SMAppService().unregister()
do {
if launchAtLogin {
try SMAppService().register()
} else {
try SMAppService().unregister()
}
} catch {
print("Failed to \(launchAtLogin ? "register" : "unregister") login item: \(error.localizedDescription)")
}
}
}
Expand Down
26 changes: 14 additions & 12 deletions Loop/Managers/WindowDragManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -72,15 +72,19 @@ class WindowDragManager {

let mousePosition = NSEvent.mouseLocation.flipY(screen: screen)

guard
let draggingWindow = WindowEngine.windowAtPosition(mousePosition),
!draggingWindow.isAppExcluded
else {
return
}
do {
guard
let draggingWindow = try WindowEngine.windowAtPosition(mousePosition),
!draggingWindow.isAppExcluded
else {
return
}

self.draggingWindow = draggingWindow
initialWindowFrame = draggingWindow.frame
self.draggingWindow = draggingWindow
initialWindowFrame = draggingWindow.frame
} catch {
print("Failed to get window at position: \(error.localizedDescription)")
}
}

private func hasWindowMoved(_ windowFrame: CGRect, _ initialFrame: CGRect) -> Bool {
Expand All @@ -103,7 +107,7 @@ class WindowDragManager {
newWindowFrame = newWindowFrame.pushBottomRightPointInside(screen.frame)
window.setFrame(newWindowFrame)
} else {
window.setSize(initialFrame.size)
window.size = initialFrame.size
}

// If the window doesn't contain the cursor, keep the original maxX
Expand Down Expand Up @@ -178,9 +182,7 @@ class WindowDragManager {
}

private func attemptWindowSnap(_ window: Window) {
guard
let screen = NSScreen.screenWithMouse
else {
guard let screen = NSScreen.screenWithMouse else {
return
}

Expand Down
2 changes: 1 addition & 1 deletion Loop/MenuBar/MenuBarResizeButton.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ struct MenuBarResizeButton: View {

var body: some View {
Button {
if let frontmostWindow = WindowEngine.frontmostWindow,
if let frontmostWindow = try? WindowEngine.getFrontmostWindow(),
let screen = NSScreen.main {
WindowEngine.resize(frontmostWindow, to: .init(direction), on: screen)
}
Expand Down
8 changes: 6 additions & 2 deletions Loop/Updater/Updater.swift
Original file line number Diff line number Diff line change
Expand Up @@ -278,13 +278,17 @@ class Updater: ObservableObject {

DispatchQueue.main.async {
self.progressBar = 0.8
try? fileManager.removeItem(atPath: fileURL) // Clean up the zip file after extraction.
do {
try fileManager.removeItem(atPath: fileURL) // Clean up the zip file after extraction.
} catch {
print("Failed to delete downloaded file: \(error.localizedDescription)")
}
self.progressBar = 1.0
self.updateState = .unavailable // Update the state to reflect that the update has been applied.
}
} catch {
DispatchQueue.main.async {
NSLog("Error updating the app: \(error)")
NSLog("Error updating the app: \(error.localizedDescription)")
}
}
}
Expand Down
Loading

0 comments on commit e56c564

Please sign in to comment.