Skip to content

feat: OpenHarmony 平台适配——插件化桥接、多窗口、菜单/托盘、无障碍与设备插件(desktop + mobile) - #82

Open
ljy9812 wants to merge 185 commits into
harmony-contrib:mainfrom
ljy9812:ohos-adaptation
Open

ljy9812 wants to merge 185 commits into
harmony-contrib:mainfrom
ljy9812:ohos-adaptation

Conversation

@ljy9812

@ljy9812 ljy9812 commented Sep 9, 2026 •

Copy link
Copy Markdown

概述

为 openharmony-ability 带来完整的 OpenHarmony(OHOS)支持,覆盖 desktop(二合一) 与 mobile 两种形态。取代 #63(分支内容相同——即维护在 ohdev 上的适配线——新开 PR 以获得最新的描述;#63 的 review 历史保留作参考)。

本分支持续与 main 引入的插件化架构保持同步(crates/plugin-* 插件 crate、native_ability/ 下的 ArkTS 桥接、plugins/ 下的逐插件 ArkTS 包),并在此之上扩展 OHOS 适配线。

180 个文件变更,+22,936 / −535(合并 main 后的当前口径)——内部开发产物(agent skill、内部规划文档、Windows 打包辅助脚本、legacy 归档说明)已从 PR 剔除,只携带适配内容。

内容

新增插件 crate(crates/plugin-*)——在既有集合之外新增 10 个插件:

  • plugin-menu——菜单栏 / 弹出菜单 / 预定义项 / 加速键触发链
  • plugin-statusbar——系统托盘(图标、菜单、快捷操作面板)
  • plugin-accessibility——fontScale / 屏幕朗读器查询
  • plugin-screenshot——webview 截图 + 像素取色
  • plugin-continuation——应用接续(源端保存 / 恢复)
  • plugin-clipboard、plugin-deep-link、plugin-global-shortcut、plugin-autostart,以及 plugin-app-control 扩展

ArkTS 桥接(native_ability/ + plugins/)

  • 多窗口管理(主窗口 + 子窗口、FloatPage)、菜单系统(MenuBarComponent、弹出菜单、加速键匹配器)、托盘/statusbar 集成
  • Webview 集成:自定义协议处理、init scripts、剪贴板 / 缩放热键开关、物理键盘 DOM 按键重复合成
  • 无障碍、截图、接续、process(经 appRecovery 退出/重启)插件
  • 生命周期:onCreate fail-fast 回收、onAbilityDestroy 分发、want/接续会话迁移

Rust 核心(crates/ability)

  • 窗口/webview/事件管线、生命周期序列化、几何查询 API、save_state 标志、updater(AppGallery 流程)、版本检测(sdk/distribution API level)

冲突状态(与 main 重构系列)——已解决

