cj-tauri 1.1.2 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (162) hide show
  1. package/README.md +137 -77
  2. package/bin/cj-tauri.js +287 -287
  3. package/cjpm.toml +13 -2
  4. package/cli/cjpm.toml +22 -13
  5. package/cli/src/doctor.cj +308 -304
  6. package/cli/src/main.cj +129 -121
  7. package/cli/src/manifest.cj +632 -0
  8. package/cli/src/manifest_test.cj +87 -0
  9. package/cli/src/project.cj +770 -754
  10. package/cli/src/resolve.cj +348 -348
  11. package/cli/src/scaffold.cj +46 -38
  12. package/cli/src/sync.cj +526 -0
  13. package/cli/src/util.cj +9 -0
  14. package/cli/templates/app/README.md +30 -59
  15. package/cli/templates/app/{ui → assets/ui}/index.html +2 -0
  16. package/cli/templates/app/cj-tauri.toml +32 -0
  17. package/cli/templates/app/cjpm.toml +21 -4
  18. package/cli/templates/app/src/main.cj +29 -16
  19. package/cli/templates/app-react/README.md +34 -45
  20. package/cli/templates/app-react/{ui → assets/ui}/index.html +25 -25
  21. package/cli/templates/app-react/{ui → assets/ui}/src/App.css +67 -67
  22. package/cli/templates/app-react/{ui → assets/ui}/src/App.jsx +124 -124
  23. package/cli/templates/app-react/cj-tauri.toml +33 -0
  24. package/cli/templates/app-react/cjpm.toml +21 -4
  25. package/cli/templates/app-react/src/main.cj +18 -29
  26. package/cli/templates/app-vue/README.md +34 -38
  27. package/cli/templates/app-vue/{ui → assets/ui}/index.html +25 -25
  28. package/cli/templates/app-vue/{ui → assets/ui}/src/App.vue +176 -176
  29. package/cli/templates/app-vue/cj-tauri.toml +33 -0
  30. package/cli/templates/app-vue/cjpm.toml +21 -4
  31. package/cli/templates/app-vue/src/main.cj +18 -29
  32. package/cli/templates/{ohos-pc → ohos-skeleton}/README.md +5 -0
  33. package/cli/templates/{ohos-pc → ohos-skeleton}/build-hap.sh +15 -0
  34. package/cli/templates/{ohos-pc → ohos-skeleton}/entry/cjpm.toml +8 -0
  35. package/cli/templates/ohos-skeleton/entry/src/main/cangjie/dialog_host.cj +217 -0
  36. package/cli/templates/ohos-skeleton/entry/src/main/cangjie/index.cj +527 -0
  37. package/cli/templates/{ohos-pc → ohos-skeleton}/entry/src/main/module.json5 +8 -0
  38. package/native/build_win.bat +62 -62
  39. package/native/core/bridge_core.c +1681 -1626
  40. package/native/core/bridge_core.h +675 -618
  41. package/native/core/bridge_js.h +108 -108
  42. package/native/platforms/harmonyos/README.md +115 -6
  43. package/native/platforms/linux/bridge_linux.c +2787 -2485
  44. package/native/platforms/windows/bridge_win.c +3740 -3248
  45. package/native/tests/gtk_threading_probe.c +143 -143
  46. package/native/tests/test_bridge_core.c +1339 -1228
  47. package/package.json +53 -51
  48. package/prebuilt/linux-x64/cj-tauri +0 -0
  49. package/src/api_system.cj +67 -67
  50. package/src/app.cj +325 -940
  51. package/src/app_events.cj +166 -0
  52. package/src/app_host_ops.cj +287 -0
  53. package/src/app_run.cj +283 -0
  54. package/src/app_spec.cj +76 -0
  55. package/src/arkweb_menu.cj +216 -0
  56. package/src/arkweb_script.cj +0 -12
  57. package/src/capability.cj +409 -409
  58. package/src/clipboard_bridge.cj +178 -0
  59. package/src/dialog_spec.cj +79 -0
  60. package/src/dragdrop.cj +26 -26
  61. package/src/embedded_assets.cj +98 -98
  62. package/src/host.cj +752 -604
  63. package/src/host_arkweb.cj +201 -17
  64. package/src/host_webkit.cj +663 -548
  65. package/src/host_webview2.cj +663 -548
  66. package/src/hotkey.cj +66 -66
  67. package/src/ipc_hub.cj +337 -337
  68. package/src/ipc_message.cj +286 -286
  69. package/src/launch.cj +62 -0
  70. package/src/logging.cj +127 -127
  71. package/src/menu.cj +197 -197
  72. package/src/platform/platform_app_context.cj +77 -0
  73. package/src/platform/platform_assets.cj +33 -0
  74. package/src/platform/platform_discover.cj +176 -0
  75. package/src/platform/platform_env.cj +29 -0
  76. package/src/platform/platform_feature.cj +31 -0
  77. package/src/platform/platform_page_source.cj +87 -0
  78. package/src/platform/platform_target_info.cj +32 -0
  79. package/src/platform_host_provider.cj +87 -0
  80. package/src/plugin.cj +333 -299
  81. package/src/plugins/clipboard/plugin_clipboard.cj +152 -144
  82. package/src/plugins/dialog/plugin_dialog.cj +209 -209
  83. package/src/plugins/fs/plugin_fs.cj +105 -1
  84. package/src/plugins/http/http_backend.cj +131 -0
  85. package/src/plugins/http/http_desktop.cj +195 -0
  86. package/src/plugins/http/http_ohos.cj +204 -0
  87. package/src/plugins/http/plugin_http.cj +310 -459
  88. package/src/plugins/log/plugin_log.cj +105 -105
  89. package/src/plugins/menu/plugin_menu.cj +8 -0
  90. package/src/plugins/notification/plugin_notification.cj +154 -146
  91. package/src/plugins/serial/plugin_serial.cj +17 -1
  92. package/src/plugins/serial/serial_bridge.cj +9 -1
  93. package/src/plugins/serial/serial_manager_ohos.cj +92 -0
  94. package/src/plugins/serial/serial_ohos.cj +529 -0
  95. package/src/plugins/shell/plugin_shell.cj +29 -1
  96. package/src/plugins/sqlite/plugin_sqlite.cj +8 -0
  97. package/src/plugins/ws/plugin_ws.cj +488 -470
  98. package/src/process_hidden.cj +174 -174
  99. package/src/scope.cj +391 -391
  100. package/src/shell_event.cj +69 -69
  101. package/src/tests/app_spec_test.cj +126 -0
  102. package/src/tests/capability_test.cj +243 -243
  103. package/src/tests/dialog_spec_test.cj +73 -0
  104. package/src/tests/fake_host.cj +201 -186
  105. package/src/tests/ipc_hub_test.cj +487 -487
  106. package/src/tests/logging_test.cj +62 -62
  107. package/src/tests/platform_contract_test.cj +251 -0
  108. package/src/tests/plugin_clipboard_test.cj +222 -222
  109. package/src/tests/plugin_dialog_test.cj +237 -237
  110. package/src/tests/plugin_fs_test.cj +207 -0
  111. package/src/tests/plugin_http_test.cj +124 -124
  112. package/src/tests/plugin_notification_test.cj +205 -205
  113. package/src/tests/plugin_serial_test.cj +48 -0
  114. package/src/tests/plugin_test.cj +66 -3
  115. package/src/tests/plugin_ws_test.cj +40 -40
  116. package/src/tests/process_hidden_test.cj +129 -129
  117. package/src/tests/scope_test.cj +350 -350
  118. package/src/tests/system_host_test.cj +99 -81
  119. package/src/tests/urlutil_test.cj +53 -53
  120. package/src/tests/version_test.cj +75 -75
  121. package/src/tests/window_registry_test.cj +123 -123
  122. package/src/tray.cj +90 -90
  123. package/src/urlutil.cj +159 -159
  124. package/src/version.cj +44 -44
  125. package/src/window.cj +51 -51
  126. package/src/window_registry.cj +116 -116
  127. package/cli/templates/ohos-pc/entry/src/main/cangjie/index.cj +0 -167
  128. package/prebuilt/win32-x64/cj-tauri.exe +0 -0
  129. package/src/host_factory.cj +0 -48
  130. /package/cli/templates/app/{capabilities → assets/capabilities}/default.json +0 -0
  131. /package/cli/templates/app-react/{capabilities → assets/capabilities}/default.json +0 -0
  132. /package/cli/templates/app-react/{ui → assets/ui}/package.json +0 -0
  133. /package/cli/templates/app-react/{ui → assets/ui}/src/main.jsx +0 -0
  134. /package/cli/templates/app-react/{ui → assets/ui}/vite.config.js +0 -0
  135. /package/cli/templates/app-vue/{capabilities → assets/capabilities}/default.json +0 -0
  136. /package/cli/templates/app-vue/{ui → assets/ui}/package.json +0 -0
  137. /package/cli/templates/app-vue/{ui → assets/ui}/src/main.js +0 -0
  138. /package/cli/templates/app-vue/{ui → assets/ui}/vite.config.js +0 -0
  139. /package/cli/templates/{ohos-pc → ohos-skeleton}/AppScope/app.json5 +0 -0
  140. /package/cli/templates/{ohos-pc → ohos-skeleton}/AppScope/resources/base/element/string.json +0 -0
  141. /package/cli/templates/{ohos-pc → ohos-skeleton}/AppScope/resources/base/media/background.png +0 -0
  142. /package/cli/templates/{ohos-pc → ohos-skeleton}/AppScope/resources/base/media/foreground.png +0 -0
  143. /package/cli/templates/{ohos-pc → ohos-skeleton}/AppScope/resources/base/media/layered_image.json +0 -0
  144. /package/cli/templates/{ohos-pc → ohos-skeleton}/build-profile.json5 +0 -0
  145. /package/cli/templates/{ohos-pc → ohos-skeleton}/entry/build-profile.json5 +0 -0
  146. /package/cli/templates/{ohos-pc → ohos-skeleton}/entry/hvigorfile.ts +0 -0
  147. /package/cli/templates/{ohos-pc → ohos-skeleton}/entry/oh-package.json5 +0 -0
  148. /package/cli/templates/{ohos-pc → ohos-skeleton}/entry/src/main/cangjie/ability_stage.cj +0 -0
  149. /package/cli/templates/{ohos-pc → ohos-skeleton}/entry/src/main/cangjie/app_host.cj +0 -0
  150. /package/cli/templates/{ohos-pc → ohos-skeleton}/entry/src/main/cangjie/main_ability.cj +0 -0
  151. /package/cli/templates/{ohos-pc → ohos-skeleton}/entry/src/main/resources/base/element/color.json +0 -0
  152. /package/cli/templates/{ohos-pc → ohos-skeleton}/entry/src/main/resources/base/element/string.json +0 -0
  153. /package/cli/templates/{ohos-pc → ohos-skeleton}/entry/src/main/resources/base/media/background.png +0 -0
  154. /package/cli/templates/{ohos-pc → ohos-skeleton}/entry/src/main/resources/base/media/foreground.png +0 -0
  155. /package/cli/templates/{ohos-pc → ohos-skeleton}/entry/src/main/resources/base/media/layered_image.json +0 -0
  156. /package/cli/templates/{ohos-pc → ohos-skeleton}/entry/src/main/resources/base/media/startIcon.png +0 -0
  157. /package/cli/templates/{ohos-pc → ohos-skeleton}/entry/src/main/resources/base/profile/main_pages.json +0 -0
  158. /package/cli/templates/{ohos-pc → ohos-skeleton}/entry/src/main/resources/rawfile/capabilities.json +0 -0
  159. /package/cli/templates/{ohos-pc → ohos-skeleton}/entry/src/main/resources/rawfile/index.html +0 -0
  160. /package/cli/templates/{ohos-pc → ohos-skeleton}/hvigor/hvigor-config.json5 +0 -0
  161. /package/cli/templates/{ohos-pc → ohos-skeleton}/hvigorfile.ts +0 -0
  162. /package/cli/templates/{ohos-pc → ohos-skeleton}/oh-package.json5 +0 -0
