← Commit history

v0.9.183 URGENT: 0.9.182's low-level mouse hook did window lookups and SetWindowLongPtr INSIDE the system input path, which stutters or freezes the cursor machine-wide. The hook now only timestamps; all work moved to a worker thread

John Lauer ·91766f179d ·1mo ago ·parent 28800f0
5 files changed +56−18
BRIDGE_VERSION+1−1
@@ -1 +1 @@-0.9.182+0.9.183
adom-bridge-kicad-manifest.json+5−5
@@ -1,15 +1,15 @@ {   "manifest_version": 1,   "name": "kicad",-  "version": "0.9.182",-  "url": "https://wiki.adom.inc/download/adom/kicad-bridge/0.9.182/adom-bridge-kicad-v0.9.182.zip",-  "sha256": "4933a1adfd550d7ed30460d35e76c97ba2d968f28949bf7c8f68ceb0422553c6",-  "size": 454594,+  "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,   "verbPrefixes": [     "kicad_"   ],   "healthEndpoint": "/status",-  "released_at": "2026-08-19T18:26:38Z",+  "released_at": "2026-08-19T18:34:11Z",   "hero": "https://wiki.adom.inc/blob/app/kicad-bridge/kicad-hero.png",   "languages": [     "Python",
adom-bridge-kicad-v0.9.183.zipadded
⋯ 1 unchanged line ⋯
bridge.json+1−1
@@ -2,7 +2,7 @@   "manifest_version": 1,   "name": "kicad",   "displayName": "KiCad EDA",-  "version": "0.9.182",+  "version": "0.9.183",   "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+49−11
@@ -288,6 +288,13 @@ except Exception: # 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 @@ -346,17 +353,19 @@ def start_user_intent_hooks() -> None:             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):-                pt = ctypes.wintypes.POINT()-                _user32.GetCursorPos(ctypes.byref(pt))-                under = _user32.WindowFromPoint(pt)-                root = _user32.GetAncestor(under, 2) if under else None   # GA_ROOT-                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")+                _intent_events.append(("click", _clock()))         except Exception:             pass         return _user32.CallNextHookEx(None, code, wparam, lparam)@@ -365,12 +374,40 @@ def start_user_intent_hooks() -> None:         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 and (_user32.GetAsyncKeyState(VK_MENU) & 0x8000):-                    _note_user_intent("alt-tab")+                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)@@ -391,6 +428,7 @@ def start_user_intent_hooks() -> None:      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