cj-tauri 0.8.0 → 1.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 (124) hide show
  1. package/README.md +3 -3
  2. package/cjpm.toml +52 -25
  3. package/cli/cjpm.toml +1 -1
  4. package/cli/src/doctor.cj +304 -0
  5. package/cli/src/main.cj +33 -7
  6. package/cli/src/project.cj +59 -1
  7. package/cli/src/resolve.cj +348 -304
  8. package/cli/src/scaffold.cj +139 -22
  9. package/cli/templates/ohos-pc/AppScope/app.json5 +13 -0
  10. package/cli/templates/ohos-pc/AppScope/resources/base/element/string.json +8 -0
  11. package/cli/templates/ohos-pc/AppScope/resources/base/media/background.png +0 -0
  12. package/cli/templates/ohos-pc/AppScope/resources/base/media/foreground.png +0 -0
  13. package/cli/templates/ohos-pc/AppScope/resources/base/media/layered_image.json +6 -0
  14. package/cli/templates/ohos-pc/README.md +79 -0
  15. package/cli/templates/ohos-pc/build-hap.sh +190 -0
  16. package/cli/templates/ohos-pc/build-profile.json5 +46 -0
  17. package/cli/templates/ohos-pc/entry/build-profile.json5 +26 -0
  18. package/cli/templates/ohos-pc/entry/cjpm.toml +43 -0
  19. package/cli/templates/ohos-pc/entry/hvigorfile.ts +6 -0
  20. package/cli/templates/ohos-pc/entry/oh-package.json5 +9 -0
  21. package/cli/templates/ohos-pc/entry/src/main/cangjie/ability_stage.cj +17 -0
  22. package/cli/templates/ohos-pc/entry/src/main/cangjie/app_host.cj +94 -0
  23. package/cli/templates/ohos-pc/entry/src/main/cangjie/index.cj +167 -0
  24. package/cli/templates/ohos-pc/entry/src/main/cangjie/main_ability.cj +139 -0
  25. package/cli/templates/ohos-pc/entry/src/main/module.json5 +49 -0
  26. package/cli/templates/ohos-pc/entry/src/main/resources/base/element/color.json +8 -0
  27. package/cli/templates/ohos-pc/entry/src/main/resources/base/element/string.json +16 -0
  28. package/cli/templates/ohos-pc/entry/src/main/resources/base/media/background.png +0 -0
  29. package/cli/templates/ohos-pc/entry/src/main/resources/base/media/foreground.png +0 -0
  30. package/cli/templates/ohos-pc/entry/src/main/resources/base/media/layered_image.json +6 -0
  31. package/cli/templates/ohos-pc/entry/src/main/resources/base/media/startIcon.png +0 -0
  32. package/cli/templates/ohos-pc/entry/src/main/resources/base/profile/main_pages.json +2 -0
  33. package/cli/templates/ohos-pc/entry/src/main/resources/rawfile/capabilities.json +6 -0
  34. package/cli/templates/ohos-pc/entry/src/main/resources/rawfile/index.html +92 -0
  35. package/cli/templates/ohos-pc/hvigor/hvigor-config.json5 +5 -0
  36. package/cli/templates/ohos-pc/hvigorfile.ts +6 -0
  37. package/cli/templates/ohos-pc/oh-package.json5 +10 -0
  38. package/native/build_linux.sh +30 -2
  39. package/native/build_win.bat +62 -61
  40. package/native/core/bridge_core.c +1626 -0
  41. package/native/{bridge_core.h → core/bridge_core.h} +246 -6
  42. package/native/platforms/harmonyos/README.md +106 -0
  43. package/native/platforms/linux/bridge_linux.c +2485 -0
  44. package/native/platforms/windows/bridge_win.c +3248 -0
  45. package/native/tests/test_bridge_core.c +528 -1
  46. package/package.json +12 -3
  47. package/prebuilt/win32-x64/cj-tauri.exe +0 -0
  48. package/src/api_system.cj +10 -2
  49. package/src/app.cj +248 -85
  50. package/src/arkweb_proxy.cj +56 -0
  51. package/src/arkweb_script.cj +154 -0
  52. package/src/capability.cj +140 -0
  53. package/src/clipboard_bridge.cj +55 -0
  54. package/src/dragdrop.cj +26 -0
  55. package/src/embedded_assets.cj +98 -0
  56. package/src/hexutil.cj +53 -0
  57. package/src/host.cj +197 -26
  58. package/src/host_arkweb.cj +740 -0
  59. package/src/host_factory.cj +48 -0
  60. package/src/host_webkit.cj +202 -26
  61. package/src/host_webview2.cj +176 -3
  62. package/src/hotkey.cj +66 -0
  63. package/src/ipc_hub.cj +49 -0
  64. package/src/ipc_message.cj +2 -1
  65. package/src/jsutil.cj +33 -0
  66. package/src/log_bridge.cj +107 -0
  67. package/src/logging.cj +127 -0
  68. package/src/menu.cj +197 -196
  69. package/src/plugin.cj +40 -0
  70. package/src/plugins/clipboard/plugin_clipboard.cj +144 -0
  71. package/src/{plugin_dialog.cj → plugins/dialog/plugin_dialog.cj} +74 -17
  72. package/src/plugins/fs/plugin_fs.cj +194 -0
  73. package/src/plugins/http/plugin_http.cj +459 -0
  74. package/src/plugins/log/plugin_log.cj +105 -0
  75. package/src/{plugin_menu.cj → plugins/menu/plugin_menu.cj} +4 -2
  76. package/src/plugins/mqtt/mqtt_codec.cj +661 -0
  77. package/src/plugins/mqtt/plugin_mqtt.cj +1446 -0
  78. package/src/plugins/notification/plugin_notification.cj +146 -0
  79. package/src/plugins/plugins.cj +5 -0
  80. package/src/plugins/redis/plugin_redis.cj +2409 -0
  81. package/src/plugins/redis/redis_codec.cj +556 -0
  82. package/src/{plugin_serial.cj → plugins/serial/plugin_serial.cj} +442 -489
  83. package/src/{serial_bridge.cj → plugins/serial/serial_bridge.cj} +185 -184
  84. package/src/{plugin_shell.cj → plugins/shell/plugin_shell.cj} +230 -228
  85. package/src/plugins/sqlite/plugin_sqlite.cj +697 -0
  86. package/src/plugins/sqlite/sqlite_ffi.cj +206 -0
  87. package/src/plugins/store/plugin_store.cj +287 -0
  88. package/src/plugins/ws/plugin_ws.cj +470 -0
  89. package/src/process_hidden.cj +174 -0
  90. package/src/scope.cj +391 -0
  91. package/src/shell_event.cj +69 -68
  92. package/src/tests/capability_test.cj +53 -0
  93. package/src/tests/fake_host.cj +85 -20
  94. package/src/tests/ipc_hub_test.cj +13 -0
  95. package/src/tests/jsutil_test.cj +62 -0
  96. package/src/tests/logging_test.cj +62 -0
  97. package/src/tests/mqtt_codec_test.cj +405 -0
  98. package/src/tests/plugin_clipboard_test.cj +222 -0
  99. package/src/tests/plugin_dialog_test.cj +98 -4
  100. package/src/tests/plugin_http_test.cj +124 -0
  101. package/src/tests/plugin_menu_test.cj +1 -0
  102. package/src/tests/plugin_notification_test.cj +205 -0
  103. package/src/tests/plugin_serial_test.cj +269 -267
  104. package/src/tests/plugin_shell_test.cj +189 -188
  105. package/src/tests/plugin_sqlite_test.cj +230 -0
  106. package/src/tests/plugin_store_test.cj +260 -0
  107. package/src/tests/plugin_test.cj +542 -524
  108. package/src/tests/plugin_ws_test.cj +40 -0
  109. package/src/tests/process_hidden_test.cj +129 -0
  110. package/src/tests/redis_codec_test.cj +396 -0
  111. package/src/tests/scope_test.cj +350 -0
  112. package/src/tests/shell_event_test.cj +0 -1
  113. package/src/tests/system_host_test.cj +81 -79
  114. package/src/tests/urlutil_test.cj +53 -0
  115. package/src/tests/version_test.cj +75 -65
  116. package/src/tray.cj +90 -0
  117. package/src/urlutil.cj +159 -0
  118. package/src/version.cj +23 -2
  119. package/src/window.cj +51 -42
  120. package/native/bridge_core.c +0 -797
  121. package/native/bridge_linux.c +0 -1268
  122. package/native/bridge_win.c +0 -1633
  123. package/src/plugin_fs.cj +0 -118
  124. /package/native/{bridge_js.h → core/bridge_js.h} +0 -0
