ARexx Reference¶
AmiPilotServer is a small commodity that hosts AmiPilot's action engine
(genuine input.device click/type synthesis) and the intuition-model
walker behind a public ARexx port — so an ARexx script running on the
same Amiga can locate and drive another program's GUI. No host
machine, transport, or emulator involved.
Starting it¶
> Run SRC:AmiPilotServer
(or wherever you've copied it — see Installation). It
opens an ARexx port named AMIPILOT.1 (or the next free slot —
AMIPILOT.2, AMIPILOT.3, ... — if more than one instance is running)
and prints the name it actually got:
AmiPilotServer: ARexx port AMIPILOT.1 ready
It has no window of its own; it just sits waiting for ARexx commands
until it receives QUIT or a break signal (Ctrl-C).
Commands¶
Every command's first argument is a window-title substring — the
same matching AmiInspect WINDOW= uses, across every screen, first
match wins. Quote it if it contains spaces. TREE/CLICK/TYPE/
GETTEXT/MENU/MENUPICK's classic form additionally accept an
optional leading SCREEN=<substring> token right after the command
keyword, narrowing the search to screens whose own name contains it —
for disambiguating two same-titled windows on different screens; see
Screens.
| Command | Arguments | What it does |
|---|---|---|
TREE |
<window-pattern> |
Returns the matched window's full gadget tree as a multi-line string, in the same format AmiInspect prints (RESULT gains embedded newlines — most REXX interpreters handle that fine in a variable). |
CLICK |
<window-pattern> (<gadget-id> \| ROLE=<r> [LABEL=<l>] [INDEX=<n>]) [EXPECT=WINDOW=<pattern> \| EXPECT=NOWINDOW] [TIMEOUT=<n>] |
Clicks the target gadget — a genuine input.device click, not a shortcut. Either a numeric GA_ID, or a tier-2 locator below. Optional EXPECT= composes the click atomically with a server-side wait — see Wait/expectation primitives. |
TYPE |
<window-pattern> (<gadget-id> \| ROLE=<r> [LABEL=<l>] [INDEX=<n>]) <text...> |
Clicks the gadget (to focus it), then types text into it via real IECLASS_RAWKEY events, human-paced. Everything after the locator is taken verbatim as the text — no quoting needed unless the text itself starts with ". |
GETTEXT |
<window-pattern> (<gadget-id> \| ROLE=<r> [LABEL=<l>] [INDEX=<n>]) |
Returns that gadget's current text: a string/integer gadget's live value if it has one, otherwise its label. |
MANIFEST |
<file-path> |
Loads an application's manifest (see below). Replaces any previously loaded one. RESULT reports what loaded (loaded GTApp: 1 windows, 3 gadgets); a rejected manifest returns RC=10 with the reason (including line number) in RESULT. |
VERSION |
(none) | Returns the server version, wire-protocol number, and the stable/experimental verb lists (multi-line, same payload the wire handshake uses) — for feature-testing from a script. |
LAUNCH |
[STACK=<n>] <command-line...> |
Starts command-line as an AmigaDOS process (asynchronous — the commodity keeps servicing the port while it runs). STACK sets the new process's stack in bytes (default 4000, AmigaDOS's own CreateNewProc() default — most Intuition/ReAction GUI apps need more). RC=0 means the process itself could be created, not that the command was found or ran successfully; assert on the expected effect (a window appearing) instead. See Wire Protocol for the full contract and its honest limits. |
WBLAUNCH |
<icon-path> [TOOLTYPE=<key>=<value> ...] [ARG=<path> ...] |
Launches <icon-path> (WITHOUT ".info") as if its icon had been double-clicked — a real, hand-built Workbench-style start (genuine WBStartup/WBArg message), not LAUNCH's Shell-style one. TOOLTYPE= overrides (or adds) one tooltype for this launch only, everything else on the real icon left alone; ARG= adds further project-file arguments. Same async honesty as LAUNCH: RC=0 means the process was created and the startup message queued, not that it finished starting. See Wire Protocol for the full mechanism. |
SCREENSHOT |
[SCREEN=<substring>] [WINDOW=<pattern>] |
Raw, uncompressed bitmap capture (planar, or Picasso96/RTG when a real P96 board is present — optional, never required, see issue #44) — inspector tooling for human viewing/debugging, not a locator mechanism. With both omitted, captures the frontmost screen; WINDOW= captures that window's owning screen in full, plus its rectangle for cropping (the host client crops to just that window by default). Host-side (amipilot.screenshot) turns the raw capture into a real .iff (IFF ILBM) and .png — true-colour PNG (no palette) for a P96 truecolor/hicolor capture, which has no ILBM equivalent. See Wire Protocol for the full payload layout and serial transfer-time estimates. |
FSLIST |
<path> |
Lists a directory's entries (name, file/dir, size, protection, date, comment). Only works inside a root granted at startup — see File API. |
FSSTAT |
<path> |
The metadata for a single file or directory, without listing its contents. |
FSMKDIR |
<path> |
Creates a directory. Its parent must already exist and be inside a granted root. |
FSDELETE |
<path> |
Deletes a file or empty directory. |
FSGET |
<path> |
Returns a file's full contents (RESULT is the raw bytes, capped at the server's own small internal buffer — a test-staging channel, not a file manager). FSPUT (the opposite direction) is not listed here: it's wire-only, not answerable over ARexx at all — see File API. |
SCREENS |
(none) | Lists every open screen: title, position, size, and whether it's frontmost. Title is each screen's own name (DefaultTitle), not the live title-bar text a window's WA_ScreenTitle can override. |
PICK |
[SCREEN=<substring>] |
Interactive "pick mode" discovery: hit-tests the LIVE global pointer position against SCREEN='s windows (the frontmost screen if omitted) and returns the same TREE-shaped window line, plus (if any) a single gadget line for whichever gadget under the pointer also contains it — point at a gadget, get back its exact locator, no batch TREE dump required. RC=5 if no window on the target screen contains the point at all. A window hit with no gadget hit is still RC=0 (the window line alone — chrome/background, not an error). AmiInspect PICK is the standing-at-the-machine equivalent with no ARexx/wire session at all. See Wire Protocol. |
MENU |
<window-pattern> |
Returns the matched window's full menu strip — every pulldown menu, its items, and (one level deep) their submenu items, with checkit/checked/enabled state and any keyboard shortcut, in the same text shape AmiInspect prints. |
MENUPICK |
<window-pattern> <menu-num> <item-num> [<sub-num>] |
Selects a menu item — the numbers are the same 0-based chain positions MENU's own output reports. Items with a keyboard shortcut are picked via Right-Amiga + the shortcut character; items with none are picked via a genuine synthesized RMB-down/move/RMB-up sequence instead, automatically. RC=20 if the item is disabled, or (pointer path only) if the window traps the right mouse button (WFLG_RMBTRAP — no synthesized RMB-down can ever open its menu). See Wire Protocol for the full contract. |
DRAG |
<window-pattern> <locator> <dx> <dy> or <window-pattern> <locator> TO (<dest-gadget-id> \| @<dest-name>) |
A genuine press/move/release drag. The offset form (<dx> <dy>) moves the gadget's current center by a pixel delta — the natural shape for a slider/scroller. The TO form drags onto a second gadget's center instead, both resolved live, for drag-and-drop/reorder — the destination must be in the same window as the source. <locator> is the same numeric GA_ID, ROLE=/LABEL=/INDEX=, or @name form CLICK/TYPE/GETTEXT accept. |
WINDOWMOVE |
[SCREEN=<s>] <window-pattern> <dx> <dy> |
Moves the WHOLE window by a pixel offset — a genuine title-bar drag (WFLG_DRAGBAR), built on the same primitive DRAG's gadget forms use. Classic locator form only, no @name — a window-level action, same scope as TREE/MENU. RC=20 ("window has no drag bar") if the window never had one. No separate "get position" verb — TREE's own [left,top WxH] header already carries it. |
WINDOWSIZE |
[SCREEN=<s>] <window-pattern> <width> <height> |
Resizes the WHOLE window to an ABSOLUTE target size — a genuine sizing-gadget drag (WFLG_SIZEGADGET) from its current bottom-right corner. Doesn't pre-check against the window's own min/max — Intuition clamps the drag as it would a real one; confirm the actual result with a follow-up TREE. RC=20 ("window has no sizing gadget") if the window never had one. Same classic-form-only scope as WINDOWMOVE. |
WAITFOR |
[SCREEN=<s>] WINDOW=<pattern> [TIMEOUT=<n>] or [SCREEN=<s>] NOWINDOW=<pattern> [TIMEOUT=<n>] or [SCREEN=<s>] <window-pattern> (<gadget-id> \| ROLE=<r> [LABEL=<l>] [INDEX=<n>]) TEXT=<value> [TIMEOUT=<n>] or @<name> TEXT=<value> [TIMEOUT=<n>] or [SCREEN=<s>] REQUESTER [TIMEOUT=<n>] |
Polls (server-side, one round trip) until a window matching <pattern> appears, until none does, until a gadget's text exactly equals <value>, or until a genuine Intuition Requester appears (window-attached only — issue #52; once detected, its own gadgets are reachable via ordinary CLICK against the same window pattern, no separate verb needed — see below). TIMEOUT defaults to 10 seconds. RC=15 if the condition never becomes true in time — a new RC distinct from "nothing matched" (RC=5) or "the action itself failed" (RC=20). See Wait/expectation primitives. |
AUTH |
<password> |
Authenticates a TCP connection (no effect on ARexx or serial.device, which have their own implicit trust boundaries). Until it succeeds, the TCP transport refuses every command except VERSION/AUTH/QUIT with RC=10. See Securing TCP. |
MUIREXX |
<app-base> [TIMEOUT=<n>] <command...> |
Sends <command> verbatim to a MUI application's own ARexx port. The MUI-ARexx bridge tier — see Driving MUI applications. |
WHERE |
@<name> [TIMEOUT=<n>] |
Diagnostic query of the cooperative geometry port: returns <name>'s current "<x> <y> <w> <h>" geometry from the application's own declared WHEREPORT. Manifest-only, WHEREGADGET names only — see Driving layout.gadget-only applications. |
QUIT |
(none) | Shuts the commodity down cleanly. |
The same command set is also reachable from a host machine over serial.device — see Wire Protocol.
Manifest locators¶
If the application you're driving ships an
AmiPilot manifest
— a small text file mapping stable logical names to its window titles
and GA_IDs — load it with MANIFEST, and CLICK/TYPE/GETTEXT
then accept @<logical-name> in place of the
<window-pattern> <gadget-id> pair:
'MANIFEST Prog:MyApp.manifest'
'TYPE @host_field aminet.net'
'CLICK @connect_button'
No GA_ID, window title, or position appears in the script at all —
the manifest pins the identity of each target, and the actual window
and gadget are still located live at action time. Relayout, relabelling,
and translation can't break the script; only a GA_ID change would
touch anything, and then only the manifest.
Using @name with no manifest loaded, or with a name the manifest
doesn't define, is RC=10 with the reason in RESULT.
A format-version-2 manifest can also declare a name via WHEREGADGET
instead of GADGET — for a gadget structural walking can never reach
at all (a layout.gadget child on classic OS 3.x). CLICK @name/
TYPE @name work exactly the same either way; see Driving
layout.gadget-only applications
for what's different underneath and GETTEXT/DRAG's own honest
limit against such a name.
Tier-2 semantic locators¶
For an application with no manifest, CLICK/TYPE/GETTEXT also
accept a locator by role and label text in place of the bare
GA_ID — one or more of ROLE=<name>, LABEL=<substring>, and
INDEX=<n>, at least one of ROLE=/LABEL= required:
'CLICK GadTools ROLE=button LABEL=Connect'
'GETTEXT GadTools ROLE=string LABEL=Host'
ROLE= matches the same role name TREE's own output prints for a
gadget (button, string, checkbox, slider, ... — case doesn't
matter). LABEL= is a case-sensitive substring match against the
gadget's label, quoted the same way a window pattern is if it
contains a space (LABEL="Connect Now") — same convention window/
screen patterns already use, not a separate rule to learn. INDEX=
(0-based, default the first match) picks between several gadgets that
share a role and/or label, e.g. ROLE=button INDEX=1 for the second
button in a window.
This is honest and useful, but fragile against relabelling in a way
@name isn't — prefer a manifest when the target application ships
one. No match (nothing with that role/label, or INDEX= past the
last match) is RC=5, the same class an unmatched GA_ID already
uses.
Wait/expectation primitives¶
A naive "click, then immediately check" ordering can race — the app
hasn't necessarily processed the click's event yet by the time your
next command runs. WAITFOR and CLICK's own EXPECT= close that
race by polling server-side, in the same request, instead of the
script polling over and over from the host:
'WAITFOR WINDOW=Preferences TIMEOUT=15'
'CLICK GadTools 1 EXPECT=WINDOW=Async TIMEOUT=15'
'CLICK GadTools 1 EXPECT=NOWINDOW'
WAITFOR [SCREEN=<substring>] WINDOW=<pattern> [TIMEOUT=<n>] waits
for a window matching <pattern> to appear; NOWINDOW=<pattern>
waits for none to match. TIMEOUT (seconds) defaults to 10.
CLICK additionally accepts a trailing EXPECT=WINDOW=<pattern> or
bare EXPECT=NOWINDOW (no argument) — the click still always happens
either way. The two NOWINDOW forms mean subtly different things,
deliberately: CLICK ... EXPECT=NOWINDOW means "the window this
click itself just acted on has closed" — precise, because the server
already knows exactly which window that was. WAITFOR NOWINDOW=<x>
has no click to anchor to, so it means "no window currently matches
<x>" — a fresh search each time, which in principle a different,
similarly-titled window could satisfy without the original ever
having closed. Use CLICK ... EXPECT=NOWINDOW when you want "the
thing I just clicked away is gone," and WAITFOR NOWINDOW=<pattern>
when there's no click to anchor to at all.
A condition that never becomes true in time is RC=15 — a distinct
RC from "nothing matched at all" (RC=5) or "the action's own
input.device injection failed" (RC=20), since none of those mean the
same thing to a test author deciding how to react.
WAITFOR also has a third condition, for waiting on a gadget's
text rather than a window:
'CLICK GadTools 4'
'WAITFOR GadTools 2 TEXT="cancel clicked" TIMEOUT=15'
'WAITFOR @host_field TEXT="hello wire" TIMEOUT=15'
WAITFOR [SCREEN=<s>] <window-pattern> (<gadget-id> | ROLE=<r>
[LABEL=<l>] [INDEX=<n>]) TEXT=<value> [TIMEOUT=<n>] (or WAITFOR
@<name> TEXT=<value>) polls the same gadget-text GETTEXT would read
until it exactly equals <value> — not a substring match, since a
partial match could be satisfied by an intermediate state on the way
to the final one (e.g. "loading..." matching before "loaded" does).
This condition is WAITFOR-only, not available on CLICK's
EXPECT=, because the gadget whose text changes as a result of a
click is often a different gadget than the one clicked.
WAITFOR also has a fourth condition, for detecting an Intuition
Requester (GitHub issue #52's original "cheap first step" — a way to
know a modal requester appeared):
'CLICK GadTools 7'
'WAITFOR REQUESTER TIMEOUT=10'
WAITFOR [SCREEN=<s>] REQUESTER [TIMEOUT=<n>] polls until ANY
currently-open window (optionally narrowed by SCREEN=) shows a
genuine Intuition Requester, or TIMEOUT (default 10s) elapses. No
pattern argument — a requester generally has no title of its own to
match against, so "any requester, anywhere" (or on a matching screen)
is the right granularity for a detection-only primitive.
Window-attached Requesters only, by design: a system-wide
Requester with no owning window (a disk-swap prompt, a DOS error, a
Guru) is a genuinely harder detection problem — BuildSysRequest()'s
own autodoc says these can come back as a standalone window, but
reliably telling that apart from an ordinary app window needs more
research than this pass covers. Confirmed live against a real,
AutoRequest()-triggered requester (fixtures/gadtools-app,
tests/copperline/run.sh's run_requester_check): detection does
NOT rely on window->FirstRequest alone (checked, but AutoRequest()/
BuildSysRequest()/EasyRequest() never populate it when given a
real owning window on this project's target OS/ROM — they open a
genuinely separate window instead, confirmed against
BuildSysRequest()'s own autodoc text: "a new window is opened in the
same screen as the one containing your window") — the actual signal
is that separate window sharing its owning window's exact title text,
which no ordinary well-behaved app window does on its own. Full
technical story in WaitForRequesterPresent()'s own comment,
server/src/amipilotserver/main.c.
Acting on a window-owned requester needs no new verb (issue #52
follow-up): since it's a genuinely separate struct Window sharing
its owner's exact title, ordinary CLICK <window-pattern> <gadget-id>
already reaches it. BuildSysRequest()'s own autodoc documents a
fixed, app-independent GadgetID convention: the positive/"Yes"
gadget is always GadgetID TRUE (1), the negative/"No" gadget is
always FALSE (0) — so CLICK <same-pattern> 1 reliably dismisses
ANY window-owned system requester with the positive choice:
'CLICK GadTools 7'
'WAITFOR REQUESTER TIMEOUT=10'
'CLICK GadTools 1'
Confirmed live (tests/copperline/requester-test.py's
REQUESTER-YES-CLICKED/REQUESTER-DISMISSED checks) — a real dismiss,
verified by WAITFOR REQUESTER correctly timing out again afterward.
System-wide requesters (no owning window) work too, the same way
(BuildSysRequest(NULL, ...) — exactly what dos.library calls
internally for a real disk-swap "Please insert volume..." prompt or a
DOS error requester): confirmed live to produce a window titled
EXACTLY "System Request" — Intuition's own documented fallback
title when no owning window (or explicit title) is given, not a
coincidence:
'CLICK GadTools 9'
'WAITFOR REQUESTER TIMEOUT=10'
'CLICK "System Request" 1'
Same fixed GadgetID convention, same plain CLICK mechanism — no
special casing needed once you know the title. English-locale
specific (like every window/screen title this project already treats
as Locale-sensitive), and a real app titling its own window exactly
"System Request" would false-positive here — an accepted,
exceedingly unlikely collision.
GETTEXT cannot read a Requester's body text — confirmed as a
permanent limit, not left open. struct Requester->ReqText (a
struct IntuiText *) is where BuildSysRequest()'s own autodoc says
the body text ends up — but that field lives on a struct Requester
this project confirmed (2026-08-09) genuinely does not exist anywhere
reachable here: dumping FirstRequest/ReqCount for every open
window while a real AutoRequest() was up showed both NULL/0 on the
owning window (blocked inside AutoRequest() at the time) AND the
requester's own separate window. The text is rendered directly at
open time with no structural field retaining it — GETTEXT has
nothing to query, the same shape as other confirmed structural-reading
limits (a PLACETEXT_IN button's baked-in label).
Scope today: WINDOW=/NOWINDOW=/TEXT=/REQUESTER conditions
are understood, and only CLICK composes with EXPECT= (WINDOW=/
NOWINDOW= only, not TEXT=/REQUESTER) — TYPE/DRAG/MENUPICK
callers, and anyone wanting a TEXT=/REQUESTER wait, use a separate
WAITFOR call instead.
Driving MUI applications¶
MUI (Magic User Interface) is a third-party AmigaOS GUI toolkit whose
internals are opaque to TREE/CLICK/TYPE/GETTEXT — the same limit
a window.class window's layout.gadget children already have (see
Locator Tiers and Limits), just for a
different reason. Every MUI application carries an automatic ARexx port
of its own, though, so MUIREXX <app-base> [TIMEOUT=<n>] <command...>
drives it through that instead — a different mechanism entirely, sent
straight to the target application, not resolved by AmiPilotServer
against any window/gadget model:
'MUIREXX MUIDEMO info title'
SAY 'Target is: 'RESULT
'MUIREXX MUIDEMO quit'
<app-base> is the target application's MUIA_Application_Base (its
own chosen ARexx port name, e.g. "MUIDEMO" for MUI's own demo app) —
MUIREXX tries it verbatim first, then <app-base>.1, since both are
real conventions in use. RC=5 if neither is found.
Be honest about what this reaches. MUI's own built-in ARexx support
is a small, universal set — quit, hide, show, activate,
deactivate, info <item> (fixed metadata: title/author/version/base/
screen/copyright/description), and help [file] (lists every command
the target understands, confirmed live as the way to find out what's
really there rather than guessing) — confirmed against AmigaOS 3.2's
own MUI:Demos/MUI-Demo. There is no generic "read or set this
widget's value" command the way a numeric GA_ID or a tier-2
ROLE=/LABEL=/INDEX= locator reaches a classic gadget — MUI-Demo
itself registers zero commands beyond that universal set. Anything
richer is entirely up to the target application having added its own
commands; MUIREXX passes whatever you send through unchanged rather
than inventing capability a given app doesn't have. quit is the one
command every MUI app answers, and is genuinely useful on its own —
composing a script's teardown the same "exit via the app's own
affordances" way this project's other verbs already do.
The target's own reply code (an arbitrary, app-defined value this
bridge relays rather than reinterprets) surfaces as RC=10 with the
code prefixed onto RESULT when nonzero; RC=15 if the target never
replied within TIMEOUT (default 10 seconds); RC=20 only if
AmiPilotServer itself couldn't allocate the ARexx message (its own
resource problem, not the target's).
Driving layout.gadget-only applications¶
A window.class window attaches only its single top-level layout
object to window->FirstGadget — its own button/string/checkbox
children aren't individually walkable, and classic OS 3.x has no
public API to enumerate them (see Locator Tiers and
Limits). A plain GADGET manifest entry
can't name such a gadget; a WHEREGADGET entry can, if the
application itself cooperates.
An application implementing this (issue #49) exposes a small,
optional ARexx port answering WHERE <name> with that gadget's own
live geometry — it already holds the object pointer for its own event
dispatch, so it just reads GetAttr(GA_Left/GA_Top/GA_Width/
GA_Height) and reports back. Its manifest declares the port's name
with WHEREPORT:
MANIFEST 2
APP MyApp
WHEREPORT MYAPP.WHERE
WINDOW main "My App"
WHEREGADGET connect_button main
Once that manifest is loaded, CLICK @connect_button/
TYPE @connect_button ... work exactly like they would for a plain
GADGET name — AmiPilot queries the port for the gadget's current
geometry, then clicks it with a genuine input.device event. Only
discovery is cooperative; the click is real input, unlike MUIREXX
above, where the target's own port does the acting too. No coordinate
ever appears in the script — it's resolved live, at action time, so
relayout and font changes can't break anything.
WHERE @<name> [TIMEOUT=<n>] is the standalone diagnostic form —
useful for confirming a third-party port answers correctly, or for
asserting on geometry directly:
'WHERE @connect_button'
SAY 'RESULT is "x y w h": 'RESULT
GETTEXT/DRAG have no path through a WHEREPORT — a WHEREGADGET
name given to either is RC=10 with an explicit "geometry only"
message, an honest stated limit rather than a silent fallback.
RC=5 if the declared WHEREPORT doesn't exist (checked by its
exact declared name — no .1-slot fallback the way MUIREXX's
MUI-specific convention gets); RC=10 if the port itself reports the
name unknown, or its reply doesn't parse as exactly four integers;
RC=15 if it never replies within TIMEOUT (default 10 seconds).
Full contract for application authors: manifest/SPEC.md's "The
cooperative geometry port"
section.
Example¶
/* rexx */
OPTIONS RESULTS
ADDRESS 'AMIPILOT.1'
'CLICK GadTools 2'
'TYPE GadTools 2 hello amipilot'
'GETTEXT GadTools 2'
SAY 'Host field now reads: 'RESULT
'CLICK GadTools 1'
'QUIT'
Run it with rx myscript.rexx (rx is the standard AmigaOS ARexx
launcher; AmiPilotServer needs a real resident RexxMast to talk to,
same as any other ARexx-scriptable application). OPTIONS RESULTS is
required — without it ARexx never asks the host for a RESULT string at
all.
Return codes¶
Same convention this project's sibling tools use for their own ARexx ports:
RC |
Meaning |
|---|---|
0 |
Success. |
5 |
Warning — the window or gadget wasn't found. The command was well-formed; there was just nothing to act on. |
10 |
Error — unknown command, or a required argument was missing. |
15 |
Timeout — WAITFOR/CLICK ... EXPECT='s own awaited condition (or FSPUT's payload) never arrived within TIMEOUT. Distinct from 5 (nothing matched at all) and 20 (the action itself failed) — the command was accepted and, for CLICK, the action DID happen; the expected effect just didn't show up in time. |
20 |
Failure — the action itself didn't deliver (the underlying input.device event injection failed). |
What it needs open¶
AmiPilotServer always opens intuition.library (V37+; fails outright
without it), input.device, and rexxsyslib.library (fails outright
without it — there's no point running with no ARexx port).
gadtools.library and keymap.library are opened opportunistically:
without the former, the same button/checkbox discrimination gap
AmiInspect has applies; without the latter, TYPE fails outright
(there's no way to synthesize keypresses without it) but every other
command still works.
Next steps¶
See Locator Tiers and Limits for what a
window-pattern/gadget-id pair can and can't reach today (the same
structural limits AmiInspect documents apply here too — a gadget has
to be visible to the walker before AmiPilotServer can click or type
into it).