Files

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.

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 (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 (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

. 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:

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, 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:

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.