package/src/host.cj CHANGED
@@ -1,6 +1,6 @@
1
- package cjTauri
2
-
3
- import std.collection.*
1
+ package cjTauri
2
+
3
+ import std.collection.*
4
4
  import stdx.encoding.json.*
5
5
 
6
6
  /**
@@ -29,6 +29,13 @@ public const CJ_CAP_TRAY: Int32 = 2
29
29
 
30
30
  public const CJ_CAP_DRAG_DROP: Int32 = 4
31
31
 
32
+ /** 全局快捷键(与 native/bridge_core.h 的 CJ_CAP_HOTKEY 逐位对齐;Linux = 编进了 X11 支持) */
33
+ public const CJ_CAP_HOTKEY: Int32 = 16
34
+
35
+ /** 标题栏副标题(与 native/bridge_core.h 的 CJ_CAP_SUBTITLE 逐位对齐;Linux = 菜单栏同排的 label,
36
+ Windows 的系统菜单没有自绘区、不置位)。应用据它决定要不要自己画页面顶栏。 */
37
+ public const CJ_CAP_SUBTITLE: Int32 = 32
38
+
32
39
  /**
33
40
  * 宿主能力位的**可读形态**(RFC-002 §5.3):`CJ_CAP_*` 位域 → 具名布尔 + `toJson`。
34
41
  *
@@ -44,12 +51,16 @@ public class HostCapabilities {
44
51
  public let menu: Bool
45
52
  public let tray: Bool
46
53
  public let dragDrop: Bool
54
+ public let hotkey: Bool
55
+ public let subtitle: Bool
47
56
 
48
- public init(platform: String, menu: Bool, tray: Bool, dragDrop: Bool) {
57
+ public init(platform: String, menu: Bool, tray: Bool, dragDrop: Bool, hotkey: Bool, subtitle: Bool) {
49
58
  this.platform = platform
50
59
  this.menu = menu
51
60
  this.tray = tray
52
61
  this.dragDrop = dragDrop
62
+ this.hotkey = hotkey
63
+ this.subtitle = subtitle
53
64
  }
54
65
 
55
66
  /** 位域 → 具名布尔(`platform` 由调用方给,见类注释) */
