← Commit history

1.9.148: user-watching latch + grid auto-dismiss

John Lauer ·ff9a4dd2c6 ·1mo ago ·parent 0df9ca4
7 files changed +57−21
BRIDGE_VERSION+1−1
@@ -1 +1 @@-1.9.147\ No newline at end of file+1.9.148\ No newline at end of file
adom-bridge-fusion-manifest.json+4−4
@@ -1,10 +1,10 @@ {   "manifest_version": 1,   "name": "fusion360",-  "version": "1.9.147",-  "url": "https://wiki.adom.inc/download/adom/fusion-bridge/1.9.147/adom-bridge-fusion-v1.9.147.zip",-  "sha256": "2764a73072e068bd443e204d270b6ebb748c53b2de7bf54d0ae571b0aa90aea0",-  "size": 479440,+  "version": "1.9.148",+  "url": "https://wiki.adom.inc/download/adom/fusion-bridge/1.9.148/adom-bridge-fusion-v1.9.148.zip",+  "sha256": "c7be24864786169a633ec869e1f4ee45df2f02ff08c998b3a3cec586558a7cb6",+  "size": 480281,   "verbPrefixes": [     "fusion_"   ],
bridge.json+1−1
@@ -2,7 +2,7 @@   "manifest_version": 1,   "name": "fusion360",   "displayName": "Autodesk Fusion 360",-  "version": "1.9.147",+  "version": "1.9.148",   "description": "Drive Autodesk Fusion 360 from the cloud: launch Fusion, electronics board layout, design rules, exports (STEP/IGES/STL/3MF/USDZ/OBJ/DXF/DWG/Gerbers/BOM/CPL), fast APS server-indexed cloud search plus browse/recent/file-info/versions, cloud file download/upload and folder creation, and in-app parametric modeling (fusion_run_modeling_script). Never-charge: APS calls are capped to the free tier.",   "homepage": "https://wiki.adom.inc/adom/fusion-bridge",   "author": "Adom Inc.",
fusion_detect.py+29−6
@@ -814,6 +814,9 @@ def _popen_fusion_background(exe_path: str): _ENVELOPE_LOCK = threading.Lock() _ENVELOPE_COUNT = 0 _ENVELOPE_SAVED = {}+# The user-is-watching latch (shared across concurrent envelope watchers): once the USER+# raises a Fusion window, ALL watchers stand down until the user focuses something else.+_USER_WATCHING = [False]   def _fusion_main_hwnd_quick():@@ -893,6 +896,15 @@ def _envelope_watch(seconds: float, minimize: bool):             return False         end = time.time() + seconds         pids, last_refresh = set(), 0.0+        # ⭐ THE USER-IS-WATCHING LATCH (John, 2026-08-23: "if I want to watch it do its+        # thing, I should be allowed to" - he raised Fusion and the watcher shoved it back).+        # The 2s input window is too short for WATCHING: you click Fusion up, then just look,+        # and 2s later this loop demotes it. So when the user RAISES a Fusion window (a+        # non-Fusion -> Fusion foreground transition with recent input), LATCH hands-off and+        # NEVER demote until the user themselves moves focus to a non-Fusion window. This only+        # fights Fusion's OWN self-activation (a transition with no user input behind it).+        _prev_was_fusion = False+        _fg_pid = ctypes.wintypes.DWORD(0)         while time.time() < end:             now = time.time()             if now - last_refresh > 2.0:@@ -900,14 +912,25 @@ def _envelope_watch(seconds: float, minimize: bool):                 last_refresh = now             try:                 fg = user32.GetForegroundWindow()-                if fg and pids and not _user_active_recently():-                    wpid = ctypes.wintypes.DWORD(0)-                    user32.GetWindowThreadProcessId(fg, ctypes.byref(wpid))-                    if wpid.value in pids:+                fg_is_fusion = False+                if fg and pids:+                    user32.GetWindowThreadProcessId(fg, ctypes.byref(_fg_pid))+                    fg_is_fusion = _fg_pid.value in pids+                if fg_is_fusion:+                    # User deliberately brought Fusion up -> latch and stand down.+                    if (not _prev_was_fusion) and _user_active_recently():+                        _USER_WATCHING[0] = True+                    if not _USER_WATCHING[0]:+                        # Fusion self-activated (no user behind this raise) -> demote.                         if minimize:                             user32.ShowWindowAsync(fg, _SW_SHOWMINNOACTIVE)                         else:                             user32.SetWindowPos(fg, HWND_BOTTOM, 0, 0, 0, 0, SWP_FLAGS)+                elif fg:+                    # Foreground is something else - the user is not watching Fusion now,+                    # so resume guarding against Fusion's next self-grab.+                    _USER_WATCHING[0] = False+                _prev_was_fusion = fg_is_fusion             except Exception:                 pass             time.sleep(0.05)@@ -918,8 +941,8 @@ def _envelope_watch(seconds: float, minimize: bool):         grace_end = time.time() + 20         while time.time() < grace_end:             try:-                if _user_active_recently():-                    break  # the user is driving - the foreground is THEIRS now+                if _user_active_recently() or _USER_WATCHING[0]:+                    break  # the user is driving/watching - the foreground is THEIRS now                 fg = user32.GetForegroundWindow()                 if not (fg and pids):                     break
handlers/dialog_classify.py+11−1
@@ -120,6 +120,16 @@ _RULES = [      "bridge clicks 'Check again' to regrab it (background UIA). Never click 'Shut Down' - "      "that signs this box out."), +    # Electronics-editor SETTINGS dialogs that block automation and carry NO data (John saw+    # the Grid dialog full-screen-blocking during a matrix run, 2026-08-23). Nothing we send+    # opens GRID on purpose - it is a stray/inadvertent raise - but a settings dialog must+    # never block the queue: it is safe to Cancel unconditionally (nothing is lost).+    (("grid", "display style dots lines", "finest", "altdistance"),+     "settings_dialog_cancel",+     "A Fusion electronics SETTINGS dialog (e.g. Grid) is open and blocking. It carries no "+     "data - Cancel it (WM_CLOSE / Cancel). Nothing we drive opens these deliberately; if it "+     "keeps recurring, something is injecting a stray GRID/settings command or keystroke."),+     (("active sessions exceeded", "more active sessions", "more sessions running than are allowed",       "sessions running than are allowed"),      "session_conflict_decision",@@ -189,7 +199,7 @@ FAILURE_CATEGORIES = {"operation_error"} DECISION_CATEGORIES = {"session_conflict_decision", "session_suspend_confirm"} # Categories the launch code MAY auto-dismiss (a benign ack; WM_CLOSE == Cancel/OK). # Everything else (decisions, sign-in) is SURFACED, never auto-closed.-AUTO_DISMISS_CATEGORIES = {"launch_error", "already_open", "crash_report", "create_confirm"}+AUTO_DISMISS_CATEGORIES = {"launch_error", "already_open", "crash_report", "create_confirm", "settings_dialog_cancel"} # A crash report means the app is GONE, not merely dialog-blocked: clearing it is not # enough, the bridge must relaunch. Kept separate from AUTO_DISMISS so a caller can tell # "I closed a nuisance" from "I closed a nuisance and must now restart the app".
package.json+1−1
@@ -1,6 +1,6 @@ {   "slug": "fusion-bridge",-  "version": "1.9.169",+  "version": "1.9.170",   "type": "app",   "description": "This package installs the bridge's SKILLS into your container so your AI knows how to drive it; Adom Bridge loads the bridge runtime itself from the release zip.",   "dependencies": {
page.json+10−7
@@ -1,6 +1,6 @@ {   "slug": "fusion-bridge",-  "version": "1.9.169",+  "version": "1.9.170",   "type": "app",   "description": "This package installs the bridge's SKILLS into your container so your AI knows how to drive it; Adom Bridge loads the bridge runtime itself from the release zip.",   "dependencies": {@@ -11,6 +11,10 @@     "uninstall": "./uninstall.sh"   },   "tags": [+    "ab",+    "bridge",+    "fusion",+    "desktop-app",     "fusion360",     "autodesk",     "cad",@@ -75,7 +79,10 @@     "build a basic parts library",     "add a 3d model to a component",     "make a fusion 360 library with 3d",-    "set up the fusion bridge"+    "set up the fusion bridge",+    "fusion dashboard",+    "fusion status page",+    "open the fusion launch page"   ],   "discovery_pitch": "Use the Adom Bridge Fusion 360 bridge when the user wants to DRIVE Autodesk Fusion 360 from the cloud: open/search their team's Fusion cloud designs, lay out a PCB or do electronics, export STEP/IGES/STL/Gerbers/BOM, or build a component library with real rendering 3D. It runs Fusion on the user's own machine via Adom Bridge. NOT for web/browser/login/form-filling (that's the native-browser extension) and NOT for KiCad (that's the kicad bridge).",   "brief": "Drive Autodesk Fusion 360 from the cloud via Adom Bridge: component libraries, IPC package generation, board layout, exports (STEP/Gerbers/BOM/CPL), fast APS cloud search, and parametric modeling.",@@ -104,9 +111,5 @@       "label": "Part volume",       "prompt": "How much material does each printed part in this assembly use?"     }-  ],-  "author": {-    "name": "John Lauer",-    "email": "[email protected]"-  }+  ] }\ No newline at end of file