← Commit history

v0.9.180: NEVER blip. Remove the minimize-bounce and off-screen parking entirely - both are visible frames by construction, so capping their count could never reach zero. Backgrounding is now style + z-order only, and activation is released the moment the bridge goes idle

John Lauer ·0990ca9025 ·1mo ago ·parent b83ce3a
5 files changed +57−136
BRIDGE_VERSION+1−1
@@ -1 +1 @@-0.9.179+0.9.180
adom-bridge-kicad-manifest.json+5−5
@@ -1,15 +1,15 @@ {   "manifest_version": 1,   "name": "kicad",-  "version": "0.9.179",-  "url": "https://wiki.adom.inc/download/adom/kicad-bridge/0.9.179/adom-bridge-kicad-v0.9.179.zip",-  "sha256": "e71abb1dfec300ec654b5a99b0f15f21d4809c592e644faf2cafe3a62fbdfca0",-  "size": 452970,+  "version": "0.9.180",+  "url": "https://wiki.adom.inc/download/adom/kicad-bridge/0.9.180/adom-bridge-kicad-v0.9.180.zip",+  "sha256": "6f1da6a951115abab10602076dfc15aadb31933e0ee3b5e10cebcbbe8c618176",+  "size": 452129,   "verbPrefixes": [     "kicad_"   ],   "healthEndpoint": "/status",-  "released_at": "2026-08-19T17:45:00Z",+  "released_at": "2026-08-19T18:03:58Z",   "hero": "https://wiki.adom.inc/blob/app/kicad-bridge/kicad-hero.png",   "languages": [     "Python",
adom-bridge-kicad-v0.9.180.zipadded
⋯ 1 unchanged line ⋯
bridge.json+1−1
@@ -2,7 +2,7 @@   "manifest_version": 1,   "name": "kicad",   "displayName": "KiCad EDA",-  "version": "0.9.179",+  "version": "0.9.180",   "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+50−129
@@ -270,22 +270,29 @@ def _sweep_suppress_all() -> None:   def _push_kicad_to_background(h) -> None:-    """v0.9.104 (John, verbatim design): "you just always have to only deal with-    the windows you opened from kicad" — every action here targets the KICAD-    window ONLY. If it holds the foreground, MINIMIZE-BOUNCE it: minimizing the-    foreground window makes WINDOWS ITSELF activate the user's previous window-    (we never locate, choose, or raise any user window), then show it again-    unfocused, and drop it to the bottom of the z-order."""-    import time as _t+    """Put a KiCad window BEHIND, without ever making it blink.++    0.9.180 (John, 2026-08-19, after seeing it again: "you make the window blip+    at me like it foregrounds and then it backgrounds and then it fg's and then+    it bg's - don't EVER blip it"). He is right and the old design could not+    satisfy him: it MINIMIZE-BOUNCED (SW_MINIMIZE, then SW_SHOWNOACTIVATE, then+    z-bottom). A minimize is one visible frame and the restore is another, so+    every single call was a blip BY CONSTRUCTION. Capping how many times we did+    it (0.9.175, 0.9.179) only reduced the number of blips; it could never reach+    zero.++    The only way to background a window with no visible transition is to change+    NOTHING the compositor animates: no minimize, no restore, no move, no size.+    Just z-order, once, with SWP_NOACTIVATE - plus WS_EX_NOACTIVATE so Windows+    refuses the window's own activation attempts in the first place. If the+    window still holds the foreground after that, we LEAVE IT: a window that is+    briefly in front is infinitely better than one that strobes."""     try:-        suppress_activation(h)-        if _user32.GetForegroundWindow() == h:-            _user32.ShowWindow(h, 6)   # SW_MINIMIZE -> OS returns focus naturally-            _t.sleep(0.05)-            _user32.ShowWindow(h, 4)   # SW_SHOWNOACTIVATE -> visible again, unfocused+        suppress_activation(h)          # style only - no visible change         HWND_BOTTOM = 1-        SWP = 0x0010 | 0x0002 | 0x0001  # NOACTIVATE|NOMOVE|NOSIZE-        _user32.SetWindowPos(h, HWND_BOTTOM, 0, 0, 0, 0, SWP)+        SWP_NOACTIVATE, SWP_NOMOVE, SWP_NOSIZE = 0x0010, 0x0002, 0x0001+        _user32.SetWindowPos(h, HWND_BOTTOM, 0, 0, 0, 0,+                             SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE)     except Exception:         pass @@ -909,8 +916,16 @@ def begin_gui_verb() -> None:   def end_gui_verb() -> None:+    """0.9.180: the moment the bridge is idle, hand every window back. A stale+    WS_EX_NOACTIVATE is what makes a taskbar click do nothing (John hit exactly+    that), so it must never outlive the work that justified it."""     global _verb_inflight     _verb_inflight = max(0, _verb_inflight - 1)+    if _verb_inflight == 0 and _guard_count == 0:+        try:+            allow_activation_all()+        except Exception:+            pass   class _WINDOWPLACEMENT(ctypes.Structure if IS_WINDOWS else object):@@ -990,89 +1005,19 @@ def _ensure_window_icon(hwnd) -> None:         pass  -def _park_offscreen(hwnd) -> None:  # noqa: D401 - clock stamped inside-    """Move a window fully off the visible desktop WITHOUT activation.-    (Instrumented: attempts + last error surface in focusDebug.)-    SetWindowPos silently cannot move a MAXIMIZED window (the -13,-13 no-op,-    2026-08-17), so parking goes through SetWindowPlacement: demote to-    normal-size at -32000 with SW_SHOWNOACTIVATE, remembering whether it was-    maximized so unpark can honor it."""-    global _park_attempts, _park_last_error+def _park_offscreen(hwnd) -> None:+    """RETIRED in 0.9.180 - kept as a no-visible-motion shim.++    Parking used to move the window to (-32000,-32000) and later back, and+    escalated to minimize-first when KiCad fought it. Both are visible frames,+    which is exactly the blinking John reported twice. Backgrounding no longer+    moves windows AT ALL: suppress activation (a style change, invisible) and+    drop to the bottom of the z-order (invisible unless it was on top). Nothing+    to unpark, nothing to restore, nothing to flicker."""+    global _park_attempts+    _park_attempts += 1     _park_since.setdefault(hwnd, __import__("time").monotonic())-    try:-        _park_attempts += 1-        if not _user32.IsWindow(hwnd):-            return-        r = ctypes.wintypes.RECT()-        _user32.GetWindowRect(hwnd, ctypes.byref(r))-        if r.left <= -20000:-            return  # already off-screen-        wp = _WINDOWPLACEMENT()-        wp.length = ctypes.sizeof(_WINDOWPLACEMENT)-        _user32.GetWindowPlacement(hwnd, ctypes.byref(wp))-        SW_SHOWMAXIMIZED = 3-        was_max = wp.showCmd == SW_SHOWMAXIMIZED-        if hwnd not in _parked:-            _parked[hwnd] = (r.left, r.top, was_max)-        # ESCALATION: an app that keeps reasserting its geometry (eeschema-        # during load re-maximizes every ~120ms) turns positional parking into-        # visible flicker — after a few fights, minimize it without activation-        # instead. wx does not un-minimize itself; the taskbar button still-        # works; the screenshot path restores off-screen for capture.-        _ensure_window_icon(hwnd)  # never freeze a blank icon into the taskbar-        _park_fights[hwnd] = _park_fights.get(hwnd, 0) + 1-        n = _park_fights[hwnd]--        # STOP FIGHTING (0.9.175). KiCad reasserts its own geometry while it-        # loads, so a 50ms sweep that re-parks every time turns into 20-30-        # visible blinks - John watched it happen and it reads as broken-        # software. We cannot win that argument and should not try: after-        # _PARK_FIGHT_LIMIT rounds we concede the POSITION and settle for the-        # thing that actually matters - the window stays BEHIND and never takes-        # focus. Quiet and slightly visible beats invisible and seizuring.-        if n > _PARK_FIGHT_LIMIT:-            if hwnd not in _park_gaveup:-                _park_gaveup.add(hwnd)-                _park_fight_log.append({-                    "t": __import__("time").time(), "hwnd": int(hwnd), "fights": n,-                    "action": "conceded position - keeping it behind instead",-                })-                del _park_fight_log[:-40]-                print(f"[park] {hwnd}: conceded after {n} rounds - KiCad keeps "-                      f"reasserting geometry; leaving it in place, behind, "-                      f"unfocused", file=sys.stderr, flush=True)-            suppress_activation(hwnd)-            HWND_BOTTOM = 1-            _user32.SetWindowPos(hwnd, HWND_BOTTOM, 0, 0, 0, 0,-                                 0x0010 | 0x0001 | 0x0002)  # NOACTIVATE|NOSIZE|NOMOVE-            _parked.pop(hwnd, None)-            _park_since.pop(hwnd, None)-            return-        if n > 1:-            _park_fight_log.append({"t": __import__("time").time(), "hwnd": int(hwnd),-                                    "fights": n, "action": "re-parked"})-            del _park_fight_log[:-40]-        if _park_fights[hwnd] >= 1:  # minimize from first sighting: eeschema-            # recreates hwnds during load, resetting any higher threshold, and-            # every positional fight round is a visible flicker. Minimize wins-            # instantly and wx never un-minimizes itself.-            wp.flags = 2 if was_max else 0  # WPF_RESTORETOMAXIMIZED-            wp.showCmd = 7  # SW_SHOWMINNOACTIVE-            _user32.SetWindowPlacement(hwnd, ctypes.byref(wp))-            return-        wp.showCmd = SW_SHOWNOACTIVATE  # 4-        w = max(200, wp.rcNormalPosition.right - wp.rcNormalPosition.left)-        h = max(150, wp.rcNormalPosition.bottom - wp.rcNormalPosition.top)-        wp.rcNormalPosition.left = -32000-        wp.rcNormalPosition.top = -32000-        wp.rcNormalPosition.right = -32000 + w-        wp.rcNormalPosition.bottom = -32000 + h-        wp.flags = 0-        ok = _user32.SetWindowPlacement(hwnd, ctypes.byref(wp))-        if not ok:-            _park_last_error = f"SetWindowPlacement rc=0 lasterr={ctypes.get_last_error()}"-    except Exception as e:-        _park_last_error = f"{type(e).__name__}: {e}"+    _push_kicad_to_background(hwnd)   def _sane_position(hwnd) -> None:@@ -1102,42 +1047,18 @@ def _sane_position(hwnd) -> None:   def _unpark(hwnd, to_bottom: bool = True) -> None:+    """0.9.180: nothing is moved any more, so "unparking" only means handing the+    window back to the user - clear the activation block, make sure it has a+    taskbar button and an icon, and leave its geometry exactly where KiCad put+    it. No SetWindowPlacement, no restore, no motion."""     try:-        pos = _parked.pop(hwnd, None)+        _parked.pop(hwnd, None)         _park_fights.pop(hwnd, None)-        _park_gaveup.discard(hwnd)-        _demote_counts.pop(hwnd, None)-        if pos is None or not _user32.IsWindow(hwnd):-            return-        x, y, was_max = (pos if len(pos) == 3 else (pos[0], pos[1], False))-        wp = _WINDOWPLACEMENT()-        wp.length = ctypes.sizeof(_WINDOWPLACEMENT)-        _user32.GetWindowPlacement(hwnd, ctypes.byref(wp))-        w = max(200, wp.rcNormalPosition.right - wp.rcNormalPosition.left)-        h = max(150, wp.rcNormalPosition.bottom - wp.rcNormalPosition.top)-        wp.rcNormalPosition.left = x-        wp.rcNormalPosition.top = y-        wp.rcNormalPosition.right = x + w-        wp.rcNormalPosition.bottom = y + h-        if was_max and to_bottom:-            # can't re-maximize without activation: hand it to the taskbar,-            # minimized with restore-to-maximized — the user's click brings it-            # back exactly like a normal maximized window (John's contract).-            WPF_RESTORETOMAXIMIZED = 2-            wp.flags = WPF_RESTORETOMAXIMIZED-            wp.showCmd = 7  # SW_SHOWMINNOACTIVE-            _user32.SetWindowPlacement(hwnd, ctypes.byref(wp))-        else:-            wp.flags = 0-            wp.showCmd = SW_SHOWNOACTIVATE-            _user32.SetWindowPlacement(hwnd, ctypes.byref(wp))-            if to_bottom:-                HWND_BOTTOM = 1-                _user32.SetWindowPos(hwnd, HWND_BOTTOM, 0, 0, 0, 0,-                                     0x0010 | 0x0001 | 0x0002)  # NOACTIVATE|NOSIZE|NOMOVE-            _sane_position(hwnd)   # never leave it stranded off-screen         _park_since.pop(hwnd, None)-        _ensure_window_icon(hwnd)  # a fresh button must not read as a blank doc+        if not _user32.IsWindow(hwnd):+            return+        allow_activation(hwnd)+        _ensure_window_icon(hwnd)         try:             from handlers.win_taskbar import apply_adom_badge             apply_adom_badge(hwnd)