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
0.9.296: Tour foreground fix (John: 'why do I not see you foregrounding any kicad window'): the beat raise called SetForegroundWindow from the bridge process and reported true regardless; Windows refuses that from a background process while the user works elsewhere. The bridge now lifts its no-activate mark, asks ab's desktop_bring_to_front to raise the beat's window once, and reports foregrounded only when the z-order afterwards shows that window on top.
3 files changed
+44−15
BRIDGE_VERSION+1−1@@ -1 +1 @@-0.9.295+0.9.296
bridge.json+1−1@@ -2,7 +2,7 @@ "manifest_version": 1, "name": "kicad", "displayName": "KiCad EDA",- "version": "0.9.295",+ "version": "0.9.296", "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/demo.py+42−13@@ -587,26 +587,55 @@ def _job_update(**kw): def _foreground_once(window_title: str) -> bool: """Bring the window this beat opened to the front ONCE, then hands off.- The bridge's background contract marks its KiCad windows no-activate, so- lift that first; AttachThreadInput makes SetForegroundWindow honest from a- background process. Never re-raised later: if the user alt-tabs away- mid-beat, that is their desktop."""++ John, 2026-09-03: the first version called SetForegroundWindow from the+ bridge process and reported True whether or not Windows obeyed; Windows+ refuses a foreground change from a background process while the user is+ interacting elsewhere (it flashes the taskbar instead), so on his laptop+ nothing came forward and the flag lied. Now: lift the bridge's no-activate+ mark, ask AD (which owns the desktop's input and has the foreground+ unlock) to bring it forward, and REPORT ONLY WHAT THE Z-ORDER SAYS+ afterwards. Never re-raised later: if the user alt-tabs away mid-beat,+ that is their desktop.""" if not window_title: return False try: from handlers.kicad_ui import _find_all_kicad_windows- from handlers import win_focus+ from handlers import win_focus, ad_client, kicad_windows+ target = None for w in _find_all_kicad_windows(): if window_title.lower() in (w.get("title") or "").lower():- h = w["hwnd"]- try:- win_focus.allow_activation(h)- except Exception: # pylint: disable=broad-except- pass- return bool(win_focus._set_foreground_robust(h))+ target = int(w["hwnd"]); break+ if not target:+ return False+ try:+ win_focus.allow_activation(target)+ except Exception: # pylint: disable=broad-except+ pass+ raised = False+ try:+ r = ad_client.call("desktop_bring_to_front",+ {"hwnd": target,+ "reason": f"KiCad tour: show the user the {window_title} this beat opened, once"},+ timeout=8) or {}+ raised = bool(r.get("success")) and "unknown verb" not in str(r.get("error") or "").lower()+ except Exception: # pylint: disable=broad-except+ raised = False+ if not raised:+ try:+ win_focus._set_foreground_robust(target)+ except Exception: # pylint: disable=broad-except+ pass+ import time as _t+ for _ in range(4): # up to ~1 s for the z-order to settle+ _t.sleep(0.25)+ rows = [x for x in (kicad_windows.find(all_windows=True, fresh=True) or []) if x.get("z") is not None]+ if rows and int(sorted(rows, key=lambda x: x["z"])[0].get("hwnd") or 0) == target:+ return True+ return False except Exception: # pylint: disable=broad-except- pass- return False+ return False+ def _beat_shot(kicad_info: dict, step_label: str, window_title: str) -> dict | None: