Files
mail/platform/ohos/README.md
T

229 lines
12 KiB
Markdown

# OpenHarmony platform
`platform/ohos` hosts the OpenHarmony port: a small Slint platform backend
that renders into an ArkTS `XComponent` surface, the NAPI cdylibs that host
it, and the ArkTS hap shell used to build and install both the backend demo
and the mail application. It mirrors `platform/android`; the shared UI and
application code stay at the repository root.
**Maintainer:** [springmin](https://github.com/springmin).
## How it plugs into the architecture
The application selects behaviour through the capability aliases declared in
`build.rs` (`mobile`, `desktop`, `linux_desktop`, ...), never through an
operating-system name. OpenHarmony is one of the `mobile` platforms:
- `build.rs` defines `mobile = any(target_os = "android", target_os = "ios",
target_env = "ohos")` and keeps OHOS out of `desktop`/`linux_desktop`.
- `src/platform/ohos.rs` is the application-side platform module: the items
of the host contract in [platform/README.md](../README.md) (PDFium location,
renderer name, PDF worker priority, preview lifecycle, app icon) plus the
system font directory for the email renderer.
- This tree is the only place that may name OpenHarmony: the host builds a
[`PlatformContext`](../README.md) (app-private paths, a document provider, a
credential store and an OAuth redirect broker) and calls
`flectar_mail::run`.
`scripts/check-platform-gates.py` (run in `quality.yml`) rejects operating
system names outside `src/platform/` and the build scripts, so new call sites
must use the aliases.
## Layout
| Path | What it is |
|------|------------|
| `backend/` | `slint-ohos-backend`: Slint `Platform`/`WindowAdapter` for a single XComponent window. femtovg/wgpu renderer (EGL/GLES) with the software renderer as fallback, input translation, IME hooks, clipboard, system fonts. |
| `mail/` | `flectar-mail-ohos` cdylib: NAPI module that builds the `PlatformContext` (app-private paths, `DocumentProvider` over the ArkTS pickers, Asset Store credential store, loopback OAuth broker), starts the application, and exposes the ArkTS bridge (file pickers, clipboard, IME text, external links). |
| `demo/` | `slint-ohos-demo`: minimal cdylib used as a smoke target for backend changes. |
| `shell/` | DevEco shell project (ArkTS). `page-templates/Index.mail.ets` is the real app page and `page-templates/Index.demo.ets` the demo page; `build-hap.sh` copies the selected template to `shell/entry/src/main/ets/pages/Index.ets`. |
| `scripts/env.sh` | Environment: SDK paths, `hdc`, `hap-sign-tool`, `LD_LIBRARY_PATH` for lld. Source it before any `cargo` command in this workspace. |
| `scripts/build-hap.sh` | End-to-end build: Rust cdylib → ArkTS `modules.abc` → hap staging → re-packed resources → signing. `APP=mail` or `APP=demo`. |
| `scripts/run-device.sh` | Installs the built hap, starts it and streams filtered logs (demo by default). |
| `scripts/fetch-pdfium.sh` | Fetches the prebuilt `libpdfium.so` used by the PDF preview. |
| `prebuilt/arm64/` | Prebuilt native libraries that ride inside the hap (signature covers `libs/**`). |
The workspace repeats the root `[patch.crates-io]` entries because this
directory is a cargo workspace of its own, and it depends on the application
with `default-features = false` (the host only drives the library; the
desktop binary is not built). fontique comes from crates.io with its
`fontconfig-dlopen` feature; OHOS has no Fontconfig, so the backend loads
system fonts explicitly from `/system/fonts`.
## Prerequisites
All paths are the ones this workspace uses by default; override through the
environment variables understood by `scripts/env.sh`.
- HarmonyOS SDK root (`OHOS_SDK_ROOT`, default `~/.harmonybrew/opt/ohos-sdk`)
plus its native subdirectory (`OHOS_SDK_NATIVE`).
- `hdc` and a DevEco-created signing material directory
(`$HOME/Documents/ohos/config`, bound to the device UDID). The store
passphrase is fetched at build time by the local `sign-huawei.sh` helper and
is never stored in this repository.
- `node` with hvigor installed (`$HOME/arkts-build/node_modules/@ohos/hvigor`),
and OpenJDK 17 (`$HOME/.harmonybrew/opt/openjdk@17`).
- The Rust `aarch64-unknown-linux-ohos` target (built natively on the device).
## Build and install
```sh
. platform/ohos/scripts/env.sh
APP=mail PROFILE=release CARGO_BUILD_JOBS=4 sh platform/ohos/scripts/build-hap.sh
hdc file send platform/ohos/build/flectar_mail_ohos-signed.hap /data/local/tmp/flectar.hap
hdc shell "bm install -r -d -p /data/local/tmp/flectar.hap"
hdc shell "aa start -a EntryAbility -b com.flectar.mail"
```
The bundle name defaults to `com.flectar.mail`. The hap version follows the
workspace `Cargo.toml`: `versionName` is the crate version and `versionCode`
follows the Android sequence from `scripts/android-version-code.py` (alpha <
beta < rc < release < the next patch), so store uploads stay monotonic
(`0.1.0-alpha.7` becomes `1000007`, `0.1.0` becomes `1009999`).
`VERSION_NAME` and `VERSION_CODE` override either value.
Release builds are signed with the AGC material through `hap-sign-tool`:
```sh
RELEASE_SIGN=1 \
RELEASE_P12=release.p12 RELEASE_CERT=com.flectar.mail.cer \
RELEASE_PROFILE=FlectarMailProfileRelease.p7b \
RELEASE_KEY_ALIAS=releasekey RELEASE_KEY_PWD=... RELEASE_STORE_PWD=... \
APP=mail PROFILE=release sh platform/ohos/scripts/build-hap.sh
```
Without `RELEASE_SIGN=1` the script uses the DevEco auto-signing material,
which is bound to the bundle it was issued for: if that material was generated
for a different name, pass `BUNDLE=<that name>` for local debug builds.
`bm install -r -d` is deliberate: `hdc install` rejects the hap ("install
version downgrade") because the shared bundle name carries the DevEco
project's version code.
The ArkTS entry page (`shell/entry/src/main/ets/pages/Index.ets`) is generated
from `shell/page-templates/Index.<APP>.ets` by the build script and is not
tracked; an IDE build that bypasses the script needs that copy first.
## CI
The platform is tier 3: it is built by a manually dispatched workflow
(`.github/workflows/ohos.yml`), not on every pull request. The HarmonyOS SDK
and hvigor are not anonymously downloadable, so the build job targets a
self-hosted runner with the prerequisites above. Dispatch runs build unsigned
(`SKIP_SIGN=1`); `sign: true` switches to `RELEASE_SIGN=1`, which consumes the
`FLECTAR_OHOS_*` repository secrets through `hap-sign-tool` and never touches
the pull request context.
## Why the hap is re-packed by hand
DevEco's `PackageHap` step needs the packing-tool jar, so `build-hap.sh`
builds the payload with hvigor only up to `modules.abc` and assembles the hap
itself. Three device-side quirks make the hand packing necessary — they are
handled (and asserted) in the script, do not "simplify" them away:
1. **Resources index.** hvigor's resource compile passes a `compression`
option and emits a legacy `resources.index` this device cannot load; every
`$string`/`$media` reference silently resolves to nothing (blank icon, app
name replaced by the bundle name). The script re-runs restool without
`compression` to obtain the `RestoolV2` index DevEco-built haps carry.
2. **Zip layout.** `resources.index` and `resources/**` must be STORED, and
the archive must not contain directory entries (the installer rejects
them).
3. **Resolved ids and module fields.** `labelId`/`iconId`/... are normally
resolved from `$string:`/`$media:` references by `PackageHap`; the script
injects them from the compiler's `id_defined.json`, together with the
device types (2in1/tablet/phone), `ohos.permission.INTERNET` (without it
every socket fails with EPERM) and the HarmonyOS 6.1 SDK coordinates.
## Runtime notes
- **Logs**: `hdc shell "hilog -T slint-ohos,flectar-mail"` for the backend and
application, `-T JSAPP` for the ArkTS console.
- **GPU rendering**: the femtovg/wgpu path (EGL/GLES) is attempted by default
and falls back to the software renderer automatically — before the surface
exists, after a failed attach, and permanently once an attach failed. Local
runs can opt out with `FLECTAR_OHOS_SOFTWARE=1`; the device build has no
environment, so the fallback is the safety net. The wgpu GL-backend
subtleties (why `Backends::GL` must be requested explicitly) are documented
in `backend/src/gpu.rs`.
- **System pickers**: the document picker opens in a window over the
XComponent without hiding it, and frames presented into the covered surface
leave the window unresponsive once the picker closes. Each pending picker
request therefore holds presentation (`backend/src/hold.rs`): the Slint
thread waits, without drawing, until the page reports the result.
- **Soft keyboard**: the page attaches the OHOS input-method controller when
Slint requests the input method. On 2in1 devices with a physical keyboard
the system suppresses the soft keyboard (system applications included)
unless it is explicitly summoned; the built-in IME uses `Alt+Shift`.
- **Input**: XComponent touch events drive Slint's pointer handling; physical
keyboard events are translated (key codes to text, modifiers, auto-repeat)
in `backend/src/xcomponent.rs`, and wheel/touchpad axis events reach the
same module through the XComponent UI input callback (Slint's flickables
then scroll without any application-side change).
- **Privacy**: Flectar publishes the app-specific policy at
`https://flectar.com/mail/privacy/`. The shared app links it from the
welcome screen and Settings > About, in the UI language; a consent flow is
tracked separately from this port.
- **Clipboard**: copying needs no permission, but pasting reads the system
pasteboard and therefore requires `ohos.permission.READ_PASTEBOARD`, an ACL
(restricted) permission. The module declares it and the page requests it on
the first denied paste. The signing profile must list the permission in
`acls.allowed-acls`; a hap built without such a profile fails to install
with "grant request permissions failed".
- **Data**: the application keeps its data under the standard bundle data
path. Import/export goes through the system document picker via the host
`DocumentProvider`, and credentials live in the system Asset Store, never
in plain files.
## Lint
The ArkTS sources are checked with `oxk` from
[ohos-rs/oxc-ark](https://github.com/ohos-rs/oxc-ark), the ArkTS tooling built
on oxc/oxlint (`cargo install oxk --git
https://github.com/ohos-rs/oxc-ark.git` with the `scripts/env.sh` environment).
The rule selection and the project API level live in
`shell/.oxlintrc.jsonc`:
```sh
cd platform/ohos/shell
oxk lint entry/src/main/ets --config .oxlintrc.jsonc --threads 1
```
## Verified on device
HarmonyOS 7.0.0.111 (API 26), 2-in-1 (HUAWEI MateBook Pro):
- IMAP IDLE sync, SMTP sending, OAuth sign-in through the system browser and
the loopback broker.
- CJK glyphs in mail bodies (Blitz/Parley) and the compose editor.
- Compose attachments, account-backup export, database-snapshot export and
certificate import through the system pickers.
- External links, wheel/touchpad scrolling, Asset Store
credentials including recovery from a stale row after reinstalling, and PDF
attachment preview through the bundled PDFium.
- Fresh install signed with the AGC release and debug profiles under the AGC
bundle.
## Known limitations
- Clipboard paste uses the ACL permission `ohos.permission.READ_PASTEBOARD`
(approved). A hap that declares it installs only with a signing profile
whose `acls.allowed-acls` lists it.
- Backup and snapshot import and multi-account setups are not yet exercised on
device.
- The clipboard path reads plain text only: no rich text, images or
drag-and-drop.
- The soft keyboard is summoned explicitly (Alt+Shift on 2-in-1) because the
system suppresses it for native surfaces with a physical keyboard.
- The email renderer runs on the CPU (upstream enables the GPU path only on
desktop).
## Tests
The backend has no automated device-side harness: `cargo test` covers the
shared crates at the repository root, and `APP=demo` (installed with
`scripts/run-device.sh`) is the manual smoke target for backend changes.
The non-Latin font fallback policy lives in `backend/src/fonts.rs`; the host
hands it to the mail renderer and the composer with `configure_email_fonts`.