Locator Tiers and Limits¶
AmiPilot's stated design principle is honest best-effort: read what's
genuinely readable, and say plainly what isn't, rather than guess. This
page is the up-to-date, plain-language version of that promise —
covering both what AmiInspect can see and what AmiPilotServer's
CLICK/TYPE/GETTEXT can act on, since a gadget invisible to one is
unreachable by the other.
Which tier reaches which toolkit, and why¶
The implementation plan
defines four locator tiers: 1 (manifest logical names — or a
community-authored quirk profile,
same format), 2 (semantic — window pattern + role/GA_ID), 3
(the MUI-ARexx bridge), and 4 (raw coordinates, the fragile floor).
Between 1 and 2 sits a fifth, cooperative variant of tier 1 (issue
49): a WHEREGADGET manifest entry, resolved by querying an ARexx¶
port the target application itself exposes for a gadget's live
geometry, rather than a GA_ID — see ARexx Reference.
This table is the honest answer to "which tier actually reaches my
application's UI today":
| UI built from | Reaches | Why |
|---|---|---|
Plain GadTools gadgets (button/checkbox/string/integer/slider), top-level BOOPSI/ReAction gadgets attached directly to a window (button.gadget, string.gadget, etc., not nested inside a layout) |
Tier 1 with a manifest/quirk profile, or Tier 2 without one | Fully classified and structurally reachable — see "What's classified today" below. |
A window.class + layout.gadget window's nested button/string/checkbox children, where the application implements a WHERE port |
Tier 1 (cooperative) — a WHEREGADGET manifest entry |
The confirmed layout.gadget limit below — invisible to structural walking, so plain tiers 1–3 can't name them at all (not even via a quirk profile, since there's no GA_ID to record). The application's own cooperation closes the gap: it reports its own gadgets' geometry live, and AmiPilot still does the actual clicking with real input.device input. |
The same nested children, where the application does not implement a WHERE port |
Tier 4 only (raw coordinates) | Nothing changes without the target's own cooperation — a third party can't retrofit a WHERE port onto a binary that doesn't offer one, the same way a quirk profile can't retrofit a GA_ID onto an unreachable gadget. |
| MUI applications | Tier 3 (MUIREXX) for whatever the app's own ARexx commands cover, Tier 4 for anything else |
intuition-model's class-name walker has no MUI recognition, so Tier 2 doesn't reach MUI gadgets at all — confirmed live that a MUI window attaches only its own single top-level object to window->FirstGadget, the same limit window.class/layout.gadget has. Tier 3 (see ARexx Reference) drives through the ARexx port every MUI app carries automatically, but MUI's own built-in support there is a small, universal set (quit/hide/show/activate/deactivate/info/help) — confirmed against AmigaOS 3.2's own MUI-Demo, which registers zero commands beyond it. Genuine per-widget interaction needs the target application to have added its own ARexx commands; MUIREXX passes them through, it can't invent them. |
| Custom-rendered UIs (games, hand-rolled bitmap rendering) | Tier 4 only (raw coordinates) | Out of scope by definition — nothing structural exists for a walker to find, and there's no ARexx port to assume. |
What's classified today¶
Plain GadTools gadgets (BOOLGADGET/STRGADGET/PROPGADGET):
STRING_KIND→role=string,INTEGER_KIND→role=integer(issue #64). Both share the same underlyingGTYP_STRGADGETtype — the same ambiguityBUTTON_KIND/CHECKBOX_KINDhave below, solved the same way:GT_GetGadgetAttrsA's documented per-kind tag table listsGTIN_NumberunderINTEGER_KINDonly, so asking a plain string gadget for it is a safe, documented no-op — the discriminator, not a guess. Needsgadtools.libraryopen, same caveat as the button/ checkbox case below; without it everySTRGADGETreports asstring.PROPGADGET(sliders/scrollers) →role=slider.BUTTON_KINDandCHECKBOX_KINDboth produce the exact sameGTYP_BOOLGADGET— nothing in the gadget structure itself tells them apart.AmiInspectdistinguishes them usingGT_GetGadgetAttrsA's documented contract: it only fills in attributes that apply to a gadget's real kind, so asking a plain button for the checkbox-onlyGTCB_Checkedattribute and checking whether it actually got filled in is an officially sanctioned way to tell them apart — not a guess. This needsgadtools.libraryopen; without it (see AmiInspect Reference), everyBOOLGADGETreports asbutton.
BOOPSI/ReAction gadgets (CUSTOMGADGET): AmiPilot reads the real,
live class name via OCLASS() — a documented NDK mechanism for exactly
this, not a private hack — and maps known classes to a role:
button.gadget, checkbox.gadget, string.gadget/getstring.gadget,
integer.gadget, radiobutton.gadget, chooser.gadget,
scroller.gadget, slider.gadget, listbrowser.gadget, and (issue
69) the WB3.2-era classes clicktab.gadget (role=page_tab_list),¶
colorwheel.gadget (role=color_wheel), datebrowser.gadget
(role=calendar), fuelgauge.gadget (role=progress_bar),
getcolor.gadget (role=color_chooser), getfile.gadget
(role=file_chooser), getfont.gadget (role=font_chooser),
getscreenmode.gadget (role=screenmode_chooser),
gradientslider.gadget (role=slider — functionally a slider variant,
no separate AT-SPI role exists for one either), palette.gadget
(role=palette), sketchboard.gadget (role=canvas), speedbar.gadget
(role=toolbar — registers its class as literally "speedbar", not
"speedbar.gadget" like every other class here, a real, easy-to-guess-
wrong exception confirmed live), and texteditor.gadget
(role=text_editor). An unrecognised class still gets its real name
reported (class="...", role=custom) rather than a blank field. A
gadget whose GadgetType bits claim GTYP_CUSTOMGADGET but doesn't
actually carry a real BOOPSI object header (confirmed against a real,
OS-shipped stock application) degrades the same way — role=custom,
no class or label — rather than trusting the claim and dereferencing
garbage.
Deliberately not classified (issue #69's own research pass):
space.gadget (a pure layout placeholder with no interactive state of
its own — nothing a role would usefully describe), virtual.gadget (a
scrolling container whose children are exactly as unreachable as
layout.gadget's — see the confirmed limit below), listview.gadget
(its own autodoc says outright "listbrowser.gadget is a better
alternative" — already covered by that mapping), and tabs.gadget/
tapedeck.gadget (both ship as real library files on a stock WB3.2.3
install, but neither has a documented, NDK-supported construction path
in this project's own NDK 3.2 snapshot — no XXX_GetClass() proto/
pragma header and no reaction_macros.h convenience macro exists for
either, unlike every class classified above — an honest gap, not a
guess).
Documented gaps¶
These are permanent, stated limits of what a structural walk can see — not bugs waiting to be fixed:
A PLACETEXT_IN button's label reads empty. GadTools only populates
the classic GadgetText field for labels placed
PLACETEXT_LEFT/RIGHT/ABOVE/BELOW. A button using PLACETEXT_IN
(text drawn inside the button's own imagery — the common case) bakes that
text into rendered graphics instead, so there is genuinely nothing to
read back at this tier.
A window.class window's layout.gadget children are invisible. A
ReAction window built from window.class + layout.gadget attaches
exactly one gadget to the window's own gadget list — the top-level layout
object itself, correctly identified by class name — not its individual
button/string/checkbox children. There is no documented, public API to
enumerate a layout.gadget's children on classic AmigaOS 3.x: the
methods that would do it (LM_ADDCHILD/LM_REMOVECHILD/LM_MODIFYCHILD)
are OS4-only. Seeing those children would require reading
layout.gadget's private, undocumented internal data — the kind of
version-fragile reverse-engineering this project deliberately doesn't do.
If you need to drive a specific ReAction application's nested gadgets
today, you'll need to know its GA_IDs by other means (its source) —
and even then, a quirk profile can't help name them, since they're
invisible to structural walking regardless of who wrote the file down
(see the quirk profiles section
of the manifest spec).
This particular limit now has a real escape hatch, though only for
applications that opt into it: a WHEREGADGET manifest entry
(format version 2) resolves such a gadget by querying a small,
optional ARexx port the application itself exposes, reporting its own
live geometry — see Driving layout.gadget-only
applications.
It doesn't change what AmiInspect/structural walking can see (a
WHEREGADGET-addressed gadget still never appears in a TREE/
AmiInspect dump — this is a discovery mechanism cooperating with the
application, not a walker enhancement), only what CLICK/TYPE can
act on. A third party still can't retrofit this onto a binary that
doesn't implement the port, the same honest boundary a quirk profile
already has.
Custom-rendered UIs are invisible. Anything an application draws directly into a bitmap rather than building from real gadget structures has nothing for a structural walker to find. This applies to games and any hand-rolled rendering, by definition.
Why this matters for automation, not just inspection¶
Everything on this page describes what AmiInspect can see — and
AmiPilotServer's CLICK/TYPE/GETTEXT (see the
ARexx Reference) locate their target the exact
same way, by walking the live structure and matching a GA_ID. A gadget
AmiInspect can't classify or reach is a gadget no automation verb can
target either, until the underlying gap closes — a layout.gadget
child, for instance, has no GA_ID to CLICK by, for the same reason
AmiInspect can't list it. See the
implementation plan
for the full tiered locator model this is building toward.