← Commit history

v0.9.175: STOP FIGHTING KiCad's geometry - after 3 rounds concede the position and just keep the window behind and unfocused, instead of blinking it 20-30 times; every fight is now counted and logged in focusDebug

John Lauer ·c67114f43e ·1mo ago ·parent 1093d05
5 files changed +51−8
BRIDGE_VERSION+1−1
@@ -1 +1 @@-0.9.174+0.9.175
adom-bridge-kicad-manifest.json+5−5
@@ -1,15 +1,15 @@ {   "manifest_version": 1,   "name": "kicad",-  "version": "0.9.174",-  "url": "https://wiki.adom.inc/download/adom/kicad-bridge/0.9.174/adom-bridge-kicad-v0.9.174.zip",-  "sha256": "13daa131f7541d37af456a73176201d88ce6a194db39ff056cfe3498e9263712",-  "size": 447445,+  "version": "0.9.175",+  "url": "https://wiki.adom.inc/download/adom/kicad-bridge/0.9.175/adom-bridge-kicad-v0.9.175.zip",+  "sha256": "660e2e2043c3cbb24d9c21cb7a7f7c9c4d4f9fee32497e08b5cd3d933d8cabcd",+  "size": 448190,   "verbPrefixes": [     "kicad_"   ],   "healthEndpoint": "/status",-  "released_at": "2026-08-19T08:51:29Z",+  "released_at": "2026-08-19T08:52:49Z",   "hero": "https://wiki.adom.inc/blob/app/kicad-bridge/kicad-hero.png",   "languages": [     "Python",
adom-bridge-kicad-v0.9.175.zipadded
⋯ 1 unchanged line ⋯
bridge.json+1−1
@@ -2,7 +2,7 @@   "manifest_version": 1,   "name": "kicad",   "displayName": "KiCad EDA",-  "version": "0.9.174",+  "version": "0.9.175",   "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+44−1
@@ -355,6 +355,12 @@ def get_focus_debug() -> dict:         "parkLastError": _park_last_error,         "sweepTicks": _sweep_ticks,         "ownedPids": sorted(_owned_pids),+        # How hard did we fight KiCad, and where did we concede? (John asked for+        # 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),+        "parkFightLog": list(_park_fight_log)[-10:],+        "parkFightLimit": _PARK_FIGHT_LIMIT,     }  @@ -391,7 +397,10 @@ def get_focus_events() -> list: # capture works off-screen; the user sees nothing appear. # --------------------------------------------------------------------------- _parked: dict = {}  # hwnd -> (x, y, was_max)-_park_fights: dict = {}  # hwnd -> repark count+_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" _park_attempts = 0 _park_last_error = "" _sweep_ticks = 0@@ -821,6 +830,8 @@ 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)                             try:@@ -1006,6 +1017,37 @@ def _park_offscreen(hwnd) -> None:  # noqa: D401 - clock stamped inside         # 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@@ -1059,6 +1101,7 @@ def _unpark(hwnd, to_bottom: bool = True) -> None:     try:         pos = _parked.pop(hwnd, None)         _park_fights.pop(hwnd, None)+        _park_gaveup.discard(hwnd)         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))