Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

✨ Improve handling of Accessibility API #443

Merged
merged 7 commits into from
Jun 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading