KiCad - the KiCad Bridge
Public Made by Adomby adom
Reference implementation of the KiCad bridge: multi-instance Python server, forward path via kicad-cli, reverse path via in-process plugin. Most complex of the three bundled bridges.
Releases 350
Standalone per-platform binaries to download and run, no tools needed. The newest is pinned on top.
Stable link for websites and docs: /download/adom/kicad-bridge/latest
All releases showing 261-280 of 350
The one-line root of the unguarded gaps: restore_foreground_persistent's hwnd parameter has been vestigial since 0.9.104 (the guardian touches only KiCad windows), but its early-return still required an hwnd - so whenever foreground capture returned None, NO post-verb guard armed at all: no guardian sweeps, parking sweep idle during editor load tails, late windows visible and adopted by rebaselining. The guardian now arms regardless.
The unguarded gap closed: the post-verb focus guardian only armed when a user foreground had been captured, and when capture returned None no guard armed at all - so windows KiCad creates asynchronously after a spawn verb returned appeared in a gap with no guard and no in-flight marker, and the parking rebaseline adopted them as the user's. The guardian (which touches only KiCad windows) now arms unconditionally for every window-spawning verb, keeping the parking sweep continuously active through editor load tails.
Parking instrumentation: focusDebug now carries parkAttempts, parkLastError, sweepTicks and bridgeWindows so a parking no-op is attributable in one read.
Parking model fix: windows the bridge creates are tracked as bridge-created for their lifetime, so per-verb rebaselining can no longer adopt the previous verb's window as the user's (which silently exempted everything from parking). Between verbs the sweep defends parked windows against KiCad's own geometry restores, and after 2 seconds of bridge quiet every parked window is placed at its true coordinates at the bottom of the z-order (ex-maximized ones minimized to the taskbar with restore-to-maximized).
kicad_state.focusDebug: liveness and counters for the parking machinery (sweep/hook/sentinel started, verb-inflight, guard count, parked and baseline counts) so parking failures are diagnosable from one read instead of blind iteration.
Parking works on maximized windows now: SetWindowPos silently cannot move a maximized window, which is why parking no-opped on the demo's maximized editors. Parking now goes through SetWindowPlacement (demote to normal-size at -32000, SW_SHOWNOACTIVATE, remembering the maximized state); unpark restores true coordinates at bottom-z, and ex-maximized windows are handed to the taskbar minimized with WPF_RESTORETOMAXIMIZED so the user's taskbar click restores them exactly like normal.
Parking holds now: KiCad applies its saved window geometry late in initialization, moving itself back on-screen after the early off-screen park. The 50ms sweep re-parks on every visible sighting while bridge work is live (recording the true coordinates only at first sighting), so the window stays off-screen until unpark places it at its real position, bottom-z.
Deterministic off-screen parking: a 50ms sweep, active only while a GUI verb is in flight or a guard is armed, baselines the KiCad windows that existed at verb start (the user's own) and parks anything NEWER off-screen before meaningful paint. Complements the event hook, which arms but receives no events in some sessions. Parked windows return to their true coordinates at bottom-z when bridge work ends.
Focus-hook diagnostics: log hook arming and each KiCad window event to stderr; SetWinEventHook restype declared for 64-bit safety.
Parking timing fix: KiCad windows are created DURING handler execution but guards only arm after the handler returns, so 0.9.138's SHOW-hook parking never engaged. The dispatcher now marks GUI-verb-in-flight around every handler call and the hook parks on either signal (in-flight or guard active).
Off-screen parking, designed by John: NOACTIVATE stops focus theft but a freshly created window still paints near the top of the z-order, visible over the user's work even unfocused. The SHOW event hook now teleports a new KiCad window to (-32000,-32000) before its first visible paint; when bridge work ends every parked window is placed back at its true coordinates at the BOTTOM of the z-order, unfocused. Taskbar buttons work normally, PrintWindow capture works off-screen, and a deliberate foreground (bring_to_user) unparks first. The user sees nothing appear, ever.
Event-driven focus enforcement: SetWinEventHook on EVENT_OBJECT_SHOW and EVENT_SYSTEM_FOREGROUND with a dedicated message-pump thread. During bridge work (guards active, no sanctioned foreground in progress) a KiCad window is NOACTIVATE-suppressed the instant it appears and, if it still grabs the foreground, bounced within milliseconds - replacing the 120-500ms polling reaction that was the visible flash on KiCad 10.0.5. Outside bridge work the click-aware sentinel still owns policy so user clicks into KiCad always work. Hook-bounced events are logged to focusEvents as guarded-steal (hook-bounced, ms).
kicad_state now reports userIdleSeconds (seconds since the last human keyboard/mouse input on the box). This is the PRESENCE GATE for automation: anything that spawns windows must check it first and defer while a human is active (the daily verb suite now requires 600s idle and retries for up to 6h). Born from a live incident: a full suite run foregrounding windows while John worked, on KiCad 10.0.5 where WS_EX_NOACTIVATE no longer prevents first-steals (kicad-bridge#31).
board3d demo beat waits up to 15s for the PCB editor window to be findable before invoking the 3D viewer - the board beat can return while pcbnew is mid-transition and the precheck then missed it.
The last 10.0.5 demo blocker: eeschema's Symbol Editor menu id changed 20390 -> 20391 (found by asking the LIVE process via the plugin's get_menu_ids). All editor-open tiers now resolve menu ids from the live menu bar through the plugin, with the old constants as offline fallbacks. The demo's warm phase also launches the Project Manager so the part beats' in-process plugin route has a bound target by the time they run.
#31's primary root cause found and fixed: it was never a 10.0.5 Python regression - dozens of test runs accumulated dead-pid discovery files (92 on the test box), and plugin resolution probed every one with an HTTP timeout, running past callers' budgets so a perfectly-alive plugin reported plugin_not_running. After pruning, the plugin binds in kicad.exe, standalone eeschema AND pcbnew within 10-12s on 10.0.5. Resolution now skips and DELETES dead-pid files inline (a cheap kernel liveness check, no HTTP), and the bridge sweeps stale files at every boot. Remaining true 10.0.5 items stay open on #31: NOACTIVATE no longer prevents first-steal (sentinel bounce covers it), external WM_COMMAND to a project-loaded PM, and the eeschema 20390 menu id.
Every window-spawning verb response now tells the calling AI explicitly: openedInBackground:true plus a _foregroundHint spelling out the one sanctioned way to show the window to the user (re-call with foreground:true AND foregroundReason; the bridge captions the reason via ab toast for 3 seconds, then brings the window forward; reasonless foreground is refused with _foregroundRefused guidance).
The foreground contract, hardened per John (2026-08-16): (1) any foreground request now REQUIRES foregroundReason - a bare foreground:true is refused with guidance; (2) every sanctioned foreground shows an ab caption carrying the reason for 3 seconds BEFORE the window moves, then hides; (3) after the focus sentinel bounces a self-raising window it holds WS_EX_NOACTIVATE for 10s so 10.0.5's repeat-steal storms are refused silently instead of flickering (on 10.0.5 NOACTIVATE no longer PREVENTS the first steal - evidence on #31). Also: the icon-less taskbar blips users saw are KiCad's own splash/progress windows surfacing during foreground churn; fixing the churn fixes them.
Demo beat order is now a reliability decision: schematic, 2D board and 3D board (plain file-opens, no plugin needed) run first; the part beats (symbol, footprint, part 3D) run after, by which time KiCad's lazily-started embedded Python has had 1-2 minutes to bind the in-process plugin - the only reliable editor-open route on 10.0.5. Narration renumbered to match.
PM plugin bind-wait extended to ~45s: KiCad starts the Project Manager's embedded Python lazily, and on a fresh 10.0.5 PM the reverse-bridge plugin takes 30-45s to come up (15s of retries all hit plugin_not_running while a minutes-old PM answered instantly).