@@ -43,6 +43,16 @@ import stdx.encoding.json.JsonArray
43
43
  @When[env == "ohos"]
44
44
  import stdx.encoding.json.JsonString
45
45
 
46
+ // 页面来源契约(load(source) 收敛,RFC §4.3):Asset 由胶水注入的 rawfile 解析器材化
47
+ @When[env == "ohos"]
48
+ import cjTauri.platform.*
49
+
50
+ // chdir:musl libc 的 C 函数(与 serial_ohos.cj 的 FFI 同模式;@When 门禁保桌面零影响)。
51
+ // foreign func 是包级符号——必须放文件顶层,嵌进类体会报「member function can not be foreign」。
52
+ @When[env == "ohos"]
53
+ @C
54
+ foreign func chdir(path: CString): Int32
55
+
46
56
  /**
47
57
  * 鸿蒙宿主的进程级运行时:沙箱目录、各窗口宿主、跨线程队列开关
48
58
  *
@@ -71,6 +81,11 @@ public class ArkWebRuntime {
71
81
  *
72
82
  * 用 `context.filesDir`:鸿蒙上它是应用私有可写目录,进程重启后内容仍在,
73
83
  * 于是页面文件可以在**每次启动**重建(内容由应用决定,不做持久化依赖)。
84
+ *
85
+ * **cwd 同时切进沙箱**:鸿蒙上进程 cwd 指向不可写位置(实测 errno 13),而应用唯一
86
+ * 免权限可写的就是沙箱目录。相对路径(业务的工作区、store 插件的落盘、scope 的
87
+ * `$CWD` 变量)全部以 cwd 为基准——不切的话,所有按相对路径落盘的插件在设备上
88
+ * 全部不可用。这等价于 Tauri 移动端的做法:路径基准必须是 appDataDir,不是进程 cwd。
74
89
  */
