cj-tauri 2.0.0 → 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 (158) hide show
  1. package/README.md +113 -99
  2. package/bin/cj-tauri.js +287 -287
  3. package/cjpm.toml +63 -52
  4. package/cli/cjpm.toml +22 -22
  5. package/cli/src/doctor.cj +308 -306
  6. package/cli/src/main.cj +129 -129
  7. package/cli/src/manifest.cj +632 -558
  8. package/cli/src/manifest_test.cj +87 -0
  9. package/cli/src/project.cj +770 -770
  10. package/cli/src/resolve.cj +348 -348
  11. package/cli/src/scaffold.cj +369 -369
  12. package/cli/src/sync.cj +526 -526
  13. package/cli/src/util.cj +168 -168
  14. package/cli/templates/app/README.md +51 -48
  15. package/cli/templates/app/assets/capabilities/default.json +7 -7
  16. package/cli/templates/app/assets/ui/index.html +120 -118
  17. package/cli/templates/app/cj-tauri.toml +32 -22
  18. package/cli/templates/app/cjpm.toml +34 -34
  19. package/cli/templates/app/src/main.cj +101 -90
  20. package/cli/templates/app-react/README.md +56 -56
  21. package/cli/templates/app-react/assets/capabilities/default.json +7 -7
  22. package/cli/templates/app-react/assets/ui/index.html +25 -25
  23. package/cli/templates/app-react/assets/ui/package.json +19 -19
  24. package/cli/templates/app-react/assets/ui/src/App.css +67 -67
  25. package/cli/templates/app-react/assets/ui/src/App.jsx +124 -124
  26. package/cli/templates/app-react/assets/ui/src/main.jsx +6 -6
  27. package/cli/templates/app-react/assets/ui/vite.config.js +24 -24
  28. package/cli/templates/app-react/cj-tauri.toml +33 -23
  29. package/cli/templates/app-react/cjpm.toml +34 -34
  30. package/cli/templates/app-react/src/main.cj +91 -91
  31. package/cli/templates/app-vue/README.md +56 -56
  32. package/cli/templates/app-vue/assets/capabilities/default.json +7 -7
  33. package/cli/templates/app-vue/assets/ui/index.html +25 -25
  34. package/cli/templates/app-vue/assets/ui/package.json +18 -18
  35. package/cli/templates/app-vue/assets/ui/src/App.vue +176 -176
  36. package/cli/templates/app-vue/assets/ui/src/main.js +4 -4
  37. package/cli/templates/app-vue/assets/ui/vite.config.js +24 -24
  38. package/cli/templates/app-vue/cj-tauri.toml +33 -23
  39. package/cli/templates/app-vue/cjpm.toml +34 -34
  40. package/cli/templates/app-vue/src/main.cj +91 -91
  41. package/cli/templates/ohos-skeleton/AppScope/app.json5 +13 -13
  42. package/cli/templates/ohos-skeleton/AppScope/resources/base/element/string.json +8 -8
  43. package/cli/templates/ohos-skeleton/AppScope/resources/base/media/layered_image.json +6 -6
  44. package/cli/templates/ohos-skeleton/README.md +84 -79
  45. package/cli/templates/ohos-skeleton/build-hap.sh +15 -0
  46. package/cli/templates/ohos-skeleton/build-profile.json5 +46 -46
  47. package/cli/templates/ohos-skeleton/entry/build-profile.json5 +26 -26
  48. package/cli/templates/ohos-skeleton/entry/cjpm.toml +51 -43
  49. package/cli/templates/ohos-skeleton/entry/hvigorfile.ts +6 -6
  50. package/cli/templates/ohos-skeleton/entry/oh-package.json5 +9 -9
  51. package/cli/templates/ohos-skeleton/entry/src/main/cangjie/ability_stage.cj +17 -17
  52. package/cli/templates/ohos-skeleton/entry/src/main/cangjie/app_host.cj +94 -94
  53. package/cli/templates/ohos-skeleton/entry/src/main/cangjie/dialog_host.cj +217 -0
  54. package/cli/templates/ohos-skeleton/entry/src/main/cangjie/index.cj +527 -167
  55. package/cli/templates/ohos-skeleton/entry/src/main/cangjie/main_ability.cj +139 -139
  56. package/cli/templates/ohos-skeleton/entry/src/main/module.json5 +57 -49
  57. package/cli/templates/ohos-skeleton/entry/src/main/resources/base/element/color.json +8 -8
  58. package/cli/templates/ohos-skeleton/entry/src/main/resources/base/element/string.json +16 -16
  59. package/cli/templates/ohos-skeleton/entry/src/main/resources/base/media/layered_image.json +6 -6
  60. package/cli/templates/ohos-skeleton/entry/src/main/resources/base/profile/main_pages.json +2 -2
  61. package/cli/templates/ohos-skeleton/entry/src/main/resources/rawfile/capabilities.json +6 -6
  62. package/cli/templates/ohos-skeleton/entry/src/main/resources/rawfile/index.html +92 -92
  63. package/cli/templates/ohos-skeleton/hvigor/hvigor-config.json5 +5 -5
  64. package/cli/templates/ohos-skeleton/hvigorfile.ts +6 -6
  65. package/cli/templates/ohos-skeleton/oh-package.json5 +10 -10
  66. package/native/build_win.bat +62 -62
  67. package/native/core/bridge_core.c +1681 -1626
  68. package/native/core/bridge_core.h +675 -618
  69. package/native/core/bridge_js.h +108 -108
  70. package/native/platforms/harmonyos/README.md +115 -6
  71. package/native/platforms/linux/bridge_linux.c +2787 -2485
  72. package/native/platforms/windows/bridge_win.c +3740 -3248
  73. package/native/tests/gtk_threading_probe.c +143 -143
  74. package/native/tests/test_bridge_core.c +1339 -1228
  75. package/package.json +53 -53
  76. package/prebuilt/linux-x64/cj-tauri +0 -0
  77. package/src/api_system.cj +67 -67
  78. package/src/app.cj +325 -969
  79. package/src/app_events.cj +166 -0
  80. package/src/app_host_ops.cj +287 -0
  81. package/src/app_run.cj +283 -0
  82. package/src/app_spec.cj +76 -76
  83. package/src/arkweb_menu.cj +216 -0
  84. package/src/arkweb_script.cj +142 -142
  85. package/src/capability.cj +409 -409
  86. package/src/clipboard_bridge.cj +178 -0
  87. package/src/dialog_spec.cj +79 -0
  88. package/src/dragdrop.cj +26 -26
  89. package/src/embedded_assets.cj +98 -98
  90. package/src/host.cj +752 -607
  91. package/src/host_arkweb.cj +924 -778
  92. package/src/host_webkit.cj +663 -570
  93. package/src/host_webview2.cj +663 -570
  94. package/src/hotkey.cj +66 -66
  95. package/src/ipc_hub.cj +337 -337
  96. package/src/ipc_message.cj +286 -286
  97. package/src/launch.cj +62 -62
  98. package/src/logging.cj +127 -127
  99. package/src/menu.cj +197 -197
  100. package/src/platform/platform_app_context.cj +77 -77
  101. package/src/platform/platform_assets.cj +33 -33
  102. package/src/platform/platform_discover.cj +176 -176
  103. package/src/platform/platform_env.cj +29 -29
  104. package/src/platform/platform_feature.cj +31 -31
  105. package/src/platform/platform_page_source.cj +87 -87
  106. package/src/platform/platform_target_info.cj +32 -32
  107. package/src/platform_host_provider.cj +87 -87
  108. package/src/plugin.cj +333 -333
  109. package/src/plugins/clipboard/plugin_clipboard.cj +152 -152
  110. package/src/plugins/dialog/plugin_dialog.cj +209 -209
  111. package/src/plugins/fs/plugin_fs.cj +105 -1
  112. package/src/plugins/http/http_backend.cj +131 -0
  113. package/src/plugins/http/http_desktop.cj +195 -0
  114. package/src/plugins/http/http_ohos.cj +204 -0
  115. package/src/plugins/http/plugin_http.cj +310 -459
  116. package/src/plugins/log/plugin_log.cj +105 -105
  117. package/src/plugins/menu/plugin_menu.cj +158 -158
  118. package/src/plugins/notification/plugin_notification.cj +154 -154
  119. package/src/plugins/serial/plugin_serial.cj +458 -454
  120. package/src/plugins/serial/serial_bridge.cj +194 -194
  121. package/src/plugins/serial/serial_manager_ohos.cj +92 -0
  122. package/src/plugins/serial/serial_ohos.cj +529 -0
  123. package/src/plugins/shell/plugin_shell.cj +258 -258
  124. package/src/plugins/sqlite/plugin_sqlite.cj +705 -710
  125. package/src/plugins/sqlite/sqlite_ffi.cj +206 -211
  126. package/src/plugins/ws/plugin_ws.cj +488 -470
  127. package/src/process_hidden.cj +174 -174
  128. package/src/scope.cj +391 -391
  129. package/src/shell_event.cj +69 -69
  130. package/src/tests/app_spec_test.cj +126 -127
  131. package/src/tests/capability_test.cj +243 -243
  132. package/src/tests/dialog_spec_test.cj +73 -0
  133. package/src/tests/fake_host.cj +201 -201
  134. package/src/tests/ipc_hub_test.cj +487 -487
  135. package/src/tests/logging_test.cj +62 -62
  136. package/src/tests/platform_contract_test.cj +251 -251
  137. package/src/tests/plugin_clipboard_test.cj +222 -222
  138. package/src/tests/plugin_dialog_test.cj +237 -237
  139. package/src/tests/plugin_fs_test.cj +207 -0
  140. package/src/tests/plugin_http_test.cj +124 -124
  141. package/src/tests/plugin_notification_test.cj +205 -205
  142. package/src/tests/plugin_serial_test.cj +48 -0
  143. package/src/tests/plugin_test.cj +605 -585
  144. package/src/tests/plugin_ws_test.cj +40 -40
  145. package/src/tests/process_hidden_test.cj +129 -129
  146. package/src/tests/scope_test.cj +350 -350
  147. package/src/tests/system_host_test.cj +99 -81
  148. package/src/tests/urlutil_test.cj +53 -53
  149. package/src/tests/version_test.cj +75 -75
  150. package/src/tests/window_registry_test.cj +123 -123
  151. package/src/tray.cj +90 -90
  152. package/src/urlutil.cj +159 -159
  153. package/src/version.cj +44 -44
  154. package/src/window.cj +51 -51
  155. package/src/window_registry.cj +116 -116
  156. package/prebuilt/win32-x64/cj-tauri.exe +0 -0
  157. package/src/plugins/serial/serial_ohos_stub.cj +0 -61
  158. package/src/plugins/sqlite/sqlite_ohos_stub.cj +0 -91
