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.152: Adom overlay badges on bridge-driven KiCad taskbar buttons (ITaskbarList3, pup pattern) + kicad_get_settings/kicad_set_settings (overlayBadges toggle, dashboard backend)
9 files changed
+240−8
BRIDGE_VERSION+1−1@@ -1 +1 @@-0.9.151+0.9.152
adom-bridge-kicad-manifest.json+5−5@@ -1,15 +1,15 @@ { "manifest_version": 1, "name": "kicad",- "version": "0.9.151",- "url": "https://wiki.adom.inc/download/adom/kicad-bridge/0.9.151/adom-bridge-kicad-v0.9.151.zip",- "sha256": "a5e38ba6cfc2b1f15a2d3a5cdbdd633937d6be683ee2c49a3c337647b87fdb38",- "size": 420496,+ "version": "0.9.152",+ "url": "https://wiki.adom.inc/download/adom/kicad-bridge/0.9.152/adom-bridge-kicad-v0.9.152.zip",+ "sha256": "b1e0e81e561c111003e2f0798b484473a03952f86aefa1937bc196d5a7a73e03",+ "size": 427311, "verbPrefixes": [ "kicad_" ], "healthEndpoint": "/status",- "released_at": "2026-08-17T15:01:11Z",+ "released_at": "2026-08-17T15:05:24Z", "hero": "https://wiki.adom.inc/blob/app/kicad-bridge/kicad-hero.png", "languages": [ "Python",
adom-bridge-kicad-v0.9.152.zipadded⋯ 1 unchanged line ⋯
bridge.json+1−1@@ -2,7 +2,7 @@ "manifest_version": 1, "name": "kicad", "displayName": "KiCad EDA",- "version": "0.9.151",+ "version": "0.9.152", "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/settings.pyadded+78@@ -0,0 +1,78 @@+"""settings.py — kicad_get_settings / kicad_set_settings: the bridge's+user-visible preferences, persisted per box.++Born 2026-08-17 (John): "place adom favicon as overlay icons into all your+kicad windows taskbar icons ... but this should be a Setting in your settings+window in your dashboard." These verbs are the settings BACKEND; the dashboard+(kicad launch page, modeled on fusion-dashboard) renders them.++Settings live in %USERPROFILE%/.adom/kicad-bridge-settings.json.++Current settings:+ overlayBadges (bool, default true) — stamp the Adom favicon as a taskbar+ overlay on every bridge-created KiCad window, so the user can see at a+ glance which windows Adom is driving.+"""+import json+import os+from pathlib import Path++_DEFAULTS = {+ "overlayBadges": True,+}+++def _path() -> Path:+ base = os.environ.get("USERPROFILE") or str(Path.home())+ d = Path(base) / ".adom"+ d.mkdir(parents=True, exist_ok=True)+ return d / "kicad-bridge-settings.json"+++def get_settings() -> dict:+ out = dict(_DEFAULTS)+ try:+ out.update(json.loads(_path().read_text(encoding="utf-8")))+ except Exception:+ pass+ return out+++def handle_get_settings(kicad_info: dict, args: dict) -> dict:+ s = get_settings()+ return {+ "success": True,+ "settings": s,+ "defaults": dict(_DEFAULTS),+ "path": str(_path()),+ "_hint": ("kicad_set_settings {\"overlayBadges\": false} to change; changes apply "+ "immediately (overlay badges are stamped/cleared on live windows)."),+ }+++def handle_set_settings(kicad_info: dict, args: dict) -> dict:+ s = get_settings()+ changed = {}+ for k in _DEFAULTS:+ if k in args:+ v = bool(args[k])+ if s.get(k) != v:+ changed[k] = v+ s[k] = v+ if not changed:+ return {"success": True, "settings": s, "changed": {},+ "output": "no settings changed",+ "_hint": f"Settable keys: {', '.join(_DEFAULTS)}."}+ try:+ _path().write_text(json.dumps(s, indent=2), encoding="utf-8")+ except Exception as e:+ return {"success": False, "error": f"could not persist settings: {e}"}+ applied = {}+ if "overlayBadges" in changed:+ try:+ from handlers.win_taskbar import rebadge_all+ applied["rebadgedWindows"] = rebadge_all(changed["overlayBadges"])+ except Exception:+ pass+ return {"success": True, "settings": s, "changed": changed, **applied,+ "output": f"settings updated: {changed}"}
handlers/win_focus.py+5@@ -416,6 +416,11 @@ def _start_park_sweep() -> None: _bridge_windows.add(h) suppress_activation(h) _park_offscreen(h)+ try:+ from handlers.win_taskbar import apply_adom_badge+ apply_adom_badge(h) # "adom is driving this window"+ except Exception:+ pass _t.sleep(0.05) elif _parked: # bridge quiet but windows still parked: keep re-parking
handlers/win_taskbar.pyadded+139@@ -0,0 +1,139 @@+"""win_taskbar.py — taskbar overlay badges via ITaskbarList3::SetOverlayIcon.++John's design (2026-08-17, after seeing pup's overlay badges): stamp the Adom+favicon as an OVERLAY on every bridge-created KiCad window's taskbar button, so+the user sees at a glance "adom is controlling my kicad". Gated by the+`overlayBadges` setting (handlers/settings.py, default on).++Mechanics mirror pup's: Windows' ITaskbarList3 COM interface takes a per-window+HICON overlay drawn in the button's corner. The COM call is made from the+parking sweep thread (CoInitialize'd once per thread).+"""+import os+import sys+from pathlib import Path++IS_WINDOWS = sys.platform == "win32"++if IS_WINDOWS:+ import ctypes+ import ctypes.wintypes+ from ctypes import POINTER, byref, c_void_p++_ICON_PATH = str(Path(__file__).resolve().parent.parent / "resources" / "adom-overlay.ico")++_taskbar = None # ITaskbarList3 pointer (per process, sweep thread)+_overlay_icon = None # HICON, loaded once+_com_ready = False+_badged: set = set()+++def _guid(s: str):+ class GUID(ctypes.Structure):+ _fields_ = [("Data1", ctypes.c_uint32), ("Data2", ctypes.c_uint16),+ ("Data3", ctypes.c_uint16), ("Data4", ctypes.c_ubyte * 8)]+ import uuid+ u = uuid.UUID(s)+ g = GUID()+ g.Data1, g.Data2, g.Data3 = u.time_low, u.time_mid, u.time_hi_version+ rest = u.bytes[8:]+ for i in range(8):+ g.Data4[i] = rest[i]+ return g+++def _ensure_taskbar() -> bool:+ """CoCreate ITaskbarList3 on the CALLING thread (call from one thread only)."""+ global _taskbar, _overlay_icon, _com_ready+ if not IS_WINDOWS:+ return False+ if _taskbar is not None:+ return True+ try:+ ole32 = ctypes.windll.ole32+ if not _com_ready:+ ole32.CoInitialize(None)+ _com_ready = True+ CLSID_TaskbarList = _guid("56FDF344-FD6D-11d0-958A-006097C9A090")+ IID_ITaskbarList3 = _guid("EA1AFB91-9E28-4B86-90E9-9E9F8A5EECAF")+ p = c_void_p()+ CLSCTX_INPROC_SERVER = 1+ hr = ole32.CoCreateInstance(byref(CLSID_TaskbarList), None,+ CLSCTX_INPROC_SERVER,+ byref(IID_ITaskbarList3), byref(p))+ if hr != 0 or not p.value:+ return False+ # vtable: [0]QI [1]AddRef [2]Release [3]HrInit ... [18]SetOverlayIcon+ vtbl = ctypes.cast(p, POINTER(POINTER(c_void_p))).contents+ HrInit = ctypes.WINFUNCTYPE(ctypes.c_long, c_void_p)(vtbl[3])+ if HrInit(p) != 0:+ return False+ _taskbar = p+ # load the overlay icon once (16px is what the badge renders at)+ if os.path.isfile(_ICON_PATH):+ LR_LOADFROMFILE = 0x0010+ IMAGE_ICON = 1+ _overlay_icon = ctypes.windll.user32.LoadImageW(+ None, _ICON_PATH, IMAGE_ICON, 16, 16, LR_LOADFROMFILE)+ return True+ except Exception:+ return False+++def _set_overlay(hwnd, hicon, description: str) -> bool:+ try:+ vtbl = ctypes.cast(_taskbar, POINTER(POINTER(c_void_p))).contents+ SetOverlayIcon = ctypes.WINFUNCTYPE(+ ctypes.c_long, c_void_p, ctypes.wintypes.HWND,+ c_void_p, ctypes.wintypes.LPCWSTR)(vtbl[18])+ return SetOverlayIcon(_taskbar, hwnd, hicon, description) == 0+ except Exception:+ return False+++def apply_adom_badge(hwnd) -> bool:+ """Stamp the Adom overlay on a window's taskbar button (idempotent)."""+ try:+ from handlers.settings import get_settings+ if not get_settings().get("overlayBadges", True):+ return False+ except Exception:+ pass+ if hwnd in _badged:+ return True+ if not _ensure_taskbar() or not _overlay_icon:+ return False+ ok = _set_overlay(hwnd, _overlay_icon, "Adom is driving this KiCad window")+ if ok:+ _badged.add(hwnd)+ return ok+++def clear_adom_badge(hwnd) -> None:+ try:+ if hwnd in _badged and _ensure_taskbar():+ _set_overlay(hwnd, None, "")+ _badged.discard(hwnd)+ except Exception:+ pass+++def rebadge_all(enable: bool) -> int:+ """Setting flipped: apply/clear the badge on every bridge-created window."""+ n = 0+ try:+ from handlers.win_focus import _bridge_windows+ import ctypes as _ct+ for h in list(_bridge_windows):+ if not _ct.windll.user32.IsWindow(h):+ continue+ if enable:+ _badged.discard(h) # force reapply+ if apply_adom_badge(h):+ n += 1+ else:+ clear_adom_badge(h)+ n += 1+ except Exception:+ pass+ return n
resources/adom-overlay.icoadded⋯ 1 unchanged line ⋯
server.py+11−1@@ -68,6 +68,7 @@ from handlers.export_molecule import handle_export_molecule from handlers.demo import handle_demo from handlers.make_part_project import handle_make_part_project from handlers.export_part import handle_export_part+from handlers.settings import handle_get_settings, handle_set_settings # v0.9.10+: check/upgrade KiCad via the OFFICIAL downloads.kicad.org installer # (NOT winget, which lags the real release). check_for_updates is read-only; # upgrade silent-installs the official signed NSIS build.@@ -367,6 +368,12 @@ _VERB_CATALOG = { "export_step": {"summary": "Export a STEP 3D model from a board.", "long": False, "hint": "Absolute board path + output; exports a STEP assembly via kicad-cli.", "related": ["kicad_open_3d_viewer", "kicad_export_gerber"], "pitfalls": ["only components with 3D models attached appear in the STEP"]},+ "get_settings": {"summary": "Read the bridge's user-visible settings (overlay badges, ...).", "long": False,+ "hint": "kicad_get_settings {} returns settings+defaults. Currently: overlayBadges (Adom favicon stamped on bridge-driven KiCad taskbar buttons, default on).",+ "related": ["kicad_set_settings"], "pitfalls": []},+ "set_settings": {"summary": "Change bridge settings; applies immediately (e.g. overlay badges stamped/cleared on live windows).", "long": False,+ "hint": "kicad_set_settings {\"overlayBadges\": false}.",+ "related": ["kicad_get_settings"], "pitfalls": []}, "export_part": {"summary": "One-off single-part library exports: a .kicad_sym with ONLY that symbol, a .pretty with ONLY that footprint, the bare 3D model file — the reverse of install.", "long": False, "hint": "kicad_export_part {\"partName\":\"IN-S42ATA\"} (optional what:[\"symbol\",\"footprint\",\"model\"]). Writes to Documents/adom-part-exports/<part>/; pull with ab pull_file. Proves per-asset round-trip.", "related": ["kicad_install_symbol", "kicad_install_footprint", "kicad_make_part_project"],@@ -549,6 +556,8 @@ COMMAND_HANDLERS = { "demo": handle_demo, "make_part_project": handle_make_part_project, "export_part": handle_export_part,+ "get_settings": handle_get_settings,+ "set_settings": handle_set_settings, "export_bom_csv": handle_export_bom_csv, # v0.9.10+: KiCad version check + upgrade via the official downloads.kicad.org # installer (read-only check; silent /S install). Exposed as kicad_check_for_updates@@ -614,7 +623,8 @@ def dispatch_command(command: str, args: dict) -> dict: # diagnostics — self-inspection; MOST needed when KiCad is undetected # None of these need the reverse-bridge plugin (there may be no KiCad to load # it into), so they also skip ensure_plugin_installed_once.- if command in ("readiness", "describe", "check_for_updates", "diagnostics"):+ if command in ("readiness", "describe", "check_for_updates", "diagnostics",+ "get_settings", "set_settings"): return handler(kicad_info, args) # upgrade INSTALLS KiCad from scratch — the whole point is it runs when KiCad