← Commit history

v0.9.179: fixes wiki issue #33 - the focus GUARDIAN demoted a self-raising window on every 100ms tick with no debounce (10x/sec flicker + CPU burn). It now concedes after 3 rounds and holds the window below instead

John Lauer ·b83ce3affa ·1mo ago ·parent 96e74a9
5 files changed +41−8
BRIDGE_VERSION+1−1
@@ -1 +1 @@-0.9.178+0.9.179
adom-bridge-kicad-manifest.json+5−5
@@ -1,15 +1,15 @@ {   "manifest_version": 1,   "name": "kicad",-  "version": "0.9.178",-  "url": "https://wiki.adom.inc/download/adom/kicad-bridge/0.9.178/adom-bridge-kicad-v0.9.178.zip",-  "sha256": "c69c075b4cce46fd4478185e5485f21ae434a42a4519e0415d6e23506fdd3854",-  "size": 452395,+  "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,   "verbPrefixes": [     "kicad_"   ],   "healthEndpoint": "/status",-  "released_at": "2026-08-19T09:22:18Z",+  "released_at": "2026-08-19T17:45:00Z",   "hero": "https://wiki.adom.inc/blob/app/kicad-bridge/kicad-hero.png",   "languages": [     "Python",
adom-bridge-kicad-v0.9.179.zipadded
⋯ 1 unchanged line ⋯
bridge.json+1−1
@@ -2,7 +2,7 @@   "manifest_version": 1,   "name": "kicad",   "displayName": "KiCad EDA",-  "version": "0.9.178",+  "version": "0.9.179",   "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+34−1
@@ -359,6 +359,8 @@ def get_focus_debug() -> dict:         # exactly this: "log what happens on how often you blink the window".)         "parkFights": {str(k): v for k, v in list(_park_fights.items())[-20:]},         "parkGaveUp": len(_park_gaveup),+        "guardianDemotes": {str(k): v for k, v in list(_demote_counts.items())[-20:]},+        "guardianDemoteLimit": _DEMOTE_LIMIT,         "parkFightLog": list(_park_fight_log)[-10:],         "parkFightLimit": _PARK_FIGHT_LIMIT,     }@@ -401,6 +403,8 @@ _park_fights: dict = {}   # hwnd -> how many times we re-parked it _park_gaveup: set = set()  # windows we STOPPED fighting (see _PARK_FIGHT_LIMIT) _park_fight_log: list = []  # rolling record: what we fought, how hard, and why _PARK_FIGHT_LIMIT = 3      # John, 2026-08-19: "you blink them 20 or 30 times"+_demote_counts: dict = {}  # hwnd -> demotes by the GUARDIAN (wiki issue #33)+_DEMOTE_LIMIT = 3          # then hold it down once and stop re-demoting _park_attempts = 0 _park_last_error = "" _sweep_ticks = 0@@ -1102,6 +1106,7 @@ def _unpark(hwnd, to_bottom: bool = True) -> None:         pos = _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))@@ -1425,7 +1430,35 @@ def restore_foreground_persistent(hwnd, seconds: float = 6.0, interval: float =                         _sweep_suppress_all()   # catch windows CREATED mid-verb (~every 0.5s)                     fg = _user32.GetForegroundWindow()                     if fg and is_kicad_window(fg):-                        _push_kicad_to_background(fg)+                        # WIKI ISSUE #33 (Colby Knox, independently of John's+                        # report of the same class of bug): a just-launched wx+                        # frame Raise()es itself repeatedly while it loads, and+                        # this loop demoted it EVERY tick with no debounce - so+                        # KiCad raised, we demoted, KiCad raised... at 10x/sec.+                        # That is the visible "epileptic" flicker and the CPU+                        # burn. One demote that STICKS is enough: after a few+                        # 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)                     # 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.)