app
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.
← Commit history
v0.9.131: John's foreground contract - NO foreground without foregroundReason (refused otherwise), every sanctioned foreground captions the reason via ab toast for 3s BEFORE the window moves, and sentinel bounces hold NOACTIVATE for 10s (10.0.5 steal storms flickered because prevention is reactive-only there)
7 files changed
+46−16
BRIDGE_VERSION+1−1@@ -1 +1 @@-0.9.130+0.9.131
adom-bridge-kicad-manifest.json+5−5@@ -1,15 +1,15 @@ { "manifest_version": 1, "name": "kicad",- "version": "0.9.130",- "url": "https://wiki.adom.inc/download/adom/kicad-bridge/0.9.130/adom-bridge-kicad-v0.9.130.zip",- "sha256": "674e14a36048e2843dde2c589e3ed8988a29561677f427a7d57164c6977ef9e4",- "size": 413375,+ "version": "0.9.131",+ "url": "https://wiki.adom.inc/download/adom/kicad-bridge/0.9.131/adom-bridge-kicad-v0.9.131.zip",+ "sha256": "c3de9c82227d4510c83e5f14eaa9afe21ab084facb2ac7883bc34a9a7a6d8113",+ "size": 413883, "verbPrefixes": [ "kicad_" ], "healthEndpoint": "/status",- "released_at": "2026-08-16T18:43:05Z",+ "released_at": "2026-08-16T20:02:28Z", "hero": "https://wiki.adom.inc/blob/app/kicad-bridge/kicad-hero.png", "languages": [ "Python",
adom-bridge-kicad-v0.9.131.zipadded⋯ 1 unchanged line ⋯
bridge.json+1−1@@ -2,7 +2,7 @@ "manifest_version": 1, "name": "kicad", "displayName": "KiCad EDA",- "version": "0.9.130",+ "version": "0.9.131", "description": "Reverse bridge for KiCad \u2014 board/schematic introspection, lint via kicad-cli, plugin install, multi-instance probe, in-process DRC.", "homepage": "https://wiki.adom.inc/adom/adom-bridge", "author": "Adom Inc.",
handlers/open_footprint_editor.py+1−1@@ -262,7 +262,7 @@ def _send_ctrl_shift_e(hwnd: int): try: from handlers import win_focus win_focus.announce_foreground("load the footprint via keyboard (Ctrl+Shift+E)")- win_focus.bring_to_user(hwnd)+ win_focus.bring_to_user(hwnd, reason="Loading the footprint needs the editor visible for one keyboard command") except Exception: pass time.sleep(0.3)
handlers/win_focus.py+26−7@@ -359,6 +359,7 @@ def start_focus_sentinel() -> None: last_fg = None last_click_t = 0.0 last_alt_t = 0.0+ bounce_hold = {} # hwnd -> monotonic deadline to clear NOACTIVATE VK_LBUTTON, VK_RBUTTON, VK_MENU = 0x01, 0x02, 0x12 while True: try:@@ -386,9 +387,16 @@ def start_focus_sentinel() -> None: else: _log("self-raise (bounced to background)", fg, ago) _push_kicad_to_background(fg)- # no guard is active: immediately clear the NOACTIVATE- # bit the bounce set, so a real user click still works- allow_activation(fg)+ # 10.0.5: NOACTIVATE no longer PREVENTS the steal, it+ # only helps after the bounce — hold the suppression+ # for 10s so a repeat-raising window is refused+ # silently instead of flickering, THEN clear it so+ # user clicks work again.+ bounce_hold[fg] = now + 10.0+ for h, until in list(bounce_hold.items()):+ if now >= until:+ allow_activation(h)+ del bounce_hold[h] last_fg = fg _t.sleep(0.12) except Exception:@@ -474,14 +482,25 @@ def user_wants_foreground(args) -> bool: return bool(args.get("foreground") or args.get("showToUser") or args.get("bringToFront")) -def bring_to_user(hwnd) -> bool:+def bring_to_user(hwnd, reason: str = "") -> bool: """Explicit, user-requested foreground of a window (the ONLY sanctioned way a KiCad window is brought to the front for the user — everything else stays- background). Returns True if it took."""+ background). John's contract (2026-08-16): NO foreground without a stated+ reason, and every sanctioned foreground shows an ab caption with that reason+ for 3 seconds first. A call without a reason is REFUSED.""" if not IS_WINDOWS or not hwnd: return False+ if not (reason or "").strip():+ try:+ print("[focus] bring_to_user REFUSED: no reason given", file=sys.stderr)+ except Exception:+ pass+ return False try:- expect_foreground(30.0) # tell the sentinel this foreground is sanctioned+ announce_foreground(reason, seconds=3)+ import time as _t+ _t.sleep(1.2) # let the caption render BEFORE the window moves+ expect_foreground(20.0) # tell the sentinel this foreground is sanctioned allow_activation(hwnd) # a suppressed window can't be activated _user32.ShowWindow(hwnd, 9) # SW_RESTORE (in case minimized) return _set_foreground_robust(hwnd)@@ -489,7 +508,7 @@ def bring_to_user(hwnd) -> bool: return False -def announce_foreground(action: str, seconds: int = 5) -> None:+def announce_foreground(action: str, seconds: int = 3) -> None: """MANDATORY courtesy caption before any forced foreground (John 2026-08-15: "when you HAVE to foreground cuz you have no other choice, show an ab caption telling the user why"). Fires a native ab toast — branded, and attributed to
server.py+12−1@@ -808,11 +808,22 @@ def dispatch_command(command: str, args: dict) -> dict: wins = _find_all_kicad_windows() if wins: _shown = wins[-1].get("hwnd") # the most-recently surfaced KiCad window- _wf.bring_to_user(_shown)+ _reason = (args.get("foregroundReason") or args.get("reason") or "").strip()+ if _reason:+ _wf.bring_to_user(_shown, reason=_reason)+ else:+ # John's contract (2026-08-16): NO foreground without a+ # stated reason — refuse and tell the caller how.+ _shown = None except Exception: pass if isinstance(result, dict): result["_broughtToUser"] = bool(_shown)+ if not _shown and isinstance(result, dict):+ result.setdefault("_hint", "")+ result["_foregroundRefused"] = ("foreground requested without foregroundReason - "+ "pass foregroundReason:'<why the user needs to see this>' "+ "and the bridge will caption it for 3s, then foreground.") _user_fg = None # don't also restore-away what the user just asked to see except Exception: pass