@@ -1,108 +1,108 @@
1
- /*
2
- * 前端桥接脚本(单份):暴露 window.__CJ_TAURI__,对标 @tauri-apps/api。
3
- *
4
- * 为什么单份:Linux 与 Windows 两座桥原本各存一份同内容的 JS,任何修补都要改两处,
5
- * 极易漂移("与 Windows 对齐"曾是反复出现的叮嘱)。这里抽成一份宏,纳入本文件即可。
6
- *
7
- * 用法(两平台唯一差异只有两条语句:post 通道 + reload 控制消息):
8
- * 1) 先定义 CJ_LIT(x) —— 窄/宽字符串前缀:
9
- * Linux (bridge_linux.c):#define CJ_LIT(s) s
10
- * Windows (bridge_win.c) :#define CJ_LIT(s) L##s (AddScriptToExecuteOnDocumentCreated 要 wchar_t)
11
- * 2) 提供两条自带缩进与分号的平台语句字面量;
12
- * 3) 展开 CJ_BRIDGE_JS(POST_STMT, RELOAD_STMT)。
13
- *
14
- * 注意:JS 源码里的块注释(斜杠星号)与单引号字符串都是**字符串内容**,不是 C 注释/字符——
15
- * 改本文件时别把它们挪出引号(仓颉侧内联 HTML 已踩过同类坑,见 AGENTS.md §4)。
16
- */
17
- #ifndef CJ_LIT
18
- #error "bridge_js.h: 请先定义 CJ_LIT(x)(Linux 原样 / Windows L##x)再 include"
19
- #endif
20
-
21
- #define CJ_BRIDGE_JS(POST_STMT, RELOAD_STMT) \
22
- CJ_LIT("window.__CJ_TAURI__ = (function () {") \
23
- CJ_LIT(" var seq = 0;") \
24
- CJ_LIT(" var pending = {};") \
25
- CJ_LIT(" var listeners = {};") \
26
- CJ_LIT(" var coalesceSlots = Object.create(null); /* 本帧待投的事件:event -> 最新 payload */") \
27
- CJ_LIT(" var coalesceTimer = null;") \
28
- CJ_LIT(" var coalesceRaf = 0;") \
29
- CJ_LIT(" /* tick = 一帧:同一帧内同名事件只投最后一次 payload(中间值丢弃) */") \
30
- CJ_LIT(" function flushCoalesced() {") \
31
- CJ_LIT(" if (coalesceTimer !== null) { clearTimeout(coalesceTimer); coalesceTimer = null; }") \
32
- CJ_LIT(" if (coalesceRaf && typeof cancelAnimationFrame === 'function') cancelAnimationFrame(coalesceRaf);") \
33
- CJ_LIT(" coalesceRaf = 0;") \
34
- CJ_LIT(" var due = coalesceSlots;") \
35
- CJ_LIT(" coalesceSlots = Object.create(null);") \
36
- CJ_LIT(" for (var name in due) {") \
37
- CJ_LIT(" var arr = listeners[name] || [];") \
38
- CJ_LIT(" for (var i = 0; i < arr.length; i++) arr[i](due[name]);") \
39
- CJ_LIT(" }") \
40
- CJ_LIT(" }") \
41
- CJ_LIT(" function scheduleCoalesced() {") \
42
- CJ_LIT(" if (coalesceTimer !== null || coalesceRaf) return;") \
43
- CJ_LIT(" /* rAF 是帧边界;窗口不可见时它不来,用定时器兜底,保证尾值一定送达 */") \
44
- CJ_LIT(" if (typeof requestAnimationFrame === 'function') coalesceRaf = requestAnimationFrame(flushCoalesced);") \
45
- CJ_LIT(" coalesceTimer = setTimeout(flushCoalesced, 50);") \
46
- CJ_LIT(" }") \
47
- CJ_LIT(" /* 本页面所属窗口的 label:宿主在 document-start 注入 window.__CJ_TAURI_LABEL__ */") \
48
- CJ_LIT(" /* 懒读 + 缓存(首次用到才取值):注入脚本与桥 JS 谁先执行都可能,默认 'main' 与老前端对齐 */") \
49
- CJ_LIT(" var cachedLabel = null;") \
50
- CJ_LIT(" function labelOf() {") \
51
- CJ_LIT(" if (cachedLabel === null) {") \
52
- CJ_LIT(" var v = (typeof window !== 'undefined') ? window.__CJ_TAURI_LABEL__ : null;") \
53
- CJ_LIT(" cachedLabel = (typeof v === 'string' && v.length > 0) ? v : 'main';") \
54
- CJ_LIT(" }") \
55
- CJ_LIT(" return cachedLabel;") \
56
- CJ_LIT(" }") \
57
- CJ_LIT(" function post(obj) {") \
58
- POST_STMT \
59
- CJ_LIT(" }") \
60
- CJ_LIT(" return {") \
61
- CJ_LIT(" invoke: function (cmd, args) {") \
62
- CJ_LIT(" var id = ++seq;") \
63
- CJ_LIT(" var p = new Promise(function (resolve, reject) { pending[id] = { resolve: resolve, reject: reject }; });") \
64
- CJ_LIT(" post({ type: 'invoke', id: id, cmd: cmd, args: args || {}, window: labelOf() });") \
65
- CJ_LIT(" return p;") \
66
- CJ_LIT(" },") \
67
- CJ_LIT(" listen: function (event, cb) {") \
68
- CJ_LIT(" if (!listeners[event]) listeners[event] = [];") \
69
- CJ_LIT(" listeners[event].push(cb);") \
70
- CJ_LIT(" return function () {") \
71
- CJ_LIT(" var arr = listeners[event] || [];") \
72
- CJ_LIT(" var i = arr.indexOf(cb);") \
73
- CJ_LIT(" if (i >= 0) arr.splice(i, 1);") \
74
- CJ_LIT(" };") \
75
- CJ_LIT(" },") \
76
- CJ_LIT(" emit: function (event, payload) {") \
77
- CJ_LIT(" /* 带 id 才有回执:后端能把「事件未授权 / 报文非法」reject 回来(对齐 Tauri 的 emit 返回 Promise) */") \
78
- CJ_LIT(" var id = ++seq;") \
79
- CJ_LIT(" var p = new Promise(function (resolve, reject) { pending[id] = { resolve: resolve, reject: reject }; });") \
80
- CJ_LIT(" post({ type: 'emit', id: id, event: event, payload: payload === undefined ? {} : payload, window: labelOf() });") \
81
- CJ_LIT(" return p;") \
82
- CJ_LIT(" },") \
83
- CJ_LIT(" reload: function () {") \
84
- CJ_LIT(" /* 与 CJT_RELOAD_MSG 保持一致:宿主拦截后不会进 IPC hub */") \
85
- RELOAD_STMT \
86
- CJ_LIT(" },") \
87
- CJ_LIT(" _dispatch: function (msg) {") \
88
- CJ_LIT(" if (!msg) return;") \
89
- CJ_LIT(" if (msg.type === 'resolve') {") \
90
- CJ_LIT(" var p = pending[msg.id];") \
91
- CJ_LIT(" if (!p) return;") \
92
- CJ_LIT(" delete pending[msg.id];") \
93
- CJ_LIT(" if (msg.ok) p.resolve(msg.data); else p.reject(new Error(msg.error || 'invoke failed'));") \
94
- CJ_LIT(" } else if (msg.type === 'event') {") \
95
- CJ_LIT(" /* window 缺省 = 广播,放行;定向推送只投给 label 匹配的页面 */") \
96
- CJ_LIT(" if (msg.window && msg.window !== labelOf()) return;") \
97
- CJ_LIT(" if (msg.coalesce) {") \
98
- CJ_LIT(" /* emitLatest:只记最新值,一帧后统一投(同一帧内同名事件合成一条) */") \
99
- CJ_LIT(" coalesceSlots[msg.event] = msg.payload;") \
100
- CJ_LIT(" scheduleCoalesced();") \
101
- CJ_LIT(" return;") \
102
- CJ_LIT(" }") \
103
- CJ_LIT(" var arr = listeners[msg.event] || [];") \
104
- CJ_LIT(" for (var i = 0; i < arr.length; i++) arr[i](msg.payload);") \
105
- CJ_LIT(" }") \
106
- CJ_LIT(" }") \
107
- CJ_LIT(" };") \
108
- CJ_LIT("})();")
1
+ /*
2
+ * 前端桥接脚本(单份):暴露 window.__CJ_TAURI__,对标 @tauri-apps/api。
3
+ *
4
+ * 为什么单份:Linux 与 Windows 两座桥原本各存一份同内容的 JS,任何修补都要改两处,
5
+ * 极易漂移("与 Windows 对齐"曾是反复出现的叮嘱)。这里抽成一份宏,纳入本文件即可。
6
+ *
7
+ * 用法(两平台唯一差异只有两条语句:post 通道 + reload 控制消息):
8
+ * 1) 先定义 CJ_LIT(x) —— 窄/宽字符串前缀:
9
+ * Linux (bridge_linux.c):#define CJ_LIT(s) s
10
+ * Windows (bridge_win.c) :#define CJ_LIT(s) L##s (AddScriptToExecuteOnDocumentCreated 要 wchar_t)
11
+ * 2) 提供两条自带缩进与分号的平台语句字面量;
12
+ * 3) 展开 CJ_BRIDGE_JS(POST_STMT, RELOAD_STMT)。
13
+ *
14
+ * 注意:JS 源码里的块注释(斜杠星号)与单引号字符串都是**字符串内容**,不是 C 注释/字符——
15
+ * 改本文件时别把它们挪出引号(仓颉侧内联 HTML 已踩过同类坑,见 AGENTS.md §4)。
16
+ */
17
+ #ifndef CJ_LIT
18
+ #error "bridge_js.h: 请先定义 CJ_LIT(x)(Linux 原样 / Windows L##x)再 include"
19
+ #endif
20
+
21
+ #define CJ_BRIDGE_JS(POST_STMT, RELOAD_STMT) \
22
+ CJ_LIT("window.__CJ_TAURI__ = (function () {") \
23
+ CJ_LIT(" var seq = 0;") \
24
+ CJ_LIT(" var pending = {};") \
25
+ CJ_LIT(" var listeners = {};") \
26
+ CJ_LIT(" var coalesceSlots = Object.create(null); /* 本帧待投的事件:event -> 最新 payload */") \
27
+ CJ_LIT(" var coalesceTimer = null;") \
28
+ CJ_LIT(" var coalesceRaf = 0;") \
29
+ CJ_LIT(" /* tick = 一帧:同一帧内同名事件只投最后一次 payload(中间值丢弃) */") \
30
+ CJ_LIT(" function flushCoalesced() {") \
31
+ CJ_LIT(" if (coalesceTimer !== null) { clearTimeout(coalesceTimer); coalesceTimer = null; }") \
32
+ CJ_LIT(" if (coalesceRaf && typeof cancelAnimationFrame === 'function') cancelAnimationFrame(coalesceRaf);") \
33
+ CJ_LIT(" coalesceRaf = 0;") \
34
+ CJ_LIT(" var due = coalesceSlots;") \
35
+ CJ_LIT(" coalesceSlots = Object.create(null);") \
36
+ CJ_LIT(" for (var name in due) {") \
37
+ CJ_LIT(" var arr = listeners[name] || [];") \
38
+ CJ_LIT(" for (var i = 0; i < arr.length; i++) arr[i](due[name]);") \
39
+ CJ_LIT(" }") \
40
+ CJ_LIT(" }") \
41
+ CJ_LIT(" function scheduleCoalesced() {") \
42
+ CJ_LIT(" if (coalesceTimer !== null || coalesceRaf) return;") \
43
+ CJ_LIT(" /* rAF 是帧边界;窗口不可见时它不来,用定时器兜底,保证尾值一定送达 */") \
44
+ CJ_LIT(" if (typeof requestAnimationFrame === 'function') coalesceRaf = requestAnimationFrame(flushCoalesced);") \
45
+ CJ_LIT(" coalesceTimer = setTimeout(flushCoalesced, 50);") \
46
+ CJ_LIT(" }") \
47
+ CJ_LIT(" /* 本页面所属窗口的 label:宿主在 document-start 注入 window.__CJ_TAURI_LABEL__ */") \
48
+ CJ_LIT(" /* 懒读 + 缓存(首次用到才取值):注入脚本与桥 JS 谁先执行都可能,默认 'main' 与老前端对齐 */") \
49
+ CJ_LIT(" var cachedLabel = null;") \
50
+ CJ_LIT(" function labelOf() {") \
51
+ CJ_LIT(" if (cachedLabel === null) {") \
52
+ CJ_LIT(" var v = (typeof window !== 'undefined') ? window.__CJ_TAURI_LABEL__ : null;") \
53
+ CJ_LIT(" cachedLabel = (typeof v === 'string' && v.length > 0) ? v : 'main';") \
54
+ CJ_LIT(" }") \
55
+ CJ_LIT(" return cachedLabel;") \
56
+ CJ_LIT(" }") \
57
+ CJ_LIT(" function post(obj) {") \
58
+ POST_STMT \
59
+ CJ_LIT(" }") \
60
+ CJ_LIT(" return {") \
61
+ CJ_LIT(" invoke: function (cmd, args) {") \
62
+ CJ_LIT(" var id = ++seq;") \
63
+ CJ_LIT(" var p = new Promise(function (resolve, reject) { pending[id] = { resolve: resolve, reject: reject }; });") \
64
+ CJ_LIT(" post({ type: 'invoke', id: id, cmd: cmd, args: args || {}, window: labelOf() });") \
65
+ CJ_LIT(" return p;") \
66
+ CJ_LIT(" },") \
67
+ CJ_LIT(" listen: function (event, cb) {") \
68
+ CJ_LIT(" if (!listeners[event]) listeners[event] = [];") \
69
+ CJ_LIT(" listeners[event].push(cb);") \
70
+ CJ_LIT(" return function () {") \
71
+ CJ_LIT(" var arr = listeners[event] || [];") \
72
+ CJ_LIT(" var i = arr.indexOf(cb);") \
73
+ CJ_LIT(" if (i >= 0) arr.splice(i, 1);") \
74
+ CJ_LIT(" };") \
75
+ CJ_LIT(" },") \
76
+ CJ_LIT(" emit: function (event, payload) {") \
77
+ CJ_LIT(" /* 带 id 才有回执:后端能把「事件未授权 / 报文非法」reject 回来(对齐 Tauri 的 emit 返回 Promise) */") \
78
+ CJ_LIT(" var id = ++seq;") \
79
+ CJ_LIT(" var p = new Promise(function (resolve, reject) { pending[id] = { resolve: resolve, reject: reject }; });") \
80
+ CJ_LIT(" post({ type: 'emit', id: id, event: event, payload: payload === undefined ? {} : payload, window: labelOf() });") \
81
+ CJ_LIT(" return p;") \
82
+ CJ_LIT(" },") \
83
+ CJ_LIT(" reload: function () {") \
84
+ CJ_LIT(" /* 与 CJT_RELOAD_MSG 保持一致:宿主拦截后不会进 IPC hub */") \
85
+ RELOAD_STMT \
86
+ CJ_LIT(" },") \
87
+ CJ_LIT(" _dispatch: function (msg) {") \
88
+ CJ_LIT(" if (!msg) return;") \
89
+ CJ_LIT(" if (msg.type === 'resolve') {") \
90
+ CJ_LIT(" var p = pending[msg.id];") \
91
+ CJ_LIT(" if (!p) return;") \
92
+ CJ_LIT(" delete pending[msg.id];") \
93
+ CJ_LIT(" if (msg.ok) p.resolve(msg.data); else p.reject(new Error(msg.error || 'invoke failed'));") \
94
+ CJ_LIT(" } else if (msg.type === 'event') {") \
95
+ CJ_LIT(" /* window 缺省 = 广播,放行;定向推送只投给 label 匹配的页面 */") \
96
+ CJ_LIT(" if (msg.window && msg.window !== labelOf()) return;") \
97
+ CJ_LIT(" if (msg.coalesce) {") \
98
+ CJ_LIT(" /* emitLatest:只记最新值,一帧后统一投(同一帧内同名事件合成一条) */") \
99
+ CJ_LIT(" coalesceSlots[msg.event] = msg.payload;") \
100
+ CJ_LIT(" scheduleCoalesced();") \
101
+ CJ_LIT(" return;") \
102
+ CJ_LIT(" }") \
103
+ CJ_LIT(" var arr = listeners[msg.event] || [];") \
104
+ CJ_LIT(" for (var i = 0; i < arr.length; i++) arr[i](msg.payload);") \
105
+ CJ_LIT(" }") \
106
+ CJ_LIT(" }") \
107
+ CJ_LIT(" };") \
108
+ CJ_LIT("})();")
@@ -1,6 +1,7 @@
1
1
  # HarmonyOS ArkWeb 平台适配