本分支自 main 的 6c52bb4 分出;此后 main 前进了 13 个提交(插件化 / node-surface / 生命周期序列化系列,#64–#80),与两侧的适配改动重叠,产生 84 处冲突路径:

  • 约 75% 为机械性——lockfile、包 version 字段、BuildProfile.ets / CHANGELOG.md 戳记
  • 实质冲突集中在 3 个文件——crates/plugin-webview/src/lib.rs、crates/ability/src/app.rs、native_ability/src/main/ets/ability/NativeAbility.ets(两侧演进了相同区域)

两条分支架构本就同构(均已插件化、桥接布局一致),冲突是文本级分叉而非架构对撞。

对齐已完成:main(282425ae)已合入本分支(d88bee7,后续 edac3a8/1af3adc),84 处冲突全部解决,分支与 main 完全同步、PR 不删除任何 main 文件。裁决原则、逐块说明与验证矩阵见下方评论。

测试

  • examples/api 自动化套件:296 例真机运行——295 通过、2 例按设计跳过(剪贴板读权限、设备无振动马达);与同一套件的 Windows 运行逐例对齐
  • 手动测试套件覆盖视觉 / 系统级场景(托盘面板、通知、生物识别、多设备接续、全屏、透明等)
  • 已在二合一 desktop(MateBook Pro)与 mobile(Mate 70)HarmonyOS 设备上验证
  • 合并后的树:真机单元测试 169 通过 / 0 失败 / 0 忽略(openharmony-ability 83 + 16 个插件 crate 86,含非默认特性门控测试;明细见下方评论)

🤖 Generated with Claude Code

MingyuChen1 and others added 30 commits June 8, 2026 19:00
… version detection, autostart, and WebView transparency

This commit brings a comprehensive set of new features and improvements
to openharmony-ability, transforming it from a basic single-window
ability framework into a feature-rich platform for OpenHarmony/HarmonyOS
native applications.

74 files changed, ~8,500 lines added, ~875 lines removed.

New Features:

1. Menu System (crates/ability/src/menu/ — 6 new files, ~1,640 lines)
   Complete menu system with menubar and context popup support:
   - mod.rs: Channel-based architecture using crossbeam_channel for
     bidirectional Rust (muda) <-> ArkTS communication. Background
     forwarder thread dispatches via TSFN callback. Per-window menubar
     visibility state in RwLock<HashMap>. Buffered menubar JSON per
     window for late ArkTS callback registration.
   - types.rs: MenuRequest/MenuRequestData (NAPI object), MenuItemData
     (recursive submenu), AboutMetadataData, NAPI classes Menu,
     MenuItem, Submenu.
   - event.rs: MenuEvent struct with MenuEventDispatcher and global
     GLOBAL_DISPATCHER (LazyLock<Mutex>).
   - predefined.rs: PredefinedType enum (Copy, Cut, Paste, SelectAll,
     Undo, Redo, Minimize, Maximize, Quit, etc.) with factory methods
     and default accelerators (Ctrl+C, Ctrl+V, etc.).
   - state.rs: MenuStateController NAPI class for create/append/destroy.
   - popup.rs: MenuPopup NAPI class for context menu show/hide.

2. System Tray / StatusBar (crates/ability/src/statusbar/ — 5 new files, ~855 lines)
   Full system tray integration:
   - types.rs: StatusBarItem, StatusBarIcon (RGBA pixel data),
     QuickOperation, StatusBarMenuItem/SubMenuItem, StatusBarMenuAction,
     StatusBarClickEvent enum.
   - event.rs: Dual crossbeam_channel pairs for icon click and menu
     click events with register/unregister handlers.
   - manager.rs: 6 TSFNs (Add, Remove, UpdateIcon, UpdateMenu,
     UpdateTips, PredefinedAction) with thread-safe main-thread dispatch.
   - validate.rs: Input validation (max 20 menu items, max 20 sub-items,
     hover tips 1-128 chars, height > 0).

3. Clipboard (crates/ability/src/clipboard/mod.rs — 178 lines)
   clipboard_write_image(rgba, width, height) async function writing
   RGBA image data to system clipboard via TSFN bridge to ArkTS
   writeImageToClipboard (Promise-based with 10-second timeout).

4. App Updater / AppGallery Integration (crates/ability/src/updater.rs + helper/updater.rs — ~420 lines)
   - Updater::check() queries AppGallery for available updates, returns
     CheckResult with version, body, and date.
   - Updater::download_and_install() shows update dialog and drives
     download+install flow.
   - 3 TSFNs for check, show dialog, and download/install operations.

5. Version Detection (crates/ability/src/version.rs — 316 lines)
   - sdk_api_version(): OpenHarmony base API Level (e.g., 12, 14, 20).
   - distribution_api_version(): HarmonyOS distribution version
     (M*10000 + S*100 + F).
   - can_i_use(syscap): queries device system capabilities via ArkTS
     canIUse().
   - Includes device tests for worker thread safety.

6. Multi-Window Support (crates/ability/src/window/mod.rs — 200+ lines)
   - create_os_window(name, window_type) creates OS-level sub-windows
     with global atomic NEXT_WINDOW_ID for unique ID generation.
   - Window decorations support (title bar, border, etc.).
   - WindowManager with destroyWindow() for proper resource cleanup.

7. Float Window (native_ability/.../FloatPage.ets — 300 lines)
   Floating window page component with drag and resize functionality
   using createSubWindow API.

8. Autostart Support (crates/ability/src/autostart.rs + helper/autostart.rs — 412 lines)
   - AutostartManager for managing app auto-start on device boot.
   - TSFN bridge for ArkTS autostart registration/unregistration.
   - Integration with HarmonyOS AbilityManagerService.

9. WebView Transparency (native_ability/.../DefaultWebview.ets — 71+ lines)
   - Transparent background support for WebView.
   - Window transparency synchronization.
   - TransparencyTest demo page for validation.

Improvements & Refactoring:

App Enhancements (app.rs):
- restart(): hard process restart via TSFN with 3-second cooldown.
- set_color_mode(mode: ColorMode): dark/light/system theme switching.
- updater(): get updater handle for AppGallery integration.
- AbilityInitContext now includes sdk_api_version and
  distribution_api_version.
- New NAPI export: is_desktop_device() for compile-time device type
  detection.

Helper Infrastructure (helper/):
- Refactored from thread_local to global Mutex for HELPER storage,
  enables cross-thread access via SendableHelper wrapper.
- New helper/restart.rs: restart TSFN with LazyLock<RwLock> pattern.
- New helper/updater.rs: three TSFN pairs for updater operations.
- New helper/autostart.rs: autostart TSFN management.

WebView Enhancements (webview/):
- window_id field on WebViewInitData and WebViewBuilder for per-window
  webview creation.
- on_page_begin / on_page_end lifecycle callbacks.
- Fixed callback argument indexing (off-by-one in NAPI extraction).
- Added 'static bound to custom protocol callback closures.
- Fixed closure invocation to avoid holding lock during callback.
- Full URL matching for close-window intercept.

Lifecycle & Events:
- onWindowStageDestroy lifecycle hook.
- NewWant event support.
- runJavaScript error handling.

ArkTS Side (Native Layer — ~2,500+ lines, 18 new files):
- ArkHelper.ets (650+ lines): Full ArkHelper interface covering exit,
  restart, updater, permissions, window, webview, statusbar, clipboard,
  autostart.
- MenuBarComponent.ets (451 lines): Per-window menubar UI with click
  handlers and keyboard accelerator support.
- FloatPage.ets (300 lines): Floating window page component.
- menu.ets (239 lines): MenuManager and PredefinedActionExecutor.
- WindowManager.ets (350+ lines): Multi-window management singleton
  with proper destroyWindow() cleanup.
- StatusBarUtils.ets (182 lines): Status bar pixel map creation.
- accelerator_matcher.ets (142 lines): Keyboard shortcut matching.
- Utils.ets (140+ lines): JsHelper and ProxyJsHelper for deferred
  webview init.
- updater.ets (115 lines): AppGallery updater using AppGalleryKit.
- predefined.ets (109 lines): Predefined menu action implementations.
- menu_state.ets (85 lines): Menu state management.
- MenuPopup.ets (80 lines): Context popup menu component.
- menu_types.ets (74 lines): Type definitions for menu system.
- autostart.ets (63 lines): Autostart registration helper.
- os.ets (37 lines): OS-level utility functions.
- ClipboardHelper.ets (33 lines): Clipboard write image implementation.
- TransparencyTest.ets (167 lines): Demo page for transparency testing.

Bug Fixes:
- fix(statusbar): re-register click handlers after addToStatusBar to
  survive receiver rebuilds.
- fix(statusbar): prevent FloatPage from overwriting main window
  helperRef.
- fix(ohos): tray icon click events — abilityName null check and
  one-time on() guard, support relaunch.
- fix(tray): close/fullscreen behavior, menubar startup timing.
- fix(webview): callback argument off-by-one in NAPI extraction.
- fix(webview): closure lock held during callback (potential deadlock).
- fix(webview): use full URL for close-window intercept.
- fix(NativeAbility): duplicate onWindowStageDestroy method.
- fix(WindowManager): proper destroyWindow() cleanup on sub-window close.
- refactor: renamed TAURI_OHOS_DEVICE_TYPE to OHOS_DEVICE_TYPE for
  framework-agnostic naming.
- docs: changed Chinese doc comments to English in helper/mod.rs.

Design Patterns:
- TSFN Bridge Pattern for safe ArkTS calls from Rust worker threads.
- Channel-based Event Distribution via crossbeam_channel.
- OnceLock/LazyLock for thread-safe lazy initialization.
- Per-Window State Maps (HashMap<String, bool>) for menubar state.
- Promise-to-Future Bridge via oneshot::channel + PromiseRaw + timeout.

Testing:
- Added device tests for the version module.
- Added TransparencyTest demo page for WebView transparency validation.
…dedWebBuilder

Call ctrl.setCustomUserAgent(data.userAgent) in onControllerAttached
callback when userAgent is provided. Enables OHOS WebView custom
User-Agent via WebviewController API.
Round 1:
- Extract shared DOMAIN constant into helper/constants.ets (10 files)
- Use system icon for FloatPage close button
- Replace eprintln! with crate::error! in xcomponent.rs and app.rs

Round 2:
- Add feature flags for modular compilation (statusbar, updater, version, window, clipboard, webview, menu)
- Define cfg-gated crate::error!/info!/warn!/debug! macros replacing direct log::*/hilog_*! usage
- Make log/hilog dependencies optional behind 'log' feature flag
- Add cfg gates for version-dependent and module-dependent code paths

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
fix: address upstream PR harmony-contrib#63 review feedback and code quality cleanups
…-user-agent

feat(webview): add setCustomUserAgent support in WebBuilder and EmbeddedWebBuilder
feat(ohos): add window close lifecycle mechanism via NAPI queue
- Extend onNewWant ArkTS callback to extract and serialize want.parameters
- Log parametersJson.length in NativeAbility for debugging
- Define NewWantData interface in type.ets for type-safe parameter passing
- Update NAPI bridge from Function<String> to Function<Object>
- Add #[cfg(target_env = "ohos")] gate on WANT_PARAMETERS static and functions
- Add static Mutex storage with store/take semantics and poison-safe error logging
- Use ? for property extraction (consistent with other lifecycle callbacks)
- Document concurrency contract: store on ArkTS main thread, take on tauri event loop
- Update index.d.ts to match new onNewWant signature
- Add single combined unit test for store/take/clear/overwrite (avoids parallel races)
feat(ohos): forward want.parameters through onNewWant event chain
- Add SnapshotData interface and JsHelper.webPageSnapshot method (ArkTS)
- Implement webPageSnapshot with PixelMap to RGBA conversion and resource cleanup
- Add Rust web_page_snapshot() callback API on Webview struct
- Add unsafe impl Send for Webview (main thread only access)
feat(webview): add webPageSnapshot support for WebView content capture
- Restore PdfConfig struct with to_napi_map() for NAPI transport
- Add scale field to PdfConfig (SDK PdfConfiguration.scale support)
- Restore config parameter in ArkTS createPdf with merge logic
- Update JsHelper/ProxyJsHelper interface to include config
- Revert pdfArrayBuffer().buffer removal (Uint8Array -> ArrayBuffer)
- Revert async fileIo.write back to fileIo.writeSync
- Restore explanatory NAPI callback behavior comments
feat: add PdfConfig, createPdf NAPI + ArkTS implementation
- Add predefined menu actions: hide, close, minimize, showAll, bringAllToFront
- Add clipboard ops (copy, cut, paste, selectAll, undo, redo) using target window webview
- Align predefined menu actions with macOS Key Window semantics
- Add MAIN_WINDOW_ID constant for main window identification
- Centralize resetUserInteractionTracking in WindowManager wrapper methods
- Add getPrimaryWebviewController for clipboard operations on primary webview
- Fix FloatPage aboutToAppear async declaration
- Remove Bug 6 references from comments
feat(menu): predefined multi-window clipboard ops and WindowManager fixes
…lper pending path fix, setBounds in controllers
feat(ohos): add WebViewStyle width/height, set_bounds NAPI, ProxyJsHelper pending path fix
Add Webview::set_cookie NAPI method and ArkTS setCookie (JsHelper
interface, ProxyJsHelper pending-replay, DefaultWebview buildJsHelper)
delegating to WebCookieManager.configCookieSync.
Add comprehensive mouse input handling for OHOS 2in1 desktop devices:

- MouseEventData struct wrapping OH_NativeXComponent_MouseEvent FFI
- MouseAction enum with Press/Release/Move/HoverEnter/HoverLeave variants
- dispatch_mouse_event: NDK DispatchMouseEvent callback
- dispatch_hover_event: NDK DispatchHoverEvent callback for cursor enter/leave
- AxisEventData struct for scroll wheel delta (delta_x, delta_y)
- dispatch_axis_event: ArkUI UIInputEvent AXIS callback for scroll wheel
- register_mouse_callbacks: registers all three callbacks with NDK
- InputEvent::MouseEvent and InputEvent::AxisEvent variants
- Added ohos-xcomponent-sys and ohos-arkui-sys as direct dependencies

Co-Authored-By: Claude <noreply@anthropic.com>
- Add InputSourceType enum (Mouse/TouchScreen/Touchpad/Joystick/Keyboard)
- Add pinch_scale and source_type fields to AxisEventData
- Extract pinch scale via OH_ArkUI_AxisEvent_GetPinchAxisScaleValue
- Extract source type via OH_ArkUI_UIInputEvent_GetSourceType
- Skip axis events with no scroll delta AND no pinch data

Co-Authored-By: Claude <noreply@anthropic.com>
feat(ohos): add setCookie NAPI bridge for webview cookie management
OHOS NDK DispatchMouseEvent doesn't fire when cursor is over WebView,
so we track cursor position from ArkTS onMouse events instead.

- Add CURSOR_POSITION_X/Y atomic statics in app.rs
- Add update_cursor_position() NAPI function for ArkTS to call
- Add .onMouse() handler in MainPage.ets that calls NAPI on Move/Press
- Export CURSOR_POSITION_X/Y for tao to read

Co-Authored-By: Claude <noreply@anthropic.com>
Add a native ArkTS overlay for testing key repeat detection:
- Floating '⌨ Key Test' button (bottom-right corner)
- Focusable overlay panel with HashSet-based repeat tracking
- .onKeyEvent() captures keys at ArkUI level (bypasses WebView)
- ESC or ✕ to close overlay
- repeat=true shown in green bold when key hold is detected

Co-Authored-By: Claude <noreply@anthropic.com>
Add Webview::set_web_debugging_access(bool) and is_web_debugging_access()
NAPI methods. ArkTS side (Utils.ets) tracks webDebuggingEnabled state
(setWebDebuggingAccess const calls the static API first then updates the
var; isWebDebuggingAccess returns the var). DefaultWebview.ets init
routes through the const to keep state in sync. Uses domain socket
(setWebDebuggingAccess(true), no port) — connect Chrome DevTools via
examples/api/devtools.bat which auto-discovers the socket and forwards
via hdc fport.
feat(ohos): add setWebDebuggingAccess NAPI bridge for devtools toggle
…ents

feat(ohos): add mouse event, hover event, and scroll wheel support
…mer API

- Forwarder thread + crossbeam channel + run_on_main_thread architecture
- inputConsumer API (API 14+) for hotkey registration
- 60+ key code mapping (Rust/ArkTS consistent)
- Error code handling (801/4200002/4200003)
- Duplicate modifier dedup, blocking send in unregister_all
- Documented fire-and-forget design and synthetic Released event
- SAFETY comments, dispatch_to_main_thread returns Result

Co-Authored-By: Claude <noreply@anthropic.com>
feat(global_shortcut): add OHOS global shortcut bridge via inputConsumer API
- Phase 6/7: on_window_new Create + window focus NAPI bridge
- Fix crash from napi_reference_unref on tokio worker threads:
  napi_ohos::Error's Drop calls napi_reference_unref which must run
  on main thread. Convert Error to String before sending through
  oneshot channels (clipboard, autostart, updater). Wrap ObjectRef
  in ManuallyDrop in thread-local cache.
feat(ohos): add window focus NAPI and fix napi_reference_unref crash
ljy9812 and others added 24 commits September 28, 2026 09:00
feat(ohos): expand Ability windowing and desktop plugin support
Co-Authored-By: Claude <noreply@anthropic.com>
dispose_render/dispose_all_renders dropped the RootNode before calling
release_render. RootNode::drop unmounts and disposes the XComponent
ArkUI node, so release_render then removed gestures and unregistered
callbacks on freed memory, crashing with SIGSEGV in
GestureEventHub::RemoveGesture (via NDK RemoveGestureFromNode) during
DefaultXComponent teardown.

Reproduced deterministically (2/2 builds) on HAD-W24 by switching tabs
away from a DefaultXComponent page in the demo app: aboutToDisappear
runs inside the ArkUI subtree destruction, so the disposed node is
touched while it is being torn down. Swap the order in all four
dispose sites (main window + sub window, single + all) so
release_render runs while the node is still alive; the RootNode drop
then unmounts and disposes it cleanly.

Pre-existing since the adaptation line (verified identical at
e5220ce); surfaced now because the demo tab UI is the first consumer
that destroys a live DefaultXComponent with registered gestures.

Verified on device: tab switch no longer crashes (pid survives,
no new faultlog) and the native sub-window render path still works.

Co-Authored-By: Claude <noreply@anthropic.com>
Match the established annotation on the neighboring Arc<RefCell> fields
(ime, back_press_interceptor); the lint fires on the current clippy
toolchain.

Co-Authored-By: Claude <noreply@anthropic.com>
Every other module ships its hvigor-generated BuildProfile.ets; the
accessibility module (added to the demo build in the PR #3 merge) was
missing its generated file from tracking.

Co-Authored-By: Claude <noreply@anthropic.com>
The file was committed before being run through the repo formatter; the
format gate formats it on every check.

Co-Authored-By: Claude <noreply@anthropic.com>
Expose per-window continuous or on-demand frame delivery while keeping existing consumers continuous by default. Control only the native frame callback, preserving input and surface callbacks.

Frame handlers retain weak runtime references, validate the render owner, and active delivery changes enforce the render UI thread. Cache policy tests and ARM64 integration builds cover the new API.

(cherry picked from commit 23047cc)
Upstream already releases native consumers before dropping each RootNode. Preserve that ordering and drain the sub-window registry before invoking teardown callbacks, so callbacks can re-enter the registry without a RefCell borrow panic.

(cherry picked from commit 4493821)
Remove the device-type and legacy SysCap precheck for folder selection,
while retaining the independent save-filter-index capability guard.
Preserve picker error codes with device/API context and reject calls
before launch or after completion when the Ability is inactive.
Update the Rust and ArkTS plugin documentation without changing the ABI.

Validation: oxk format and lint, six production-ETS boundary checks,
and source parity with the NearSend 1.2.2 Release package. API 24 tablet
folder selection and nested-file enumeration passed in the simulator;
the original MatePad Edge-specific failure still needs device confirmation.

(cherry picked from commit 9e003b6)
Remove the separately cached render thread ID and validate native render operations against the current bridge session main thread. Preserve pre-surface frame configuration and keep repeated applied-mode calls on a read-only fast path.
Use the API version and declared system capability for getSelectedIndex so capable tablets are not excluded. Rename the capability constant to distinguish this save-result query from folder selection, which is already delegated to the system picker.
Require WindowManager 0.1.1 to unify image bindings, remove the unused libloading dependency, and delete obsolete patch instructions.
ArkUI 0.4.1 handles API-14 keyboard callbacks without the API-20 discriminator. Remove six redundant package.workspace paths and inherit the workspace automatically.
Gate ArkUI keyboard snapshots, reply types, and callbacks behind the keyboard feature, which enables API 14. Preserve raw XComponent input as the default and keep drag independent.
Preserve clipboard record order and harden URL scheme checks, prompt
placement and cancellation, failed window creation rollback, serialized
restore operations, inactive colors, blur cleanup and thermal subscriptions.

Require business-owned clipboard metadata MIME and notification scheme
options. Preserve menu-open window identity, reject excess notification
actions, align system-state wire names and include the new plugins and
configured factories in the aggregate HAR. Update demos and documentation,
and add host runtime regression coverage.

BREAKING CHANGE: ClipboardPlugin and NotificationPlugin require constructor
options. The aggregate exports createAllBridgePlugins(options) instead of
allBridgePlugins. Menu-open channels carry MenuOpenEvent, and system-state
wire types now use the ohos.system-state prefix.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants