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.86: activate the selected tree row (Enter to tree hwnd + PostMessage dblclick)
5 files changed
+59−15
BRIDGE_VERSION+1−1@@ -1 +1 @@-0.9.85+0.9.86
adom-bridge-kicad-manifest.json+5−5@@ -1,15 +1,15 @@ { "manifest_version": 1, "name": "kicad",- "version": "0.9.85",- "url": "https://wiki.adom.inc/download/adom/kicad-bridge/0.9.85/adom-bridge-kicad-v0.9.85.zip",- "sha256": "3fd994fd1b2a8c37f6c404715c5fbd8643e77f26dda44e5d525dc896eb97aba7",- "size": 378108,+ "version": "0.9.86",+ "url": "https://wiki.adom.inc/download/adom/kicad-bridge/0.9.86/adom-bridge-kicad-v0.9.86.zip",+ "sha256": "79971a758f954299d7a0df3db3df4ad7809b30e979df5bdca3522c0ac19c0d16",+ "size": 378689, "verbPrefixes": [ "kicad_" ], "healthEndpoint": "/status",- "released_at": "2026-08-14T15:39:13Z",+ "released_at": "2026-08-14T15:41:37Z", "hero": "https://wiki.adom.inc/blob/app/kicad-bridge/kicad-hero.png", "languages": [ "Python",
adom-bridge-kicad-v0.9.86.zipadded⋯ 1 unchanged line ⋯
bridge.json+1−1@@ -2,7 +2,7 @@ "manifest_version": 1, "name": "kicad", "displayName": "KiCad EDA",- "version": "0.9.85",+ "version": "0.9.86", "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_symbol_editor.py+52−8@@ -205,18 +205,62 @@ def _background_navigate_win32(sym_hwnd: int, filter_text: str, verify: str) -> return False time.sleep(1.4) # LIB_TREE search debounce runs on the UI thread (~200-500ms) + tree rebuild VK_DOWN = 0x28- # KiCad's filter box forwards navigation keys to the tree: Down selects the- # first filtered match, Enter activates (= load into canvas). Same sequence- # a human types; all posted to the Edit hwnd, zero focus needed.+ # Down THROUGH the filter box: KiCad forwards nav keys to the tree, which+ # selects the first filtered match (verified live: Device:R row highlights). user32.PostMessageW(edit, WM_KEYDOWN, VK_DOWN, 0) user32.PostMessageW(edit, WM_KEYUP, VK_DOWN, 0) time.sleep(0.5)- user32.PostMessageW(edit, WM_KEYDOWN, VK_RETURN, 0)- user32.PostMessageW(edit, WM_KEYUP, VK_RETURN, 0)- for _ in range(10):- time.sleep(0.7)++ def _tree_candidates():+ """The LIB_TREE dataview lives near the searchCtrl: collect tall sibling+ windows under the Libraries pane (wx generic controls share bland class+ names, so geometry is the discriminator)."""+ cands = []+ try:+ pane = user32.GetParent(user32.GetParent(edit)) or user32.GetParent(edit)+ WNDENUMPROC = ctypes.WINFUNCTYPE(ctypes.c_bool, ctypes.wintypes.HWND,+ ctypes.wintypes.LPARAM)+ def cb(hwnd, _l):+ r = ctypes.wintypes.RECT()+ user32.GetWindowRect(hwnd, ctypes.byref(r))+ if (r.bottom - r.top) > 200 and hwnd != edit:+ cands.append((hwnd, r))+ return True+ cbp = WNDENUMPROC(cb)+ _callbacks.append(cbp)+ user32.EnumChildWindows(pane, cbp, 0)+ except Exception:+ pass+ return cands++ def _title_ok(): t = (_get_symbol_editor_title() or "").lower()- if verify in t and "no symbol loaded" not in t:+ return verify in t and "no symbol loaded" not in t++ # Round 1: Enter posted straight to each tree candidate (the payload's own+ # in-process technique, aimed from outside).+ for hwnd, _r in _tree_candidates():+ user32.PostMessageW(hwnd, WM_KEYDOWN, VK_RETURN, 0)+ user32.PostMessageW(hwnd, WM_KEYUP, VK_RETURN, 0)+ for _ in range(6):+ time.sleep(0.7)+ if _title_ok():+ return True++ # Round 2: double-click the selected row (first visible row sits just under+ # the header) — pure PostMessage, still zero focus.+ WM_LBUTTONDOWN, WM_LBUTTONUP, WM_LBUTTONDBLCLK, MK_LBUTTON = 0x0201, 0x0202, 0x0203, 0x0001+ for hwnd, r in _tree_candidates():+ w = r.right - r.left+ x, y = max(10, w // 3), 58 # inside the item column, ~second row band+ lp = (y << 16) | (x & 0xFFFF)+ user32.PostMessageW(hwnd, WM_LBUTTONDOWN, MK_LBUTTON, lp)+ user32.PostMessageW(hwnd, WM_LBUTTONUP, 0, lp)+ user32.PostMessageW(hwnd, WM_LBUTTONDBLCLK, MK_LBUTTON, lp)+ user32.PostMessageW(hwnd, WM_LBUTTONUP, 0, lp)+ for _ in range(8):+ time.sleep(0.7)+ if _title_ok(): return True return False