2
2
 
3
- 状态(2026-10-19):**宿主已在仓颉侧落地并在真机跑通完整闭环;C 桥未落地,宿主能力位仍全 0**。
3
+ 状态(2026-10-24):**宿主已在仓颉侧落地并在真机跑通完整闭环;C 桥未落地;宿主能力位已有 1 项
4
+ (菜单)取证落地,其余仍全 0**。
4
5
 
5
6
  判据分三层,别混为一谈:
6
7
 
@@ -13,9 +14,19 @@
13
14
  实现,`@When[env == "ohos"]` 一平台一份。按 AGENTS §2,新增宿主能力仍是「先扩 `WebViewHost` 接口、
14
15
  再改各平台实现」,但不要求鸿蒙侧补同名 `cj_bridge_*` 导出(无 C 桥平台:核心 FFI 一律
15
16
  `@When` 门禁 + 如实降级,见 §4)。
16
- - **宿主能力位全 0**——菜单、托盘、全局快捷键、拖放、原生对话框、剪贴板、通知、标题栏副标题
17
- 一律如实拒绝(`hostCapabilities()` 返回 0)。按 AGENTS §1 门禁 4:**没有实机证据的能力位
18
- 不许上报**,所以它们不会因为「代码里写了」就变成 1。
17
+ - **宿主能力位:只有菜单置 1,其余五项为 0**——`CJ_CAP_MENU` 已于 2026-10-24 真机取证
18
+ (点得动菜单项、原生回投 `{"kind":"menu","id":…}`、页面收到 `menu:click`,见文末「菜单能力位」);
19
+ 托盘 / 全局快捷键 / 拖放 / 通知 / 标题栏副标题一律如实拒绝(对应位为 0)。
20
+ 按 AGENTS §1 门禁 4:**没有实机证据的能力位不许上报**,所以它们不会因为「代码里写了」就变成 1。
21
+ **注意「对话框」与「剪贴板」根本不占能力位**——`bridge_core.h` 的全部位就是
22
+ menu / tray / dragdrop / notification / hotkey / subtitle 这六个,没有 dialog、也没有 clipboard。
23
+ 两者的真实形态都是**插件命令**,开关在能力清单的 `commands`(或命名权限集)里:
24
+ - 对话框:`dialog:message` / `dialog:confirm` / `dialog:open` → 宿主 `showMessageDialog()`
25
+ 在鸿蒙侧是「入队 + 有界轮询等 UI 侧回填」(不占 UI 线程),**渲染由应用侧 `CjDialogHost`
26
+ 组件完成**(ArkUI 只能在应用侧),真机四段日志已取证;
27
+ - 剪贴板:`clipboard:*` 直连 NDK pasteboard,**真机写通、读被 `READ_PASTEBOARD` 挡住**
28
+ (系统权限等级 `SYSTEM_BASIC`,普通调试证书声明了也装不上,详见文末「剪贴板」一节)。
29
+ 所以「能力位表里看不到对话框/剪贴板」= 它们不在这张表里,不是「能力不可用」;判据是那三条命令调得通不通。
19
30
 
