From 1311bed5e96711b6cc9b516bc04f0ccf4880d850 Mon Sep 17 00:00:00 2001 From: OverSu Date: Tue, 13 Oct 2026 00:19:45 +0200 Subject: [PATCH] Release Preview 20 shared desktop, Android and Sargo updates Restore Gaia settings and media workflows, extend native network adapters, and align the APK payload. Generate French/English changelogs and website entries from the simplified JSON source. Validation: shared JS and Tundra unit tests; desktop workflows and persistence; signed APK payload parity; Sargo RAM and installed startup with boot readback. Android runtime qualification is recorded separately. --- CHANGELOG.en.md | 1069 ++++------ CHANGELOG.md | 1093 ++++------ README.fr.md | 8 +- README.md | 8 +- android/app/src/main/AndroidManifest.xml | 1 + .../vulpes_os/preview/AndroidServices.java | 21 + .../org/vulpes_os/preview/MainActivity.java | 11 +- android/extension/background.js | 40 +- android/extension/content.js | 55 + android/extension/manifest.json | 1 + android/tools/check-camera.py | 6 +- android/tools/pack-assets.py | 18 +- .../webapps/camera.gaiamobile.org/js/main.js | 4 + .../js/panels/app_permissions_list/panel.js | 4 +- .../js/panels/screen_lock_passcode/panel.js | 18 +- .../gaia_build_defer_index.js | 2 +- .../style/wallpaper.css | 3 + changelog.json | 776 ++++++- gaia/apps/camera/js/controllers/activity.js | 2 +- gaia/apps/camera/js/controllers/overlay.js | 6 +- gaia/apps/camera/js/controllers/storage.js | 2 +- gaia/apps/camera/js/controllers/viewfinder.js | 1 + gaia/apps/camera/js/lib/dcf.js | 2 +- gaia/apps/camera/js/lib/storage.js | 4 +- gaia/apps/camera/js/views/viewfinder.js | 3 + .../dialer/js/telephony_helper.js | 25 +- gaia/apps/gallery/js/MetadataParser.js | 39 +- gaia/apps/settings/js/modules/date_time.js | 11 +- .../settings/js/modules/dialog_manager.js | 54 +- .../settings/js/modules/dialog_service.js | 53 +- .../settings/js/modules/settings_service.js | 11 + .../app_permissions_list.js | 7 +- gaia/apps/settings/js/panels/help/support.js | 13 +- .../js/panels/screen_lock_passcode/panel.js | 45 +- .../panels/simcard_manager/simcard_manager.js | 4 +- .../settings/js/panels/wifi_status/panel.js | 3 +- gaia/apps/verticalhome/js/stores/item.js | 3 +- gaia/apps/video/js/video.js | 4 +- gaia/apps/wallpaper/style/wallpaper.css | 3 + gaia/shared/js/download/download_helper.js | 8 + host/Downloads.sys.mjs | 30 + host/Input.sys.mjs | 37 +- host/Messages.sys.mjs | 43 +- host/NativeCamera.sys.mjs | 5 +- host/Platform.sys.mjs | 90 +- host/Runtime.sys.mjs | 43 +- host/SettingsAudit.sys.mjs | 133 ++ host/SettingsFollowupAudit.sys.mjs | 56 + host/StorageStats.sys.mjs | 28 + host/Tundra.sys.mjs | 41 +- host/actors/VulpesChild.sys.mjs | 89 +- host/desktop.css | 50 + host/gaia-bluetooth.js | 126 ++ host/gaia-camera.js | 231 ++- host/gaia-compat.js | 51 +- host/gaia-input.js | 14 +- host/gaia-mobile-settings.js | 366 ++++ host/gaia-restoration.js | 175 +- host/gaia-select.js | 206 ++ host/gaia-services.js | 66 +- host/gaia-styles.js | 8 + host/gaia-theme.css | 105 + host/gaia-theme.js | 58 + host/gaia-wifi.js | 104 +- host/platform-ui.js | 17 +- host/shell.js | 17 +- overrides/camera.gaiamobile.org/js/lib/dcf.js | 101 + overrides/camera.gaiamobile.org/js/main.js | 1406 ++++++++----- .../js/MetadataParser.js | 713 +++++++ .../js/metadata_scripts.js | 1777 +++++++++++++++++ overrides/settings.gaiamobile.org/js/main.js | 435 +++- .../js/modules/date_time.js | 419 ++++ .../js/modules/dialog_service.js | 483 +++++ .../js/panels/display/panel.js | 49 +- .../js/panels/feedback_send/panel.js | 492 +++++ .../js/panels/firefox_accounts/panel.js | 508 +++++ .../js/panels/firefox_sync/panel.js | 505 +++++ .../js/panels/help/panel.js | 171 ++ .../js/panels/keyboard_add_layouts/panel.js | 490 +++++ .../js/panels/screen_lock/panel.js | 486 +++++ .../js/panels/simcard_manager/panel.js | 17 + .../js/panels/simpin/panel.js | 502 +++++ .../js/panels/usb_storage/panel.js | 487 +++++ .../js/panels/wifi/panel.js | 1 - .../wifi_select_certificate_file/panel.js | 485 +++++ .../js/panels/wifi_status/panel.js | 50 + .../resources/support.json | 1 + .../shared/js/download/download_helper.js | 693 +++++++ .../gaia_build_defer_index.js | 1177 +++++++++++ package-lock.json | 15 + package.json | 1 + release.json | 6 +- services/activities.mjs | 69 +- services/input-editor.mjs | 68 + services/input.mjs | 36 + services/mms.mjs | 29 + services/platform.mjs | 10 +- tests/ProbeChild.sys.mjs | 15 + tests/activities.test.mjs | 40 +- tests/android-input-bridge.test.mjs | 53 + tests/android-sms-handoff.test.mjs | 8 +- tests/bluetooth.test.mjs | 61 + tests/camera-lifecycle.test.mjs | 136 +- tests/input.test.mjs | 19 +- tests/messages.test.mjs | 10 + tests/mms.test.mjs | 34 + tests/platform.test.mjs | 37 + tests/settings-dialogs.test.mjs | 53 + tests/test_changelog.py | 115 ++ tests/test_publication.py | 4 +- tests/wifi-throttle.test.mjs | 5 + tools/amd-module-range.cjs | 14 + tools/audit-settings.py | 43 + tools/check-parity.py | 20 +- tools/export-changelog.py | 249 +++ tools/prepare-publication.py | 1 - tools/project.py | 7 + tools/refresh-camera-modules.py | 16 + tools/refresh-settings-panels.py | 36 +- tools/serve-gaia.py | 19 +- tools/sync-forgejo-changelog.py | 114 ++ tools/test-appearance.py | 59 + tools/test-call-screen.py | 18 +- tools/test-camera.py | 4 +- tools/test-composer-actions.py | 56 + tools/test-forgejo-feedback.py | 78 + tools/test-gaia-dialogs.py | 50 + tools/test-gaia-keyboard.py | 4 +- tools/test-hotspot-ui.py | 71 + tools/test-notification-swipe.py | 32 + tools/test-settings-followup.py | 25 + tools/test-settings-selector.py | 46 + tundra/droidian/export-runtime.py | 3 +- tundra/droidian/probe.sys.mjs | 228 ++- tundra/droidian/session.py | 1 + tundra/mms-codec/COPYING | 674 +++++++ tundra/mms-codec/go.mod | 10 + tundra/mms-codec/go.sum | 12 + tundra/mms-codec/main.go | 145 ++ tundra/mms-codec/main_test.go | 40 + tundra/runtime/audio-devices.py | 29 + tundra/runtime/bluetooth-control.py | 323 +++ tundra/runtime/camera-preview.py | 15 +- tundra/runtime/clock-service.conf | 3 + tundra/runtime/hal_policy.py | 6 +- tundra/runtime/hotspot-firewall.conf | 3 + tundra/runtime/mms-codec.py | 23 + tundra/runtime/mms-control.py | 326 +++ tundra/runtime/mobile-settings.py | 316 +++ tundra/runtime/prepare-bluetooth.py | 27 +- tundra/runtime/prepare-hardware.py | 40 +- tundra/runtime/prepare-native.py | 12 +- tundra/runtime/radio-control.py | 142 +- tundra/runtime/runtime-packages.py | 31 +- tundra/runtime/setup-hal.py | 4 + tundra/runtime/wifi-control.py | 147 +- tundra/tests/test_bluetooth_control.py | 22 + tundra/tests/test_hotspot.py | 69 + tundra/tests/test_mms_control.py | 69 + tundra/tests/test_mobile_settings.py | 115 ++ tundra/tests/test_network_clock.py | 31 + tundra/tests/test_radio_control.py | 104 + tundra/tests/test_ramdisk.py | 15 + tundra/tests/test_runtime_packages.py | 12 + tundra/tests/test_telephony_diagnostic.py | 65 + tundra/tests/test_wifi_control.py | 22 + tundra/tools/build-rescue-boot.py | 31 +- tundra/tools/diagnose-telephony.py | 116 ++ tundra/tools/refresh-boot-ui.py | 117 ++ tundra/tools/shared_ui.py | 51 + 170 files changed, 19733 insertions(+), 2530 deletions(-) create mode 100644 host/Downloads.sys.mjs create mode 100644 host/SettingsAudit.sys.mjs create mode 100644 host/SettingsFollowupAudit.sys.mjs create mode 100644 host/StorageStats.sys.mjs create mode 100644 host/gaia-bluetooth.js create mode 100644 host/gaia-mobile-settings.js create mode 100644 host/gaia-select.js create mode 100644 host/gaia-theme.css create mode 100644 host/gaia-theme.js create mode 100644 overrides/camera.gaiamobile.org/js/lib/dcf.js create mode 100644 overrides/gallery.gaiamobile.org/js/MetadataParser.js create mode 100644 overrides/gallery.gaiamobile.org/js/metadata_scripts.js create mode 100644 overrides/settings.gaiamobile.org/js/modules/date_time.js create mode 100644 overrides/settings.gaiamobile.org/js/modules/dialog_service.js create mode 100644 overrides/settings.gaiamobile.org/js/panels/feedback_send/panel.js create mode 100644 overrides/settings.gaiamobile.org/js/panels/firefox_accounts/panel.js create mode 100644 overrides/settings.gaiamobile.org/js/panels/firefox_sync/panel.js create mode 100644 overrides/settings.gaiamobile.org/js/panels/help/panel.js create mode 100644 overrides/settings.gaiamobile.org/js/panels/keyboard_add_layouts/panel.js create mode 100644 overrides/settings.gaiamobile.org/js/panels/screen_lock/panel.js create mode 100644 overrides/settings.gaiamobile.org/js/panels/simcard_manager/panel.js create mode 100644 overrides/settings.gaiamobile.org/js/panels/simpin/panel.js create mode 100644 overrides/settings.gaiamobile.org/js/panels/usb_storage/panel.js create mode 100644 overrides/settings.gaiamobile.org/js/panels/wifi_select_certificate_file/panel.js create mode 100644 overrides/settings.gaiamobile.org/js/panels/wifi_status/panel.js create mode 100644 overrides/settings.gaiamobile.org/resources/support.json create mode 100644 overrides/settings.gaiamobile.org/shared/js/download/download_helper.js create mode 100644 overrides/video.gaiamobile.org/gaia_build_defer_index.js create mode 100644 services/input-editor.mjs create mode 100644 services/input.mjs create mode 100644 services/mms.mjs create mode 100644 tests/android-input-bridge.test.mjs create mode 100644 tests/bluetooth.test.mjs create mode 100644 tests/mms.test.mjs create mode 100644 tests/settings-dialogs.test.mjs create mode 100644 tests/test_changelog.py create mode 100644 tools/amd-module-range.cjs create mode 100644 tools/audit-settings.py create mode 100644 tools/export-changelog.py create mode 100644 tools/refresh-camera-modules.py create mode 100644 tools/sync-forgejo-changelog.py create mode 100644 tools/test-appearance.py create mode 100644 tools/test-composer-actions.py create mode 100644 tools/test-forgejo-feedback.py create mode 100644 tools/test-gaia-dialogs.py create mode 100644 tools/test-hotspot-ui.py create mode 100644 tools/test-notification-swipe.py create mode 100644 tools/test-settings-followup.py create mode 100644 tools/test-settings-selector.py create mode 100644 tundra/mms-codec/COPYING create mode 100644 tundra/mms-codec/go.mod create mode 100644 tundra/mms-codec/go.sum create mode 100644 tundra/mms-codec/main.go create mode 100644 tundra/mms-codec/main_test.go create mode 100644 tundra/runtime/audio-devices.py create mode 100644 tundra/runtime/bluetooth-control.py create mode 100644 tundra/runtime/clock-service.conf create mode 100644 tundra/runtime/hotspot-firewall.conf create mode 100644 tundra/runtime/mms-codec.py create mode 100644 tundra/runtime/mms-control.py create mode 100644 tundra/runtime/mobile-settings.py create mode 100644 tundra/tests/test_bluetooth_control.py create mode 100644 tundra/tests/test_hotspot.py create mode 100644 tundra/tests/test_mms_control.py create mode 100644 tundra/tests/test_mobile_settings.py create mode 100644 tundra/tests/test_network_clock.py create mode 100644 tundra/tests/test_telephony_diagnostic.py create mode 100644 tundra/tools/diagnose-telephony.py create mode 100644 tundra/tools/refresh-boot-ui.py create mode 100644 tundra/tools/shared_ui.py diff --git a/CHANGELOG.en.md b/CHANGELOG.en.md index d88e6361..debb7487 100644 --- a/CHANGELOG.en.md +++ b/CHANGELOG.en.md @@ -2,805 +2,546 @@ [Français](CHANGELOG.md) -One Gaia base, three platforms: **desktop**, **Android (APK)** and **Tundra / Sargo**. -This journal covers visible changes and major technical milestones. +Desktop, Android (APK) and Tundra/Sargo. Status indicates development sources, a built APK or a published release. Source changes require an update to the installed package. -**Status:** “In development” refers to local sources, “APK built” to a test package, -and “Published” to a public repository release. Shared source changes reach installed -APKs and phone images only after those packages are updated. + -

Gaia Settings, system information screen

-

Gaia Settings · screenshot from the desktop version.

+## 2.7-preview.20 — APK built · 13 October 2026 + +- ![Change](https://img.shields.io/badge/Change-64748B?style=flat-square) Preview 20 brings together shared fixes since APK Preview 17: Gaia settings, appearance, selectors, keyboard, contacts, Marketplace and media. +- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Camera: WebM recording with posters and Gallery/Video playback. Microphone permission requested; controls follow hardware capabilities. +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Camera: more reliable sensor switching, stopping and resuming. Desktop and Android use their camera APIs; Sargo retains its HAL adapter. +- ![Change](https://img.shields.io/badge/Change-64748B?style=flat-square) Sargo: Wi-Fi, Bluetooth, calls, SMS/MMS and network settings updates included in the sources. Unimplemented functions remain identified in their panels. +- ![Change](https://img.shields.io/badge/Change-64748B?style=flat-square) Changelog: simplified French/English text and badges retained; desktop/APK/Sargo filters independent of headings. Markdown, website and release notes generated from one JSON. +- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) APK signed with the previous Preview key; alignment and consistency of 53 shared files verified. Android runtime validation in progress. +- ![Note](https://img.shields.io/badge/Note-6B7280?style=flat-square) Desktop/Sargo Gecko 156.0.1; Android GeckoView 156.0. Gaia 2.7.0. The APK delegates calls/SMS to Android; it does not replace Android telephony. +- ![Note](https://img.shields.io/badge/Note-6B7280?style=flat-square) Experimental Preview: Mozilla account/Sync, remote location, Firefox OS add-ons and USB file transfer remain incomplete. Sargo: preview around 16 fps; recorded sound and call audio require separate qualification. +- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Pixel 3a: Preview 20 r62 RAM-booted, then flashed to boot_a. Matching readback hash; installed startup, eleven Gaia checks and ten native checks passed. Data retained, Wi-Fi connected and LTE registration observed. No new two-way audio call tested. + + + +## In development — 12 October 2026 — Video and clock settings + +- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Camera: WebM VP8/Opus recording with microphone permission, poster and storage. Buffer limited to 32 MiB; recording stops before releasing the sensor. +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Camera, Gallery and Video: handle WebM files and their posters. +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Settings: original Gaia date/time pickers, Back from built-in keyboard settings and unclipped select labels. +- ![Change](https://img.shields.io/badge/Change-64748B?style=flat-square) Improve Vulpes: link to project issues. Unavailable services explained in their panels. +- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Desktop: simulated 1920 × 1080 video decoded; photo, gallery, camera resume and navigation checked. +- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Sargo r61 in RAM: 480 × 640 video decoded, eight front/rear switches passed. Test media deleted; recorded audio not listened to. +- ![Change](https://img.shields.io/badge/Change-64748B?style=flat-square) Sargo: preview measured around 16 fps versus 12 in the previous test. 30 fps is not reached yet. +- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Sargo: automatic NITZ time, persistent setting and guarded manual writes. Sync depends on carrier time; no NTP fallback, timezone selected manually. +- ![Note](https://img.shields.io/badge/Note-6B7280?style=flat-square) Android: assets staged and Java compiled, without a new APK or Android video test at this stage. Mozilla account/Sync, remote location, Firefox OS add-ons and USB file transfer remain unimplemented. No flashing or publication. + + + +## In development — 12 October 2026 — Calls and Wi-Fi sharing + +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Gaia dialogs: close after opening, complete interrupted transitions and allow retry after rejected validation. +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Sargo: longer carrier-service timeout; slow settings separated from voice/SMS commands. +- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Sargo: call waiting, caller ID and four forwarding rules connected to oFono, with validation and readback. +- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Sargo: WPA2 or open Wi-Fi hotspot, NetworkManager configuration and compatible firewall. Private password, accessible only to Settings. +- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) r59 in RAM: 69 panels, dialogs and APNs checked on desktop/Pixel. Hotspot saved, started and stopped through Gaia; previous Wi-Fi restored. 44 Python tests and dialog regressions passed. +- ![Note](https://img.shields.io/badge/Note-6B7280?style=flat-square) Internet forwarding to another device untested. Voicemail, call barring, PIN/PUK, Wi-Fi certificates and USB tethering remain open. Android assets synchronized; no new APK, flashing or publication. + + + +## In development — 12 October 2026 — Network settings + +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Wi-Fi: actual IPv4 address and link speed; no “undefined” label when a measurement is missing. +- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Sargo: GSM/UMTS/LTE selection and editing of existing Internet/MMS APNs, with validation and readback. +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) APNs: reject changes to active profiles, protect IMS and passwords, attempt rollback after failure and report its result. +- ![Change](https://img.shields.io/badge/Change-64748B?style=flat-square) Sargo: bounded queue for slow settings. Call status read in 20 ms during a five-second settings wait. +- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) 38 Python tests, 69 desktop panels and APN/network workflows on RAM-booted Pixel checked. Android assets synchronized. +- ![Note](https://img.shields.io/badge/Note-6B7280?style=flat-square) APN creation, deletion and reset unavailable. Manual carrier selection and certificates still open. No new APK, flashing or publication. + + + +## In development — 12 October 2026 — Settings audit + +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Settings: recover navigation after a controller or dialog fails; consistent module generation. +- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Accessibility filters and Vulpes support. Original panels retained with explanations for unavailable features. +- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Desktop: profile statistics and Gecko downloads. Android: private storage statistics. +- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Sargo: mobile data, roaming, clock and hidden Wi-Fi connected. Legacy network errors in Sound settings fixed. +- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) 97 views inventoried; 69 desktop/Pixel panels opened and seven native dialogs checked. Storage, data and timezone read back after writes on r51 in RAM. +- ![Note](https://img.shields.io/badge/Note-6B7280?style=flat-square) Contextual views and unavailable services are not all qualified. Android Java compiled, without a new APK. No flashing, ticket closure or publication. + + + +## In development — 12 October 2026 — Settings navigation + +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Dropdowns: Gaia selector with confirm/cancel; Light/Dark choice accessible. SIM manager adapted to one slot. +- ![Change](https://img.shields.io/badge/Change-64748B?style=flat-square) Unavailable panels: explanations and disabled controls instead of unresponsive links. +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Sargo: saved Wi-Fi networks separated from slow scans; cache preserved during status reads. +- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Sargo: SIM information, SMS centre and delivery reports connected to the modem. +- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) r45 in RAM: eight appearance checks, five panels and actual SMS report setting changes passed, then setting restored. Desktop selector checked; APK assets staged. +- ![Note](https://img.shields.io/badge/Note-6B7280?style=flat-square) Enterprise certificates and other carrier options still unavailable. No SMS sent, new APK, flashing or publication. + + + +## In development — 12 October 2026 — Shared fixes on Pixel 3a + +- ![Change](https://img.shields.io/badge/Change-64748B?style=flat-square) Sargo: r39 RAM boot with shared fixes and initial dark mode. r35 kernel and hardware configuration preserved. +- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Boot, viewport size, persistent settings, battery, call UI and Fira checked. Modem, audio, Wi-Fi and Bluetooth services active. +- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Seven Gaia appearance checks passed: propagated theme, readable headers, aligned labels and return to light mode. Pixel screenshot inspected. +- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Tool to update Gaia in a private boot image, with hashes, retained sources and checks for unchanged hardware components. +- ![Note](https://img.shields.io/badge/Note-6B7280?style=flat-square) No new call, SMS or pairing tested. Full theme and Android behavior still need qualification. No new APK, flashing or publication. + + + +## In development — 11 October 2026 — Appearance and shared keyboard + +- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Appearance: Light, Dark or Follow system, shared between apps and retained across restarts. Wallpapers, photos and external websites preserved. +- ![Change](https://img.shields.io/badge/Change-64748B?style=flat-square) Keyboard: shared editing operations, selection and deletion, protection against late replies. +- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Desktop: nine appearance checks and Messages input gestures passed, without sending SMS. Visual review of Settings, Messages, Phone and Contacts. +- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Android: experimental Gaia keyboard option; Android keyboard remains the default, input mode restored on close. +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Android: briefly retry the same busy sensor, without retrying after permission denial or closing Camera. +- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Preview 19 built locally: signature, alignment, parity and bridge/camera tests passed. +- ![Note](https://img.shields.io/badge/Note-6B7280?style=flat-square) Gaia keyboard, Android front camera and full theme still need qualification. Sources ready for Sargo, which remained on r35 at this stage. No upload, flashing or ticket closure. + + + +## In development — 11 October 2026 — Settings and homescreens + +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Settings: ringtone management, PIN entry/deletion, lock disabling and third-party app permission list. +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Home: first initialization of the second launcher and wallpaper previews. +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Languages: refresh Vulpes labels after language changes; English fallback outside French. Dismiss camera failure panels and installation banners. +- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Android: open the Gaia tray by tapping the status bar. Preview 18 built, signed and compared with sources. +- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Desktop: nine Gaia workflows, five banner checks and twelve activity/camera tests passed. Sargo r35 Bluetooth confirmed by the user. +- ![Note](https://img.shields.io/badge/Note-6B7280?style=flat-square) Compositor hot restart still unstable: files restored and return to r35. Fixes not yet qualified on Sargo at this stage; Android keyboard/theme open. No APK publication or flashing. + + + +## In development — 9 October 2026 — MMS and Bluetooth + +- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Bluetooth: shared Gaia adapter, separate hardware commands and access restricted to authorized apps. +- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Sargo: power, discovery, explicit pairing, connection and removal from Gaia Bluetooth settings. Bluetooth audio modules included. +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Sargo: Qualcomm network services and MMS transport connected. D-Bus disconnect no longer terminates the MMS daemon. Persistent state, retry without duplicates or uncertain automatic resends. +- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) r35 in RAM: image MMS visible and accepted by Orange MMSC; recipient delivery still unconfirmed. Five Bluetooth devices discovered, A2DP to desktop verified. +- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) 191 Tundra Python tests and 18 JavaScript tests passed. Smaller ramdisk and synchronized Android assets. +- ![Note](https://img.shields.io/badge/Note-6B7280?style=flat-square) Headset call audio unqualified. No new APK, flashing or push at this stage. + + + +## In development — 8 October 2026 — Camera and MMS transport + +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Camera: stop the stream before detaching video; await sensor closure, reset the viewfinder and recalculate size after rotation. +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) MMS: preserve Blob attachments and keep messages pending until transport confirmation. +- ![Change](https://img.shields.io/badge/Change-64748B?style=flat-square) Sargo: reused WebGL viewfinder without intermediate video capture; screen stays awake during preview. +- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Desktop and Sargo: Camera/Gallery workflows and eight native switches checked. JPEG, focus and zoom working; rear ~16 fps, front ~25–27 fps in this test. +- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Sargo: persistent MMS queue, Nuntium codec and oFono transport. Text/photo UI tested with simulated transport. +- ![Note](https://img.shields.io/badge/Note-6B7280?style=flat-square) Mobile data blocked carrier MMS at this stage. Android assets synchronized; no new APK, flashing or push. + + + +## 8 October 2026 — Call-log redial and error dialogs + +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Gaia dialogs: visible text instead of a grey screen with a lone button. +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Call log: redial through the same service as the keypad, without legacy Gecko APIs. Shared desktop/Android/Sargo file. +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) MMS: clearly report unavailable sending, retain text and attachments, no false airplane-mode message. +- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Desktop: FR/EN dialogs and preserved composer. RAM-booted Sargo: 11 Gaia checks passed, redial tested with a simulated modem. +- ![Note](https://img.shields.io/badge/Note-6B7280?style=flat-square) No real call or audio validation at this stage. Android assets synchronized, no new APK published. + + + +## In development — 8 October 2026 — SMS and audio + +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) SMS: send in one tap while keeping the keyboard. Paperclip connected to the Gaia image, audio, video or contact menu. +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) UI: visible volume indicator, lockscreen gestures and notification swiping fixed. +- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Desktop: keyboard, attachments, Gallery, call UI and volume checked; notification swiped without moving the page. No new APK published. +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Sargo: PulseAudio access, Qualcomm relay, D-Bus protocol and calibration devices restored. Conflicting Android audio service removed. +- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Sargo: 11 Gaia checks, calibration and rear/front/rear previews passed. +- ![Note](https://img.shields.io/badge/Note-6B7280?style=flat-square) Calls failed before ringing with a network error in this test. Two-way audio, Pixel gesture and MMS still unverified. No flashing or publication. + + + +## In development — 8 October 2026 — Telephony reliability + +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Sargo: radio service survives client closure. No automatic new call or SMS after a lost response. Errors exclude phone numbers and message text. +- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Network/audio diagnostics without personal identifiers, and pinned carrier settings in the native image. +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) SMS: wait for the actual Gaia manager and accept uppercase oFono IDs. Conversation checks restored without resending. +- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Sargo: LTE restored after reboot, SMS confirmed sent by the modem, non-silent microphone and audio playback accepted. +- ![Note](https://img.shields.io/badge/Note-6B7280?style=flat-square) Recipient delivery, two-way call audio and modem stability remain unqualified. Compositor hot restart unstable. No new APK, flashing or push. ## 8 October 2026 — Public source update -### Shared · Desktop / Android / Tundra - -- Bring recent camera, Contacts, SMS and Marketplace fixes, plus Sargo build and recovery tools, into the public sources. Update both READMEs with verified capabilities and limitations. -- Make the Android parity check create its report directory in a fresh checkout. Verify that native Wi-Fi configuration ships an empty connections directory. -- Keep tickets, roadmaps, internal documents, personal data and keys outside the repository. - -### Packages and qualification - -- The public APK remains Preview17; the 8 October fixes require a future build. No new native image or flash in this step. -- At the latest Sargo check, cellular registration is still searching; two-way call audio, Bluetooth pairing and profiles remain to be qualified. +- ![Change](https://img.shields.io/badge/Change-64748B?style=flat-square) Published Camera, Contacts, SMS and Marketplace fixes, plus Sargo build and recovery tools. +- ![Change](https://img.shields.io/badge/Change-64748B?style=flat-square) Updated French and English READMEs with verified features and limitations. +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Create the report directory when checking Android asset parity in a fresh checkout. +- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Checked native Wi-Fi configuration: no bundled connection profiles. +- ![Note](https://img.shields.io/badge/Note-6B7280?style=flat-square) Internal documents, personal data and keys excluded from the repository. +- ![Note](https://img.shields.io/badge/Note-6B7280?style=flat-square) Public APK unchanged (Preview17); October 8 fixes not included. No new native image published. +- ![Note](https://img.shields.io/badge/Note-6B7280?style=flat-square) Sargo: modem still searching for a network; call audio, Bluetooth pairing and profiles await validation. ## 8 October 2026 — Camera and Contacts workflows · in development -### Shared · Gaia / Desktop / Android / Tundra - -- Serialize camera opening, await the previous sensor shutdown and release tracks and video resources after failures. Keep stable front/rear identities on desktop and Android. -- Preserve binary images across desktop/Tundra transport; explicitly encode contact photos and dates for Android's JSON bridge. -- Refresh contact details after editing. Restore Contacts → SMS, number picking and prefilled dialer activities, preserving nested requests and Gaia entry points. -- Reopen Gallery in picker mode when adding a contact photo, even when Gallery is already open for browsing. Selection, cropping and photo persistence verified on desktop. -- Add camera lifecycle, contact persistence, permissions, binary transport and activity routing tests. - -### Desktop - -- Verify the original Camera → JPEG capture → Gallery → photo opening → camera workflow. -- Verify Gaia contact creation, editing, detail refresh, accent-insensitive search and empty results, favourites, SMS recipient, number picker, prefilled keypad, cancelled deletion and confirmed deletion. The test sends no message and makes no call; its temporary contact is removed. - -### Tundra · Sargo - -- Verify on a RAM-booted Pixel 3a: autofocus, zoom, four front/rear switches with fresh frames, shutter capture, JPEG persistence, Gallery opening and sensor shutdown on Home. -- Check stored conversations, keyboard and notifications. Wi-Fi scan finds four networks. New SMS transmission/reception and call audio are still being qualified. -- Add SHA256-pinned BlueZ packages and required libraries. The controller starts, powers on and discovers; pairing through Gaia and Bluetooth profiles are not yet validated. -- Add native trusted certificates and verify HTTPS without disabling TLS. Test corrupted archives, file collisions and repeatable staging. - -- Include the corrected Contacts files in the boot build: contact creation, editing, SMS handoff, picking and dialer prefilling also pass using touch gestures on Sargo. - -### Android · APK - -- Publish the existing Preview17 APK and verify its download and SHA256. It predates the 8 October fixes. -- Fix recipient and body handoff from Gaia to the Android SMS app, including replies and a typed recipient that was not yet committed. Bridge tests pass; real Android validation is still required. -- Repackage corrected shared resources for the next build; no APK containing these new fixes has been published. - -**Status:** native qualification builds booted in RAM. No flashing, source push or private documentation publication in this step. Full phone qualification remains open. +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Camera: corrected sequential opening, sensor cleanup and front/rear identification. +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Contacts: corrected photo and date transport on desktop, Android and Tundra. +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Contacts: restored refresh after editing, SMS handoff, number picking and dialer prefilling. +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Gallery: open the contact photo picker even when the app is already running. +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Sargo: include corrected Contacts files in the boot image. +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Android: pass recipients and text to the SMS app, including replies and unconfirmed typed numbers. +- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Tests for camera lifecycle, contact storage, permissions, binary transport and activities. +- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Desktop: verified JPEG capture, Gallery, contact photos, creation, editing, search, favourites and deletion. No calls or SMS sent. +- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Sargo in RAM: verified autofocus, zoom, four camera switches, capture and Gallery; tested Contacts with touch input. +- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Sargo: checked stored conversations, keyboard and notifications; detected four Wi-Fi networks. New SMS and call audio await validation. +- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Sargo: BlueZ and dependencies pinned by SHA256. Power-on and discovery tested; pairing and profiles await validation. +- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Sargo: native certificate store. Tested HTTPS, corrupted archives, file collisions and repeated builds. +- ![Note](https://img.shields.io/badge/Note-6B7280?style=flat-square) Published Preview17 with download and SHA256 checks. These fixes remain in source; Android SMS needs a real-device test. No Sargo flash. ## 6 October 2026 — Embedded Marketplace theme · in development -### Shared · Gaia / Desktop / Android / Tundra - -- Open the catalogue with `mode=app`, retained in navigation, search, filters, app details and language switches. -- Prepare a dedicated Vulpes web theme with local Fira fonts, compact navigation, touch-friendly controls, readable cards, collapsible filters and scrolling screenshots. Omit website headers in embedded mode while preserving the standard website layout. -- Keep confirmations and system messages in Gaia. The theme grants catalogue pages no additional privileges. -- Check 16 French/English pages, 2 standard pages and theme assets. Inspect mobile renders at 320 and 360 pixels. Desktop and Sargo launchers updated; shared sources will be included in the next Android packaging run. - -Website files prepared locally, not deployed to the public server. No push or new APK/ROM publication. +- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Marketplace: embedded theme with local Fira fonts, compact navigation, touch controls, collapsible filters and scrolling screenshots. +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Preserve `mode=app` across navigation, search, filters and language changes. +- ![Change](https://img.shields.io/badge/Change-64748B?style=flat-square) Hide website headers in embedded mode. Gaia handles confirmations; the catalogue receives no extra privileges. +- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Checked 16 French/English pages, 2 standard pages and mobile layouts at 320 and 360 pixels. +- ![Note](https://img.shields.io/badge/Note-6B7280?style=flat-square) Theme ready locally, not deployed. Desktop and Sargo launchers updated; Android integration pending the next APK. ## 6 October 2026 — Gaia banners · in development -### Shared · Gaia / Desktop / Android / Tundra - -- Dismiss the “Application installed” banner after its normal timeout even if the animation-end event does not reach the Gaia component host. Keep its appearance and tap-to-dismiss behavior. -- Cancel old timers when a new message replaces the previous one, preventing premature dismissal. -- Check display, expiry, replacement and click dismissal on desktop; verify actual removal after five seconds on the Pixel. Fix deployed to the Sargo RAM trial and available in shared sources for the next APK. - -No flash or publication in this step. +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Automatically dismiss the “Application installed” banner even without an animation-end event. +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Cancel old timers when a new banner replaces the previous one. +- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Desktop: checked display, expiry, replacement and click dismissal. Sargo: verified dismissal after five seconds in RAM. +- ![Note](https://img.shields.io/badge/Note-6B7280?style=flat-square) Fix available in shared sources for the next APK. No flash or release. ## 6 October 2026 — Wi-Fi and Settings panels · in development -### Shared · Gaia / Desktop / Android / Tundra - -- Complete Settings panel navigation even when Gecko does not emit an animation-end event. Preserve Gaia transitions and prevent duplicate initialization. -- Updated shared sources and the desktop server; included in the next Android packaging run. No new APK published in this step. - -### Tundra · Sargo - -- Fix Wi-Fi signal events: include the associated network and do not emit connection information while disconnected. The incomplete event prevented Gaia from displaying scan results. -- Check the driver, native service, Gecko bridge and corrected panel on the Pixel: four access points detected, grouped into two networks, with no scan error. -- Wi-Fi connection confirmed by the user; a real HTTPS request from Gecko to example.org passed (HTTP 200, certificate verification enabled). Fixes applied to the RAM trial; no flash or Git push. +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Settings: open panels even without an animation-end event, without duplicate initialization. +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Sargo: corrected Wi-Fi events to restore network results and report disconnection properly. +- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Sargo: detected four access points grouped into two networks. Connection confirmed and HTTPS request passed with certificate verification. +- ![Note](https://img.shields.io/badge/Note-6B7280?style=flat-square) Shared sources, desktop server and Sargo RAM trial updated. Fixes not included in the public APK. ## 6 October 2026 — Marketplace · in development -### Desktop / Tundra · Sargo - -- Keep the catalogue below the status bar and inside its owning application. Hide its web view on Home and in the task switcher; remove it when the app closes. -- Prevent a cancelled navigation or a previous error page from hiding a retry. -- Show a local error message with a retry action when the catalogue is unavailable, instead of leaving its error page over Gaia. -- Desktop navigation, network failure, retry, Home and closing from the task switcher checked. Seven direct checks pass on Sargo in RAM without a debugger window, using a local page and a forced-unlock precondition; the remote catalogue requires an Internet connection on the phone. - -### Android · APK - -- Rebuild the local Preview17 APK with the shared Marketplace error message. Signature, alignment and asset parity checked; no new Android device regression run in this step. - -Nothing published in this step. +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Desktop/Sargo: keep Marketplace below the status bar, hide it on Home and remove it when closed. +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Cancelled navigation or an old error page no longer hides a retry. +- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Local error message with a Retry button when the catalogue is unavailable. +- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Desktop: verified navigation, network errors, retry, Home and closing. +- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Sargo in RAM: seven checks passed with a local page and forced unlock. Remote catalogue not validated without Internet access. +- ![Change](https://img.shields.io/badge/Change-64748B?style=flat-square) Rebuilt local Preview17 APK; checked signature, alignment and assets. No new Android device test or release. ## 2.7-preview.17 — APK built · 5 October 2026 -### Shared · Gaia / Desktop - -- Restore Marketplace on Home with its original icon and the Vulpes catalogue. Select French for fr languages and English otherwise. -- Turn catalogue downloads into confirmed installations: validate ZIP archives, add the application to Home and support launch and removal. -- Store installed applications separately from Gaia sources, on distinct origins and without system privileges inherited from archived manifests. -- Validate the real desktop workflow with 2048: catalogue, confirmation, installation, launch, persistence and removal. Check damaged archives, invalid paths, limits, cancellation and single-use confirmations. - -### Android · APK - -- Build and sign Preview17 with the same Marketplace and installation service. Use the existing GeckoView browser for the catalogue and private Android storage for packages. -- Verify the Install click, confirmation, Home icon and 2048 launch without access to the Vulpes system bridge on an Android 16/API36 emulator. Also verify persistence after force-stop/relaunch and removal. - -### Tundra · Sargo - -- Prepare a local boot image containing Marketplace, the installer and the added-application server. RAM boot performed on the Pixel on 6 October: Home and Marketplace resources verified, without flashing. Nine of ten system checks pass; the overall Gaia check records a viewport change during unlocking. Package installation on Sargo still needs qualification. - -**Limitations:** initial support for packaged web applications. Old privileged APIs, hosted apps and discontinued services are not restored automatically. Nothing published to Forgejo or the website. +- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Marketplace on Home with its original icon and the Vulpes catalogue in French or English. +- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Confirmed ZIP app installation, launch from Home and removal. +- ![Change](https://img.shields.io/badge/Change-64748B?style=flat-square) Installed apps isolated from Gaia sources and system privileges. +- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Desktop: full 2048 workflow plus archive, path, limit, cancellation and single-use confirmation checks. +- ![Change](https://img.shields.io/badge/Change-64748B?style=flat-square) Preview17 APK built and signed with Marketplace, GeckoView and private package storage. +- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Android 16/API 36 emulator: 2048 installation, launch, isolation, persistence after app restart and removal. +- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Sargo in RAM on October 6: Home and Marketplace assets verified, 9 of 10 system checks passed. Viewport changes on unlock; package installation awaits validation. +- ![Note](https://img.shields.io/badge/Note-6B7280?style=flat-square) Initial support for packaged web apps. Legacy privileged APIs and hosted apps not restored. No release at this stage. ## 2.7-preview.16 — APK built · 5 October 2026 -### Shared · Gaia / Desktop - -- Restore Gaia wallpaper and sound pickers, return navigation to Settings and cancellation through Home. Share persisted image and sound transport between desktop and Android. -- Propagate language changes to Home without granting general settings read access. Check French/English labels and persistence. -- Restore horizontal page snapping and hide development apps from Home without deleting their sources. -- Use the supplied Vulpes logo in the splash and adapt the original sound-list components to the current engine. -- Verify two consecutive wallpaper changes, sound selection and restored Gaia panels on desktop. - -### Android · APK - -- Build and sign local Preview16; verify shared payload and version. Nothing published in this step. -- Validate Vulpes as the HOME launcher, restore Gaia locking after sleep and Android Back navigation from the lockscreen camera. -- Hide Android system bars in immersive mode while retaining temporary swipe access. -- Android 16/API36 emulator checks: locking, camera/Back, French/English, Home without test apps, wallpaper and sound. Wallpaper retained after force-stop/relaunch with no repeated changes over 15 seconds. Other Android devices not qualified in this step. - -### Tundra · Sargo - -- Include shared fixes and app policy in a new test boot image. The previous boot reapplied an old file snapshot, undoing hot updates. -- RAM boot on Pixel passed: six language/Home/picker checks and ten system checks. Image/sound picker results verified without replacing personal selections. Installed boot remains Preview15; no partition flashed in this step. - -**Limitations:** third-party FFAA installation remains open. The site depends on old mozApps APIs and a nomodule script; an installer with application and permission isolation still needs implementation. Selecting a Gaia sound does not change the global Android ringtone; actual call audio was not qualified here. +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Restored Gaia wallpaper and sound pickers, return to Settings and cancellation through Home. +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Apply language changes to Home and preserve them after restart. +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Restore horizontal page snapping; hide development apps on Home. +- ![Change](https://img.shields.io/badge/Change-64748B?style=flat-square) Vulpes startup logo and sound list adapted to the current engine. Media transport shared by desktop and Android. +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Android: Gaia locking after sleep and Back from the lockscreen camera. HOME launcher mode validated. +- ![Change](https://img.shields.io/badge/Change-64748B?style=flat-square) Android: hide system bars in immersive mode, with swipe access. Preview16 APK built and signed. +- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Desktop and Android 16/API 36 emulator: checked wallpapers, sounds and panels; tested language, locking and wallpaper persistence on Android. +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Sargo: updated the boot image to stop restoring outdated files. +- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Sargo in RAM: 6 UI and 10 system checks passed. Installed build remains Preview15, without flashing. +- ![Note](https://img.shields.io/badge/Note-6B7280?style=flat-square) APK not published. FFAA installation and call audio await validation; Gaia sound selection does not change the Android ringtone. ## In development — 4 October 2026 — Standalone Sargo installation -### Tundra · Sargo - -- Build a fresh ext4 volume directly for userdata, including the system, writable data and vendor/modem hardware images. Startup no longer looks for files in a Droidian installation; installation requires neither an Android session nor ADB. -- Python Fastboot installer checks the Sargo model, unlock state, partition sizes and checksums, then replaces userdata, dtbo_a, vbmeta_a and boot_a. The bootloader stays unlocked and device-specific calibration is retained. -- Full offline userdata backup, complete comparison against the phone and recovery archive verification before flashing. Backups remain private; an actual restoration was not performed in this step. -- Two successful boots from internal storage: eleven Gaia and ten native checks per boot, verified boot-image readback and retained settings. Slot A is marked successful only after validating Gaia startup in the current boot. -- Generic variant without an SSH server or maintenance keys: actual boot_a flash and boot passed. The development variant was then restored on the Pixel for further work. Generic package images retain their original fresh state. -- Final checks on the standalone development boot passed for Camera (including lens switches), the Gaia keyboard and notifications. -- Local Fastboot package assembled with checksums and a qualification report. 44 targeted tests pass. It is not published yet: provenance and redistribution of some hardware components still need final review. -- Limits: qualified on one Pixel 3a, not every firmware combination; an 8 GiB volume including 1 GiB of writable data, without automatic expansion. Hardware functionality remains experimental. - -### Desktop / Android · APK - -- No interface changes or new binary in this step. Gaia remains shared by all three platforms. +- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Sargo: standalone ext4 userdata volume with system, data and vendor/modem components. Installation independent of Droidian and ADB. +- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Fastboot installer with model, unlock, size and checksum checks. Replaces userdata, dtbo_a, vbmeta_a and boot_a; retains calibration. +- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Full userdata backup and recovery archive verified before flashing. Actual restoration not tested. +- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Two installed boots passed: 11 Gaia and 10 native checks per boot, retained settings and matching boot readback. +- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Variant without SSH or maintenance keys booted after flashing. Development variant reinstalled; camera, keyboard and notifications checked. +- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Local Fastboot package assembled; 44 targeted tests passed. +- ![Note](https://img.shields.io/badge/Note-6B7280?style=flat-square) Package unpublished: hardware component provenance and redistribution pending. Tested on one Pixel 3a; 8 GiB volume with 1 GiB writable, without automatic expansion. ## In development — 3 October 2026 — Per-device Sargo installation -### Tundra · Sargo - -- Read-only collection of six boot partitions and existing storage, bound to the phone identity and slot A. Backups remain private. -- Portable device workspace: RAM builds, boot checks, installation and recovery export can use it without the development Pixel’s shared inventory. Workspace-bound builds reject implicit fallback to that inventory. -- Checked SSH transfers verify identity, storage and checksums and create files without replacing existing data. The transfer tool never formats, flashes or reboots. -- Build and transfer tested on the Pixel. First RAM boot passed eleven Gaia and ten native checks; keyboard and notifications passed. Camera testing encountered an autofocus timeout, retained in the report. -- Second boot of the same image retained the Gaia setting and passed eleven Gaia and ten native checks; camera, keyboard and notifications passed. The first autofocus timeout remains documented, not declared fixed. -- Verify return to the installed build: Gaia starts and the boot partition is unchanged. A repeated transfer correctly refuses to replace existing data. -- Refine HAL provenance: identify the installed package and download recipe and retrieve historical UBports build recipes. Exact correspondence with the old Android artifact and resolved source manifest remains unverified. -- Still limited to the qualified kernel and compatible ext4/LVM storage. This does not yet provide direct installation from stock Android. - -### Desktop / Android · APK - -- No interface changes or new build in this step. +- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Sargo: portable device workspace for builds, installation and recovery, independent of the development Pixel inventory. +- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Read-only collection of six boot partitions and storage, bound to the phone identity. Private backups. +- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) SSH transfer with identity, storage and checksum checks, without overwriting, formatting or flashing. +- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Two RAM boots: 11 Gaia and 10 native checks passed each; setting retained. Autofocus timed out on the first trial; camera worked on the second. +- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Return to the installed build verified; boot unchanged and data overwrite rejected. +- ![Note](https://img.shields.io/badge/Note-6B7280?style=flat-square) Autofocus timeout not considered fixed. Exact HAL provenance still pending; qualified kernel and compatible ext4/LVM storage required, without direct installation from stock Android. ## In development — 3 October 2026 — Sargo preparation and recovery tools -### Tundra · Sargo - -- Add local package preparation: verify manifest and archive checksums, bound decompression, check ext4 and create a fresh data image for each installation. Existing directories are never overwritten. -- Add a private recovery kit that can be exported outside the checkout. It retains the original boot_a backup and a standalone tool that checks model, serial number, slot and checksums before explicit restoration. It is not a user-data backup. -- Existing restoration no longer requires the diagnostic image to remain available. Backup validation is retained. -- System builds now require an explicit ARM64 hardware reference and reject display-only references or missing required services. -- Prepare the real system archive and verify the recovery kit locally. No new flash or public ROM release; automated installation on another Sargo still needs completion. - -### Shared · Desktop / Android / Tundra - -- Retain the shared JSON changelog in future source publications; internal Markdown working documents remain excluded. - -### Desktop / Android · APK - -- No functional changes or new build in this step. +- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Sargo: local package preparation with checksum checks, bounded extraction, ext4 validation and fresh data creation without overwriting. +- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Private recovery kit with boot_a backup and model, serial, slot and checksum checks. User data not included. +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Restoration works without the diagnostic image. +- ![Change](https://img.shields.io/badge/Change-64748B?style=flat-square) System builds require a complete ARM64 hardware reference. +- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) System archive prepared and recovery kit checked locally. Installation on another Sargo pending; no flash or ROM release. +- ![Change](https://img.shields.io/badge/Change-64748B?style=flat-square) JSON changelog included in future source releases; working documents excluded. ## In development — 3 October 2026 — Sargo modem recovery and fresh installation -### Tundra · Sargo - -- Rebuild the system image with the font-cache fix and creation of oFono’s private state directory on first boot. -- Limit recovery to the modem subsystem: a controlled restart went offline, recovered and registered on the network in about 22 seconds without rebooting the phone. This does not fix the firmware’s IMS watchdog root cause. -- Check Gaia startup, hardware rendering, Fira fonts, both camera sensors, focus/zoom, keyboard and visual notifications with a fresh profile. Wi-Fi scanning still works after the modem restart. -- Two successful boots retained the same Gaia setting in a separate data image. Camera, keyboard and notifications rechecked after the second boot. Installed boot partition and existing data retained. -- No new native image published: the public installation/recovery package remains unfinished. - -### Desktop / Android · APK - -- No shared interface changes or new APK build in this step. +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Sargo: corrected font cache and first-boot creation of oFono’s private directory in the system image. +- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Modem recovery without rebooting the phone. Network registration took about 22 seconds in a controlled trial. +- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Fresh profile: checked Gaia, hardware rendering, Fira, both cameras, focus, zoom, keyboard and notifications. Wi-Fi scan worked after modem restart. +- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Two boots retained settings in a separate data image. Existing installation preserved. +- ![Note](https://img.shields.io/badge/Note-6B7280?style=flat-square) IMS watchdog root cause unresolved. Public installation and recovery package still incomplete; no image released. ## October 1, 2026 — Sargo distribution preparation — in development -### Tundra · Sargo - -- Rebuild Preview 15 system image with empty profiles, internal working documents excluded, license notices retained and an inventory of 493 runtime packages. -- Filesystem and content checks completed; these checks do not qualify hardware boot. -- Read-only storage inventory and mount-table generation from that inventory instead of hardcoded development-phone dimensions. Limited to a single ext4 extent on Sargo userdata; no formatting added. -- Clean image transferred and verified on the charged Pixel. Temporary RAM boot tests left boot_a unchanged; automatic return to installed v59 and Gaia startup checked. -- SIM detection, LTE registration and Wi-Fi scanning checked on this image; these checks do not establish end-to-end calling, SMS or mobile data. -- Fix first-boot font cache ownership by running fc-cache as the session user. Gaia startup, hardware rendering, Fira, both camera sensors, keyboard and visual notifications checked in RAM. Qualification of the rebuilt image remains incomplete. -- Intermittent reboot traced to modem IMS firmware; targeted recovery policy applied and another boot with LTE, camera, keyboard and notifications passed. Recovery after a new IMS crash has not been exercised. The public image remains non-flashable and unpublished. - -### Desktop / APK - -- No functional changes or new APK build in this step. +- ![Change](https://img.shields.io/badge/Change-64748B?style=flat-square) Sargo: rebuilt Preview15 image with empty profiles, licenses and an inventory of 493 packages. Working documents excluded. +- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Mount table generated from the storage inventory. Support limited to a single ext4 extent on userdata. +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) First-boot font cache permissions: run fc-cache as the session user. +- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Filesystem, content and transfer checks passed. RAM trials left boot_a unchanged; return to v59 verified. +- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) SIM, LTE, Wi-Fi scan, Gaia, hardware rendering, Fira, cameras, keyboard and notifications checked in RAM. +- ![Change](https://img.shields.io/badge/Change-64748B?style=flat-square) Targeted recovery after tracing intermittent reboots to IMS firmware. Second boot passed; recovery from another IMS crash not tested. +- ![Note](https://img.shields.io/badge/Note-6B7280?style=flat-square) Image qualification incomplete; end-to-end calls, SMS and mobile data unvalidated. Image unpublished and non-flashable. ## 2.7-preview.15 — 30 September 2026 — published +- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) First publication of desktop, Android and Tundra sources, with French/English READMEs, licenses and setup scripts. Private files excluded. +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) SMS: corrected bubble dates and sent/failed states. Improved camera stream cleanup and replacement. +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Desktop: stabilized navigation checks, adjusted cold-start timeout and clean shutdown after Gaia times out. +- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Desktop with Gecko 156.0.1: 42 service tests, startup, navigation, settings, gestures, history, isolation and persistence checked. +- ![Change](https://img.shields.io/badge/Change-64748B?style=flat-square) Preview15 APK signed with the previous key and GeckoView 156.0; asset parity verified. +- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Android 16 emulator: validated update, Camera/Gallery, camera resume, ten background returns and renderer recovery. +- ![Note](https://img.shields.io/badge/Note-6B7280?style=flat-square) Sargo: sources included, private v59 retained. No generic ROM; boot depends on storage layout and preinstalled images. +- ![Note](https://img.shields.io/badge/Note-6B7280?style=flat-square) Sargo: preview around 16 fps; flash, video, rotation and deep suspend incomplete. Call audio and the dialog after SMS sending need investigation. + [Release](https://nnsprod.com/git/oversu/VulpesOS/releases/tag/v2.7-preview.15) -### Shared · Gaia / Desktop / Android / Tundra - -- First shared source publication, including English/French READMEs, licenses and setup scripts. Internal documents, roadmap, profiles, keys and private images excluded from the public repository. -- Include the latest SMS fixes (bubble dates, sent/failed states) and camera lifecycle fixes. The two 30 September entries detail the hardware changes. - -### Desktop PC - -- Published checkout tested with Gecko 156.0.1 and a fresh profile: Gaia startup, navigation, settings, gestures, history and page isolation. 42 service tests pass; service persistence after restart checked. -- Stabilize navigation checks during normal document replacement and allow more time for cold startup. Close the host cleanly even if Gaia exceeds its preparation timeout. - -### Android · APK - -- Preview 15 signed with the previous Preview key, GeckoView 156.0; shared resource parity checked. Installed as an update on an Android 16 emulator. -- Photo/Gallery capture, image display and sensor resume validated. Ten background/resume cycles and recovery after renderer termination validated. This does not qualify every Android phone. - -### Tundra · Sargo - -- Runtime sources included; private v59 remains the build qualified on the development phone. No generic flashable ROM published: boot still depends on inventoried storage, preinstalled images and private maintenance access. -- Limits remain: approximately 16 fps camera preview, incomplete flash/video, rotation and deep suspend; call audio awaiting qualification and a large dialog after SMS sending still under investigation. - ## In development — SMS and camera feedback · 30 September 2026 -### Shared · Gaia / Desktop / Tundra - -- Messages: serialize bubble dates as numbers. Fix empty conversations caused by date formatting errors. Composer flow and sent/received bubbles verified on desktop with a simulated modem. -- Camera: make release idempotent; detach videos, await sensor release and dispose of the WebGL context before switching cameras. Identify each session so an old stream cannot stop its replacement. Repeated rear/front switches produce fresh frames; Home return and sensor shutdown passed on two RAM boots. - -### Tundra / Sargo - -- Vibration: use the DRV2624 brightness control; the legacy activate command did not physically stop the motor. The user confirmed stopping; single-pulse, 200/200/200 ms pattern and service-stop checks all returned to zero. Add a stop command when the service starts or exits. -- The user confirms focus, zoom and SMS transport both ways on v54. Five existing conversations render on the Pixel, with message bubbles and no date errors. -- v59 installed in boot_a after two successful RAM boots; installed startup and readback verified, slot A marked successful. Desktop sources and runtime synchronized. The large dialog after a real send still needs identification; camera smoothness remains separate work. - -### Android · APK - -- Shared Gaia sources updated; no APK rebuilt or published. +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) SMS: convert dates to fix empty conversations. +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Camera: corrected stream and sensor cleanup; an old session can no longer close its replacement. +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Sargo: physical vibration shutdown through DRV2624, with a safeguard at service startup and exit. +- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Desktop: SMS composition and display checked with a simulated modem. Sargo: camera switching and shutdown tested over two RAM boots. +- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Sargo v54: user confirmed focus, zoom and two-way SMS. Five conversations displayed; vibration shutdown confirmed. +- ![Change](https://img.shields.io/badge/Change-64748B?style=flat-square) Sargo v59 installed on boot_a after two RAM boots; startup, readback and slot A verified. +- ![Note](https://img.shields.io/badge/Note-6B7280?style=flat-square) Post-SMS dialog and camera smoothness still pending. Shared sources updated, without a new APK. ## In development — Messages and Camera · 30 September 2026 -### Desktop / Tundra - -- Messages now retain the message and conversation after a modem rejection and dispatch Gaia sending, failed and sent events. -- Distinguish the oFono queue from confirmed transmission; persist terminal states. Modem confirmation is not a recipient delivery receipt. -- Desktop checks cover failed messages visible in conversations, touch capture, Gallery opening and camera release/resume. - -### Tundra / Sargo - -- Connect touch focus areas, digital zoom and sensor JPEG capture to the original Gaia Camera. Preview stays at 640 × 480; directly measured JPEG sizes are 4032 × 3024 (12.19 MP) rear and 3264 × 2448 (7.99 MP) front. The front camera has fixed focus. -- Keep HAL logs out of the binary protocol, bound replies and resume preview after capture. A focus failure must not close the session. -- Reject SMS submission explicitly when unregistered; retain sent/failed states after the oFono object disappears. Carrier messages triggered Gaia notifications on v49 after LTE registration recovered. -- Qualification: two v54 RAM boots and the installed boot_a startup passed; image installed on the Pixel and slot marked successful after readback. Persistence, Gaia startup and Camera checks passed. Network registration remains unstable: SMS tests stopped before transmission, with no end-to-end delivery confirmed. - -### Android · APK - -- No APK rebuilt or published in this pass. The available Preview 14 is unchanged. +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Desktop/Tundra: retain SMS after modem rejection and pass sending, failed and sent states to Gaia. +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Sargo: persist SMS states after the oFono object disappears; reject sending when the modem is unregistered. +- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Sargo: touch focus, digital zoom and sensor JPEG capture in Gaia Camera. +- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Measured photos: 4032 × 3024 rear, 3264 × 2448 front. Preview 640 × 480; fixed front focus. +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Native camera: separate binary data from HAL logs, bound responses and resume preview after capture or focus errors. +- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Desktop: checked visible send failures and Camera/Gallery workflows. Sargo v54: validated two RAM boots, installation, persistence and camera. +- ![Note](https://img.shields.io/badge/Note-6B7280?style=flat-square) Unstable network; no end-to-end SMS delivery confirmed in this pass. Modem confirmation is not a delivery receipt. Public APK unchanged (Preview14). ## 2.7-preview.14 — APK published · 30 September 2026 +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) System information: version alignment and Fira fonts in Shadow DOM components. +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Desktop/Tundra: portrait key sizing, cursor placement in the active field and stale input replies. +- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Desktop/Tundra: Gaia keyboard bridge and numeric keypad for SMS recipients. Android keeps its system keyboard. +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Lockscreen: resize without a keyboard window. Honour silent notifications, including after restoration. +- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Sargo: persistent tray and lockscreen notifications, opening the owning application. +- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Sargo: cancellable vibrations capped at ten seconds; private persistent Wi-Fi profiles, automatic reconnection and network removal. +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Sargo: store incoming SMS before acknowledgement, remove duplicates and create the state directory. +- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Sargo: earpiece/speaker, microphone mute and call-volume controls. Routing, mute and volume tested outside a call. +- ![Change](https://img.shields.io/badge/Change-64748B?style=flat-square) Installer: reject flashing diagnostic images with a debugger. Sargo v48 installed after two RAM boots; persistence, readback and slot A verified. +- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Desktop: Calendar and keyboard checked. Sargo: keyboard, notifications and Wi-Fi reconnection checked; notification sound and real telephony await validation. +- ![Change](https://img.shields.io/badge/Change-64748B?style=flat-square) Preview14 APK signed and published. Startup, versions and fonts checked on an Android 16 emulator. No Sargo image released. + [Release](https://nnsprod.com/git/oversu/VulpesOS/releases/tag/android-2.7-preview.14) -The Preview 14 APK is published. Desktop is up to date and the private Sargo v48 build is installed and tested; this release does not include a public phone image. - -### Shared · PC / APK / Gaia - -- About: remove the extra indentation in version rows above “Last updated”; alignment checked on desktop and Android. -- Fonts: normalize text font names inside Shadow DOM stylesheets, including styles added later; keep icon fonts intact. -- Calendar: month view opens without JavaScript errors in the desktop check. - -### PC / Tundra - -- Keyboard: use the screen orientation instead of the short keyboard frame. Keys measure about 60 CSS pixels on Pixel; rotation tested on desktop at DPR 3. -- SMS: restore the caret to the focused field when Gecko retains the recipient selection; preserve valid selections during editing. -- Gaia notifications: respect silent, no-sound and no-vibration flags, including restored notifications. - -- Gaia keyboard: native input bridge, numeric SMS recipient layout, corrected selection events and protection against stale field replies. Recipient and message typing tested on desktop and Pixel; carrier delivery still requires qualification. -- Lockscreen: tolerate the absence of a keyboard window during resize. -- Native notifications: persistent application-owned entries and launch through Gaia's declared Phone entry point. Tray and lockscreen display/removal verified on Sargo; audible sound still needs qualification. - -### Tundra / Sargo — v48 build installed - -- Vibration: Sargo actuator bridge, patterns limited to ten seconds, non-blocking sequencing and cancellation. Driver activation and stop measured. - -- Wi-Fi: private persistent profiles, autoconnect and forget operation; storage and failed-update rollback tests. Connection and reconnection after a RAM reboot verified on Sargo; private profile files (0600). -- Incoming SMS: persist before acknowledgement and deduplicate retries. Services now create their state directory at startup, fixing a mount failure reproduced on Sargo. -- Call audio: earpiece/speaker, microphone mute and call volume controls added; earpiece/speaker switching, mute and volume verified without placing a call. Two-way conversation still unverified. -- Local-debugger images cannot be flashed by the installer. v48/preview.14 written to boot_a after two RAM boots and persistence checks; installed boot verified, image readback matches and slot A marked successful. v43–v48 do not enable a debugger. - -### Android · APK - -- Preview.14 APK built, signed and published on Forgejo. Startup, version information, alignment and component font selection checked on an Android 16 emulator. Android retains its system keyboard. - ## 2.7-preview.13 — application workflows · 27 September 2026 -### Shared · Gaia / Desktop - -- Calendar: successfully scheduling a reminder no longer rejects event saving; scheduling failures reach the caller. -- Reminders: ignore deleted events and do not mark denied notifications as delivered. Native Tundra notification support remains incomplete. -- Notifications: accept empty raw text without requesting an “undefined” translation. -- Gallery: positive thumbnail size even when starting hidden; cancel preview updates after leaving the editor. -- SMS: apply correspondent, read state, date range and delivery filters; cover conversation reading and deletion. -- Deeper review: WebM playback, seeking and deletion; black-and-white photo editing and saved copy; event creation and deletion. Retain original Gaia interfaces. - -- Isolated checkout qualification passes startup, desktop tests, persistence and Android packaging without rewriting sources. - -### Android · APK - -- Signed preview.13 APK built; Camera/Gallery, Calendar, ten background/resume cycles and renderer recovery validated on an Android 16 emulator. - -### Tundra · Sargo - -- Build v40/preview.13 installed after two RAM boots: Gaia, camera, Wi-Fi, brightness and controls checked; matching boot_a readback and successful slot marking. Remaining limits in `docs/APPLICATION-REVIEW.md`. +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Calendar: save events with alarms, report scheduling errors and skip reminders for deleted events. +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Notifications: accept empty text; do not mark rejected notifications as delivered. Native Tundra support still incomplete. +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Gallery: thumbnail sizing at hidden startup and cancellation of updates after closing the editor. +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) SMS: apply correspondent, read-state, date and delivery filters. +- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Desktop: checked WebM playback, photo editing and copying, calendar, conversation reading and deletion. +- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Isolated checkout: startup, desktop tests, persistence and Android packaging passed. +- ![Change](https://img.shields.io/badge/Change-64748B?style=flat-square) Preview13 APK built and signed. Camera/Gallery, Calendar, ten background returns and renderer recovery validated on emulated Android 16. +- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Sargo v40 installed after two RAM boots; Gaia, camera, Wi-Fi, brightness, controls and boot_a readback verified. ## 2.7-preview.12 — application review · 27 September 2026 -### Shared · Gaia / Desktop - -- Restore original Gaia 1×–2.25× raster variants for 1,047 CSS image references. -- Camera switch becomes available after device discovery; unavailable flash stays - visible and disabled; pinching no longer zooms the whole camera application. -- Reserve Home gestures for long press and suppress its context menu; retain Gaia's task manager. -- Relay Gaia touch gestures using unprivileged DOM events, without granting trusted - user activation to external websites. -- Bundle the supplied bilingual, local browser welcome page and route links and - searches into the actual browser. -- Welcome page follows the Gaia language live: `fr`/`fr-*` select French; - all other languages select English. -- SMS: numeric identifiers, migration of older records, Date values, sent events, - multi-recipient request shape and serialized storage writes. -- Calendar: restore the current day's accessibility label in 37 locales. -- Review 14 apps; 26 media/clock/UI checks, contact and SMS checks, plus real Web - navigation and isolation. Scope and limitations: `docs/APPLICATION-REVIEW.md`. - -### Android · APK - -- Rebuild `2.7-preview.12` with the same shared UI and local welcome page; check - payload parity. Android camera capture continues to use getUserMedia. - -- Tundra hardware features are not automatically ported to Android: each platform - retains its adapter and reports its actual capabilities. - -### Tundra · Sargo - -- Convert NV21 using a shader and transfer binary frames instead of base64: - approximately 23 fps measured in RAM, versus 8 fps in the intermediate trial. -- Wait for camera worker exit before opening the other sensor. -- Manual brightness reaches its target in about 200 ms in the hardware test; - keep screen sleep/wake fades. -- Connect Gaia Wi-Fi power settings to actual radio state; support WPA-PSK on - mixed WPA2/WPA3 networks. -- Scan, connection and HTTPS over wlan0 validated in RAM; connection profiles are temporary. -- Wi-Fi profiles are still temporary. Flash, video, light/orientation sensors, - Bluetooth, data counters and advanced call controls remain incomplete. -- v38 installed: two RAM boots, installed startup, `boot_a` readback, other boot - partitions preserved and slot A marked successful. +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Gaia icons: restored 1× to 2.25× variants across 1,047 CSS references. +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Camera: enable switching after sensor detection and prevent pinch zoom on the UI. Unavailable flash disabled. +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Home: restore long press and gesture forwarding without additional privileges for websites. +- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Browser: local bilingual welcome page following Gaia’s language without restarting. +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) SMS: corrected identifiers, migration, dates, sending events, multi-recipient requests and concurrent writes. +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Calendar: restored the current-day accessibility label in 37 languages. +- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Reviewed 14 apps; 26 Gallery/Music/Clock/UI checks, Contacts/SMS tests and web isolation checks. +- ![Change](https://img.shields.io/badge/Change-64748B?style=flat-square) Preview12 APK rebuilt with shared assets; parity verified. Hardware features handled separately on Android and Tundra. +- ![Change](https://img.shields.io/badge/Change-64748B?style=flat-square) Sargo: shader-based NV21 preview and binary transport, about 23 fps measured versus 8 in the intermediate trial. +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Sargo: wait for camera worker shutdown before switching sensors; manual brightness reaches its target in about 200 ms. +- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Sargo: Wi-Fi power control from Gaia and WPA-PSK connections to mixed WPA2/WPA3 networks. +- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Sargo: scan, connection and HTTPS validated in RAM. v38 installed after two RAM boots, with readback and slot A checks. +- ![Note](https://img.shields.io/badge/Note-6B7280?style=flat-square) Sargo: temporary Wi-Fi profiles; flash, video, sensors, Bluetooth, data counters and advanced call controls incomplete. ## In development — Sargo feedback and interface · 27 September 2026 -### Shared · Gaia / Desktop - -- Shutdown animation: three blue shades replace the orange rings; original Gaia - dimensions, sequence and timing are preserved. - -- Phone: prevent document scrolling on tab changes, keeping Recents and Contacts - headers in place. Use Gaia’s original call-log artwork at multiple densities up to 2.25×. -- Select controls: remove the old engine offset that clipped Fira descenders. - Video: keep the introductory text within the viewport. -- Brightness: save a number from input/change events; normalize legacy text values - before Gaia animates the transition. Coalesce stale hardware brightness steps - while preserving the order of sleep/wake commands. -- Home: extend wallpaper beneath the transparent gradient, reserve navigation space - inside applications, and place Home on the right in landscape. -- Browser: editable local page at `overrides/search.gaiamobile.org/home/index.html`, - retaining Gaia integration and private mode. -- Wi-Fi: show a scan failure and retry action instead of endless retries. Network - discovery and results in the original Gaia panel now verified on Sargo in RAM. -- Consolidated tracking: `docs/CHANTIER.md` includes earlier and new feedback and limitations. - -### Android · APK - -- Build and sign `2.7-preview.11` with shared Gaia; payload parity checked. - Place Home on the right in landscape and fix its wallpaper background. - Check rotation and non-overlapping app/navigation bounds on Android 16 emulator. - -### Tundra · Sargo - -- Camera: hardware preview in Gaia, JPEG capture and rear/front switching verified - on Sargo in RAM; sensors close on returning Home. Capture is currently limited - to 640 × 480 (480 × 640 portrait); video and flash remain unavailable. - Desktop and APK keep getUserMedia and share the same Gaia interface. - -- v33 (`2.7-preview.11`) installed after two successful RAM boots. Installed boot, `boot_a` readback, - data preservation and slot A success finalization verified. -- Backlight measured from Gaia: 35% → 89/255, 80% → 204/255. Wi-Fi results visible - in the original panel. Blue shutdown rings included. -- Call audio: support PulseAudio 14 text output and prevent loading audio modules twice. - Voice-call profile, earpiece selection and media-profile restoration verified on Sargo. - An actual two-way conversation remains unvalidated. -- Still open: audible two-way calls, full-resolution camera capture/video and sensor-driven rotation. - See the tracking document. +- ![Change](https://img.shields.io/badge/Change-64748B?style=flat-square) Gaia shutdown animation recoloured blue. +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Phone: keep headers fixed when changing tabs and use call-log icons up to 2.25× density. +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Clipped dropdown text and overflowing Video welcome text. +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Brightness: numeric values and coalesced stale commands, preserving sleep/wake order. +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Home: wallpaper under the gradient and right-side button in landscape, without covering apps. +- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Browser: editable local welcome page, retaining private mode. +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Wi-Fi: error message and Retry button instead of endless retries. +- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Preview11 APK built and signed; rotation and Home button placement checked on emulated Android 16. +- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Sargo: preview, JPEG and camera switching validated in RAM. Capture limited to 640 × 480; flash and video unavailable. +- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Sargo v33 installed after two RAM boots; startup, data, readback and slot A verified. Brightness and Wi-Fi results checked in Gaia. +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Sargo: PulseAudio 14 compatibility and duplicate audio module loading. Call/media profiles and earpiece tested. +- ![Note](https://img.shields.io/badge/Note-6B7280?style=flat-square) Sargo: call audio, full-resolution capture, video and automatic rotation await validation. ## In development — native Wi-Fi stability · 27 September 2026 -### Shared · Gaia / Desktop - -- Fix the Wi-Fi loop with no delay: scan every 20 seconds as intended by Gaia. - Coalesce concurrent scans. Add burst, failure and timer regression tests. -- Shared development version: `2.7-preview.9`, Gaia `2.7.0`; actual Gecko versions - remain unchanged. - -### Android · APK - -- Build `2.7-preview.9` to retain shared Gaia parity. Android still manages its - own Wi-Fi; this change does not claim to add a hardware driver to the APK. - -### Tundra · Sargo - -- Identify the v21/v22 failure: unbounded Python Wi-Fi scan clients exhausted - memory, causing the lowmemorykiller to terminate Gecko. The phoc SIGSEGV occurs - afterwards during display teardown. -- Allow one hardware scan at a time, briefly cache results and errors, invalidate - after connect/disconnect. Application isolation and memory protection remain enabled. -- Qualify v25 through two RAM boots, then install and verify it on the Pixel: - 11 Gaia and 10 system checks pass, with no memory-pressure process kills. - Check the Gaia lockscreen, Fira, battery, date and Wi-Fi panel; exercise - sleep/wake and volume through injected input events. -- Verify `boot_a` by readback, preserve the other boot partitions and user data, - and mark slot A successful. Native camera support and full call validation - remain unfinished. +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Wi-Fi: restore the 20-second scan interval and coalesce concurrent scans. +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Sargo: one hardware scan at a time, with a short cache cleared on connect/disconnect. Fixes memory exhaustion in v21/v22. +- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Wi-Fi burst, failure and timer tests. +- ![Change](https://img.shields.io/badge/Change-64748B?style=flat-square) Shared Preview9 version, Gaia 2.7.0; new APK with the same sources. Android still manages its own Wi-Fi. +- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Sargo v25: two RAM boots then installation, 11 Gaia and 10 system checks passed, without low-memory process kills. +- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Checked locking, fonts, battery, date and Wi-Fi; tested sleep/wake and volume with injected events. Data, readback and slot A verified. +- ![Note](https://img.shields.io/badge/Note-6B7280?style=flat-square) Native camera and full call validation still pending. ## In development — Gaia restoration · 27 September 2026 -### Shared · Gaia / Desktop - -- Restore Gaia panels and remove temporary windows/floating controls: Settings, - Usage, FM and contact/media import. Features without an API remain visible with - disabled controls, without invented hardware values. -- Fix legacy style scoping: ordinary `div` selectors no longer become component - roots. Restore lockscreen icons, retain Fira and use scalable Gaia glyphs for - Phone tabs. -- Reserve 44 CSS pixels for Home so it cannot cover Answer/Hang up. The date uses - its original component; battery updates reach Settings and its labels no longer - display `{{ level }}`. -- Re-enable the original Gaia lockscreen; temporary splash uses the supplied logo, - stable dimensions and a gentle animation. Localized name: Navigateur / Browser. - Credits appear in Legal Information; original license notices remain. -- All 26 application checks pass (Gallery, Music, Clock, task management). Further - desktop checks cover panels, imports, battery, date and navigation; simulated - calls, history, sleep/wake, Power menu and volume. See `docs/GAIA-RESTORATION.md`. - -### Android · APK - -- Build and install `2.7-preview.8` on the Android 16 emulator with shared Gaia; read battery state from Android, - add Android Settings at the bottom and optional Android Home mode. The Android - application catalogue in Gaia still needs integration. -- Android checks: ten background/resume cycles and renderer termination recovery; - shared payload parity verified. No physical Android device test in this pass. -- Imports use Gaia components; the SMS button opens Android confirmation. - The internal lockscreen does not replace Android security. - -### Tundra · Sargo - -- Build v21/v22 candidates with all `overrides/` fixes in the boot payload; connect Wi-Fi - scan/association to the Gaia panel through NetworkManager. -- RAM trials blocked: the display service exits with code 139 before qualification. - No new flash. Restore installed v20 and verify 11 Gaia and 9 system checks with - preserved data; restore the native profile’s previous unlocked state. Candidate - Wi-Fi, native camera, full SIM/Bluetooth APIs and CPU suspend remain unqualified. +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Restored Gaia Settings, Usage, FM and import panels. Temporary interfaces removed; unavailable features disabled. +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Corrected legacy component styles, lockscreen icons and Phone glyphs. +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Reserve 44 pixels for Home; restore date and battery without `{{ level }}` placeholders. +- ![Change](https://img.shields.io/badge/Change-64748B?style=flat-square) Gaia lockscreen re-enabled, splash stabilized, Browser name localized and credits added. +- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Desktop: 26 app checks passed, plus panels, imports, battery, navigation and simulated calls. +- ![Change](https://img.shields.io/badge/Change-64748B?style=flat-square) Preview8 APK installed on emulated Android 16: Android battery, settings access and optional HOME mode. Android app catalogue still incomplete. +- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Emulated Android: ten background returns, renderer recovery and asset parity checked. Gaia locking remains separate from Android security. +- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Sargo v21/v22: Wi-Fi scan and association connected to Gaia through NetworkManager. +- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Sargo: RAM trials interrupted by display-service failure (code 139). Return to installed v20 verified, without a new flash. +- ![Note](https://img.shields.io/badge/Note-6B7280?style=flat-square) Candidate Wi-Fi, camera, SIM/Bluetooth APIs and CPU suspend unvalidated. ## In development — 27 September 2026 -### Shared · Gaia / Desktop - -- Replace the temporary call overlay with Gaia’s original **Callscreen**: - incoming/outgoing calls, answer, hang up and automatic close. A compatibility - adapter supplies `mozTelephony` events to the original modules. Unsupported - audio/conference controls remain disabled. -- Persist completed calls in the host and import them into Gaia’s original call - log, with acknowledgement and duplicate detection after interruption. -- Connect real battery state to the original status icon; unknown is not empty. - Handle a missing/malformed Facebook contacts index without blocking local - number lookup or call history. -- Compare rendered font faces: home already used Fira on both platforms, but - desktop system text partly used Noto. Main text rules now select Gaia’s - bundled Fira face while preserving icon fonts. -- Desktop Gecko test with a simulated modem covers incoming/outgoing UI, - answer/hang up, history, screen sleep/wake, power menu, volume and battery. - No APK rebuilt in this step. - -- Earlier v13 step (superseded above): system-owned call screen with dialing, ringing, connected state and Hang up. - Controls remain available if the service temporarily loses call status. -- Tested in desktop Gecko with a simulated modem, including hang-up failure and - retry. Desktop sources synchronized; no APK rebuilt. - -### Tundra · Sargo - -- Private v20 installed and read back in `boot_a` after two RAM boots and one - installed boot; settings retained and slot marked successful. -- Read only Sargo’s physical Power/Volume keys and forward - their edges to Gaia HardwareButtons. Short press sleeps/wakes the screen; - long press opens Gaia’s menu. The restricted native broker controls backlight, - volume, shutdown and reboot. -- Serialized brightness and power changes: concurrent requests no longer - leave the backlight disabled while waking the screen. -- Native volume: expose the session PulseAudio socket read-only to the service; - otherwise Gaia’s indicator changed while the audio output remained unchanged. -- Screen sleep does not suspend the kernel yet. Real incoming calls, conversation - audio and advanced call audio controls remain to be qualified. - -- Earlier step: private v13 installed in `boot_a` after two RAM boots; installed boot, - readback and the A/B success flag verified. -- oFono call state reporting and Answer/Hang up commands restricted to modem call paths. -- Successful void D-Bus responses no longer cause a false invalid-response error. -- A short real outgoing call reached ringing and was automatically hung up. - Conversation audio and incoming calls remain unqualified. -- Camera diagnostics no longer mistake a timeout for successful initialization. - Libhybris detects two sensors, but the preview probe receives no frames. - Native Gaia camera access remains unavailable. +- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Gaia Callscreen for incoming/outgoing calls, answer and hangup. Replaces the temporary v13 screen. +- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Persistent call log with acknowledgement and duplicate detection. +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Distinguish unknown battery state from empty battery; tolerate missing or malformed Contacts indexes. +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Render system text with Fira Sans while preserving icon fonts. +- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Desktop with simulated modem: calls, failed hangup and retry, history, sleep/wake, Power menu, volume and battery. +- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Sargo: Power/Volume buttons connected to Gaia, with native screen, volume, shutdown and reboot controls. +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Sargo: serialize brightness and power changes to prevent a black screen on wake; fix PulseAudio access for volume. +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Sargo: successful hangup with an empty D-Bus reply no longer produces a false error. +- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Sargo v13 then v20 installed after RAM trials; startup, readback, settings and slot validation checked. +- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Real outgoing call reached ringing and was hung up. Incoming calls and conversation audio unvalidated. +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Camera diagnostics: a timeout no longer counts as successful initialization. Two sensors detected, without received frames. +- ![Note](https://img.shields.io/badge/Note-6B7280?style=flat-square) Native camera unavailable, CPU suspend and advanced audio controls incomplete. No new APK. ## In development — 26 September 2026 -### Shared · Gaia - -- Fonts: use the archived simulator’s Fira Sans OpenType files for both Gaia - pages and Tundra system text. Fix native sans-serif fallback and add Fontconfig - validation; icon fonts remain separate. -- Settings: remove the “Tundra settings” and “Test a notification” diagnostic - buttons. Release information and the technical details remain available. -- Hardware panel: pass platform capabilities into the panel. An undefined variable - used to open a Gecko alert instead of displaying battery information. - -### Desktop - -- Local adapter synchronized with the shared sources. Gecko remains at 156.0.1. - -### Android - -- Hardware panel fix is in the shared sources; no new APK has been built. - -### Tundra / Sargo - -- Explicit A/B boot-success finalization after installation: backed-up and checked - GPT metadata, verified Qualcomm utility, Fastboot confirmation. -- Modem rejections distinguished from transport failures; errors shown inside Gaia. - The unavailable native camera no longer suggests an ineffective permission change. -- Rendered-font and radio diagnostics without automatic calls or messages. Fira Sans - confirmed on the home screen and status bar. -- Installed the v9 font build into `boot_a` on the inventoried Sargo. Written - image verified by readback; the other five backed-up boot partitions are unchanged. -- Two boots from device storage pass 10 Gaia and 9 system checks each and retain - settings. A PC is no longer required to boot. Droidian fallback files retained; - actual restoration has not been tested. -- Private persistent candidate: separate 2 GiB ext4 data image, retained Gaia - settings/profiles and system identities, no ten-minute reboot watchdog. -- Two consecutive boots of the same v8 image: 9 Gaia and 9 system checks pass on - each boot. Separate diagnostic: 15 checks using identical shared UI files. -- Boot-a-only installation and original-boot restoration tools prepared and tested - locally. No actual flash performed; remaining hardware limits are documented. -- Normal startup no longer opens Settings/Clock or scrolls the home screen. These - actions now belong to the explicit `--diagnostics` mode. -- Enter fullscreen before loading Gaia and hide native window decorations. On the - tested Pixel: 360 × 740 CSS pixels at scale 3, stable dimensions and an unchanged - home screen throughout five post-startup samples. -- v13 RAM trial passes eight startup checks and nine system checks. Reports now - distinguish startup observation from interactive qualification. -- Separate v14 diagnostic: thirteen interactive checks pass, including the hardware - panel; Wi-Fi panel and scanning verified with the same UI files as normal v13. -- Interactive diagnostic images are rejected as the normal launch target. The - earlier v13 selection is retained in the launcher history. -- Modem registered on UMTS during the trial; audio playback completed and microphone - samples were measured without retaining a recording. In-call audio, camera and - suspend remain unqualified. -- Ramdisk dependency discovery now runs `readelf` in the C locale, preserving the - ARM64 dynamic loader regardless of the build machine's language. - -All six A/B boot/DTBO/vbmeta partitions are unchanged. This remains a private RAM -trial, **not a flashable ROM**. See [Tundra status](tundra/docs/STATUS.md). +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Use historical Fira Sans fonts in Gaia and Tundra; correct sans-serif fallback and Fontconfig checks. +- ![Change](https://img.shields.io/badge/Change-64748B?style=flat-square) Removed diagnostic buttons from Settings. +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Hardware panel: pass platform capabilities correctly to display the battery. +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Sargo: distinguish modem and transport errors; correct the unavailable-camera message. +- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Sargo: persistent data on a 2 GiB ext4 image and A/B success validation after installation. +- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Sargo v8: two boots with persistence, 9 Gaia and 9 system checks per boot. Installation and recovery tools tested locally. +- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Sargo v9 installed on boot_a: two boots with 10 Gaia and 9 system checks each. Matching readback, five other partitions preserved; actual restoration untested. +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Normal startup without automatic diagnostic gestures; fullscreen before Gaia, stable 360 × 740 CSS-pixel layout. +- ![Change](https://img.shields.io/badge/Change-64748B?style=flat-square) Separate `--diagnostics` mode; diagnostic images excluded from normal launch. +- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Sargo: v13 tested in RAM (8 startup, 9 system checks), v14 diagnostics (13 interactive checks), including Wi-Fi. +- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) UMTS registration, audio playback and microphone signal checked. Call audio, camera and suspend unvalidated. +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Ramdisk builds independent of the desktop language by running readelf in the C locale. +- ![Note](https://img.shields.io/badge/Note-6B7280?style=flat-square) Private trials, without a public ROM. Desktop sources synchronized; shared Android fix available, without a new APK. ## In development — 25 September 2026 -### Shared · Gaia - -- **Navigation:** a circular Home button, a black-to-transparent gradient over the wallpaper, - and a solid blue bar inside apps. Pressing Home now changes the ring instead of leaving - a blue rectangle over the home screen. -- **Scrolling:** the gradient disappears at the bottom of the home panel and returns when - scrolling up. Switching between the Apps and Pages panels is supported. -- **Typography:** bundled Fira Sans fonts, with multiple weights and italics, instead of - relying on the host system’s installed fonts. -- **Device information:** separate product, Gaia, actual engine, platform, model and adapter - versions. The panel includes support for Tundra and hardware adaptation fields; - connecting these to the native platform is still pending. -- **Icons:** loading resumes after an initial zero-width measurement, across desktop, - Android and Tundra. This fix was previously limited to Android. -- **Maintenance:** this changelog brings together the three platforms’ progress. - -### Desktop · Locally tested - -- Tested an icon initially measured at zero width, then made visible. -- Tested Home / Settings / Home navigation, a transparent bar at the bottom, the gradient - at the top and permission checks in an isolated profile. -- Tested the information panel in French and English, without duplicate legacy rows. -- The engine remains Gecko **156.0.1**; these UI changes do not rebuild Gecko. - -### Android · Sources adapted, Java compiled - -- The GeckoView / Android bridge implements the same navigation states: an overlay on Home, - with space reserved for the bar in apps and the browser. -- Navigation state survives Android activity recreation. -- Java compilation passed. **No new APK delivered for these changes**; - visual behaviour still needs testing on Android. - -### Tundra / Sargo · Private RAM trials - -- Network build v6 is the launcher reference after 13 Gaia and 9 system checks; - automatic return and unchanged hashes of all six boot partitions verified. -- Separate Tundra backend reports Pixel 3a, Sargo adaptation and native version, - without claiming an unimplemented camera or telephony stack. -- Real battery and charging state with a bilingual Gaia hardware panel. - The network trial passed 13 Gaia checks, including this panel. -- Real Wi-Fi tested: scan, connection, IPv4, DNS and HTTPS in Gecko. Added - `pd-mapper`, disabled uninitialized IPA offload and premature camera probing; - 9 system checks pass without kernel Oops. Gaia network selection is still - pending; trial credentials remain in RAM. -- Service configuration can travel in the small ramdisk, avoiding another 3 GiB - transfer per boot adjustment. Qualcomm trials use RAM copies of modem NV data. - -- A new ext4 system image contains the ARM64 runtime, reference HALs, Phoc, Gecko and Gaia. - The runtime is no longer loaded from the Droidian working directory, nor the HAL - from its previous `/var/lib/lxc/android/` location. -- The second Pixel 3a trial passed **7 Gaia checks and 8 system checks**, with Tundra PID 1, - its HAL services, compositor and Gaia. -- A physical display capture confirms home icons, loaded Fira Sans, the circular button - and gradient. Gecko **156.0.1 ARM64**, with hardware WebRender. -- Small UI changes travel in the trial ramdisk, avoiding another 3 GiB system transfer - for each CSS or JavaScript adjustment. -- Build sources are saved alongside new images and verified by hashes, so qualified - images remain testable after the checkout changes. -- The latest trial (`sargo-system-boot-20260925-v5`) passes **10 Gaia checks and 8 system - checks**, including icons, fonts, pressed Home, bottom-of-page behaviour and app return. - Physical capture reviewed; local launcher reference updated. No APK published or phone flashed. - -**Limitations:** the installed filesystem still carries the system image; writes are lost -at shutdown; the trial returns to the retained system after ten minutes. No flashing or -Droidian replacement. A warm compositor restart failed during one trial; cold boot works. -Networking, modem, audio, camera and suspend still need integration or qualification. -This is not a public installable ROM or a validated daily-use phone. +- ![Change](https://img.shields.io/badge/Change-64748B?style=flat-square) Round Home button, transparent wallpaper gradient and blue bar inside apps. +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Hide the gradient at the bottom and restore it when scrolling; remove the blue rectangle when pressing Home. +- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Bundled Fira Sans weights and italics; separate product, Gaia, engine and platform versions. +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Resume icon loading after an initial zero-size measurement on all three platforms. +- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Desktop: icons, navigation, permissions and French/English information panel checked with Gecko 156.0.1. +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Android: retain navigation state after activity recreation. Java compilation passed; visuals need retesting, no new APK delivered. +- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Tundra: ext4 image with ARM64 runtime, HAL, Phoc, Gecko and Gaia; small fixes bundled in the ramdisk. +- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Sargo: dedicated backend, Pixel 3a information and hardware battery readings. Build sources archived with checksums. +- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Sargo: hardware WebRender, icons and fonts checked on a physical display capture. v5: 10 Gaia and 8 system checks passed. +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Sargo: add pd-mapper, disable uninitialized IPA and premature camera probing. Copy modem NV data to RAM for trials. +- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Sargo v6: Wi-Fi scan, connection, IPv4, DNS and HTTPS verified; 13 Gaia and 9 system checks passed. Gaia network selection still pending. +- ![Note](https://img.shields.io/badge/Note-6B7280?style=flat-square) RAM trials without flashing, automatic return after ten minutes and changes lost at shutdown. Cold boot passed; warm compositor restart failed. +- ![Note](https://img.shields.io/badge/Note-6B7280?style=flat-square) No public ROM; modem, audio, camera and suspend still need integration or validation. ## 2.7-preview.7 — APK built, local reference -### Shared / Desktop / Android - -- Fixed internal camera allocation permission on desktop and recovery after the first - CAMERA permission prompt on Android. -- Photo distinguishes permission denial, a missing sensor and an opening failure. -- Shared product version **2.7-preview.7**, Gaia **2.7.0**, with separate engine versions. -- Desktop Gecko **156.0.1**, Android GeckoView **156.0**: the real patch difference is - reported rather than relabelling either engine. +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Desktop camera permission and recovery after the first Android permission prompt. +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Separate messages for denied permission, missing sensors and opening failures. +- ![Change](https://img.shields.io/badge/Change-64748B?style=flat-square) Separate product and engine versions: Vulpes Preview7, Gaia 2.7.0, desktop Gecko 156.0.1 and Android GeckoView 156.0. ## 2.7-preview.6 — APK built -### Shared / Desktop / Android - -- Ported the original Gaia Camera app: webcam preview and JPEG capture on desktop, - or capture through GeckoView on Android. -- Photos saved in Gaia Gallery; media imports use the host system’s file picker. -- Unsupported controls disabled, including flash and video; camera switching depends - on available sensors. +- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Gaia Camera on desktop and Android: preview and JPEG capture through webcam or GeckoView. +- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Save photos in Gallery and import through the system picker. +- ![Note](https://img.shields.io/badge/Note-6B7280?style=flat-square) Flash and video disabled; camera switching depends on available sensors. ## 2.7-preview.5 — APK built -### Shared - -- Centralised versions in `release.json`, separating product, Gaia and engine versions. -- Shared native integration UI between desktop and Android; packaged common files - checked inside the APK before distribution. - -### Desktop - -- Integration with available Linux settings panels, media and vCard imports, - desktop notifications and a test webcam capture. - -### Android - -- Same Gaia interface and native commands as desktop, adapted to Android capabilities. -- Foreground return and recovery after rendering-process termination tested. +- ![Change](https://img.shields.io/badge/Change-64748B?style=flat-square) Versions centralized in `release.json`; product, Gaia and engine separated. +- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Shared desktop/Android native interface and APK asset parity checks. +- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Desktop: Linux settings, media/vCard imports, notifications and test webcam capture. +- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Android: foreground return and recovery after renderer termination. ## 2.7-156.0.2 — Historical APK built (revision 4) -This historical name mixed product revision and engine version; it does not mean -that the APK used Gecko 156.0.2. Its Android engine was Gecko 156.0. - -### Android - -- Activity and session recovery to reduce white screens when returning to Vulpes. -- Access to Android settings, volume and window brightness. -- Media and contact imports, native notifications with permissions. -- Handoff to Android’s dialer, SMS and clock apps; no silent sending or replacement - of the system Phone and SMS apps. -- Capture initially delegated to Android’s camera app, before the Gaia Camera port. +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Android: activity and session recovery to reduce blank screens. +- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Access to Android settings, volume and window brightness. +- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Media and contact imports; native notifications with permissions. +- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Handoff to Android Phone, SMS, Clock and Camera. No silent sending or replacement of default apps. +- ![Note](https://img.shields.io/badge/Note-6B7280?style=flat-square) Historical numbering: Android engine Gecko 156.0, despite the 2.7-156.0.2 name. ## 2.7-156.0.1 — APK published (revision 3) -- **Vulpes OS - Preview** name and project icon. -- Wallpaper flickering fix included. -- Historical numbering retained; it does not identify the Android engine’s patch version. +- ![Change](https://img.shields.io/badge/Change-64748B?style=flat-square) “Vulpes OS - Preview” name and project icon. +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Wallpaper flickering corrected. +- ![Note](https://img.shields.io/badge/Note-6B7280?style=flat-square) Historical numbering, separate from the Android engine version. [Release](https://nnsprod.com/git/oversu/VulpesOS/releases/tag/android-2.7-156.0.1) @@ -808,7 +549,7 @@ that the APK used Gecko 156.0.2. Its Android engine was Gecko 156.0. ## 2.7-preview.2 — APK published -- Fixed wallpaper flickering in the Android Preview. +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Wallpaper flickering in the Android Preview. [Release](https://nnsprod.com/git/oversu/VulpesOS/releases/tag/android-2.7-preview.2) @@ -816,8 +557,8 @@ that the APK used Gecko 156.0.2. Its Android engine was Gecko 156.0. ## 2.7-preview.1 — APK published -- First Android Preview bundling Gaia and GeckoView. -- Archived version; known wallpaper flickering issue. +- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) First Android Preview with Gaia and GeckoView. +- ![Note](https://img.shields.io/badge/Note-6B7280?style=flat-square) Archived version; known wallpaper flickering issue. [Release](https://nnsprod.com/git/oversu/VulpesOS/releases/tag/android-2.7-preview.1) @@ -825,26 +566,12 @@ that the APK used Gecko 156.0.2. Its Android engine was Gecko 156.0. ## Earlier milestones — technical retrospective -These milestones are not newly dated public releases. - -### Gaia / Desktop - -- Vulpes assembled from historical Gaia: branding, wallpaper, translations and - work on system information. -- Experimental Gecko 45 → 52 port, followed by separation of Gaia, services and - the engine for the current Gecko host. -- Reimplementation of required APIs: settings, application registry, local contacts, - messages, alarms, storage and inter-app communication. -- Adaptation of old UI components, simulated touch, browser and applications; - testing of archived Firefox OS apps. -- Gecko update tools with checks, candidate engines and explicit rollback. - They detect incompatibilities without automatically repairing them. -- Gaia launch and updates without rebuilding the whole engine. - -### Tundra / Sargo - -- Pixel 3a inventory, boot partition backups and USB diagnostic trials. -- QEMU trials, then ARM64 Gecko inside Droidian and under a dedicated compositor. -- Separate root, selected HAL services and Tundra PID 1; temporary graphical - Fastboot trials with rescue SSH and automatic return. -- Reference kernel **4.9-124-google-sargo**, **Android 9 / API 28** adaptation. +- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Historical Gaia base with Vulpes branding, wallpaper, translations and system information. +- ![Change](https://img.shields.io/badge/Change-64748B?style=flat-square) Experimental Gecko 45 to 52 port, followed by separation of Gaia, services and engine. +- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) APIs for settings, apps, contacts, messages, alarms, storage and inter-app communication. +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Adapt Gaia components, touch gestures, browser and apps to recent engines. +- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Gecko updates with checks and rollback; Gaia updates without rebuilding the engine. +- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Archived Firefox OS apps, QEMU, then ARM64 Gecko inside Droidian and under a dedicated compositor. +- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Sargo: inventory, boot backups, separate root, HAL services and Tundra PID 1. +- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Temporary graphical Fastboot trials with SSH recovery and automatic return. +- ![Note](https://img.shields.io/badge/Note-6B7280?style=flat-square) Kernel 4.9-124-google-sargo, Android 9/API 28 adaptation. Historical milestones, without newly dated releases. diff --git a/CHANGELOG.md b/CHANGELOG.md index c797ca7c..ac32180c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,828 +1,547 @@ -# Journal des évolutions — Vulpes OS +# Journal des modifications — Vulpes OS [English](CHANGELOG.en.md) -Une base Gaia, trois plateformes : **PC**, **Android (APK)** et **Tundra / Sargo**. -Ce journal décrit les changements visibles et les étapes techniques importantes. -Les rapports détaillés restent dans la documentation de chaque plateforme. +PC, Android (APK) et Tundra/Sargo. Les statuts indiquent des sources en développement, un APK construit ou une version publiée. Les changements des sources nécessitent une mise à jour du paquet installé. -**Statuts :** « En développement » concerne les sources locales, « APK construit » -un paquet de test, et « Publié » une release accessible dans le dépôt. Un correctif -commun n’apparaît dans un APK installé ou une image téléphone qu’après leur mise à jour. + -

Les paramètres de Gaia, écran Informations système

-

Paramètres de Gaia · capture de la version pour ordinateur.

+## 2.7-preview.20 — APK construit · 13 octobre 2026 + +- ![Change](https://img.shields.io/badge/Change-64748B?style=flat-square) Preview 20 réunit les corrections communes depuis l’APK Preview 17 : paramètres Gaia, apparence, sélecteurs, clavier, contacts, Marketplace et médias. +- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Photo : enregistrement WebM avec miniature et lecture dans Galerie/Vidéo. Autorisation micro demandée ; commandes adaptées aux capacités du matériel. +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Caméra : changement de capteur, arrêt et reprise fiabilisés. PC et Android utilisent leurs API caméra ; Sargo conserve son adaptateur HAL. +- ![Change](https://img.shields.io/badge/Change-64748B?style=flat-square) Sargo : mises à jour Wi-Fi, Bluetooth, appels, SMS/MMS et réglages réseau incluses dans les sources. Les fonctions non raccordées restent indiquées dans les panneaux. +- ![Change](https://img.shields.io/badge/Change-64748B?style=flat-square) Changelog : reprise des textes simplifiés FR/EN, badges conservés, filtres PC/APK/Sargo indépendants des titres. Markdown, site et notes de release issus du même JSON. +- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) APK signé avec la clé des Preview précédentes ; alignement et cohérence des 53 fichiers partagés vérifiés. Validation d’exécution Android en cours. +- ![Note](https://img.shields.io/badge/Note-6B7280?style=flat-square) Gecko PC/Sargo 156.0.1 ; GeckoView Android 156.0. Gaia 2.7.0. L’APK délègue appels/SMS à Android : il ne remplace pas le système téléphonique Android. +- ![Note](https://img.shields.io/badge/Note-6B7280?style=flat-square) Preview expérimentale : Compte Mozilla/Sync, localisation distante, extensions Firefox OS et transfert USB restent incomplets. Sargo : aperçu autour de 16 FPS ; son des vidéos et audio téléphonique à qualifier séparément. +- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Pixel 3a : Preview 20 r62 démarrée en RAM puis flashée sur boot_a. Empreinte relue identique ; démarrage installé, onze contrôles Gaia et dix contrôles natifs réussis. Données conservées, Wi-Fi connecté et enregistrement LTE observé. Pas de nouvelle conversation audio testée. + + + +## En développement — 12 octobre 2026 — Vidéo et réglages de l’heure + +- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Photo : enregistrement WebM VP8/Opus avec autorisation micro, miniature et stockage. Tampon limité à 32 Mio, arrêt avant libération du capteur. +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Photo, Galerie et Vidéo : prise en charge des fichiers WebM et de leurs miniatures. +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Paramètres : véritables sélecteurs Date/Heure Gaia, retour du clavier intégré et lettres non tronquées dans les listes. +- ![Change](https://img.shields.io/badge/Change-64748B?style=flat-square) Améliorer Vulpes : lien vers les tickets du projet. Services non raccordés expliqués dans leurs panneaux. +- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) PC : vidéo simulée 1920 × 1080 relue ; photo, galerie, reprise caméra et navigation vérifiées. +- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Sargo r61 en RAM : vidéo 480 × 640 relue, huit bascules avant/arrière réussies. Médias de test supprimés ; son enregistré non écouté. +- ![Change](https://img.shields.io/badge/Change-64748B?style=flat-square) Sargo : aperçu mesuré autour de 16 FPS contre 12 dans le test précédent. 30 FPS restent à atteindre. +- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Sargo : heure automatique par NITZ, réglage persistant et protection des écritures manuelles. Synchronisation dépendante du réseau ; pas de NTP de secours, fuseau choisi manuellement. +- ![Note](https://img.shields.io/badge/Note-6B7280?style=flat-square) Android : ressources préparées et Java compilé, sans nouvel APK ni test vidéo Android à cette étape. Compte Mozilla/Sync, localisation distante, extensions Firefox OS et transfert USB restent à implémenter. Aucun flash ni publication. + + + +## En développement — 12 octobre 2026 — Appels et partage Wi-Fi + +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Dialogues Gaia : fermeture après ouverture, transitions interrompues et nouvelle saisie après validation refusée. +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Sargo : délai des services opérateur augmenté ; réglages lents séparés des commandes voix/SMS. +- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Sargo : double appel, présentation du numéro et quatre règles de renvoi raccordés à oFono, avec validation et relecture. +- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Sargo : point d’accès Wi-Fi WPA2 ou ouvert, configuration NetworkManager et pare-feu adapté. Mot de passe privé, accessible uniquement à Paramètres. +- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) r59 en RAM : 69 panneaux, dialogues et APN vérifiés sur PC/Pixel. Point d’accès enregistré, activé puis arrêté depuis Gaia ; Wi-Fi précédent restauré. 44 tests Python et régressions des dialogues réussis. +- ![Note](https://img.shields.io/badge/Note-6B7280?style=flat-square) Transit Internet vers un autre appareil non testé. Messagerie vocale, blocage d’appels, PIN/PUK, certificats Wi-Fi et partage USB restent ouverts. Ressources Android synchronisées ; aucun nouvel APK, flash ou publication. + + + +## En développement — 12 octobre 2026 — Paramètres réseau + +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Wi-Fi : adresse IPv4 et débit réels ; ne plus afficher « undefined » lorsqu’une mesure manque. +- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Sargo : choix GSM/UMTS/LTE et modification des APN Internet/MMS existants, avec validation et relecture. +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) APN : refuser les changements d’un profil actif, protéger IMS et les mots de passe, tenter la restauration après échec et signaler son résultat. +- ![Change](https://img.shields.io/badge/Change-64748B?style=flat-square) Sargo : file bornée pour les réglages lents. Lecture des appels en 20 ms pendant une attente de réglages de 5 secondes. +- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) 38 tests Python, 69 panneaux PC et parcours APN/réseau sur Pixel en RAM vérifiés. Ressources Android synchronisées. +- ![Note](https://img.shields.io/badge/Note-6B7280?style=flat-square) Ajout, suppression et réinitialisation des APN indisponibles. Sélection manuelle d’opérateur et certificats encore ouverts. Aucun nouvel APK, flash ou publication. + + + +## En développement — 12 octobre 2026 — Audit des paramètres + +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Paramètres : navigation récupérable après échec d’un contrôleur ou d’un dialogue ; génération cohérente des modules. +- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Filtres d’accessibilité et assistance Vulpes. Panneaux d’origine conservés avec explication des fonctions indisponibles. +- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) PC : statistiques du profil et téléchargements Gecko. Android : statistiques du stockage privé. +- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Sargo : données mobiles, itinérance, horloge et Wi-Fi masqué raccordés. Erreurs des anciennes interfaces réseau du panneau Son corrigées. +- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) 97 vues inventoriées ; 69 panneaux PC/Pixel ouverts et sept dialogues natifs vérifiés. Stockage, données et fuseau relus après écriture sur r51 en RAM. +- ![Note](https://img.shields.io/badge/Note-6B7280?style=flat-square) Les vues contextuelles et services indisponibles ne sont pas tous qualifiés. Java Android compilé, sans nouvel APK. Aucun flash, ticket fermé ni publication. + + + +## En développement — 12 octobre 2026 — Navigation des paramètres + +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Listes déroulantes : sélecteur Gaia avec confirmation/annulation ; choix Clair/Sombre accessible. Gestionnaire SIM adapté à un emplacement. +- ![Change](https://img.shields.io/badge/Change-64748B?style=flat-square) Panneaux indisponibles : explication et commandes désactivées au lieu de liens sans effet. +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Sargo : réseaux Wi-Fi mémorisés séparés des scans lents ; cache conservé lors des lectures d’état. +- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Sargo : informations SIM, centre SMS et accusés de réception reliés au modem. +- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) r45 en RAM : huit contrôles d’apparence, cinq panneaux et changement réel des accusés SMS validés, puis réglage restauré. Sélecteur PC vérifié ; ressources APK préparées. +- ![Note](https://img.shields.io/badge/Note-6B7280?style=flat-square) Certificats Enterprise et autres options opérateur encore indisponibles. Aucun SMS émis, nouvel APK, flash ou publication. + + + +## En développement — 12 octobre 2026 — Correctifs communs sur Pixel 3a + +- ![Change](https://img.shields.io/badge/Change-64748B?style=flat-square) Sargo : boot r39 en RAM avec correctifs partagés et premier mode sombre. Noyau et configuration matérielle du r35 conservés. +- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Démarrage, taille d’écran, réglages persistants, batterie, interface d’appel et Fira vérifiés. Services modem, audio, Wi-Fi et Bluetooth actifs. +- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Sept contrôles d’apparence réussis dans Gaia : thème propagé, en-têtes lisibles, libellés placés et retour au clair. Capture Pixel inspectée. +- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Outil de mise à jour Gaia dans un boot privé, avec empreintes, sources conservées et vérification des composants matériels inchangés. +- ![Note](https://img.shields.io/badge/Note-6B7280?style=flat-square) Aucun nouvel appel, SMS ou appairage testé. Thème complet et comportement Android encore à qualifier. Aucun nouvel APK, flash ou publication. + + + +## En développement — 11 octobre 2026 — Apparence et clavier partagé + +- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Apparence : Clair, Sombre ou Selon le système, partagé entre les applications et conservé au redémarrage. Fonds d’écran, photos et sites externes préservés. +- ![Change](https://img.shields.io/badge/Change-64748B?style=flat-square) Clavier : opérations d’édition mutualisées, sélection et effacement, protection contre les réponses tardives. +- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) PC : neuf contrôles d’apparence et gestes de saisie dans Messages réussis, sans envoi de SMS. Revue visuelle de Paramètres, Messages, Téléphone et Contacts. +- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Android : option expérimentale de clavier Gaia ; clavier Android conservé par défaut et mode de saisie restauré à la fermeture. +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Android : réessayer brièvement le même capteur encore occupé, sans réessayer après refus de permission ou fermeture de Photo. +- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Preview 19 construit localement : signature, alignement, parité et tests du pont/caméra réussis. +- ![Note](https://img.shields.io/badge/Note-6B7280?style=flat-square) Clavier Gaia, caméra avant Android et thème complet encore à qualifier. Sources prêtes pour Sargo, resté sur r35 à cette étape. Aucun upload, flash ni ticket fermé. + + + +## En développement — 11 octobre 2026 — Paramètres et accueil + +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Paramètres : gestion des sonneries, saisie/effacement du code PIN, désactivation du verrouillage et liste des permissions des applications tierces. +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Accueil : première initialisation du second lanceur et aperçus des fonds d’écran. +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Langues : actualiser les libellés Vulpes au changement de langue ; anglais de secours hors français. Fermer le panneau d’échec caméra et les bandeaux d’installation. +- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Android : ouvrir le volet Gaia en touchant la barre de statut. Preview 18 construit, signé et comparé aux sources. +- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) PC : neuf parcours Gaia, cinq contrôles de bandeaux et douze tests activités/caméra réussis. Bluetooth Sargo r35 confirmé par l’utilisateur. +- ![Note](https://img.shields.io/badge/Note-6B7280?style=flat-square) Redémarrage à chaud du compositeur encore instable : fichiers restaurés et retour à r35. Corrections non qualifiées sur Sargo à cette étape ; clavier/thème Android ouverts. Aucun APK publié ni flash. + + + +## En développement — 9 octobre 2026 — MMS et Bluetooth + +- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Bluetooth : adaptateur Gaia partagé, commandes matérielles séparées et accès limité aux applications autorisées. +- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Sargo : activation, découverte, appairage explicite, connexion et suppression depuis le panneau Bluetooth Gaia. Modules audio Bluetooth intégrés. +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Sargo : services réseau Qualcomm et transport MMS raccordés. Une déconnexion D-Bus ne termine plus le daemon MMS. État persistant, reprise sans doublon ni renvoi automatique incertain. +- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) r35 en RAM : MMS avec image visible et accepté par le MMSC Orange ; réception destinataire à confirmer. Cinq appareils Bluetooth détectés, A2DP vers PC validé. +- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) 191 tests Python Tundra et 18 tests JavaScript réussis. Ramdisk allégé et ressources Android synchronisées. +- ![Note](https://img.shields.io/badge/Note-6B7280?style=flat-square) Audio d’appel via casque non qualifié. Aucun nouvel APK, flash ni push à cette étape. + + + +## En développement — 8 octobre 2026 — Caméra et transport MMS + +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Caméra : arrêter le flux avant de détacher la vidéo ; attendre la fermeture du capteur, réinitialiser le viseur et recalculer sa taille après rotation. +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) MMS : préserver les pièces jointes Blob et garder les messages en attente jusqu’à confirmation du transport. +- ![Change](https://img.shields.io/badge/Change-64748B?style=flat-square) Sargo : viseur WebGL réutilisé sans recapture vidéo intermédiaire ; écran maintenu éveillé pendant l’aperçu. +- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) PC et Sargo : parcours Photo/Galerie et huit bascules natives vérifiés. JPEG, focus et zoom fonctionnels ; arrière ~16 FPS, avant ~25–27 FPS dans cet essai. +- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Sargo : file MMS persistante, codec Nuntium et transport oFono. Interface texte/photo testée avec transport simulé. +- ![Note](https://img.shields.io/badge/Note-6B7280?style=flat-square) Données mobiles bloquantes pour les MMS opérateur à cette étape. Ressources Android synchronisées ; aucun nouvel APK, flash ou push. + + + +## 8 octobre 2026 — Rappel et messages d’erreur + +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Dialogues Gaia : texte visible au lieu d’un écran gris avec un bouton seul. +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Journal d’appel : rappel via le même service que le pavé numérique, sans anciennes API Gecko. Fichier partagé PC/Android/Sargo. +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) MMS : signaler clairement l’envoi indisponible, garder texte et pièces jointes, ne plus annoncer un faux mode avion. +- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) PC : dialogues FR/EN et composeur préservé. Sargo en RAM : 11 contrôles Gaia réussis, rappel testé avec modem simulé. +- ![Note](https://img.shields.io/badge/Note-6B7280?style=flat-square) Aucun appel réel ni validation audio dans cette étape. Ressources Android synchronisées, aucun nouvel APK publié. + + + +## En développement — 8 octobre 2026 — SMS et audio + +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) SMS : envoyer en un appui en conservant le clavier. Trombone raccordé au menu Gaia : image, audio, vidéo ou contact. +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Interface : indicateur de volume visible, gestes du verrouillage et balayage des notifications corrigés. +- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) PC : clavier, pièces jointes, Galerie, interface d’appel et volume vérifiés ; notification balayée sans déplacer la page. Aucun nouvel APK publié. +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Sargo : accès à PulseAudio, relais Qualcomm, protocole D-Bus et périphériques de calibration rétablis. Service audio Android concurrent retiré. +- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Sargo : 11 contrôles Gaia, calibration et prévisualisations arrière/avant/arrière réussis. +- ![Note](https://img.shields.io/badge/Note-6B7280?style=flat-square) Appels échouant avant sonnerie avec une erreur réseau dans cet essai. Audio bidirectionnel, geste Pixel et MMS encore non validés. Aucun flash ni publication. + + + +## En développement — 8 octobre 2026 — Fiabilité téléphonie + +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Sargo : service radio maintenu après fermeture d’un client. Jamais de nouvel appel ou SMS automatique après une réponse perdue. Erreurs sans numéro ni texte du message. +- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Diagnostic réseau/audio sans identifiants personnels et paramètres opérateur épinglés dans l’image native. +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) SMS : attendre le vrai gestionnaire Gaia et accepter les identifiants oFono avec majuscules. Contrôle des conversations rétabli sans réémission. +- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Sargo : LTE retrouvé après redémarrage, SMS confirmé envoyé par le modem, microphone non silencieux et lecture audio acceptée. +- ![Note](https://img.shields.io/badge/Note-6B7280?style=flat-square) Réception destinataire, audio d’appel bidirectionnel et stabilité modem restent à qualifier. Compositeur instable au redémarrage à chaud. Aucun nouvel APK, flash ou push. ## 8 octobre 2026 — Mise à jour des sources publiques -### Commun · PC / Android / Tundra - -- Regrouper les correctifs récents de caméra, Contacts, SMS, Marketplace et les outils de construction et de restauration Sargo dans les sources publiques. Mettre à jour les README français et anglais avec les capacités et limites vérifiées. -- Corriger le contrôle de parité Android pour créer son dossier de rapports sur une copie neuve du dépôt. Vérifier que la configuration Wi-Fi native embarque un répertoire de connexions vide. -- Conserver les tickets, feuilles de route, documents internes, données personnelles et clés hors du dépôt. - -### Paquets et qualification - -- L’APK public reste Preview17 : les correctifs du 8 octobre nécessitent une prochaine compilation. Aucune nouvelle image native publiée ni flash dans cette étape. -- Sur Sargo, l’enregistrement cellulaire reste en recherche lors du dernier contrôle ; audio bidirectionnel, appairage Bluetooth et profils restent à qualifier. +- ![Change](https://img.shields.io/badge/Change-64748B?style=flat-square) Publication des correctifs Caméra, Contacts, SMS et Marketplace, et des outils Sargo de construction et de restauration. +- ![Change](https://img.shields.io/badge/Change-64748B?style=flat-square) README français et anglais actualisés avec les fonctions et limites vérifiées. +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Création du dossier de rapports lors du contrôle de parité Android sur un dépôt neuf. +- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Configuration Wi-Fi native vérifiée : aucun profil de connexion embarqué. +- ![Note](https://img.shields.io/badge/Note-6B7280?style=flat-square) Documents internes, données personnelles et clés exclus du dépôt. +- ![Note](https://img.shields.io/badge/Note-6B7280?style=flat-square) APK public inchangé (Preview17) ; correctifs du 8 octobre non inclus. Aucune nouvelle image native publiée. +- ![Note](https://img.shields.io/badge/Note-6B7280?style=flat-square) Sargo : modem encore en recherche réseau ; audio des appels, appairage et profils Bluetooth à valider. ## 8 octobre 2026 — Caméra et parcours Contacts · en développement -### Commun · Gaia / PC / Android / Tundra - -- Sérialiser les ouvertures de caméra, attendre la fermeture du capteur précédent et libérer les pistes et ressources vidéo même après un échec. Stabiliser l’identification avant/arrière sur PC et Android. -- Conserver les images binaires lors des échanges PC/Tundra ; encoder explicitement les photos et dates des contacts pour le pont JSON Android. -- Corriger l’actualisation de la fiche après modification d’un contact. Rétablir les activités Contacts → SMS, sélection d’un numéro et ouverture du pavé numérique, en préservant les demandes imbriquées et les points d’entrée de Gaia. -- Réouvrir Galerie en mode sélection pour ajouter une photo à un contact, même lorsque Galerie est déjà ouverte en consultation. Sélection, recadrage et enregistrement de la photo validés sur PC. -- Ajouter des tests de caméra, de stockage des contacts, de permissions, de transport binaire et de routage des activités. - -### PC - -- Vérifier le parcours original Appareil photo → capture JPEG → Galerie → ouverture de la photo → retour caméra. -- Vérifier dans Gaia : création et modification d’un contact, actualisation de la fiche, recherche sans accents et sans résultats, favoris, destinataire SMS, sélection d’un numéro, pavé numérique prérempli, annulation puis confirmation de suppression. Le test n’émet ni appel ni SMS et supprime son contact temporaire. - -### Tundra · Sargo - -- Vérifier en RAM sur Pixel 3a : autofocus, zoom, quatre alternances avant/arrière avec nouvelles trames, capture JPEG depuis Gaia, stockage, ouverture dans Galerie et arrêt du capteur à l’accueil. -- Vérifier les conversations déjà stockées, le clavier et les notifications. Recherche Wi-Fi : quatre réseaux détectés. Nouvelle émission/réception SMS et audio des appels encore en qualification. -- Ajouter les paquets BlueZ et les bibliothèques nécessaires, verrouillés par SHA256. Le contrôleur Bluetooth démarre, s’active et lance une recherche ; l’appairage depuis Gaia et les profils ne sont pas encore validés. -- Ajouter le magasin de certificats natif : HTTPS vérifié sans désactivation de TLS. Tester les archives altérées, collisions de fichiers et reconstructions répétées. - -- Corriger les fichiers Contacts embarqués dans le build de démarrage : la création, l’édition, le passage vers SMS, le sélecteur et le pavé numérique passent aussi avec les gestes tactiles sur Sargo. - -### Android · APK - -- Publier l’APK Preview17 existant, avec téléchargement et SHA256 vérifiés. Il précède les correctifs du 8 octobre. -- Corriger la transmission des destinataires et du texte de Gaia vers l’application SMS Android, y compris la réponse à une conversation et le numéro saisi sans validation préalable. Tests du pont réussis ; essai Android réel encore nécessaire. -- Réempaqueter les ressources communes corrigées pour la prochaine compilation ; aucun nouvel APK incluant ces correctifs n’est publié. - -**État :** builds natifs de qualification démarrés en RAM. Aucun flash, push de code ni publication de documentation privée dans cette étape. La qualification complète des fonctions de téléphone reste ouverte. +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Caméra : ouvertures successives, libération du capteur et identification avant/arrière corrigées. +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Contacts : transport des photos et des dates corrigé sur PC, Android et Tundra. +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Contacts : actualisation après modification, ouverture de SMS, sélection d’un numéro et préremplissage du pavé numérique rétablis. +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Galerie : ouverture en mode sélection pour la photo d’un contact, même si l’application est déjà ouverte. +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Sargo : fichiers Contacts corrigés inclus dans l’image de démarrage. +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Android : transmission du destinataire et du texte à l’application SMS, y compris les réponses et les numéros non encore validés. +- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Tests de caméra, stockage des contacts, permissions, transport binaire et activités. +- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) PC : capture JPEG, Galerie, photo de contact, création, édition, recherche, favoris et suppression vérifiés. Aucun appel ni SMS émis. +- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Sargo en RAM : autofocus, zoom, quatre bascules de caméra, capture et Galerie vérifiés ; parcours Contacts testés au toucher. +- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Sargo : conversations stockées, clavier et notifications vérifiés ; quatre réseaux Wi-Fi détectés. Nouveaux SMS et audio d’appel à valider. +- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Sargo : BlueZ et ses dépendances avec empreintes SHA256. Activation et recherche Bluetooth testées ; appairage et profils à valider. +- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Sargo : magasin de certificats natif. HTTPS, archives altérées, collisions de fichiers et reconstructions testés. +- ![Note](https://img.shields.io/badge/Note-6B7280?style=flat-square) Preview17 publié avec téléchargement et SHA256 vérifiés. Les correctifs ci-dessus restent dans les sources ; essai SMS Android réel nécessaire. Aucun flash Sargo. ## 6 octobre 2026 — Thème Marketplace intégré · en développement -### Commun · Gaia / PC / Android / Tundra - -- Ouvrir le catalogue avec `mode=app`, conservé dans les liens, la recherche, les filtres, les fiches et le changement de langue. -- Préparer un thème web Vulpes dédié : Fira locale, navigation compacte, boutons tactiles, cartes lisibles, filtres repliables et captures défilantes. Retirer les en-têtes du site dans le mode intégré, tout en conservant l’interface du site classique. -- Conserver les confirmations et messages système côté Gaia. Le thème ne donne aucun privilège supplémentaire aux pages du catalogue. -- Vérifier 16 pages FR/EN, 2 pages en mode classique et les ressources du thème. Rendus mobiles de 320 et 360 pixels inspectés. Lanceurs PC et Sargo mis à jour ; les sources seront reprises au prochain empaquetage Android. - -Fichiers du site prêts localement, non déployés sur le serveur public. Aucun push ni nouvelle publication APK/ROM. +- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Marketplace : thème intégré avec Fira locale, navigation compacte, boutons tactiles, filtres repliables et captures défilantes. +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Conservation de `mode=app` dans la navigation, la recherche, les filtres et le changement de langue. +- ![Change](https://img.shields.io/badge/Change-64748B?style=flat-square) En-têtes du site masqués en mode intégré. Confirmations gérées par Gaia, sans privilèges supplémentaires pour le catalogue. +- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) 16 pages FR/EN, 2 pages classiques et affichages mobiles à 320 et 360 pixels vérifiés. +- ![Note](https://img.shields.io/badge/Note-6B7280?style=flat-square) Thème prêt localement, non déployé. Lanceurs PC et Sargo mis à jour ; intégration Android au prochain APK. ## 6 octobre 2026 — Bandeaux Gaia · en développement -### Commun · Gaia / PC / Android / Tundra - -- Faire disparaître le bandeau « Application installée » après son délai normal, même si la fin d’animation ne remonte pas du composant Gaia. Conserver son apparence et permettre sa fermeture au toucher. -- Annuler les anciens délais lorsqu’un nouveau message remplace le précédent pour éviter une disparition prématurée. -- Vérifier sur PC l’affichage, l’expiration, le remplacement et la fermeture au clic ; vérifier sur le Pixel le retrait effectif du bandeau après cinq secondes. Correctif déployé sur l’essai Sargo en RAM et disponible dans les sources communes pour le prochain APK. - -Aucun flash ni publication dans cette étape. +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Fermeture automatique du bandeau « Application installée », même sans événement de fin d’animation. +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Annulation des anciens délais lorsqu’un nouveau bandeau remplace le précédent. +- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) PC : affichage, expiration, remplacement et fermeture au clic vérifiés. Sargo : disparition après cinq secondes vérifiée en RAM. +- ![Note](https://img.shields.io/badge/Note-6B7280?style=flat-square) Correctif disponible dans les sources communes pour le prochain APK. Aucun flash ni publication. ## 6 octobre 2026 — Wi-Fi et panneaux Paramètres · en développement -### Commun · Gaia / PC / Android / Tundra - -- Terminer l’ouverture d’un panneau Paramètres même si Gecko ne produit pas l’événement de fin d’animation. Conserver les transitions Gaia et éviter les doubles initialisations. -- Correctif dans les sources communes et le serveur PC ; inclus au prochain empaquetage Android. Aucun nouvel APK publié dans cette étape. - -### Tundra · Sargo - -- Corriger les événements de signal Wi-Fi : transmettre le réseau associé et ne pas annoncer de signal de connexion lorsque le téléphone est déconnecté. L’événement incomplet faisait échouer l’affichage des réseaux dans Gaia. -- Sur le Pixel, vérifier le pilote, le service natif, le passage par Gecko et l’affichage du panneau corrigé : quatre points d’accès détectés, regroupés en deux réseaux, sans erreur de recherche. -- Connexion Wi-Fi confirmée par l’utilisateur ; requête HTTPS réelle réussie depuis Gecko vers example.org (HTTP 200, vérification des certificats active). Correctifs appliqués à l’essai en RAM ; aucun flash ni push. +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Paramètres : ouverture des panneaux même sans événement de fin d’animation, sans double initialisation. +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Sargo : événements Wi-Fi corrigés pour rétablir l’affichage des réseaux et signaler correctement la déconnexion. +- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Sargo : quatre points d’accès détectés, regroupés en deux réseaux. Connexion confirmée et requête HTTPS réussie avec certificats vérifiés. +- ![Note](https://img.shields.io/badge/Note-6B7280?style=flat-square) Sources communes, serveur PC et essai Sargo en RAM mis à jour. Correctifs non inclus dans l’APK public. ## 6 octobre 2026 — Marketplace · en développement -### PC / Tundra · Sargo - -- Maintenir le catalogue sous la barre de statut et dans les limites de son application. Masquer sa vue web à l’accueil et dans le multitâche ; la retirer à la fermeture de l’application. -- Éviter qu’une navigation annulée ou l’ancienne page d’erreur masque une nouvelle tentative. -- Afficher un message local et proposer une nouvelle tentative lorsque le catalogue est inaccessible, au lieu de laisser sa page d’erreur recouvrir Gaia. -- Sur PC : navigation, erreur réseau, nouvelle tentative, retour à l’accueil et fermeture depuis le multitâche vérifiés. Sept contrôles directs passent sur Sargo en RAM, sans fenêtre de débogage, avec une page locale et une précondition de déverrouillage forcée ; le catalogue distant nécessite une connexion Internet sur le téléphone. - -### Android · APK - -- Reconstruire l’APK local Preview17 avec le message d’erreur Marketplace commun. Signature, alignement et parité des ressources vérifiés ; nouvelle régression sur appareil Android non effectuée dans cette étape. - -Aucune publication dans cette étape. +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) PC/Sargo : catalogue Marketplace contenu sous la barre de statut, masqué à l’accueil et retiré à la fermeture. +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Une navigation annulée ou une ancienne page d’erreur ne masque plus la nouvelle tentative. +- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Message d’erreur local avec bouton Réessayer lorsque le catalogue est inaccessible. +- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) PC : navigation, erreur réseau, nouvelle tentative, accueil et fermeture vérifiés. +- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Sargo en RAM : sept contrôles réussis avec une page locale et déverrouillage forcé. Catalogue distant non validé sans connexion Internet. +- ![Change](https://img.shields.io/badge/Change-64748B?style=flat-square) APK Preview17 local reconstruit ; signature, alignement et ressources vérifiés. Aucun nouveau test sur appareil Android ni publication. ## 2.7-preview.17 — APK construit · 5 octobre 2026 -### Commun · Gaia / PC - -- Remettre Marketplace sur l’accueil avec son icône originale et le catalogue Vulpes. Ouvrir sa version française pour les langues fr, anglaise sinon. -- Adapter les téléchargements du catalogue en installations confirmées : vérifier les archives ZIP, ajouter l’application à l’accueil et permettre son lancement et sa désinstallation. -- Conserver les applications installées séparément des sources Gaia, sur des origines distinctes et sans privilèges système hérités des anciens manifestes. -- Valider sur PC le parcours réel avec 2048 : catalogue, confirmation, installation, lancement, persistance et désinstallation. Contrôler les archives corrompues, chemins invalides, limites, annulation et confirmations à usage unique. - -### Android · APK - -- Construire et signer Preview17 avec le même Marketplace et le même service d’installation. Utiliser la vue GeckoView existante pour le catalogue et le stockage privé Android pour les paquets. -- Vérifier sur émulateur Android 16/API36 le clic Installer, la confirmation, l’apparition sur l’accueil et le lancement de 2048 sans accès au pont système Vulpes. Vérifier aussi la conservation après arrêt forcé et relance, puis la désinstallation. - -### Tundra · Sargo - -- Préparer une image de boot locale incluant Marketplace, l’installateur et le serveur des applications ajoutées. Démarrage en RAM effectué sur le Pixel le 6 octobre : accueil et ressources Marketplace vérifiés, sans flash. Neuf contrôles système sur dix passent ; le contrôle global Gaia relève une variation du viewport au déverrouillage. Installation de paquets sur Sargo encore à qualifier. - -**Limites :** compatibilité initiale avec les applications web empaquetées. Les anciennes API privilégiées, applications hébergées et services disparus ne sont pas restaurés automatiquement. Rien publié sur Forgejo ni sur le site. +- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Marketplace sur l’accueil, avec son icône originale et le catalogue Vulpes en français ou en anglais. +- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Installation confirmée des applications ZIP, lancement depuis l’accueil et désinstallation. +- ![Change](https://img.shields.io/badge/Change-64748B?style=flat-square) Applications installées isolées des sources Gaia et des privilèges système. +- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) PC : parcours complet avec 2048 et contrôles des archives, chemins, limites, annulations et confirmations à usage unique. +- ![Change](https://img.shields.io/badge/Change-64748B?style=flat-square) APK Preview17 construit et signé avec Marketplace, GeckoView et stockage privé des paquets. +- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Émulateur Android 16/API 36 : installation, lancement, isolation, conservation après redémarrage de l’application et désinstallation de 2048. +- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Sargo en RAM le 6 octobre : accueil et ressources Marketplace vérifiés, 9 contrôles système sur 10 réussis. Variation du viewport au déverrouillage ; installation de paquets à valider. +- ![Note](https://img.shields.io/badge/Note-6B7280?style=flat-square) Prise en charge initiale des applications web empaquetées. Anciennes API privilégiées et applications hébergées non restaurées. Aucune publication à cette étape. ## 2.7-preview.16 — APK construit · 5 octobre 2026 -### Commun · Gaia / PC - -- Rétablir les sélecteurs Gaia de fond d’écran et de sons, leur retour vers Paramètres et leur annulation par Accueil. Partager le transport des images et sons enregistrés entre PC et Android. -- Propager les changements de langue à l’accueil, y compris sans permission de lecture générale des réglages. Vérifier les libellés FR/EN et la persistance. -- Rétablir l’arrêt du balayage horizontal sur une page et masquer les applications de développement sur l’accueil, sans les supprimer des sources. -- Utiliser le logo Vulpes fourni dans le splash et adapter les composants originaux de la liste des sons au moteur actuel. -- Vérifier sur PC deux changements consécutifs de fond, le choix d’un son et les panneaux Gaia restaurés. - -### Android · APK - -- APK local Preview16 construit et signé ; contenu commun et version vérifiés. Aucune publication dans cette étape. -- Valider Vulpes comme lanceur HOME, afficher le verrouillage Gaia après veille et rétablir le retour Android depuis la caméra ouverte au verrouillage. -- Masquer les barres système Android en mode immersif, en conservant leur accès temporaire par balayage. -- Tests sur émulateur Android 16/API36 : verrouillage, caméra/retour, FR/EN, accueil sans applications de test, fond d’écran et son. Fond conservé après arrêt forcé et relance, sans changement répété pendant 15 secondes. Autres appareils Android non qualifiés dans cette étape. - -### Tundra · Sargo - -- Inclure les mêmes correctifs et la politique des applications dans une nouvelle image de boot de test. Le boot précédent réappliquait une copie ancienne des fichiers et annulait les modifications à chaud. -- Démarrage en RAM sur le Pixel réussi : six contrôles de langues/accueil/sélecteurs et dix contrôles système passent. Sélecteurs image/son vérifiés sans écraser les choix personnels. Le boot installé reste Preview15 ; aucune partition flashée dans cette étape. - -**Limites :** l’installation tierce FFAA reste ouverte. Le site dépend d’anciennes API mozApps et d’un script nomodule ; un installateur isolant les applications et leurs permissions reste à construire. Le choix d’un son Gaia ne modifie pas la sonnerie globale Android ; l’audio d’appel réel n’a pas été qualifié ici. +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Sélecteurs Gaia de fond d’écran et de sons rétablis, avec retour vers Paramètres et annulation par Accueil. +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Changement de langue appliqué à l’accueil et conservé après redémarrage. +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Arrêt du balayage horizontal sur chaque page ; applications de développement masquées sur l’accueil. +- ![Change](https://img.shields.io/badge/Change-64748B?style=flat-square) Logo Vulpes au démarrage et liste des sons adaptée au moteur actuel. Transport des médias partagé entre PC et Android. +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Android : verrouillage Gaia après veille et retour depuis la caméra du verrouillage. Mode lanceur HOME validé. +- ![Change](https://img.shields.io/badge/Change-64748B?style=flat-square) Android : barres système masquées en mode immersif, accessibles par balayage. APK Preview16 construit et signé. +- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) PC et émulateur Android 16/API 36 : fonds d’écran, sons et panneaux vérifiés ; langue, verrouillage et persistance du fond testés sur Android. +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Sargo : image de démarrage mise à jour pour ne plus restaurer d’anciens fichiers. +- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Sargo en RAM : 6 contrôles d’interface et 10 contrôles système réussis. Installation conservée en Preview15, sans flash. +- ![Note](https://img.shields.io/badge/Note-6B7280?style=flat-square) APK non publié. Installation FFAA et audio d’appel à valider ; le choix d’un son Gaia ne change pas la sonnerie Android. ## En développement — 4 octobre 2026 — Installation autonome Sargo -### Tundra · Sargo - -- Construction d’un volume ext4 neuf directement destiné à userdata : système, données et composants matériels vendor/modem embarqués. Le démarrage ne cherche plus de fichiers dans une installation Droidian ; aucune session Android ni connexion ADB n’est nécessaire à l’installation. -- Installateur Fastboot en Python : contrôle du modèle Sargo, du déverrouillage, des tailles et des empreintes, puis remplacement de userdata, dtbo_a, vbmeta_a et boot_a. Le bootloader reste déverrouillé ; les calibrations propres au téléphone sont conservées. -- Sauvegarde complète de userdata hors ligne, comparaison intégrale avec le téléphone et vérification de l’archive de restauration avant le flash. Les sauvegardes restent privées ; une restauration réelle n’a pas été effectuée dans cette étape. -- Deux démarrages depuis le stockage interne réussis : onze contrôles Gaia et dix contrôles natifs par démarrage, image de boot relue et vérifiée, réglages conservés. Le slot A est marqué réussi uniquement après validation du démarrage Gaia courant. -- Variante générique sans serveur SSH ni clés de maintenance : flash de boot_a et démarrage réel réussis. La variante de développement est ensuite remise sur le Pixel pour poursuivre les travaux. Les images du paquet générique conservent leur état initial vierge. -- Contrôles finaux sur le démarrage autonome de développement : Photo (dont bascules de capteur), clavier Gaia et notifications réussis. -- Paquet Fastboot local assemblé avec empreintes et rapport de qualification. 44 tests ciblés passent. Il n’est pas encore publié : la provenance et la redistribution de certains composants matériels restent à finaliser. -- Limites : qualification sur un Pixel 3a, pas sur toutes les combinaisons de firmware ; volume de 8 Gio comprenant 1 Gio de données modifiables, sans extension automatique. Les fonctionnalités matérielles restent expérimentales. - -### PC / Android · APK - -- Aucun changement d’interface ni nouveau binaire dans cette étape. Gaia reste partagé entre les trois plateformes. +- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Sargo : volume ext4 autonome pour userdata, avec système, données et composants vendor/modem. Installation indépendante de Droidian et d’ADB. +- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Installateur Fastboot avec contrôles du modèle, du déverrouillage, des tailles et des empreintes. Remplacement de userdata, dtbo_a, vbmeta_a et boot_a ; calibrations conservées. +- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Sauvegarde complète de userdata et archive de restauration vérifiées avant le flash. Restauration réelle non testée. +- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Deux démarrages installés réussis : 11 contrôles Gaia et 10 contrôles natifs par démarrage, réglages conservés et boot relu conforme. +- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Variante sans SSH ni clés de maintenance démarrée après flash. Variante de développement réinstallée ; caméra, clavier et notifications vérifiés. +- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Paquet Fastboot local assemblé ; 44 tests ciblés réussis. +- ![Note](https://img.shields.io/badge/Note-6B7280?style=flat-square) Paquet non publié : provenance et redistribution de composants matériels à finaliser. Essais sur un seul Pixel 3a ; volume de 8 Gio dont 1 Gio modifiable, sans extension automatique. ## En développement — 3 octobre 2026 — Installation Sargo par appareil -### Tundra · Sargo - -- Collecte des six partitions de démarrage en lecture seule et du stockage existant, liés à l’identité du téléphone et au slot A. Les sauvegardes restent privées. -- Dossier appareil portable : construction RAM, contrôle du boot, installation et export du secours peuvent l’utiliser sans l’inventaire partagé du Pixel de développement. Un build lié à un dossier appareil refuse le repli implicite vers cet inventaire. -- Transfert SSH contrôlé : vérification de l’identité et du stockage, contrôle des empreintes et création des fichiers sans remplacer les données existantes. Aucun formatage, flash ou redémarrage dans l’outil de transfert. -- Construction et transfert testés sur le Pixel. Premier boot RAM : onze contrôles Gaia et dix contrôles natifs réussis ; clavier et notifications réussis. Le contrôle caméra a rencontré un délai d’autofocus dépassé, conservé dans le rapport. -- Second boot de la même image : réglage Gaia conservé, onze contrôles Gaia et dix contrôles natifs réussis ; caméra, clavier et notifications réussis. Le timeout autofocus du premier essai reste documenté, pas déclaré corrigé. -- Retour au build installé vérifié : démarrage Gaia réussi et partition de boot inchangée. Un nouveau transfert refuse bien de remplacer les données déjà utilisées. -- Provenance du HAL précisée : paquet et recette de téléchargement identifiés, recettes UBports historiques récupérées. La correspondance exacte avec l’ancien artefact Android et son manifeste de sources reste à établir. -- Prise en charge toujours limitée au noyau qualifié et au stockage ext4/LVM compatible. Ce travail ne fournit pas encore une installation directe depuis Android d’origine. - -### PC / Android · APK - -- Aucun changement d’interface ni nouvelle compilation dans cette étape. +- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Sargo : dossier appareil portable pour les builds, l’installation et le secours, indépendant de l’inventaire du Pixel de développement. +- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Collecte en lecture seule des six partitions de démarrage et du stockage, liée à l’identité du téléphone. Sauvegardes privées. +- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Transfert SSH avec contrôle d’identité, du stockage et des empreintes, sans écrasement, formatage ni flash. +- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Deux boots RAM : 11 contrôles Gaia et 10 contrôles natifs réussis chacun ; réglage conservé. Timeout autofocus au premier essai, caméra fonctionnelle au second. +- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Retour au build installé vérifié ; boot inchangé et écrasement des données refusé. +- ![Note](https://img.shields.io/badge/Note-6B7280?style=flat-square) Timeout autofocus non déclaré corrigé. Provenance exacte du HAL encore à établir ; noyau et stockage ext4/LVM compatibles requis, sans installation directe depuis Android d’origine. ## En développement — 3 octobre 2026 — Préparation et secours Sargo -### Tundra · Sargo - -- Ajout d’un outil de préparation locale : contrôle du manifeste et de l’archive, décompression bornée, vérification ext4 et création d’un espace de données vierge propre à l’installation. Les dossiers existants ne sont pas écrasés. -- Ajout d’un kit de restauration privé exportable hors du dépôt. Il conserve la sauvegarde originale de boot_a et un outil autonome qui vérifie le modèle, le numéro de série, le slot et les empreintes avant toute restauration explicite. Il ne constitue pas une sauvegarde des données utilisateur. -- La restauration existante ne dépend plus de la présence de l’image de diagnostic. Les contrôles de la sauvegarde sont conservés. -- Construction système : référence matérielle ARM64 désormais explicite, avec refus d’une référence limitée à l’affichage ou dépourvue des services requis. -- Préparation de l’archive système réelle et vérification locale du kit de secours effectuées. Aucun nouveau flash ni nouvelle ROM publiée ; l’installation automatique sur un autre Sargo reste à finaliser. - -### Commun · PC / Android / Tundra - -- Le journal JSON partagé est conservé dans les futures publications de sources ; les documents de travail Markdown restent exclus. - -### PC / Android · APK - -- Aucune modification fonctionnelle ni nouvelle compilation dans cette étape. +- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Sargo : préparation locale des paquets avec contrôles d’empreintes, décompression bornée, vérification ext4 et création de données vierges sans écrasement. +- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Kit de secours privé avec sauvegarde de boot_a et contrôles du modèle, du numéro de série, du slot et des empreintes. Données utilisateur non incluses. +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Restauration possible sans l’image de diagnostic. +- ![Change](https://img.shields.io/badge/Change-64748B?style=flat-square) Construction système : référence matérielle ARM64 complète exigée. +- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Archive système préparée et kit de secours vérifié localement. Installation sur un autre Sargo à finaliser ; aucun flash ni ROM publiée. +- ![Change](https://img.shields.io/badge/Change-64748B?style=flat-square) Journal JSON inclus dans les prochaines publications de sources ; documents de travail exclus. ## En développement — 3 octobre 2026 — Sargo : récupération modem et installation vierge -### Tundra · Sargo - -- Reconstruction de l’image système avec le correctif du cache des polices et la création du répertoire privé d’oFono au premier démarrage. -- Récupération limitée au sous-système modem : test contrôlé avec passage hors ligne, remise en service et réenregistrement réseau en environ 22 secondes, sans redémarrage du téléphone. Cela ne corrige pas la cause du watchdog IMS du firmware. -- Démarrage Gaia, rendu matériel, polices Fira, deux capteurs photo, focus/zoom, clavier et notifications visuelles contrôlés sur le profil neuf. Scan Wi-Fi encore fonctionnel après le redémarrage du modem. -- Deux démarrages réussis avec conservation du même réglage Gaia sur un espace de données séparé. Caméra, clavier et notifications revérifiés au second démarrage. La partition de boot installée et les données existantes sont conservées. -- Aucune nouvelle image native publiée : le paquet public d’installation/restauration reste à terminer. - -### PC / Android · APK - -- Aucun changement de l’interface commune ni nouvelle compilation APK dans cette étape. +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Sargo : cache des polices et création du dossier privé oFono au premier démarrage corrigés dans l’image système. +- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Récupération du modem sans redémarrer le téléphone. Réenregistrement réseau mesuré à environ 22 secondes lors d’un essai contrôlé. +- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Profil neuf : Gaia, rendu matériel, Fira, deux caméras, focus, zoom, clavier et notifications vérifiés. Scan Wi-Fi fonctionnel après redémarrage du modem. +- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Deux démarrages avec conservation des réglages dans un espace de données séparé. Installation existante préservée. +- ![Note](https://img.shields.io/badge/Note-6B7280?style=flat-square) Cause du watchdog IMS non corrigée. Paquet public d’installation et de restauration encore incomplet ; aucune image publiée. ## 1er octobre 2026 — Sargo : préparation de la distribution — en développement -### Tundra · Sargo - -- Image système Preview 15 reconstruite avec profils vierges, exclusion des documents de travail et conservation des licences ; inventaire des 493 paquets embarqués. -- Vérification ext4 et contrôle du contenu effectués. Ces contrôles ne constituent pas une validation de démarrage. -- Inventaire du stockage en lecture seule et génération de la table de montage depuis cet inventaire, au lieu de reprendre systématiquement les dimensions du Pixel de développement. Prise en charge limitée au volume ext4 à une seule étendue sur userdata Sargo ; aucun formatage ajouté. -- Image nettoyée transférée et vérifiée sur le Pixel chargé. Essais temporaires en RAM, sans écriture de boot_a ; retour automatique vers le build v59 et redémarrage de Gaia contrôlés. -- SIM reconnue, enregistrement LTE et scan Wi-Fi contrôlés sur cette image ; ces contrôles ne valident pas les appels, SMS ou données mobiles de bout en bout. -- Correction des droits du cache des polices au premier démarrage : fc-cache utilise le compte de session. Démarrage Gaia, rendu matériel, Fira, deux capteurs photo, clavier et notifications visuelles contrôlés en RAM. La qualification du fichier reconstruit reste incomplète. -- Redémarrage intermittent identifié dans le firmware IMS du modem ; politique de récupération ciblée appliquée et second démarrage avec LTE, caméra, clavier et notifications réussi. La récupération après un nouveau crash IMS reste non testée. L’image publique reste non flashable et non publiée. - -### PC / APK - -- Aucun changement fonctionnel ni nouvelle compilation APK dans cette étape. +- ![Change](https://img.shields.io/badge/Change-64748B?style=flat-square) Sargo : image Preview15 reconstruite avec profils vierges, licences et inventaire des 493 paquets. Documents de travail exclus. +- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Table de montage générée depuis l’inventaire du stockage. Prise en charge limitée à une seule étendue ext4 sur userdata. +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Droits du cache des polices au premier démarrage : fc-cache exécuté avec le compte de session. +- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Contrôles ext4, contenu et transfert réussis. Essais RAM sans écriture de boot_a ; retour au build v59 vérifié. +- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) SIM, LTE, scan Wi-Fi, Gaia, rendu matériel, Fira, caméras, clavier et notifications vérifiés en RAM. +- ![Change](https://img.shields.io/badge/Change-64748B?style=flat-square) Récupération ciblée après identification d’un redémarrage intermittent du firmware IMS. Second démarrage réussi ; nouveau crash IMS non testé. +- ![Note](https://img.shields.io/badge/Note-6B7280?style=flat-square) Qualification de l’image incomplète, sans validation des appels, SMS ou données mobiles de bout en bout. Image non publiée et non flashable. ## 2.7-preview.15 — 30 septembre 2026 — publiée +- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Première publication des sources PC, Android et Tundra, avec README FR/EN, licences et scripts de préparation. Fichiers privés exclus. +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) SMS : dates des bulles et états d’envoi/échec corrigés. Libération et remplacement des flux caméra fiabilisés. +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) PC : tests de navigation stabilisés, délai de démarrage à froid ajusté et fermeture propre après dépassement du délai Gaia. +- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) PC avec Gecko 156.0.1 : 42 tests de services, démarrage, navigation, réglages, gestes, historique, isolation et persistance vérifiés. +- ![Change](https://img.shields.io/badge/Change-64748B?style=flat-square) APK Preview15 signé avec la clé précédente et GeckoView 156.0 ; parité des ressources vérifiée. +- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Émulateur Android 16 : mise à jour, Photo/Galerie, reprise caméra, dix retours d’arrière-plan et récupération du rendu validés. +- ![Note](https://img.shields.io/badge/Note-6B7280?style=flat-square) Sargo : sources incluses, build privé v59 conservé. Aucune ROM générique ; démarrage lié au stockage et aux images préinstallées. +- ![Note](https://img.shields.io/badge/Note-6B7280?style=flat-square) Sargo : aperçu proche de 16 i/s ; flash, vidéo, rotation et veille profonde incomplets. Audio d’appel et dialogue après envoi SMS à examiner. + [Release](https://nnsprod.com/git/oversu/VulpesOS/releases/tag/v2.7-preview.15) -### Commun · Gaia / PC / Android / Tundra - -- Première publication des sources communes, avec README français et anglais, licences et scripts de préparation. Documentation interne, roadmap, profils, clés et images privées exclus du dépôt public. -- Intégration des derniers correctifs SMS (dates des bulles, états d’envoi/échec) et du cycle de vie caméra. Les deux entrées du 30 septembre détaillent les correctifs matériels. - -### PC - -- Copie de publication testée avec moteur Gecko 156.0.1 et profil neuf : démarrage Gaia, navigation, réglages, gestes, historique et isolation des pages. 42 tests de services passants ; persistance des services après redémarrage vérifiée. -- Stabilisation du test de navigation lors du remplacement normal d’un document et allongement de l’attente de démarrage à froid. Fermeture propre de l’hôte même si Gaia dépasse le délai de préparation. - -### Android · APK - -- Preview 15 signée avec la clé des précédentes Preview, GeckoView 156.0 ; parité des ressources communes vérifiée. Mise à jour installée sur émulateur Android 16. -- Photo et Galerie : capture, affichage et reprise du capteur validés. Dix passages en arrière-plan et récupération après arrêt du processus d’affichage validés. Cette qualification ne couvre pas tous les téléphones Android. - -### Tundra · Sargo - -- Sources du runtime incluses ; l’image privée v59 reste celle qualifiée sur le téléphone de développement. Aucune ROM générique flashable publiée : démarrage encore lié au stockage inventorié, aux images préinstallées et aux accès de maintenance privés. -- Limites conservées : aperçu caméra proche de 16 i/s, flash/vidéo, rotation et veille profonde incomplets ; audio d’appel à qualifier et grand dialogue après envoi SMS à identifier. - ## En développement — retours SMS et caméra · 30 septembre 2026 -### Commun · Gaia / PC / Tundra - -- SMS : convertir les dates en nombres avant de construire les bulles. Corrige les conversations vides dues à une erreur de formatage de date. Parcours du compositeur et affichage des messages envoyés/reçus vérifiés sur PC avec un modem simulé. -- Photo : fermeture du flux rendue idempotente; détacher les vidéos, attendre la libération du capteur et libérer le contexte WebGL avant de changer de caméra. Identifier chaque session pour qu’un ancien flux ne puisse pas arrêter son remplaçant. Allers-retours arrière/avant vérifiés avec de nouvelles trames à chaque bascule; retour Accueil et arrêt du capteur validés lors de deux démarrages RAM. - -### Tundra / Sargo - -- Vibreur : utiliser le contrôle brightness du pilote DRV2624; la commande legacy activate ne coupait pas physiquement le moteur. Coupure confirmée par l'utilisateur; impulsion, motif 200/200/200 ms et arrêt du service vérifiés, avec retour à zéro. Ajouter une coupure de secours au démarrage et à la sortie du service. -- L'utilisateur confirme focus, zoom et transport SMS dans les deux sens sur v54. Cinq conversations existantes affichées sur le Pixel, avec leurs bulles et sans erreur de date. -- v59 installé sur boot_a après deux démarrages RAM réussis; démarrage installé et relecture vérifiés, slot A marqué réussi. Sources et runtime PC synchronisés. Le grand dialogue après un envoi réel reste à identifier; la fluidité caméra reste un chantier distinct. - -### Android · APK - -- Sources Gaia communes corrigées; aucun nouvel APK construit ou publié. +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) SMS : conversion des dates pour corriger les conversations vides. +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Caméra : fermeture des flux et libération du capteur corrigées ; une ancienne session ne peut plus fermer la suivante. +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Sargo : arrêt physique du vibreur via DRV2624, avec coupure de secours au démarrage et à l’arrêt du service. +- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) PC : composition et affichage des SMS vérifiés avec modem simulé. Sargo : bascules caméra et arrêt du capteur testés sur deux boots RAM. +- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Sargo v54 : focus, zoom et SMS dans les deux sens confirmés par l’utilisateur. Cinq conversations affichées ; arrêt du vibreur confirmé. +- ![Change](https://img.shields.io/badge/Change-64748B?style=flat-square) Sargo v59 installé sur boot_a après deux boots RAM ; démarrage, relecture et slot A vérifiés. +- ![Note](https://img.shields.io/badge/Note-6B7280?style=flat-square) Dialogue après envoi SMS et fluidité caméra encore à traiter. Sources communes mises à jour, sans nouvel APK. ## En développement — SMS et Photo · 30 septembre 2026 -### PC / Tundra - -- SMS : conserver le message et sa conversation après un refus du modem; transmettre les événements Gaia « envoi », « échec » et « envoyé ». -- SMS : distinguer mise en file oFono et confirmation d'envoi, avec suivi persistant des états terminaux. La confirmation modem n'est pas un accusé de réception du destinataire. -- PC : tests de l'échec d'envoi visible dans la liste des conversations; Photo et Galerie testées avec capture tactile, ouverture, fermeture et reprise de la caméra. - -### Tundra / Sargo - -- Photo : raccorder les zones de focus tactile, le zoom numérique et la capture JPEG du capteur à l'interface Gaia originale. Aperçu conservé en 640 × 480; JPEG arrière 4032 × 3024 (12,19 Mpx) et avant 3264 × 2448 (7,99 Mpx) mesurés directement. Focus fixe à l'avant. -- Protocole caméra : séparer les traces du HAL des données binaires, borner les réponses et relancer l'aperçu après une capture. Une erreur de focus ne doit plus fermer la session. -- SMS : refuser explicitement l'envoi si le modem n'est pas enregistré; conserver les états sent/failed même après disparition de l'objet oFono. La réception a produit des notifications opérateur sur v49 après retour du réseau LTE. -- Qualification : deux démarrages v54 en RAM puis démarrage depuis boot_a validés; image installée sur le Pixel et slot marqué réussi après relecture. Persistance, interface Gaia et tests caméra passants. L'enregistrement réseau reste instable : essais SMS arrêtés avant émission, aucune livraison de bout en bout confirmée. - -### Android · APK - -- Aucun nouvel APK construit ou publié dans cette passe. La Preview 14 disponible reste inchangée. +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) PC/Tundra : conservation des SMS après refus du modem et transmission des états envoi, échec et envoyé à Gaia. +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Sargo : états SMS persistants après disparition de l’objet oFono ; envoi refusé si le modem n’est pas enregistré. +- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Sargo : focus tactile, zoom numérique et capture JPEG du capteur dans Photo Gaia. +- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Photos mesurées : 4032 × 3024 à l’arrière, 3264 × 2448 à l’avant. Aperçu 640 × 480 ; focus avant fixe. +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Caméra native : données binaires séparées des traces HAL, réponses bornées et reprise de l’aperçu après capture ou erreur de focus. +- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) PC : échec d’envoi visible et parcours Photo/Galerie vérifiés. Sargo v54 : deux boots RAM puis installation, persistance et caméra validées. +- ![Note](https://img.shields.io/badge/Note-6B7280?style=flat-square) Réseau instable ; aucune livraison SMS de bout en bout confirmée dans cette passe. Confirmation modem distincte de l’accusé de réception. APK public inchangé (Preview14). ## 2.7-preview.14 — APK publié · 30 septembre 2026 +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Informations système : alignement des versions et polices Fira dans les composants Shadow DOM. +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) PC/Tundra : taille des touches en portrait, curseur dans le champ actif et réponses de saisie obsolètes. +- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) PC/Tundra : pont clavier Gaia et pavé numérique pour les destinataires SMS. Clavier Android inchangé. +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Verrouillage : redimensionnement sans fenêtre clavier. Notifications silencieuses respectées, y compris après restauration. +- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Sargo : notifications persistantes dans le volet et sur le verrouillage, avec ouverture de l’application concernée. +- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Sargo : vibrations annulables, limitées à dix secondes ; profils Wi-Fi privés persistants, reconnexion automatique et oubli du réseau. +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Sargo : stockage des SMS entrants avant acquittement, dédoublonnage et création du dossier d’état. +- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Sargo : commandes écouteur/haut-parleur, micro muet et volume d’appel. Bascule, sourdine et volume testés hors appel. +- ![Change](https://img.shields.io/badge/Change-64748B?style=flat-square) Installation : flash des images de diagnostic avec débogueur refusé. Sargo v48 installé après deux boots RAM ; persistance, relecture et slot A vérifiés. +- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) PC : Calendrier et clavier vérifiés. Sargo : clavier, notifications et reconnexion Wi-Fi vérifiés ; son des notifications et téléphonie réelle à valider. +- ![Change](https://img.shields.io/badge/Change-64748B?style=flat-square) APK Preview14 signé et publié. Démarrage, versions et polices vérifiés sur émulateur Android 16. Aucune image Sargo publiée. + [Release](https://nnsprod.com/git/oversu/VulpesOS/releases/tag/android-2.7-preview.14) -L’APK Preview 14 est publié. La version PC est à jour et le build privé Sargo v48 est installé et testé ; aucune image téléphone n’est publiée avec cette release. - -### Commun · PC / APK / Gaia - -- Informations : suppression du retrait supplémentaire des lignes de version avant « Dernière mise à jour » ; alignement contrôlé sur PC et Android. -- Polices : normalisation des noms de fontes dans les feuilles des composants Shadow DOM, y compris les styles ajoutés après leur création. Les polices d’icônes restent distinctes. -- Calendrier : ouverture du mois et absence d’erreur JavaScript vérifiées sur PC. - -### PC / Tundra - -- Clavier : calculer le mode portrait depuis l’écran, et non depuis la petite fenêtre du clavier. Touches de 60 px CSS environ sur Pixel ; rotation et retour au portrait testés sur PC à densité 3. -- SMS : replacer le curseur dans le champ actif lorsque Gecko conserve la sélection du destinataire ; conserver les sélections valides pour l’édition. -- Notifications Gaia : respecter les indicateurs silencieux, sans son et sans vibration, y compris à la restauration. - -- Clavier Gaia : pont de saisie natif, pavé numérique pour les destinataires SMS, événements de sélection corrigés et protection contre les réponses d’un champ devenu inactif. Saisie destinataire et message testée sur PC et Pixel ; l’envoi opérateur reste à qualifier. -- Verrouillage : accepter l’absence de fenêtre clavier lors d’un redimensionnement. -- Notifications natives : stockage, propriété par application et ouverture du point d’entrée Téléphone déclaré par Gaia. Présentation et suppression dans le volet et sur le verrouillage validées sur Sargo ; écoute du son à qualifier. - -### Tundra / Sargo — build v48 installé - -- Vibrations : pont vers le moteur Sargo, motifs bornés à dix secondes, séquencement sans bloquer les boutons physiques et annulation. Activation et arrêt mesurés sur le pilote. - -- Wi-Fi : profils privés conservés avec reconnexion automatique et commande d’oubli ; tests du stockage et de la restauration en cas d’échec. Connexion et reconnexion après redémarrage RAM vérifiées sur Sargo ; fichiers de profil privés (0600). -- SMS entrants : stockage persistant avant acquittement, dédoublonnage des reprises et services créant leur dossier d’état au démarrage. Défaut de montage du dossier corrigé après reproduction sur Sargo. -- Audio : commandes écouteur/haut-parleur, micro et volume d’appel ajoutées au pont ; bascule écouteur/haut-parleur, sourdine et volume vérifiés sans appel. Conversation dans les deux sens encore à vérifier. -- Images de diagnostic avec débogueur local interdites au flash par l’outil d’installation. v48/preview.14 écrite sur boot_a après deux démarrages RAM et contrôle de persistance ; démarrage installé validé, image relue conforme et slot A marqué réussi. v43 à v48 n’activent pas de débogueur. - -### Android · APK - -- APK preview.14 construit, signé et publié sur Forgejo. Démarrage, informations de version, alignement et sélection des fontes des composants contrôlés sur Android 16 émulé. Le clavier Android reste celui du système. - ## 2.7-preview.13 — parcours dans les applications · 27 septembre 2026 -### Commun · Gaia / PC - -- Calendrier : une alarme planifiée avec succès ne provoque plus un rejet lors de la sauvegarde de l’événement; les erreurs de planification sont transmises au demandeur. -- Rappels : ignorer ceux d’événements supprimés; ne plus marquer une notification refusée comme livrée. La prise en charge des notifications natives Tundra reste à terminer. -- Notifications : accepter un texte brut vide sans chercher une traduction « undefined ». -- Galerie : vignettes de taille positive même au démarrage masqué; annulation des mises à jour d’aperçu après fermeture de l’éditeur. -- SMS : appliquer les filtres correspondant, lu/non lu, période et livraison; tests de lecture et suppression des conversations. -- Revue approfondie : lecture, déplacement et suppression d’une vidéo WebM; édition noir et blanc et copie d’une photo; création et suppression d’un événement. Interfaces Gaia originales conservées. - -- Qualification depuis une copie isolée : démarrage, tests bureau, persistance et empaquetage Android réussis sans réécriture des sources. - -### Android · APK - -- APK preview.13 construit et signé; Photo/Galerie, Calendrier, dix retours d’arrière-plan et récupération du rendu validés sur Android 16 émulé. - -### Tundra · Sargo - -- Build v40/preview.13 installé après deux démarrages RAM : Gaia, caméra, Wi-Fi, luminosité et commandes contrôlés; relecture de boot_a conforme et slot validé. Limites restantes dans `docs/APPLICATION-REVIEW.md`. +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Calendrier : sauvegarde des événements avec alarme, remontée des erreurs et exclusion des rappels d’événements supprimés. +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Notifications : texte vide accepté ; notification refusée non marquée comme livrée. Support natif Tundra encore incomplet. +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Galerie : taille des vignettes au démarrage masqué et arrêt des mises à jour après fermeture de l’éditeur. +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) SMS : filtres par correspondant, lecture, période et livraison appliqués. +- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) PC : vidéo WebM, édition et copie de photo, calendrier, lecture et suppression des conversations vérifiés. +- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Copie isolée : démarrage, tests PC, persistance et empaquetage Android réussis. +- ![Change](https://img.shields.io/badge/Change-64748B?style=flat-square) APK Preview13 construit et signé. Photo/Galerie, Calendrier, dix reprises d’arrière-plan et récupération du rendu validés sur Android 16 émulé. +- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Sargo v40 installé après deux boots RAM ; Gaia, caméra, Wi-Fi, luminosité, commandes et relecture de boot_a vérifiés. ## 2.7-preview.12 — revue des applications · 27 septembre 2026 -### Commun · Gaia / PC - -- Icônes internes : variantes raster Gaia originales de 1× à 2,25× rétablies dans - 1 047 références CSS. Les icônes vectorielles et les polices restent originales. -- Photo : bouton de changement de capteur réactivé après découverte des caméras; - flash indisponible visible et grisé; le pincement ne zoome plus le document Gaia. -- Accueil : gestes natifs réservés au bouton pendant l’appui long; menu contextuel - désactivé sur ce bouton. Le gestionnaire des tâches Gaia reste utilisé. -- Transmission des gestes entre System et les applications restaurée avec des - événements DOM non privilégiés, sans autoriser de faux gestes sur les sites Web. -- Navigateur : intégration de la page `browser-welcome` fournie, locale, bilingue, - sans suivi; recherches et liens transmis au véritable navigateur. -- Page d’accueil : suit automatiquement la langue Gaia, `fr`/`fr-*` en français, - les autres en anglais; changement appliqué sans redémarrage. -- SMS : identifiants compatibles avec le registre numérique Gaia, migration des - anciens messages, dates de l’API et événement d’envoi corrigés; écritures sérialisées. -- Calendrier : libellé d’accessibilité du jour courant retrouvé dans 37 langues. -- Revue de 14 applications; 26 contrôles fonctionnels Galerie/Musique/Horloge/UI, - tests Contacts et SMS, et navigation Web isolée. Voir `docs/APPLICATION-REVIEW.md`. - -### Android · APK - -- APK `2.7-preview.12` : même interface, correctifs et page locale; reconstruction - et parité contrôlées. L’accès caméra reste celui d’Android via getUserMedia. -- Aucun portage automatique des fonctions matérielles Tundra vers Android : chaque - plateforme conserve son adaptateur et annonce ses capacités réelles. - -### Tundra · Sargo - -- Aperçu Photo : conversion NV21 par shader, transport binaire au lieu de base64; - environ 23 images/s mesurées en RAM contre environ 8 dans l’essai intermédiaire. -- Fermeture du worker attendue avant de rouvrir un autre capteur. -- Luminosité manuelle : retrait des animations successives, environ 200 ms pour - atteindre la valeur demandée lors du test matériel; transitions veille/réveil conservées. -- Wi-Fi : état radio réel et activation/désactivation raccordés aux réglages Gaia; - réseau mixte WPA2/WPA3 compatible avec le formulaire WPA-PSK. -- Scan par Gaia, connexion et navigation HTTPS par wlan0 validés en RAM. Les - profils de connexion restent temporaires; leur persistance est encore à réaliser. -- Image v38 installée : deux démarrages RAM, démarrage installé, relecture de - `boot_a`, préservation des autres partitions et validation du slot A réussis. -- Flash photo, vidéo, capteur de luminosité/rotation, Bluetooth, comptage de données - et fonctions avancées d’appel restent incomplets. Un écran qui s’ouvre ne vaut - pas validation de tout son matériel. +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Icônes Gaia : variantes de 1× à 2,25× restaurées dans 1 047 références CSS. +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Photo : changement de capteur après détection et désactivation du zoom de l’interface au pincement. Flash indisponible grisé. +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Accueil : appui long et transmission des gestes rétablis, sans privilèges supplémentaires pour les sites web. +- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Navigateur : page d’accueil locale bilingue, suivant la langue Gaia sans redémarrage. +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) SMS : identifiants, migration, dates, événements d’envoi, requêtes multi-destinataires et écritures concurrentes corrigés. +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Calendrier : libellé d’accessibilité du jour courant rétabli dans 37 langues. +- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Revue de 14 applications ; 26 contrôles Galerie/Musique/Horloge/UI, tests Contacts/SMS et isolation web. +- ![Change](https://img.shields.io/badge/Change-64748B?style=flat-square) APK Preview12 reconstruit avec les mêmes ressources ; parité vérifiée. Fonctions matérielles adaptées séparément sur Android et Tundra. +- ![Change](https://img.shields.io/badge/Change-64748B?style=flat-square) Sargo : aperçu caméra NV21 par shader et transport binaire, environ 23 i/s mesurées contre 8 dans l’essai intermédiaire. +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Sargo : fermeture du worker caméra attendue avant changement de capteur ; luminosité manuelle réglée en environ 200 ms. +- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Sargo : activation Wi-Fi depuis Gaia et connexion WPA-PSK aux réseaux mixtes WPA2/WPA3. +- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Sargo : scan, connexion et HTTPS validés en RAM. v38 installé après deux boots RAM, relecture et slot A vérifiés. +- ![Note](https://img.shields.io/badge/Note-6B7280?style=flat-square) Sargo : profils Wi-Fi temporaires ; flash, vidéo, capteurs, Bluetooth, comptage de données et appels avancés incomplets. ## En développement — retours Sargo et interface · 27 septembre 2026 -### Commun · Gaia / PC - -- Animation d’arrêt : trois nuances bleues remplacent les ronds orange; dimensions, - séquence et durées de l’animation Gaia sont conservées. - -- Téléphone : empêcher le défilement du document lors du changement d’onglet; - les en-têtes Journal et Contacts restent en place. Les icônes du journal utilisent - les images Gaia originales en plusieurs densités, jusqu’à 2,25×. -- Menus déroulants : retrait du décalage prévu pour les anciens moteurs, qui coupait - les jambages de Fira. Vidéo : texte de présentation contenu dans la fenêtre. -- Luminosité : le curseur enregistre un nombre et répond aux événements input/change; - les anciennes valeurs textuelles sont converties avant la transition Gaia. La file - matérielle regroupe les pas obsolètes de luminosité sans réordonner veille/réveil. -- Accueil : le fond d’écran continue sous le dégradé transparent. L’espace de navigation - reste réservé dans les applications; en paysage, le bouton se place à droite. -- Navigateur : page locale éditable dans `overrides/search.gaiamobile.org/home/index.html`. - Les branchements de Gaia et le mode privé restent en place. -- Wi-Fi : un échec affiche une explication et le bouton de reprise au lieu de relancer - indéfiniment le scanner. La recherche et l’affichage dans le panneau Gaia ont - maintenant été vérifiés sur Sargo en RAM. -- Suivi consolidé : `docs/CHANTIER.md`, avec anciens et nouveaux retours, preuves et limites. - -### Android · APK - -- APK `2.7-preview.11` construit et signé avec la même base Gaia; parité vérifiée. - Barre Accueil à droite en paysage et correction du fond en mode Accueil. - Rotation et absence de recouvrement vérifiées sur émulateur Android 16. - -### Tundra · Sargo - -- Caméra : aperçu matériel dans Photo Gaia, JPEG et bascule arrière/avant validés - en RAM sur Sargo; les capteurs se ferment au retour à l’accueil. Capture initiale - limitée à 640 × 480 (480 × 640 en portrait); vidéo et flash non disponibles. - L’interface reste commune au PC et à l’APK, qui conservent getUserMedia. - -- v33 (`2.7-preview.11`) installé après deux démarrages RAM réussis. Démarrage installé, relecture de - `boot_a`, conservation des données et finalisation du slot A vérifiés. -- Luminosité mesurée depuis Gaia : 35 % → 89/255, 80 % → 204/255. Résultats Wi-Fi - visibles dans le panneau original. Animation d’arrêt bleue incluse. -- Routage audio : prise en charge du format de PulseAudio 14; suppression du double - chargement des modules audio. Passage au profil téléphonique et à l’écouteur, puis - restauration du profil média, vérifiés sur Sargo. Conversation réelle encore à valider. -- Restent ouverts : conversation audible dans les deux sens, capture pleine résolution/vidéo et - raccordement des capteurs pour la rotation automatique. Voir la liste de suivi. +- ![Change](https://img.shields.io/badge/Change-64748B?style=flat-square) Animation d’arrêt Gaia recolorée en bleu. +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Téléphone : en-têtes fixes au changement d’onglet et icônes du journal adaptées aux densités jusqu’à 2,25×. +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Texte coupé dans les menus déroulants et débordement du texte d’accueil Vidéo. +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Luminosité : valeurs numériques et regroupement des commandes obsolètes, avec ordre veille/réveil préservé. +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Accueil : fond sous le dégradé et bouton à droite en paysage, sans recouvrir les applications. +- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Navigateur : page d’accueil locale modifiable, avec mode privé conservé. +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Wi-Fi : message d’erreur et bouton Réessayer à la place des relances sans fin. +- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) APK Preview11 construit et signé ; rotation et position du bouton Accueil vérifiées sur Android 16 émulé. +- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Sargo : aperçu, JPEG et bascules caméra validés en RAM. Capture limitée à 640 × 480 ; flash et vidéo indisponibles. +- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Sargo v33 installé après deux boots RAM ; démarrage, données, relecture et slot A vérifiés. Luminosité et résultats Wi-Fi contrôlés dans Gaia. +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Sargo : compatibilité PulseAudio 14 et suppression du double chargement des modules audio. Profils appel/média et écouteur testés. +- ![Note](https://img.shields.io/badge/Note-6B7280?style=flat-square) Sargo : conversation audio, capture pleine résolution, vidéo et rotation automatique à valider. ## En développement — stabilité Wi-Fi native · 27 septembre 2026 -### Commun · Gaia / PC - -- Correction de la boucle Wi-Fi sans délai : recherche toutes les 20 secondes, - conformément à l’intervalle prévu par Gaia. Regroupement des scans simultanés. -- Version de développement commune : `2.7-preview.9`, Gaia `2.7.0`; versions - réelles de Gecko inchangées. Tests de rafales, erreurs et temporisation ajoutés. - -### Android · APK - -- Nouveau paquet `2.7-preview.9` pour conserver la même base Gaia. Le Wi-Fi Android - reste géré par Android; ce correctif ne prétend pas ajouter un pilote à l’APK. - -### Tundra · Sargo - -- Cause du blocage v21/v22 identifiée : multiplication des clients Python de - recherche Wi-Fi, épuisement mémoire et arrêt de Gecko par le lowmemorykiller. - Le SIGSEGV de phoc survient ensuite pendant sa fermeture. -- Un seul scan matériel en cours, cache court des résultats et des erreurs, - invalidation après connexion/déconnexion. Aucun affaiblissement de l’isolation - des applications, ni désactivation du mécanisme de protection mémoire. -- Candidat v25 qualifié sur deux démarrages RAM, puis installé et vérifié sur le - Pixel : 11 contrôles Gaia et 10 contrôles système passent, sans destruction de - processus par manque de mémoire. Verrouillage Gaia, Fira, batterie, date et - panneau Wi-Fi contrôlés; veille/réveil et volume testés par événements injectés. -- Relecture de `boot_a` conforme, autres partitions de démarrage inchangées, - données conservées et slot A marqué comme démarré avec succès. La caméra native - et la validation complète des appels restent à terminer. +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Wi-Fi : intervalle de recherche rétabli à 20 secondes et regroupement des scans simultanés. +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Sargo : un seul scan matériel à la fois, avec cache court et invalidation à la connexion/déconnexion. Corrige l’épuisement mémoire des builds v21/v22. +- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Tests de rafales, erreurs et temporisation Wi-Fi. +- ![Change](https://img.shields.io/badge/Change-64748B?style=flat-square) Version commune Preview9, Gaia 2.7.0 ; nouvel APK avec les mêmes sources. Wi-Fi Android toujours géré par Android. +- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Sargo v25 : deux boots RAM puis installation, 11 contrôles Gaia et 10 système réussis, sans arrêt de processus par manque de mémoire. +- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Verrouillage, polices, batterie, date et Wi-Fi vérifiés ; veille/réveil et volume testés par événements injectés. Données, relecture et slot A contrôlés. +- ![Note](https://img.shields.io/badge/Note-6B7280?style=flat-square) Caméra native et validation complète des appels encore à terminer. ## En développement — restauration Gaia · 27 septembre 2026 -### Commun · Gaia / PC - -- Restauration des panneaux Gaia et suppression des fenêtres/boutons provisoires : - Paramètres, Suivi conso, FM et import de contacts/médias. Les fonctions sans API - restent visibles avec commandes désactivées, sans valeurs matérielles inventées. -- Correction de la portée des anciens styles : les sélecteurs `div` ne sont plus - transformés en racines de composants. Retour des icônes du verrouillage, - conservation de Fira et glyphes Gaia vectoriels pour les onglets Téléphone. -- Accueil dispose de 44 pixels CSS réservés et ne recouvre plus Décrocher/Raccrocher. - La date utilise le composant historique; la batterie est actualisée dans Paramètres - et ses libellés n’affichent plus `{{ level }}`. -- Écran de verrouillage Gaia réactivé; splash provisoire avec le logo fourni, - dimensions stables et animation douce. Nom localisé : Navigateur / Browser. - Remerciements dans Informations légales, avis de licences conservés. -- Tests PC : 26 contrôles applicatifs passent (Galerie, Musique, Horloge, tâches), - panneaux, imports, batterie, date et navigation; appels simulés, - journal, veille/réveil, menu Power et volume. Voir `docs/GAIA-RESTORATION.md`. - -### Android · APK - -- APK `2.7-preview.8` construit et installé sur l’émulateur Android 16, avec la même base Gaia; batterie lue depuis Android, - Paramètres Android en bas de liste et mode Accueil Android optionnel. Le catalogue - des applications Android dans Gaia reste à raccorder. -- Validation Android : dix reprises après changement d’application et récupération - après destruction du renderer. Parité du contenu partagé vérifiée; pas de test - physique Android dans cette passe. -- Les imports utilisent les composants Gaia; le bouton SMS ouvre la confirmation - Android. Le verrouillage interne ne remplace pas la sécurité Android. - -### Tundra · Sargo - -- Candidats v21/v22 construits avec tous les correctifs `overrides/` dans le paquet de boot; - raccordement scan/association Wi-Fi au panneau Gaia via NetworkManager. -- Essais RAM bloqués : service graphique arrêté avec le code 139 avant qualification. - Aucun nouveau flash. Retour sur v20 installé validé (11 contrôles Gaia, 9 système, - données conservées); verrouillage du profil natif remis à son état précédent. - Wi-Fi du candidat, caméra native, API SIM/Bluetooth et suspension CPU non qualifiés. +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Panneaux Gaia restaurés : Paramètres, Suivi conso, FM et imports. Interfaces provisoires retirées ; fonctions indisponibles désactivées. +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Styles des anciens composants, icônes du verrouillage et glyphes Téléphone corrigés. +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Espace de 44 pixels réservé au bouton Accueil ; date et batterie rétablies, sans texte `{{ level }}`. +- ![Change](https://img.shields.io/badge/Change-64748B?style=flat-square) Verrouillage Gaia réactivé, splash stabilisé, nom Navigateur/Browser localisé et crédits ajoutés. +- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) PC : 26 contrôles applicatifs réussis, plus panneaux, imports, batterie, navigation et appels simulés. +- ![Change](https://img.shields.io/badge/Change-64748B?style=flat-square) APK Preview8 installé sur Android 16 émulé : batterie Android, accès aux paramètres et mode HOME optionnel. Catalogue Android encore incomplet. +- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Android émulé : dix reprises d’arrière-plan, récupération du rendu et parité des ressources vérifiées. Verrouillage Gaia distinct de la sécurité Android. +- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Sargo v21/v22 : scan et association Wi-Fi raccordés à Gaia via NetworkManager. +- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Sargo : essais RAM interrompus par l’arrêt du service graphique (code 139). Retour au v20 installé vérifié, sans nouveau flash. +- ![Note](https://img.shields.io/badge/Note-6B7280?style=flat-square) Wi-Fi du candidat, caméra, API SIM/Bluetooth et suspension CPU non validés. ## En développement — 27 septembre 2026 -### Commun · Gaia / PC - -- Remplacement de l’écran d’appel provisoire par **Callscreen**, l’interface - originale de Gaia : appel entrant/sortant, réponse, raccrochage et fermeture. - L’adaptateur fournit les événements `mozTelephony` aux modules historiques. - Les contrôles audio/conférence non encore raccordés restent grisés. -- Journal d’appels persistant côté hôte, puis import dans le vrai journal Gaia, - avec acquittement et détection des doublons après interruption. -- Batterie : pont vers l’icône historique ; une batterie inconnue n’est pas - présentée comme vide. Correction de l’index de contacts Facebook absent ou - mal formé, qui bloquait aussi la recherche de numéro et l’historique local. -- Polices : comparaison des glyphes réellement rendus. L’accueil utilisait - déjà Fira Sans des deux côtés, mais certains textes système utilisaient Noto - sur PC. Les règles de texte principales sélectionnent désormais la Fira de - Gaia ; les polices d’icônes sont conservées. -- Test Gecko avec modem simulé : appels entrants/sortants, boutons Répondre et - Raccrocher, journal, veille/réveil, menu Power, volume et batterie. - Aucun nouvel APK compilé dans cette étape. - -- Étape v13 (remplacée par Callscreen ci-dessus) : écran d’appel géré par l’application système : numérotation, sonnerie, - communication et bouton Raccrocher. Les contrôles restent présents si le - service perd momentanément l’état de l’appel. -- Tests sur le vrai Gecko bureau avec modem simulé, y compris échec puis nouvel - essai de raccrochage. Sources PC synchronisées ; aucun APK reconstruit. - -### Tundra · Sargo - -- Build privé v20 installé et relu dans `boot_a`, après deux démarrages RAM - et un démarrage depuis le stockage ; réglages conservés, slot marqué réussi. -- Lecture des seules touches Power/Volume du Sargo et - transmission à `HardwareButtons`. Appui court : écran éteint/rallumé ; appui - long : menu Gaia. Le broker natif commande rétroéclairage, volume, arrêt et - redémarrage avec une liste d’opérations fixe. -- Sérialisation des changements de luminosité et d’alimentation : deux - requêtes simultanées ne peuvent plus laisser le rétroéclairage éteint au réveil. -- Volume natif : accès en lecture au socket PulseAudio de la session, sans - lequel l’indicateur Gaia changeait mais la sortie audio restait inchangée. -- La veille écran ne suspend pas encore le noyau. Réception réelle des appels, - audio de conversation et commandes audio avancées restent à qualifier. - -- Étape précédente : build privé v13 installé dans `boot_a` après deux démarrages RAM ; démarrage - depuis le stockage, relecture et indicateur de réussite A/B vérifiés. -- Transmission des appels et de leurs états depuis oFono ; commandes Répondre et - Raccrocher limitées aux chemins d’appel du modem. -- Correction des réponses D-Bus sans contenu : un raccrochage réussi ne provoque - plus une fausse erreur de réponse invalide. -- Un appel réel court atteint la sonnerie puis est raccroché automatiquement. - Cela ne qualifie pas encore l’audio en conversation ni les appels entrants. -- Caméra : diagnostic corrigé, sans annoncer une initialisation à partir d’un - simple délai d’attente. Deux capteurs sont détectés via libhybris, mais le test - de flux reçoit zéro image. La caméra Gaia native reste indisponible ; voir la - [note technique](tundra/docs/CAMERA-SARGO.md). +- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Callscreen Gaia pour les appels entrants/sortants, avec réponse et raccrochage. Remplace l’écran provisoire v13. +- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Journal d’appels persistant avec acquittement et dédoublonnage. +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Batterie inconnue distincte d’une batterie vide ; index Contacts absent ou mal formé toléré. +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Textes système affichés avec Fira Sans, sans modifier les polices d’icônes. +- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) PC avec modem simulé : appels, raccrochage en échec puis nouvel essai, journal, veille/réveil, menu Power, volume et batterie. +- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Sargo : boutons Power/Volume raccordés à Gaia et commandes natives d’écran, volume, arrêt et redémarrage. +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Sargo : luminosité et alimentation sérialisées pour éviter l’écran noir au réveil ; accès PulseAudio corrigé pour le volume. +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Sargo : un raccrochage réussi avec réponse D-Bus vide ne produit plus de fausse erreur. +- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Sargo v13 puis v20 installés après essais RAM ; démarrage, relecture, réglages et validation du slot contrôlés. +- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Appel sortant réel arrivé à la sonnerie puis raccroché. Appels entrants et audio de conversation non validés. +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Diagnostic caméra : un timeout n’est plus présenté comme une initialisation réussie. Deux capteurs détectés, sans trames reçues. +- ![Note](https://img.shields.io/badge/Note-6B7280?style=flat-square) Caméra native indisponible, suspension CPU et commandes audio avancées incomplètes. Aucun nouvel APK. ## En développement — 26 septembre 2026 -### Commun · Gaia - -- Polices : Fira Sans OpenType du simulateur historique, avec les mêmes fichiers - pour les pages Gaia et le système Tundra. Correction du repli `sans-serif` natif - et vérification Fontconfig ajoutée ; les polices d’icônes restent distinctes. -- Paramètres : retrait des boutons de diagnostic « Paramètres Tundra » et - « Tester une notification ». Le résumé des versions et les informations - techniques restent disponibles. -- Panneau matériel : transmission des capacités de la plateforme au panneau. - Une variable absente provoquait une alerte Gecko au lieu d’afficher la batterie. - -### PC - -- Adaptateur local synchronisé avec les sources communes. Gecko reste en 156.0.1. - -### Android - -- Correctif du panneau présent dans les sources communes ; aucun nouvel APK construit. - -### Tundra / Sargo - -- Validation explicite du succès A/B après installation : sauvegarde et contrôle - des métadonnées GPT, outil Qualcomm vérifié, confirmation Fastboot. -- Erreurs du modem distinguées des erreurs de transport ; boîte d’erreur intégrée - à Gaia. La caméra native indisponible ne suggère plus une autorisation inutile. -- Diagnostic des polices réellement utilisées par Gecko et du réseau radio, sans - appel ni SMS automatique. Fira Sans confirmée sur l’accueil et la barre d’état. -- Installation réelle du v9 polices dans `boot_a` sur le Sargo inventorié. - Contenu écrit relu et conforme ; cinq autres partitions de boot sauvegardées intactes. -- Deux démarrages depuis le stockage : 10 contrôles Gaia et 9 contrôles système - réussis par boot, réglages conservés. Le PC n’est plus nécessaire pour démarrer. - Fichiers de secours Droidian conservés ; restauration réelle non testée. -- Candidat persistant privé : image de données ext4 2 Gio, profils et réglages Gaia - conservés, identité/comptes système préservés et suppression du watchdog de dix minutes. -- Deux démarrages successifs de la même image v8 : 9 contrôles Gaia et 9 contrôles - système réussis par boot. Diagnostic séparé : 15 contrôles avec la même interface. -- Installation limitée à `boot_a` et restauration de l’original préparées et testées - localement ; aucun flash effectué. Les limites matérielles restent documentées. -- Démarrage normal sans ouverture automatique de Paramètres/Horloge ni défilement - de l’accueil. Ces gestes appartiennent désormais au mode explicite `--diagnostics`. -- Plein écran établi avant le chargement de Gaia ; décorations natives désactivées. - Sur le Pixel testé : 360 × 740 pixels CSS, facteur 3, dimensions stables et accueil - inchangé pendant les cinq relevés après démarrage. -- v13 testé en RAM : huit contrôles de démarrage et neuf contrôles système réussis. - Les rapports distinguent cette vérification du démarrage des tests interactifs. -- Diagnostic v14 séparé : treize contrôles interactifs réussis, dont le panneau - matériel ; ouverture et scan Wi-Fi vérifiés. Même interface que le v13 normal. -- Les images de diagnostic interactif ne peuvent pas devenir la sélection normale - du lanceur. L’ancienne sélection v13 est conservée dans son historique. -- Modem enregistré en UMTS pendant l’essai ; lecture audio réussie et signal du - microphone mesuré sans conserver d’enregistrement. Cela ne qualifie pas encore - l’audio en appel, la caméra ou la veille. -- Construction du ramdisk indépendante de la langue du PC : analyse `readelf` en - locale C pour conserver le chargeur dynamique ARM64 et ses dépendances. - -Les six partitions boot/DTBO/vbmeta A/B sont inchangées. Le build reste un essai -privé en RAM, **pas une ROM flashable**. Détails : [état de Tundra](tundra/docs/STATUS.md). +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Polices Fira Sans historiques utilisées dans Gaia et Tundra ; repli sans-serif et contrôle Fontconfig corrigés. +- ![Change](https://img.shields.io/badge/Change-64748B?style=flat-square) Boutons de diagnostic retirés des Paramètres. +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Panneau matériel : capacités de la plateforme transmises correctement pour afficher la batterie. +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Sargo : erreurs modem et transport distinguées ; message caméra indisponible corrigé. +- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Sargo : données persistantes sur ext4 2 Gio et validation du succès A/B après installation. +- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Sargo v8 : deux boots avec persistance, 9 contrôles Gaia et 9 système par boot. Outils d’installation et de secours testés localement. +- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Sargo v9 installé sur boot_a : deux démarrages avec 10 contrôles Gaia et 9 système chacun. Relecture conforme, cinq autres partitions préservées ; restauration réelle non testée. +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Démarrage normal sans gestes de diagnostic automatiques ; plein écran établi avant Gaia, dimensions stables à 360 × 740 pixels CSS. +- ![Change](https://img.shields.io/badge/Change-64748B?style=flat-square) Mode `--diagnostics` séparé ; images de diagnostic exclues du lancement normal. +- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Sargo : v13 testé en RAM (8 contrôles de démarrage, 9 système), v14 en diagnostic (13 contrôles interactifs), Wi-Fi inclus. +- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Enregistrement UMTS, lecture audio et signal du micro vérifiés. Audio d’appel, caméra et veille non validés. +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Construction du ramdisk indépendante de la langue du PC grâce à readelf en locale C. +- ![Note](https://img.shields.io/badge/Note-6B7280?style=flat-square) Essais privés, sans ROM publique. Sources PC synchronisées ; correctif commun disponible pour Android, sans nouvel APK. ## En développement — 25 septembre 2026 -### Commun · Gaia - -- **Navigation :** bouton Accueil rond ; dégradé noir vers transparent au-dessus du - fond d’écran, fond bleu uni dans les applications. L’appui modifie le cercle - plutôt que de laisser un rectangle bleu sur l’accueil. -- **Défilement :** le dégradé disparaît au bas du panneau de l’accueil ; il revient - quand on remonte. Le changement de panneau Applications / Pages est pris en compte. -- **Typographie :** polices Fira Sans embarquées, avec plusieurs graisses et italiques, - au lieu de dépendre des polices installées sur le système hôte. -- **Informations :** affichage séparé de Vulpes, Gaia, du moteur réellement exécuté, - de la plateforme, du modèle et de l’adaptateur. Le panneau prévoit Tundra et - l’adaptation matérielle, maintenant raccordés au backend natif Tundra. -- **Icônes :** reprise du chargement après une première mesure de taille nulle, - partagée entre PC, Android et Tundra. Le correctif était auparavant limité à Android. -- **Maintenance :** ce journal centralise les évolutions des trois plateformes. - -### PC · Validé localement - -- Chargement d’une icône initialisée à taille nulle puis rendue visible : testé. -- Navigation Accueil / Paramètres / retour Accueil, fond transparent en bas de page, - dégradé en haut et contrôle des permissions testés dans un profil isolé. -- Panneau Informations testé en français et en anglais, sans anciennes lignes en double. -- Le moteur demeure Gecko **156.0.1** ; ces changements d’interface ne recompilent pas Gecko. - -### Android · Sources adaptées, Java compilé - -- Même comportement de navigation prévu par le pont GeckoView / Android : barre - superposée sur l’accueil, place réservée dans les applications et le navigateur. -- État de navigation conservé lors de la recréation de l’activité Android. -- Compilation Java réussie. **Aucun nouvel APK livré pour ces changements** et - comportement visuel encore à retester sur Android. - -### Tundra / Sargo · Essais privés en RAM - -- Build réseau v6 devenu référence du lanceur après 13 contrôles Gaia et 9 - contrôles système ; retour automatique et six partitions inchangées vérifiés. -- Backend Tundra distinct du PC : modèle Pixel 3a, adaptation Sargo et version - native affichés sans annoncer une caméra ou une téléphonie non raccordées. -- Batterie et charge lues sur le matériel ; panneau d’état bilingue dans Gaia. - 13 contrôles Gaia réussis sur l’essai réseau, dont ce nouveau panneau. -- Wi-Fi réel testé : scan, connexion, IPv4, DNS et page HTTPS sous Gecko. - Ajout de `pd-mapper`, désactivation d’IPA non initialisé et du sondage caméra - prématuré ; 9 contrôles système passent, sans Oops noyau. La sélection du réseau - dans Gaia reste à raccorder ; les identifiants du test restent en RAM. -- Configuration des services embarquable dans le petit ramdisk : pas de nouvelle - copie des 3 Gio pour chaque ajustement de démarrage. Partitions modem protégées - en utilisant des copies NV en RAM pour les essais Qualcomm. - -- Nouvelle image système ext4 contenant le runtime ARM64, les HAL de référence, - Phoc, Gecko et Gaia. Le runtime n’est plus chargé depuis l’arborescence de travail - Droidian, ni le HAL depuis son ancien fichier `/var/lib/lxc/android/`. -- Démarrage du second essai vérifié sur Pixel 3a : PID 1 Tundra, services HAL, - compositeur et Gaia ; **7 contrôles Gaia et 8 contrôles système réussis**. -- Capture physique de l’accueil contrôlée : icônes présentes, Fira Sans chargée, - bouton rond et dégradé visibles. Gecko **156.0.1 ARM64**, WebRender matériel. -- Petits changements d’interface embarqués dans le ramdisk d’essai : inutile de - retransférer les 3 Gio du système pour chaque correction CSS/JavaScript. -- Copie des sources de construction conservée avec les nouvelles images, vérifiée - par empreintes, pour permettre de les retester après l’évolution du code courant. -- Le dernier essai (`sargo-system-boot-20260925-v5`) valide **10 contrôles Gaia et - 8 contrôles système**, dont icônes, police chargée, fond à l’appui, bas de page - et retour depuis une application. Capture physique contrôlée ; référence locale - du lanceur mise à jour. Aucun APK publié ni téléphone flashé. - -**Limites :** stockage installé encore utilisé comme support du fichier système ; -modifications perdues à l’arrêt ; retour automatique au système conservé après dix -minutes. Aucun flash ni remplacement de Droidian. Le redémarrage à chaud du -compositeur a échoué lors d’un essai ; le démarrage à froid fonctionne. Réseau, -modem, audio, caméra et veille restent à intégrer ou qualifier. Ce n’est pas une -ROM publique installable ni un téléphone quotidien validé. - -Détails : [image système](tundra/docs/SYSTEM-IMAGE-SARGO.md), -[état Tundra](tundra/docs/STATUS.md), [parité des plateformes](docs/PARITE-BUREAU-ANDROID.md). +- ![Change](https://img.shields.io/badge/Change-64748B?style=flat-square) Bouton Accueil rond, dégradé transparent sur le fond d’écran et barre bleue dans les applications. +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Dégradé masqué en bas de page et rétabli au défilement ; plus de rectangle bleu à l’appui sur Accueil. +- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Fira Sans embarquée avec graisses et italiques ; versions produit, Gaia, moteur et plateforme affichées séparément. +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Reprise du chargement des icônes après une première mesure de taille nulle sur les trois plateformes. +- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) PC : icônes, navigation, permissions et panneau Informations FR/EN vérifiés avec Gecko 156.0.1. +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Android : état de navigation conservé après recréation de l’activité. Compilation Java réussie ; rendu à retester, aucun nouvel APK livré. +- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Tundra : image ext4 avec runtime ARM64, HAL, Phoc, Gecko et Gaia ; petits correctifs embarqués dans le ramdisk. +- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Sargo : backend dédié, informations Pixel 3a et lecture matérielle de la batterie. Sources de construction archivées avec empreintes. +- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Sargo : WebRender matériel, icônes et polices contrôlés sur capture physique. v5 : 10 contrôles Gaia et 8 système réussis. +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Sargo : ajout de pd-mapper, désactivation d’IPA non initialisé et du sondage caméra prématuré. Données NV du modem copiées en RAM pour les essais. +- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Sargo v6 : scan Wi-Fi, connexion, IPv4, DNS et HTTPS vérifiés ; 13 contrôles Gaia et 9 système réussis. Sélection du réseau dans Gaia encore à raccorder. +- ![Note](https://img.shields.io/badge/Note-6B7280?style=flat-square) Essais RAM sans flash, retour automatique après dix minutes et modifications perdues à l’arrêt. Démarrage à froid réussi ; redémarrage à chaud du compositeur en échec. +- ![Note](https://img.shields.io/badge/Note-6B7280?style=flat-square) Pas de ROM publique ; modem, audio, caméra et veille encore à intégrer ou valider. ## 2.7-preview.7 — APK construit, référence locale -### Commun / PC / Android - -- Correction de l’autorisation interne d’ouverture du capteur sur PC et de la - reprise après la première demande de permission CAMERA sur Android. -- Messages Photo distincts pour un refus, un capteur absent ou un échec d’ouverture. -- Version produit commune **2.7-preview.7**, Gaia **2.7.0** ; numéros du moteur séparés. -- Gecko PC **156.0.1**, GeckoView Android **156.0** : l’écart réel de correctif est - affiché, sans renommer artificiellement l’un des moteurs. - -Détails : [Photo et Galerie](docs/CAMERA-GALERIE.md), [README Android](android/README.md). +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Permission caméra sur PC et reprise après la première autorisation Android. +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Messages distincts pour une permission refusée, un capteur absent et un échec d’ouverture. +- ![Change](https://img.shields.io/badge/Change-64748B?style=flat-square) Versions produit et moteur séparées : Vulpes Preview7, Gaia 2.7.0, Gecko PC 156.0.1 et GeckoView Android 156.0. ## 2.7-preview.6 — APK construit -### Commun / PC / Android - -- Portage de la véritable application Photo Gaia : aperçu et capture JPEG via - webcam sur PC ou capteur via GeckoView sur Android. -- Photos enregistrées dans Galerie Gaia ; import de médias par le sélecteur du système. -- Commandes non prises en charge désactivées : notamment flash et vidéo ; changement - de caméra selon les capteurs disponibles. +- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Photo Gaia sur PC et Android : aperçu et capture JPEG via webcam ou GeckoView. +- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Enregistrement dans Galerie et import par le sélecteur du système. +- ![Note](https://img.shields.io/badge/Note-6B7280?style=flat-square) Flash et vidéo désactivés ; changement de caméra selon les capteurs disponibles. ## 2.7-preview.5 — APK construit -### Commun - -- Versions centralisées dans `release.json`, séparation produit / Gaia / moteur. -- Interface des fonctions natives partagée entre PC et Android ; contrôle du contenu - commun réellement embarqué dans l’APK avant distribution. - -### PC - -- Intégration des panneaux de réglages Linux disponibles, import de médias et vCard, - notifications du bureau et capture webcam de test. - -### Android - -- Même interface Gaia et commandes natives que le PC, adaptées aux capacités Android. -- Tests de retour au premier plan et de récupération après arrêt du processus de rendu. +- ![Change](https://img.shields.io/badge/Change-64748B?style=flat-square) Versions centralisées dans `release.json` ; produit, Gaia et moteur séparés. +- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Interface native commune PC/Android et contrôle de parité des ressources de l’APK. +- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) PC : réglages Linux, imports médias/vCard, notifications et capture webcam de test. +- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Android : retour au premier plan et récupération après arrêt du processus de rendu. ## 2.7-156.0.2 — APK historique construit (révision 4) -Ce nom historique mélangeait révision du produit et version du moteur ; il ne -signifie pas que l’APK utilisait Gecko 156.0.2. Le moteur Android était Gecko 156.0. - -### Android - -- Reprise de l’activité et des sessions pour limiter les écrans blancs au retour dans Vulpes. -- Accès aux réglages Android, au volume et à la luminosité de la fenêtre. -- Import de médias et contacts, notifications natives avec permissions. -- Transfert au composeur, à SMS et à Horloge Android ; aucun envoi silencieux ni - remplacement des applications système Téléphone/SMS. -- Capture initialement déléguée à l’application photo Android, avant le portage de Photo Gaia. +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Android : reprise de l’activité et des sessions pour réduire les écrans blancs. +- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Accès aux réglages Android, volume et luminosité de la fenêtre. +- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Import de médias et contacts ; notifications natives avec permissions. +- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Transfert vers Téléphone, SMS, Horloge et Appareil photo Android. Aucun envoi silencieux ni remplacement des applications par défaut. +- ![Note](https://img.shields.io/badge/Note-6B7280?style=flat-square) Numérotation historique : moteur Android Gecko 156.0, malgré le nom 2.7-156.0.2. ## 2.7-156.0.1 — APK publié (révision 3) -- Nom **Vulpes OS - Preview** et icône du projet. -- Correctif du fond d’écran clignotant inclus. -- Numérotation historique conservée ; ne pas en déduire le patch du moteur Android. +- ![Change](https://img.shields.io/badge/Change-64748B?style=flat-square) Nom « Vulpes OS - Preview » et icône du projet. +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Clignotement du fond d’écran corrigé. +- ![Note](https://img.shields.io/badge/Note-6B7280?style=flat-square) Numérotation historique, distincte de la version du moteur Android. [Release](https://nnsprod.com/git/oversu/VulpesOS/releases/tag/android-2.7-156.0.1) @@ -830,7 +549,7 @@ signifie pas que l’APK utilisait Gecko 156.0.2. Le moteur Android était Gecko ## 2.7-preview.2 — APK publié -- Correction du fond d’écran clignotant dans la Preview Android. +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Clignotement du fond d’écran dans la Preview Android. [Release](https://nnsprod.com/git/oversu/VulpesOS/releases/tag/android-2.7-preview.2) @@ -838,8 +557,8 @@ signifie pas que l’APK utilisait Gecko 156.0.2. Le moteur Android était Gecko ## 2.7-preview.1 — APK publié -- Première Preview Android embarquant Gaia et GeckoView. -- Version archivée ; clignotement du fond d’écran connu. +- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Première Preview Android avec Gaia et GeckoView. +- ![Note](https://img.shields.io/badge/Note-6B7280?style=flat-square) Version archivée ; clignotement du fond d’écran connu. [Release](https://nnsprod.com/git/oversu/VulpesOS/releases/tag/android-2.7-preview.1) @@ -847,26 +566,12 @@ signifie pas que l’APK utilisait Gecko 156.0.2. Le moteur Android était Gecko ## Étapes antérieures — rétrospective technique -Ces étapes ne correspondent pas à de nouvelles releases publiques datées. - -### Gaia / PC - -- Constitution de Vulpes à partir de Gaia historique ; identité visuelle, fond - d’écran, traductions et travail sur les informations système. -- Portage expérimental Gecko 45 → 52, puis séparation de Gaia, des services et du - moteur pour l’hôte Gecko récent. -- Réimplémentation des API nécessaires : réglages, registre d’applications, - contacts locaux, messages, alarmes, stockage et communications entre applications. -- Adaptation des composants visuels anciens, des interactions tactiles simulées, - du navigateur et des applications ; essais d’applications Firefox OS archivées. -- Outillage de mise à jour Gecko avec vérifications, moteur candidat et retour - arrière explicite. Il détecte les incompatibilités, sans les réparer automatiquement. -- Lancement et mise à jour de Gaia sans recompilation complète du moteur. - -### Tundra / Sargo - -- Inventaire du Pixel 3a, sauvegardes des partitions de boot et tests USB de diagnostic. -- Tests QEMU, puis Gecko ARM64 dans Droidian et sous un compositeur dédié. -- Racine distincte, services HAL sélectionnés et PID 1 Tundra ; essais graphiques - temporaires par Fastboot avec secours SSH et retour automatique. -- Noyau de référence **4.9-124-google-sargo**, adaptation **Android 9 / API 28**. +- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Base Gaia historique avec identité Vulpes, fond d’écran, traductions et informations système. +- ![Change](https://img.shields.io/badge/Change-64748B?style=flat-square) Portage expérimental Gecko 45 vers 52, puis séparation de Gaia, des services et du moteur. +- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) API de réglages, applications, contacts, messages, alarmes, stockage et communications entre applications. +- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Adaptation des composants Gaia, gestes tactiles, navigateur et applications aux moteurs récents. +- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Mise à jour Gecko avec contrôles et retour arrière ; mise à jour Gaia sans recompilation du moteur. +- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Applications Firefox OS archivées, QEMU, puis Gecko ARM64 dans Droidian et sous compositeur dédié. +- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Sargo : inventaire, sauvegardes de boot, racine distincte, services HAL et PID 1 Tundra. +- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Essais graphiques Fastboot temporaires avec secours SSH et retour automatique. +- ![Note](https://img.shields.io/badge/Note-6B7280?style=flat-square) Noyau 4.9-124-google-sargo, adaptation Android 9/API 28. Étapes historiques, sans nouvelles releases datées. diff --git a/README.fr.md b/README.fr.md index fe75f913..92c85dc5 100644 --- a/README.fr.md +++ b/README.fr.md @@ -4,7 +4,7 @@

Firefox OS fork · Open source

-Vulpes: 2.7 Preview 17 +Vulpes: 2.7 Preview 20 Gaia: 2.7 Gecko: 156.0.1 Tundra: 0.1.0 @@ -38,6 +38,12 @@ Je développe Vulpes principalement seul, sous le nom d’OverSu, avec l’aide Sur Pixel 3a, **Tundra démarre directement Gecko et Gaia** et assure leur accès au matériel. Droidian a servi d’environnement de test au début du projet : il n’est pas inclus dans Vulpes OS et Tundra ne repose pas dessus. +### Preview 20 · 13 octobre 2026 + +[Télécharger l’APK](https://nnsprod.com/git/oversu/VulpesOS/releases/tag/v2.7-preview.20). Les corrections communes concernent les paramètres Gaia, l’enregistrement vidéo, les contacts et les médias. Les adaptations réseau natives sont incluses dans les sources. Le [changelog](CHANGELOG.md) précise les vérifications et les limites restantes. + +PC/Sargo utilisent Gecko **156.0.1** ; Android utilise GeckoView **156.0**. Android 8 / API 26 minimum déclaré, sans validation de tous les appareils. Appels et SMS ouvrent les applications Android. L’APK est un build de développement signé, avec débogage via ADB autorisé. + ### Linux Prérequis : Linux x86_64, Git, Python 3 et les bibliothèques système nécessaires à Firefox. diff --git a/README.md b/README.md index a3779e29..72bee2ba 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@

Firefox OS fork · Open source

-Vulpes: 2.7 Preview 17 +Vulpes: 2.7 Preview 20 Gaia: 2.7 Gecko: 156.0.1 Tundra: 0.1.0 @@ -38,6 +38,12 @@ I’m OverSu, and I develop Vulpes mostly on my own, with help from people who t On the Pixel 3a, **Tundra boots Gecko and Gaia directly** and provides access to the hardware. Droidian was used as a testing environment early in development. It is not included in Vulpes OS, and Tundra is not based on it. +### Preview 20 · 13 October 2026 + +[Download the APK](https://nnsprod.com/git/oversu/VulpesOS/releases/tag/v2.7-preview.20). Shared updates cover Gaia settings, camera recording, contacts and media. Native network changes are included in the sources. See the [changelog](CHANGELOG.en.md) for checks and remaining limitations. + +Desktop/Sargo use Gecko **156.0.1**; Android uses GeckoView **156.0**. Android 8 / API 26 minimum is declared; not every device is tested. Calls and SMS open Android applications. The APK is a signed development build, with debugging through authorized ADB. + ### Linux You’ll need Linux x86_64, Git, Python 3 and the system libraries required by Firefox. diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 1d1d8db4..fae6411a 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -1,6 +1,7 @@ + diff --git a/android/app/src/main/java/org/vulpes_os/preview/AndroidServices.java b/android/app/src/main/java/org/vulpes_os/preview/AndroidServices.java index f52c11c4..64020f99 100644 --- a/android/app/src/main/java/org/vulpes_os/preview/AndroidServices.java +++ b/android/app/src/main/java/org/vulpes_os/preview/AndroidServices.java @@ -102,6 +102,18 @@ final class AndroidServices { ); result.complete(null); break; + case "android.storage": + io.execute(() -> { + try { + File data = new File(app.getApplicationInfo().dataDir); + File mediaRoot = new File(app.getFilesDir(), "media"); + JSONObject stats = new JSONObject().put("free", data.getUsableSpace()) + .put("apps", storageBytes(data, mediaRoot)).put("sdcard", storageBytes(mediaRoot, null)) + .put("scope", "vulpes-profile"); + main.post(() -> result.complete(stats)); + } catch (Exception e) { fail(result, e); } + }); + break; case "android.media": JSONObject params = p; io.execute(() -> { @@ -122,6 +134,15 @@ final class AndroidServices { return result; } + private static long storageBytes(File file, File skip) throws IOException { + if (file.equals(skip) || !file.exists() || !file.getCanonicalFile().equals(file.getAbsoluteFile())) return 0; + if (file.isFile()) return file.length(); + long size = 0; + File[] children = file.listFiles(); + if (children != null) for (File child : children) size += storageBytes(child, skip); + return size; + } + private void fail(GeckoResult result, Exception e) { main.post(() -> { try { diff --git a/android/app/src/main/java/org/vulpes_os/preview/MainActivity.java b/android/app/src/main/java/org/vulpes_os/preview/MainActivity.java index 5b4dcba7..640a5578 100644 --- a/android/app/src/main/java/org/vulpes_os/preview/MainActivity.java +++ b/android/app/src/main/java/org/vulpes_os/preview/MainActivity.java @@ -550,12 +550,17 @@ public final class MainActivity extends Activity { "http".equals(origin.getScheme()) && "camera.localhost".equals(origin.getHost()) && origin.getPort() == 18765; - if (!camera || (audio != null && audio.length > 0) || video == null || video.length == 0) { + boolean hasVideo = video != null && video.length > 0; + boolean hasAudio = audio != null && audio.length > 0; + if (!camera || (!hasVideo && !hasAudio)) { callback.reject(); return; } - permission(new String[] { android.Manifest.permission.CAMERA }, granted -> { - if (granted) callback.grant(video[0], null); + java.util.ArrayList requested = new java.util.ArrayList<>(); + if (hasVideo) requested.add(android.Manifest.permission.CAMERA); + if (hasAudio) requested.add(android.Manifest.permission.RECORD_AUDIO); + permission(requested.toArray(new String[0]), granted -> { + if (granted) callback.grant(hasVideo ? video[0] : null, hasAudio ? audio[0] : null); else callback.reject(); }); } diff --git a/android/extension/background.js b/android/extension/background.js index e189b1d5..4baab916 100644 --- a/android/extension/background.js +++ b/android/extension/background.js @@ -1,3 +1,4 @@ +import {Input} from './services/input.mjs'; import { Packages, packageURL } from './services/packages.mjs'; import { Activities, isGaiaActivity } from './services/activities.mjs'; import { authorizePlatform } from './services/platform.mjs'; @@ -10,6 +11,8 @@ import { Connections } from './Connections.mjs'; import { Alarms } from './Alarms.mjs'; const peers = new Set(); let activities, packages; +const input = new Input({identity:peer=>peer.app,broadcast}); +let editSequence=0; const nativePackages=(operation,data={})=>browser.runtime.sendNativeMessage('vulpes',{type:'packages.'+operation,...data}).then(JSON.parse); async function refreshInstalled(){const installed=await nativePackages('list');apps.splice(0,apps.length,...apps.filter(a=>!a.installed),...installed);} let apps, values, settings, dataStores, contacts, messages, connections, alarms, storage; @@ -31,7 +34,7 @@ function grants(app) { function broadcast(type, data, target) { for (const peer of peers) { if (target && peer.app.id !== target) continue; - if (type === 'settingchange' && data.key !== 'language.current' && !grants(peer.app).includes('settings.read')) continue; + if (type === 'settingchange' && !['language.current', 'vulpes.theme'].includes(data.key) && !grants(peer.app).includes('settings.read')) continue; if ( type === 'contactchange' && !peer.app.manifest.permissions?.contacts?.access?.includes('read') @@ -90,6 +93,7 @@ const ready = (async () => { await browser.storage.local.set({ settings: values }); } activities = new Activities({identity,apps:()=>apps,notify,uuid:()=>crypto.randomUUID(), + choose:data=>broadcast('activity-choice',data,'system'), launch:(target,path,{reload=false,entryPoint}={})=>launch(target,entryPoint,path,reload)}); let writes = Promise.resolve(); settings = new SettingsService( @@ -100,7 +104,12 @@ const ready = (async () => { const next = { ...values, ...Object.fromEntries(entries) }; await browser.storage.local.set({ settings: next }); values = next; - for (const [key, value] of entries) broadcast('settingchange', { key, value }); + for (const [key, value] of entries) { + broadcast('settingchange', { key, value }); + if(key==='keyboard.gaia.enabled') { + input.hide();broadcast('input-mode',{enabled:value===true}); + } + } }); writes = write.catch(() => {}); return write; @@ -302,6 +311,9 @@ async function handle(peer, request) { } if (!Object.keys(params.values).length) return null; } + if (method === 'input.config') return values['keyboard.gaia.enabled'] === true; + if (method === 'input.request') return input.request(peer,params); + if (method === 'appearance.current') return values['vulpes.theme'] || 'light'; if (method === 'locale.current') return values['language.current'] || 'en-US'; if (method === 'settings.snapshot') { if (!grants(app).includes('settings.read')) throw Error('PERMISSION_DENIED'); @@ -357,6 +369,7 @@ async function handle(peer, request) { return null; } if (method === 'activities.cancel') return activities.cancel(peer); + if (method === 'activities.select') return activities.select(peer,params); if (method === 'activities.subscribe') return activities.subscribe(peer); if (method === 'activities.finish') return activities.finish(peer,params); if (method === 'activities.start' && isGaiaActivity(params)) return activities.start(peer,params); @@ -412,6 +425,7 @@ async function handle(peer, request) { return null; } if (method === 'desktop.wakelock') return null; // Android Activity owns the visible screen. + if (method === 'storage.stats') {if(app.id!=='settings')throw Error('PERMISSION_DENIED');return android('storage',{});} if (method === 'media.request') return media(app, params); throw Error('METHOD_NOT_SUPPORTED'); } @@ -449,6 +463,15 @@ browser.runtime.onConnect.addListener((port) => { app, manager: { documentURI: { spec: url.href } }, sendAsyncMessage: (_, event) => notify(peer, event.type, event.data), + edits: new Map(), + sendQuery(_, params) { + const id=++editSequence; + return new Promise((resolve,reject)=>{ + const timer=setTimeout(()=>{peer.edits.delete(id);reject(Error('INPUT_CONTEXT_EXPIRED'));},5000); + peer.edits.set(id,{resolve,reject,timer}); + port.postMessage({edit:{id,params}}); + }); + }, }; peers.add(peer); }); @@ -456,6 +479,16 @@ browser.runtime.onConnect.addListener((port) => { let reply; try { await attached; + if(Object.hasOwn(message,'inputFocus')) { + if(values['keyboard.gaia.enabled']===true) input.changed(peer,message.inputFocus); + return; + } + if(Object.hasOwn(message,'editReply')) { + const edit=peer.edits.get(message.editReply); + if(edit) {clearTimeout(edit.timer);peer.edits.delete(message.editReply); + if(message.error)edit.reject(Error(message.error));else edit.resolve(message.result);} + return; + } reply = { result: await handle(peer, message.request) }; } catch (e) { reply = { error: { code: e.message || 'HOST_ERROR' } }; @@ -469,6 +502,9 @@ browser.runtime.onConnect.addListener((port) => { attached .then(() => { peers.delete(peer); + input.cleanup(peer); + for(const edit of peer.edits.values()) {clearTimeout(edit.timer);edit.reject(Error('INPUT_CONTEXT_EXPIRED'));} + peer.edits.clear(); if (peer.app.id === 'system' && ![...peers].some((p) => p.app.id === 'system')) systemReady = false; activities.cleanup(peer); diff --git a/android/extension/content.js b/android/extension/content.js index 17bbcc85..4c1b7d5c 100644 --- a/android/extension/content.js +++ b/android/extension/content.js @@ -25,7 +25,23 @@ }); } function onMessage(message) { + if (message.edit) { + try { + const result=editor.edit(message.edit.params); + result.inputMode=originalModes.get(editor.inputElement) || + (editor.inputElement.classList.contains('recipient')?'tel':''); + port.postMessage({editReply:message.edit.id, result}); + } + catch (error) { port.postMessage({editReply:message.edit.id, error:String(error.message)}); } + return; + } if (message.event) { + if(message.event.type==='input-mode') { + gaiaKeyboard=message.event.data.enabled===true; + if(!gaiaKeyboard) restoreMode(); + win.VulpesNativeInput=gaiaKeyboard; + win.dispatchEvent(new window.Event('vulpes-input-configured')); + } win.dispatchEvent( new window.CustomEvent('vulpes-service-event', { detail: cloneInto(message.event, win) }), ); @@ -50,6 +66,45 @@ }, 1000); } connect(); + const editor = new InputEditor(window); + let gaiaKeyboard = false; + const originalModes = new WeakMap(); + function restoreMode() { + const field = editor.inputElement; + if (!field || !originalModes.has(field)) return; + const value = originalModes.get(field); + if (value === null) field.removeAttribute('inputmode'); + else field.setAttribute('inputmode', value); + originalModes.delete(field); + } + request({version:1,id:0,method:'input.config',params:{}}).then(reply => { + gaiaKeyboard = reply.result === true; + win.VulpesNativeInput = gaiaKeyboard; + win.dispatchEvent(new window.Event('vulpes-input-configured')); + }).catch(error => console.error('Gaia input configuration:', error)); + for (const type of ['focusin', 'pointerdown', 'click']) addEventListener(type, event => { + if (!gaiaKeyboard || editor.editing || location.hostname === 'keyboard.localhost') return; + let element = event.target; + if (element?.isContentEditable) element=element.closest('[contenteditable=true]') || element; + const editable = element?.isContentEditable || + (['input','textarea'].includes(element?.localName) && + !['button','submit','range','checkbox','radio','date','time','datetime-local','file'].includes(element.type)); + if (!editable || element.disabled || element.readOnly) { + if (type === 'click' && !element?.closest?.('[data-vulpes-keep-input]')) port.postMessage({inputFocus:null}); + return; + } + if (element !== editor.inputElement) restoreMode(); + editor.focus(element); + if (!originalModes.has(element)) originalModes.set(element, element.getAttribute('inputmode')); + // Suppress Android's IME only in this editor; external web pages keep it. + element.setAttribute('inputmode', 'none'); + const state=editor.state(); + state.inputMode=originalModes.get(element) || (element.classList.contains('recipient')?'tel':''); + port.postMessage({inputFocus:state}); + }, true); + addEventListener('visibilitychange', () => { + if (document.hidden && gaiaKeyboard) { restoreMode(); port.postMessage({inputFocus:null}); } + }); addEventListener('unload', () => clearTimeout(reconnect)); exportFunction(request, win.navigator, { defineAs: 'vulpesRequest' }); const frames = new Map(); diff --git a/android/extension/manifest.json b/android/extension/manifest.json index 3220ceb0..740241d3 100644 --- a/android/extension/manifest.json +++ b/android/extension/manifest.json @@ -25,6 +25,7 @@ "http://*.localhost/*" ], "js": [ + "input-editor.js", "content.js" ], "all_frames": true, diff --git a/android/tools/check-camera.py b/android/tools/check-camera.py index 636d32ce..a6da92f7 100644 --- a/android/tools/check-camera.py +++ b/android/tools/check-camera.py @@ -125,11 +125,11 @@ result["camera_resumes_from_android_background"] = wait( "camera", '!document.hidden && !!app.camera.mozCamera && app.camera.mozCamera!==__streamBefore && app.camera.mozCamera.getVideoTracks().every(t=>t.readyState==="live") && !app.camera.isBusy', ) -result["unavailable_controls"] = ev( +result["capability_controls"] = ev( "camera", - '[".js-flash",".mode-switch"].every(s=>document.querySelector(s).getAttribute("aria-disabled")==="true")', + 'document.querySelector(".js-flash").getAttribute("aria-disabled")==="true" && (document.querySelector(".mode-switch").getAttribute("aria-disabled")!=="true") === !!Object.keys(app.camera.mozCamera.capabilities.recorderProfiles).length', ) -assert result["unavailable_controls"] +assert result["capability_controls"] result["errors"] = ev( "camera", '__vulpesDiagnostics.filter(d=>d.kind==="error" || d.kind==="rejection")' ) diff --git a/android/tools/pack-assets.py b/android/tools/pack-assets.py index e6c9e1ed..5f004300 100644 --- a/android/tools/pack-assets.py +++ b/android/tools/pack-assets.py @@ -12,7 +12,7 @@ from pathlib import Path ROOT = Path(__file__).resolve().parents[2] sys.path.insert(0, str(ROOT / "tools")) -from project import build_info, validate_assets, release_info, application_manifest +from project import SOURCE_OVERRIDES, build_info, validate_assets, release_info, application_manifest validate_assets() OUT = ROOT / "android/app/src/main/assets" @@ -46,17 +46,8 @@ info = build_info() release = release_info() revision = info["revision"] commit_time = str(info["timestamp"]) -source_overrides = { - "system": [ - "js/launcher.js", - "js/gaia_scheduler.js", - "js/keyboard_manager.js", - "js/core.js", - "js/rocketbar.js", - ] -} textsuffix = {".html", ".js", ".css", ".json", ".webapp", ".properties", ".svg"} -head = b'' +head = b'' if "--bridge-only" not in sys.argv or not (OUT / "gaia.zip").exists(): with zipfile.ZipFile(OUT / "gaia.zip", "w", zipfile.ZIP_DEFLATED, compresslevel=6) as z: hashes = {} @@ -82,7 +73,7 @@ if "--bridge-only" not in sys.argv or not (OUT / "gaia.zip").exists(): if p.is_file() and p.name != "manifest.webapp" } ) - for name in source_overrides.get(app["id"], []): + for name in SOURCE_OVERRIDES.get(app["id"], []): files[name] = ROOT / "gaia/apps" / app["id"] / name for name, p in files.items(): data = p.read_bytes() @@ -120,7 +111,7 @@ if "--bridge-only" not in sys.argv or not (OUT / "gaia.zip").exists(): data = translate(data.decode("utf8")).encode() put("_shared/" + p.relative_to(ROOT / "gaia/shared").as_posix(), data) put("_vulpes/platform-config.js", b'window.VulpesPlatform="android";') - for name in ["gaia-compat.js", "gaia-services.js", "gaia-styles.js", "gaia-restoration.js", "gaia-wifi.js", "desktop.css", "platform-ui.js", "gaia-camera.js"]: + for name in ["gaia-compat.js", "gaia-input.js", "gaia-services.js", "gaia-styles.js", "gaia-theme.js", "gaia-select.js", "gaia-mobile-settings.js", "gaia-theme.css", "gaia-restoration.js", "gaia-wifi.js", "gaia-bluetooth.js", "desktop.css", "platform-ui.js", "gaia-camera.js"]: text = ( (ROOT / "host" / name) .read_text() @@ -145,6 +136,7 @@ for name in ["Connections", "Contacts", "Messages"]: ) (ext / f"{name}.mjs").write_text(text) shutil.copy2(ROOT / "host/Alarms.sys.mjs", ext / "Alarms.mjs") +(ext / "input-editor.js").write_text((ROOT / "services/input-editor.mjs").read_text().replace('export class InputEditor', 'class InputEditor')) shutil.copy2(ROOT / "host/marketplace-page.js", ext / "marketplace-page.js") (ext / "release.json").write_text(json.dumps(release)) (ext / "registry.json").write_text(json.dumps(registry, ensure_ascii=False)) diff --git a/assets/webapps/camera.gaiamobile.org/js/main.js b/assets/webapps/camera.gaiamobile.org/js/main.js index 6ed05434..1d04d37a 100644 --- a/assets/webapps/camera.gaiamobile.org/js/main.js +++ b/assets/webapps/camera.gaiamobile.org/js/main.js @@ -5447,6 +5447,9 @@ define('views/viewfinder', [ if (!preview) { return; } + // Recompute when the embedded app becomes visible or rotates. + this.getSize(); + if (!this.width || !this.height) { return; } var aspect; if (sensorAngle % 180 === 0) { this.container = { @@ -5697,6 +5700,7 @@ define('controllers/viewfinder', [ this.views.viewfinder.set('grid', 'off'); }; ViewfinderController.prototype.bindEvents = function () { + window.addEventListener('resize', this.configurePreview); this.views.viewfinder.on('fadedin', this.app.firer('viewfinder:visible')); this.views.viewfinder.on('fadedout', this.app.firer('viewfinder:hidden')); this.views.viewfinder.on('click', this.app.firer('viewfinder:click')); diff --git a/assets/webapps/settings.gaiamobile.org/js/panels/app_permissions_list/panel.js b/assets/webapps/settings.gaiamobile.org/js/panels/app_permissions_list/panel.js index 86119b44..773df448 100644 --- a/assets/webapps/settings.gaiamobile.org/js/panels/app_permissions_list/panel.js +++ b/assets/webapps/settings.gaiamobile.org/js/panels/app_permissions_list/panel.js @@ -1,7 +1,7 @@ -define('modules/navigator/mozPermissionSettings',[],function(){'use strict';return window.navigator.mozPermissionSettings;});define('panels/app_permissions_list/app_permissions_list',['require','modules/settings_service','shared/manifest_helper','modules/apps_cache','modules/navigator/mozApps','modules/navigator/mozPermissionSettings'],function(require){'use strict';var SettingsService=require('modules/settings_service');var ManifestHelper=require('shared/manifest_helper');var AppsCache=require('modules/apps_cache');var mozApps=require('modules/navigator/mozApps');var mozPerms=require('modules/navigator/mozPermissionSettings');var PermissionsList=function pl(){this._listRoot=null;this._permissionsTable=null;this._permissionTableHaveProcessed=false;this._apps=null;this._enabled=false;};PermissionsList.prototype={init:function pl_init(listRoot){this._listRoot=listRoot;this._boundOnAppChoose=this._onAppChoose.bind(this);this._boundOnApplicationInstall=this._onApplicationInstall.bind(this);this._boundOnApplicationUninstall=this._onApplicationUninstall.bind(this);},set enabled(value){if(value!==this._enabled){this._enabled=value;if(this._enabled){this._bindEvents();}else{this._unbindEvents();}}},_bindEvents:function pl__bindEvents(){this._listRoot.addEventListener('click',this._boundOnAppChoose);AppsCache.addEventListener('oninstall',this._boundOnApplicationInstall);AppsCache.addEventListener('onuninstall',this._boundOnApplicationUninstall);},_unbindEvents:function pl__unbindEvents(){this._listRoot.removeEventListener('click',this._boundOnAppChoose);AppsCache.removeEventListener('oninstall',this._boundOnApplicationInstall);AppsCache.removeEventListener('onuninstall',this._boundOnApplicationUninstall);},setPermissionsTable:function pl_setPermissionsTable(permissionTable){this._permissionTable=permissionTable;},refresh:function pl_refresh(){var self=this;this._apps=[];if(this._permissionTableHaveProcessed){return this.loadApps();}else{return mozApps.getSelf().then(function(app){return self._initExplicitPermissionsTable(app);}).then(function(){return self.loadApps();});}},_onAppChoose:function pl__onAppChoose(evt){if(evt.target.dataset&&evt.target.dataset.appIndex){SettingsService.navigate('appPermissions-details',{app:this._apps[evt.target.dataset.appIndex],permissionsTable:this._permissionTable});}},_onApplicationInstall:function pl__onApplicationInstall(evt){var app=evt.application;this._apps.push(app);this._sortApps();this.renderList();},_onApplicationUninstall:function pl__onApplicationUninstall(evt){var app;var appIndex;this._apps.some(function findApp(anApp,index){if(anApp.origin===evt.application.origin){app=anApp;appIndex=index;return true;} +define('modules/navigator/mozPermissionSettings',[],function(){'use strict';return window.navigator.mozPermissionSettings;});define('panels/app_permissions_list/app_permissions_list',['require','modules/settings_service','shared/manifest_helper','modules/apps_cache','modules/navigator/mozApps','modules/navigator/mozPermissionSettings'],function(require){'use strict';var SettingsService=require('modules/settings_service');var ManifestHelper=require('shared/manifest_helper');var AppsCache=require('modules/apps_cache');var mozApps=require('modules/navigator/mozApps');var mozPerms=require('modules/navigator/mozPermissionSettings');var PermissionsList=function pl(){this._listRoot=null;this._permissionsTable=null;this._permissionTableHaveProcessed=false;this._apps=null;this._enabled=false;};PermissionsList.prototype={init:function pl_init(listRoot){this._listRoot=listRoot;this._boundOnAppChoose=this._onAppChoose.bind(this);this._boundOnApplicationInstall=this._onApplicationInstall.bind(this);this._boundOnApplicationUninstall=this._onApplicationUninstall.bind(this);},set enabled(value){if(value!==this._enabled){this._enabled=value;if(this._enabled){this._bindEvents();}else{this._unbindEvents();}}},_bindEvents:function pl__bindEvents(){this._listRoot.addEventListener('click',this._boundOnAppChoose);AppsCache.addEventListener('oninstall',this._boundOnApplicationInstall);AppsCache.addEventListener('onuninstall',this._boundOnApplicationUninstall);},_unbindEvents:function pl__unbindEvents(){this._listRoot.removeEventListener('click',this._boundOnAppChoose);AppsCache.removeEventListener('oninstall',this._boundOnApplicationInstall);AppsCache.removeEventListener('onuninstall',this._boundOnApplicationUninstall);},setPermissionsTable:function pl_setPermissionsTable(permissionTable){this._permissionTable=permissionTable;},refresh:function pl_refresh(){var self=this;this._apps=[];if(this._permissionTableHaveProcessed){return this.loadApps();}else{return mozApps.getSelf().then(function(app){return self._initExplicitPermissionsTable(app);}).then(function(){return self.loadApps();});}},_onAppChoose:function pl__onAppChoose(evt){var target=evt.target.closest('[data-app-index]');if(target&&this._listRoot.contains(target)){SettingsService.navigate('appPermissions-details',{app:this._apps[target.dataset.appIndex],permissionsTable:this._permissionTable});}},_onApplicationInstall:function pl__onApplicationInstall(evt){var app=evt.application;this._apps.push(app);this._sortApps();this.renderList();},_onApplicationUninstall:function pl__onApplicationUninstall(evt){var app;var appIndex;this._apps.some(function findApp(anApp,index){if(anApp.origin===evt.application.origin){app=anApp;appIndex=index;return true;} return false;});if(!app){return;} -SettingsService.navigate('appPermissions');this._apps.splice(appIndex,1);this.renderList();},_sortApps:function pl__sortApps(){this._apps.sort(function alphabeticalSort(app,otherApp){var manifest=new ManifestHelper(app.manifest?app.manifest:app.updateManifest);var otherManifest=new ManifestHelper(otherApp.manifest?otherApp.manifest:otherApp.updateManifest);return manifest.name>otherManifest.name;});},_genAppItemTemplate:function pl__genAppItemTemplate(itemData){var icon=document.createElement('img');var item=document.createElement('li');var link=document.createElement('a');var span=document.createElement('span');span.textContent=itemData.name;span.dir='auto';icon.src=itemData.iconSrc;link.dataset.appIndex=itemData.index;link.href='#';link.classList.add('menu-item');link.appendChild(icon);link.appendChild(span);item.appendChild(link);return item;},renderList:function pl_renderList(){this._listRoot.innerHTML='';var listFragment=document.createDocumentFragment();this._apps.forEach(function appIterator(app,index){var manifest=new ManifestHelper(app.manifest?app.manifest:app.updateManifest);var li=this._genAppItemTemplate({name:manifest.displayName,index:index,iconSrc:this._getBestIconURL(app,manifest.icons)});listFragment.appendChild(li);}.bind(this));this._listRoot.appendChild(listFragment);},_initExplicitPermissionsTable:function pl__initExplicitPermissionsTable(app){this._currentApp=app;var table=this._permissionTable;table.explicitCertifiedPermissions=[];table.plainPermissions.forEach(function plainPermIterator(perm){if(this._isExplicitPerm(perm)){table.explicitCertifiedPermissions.push({explicitPermission:perm,permission:perm});}}.bind(this));table.composedPermissions.forEach(function permIterator(perm){table.accessModes.forEach(function modeIterator(mode){var composedPerm=perm+'-'+mode;if(this._isExplicitPerm(composedPerm)){table.explicitCertifiedPermissions.push({explicitPermission:composedPerm,permission:perm});}}.bind(this));}.bind(this));this._permissionTableHaveProcessed=true;},_isExplicitPerm:function pl_isExplicitPerm(perm){return mozPerms.isExplicit(perm,this._currentApp.manifestURL,this._currentApp.origin,false);},loadApps:function pl_loadApps(){var self=this;return AppsCache.apps().then(function(apps){self._loadApps(apps);});},_loadApps:function pl__loadApps(apps){var table=this._permissionTable;apps.forEach(function(app){var manifest=app.manifest?app.manifest:app.updateManifest;if(manifest.type!='certified'){this._apps.push(app);return;} +SettingsService.navigate('appPermissions');this._apps.splice(appIndex,1);this.renderList();},_sortApps:function pl__sortApps(){this._apps.sort(function alphabeticalSort(app,otherApp){var manifest=new ManifestHelper(app.manifest?app.manifest:app.updateManifest);var otherManifest=new ManifestHelper(otherApp.manifest?otherApp.manifest:otherApp.updateManifest);return manifest.name>otherManifest.name;});},_genAppItemTemplate:function pl__genAppItemTemplate(itemData){var icon=document.createElement('img');var item=document.createElement('li');var link=document.createElement('a');var span=document.createElement('span');span.textContent=itemData.name;span.dir='auto';icon.src=itemData.iconSrc;link.dataset.appIndex=itemData.index;link.href='#';link.classList.add('menu-item');link.appendChild(icon);link.appendChild(span);item.appendChild(link);return item;},renderList:function pl_renderList(){this._listRoot.innerHTML='';var listFragment=document.createDocumentFragment();this._apps.forEach(function appIterator(app,index){var manifest=new ManifestHelper(app.manifest?app.manifest:app.updateManifest);var li=this._genAppItemTemplate({name:manifest.displayName,index:index,iconSrc:this._getBestIconURL(app,manifest.icons)});listFragment.appendChild(li);}.bind(this));this._listRoot.appendChild(listFragment);},_initExplicitPermissionsTable:function pl__initExplicitPermissionsTable(app){this._currentApp=app;var table=this._permissionTable;table.explicitCertifiedPermissions=[];table.plainPermissions.forEach(function plainPermIterator(perm){if(this._isExplicitPerm(perm)){table.explicitCertifiedPermissions.push({explicitPermission:perm,permission:perm});}}.bind(this));table.composedPermissions.forEach(function permIterator(perm){table.accessModes.forEach(function modeIterator(mode){var composedPerm=perm+'-'+mode;if(this._isExplicitPerm(composedPerm)){table.explicitCertifiedPermissions.push({explicitPermission:composedPerm,permission:perm});}}.bind(this));}.bind(this));this._permissionTableHaveProcessed=true;},_isExplicitPerm:function pl_isExplicitPerm(perm){return mozPerms&&mozPerms.isExplicit(perm,this._currentApp.manifestURL,this._currentApp.origin,false);},loadApps:function pl_loadApps(){var self=this;return AppsCache.apps().then(function(apps){self._loadApps(apps);});},_loadApps:function pl__loadApps(apps){var table=this._permissionTable;apps.forEach(function(app){var manifest=app.manifest?app.manifest:app.updateManifest;if(manifest.type!='certified'){this._apps.push(app);return;} var display=table.explicitCertifiedPermissions.some(function iterator(perm){var permInfo=mozPerms.get(perm.explicitPermission,app.manifestURL,app.origin,false);return permInfo!='unknown';}.bind(this));if(display){this._apps.push(app);}}.bind(this));this._sortApps();this.renderList();},_getBestIconURL:function pl__getBestIconURL(app,icons){if(!icons||!Object.keys(icons).length){return'../style/images/default.png';} var preferredIconSize=30*(window.devicePixelRatio||1);var preferredSize=Number.MAX_VALUE;var max=0;for(var size in icons){size=parseInt(size,10);if(size>max){max=size;} if(size>=preferredIconSize&&size0x39)){return;} -var key=String.fromCharCode(code);if(evt.charCode===0){if(this._passcodeBuffer.length>0){this._passcodeBuffer=this._getPasscodeBuffer('all');if(this._elements.panel.dataset.passcodeStatus==='success'){this._resetPasscodeStatus();}}}else if(this._passcodeBuffer.length<8){this._passcodeBuffer+=key;} -this._updatePassCodeUI();this._processPasscode();},_updateHeaderAndButtonUI:function(){var headerL10nId=this._headerL10nIdMapping[this._mode];var buttonL10nId=this._submitButtonL10nIdMapping[this._mode];if(headerL10nId){this._elements.header.setAttribute('data-l10n-id',headerL10nId);} +define('panels/screen_lock_passcode/panel',['require','modules/settings_utils','modules/dialog_panel','shared/passcode_helper'],function(require){'use strict';var SettingsUtils=require('modules/settings_utils');var DialogPanel=require('modules/dialog_panel');var PasscodeHelper=require('shared/passcode_helper');return function ctor_screenlockPasscode(){var mozSettings=window.navigator.mozSettings;return DialogPanel({_elements:{},_mode:'create',_leftApp:false,_passcodeBuffer:'',_checkingLength:{'create':8,'new':8,'edit':4,'confirm':4,'confirmLock':4},_headerL10nIdMapping:{'create':'new-passcode','edit':'current-passcode','new':'new-passcode','confirm':'enter-passcode','confirmLock':'enter-passcode'},_submitButtonL10nIdMapping:{'new':'change','create':'create','edit':'','confirm':'','confirmLock':''},onInit:function(panel){this._elements.panel=panel;this._elements.gaiaHeader=panel.querySelector('gaia-header');this._elements.header=panel.querySelector('gaia-header h1');this._elements.passcodeInput=panel.querySelector('.passcode-input');this._elements.passcodeDigits=panel.querySelectorAll('.passcode-digit');this._elements.passcodeContainer=panel.querySelector('.passcode-container');this._elements.submitButton=panel.querySelector('button[type="submit"]');this._elements.submitButtonText=this._elements.submitButton.querySelector('span');this._bindEvents();},_bindEvents:function(){this._elements.passcodeInput.type='password';this._elements.passcodeInput.inputMode='numeric';this._elements.passcodeInput.maxLength=8;this._elements.passcodeInput.oninput=this._onPasscodeInput.bind(this);this._elements.passcodeContainer.onclick=this._onPasscodeContainerClick.bind(this);},_onPasscodeContainerClick:function(evt){evt.preventDefault();this._elements.passcodeInput.focus();},_onPasscodeInput: function() { + // Android IMEs commit text without legacy keypress/charCode events. + var value = this._elements.passcodeInput.value.replace(/[^0-9]/g, '').slice(0, 8); + this._hideErrorMessage(); + if (this._elements.panel.dataset.passcodeStatus === 'success') { + this._resetPasscodeStatus(); + } + this._passcodeBuffer = value; + this._updatePassCodeUI(); + this._processPasscode(); + },_updateHeaderAndButtonUI:function(){var headerL10nId=this._headerL10nIdMapping[this._mode];var buttonL10nId=this._submitButtonL10nIdMapping[this._mode];if(headerL10nId){this._elements.header.setAttribute('data-l10n-id',headerL10nId);} if(buttonL10nId===''){this._elements.submitButton.disabled=true;this._elements.submitButtonText.removeAttribute('data-l10n-id');this._elements.submitButtonText.textContent='';}else if(buttonL10nId){this._elements.submitButton.disabled=false;this._elements.submitButtonText.setAttribute('data-l10n-id',buttonL10nId);} -SettingsUtils.runHeaderFontFit(this._elements.gaiaHeader);},_updatePassCodeUI:function(){for(var i=0;i<8;i++){if(i{if(result){mozSettings.createLock().set({'lockscreen.passcode-lock.enabled':false}).then(()=>{this._showSuccessPasscodeStatus();this.cancel();}).catch((error)=>{console.log(error);});}else{this._passcodeBuffer='';this._updatePassCodeUI();}});break;case'confirmLock':this._checkPasscode().then((result)=>{if(result){mozSettings.createLock().set({'lockscreen.enabled':false,'lockscreen.passcode-lock.enabled':false}).then(()=>{this._showSuccessPasscodeStatus();this.cancel();}).catch((error)=>{console.log(error);});}else{this._passcodeBuffer='';this._updatePassCodeUI();}});break;case'edit':this._checkPasscode().then((result)=>{if(result){this._passcodeBuffer='';this._updatePassCodeUI();this._showDialogInMode('new');}else{this._passcodeBuffer='';this._updatePassCodeUI();}});break;}}},_showDialogInMode:function(mode){this._mode=mode;this._elements.panel.dataset.mode=mode;this._hideErrorMessage();this._updatePassCodeUI();this._updateHeaderAndButtonUI();},onSubmit:function(){if(this._elements.panel.dataset.passcodeStatus!=='success'){this._showErrorMessage();this._elements.passcodeInput.focus();return Promise.reject();}else{var originalPasscode=this._getPasscodeBuffer('original');return mozSettings.createLock().set({'lockscreen.passcode-lock.enabled':true}).then(()=>{return PasscodeHelper.set(originalPasscode);});}},onCancel:function(){this._elements.passcodeInput.blur();},onBeforeShow:function(panel,options){this._mode=options.mode;if(!this._leftApp){this._showDialogInMode(this._mode);} this._leftApp=false;},onShow:function(){this._elements.passcodeInput.focus();},onHide:function(){this._leftApp=document.hidden;if(!this._leftApp){this._passcodeBuffer='';}},_checkPasscode:function(){return PasscodeHelper.check(this._passcodeBuffer).then((result)=>{if(result){this._hideErrorMessage();}else{this._showErrorMessage();} return result;}).catch(()=>{this._showErrorMessage();});},_showErrorMessage:function(){this._elements.panel.dataset.passcodeStatus='error';},_hideErrorMessage:function(){this._elements.panel.dataset.passcodeStatus='';},_resetPasscodeStatus:function(){this._elements.panel.dataset.passcodeStatus='';},_showSuccessPasscodeStatus:function(){this._elements.panel.dataset.passcodeStatus='success';},_getPasscodeBuffer:function(part){if(part==='original'){return this._passcodeBuffer.substring(0,4);}else if(part==='confirm'){return this._passcodeBuffer.substring(4,8);}else if(part==='all'){return this._passcodeBuffer.substring(0,this._passcodeBuffer.length-1);}else{console.log('You are using _getPasscodeBuffer wrongly, '+'please check your argument again - '+part);}}});};}); \ No newline at end of file diff --git a/assets/webapps/verticalhome.gaiamobile.org/gaia_build_defer_index.js b/assets/webapps/verticalhome.gaiamobile.org/gaia_build_defer_index.js index e56f7a77..7aa6a9b6 100644 --- a/assets/webapps/verticalhome.gaiamobile.org/gaia_build_defer_index.js +++ b/assets/webapps/verticalhome.gaiamobile.org/gaia_build_defer_index.js @@ -399,7 +399,7 @@ function iteratorSV(value){this.applicationSource.addPreviouslyInstalledSvApp(va loadTable(DB_SV_APP_STORE_NAME,'indexSV',iteratorSV.bind(this));loadTable(DB_ITEM_STORE,'index',iterator,finish.bind(this));function addIfUnique(item){if(!cached[item.identifier]){cached[item.identifier]=true;this._allItems.push(item);}} function finish(){for(var i=0,iLen=collected.length;i{if(event.data.action!=='migrate'){console.error('Unhandled migration action requested:',event.data.action);return;} this.migrate(event.data.manifestURL);};port.start();},migrate:function(manifestURL){this.migratingTo=manifestURL;console.info('verticalhome migrating to '+this.migratingTo);if(this.migrating){return;} this.migrating=true;(datastore?Promise.resolve():navigator.getDataStores(DATASTORE_NAME).then(stores=>{datastore=stores[0];return Promise.resolve();},(e)=>{return Promise.reject(e);})).then(()=>{this.all(entries=>{this.saveMigrationData(entries,'detail',true).then(()=>{this.migrating=false;window.navigator.mozSettings.createLock().set({'homescreen.manifestURL':this.migratingTo});navigator.getDataStores(PREFS_STORE).then(stores=>{if(stores.length<1){console.error('Error opening verticalhome prefs datastore');return;} diff --git a/assets/webapps/wallpaper.gaiamobile.org/style/wallpaper.css b/assets/webapps/wallpaper.gaiamobile.org/style/wallpaper.css index 0a581104..51e131ed 100644 --- a/assets/webapps/wallpaper.gaiamobile.org/style/wallpaper.css +++ b/assets/webapps/wallpaper.gaiamobile.org/style/wallpaper.css @@ -70,3 +70,6 @@ html[dir="rtl"] .wallpaper { margin: 0 0.5rem; } + +/* Keep preview tiles aligned when source wallpapers have different ratios. */ +#wallpapers .wallpaper { aspect-ratio: 2 / 3; object-fit: cover; display: block; } diff --git a/changelog.json b/changelog.json index b2ab60b7..96d2f307 100644 --- a/changelog.json +++ b/changelog.json @@ -1,239 +1,873 @@ { "schema": 1, "preamble": { - "fr": "# Journal des évolutions! — Vulpes OS\n\n[English](CHANGELOG.en.md)\n\nUne base Gaia, trois plateformes : **PC**, **Android (APK)** et **Tundra / Sargo**.\nCe journal décrit les changements visibles et les étapes techniques importantes.\nLes rapports détaillés restent dans la documentation de chaque plateforme.\n\n**Statuts :** « En développement » concerne les sources locales, « APK construit »\nun paquet de test, et « Publié » une release accessible dans le dépôt. Un correctif\ncommun n’apparaît dans un APK installé ou une image téléphone qu’après leur mise à jour.\n\n

\"Les

\n

Paramètres de Gaia · capture de la version pour ordinateur.

", - "en": "# Changelog — Vulpes OS\n\n[Français](CHANGELOG.md)\n\nOne Gaia base, three platforms: **desktop**, **Android (APK)** and **Tundra / Sargo**.\nThis journal covers visible changes and major technical milestones.\n\n**Status:** “In development” refers to local sources, “APK built” to a test package,\nand “Published” to a public repository release. Shared source changes reach installed\nAPKs and phone images only after those packages are updated.\n\n

\"Gaia

\n

Gaia Settings · screenshot from the desktop version.

" + "fr": "# Journal des modifications — Vulpes OS\n\n[English](CHANGELOG.en.md)\n\nPC, Android (APK) et Tundra/Sargo. Les statuts indiquent des sources en développement, un APK construit ou une version publiée. Les changements des sources nécessitent une mise à jour du paquet installé.\n", + "en": "# Changelog — Vulpes OS\n\n[Français](CHANGELOG.md)\n\nDesktop, Android (APK) and Tundra/Sargo. Status indicates development sources, a built APK or a published release. Source changes require an update to the installed package.\n" }, "entries": [ + { + "id": "2.7-preview.20", + "text": { + "fr": "## 2.7-preview.20 — APK construit · 13 octobre 2026\n\n- ![Change](https://img.shields.io/badge/Change-64748B?style=flat-square) Preview 20 réunit les corrections communes depuis l’APK Preview 17 : paramètres Gaia, apparence, sélecteurs, clavier, contacts, Marketplace et médias.\n- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Photo : enregistrement WebM avec miniature et lecture dans Galerie/Vidéo. Autorisation micro demandée ; commandes adaptées aux capacités du matériel.\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Caméra : changement de capteur, arrêt et reprise fiabilisés. PC et Android utilisent leurs API caméra ; Sargo conserve son adaptateur HAL.\n- ![Change](https://img.shields.io/badge/Change-64748B?style=flat-square) Sargo : mises à jour Wi-Fi, Bluetooth, appels, SMS/MMS et réglages réseau incluses dans les sources. Les fonctions non raccordées restent indiquées dans les panneaux.\n- ![Change](https://img.shields.io/badge/Change-64748B?style=flat-square) Changelog : reprise des textes simplifiés FR/EN, badges conservés, filtres PC/APK/Sargo indépendants des titres. Markdown, site et notes de release issus du même JSON.\n- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) APK signé avec la clé des Preview précédentes ; alignement et cohérence des 53 fichiers partagés vérifiés. Validation d’exécution Android en cours.\n- ![Note](https://img.shields.io/badge/Note-6B7280?style=flat-square) Gecko PC/Sargo 156.0.1 ; GeckoView Android 156.0. Gaia 2.7.0. L’APK délègue appels/SMS à Android : il ne remplace pas le système téléphonique Android.\n- ![Note](https://img.shields.io/badge/Note-6B7280?style=flat-square) Preview expérimentale : Compte Mozilla/Sync, localisation distante, extensions Firefox OS et transfert USB restent incomplets. Sargo : aperçu autour de 16 FPS ; son des vidéos et audio téléphonique à qualifier séparément.\n- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Pixel 3a : Preview 20 r62 démarrée en RAM puis flashée sur boot_a. Empreinte relue identique ; démarrage installé, onze contrôles Gaia et dix contrôles natifs réussis. Données conservées, Wi-Fi connecté et enregistrement LTE observé. Pas de nouvelle conversation audio testée.\n", + "en": "## 2.7-preview.20 — APK built · 13 October 2026\n\n- ![Change](https://img.shields.io/badge/Change-64748B?style=flat-square) Preview 20 brings together shared fixes since APK Preview 17: Gaia settings, appearance, selectors, keyboard, contacts, Marketplace and media.\n- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Camera: WebM recording with posters and Gallery/Video playback. Microphone permission requested; controls follow hardware capabilities.\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Camera: more reliable sensor switching, stopping and resuming. Desktop and Android use their camera APIs; Sargo retains its HAL adapter.\n- ![Change](https://img.shields.io/badge/Change-64748B?style=flat-square) Sargo: Wi-Fi, Bluetooth, calls, SMS/MMS and network settings updates included in the sources. Unimplemented functions remain identified in their panels.\n- ![Change](https://img.shields.io/badge/Change-64748B?style=flat-square) Changelog: simplified French/English text and badges retained; desktop/APK/Sargo filters independent of headings. Markdown, website and release notes generated from one JSON.\n- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) APK signed with the previous Preview key; alignment and consistency of 53 shared files verified. Android runtime validation in progress.\n- ![Note](https://img.shields.io/badge/Note-6B7280?style=flat-square) Desktop/Sargo Gecko 156.0.1; Android GeckoView 156.0. Gaia 2.7.0. The APK delegates calls/SMS to Android; it does not replace Android telephony.\n- ![Note](https://img.shields.io/badge/Note-6B7280?style=flat-square) Experimental Preview: Mozilla account/Sync, remote location, Firefox OS add-ons and USB file transfer remain incomplete. Sargo: preview around 16 fps; recorded sound and call audio require separate qualification.\n- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Pixel 3a: Preview 20 r62 RAM-booted, then flashed to boot_a. Matching readback hash; installed startup, eleven Gaia checks and ten native checks passed. Data retained, Wi-Fi connected and LTE registration observed. No new two-way audio call tested.\n" + }, + "metadata": { + "kind": "version", + "status": "built", + "platforms": [ + "pc", + "apk", + "models" + ], + "models": [ + "sargo" + ] + } + }, + { + "id": "development-2026-10-12-video-clock", + "text": { + "fr": "## En développement — 12 octobre 2026 — Vidéo et réglages de l’heure\n\n- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Photo : enregistrement WebM VP8/Opus avec autorisation micro, miniature et stockage. Tampon limité à 32 Mio, arrêt avant libération du capteur.\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Photo, Galerie et Vidéo : prise en charge des fichiers WebM et de leurs miniatures.\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Paramètres : véritables sélecteurs Date/Heure Gaia, retour du clavier intégré et lettres non tronquées dans les listes.\n- ![Change](https://img.shields.io/badge/Change-64748B?style=flat-square) Améliorer Vulpes : lien vers les tickets du projet. Services non raccordés expliqués dans leurs panneaux.\n- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) PC : vidéo simulée 1920 × 1080 relue ; photo, galerie, reprise caméra et navigation vérifiées.\n- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Sargo r61 en RAM : vidéo 480 × 640 relue, huit bascules avant/arrière réussies. Médias de test supprimés ; son enregistré non écouté.\n- ![Change](https://img.shields.io/badge/Change-64748B?style=flat-square) Sargo : aperçu mesuré autour de 16 FPS contre 12 dans le test précédent. 30 FPS restent à atteindre.\n- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Sargo : heure automatique par NITZ, réglage persistant et protection des écritures manuelles. Synchronisation dépendante du réseau ; pas de NTP de secours, fuseau choisi manuellement.\n- ![Note](https://img.shields.io/badge/Note-6B7280?style=flat-square) Android : ressources préparées et Java compilé, sans nouvel APK ni test vidéo Android à cette étape. Compte Mozilla/Sync, localisation distante, extensions Firefox OS et transfert USB restent à implémenter. Aucun flash ni publication.\n", + "en": "## In development — 12 October 2026 — Video and clock settings\n\n- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Camera: WebM VP8/Opus recording with microphone permission, poster and storage. Buffer limited to 32 MiB; recording stops before releasing the sensor.\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Camera, Gallery and Video: handle WebM files and their posters.\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Settings: original Gaia date/time pickers, Back from built-in keyboard settings and unclipped select labels.\n- ![Change](https://img.shields.io/badge/Change-64748B?style=flat-square) Improve Vulpes: link to project issues. Unavailable services explained in their panels.\n- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Desktop: simulated 1920 × 1080 video decoded; photo, gallery, camera resume and navigation checked.\n- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Sargo r61 in RAM: 480 × 640 video decoded, eight front/rear switches passed. Test media deleted; recorded audio not listened to.\n- ![Change](https://img.shields.io/badge/Change-64748B?style=flat-square) Sargo: preview measured around 16 fps versus 12 in the previous test. 30 fps is not reached yet.\n- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Sargo: automatic NITZ time, persistent setting and guarded manual writes. Sync depends on carrier time; no NTP fallback, timezone selected manually.\n- ![Note](https://img.shields.io/badge/Note-6B7280?style=flat-square) Android: assets staged and Java compiled, without a new APK or Android video test at this stage. Mozilla account/Sync, remote location, Firefox OS add-ons and USB file transfer remain unimplemented. No flashing or publication.\n" + }, + "metadata": { + "kind": "development", + "status": "development", + "platforms": [ + "pc", + "apk", + "models" + ], + "models": [ + "sargo" + ] + } + }, + { + "id": "development-2026-10-12-settings-calls-hotspot", + "text": { + "fr": "## En développement — 12 octobre 2026 — Appels et partage Wi-Fi\n\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Dialogues Gaia : fermeture après ouverture, transitions interrompues et nouvelle saisie après validation refusée.\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Sargo : délai des services opérateur augmenté ; réglages lents séparés des commandes voix/SMS.\n- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Sargo : double appel, présentation du numéro et quatre règles de renvoi raccordés à oFono, avec validation et relecture.\n- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Sargo : point d’accès Wi-Fi WPA2 ou ouvert, configuration NetworkManager et pare-feu adapté. Mot de passe privé, accessible uniquement à Paramètres.\n- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) r59 en RAM : 69 panneaux, dialogues et APN vérifiés sur PC/Pixel. Point d’accès enregistré, activé puis arrêté depuis Gaia ; Wi-Fi précédent restauré. 44 tests Python et régressions des dialogues réussis.\n- ![Note](https://img.shields.io/badge/Note-6B7280?style=flat-square) Transit Internet vers un autre appareil non testé. Messagerie vocale, blocage d’appels, PIN/PUK, certificats Wi-Fi et partage USB restent ouverts. Ressources Android synchronisées ; aucun nouvel APK, flash ou publication.\n", + "en": "## In development — 12 October 2026 — Calls and Wi-Fi sharing\n\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Gaia dialogs: close after opening, complete interrupted transitions and allow retry after rejected validation.\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Sargo: longer carrier-service timeout; slow settings separated from voice/SMS commands.\n- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Sargo: call waiting, caller ID and four forwarding rules connected to oFono, with validation and readback.\n- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Sargo: WPA2 or open Wi-Fi hotspot, NetworkManager configuration and compatible firewall. Private password, accessible only to Settings.\n- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) r59 in RAM: 69 panels, dialogs and APNs checked on desktop/Pixel. Hotspot saved, started and stopped through Gaia; previous Wi-Fi restored. 44 Python tests and dialog regressions passed.\n- ![Note](https://img.shields.io/badge/Note-6B7280?style=flat-square) Internet forwarding to another device untested. Voicemail, call barring, PIN/PUK, Wi-Fi certificates and USB tethering remain open. Android assets synchronized; no new APK, flashing or publication.\n" + }, + "metadata": { + "kind": "development", + "status": "development", + "platforms": [ + "pc", + "apk", + "models" + ], + "models": [ + "sargo" + ] + } + }, + { + "id": "development-2026-10-12-settings-network", + "text": { + "fr": "## En développement — 12 octobre 2026 — Paramètres réseau\n\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Wi-Fi : adresse IPv4 et débit réels ; ne plus afficher « undefined » lorsqu’une mesure manque.\n- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Sargo : choix GSM/UMTS/LTE et modification des APN Internet/MMS existants, avec validation et relecture.\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) APN : refuser les changements d’un profil actif, protéger IMS et les mots de passe, tenter la restauration après échec et signaler son résultat.\n- ![Change](https://img.shields.io/badge/Change-64748B?style=flat-square) Sargo : file bornée pour les réglages lents. Lecture des appels en 20 ms pendant une attente de réglages de 5 secondes.\n- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) 38 tests Python, 69 panneaux PC et parcours APN/réseau sur Pixel en RAM vérifiés. Ressources Android synchronisées.\n- ![Note](https://img.shields.io/badge/Note-6B7280?style=flat-square) Ajout, suppression et réinitialisation des APN indisponibles. Sélection manuelle d’opérateur et certificats encore ouverts. Aucun nouvel APK, flash ou publication.\n", + "en": "## In development — 12 October 2026 — Network settings\n\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Wi-Fi: actual IPv4 address and link speed; no “undefined” label when a measurement is missing.\n- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Sargo: GSM/UMTS/LTE selection and editing of existing Internet/MMS APNs, with validation and readback.\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) APNs: reject changes to active profiles, protect IMS and passwords, attempt rollback after failure and report its result.\n- ![Change](https://img.shields.io/badge/Change-64748B?style=flat-square) Sargo: bounded queue for slow settings. Call status read in 20 ms during a five-second settings wait.\n- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) 38 Python tests, 69 desktop panels and APN/network workflows on RAM-booted Pixel checked. Android assets synchronized.\n- ![Note](https://img.shields.io/badge/Note-6B7280?style=flat-square) APN creation, deletion and reset unavailable. Manual carrier selection and certificates still open. No new APK, flashing or publication.\n" + }, + "metadata": { + "kind": "development", + "status": "development", + "platforms": [ + "pc", + "apk", + "models" + ], + "models": [ + "sargo" + ] + } + }, + { + "id": "development-2026-10-12-settings-audit", + "text": { + "fr": "## En développement — 12 octobre 2026 — Audit des paramètres\n\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Paramètres : navigation récupérable après échec d’un contrôleur ou d’un dialogue ; génération cohérente des modules.\n- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Filtres d’accessibilité et assistance Vulpes. Panneaux d’origine conservés avec explication des fonctions indisponibles.\n- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) PC : statistiques du profil et téléchargements Gecko. Android : statistiques du stockage privé.\n- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Sargo : données mobiles, itinérance, horloge et Wi-Fi masqué raccordés. Erreurs des anciennes interfaces réseau du panneau Son corrigées.\n- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) 97 vues inventoriées ; 69 panneaux PC/Pixel ouverts et sept dialogues natifs vérifiés. Stockage, données et fuseau relus après écriture sur r51 en RAM.\n- ![Note](https://img.shields.io/badge/Note-6B7280?style=flat-square) Les vues contextuelles et services indisponibles ne sont pas tous qualifiés. Java Android compilé, sans nouvel APK. Aucun flash, ticket fermé ni publication.\n", + "en": "## In development — 12 October 2026 — Settings audit\n\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Settings: recover navigation after a controller or dialog fails; consistent module generation.\n- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Accessibility filters and Vulpes support. Original panels retained with explanations for unavailable features.\n- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Desktop: profile statistics and Gecko downloads. Android: private storage statistics.\n- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Sargo: mobile data, roaming, clock and hidden Wi-Fi connected. Legacy network errors in Sound settings fixed.\n- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) 97 views inventoried; 69 desktop/Pixel panels opened and seven native dialogs checked. Storage, data and timezone read back after writes on r51 in RAM.\n- ![Note](https://img.shields.io/badge/Note-6B7280?style=flat-square) Contextual views and unavailable services are not all qualified. Android Java compiled, without a new APK. No flashing, ticket closure or publication.\n" + }, + "metadata": { + "kind": "development", + "status": "development", + "platforms": [ + "pc", + "apk", + "models" + ], + "models": [ + "sargo" + ] + } + }, + { + "id": "development-2026-10-12-settings-navigation", + "text": { + "fr": "## En développement — 12 octobre 2026 — Navigation des paramètres\n\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Listes déroulantes : sélecteur Gaia avec confirmation/annulation ; choix Clair/Sombre accessible. Gestionnaire SIM adapté à un emplacement.\n- ![Change](https://img.shields.io/badge/Change-64748B?style=flat-square) Panneaux indisponibles : explication et commandes désactivées au lieu de liens sans effet.\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Sargo : réseaux Wi-Fi mémorisés séparés des scans lents ; cache conservé lors des lectures d’état.\n- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Sargo : informations SIM, centre SMS et accusés de réception reliés au modem.\n- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) r45 en RAM : huit contrôles d’apparence, cinq panneaux et changement réel des accusés SMS validés, puis réglage restauré. Sélecteur PC vérifié ; ressources APK préparées.\n- ![Note](https://img.shields.io/badge/Note-6B7280?style=flat-square) Certificats Enterprise et autres options opérateur encore indisponibles. Aucun SMS émis, nouvel APK, flash ou publication.\n", + "en": "## In development — 12 October 2026 — Settings navigation\n\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Dropdowns: Gaia selector with confirm/cancel; Light/Dark choice accessible. SIM manager adapted to one slot.\n- ![Change](https://img.shields.io/badge/Change-64748B?style=flat-square) Unavailable panels: explanations and disabled controls instead of unresponsive links.\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Sargo: saved Wi-Fi networks separated from slow scans; cache preserved during status reads.\n- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Sargo: SIM information, SMS centre and delivery reports connected to the modem.\n- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) r45 in RAM: eight appearance checks, five panels and actual SMS report setting changes passed, then setting restored. Desktop selector checked; APK assets staged.\n- ![Note](https://img.shields.io/badge/Note-6B7280?style=flat-square) Enterprise certificates and other carrier options still unavailable. No SMS sent, new APK, flashing or publication.\n" + }, + "metadata": { + "kind": "development", + "status": "development", + "platforms": [ + "pc", + "apk", + "models" + ], + "models": [ + "sargo" + ] + } + }, + { + "id": "development-2026-10-12-sargo-appearance", + "text": { + "fr": "## En développement — 12 octobre 2026 — Correctifs communs sur Pixel 3a\n\n- ![Change](https://img.shields.io/badge/Change-64748B?style=flat-square) Sargo : boot r39 en RAM avec correctifs partagés et premier mode sombre. Noyau et configuration matérielle du r35 conservés.\n- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Démarrage, taille d’écran, réglages persistants, batterie, interface d’appel et Fira vérifiés. Services modem, audio, Wi-Fi et Bluetooth actifs.\n- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Sept contrôles d’apparence réussis dans Gaia : thème propagé, en-têtes lisibles, libellés placés et retour au clair. Capture Pixel inspectée.\n- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Outil de mise à jour Gaia dans un boot privé, avec empreintes, sources conservées et vérification des composants matériels inchangés.\n- ![Note](https://img.shields.io/badge/Note-6B7280?style=flat-square) Aucun nouvel appel, SMS ou appairage testé. Thème complet et comportement Android encore à qualifier. Aucun nouvel APK, flash ou publication.\n", + "en": "## In development — 12 October 2026 — Shared fixes on Pixel 3a\n\n- ![Change](https://img.shields.io/badge/Change-64748B?style=flat-square) Sargo: r39 RAM boot with shared fixes and initial dark mode. r35 kernel and hardware configuration preserved.\n- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Boot, viewport size, persistent settings, battery, call UI and Fira checked. Modem, audio, Wi-Fi and Bluetooth services active.\n- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Seven Gaia appearance checks passed: propagated theme, readable headers, aligned labels and return to light mode. Pixel screenshot inspected.\n- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Tool to update Gaia in a private boot image, with hashes, retained sources and checks for unchanged hardware components.\n- ![Note](https://img.shields.io/badge/Note-6B7280?style=flat-square) No new call, SMS or pairing tested. Full theme and Android behavior still need qualification. No new APK, flashing or publication.\n" + }, + "metadata": { + "kind": "development", + "status": "development", + "platforms": [ + "pc", + "apk", + "models" + ], + "models": [ + "sargo" + ] + } + }, + { + "id": "development-2026-10-11-appearance-input", + "text": { + "fr": "## En développement — 11 octobre 2026 — Apparence et clavier partagé\n\n- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Apparence : Clair, Sombre ou Selon le système, partagé entre les applications et conservé au redémarrage. Fonds d’écran, photos et sites externes préservés.\n- ![Change](https://img.shields.io/badge/Change-64748B?style=flat-square) Clavier : opérations d’édition mutualisées, sélection et effacement, protection contre les réponses tardives.\n- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) PC : neuf contrôles d’apparence et gestes de saisie dans Messages réussis, sans envoi de SMS. Revue visuelle de Paramètres, Messages, Téléphone et Contacts.\n- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Android : option expérimentale de clavier Gaia ; clavier Android conservé par défaut et mode de saisie restauré à la fermeture.\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Android : réessayer brièvement le même capteur encore occupé, sans réessayer après refus de permission ou fermeture de Photo.\n- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Preview 19 construit localement : signature, alignement, parité et tests du pont/caméra réussis.\n- ![Note](https://img.shields.io/badge/Note-6B7280?style=flat-square) Clavier Gaia, caméra avant Android et thème complet encore à qualifier. Sources prêtes pour Sargo, resté sur r35 à cette étape. Aucun upload, flash ni ticket fermé.\n", + "en": "## In development — 11 October 2026 — Appearance and shared keyboard\n\n- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Appearance: Light, Dark or Follow system, shared between apps and retained across restarts. Wallpapers, photos and external websites preserved.\n- ![Change](https://img.shields.io/badge/Change-64748B?style=flat-square) Keyboard: shared editing operations, selection and deletion, protection against late replies.\n- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Desktop: nine appearance checks and Messages input gestures passed, without sending SMS. Visual review of Settings, Messages, Phone and Contacts.\n- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Android: experimental Gaia keyboard option; Android keyboard remains the default, input mode restored on close.\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Android: briefly retry the same busy sensor, without retrying after permission denial or closing Camera.\n- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Preview 19 built locally: signature, alignment, parity and bridge/camera tests passed.\n- ![Note](https://img.shields.io/badge/Note-6B7280?style=flat-square) Gaia keyboard, Android front camera and full theme still need qualification. Sources ready for Sargo, which remained on r35 at this stage. No upload, flashing or ticket closure.\n" + }, + "metadata": { + "kind": "development", + "status": "development", + "platforms": [ + "pc", + "apk", + "models" + ], + "models": [ + "sargo" + ] + } + }, + { + "id": "development-2026-10-11-settings", + "text": { + "fr": "## En développement — 11 octobre 2026 — Paramètres et accueil\n\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Paramètres : gestion des sonneries, saisie/effacement du code PIN, désactivation du verrouillage et liste des permissions des applications tierces.\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Accueil : première initialisation du second lanceur et aperçus des fonds d’écran.\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Langues : actualiser les libellés Vulpes au changement de langue ; anglais de secours hors français. Fermer le panneau d’échec caméra et les bandeaux d’installation.\n- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Android : ouvrir le volet Gaia en touchant la barre de statut. Preview 18 construit, signé et comparé aux sources.\n- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) PC : neuf parcours Gaia, cinq contrôles de bandeaux et douze tests activités/caméra réussis. Bluetooth Sargo r35 confirmé par l’utilisateur.\n- ![Note](https://img.shields.io/badge/Note-6B7280?style=flat-square) Redémarrage à chaud du compositeur encore instable : fichiers restaurés et retour à r35. Corrections non qualifiées sur Sargo à cette étape ; clavier/thème Android ouverts. Aucun APK publié ni flash.\n", + "en": "## In development — 11 October 2026 — Settings and homescreens\n\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Settings: ringtone management, PIN entry/deletion, lock disabling and third-party app permission list.\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Home: first initialization of the second launcher and wallpaper previews.\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Languages: refresh Vulpes labels after language changes; English fallback outside French. Dismiss camera failure panels and installation banners.\n- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Android: open the Gaia tray by tapping the status bar. Preview 18 built, signed and compared with sources.\n- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Desktop: nine Gaia workflows, five banner checks and twelve activity/camera tests passed. Sargo r35 Bluetooth confirmed by the user.\n- ![Note](https://img.shields.io/badge/Note-6B7280?style=flat-square) Compositor hot restart still unstable: files restored and return to r35. Fixes not yet qualified on Sargo at this stage; Android keyboard/theme open. No APK publication or flashing.\n" + }, + "metadata": { + "kind": "development", + "status": "development", + "platforms": [ + "pc", + "apk", + "models" + ], + "models": [ + "sargo" + ] + } + }, + { + "id": "development-2026-10-09-mms-bluetooth", + "text": { + "fr": "## En développement — 9 octobre 2026 — MMS et Bluetooth\n\n- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Bluetooth : adaptateur Gaia partagé, commandes matérielles séparées et accès limité aux applications autorisées.\n- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Sargo : activation, découverte, appairage explicite, connexion et suppression depuis le panneau Bluetooth Gaia. Modules audio Bluetooth intégrés.\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Sargo : services réseau Qualcomm et transport MMS raccordés. Une déconnexion D-Bus ne termine plus le daemon MMS. État persistant, reprise sans doublon ni renvoi automatique incertain.\n- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) r35 en RAM : MMS avec image visible et accepté par le MMSC Orange ; réception destinataire à confirmer. Cinq appareils Bluetooth détectés, A2DP vers PC validé.\n- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) 191 tests Python Tundra et 18 tests JavaScript réussis. Ramdisk allégé et ressources Android synchronisées.\n- ![Note](https://img.shields.io/badge/Note-6B7280?style=flat-square) Audio d’appel via casque non qualifié. Aucun nouvel APK, flash ni push à cette étape.\n", + "en": "## In development — 9 October 2026 — MMS and Bluetooth\n\n- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Bluetooth: shared Gaia adapter, separate hardware commands and access restricted to authorized apps.\n- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Sargo: power, discovery, explicit pairing, connection and removal from Gaia Bluetooth settings. Bluetooth audio modules included.\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Sargo: Qualcomm network services and MMS transport connected. D-Bus disconnect no longer terminates the MMS daemon. Persistent state, retry without duplicates or uncertain automatic resends.\n- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) r35 in RAM: image MMS visible and accepted by Orange MMSC; recipient delivery still unconfirmed. Five Bluetooth devices discovered, A2DP to desktop verified.\n- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) 191 Tundra Python tests and 18 JavaScript tests passed. Smaller ramdisk and synchronized Android assets.\n- ![Note](https://img.shields.io/badge/Note-6B7280?style=flat-square) Headset call audio unqualified. No new APK, flashing or push at this stage.\n" + }, + "metadata": { + "kind": "development", + "status": "development", + "platforms": [ + "pc", + "apk", + "models" + ], + "models": [ + "sargo" + ] + } + }, + { + "id": "development-2026-10-08-camera-mms", + "text": { + "fr": "## En développement — 8 octobre 2026 — Caméra et transport MMS\n\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Caméra : arrêter le flux avant de détacher la vidéo ; attendre la fermeture du capteur, réinitialiser le viseur et recalculer sa taille après rotation.\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) MMS : préserver les pièces jointes Blob et garder les messages en attente jusqu’à confirmation du transport.\n- ![Change](https://img.shields.io/badge/Change-64748B?style=flat-square) Sargo : viseur WebGL réutilisé sans recapture vidéo intermédiaire ; écran maintenu éveillé pendant l’aperçu.\n- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) PC et Sargo : parcours Photo/Galerie et huit bascules natives vérifiés. JPEG, focus et zoom fonctionnels ; arrière ~16 FPS, avant ~25–27 FPS dans cet essai.\n- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Sargo : file MMS persistante, codec Nuntium et transport oFono. Interface texte/photo testée avec transport simulé.\n- ![Note](https://img.shields.io/badge/Note-6B7280?style=flat-square) Données mobiles bloquantes pour les MMS opérateur à cette étape. Ressources Android synchronisées ; aucun nouvel APK, flash ou push.\n", + "en": "## In development — 8 October 2026 — Camera and MMS transport\n\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Camera: stop the stream before detaching video; await sensor closure, reset the viewfinder and recalculate size after rotation.\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) MMS: preserve Blob attachments and keep messages pending until transport confirmation.\n- ![Change](https://img.shields.io/badge/Change-64748B?style=flat-square) Sargo: reused WebGL viewfinder without intermediate video capture; screen stays awake during preview.\n- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Desktop and Sargo: Camera/Gallery workflows and eight native switches checked. JPEG, focus and zoom working; rear ~16 fps, front ~25–27 fps in this test.\n- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Sargo: persistent MMS queue, Nuntium codec and oFono transport. Text/photo UI tested with simulated transport.\n- ![Note](https://img.shields.io/badge/Note-6B7280?style=flat-square) Mobile data blocked carrier MMS at this stage. Android assets synchronized; no new APK, flashing or push.\n" + }, + "metadata": { + "kind": "development", + "status": "development", + "platforms": [ + "pc", + "apk", + "models" + ], + "models": [ + "sargo" + ] + } + }, + { + "id": "development-2026-10-08-call-log-dialogs", + "text": { + "fr": "## 8 octobre 2026 — Rappel et messages d’erreur\n\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Dialogues Gaia : texte visible au lieu d’un écran gris avec un bouton seul.\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Journal d’appel : rappel via le même service que le pavé numérique, sans anciennes API Gecko. Fichier partagé PC/Android/Sargo.\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) MMS : signaler clairement l’envoi indisponible, garder texte et pièces jointes, ne plus annoncer un faux mode avion.\n- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) PC : dialogues FR/EN et composeur préservé. Sargo en RAM : 11 contrôles Gaia réussis, rappel testé avec modem simulé.\n- ![Note](https://img.shields.io/badge/Note-6B7280?style=flat-square) Aucun appel réel ni validation audio dans cette étape. Ressources Android synchronisées, aucun nouvel APK publié.\n", + "en": "## 8 October 2026 — Call-log redial and error dialogs\n\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Gaia dialogs: visible text instead of a grey screen with a lone button.\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Call log: redial through the same service as the keypad, without legacy Gecko APIs. Shared desktop/Android/Sargo file.\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) MMS: clearly report unavailable sending, retain text and attachments, no false airplane-mode message.\n- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Desktop: FR/EN dialogs and preserved composer. RAM-booted Sargo: 11 Gaia checks passed, redial tested with a simulated modem.\n- ![Note](https://img.shields.io/badge/Note-6B7280?style=flat-square) No real call or audio validation at this stage. Android assets synchronized, no new APK published.\n" + }, + "metadata": { + "kind": "history", + "status": "history", + "platforms": [ + "pc", + "apk", + "models" + ], + "models": [ + "sargo" + ] + } + }, + { + "id": "development-2026-10-08-sms-audio", + "text": { + "fr": "## En développement — 8 octobre 2026 — SMS et audio\n\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) SMS : envoyer en un appui en conservant le clavier. Trombone raccordé au menu Gaia : image, audio, vidéo ou contact.\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Interface : indicateur de volume visible, gestes du verrouillage et balayage des notifications corrigés.\n- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) PC : clavier, pièces jointes, Galerie, interface d’appel et volume vérifiés ; notification balayée sans déplacer la page. Aucun nouvel APK publié.\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Sargo : accès à PulseAudio, relais Qualcomm, protocole D-Bus et périphériques de calibration rétablis. Service audio Android concurrent retiré.\n- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Sargo : 11 contrôles Gaia, calibration et prévisualisations arrière/avant/arrière réussis.\n- ![Note](https://img.shields.io/badge/Note-6B7280?style=flat-square) Appels échouant avant sonnerie avec une erreur réseau dans cet essai. Audio bidirectionnel, geste Pixel et MMS encore non validés. Aucun flash ni publication.\n", + "en": "## In development — 8 October 2026 — SMS and audio\n\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) SMS: send in one tap while keeping the keyboard. Paperclip connected to the Gaia image, audio, video or contact menu.\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) UI: visible volume indicator, lockscreen gestures and notification swiping fixed.\n- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Desktop: keyboard, attachments, Gallery, call UI and volume checked; notification swiped without moving the page. No new APK published.\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Sargo: PulseAudio access, Qualcomm relay, D-Bus protocol and calibration devices restored. Conflicting Android audio service removed.\n- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Sargo: 11 Gaia checks, calibration and rear/front/rear previews passed.\n- ![Note](https://img.shields.io/badge/Note-6B7280?style=flat-square) Calls failed before ringing with a network error in this test. Two-way audio, Pixel gesture and MMS still unverified. No flashing or publication.\n" + }, + "metadata": { + "kind": "development", + "status": "development", + "platforms": [ + "pc", + "apk", + "models" + ], + "models": [ + "sargo" + ] + } + }, + { + "id": "development-2026-10-08-radio-reliability", + "text": { + "fr": "## En développement — 8 octobre 2026 — Fiabilité téléphonie\n\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Sargo : service radio maintenu après fermeture d’un client. Jamais de nouvel appel ou SMS automatique après une réponse perdue. Erreurs sans numéro ni texte du message.\n- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Diagnostic réseau/audio sans identifiants personnels et paramètres opérateur épinglés dans l’image native.\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) SMS : attendre le vrai gestionnaire Gaia et accepter les identifiants oFono avec majuscules. Contrôle des conversations rétabli sans réémission.\n- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Sargo : LTE retrouvé après redémarrage, SMS confirmé envoyé par le modem, microphone non silencieux et lecture audio acceptée.\n- ![Note](https://img.shields.io/badge/Note-6B7280?style=flat-square) Réception destinataire, audio d’appel bidirectionnel et stabilité modem restent à qualifier. Compositeur instable au redémarrage à chaud. Aucun nouvel APK, flash ou push.\n", + "en": "## In development — 8 October 2026 — Telephony reliability\n\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Sargo: radio service survives client closure. No automatic new call or SMS after a lost response. Errors exclude phone numbers and message text.\n- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Network/audio diagnostics without personal identifiers, and pinned carrier settings in the native image.\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) SMS: wait for the actual Gaia manager and accept uppercase oFono IDs. Conversation checks restored without resending.\n- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Sargo: LTE restored after reboot, SMS confirmed sent by the modem, non-silent microphone and audio playback accepted.\n- ![Note](https://img.shields.io/badge/Note-6B7280?style=flat-square) Recipient delivery, two-way call audio and modem stability remain unqualified. Compositor hot restart unstable. No new APK, flashing or push.\n" + }, + "metadata": { + "kind": "development", + "status": "development", + "platforms": [ + "pc", + "apk", + "models" + ], + "models": [ + "sargo" + ] + } + }, { "id": "2026-10-08-source-publication", "text": { - "fr": "## 8 octobre 2026 — Mise à jour des sources publiques\n\n### Commun · PC / Android / Tundra\n\n- Regrouper les correctifs récents de caméra, Contacts, SMS, Marketplace et les outils de construction et de restauration Sargo dans les sources publiques. Mettre à jour les README français et anglais avec les capacités et limites vérifiées.\n- Corriger le contrôle de parité Android pour créer son dossier de rapports sur une copie neuve du dépôt. Vérifier que la configuration Wi-Fi native embarque un répertoire de connexions vide.\n- Conserver les tickets, feuilles de route, documents internes, données personnelles et clés hors du dépôt.\n\n### Paquets et qualification\n\n- L’APK public reste Preview17 : les correctifs du 8 octobre nécessitent une prochaine compilation. Aucune nouvelle image native publiée ni flash dans cette étape.\n- Sur Sargo, l’enregistrement cellulaire reste en recherche lors du dernier contrôle ; audio bidirectionnel, appairage Bluetooth et profils restent à qualifier.", - "en": "## 8 October 2026 — Public source update\n\n### Shared · Desktop / Android / Tundra\n\n- Bring recent camera, Contacts, SMS and Marketplace fixes, plus Sargo build and recovery tools, into the public sources. Update both READMEs with verified capabilities and limitations.\n- Make the Android parity check create its report directory in a fresh checkout. Verify that native Wi-Fi configuration ships an empty connections directory.\n- Keep tickets, roadmaps, internal documents, personal data and keys outside the repository.\n\n### Packages and qualification\n\n- The public APK remains Preview17; the 8 October fixes require a future build. No new native image or flash in this step.\n- At the latest Sargo check, cellular registration is still searching; two-way call audio, Bluetooth pairing and profiles remain to be qualified." + "fr": "## 8 octobre 2026 — Mise à jour des sources publiques\n\n- ![Change](https://img.shields.io/badge/Change-64748B?style=flat-square) Publication des correctifs Caméra, Contacts, SMS et Marketplace, et des outils Sargo de construction et de restauration.\n- ![Change](https://img.shields.io/badge/Change-64748B?style=flat-square) README français et anglais actualisés avec les fonctions et limites vérifiées.\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Création du dossier de rapports lors du contrôle de parité Android sur un dépôt neuf.\n- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Configuration Wi-Fi native vérifiée : aucun profil de connexion embarqué.\n- ![Note](https://img.shields.io/badge/Note-6B7280?style=flat-square) Documents internes, données personnelles et clés exclus du dépôt.\n- ![Note](https://img.shields.io/badge/Note-6B7280?style=flat-square) APK public inchangé (Preview17) ; correctifs du 8 octobre non inclus. Aucune nouvelle image native publiée.\n- ![Note](https://img.shields.io/badge/Note-6B7280?style=flat-square) Sargo : modem encore en recherche réseau ; audio des appels, appairage et profils Bluetooth à valider.\n", + "en": "## 8 October 2026 — Public source update\n\n- ![Change](https://img.shields.io/badge/Change-64748B?style=flat-square) Published Camera, Contacts, SMS and Marketplace fixes, plus Sargo build and recovery tools.\n- ![Change](https://img.shields.io/badge/Change-64748B?style=flat-square) Updated French and English READMEs with verified features and limitations.\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Create the report directory when checking Android asset parity in a fresh checkout.\n- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Checked native Wi-Fi configuration: no bundled connection profiles.\n- ![Note](https://img.shields.io/badge/Note-6B7280?style=flat-square) Internal documents, personal data and keys excluded from the repository.\n- ![Note](https://img.shields.io/badge/Note-6B7280?style=flat-square) Public APK unchanged (Preview17); October 8 fixes not included. No new native image published.\n- ![Note](https://img.shields.io/badge/Note-6B7280?style=flat-square) Sargo: modem still searching for a network; call audio, Bluetooth pairing and profiles await validation.\n" + }, + "metadata": { + "kind": "history", + "status": "history", + "platforms": [ + "pc", + "apk", + "models" + ], + "models": [ + "sargo" + ] } }, { "id": "development-2026-10-08-camera-lifecycle", "text": { - "fr": "## 8 octobre 2026 — Caméra et parcours Contacts · en développement\n\n### Commun · Gaia / PC / Android / Tundra\n\n- Sérialiser les ouvertures de caméra, attendre la fermeture du capteur précédent et libérer les pistes et ressources vidéo même après un échec. Stabiliser l’identification avant/arrière sur PC et Android.\n- Conserver les images binaires lors des échanges PC/Tundra ; encoder explicitement les photos et dates des contacts pour le pont JSON Android.\n- Corriger l’actualisation de la fiche après modification d’un contact. Rétablir les activités Contacts → SMS, sélection d’un numéro et ouverture du pavé numérique, en préservant les demandes imbriquées et les points d’entrée de Gaia.\n- Réouvrir Galerie en mode sélection pour ajouter une photo à un contact, même lorsque Galerie est déjà ouverte en consultation. Sélection, recadrage et enregistrement de la photo validés sur PC.\n- Ajouter des tests de caméra, de stockage des contacts, de permissions, de transport binaire et de routage des activités.\n\n### PC\n\n- Vérifier le parcours original Appareil photo → capture JPEG → Galerie → ouverture de la photo → retour caméra.\n- Vérifier dans Gaia : création et modification d’un contact, actualisation de la fiche, recherche sans accents et sans résultats, favoris, destinataire SMS, sélection d’un numéro, pavé numérique prérempli, annulation puis confirmation de suppression. Le test n’émet ni appel ni SMS et supprime son contact temporaire.\n\n### Tundra · Sargo\n\n- Vérifier en RAM sur Pixel 3a : autofocus, zoom, quatre alternances avant/arrière avec nouvelles trames, capture JPEG depuis Gaia, stockage, ouverture dans Galerie et arrêt du capteur à l’accueil.\n- Vérifier les conversations déjà stockées, le clavier et les notifications. Recherche Wi-Fi : quatre réseaux détectés. Nouvelle émission/réception SMS et audio des appels encore en qualification.\n- Ajouter les paquets BlueZ et les bibliothèques nécessaires, verrouillés par SHA256. Le contrôleur Bluetooth démarre, s’active et lance une recherche ; l’appairage depuis Gaia et les profils ne sont pas encore validés.\n- Ajouter le magasin de certificats natif : HTTPS vérifié sans désactivation de TLS. Tester les archives altérées, collisions de fichiers et reconstructions répétées.\n\n- Corriger les fichiers Contacts embarqués dans le build de démarrage : la création, l’édition, le passage vers SMS, le sélecteur et le pavé numérique passent aussi avec les gestes tactiles sur Sargo.\n\n### Android · APK\n\n- Publier l’APK Preview17 existant, avec téléchargement et SHA256 vérifiés. Il précède les correctifs du 8 octobre.\n- Corriger la transmission des destinataires et du texte de Gaia vers l’application SMS Android, y compris la réponse à une conversation et le numéro saisi sans validation préalable. Tests du pont réussis ; essai Android réel encore nécessaire.\n- Réempaqueter les ressources communes corrigées pour la prochaine compilation ; aucun nouvel APK incluant ces correctifs n’est publié.\n\n**État :** builds natifs de qualification démarrés en RAM. Aucun flash, push de code ni publication de documentation privée dans cette étape. La qualification complète des fonctions de téléphone reste ouverte.\n", - "en": "## 8 October 2026 — Camera and Contacts workflows · in development\n\n### Shared · Gaia / Desktop / Android / Tundra\n\n- Serialize camera opening, await the previous sensor shutdown and release tracks and video resources after failures. Keep stable front/rear identities on desktop and Android.\n- Preserve binary images across desktop/Tundra transport; explicitly encode contact photos and dates for Android's JSON bridge.\n- Refresh contact details after editing. Restore Contacts → SMS, number picking and prefilled dialer activities, preserving nested requests and Gaia entry points.\n- Reopen Gallery in picker mode when adding a contact photo, even when Gallery is already open for browsing. Selection, cropping and photo persistence verified on desktop.\n- Add camera lifecycle, contact persistence, permissions, binary transport and activity routing tests.\n\n### Desktop\n\n- Verify the original Camera → JPEG capture → Gallery → photo opening → camera workflow.\n- Verify Gaia contact creation, editing, detail refresh, accent-insensitive search and empty results, favourites, SMS recipient, number picker, prefilled keypad, cancelled deletion and confirmed deletion. The test sends no message and makes no call; its temporary contact is removed.\n\n### Tundra · Sargo\n\n- Verify on a RAM-booted Pixel 3a: autofocus, zoom, four front/rear switches with fresh frames, shutter capture, JPEG persistence, Gallery opening and sensor shutdown on Home.\n- Check stored conversations, keyboard and notifications. Wi-Fi scan finds four networks. New SMS transmission/reception and call audio are still being qualified.\n- Add SHA256-pinned BlueZ packages and required libraries. The controller starts, powers on and discovers; pairing through Gaia and Bluetooth profiles are not yet validated.\n- Add native trusted certificates and verify HTTPS without disabling TLS. Test corrupted archives, file collisions and repeatable staging.\n\n- Include the corrected Contacts files in the boot build: contact creation, editing, SMS handoff, picking and dialer prefilling also pass using touch gestures on Sargo.\n\n### Android · APK\n\n- Publish the existing Preview17 APK and verify its download and SHA256. It predates the 8 October fixes.\n- Fix recipient and body handoff from Gaia to the Android SMS app, including replies and a typed recipient that was not yet committed. Bridge tests pass; real Android validation is still required.\n- Repackage corrected shared resources for the next build; no APK containing these new fixes has been published.\n\n**Status:** native qualification builds booted in RAM. No flashing, source push or private documentation publication in this step. Full phone qualification remains open.\n" + "fr": "## 8 octobre 2026 — Caméra et parcours Contacts · en développement\n\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Caméra : ouvertures successives, libération du capteur et identification avant/arrière corrigées.\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Contacts : transport des photos et des dates corrigé sur PC, Android et Tundra.\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Contacts : actualisation après modification, ouverture de SMS, sélection d’un numéro et préremplissage du pavé numérique rétablis.\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Galerie : ouverture en mode sélection pour la photo d’un contact, même si l’application est déjà ouverte.\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Sargo : fichiers Contacts corrigés inclus dans l’image de démarrage.\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Android : transmission du destinataire et du texte à l’application SMS, y compris les réponses et les numéros non encore validés.\n- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Tests de caméra, stockage des contacts, permissions, transport binaire et activités.\n- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) PC : capture JPEG, Galerie, photo de contact, création, édition, recherche, favoris et suppression vérifiés. Aucun appel ni SMS émis.\n- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Sargo en RAM : autofocus, zoom, quatre bascules de caméra, capture et Galerie vérifiés ; parcours Contacts testés au toucher.\n- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Sargo : conversations stockées, clavier et notifications vérifiés ; quatre réseaux Wi-Fi détectés. Nouveaux SMS et audio d’appel à valider.\n- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Sargo : BlueZ et ses dépendances avec empreintes SHA256. Activation et recherche Bluetooth testées ; appairage et profils à valider.\n- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Sargo : magasin de certificats natif. HTTPS, archives altérées, collisions de fichiers et reconstructions testés.\n- ![Note](https://img.shields.io/badge/Note-6B7280?style=flat-square) Preview17 publié avec téléchargement et SHA256 vérifiés. Les correctifs ci-dessus restent dans les sources ; essai SMS Android réel nécessaire. Aucun flash Sargo.\n", + "en": "## 8 October 2026 — Camera and Contacts workflows · in development\n\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Camera: corrected sequential opening, sensor cleanup and front/rear identification.\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Contacts: corrected photo and date transport on desktop, Android and Tundra.\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Contacts: restored refresh after editing, SMS handoff, number picking and dialer prefilling.\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Gallery: open the contact photo picker even when the app is already running.\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Sargo: include corrected Contacts files in the boot image.\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Android: pass recipients and text to the SMS app, including replies and unconfirmed typed numbers.\n- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Tests for camera lifecycle, contact storage, permissions, binary transport and activities.\n- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Desktop: verified JPEG capture, Gallery, contact photos, creation, editing, search, favourites and deletion. No calls or SMS sent.\n- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Sargo in RAM: verified autofocus, zoom, four camera switches, capture and Gallery; tested Contacts with touch input.\n- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Sargo: checked stored conversations, keyboard and notifications; detected four Wi-Fi networks. New SMS and call audio await validation.\n- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Sargo: BlueZ and dependencies pinned by SHA256. Power-on and discovery tested; pairing and profiles await validation.\n- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Sargo: native certificate store. Tested HTTPS, corrupted archives, file collisions and repeated builds.\n- ![Note](https://img.shields.io/badge/Note-6B7280?style=flat-square) Published Preview17 with download and SHA256 checks. These fixes remain in source; Android SMS needs a real-device test. No Sargo flash.\n" + }, + "metadata": { + "kind": "history", + "status": "history", + "platforms": [ + "pc", + "apk", + "models" + ], + "models": [ + "sargo" + ] } }, { "id": "development-2026-10-06-marketplace-theme", "text": { - "fr": "## 6 octobre 2026 — Thème Marketplace intégré · en développement\n\n### Commun · Gaia / PC / Android / Tundra\n\n- Ouvrir le catalogue avec `mode=app`, conservé dans les liens, la recherche, les filtres, les fiches et le changement de langue.\n- Préparer un thème web Vulpes dédié : Fira locale, navigation compacte, boutons tactiles, cartes lisibles, filtres repliables et captures défilantes. Retirer les en-têtes du site dans le mode intégré, tout en conservant l’interface du site classique.\n- Conserver les confirmations et messages système côté Gaia. Le thème ne donne aucun privilège supplémentaire aux pages du catalogue.\n- Vérifier 16 pages FR/EN, 2 pages en mode classique et les ressources du thème. Rendus mobiles de 320 et 360 pixels inspectés. Lanceurs PC et Sargo mis à jour ; les sources seront reprises au prochain empaquetage Android.\n\nFichiers du site prêts localement, non déployés sur le serveur public. Aucun push ni nouvelle publication APK/ROM.\n", - "en": "## 6 October 2026 — Embedded Marketplace theme · in development\n\n### Shared · Gaia / Desktop / Android / Tundra\n\n- Open the catalogue with `mode=app`, retained in navigation, search, filters, app details and language switches.\n- Prepare a dedicated Vulpes web theme with local Fira fonts, compact navigation, touch-friendly controls, readable cards, collapsible filters and scrolling screenshots. Omit website headers in embedded mode while preserving the standard website layout.\n- Keep confirmations and system messages in Gaia. The theme grants catalogue pages no additional privileges.\n- Check 16 French/English pages, 2 standard pages and theme assets. Inspect mobile renders at 320 and 360 pixels. Desktop and Sargo launchers updated; shared sources will be included in the next Android packaging run.\n\nWebsite files prepared locally, not deployed to the public server. No push or new APK/ROM publication.\n" + "fr": "## 6 octobre 2026 — Thème Marketplace intégré · en développement\n\n- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Marketplace : thème intégré avec Fira locale, navigation compacte, boutons tactiles, filtres repliables et captures défilantes.\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Conservation de `mode=app` dans la navigation, la recherche, les filtres et le changement de langue.\n- ![Change](https://img.shields.io/badge/Change-64748B?style=flat-square) En-têtes du site masqués en mode intégré. Confirmations gérées par Gaia, sans privilèges supplémentaires pour le catalogue.\n- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) 16 pages FR/EN, 2 pages classiques et affichages mobiles à 320 et 360 pixels vérifiés.\n- ![Note](https://img.shields.io/badge/Note-6B7280?style=flat-square) Thème prêt localement, non déployé. Lanceurs PC et Sargo mis à jour ; intégration Android au prochain APK.\n", + "en": "## 6 October 2026 — Embedded Marketplace theme · in development\n\n- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Marketplace: embedded theme with local Fira fonts, compact navigation, touch controls, collapsible filters and scrolling screenshots.\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Preserve `mode=app` across navigation, search, filters and language changes.\n- ![Change](https://img.shields.io/badge/Change-64748B?style=flat-square) Hide website headers in embedded mode. Gaia handles confirmations; the catalogue receives no extra privileges.\n- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Checked 16 French/English pages, 2 standard pages and mobile layouts at 320 and 360 pixels.\n- ![Note](https://img.shields.io/badge/Note-6B7280?style=flat-square) Theme ready locally, not deployed. Desktop and Sargo launchers updated; Android integration pending the next APK.\n" + }, + "metadata": { + "kind": "history", + "status": "history", + "platforms": [ + "pc", + "apk", + "models" + ], + "models": [ + "sargo" + ] } }, { "id": "development-2026-10-06-system-banner", "text": { - "fr": "## 6 octobre 2026 — Bandeaux Gaia · en développement\n\n### Commun · Gaia / PC / Android / Tundra\n\n- Faire disparaître le bandeau « Application installée » après son délai normal, même si la fin d’animation ne remonte pas du composant Gaia. Conserver son apparence et permettre sa fermeture au toucher.\n- Annuler les anciens délais lorsqu’un nouveau message remplace le précédent pour éviter une disparition prématurée.\n- Vérifier sur PC l’affichage, l’expiration, le remplacement et la fermeture au clic ; vérifier sur le Pixel le retrait effectif du bandeau après cinq secondes. Correctif déployé sur l’essai Sargo en RAM et disponible dans les sources communes pour le prochain APK.\n\nAucun flash ni publication dans cette étape.\n", - "en": "## 6 October 2026 — Gaia banners · in development\n\n### Shared · Gaia / Desktop / Android / Tundra\n\n- Dismiss the “Application installed” banner after its normal timeout even if the animation-end event does not reach the Gaia component host. Keep its appearance and tap-to-dismiss behavior.\n- Cancel old timers when a new message replaces the previous one, preventing premature dismissal.\n- Check display, expiry, replacement and click dismissal on desktop; verify actual removal after five seconds on the Pixel. Fix deployed to the Sargo RAM trial and available in shared sources for the next APK.\n\nNo flash or publication in this step.\n" + "fr": "## 6 octobre 2026 — Bandeaux Gaia · en développement\n\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Fermeture automatique du bandeau « Application installée », même sans événement de fin d’animation.\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Annulation des anciens délais lorsqu’un nouveau bandeau remplace le précédent.\n- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) PC : affichage, expiration, remplacement et fermeture au clic vérifiés. Sargo : disparition après cinq secondes vérifiée en RAM.\n- ![Note](https://img.shields.io/badge/Note-6B7280?style=flat-square) Correctif disponible dans les sources communes pour le prochain APK. Aucun flash ni publication.\n", + "en": "## 6 October 2026 — Gaia banners · in development\n\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Automatically dismiss the “Application installed” banner even without an animation-end event.\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Cancel old timers when a new banner replaces the previous one.\n- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Desktop: checked display, expiry, replacement and click dismissal. Sargo: verified dismissal after five seconds in RAM.\n- ![Note](https://img.shields.io/badge/Note-6B7280?style=flat-square) Fix available in shared sources for the next APK. No flash or release.\n" + }, + "metadata": { + "kind": "history", + "status": "history", + "platforms": [ + "pc", + "apk", + "models" + ], + "models": [ + "sargo" + ] } }, { "id": "development-2026-10-06-wifi", "text": { - "fr": "## 6 octobre 2026 — Wi-Fi et panneaux Paramètres · en développement\n\n### Commun · Gaia / PC / Android / Tundra\n\n- Terminer l’ouverture d’un panneau Paramètres même si Gecko ne produit pas l’événement de fin d’animation. Conserver les transitions Gaia et éviter les doubles initialisations.\n- Correctif dans les sources communes et le serveur PC ; inclus au prochain empaquetage Android. Aucun nouvel APK publié dans cette étape.\n\n### Tundra · Sargo\n\n- Corriger les événements de signal Wi-Fi : transmettre le réseau associé et ne pas annoncer de signal de connexion lorsque le téléphone est déconnecté. L’événement incomplet faisait échouer l’affichage des réseaux dans Gaia.\n- Sur le Pixel, vérifier le pilote, le service natif, le passage par Gecko et l’affichage du panneau corrigé : quatre points d’accès détectés, regroupés en deux réseaux, sans erreur de recherche.\n- Connexion Wi-Fi confirmée par l’utilisateur ; requête HTTPS réelle réussie depuis Gecko vers example.org (HTTP 200, vérification des certificats active). Correctifs appliqués à l’essai en RAM ; aucun flash ni push.\n", - "en": "## 6 October 2026 — Wi-Fi and Settings panels · in development\n\n### Shared · Gaia / Desktop / Android / Tundra\n\n- Complete Settings panel navigation even when Gecko does not emit an animation-end event. Preserve Gaia transitions and prevent duplicate initialization.\n- Updated shared sources and the desktop server; included in the next Android packaging run. No new APK published in this step.\n\n### Tundra · Sargo\n\n- Fix Wi-Fi signal events: include the associated network and do not emit connection information while disconnected. The incomplete event prevented Gaia from displaying scan results.\n- Check the driver, native service, Gecko bridge and corrected panel on the Pixel: four access points detected, grouped into two networks, with no scan error.\n- Wi-Fi connection confirmed by the user; a real HTTPS request from Gecko to example.org passed (HTTP 200, certificate verification enabled). Fixes applied to the RAM trial; no flash or Git push.\n" + "fr": "## 6 octobre 2026 — Wi-Fi et panneaux Paramètres · en développement\n\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Paramètres : ouverture des panneaux même sans événement de fin d’animation, sans double initialisation.\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Sargo : événements Wi-Fi corrigés pour rétablir l’affichage des réseaux et signaler correctement la déconnexion.\n- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Sargo : quatre points d’accès détectés, regroupés en deux réseaux. Connexion confirmée et requête HTTPS réussie avec certificats vérifiés.\n- ![Note](https://img.shields.io/badge/Note-6B7280?style=flat-square) Sources communes, serveur PC et essai Sargo en RAM mis à jour. Correctifs non inclus dans l’APK public.\n", + "en": "## 6 October 2026 — Wi-Fi and Settings panels · in development\n\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Settings: open panels even without an animation-end event, without duplicate initialization.\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Sargo: corrected Wi-Fi events to restore network results and report disconnection properly.\n- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Sargo: detected four access points grouped into two networks. Connection confirmed and HTTPS request passed with certificate verification.\n- ![Note](https://img.shields.io/badge/Note-6B7280?style=flat-square) Shared sources, desktop server and Sargo RAM trial updated. Fixes not included in the public APK.\n" + }, + "metadata": { + "kind": "history", + "status": "history", + "platforms": [ + "pc", + "apk", + "models" + ], + "models": [ + "sargo" + ] } }, { "id": "development-2026-10-06-marketplace-views", "text": { - "fr": "## 6 octobre 2026 — Marketplace · en développement\n\n### PC / Tundra · Sargo\n\n- Maintenir le catalogue sous la barre de statut et dans les limites de son application. Masquer sa vue web à l’accueil et dans le multitâche ; la retirer à la fermeture de l’application.\n- Éviter qu’une navigation annulée ou l’ancienne page d’erreur masque une nouvelle tentative.\n- Afficher un message local et proposer une nouvelle tentative lorsque le catalogue est inaccessible, au lieu de laisser sa page d’erreur recouvrir Gaia.\n- Sur PC : navigation, erreur réseau, nouvelle tentative, retour à l’accueil et fermeture depuis le multitâche vérifiés. Sept contrôles directs passent sur Sargo en RAM, sans fenêtre de débogage, avec une page locale et une précondition de déverrouillage forcée ; le catalogue distant nécessite une connexion Internet sur le téléphone.\n\n### Android · APK\n\n- Reconstruire l’APK local Preview17 avec le message d’erreur Marketplace commun. Signature, alignement et parité des ressources vérifiés ; nouvelle régression sur appareil Android non effectuée dans cette étape.\n\nAucune publication dans cette étape.\n", - "en": "## 6 October 2026 — Marketplace · in development\n\n### Desktop / Tundra · Sargo\n\n- Keep the catalogue below the status bar and inside its owning application. Hide its web view on Home and in the task switcher; remove it when the app closes.\n- Prevent a cancelled navigation or a previous error page from hiding a retry.\n- Show a local error message with a retry action when the catalogue is unavailable, instead of leaving its error page over Gaia.\n- Desktop navigation, network failure, retry, Home and closing from the task switcher checked. Seven direct checks pass on Sargo in RAM without a debugger window, using a local page and a forced-unlock precondition; the remote catalogue requires an Internet connection on the phone.\n\n### Android · APK\n\n- Rebuild the local Preview17 APK with the shared Marketplace error message. Signature, alignment and asset parity checked; no new Android device regression run in this step.\n\nNothing published in this step.\n" + "fr": "## 6 octobre 2026 — Marketplace · en développement\n\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) PC/Sargo : catalogue Marketplace contenu sous la barre de statut, masqué à l’accueil et retiré à la fermeture.\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Une navigation annulée ou une ancienne page d’erreur ne masque plus la nouvelle tentative.\n- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Message d’erreur local avec bouton Réessayer lorsque le catalogue est inaccessible.\n- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) PC : navigation, erreur réseau, nouvelle tentative, accueil et fermeture vérifiés.\n- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Sargo en RAM : sept contrôles réussis avec une page locale et déverrouillage forcé. Catalogue distant non validé sans connexion Internet.\n- ![Change](https://img.shields.io/badge/Change-64748B?style=flat-square) APK Preview17 local reconstruit ; signature, alignement et ressources vérifiés. Aucun nouveau test sur appareil Android ni publication.\n", + "en": "## 6 October 2026 — Marketplace · in development\n\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Desktop/Sargo: keep Marketplace below the status bar, hide it on Home and remove it when closed.\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Cancelled navigation or an old error page no longer hides a retry.\n- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Local error message with a Retry button when the catalogue is unavailable.\n- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Desktop: verified navigation, network errors, retry, Home and closing.\n- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Sargo in RAM: seven checks passed with a local page and forced unlock. Remote catalogue not validated without Internet access.\n- ![Change](https://img.shields.io/badge/Change-64748B?style=flat-square) Rebuilt local Preview17 APK; checked signature, alignment and assets. No new Android device test or release.\n" + }, + "metadata": { + "kind": "history", + "status": "history", + "platforms": [ + "pc", + "apk", + "models" + ], + "models": [ + "sargo" + ] } }, { "id": "2.7-preview.17", "text": { - "fr": "## 2.7-preview.17 — APK construit · 5 octobre 2026\n\n### Commun · Gaia / PC\n\n- Remettre Marketplace sur l’accueil avec son icône originale et le catalogue Vulpes. Ouvrir sa version française pour les langues fr, anglaise sinon.\n- Adapter les téléchargements du catalogue en installations confirmées : vérifier les archives ZIP, ajouter l’application à l’accueil et permettre son lancement et sa désinstallation.\n- Conserver les applications installées séparément des sources Gaia, sur des origines distinctes et sans privilèges système hérités des anciens manifestes.\n- Valider sur PC le parcours réel avec 2048 : catalogue, confirmation, installation, lancement, persistance et désinstallation. Contrôler les archives corrompues, chemins invalides, limites, annulation et confirmations à usage unique.\n\n### Android · APK\n\n- Construire et signer Preview17 avec le même Marketplace et le même service d’installation. Utiliser la vue GeckoView existante pour le catalogue et le stockage privé Android pour les paquets.\n- Vérifier sur émulateur Android 16/API36 le clic Installer, la confirmation, l’apparition sur l’accueil et le lancement de 2048 sans accès au pont système Vulpes. Vérifier aussi la conservation après arrêt forcé et relance, puis la désinstallation.\n\n### Tundra · Sargo\n\n- Préparer une image de boot locale incluant Marketplace, l’installateur et le serveur des applications ajoutées. Démarrage en RAM effectué sur le Pixel le 6 octobre : accueil et ressources Marketplace vérifiés, sans flash. Neuf contrôles système sur dix passent ; le contrôle global Gaia relève une variation du viewport au déverrouillage. Installation de paquets sur Sargo encore à qualifier.\n\n**Limites :** compatibilité initiale avec les applications web empaquetées. Les anciennes API privilégiées, applications hébergées et services disparus ne sont pas restaurés automatiquement. Rien publié sur Forgejo ni sur le site.\n", - "en": "## 2.7-preview.17 — APK built · 5 October 2026\n\n### Shared · Gaia / Desktop\n\n- Restore Marketplace on Home with its original icon and the Vulpes catalogue. Select French for fr languages and English otherwise.\n- Turn catalogue downloads into confirmed installations: validate ZIP archives, add the application to Home and support launch and removal.\n- Store installed applications separately from Gaia sources, on distinct origins and without system privileges inherited from archived manifests.\n- Validate the real desktop workflow with 2048: catalogue, confirmation, installation, launch, persistence and removal. Check damaged archives, invalid paths, limits, cancellation and single-use confirmations.\n\n### Android · APK\n\n- Build and sign Preview17 with the same Marketplace and installation service. Use the existing GeckoView browser for the catalogue and private Android storage for packages.\n- Verify the Install click, confirmation, Home icon and 2048 launch without access to the Vulpes system bridge on an Android 16/API36 emulator. Also verify persistence after force-stop/relaunch and removal.\n\n### Tundra · Sargo\n\n- Prepare a local boot image containing Marketplace, the installer and the added-application server. RAM boot performed on the Pixel on 6 October: Home and Marketplace resources verified, without flashing. Nine of ten system checks pass; the overall Gaia check records a viewport change during unlocking. Package installation on Sargo still needs qualification.\n\n**Limitations:** initial support for packaged web applications. Old privileged APIs, hosted apps and discontinued services are not restored automatically. Nothing published to Forgejo or the website.\n" + "fr": "## 2.7-preview.17 — APK construit · 5 octobre 2026\n\n- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Marketplace sur l’accueil, avec son icône originale et le catalogue Vulpes en français ou en anglais.\n- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Installation confirmée des applications ZIP, lancement depuis l’accueil et désinstallation.\n- ![Change](https://img.shields.io/badge/Change-64748B?style=flat-square) Applications installées isolées des sources Gaia et des privilèges système.\n- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) PC : parcours complet avec 2048 et contrôles des archives, chemins, limites, annulations et confirmations à usage unique.\n- ![Change](https://img.shields.io/badge/Change-64748B?style=flat-square) APK Preview17 construit et signé avec Marketplace, GeckoView et stockage privé des paquets.\n- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Émulateur Android 16/API 36 : installation, lancement, isolation, conservation après redémarrage de l’application et désinstallation de 2048.\n- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Sargo en RAM le 6 octobre : accueil et ressources Marketplace vérifiés, 9 contrôles système sur 10 réussis. Variation du viewport au déverrouillage ; installation de paquets à valider.\n- ![Note](https://img.shields.io/badge/Note-6B7280?style=flat-square) Prise en charge initiale des applications web empaquetées. Anciennes API privilégiées et applications hébergées non restaurées. Aucune publication à cette étape.\n", + "en": "## 2.7-preview.17 — APK built · 5 October 2026\n\n- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Marketplace on Home with its original icon and the Vulpes catalogue in French or English.\n- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Confirmed ZIP app installation, launch from Home and removal.\n- ![Change](https://img.shields.io/badge/Change-64748B?style=flat-square) Installed apps isolated from Gaia sources and system privileges.\n- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Desktop: full 2048 workflow plus archive, path, limit, cancellation and single-use confirmation checks.\n- ![Change](https://img.shields.io/badge/Change-64748B?style=flat-square) Preview17 APK built and signed with Marketplace, GeckoView and private package storage.\n- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Android 16/API 36 emulator: 2048 installation, launch, isolation, persistence after app restart and removal.\n- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Sargo in RAM on October 6: Home and Marketplace assets verified, 9 of 10 system checks passed. Viewport changes on unlock; package installation awaits validation.\n- ![Note](https://img.shields.io/badge/Note-6B7280?style=flat-square) Initial support for packaged web apps. Legacy privileged APIs and hosted apps not restored. No release at this stage.\n" + }, + "metadata": { + "kind": "version", + "status": "built", + "platforms": [ + "pc", + "apk", + "models" + ], + "models": [ + "sargo" + ] } }, { "id": "2.7-preview.16", "text": { - "fr": "## 2.7-preview.16 — APK construit · 5 octobre 2026\n\n### Commun · Gaia / PC\n\n- Rétablir les sélecteurs Gaia de fond d’écran et de sons, leur retour vers Paramètres et leur annulation par Accueil. Partager le transport des images et sons enregistrés entre PC et Android.\n- Propager les changements de langue à l’accueil, y compris sans permission de lecture générale des réglages. Vérifier les libellés FR/EN et la persistance.\n- Rétablir l’arrêt du balayage horizontal sur une page et masquer les applications de développement sur l’accueil, sans les supprimer des sources.\n- Utiliser le logo Vulpes fourni dans le splash et adapter les composants originaux de la liste des sons au moteur actuel.\n- Vérifier sur PC deux changements consécutifs de fond, le choix d’un son et les panneaux Gaia restaurés.\n\n### Android · APK\n\n- APK local Preview16 construit et signé ; contenu commun et version vérifiés. Aucune publication dans cette étape.\n- Valider Vulpes comme lanceur HOME, afficher le verrouillage Gaia après veille et rétablir le retour Android depuis la caméra ouverte au verrouillage.\n- Masquer les barres système Android en mode immersif, en conservant leur accès temporaire par balayage.\n- Tests sur émulateur Android 16/API36 : verrouillage, caméra/retour, FR/EN, accueil sans applications de test, fond d’écran et son. Fond conservé après arrêt forcé et relance, sans changement répété pendant 15 secondes. Autres appareils Android non qualifiés dans cette étape.\n\n### Tundra · Sargo\n\n- Inclure les mêmes correctifs et la politique des applications dans une nouvelle image de boot de test. Le boot précédent réappliquait une copie ancienne des fichiers et annulait les modifications à chaud.\n- Démarrage en RAM sur le Pixel réussi : six contrôles de langues/accueil/sélecteurs et dix contrôles système passent. Sélecteurs image/son vérifiés sans écraser les choix personnels. Le boot installé reste Preview15 ; aucune partition flashée dans cette étape.\n\n**Limites :** l’installation tierce FFAA reste ouverte. Le site dépend d’anciennes API mozApps et d’un script nomodule ; un installateur isolant les applications et leurs permissions reste à construire. Le choix d’un son Gaia ne modifie pas la sonnerie globale Android ; l’audio d’appel réel n’a pas été qualifié ici.", - "en": "## 2.7-preview.16 — APK built · 5 October 2026\n\n### Shared · Gaia / Desktop\n\n- Restore Gaia wallpaper and sound pickers, return navigation to Settings and cancellation through Home. Share persisted image and sound transport between desktop and Android.\n- Propagate language changes to Home without granting general settings read access. Check French/English labels and persistence.\n- Restore horizontal page snapping and hide development apps from Home without deleting their sources.\n- Use the supplied Vulpes logo in the splash and adapt the original sound-list components to the current engine.\n- Verify two consecutive wallpaper changes, sound selection and restored Gaia panels on desktop.\n\n### Android · APK\n\n- Build and sign local Preview16; verify shared payload and version. Nothing published in this step.\n- Validate Vulpes as the HOME launcher, restore Gaia locking after sleep and Android Back navigation from the lockscreen camera.\n- Hide Android system bars in immersive mode while retaining temporary swipe access.\n- Android 16/API36 emulator checks: locking, camera/Back, French/English, Home without test apps, wallpaper and sound. Wallpaper retained after force-stop/relaunch with no repeated changes over 15 seconds. Other Android devices not qualified in this step.\n\n### Tundra · Sargo\n\n- Include shared fixes and app policy in a new test boot image. The previous boot reapplied an old file snapshot, undoing hot updates.\n- RAM boot on Pixel passed: six language/Home/picker checks and ten system checks. Image/sound picker results verified without replacing personal selections. Installed boot remains Preview15; no partition flashed in this step.\n\n**Limitations:** third-party FFAA installation remains open. The site depends on old mozApps APIs and a nomodule script; an installer with application and permission isolation still needs implementation. Selecting a Gaia sound does not change the global Android ringtone; actual call audio was not qualified here." + "fr": "## 2.7-preview.16 — APK construit · 5 octobre 2026\n\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Sélecteurs Gaia de fond d’écran et de sons rétablis, avec retour vers Paramètres et annulation par Accueil.\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Changement de langue appliqué à l’accueil et conservé après redémarrage.\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Arrêt du balayage horizontal sur chaque page ; applications de développement masquées sur l’accueil.\n- ![Change](https://img.shields.io/badge/Change-64748B?style=flat-square) Logo Vulpes au démarrage et liste des sons adaptée au moteur actuel. Transport des médias partagé entre PC et Android.\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Android : verrouillage Gaia après veille et retour depuis la caméra du verrouillage. Mode lanceur HOME validé.\n- ![Change](https://img.shields.io/badge/Change-64748B?style=flat-square) Android : barres système masquées en mode immersif, accessibles par balayage. APK Preview16 construit et signé.\n- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) PC et émulateur Android 16/API 36 : fonds d’écran, sons et panneaux vérifiés ; langue, verrouillage et persistance du fond testés sur Android.\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Sargo : image de démarrage mise à jour pour ne plus restaurer d’anciens fichiers.\n- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Sargo en RAM : 6 contrôles d’interface et 10 contrôles système réussis. Installation conservée en Preview15, sans flash.\n- ![Note](https://img.shields.io/badge/Note-6B7280?style=flat-square) APK non publié. Installation FFAA et audio d’appel à valider ; le choix d’un son Gaia ne change pas la sonnerie Android.\n", + "en": "## 2.7-preview.16 — APK built · 5 October 2026\n\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Restored Gaia wallpaper and sound pickers, return to Settings and cancellation through Home.\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Apply language changes to Home and preserve them after restart.\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Restore horizontal page snapping; hide development apps on Home.\n- ![Change](https://img.shields.io/badge/Change-64748B?style=flat-square) Vulpes startup logo and sound list adapted to the current engine. Media transport shared by desktop and Android.\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Android: Gaia locking after sleep and Back from the lockscreen camera. HOME launcher mode validated.\n- ![Change](https://img.shields.io/badge/Change-64748B?style=flat-square) Android: hide system bars in immersive mode, with swipe access. Preview16 APK built and signed.\n- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Desktop and Android 16/API 36 emulator: checked wallpapers, sounds and panels; tested language, locking and wallpaper persistence on Android.\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Sargo: updated the boot image to stop restoring outdated files.\n- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Sargo in RAM: 6 UI and 10 system checks passed. Installed build remains Preview15, without flashing.\n- ![Note](https://img.shields.io/badge/Note-6B7280?style=flat-square) APK not published. FFAA installation and call audio await validation; Gaia sound selection does not change the Android ringtone.\n" + }, + "metadata": { + "kind": "version", + "status": "built", + "platforms": [ + "pc", + "apk", + "models" + ], + "models": [ + "sargo" + ] } }, { "id": "2026-10-04-sargo-standalone", "text": { - "fr": "## En développement — 4 octobre 2026 — Installation autonome Sargo\n\n### Tundra · Sargo\n\n- Construction d’un volume ext4 neuf directement destiné à userdata : système, données et composants matériels vendor/modem embarqués. Le démarrage ne cherche plus de fichiers dans une installation Droidian ; aucune session Android ni connexion ADB n’est nécessaire à l’installation.\n- Installateur Fastboot en Python : contrôle du modèle Sargo, du déverrouillage, des tailles et des empreintes, puis remplacement de userdata, dtbo_a, vbmeta_a et boot_a. Le bootloader reste déverrouillé ; les calibrations propres au téléphone sont conservées.\n- Sauvegarde complète de userdata hors ligne, comparaison intégrale avec le téléphone et vérification de l’archive de restauration avant le flash. Les sauvegardes restent privées ; une restauration réelle n’a pas été effectuée dans cette étape.\n- Deux démarrages depuis le stockage interne réussis : onze contrôles Gaia et dix contrôles natifs par démarrage, image de boot relue et vérifiée, réglages conservés. Le slot A est marqué réussi uniquement après validation du démarrage Gaia courant.\n- Variante générique sans serveur SSH ni clés de maintenance : flash de boot_a et démarrage réel réussis. La variante de développement est ensuite remise sur le Pixel pour poursuivre les travaux. Les images du paquet générique conservent leur état initial vierge.\n- Contrôles finaux sur le démarrage autonome de développement : Photo (dont bascules de capteur), clavier Gaia et notifications réussis.\n- Paquet Fastboot local assemblé avec empreintes et rapport de qualification. 44 tests ciblés passent. Il n’est pas encore publié : la provenance et la redistribution de certains composants matériels restent à finaliser.\n- Limites : qualification sur un Pixel 3a, pas sur toutes les combinaisons de firmware ; volume de 8 Gio comprenant 1 Gio de données modifiables, sans extension automatique. Les fonctionnalités matérielles restent expérimentales.\n\n### PC / Android · APK\n\n- Aucun changement d’interface ni nouveau binaire dans cette étape. Gaia reste partagé entre les trois plateformes.", - "en": "## In development — 4 October 2026 — Standalone Sargo installation\n\n### Tundra · Sargo\n\n- Build a fresh ext4 volume directly for userdata, including the system, writable data and vendor/modem hardware images. Startup no longer looks for files in a Droidian installation; installation requires neither an Android session nor ADB.\n- Python Fastboot installer checks the Sargo model, unlock state, partition sizes and checksums, then replaces userdata, dtbo_a, vbmeta_a and boot_a. The bootloader stays unlocked and device-specific calibration is retained.\n- Full offline userdata backup, complete comparison against the phone and recovery archive verification before flashing. Backups remain private; an actual restoration was not performed in this step.\n- Two successful boots from internal storage: eleven Gaia and ten native checks per boot, verified boot-image readback and retained settings. Slot A is marked successful only after validating Gaia startup in the current boot.\n- Generic variant without an SSH server or maintenance keys: actual boot_a flash and boot passed. The development variant was then restored on the Pixel for further work. Generic package images retain their original fresh state.\n- Final checks on the standalone development boot passed for Camera (including lens switches), the Gaia keyboard and notifications.\n- Local Fastboot package assembled with checksums and a qualification report. 44 targeted tests pass. It is not published yet: provenance and redistribution of some hardware components still need final review.\n- Limits: qualified on one Pixel 3a, not every firmware combination; an 8 GiB volume including 1 GiB of writable data, without automatic expansion. Hardware functionality remains experimental.\n\n### Desktop / Android · APK\n\n- No interface changes or new binary in this step. Gaia remains shared by all three platforms." + "fr": "## En développement — 4 octobre 2026 — Installation autonome Sargo\n\n- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Sargo : volume ext4 autonome pour userdata, avec système, données et composants vendor/modem. Installation indépendante de Droidian et d’ADB.\n- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Installateur Fastboot avec contrôles du modèle, du déverrouillage, des tailles et des empreintes. Remplacement de userdata, dtbo_a, vbmeta_a et boot_a ; calibrations conservées.\n- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Sauvegarde complète de userdata et archive de restauration vérifiées avant le flash. Restauration réelle non testée.\n- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Deux démarrages installés réussis : 11 contrôles Gaia et 10 contrôles natifs par démarrage, réglages conservés et boot relu conforme.\n- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Variante sans SSH ni clés de maintenance démarrée après flash. Variante de développement réinstallée ; caméra, clavier et notifications vérifiés.\n- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Paquet Fastboot local assemblé ; 44 tests ciblés réussis.\n- ![Note](https://img.shields.io/badge/Note-6B7280?style=flat-square) Paquet non publié : provenance et redistribution de composants matériels à finaliser. Essais sur un seul Pixel 3a ; volume de 8 Gio dont 1 Gio modifiable, sans extension automatique.\n", + "en": "## In development — 4 October 2026 — Standalone Sargo installation\n\n- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Sargo: standalone ext4 userdata volume with system, data and vendor/modem components. Installation independent of Droidian and ADB.\n- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Fastboot installer with model, unlock, size and checksum checks. Replaces userdata, dtbo_a, vbmeta_a and boot_a; retains calibration.\n- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Full userdata backup and recovery archive verified before flashing. Actual restoration not tested.\n- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Two installed boots passed: 11 Gaia and 10 native checks per boot, retained settings and matching boot readback.\n- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Variant without SSH or maintenance keys booted after flashing. Development variant reinstalled; camera, keyboard and notifications checked.\n- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Local Fastboot package assembled; 44 targeted tests passed.\n- ![Note](https://img.shields.io/badge/Note-6B7280?style=flat-square) Package unpublished: hardware component provenance and redistribution pending. Tested on one Pixel 3a; 8 GiB volume with 1 GiB writable, without automatic expansion.\n" + }, + "metadata": { + "kind": "development", + "status": "development", + "platforms": [ + "pc", + "apk", + "models" + ], + "models": [ + "sargo" + ] } }, { "id": "2026-10-03-sargo-per-device-installation", "text": { - "fr": "## En développement — 3 octobre 2026 — Installation Sargo par appareil\n\n### Tundra · Sargo\n\n- Collecte des six partitions de démarrage en lecture seule et du stockage existant, liés à l’identité du téléphone et au slot A. Les sauvegardes restent privées.\n- Dossier appareil portable : construction RAM, contrôle du boot, installation et export du secours peuvent l’utiliser sans l’inventaire partagé du Pixel de développement. Un build lié à un dossier appareil refuse le repli implicite vers cet inventaire.\n- Transfert SSH contrôlé : vérification de l’identité et du stockage, contrôle des empreintes et création des fichiers sans remplacer les données existantes. Aucun formatage, flash ou redémarrage dans l’outil de transfert.\n- Construction et transfert testés sur le Pixel. Premier boot RAM : onze contrôles Gaia et dix contrôles natifs réussis ; clavier et notifications réussis. Le contrôle caméra a rencontré un délai d’autofocus dépassé, conservé dans le rapport.\n- Second boot de la même image : réglage Gaia conservé, onze contrôles Gaia et dix contrôles natifs réussis ; caméra, clavier et notifications réussis. Le timeout autofocus du premier essai reste documenté, pas déclaré corrigé.\n- Retour au build installé vérifié : démarrage Gaia réussi et partition de boot inchangée. Un nouveau transfert refuse bien de remplacer les données déjà utilisées.\n- Provenance du HAL précisée : paquet et recette de téléchargement identifiés, recettes UBports historiques récupérées. La correspondance exacte avec l’ancien artefact Android et son manifeste de sources reste à établir.\n- Prise en charge toujours limitée au noyau qualifié et au stockage ext4/LVM compatible. Ce travail ne fournit pas encore une installation directe depuis Android d’origine.\n\n### PC / Android · APK\n\n- Aucun changement d’interface ni nouvelle compilation dans cette étape.", - "en": "## In development — 3 October 2026 — Per-device Sargo installation\n\n### Tundra · Sargo\n\n- Read-only collection of six boot partitions and existing storage, bound to the phone identity and slot A. Backups remain private.\n- Portable device workspace: RAM builds, boot checks, installation and recovery export can use it without the development Pixel’s shared inventory. Workspace-bound builds reject implicit fallback to that inventory.\n- Checked SSH transfers verify identity, storage and checksums and create files without replacing existing data. The transfer tool never formats, flashes or reboots.\n- Build and transfer tested on the Pixel. First RAM boot passed eleven Gaia and ten native checks; keyboard and notifications passed. Camera testing encountered an autofocus timeout, retained in the report.\n- Second boot of the same image retained the Gaia setting and passed eleven Gaia and ten native checks; camera, keyboard and notifications passed. The first autofocus timeout remains documented, not declared fixed.\n- Verify return to the installed build: Gaia starts and the boot partition is unchanged. A repeated transfer correctly refuses to replace existing data.\n- Refine HAL provenance: identify the installed package and download recipe and retrieve historical UBports build recipes. Exact correspondence with the old Android artifact and resolved source manifest remains unverified.\n- Still limited to the qualified kernel and compatible ext4/LVM storage. This does not yet provide direct installation from stock Android.\n\n### Desktop / Android · APK\n\n- No interface changes or new build in this step." + "fr": "## En développement — 3 octobre 2026 — Installation Sargo par appareil\n\n- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Sargo : dossier appareil portable pour les builds, l’installation et le secours, indépendant de l’inventaire du Pixel de développement.\n- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Collecte en lecture seule des six partitions de démarrage et du stockage, liée à l’identité du téléphone. Sauvegardes privées.\n- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Transfert SSH avec contrôle d’identité, du stockage et des empreintes, sans écrasement, formatage ni flash.\n- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Deux boots RAM : 11 contrôles Gaia et 10 contrôles natifs réussis chacun ; réglage conservé. Timeout autofocus au premier essai, caméra fonctionnelle au second.\n- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Retour au build installé vérifié ; boot inchangé et écrasement des données refusé.\n- ![Note](https://img.shields.io/badge/Note-6B7280?style=flat-square) Timeout autofocus non déclaré corrigé. Provenance exacte du HAL encore à établir ; noyau et stockage ext4/LVM compatibles requis, sans installation directe depuis Android d’origine.\n", + "en": "## In development — 3 October 2026 — Per-device Sargo installation\n\n- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Sargo: portable device workspace for builds, installation and recovery, independent of the development Pixel inventory.\n- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Read-only collection of six boot partitions and storage, bound to the phone identity. Private backups.\n- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) SSH transfer with identity, storage and checksum checks, without overwriting, formatting or flashing.\n- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Two RAM boots: 11 Gaia and 10 native checks passed each; setting retained. Autofocus timed out on the first trial; camera worked on the second.\n- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Return to the installed build verified; boot unchanged and data overwrite rejected.\n- ![Note](https://img.shields.io/badge/Note-6B7280?style=flat-square) Autofocus timeout not considered fixed. Exact HAL provenance still pending; qualified kernel and compatible ext4/LVM storage required, without direct installation from stock Android.\n" + }, + "metadata": { + "kind": "development", + "status": "development", + "platforms": [ + "pc", + "apk", + "models" + ], + "models": [ + "sargo" + ] } }, { "id": "2026-10-03-sargo-installation-tools", "text": { - "fr": "## En développement — 3 octobre 2026 — Préparation et secours Sargo\n\n### Tundra · Sargo\n\n- Ajout d’un outil de préparation locale : contrôle du manifeste et de l’archive, décompression bornée, vérification ext4 et création d’un espace de données vierge propre à l’installation. Les dossiers existants ne sont pas écrasés.\n- Ajout d’un kit de restauration privé exportable hors du dépôt. Il conserve la sauvegarde originale de boot_a et un outil autonome qui vérifie le modèle, le numéro de série, le slot et les empreintes avant toute restauration explicite. Il ne constitue pas une sauvegarde des données utilisateur.\n- La restauration existante ne dépend plus de la présence de l’image de diagnostic. Les contrôles de la sauvegarde sont conservés.\n- Construction système : référence matérielle ARM64 désormais explicite, avec refus d’une référence limitée à l’affichage ou dépourvue des services requis.\n- Préparation de l’archive système réelle et vérification locale du kit de secours effectuées. Aucun nouveau flash ni nouvelle ROM publiée ; l’installation automatique sur un autre Sargo reste à finaliser.\n\n### Commun · PC / Android / Tundra\n\n- Le journal JSON partagé est conservé dans les futures publications de sources ; les documents de travail Markdown restent exclus.\n\n### PC / Android · APK\n\n- Aucune modification fonctionnelle ni nouvelle compilation dans cette étape.", - "en": "## In development — 3 October 2026 — Sargo preparation and recovery tools\n\n### Tundra · Sargo\n\n- Add local package preparation: verify manifest and archive checksums, bound decompression, check ext4 and create a fresh data image for each installation. Existing directories are never overwritten.\n- Add a private recovery kit that can be exported outside the checkout. It retains the original boot_a backup and a standalone tool that checks model, serial number, slot and checksums before explicit restoration. It is not a user-data backup.\n- Existing restoration no longer requires the diagnostic image to remain available. Backup validation is retained.\n- System builds now require an explicit ARM64 hardware reference and reject display-only references or missing required services.\n- Prepare the real system archive and verify the recovery kit locally. No new flash or public ROM release; automated installation on another Sargo still needs completion.\n\n### Shared · Desktop / Android / Tundra\n\n- Retain the shared JSON changelog in future source publications; internal Markdown working documents remain excluded.\n\n### Desktop / Android · APK\n\n- No functional changes or new build in this step." + "fr": "## En développement — 3 octobre 2026 — Préparation et secours Sargo\n\n- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Sargo : préparation locale des paquets avec contrôles d’empreintes, décompression bornée, vérification ext4 et création de données vierges sans écrasement.\n- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Kit de secours privé avec sauvegarde de boot_a et contrôles du modèle, du numéro de série, du slot et des empreintes. Données utilisateur non incluses.\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Restauration possible sans l’image de diagnostic.\n- ![Change](https://img.shields.io/badge/Change-64748B?style=flat-square) Construction système : référence matérielle ARM64 complète exigée.\n- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Archive système préparée et kit de secours vérifié localement. Installation sur un autre Sargo à finaliser ; aucun flash ni ROM publiée.\n- ![Change](https://img.shields.io/badge/Change-64748B?style=flat-square) Journal JSON inclus dans les prochaines publications de sources ; documents de travail exclus.\n", + "en": "## In development — 3 October 2026 — Sargo preparation and recovery tools\n\n- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Sargo: local package preparation with checksum checks, bounded extraction, ext4 validation and fresh data creation without overwriting.\n- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Private recovery kit with boot_a backup and model, serial, slot and checksum checks. User data not included.\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Restoration works without the diagnostic image.\n- ![Change](https://img.shields.io/badge/Change-64748B?style=flat-square) System builds require a complete ARM64 hardware reference.\n- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) System archive prepared and recovery kit checked locally. Installation on another Sargo pending; no flash or ROM release.\n- ![Change](https://img.shields.io/badge/Change-64748B?style=flat-square) JSON changelog included in future source releases; working documents excluded.\n" + }, + "metadata": { + "kind": "development", + "status": "development", + "platforms": [ + "pc", + "apk", + "models" + ], + "models": [ + "sargo" + ] } }, { "id": "2026-10-03-sargo-modem-persistence", "text": { - "fr": "## En développement — 3 octobre 2026 — Sargo : récupération modem et installation vierge\n\n### Tundra · Sargo\n\n- Reconstruction de l’image système avec le correctif du cache des polices et la création du répertoire privé d’oFono au premier démarrage.\n- Récupération limitée au sous-système modem : test contrôlé avec passage hors ligne, remise en service et réenregistrement réseau en environ 22 secondes, sans redémarrage du téléphone. Cela ne corrige pas la cause du watchdog IMS du firmware.\n- Démarrage Gaia, rendu matériel, polices Fira, deux capteurs photo, focus/zoom, clavier et notifications visuelles contrôlés sur le profil neuf. Scan Wi-Fi encore fonctionnel après le redémarrage du modem.\n- Deux démarrages réussis avec conservation du même réglage Gaia sur un espace de données séparé. Caméra, clavier et notifications revérifiés au second démarrage. La partition de boot installée et les données existantes sont conservées.\n- Aucune nouvelle image native publiée : le paquet public d’installation/restauration reste à terminer.\n\n### PC / Android · APK\n\n- Aucun changement de l’interface commune ni nouvelle compilation APK dans cette étape.", - "en": "## In development — 3 October 2026 — Sargo modem recovery and fresh installation\n\n### Tundra · Sargo\n\n- Rebuild the system image with the font-cache fix and creation of oFono’s private state directory on first boot.\n- Limit recovery to the modem subsystem: a controlled restart went offline, recovered and registered on the network in about 22 seconds without rebooting the phone. This does not fix the firmware’s IMS watchdog root cause.\n- Check Gaia startup, hardware rendering, Fira fonts, both camera sensors, focus/zoom, keyboard and visual notifications with a fresh profile. Wi-Fi scanning still works after the modem restart.\n- Two successful boots retained the same Gaia setting in a separate data image. Camera, keyboard and notifications rechecked after the second boot. Installed boot partition and existing data retained.\n- No new native image published: the public installation/recovery package remains unfinished.\n\n### Desktop / Android · APK\n\n- No shared interface changes or new APK build in this step." + "fr": "## En développement — 3 octobre 2026 — Sargo : récupération modem et installation vierge\n\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Sargo : cache des polices et création du dossier privé oFono au premier démarrage corrigés dans l’image système.\n- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Récupération du modem sans redémarrer le téléphone. Réenregistrement réseau mesuré à environ 22 secondes lors d’un essai contrôlé.\n- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Profil neuf : Gaia, rendu matériel, Fira, deux caméras, focus, zoom, clavier et notifications vérifiés. Scan Wi-Fi fonctionnel après redémarrage du modem.\n- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Deux démarrages avec conservation des réglages dans un espace de données séparé. Installation existante préservée.\n- ![Note](https://img.shields.io/badge/Note-6B7280?style=flat-square) Cause du watchdog IMS non corrigée. Paquet public d’installation et de restauration encore incomplet ; aucune image publiée.\n", + "en": "## In development — 3 October 2026 — Sargo modem recovery and fresh installation\n\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Sargo: corrected font cache and first-boot creation of oFono’s private directory in the system image.\n- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Modem recovery without rebooting the phone. Network registration took about 22 seconds in a controlled trial.\n- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Fresh profile: checked Gaia, hardware rendering, Fira, both cameras, focus, zoom, keyboard and notifications. Wi-Fi scan worked after modem restart.\n- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Two boots retained settings in a separate data image. Existing installation preserved.\n- ![Note](https://img.shields.io/badge/Note-6B7280?style=flat-square) IMS watchdog root cause unresolved. Public installation and recovery package still incomplete; no image released.\n" + }, + "metadata": { + "kind": "development", + "status": "development", + "platforms": [ + "pc", + "apk", + "models" + ], + "models": [ + "sargo" + ] } }, { "id": "2026-10-01-sargo-distribution-preparation", "text": { - "fr": "## 1er octobre 2026 — Sargo : préparation de la distribution — en développement\n\n### Tundra · Sargo\n\n- Image système Preview 15 reconstruite avec profils vierges, exclusion des documents de travail et conservation des licences ; inventaire des 493 paquets embarqués.\n- Vérification ext4 et contrôle du contenu effectués. Ces contrôles ne constituent pas une validation de démarrage.\n- Inventaire du stockage en lecture seule et génération de la table de montage depuis cet inventaire, au lieu de reprendre systématiquement les dimensions du Pixel de développement. Prise en charge limitée au volume ext4 à une seule étendue sur userdata Sargo ; aucun formatage ajouté.\n- Image nettoyée transférée et vérifiée sur le Pixel chargé. Essais temporaires en RAM, sans écriture de boot_a ; retour automatique vers le build v59 et redémarrage de Gaia contrôlés.\n- SIM reconnue, enregistrement LTE et scan Wi-Fi contrôlés sur cette image ; ces contrôles ne valident pas les appels, SMS ou données mobiles de bout en bout.\n- Correction des droits du cache des polices au premier démarrage : fc-cache utilise le compte de session. Démarrage Gaia, rendu matériel, Fira, deux capteurs photo, clavier et notifications visuelles contrôlés en RAM. La qualification du fichier reconstruit reste incomplète.\n- Redémarrage intermittent identifié dans le firmware IMS du modem ; politique de récupération ciblée appliquée et second démarrage avec LTE, caméra, clavier et notifications réussi. La récupération après un nouveau crash IMS reste non testée. L’image publique reste non flashable et non publiée.\n\n### PC / APK\n\n- Aucun changement fonctionnel ni nouvelle compilation APK dans cette étape.", - "en": "## October 1, 2026 — Sargo distribution preparation — in development\n\n### Tundra · Sargo\n\n- Rebuild Preview 15 system image with empty profiles, internal working documents excluded, license notices retained and an inventory of 493 runtime packages.\n- Filesystem and content checks completed; these checks do not qualify hardware boot.\n- Read-only storage inventory and mount-table generation from that inventory instead of hardcoded development-phone dimensions. Limited to a single ext4 extent on Sargo userdata; no formatting added.\n- Clean image transferred and verified on the charged Pixel. Temporary RAM boot tests left boot_a unchanged; automatic return to installed v59 and Gaia startup checked.\n- SIM detection, LTE registration and Wi-Fi scanning checked on this image; these checks do not establish end-to-end calling, SMS or mobile data.\n- Fix first-boot font cache ownership by running fc-cache as the session user. Gaia startup, hardware rendering, Fira, both camera sensors, keyboard and visual notifications checked in RAM. Qualification of the rebuilt image remains incomplete.\n- Intermittent reboot traced to modem IMS firmware; targeted recovery policy applied and another boot with LTE, camera, keyboard and notifications passed. Recovery after a new IMS crash has not been exercised. The public image remains non-flashable and unpublished.\n\n### Desktop / APK\n\n- No functional changes or new APK build in this step." + "fr": "## 1er octobre 2026 — Sargo : préparation de la distribution — en développement\n\n- ![Change](https://img.shields.io/badge/Change-64748B?style=flat-square) Sargo : image Preview15 reconstruite avec profils vierges, licences et inventaire des 493 paquets. Documents de travail exclus.\n- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Table de montage générée depuis l’inventaire du stockage. Prise en charge limitée à une seule étendue ext4 sur userdata.\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Droits du cache des polices au premier démarrage : fc-cache exécuté avec le compte de session.\n- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Contrôles ext4, contenu et transfert réussis. Essais RAM sans écriture de boot_a ; retour au build v59 vérifié.\n- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) SIM, LTE, scan Wi-Fi, Gaia, rendu matériel, Fira, caméras, clavier et notifications vérifiés en RAM.\n- ![Change](https://img.shields.io/badge/Change-64748B?style=flat-square) Récupération ciblée après identification d’un redémarrage intermittent du firmware IMS. Second démarrage réussi ; nouveau crash IMS non testé.\n- ![Note](https://img.shields.io/badge/Note-6B7280?style=flat-square) Qualification de l’image incomplète, sans validation des appels, SMS ou données mobiles de bout en bout. Image non publiée et non flashable.\n", + "en": "## October 1, 2026 — Sargo distribution preparation — in development\n\n- ![Change](https://img.shields.io/badge/Change-64748B?style=flat-square) Sargo: rebuilt Preview15 image with empty profiles, licenses and an inventory of 493 packages. Working documents excluded.\n- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Mount table generated from the storage inventory. Support limited to a single ext4 extent on userdata.\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) First-boot font cache permissions: run fc-cache as the session user.\n- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Filesystem, content and transfer checks passed. RAM trials left boot_a unchanged; return to v59 verified.\n- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) SIM, LTE, Wi-Fi scan, Gaia, hardware rendering, Fira, cameras, keyboard and notifications checked in RAM.\n- ![Change](https://img.shields.io/badge/Change-64748B?style=flat-square) Targeted recovery after tracing intermittent reboots to IMS firmware. Second boot passed; recovery from another IMS crash not tested.\n- ![Note](https://img.shields.io/badge/Note-6B7280?style=flat-square) Image qualification incomplete; end-to-end calls, SMS and mobile data unvalidated. Image unpublished and non-flashable.\n" + }, + "metadata": { + "kind": "history", + "status": "history", + "platforms": [ + "pc", + "models" + ], + "models": [ + "sargo" + ] } }, { "id": "2.7-preview.15", "text": { - "fr": "## 2.7-preview.15 — 30 septembre 2026 — publiée\n\n[Release](https://nnsprod.com/git/oversu/VulpesOS/releases/tag/v2.7-preview.15)\n\n### Commun · Gaia / PC / Android / Tundra\n\n- Première publication des sources communes, avec README français et anglais, licences et scripts de préparation. Documentation interne, roadmap, profils, clés et images privées exclus du dépôt public.\n- Intégration des derniers correctifs SMS (dates des bulles, états d’envoi/échec) et du cycle de vie caméra. Les deux entrées du 30 septembre détaillent les correctifs matériels.\n\n### PC\n\n- Copie de publication testée avec moteur Gecko 156.0.1 et profil neuf : démarrage Gaia, navigation, réglages, gestes, historique et isolation des pages. 42 tests de services passants ; persistance des services après redémarrage vérifiée.\n- Stabilisation du test de navigation lors du remplacement normal d’un document et allongement de l’attente de démarrage à froid. Fermeture propre de l’hôte même si Gaia dépasse le délai de préparation.\n\n### Android · APK\n\n- Preview 15 signée avec la clé des précédentes Preview, GeckoView 156.0 ; parité des ressources communes vérifiée. Mise à jour installée sur émulateur Android 16.\n- Photo et Galerie : capture, affichage et reprise du capteur validés. Dix passages en arrière-plan et récupération après arrêt du processus d’affichage validés. Cette qualification ne couvre pas tous les téléphones Android.\n\n### Tundra · Sargo\n\n- Sources du runtime incluses ; l’image privée v59 reste celle qualifiée sur le téléphone de développement. Aucune ROM générique flashable publiée : démarrage encore lié au stockage inventorié, aux images préinstallées et aux accès de maintenance privés.\n- Limites conservées : aperçu caméra proche de 16 i/s, flash/vidéo, rotation et veille profonde incomplets ; audio d’appel à qualifier et grand dialogue après envoi SMS à identifier.\n", - "en": "## 2.7-preview.15 — 30 September 2026 — published\n\n[Release](https://nnsprod.com/git/oversu/VulpesOS/releases/tag/v2.7-preview.15)\n\n### Shared · Gaia / Desktop / Android / Tundra\n\n- First shared source publication, including English/French READMEs, licenses and setup scripts. Internal documents, roadmap, profiles, keys and private images excluded from the public repository.\n- Include the latest SMS fixes (bubble dates, sent/failed states) and camera lifecycle fixes. The two 30 September entries detail the hardware changes.\n\n### Desktop PC\n\n- Published checkout tested with Gecko 156.0.1 and a fresh profile: Gaia startup, navigation, settings, gestures, history and page isolation. 42 service tests pass; service persistence after restart checked.\n- Stabilize navigation checks during normal document replacement and allow more time for cold startup. Close the host cleanly even if Gaia exceeds its preparation timeout.\n\n### Android · APK\n\n- Preview 15 signed with the previous Preview key, GeckoView 156.0; shared resource parity checked. Installed as an update on an Android 16 emulator.\n- Photo/Gallery capture, image display and sensor resume validated. Ten background/resume cycles and recovery after renderer termination validated. This does not qualify every Android phone.\n\n### Tundra · Sargo\n\n- Runtime sources included; private v59 remains the build qualified on the development phone. No generic flashable ROM published: boot still depends on inventoried storage, preinstalled images and private maintenance access.\n- Limits remain: approximately 16 fps camera preview, incomplete flash/video, rotation and deep suspend; call audio awaiting qualification and a large dialog after SMS sending still under investigation.\n" + "fr": "## 2.7-preview.15 — 30 septembre 2026 — publiée\n\n- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Première publication des sources PC, Android et Tundra, avec README FR/EN, licences et scripts de préparation. Fichiers privés exclus.\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) SMS : dates des bulles et états d’envoi/échec corrigés. Libération et remplacement des flux caméra fiabilisés.\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) PC : tests de navigation stabilisés, délai de démarrage à froid ajusté et fermeture propre après dépassement du délai Gaia.\n- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) PC avec Gecko 156.0.1 : 42 tests de services, démarrage, navigation, réglages, gestes, historique, isolation et persistance vérifiés.\n- ![Change](https://img.shields.io/badge/Change-64748B?style=flat-square) APK Preview15 signé avec la clé précédente et GeckoView 156.0 ; parité des ressources vérifiée.\n- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Émulateur Android 16 : mise à jour, Photo/Galerie, reprise caméra, dix retours d’arrière-plan et récupération du rendu validés.\n- ![Note](https://img.shields.io/badge/Note-6B7280?style=flat-square) Sargo : sources incluses, build privé v59 conservé. Aucune ROM générique ; démarrage lié au stockage et aux images préinstallées.\n- ![Note](https://img.shields.io/badge/Note-6B7280?style=flat-square) Sargo : aperçu proche de 16 i/s ; flash, vidéo, rotation et veille profonde incomplets. Audio d’appel et dialogue après envoi SMS à examiner.\n\n[Release](https://nnsprod.com/git/oversu/VulpesOS/releases/tag/v2.7-preview.15)\n", + "en": "## 2.7-preview.15 — 30 September 2026 — published\n\n- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) First publication of desktop, Android and Tundra sources, with French/English READMEs, licenses and setup scripts. Private files excluded.\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) SMS: corrected bubble dates and sent/failed states. Improved camera stream cleanup and replacement.\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Desktop: stabilized navigation checks, adjusted cold-start timeout and clean shutdown after Gaia times out.\n- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Desktop with Gecko 156.0.1: 42 service tests, startup, navigation, settings, gestures, history, isolation and persistence checked.\n- ![Change](https://img.shields.io/badge/Change-64748B?style=flat-square) Preview15 APK signed with the previous key and GeckoView 156.0; asset parity verified.\n- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Android 16 emulator: validated update, Camera/Gallery, camera resume, ten background returns and renderer recovery.\n- ![Note](https://img.shields.io/badge/Note-6B7280?style=flat-square) Sargo: sources included, private v59 retained. No generic ROM; boot depends on storage layout and preinstalled images.\n- ![Note](https://img.shields.io/badge/Note-6B7280?style=flat-square) Sargo: preview around 16 fps; flash, video, rotation and deep suspend incomplete. Call audio and the dialog after SMS sending need investigation.\n\n[Release](https://nnsprod.com/git/oversu/VulpesOS/releases/tag/v2.7-preview.15)\n" + }, + "metadata": { + "kind": "version", + "status": "published", + "platforms": [ + "pc", + "apk", + "models" + ], + "models": [ + "sargo" + ] } }, { "id": "development-2026-09-30-sms-camera-followup", "text": { - "fr": "## En développement — retours SMS et caméra · 30 septembre 2026\n\n### Commun · Gaia / PC / Tundra\n\n- SMS : convertir les dates en nombres avant de construire les bulles. Corrige les conversations vides dues à une erreur de formatage de date. Parcours du compositeur et affichage des messages envoyés/reçus vérifiés sur PC avec un modem simulé.\n- Photo : fermeture du flux rendue idempotente; détacher les vidéos, attendre la libération du capteur et libérer le contexte WebGL avant de changer de caméra. Identifier chaque session pour qu’un ancien flux ne puisse pas arrêter son remplaçant. Allers-retours arrière/avant vérifiés avec de nouvelles trames à chaque bascule; retour Accueil et arrêt du capteur validés lors de deux démarrages RAM.\n\n### Tundra / Sargo\n\n- Vibreur : utiliser le contrôle brightness du pilote DRV2624; la commande legacy activate ne coupait pas physiquement le moteur. Coupure confirmée par l'utilisateur; impulsion, motif 200/200/200 ms et arrêt du service vérifiés, avec retour à zéro. Ajouter une coupure de secours au démarrage et à la sortie du service.\n- L'utilisateur confirme focus, zoom et transport SMS dans les deux sens sur v54. Cinq conversations existantes affichées sur le Pixel, avec leurs bulles et sans erreur de date.\n- v59 installé sur boot_a après deux démarrages RAM réussis; démarrage installé et relecture vérifiés, slot A marqué réussi. Sources et runtime PC synchronisés. Le grand dialogue après un envoi réel reste à identifier; la fluidité caméra reste un chantier distinct.\n\n### Android · APK\n\n- Sources Gaia communes corrigées; aucun nouvel APK construit ou publié.\n", - "en": "## In development — SMS and camera feedback · 30 September 2026\n\n### Shared · Gaia / Desktop / Tundra\n\n- Messages: serialize bubble dates as numbers. Fix empty conversations caused by date formatting errors. Composer flow and sent/received bubbles verified on desktop with a simulated modem.\n- Camera: make release idempotent; detach videos, await sensor release and dispose of the WebGL context before switching cameras. Identify each session so an old stream cannot stop its replacement. Repeated rear/front switches produce fresh frames; Home return and sensor shutdown passed on two RAM boots.\n\n### Tundra / Sargo\n\n- Vibration: use the DRV2624 brightness control; the legacy activate command did not physically stop the motor. The user confirmed stopping; single-pulse, 200/200/200 ms pattern and service-stop checks all returned to zero. Add a stop command when the service starts or exits.\n- The user confirms focus, zoom and SMS transport both ways on v54. Five existing conversations render on the Pixel, with message bubbles and no date errors.\n- v59 installed in boot_a after two successful RAM boots; installed startup and readback verified, slot A marked successful. Desktop sources and runtime synchronized. The large dialog after a real send still needs identification; camera smoothness remains separate work.\n\n### Android · APK\n\n- Shared Gaia sources updated; no APK rebuilt or published.\n" + "fr": "## En développement — retours SMS et caméra · 30 septembre 2026\n\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) SMS : conversion des dates pour corriger les conversations vides.\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Caméra : fermeture des flux et libération du capteur corrigées ; une ancienne session ne peut plus fermer la suivante.\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Sargo : arrêt physique du vibreur via DRV2624, avec coupure de secours au démarrage et à l’arrêt du service.\n- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) PC : composition et affichage des SMS vérifiés avec modem simulé. Sargo : bascules caméra et arrêt du capteur testés sur deux boots RAM.\n- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Sargo v54 : focus, zoom et SMS dans les deux sens confirmés par l’utilisateur. Cinq conversations affichées ; arrêt du vibreur confirmé.\n- ![Change](https://img.shields.io/badge/Change-64748B?style=flat-square) Sargo v59 installé sur boot_a après deux boots RAM ; démarrage, relecture et slot A vérifiés.\n- ![Note](https://img.shields.io/badge/Note-6B7280?style=flat-square) Dialogue après envoi SMS et fluidité caméra encore à traiter. Sources communes mises à jour, sans nouvel APK.\n", + "en": "## In development — SMS and camera feedback · 30 September 2026\n\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) SMS: convert dates to fix empty conversations.\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Camera: corrected stream and sensor cleanup; an old session can no longer close its replacement.\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Sargo: physical vibration shutdown through DRV2624, with a safeguard at service startup and exit.\n- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Desktop: SMS composition and display checked with a simulated modem. Sargo: camera switching and shutdown tested over two RAM boots.\n- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Sargo v54: user confirmed focus, zoom and two-way SMS. Five conversations displayed; vibration shutdown confirmed.\n- ![Change](https://img.shields.io/badge/Change-64748B?style=flat-square) Sargo v59 installed on boot_a after two RAM boots; startup, readback and slot A verified.\n- ![Note](https://img.shields.io/badge/Note-6B7280?style=flat-square) Post-SMS dialog and camera smoothness still pending. Shared sources updated, without a new APK.\n" + }, + "metadata": { + "kind": "development", + "status": "development", + "platforms": [ + "pc", + "apk", + "models" + ], + "models": [ + "sargo" + ] } }, { "id": "development-2026-09-30-sms-camera", "text": { - "fr": "## En développement — SMS et Photo · 30 septembre 2026\n\n### PC / Tundra\n\n- SMS : conserver le message et sa conversation après un refus du modem; transmettre les événements Gaia « envoi », « échec » et « envoyé ».\n- SMS : distinguer mise en file oFono et confirmation d'envoi, avec suivi persistant des états terminaux. La confirmation modem n'est pas un accusé de réception du destinataire.\n- PC : tests de l'échec d'envoi visible dans la liste des conversations; Photo et Galerie testées avec capture tactile, ouverture, fermeture et reprise de la caméra.\n\n### Tundra / Sargo\n\n- Photo : raccorder les zones de focus tactile, le zoom numérique et la capture JPEG du capteur à l'interface Gaia originale. Aperçu conservé en 640 × 480; JPEG arrière 4032 × 3024 (12,19 Mpx) et avant 3264 × 2448 (7,99 Mpx) mesurés directement. Focus fixe à l'avant.\n- Protocole caméra : séparer les traces du HAL des données binaires, borner les réponses et relancer l'aperçu après une capture. Une erreur de focus ne doit plus fermer la session.\n- SMS : refuser explicitement l'envoi si le modem n'est pas enregistré; conserver les états sent/failed même après disparition de l'objet oFono. La réception a produit des notifications opérateur sur v49 après retour du réseau LTE.\n- Qualification : deux démarrages v54 en RAM puis démarrage depuis boot_a validés; image installée sur le Pixel et slot marqué réussi après relecture. Persistance, interface Gaia et tests caméra passants. L'enregistrement réseau reste instable : essais SMS arrêtés avant émission, aucune livraison de bout en bout confirmée.\n\n### Android · APK\n\n- Aucun nouvel APK construit ou publié dans cette passe. La Preview 14 disponible reste inchangée.\n", - "en": "## In development — Messages and Camera · 30 September 2026\n\n### Desktop / Tundra\n\n- Messages now retain the message and conversation after a modem rejection and dispatch Gaia sending, failed and sent events.\n- Distinguish the oFono queue from confirmed transmission; persist terminal states. Modem confirmation is not a recipient delivery receipt.\n- Desktop checks cover failed messages visible in conversations, touch capture, Gallery opening and camera release/resume.\n\n### Tundra / Sargo\n\n- Connect touch focus areas, digital zoom and sensor JPEG capture to the original Gaia Camera. Preview stays at 640 × 480; directly measured JPEG sizes are 4032 × 3024 (12.19 MP) rear and 3264 × 2448 (7.99 MP) front. The front camera has fixed focus.\n- Keep HAL logs out of the binary protocol, bound replies and resume preview after capture. A focus failure must not close the session.\n- Reject SMS submission explicitly when unregistered; retain sent/failed states after the oFono object disappears. Carrier messages triggered Gaia notifications on v49 after LTE registration recovered.\n- Qualification: two v54 RAM boots and the installed boot_a startup passed; image installed on the Pixel and slot marked successful after readback. Persistence, Gaia startup and Camera checks passed. Network registration remains unstable: SMS tests stopped before transmission, with no end-to-end delivery confirmed.\n\n### Android · APK\n\n- No APK rebuilt or published in this pass. The available Preview 14 is unchanged.\n" + "fr": "## En développement — SMS et Photo · 30 septembre 2026\n\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) PC/Tundra : conservation des SMS après refus du modem et transmission des états envoi, échec et envoyé à Gaia.\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Sargo : états SMS persistants après disparition de l’objet oFono ; envoi refusé si le modem n’est pas enregistré.\n- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Sargo : focus tactile, zoom numérique et capture JPEG du capteur dans Photo Gaia.\n- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Photos mesurées : 4032 × 3024 à l’arrière, 3264 × 2448 à l’avant. Aperçu 640 × 480 ; focus avant fixe.\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Caméra native : données binaires séparées des traces HAL, réponses bornées et reprise de l’aperçu après capture ou erreur de focus.\n- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) PC : échec d’envoi visible et parcours Photo/Galerie vérifiés. Sargo v54 : deux boots RAM puis installation, persistance et caméra validées.\n- ![Note](https://img.shields.io/badge/Note-6B7280?style=flat-square) Réseau instable ; aucune livraison SMS de bout en bout confirmée dans cette passe. Confirmation modem distincte de l’accusé de réception. APK public inchangé (Preview14).\n", + "en": "## In development — Messages and Camera · 30 September 2026\n\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Desktop/Tundra: retain SMS after modem rejection and pass sending, failed and sent states to Gaia.\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Sargo: persist SMS states after the oFono object disappears; reject sending when the modem is unregistered.\n- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Sargo: touch focus, digital zoom and sensor JPEG capture in Gaia Camera.\n- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Measured photos: 4032 × 3024 rear, 3264 × 2448 front. Preview 640 × 480; fixed front focus.\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Native camera: separate binary data from HAL logs, bound responses and resume preview after capture or focus errors.\n- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Desktop: checked visible send failures and Camera/Gallery workflows. Sargo v54: validated two RAM boots, installation, persistence and camera.\n- ![Note](https://img.shields.io/badge/Note-6B7280?style=flat-square) Unstable network; no end-to-end SMS delivery confirmed in this pass. Modem confirmation is not a delivery receipt. Public APK unchanged (Preview14).\n" + }, + "metadata": { + "kind": "development", + "status": "development", + "platforms": [ + "pc", + "apk", + "models" + ], + "models": [ + "sargo" + ] } }, { "id": "2.7-preview.14", "text": { - "fr": "## 2.7-preview.14 — APK publié · 30 septembre 2026\n\n[Release](https://nnsprod.com/git/oversu/VulpesOS/releases/tag/android-2.7-preview.14)\n\nL’APK Preview 14 est publié. La version PC est à jour et le build privé Sargo v48 est installé et testé ; aucune image téléphone n’est publiée avec cette release.\n\n### Commun · PC / APK / Gaia\n\n- Informations : suppression du retrait supplémentaire des lignes de version avant « Dernière mise à jour » ; alignement contrôlé sur PC et Android.\n- Polices : normalisation des noms de fontes dans les feuilles des composants Shadow DOM, y compris les styles ajoutés après leur création. Les polices d’icônes restent distinctes.\n- Calendrier : ouverture du mois et absence d’erreur JavaScript vérifiées sur PC.\n\n### PC / Tundra\n\n- Clavier : calculer le mode portrait depuis l’écran, et non depuis la petite fenêtre du clavier. Touches de 60 px CSS environ sur Pixel ; rotation et retour au portrait testés sur PC à densité 3.\n- SMS : replacer le curseur dans le champ actif lorsque Gecko conserve la sélection du destinataire ; conserver les sélections valides pour l’édition.\n- Notifications Gaia : respecter les indicateurs silencieux, sans son et sans vibration, y compris à la restauration.\n\n- Clavier Gaia : pont de saisie natif, pavé numérique pour les destinataires SMS, événements de sélection corrigés et protection contre les réponses d’un champ devenu inactif. Saisie destinataire et message testée sur PC et Pixel ; l’envoi opérateur reste à qualifier.\n- Verrouillage : accepter l’absence de fenêtre clavier lors d’un redimensionnement.\n- Notifications natives : stockage, propriété par application et ouverture du point d’entrée Téléphone déclaré par Gaia. Présentation et suppression dans le volet et sur le verrouillage validées sur Sargo ; écoute du son à qualifier.\n\n### Tundra / Sargo — build v48 installé\n\n- Vibrations : pont vers le moteur Sargo, motifs bornés à dix secondes, séquencement sans bloquer les boutons physiques et annulation. Activation et arrêt mesurés sur le pilote.\n\n- Wi-Fi : profils privés conservés avec reconnexion automatique et commande d’oubli ; tests du stockage et de la restauration en cas d’échec. Connexion et reconnexion après redémarrage RAM vérifiées sur Sargo ; fichiers de profil privés (0600).\n- SMS entrants : stockage persistant avant acquittement, dédoublonnage des reprises et services créant leur dossier d’état au démarrage. Défaut de montage du dossier corrigé après reproduction sur Sargo.\n- Audio : commandes écouteur/haut-parleur, micro et volume d’appel ajoutées au pont ; bascule écouteur/haut-parleur, sourdine et volume vérifiés sans appel. Conversation dans les deux sens encore à vérifier.\n- Images de diagnostic avec débogueur local interdites au flash par l’outil d’installation. v48/preview.14 écrite sur boot_a après deux démarrages RAM et contrôle de persistance ; démarrage installé validé, image relue conforme et slot A marqué réussi. v43 à v48 n’activent pas de débogueur.\n\n### Android · APK\n\n- APK preview.14 construit, signé et publié sur Forgejo. Démarrage, informations de version, alignement et sélection des fontes des composants contrôlés sur Android 16 émulé. Le clavier Android reste celui du système.", - "en": "## 2.7-preview.14 — APK published · 30 September 2026\n\n[Release](https://nnsprod.com/git/oversu/VulpesOS/releases/tag/android-2.7-preview.14)\n\nThe Preview 14 APK is published. Desktop is up to date and the private Sargo v48 build is installed and tested; this release does not include a public phone image.\n\n### Shared · PC / APK / Gaia\n\n- About: remove the extra indentation in version rows above “Last updated”; alignment checked on desktop and Android.\n- Fonts: normalize text font names inside Shadow DOM stylesheets, including styles added later; keep icon fonts intact.\n- Calendar: month view opens without JavaScript errors in the desktop check.\n\n### PC / Tundra\n\n- Keyboard: use the screen orientation instead of the short keyboard frame. Keys measure about 60 CSS pixels on Pixel; rotation tested on desktop at DPR 3.\n- SMS: restore the caret to the focused field when Gecko retains the recipient selection; preserve valid selections during editing.\n- Gaia notifications: respect silent, no-sound and no-vibration flags, including restored notifications.\n\n- Gaia keyboard: native input bridge, numeric SMS recipient layout, corrected selection events and protection against stale field replies. Recipient and message typing tested on desktop and Pixel; carrier delivery still requires qualification.\n- Lockscreen: tolerate the absence of a keyboard window during resize.\n- Native notifications: persistent application-owned entries and launch through Gaia's declared Phone entry point. Tray and lockscreen display/removal verified on Sargo; audible sound still needs qualification.\n\n### Tundra / Sargo — v48 build installed\n\n- Vibration: Sargo actuator bridge, patterns limited to ten seconds, non-blocking sequencing and cancellation. Driver activation and stop measured.\n\n- Wi-Fi: private persistent profiles, autoconnect and forget operation; storage and failed-update rollback tests. Connection and reconnection after a RAM reboot verified on Sargo; private profile files (0600).\n- Incoming SMS: persist before acknowledgement and deduplicate retries. Services now create their state directory at startup, fixing a mount failure reproduced on Sargo.\n- Call audio: earpiece/speaker, microphone mute and call volume controls added; earpiece/speaker switching, mute and volume verified without placing a call. Two-way conversation still unverified.\n- Local-debugger images cannot be flashed by the installer. v48/preview.14 written to boot_a after two RAM boots and persistence checks; installed boot verified, image readback matches and slot A marked successful. v43–v48 do not enable a debugger.\n\n### Android · APK\n\n- Preview.14 APK built, signed and published on Forgejo. Startup, version information, alignment and component font selection checked on an Android 16 emulator. Android retains its system keyboard." + "fr": "## 2.7-preview.14 — APK publié · 30 septembre 2026\n\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Informations système : alignement des versions et polices Fira dans les composants Shadow DOM.\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) PC/Tundra : taille des touches en portrait, curseur dans le champ actif et réponses de saisie obsolètes.\n- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) PC/Tundra : pont clavier Gaia et pavé numérique pour les destinataires SMS. Clavier Android inchangé.\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Verrouillage : redimensionnement sans fenêtre clavier. Notifications silencieuses respectées, y compris après restauration.\n- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Sargo : notifications persistantes dans le volet et sur le verrouillage, avec ouverture de l’application concernée.\n- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Sargo : vibrations annulables, limitées à dix secondes ; profils Wi-Fi privés persistants, reconnexion automatique et oubli du réseau.\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Sargo : stockage des SMS entrants avant acquittement, dédoublonnage et création du dossier d’état.\n- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Sargo : commandes écouteur/haut-parleur, micro muet et volume d’appel. Bascule, sourdine et volume testés hors appel.\n- ![Change](https://img.shields.io/badge/Change-64748B?style=flat-square) Installation : flash des images de diagnostic avec débogueur refusé. Sargo v48 installé après deux boots RAM ; persistance, relecture et slot A vérifiés.\n- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) PC : Calendrier et clavier vérifiés. Sargo : clavier, notifications et reconnexion Wi-Fi vérifiés ; son des notifications et téléphonie réelle à valider.\n- ![Change](https://img.shields.io/badge/Change-64748B?style=flat-square) APK Preview14 signé et publié. Démarrage, versions et polices vérifiés sur émulateur Android 16. Aucune image Sargo publiée.\n\n[Release](https://nnsprod.com/git/oversu/VulpesOS/releases/tag/android-2.7-preview.14)\n", + "en": "## 2.7-preview.14 — APK published · 30 September 2026\n\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) System information: version alignment and Fira fonts in Shadow DOM components.\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Desktop/Tundra: portrait key sizing, cursor placement in the active field and stale input replies.\n- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Desktop/Tundra: Gaia keyboard bridge and numeric keypad for SMS recipients. Android keeps its system keyboard.\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Lockscreen: resize without a keyboard window. Honour silent notifications, including after restoration.\n- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Sargo: persistent tray and lockscreen notifications, opening the owning application.\n- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Sargo: cancellable vibrations capped at ten seconds; private persistent Wi-Fi profiles, automatic reconnection and network removal.\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Sargo: store incoming SMS before acknowledgement, remove duplicates and create the state directory.\n- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Sargo: earpiece/speaker, microphone mute and call-volume controls. Routing, mute and volume tested outside a call.\n- ![Change](https://img.shields.io/badge/Change-64748B?style=flat-square) Installer: reject flashing diagnostic images with a debugger. Sargo v48 installed after two RAM boots; persistence, readback and slot A verified.\n- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Desktop: Calendar and keyboard checked. Sargo: keyboard, notifications and Wi-Fi reconnection checked; notification sound and real telephony await validation.\n- ![Change](https://img.shields.io/badge/Change-64748B?style=flat-square) Preview14 APK signed and published. Startup, versions and fonts checked on an Android 16 emulator. No Sargo image released.\n\n[Release](https://nnsprod.com/git/oversu/VulpesOS/releases/tag/android-2.7-preview.14)\n" + }, + "metadata": { + "kind": "version", + "status": "published", + "platforms": [ + "pc", + "apk", + "models" + ], + "models": [ + "sargo" + ] } }, { "id": "2.7-preview.13", "text": { - "fr": "## 2.7-preview.13 — parcours dans les applications · 27 septembre 2026\n\n### Commun · Gaia / PC\n\n- Calendrier : une alarme planifiée avec succès ne provoque plus un rejet lors de la sauvegarde de l’événement; les erreurs de planification sont transmises au demandeur.\n- Rappels : ignorer ceux d’événements supprimés; ne plus marquer une notification refusée comme livrée. La prise en charge des notifications natives Tundra reste à terminer.\n- Notifications : accepter un texte brut vide sans chercher une traduction « undefined ».\n- Galerie : vignettes de taille positive même au démarrage masqué; annulation des mises à jour d’aperçu après fermeture de l’éditeur.\n- SMS : appliquer les filtres correspondant, lu/non lu, période et livraison; tests de lecture et suppression des conversations.\n- Revue approfondie : lecture, déplacement et suppression d’une vidéo WebM; édition noir et blanc et copie d’une photo; création et suppression d’un événement. Interfaces Gaia originales conservées.\n\n- Qualification depuis une copie isolée : démarrage, tests bureau, persistance et empaquetage Android réussis sans réécriture des sources.\n\n### Android · APK\n\n- APK preview.13 construit et signé; Photo/Galerie, Calendrier, dix retours d’arrière-plan et récupération du rendu validés sur Android 16 émulé.\n\n### Tundra · Sargo\n\n- Build v40/preview.13 installé après deux démarrages RAM : Gaia, caméra, Wi-Fi, luminosité et commandes contrôlés; relecture de boot_a conforme et slot validé. Limites restantes dans `docs/APPLICATION-REVIEW.md`.", - "en": "## 2.7-preview.13 — application workflows · 27 September 2026\n\n### Shared · Gaia / Desktop\n\n- Calendar: successfully scheduling a reminder no longer rejects event saving; scheduling failures reach the caller.\n- Reminders: ignore deleted events and do not mark denied notifications as delivered. Native Tundra notification support remains incomplete.\n- Notifications: accept empty raw text without requesting an “undefined” translation.\n- Gallery: positive thumbnail size even when starting hidden; cancel preview updates after leaving the editor.\n- SMS: apply correspondent, read state, date range and delivery filters; cover conversation reading and deletion.\n- Deeper review: WebM playback, seeking and deletion; black-and-white photo editing and saved copy; event creation and deletion. Retain original Gaia interfaces.\n\n- Isolated checkout qualification passes startup, desktop tests, persistence and Android packaging without rewriting sources.\n\n### Android · APK\n\n- Signed preview.13 APK built; Camera/Gallery, Calendar, ten background/resume cycles and renderer recovery validated on an Android 16 emulator.\n\n### Tundra · Sargo\n\n- Build v40/preview.13 installed after two RAM boots: Gaia, camera, Wi-Fi, brightness and controls checked; matching boot_a readback and successful slot marking. Remaining limits in `docs/APPLICATION-REVIEW.md`." + "fr": "## 2.7-preview.13 — parcours dans les applications · 27 septembre 2026\n\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Calendrier : sauvegarde des événements avec alarme, remontée des erreurs et exclusion des rappels d’événements supprimés.\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Notifications : texte vide accepté ; notification refusée non marquée comme livrée. Support natif Tundra encore incomplet.\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Galerie : taille des vignettes au démarrage masqué et arrêt des mises à jour après fermeture de l’éditeur.\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) SMS : filtres par correspondant, lecture, période et livraison appliqués.\n- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) PC : vidéo WebM, édition et copie de photo, calendrier, lecture et suppression des conversations vérifiés.\n- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Copie isolée : démarrage, tests PC, persistance et empaquetage Android réussis.\n- ![Change](https://img.shields.io/badge/Change-64748B?style=flat-square) APK Preview13 construit et signé. Photo/Galerie, Calendrier, dix reprises d’arrière-plan et récupération du rendu validés sur Android 16 émulé.\n- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Sargo v40 installé après deux boots RAM ; Gaia, caméra, Wi-Fi, luminosité, commandes et relecture de boot_a vérifiés.\n", + "en": "## 2.7-preview.13 — application workflows · 27 September 2026\n\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Calendar: save events with alarms, report scheduling errors and skip reminders for deleted events.\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Notifications: accept empty text; do not mark rejected notifications as delivered. Native Tundra support still incomplete.\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Gallery: thumbnail sizing at hidden startup and cancellation of updates after closing the editor.\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) SMS: apply correspondent, read-state, date and delivery filters.\n- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Desktop: checked WebM playback, photo editing and copying, calendar, conversation reading and deletion.\n- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Isolated checkout: startup, desktop tests, persistence and Android packaging passed.\n- ![Change](https://img.shields.io/badge/Change-64748B?style=flat-square) Preview13 APK built and signed. Camera/Gallery, Calendar, ten background returns and renderer recovery validated on emulated Android 16.\n- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Sargo v40 installed after two RAM boots; Gaia, camera, Wi-Fi, brightness, controls and boot_a readback verified.\n" + }, + "metadata": { + "kind": "version", + "status": "built", + "platforms": [ + "pc", + "apk", + "models" + ], + "models": [ + "sargo" + ] } }, { "id": "2.7-preview.12", "text": { - "fr": "## 2.7-preview.12 — revue des applications · 27 septembre 2026\n\n### Commun · Gaia / PC\n\n- Icônes internes : variantes raster Gaia originales de 1× à 2,25× rétablies dans\n 1 047 références CSS. Les icônes vectorielles et les polices restent originales.\n- Photo : bouton de changement de capteur réactivé après découverte des caméras;\n flash indisponible visible et grisé; le pincement ne zoome plus le document Gaia.\n- Accueil : gestes natifs réservés au bouton pendant l’appui long; menu contextuel\n désactivé sur ce bouton. Le gestionnaire des tâches Gaia reste utilisé.\n- Transmission des gestes entre System et les applications restaurée avec des\n événements DOM non privilégiés, sans autoriser de faux gestes sur les sites Web.\n- Navigateur : intégration de la page `browser-welcome` fournie, locale, bilingue,\n sans suivi; recherches et liens transmis au véritable navigateur.\n- Page d’accueil : suit automatiquement la langue Gaia, `fr`/`fr-*` en français,\n les autres en anglais; changement appliqué sans redémarrage.\n- SMS : identifiants compatibles avec le registre numérique Gaia, migration des\n anciens messages, dates de l’API et événement d’envoi corrigés; écritures sérialisées.\n- Calendrier : libellé d’accessibilité du jour courant retrouvé dans 37 langues.\n- Revue de 14 applications; 26 contrôles fonctionnels Galerie/Musique/Horloge/UI,\n tests Contacts et SMS, et navigation Web isolée. Voir `docs/APPLICATION-REVIEW.md`.\n\n### Android · APK\n\n- APK `2.7-preview.12` : même interface, correctifs et page locale; reconstruction\n et parité contrôlées. L’accès caméra reste celui d’Android via getUserMedia.\n- Aucun portage automatique des fonctions matérielles Tundra vers Android : chaque\n plateforme conserve son adaptateur et annonce ses capacités réelles.\n\n### Tundra · Sargo\n\n- Aperçu Photo : conversion NV21 par shader, transport binaire au lieu de base64;\n environ 23 images/s mesurées en RAM contre environ 8 dans l’essai intermédiaire.\n- Fermeture du worker attendue avant de rouvrir un autre capteur.\n- Luminosité manuelle : retrait des animations successives, environ 200 ms pour\n atteindre la valeur demandée lors du test matériel; transitions veille/réveil conservées.\n- Wi-Fi : état radio réel et activation/désactivation raccordés aux réglages Gaia;\n réseau mixte WPA2/WPA3 compatible avec le formulaire WPA-PSK.\n- Scan par Gaia, connexion et navigation HTTPS par wlan0 validés en RAM. Les\n profils de connexion restent temporaires; leur persistance est encore à réaliser.\n- Image v38 installée : deux démarrages RAM, démarrage installé, relecture de\n `boot_a`, préservation des autres partitions et validation du slot A réussis.\n- Flash photo, vidéo, capteur de luminosité/rotation, Bluetooth, comptage de données\n et fonctions avancées d’appel restent incomplets. Un écran qui s’ouvre ne vaut\n pas validation de tout son matériel.", - "en": "## 2.7-preview.12 — application review · 27 September 2026\n\n### Shared · Gaia / Desktop\n\n- Restore original Gaia 1×–2.25× raster variants for 1,047 CSS image references.\n- Camera switch becomes available after device discovery; unavailable flash stays\n visible and disabled; pinching no longer zooms the whole camera application.\n- Reserve Home gestures for long press and suppress its context menu; retain Gaia's task manager.\n- Relay Gaia touch gestures using unprivileged DOM events, without granting trusted\n user activation to external websites.\n- Bundle the supplied bilingual, local browser welcome page and route links and\n searches into the actual browser.\n- Welcome page follows the Gaia language live: `fr`/`fr-*` select French;\n all other languages select English.\n- SMS: numeric identifiers, migration of older records, Date values, sent events,\n multi-recipient request shape and serialized storage writes.\n- Calendar: restore the current day's accessibility label in 37 locales.\n- Review 14 apps; 26 media/clock/UI checks, contact and SMS checks, plus real Web\n navigation and isolation. Scope and limitations: `docs/APPLICATION-REVIEW.md`.\n\n### Android · APK\n\n- Rebuild `2.7-preview.12` with the same shared UI and local welcome page; check\n payload parity. Android camera capture continues to use getUserMedia.\n\n- Tundra hardware features are not automatically ported to Android: each platform\n retains its adapter and reports its actual capabilities.\n\n### Tundra · Sargo\n\n- Convert NV21 using a shader and transfer binary frames instead of base64:\n approximately 23 fps measured in RAM, versus 8 fps in the intermediate trial.\n- Wait for camera worker exit before opening the other sensor.\n- Manual brightness reaches its target in about 200 ms in the hardware test;\n keep screen sleep/wake fades.\n- Connect Gaia Wi-Fi power settings to actual radio state; support WPA-PSK on\n mixed WPA2/WPA3 networks.\n- Scan, connection and HTTPS over wlan0 validated in RAM; connection profiles are temporary.\n- Wi-Fi profiles are still temporary. Flash, video, light/orientation sensors,\n Bluetooth, data counters and advanced call controls remain incomplete.\n- v38 installed: two RAM boots, installed startup, `boot_a` readback, other boot\n partitions preserved and slot A marked successful." + "fr": "## 2.7-preview.12 — revue des applications · 27 septembre 2026\n\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Icônes Gaia : variantes de 1× à 2,25× restaurées dans 1 047 références CSS.\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Photo : changement de capteur après détection et désactivation du zoom de l’interface au pincement. Flash indisponible grisé.\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Accueil : appui long et transmission des gestes rétablis, sans privilèges supplémentaires pour les sites web.\n- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Navigateur : page d’accueil locale bilingue, suivant la langue Gaia sans redémarrage.\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) SMS : identifiants, migration, dates, événements d’envoi, requêtes multi-destinataires et écritures concurrentes corrigés.\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Calendrier : libellé d’accessibilité du jour courant rétabli dans 37 langues.\n- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Revue de 14 applications ; 26 contrôles Galerie/Musique/Horloge/UI, tests Contacts/SMS et isolation web.\n- ![Change](https://img.shields.io/badge/Change-64748B?style=flat-square) APK Preview12 reconstruit avec les mêmes ressources ; parité vérifiée. Fonctions matérielles adaptées séparément sur Android et Tundra.\n- ![Change](https://img.shields.io/badge/Change-64748B?style=flat-square) Sargo : aperçu caméra NV21 par shader et transport binaire, environ 23 i/s mesurées contre 8 dans l’essai intermédiaire.\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Sargo : fermeture du worker caméra attendue avant changement de capteur ; luminosité manuelle réglée en environ 200 ms.\n- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Sargo : activation Wi-Fi depuis Gaia et connexion WPA-PSK aux réseaux mixtes WPA2/WPA3.\n- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Sargo : scan, connexion et HTTPS validés en RAM. v38 installé après deux boots RAM, relecture et slot A vérifiés.\n- ![Note](https://img.shields.io/badge/Note-6B7280?style=flat-square) Sargo : profils Wi-Fi temporaires ; flash, vidéo, capteurs, Bluetooth, comptage de données et appels avancés incomplets.\n", + "en": "## 2.7-preview.12 — application review · 27 September 2026\n\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Gaia icons: restored 1× to 2.25× variants across 1,047 CSS references.\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Camera: enable switching after sensor detection and prevent pinch zoom on the UI. Unavailable flash disabled.\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Home: restore long press and gesture forwarding without additional privileges for websites.\n- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Browser: local bilingual welcome page following Gaia’s language without restarting.\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) SMS: corrected identifiers, migration, dates, sending events, multi-recipient requests and concurrent writes.\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Calendar: restored the current-day accessibility label in 37 languages.\n- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Reviewed 14 apps; 26 Gallery/Music/Clock/UI checks, Contacts/SMS tests and web isolation checks.\n- ![Change](https://img.shields.io/badge/Change-64748B?style=flat-square) Preview12 APK rebuilt with shared assets; parity verified. Hardware features handled separately on Android and Tundra.\n- ![Change](https://img.shields.io/badge/Change-64748B?style=flat-square) Sargo: shader-based NV21 preview and binary transport, about 23 fps measured versus 8 in the intermediate trial.\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Sargo: wait for camera worker shutdown before switching sensors; manual brightness reaches its target in about 200 ms.\n- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Sargo: Wi-Fi power control from Gaia and WPA-PSK connections to mixed WPA2/WPA3 networks.\n- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Sargo: scan, connection and HTTPS validated in RAM. v38 installed after two RAM boots, with readback and slot A checks.\n- ![Note](https://img.shields.io/badge/Note-6B7280?style=flat-square) Sargo: temporary Wi-Fi profiles; flash, video, sensors, Bluetooth, data counters and advanced call controls incomplete.\n" + }, + "metadata": { + "kind": "version", + "status": "built", + "platforms": [ + "pc", + "apk", + "models" + ], + "models": [ + "sargo" + ] } }, { "id": "development-2026-09-27-4", "text": { - "fr": "## En développement — retours Sargo et interface · 27 septembre 2026\n\n### Commun · Gaia / PC\n\n- Animation d’arrêt : trois nuances bleues remplacent les ronds orange; dimensions,\n séquence et durées de l’animation Gaia sont conservées.\n\n- Téléphone : empêcher le défilement du document lors du changement d’onglet;\n les en-têtes Journal et Contacts restent en place. Les icônes du journal utilisent\n les images Gaia originales en plusieurs densités, jusqu’à 2,25×.\n- Menus déroulants : retrait du décalage prévu pour les anciens moteurs, qui coupait\n les jambages de Fira. Vidéo : texte de présentation contenu dans la fenêtre.\n- Luminosité : le curseur enregistre un nombre et répond aux événements input/change;\n les anciennes valeurs textuelles sont converties avant la transition Gaia. La file\n matérielle regroupe les pas obsolètes de luminosité sans réordonner veille/réveil.\n- Accueil : le fond d’écran continue sous le dégradé transparent. L’espace de navigation\n reste réservé dans les applications; en paysage, le bouton se place à droite.\n- Navigateur : page locale éditable dans `overrides/search.gaiamobile.org/home/index.html`.\n Les branchements de Gaia et le mode privé restent en place.\n- Wi-Fi : un échec affiche une explication et le bouton de reprise au lieu de relancer\n indéfiniment le scanner. La recherche et l’affichage dans le panneau Gaia ont\n maintenant été vérifiés sur Sargo en RAM.\n- Suivi consolidé : `docs/CHANTIER.md`, avec anciens et nouveaux retours, preuves et limites.\n\n### Android · APK\n\n- APK `2.7-preview.11` construit et signé avec la même base Gaia; parité vérifiée.\n Barre Accueil à droite en paysage et correction du fond en mode Accueil.\n Rotation et absence de recouvrement vérifiées sur émulateur Android 16.\n\n### Tundra · Sargo\n\n- Caméra : aperçu matériel dans Photo Gaia, JPEG et bascule arrière/avant validés\n en RAM sur Sargo; les capteurs se ferment au retour à l’accueil. Capture initiale\n limitée à 640 × 480 (480 × 640 en portrait); vidéo et flash non disponibles.\n L’interface reste commune au PC et à l’APK, qui conservent getUserMedia.\n\n- v33 (`2.7-preview.11`) installé après deux démarrages RAM réussis. Démarrage installé, relecture de\n `boot_a`, conservation des données et finalisation du slot A vérifiés.\n- Luminosité mesurée depuis Gaia : 35 % → 89/255, 80 % → 204/255. Résultats Wi-Fi\n visibles dans le panneau original. Animation d’arrêt bleue incluse.\n- Routage audio : prise en charge du format de PulseAudio 14; suppression du double\n chargement des modules audio. Passage au profil téléphonique et à l’écouteur, puis\n restauration du profil média, vérifiés sur Sargo. Conversation réelle encore à valider.\n- Restent ouverts : conversation audible dans les deux sens, capture pleine résolution/vidéo et\n raccordement des capteurs pour la rotation automatique. Voir la liste de suivi.", - "en": "## In development — Sargo feedback and interface · 27 September 2026\n\n### Shared · Gaia / Desktop\n\n- Shutdown animation: three blue shades replace the orange rings; original Gaia\n dimensions, sequence and timing are preserved.\n\n- Phone: prevent document scrolling on tab changes, keeping Recents and Contacts\n headers in place. Use Gaia’s original call-log artwork at multiple densities up to 2.25×.\n- Select controls: remove the old engine offset that clipped Fira descenders.\n Video: keep the introductory text within the viewport.\n- Brightness: save a number from input/change events; normalize legacy text values\n before Gaia animates the transition. Coalesce stale hardware brightness steps\n while preserving the order of sleep/wake commands.\n- Home: extend wallpaper beneath the transparent gradient, reserve navigation space\n inside applications, and place Home on the right in landscape.\n- Browser: editable local page at `overrides/search.gaiamobile.org/home/index.html`,\n retaining Gaia integration and private mode.\n- Wi-Fi: show a scan failure and retry action instead of endless retries. Network\n discovery and results in the original Gaia panel now verified on Sargo in RAM.\n- Consolidated tracking: `docs/CHANTIER.md` includes earlier and new feedback and limitations.\n\n### Android · APK\n\n- Build and sign `2.7-preview.11` with shared Gaia; payload parity checked.\n Place Home on the right in landscape and fix its wallpaper background.\n Check rotation and non-overlapping app/navigation bounds on Android 16 emulator.\n\n### Tundra · Sargo\n\n- Camera: hardware preview in Gaia, JPEG capture and rear/front switching verified\n on Sargo in RAM; sensors close on returning Home. Capture is currently limited\n to 640 × 480 (480 × 640 portrait); video and flash remain unavailable.\n Desktop and APK keep getUserMedia and share the same Gaia interface.\n\n- v33 (`2.7-preview.11`) installed after two successful RAM boots. Installed boot, `boot_a` readback,\n data preservation and slot A success finalization verified.\n- Backlight measured from Gaia: 35% → 89/255, 80% → 204/255. Wi-Fi results visible\n in the original panel. Blue shutdown rings included.\n- Call audio: support PulseAudio 14 text output and prevent loading audio modules twice.\n Voice-call profile, earpiece selection and media-profile restoration verified on Sargo.\n An actual two-way conversation remains unvalidated.\n- Still open: audible two-way calls, full-resolution camera capture/video and sensor-driven rotation.\n See the tracking document." + "fr": "## En développement — retours Sargo et interface · 27 septembre 2026\n\n- ![Change](https://img.shields.io/badge/Change-64748B?style=flat-square) Animation d’arrêt Gaia recolorée en bleu.\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Téléphone : en-têtes fixes au changement d’onglet et icônes du journal adaptées aux densités jusqu’à 2,25×.\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Texte coupé dans les menus déroulants et débordement du texte d’accueil Vidéo.\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Luminosité : valeurs numériques et regroupement des commandes obsolètes, avec ordre veille/réveil préservé.\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Accueil : fond sous le dégradé et bouton à droite en paysage, sans recouvrir les applications.\n- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Navigateur : page d’accueil locale modifiable, avec mode privé conservé.\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Wi-Fi : message d’erreur et bouton Réessayer à la place des relances sans fin.\n- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) APK Preview11 construit et signé ; rotation et position du bouton Accueil vérifiées sur Android 16 émulé.\n- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Sargo : aperçu, JPEG et bascules caméra validés en RAM. Capture limitée à 640 × 480 ; flash et vidéo indisponibles.\n- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Sargo v33 installé après deux boots RAM ; démarrage, données, relecture et slot A vérifiés. Luminosité et résultats Wi-Fi contrôlés dans Gaia.\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Sargo : compatibilité PulseAudio 14 et suppression du double chargement des modules audio. Profils appel/média et écouteur testés.\n- ![Note](https://img.shields.io/badge/Note-6B7280?style=flat-square) Sargo : conversation audio, capture pleine résolution, vidéo et rotation automatique à valider.\n", + "en": "## In development — Sargo feedback and interface · 27 September 2026\n\n- ![Change](https://img.shields.io/badge/Change-64748B?style=flat-square) Gaia shutdown animation recoloured blue.\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Phone: keep headers fixed when changing tabs and use call-log icons up to 2.25× density.\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Clipped dropdown text and overflowing Video welcome text.\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Brightness: numeric values and coalesced stale commands, preserving sleep/wake order.\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Home: wallpaper under the gradient and right-side button in landscape, without covering apps.\n- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Browser: editable local welcome page, retaining private mode.\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Wi-Fi: error message and Retry button instead of endless retries.\n- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Preview11 APK built and signed; rotation and Home button placement checked on emulated Android 16.\n- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Sargo: preview, JPEG and camera switching validated in RAM. Capture limited to 640 × 480; flash and video unavailable.\n- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Sargo v33 installed after two RAM boots; startup, data, readback and slot A verified. Brightness and Wi-Fi results checked in Gaia.\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Sargo: PulseAudio 14 compatibility and duplicate audio module loading. Call/media profiles and earpiece tested.\n- ![Note](https://img.shields.io/badge/Note-6B7280?style=flat-square) Sargo: call audio, full-resolution capture, video and automatic rotation await validation.\n" + }, + "metadata": { + "kind": "development", + "status": "development", + "platforms": [ + "pc", + "apk", + "models" + ], + "models": [ + "sargo" + ] } }, { "id": "development-2026-09-27-3", "text": { - "fr": "## En développement — stabilité Wi-Fi native · 27 septembre 2026\n\n### Commun · Gaia / PC\n\n- Correction de la boucle Wi-Fi sans délai : recherche toutes les 20 secondes,\n conformément à l’intervalle prévu par Gaia. Regroupement des scans simultanés.\n- Version de développement commune : `2.7-preview.9`, Gaia `2.7.0`; versions\n réelles de Gecko inchangées. Tests de rafales, erreurs et temporisation ajoutés.\n\n### Android · APK\n\n- Nouveau paquet `2.7-preview.9` pour conserver la même base Gaia. Le Wi-Fi Android\n reste géré par Android; ce correctif ne prétend pas ajouter un pilote à l’APK.\n\n### Tundra · Sargo\n\n- Cause du blocage v21/v22 identifiée : multiplication des clients Python de\n recherche Wi-Fi, épuisement mémoire et arrêt de Gecko par le lowmemorykiller.\n Le SIGSEGV de phoc survient ensuite pendant sa fermeture.\n- Un seul scan matériel en cours, cache court des résultats et des erreurs,\n invalidation après connexion/déconnexion. Aucun affaiblissement de l’isolation\n des applications, ni désactivation du mécanisme de protection mémoire.\n- Candidat v25 qualifié sur deux démarrages RAM, puis installé et vérifié sur le\n Pixel : 11 contrôles Gaia et 10 contrôles système passent, sans destruction de\n processus par manque de mémoire. Verrouillage Gaia, Fira, batterie, date et\n panneau Wi-Fi contrôlés; veille/réveil et volume testés par événements injectés.\n- Relecture de `boot_a` conforme, autres partitions de démarrage inchangées,\n données conservées et slot A marqué comme démarré avec succès. La caméra native\n et la validation complète des appels restent à terminer.", - "en": "## In development — native Wi-Fi stability · 27 September 2026\n\n### Shared · Gaia / Desktop\n\n- Fix the Wi-Fi loop with no delay: scan every 20 seconds as intended by Gaia.\n Coalesce concurrent scans. Add burst, failure and timer regression tests.\n- Shared development version: `2.7-preview.9`, Gaia `2.7.0`; actual Gecko versions\n remain unchanged.\n\n### Android · APK\n\n- Build `2.7-preview.9` to retain shared Gaia parity. Android still manages its\n own Wi-Fi; this change does not claim to add a hardware driver to the APK.\n\n### Tundra · Sargo\n\n- Identify the v21/v22 failure: unbounded Python Wi-Fi scan clients exhausted\n memory, causing the lowmemorykiller to terminate Gecko. The phoc SIGSEGV occurs\n afterwards during display teardown.\n- Allow one hardware scan at a time, briefly cache results and errors, invalidate\n after connect/disconnect. Application isolation and memory protection remain enabled.\n- Qualify v25 through two RAM boots, then install and verify it on the Pixel:\n 11 Gaia and 10 system checks pass, with no memory-pressure process kills.\n Check the Gaia lockscreen, Fira, battery, date and Wi-Fi panel; exercise\n sleep/wake and volume through injected input events.\n- Verify `boot_a` by readback, preserve the other boot partitions and user data,\n and mark slot A successful. Native camera support and full call validation\n remain unfinished." + "fr": "## En développement — stabilité Wi-Fi native · 27 septembre 2026\n\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Wi-Fi : intervalle de recherche rétabli à 20 secondes et regroupement des scans simultanés.\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Sargo : un seul scan matériel à la fois, avec cache court et invalidation à la connexion/déconnexion. Corrige l’épuisement mémoire des builds v21/v22.\n- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Tests de rafales, erreurs et temporisation Wi-Fi.\n- ![Change](https://img.shields.io/badge/Change-64748B?style=flat-square) Version commune Preview9, Gaia 2.7.0 ; nouvel APK avec les mêmes sources. Wi-Fi Android toujours géré par Android.\n- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Sargo v25 : deux boots RAM puis installation, 11 contrôles Gaia et 10 système réussis, sans arrêt de processus par manque de mémoire.\n- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Verrouillage, polices, batterie, date et Wi-Fi vérifiés ; veille/réveil et volume testés par événements injectés. Données, relecture et slot A contrôlés.\n- ![Note](https://img.shields.io/badge/Note-6B7280?style=flat-square) Caméra native et validation complète des appels encore à terminer.\n", + "en": "## In development — native Wi-Fi stability · 27 September 2026\n\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Wi-Fi: restore the 20-second scan interval and coalesce concurrent scans.\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Sargo: one hardware scan at a time, with a short cache cleared on connect/disconnect. Fixes memory exhaustion in v21/v22.\n- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Wi-Fi burst, failure and timer tests.\n- ![Change](https://img.shields.io/badge/Change-64748B?style=flat-square) Shared Preview9 version, Gaia 2.7.0; new APK with the same sources. Android still manages its own Wi-Fi.\n- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Sargo v25: two RAM boots then installation, 11 Gaia and 10 system checks passed, without low-memory process kills.\n- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Checked locking, fonts, battery, date and Wi-Fi; tested sleep/wake and volume with injected events. Data, readback and slot A verified.\n- ![Note](https://img.shields.io/badge/Note-6B7280?style=flat-square) Native camera and full call validation still pending.\n" + }, + "metadata": { + "kind": "development", + "status": "development", + "platforms": [ + "pc", + "apk", + "models" + ], + "models": [ + "sargo" + ] } }, { "id": "development-2026-09-27-2", "text": { - "fr": "## En développement — restauration Gaia · 27 septembre 2026\n\n### Commun · Gaia / PC\n\n- Restauration des panneaux Gaia et suppression des fenêtres/boutons provisoires :\n Paramètres, Suivi conso, FM et import de contacts/médias. Les fonctions sans API\n restent visibles avec commandes désactivées, sans valeurs matérielles inventées.\n- Correction de la portée des anciens styles : les sélecteurs `div` ne sont plus\n transformés en racines de composants. Retour des icônes du verrouillage,\n conservation de Fira et glyphes Gaia vectoriels pour les onglets Téléphone.\n- Accueil dispose de 44 pixels CSS réservés et ne recouvre plus Décrocher/Raccrocher.\n La date utilise le composant historique; la batterie est actualisée dans Paramètres\n et ses libellés n’affichent plus `{{ level }}`.\n- Écran de verrouillage Gaia réactivé; splash provisoire avec le logo fourni,\n dimensions stables et animation douce. Nom localisé : Navigateur / Browser.\n Remerciements dans Informations légales, avis de licences conservés.\n- Tests PC : 26 contrôles applicatifs passent (Galerie, Musique, Horloge, tâches),\n panneaux, imports, batterie, date et navigation; appels simulés,\n journal, veille/réveil, menu Power et volume. Voir `docs/GAIA-RESTORATION.md`.\n\n### Android · APK\n\n- APK `2.7-preview.8` construit et installé sur l’émulateur Android 16, avec la même base Gaia; batterie lue depuis Android,\n Paramètres Android en bas de liste et mode Accueil Android optionnel. Le catalogue\n des applications Android dans Gaia reste à raccorder.\n- Validation Android : dix reprises après changement d’application et récupération\n après destruction du renderer. Parité du contenu partagé vérifiée; pas de test\n physique Android dans cette passe.\n- Les imports utilisent les composants Gaia; le bouton SMS ouvre la confirmation\n Android. Le verrouillage interne ne remplace pas la sécurité Android.\n\n### Tundra · Sargo\n\n- Candidats v21/v22 construits avec tous les correctifs `overrides/` dans le paquet de boot;\n raccordement scan/association Wi-Fi au panneau Gaia via NetworkManager.\n- Essais RAM bloqués : service graphique arrêté avec le code 139 avant qualification.\n Aucun nouveau flash. Retour sur v20 installé validé (11 contrôles Gaia, 9 système,\n données conservées); verrouillage du profil natif remis à son état précédent.\n Wi-Fi du candidat, caméra native, API SIM/Bluetooth et suspension CPU non qualifiés.", - "en": "## In development — Gaia restoration · 27 September 2026\n\n### Shared · Gaia / Desktop\n\n- Restore Gaia panels and remove temporary windows/floating controls: Settings,\n Usage, FM and contact/media import. Features without an API remain visible with\n disabled controls, without invented hardware values.\n- Fix legacy style scoping: ordinary `div` selectors no longer become component\n roots. Restore lockscreen icons, retain Fira and use scalable Gaia glyphs for\n Phone tabs.\n- Reserve 44 CSS pixels for Home so it cannot cover Answer/Hang up. The date uses\n its original component; battery updates reach Settings and its labels no longer\n display `{{ level }}`.\n- Re-enable the original Gaia lockscreen; temporary splash uses the supplied logo,\n stable dimensions and a gentle animation. Localized name: Navigateur / Browser.\n Credits appear in Legal Information; original license notices remain.\n- All 26 application checks pass (Gallery, Music, Clock, task management). Further\n desktop checks cover panels, imports, battery, date and navigation; simulated\n calls, history, sleep/wake, Power menu and volume. See `docs/GAIA-RESTORATION.md`.\n\n### Android · APK\n\n- Build and install `2.7-preview.8` on the Android 16 emulator with shared Gaia; read battery state from Android,\n add Android Settings at the bottom and optional Android Home mode. The Android\n application catalogue in Gaia still needs integration.\n- Android checks: ten background/resume cycles and renderer termination recovery;\n shared payload parity verified. No physical Android device test in this pass.\n- Imports use Gaia components; the SMS button opens Android confirmation.\n The internal lockscreen does not replace Android security.\n\n### Tundra · Sargo\n\n- Build v21/v22 candidates with all `overrides/` fixes in the boot payload; connect Wi-Fi\n scan/association to the Gaia panel through NetworkManager.\n- RAM trials blocked: the display service exits with code 139 before qualification.\n No new flash. Restore installed v20 and verify 11 Gaia and 9 system checks with\n preserved data; restore the native profile’s previous unlocked state. Candidate\n Wi-Fi, native camera, full SIM/Bluetooth APIs and CPU suspend remain unqualified." + "fr": "## En développement — restauration Gaia · 27 septembre 2026\n\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Panneaux Gaia restaurés : Paramètres, Suivi conso, FM et imports. Interfaces provisoires retirées ; fonctions indisponibles désactivées.\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Styles des anciens composants, icônes du verrouillage et glyphes Téléphone corrigés.\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Espace de 44 pixels réservé au bouton Accueil ; date et batterie rétablies, sans texte `{{ level }}`.\n- ![Change](https://img.shields.io/badge/Change-64748B?style=flat-square) Verrouillage Gaia réactivé, splash stabilisé, nom Navigateur/Browser localisé et crédits ajoutés.\n- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) PC : 26 contrôles applicatifs réussis, plus panneaux, imports, batterie, navigation et appels simulés.\n- ![Change](https://img.shields.io/badge/Change-64748B?style=flat-square) APK Preview8 installé sur Android 16 émulé : batterie Android, accès aux paramètres et mode HOME optionnel. Catalogue Android encore incomplet.\n- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Android émulé : dix reprises d’arrière-plan, récupération du rendu et parité des ressources vérifiées. Verrouillage Gaia distinct de la sécurité Android.\n- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Sargo v21/v22 : scan et association Wi-Fi raccordés à Gaia via NetworkManager.\n- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Sargo : essais RAM interrompus par l’arrêt du service graphique (code 139). Retour au v20 installé vérifié, sans nouveau flash.\n- ![Note](https://img.shields.io/badge/Note-6B7280?style=flat-square) Wi-Fi du candidat, caméra, API SIM/Bluetooth et suspension CPU non validés.\n", + "en": "## In development — Gaia restoration · 27 September 2026\n\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Restored Gaia Settings, Usage, FM and import panels. Temporary interfaces removed; unavailable features disabled.\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Corrected legacy component styles, lockscreen icons and Phone glyphs.\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Reserve 44 pixels for Home; restore date and battery without `{{ level }}` placeholders.\n- ![Change](https://img.shields.io/badge/Change-64748B?style=flat-square) Gaia lockscreen re-enabled, splash stabilized, Browser name localized and credits added.\n- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Desktop: 26 app checks passed, plus panels, imports, battery, navigation and simulated calls.\n- ![Change](https://img.shields.io/badge/Change-64748B?style=flat-square) Preview8 APK installed on emulated Android 16: Android battery, settings access and optional HOME mode. Android app catalogue still incomplete.\n- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Emulated Android: ten background returns, renderer recovery and asset parity checked. Gaia locking remains separate from Android security.\n- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Sargo v21/v22: Wi-Fi scan and association connected to Gaia through NetworkManager.\n- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Sargo: RAM trials interrupted by display-service failure (code 139). Return to installed v20 verified, without a new flash.\n- ![Note](https://img.shields.io/badge/Note-6B7280?style=flat-square) Candidate Wi-Fi, camera, SIM/Bluetooth APIs and CPU suspend unvalidated.\n" + }, + "metadata": { + "kind": "development", + "status": "development", + "platforms": [ + "pc", + "apk", + "models" + ], + "models": [ + "sargo" + ] } }, { "id": "development-2026-09-27-1", "text": { - "fr": "## En développement — 27 septembre 2026\n\n### Commun · Gaia / PC\n\n- Remplacement de l’écran d’appel provisoire par **Callscreen**, l’interface\n originale de Gaia : appel entrant/sortant, réponse, raccrochage et fermeture.\n L’adaptateur fournit les événements `mozTelephony` aux modules historiques.\n Les contrôles audio/conférence non encore raccordés restent grisés.\n- Journal d’appels persistant côté hôte, puis import dans le vrai journal Gaia,\n avec acquittement et détection des doublons après interruption.\n- Batterie : pont vers l’icône historique ; une batterie inconnue n’est pas\n présentée comme vide. Correction de l’index de contacts Facebook absent ou\n mal formé, qui bloquait aussi la recherche de numéro et l’historique local.\n- Polices : comparaison des glyphes réellement rendus. L’accueil utilisait\n déjà Fira Sans des deux côtés, mais certains textes système utilisaient Noto\n sur PC. Les règles de texte principales sélectionnent désormais la Fira de\n Gaia ; les polices d’icônes sont conservées.\n- Test Gecko avec modem simulé : appels entrants/sortants, boutons Répondre et\n Raccrocher, journal, veille/réveil, menu Power, volume et batterie.\n Aucun nouvel APK compilé dans cette étape.\n\n- Étape v13 (remplacée par Callscreen ci-dessus) : écran d’appel géré par l’application système : numérotation, sonnerie,\n communication et bouton Raccrocher. Les contrôles restent présents si le\n service perd momentanément l’état de l’appel.\n- Tests sur le vrai Gecko bureau avec modem simulé, y compris échec puis nouvel\n essai de raccrochage. Sources PC synchronisées ; aucun APK reconstruit.\n\n### Tundra · Sargo\n\n- Build privé v20 installé et relu dans `boot_a`, après deux démarrages RAM\n et un démarrage depuis le stockage ; réglages conservés, slot marqué réussi.\n- Lecture des seules touches Power/Volume du Sargo et\n transmission à `HardwareButtons`. Appui court : écran éteint/rallumé ; appui\n long : menu Gaia. Le broker natif commande rétroéclairage, volume, arrêt et\n redémarrage avec une liste d’opérations fixe.\n- Sérialisation des changements de luminosité et d’alimentation : deux\n requêtes simultanées ne peuvent plus laisser le rétroéclairage éteint au réveil.\n- Volume natif : accès en lecture au socket PulseAudio de la session, sans\n lequel l’indicateur Gaia changeait mais la sortie audio restait inchangée.\n- La veille écran ne suspend pas encore le noyau. Réception réelle des appels,\n audio de conversation et commandes audio avancées restent à qualifier.\n\n- Étape précédente : build privé v13 installé dans `boot_a` après deux démarrages RAM ; démarrage\n depuis le stockage, relecture et indicateur de réussite A/B vérifiés.\n- Transmission des appels et de leurs états depuis oFono ; commandes Répondre et\n Raccrocher limitées aux chemins d’appel du modem.\n- Correction des réponses D-Bus sans contenu : un raccrochage réussi ne provoque\n plus une fausse erreur de réponse invalide.\n- Un appel réel court atteint la sonnerie puis est raccroché automatiquement.\n Cela ne qualifie pas encore l’audio en conversation ni les appels entrants.\n- Caméra : diagnostic corrigé, sans annoncer une initialisation à partir d’un\n simple délai d’attente. Deux capteurs sont détectés via libhybris, mais le test\n de flux reçoit zéro image. La caméra Gaia native reste indisponible ; voir la\n [note technique](tundra/docs/CAMERA-SARGO.md).", - "en": "## In development — 27 September 2026\n\n### Shared · Gaia / Desktop\n\n- Replace the temporary call overlay with Gaia’s original **Callscreen**:\n incoming/outgoing calls, answer, hang up and automatic close. A compatibility\n adapter supplies `mozTelephony` events to the original modules. Unsupported\n audio/conference controls remain disabled.\n- Persist completed calls in the host and import them into Gaia’s original call\n log, with acknowledgement and duplicate detection after interruption.\n- Connect real battery state to the original status icon; unknown is not empty.\n Handle a missing/malformed Facebook contacts index without blocking local\n number lookup or call history.\n- Compare rendered font faces: home already used Fira on both platforms, but\n desktop system text partly used Noto. Main text rules now select Gaia’s\n bundled Fira face while preserving icon fonts.\n- Desktop Gecko test with a simulated modem covers incoming/outgoing UI,\n answer/hang up, history, screen sleep/wake, power menu, volume and battery.\n No APK rebuilt in this step.\n\n- Earlier v13 step (superseded above): system-owned call screen with dialing, ringing, connected state and Hang up.\n Controls remain available if the service temporarily loses call status.\n- Tested in desktop Gecko with a simulated modem, including hang-up failure and\n retry. Desktop sources synchronized; no APK rebuilt.\n\n### Tundra · Sargo\n\n- Private v20 installed and read back in `boot_a` after two RAM boots and one\n installed boot; settings retained and slot marked successful.\n- Read only Sargo’s physical Power/Volume keys and forward\n their edges to Gaia HardwareButtons. Short press sleeps/wakes the screen;\n long press opens Gaia’s menu. The restricted native broker controls backlight,\n volume, shutdown and reboot.\n- Serialized brightness and power changes: concurrent requests no longer\n leave the backlight disabled while waking the screen.\n- Native volume: expose the session PulseAudio socket read-only to the service;\n otherwise Gaia’s indicator changed while the audio output remained unchanged.\n- Screen sleep does not suspend the kernel yet. Real incoming calls, conversation\n audio and advanced call audio controls remain to be qualified.\n\n- Earlier step: private v13 installed in `boot_a` after two RAM boots; installed boot,\n readback and the A/B success flag verified.\n- oFono call state reporting and Answer/Hang up commands restricted to modem call paths.\n- Successful void D-Bus responses no longer cause a false invalid-response error.\n- A short real outgoing call reached ringing and was automatically hung up.\n Conversation audio and incoming calls remain unqualified.\n- Camera diagnostics no longer mistake a timeout for successful initialization.\n Libhybris detects two sensors, but the preview probe receives no frames.\n Native Gaia camera access remains unavailable." + "fr": "## En développement — 27 septembre 2026\n\n- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Callscreen Gaia pour les appels entrants/sortants, avec réponse et raccrochage. Remplace l’écran provisoire v13.\n- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Journal d’appels persistant avec acquittement et dédoublonnage.\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Batterie inconnue distincte d’une batterie vide ; index Contacts absent ou mal formé toléré.\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Textes système affichés avec Fira Sans, sans modifier les polices d’icônes.\n- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) PC avec modem simulé : appels, raccrochage en échec puis nouvel essai, journal, veille/réveil, menu Power, volume et batterie.\n- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Sargo : boutons Power/Volume raccordés à Gaia et commandes natives d’écran, volume, arrêt et redémarrage.\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Sargo : luminosité et alimentation sérialisées pour éviter l’écran noir au réveil ; accès PulseAudio corrigé pour le volume.\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Sargo : un raccrochage réussi avec réponse D-Bus vide ne produit plus de fausse erreur.\n- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Sargo v13 puis v20 installés après essais RAM ; démarrage, relecture, réglages et validation du slot contrôlés.\n- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Appel sortant réel arrivé à la sonnerie puis raccroché. Appels entrants et audio de conversation non validés.\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Diagnostic caméra : un timeout n’est plus présenté comme une initialisation réussie. Deux capteurs détectés, sans trames reçues.\n- ![Note](https://img.shields.io/badge/Note-6B7280?style=flat-square) Caméra native indisponible, suspension CPU et commandes audio avancées incomplètes. Aucun nouvel APK.\n", + "en": "## In development — 27 September 2026\n\n- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Gaia Callscreen for incoming/outgoing calls, answer and hangup. Replaces the temporary v13 screen.\n- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Persistent call log with acknowledgement and duplicate detection.\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Distinguish unknown battery state from empty battery; tolerate missing or malformed Contacts indexes.\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Render system text with Fira Sans while preserving icon fonts.\n- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Desktop with simulated modem: calls, failed hangup and retry, history, sleep/wake, Power menu, volume and battery.\n- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Sargo: Power/Volume buttons connected to Gaia, with native screen, volume, shutdown and reboot controls.\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Sargo: serialize brightness and power changes to prevent a black screen on wake; fix PulseAudio access for volume.\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Sargo: successful hangup with an empty D-Bus reply no longer produces a false error.\n- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Sargo v13 then v20 installed after RAM trials; startup, readback, settings and slot validation checked.\n- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Real outgoing call reached ringing and was hung up. Incoming calls and conversation audio unvalidated.\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Camera diagnostics: a timeout no longer counts as successful initialization. Two sensors detected, without received frames.\n- ![Note](https://img.shields.io/badge/Note-6B7280?style=flat-square) Native camera unavailable, CPU suspend and advanced audio controls incomplete. No new APK.\n" + }, + "metadata": { + "kind": "development", + "status": "development", + "platforms": [ + "pc", + "models" + ], + "models": [ + "sargo" + ] } }, { "id": "development-2026-09-26-1", "text": { - "fr": "## En développement — 26 septembre 2026\n\n### Commun · Gaia\n\n- Polices : Fira Sans OpenType du simulateur historique, avec les mêmes fichiers\n pour les pages Gaia et le système Tundra. Correction du repli `sans-serif` natif\n et vérification Fontconfig ajoutée ; les polices d’icônes restent distinctes.\n- Paramètres : retrait des boutons de diagnostic « Paramètres Tundra » et\n « Tester une notification ». Le résumé des versions et les informations\n techniques restent disponibles.\n- Panneau matériel : transmission des capacités de la plateforme au panneau.\n Une variable absente provoquait une alerte Gecko au lieu d’afficher la batterie.\n\n### PC\n\n- Adaptateur local synchronisé avec les sources communes. Gecko reste en 156.0.1.\n\n### Android\n\n- Correctif du panneau présent dans les sources communes ; aucun nouvel APK construit.\n\n### Tundra / Sargo\n\n- Validation explicite du succès A/B après installation : sauvegarde et contrôle\n des métadonnées GPT, outil Qualcomm vérifié, confirmation Fastboot.\n- Erreurs du modem distinguées des erreurs de transport ; boîte d’erreur intégrée\n à Gaia. La caméra native indisponible ne suggère plus une autorisation inutile.\n- Diagnostic des polices réellement utilisées par Gecko et du réseau radio, sans\n appel ni SMS automatique. Fira Sans confirmée sur l’accueil et la barre d’état.\n- Installation réelle du v9 polices dans `boot_a` sur le Sargo inventorié.\n Contenu écrit relu et conforme ; cinq autres partitions de boot sauvegardées intactes.\n- Deux démarrages depuis le stockage : 10 contrôles Gaia et 9 contrôles système\n réussis par boot, réglages conservés. Le PC n’est plus nécessaire pour démarrer.\n Fichiers de secours Droidian conservés ; restauration réelle non testée.\n- Candidat persistant privé : image de données ext4 2 Gio, profils et réglages Gaia\n conservés, identité/comptes système préservés et suppression du watchdog de dix minutes.\n- Deux démarrages successifs de la même image v8 : 9 contrôles Gaia et 9 contrôles\n système réussis par boot. Diagnostic séparé : 15 contrôles avec la même interface.\n- Installation limitée à `boot_a` et restauration de l’original préparées et testées\n localement ; aucun flash effectué. Les limites matérielles restent documentées.\n- Démarrage normal sans ouverture automatique de Paramètres/Horloge ni défilement\n de l’accueil. Ces gestes appartiennent désormais au mode explicite `--diagnostics`.\n- Plein écran établi avant le chargement de Gaia ; décorations natives désactivées.\n Sur le Pixel testé : 360 × 740 pixels CSS, facteur 3, dimensions stables et accueil\n inchangé pendant les cinq relevés après démarrage.\n- v13 testé en RAM : huit contrôles de démarrage et neuf contrôles système réussis.\n Les rapports distinguent cette vérification du démarrage des tests interactifs.\n- Diagnostic v14 séparé : treize contrôles interactifs réussis, dont le panneau\n matériel ; ouverture et scan Wi-Fi vérifiés. Même interface que le v13 normal.\n- Les images de diagnostic interactif ne peuvent pas devenir la sélection normale\n du lanceur. L’ancienne sélection v13 est conservée dans son historique.\n- Modem enregistré en UMTS pendant l’essai ; lecture audio réussie et signal du\n microphone mesuré sans conserver d’enregistrement. Cela ne qualifie pas encore\n l’audio en appel, la caméra ou la veille.\n- Construction du ramdisk indépendante de la langue du PC : analyse `readelf` en\n locale C pour conserver le chargeur dynamique ARM64 et ses dépendances.\n\nLes six partitions boot/DTBO/vbmeta A/B sont inchangées. Le build reste un essai\nprivé en RAM, **pas une ROM flashable**. Détails : [état de Tundra](tundra/docs/STATUS.md).", - "en": "## In development — 26 September 2026\n\n### Shared · Gaia\n\n- Fonts: use the archived simulator’s Fira Sans OpenType files for both Gaia\n pages and Tundra system text. Fix native sans-serif fallback and add Fontconfig\n validation; icon fonts remain separate.\n- Settings: remove the “Tundra settings” and “Test a notification” diagnostic\n buttons. Release information and the technical details remain available.\n- Hardware panel: pass platform capabilities into the panel. An undefined variable\n used to open a Gecko alert instead of displaying battery information.\n\n### Desktop\n\n- Local adapter synchronized with the shared sources. Gecko remains at 156.0.1.\n\n### Android\n\n- Hardware panel fix is in the shared sources; no new APK has been built.\n\n### Tundra / Sargo\n\n- Explicit A/B boot-success finalization after installation: backed-up and checked\n GPT metadata, verified Qualcomm utility, Fastboot confirmation.\n- Modem rejections distinguished from transport failures; errors shown inside Gaia.\n The unavailable native camera no longer suggests an ineffective permission change.\n- Rendered-font and radio diagnostics without automatic calls or messages. Fira Sans\n confirmed on the home screen and status bar.\n- Installed the v9 font build into `boot_a` on the inventoried Sargo. Written\n image verified by readback; the other five backed-up boot partitions are unchanged.\n- Two boots from device storage pass 10 Gaia and 9 system checks each and retain\n settings. A PC is no longer required to boot. Droidian fallback files retained;\n actual restoration has not been tested.\n- Private persistent candidate: separate 2 GiB ext4 data image, retained Gaia\n settings/profiles and system identities, no ten-minute reboot watchdog.\n- Two consecutive boots of the same v8 image: 9 Gaia and 9 system checks pass on\n each boot. Separate diagnostic: 15 checks using identical shared UI files.\n- Boot-a-only installation and original-boot restoration tools prepared and tested\n locally. No actual flash performed; remaining hardware limits are documented.\n- Normal startup no longer opens Settings/Clock or scrolls the home screen. These\n actions now belong to the explicit `--diagnostics` mode.\n- Enter fullscreen before loading Gaia and hide native window decorations. On the\n tested Pixel: 360 × 740 CSS pixels at scale 3, stable dimensions and an unchanged\n home screen throughout five post-startup samples.\n- v13 RAM trial passes eight startup checks and nine system checks. Reports now\n distinguish startup observation from interactive qualification.\n- Separate v14 diagnostic: thirteen interactive checks pass, including the hardware\n panel; Wi-Fi panel and scanning verified with the same UI files as normal v13.\n- Interactive diagnostic images are rejected as the normal launch target. The\n earlier v13 selection is retained in the launcher history.\n- Modem registered on UMTS during the trial; audio playback completed and microphone\n samples were measured without retaining a recording. In-call audio, camera and\n suspend remain unqualified.\n- Ramdisk dependency discovery now runs `readelf` in the C locale, preserving the\n ARM64 dynamic loader regardless of the build machine's language.\n\nAll six A/B boot/DTBO/vbmeta partitions are unchanged. This remains a private RAM\ntrial, **not a flashable ROM**. See [Tundra status](tundra/docs/STATUS.md)." + "fr": "## En développement — 26 septembre 2026\n\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Polices Fira Sans historiques utilisées dans Gaia et Tundra ; repli sans-serif et contrôle Fontconfig corrigés.\n- ![Change](https://img.shields.io/badge/Change-64748B?style=flat-square) Boutons de diagnostic retirés des Paramètres.\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Panneau matériel : capacités de la plateforme transmises correctement pour afficher la batterie.\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Sargo : erreurs modem et transport distinguées ; message caméra indisponible corrigé.\n- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Sargo : données persistantes sur ext4 2 Gio et validation du succès A/B après installation.\n- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Sargo v8 : deux boots avec persistance, 9 contrôles Gaia et 9 système par boot. Outils d’installation et de secours testés localement.\n- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Sargo v9 installé sur boot_a : deux démarrages avec 10 contrôles Gaia et 9 système chacun. Relecture conforme, cinq autres partitions préservées ; restauration réelle non testée.\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Démarrage normal sans gestes de diagnostic automatiques ; plein écran établi avant Gaia, dimensions stables à 360 × 740 pixels CSS.\n- ![Change](https://img.shields.io/badge/Change-64748B?style=flat-square) Mode `--diagnostics` séparé ; images de diagnostic exclues du lancement normal.\n- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Sargo : v13 testé en RAM (8 contrôles de démarrage, 9 système), v14 en diagnostic (13 contrôles interactifs), Wi-Fi inclus.\n- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Enregistrement UMTS, lecture audio et signal du micro vérifiés. Audio d’appel, caméra et veille non validés.\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Construction du ramdisk indépendante de la langue du PC grâce à readelf en locale C.\n- ![Note](https://img.shields.io/badge/Note-6B7280?style=flat-square) Essais privés, sans ROM publique. Sources PC synchronisées ; correctif commun disponible pour Android, sans nouvel APK.\n", + "en": "## In development — 26 September 2026\n\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Use historical Fira Sans fonts in Gaia and Tundra; correct sans-serif fallback and Fontconfig checks.\n- ![Change](https://img.shields.io/badge/Change-64748B?style=flat-square) Removed diagnostic buttons from Settings.\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Hardware panel: pass platform capabilities correctly to display the battery.\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Sargo: distinguish modem and transport errors; correct the unavailable-camera message.\n- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Sargo: persistent data on a 2 GiB ext4 image and A/B success validation after installation.\n- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Sargo v8: two boots with persistence, 9 Gaia and 9 system checks per boot. Installation and recovery tools tested locally.\n- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Sargo v9 installed on boot_a: two boots with 10 Gaia and 9 system checks each. Matching readback, five other partitions preserved; actual restoration untested.\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Normal startup without automatic diagnostic gestures; fullscreen before Gaia, stable 360 × 740 CSS-pixel layout.\n- ![Change](https://img.shields.io/badge/Change-64748B?style=flat-square) Separate `--diagnostics` mode; diagnostic images excluded from normal launch.\n- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Sargo: v13 tested in RAM (8 startup, 9 system checks), v14 diagnostics (13 interactive checks), including Wi-Fi.\n- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) UMTS registration, audio playback and microphone signal checked. Call audio, camera and suspend unvalidated.\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Ramdisk builds independent of the desktop language by running readelf in the C locale.\n- ![Note](https://img.shields.io/badge/Note-6B7280?style=flat-square) Private trials, without a public ROM. Desktop sources synchronized; shared Android fix available, without a new APK.\n" + }, + "metadata": { + "kind": "development", + "status": "development", + "platforms": [ + "pc", + "apk", + "models" + ], + "models": [ + "sargo" + ] } }, { "id": "development-2026-09-25-1", "text": { - "fr": "## En développement — 25 septembre 2026\n\n### Commun · Gaia\n\n- **Navigation :** bouton Accueil rond ; dégradé noir vers transparent au-dessus du\n fond d’écran, fond bleu uni dans les applications. L’appui modifie le cercle\n plutôt que de laisser un rectangle bleu sur l’accueil.\n- **Défilement :** le dégradé disparaît au bas du panneau de l’accueil ; il revient\n quand on remonte. Le changement de panneau Applications / Pages est pris en compte.\n- **Typographie :** polices Fira Sans embarquées, avec plusieurs graisses et italiques,\n au lieu de dépendre des polices installées sur le système hôte.\n- **Informations :** affichage séparé de Vulpes, Gaia, du moteur réellement exécuté,\n de la plateforme, du modèle et de l’adaptateur. Le panneau prévoit Tundra et\n l’adaptation matérielle, maintenant raccordés au backend natif Tundra.\n- **Icônes :** reprise du chargement après une première mesure de taille nulle,\n partagée entre PC, Android et Tundra. Le correctif était auparavant limité à Android.\n- **Maintenance :** ce journal centralise les évolutions des trois plateformes.\n\n### PC · Validé localement\n\n- Chargement d’une icône initialisée à taille nulle puis rendue visible : testé.\n- Navigation Accueil / Paramètres / retour Accueil, fond transparent en bas de page,\n dégradé en haut et contrôle des permissions testés dans un profil isolé.\n- Panneau Informations testé en français et en anglais, sans anciennes lignes en double.\n- Le moteur demeure Gecko **156.0.1** ; ces changements d’interface ne recompilent pas Gecko.\n\n### Android · Sources adaptées, Java compilé\n\n- Même comportement de navigation prévu par le pont GeckoView / Android : barre\n superposée sur l’accueil, place réservée dans les applications et le navigateur.\n- État de navigation conservé lors de la recréation de l’activité Android.\n- Compilation Java réussie. **Aucun nouvel APK livré pour ces changements** et\n comportement visuel encore à retester sur Android.\n\n### Tundra / Sargo · Essais privés en RAM\n\n- Build réseau v6 devenu référence du lanceur après 13 contrôles Gaia et 9\n contrôles système ; retour automatique et six partitions inchangées vérifiés.\n- Backend Tundra distinct du PC : modèle Pixel 3a, adaptation Sargo et version\n native affichés sans annoncer une caméra ou une téléphonie non raccordées.\n- Batterie et charge lues sur le matériel ; panneau d’état bilingue dans Gaia.\n 13 contrôles Gaia réussis sur l’essai réseau, dont ce nouveau panneau.\n- Wi-Fi réel testé : scan, connexion, IPv4, DNS et page HTTPS sous Gecko.\n Ajout de `pd-mapper`, désactivation d’IPA non initialisé et du sondage caméra\n prématuré ; 9 contrôles système passent, sans Oops noyau. La sélection du réseau\n dans Gaia reste à raccorder ; les identifiants du test restent en RAM.\n- Configuration des services embarquable dans le petit ramdisk : pas de nouvelle\n copie des 3 Gio pour chaque ajustement de démarrage. Partitions modem protégées\n en utilisant des copies NV en RAM pour les essais Qualcomm.\n\n- Nouvelle image système ext4 contenant le runtime ARM64, les HAL de référence,\n Phoc, Gecko et Gaia. Le runtime n’est plus chargé depuis l’arborescence de travail\n Droidian, ni le HAL depuis son ancien fichier `/var/lib/lxc/android/`.\n- Démarrage du second essai vérifié sur Pixel 3a : PID 1 Tundra, services HAL,\n compositeur et Gaia ; **7 contrôles Gaia et 8 contrôles système réussis**.\n- Capture physique de l’accueil contrôlée : icônes présentes, Fira Sans chargée,\n bouton rond et dégradé visibles. Gecko **156.0.1 ARM64**, WebRender matériel.\n- Petits changements d’interface embarqués dans le ramdisk d’essai : inutile de\n retransférer les 3 Gio du système pour chaque correction CSS/JavaScript.\n- Copie des sources de construction conservée avec les nouvelles images, vérifiée\n par empreintes, pour permettre de les retester après l’évolution du code courant.\n- Le dernier essai (`sargo-system-boot-20260925-v5`) valide **10 contrôles Gaia et\n 8 contrôles système**, dont icônes, police chargée, fond à l’appui, bas de page\n et retour depuis une application. Capture physique contrôlée ; référence locale\n du lanceur mise à jour. Aucun APK publié ni téléphone flashé.\n\n**Limites :** stockage installé encore utilisé comme support du fichier système ;\nmodifications perdues à l’arrêt ; retour automatique au système conservé après dix\nminutes. Aucun flash ni remplacement de Droidian. Le redémarrage à chaud du\ncompositeur a échoué lors d’un essai ; le démarrage à froid fonctionne. Réseau,\nmodem, audio, caméra et veille restent à intégrer ou qualifier. Ce n’est pas une\nROM publique installable ni un téléphone quotidien validé.\n\nDétails : [image système](tundra/docs/SYSTEM-IMAGE-SARGO.md),\n[état Tundra](tundra/docs/STATUS.md), [parité des plateformes](docs/PARITE-BUREAU-ANDROID.md).", - "en": "## In development — 25 September 2026\n\n### Shared · Gaia\n\n- **Navigation:** a circular Home button, a black-to-transparent gradient over the wallpaper,\n and a solid blue bar inside apps. Pressing Home now changes the ring instead of leaving\n a blue rectangle over the home screen.\n- **Scrolling:** the gradient disappears at the bottom of the home panel and returns when\n scrolling up. Switching between the Apps and Pages panels is supported.\n- **Typography:** bundled Fira Sans fonts, with multiple weights and italics, instead of\n relying on the host system’s installed fonts.\n- **Device information:** separate product, Gaia, actual engine, platform, model and adapter\n versions. The panel includes support for Tundra and hardware adaptation fields;\n connecting these to the native platform is still pending.\n- **Icons:** loading resumes after an initial zero-width measurement, across desktop,\n Android and Tundra. This fix was previously limited to Android.\n- **Maintenance:** this changelog brings together the three platforms’ progress.\n\n### Desktop · Locally tested\n\n- Tested an icon initially measured at zero width, then made visible.\n- Tested Home / Settings / Home navigation, a transparent bar at the bottom, the gradient\n at the top and permission checks in an isolated profile.\n- Tested the information panel in French and English, without duplicate legacy rows.\n- The engine remains Gecko **156.0.1**; these UI changes do not rebuild Gecko.\n\n### Android · Sources adapted, Java compiled\n\n- The GeckoView / Android bridge implements the same navigation states: an overlay on Home,\n with space reserved for the bar in apps and the browser.\n- Navigation state survives Android activity recreation.\n- Java compilation passed. **No new APK delivered for these changes**;\n visual behaviour still needs testing on Android.\n\n### Tundra / Sargo · Private RAM trials\n\n- Network build v6 is the launcher reference after 13 Gaia and 9 system checks;\n automatic return and unchanged hashes of all six boot partitions verified.\n- Separate Tundra backend reports Pixel 3a, Sargo adaptation and native version,\n without claiming an unimplemented camera or telephony stack.\n- Real battery and charging state with a bilingual Gaia hardware panel.\n The network trial passed 13 Gaia checks, including this panel.\n- Real Wi-Fi tested: scan, connection, IPv4, DNS and HTTPS in Gecko. Added\n `pd-mapper`, disabled uninitialized IPA offload and premature camera probing;\n 9 system checks pass without kernel Oops. Gaia network selection is still\n pending; trial credentials remain in RAM.\n- Service configuration can travel in the small ramdisk, avoiding another 3 GiB\n transfer per boot adjustment. Qualcomm trials use RAM copies of modem NV data.\n\n- A new ext4 system image contains the ARM64 runtime, reference HALs, Phoc, Gecko and Gaia.\n The runtime is no longer loaded from the Droidian working directory, nor the HAL\n from its previous `/var/lib/lxc/android/` location.\n- The second Pixel 3a trial passed **7 Gaia checks and 8 system checks**, with Tundra PID 1,\n its HAL services, compositor and Gaia.\n- A physical display capture confirms home icons, loaded Fira Sans, the circular button\n and gradient. Gecko **156.0.1 ARM64**, with hardware WebRender.\n- Small UI changes travel in the trial ramdisk, avoiding another 3 GiB system transfer\n for each CSS or JavaScript adjustment.\n- Build sources are saved alongside new images and verified by hashes, so qualified\n images remain testable after the checkout changes.\n- The latest trial (`sargo-system-boot-20260925-v5`) passes **10 Gaia checks and 8 system\n checks**, including icons, fonts, pressed Home, bottom-of-page behaviour and app return.\n Physical capture reviewed; local launcher reference updated. No APK published or phone flashed.\n\n**Limitations:** the installed filesystem still carries the system image; writes are lost\nat shutdown; the trial returns to the retained system after ten minutes. No flashing or\nDroidian replacement. A warm compositor restart failed during one trial; cold boot works.\nNetworking, modem, audio, camera and suspend still need integration or qualification.\nThis is not a public installable ROM or a validated daily-use phone." + "fr": "## En développement — 25 septembre 2026\n\n- ![Change](https://img.shields.io/badge/Change-64748B?style=flat-square) Bouton Accueil rond, dégradé transparent sur le fond d’écran et barre bleue dans les applications.\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Dégradé masqué en bas de page et rétabli au défilement ; plus de rectangle bleu à l’appui sur Accueil.\n- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Fira Sans embarquée avec graisses et italiques ; versions produit, Gaia, moteur et plateforme affichées séparément.\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Reprise du chargement des icônes après une première mesure de taille nulle sur les trois plateformes.\n- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) PC : icônes, navigation, permissions et panneau Informations FR/EN vérifiés avec Gecko 156.0.1.\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Android : état de navigation conservé après recréation de l’activité. Compilation Java réussie ; rendu à retester, aucun nouvel APK livré.\n- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Tundra : image ext4 avec runtime ARM64, HAL, Phoc, Gecko et Gaia ; petits correctifs embarqués dans le ramdisk.\n- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Sargo : backend dédié, informations Pixel 3a et lecture matérielle de la batterie. Sources de construction archivées avec empreintes.\n- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Sargo : WebRender matériel, icônes et polices contrôlés sur capture physique. v5 : 10 contrôles Gaia et 8 système réussis.\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Sargo : ajout de pd-mapper, désactivation d’IPA non initialisé et du sondage caméra prématuré. Données NV du modem copiées en RAM pour les essais.\n- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Sargo v6 : scan Wi-Fi, connexion, IPv4, DNS et HTTPS vérifiés ; 13 contrôles Gaia et 9 système réussis. Sélection du réseau dans Gaia encore à raccorder.\n- ![Note](https://img.shields.io/badge/Note-6B7280?style=flat-square) Essais RAM sans flash, retour automatique après dix minutes et modifications perdues à l’arrêt. Démarrage à froid réussi ; redémarrage à chaud du compositeur en échec.\n- ![Note](https://img.shields.io/badge/Note-6B7280?style=flat-square) Pas de ROM publique ; modem, audio, caméra et veille encore à intégrer ou valider.\n", + "en": "## In development — 25 September 2026\n\n- ![Change](https://img.shields.io/badge/Change-64748B?style=flat-square) Round Home button, transparent wallpaper gradient and blue bar inside apps.\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Hide the gradient at the bottom and restore it when scrolling; remove the blue rectangle when pressing Home.\n- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Bundled Fira Sans weights and italics; separate product, Gaia, engine and platform versions.\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Resume icon loading after an initial zero-size measurement on all three platforms.\n- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Desktop: icons, navigation, permissions and French/English information panel checked with Gecko 156.0.1.\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Android: retain navigation state after activity recreation. Java compilation passed; visuals need retesting, no new APK delivered.\n- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Tundra: ext4 image with ARM64 runtime, HAL, Phoc, Gecko and Gaia; small fixes bundled in the ramdisk.\n- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Sargo: dedicated backend, Pixel 3a information and hardware battery readings. Build sources archived with checksums.\n- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Sargo: hardware WebRender, icons and fonts checked on a physical display capture. v5: 10 Gaia and 8 system checks passed.\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Sargo: add pd-mapper, disable uninitialized IPA and premature camera probing. Copy modem NV data to RAM for trials.\n- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Sargo v6: Wi-Fi scan, connection, IPv4, DNS and HTTPS verified; 13 Gaia and 9 system checks passed. Gaia network selection still pending.\n- ![Note](https://img.shields.io/badge/Note-6B7280?style=flat-square) RAM trials without flashing, automatic return after ten minutes and changes lost at shutdown. Cold boot passed; warm compositor restart failed.\n- ![Note](https://img.shields.io/badge/Note-6B7280?style=flat-square) No public ROM; modem, audio, camera and suspend still need integration or validation.\n" + }, + "metadata": { + "kind": "development", + "status": "development", + "platforms": [ + "pc", + "apk", + "models" + ], + "models": [ + "sargo" + ] } }, { "id": "2.7-preview.7", "text": { - "fr": "## 2.7-preview.7 — APK construit, référence locale\n\n### Commun / PC / Android\n\n- Correction de l’autorisation interne d’ouverture du capteur sur PC et de la\n reprise après la première demande de permission CAMERA sur Android.\n- Messages Photo distincts pour un refus, un capteur absent ou un échec d’ouverture.\n- Version produit commune **2.7-preview.7**, Gaia **2.7.0** ; numéros du moteur séparés.\n- Gecko PC **156.0.1**, GeckoView Android **156.0** : l’écart réel de correctif est\n affiché, sans renommer artificiellement l’un des moteurs.\n\nDétails : [Photo et Galerie](docs/CAMERA-GALERIE.md), [README Android](android/README.md).", - "en": "## 2.7-preview.7 — APK built, local reference\n\n### Shared / Desktop / Android\n\n- Fixed internal camera allocation permission on desktop and recovery after the first\n CAMERA permission prompt on Android.\n- Photo distinguishes permission denial, a missing sensor and an opening failure.\n- Shared product version **2.7-preview.7**, Gaia **2.7.0**, with separate engine versions.\n- Desktop Gecko **156.0.1**, Android GeckoView **156.0**: the real patch difference is\n reported rather than relabelling either engine." + "fr": "## 2.7-preview.7 — APK construit, référence locale\n\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Permission caméra sur PC et reprise après la première autorisation Android.\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Messages distincts pour une permission refusée, un capteur absent et un échec d’ouverture.\n- ![Change](https://img.shields.io/badge/Change-64748B?style=flat-square) Versions produit et moteur séparées : Vulpes Preview7, Gaia 2.7.0, Gecko PC 156.0.1 et GeckoView Android 156.0.\n", + "en": "## 2.7-preview.7 — APK built, local reference\n\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Desktop camera permission and recovery after the first Android permission prompt.\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Separate messages for denied permission, missing sensors and opening failures.\n- ![Change](https://img.shields.io/badge/Change-64748B?style=flat-square) Separate product and engine versions: Vulpes Preview7, Gaia 2.7.0, desktop Gecko 156.0.1 and Android GeckoView 156.0.\n" + }, + "metadata": { + "kind": "version", + "status": "built", + "platforms": [ + "pc", + "apk" + ], + "models": [] } }, { "id": "2.7-preview.6", "text": { - "fr": "## 2.7-preview.6 — APK construit\n\n### Commun / PC / Android\n\n- Portage de la véritable application Photo Gaia : aperçu et capture JPEG via\n webcam sur PC ou capteur via GeckoView sur Android.\n- Photos enregistrées dans Galerie Gaia ; import de médias par le sélecteur du système.\n- Commandes non prises en charge désactivées : notamment flash et vidéo ; changement\n de caméra selon les capteurs disponibles.", - "en": "## 2.7-preview.6 — APK built\n\n### Shared / Desktop / Android\n\n- Ported the original Gaia Camera app: webcam preview and JPEG capture on desktop,\n or capture through GeckoView on Android.\n- Photos saved in Gaia Gallery; media imports use the host system’s file picker.\n- Unsupported controls disabled, including flash and video; camera switching depends\n on available sensors." + "fr": "## 2.7-preview.6 — APK construit\n\n- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Photo Gaia sur PC et Android : aperçu et capture JPEG via webcam ou GeckoView.\n- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Enregistrement dans Galerie et import par le sélecteur du système.\n- ![Note](https://img.shields.io/badge/Note-6B7280?style=flat-square) Flash et vidéo désactivés ; changement de caméra selon les capteurs disponibles.\n", + "en": "## 2.7-preview.6 — APK built\n\n- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Gaia Camera on desktop and Android: preview and JPEG capture through webcam or GeckoView.\n- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Save photos in Gallery and import through the system picker.\n- ![Note](https://img.shields.io/badge/Note-6B7280?style=flat-square) Flash and video disabled; camera switching depends on available sensors.\n" + }, + "metadata": { + "kind": "version", + "status": "built", + "platforms": [ + "pc", + "apk" + ], + "models": [] } }, { "id": "2.7-preview.5", "text": { - "fr": "## 2.7-preview.5 — APK construit\n\n### Commun\n\n- Versions centralisées dans `release.json`, séparation produit / Gaia / moteur.\n- Interface des fonctions natives partagée entre PC et Android ; contrôle du contenu\n commun réellement embarqué dans l’APK avant distribution.\n\n### PC\n\n- Intégration des panneaux de réglages Linux disponibles, import de médias et vCard,\n notifications du bureau et capture webcam de test.\n\n### Android\n\n- Même interface Gaia et commandes natives que le PC, adaptées aux capacités Android.\n- Tests de retour au premier plan et de récupération après arrêt du processus de rendu.", - "en": "## 2.7-preview.5 — APK built\n\n### Shared\n\n- Centralised versions in `release.json`, separating product, Gaia and engine versions.\n- Shared native integration UI between desktop and Android; packaged common files\n checked inside the APK before distribution.\n\n### Desktop\n\n- Integration with available Linux settings panels, media and vCard imports,\n desktop notifications and a test webcam capture.\n\n### Android\n\n- Same Gaia interface and native commands as desktop, adapted to Android capabilities.\n- Foreground return and recovery after rendering-process termination tested." + "fr": "## 2.7-preview.5 — APK construit\n\n- ![Change](https://img.shields.io/badge/Change-64748B?style=flat-square) Versions centralisées dans `release.json` ; produit, Gaia et moteur séparés.\n- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Interface native commune PC/Android et contrôle de parité des ressources de l’APK.\n- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) PC : réglages Linux, imports médias/vCard, notifications et capture webcam de test.\n- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Android : retour au premier plan et récupération après arrêt du processus de rendu.\n", + "en": "## 2.7-preview.5 — APK built\n\n- ![Change](https://img.shields.io/badge/Change-64748B?style=flat-square) Versions centralized in `release.json`; product, Gaia and engine separated.\n- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Shared desktop/Android native interface and APK asset parity checks.\n- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Desktop: Linux settings, media/vCard imports, notifications and test webcam capture.\n- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Android: foreground return and recovery after renderer termination.\n" + }, + "metadata": { + "kind": "version", + "status": "built", + "platforms": [ + "pc", + "apk" + ], + "models": [] } }, { "id": "2.7-156.0.2", "text": { - "fr": "## 2.7-156.0.2 — APK historique construit (révision 4)\n\nCe nom historique mélangeait révision du produit et version du moteur ; il ne\nsignifie pas que l’APK utilisait Gecko 156.0.2. Le moteur Android était Gecko 156.0.\n\n### Android\n\n- Reprise de l’activité et des sessions pour limiter les écrans blancs au retour dans Vulpes.\n- Accès aux réglages Android, au volume et à la luminosité de la fenêtre.\n- Import de médias et contacts, notifications natives avec permissions.\n- Transfert au composeur, à SMS et à Horloge Android ; aucun envoi silencieux ni\n remplacement des applications système Téléphone/SMS.\n- Capture initialement déléguée à l’application photo Android, avant le portage de Photo Gaia.", - "en": "## 2.7-156.0.2 — Historical APK built (revision 4)\n\nThis historical name mixed product revision and engine version; it does not mean\nthat the APK used Gecko 156.0.2. Its Android engine was Gecko 156.0.\n\n### Android\n\n- Activity and session recovery to reduce white screens when returning to Vulpes.\n- Access to Android settings, volume and window brightness.\n- Media and contact imports, native notifications with permissions.\n- Handoff to Android’s dialer, SMS and clock apps; no silent sending or replacement\n of the system Phone and SMS apps.\n- Capture initially delegated to Android’s camera app, before the Gaia Camera port." + "fr": "## 2.7-156.0.2 — APK historique construit (révision 4)\n\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Android : reprise de l’activité et des sessions pour réduire les écrans blancs.\n- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Accès aux réglages Android, volume et luminosité de la fenêtre.\n- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Import de médias et contacts ; notifications natives avec permissions.\n- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Transfert vers Téléphone, SMS, Horloge et Appareil photo Android. Aucun envoi silencieux ni remplacement des applications par défaut.\n- ![Note](https://img.shields.io/badge/Note-6B7280?style=flat-square) Numérotation historique : moteur Android Gecko 156.0, malgré le nom 2.7-156.0.2.\n", + "en": "## 2.7-156.0.2 — Historical APK built (revision 4)\n\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Android: activity and session recovery to reduce blank screens.\n- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Access to Android settings, volume and window brightness.\n- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Media and contact imports; native notifications with permissions.\n- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Handoff to Android Phone, SMS, Clock and Camera. No silent sending or replacement of default apps.\n- ![Note](https://img.shields.io/badge/Note-6B7280?style=flat-square) Historical numbering: Android engine Gecko 156.0, despite the 2.7-156.0.2 name.\n" + }, + "metadata": { + "kind": "version", + "status": "built", + "platforms": [ + "apk" + ], + "models": [] } }, { "id": "2.7-156.0.1", "text": { - "fr": "## 2.7-156.0.1 — APK publié (révision 3)\n\n- Nom **Vulpes OS - Preview** et icône du projet.\n- Correctif du fond d’écran clignotant inclus.\n- Numérotation historique conservée ; ne pas en déduire le patch du moteur Android.\n\n[Release](https://nnsprod.com/git/oversu/VulpesOS/releases/tag/android-2.7-156.0.1)", - "en": "## 2.7-156.0.1 — APK published (revision 3)\n\n- **Vulpes OS - Preview** name and project icon.\n- Wallpaper flickering fix included.\n- Historical numbering retained; it does not identify the Android engine’s patch version.\n\n[Release](https://nnsprod.com/git/oversu/VulpesOS/releases/tag/android-2.7-156.0.1)" + "fr": "## 2.7-156.0.1 — APK publié (révision 3)\n\n- ![Change](https://img.shields.io/badge/Change-64748B?style=flat-square) Nom « Vulpes OS - Preview » et icône du projet.\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Clignotement du fond d’écran corrigé.\n- ![Note](https://img.shields.io/badge/Note-6B7280?style=flat-square) Numérotation historique, distincte de la version du moteur Android.\n\n[Release](https://nnsprod.com/git/oversu/VulpesOS/releases/tag/android-2.7-156.0.1)\n", + "en": "## 2.7-156.0.1 — APK published (revision 3)\n\n- ![Change](https://img.shields.io/badge/Change-64748B?style=flat-square) “Vulpes OS - Preview” name and project icon.\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Wallpaper flickering corrected.\n- ![Note](https://img.shields.io/badge/Note-6B7280?style=flat-square) Historical numbering, separate from the Android engine version.\n\n[Release](https://nnsprod.com/git/oversu/VulpesOS/releases/tag/android-2.7-156.0.1)\n" + }, + "metadata": { + "kind": "version", + "status": "published", + "platforms": [ + "apk" + ], + "models": [] } }, { "id": "2.7-preview.2", "text": { - "fr": "## 2.7-preview.2 — APK publié\n\n- Correction du fond d’écran clignotant dans la Preview Android.\n\n[Release](https://nnsprod.com/git/oversu/VulpesOS/releases/tag/android-2.7-preview.2)", - "en": "## 2.7-preview.2 — APK published\n\n- Fixed wallpaper flickering in the Android Preview.\n\n[Release](https://nnsprod.com/git/oversu/VulpesOS/releases/tag/android-2.7-preview.2)" + "fr": "## 2.7-preview.2 — APK publié\n\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Clignotement du fond d’écran dans la Preview Android.\n\n[Release](https://nnsprod.com/git/oversu/VulpesOS/releases/tag/android-2.7-preview.2)\n", + "en": "## 2.7-preview.2 — APK published\n\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Wallpaper flickering in the Android Preview.\n\n[Release](https://nnsprod.com/git/oversu/VulpesOS/releases/tag/android-2.7-preview.2)\n" + }, + "metadata": { + "kind": "version", + "status": "published", + "platforms": [ + "apk" + ], + "models": [] } }, { "id": "2.7-preview.1", "text": { - "fr": "## 2.7-preview.1 — APK publié\n\n- Première Preview Android embarquant Gaia et GeckoView.\n- Version archivée ; clignotement du fond d’écran connu.\n\n[Release](https://nnsprod.com/git/oversu/VulpesOS/releases/tag/android-2.7-preview.1)", - "en": "## 2.7-preview.1 — APK published\n\n- First Android Preview bundling Gaia and GeckoView.\n- Archived version; known wallpaper flickering issue.\n\n[Release](https://nnsprod.com/git/oversu/VulpesOS/releases/tag/android-2.7-preview.1)" + "fr": "## 2.7-preview.1 — APK publié\n\n- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Première Preview Android avec Gaia et GeckoView.\n- ![Note](https://img.shields.io/badge/Note-6B7280?style=flat-square) Version archivée ; clignotement du fond d’écran connu.\n\n[Release](https://nnsprod.com/git/oversu/VulpesOS/releases/tag/android-2.7-preview.1)\n", + "en": "## 2.7-preview.1 — APK published\n\n- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) First Android Preview with Gaia and GeckoView.\n- ![Note](https://img.shields.io/badge/Note-6B7280?style=flat-square) Archived version; known wallpaper flickering issue.\n\n[Release](https://nnsprod.com/git/oversu/VulpesOS/releases/tag/android-2.7-preview.1)\n" + }, + "metadata": { + "kind": "version", + "status": "published", + "platforms": [ + "apk" + ], + "models": [] } }, { "id": "history", "text": { - "fr": "## Étapes antérieures — rétrospective technique\n\nCes étapes ne correspondent pas à de nouvelles releases publiques datées.\n\n### Gaia / PC\n\n- Constitution de Vulpes à partir de Gaia historique ; identité visuelle, fond\n d’écran, traductions et travail sur les informations système.\n- Portage expérimental Gecko 45 → 52, puis séparation de Gaia, des services et du\n moteur pour l’hôte Gecko récent.\n- Réimplémentation des API nécessaires : réglages, registre d’applications,\n contacts locaux, messages, alarmes, stockage et communications entre applications.\n- Adaptation des composants visuels anciens, des interactions tactiles simulées,\n du navigateur et des applications ; essais d’applications Firefox OS archivées.\n- Outillage de mise à jour Gecko avec vérifications, moteur candidat et retour\n arrière explicite. Il détecte les incompatibilités, sans les réparer automatiquement.\n- Lancement et mise à jour de Gaia sans recompilation complète du moteur.\n\n### Tundra / Sargo\n\n- Inventaire du Pixel 3a, sauvegardes des partitions de boot et tests USB de diagnostic.\n- Tests QEMU, puis Gecko ARM64 dans Droidian et sous un compositeur dédié.\n- Racine distincte, services HAL sélectionnés et PID 1 Tundra ; essais graphiques\n temporaires par Fastboot avec secours SSH et retour automatique.\n- Noyau de référence **4.9-124-google-sargo**, adaptation **Android 9 / API 28**.", - "en": "## Earlier milestones — technical retrospective\n\nThese milestones are not newly dated public releases.\n\n### Gaia / Desktop\n\n- Vulpes assembled from historical Gaia: branding, wallpaper, translations and\n work on system information.\n- Experimental Gecko 45 → 52 port, followed by separation of Gaia, services and\n the engine for the current Gecko host.\n- Reimplementation of required APIs: settings, application registry, local contacts,\n messages, alarms, storage and inter-app communication.\n- Adaptation of old UI components, simulated touch, browser and applications;\n testing of archived Firefox OS apps.\n- Gecko update tools with checks, candidate engines and explicit rollback.\n They detect incompatibilities without automatically repairing them.\n- Gaia launch and updates without rebuilding the whole engine.\n\n### Tundra / Sargo\n\n- Pixel 3a inventory, boot partition backups and USB diagnostic trials.\n- QEMU trials, then ARM64 Gecko inside Droidian and under a dedicated compositor.\n- Separate root, selected HAL services and Tundra PID 1; temporary graphical\n Fastboot trials with rescue SSH and automatic return.\n- Reference kernel **4.9-124-google-sargo**, **Android 9 / API 28** adaptation." + "fr": "## Étapes antérieures — rétrospective technique\n\n- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Base Gaia historique avec identité Vulpes, fond d’écran, traductions et informations système.\n- ![Change](https://img.shields.io/badge/Change-64748B?style=flat-square) Portage expérimental Gecko 45 vers 52, puis séparation de Gaia, des services et du moteur.\n- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) API de réglages, applications, contacts, messages, alarmes, stockage et communications entre applications.\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Adaptation des composants Gaia, gestes tactiles, navigateur et applications aux moteurs récents.\n- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Mise à jour Gecko avec contrôles et retour arrière ; mise à jour Gaia sans recompilation du moteur.\n- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Applications Firefox OS archivées, QEMU, puis Gecko ARM64 dans Droidian et sous compositeur dédié.\n- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Sargo : inventaire, sauvegardes de boot, racine distincte, services HAL et PID 1 Tundra.\n- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Essais graphiques Fastboot temporaires avec secours SSH et retour automatique.\n- ![Note](https://img.shields.io/badge/Note-6B7280?style=flat-square) Noyau 4.9-124-google-sargo, adaptation Android 9/API 28. Étapes historiques, sans nouvelles releases datées.\n", + "en": "## Earlier milestones — technical retrospective\n\n- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Historical Gaia base with Vulpes branding, wallpaper, translations and system information.\n- ![Change](https://img.shields.io/badge/Change-64748B?style=flat-square) Experimental Gecko 45 to 52 port, followed by separation of Gaia, services and engine.\n- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) APIs for settings, apps, contacts, messages, alarms, storage and inter-app communication.\n- ![Fix](https://img.shields.io/badge/Fix-52677D?style=flat-square) Adapt Gaia components, touch gestures, browser and apps to recent engines.\n- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Gecko updates with checks and rollback; Gaia updates without rebuilding the engine.\n- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Archived Firefox OS apps, QEMU, then ARM64 Gecko inside Droidian and under a dedicated compositor.\n- ![Add](https://img.shields.io/badge/Add-487768?style=flat-square) Sargo: inventory, boot backups, separate root, HAL services and Tundra PID 1.\n- ![Test](https://img.shields.io/badge/Test-75658A?style=flat-square) Temporary graphical Fastboot trials with SSH recovery and automatic return.\n- ![Note](https://img.shields.io/badge/Note-6B7280?style=flat-square) Kernel 4.9-124-google-sargo, Android 9/API 28 adaptation. Historical milestones, without newly dated releases.\n" + }, + "metadata": { + "kind": "history", + "status": "history", + "platforms": [ + "pc", + "models" + ], + "models": [ + "sargo" + ] } } ], @@ -254,6 +888,12 @@ "after": "2.7-preview.15", "url": "https://nnsprod.com/git/oversu/VulpesOS/releases/tag/v2.7-preview.17", "summary": "# Français\n\nAPK expérimental Android : Vulpes OS 2.7-preview.17, Gaia 2.7.0, GeckoView 156.0. Installation par-dessus une Preview précédente signée avec la même clé. Android 8.0/API26 minimum ; essais réalisés sur émulateur Android 16/API36.\n\n## 2.7-preview.17 — APK construit · 5 octobre 2026\n\n### Commun · Gaia / PC\n\n- Remettre Marketplace sur l’accueil avec son icône originale et le catalogue Vulpes. Ouvrir sa version française pour les langues fr, anglaise sinon.\n- Adapter les téléchargements du catalogue en installations confirmées : vérifier les archives ZIP, ajouter l’application à l’accueil et permettre son lancement et sa désinstallation.\n- Conserver les applications installées séparément des sources Gaia, sur des origines distinctes et sans privilèges système hérités des anciens manifestes.\n- Valider sur PC le parcours réel avec 2048 : catalogue, confirmation, installation, lancement, persistance et désinstallation. Contrôler les archives corrompues, chemins invalides, limites, annulation et confirmations à usage unique.\n\n### Android · APK\n\n- Construire et signer Preview17 avec le même Marketplace et le même service d’installation. Utiliser la vue GeckoView existante pour le catalogue et le stockage privé Android pour les paquets.\n- Vérifier sur émulateur Android 16/API36 le clic Installer, la confirmation, l’apparition sur l’accueil et le lancement de 2048 sans accès au pont système Vulpes. Vérifier aussi la conservation après arrêt forcé et relance, puis la désinstallation.\n\n## 2.7-preview.16 — APK construit · 5 octobre 2026\n\n### Commun · Gaia / PC\n\n- Rétablir les sélecteurs Gaia de fond d’écran et de sons, leur retour vers Paramètres et leur annulation par Accueil. Partager le transport des images et sons enregistrés entre PC et Android.\n- Propager les changements de langue à l’accueil, y compris sans permission de lecture générale des réglages. Vérifier les libellés FR/EN et la persistance.\n- Rétablir l’arrêt du balayage horizontal sur une page et masquer les applications de développement sur l’accueil, sans les supprimer des sources.\n- Utiliser le logo Vulpes fourni dans le splash et adapter les composants originaux de la liste des sons au moteur actuel.\n- Vérifier sur PC deux changements consécutifs de fond, le choix d’un son et les panneaux Gaia restaurés.\n\n### Android · APK\n\n- APK local Preview16 construit et signé ; contenu commun et version vérifiés. Aucune publication dans cette étape.\n- Valider Vulpes comme lanceur HOME, afficher le verrouillage Gaia après veille et rétablir le retour Android depuis la caméra ouverte au verrouillage.\n- Masquer les barres système Android en mode immersif, en conservant leur accès temporaire par balayage.\n- Tests sur émulateur Android 16/API36 : verrouillage, caméra/retour, FR/EN, accueil sans applications de test, fond d’écran et son. Fond conservé après arrêt forcé et relance, sans changement répété pendant 15 secondes. Autres appareils Android non qualifiés dans cette étape.\n\nLes correctifs caméra du 8 octobre ainsi que les derniers correctifs Wi-Fi, bannière et thème Marketplace du 6 octobre ne sont pas inclus dans cet APK. La validation de la caméra avant sur Sargo reste ouverte. Cette publication concerne uniquement Android.\n\n# English\n\nExperimental Android APK: Vulpes OS 2.7-preview.17, Gaia 2.7.0, GeckoView 156.0. Install over an earlier Preview signed with the same key. Android 8.0/API26 minimum; tested on an Android 16/API36 emulator.\n\n## 2.7-preview.17 — APK built · 5 October 2026\n\n### Shared · Gaia / Desktop\n\n- Restore Marketplace on Home with its original icon and the Vulpes catalogue. Select French for fr languages and English otherwise.\n- Turn catalogue downloads into confirmed installations: validate ZIP archives, add the application to Home and support launch and removal.\n- Store installed applications separately from Gaia sources, on distinct origins and without system privileges inherited from archived manifests.\n- Validate the real desktop workflow with 2048: catalogue, confirmation, installation, launch, persistence and removal. Check damaged archives, invalid paths, limits, cancellation and single-use confirmations.\n\n### Android · APK\n\n- Build and sign Preview17 with the same Marketplace and installation service. Use the existing GeckoView browser for the catalogue and private Android storage for packages.\n- Verify the Install click, confirmation, Home icon and 2048 launch without access to the Vulpes system bridge on an Android 16/API36 emulator. Also verify persistence after force-stop/relaunch and removal.\n\n## 2.7-preview.16 — APK built · 5 October 2026\n\n### Shared · Gaia / Desktop\n\n- Restore Gaia wallpaper and sound pickers, return navigation to Settings and cancellation through Home. Share persisted image and sound transport between desktop and Android.\n- Propagate language changes to Home without granting general settings read access. Check French/English labels and persistence.\n- Restore horizontal page snapping and hide development apps from Home without deleting their sources.\n- Use the supplied Vulpes logo in the splash and adapt the original sound-list components to the current engine.\n- Verify two consecutive wallpaper changes, sound selection and restored Gaia panels on desktop.\n\n### Android · APK\n\n- Build and sign local Preview16; verify shared payload and version. Nothing published in this step.\n- Validate Vulpes as the HOME launcher, restore Gaia locking after sleep and Android Back navigation from the lockscreen camera.\n- Hide Android system bars in immersive mode while retaining temporary swipe access.\n- Android 16/API36 emulator checks: locking, camera/Back, French/English, Home without test apps, wallpaper and sound. Wallpaper retained after force-stop/relaunch with no repeated changes over 15 seconds. Other Android devices not qualified in this step.\n\nThe October 8 camera changes and the later October 6 Wi-Fi, banner and Marketplace theme changes are not included in this APK. Front-camera validation on Sargo remains open. This release concerns Android only." + }, + "v2.7-preview.20": { + "version": "2.7-preview.20", + "after": "2.7-preview.17", + "url": "https://nnsprod.com/git/oversu/VulpesOS/releases/tag/v2.7-preview.20", + "summary": "# 🇫🇷 Vulpes OS — Preview 20\n\nAPK expérimental et sources communes PC / Android / Tundra mis à jour depuis la Preview 17. Les étapes 18 et 19 sont incluses dans cette version ; aucun ancien APK supplémentaire n’est publié.\n\n- Android 8 / API 26 minimum déclaré, API 37 ciblée. ARM64, ARMv7 et x86_64. Installer l’APK par-dessus une Preview signée avec la même clé conserve ses données.\n- Vérifier le téléchargement avec `sha256sum -c SHA256SUMS.txt`.\n- PC : `./setup.sh`, puis `./run.sh`. Sources Sargo incluses ; cette release ne distribue pas de nouvelle image native.\n- Vulpes 2.7-preview.20 · Gaia 2.7.0 · Gecko PC/Sargo 156.0.1 · GeckoView Android 156.0.\n\nAPK de développement signé, débogage via ADB autorisé. Appels/SMS délégués aux applications Android. Aucun engagement de compatibilité avec tous les appareils ; fonctions matérielles et services distants encore incomplets. L’historique ci-dessous distingue les tests de chaque plateforme.\n\n# 🇬🇧 Vulpes OS — Preview 20\n\nExperimental APK and shared desktop / Android / Tundra sources updated since Preview 17. Stages 18 and 19 are included; no additional older APKs are published.\n\n- Declared minimum Android 8 / API 26, target API 37. ARM64, ARMv7 and x86_64. Installing over a Preview signed with the same key preserves its data.\n- Verify your download with `sha256sum -c SHA256SUMS.txt`.\n- Desktop: `./setup.sh`, then `./run.sh`. Sargo sources included; this release supplies no new native image.\n- Vulpes 2.7-preview.20 · Gaia 2.7.0 · desktop/Sargo Gecko 156.0.1 · Android GeckoView 156.0.\n\nSigned development APK, debugging through authorized ADB. Calls/SMS delegated to Android applications. Not every device is qualified; hardware functions and remote services remain incomplete. The history below separates platform-specific test results.\n" } } } diff --git a/gaia/apps/camera/js/controllers/activity.js b/gaia/apps/camera/js/controllers/activity.js index 9be7de3f..3f167e07 100755 --- a/gaia/apps/camera/js/controllers/activity.js +++ b/gaia/apps/camera/js/controllers/activity.js @@ -272,7 +272,7 @@ ActivityController.prototype.onActivityConfirmed = function(newMedia) { // Video if (newMedia.isVideo) { - media.type = 'video/3gpp'; + media.type = media.blob.type || 'video/3gpp'; media.poster = newMedia.poster.blob; activity.postResult(media); this.app.clearSpinner(); diff --git a/gaia/apps/camera/js/controllers/overlay.js b/gaia/apps/camera/js/controllers/overlay.js index 952d8633..89d29fa1 100755 --- a/gaia/apps/camera/js/controllers/overlay.js +++ b/gaia/apps/camera/js/controllers/overlay.js @@ -107,7 +107,7 @@ OverlayController.prototype.updateOverlay = function(type, enabled, reason) { return; } - var closable = self.activity.pick && type !== 'request-fail'; + var closable = self.activity.pick || reason === 'request-fail'; var view = new OverlayView({ type: reason, closable: closable, @@ -118,6 +118,10 @@ OverlayController.prototype.updateOverlay = function(type, enabled, reason) { overlay.view = view.appendTo(document.body) .on('click:close-btn', function() { + if (reason === 'request-fail' && !self.activity.pick) { + self.updateOverlay(type, false); + return; + } self.app.emit('activitycanceled'); }); diff --git a/gaia/apps/camera/js/controllers/storage.js b/gaia/apps/camera/js/controllers/storage.js index 0f196f32..23a8058c 100755 --- a/gaia/apps/camera/js/controllers/storage.js +++ b/gaia/apps/camera/js/controllers/storage.js @@ -147,7 +147,7 @@ StorageController.prototype.storeVideo = function(video) { var self = this; // Add the poster image to the image storage - poster.filepath = video.filepath.replace('.3gp', '.jpg'); + poster.filepath = video.filepath.replace(/\.(?:3gp|mp4|webm)$/, '.jpg'); video.isVideo = true; this.storage.addPicture( diff --git a/gaia/apps/camera/js/controllers/viewfinder.js b/gaia/apps/camera/js/controllers/viewfinder.js index ccd9d398..8462f776 100755 --- a/gaia/apps/camera/js/controllers/viewfinder.js +++ b/gaia/apps/camera/js/controllers/viewfinder.js @@ -102,6 +102,7 @@ ViewfinderController.prototype.hideGrid = function() { * @private */ ViewfinderController.prototype.bindEvents = function() { + window.addEventListener('resize', this.configurePreview); // View this.views.viewfinder.on('fadedin', this.app.firer('viewfinder:visible')); diff --git a/gaia/apps/camera/js/lib/dcf.js b/gaia/apps/camera/js/lib/dcf.js index 98ffb082..ba28ba98 100755 --- a/gaia/apps/camera/js/lib/dcf.js +++ b/gaia/apps/camera/js/lib/dcf.js @@ -25,7 +25,7 @@ var dcfConfig = { seq: null, postFix: 'MZLLA', prefix: {video: 'VID_', image: 'IMG_'}, - ext: {video: '3gp', image: 'jpg'} + ext: {video: window.VulpesVideoExtension || '3gp', image: 'jpg'} }; exports.init = function() { diff --git a/gaia/apps/camera/js/lib/storage.js b/gaia/apps/camera/js/lib/storage.js index fcc18ac4..e92f6a5c 100755 --- a/gaia/apps/camera/js/lib/storage.js +++ b/gaia/apps/camera/js/lib/storage.js @@ -252,7 +252,7 @@ Storage.prototype.checkFilepath = function(filepath) { // image and get the video filepath by changing '.jpg' to '.3gp' if (filepath.indexOf('VID') != -1 && filepath.lastIndexOf('.jpg') === filepath.length - 4) { - filepath = filepath.replace('.jpg', '.3gp'); + filepath = filepath.replace('.jpg', '.' + (window.VulpesVideoExtension || '3gp')); } return filepath; @@ -380,7 +380,7 @@ Storage.prototype.deletePicture = function(filepath, done) { * @param {String} filepath */ Storage.prototype.deleteVideo = function(filepath) { - var poster = filepath.replace('.3gp', '.jpg'); + var poster = filepath.replace(/\.(?:3gp|mp4|webm)$/, '.jpg'); this.video.delete(filepath).onerror = function(e) { console.warn( diff --git a/gaia/apps/camera/js/views/viewfinder.js b/gaia/apps/camera/js/views/viewfinder.js index ed6f508a..d89e3c23 100755 --- a/gaia/apps/camera/js/views/viewfinder.js +++ b/gaia/apps/camera/js/views/viewfinder.js @@ -205,6 +205,9 @@ module.exports = View.extend({ */ updatePreview: function(preview, sensorAngle, mirrored) { if (!preview) { return; } + // An embedded app can initialize before its window is visible. + this.getSize(); + if (!this.width || !this.height) { return; } var aspect; // Invert dimensions if the camera's `sensorAngle` is diff --git a/gaia/apps/communications/dialer/js/telephony_helper.js b/gaia/apps/communications/dialer/js/telephony_helper.js index 6d7b7052..cdf2ce0c 100755 --- a/gaia/apps/communications/dialer/js/telephony_helper.js +++ b/gaia/apps/communications/dialer/js/telephony_helper.js @@ -25,6 +25,19 @@ var TelephonyHelper = (function() { return Promise.reject(); } + // The platform adapter owns network checks on Tundra and Android. + if (window.VulpesPlatformDial) { + return window.VulpesPlatformDial(sanitizedNumber.split(',')[0]).then(function(obj) { + if (window.TelephonyCall && obj instanceof TelephonyCall) { + installHandlers(obj, sanitizedNumber, false, cardIndex, + oncall, onconnected, ondisconnected); + } else if (oncall) { + oncall(); + } + return obj; + }); + } + var conn = navigator.mozMobileConnections && navigator.mozMobileConnections[cardIndex]; @@ -170,11 +183,13 @@ var TelephonyHelper = (function() { call.addEventListener('connected', onconnected); call.addEventListener('disconnected', function handleDisconnect(evt) { - loadTelephonyMessages(function() { - TelephonyMessages.handleDisconnect( - evt.call.disconnectedReason, number, emergencyOnly - ); - }); + if (evt.call.disconnectedReason) { + loadTelephonyMessages(function() { + TelephonyMessages.handleDisconnect( + evt.call.disconnectedReason, number, emergencyOnly + ); + }); + } ondisconnected && ondisconnected(); }); } diff --git a/gaia/apps/gallery/js/MetadataParser.js b/gaia/apps/gallery/js/MetadataParser.js index 43607764..87fba101 100755 --- a/gaia/apps/gallery/js/MetadataParser.js +++ b/gaia/apps/gallery/js/MetadataParser.js @@ -651,25 +651,30 @@ var metadataParser = (function() { function videoMetadataParser(file, metadataCallback, errorCallback) { var metadata = {}; - var videofilename = file.name.replace('.jpg', '.3gp'); - metadata.video = videofilename; - - var getreq = videostorage.get(videofilename); - getreq.onerror = function() { - errorCallback('cannot get video file: ' + videofilename); - }; - getreq.onsuccess = function() { - var videofile = getreq.result; - getVideoRotation(videofile, function(rotation) { - if (typeof rotation === 'number') { - metadata.rotation = rotation; + var extensions = ['webm', '3gp', 'mp4']; + function findVideo() { + var videofilename = file.name.replace(/\.jpg$/, '.' + extensions.shift()); + var getreq = videostorage.get(videofilename); + getreq.onerror = function() { + if (extensions.length) findVideo(); + else errorCallback('cannot get video file: ' + videofilename); + }; + getreq.onsuccess = function() { + metadata.video = videofilename; + if (/\.webm$/.test(videofilename)) { + metadata.rotation = 0; getVideoThumbnailAndSize(); + return; } - else if (typeof rotation === 'string') { - errorCallback('Video rotation:', rotation); - } - }); - }; + getVideoRotation(getreq.result, function(rotation) { + if (typeof rotation === 'number') { + metadata.rotation = rotation; + getVideoThumbnailAndSize(); + } else errorCallback('Video rotation: ' + rotation); + }); + }; + } + findVideo(); function getVideoThumbnailAndSize() { var url = URL.createObjectURL(file); diff --git a/gaia/apps/settings/js/modules/date_time.js b/gaia/apps/settings/js/modules/date_time.js index 42eae938..300c053c 100755 --- a/gaia/apps/settings/js/modules/date_time.js +++ b/gaia/apps/settings/js/modules/date_time.js @@ -139,10 +139,6 @@ define(function(require) { */ DateTime.prototype._init = function() { this._mozTime = window.navigator.mozTime; - if (!this._mozTime) { - console.error('Could not get window.navigator.mozTime'); - return; - } this._boundSetTimeAutoEnabled = function(event) { this._clockAutoEnabled = event.settingValue; @@ -320,9 +316,7 @@ define(function(require) { switch (type) { case 'date': tokens = value.split('-'); - newDate.setYear(tokens[0]); - newDate.setMonth(tokens[1] - 1); - newDate.setDate(tokens[2]); + newDate.setFullYear(Number(tokens[0]), Number(tokens[1]) - 1, Number(tokens[2])); break; case 'time': @@ -331,7 +325,8 @@ define(function(require) { newDate.setMinutes(parseInt(tokens[1])); break; } - this._mozTime.set(newDate); + if (!this._mozTime) return; + return this._mozTime.set(newDate); }; /** diff --git a/gaia/apps/settings/js/modules/dialog_manager.js b/gaia/apps/settings/js/modules/dialog_manager.js index bc59abdc..1692d23d 100755 --- a/gaia/apps/settings/js/modules/dialog_manager.js +++ b/gaia/apps/settings/js/modules/dialog_manager.js @@ -99,37 +99,35 @@ define(function(require) { _transit: function dm__transit(method, dialog, options) { var promise = new Promise(function(resolve) { var panel = dialog.panel; - - panel.addEventListener('transitionend', function paintWait(evt) { - if ((method === 'close' || method === 'open') && - evt.propertyName === 'visibility') { - // After transition, we have to `hide` the panel, otherwise - // the panel would still exist on the layer and would block - // the scrolling event. - if (method === 'close') { - panel.hidden = true; - } - panel.removeEventListener('transitionend', paintWait); - resolve(); - } - }); - - // Before transition, we have to `show` the panel, otherwise - // the panel before applying transition class. - if (method === 'open') { - panel.hidden = false; + var completed = false; + var fallback; + function finish(event) { + if (event && (event.target !== panel || event.propertyName !== 'visibility')) return; + if (completed) return; + completed = true; + clearTimeout(fallback); + panel.removeEventListener('transitionend', finish); + panel.removeEventListener('transitioncancel', finish); + if (method === 'close') panel.hidden = true; + resolve(); } - - // We need to apply class later otherwise Gecko can't apply - // this transition and 150ms is an approximate number after doing - // several rounds of manual tests. + if (method === 'open') panel.hidden = false; setTimeout(function() { - if (method === 'open') { - // Let's unhide the panel first - panel.classList.add('current'); - } else { - panel.classList.remove('current'); + panel.addEventListener('transitionend', finish); + panel.addEventListener('transitioncancel', finish); + panel.classList.toggle('current', method === 'open'); + var style = window.getComputedStyle(panel); + function milliseconds(value) { + return parseFloat(value) * (value.trim().endsWith('ms') ? 1 : 1000) || 0; } + var durations = style.transitionDuration.split(',').map(milliseconds); + var delays = style.transitionDelay.split(',').map(milliseconds); + var duration = Math.max.apply(Math, durations.map(function(value, index) { + return value + delays[index % delays.length]; + })); + // Reduced motion, display changes or interrupted transitions may + // omit the visibility event. Always release the dialog queue. + fallback = setTimeout(finish, Math.max(0, duration) + 100); }, 150); }); return promise; diff --git a/gaia/apps/settings/js/modules/dialog_service.js b/gaia/apps/settings/js/modules/dialog_service.js index a9188715..8f96465c 100755 --- a/gaia/apps/settings/js/modules/dialog_service.js +++ b/gaia/apps/settings/js/modules/dialog_service.js @@ -190,29 +190,30 @@ define(function(require) { if ('#' + panelId === currentPanel) { defer.reject('You are showing the same panel #' + panelId); } else { - options.onWrapSubmit = function() { - DialogManager.close(dialog, 'submit', options) - .then(function(result) { - defer.resolve({ - type: 'submit', - value: result - }); + var opening; + var closing = false; + function close(type) { + if (closing) return; + closing = true; + // A tap during the opening animation must not run hide/show + // concurrently, or a queued dialog can inherit stale form values. + Promise.resolve(opening).then(function() { + return DialogManager.close(dialog, type, options); + }).then(function(result) { self._navigating = false; + defer.resolve({type: type, value: result}); self._execPendingRequest(); + }, function(error) { + // Panel validation rejects to keep the form open. Allow a + // correction or cancellation without rejecting the show promise. + closing = false; + dialogDOM.dispatchEvent(new CustomEvent('dialogvalidationfailed', { + detail: error + })); }); - }; - - options.onWrapCancel = function() { - DialogManager.close(dialog, 'cancel', options) - .then(function(result) { - defer.resolve({ - type: 'cancel', - value: result - }); - self._navigating = false; - self._execPendingRequest(); - }); - }; + } + options.onWrapSubmit = function() { close('submit'); }; + options.onWrapCancel = function() { close('cancel'); }; switch (options.type) { case 'alert': @@ -229,7 +230,17 @@ define(function(require) { break; } this._navigating = true; - DialogManager.open(dialog, options); + opening = DialogManager.open(dialog, options); + opening.catch(function(error) { + // A missing host API must not lock every later Settings dialog. + dialogDOM.classList.remove('current'); + dialogDOM.hidden = true; + var overlay = document.querySelector('.settings-dialog-overlay'); + if (overlay) overlay.hidden = true; + self._navigating = false; + defer.reject(error); + self._execPendingRequest(); + }); } } diff --git a/gaia/apps/settings/js/modules/settings_service.js b/gaia/apps/settings/js/modules/settings_service.js index 4b81f1cf..767adbef 100755 --- a/gaia/apps/settings/js/modules/settings_service.js +++ b/gaia/apps/settings/js/modules/settings_service.js @@ -246,6 +246,17 @@ define(function(require) { // directly Settings._currentPanel = '#' + panelId; callback(); + }).catch(function(error) { + console.error('Settings panel ' + panelId + ':', error); + newPanelElement.classList.remove('current'); + if (currentPanelElement) currentPanelElement.classList.add('current'); + // Release the navigation lock even when a hardware query fails. + callback(error); + if (window.VulpesGaiaUI) { + window.VulpesGaiaUI.notice(document.documentElement.lang.indexOf('fr') === 0 ? + 'Ce panneau n’a pas pu être chargé. Vous pouvez revenir aux autres paramètres.' : + 'This panel could not be loaded. You can return to the other settings.'); + } }); }); }); diff --git a/gaia/apps/settings/js/panels/app_permissions_list/app_permissions_list.js b/gaia/apps/settings/js/panels/app_permissions_list/app_permissions_list.js index e5f3b161..7e01cc98 100755 --- a/gaia/apps/settings/js/panels/app_permissions_list/app_permissions_list.js +++ b/gaia/apps/settings/js/panels/app_permissions_list/app_permissions_list.js @@ -103,9 +103,10 @@ define(function(require) { * @access public */ _onAppChoose: function pl__onAppChoose(evt) { - if (evt.target.dataset && evt.target.dataset.appIndex) { + var target = evt.target.closest('[data-app-index]'); + if (target && this._listRoot.contains(target)) { SettingsService.navigate('appPermissions-details', { - app: this._apps[evt.target.dataset.appIndex], + app: this._apps[target.dataset.appIndex], permissionsTable: this._permissionTable }); } @@ -262,7 +263,7 @@ define(function(require) { * @return {Bool} */ _isExplicitPerm: function pl_isExplicitPerm(perm) { - return mozPerms.isExplicit(perm, this._currentApp.manifestURL, + return mozPerms && mozPerms.isExplicit(perm, this._currentApp.manifestURL, this._currentApp.origin, false); }, diff --git a/gaia/apps/settings/js/panels/help/support.js b/gaia/apps/settings/js/panels/help/support.js index 24a2e565..01c77d96 100755 --- a/gaia/apps/settings/js/panels/help/support.js +++ b/gaia/apps/settings/js/panels/help/support.js @@ -21,7 +21,7 @@ define(function(require) { * refer to the JSON File Data. */ this._callSupportInfo = null; - var url = 'http://support.mozilla.org/products/firefox-os'; + var url = 'https://vulpes-os.org/'; this._elements.userGuide.addEventListener('click', () => { this._elements.userGuide.blur(); var activity = new window.MozActivity({ @@ -55,7 +55,10 @@ define(function(require) { .then(function loadSupportInfo(data) { this._supportInfo = data; callback(this._supportInfo); - }.bind(this)); + }.bind(this)).catch(function(error) { + console.warn('Support information:', error); + callback({onlinesupport: {title: 'Vulpes OS', href: 'https://vulpes-os.org/'}, callsupport: []}); + }); }, /** * create support url items @@ -103,7 +106,7 @@ define(function(require) { _renderSupportInfo: function support_renderSupportInfo(result) { var onlineSupportInfo = null; var onlineSupportTitle = result['support.onlinesupport.title']; - if (onlineSupportTitle !== '') { + if (typeof onlineSupportTitle === 'string' && onlineSupportTitle !== '') { onlineSupportInfo = { title: onlineSupportTitle }; onlineSupportInfo.href = result['support.onlinesupport.href']; this._setOnlineSupportInfo(onlineSupportInfo); @@ -114,7 +117,7 @@ define(function(require) { // Check to see if we have a title for the first support number. var callSupport1Title = result['support.callsupport1.title']; // If we have a title we'll go ahead and load the href for it too. - if (callSupport1Title !== '') { + if (typeof callSupport1Title === 'string' && callSupport1Title !== '') { this._callSupportInfo = [ { 'title': callSupport1Title, @@ -125,7 +128,7 @@ define(function(require) { // Now check to see if we have a title for the second // support number. If we do, we'll load it's href as well. var callSupport2Title = result['support.callsupport2.title']; - if (callSupport2Title !== '') { + if (typeof callSupport2Title === 'string' && callSupport2Title !== '') { this._callSupportInfo.push({ 'title': callSupport2Title, 'href': result['support.callsupport2.href'] diff --git a/gaia/apps/settings/js/panels/screen_lock_passcode/panel.js b/gaia/apps/settings/js/panels/screen_lock_passcode/panel.js index b13186d2..d87a0810 100755 --- a/gaia/apps/settings/js/panels/screen_lock_passcode/panel.js +++ b/gaia/apps/settings/js/panels/screen_lock_passcode/panel.js @@ -66,8 +66,11 @@ define(function(require) { }, _bindEvents: function() { - this._elements.passcodeInput.onkeypress = - this._onPasscodeInputKeypress.bind(this); + this._elements.passcodeInput.type = 'password'; + this._elements.passcodeInput.inputMode = 'numeric'; + this._elements.passcodeInput.maxLength = 8; + this._elements.passcodeInput.oninput = + this._onPasscodeInput.bind(this); this._elements.passcodeContainer.onclick = this._onPasscodeContainerClick.bind(this); }, @@ -77,37 +80,14 @@ define(function(require) { this._elements.passcodeInput.focus(); }, - _onPasscodeInputKeypress: function(evt) { - // key code for key strokes from the keypad are 0 (numbers) and 8 - // (backspace). Filter out the events that are not from the keypad. - var keyCode = evt.keyCode; - if (keyCode !== 0 && keyCode !== 8) { - return; + _onPasscodeInput: function() { + // Android IMEs commit text without legacy keypress/charCode events. + var value = this._elements.passcodeInput.value.replace(/[^0-9]/g, '').slice(0, 8); + this._hideErrorMessage(); + if (this._elements.panel.dataset.passcodeStatus === 'success') { + this._resetPasscodeStatus(); } - - evt.preventDefault(); - - if (this._passcodeBuffer === '') { - this._hideErrorMessage(); - } - - var code = evt.charCode; - if (code !== 0 && (code < 0x30 || code > 0x39)) { - return; - } - - var key = String.fromCharCode(code); - if (evt.charCode === 0) { - if (this._passcodeBuffer.length > 0) { - this._passcodeBuffer = this._getPasscodeBuffer('all'); - if (this._elements.panel.dataset.passcodeStatus === 'success') { - this._resetPasscodeStatus(); - } - } - } else if (this._passcodeBuffer.length < 8) { - this._passcodeBuffer += key; - } - + this._passcodeBuffer = value; this._updatePassCodeUI(); this._processPasscode(); }, @@ -134,6 +114,7 @@ define(function(require) { }, _updatePassCodeUI: function() { + this._elements.passcodeInput.value = this._passcodeBuffer; for (var i = 0; i < 8; i++) { if (i < this._passcodeBuffer.length) { this._elements.passcodeDigits[i].dataset.dot = true; diff --git a/gaia/apps/settings/js/panels/simcard_manager/simcard_manager.js b/gaia/apps/settings/js/panels/simcard_manager/simcard_manager.js index 3958d8b4..261320af 100755 --- a/gaia/apps/settings/js/panels/simcard_manager/simcard_manager.js +++ b/gaia/apps/settings/js/panels/simcard_manager/simcard_manager.js @@ -304,7 +304,7 @@ define(function(require) { */ _updateSimSettingsUI: function scm__updateSimSettingsUI() { var firstCardInfo = this._simcards[0].getInfo(); - var secondCardInfo = this._simcards[1].getInfo(); + var secondCardInfo = (this._simcards[1] ? this._simcards[1].getInfo() : {absent: true}); var hidden = firstCardInfo.absent && secondCardInfo.absent || this._isAirplaneMode; @@ -322,7 +322,7 @@ define(function(require) { */ _updateSelectOptionsUI: function scm__updateSelectOptionsUI() { var firstCardInfo = this._simcards[0].getInfo(); - var secondCardInfo = this._simcards[1].getInfo(); + var secondCardInfo = (this._simcards[1] ? this._simcards[1].getInfo() : {absent: true}); // two cards all are not absent, we have to update separately if (!firstCardInfo.absent && !secondCardInfo.absent) { diff --git a/gaia/apps/settings/js/panels/wifi_status/panel.js b/gaia/apps/settings/js/panels/wifi_status/panel.js index e992f3f6..aa325547 100755 --- a/gaia/apps/settings/js/panels/wifi_status/panel.js +++ b/gaia/apps/settings/js/panels/wifi_status/panel.js @@ -41,7 +41,8 @@ define(function(require) { var info = wifiManager.connectionInformation || {}; elements.ip.textContent = info.ipAddress || ''; document.l10n.setAttributes(elements.speed, - 'linkSpeedMbs', { linkSpeed: info.linkSpeed }); + Number.isFinite(info.linkSpeed) ? 'linkSpeedMbs' : 'unavailable', + Number.isFinite(info.linkSpeed) ? { linkSpeed: info.linkSpeed } : {}); } }); }; diff --git a/gaia/apps/verticalhome/js/stores/item.js b/gaia/apps/verticalhome/js/stores/item.js index e5fd249c..5da09703 100755 --- a/gaia/apps/verticalhome/js/stores/item.js +++ b/gaia/apps/verticalhome/js/stores/item.js @@ -439,7 +439,8 @@ */ populate: function(callback) { this.initSources(function(entries) { - this.deferredSave(entries, callback); + // The first render waits for this write; deferring it waits for that render. + this.save(entries, callback); }.bind(this)); }, diff --git a/gaia/apps/video/js/video.js b/gaia/apps/video/js/video.js index ebeaf7a7..ab1cce7c 100755 --- a/gaia/apps/video/js/video.js +++ b/gaia/apps/video/js/video.js @@ -102,7 +102,7 @@ var currentLayoutMode; var pendingUpdateTitleText = false; // Videos recorded by our own camera have filenames of this form -var FROMCAMERA = /DCIM\/\d{3}MZLLA\/VID_\d{4}\.3gp$/; +var FROMCAMERA = /DCIM\/\d{3}MZLLA\/VID_\d{4}\.(?:3gp|mp4|webm)$/; // We have a single instance of the loading checker because it is used // across functions @@ -664,7 +664,7 @@ function deleteFile(filename) { // If we're deleting a video file recorded by our camera, // we also need to delete the poster image associated with // that video. - var postername = filename.replace('.3gp', '.jpg'); + var postername = filename.replace(/\.(?:3gp|mp4|webm)$/, '.jpg'); navigator.getDeviceStorage('pictures'). delete(postername); } diff --git a/gaia/apps/wallpaper/style/wallpaper.css b/gaia/apps/wallpaper/style/wallpaper.css index 0a581104..51e131ed 100755 --- a/gaia/apps/wallpaper/style/wallpaper.css +++ b/gaia/apps/wallpaper/style/wallpaper.css @@ -70,3 +70,6 @@ html[dir="rtl"] .wallpaper { margin: 0 0.5rem; } + +/* Keep preview tiles aligned when source wallpapers have different ratios. */ +#wallpapers .wallpaper { aspect-ratio: 2 / 3; object-fit: cover; display: block; } diff --git a/gaia/shared/js/download/download_helper.js b/gaia/shared/js/download/download_helper.js index 70ff7080..58427d5e 100755 --- a/gaia/shared/js/download/download_helper.js +++ b/gaia/shared/js/download/download_helper.js @@ -113,6 +113,10 @@ var DownloadHelper = (function() { */ function getBlob(download) { var req = new Request(); + if (download.vulpesDownload) { + download.getBlob().then(function(blob) { req.done(blob); }, function(error) { req.failed(error); }); + return req; + } var storage = getVolume(download.storageName); var storeAvailableReq = storage.available(); @@ -394,6 +398,10 @@ var DownloadHelper = (function() { */ function remove(download) { var req = new Request(); + if (download.vulpesDownload) { + navigator.mozDownloadManager.remove(download).then(function() { req.done(download); }, function(error) { req.failed(error); }); + return req; + } var incompleteDownload = download.state !== 'succeeded'; // If is not done, use download manager to remove it, // otherwise, deal with the datastore. diff --git a/host/Downloads.sys.mjs b/host/Downloads.sys.mjs new file mode 100644 index 00000000..8e62a4c0 --- /dev/null +++ b/host/Downloads.sys.mjs @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: MPL-2.0 +// Download identities belong to Gecko; paths supplied by content are never used. +export class GaiaDownloads { + constructor() {this.ids=new WeakMap();this.items=new Map();} + async request(app, params={}) { + if(!['settings','system'].includes(app.id))throw Error('PERMISSION_DENIED'); + if(!['list','pause','resume','remove','blob'].includes(params.operation))throw Error('INVALID_VALUE'); + const {Downloads}=ChromeUtils.importESModule('resource://gre/modules/Downloads.sys.mjs'); + const list=await Downloads.getList(Downloads.PUBLIC); + const entries=await list.getAll(); + this.items.clear(); + const rows=entries.map(item=>{ + if(!this.ids.has(item))this.ids.set(item,Services.uuid.generateUUID().toString()); + const id=this.ids.get(item);this.items.set(id,item); + return {id,url:item.source.url,path:item.target.path,startTime:+item.startTime, + contentType:item.contentType||'application/octet-stream',currentBytes:item.currentBytes,totalBytes:item.totalBytes, + state:item.succeeded?'succeeded':item.stopped?'stopped':'downloading'}; + }); + if(params.operation==='list')return rows; + const item=this.items.get(params.id);if(!item)throw Error('DOWNLOAD_NOT_FOUND'); + if(params.operation==='blob') { + if(!item.succeeded)throw Error('DOWNLOAD_NOT_COMPLETE'); + return File.createFromFileName(item.target.path); + } + if(params.operation==='pause')await item.cancel(); + if(params.operation==='resume')await item.start(); + if(params.operation==='remove') {await item.cancel();await item.finalize(true);if(item.succeeded)await IOUtils.remove(item.target.path,{ignoreAbsent:true});await list.remove(item);} + return true; + } +} diff --git a/host/Input.sys.mjs b/host/Input.sys.mjs index 6c73c982..d4b8fc75 100644 --- a/host/Input.sys.mjs +++ b/host/Input.sys.mjs @@ -1,36 +1 @@ -// Only the bundled keyboard can edit the field reported by a content actor. -export class Input { - constructor(runtime) { this.runtime=runtime; this.focus=null; this.sequence=0; } - changed(actor, state) { - const app=this.runtime.identity(actor); - if (!app || app.id==='keyboard') return; - if (!state) { if(this.focus?.actor===actor) this.hide(); return; } - this.focus={actor,state:{...state,id:++this.sequence}}; - this.runtime.broadcast('input-focus',this.focus.state,'system'); - this.runtime.broadcast('input-focus',this.focus.state,'keyboard'); - } - hide() { - this.focus=null; - this.runtime.broadcast('input-focus',null,'system'); - this.runtime.broadcast('input-focus',null,'keyboard'); - } - cleanup(actor) { if(this.focus?.actor===actor) this.hide(); } - async request(actor, p) { - const app=this.runtime.identity(actor); - if (!app || !['keyboard','system'].includes(app.id)) throw Error('PERMISSION_DENIED'); - if (p.operation==='hide') { this.hide();return null; } - if(app.id!=='keyboard') throw Error('PERMISSION_DENIED'); - if(p.operation==='state') return this.focus?.state || null; - if(p.operation==='height') { - if(!Number.isFinite(p.height) || p.height<0 || p.height>600) throw Error('INVALID_VALUE'); - this.runtime.broadcast('input-height',{height:p.height},'system');return null; - } - if(!this.focus || p.id!==this.focus.state.id) throw Error('INPUT_CONTEXT_EXPIRED'); - const context=this.focus; - const result=await context.actor.sendQuery('Vulpes:Edit',p); - // An application can close or focus another field while the edit is pending. - if(this.focus!==context) throw Error('INPUT_CONTEXT_EXPIRED'); - Object.assign(context.state,result); - return context.state; - } -} +export {Input} from 'resource://vulpes/services/input.mjs'; diff --git a/host/Messages.sys.mjs b/host/Messages.sys.mjs index 02a88f0b..6f6730e2 100644 --- a/host/Messages.sys.mjs +++ b/host/Messages.sys.mjs @@ -1,10 +1,11 @@ // Desktop message store. Radio operations fail explicitly; no synthetic SMS. export class Messages { - constructor(storage, radio = null, notify = () => {}, radioStatus = null) { + constructor(storage, radio = null, notify = () => {}, radioStatus = null, mms = null) { this.storage = storage; this.radio = radio; this.notify = notify; this.radioStatus = radioStatus; + this.mms = mms; this.queue = Promise.resolve(); } receive(incoming) { @@ -14,7 +15,8 @@ export class Messages { if (existing) return existing; const id=Math.max(Date.now(),...records.map(m=>Number(m.id)+1 || 0)); const record={id,threadId:records.find(m=>(m.sender||m.receiver)===incoming.sender)?.threadId || id+1, - receipt:incoming.receipt,type:'sms',sender:incoming.sender,receiver:null, + receipt:incoming.receipt,type:incoming.type || 'sms',sender:incoming.sender,receiver:null, + ...(incoming.type==='mms' ? {receivers:incoming.receivers || [],subject:incoming.subject || '',smil:incoming.smil || '',attachments:incoming.attachments || [],deliveryInfo:[]} : {}), body:incoming.body,timestamp:incoming.timestamp,delivery:'received',read:false}; records.push(record); await this.storage.run('vulpes-private-messages','put',[records,'records']); @@ -24,14 +26,16 @@ export class Messages { return work; } refreshOutgoing() { - if (!this.radioStatus) return Promise.resolve(); + if (!this.radioStatus && !this.mms) return Promise.resolve(); const work=this.queue.then(async()=>{ const records=(await this.storage.run('vulpes-private-messages','get',['records'])).value || []; for (const message of records.filter(m=>m.delivery==='sending' && m.radioPath)) { - const result=await this.radioStatus(message.radioPath); + const result=await (message.type==='mms' ? this.mms.status(message.radioPath) : this.radioStatus(message.radioPath)); if (!['sent','failed'].includes(result.state)) continue; message.delivery=result.state==='sent'?'sent':'error'; message.deliveryStatus=result.state==='sent'?'not-applicable':'error'; + if(result.error) message.error=result.error; + if(message.type==='mms') message.deliveryInfo=message.receivers.map(receiver=>({receiver,deliveryStatus:message.deliveryStatus,readStatus:'not-applicable'})); await this.storage.run('vulpes-private-messages','put',[records,'records']); this.notify(result.state,structuredClone(message)); } @@ -104,7 +108,34 @@ export class Messages { this.notify('sent', structuredClone(record)); return record; } - if (['send', 'sendMMS', 'retrieveMMS', 'getSmscAddress', 'setSmscAddress'].includes(operation)) + if (operation === 'sendMMS' && this.mms) { + const payload=args[0]; + this.mms.validate(payload); + const receivers=payload.receivers.slice(); + const participants=receivers.slice().sort().join(','); + const existing=records.find(m=>(m.type==='mms' && m.delivery!=='received' ? m.receivers.slice().sort().join(',') : m.receiver || m.sender)===participants); + const record={id:nextId++,threadId:existing?.threadId || nextId++,type:'mms',sender:null, + receivers,subject:payload.subject || '',smil:payload.smil || '',attachments:payload.attachments || [],body:payload.subject || '', + timestamp:Date.now(),delivery:'sending',deliveryStatus:'pending',read:true, + deliveryInfo:receivers.map(receiver=>({receiver,deliveryStatus:'pending',readStatus:'not-applicable'}))}; + records.push(record); + await this.storage.run('vulpes-private-messages','put',[records,'records']); + this.notify('sending',structuredClone(record)); + try { + const submitted=await this.mms.send(payload); + record.radioPath=submitted.path; + await this.storage.run('vulpes-private-messages','put',[records,'records']); + return record; + } catch(error) { + record.delivery='error';record.deliveryStatus='error';record.error=error.message; + record.deliveryInfo=receivers.map(receiver=>({receiver,deliveryStatus:'error',readStatus:'not-applicable'})); + await this.storage.run('vulpes-private-messages','put',[records,'records']); + this.notify('failed',structuredClone(record));throw error; + } + } + if (['sendMMS', 'retrieveMMS'].includes(operation)) + throw new Error('NotSupportedError'); + if (['send', 'getSmscAddress', 'setSmscAddress'].includes(operation)) throw new Error('RadioDisabledError'); if (operation === 'getThreads') { const threads = new Map(); @@ -115,7 +146,7 @@ export class Messages { id: message.threadId, body: message.body, timestamp: message.timestamp, - participants: [message.sender || message.receiver], + participants: message.delivery==='received' ? [message.sender] : message.type==='mms' ? message.receivers : [message.receiver], unreadCount: records.filter((m) => m.threadId === message.threadId && !m.read).length, lastMessageType: message.type, }); diff --git a/host/NativeCamera.sys.mjs b/host/NativeCamera.sys.mjs index 2ae36500..4afb80f1 100644 --- a/host/NativeCamera.sys.mjs +++ b/host/NativeCamera.sys.mjs @@ -17,8 +17,9 @@ export class NativeCamera { return await Promise.race([ (async () => { const pipe=session.process.stdout; - const sequence=metadata ? null : await pipe.readUint32(); - const length=await pipe.readUint32(); + const header=new DataView(await pipe.read(metadata ? 4 : 8)); + const sequence=metadata ? null : header.getUint32(0,true); + const length=header.getUint32(metadata ? 0 : 4,true); if(metadata) { if(length>65536) throw Error('CAMERA_INVALID_FORMAT'); return pipe.readJSON(length); diff --git a/host/Platform.sys.mjs b/host/Platform.sys.mjs index 84fe5cfb..4b8503cc 100644 --- a/host/Platform.sys.mjs +++ b/host/Platform.sys.mjs @@ -1,9 +1,12 @@ +import {receivedMms} from 'resource://vulpes/services/mms.mjs'; import { authorizePlatform } from 'resource://vulpes/services/platform.mjs'; import { tundraCapabilities, + tundraBluetooth, tundraCallAudio, tundraHardware, tundraRadio, + tundraMms, tundraSnapshot, tundraWifi, } from 'resource://vulpes/host/Tundra.sys.mjs'; @@ -48,6 +51,25 @@ export class Platform { } async init() { this.radioQueue = Promise.resolve(); + if (Services.env.get('VULPES_TUNDRA') === '1') { + let shown = null; + const pollBluetooth = async () => { + try { + const state = await tundraBluetooth({operation:'status'}); + const pending = state.pairing?.[0]; + const app = this.runtime.apps.find(a=>a.id==='bluetooth'); + if (pending && app && pending.id!==shown) { + shown=pending.id; + this.runtime.broadcast('launch',{manifestURL:app.manifestURL,origin:app.origin, + url:app.origin+'/onpair.html',timestamp:Date.now()},'system'); + } + if (!pending) shown=null; + } catch (_) {} + setTimeout(pollBluetooth,1500); + }; + pollBluetooth(); + } + this.notificationPath = PathUtils.join(PathUtils.profileDir,'vulpes-notifications.json'); let notifications = []; try { notifications = await IOUtils.readJSON(this.notificationPath); } catch (_) {} @@ -80,6 +102,16 @@ export class Platform { await tundraRadio('ackMessages',{ids:[row.receipt]}); } } catch(error) {console.error('SMS inbox:',error.message);} + try { + const rows=await tundraMms('incoming'); + const app=this.runtime.apps.find(a=>a.id==='sms'); + for(const row of rows) { + const message=await this.runtime.messages.receive(receivedMms(row)); + await this.gaiaNotification(app,{operation:'show',title:message.sender,body:message.body || 'MMS',tag:'sms-'+message.id,data:{threadId:message.threadId}}); + this.runtime.broadcast('sms-received',message,'sms'); + await tundraMms('ack',{ids:[row.receipt]}); + } + } catch(error) {console.error('MMS inbox:',error.message);} setTimeout(inbox,2000); }; inbox(); @@ -136,48 +168,39 @@ export class Platform { !request.secure || request.sharingScreen || request.sharingAudio || - request.audioInputDevices?.length || - !request.videoInputDevices?.length + !(request.videoInputDevices?.length || request.audioInputDevices?.length) ) { deny(); return; } const principal = global.documentPrincipal; - let permission = Services.perms.testExactPermissionFromPrincipal(principal, 'camera'); - if (permission === Ci.nsIPermissionManager.UNKNOWN_ACTION) { - const allowed = Services.prompt.confirm( - browser.ownerDocument.defaultView, - 'Vulpes — Photo', - 'Autoriser Photo à utiliser la webcam ? / Allow Camera to use your webcam?', - ); - permission = allowed - ? Ci.nsIPermissionManager.ALLOW_ACTION - : Ci.nsIPermissionManager.DENY_ACTION; - Services.perms.addFromPrincipal(principal, 'camera', permission); + const video = request.videoInputDevices?.[0], audio = request.audioInputDevices?.[0]; + for (const [device, kind, message] of [ + [video, 'camera', 'Autoriser Photo à utiliser la caméra ? / Allow Camera to use your camera?'], + [audio, 'microphone', 'Autoriser Photo à enregistrer le son ? / Allow Camera to record audio?'] + ]) { + if (!device) continue; + let permission = Services.perms.testExactPermissionFromPrincipal(principal, kind); + if (permission === Ci.nsIPermissionManager.UNKNOWN_ACTION) { + permission = Services.prompt.confirm(browser.ownerDocument.defaultView, 'Vulpes — Photo', message) + ? Ci.nsIPermissionManager.ALLOW_ACTION : Ci.nsIPermissionManager.DENY_ACTION; + Services.perms.addFromPrincipal(principal, kind, permission); + } + if (permission !== Ci.nsIPermissionManager.ALLOW_ACTION) { deny(); return; } } - if (permission !== Ci.nsIPermissionManager.ALLOW_ACTION) { - deny(); - return; - } - if (!(await actor.checkOSPermission(true, false, false))) { - deny(); - return; - } - // Gecko consumes this one-shot grant when allocating the real device. - // Use its top-level principal, as Firefox's WebRTC permission handler does. + if (!(await actor.checkOSPermission(!!video, !!audio, false))) { deny(); return; } const topPrincipal = browser.browsingContext.currentWindowGlobal.documentPrincipal; - Services.perms.addFromPrincipal( - topPrincipal, - 'MediaManagerVideo', - Ci.nsIPermissionManager.ALLOW_ACTION, - Ci.nsIPermissionManager.EXPIRE_SESSION, - ); - const device = request.videoInputDevices[0]; - actor.activateDevicePerm(request.windowID, device.mediaSource, device.rawId); + const devices = []; + for (const [device, kind] of [[video, 'MediaManagerVideo'], [audio, 'MediaManagerAudio']]) { + if (!device) continue; + Services.perms.addFromPrincipal(topPrincipal, kind, Ci.nsIPermissionManager.ALLOW_ACTION, Ci.nsIPermissionManager.EXPIRE_SESSION); + actor.activateDevicePerm(request.windowID, device.mediaSource, device.rawId); + devices.push(device.deviceIndex); + } actor.sendAsyncMessage('webrtc:Allow', { callID: request.callID, windowID: request.windowID, - devices: [request.videoInputDevices[0].deviceIndex], + devices, }); } async request(actor, app, operation, params) { @@ -193,6 +216,9 @@ export class Platform { if (operation === 'snapshot') return tundraSnapshot(); if (operation === 'power') return tundraHardware({operation:'power', action:params.action}); if (operation === 'wifi') return tundraWifi(params); + if (operation === 'deviceSettings') return tundraRadio('deviceSettings', params); + if (operation === 'mobileSettings') return tundraRadio('mobileSettings', params); + if (operation === 'bluetooth') return tundraBluetooth(params); if (['callHistory','ackCallHistory'].includes(operation)) return this.radio(operation, params); if (operation === 'dial') return this.radio('dial', params); if (operation === 'hangup') return tundraRadio('hangup', params); diff --git a/host/Runtime.sys.mjs b/host/Runtime.sys.mjs index e328a542..381f999c 100644 --- a/host/Runtime.sys.mjs +++ b/host/Runtime.sys.mjs @@ -1,3 +1,4 @@ +import {mmsPayload} from 'resource://vulpes/services/mms.mjs'; import { setInterval, setTimeout, clearTimeout } from 'resource://gre/modules/Timer.sys.mjs'; import {Input} from 'resource://vulpes/host/Input.sys.mjs'; import { ScreenPower } from 'resource://vulpes/services/screen-power.mjs'; @@ -13,7 +14,7 @@ import { Messages } from 'resource://vulpes/host/Messages.sys.mjs'; import { Alarms } from 'resource://vulpes/host/Alarms.sys.mjs'; import { Platform } from 'resource://vulpes/host/Platform.sys.mjs'; import { Media } from 'resource://vulpes/host/Media.sys.mjs'; -import { tundraCallAudio, tundraHardware, tundraRadio } from 'resource://vulpes/host/Tundra.sys.mjs'; +import { tundraCallAudio, tundraHardware, tundraRadio, tundraMms } from 'resource://vulpes/host/Tundra.sys.mjs'; Cu.importGlobalProperties(['indexedDB']); // This is the only privileged owner of application identity and settings data. @@ -111,6 +112,7 @@ export const Runtime = { } Services.prefs.setStringPref('intl.accept_languages', this.values['language.current']); this.activities = new Activities({identity:actor=>this.identity(actor),apps:()=>this.apps, + choose:data=>this.broadcast('activity-choice',data,'system'), uuid:()=>Services.uuid.generateUUID().toString(), notify:(actor,type,data)=>actor.sendAsyncMessage('Vulpes:Event',{type,data}), launch:(target,path,{reload=false,entryPoint}={})=>this.broadcast(reload?'activity-launch':'launch',{manifestURL:target.manifestURL,origin:target.origin, @@ -123,6 +125,19 @@ export const Runtime = { const values = { ...this.values, ...Object.fromEntries(entries) }; await IOUtils.writeJSON(path, values, { tmpPath: path + '.tmp' }); this.values = values; + const prefMap={ + 'ui.click_hold_context_menus.delay':'ui.click_hold_context_menus.delay', + 'accessibility.monoaudio.enable':'accessibility.monoaudio.enable', + 'geolocation.enabled':'geo.enabled', + 'privacy.donottrackheader.enabled':'privacy.donottrackheader.enabled', + 'debug.paint-flashing.enabled':'nglayout.debug.paint_flashing', + 'apz.overscroll.enabled':'apz.overscroll.enabled' + }; + for(const [key,value] of entries) { + const pref=prefMap[key];if(!pref)continue; + if(typeof value==='boolean')Services.prefs.setBoolPref(pref,value); + else if(key==='ui.click_hold_context_menus.delay' && [400,800,1600].includes(value))Services.prefs.setIntPref(pref,value); + } if (entries.some(([key]) => key === 'language.current')) Services.prefs.setStringPref('intl.accept_languages', values['language.current']); if (Services.env.get('VULPES_TUNDRA') === '1') { @@ -156,6 +171,8 @@ export const Runtime = { : null, (type, message) => this.broadcast('sms-' + type, message, 'sms'), path => tundraRadio('messageState', {path}), + Services.env.get('VULPES_TUNDRA_RADIO') === '1' ? {validate:mmsPayload, + send:message=>tundraMms('send',mmsPayload(message)), status:path=>tundraMms('state',{path})} : null, ); this.media = new Media((data) => this.broadcast('mediachange', data)); this.platform = new Platform(this); @@ -301,9 +318,10 @@ export const Runtime = { } } if (typeof method !== 'string') return { error: { code: 'INVALID_REQUEST' } }; - if (['activities.subscribe','activities.finish','activities.cancel'].includes(method) || (method === 'activities.start' && isGaiaActivity(params))) { + if (['activities.subscribe','activities.finish','activities.cancel','activities.select'].includes(method) || (method === 'activities.start' && isGaiaActivity(params))) { try { - const result = method === 'activities.cancel' ? this.activities.cancel(actor) + const result = method === 'activities.select' ? this.activities.select(actor,params) + : method === 'activities.cancel' ? this.activities.cancel(actor) : method === 'activities.subscribe' ? this.activities.subscribe(actor) : method === 'activities.finish' ? this.activities.finish(actor,params) : await this.activities.start(actor,params); @@ -359,11 +377,28 @@ export const Runtime = { return { error: { code: error.message || 'VIEW_ERROR' } }; } } + if (method === 'storage.stats') { + try {const {storageStats}=ChromeUtils.importESModule('resource://vulpes/host/StorageStats.sys.mjs');return {result:await storageStats(app)};} + catch(error) {return {error:{code:error.message || 'STORAGE_ERROR'}};} + } + if (method === 'downloads.request') { + try { + const {GaiaDownloads}=ChromeUtils.importESModule('resource://vulpes/host/Downloads.sys.mjs'); + this.downloads ||= new GaiaDownloads(); + return {result:await this.downloads.request(app,params)}; + } catch(error) {return {error:{code:error.message || 'DOWNLOAD_ERROR'}};} + } + if (method === 'appearance.current') return { result: this.values['vulpes.theme'] || 'light' }; if (method === 'locale.current') return { result: this.values['language.current'] || 'en-US' }; if (method === 'settings.snapshot') return this.grants(app).includes('settings.read') ? { result: { ...this.values } } : { error: { code: 'PERMISSION_DENIED' } }; + if (method === 'settings.clockChanged') { + if(app.id !== 'settings' || Services.env.get('VULPES_TUNDRA') !== '1') return {error:{code:'PERMISSION_DENIED'}}; + Services.obs.notifyObservers(null, 'system-timezone-changed'); + return {result:true}; + } if (method.startsWith('settings.')) return this.settings.bindSession(app.id, this.grants(app))(request); if (method === 'desktop.idle') { @@ -517,7 +552,7 @@ export const Runtime = { try { const app = this.identity(actor); if (!app || (target && app.id !== target)) continue; - if (type === 'settingchange' && data.key !== 'language.current' && !this.grants(app).includes('settings.read')) continue; + if (type === 'settingchange' && !['language.current', 'vulpes.theme'].includes(data.key) && !this.grants(app).includes('settings.read')) continue; if ( type === 'mediachange' && !app.manifest.permissions?.['device-storage:' + data.type]?.access?.includes('read') diff --git a/host/SettingsAudit.sys.mjs b/host/SettingsAudit.sys.mjs new file mode 100644 index 00000000..952b0144 --- /dev/null +++ b/host/SettingsAudit.sys.mjs @@ -0,0 +1,133 @@ +// SPDX-License-Identifier: MPL-2.0 +// Settings navigation and bounded readback tests, enabled by a private marker. +export async function auditSettings({evaluate,wait,sleep,save}) { + const context=new Set(['wifi-status','wifi-auth','wifi-joinHidden','wifi-wps','wifi-enterCertificateNickname','call-cf-not-reachable-settings','call-cf-no-reply-settings','call-cf-mobile-busy-settings','call-cf-unconditional-settings','call-voiceMailSettings','hotspot-wifiSettings','screenLock-passcode','apn-editor','apn-list','appPermissions-details','addon-details','homescreen-details','frame','simpin-dialog','call-fdnList-add','call-barring-passcode-change','settings-alert-dialog','settings-confirm-dialog','settings-prompt-dialog','ums-partial-warning','template-messaging','full-developer-mode-warning','full-developer-mode-final-warning']); + await evaluate('system','ScreenManager.turnScreenOn(true);if(Service.query("locked"))await Service.request("unlock",{forcibly:true});await VulpesCompat.call("apps.launch",{manifestURL:"http://settings.localhost:8765/manifest.webapp"});return true;'); + await wait('settings','return document.readyState==="complete" && typeof require==="function";'); + const ids=await evaluate('settings','return Array.from(document.querySelectorAll("section[id]"),e=>e.id);'); + const report={scope:'navigation only; no destructive actions, SIM PIN attempts or subscriptions',panels:{}}; + for(const id of ids) { + if(context.has(id)){report.panels[id]={contextRequired:true};continue;} + try { + await evaluate('settings',`window.__vulpesDiagnostics=[];require(['modules/settings_service'],s=>s.navigate(${JSON.stringify(id)},${JSON.stringify(id==='messaging-details'?{cardIndex:0}:{})}));return true;`); + await wait('settings',`return document.querySelector('section.current')?.id===${JSON.stringify(id)};`,25000); + // Slow carrier queries continue after the panel appears. Wait for those + // controls before leaving, rather than flooding the radio worker while + // sweeping every panel in a few milliseconds. + if(id==='call')await wait('settings',"return !document.querySelector('#ril-callerId').disabled;",30000); + if(id==='call-forwarding')await wait('settings',"return !!document.querySelector('#call-forwarding .callForwardingUnconditional[aria-disabled=false]');",30000); + await sleep(450); + report.panels[id]=await evaluate('settings',`const p=document.getElementById(${JSON.stringify(id)});return {opened:true,title:p.querySelector('h1')?.textContent,unavailable:!!p.querySelector('[data-vulpes-unavailable]'),links:Array.from(p.querySelectorAll('a[href^="#"]'),e=>e.getAttribute('href')),controls:Array.from(p.querySelectorAll('input,select,gaia-switch,gaia-checkbox'),e=>({name:e.name||e.id||e.getAttribute('name'),disabled:!!e.disabled,hidden:!e.checkVisibility()})),errors:window.__vulpesDiagnostics};`); + } catch(error) { + report.panels[id]={opened:false,error:String(error),errors:await evaluate('settings','return window.__vulpesDiagnostics;')}; + await evaluate('settings','location.reload();return true;');await sleep(1500); + await wait('settings','return document.readyState==="complete" && typeof require==="function";'); + } + await save(report); + } + report.dialogs={}; + for(const [id,method,options] of [ + ['settings-confirm-dialog','confirm',{}],['settings-prompt-dialog','prompt',{}],['settings-alert-dialog','alert',{}], + ['wifi-auth','show',{network:{ssid:'Audit — not connected',security:['WPA-PSK']},security:'WPA-PSK',sl:3}], + ['wifi-status','show',{network:{ssid:'Audit — not connected',security:[]},security:'',sl:3}], + ['wifi-joinHidden','show',{}],['screenLock-passcode','show',{mode:'create'}] + ]) { + try { + await evaluate('settings',`window.__dialogResult=null;window.__vulpesDiagnostics=[];require(['modules/dialog_service'],d=>d.${method}(${JSON.stringify(method==='show'?id:'cancel')},${JSON.stringify(options)}).then(r=>window.__dialogResult=r,e=>window.__dialogResult={error:String(e)}));return true;`); + await wait('settings',`return document.getElementById(${JSON.stringify(id)}).classList.contains('current');`,3000); + await evaluate('settings',`const p=document.getElementById(${JSON.stringify(id)});const button=p.querySelector('button[type=reset]')||p.querySelector('button[type=submit]');if(button)button.click();else p.querySelector('gaia-header')?.dispatchEvent(new CustomEvent('action'));return true;`); + await wait('settings',`return !!window.__dialogResult;`,3000); + report.dialogs[id]=await evaluate('settings',`return {result:window.__dialogResult,errors:window.__vulpesDiagnostics,closed:!document.getElementById(${JSON.stringify(id)}).classList.contains('current')};`); + } catch(error) { + report.dialogs[id]={error:String(error),errors:await evaluate('settings','return window.__vulpesDiagnostics;')}; + await evaluate('settings','location.reload();return true;');await sleep(1500); + await wait('settings','return document.readyState==="complete" && typeof require==="function";'); + } + await save(report); + } + report.effects={}; + for(const [name,code] of Object.entries({ + storage:'return await VulpesCompat.call("storage.stats",{});', + modemReadback:'const read=await VulpesCompat.call("platform.mobileSettings",{action:"network"});if(typeof read.enabled!=="boolean")throw Error("Unknown data state");const after=await VulpesCompat.call("platform.mobileSettings",{action:"dataEnabled",enabled:read.enabled});return {same:read.enabled===after.enabled,registered:after.status,technology:after.technology};', + clockReadback:'const read=await VulpesCompat.call("platform.deviceSettings",{action:"clock"});const after=await VulpesCompat.call("platform.deviceSettings",{action:"setTimezone",timezone:read.timezone});return {same:read.timezone===after.timezone,timezone:after.timezone,automaticAvailable:after.automaticAvailable};' + })) { + try {report.effects[name]=await evaluate('settings',code);} + catch(error){report.effects[name]={error:String(error)};} + } + report.networkUi={}; + try { + await evaluate('settings',"window.__vulpesDiagnostics=[];require(['modules/settings_service'],s=>s.navigate('operator-settings'));return true;"); + await wait('settings',"return document.querySelector('#operator-settings.current select:not([disabled])')?.options.length>0;",7000); + report.networkUi.radio=await evaluate('settings',"const s=document.querySelector('#operator-settings select');s.dispatchEvent(new Event('change',{bubbles:true}));return {mode:s.value,choices:Array.from(s.options,o=>o.value)};"); + await wait('settings',"return !document.querySelector('#operator-settings select').disabled;",7000); + report.networkUi.radio.same=await evaluate('settings',`return document.querySelector('#operator-settings select').value===${JSON.stringify(report.networkUi.radio.mode)};`); + for(const type of ['default','mms']) { + await evaluate('settings',`require(['modules/settings_service'],s=>s.navigate('apn-settings'));return true;`); + await wait('settings',"return !!document.querySelector('#apn-settings.current [data-apn-type]');",4000); + await evaluate('settings',`document.querySelector('#apn-settings [data-apn-type=${type}]').click();return true;`); + await wait('settings',"return !!document.querySelector('#apn-list.current label.name');",7000); + await evaluate('settings',"document.querySelector('#apn-list label.name').dispatchEvent(new MouseEvent('mouseup',{bubbles:true}));return true;"); + await wait('settings',"return !!document.querySelector('#apn-editor.current .apn:not([disabled])');",7000); + report.networkUi[type]=await evaluate('settings',"const p=document.querySelector('#apn-editor');return {opened:true,apnPopulated:!!p.querySelector('.apn').value,passwordBlank:p.querySelector('.password').value==='',protocol:p.querySelector('.protocol').value,authentication:p.querySelector('.authtype').value,mmscEditable:!p.querySelector('.mmsc').disabled};"); + // Submit the current APN unchanged, through Gaia. Never change operator + // credentials, activate a bearer or expose any APN/user/password in logs. + await evaluate('settings',"const p=document.querySelector('#apn-editor');p.querySelector('.apn').dispatchEvent(new Event('input',{bubbles:true}));p.querySelector('.ok').click();return true;"); + await wait('settings',"return !!document.querySelector('#apn-list.current label.name');",7000); + report.networkUi[type].savedUnchanged=true; + } + report.networkUi.errors=await evaluate('settings','return window.__vulpesDiagnostics;'); + } catch(error) {report.networkUi.error=String(error);report.networkUi.errors=await evaluate('settings','return window.__vulpesDiagnostics;');} + report.supplementaryUi={}; + try { + await evaluate('settings',"window.__vulpesDiagnostics=[];require(['modules/settings_service'],s=>s.navigate('call'));return true;"); + await wait('settings',"return !!document.querySelector('#call.current #ril-callerId:not([disabled])');",30000); + report.supplementaryUi.calls=await evaluate('settings',"return {waitingEnabled:!document.querySelector('#menuItem-callWaiting gaia-switch').disabled,callerEnabled:!document.querySelector('#ril-callerId').disabled};"); + await evaluate('settings',"document.querySelector('#ril-callerId').dispatchEvent(new Event('change',{bubbles:true}));return true;"); + await wait('settings',"return !document.querySelector('#ril-callerId').disabled;",50000); + report.supplementaryUi.callerSavedUnchanged=true; + await evaluate('settings',"require(['modules/settings_service'],s=>s.navigate('call-forwarding'));return true;"); + await wait('settings',"return !!document.querySelector('#call-forwarding.current .callForwardingUnconditional[aria-disabled=false]');",30000); + report.supplementaryUi.dialogs={}; + for(const [selector,id] of [ + ['.callForwardingUnconditional','call-cf-unconditional-settings'],['.callForwardingMobileBusy','call-cf-mobile-busy-settings'], + ['.callForwardingNoReply','call-cf-no-reply-settings'],['.callForwardingNotReachable','call-cf-not-reachable-settings'] + ]) { + await evaluate('settings',`document.querySelector('#call-forwarding ${selector}').click();return true;`); + await wait('settings',`return document.getElementById('${id}').classList.contains('current');`,4000); + await evaluate('settings',`document.querySelector('#${id} button[type=reset]').click();return true;`); + await wait('settings',"return document.getElementById('call-forwarding').classList.contains('current');",4000); + report.supplementaryUi.dialogs[id]={opened:true,cancelled:true}; + } + report.supplementaryUi.errors=await evaluate('settings','return window.__vulpesDiagnostics;'); + } catch(error) {report.supplementaryUi.error=String(error);report.supplementaryUi.errors=await evaluate('settings','return window.__vulpesDiagnostics;');} + report.hotspotUi={}; + try { + await evaluate('settings',"window.__vulpesDiagnostics=[];require(['modules/settings_service'],s=>s.navigate('hotspot'));return true;"); + await wait('settings',"return !!document.querySelector('#hotspot.current #tethering-wifi-enabled:not([disabled])');",10000); + await evaluate('settings',"document.querySelector('#hotspot-settings-section a').click();return true;"); + await wait('settings',"return document.getElementById('hotspot-wifiSettings').classList.contains('current');",4000); + report.hotspotUi=await evaluate('settings',"const p=document.getElementById('hotspot-wifiSettings');return {opened:true,securityChoices:p.querySelector('select').options.length,passwordMasked:p.querySelector('input[name=password]').type==='password'};"); + await evaluate('settings',"document.querySelector('#hotspot-wifiSettings button[type=reset]').click();return true;"); + await wait('settings',"return document.getElementById('hotspot').classList.contains('current') && !document.getElementById('hotspot-wifiSettings').classList.contains('current') && !document.querySelector('#tethering-wifi-enabled').disabled;",4000); + report.hotspotUi.cancelled=true; + const unconfigured=await evaluate('settings',"const s=await VulpesCompat.call('platform.wifi',{operation:'hotspotStatus'});return !s.configured && !s.enabled;"); + if(unconfigured) { + await evaluate('settings',"document.querySelector('#hotspot-settings-section a').click();return true;"); + await wait('settings',"return document.getElementById('hotspot-wifiSettings').classList.contains('current');",4000); + await evaluate('settings',"const p=document.getElementById('hotspot-wifiSettings');p.querySelector('input[type=text]').value='Vulpes-qualification';p.querySelector('select').value='wpa2-psk';p.querySelector('input[name=password]').value=crypto.randomUUID();p.querySelector('button[type=submit]').click();return true;"); + await wait('settings',"return !!document.querySelector('#hotspot.current #tethering-wifi-enabled:not([disabled])') && !document.getElementById('hotspot-wifiSettings').classList.contains('current');",10000); + report.hotspotUi.configuredByAudit=true; + for(const enabled of [true,false]) { + await evaluate('settings',`const c=document.querySelector('#tethering-wifi-enabled');c.checked=${enabled};c.dispatchEvent(new Event('change',{bubbles:true}));return true;`); + await wait('settings',`const c=document.querySelector('#tethering-wifi-enabled');return !c.disabled && c.checked===${enabled};`,90000); + const actual=await evaluate('settings',"return (await VulpesCompat.call('platform.wifi',{operation:'hotspotStatus'})).enabled;"); + if(actual!==enabled)throw Error('Hotspot UI/readback mismatch'); + report.hotspotUi[enabled?'started':'stopped']=true; + } + } + report.hotspotUi.errors=await evaluate('settings','return window.__vulpesDiagnostics;'); + } catch(error) {report.hotspotUi.error=String(error);report.hotspotUi.errors=await evaluate('settings','return window.__vulpesDiagnostics;');report.hotspotUi.formState=await evaluate('settings',"const p=document.getElementById('hotspot-wifiSettings');return {current:p.classList.contains('current'),message:p.querySelector('[role=status]')?.textContent,submitDisabled:p.querySelector('button[type=submit]')?.disabled};");} + report.scope='panel navigation plus storage read and idempotent data/timezone/radio/APN write/readback; temporary hotspot start/stop when initially unconfigured; no destructive actions, PIN attempts or subscriptions'; + await evaluate('settings','require(["modules/settings_service"],s=>s.navigate("root"));return true;'); + return report; +} diff --git a/host/SettingsFollowupAudit.sys.mjs b/host/SettingsFollowupAudit.sys.mjs new file mode 100644 index 00000000..38357bd5 --- /dev/null +++ b/host/SettingsFollowupAudit.sys.mjs @@ -0,0 +1,56 @@ +// Private qualification entry point, invoked only by a test marker/profile. +export async function auditFollowup({evaluate,wait,sleep,save,tap}) { + const report={checks:{},errors:{},mediaRetained:false}; + const launch=async app=>{ + await evaluate('system',`ScreenManager.turnScreenOn(true);if(Service.query('locked'))await Service.request('unlock',{forcibly:true});await VulpesCompat.call('apps.launch',{manifestURL:'http://${app}.localhost:8765/manifest.webapp'});return true;`); + await wait(app,'return document.readyState==="complete" && !document.hidden;'); + await wait('system',`return Service.query('getActiveApp')?.origin==='http://${app}.localhost:8765';`); + }; + await wait('system',`return document.body?.getAttribute('ready-state')==='fullyLoaded';`,30000); + await sleep(1000); + try { + await launch('settings');await wait('settings','return typeof require==="function";'); + await evaluate('settings',`const s=await new Promise(r=>require(['modules/settings_service'],r));await s.navigate('dateTime');return true;`); + await sleep(800); + // Test the actual Gaia picker without changing the user's system clock. + await evaluate('settings',`if(!navigator.mozTime){window.__testClockInstalled=true;Object.defineProperty(navigator,'mozTime',{value:{},configurable:true});}window.__dateModule=await new Promise(r=>require(['modules/date_time'],r));window.__oldMozTime=__dateModule._mozTime;window.__pickedTime=null;__dateModule._mozTime={set:d=>{window.__pickedTime=+d;return Promise.resolve();}};document.querySelector('#dateTime .time-manual').classList.remove('disabled');for(const e of document.querySelectorAll('#dateTime input'))e.disabled=false;return true;`); + for(const type of ['date','time']) { + await evaluate('settings',`const e=document.querySelector('#dateTime .${type}-picker');e.disabled=false;e.click();return true;`); + await wait('system',`const p=Service.query('getActiveApp')?.valueSelector;return !!(p?.isShown && p._${type}Picker);`); + report.checks[type+'Picker']=await evaluate('system',`const p=Service.query('getActiveApp').valueSelector;const e=p.elements.${type==='date'?'spinDate':'time'}PickerPopup;return {visible:e.checkVisibility(),height:e.getBoundingClientRect().height};`); + await evaluate('system',`Service.query('getActiveApp').valueSelector.confirm();return true;`); + await wait('settings','return Number.isFinite(window.__pickedTime);'); + report.checks[type+'Commit']=true; + await evaluate('settings','window.__pickedTime=null;return true;'); + } + await evaluate('settings',`__dateModule._mozTime=__oldMozTime;const s=await new Promise(r=>require(['modules/settings_service'],r));await s.navigate('keyboard');return true;`); + await launch('keyboard'); + await wait('keyboard','return !!document.querySelector("#general gaia-header");'); + await evaluate('keyboard',`document.querySelector('#general gaia-header').dispatchEvent(new CustomEvent('action'));return true;`); + await wait('system',`return Service.query('getActiveApp')?.origin==='http://settings.localhost:8765';`); + report.checks.keyboardBack=true; + } catch(error){report.errors.settings=String(error);} + finally { + await evaluate('settings','if(window.__dateModule)__dateModule._mozTime=window.__oldMozTime;if(window.__testClockInstalled)delete navigator.mozTime;return true;').catch(()=>{}); + } + await save(report); + try { + await launch('camera'); + await wait('camera','return !!window.app?.camera?.mozCamera && !app.camera.isBusy;'); + await evaluate('camera',`window.__vulpesDiagnostics=[];window.__lastVideo=null;app.once('newmedia',v=>{window.__lastVideo=v;});app.settings.mode.select('video');return true;`); + await wait('camera','return app.camera.mode==="video" && !app.camera.isBusy;'); + await tap('camera','.js-capture'); + await wait('camera','return app.camera.get("recording")==="started";'); + await sleep(3000); + await tap('camera','.js-capture'); + await wait('camera','return !!window.__lastVideo?.blob;',20000); + report.checks.video=await evaluate('camera',`const v=window.__lastVideo;const el=document.createElement('video');el.muted=true;el.src=URL.createObjectURL(v.blob);try{await new Promise((ok,fail)=>{el.onloadeddata=ok;el.onerror=()=>fail(Error('Recorded video cannot be decoded'));});return {bytes:v.blob.size,mime:v.blob.type,path:v.filepath,width:el.videoWidth,height:el.videoHeight,poster:v.poster.blob.size};}finally{URL.revokeObjectURL(el.src);el.remove();}`); + report.errors.camera=await evaluate('camera','return __vulpesDiagnostics.filter(x=>x.kind==="error" || x.kind==="rejection");'); + } catch(error){report.errors.video=String(error);report.errors.camera=await evaluate('camera','return window.__vulpesDiagnostics;').catch(()=>[]);} + finally { + await evaluate('camera',`await app.camera.mozCamera?.vulpesFinishRecording?.();if(window.__lastVideo){await navigator.getDeviceStorage('videos').delete(__lastVideo.filepath);await navigator.getDeviceStorage('pictures').delete(__lastVideo.poster.filepath);}app.settings.mode.select('picture');return true;`).catch(()=>{}); + await evaluate('system',"dispatchEvent(new Event('home'));return true;").catch(()=>{}); + } + report.passed=!Object.values(report.errors).some(x=>Array.isArray(x)?x.length:!!x) && !!report.checks.video; + await save(report);return report; +} diff --git a/host/StorageStats.sys.mjs b/host/StorageStats.sys.mjs new file mode 100644 index 00000000..0c07bd25 --- /dev/null +++ b/host/StorageStats.sys.mjs @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: MPL-2.0 +// Only byte counts for Vulpes-owned storage cross the application boundary. +let pending, cached; +export async function storageStats(app) { + if(app.id!=='settings')throw Error('PERMISSION_DENIED'); + if(cached && Date.now()-cached.at<5000)return cached.value; + if(pending)return pending; + pending=(async()=>{ + async function bytes(path,skip='') { + if(path===skip || !await IOUtils.exists(path))return 0; + const file=Cc['@mozilla.org/file/local;1'].createInstance(Ci.nsIFile);file.initWithPath(path); + if(file.isSymlink())return 0; + const info=await IOUtils.stat(path); + if(info.type==='regular')return info.size; + if(info.type!=='directory')return 0; + let total=0; + for(const child of await IOUtils.getChildren(path)) { + try {total+=await bytes(child,skip);} catch(error) {if(error.name!=='NotFoundError')throw error;} + } + return total; + } + const profile=PathUtils.profileDir,media=PathUtils.join(profile,'vulpes-media'); + const file=Cc['@mozilla.org/file/local;1'].createInstance(Ci.nsIFile);file.initWithPath(profile); + const value={free:file.diskSpaceAvailable,apps:await bytes(profile,media),sdcard:await bytes(media),scope:'vulpes-profile'}; + cached={at:Date.now(),value};return value; + })().finally(()=>{pending=null;}); + return pending; +} diff --git a/host/Tundra.sys.mjs b/host/Tundra.sys.mjs index a6667461..3c5f29e9 100644 --- a/host/Tundra.sys.mjs +++ b/host/Tundra.sys.mjs @@ -42,6 +42,7 @@ export function tundraCapabilities(release) { dial: radio, callControl: radio, sms: radio, + mms: radio, alarms: false, hardwareStatus: true, wifiControl: Services.env?.get('VULPES_TUNDRA_WIFI') === '1', @@ -50,7 +51,7 @@ export function tundraCapabilities(release) { export async function tundraRadio(operation, params = {}) { if (Services.env.get('VULPES_TUNDRA_RADIO') !== '1') throw Error('RADIO_UNAVAILABLE'); - if (!['status', 'calls', 'dial', 'hangup', 'answer', 'sms', 'incomingMessages', 'ackMessages', 'messageState'].includes(operation)) throw Error('RADIO_INVALID_REQUEST'); + if (!['deviceSettings', 'mobileSettings', 'status', 'calls', 'dial', 'hangup', 'answer', 'sms', 'incomingMessages', 'ackMessages', 'messageState'].includes(operation)) throw Error('RADIO_INVALID_REQUEST'); const { Subprocess } = ChromeUtils.importESModule('resource://gre/modules/Subprocess.sys.mjs'); const process = await Subprocess.call({ command: '/usr/bin/python3', @@ -78,8 +79,8 @@ export async function tundraRadio(operation, params = {}) { throw Error('RADIO_INVALID_REPLY'); } if (reply?.ok === false) { - const errors = ['RADIO_NOT_REGISTERED', 'RADIO_BUSY', 'RADIO_NOT_READY', - 'RADIO_REQUEST_FAILED', 'INVALID_NUMBER', 'INVALID_SMS', 'INVALID_CALL']; + const errors = ['RADIO_NOT_REGISTERED', 'RADIO_BUSY', 'RADIO_NOT_READY', 'RADIO_TIMEOUT', + 'RADIO_REQUEST_FAILED', 'RADIO_READBACK_FAILED', 'INVALID_REQUEST', 'APN_IN_USE', 'APN_NOT_FOUND', 'APN_WRITE_FAILED', 'APN_ROLLBACK_FAILED', 'CLOCK_REQUEST_FAILED', 'CLOCK_UNAVAILABLE', 'INVALID_NUMBER', 'INVALID_SMS', 'INVALID_CALL']; throw Error(errors.includes(reply.error) ? reply.error : 'RADIO_REQUEST_FAILED'); } if (reply?.ok !== true) throw Error('RADIO_INVALID_REPLY'); @@ -92,11 +93,11 @@ const WIFI_SCAN_CACHE_MS = 5000; export async function tundraWifi(params) { if (Services.env.get('VULPES_TUNDRA_WIFI') !== '1') throw Error('NOT_SUPPORTED'); - if (!['scan', 'connect', 'disconnect', 'status', 'setEnabled', 'known', 'forget'].includes(params.operation)) + if (!['scan', 'connect', 'connectHidden', 'disconnect', 'status', 'setEnabled', 'known', 'forget','hotspotStatus','hotspotSave','hotspotPower'].includes(params.operation)) throw Error('WIFI_INVALID_REQUEST'); if (params.operation !== 'scan') { try { return await wifiCommand(params); } - finally { wifiScanCache = null; } + finally { if (['connect','connectHidden','disconnect','forget','setEnabled','hotspotPower'].includes(params.operation)) wifiScanCache = null; } } // System and Settings share a broker: at most one Python scan client exists. if (wifiScanPending) return wifiScanPending; @@ -176,3 +177,33 @@ export async function tundraCallAudio(params) { if (reply.error) throw Error(reply.error); return reply.result; } + + +// Separate socket and bounded payload so MMS traffic cannot block call control. +export async function tundraMms(operation, params={}) { + if(Services.env.get('VULPES_TUNDRA_RADIO')!=='1') throw Error('NotSupportedError'); + if(!['send','state','incoming','ack'].includes(operation)) throw Error('INVALID_REQUEST'); + const {Subprocess}=ChromeUtils.importESModule('resource://gre/modules/Subprocess.sys.mjs'); + const process=await Subprocess.call({command:'/usr/bin/python3',arguments:['/etc/tundra/mms-control.py','--client'],stderr:'ignore'}); + await process.stdin.write(JSON.stringify({version:1,operation,...params}));await process.stdin.close(); + let output='';for(;;){const chunk=await process.stdout.readString();if(!chunk)break;output+=chunk;if(output.length>1228800){process.kill();throw Error('MMS_TOO_LARGE');}} + const {exitCode}=await process.wait();if(exitCode)throw Error('MMS_UNAVAILABLE'); + const reply=JSON.parse(output);if(reply.ok!==true)throw Error(reply.error || 'MMS_UNAVAILABLE');return reply.result; +} + +// A persistent daemon owns BlueZ discovery and pending pairing requests. +export async function tundraBluetooth(params) { + if (Services.env.get('VULPES_TUNDRA') !== '1') throw Error('NotSupportedError'); + const {Subprocess}=ChromeUtils.importESModule('resource://gre/modules/Subprocess.sys.mjs'); + const process=await Subprocess.call({command:'/usr/bin/python3', + arguments:['/etc/tundra/bluetooth-control.py','--client'],stderr:'ignore'}); + await process.stdin.write(JSON.stringify({...params,version:1}));await process.stdin.close(); + let output=''; + for (;;) { + const chunk=await process.stdout.readString();if(!chunk)break; + output+=chunk;if(output.length>65536){process.kill();throw Error('BLUETOOTH_INVALID_REPLY');} + } + const {exitCode}=await process.wait();if(exitCode)throw Error('BLUETOOTH_UNAVAILABLE'); + const reply=JSON.parse(output);if(reply.error)throw Error(reply.error); + return reply.result; +} diff --git a/host/actors/VulpesChild.sys.mjs b/host/actors/VulpesChild.sys.mjs index c69ed27f..960dd01f 100644 --- a/host/actors/VulpesChild.sys.mjs +++ b/host/actors/VulpesChild.sys.mjs @@ -1,16 +1,20 @@ +import {InputEditor} from 'resource://vulpes/services/input-editor.mjs'; export class VulpesChild extends JSWindowActorChild { handleEvent(event) { - if(['focusin','pointerdown'].includes(event.type)) { - if(this.editing) return; + if(['focusin','pointerdown','click'].includes(event.type)) { + if(this._editor?.editing) return; if(!Services.cpmm.sharedData.get('vulpes:native-input')) return; let element=event.target; if(element?.isContentEditable) element=element.closest('[contenteditable=true]') || element; const editable=element?.isContentEditable || (['input','textarea'].includes(element?.localName) && !['button','submit','range','checkbox','radio','date','time','datetime-local','file'].includes(element.type)); if(!editable || element.disabled || element.readOnly) { - if(event.type==='pointerdown') this.sendAsyncMessage('Vulpes:InputFocus',null); + // Dismissing on pointerdown moves buttons before pointerup and loses + // the click. Composer actions may also explicitly retain the editor. + if(event.type==='click' && !element?.closest?.('[data-vulpes-keep-input]')) + this.sendAsyncMessage('Vulpes:InputFocus',null); return; } - this.inputElement=element; + this.editor.focus(element); this.sendAsyncMessage('Vulpes:InputFocus',this.inputState()); return; } @@ -76,69 +80,9 @@ export class VulpesChild extends JSWindowActorChild { { defineAs: 'vulpesFrameForContext' }, ); } - inputState() { - const e=this.inputElement; - const text=e.isContentEditable ? e.textContent : e.value; - const selection=this.contentWindow.getSelection(); - let start=e.selectionStart ?? text.length, end=e.selectionEnd ?? start; - if(e.isContentEditable && selection.rangeCount && e.contains(selection.anchorNode)) { - const range=this.document.createRange();range.selectNodeContents(e); - range.setEnd(selection.anchorNode,selection.anchorOffset);start=range.toString().length; - range.setEnd(selection.focusNode,selection.focusOffset);end=range.toString().length; - [start,end]=[Math.min(start,end),Math.max(start,end)]; - } - return {type:e.isContentEditable?'contenteditable':e.localName,inputType:e.type || (e.classList.contains('recipient')?'tel':'text'), - inputMode:e.inputMode || (e.classList.contains('recipient')?'tel':''), - text:e.type==='password'?'':text,selectionStart:start,selectionEnd:end}; - } - edit(p) { - const e=this.inputElement,win=this.contentWindow; - if(!e?.isConnected || this.document.hidden) throw Error('INPUT_CONTEXT_EXPIRED'); - this.editing=true; - try {e.focus();} finally {this.editing=false;} - // A touch can focus a new contenteditable while Gecko retains the previous - // field's selection. execCommand follows that selection, not activeElement. - if(e.isContentEditable) { - const selection=win.getSelection(); - if(!e.contains(selection.anchorNode) || !e.contains(selection.focusNode)) { - const range=this.document.createRange(); - range.selectNodeContents(e);range.collapse(false); - selection.removeAllRanges();selection.addRange(range); - } - } - if(p.operation==='key') { - let key=typeof p.key==='object'?p.key.key:p.key; - if(key===8 || key==='Backspace') this.document.execCommand('delete'); - else if(key===13 || key==='Enter') { - const options={key:'Enter',code:'Enter',keyCode:13,charCode:13,which:13,bubbles:true,cancelable:true}; - const accepted=e.dispatchEvent(new win.KeyboardEvent('keydown',options)) && e.dispatchEvent(new win.KeyboardEvent('keypress',options)); - if(accepted && (e.isContentEditable || e.localName==='textarea')) this.document.execCommand('insertLineBreak'); - e.dispatchEvent(new win.KeyboardEvent('keyup',options)); - } else { - const text=typeof key==='string'?key:String.fromCodePoint(p.charCode || key); - if(text.length>16) throw Error('INVALID_VALUE'); - this.document.execCommand('insertText',false,text); - } - } else if(['selection','replace'].includes(p.operation)) { - const state=this.inputState(); - const start=p.operation==='selection'?p.start:state.selectionStart+(p.offset||0); - const length=p.length||0; - if(!Number.isInteger(start)||!Number.isInteger(length)||start<0||length<0||start+length>(e.value ?? e.textContent).length) throw Error('INVALID_VALUE'); - if(e.isContentEditable) { - const walker=this.document.createTreeWalker(e,win.NodeFilter.SHOW_TEXT); - const points=[];let offset=0,node; - while((node=walker.nextNode())) {points.push({node,start:offset,end:offset+node.length});offset+=node.length;} - const point=index=>{const part=points.find(p=>index<=p.end);return part?[part.node,index-part.start]:[e,0];}; - const range=this.document.createRange();range.setStart(...point(start));range.setEnd(...point(start+length)); - win.getSelection().removeAllRanges();win.getSelection().addRange(range); - } else e.setSelectionRange(start,start+length); - if(p.operation==='replace') { - if(typeof p.text!=='string'||p.text.length>4096) throw Error('INVALID_VALUE'); - this.document.execCommand('insertText',false,p.text); - } - } else throw Error('METHOD_NOT_SUPPORTED'); - return this.inputState(); - } + get editor() { return this._editor ||= new InputEditor(this.contentWindow); } + inputState() { return this.editor.state(); } + edit(params) { return this.editor.edit(params); } receiveMessage(message) { if(message.name==='Vulpes:Edit') return this.edit(message.data); if (message.name === 'Vulpes:ViewState') { @@ -146,9 +90,16 @@ export class VulpesChild extends JSWindowActorChild { f => f.browsingContext?.id === message.data.contextId); if (!frame?.isConnected) return {visible:false}; const win = this.contentWindow, rect = frame.getBoundingClientRect(); + // App chrome (the status bar and browser controls) legitimately covers + // the corners of a mozbrowser iframe. Requiring every corner to hit the + // iframe hid the external browser completely, leaving a white page for + // sites such as YouTube. The centre plus one side sample is sufficient + // to prove that the content viewport is visible. + const samples = [[.5,.5],[.05,.5],[.95,.5],[.5,.05],[.5,.95]]; + const hits = samples.reduce((count,[x,y]) => count + + (this.document.elementFromPoint(rect.left+rect.width*x,rect.top+rect.height*y)===frame ? 1 : 0), 0); const visible = !this.document.hidden && frame.checkVisibility({checkOpacity:true,checkVisibilityCSS:true}) && - [[.5,.5],[.05,.05],[.95,.05],[.05,.95],[.95,.95]].every(([x,y]) => - this.document.elementFromPoint(rect.left+rect.width*x,rect.top+rect.height*y)===frame); + hits >= 2; let top = Math.max(0,rect.top); // Gaia's status bar belongs above all app content, including native web views. if (this.document.location.hostname === 'system.localhost') { diff --git a/host/desktop.css b/host/desktop.css index 40a36351..2f92504c 100644 --- a/host/desktop.css +++ b/host/desktop.css @@ -146,6 +146,12 @@ h1, h2, h3, h4, h5, h6, #statusbar-time, /* Gaia owns edge gestures; avoid a second host overscroll effect. */ html[data-vulpes-app] { overscroll-behavior:none; } +/* padding-box is no longer supported. Content-box made the full-width + section wrap below the dialog's spacer, hiding its title and message. */ +form[role='dialog'][data-type='confirm'] > section { + box-sizing: border-box; +} + /* Native selects now paint their own arrow; the old Gecko arrow-hiding offset clips Fira descenders on current Gecko. */ @@ -200,3 +206,47 @@ html[data-vulpes-app='camera'], html[data-vulpes-app='camera'] body { touch-acti /* Homescreen pages must settle at their boundary after a drag (Forgejo #3). */ html[data-vulpes-app="homescreen"] #panels { scroll-snap-type:x mandatory; } html[data-vulpes-app="homescreen"] #panels > * { scroll-snap-align:start; } + +/* System gestures are handled by Gaia, not by the page's pan/zoom handler. + App frames keep their own scrolling; the notification list scrolls vertically. */ +html[data-vulpes-app='system'], +html[data-vulpes-app='system'] body { + overscroll-behavior: none; +} +html[data-vulpes-app='system'] #lockscreen { + touch-action: none; +} +html[data-vulpes-app='system'] #utility-tray { + touch-action: pan-y; + overscroll-behavior: contain; +} + +/* Keep the original Gaia value selector inside the application's viewport. */ +html[data-vulpes-app] #vulpes-value-selector { + position: fixed; inset: 0; z-index: 2147483000; margin: 0; + font-family: FiraSans, sans-serif; box-sizing: border-box; +} +html[data-vulpes-app] #vulpes-value-selector > section { box-sizing: border-box; } +html[data-vulpes-app] #vulpes-value-selector [role=option][aria-disabled=true] { opacity: .45; } + +html[data-vulpes-app] #vulpes-value-selector-layer { + position: fixed; inset: 0; z-index: 2147483000; +} + +html[data-vulpes-app] :is(.button,.bb-button) select.vulpes-select-label-owner { + opacity: 0; +} +html[data-vulpes-app] .vulpes-select-label { + position: absolute; + inset: 0 3.5rem 0 1.1rem; + display: flex; + align-items: center; + pointer-events: none; + font: italic 1.7rem/1.5 FiraSans, sans-serif; + white-space: nowrap; + overflow: hidden; + color: var(--text-color, #333); + text-shadow: none; +} +html[dir=rtl][data-vulpes-app] .vulpes-select-label { inset: 0 1.1rem 0 3.5rem; } +html[data-vulpes-theme=dark] .vulpes-select-label { color: #eee; } diff --git a/host/gaia-bluetooth.js b/host/gaia-bluetooth.js new file mode 100644 index 00000000..cc848e55 --- /dev/null +++ b/host/gaia-bluetooth.js @@ -0,0 +1,126 @@ +// Gaia Bluetooth v2, backed by the host's permission-checked BlueZ broker. +(() => { + const app = location.hostname.split('.')[0]; + if (!(window.VulpesPlatform === 'tundra' || window.VulpesTestBluetooth) || + !['system', 'settings', 'bluetooth'].includes(app)) return; + const {call} = VulpesCompat; + const manager = Object.assign(new EventTarget(), {defaultAdapter:null}); + const adapter = Object.assign(new EventTarget(), { + state:'disabled', address:'', name:'', discoverable:false, discovering:false, + pairingReqs:new EventTarget(), + }); + const devices = new Map(); + let discovery = null, seen = new Set(), pending = false, timer, closed = false; + let currentPairing = null, initialized = false; + const emit = (target, type, values={}) => { + const event = Object.assign(new Event(type), values); + target.dispatchEvent(event); target['on'+type]?.(event); + }; + function change(target, fields) { + const attrs = Object.keys(fields).filter(k=>JSON.stringify(target[k])!==JSON.stringify(fields[k])); + Object.assign(target, fields); + if (attrs.length) emit(target, 'attributechanged', {attrs}); + } + function apply(state) { + if (!state?.available) return; + for (const row of state.devices) { + let device = devices.get(row.address); + const oldPaired = device?.paired; + if (!device) { + device = new EventTarget(); devices.set(row.address, device); + } + change(device, {...row, connectionStatus:row.connected?'connected':'disconnected'}); + if (oldPaired !== undefined && oldPaired !== row.paired) + emit(adapter, row.paired?'devicepaired':'deviceunpaired', {device,address:row.address}); + if (discovery && state.discovering && !seen.has(row.address)) { + seen.add(row.address); emit(discovery, 'devicefound', {device}); + } + } + for (const [address, device] of devices) { + if (!state.devices.some(d=>d.address===address)) { + if (device.paired) emit(adapter,'deviceunpaired',{address,device}); + devices.delete(address); + } + } + change(adapter, {state:state.enabled?'enabled':'disabled',address:state.address, + name:state.name,discoverable:state.discoverable,discovering:state.discovering}); + if (!manager.defaultAdapter) { + manager.defaultAdapter=adapter; emit(manager,'attributechanged',{attrs:['defaultAdapter']}); + } + currentPairing = state.pairing?.[0] || null; + if (app==='bluetooth' && location.pathname==='/onpair.html') showPairing(); + } + async function command(operation, params={}) { + const value=await call('platform.bluetooth',{operation,...params}); + if (value?.available) apply(value); + return value; + } + async function refresh() { + if (pending || closed) return; + pending=true; + try {apply(await command('status'));} + catch(error) {console.warn('Bluetooth:',error.message);} + finally {pending=false;} + } + adapter.enable=()=>command('power',{enabled:true}); + adapter.disable=()=>command('power',{enabled:false}); + adapter.setName=name=>command('rename',{name}); + adapter.setDiscoverable=enabled=>command('discoverable',{enabled}); + adapter.startDiscovery=async()=>{ + await command('scan'); + discovery=new EventTarget(); seen=new Set(); + const handle=discovery; + // Gaia installs its discovery listeners after this promise resolves. + setTimeout(()=>refresh(),0); + return handle; + }; + adapter.stopDiscovery=async()=>{await command('stopScan');discovery=null;}; + adapter.getPairedDevices=()=>[...devices.values()].filter(d=>d.paired); + adapter.getConnectedDevices=async profile=>{ + await refresh(); + const uuid=Number(profile).toString(16).padStart(8,'0')+'-0000-1000-8000-00805f9b34fb'; + return [...devices.values()].filter(d=>d.connected && d.uuids.includes(uuid)); + }; + for (const op of ['pair','unpair','connect','disconnect']) + adapter[op]=async value=>{ + await command(op,{address:typeof value==='string'?value:value.address}); + await refresh(); + }; + const unsupported=()=>Promise.reject(new DOMException('Bluetooth profile is unavailable','NotSupportedError')); + for (const name of ['sendFile','stopSendingFile','confirmReceivingFile','connectSco','disconnectSco', + 'sendMediaMetaData','sendMediaPlayStatus']) adapter[name]=unsupported; + adapter.isScoConnected=()=>Promise.resolve(false); + manager.getAdapters=()=>Promise.resolve(manager.defaultAdapter?[adapter]:[]); + Object.defineProperty(navigator,'mozBluetooth',{value:manager}); + + let shown = null; + function showPairing() { + if (!initialized || !window.Pairview) return; + const item=currentPairing; + if (!item) {if(shown) window.close();return;} + if (shown===item.id) return; + shown=item.id; + const reply=(accept,code)=>command('reply',{id:item.id,accept,code}); + Pairview.init(item.method==='enterpasskey'?'enterpincode':item.method,{ + deviceName:item.deviceName,handle:{passkey:item.passkey, + accept:()=>reply(true),reject:()=>reply(false),setPinCode:code=>reply(true,code)}, + }); + } + addEventListener('DOMContentLoaded',async()=>{ + initialized=true; + await refresh(); showPairing(); + if(app==='system') { + navigator.mozSettings.addObserver('bluetooth.enabled',event=>{ + const enabled=event.settingValue===true; + if((adapter.state==='enabled')!==enabled) + command('power',{enabled}).catch(()=>call('settings.set',{values:{'bluetooth.enabled':adapter.state==='enabled'}})); + }); + await call('settings.set',{values:{'bluetooth.enabled':adapter.state==='enabled'}}); + } + timer=setInterval(()=>{if(app==='system'||!document.hidden||discovery)refresh();},1500); + },{once:true}); + addEventListener('pagehide',()=>{ + closed=true;clearInterval(timer); + if(discovery)command('stopScan').catch(()=>{}); + },{once:true}); +})(); diff --git a/host/gaia-camera.js b/host/gaia-camera.js index b056ff2d..63b46b0c 100644 --- a/host/gaia-camera.js +++ b/host/gaia-camera.js @@ -1,6 +1,8 @@ // The Gaia camera UI consumes mozCameras. Only live MediaStream capabilities are exposed here. (() => { if (location.hostname !== 'camera.localhost') return; + const videoMime = typeof MediaRecorder !== 'undefined' && MediaRecorder.isTypeSupported('video/webm;codecs=vp8,opus') ? 'video/webm;codecs=vp8,opus' : null; + window.VulpesVideoExtension = videoMime ? 'webm' : null; let cameras = ['back'], devices = new Map(), current = null, @@ -8,11 +10,22 @@ let cameraError = null; let opening = Promise.resolve(), closing = Promise.resolve(); const releases = new WeakMap(); + const nativeViews = new WeakMap(); function releaseStream(stream, preview) { if (releases.has(stream)) return releases.get(stream); if (current === stream) current = null; const release = Promise.resolve().then(async () => { + await stream.vulpesFinishRecording?.(); + emit(stream, 'previewstatechange', {newState:'stopped'}); + // Stop the capture producer before detaching its consumers. Gecko can + // otherwise block in srcObject=null while a GPU-backed frame is in use. + stream.getTracks().forEach(track => track.stop()); for (const video of document.querySelectorAll('video')) { + if (nativeViews.get(video) === stream) { + nativeViews.delete(video); + stream.vulpesPreviewCanvas.remove(); + video.style.display = ''; + } if (video.srcObject === stream) { video.pause(); video.srcObject = null; if ('vulpesCapture' in video.dataset) video.remove(); @@ -96,12 +109,33 @@ Object.defineProperty(HTMLMediaElement.prototype, 'mozSrcObject', { configurable: true, get() { - return nativeSource.get.call(this); + return nativeViews.get(this) || nativeSource.get.call(this); }, set(stream) { - nativeSource.set.call(this, stream); + const old = nativeViews.get(this); + if (old) { + old.vulpesPreviewCanvas.remove(); + nativeViews.delete(this); + this.style.display = ''; + } + if (stream?.vulpesPreviewCanvas) { + // The HAL already delivers NV21 frames. Render them directly in Gaia's + // viewfinder instead of capturing the WebGL canvas into a second GPU + // video pipeline, which stalls on repeated switches on Sargo. + nativeViews.set(this, stream); + const canvas = stream.vulpesPreviewCanvas; + canvas.className = this.className; + canvas.dataset.vulpesNativePreview = ''; + canvas.style.cssText = 'display:block;width:100%;height:100%;pointer-events:none'; + this.style.display = 'none'; + this.parentNode.append(canvas); + } else nativeSource.set.call(this, stream); }, }); + const playVideo = HTMLMediaElement.prototype.play; + HTMLMediaElement.prototype.play = function() { + return nativeViews.has(this) ? Promise.resolve() : playVideo.call(this); + }; function emit(target, type, detail = {}) { const event = new Event(type); Object.assign(event, detail); @@ -136,15 +170,20 @@ if (second) devices.set('front', second.deviceId); cameras = devices.size ? [...devices.keys()] : ['back']; } + // Keep one graphics context per app. Losing/recreating it at every lens switch + // can exhaust the mobile driver while captured video frames are still in use. + let nativeCanvas = null; async function nativeStream(name) { let session; const call = (params) => VulpesCompat.call('platform.cameraPreview', {...params, session}); const meta = await call({action:'start', camera:name === 'front' ? 'front' : 'back'}); session = meta.session; - const canvas = document.createElement('canvas'); + const canvas = nativeCanvas || (nativeCanvas = document.createElement('canvas')); const sideways = meta.orientation % 180 !== 0; - canvas.width = sideways ? meta.height : meta.width; - canvas.height = sideways ? meta.width : meta.height; + const width = sideways ? meta.height : meta.width; + const height = sideways ? meta.width : meta.height; + if (canvas.width !== width) canvas.width = width; + if (canvas.height !== height) canvas.height = height; // Convert NV21 in a shader instead of blocking Gaia's UI with one JS loop // per pixel. The two textures contain Y and interleaved V/U respectively. const gl = canvas.getContext('webgl', {alpha:false, antialias:false, preserveDrawingBuffer:true}); @@ -152,18 +191,21 @@ await call({action:'stop'}); throw new DOMException('Camera preview requires WebGL', 'NotSupportedError'); } - const timing = {frames:0, startedAt:performance.now(), conversionMs:0}; - let stopped = false, timer, stream, sequence = -1, closing, program, buffer; + const timing = {frames:0, startedAt:performance.now(), conversionMs:0, transferMs:0, sensorFrames:0}; + let stopped = false, timer, stream, wakeLock, sequence = -1, closing, program, buffer; const textures = [], shaders = []; const stop = () => { if (closing) return closing; stopped = true; clearTimeout(timer); + wakeLock?.unlock(); wakeLock = null; closing = call({action:'stop'}).finally(() => { + if (stream) stream.vulpesClosed = true; textures.forEach(texture => gl.deleteTexture(texture)); shaders.forEach(value => gl.deleteShader(value)); if (buffer) gl.deleteBuffer(buffer); if (program) gl.deleteProgram(program); - gl.getExtension('WEBGL_lose_context')?.loseContext(); + // The next lens reuses this context after its old stream has stopped. + gl.clearColor(0,0,0,1); gl.clear(gl.COLOR_BUFFER_BIT); }); return closing; }; @@ -210,17 +252,25 @@ gl.texParameteri(gl.TEXTURE_2D,gl.TEXTURE_WRAP_T,gl.CLAMP_TO_EDGE); gl.uniform1i(gl.getUniformLocation(program,name),index); }); + gl.viewport(0,0,canvas.width,canvas.height); + const pixels = meta.width * meta.height; + gl.activeTexture(gl.TEXTURE0); + gl.texImage2D(gl.TEXTURE_2D,0,gl.LUMINANCE,meta.width,meta.height,0,gl.LUMINANCE,gl.UNSIGNED_BYTE,null); + gl.activeTexture(gl.TEXTURE1); + gl.texImage2D(gl.TEXTURE_2D,0,gl.LUMINANCE_ALPHA,meta.width/2,meta.height/2,0,gl.LUMINANCE_ALPHA,gl.UNSIGNED_BYTE,null); const draw = async () => { + const requested = performance.now(); const frame = await call({action:'frame'}); + timing.transferMs += performance.now()-requested; if (stopped || !frame.data || frame.sequence === sequence) return false; const data = new Uint8Array(frame.data), w = meta.width, h = meta.height; if (data.length !== w * h * 3 / 2) throw Error('Invalid native camera frame'); - sequence = frame.sequence; + sequence = frame.sequence; timing.sensorFrames = sequence; const start=performance.now(); gl.activeTexture(gl.TEXTURE0); gl.bindTexture(gl.TEXTURE_2D,textures[0]); - gl.texImage2D(gl.TEXTURE_2D,0,gl.LUMINANCE,w,h,0,gl.LUMINANCE,gl.UNSIGNED_BYTE,data.subarray(0,w*h)); + gl.texSubImage2D(gl.TEXTURE_2D,0,0,0,w,h,gl.LUMINANCE,gl.UNSIGNED_BYTE,data.subarray(0,pixels)); gl.activeTexture(gl.TEXTURE1); gl.bindTexture(gl.TEXTURE_2D,textures[1]); - gl.texImage2D(gl.TEXTURE_2D,0,gl.LUMINANCE_ALPHA,w/2,h/2,0,gl.LUMINANCE_ALPHA,gl.UNSIGNED_BYTE,data.subarray(w*h)); + gl.texSubImage2D(gl.TEXTURE_2D,0,0,0,w/2,h/2,gl.LUMINANCE_ALPHA,gl.UNSIGNED_BYTE,data.subarray(pixels)); gl.drawArrays(gl.TRIANGLE_STRIP,0,4); timing.frames++;timing.conversionMs+=performance.now()-start; return true; @@ -230,7 +280,11 @@ if (Date.now() > deadline) throw new DOMException('No camera frames', 'NotReadableError'); await new Promise(resolve => setTimeout(resolve, 50)); } - stream = canvas.captureStream(24); + stream = new MediaStream(); + stream.vulpesPreviewCanvas = canvas; + stream.vulpesClosed = false; + // A canvas has no video element's implicit screen wake lock. + wakeLock = navigator.requestWakeLock?.('screen'); stream.vulpesTiming = timing; stream.vulpesNative = true; stream.vulpesCameraMetadata = meta; @@ -245,15 +299,28 @@ const started = performance.now(); try { await draw(); } catch (error) { - if (!stopped) { recordError(error); stream.getTracks().forEach(track => track.stop()); } + if (!stopped) { recordError(error); await stop().catch(recordError); } return; } - if (!stopped) timer = setTimeout(next, Math.max(0, 1000/24-(performance.now()-started))); + if (!stopped) timer = setTimeout(next, Math.max(0, 1000/30-(performance.now()-started))); }; - timer = setTimeout(next, 1000/24); + timer = setTimeout(next, 1000/30); return stream; } catch (error) { await stop().catch(() => {}); throw error; } } + async function captureVideo(constraints, ticket, platform) { + // track.stop() returns before some Android camera drivers finish closing. + // Retry the same sensor, never replace a refused/missing camera silently. + for (let attempt = 0; ; attempt++) { + if (document.hidden || ticket !== generation) + throw new DOMException('Camera request superseded or hidden', 'AbortError'); + try { return await navigator.mediaDevices.getUserMedia(constraints); } + catch (error) { + if (platform !== 'android' || error.name !== 'NotReadableError' || attempt >= 2) throw error; + await new Promise(resolve => setTimeout(resolve, 200 * (attempt + 1))); + } + } + } function getCamera(name, config = {}) { const ticket = ++generation; const request = opening.then(() => openCamera(name, config, ticket)); @@ -275,7 +342,7 @@ const platform = await VulpesCompat.call('platform.capabilities', {}); if (platform.platform === 'tundra' && !platform.camera) throw new DOMException('Native camera adapter unavailable', 'NotSupportedError'); - stream = platform.platform === 'tundra' ? await nativeStream(name) : await navigator.mediaDevices.getUserMedia({ + stream = platform.platform === 'tundra' ? await nativeStream(name) : await captureVideo({ audio: false, video: { ...(selected @@ -284,7 +351,7 @@ width: { ideal: 1920 }, height: { ideal: 1080 }, }, - }); + }, ticket, platform.platform); } catch (error) { recordError(error); throw error; @@ -304,44 +371,55 @@ throw new DOMException('Camera hidden', 'AbortError'); const track = stream.getVideoTracks()[0]; - const settings = track.getSettings(); - const size = { width: settings.width, height: settings.height }; - preview = document.createElement('video'); - preview.muted = true; - preview.playsInline = true; - preview.srcObject = stream; - preview.dataset.vulpesCapture = ''; - preview.style.cssText = 'position:fixed;width:1px;height:1px;opacity:0;pointer-events:none'; - document.body.append(preview); - let previewTimeout; - await Promise.race([ - preview.play(), - new Promise((_, reject) => { - previewTimeout = setTimeout( - () => reject(new DOMException('Camera preview timed out', 'NotReadableError')), - 10000, - ); - }), - ]).finally(() => clearTimeout(previewTimeout)); + const native = stream.vulpesCameraMetadata; + let size; + if (native) { + // Preview dimensions and still photos come directly from the HAL. + // The native canvas needs no hidden capture video. + const sideways = native.orientation % 180 !== 0; + size = {width:sideways ? native.height : native.width, + height:sideways ? native.width : native.height}; + } else { + preview = document.createElement('video'); + preview.muted = true; + preview.playsInline = true; + preview.srcObject = stream; + preview.dataset.vulpesCapture = ''; + preview.style.cssText = 'position:fixed;width:1px;height:1px;opacity:0;pointer-events:none'; + document.body.append(preview); + let previewTimeout; + await Promise.race([ + preview.play(), + new Promise((_, reject) => { + previewTimeout = setTimeout( + () => reject(new DOMException('Camera preview timed out', 'NotReadableError')), + 10000, + ); + }), + ]).finally(() => clearTimeout(previewTimeout)); + const settings = stream.getVideoTracks()[0].getSettings(); + size = {width:preview.videoWidth || settings.width, + height:preview.videoHeight || settings.height}; + } if (document.hidden || ticket !== generation) throw new DOMException('Camera hidden', 'AbortError'); - const native = stream.vulpesCameraMetadata; const pictureSizes = native?.pictureSizes || [size]; let pictureSize = pictureSizes.reduce((a,b)=>a.width*a.height>b.width*b.height?a:b); - let focusArea = null, zoom = 1; + let focusArea = null, zoom = 1, mode = 'picture', recording, startingRecording = false; + const profile = {containerFormat:'webm',video:{width:size.width,height:size.height,codec:'vp8',bitrate:1500000,framerate:30},audio:{codec:'opus',samplerate:48000,channels:1,bitrate:64000}}; const configuration = () => ({ - mode: 'picture', + mode, previewSize: size, pictureSize, - recorderProfile: null, + recorderProfile: videoMime ? 'webm' : null, }); Object.assign(stream, { capabilities: { pictureSizes, previewSizes: [size], thumbnailSizes: [], - recorderProfiles: {}, + recorderProfiles: videoMime ? {webm:profile} : {}, flashModes: ['off'], focusModes: native ? (native.focusModes.includes('auto') ? ['auto','infinity'] : ['fixed']) : ['fixed'], sceneModes: ['auto'], @@ -359,8 +437,9 @@ zoom: 1, async setConfiguration(value) { if (value.pictureSize) stream.setPictureSize(value.pictureSize); - if (value.mode && value.mode !== 'picture') + if (value.mode && !['picture',...(videoMime ? ['video'] : [])].includes(value.mode)) throw new DOMException('Video unavailable', 'NotSupportedError'); + if(value.mode)mode=value.mode; queueMicrotask(() => emit(stream, 'previewstatechange', { newState: 'started' })); return configuration(); }, @@ -415,10 +494,66 @@ release() { return releaseStream(stream, preview); }, - async startRecording() { - throw new DOMException('Video unavailable', 'NotSupportedError'); + async startRecording(options, storage, filepath) { + if(!videoMime)throw new DOMException('Video unavailable','NotSupportedError'); + if(recording || startingRecording)throw new DOMException('Recording in progress','InvalidStateError'); + startingRecording=true; + let audio, captured; + try { + audio=await navigator.mediaDevices.getUserMedia({audio:true,video:false}); + if(current!==stream || document.hidden)throw new DOMException('Camera closed','AbortError'); + // Only recording captures the native canvas. The preview keeps its + // direct WebGL surface and the capture track is stopped before a switch. + captured=native ? stream.vulpesPreviewCanvas.captureStream(30) : new MediaStream(stream.getVideoTracks().map(t=>t.clone())); + for(const track of audio.getAudioTracks())captured.addTrack(track); + const recorder=new MediaRecorder(captured,{mimeType:videoMime,videoBitsPerSecond:1500000,audioBitsPerSecond:64000}); + const chunks=[];let bytes=0,failed=false,finish; + const done=new Promise(resolve=>{finish=resolve;}); + const cap=Math.min(options.maxFileSizeBytes || Infinity,32*1024*1024); + recorder.ondataavailable=event=>{ + if(event.data.size) {chunks.push(event.data);bytes+=event.data.size;} + if(bytes>=cap && recorder.state==='recording') { + emit(stream,'recorderstatechange',{newState:'FileSizeLimitReached'}); + recorder.stop(); + } + }; + recorder.onerror=()=>{failed=true;emit(stream,'recorderstatechange',{newState:'MediaRecorderFailed'});}; + recorder.onstop=async()=>{ + try { + if(failed || !bytes)throw Error('Empty or failed recording'); + const blob=new Blob(chunks,{type:'video/webm'}); + await storage.addNamed(blob,filepath); + const canvas=document.createElement('canvas'); + const source=native ? stream.vulpesPreviewCanvas : preview; + canvas.width=native ? source.width : preview.videoWidth; + canvas.height=native ? source.height : preview.videoHeight; + canvas.getContext('2d').drawImage(source,0,0,canvas.width,canvas.height); + const poster=await new Promise(resolve=>canvas.toBlob(resolve,'image/jpeg',0.9)); + if(!poster)throw Error('Video poster failed'); + emit(stream,'poster',{data:poster}); + } catch(error) { + recordError(error);emit(stream,'recorderstatechange',{newState:'MediaRecorderFailed'}); + } finally { + captured.getTracks().forEach(t=>t.stop()); + chunks.length=0;recording=null;finish(); + emit(stream,'recorderstatechange',{newState:'Stopped'}); + } + }; + recording={recorder,done}; + recorder.start(500); + emit(stream,'recorderstatechange',{newState:'Started'}); + } catch(error) { + captured?.getTracks().forEach(t=>t.stop());audio?.getTracks().forEach(t=>t.stop()); + recording=null;throw error; + } finally {startingRecording=false;} + }, + stopRecording() { + if(recording?.recorder.state==='recording')recording.recorder.stop(); + }, + async vulpesFinishRecording() { + if(!recording)return; + const done=recording.done;stream.stopRecording();await done; }, - stopRecording() {}, }); if (native) Object.defineProperty(stream,'zoom',{ configurable:true, get:()=>zoom, @@ -433,7 +568,7 @@ }); dispatchEvent( new CustomEvent('vulpes-camera-capabilities', { - detail: { cameras: [...cameras], flash: false, video: false }, + detail: { cameras: [...cameras], flash: false, video: !!videoMime }, }), ); return { camera: stream, configuration: configuration() }; @@ -479,7 +614,7 @@ 'Only one camera available', 'toggle-camera-rear', ], - ['.mode-switch', true, 'Vidéo pas encore disponible', 'Video is not available yet', null], + ['.mode-switch', !videoMime, 'Vidéo pas encore disponible', 'Video is not available yet', null], ]; const fr = (document.documentElement.lang || navigator.language).startsWith('fr'); for (const [selector, disabled, french, english, icon] of controls) { @@ -489,7 +624,7 @@ e.setAttribute('aria-disabled', String(disabled)); if (!disabled) { e.removeAttribute('title'); - e.setAttribute('aria-label', fr ? 'Changer de caméra' : 'Switch camera'); + if(selector==='.js-camera')e.setAttribute('aria-label', fr ? 'Changer de caméra' : 'Switch camera'); } if (disabled) { e.removeAttribute('data-l10n-id'); diff --git a/host/gaia-compat.js b/host/gaia-compat.js index b0c4627b..a56ecc2c 100644 --- a/host/gaia-compat.js +++ b/host/gaia-compat.js @@ -67,7 +67,25 @@ const unsupported = () => Promise.reject(new DOMException('Not available on this adapter', 'NotSupportedError')); telephony.conferenceGroup = Object.assign(new EventTarget(), {calls:[], state:'', add:unsupported, remove:unsupported, hangUp:unsupported}); telephony.ready = Promise.resolve(); - telephony.dial = number => call('platform.dial',{number}); + class TelephonyCall extends EventTarget {} + Object.defineProperty(window,'TelephonyCall',{value:TelephonyCall}); + function makeCall(item) { + return Object.assign(new TelephonyCall(),{path:item.path,id:{number:item.number},serviceId:0, + state:({active:'connected',waiting:'incoming'})[item.state] || item.state, + emergency:false,group:null,switchable:false,mergeable:false, + answer:()=>call('platform.answer',{path:item.path}),hangUp:()=>call('platform.hangup',{path:item.path}), + hold:unsupported,resume:unsupported}); + } + telephony.dial = async number => { + const result = await call('platform.dial',{number}); + let current = telephony.calls.find(c=>c.path===result.path); + if (!current) { + current = makeCall({path:result.path,number,state:'dialing'}); + telephony.calls.push(current); + emit(telephony,'callschanged'); + } + return current; + }; telephony.dialEmergency = unsupported; telephony.sendTones = unsupported; telephony.startTone = unsupported; @@ -94,14 +112,17 @@ const state = ({active:'connected',waiting:'incoming'})[item.state] || item.state; if (!c) { changed = true; - c = Object.assign(new EventTarget(),{path:item.path,id:{number:item.number}, serviceId:0, - state, emergency:false, group:null, switchable:false, mergeable:false, - answer:()=>call('platform.answer',{path:item.path}), hangUp:()=>call('platform.hangup',{path:item.path}), - hold:unsupported,resume:unsupported}); + c = makeCall(item); } else if (c.state !== state) { c.state=state;changes.push(c); } return c; }); - for (const c of old) if (!telephony.calls.includes(c)) {c.state='disconnected';changes.push(c);changed=true;} + for (const c of old) if (!telephony.calls.includes(c)) { + c.state='disconnected'; + // The broker currently supplies no modem disconnect cause. Avoid + // inventing an error when a call simply disappears after hang-up. + c.disconnectedReason=null; + changes.push(c);changed=true; + } for (const c of changes) {emit(c,'statechange',{call:c});emit(c,c.state,{call:c});} if (changed) { if (location.hostname === 'system.localhost' && live.some(c => ['incoming','waiting'].includes(c.state))) window.Service?.request('turnScreenOn'); @@ -131,13 +152,13 @@ const settingsCodec = window.VulpesSettingsCodec; async function call(method, params) { if (!navigator.vulpesRequest) return Promise.reject(new Error('VULPES_HOST_UNAVAILABLE')); - if (settingsCodec && (method === 'settings.set' || + if (settingsCodec && (method === 'settings.set' || method === 'messages.request' || (window.VulpesPlatform === 'android' && ['media.request', 'activities.finish', 'contacts.request'].includes(method)))) params = await settingsCodec.encode(params); return navigator.vulpesRequest({ version: 1, id: ++sequence, method, params }).then((reply) => { if (reply.error) throw new DOMException(reply.error.code, reply.error.code); return settingsCodec && - ['settings.get', 'settings.snapshot', 'media.request', 'activities.start', 'contacts.request'].includes(method) + ['messages.request', 'settings.get', 'settings.snapshot', 'media.request', 'activities.start', 'contacts.request'].includes(method) ? settingsCodec.decode(reply.result) : reply.result; }); @@ -217,11 +238,15 @@ } if (type === 'callscreen-closed') document.querySelector('iframe[name="call_screen"]')?.dispatchEvent(new CustomEvent('mozbrowserclose',{bubbles:true})); if (type === 'hardware-key') dispatchEvent(new CustomEvent('softwareButtonEvent', {detail:data})); + if (type === 'activity-choice') dispatchEvent(new CustomEvent('mozChromeEvent', {detail:{...data,type:'activity-choice'}})); if (type === 'activity-launch') dispatchEvent(new CustomEvent('open-app', {detail:{...data,showApp:true,onlyShowApp:false}})); if (type === 'launch') dispatchEvent(new CustomEvent('webapps-launch', { detail: data })); if (type === 'screen-off') { - dispatchEvent(new CustomEvent('secure-killapps')); - window.Service?.request('lock').catch(console.error); + settings.createLock().get('lockscreen.enabled').then(values => { + if (values['lockscreen.enabled'] === false) return; + dispatchEvent(new CustomEvent('secure-killapps')); + window.Service?.request('lock').catch(console.error); + }).catch(console.error); } if (type === 'screen-on') window.ScreenManager?.turnScreenOn(true); if (type === 'home' || type === 'holdhome') { @@ -230,6 +255,10 @@ } if (type === 'activity-view') dispatchEvent(new CustomEvent('activity-view', { detail: data })); }); + if (location.hostname==='system.localhost') addEventListener('mozContentEvent', event=>{ + if (event.detail?.type==='activity-choice') + call('activities.select',{id:event.detail.id,value:event.detail.value}).catch(console.error); + }); function application(record) { // DOMApplication never exposed the registry's internal id. Gaia uses `id` // to distinguish a bookmark from an installed application. @@ -520,7 +549,7 @@ frames.add(frame); // Set delegation before navigation; a MutationObserver runs too late on Android. if (location.hostname === 'system.localhost') - frame.setAttribute('allow', 'camera http://camera.localhost:' + location.port); + frame.setAttribute('allow', 'camera http://camera.localhost:' + location.port + '; microphone http://camera.localhost:' + location.port); frame.addEventListener('load', () => { if (!frame.hasAttribute('mozbrowser')) return; if (webViews.has(frame)) return; diff --git a/host/gaia-input.js b/host/gaia-input.js index 662aee9b..59f6c7e3 100644 --- a/host/gaia-input.js +++ b/host/gaia-input.js @@ -1,6 +1,10 @@ // Keep Gaia's keyboard and layouts; replace its retired Gecko input-method API. (() => { - if (!window.VulpesNativeInput) return; + function start() { + // Android emits focus only when enabled. Install the keyboard API before Gaia's + // startup scripts, without waiting for the asynchronous preference handshake. + if ((!window.VulpesNativeInput && window.VulpesPlatform !== 'android') || window.__vulpesInputInstalled) return; + window.__vulpesInputInstalled=true; const app=location.hostname.split('.')[0], {call}=VulpesCompat; if (app==='keyboard') { let viewport, pendingState; @@ -16,13 +20,14 @@ dispatchEvent(new Event('resize')); if(pendingState!==undefined) {setState(pendingState);pendingState=undefined;} }); - addEventListener('DOMContentLoaded', () => { + const ready = () => { ViewManager.prototype.screenInPortraitMode = function() { return viewport ? viewport.width<=viewport.height : this.cachedWindowWidth<=this.cachedWindowHeight; }; parent.postMessage({type:'vulpes-keyboard-ready'},'http://system.localhost:8765'); - }, {once:true}); + }; + if(document.readyState==='loading')addEventListener('DOMContentLoaded',ready,{once:true});else ready(); const ime=Object.assign(new EventTarget(),{inputcontext:null,mgmt:{ supportsSwitching:()=>false, hide:()=>call('input.request',{operation:'hide'}), @@ -93,4 +98,7 @@ for(const type of ['home','lockscreen-appopened','appwillclose']) addEventListener(type,()=>call('input.request',{operation:'hide'}).catch(console.error)); } + } + addEventListener('vulpes-input-configured',start); + start(); })(); diff --git a/host/gaia-mobile-settings.js b/host/gaia-mobile-settings.js new file mode 100644 index 00000000..628e1849 --- /dev/null +++ b/host/gaia-mobile-settings.js @@ -0,0 +1,366 @@ +// Expose the single native SIM to the original Gaia Settings controllers. +// Other applications retain their existing telephony/message adapters. +(() => { + if (location.hostname !== 'settings.localhost' || !window.VulpesNativeTelephony) return; + const {call} = VulpesCompat; + const read = action => call('platform.mobileSettings', {action}); + const unavailable = () => Promise.reject(new DOMException('Not supported by this adapter', 'NotSupportedError')); + const icc = Object.assign(new EventTarget(), {cardState:'unknown', iccInfo:null}); + const conn = Object.assign(new EventTarget(), {iccId:null, radioState:'unknown', supportedNetworkTypes:[], voice:{type:null}, data:{type:null}}); + const manager = Object.assign(new EventTarget(), {iccIds:[], getIccById:id => id && id === conn.iccId ? icc : null}); + let fixedDialing, pinEnabled; + icc.getCardLock = async name => { + await VulpesMobileSettings.ready; + const enabled = name === 'fdn' ? fixedDialing : name === 'pin' ? pinEnabled : undefined; + if (typeof enabled !== 'boolean') return unavailable(); + return {enabled}; + }; + // Fixed-dialling changes, PIN operations and SIM contacts are not ported. + icc.getServiceState = () => Promise.resolve(false); + for (const method of ['unlockCardLock','setCardLock','getCardLockRetryCount','readContacts','updateContact']) icc[method] = unavailable; + // Supplementary services can fail independently of reading the SIM itself. + // Do not pretend a modem query is a successful preference change. + conn.getCallingLineIdRestriction = unavailable; + conn.setCallingLineIdRestriction = unavailable; + conn.getCallWaitingOption = () => read('calls').then(value => { + if (!['enabled','disabled'].includes(value.VoiceCallWaiting)) throw Error('RADIO_NOT_READY'); + return value.VoiceCallWaiting === 'enabled'; + }); + conn.setCallWaitingOption = unavailable; + conn.getVoicePrivacyMode = unavailable; conn.setVoicePrivacyMode = unavailable; + Object.defineProperty(navigator,'mozMobileConnections',{value:[conn]}); + Object.defineProperty(navigator,'mozIccManager',{value:manager}); + Object.defineProperty(navigator,'mozMobileMessage',{value:{getSmscAddress:() => read('smsc').then(v=>({address:v.address || ''}))}}); + async function refresh() { + const state = await read('sim'); + const previous = conn.iccId; + conn.radioState = state.radioEnabled ? 'enabled' : 'disabled'; + conn.dispatchEvent(new Event('radiostatechange')); + conn.iccId = state.present ? state.id : null; + fixedDialing = state.fixedDialing; pinEnabled = state.pinEnabled; + manager.iccIds = conn.iccId ? [conn.iccId] : []; + icc.cardState = !state.present ? 'absent' : state.pin === 'none' ? 'ready' : state.pin === 'pin' ? 'pinRequired' : state.pin === 'puk' ? 'pukRequired' : 'unknown'; + icc.iccInfo = {iccid:conn.iccId, spn:state.operator, msisdn:state.numbers?.[0] || '', iccType:'sim', isDisplaySpnRequired:true}; + if (previous !== conn.iccId) manager.dispatchEvent(Object.assign(new Event(conn.iccId ? 'iccdetected' : 'iccundetected'),{iccId:conn.iccId || previous})); + icc.dispatchEvent(new Event('cardstatechange')); icc.dispatchEvent(new Event('iccinfochange')); + } + let messageState, changing = false; + async function messaging(panel) { + const control=panel.querySelector('[name="ril.sms.requestStatusReport.enabled"]'); + if (!control || control.dataset.vulpesBound) return; + control.dataset.vulpesBound='true'; control.disabled=true; + control.addEventListener('change', async event => { + event.stopImmediatePropagation(); + const desired=control.checked; + if (changing || typeof messageState !== 'boolean') return; + changing=true;control.disabled=true; + try { + const result=await call('platform.mobileSettings',{action:'deliveryReports',enabled:desired}); + messageState=result.deliveryReports; + await navigator.mozSettings.createLock().set({'ril.sms.requestStatusReport.enabled':messageState}); + } catch(error) {window.VulpesGaiaUI?.notice('SMS: '+error.message);} + finally {control.checked=messageState;changing=false;control.disabled=false;} + },true); + try { + const state=await read('messages'); + if (typeof state.deliveryReports !== 'boolean') return; + messageState=state.deliveryReports; + control.checked=messageState; + await navigator.mozSettings.createLock().set({'ril.sms.requestStatusReport.enabled':messageState}); + control.disabled=false;control.closest('li').removeAttribute('aria-disabled'); + control.dataset.vulpesSupported='true'; + } catch(error) {console.warn('Message settings:',error.message);} + } + function carrierPanel() { + let panel, state, pending=false; + const controls = () => [panel.querySelector('#menuItem-enableDataCall gaia-switch'), panel.querySelector('#menuItem-enableDataRoaming gaia-switch')]; + async function sync() { + if(typeof state?.enabled==='boolean' && typeof state?.roaming==='boolean') + await navigator.mozSettings.createLock().set({'ril.data.enabled':state.enabled,'ril.data.roaming_enabled':state.roaming}); + } + function render() { + const [data,roaming]=controls(); + for(const [control,key] of [[data,'enabled'],[roaming,'roaming']]) { + control.disabled=pending || typeof state?.[key]!=='boolean'; + control.checked=state?.[key]===true; + control.closest('li').setAttribute('aria-disabled',String(control.disabled)); + } + panel.querySelector('.carrier-info').hidden=false; + panel.querySelector('.carrier-advancedSettings').hidden=false; + panel.querySelector('#dataNetwork-desc').textContent=[state?.operator,(state?.technology||'').toUpperCase(),state?.status].filter(Boolean).join(' · '); + } + return { + onInit(element) { + panel=element; + for(const [control,action] of [[controls()[0],'dataEnabled'],[controls()[1],'roaming']]) { + control.addEventListener('change',async event=>{ + event.stopImmediatePropagation();if(pending)return; + const enabled=control.checked;pending=true;render(); + try {state=await call('platform.mobileSettings',{action,enabled});await sync();} + catch(error) {window.VulpesGaiaUI?.notice(error.message);} + finally {pending=false;render();} + },true); + } + }, + async onBeforeShow() { + pending=true;render(); + try {state=await read('network');await sync();} + catch(error) {window.VulpesGaiaUI?.notice(error.message);} + finally {pending=false;render();} + } + }; + } + const fr = () => document.documentElement.lang.toLowerCase().startsWith('fr'); + const text = (f,e) => fr()?f:e; + const navigate = (id,options={}) => require(['modules/settings_service'],service=>service.navigate(id,options)); + function explain(panel,key,message) { + let row=panel.querySelector('[data-vulpes-note="'+key+'"]'); + if(!row) {row=document.createElement('p');row.className='explanation';row.dataset.vulpesNote=key;(panel.querySelector(':scope > div')||panel).prepend(row);} + row.textContent=message; + } + function failure(error) { + const messages={ + APN_IN_USE:text('Désactivez les données mobiles avant de modifier ce profil actif.','Turn off mobile data before editing this active profile.'), + APN_WRITE_FAILED:text('Enregistrement impossible. Le profil précédent a été restauré.','Could not save. The previous profile was restored.'), + APN_ROLLBACK_FAILED:text('Le profil n’a pas pu être restauré. Vérifiez ses paramètres avant de réactiver les données.','The profile could not be restored. Check its settings before re-enabling data.'), + RADIO_READBACK_FAILED:text('Le réseau n’a pas confirmé le réglage. Son état va être relu.','The network did not confirm the setting. Its state will be read again.'), + INVALID_REQUEST:text('Vérifiez les valeurs saisies.','Check the values you entered.') + }; + window.VulpesGaiaUI?.notice(messages[error.message]||text('Le service réseau n’a pas pu appliquer ce réglage.','The network service could not apply this setting.')); + } + function callPanel() { + let panel,waiting,caller,state,pending=false; + const modes={default:'CLIR_DEFAULT',enabled:'CLIR_INVOCATION',disabled:'CLIR_SUPPRESSION'}; + function render() { + waiting.disabled=pending || !['enabled','disabled'].includes(state?.VoiceCallWaiting); + waiting.checked=state?.VoiceCallWaiting==='enabled'; + waiting.closest('li').dataset.state=waiting.disabled?'unknown':waiting.checked?'on':'off'; + caller.disabled=pending || !Object.hasOwn(modes,state?.HideCallerId); + caller.value=modes[state?.HideCallerId]||''; + for(const c of [waiting,caller])c.closest('li').setAttribute('aria-disabled',String(c.disabled)); + explain(panel,'call-state',pending?text('Interrogation du réseau… Cela peut prendre plusieurs secondes.','Querying the network… This may take several seconds.'):text('Le double appel et la présentation du numéro utilisent les réglages de votre opérateur.','Call waiting and caller ID use your carrier’s settings.')); + } + async function update(params) { + if(pending)return; + pending=true;render(); + try {state=await call('platform.mobileSettings',params);} + catch(error) {state=null;failure(error);try {state=await read('calls');}catch(_) {}} + finally {pending=false;render();} + } + return {onInit(element){ + panel=element;waiting=panel.querySelector('#menuItem-callWaiting gaia-switch');caller=panel.querySelector('#ril-callerId');caller.removeAttribute('data-setting'); + for(const id of ['menuItem-callWaiting','menuItem-callerId','menuItem-callForwarding','menuItem-callFdn','menuItem-callBarring'])panel.querySelector('#'+id).hidden=false; + waiting.addEventListener('change',()=>update({action:'setCallWaiting',enabled:waiting.checked})); + caller.addEventListener('change',()=>update({action:'setCallerId',mode:Object.keys(modes).find(key=>modes[key]===caller.value)})); + for(const [id,target] of [['menuItem-callForwarding','call-forwarding'],['menuItem-callFdn','call-fdnSettings'],['menuItem-callBarring','call-cbSettings']]) { + const a=panel.querySelector('#'+id+' a');a.removeAttribute('data-href');a.href='#'+target; + a.addEventListener('click',event=>{event.preventDefault();event.stopImmediatePropagation();navigate(target);},true); + } + const voicemail=panel.querySelector('.menuItem-voicemail');voicemail.setAttribute('aria-disabled','true'); + voicemail.addEventListener('click',event=>{event.preventDefault();event.stopImmediatePropagation();},true); + document.l10n.setAttributes(panel.querySelector('#voiceMail-desc'),'unavailable'); + explain(panel,'call-limits',text('Le numéro de messagerie vocale, les numéros autorisés et le blocage des appels ne sont pas encore raccordés.','Voicemail number, fixed dialling and call barring are not connected yet.')); + render(); + },onBeforeShow(){void update({action:'calls'});}}; + } + function forwardingPanel() { + let panel,state,pending=false; + const rows={ + unconditional:['cfu','call-cf-unconditional-settings','.callForwardingUnconditional'], + busy:['cfmb','call-cf-mobile-busy-settings','.callForwardingMobileBusy'], + noReply:['cfnrep','call-cf-no-reply-settings','.callForwardingNoReply'], + notReachable:['cfnrea','call-cf-not-reachable-settings','.callForwardingNotReachable'] + }; + function render() { + for(const [reason,[id,,selector]] of Object.entries(rows)) { + const known=typeof state?.[reason]==='string'; + panel.querySelector(selector).setAttribute('aria-disabled',String(pending||!known)); + const node=panel.querySelector('#'+id+'-desc'); + document.l10n.setAttributes(node,pending?'callSettingsQuery':!known?'callSettingsQueryError':state[reason]?'callForwardingForwardingVoiceToNumber':'callForwardingNotForwarding',state?.[reason]?{number:state[reason]}:{}); + } + } + async function load() { + if(pending)return; + pending=true;render(); + try {state=await read('forwarding');}catch(error){state=null;failure(error);} + finally {pending=false;render();} + } + return {onInit(element){ + panel=element; + for(const [reason,[,dialog,selector]] of Object.entries(rows))panel.querySelector(selector).addEventListener('click',async event=>{ + event.preventDefault();event.stopImmediatePropagation(); + if(pending || typeof state?.[reason]!=='string')return; + const service=await new Promise(resolve=>require(['modules/dialog_service'],resolve)); + const result=await service.show(dialog,{number:state[reason],enabled:!!state[reason]}); + if(result.type!=='submit')return; + const enabled=!!result.value.enabled; + const number=enabled?String(result.value.number||'').replace(/[\s()-]/g,''):''; + pending=true;render(); + try {state=await call('platform.mobileSettings',{action:'setForwarding',reason,enabled,number});} + catch(error){failure(error);try{state=await read('forwarding');}catch(_){state=null;}} + finally {pending=false;render();} + }); + },onBeforeShow(){void load();}}; + } + + function operatorPanel() { + let panel,select,pending=false,state; + return { + onInit(element) { + panel=element;select=panel.querySelector('.preferred-network-type select'); + // Operator scanning/registration has a different service and remains + // unavailable; it must not be confused with the radio mode selector. + panel.querySelector('.auto-select gaia-switch').disabled=true; + panel.querySelector('.search-btn').disabled=true; + select.addEventListener('change',async()=>{ + if(pending || !state)return; + const mode=select.value;pending=true;select.disabled=true; + try {state=await call('platform.mobileSettings',{action:'setRadioMode',mode});} + catch(error){failure(error);} + finally {select.value=state.mode;pending=false;select.disabled=false;} + }); + }, + async onBeforeShow() { + select.disabled=true; + explain(panel,'operator',text('Le type de réseau est réglable. La recherche et la sélection manuelle d’opérateur ne sont pas encore raccordées.','Network type can be changed. Operator scanning and manual selection are not connected yet.')); + try { + const [radio,network]=await Promise.all([read('radioMode'),read('network')]);state=radio; + select.replaceChildren(); + const names={any:text('Automatique','Automatic'),gsm:'2G (GSM)',umts:'3G (UMTS)',lte:'4G (LTE)'}; + for(const mode of radio.available)select.add(new Option(names[mode]||mode,mode)); + select.value=radio.mode;select.disabled=!radio.available.includes(radio.mode); + panel.querySelector('.auto-select gaia-switch').checked=network.mode==='auto'; + } catch(error){failure(error);} + } + }; + } + function apnSettingsPanel() { + return {onInit(panel){ + for(const anchor of panel.querySelectorAll('[data-apn-type]')) { + const supported=['default','mms'].includes(anchor.dataset.apnType); + if(!supported)anchor.setAttribute('aria-disabled','true'); + anchor.addEventListener('click',event=>{ + event.preventDefault();event.stopImmediatePropagation(); + if(supported)navigate('apn-list',{type:anchor.dataset.apnType}); + }); + } + panel.querySelector('.reset-apn').disabled=true; + },onBeforeShow(panel){ + explain(panel,'apn',text('Modifiez les profils Internet et MMS existants. Les profils IMS restent gérés par le modem. L’ajout, la suppression et la réinitialisation ne sont pas encore disponibles.','Edit existing Internet and MMS profiles. IMS profiles remain managed by the modem. Adding, deleting and resetting profiles are not available yet.')); + }}; + } + function apnListPanel() { + let panel,type='default'; + return {onInit(element){panel=element;panel.querySelector('.add-apn').disabled=true;panel.querySelector('gaia-header').dataset.href='#apn-settings';}, + async onBeforeShow(element,options={}) { + type=options.type==='mms'?'mms':'default'; + document.l10n.setAttributes(panel.querySelector('h1'),type==='mms'?'messageSettings-header':'dataSettings-header'); + const list=panel.querySelector('.apn-list');list.replaceChildren(); + try { + const profiles=(await read('apns')).filter(p=>p.type===type); + const template=await new Promise(resolve=>require(['panels/apn_list/apn_template_factory'],factory=>resolve(factory(type,item=>navigate('apn-editor',{profileId:item.id,type}),()=>{})))); + for(const profile of profiles) { + const li=template({id:profile.id,apn:{apn:profile.fields.apn},active:profile.active}); + // This mark reflects an active data context; changing the profile + // does not activate/deactivate a bearer implicitly. + const radio=li.querySelector('gaia-radio');radio.setAttribute('aria-readonly','true'); + list.append(li); + } + explain(panel,'list',profiles.length?text('Touchez le nom du profil pour le modifier.','Tap a profile name to edit it.'):text('Aucun profil fourni par le modem.','The modem returned no profile.')); + }catch(error){failure(error);} + } + }; + } + function apnEditorPanel() { + let panel,profile,pending=false,dirty=new Set(); + const selectors={apn:'.apn',user:'.user',password:'.password',protocol:'.protocol',authtype:'.authtype',mmsc:'.mmsc',mmsproxy:'.mmsproxy'}; + const protocols={ip:'IP',ipv6:'IPV6',dual:'IPV4V6'}; + const auth={none:'none',pap:'pap',chap:'chap',any:'papOrChap'}; + function editable() { + for(const [key,selector] of Object.entries(selectors))panel.querySelector(selector).disabled=pending || !profile || (['mmsc','mmsproxy'].includes(key)&&profile.type!=='mms'); + panel.querySelector('.ok').disabled=pending || !profile; + } + function render() { + editable(); + for(const [key,selector] of Object.entries(selectors)) { + const input=panel.querySelector(selector); + input.disabled=pending || !profile || (['mmsc','mmsproxy'].includes(key)&&profile.type!=='mms'); + const value=profile?.fields[key]||''; + input.value=key==='password'?'':key==='protocol'?protocols[value]||'notDefined':key==='authtype'?auth[value]||'notDefined':value; + if(key==='password')input.placeholder=profile?.hasPassword?text('Inchangé','Unchanged'):''; + } + panel.querySelector('.ok').disabled=pending || !profile; + } + return {onInit(element){ + panel=element; + for(const input of panel.querySelectorAll('input,select,button'))input.disabled=true; + for(const [key,selector] of Object.entries(selectors))for(const event of ['input','change'])panel.querySelector(selector).addEventListener(event,()=>dirty.add(key)); + panel.querySelector('.ok').addEventListener('click',async()=>{ + if(pending||!profile)return; + const fields={}; + for(const key of dirty) { + const value=panel.querySelector(selectors[key]).value; + fields[key]=key==='protocol'?Object.keys(protocols).find(k=>protocols[k]===value):key==='authtype'?Object.keys(auth).find(k=>auth[k]===value):value; + if(fields[key]===undefined){failure({message:'INVALID_REQUEST'});return;} + } + if(!dirty.size){navigate('apn-list',{type:profile.type});return;} + pending=true;editable(); + try { + profile=await call('platform.mobileSettings',{action:'editApn',id:profile.id,fields}); + dirty.clear();navigate('apn-list',{type:profile.type}); + }catch(error){failure(error);} + finally {pending=false;editable();} + }); + panel.querySelector('gaia-header').addEventListener('action',event=>{event.stopImmediatePropagation();if(!pending)navigate('apn-list',{type:profile?.type||'default'});},true); + },async onBeforeShow(element,options={}) { + profile=null;dirty.clear();render(); + explain(panel,'fields',text('Les champs grisés ne sont pas gérés par ce modem. Le champ mot de passe vide conserve le secret existant tant que vous ne le modifiez pas.','Greyed-out fields are not handled by this modem. An untouched password field keeps the existing secret.')); + try {profile=(await read('apns')).find(p=>p.id===options.profileId);if(!profile)throw Error('APN_NOT_FOUND');render();} + catch(error){failure(error);} + },onHide(){panel.querySelector('.password').value='';dirty.clear();}}; + } + + let clockState, clockUpdating=false; + Object.defineProperty(navigator,'mozTime',{value:{set:async date=>{ + try { + await call('platform.deviceSettings',{action:'setTime',milliseconds:+date}); + dispatchEvent(new Event('moztimechange')); + } catch(error) {window.VulpesGaiaUI?.notice(error.message);} + }}}); + async function clock() { + clockState=await call('platform.deviceSettings',{action:'clock'}); + clockUpdating=true; + try {await navigator.mozSettings.createLock().set({ + 'time.timezone':clockState.timezone, 'time.timezone.user-selected':clockState.timezone, + 'time.clock.automatic-update.available':clockState.automaticAvailable, + 'time.clock.automatic-update.enabled':clockState.automatic, + 'time.timezone.automatic-update.available':false + });} finally {clockUpdating=false;} + return clockState; + } + navigator.mozSettings.addObserver('time.clock.automatic-update.enabled',async event=>{ + if(clockUpdating || !clockState || event.settingValue===clockState.automatic)return; + try { + clockState=await call('platform.deviceSettings',{action:'setAutomatic',enabled:event.settingValue}); + await call('settings.clockChanged',{}); + dispatchEvent(new Event('moztimechange')); + } catch(error) { + window.VulpesGaiaUI?.notice(error.message); + await clock(); + } + }); + navigator.mozSettings.addObserver('time.timezone',async event=>{ + if(clockUpdating || !clockState || event.settingValue===clockState.timezone)return; + try { + clockState=await call('platform.deviceSettings',{action:'setTimezone',timezone:event.settingValue}); + await call('settings.clockChanged',{}); + dispatchEvent(new Event('moztimechange')); + } catch(error) { + window.VulpesGaiaUI?.notice(error.message); + await navigator.mozSettings.createLock().set({'time.timezone':clockState.timezone}); + } + }); + clock().catch(error=>console.warn('Clock settings:',error.message)); + window.VulpesMobileSettings = {ready:refresh(), refresh, messaging, clock, panels:{call:callPanel,'call-forwarding':forwardingPanel,carrier:carrierPanel,'operator-settings':operatorPanel,'apn-settings':apnSettingsPanel,'apn-list':apnListPanel,'apn-editor':apnEditorPanel}}; + VulpesMobileSettings.ready.catch(error=>console.warn('SIM settings:',error.message)); +})(); diff --git a/host/gaia-restoration.js b/host/gaia-restoration.js index d5d72a6f..1ac4bb39 100644 --- a/host/gaia-restoration.js +++ b/host/gaia-restoration.js @@ -3,6 +3,22 @@ const app = location.hostname.split('.')[0]; const fr = () => (document.documentElement.lang || navigator.language).startsWith('fr'); const label = (a, b) => fr() ? a : b; + const labels = new Map(); + function localize(node, french, english, attribute) { + labels.set(node, {french, english, attribute}); + translate(); + } + function translate() { + for (const [node, entry] of labels) { + const value = label(entry.french, entry.english); + if (entry.attribute) { + if (node.getAttribute(entry.attribute) !== value) node.setAttribute(entry.attribute, value); + } else if (node.textContent !== value) node.textContent = value; + } + } + addEventListener('localized', translate); + new MutationObserver(translate).observe(document.documentElement, {attributes:true, attributeFilter:['lang']}); + function notice(message) { document.getElementById('vulpes-gaia-notice')?.remove(); const link = document.createElement('link'); @@ -28,6 +44,14 @@ window.VulpesGaiaUI = {notice}; async function ready() { if (app === 'system') { + // Android reserves the edge swipe for its own shade. A tap on Gaia's + // status bar provides access without competing with that system gesture. + if (window.VulpesPlatform === 'android') { + document.getElementById('statusbar')?.addEventListener('click', () => { + if (!window.Service?.query('locked')) window.UtilityTray?.show(true); + }); + } + // OperatorIcon also owns the date. It must exist even without a modem. const startDate = async () => { if (!window.BaseIcon || !window.Service || !window.LazyLoader) return false; @@ -50,8 +74,8 @@ const li = document.createElement('li'), button = document.createElement('button'), error = document.createElement('p'); li.id = 'import-sim-option-unavailable';li.setAttribute('role','presentation'); button.className = 'icon icon-sim';button.setAttribute('role','option');button.disabled = true; - button.textContent = label('Carte SIM', 'SIM card'); - error.className = 'error-message';error.textContent = unavailable(); + localize(button, 'Carte SIM', 'SIM card'); + error.className = 'error-message';localize(error, 'Cette fonction n’est pas encore disponible sur cette plateforme.', 'This feature is not available on this platform yet.'); li.append(button,error);list.prepend(li); } const item = document.getElementById('import-sd-option'); @@ -62,7 +86,7 @@ if(item.classList.contains('disabled')) item.classList.remove('disabled'); const span = item.querySelector('span'); if(span && span.dataset.l10nId === 'importMemoryCard') { - span.removeAttribute('data-l10n-id');span.textContent = label('Fichier de contacts', 'Contact file'); + span.removeAttribute('data-l10n-id');localize(span, 'Fichier de contacts', 'Contact file'); } const error = item.querySelector('p'); if(error && !error.hidden) error.hidden = true; @@ -79,6 +103,17 @@ }, true); } + if (app === 'sms') { + let mmsAvailable=false; + VulpesCompat.call('platform.capabilities',{}).then(c=>{mmsAvailable=c.mms===true;}).catch(()=>{}); + // Keep the composer intact until a real MMS transport is available. + document.addEventListener('click', event => { + if (mmsAvailable || !event.target.closest('#messages-send-button') || window.Compose?.type !== 'mms') return; + event.preventDefault(); event.stopImmediatePropagation(); + notice(label('L’envoi de MMS n’est pas encore disponible dans cette version. Votre message et ses pièces jointes sont conservés.', + 'MMS sending is not yet available in this version. Your message and attachments have been kept.')); + }, true); + } if (app === 'sms' && window.VulpesPlatform === 'android') { document.addEventListener('click', event => { if (!event.target.closest('#messages-send-button')) return; @@ -103,7 +138,7 @@ if (!header || header.querySelector('[data-vulpes-import]')) return; const action = document.createElement('button'); action.dataset.vulpesImport = ''; action.dataset.icon = 'add'; - action.setAttribute('aria-label', label('Importer des fichiers', 'Import files')); + localize(action, 'Importer des fichiers', 'Import files', 'aria-label'); action.onclick = () => VulpesCompat.call('platform.pick',{type:mediaType}).catch(error => { if (error.name !== 'AbortError' && error.message !== 'AbortError') notice(unavailable()); }); @@ -134,31 +169,79 @@ const unsupported = { wifi: !navigator.mozWifiManager, bluetooth: !navigator.mozBluetooth, - carrier: !navigator.mozMobileConnections, - call: !navigator.mozMobileConnections, - messaging: !navigator.mozMobileConnections, + carrier: !window.VulpesMobileSettings, + call: !window.VulpesMobileSettings, + messaging: !navigator.mozMobileConnections?.length, 'sim-manager': !navigator.mozIccManager, - simpin: !navigator.mozIccManager, - hotspot: !navigator.mozWifiManager, + simpin: true, + 'wifi-manageCertificates': true, + 'wifi-selectCertificateFile': true, + hotspot: !window.VulpesWifiSettings, + downloads: capabilities.platform === 'android', + 'wifi-manageNetworks': !navigator.mozWifiManager, + 'messaging-details': !navigator.mozMobileConnections?.length, + 'operator-settings': !window.VulpesMobileSettings, + 'carrier-detail': !window.VulpesMobileSettings, + 'apn-settings': !window.VulpesMobileSettings, + 'apn-list': !window.VulpesMobileSettings, + 'apn-editor': !window.VulpesMobileSettings, + 'call-forwarding': !window.VulpesMobileSettings, + 'call-cbSettings': true, + 'call-fdnSettings': true, + 'call-fdnList': true, + 'developer-service-workers': true, + 'developer-hud': true, + 'accessibility-screenreader': true, + crashReports: true, + improveBrowserOS: true, + 'improveBrowserOS-sendFeedback': true, + 'improveBrowserOS-chooseFeedback': true, + 'full-developer-mode': true, findmydevice: true, fxa: true, fxsync: true, addons: true, mediaStorage: !navigator.getDeviceStorages, applicationStorage: !navigator.getDeviceStorage, - usbStorage: !navigator.getDeviceStorages, + usbStorage: true, }; function preservePanel(panel) { - const content = panel.querySelector(':scope > div'); - if (!content || content.querySelector('[data-vulpes-unavailable]')) return; + const content = panel.querySelector(':scope > div') || panel; + if (content.querySelector('[data-vulpes-unavailable]')) return; const explanation = document.createElement('p'); explanation.className = 'explanation'; explanation.dataset.vulpesUnavailable = ''; - explanation.setAttribute('role', 'status'); explanation.textContent = unavailable(); - content.prepend(explanation); + explanation.setAttribute('role', 'status'); localize(explanation, 'Cette fonction n’est pas encore disponible sur cette plateforme.', 'This feature is not available on this platform yet.'); + const header=content===panel ? panel.querySelector(':scope > gaia-header') : null; + if(header)header.after(explanation);else content.prepend(explanation); + const serviceNotes = { + fxa: ['La connexion intégrée au compte Mozilla doit être portée vers l’authentification actuelle. Ouvrir le site du compte ne connecte pas encore Vulpes.', 'Integrated Mozilla account sign-in needs the current authentication protocol. Opening the account website does not sign Vulpes in.'], + fxsync: ['La synchronisation nécessite la connexion au compte Mozilla et un adaptateur pour les données Vulpes. Elle n’est pas encore raccordée.', 'Sync requires Mozilla account sign-in and an adapter for Vulpes data. It is not connected yet.'], + findmydevice: ['La localisation distante nécessite un service Vulpes sécurisé et associé à votre compte. Aucun appareil n’est actuellement suivi.', 'Remote device location requires a secure Vulpes service linked to your account. No device is currently tracked.'], + addons: ['Ce panneau gérait les extensions des applications Firefox OS, pas les extensions du navigateur Firefox. Leur moteur d’exécution reste à porter.', 'This panel managed Firefox OS app add-ons, not Firefox browser extensions. Their runtime still needs to be ported.'], + usbStorage: ['Le transfert de fichiers USB n’est pas encore raccordé. La connexion USB de maintenance reste disponible.', 'USB file transfer is not connected yet. The maintenance USB connection remains available.'] + }; + if(serviceNotes[panel.id]) localize(explanation,...serviceNotes[panel.id]); + if (panel.id === 'wifi-manageCertificates' || panel.id === 'wifi-selectCertificateFile') localize(explanation, 'Les certificats Wi-Fi et les réseaux WPA-Enterprise ne sont pas encore pris en charge par cet adaptateur.', 'Wi-Fi certificates and WPA-Enterprise networks are not supported by this adapter yet.'); + if (panel.id === 'call' && window.VulpesNativeTelephony) { + localize(explanation, 'Les appels fonctionnent. Le renvoi, le double appel et le masquage du numéro ne sont pas encore configurables ici.', 'Calling is available. Call forwarding, call waiting and caller ID cannot be configured here yet.'); + for (const item of content.querySelectorAll('li[hidden]')) item.hidden = false; + } + if(panel.id==='improveBrowserOS') { + const list=content.querySelector('ul'); + for(const child of [...content.children]) if(child!==list && child!==explanation)child.hidden=true; + const anchor=list.querySelector('a');anchor.href='https://nnsprod.com/git/oversu/VulpesOS/issues'; + const title=anchor.querySelector('span');title.removeAttribute('data-l10n-id'); + localize(title,'Signaler un problème ou proposer une idée','Report a problem or suggest an idea'); + localize(explanation,'Les retours sont recueillis sur le dépôt Vulpes OS. Aucun rapport n’est envoyé automatiquement.','Feedback is collected on the Vulpes OS repository. No report is sent automatically.'); + anchor.addEventListener('click',event=>{event.preventDefault();new MozActivity({name:'view',data:{type:'url',url:anchor.href}});}); + return; + } for (const control of content.querySelectorAll('button,input,select,gaia-switch,gaia-checkbox,gaia-radio')) { control.disabled = true; control.setAttribute('disabled', ''); } for (const anchor of content.querySelectorAll('a')) { + const href=anchor.getAttribute('href'); + if(href && href!=='#' && href.startsWith('#') && unsupported[href.slice(1)]) continue; anchor.setAttribute('aria-disabled', 'true'); anchor.addEventListener('click', event => {event.preventDefault();event.stopImmediatePropagation();}, true); } @@ -168,9 +251,19 @@ const install = setInterval(() => { if (typeof window.require !== 'function' || !require.defined?.('modules/settings_panel') || !require.defined?.('modules/panel_cache')) return; clearInterval(install); - require(['modules/panel_cache', 'modules/settings_panel'], (cache, SettingsPanel) => { + require(['modules/panel_cache', 'modules/settings_panel', 'modules/dialog_panel'], (cache, SettingsPanel, DialogPanel) => { const original = cache.get.bind(cache), panels = new Map(); cache.get = (id, callback) => { + const dialog = window.VulpesWifiSettings?.dialogs[id]; + const native = window.VulpesMobileSettings?.panels[id] || window.VulpesWifiSettings?.panels[id]; + if(dialog) { + if(!panels.has(id))panels.set(id,DialogPanel(dialog())); + callback?.(panels.get(id));return; + } + if(native) { + if(!panels.has(id))panels.set(id,SettingsPanel(native())); + callback?.(panels.get(id));return; + } if (!unsupported[id]) return original(id, callback); if (!panels.has(id)) panels.set(id, SettingsPanel({onInit:preservePanel, onBeforeShow:preservePanel})); callback?.(panels.get(id)); @@ -179,26 +272,70 @@ }, 100); function restore() { batteryText(); + if (window.VulpesMobileSettings) { + const panel = document.querySelector('#messaging > div'); + if (panel && !panel.querySelector('[data-vulpes-message-limit]')) { + const p = document.createElement('p'); p.className = 'explanation'; p.dataset.vulpesMessageLimit = ''; + localize(p, 'Les accusés de réception SMS sont reliés au modem. Les autres options grisées ne sont pas encore prises en charge.', 'SMS delivery reports are connected to the modem. The other greyed-out options are not supported yet.'); panel.prepend(p); + } + if (panel) VulpesMobileSettings.messaging(panel); + for (const control of panel?.querySelectorAll('gaia-switch,select') || []) { + if (control.dataset.vulpesSupported === 'true') continue; + if (!control.hasAttribute('disabled')) control.setAttribute('disabled',''); + if (control.disabled !== true) control.disabled = true; + } + } + const clockPanel=document.getElementById('dateTime'); + if(clockPanel?.querySelector('.date-picker') && !navigator.mozTime) { + for(const control of clockPanel.querySelectorAll('.date-picker,.time-picker,.timezone-picker select')) if(!control.disabled)control.disabled=true; + if(!clockPanel.querySelector('[data-vulpes-clock-host]')) { + const row=document.createElement('p');row.className='explanation';row.dataset.vulpesClockHost=''; + localize(row,'La date et le fuseau horaire sont gérés par le système hôte. Le format de l’heure reste réglable ici.', 'Date and timezone are managed by the host system. The time format can still be changed here.'); + clockPanel.querySelector(':scope > div').prepend(row); + } + } + for(const id of ['mediaStorage','applicationStorage']) { + const content=document.querySelector('#'+id+' > div'); + if(content && !content.querySelector('[data-vulpes-storage-scope]')) { + const p=document.createElement('p');p.className='explanation';p.dataset.vulpesStorageScope=''; + localize(p,'Espace utilisé par Vulpes et espace libre sur le volume hôte. Les données des autres applications du système ne sont pas incluses.', 'Vulpes storage usage and free space on the host volume. Other host applications are not included.'); + content.prepend(p); + } + } const root = document.querySelector('#root'); if (root) for (const id of ['wifi','bluetooth','carrier','call','messaging','sim-manager','simpin', 'findmydevice','addons','mediaStorage','applicationStorage']) { const li = root.querySelector('a[href="#' + id + '"]')?.closest('li'); if (li?.hidden) li.hidden = false; + if (li?.hasAttribute('aria-disabled')) li.removeAttribute('aria-disabled'); + const anchor = li?.querySelector('a'); + if (anchor?.hasAttribute('aria-disabled')) anchor.removeAttribute('aria-disabled'); } if (capabilities.platform === 'android' && root && !document.getElementById('vulpes-android-settings')) { const ul = document.createElement('ul'), li = document.createElement('li'), a = document.createElement('a'); a.id = 'vulpes-android-settings'; a.className = 'menu-item'; a.href = '#'; - a.textContent = label('Paramètres Android', 'Android settings'); + localize(a, 'Paramètres Android', 'Android settings'); a.onclick = event => {event.preventDefault();VulpesCompat.call('platform.settings',{panel:'root'}).catch(() => notice(unavailable()));}; li.append(a); ul.append(li); root.querySelector(':scope > div')?.append(ul); } + if (capabilities.platform === 'android') { + const keyboard=document.querySelector('#keyboard > div'); + if(keyboard && !document.getElementById('vulpes-keyboard-mode')) { + const ul=document.createElement('ul'),li=document.createElement('li'),control=document.createElement('gaia-switch'),title=document.createElement('label'); + control.id='vulpes-keyboard-mode'; + localize(title,'Clavier Gaia (expérimental)','Gaia keyboard (experimental)'); + control.append(title);li.append(control);ul.append(li);keyboard.prepend(ul); + VulpesCompat.call('input.config',{}).then(enabled=>{control.checked=enabled;}); + control.addEventListener('change',()=>navigator.mozSettings.createLock().set({'keyboard.gaia.enabled':control.checked}).catch(console.error)); + } + } if (capabilities.platform === 'android') { const homescreens = document.querySelector('#homescreens > div'); if (homescreens && !document.getElementById('vulpes-default-home')) { const ul = document.createElement('ul'), li = document.createElement('li'), a = document.createElement('a'); a.id = 'vulpes-default-home'; a.className = 'menu-item'; a.href = '#'; - a.textContent = label('Accueil Android par défaut', 'Default Android home'); + localize(a, 'Accueil Android par défaut', 'Default Android home'); a.onclick = event => {event.preventDefault();VulpesCompat.call('platform.settings',{panel:'home'}).catch(() => notice(unavailable()));}; li.append(a);ul.append(li);homescreens.append(ul); } @@ -213,12 +350,12 @@ const legal = document.querySelector('#about-legal > div'); if (legal && !document.getElementById('vulpes-credits')) { const p = document.createElement('p'); p.id = 'vulpes-credits'; p.className = 'explanation'; - p.textContent = label('Vulpes OS repose sur Gaia et Gecko de Mozilla et le travail de leurs contributeurs. Capyloon fait partie des projets qui inspirent cette continuité. Les licences et attributions d’origine sont conservées.', + localize(p, 'Vulpes OS repose sur Gaia et Gecko de Mozilla et le travail de leurs contributeurs. Capyloon fait partie des projets qui inspirent cette continuité. Les licences et attributions d’origine sont conservées.', 'Vulpes OS builds on Mozilla Gaia and Gecko and the work of their contributors. Capyloon is among the projects inspiring this continuation. Original licenses and attributions are retained.'); legal.append(p); } } - new MutationObserver(restore).observe(document.body, {subtree:true, childList:true, attributes:true, attributeFilter:['hidden']}); + new MutationObserver(restore).observe(document.body, {subtree:true, childList:true, attributes:true, attributeFilter:['hidden','aria-disabled','disabled']}); restore(); } if (document.readyState === 'loading') addEventListener('DOMContentLoaded', () => ready().catch(console.error), {once:true}); diff --git a/host/gaia-select.js b/host/gaia-select.js new file mode 100644 index 00000000..c6b94c76 --- /dev/null +++ b/host/gaia-select.js @@ -0,0 +1,206 @@ +// Gaia's value-selector markup and stylesheet, without a browser-chrome popup. +(() => { + let active; + function close(commit) { + if (!active) return; + const {select, wrapper, chosen} = active; + active = null; + if (commit && select.isConnected && !select.disabled) { + const before = [...select.options].map(o => o.selected).join(','); + [...select.options].forEach((o, i) => { o.selected = chosen.has(i); }); + if (before !== [...select.options].map(o => o.selected).join(',')) { + select.dispatchEvent(new Event('input', {bubbles:true})); + select.dispatchEvent(new Event('change', {bubbles:true})); + } + } + wrapper.remove(); + // Several original panels commit modem settings on blur. + select.blur(); + } + function open(select) { + if (select.disabled || select.closest('[aria-disabled="true"]')) return; + close(false); + if (!document.querySelector('[data-vulpes-select-style]')) { + const link = document.createElement('link'); + link.rel = 'stylesheet'; link.dataset.vulpesSelectStyle = ''; + link.href = `${location.protocol}//system.localhost:${location.port}/shared/style/value_selector.css`; + document.head.append(link); + } + const wrapper = document.createElement('div'); wrapper.className = 'value-selector'; + wrapper.id = 'vulpes-value-selector-layer'; + const form = document.createElement('form'); + form.id = 'vulpes-value-selector'; form.role = 'dialog'; + form.dataset.type = 'value-selector'; form.setAttribute('aria-modal', 'true'); + form.innerHTML = '

    '; + form.setAttribute('aria-labelledby', 'vulpes-select-title'); + const fr = (document.documentElement.lang || navigator.language).startsWith('fr'); + form.querySelector('[data-action="cancel"]').textContent = fr ? 'Annuler' : 'Cancel'; + form.querySelector('h1').textContent = document.getElementById(select.getAttribute('aria-labelledby'))?.textContent || select.closest('li')?.querySelector('span[data-l10n-id],label,p')?.textContent || (fr ? 'Choisir une option' : 'Choose an option'); + const chosen = new Set([...select.options].flatMap((o,i) => o.selected ? [i] : [])); + const list = form.querySelector('ol'); + if (select.multiple) list.setAttribute('aria-multiselectable', 'true'); + [...select.children].forEach(child => { + if (child.tagName === 'OPTGROUP') { + const heading = document.createElement('li'); heading.role = 'subheader'; + heading.textContent = child.label; list.append(heading); + } + for (const option of child.tagName === 'OPTGROUP' ? child.children : [child]) { + const i = [...select.options].indexOf(option); + if (i < 0 || option.hidden) continue; + const li = document.createElement('li'); li.role = 'option'; li.tabIndex = 0; + li.dataset.index = i; li.dataset.value = option.value; + li.setAttribute('aria-selected', String(chosen.has(i))); + const disabled = option.disabled || option.parentElement.disabled === true; + li.setAttribute('aria-disabled', String(disabled)); + const label = document.createElement('label'), span = document.createElement('span'); + label.role = 'presentation'; span.textContent = option.textContent; + label.append(span); li.append(label); list.append(li); + const pick = () => { + if (disabled) return; + if (!select.multiple) chosen.clear(); + if (select.multiple && chosen.has(i)) chosen.delete(i); else chosen.add(i); + list.querySelectorAll('[role="option"]').forEach(n => n.setAttribute('aria-selected', String(chosen.has(Number(n.dataset.index))))); + }; + li.onclick = pick; + li.onkeydown = event => { if (['Enter',' '].includes(event.key)) {event.preventDefault();pick();} }; + } + }); + active = {select, form, wrapper, chosen}; + select.focus({preventScroll:true}); + form.onsubmit = event => {event.preventDefault();close(true);}; + form.querySelector('[data-action="cancel"]').onclick = () => close(false); + wrapper.append(form); document.body.append(wrapper); + } + // Gecko's embedded native select may consume the compatibility click. + // Open on a completed tap too, without treating a scroll or a hold as a tap. + let gesture, recentTap; + addEventListener('touchstart', event => { + const select=event.target; + if (select.tagName !== 'SELECT' || select.size > 1 || event.touches.length !== 1) {gesture=null;return;} + const point=event.touches[0]; + gesture={select,id:point.identifier,x:point.clientX,y:point.clientY,moved:false,at:performance.now()}; + },{capture:true,passive:true}); + addEventListener('touchmove', event => { + if (!gesture) return; + const point=[...event.touches].find(p=>p.identifier===gesture.id); + if (!point || event.touches.length!==1 || Math.hypot(point.clientX-gesture.x,point.clientY-gesture.y)>10) gesture.moved=true; + },{capture:true,passive:true}); + addEventListener('touchcancel',()=>{gesture=null;},{capture:true,passive:true}); + addEventListener('touchend', event => { + const tap=gesture;gesture=null; + if (!tap || tap.moved || performance.now()-tap.at>600 || event.touches.length) return; + const point=[...event.changedTouches].find(p=>p.identifier===tap.id); + if (!point || Math.hypot(point.clientX-tap.x,point.clientY-tap.y)>10) return; + event.preventDefault();event.stopImmediatePropagation(); + recentTap={select:tap.select,at:performance.now()};open(tap.select); + },{capture:true,passive:false}); + // Cancel the browser-chrome popup. Mouse and keyboard activation use Gaia too. + for (const type of ['mousedown','pointerdown']) addEventListener(type, event => { + if (event.target.tagName === 'SELECT' && event.target.size <= 1) event.preventDefault(); + }, true); + addEventListener('click', event => { + if (event.target.tagName !== 'SELECT' || event.target.size > 1) return; + event.preventDefault(); event.stopImmediatePropagation(); + if (recentTap?.select===event.target && performance.now()-recentTap.at<700) return; + open(event.target); + }, true); + addEventListener('keydown', event => { + if (active && event.key === 'Escape') {event.preventDefault();close(false);} + else if (event.target.tagName === 'SELECT' && [' ','Enter','ArrowDown'].includes(event.key)) { + event.preventDefault();open(event.target); + } + }, true); + addEventListener('pagehide', () => close(false)); + document.addEventListener('visibilitychange', () => {if (document.hidden) close(false);}); + addEventListener('panelready', event => { + // A late transition event for the same panel must not dismiss a menu + // opened immediately after navigation. Leaving the panel still cancels it. + if (active && event.detail?.current !== '#' + active.select.closest('section')?.id) close(false); + }); +})(); + +// Date/time pickers still belong to Gaia System. Embedded Gecko does not +// provide the retired mozInputMethod value-selector events for these inputs. +(() => { + const systemOrigin = 'http://system.localhost:8765'; + if (location.origin === systemOrigin) { + addEventListener('message', event => { + if (event.data?.type === 'vulpes-keyboard-close' && event.origin === 'http://keyboard.localhost:8765') { + const frame=[...document.querySelectorAll('iframe')].find(f=>f.contentWindow===event.source && f.src.startsWith(event.origin+'/settings.html')); + if(frame) { + const keyboard=Service.query('getApp',event.origin); + const close=()=>frame.isConnected && frame.dispatchEvent(new CustomEvent('mozbrowserclose',{bubbles:true})); + // Closing a still-transitioning background window can send Gaia + // Home over the application which has just replaced it. + if(keyboard?.isActive())keyboard.element.addEventListener('_closed',close,{once:true}); + else close(); + } + return; + } + const app = window.Service?.query('getActiveApp'); + if (!app || event.source !== app.iframe?.contentWindow || event.origin !== app.origin) return; + if (event.data?.type !== 'vulpes-datetime-open' || !['date','time'].includes(event.data.inputType)) return; + const picker = app.valueSelector; + if (!picker) return; + const previous = picker._im; + const reply = value => event.source.postMessage({type:'vulpes-datetime-result', id:event.data.id, value}, event.origin); + picker._im = { + setValue: value => reply(value), + removeFocus() { reply(null); picker._im = previous; } + }; + picker.handleEvent({type:'_inputfocus', detail:{inputType:event.data.inputType, + value:String(event.data.value || '').slice(0,32), min:event.data.min, max:event.data.max}}); + }); + return; + } + if (location.hostname === 'keyboard.localhost' && /settings/.test(location.pathname)) { + window.close = () => parent.postMessage({type:'vulpes-keyboard-close'}, systemOrigin); + } + let pending, sequence=0; + addEventListener('click', event => { + const input=event.target; + if (input.tagName !== 'INPUT' || !['date','time'].includes(input.type) || input.disabled || input.readOnly) return; + event.preventDefault();event.stopImmediatePropagation(); + pending={input,id:++sequence}; + parent.postMessage({type:'vulpes-datetime-open',id:sequence,inputType:input.type, + value:input.value,min:input.min,max:input.max},systemOrigin); + },true); + addEventListener('message', event => { + if(event.source!==parent || event.origin!==systemOrigin || event.data?.type!=='vulpes-datetime-result' || event.data.id!==pending?.id)return; + const {input}=pending;pending=null; + if(event.data.value!==null && input.isConnected && !input.disabled) { + input.value=event.data.value; + if(input.value) { + input.dispatchEvent(new Event('input',{bubbles:true})); + input.dispatchEvent(new Event('change',{bubbles:true})); + } + } + input.blur(); + }); +})(); + +// Modern native select text metrics differ from Gaia's 4rem button box. +// Keep the real select for interaction and accessibility, and paint its label +// in a regular line box so Fira's descenders are not clipped by the control. +(() => { + const labels=new WeakMap(); + function refresh() { + for(const select of document.querySelectorAll('.button select,.bb-button select')) { + if(select.multiple || select.size>1)continue; + let label=labels.get(select); + if(!label) { + label=document.createElement('span');label.className='vulpes-select-label'; + label.setAttribute('aria-hidden','true');select.after(label);labels.set(select,label); + select.classList.add('vulpes-select-label-owner'); + select.addEventListener('change',refresh); + } + const text=select.selectedOptions[0]?.textContent || ''; + if(label.textContent!==text)label.textContent=text; + } + } + let queued=false; + new MutationObserver(()=>{if(!queued){queued=true;queueMicrotask(()=>{queued=false;refresh();});}}) + .observe(document.documentElement,{childList:true,subtree:true,characterData:true,attributes:true,attributeFilter:['selected','value']}); + addEventListener('panelready',refresh);addEventListener('localized',refresh); + refresh(); +})(); diff --git a/host/gaia-services.js b/host/gaia-services.js index 238282e8..de360e80 100644 --- a/host/gaia-services.js +++ b/host/gaia-services.js @@ -18,6 +18,21 @@ function unavailable(message) { window.VulpesGaiaUI?.notice(message); } + if(location.hostname==='system.localhost') { + const colorKeys=['accessibility.colors.enable','accessibility.colors.invert','accessibility.colors.grayscale','accessibility.colors.contrast']; + const values={}; + function colors() { + const screen=document.getElementById('screen');if(!screen)return; + const enabled=values[colorKeys[0]]===true; + const contrast=Number(values[colorKeys[3]])||0; + screen.style.filter=enabled ? `invert(${values[colorKeys[1]]?1:0}) grayscale(${values[colorKeys[2]]?1:0}) contrast(${Math.max(0,Math.min(2,1+contrast))})` : ''; + } + for(const key of colorKeys) { + navigator.mozSettings.addObserver(key,e=>{values[key]=e.settingValue;colors();}); + navigator.mozSettings.createLock().get(key).then(row=>{values[key]=row[key];colors();}); + } + addEventListener('DOMContentLoaded',colors,{once:true}); + } addEventListener('DOMContentLoaded', () => { if (location.hostname === 'system.localhost' && !window.VulpesPlatform) { const ids = ['wifi', 'bluetooth', 'data', 'airplane-mode']; @@ -72,6 +87,23 @@ ); } }); + if(['settings.localhost','system.localhost'].includes(location.hostname) && window.VulpesPlatform!=='android') { + const manager=Object.assign(new EventTarget(),{}), downloads=new Map(); + manager.getDownloads=async()=>{ + const rows=await call('downloads.request',{operation:'list'}); + return rows.map(row=>{ + let item=downloads.get(row.id); + if(!item) { + item=Object.assign(new EventTarget(),{pause:()=>call('downloads.request',{operation:'pause',id:row.id}),resume:()=>call('downloads.request',{operation:'resume',id:row.id})}); + downloads.set(row.id,item); + } + Object.assign(item,row,{vulpesDownload:true,getBlob:()=>call('downloads.request',{operation:'blob',id:row.id}),startTime:new Date(row.startTime)});return item; + }); + }; + manager.remove=item=>call('downloads.request',{operation:'remove',id:item.id}); + manager.clearAllDone=async()=>{for(const item of await manager.getDownloads())if(item.state==='succeeded')await manager.remove(item);}; + Object.defineProperty(navigator,'mozDownloadManager',{value:manager}); + } Object.defineProperty(navigator, 'mozAlarms', { value: { getAll: () => @@ -222,6 +254,15 @@ } window.VulpesCompat.cursor = cursor; if (location.hostname === 'sms.localhost') { + addEventListener('DOMContentLoaded', () => { + const send = document.getElementById('messages-send-button'); + if (!send) return; + send.setAttribute('data-vulpes-keep-input', ''); + // Keep the composer focused, including with Android's native keyboard. + // Sending remains a click action, so a cancelled gesture never sends. + for (const type of ['pointerdown', 'mousedown']) + send.addEventListener(type, event => event.preventDefault()); + }); Object.defineProperty(navigator, 'mozMobileConnections', { value: Object.freeze([]) }); const messages = new EventTarget(); for (const operation of ['getThreads', 'getMessages']) @@ -242,11 +283,10 @@ 'getSegmentInfoForText', ]) messages[operation] = (...args) => request(call('messages.request', { operation, args })); - const dated = value => value && typeof value==='object' && 'timestamp' in value ? - {...value,timestamp:new Date(value.timestamp)} : value; + const dated = raw => { const value=window.VulpesSettingsCodec.decode(raw);return value && typeof value==='object' && 'timestamp' in value ? + {...value,timestamp:new Date(value.timestamp)} : value; }; messages.getMessage = id => request(call('messages.request',{operation:'getMessage',args:[id]}).then(dated)); - messages.send = (recipients,body,options) => { - const send = recipient => { + const submit = (operation,args) => { let id, settle; const early = new Map(); const terminal = new Promise((resolve,reject)=>{settle=message=>{ @@ -260,7 +300,7 @@ else if(id===undefined && early.size<100) early.set(message.id,message); }; addEventListener('vulpes-service-event',listener); - const result=call('messages.request',{operation:'send',args:[recipient,body,options]}).then(message=>{ + const result=call('messages.request',{operation,args}).then(message=>{ if(message.delivery!=='sending') return dated(message); id=message.id; if(early.has(id)) settle(early.get(id)); @@ -268,9 +308,12 @@ return terminal; }).finally(()=>removeEventListener('vulpes-service-event',listener)); return request(result); - }; + }; + messages.send = (recipients,body,options) => { + const send=recipient=>submit('send',[recipient,body,options]); return Array.isArray(recipients) ? recipients.map(send) : send(recipients); }; + messages.sendMMS = (message,options) => submit('sendMMS',[message,options]); addEventListener('vulpes-service-event',({detail})=>{ const type = {'sms-received':'received','sms-sending':'sending','sms-sent':'sent','sms-failed':'failed'}[detail.type]; if (!type) return; @@ -281,17 +324,22 @@ Object.defineProperty(navigator, 'mozMobileMessage', { value: messages }); } if ( - ['gallery', 'music', 'video', 'camera', 'ringtones'].includes(location.hostname.split('.')[0]) + ['settings', 'gallery', 'music', 'video', 'camera', 'ringtones'].includes(location.hostname.split('.')[0]) ) { const stores = new Map(); function storage(type) { - if (!['pictures', 'music', 'videos'].includes(type)) return null; + if (!['pictures', 'music', 'videos', ...(location.hostname==='settings.localhost'?['apps','sdcard']:[])].includes(type)) return null; if (stores.has(type)) return stores.get(type); const s = new EventTarget(); s.storageName = 'sdcard'; s.default = true; s.canBeMounted = false; - const run = (operation, params = {}) => call('media.request', { type, operation, ...params }); + const run = (operation, params = {}) => { + if(!['apps','sdcard'].includes(type))return call('media.request', { type, operation, ...params }); + if(operation==='available')return call('storage.stats',{}).then(()=>'available'); + if(['freeSpace','usedSpace'].includes(operation))return call('storage.stats',{}).then(stats=>operation==='freeSpace'?stats.free:stats[type]); + return Promise.reject(new DOMException('Read-only storage statistics','NotSupportedError')); + }; for (const op of ['available', 'freeSpace', 'usedSpace']) s[op] = () => request(run(op)); s.get = (name) => request( diff --git a/host/gaia-styles.js b/host/gaia-styles.js index 01000cf2..1ece3fb7 100644 --- a/host/gaia-styles.js +++ b/host/gaia-styles.js @@ -109,6 +109,14 @@ // Only a custom element's type selector names the scope root. A div // scope still contains ordinary div descendants (notably Lockscreen). if (owner.localName.includes('-')) text = text.replace(tag, ':scope'); + if (owner.id) { + const rootId = '#' + CSS.escape(owner.id); + const pattern = new RegExp(rootId.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') + '(?![\\w-])', 'g'); + // Old scoped sheets could style their owner by ID. Modern @scope + // requires :scope for that element (for example the volume HUD). + text = text.replace(/([^{}]+)\{/g, (rule, selector) => + selector.replace(pattern, ':scope') + '{'); + } text = text.replace(/\.-(?:host|content)(?![\w-])/g, (match) => ':scope' + match); style.textContent = '@scope ([data-vulpes-scope="' + id + '"]) {' + text + '}'; style.removeAttribute('scoped'); diff --git a/host/gaia-theme.css b/host/gaia-theme.css new file mode 100644 index 00000000..1d91c0bf --- /dev/null +++ b/host/gaia-theme.css @@ -0,0 +1,105 @@ +/* Keep Gaia geometry, icons and media intact; change the surfaces and ink. */ +:root[data-vulpes-theme="dark"] { + color-scheme: dark; + --vulpes-surface: #18232e; + --vulpes-raised: #253442; + --vulpes-text: #f1f5f9; + --vulpes-muted: #b8c8d8; + --vulpes-line: #526272; + --link-color: #79ceff; + --header-background: var(--vulpes-raised); + --header-color: var(--vulpes-text); + --header-title-color: var(--vulpes-text); + --header-button-color: var(--vulpes-text); + --header-icon-color: var(--vulpes-text); + --header-action-button-color: #79ceff; + --header-disabled-button-color: var(--vulpes-muted); +} +:root[data-vulpes-theme="dark"]:is([data-vulpes-app="settings"], + [data-vulpes-app="communications"], [data-vulpes-app="sms"], + [data-vulpes-app="email"], [data-vulpes-app="calendar"], + [data-vulpes-app="ringtones"]) { + --background: var(--vulpes-surface); + --text-color: var(--vulpes-text); + --title-color: var(--vulpes-text); + --label-color: var(--vulpes-text); + background: var(--vulpes-surface) !important; + color: var(--vulpes-text) !important; +} +:root[data-vulpes-theme="dark"] gaia-header { + --header-background: var(--vulpes-raised) !important; + --header-title-color: var(--vulpes-text) !important; + --header-action-button-color: #79ceff !important; +} +#display #vulpes-theme-row { + display: block !important; + min-height: 10rem; + height: auto !important; + padding-bottom: 1rem; +} +#vulpes-theme-row > p { + height: auto; + line-height: 2.4rem; + padding: 1rem 1.5rem; + transform: none; +} +:root[data-vulpes-theme="dark"][data-vulpes-app="settings"] h2 { + background-color: var(--vulpes-surface) !important; +} +:root[data-vulpes-theme="dark"]:is([data-vulpes-app="settings"], + [data-vulpes-app="communications"], [data-vulpes-app="sms"], + [data-vulpes-app="email"], [data-vulpes-app="calendar"], + [data-vulpes-app="ringtones"]) :is(body, section, [role="region"], + [data-type="list"], .view, .card, #main, #messages-container, #thread-list, + #contacts-list, #view-contacts-list, #no-contacts, #call-log-container, #messages-compose-form) { + background-color: var(--vulpes-surface) !important; + color: var(--vulpes-text) !important; +} +:root[data-vulpes-theme="dark"] :is(input:not([type="range"]):not([type="checkbox"]):not([type="radio"]), textarea, select, [contenteditable="true"]) { + background-color: var(--vulpes-raised) !important; + color: var(--vulpes-text) !important; + border-color: var(--vulpes-line) !important; + caret-color: #79ceff; +} +:root[data-vulpes-theme="dark"] :is(input, textarea)::placeholder { color: var(--vulpes-muted) !important; } +:root[data-vulpes-theme="dark"][data-vulpes-app="settings"] :is(li, li > a, li > a > span, li > p, li > small, label, .explanation, h1, h2) { + color: var(--vulpes-text) !important; + border-color: var(--vulpes-line) !important; +} +:root[data-vulpes-theme="dark"][data-vulpes-app="settings"] :is(ul, header, .button, button:not(.danger)) { + background-color: var(--vulpes-raised) !important; + color: var(--vulpes-text) !important; +} +:root[data-vulpes-theme="dark"][data-vulpes-app="communications"] :is(.contact-item, .contact-item a, .contact-item p, #call-log-container li, #call-log-container a, #call-log-container p, .phone-number, #keypad-phone-number, .keypad-key) { + color: var(--vulpes-text) !important; +} +:root[data-vulpes-theme="dark"][data-vulpes-app="communications"] #phone-number-view-container { + background-color: var(--vulpes-raised) !important; +} +:root[data-vulpes-theme="dark"][data-vulpes-app="communications"] :is(.keypad-subicon, [role="tab"]) { + color: #79ceff !important; +} +:root[data-vulpes-theme="dark"][data-vulpes-app="communications"] [role="tab"][aria-selected="true"] { + background-color: var(--vulpes-raised) !important; +} +:root[data-vulpes-theme="dark"][data-vulpes-app="communications"] #no-contacts :is(p, .info) { + color: var(--vulpes-muted) !important; +} +:root[data-vulpes-theme="dark"][data-vulpes-app="communications"] #no-contacts { + background-blend-mode: soft-light; +} +:root[data-vulpes-theme="dark"][data-vulpes-app="sms"] :is(.threadlist-item, .threadlist-item a, .threadlist-item p, .time, .name, .body-text) { + color: var(--vulpes-text) !important; +} +:root[data-vulpes-theme="dark"][data-vulpes-app="sms"] .bubble { + background-color: var(--vulpes-raised) !important; + color: var(--vulpes-text) !important; +} +:root[data-vulpes-theme="dark"] :is([data-type="confirm"], [data-type="action"]) :is(section, menu, header, p) { + background-color: var(--vulpes-raised) !important; + color: var(--vulpes-text) !important; +} +:root[data-vulpes-theme="dark"][data-vulpes-app="system"] :is(#utility-tray, #notifications-container) { + background-color: var(--vulpes-surface) !important; + color: var(--vulpes-text) !important; +} diff --git a/host/gaia-theme.js b/host/gaia-theme.js new file mode 100644 index 00000000..f6745cf3 --- /dev/null +++ b/host/gaia-theme.js @@ -0,0 +1,58 @@ +// Shared appearance preference; external browser pages are not injected. +(() => { + const key = 'vulpes.theme'; + const system = matchMedia('(prefers-color-scheme: dark)'); + let mode = 'light', select; + function apply(value) { + mode = ['light', 'dark', 'system'].includes(value) ? value : 'light'; + document.documentElement.dataset.vulpesTheme = + mode === 'system' ? (system.matches ? 'dark' : 'light') : mode; + if (select) select.value = mode; + } + system.addEventListener('change', () => apply(mode)); + addEventListener('vulpes-service-event', ({detail}) => { + if (detail.type === 'settingchange' && detail.data.key === key) apply(detail.data.value); + }); + VulpesCompat.call('appearance.current', {}).then(apply).catch(console.error); + + if (location.hostname !== 'settings.localhost') return; + function label() { + const lang = document.documentElement.lang || navigator.language; + const labels = lang.startsWith('fr') ? ['Apparence', 'Clair', 'Sombre', 'Selon le système'] : + lang.startsWith('el') ? ['Εμφάνιση', 'Φωτεινό', 'Σκοτεινό', 'Σύμφωνα με το σύστημα'] : + ['Appearance', 'Light', 'Dark', 'Follow system']; + const title = document.getElementById('vulpes-theme-label'); + if (title && title.textContent !== labels[0]) title.textContent = labels[0]; + if (select) [...select.options].forEach((option, i) => { + if (option.textContent !== labels[i + 1]) option.textContent = labels[i + 1]; + }); + } + function insert() { + const list = document.querySelector('#display > div > ul'); + if (!list || select?.isConnected) return; + const row = document.createElement('li'), title = document.createElement('p'); + row.id = 'vulpes-theme-row'; + title.id = 'vulpes-theme-label'; + const wrapper = document.createElement('span'); + wrapper.className = 'button icon icon-dialog'; + select = document.createElement('select'); + select.id = 'vulpes-theme'; + select.setAttribute('aria-labelledby', title.id); + for (const value of ['light', 'dark', 'system']) { + const option = document.createElement('option'); + option.value = value; select.append(option); + } + select.value = mode; + select.onchange = async () => { + select.disabled = true; + try { await navigator.mozSettings.createLock().set({[key]:select.value}); } + catch (error) { select.value = mode; console.error(error); } + finally { select.disabled = false; } + }; + wrapper.append(select); row.append(title, wrapper); list.append(row); label(); + } + addEventListener('localized', label); + new MutationObserver(() => { insert(); label(); }).observe(document.documentElement, + {childList:true, subtree:true, attributes:true, attributeFilter:['lang']}); + insert(); +})(); diff --git a/host/gaia-wifi.js b/host/gaia-wifi.js index e2d81f9d..9de90ff4 100644 --- a/host/gaia-wifi.js +++ b/host/gaia-wifi.js @@ -6,7 +6,7 @@ enabled:false, connection:{status:'disconnected',network:null}, connectionInformation:null, macAddress:'', }); - let networks = []; + let networks = [], connectionInfo = {}; function emit(type, values = {}) { const event = Object.assign(new Event(type), values); manager.dispatchEvent(event); manager['on' + type]?.(event); @@ -21,7 +21,7 @@ const status = network ? 'connected' : 'disconnected'; const changed = manager.connection.status !== status || manager.connection.network?.ssid !== network?.ssid; manager.connection = {status, network}; - manager.connectionInformation = network ? {relSignalStrength:network.relSignalStrength} : null; + manager.connectionInformation = network ? {...connectionInfo,relSignalStrength:network.relSignalStrength} : null; if (changed) emit('statuschange', manager.connection); // Gaia's signal listeners require the associated network. A scan while // disconnected must not announce connection information for a missing AP. @@ -36,17 +36,23 @@ async function scanNetworks() { const result = await call('platform.wifi',{operation:'scan'}); power(result.enabled !== false); + connectionInfo=result.connectionInformation || {}; networks = result.networks.map(n => ({ssid:n.ssid, relSignalStrength:n.signal, - security:n.security === 'open' ? [] : [n.security === 'wpa-psk' ? 'WPA-PSK' : n.security === 'sae' ? 'SAE' : 'WPA-EAP'], + security:n.security === 'open' ? [] : [['wpa-psk','sae'].includes(n.security) ? 'WPA-PSK' : 'WPA-EAP'], known:n.known === true, connected:n.connected, vulpesId:n.id, savedId:n.savedId})); update(networks.find(n => n.connected) || null); return networks; } manager.getNetworks = () => request(scan()); - manager.getKnownNetworks = () => request(call('platform.wifi',{operation:'known'}).then(r=>r.networks.map(n=>({ssid:n.ssid,security:n.security==='open'?[]:['WPA-PSK'],known:true,savedId:n.id})))); + let knownPending; + manager.getKnownNetworks = () => { + if (!knownPending) knownPending = call('platform.wifi',{operation:'known'}).then(r=>r.networks.map(n=>({ssid:n.ssid,security:n.security==='open'?[]:['WPA-PSK'],known:true,savedId:n.id}))).finally(()=>{knownPending=null;}); + return request(knownPending); + }; manager.associate = network => request((async () => { - if (network.security.length && network.security[0] !== 'WPA-PSK') throw Error('WIFI_UNSUPPORTED_SECURITY'); - await call('platform.wifi',{operation:'connect',id:network.vulpesId,password:network.psk || ''}); + const security=(network.security || []).filter(Boolean); + if (security.length && security[0] !== 'WPA-PSK') throw Error('WIFI_UNSUPPORTED_SECURITY'); + await call('platform.wifi',network.hidden ? {operation:'connectHidden',ssid:network.ssid,security:security.length?'wpa-psk':'open',password:network.psk || ''} : {operation:'connect',id:network.vulpesId,password:network.psk || ''}); await scan(); return network; })()); manager.forget = network => request(call('platform.wifi',{operation:'forget',id:network.savedId}).then(()=>scan())); @@ -54,6 +60,91 @@ manager.wps = unsupported; manager.setPowerSavingMode = unsupported; Object.defineProperty(navigator,'mozWifiManager',{value:manager}); + if(location.hostname.startsWith('settings.')) { + const wifi = value => call('platform.wifi',value); + const french = () => (document.documentElement.lang || navigator.language || '').startsWith('fr'); + const message = (fr,en) => french() ? fr : en; + const note = panel => { + const p=document.createElement('p');p.className='explanation';p.setAttribute('role','status'); + panel.querySelector(':scope > div').prepend(p);return p; + }; + function hotspotPanel() { + let panel,toggle,link,status,state,busy=false; + function render() { + toggle.checked=state?.enabled===true;toggle.disabled=busy || !state; + link.setAttribute('aria-disabled',String(busy || !state || state.enabled)); + panel.querySelector('#wifi-hotspot-msg').textContent=state?.enabled ? state.ssid : ''; + } + async function refresh() { + busy=true;render(); + try {state=await wifi({operation:'hotspotStatus'});status.textContent=''; + await call('settings.set',{values:{'tethering.wifi.enabled':state.enabled}}); + } catch(error) {state=null;status.textContent=message('Impossible de lire le point d’accès Wi-Fi.','Cannot read Wi-Fi hotspot status.');} + finally {busy=false;render();} + } + return { + onInit(element) { + panel=element;toggle=panel.querySelector('#tethering-wifi-enabled');link=panel.querySelector('#hotspot-settings-section a');status=note(panel); + const usb=panel.querySelector('#tethering-usb-enabled');usb.disabled=true; + const explanation=document.createElement('p');explanation.className='explanation'; + explanation.textContent=message('Le partage par USB n’est pas encore disponible.','USB tethering is not available yet.');usb.closest('ul').append(explanation); + link.addEventListener('click',event=>{ + event.preventDefault();event.stopImmediatePropagation();if(busy || !state || state.enabled)return; + require(['modules/dialog_service'],DialogService=>DialogService.show('hotspot-wifiSettings',{state}).then(refresh)); + }); + toggle.addEventListener('change',async()=>{ + if(busy || !state)return; + if(!state.configured) {toggle.checked=false;status.textContent=message('Configurez d’abord le nom et le mot de passe du point d’accès.','Configure the hotspot name and password first.');return;} + const enabled=toggle.checked;busy=true;render(); + status.textContent=message('Modification du partage Wi-Fi…','Changing Wi-Fi sharing…'); + try { + if(enabled && (await call('settings.get',{key:'airplaneMode.status'})).value==='enabled')throw Error('AIRPLANE_MODE'); + state=await wifi({operation:'hotspotPower',enabled}); + await call('settings.set',{values:{'tethering.wifi.enabled':state.enabled}});status.textContent=''; + } catch(error) { + try {state=await wifi({operation:'hotspotStatus'});}catch {state=null;} + status.textContent=error.message==='AIRPLANE_MODE' ? message('Désactivez le mode avion avant de partager la connexion.','Turn off airplane mode before sharing the connection.') : message('Le changement a échoué. Vérifiez la connexion réseau.','The change failed. Check the network connection.'); + } finally {busy=false;render();} + }); + },onBeforeShow:refresh + }; + } + function hotspotDialog() { + let panel,ssid,security,password,show,status,busy=false; + function visibility() { + const hidden=security.value==='open'; + panel.querySelector('.password').hidden=hidden;panel.querySelector('.password-description').hidden=hidden; + } + return { + onInit(element) { + panel=element;ssid=panel.querySelector('input[type=text]');security=panel.querySelector('select');password=panel.querySelector('input[name=password]');show=panel.querySelector('gaia-checkbox');status=note(panel); + // These credentials belong to NetworkManager, not the global settings database. + for(const control of [ssid,security,password])control.removeAttribute('data-setting'); + security.replaceChildren(new Option('WPA2','wpa2-psk'),new Option(message('Ouvert','Open'),'open')); + security.addEventListener('change',visibility); + show.addEventListener('change',()=>{password.type=show.checked?'text':'password';}); + }, + onBeforeShow(element,options) { + const state=options.state;ssid.value=state.ssid;security.value=state.security;password.value=state.password; + password.type='password';show.checked=false;status.textContent='';visibility(); + }, + async onSubmit() { + if(busy)throw Error('HOTSPOT_BUSY'); + const values={operation:'hotspotSave',ssid:ssid.value,security:security.value,password:password.value}; + if(!values.ssid || new TextEncoder().encode(values.ssid).length>32 || (values.security!=='open' && (new TextEncoder().encode(values.password).length<8 || new TextEncoder().encode(values.password).length>63))) { + status.textContent=message('Nom : 1 à 32 octets. Mot de passe WPA2 : 8 à 63 octets.','Name: 1–32 bytes. WPA2 password: 8–63 bytes.');throw Error('HOTSPOT_INVALID'); + } + busy=true;const controls=panel.querySelectorAll('input,select,button,gaia-checkbox');controls.forEach(c=>c.disabled=true); + try {await wifi(values);return {};} + catch(error) {status.textContent=message('Impossible d’enregistrer le point d’accès.','Cannot save the hotspot.');throw error;} + finally {busy=false;controls.forEach(c=>c.disabled=false);} + }, + onCancel() {if(busy)throw Error('HOTSPOT_BUSY');}, + onHide() {password.value='';} + }; + } + window.VulpesWifiSettings={panels:{hotspot:hotspotPanel},dialogs:{'hotspot-wifiSettings':hotspotDialog}}; + } addEventListener('DOMContentLoaded', async () => { try { const state = await call('platform.wifi',{operation:'status'}); @@ -67,6 +158,7 @@ try { const result=await call('platform.wifi',{operation:'setEnabled',enabled}); power(result.enabled,true); + if(!result.enabled)await call('settings.set',{values:{'tethering.wifi.enabled':false}}); } catch(error) { await call('settings.set',{values:{'wifi.enabled':manager.enabled}}); console.warn('Wi-Fi radio change failed',error); diff --git a/host/platform-ui.js b/host/platform-ui.js index 182d4947..241b2174 100644 --- a/host/platform-ui.js +++ b/host/platform-ui.js @@ -86,6 +86,18 @@ try { return await call('platform.' + operation, params); } catch (error) { failure(error); } }; + if (app === 'communications') { + // All Gaia call entry points use the same authorized host operation. + window.VulpesPlatformDial = async number => { + try { + return navigator.mozTelephony ? await navigator.mozTelephony.dial(number) : + await call('platform.dial', {number}); + } catch (error) { + failure(error); + throw error; + } + }; + } function button(id, french, english, action) { const b = text(document.createElement('button'), french, english); b.id = id; @@ -145,7 +157,10 @@ event.preventDefault(); event.stopImmediatePropagation(); const number = document.getElementById('phone-number-view')?.value; - if (number) run('dial', {number}); + if (number) { + if (window.CallHandler) CallHandler.call(number, 0); + else window.VulpesPlatformDial(number).catch(() => {}); + } }, true); async function initialize() { const caps = await capabilities; diff --git a/host/shell.js b/host/shell.js index 2c57bc75..cfc1615f 100644 --- a/host/shell.js +++ b/host/shell.js @@ -30,7 +30,15 @@ window.addEventListener('load', async () => { const pollKeys = async () => { try { const state = await IOUtils.readJSON('/run/tundra-buttons/events.json'); - if (state.session !== session) { session = state.session; sequence = state.sequence; } + if (state.session !== session) { + session = state.session; + // Keep events already present when the poller first sees a new + // service session. Setting the sequence to the file's final + // value silently dropped the first physical key presses after a + // Gecko reload. + const first = state.events?.[0]?.id; + sequence = Number.isInteger(first) ? first - 1 : 0; + } else if (Date.now()/1000-state.recordedAt < 5) { for (const event of state.events) if (event.id > sequence) { Runtime.broadcast('hardware-key', {type:event.type}, 'system'); @@ -46,7 +54,7 @@ window.addEventListener('load', async () => { parent: { esModuleURI: 'resource://vulpes/host/actors/VulpesParent.sys.mjs' }, child: { esModuleURI: 'resource://vulpes/host/actors/VulpesChild.sys.mjs', - events: { DOMDocElementInserted: { capture: true }, focusin:{capture:true}, pointerdown:{capture:true} }, + events: { DOMDocElementInserted: { capture: true }, focusin:{capture:true}, pointerdown:{capture:true}, click:{capture:true} }, }, allFrames: true, safeForUntrustedWebProcess: true, @@ -89,6 +97,11 @@ window.addEventListener('load', async () => { browser.style.width = width + 'px'; browser.style.height = height + 'px'; homeButton.style.width = landscape ? '44px' : window.innerWidth + 'px'; + // Flush the embedder's layout before notifying the remote document. + // On Wayland its initial viewport can otherwise remain 0x0 until an + // unrelated chrome layout query, leaving Gaia and video unpainted. + browser.getBoundingClientRect(); + browser.frameLoader?.requestUpdatePosition?.(); }; window.addEventListener('resize', resize); diff --git a/overrides/camera.gaiamobile.org/js/lib/dcf.js b/overrides/camera.gaiamobile.org/js/lib/dcf.js new file mode 100644 index 00000000..ba28ba98 --- /dev/null +++ b/overrides/camera.gaiamobile.org/js/lib/dcf.js @@ -0,0 +1,101 @@ +define(function(require, exports, module) { +'use strict'; + +// This handles the logic pertaining to the naming of files according +// to the Design rule for Camera File System +// * http://en.wikipedia.org/wiki/Design_rule_for_Camera_File_system + +/** + * Dependencies + */ + +var asyncStorage = require('asyncStorage'); +var debug = require('debug')('dcf'); +var format = require('format'); + +/** + * Locals + */ + +var dcfConfigLoaded = false; +var deferredArgs = null; +var defaultSeq = {file: 1, dir: 100}; +var dcfConfig = { + key: 'dcf_key', + seq: null, + postFix: 'MZLLA', + prefix: {video: 'VID_', image: 'IMG_'}, + ext: {video: window.VulpesVideoExtension || '3gp', image: 'jpg'} +}; + +exports.init = function() { + debug('initializing'); + exports.checkFileCounter(function() { + // We have a previous call to createDCFFilename that is waiting for + // a response, fire it again + if (deferredArgs) { + var args = deferredArgs; + exports.createDCFFilename(args.storage, args.type, args.callback); + deferredArgs = null; + } + + debug('initialized'); + }); +}; + +exports.checkFileCounter = function(callback) { + asyncStorage.getItem(dcfConfig.key, function(value) { + dcfConfigLoaded = true; + dcfConfig.seq = value ? value : defaultSeq; + + if (callback) { + callback(); + } + }); +}; + +exports.createDCFFilename = function(storage, type, callback) { + + // We havent loaded the current counters from indexedDB yet, defer + // the call + if (!dcfConfigLoaded) { + deferredArgs = {storage: storage, type: type, callback: callback}; + return; + } + + var dir = 'DCIM/' + dcfConfig.seq.dir + dcfConfig.postFix + '/'; + var filename = dcfConfig.prefix[type] + + format.padLeft(dcfConfig.seq.file, 4, '0') + '.' + + dcfConfig.ext[type]; + var filepath = dir + filename; + + // A file with this name may have been written by the user or + // our indexeddb sequence tracker was cleared, check we wont overwrite + // anything + var req = storage.get(filepath); + + // A file existed, we bump the directory then try to generate a + // new filename + req.onsuccess = function() { + dcfConfig.seq.file = 1; + dcfConfig.seq.dir += 1; + asyncStorage.setItem(dcfConfig.key, dcfConfig.seq, function() { + exports.createDCFFilename(storage, type, callback); + }); + }; + + // No file existed, we are good to go + req.onerror = function() { + if (dcfConfig.seq.file < 9999) { + dcfConfig.seq.file += 1; + } else { + dcfConfig.seq.file = 1; + dcfConfig.seq.dir += 1; + } + asyncStorage.setItem(dcfConfig.key, dcfConfig.seq, function() { + callback(filepath, filename, dir); + }); + }; +}; + +}); diff --git a/overrides/camera.gaiamobile.org/js/main.js b/overrides/camera.gaiamobile.org/js/main.js index e8443fe6..376b3c17 100644 --- a/overrides/camera.gaiamobile.org/js/main.js +++ b/overrides/camera.gaiamobile.org/js/main.js @@ -4265,7 +4265,7 @@ define('controllers/overlay', [ if (id !== overlay.id) { return; } - var closable = self.activity.pick && type !== 'request-fail'; + var closable = self.activity.pick || reason === 'request-fail'; var view = new OverlayView({ type: reason, closable: closable @@ -4274,6 +4274,10 @@ define('controllers/overlay', [ return; } overlay.view = view.appendTo(document.body).on('click:close-btn', function () { + if (reason === 'request-fail' && !self.activity.pick) { + self.updateOverlay(type, false); + return; + } self.app.emit('activitycanceled'); }); debug('inserted \'%s/%s\' overlay', type, reason); @@ -5452,6 +5456,9 @@ define('views/viewfinder', [ if (!preview) { return; } + // Recompute when the embedded app becomes visible or rotates. + this.getSize(); + if (!this.width || !this.height) { return; } var aspect; if (sensorAngle % 180 === 0) { this.container = { @@ -5702,6 +5709,7 @@ define('controllers/viewfinder', [ this.views.viewfinder.set('grid', 'off'); }; ViewfinderController.prototype.bindEvents = function () { + window.addEventListener('resize', this.configurePreview); this.views.viewfinder.on('fadedin', this.app.firer('viewfinder:visible')); this.views.viewfinder.on('fadedout', this.app.firer('viewfinder:hidden')); this.views.viewfinder.on('click', this.app.firer('viewfinder:click')); @@ -6280,167 +6288,313 @@ define('lib/bytes-to-pixels', [ return Math.round(uncompressedBytes / bytesPerPixel); }; }); -define('controllers/activity', [ - 'require', - 'exports', - 'module', - 'debug', - 'lib/bytes-to-pixels', - 'lib/bind-all' -], function (require, exports, module) { - 'use strict'; - var debug = require('debug')('controller:activity'); - var bytesToPixels = require('lib/bytes-to-pixels'); - var bindAll = require('lib/bind-all'); - module.exports = function (app) { - return new ActivityController(app); - }; - module.exports.ActivityController = ActivityController; - function ActivityController(app) { - bindAll(this); - this.app = app; - this.win = app.win; - this.settings = app.settings; - this.configure(); - this.bindEvents(); - debug('initialized'); +define("controllers/activity",["require", "exports", "module", "debug", "lib/bytes-to-pixels", "lib/bind-all"],function(require, exports, module) { +'use strict'; + +/** + * Dependencies + */ + +var debug = require('debug')('controller:activity'); +var bytesToPixels = require('lib/bytes-to-pixels'); +var bindAll = require('lib/bind-all'); + +/** + * Exports + */ + +module.exports = function(app) { return new ActivityController(app); }; +module.exports.ActivityController = ActivityController; + +/** + * Initialize new `ActivityController` + * + * @param {App} app + */ +function ActivityController(app) { + bindAll(this); + this.app = app; + this.win = app.win; + this.settings = app.settings; + this.configure(); + this.bindEvents(); + debug('initialized'); +} + +/** + * Supported activity types. + * + * @type {Object} + */ +ActivityController.prototype.types = { + pick: 'pick', + record: 'record' +}; + +/** + * Initial configuration. + * + * @private + */ +ActivityController.prototype.configure = function() { + this.name = this.getName(); + this.app.activity[this.name] = true; +}; + +/** + * Filter down pictureSizes and + * recorderProfiles to match activity + * parameters each time the settings + * are configured. + * + * @private + */ +ActivityController.prototype.bindEvents = function() { + this.app.on('activitycanceled', this.onActivityCanceled); + this.app.on('confirm:selected', this.onActivityConfirmed); + + // If an activity name was found, we must bind + // the listener straight away so we don't miss + // the event, otherwise we can bind more lazily. + if (this.name) { this.setupListener(); } + else { this.app.once('criticalpathdone', this.setupListener); } +}; + +ActivityController.prototype.setupListener = function() { + debug('setup listener'); + navigator.mozSetMessageHandler('activity', this.onMessage); + debug('listener setup'); +}; + +/** + * Get activity name from the hash fragment. + * + * This is used only so that some parts + * of the app can prepare for an incoming + * activity before the message arrives. + * + * @private + */ +ActivityController.prototype.getName = function() { + var hash = this.win.location.hash; + var name = hash && hash.substr(1); + return this.types[name]; +}; + +/** + * Responds to incoming activity message. + * + * Configures the mode then emits an + * event so that other parts of + * the app can update accordingly. + * + * @param {MozActivity} activity + */ +ActivityController.prototype.onMessage = function(activity) { + debug('incoming activity', activity); + var name = activity.source.name; + var supported = this.types[name]; + + // Exit if this activity isn't supported + if (!supported) { return; } + + var data = { + name: name, + maxPixelSize: this.getMaxPixelSize(activity), + maxFileSizeBytes: activity.source.data.maxFileSizeBytes + }; + + this.activity = activity; + this.configureMode(activity); + this.app.emit('activity', data); + this.app.emit('activity:' + name, data); +}; + +/** + * Configures the mode setting based + * on the parameters supplied by + * the incoming activity. + * + * @param {MozActivity} activity + * @private + */ +ActivityController.prototype.configureMode = function(activity) { + var type = activity.source.data.type; + var name = activity.source.name; + var modes = (name === 'pick') ? + this.getModesForPick(type) : + this.getModesForRecord(type); + + this.settings.mode.filterOptions(modes); + this.settings.mode.select(modes[0]); + debug('configured mode', modes); +}; + +/** + * Get a max pixel size estimate based + * on the parameters supplied by the + * incoming activity. + * + * Activities don't always supply pixel + * size restrictions. + * + * NOTE: There + * + * @param {MozActivity} activity + * @return {Number|null} + */ +ActivityController.prototype.getMaxPixelSize = function(activity) { + var data = activity.source.data; + var bytes = data.maxFileSizeBytes; + var maxPickPixelSize = this.settings.activity.get('maxPickPixelSize') || 0; + var maxPixelSize; + + // If bytes were specified then derive + // a maxPixelSize from that, else we + // calculate the maxPixelSize using + // supplied dimensions. + if (bytes) { + maxPixelSize = bytesToPixels(bytes); + } else if (data.width || data.height) { + maxPixelSize = this.getMaxPixelsFromSize(data); + } else { + maxPixelSize = maxPickPixelSize; + } + + // If the Camera app has been configured to have a max pixel size + // for pick activities, ensure we are at or below that value. + if (maxPickPixelSize > 0) { + maxPixelSize = Math.min(maxPixelSize, maxPickPixelSize); + } + + debug('maxPixelsSize: %s', maxPixelSize); + return maxPixelSize; +}; + +ActivityController.prototype.getMaxPixelsFromSize = function(size) { + var scale = this.settings.activity.get('maxPixelSizeScaleFactor'); + var aspect = 4 / 3; + + // In the event that only one dimension has + // been supplied, calculate the largest the + // other edge could be based on a 4:3 aspect. + var width = size.width || size.height * aspect; + var height = size.height || size.width * aspect; + var pixels = width * height; + + // Take the actual total number of + // pixels asked for and bump it by the + // `scale` to cover pictureSizes above + // the pixels asked for. We later resize + // the image post-capture to the exact size + // requested (data.width * data.height). + // + // This is to avoid us picking a pictureSize + // smaller than the number of pixels requested + // and then to scaling up post-capture, + // resulting in a shitty image. + return pixels * scale; +}; + +/** + * Parse types given by 'pick' activity + * and return a list of modes. + * + * @param {Array|String} types + * @return {Array} + */ +ActivityController.prototype.getModesForPick = function(types) { + types = [].concat(types || []); + var modes = []; + + types.forEach(function(item) { + var type = item.split('/')[0]; + var mode = type === 'image' ? 'picture' : type; + + if (modes.indexOf(mode) === -1) { + modes.push(mode); } - ActivityController.prototype.types = { - pick: 'pick', - record: 'record' - }; - ActivityController.prototype.configure = function () { - this.name = this.getName(); - this.app.activity[this.name] = true; - }; - ActivityController.prototype.bindEvents = function () { - this.app.on('activitycanceled', this.onActivityCanceled); - this.app.on('confirm:selected', this.onActivityConfirmed); - if (this.name) { - this.setupListener(); - } else { - this.app.once('criticalpathdone', this.setupListener); + }); + + if (modes.length === 0) { + modes = ['picture', 'video']; + } + + return modes; +}; + +/** + * Parse types given by 'record' activity + * and return a list of modes. + * + * @param {String} type + * @return {Array} + */ +ActivityController.prototype.getModesForRecord = function(type) { + return type === 'videos' ? + ['video', 'picture'] : + ['picture', 'video']; +}; + +/** + * Respond to activity cancelation + * events and send the error call + * via the original acitity object. + * + * @private + */ +ActivityController.prototype.onActivityCanceled = function() { + if (!this.activity) { return; } + this.activity.postError('pick cancelled'); +}; + +// TODO: Messy, tidy up! +ActivityController.prototype.onActivityConfirmed = function(newMedia) { + var self = this; + var activity = this.activity; + var media = { + blob: newMedia.blob + }; + + // In low end devices resizing can be slow. + // We display a spinner + this.app.showSpinner(); + + // Video + if (newMedia.isVideo) { + media.type = media.blob.type || 'video/3gpp'; + media.poster = newMedia.poster.blob; + activity.postResult(media); + this.app.clearSpinner(); + } + + // Image + else { + media.type = 'image/jpeg'; + + // We need to call resizeImageAndSave() unconditionally even if + // the image does not need to be resized because if the camera + // produces images with EXIF orientation, we need to rotate the + // image before returning it from the pick activity. If the image + // does not need to be resized or rotated, then resizeImageAndSave + // will return it unchanged, and will not re-save it. + require(['lib/resize-image-and-save'], function(resizeImageAndSave) { + resizeImageAndSave( + { + blob: newMedia.blob, + width: activity.source.data.width, + height: activity.source.data.height + }, + function onImageResized(resizedBlob) { + media.blob = resizedBlob; + activity.postResult(media); + self.app.clearSpinner(); } - }; - ActivityController.prototype.setupListener = function () { - debug('setup listener'); - navigator.mozSetMessageHandler('activity', this.onMessage); - debug('listener setup'); - }; - ActivityController.prototype.getName = function () { - var hash = this.win.location.hash; - var name = hash && hash.substr(1); - return this.types[name]; - }; - ActivityController.prototype.onMessage = function (activity) { - debug('incoming activity', activity); - var name = activity.source.name; - var supported = this.types[name]; - if (!supported) { - return; - } - var data = { - name: name, - maxPixelSize: this.getMaxPixelSize(activity), - maxFileSizeBytes: activity.source.data.maxFileSizeBytes - }; - this.activity = activity; - this.configureMode(activity); - this.app.emit('activity', data); - this.app.emit('activity:' + name, data); - }; - ActivityController.prototype.configureMode = function (activity) { - var type = activity.source.data.type; - var name = activity.source.name; - var modes = name === 'pick' ? this.getModesForPick(type) : this.getModesForRecord(type); - this.settings.mode.filterOptions(modes); - this.settings.mode.select(modes[0]); - debug('configured mode', modes); - }; - ActivityController.prototype.getMaxPixelSize = function (activity) { - var data = activity.source.data; - var bytes = data.maxFileSizeBytes; - var maxPickPixelSize = this.settings.activity.get('maxPickPixelSize') || 0; - var maxPixelSize; - if (bytes) { - maxPixelSize = bytesToPixels(bytes); - } else if (data.width || data.height) { - maxPixelSize = this.getMaxPixelsFromSize(data); - } else { - maxPixelSize = maxPickPixelSize; - } - if (maxPickPixelSize > 0) { - maxPixelSize = Math.min(maxPixelSize, maxPickPixelSize); - } - debug('maxPixelsSize: %s', maxPixelSize); - return maxPixelSize; - }; - ActivityController.prototype.getMaxPixelsFromSize = function (size) { - var scale = this.settings.activity.get('maxPixelSizeScaleFactor'); - var aspect = 4 / 3; - var width = size.width || size.height * aspect; - var height = size.height || size.width * aspect; - var pixels = width * height; - return pixels * scale; - }; - ActivityController.prototype.getModesForPick = function (types) { - types = [].concat(types || []); - var modes = []; - types.forEach(function (item) { - var type = item.split('/')[0]; - var mode = type === 'image' ? 'picture' : type; - if (modes.indexOf(mode) === -1) { - modes.push(mode); - } - }); - if (modes.length === 0) { - modes = [ - 'picture', - 'video' - ]; - } - return modes; - }; - ActivityController.prototype.getModesForRecord = function (type) { - return type === 'videos' ? [ - 'video', - 'picture' - ] : [ - 'picture', - 'video' - ]; - }; - ActivityController.prototype.onActivityCanceled = function () { - if (!this.activity) { - return; - } - this.activity.postError('pick cancelled'); - }; - ActivityController.prototype.onActivityConfirmed = function (newMedia) { - var self = this; - var activity = this.activity; - var media = { blob: newMedia.blob }; - this.app.showSpinner(); - if (newMedia.isVideo) { - media.type = 'video/3gpp'; - media.poster = newMedia.poster.blob; - activity.postResult(media); - this.app.clearSpinner(); - } else { - media.type = 'image/jpeg'; - require(['lib/resize-image-and-save'], function (resizeImageAndSave) { - resizeImageAndSave({ - blob: newMedia.blob, - width: activity.source.data.width, - height: activity.source.data.height - }, function onImageResized(resizedBlob) { - media.blob = resizedBlob; - activity.postResult(media); - self.app.clearSpinner(); - }); - }); - } - }; + ); + }); + } +}; + }); define('controllers/camera', [ 'require', @@ -6792,354 +6946,606 @@ define('controllers/camera', [ } }; }); -define('lib/storage', [ - 'require', - 'exports', - 'module', - 'debug', - 'lib/bind-all', - 'evt' -], function (require, exports, module) { - 'use strict'; - var debug = require('debug')('storage'); - var bindAll = require('lib/bind-all'); - var events = require('evt'); - var storageSingleton; - module.exports = Storage; - events(Storage.prototype); - function Storage(options) { - if (storageSingleton) { - return storageSingleton; - } - storageSingleton = this; - bindAll(this); - this.maxFileSize = 0; - options = options || {}; - this.dcf = options.dcf; - if (this.dcf) { - this.dcf.init(); - } - this.require = options.require || require; - navigator.mozSettings.addObserver('device.storage.writable.name', this.onStorageVolumeChanged); - this.configure(); - debug('initialized'); +define("lib/storage",["require", "exports", "module", "debug", "lib/bind-all", "evt"],function(require, exports, module) { +'use strict'; + +/** + * Dependencies + */ + +var debug = require('debug')('storage'); +var bindAll = require('lib/bind-all'); +var events = require('evt'); +var storageSingleton; + +/** + * Expose `Storage` + */ + +module.exports = Storage; + +/** + * Mixin event emitter + */ + +events(Storage.prototype); + +/** + * Initialize a new `Storage`. + * + * @param {Object} options + */ +function Storage(options) { + if (storageSingleton) { + return storageSingleton; + } + storageSingleton = this; + bindAll(this); + this.maxFileSize = 0; + options = options || {}; + this.dcf = options.dcf; + if (this.dcf) { + this.dcf.init(); + } + this.require = options.require || require; + navigator.mozSettings.addObserver( + 'device.storage.writable.name', + this.onStorageVolumeChanged); + this.configure(); + debug('initialized'); +} + +Storage.prototype.createFilename = function(storage, type, done) { + var self = this; + this.require(['asyncStorage', 'lib/dcf'], function(as, dcf) { + if (!self.dcf) { + self.dcf = dcf; + self.dcf.init(); } - Storage.prototype.createFilename = function (storage, type, done) { - var self = this; - this.require([ - 'asyncStorage', - 'lib/dcf' - ], function (as, dcf) { - if (!self.dcf) { - self.dcf = dcf; - self.dcf.init(); - } - self.dcf.createDCFFilename(storage, type, done); - }); + + self.dcf.createDCFFilename(storage, type, done); + }); +}; + +/** + * Save the image Blob to DeviceStorage + * then lookup the File reference and + * return that in the callback as well + * as the resulting paths. + * + * You always want to forget about the + * Blob you told us about and use the + * File instead since otherwise you + * are wasting precious memory. + * + * @param {Object} [options] + * @param {String} options.filepath + * The path to save the image to. + */ +Storage.prototype.addPicture = function(blob, options, done) { + if (typeof options === 'function') { + done = options; + options = {}; + } + + done = done || function() {}; + var filepath = options && options.filepath; + var self = this; + debug('add picture', filepath); + + // Create a filepath if + // one hasn't been given. + if (!filepath) { + debug('creating filename'); + this.createFilename(this.picture, 'image', onCreated); + } else { + onCreated(filepath); + } + + function onCreated(filepath) { + var req = self.picture.addNamed(blob, filepath); + req.onerror = function() { self.emit('error'); }; + req.onsuccess = function(e) { + debug('image stored', filepath); + var absolutePath = e.target.result; + + // `addNamed` does not give us a File + // handle so we need to get() it again. + refetchFile(filepath, absolutePath); }; - Storage.prototype.addPicture = function (blob, options, done) { - if (typeof options === 'function') { - done = options; - options = {}; - } - done = done || function () { - }; - var filepath = options && options.filepath; - var self = this; - debug('add picture', filepath); - if (!filepath) { - debug('creating filename'); - this.createFilename(this.picture, 'image', onCreated); - } else { - onCreated(filepath); - } - function onCreated(filepath) { - var req = self.picture.addNamed(blob, filepath); - req.onerror = function () { - self.emit('error'); - }; - req.onsuccess = function (e) { - debug('image stored', filepath); - var absolutePath = e.target.result; - refetchFile(filepath, absolutePath); - }; - } - function refetchFile(filepath, absolutePath) { - var req = self.picture.get(filepath); - req.onerror = function () { - self.emit('error'); - done('Error adding picture to storage'); - }; - req.onsuccess = function (e) { - debug('image file blob handle retrieved'); - var fileBlob = e.target.result; - done(null, filepath, absolutePath, fileBlob); - }; - } + } + + function refetchFile(filepath, absolutePath) { + var req = self.picture.get(filepath); + req.onerror = function() { + self.emit('error'); + done('Error adding picture to storage'); }; - Storage.prototype.createVideoFilepath = function (done) { - var videoStorage = this.video; - var self = this; - this.createFilename(this.video, 'video', function (filepath) { - var dummyFilepath = getDir(filepath) + '.tmp.3gp'; - var blob = new Blob([''], { type: 'video/3gpp' }); - var req = videoStorage.addNamed(blob, dummyFilepath); - req.onerror = function (e) { - debug('Failed to add ' + filepath + ' to DeviceStorage', e); - var req = videoStorage.delete(dummyFilepath); - req.onerror = function () { - done('Error creating video file path'); - }; - req.onsuccess = function () { - self.createVideoFilepath(done); - }; - }; - req.onsuccess = function (e) { - videoStorage.delete(e.target.result); - done(null, filepath); - }; - }); + req.onsuccess = function(e) { + debug('image file blob handle retrieved'); + var fileBlob = e.target.result; + done(null, filepath, absolutePath, fileBlob); }; - Storage.prototype.onStorageChange = function (e) { - debug('state change: %s', e.reason); - var value = e.reason; - switch (value) { - case 'deleted': - var filepath = this.checkFilepath(e.path); - this.emit('itemdeleted', { path: filepath }); - break; - case 'available': - case 'shared': - case 'unavailable': - this.setState(value); - break; - } - this.check(); + } +}; + +/** + * Create a new video filepath. + * + * The CameraControl API will not + * automatically create directories + * for the new file if they do not + * exist. + * + * So we write a dummy file to the + * same directory via DeviceStorage + * to ensure that the directory exists + * before attempting to record to this + * filepath. + * + * If the request errors it can mean that + * there is a file that already exists + * in that name. If so, we attempt to delete + * it and try to create the filepath again. + * + * @param {Function} done + * @public + */ +Storage.prototype.createVideoFilepath = function(done) { + var videoStorage = this.video; + var self = this; + + this.createFilename(this.video, 'video', function(filepath) { + var dummyFilepath = getDir(filepath) + '.tmp.3gp'; + var blob = new Blob([''], { type: 'video/3gpp' }); + var req = videoStorage.addNamed(blob, dummyFilepath); + + req.onerror = function(e) { + debug('Failed to add ' + filepath + ' to DeviceStorage', e); + var req = videoStorage.delete(dummyFilepath); + req.onerror = function() { done('Error creating video file path'); }; + req.onsuccess = function() { self.createVideoFilepath(done); }; }; - Storage.prototype.configure = function (storageVolumeName) { - var i; - var videosStorages; - var picturesStorages; - if (this.picture) { - this.picture.removeEventListener('change', this.onStorageChange); - } - if (!storageVolumeName) { - this.video = navigator.getDeviceStorage('videos'); - this.picture = navigator.getDeviceStorage('pictures'); - } else { - videosStorages = navigator.getDeviceStorages('videos'); - this.video = videosStorages[0]; - for (i = 0; i < videosStorages.length; ++i) { - if (videosStorages[i].storageName === storageVolumeName) { - this.video = videosStorages[i]; - break; - } - } - picturesStorages = navigator.getDeviceStorages('pictures'); - this.picture = picturesStorages[0]; - for (i = 0; i < picturesStorages.length; ++i) { - if (picturesStorages[i].storageName === storageVolumeName) { - this.picture = picturesStorages[i]; - break; - } - } - } - if (!this.picture) { - this.setState('unavailable'); - return; - } - this.picture.addEventListener('change', this.onStorageChange); - this.emit('volumechanged', { - video: this.video, - picture: this.picture - }); + + req.onsuccess = function(e) { + videoStorage.delete(e.target.result); + done(null, filepath); }; - Storage.prototype.onStorageVolumeChanged = function (setting) { - debug('default storage volume change: %s', setting.settingValue); - this.configure(setting.settingValue); - }; - Storage.prototype.checkFilepath = function (filepath) { - var startString = filepath.indexOf('DCIM/'); - if (startString < -1) { - return; - } else if (startString > 0) { - filepath = filepath.substr(startString); - } - if (filepath.indexOf('VID') != -1 && filepath.lastIndexOf('.jpg') === filepath.length - 4) { - filepath = filepath.replace('.jpg', '.3gp'); - } - return filepath; - }; - Storage.prototype.setState = function (value) { - this.state = value; - debug('set state: %s', value); - this.emit('changed', value); - }; - Storage.prototype.setMaxFileSize = function (maxFileSize) { - this.maxFileSize = maxFileSize; - this.check(); - debug('max file size set: %d', maxFileSize); - }; - Storage.prototype.check = function (done) { - debug('check'); - var self = this; - done = done || function () { - }; - this.getState(function (result) { - self.setState(result); - if (!self.available()) { - onComplete('unhealthy'); - return; - } - if (self.dcf) { - self.dcf.checkFileCounter(); - } - self.isSpace(function (result) { - if (!result) { - self.setState('nospace'); - } - onComplete('healthy'); - }); - }); - function onComplete(state) { - self.emit('checked', state); - } - }; - Storage.prototype.isSpace = function (done) { - var maxFileSize = this.maxFileSize; - this.picture.freeSpace().onsuccess = function (e) { - var freeSpace = e.target.result; - var result = freeSpace > maxFileSize; - debug('is space: %s', result, freeSpace, maxFileSize); - done(result); - }; - }; - Storage.prototype.getState = function (done) { - if (!this.picture) { - setTimeout(function () { - done('unavailable'); - }); - return; - } - this.picture.available().onsuccess = function (e) { - done(e.target.result); - }; - }; - Storage.prototype.available = function () { - return this.state === 'available'; - }; - Storage.prototype.deletePicture = function (filepath, done) { - var req = this.picture.delete(filepath); - req.onerror = function (e) { - var message = 'Failed to delete ' + filepath + ' from DeviceStorage:' + e.target.error; - console.warn(message); - done(message); - }; - if (done) { - req.onsuccess = function () { - done(null); - }; - } - }; - Storage.prototype.deleteVideo = function (filepath) { - var poster = filepath.replace('.3gp', '.jpg'); - this.video.delete(filepath).onerror = function (e) { - console.warn('Failed to delete', filepath, 'from DeviceStorage:', e.target.error); - }; - this.picture.delete(poster).onerror = function (e) { - console.warn('Failed to delete poster image', poster, 'for video', filepath, 'from DeviceStorage:', e.target.error); - }; - }; - function getDir(filepath) { - var index = filepath.lastIndexOf('/') + 1; - return index ? filepath.substring(0, index) : ''; + }); +}; + +Storage.prototype.onStorageChange = function(e) { + debug('state change: %s', e.reason); + var value = e.reason; + + switch (value) { + case 'deleted': + // Emit an `itemdeleted` event to + // allow parts of the UI to update. + var filepath = this.checkFilepath(e.path); + this.emit('itemdeleted', { path: filepath }); + break; + case 'available': + case 'shared': + case 'unavailable': + this.setState(value); + break; + } + + // Check storage + // has spare capacity + this.check(); +}; + +Storage.prototype.configure = function(storageVolumeName) { + var i; + var videosStorages; + var picturesStorages; + // If we had a previous ds for pictures, let's remove the observer + // we had set as well before fetching new ds. + if (this.picture) { + this.picture.removeEventListener('change', this.onStorageChange); + } + if (!storageVolumeName) { + this.video = navigator.getDeviceStorage('videos'); + this.picture = navigator.getDeviceStorage('pictures'); + } else { // We select the volumes with the passed name + videosStorages = navigator.getDeviceStorages('videos'); + this.video = videosStorages[0]; + for (i = 0; i < videosStorages.length; ++i) { + if (videosStorages[i].storageName === storageVolumeName) { + this.video = videosStorages[i]; + break; + } } + + picturesStorages = navigator.getDeviceStorages('pictures'); + this.picture = picturesStorages[0]; + for (i = 0; i < picturesStorages.length; ++i) { + if (picturesStorages[i].storageName === storageVolumeName) { + this.picture = picturesStorages[i]; + break; + } + } + } + + // Shouldn't happen? + if (!this.picture) { + this.setState('unavailable'); + return; + } + + this.picture.addEventListener('change', this.onStorageChange); + this.emit('volumechanged',{ + video: this.video, + picture: this.picture + }); +}; + +Storage.prototype.onStorageVolumeChanged = function(setting) { + debug('default storage volume change: %s', setting.settingValue); + this.configure(setting.settingValue); +}; + +Storage.prototype.checkFilepath = function(filepath) { + var startString = filepath.indexOf('DCIM/'); + + if (startString < -1) { return; } + else if (startString > 0) { + filepath = filepath.substr(startString); + } + + // Check whether filepath is a video poster image or not. If filepath + // contains 'VID' and ends with '.jpg', consider it a video poster + // image and get the video filepath by changing '.jpg' to '.3gp' + if (filepath.indexOf('VID') != -1 && + filepath.lastIndexOf('.jpg') === filepath.length - 4) { + filepath = filepath.replace('.jpg', '.' + (window.VulpesVideoExtension || '3gp')); + } + + return filepath; +}; + +Storage.prototype.setState = function(value) { + this.state = value; + debug('set state: %s', value); + this.emit('changed', value); +}; + +Storage.prototype.setMaxFileSize = function(maxFileSize) { + this.maxFileSize = maxFileSize; + this.check(); + debug('max file size set: %d', maxFileSize); +}; + +/** + * Run a full storage check. + * + * @param {Function} done + * @public + */ +Storage.prototype.check = function(done) { + debug('check'); + + var self = this; + done = done || function() {}; + + this.getState(function(result) { + self.setState(result); + + if (!self.available()) { + onComplete('unhealthy'); + return; + } + + // check file counter in dcf if it already exist. + if (self.dcf) { + self.dcf.checkFileCounter(); + } + self.isSpace(function(result) { + if (!result) { self.setState('nospace'); } + onComplete('healthy'); + }); + }); + + function onComplete(state) { + self.emit('checked', state); + } +}; + +/** + * Checks if there is enough space to + * accomdate the current `maxFileSize`. + * + * @param {Function} done + */ +Storage.prototype.isSpace = function(done) { + var maxFileSize = this.maxFileSize; + this.picture + .freeSpace() + .onsuccess = function(e) { + var freeSpace = e.target.result; + var result = freeSpace > maxFileSize; + debug('is space: %s', result, freeSpace, maxFileSize); + done(result); + }; +}; + +/** + * Get current storage state. + * + * @param {Function} done + */ +Storage.prototype.getState = function(done) { + if (!this.picture) { + setTimeout(function() { + done('unavailable'); + }); + return; + } + + this.picture + .available() + .onsuccess = function(e) { + done(e.target.result); + }; +}; + +/** + * States if sotrage is available. + * + * @return {Boolean} + */ +Storage.prototype.available = function() { + return this.state === 'available'; +}; + +/** + * Delete a picture. + * + * @param {String} filepath + */ +Storage.prototype.deletePicture = function(filepath, done) { + var req = this.picture.delete(filepath); + req.onerror = function(e) { + var message = 'Failed to delete ' + filepath + + ' from DeviceStorage:' + e.target.error; + console.warn(message); + done(message); + }; + + if (done) { + req.onsuccess = function() { + done(null); + }; + } +}; + +/** + * Delete a video and accompanying + * poster image. + * + * @param {String} filepath + */ +Storage.prototype.deleteVideo = function(filepath) { + var poster = filepath.replace(/\.(?:3gp|mp4|webm)$/, '.jpg'); + + this.video.delete(filepath).onerror = function(e) { + console.warn( + 'Failed to delete', filepath, + 'from DeviceStorage:', e.target.error); + }; + + this.picture.delete(poster).onerror = function(e) { + console.warn( + 'Failed to delete poster image', poster, + 'for video', filepath, + 'from DeviceStorage:', e.target.error); + }; +}; + +/** + * Get the directory from a filepath. + * + * @param {String} filepath + * @return {String} + */ +function getDir(filepath) { + var index = filepath.lastIndexOf('/') + 1; + return index ? filepath.substring(0, index) : ''; +} + }); -define('controllers/storage', [ - 'require', - 'exports', - 'module', - 'debug', - 'lib/bind-all', - 'lib/storage' -], function (require, exports, module) { - 'use strict'; - var debug = require('debug')('controller:storage'); - var bindAll = require('lib/bind-all'); - var Storage = require('lib/storage'); - module.exports = function (app) { - return new StorageController(app); - }; - module.exports.StorageController = StorageController; - function StorageController(app) { - bindAll(this); - this.app = app; - this.camera = app.camera; - this.settings = app.settings; - this.storage = app.storage || new Storage(); - this.bindEvents(); - this.configure(); - debug('initialized'); - } - StorageController.prototype.configure = function () { - this.storage.configure(); - this.camera.createVideoFilepath = this.storage.createVideoFilepath; - this.updateMaxFileSize(); - }; - StorageController.prototype.bindEvents = function () { - debug('bind events'); - this.settings.pictureSizes.on('change:selected', this.updateMaxFileSize); - this.app.on('previewgallery:deletepicture', this.storage.deletePicture); - this.app.on('previewgallery:deletevideo', this.storage.deleteVideo); - this.app.on('settings:configured', this.updateMaxFileSize); - this.app.on('camera:newimage', this.storePicture); - this.app.on('camera:newvideo', this.storeVideo); - this.app.on('visible', this.storage.check); - this.storage.on('volumechanged', this.app.firer('storage:volumechanged')); - this.storage.on('itemdeleted', this.app.firer('storage:itemdeleted')); - this.storage.on('changed', this.onChanged); - this.storage.on('checked', this.onChecked); - debug('events bound'); - }; - StorageController.prototype.onChanged = function (state) { - debug('changed: %s', state); - this.app.emit('storage:changed', state); - }; - StorageController.prototype.onChecked = function (value) { - debug('checked: %s', value); - this.app.emit('storage:checked', value); - this.app.emit('storage:checked:' + value); - }; - StorageController.prototype.storePicture = function (picture) { - var memoryBlob = picture.blob; - var self = this; - this.storage.addPicture(memoryBlob, function (error, filepath, abspath, fileBlob) { - picture.blob = fileBlob; - picture.filepath = filepath; - debug('stored picture', picture); - self.app.emit('newmedia', picture); - }); - }; - StorageController.prototype.storeVideo = function (video) { - debug('new video', video); - var poster = video.poster; - var self = this; - poster.filepath = video.filepath.replace('.3gp', '.jpg'); - video.isVideo = true; - this.storage.addPicture(poster.blob, { filepath: poster.filepath }, function (error, path, absolutePath, fileBlob) { - poster.blob = fileBlob; - debug('new video', video); - self.app.emit('newmedia', video); - }); - }; - StorageController.prototype.updateMaxFileSize = function () { - var pictureSize = this.settings.pictureSizes.selected('data'); - if (pictureSize) { - var bytes = pictureSize.width * pictureSize.height / 2 + 25000; - this.storage.setMaxFileSize(bytes); - debug('maxFileSize updated %s', bytes); - } - }; +define("controllers/storage",["require", "exports", "module", "debug", "lib/bind-all", "lib/storage"],function(require, exports, module) { +'use strict'; + +/** + * Dependencies + */ + +var debug = require('debug')('controller:storage'); +var bindAll = require('lib/bind-all'); +var Storage = require('lib/storage'); + +/** + * Exports + */ + +module.exports = function(app) { return new StorageController(app); }; +module.exports.StorageController = StorageController; + +/** + * Initialize a new `StorageController` + * + * @param {App} app + */ +function StorageController(app) { + bindAll(this); + this.app = app; + this.camera = app.camera; + this.settings = app.settings; + this.storage = app.storage || new Storage(); + this.bindEvents(); + this.configure(); + debug('initialized'); +} + +/** + * Initial configuration. + * + * Give the camera a way to create video filepaths. + * This is so that the camera can record videos + * directly to the final location without us + * having to move the video file from temporary, + * to final location at recording end. + * + * @private + */ +StorageController.prototype.configure = function() { + this.storage.configure(); + this.camera.createVideoFilepath = this.storage.createVideoFilepath; + this.updateMaxFileSize(); +}; + +/** + * Bind to relevant events. + * + * @private + */ +StorageController.prototype.bindEvents = function() { + debug('bind events'); + + // App + this.settings.pictureSizes.on('change:selected', this.updateMaxFileSize); + this.app.on('previewgallery:deletepicture', this.storage.deletePicture); + this.app.on('previewgallery:deletevideo', this.storage.deleteVideo); + this.app.on('settings:configured', this.updateMaxFileSize); + this.app.on('camera:newimage', this.storePicture); + this.app.on('camera:newvideo', this.storeVideo); + this.app.on('visible', this.storage.check); + + // Storage + this.storage.on('volumechanged', this.app.firer('storage:volumechanged')); + this.storage.on('itemdeleted', this.app.firer('storage:itemdeleted')); + this.storage.on('changed', this.onChanged); + this.storage.on('checked', this.onChecked); + debug('events bound'); +}; + +/** + * Relay storage state change events. + * + * @param {String} state + * @private + */ +StorageController.prototype.onChanged = function(state) { + debug('changed: %s', state); + this.app.emit('storage:changed', state); +}; + +/** + * Emit the outcome of a storage check + * so that other parts of the app + * can respond. + * + * @param {String} value + * @private + */ +StorageController.prototype.onChecked = function(value) { + debug('checked: %s', value); + this.app.emit('storage:checked', value); + this.app.emit('storage:checked:' + value); +}; + +/** + * Store a picture. + * + * In either case, save the memory-backed + * photo blob to device storage, retrieve + * the resulting File (blob) and pass that + * around instead of the original memory blob. + * + * This is critical for "pick" activity consumers + * where the memory-backed Blob is either highly + * inefficent or will almost-immediately become + * inaccesible, depending on the state of the + * platform. https://bugzil.la/982779 + * + * @param {Object} picture + * @private + */ +StorageController.prototype.storePicture = function(picture) { + var memoryBlob = picture.blob; + var self = this; + + this.storage.addPicture( + memoryBlob, + function(error, filepath, abspath, fileBlob) { + picture.blob = fileBlob; + picture.filepath = filepath; + debug('stored picture', picture); + self.app.emit('newmedia', picture); + }); +}; + +/** + * Store a video. + * + * We don't store the video blob like + * we do for images, as it is recorded + * directly to the final location. + * This is for memory reason. The poster + * is also automatically created. + * + * @param {Object} video + */ +StorageController.prototype.storeVideo = function(video) { + debug('new video', video); + var poster = video.poster; + var self = this; + + // Add the poster image to the image storage + poster.filepath = video.filepath.replace(/\.(?:3gp|mp4|webm)$/, '.jpg'); + video.isVideo = true; + + this.storage.addPicture( + poster.blob, + { filepath: poster.filepath }, + function(error, path, absolutePath, fileBlob) { + // Replace the memory-backed Blob with the DeviceStorage file-backed File. + // Note that "video" has a reference to "poster", so video previews will + // use this File. + poster.blob = fileBlob; + debug('new video', video); + self.app.emit('newmedia', video); + }); +}; + +/** + * Calculate and set a `maxFileSize` + * value on the storage instance. + * + * This is so that it can alert us when there + * isn't enough space left in storage + * to accomodate a new picture. + * + * It is very unlikely that a JPEG file will have a file size that is + * more than half a byte per pixel. There is some fixed EXIF overhead + * that is the same for small and large pictures, however, so we add + * an additional 25,000 bytes of padding. + * + * @private + */ +StorageController.prototype.updateMaxFileSize = function() { + var pictureSize = this.settings.pictureSizes.selected('data'); + /* Depending on when we get loaded, we may not have a picture size yet. */ + if (pictureSize) { + var bytes = (pictureSize.width * pictureSize.height / 2) + 25000; + this.storage.setMaxFileSize(bytes); + debug('maxFileSize updated %s', bytes); + } +}; + }); window.jsStarted = Date.now(); define('main', [ diff --git a/overrides/gallery.gaiamobile.org/js/MetadataParser.js b/overrides/gallery.gaiamobile.org/js/MetadataParser.js new file mode 100644 index 00000000..87fba101 --- /dev/null +++ b/overrides/gallery.gaiamobile.org/js/MetadataParser.js @@ -0,0 +1,713 @@ +'use strict'; +/* global + CONFIG_MAX_IMAGE_PIXEL_SIZE, + CONFIG_REQUIRED_EXIF_PREVIEW_HEIGHT, + CONFIG_REQUIRED_EXIF_PREVIEW_WIDTH, + cropResizeRotate, + Downsample, + getImageSize, + getVideoRotation, + isPhone, + parseJPEGMetadata, + videostorage +*/ +/* exported + metadataParser +*/ + +// +// This file defines a single metadataParsers object. The two +// properties of this object are metadata parsing functions for image +// files and video files, intended for use with the MediaDB class. +// +// This file depends on JPEGMetadataParser.js and blobview.js +// +var metadataParser = (function() { + // If we generate our own thumbnails, aim for this size. + // Calculate needed size from longer side of the screen. + var THUMBNAIL_WIDTH = computeThumbnailWidth(); + var THUMBNAIL_HEIGHT = THUMBNAIL_WIDTH; + + function computeThumbnailWidth() { + // Make sure this works regardless of current device orientation + var width = window.innerWidth || screen.width; + var height = window.innerHeight || screen.height; + var portraitWidth = Math.max(1, Math.min(width, height)); + var landscapeWidth = Math.max(1, Math.max(width, height)); + var thumbnailsPerRowPortrait = isPhone ? 3 : 4; + var thumbnailsPerRowLandscape = isPhone ? 4 : 6; + return Math.round(window.devicePixelRatio * + Math.max(portraitWidth / thumbnailsPerRowPortrait, + landscapeWidth / thumbnailsPerRowLandscape)); + } + + var thumbnailSize = { + width: THUMBNAIL_WIDTH, + height: THUMBNAIL_HEIGHT + }; + + // Don't try to decode image files of unknown type if bigger than this + var MAX_UNKNOWN_IMAGE_FILE_SIZE = 0.5 * 1024 * 1024; // half a megabyte + + // If a jpeg does not have a suitable EXIF preview and has more than + // this number of pixels, then we create an external preview for it + var MAX_PREVIEWLESS_JPEG_SIZE = 5 * 1024 * 1024; // 5 megapixels + + // An element for loading images + var offscreenImage = new Image(); + + // The screen size. Preview images must be at least this big + // Note that we use screen.width instead of window.innerWidth because + // the window size is different when we are invoked for a pick activity + // (non-fullscreen) and when we are invoked normally (fullscreen). + var sw = screen.width * window.devicePixelRatio; + var sh = screen.height * window.devicePixelRatio; + + // Create a thumbnail size canvas, copy the or