swiftformat

This commit is contained in:
Peter Steinberger 2025-05-29 01:17:08 +02:00
parent 3bdb9f7b53
commit 4826d2bfa3
8 changed files with 92 additions and 92 deletions

View File

@ -117,27 +117,27 @@ func exampleObserverCenter() {
@MainActor
func exampleModernActions() {
guard let app = frontmostApplicationElement() else { return }
do {
// Old way (still supported):
// try app.performAction("AXPress")
// try app.performAction(AXActionNames.kAXRaiseAction)
// New way with cleaner enum syntax:
try app.performAction(.press)
print("✅ Pressed element successfully")
try app.performAction(.raise)
print("✅ Raised element successfully")
// All available actions:
let availableActions: [AXAction] = [
.press, .increment, .decrement, .confirm, .cancel,
.showMenu, .pick, .raise, .setValue
]
print("Available actions: \(availableActions.map(\.rawValue).joined(separator: ", "))")
} catch {
print("❌ Action failed: \(error)")
}

View File

@ -49,34 +49,34 @@ import Foundation
public enum AXCommand: Sendable {
/// Searches for UI elements matching specified criteria.
case query(QueryCommand)
/// Performs an accessibility action on a target element.
case performAction(PerformActionCommand)
/// Retrieves specific accessibility attributes from an element.
case getAttributes(GetAttributesCommand)
/// Provides detailed information about an element's structure and properties.
case describeElement(DescribeElementCommand)
/// Extracts text content from an element and its descendants.
case extractText(ExtractTextCommand)
/// Executes multiple commands in a single batch operation.
case batch(AXBatchCommand)
/// Sets the value of the currently focused element.
case setFocusedValue(SetFocusedValueCommand)
/// Finds the UI element at specific screen coordinates.
case getElementAtPoint(GetElementAtPointCommand)
/// Retrieves the currently focused accessibility element.
case getFocusedElement(GetFocusedElementCommand)
/// Observes accessibility notifications from specified elements.
case observe(ObserveCommand)
/// Collects all elements from an application with optional filtering.
case collectAll(CollectAllCommand)
@ -132,7 +132,7 @@ public struct AXCommandEnvelope: Sendable {
/// Used for tracking, logging, and correlating commands with their responses.
/// Should be unique across command executions for proper traceability.
public let commandID: String
/// The accessibility command to execute.
///
/// Contains the specific operation and its parameters that will be
@ -386,7 +386,7 @@ public struct AXBatchCommand: Sendable {
public struct SubCommandEnvelope: Sendable {
/// Unique identifier for this sub-command within the batch.
public let commandID: String
/// The accessibility command to execute.
public let command: AXCommand

View File

@ -36,7 +36,7 @@ public enum AXAction {
case pick
case raise
case setValue
/// The raw string value for the action
public var rawValue: String {
switch self {

View File

@ -53,71 +53,71 @@ public struct CommandEnvelope: Codable {
/// This ID is used for tracking and correlating commands with their responses,
/// especially useful in batch operations or asynchronous processing.
public let commandId: String
/// The type of command to execute.
///
/// Determines what operation will be performed (query, action, observation, etc.).
public let command: CommandType
/// Target application name or bundle identifier.
///
/// Specifies which application the command should operate on. Can be an
/// application name like "Safari" or a bundle identifier like "com.apple.Safari".
public let application: String?
/// Specific attributes to retrieve or filter by.
///
/// When provided, limits the operation to only the specified accessibility attributes.
public let attributes: [String]?
/// Key-value payload for compatibility with ping operations.
public let payload: [String: String]?
/// Whether to enable debug logging for this command.
public let debugLogging: Bool
/// Element locator specifying how to find the target element.
///
/// Provides search criteria for identifying specific UI elements within the application.
public let locator: Locator?
/// Legacy path hint for element location (deprecated).
///
/// > Important: Use ``locator`` instead of this property for new code.
public let pathHint: [String]?
/// Maximum number of elements to return in search results.
public let maxElements: Int?
/// Maximum depth for hierarchical searches.
///
/// Controls how deep into the UI element tree the search should traverse.
public let maxDepth: Int?
/// Output format for the command response.
public let outputFormat: OutputFormat?
/// Name of the action to perform for action commands.
///
/// Examples: "AXPress", "AXShowMenu", "AXScrollToVisible"
public let actionName: String?
/// Value parameter for action commands.
///
/// Some actions require additional parameters (e.g., scroll amount, text to enter).
public let actionValue: AnyCodable?
/// Sub-commands for batch operations.
///
/// When present, this command represents a batch operation containing
/// multiple individual commands to execute in sequence.
public let subCommands: [CommandEnvelope]?
/// Screen coordinates for point-based operations.
///
/// Used with commands that need to interact with elements at specific screen locations.
public let point: CGPoint?
/// Process ID for targeting specific application instances.
///
/// When multiple instances of an application are running, this specifies
@ -125,17 +125,17 @@ public struct CommandEnvelope: Codable {
public let pid: Int?
// MARK: - Observation Parameters
/// Notification types to observe for observation commands.
///
/// List of accessibility notification names to monitor (e.g., "AXValueChanged").
public let notifications: [String]?
/// Element details to include in observation notifications.
///
/// Specifies which element attributes should be included when notifications are triggered.
public let includeElementDetails: [String]?
/// Whether to monitor child elements for notifications.
///
/// When true, notifications from child elements will also be captured.
@ -230,21 +230,21 @@ public struct CommandEnvelope: Codable {
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
commandId = try container.decode(String.self, forKey: .commandId)
command = try container.decode(CommandType.self, forKey: .command)
command = try container.decode(CommandType.self, forKey: .command)
application = try container.decodeIfPresent(String.self, forKey: .application)
attributes = try container.decodeIfPresent([String].self, forKey: .attributes)
payload = try container.decodeIfPresent([String:String].self, forKey: .payload)
attributes = try container.decodeIfPresent([String].self, forKey: .attributes)
payload = try container.decodeIfPresent([String:String].self, forKey: .payload)
debugLogging = try container.decodeIfPresent(Bool.self, forKey: .debugLogging) ?? false
locator = try container.decodeIfPresent(Locator.self, forKey: .locator)
pathHint = try container.decodeIfPresent([String].self, forKey: .pathHint)
locator = try container.decodeIfPresent(Locator.self, forKey: .locator)
pathHint = try container.decodeIfPresent([String].self, forKey: .pathHint)
maxElements = try container.decodeIfPresent(Int.self, forKey: .maxElements)
maxDepth = try container.decodeIfPresent(Int.self, forKey: .maxDepth)
maxDepth = try container.decodeIfPresent(Int.self, forKey: .maxDepth)
outputFormat = try container.decodeIfPresent(OutputFormat.self, forKey: .outputFormat)
actionName = try container.decodeIfPresent(String.self, forKey: .actionName)
actionName = try container.decodeIfPresent(String.self, forKey: .actionName)
actionValue = try container.decodeIfPresent(AnyCodable.self, forKey: .actionValue)
subCommands = try container.decodeIfPresent([CommandEnvelope].self, forKey: .subCommands)
point = try container.decodeIfPresent(CGPoint.self, forKey: .point)
pid = try container.decodeIfPresent(Int.self, forKey: .pid)
point = try container.decodeIfPresent(CGPoint.self, forKey: .point)
pid = try container.decodeIfPresent(Int.self, forKey: .pid)
notifications = try container.decodeIfPresent([String].self, forKey: .notifications)
includeElementDetails = try container.decodeIfPresent([String].self, forKey: .includeElementDetails)
watchChildren = try container.decodeIfPresent(Bool.self, forKey: .watchChildren)

View File

@ -10,7 +10,7 @@ public extension Element {
func isWindowMinimized() -> Bool? {
return isMinimized()
}
/// Checks if the window is hidden (different from minimized - this is when the app is hidden with Cmd+H)
/// - Returns: true if hidden, false if not, nil if cannot be determined
func isWindowHidden() -> Bool? {
@ -22,17 +22,17 @@ public extension Element {
}
current = element.parent()
}
// Alternative: If we have the PID, we can create the application element directly
if let windowPid = self.pid() {
if let app = Element.application(for: windowPid) {
return app.attribute(Attribute<Bool>("AXHidden"))
}
}
return nil
}
/// Minimizes the window
/// - Returns: AXError indicating success or failure
func minimizeWindow() -> AXError {
@ -41,7 +41,7 @@ public extension Element {
if error == .success {
return .success
}
// Fallback: Try to press the minimize button
if let minimizeBtn = minimizeButton() {
do {
@ -51,18 +51,18 @@ public extension Element {
return .actionUnsupported
}
}
return error
}
/// Unminimizes the window
/// - Returns: AXError indicating success or failure
func unminimizeWindow() -> AXError {
return setMinimized(false)
}
/// Closes the window
/// - Returns: AXError indicating success or failure
/// - Returns: AXError indicating success or failure
func closeWindow() -> AXError {
// Try to press the close button
if let closeBtn = closeButton() {
@ -73,7 +73,7 @@ public extension Element {
return .actionUnsupported
}
}
// Fallback: Try the close action if available
if let supportedActions = supportedActions(), supportedActions.contains("AXClose") {
do {
@ -83,10 +83,10 @@ public extension Element {
return .actionUnsupported
}
}
return .actionUnsupported
}
/// Brings the window to front
/// - Returns: AXError indicating success or failure
func raiseWindow() -> AXError {
@ -110,49 +110,49 @@ public extension Element {
if isMinimized() == true {
return nil
}
// Get window frame
guard let windowFrame = frame() else {
return nil
}
// Handle case where window might be hidden
if isWindowHidden() == true {
// Hidden windows maintain their position, so we can still determine the screen
return screenContainingRect(windowFrame)
}
return screenContainingRect(windowFrame)
}
/// Gets the screen number (1-based) that contains this window
/// - Returns: The screen number, or nil if the window is minimized or cannot be determined
func windowScreenNumber() -> Int? {
guard let screen = windowScreen() else {
return nil
}
let screens = NSScreen.screens
for (index, s) in screens.enumerated() {
if s == screen {
return index + 1 // 1-based numbering
return index + 1 // 1-based numbering
}
}
return nil
}
/// Determines which screen contains the largest portion of the given rect
/// - Parameter rect: The rect to check
/// - Returns: The screen containing the largest portion of the rect, or nil if no intersection
private func screenContainingRect(_ rect: CGRect) -> NSScreen? {
var bestScreen: NSScreen?
var bestArea: CGFloat = 0
for screen in NSScreen.screens {
let screenFrame = screen.frame
let intersection = screenFrame.intersection(rect)
if !intersection.isNull {
let area = intersection.width * intersection.height
if area > bestArea {
@ -161,7 +161,7 @@ public extension Element {
}
}
}
// If no intersection found, check by window center point
if bestScreen == nil {
let centerPoint = CGPoint(x: rect.midX, y: rect.midY)
@ -171,10 +171,10 @@ public extension Element {
}
}
}
return bestScreen
}
/// Gets detailed screen information for the window
/// - Returns: A dictionary containing screen information, or nil if cannot be determined
func windowScreenInfo() -> [String: Any]? {
@ -182,7 +182,7 @@ public extension Element {
// Check if minimized or hidden
let isMin = isMinimized() ?? false
let isHid = isWindowHidden() ?? false
return [
"screenNumber": NSNull(),
"isMinimized": isMin,
@ -190,11 +190,11 @@ public extension Element {
"hasScreen": false
]
}
let screenNumber = windowScreenNumber() ?? 0
let screenFrame = screen.frame
let visibleFrame = screen.visibleFrame
return [
"screenNumber": screenNumber,
"screenFrame": NSStringFromRect(screenFrame),
@ -219,20 +219,20 @@ public extension Element {
if let minimized = isMinimized(), minimized {
return false
}
// Check if hidden
if let hidden = isWindowHidden(), hidden {
return false
}
// Check if it has a valid frame on a screen
if let _ = windowScreen() {
return true
}
return nil
}
/// Shows a hidden window (unhides the application if needed)
/// - Returns: AXError indicating success or failure
func showWindow() -> AXError {
@ -243,11 +243,11 @@ public extension Element {
return error
}
}
// Then unhide the app if needed
// Get the application element by walking up the parent hierarchy or using PID
var appElement: Element? = nil
// Try parent hierarchy first
var current: Element? = self
while let element = current {
@ -257,20 +257,20 @@ public extension Element {
}
current = element.parent()
}
// If not found, try using PID
if appElement == nil, let windowPid = self.pid() {
appElement = Element.application(for: windowPid)
}
if let app = appElement, app.attribute(Attribute<Bool>("AXHidden")) == true {
let error = AXUIElementSetAttributeValue(app.underlyingElement, "AXHidden" as CFString, false as CFBoolean)
if error != AXError.success {
return error
}
}
// Finally raise the window
return raiseWindow()
}
}
}

View File

@ -52,13 +52,13 @@ public struct Element: Equatable, Hashable {
/// When populated (typically by deep queries), this contains all the
/// accessibility attributes for the element, avoiding repeated API calls.
public var attributes: [String: AnyCodable]?
/// Pre-fetched child elements.
///
/// When populated by deep queries, this contains all direct child elements,
/// allowing for efficient tree traversal without additional API calls.
public var prefetchedChildren: [Element]?
/// Pre-fetched available actions for this element.
///
/// When populated, this contains all actions that can be performed on

View File

@ -14,7 +14,7 @@ public typealias AXNotificationSubscriptionHandler = @MainActor (/*element: Elem
public struct AXNotificationSubscriptionKey: Hashable {
/// Process ID to monitor, or nil for global monitoring.
let pid: pid_t?
/// The accessibility notification type to observe.
let notification: AXNotification
}
@ -26,7 +26,7 @@ public struct AXNotificationSubscriptionKey: Hashable {
public struct AXObserverKeyAndPID: Hashable {
/// Process ID being observed.
let pid: pid_t
/// Notification type being monitored.
let key: AXNotification
}
@ -38,7 +38,7 @@ public struct AXObserverKeyAndPID: Hashable {
public struct AXObserverObjAndPID {
/// The active accessibility observer.
var observer: AXObserver
/// Process ID that this observer is monitoring.
var pid: pid_t
}

View File

@ -89,7 +89,7 @@ extension HandlerResponse {
/// message: "Element not found",
/// underlyingError: "AXUIElementCopyAttributeValue failed"
/// )
///
///
/// let response = HandlerResponse(
/// data: AnyCodable(detailedError),
/// error: "Element lookup failed"
@ -101,12 +101,12 @@ public struct DetailedError: Codable, Sendable {
/// Use consistent error codes across your application to enable
/// programmatic error handling.
public let code: Int
/// Human-readable error message describing what went wrong.
///
/// This should be clear and actionable for developers debugging issues.
public let message: String
/// Additional details about underlying system errors.
///
/// When the error is caused by a lower-level system call or API,