diff --git a/Cargo.lock b/Cargo.lock index 30ced5e7e..e5e88a4a5 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", @@ -11383,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", @@ -11394,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", @@ -11420,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", @@ -11448,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", @@ -11470,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 8b8c6f806..af437d780 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -88,54 +88,54 @@ 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 } +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 } +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" } +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/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/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/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..bc9f63134 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, @@ -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, @@ -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"] }