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.281: Wiki #71: kicad_show_library failure responses now carry a per-step navigation trace (search Edit found, parent chain, filter text as read back, tree candidates with class and rect, editor title after Down, after posted Enter, after posted double-click). Ray measured on his box that the tree lists the symbol and a real Enter loads it while the bridge's posted Enter and double-click do not; the trace is how the next build finds which hwnd the Enter must reach.
1 file changed
+65−1
handlers/open_symbol_editor.py+65−1@@ -441,6 +441,41 @@ def _plugin_background_load(symbol_name: str, library_name: str = "", return {"loaded": False, "_error": f"{type(e).__name__}: {e}"} +# wiki #71 (2026-09-03): the background navigation failed on a CLEAN library on+# ConfRoomROG and on Ray's box with no measurement of WHICH step missed. Every+# _background_navigate_win32 run now records its steps here; the failing+# show_library response carries the last run as `navigation`.+_LAST_NAV_TRACE: dict = {}+++def _win_class(hwnd: int) -> str:+ try:+ buf = ctypes.create_unicode_buffer(128)+ user32.GetClassNameW(hwnd, buf, 128)+ return buf.value+ except Exception: # pylint: disable=broad-except+ return "?"+++def _win_rect(hwnd: int) -> list:+ try:+ r = ctypes.wintypes.RECT()+ user32.GetWindowRect(hwnd, ctypes.byref(r))+ return [r.left, r.top, r.right, r.bottom]+ except Exception: # pylint: disable=broad-except+ return []+++def _edit_text(hwnd: int) -> str:+ try:+ n = user32.GetWindowTextLengthW(hwnd)+ buf = ctypes.create_unicode_buffer(n + 1)+ user32.GetWindowTextW(hwnd, buf, n + 1)+ return buf.value+ except Exception: # pylint: disable=broad-except+ return "?"++ def _background_navigate_win32(sym_hwnd: int, filter_text: str, verify: str, verify_lib: str = "") -> bool: """v0.9.83 (#20/#21): plugin-free BACKGROUND symbol load. WM_SETTEXT into the@@ -450,22 +485,42 @@ def _background_navigate_win32(sym_hwnd: int, filter_text: str, verify: str, no in-process plugin needed — works even while eeschema's embedded Python is still minutes from initializing (issue #21). Returns True when the editor title confirms `verify` loaded."""+ global _LAST_NAV_TRACE+ trace: dict = {"filter": filter_text, "verify": verify, "editorHwnd": int(sym_hwnd),+ "titleBefore": _get_symbol_editor_title(), "steps": []}+ _LAST_NAV_TRACE = trace edit = _find_child_by_class_and_parent(sym_hwnd, "Edit", "searchCtrl") if not edit:+ trace["steps"].append("search Edit under a 'searchCtrl' parent NOT FOUND (KiCad 10 child window layout?)") return False+ trace["editHwnd"] = int(edit)+ trace["editParentChain"] = []+ try:+ h = edit+ for _ in range(4):+ h = user32.GetParent(h)+ if not h:+ break+ trace["editParentChain"].append({"hwnd": int(h), "class": _win_class(h), "text": _edit_text(h)[:40]})+ except Exception: # pylint: disable=broad-except+ pass WM_SETTEXT = 0x000C WM_KEYDOWN, WM_KEYUP, VK_RETURN = 0x0100, 0x0101, 0x0D try: user32.SendMessageW(edit, WM_SETTEXT, 0, ctypes.create_unicode_buffer(filter_text)) except Exception:+ trace["steps"].append("WM_SETTEXT raised") return False time.sleep(1.4) # LIB_TREE search debounce runs on the UI thread (~200-500ms) + tree rebuild+ trace["editTextAfterSet"] = _edit_text(edit)+ trace["steps"].append(f"WM_SETTEXT -> edit reads {trace['editTextAfterSet']!r}") VK_DOWN = 0x28 # 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)+ trace["steps"].append(f"VK_DOWN posted to edit; title now {_get_symbol_editor_title()!r}") def _tree_candidates(): """The LIB_TREE dataview lives near the searchCtrl: collect tall sibling@@ -497,13 +552,17 @@ def _background_navigate_win32(sym_hwnd: int, filter_text: str, verify: str, # 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():+ cands = _tree_candidates()+ trace["treeCandidates"] = [{"hwnd": int(h), "class": _win_class(h), "rect": [r.left, r.top, r.right, r.bottom]} for h, r in cands]+ for hwnd, _r in cands: 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():+ trace["steps"].append("VK_RETURN to tree candidates -> title confirms") return True+ trace["steps"].append(f"VK_RETURN to {len(cands)} tree candidate(s); title still {_get_symbol_editor_title()!r}") # Round 2: double-click the selected row (first visible row sits just under # the header) — pure PostMessage, still zero focus.@@ -519,7 +578,10 @@ def _background_navigate_win32(sym_hwnd: int, filter_text: str, verify: str, for _ in range(8): time.sleep(0.7) if _title_ok():+ trace["steps"].append("posted double-click on first row -> title confirms") return True+ trace["steps"].append(f"posted double-click on first row; title still {_get_symbol_editor_title()!r}")+ trace["editTextAtEnd"] = _edit_text(edit) return False @@ -1129,10 +1191,12 @@ def handle_open_symbol_editor(kicad_info: dict, args: dict) -> dict: # a OneDrive-redirected box the bridge had been writing elsewhere. # So report the paths, measured, and say only what they show. **_stale_explanation(kicad_info, search_text),+ "navigation": dict(_LAST_NAV_TRACE), } return { "success": False, "editorOpened": True, "symbolLoaded": False, "attempts": 2,+ "navigation": dict(_LAST_NAV_TRACE), "error": f"Symbol Editor open but '{search_text}' didn't load in the background " f"after two attempts (title: {_get_symbol_editor_title() or 'unknown'})", "_hint": ("Two background attempts did not confirm, so a third is unlikely to "