75
90
  public static func configure(filesDir: String): Unit {
76
91
  if (filesDir.size == 0) {
@@ -78,6 +93,14 @@ public class ArkWebRuntime {
78
93
  return
79
94
  }
80
95
  ArkWebRuntime.sandboxDir = filesDir
96
+ unsafe {
97
+ let cpath = LibC.mallocCString(filesDir)
98
+ let r = chdir(cpath)
99
+ LibC.free(cpath)
100
+ if (r != 0) {
101
+ eprintln("[cj-tauri] chdir(${filesDir}) failed: errno=${r}(相对路径落盘仍以原 cwd 为基准)")
102
+ }
103
+ }
81
104
  eprintln("[cj-tauri] ArkWeb sandbox: ${filesDir}")
82
105
  }
83
106
 
@@ -145,9 +168,14 @@ public class ArkWebDialogRequest {
145
168
  this.message = message
146
169
  }
147
170
 
148
- /** 是否「确定 / 取消」两键(其余 kind 只有一个确定键,弹出即成立) */
171
+ /**
172
+ * 是否「确定 / 取消」两键(`info` / `warning` / `error` 只有一个确定键,弹出即成立)
173
+ *
174
+ * 判定本身在 `src/dialog_spec.cj`(三平台共用一份):这里原先写死 `kind == "confirm"`,
175
+ * 而这个文件带平台门禁、桌面编不到,同一个 kind 在两个平台各判一次就迟早判岔。
176
+ */
149
177
  public func isConfirm(): Bool {
150
- return this.kind == "confirm"
178
+ return dialogShapeIsTwoKey(dialogShapeOf(this.kind))
151
179
  }
152
180
  }
