← 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

John Lauer ·7969a4f871 ·1mo ago ·parent 91766f1
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: