← Commit history

v0.9.181: backgrounding is now ONE SetWindowPos to the bottom, once per window, only if that window took the foreground. No minimize (breaks screenshots), no off-screen parking (removed), no WS_EX_NOACTIVATE (blocks the user's own click), no repetition

John Lauer ·051d894377 ·1mo ago ·parent 0990ca9
6 files changed +91−82
BRIDGE_VERSION+1−1
@@ -1 +1 @@-0.9.180+0.9.181
adom-bridge-kicad-manifest.json+5−5
@@ -1,15 +1,15 @@ {   "manifest_version": 1,   "name": "kicad",-  "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,+  "version": "0.9.181",+  "url": "https://wiki.adom.inc/download/adom/kicad-bridge/0.9.181/adom-bridge-kicad-v0.9.181.zip",+  "sha256": "5d507ca02975d9470e9b3e2ee7e846fab6fa54087f0f7d54eca862e9eff80f55",+  "size": 452249,   "verbPrefixes": [     "kicad_"   ],   "healthEndpoint": "/status",-  "released_at": "2026-08-19T18:03:58Z",+  "released_at": "2026-08-19T18:17:15Z",   "hero": "https://wiki.adom.inc/blob/app/kicad-bridge/kicad-hero.png",   "languages": [     "Python",
adom-bridge-kicad-v0.9.181.zipadded
⋯ 1 unchanged line ⋯
bridge.json+1−1
@@ -2,7 +2,7 @@   "manifest_version": 1,   "name": "kicad",   "displayName": "KiCad EDA",-  "version": "0.9.180",+  "version": "0.9.181",   "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+75−75
@@ -249,50 +249,64 @@ def _demote_below(kicad_hwnd, user_hwnd) -> None:   def _sweep_suppress_all() -> None:-    """Pre-suppress EVERY visible KiCad top-level window — so a window's FIRST-    self-activation is refused too, not just its repeat offenses. (v0.9.102: the-    0.9.101 proof run still showed exactly one visible steal per fresh window —-    the free first offense. This closes it.)"""-    try:-        import ctypes as _ct-        WNDENUMPROC = _ct.WINFUNCTYPE(_ct.c_bool, _ct.wintypes.HWND, _ct.wintypes.LPARAM)-        def cb(h, _l):-            try:-                if _user32.IsWindowVisible(h) and is_kicad_window(h):-                    suppress_activation(h)-            except Exception:-                pass-            return True-        cbp = WNDENUMPROC(cb)-        _user32.EnumWindows(cbp, 0)-    except Exception:-        pass+    """REMOVED (0.9.181). This used to stamp WS_EX_NOACTIVATE on every visible+    KiCad window so none could self-activate. That style also blocks the USER's+    click, which John called out as rude: they may want the window in front the+    very next second. Prevention is not worth taking the user's click away."""+    return++++_backgrounded_once: set = set()   # hwnds already handled - never touched again+_bg_lock = None+try:+    import threading as _thr2+    _bg_lock = _thr2.Lock()+except Exception:+    pass   def _push_kicad_to_background(h) -> None:-    """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."""+    """THE ENTIRE BACKGROUNDING ALGORITHM (0.9.181). John's rules, verbatim:++      "never fucking minimize it, cuz then you can't take screenshots of the+       window when it's in the background"+      "ONLY background the window once IF IT APPEARS IN THE FOREGROUND. then do+       nothing else"+      "if you are waiting 2 seconds to let the user activate the window that's+       rude as fuck. a user sometimes wants to immediately foreground it"++    So, for a window that actually took the foreground, exactly one action:++        SetWindowPos(hwnd, HWND_BOTTOM, SWP_NOACTIVATE|SWP_NOMOVE|SWP_NOSIZE)++    and nothing else, ever. In particular:+      - NEVER minimize. A minimized window cannot be screenshotted, which breaks+        every screenshot-based verb in this bridge.+      - NEVER move it (no off-screen parking - removed entirely, see below).+      - NEVER set WS_EX_NOACTIVATE. It blocks the user's own click, and a user+        may want the window in front the very next second. Their click must work+        immediately, with no hold-off window.+      - NEVER act on the same window twice. One z-order change is invisible;+        repeating anything is what produced the strobing.+    """     try:-        suppress_activation(h)          # style only - no visible change+        if _bg_lock:+            with _bg_lock:+                if h in _backgrounded_once:+                    return+                _backgrounded_once.add(h)+        elif h in _backgrounded_once:+            return+        else:+            _backgrounded_once.add(h)+         HWND_BOTTOM = 1         SWP_NOACTIVATE, SWP_NOMOVE, SWP_NOSIZE = 0x0010, 0x0002, 0x0001         _user32.SetWindowPos(h, HWND_BOTTOM, 0, 0, 0, 0,                              SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE)+        print(f"[bg] {h}: sent behind once (z-order only). Hands off from here.",+              file=sys.stderr, flush=True)     except Exception:         pass @@ -368,6 +382,8 @@ def get_focus_debug() -> dict:         "parkGaveUp": len(_park_gaveup),         "guardianDemotes": {str(k): v for k, v in list(_demote_counts.items())[-20:]},         "guardianDemoteLimit": _DEMOTE_LIMIT,+        "backgroundedOnce": len(_backgrounded_once),+        "rule": "each window is backgrounded EXACTLY once, then never touched again",         "parkFightLog": list(_park_fight_log)[-10:],         "parkFightLimit": _PARK_FIGHT_LIMIT,     }@@ -841,10 +857,13 @@ def _start_park_sweep() -> None:                             if h not in _bridge_windows:                                 _bridge_windows.add(h)                                 _save_ledger()-                            if h in _park_gaveup:-                                continue   # conceded: never blink it again-                            suppress_activation(h)-                            _park_offscreen(h)+                            # ONLY act if this window actually took the+                            # foreground. A window that opened quietly behind+                            # the user's work needs nothing from us (John:+                            # "ONLY background the window once IF IT APPEARS IN+                            # THE FOREGROUND. then do nothing else").+                            if _user32.GetForegroundWindow() == h:+                                _push_kicad_to_background(h)                             try:                                 from handlers.win_taskbar import apply_adom_badge                                 apply_adom_badge(h)  # "adom is driving this window"@@ -861,9 +880,10 @@ def _start_park_sweep() -> None:                         unpark_all()                         quiet_since = None                     else:-                        for h in list(_parked.keys()):-                            if not _user32.IsIconic(h):-                                _park_offscreen(h)+                        # 0.9.181: this used to RE-PARK every quiet window on a+                        # 50ms tick. Repetition is exactly what strobed. Windows+                        # are handled once, by _push_kicad_to_background, and+                        # then left alone.                         _t.sleep(0.05)                         continue                     _t.sleep(0.1)@@ -1006,17 +1026,10 @@ def _ensure_window_icon(hwnd) -> None:   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())+    """REMOVED (0.9.181). Off-screen parking moved a window to (-32000,-32000)+    and later moved it back; John asked for it originally, it never delivered,+    and every move is a visible frame. Kept only as a name so older call sites+    do not break - it now does the ONE sanctioned thing, or nothing."""     _push_kicad_to_background(hwnd)  @@ -1122,8 +1135,11 @@ def start_focus_event_hook() -> None:                 return  # the user's own KiCad: not ours to park or bounce             print(f"[focus-hook] event=0x{event:04x} hwnd={hwnd} inflight={_verb_inflight} guards={_guard_count}", file=sys.stderr)             if event == EVENT_OBJECT_SHOW:-                suppress_activation(hwnd)-                _park_offscreen(hwnd)+                # A window appearing is NOT a foreground steal. Only act if it+                # actually took the foreground (John: "ONLY background the+                # window once IF IT APPEARS IN THE FOREGROUND").+                if _user32.GetForegroundWindow() == hwnd:+                    _push_kicad_to_background(hwnd)             elif event == EVENT_SYSTEM_FOREGROUND:                 _push_kicad_to_background(hwnd)                 try:@@ -1361,25 +1377,9 @@ def restore_foreground_persistent(hwnd, seconds: float = 6.0, interval: float =                         # rounds we hold the window down (bottom of z-order,                         # activation suppressed) and stop re-demoting it, which                         # also keeps the loop off the UI frame rate.-                        n = _demote_counts.get(fg, 0) + 1-                        _demote_counts[fg] = n-                        if n <= _DEMOTE_LIMIT:-                            _push_kicad_to_background(fg)-                            if n == _DEMOTE_LIMIT:-                                suppress_activation(fg)-                                HWND_BOTTOM = 1-                                _user32.SetWindowPos(fg, HWND_BOTTOM, 0, 0, 0, 0,-                                                     0x0010 | 0x0001 | 0x0002)-                                _park_fight_log.append({-                                    "t": __import__("time").time(), "hwnd": int(fg),-                                    "fights": n,-                                    "action": "guardian conceded - holding it below "-                                              "instead of re-demoting (issue #33)",-                                })-                                del _park_fight_log[:-40]-                                print(f"[guard] {fg}: stopped re-demoting after {n} "-                                      f"rounds; it self-raises while loading, so it "-                                      f"is held below instead", file=sys.stderr, flush=True)+                        # One funnel, one action per window, for its whole+                        # life. No counting, no escalation, no second chance.+                        _push_kicad_to_background(fg)                     # NON-KiCad foreground = the user's business. We never track,                     # never adopt, never raise it. (v0.9.103's conditional restore                     # was still user-window surgery — removed entirely.)
server.py+9
@@ -1173,6 +1173,15 @@ def main():     def _boot_maintenance():         import time as _t         _t.sleep(2)  # let the port bind and the first verbs answer first+        # 0.9.181: also clear WS_EX_NOACTIVATE left behind by OLDER versions -+        # that style blocks the USER's own click and could persist indefinitely.+        try:+            n = win_focus.allow_activation_all()+            if n:+                print(f"[boot] cleared stale activation blocks on {n} window(s)",+                      file=sys.stderr, flush=True)+        except Exception:+            pass         for fn in ("load_owned_ledger", "heal_offscreen_windows",                    "heal_window_usability", "sanitize_kicad_session",                    "_start_park_sweep"):