@@ -58,17 +69,21 @@ public class HostCapabilities {
58
69
  platform,
59
70
  (caps & CJ_CAP_MENU) != 0,
60
71
  (caps & CJ_CAP_TRAY) != 0,
61
- (caps & CJ_CAP_DRAG_DROP) != 0
72
+ (caps & CJ_CAP_DRAG_DROP) != 0,
73
+ (caps & CJ_CAP_HOTKEY) != 0,
74
+ (caps & CJ_CAP_SUBTITLE) != 0
62
75
  )
63
76
  }
64
77
 
65
- /** `system:host` 的返回形状:`{"platform":"windows","menu":true,"tray":true,"dragDrop":true}` */
78
+ /** `system:host` 的返回形状:`{"platform":"linux","menu":true,"tray":true,"dragDrop":true,"hotkey":true,"subtitle":true}` */
66
79
  public func toJson(): JsonValue {
67
80
  var obj = JsonObject()
68
81
  obj.put("platform", JsonString(this.platform))
69
82
  obj.put("menu", JsonBool(this.menu))
70
83
  obj.put("tray", JsonBool(this.tray))
71
84
  obj.put("dragDrop", JsonBool(this.dragDrop))
85
+ obj.put("hotkey", JsonBool(this.hotkey))
86
+ obj.put("subtitle", JsonBool(this.subtitle))
72
87
  return obj
73
88
  }
74
89
  }
