diff --git a/.gitignore b/.gitignore index b1bcb06..0e4c1d2 100644 --- a/.gitignore +++ b/.gitignore @@ -17,4 +17,6 @@ xcuserdata *.xccheckout *.moved-aside *.xcuserstate -*.hmap \ No newline at end of file +*.hmap + +*xcshareddata/ \ No newline at end of file diff --git a/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata b/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..919434a --- /dev/null +++ b/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/Package.swift b/Package.swift new file mode 100644 index 0000000..921bdca --- /dev/null +++ b/Package.swift @@ -0,0 +1,20 @@ +// swift-tools-version:5.2 + +import PackageDescription + +let package = Package( + name: "ZMAX", + platforms: [ + .macOS(.v10_15) + ], + products: [ + .library( + name: "ZMAX", + targets: ["ZMAX"]), + ], + targets: [ + .target( + name: "ZMAX", + path: "ZMAX/Sources"), + ] +) diff --git a/README.md b/README.md index 10f6689..fcfa264 100644 --- a/README.md +++ b/README.md @@ -2,8 +2,8 @@ ZMAX is a framework that bridges AX (Accessibility) APIs to Swift. -- Requirements: OS X 10.11 or later -- Swift version: 4.0 +- Requirements: OS X 10.15 or later +- Swift version: 5.2 ## Installation @@ -12,7 +12,7 @@ ZMAX is a framework that bridges AX (Accessibility) APIs to Swift. To install this framework using [Carthage](https://github.com/Carthage/Carthage), add the following line to your Cartfile. ``` -github "zumuya/ZMAX" +github "soundflix/ZMAX" ``` ## General usage diff --git a/ZMAX.xcodeproj/project.pbxproj b/ZMAX.xcodeproj/project.pbxproj index 8110867..d60cb4c 100644 --- a/ZMAX.xcodeproj/project.pbxproj +++ b/ZMAX.xcodeproj/project.pbxproj @@ -3,22 +3,25 @@ archiveVersion = 1; classes = { }; - objectVersion = 48; + objectVersion = 54; objects = { /* Begin PBXBuildFile section */ 7839D15E2054CB43003B388E /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7839D15D2054CB43003B388E /* Constants.swift */; }; 7839D1602054CC5A003B388E /* Observer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7839D15F2054CC5A003B388E /* Observer.swift */; }; 7839D1622054CC8D003B388E /* Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7839D1612054CC8D003B388E /* Extensions.swift */; }; + DCF6E2BC292D9C9B00CBF027 /* Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCF6E2BB292D9C9B00CBF027 /* Utilities.swift */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ + 28EEEBA126B89FA400360256 /* Package.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Package.swift; sourceTree = ""; }; 7839D151205418D0003B388E /* ZMAX.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = ZMAX.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 7839D155205418D0003B388E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 7839D15D2054CB43003B388E /* Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = ""; }; 7839D15F2054CC5A003B388E /* Observer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Observer.swift; sourceTree = ""; }; 7839D1612054CC8D003B388E /* Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Extensions.swift; sourceTree = ""; }; 7839D16320550CC8003B388E /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; + DCF6E2BB292D9C9B00CBF027 /* Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Utilities.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -32,30 +35,32 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ - 7839D147205418D0003B388E = { + 28EEEBAE26B8A90C00360256 /* ZMAX */ = { isa = PBXGroup; children = ( - 7839D16320550CC8003B388E /* README.md */, - 7839D153205418D0003B388E /* ZMAX */, + 7839D155205418D0003B388E /* Info.plist */, + 7839D15C2054CAF0003B388E /* Sources */, 7839D152205418D0003B388E /* Products */, ); + path = ZMAX; sourceTree = ""; }; - 7839D152205418D0003B388E /* Products */ = { + 7839D147205418D0003B388E = { isa = PBXGroup; children = ( - 7839D151205418D0003B388E /* ZMAX.framework */, + 7839D16320550CC8003B388E /* README.md */, + 28EEEBA126B89FA400360256 /* Package.swift */, + 28EEEBAE26B8A90C00360256 /* ZMAX */, ); - name = Products; sourceTree = ""; }; - 7839D153205418D0003B388E /* ZMAX */ = { + 7839D152205418D0003B388E /* Products */ = { isa = PBXGroup; children = ( - 7839D155205418D0003B388E /* Info.plist */, - 7839D15C2054CAF0003B388E /* Sources */, + 7839D151205418D0003B388E /* ZMAX.framework */, ); - path = ZMAX; + name = Products; + path = ..; sourceTree = ""; }; 7839D15C2054CAF0003B388E /* Sources */ = { @@ -64,6 +69,7 @@ 7839D15D2054CB43003B388E /* Constants.swift */, 7839D15F2054CC5A003B388E /* Observer.swift */, 7839D1612054CC8D003B388E /* Extensions.swift */, + DCF6E2BB292D9C9B00CBF027 /* Utilities.swift */, ); path = Sources; sourceTree = ""; @@ -105,22 +111,23 @@ 7839D148205418D0003B388E /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 0900; + LastUpgradeCheck = 1240; ORGANIZATIONNAME = zumuya; TargetAttributes = { 7839D150205418D0003B388E = { CreatedOnToolsVersion = 9.0; - LastSwiftMigration = 0900; + LastSwiftMigration = 1240; ProvisioningStyle = Manual; }; }; }; buildConfigurationList = 7839D14B205418D0003B388E /* Build configuration list for PBXProject "ZMAX" */; - compatibilityVersion = "Xcode 8.0"; + compatibilityVersion = "Xcode 12.0"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( en, + Base, ); mainGroup = 7839D147205418D0003B388E; productRefGroup = 7839D152205418D0003B388E /* Products */; @@ -149,6 +156,7 @@ files = ( 7839D15E2054CB43003B388E /* Constants.swift in Sources */, 7839D1622054CC8D003B388E /* Extensions.swift in Sources */, + DCF6E2BC292D9C9B00CBF027 /* Utilities.swift in Sources */, 7839D1602054CC5A003B388E /* Observer.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -170,6 +178,7 @@ CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; @@ -177,8 +186,10 @@ CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; @@ -205,7 +216,8 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.11; + GENERATE_PKGINFO_FILE = NO; + MACOSX_DEPLOYMENT_TARGET = 10.15; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = macosx; @@ -230,6 +242,7 @@ CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; @@ -237,8 +250,10 @@ CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; @@ -259,10 +274,12 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.11; + GENERATE_PKGINFO_FILE = NO; + MACOSX_DEPLOYMENT_TARGET = 10.15; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = macosx; - SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; @@ -283,13 +300,17 @@ FRAMEWORK_VERSION = A; INFOPLIST_FILE = ZMAX/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + "@loader_path/Frameworks", + ); PRODUCT_BUNDLE_IDENTIFIER = zom.zumuya.ZMAX; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; PROVISIONING_PROFILE_SPECIFIER = ""; SKIP_INSTALL = YES; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 4.0; + SWIFT_VERSION = 5.0; }; name = Debug; }; @@ -308,12 +329,16 @@ FRAMEWORK_VERSION = A; INFOPLIST_FILE = ZMAX/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + "@loader_path/Frameworks", + ); PRODUCT_BUNDLE_IDENTIFIER = zom.zumuya.ZMAX; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; PROVISIONING_PROFILE_SPECIFIER = ""; SKIP_INSTALL = YES; - SWIFT_VERSION = 4.0; + SWIFT_VERSION = 5.0; }; name = Release; }; diff --git a/ZMAX.xcodeproj/xcshareddata/xcschemes/ZMAX.xcscheme b/ZMAX.xcodeproj/xcshareddata/xcschemes/ZMAX.xcscheme index f9e30c5..17235cb 100644 --- a/ZMAX.xcodeproj/xcshareddata/xcschemes/ZMAX.xcscheme +++ b/ZMAX.xcodeproj/xcshareddata/xcschemes/ZMAX.xcscheme @@ -1,6 +1,6 @@ - - - - CFBundleDevelopmentRegion - $(DEVELOPMENT_LANGUAGE) + en CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 1.0.1 + 1.1 CFBundleVersion $(CURRENT_PROJECT_VERSION) NSHumanReadableCopyright diff --git a/ZMAX/Sources/Constants.swift b/ZMAX/Sources/Constants.swift index a0e673d..3e68f6b 100644 --- a/ZMAX/Sources/Constants.swift +++ b/ZMAX/Sources/Constants.swift @@ -23,6 +23,7 @@ */ import Cocoa +import os public struct ZMAXActionName: RawRepresentable { @@ -137,7 +138,7 @@ extension AXError: LocalizedError case .invalidUIElementObserver: return "Accessibility: Invalid UI element observer" case .cannotComplete: - return "Cannot complete." + return "Accessibility: Cannot complete." case .attributeUnsupported: return "Accessibility Attribute is not supported." case .actionUnsupported: @@ -158,6 +159,9 @@ extension AXError: LocalizedError return "Accessibility parameterized attribute is not supported" case .notEnoughPrecision: return "Accessibility: Not enough precision" - } + @unknown default: + os_log(.error, log: .init(subsystem: "ZMAX", category: "Constants"), "Unknown Error") + return "Accessibility: Unknown Error" + } } } diff --git a/ZMAX/Sources/Extensions.swift b/ZMAX/Sources/Extensions.swift index 8359b98..b4eebf4 100644 --- a/ZMAX/Sources/Extensions.swift +++ b/ZMAX/Sources/Extensions.swift @@ -23,6 +23,7 @@ */ import Cocoa +import os extension NSRunningApplication { @@ -31,14 +32,15 @@ extension NSRunningApplication return AXUIElement.application(processIdentifier: processIdentifier) } - public func newAccessibilityObserver(runLoopModes: [RunLoopMode] = [.defaultRunLoopMode]) throws -> ZMAXObserver + public func newAccessibilityObserver(runLoopModes: [RunLoop.Mode] = [RunLoop.Mode.default]) throws -> ZMAXObserver { return try ZMAXObserver(processIdentifier: processIdentifier, runLoopModes: runLoopModes) } } -extension AXUIElement -{ +extension AXUIElement { + + /// Creates the systemwide `AXUIElement` public class var systemwide: AXUIElement { return AXUIElementCreateSystemWide() @@ -47,49 +49,70 @@ extension AXUIElement { return AXUIElementCreateApplication(processIdentifier) } - - public func getAttribute(for name: NSAccessibilityAttributeName) throws -> T - { - let objectPtr = UnsafeMutablePointer.allocate(capacity: 1) - defer { objectPtr.deallocate(capacity: 1) } - - try AXUIElementCopyAttributeValue(self, (name.rawValue as CFString), objectPtr).throwIfNotSuccess() - let object = objectPtr.pointee - return (object as! T) - } - public func getAttribute(for name: NSAccessibilityAttributeName, axType: AXValueType) throws -> T + /// Gets the specified attribute value + /// + /// - throws: + /// - AXError of wrapped method + /// - ZMAXError.AttributeCastValueFailed(attribute.rawValue) if value cast fails + public func getAttribute(for name: NSAccessibility.Attribute) throws -> T + { + let objectPtr = UnsafeMutablePointer.allocate(capacity: 1) + defer { objectPtr.deallocate() } + + try AXUIElementCopyAttributeValue(self, (name.rawValue as CFString), objectPtr).throwIfNotSuccess() + let object = objectPtr.pointee + // return (object as! T) + /// Throw if can not cast + guard let objectValue = object as? T else { + os_log(.error, log: .init(subsystem: "ZMAX", category: "Extensions"), "Casting attribute value failed, %{Public}@", name.rawValue) + throw ZMAXError.AttributeCastValueFailed(name.rawValue) + } + return objectValue + // TODO: another option: check for AXValue type: Only for CGSize, CGPoint, CFRange, CGRect? + // https://github.com/keith/ModMove/blob/main/ModMove/AccessibilityElement.swift + } + public func getAttributeOptional(for name: NSAccessibility.Attribute) throws -> T? + { + let objectPtr = UnsafeMutablePointer.allocate(capacity: 1) + defer { objectPtr.deallocate() } + + try AXUIElementCopyAttributeValue(self, (name.rawValue as CFString), objectPtr).throwIfNotSuccess() + let object = objectPtr.pointee + return (object as? T) + } + public func getAttribute(for name: NSAccessibility.Attribute, axType: AXValueType) throws -> T { let axValue: AXValue = try getAttribute(for: name) var value: T?; do { let valuePtr = UnsafeMutablePointer.allocate(capacity: 1) AXValueGetValue(axValue, axType, valuePtr) value = valuePtr.pointee - valuePtr.deallocate(capacity: 1) + valuePtr.deallocate() } return value! } - public func getAttribute(for name: NSAccessibilityAttributeName) throws -> Int + public func getAttribute(for name: NSAccessibility.Attribute) throws -> Int { return (try getAttribute(for: name) as NSNumber).intValue } - public func getAttribute(for name: NSAccessibilityAttributeName) throws -> CGPoint + public func getAttribute(for name: NSAccessibility.Attribute) throws -> CGPoint { return try getAttribute(for: name, axType: .cgPoint) as CGPoint } - public func getAttribute(for name: NSAccessibilityAttributeName) throws -> CGSize + public func getAttribute(for name: NSAccessibility.Attribute) throws -> CGSize { return try getAttribute(for: name, axType: .cgSize) as CGSize } - public func getAttribute(for name: NSAccessibilityAttributeName) throws -> CGRect + public func getAttribute(for name: NSAccessibility.Attribute) throws -> CGRect { return try getAttribute(for: name, axType: .cgRect) as CGRect } - public func getAttribute(for name: NSAccessibilityAttributeName) throws -> CFRange + public func getAttribute(for name: NSAccessibility.Attribute) throws -> CFRange { return try getAttribute(for: name, axType: .cfRange) as CFRange } - public func setAttribute(_ value: Any, for name: NSAccessibilityAttributeName) throws + public func setAttribute(_ value: Any, for name: NSAccessibility.Attribute) throws { let objectValue: AnyObject if var point = value as? CGPoint { @@ -102,46 +125,46 @@ extension AXUIElement try AXUIElementSetAttributeValue(self, (name.rawValue as CFString), objectValue).throwIfNotSuccess() } - public func getMultipleAttributes(for names: [NSAccessibilityAttributeName], options: AXCopyMultipleAttributeOptions = []) throws -> T + public func getMultipleAttributes(for names: [NSAccessibility.Attribute], options: AXCopyMultipleAttributeOptions = []) throws -> T { let arrayPtr = UnsafeMutablePointer.allocate(capacity: 1) - defer { arrayPtr.deallocate(capacity: 1) } + defer { arrayPtr.deallocate() } try AXUIElementCopyMultipleAttributeValues(self, (names as CFArray), options, arrayPtr).throwIfNotSuccess() let array = arrayPtr.pointee return (array as! T) } - public func getAttributeCount(for name: NSAccessibilityAttributeName) throws -> Int + public func getAttributeCount(for name: NSAccessibility.Attribute) throws -> Int { var count: CFIndex = 0 try AXUIElementGetAttributeValueCount(self, (name.rawValue as CFString), &count).throwIfNotSuccess() return count } - public func getAttributeValues(for name: NSAccessibilityAttributeName, in range: CountableRange) throws -> T + public func getAttributeValues(for name: NSAccessibility.Attribute, in range: CountableRange) throws -> T { let arrayPtr = UnsafeMutablePointer.allocate(capacity: 1) - defer { arrayPtr.deallocate(capacity: 1) } + defer { arrayPtr.deallocate() } try AXUIElementCopyAttributeValues(self, (name.rawValue as CFString), range.min()!, range.max()!, arrayPtr).throwIfNotSuccess() let array = arrayPtr.pointee return (array as! T) } - public func getAttributeNames() throws -> [NSAccessibilityAttributeName] + public func getAttributeNames() throws -> [NSAccessibility.Attribute] { let arrayPtr = UnsafeMutablePointer.allocate(capacity: 1) - defer { arrayPtr.deallocate(capacity: 1) } + defer { arrayPtr.deallocate() } try AXUIElementCopyAttributeNames(self, arrayPtr).throwIfNotSuccess() let array = arrayPtr.pointee - return (array as! [NSAccessibilityAttributeName]) + return (array as! [NSAccessibility.Attribute]) } - public func isAttributeSettable(for name: NSAccessibilityAttributeName) throws -> Bool + public func isAttributeSettable(for name: NSAccessibility.Attribute) throws -> Bool { let boolPtr = UnsafeMutablePointer.allocate(capacity: 1) - defer { boolPtr.deallocate(capacity: 1) } + defer { boolPtr.deallocate() } try AXUIElementIsAttributeSettable(self, (name.rawValue as CFString), boolPtr).throwIfNotSuccess() let bool = boolPtr.pointee.boolValue @@ -153,3 +176,79 @@ extension AXUIElement try AXUIElementPerformAction(self, action.rawValue as CFString).throwIfNotSuccess() } } + +extension AXUIElement { + /// Gets the topmost element at the specified coordinates. + /// + /// **This method can only be called on applications and the system-wide element.** + /// + /// From ``AXUIElementCopyElementAtPosition(application:,x:,y:,element:)``: + /// + /// "This function does hit-testing based on window z-order (that is, layering). + /// If one window is on top of another window, the returned accessibility object comes from + /// whichever window is topmost at the specified location. Note that if the system-wide accessibility + /// object is passed in the application parameter, the position test is not restricted to a particular application." + /// + public func elementAtPosition(_ x: Float, _ y: Float) throws -> AXUIElement? { + var element: AXUIElement? + let error = AXUIElementCopyElementAtPosition(self, x, y, &element) + if error != .success { + throw error + } + return element + } + + /// Gets the topmost element at the specified coordinates. + /// + /// **This method can only be called on applications and the system-wide element.** + /// + /// From ``AXUIElementCopyElementAtPosition(application:,x:,y:,element:)``: + /// + /// "This function does hit-testing based on window z-order (that is, layering). + /// If one window is on top of another window, the returned accessibility object comes from + /// whichever window is topmost at the specified location. Note that if the system-wide accessibility + /// object is passed in the application parameter, the position test is not restricted to a particular application." + /// + public func element(at position: CGPoint) throws -> AXUIElement? { + try self.elementAtPosition(Float(position.x), Float(position.y)) + } + + /// Returns the process ID of the application that the element is a part of. + /// + /// Throws only if the element is invalid (`Errors.InvalidUIElement`). + public func pid() throws -> pid_t { + var pid: pid_t = -1 + let error = AXUIElementGetPid(self, &pid) + if error != .success { + throw error + } + return pid + } +} + +extension AXValue { + // for conversion AXValue (wrapper) <> wrapped value + // from: https://github.com/keith/ModMove/blob/main/ModMove/AXValue%2BHelper.swift + public func toValue() -> T? { + let pointer = UnsafeMutablePointer.allocate(capacity: 1) + defer { + pointer.deallocate() + } + let success = AXValueGetValue(self, AXValueGetType(self), pointer) + return success ? pointer.pointee : nil + } + + public static func from(value: T, type: AXValueType) -> AXValue? { + let pointer = UnsafeMutablePointer.allocate(capacity: 1) + defer { + pointer.deallocate() + } + pointer.pointee = value + return AXValueCreate(type, pointer) + } +} + +// FIXME: New file not recognized in build? +enum ZMAXError: Error { + case AttributeCastValueFailed(_ attribute: String) +} diff --git a/ZMAX/Sources/Observer.swift b/ZMAX/Sources/Observer.swift index c3d4a2c..ed0e9dc 100644 --- a/ZMAX/Sources/Observer.swift +++ b/ZMAX/Sources/Observer.swift @@ -26,18 +26,18 @@ import Cocoa public final class ZMAXObserver { - let axObserver: AXObserver + public let axObserver: AXObserver var removeRunLoopHandlers: [()->Void] = [] - convenience init(application: NSRunningApplication, runLoopModes: [RunLoopMode] = [.defaultRunLoopMode]) throws + public convenience init(application: NSRunningApplication, runLoopModes: [RunLoop.Mode] = [RunLoop.Mode.default]) throws { try self.init(processIdentifier: application.processIdentifier, runLoopModes: runLoopModes) } - public init(processIdentifier: pid_t, runLoopModes: [RunLoopMode] = [.defaultRunLoopMode]) throws + public init(processIdentifier: pid_t, runLoopModes: [RunLoop.Mode] = [RunLoop.Mode.default]) throws { let observerPtr = UnsafeMutablePointer.allocate(capacity: 1) - defer { observerPtr.deallocate(capacity: 1) } + defer { observerPtr.deallocate() } try AXObserverCreateWithInfoCallback(processIdentifier, { (observer, element, notification, /*nullable*/changesCf, userInfoPtr) in if let userInfoPtr = userInfoPtr { let userInfo = Unmanaged<_ObservingUserInfo>.fromOpaque(userInfoPtr).takeUnretainedValue() @@ -64,7 +64,7 @@ public final class ZMAXObserver removeRunLoopHandler() } } - func invalidate() + public func invalidate() { let handleInfos_ = handleInfos for handleInfo in handleInfos_ { @@ -77,7 +77,7 @@ public final class ZMAXObserver } func removeNotification(userInfo: _ObservingUserInfo) throws { - if let index = handleInfos.index(of: userInfo) { + if let index = handleInfos.firstIndex(of: userInfo) { try AXObserverRemoveNotification(axObserver, userInfo.element, (userInfo.notification.rawValue as CFString)).throwIfNotSuccess() Unmanaged.passUnretained(userInfo).release() handleInfos.remove(at: index) diff --git a/ZMAX/Sources/Utilities.swift b/ZMAX/Sources/Utilities.swift new file mode 100644 index 0000000..740b43f --- /dev/null +++ b/ZMAX/Sources/Utilities.swift @@ -0,0 +1,16 @@ +// +// Utilities.swift +// ZMAX +// +// Created by Felix on 23.11.22. +// Copyright © 2022 zumuya. All rights reserved. +// + +import Cocoa + +@discardableResult +public func checkIsProcessTrusted(prompt: Bool = false) -> Bool { + let promptKey = kAXTrustedCheckOptionPrompt.takeUnretainedValue() as String + let opts = [promptKey: prompt] as CFDictionary + return AXIsProcessTrustedWithOptions(opts) +}