← Commit history

Update 40 file(s)

John Lauer ·5a3b25e057 ·1mo ago ·parent 80a119c
4 files changed +104−7
BRIDGE_VERSION+1−1
@@ -1 +1 @@-0.9.266\ No newline at end of file+0.9.267\ No newline at end of file
adom-bridge-kicad-manifest.json+5−5
@@ -1,15 +1,15 @@ {   "manifest_version": 1,   "name": "kicad",-  "version": "0.9.266",-  "url": "https://wiki.adom.inc/download/adom/kicad-bridge/0.9.266/adom-bridge-kicad-v0.9.266.zip",-  "sha256": "53b43dbac7db105ecc771b4c31e56eb982d37680bc52907f21d2065ad7ac8a37",-  "size": 1037305,+  "version": "0.9.267",+  "url": "https://wiki.adom.inc/download/adom/kicad-bridge/0.9.267/adom-bridge-kicad-v0.9.267.zip",+  "sha256": "97384b29d0c8789dd3c0dbf7bf6a5cfde124066fece969a67b6108a696692c96",+  "size": 1039132,   "verbPrefixes": [     "kicad_"   ],   "healthEndpoint": "/status",-  "released_at": "2026-09-01T02:28:40Z",+  "released_at": "2026-09-01T02:31:50Z",   "hero": "https://wiki.adom.inc/blob/app/kicad-bridge/kicad-hero.png",   "languages": [     "Python",
bridge.json+1−1
@@ -2,7 +2,7 @@   "manifest_version": 1,   "name": "kicad",   "displayName": "KiCad EDA",-  "version": "0.9.266",+  "version": "0.9.267",   "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/kicad_log.py+97
@@ -140,3 +140,100 @@ def handle_log_tail(kicad_info: dict, args: dict) -> dict:             "_hint": ("Carry nextOffset into the next call and you get ONLY what happened "                       "since — tail, act, tail again, and the second read is your action's "                       "consequences. errorsOnly:true filters to error/warning.")}+++# ---------------------------------------------------------------------------+# THE ERROR CHANNEL THAT ACTUALLY WORKS: KiCad's own dialogs.+#+# Measured on ConfRoomROG, KiCad 10.0, 2026-09-01. A board that fails to parse+# produces a "KiCad PCB Editor Error" window carrying the exact reason:+#+#   Error loading PCB '...demo-project.kicad_pcb'.+#   Expecting thru_hole, smd, connect, or np_thru_hole+#+# ...and the captured stderr log for that same launch was ZERO BYTES. That is+# not a bug in the capture: wxWidgets in a GUI app installs wxLogGui as the+# active log target, so wxLogError becomes a MESSAGE BOX, not a line on stderr.+# KICAD_ENABLE_WXTRACE turns logging on but does not redirect it to a console+# that a windowed process does not have.+#+# So: the dialogs ARE the log. This function reads them without screenshots,+# without OCR, and without touching the foreground - it is pure win32 text+# harvesting (EnumWindows + WM_GETTEXT), which is read-only by construction.+#+# stderr capture stays (kicad_log_tail) because it still catches the things that+# BYPASS wxLog: crashes, asserts, and anything printed with stdio. It is the+# second channel, not the first.++_ERR_TITLE = re.compile(r"\b(error|warning|problem|failed|cannot)\b", re.I)+++def _enum_dialog_text(hwnd: int) -> list[str]:+    """Every static/text child of a window, in z-order. wx dialogs put the+    message in one or more Static controls."""+    import ctypes+    from ctypes import wintypes+    user32 = ctypes.windll.user32+    out: list[str] = []++    @ctypes.WINFUNCTYPE(wintypes.BOOL, wintypes.HWND, wintypes.LPARAM)+    def cb(child, _lp):+        buf = ctypes.create_unicode_buffer(2048)+        user32.GetWindowTextW(child, buf, 2048)+        txt = (buf.value or "").strip()+        cls = ctypes.create_unicode_buffer(256)+        user32.GetClassNameW(child, cls, 256)+        if txt and cls.value.lower() in ("static", "wxwindownr", "wxstatictext"):+            out.append(txt)+        return True++    user32.EnumChildWindows(hwnd, cb, 0)+    return out+++def handle_errors(kicad_info: dict, args: dict) -> dict:+    """Every KiCad error/warning dialog currently on screen, with its text.++    Read-only and background: enumerates windows and reads their text. Never+    raises, never clicks, never screenshots. Args: includeAll:true to return+    every KiCad dialog rather than only ones whose title looks like an error."""+    import ctypes+    from ctypes import wintypes+    user32 = ctypes.windll.user32+    wins: list[dict] = []++    @ctypes.WINFUNCTYPE(wintypes.BOOL, wintypes.HWND, wintypes.LPARAM)+    def cb(hwnd, _lp):+        if not user32.IsWindowVisible(hwnd):+            return True+        buf = ctypes.create_unicode_buffer(512)+        user32.GetWindowTextW(hwnd, buf, 512)+        title = (buf.value or "").strip()+        if not title:+            return True+        low = title.lower()+        looks_kicad = any(k in low for k in ("kicad", "pcb editor", "schematic editor",+                                             "symbol editor", "footprint editor", "3d viewer"))+        if not looks_kicad:+            return True+        if not args.get("includeAll") and not _ERR_TITLE.search(title):+            return True+        texts = _enum_dialog_text(hwnd)+        # the message is the longest static; buttons are short ("OK", "Cancel")+        msg = max(texts, key=len) if texts else ""+        wins.append({"hwnd": hwnd, "title": title, "message": msg,+                     "allText": texts[:12],+                     "level": "error" if _ERR_TITLE.search(title) else "info"})+        return True++    try:+        user32.EnumWindows(cb, 0)+    except Exception as e:  # pylint: disable=broad-except+        return {"success": False, "error": f"window enumeration failed: {type(e).__name__}: {e}"}++    return {"success": True, "count": len(wins), "dialogs": wins,+            "_hint": ("KiCad has NO log file and in GUI mode its errors go to DIALOGS, not "+                      "stderr — so this, not kicad_log_tail, is where a failure reason lives. "+                      "Read-only: nothing is clicked or raised. Dismiss with kicad_dismiss_dialogs "+                      "(which DOES foreground). kicad_log_tail is the second channel and catches "+                      "only what bypasses wxLog: crashes, asserts, stdio.")}