← Commit history

v1.9.38: envelope-proof update detection + readiness offers

John Lauer ·af37abefcf · ·parent 047fdc3
4 files changed +51−9
BRIDGE_VERSION+1−1
@@ -1 +1 @@-1.9.37\ No newline at end of file+1.9.38\ 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.37",-    "url": "https://wiki.adom.inc/download/adom/fusion-bridge/1.9.37/adom-bridge-fusion-v1.9.37.zip",-    "sha256": "478062c241e344a8258dacde34281ff237217557ec039af24dc167caa88d8d4a",-    "size": 384345,+    "version": "1.9.38",+    "url": "https://wiki.adom.inc/download/adom/fusion-bridge/1.9.38/adom-bridge-fusion-v1.9.38.zip",+    "sha256": "128cd2992aab0db5274f67acc9fd452f5d757777bcea8c15dd0c18d14308f869",+    "size": 384995,     "verbPrefixes": [         "fusion_"     ],
bridge.json+1−1
@@ -2,7 +2,7 @@   "manifest_version": 1,   "name": "fusion360",   "displayName": "Autodesk Fusion 360",-  "version": "1.9.37",+  "version": "1.9.38",   "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.",
server.py+45−3
@@ -1445,6 +1445,21 @@ def _handle_fusion_readiness(fusion_info: dict, args: dict) -> dict:         # always want the latest fusion... make this generally invisible to me"). Fusion nags         # constantly; we click it in the BACKGROUND so the user never sees it. Sticky opt-out.         _upd = _apply_fusion_update_silently()+        if _LAST_UPDATE_OFFER.get("offered"):+            out["fusionUpdateOffered"] = True+            out["fusionUpdateHandled"] = bool(_upd.get("applied"))+            if _upd.get("applied"):+                out.setdefault("_hints", []).append(+                    "Fusion offered an update and I already clicked %r in the background - "+                    "tell the user it is handled, they never need to touch the nag."+                    % _upd.get("button"))+            else:+                out.setdefault("_hints", []).append(+                    "Fusion is OFFERING AN UPDATE and auto-update is %s. OFFER to run it for "+                    "the user ('want me to update Fusion in the background?') - "+                    "fusion_set_auto_update {enabled:true} makes it permanent, or a one-off "+                    "background click happens on the next sweep after they say yes."+                    % ("opted out" if not _auto_update_enabled() else "on but the click failed"))         # APS is checked on EVERY launch now (John, 2026-07-22: "why aren't those just driven from         # 1 login?"). Fusion sign-in and APS are both Autodesk logins; the user should authenticate         # ONCE. They cannot share a token (Autodesk DPAPI-encrypts Fusion's store), but they CAN@@ -4063,6 +4078,31 @@ def _auto_update_enabled() -> bool: _UPDATE_BUTTONS = ("Update Now", "Update now", "Install Now", "Restart Now")  +def _find_controls_any(obj, depth=0):+    """Envelope-proof extraction of UIA find_control results: ab has moved best/controls+    across response levels between versions (same drift family as coordMap). Returns+    {best, controls} from anywhere in the response, parsing stringified output."""+    if depth > 5:+        return {}+    if isinstance(obj, str) and obj[:1] in "[{":+        try:+            import json as _j+            return _find_controls_any(_j.loads(obj), depth + 1)+        except Exception:+            return {}+    if isinstance(obj, dict):+        if isinstance(obj.get("best"), dict) or isinstance(obj.get("controls"), list):+            return {"best": obj.get("best") or {}, "controls": obj.get("controls") or []}+        for k in ("data", "output", "result", "response"):+            got = _find_controls_any(obj.get(k), depth + 1)+            if got:+                return got+    return {}+++_LAST_UPDATE_OFFER = {"offered": False, "at": 0}++ def _find_update_offer(hwnd=None) -> dict:     """Is Fusion offering an update right now? Detected via UIA (background, no foreground).     Returns {offered, button, hwnd, detail} - never raises."""@@ -4083,9 +4123,11 @@ def _find_update_offer(hwnd=None) -> dict:             pass         for h in candidates:             for label in _UPDATE_BUTTONS:-                r = _unwrap(_ad_call("desktop_find_control", {"hwnd": h, "name": label}, timeout=20))+                r = _find_controls_any(_ad_call("desktop_find_control", {"hwnd": h, "name": label}, timeout=20))                 best = r.get("best") or {}                 if best.get("name") and best.get("invokable"):+                    import time as _ut+                    _LAST_UPDATE_OFFER.update(offered=True, at=_ut.time(), button=best.get("name"))                     return {"offered": True, "button": best.get("name"), "hwnd": h,                             "detail": "Fusion is offering an update ('%s' is on screen)" % best.get("name")}         return {"offered": False, "hwnd": hw}@@ -4805,8 +4847,8 @@ def _advance_credentials_page(hwnd, email) -> dict:     import time as _t     out = {"advanced": False, "wall": "credentials", "detail": ""}     try:-        found = _unwrap(_ad_call("desktop_find_control", {"hwnd": int(hwnd), "role": "edit"}, timeout=40))-        ctrls = found.get("controls") or found.get("matches") or []+        found = _find_controls_any(_ad_call("desktop_find_control", {"hwnd": int(hwnd), "role": "edit"}, timeout=40))+        ctrls = found.get("controls") or []         if not ctrls and found.get("best"):             ctrls = [found["best"]]         pwd = [c for c in ctrls if "password" in str(c.get("name", "")).lower()