20
31
  ⚠️ **两条与桌面平台不同的硬约束**(真机踩过,改动前务必看):
21
32
 
@@ -102,5 +113,103 @@
102
113
  每条报文里 `window=`(原生侧归属)与 `me=`(页面自认的 label)**逐条恒等**——这就是「不串台」;
103
114
  - 同轮 `SIGSEGV` / `panic` / `fatal` 计数 0,`command not allowed|registered|rejected` 计数 0。
104
115
 
105
- 宿主能力位(菜单 / 托盘 / 快捷键 / 拖放 / 对话框 / 剪贴板 / 通知)**仍一项都没有证据**,
106
- 所以 `hostCapabilities()` 保持返回 0——别因为「代码里写了降级」就把它读成验收过了。
116
+ ## 菜单能力位(2026-10-24 真机取证)
117
+
118
+ **结论:`CJ_CAP_MENU` 置 1。** 分工与桌面两平台一致——框架只管「模型 → 宿主」的翻译与报文回投,
119
+ **渲染在应用侧**(鸿蒙的 `bindMenu` 只接受应用侧组件上的 `Array<MenuElement>`,框架包不能碰 ArkUI,
120
+ 见上面的硬约束 1):
121
+
122
+ | 环节 | 位置 | 做什么 |
123
+ | --- | --- | --- |
124
+ | 模型 | 框架 `src/arkweb_menu.cj` | `MenuModel` / `MenuItem` → 扁平 `Array<MenuElement>`(标题、勾选标记、分隔线),并解析宿主回投的 `{"kind":"menu","id":…}` |
125
+ | 渲染 | 应用侧 `cli/templates/ohos-skeleton/entry/src/main/cangjie/index.cj` | `Button("菜单").bindMenu(...)` + 点击回调 `noteMenuClick(id)`,顺带一行 hilog `menu click label=… id=…` |
126
+ | 回投 | 框架 `src/host_arkweb.cj` | shell 报文 → 应用 `onShellEvent` → 页面 `menu:click` 事件(需在能力清单 `events` 声明) |
127
+ | 状态 | 应用侧 + `menu` 插件 | `menu:setChecked` / `setEnabled` 命令 → 宿主 `setMenuItemState`,应用侧留存「另一维状态」后重新 `bindMenu` |
128
+
129
+ 真机判据(`examples/ohos/ohos-capabilities`,`hdc shell hilog` 原样摘录):
130
+
131
+ ```
132
+ CjTauri: menu click label=main id=view.sidebar
133
+ CjProbe: PASS menu click id=view.sidebar enabled=true checked=false
134
+ CjProbe: PASS cap menu expect=true actual=true
135
+ CjProbe: PASS menu setChecked view.sidebar=true
136
+ ```
137
+
138
+ - **原生侧真的收到点击**:`menu click` 那行由应用侧组件在 `bindMenu` 回调里打印(设备上框架 stderr
139
+ 不进 hilog,只有应用侧 `Hilog.info` 抓得到);
140
+ - **回投真的到了页面**:`PASS menu click id=…` 是页面在 `menu:click` 回调里发出的断言,
141
+ `enabled` / `checked` 与弹窗里看到的状态一致;
142
+ - **两个方向都通**:页面 `menu:setChecked` → `menu` 插件 → 宿主 `setMenuItemState` → 下一次 `bindMenu`
143
+ 的勾选态,所以页面那行 `PASS menu setChecked` 不是自说自话。
144
+
145
+ **已知限制(`bindMenu` 的形状决定,不是没实现)**:菜单是**一层扁平**结构——`MenuElement` 没有
146
+ 禁用位、也不能嵌套子菜单,所以 `MenuItemKind.Separator` 与 `Submenu` 在鸿蒙侧被**拍平**成普通项
147
+ (分隔线渲染成一条横线项,子菜单的项按顺序平铺);`setMenuItemState` 的 enabled 改动在鸿蒙侧
148
+ **只能改灰(不参与点击高亮)**,不能真正禁用。要完整的层级/禁用语义得等宿主侧自绘菜单,
149
+ 在那之前文档与示例按「扁平菜单」表述,别照桌面示例的嵌套树写。其余四项能力位(托盘 / 快捷键 /
150
+ 拖放 / 对话框)仍为 0,各有各的阻断原因:
151
+
152
+ - **托盘 / 全局快捷键**:鸿蒙没有桌面级托盘与系统级热键 API(`2in1` 也没有),如实拒绝;
153
+ - **原生对话框**:`showAlertDialog` / `getPromptAction().showDialog` 在 `javaScriptProxy` 回调的
154
+ 同步栈上会被**静默丢弃**(真机四轮对照,见 §4 与 `docs/鸿蒙ArkWeb接入踩坑.md` §10.11),
155
+ 应用侧只能用声明式 overlay——那不是宿主能力;
156
+ - **文件拖放**:组件有 `onDrop`,但路径来自系统 drag 事件而非桥,且没有 `DragDropPolicy`
157
+ 的「start 前定稿」语义,未做能力位。
158
+
159
+ ## 剪贴板(2026-10-24 真机取证:写通,读被平台门槛挡住)
160
+
161
+ 鸿蒙的仓颉 SDK 6.1 **没有 `ohos.pasteboard` 绑定**(340 个 ohos 包里一个都没有,只有旧的 5.0 Beta 带),
162
+ 所以走系统原生 C API:`src/clipboard_bridge.cj` 的 `@When[env == "ohos"]` 段用 `foreign func` 直连
163
+ `libpasteboard.so`(`OH_Pasteboard_*`)与 `libudmf.so`(`OH_Udmf*` / `OH_UdsPlainText_*`)。
164
+
165
+ ### 链接:开关必须落在 entry 模块,不是框架
166
+
167
+ `OH_Pasteboard_*` 与 `OH_Udmf*` 分属两个库,**两个都要链**:
168
+
169
+ ```toml
170
+ [target.aarch64-linux-ohos]
171
+ link-option = "-lpasteboard -ludmf"
172
+ ```
173
+
174
+ 落点是**入口模块的 `entry/cjpm.toml`**(`output-type = "dynamic"`,最终链接发生在那儿)。
175
+ 写进框架自己的 `cjpm.toml` 不生效——生成器 `cli/src/manifest.cj` 的 `renderOhosEntryToml` 会重写
176
+ `targets/ohos/entry/cjpm.toml`,漏了这条的表现是:`libohos_app_cangjie_entry.so` 里留一堆
177
+ `U OH_Pasteboard_*`、`DT_NEEDED` 里**没有** `libpasteboard.so`/`libudmf.so`,应用**启动即 SIGSEGV**
178
+ (模块加载期解析不到符号,表象与「白屏 / 秒退」同形,见 AGENTS §4)。
179
+ 判据两条,缺一不可:`readelf -d <模块.so> | grep -E "pasteboard|udmf"` 有输出、`nm -D --undefined-only`
180
+ 里的 `OH_*` 能被 NEEDED 解释。设备侧这两个库在 `/system/lib64/ndk/`(`ls` 该目录本身是 Permission
181
+ denied,用 `hdc shell stat -L /system/lib64/ndk/libpasteboard.so` 查)。
182
+
183
+ ### 写能通,读要权限,而普通应用拿不到那个权限
184
+
185
+ | 方向 | 原生 API | 权限要求 | 真机结果 |
186
+ | --- | --- | --- | --- |
187
+ | 写 | `OH_Pasteboard_SetData` | 无 | **PASS** |
188
+ | 读 | `OH_Pasteboard_GetData` | `ohos.permission.READ_PASTEBOARD`(头文件注明) | 状态码 **201**(`ERR_PERMISSION_ERROR`),框架如实返回 `null` |
189
+
190
+ `ohos.permission.READ_PASTEBOARD` 在设备上的定义是 `USER_GRANT` + `SYSTEM_BASIC`(`atm dump -d -p` 可查)。
191
+ 它是 `user_grant`,所以 `module.json5` 里必须带 `reason` + `usedScene` 才过 schema;但更关键的是
192
+ **`SYSTEM_BASIC` 要签名 profile 里有 ACL**——普通调试证书把这条写进 `requestPermissions` 之后,
193
+ **连装包都失败**:
194
+
195
+ ```
196
+ code:9568289 error: install failed due to grant request permissions failed. PermissionName: ohos.permission.READ_PASTEBOARD
197
+ ```
198
+
199
+ 所以**不要把这条权限写进骨架模板**(`cli/templates/ohos-skeleton/entry/src/main/module.json5`):
200
+ 那会让所有 `cj-tauri sync ohos` 的工程装不上。当前的落地是「写完整可用、读如实返回 `None`」,
201
+ 探针页把读被拒记成 `SKIP clipboard read denied-by-platform`(不是 `FAIL`:这是平台准入门槛,
202
+ 不是框架回归;也不是 `PASS`:没读回内容就不许说通)。
203
+ 要真正读到内容,得由应用方自己拿带 ACL 的签名 profile,再按平台要求做运行时授权——
204
+ 框架不替应用做这条确认,也不假装读到。
205
+
206
+ 真机判据(`examples/ohos/ohos-capabilities`,`hdc shell hilog` 摘录):
207
+
208
+ ```
209
+ CjProbe: PASS clipboard write cj-tauri-clip-<时间戳>
210
+ CjProbe: SKIP clipboard read denied-by-platform (needs READ_PASTEBOARD; write verified)
211
+ ```
212
+
213
+ 一轮 9 PASS / 0 FAIL(含菜单那三条与能力位对账)。读的状态码用一次**临时诊断**取得
214
+ (把 `statusBox.read()` 拼进返回值),取证后已撤——现在框架返回的就是契约里的 `None`。
215
+