diff --git a/AXSwiftExample/AppDelegate.swift b/AXSwiftExample/AppDelegate.swift index acd2af6..0ab861a 100644 --- a/AXSwiftExample/AppDelegate.swift +++ b/AXSwiftExample/AppDelegate.swift @@ -33,7 +33,7 @@ class ApplicationDelegate: NSObject, NSApplicationDelegate { if let title: String = try! app.attribute(.title) { NSLog("title: \(title)") } - NSLog("multi: \(try! app.getMultipleAttributes(["AXRole", "asdf", "AXTitle"]))") + NSLog("multi: \(try! app.getMultipleAttributes([.role, .init(rawValue: "asdf"), .title]))") NSLog("multi: \(try! app.getMultipleAttributes(.role, .title))") // Try to set an unsettable attribute diff --git a/AXSwiftObserverExample/AppDelegate.swift b/AXSwiftObserverExample/AppDelegate.swift index 1f56849..8fae6c5 100644 --- a/AXSwiftObserverExample/AppDelegate.swift +++ b/AXSwiftObserverExample/AppDelegate.swift @@ -7,6 +7,11 @@ class AppDelegate: NSObject, NSApplicationDelegate { func applicationDidFinishLaunching(_ aNotification: Notification) { let app = Application.allForBundleID("com.apple.finder").first! + guard UIElement.isProcessTrusted(withPrompt: true) else { + NSLog("No accessibility API permission, exiting") + NSRunningApplication.current.terminate() + return + } do { try startWatcher(app) @@ -16,9 +21,24 @@ class AppDelegate: NSObject, NSApplicationDelegate { } } + private class Sendly: @unchecked Sendable { + private let lock = NSLock() + private var _value: T + var value: T { + get { lock.withLock { _value } } + set { lock.withLock { _value = newValue } } + } + + init(_ value: T) { + lock.lock() + defer { lock.unlock() } + self._value = value + } + } + func startWatcher(_ app: Application) throws { - var updated = false - observer = app.createObserver { (observer: Observer, element: UIElement, event: AXNotification, info: [String: AnyObject]?) in + let updated = Sendly(false) + observer = app.createObserver { (observer: Observer, element: UIElement, event: UIElement.AXNotification, info: [String: AnyObject]?) in var elementDesc: String! if let role = try? element.role()!, role == .window { elementDesc = "\(element) \"\(try! (element.attribute(.title) as String?)!)\"" @@ -38,12 +58,12 @@ class AppDelegate: NSObject, NSApplicationDelegate { } // Group simultaneous events together with --- lines - if !updated { - updated = true + if !updated.value { + updated.value = true // Set this code to run after the current run loop, which is dispatching all notifications. DispatchQueue.main.async { print("---") - updated = false + updated.value = false } } } @@ -55,5 +75,4 @@ class AppDelegate: NSObject, NSApplicationDelegate { func applicationWillTerminate(_ aNotification: Notification) { // Insert code here to tear down your application } - } diff --git a/Package.swift b/Package.swift index 112f472..cc5572f 100644 --- a/Package.swift +++ b/Package.swift @@ -1,9 +1,12 @@ -// swift-tools-version:4.0 +// swift-tools-version:6.1 import PackageDescription let package = Package( name: "AXSwift", + platforms: [ + .macOS(.v10_15) + ], products: [ .library( name: "AXSwift", @@ -13,10 +16,12 @@ let package = Package( .target( name: "AXSwift", path: "Sources"), - .target(name: "AXSwiftExample", + .executableTarget( + name: "AXSwiftExample", dependencies: ["AXSwift"], path: "AXSwiftExample"), - .target(name: "AXSwiftObserverExample", + .executableTarget( + name: "AXSwiftObserverExample", dependencies: ["AXSwift"], path: "AXSwiftObserverExample"), ] diff --git a/Sources/AXSwift.swift b/Sources/AXSwift.swift index 22085c5..2996895 100644 --- a/Sources/AXSwift.swift +++ b/Sources/AXSwift.swift @@ -2,7 +2,5 @@ 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) + UIElement.isProcessTrusted(withPrompt: prompt) } diff --git a/Sources/Application.swift b/Sources/Application.swift index 65a68e0..99f55d1 100644 --- a/Sources/Application.swift +++ b/Sources/Application.swift @@ -75,13 +75,13 @@ public final class Application: UIElement { /// Returns a list of the application's visible windows. /// - returns: An array of `UIElement`s, one for every visible window. Or `nil` if the list /// cannot be retrieved. - public func windows() throws -> [UIElement]? { - let axWindows: [AXUIElement]? = try attribute("AXWindows") + public func windows() throws(AXError) -> [UIElement]? { + let axWindows: [AXUIElement]? = try attribute(.windows) return axWindows?.map({ UIElement($0) }) } /// Returns the element at the specified top-down coordinates, or nil if there is none. - public override func elementAtPosition(_ x: Float, _ y: Float) throws -> UIElement? { + public override func elementAtPosition(_ x: Float, _ y: Float) throws(AXError) -> UIElement? { return try super.elementAtPosition(x, y) } } diff --git a/Sources/Constants.swift b/Sources/Constants.swift deleted file mode 100644 index 1fd8d7e..0000000 --- a/Sources/Constants.swift +++ /dev/null @@ -1,408 +0,0 @@ -/// All possible notifications you can subscribe to with `Observer`. -/// - seeAlso: [Notificatons](https://developer.apple.com/library/mac/documentation/AppKit/Reference/NSAccessibility_Protocol_Reference/index.html#//apple_ref/c/data/NSAccessibilityAnnouncementRequestedNotification) -public enum AXNotification: String { - // Focus notifications - case mainWindowChanged = "AXMainWindowChanged" - case focusedWindowChanged = "AXFocusedWindowChanged" - case focusedUIElementChanged = "AXFocusedUIElementChanged" - case focusedTabChanged = "AXFocusedTabChanged" - - // Application notifications - case applicationActivated = "AXApplicationActivated" - case applicationDeactivated = "AXApplicationDeactivated" - case applicationHidden = "AXApplicationHidden" - case applicationShown = "AXApplicationShown" - - // Window notifications - case windowCreated = "AXWindowCreated" - case windowMoved = "AXWindowMoved" - case windowResized = "AXWindowResized" - case windowMiniaturized = "AXWindowMiniaturized" - case windowDeminiaturized = "AXWindowDeminiaturized" - - // Drawer & sheet notifications - case drawerCreated = "AXDrawerCreated" - case sheetCreated = "AXSheetCreated" - - // Element notifications - case uiElementDestroyed = "AXUIElementDestroyed" - case valueChanged = "AXValueChanged" - case titleChanged = "AXTitleChanged" - case resized = "AXResized" - case moved = "AXMoved" - case created = "AXCreated" - - // Used when UI changes require the attention of assistive application. Pass along a user info - // dictionary with the key NSAccessibilityUIElementsKey and an array of elements that have been - // added or changed as a result of this layout change. - case layoutChanged = "AXLayoutChanged" - - // Misc notifications - case helpTagCreated = "AXHelpTagCreated" - case selectedTextChanged = "AXSelectedTextChanged" - case rowCountChanged = "AXRowCountChanged" - case selectedChildrenChanged = "AXSelectedChildrenChanged" - case selectedRowsChanged = "AXSelectedRowsChanged" - case selectedColumnsChanged = "AXSelectedColumnsChanged" - case loadComplete = "AXLoadComplete" - - case rowExpanded = "AXRowExpanded" - case rowCollapsed = "AXRowCollapsed" - - // Cell-table notifications - case selectedCellsChanged = "AXSelectedCellsChanged" - - // Layout area notifications - case unitsChanged = "AXUnitsChanged" - case selectedChildrenMoved = "AXSelectedChildrenMoved" - - // This notification allows an application to request that an announcement be made to the user - // by an assistive application such as VoiceOver. The notification requires a user info - // dictionary with the key NSAccessibilityAnnouncementKey and the announcement as a localized - // string. In addition, the key NSAccessibilityAnnouncementPriorityKey should also be used to - // help an assistive application determine the importance of this announcement. This - // notification should be posted for the application element. - case announcementRequested = "AXAnnouncementRequested" -} - -/// All UIElement roles. -/// - seeAlso: [Roles](https://developer.apple.com/library/mac/documentation/AppKit/Reference/NSAccessibility_Protocol_Reference/index.html#//apple_ref/doc/constant_group/Roles) -public enum Role: String { - case unknown = "AXUnknown" - case button = "AXButton" - case radioButton = "AXRadioButton" - case checkBox = "AXCheckBox" - case slider = "AXSlider" - case tabGroup = "AXTabGroup" - case textField = "AXTextField" - case staticText = "AXStaticText" - case textArea = "AXTextArea" - case scrollArea = "AXScrollArea" - case popUpButton = "AXPopUpButton" - case menuButton = "AXMenuButton" - case table = "AXTable" - case application = "AXApplication" - case group = "AXGroup" - case radioGroup = "AXRadioGroup" - case list = "AXList" - case scrollBar = "AXScrollBar" - case valueIndicator = "AXValueIndicator" - case image = "AXImage" - case menuBar = "AXMenuBar" - case menu = "AXMenu" - case menuItem = "AXMenuItem" - case menuBarItem = "AXMenuBarItem" - case column = "AXColumn" - case row = "AXRow" - case toolbar = "AXToolbar" - case busyIndicator = "AXBusyIndicator" - case progressIndicator = "AXProgressIndicator" - case window = "AXWindow" - case drawer = "AXDrawer" - case systemWide = "AXSystemWide" - case outline = "AXOutline" - case incrementor = "AXIncrementor" - case browser = "AXBrowser" - case comboBox = "AXComboBox" - case splitGroup = "AXSplitGroup" - case splitter = "AXSplitter" - case colorWell = "AXColorWell" - case growArea = "AXGrowArea" - case sheet = "AXSheet" - case helpTag = "AXHelpTag" - case matte = "AXMatte" - case ruler = "AXRuler" - case rulerMarker = "AXRulerMarker" - case link = "AXLink" - case disclosureTriangle = "AXDisclosureTriangle" - case grid = "AXGrid" - case relevanceIndicator = "AXRelevanceIndicator" - case levelIndicator = "AXLevelIndicator" - case cell = "AXCell" - case popover = "AXPopover" - case layoutArea = "AXLayoutArea" - case layoutItem = "AXLayoutItem" - case handle = "AXHandle" -} - -/// All UIElement subroles. -/// - seeAlso: [Subroles](https://developer.apple.com/library/mac/documentation/AppKit/Reference/NSAccessibility_Protocol_Reference/index.html#//apple_ref/doc/constant_group/Subroles) -public enum Subrole: String { - case unknown = "AXUnknown" - case closeButton = "AXCloseButton" - case zoomButton = "AXZoomButton" - case minimizeButton = "AXMinimizeButton" - case toolbarButton = "AXToolbarButton" - case tableRow = "AXTableRow" - case outlineRow = "AXOutlineRow" - case secureTextField = "AXSecureTextField" - case standardWindow = "AXStandardWindow" - case dialog = "AXDialog" - case systemDialog = "AXSystemDialog" - case floatingWindow = "AXFloatingWindow" - case systemFloatingWindow = "AXSystemFloatingWindow" - case incrementArrow = "AXIncrementArrow" - case decrementArrow = "AXDecrementArrow" - case incrementPage = "AXIncrementPage" - case decrementPage = "AXDecrementPage" - case searchField = "AXSearchField" - case textAttachment = "AXTextAttachment" - case textLink = "AXTextLink" - case timeline = "AXTimeline" - case sortButton = "AXSortButton" - case ratingIndicator = "AXRatingIndicator" - case contentList = "AXContentList" - case definitionList = "AXDefinitionList" - case fullScreenButton = "AXFullScreenButton" - case toggle = "AXToggle" - case switchSubrole = "AXSwitch" - case descriptionList = "AXDescriptionList" -} - -/// Orientations returned by the orientation property. -/// - seeAlso: [NSAccessibilityOrientation](https://developer.apple.com/library/mac/documentation/AppKit/Reference/NSAccessibility_Protocol_Reference/index.html#//apple_ref/c/tdef/NSAccessibilityOrientation) -public enum Orientation: Int { - case unknown = 0 - case vertical = 1 - case horizontal = 2 -} - -public enum Attribute: String { - // Standard attributes - case role = "AXRole" //(NSString *) - type, non-localized (e.g. radioButton) - case roleDescription = "AXRoleDescription" //(NSString *) - user readable role (e.g. "radio button") - case subrole = "AXSubrole" //(NSString *) - type, non-localized (e.g. closeButton) - case help = "AXHelp" //(NSString *) - instance description (e.g. a tool tip) - case value = "AXValue" //(id) - element's value - case minValue = "AXMinValue" //(id) - element's min value - case maxValue = "AXMaxValue" //(id) - element's max value - case enabled = "AXEnabled" //(NSNumber *) - (boolValue) responds to user? - case focused = "AXFocused" //(NSNumber *) - (boolValue) has keyboard focus? - case parent = "AXParent" //(id) - element containing you - case children = "AXChildren" //(NSArray *) - elements you contain - case window = "AXWindow" //(id) - UIElement for the containing window - case topLevelUIElement = "AXTopLevelUIElement" //(id) - UIElement for the containing top level element - case selectedChildren = "AXSelectedChildren" //(NSArray *) - child elements which are selected - case visibleChildren = "AXVisibleChildren" //(NSArray *) - child elements which are visible - case position = "AXPosition" //(NSValue *) - (pointValue) position in screen coords - case size = "AXSize" //(NSValue *) - (sizeValue) size - case frame = "AXFrame" //(NSValue *) - (rectValue) frame - case contents = "AXContents" //(NSArray *) - main elements - case title = "AXTitle" //(NSString *) - visible text (e.g. of a push button) - case description = "AXDescription" //(NSString *) - instance description - case shownMenu = "AXShownMenu" //(id) - menu being displayed - case valueDescription = "AXValueDescription" //(NSString *) - text description of value - - case sharedFocusElements = "AXSharedFocusElements" //(NSArray *) - elements that share focus - - // Misc attributes - case previousContents = "AXPreviousContents" //(NSArray *) - main elements - case nextContents = "AXNextContents" //(NSArray *) - main elements - case header = "AXHeader" //(id) - UIElement for header. - case edited = "AXEdited" //(NSNumber *) - (boolValue) is it dirty? - case tabs = "AXTabs" //(NSArray *) - UIElements for tabs - case horizontalScrollBar = "AXHorizontalScrollBar" //(id) - UIElement for the horizontal scroller - case verticalScrollBar = "AXVerticalScrollBar" //(id) - UIElement for the vertical scroller - case overflowButton = "AXOverflowButton" //(id) - UIElement for overflow - case incrementButton = "AXIncrementButton" //(id) - UIElement for increment - case decrementButton = "AXDecrementButton" //(id) - UIElement for decrement - case filename = "AXFilename" //(NSString *) - filename - case expanded = "AXExpanded" //(NSNumber *) - (boolValue) is expanded? - case selected = "AXSelected" //(NSNumber *) - (boolValue) is selected? - case splitters = "AXSplitters" //(NSArray *) - UIElements for splitters - case document = "AXDocument" //(NSString *) - url as string - for open document - case activationPoint = "AXActivationPoint" //(NSValue *) - (pointValue) - - case url = "AXURL" //(NSURL *) - url - case index = "AXIndex" //(NSNumber *) - (intValue) - - case rowCount = "AXRowCount" //(NSNumber *) - (intValue) number of rows - - case columnCount = "AXColumnCount" //(NSNumber *) - (intValue) number of columns - - case orderedByRow = "AXOrderedByRow" //(NSNumber *) - (boolValue) is ordered by row? - - case warningValue = "AXWarningValue" //(id) - warning value of a level indicator, typically a number - - case criticalValue = "AXCriticalValue" //(id) - critical value of a level indicator, typically a number - - case placeholderValue = "AXPlaceholderValue" //(NSString *) - placeholder value of a control such as a text field - - case containsProtectedContent = "AXContainsProtectedContent" // (NSNumber *) - (boolValue) contains protected content? - case alternateUIVisible = "AXAlternateUIVisible" //(NSNumber *) - (boolValue) - - // Linkage attributes - case titleUIElement = "AXTitleUIElement" //(id) - UIElement for the title - case servesAsTitleForUIElements = "AXServesAsTitleForUIElements" //(NSArray *) - UIElements this titles - case linkedUIElements = "AXLinkedUIElements" //(NSArray *) - corresponding UIElements - - // Text-specific attributes - case selectedText = "AXSelectedText" //(NSString *) - selected text - case selectedTextRange = "AXSelectedTextRange" //(NSValue *) - (rangeValue) range of selected text - case numberOfCharacters = "AXNumberOfCharacters" //(NSNumber *) - number of characters - case visibleCharacterRange = "AXVisibleCharacterRange" //(NSValue *) - (rangeValue) range of visible text - case sharedTextUIElements = "AXSharedTextUIElements" //(NSArray *) - text views sharing text - case sharedCharacterRange = "AXSharedCharacterRange" //(NSValue *) - (rangeValue) part of shared text in this view - case insertionPointLineNumber = "AXInsertionPointLineNumber" //(NSNumber *) - line# containing caret - case selectedTextRanges = "AXSelectedTextRanges" //(NSArray *) - array of NSValue (rangeValue) ranges of selected text - /// - note: private/undocumented attribute - case textInputMarkedRange = "AXTextInputMarkedRange" - - // Parameterized text-specific attributes - case lineForIndexParameterized = "AXLineForIndexParameterized" //(NSNumber *) - line# for char index; param:(NSNumber *) - case rangeForLineParameterized = "AXRangeForLineParameterized" //(NSValue *) - (rangeValue) range of line; param:(NSNumber *) - case stringForRangeParameterized = "AXStringForRangeParameterized" //(NSString *) - substring; param:(NSValue * - rangeValue) - case rangeForPositionParameterized = "AXRangeForPositionParameterized" //(NSValue *) - (rangeValue) composed char range; param:(NSValue * - pointValue) - case rangeForIndexParameterized = "AXRangeForIndexParameterized" //(NSValue *) - (rangeValue) composed char range; param:(NSNumber *) - case boundsForRangeParameterized = "AXBoundsForRangeParameterized" //(NSValue *) - (rectValue) bounds of text; param:(NSValue * - rangeValue) - case rtfForRangeParameterized = "AXRTFForRangeParameterized" //(NSData *) - rtf for text; param:(NSValue * - rangeValue) - case styleRangeForIndexParameterized = "AXStyleRangeForIndexParameterized" //(NSValue *) - (rangeValue) extent of style run; param:(NSNumber *) - case attributedStringForRangeParameterized = "AXAttributedStringForRangeParameterized" //(NSAttributedString *) - does _not_ use attributes from Appkit/AttributedString.h - - // Text attributed string attributes and constants - case fontText = "AXFontText" //(NSDictionary *) - NSAccessibilityFontXXXKey's - case foregroundColorText = "AXForegroundColorText" //CGColorRef - case backgroundColorText = "AXBackgroundColorText" //CGColorRef - case underlineColorText = "AXUnderlineColorText" //CGColorRef - case strikethroughColorText = "AXStrikethroughColorText" //CGColorRef - case underlineText = "AXUnderlineText" //(NSNumber *) - underline style - case superscriptText = "AXSuperscriptText" //(NSNumber *) - superscript>0, subscript<0 - case strikethroughText = "AXStrikethroughText" //(NSNumber *) - (boolValue) - case shadowText = "AXShadowText" //(NSNumber *) - (boolValue) - case attachmentText = "AXAttachmentText" //id - corresponding element - case linkText = "AXLinkText" //id - corresponding element - case autocorrectedText = "AXAutocorrectedText" //(NSNumber *) - (boolValue) - - // Textual list attributes and constants. Examples: unordered or ordered lists in a document. - case listItemPrefixText = "AXListItemPrefixText" // NSAttributedString, the prepended string of the list item. If the string is a common unicode character (e.g. a bullet •), return that unicode character. For lists with images before the text, return a reasonable label of the image. - case listItemIndexText = "AXListItemIndexText" // NSNumber, integerValue of the line index. Each list item increments the index, even for unordered lists. The first item should have index 0. - case listItemLevelText = "AXListItemLevelText" // NSNumber, integerValue of the indent level. Each sublist increments the level. The first item should have level 0. - - // MisspelledText attributes - case misspelledText = "AXMisspelledText" //(NSNumber *) - (boolValue) - case markedMisspelledText = "AXMarkedMisspelledText" //(NSNumber *) - (boolValue) - - // Window-specific attributes - case main = "AXMain" //(NSNumber *) - (boolValue) is it the main window? - case minimized = "AXMinimized" //(NSNumber *) - (boolValue) is window minimized? - case closeButton = "AXCloseButton" //(id) - UIElement for close box (or nil) - case zoomButton = "AXZoomButton" //(id) - UIElement for zoom box (or nil) - case minimizeButton = "AXMinimizeButton" //(id) - UIElement for miniaturize box (or nil) - case toolbarButton = "AXToolbarButton" //(id) - UIElement for toolbar box (or nil) - case proxy = "AXProxy" //(id) - UIElement for title's icon (or nil) - case growArea = "AXGrowArea" //(id) - UIElement for grow box (or nil) - case modal = "AXModal" //(NSNumber *) - (boolValue) is the window modal - case defaultButton = "AXDefaultButton" //(id) - UIElement for default button - case cancelButton = "AXCancelButton" //(id) - UIElement for cancel button - case fullScreenButton = "AXFullScreenButton" //(id) - UIElement for full screen button (or nil) - /// - note: private/undocumented attribute - case fullScreen = "AXFullScreen" //(NSNumber *) - (boolValue) is the window fullscreen - - // Application-specific attributes - case menuBar = "AXMenuBar" //(id) - UIElement for the menu bar - case windows = "AXWindows" //(NSArray *) - UIElements for the windows - case frontmost = "AXFrontmost" //(NSNumber *) - (boolValue) is the app active? - case hidden = "AXHidden" //(NSNumber *) - (boolValue) is the app hidden? - case mainWindow = "AXMainWindow" //(id) - UIElement for the main window. - case focusedWindow = "AXFocusedWindow" //(id) - UIElement for the key window. - case focusedUIElement = "AXFocusedUIElement" //(id) - Currently focused UIElement. - case extrasMenuBar = "AXExtrasMenuBar" //(id) - UIElement for the application extras menu bar. - /// - note: private/undocumented attribute - case enhancedUserInterface = "AXEnhancedUserInterface" //(NSNumber *) - (boolValue) is the enhanced user interface active? - - case orientation = "AXOrientation" //(NSString *) - NSAccessibilityXXXOrientationValue - - case columnTitles = "AXColumnTitles" //(NSArray *) - UIElements for titles - - case searchButton = "AXSearchButton" //(id) - UIElement for search field search btn - case searchMenu = "AXSearchMenu" //(id) - UIElement for search field menu - case clearButton = "AXClearButton" //(id) - UIElement for search field clear btn - - // Table/outline view attributes - case rows = "AXRows" //(NSArray *) - UIElements for rows - case visibleRows = "AXVisibleRows" //(NSArray *) - UIElements for visible rows - case selectedRows = "AXSelectedRows" //(NSArray *) - UIElements for selected rows - case columns = "AXColumns" //(NSArray *) - UIElements for columns - case visibleColumns = "AXVisibleColumns" //(NSArray *) - UIElements for visible columns - case selectedColumns = "AXSelectedColumns" //(NSArray *) - UIElements for selected columns - case sortDirection = "AXSortDirection" //(NSString *) - see sort direction values below - - // Cell-based table attributes - case selectedCells = "AXSelectedCells" //(NSArray *) - UIElements for selected cells - case visibleCells = "AXVisibleCells" //(NSArray *) - UIElements for visible cells - case rowHeaderUIElements = "AXRowHeaderUIElements" //(NSArray *) - UIElements for row headers - case columnHeaderUIElements = "AXColumnHeaderUIElements" //(NSArray *) - UIElements for column headers - - // Cell-based table parameterized attributes. The parameter for this attribute is an NSArray containing two NSNumbers, the first NSNumber specifies the column index, the second NSNumber specifies the row index. - case cellForColumnAndRowParameterized = "AXCellForColumnAndRowParameterized" // (id) - UIElement for cell at specified row and column - - // Cell attributes. The index range contains both the starting index, and the index span in a table. - case rowIndexRange = "AXRowIndexRange" //(NSValue *) - (rangeValue) location and row span - case columnIndexRange = "AXColumnIndexRange" //(NSValue *) - (rangeValue) location and column span - - // Layout area attributes - case horizontalUnits = "AXHorizontalUnits" //(NSString *) - see ruler unit values below - case verticalUnits = "AXVerticalUnits" //(NSString *) - see ruler unit values below - case horizontalUnitDescription = "AXHorizontalUnitDescription" //(NSString *) - case verticalUnitDescription = "AXVerticalUnitDescription" //(NSString *) - - // Layout area parameterized attributes - case layoutPointForScreenPointParameterized = "AXLayoutPointForScreenPointParameterized" //(NSValue *) - (pointValue); param:(NSValue * - pointValue) - case layoutSizeForScreenSizeParameterized = "AXLayoutSizeForScreenSizeParameterized" //(NSValue *) - (sizeValue); param:(NSValue * - sizeValue) - case screenPointForLayoutPointParameterized = "AXScreenPointForLayoutPointParameterized" //(NSValue *) - (pointValue); param:(NSValue * - pointValue) - case screenSizeForLayoutSizeParameterized = "AXScreenSizeForLayoutSizeParameterized" //(NSValue *) - (sizeValue); param:(NSValue * - sizeValue) - - // Layout item attributes - case handles = "AXHandles" //(NSArray *) - UIElements for handles - - // Outline attributes - case disclosing = "AXDisclosing" //(NSNumber *) - (boolValue) is disclosing rows? - case disclosedRows = "AXDisclosedRows" //(NSArray *) - UIElements for disclosed rows - case disclosedByRow = "AXDisclosedByRow" //(id) - UIElement for disclosing row - case disclosureLevel = "AXDisclosureLevel" //(NSNumber *) - indentation level - - // Slider attributes - case allowedValues = "AXAllowedValues" //(NSArray *) - array of allowed values - case labelUIElements = "AXLabelUIElements" //(NSArray *) - array of label UIElements - case labelValue = "AXLabelValue" //(NSNumber *) - value of a label UIElement - - // Matte attributes - // Attributes no longer supported - case matteHole = "AXMatteHole" //(NSValue *) - (rect value) bounds of matte hole in screen coords - case matteContentUIElement = "AXMatteContentUIElement" //(id) - UIElement clipped by the matte - - // Ruler view attributes - case markerUIElements = "AXMarkerUIElements" //(NSArray *) - case markerValues = "AXMarkerValues" // - case markerGroupUIElement = "AXMarkerGroupUIElement" //(id) - case units = "AXUnits" //(NSString *) - see ruler unit values below - case unitDescription = "AXUnitDescription" //(NSString *) - case markerType = "AXMarkerType" //(NSString *) - see ruler marker type values below - case markerTypeDescription = "AXMarkerTypeDescription" //(NSString *) - - // UI element identification attributes - case identifier = "AXIdentifier" //(NSString *) - - // System-wide attributes - case focusedApplication = "AXFocusedApplication" - - // Unknown attributes - case functionRowTopLevelElements = "AXFunctionRowTopLevelElements" - case childrenInNavigationOrder = "AXChildrenInNavigationOrder" -} - -/// All actions a `UIElement` can support. -/// - seeAlso: [Actions](https://developer.apple.com/library/mac/documentation/Cocoa/Reference/ApplicationKit/Protocols/NSAccessibility_Protocol/#//apple_ref/doc/constant_group/Actions) -public enum Action: String { - case press = "AXPress" - case increment = "AXIncrement" - case decrement = "AXDecrement" - case confirm = "AXConfirm" - case pick = "AXPick" - case cancel = "AXCancel" - case raise = "AXRaise" - case showMenu = "AXShowMenu" - case delete = "AXDelete" - case showAlternateUI = "AXShowAlternateUI" - case showDefaultUI = "AXShowDefaultUI" -} diff --git a/Sources/Constants/AXNotification.swift b/Sources/Constants/AXNotification.swift new file mode 100644 index 0000000..e5174e9 --- /dev/null +++ b/Sources/Constants/AXNotification.swift @@ -0,0 +1,79 @@ +import Foundation + +@available(*, deprecated, renamed: "UIElement.AXNotification", message: "Provided for drop in replacement, but now the type is located under the `UIElement` namespace.") +public typealias AXNotification = UIElement.AXNotification +extension UIElement { + /// Provides all known possible notifications you can subscribe to with `Observer`. + /// - seeAlso: [Notificatons](https://developer.apple.com/library/mac/documentation/AppKit/Reference/NSAccessibility_Protocol_Reference/index.html#//apple_ref/c/data/NSAccessibilityAnnouncementRequestedNotification) + public struct AXNotification: RawRepresentable, Hashable, Sendable { + public let rawValue: String + public var rawCFStringValue: CFString { rawValue as CFString } + + public init(rawValue: String) { + self.rawValue = rawValue + } + + // Focus notifications + public static let mainWindowChanged = AXNotification(rawValue: "AXMainWindowChanged") + public static let focusedWindowChanged = AXNotification(rawValue: "AXFocusedWindowChanged") + public static let focusedUIElementChanged = AXNotification(rawValue: "AXFocusedUIElementChanged") + public static let focusedTabChanged = AXNotification(rawValue: "AXFocusedTabChanged") + + // Application notifications + public static let applicationActivated = AXNotification(rawValue: "AXApplicationActivated") + public static let applicationDeactivated = AXNotification(rawValue: "AXApplicationDeactivated") + public static let applicationHidden = AXNotification(rawValue: "AXApplicationHidden") + public static let applicationShown = AXNotification(rawValue: "AXApplicationShown") + + // Window notifications + public static let windowCreated = AXNotification(rawValue: "AXWindowCreated") + public static let windowMoved = AXNotification(rawValue: "AXWindowMoved") + public static let windowResized = AXNotification(rawValue: "AXWindowResized") + public static let windowMiniaturized = AXNotification(rawValue: "AXWindowMiniaturized") + public static let windowDeminiaturized = AXNotification(rawValue: "AXWindowDeminiaturized") + + // Drawer & sheet notifications + public static let drawerCreated = AXNotification(rawValue: "AXDrawerCreated") + public static let sheetCreated = AXNotification(rawValue: "AXSheetCreated") + + // Element notifications + public static let uiElementDestroyed = AXNotification(rawValue: "AXUIElementDestroyed") + public static let valueChanged = AXNotification(rawValue: "AXValueChanged") + public static let titleChanged = AXNotification(rawValue: "AXTitleChanged") + public static let resized = AXNotification(rawValue: "AXResized") + public static let moved = AXNotification(rawValue: "AXMoved") + public static let created = AXNotification(rawValue: "AXCreated") + + // Used when UI changes require the attention of assistive application. Pass along a user info + // dictionary with the key NSAccessibilityUIElementsKey and an array of elements that have been + // added or changed as a result of this layout change. + public static let layoutChanged = AXNotification(rawValue: "AXLayoutChanged") + + // Misc notifications + public static let helpTagCreated = AXNotification(rawValue: "AXHelpTagCreated") + public static let selectedTextChanged = AXNotification(rawValue: "AXSelectedTextChanged") + public static let rowCountChanged = AXNotification(rawValue: "AXRowCountChanged") + public static let selectedChildrenChanged = AXNotification(rawValue: "AXSelectedChildrenChanged") + public static let selectedRowsChanged = AXNotification(rawValue: "AXSelectedRowsChanged") + public static let selectedColumnsChanged = AXNotification(rawValue: "AXSelectedColumnsChanged") + public static let loadComplete = AXNotification(rawValue: "AXLoadComplete") + + public static let rowExpanded = AXNotification(rawValue: "AXRowExpanded") + public static let rowCollapsed = AXNotification(rawValue: "AXRowCollapsed") + + // Cell-table notifications + public static let selectedCellsChanged = AXNotification(rawValue: "AXSelectedCellsChanged") + + // Layout area notifications + public static let unitsChanged = AXNotification(rawValue: "AXUnitsChanged") + public static let selectedChildrenMoved = AXNotification(rawValue: "AXSelectedChildrenMoved") + + // This notification allows an application to request that an announcement be made to the user + // by an assistive application such as VoiceOver. The notification requires a user info + // dictionary with the key NSAccessibilityAnnouncementKey and the announcement as a localized + // string. In addition, the key NSAccessibilityAnnouncementPriorityKey should also be used to + // help an assistive application determine the importance of this announcement. This + // notification should be posted for the application element. + public static let announcementRequested = AXNotification(rawValue: "AXAnnouncementRequested") + } +} diff --git a/Sources/Constants/Action.swift b/Sources/Constants/Action.swift new file mode 100644 index 0000000..815fb1d --- /dev/null +++ b/Sources/Constants/Action.swift @@ -0,0 +1,28 @@ +import Foundation + +@available(*, deprecated, renamed: "UIElement.Action", message: "Provided for drop in replacement, but now the type is located under the `UIElement` namespace.") +public typealias Action = UIElement.Action +extension UIElement { + /// Provides all known actions a `UIElement` can support. + /// - seeAlso: [Actions](https://developer.apple.com/library/mac/documentation/Cocoa/Reference/ApplicationKit/Protocols/NSAccessibility_Protocol/#//apple_ref/doc/constant_group/Actions) + public struct Action: RawRepresentable, Hashable, Sendable { + public let rawValue: String + public var rawCFStringValue: CFString { rawValue as CFString } + + public init(rawValue: String) { + self.rawValue = rawValue + } + + public static let press = Action(rawValue: "AXPress") + public static let increment = Action(rawValue: "AXIncrement") + public static let decrement = Action(rawValue: "AXDecrement") + public static let confirm = Action(rawValue: "AXConfirm") + public static let pick = Action(rawValue: "AXPick") + public static let cancel = Action(rawValue: "AXCancel") + public static let raise = Action(rawValue: "AXRaise") + public static let showMenu = Action(rawValue: "AXShowMenu") + public static let delete = Action(rawValue: "AXDelete") + public static let showAlternateUI = Action(rawValue: "AXShowAlternateUI") + public static let showDefaultUI = Action(rawValue: "AXShowDefaultUI") + } +} diff --git a/Sources/Constants/Attribute.swift b/Sources/Constants/Attribute.swift new file mode 100644 index 0000000..88f0483 --- /dev/null +++ b/Sources/Constants/Attribute.swift @@ -0,0 +1,388 @@ +import Foundation + +@available(*, deprecated, renamed: "UIElement.Attribute", message: "Provided for drop in replacement, but now the type is located under the `UIElement` namespace.") +public typealias Attribute = UIElement.Attribute +extension UIElement { + public struct Attribute: RawRepresentable, Hashable, Sendable { + public let rawValue: String + public var rawCFStringValue: CFString { rawValue as CFString } + + public init(rawValue: String) { + self.rawValue = rawValue + } + + // Standard attributes + ///(NSString *) - type, non-localized (e.g. radioButton) + public static let role = Attribute(rawValue: "AXRole") + ///(NSString *) - user readable role (e.g. "radio button") + public static let roleDescription = Attribute(rawValue: "AXRoleDescription") + ///(NSString *) - type, non-localized (e.g. closeButton) + public static let subrole = Attribute(rawValue: "AXSubrole") + ///(NSString *) - instance description (e.g. a tool tip) + public static let help = Attribute(rawValue: "AXHelp") + ///(id) - element's value + public static let value = Attribute(rawValue: "AXValue") + ///(id) - element's min value + public static let minValue = Attribute(rawValue: "AXMinValue") + ///(id) - element's max value + public static let maxValue = Attribute(rawValue: "AXMaxValue") + ///(NSNumber *) - (boolValue) responds to user? + public static let enabled = Attribute(rawValue: "AXEnabled") + ///(NSNumber *) - (boolValue) has keyboard focus? + public static let focused = Attribute(rawValue: "AXFocused") + ///(id) - element containing you + public static let parent = Attribute(rawValue: "AXParent") + ///(NSArray *) - elements you contain + public static let children = Attribute(rawValue: "AXChildren") + ///(id) - UIElement for the containing window + public static let window = Attribute(rawValue: "AXWindow") + ///(id) - UIElement for the containing top level element + public static let topLevelUIElement = Attribute(rawValue: "AXTopLevelUIElement") + ///(NSArray *) - child elements which are selected + public static let selectedChildren = Attribute(rawValue: "AXSelectedChildren") + ///(NSArray *) - child elements which are visible + public static let visibleChildren = Attribute(rawValue: "AXVisibleChildren") + ///(NSValue *) - (pointValue) position in screen coords + public static let position = Attribute(rawValue: "AXPosition") + ///(NSValue *) - (sizeValue) size + public static let size = Attribute(rawValue: "AXSize") + ///(NSValue *) - (rectValue) frame + public static let frame = Attribute(rawValue: "AXFrame") + ///(NSArray *) - main elements + public static let contents = Attribute(rawValue: "AXContents") + ///(NSString *) - visible text (e.g. of a push button) + public static let title = Attribute(rawValue: "AXTitle") + ///(NSString *) - instance description + public static let description = Attribute(rawValue: "AXDescription") + ///(id) - menu being displayed + public static let shownMenu = Attribute(rawValue: "AXShownMenu") + ///(NSString *) - text description of value + public static let valueDescription = Attribute(rawValue: "AXValueDescription") + + ///(NSArray *) - elements that share focus + public static let sharedFocusElements = Attribute(rawValue: "AXSharedFocusElements") + + // Misc attributes + ///(NSArray *) - main elements + public static let previousContents = Attribute(rawValue: "AXPreviousContents") + ///(NSArray *) - main elements + public static let nextContents = Attribute(rawValue: "AXNextContents") + ///(id) - UIElement for header. + public static let header = Attribute(rawValue: "AXHeader") + ///(NSNumber *) - (boolValue) is it dirty? + public static let edited = Attribute(rawValue: "AXEdited") + ///(NSArray *) - UIElements for tabs + public static let tabs = Attribute(rawValue: "AXTabs") + ///(id) - UIElement for the horizontal scroller + public static let horizontalScrollBar = Attribute(rawValue: "AXHorizontalScrollBar") + ///(id) - UIElement for the vertical scroller + public static let verticalScrollBar = Attribute(rawValue: "AXVerticalScrollBar") + ///(id) - UIElement for overflow + public static let overflowButton = Attribute(rawValue: "AXOverflowButton") + ///(id) - UIElement for increment + public static let incrementButton = Attribute(rawValue: "AXIncrementButton") + ///(id) - UIElement for decrement + public static let decrementButton = Attribute(rawValue: "AXDecrementButton") + ///(NSString *) - filename + public static let filename = Attribute(rawValue: "AXFilename") + ///(NSNumber *) - (boolValue) is expanded? + public static let expanded = Attribute(rawValue: "AXExpanded") + ///(NSNumber *) - (boolValue) is selected? + public static let selected = Attribute(rawValue: "AXSelected") + ///(NSArray *) - UIElements for splitters + public static let splitters = Attribute(rawValue: "AXSplitters") + ///(NSString *) - url as string - for open document + public static let document = Attribute(rawValue: "AXDocument") + ///(NSValue *) - (pointValue) + public static let activationPoint = Attribute(rawValue: "AXActivationPoint") + + ///(NSURL *) - url + public static let url = Attribute(rawValue: "AXURL") + ///(NSNumber *) - (intValue) + public static let index = Attribute(rawValue: "AXIndex") + + ///(NSNumber *) - (intValue) number of rows + public static let rowCount = Attribute(rawValue: "AXRowCount") + + ///(NSNumber *) - (intValue) number of columns + public static let columnCount = Attribute(rawValue: "AXColumnCount") + + ///(NSNumber *) - (boolValue) is ordered by row? + public static let orderedByRow = Attribute(rawValue: "AXOrderedByRow") + + ///(id) - warning value of a level indicator, typically a number + public static let warningValue = Attribute(rawValue: "AXWarningValue") + + ///(id) - critical value of a level indicator, typically a number + public static let criticalValue = Attribute(rawValue: "AXCriticalValue") + + ///(NSString *) - placeholder value of a control such as a text field + public static let placeholderValue = Attribute(rawValue: "AXPlaceholderValue") + + /// (NSNumber *) - (boolValue) contains protected content? + public static let containsProtectedContent = Attribute(rawValue: "AXContainsProtectedContent") + ///(NSNumber *) - (boolValue) + public static let alternateUIVisible = Attribute(rawValue: "AXAlternateUIVisible") + + // Linkage attributes + ///(id) - UIElement for the title + public static let titleUIElement = Attribute(rawValue: "AXTitleUIElement") + ///(NSArray *) - UIElements this titles + public static let servesAsTitleForUIElements = Attribute(rawValue: "AXServesAsTitleForUIElements") + ///(NSArray *) - corresponding UIElements + public static let linkedUIElements = Attribute(rawValue: "AXLinkedUIElements") + + // Text-specific attributes + ///(NSString *) - selected text + public static let selectedText = Attribute(rawValue: "AXSelectedText") + ///(NSValue *) - (rangeValue) range of selected text + public static let selectedTextRange = Attribute(rawValue: "AXSelectedTextRange") + ///(NSNumber *) - number of characters + public static let numberOfCharacters = Attribute(rawValue: "AXNumberOfCharacters") + ///(NSValue *) - (rangeValue) range of visible text + public static let visibleCharacterRange = Attribute(rawValue: "AXVisibleCharacterRange") + ///(NSArray *) - text views sharing text + public static let sharedTextUIElements = Attribute(rawValue: "AXSharedTextUIElements") + ///(NSValue *) - (rangeValue) part of shared text in this view + public static let sharedCharacterRange = Attribute(rawValue: "AXSharedCharacterRange") + ///(NSNumber *) - line# containing caret + public static let insertionPointLineNumber = Attribute(rawValue: "AXInsertionPointLineNumber") + ///(NSArray *) - array of NSValue (rangeValue) ranges of selected text + public static let selectedTextRanges = Attribute(rawValue: "AXSelectedTextRanges") + /// - note: private/undocumented attribute + public static let textInputMarkedRange = Attribute(rawValue: "AXTextInputMarkedRange") + + // Parameterized text-specific attributes + ///(NSNumber *) - line# for char index; param:(NSNumber *) + public static let lineForIndexParameterized = Attribute(rawValue: "AXLineForIndexParameterized") + ///(NSValue *) - (rangeValue) range of line; param:(NSNumber *) + public static let rangeForLineParameterized = Attribute(rawValue: "AXRangeForLineParameterized") + ///(NSString *) - substring; param:(NSValue * - rangeValue) + public static let stringForRangeParameterized = Attribute(rawValue: "AXStringForRangeParameterized") + ///(NSValue *) - (rangeValue) composed char range; param:(NSValue * - pointValue) + public static let rangeForPositionParameterized = Attribute(rawValue: "AXRangeForPositionParameterized") + ///(NSValue *) - (rangeValue) composed char range; param:(NSNumber *) + public static let rangeForIndexParameterized = Attribute(rawValue: "AXRangeForIndexParameterized") + ///(NSValue *) - (rectValue) bounds of text; param:(NSValue * - rangeValue) + public static let boundsForRangeParameterized = Attribute(rawValue: "AXBoundsForRangeParameterized") + ///(NSData *) - rtf for text; param:(NSValue * - rangeValue) + public static let rtfForRangeParameterized = Attribute(rawValue: "AXRTFForRangeParameterized") + ///(NSValue *) - (rangeValue) extent of style run; param:(NSNumber *) + public static let styleRangeForIndexParameterized = Attribute(rawValue: "AXStyleRangeForIndexParameterized") + ///(NSAttributedString *) - does _not_ use attributes from Appkit/AttributedString.h + public static let attributedStringForRangeParameterized = Attribute(rawValue: "AXAttributedStringForRangeParameterized") + + // Text attributed string attributes and constants + ///(NSDictionary *) - NSAccessibilityFontXXXKey's + public static let fontText = Attribute(rawValue: "AXFontText") + ///CGColorRef + public static let foregroundColorText = Attribute(rawValue: "AXForegroundColorText") + ///CGColorRef + public static let backgroundColorText = Attribute(rawValue: "AXBackgroundColorText") + ///CGColorRef + public static let underlineColorText = Attribute(rawValue: "AXUnderlineColorText") + ///CGColorRef + public static let strikethroughColorText = Attribute(rawValue: "AXStrikethroughColorText") + ///(NSNumber *) - underline style + public static let underlineText = Attribute(rawValue: "AXUnderlineText") + ///(NSNumber *) - superscript>0, subscript<0 + public static let superscriptText = Attribute(rawValue: "AXSuperscriptText") + ///(NSNumber *) - (boolValue) + public static let strikethroughText = Attribute(rawValue: "AXStrikethroughText") + ///(NSNumber *) - (boolValue) + public static let shadowText = Attribute(rawValue: "AXShadowText") + ///id - corresponding element + public static let attachmentText = Attribute(rawValue: "AXAttachmentText") + ///id - corresponding element + public static let linkText = Attribute(rawValue: "AXLinkText") + ///(NSNumber *) - (boolValue) + public static let autocorrectedText = Attribute(rawValue: "AXAutocorrectedText") + + // Textual list attributes and constants. Examples: unordered or ordered lists in a document. + /// NSAttributedString, the prepended string of the list item. If the string is a common unicode character (e.g. a bullet •), return that unicode character. For lists with images before the text, return a reasonable label of the image. + public static let listItemPrefixText = Attribute(rawValue: "AXListItemPrefixText") + /// NSNumber, integerValue of the line index. Each list item increments the index, even for unordered lists. The first item should have index 0. + public static let listItemIndexText = Attribute(rawValue: "AXListItemIndexText") + /// NSNumber, integerValue of the indent level. Each sublist increments the level. The first item should have level 0. + public static let listItemLevelText = Attribute(rawValue: "AXListItemLevelText") + + // MisspelledText attributes + ///(NSNumber *) - (boolValue) + public static let misspelledText = Attribute(rawValue: "AXMisspelledText") + ///(NSNumber *) - (boolValue) + public static let markedMisspelledText = Attribute(rawValue: "AXMarkedMisspelledText") + + // Window-specific attributes + ///(NSNumber *) - (boolValue) is it the main window? + public static let main = Attribute(rawValue: "AXMain") + ///(NSNumber *) - (boolValue) is window minimized? + public static let minimized = Attribute(rawValue: "AXMinimized") + ///(id) - UIElement for close box (or nil) + public static let closeButton = Attribute(rawValue: "AXCloseButton") + ///(id) - UIElement for zoom box (or nil) + public static let zoomButton = Attribute(rawValue: "AXZoomButton") + ///(id) - UIElement for miniaturize box (or nil) + public static let minimizeButton = Attribute(rawValue: "AXMinimizeButton") + ///(id) - UIElement for toolbar box (or nil) + public static let toolbarButton = Attribute(rawValue: "AXToolbarButton") + ///(id) - UIElement for title's icon (or nil) + public static let proxy = Attribute(rawValue: "AXProxy") + ///(id) - UIElement for grow box (or nil) + public static let growArea = Attribute(rawValue: "AXGrowArea") + ///(NSNumber *) - (boolValue) is the window modal + public static let modal = Attribute(rawValue: "AXModal") + ///(id) - UIElement for default button + public static let defaultButton = Attribute(rawValue: "AXDefaultButton") + ///(id) - UIElement for cancel button + public static let cancelButton = Attribute(rawValue: "AXCancelButton") + ///(id) - UIElement for full screen button (or nil) + public static let fullScreenButton = Attribute(rawValue: "AXFullScreenButton") + /// - note: private/undocumented attribute + ///(NSNumber *) - (boolValue) is the window fullscreen + public static let fullScreen = Attribute(rawValue: "AXFullScreen") + + // Application-specific attributes + ///(id) - UIElement for the menu bar + public static let menuBar = Attribute(rawValue: "AXMenuBar") + ///(NSArray *) - UIElements for the windows + public static let windows = Attribute(rawValue: "AXWindows") + ///(NSNumber *) - (boolValue) is the app active? + public static let frontmost = Attribute(rawValue: "AXFrontmost") + ///(NSNumber *) - (boolValue) is the app hidden? + public static let hidden = Attribute(rawValue: "AXHidden") + ///(id) - UIElement for the main window. + public static let mainWindow = Attribute(rawValue: "AXMainWindow") + ///(id) - UIElement for the key window. + public static let focusedWindow = Attribute(rawValue: "AXFocusedWindow") + ///(id) - Currently focused UIElement. + public static let focusedUIElement = Attribute(rawValue: "AXFocusedUIElement") + ///(id) - UIElement for the application extras menu bar. + public static let extrasMenuBar = Attribute(rawValue: "AXExtrasMenuBar") + /// - note: private/undocumented attribute + ///(NSNumber *) - (boolValue) is the enhanced user interface active? + public static let enhancedUserInterface = Attribute(rawValue: "AXEnhancedUserInterface") + + ///(NSString *) - NSAccessibilityXXXOrientationValue + public static let orientation = Attribute(rawValue: "AXOrientation") + + ///(NSArray *) - UIElements for titles + public static let columnTitles = Attribute(rawValue: "AXColumnTitles") + + ///(id) - UIElement for search field search btn + public static let searchButton = Attribute(rawValue: "AXSearchButton") + ///(id) - UIElement for search field menu + public static let searchMenu = Attribute(rawValue: "AXSearchMenu") + ///(id) - UIElement for search field clear btn + public static let clearButton = Attribute(rawValue: "AXClearButton") + + // Table/outline view attributes + ///(NSArray *) - UIElements for rows + public static let rows = Attribute(rawValue: "AXRows") + ///(NSArray *) - UIElements for visible rows + public static let visibleRows = Attribute(rawValue: "AXVisibleRows") + ///(NSArray *) - UIElements for selected rows + public static let selectedRows = Attribute(rawValue: "AXSelectedRows") + ///(NSArray *) - UIElements for columns + public static let columns = Attribute(rawValue: "AXColumns") + ///(NSArray *) - UIElements for visible columns + public static let visibleColumns = Attribute(rawValue: "AXVisibleColumns") + ///(NSArray *) - UIElements for selected columns + public static let selectedColumns = Attribute(rawValue: "AXSelectedColumns") + ///(NSString *) - see sort direction values below + public static let sortDirection = Attribute(rawValue: "AXSortDirection") + + // Cell-based table attributes + ///(NSArray *) - UIElements for selected cells + public static let selectedCells = Attribute(rawValue: "AXSelectedCells") + ///(NSArray *) - UIElements for visible cells + public static let visibleCells = Attribute(rawValue: "AXVisibleCells") + ///(NSArray *) - UIElements for row headers + public static let rowHeaderUIElements = Attribute(rawValue: "AXRowHeaderUIElements") + ///(NSArray *) - UIElements for column headers + public static let columnHeaderUIElements = Attribute(rawValue: "AXColumnHeaderUIElements") + + // Cell-based table parameterized attributes. The parameter for this attribute is an NSArray containing two NSNumbers, the first NSNumber specifies the column index, the second NSNumber specifies the row index. + /// (id) - UIElement for cell at specified row and column + public static let cellForColumnAndRowParameterized = Attribute(rawValue: "AXCellForColumnAndRowParameterized") + + // Cell attributes. The index range contains both the starting index, and the index span in a table. + ///(NSValue *) - (rangeValue) location and row span + public static let rowIndexRange = Attribute(rawValue: "AXRowIndexRange") + ///(NSValue *) - (rangeValue) location and column span + public static let columnIndexRange = Attribute(rawValue: "AXColumnIndexRange") + + // Layout area attributes + ///(NSString *) - see ruler unit values below + public static let horizontalUnits = Attribute(rawValue: "AXHorizontalUnits") + ///(NSString *) - see ruler unit values below + public static let verticalUnits = Attribute(rawValue: "AXVerticalUnits") + ///(NSString *) + public static let horizontalUnitDescription = Attribute(rawValue: "AXHorizontalUnitDescription") + ///(NSString *) + public static let verticalUnitDescription = Attribute(rawValue: "AXVerticalUnitDescription") + + // Layout area parameterized attributes + public static let layoutPointForScreenPointParameterized = "Attribute(rawValue: AXLayoutPointForScreenPointParameterized" //)(NSValue *) - (pointValue); param:(NSValue * - pointValue) + ///(NSValue *) - (sizeValue); param:(NSValue * - sizeValue) + public static let layoutSizeForScreenSizeParameterized = Attribute(rawValue: "AXLayoutSizeForScreenSizeParameterized") + public static let screenPointForLayoutPointParameterized = "Attribute(rawValue: AXScreenPointForLayoutPointParameterized" //)(NSValue *) - (pointValue); param:(NSValue * - pointValue) + ///(NSValue *) - (sizeValue); param:(NSValue * - sizeValue) + public static let screenSizeForLayoutSizeParameterized = Attribute(rawValue: "AXScreenSizeForLayoutSizeParameterized") + + // Layout item attributes + ///(NSArray *) - UIElements for handles + public static let handles = Attribute(rawValue: "AXHandles") + + // Outline attributes + ///(NSNumber *) - (boolValue) is disclosing rows? + public static let disclosing = Attribute(rawValue: "AXDisclosing") + ///(NSArray *) - UIElements for disclosed rows + public static let disclosedRows = Attribute(rawValue: "AXDisclosedRows") + ///(id) - UIElement for disclosing row + public static let disclosedByRow = Attribute(rawValue: "AXDisclosedByRow") + ///(NSNumber *) - indentation level + public static let disclosureLevel = Attribute(rawValue: "AXDisclosureLevel") + + // Slider attributes + ///(NSArray *) - array of allowed values + public static let allowedValues = Attribute(rawValue: "AXAllowedValues") + ///(NSArray *) - array of label UIElements + public static let labelUIElements = Attribute(rawValue: "AXLabelUIElements") + ///(NSNumber *) - value of a label UIElement + public static let labelValue = Attribute(rawValue: "AXLabelValue") + + // Matte attributes + // Attributes no longer supported + ///(NSValue *) - (rect value) bounds of matte hole in screen coords + public static let matteHole = Attribute(rawValue: "AXMatteHole") + ///(id) - UIElement clipped by the matte + public static let matteContentUIElement = Attribute(rawValue: "AXMatteContentUIElement") + + // Ruler view attributes + ///(NSArray *) + public static let markerUIElements = Attribute(rawValue: "AXMarkerUIElements") + /// + public static let markerValues = Attribute(rawValue: "AXMarkerValues") + ///(id) + public static let markerGroupUIElement = Attribute(rawValue: "AXMarkerGroupUIElement") + ///(NSString *) - see ruler unit values below + public static let units = Attribute(rawValue: "AXUnits") + ///(NSString *) + public static let unitDescription = Attribute(rawValue: "AXUnitDescription") + ///(NSString *) - see ruler marker type values below + public static let markerType = Attribute(rawValue: "AXMarkerType") + ///(NSString *) + public static let markerTypeDescription = Attribute(rawValue: "AXMarkerTypeDescription") + + // UI element identification attributes + ///(NSString *) + public static let identifier = Attribute(rawValue: "AXIdentifier") + + // System-wide attributes + public static let focusedApplication = Attribute(rawValue: "AXFocusedApplication") + + // Unknown attributes + public static let functionRowTopLevelElements = Attribute(rawValue: "AXFunctionRowTopLevelElements") + public static let childrenInNavigationOrder = Attribute(rawValue: "AXChildrenInNavigationOrder") + } +} diff --git a/Sources/Constants/Orientation.swift b/Sources/Constants/Orientation.swift new file mode 100644 index 0000000..5ca86a6 --- /dev/null +++ b/Sources/Constants/Orientation.swift @@ -0,0 +1,13 @@ +import Foundation + +@available(*, deprecated, renamed: "UIElement.Orientation", message: "Provided for drop in replacement, but now the type is located under the `UIElement` namespace.") +public typealias Orientation = UIElement.Orientation +extension UIElement { + /// Orientations returned by the orientation property. + /// - seeAlso: [NSAccessibilityOrientation](https://developer.apple.com/library/mac/documentation/AppKit/Reference/NSAccessibility_Protocol_Reference/index.html#//apple_ref/c/tdef/NSAccessibilityOrientation) + public enum Orientation: Int { + case unknown = 0 + case vertical = 1 + case horizontal = 2 + } +} diff --git a/Sources/Constants/Role.swift b/Sources/Constants/Role.swift new file mode 100644 index 0000000..d48c586 --- /dev/null +++ b/Sources/Constants/Role.swift @@ -0,0 +1,72 @@ +import Foundation + +@available(*, deprecated, renamed: "UIElement.Role", message: "Provided for drop in replacement, but now the type is located under the `UIElement` namespace.") +public typealias Role = UIElement.Role +extension UIElement { + /// Provides all known UIElement roles. + /// - seeAlso: [Roles](https://developer.apple.com/library/mac/documentation/AppKit/Reference/NSAccessibility_Protocol_Reference/index.html#//apple_ref/doc/constant_group/Roles) + public struct Role: RawRepresentable, Hashable, Sendable { + public let rawValue: String + public var rawCFStringValue: CFString { rawValue as CFString } + + public init(rawValue: String) { + self.rawValue = rawValue + } + + public static let unknown = Role(rawValue: "AXUnknown") + public static let button = Role(rawValue: "AXButton") + public static let radioButton = Role(rawValue: "AXRadioButton") + public static let checkBox = Role(rawValue: "AXCheckBox") + public static let slider = Role(rawValue: "AXSlider") + public static let tabGroup = Role(rawValue: "AXTabGroup") + public static let textField = Role(rawValue: "AXTextField") + public static let staticText = Role(rawValue: "AXStaticText") + public static let textArea = Role(rawValue: "AXTextArea") + public static let scrollArea = Role(rawValue: "AXScrollArea") + public static let popUpButton = Role(rawValue: "AXPopUpButton") + public static let menuButton = Role(rawValue: "AXMenuButton") + public static let table = Role(rawValue: "AXTable") + public static let application = Role(rawValue: "AXApplication") + public static let group = Role(rawValue: "AXGroup") + public static let radioGroup = Role(rawValue: "AXRadioGroup") + public static let list = Role(rawValue: "AXList") + public static let scrollBar = Role(rawValue: "AXScrollBar") + public static let valueIndicator = Role(rawValue: "AXValueIndicator") + public static let image = Role(rawValue: "AXImage") + public static let menuBar = Role(rawValue: "AXMenuBar") + public static let menu = Role(rawValue: "AXMenu") + public static let menuItem = Role(rawValue: "AXMenuItem") + public static let menuBarItem = Role(rawValue: "AXMenuBarItem") + public static let column = Role(rawValue: "AXColumn") + public static let row = Role(rawValue: "AXRow") + public static let toolbar = Role(rawValue: "AXToolbar") + public static let busyIndicator = Role(rawValue: "AXBusyIndicator") + public static let progressIndicator = Role(rawValue: "AXProgressIndicator") + public static let window = Role(rawValue: "AXWindow") + public static let drawer = Role(rawValue: "AXDrawer") + public static let systemWide = Role(rawValue: "AXSystemWide") + public static let outline = Role(rawValue: "AXOutline") + public static let incrementor = Role(rawValue: "AXIncrementor") + public static let browser = Role(rawValue: "AXBrowser") + public static let comboBox = Role(rawValue: "AXComboBox") + public static let splitGroup = Role(rawValue: "AXSplitGroup") + public static let splitter = Role(rawValue: "AXSplitter") + public static let colorWell = Role(rawValue: "AXColorWell") + public static let growArea = Role(rawValue: "AXGrowArea") + public static let sheet = Role(rawValue: "AXSheet") + public static let helpTag = Role(rawValue: "AXHelpTag") + public static let matte = Role(rawValue: "AXMatte") + public static let ruler = Role(rawValue: "AXRuler") + public static let rulerMarker = Role(rawValue: "AXRulerMarker") + public static let link = Role(rawValue: "AXLink") + public static let disclosureTriangle = Role(rawValue: "AXDisclosureTriangle") + public static let grid = Role(rawValue: "AXGrid") + public static let relevanceIndicator = Role(rawValue: "AXRelevanceIndicator") + public static let levelIndicator = Role(rawValue: "AXLevelIndicator") + public static let cell = Role(rawValue: "AXCell") + public static let popover = Role(rawValue: "AXPopover") + public static let layoutArea = Role(rawValue: "AXLayoutArea") + public static let layoutItem = Role(rawValue: "AXLayoutItem") + public static let handle = Role(rawValue: "AXHandle") + } +} diff --git a/Sources/Constants/Subrole.swift b/Sources/Constants/Subrole.swift new file mode 100644 index 0000000..ce00aea --- /dev/null +++ b/Sources/Constants/Subrole.swift @@ -0,0 +1,46 @@ +import Foundation + +@available(*, deprecated, renamed: "UIElement.Role.Subrole", message: "Provided for drop in replacement, but now the type is located under the `UIElement.Role` namespace.") +public typealias Subrole = UIElement.Role.Subrole +extension UIElement.Role { + /// Provides all known UIElement subroles. + /// - seeAlso: [Subroles](https://developer.apple.com/library/mac/documentation/AppKit/Reference/NSAccessibility_Protocol_Reference/index.html#//apple_ref/doc/constant_group/Subroles) + public struct Subrole: RawRepresentable, Hashable, Sendable { + public let rawValue: String + public var rawCFStringValue: CFString { rawValue as CFString } + + public init(rawValue: String) { + self.rawValue = rawValue + } + + public static let unknown = Subrole(rawValue: "AXUnknown") + public static let closeButton = Subrole(rawValue: "AXCloseButton") + public static let zoomButton = Subrole(rawValue: "AXZoomButton") + public static let minimizeButton = Subrole(rawValue: "AXMinimizeButton") + public static let toolbarButton = Subrole(rawValue: "AXToolbarButton") + public static let tableRow = Subrole(rawValue: "AXTableRow") + public static let outlineRow = Subrole(rawValue: "AXOutlineRow") + public static let secureTextField = Subrole(rawValue: "AXSecureTextField") + public static let standardWindow = Subrole(rawValue: "AXStandardWindow") + public static let dialog = Subrole(rawValue: "AXDialog") + public static let systemDialog = Subrole(rawValue: "AXSystemDialog") + public static let floatingWindow = Subrole(rawValue: "AXFloatingWindow") + public static let systemFloatingWindow = Subrole(rawValue: "AXSystemFloatingWindow") + public static let incrementArrow = Subrole(rawValue: "AXIncrementArrow") + public static let decrementArrow = Subrole(rawValue: "AXDecrementArrow") + public static let incrementPage = Subrole(rawValue: "AXIncrementPage") + public static let decrementPage = Subrole(rawValue: "AXDecrementPage") + public static let searchField = Subrole(rawValue: "AXSearchField") + public static let textAttachment = Subrole(rawValue: "AXTextAttachment") + public static let textLink = Subrole(rawValue: "AXTextLink") + public static let timeline = Subrole(rawValue: "AXTimeline") + public static let sortButton = Subrole(rawValue: "AXSortButton") + public static let ratingIndicator = Subrole(rawValue: "AXRatingIndicator") + public static let contentList = Subrole(rawValue: "AXContentList") + public static let definitionList = Subrole(rawValue: "AXDefinitionList") + public static let fullScreenButton = Subrole(rawValue: "AXFullScreenButton") + public static let toggle = Subrole(rawValue: "AXToggle") + public static let switchSubrole = Subrole(rawValue: "AXSwitch") + public static let descriptionList = Subrole(rawValue: "AXDescriptionList") + } +} diff --git a/Sources/Error.swift b/Sources/Error.swift index 1be2d31..26f0010 100644 --- a/Sources/Error.swift +++ b/Sources/Error.swift @@ -4,7 +4,7 @@ import Cocoa extension AXError: Swift.Error {} // For some reason values don't get described in this enum, so we have to do it manually. -extension AXError: CustomStringConvertible { +extension AXError: @retroactive CustomStringConvertible { fileprivate var valueAsString: String { switch self { case .success: diff --git a/Sources/Observer.swift b/Sources/Observer.swift index 75954d5..682c22e 100644 --- a/Sources/Observer.swift +++ b/Sources/Observer.swift @@ -10,10 +10,10 @@ import Darwin public final class Observer { public typealias Callback = (_ observer: Observer, _ element: UIElement, - _ notification: AXNotification) -> Void + _ notification: UIElement.AXNotification) -> Void public typealias CallbackWithInfo = (_ observer: Observer, _ element: UIElement, - _ notification: AXNotification, + _ notification: UIElement.AXNotification, _ info: [String: AnyObject]?) -> Void let pid: pid_t @@ -22,10 +22,10 @@ public final class Observer { let callbackWithInfo: CallbackWithInfo? public fileprivate(set) lazy var application: Application = - Application(forKnownProcessID: self.pid)! + Application(forKnownProcessID: self.pid)! /// Creates and starts an observer on the given `processID`. - public init(processID: pid_t, callback: @escaping Callback) throws { + public init(processID: pid_t, callback: @escaping Callback) throws(AXError) { var axObserver: AXObserver? let error = AXObserverCreate(processID, internalCallback, &axObserver) @@ -46,7 +46,7 @@ public final class Observer { /// /// Use this initializer if you want the extra user info provided with notifications. /// - seeAlso: [UserInfo Keys for Posting Accessibility Notifications](https://developer.apple.com/library/mac/documentation/AppKit/Reference/NSAccessibility_Protocol_Reference/index.html#//apple_ref/doc/constant_group/UserInfo_Keys_for_Posting_Accessibility_Notifications) - public init(processID: pid_t, callback: @escaping CallbackWithInfo) throws { + public init(processID: pid_t, callback: @escaping CallbackWithInfo) throws(AXError) { var axObserver: AXObserver? let error = AXObserverCreateWithInfoCallback(processID, internalInfoCallback, &axObserver) @@ -100,8 +100,8 @@ public final class Observer { /// error is not passed on for consistency with `start()` and `stop()`. /// - throws: `Error.NotificationUnsupported`: The element does not support notifications (note /// that the system-wide element does not support notifications). - public func addNotification(_ notification: AXNotification, - forElement element: UIElement) throws { + public func addNotification(_ notification: UIElement.AXNotification, + forElement element: UIElement) throws(AXError) { let selfPtr = UnsafeMutableRawPointer(Unmanaged.passUnretained(self).toOpaque()) let error = AXObserverAddNotification( axObserver, element.element, notification.rawValue as CFString, selfPtr @@ -119,8 +119,8 @@ public final class Observer { /// error is not passed on for consistency with `start()` and `stop()`. /// - throws: `Error.NotificationUnsupported`: The element does not support notifications (note /// that the system-wide element does not support notifications). - public func removeNotification(_ notification: AXNotification, - forElement element: UIElement) throws { + public func removeNotification(_ notification: UIElement.AXNotification, + forElement element: UIElement) throws(AXError) { let error = AXObserverRemoveNotification( axObserver, element.element, notification.rawValue as CFString ) @@ -138,10 +138,7 @@ private func internalCallback(_ axObserver: AXObserver, let observer = Unmanaged.fromOpaque(userData).takeUnretainedValue() let element = UIElement(axElement) - guard let notif = AXNotification(rawValue: notification as String) else { - NSLog("Unknown AX notification %s received", notification as String) - return - } + let notif = UIElement.AXNotification(rawValue: notification as String) observer.callback!(observer, element, notif) } @@ -155,9 +152,6 @@ private func internalInfoCallback(_ axObserver: AXObserver, let observer = Unmanaged.fromOpaque(userData).takeUnretainedValue() let element = UIElement(axElement) let info = cfInfo as NSDictionary? as! [String: AnyObject]? - guard let notif = AXNotification(rawValue: notification as String) else { - NSLog("Unknown AX notification %s received", notification as String) - return - } + let notif = UIElement.AXNotification(rawValue: notification as String) observer.callbackWithInfo!(observer, element, notif, info) } diff --git a/Sources/SystemWideElement.swift b/Sources/SystemWideElement.swift index cc0e80c..a1b2d9c 100644 --- a/Sources/SystemWideElement.swift +++ b/Sources/SystemWideElement.swift @@ -2,7 +2,8 @@ import Foundation import Cocoa /// A singleton for the system-wide element. -public var systemWideElement = SystemWideElement() +nonisolated(unsafe) +public let systemWideElement = SystemWideElement() /// A `UIElement` for the system-wide accessibility element, which can be used to retrieve global, /// application-inspecific parameters like the currently focused element. @@ -12,7 +13,7 @@ open class SystemWideElement: UIElement { } /// Returns the element at the specified top-down coordinates, or nil if there is none. - open override func elementAtPosition(_ x: Float, _ y: Float) throws -> UIElement? { + open override func elementAtPosition(_ x: Float, _ y: Float) throws(AXError) -> UIElement? { return try super.elementAtPosition(x, y) } } diff --git a/Sources/UIElement.swift b/Sources/UIElement.swift index 6a34ddc..fe84ce3 100644 --- a/Sources/UIElement.swift +++ b/Sources/UIElement.swift @@ -11,14 +11,24 @@ import Foundation /// means that operations are synchronous and can hang until they time out. The default timeout is /// 6 seconds, but it can be changed using `setMessagingTimeout` and `setGlobalMessagingTimeout`. /// -/// Every attribute- or action-related function has an enum version and a String version. This is -/// because certain processes might report attributes or actions not documented in the standard API. -/// These will be ignored by enum functions (and you can't specify them). Most users will want to -/// use the enum-based versions, but if you want to be exhaustive or use non-standard attributes and -/// actions, you can use the String versions. +/// Every attribute- or action-related function has an RawRepresentable type safe version and a deprecated String +/// version. There are attributes and actions that might not be included in the lists in this package, and while this +/// was previously handled by provided a raw String interface, now the RawRepresentable structs can serve both +/// purposes. Dot syntax will still work (`element.attribute(.windows)`) and you can add additionally +/// unaccounted for values through extending the `Attribute`, `Action` or whatever type like so: +/// ```swift +/// extension Attribute { +/// static let windows = Attribute(rawValue: "AXWindows") +/// } +/// ``` +/// +/// Additionally, unaccounteded for attributes will now be provided in their appropriate RawRepresentable type +/// when returned from the system. /// /// ### Error handling /// +/// All throwing methods throw typed errors (Specifically `AXError`) +/// /// Unless otherwise specified, during reads, "missing data/attribute" errors are handled by /// returning optionals as nil. During writes, missing attribute errors are thrown. /// @@ -59,7 +69,7 @@ open class UIElement { /// happens asynchronously and does not affect the return value. open class func isProcessTrusted(withPrompt showPrompt: Bool = false) -> Bool { let options = [ - kAXTrustedCheckOptionPrompt.takeUnretainedValue() as String: showPrompt as CFBoolean + "AXTrustedCheckOptionPrompt": showPrompt as CFBoolean // `kAXTrustedCheckOptionPrompt` causes a concurrency build error, so just using raw string here ] return AXIsProcessTrustedWithOptions(options as CFDictionary) } @@ -76,21 +86,11 @@ open class UIElement { /// Returns the list of all attributes. /// /// Does not include parameterized attributes. - open func attributes() throws -> [Attribute] { - let attrs = try attributesAsStrings() - for attr in attrs where Attribute(rawValue: attr) == nil { - print("Unrecognized attribute: \(attr)") - } - return attrs.compactMap({ Attribute(rawValue: $0) }) - } - - // This version is named differently so the caller doesn't have to specify the return type when - // using the enum version. - open func attributesAsStrings() throws -> [String] { - var names: CFArray? + open func attributes() throws(AXError) -> [Attribute] { + var names: CFArray! let error = AXUIElementCopyAttributeNames(element, &names) - if error == .noValue || error == .attributeUnsupported { + guard error != .noValue, error != .attributeUnsupported, let names else { return [] } @@ -100,7 +100,9 @@ open class UIElement { // We must first convert the CFArray to a native array, then downcast to an array of // strings. - return names! as [AnyObject] as! [String] + let strings = names as! [String] + + return strings.map(Attribute.init(rawValue:)) } /// Returns whether `attribute` is supported by this element. @@ -109,14 +111,10 @@ open class UIElement { /// which is more convenient than dealing with exceptions (which are used for more serious /// errors). However, if you'd like to specifically test an attribute is actually supported, you /// can use this method. - open func attributeIsSupported(_ attribute: Attribute) throws -> Bool { - return try attributeIsSupported(attribute.rawValue) - } - - open func attributeIsSupported(_ attribute: String) throws -> Bool { + open func attributeIsSupported(_ attribute: Attribute) throws(AXError) -> Bool { // Ask to copy 0 values, since we are only interested in the return code. var value: CFArray? - let error = AXUIElementCopyAttributeValues(element, attribute as CFString, 0, 0, &value) + let error = AXUIElementCopyAttributeValues(element, attribute.rawCFStringValue, 0, 0, &value) if error == .attributeUnsupported { return false @@ -134,13 +132,9 @@ open class UIElement { } /// Returns whether `attribute` is writeable. - open func attributeIsSettable(_ attribute: Attribute) throws -> Bool { - return try attributeIsSettable(attribute.rawValue) - } - - open func attributeIsSettable(_ attribute: String) throws -> Bool { + open func attributeIsSettable(_ attribute: Attribute) throws(AXError) -> Bool { var settable: DarwinBoolean = false - let error = AXUIElementIsAttributeSettable(element, attribute as CFString, &settable) + let error = AXUIElementIsAttributeSettable(element, attribute.rawCFStringValue, &settable) if error == .noValue || error == .attributeUnsupported { return false @@ -162,43 +156,40 @@ open class UIElement { /// /// - warning: This method force-casts the attribute to the desired type, which will abort if /// the cast fails. If you want to check the return type, ask for Any. - open func attribute(_ attribute: Attribute) throws -> T? { - return try self.attribute(attribute.rawValue) - } - - open func attribute(_ attribute: String) throws -> T? { + open func attribute(_ attribute: Attribute) throws(AXError) -> T? { var value: AnyObject? - let error = AXUIElementCopyAttributeValue(element, attribute as CFString, &value) + let error = AXUIElementCopyAttributeValue(element, attribute.rawCFStringValue, &value) - if error == .noValue || error == .attributeUnsupported { - return nil - } + guard + error != .noValue, + error != .attributeUnsupported + else { return nil } guard error == .success else { throw error } - guard let unpackedValue = (unpackAXValue(value!) as? T) else { + guard let value else { + return nil + } + + guard let unpackedValue = (unpackAXValue(value) as? T) else { throw AXError.illegalArgument } - + return unpackedValue } /// Sets the value of `attribute` to `value`. /// - /// - warning: Unlike read-only methods, this method throws if the attribute doesn't exist. + /// - warning: Unlike read-only methods, this method throws(AXError) if the attribute doesn't exist. /// /// - throws: /// - `Error.AttributeUnsupported`: `attribute` isn't supported. /// - `Error.IllegalArgument`: `value` is an illegal value. /// - `Error.Failure`: A temporary failure occurred. - open func setAttribute(_ attribute: Attribute, value: Any) throws { - try setAttribute(attribute.rawValue, value: value) - } - - open func setAttribute(_ attribute: String, value: Any) throws { - let error = AXUIElementSetAttributeValue(element, attribute as CFString, packAXValue(value)) + open func setAttribute(_ attribute: Attribute, value: Any) throws(AXError) { + let error = AXUIElementSetAttributeValue(element, attribute.rawCFStringValue, packAXValue(value)) guard error == .success else { throw error @@ -217,26 +208,21 @@ open class UIElement { /// /// - note: Presumably you would use this API for performance, though it's not explicitly /// documented by Apple that there is actually a difference. - open func getMultipleAttributes(_ names: Attribute...) throws -> [Attribute: Any] { + open func getMultipleAttributes(_ names: Attribute...) throws(AXError) -> [Attribute: Any] { return try getMultipleAttributes(names) } - open func getMultipleAttributes(_ attributes: [Attribute]) throws -> [Attribute: Any] { - let values = try fetchMultiAttrValues(attributes.map({ $0.rawValue })) - return try packMultiAttrValues(attributes, values: values) - } - - open func getMultipleAttributes(_ attributes: [String]) throws -> [String: Any] { + open func getMultipleAttributes(_ attributes: [Attribute]) throws(AXError) -> [Attribute: Any] { let values = try fetchMultiAttrValues(attributes) return try packMultiAttrValues(attributes, values: values) } // Helper: Gets list of values - fileprivate func fetchMultiAttrValues(_ attributes: [String]) throws -> [AnyObject] { + fileprivate func fetchMultiAttrValues(_ attributes: [Attribute]) throws(AXError) -> [AnyObject] { var valuesCF: CFArray? let error = AXUIElementCopyMultipleAttributeValues( element, - attributes as CFArray, + attributes.map(\.rawCFStringValue) as CFArray, // keep going on errors (particularly NoValue) AXCopyMultipleAttributeOptions(rawValue: 0), &valuesCF) @@ -249,8 +235,10 @@ open class UIElement { } // Helper: Packs names, values into dictionary - fileprivate func packMultiAttrValues(_ attributes: [Attr], - values: [AnyObject]) throws -> [Attr: Any] { + fileprivate func packMultiAttrValues( + _ attributes: [Attr], + values: [AnyObject] + ) throws(AXError) -> [Attr: Any] { var result = [Attr: Any]() for (index, attribute) in attributes.enumerated() { if try checkMultiAttrValue(values[index]) { @@ -261,7 +249,7 @@ open class UIElement { } // Helper: Checks if value is present and not an error (throws on nontrivial errors). - fileprivate func checkMultiAttrValue(_ value: AnyObject) throws -> Bool { + fileprivate func checkMultiAttrValue(_ value: AnyObject) throws(AXError) -> Bool { // Check for null if value is NSNull { return false @@ -291,11 +279,7 @@ open class UIElement { /// - parameter attribute: The name of the array attribute. /// /// - throws: `Error.IllegalArgument` if the attribute isn't an array. - open func arrayAttribute(_ attribute: Attribute) throws -> [T]? { - return try arrayAttribute(attribute.rawValue) - } - - open func arrayAttribute(_ attribute: String) throws -> [T]? { + open func arrayAttribute(_ attribute: Attribute) throws(AXError) -> [T]? { guard let value: Any = try self.attribute(attribute) else { return nil } @@ -318,38 +302,31 @@ open class UIElement { /// /// - throws: `Error.IllegalArgument` if the attribute isn't an array. open func valuesForAttribute - (_ attribute: Attribute, startAtIndex index: Int, maxValues: Int) throws -> [T]? { - return try valuesForAttribute(attribute.rawValue, startAtIndex: index, maxValues: maxValues) - } - - open func valuesForAttribute - (_ attribute: String, startAtIndex index: Int, maxValues: Int) throws -> [T]? { + (_ attribute: Attribute, startAtIndex index: Int, maxValues: Int) throws(AXError) -> [T]? { var values: CFArray? let error = AXUIElementCopyAttributeValues( - element, attribute as CFString, index, maxValues, &values + element, attribute.rawCFStringValue, index, maxValues, &values ) - if error == .noValue || error == .attributeUnsupported { - return nil - } + guard + error != .noValue, + error != .attributeUnsupported, + let values + else { return nil } guard error == .success else { throw error } - let array = values! as [AnyObject] + let array = values as [AnyObject] return array.map({ unpackAXValue($0) as! T }) } /// Returns the number of values an array attribute has. /// - returns: The number of values, or `nil` if `attribute` isn't an array (or doesn't exist). - open func valueCountForAttribute(_ attribute: Attribute) throws -> Int? { - return try valueCountForAttribute(attribute.rawValue) - } - - open func valueCountForAttribute(_ attribute: String) throws -> Int? { + open func valueCountForAttribute(_ attribute: Attribute) throws(AXError) -> Int? { var count: Int = 0 - let error = AXUIElementGetAttributeValueCount(element, attribute as CFString, &count) + let error = AXUIElementGetAttributeValueCount(element, attribute.rawCFStringValue, &count) if error == .attributeUnsupported || error == .illegalArgument { return nil @@ -362,23 +339,22 @@ open class UIElement { return count } + // MARK: Parameterized attributes /// Returns a list of all parameterized attributes of the element. /// /// Parameterized attributes are attributes that require parameters to retrieve. For example, /// the cell contents of a spreadsheet might require the row and column of the cell you want. - open func parameterizedAttributes() throws -> [Attribute] { - return try parameterizedAttributesAsStrings().compactMap({ Attribute(rawValue: $0) }) - } - - open func parameterizedAttributesAsStrings() throws -> [String] { + open func parameterizedAttributes() throws(AXError) -> [Attribute] { var names: CFArray? let error = AXUIElementCopyParameterizedAttributeNames(element, &names) - if error == .noValue || error == .attributeUnsupported { - return [] - } + guard + error != .noValue, + error != .attributeUnsupported, + let names = names as? [AnyObject] + else { return [] } guard error == .success else { throw error @@ -386,7 +362,11 @@ open class UIElement { // We must first convert the CFArray to a native array, then downcast to an array of // strings. - return names! as [AnyObject] as! [String] + guard + let strings = names as? [String] + else { return [] } + + return strings.map(Attribute.init(rawValue:)) } /// Returns the value of the parameterized attribute `attribute` with parameter `param`. @@ -394,27 +374,26 @@ open class UIElement { /// The expected type of `param` depends on the attribute. See the /// [NSAccessibility Informal Protocol Reference](https://developer.apple.com/library/mac/documentation/Cocoa/Reference/ApplicationKit/Protocols/NSAccessibility_Protocol/) /// for more info. - open func parameterizedAttribute(_ attribute: Attribute, param: U) throws -> T? { - return try parameterizedAttribute(attribute.rawValue, param: param) - } - - open func parameterizedAttribute(_ attribute: String, param: U) throws -> T? { + open func parameterizedAttribute(_ attribute: Attribute, param: U) throws(AXError) -> T? { var value: AnyObject? let error = AXUIElementCopyParameterizedAttributeValue( - element, attribute as CFString, param as AnyObject, &value + element, attribute.rawCFStringValue, param as AnyObject, &value ) - if error == .noValue || error == .attributeUnsupported { - return nil - } + guard + error != .noValue, + error != .attributeUnsupported, + let value + else { return nil } guard error == .success else { throw error } - return (unpackAXValue(value!) as! T) + return (unpackAXValue(value) as! T) } + // MARK: Attribute helpers // Checks if the value is an AXValue and if so, unwraps it. @@ -483,44 +462,47 @@ open class UIElement { // MARK: - Actions /// Returns a list of actions that can be performed on the element. - open func actions() throws -> [Action] { - return try actionsAsStrings().compactMap({ Action(rawValue: $0) }) - } - - open func actionsAsStrings() throws -> [String] { + open func actions() throws(AXError) -> [Action] { var names: CFArray? let error = AXUIElementCopyActionNames(element, &names) if error == .noValue || error == .attributeUnsupported { return [] } + guard + error != .noValue, + error != .attributeUnsupported, + let names = names as? [AnyObject] + else { return [] } guard error == .success else { throw error } // We must first convert the CFArray to a native array, then downcast to an array of strings. - return names! as [AnyObject] as! [String] - } + guard + let strings = names as? [String] + else { return [] } - /// Returns the human-readable description of `action`. - open func actionDescription(_ action: Action) throws -> String? { - return try actionDescription(action.rawValue) + return strings.map(Action.init(rawValue:)) } - open func actionDescription(_ action: String) throws -> String? { + + /// Returns the human-readable description of `action`. + open func actionDescription(_ action: Action) throws(AXError) -> String? { var description: CFString? - let error = AXUIElementCopyActionDescription(element, action as CFString, &description) + let error = AXUIElementCopyActionDescription(element, action.rawCFStringValue, &description) - if error == .noValue || error == .actionUnsupported { - return nil - } + guard + error != .noValue, + error != .attributeUnsupported + else { return nil } guard error == .success else { throw error } - return description! as String + return description as? String } /// Performs the action `action` on the element, returning on success. @@ -529,12 +511,8 @@ open class UIElement { /// actually perform the action. It doesn't necessarily mean that the action wasn't /// performed. /// - throws: `Error.ActionUnsupported` if the action is not supported. - open func performAction(_ action: Action) throws { - try performAction(action.rawValue) - } - - open func performAction(_ action: String) throws { - let error = AXUIElementPerformAction(element, action as CFString) + open func performAction(_ action: Action) throws(AXError) { + let error = AXUIElementPerformAction(element, action.rawCFStringValue) guard error == .success else { throw error @@ -545,8 +523,8 @@ open class UIElement { /// Returns the process ID of the application that the element is a part of. /// - /// Throws only if the element is invalid (`Errors.InvalidUIElement`). - open func pid() throws -> pid_t { + /// throws(AXError) only if the element is invalid (`Errors.InvalidUIElement`). + open func pid() throws(AXError) -> pid_t { var pid: pid_t = -1 let error = AXUIElementGetPid(element, &pid) @@ -578,7 +556,7 @@ open class UIElement { // Gets the element at the specified coordinates. // This can only be called on applications and the system-wide element, so it is internal here. - func elementAtPosition(_ x: Float, _ y: Float) throws -> UIElement? { + func elementAtPosition(_ x: Float, _ y: Float) throws(AXError) -> UIElement? { var result: AXUIElement? let error = AXUIElementCopyElementAtPosition(element, x, y, &result) @@ -590,7 +568,87 @@ open class UIElement { throw error } - return UIElement(result!) + return result.map(UIElement.init) + } + + func elementAtPosition(_ position: CGPoint) throws(AXError) -> UIElement? { + try elementAtPosition(Float(position.x), Float(position.y)) + } + + // MARK: - Deprecated Strings Methods + + // This version is named differently so the caller doesn't have to specify the return type when + // using the enum version. + @available(*, deprecated, renamed: "attributes", message: "If you need strings, just call `attributes().map(\\.rawValue)`") + open func attributesAsStrings() throws(AXError) -> [String] { + try attributes().map(\.rawValue) + } + + @available(*, deprecated, renamed: "attributeIsSupported") + open func attributeIsSupported(_ attribute: String) throws(AXError) -> Bool { + try attributeIsSupported(Attribute(rawValue: attribute)) + } + + @available(*, deprecated, renamed: "attributeIsSettable") + open func attributeIsSettable(_ attribute: String) throws(AXError) -> Bool { + try attributeIsSettable(Attribute(rawValue: attribute)) + } + + @available(*, deprecated, renamed: "attribute") + open func attribute(_ attribute: String) throws(AXError) -> T? { + try self.attribute(Attribute(rawValue: attribute)) + } + + @available(*, deprecated, renamed: "setAttribute") + open func setAttribute(_ attribute: String, value: Any) throws(AXError) { + try setAttribute(Attribute(rawValue: attribute), value: value) + } + + @available(*, deprecated, renamed: "getMultipleAttributes") + open func getMultipleAttributes(_ attributes: [String]) throws(AXError) -> [String: Any] { + let values = try fetchMultiAttrValues(attributes.map(Attribute.init(rawValue:))) + return try packMultiAttrValues(attributes, values: values) + } + + @available(*, deprecated, renamed: "arrayAttribute") + open func arrayAttribute(_ attribute: String) throws(AXError) -> [T]? { + try arrayAttribute(Attribute(rawValue: attribute)) + } + + @available(*, deprecated, renamed: "valuesForAttribute") + open func valuesForAttribute + (_ attribute: String, startAtIndex index: Int, maxValues: Int) throws(AXError) -> [T]? { + try valuesForAttribute(Attribute(rawValue: attribute), startAtIndex: index, maxValues: maxValues) + } + + @available(*, deprecated, renamed: "valueCountForAttribute") + open func valueCountForAttribute(_ attribute: String) throws(AXError) -> Int? { + try valueCountForAttribute(Attribute(rawValue: attribute)) + } + + @available(*, deprecated, renamed: "parameterizedAttributes") + open func parameterizedAttributesAsStrings() throws(AXError) -> [String] { + try parameterizedAttributes().map(\.rawValue) + } + + @available(*, deprecated, renamed: "parameterizedAttribute") + open func parameterizedAttribute(_ attribute: String, param: U) throws(AXError) -> T? { + try parameterizedAttribute(Attribute(rawValue: attribute), param: param) + } + + @available(*, deprecated, renamed: "actions") + open func actionsAsStrings() throws(AXError) -> [String] { + try actions().map(\.rawValue) + } + + @available(*, deprecated, renamed: "actionDescription") + open func actionDescription(_ action: String) throws(AXError) -> String? { + try actionDescription(Action(rawValue: action)) + } + + @available(*, deprecated, renamed: "performAction") + open func performAction(_ action: String) throws(AXError) { + try performAction(Action(rawValue: action)) } // TODO: convenience functions for attributes @@ -627,8 +685,8 @@ extension UIElement: CustomStringConvertible { let pidString = (pid == nil) ? "??" : String(pid!) return "<\(roleString) \"" - + "\(description ?? String(describing: element))" - + "\" (pid=\(pidString))>" + + "\(description ?? String(describing: element))" + + "\" (pid=\(pidString))>" } public var inspect: String { @@ -649,6 +707,13 @@ public func ==(lhs: UIElement, rhs: UIElement) -> Bool { return CFEqual(lhs.element, rhs.element) } +extension UIElement: Hashable { + public func hash(into hasher: inout Hasher) { + let hashCode = CFHash(self) + hasher.combine(hashCode) + } +} + // MARK: - Convenience getters extension UIElement { @@ -658,7 +723,7 @@ extension UIElement { /// finished initializing. /// /// - seeAlso: [Roles](https://developer.apple.com/library/mac/documentation/AppKit/Reference/NSAccessibility_Protocol_Reference/index.html#//apple_ref/doc/constant_group/Roles) - public func role() throws -> Role? { + public func role() throws(AXError) -> Role? { // should this be non-optional? if let str: String = try self.attribute(.role) { return Role(rawValue: str) @@ -668,9 +733,9 @@ extension UIElement { } /// - seeAlso: [Subroles](https://developer.apple.com/library/mac/documentation/AppKit/Reference/NSAccessibility_Protocol_Reference/index.html#//apple_ref/doc/constant_group/Subroles) - public func subrole() throws -> Subrole? { + public func subrole() throws(AXError) -> Role.Subrole? { if let str: String = try self.attribute(.subrole) { - return Subrole(rawValue: str) + return Role.Subrole(rawValue: str) } else { return nil }