- short quick form documentation in a way that makes sense to me - trying to be as heavy with code samples as reasonable for me
- feel free to contribute in a way that is reasonable to you
- some official documentation exists but I've had difficulty finding more, so most of how I learned was through searches on GitHub of the things listed in the official documentation
- note that this API exists in objective c as well but the notes below provide syntax in swift
- hierarchy of UI elements can be explored using the accessibility inspector tool in xcode
- for inspecting menus, open the menu using the accessibility inspector, then refresh using command R, or open the menu, then use option space to toggle the inspector selector
- everything is an
AXUIElement
kAXWindowsAttribute
- if a
AXUIElement
is an application, this is all the elements that are windows for that application
kAXFocusedUIElementAttribute
- the corresponding
AXUIElement
that is in focus for the systemwideAXUIElement
kAXChildrenAttribute
- the
AXUIElement
children of anAXUIElement
kAXPressAction
- presses the element
kAXShowMenuAction
- shows the menu if there is one
- returns
Bool
- options is in the form of a dictionary
- checkOptPrompt as true prompts the user to allow control
- returns
AXUIElement
- gets the UI element of the application with pid
- returns
AXUIElement
- gets UI element representing entire system
- puts all the attribute/action names available for an
AXUIElement
in the variable at pointer
var names:CFArray? = nil
AXUIElementCopyAttributeNames(element, &names)
- puts all the value for a specified attribute for an
AXUIElement
in the variable at pointer (ex.kAXFocusedUIElementAttribute
)
var focused: CFTypeRef?
AXUIElementCopyAttributeValue(systemWide, kAXFocusedUIElementAttribute as CFString, &focused)
- for values that are certain types (ex.
kAXPositionAttribute
is aCGPoint
), need to useAXValueGetValue
var positionRef: AnyObject?
AXUIElementCopyAttributeValue(focusedWindow!, kAXPositionAttribute as CFString, &positionRef)
var position = CGPoint.zero
AXValueGetValue(positionRef as! AXValue, .cgPoint, &position)
- puts the pid that is managing an
AXUIElement
in the variable at pointer
var pid: pid_t = 0
AXUIElementGetPid(element as! AXUIElement, &pid)
- for attributes that are arrays, puts the attribute values in the variable at pointer
- I've always used index as 0 and limit as 99999
- most used for me for the children attribute
var childrenArray: CFArray?
AXUIElementCopyAttributeValues(element, kAXChildrenAttribute as CFString, 0, 99999, &childrenArray)
var children = (childrenArray as! Array<AXUIElement>)
- performs an action (ex. press)
AXUIElementPerformAction(element, kAXPressAction as CFString)
- checks if an attribute is settable (ex. focused)
var att: DarwinBoolean = false
AXUIElementIsAttributeSettable(font, kAXFocusedAttribute as CFString, &(att))
- sets an attribute, although the value type is sometimes annoying (ex. focused)
AXUIElementSetAttributeValue(element, kAXFocusedAttribute as CFString, kCFBooleanTrue as CFTypeRef)
- used this so main thread isn't blocked whenever trying to do something with accessibility api
- i think no matter what the function will eventually occur, this just limits how long you wait for it to return
- setting a timeout of 0.0 didn't seem to keep it from being blocked but 0.01 was sufficient
- i didnt use this every time i used the accessibility api; i only used it for performing certain actions that were known to be problematic
- can't stop events from propagating - because handler has to return void
- based on bit flags
- for each event you want to listen for (ex. keyDown), take the bitwise OR with
(1 << CGEventType.keyDown.rawValue)
var eventMask = (1 << CGEventType.keyDown.rawValue) | (1 << CGEventType.flagsChanged.rawValue)
...
eventMask = eventMask | (1 << CGEventType.leftMouseDown.rawValue) | (1 << CGEventType.rightMouseDown.rawValue) | (1 << CGEventType.otherMouseDown.rawValue)
- returns
CGEventTap
- placement allows for setting if the callback occurs before or after existing responses
- callback has to be outside of any class and return an optional
Unmanaged<CGEvent>
type - I just set userInfo to nil
- then follow up code including enabling the tap
guard let eventTap = CGEvent.tapCreate(tap: .cgSessionEventTap, place: .headInsertEventTap, options: .defaultTap, eventsOfInterest: CGEventMask(eventMask), callback: respondToEvent, userInfo: nil) else {
print("event tap failed to create")
exit(1)
}
let loopSource = CFMachPortCreateRunLoopSource(kCFAllocatorDefault, eventTap, 0)
CFRunLoopAddSource(CFRunLoopGetCurrent(), loopSource, .commonModes)
CGEvent.tapEnable(tap: eventTap, enable: true)
CFRunLoopRun()
- in the callback function,
return nil
stops propagation, whilereturn Unmanaged.passRetained(event)
propagates it
let nsEvent = NSEvent.init(cgEvent: event)!
- access using .#
- create with ()
- add a row in info.plist
- key: Applicaiton is agent (UIElement)
- type: Boolean
- Value: YES
- with
NSApplication.shared.delegate
- argVariableName is what is used within the function
- argName is what is used when calling the function
- function declaration
- (void)funcName:(argType)argVariableName argName:(argType)argVariableName
{
- function call
[self.funcName:argValue argName:argValue];
if (self.delegate && [self.delegate respondsToSelector:@selector(funcInParent:)]) {
[self.delegate funcAtParent:argValueIfNeeded];
}
NSDateComponents *components = [[NSCalendar currentCalendar] components:NSCalendarUnitDay | NSCalendarUnitMonth | NSCalendarUnitYear fromDate:date];
// usage: components.year, components.month, components.day
- some things require you to not be sandboxing
- remove by clicking the project in project navigator, then the signing and capabilities tab, then clicking the x in the sandbox section
If you found this helpful and want to support me, consider sponsoring me via (Venmo)[https://venmo.com/garyshen]/(Paypal)[paypal.me/GaryShen]