Loading files
This file is pinned to the selected release. Download the original to open it in your design tool.
670d2a9c4This file is pinned to the selected release. Download the original to open it in your design tool.
# GUI-driver capability for the KiCad MCP harness — spec
> Design notes from the original investigation (#333). The implementation lives in
> `python/commands/gui_driver.py` (MCP tools and socket client) and `gui_driver_plugin/`
> (the helper installed into KiCad).
_Backlog spec, opened 2026-07-24. Proven value this session: AT-SPI both (a) demonstrated
click-by-name on a live pcbnew and (b) DIAGNOSED the invisible-plugin-button bug by showing
which ActionPlugins actually registered ("Git Plugin" present, "Open Loom"/"Open kiHarness"
absent) — something `kipy` cannot see. Goal: end-to-end GUI verification (toolbars, menus,
dialogs, plugin buttons) to complement `kipy` (design-content only)._
**Multi-platform is a GOAL, not a hard requirement** (Ross): ship a Linux fast-path now, keep
the MCP tool interface OS-agnostic so a cross-platform backend can slot in.
## The kipy boundary (why this is needed)
`kipy` manipulates the _design_ (board/schematic objects). It does NOT touch GUI chrome —
you can't ask it "is the Open kiHarness button present?", click a menu item, or drive a dialog.
The PCB/schematic **canvas is one opaque widget** to any GUI-a11y layer, so the split is clean:
**kipy = design content, gui-driver = chrome.**
## Backend A — in-process native (CROSS-PLATFORM, preferred long-term)
KiCad's own Python has **wx 4.2.1 (Phoenix)** importable, and `pcbnew` exposes `ActionPlugin`,
`LoadPlugins`, `GetPluginForPath`, `IsActionRunning`. Because **wxWidgets is cross-platform**,
a helper running _inside_ KiCad works on Linux/Windows/macOS:
- Walk `wx.GetTopLevelWindows()` → frame → `GetToolBar()`/`GetMenuBar()`; find a tool/menu by
label or id; trigger it by posting a `wx.CommandEvent(wx.wxEVT_COMMAND_TOOL_CLICKED, id)` to
`frame.ProcessEvent(evt)` — a real activation, no pixel coordinates.
- For plugin actions specifically, invoke the registered `ActionPlugin.Run()` directly.
- **Needs:** a tiny helper loaded inside KiCad (a bundled ActionPlugin or a scripting-console
bootstrap) + a control channel between the MCP and the helper (local socket / named pipe /
watched file). The MCP sends "click X" / "dump tree"; the helper executes in KiCad's UI thread.
- **Ours to build, no upstream:** KiCad already exposes Python + wx to plugins, so the
helper uses only public surface — no KiCad C++ changes. Same delivery pattern as
kiHarness/Loom: a PCM plugin whose `__init__.py` starts a background listener on KiCad
launch; commands marshal to the UI thread via `wx.CallAfter`. The ONLY upstream-y option
is exposing KiCad's `TOOL_ACTION` 'run-by-name' registry to Python — optional; the
wx-event path doesn't need it.
- **Trade-off:** must run code in-process (deploy a helper); introspecting wx toolbars is a bit
fiddly (labels/ids), but it's the only path that is both native and cross-platform.
## Backend B — AT-SPI (Linux-only, external, zero-in-process) — VERIFIED
The Linux accessibility bus; screen-reader tech. GTK/Qt/wx apps publish their widget tree.
- Binding ships with the OS: `from gi.repository import Atspi` (GObject-introspection, **no pip**).
- Enable once: `gsettings set org.gnome.desktop.interface toolkit-accessibility true`; launch
the app with the bridge (`GTK_MODULES=gail:atk-bridge`).
- `Atspi.init()` → `get_desktop(0)` → find app → recurse; each node has a **role**
(`push button`, `menu item`, `dialog`, `text`) + **name**. Click = `node.do_action(0)`
(press/activate). Also read/set text, query states (enabled/focused/checked).
- **Pro:** external, black-box, touches nothing in KiCad — ideal for CI-style "did the button
appear + do the right thing" checks. **Con:** Linux-only (Windows=UIAutomation,
macOS=AXUIElement would be separate adapters).
## The "read a UI file" question (Ross)
- There is **no live rendered-UI dump file** KiCad writes.
- BUT `plugin.json` **is** the declarative UI definition for API plugins (actions, buttons,
scopes) — reading it on disk tells you what _should_ register (that's how the button bug was
diagnosed). Static, not live.
- Live rendered state is only introspectable in-process (Backend A's wx/plugin registry) or via
the a11y tree (Backend B). No file shortcut.
- **Patching KiCad's C++** to expose the tool-manager / a test hook is possible but impractical
(build-from-source + maintenance) — rejected.
## Proposed MCP tool surface (backend-agnostic)
- `kicad_gui_tree()` → the clickable widget tree (roles + names), so an agent can _see_ the GUI.
- `kicad_gui_click(name|path)` → find + activate by name.
- `kicad_gui_set_text(name, value)` / `kicad_gui_wait_for(title, timeout)` → drive dialogs.
- `kicad_gui_screenshot()` → capture for visual verification.
- (Optional) `kicad_run_action_plugin(identifier)` → invoke a plugin's action directly (Backend A).
## Recommended build order
1. **Backend B (AT-SPI) first** — already proven, zero in-KiCad code, unblocks Linux GUI
verification immediately (and would have caught the button regression in CI).
2. **Backend A (in-process wx)** — the cross-platform core; the helper + control channel is the
real work. Design the tool interface now so B and A are swappable behind it.
3. Windows/macOS a11y adapters (UIAutomation / AXUIElement) only if/when needed.
Could live as a small standalone **`gui-driver` MCP** (useful for ANY GTK/wx/Qt app, not just
KiCad) or as tools inside the existing KiCad MCP.