← Commit history

0.9.296: Tour foreground fix (John: 'why do I not see you foregrounding any kicad window'): the beat raise called SetForegroundWindow from the bridge process and reported true regardless; Windows refuses that from a background process while the user works elsewhere. The bridge now lifts its no-activate mark, asks ab's desktop_bring_to_front to raise the beat's window once, and reports foregrounded only when the z-order afterwards shows that window on top.

John Lauer ·ce1759cef9 ·1mo ago ·parent e14b669
3 files changed +44−15
BRIDGE_VERSION+1−1
@@ -1 +1 @@-0.9.295\ No newline at end of file+0.9.296\ No newline at end of file
bridge.json+1−1
@@ -2,7 +2,7 @@   "manifest_version": 1,   "name": "kicad",   "displayName": "KiCad EDA",-  "version": "0.9.295",+  "version": "0.9.296",   "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/demo.py+42−13
@@ -587,26 +587,55 @@ def _job_update(**kw):  def _foreground_once(window_title: str) -> bool:     """Bring the window this beat opened to the front ONCE, then hands off.-    The bridge's background contract marks its KiCad windows no-activate, so-    lift that first; AttachThreadInput makes SetForegroundWindow honest from a-    background process. Never re-raised later: if the user alt-tabs away-    mid-beat, that is their desktop."""++    John, 2026-09-03: the first version called SetForegroundWindow from the+    bridge process and reported True whether or not Windows obeyed; Windows+    refuses a foreground change from a background process while the user is+    interacting elsewhere (it flashes the taskbar instead), so on his laptop+    nothing came forward and the flag lied. Now: lift the bridge's no-activate+    mark, ask AD (which owns the desktop's input and has the foreground+    unlock) to bring it forward, and REPORT ONLY WHAT THE Z-ORDER SAYS+    afterwards. Never re-raised later: if the user alt-tabs away mid-beat,+    that is their desktop."""     if not window_title:         return False     try:         from handlers.kicad_ui import _find_all_kicad_windows-        from handlers import win_focus+        from handlers import win_focus, ad_client, kicad_windows+        target = None         for w in _find_all_kicad_windows():             if window_title.lower() in (w.get("title") or "").lower():-                h = w["hwnd"]-                try:-                    win_focus.allow_activation(h)-                except Exception:  # pylint: disable=broad-except-                    pass-                return bool(win_focus._set_foreground_robust(h))+                target = int(w["hwnd"]); break+        if not target:+            return False+        try:+            win_focus.allow_activation(target)+        except Exception:  # pylint: disable=broad-except+            pass+        raised = False+        try:+            r = ad_client.call("desktop_bring_to_front",+                               {"hwnd": target,+                                "reason": f"KiCad tour: show the user the {window_title} this beat opened, once"},+                               timeout=8) or {}+            raised = bool(r.get("success")) and "unknown verb" not in str(r.get("error") or "").lower()+        except Exception:  # pylint: disable=broad-except+            raised = False+        if not raised:+            try:+                win_focus._set_foreground_robust(target)+            except Exception:  # pylint: disable=broad-except+                pass+        import time as _t+        for _ in range(4):                       # up to ~1 s for the z-order to settle+            _t.sleep(0.25)+            rows = [x for x in (kicad_windows.find(all_windows=True, fresh=True) or []) if x.get("z") is not None]+            if rows and int(sorted(rows, key=lambda x: x["z"])[0].get("hwnd") or 0) == target:+                return True+        return False     except Exception:  # pylint: disable=broad-except-        pass-    return False+        return False+   def _beat_shot(kicad_info: dict, step_label: str, window_title: str) -> dict | None: