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)
+}