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.175: STOP FIGHTING KiCad's geometry - after 3 rounds concede the position and just keep the window behind and unfocused, instead of blinking it 20-30 times; every fight is now counted and logged in focusDebug
5 files changed
+51−8
BRIDGE_VERSION+1−1@@ -1 +1 @@-0.9.174+0.9.175
adom-bridge-kicad-manifest.json+5−5@@ -1,15 +1,15 @@ { "manifest_version": 1, "name": "kicad",- "version": "0.9.174",- "url": "https://wiki.adom.inc/download/adom/kicad-bridge/0.9.174/adom-bridge-kicad-v0.9.174.zip",- "sha256": "13daa131f7541d37af456a73176201d88ce6a194db39ff056cfe3498e9263712",- "size": 447445,+ "version": "0.9.175",+ "url": "https://wiki.adom.inc/download/adom/kicad-bridge/0.9.175/adom-bridge-kicad-v0.9.175.zip",+ "sha256": "660e2e2043c3cbb24d9c21cb7a7f7c9c4d4f9fee32497e08b5cd3d933d8cabcd",+ "size": 448190, "verbPrefixes": [ "kicad_" ], "healthEndpoint": "/status",- "released_at": "2026-08-19T08:51:29Z",+ "released_at": "2026-08-19T08:52:49Z", "hero": "https://wiki.adom.inc/blob/app/kicad-bridge/kicad-hero.png", "languages": [ "Python",
adom-bridge-kicad-v0.9.175.zipadded⋯ 1 unchanged line ⋯
bridge.json+1−1@@ -2,7 +2,7 @@ "manifest_version": 1, "name": "kicad", "displayName": "KiCad EDA",- "version": "0.9.174",+ "version": "0.9.175", "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/win_focus.py+44−1@@ -355,6 +355,12 @@ def get_focus_debug() -> dict: "parkLastError": _park_last_error, "sweepTicks": _sweep_ticks, "ownedPids": sorted(_owned_pids),+ # How hard did we fight KiCad, and where did we concede? (John asked for+ # exactly this: "log what happens on how often you blink the window".)+ "parkFights": {str(k): v for k, v in list(_park_fights.items())[-20:]},+ "parkGaveUp": len(_park_gaveup),+ "parkFightLog": list(_park_fight_log)[-10:],+ "parkFightLimit": _PARK_FIGHT_LIMIT, } @@ -391,7 +397,10 @@ def get_focus_events() -> list: # capture works off-screen; the user sees nothing appear. # --------------------------------------------------------------------------- _parked: dict = {} # hwnd -> (x, y, was_max)-_park_fights: dict = {} # hwnd -> repark count+_park_fights: dict = {} # hwnd -> how many times we re-parked it+_park_gaveup: set = set() # windows we STOPPED fighting (see _PARK_FIGHT_LIMIT)+_park_fight_log: list = [] # rolling record: what we fought, how hard, and why+_PARK_FIGHT_LIMIT = 3 # John, 2026-08-19: "you blink them 20 or 30 times" _park_attempts = 0 _park_last_error = "" _sweep_ticks = 0@@ -821,6 +830,8 @@ def _start_park_sweep() -> None: if h not in _bridge_windows: _bridge_windows.add(h) _save_ledger()+ if h in _park_gaveup:+ continue # conceded: never blink it again suppress_activation(h) _park_offscreen(h) try:@@ -1006,6 +1017,37 @@ def _park_offscreen(hwnd) -> None: # noqa: D401 - clock stamped inside # works; the screenshot path restores off-screen for capture. _ensure_window_icon(hwnd) # never freeze a blank icon into the taskbar _park_fights[hwnd] = _park_fights.get(hwnd, 0) + 1+ n = _park_fights[hwnd]++ # STOP FIGHTING (0.9.175). KiCad reasserts its own geometry while it+ # loads, so a 50ms sweep that re-parks every time turns into 20-30+ # visible blinks - John watched it happen and it reads as broken+ # software. We cannot win that argument and should not try: after+ # _PARK_FIGHT_LIMIT rounds we concede the POSITION and settle for the+ # thing that actually matters - the window stays BEHIND and never takes+ # focus. Quiet and slightly visible beats invisible and seizuring.+ if n > _PARK_FIGHT_LIMIT:+ if hwnd not in _park_gaveup:+ _park_gaveup.add(hwnd)+ _park_fight_log.append({+ "t": __import__("time").time(), "hwnd": int(hwnd), "fights": n,+ "action": "conceded position - keeping it behind instead",+ })+ del _park_fight_log[:-40]+ print(f"[park] {hwnd}: conceded after {n} rounds - KiCad keeps "+ f"reasserting geometry; leaving it in place, behind, "+ f"unfocused", file=sys.stderr, flush=True)+ suppress_activation(hwnd)+ HWND_BOTTOM = 1+ _user32.SetWindowPos(hwnd, HWND_BOTTOM, 0, 0, 0, 0,+ 0x0010 | 0x0001 | 0x0002) # NOACTIVATE|NOSIZE|NOMOVE+ _parked.pop(hwnd, None)+ _park_since.pop(hwnd, None)+ return+ if n > 1:+ _park_fight_log.append({"t": __import__("time").time(), "hwnd": int(hwnd),+ "fights": n, "action": "re-parked"})+ del _park_fight_log[:-40] if _park_fights[hwnd] >= 1: # minimize from first sighting: eeschema # recreates hwnds during load, resetting any higher threshold, and # every positional fight round is a visible flicker. Minimize wins@@ -1059,6 +1101,7 @@ def _unpark(hwnd, to_bottom: bool = True) -> None: try: pos = _parked.pop(hwnd, None) _park_fights.pop(hwnd, None)+ _park_gaveup.discard(hwnd) if pos is None or not _user32.IsWindow(hwnd): return x, y, was_max = (pos if len(pos) == 3 else (pos[0], pos[1], False))