@@ -145,6 +160,70 @@ public interface WebViewHost {
145
160
  */
146
161
  func setMenuItemState(id: String, enabled: Bool, checked: Bool): Unit
147
162
 
163
+ /**
164
+ * 设置**窗口顶部副标题**:一行文本画在原生菜单栏**同一行的右侧**
165
+ *
166
+ * 用途是让应用省掉自己那条页面顶栏——标题 / 保存状态 / 字数挂在原生这一行上,
167
+ * 界面高度直接少一层。允许运行期高频更新(每次编辑都可能改),平台自己投递到
168
+ * UI 线程,调用方在哪个线程调都行。
169
+ * 这是**运行期能力**:建窗之前调用返回 -3(不像 `setMenu` 那样记 pending),
170
+ * 应用要么等页面加载后再调,要么按返回值如实处理。
171
+ * 平台没有该能力(能力位不含 `CJ_CAP_SUBTITLE`,Windows 即如此)时如实降级:
172
+ * 文本不显示、桥打一行 stderr,应用据能力位自己决定画不画页面顶栏。
173
+ */
174
+ func setTitlebarSubtitle(text: String): Unit
175
+
176
+ /**
177
+ * 设置拖放策略(RFC-002 §5):enabled=false 平台不注册 drop target。
178
+ * 必须在 `start()` / `startUrl()` 之前调用(Linux 建 view 时决定挂不挂 drag dest);
179
+ * 之后调用在未就绪时只记 pending、建窗生效,已就绪平台投 UI 线程重挂。
180
+ * 平台没有该能力(能力位不含 `CJ_CAP_DRAG_DROP`)时宿主只记日志,不报错。
181
+ */
182
+ func setDragDropPolicy(enabled: Bool, files: Bool): Unit
183
+
184
+ /**
185
+ * 注册**全局快捷键**(RFC-003 第 5 期):wire 一行一条 `<accel>:<id>`(见 `HotkeyModel.toWire`)
186
+ *
187
+ * 全局 = 焦点不在本应用窗口时也生效(X11 grab / Win32 RegisterHotKey)。
188
+ * 允许在 `start()` 之前调(桥记 pending,宿主线程起来后注册);运行期再调 = 整表替换。
189
+ * 触发事件以 `{"kind":"hotkey","id":"…"}` 走 shell 回调(按句柄路由)→ 前端 `hotkey:press`。
190
+ * 平台不支持时桥打一行 stderr 如实说明,调用不报错、事件不来。
191
+ */
192
+ func setGlobalHotkeys(wire: String): Unit
193
+
194
+ /**
195
+ * 设置**托盘**(RFC-002 §7.C):wire 见 `TrayModel.toWire()`,空串 = 移除托盘
196
+ *
197
+ * 允许在 `start()` 之前调(桥记 pending,宿主线程装配时创建);运行期再调 = 整体重建。
198
+ * 菜单项被点以 `{"kind":"tray","id":"…"}` 走 shell 回调(按句柄路由)→ 前端 `tray:click`。
199
+ * 平台不支持 / 依赖没编进来时桥打一行 stderr 如实说明,调用不报错、托盘不出现。
200
+ */
201
+ func setTray(wire: String): Unit
202
+
203
+ /**
204
+ * 显示 / 隐藏本窗口(对标 Tauri 2 的 `window.show()` / `window.hide()`)
205
+ *
206
+ * 隐藏 = 从任务栏与屏幕上消失(Windows 走 `ShowWindow(SW_HIDE)`,Linux 走 `gtk_widget_hide`),
207
+ * 事件循环照常跑、页面照常活着——所以「最小化到托盘」的还原路径(托盘菜单 / 全局快捷键)才成立。
208
+ * 两头都能调:`start()` 之前调只记下(建窗时按它定稿,隐藏态的窗口首次出现前就是隐藏的);
209
+ * 之后调用投到宿主 UI 线程执行。显示时顺带把窗口提到前台(Linux 是 `gtk_window_present`,
210
+ * Windows 是 `SetForegroundWindow`)。
211
+ * 这是**宿主基元**,两平台都实现,不占能力位(没有平台只有一半)。
212
+ */
213
+ func setWindowVisible(visible: Bool): Unit
214
+
215
+ /**
216
+ * 最小化到托盘(RFC-003 第 5 期余项,`WindowConfig.minimizeToTray` 的落点)
217
+ *
218
+ * enabled=true 时,用户在标题栏点「最小化」不再缩到任务栏,而是**隐藏窗口**
219
+ * (= `setWindowVisible(false)` 的效果);还原走应用自己的托盘菜单 / 全局快捷键。
220
+ * 必须在 `start()` / `startUrl()` 之前调用(窗口过程要在建窗时就知道这条策略)。
221
+ * 真到最小化那一刻,平台会复核托盘是否**真的**装上了(能力位不含 `CJ_CAP_TRAY`,
222
+ * 或应用压根没调 `setTray`):没装就如实降级为普通最小化并打一行 stderr——
223
+ * 窗口藏起来却没有任何还原入口,是比「不支持」更糟的结果。
224
+ */
225
+ func setMinimizeToTray(enabled: Bool): Unit
226
+
148
227
  /**
149
228
  * 本宿主的静态能力位(`CJ_CAP_*` 的按位或):能不能做菜单 / 托盘 / 文件拖入
150
229
  *
@@ -160,15 +239,15 @@ public interface WebViewHost {
160
239
  * **多窗口下必须按宿主身份路由**:C 侧回调首参是句柄,只有本窗口的句柄才转给本实例的
161
240
  * 这个回调——这正是「回调带宿主身份」的落地方式(见 docs/RFC-菜单-托盘-拖放.md §5.5)。
162
241
  */
163
- func setShellHandler(handler: (String) -> Unit): Unit
164
-
165
- /**
166
- * 装配本窗口的销毁回调(窗口关闭 / 退出收尾时恰好触发一次)
167
- *
168
- * 多窗口下按宿主身份路由(C 侧回调首参是句柄),所以每扇窗口装自己的——回调不带参数,
169
- * 因为「是哪扇窗」在装配时就已确定(应用可经 `app.hostOf(label)` 逐窗安装)。
170
- * 与 `setShellHandler` 同为**可变槽位**:在 `start()` 之后再装同样生效(路由闭包读字段,不是快照)。
171
- */
242
+ func setShellHandler(handler: (String) -> Unit): Unit
243
+
244
+ /**
245
+ * 装配本窗口的销毁回调(窗口关闭 / 退出收尾时恰好触发一次)
246
+ *
247
+ * 多窗口下按宿主身份路由(C 侧回调首参是句柄),所以每扇窗口装自己的——回调不带参数,
248
+ * 因为「是哪扇窗」在装配时就已确定(应用可经 `app.hostOf(label)` 逐窗安装)。
249
+ * 与 `setShellHandler` 同为**可变槽位**:在 `start()` 之后再装同样生效(路由闭包读字段,不是快照)。
250
+ */
172
251
  func setDestroyHandler(handler: () -> Unit): Unit
173
252
 
174
253
  /**
@@ -180,6 +259,26 @@ public interface WebViewHost {
180
259
  */
181
260
  func showFileDialog(save: Bool, title: String, filter: String): String
182
261
 
262
+ /**
263
+ * 带默认文件名的文件对话框:除预置名字外与 `showFileDialog` 完全同语义。
264
+ *
265
+ * 预置名字的用途是**自动化取证**——探针对原生框发 IDOK 时框里得已有名字才能关,
266
+ * 返回值因此可预期(预置什么回什么),多窗并发的「结果各归各窗」才验得动。
267
+ * 平台不支持预置(如 GTK 轮未接)时行为退化为 `showFileDialog`,如实降级。
268
+ */
269
+ func showFileDialogPreset(save: Bool, title: String, filter: String, defaultName: String): String
270
+
271
+ /**
272
+ * 原生目录选择对话框(阻塞到用户关闭):返回选中的目录路径,取消 / 失败返回空串。
273
+ *
274
+ * 独立成方法而不是给 `showFileDialog` 加一个开关:选目录在两端都不是「换个动作」——
275
+ * GTK 是另一个 chooser action,Windows 更是另一套 API(`IFileOpenDialog` +
276
+ * `FOS_PICKFOLDERS`,`GetOpenFileName` 没有这个能力)。桥侧两者共用同名同签名的
277
+ * `cj_bridge_show_dialog`,以 kind=6 区分(与文件类 / 消息类并列,见 `dlg_req`)。
278
+ * 与 `showFileDialog` 同规矩:必须在 start() / startUrl() 之后调用(要拿应用窗口当父窗口)。
279
+ */
280
+ func showFolderDialog(title: String): String
281
+
183
282
  /**
184
283
  * 原生消息对话框(阻塞到用户关闭):返回用户是否点了「确定」。
185
284
  *
@@ -188,6 +287,24 @@ public interface WebViewHost {
188
287
  */
189
288
  func showMessageDialog(kind: String, title: String, message: String): Bool
190
289
 
290
+ /**
291
+ * 写文本进系统剪贴板(上限 8 MiB,C 侧 core 校验)
292
+ *
293
+ * 与对话框同类:宿主能力,要在 start() 之后调用(未就绪时返回 false)。
294
+ */
295
+ func clipboardWriteText(text: String): Bool
296
+
297
+ /**
298
+ * 读系统剪贴板文本:空 / 非文本 / 平台失败都返回 None,不区分(协议层没有可读的失败明细)
299
+ */
300
+ func clipboardReadText(): Option<String>
301
+
302
+ /**
303
+ * 发一条系统通知(发后即忘:提交即返回,显示成败不回传)。
304
+ * 本平台未实现时返回 false(`system:host` 的能力位会同口径如实上报)。
305
+ */
306
+ func notify(title: String, body: String): Bool
307
+
191
308
  /**
192
309
  * 原生 → JS:在 WebView 中执行脚本
193
310
  */
@@ -300,11 +417,7 @@ class HostGlobals {
300
417
  static func registerHostRoute(handle: CPointer<Unit>, onShell: (String) -> Unit,
301
418
  onDestroy: () -> Unit, onDialog: (String) -> Unit): Unit {
302
419
  for (r in HostGlobals.hostRoutes) {
303
- var same: Int32 = 0
304
- unsafe {
305
- same = cj_bridge_host_eq(r.handle, handle)
306
- }
307
- if (same != 0) {
420
+ if (hostHandleEquals(r.handle, handle)) {
308
421
  r.onShell = onShell
309
422
  r.onDestroy = onDestroy
310
423
  r.onDialog = onDialog
@@ -319,11 +432,7 @@ class HostGlobals {
319
432
  */
320
433
  static func hostRouteOf(handle: CPointer<Unit>): Option<HostRoute> {
321
434
  for (r in HostGlobals.hostRoutes) {
322
- var same: Int32 = 0
323
- unsafe {
324
- same = cj_bridge_host_eq(r.handle, handle)
325
- }
326
- if (same != 0) {
435
+ if (hostHandleEquals(r.handle, handle)) {
327
436
  return Some(r)
328
437
  }
329
438
  }
@@ -407,23 +516,85 @@ func shellOnEvent(host: CPointer<Unit>, json: CString): Unit {
407
516
  * 必须是构造参数而不是事后 setter——label 决定页面侧 `window.__CJ_TAURI_LABEL__` 与事件过滤,
408
517
  * 启动之后再改就晚了(见 docs/架构演进-多平台与多窗口.md §7.3)。
409
518
  */
410
- @When[os == "Linux"]
519
+ // 判据为什么是 `env`(2026-10-19 校正):鸿蒙目标下 `os` 仍取 `Linux`,`arch` 也只有
520
+ // `aarch64`——桌面 aarch64 Linux 同样是 `aarch64`,拿它当判据会把「跑在 GTK 上」误判成
521
+ // 「跑在 ArkWeb 上」,两边的宿主实现会互相打架(编译期就撞名)。官方条件编译映射表给出
522
+ // `aarch64-linux-ohos` → `env == "ohos"`、桌面 Linux → `gnu`,这才是真正互斥的维度。
523
+ // 于是桌面 Linux 侧写 `env != "ohos"`:**桌面 aarch64 Linux 因此重新拿回 WebKit 宿主**
524
+ // (改判据之前它落到「只能注入宿主」的分支,是同一处误判的另一半)。
525
+ @When[os == "Linux" && env != "ohos"]
411
526
  func createHost(label: String): WebViewHost {
412
527
  return WebKitHost(label)
413
528
  }
414
529
 
530
+ // 鸿蒙:内置 ArkWeb 宿主(应用侧只负责建 ArkUI 组件并把 controller 交给它,见 src/host_arkweb.cj)
531
+ @When[env == "ohos"]
532
+ func createHost(label: String): WebViewHost {
533
+ return ArkWebHost(label)
534
+ }
535
+
415
536
  @When[os == "Windows"]
416
537
  func createHost(label: String): WebViewHost {
417
538
  return WebView2Host(label)
418
539
  }
419
540
 
541
+ /**
542
+ * 两个宿主句柄是否指向同一宿主
543
+ *
544
+ * 桌面两平台走 C 桥 `cj_bridge_host_eq`——仓颉的 `CPointer` 既没有 `==` 也没有 `!=`
545
+ * (实测报 `invalid binary operator '!=' on type 'CPointer<Unit>'`),也没有 `hashCode`,
546
+ * 比较只能交给 C 侧。鸿蒙侧没有 C 桥(`native/platforms/harmonyos/` 尚未产出 `cj_bridge_*`),
547
+ * `ArkWebHost` 的句柄在仓颉侧不参与路由(回调是仓颉闭包,不经 C 中转),因此恒为 false
548
+ * 并留一行 stderr——**不假装相等**(假装相等会把两扇窗的路由串台)。
549
+ */
550
+ @When[os == "Windows"]
551
+ func hostHandleEquals(a: CPointer<Unit>, b: CPointer<Unit>): Bool {
552
+ unsafe {
553
+ return cj_bridge_host_eq(a, b) != 0
554
+ }
555
+ }
556
+
557
+ @When[os == "Linux" && env != "ohos"]
558
+ func hostHandleEquals(a: CPointer<Unit>, b: CPointer<Unit>): Bool {
559
+ unsafe {
560
+ return cj_bridge_host_eq(a, b) != 0
561
+ }
562
+ }
563
+
564
+ // 只在无 C 桥的平台用到,声明也放进同一门禁:桌面侧保留会变成 unused variable
565
+ @When[env == "ohos"]
566
+ private var HOST_ROUTE_WARNED: Bool = false
567
+
568
+ @When[env == "ohos"]
569
+ func hostHandleEquals(a: CPointer<Unit>, b: CPointer<Unit>): Bool {
570
+ let _ = a
571
+ let _ = b
572
+ if (!HOST_ROUTE_WARNED) {
573
+ HOST_ROUTE_WARNED = true
574
+ eprintln("[cj-tauri] host route by handle unavailable on this platform: injected host keeps its own callbacks")
575
+ }
576
+ return false
577
+ }
578
+
420
579
  /**
421
580
  * 造一个「裸宿主句柄」:只分配 C 侧宿主对象(不 init / 不 start、不碰图形栈)
422
581
  *
423
582
  * 只给单元测试用(子包可见,不算对外 API)。为什么需要这层包皮:`cj_bridge_create` 是
424
583
  * `foreign func`——没有函数体、也就没有跨包符号,子包 `cjTauri.tests` 直接调会在**链接期**炸
425
584
  * (实测 `ld.lld: error: undefined symbol: cjTauri:cj_bridge_create`);框架包内包一层就有实体符号了。
585
+ *
586
+ * 只编桌面两平台:鸿蒙没有 C 桥也就没有这个符号,而单测只在桌面跑(`cjpm test` 用桌面目标)。
426
587
  */
588
+ @When[os == "Windows"]
589
+ func createBareHostHandle(): CPointer<Unit> {
590
+ var h: CPointer<Unit> = CPointer<Unit>()
591
+ unsafe {
592
+ h = cj_bridge_create()
593
+ }
594
+ return h
595
+ }
596
+
597
+ @When[os == "Linux" && env != "ohos"]
427
598
  func createBareHostHandle(): CPointer<Unit> {
428
599
  var h: CPointer<Unit> = CPointer<Unit>()
429
600
  unsafe {