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.184: REMOVE the machine-wide input hooks added in 0.9.182. They degraded the user's cursor and cost him four reboots. Never again, and never without asking first
5 files changed
+26−166
BRIDGE_VERSION+1−1@@ -1 +1 @@-0.9.183+0.9.184
adom-bridge-kicad-manifest.json+5−5@@ -1,15 +1,15 @@ { "manifest_version": 1, "name": "kicad",- "version": "0.9.183",- "url": "https://wiki.adom.inc/download/adom/kicad-bridge/0.9.183/adom-bridge-kicad-v0.9.183.zip",- "sha256": "6f24e3dc105746e1094d34bfa770c64a936b28fd6e8b93331f13b229c776215c",- "size": 455061,+ "version": "0.9.184",+ "url": "https://wiki.adom.inc/download/adom/kicad-bridge/0.9.184/adom-bridge-kicad-v0.9.184.zip",+ "sha256": "7778eb8b5ce1f04aa2bff108d478b2d41f7fd1fff6f1dfb28b8685639df987d5",+ "size": 453470, "verbPrefixes": [ "kicad_" ], "healthEndpoint": "/status",- "released_at": "2026-08-19T18:34:11Z",+ "released_at": "2026-08-19T18:39:05Z", "hero": "https://wiki.adom.inc/blob/app/kicad-bridge/kicad-hero.png", "languages": [ "Python",
adom-bridge-kicad-v0.9.184.zipadded⋯ 1 unchanged line ⋯
bridge.json+1−1@@ -2,7 +2,7 @@ "manifest_version": 1, "name": "kicad", "displayName": "KiCad EDA",- "version": "0.9.183",+ "version": "0.9.184", "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+19−159@@ -266,171 +266,31 @@ except Exception: pass -# ── USER-INTENT HOOKS (0.9.182) ──────────────────────────────────────────────-# John's requirement, verbatim: "if you need NOACTIVATE then you HAVE to detect-# if the human is asking for the window to be foregrounded, meaning if they-# click the windows taskbar icon or alt_tab to the window, you MUST know that-# and allow them to see it or you have broken the entire user experience."+# ── NO SYSTEM-WIDE HOOKS. EVER. (0.9.184) ───────────────────────────────────+# 0.9.182 installed machine-wide low-level input hooks to detect a taskbar+# click or alt-tab. Those are MACHINE-WIDE input hooks: every mouse event and+# keystroke on the computer passes through this bridge's callback. That is a+# grossly disproportionate thing for an EDA bridge to install, I did it without+# asking, and it degraded John's cursor within minutes. #-# The old detector POLLED GetAsyncKeyState every 120ms, so a click that began-# and ended between two polls was invisible - which is exactly how a taskbar-# click ended up treated as a self-raise and bounced. Polling cannot fix that;-# the gap is inherent.+# They are removed, not repaired. The rule, permanently:+# * NEVER install a system-wide hook (SetWindowsHookEx of any kind), a global+# keyboard/mouse listener, or anything else that sits in the OS input path.+# * If a future problem seems to need one, STOP and ask first, with the+# tradeoff spelled out. Do not ship it and see. #-# These are LOW-LEVEL HOOKS instead (WH_MOUSE_LL / WH_KEYBOARD_LL). Windows-# delivers every mouse button and every keystroke to them synchronously, before-# any application sees it. There is no sampling window and nothing to miss. On-# any of:-# * a mouse button pressed anywhere on the shell (taskbar / Start / tray)-# * a mouse button pressed on one of OUR KiCad windows-# * Alt+Tab-# we IMMEDIATELY drop every activation block, so the user's click or alt-tab-# lands on a window that can take focus normally. Release is instant: there is-# no hold-off, because a user may want the window up the very next second.-_user_intent_until = 0.0 # monotonic deadline: "the user is driving"-_intent_events = __import__("collections").deque(maxlen=8) # hook -> worker---def _clock() -> float:- import time as _t- return _t.monotonic()--_intent_hooks_started = False-_hook_refs = [] # keep callbacks alive for the process lifetime--+# The bridge does not need intent detection anyway, because it no longer blocks+# activation: WS_EX_NOACTIVATE is gone, so a click or alt-tab simply works, the+# way it does for every other window on the machine. Nothing to detect. def user_wants_foreground() -> bool:- """True if the user just asked for a window (click or alt-tab)."""- try:- import time as _t- return _t.monotonic() < _user_intent_until- except Exception:- return False---def _note_user_intent(source: str) -> None:- """Release EVERY activation block, right now."""- global _user_intent_until- try:- import time as _t- _user_intent_until = _t.monotonic() + 5.0- n = allow_activation_all()- if n:- print(f"[intent] {source}: released activation on {n} window(s)",- file=sys.stderr, flush=True)- if _focus_events is not None:- _focus_events.append({"t": _t.time(), "action": f"user intent ({source})",- "title": "", "secondsSinceUserInput": 0.0})- except Exception:- pass+ """Always False: the bridge never blocks the user's activation any more, so+ there is nothing to detect and nothing to override."""+ return False def start_user_intent_hooks() -> None:- """Low-level mouse + keyboard hooks on a dedicated pumped thread."""- global _intent_hooks_started- if not IS_WINDOWS or _intent_hooks_started:- return- _intent_hooks_started = True- import threading-- WH_MOUSE_LL, WH_KEYBOARD_LL = 14, 13- WM_LBUTTONDOWN, WM_RBUTTONDOWN, WM_MBUTTONDOWN = 0x0201, 0x0204, 0x0207- WM_KEYDOWN, WM_SYSKEYDOWN = 0x0100, 0x0104- VK_TAB, VK_MENU = 0x09, 0x12- SHELL_CLASSES = {"Shell_TrayWnd", "Shell_SecondaryTrayWnd", "TaskListThumbnailWnd",- "Windows.UI.Core.CoreWindow", "XamlExplorerHostIslandWindow",- "MultitaskingViewFrame", "TaskSwitcherWnd", "TaskSwitcherOverlayWnd"}-- HOOKPROC = ctypes.WINFUNCTYPE(ctypes.c_long, ctypes.c_int,- ctypes.wintypes.WPARAM, ctypes.wintypes.LPARAM)-- def _class_of(hwnd) -> str:- try:- buf = ctypes.create_unicode_buffer(96)- _user32.GetClassNameW(hwnd, buf, 96)- return buf.value or ""- except Exception:- return ""-- def _on_mouse(code, wparam, lparam):- # 0.9.183 - THE CURSOR BUG. This callback executes inside the system's- # input path: every microsecond spent here delays the mouse for the- # WHOLE MACHINE, and Windows silently unhooks a callback that overruns- # LowLevelHooksTimeout. 0.9.182 did window lookups, a cross-process- # class-name read, a process-image query and then SetWindowLongPtr- # (which SENDS WM_STYLECHANGING and can block for seconds on a busy- # KiCad) - all inline. That is exactly how you break someone's cursor.- #- # A hook may only record a fact. All interpretation happens on the- # worker thread below.- try:- if code >= 0 and wparam in (WM_LBUTTONDOWN, WM_RBUTTONDOWN, WM_MBUTTONDOWN):- _intent_events.append(("click", _clock()))- except Exception:- pass- return _user32.CallNextHookEx(None, code, wparam, lparam)-- def _on_key(code, wparam, lparam):- try:- if code >= 0 and wparam in (WM_KEYDOWN, WM_SYSKEYDOWN):- vk = ctypes.cast(lparam, ctypes.POINTER(ctypes.wintypes.DWORD))[0]- if vk == VK_TAB:- _intent_events.append(("alt-tab?", _clock()))- except Exception:- pass- return _user32.CallNextHookEx(None, code, wparam, lparam)-- def _worker():- """All the real work, OFF the input path."""- import time as _t- while True:- try:- if not _intent_events:- _t.sleep(0.03)- continue- kind, _when = _intent_events.pop()- _intent_events.clear()- if kind == "click":- pt = ctypes.wintypes.POINT()- _user32.GetCursorPos(ctypes.byref(pt))- under = _user32.WindowFromPoint(pt)- root = _user32.GetAncestor(under, 2) if under else None- cls = _class_of(root) if root else ""- if cls in SHELL_CLASSES:- _note_user_intent(f"click on the shell ({cls})")- elif root and is_kicad_window(root):- _note_user_intent("click on a KiCad window")- elif _user32.GetAsyncKeyState(VK_MENU) & 0x8000:- _note_user_intent("alt-tab")- except Exception:- try:- _t.sleep(0.1)- except Exception:- pass-- def _pump():- try:- mp, kp = HOOKPROC(_on_mouse), HOOKPROC(_on_key)- _hook_refs.extend([mp, kp])- _user32.SetWindowsHookExW.restype = ctypes.wintypes.HANDLE- hm = _user32.SetWindowsHookExW(WH_MOUSE_LL, mp, None, 0)- hk = _user32.SetWindowsHookExW(WH_KEYBOARD_LL, kp, None, 0)- print(f"[intent] hooks armed mouse={bool(hm)} keyboard={bool(hk)}",- file=sys.stderr, flush=True)- if not (hm or hk):- return- msg = ctypes.wintypes.MSG()- while _user32.GetMessageW(ctypes.byref(msg), None, 0, 0) != 0:- _user32.TranslateMessage(ctypes.byref(msg))- _user32.DispatchMessageW(ctypes.byref(msg))- except Exception as e:- print(f"[intent] hook thread died: {e}", file=sys.stderr, flush=True)-- try:- threading.Thread(target=_pump, daemon=True, name="kicad-user-intent").start()- threading.Thread(target=_worker, daemon=True, name="kicad-intent-worker").start()- except Exception:- pass+ """Removed - see the note above. Kept as a no-op so no call site breaks."""+ return def _push_kicad_to_background(h) -> None: