Skip to content

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_KINDrole=string, INTEGER_KINDrole=integer (issue #64). Both share the same underlying GTYP_STRGADGET type — the same ambiguity BUTTON_KIND/CHECKBOX_KIND have below, solved the same way: GT_GetGadgetAttrsA's documented per-kind tag table lists GTIN_Number under INTEGER_KIND only, so asking a plain string gadget for it is a safe, documented no-op — the discriminator, not a guess. Needs gadtools.library open, same caveat as the button/ checkbox case below; without it every STRGADGET reports as string.
  • PROPGADGET (sliders/scrollers) → role=slider.
  • BUTTON_KIND and CHECKBOX_KIND both produce the exact same GTYP_BOOLGADGET — nothing in the gadget structure itself tells them apart. AmiInspect distinguishes them using GT_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-only GTCB_Checked attribute and checking whether it actually got filled in is an officially sanctioned way to tell them apart — not a guess. This needs gadtools.library open; without it (see AmiInspect Reference), every BOOLGADGET reports as button.

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.