153
181
 
@@ -175,6 +203,8 @@ let CJ_ARKWEB_DIALOG_POLL_MS: Int64 = 20
175
203
  @When[env == "ohos"]
176
204
  public class ArkWebHost <: WebViewHost {
177
205
  private let windowLabel: String
206
+ // 资产解析器:胶水在 load 前注入(rawfile 材化);鸿蒙没有 cwd 读盘兜底
207
+ private var assetResolver: Option<AssetResolver> = None
178
208
 
179
209
  /* 页面来源:start(html) 先存 html、落盘后 pageUrl 才成立;startUrl 直接定 pageUrl。 */
180
210
  private var pageHtml: String = ""
@@ -208,6 +238,15 @@ public class ArkWebHost <: WebViewHost {
208
238
  private var dialogRequestCount: Int64 = 0
209
239
  private var dialogAnswerCount: Int64 = 0
210
240
 
241
+ /*
242
+ * 菜单模型快照:worker 线程写(setMenu / setMenuItemState)、UI 线程读(menuModel),
243
+ * 与上面三个对话框槽同受 queueLock 保护。ArkUI 那半边(bindMenu)在应用侧,
244
+ * 框架这边只存模型——理由见 arkweb_menu.cj 的类注释。
245
+ */
246
+ private var menuRoot: ArrayList<ArkWebMenuItem> = ArrayList<ArkWebMenuItem>()
247
+ private var menuItemCount: Int64 = 0
248
+ private var menuClickCount: Int64 = 0
249
+
211
250
  private var messageHandler: (String) -> Unit = { _ => }
212
251
  private var shellHandler: (String) -> Unit = { _ => }
213
252
  private var destroyHandler: () -> Unit = { => }
@@ -249,7 +288,37 @@ public class ArkWebHost <: WebViewHost {
249
288
  }
250
289
 
251
290
  /**
252
- * 注册预执行脚本(必须早于 start / startUrl,与桌面同一契约)
291
+ * 加载页面(唯一入口,RFC §4.3)
292
+ *
293
+ * 与桌面的差异:Asset **没有 cwd 读盘兜底**——hap 里没有工程目录,资产只能来自
294
+ * 胶水注入的 resolver(rawfile 材化)。缺注入或资产缺失时如实报错,不静默白屏。
295
+ */
296
+ public func load(source: PageSource): Unit {
297
+ match (source) {
298
+ case Inline(html) => this.start(html)
299
+ case Url(url) => this.startUrl(url)
300
+ case DevServer(url) => this.startUrl(url)
301
+ case Asset(name) =>
302
+ match (this.assetResolver) {
303
+ case Some(r) =>
304
+ match (r.readText(name)) {
305
+ case Some(html) => this.start(html)
306
+ case None =>
307
+ throw AssetResolutionException("资产未找到:${name}(胶水注入的 rawfile 里没有这个资产,检查清单 [assets] 与骨架 rawfile 拷贝)")
308
+ }
309
+ case None =>
310
+ throw AssetResolutionException("资产 ${name} 未注入 AssetResolver(鸿蒙侧胶水须先把 rawfile 材化并注入解析器,再 load)")
311
+ }
312
+ }
313
+ }
314
+
315
+ /** 注入资产解析器(胶水在 load 前调用,rawfile 材化器) */
316
+ public func setAssetResolver(resolver: Option<AssetResolver>): Unit {
317
+ this.assetResolver = resolver
318
+ }
319
+
320
+ /**
321
+ * 注册预执行脚本(必须早于 load,与桌面同一契约)
253
322
  *
254
323
  * 内联 HTML 走「拼进 head 最前」这条通道(真正早于页面脚本);URL 页面只能等
255
324
  * `onPageBegin` 再 runJavaScript——那时页面顶层脚本可能已经跑过,**这是本平台的已知限制**,
@@ -284,17 +353,99 @@ public class ArkWebHost <: WebViewHost {
284
353
  eprintln("[cj-tauri] ArkWeb: openDevTools is not available on this platform (use web debugging)")
285
354
  }
286
355
 
287
- /* ---- 以下宿主能力位为 0:一律如实拒绝,不假装成功(AGENTS §2 的 scaffold 纪律) ---- */
356
+ /* ---- 菜单:框架存模型,应用侧用 bindMenu 画(见 arkweb_menu.cj 的类注释) ---- */
288
357
 
358
+ /**
359
+ * 收下应用给的菜单线路格式(`MenuModel.toWire()`),解析成快照存起来。
360
+ *
361
+ * **不阻塞、也不画界面**:真正的菜单由应用侧的 `ArkWebView` 在 `bindMenu` 里画,
362
+ * UI 线程经 [menuModel] 取快照。这样框架包不引用 ArkUI(AGENTS §4 的 SIGSEGV 坑)。
363
+ */
289
364
  public func setMenu(wire: String): Unit {
290
- let _ = wire
291
- eprintln("[cj-tauri] ArkWeb: menu unsupported (host capability bit is 0)")
365
+ let parsed = parseArkWebMenuWire(wire)
366
+ var rows: Int64 = 0
367
+ synchronized(ArkWebRuntime.queueLock) {
368
+ this.menuRoot = parsed
369
+ rows = countMenuItems(parsed)
370
+ this.menuItemCount = rows
371
+ }
372
+ eprintln("[cj-tauri] ArkWeb: menu applied (${wire.size} bytes, ${rows} item(s), app-side bindMenu)")
292
373
  }
293
374
 
375
+ /**
376
+ * 运行期改单项状态(按 id 找,找不到静默忽略——与桌面两平台同口径)。
377
+ *
378
+ * 这里**不再抛异常**:菜单模型住在本进程,改动是纯内存动作;UI 下一轮 [menuModel]
379
+ * 拿到的就是新状态(`bindMenu` 每次 build 重新生成,不需要额外通知)。
380
+ */
294
381
  public func setMenuItemState(id: String, enabled: Bool, checked: Bool): Unit {
295
- let _ = id
296
- let _ = enabled
297
- let _ = checked
382
+ var hit = false
383
+ synchronized(ArkWebRuntime.queueLock) {
384
+ for (item in this.menuRoot) {
385
+ match (item.find(id)) {
386
+ case Some(node) =>
387
+ node.enabled = enabled
388
+ node.checked = checked
389
+ hit = true
390
+ case None => ()
391
+ }
392
+ }
393
+ }
394
+ eprintln("[cj-tauri] ArkWeb: menu item state id=\"${id}\" enabled=${enabled} checked=${checked} hit=${hit}")
395
+ }
396
+
397
+ /**
398
+ * UI 线程取菜单快照(每次一份深拷贝,UI 改它不会动到宿主里的模型)。
399
+ *
400
+ * 应用侧的 `ArkWebView` 在 `build()` 里按它生成 `bindMenu` 的 `MenuElement` 数组;
401
+ * 没有菜单时回空表(应用照常建树,别等菜单才画 Web)。
402
+ */
403
+ public func menuModel(): ArrayList<ArkWebMenuItem> {
404
+ let out = ArrayList<ArkWebMenuItem>()
405
+ synchronized(ArkWebRuntime.queueLock) {
406
+ for (item in this.menuRoot) {
407
+ out.add(item.snapshot())
408
+ }
409
+ }
410
+ return out
411
+ }
412
+
413
+ /**
414
+ * 应用侧 UI 里「这一项被点了」→ 回投 shell 事件。
415
+ *
416
+ * 报文与桌面 C 桥的 `cj_core_menu_clicked` **逐字同形**
417
+ * (`{"kind":"menu","id":…,"enabled":…,"checked":…}`),于是应用层的 `onShellEvent`、
418
+ * 前端 `menu:click`、以及应用按事件同步自己模型的代码都不用分平台。
419
+ *
420
+ * 勾选项的翻转由本函数做(与 Windows 侧「Win32 不替我们翻,自己翻」同口径):
421
+ * 报出去的是**翻转后**的状态,页面照它更新即可。
422
+ */
423
+ public func noteMenuClick(id: String): Unit {
424
+ var enabled = true
425
+ var checked = false
426
+ var found = false
427
+ synchronized(ArkWebRuntime.queueLock) {
428
+ for (item in this.menuRoot) {
429
+ match (item.find(id)) {
430
+ case Some(node) =>
431
+ if (node.isCheckbox()) {
432
+ node.checked = !node.checked
433
+ }
434
+ enabled = node.enabled
435
+ checked = node.checked
436
+ found = true
437
+ case None => ()
438
+ }
439
+ }
440
+ }
441
+ if (!found) {
442
+ eprintln("[cj-tauri] ArkWeb: menu click ignored (id=\"${id}\" is not in the current menu)")
443
+ return
444
+ }
445
+ this.menuClickCount++
446
+ eprintln("[cj-tauri] ArkWeb: menu clicked id=\"${id}\" enabled=${enabled} checked=${checked}")
447
+ this.shellHandler("{\"kind\":\"menu\",\"id\":\"${arkWebJsonEscape(id)}\",\"enabled\":${enabled}," +
448
+ "\"checked\":${checked}}")
298
449
  }
299
450
 
300
451
  public func setTitlebarSubtitle(text: String): Unit {
@@ -325,9 +476,16 @@ public class ArkWebHost <: WebViewHost {
325
476
  let _ = enabled
326
477
  }
327
478
 
328
- /** 能力位全 0:鸿蒙侧还没有任何一项宿主级能力通过实机取证(AGENTS §1 门禁 4) */
479
+ /** 能力位:菜单已置 1(2026-10-24 真机取证),其余仍为 0(AGENTS §1 门禁 4) */
329
480
  public func hostCapabilities(): Int32 {
330
- return 0
481
+ /*
482
+ * 菜单是鸿蒙侧第一项真落地的宿主能力:框架存模型(arkweb_menu.cj)、应用侧 bindMenu 画、
483
+ * 点击经 noteMenuClick 回投(报文与桌面同形)。其余位仍为 0——判据是「仓颉侧有没有 API」:
484
+ * 托盘(鸿蒙无系统托盘)、全局快捷键(只有组件级 keyboardshortcut)、拖入文件(SDK 只有
485
+ * 拖出侧的 drag / dragPreview,没有 drop 回调)、通知(SDK 无 notification_manager 模块)、
486
+ * 标题栏副标题(无 setWindowTitle)。AGENTS §1 门禁 4:没有实机证据的位置 1。
487
+ */
488
+ return CJ_CAP_MENU
331
489
  }
332
490
 
333
491
  public func setShellHandler(handler: (String) -> Unit): Unit {
@@ -469,16 +627,23 @@ public class ArkWebHost <: WebViewHost {
469
627
  }
470
628
  }
471
629
 
630
+ /**
631
+ * 写文本进系统剪贴板(鸿蒙 = 原生 pasteboard,见 `clipboard_bridge.cj` 的鸿蒙段)
632
+ *
633
+ * 返回 false 只有三种可能:句柄拿不到、UDMF 对象建不出来、平台拒绝这次写入。
634
+ * **不要把 false 读成「暂时没接」**——这条能力在鸿蒙上已经接了,false 就是真失败。
635
+ */
472
636
  public func clipboardWriteText(text: String): Bool {
473
- let _ = text
474
- // 剪贴板在鸿蒙走 pasteboard 模块(`ohos.pasteboard`),尚未接入:如实 false,
475
- // 不返回 true 骗调用方(这正是「发后即忘返回 0 ≠ 发送成功」那条坑的同族)。
476
- eprintln("[cj-tauri] ArkWeb: clipboard write unsupported (pasteboard not wired yet)")
477
- return false
637
+ return arkwebClipboardWriteText(text)
478
638
  }
479
639
 
640
+ /**
641
+ * 读系统剪贴板文本:None = 空 / 非文本 / 平台拒绝(含缺 `READ_PASTEBOARD` 权限)
642
+ *
643
+ * 三种 None 对调用方是同一个「没拿到」——接口契约就是 Option,不在这儿编造区分。
644
+ */
480
645
  public func clipboardReadText(): Option<String> {
481
- return None
646
+ return arkwebClipboardReadText()
482
647
  }
483
648
 
484
649
  public func notify(title: String, body: String): Bool {
@@ -738,3 +903,22 @@ public class ArkWebHost <: WebViewHost {
738
903
  return "${block}${html}"
739
904
  }
740
905
  }
906
+
907
+ /* 菜单项总数(含各级子孙):只用于日志对账,不参与渲染 */
908
+ @When[env == "ohos"]
909
+ private func countMenuItems(items: ArrayList<ArkWebMenuItem>): Int64 {
910
+ var total: Int64 = 0
911
+ for (item in items) {
912
+ total++
913
+ total += countMenuItems(item.children)
914
+ }
915
+ return total
916
+ }
917
+
918
+ /* shell 报文里 id 的最小转义:只处理 `\` 与 `"`(与 C 侧 cj_json_escape_into 同口径) */
919
+ @When[env == "ohos"]
920
+ private func arkWebJsonEscape(text: String): String {
921
+ // 迭代 String 得到的是 UInt8 字节(AGENTS §4),`String(byte)` 并不接受 UInt8——
922
+ // 这里只需要处理两个 ASCII 元字符,直接走 replace 更稳,也不碰字节/码点的口径。
923
+ return text.replace("\\", "\\\\").replace("\"", "\\\"")
924
+ }