473 lines
10 KiB
Markdown
473 lines
10 KiB
Markdown
# macOS Accessibility Tools Guide
|
|
|
|
**Last updated**: January 2025
|
|
**Source of truth**: `crates/g3-computer-control/src/macax/`
|
|
|
|
## Purpose
|
|
|
|
G3 includes tools for controlling macOS applications via the Accessibility API. This enables automation of native macOS apps, including those you're building with G3.
|
|
|
|
## Overview
|
|
|
|
The macOS Accessibility API provides programmatic access to UI elements in any application. G3 exposes this through the `macax_*` tools, allowing you to:
|
|
|
|
- List and activate applications
|
|
- Inspect UI element hierarchies
|
|
- Find elements by role, title, or identifier
|
|
- Click buttons and interact with controls
|
|
- Read and set values in text fields
|
|
- Simulate keyboard input
|
|
|
|
## Setup
|
|
|
|
### 1. Enable in Configuration
|
|
|
|
```toml
|
|
# ~/.config/g3/config.toml
|
|
[macax]
|
|
enabled = true
|
|
```
|
|
|
|
Or use the CLI flag:
|
|
|
|
```bash
|
|
g3 --macax
|
|
```
|
|
|
|
### 2. Grant Accessibility Permissions
|
|
|
|
1. Open **System Preferences** → **Security & Privacy** → **Privacy**
|
|
2. Select **Accessibility** in the left sidebar
|
|
3. Click the lock icon and authenticate
|
|
4. Add your terminal application (Terminal, iTerm2, etc.)
|
|
5. Restart your terminal
|
|
|
|
**Note**: If using VS Code's integrated terminal, add VS Code to the list.
|
|
|
|
### 3. Verify Setup
|
|
|
|
```json
|
|
{"tool": "macax_list_apps", "args": {}}
|
|
```
|
|
|
|
This should return a list of running applications.
|
|
|
|
## Available Tools
|
|
|
|
### macax_list_apps
|
|
|
|
List all running applications.
|
|
|
|
**Parameters**: None
|
|
|
|
**Example**:
|
|
```json
|
|
{"tool": "macax_list_apps", "args": {}}
|
|
```
|
|
|
|
**Returns**:
|
|
```
|
|
Running Applications:
|
|
- Safari (com.apple.Safari)
|
|
- Finder (com.apple.finder)
|
|
- Terminal (com.apple.Terminal)
|
|
- MyApp (com.example.myapp)
|
|
```
|
|
|
|
---
|
|
|
|
### macax_get_frontmost_app
|
|
|
|
Get the currently active (frontmost) application.
|
|
|
|
**Parameters**: None
|
|
|
|
**Example**:
|
|
```json
|
|
{"tool": "macax_get_frontmost_app", "args": {}}
|
|
```
|
|
|
|
**Returns**:
|
|
```
|
|
Frontmost Application: Safari (com.apple.Safari)
|
|
```
|
|
|
|
---
|
|
|
|
### macax_activate_app
|
|
|
|
Bring an application to the front.
|
|
|
|
**Parameters**:
|
|
- `app_name` (string, required): Application name
|
|
|
|
**Example**:
|
|
```json
|
|
{"tool": "macax_activate_app", "args": {"app_name": "Safari"}}
|
|
```
|
|
|
|
---
|
|
|
|
### macax_get_ui_tree
|
|
|
|
Get the UI element hierarchy of an application.
|
|
|
|
**Parameters**:
|
|
- `app_name` (string, required): Application name
|
|
- `max_depth` (integer, optional): Maximum tree depth (default: 5)
|
|
|
|
**Example**:
|
|
```json
|
|
{"tool": "macax_get_ui_tree", "args": {"app_name": "Calculator", "max_depth": 3}}
|
|
```
|
|
|
|
**Returns**:
|
|
```
|
|
UI Tree for Calculator:
|
|
└── AXApplication "Calculator"
|
|
└── AXWindow "Calculator"
|
|
├── AXGroup
|
|
│ ├── AXButton "1" [id: digit_1]
|
|
│ ├── AXButton "2" [id: digit_2]
|
|
│ ├── AXButton "+" [id: add]
|
|
│ └── AXButton "=" [id: equals]
|
|
└── AXStaticText "0" [id: display]
|
|
```
|
|
|
|
**Notes**:
|
|
- Use lower `max_depth` for complex apps to avoid overwhelming output
|
|
- Elements show role, title, and accessibility identifier (if set)
|
|
|
|
---
|
|
|
|
### macax_find_elements
|
|
|
|
Find UI elements matching criteria.
|
|
|
|
**Parameters**:
|
|
- `app_name` (string, required): Application name
|
|
- `role` (string, optional): Element role (e.g., "button", "textField")
|
|
- `title` (string, optional): Element title/label
|
|
- `identifier` (string, optional): Accessibility identifier
|
|
|
|
**Example**:
|
|
```json
|
|
{"tool": "macax_find_elements", "args": {
|
|
"app_name": "Safari",
|
|
"role": "button"
|
|
}}
|
|
```
|
|
|
|
**Returns**:
|
|
```
|
|
Found 5 elements:
|
|
1. AXButton "Back" [id: BackButton]
|
|
2. AXButton "Forward" [id: ForwardButton]
|
|
3. AXButton "Reload" [id: ReloadButton]
|
|
4. AXButton "Share" [id: ShareButton]
|
|
5. AXButton "New Tab" [id: NewTabButton]
|
|
```
|
|
|
|
---
|
|
|
|
### macax_click
|
|
|
|
Click a UI element.
|
|
|
|
**Parameters**:
|
|
- `app_name` (string, required): Application name
|
|
- `identifier` (string, optional): Accessibility identifier
|
|
- `title` (string, optional): Element title
|
|
- `role` (string, optional): Element role
|
|
|
|
At least one of `identifier`, `title`, or `role` must be provided.
|
|
|
|
**Examples**:
|
|
|
|
```json
|
|
// Click by identifier (most reliable)
|
|
{"tool": "macax_click", "args": {
|
|
"app_name": "Calculator",
|
|
"identifier": "digit_5"
|
|
}}
|
|
|
|
// Click by title
|
|
{"tool": "macax_click", "args": {
|
|
"app_name": "Calculator",
|
|
"title": "5"
|
|
}}
|
|
|
|
// Click by role and title
|
|
{"tool": "macax_click", "args": {
|
|
"app_name": "Safari",
|
|
"role": "button",
|
|
"title": "Reload"
|
|
}}
|
|
```
|
|
|
|
---
|
|
|
|
### macax_set_value
|
|
|
|
Set the value of a UI element (text fields, sliders, etc.).
|
|
|
|
**Parameters**:
|
|
- `app_name` (string, required): Application name
|
|
- `identifier` (string, optional): Accessibility identifier
|
|
- `title` (string, optional): Element title
|
|
- `value` (string, required): Value to set
|
|
|
|
**Example**:
|
|
```json
|
|
{"tool": "macax_set_value", "args": {
|
|
"app_name": "TextEdit",
|
|
"role": "textArea",
|
|
"value": "Hello, World!"
|
|
}}
|
|
```
|
|
|
|
---
|
|
|
|
### macax_get_value
|
|
|
|
Get the current value of a UI element.
|
|
|
|
**Parameters**:
|
|
- `app_name` (string, required): Application name
|
|
- `identifier` (string, optional): Accessibility identifier
|
|
- `title` (string, optional): Element title
|
|
|
|
**Example**:
|
|
```json
|
|
{"tool": "macax_get_value", "args": {
|
|
"app_name": "Calculator",
|
|
"identifier": "display"
|
|
}}
|
|
```
|
|
|
|
**Returns**:
|
|
```
|
|
Value: 42
|
|
```
|
|
|
|
---
|
|
|
|
### macax_press_key
|
|
|
|
Simulate a key press.
|
|
|
|
**Parameters**:
|
|
- `key` (string, required): Key to press
|
|
- `modifiers` (array, optional): Modifier keys
|
|
|
|
**Supported modifiers**: `command`, `shift`, `option`, `control`
|
|
|
|
**Examples**:
|
|
|
|
```json
|
|
// Simple key press
|
|
{"tool": "macax_press_key", "args": {"key": "a"}}
|
|
|
|
// With modifiers (Cmd+S)
|
|
{"tool": "macax_press_key", "args": {
|
|
"key": "s",
|
|
"modifiers": ["command"]
|
|
}}
|
|
|
|
// Multiple modifiers (Cmd+Shift+N)
|
|
{"tool": "macax_press_key", "args": {
|
|
"key": "n",
|
|
"modifiers": ["command", "shift"]
|
|
}}
|
|
|
|
// Special keys
|
|
{"tool": "macax_press_key", "args": {"key": "return"}}
|
|
{"tool": "macax_press_key", "args": {"key": "escape"}}
|
|
{"tool": "macax_press_key", "args": {"key": "tab"}}
|
|
{"tool": "macax_press_key", "args": {"key": "delete"}}
|
|
```
|
|
|
|
**Special key names**:
|
|
- `return`, `enter`
|
|
- `escape`, `esc`
|
|
- `tab`
|
|
- `delete`, `backspace`
|
|
- `space`
|
|
- `up`, `down`, `left`, `right`
|
|
- `home`, `end`, `pageup`, `pagedown`
|
|
- `f1` through `f12`
|
|
|
|
## Common Roles
|
|
|
|
| Role | Description |
|
|
|------|-------------|
|
|
| `button` | Clickable button |
|
|
| `textField` | Single-line text input |
|
|
| `textArea` | Multi-line text input |
|
|
| `checkbox` | Checkbox control |
|
|
| `radioButton` | Radio button |
|
|
| `popUpButton` | Dropdown/popup menu |
|
|
| `slider` | Slider control |
|
|
| `table` | Table view |
|
|
| `list` | List view |
|
|
| `outline` | Outline/tree view |
|
|
| `group` | Container group |
|
|
| `window` | Application window |
|
|
| `sheet` | Modal sheet |
|
|
| `dialog` | Dialog window |
|
|
| `staticText` | Non-editable text |
|
|
| `image` | Image element |
|
|
| `scrollArea` | Scrollable container |
|
|
| `toolbar` | Toolbar |
|
|
| `menuBar` | Menu bar |
|
|
| `menu` | Menu |
|
|
| `menuItem` | Menu item |
|
|
|
|
## Best Practices
|
|
|
|
### 1. Use Accessibility Identifiers
|
|
|
|
When building apps you'll automate with G3, add accessibility identifiers:
|
|
|
|
**SwiftUI**:
|
|
```swift
|
|
Button("Submit") { ... }
|
|
.accessibilityIdentifier("submit_button")
|
|
```
|
|
|
|
**UIKit**:
|
|
```swift
|
|
button.accessibilityIdentifier = "submit_button"
|
|
```
|
|
|
|
**AppKit**:
|
|
```swift
|
|
button.setAccessibilityIdentifier("submit_button")
|
|
```
|
|
|
|
Identifiers are more reliable than titles (which may be localized).
|
|
|
|
### 2. Inspect Before Automating
|
|
|
|
Always inspect the UI tree first:
|
|
|
|
```json
|
|
{"tool": "macax_get_ui_tree", "args": {"app_name": "MyApp", "max_depth": 4}}
|
|
```
|
|
|
|
This helps you understand:
|
|
- Element hierarchy
|
|
- Available identifiers
|
|
- Correct role names
|
|
|
|
### 3. Activate App First
|
|
|
|
Some actions require the app to be frontmost:
|
|
|
|
```json
|
|
{"tool": "macax_activate_app", "args": {"app_name": "MyApp"}}
|
|
{"tool": "macax_click", "args": {"app_name": "MyApp", "identifier": "button1"}}
|
|
```
|
|
|
|
### 4. Handle Timing
|
|
|
|
UI updates may take time. If an element isn't found:
|
|
1. Wait briefly
|
|
2. Retry the operation
|
|
3. Check if the app state changed
|
|
|
|
### 5. Prefer Identifiers Over Titles
|
|
|
|
```json
|
|
// Good: Uses identifier
|
|
{"tool": "macax_click", "args": {"app_name": "MyApp", "identifier": "save_btn"}}
|
|
|
|
// Less reliable: Uses title (may be localized)
|
|
{"tool": "macax_click", "args": {"app_name": "MyApp", "title": "Save"}}
|
|
```
|
|
|
|
## Example: Automating Calculator
|
|
|
|
```json
|
|
// 1. Activate Calculator
|
|
{"tool": "macax_activate_app", "args": {"app_name": "Calculator"}}
|
|
|
|
// 2. Inspect UI
|
|
{"tool": "macax_get_ui_tree", "args": {"app_name": "Calculator", "max_depth": 3}}
|
|
|
|
// 3. Click "5"
|
|
{"tool": "macax_click", "args": {"app_name": "Calculator", "title": "5"}}
|
|
|
|
// 4. Click "+"
|
|
{"tool": "macax_click", "args": {"app_name": "Calculator", "title": "+"}}
|
|
|
|
// 5. Click "3"
|
|
{"tool": "macax_click", "args": {"app_name": "Calculator", "title": "3"}}
|
|
|
|
// 6. Click "="
|
|
{"tool": "macax_click", "args": {"app_name": "Calculator", "title": "="}}
|
|
|
|
// 7. Read result
|
|
{"tool": "macax_get_value", "args": {"app_name": "Calculator", "role": "staticText"}}
|
|
```
|
|
|
|
## Troubleshooting
|
|
|
|
### "Accessibility permission denied"
|
|
|
|
1. Check System Preferences → Security & Privacy → Accessibility
|
|
2. Ensure your terminal app is listed and checked
|
|
3. Restart the terminal after granting permission
|
|
|
|
### "Application not found"
|
|
|
|
1. Use exact app name (case-sensitive)
|
|
2. Run `macax_list_apps` to see available apps
|
|
3. App must be running
|
|
|
|
### "Element not found"
|
|
|
|
1. Inspect UI tree to verify element exists
|
|
2. Check identifier/title spelling
|
|
3. Element may be in a different window or sheet
|
|
4. App state may have changed
|
|
|
|
### "Cannot perform action"
|
|
|
|
1. Element may be disabled
|
|
2. App may need to be frontmost
|
|
3. Element may not support the action
|
|
4. Check element role supports the operation
|
|
|
|
### Slow Performance
|
|
|
|
1. Reduce `max_depth` in `macax_get_ui_tree`
|
|
2. Use specific identifiers instead of searching
|
|
3. Complex apps have large UI trees
|
|
|
|
## Comparison with Other Tools
|
|
|
|
| Feature | macax | Vision Tools | WebDriver |
|
|
|---------|-------|--------------|----------|
|
|
| Native apps | ✅ | ✅ (via OCR) | ❌ |
|
|
| Web browsers | ✅ | ✅ | ✅ |
|
|
| Electron apps | ✅ | ✅ | Partial |
|
|
| Reliability | High | Medium | High |
|
|
| Setup | Permissions | None | Driver |
|
|
| Speed | Fast | Slower | Medium |
|
|
|
|
**Use macax when**:
|
|
- Automating native macOS apps
|
|
- You control the app and can add identifiers
|
|
- Need reliable, fast automation
|
|
|
|
**Use Vision tools when**:
|
|
- App doesn't expose accessibility
|
|
- Need to find text visually
|
|
- Cross-platform approach needed
|
|
|
|
**Use WebDriver when**:
|
|
- Automating web content
|
|
- Need JavaScript execution
|
|
- Testing web applications
|