From 4e9f215ed23feb5f3cf05ce6aeeca7015418f5db Mon Sep 17 00:00:00 2001 From: ymote <151983+ymote@users.noreply.github.com> Date: Fri, 9 Oct 2026 17:58:20 -0700 Subject: [PATCH 1/3] Pin makepad d653bc44 / Octoscript-Makepad 6cf2d90a (14th sweep) Makepad main d653bc44 carries the end of the per-app Splash runtime gates under the ruling of 8 October 2026 (#450): #117 drops the URL, media, socket and instruction-budget gates, and #118 the capability check before a host.request (option (c), confirmed by the product owner). The storage jail, inert password fields and the runtime's own checks on location, saved lists, the camera preview and agent.notify stay; host services that need a grant check it themselves (the device service checks the app's grant and the person's device consent for every camera, microphone and location call). It also carries #110-#116: renderer, Android, Linux and TextFlow work. The runtime overlay is rebased onto the new base: 34 patches, tree 0e7e0dac. Resolved by hand: - Settings patch, Android IME: upstream defers HideTextIME (pending_ime_hide); ShowTextIME still skips a dismissed keyboard and publishes a new editor session, and now also cancels a pending hide; HideTextIME publishes the editor before deferring. - Settings patch, GL program cache: the validation state (NeedsValidation, Supported, Disabled, the probe of a fresh export) is applied to upstream's rewritten cache functions. - host-json-boundary and host-response-json: the capability pre-check they edited is gone. - host-response-json's raw-envelope test drops its refusal mode (the runtime no longer builds that envelope); host-api-runtime-fixes' nested callback test has the host refuse the location sample instead. makepad-map-script-api.patch is dropped: makepad#113 merged it. video-api-smoke no longer expects the runtime to refuse an unlisted URL. Co-Authored-By: Claude Opus 5.5 --- Cargo.lock | 1 + Cargo.toml | 50 +- apps/appcard/native-runtime.lock.json | 2 +- crates/video-api-smoke/src/main.rs | 4 +- docs/architecture.md | 4 +- docs/architecture.zh-CN.md | 4 +- docs/mail-composable-cards.md | 2 +- docs/mail-composable-cards.zh-CN.md | 2 +- native-apps.json | 16 +- native-runtime.lock.json | 2 +- phone/docs/build-tool.md | 4 +- phone/docs/makepad-fork.md | 12 +- runtime-patches.lock.json | 63 +- .../makepad-application-extension.patch | 4 +- .../makepad-async-prompt-provenance.patch | 6 +- .../makepad-auth-webview.patch | 30 +- .../makepad-camera-checked-start.patch | 304 ++++--- .../makepad-camera-permission-wait.patch | 12 +- .../makepad-desktop-trusted-input.patch | 16 +- .../makepad-desktop-webview.patch | 42 +- .../makepad-device-consent.patch | 36 +- .../makepad-host-api-runtime-fixes.patch | 22 +- .../makepad-host-json-boundary.patch | 20 +- .../makepad-host-os-batch1.patch | 68 +- .../makepad-host-response-json.patch | 44 +- .../makepad-ime-inactive-no-restart.patch | 4 +- .../makepad-label-restyle-wrap.patch | 2 + .../makepad-location-explicit-consent.patch | 6 +- .../makepad-location-request.patch | 86 +- .../makepad-map-script-api.patch | 831 ------------------ ...kepad-native-audio-session-ownership.patch | 2 +- .../makepad-photo-text-sharing.patch | 14 +- tools/runtime-patches/makepad-settings.patch | 183 ++-- .../makepad-splash-budget.patch | 2 + .../runtime-patches/makepad-system-back.patch | 8 +- .../makepad-trusted-user-input.patch | 16 +- .../makepad-video-controls-lifecycle.patch | 2 +- .../makepad-web-reader-availability.patch | 6 +- .../makepad-windows-frame-completion.patch | 8 +- .../makepad-windows-remote-capture.patch | 8 +- .../makepad-windows-still-capture.patch | 4 +- 41 files changed, 568 insertions(+), 1384 deletions(-) delete mode 100644 tools/runtime-patches/makepad-map-script-api.patch diff --git a/Cargo.lock b/Cargo.lock index 30ced5e7e..7465adbc6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9064,6 +9064,7 @@ version = "2.0.0" dependencies = [ "ash 0.38.0+1.3.281", "bitflags 2.10.0", + "foldhash 0.2.0", "hilog-sys", "jpeg-encoder 0.6.1", "makepad-android-state", diff --git a/Cargo.toml b/Cargo.toml index 8b8c6f806..3335866d4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -88,46 +88,46 @@ authors = ["octos-app contributors"] # BEGIN native-apps: pins (generated by tools/native_apps.py from native-apps.json; do not edit) rinx = { git = "https://github.com/hagency-org/Rinx.git", tag = "v1.1.0", default-features = false, features = ["octosense-module"] } octosense-reference = { path = "apps/reference" } -makepad-sheets = { git = "https://github.com/OctoSense-org/makepad.git", rev = "32d6415fb7476345ad36ee4f98d6f844d1f07fd2", default-features = false } -makepad-terminal = { git = "https://github.com/OctoSense-org/makepad.git", rev = "32d6415fb7476345ad36ee4f98d6f844d1f07fd2" } +makepad-sheets = { git = "https://github.com/OctoSense-org/makepad.git", rev = "d653bc4493619ab979ccff165cf48c0c63b77d19", default-features = false } +makepad-terminal = { git = "https://github.com/OctoSense-org/makepad.git", rev = "d653bc4493619ab979ccff165cf48c0c63b77d19" } octosense-appcard = { path = "apps/appcard/module" } octosense-app-hub-app = { git = "https://github.com/OctoSense-org/OctoSense-App-Hub", rev = "371f51aeb6ba90e9a214c5ddb6ccfc40a2e2a339" } -makepad-calculator = { git = "https://github.com/OctoSense-org/makepad.git", rev = "32d6415fb7476345ad36ee4f98d6f844d1f07fd2", default-features = false } -makepad-clock = { git = "https://github.com/OctoSense-org/makepad.git", rev = "32d6415fb7476345ad36ee4f98d6f844d1f07fd2", default-features = false } -makepad-notes = { git = "https://github.com/OctoSense-org/makepad.git", rev = "32d6415fb7476345ad36ee4f98d6f844d1f07fd2", default-features = false } -makepad-reminders = { git = "https://github.com/OctoSense-org/makepad.git", rev = "32d6415fb7476345ad36ee4f98d6f844d1f07fd2", default-features = false } -makepad-weather = { git = "https://github.com/OctoSense-org/makepad.git", rev = "32d6415fb7476345ad36ee4f98d6f844d1f07fd2", default-features = false } +makepad-calculator = { git = "https://github.com/OctoSense-org/makepad.git", rev = "d653bc4493619ab979ccff165cf48c0c63b77d19", default-features = false } +makepad-clock = { git = "https://github.com/OctoSense-org/makepad.git", rev = "d653bc4493619ab979ccff165cf48c0c63b77d19", default-features = false } +makepad-notes = { git = "https://github.com/OctoSense-org/makepad.git", rev = "d653bc4493619ab979ccff165cf48c0c63b77d19", default-features = false } +makepad-reminders = { git = "https://github.com/OctoSense-org/makepad.git", rev = "d653bc4493619ab979ccff165cf48c0c63b77d19", default-features = false } +makepad-weather = { git = "https://github.com/OctoSense-org/makepad.git", rev = "d653bc4493619ab979ccff165cf48c0c63b77d19", default-features = false } octoscode-module = { git = "https://github.com/octos-org/octoscode-app.git", rev = "5d0c2a0962218aa94497201ce1a39b7429f09e36", default-features = false, features = ["octosense-module"] } -makepad-task = { git = "https://github.com/OctoSense-org/makepad.git", rev = "32d6415fb7476345ad36ee4f98d6f844d1f07fd2" } +makepad-task = { git = "https://github.com/OctoSense-org/makepad.git", rev = "d653bc4493619ab979ccff165cf48c0c63b77d19" } # END native-apps: pins # --- Makepad (OctoSense-org/makepad), the revision Octoscript-Makepad's # runtime.json pins (native-runtime.lock.json); resolved to .sources/makepad. -makepad-widgets = { git = "https://github.com/OctoSense-org/makepad.git", rev = "32d6415fb7476345ad36ee4f98d6f844d1f07fd2" } +makepad-widgets = { git = "https://github.com/OctoSense-org/makepad.git", rev = "d653bc4493619ab979ccff165cf48c0c63b77d19" } # The Splash compute kernel JIT (ADR 0013: sheet.fill lowers formulas onto it). -makepad-script-compute = { git = "https://github.com/OctoSense-org/makepad.git", rev = "32d6415fb7476345ad36ee4f98d6f844d1f07fd2" } +makepad-script-compute = { git = "https://github.com/OctoSense-org/makepad.git", rev = "d653bc4493619ab979ccff165cf48c0c63b77d19" } # The script VM alone: system apps' script tests (News's main.splash). -makepad-script = { git = "https://github.com/OctoSense-org/makepad.git", rev = "32d6415fb7476345ad36ee4f98d6f844d1f07fd2" } -makepad-code-editor = { git = "https://github.com/OctoSense-org/makepad.git", rev = "32d6415fb7476345ad36ee4f98d6f844d1f07fd2" } -makepad-ai = { git = "https://github.com/OctoSense-org/makepad.git", rev = "32d6415fb7476345ad36ee4f98d6f844d1f07fd2" } -makepad-studio-protocol = { git = "https://github.com/OctoSense-org/makepad.git", rev = "32d6415fb7476345ad36ee4f98d6f844d1f07fd2" } -makepad-network = { git = "https://github.com/OctoSense-org/makepad.git", rev = "32d6415fb7476345ad36ee4f98d6f844d1f07fd2" } -makepad-audio-decode = { git = "https://github.com/OctoSense-org/makepad.git", rev = "32d6415fb7476345ad36ee4f98d6f844d1f07fd2" } -makepad-wm-api = { git = "https://github.com/OctoSense-org/makepad.git", rev = "32d6415fb7476345ad36ee4f98d6f844d1f07fd2" } -makepad-ai-services = { git = "https://github.com/OctoSense-org/makepad.git", rev = "32d6415fb7476345ad36ee4f98d6f844d1f07fd2" } -makepad-strict-json = { git = "https://github.com/OctoSense-org/makepad.git", rev = "32d6415fb7476345ad36ee4f98d6f844d1f07fd2" } -makepad-app-module = { git = "https://github.com/OctoSense-org/makepad.git", rev = "32d6415fb7476345ad36ee4f98d6f844d1f07fd2" } -makepad-wm-theme = { git = "https://github.com/OctoSense-org/makepad.git", rev = "32d6415fb7476345ad36ee4f98d6f844d1f07fd2" } -makepad-aichat = { git = "https://github.com/OctoSense-org/makepad.git", rev = "32d6415fb7476345ad36ee4f98d6f844d1f07fd2", default-features = false } +makepad-script = { git = "https://github.com/OctoSense-org/makepad.git", rev = "d653bc4493619ab979ccff165cf48c0c63b77d19" } +makepad-code-editor = { git = "https://github.com/OctoSense-org/makepad.git", rev = "d653bc4493619ab979ccff165cf48c0c63b77d19" } +makepad-ai = { git = "https://github.com/OctoSense-org/makepad.git", rev = "d653bc4493619ab979ccff165cf48c0c63b77d19" } +makepad-studio-protocol = { git = "https://github.com/OctoSense-org/makepad.git", rev = "d653bc4493619ab979ccff165cf48c0c63b77d19" } +makepad-network = { git = "https://github.com/OctoSense-org/makepad.git", rev = "d653bc4493619ab979ccff165cf48c0c63b77d19" } +makepad-audio-decode = { git = "https://github.com/OctoSense-org/makepad.git", rev = "d653bc4493619ab979ccff165cf48c0c63b77d19" } +makepad-wm-api = { git = "https://github.com/OctoSense-org/makepad.git", rev = "d653bc4493619ab979ccff165cf48c0c63b77d19" } +makepad-ai-services = { git = "https://github.com/OctoSense-org/makepad.git", rev = "d653bc4493619ab979ccff165cf48c0c63b77d19" } +makepad-strict-json = { git = "https://github.com/OctoSense-org/makepad.git", rev = "d653bc4493619ab979ccff165cf48c0c63b77d19" } +makepad-app-module = { git = "https://github.com/OctoSense-org/makepad.git", rev = "d653bc4493619ab979ccff165cf48c0c63b77d19" } +makepad-wm-theme = { git = "https://github.com/OctoSense-org/makepad.git", rev = "d653bc4493619ab979ccff165cf48c0c63b77d19" } +makepad-aichat = { git = "https://github.com/OctoSense-org/makepad.git", rev = "d653bc4493619ab979ccff165cf48c0c63b77d19", default-features = false } # --- OctoScript (Octoscript 2e37d9e6) and its Makepad runtime -# (Octoscript-Makepad 33dea2f1, native-runtime.lock.json). +# (Octoscript-Makepad 6cf2d90a, native-runtime.lock.json). octoscript-ui-l0 = { git = "https://github.com/OctoSense-org/Octoscript.git", rev = "2e37d9e657a246f16718d9a475e167ccd2d5b5fa" } # The bounded VM, capability-leased host modules and executable JSON schemas # (the system toolbox's workflow templates, crates/toolbox). octoscript-core = { git = "https://github.com/OctoSense-org/Octoscript.git", rev = "2e37d9e657a246f16718d9a475e167ccd2d5b5fa" } octoscript-capabilities = { git = "https://github.com/OctoSense-org/Octoscript.git", rev = "2e37d9e657a246f16718d9a475e167ccd2d5b5fa" } octoscript-schema = { git = "https://github.com/OctoSense-org/Octoscript.git", rev = "2e37d9e657a246f16718d9a475e167ccd2d5b5fa" } -octoscript-makepad = { git = "https://github.com/OctoSense-org/Octoscript-Makepad.git", rev = "33dea2f1f3ad3f1346a219aa8cf6e91b31361e23" } -octoscript-node = { git = "https://github.com/OctoSense-org/Octoscript-Makepad.git", rev = "33dea2f1f3ad3f1346a219aa8cf6e91b31361e23" } +octoscript-makepad = { git = "https://github.com/OctoSense-org/Octoscript-Makepad.git", rev = "6cf2d90aa33ff4ae603f49c7557f3ecf986207f0" } +octoscript-node = { git = "https://github.com/OctoSense-org/Octoscript-Makepad.git", rev = "6cf2d90aa33ff4ae603f49c7557f3ecf986207f0" } # --- App Hub's host service registry the Mail and llm services register # into; the same revision as App Hub's app crate (a native app, above). octosense-appstore = { git = "https://github.com/OctoSense-org/OctoSense-App-Hub", rev = "371f51aeb6ba90e9a214c5ddb6ccfc40a2e2a339" } diff --git a/apps/appcard/native-runtime.lock.json b/apps/appcard/native-runtime.lock.json index 9f35c44c5..ef21c6e91 100644 --- a/apps/appcard/native-runtime.lock.json +++ b/apps/appcard/native-runtime.lock.json @@ -1,5 +1,5 @@ { "schema_version": 1, "url": "https://github.com/OctoSense-org/Octoscript-Makepad.git", - "revision": "33dea2f1f3ad3f1346a219aa8cf6e91b31361e23" + "revision": "6cf2d90aa33ff4ae603f49c7557f3ecf986207f0" } diff --git a/crates/video-api-smoke/src/main.rs b/crates/video-api-smoke/src/main.rs index 6b3beda48..ab61e1c25 100644 --- a/crates/video-api-smoke/src/main.rs +++ b/crates/video-api-smoke/src/main.rs @@ -301,7 +301,9 @@ impl MatchEvent for App { && splash_policy::local_path_for_heap(heap, "playback.mp4") .is_some_and(|path| PathBuf::from(path) == jail.join("playback.mp4")) && splash_policy::local_path_for_heap(heap, "../outside.mp4").is_none() - && !splash_policy::url_allowed(heap, "https://video.invalid/fixture.mp4"); + // Its host list is a declaration since makepad#117 (OctoSense + // #450): a network source is not refused by the runtime. + && splash_policy::url_allowed(heap, "https://video.invalid/fixture.mp4"); assert!(self.contained, "Fixture must retain the app storage jail"); self.timer = Some(cx.start_interval(0.05)); self.write_state(); diff --git a/docs/architecture.md b/docs/architecture.md index d037f88c2..8603f5630 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -529,8 +529,8 @@ Code outside this repository, at the revisions `Cargo.toml` pins: | What | Where | | --- | --- | -| Makepad: the `OctosPeer` client; each native app's agent code | [`libs/ai/services/src/peer.rs`](https://github.com/OctoSense-org/makepad/blob/32d6415fb7476345ad36ee4f98d6f844d1f07fd2/libs/ai/services/src/peer.rs), `apps//src/view.rs` | -| The Terminal's own-agent link (Makepad #100) | [`apps/terminal/src/module.rs`](https://github.com/OctoSense-org/makepad/blob/32d6415fb7476345ad36ee4f98d6f844d1f07fd2/apps/terminal/src/module.rs) | +| Makepad: the `OctosPeer` client; each native app's agent code | [`libs/ai/services/src/peer.rs`](https://github.com/OctoSense-org/makepad/blob/d653bc4493619ab979ccff165cf48c0c63b77d19/libs/ai/services/src/peer.rs), `apps//src/view.rs` | +| The Terminal's own-agent link (Makepad #100) | [`apps/terminal/src/module.rs`](https://github.com/OctoSense-org/makepad/blob/d653bc4493619ab979ccff165cf48c0c63b77d19/apps/terminal/src/module.rs) | | octos: the lanes' shared history (20 rows, 16 KiB); `peer_send_input` (64 KiB) | [`crates/octos-cli/src/peers/shared_history.rs`](https://github.com/octos-org/octos/blob/39e22d457c47df57d7c7c9fa64539979c9da93fd/crates/octos-cli/src/peers/shared_history.rs), [`crates/octos-agent/src/tools/peer_send_input.rs`](https://github.com/octos-org/octos/blob/39e22d457c47df57d7c7c9fa64539979c9da93fd/crates/octos-agent/src/tools/peer_send_input.rs) | | octos: host-managed serve, app peers, peer host tools | [`docs/HOST_MANAGED_SERVE.md`](https://github.com/octos-org/octos/blob/39e22d457c47df57d7c7c9fa64539979c9da93fd/docs/HOST_MANAGED_SERVE.md), [UPCR-2026-034](https://github.com/octos-org/octos/blob/39e22d457c47df57d7c7c9fa64539979c9da93fd/docs/OCTOS_UI_PROTOCOL_CHANGE_REQUEST_UPCR_2026_034_HOST_APP_PEERS.md), [UPCR-2026-035](https://github.com/octos-org/octos/blob/39e22d457c47df57d7c7c9fa64539979c9da93fd/docs/OCTOS_UI_PROTOCOL_CHANGE_REQUEST_UPCR_2026_035_PEER_HOST_TOOLS.md), [UPCR-2026-036](https://github.com/octos-org/octos/blob/39e22d457c47df57d7c7c9fa64539979c9da93fd/docs/OCTOS_UI_PROTOCOL_CHANGE_REQUEST_UPCR_2026_036_HOST_MANAGED_SERVE.md) | | App Hub: the kernel tools a script app may keep (`KERNEL_TOOLS`) | [`crates/app-policy/src/policy.rs`](https://github.com/OctoSense-org/OctoSense-App-Hub/blob/d2ca3a30ce06b0b1390cff305520962731baa1f8/crates/app-policy/src/policy.rs) | diff --git a/docs/architecture.zh-CN.md b/docs/architecture.zh-CN.md index 0b4930f23..afd1b49d3 100644 --- a/docs/architecture.zh-CN.md +++ b/docs/architecture.zh-CN.md @@ -529,8 +529,8 @@ sequenceDiagram | 内容 | 位置 | | --- | --- | -| Makepad:`OctosPeer` 客户端;各原生应用的 Agent 代码 | [`libs/ai/services/src/peer.rs`](https://github.com/OctoSense-org/makepad/blob/32d6415fb7476345ad36ee4f98d6f844d1f07fd2/libs/ai/services/src/peer.rs)、`apps//src/view.rs` | -| Terminal 自己的 Agent 链接(Makepad #100) | [`apps/terminal/src/module.rs`](https://github.com/OctoSense-org/makepad/blob/32d6415fb7476345ad36ee4f98d6f844d1f07fd2/apps/terminal/src/module.rs) | +| Makepad:`OctosPeer` 客户端;各原生应用的 Agent 代码 | [`libs/ai/services/src/peer.rs`](https://github.com/OctoSense-org/makepad/blob/d653bc4493619ab979ccff165cf48c0c63b77d19/libs/ai/services/src/peer.rs)、`apps//src/view.rs` | +| Terminal 自己的 Agent 链接(Makepad #100) | [`apps/terminal/src/module.rs`](https://github.com/OctoSense-org/makepad/blob/d653bc4493619ab979ccff165cf48c0c63b77d19/apps/terminal/src/module.rs) | | octos:两条通道的共享历史(20 条,16 KiB);`peer_send_input`(64 KiB) | [`crates/octos-cli/src/peers/shared_history.rs`](https://github.com/octos-org/octos/blob/39e22d457c47df57d7c7c9fa64539979c9da93fd/crates/octos-cli/src/peers/shared_history.rs)、[`crates/octos-agent/src/tools/peer_send_input.rs`](https://github.com/octos-org/octos/blob/39e22d457c47df57d7c7c9fa64539979c9da93fd/crates/octos-agent/src/tools/peer_send_input.rs) | | octos:宿主管理的 serve、应用 peer、peer 宿主工具 | [`docs/HOST_MANAGED_SERVE.md`](https://github.com/octos-org/octos/blob/39e22d457c47df57d7c7c9fa64539979c9da93fd/docs/HOST_MANAGED_SERVE.md)、[UPCR-2026-034](https://github.com/octos-org/octos/blob/39e22d457c47df57d7c7c9fa64539979c9da93fd/docs/OCTOS_UI_PROTOCOL_CHANGE_REQUEST_UPCR_2026_034_HOST_APP_PEERS.md)、[UPCR-2026-035](https://github.com/octos-org/octos/blob/39e22d457c47df57d7c7c9fa64539979c9da93fd/docs/OCTOS_UI_PROTOCOL_CHANGE_REQUEST_UPCR_2026_035_PEER_HOST_TOOLS.md)、[UPCR-2026-036](https://github.com/octos-org/octos/blob/39e22d457c47df57d7c7c9fa64539979c9da93fd/docs/OCTOS_UI_PROTOCOL_CHANGE_REQUEST_UPCR_2026_036_HOST_MANAGED_SERVE.md) | | App Hub:脚本应用可以保留的内核工具(`KERNEL_TOOLS`) | [`crates/app-policy/src/policy.rs`](https://github.com/OctoSense-org/OctoSense-App-Hub/blob/d2ca3a30ce06b0b1390cff305520962731baa1f8/crates/app-policy/src/policy.rs) | diff --git a/docs/mail-composable-cards.md b/docs/mail-composable-cards.md index b463e7c0a..ea4719ef2 100644 --- a/docs/mail-composable-cards.md +++ b/docs/mail-composable-cards.md @@ -310,7 +310,7 @@ receipt without a payload fingerprint allows one validated refresh. The [tool schemas](../apps/mail/bundle/tools.json) describe the four draft/proposal tools. Catalog contracts are pinned to Octoscript `2e37d9e657a246f16718d9a475e167ccd2d5b5fa`; Octoscript-Makepad is pinned to -`33dea2f1f3ad3f1346a219aa8cf6e91b31361e23`. Both Mail sources require literal app +`6cf2d90aa33ff4ae603f49c7557f3ecf986207f0`. Both Mail sources require literal app identity; this host additionally requires their literal draft ID to match the trusted publication. Draft `to`, `subject`, `body` and `suggestion_body` remain model-tainted for checker purposes: display/edit is allowed, direct reuse in diff --git a/docs/mail-composable-cards.zh-CN.md b/docs/mail-composable-cards.zh-CN.md index 48f612137..ecb152696 100644 --- a/docs/mail-composable-cards.zh-CN.md +++ b/docs/mail-composable-cards.zh-CN.md @@ -104,7 +104,7 @@ Lab **0468** 还验证了拒绝首次助手同意:Enable assistant 按钮消 ## 运行时契约与输入来源 -[工具 schema](../apps/mail/bundle/tools.json)定义四个草稿/提议工具。目录契约固定在 Octoscript `2e37d9e657a246f16718d9a475e167ccd2d5b5fa`,Octoscript-Makepad 固定在 `33dea2f1f3ad3f1346a219aa8cf6e91b31361e23`。两个 Mail 源都要求应用身份为字面量;本宿主还要求草稿 ID 字面量与可信发布绑定一致。检查器将草稿 `to`、`subject`、`body` 和 `suggestion_body` 保守地视为模型文本:可以显示/编辑,不能直接复用为操作载荷或源选择参数。宿主批准授权准确的已保存邮件,不要求用户把未经修改的 AI 草稿重新输入一遍。 +[工具 schema](../apps/mail/bundle/tools.json)定义四个草稿/提议工具。目录契约固定在 Octoscript `2e37d9e657a246f16718d9a475e167ccd2d5b5fa`,Octoscript-Makepad 固定在 `6cf2d90aa33ff4ae603f49c7557f3ecf986207f0`。两个 Mail 源都要求应用身份为字面量;本宿主还要求草稿 ID 字面量与可信发布绑定一致。检查器将草稿 `to`、`subject`、`body` 和 `suggestion_body` 保守地视为模型文本:可以显示/编辑,不能直接复用为操作载荷或源选择参数。宿主批准授权准确的已保存邮件,不要求用户把未经修改的 AI 草稿重新输入一遍。 下文的历史真机运行对应 **0416**(`abf06d8f`),当时使用 Octoscript `9ca9545b` 和 Octoscript-Makepad `a950f7fb`。当前固定版本仅对同一份 L0 实现应用 rustfmt,并将该修订同步到包装层。较新的 0427–0432 工作区版本使用当前固定版本,其验证单独记录在上方。 diff --git a/native-apps.json b/native-apps.json index 201bb79b2..b99175023 100644 --- a/native-apps.json +++ b/native-apps.json @@ -41,7 +41,7 @@ { "id": "sheets", "crate": "makepad-sheets", - "source": { "git": "https://github.com/OctoSense-org/makepad.git", "rev": "32d6415fb7476345ad36ee4f98d6f844d1f07fd2", "local": ".sources/makepad/apps/sheets" }, + "source": { "git": "https://github.com/OctoSense-org/makepad.git", "rev": "d653bc4493619ab979ccff165cf48c0c63b77d19", "local": ".sources/makepad/apps/sheets" }, "module": "makepad_sheets::SHEETS_MODULE", "bin": "sheets", "bin_features": ["standalone"], @@ -72,7 +72,7 @@ { "id": "terminal", "crate": "makepad-terminal", - "source": { "git": "https://github.com/OctoSense-org/makepad.git", "rev": "32d6415fb7476345ad36ee4f98d6f844d1f07fd2", "local": ".sources/makepad/apps/terminal" }, + "source": { "git": "https://github.com/OctoSense-org/makepad.git", "rev": "d653bc4493619ab979ccff165cf48c0c63b77d19", "local": ".sources/makepad/apps/terminal" }, "module": "makepad_terminal::TERMINAL_MODULE", "bin": "terminal", "default_features": true, @@ -138,7 +138,7 @@ { "id": "calculator", "crate": "makepad-calculator", - "source": { "git": "https://github.com/OctoSense-org/makepad.git", "rev": "32d6415fb7476345ad36ee4f98d6f844d1f07fd2", "local": ".sources/makepad/apps/calculator" }, + "source": { "git": "https://github.com/OctoSense-org/makepad.git", "rev": "d653bc4493619ab979ccff165cf48c0c63b77d19", "local": ".sources/makepad/apps/calculator" }, "module": "makepad_calculator::CALCULATOR_MODULE", "bin": "calculator", "bin_features": ["standalone"], @@ -161,7 +161,7 @@ { "id": "clock", "crate": "makepad-clock", - "source": { "git": "https://github.com/OctoSense-org/makepad.git", "rev": "32d6415fb7476345ad36ee4f98d6f844d1f07fd2", "local": ".sources/makepad/apps/clock" }, + "source": { "git": "https://github.com/OctoSense-org/makepad.git", "rev": "d653bc4493619ab979ccff165cf48c0c63b77d19", "local": ".sources/makepad/apps/clock" }, "module": "makepad_clock::CLOCK_MODULE", "bin": "clock", "bin_features": ["standalone"], @@ -184,7 +184,7 @@ { "id": "notes", "crate": "makepad-notes", - "source": { "git": "https://github.com/OctoSense-org/makepad.git", "rev": "32d6415fb7476345ad36ee4f98d6f844d1f07fd2", "local": ".sources/makepad/apps/notes" }, + "source": { "git": "https://github.com/OctoSense-org/makepad.git", "rev": "d653bc4493619ab979ccff165cf48c0c63b77d19", "local": ".sources/makepad/apps/notes" }, "module": "makepad_notes::NOTES_MODULE", "bin": "notes", "bin_features": ["standalone"], @@ -208,7 +208,7 @@ { "id": "reminders", "crate": "makepad-reminders", - "source": { "git": "https://github.com/OctoSense-org/makepad.git", "rev": "32d6415fb7476345ad36ee4f98d6f844d1f07fd2", "local": ".sources/makepad/apps/reminders" }, + "source": { "git": "https://github.com/OctoSense-org/makepad.git", "rev": "d653bc4493619ab979ccff165cf48c0c63b77d19", "local": ".sources/makepad/apps/reminders" }, "module": "makepad_reminders::REMINDERS_MODULE", "bin": "reminders", "bin_features": ["standalone"], @@ -232,7 +232,7 @@ { "id": "weather", "crate": "makepad-weather", - "source": { "git": "https://github.com/OctoSense-org/makepad.git", "rev": "32d6415fb7476345ad36ee4f98d6f844d1f07fd2", "local": ".sources/makepad/apps/weather" }, + "source": { "git": "https://github.com/OctoSense-org/makepad.git", "rev": "d653bc4493619ab979ccff165cf48c0c63b77d19", "local": ".sources/makepad/apps/weather" }, "module": "makepad_weather::WEATHER_MODULE", "bin": "weather", "bin_features": ["standalone"], @@ -274,7 +274,7 @@ { "id": "task", "crate": "makepad-task", - "source": { "git": "https://github.com/OctoSense-org/makepad.git", "rev": "32d6415fb7476345ad36ee4f98d6f844d1f07fd2", "local": ".sources/makepad/apps/task" }, + "source": { "git": "https://github.com/OctoSense-org/makepad.git", "rev": "d653bc4493619ab979ccff165cf48c0c63b77d19", "local": ".sources/makepad/apps/task" }, "module": null, "bin": "task", "default_features": true, diff --git a/native-runtime.lock.json b/native-runtime.lock.json index 9f35c44c5..ef21c6e91 100644 --- a/native-runtime.lock.json +++ b/native-runtime.lock.json @@ -1,5 +1,5 @@ { "schema_version": 1, "url": "https://github.com/OctoSense-org/Octoscript-Makepad.git", - "revision": "33dea2f1f3ad3f1346a219aa8cf6e91b31361e23" + "revision": "6cf2d90aa33ff4ae603f49c7557f3ecf986207f0" } diff --git a/phone/docs/build-tool.md b/phone/docs/build-tool.md index c26965402..82209cd6d 100644 --- a/phone/docs/build-tool.md +++ b/phone/docs/build-tool.md @@ -73,9 +73,9 @@ move that touches `tools/cargo_makepad` is the usual trigger): cargo install --path .sources/makepad/tools/cargo_makepad --force ``` -The shared release selects Makepad main `32d6415f`, which includes App Hub's +The shared release selects Makepad main `d653bc44`, which includes App Hub's isolate containment (#22), contained script apps (#30), the QR scanner -(#31), Splash `reapply_text` (#35) self-confirmed assistant tools (#36) and the one-call-site Slug, cursor and glass shaders (#37, #39), the terminal's confirmed runs (#41), trackpad scrolling (#42), gestures (#46), Chinese text (#48), tabs and installed fonts (#47), and every app's system-font fallback (#49, #51), WebReader's close ending the page (#43), the OpenHarmony packager's workspace target dir (#52), the `sys.dataset` and `sys.news_digest` helpers (#53) the peer-link client (#54), the Linux Vulkan build (#45), the map's hosted-archive source (#55) HTTPS on OpenHarmony (#56) the keyboard's hide after a cancelled search (#57, #59) and the terminal's tab titles, wide-character reflow, non-blocking PTY writes, control-request timeout and running-job close confirmation (#61–#65), and its grapheme clusters, Kitty keyboard and synchronized output, close handshake and rendering polish (#66–#69), text shaping and drag-select (#70–#72), clickable links and scrollback search (#73, #74), keyboard shortcuts (#76) and OpenHarmony run (#91), GL GPU timing (#81), atlas text on phones (#82), the Android touch-down power hint (#83) and Vulkan without a debug messenger (#92), Linux GL remote grabs (#93), a hidden map's idle frame loop (#79), and OpenHarmony's keyboard, Studio, GL lifetimes and idle frames (#84, #85, #87–#89), a library target and an own agent for each Makepad app OctoSense makes native (#94, #95), hosted Notes and Reminders that start empty (#96), Files' read-only chat panel (#97), a CEF profile under `MAKEPAD_HOME` (#98), Calculator's keypad laid out again after every apply (#99), the Terminal's own agent with its read tools only (#100) and the kernel port for an app that is an octos client (#101); the consumer lock +(#31), Splash `reapply_text` (#35) self-confirmed assistant tools (#36) and the one-call-site Slug, cursor and glass shaders (#37, #39), the terminal's confirmed runs (#41), trackpad scrolling (#42), gestures (#46), Chinese text (#48), tabs and installed fonts (#47), and every app's system-font fallback (#49, #51), WebReader's close ending the page (#43), the OpenHarmony packager's workspace target dir (#52), the `sys.dataset` and `sys.news_digest` helpers (#53) the peer-link client (#54), the Linux Vulkan build (#45), the map's hosted-archive source (#55) HTTPS on OpenHarmony (#56) the keyboard's hide after a cancelled search (#57, #59) and the terminal's tab titles, wide-character reflow, non-blocking PTY writes, control-request timeout and running-job close confirmation (#61–#65), and its grapheme clusters, Kitty keyboard and synchronized output, close handshake and rendering polish (#66–#69), text shaping and drag-select (#70–#72), clickable links and scrollback search (#73, #74), keyboard shortcuts (#76) and OpenHarmony run (#91), GL GPU timing (#81), atlas text on phones (#82), the Android touch-down power hint (#83) and Vulkan without a debug messenger (#92), Linux GL remote grabs (#93), a hidden map's idle frame loop (#79), and OpenHarmony's keyboard, Studio, GL lifetimes and idle frames (#84, #85, #87–#89), a library target and an own agent for each Makepad app OctoSense makes native (#94, #95), hosted Notes and Reminders that start empty (#96), Files' read-only chat panel (#97), a CEF profile under `MAKEPAD_HOME` (#98), Calculator's keypad laid out again after every apply (#99), the Terminal's own agent with its read tools only (#100) the kernel port for an app that is an octos client (#101), the renderer, Android, Linux and TextFlow work of #110–#116, and no per-app Splash runtime gates (#117, #118); the consumer lock needs no `makepad_override`. To see which checkout an installed tool reads its Java from: diff --git a/phone/docs/makepad-fork.md b/phone/docs/makepad-fork.md index 81f7b6c43..8e4f119dd 100644 --- a/phone/docs/makepad-fork.md +++ b/phone/docs/makepad-fork.md @@ -20,9 +20,9 @@ Makepad and that no fork is required do not. The revision is pinned as a chain, not in one place: 1. `native-runtime.lock.json` here names one revision of - `OctoSense-org/Octoscript-Makepad` (`33dea2f1`, its main). + `OctoSense-org/Octoscript-Makepad` (`6cf2d90a`, its main). 2. That repo's `runtime.json` names the Makepad fork revision - (`32d6415f`, Makepad main: the enforced isolate policy of PR #22, module + (`d653bc44`, Makepad main: the enforced isolate policy of PR #22, module windows, the ROM's Splash fixes, the contained script apps and host services of PR #30, the QR scanner of PR #31, Splash `reapply_text` of PR #35, the self-confirmed assistant tools of PR #36, the one-call-site @@ -48,8 +48,12 @@ The revision is pinned as a chain, not in one place: Reminders that start empty of PR #96, Files' read-only chat panel of PR #97, a CEF profile under `MAKEPAD_HOME` of PR #98, Calculator's keypad laid out again after every apply of PR #99, the Terminal's own agent with - its read tools only of PR #100 and the kernel port for an app that is an - octos client of PR #101) and the Octoscript revision + its read tools only of PR #100, the kernel port for an app that is an + octos client of PR #101, the renderer, Android, Linux and TextFlow work of + PRs #110–#116, and PRs #117 and #118, which drop the isolate policy's URL, + media, socket and instruction-budget gates and its capability check before + a `host.request`, under the ruling of 8 October 2026, OctoSense #450) and + the Octoscript revision (`2e37d9e6`, with the L0 `sys.digest` source of Octoscript #40 and the Mail draft and review capabilities of Octoscript #58). OctoSense, AppCard, Rinx and the ROM lock the same release. 3. The manifests repeat the Makepad revision as `rev = "…"`: here in diff --git a/runtime-patches.lock.json b/runtime-patches.lock.json index 8d9a77b10..242214d89 100644 --- a/runtime-patches.lock.json +++ b/runtime-patches.lock.json @@ -1,10 +1,10 @@ { "schema_version": 1, "makepad": { - "base_revision": "32d6415fb7476345ad36ee4f98d6f844d1f07fd2", + "base_revision": "d653bc4493619ab979ccff165cf48c0c63b77d19", "patch": "tools/runtime-patches/makepad-settings.patch", - "sha256": "b17352ebf59e50ab3db6ff1cb825c4e006bcdff47d83522e372b8fe585e57570", - "tree": "77308d82093bb86bb7d306a32247b9b4e8395447", + "sha256": "3a79cddddc89dc4d064a9f0bdd17dc8111dcab9c796a334d95f61ecb9d5011aa", + "tree": "0e7e0dacab19db708793e63ded641cd7b3ea319c", "product_fixes": [ "Settings integration: Android IME synchronization, accessibility and reduced motion, GLES program-cache validation, obscured-gesture cancellation and scroll-position observation.", "Contained-app isolation and secret-input restrictions come from the pinned upstream runtime; the Settings overlay preserves those gates.", @@ -45,38 +45,37 @@ "CameraPreview correlates native events before the child retires its player generation; cancelling preparation permits an explicit retry without waiting for a stale release callback.", "Video numeric controls accept both integer and floating Splash arguments while refusing nonnumeric, nonfinite and out-of-range values; native acceptance exposed the integer-seek regression.", "Android Video seeks to the closest frame on API26+ instead of the previous keyframe; older backends retain a bounded legacy fallback.", - "A script can move a plain map and hear it (makepad#113, branch octosense/map-script-api): fly_to, fit_route and clear_route, and the on_tap, on_long_press, on_marker and on_viewport callbacks on MapView. A script's fly_to and a pending fit give way to the person's drag; clear_route also drops a script route, its vehicle and a pending fit. On any map, a wheel that a scroll view over or around it already used no longer zooms it. Maps' route preview stays draggable; the plan, follow and drive cameras are unchanged.", "A native host may stage up to 64 MiB into an app's jail (MAX_IMPORT_BYTES; prepare_import and commit_import), so files.import can take documents above the 1 MiB script write limit. Staged bytes never enter the script heap; the jail's byte quota and entry cap still apply, and script writes, photo picks and exports keep 1 MiB.", "An OS resume restarts only the Video playback its pause stopped, including a start it cancelled; a player paused by the person, or paused or stopped by its app during the pause, stays paused (#406)." ], "stacked": [ { "patch": "tools/runtime-patches/makepad-system-back.patch", - "sha256": "3268cb486bbd05db7bb6920c26007e437017b6234a4d9f045a99a7d1954b7427" + "sha256": "c5965f3ca8d9ebea874ae533146e9168fe36c82dbc80cff0554170602e96091e" }, { "patch": "tools/runtime-patches/makepad-ime-inactive-no-restart.patch", - "sha256": "8ae6a6aceb93a013234c724ffdc6efc614cc8b9dddd7cae6ed8b1c569b94fdf1" + "sha256": "2c4b21d7adbb2f58f5e156cf39c6bbf51948e280fed1505245f935fd4e10514e" }, { "patch": "tools/runtime-patches/makepad-windows-remote-capture.patch", - "sha256": "2c0fabb6f5c0733d9d3356cde4cc7c8d40aabebf9a4752740c94f332b7e1317a" + "sha256": "30e7c7994aeb80b0608242341bed84226f40dfba36030c271f9b1ff50c2eb8f8" }, { "patch": "tools/runtime-patches/makepad-windows-frame-completion.patch", - "sha256": "8d58e16bc12aae5f4313fdc5c9d3f6944b96ee20e609fe5ff069fe2403954595" + "sha256": "0e29a789d106a689b5ed394c681de53dec49f6b938e21acceafbb5358fbe2bf9" }, { "patch": "tools/runtime-patches/makepad-application-extension.patch", - "sha256": "133099ee598a9794ec5df61dfbef30bca25df7bc03a66deecdb66355a8b8e6bf" + "sha256": "0668f176fb7b115b17550e6b2dd76109f4cc3906b4815a5d3687ff9d838d91c7" }, { "patch": "tools/runtime-patches/makepad-trusted-user-input.patch", - "sha256": "48ac389a3b0fab5051092539700f63d4d58b7dd21cb60af43c30b0fda60a6b32" + "sha256": "405558a2a2cf136e6e2cf49f312f5652cb5f2a34c418e49cf2e50e4599014bcd" }, { "patch": "tools/runtime-patches/makepad-desktop-trusted-input.patch", - "sha256": "0bb2bbc7d776bb9c8b8064b2f44bcec6e9442c10e1fbf84acbdfd03f0b89be50" + "sha256": "90b188d3b161bfa54bd349f8ae7db4b7dc112052d997238d11bd4234b1b41cdd" }, { "patch": "tools/runtime-patches/makepad-android-enter-composition.patch", @@ -84,71 +83,71 @@ }, { "patch": "tools/runtime-patches/makepad-auth-webview.patch", - "sha256": "61d5e247627bd7a3a883dab6689d6c82b7df7cbc031e3731b82b31f388555131" + "sha256": "71e1cc7745b6fa7927b470a0326ac4e3739c8182c1e95ffe67991f59710fa606" }, { "patch": "tools/runtime-patches/makepad-camera-permission-wait.patch", - "sha256": "6b1c5998521dbf44e70bc8b90ab025d8f449a8e646c363585f3b172734d478c2" + "sha256": "96c30eca01ce5ed6d2eb56f9503eebd4e2404ac29027f20597ec18116ef1f4bd" }, { "patch": "tools/runtime-patches/makepad-location-explicit-consent.patch", - "sha256": "e4490b7a6e717b2737431c99bd9486ea14c272a45effdd3ddab2d679f4fa5c63" + "sha256": "cf1c948a0c9dd97e7b063a46758ff9d5106ca48fe32b7419dfc7406bc2b80976" }, { "patch": "tools/runtime-patches/makepad-location-request.patch", - "sha256": "9cd0980e8d07a24bf0a650e5e4f8bddc736cfa4bf143ced72d772d5f3f6f1e06" + "sha256": "aa85f0dae99db54e3201e3b752aef121253248981cfe5046b3daa1c0456aa503" }, { "patch": "tools/runtime-patches/makepad-label-restyle-wrap.patch", - "sha256": "caffe63992d32aa5295af426903d2e6ef028cdb5d831336fa43ea79710d34305" + "sha256": "35ea02662b7b998737ffe1f1d98600f05b80acc5dd7c7d4bd2db828b1e5bc603" }, { "patch": "tools/runtime-patches/makepad-camera-checked-start.patch", - "sha256": "f28349631a2f7b851c0d5289ebebfa387a020170a69793f65c4f2f985504ffc0" + "sha256": "614613dc9780faab0231c946d1a50c5db07f9370a06c07d5016bef7b1e6cfb84" }, { "patch": "tools/runtime-patches/makepad-device-consent.patch", - "sha256": "8a09c8c07f15d899667db40500bf407393fc9ec262b8a01d01d4a7852c4c2693" + "sha256": "edb8fe27f19f6ec36afb10fff4f2fe234ee223b5fce6827b01ce0d68ce209349" }, { "patch": "tools/runtime-patches/makepad-web-reader-availability.patch", - "sha256": "5db088e4fa0b15a59861e52e65a2194e80bb8c351eafc2dd57e59c0d7161c71e" + "sha256": "96d02f21e37a6d7dbd06cdb7870273787d8d45bfa378a4e53ccb734254047d64" }, { "patch": "tools/runtime-patches/makepad-host-json-boundary.patch", - "sha256": "7235aea8fa614e19e7ca9133712967d49ec908bf5a0319fcdda4cea7134cbe69" + "sha256": "d5b562950b1908df52df9460046f502dc99db2e63eb95f1a5ca1ab13725008df" }, { "patch": "tools/runtime-patches/makepad-async-prompt-provenance.patch", - "sha256": "46bb7620ccb4f6a76a08d0333c0249d2619123da2a2649f55294fd4d35f0d905" + "sha256": "dbf816dca437906391f00e11e0344d08228654ff41af43d11e4272602749f9df" }, { "patch": "tools/runtime-patches/makepad-desktop-webview.patch", - "sha256": "dda81412903a247525cf3750a00aab820aa1dea28722cc633cbdf65fd5770221" + "sha256": "613b6d042fbb09e6b33736292650180011f5576a13aaa6fc72531b615dc58500" }, { "patch": "tools/runtime-patches/makepad-host-response-json.patch", - "sha256": "a95cd969b2d034ca30d38bfdb6ca6a890aaef4f24c7034076ddf72eb4c3d829a" + "sha256": "cf9baf53a11c35e9fa2d6bc84fcce20b3cce901cb0a31411ffd6f26373d147fd" }, { "patch": "tools/runtime-patches/makepad-splash-budget.patch", - "sha256": "cebcae3327ec955a2b37eb1f3353e97160d702bd4081abb264006fee4ac17ee5" + "sha256": "c40e263f1869b157bf5a31132025a85091b6f34cd4b207998dbfbaa87434aa47" }, { "patch": "tools/runtime-patches/makepad-host-os-batch1.patch", - "sha256": "e336290797373331285ae16c2a6414c20b5674eb550b7219ba1132e4743c0141" + "sha256": "40cff18e59a7abad2808e6751a909f1313517ee148e650cd56d9ba377f3941f9" }, { "patch": "tools/runtime-patches/makepad-host-api-runtime-fixes.patch", - "sha256": "89459d67c66ec1bea55987b89d4ae0b080324f873ddd17883d6f698f8e7a4343" + "sha256": "48dfff13b94532fed3864c6b1fa540748d6048eb9185153acfb5d3fe4d8a076e" }, { "patch": "tools/runtime-patches/makepad-photo-text-sharing.patch", - "sha256": "8a027f43b6d63f31bfe272e48c2b1968047907e5c76f2685f01b1c5446c4735f" + "sha256": "dc832e970adf7509fcab296101c8841a37f27d7a8f7e64aac58abfddd8f78472" }, { "patch": "tools/runtime-patches/makepad-native-audio-session-ownership.patch", - "sha256": "79d33aa0756f140de9f15db326e17bfd01d12856069d17721845b459540dea3f" + "sha256": "4ee0d436e0e5bc25b9bc063edb4c4e88f8b7803c28efe0690bdf4d57173cad39" }, { "patch": "tools/runtime-patches/makepad-apple-audio-open-generation.patch", @@ -156,11 +155,11 @@ }, { "patch": "tools/runtime-patches/makepad-video-controls-lifecycle.patch", - "sha256": "c92fd343720ab6e26564ac782b9276ce9e5f0708e3c7547f0b17981c340f88ea" + "sha256": "83ec1f0b8596afff7996814bbc31f939331c950b4cfc1a93dbcba89d7c839d37" }, { "patch": "tools/runtime-patches/makepad-windows-still-capture.patch", - "sha256": "7e80e9414355979a9bafbfda847c2a33f63ce4b30776a7cb813e78cc3b6fa964" + "sha256": "f2056252f5859f550549290fcab38f331b6276d123e7702cfb8fdf38da57594b" }, { "patch": "tools/runtime-patches/makepad-camera-video-generation-routing.patch", @@ -174,10 +173,6 @@ "patch": "tools/runtime-patches/makepad-android-video-seek.patch", "sha256": "e52e281cd375d25105dbaa285354c970148ab07442400c3cda6a712c460b01e9" }, - { - "patch": "tools/runtime-patches/makepad-map-script-api.patch", - "sha256": "f0626c970bb5362eb10fddd7eb22421e457ec428062a4bcefdd4c59c8505eb50" - }, { "patch": "tools/runtime-patches/makepad-large-document-import.patch", "sha256": "d8c3c516d7f6feff3f36162a745bd5f939bb971d13c8c10942a9e5731728f3df" diff --git a/tools/runtime-patches/makepad-application-extension.patch b/tools/runtime-patches/makepad-application-extension.patch index d3445d53d..01e2ef8a0 100644 --- a/tools/runtime-patches/makepad-application-extension.patch +++ b/tools/runtime-patches/makepad-application-extension.patch @@ -1,8 +1,8 @@ diff --git a/tools/cargo_makepad/src/android/java/dev/makepad/android/MakepadActivity.java b/tools/cargo_makepad/src/android/java/dev/makepad/android/MakepadActivity.java -index 7be1adfc2..3d8242230 100644 +index b10842450..41aa2c8f7 100644 --- a/tools/cargo_makepad/src/android/java/dev/makepad/android/MakepadActivity.java +++ b/tools/cargo_makepad/src/android/java/dev/makepad/android/MakepadActivity.java -@@ -1262,12 +1262,22 @@ public class MakepadActivity +@@ -1259,12 +1259,22 @@ public class MakepadActivity private void createApplicationExtension() { try { diff --git a/tools/runtime-patches/makepad-async-prompt-provenance.patch b/tools/runtime-patches/makepad-async-prompt-provenance.patch index e915fe290..817e91907 100644 --- a/tools/runtime-patches/makepad-async-prompt-provenance.patch +++ b/tools/runtime-patches/makepad-async-prompt-provenance.patch @@ -448,7 +448,7 @@ index 6a8830380..0559cd9dd 100644 } std::panic::resume_unwind(payload) diff --git a/widgets/src/splash_host.rs b/widgets/src/splash_host.rs -index 679ac186a..257498cfc 100644 +index 73c28afce..78c9a78c4 100644 --- a/widgets/src/splash_host.rs +++ b/widgets/src/splash_host.rs @@ -84,9 +84,9 @@ struct HostCallback { @@ -471,7 +471,7 @@ index 679ac186a..257498cfc 100644 } /// Drops all bridge state for reclaimed isolates (called from the isolate GC). -@@ -348,7 +349,9 @@ pub fn script_mod(vm: &mut ScriptVm) { +@@ -332,7 +333,9 @@ pub fn script_mod(vm: &mut ScriptVm) { let mut b = b.borrow_mut(); b.next_req_id += 1; let req_id = b.next_req_id; @@ -483,7 +483,7 @@ index 679ac186a..257498cfc 100644 b.callbacks.insert((heap_key, req_id), HostCallback { function: callback, may_prompt }); } diff --git a/widgets/src/widget_async.rs b/widgets/src/widget_async.rs -index 81218427d..0f5300799 100644 +index 801b82e1b..7b8eaebc4 100644 --- a/widgets/src/widget_async.rs +++ b/widgets/src/widget_async.rs @@ -331,6 +331,7 @@ pub enum ScriptAsyncResult { diff --git a/tools/runtime-patches/makepad-auth-webview.patch b/tools/runtime-patches/makepad-auth-webview.patch index 83d425dec..1b7b5d74e 100644 --- a/tools/runtime-patches/makepad-auth-webview.patch +++ b/tools/runtime-patches/makepad-auth-webview.patch @@ -1,5 +1,5 @@ diff --git a/platform/src/cx_api.rs b/platform/src/cx_api.rs -index 71c12d2a3..4782017e0 100644 +index 363dc7eeb..d3e27fc32 100644 --- a/platform/src/cx_api.rs +++ b/platform/src/cx_api.rs @@ -131,6 +131,18 @@ pub fn spawn_navigable(&mut self, url: &str) { @@ -527,10 +527,10 @@ index 2d7bf1496..77a5b9f05 100644 fn clipped_browser_layout( unclipped_rect: Rect, diff --git a/platform/src/os/apple/macos/macos.rs b/platform/src/os/apple/macos/macos.rs -index 064e008f0..bfb3af932 100644 +index 342378ca8..a24f806dd 100644 --- a/platform/src/os/apple/macos/macos.rs +++ b/platform/src/os/apple/macos/macos.rs -@@ -2040,6 +2040,15 @@ fn handle_platform_ops( +@@ -2053,6 +2053,15 @@ fn handle_platform_ops( .or_insert_with(|| MacosSystemBrowser::new(&url)) .set_url(&url, false); } @@ -546,7 +546,7 @@ index 064e008f0..bfb3af932 100644 CxOsOp::UpdateSystemBrowser { browser_id, area, -@@ -2108,6 +2117,13 @@ fn handle_platform_ops( +@@ -2121,6 +2130,13 @@ fn handle_platform_ops( let _ = std::fs::write(result_path, r#"{"error":"native browser is closed"}"#); } } @@ -561,10 +561,10 @@ index 064e008f0..bfb3af932 100644 with_macos_app(|app| app.open_save_file_dialog(settings)); } diff --git a/platform/src/os/linux/android/android.rs b/platform/src/os/linux/android/android.rs -index 8d910fc7b..66969ce8b 100644 +index 1012812ae..94029dd20 100644 --- a/platform/src/os/linux/android/android.rs +++ b/platform/src/os/linux/android/android.rs -@@ -1588,6 +1588,10 @@ pub(crate) fn handle_message(&mut self, msg: FromJavaMessage) { +@@ -1597,6 +1597,10 @@ pub(crate) fn handle_message(&mut self, msg: FromJavaMessage) { Cx::post_action(crate::event::NativeQrCancelled { reason }); self.handle_action_receiver(); } @@ -575,7 +575,7 @@ index 8d910fc7b..66969ce8b 100644 FromJavaMessage::SystemBrowserInvoke { browser_id, call_id, -@@ -2867,6 +2871,9 @@ fn handle_platform_ops(&mut self) -> EventFlow { +@@ -2914,6 +2918,9 @@ fn handle_platform_ops(&mut self) -> EventFlow { CxOsOp::ShowQrScanner => unsafe { android_jni::to_java_request_qr_scanner(); }, @@ -586,7 +586,7 @@ index 8d910fc7b..66969ce8b 100644 browser_id, url, diff --git a/platform/src/os/linux/android/android_jni.rs b/platform/src/os/linux/android/android_jni.rs -index 2f3608350..1d2434abc 100644 +index 376b20e96..2ee73cd8c 100644 --- a/platform/src/os/linux/android/android_jni.rs +++ b/platform/src/os/linux/android/android_jni.rs @@ -256,6 +256,9 @@ pub enum FromJavaMessage { @@ -599,7 +599,7 @@ index 2f3608350..1d2434abc 100644 SystemBrowserInvoke { browser_id: i64, call_id: i64, tool: String, args: String }, SystemBrowserPageError { browser_id: i64, code: i32, description: String, url: String }, DeepLink { url: String }, -@@ -2857,6 +2860,22 @@ extern "C" fn Java_dev_makepad_android_MakepadNative_onLocation( +@@ -2894,6 +2897,22 @@ extern "C" fn Java_dev_makepad_android_MakepadNative_onLocation( }); } @@ -622,7 +622,7 @@ index 2f3608350..1d2434abc 100644 /// A system browser's main-frame load failed (see `MakepadActivity`'s /// WebViewClient). Delivered as a `NativeSystemBrowserPageError` action so the /// widget hosting the browser can show that the page did not load, instead of -@@ -3135,6 +3154,21 @@ pub unsafe fn to_java_spawn_system_browser(browser_id: LiveId, url: &str, naviga +@@ -3172,6 +3191,21 @@ pub unsafe fn to_java_spawn_system_browser(browser_id: LiveId, url: &str, naviga ); } @@ -659,10 +659,10 @@ index 1caaa86da..fe5d7fbf0 100644 makepad_java_classes_dir.join("MakepadNetwork.java"), makepad_java_classes_dir.join("MakepadSpeech.java"), diff --git a/tools/cargo_makepad/src/android/java/dev/makepad/android/MakepadActivity.java b/tools/cargo_makepad/src/android/java/dev/makepad/android/MakepadActivity.java -index 3d8242230..59238ad36 100644 +index 41aa2c8f7..69314ccdf 100644 --- a/tools/cargo_makepad/src/android/java/dev/makepad/android/MakepadActivity.java +++ b/tools/cargo_makepad/src/android/java/dev/makepad/android/MakepadActivity.java -@@ -4425,10 +4425,15 @@ public class MakepadActivity +@@ -4498,10 +4498,15 @@ public class MakepadActivity }); } @@ -1148,10 +1148,10 @@ index 000000000..faa8f789b + String domain() { return start.getHost().toLowerCase(Locale.ROOT) + (start.getPort() > 0 ? ":" + start.getPort() : ""); } +} diff --git a/tools/cargo_makepad/src/android/java/dev/makepad/android/MakepadNative.java b/tools/cargo_makepad/src/android/java/dev/makepad/android/MakepadNative.java -index 9f27bf02c..99b451ab5 100644 +index c1fe6470b..520fdfa38 100644 --- a/tools/cargo_makepad/src/android/java/dev/makepad/android/MakepadNative.java +++ b/tools/cargo_makepad/src/android/java/dev/makepad/android/MakepadNative.java -@@ -82,6 +82,8 @@ public class MakepadNative { +@@ -102,6 +102,8 @@ public class MakepadNative { // A system browser's main-frame load failed (no network, host unresolved, // server refused). Rust hands it to the widget hosting the browser so the // failure is shown rather than leaving an empty view on screen. @@ -1471,7 +1471,7 @@ index bcdc907b3..d1e2a1b04 100644 self.hide_overlay(cx); let why = format!("could not load {}: {}", self.url.as_deref().unwrap_or(""), err.description); diff --git a/widgets/src/widget_async.rs b/widgets/src/widget_async.rs -index d2f22abeb..81218427d 100644 +index 1f5a2872e..801b82e1b 100644 --- a/widgets/src/widget_async.rs +++ b/widgets/src/widget_async.rs @@ -8,7 +8,8 @@ diff --git a/tools/runtime-patches/makepad-camera-checked-start.patch b/tools/runtime-patches/makepad-camera-checked-start.patch index c50f841f2..ca185544f 100644 --- a/tools/runtime-patches/makepad-camera-checked-start.patch +++ b/tools/runtime-patches/makepad-camera-checked-start.patch @@ -1,6 +1,8 @@ +diff --git a/platform/src/os/linux/android/android.rs b/platform/src/os/linux/android/android.rs +index acff6af41..5b583ff97 100644 --- a/platform/src/os/linux/android/android.rs +++ b/platform/src/os/linux/android/android.rs -@@ -1975,6 +1975,7 @@ +@@ -1990,6 +1990,7 @@ fn poll_camera_players(&mut self) { None }; let mut events = Vec::new(); @@ -8,7 +10,7 @@ for (_video_id, player) in players.iter_mut() { match player.check_prepared() { -@@ -1997,10 +1998,12 @@ +@@ -2012,10 +2013,12 @@ fn poll_camera_players(&mut self) { })); } Some(Err(err)) => { @@ -21,7 +23,7 @@ } None => {} } -@@ -2084,6 +2087,12 @@ +@@ -2099,6 +2102,12 @@ fn poll_camera_players(&mut self) { } } @@ -34,9 +36,11 @@ self.os.camera_players = players; for event in events { self.call_event_handler(&event); +diff --git a/platform/src/os/linux/android/android_camera.rs b/platform/src/os/linux/android/android_camera.rs +index 042d3032f..beb4c00db 100644 --- a/platform/src/os/linux/android/android_camera.rs +++ b/platform/src/os/linux/android/android_camera.rs -@@ -95,10 +95,12 @@ +@@ -95,10 +95,12 @@ struct CameraStreamNode { controls: CameraControlState, dispatch: Arc>, session: Option, @@ -49,7 +53,7 @@ pub struct AndroidCaptureSession { capture_session: *mut ACameraCaptureSession, output_container: *mut ACaptureSessionOutputContainer, -@@ -562,7 +564,16 @@ +@@ -562,7 +564,16 @@ unsafe fn start( preview_window: Option<*mut ANativeWindow>, needs_image_reader: bool, still: Option<(VideoInputId, i32, i32)>, @@ -67,7 +71,7 @@ let reader_mode = { let guard = dispatch.lock().unwrap(); if !guard.preview_hardware_buffer_input_cbs.is_empty() -@@ -570,252 +581,131 @@ +@@ -570,252 +581,131 @@ unsafe fn start( && guard.frame_input_cbs.is_empty() && guard.preview_frame_input_cbs.is_empty() && guard.encoders.is_empty() @@ -150,6 +154,7 @@ - 2, - &mut image_reader, - ) +- } + // Every acquired resource belongs to this partial session. A failed + // stage closes it; no null/incomplete session is reported as running. + let result = (|| -> Result<(), String> { @@ -157,95 +162,6 @@ + onError: Some(Self::device_on_error), + onDisconnected: Some(Self::device_on_disconnected), + context: session.capture_context as *mut _, -+ }; -+ check("open-device", ACameraManager_openCamera(manager, camera_id.as_ptr(), -+ &mut callbacks, &mut session.camera_device))?; -+ pointer("open-device", session.camera_device)?; -+ check("preview-request", ACameraDevice_createCaptureRequest(session.camera_device, -+ TEMPLATE_PREVIEW, &mut session.capture_request))?; -+ pointer("preview-request", session.capture_request)?; -+ -+ if needs_image_reader { -+ let status = match reader_mode { -+ AndroidImageReaderMode::HardwareBufferYuv => AImageReader_newWithUsage( -+ format.width as _, format.height as _, AIMAGE_FORMAT_YUV_420_888, -+ ndk_sys::AHARDWAREBUFFER_USAGE_GPU_SAMPLED_IMAGE, 3, &mut session.image_reader), -+ AndroidImageReaderMode::CpuReadable => { -+ let image_format = match format.pixel_format { -+ VideoPixelFormat::YUV420 => AIMAGE_FORMAT_YUV_420_888, -+ VideoPixelFormat::MJPEG => AIMAGE_FORMAT_JPEG, -+ _ => return Err("camera startup has an unsupported image format".into()), -+ }; -+ AImageReader_new(format.width as _, format.height as _, image_format, -+ 2, &mut session.image_reader) -+ } -+ }; -+ check("preview-reader", status)?; -+ pointer("preview-reader", session.image_reader)?; -+ let mut listener = AImageReader_ImageListener { -+ context: session.capture_context as *mut _, -+ onImageAvailable: Some(Self::image_on_image_available), -+ }; -+ check("preview-listener", AImageReader_setImageListener(session.image_reader, &mut listener))?; -+ let mut window = std::ptr::null_mut(); -+ check("preview-window", AImageReader_getWindow(session.image_reader, &mut window))?; -+ pointer("preview-window", window)?; -+ ANativeWindow_acquire(window); -+ session.image_window = window; -+ check("preview-target", ACameraOutputTarget_create(window, &mut session.image_target))?; -+ pointer("preview-target", session.image_target)?; -+ check("preview-request-target", ACaptureRequest_addTarget(session.capture_request, session.image_target))?; -+ check("preview-output", ACaptureSessionOutput_create(window, &mut session.image_output))?; -+ pointer("preview-output", session.image_output)?; -+ if reader_mode == AndroidImageReaderMode::CpuReadable && format.pixel_format == VideoPixelFormat::MJPEG { -+ let quality = 60u8; -+ check("preview-jpeg-quality", ACaptureRequest_setEntry_u8(session.capture_request, -+ ACAMERA_JPEG_QUALITY, 1, &quality))?; - } -+ } -+ -+ if let Some((input_id, width, height)) = still { -+ check("still-reader", AImageReader_new(width, height, AIMAGE_FORMAT_JPEG, -+ 2, &mut session.still_reader))?; -+ pointer("still-reader", session.still_reader)?; -+ session.still_context = Box::into_raw(Box::new(AndroidStillContext { -+ input_id, pending: Mutex::new(None), alive: Arc::new(AtomicBool::new(true)), -+ })); -+ let mut listener = AImageReader_ImageListener { -+ context: session.still_context as *mut _, onImageAvailable: Some(Self::still_on_image_available), -+ }; -+ check("still-listener", AImageReader_setImageListener(session.still_reader, &mut listener))?; -+ let mut window = std::ptr::null_mut(); -+ check("still-window", AImageReader_getWindow(session.still_reader, &mut window))?; -+ pointer("still-window", window)?; -+ ANativeWindow_acquire(window); -+ session.still_window = window; -+ check("still-target", ACameraOutputTarget_create(window, &mut session.still_target))?; -+ pointer("still-target", session.still_target)?; -+ check("still-output", ACaptureSessionOutput_create(window, &mut session.still_output))?; -+ pointer("still-output", session.still_output)?; -+ } -+ check("output-container", ACaptureSessionOutputContainer_create(&mut session.output_container))?; -+ pointer("output-container", session.output_container)?; -+ for (stage, output) in [("still-output-add", session.still_output), ("preview-output-add", session.image_output)] { -+ if !output.is_null() { check(stage, ACaptureSessionOutputContainer_add(session.output_container, output))?; } -+ } -+ if let Some(window) = preview_window.filter(|w| !w.is_null()) { -+ ANativeWindow_acquire(window); -+ session.preview_window = window; -+ check("native-preview-target", ACameraOutputTarget_create(window, &mut session.preview_target))?; -+ pointer("native-preview-target", session.preview_target)?; -+ check("native-preview-request-target", ACaptureRequest_addTarget(session.capture_request, session.preview_target))?; -+ check("native-preview-output", ACaptureSessionOutput_create(window, &mut session.preview_output))?; -+ pointer("native-preview-output", session.preview_output)?; -+ check("native-preview-output-add", ACaptureSessionOutputContainer_add(session.output_container, session.preview_output))?; -+ } -+ if session.image_output.is_null() && session.preview_output.is_null() { -+ return Err("camera startup has no preview output".into()); -+ } -+ let callbacks = ACameraCaptureSession_stateCallbacks { -+ context: session.capture_context as *mut _, onClosed: Some(Self::session_on_closed), -+ onReady: Some(Self::session_on_ready), onActive: Some(Self::session_on_active), }; - if image_reader_result != 0 || image_reader.is_null() { - crate::warning!( @@ -290,8 +206,52 @@ - 1, - &jpeg_quality, - ); -- } -- ++ check("open-device", ACameraManager_openCamera(manager, camera_id.as_ptr(), ++ &mut callbacks, &mut session.camera_device))?; ++ pointer("open-device", session.camera_device)?; ++ check("preview-request", ACameraDevice_createCaptureRequest(session.camera_device, ++ TEMPLATE_PREVIEW, &mut session.capture_request))?; ++ pointer("preview-request", session.capture_request)?; ++ ++ if needs_image_reader { ++ let status = match reader_mode { ++ AndroidImageReaderMode::HardwareBufferYuv => AImageReader_newWithUsage( ++ format.width as _, format.height as _, AIMAGE_FORMAT_YUV_420_888, ++ ndk_sys::AHARDWAREBUFFER_USAGE_GPU_SAMPLED_IMAGE, 3, &mut session.image_reader), ++ AndroidImageReaderMode::CpuReadable => { ++ let image_format = match format.pixel_format { ++ VideoPixelFormat::YUV420 => AIMAGE_FORMAT_YUV_420_888, ++ VideoPixelFormat::MJPEG => AIMAGE_FORMAT_JPEG, ++ _ => return Err("camera startup has an unsupported image format".into()), ++ }; ++ AImageReader_new(format.width as _, format.height as _, image_format, ++ 2, &mut session.image_reader) ++ } ++ }; ++ check("preview-reader", status)?; ++ pointer("preview-reader", session.image_reader)?; ++ let mut listener = AImageReader_ImageListener { ++ context: session.capture_context as *mut _, ++ onImageAvailable: Some(Self::image_on_image_available), ++ }; ++ check("preview-listener", AImageReader_setImageListener(session.image_reader, &mut listener))?; ++ let mut window = std::ptr::null_mut(); ++ check("preview-window", AImageReader_getWindow(session.image_reader, &mut window))?; ++ pointer("preview-window", window)?; ++ ANativeWindow_acquire(window); ++ session.image_window = window; ++ check("preview-target", ACameraOutputTarget_create(window, &mut session.image_target))?; ++ pointer("preview-target", session.image_target)?; ++ check("preview-request-target", ACaptureRequest_addTarget(session.capture_request, session.image_target))?; ++ check("preview-output", ACaptureSessionOutput_create(window, &mut session.image_output))?; ++ pointer("preview-output", session.image_output)?; ++ if reader_mode == AndroidImageReaderMode::CpuReadable && format.pixel_format == VideoPixelFormat::MJPEG { ++ let quality = 60u8; ++ check("preview-jpeg-quality", ACaptureRequest_setEntry_u8(session.capture_request, ++ ACAMERA_JPEG_QUALITY, 1, &quality))?; ++ } + } + - let _ = ACaptureSessionOutput_create(image_window, &mut image_output); - } - @@ -316,11 +276,19 @@ - input_id, - pending: Mutex::new(None), - alive: Arc::new(AtomicBool::new(true)), -- })); ++ if let Some((input_id, width, height)) = still { ++ check("still-reader", AImageReader_new(width, height, AIMAGE_FORMAT_JPEG, ++ 2, &mut session.still_reader))?; ++ pointer("still-reader", session.still_reader)?; ++ session.still_context = Box::into_raw(Box::new(AndroidStillContext { ++ input_id, pending: Mutex::new(None), alive: Arc::new(AtomicBool::new(true)), + })); - let mut still_listener = AImageReader_ImageListener { - context: still_context as *mut _, - onImageAvailable: Some(Self::still_on_image_available), -- }; ++ let mut listener = AImageReader_ImageListener { ++ context: session.still_context as *mut _, onImageAvailable: Some(Self::still_on_image_available), + }; - AImageReader_setImageListener(still_reader, &mut still_listener); - AImageReader_getWindow(still_reader, &mut still_window); - ANativeWindow_acquire(still_window); @@ -328,7 +296,17 @@ - let _ = ACaptureSessionOutput_create(still_window, &mut still_output); - } else { - crate::warning!("Android camera: no still stream at {still_width}x{still_height}"); -- } ++ check("still-listener", AImageReader_setImageListener(session.still_reader, &mut listener))?; ++ let mut window = std::ptr::null_mut(); ++ check("still-window", AImageReader_getWindow(session.still_reader, &mut window))?; ++ pointer("still-window", window)?; ++ ANativeWindow_acquire(window); ++ session.still_window = window; ++ check("still-target", ACameraOutputTarget_create(window, &mut session.still_target))?; ++ pointer("still-target", session.still_target)?; ++ check("still-output", ACaptureSessionOutput_create(window, &mut session.still_output))?; ++ pointer("still-output", session.still_output)?; + } - } - - let mut output_container = std::ptr::null_mut(); @@ -356,7 +334,11 @@ - if !preview_output.is_null() { - ACaptureSessionOutputContainer_add(output_container, preview_output); - } -- } ++ check("output-container", ACaptureSessionOutputContainer_create(&mut session.output_container))?; ++ pointer("output-container", session.output_container)?; ++ for (stage, output) in [("still-output-add", session.still_output), ("preview-output-add", session.image_output)] { ++ if !output.is_null() { check(stage, ACaptureSessionOutputContainer_add(session.output_container, output))?; } + } - } - - if image_output.is_null() && preview_output.is_null() { @@ -419,6 +401,23 @@ - still_target, - still_context, - }) ++ if let Some(window) = preview_window.filter(|w| !w.is_null()) { ++ ANativeWindow_acquire(window); ++ session.preview_window = window; ++ check("native-preview-target", ACameraOutputTarget_create(window, &mut session.preview_target))?; ++ pointer("native-preview-target", session.preview_target)?; ++ check("native-preview-request-target", ACaptureRequest_addTarget(session.capture_request, session.preview_target))?; ++ check("native-preview-output", ACaptureSessionOutput_create(window, &mut session.preview_output))?; ++ pointer("native-preview-output", session.preview_output)?; ++ check("native-preview-output-add", ACaptureSessionOutputContainer_add(session.output_container, session.preview_output))?; ++ } ++ if session.image_output.is_null() && session.preview_output.is_null() { ++ return Err("camera startup has no preview output".into()); ++ } ++ let callbacks = ACameraCaptureSession_stateCallbacks { ++ context: session.capture_context as *mut _, onClosed: Some(Self::session_on_closed), ++ onReady: Some(Self::session_on_ready), onActive: Some(Self::session_on_active), ++ }; + check("configure-session", ACameraDevice_createCaptureSession(session.camera_device, + session.output_container, &callbacks, &mut session.capture_session))?; + pointer("configure-session", session.capture_session)?; @@ -440,7 +439,7 @@ } /// Apply the person's controls to the repeating request. `rotation` is the -@@ -979,68 +869,41 @@ +@@ -979,68 +869,41 @@ unsafe fn capture_still( } unsafe fn stop(self) { @@ -492,20 +491,6 @@ - if !self.preview_target.is_null() { - ACaptureRequest_removeTarget(self.capture_request, self.preview_target); - ACameraOutputTarget_free(self.preview_target); -- } -- ACaptureRequest_free(self.capture_request); -- if !self.image_window.is_null() { -- ANativeWindow_release(self.image_window); -- } -- if !self.preview_window.is_null() { -- ANativeWindow_release(self.preview_window); -- } -- if !self.image_reader.is_null() { -- AImageReader_delete(self.image_reader); -- } -- ACameraDevice_close(self.camera_device); -- let _ = Box::from_raw(self.capture_context); -- } + if !self.capture_context.is_null() { (*self.capture_context).alive.store(false, Ordering::Relaxed); } + if !self.still_context.is_null() { (*self.still_context).alive.store(false, Ordering::Relaxed); } + // Stop producers before deleting their windows/readers and callback @@ -521,30 +506,39 @@ + AImageReader_setImageListener(reader, &mut none); + AImageReader_delete(reader); + } -+ } + } +- ACaptureRequest_free(self.capture_request); +- if !self.image_window.is_null() { +- ANativeWindow_release(self.image_window); + if !self.output_container.is_null() { ACaptureSessionOutputContainer_free(self.output_container); } + for output in [self.image_output, self.preview_output, self.still_output] { + if !output.is_null() { ACaptureSessionOutput_free(output); } -+ } + } +- if !self.preview_window.is_null() { +- ANativeWindow_release(self.preview_window); + for target in [self.image_target, self.preview_target] { + if !target.is_null() { + if !self.capture_request.is_null() { ACaptureRequest_removeTarget(self.capture_request, target); } + ACameraOutputTarget_free(target); + } -+ } + } +- if !self.image_reader.is_null() { +- AImageReader_delete(self.image_reader); + if !self.still_target.is_null() { ACameraOutputTarget_free(self.still_target); } + if !self.capture_request.is_null() { ACaptureRequest_free(self.capture_request); } + for window in [self.image_window, self.preview_window, self.still_window] { + if !window.is_null() { ANativeWindow_release(window); } -+ } + } +- ACameraDevice_close(self.camera_device); +- let _ = Box::from_raw(self.capture_context); + if !self.still_context.is_null() { let _ = Box::from_raw(self.still_context); } + if !self.capture_context.is_null() { let _ = Box::from_raw(self.capture_context); } -+ } + } + } pub struct AndroidCameraAccess { -@@ -1225,7 +1088,7 @@ +@@ -1225,7 +1088,7 @@ fn restart_stream_if_needed(&mut self, key: CameraStreamKey) { *node.dispatch.lock().unwrap() = dispatch; @@ -553,7 +547,7 @@ || node.preview_window != desired_preview_window || node.needs_image_reader != needs_image_reader; -@@ -1249,7 +1112,8 @@ +@@ -1249,7 +1112,8 @@ fn restart_stream_if_needed(&mut self, key: CameraStreamKey) { .iter() .find(|device| device.desc.input_id == key.input_id) .and_then(|device| device.still_size.map(|(w, h)| (key.input_id, w, h))); @@ -563,7 +557,7 @@ AndroidCaptureSession::start( node.dispatch.clone(), self.manager, -@@ -1264,6 +1128,10 @@ +@@ -1264,6 +1128,10 @@ fn restart_stream_if_needed(&mut self, key: CameraStreamKey) { still, ) }; @@ -574,7 +568,7 @@ if let Some(session) = node.session.as_ref() { let (active_array, rotation) = self .devices -@@ -1386,6 +1254,7 @@ +@@ -1386,6 +1254,7 @@ fn reconcile_streams(&mut self) { controls: CameraControlState::default(), dispatch: Arc::new(Mutex::new(StreamDispatch::default())), session: None, @@ -582,7 +576,7 @@ preview_window: std::ptr::null_mut(), needs_image_reader: false, }, -@@ -1432,6 +1301,7 @@ +@@ -1432,6 +1301,7 @@ pub fn register_preview( crate::log!("camera: no stream for this input and format; the preview stays empty"); return; }; @@ -590,7 +584,7 @@ if let Some(old) = self.preview_subscriptions.remove(&video_id) { if !old.preview_window.is_null() { unsafe { ANativeWindow_release(old.preview_window) }; -@@ -1462,6 +1332,7 @@ +@@ -1462,6 +1332,7 @@ pub fn register_preview_hardware_buffer( return; }; @@ -598,21 +592,23 @@ if let Some(old) = self.preview_subscriptions.remove(&video_id) { if !old.preview_window.is_null() { unsafe { ANativeWindow_release(old.preview_window) }; -@@ -1478,6 +1349,11 @@ - }, - ); +@@ -1480,6 +1351,11 @@ pub fn register_preview_hardware_buffer( self.reconcile_streams(); -+ } -+ + } + + pub fn preview_start_error(&self, video_id: LiveId) -> Option { + let sub = self.preview_subscriptions.get(&video_id)?; + self.streams.get(&sub.stream)?.start_error.clone() - } - ++ } ++ pub fn update_preview_window( + &mut self, + video_id: LiveId, +diff --git a/platform/src/os/linux/android/android_camera_player.rs b/platform/src/os/linux/android/android_camera_player.rs +index 2dff90dd5..4fb79fdc3 100644 --- a/platform/src/os/linux/android/android_camera_player.rs +++ b/platform/src/os/linux/android/android_camera_player.rs -@@ -237,63 +237,42 @@ +@@ -237,63 +237,42 @@ pub fn check_prepared(&mut self) -> Option> { return None; } @@ -629,7 +625,8 @@ - vec!["camera".to_string()], - vec![], - ))); -- } ++ return Some(Err(error)); + } - - if let Some(latest) = self.hardware_buffer_frame.as_ref() { - let guard = latest.lock().unwrap(); @@ -643,8 +640,6 @@ - }; - self.width = frame.width; - self.height = frame.height; -+ return Some(Err(error)); -+ } + // Readiness wins over the deadline: the UI can be suspended after the + // producer queues its first frame. A late poll must not reject it. + let ready_size = if self.native_preview && self.prepared { @@ -677,7 +672,10 @@ - let frames = self.i420_frames.as_mut()?; - if !frames.prime_pending_from_latest() { - return None; -- } ++ if !self.native_preview && self.created_at.elapsed().as_secs_f32() >= 10.0 { ++ self.prepare_notified = true; ++ return Some(Err("camera startup first-frame timed out after 10 seconds".into())); + } - - let (width, height) = { - let frame = frames.pending_frame()?; @@ -695,10 +693,6 @@ - vec!["camera".to_string()], - vec![], - ))) -+ if !self.native_preview && self.created_at.elapsed().as_secs_f32() >= 10.0 { -+ self.prepare_notified = true; -+ return Some(Err("camera startup first-frame timed out after 10 seconds".into())); -+ } + if !self.warned_waiting_for_first_frame && self.created_at.elapsed().as_secs_f32() >= 2.0 { + self.warned_waiting_for_first_frame = true; + } @@ -706,9 +700,11 @@ } pub fn take_hardware_buffer_frame(&mut self) -> Option { +diff --git a/widgets/src/camera_preview.rs b/widgets/src/camera_preview.rs +index 4a477b7dd..ce7ca3217 100644 --- a/widgets/src/camera_preview.rs +++ b/widgets/src/camera_preview.rs -@@ -229,6 +229,10 @@ +@@ -229,6 +229,10 @@ fn start(&mut self, cx: &mut Cx) -> bool { return false; } self.wanted = true; @@ -719,7 +715,7 @@ if self.permission.is_none() && !self.permission_pending { self.permission_pending = true; self.asked_at = cx.seconds_since_app_start(); -@@ -574,19 +578,20 @@ +@@ -574,19 +578,20 @@ fn handle_event(&mut self, cx: &mut Cx, event: &Event, scope: &mut Scope) { .collect(); self.drive(cx); } @@ -743,12 +739,10 @@ let why = format!("the camera stopped: {}", e.error); self.fail(cx, why); } -@@ -616,6 +621,43 @@ - - fn format(width: usize, height: usize, pixel_format: VideoPixelFormat, id: u64) -> VideoFormat { +@@ -618,6 +623,43 @@ fn format(width: usize, height: usize, pixel_format: VideoPixelFormat, id: u64) VideoFormat { format_id: VideoFormatId(LiveId(id)), width, height, frame_rate: Some(30.0), pixel_format } -+ } -+ + } + + #[test] + fn failed_start_stops_retries_until_explicit_start_and_ignores_other_players() { + let mut cx = Cx::new(Box::new(|_, _| {})); @@ -784,12 +778,16 @@ + assert!(!preview.permission_pending, "an already granted camera does not prompt again"); + preview.stop(&mut cx); + crate::splash_policy::gc_policies(&[heap]); - } - ++ } ++ #[test] + fn the_preview_profile_matches_the_viewfinder_then_takes_the_most_pixels() { + let choice = CameraChoice { +diff --git a/widgets/src/video.rs b/widgets/src/video.rs +index 16c48f7d1..197599cf4 100644 --- a/widgets/src/video.rs +++ b/widgets/src/video.rs -@@ -622,6 +622,11 @@ +@@ -622,6 +622,11 @@ fn on_after_apply( } impl VideoRef { diff --git a/tools/runtime-patches/makepad-camera-permission-wait.patch b/tools/runtime-patches/makepad-camera-permission-wait.patch index c1a9b4f26..44e5da964 100644 --- a/tools/runtime-patches/makepad-camera-permission-wait.patch +++ b/tools/runtime-patches/makepad-camera-permission-wait.patch @@ -1,7 +1,8 @@ diff --git a/widgets/src/camera_preview.rs b/widgets/src/camera_preview.rs +index 72bccb9fb..4a477b7dd 100644 --- a/widgets/src/camera_preview.rs +++ b/widgets/src/camera_preview.rs -@@ -185,6 +185,8 @@ +@@ -185,6 +185,8 @@ pub struct CameraPreview { #[rust] asked_at: f64, #[rust] @@ -10,7 +11,7 @@ diff --git a/widgets/src/camera_preview.rs b/widgets/src/camera_preview.rs timer: Option, #[rust] drawn: bool, -@@ -227,7 +229,8 @@ +@@ -227,7 +229,8 @@ fn start(&mut self, cx: &mut Cx) -> bool { return false; } self.wanted = true; @@ -20,7 +21,7 @@ diff --git a/widgets/src/camera_preview.rs b/widgets/src/camera_preview.rs self.asked_at = cx.seconds_since_app_start(); cx.request_permission(Permission::Camera); } -@@ -284,7 +287,12 @@ +@@ -284,7 +287,12 @@ fn drive(&mut self, cx: &mut Cx) { self.fail(cx, format!("camera permission {status:?}")); return; } @@ -34,7 +35,7 @@ diff --git a/widgets/src/camera_preview.rs b/widgets/src/camera_preview.rs None => self.permission = Some(PermissionStatus::Granted), } let Some(choice) = self.cameras.iter().find(|c| c.front == want_front).or(self.cameras.first()).cloned() else { -@@ -547,7 +555,13 @@ +@@ -547,7 +555,13 @@ fn handle_event(&mut self, cx: &mut Cx, event: &Event, scope: &mut Scope) { } match event { Event::PermissionResult(result) if result.permission == Permission::Camera => { @@ -49,9 +50,10 @@ diff --git a/widgets/src/camera_preview.rs b/widgets/src/camera_preview.rs } Event::VideoInputs(VideoInputsEvent { descs }) => { diff --git a/widgets/src/video.rs b/widgets/src/video.rs +index 67f028061..16c48f7d1 100644 --- a/widgets/src/video.rs +++ b/widgets/src/video.rs -@@ -1670,7 +1670,10 @@ +@@ -1665,7 +1665,10 @@ fn handle_secondary_click( } fn pause_playback(&mut self, cx: &mut Cx) { diff --git a/tools/runtime-patches/makepad-desktop-trusted-input.patch b/tools/runtime-patches/makepad-desktop-trusted-input.patch index 6d48740b0..47aa9bc5e 100644 --- a/tools/runtime-patches/makepad-desktop-trusted-input.patch +++ b/tools/runtime-patches/makepad-desktop-trusted-input.patch @@ -112,10 +112,10 @@ index 371b5401b..172a09162 100644 fn android_requires_physical_unobscured_touch_and_never_cancel() { for action in [0, 1, 2, 5, 6] { diff --git a/platform/src/os/apple/macos/macos.rs b/platform/src/os/apple/macos/macos.rs -index df40d386d..064e008f0 100644 +index fe73a2d47..342378ca8 100644 --- a/platform/src/os/apple/macos/macos.rs +++ b/platform/src/os/apple/macos/macos.rs -@@ -1067,9 +1067,9 @@ fn cocoa_event_callback( +@@ -1076,9 +1076,9 @@ fn cocoa_event_callback( } // send a mouse up when dragging starts match &event { @@ -127,7 +127,7 @@ index df40d386d..064e008f0 100644 | MacosEvent::Scroll(_) | MacosEvent::KeyDown(_) | MacosEvent::KeyUp(_) -@@ -1505,7 +1505,7 @@ fn cocoa_event_callback( +@@ -1518,7 +1518,7 @@ fn cocoa_event_callback( // ok here we send out to all our childprocesses self.handle_repaint(metal_windows, metal_cx); } @@ -136,7 +136,7 @@ index df40d386d..064e008f0 100644 if !self.windows.is_valid(e.window_id) || !self.windows[e.window_id].is_created { return EventFlow::Wait; } -@@ -1513,7 +1513,7 @@ fn cocoa_event_callback( +@@ -1526,7 +1526,7 @@ fn cocoa_event_callback( self.dpi_override_scale(&mut e.abs, e.window_id); self.fingers.process_tap_count(e.abs, e.time); self.fingers.mouse_down(e.button, e.window_id); @@ -145,7 +145,7 @@ index df40d386d..064e008f0 100644 self.update_pointer_capture_pacing(); } MacosEvent::MouseMove(mut e) => { -@@ -1545,7 +1545,7 @@ fn cocoa_event_callback( +@@ -1558,7 +1558,7 @@ fn cocoa_event_callback( self.fingers.cycle_hover_area(live_id!(mouse).into()); self.fingers.switch_captures(); } @@ -154,7 +154,7 @@ index df40d386d..064e008f0 100644 if !self.windows.is_valid(e.window_id) || !self.windows[e.window_id].is_created { return EventFlow::Wait; } -@@ -1553,7 +1553,7 @@ fn cocoa_event_callback( +@@ -1566,7 +1566,7 @@ fn cocoa_event_callback( let button = e.button; let abs = e.abs; let modifiers = e.modifiers; @@ -384,10 +384,10 @@ index d11b9b962..0ca4dfa38 100644 } // Dispatch any synthetic geom changes queued during the original diff --git a/platform/src/remote.rs b/platform/src/remote.rs -index 4d348fa72..147397b49 100644 +index 6052e9cba..b6ab41df8 100644 --- a/platform/src/remote.rs +++ b/platform/src/remote.rs -@@ -997,7 +997,7 @@ pub(crate) fn next_grab_deadline() -> Option { +@@ -996,7 +996,7 @@ pub(crate) fn next_grab_deadline() -> Option { } fn poll_with_present(cx: &mut Cx, mut present: impl FnMut(&mut Cx, WindowId) -> Option) -> bool { diff --git a/tools/runtime-patches/makepad-desktop-webview.patch b/tools/runtime-patches/makepad-desktop-webview.patch index f1bdbcfcd..880141105 100644 --- a/tools/runtime-patches/makepad-desktop-webview.patch +++ b/tools/runtime-patches/makepad-desktop-webview.patch @@ -1,16 +1,16 @@ diff --git a/platform/Cargo.toml b/platform/Cargo.toml -index 8f4a84ffd..31b5a5398 100644 +index 41a615d82..93aa67187 100644 --- a/platform/Cargo.toml +++ b/platform/Cargo.toml -@@ -43,6 +43,7 @@ makepad-zune-png = { path = "../libs/zune/zune-png", version = "0.5.2" } - makepad-tsdf = { path = "../libs/tsdf", version = "0.1.0" } +@@ -44,6 +44,7 @@ makepad-tsdf = { path = "../libs/tsdf", version = "0.1.0" } smallvec = { version = "1.15", path = "../libs/smallvec" } bitflags = { version = "2.10", path = "../libs/bitflags" } + foldhash = { version = "0.2", path = "../libs/vulkan/foldhash" } +url = "2.5.8" [target.wasm32-unknown-unknown.dependencies] makepad-wasm-bridge = { path = "../libs/wasm_bridge", version = "1.0.0" } -@@ -107,6 +108,9 @@ ohos-sys = { version = "0.2.1", features = ["xcomponent"] } +@@ -108,6 +109,9 @@ ohos-sys = { version = "0.2.1", features = ["xcomponent"] } path = "../libs/futures_legacy" version = "1.0.0" @@ -20,7 +20,7 @@ index 8f4a84ffd..31b5a5398 100644 [target.'cfg(windows)'.dependencies.windows-core] path = "../libs/windows/windows-core" version = "0.62.2" -@@ -165,3 +169,8 @@ features = [ +@@ -166,3 +170,8 @@ features = [ ## `windows-targets` v0.52.*, so we use that version to avoid duplicates. [target.'cfg(windows)'.dependencies.windows-targets] version = "0.52" @@ -30,7 +30,7 @@ index 8f4a84ffd..31b5a5398 100644 +version = "=0.62.2" +features = ["Win32_Foundation", "Win32_System_Com", "Win32_System_Com_StructuredStorage", "Win32_System_LibraryLoader", "Win32_UI_WindowsAndMessaging"] diff --git a/platform/src/cx_api.rs b/platform/src/cx_api.rs -index 96085e29b..8c8072dc9 100644 +index 8ad82ca09..d419cea94 100644 --- a/platform/src/cx_api.rs +++ b/platform/src/cx_api.rs @@ -209,8 +209,8 @@ pub fn close(&mut self) { @@ -637,10 +637,10 @@ index e234e0fee..1994e6a98 100644 // this has to carry the same gate as its siblings: Android and OHOS are // `os::linux` too, and would otherwise fail to resolve `wayland_client`. diff --git a/platform/src/os/linux/wayland/linux_wayland.rs b/platform/src/os/linux/wayland/linux_wayland.rs -index 9fa1ec6a8..94021ab60 100644 +index 1a814f7e8..f3d3b916b 100644 --- a/platform/src/os/linux/wayland/linux_wayland.rs +++ b/platform/src/os/linux/wayland/linux_wayland.rs -@@ -184,6 +184,7 @@ fn state_event_callback(&mut self, state: &mut WaylandState, event: XlibEvent) - +@@ -198,6 +198,7 @@ fn state_event_callback(&mut self, state: &mut WaylandState, event: XlibEvent) - } } match event { @@ -648,7 +648,7 @@ index 9fa1ec6a8..94021ab60 100644 XlibEvent::WindowGotFocus(window_id) => { // repaint all window passes. Metal sometimes doesnt flip buffers when hidden/no focus let mut cx = self.cx.borrow_mut(); -@@ -674,6 +675,14 @@ fn handle_platform_ops(&self, state: &mut WaylandState) -> EventFlow { +@@ -736,6 +737,14 @@ fn handle_platform_ops(&self, state: &mut WaylandState) -> EventFlow { } } match op { @@ -664,7 +664,7 @@ index 9fa1ec6a8..94021ab60 100644 #[cfg(not(use_vulkan))] let gl_cx = cx.os.opengl_cx.as_ref().unwrap(); diff --git a/platform/src/os/linux/windowing_backend.rs b/platform/src/os/linux/windowing_backend.rs -index 499c6dc3c..f7e8af444 100644 +index aeb3efaac..4cbfdb617 100644 --- a/platform/src/os/linux/windowing_backend.rs +++ b/platform/src/os/linux/windowing_backend.rs @@ -11,6 +11,13 @@ @@ -718,7 +718,7 @@ index 499c6dc3c..f7e8af444 100644 println!("Reason: DISPLAY environment variable is set"); } else { diff --git a/platform/src/os/linux/x11/linux_x11.rs b/platform/src/os/linux/x11/linux_x11.rs -index b5c58e27e..c445ec3b0 100644 +index 4df47ec0f..55a618663 100644 --- a/platform/src/os/linux/x11/linux_x11.rs +++ b/platform/src/os/linux/x11/linux_x11.rs @@ -1,6 +1,7 @@ @@ -805,8 +805,8 @@ index b5c58e27e..c445ec3b0 100644 + } let mut cx = self.cx.borrow_mut(); if e.timer_id == 0 { - if SignalToUI::check_and_clear_ui_signal() { -@@ -567,6 +587,7 @@ fn handle_platform_ops( + let internal_signal = SignalToUI::check_and_clear_internal_signal(); +@@ -571,6 +591,7 @@ fn handle_platform_ops( opengl_windows.push(opengl_window); } CxOsOp::CloseWindow(window_id) => { @@ -814,7 +814,7 @@ index b5c58e27e..c445ec3b0 100644 drop(cx); self.close_popup_children(opengl_windows, xlib_app, window_id); cx = self.cx.borrow_mut(); -@@ -596,7 +617,7 @@ fn handle_platform_ops( +@@ -600,7 +621,7 @@ fn handle_platform_ops( } } } @@ -823,7 +823,7 @@ index b5c58e27e..c445ec3b0 100644 CxOsOp::MinimizeWindow(window_id) => { if let Some(window) = opengl_windows.iter_mut().find(|w| w.window_id == window_id) -@@ -912,6 +933,53 @@ fn handle_platform_ops( +@@ -916,6 +937,53 @@ fn handle_platform_ops( let _ = player.select_audio_track(index); } } @@ -920,7 +920,7 @@ index 6e69c6605..84372712a 100644 MouseDown(MouseDownEvent), MouseUp(MouseUpEvent), diff --git a/platform/src/os/windows/d3d11.rs b/platform/src/os/windows/d3d11.rs -index f060468ab..6beec0f15 100644 +index 08802b7b0..1fdf234af 100644 --- a/platform/src/os/windows/d3d11.rs +++ b/platform/src/os/windows/d3d11.rs @@ -33,7 +33,7 @@ @@ -932,7 +932,7 @@ index f060468ab..6beec0f15 100644 D3D_SRV_DIMENSION_TEXTURECUBE, }, Direct3D11::{ -@@ -2542,15 +2542,19 @@ fn create_device_tier() -> windows_core::Result<( +@@ -2543,15 +2543,19 @@ fn create_device_tier() -> windows_core::Result<( // before a hybrid-GPU transition or a driver reinstall keeps handing back the // adapter that went away. Recovery always starts from a fresh factory. let factory: IDXGIFactory2 = CreateDXGIFactory2(DXGI_CREATE_FACTORY_FLAGS(0))?; @@ -969,7 +969,7 @@ index 19aea0eb4..9ec64eaec 100644 pub mod windows_media_engine_notify; pub mod windows_mf_source_reader; diff --git a/platform/src/os/windows/windows.rs b/platform/src/os/windows/windows.rs -index c0404864b..19a5b124a 100644 +index 250cb26dd..b9c25eb14 100644 --- a/platform/src/os/windows/windows.rs +++ b/platform/src/os/windows/windows.rs @@ -24,6 +24,7 @@ @@ -988,7 +988,7 @@ index c0404864b..19a5b124a 100644 // Cascade-close popups parented to this window. Their own WindowClosed // arrives via the queued authentic event; removing the D3d11Window now // makes the app's `WindowHandle::close()` response a no-op. -@@ -990,6 +992,7 @@ fn handle_platform_ops( +@@ -994,6 +996,7 @@ fn handle_platform_ops( }); } CxOsOp::CloseWindow(window_id) => { @@ -996,7 +996,7 @@ index c0404864b..19a5b124a 100644 // The authentic WindowClosed event this triggers delivers // Event::WindowClosed and the exit check; firing it here would double it. // Remove the D3d11Window now so later ops cannot touch the destroyed hwnd. -@@ -1290,6 +1293,51 @@ fn handle_platform_ops( +@@ -1294,6 +1297,51 @@ fn handle_platform_ops( player.set_playback_rate(rate); } } @@ -1048,7 +1048,7 @@ index c0404864b..19a5b124a 100644 // Track selection is currently implemented on Linux GStreamer only. CxOsOp::SelectVideoTrack(_, _) | CxOsOp::SelectAudioTrack(_, _) => {} CxOsOp::AttachCameraNativePreview { .. } -@@ -1424,6 +1472,7 @@ pub struct CxOs { +@@ -1428,6 +1476,7 @@ pub struct CxOs { pub(crate) game_input_events: GameInputEventChannel, pub(crate) windows_game_input: Option, pub(crate) video_players: HashMap, diff --git a/tools/runtime-patches/makepad-device-consent.patch b/tools/runtime-patches/makepad-device-consent.patch index 8f7cbae09..25327637f 100644 --- a/tools/runtime-patches/makepad-device-consent.patch +++ b/tools/runtime-patches/makepad-device-consent.patch @@ -322,7 +322,7 @@ index ce7ca3217..21e6b1eae 100644 fn the_preview_profile_matches_the_viewfinder_then_takes_the_most_pixels() { let choice = CameraChoice { diff --git a/widgets/src/splash.rs b/widgets/src/splash.rs -index fc374b01a..c8f86598b 100644 +index 56d06f846..5165a076d 100644 --- a/widgets/src/splash.rs +++ b/widgets/src/splash.rs @@ -68,6 +68,9 @@ pub struct Splash { @@ -335,7 +335,7 @@ index fc374b01a..c8f86598b 100644 /// Whole-jail byte cap when the host has granted this app extra room. /// None leaves the storage default. #[rust] -@@ -227,6 +230,7 @@ fn eval_styled_body_with_apply(&mut self, cx: &mut Cx, preserve: bool, apply: &A +@@ -229,6 +232,7 @@ fn eval_styled_body_with_apply(&mut self, cx: &mut Cx, preserve: bool, apply: &A crate::splash_host::set_prompts_for_heap(heap_key, self.host_prompts); crate::splash_storage::set_quota_for_heap(heap_key, self.storage_quota); self.apply_policy(cx, heap_key); @@ -343,7 +343,7 @@ index fc374b01a..c8f86598b 100644 if !crate::splash_policy::may_run(heap_key) { log!("splash: {} is stopped: its instruction budget is spent", self.source_label()); return; -@@ -831,6 +835,16 @@ pub fn set_host_prompts(&mut self, cx: &mut Cx, may_prompt: bool) { +@@ -833,6 +837,16 @@ pub fn set_host_prompts(&mut self, cx: &mut Cx, may_prompt: bool) { } } @@ -357,10 +357,10 @@ index fc374b01a..c8f86598b 100644 + } + } + - /// Puts this isolate under an ENFORCED policy (ADR 0002): `host.request` - /// is refused outside `host_caps`, every network path (`net`, artwork, - /// `sys.*` data) is refused outside `hosts`, and `instruction_budget` -@@ -1005,6 +1019,11 @@ pub fn set_host_prompts(&self, cx: &mut Cx, may_prompt: bool) { + /// Puts this isolate under an ENFORCED policy (ADR 0002): files stay in + /// its jail, and the runtime's own reads of the person's data and devices + /// need their grants. `hosts`, `instruction_budget` and the capabilities +@@ -1007,6 +1021,11 @@ pub fn set_host_prompts(&self, cx: &mut Cx, may_prompt: bool) { } } @@ -372,7 +372,7 @@ index fc374b01a..c8f86598b 100644 /// See [`Splash::isolate_heap_key`]. pub fn isolate_heap_key(&self, cx: &mut Cx) -> Option { if let Some(mut inner) = self.borrow_mut() { -@@ -3762,7 +3781,8 @@ pub fn register_agent_module(vm: &mut ScriptVm) { +@@ -3764,7 +3783,8 @@ pub fn register_agent_module(vm: &mut ScriptVm) { id_lut!(request_location), script_args_def!(), |vm, _args| { @@ -383,7 +383,7 @@ index fc374b01a..c8f86598b 100644 } #[cfg(target_os = "android")] diff --git a/widgets/src/splash_host.rs b/widgets/src/splash_host.rs -index 81529af28..a8b8f1f65 100644 +index c77786d1a..52cab0d95 100644 --- a/widgets/src/splash_host.rs +++ b/widgets/src/splash_host.rs @@ -97,6 +97,11 @@ pub(crate) fn set_tag_for_heap(heap_key: usize, tag: Option) { @@ -410,11 +410,11 @@ index 81529af28..a8b8f1f65 100644 pub(crate) fn gc_bridge(dead_heaps: &[usize]) { BRIDGE.with(|b| { diff --git a/widgets/src/splash_policy.rs b/widgets/src/splash_policy.rs -index 04cf78b36..b0f356bf6 100644 +index 56e21eee3..ea4e6396f 100644 --- a/widgets/src/splash_policy.rs +++ b/widgets/src/splash_policy.rs -@@ -24,7 +24,8 @@ - //! host, URLs are allowed, and only the per-evaluation cap applies. +@@ -35,7 +35,8 @@ + //! host. use crate::makepad_draw::makepad_platform::makepad_script_std; use std::cell::RefCell; -use std::collections::HashMap; @@ -423,7 +423,7 @@ index 04cf78b36..b0f356bf6 100644 #[derive(Clone, Debug, Default)] pub struct HeapPolicy { -@@ -43,6 +44,45 @@ pub struct HeapPolicy { +@@ -53,6 +54,45 @@ pub struct HeapPolicy { thread_local! { static POLICIES: RefCell> = RefCell::new(HashMap::new()); @@ -469,7 +469,7 @@ index 04cf78b36..b0f356bf6 100644 } /// Set (or replace) the policy for a heap. Enforcement starts here: a heap -@@ -70,6 +110,7 @@ pub fn set_policy_for_heap(heap_key: usize, capabilities: Vec, hosts: Ve +@@ -75,6 +115,7 @@ pub fn set_policy_for_heap(heap_key: usize, capabilities: Vec, hosts: Ve /// Drop policies for reclaimed isolates (from the isolate GC). pub(crate) fn gc_policies(dead_heaps: &[usize]) { @@ -477,8 +477,8 @@ index 04cf78b36..b0f356bf6 100644 POLICIES.with(|p| { let mut p = p.borrow_mut(); for heap in dead_heaps { -@@ -95,7 +136,8 @@ pub fn service_allowed(heap_key: usize, service: &str) -> Result<(), String> { - } +@@ -98,7 +139,8 @@ pub fn service_allowed(heap_key: usize, service: &str) -> Result<(), String> { + let Some(policy) = p.get(&heap_key) else { return Ok(()) }; let family = service.split('.').next().unwrap_or(service); if policy.capabilities.iter().any(|c| c == service || c == family) { - Ok(()) @@ -487,8 +487,8 @@ index 04cf78b36..b0f356bf6 100644 } else { Err(format!("this app was not granted {family:?}, which {service:?} needs")) } -@@ -459,6 +501,40 @@ fn a_policed_heap_opens_no_server_and_no_raw_socket_whatever_it_holds() { - assert!(makepad_script_std::script_sockets_allowed(911)); +@@ -386,6 +428,40 @@ fn a_policy_installs_no_gate() { + gc_policies(&[911]); } + #[test] diff --git a/tools/runtime-patches/makepad-host-api-runtime-fixes.patch b/tools/runtime-patches/makepad-host-api-runtime-fixes.patch index f377d41c7..0d6dad563 100644 --- a/tools/runtime-patches/makepad-host-api-runtime-fixes.patch +++ b/tools/runtime-patches/makepad-host-api-runtime-fixes.patch @@ -254,7 +254,7 @@ index cf0804a97..908287413 100644 /// Set the current thread by ScriptThreadId diff --git a/platform/script/src/vm.rs b/platform/script/src/vm.rs -index a25df99d6..84e4ab0ff 100644 +index e9916d8a8..14a217f51 100644 --- a/platform/script/src/vm.rs +++ b/platform/script/src/vm.rs @@ -656,6 +656,85 @@ fn script_me_from_value(&mut self, me: ScriptValue) -> Option { @@ -489,15 +489,20 @@ index 4e380243d..18f2a529a 100644 + assert_eq!(vm.bx.threads.len(), allocated, "completed callback thread was not reused"); +} diff --git a/widgets/src/splash_host.rs b/widgets/src/splash_host.rs -index 15d9c8a76..3709be935 100644 +index b086c99b4..a5ddb50df 100644 --- a/widgets/src/splash_host.rs +++ b/widgets/src/splash_host.rs -@@ -536,6 +536,68 @@ fn delayed_background_callbacks_keep_nested_requests_nonprompting() { +@@ -517,6 +517,78 @@ fn delayed_background_callbacks_keep_nested_requests_nonprompting() { gc_bridge(&[heap]); } ++ /// A refusal deep in a chain of callbacks still updates a widget and ++ /// completes the nested request. Since makepad#118 (OctoSense #450) the ++ /// runtime no longer refuses a family the app did not declare, so the ++ /// host refuses here, as the device service does for an app without ++ /// `location`. + #[test] -+ fn synchronous_refusal_can_update_a_widget_and_complete_a_nested_callback() { ++ fn a_refused_request_can_update_a_widget_and_complete_a_nested_callback() { + use crate::*; + let mut cx = Cx::new(Box::new(|_, _| {})); + let root = cx.with_vm(|vm| { @@ -534,11 +539,16 @@ index 15d9c8a76..3709be935 100644 + "#); + let heap = card.isolate_heap_key(&mut cx).unwrap(); + assert!(card.call_script_fn(&mut cx, id!(run), &[])); -+ for service in ["fixture.status", "fixture.import", "fixture.export"] { ++ for service in ["fixture.status", "fixture.import", "fixture.export", "location.sample"] { + let requests = take_splash_host_requests_for(&[heap]); + assert_eq!(requests.len(), 1, "missing {service}"); + assert_eq!(requests[0].service, service); -+ splash_host_respond(&mut cx, heap, requests[0].req_id, Ok("{}")); ++ let answer = if service == "location.sample" { ++ Err("permission_denied: this app was not granted location") ++ } else { ++ Ok("{}") ++ }; ++ splash_host_respond(&mut cx, heap, requests[0].req_id, answer); + } + crate::makepad_platform::makepad_script_std::pump(&mut cx); + let vm_id = crate::widget_async::vm_for_heap(&mut cx, heap).unwrap(); diff --git a/tools/runtime-patches/makepad-host-json-boundary.patch b/tools/runtime-patches/makepad-host-json-boundary.patch index 4cbdf8cfc..92b1e9d51 100644 --- a/tools/runtime-patches/makepad-host-json-boundary.patch +++ b/tools/runtime-patches/makepad-host-json-boundary.patch @@ -82,7 +82,7 @@ index 58c21a392..11292ce9e 100644 fn numeric_string_conversion_handles_inline_and_heap_strings_and_yields_nan() { let mut host = ScriptVmHost::new((), ()); diff --git a/widgets/src/splash_host.rs b/widgets/src/splash_host.rs -index 81529af28..7028cd4ad 100644 +index 52cab0d95..73c28afce 100644 --- a/widgets/src/splash_host.rs +++ b/widgets/src/splash_host.rs @@ -73,10 +73,30 @@ struct HostBridge { @@ -117,7 +117,7 @@ index 81529af28..7028cd4ad 100644 std::thread_local! { /// UI-thread-owned like the isolate registry itself. static BRIDGE: std::cell::RefCell = Default::default(); -@@ -164,6 +184,7 @@ pub fn splash_host_respond( +@@ -173,6 +193,7 @@ pub fn splash_host_respond( let Some(vm_id) = crate::widget_async::vm_for_heap(cx, heap_key) else { return SplashRespondOutcome::IsolateGone; }; @@ -125,7 +125,7 @@ index 81529af28..7028cd4ad 100644 let mut delivered = true; let contained = crate::widget_async::contain_isolate_panic("host request callback", || { cx.with_script_vm_id(vm_id, |vm| { -@@ -196,7 +217,7 @@ pub fn splash_host_respond( +@@ -205,7 +226,7 @@ pub fn splash_host_respond( return; } vm.with_instruction_limit(WIDGET_SCRIPT_INSTRUCTION_LIMIT, |vm| { @@ -134,7 +134,7 @@ index 81529af28..7028cd4ad 100644 }); if !crate::splash_policy::charge(heap_key, vm.last_limit_consumed() as u64) { crate::makepad_draw::log!("splash host: an app spent its instruction budget in a callback and is stopped"); -@@ -253,13 +274,38 @@ pub fn script_mod(vm: &mut ScriptVm) { +@@ -262,13 +283,38 @@ pub fn script_mod(vm: &mut ScriptVm) { let on_result = script_value!(vm, args.on_result); let mut service = None; @@ -177,15 +177,15 @@ index 81529af28..7028cd4ad 100644 let callback = if let Some(obj) = on_result.as_object() { Some(vm.bx.heap.new_fn_ref(obj)) -@@ -269,7 +315,6 @@ pub fn script_mod(vm: &mut ScriptVm) { +@@ -278,7 +324,6 @@ pub fn script_mod(vm: &mut ScriptVm) { return script_err_invalid_args!(vm.trap(), "on_result must be a fn or nil"); }; - let heap_key = vm.bx.heap.heap_key(); - // ADR 0002 phase 2: the capability list is checked HERE, not - // merely reported. A refused request never reaches the host's - // queue; the app hears `{is_ok: false, error}` like any other -@@ -294,11 +339,11 @@ pub fn script_mod(vm: &mut ScriptVm) { + // No capability check here: an app's capabilities are declarations + // since the ruling of 8 October 2026 (OctoSense #450), and the + // host decides, as for an isolate with no policy. A host service +@@ -287,11 +332,11 @@ pub fn script_mod(vm: &mut ScriptVm) { let mut b = b.borrow_mut(); b.next_req_id += 1; let req_id = b.next_req_id; @@ -199,7 +199,7 @@ index 81529af28..7028cd4ad 100644 b.queue.push(SplashHostRequest { app_tag, heap_key, -@@ -356,6 +401,85 @@ pub fn script_mod(vm: &mut ScriptVm) { +@@ -349,6 +394,85 @@ pub fn script_mod(vm: &mut ScriptVm) { mod tests { use super::*; diff --git a/tools/runtime-patches/makepad-host-os-batch1.patch b/tools/runtime-patches/makepad-host-os-batch1.patch index 0abd3b2a8..2648c51c3 100644 --- a/tools/runtime-patches/makepad-host-os-batch1.patch +++ b/tools/runtime-patches/makepad-host-os-batch1.patch @@ -36,7 +36,7 @@ index 159a9d530..66f5822d5 100644 true } diff --git a/platform/src/cx.rs b/platform/src/cx.rs -index de09102a7..8a48805f3 100644 +index 52beef62b..afc2fa954 100644 --- a/platform/src/cx.rs +++ b/platform/src/cx.rs @@ -131,6 +131,10 @@ pub struct Cx { @@ -50,7 +50,7 @@ index de09102a7..8a48805f3 100644 pub(crate) pending_camera_playbacks: Vec, pub(crate) new_next_frames: HashSet, -@@ -908,6 +912,8 @@ pub fn new(event_handler: Box) -> Self { +@@ -906,6 +910,8 @@ pub fn new(event_handler: Box) -> Self { ime_area: Default::default(), keyboard_shift: 0.0, platform_ops: Default::default(), @@ -60,7 +60,7 @@ index de09102a7..8a48805f3 100644 studio_http: "".to_string(), new_next_frames: Default::default(), diff --git a/platform/src/cx_api.rs b/platform/src/cx_api.rs -index 8c8072dc9..e5087a9bd 100644 +index d419cea94..ac244fadd 100644 --- a/platform/src/cx_api.rs +++ b/platform/src/cx_api.rs @@ -40,6 +40,17 @@ pub enum OpenUrlInPlace { @@ -97,7 +97,7 @@ index 8c8072dc9..e5087a9bd 100644 Self::StopLocationUpdates => write!(f, "StopLocationUpdates"), Self::HttpRequest { .. } => write!(f, "HttpRequest"), -@@ -1867,12 +1880,36 @@ pub fn request_gps_location(&mut self) { +@@ -1874,12 +1887,36 @@ pub fn request_gps_location(&mut self) { /// platform, failures arrive as [`Event::LocationError`]. Platforms /// without a location service log an error and stay silent. pub fn start_location_updates(&mut self) { @@ -458,10 +458,10 @@ index f0b9c7ddf..0ce896e83 100644 if let Some(access) = self.os.media.location.access.as_mut() { access.stop(); diff --git a/platform/src/os/apple/ios/ios.rs b/platform/src/os/apple/ios/ios.rs -index 9b14fcb3c..83a5fab33 100644 +index 9f5e3f543..e13b6b6a1 100644 --- a/platform/src/os/apple/ios/ios.rs +++ b/platform/src/os/apple/ios/ios.rs -@@ -1213,6 +1213,9 @@ fn handle_platform_ops(&mut self, metal_cx: &MetalCx) { +@@ -1217,6 +1217,9 @@ fn handle_platform_ops(&mut self, metal_cx: &MetalCx) { CxOsOp::StartLocationUpdates => { self.apple_start_location_updates(); } @@ -471,7 +471,7 @@ index 9b14fcb3c..83a5fab33 100644 CxOsOp::StopLocationUpdates => { self.apple_stop_location_updates(); } -@@ -1884,8 +1887,29 @@ fn seconds_since_app_start(&self) -> f64 { +@@ -1888,8 +1891,29 @@ fn seconds_since_app_start(&self) -> f64 { .as_secs_f64() } @@ -504,10 +504,10 @@ index 9b14fcb3c..83a5fab33 100644 fn max_texture_width() -> usize { diff --git a/platform/src/os/apple/macos/macos.rs b/platform/src/os/apple/macos/macos.rs -index bfb3af932..877a3781a 100644 +index a24f806dd..c9250405b 100644 --- a/platform/src/os/apple/macos/macos.rs +++ b/platform/src/os/apple/macos/macos.rs -@@ -2157,6 +2157,9 @@ fn handle_platform_ops( +@@ -2170,6 +2170,9 @@ fn handle_platform_ops( CxOsOp::StartLocationUpdates => { self.apple_start_location_updates(); } @@ -518,10 +518,10 @@ index bfb3af932..877a3781a 100644 self.apple_stop_location_updates(); } diff --git a/platform/src/os/linux/android/android.rs b/platform/src/os/linux/android/android.rs -index edf7cf611..26fbdba61 100644 +index 5b583ff97..867305424 100644 --- a/platform/src/os/linux/android/android.rs +++ b/platform/src/os/linux/android/android.rs -@@ -3054,6 +3054,23 @@ fn handle_platform_ops(&mut self) -> EventFlow { +@@ -3101,6 +3101,23 @@ fn handle_platform_ops(&mut self) -> EventFlow { }, } } @@ -545,7 +545,7 @@ index edf7cf611..26fbdba61 100644 CxOsOp::StopLocationUpdates => { self.os.location_updates_wanted = false; unsafe { -@@ -3540,8 +3557,11 @@ fn seconds_since_app_start(&self) -> f64 { +@@ -3587,8 +3604,11 @@ fn seconds_since_app_start(&self) -> f64 { .as_secs_f64() } @@ -572,10 +572,10 @@ index 1e42638f4..5f00c2d45 100644 } } diff --git a/platform/src/os/linux/android/android_jni.rs b/platform/src/os/linux/android/android_jni.rs -index 226a23c85..e77a763d8 100644 +index 7890d0926..a68c5b32d 100644 --- a/platform/src/os/linux/android/android_jni.rs +++ b/platform/src/os/linux/android/android_jni.rs -@@ -1776,13 +1776,17 @@ extern "C" fn Java_dev_makepad_android_MakepadNative_onWebSocketError( +@@ -1813,13 +1813,17 @@ extern "C" fn Java_dev_makepad_android_MakepadNative_onWebSocketError( } unsafe fn jstring_to_string(env: *mut jni_sys::JNIEnv, java_string: jni_sys::jstring) -> String { @@ -600,7 +600,7 @@ index 226a23c85..e77a763d8 100644 } unsafe fn java_string_array_to_vec( -@@ -1797,6 +1801,7 @@ unsafe fn java_string_array_to_vec( +@@ -1834,6 +1838,7 @@ unsafe fn java_string_array_to_vec( for i in 0..length { let string = (**env).GetObjectArrayElement.unwrap()(env, object_array, i as jni_sys::jsize); out.push(jstring_to_string(env, string)); @@ -608,7 +608,7 @@ index 226a23c85..e77a763d8 100644 } out } -@@ -2602,6 +2607,7 @@ pub unsafe fn to_java_open_file_dialog( +@@ -2639,6 +2644,7 @@ pub unsafe fn to_java_open_file_dialog( mime_types: &[String], allow_multiple: bool, file_name: &str, @@ -616,7 +616,7 @@ index 226a23c85..e77a763d8 100644 ) { let env = attach_jni_env(); let mime_jstr = new_java_string(env, mime_type); -@@ -2612,13 +2618,14 @@ pub unsafe fn to_java_open_file_dialog( +@@ -2649,13 +2655,14 @@ pub unsafe fn to_java_open_file_dialog( env, get_activity(), "openFileDialog", @@ -633,7 +633,7 @@ index 226a23c85..e77a763d8 100644 ); (**env).DeleteLocalRef.unwrap()(env, file_name_jstr); -@@ -2626,12 +2633,138 @@ pub unsafe fn to_java_open_file_dialog( +@@ -2663,12 +2670,138 @@ pub unsafe fn to_java_open_file_dialog( (**env).DeleteLocalRef.unwrap()(env, mime_jstr); } @@ -777,7 +777,7 @@ index 226a23c85..e77a763d8 100644 } unsafe fn new_java_string_array( -@@ -3039,6 +3172,22 @@ fn decode_qr_luma(luma: &[u8], w: usize, h: usize) -> Option { +@@ -3076,6 +3209,22 @@ fn decode_qr_luma(luma: &[u8], w: usize, h: usize) -> Option { // Outbound: calls into the buildtool's octos Java (MakepadActivity). // =========================================================================== @@ -820,7 +820,7 @@ index 84f7b6c92..4c0c8cfee 100644 /// What the caller wants a path for. diff --git a/platform/src/os/linux/windowing_backend.rs b/platform/src/os/linux/windowing_backend.rs -index f7e8af444..142635bd4 100644 +index 4cbfdb617..0eb1494ce 100644 --- a/platform/src/os/linux/windowing_backend.rs +++ b/platform/src/os/linux/windowing_backend.rs @@ -188,8 +188,33 @@ fn seconds_since_app_start(&self) -> f64 { @@ -860,10 +860,10 @@ index f7e8af444..142635bd4 100644 } diff --git a/platform/src/os/windows/windows.rs b/platform/src/os/windows/windows.rs -index 19a5b124a..b3cc8f7d7 100644 +index b9c25eb14..fb1dd91b6 100644 --- a/platform/src/os/windows/windows.rs +++ b/platform/src/os/windows/windows.rs -@@ -1453,8 +1453,42 @@ fn seconds_since_app_start(&self) -> f64 { +@@ -1457,8 +1457,42 @@ fn seconds_since_app_start(&self) -> f64 { .as_secs_f64() } @@ -924,10 +924,10 @@ index f57575ea1..082980a69 100644 /// Required on: iOS, Android, macOS, Web /// Auto-granted on: Windows, Linux (no location service there yet) diff --git a/tools/cargo_makepad/src/android/java/dev/makepad/android/MakepadActivity.java b/tools/cargo_makepad/src/android/java/dev/makepad/android/MakepadActivity.java -index e50abdfc3..ebb6cb2ab 100644 +index 13db67689..94c99d8e0 100644 --- a/tools/cargo_makepad/src/android/java/dev/makepad/android/MakepadActivity.java +++ b/tools/cargo_makepad/src/android/java/dev/makepad/android/MakepadActivity.java -@@ -1985,7 +1985,11 @@ public class MakepadActivity +@@ -1983,7 +1983,11 @@ public class MakepadActivity public void onRequestPermissionsResult(int requestId, String[] permissions, int[] grantResults) { super.onRequestPermissionsResult(requestId, permissions, grantResults); @@ -940,7 +940,7 @@ index e50abdfc3..ebb6cb2ab 100644 int status; if (grantResults[i] == PackageManager.PERMISSION_GRANTED) { status = 1; // Granted -@@ -1998,8 +2002,16 @@ public class MakepadActivity +@@ -1996,8 +2000,16 @@ public class MakepadActivity } } @@ -959,7 +959,7 @@ index e50abdfc3..ebb6cb2ab 100644 } // QR-scanner camera permission: open the scanner once granted, else -@@ -2034,6 +2046,10 @@ public class MakepadActivity +@@ -2032,6 +2044,10 @@ public class MakepadActivity public int checkPermission(String permission) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { @@ -970,7 +970,7 @@ index e50abdfc3..ebb6cb2ab 100644 if (checkSelfPermission(permission) == PackageManager.PERMISSION_GRANTED) { return 1; // Granted } else { -@@ -2056,8 +2072,13 @@ public class MakepadActivity +@@ -2054,8 +2070,13 @@ public class MakepadActivity public void requestPermission(String permission, int requestId) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { @@ -986,7 +986,7 @@ index e50abdfc3..ebb6cb2ab 100644 } else { // Permission already granted MakepadNative.onPermissionResult(permission, requestId, 1); // 1 = Granted -@@ -2075,13 +2096,54 @@ public class MakepadActivity +@@ -2073,13 +2094,54 @@ public class MakepadActivity // // kind: 0 = ACTION_OPEN_DOCUMENT, 1 = ACTION_CREATE_DOCUMENT, // 2 = ACTION_OPEN_DOCUMENT_TREE. @@ -1042,7 +1042,7 @@ index e50abdfc3..ebb6cb2ab 100644 ) { runOnUiThread(new Runnable() { @Override -@@ -2111,8 +2173,9 @@ public class MakepadActivity +@@ -2109,8 +2171,9 @@ public class MakepadActivity // Ask for a grant that outlives this process, so a URI handed // to Rust is still openable after the app is killed and // relaunched (see takePersistableUriPermission below). @@ -1054,7 +1054,7 @@ index e50abdfc3..ebb6cb2ab 100644 if (kind != 0) { intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION); } -@@ -2124,6 +2187,7 @@ public class MakepadActivity +@@ -2122,6 +2185,7 @@ public class MakepadActivity // picker, and Rust must not be left waiting forever. Log.e(LOG_TAG, "openFileDialog failed", e); mFileDialogRequests.remove(requestCode); @@ -1062,7 +1062,7 @@ index e50abdfc3..ebb6cb2ab 100644 MakepadNative.onFileDialogResult(requestCode, new String[0]); } } -@@ -2132,6 +2196,7 @@ public class MakepadActivity +@@ -2130,6 +2194,7 @@ public class MakepadActivity // An empty URI array means cancelled, which is a normal outcome. private void handleFileDialogResult(int requestCode, int resultCode, Intent data) { @@ -1070,7 +1070,7 @@ index e50abdfc3..ebb6cb2ab 100644 ArrayList uris = new ArrayList<>(); if (resultCode == RESULT_OK && data != null) { ClipData clip = data.getClipData(); -@@ -2142,13 +2207,13 @@ public class MakepadActivity +@@ -2140,13 +2205,13 @@ public class MakepadActivity for (int i = 0; i < clip.getItemCount(); i++) { Uri uri = clip.getItemAt(i).getUri(); if (uri != null) { @@ -1086,7 +1086,7 @@ index e50abdfc3..ebb6cb2ab 100644 uris.add(uri.toString()); } } -@@ -2989,6 +3054,24 @@ public class MakepadActivity +@@ -2987,6 +3052,24 @@ public class MakepadActivity }).start(); } @@ -1111,7 +1111,7 @@ index e50abdfc3..ebb6cb2ab 100644 // Fire the system share sheet (ACTION_SEND) for social sharing. Called // from Rust via `android_jni::to_java_share_text`. public void shareText(String content) { -@@ -4160,38 +4243,38 @@ public class MakepadActivity +@@ -4158,38 +4241,38 @@ public class MakepadActivity @Override public void onProviderDisabled(String provider) {} }; boolean any = false; diff --git a/tools/runtime-patches/makepad-host-response-json.patch b/tools/runtime-patches/makepad-host-response-json.patch index 5cb7555d8..a3c2491d9 100644 --- a/tools/runtime-patches/makepad-host-response-json.patch +++ b/tools/runtime-patches/makepad-host-response-json.patch @@ -1,5 +1,5 @@ diff --git a/widgets/src/splash_host.rs b/widgets/src/splash_host.rs -index 257498cfc..15d9c8a76 100644 +index 78c9a78c4..b086c99b4 100644 --- a/widgets/src/splash_host.rs +++ b/widgets/src/splash_host.rs @@ -6,7 +6,7 @@ @@ -27,20 +27,7 @@ index 257498cfc..15d9c8a76 100644 if !crate::splash_policy::may_run(heap_key) { delivered = false; return; -@@ -336,9 +338,9 @@ pub fn script_mod(vm: &mut ScriptVm) { - let obj = vm.bx.heap.new_object(); - let error = vm.bx.heap.new_string_from_str(&reason); - let trap = vm.bx.threads.cur().trap.pass(); -- vm.bx.heap.set_value(obj, id!(is_ok).into(), false.into(), trap); -- vm.bx.heap.set_value(obj, id!(data).into(), NIL, trap); -- vm.bx.heap.set_value(obj, id!(error).into(), error, trap); -+ vm.bx.heap.set_value(obj, id_lut!(is_ok).into(), false.into(), trap); -+ vm.bx.heap.set_value(obj, id_lut!(data).into(), NIL, trap); -+ vm.bx.heap.set_value(obj, id_lut!(error).into(), error, trap); - vm.call(callback.as_object().into(), &[obj.into()]); - (0.0f64).into() - } -@@ -413,6 +415,90 @@ pub fn script_mod(vm: &mut ScriptVm) { +@@ -397,6 +399,87 @@ pub fn script_mod(vm: &mut ScriptVm) { mod tests { use super::*; @@ -48,7 +35,10 @@ index 257498cfc..15d9c8a76 100644 + fn raw_service_envelopes_round_trip_without_script_field_lookups() { + const CHILD: &str = "MAKEPAD_TEST_RAW_HOST_ENVELOPE"; + if std::env::var_os(CHILD).is_none() { -+ for mode in ["success", "error", "refusal"] { ++ // No "refusal" mode: the runtime built that envelope for a ++ // request its capability check refused, which makepad#118 ++ // removed; the host answers every request now (OctoSense #450). ++ for mode in ["success", "error"] { + let output = std::process::Command::new(std::env::current_exe().unwrap()) + .args(["--exact", "splash_host::tests::raw_service_envelopes_round_trip_without_script_field_lookups", "--nocapture"]) + .env(CHILD, mode).output().unwrap(); @@ -78,11 +68,7 @@ index 257498cfc..15d9c8a76 100644 + let card = root.splash(&mut cx, ids!(card)); + card.set_host_caps(&mut cx, vec!["fixture".into()]); + card.set_policy(&mut cx, Some(vec![]), Some(100_000)); -+ let service = if mode == "refusal" { -+ "ungranted.read" -+ } else { -+ "fixture.read" -+ }; ++ let service = "fixture.read"; + // In particular, do not reference any envelope field in this source: + // field access would intern its spelling and mask the native bug. + card.set_text( @@ -97,15 +83,13 @@ index 257498cfc..15d9c8a76 100644 + ), + ); + let heap = card.isolate_heap_key(&mut cx).unwrap(); -+ if mode != "refusal" { -+ let initial = take_splash_host_requests_for(&[heap]).pop().unwrap(); -+ let result = if mode == "success" { -+ Ok(r#"{"unknown_payload_key": [-2, "value"]}"#) -+ } else { -+ Err("synthetic failure") -+ }; -+ splash_host_respond(&mut cx, heap, initial.req_id, result); -+ } ++ let initial = take_splash_host_requests_for(&[heap]).pop().unwrap(); ++ let result = if mode == "success" { ++ Ok(r#"{"unknown_payload_key": [-2, "value"]}"#) ++ } else { ++ Err("synthetic failure") ++ }; ++ splash_host_respond(&mut cx, heap, initial.req_id, result); + let forwarded = take_splash_host_requests_for(&[heap]); + assert_eq!( + forwarded.len(), diff --git a/tools/runtime-patches/makepad-ime-inactive-no-restart.patch b/tools/runtime-patches/makepad-ime-inactive-no-restart.patch index b3800c701..7b6e0cf32 100644 --- a/tools/runtime-patches/makepad-ime-inactive-no-restart.patch +++ b/tools/runtime-patches/makepad-ime-inactive-no-restart.patch @@ -1,8 +1,8 @@ diff --git a/tools/cargo_makepad/src/android/java/dev/makepad/android/MakepadActivity.java b/tools/cargo_makepad/src/android/java/dev/makepad/android/MakepadActivity.java -index 274f2bbad..adad3b647 100644 +index 870a02a13..b10842450 100644 --- a/tools/cargo_makepad/src/android/java/dev/makepad/android/MakepadActivity.java +++ b/tools/cargo_makepad/src/android/java/dev/makepad/android/MakepadActivity.java -@@ -876,7 +876,12 @@ class MakepadSurface +@@ -925,7 +925,12 @@ class MakepadSurface mEditorSession=session; if (mInputConnection!=null) { if (initial) mInputConnection.bindInitialSession(session); diff --git a/tools/runtime-patches/makepad-label-restyle-wrap.patch b/tools/runtime-patches/makepad-label-restyle-wrap.patch index 90c254c5d..b7ba42c99 100644 --- a/tools/runtime-patches/makepad-label-restyle-wrap.patch +++ b/tools/runtime-patches/makepad-label-restyle-wrap.patch @@ -1,3 +1,5 @@ +diff --git a/widgets/src/label.rs b/widgets/src/label.rs +index 382e65958..270970ed1 100644 --- a/widgets/src/label.rs +++ b/widgets/src/label.rs @@ -11,6 +11,9 @@ diff --git a/tools/runtime-patches/makepad-location-explicit-consent.patch b/tools/runtime-patches/makepad-location-explicit-consent.patch index b3134410b..24016d5ba 100644 --- a/tools/runtime-patches/makepad-location-explicit-consent.patch +++ b/tools/runtime-patches/makepad-location-explicit-consent.patch @@ -1,6 +1,8 @@ +diff --git a/tools/cargo_makepad/src/android/java/dev/makepad/android/MakepadActivity.java b/tools/cargo_makepad/src/android/java/dev/makepad/android/MakepadActivity.java +index 69314ccdf..d5a6b442e 100644 --- a/tools/cargo_makepad/src/android/java/dev/makepad/android/MakepadActivity.java +++ b/tools/cargo_makepad/src/android/java/dev/makepad/android/MakepadActivity.java -@@ -1366,19 +1366,12 @@ +@@ -1363,19 +1363,12 @@ public class MakepadActivity private long mQrLastFrameMs = 0; private static final int QR_CAMERA_PERM_REQ = 0x51A2; @@ -22,7 +24,7 @@ static { System.loadLibrary("makepad"); -@@ -2183,18 +2176,10 @@ +@@ -2181,18 +2174,10 @@ public class MakepadActivity if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED && checkSelfPermission(Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) { diff --git a/tools/runtime-patches/makepad-location-request.patch b/tools/runtime-patches/makepad-location-request.patch index c7426642f..63f90d02f 100644 --- a/tools/runtime-patches/makepad-location-request.patch +++ b/tools/runtime-patches/makepad-location-request.patch @@ -1,36 +1,8 @@ ---- a/widgets/src/splash.rs -+++ b/widgets/src/splash.rs -@@ -3754,6 +3754,27 @@ - }, - ); - -+ // Explicit Android request using the existing foreground GPS cache. -+ // Reads stay passive. Return requested (1), unsupported (0), or missing -+ // app capability (-1), never a promise of permission or a position fix. -+ vm.add_method( -+ sys, -+ id_lut!(request_location), -+ script_args_def!(), -+ |vm, _args| { -+ if crate::splash_policy::service_allowed(vm.bx.heap.heap_key(), "location.request").is_err() { -+ return ScriptValue::from_f64(-1.0); -+ } -+ #[cfg(target_os = "android")] -+ { -+ vm.host.cx_mut().request_gps_location(); -+ return ScriptValue::from_f64(1.0); -+ } -+ #[cfg(not(target_os = "android"))] -+ ScriptValue::from_f64(0.0) -+ }, -+ ); -+ - // sys.gps("lat"|"lon"|"acc"|"ok") -> the device's last-known GPS fix, read - // SYNCHRONOUSLY from the platform global (NO network fetch — so, unlike - // sys.search/navroute, it must NOT gate the card via body_binds_live_data). +diff --git a/platform/src/cx_api.rs b/platform/src/cx_api.rs +index d3e27fc32..8ad82ca09 100644 --- a/platform/src/cx_api.rs +++ b/platform/src/cx_api.rs -@@ -457,6 +457,8 @@ +@@ -457,6 +457,8 @@ pub enum CxOsOp { request_id: i32, }, @@ -39,7 +11,7 @@ StartLocationUpdates, StopLocationUpdates, -@@ -674,6 +676,8 @@ +@@ -674,6 +676,8 @@ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { Self::CheckPermission { .. } => write!(f, "CheckPermission"), Self::RequestPermission { .. } => write!(f, "RequestPermission"), @@ -48,7 +20,7 @@ Self::StartLocationUpdates => write!(f, "StartLocationUpdates"), Self::StopLocationUpdates => write!(f, "StopLocationUpdates"), -@@ -1849,6 +1853,13 @@ +@@ -1856,6 +1860,13 @@ pub fn check_permission(&mut self, permission: crate::permission::Permission) -> self.permissions_request_id } @@ -62,9 +34,11 @@ /// Start streaming position fixes from the platform location service /// (CoreLocation on macOS/iOS, LocationManager on Android, /// `navigator.geolocation` on web). Fixes arrive as +diff --git a/platform/src/os/linux/android/android.rs b/platform/src/os/linux/android/android.rs +index 94029dd20..acff6af41 100644 --- a/platform/src/os/linux/android/android.rs +++ b/platform/src/os/linux/android/android.rs -@@ -3020,6 +3020,9 @@ +@@ -3067,6 +3067,9 @@ fn handle_platform_ops(&mut self) -> EventFlow { } => { self.handle_permission_request(permission, request_id); } @@ -74,9 +48,11 @@ CxOsOp::StartLocationUpdates => { self.os.location_updates_wanted = true; match self +diff --git a/platform/src/os/linux/android/android_jni.rs b/platform/src/os/linux/android/android_jni.rs +index 2ee73cd8c..7890d0926 100644 --- a/platform/src/os/linux/android/android_jni.rs +++ b/platform/src/os/linux/android/android_jni.rs -@@ -2654,6 +2654,11 @@ +@@ -2691,6 +2691,11 @@ unsafe fn new_java_string_array( array } @@ -88,9 +64,11 @@ pub unsafe fn to_java_start_location_updates(min_interval_ms: i64, min_distance_m: f32) { let env = attach_jni_env(); ndk_utils::call_void_method!( +diff --git a/tools/cargo_makepad/src/android/java/dev/makepad/android/MakepadActivity.java b/tools/cargo_makepad/src/android/java/dev/makepad/android/MakepadActivity.java +index d5a6b442e..13db67689 100644 --- a/tools/cargo_makepad/src/android/java/dev/makepad/android/MakepadActivity.java +++ b/tools/cargo_makepad/src/android/java/dev/makepad/android/MakepadActivity.java -@@ -1371,6 +1371,7 @@ +@@ -1368,6 +1368,7 @@ public class MakepadActivity private LocationManager mGpsLocationManager; private LocationListener mGpsLocationListener; private static final int LOCATION_PERM_REQ = 0x10CA; @@ -98,7 +76,7 @@ static { -@@ -2021,6 +2022,7 @@ +@@ -2019,6 +2020,7 @@ public class MakepadActivity // Location permission granted → start GPS updates. if (requestId == LOCATION_PERM_REQ) { @@ -106,7 +84,7 @@ for (int i = 0; i < permissions.length; i++) { if (grantResults[i] == PackageManager.PERMISSION_GRANTED) { startGpsLocationUpdates(); -@@ -2172,12 +2174,37 @@ +@@ -2170,12 +2172,37 @@ public class MakepadActivity // octos has no simulated location, so this feeds the DEVICE's real fix to the // nav card (via sys.gps). Started in onResume once the runtime permission is // granted, stopped in onPause so GPS isn't polled while backgrounded. @@ -146,3 +124,35 @@ // legacy cached GPS feed may only use an existing grant. return; } +diff --git a/widgets/src/splash.rs b/widgets/src/splash.rs +index 1d457bd2c..56d06f846 100644 +--- a/widgets/src/splash.rs ++++ b/widgets/src/splash.rs +@@ -3756,6 +3756,27 @@ pub fn register_agent_module(vm: &mut ScriptVm) { + }, + ); + ++ // Explicit Android request using the existing foreground GPS cache. ++ // Reads stay passive. Return requested (1), unsupported (0), or missing ++ // app capability (-1), never a promise of permission or a position fix. ++ vm.add_method( ++ sys, ++ id_lut!(request_location), ++ script_args_def!(), ++ |vm, _args| { ++ if crate::splash_policy::service_allowed(vm.bx.heap.heap_key(), "location.request").is_err() { ++ return ScriptValue::from_f64(-1.0); ++ } ++ #[cfg(target_os = "android")] ++ { ++ vm.host.cx_mut().request_gps_location(); ++ return ScriptValue::from_f64(1.0); ++ } ++ #[cfg(not(target_os = "android"))] ++ ScriptValue::from_f64(0.0) ++ }, ++ ); ++ + // sys.gps("lat"|"lon"|"acc"|"ok") -> the device's last-known GPS fix, read + // SYNCHRONOUSLY from the platform global (NO network fetch — so, unlike + // sys.search/navroute, it must NOT gate the card via body_binds_live_data). diff --git a/tools/runtime-patches/makepad-map-script-api.patch b/tools/runtime-patches/makepad-map-script-api.patch deleted file mode 100644 index bfee186f0..000000000 --- a/tools/runtime-patches/makepad-map-script-api.patch +++ /dev/null @@ -1,831 +0,0 @@ -diff --git a/widgets/src/map/nav.rs b/widgets/src/map/nav.rs -index 3f1c917b0..31ef7b1fd 100644 ---- a/widgets/src/map/nav.rs -+++ b/widgets/src/map/nav.rs -@@ -6,7 +6,14 @@ - //! `route_markers` (`"lat,lon,kind;…"`, kind 0 origin / 1 stop / 2 - //! destination), `route_badge`, and the `nav_*` tuning numbers; - //! * script methods — `ui..set_nav_polyline(s)`, `set_route_markers(s)`, --//! `set_nav_recenter(_)`, `nav_zoom_by(delta)`, `nav_center_origin()`. -+//! `set_nav_recenter(_)`, `nav_zoom_by(delta)`, `nav_center_origin()`; -+//! * for a plain map only (no `nav_mode`; with one the nav camera owns the -+//! view and these do nothing) — `fly_to(lat, lon, zoom)` (zoom optional) -+//! and `fit_route()` (frame the route once, as `"plan"` does, then leave -+//! the camera alone); -+//! * on any map — `clear_route()` (the route and any pending fit; the pins -+//! stay) and the callbacks `on_tap`, `on_long_press`, `on_marker`, -+//! `on_viewport` (see `MapView`). - //! - //! It renders through the map's own overlay (route ribbon, puck, pins) and - //! camera (center / rotation / tilt / zoom) rather than the dedicated pinhole -@@ -184,6 +191,13 @@ pub fn adopt(&mut self, polyline: &str, markers: &str) -> bool { - changed - } - -+ /// Drop the route as adopting an empty polyline would, now rather than -+ /// at the next draw. -+ pub fn forget_route(&mut self) { -+ self.poly_seen.clear(); -+ self.set_route_coords(Vec::new()); -+ } -+ - /// `(lat, lon)` route -> resampled normalized points + cumulative metres. - fn set_route_coords(&mut self, coords0: Vec<(f64, f64)>) { - self.pts.clear(); -diff --git a/widgets/src/map/view.rs b/widgets/src/map/view.rs -index 566940e86..8e08d6af8 100644 ---- a/widgets/src/map/view.rs -+++ b/widgets/src/map/view.rs -@@ -1,5 +1,6 @@ - use super::archive::*; --use crate::widget_async::ScriptAsyncResult; -+use crate::makepad_script::ScriptFnRef; -+use crate::widget_async::{CxWidgetToScriptCallExt, ScriptAsyncResult}; - use super::nav::*; - use super::geometry::*; - use super::icons::icon_mesh_by_slot; -@@ -3072,6 +3073,43 @@ pub enum MapViewAction { - None, - } - -+/// What a script hears of a map action, latitude first as scripts read -+/// places. The callbacks grant nothing: a tap reports the spot touched, and -+/// the device's own position still comes only from `sys.gps`. -+#[derive(Clone, Copy, Debug, PartialEq)] -+enum MapScriptEvent { -+ Tap { lat: f64, lon: f64 }, -+ LongPress { lat: f64, lon: f64 }, -+ /// The pin's position among the pins drawn from `route_markers` (see -+ /// `on_marker` on `MapView`). -+ Marker { index: u64 }, -+ Viewport { lat: f64, lon: f64, zoom: f64 }, -+} -+ -+impl MapScriptEvent { -+ fn args(&self) -> Vec { -+ match *self { -+ MapScriptEvent::Tap { lat, lon } | MapScriptEvent::LongPress { lat, lon } => vec![lat, lon], -+ MapScriptEvent::Marker { index } => vec![index as f64], -+ MapScriptEvent::Viewport { lat, lon, zoom } => vec![lat, lon, zoom], -+ } -+ } -+} -+ -+fn map_script_event(action: &MapViewAction) -> Option { -+ match action { -+ MapViewAction::Tapped { lon, lat, .. } => Some(MapScriptEvent::Tap { lat: *lat, lon: *lon }), -+ MapViewAction::LongPressed { lon, lat, .. } => Some(MapScriptEvent::LongPress { lat: *lat, lon: *lon }), -+ // A route pin's id is its 1-based position among the pins drawn from -+ // `route_markers`; a host's `set_markers` ids pass through as id - 1. -+ MapViewAction::MarkerClicked { id } if *id > 0 => Some(MapScriptEvent::Marker { index: *id - 1 }), -+ MapViewAction::ViewportChanged { lon, lat, zoom } => { -+ Some(MapScriptEvent::Viewport { lat: *lat, lon: *lon, zoom: *zoom }) -+ } -+ _ => None, -+ } -+} -+ - /// Animated camera flight (zoom-out-then-in arc when the target is far). - #[derive(Clone, Copy)] - struct FlyTo { -@@ -4433,6 +4471,35 @@ pub struct MapView { - /// Accepted for card compatibility; not drawn by this layer yet. - #[live] - route_badge: ArcStringMut, -+ /// Script callbacks, all optional; a map without them behaves as before. -+ /// They work with or without a `nav_mode`. -+ /// -+ /// * `on_tap: |lat, lon|`: a tap on the map, not on a pin. -+ /// * `on_long_press: |lat, lon|`: a long press (a double click with a -+ /// mouse). -+ /// * `on_marker: |index|`: a tap on a pin, by the pin's position among -+ /// the pins drawn from `route_markers`; entries without a readable -+ /// lat,lon, out-of-range ones and 0,0 are skipped and not counted. Pins -+ /// a Rust host placed with `set_markers` report their id - 1, so don't -+ /// mix them with `on_marker`. -+ /// * `on_viewport: |lat, lon, zoom|`: the centre and zoom once the camera -+ /// settles after a gesture or a zoom, when any flight lands (the -+ /// script's own `fly_to` too), and on a host's `set_center` or -+ /// `set_map_zoom`. Nothing fires on the first draw (a script seeds its -+ /// own centre), nor while a nav camera follows the vehicle. A tap or -+ /// long press that stops a flight reports no viewport: the camera stays -+ /// where the flight left it, unheard, until something else moves it. -+ /// -+ /// A tap on a tile pin (a charger, `PinTapped` to a Rust host) reaches -+ /// neither `on_tap` nor `on_marker`. -+ #[live] -+ on_tap: ScriptFnRef, -+ #[live] -+ on_long_press: ScriptFnRef, -+ #[live] -+ on_marker: ScriptFnRef, -+ #[live] -+ on_viewport: ScriptFnRef, - /// Sim-clock period (s) a card's DSL overlays cycle on; kept in the DSL - /// surface so nav cards apply unchanged. - #[live(92.0)] -@@ -4464,6 +4531,10 @@ pub struct MapView { - nav_route_width: f64, - #[rust] - nav: NavState, -+ /// `fit_route()` was called: the next draw that has an area and something -+ /// to frame frames it once. -+ #[rust] -+ fit_route_pending: bool, - /// Active theme: 0 = light, 1 = dark, 2 = circuit city. `dark_theme` - /// stays as the boolean shorthand for 0/1. - #[live(0)] -@@ -5284,7 +5355,7 @@ fn handle_event(&mut self, cx: &mut Cx, event: &Event, scope: &mut Scope) { - self.drag_start_abs = None; - self.touch_gesture = TouchGesture::None; - let (lon, lat) = self.screen_to_lon_lat(lp.abs); -- cx.widget_action(self.uid, MapViewAction::LongPressed { lon, lat, abs: lp.abs }); -+ self.send_action(cx, MapViewAction::LongPressed { lon, lat, abs: lp.abs }); - } - Hit::FingerUp(fe) => { - self.note_shimmer_activity(cx); -@@ -5315,18 +5386,15 @@ fn handle_event(&mut self, cx: &mut Cx, event: &Event, scope: &mut Scope) { - // Double-click acts as the long-press (mouse holds - // don't synthesize FingerLongPress on desktop). - let (lon, lat) = self.screen_to_lon_lat(fe.abs); -- cx.widget_action( -- self.uid, -- MapViewAction::LongPressed { lon, lat, abs: fe.abs }, -- ); -+ self.send_action(cx, MapViewAction::LongPressed { lon, lat, abs: fe.abs }); - } else if let Some((lon, lat, info)) = self.pin_at(fe.abs) { - cx.widget_action(self.uid, MapViewAction::PinTapped { lon, lat, info }); - } else if let Some(id) = self.overlay.marker_at(&self.overlay_camera(), fe.abs) - { -- cx.widget_action(self.uid, MapViewAction::MarkerClicked { id }); -+ self.send_action(cx, MapViewAction::MarkerClicked { id }); - } else { - let (lon, lat) = self.screen_to_lon_lat(fe.abs); -- cx.widget_action(self.uid, MapViewAction::Tapped { lon, lat, abs: fe.abs }); -+ self.send_action(cx, MapViewAction::Tapped { lon, lat, abs: fe.abs }); - } - } else if self.gesture_panned { - self.gesture_panned = false; -@@ -5337,7 +5405,11 @@ fn handle_event(&mut self, cx: &mut Cx, event: &Event, scope: &mut Scope) { - Hit::FingerHoverIn(_) => { - cx.set_cursor(MouseCursor::Grab); - } -- Hit::FingerScroll(fs) => { -+ // A wheel a scroll view already used is not the map's: one drawn -+ // over the map (a list in a floating panel), or one the map sits -+ // in (a feed), while it can still scroll. A wheel's hits ignore -+ // claims, so this flag is all that tells. -+ Hit::FingerScroll(fs) if !scroll_taken(event) => { - let scroll = if fs.scroll.y.abs() > f64::EPSILON { - fs.scroll.y - } else { -@@ -5351,9 +5423,14 @@ fn handle_event(&mut self, cx: &mut Cx, event: &Event, scope: &mut Scope) { - } - } - -- /// The nav-card methods: `ui..set_nav_polyline(s)`, -- /// `set_route_markers(s)`, `set_nav_recenter(_)`, `nav_zoom_by(delta)`, -- /// `nav_center_origin()` — see map/nav.rs. -+ /// The script methods: `ui..set_nav_polyline(s)`, -+ /// `set_route_markers(s)` (nav cards and plain maps), and for nav cards -+ /// `set_nav_recenter(_)`, `nav_zoom_by(delta)`, `nav_center_origin()` — -+ /// see map/nav.rs. A plain map (no `nav_mode`): `fly_to(lat, lon, zoom)` -+ /// (zoom optional; 0,0 and out-of-range points are ignored, and so is a -+ /// call while the person is moving the map), `fit_route()`, -+ /// `clear_route()`. On a map with a `nav_mode`, `fly_to` and `fit_route` -+ /// do nothing: the nav camera owns the view. - fn script_call( - &mut self, - vm: &mut ScriptVm, -@@ -5369,6 +5446,13 @@ fn first_string_arg(vm: &mut ScriptVm, args: ScriptValue) -> Option { - } - vm.bx.heap.cast_to_owned_string(value, "nav method argument") - } -+ // `vec_value_if_exist`, not `vec_value`: a left-out optional argument -+ // must not raise an out-of-bounds error on the calling script. -+ fn number_arg(vm: &mut ScriptVm, args: ScriptValue, index: usize) -> Option { -+ let args_obj = args.as_object()?; -+ let value = vm.bx.heap.vec_value_if_exist(args_obj, index)?; -+ value.as_number().filter(|n| n.is_finite()) -+ } - if method == live_id!(set_nav_polyline) { - if let Some(s) = first_string_arg(vm, args) { - if !s.trim().is_empty() && s.as_str() != self.nav_polyline.as_ref() { -@@ -5427,6 +5511,35 @@ fn first_string_arg(vm: &mut ScriptVm, args: ScriptValue) -> Option { - } - return ScriptAsyncResult::Return(NIL); - } -+ if method == live_id!(fly_to) { -+ // The nav camera owns a nav-mode map's view (as in fit_route), and -+ // the person a map they are moving: a flight started mid-drag -+ // would land after the release and undo it. -+ if nav_kind(self.nav_mode.as_ref().trim()) != NavKind::Off || self.gesturing() { -+ return ScriptAsyncResult::Return(NIL); -+ } -+ // Latitude first, as scripts read places; zoom optional. Without -+ // one, mid-flight, keep the zoom the camera is heading to, not -+ // the zoom-out arc's it is passing through. -+ let lat = number_arg(vm, args, 0); -+ let lon = number_arg(vm, args, 1); -+ let zoom = number_arg(vm, args, 2) -+ .unwrap_or_else(|| self.fly.map_or_else(|| self.view_zoom(), |f| f.to_zoom)); -+ if let (Some(lat), Some(lon)) = (lat, lon) { -+ if is_a_place(lat, lon) { -+ vm.with_cx_mut(|cx| self.fly_to(cx, lon, lat, zoom)); -+ } -+ } -+ return ScriptAsyncResult::Return(NIL); -+ } -+ if method == live_id!(fit_route) { -+ vm.with_cx_mut(|cx| self.fit_route(cx)); -+ return ScriptAsyncResult::Return(NIL); -+ } -+ if method == live_id!(clear_route) { -+ vm.with_cx_mut(|cx| self.clear_route(cx)); -+ return ScriptAsyncResult::Return(NIL); -+ } - ScriptAsyncResult::MethodNotFound - } - -@@ -8579,6 +8692,15 @@ fn touch_world_rel(&self, abs: Vec2d) -> Vec2d { - } - } - -+ /// A finger or the mouse is down moving the camera. Each move recomputes -+ /// the camera from where the gesture started, so a flight now would be -+ /// undone by the next move. -+ fn gesturing(&self) -> bool { -+ self.drag_start_abs.is_some() -+ || self.rotate_drag.is_some() -+ || !matches!(self.touch_gesture, TouchGesture::None) -+ } -+ - fn begin_two_finger_gesture(&mut self, pair: TouchPair) { - self.fly = None; - self.drag_start_abs = None; -@@ -8655,10 +8777,10 @@ fn handle_touch_tap(&mut self, cx: &mut Cx, abs: Vec2d, tap_count: u32) { - } else if let Some((lon, lat, info)) = self.pin_at(abs) { - cx.widget_action(self.uid, MapViewAction::PinTapped { lon, lat, info }); - } else if let Some(id) = self.overlay.marker_at(&self.overlay_camera(), abs) { -- cx.widget_action(self.uid, MapViewAction::MarkerClicked { id }); -+ self.send_action(cx, MapViewAction::MarkerClicked { id }); - } else { - let (lon, lat) = self.screen_to_lon_lat(abs); -- cx.widget_action(self.uid, MapViewAction::Tapped { lon, lat, abs }); -+ self.send_action(cx, MapViewAction::Tapped { lon, lat, abs }); - } - } - -@@ -11184,14 +11306,32 @@ fn sync_camera_fields(&mut self) { - } - - fn emit_viewport_changed(&mut self, cx: &mut Cx) { -- cx.widget_action( -- self.uid, -- MapViewAction::ViewportChanged { -- lon: self.center_lon, -- lat: self.center_lat, -- zoom: self.view_zoom(), -- }, -- ); -+ let action = MapViewAction::ViewportChanged { -+ lon: self.center_lon, -+ lat: self.center_lat, -+ zoom: self.view_zoom(), -+ }; -+ self.send_action(cx, action); -+ } -+ -+ fn script_callback(&self, event: MapScriptEvent) -> Option<(ScriptFnRef, Vec)> { -+ let script_fn = match event { -+ MapScriptEvent::Tap { .. } => &self.on_tap, -+ MapScriptEvent::LongPress { .. } => &self.on_long_press, -+ MapScriptEvent::Marker { .. } => &self.on_marker, -+ MapScriptEvent::Viewport { .. } => &self.on_viewport, -+ }; -+ (script_fn.as_object() != ScriptObject::ZERO).then(|| (script_fn.clone(), event.args())) -+ } -+ -+ /// Every action a script can hear goes out here: the script's callback, -+ /// when it set one, is queued; then the widget action, as before. -+ fn send_action(&mut self, cx: &mut Cx, action: MapViewAction) { -+ if let Some((script_fn, args)) = map_script_event(&action).and_then(|e| self.script_callback(e)) { -+ let args: Vec = args.into_iter().map(ScriptValue::from_f64).collect(); -+ cx.widget_to_script_call(self.uid, NIL, self.source.clone(), script_fn, &args); -+ } -+ cx.widget_action(self.uid, action); - } - - /// Screen point → map coordinate: the exact inverse of the camera that -@@ -12003,8 +12143,10 @@ pub fn set_map_zoom(&mut self, cx: &mut Cx, zoom: f64) { - - /// Animated camera flight; far targets get a zoom-out-then-in arc so - /// tiles stay loadable mid-flight and the motion reads like every -- /// mapping app. -+ /// mapping app. A `fly_to` drops a `fit_route()` that hasn't fired yet. - pub fn fly_to(&mut self, cx: &mut Cx, lon: f64, lat: f64, zoom: f64) { -+ // nav_update clears the flag itself before its own fit calls this. -+ self.fit_route_pending = false; - let min_zoom = self.min_zoom.max(0.0); - let max_zoom = self.max_zoom.max(min_zoom); - let to_zoom = zoom.clamp(min_zoom, max_zoom); -@@ -12090,8 +12232,17 @@ pub fn set_route(&mut self, cx: &mut Cx, points: &[(f64, f64)]) { - self.redraw(cx); - } - -+ /// Remove the route line, whether a Rust host set it (`set_route`) or a -+ /// script (`set_nav_polyline`), and any `fit_route()` still pending. The -+ /// nav vehicle goes with the route. The pins stay: `set_route_markers("")` -+ /// clears those. A framing flight already under way finishes; `fly_to` -+ /// replaces it. - pub fn clear_route(&mut self, cx: &mut Cx) { - self.overlay.route = None; -+ self.nav_polyline.set(""); -+ self.nav.forget_route(); -+ self.nav.release_puck(&mut self.overlay.puck); -+ self.fit_route_pending = false; - self.redraw(cx); - } - -@@ -12112,6 +12263,25 @@ pub fn set_puck(&mut self, cx: &mut Cx, puck: Option) { - self.nav.owns_puck = false; - self.redraw(cx); - } -+ -+ /// Frame the route once (or the pins, when there is no route) as plan mode -+ /// would, then leave the camera to the person. Takes effect on the next -+ /// draw that has an area and something to frame, after the map has -+ /// adopted the route a script just set. The framing is plan mode's, so it -+ /// turns the map north-up and flat (rotation and tilt 0) before flying, -+ /// but it zooms out as far as the map goes, so a long route fits whole. -+ /// A fit that arrives while the person is moving the map is dropped: the -+ /// camera is already theirs. A later `fly_to` drops a fit that hasn't -+ /// fired. -+ /// A plain map only: with a `nav_mode` the nav camera owns the view, so -+ /// this does nothing (and leaves no fit to fire later). -+ pub fn fit_route(&mut self, cx: &mut Cx) { -+ if nav_kind(self.nav_mode.as_ref().trim()) != NavKind::Off { -+ return; -+ } -+ self.fit_route_pending = true; -+ self.redraw(cx); -+ } - } - - impl MapViewRef { -@@ -12416,6 +12586,12 @@ fn label_class_color(color_class: u8, default_color: Vec4f, dark_theme: bool) -> - } - } - -+/// Whether a scroll view the event reached first (one drawn over the map, -+/// or one the map sits in) already used this wheel. -+fn scroll_taken(event: &Event) -> bool { -+ matches!(event, Event::Scroll(e) if e.handled_x.get() || e.handled_y.get()) -+} -+ - #[cfg(test)] - // Native map tests use wall-clock values only to make temporary paths unique. - #[allow(clippy::disallowed_types, clippy::disallowed_methods)] -@@ -13601,6 +13777,339 @@ fn http_archive_waiting_for_dispatch_has_no_enqueue_watchdog() { - assert!(map.local_requested_tiles.contains_key(&key)); - assert!(map.archive_pending_tiles.contains_key(&key)); - } -+ -+ fn bounds_of(a: Vec2d, b: Vec2d) -> (Vec2d, Vec2d) { -+ (dvec2(a.x.min(b.x), a.y.min(b.y)), dvec2(a.x.max(b.x), a.y.max(b.y))) -+ } -+ -+ #[test] -+ fn plan_framing_fits_the_route_into_the_band_above_the_sheet() { -+ let size = dvec2(400.0, 800.0); -+ let bounds = bounds_of( -+ lon_lat_to_normalized(-121.97, 37.376), -+ lon_lat_to_normalized(-121.96, 37.370), -+ ); -+ let zoom = plan_fit_zoom(bounds, size, 3.0, 18.0, 10.0); -+ let world = tile_world_size_zoom(zoom); -+ let px = |d: f64| d * world; -+ assert!((px(bounds.1.x - bounds.0.x) - size.x * 0.80).abs() < 1e-6); -+ assert!(px(bounds.1.y - bounds.0.y) <= size.y * 0.50 + 1e-6); -+ // A north-south route is height-limited: 0.6h - 104 = 376 px at 800, not half. -+ let tall = bounds_of(lon_lat_to_normalized(-121.965, 37.39), lon_lat_to_normalized(-121.965, 37.37)); -+ let z = plan_fit_zoom(tall, size, 3.0, 18.0, 10.0); -+ assert!(((tall.1.y - tall.0.y) * tile_world_size_zoom(z) - 376.0).abs() < 1e-6); -+ // A single point zooms no closer than 15.5; a continent no further than 10. -+ let spot = lon_lat_to_normalized(-121.96, 37.37); -+ assert_eq!(plan_fit_zoom((spot, spot), size, 3.0, 18.0, 10.0), 15.5); -+ let wide = bounds_of(lon_lat_to_normalized(-125.0, 49.0), lon_lat_to_normalized(-70.0, 25.0)); -+ assert_eq!(plan_fit_zoom(wide, size, 3.0, 18.0, 10.0), 10.0); -+ // The route's centre sits at 30% of the height: the camera is below it. -+ let c = (bounds.0 + bounds.1) * 0.5; -+ let camera = plan_center(c, size.y, world); -+ assert!(((camera.y - c.y) * world - 0.20 * size.y).abs() < 1e-6); -+ assert_eq!(camera.x, c.x); -+ } -+ -+ fn l_route() -> String { -+ super::super::encode_polyline5_for_test(&[ -+ (37.3700, -121.9700), -+ (37.3700, -121.9600), -+ (37.3760, -121.9600), -+ ]) -+ } -+ -+ #[test] -+ fn fit_route_frames_the_route_once_like_plan_mode() { -+ let mut cx = Cx::new(Box::new(|_, _| {})); -+ let mut map = test_map(&mut cx); -+ let rect = Rect { pos: dvec2(0.0, 0.0), size: dvec2(400.0, 800.0) }; -+ // The zoom fit_route flies to: plan mode's fit down to the map's own -+ // limit, clamped as fly_to clamps. -+ let fitted = |map: &MapView, bounds: (Vec2d, Vec2d)| { -+ let min = map.min_zoom.max(0.0); -+ plan_fit_zoom(bounds, rect.size, map.min_zoom, map.max_zoom, map.min_zoom.max(3.0)) -+ .clamp(min, map.max_zoom.max(min)) -+ }; -+ map.rotation = 30.0; -+ map.tilt = 20.0; -+ map.nav_polyline.as_mut_empty().push_str(&l_route()); -+ // A nav-mode map's camera owns the view: no fit is left to fire later. -+ map.nav_mode.set("plan"); -+ map.fit_route(&mut cx); -+ assert!(!map.fit_route_pending, "fit_route on a nav-mode map"); -+ map.nav_mode.set(""); -+ map.fit_route(&mut cx); -+ // A draw with no area leaves the fit for a real one. -+ map.nav_update(&mut cx, Rect::default()); -+ assert!(map.fly.is_none() && map.fit_route_pending, "fit_route framed an empty view"); -+ map.nav_update(&mut cx, rect); -+ let fly = map.fly.expect("fit_route starts a flight"); -+ let bounds = map.nav.bounds().unwrap(); -+ let zoom = fitted(&map, bounds); -+ assert_eq!(fly.to_zoom, zoom); -+ let expected = plan_center((bounds.0 + bounds.1) * 0.5, rect.size.y, tile_world_size_zoom(zoom)); -+ assert!((fly.to_center - expected).length() < 1e-9); -+ // The framing is north-up and flat, as plan mode's. -+ assert_eq!((map.rotation, map.tilt), (0.0, 0.0)); -+ // Once: the next frame leaves the camera alone. -+ map.fly = None; -+ map.nav_update(&mut cx, rect); -+ assert!(map.fly.is_none(), "fit_route framed the route again"); -+ // A route set just before the call is adopted first: the fit frames -+ // the new route (a walk to the same corner), not the last one. -+ let walk = [(37.3730, -121.9650), (37.3760, -121.9600)]; -+ map.nav_polyline.set(&super::super::encode_polyline5_for_test(&walk)); -+ map.fit_route(&mut cx); -+ map.nav_update(&mut cx, rect); -+ let fly = map.fly.expect("a re-fit starts a flight"); -+ let walk_bounds = bounds_of( -+ lon_lat_to_normalized(walk[0].1, walk[0].0), -+ lon_lat_to_normalized(walk[1].1, walk[1].0), -+ ); -+ let zoom = fitted(&map, walk_bounds); -+ assert!((fly.to_zoom - zoom).abs() < 1e-9, "{} != {zoom}", fly.to_zoom); -+ let expected = plan_center((walk_bounds.0 + walk_bounds.1) * 0.5, rect.size.y, tile_world_size_zoom(zoom)); -+ assert!((fly.to_center - expected).length() < 1e-9); -+ // A fit that arrives mid-gesture is dropped: the camera is the person's. -+ map.fly = None; -+ map.drag_start_abs = Some(dvec2(200.0, 400.0)); -+ map.fit_route(&mut cx); -+ map.nav_update(&mut cx, rect); -+ assert!(map.fly.is_none() && !map.fit_route_pending, "fit_route flew during a drag"); -+ } -+ -+ #[test] -+ fn fit_route_frames_a_long_route_whole() { -+ let mut cx = Cx::new(Box::new(|_, _| {})); -+ let mut map = test_map(&mut cx); -+ // test_map stops at zoom 11; this route needs about 8.4 on a phone. -+ map.min_zoom = 3.0; -+ let rect = Rect { pos: dvec2(0.0, 0.0), size: dvec2(400.0, 800.0) }; -+ // San Jose to Sacramento by way of Stockton, ~150 km: past plan -+ // mode's zoom-10 floor. -+ map.nav_polyline.set(&super::super::encode_polyline5_for_test(&[ -+ (37.3382, -121.8863), -+ (37.9577, -121.2908), -+ (38.5816, -121.4944), -+ ])); -+ map.fit_route(&mut cx); -+ map.nav_update(&mut cx, rect); -+ let fly = map.fly.expect("fit_route starts a flight"); -+ assert!(fly.to_zoom < 10.0, "zoom {}", fly.to_zoom); -+ // The whole route fits the band: 80% of the width, and 376 px -+ // (0.6h - 104) of an 800 px height. -+ let (min, max) = map.nav.bounds().unwrap(); -+ let world = tile_world_size_zoom(fly.to_zoom); -+ assert!((max.x - min.x) * world <= rect.size.x * 0.80 + 1e-6); -+ assert!((max.y - min.y) * world <= 376.0 + 1e-6); -+ } -+ -+ #[test] -+ fn clear_route_removes_the_line_and_keeps_the_pins() { -+ let mut cx = Cx::new(Box::new(|_, _| {})); -+ let mut map = test_map(&mut cx); -+ let rect = Rect { pos: dvec2(0.0, 0.0), size: dvec2(400.0, 800.0) }; -+ map.nav_polyline.as_mut_empty().push_str(&l_route()); -+ map.route_markers.as_mut_empty().push_str("37.37,-121.97,0;37.376,-121.96,2"); -+ map.nav_update(&mut cx, rect); -+ assert!(map.overlay.route.is_some()); -+ map.fit_route(&mut cx); -+ map.clear_route(&mut cx); -+ map.nav_update(&mut cx, rect); -+ assert!(map.overlay.route.is_none(), "the route is still drawn"); -+ assert_eq!(map.overlay.markers.len(), 2, "the pins went with it"); -+ assert!(map.fly.is_none(), "a cleared route was still framed"); -+ // New pins don't bring the cleared route back. -+ map.route_markers.set("37.37,-121.97,0"); -+ map.nav_update(&mut cx, rect); -+ assert!(map.overlay.route.is_none(), "new pins brought the route back"); -+ assert_eq!(map.overlay.markers.len(), 1); -+ // A new route draws again. -+ map.nav_polyline.as_mut_empty().push_str(&l_route()); -+ map.nav_update(&mut cx, rect); -+ assert!(map.overlay.route.is_some()); -+ } -+ -+ #[test] -+ fn clear_route_then_the_same_route_draws_it_again() { -+ let mut cx = Cx::new(Box::new(|_, _| {})); -+ let mut map = test_map(&mut cx); -+ let rect = Rect { pos: dvec2(0.0, 0.0), size: dvec2(400.0, 800.0) }; -+ map.nav_polyline.set(&l_route()); -+ map.nav_update(&mut cx, rect); -+ map.clear_route(&mut cx); -+ // Set again before the map draws. -+ map.nav_polyline.set(&l_route()); -+ map.nav_update(&mut cx, rect); -+ assert!(map.overlay.route.is_some(), "the same route set again is not drawn"); -+ } -+ -+ #[test] -+ fn clear_route_still_clears_a_host_route() { -+ let mut cx = Cx::new(Box::new(|_, _| {})); -+ let mut map = test_map(&mut cx); -+ let rect = Rect { pos: dvec2(0.0, 0.0), size: dvec2(400.0, 800.0) }; -+ map.set_route(&mut cx, &[(-121.97, 37.37), (-121.96, 37.37)]); -+ map.set_markers(&mut cx, vec![ -+ MapMarker::new(1, -121.97, 37.37, marker_color(0)), -+ MapMarker::new(2, -121.96, 37.37, marker_color(2)), -+ ]); -+ map.nav_update(&mut cx, rect); -+ // No script route: the navigation layer leaves the host's alone. -+ assert!(map.overlay.route.is_some(), "the host route was dropped"); -+ map.clear_route(&mut cx); -+ map.nav_update(&mut cx, rect); -+ assert!(map.overlay.route.is_none(), "the host route is still drawn"); -+ assert_eq!(map.overlay.markers.len(), 2, "the host's pins went with it"); -+ } -+ -+ #[test] -+ fn a_script_flies_the_map_lat_first() { -+ let mut cx = Cx::new(Box::new(|_, _| {})); -+ let mut map = test_map(&mut cx); -+ let call = |cx: &mut Cx, map: &mut MapView, values: &[ScriptValue]| { -+ cx.with_vm(|vm| { -+ let args = vm.bx.heap.new_object(); -+ vm.bx.heap.set_object_storage_vec2(args); -+ vm.bx.heap.clear_object_deep(args); -+ // Only this call's errors: setup can leave its own on the -+ // trap. Cleared before the pushes, so a failed push shows. -+ vm.bx.threads.cur().trap.err_clear(); -+ let trap = vm.bx.threads.cur().trap.pass(); -+ for v in values { -+ vm.bx.heap.vec_push(args, NIL, *v, trap); -+ } -+ map.script_call(vm, live_id!(fly_to), args.into()); -+ assert!(vm.bx.threads.cur().trap.err_is_empty(), "the call raised a script error"); -+ }); -+ }; -+ let n = ScriptValue::from_f64; -+ call(&mut cx, &mut map, &[n(0.0), n(0.0), n(15.0)]); -+ assert!(map.fly.is_none(), "0,0 is no place"); -+ call(&mut cx, &mut map, &[n(-121.9486002), n(37.3209796), n(15.0)]); -+ assert!(map.fly.is_none(), "longitude first flew the map"); -+ call(&mut cx, &mut map, &[n(37.3209796), n(-121.9486002), n(15.0)]); -+ let fly = map.fly.expect("a flight"); -+ assert_eq!(fly.to_zoom, 15.0); -+ let (lon, lat) = normalized_to_lon_lat(fly.to_center); -+ assert!((lat - 37.3209796).abs() < 1e-6 && (lon + 121.9486002).abs() < 1e-6, "{lat},{lon}"); -+ // No zoom mid-flight: the zoom the camera is heading to, not the -+ // dip's it is passing through. -+ map.zoom = 12.5; -+ call(&mut cx, &mut map, &[n(37.33), n(-121.88)]); -+ assert_eq!(map.fly.expect("a flight without a zoom").to_zoom, 15.0); -+ // No zoom, no flight: the current zoom. -+ map.fly = None; -+ call(&mut cx, &mut map, &[n(37.33), n(-121.88)]); -+ let fly = map.fly.expect("a flight without a zoom"); -+ assert_eq!(fly.to_zoom, map.view_zoom()); -+ assert_ne!(fly.to_zoom, 15.0); -+ // Past the map's limit: clamped. -+ call(&mut cx, &mut map, &[n(37.33), n(-121.88), n(30.0)]); -+ assert_eq!(map.fly.expect("a flight to zoom 30").to_zoom, map.max_zoom); -+ // A latitude that is not a number: no flight. -+ map.fly = None; -+ call(&mut cx, &mut map, &[NIL, n(-121.88), n(15.0)]); -+ assert!(map.fly.is_none(), "a non-number latitude flew the map"); -+ // A nav-mode map's camera owns the view. -+ map.nav_mode.set("follow"); -+ call(&mut cx, &mut map, &[n(37.33), n(-121.88), n(15.0)]); -+ assert!(map.fly.is_none(), "fly_to on a nav-mode map"); -+ map.nav_mode.set(""); -+ // While the person moves the map, the camera is theirs: a flight -+ // would carry on after the release and undo the drag. -+ map.drag_start_abs = Some(dvec2(200.0, 400.0)); -+ call(&mut cx, &mut map, &[n(37.33), n(-121.88), n(15.0)]); -+ assert!(map.fly.is_none(), "fly_to during a drag"); -+ map.drag_start_abs = None; -+ // A later fly_to drops a fit still pending: the fit doesn't override -+ // it. -+ let rect = Rect { pos: dvec2(0.0, 0.0), size: dvec2(400.0, 800.0) }; -+ map.nav_polyline.set(&l_route()); -+ map.fit_route(&mut cx); -+ call(&mut cx, &mut map, &[n(37.33), n(-121.88), n(15.0)]); -+ map.nav_update(&mut cx, rect); -+ assert_eq!(map.fly.expect("a flight").to_zoom, 15.0, "a pending fit overrode a later fly_to"); -+ } -+ -+ #[test] -+ fn a_wheel_a_list_over_the_map_took_does_not_zoom_it() { -+ use crate::makepad_platform::event::{ScrollEvent, ScrollPhase}; -+ use std::cell::Cell; -+ let wheel = |handled_y: bool| { -+ Event::Scroll(ScrollEvent { -+ window_id: WindowId(1, 1), -+ scroll: dvec2(0.0, 40.0), -+ abs: dvec2(100.0, 100.0), -+ modifiers: KeyModifiers::default(), -+ handled_x: Cell::new(false), -+ handled_y: Cell::new(handled_y), -+ is_mouse: true, -+ time: 0.0, -+ phase: ScrollPhase::None, -+ }) -+ }; -+ assert!(!scroll_taken(&wheel(false)), "a free wheel is the map's"); -+ assert!(scroll_taken(&wheel(true)), "a wheel a list scrolled is not"); -+ } -+ -+ #[test] -+ fn map_actions_reach_a_script_lat_first() { -+ let tap = MapViewAction::Tapped { lon: -121.9, lat: 37.3, abs: dvec2(5.0, 6.0) }; -+ assert_eq!(map_script_event(&tap).map(|e| e.args()), Some(vec![37.3, -121.9])); -+ let press = MapViewAction::LongPressed { lon: -121.9, lat: 37.3, abs: dvec2(5.0, 6.0) }; -+ assert_eq!(map_script_event(&press), Some(MapScriptEvent::LongPress { lat: 37.3, lon: -121.9 })); -+ // A route pin's id is its 1-based position among the pins drawn; -+ // scripts get the 0-based index. -+ let pin = MapViewAction::MarkerClicked { id: 3 }; -+ assert_eq!(map_script_event(&pin).map(|e| e.args()), Some(vec![2.0])); -+ assert_eq!(map_script_event(&MapViewAction::MarkerClicked { id: 0 }), None); -+ let moved = MapViewAction::ViewportChanged { lon: -121.9, lat: 37.3, zoom: 14.5 }; -+ assert_eq!(map_script_event(&moved).map(|e| e.args()), Some(vec![37.3, -121.9, 14.5])); -+ assert_eq!(map_script_event(&MapViewAction::TiltChanged { tilt: 30.0 }), None); -+ let charger = MapViewAction::PinTapped { lon: -121.9, lat: 37.3, info: vec![] }; -+ assert_eq!(map_script_event(&charger), None); -+ } -+ -+ #[test] -+ fn a_map_without_callbacks_queues_nothing() { -+ let mut cx = Cx::new(Box::new(|_, _| {})); -+ let mut map = test_map(&mut cx); -+ for event in [ -+ MapScriptEvent::Tap { lat: 37.3, lon: -121.9 }, -+ MapScriptEvent::LongPress { lat: 37.3, lon: -121.9 }, -+ MapScriptEvent::Marker { index: 0 }, -+ MapScriptEvent::Viewport { lat: 37.3, lon: -121.9, zoom: 14.0 }, -+ ] { -+ assert!(map.script_callback(event).is_none(), "{event:?}"); -+ } -+ // Rust hosts still hear the map. -+ let tap = MapViewAction::Tapped { lon: -121.9, lat: 37.3, abs: dvec2(5.0, 6.0) }; -+ let actions = cx.capture_actions(|cx| map.send_action(cx, tap.clone())); -+ assert_eq!(actions.find_widget_action_cast::(map.uid), tap); -+ } -+ -+ #[test] -+ fn a_map_with_a_callback_calls_it_and_hosts_still_hear() { -+ let mut cx = Cx::new(Box::new(|_, _| {})); -+ cx.init_cx_os(); -+ let mut map = cx.with_vm(|vm| { -+ crate::script_mod(vm); -+ let v = vm.eval(crate::makepad_script::script! { -+ use mod.prelude.widgets.* -+ MapView{on_tap: |lat, lon| lat} -+ }); -+ MapView::script_from_value(vm, v) -+ }); -+ let tap = MapScriptEvent::Tap { lat: 37.3, lon: -121.9 }; -+ assert_eq!(map.script_callback(tap).map(|(_, args)| args), Some(vec![37.3, -121.9])); -+ // Only the callback it set. -+ assert!(map.script_callback(MapScriptEvent::LongPress { lat: 37.3, lon: -121.9 }).is_none()); -+ // Queuing the script call leaves the widget action to Rust hosts. -+ let tap = MapViewAction::Tapped { lon: -121.9, lat: 37.3, abs: dvec2(5.0, 6.0) }; -+ let actions = cx.capture_actions(|cx| map.send_action(cx, tap.clone())); -+ assert_eq!(actions.find_widget_action_cast::(map.uid), tap); -+ } - } - - -@@ -13627,13 +14136,37 @@ fn nav_update(&mut self, cx: &mut Cx, rect: Rect) { - .map(|(i, &(lat, lon, k))| MapMarker::new(i as u64 + 1, lon, lat, marker_color(k))) - .collect(); - } -+ // A draw with no area (not laid out yet) would fit to nothing: wait. -+ if self.fit_route_pending && kind == NavKind::Off && rect.size.x > 0.0 && rect.size.y > 0.0 { -+ if self.gesturing() { -+ // The person is moving the map: the camera is already theirs. -+ self.fit_route_pending = false; -+ } else if let Some(bounds) = self.nav.bounds() { -+ self.fit_route_pending = false; -+ // Out as far as the map goes, not plan mode's 10, so a long -+ // route is framed whole. Clamped as fly_to clamps, so the -+ // centre is computed for the zoom actually flown to (plan -+ // mode's uses view_zoom()). -+ let min_zoom = self.min_zoom.max(0.0); -+ let zoom = plan_fit_zoom(bounds, rect.size, self.min_zoom, self.max_zoom, self.min_zoom.max(3.0)) -+ .clamp(min_zoom, self.max_zoom.max(min_zoom)); -+ let world = tile_world_size_zoom(zoom); -+ let (lon, lat) = normalized_to_lon_lat(plan_center((bounds.0 + bounds.1) * 0.5, rect.size.y, world)); -+ // plan_center frames a north-up, untilted view. -+ self.set_rotation(cx, 0.0); -+ self.set_tilt(cx, 0.0); -+ self.fly_to(cx, lon, lat, zoom); -+ } -+ } - if kind == NavKind::Off { - self.nav.release_puck(&mut self.overlay.puck); - self.nav.next_frame = NextFrame::default(); - return; - } -- // The nav camera owns center/rotation/tilt: no flight may fight it. -+ // The nav camera owns center/rotation/tilt: no flight may fight it, -+ // and no fit may fire once the map is plain again. - self.fly = None; -+ self.fit_route_pending = false; - if self.nav.home_zoom <= 0.0 { - self.nav.home_zoom = self.zoom; - } -@@ -13713,32 +14246,47 @@ fn nav_update(&mut self, cx: &mut Cx, rect: Rect) { - /// PLAN route-preview camera: static, north-up, fit to the WHOLE route, - /// framed into the top band above the card's summary sheet. - fn nav_plan_camera(&mut self, rect: Rect) { -- let Some((min, max)) = self.nav.bounds() else { -+ let Some(bounds) = self.nav.bounds() else { - return; // nothing to frame — keep the current centre (no NaN) - }; -- let c = (min + max) * 0.5; -+ let c = (bounds.0 + bounds.1) * 0.5; - if !self.nav.user_adjusted { -- let dx = (max.x - min.x).max(1e-9); -- let dy = (max.y - min.y).max(1e-9); -- let fitw = rect.size.x * 0.80; -- let fith = (rect.size.y * 0.50) -- .min(2.0 * (rect.size.y * 0.30 - 52.0)) -- .max(1.0); -- let zx = (fitw / (dx * TILE_SIZE)).log2(); -- let zy = (fith / (dy * TILE_SIZE)).log2(); -- let zmin = self.min_zoom.max(3.0); -- let zmax = self.max_zoom.max(zmin); -- self.zoom = zx.min(zy).clamp(zmin, zmax).clamp(10.0, 15.5); -+ self.zoom = plan_fit_zoom(bounds, rect.size, self.min_zoom, self.max_zoom, 10.0); - self.nav.home_zoom = self.zoom; - } - self.nav.car = c; -- // Route centre at 30% of the height: the camera centre sits below it. - let world = tile_world_size_zoom(self.view_zoom()); -- let down = dvec2(0.0, (0.5 - 0.30) * rect.size.y / world); -- self.center_norm = c + down + self.nav.pan; -+ self.center_norm = plan_center(c, rect.size.y, world) + self.nav.pan; - self.wrap_and_clamp_center(); - self.rotation = 0.0; - self.tilt = 0.0; - self.nav.release_puck(&mut self.overlay.puck); - } - } -+ -+/// The plan camera's zoom: `bounds` (normalized) fitted into the band a plan -+/// card leaves above its summary sheet: 80% of the width, and vertically a -+/// band centred 30% down (where `plan_center` puts it) that stays 52 px clear -+/// of the top edge, at most half the height. Never closer than zoom 15.5, nor -+/// farther out than `farthest`: plan mode passes 10, `fit_route()` the map's -+/// own limit, so a long route is framed whole. -+/// Plan mode reads it every frame, `fit_route()` once. -+fn plan_fit_zoom(bounds: (Vec2d, Vec2d), size: Vec2d, min_zoom: f64, max_zoom: f64, farthest: f64) -> f64 { -+ let (min, max) = bounds; -+ let dx = (max.x - min.x).max(1e-9); -+ let dy = (max.y - min.y).max(1e-9); -+ let fitw = size.x * 0.80; -+ let fith = (size.y * 0.50).min(2.0 * (size.y * 0.30 - 52.0)).max(1.0); -+ let zx = (fitw / (dx * TILE_SIZE)).log2(); -+ let zy = (fith / (dy * TILE_SIZE)).log2(); -+ let zmin = min_zoom.max(3.0); -+ let zmax = max_zoom.max(zmin); -+ // `.min(15.5)`: clamp() panics on crossed bounds (a min_zoom past 15.5); -+ // the caller's own zoom clamp lifts the result to that min_zoom. -+ zx.min(zy).clamp(zmin, zmax).clamp(farthest.min(15.5), 15.5) -+} -+ -+/// The camera centre that puts `route_center` at 30% of the view's height. -+fn plan_center(route_center: Vec2d, height: f64, world_size: f64) -> Vec2d { -+ route_center + dvec2(0.0, (0.5 - 0.30) * height / world_size) -+} diff --git a/tools/runtime-patches/makepad-native-audio-session-ownership.patch b/tools/runtime-patches/makepad-native-audio-session-ownership.patch index e9a00a93a..e5d97ab44 100644 --- a/tools/runtime-patches/makepad-native-audio-session-ownership.patch +++ b/tools/runtime-patches/makepad-native-audio-session-ownership.patch @@ -131,7 +131,7 @@ index 7ee164283..a557a7c0d 100644 } diff --git a/widgets/src/splash_host.rs b/widgets/src/splash_host.rs -index 3709be935..47a105f48 100644 +index a5ddb50df..75362aa35 100644 --- a/widgets/src/splash_host.rs +++ b/widgets/src/splash_host.rs @@ -146,6 +146,17 @@ pub(crate) fn prompts_allowed_for_heap(heap_key: usize) -> bool { diff --git a/tools/runtime-patches/makepad-photo-text-sharing.patch b/tools/runtime-patches/makepad-photo-text-sharing.patch index 087f7d806..f56de245b 100644 --- a/tools/runtime-patches/makepad-photo-text-sharing.patch +++ b/tools/runtime-patches/makepad-photo-text-sharing.patch @@ -1,5 +1,5 @@ diff --git a/platform/src/cx_api.rs b/platform/src/cx_api.rs -index e5087a9bd..d45e3f977 100644 +index ac244fadd..3399f381f 100644 --- a/platform/src/cx_api.rs +++ b/platform/src/cx_api.rs @@ -572,6 +572,7 @@ pub enum CxOsOp { @@ -46,10 +46,10 @@ index e5087a9bd..d45e3f977 100644 /// handle `CxOsOp::ShowNotification`. pub fn show_notification(&mut self, title: &str, body: &str) { diff --git a/platform/src/os/linux/android/android.rs b/platform/src/os/linux/android/android.rs -index 26fbdba61..e6249a89f 100644 +index 867305424..07e6b35bd 100644 --- a/platform/src/os/linux/android/android.rs +++ b/platform/src/os/linux/android/android.rs -@@ -2853,6 +2853,9 @@ fn handle_platform_ops(&mut self) -> EventFlow { +@@ -2900,6 +2900,9 @@ fn handle_platform_ops(&mut self) -> EventFlow { CxOsOp::ShareText(content) => unsafe { android_jni::to_java_share_text(content); }, @@ -60,10 +60,10 @@ index 26fbdba61..e6249a89f 100644 android_jni::to_java_show_notification(title, body); }, diff --git a/platform/src/os/linux/android/android_jni.rs b/platform/src/os/linux/android/android_jni.rs -index e77a763d8..9f320dea2 100644 +index a68c5b32d..1cfc4d0a1 100644 --- a/platform/src/os/linux/android/android_jni.rs +++ b/platform/src/os/linux/android/android_jni.rs -@@ -3190,16 +3190,21 @@ pub unsafe fn to_java_open_url(url: &str) { +@@ -3227,16 +3227,21 @@ pub unsafe fn to_java_open_url(url: &str) { pub unsafe fn to_java_share_text(content: String) { let env = attach_jni_env(); @@ -96,10 +96,10 @@ index e77a763d8..9f320dea2 100644 pub unsafe fn to_java_show_notification(title: String, body: String) { diff --git a/tools/cargo_makepad/src/android/java/dev/makepad/android/MakepadActivity.java b/tools/cargo_makepad/src/android/java/dev/makepad/android/MakepadActivity.java -index ebb6cb2ab..198ed71dd 100644 +index 94c99d8e0..95248e320 100644 --- a/tools/cargo_makepad/src/android/java/dev/makepad/android/MakepadActivity.java +++ b/tools/cargo_makepad/src/android/java/dev/makepad/android/MakepadActivity.java -@@ -3074,15 +3074,52 @@ public class MakepadActivity +@@ -3072,15 +3072,52 @@ public class MakepadActivity // Fire the system share sheet (ACTION_SEND) for social sharing. Called // from Rust via `android_jni::to_java_share_text`. diff --git a/tools/runtime-patches/makepad-settings.patch b/tools/runtime-patches/makepad-settings.patch index ae783ed9c..78930fca7 100644 --- a/tools/runtime-patches/makepad-settings.patch +++ b/tools/runtime-patches/makepad-settings.patch @@ -261,7 +261,7 @@ index 000000000..40e1aac9f + } +} diff --git a/platform/src/cx.rs b/platform/src/cx.rs -index f011f4bcc..de09102a7 100644 +index 013c9bc11..52beef62b 100644 --- a/platform/src/cx.rs +++ b/platform/src/cx.rs @@ -67,6 +67,7 @@ pub struct Cx { @@ -272,7 +272,7 @@ index f011f4bcc..de09102a7 100644 pub debug_trace_active: bool, -@@ -763,6 +764,12 @@ pub fn set_font_set(&mut self, font_set: crate::font_policy::FontSet) -> bool { +@@ -761,6 +762,12 @@ pub fn set_font_set(&mut self, font_set: crate::font_policy::FontSet) -> bool { true } @@ -285,7 +285,7 @@ index f011f4bcc..de09102a7 100644 pub fn font_set(&self) -> crate::font_policy::FontSet { self.font_set } -@@ -851,6 +858,7 @@ pub fn new(event_handler: Box) -> Self { +@@ -849,6 +856,7 @@ pub fn new(event_handler: Box) -> Self { package_root: None, font_set: crate::font_policy::FontSet::target_default(), font_set_frozen: false, @@ -335,7 +335,7 @@ index 815732636..2b1f2ffb6 100644 #[macro_use] mod app_main; diff --git a/platform/src/os/linux/android/android.rs b/platform/src/os/linux/android/android.rs -index c0bf8571a..de787641c 100644 +index 35e9e10c1..33fe1a569 100644 --- a/platform/src/os/linux/android/android.rs +++ b/platform/src/os/linux/android/android.rs @@ -240,6 +240,21 @@ pub(crate) fn boost_render_thread() { @@ -402,7 +402,7 @@ index c0bf8571a..de787641c 100644 } self.handle_other_events(); if self.os.in_xr_mode && self.os.openxr.session.is_none() { -@@ -586,8 +612,54 @@ fn copy_or_cut_to_clipboard(&mut self, cut: bool) { +@@ -595,8 +621,54 @@ fn copy_or_cut_to_clipboard(&mut self, cut: bool) { } } @@ -457,7 +457,7 @@ index c0bf8571a..de787641c 100644 FromJavaMessage::SwitchedActivity(activity_handle, activity_thread_id) => { self.os.activity_thread_id = Some(activity_thread_id); if self.os.in_xr_mode { -@@ -857,12 +929,17 @@ pub(crate) fn handle_message(&mut self, msg: FromJavaMessage) { +@@ -866,12 +938,17 @@ pub(crate) fn handle_message(&mut self, msg: FromJavaMessage) { }); self.call_event_handler(&e); } @@ -476,7 +476,7 @@ index c0bf8571a..de787641c 100644 } // Check for outside-click popup dismiss on touch start -@@ -893,6 +970,11 @@ pub(crate) fn handle_message(&mut self, msg: FromJavaMessage) { +@@ -902,6 +979,11 @@ pub(crate) fn handle_message(&mut self, msg: FromJavaMessage) { }; // Synthesize internal drag-and-drop events from touch gestures. @@ -488,7 +488,7 @@ index c0bf8571a..de787641c 100644 if self.os.internal_drag_items.is_some() { if let Some(touch) = e .touches -@@ -1376,9 +1458,13 @@ pub(crate) fn handle_message(&mut self, msg: FromJavaMessage) { +@@ -1385,9 +1467,13 @@ pub(crate) fn handle_message(&mut self, msg: FromJavaMessage) { } } FromJavaMessage::Pause => { @@ -502,7 +502,7 @@ index c0bf8571a..de787641c 100644 if self.os.fullscreen { unsafe { let env = attach_jni_env(); -@@ -1463,40 +1549,6 @@ pub(crate) fn handle_message(&mut self, msg: FromJavaMessage) { +@@ -1472,40 +1558,6 @@ pub(crate) fn handle_message(&mut self, msg: FromJavaMessage) { }); self.call_event_handler(&e); } @@ -543,7 +543,7 @@ index c0bf8571a..de787641c 100644 FromJavaMessage::ComposerSubmit { text } => { // The native floating composer submitted text. Deliver it as a // bare action and drain it *this tick* (post_action alone would -@@ -1603,11 +1655,13 @@ pub(crate) fn handle_message(&mut self, msg: FromJavaMessage) { +@@ -1612,11 +1664,13 @@ pub(crate) fn handle_message(&mut self, msg: FromJavaMessage) { self.handle_action_receiver(); } FromJavaMessage::SafeAreaInsets { @@ -557,7 +557,7 @@ index c0bf8571a..de787641c 100644 let new_insets = crate::event::SafeAreaInsets { top, right, -@@ -1633,6 +1687,7 @@ pub(crate) fn handle_message(&mut self, msg: FromJavaMessage) { +@@ -1642,6 +1696,7 @@ pub(crate) fn handle_message(&mut self, msg: FromJavaMessage) { } } } @@ -565,7 +565,7 @@ index c0bf8571a..de787641c 100644 FromJavaMessage::Init(_) => {} } } -@@ -2209,6 +2264,7 @@ pub fn android_entry(activity: *const std::ffi::c_void, startup: F) +@@ -2224,6 +2279,7 @@ pub fn android_entry(activity: *const std::ffi::c_void, startup: F) let mut initial_params: Option = None; let mut initial_surface: Option<(*mut ndk_sys::ANativeWindow, i32, i32)> = None; let mut initial_physical_keyboard: Option = None; @@ -573,7 +573,7 @@ index c0bf8571a..de787641c 100644 let (window, width, height, android_params) = loop { // Here use blocking method `recv` to reduce CPU usage during cold start. -@@ -2216,6 +2272,10 @@ pub fn android_entry(activity: *const std::ffi::c_void, startup: F) +@@ -2231,6 +2287,10 @@ pub fn android_entry(activity: *const std::ffi::c_void, startup: F) Ok(FromJavaMessage::Init(params)) => { initial_params = Some(params); } @@ -584,7 +584,7 @@ index c0bf8571a..de787641c 100644 Ok(FromJavaMessage::SurfaceCreated { window }) => { // Bootstrap off the first SurfaceChanged so we have a // real size. SurfaceCreated still hands us an acquired -@@ -2259,7 +2319,8 @@ pub fn android_entry(activity: *const std::ffi::c_void, startup: F) +@@ -2274,7 +2334,8 @@ pub fn android_entry(activity: *const std::ffi::c_void, startup: F) } if initial_params.is_some() && initial_surface.is_some() { @@ -594,13 +594,16 @@ index c0bf8571a..de787641c 100644 let (window, width, height) = initial_surface.take().unwrap(); break (window, width, height, android_params); } -@@ -2736,6 +2797,21 @@ fn handle_platform_ops(&mut self) -> EventFlow { +@@ -2780,9 +2841,24 @@ fn handle_platform_ops(&mut self) -> EventFlow { self.os.timers.timers.remove(&timer_id); } CxOsOp::ShowTextIME(_area, _pos, config) => unsafe { + // A queued draw request may predate Android DidHide. + // Do not let it reopen a keyboard the user just dismissed. + if self.keyboard.text_ime_dismissed { continue; } + // An input asking for the keyboard right after a hide (e.g., it just took + // focus from another input) keeps the keyboard up. + self.os.pending_ime_hide = false; + // A newly focused editor starts a new session. Tell Java + // now, before the person types: otherwise the input + // connection still carries the previous session, its @@ -616,16 +619,15 @@ index c0bf8571a..de787641c 100644 // A focused `TextInput` re-issues `ShowTextIME` on every // draw. Calling into Java each time thrashes the IME: // `configure_keyboard` can restart the input connection, -@@ -2753,38 +2829,14 @@ fn handle_platform_ops(&mut self) -> EventFlow { +@@ -2801,37 +2877,13 @@ fn handle_platform_ops(&mut self) -> EventFlow { } }, - CxOsOp::HideTextIME => unsafe { + CxOsOp::HideTextIME => { + self.publish_native_editor(); // Unconditional on purpose: unlike `ShowTextIME` this is not // issued per-frame, so there is no thrash to dedup — and a // skipped hide would leave the soft keyboard stuck open. - android_jni::to_java_show_keyboard(false); - self.os.last_ime_config = None; + self.os.pending_ime_hide = true; }, - CxOsOp::SyncImeState { - text, @@ -657,18 +659,18 @@ index c0bf8571a..de787641c 100644 CxOsOp::ShareText(content) => unsafe { android_jni::to_java_share_text(content); }, -@@ -3711,6 +3763,7 @@ fn default() -> Self { - last_ime_height: 0.0, +@@ -3759,6 +3811,7 @@ fn default() -> Self { last_ime_visible: false, last_ime_config: None, + pending_ime_hide: false, + ime_resumed:false, ime_last_operation:0, ime_input_sequence:0, ime_editor_session:0, ime_editor_identity:0, ime_focus_sequence:0, media: CxAndroidMedia::default(), display: None, surface_alive: false, -@@ -3856,6 +3909,12 @@ pub struct CxOs { - /// real change. Reset to `None` on `HideTextIME` and when Java reports the - /// keyboard closed. - pub last_ime_config: Option, +@@ -3907,6 +3960,12 @@ pub struct CxOs { + /// Set by `HideTextIME`, which waits until after the next draw, so that moving focus + /// from one text input to another keeps the keyboard up instead of hiding it. + pub pending_ime_hide: bool, + pub ime_resumed:bool, + pub ime_last_operation:u64, + pub ime_input_sequence:u64, @@ -748,7 +750,7 @@ index 000000000..ea66e8075 + } +} diff --git a/platform/src/os/linux/android/android_jni.rs b/platform/src/os/linux/android/android_jni.rs -index 1034093bc..1ae0c3e61 100644 +index 2d6fabe21..0c21b0bf3 100644 --- a/platform/src/os/linux/android/android_jni.rs +++ b/platform/src/os/linux/android/android_jni.rs @@ -90,7 +90,10 @@ pub enum TouchPhase { @@ -807,7 +809,7 @@ index 1034093bc..1ae0c3e61 100644 static MESSAGES_TX: Mutex>> = Mutex::new(None); static INTEGRATION_PENDING: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0); -@@ -1039,6 +1037,7 @@ fn init_simple_render_loop(device_refresh_rate: f32) { +@@ -1071,6 +1069,7 @@ fn init_simple_render_loop(device_refresh_rate: f32) { _: *mut jni_sys::JNIEnv, _: jni_sys::jobject, ) { @@ -815,7 +817,7 @@ index 1034093bc..1ae0c3e61 100644 send_from_java_message(FromJavaMessage::Resume); } -@@ -1047,6 +1046,7 @@ fn init_simple_render_loop(device_refresh_rate: f32) { +@@ -1079,6 +1078,7 @@ fn init_simple_render_loop(device_refresh_rate: f32) { _: *mut jni_sys::JNIEnv, _: jni_sys::jobject, ) { @@ -823,7 +825,7 @@ index 1034093bc..1ae0c3e61 100644 send_from_java_message(FromJavaMessage::Pause); } -@@ -1184,7 +1184,10 @@ pub extern "C" fn Java_dev_makepad_android_MakepadNative_surfaceOnLongClick( +@@ -1216,7 +1216,10 @@ pub extern "C" fn Java_dev_makepad_android_MakepadNative_surfaceOnLongClick( touches.push(TouchPoint { state: { @@ -835,7 +837,7 @@ index 1034093bc..1ae0c3e61 100644 match action_masked { 0 | 5 => TouchState::Start, 1 | 6 => TouchState::Stop, -@@ -1205,7 +1208,7 @@ pub extern "C" fn Java_dev_makepad_android_MakepadNative_surfaceOnLongClick( +@@ -1237,7 +1240,7 @@ pub extern "C" fn Java_dev_makepad_android_MakepadNative_surfaceOnLongClick( time: time as f64 / 1000.0, }); } @@ -844,7 +846,7 @@ index 1034093bc..1ae0c3e61 100644 } #[no_mangle] -@@ -1279,8 +1282,10 @@ extern "C" fn Java_dev_makepad_android_MakepadNative_surfaceOnSafeAreaInsets( +@@ -1311,8 +1314,10 @@ extern "C" fn Java_dev_makepad_android_MakepadNative_surfaceOnSafeAreaInsets( right: jni_sys::jfloat, bottom: jni_sys::jfloat, left: jni_sys::jfloat, @@ -855,7 +857,7 @@ index 1034093bc..1ae0c3e61 100644 top: top as f64, right: right as f64, bottom: bottom as f64, -@@ -1288,6 +1293,15 @@ extern "C" fn Java_dev_makepad_android_MakepadNative_surfaceOnSafeAreaInsets( +@@ -1320,6 +1325,15 @@ extern "C" fn Java_dev_makepad_android_MakepadNative_surfaceOnSafeAreaInsets( }); } @@ -871,7 +873,7 @@ index 1034093bc..1ae0c3e61 100644 #[no_mangle] extern "C" fn Java_dev_makepad_android_MakepadNative_onRenderLoop( _: *mut jni_sys::JNIEnv, -@@ -1694,34 +1708,24 @@ extern "C" fn Java_dev_makepad_android_MakepadNative_onWebSocketError( +@@ -1731,34 +1745,24 @@ extern "C" fn Java_dev_makepad_android_MakepadNative_onWebSocketError( } #[no_mangle] @@ -921,7 +923,7 @@ index 1034093bc..1ae0c3e61 100644 } unsafe fn jstring_to_string(env: *mut jni_sys::JNIEnv, java_string: jni_sys::jstring) -> String { -@@ -2699,6 +2703,7 @@ pub unsafe fn to_java_configure_keyboard(config: &TextInputConfig) { +@@ -2736,6 +2740,7 @@ pub unsafe fn to_java_configure_keyboard(config: &TextInputConfig) { /// Update IME text state for programmatic changes (Rust→Java) pub unsafe fn to_java_update_ime_text_state( @@ -929,7 +931,7 @@ index 1034093bc..1ae0c3e61 100644 full_text: &str, selection_start: i32, selection_end: i32, -@@ -2706,15 +2711,16 @@ pub unsafe fn to_java_update_ime_text_state( +@@ -2743,15 +2748,16 @@ pub unsafe fn to_java_update_ime_text_state( composing_end: i32, ) { let env = attach_jni_env(); @@ -961,7 +963,7 @@ index a4a335dfe..8a4c77000 100644 + +pub mod android_ime; diff --git a/platform/src/os/linux/gl_sys.rs b/platform/src/os/linux/gl_sys.rs -index 1e8933b6f..69296a0a2 100644 +index c628a6bee..409b2b1f2 100644 --- a/platform/src/os/linux/gl_sys.rs +++ b/platform/src/os/linux/gl_sys.rs @@ -416,7 +416,16 @@ macro_rules! gl_flush_error { @@ -981,7 +983,7 @@ index 1e8933b6f..69296a0a2 100644 pub glGenVertexArrays: TglGenVertexArrays, pub glBindVertexArray: TglBindVertexArray, pub glBindBuffer: TglBindBuffer, -@@ -566,6 +575,7 @@ pub fn try_load(mut loadfn: F) -> Result +@@ -574,6 +583,7 @@ pub fn try_load(mut loadfn: F) -> Result F: FnMut(&[&'static str]) -> *const raw::c_void, { Ok(Self { @@ -990,10 +992,10 @@ index 1e8933b6f..69296a0a2 100644 loadfn, TglGenVertexArrays, diff --git a/platform/src/os/linux/opengl.rs b/platform/src/os/linux/opengl.rs -index efd3e8095..c081aa356 100644 +index 5e48828a4..4bdaa109b 100644 --- a/platform/src/os/linux/opengl.rs +++ b/platform/src/os/linux/opengl.rs -@@ -1682,6 +1682,10 @@ fn read_program_cache( +@@ -1785,14 +1785,18 @@ fn read_program_cache( #[cfg(not(ohos_sim))] fn read_program_cache(gl: &LibGl, vertex: &str, pixel: &str, os_type: &OsType) -> Option { @@ -1002,9 +1004,8 @@ index efd3e8095..c081aa356 100644 + return None; + } if let Some(cache_dir) = os_type.get_cache_dir() { - let shader_hash = live_id!(shader).str_append(&vertex).str_append(&pixel); - let mut base_filename = format!("{}/shader_{:08x}", cache_dir, shader_hash.0); -@@ -1701,8 +1705,8 @@ fn read_program_cache(gl: &LibGl, vertex: &str, pixel: &str, os_type: &OsType) - + let filename = Self::program_cache_path(gl, &cache_dir, vertex, pixel, os_type); + if let Ok(mut cache_file) = File::open(&filename) { let mut binary = Vec::new(); let mut format_bytes = [0u8; 4]; @@ -1014,23 +1015,23 @@ index efd3e8095..c081aa356 100644 + Ok(()) => { let binary_format = u32::from_be_bytes(format_bytes); match cache_file.read_to_end(&mut binary) { - Ok(_full_bytes) => { -@@ -1744,8 +1748,14 @@ fn read_program_cache(gl: &LibGl, vertex: &str, pixel: &str, os_type: &OsType) - - error - ); - (gl.glDeleteProgram)(program); -+ // Stop trying further cache files until a freshly -+ // linked program establishes whether the driver can -+ // reload its own binaries. An old driver cache alone -+ // is not evidence that caching is unsupported. -+ gl.program_binary_cache.set(ProgramBinaryCacheState::NeedsValidation); - return None; - } -+ gl.program_binary_cache.set(ProgramBinaryCacheState::Supported); - return Some(program); - } - } else { -@@ -1884,12 +1894,16 @@ fn start_pending_program_compile( + Ok(_full_bytes) => unsafe { +@@ -1820,8 +1824,14 @@ fn read_program_cache(gl: &LibGl, vertex: &str, pixel: &str, os_type: &OsType) - + ); + (gl.glDeleteProgram)(program); + let _ = remove_file(&filename); ++ // Stop trying further cache files until a freshly ++ // linked program establishes whether the driver can ++ // reload its own binaries. An old driver cache alone ++ // is not evidence that caching is unsupported. ++ gl.program_binary_cache.set(ProgramBinaryCacheState::NeedsValidation); + return None; + } ++ gl.program_binary_cache.set(ProgramBinaryCacheState::Supported); + return Some(program); + }, + Err(e) => { +@@ -1958,12 +1968,16 @@ fn start_pending_program_compile( #[cfg(not(ohos_sim))] fn write_program_cache(gl: &LibGl, program: u32, vertex: &str, pixel: &str, os_type: &OsType) { @@ -1048,7 +1049,7 @@ index efd3e8095..c081aa356 100644 binary.resize(binary_len as usize, 0u8); let mut return_size = 0i32; let mut binary_format = 0u32; -@@ -1900,7 +1914,24 @@ fn write_program_cache(gl: &LibGl, program: u32, vertex: &str, pixel: &str, os_t +@@ -1974,7 +1988,24 @@ fn write_program_cache(gl: &LibGl, program: u32, vertex: &str, pixel: &str, os_t &mut binary_format as *mut _, binary.as_mut_ptr() as *mut _, ); @@ -1071,9 +1072,9 @@ index efd3e8095..c081aa356 100644 + } + gl.program_binary_cache.set(ProgramBinaryCacheState::Supported); + } - let shader_hash = live_id!(shader).str_append(&vertex).str_append(&pixel); - let mut filename = format!("{}/shader_{:08x}", cache_dir, shader_hash.0); - + let filename = + Self::program_cache_path(gl, &cache_dir, vertex, pixel, os_type); + binary.resize(return_size as usize, 0u8); diff --git a/platform/src/window.rs b/platform/src/window.rs index c763d8501..b2b321cd2 100644 --- a/platform/src/window.rs @@ -1162,10 +1163,10 @@ index c763d8501..b2b321cd2 100644 fn set_window_dpi_override_converts_every_in_window_metric() { let mut cx = test_cx(); diff --git a/tools/cargo_makepad/src/android/java/dev/makepad/android/MakepadActivity.java b/tools/cargo_makepad/src/android/java/dev/makepad/android/MakepadActivity.java -index a7dd7524d..e95620a5d 100644 +index c00c756a6..ab74f7ad9 100644 --- a/tools/cargo_makepad/src/android/java/dev/makepad/android/MakepadActivity.java +++ b/tools/cargo_makepad/src/android/java/dev/makepad/android/MakepadActivity.java -@@ -371,7 +371,7 @@ class MakepadSystemInsets { +@@ -368,7 +368,7 @@ class MakepadSystemInsets { static void report(View view, WindowInsets insets) { float density = view.getResources().getDisplayMetrics().density; MakepadSystemInsets i = MakepadSystemInsets.from(view, insets, density); @@ -1174,7 +1175,7 @@ index a7dd7524d..e95620a5d 100644 } } -@@ -387,6 +387,24 @@ class MakepadSurface +@@ -384,6 +384,24 @@ class MakepadSurface { // IME InputConnection for handling composition text private MakepadInputConnection mInputConnection; @@ -1199,7 +1200,7 @@ index a7dd7524d..e95620a5d 100644 // Shared Editable buffer for IME - this is the source of truth for Java side private SpannableStringBuilder mEditable = new SpannableStringBuilder(); -@@ -428,7 +446,8 @@ class MakepadSurface +@@ -425,7 +443,8 @@ class MakepadSurface private int mAutocapitalize = AUTOCAP_SENTENCES; private int mAutocorrect = AUTOCORRECT_DEFAULT; private int mReturnKeyType = RETURN_KEY_DEFAULT; @@ -1209,7 +1210,7 @@ index a7dd7524d..e95620a5d 100644 private boolean mIsSecure = false; // Package-private getters for MakepadInputConnection to access shared state -@@ -602,6 +621,7 @@ class MakepadSurface +@@ -599,6 +618,7 @@ class MakepadSurface } if (actionMasked == MotionEvent.ACTION_DOWN) hintTouchLoad(); @@ -1217,7 +1218,7 @@ index a7dd7524d..e95620a5d 100644 MakepadNative.surfaceOnTouch(event); return retval; } -@@ -667,6 +687,27 @@ class MakepadSurface +@@ -664,6 +684,27 @@ class MakepadSurface @SuppressWarnings("deprecation") @Override public boolean onKey(View v, int keyCode, KeyEvent event) { @@ -1245,7 +1246,7 @@ index a7dd7524d..e95620a5d 100644 if (event.getAction() == KeyEvent.ACTION_DOWN && keyCode != 0) { int metaState = event.getMetaState(); boolean isRepeat = event.getRepeatCount() > 0; -@@ -678,7 +719,11 @@ class MakepadSurface +@@ -675,7 +716,11 @@ class MakepadSurface MakepadNative.surfaceOnKeyUp(keyCode, metaState); } @@ -1258,7 +1259,7 @@ index a7dd7524d..e95620a5d 100644 int character = event.getUnicodeChar(); if (character == 0) { String characters = event.getCharacters(); -@@ -875,23 +920,39 @@ class MakepadSurface +@@ -872,23 +917,39 @@ class MakepadSurface } // Called from Rust to update text state (for programmatic changes, not IME input) @@ -1312,7 +1313,7 @@ index a7dd7524d..e95620a5d 100644 // Clamp selection int textLen = textChanged ? fullText.length() : currentText.length(); selStart = Math.max(0, Math.min(selStart, textLen)); -@@ -914,13 +975,6 @@ class MakepadSurface +@@ -911,13 +972,6 @@ class MakepadSurface mInputConnection.setComposingRegion(composingStart, composingEnd); } @@ -1326,7 +1327,7 @@ index a7dd7524d..e95620a5d 100644 // Notify IME of text change without restarting input // restartInput() destroys composition state and causes IME flicker; // updateExtractedText() + updateSelection() is the lightweight alternative -@@ -959,6 +1013,7 @@ class MakepadSurface +@@ -956,6 +1010,7 @@ class MakepadSurface } } } @@ -1334,7 +1335,7 @@ index a7dd7524d..e95620a5d 100644 } public Surface getNativeSurface() { -@@ -1167,6 +1222,7 @@ public class MakepadActivity +@@ -1164,6 +1219,7 @@ public class MakepadActivity private FrameLayout mRootLayout; private FrameLayout mApplicationOverlay; private ApplicationExtension mApplicationExtension; @@ -1342,7 +1343,7 @@ index a7dd7524d..e95620a5d 100644 /** Optional app Java client. Implementations enqueue blocking work on workers. */ public interface ApplicationExtension { -@@ -1177,6 +1233,24 @@ public class MakepadActivity +@@ -1174,6 +1230,24 @@ public class MakepadActivity void onDestroy(); default boolean onActivityResult(int requestCode, int resultCode, Intent data) { return false; } default boolean onBackPressed() { return false; } @@ -1367,7 +1368,7 @@ index a7dd7524d..e95620a5d 100644 } private void createApplicationExtension() { -@@ -1643,6 +1717,26 @@ public class MakepadActivity +@@ -1642,6 +1716,26 @@ public class MakepadActivity MakepadNative.activityOnStart(); } @@ -1394,7 +1395,7 @@ index a7dd7524d..e95620a5d 100644 @Override protected void onResume() { super.onResume(); -@@ -1669,6 +1763,7 @@ public class MakepadActivity +@@ -1668,6 +1762,7 @@ public class MakepadActivity if (mQrScanning) closeQrScanner("interrupted"); prepareSurfaceSnapshotOverlayForPause(); super.onPause(); @@ -1402,7 +1403,7 @@ index a7dd7524d..e95620a5d 100644 MakepadNative.activityOnPause(); if (mApplicationExtension != null) mApplicationExtension.onPause(); stopGpsLocationUpdates(); -@@ -2656,6 +2751,7 @@ public class MakepadActivity +@@ -2655,6 +2750,7 @@ public class MakepadActivity runOnUiThread(new Runnable() { @Override public void run() { @@ -1410,7 +1411,7 @@ index a7dd7524d..e95620a5d 100644 if (show) { if (view == null || view.getInputMode() == MakepadSurface.INPUT_MODE_NONE) { return; -@@ -2700,17 +2796,16 @@ public class MakepadActivity +@@ -2699,17 +2795,16 @@ public class MakepadActivity }); } @@ -1432,7 +1433,7 @@ index a7dd7524d..e95620a5d 100644 selStart, selEnd, composingStart, -@@ -3074,16 +3169,19 @@ public class MakepadActivity +@@ -3073,16 +3168,19 @@ public class MakepadActivity if (action == MotionEvent.ACTION_DOWN) { setSelectionHandlePosition(v, absX, absY); @@ -1848,10 +1849,10 @@ index 10f380316..4df279052 100644 } } diff --git a/tools/cargo_makepad/src/android/java/dev/makepad/android/MakepadNative.java b/tools/cargo_makepad/src/android/java/dev/makepad/android/MakepadNative.java -index 866ef68dc..9f27bf02c 100644 +index 2f6dc2a8a..c1fe6470b 100644 --- a/tools/cargo_makepad/src/android/java/dev/makepad/android/MakepadNative.java +++ b/tools/cargo_makepad/src/android/java/dev/makepad/android/MakepadNative.java -@@ -34,7 +34,8 @@ public class MakepadNative { +@@ -54,7 +54,8 @@ public class MakepadNative { public native static void surfaceOnCharacter(int character); public native static void surfaceOnResizeTextIME(int keyboard_height, boolean is_open); public native static void surfaceOnPhysicalKeyboardChanged(boolean connected); @@ -1861,7 +1862,7 @@ index 866ef68dc..9f27bf02c 100644 // networking public native static void onHttpResponse(long id, long metadata_id, int status_code, String headers, byte[] body); -@@ -50,19 +51,16 @@ public class MakepadNative { +@@ -70,19 +71,16 @@ public class MakepadNative { // selection handles public native static void onSelectionHandleDrag(int handle, int phase, float x, float y, long timeMillis); @@ -1892,7 +1893,7 @@ index 866ef68dc..9f27bf02c 100644 // Native floating chat composer (an Android view floating over the GL // surface) submitted its text — user tapped send or pressed IME "Send". diff --git a/widgets/src/animator.rs b/widgets/src/animator.rs -index 5c190169d..d88c3ea28 100644 +index f1e07ef8c..b893371df 100644 --- a/widgets/src/animator.rs +++ b/widgets/src/animator.rs @@ -627,7 +627,7 @@ pub fn play( @@ -1943,7 +1944,7 @@ index 5c190169d..d88c3ea28 100644 /* pub fn duration(&self) -> f64 { match self { -@@ -1843,3 +1850,12 @@ pub fn map(&self, t: f64) -> f64 { +@@ -1888,3 +1895,12 @@ fn emphasized_matches_androids_fast_out_extra_slow_in() { } } } @@ -1957,10 +1958,10 @@ index 5c190169d..d88c3ea28 100644 + } +} diff --git a/widgets/src/text_input.rs b/widgets/src/text_input.rs -index a3c1be2b4..410f54d6a 100644 +index 849fbc3e8..0e3fc6e01 100644 --- a/widgets/src/text_input.rs +++ b/widgets/src/text_input.rs -@@ -941,6 +941,13 @@ pub fn set_selection(&mut self, cx: &mut Cx, selection: Selection) { +@@ -954,6 +954,13 @@ pub fn set_selection(&mut self, cx: &mut Cx, selection: Selection) { self.clear_composition(); self.history.force_new_edit_group(); self.draw_bg.redraw(cx); @@ -1974,7 +1975,7 @@ index a3c1be2b4..410f54d6a 100644 } pub fn cursor(&self) -> Cursor { -@@ -2509,6 +2516,14 @@ fn handle_event(&mut self, cx: &mut Cx, event: &Event, _scope: &mut Scope) { +@@ -2518,6 +2525,14 @@ fn handle_event(&mut self, cx: &mut Cx, event: &Event, _scope: &mut Scope) { } return; } @@ -1990,10 +1991,10 @@ index a3c1be2b4..410f54d6a 100644 self.draw_bg.redraw(cx); self.draw_cursor.redraw(cx); diff --git a/widgets/src/view.rs b/widgets/src/view.rs -index ad333ec3e..fe70f06c8 100644 +index 035fd508b..838f1e4cf 100644 --- a/widgets/src/view.rs +++ b/widgets/src/view.rs -@@ -529,6 +529,11 @@ pub fn set_uniform(&self, cx: &Cx, uniform: LiveId, value: &[f32]) { +@@ -532,6 +532,11 @@ pub fn set_uniform(&self, cx: &Cx, uniform: LiveId, value: &[f32]) { } } @@ -2005,7 +2006,7 @@ index ad333ec3e..fe70f06c8 100644 pub fn set_scroll_pos(&self, cx: &mut Cx, v: Vec2d) { if let Some(mut inner) = self.borrow_mut() { inner.set_scroll_pos(cx, v) -@@ -1382,6 +1387,11 @@ pub fn child_at_index(&mut self, index: usize) -> Option<&WidgetRef> { +@@ -1385,6 +1390,11 @@ pub fn child_at_index(&mut self, index: usize) -> Option<&WidgetRef> { } } diff --git a/tools/runtime-patches/makepad-splash-budget.patch b/tools/runtime-patches/makepad-splash-budget.patch index 61445585c..3c401cb59 100644 --- a/tools/runtime-patches/makepad-splash-budget.patch +++ b/tools/runtime-patches/makepad-splash-budget.patch @@ -1,3 +1,5 @@ +diff --git a/widgets/src/widget_async.rs b/widgets/src/widget_async.rs +index 7b8eaebc4..110176edb 100644 --- a/widgets/src/widget_async.rs +++ b/widgets/src/widget_async.rs @@ -503,14 +503,31 @@ fn with_splash_prompt_authority(vm: &mut ScriptVm, f: impl FnOnce(&mut Script diff --git a/tools/runtime-patches/makepad-system-back.patch b/tools/runtime-patches/makepad-system-back.patch index d3fa42bf5..94d092fac 100644 --- a/tools/runtime-patches/makepad-system-back.patch +++ b/tools/runtime-patches/makepad-system-back.patch @@ -1,8 +1,8 @@ diff --git a/tools/cargo_makepad/src/android/java/dev/makepad/android/MakepadActivity.java b/tools/cargo_makepad/src/android/java/dev/makepad/android/MakepadActivity.java -index 274d922..ede68f2 100644 +index ab74f7ad9..870a02a13 100644 --- a/tools/cargo_makepad/src/android/java/dev/makepad/android/MakepadActivity.java +++ b/tools/cargo_makepad/src/android/java/dev/makepad/android/MakepadActivity.java -@@ -1180,6 +1180,8 @@ public class MakepadActivity +@@ -1230,6 +1230,8 @@ public class MakepadActivity void onDestroy(); default boolean onActivityResult(int requestCode, int resultCode, Intent data) { return false; } default boolean onBackPressed() { return false; } @@ -11,7 +11,7 @@ index 274d922..ede68f2 100644 /** Runs before native views or the renderer receive a touch. */ default boolean filterTouchEvent(MotionEvent event) { return true; } } -@@ -1811,6 +1813,12 @@ public class MakepadActivity +@@ -1863,6 +1865,12 @@ public class MakepadActivity // key (BACK included) and forwards it to Rust, so onBackPressed never runs. @Override public boolean dispatchKeyEvent(KeyEvent event) { @@ -24,7 +24,7 @@ index 274d922..ede68f2 100644 if (mQrScanning && event.getKeyCode() == KeyEvent.KEYCODE_BACK) { if (event.getAction() == KeyEvent.ACTION_UP) closeQrScanner("cancelled"); return true; -@@ -1821,6 +1829,7 @@ public class MakepadActivity +@@ -1873,6 +1881,7 @@ public class MakepadActivity @Override @SuppressWarnings("deprecation") public void onBackPressed() { diff --git a/tools/runtime-patches/makepad-trusted-user-input.patch b/tools/runtime-patches/makepad-trusted-user-input.patch index 50742ff08..7dfef43f4 100644 --- a/tools/runtime-patches/makepad-trusted-user-input.patch +++ b/tools/runtime-patches/makepad-trusted-user-input.patch @@ -147,7 +147,7 @@ index 2ff06e69a..d11b9b962 100644 // handler (e.g. runtime dpi_override updates) before triggers and // actions, so layout-dependent reactions see the new geometry. diff --git a/platform/src/os/linux/android/android.rs b/platform/src/os/linux/android/android.rs -index de787641c..8d910fc7b 100644 +index 33fe1a569..1012812ae 100644 --- a/platform/src/os/linux/android/android.rs +++ b/platform/src/os/linux/android/android.rs @@ -430,7 +430,7 @@ pub fn main_loop(&mut self, from_java_rx: mpsc::Receiver) { @@ -159,7 +159,7 @@ index de787641c..8d910fc7b 100644 if !cancelled && touches .iter() .all(|t| t.state == crate::event::finger::TouchState::Move) -@@ -929,7 +929,7 @@ pub(crate) fn handle_message(&mut self, msg: FromJavaMessage) { +@@ -938,7 +938,7 @@ pub(crate) fn handle_message(&mut self, msg: FromJavaMessage) { }); self.call_event_handler(&e); } @@ -168,7 +168,7 @@ index de787641c..8d910fc7b 100644 if touches.is_empty() { return; } let time = touches[0].time; let window = &self.windows[CxWindowPool::id_zero()]; -@@ -962,7 +962,7 @@ pub(crate) fn handle_message(&mut self, msg: FromJavaMessage) { +@@ -971,7 +971,7 @@ pub(crate) fn handle_message(&mut self, msg: FromJavaMessage) { touches, modifiers: Default::default(), }); @@ -178,7 +178,7 @@ index de787641c..8d910fc7b 100644 e } else { diff --git a/platform/src/os/linux/android/android_jni.rs b/platform/src/os/linux/android/android_jni.rs -index 1ae0c3e61..2f3608350 100644 +index 0c21b0bf3..376b20e96 100644 --- a/platform/src/os/linux/android/android_jni.rs +++ b/platform/src/os/linux/android/android_jni.rs @@ -128,7 +128,7 @@ pub enum FromJavaMessage { @@ -190,7 +190,7 @@ index 1ae0c3e61..2f3608350 100644 Character { character: u32, }, -@@ -1152,13 +1152,54 @@ pub extern "C" fn Java_dev_makepad_android_MakepadNative_surfaceOnLongClick( +@@ -1184,13 +1184,54 @@ pub extern "C" fn Java_dev_makepad_android_MakepadNative_surfaceOnLongClick( _: jni_sys::jclass, event: jni_sys::jobject, ) { @@ -245,7 +245,7 @@ index 1ae0c3e61..2f3608350 100644 let mut touches = Vec::with_capacity(touch_count as usize); for touch_index in 0..touch_count { let id = -@@ -1208,7 +1249,10 @@ pub extern "C" fn Java_dev_makepad_android_MakepadNative_surfaceOnLongClick( +@@ -1240,7 +1281,10 @@ pub extern "C" fn Java_dev_makepad_android_MakepadNative_surfaceOnLongClick( time: time as f64 / 1000.0, }); } @@ -258,10 +258,10 @@ index 1ae0c3e61..2f3608350 100644 #[no_mangle] diff --git a/platform/src/remote.rs b/platform/src/remote.rs -index 6b0d6a6a3..4d348fa72 100644 +index 0e95d6734..6052e9cba 100644 --- a/platform/src/remote.rs +++ b/platform/src/remote.rs -@@ -997,6 +997,7 @@ pub(crate) fn next_grab_deadline() -> Option { +@@ -996,6 +996,7 @@ pub(crate) fn next_grab_deadline() -> Option { } fn poll_with_present(cx: &mut Cx, mut present: impl FnMut(&mut Cx, WindowId) -> Option) -> bool { diff --git a/tools/runtime-patches/makepad-video-controls-lifecycle.patch b/tools/runtime-patches/makepad-video-controls-lifecycle.patch index 4ebda8c85..5f05eb01e 100644 --- a/tools/runtime-patches/makepad-video-controls-lifecycle.patch +++ b/tools/runtime-patches/makepad-video-controls-lifecycle.patch @@ -1065,7 +1065,7 @@ index 197599cf4..b11c4151d 100644 + +} diff --git a/widgets/src/widget_async.rs b/widgets/src/widget_async.rs -index c0d1a367a..bf8e11258 100644 +index 110176edb..dab21e678 100644 --- a/widgets/src/widget_async.rs +++ b/widgets/src/widget_async.rs @@ -1479,6 +1479,8 @@ fn on_widget_script_thread_completed( diff --git a/tools/runtime-patches/makepad-web-reader-availability.patch b/tools/runtime-patches/makepad-web-reader-availability.patch index 82121f2d8..63a1d91dd 100644 --- a/tools/runtime-patches/makepad-web-reader-availability.patch +++ b/tools/runtime-patches/makepad-web-reader-availability.patch @@ -1,3 +1,5 @@ +diff --git a/widgets/src/web_reader.rs b/widgets/src/web_reader.rs +index d1e2a1b04..44240521e 100644 --- a/widgets/src/web_reader.rs +++ b/widgets/src/web_reader.rs @@ -1,5 +1,7 @@ @@ -8,7 +10,7 @@ //! //! ```text //! reader := WebReader{width: Fill height: Fill on_error: || show(ui.reader.error())} -@@ -168,6 +170,14 @@ +@@ -168,6 +170,14 @@ fn report(&mut self, cx: &mut Cx, why: String) { /// Open `url`, when this app may. Returns whether it opened. pub fn open(&mut self, cx: &mut Cx, url: &str) -> bool { @@ -23,7 +25,7 @@ if self.auth_mode { self.close(cx); self.auth_mode = false; self.auth_id = None; } let heap = self.heap_key(); if !crate::splash_policy::page_allowed(heap, url) { -@@ -442,3 +452,27 @@ +@@ -442,3 +452,27 @@ fn draw_walk(&mut self, cx: &mut Cx2d, scope: &mut Scope, walk: Walk) -> DrawSte step } } diff --git a/tools/runtime-patches/makepad-windows-frame-completion.patch b/tools/runtime-patches/makepad-windows-frame-completion.patch index 61c58bdde..2a627ff1c 100644 --- a/tools/runtime-patches/makepad-windows-frame-completion.patch +++ b/tools/runtime-patches/makepad-windows-frame-completion.patch @@ -1,8 +1,8 @@ diff --git a/platform/src/os/windows/windows.rs b/platform/src/os/windows/windows.rs -index 4abb93472..c0404864b 100644 +index 2be479926..250cb26dd 100644 --- a/platform/src/os/windows/windows.rs +++ b/platform/src/os/windows/windows.rs -@@ -581,7 +581,11 @@ fn paint_tick( +@@ -585,7 +585,11 @@ fn paint_tick( } // ok here we send out to all our childprocesses @@ -15,10 +15,10 @@ index 4abb93472..c0404864b 100644 // the Poll loop to the display. A pass that presents nothing has no // blocking call at all, so a NextFrame listener that re-arms without diff --git a/platform/src/os/windows/windows_stdin.rs b/platform/src/os/windows/windows_stdin.rs -index e9467b02f..ac3a85683 100644 +index 83638bdcf..8ebcb52aa 100644 --- a/platform/src/os/windows/windows_stdin.rs +++ b/platform/src/os/windows/windows_stdin.rs -@@ -340,7 +340,11 @@ fn stdin_handle_host_to_stdin( +@@ -344,7 +344,11 @@ fn stdin_handle_host_to_stdin( self.hlsl_compile_shaders(d3d11_cx); } diff --git a/tools/runtime-patches/makepad-windows-remote-capture.patch b/tools/runtime-patches/makepad-windows-remote-capture.patch index 67c5d1209..c80fe68b1 100644 --- a/tools/runtime-patches/makepad-windows-remote-capture.patch +++ b/tools/runtime-patches/makepad-windows-remote-capture.patch @@ -1,5 +1,5 @@ diff --git a/platform/src/os/windows/d3d11.rs b/platform/src/os/windows/d3d11.rs -index ebe77a0..f060468 100644 +index d6d4d7fdb..08802b7b0 100644 --- a/platform/src/os/windows/d3d11.rs +++ b/platform/src/os/windows/d3d11.rs @@ -1187,6 +1187,23 @@ fn capture_window_screenshot(&mut self, d3d11_window: &D3d11Window, d3d11_cx: &D @@ -27,10 +27,10 @@ index ebe77a0..f060468 100644 Ok(png) => Self::send_studio_screenshot_response(request_ids, width, height, png), Err(err) => { diff --git a/platform/src/remote.rs b/platform/src/remote.rs -index 678fc72..6b0d6a6 100644 +index 7744a8152..0e95d6734 100644 --- a/platform/src/remote.rs +++ b/platform/src/remote.rs -@@ -759,11 +759,11 @@ pub(crate) fn deliver_grabs( +@@ -758,11 +758,11 @@ pub(crate) fn deliver_grabs( rest } @@ -44,7 +44,7 @@ index 678fc72..6b0d6a6 100644 ))] pub(crate) fn deliver_grab_pixels( request_ids: Vec, -@@ -778,7 +778,7 @@ pub(crate) fn deliver_grab_pixels( +@@ -777,7 +777,7 @@ pub(crate) fn deliver_grab_pixels( return request_ids; } let captured = Instant::now(); diff --git a/tools/runtime-patches/makepad-windows-still-capture.patch b/tools/runtime-patches/makepad-windows-still-capture.patch index f5c931be9..811367a8f 100644 --- a/tools/runtime-patches/makepad-windows-still-capture.patch +++ b/tools/runtime-patches/makepad-windows-still-capture.patch @@ -1,8 +1,8 @@ diff --git a/platform/Cargo.toml b/platform/Cargo.toml -index 31b5a5398..e4f424ac7 100644 +index 93aa67187..ff9e74c0e 100644 --- a/platform/Cargo.toml +++ b/platform/Cargo.toml -@@ -104,6 +104,10 @@ napi-derive-ohos = "0.0.9" +@@ -105,6 +105,10 @@ napi-derive-ohos = "0.0.9" napi-ohos = "0.1.3" ohos-sys = { version = "0.2.1", features = ["xcomponent"] } From b2f2e41759316ed285d860337f7fd8e3ca0f4124 Mon Sep 17 00:00:00 2001 From: ymote <151983+ymote@users.noreply.github.com> Date: Fri, 9 Oct 2026 18:57:37 -0700 Subject: [PATCH 2/3] Host API lab: refusals that no longer come from the runtime's grant check makepad#118 (#450) removed the runtime's capability check before a host.request, so two checks of the native acceptance run named its text: - a background location sample without consent is now refused by App Hub's dispatcher (a foreground-only method from a background surface), with the device service's consent check behind it; - an undeclared microphone recording from an agent's tool call is refused by the dispatcher too, and in the foreground by the audio service, which checks the family itself. Either refusal passes. Co-Authored-By: Claude Opus 5.5 --- crates/shell/examples/host-api-lab.rs | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/crates/shell/examples/host-api-lab.rs b/crates/shell/examples/host-api-lab.rs index 3655177f9..9bb1feff1 100644 --- a/crates/shell/examples/host-api-lab.rs +++ b/crates/shell/examples/host-api-lab.rs @@ -291,8 +291,12 @@ fn os_batch_checks(reply: &Result) -> Value { && batch["location_discovery"]["descriptor"]["agent_access"] == "foreground-only", "background_import_refused": denied("background_import_allowed", "background_import_error"), "background_export_refused": denied("background_export_allowed", "background_export_error"), + // Refused before any OS call. Since makepad#118 (OctoSense #450) the + // runtime no longer refuses on the app's grants: App Hub's dispatcher + // refuses a foreground-only method from a background surface, and the + // device service checks the person's consent behind it. "location_without_consent_refused": batch["background_location_allowed"] == false - && batch["background_location_error"] == "authorization_required: request location.permission.request from the foreground app", + && batch["background_location_error"] == "location.sample is unavailable to agents/background surfaces", }) } @@ -348,7 +352,16 @@ fn public_service_checks(reply: &Result) -> serde_json::Map Date: Fri, 9 Oct 2026 19:04:03 -0700 Subject: [PATCH 3/3] Pin App Hub 40abb23b (#191): the 14th sweep's App Hub App Hub #191 repins App Hub to the same runtime, and with the budget gate gone a runaway app tool handler still fails at once (a captured-error sink catches the per-evaluation limit); net alone hands an isolate its network module. It also brings #186 (components under wasm-components-v1), #188 (wasi:http and octosense:host admission) and Co-Authored-By: Claude Opus 5.5 --- Cargo.lock | 10 +++++----- Cargo.toml | 10 +++++----- native-apps.json | 2 +- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7465adbc6..e5e88a4a5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11384,7 +11384,7 @@ dependencies = [ [[package]] name = "octosense-app-contract" version = "1.10.0" -source = "git+https://github.com/OctoSense-org/OctoSense-App-Hub?rev=371f51aeb6ba90e9a214c5ddb6ccfc40a2e2a339#371f51aeb6ba90e9a214c5ddb6ccfc40a2e2a339" +source = "git+https://github.com/OctoSense-org/OctoSense-App-Hub?rev=40abb23b50d5c3e546c50d1d91ab27258a203eaf#40abb23b50d5c3e546c50d1d91ab27258a203eaf" dependencies = [ "blake3", "serde", @@ -11395,7 +11395,7 @@ dependencies = [ [[package]] name = "octosense-app-hub" version = "0.1.0" -source = "git+https://github.com/OctoSense-org/OctoSense-App-Hub?rev=371f51aeb6ba90e9a214c5ddb6ccfc40a2e2a339#371f51aeb6ba90e9a214c5ddb6ccfc40a2e2a339" +source = "git+https://github.com/OctoSense-org/OctoSense-App-Hub?rev=40abb23b50d5c3e546c50d1d91ab27258a203eaf#40abb23b50d5c3e546c50d1d91ab27258a203eaf" dependencies = [ "base64 0.22.1", "blake3", @@ -11421,7 +11421,7 @@ dependencies = [ [[package]] name = "octosense-app-hub-app" version = "0.1.0" -source = "git+https://github.com/OctoSense-org/OctoSense-App-Hub?rev=371f51aeb6ba90e9a214c5ddb6ccfc40a2e2a339#371f51aeb6ba90e9a214c5ddb6ccfc40a2e2a339" +source = "git+https://github.com/OctoSense-org/OctoSense-App-Hub?rev=40abb23b50d5c3e546c50d1d91ab27258a203eaf#40abb23b50d5c3e546c50d1d91ab27258a203eaf" dependencies = [ "makepad-app-module", "makepad-widgets", @@ -11449,7 +11449,7 @@ dependencies = [ [[package]] name = "octosense-app-policy" version = "0.1.0" -source = "git+https://github.com/OctoSense-org/OctoSense-App-Hub?rev=371f51aeb6ba90e9a214c5ddb6ccfc40a2e2a339#371f51aeb6ba90e9a214c5ddb6ccfc40a2e2a339" +source = "git+https://github.com/OctoSense-org/OctoSense-App-Hub?rev=40abb23b50d5c3e546c50d1d91ab27258a203eaf#40abb23b50d5c3e546c50d1d91ab27258a203eaf" dependencies = [ "makepad-widgets", "octosense-app-contract", @@ -11471,7 +11471,7 @@ dependencies = [ [[package]] name = "octosense-appstore" version = "0.1.0" -source = "git+https://github.com/OctoSense-org/OctoSense-App-Hub?rev=371f51aeb6ba90e9a214c5ddb6ccfc40a2e2a339#371f51aeb6ba90e9a214c5ddb6ccfc40a2e2a339" +source = "git+https://github.com/OctoSense-org/OctoSense-App-Hub?rev=40abb23b50d5c3e546c50d1d91ab27258a203eaf#40abb23b50d5c3e546c50d1d91ab27258a203eaf" dependencies = [ "makepad-app-module", "makepad-widgets", diff --git a/Cargo.toml b/Cargo.toml index 3335866d4..af437d780 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -91,7 +91,7 @@ octosense-reference = { path = "apps/reference" } makepad-sheets = { git = "https://github.com/OctoSense-org/makepad.git", rev = "d653bc4493619ab979ccff165cf48c0c63b77d19", default-features = false } makepad-terminal = { git = "https://github.com/OctoSense-org/makepad.git", rev = "d653bc4493619ab979ccff165cf48c0c63b77d19" } octosense-appcard = { path = "apps/appcard/module" } -octosense-app-hub-app = { git = "https://github.com/OctoSense-org/OctoSense-App-Hub", rev = "371f51aeb6ba90e9a214c5ddb6ccfc40a2e2a339" } +octosense-app-hub-app = { git = "https://github.com/OctoSense-org/OctoSense-App-Hub", rev = "40abb23b50d5c3e546c50d1d91ab27258a203eaf" } makepad-calculator = { git = "https://github.com/OctoSense-org/makepad.git", rev = "d653bc4493619ab979ccff165cf48c0c63b77d19", default-features = false } makepad-clock = { git = "https://github.com/OctoSense-org/makepad.git", rev = "d653bc4493619ab979ccff165cf48c0c63b77d19", default-features = false } makepad-notes = { git = "https://github.com/OctoSense-org/makepad.git", rev = "d653bc4493619ab979ccff165cf48c0c63b77d19", default-features = false } @@ -130,12 +130,12 @@ octoscript-makepad = { git = "https://github.com/OctoSense-org/Octoscript-Makepa octoscript-node = { git = "https://github.com/OctoSense-org/Octoscript-Makepad.git", rev = "6cf2d90aa33ff4ae603f49c7557f3ecf986207f0" } # --- App Hub's host service registry the Mail and llm services register # into; the same revision as App Hub's app crate (a native app, above). -octosense-appstore = { git = "https://github.com/OctoSense-org/OctoSense-App-Hub", rev = "371f51aeb6ba90e9a214c5ddb6ccfc40a2e2a339" } +octosense-appstore = { git = "https://github.com/OctoSense-org/OctoSense-App-Hub", rev = "40abb23b50d5c3e546c50d1d91ab27258a203eaf" } # The app policy (resolution, isolate settings, the Splash adapter) and the # Hub client the Card runner resolves an app's policy with; the shell's # glance tiles run cards under it (crates/shell/src/glance_card.rs). -octosense-app-policy = { git = "https://github.com/OctoSense-org/OctoSense-App-Hub", rev = "371f51aeb6ba90e9a214c5ddb6ccfc40a2e2a339" } -octosense-app-hub = { git = "https://github.com/OctoSense-org/OctoSense-App-Hub", rev = "371f51aeb6ba90e9a214c5ddb6ccfc40a2e2a339" } +octosense-app-policy = { git = "https://github.com/OctoSense-org/OctoSense-App-Hub", rev = "40abb23b50d5c3e546c50d1d91ab27258a203eaf" } +octosense-app-hub = { git = "https://github.com/OctoSense-org/OctoSense-App-Hub", rev = "40abb23b50d5c3e546c50d1d91ab27258a203eaf" } # The app contract (ADR 0005), from crates.io by version: App Hub, the shell # and Rinx all depend on `octosense-app-contract = "1"`, so Cargo links ONE # 1.x for the whole build (Cargo.lock pins the exact version OctoSense ships) @@ -437,7 +437,7 @@ ruma = { git = "https://github.com/project-robius/ruma.git", branch = "tsp" } # -i octos-core` still sees one octos source. [patch.crates-io] # Keep the contract identical to the pinned SDK for this release candidate. -octosense-app-contract = { git = "https://github.com/OctoSense-org/OctoSense-App-Hub", rev = "371f51aeb6ba90e9a214c5ddb6ccfc40a2e2a339" } +octosense-app-contract = { git = "https://github.com/OctoSense-org/OctoSense-App-Hub", rev = "40abb23b50d5c3e546c50d1d91ab27258a203eaf" } nix = { git = "https://github.com/octos-org/octos.git", rev = "18fcd3f16e527d2b601d7ae244f056fb711bb6b8" } # pdfcraft's bootstrap PDF stack: its workspace patches these five diff --git a/native-apps.json b/native-apps.json index b99175023..bc9f63134 100644 --- a/native-apps.json +++ b/native-apps.json @@ -114,7 +114,7 @@ "id": "apphub", "feature": "app-hub", "crate": "octosense-app-hub-app", - "source": { "git": "https://github.com/OctoSense-org/OctoSense-App-Hub", "rev": "371f51aeb6ba90e9a214c5ddb6ccfc40a2e2a339", "local": null }, + "source": { "git": "https://github.com/OctoSense-org/OctoSense-App-Hub", "rev": "40abb23b50d5c3e546c50d1d91ab27258a203eaf", "local": null }, "module": ["octosense_app_hub_app::APP_HUB_MODULE", "octosense_app_hub_app::CARD_MODULE"], "bin": null, "default_features": true,