Documentation
¶
Overview ¶
Package macos groups the drivers that operate on the Mac's own apps.
Unlike iOS, there is no single CLI here that does everything: the accessibility tree and the menus come from one tool, the input from another, and the captures from the system itself. The router already knows how to split by capability and cost, so each driver declares what it does well and leaves the rest.
Index ¶
- Constants
- func CuaVideoFile(outPath string) string
- type Axcli
- func (d *Axcli) Cost(c drivers.Capability, _ drivers.Target) int
- func (d *Axcli) ID() string
- func (d *Axcli) Probe(_ context.Context, p drivers.Probe) drivers.HealthReport
- func (d *Axcli) Provides(target drivers.Target) drivers.CapabilitySet
- func (d *Axcli) Tap(ctx context.Context, target drivers.Target, spec drivers.TapSpec) (drivers.TapResult, error)
- func (d *Axcli) Type(ctx context.Context, target drivers.Target, spec drivers.TextSpec) error
- func (d *Axcli) Warm(_ context.Context, _ drivers.Target) <-chan error
- type Cua
- func (d *Cua) Cost(c drivers.Capability, _ drivers.Target) int
- func (d *Cua) DoubleTap(ctx context.Context, target drivers.Target, spec drivers.TapSpec) error
- func (d *Cua) Drag(context.Context, drivers.Target, drivers.DragSpec) error
- func (d *Cua) DragPath(context.Context, drivers.Target, drivers.DragPathSpec) error
- func (d *Cua) Erase(ctx context.Context, target drivers.Target, spec drivers.TextSpec) error
- func (d *Cua) HideKeyboard(context.Context, drivers.Target) error
- func (d *Cua) ID() string
- func (d *Cua) Pinch(context.Context, drivers.Target, drivers.PinchSpec) error
- func (d *Cua) Probe(ctx context.Context, p drivers.Probe) drivers.HealthReport
- func (d *Cua) Provides(target drivers.Target) drivers.CapabilitySet
- func (d *Cua) Rotate(context.Context, drivers.Target, drivers.RotateSpec) error
- func (d *Cua) Screenshot(ctx context.Context, target drivers.Target, spec drivers.ScreenshotSpec) error
- func (d *Cua) Swipe(ctx context.Context, target drivers.Target, spec drivers.SwipeSpec) error
- func (d *Cua) Tap(ctx context.Context, target drivers.Target, spec drivers.TapSpec) (drivers.TapResult, error)
- func (d *Cua) Tree(ctx context.Context, target drivers.Target, _ drivers.TreeSpec) (drivers.TreeResult, error)
- func (d *Cua) TwoFingerPan(context.Context, drivers.Target, drivers.TwoFingerPanSpec) error
- func (d *Cua) Type(ctx context.Context, target drivers.Target, spec drivers.TextSpec) error
- func (d *Cua) VideoStart(ctx context.Context, _ drivers.Target, spec drivers.VideoSpec) (drivers.VideoResult, error)
- func (d *Cua) VideoStop(ctx context.Context, _ drivers.Target, pid int) error
- func (d *Cua) W3CActions(context.Context, drivers.Target, []byte) error
- func (d *Cua) Warm(_ context.Context, _ drivers.Target) <-chan error
- type Screencapture
- func (d *Screencapture) Cost(c drivers.Capability, _ drivers.Target) int
- func (d *Screencapture) ID() string
- func (d *Screencapture) Probe(_ context.Context, p drivers.Probe) drivers.HealthReport
- func (d *Screencapture) Provides(target drivers.Target) drivers.CapabilitySet
- func (d *Screencapture) Screenshot(ctx context.Context, _ drivers.Target, spec drivers.ScreenshotSpec) error
- func (d *Screencapture) VideoStart(ctx context.Context, _ drivers.Target, spec drivers.VideoSpec) (drivers.VideoResult, error)
- func (d *Screencapture) VideoStop(ctx context.Context, _ drivers.Target, pid int) error
- func (d *Screencapture) Warm(_ context.Context, _ drivers.Target) <-chan error
- type System
- func (d *System) Boot(_ context.Context, _ drivers.Target) error
- func (d *System) ClipboardRead(ctx context.Context, _ drivers.Target) (string, error)
- func (d *System) ClipboardWrite(ctx context.Context, _ drivers.Target, text string) error
- func (d *System) Cost(drivers.Capability, drivers.Target) int
- func (d *System) ID() string
- func (d *System) Install(_ context.Context, _ drivers.Target, spec drivers.InstallSpec) error
- func (d *System) Launch(ctx context.Context, target drivers.Target, spec drivers.LaunchSpec) (drivers.LaunchResult, error)
- func (d *System) ListApps(ctx context.Context, _ drivers.Target) (string, error)
- func (d *System) OpenURL(ctx context.Context, _ drivers.Target, url string) error
- func (d *System) Probe(_ context.Context, p drivers.Probe) drivers.HealthReport
- func (d *System) Provides(target drivers.Target) drivers.CapabilitySet
- func (d *System) ResetLocation(context.Context, drivers.Target) error
- func (d *System) SetLocation(context.Context, drivers.Target, float64, float64) error
- func (d *System) Terminate(ctx context.Context, _ drivers.Target, bundleID string) error
- func (d *System) Uninstall(ctx context.Context, _ drivers.Target, bundleID string) error
- func (d *System) Warm(_ context.Context, _ drivers.Target) <-chan error
Constants ¶
const AxcliID = "axcli"
AxcliID is the driver's registry key.
const CuaID = "cua"
CuaID is the driver's registry key.
const ScreencaptureID = "screencapture"
ScreencaptureID is the registry key of the system capture driver.
const SystemID = "macsystem"
SystemID is the driver's registry key.
Variables ¶
This section is empty.
Functions ¶
func CuaVideoFile ¶ added in v0.13.0
CuaVideoFile is the file the daemon produces inside cuaVideoDir. Exported because the caller has to move it into place: VideoStop is handed a pid and nothing else, so the driver has no way to know where the run wanted the finished file.
Types ¶
type Axcli ¶
type Axcli struct {
// contains filtered or unexported fields
}
Axcli wraps axcli, which is in the mix for one reason only: it delivers events to the target process with CGEventPostToPid, without activating the app, without moving the cursor and without jumping Spaces. If an agent validates while you work, that is not a comfort detail: it is the difference between being able to use the Mac and not.
It stays as an escape hatch next to cua-driver, which is the canonical one. The reason is concrete: cua-driver resolves the window through `list_windows`, which only enumerates layer 0, so a floating UI, a panel, HUD, popover, an onboarding, is invisible to it. axcli targets by `--app` and needs no window id, so it reaches exactly those windows.
Its capture is NOT declared, and that is not an omission: it returned the desktop cropped to the window's dimensions, with no error, when the process has no graphical session, and it also activated the app. A plausible but false PNG is worse than having no capture.
func (*Axcli) Cost ¶
Cost is where the split with Peekaboo is expressed, with no new traits on the Driver interface.
Taps are cost 0: they go through CGEventPostToPid, axcli's default for click and scroll, and they do not steal focus.
Typing is NOT cost 0, and this is easy to get wrong: `input` and `fill` activate the app before typing, they do it in the code, with no flag to avoid it, so there axcli is no better than Peekaboo. It is declared equally expensive so the router does not prefer it believing it gains something.
func (*Axcli) Probe ¶
Probe checks the binary. axcli has no diagnostic command: it verifies AXIsProcessTrusted when starting any targeted command and dies with `error: accessibility not granted`. It is not invoked here on purpose, asking a real app for permission just to probe would have side effects, so the TCC state is reported by Peekaboo, which does know how to ask without touching anything.
func (*Axcli) Tap ¶
func (d *Axcli) Tap(ctx context.Context, target drivers.Target, spec drivers.TapSpec) (drivers.TapResult, error)
Tap clicks without stealing focus.
type Cua ¶
type Cua struct {
// contains filtered or unexported fields
}
Cua wraps cua-driver (trycua/cua, MIT), mav's canonical driver on macOS.
It is here for a structural reason, not preference: on macOS the Accessibility and Screen Recording permissions are granted ONLY to interactive GUI processes. A CLI cannot hold them no matter how much you grant them to the terminal. The only architecture that works is a broker, an app holding the permissions plus a socket, and cua-driver ships it built in: the binary we invoke lives inside /Applications/CuaDriver.app.
What it adds over what was there before, measured inside a VM against a floating window (layer != 0), which is the case that broke the others:
- Peekaboo enumerated that window and discarded it on its own because of the layer: no tree, no capture.
- axcli read the tree, but its capture returned the DESKTOP cropped to the window's dimensions, with no error, and it also activated the app.
- cua-driver returns a tree WITH geometry and a window capture with real content in the SAME call, and the click lands in the background.
Two known limits, both its own:
- `list_windows` is layer-0 only by declared design, so a floating window does not show up in discovery.
- its elements expose no AXIdentifier: only element_token (valid within the snapshot), role, label and frame.
func (*Cua) Cost ¶
Cost declares it canonical for everything it provides: it is the only one covering all four capabilities with verified background delivery.
func (*Cua) DoubleTap ¶ added in v0.15.0
DoubleTap double-clicks. cua-driver ships a dedicated `double_click` tool, and that is why this is not two Taps in a row: two separate `click` calls arrive as two single clicks — the event's clickCount never reaches 2 — and nothing that opens on a double click would react. The selector path goes by element token for the same reason Tap does: it lands on background windows without moving the cursor or stealing focus.
func (*Cua) Drag ¶ added in v0.15.0
Drag and DragPath are here because AdvancedGestureDriver is a whole interface, and they fail saying why. cua-driver does ship a `drag` tool; leaving it unwired is deliberate, not an oversight — no current flow needs a desktop drag, and shipping an unexercised gesture would claim a capability nothing has verified.
func (*Cua) Erase ¶
Erase empties a field.
It goes through set_value and not by typing deletions: writing the empty string leaves the field empty in one shot, while sending N Delete keystrokes depends on guessing how many, and on the field having focus, which is exactly what this driver avoids needing.
func (*Cua) HideKeyboard ¶
HideKeyboard does not exist on macOS: there is no on-screen keyboard to hide. It is declared because TextDriver is a whole interface, and it does nothing instead of failing, because a flow shared between iOS and Mac will call it and here it is simply unnecessary.
func (*Cua) Pinch ¶
Pinch, Rotate, TwoFingerPan and W3CActions exist because GestureDriver is a whole interface, and they fail saying why.
It is not a gap to fill later: a trackpad sends gestures to the system and to the focused app, not to a PID, so reproducing them in the background against a specific window is not a matter of effort but of there being no path. A message that says so saves the search.
func (*Cua) Probe ¶
Probe asks the daemon about permissions, not the process running mav.
That is the difference that matters: `permissions status` answers with CuaDriver's identity (com.trycua.driver) because it is its own responsible process. With no daemon it answers `unknown` instead of lying with your terminal's permissions, which is why that case is reported as degraded and not as healthy.
func (*Cua) Screenshot ¶
func (d *Cua) Screenshot(ctx context.Context, target drivers.Target, spec drivers.ScreenshotSpec) error
Screenshot writes the window capture.
It comes from the same get_window_state as the tree, in base64, so image and tree describe the SAME instant, which for evidence accompanying a tree is exactly what you want, and there is no second invocation that could catch the screen already changed.
func (*Cua) Swipe ¶
Swipe scrolls the window's content.
On a Mac a swipe IS a scroll: there is no finger, there is a wheel. The direction is inverted on purpose, swiping up on a phone moves the content up, which on desktop is requested as scrolling down, so a flow written once means the same on both platforms.
func (*Cua) Tap ¶
func (d *Cua) Tap(ctx context.Context, target drivers.Target, spec drivers.TapSpec) (drivers.TapResult, error)
Tap clicks without bringing the app to the front.
It is two calls and not one on purpose: the tool demands a fresh snapshot before every per-element action and invalidates the index map as soon as you take another. Reusing an old snapshot is exactly how you end up clicking the wrong thing.
func (*Cua) Tree ¶
func (d *Cua) Tree(ctx context.Context, target drivers.Target, _ drivers.TreeSpec) (drivers.TreeResult, error)
Tree returns the window's accessibility tree.
func (*Cua) TwoFingerPan ¶
func (*Cua) VideoStart ¶ added in v0.13.0
func (d *Cua) VideoStart(ctx context.Context, _ drivers.Target, spec drivers.VideoSpec) (drivers.VideoResult, error)
VideoStart brings the daemon up, then holds a session open for it.
func (*Cua) VideoStop ¶ added in v0.13.0
VideoStop finalizes the mp4 before anything kills the session holding it.
It asks the daemon to stop rather than just signalling the holder, because only the daemon can write the mp4's index; a file cut off without it is a plausible-looking mp4 no player will open, which is worse than no video. Stopping is documented as unconditional, so it does not matter which session started the recording.
type Screencapture ¶
type Screencapture struct {
// contains filtered or unexported fields
}
Screencapture wraps the `screencapture` that ships with macOS. It is the last resort for CapScreenshot: it captures the whole screen, which as evidence of a specific app is worse than a capture bounded to its window. A driver that can resolve the window id must declare a lower cost and beat it.
func NewScreencapture ¶
func NewScreencapture(exec drivers.Executor) *Screencapture
NewScreencapture builds the driver.
func (*Screencapture) Cost ¶
func (d *Screencapture) Cost(c drivers.Capability, _ drivers.Target) int
Cost places screencapture as an acceptable fallback, not the canonical path: whole screen instead of window.
func (*Screencapture) ID ¶
func (d *Screencapture) ID() string
func (*Screencapture) Probe ¶
func (d *Screencapture) Probe(_ context.Context, p drivers.Probe) drivers.HealthReport
Probe checks that the system binary is where it should be. It cannot check the Screen Recording permission: that belongs to the parent process (the terminal or the agent's harness), not to mav, and there is no cheap way to ask without attempting a real capture.
func (*Screencapture) Provides ¶
func (d *Screencapture) Provides(target drivers.Target) drivers.CapabilitySet
Provides only declares capabilities on the Mac: on the simulator and on device there are better paths already covered.
func (*Screencapture) Screenshot ¶
func (d *Screencapture) Screenshot(ctx context.Context, _ drivers.Target, spec drivers.ScreenshotSpec) error
Screenshot captures the screen. `-x` silences the shutter sound, which in an automated session is only noise.
func (*Screencapture) VideoStart ¶
func (d *Screencapture) VideoStart(ctx context.Context, _ drivers.Target, spec drivers.VideoSpec) (drivers.VideoResult, error)
VideoStart begins a recording. screencapture -v records until it is sent SIGINT, which is how VideoStop stops it.
type System ¶
type System struct {
// contains filtered or unexported fields
}
System covers the lifecycle and utilities of a macOS app with the tools the system itself ships. It wraps no third-party CLI because none is needed: `open`, `pbcopy`, `pbpaste` and the filesystem are enough.
It exists above all for one concrete reason: without a CapTerminate provider on the Mac, closing the app before seeding a fixture was a silent no-op, and the fixture would write the database while the previous instance kept it open.
func (*System) ClipboardRead ¶
func (*System) ClipboardWrite ¶
func (*System) Install ¶
Install on macOS copies nothing to /Applications: to validate, the app is run from wherever the build left it. "Installing" here is checking that the bundle is where we say, which is the only part that can fail and the one that gives a useful error when the launch recipe did not produce what it believed.
func (*System) Launch ¶
func (d *System) Launch(ctx context.Context, target drivers.Target, spec drivers.LaunchSpec) (drivers.LaunchResult, error)
Launch runs the binary inside the bundle instead of using `open`.
It is not a whim: `open` does not propagate environment variables to the process it starts, and the environment is exactly how mav injects its configuration (the equivalent of the simulator's SIMCTL_CHILD_*). Running Contents/MacOS/<binary> directly inherits everything.
func (*System) Probe ¶
Probe needs to check nothing installable: these are system binaries. What it does check is that we are on macOS, because a mac target on another system is not a recoverable failure but an impossible config.
func (*System) ResetLocation ¶
func (*System) SetLocation ¶
SetLocation and ResetLocation have no equivalent: macOS does not allow overriding the location of an already launched app. The path that does exist is an Xcode scheme's "Simulate Location", which happens at launch and not here.
func (*System) Terminate ¶
Terminate closes the app. `osascript quit` asks for a clean shutdown, which is what lets the app close its database instead of leaving the WAL half-written, exactly what the fixture needs before seeding.