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,570 +1,663 @@
1
- package cjTauri
2
-
3
- // Windows 侧才用得上(类整体门禁在 Windows):加同款门禁,免得 Linux 构建把这条当「未使用 import」清掉
4
- @When[os == "Windows"]
5
- import std.collection.*
6
-
7
- // 页面来源契约(load(source) 收敛,RFC §4.3):同款门禁,免得别的目标把这条当未使用清掉
8
- @When[os == "Windows"]
9
- import cjTauri.platform.*
10
-
11
- /**
12
- * C 桥 FFI 声明(native/libcjtbridge.dll)
13
- * Win32 窗口 + WebView2(Edge Chromium)在 C 桥的宿主线程内运行(见 native/bridge_win.c 头注释)。
14
- * 与 Linux 端(host_webkit.cj)同名同签名,两端宿主实现同构。
15
- *
16
- * 句柄制(批次 1):宿主状态在 C 侧按句柄隔离,导出一律首参收句柄(`cj_host*`)。
17
- * 句柄内存由 C 侧分配/回收(create 分配、destroy 释放)——跨 FFI 绝不「这边分配那边释放」
18
- * (Windows 上 CRT 与仓颉运行时不是同一个分配器);生命周期约定见
19
- * docs/架构演进-多平台与多窗口.md §6.1。
20
- */
21
- @When[os == "Windows"]
22
- @C
23
- foreign func cj_bridge_create(): CPointer<Unit>
24
-
25
- /**
26
- * 释放宿主编:对空指针安全、幂等;调用后该句柄不可再用
27
- */
28
- @When[os == "Windows"]
29
- @C
30
- foreign func cj_bridge_destroy(h: CPointer<Unit>): Unit
31
-
32
- @When[os == "Windows"]
33
- @C
34
- foreign func cj_bridge_init(h: CPointer<Unit>, m: CFunc<(CString) -> Unit>, d: CFunc<(CPointer<Unit>) -> Unit>): Unit
35
-
36
- @When[os == "Windows"]
37
- @C
38
- foreign func cj_bridge_start(h: CPointer<Unit>, html: CString): Unit
39
-
40
- @When[os == "Windows"]
41
- @C
42
- foreign func cj_bridge_load_url(h: CPointer<Unit>, url: CString): Unit
43
-
44
- /**
45
- * 注册一段预执行脚本(插件 jsShim 等):须在 cj_bridge_start 之前调用,桥在 document-start 注入
46
- */
47
- @When[os == "Windows"]
48
- @C
49
- foreign func cj_bridge_add_init_script(h: CPointer<Unit>, js: CString): Unit
50
-
51
- @When[os == "Windows"]
52
- @C
53
- foreign func cj_bridge_reload(h: CPointer<Unit>): Unit
54
-
55
- @When[os == "Windows"]
56
- @C
57
- foreign func cj_bridge_set_window(h: CPointer<Unit>, title: CString, width: Int32, height: Int32): Unit
58
-
59
- @When[os == "Windows"]
60
- @C
61
- foreign func cj_bridge_set_devtools(h: CPointer<Unit>, enabled: Int32): Unit
62
-
63
- @When[os == "Windows"]
64
- @C
65
- foreign func cj_bridge_set_icon(h: CPointer<Unit>, path: CString): Unit
66
-
67
- @When[os == "Windows"]
68
- @C
69
- foreign func cj_bridge_open_devtools(h: CPointer<Unit>): Unit
70
-
71
- @When[os == "Windows"]
72
- @C
73
- foreign func cj_bridge_set_dialog_callback(h: CPointer<Unit>, cb: CFunc<(CPointer<Unit>, CString) -> Unit>): Unit
74
-
75
- /**
76
- * 原生对话框(阻塞式):kind 0=打开文件 1=保存文件 2=info 3=warning 4=error 5=confirm。
77
- * 返回 1 = 用户确认;文件类选中路径经 cj_bridge_set_dialog_callback 注册的回调送回。
78
- */
79
- @When[os == "Windows"]
80
- @C
81
- foreign func cj_bridge_show_dialog(h: CPointer<Unit>, kind: Int32, title: CString, message: CString,
82
- filter: CString): Int32
83
-
84
- @When[os == "Windows"]
85
- @C
86
- foreign func cj_bridge_run_js(h: CPointer<Unit>, js: CString): Unit
87
-
88
- @When[os == "Windows"]
89
- @C
90
- foreign func cj_bridge_quit(h: CPointer<Unit>): Unit
91
-
92
- @When[os == "Windows"]
93
- @C
94
- foreign func cj_bridge_is_ready(h: CPointer<Unit>): Int32
95
-
96
- @When[os == "Windows"]
97
- @C
98
- foreign func cj_bridge_should_quit(h: CPointer<Unit>): Int32
99
-
100
- /**
101
- * 菜单栏(RFC-002 §5.4 的线路文本):空串 = 清空。未就绪时桥只记下,建窗时一次建好。
102
- */
103
- @When[os == "Windows"]
104
- @C
105
- foreign func cj_bridge_set_menu(h: CPointer<Unit>, wire: CString): Unit
106
-
107
- /**
108
- * 运行期改单个菜单项的状态(按 id 找;找不到只记日志)
109
- */
110
- @When[os == "Windows"]
111
- @C
112
- foreign func cj_bridge_set_menu_item_state(h: CPointer<Unit>, id: CString, enabled: Int32,
113
- checked: Int32): Unit
114
-
115
- /**
116
- * 窗口顶部副标题:Windows 侧如实降级(返回 -4,能力位 CJ_CAP_SUBTITLE 不置位)。
117
- * 同名同签名保留是为了上层不出现平台分支(AGENTS §2 的第一条)。
118
- */
119
- @When[os == "Windows"]
120
- @C
121
- foreign func cj_bridge_set_titlebar_subtitle(h: CPointer<Unit>, text: CString): Int32
122
-
123
- /**
124
- * 拖放策略(RFC-002 §5):Windows 侧当前为桩(能力位如实不置位),同名同签名保上层零平台分支
125
- */
126
- @When[os == "Windows"]
127
- @C
128
- foreign func cj_bridge_set_drag_drop(h: CPointer<Unit>, enabled: Int32, files: Int32): Unit
129
-
130
- /**
131
- * 全局快捷键(RFC-003 第 5 期):Windows 侧走 `RegisterHotKey`(见 native/platforms/windows/
132
- * bridge_win.c),wire 词法与 Linux 同规(Ctrl/Alt/Shift/Super + 常用键名),触发经 on_shell 回投
133
- */
134
- @When[os == "Windows"]
135
- @C
136
- foreign func cj_bridge_set_global_hotkeys(h: CPointer<Unit>, wire: CString): Unit
137
-
138
- /**
139
- * 托盘(RFC-002 §7.C):Windows 侧走 `Shell_NotifyIconW`(菜单是 CreatePopupMenu,只弹不挂窗口),
140
- * 点击与 Linux 同信封回投 `{"kind":"tray","id":…}`
141
- */
142
- @When[os == "Windows"]
143
- @C
144
- foreign func cj_bridge_set_tray(h: CPointer<Unit>, wire: CString): Unit
145
-
146
- /**
147
- * 窗口可见性(`show()` / `hide()` 的 C 侧落点):start 之前调只记 pending,就绪后投 UI 线程执行
148
- */
149
- @When[os == "Windows"]
150
- @C
151
- foreign func cj_bridge_set_window_visible(h: CPointer<Unit>, visible: Int32): Unit
152
-
153
- /**
154
- * 最小化到托盘策略位(须在 start 之前调):窗口过程据此把「最小化」改写成「隐藏」
155
- */
156
- @When[os == "Windows"]
157
- @C
158
- foreign func cj_bridge_set_minimize_to_tray(h: CPointer<Unit>, enabled: Int32): Unit
159
-
160
- /**
161
- * shell 事件回调(菜单点击等):首参是宿主句柄,多窗口靠它分辨事件属于哪个窗口
162
- */
163
- @When[os == "Windows"]
164
- @C
165
- foreign func cj_bridge_set_shell_callback(h: CPointer<Unit>,
166
- cb: CFunc<(CPointer<Unit>, CString) -> Unit>): Unit
167
-
168
- /**
169
- * 宿主能力位(CJ_CAP_* 的按位或):未 init 返回 0
170
- */
171
- @When[os == "Windows"]
172
- @C
173
- foreign func cj_bridge_host_capabilities(h: CPointer<Unit>): Int32
174
-
175
- /**
176
- * 句柄同一性判定(返回 1 = 同一个宿主):仓颉侧 CPointer 不能比较,判定交回 C 侧
177
- */
178
- @When[os == "Windows"]
179
- @C
180
- foreign func cj_bridge_host_eq(a: CPointer<Unit>, b: CPointer<Unit>): Int32
181
-
182
- /**
183
- * Windows WebView2 宿主实现:通过 C 桥驱动
184
- */
185
- @When[os == "Windows"]
186
- public class WebView2Host <: WebViewHost {
187
- // 消息回调:宿主线程(原生 Win32 消息循环)→ 仓颉
188
- public var onMessage: (String) -> Unit = { _ => }
189
- // 销毁回调(应用经 setDestroyHandler 装配;由 C 桥按句柄认领,多窗口不串)
190
- public var onDestroy: () -> Unit = { => }
191
- // 本窗口文件对话框的结果:C 桥按句柄经路由回调写入(两窗各写各的,不许串台)
192
- private var dialogResult: String = ""
193
- // 本窗口的 shell 事件(菜单点击等)回调:TauriApp 装配时经 setShellHandler 覆盖它做分发,
194
- // 未覆盖时只留一行 stderr——菜单点了没人管,至少能在日志里看见。
195
- public var shellHandler: (String) -> Unit = { json =>
196
- eprintln("[cj-tauri] shell event (no handler): ${json}")
197
- }
198
- // 窗口配置(标题 / 尺寸),start() 前由 TauriApp 注入
199
- private var config: WindowConfig = WindowConfig()
200
- // 预执行脚本(插件 jsShim 等):start 前收集,prepareHost 一次性交给桥,桥在 document-start 注入
201
- private var initScripts = ArrayList<String>()
202
- // 宿主编(C 侧分配):空指针 = 尚未创建;dispose() 后置空
203
- private var handle: CPointer<Unit> = CPointer<Unit>()
204
- // 本窗口的 label(字段名不叫 label:接口有同名方法)。单窗口阶段是常量,
205
- // 与 TauriApp 注册的 WindowRecord.label 一致;多窗口时由构造参数给定。
206
- private let windowLabel: String
207
- // 资产解析器:装配层在 load 前注入(None = 按 cwd 相对路径读盘,与批次 1 行为等价)
208
- private var assetResolver: Option<AssetResolver> = None
209
-
210
- public init() {
211
- this.windowLabel = DEFAULT_WINDOW_LABEL
212
- }
213
-
214
- /**
215
- * 带 label 构造(多窗口):label 必须与 TauriApp 注册表里登记的那个一致,
216
- * 页面侧的 `window.__CJ_TAURI_LABEL__` 也取同一个值(由 TauriApp 注入)。
217
- */
218
- public init(label: String) {
219
- this.windowLabel = label
220
- }
221
-
222
- public func label(): String {
223
- return this.windowLabel
224
- }
225
-
226
- public func setMessageHandler(handler: (String) -> Unit): Unit {
227
- this.onMessage = handler
228
- }
229
-
230
- public func setWindowConfig(config: WindowConfig): Unit {
231
- this.config = config
232
- }
233
-
234
- public func setDevTools(enabled: Bool): Unit {
235
- this.config.devTools = enabled
236
- }
237
-
238
- public func addInitScript(js: String): Unit {
239
- // 空串忽略(插件没写 shim 时不必占位,桥侧也会再挡一次)
240
- if (js.size > 0) {
241
- this.initScripts.add(js)
242
- }
243
- }
244
-
245
- public func openDevTools(): Unit {
246
- unsafe {
247
- cj_bridge_open_devtools(this.handle)
248
- }
249
- }
250
-
251
- public func setMenu(wire: String): Unit {
252
- // 句柄先建出来:setMenu 允许在 start() 之前调——文本由桥记着,建窗时一次建好
253
- this.ensureHandle()
254
- unsafe {
255
- cj_bridge_set_menu(this.handle, LibC.mallocCString(wire))
256
- }
257
- }
258
-
259
- public func setMenuItemState(id: String, enabled: Bool, checked: Bool): Unit {
260
- if (this.handle.isNull()) {
261
- // 还没建宿主就谈不上「改状态」:静默返回,不拿空句柄去 FFI
262
- return
263
- }
264
- var e: Int32 = 0
265
- var c: Int32 = 0
266
- if (enabled) {
267
- e = 1
268
- }
269
- if (checked) {
270
- c = 1
271
- }
272
- unsafe {
273
- cj_bridge_set_menu_item_state(this.handle, LibC.mallocCString(id), e, c)
274
- }
275
- }
276
-
277
- public func setTitlebarSubtitle(text: String): Unit {
278
- if (this.handle.isNull()) {
279
- return
280
- }
281
- unsafe {
282
- // 本平台如实降级(桥返回 -4,能力位不含 CJ_CAP_SUBTITLE):这里不当作错误刷屏,
283
- // 应用据 hostCapabilities().subtitle 自己就不会依赖这条通道
284
- let _ = cj_bridge_set_titlebar_subtitle(this.handle, LibC.mallocCString(text))
285
- }
286
- }
287
-
288
- public func setDragDropPolicy(enabled: Bool, files: Bool): Unit {
289
- // 与 setMenu 同形:允许在 start() 之前调。Windows 侧桥当前是桩(能力位如实不置位),
290
- // 但仓颉侧 API 与调用点不写平台分支——实机轮补 IDropTarget 后上层零改动
291
- this.ensureHandle()
292
- var en: Int32 = 0
293
- var fi: Int32 = 0
294
- if (enabled) {
295
- en = 1
296
- }
297
- if (files) {
298
- fi = 1
299
- }
300
- unsafe {
301
- cj_bridge_set_drag_drop(this.handle, en, fi)
302
- }
303
- }
304
-
305
- public func setGlobalHotkeys(wire: String): Unit {
306
- // 与 setMenu 同形:允许在 start() 之前调。Windows 侧桥当前是桩(能力位如实不置位),
307
- // 仓颉侧 API 不写平台分支——实机轮补 RegisterHotKey 后上层零改动
308
- this.ensureHandle()
309
- unsafe {
310
- cj_bridge_set_global_hotkeys(this.handle, LibC.mallocCString(wire))
311
- }
312
- }
313
-
314
- public func setTray(wire: String): Unit {
315
- // 与 setMenu 同形:允许在 start() 之前调。Windows 侧桥当前是桩(能力位如实不置位),
316
- // 仓颉侧 API 不写平台分支——实机轮补 Shell_NotifyIconW 后上层零改动
317
- this.ensureHandle()
318
- unsafe {
319
- cj_bridge_set_tray(this.handle, LibC.mallocCString(wire))
320
- }
321
- }
322
-
323
- public func setWindowVisible(visible: Bool): Unit {
324
- // 与 setTray 同形:允许在 start() 之前调(桥记 pending,建窗时定稿);就绪后投宿主 UI 线程
325
- this.ensureHandle()
326
- var v: Int32 = 0
327
- if (visible) {
328
- v = 1
329
- }
330
- unsafe {
331
- cj_bridge_set_window_visible(this.handle, v)
332
- }
333
- }
334
-
335
- public func setMinimizeToTray(enabled: Bool): Unit {
336
- // 策略位:窗口过程要在建窗时就拿到它,所以只在 start() 之前调有意义(与 setDragDropPolicy 同规)
337
- this.ensureHandle()
338
- var e: Int32 = 0
339
- if (enabled) {
340
- e = 1
341
- }
342
- unsafe {
343
- cj_bridge_set_minimize_to_tray(this.handle, e)
344
- }
345
- }
346
-
347
- public func hostCapabilities(): Int32 {
348
- if (this.handle.isNull()) {
349
- return 0
350
- }
351
- var caps: Int32 = 0
352
- unsafe {
353
- caps = cj_bridge_host_capabilities(this.handle)
354
- }
355
- return caps
356
- }
357
-
358
- public func setShellHandler(handler: (String) -> Unit): Unit {
359
- this.shellHandler = handler
360
- }
361
-
362
- public func setDestroyHandler(handler: () -> Unit): Unit {
363
- this.onDestroy = handler
364
- }
365
-
366
- public func showFileDialog(save: Bool, title: String, filter: String): String {
367
- return this.showFileDialogPreset(save, title, filter, "")
368
- }
369
-
370
- public func showFileDialogPreset(save: Bool, title: String, filter: String,
371
- defaultName: String): String {
372
- var kind: Int32 = 0
373
- if (save) {
374
- kind = 1
375
- }
376
- var ok: Int32 = 0
377
- unsafe {
378
- // defaultName 经 message 参数通道交给桥(文件类对话框把它当预置文件名)
379
- ok = cj_bridge_show_dialog(this.handle, kind, LibC.mallocCString(title),
380
- LibC.mallocCString(defaultName), LibC.mallocCString(filter))
381
- }
382
- if (ok == 0) {
383
- // 取消 / 宿主未就绪:上游按「空路径」处理,不再读结果位
384
- return ""
385
- }
386
- return this.dialogResult
387
- }
388
-
389
- public func showFolderDialog(title: String): String {
390
- var ok: Int32 = 0
391
- unsafe {
392
- // 选目录:kind=6(平台侧走 IFileOpenDialog + FOS_PICKFOLDERS);message 通道不参与
393
- ok = cj_bridge_show_dialog(this.handle, 6, LibC.mallocCString(title),
394
- LibC.mallocCString(""), LibC.mallocCString(""))
395
- }
396
- if (ok == 0) {
397
- // 取消 / 宿主未就绪:上游按「空路径」处理
398
- return ""
399
- }
400
- return this.dialogResult
401
- }
402
-
403
- public func showMessageDialog(kind: String, title: String, message: String): Bool {
404
- var ok: Int32 = 0
405
- unsafe {
406
- ok = cj_bridge_show_dialog(this.handle, dialogKindCode(kind), LibC.mallocCString(title),
407
- LibC.mallocCString(message), LibC.mallocCString(""))
408
- }
409
- return ok != 0
410
- }
411
-
412
- public func clipboardWriteText(text: String): Bool {
413
- var rc: Int32 = -1
414
- unsafe {
415
- rc = cj_bridge_clipboard_write_text(this.handle, LibC.mallocCString(text))
416
- }
417
- if (rc != 0) {
418
- eprintln("[cj-tauri] clipboard write failed: code=${rc}")
419
- }
420
- return rc == 0
421
- }
422
-
423
- public func clipboardReadText(): Option<String> {
424
- let cs = unsafe { cj_bridge_clipboard_read_text(this.handle) }
425
- if (cs.isNull()) {
426
- return None<String> // 空 / 非文本 / 平台失败,协议层不区分
427
- }
428
- let text = cs.toString()
429
- unsafe {
430
- cj_bridge_clipboard_free(cs) // 归还缓冲:malloc 家族分配,必须走桥的 free
431
- }
432
- return Some(text)
433
- }
434
-
435
- public func notify(title: String, body: String): Bool {
436
- var rc: Int32 = -1
437
- unsafe {
438
- rc = cj_bridge_notification_notify(this.handle, LibC.mallocCString(title),
439
- LibC.mallocCString(body))
440
- }
441
- if (rc != 0) {
442
- // Windows 桩阶段返回 -4(未实现):静态事实,stderr 一行说清,不算异常
443
- eprintln("[cj-tauri] notification failed: code=${rc}")
444
- }
445
- return rc == 0
446
- }
447
-
448
- /**
449
- * 创建宿主编(幂等):首次启动时分配,dispose() 后置空
450
- */
451
- private func ensureHandle(): Unit {
452
- if (this.handle.isNull()) {
453
- unsafe {
454
- this.handle = cj_bridge_create()
455
- }
456
- }
457
- }
458
-
459
- /**
460
- * 启动前的共同装配:宿主编、回调槽位、窗口配置、图标
461
- */
462
- private func prepareHost(): Unit {
463
- this.ensureHandle()
464
- HostGlobals.onMessage = this.onMessage
465
- // 宿主级路由:「本句柄 → 本实例的三个回调」登记一条(表式,不覆盖别的窗口)
466
- HostGlobals.registerHostRoute(this.handle, { json => this.shellHandler(json) },
467
- { => this.onDestroy() },
468
- { path => this.dialogResult = path })
469
- var devTools: Int32 = 0
470
- if (this.config.devTools) {
471
- devTools = 1
472
- }
473
- unsafe {
474
- cj_bridge_init(this.handle, hostOnMessage, hostOnDestroy)
475
- // shell 事件回调(菜单点击等):与消息回调同一套路,多一个首参用来分辨窗口
476
- cj_bridge_set_shell_callback(this.handle, shellOnEvent)
477
- // 原生对话框的结果回调:与消息回调同一套路,多一个首参(结果只落发起这次对话框的窗口)
478
- cj_bridge_set_dialog_callback(this.handle, dialogOnResult)
479
- cj_bridge_set_window(this.handle, LibC.mallocCString(this.config.title),
480
- Int32(this.config.width), Int32(this.config.height))
481
- cj_bridge_set_devtools(this.handle, devTools)
482
- // 窗口图标:空串表示系统默认图标
483
- cj_bridge_set_icon(this.handle, LibC.mallocCString(this.config.iconPath))
484
- // 预执行脚本(插件 jsShim 等):与 BRIDGE_JS 同一条 document-start 通道,按注册顺序排在桥之后
485
- for (js in this.initScripts) {
486
- cj_bridge_add_init_script(this.handle, LibC.mallocCString(js))
487
- }
488
- }
489
- }
490
-
491
- /**
492
- * 加载页面(唯一入口,RFC §4.3):Inline/Url/DevServer 直接映射;
493
- * Asset 经注入的 resolver 读盘,未注入时按 cwd 相对路径读(批次 1 的桌面行为)
494
- */
495
- public func load(source: PageSource): Unit {
496
- match (resolvePage(source, this.assetResolver)) {
497
- case StartHtml(html) => this.start(html)
498
- case StartUrl(url) => this.startUrl(url)
499
- }
500
- }
501
-
502
- /** 注入资产解析器(装配层在 load 前调用) */
503
- public func setAssetResolver(resolver: Option<AssetResolver>): Unit {
504
- this.assetResolver = resolver
505
- }
506
-
507
- public func start(html: String): Unit {
508
- this.prepareHost()
509
- unsafe {
510
- cj_bridge_start(this.handle, LibC.mallocCString(html))
511
- }
512
- }
513
-
514
- public func startUrl(url: String): Unit {
515
- this.prepareHost()
516
- unsafe {
517
- cj_bridge_load_url(this.handle, LibC.mallocCString(url))
518
- // 空串表示「页面由 cj_bridge_load_url 指定」(同一次启动里 URL 优先)
519
- cj_bridge_start(this.handle, LibC.mallocCString(""))
520
- }
521
- }
522
-
523
- public func reload(): Unit {
524
- unsafe {
525
- cj_bridge_reload(this.handle)
526
- }
527
- }
528
-
529
- public func runJs(js: String): Unit {
530
- unsafe {
531
- cj_bridge_run_js(this.handle, LibC.mallocCString(js))
532
- }
533
- }
534
-
535
- public func quit(): Unit {
536
- unsafe {
537
- cj_bridge_quit(this.handle)
538
- }
539
- }
540
-
541
- public func shouldQuit(): Bool {
542
- unsafe {
543
- cj_bridge_should_quit(this.handle) != 0
544
- }
545
- }
546
-
547
- /**
548
- * 释放宿主编(幂等,未创建过则是空操作):事件循环退出后调用一次,之后句柄置空、本实例不可再启动
549
- */
550
- public func dispose(): Unit {
551
- if (this.handle.isNull()) {
552
- return
553
- }
554
- unsafe {
555
- cj_bridge_destroy(this.handle)
556
- }
557
- this.handle = CPointer<Unit>()
558
- }
559
- }
560
-
561
- // C 回调中转:C 桥线程 → 仓颉闭包(经全局槽位)
562
- @When[os == "Windows"]
563
- @C
564
- func hostOnMessage(json: CString): Unit {
565
- try {
566
- HostGlobals.onMessage(json.toString())
567
- } catch (e: Exception) {
568
- eprintln("[cj-tauri] host callback error: ${e}")
569
- }
570
- }
1
+ package cjTauri
2
+
3
+ // Windows 侧才用得上(类整体门禁在 Windows):加同款门禁,免得 Linux 构建把这条当「未使用 import」清掉
4
+ @When[os == "Windows"]
5
+ import std.collection.*
6
+
7
+ // 页面来源契约(load(source) 收敛,RFC §4.3):同款门禁,免得别的目标把这条当未使用清掉
8
+ @When[os == "Windows"]
9
+ import cjTauri.platform.*
10
+
11
+ /**
12
+ * C 桥 FFI 声明(native/libcjtbridge.dll)
13
+ * Win32 窗口 + WebView2(Edge Chromium)在 C 桥的宿主线程内运行(见 native/bridge_win.c 头注释)。
14
+ * 与 Linux 端(host_webkit.cj)同名同签名,两端宿主实现同构。
15
+ *
16
+ * 句柄制(批次 1):宿主状态在 C 侧按句柄隔离,导出一律首参收句柄(`cj_host*`)。
17
+ * 句柄内存由 C 侧分配/回收(create 分配、destroy 释放)——跨 FFI 绝不「这边分配那边释放」
18
+ * (Windows 上 CRT 与仓颉运行时不是同一个分配器);生命周期约定见
19
+ * docs/架构演进-多平台与多窗口.md §6.1。
20
+ */
21
+ @When[os == "Windows"]
22
+ @C
23
+ foreign func cj_bridge_create(): CPointer<Unit>
24
+
25
+ /**
26
+ * 释放宿主编:对空指针安全、幂等;调用后该句柄不可再用
27
+ */
28
+ @When[os == "Windows"]
29
+ @C
30
+ foreign func cj_bridge_destroy(h: CPointer<Unit>): Unit
31
+
32
+ @When[os == "Windows"]
33
+ @C
34
+ foreign func cj_bridge_init(h: CPointer<Unit>, m: CFunc<(CString) -> Unit>, d: CFunc<(CPointer<Unit>) -> Unit>): Unit
35
+
36
+ @When[os == "Windows"]
37
+ @C
38
+ foreign func cj_bridge_start(h: CPointer<Unit>, html: CString): Unit
39
+
40
+ @When[os == "Windows"]
41
+ @C
42
+ foreign func cj_bridge_load_url(h: CPointer<Unit>, url: CString): Unit
43
+
44
+ /**
45
+ * 注册一段预执行脚本(插件 jsShim 等):须在 cj_bridge_start 之前调用,桥在 document-start 注入
46
+ */
47
+ @When[os == "Windows"]
48
+ @C
49
+ foreign func cj_bridge_add_init_script(h: CPointer<Unit>, js: CString): Unit
50
+
51
+ @When[os == "Windows"]
52
+ @C
53
+ foreign func cj_bridge_reload(h: CPointer<Unit>): Unit
54
+
55
+ @When[os == "Windows"]
56
+ @C
57
+ foreign func cj_bridge_set_window(h: CPointer<Unit>, title: CString, width: Int32, height: Int32): Unit
58
+
59
+ @When[os == "Windows"]
60
+ @C
61
+ foreign func cj_bridge_set_devtools(h: CPointer<Unit>, enabled: Int32): Unit
62
+
63
+ @When[os == "Windows"]
64
+ @C
65
+ foreign func cj_bridge_set_icon(h: CPointer<Unit>, path: CString): Unit
66
+
67
+ @When[os == "Windows"]
68
+ @C
69
+ foreign func cj_bridge_open_devtools(h: CPointer<Unit>): Unit
70
+
71
+ @When[os == "Windows"]
72
+ @C
73
+ foreign func cj_bridge_set_dialog_callback(h: CPointer<Unit>, cb: CFunc<(CPointer<Unit>, CString) -> Unit>): Unit
74
+
75
+ /**
76
+ * 同窗内嵌视图(PLT-006):create 返回视图句柄(> 0 有效,0 = 平台未就绪 / 不支持)。
77
+ * 五条都只是「投递」——桥把动作 PostMessage 到宿主 UI 线程执行,句柄当场返回,
78
+ * 所以 create 成功不代表内容已经出来(真正的 controller 稍后才建)。
79
+ */
80
+ @When[os == "Windows"]
81
+ @C
82
+ foreign func cj_bridge_view_create(h: CPointer<Unit>, url: CString,
83
+ x: Int32, y: Int32, width: Int32, height: Int32): Int64
84
+
85
+ @When[os == "Windows"]
86
+ @C
87
+ foreign func cj_bridge_view_set_visible(h: CPointer<Unit>, view: Int64, visible: Int32): Unit
88
+
89
+ @When[os == "Windows"]
90
+ @C
91
+ foreign func cj_bridge_view_set_bounds(h: CPointer<Unit>, view: Int64,
92
+ x: Int32, y: Int32, width: Int32, height: Int32): Unit
93
+
94
+ @When[os == "Windows"]
95
+ @C
96
+ foreign func cj_bridge_view_navigate(h: CPointer<Unit>, view: Int64, url: CString): Unit
97
+
98
+ @When[os == "Windows"]
99
+ @C
100
+ foreign func cj_bridge_view_destroy(h: CPointer<Unit>, view: Int64): Unit
101
+
102
+ /**
103
+ * 原生对话框(阻塞式):kind 0=打开文件 1=保存文件 2=info 3=warning 4=error 5=confirm。
104
+ * 返回 1 = 用户确认;文件类选中路径经 cj_bridge_set_dialog_callback 注册的回调送回。
105
+ */
106
+ @When[os == "Windows"]
107
+ @C
108
+ foreign func cj_bridge_show_dialog(h: CPointer<Unit>, kind: Int32, title: CString, message: CString,
109
+ filter: CString): Int32
110
+
111
+ @When[os == "Windows"]
112
+ @C
113
+ foreign func cj_bridge_run_js(h: CPointer<Unit>, js: CString): Unit
114
+
115
+ @When[os == "Windows"]
116
+ @C
117
+ foreign func cj_bridge_quit(h: CPointer<Unit>): Unit
118
+
119
+ @When[os == "Windows"]
120
+ @C
121
+ foreign func cj_bridge_is_ready(h: CPointer<Unit>): Int32
122
+
123
+ @When[os == "Windows"]
124
+ @C
125
+ foreign func cj_bridge_should_quit(h: CPointer<Unit>): Int32
126
+
127
+ /**
128
+ * 菜单栏(RFC-002 §5.4 的线路文本):空串 = 清空。未就绪时桥只记下,建窗时一次建好。
129
+ */
130
+ @When[os == "Windows"]
131
+ @C
132
+ foreign func cj_bridge_set_menu(h: CPointer<Unit>, wire: CString): Unit
133
+
134
+ /**
135
+ * 运行期改单个菜单项的状态(按 id 找;找不到只记日志)
136
+ */
137
+ @When[os == "Windows"]
138
+ @C
139
+ foreign func cj_bridge_set_menu_item_state(h: CPointer<Unit>, id: CString, enabled: Int32,
140
+ checked: Int32): Unit
141
+
142
+ /**
143
+ * 窗口顶部副标题:Windows 侧如实降级(返回 -4,能力位 CJ_CAP_SUBTITLE 不置位)。
144
+ * 同名同签名保留是为了上层不出现平台分支(AGENTS §2 的第一条)。
145
+ */
146
+ @When[os == "Windows"]
147
+ @C
148
+ foreign func cj_bridge_set_titlebar_subtitle(h: CPointer<Unit>, text: CString): Int32
149
+
150
+ /**
151
+ * 拖放策略(RFC-002 §5):Windows 侧当前为桩(能力位如实不置位),同名同签名保上层零平台分支
152
+ */
153
+ @When[os == "Windows"]
154
+ @C
155
+ foreign func cj_bridge_set_drag_drop(h: CPointer<Unit>, enabled: Int32, files: Int32): Unit
156
+
157
+ /**
158
+ * 全局快捷键(RFC-003 第 5 期):Windows 侧走 `RegisterHotKey`(见 native/platforms/windows/
159
+ * bridge_win.c),wire 词法与 Linux 同规(Ctrl/Alt/Shift/Super + 常用键名),触发经 on_shell 回投
160
+ */
161
+ @When[os == "Windows"]
162
+ @C
163
+ foreign func cj_bridge_set_global_hotkeys(h: CPointer<Unit>, wire: CString): Unit
164
+
165
+ /**
166
+ * 托盘(RFC-002 §7.C):Windows 侧走 `Shell_NotifyIconW`(菜单是 CreatePopupMenu,只弹不挂窗口),
167
+ * 点击与 Linux 同信封回投 `{"kind":"tray","id":…}`
168
+ */
169
+ @When[os == "Windows"]
170
+ @C
171
+ foreign func cj_bridge_set_tray(h: CPointer<Unit>, wire: CString): Unit
172
+
173
+ /**
174
+ * 窗口可见性(`show()` / `hide()` 的 C 侧落点):start 之前调只记 pending,就绪后投 UI 线程执行
175
+ */
176
+ @When[os == "Windows"]
177
+ @C
178
+ foreign func cj_bridge_set_window_visible(h: CPointer<Unit>, visible: Int32): Unit
179
+
180
+ /**
181
+ * 最小化到托盘策略位(须在 start 之前调):窗口过程据此把「最小化」改写成「隐藏」
182
+ */
183
+ @When[os == "Windows"]
184
+ @C
185
+ foreign func cj_bridge_set_minimize_to_tray(h: CPointer<Unit>, enabled: Int32): Unit
186
+
187
+ /**
188
+ * 把 h 挂到 owner 下(owned window):不重复占任务栏、跟随属主显隐
189
+ *
190
+ * 与 set_window_visible 同形——都要求两个句柄都已创建;owner 为空指针时桥侧不动。
191
+ */
192
+ @When[os == "Windows"]
193
+ @C
194
+ foreign func cj_bridge_set_owner_window(h: CPointer<Unit>, owner: CPointer<Unit>): Unit
195
+
196
+ /**
197
+ * shell 事件回调(菜单点击等):首参是宿主句柄,多窗口靠它分辨事件属于哪个窗口
198
+ */
199
+ @When[os == "Windows"]
200
+ @C
201
+ foreign func cj_bridge_set_shell_callback(h: CPointer<Unit>,
202
+ cb: CFunc<(CPointer<Unit>, CString) -> Unit>): Unit
203
+
204
+ /**
205
+ * 宿主能力位(CJ_CAP_* 的按位或):未 init 返回 0
206
+ */
207
+ @When[os == "Windows"]
208
+ @C
209
+ foreign func cj_bridge_host_capabilities(h: CPointer<Unit>): Int32
210
+
211
+ /**
212
+ * 句柄同一性判定(返回 1 = 同一个宿主):仓颉侧 CPointer 不能比较,判定交回 C 侧
213
+ */
214
+ @When[os == "Windows"]
215
+ @C
216
+ foreign func cj_bridge_host_eq(a: CPointer<Unit>, b: CPointer<Unit>): Int32
217
+
218
+ /**
219
+ * Windows WebView2 宿主实现:通过 C 桥驱动
220
+ */
221
+ @When[os == "Windows"]
222
+ public class WebView2Host <: WebViewHost {
223
+ // 消息回调:宿主线程(原生 Win32 消息循环)→ 仓颉
224
+ public var onMessage: (String) -> Unit = { _ => }
225
+ // 销毁回调(应用经 setDestroyHandler 装配;由 C 桥按句柄认领,多窗口不串)
226
+ public var onDestroy: () -> Unit = { => }
227
+ // 本窗口文件对话框的结果:C 桥按句柄经路由回调写入(两窗各写各的,不许串台)
228
+ private var dialogResult: String = ""
229
+ // 本窗口的 shell 事件(菜单点击等)回调:TauriApp 装配时经 setShellHandler 覆盖它做分发,
230
+ // 未覆盖时只留一行 stderr——菜单点了没人管,至少能在日志里看见。
231
+ public var shellHandler: (String) -> Unit = { json =>
232
+ eprintln("[cj-tauri] shell event (no handler): ${json}")
233
+ }
234
+ // 窗口配置(标题 / 尺寸),start() 前由 TauriApp 注入
235
+ private var config: WindowConfig = WindowConfig()
236
+ // 预执行脚本(插件 jsShim 等):start 前收集,prepareHost 一次性交给桥,桥在 document-start 注入
237
+ private var initScripts = ArrayList<String>()
238
+ // 宿主编(C 侧分配):空指针 = 尚未创建;dispose() 后置空
239
+ private var handle: CPointer<Unit> = CPointer<Unit>()
240
+ // 本窗口的 label(字段名不叫 label:接口有同名方法)。单窗口阶段是常量,
241
+ // 与 TauriApp 注册的 WindowRecord.label 一致;多窗口时由构造参数给定。
242
+ private let windowLabel: String
243
+ // 资产解析器:装配层在 load 前注入(None = 按 cwd 相对路径读盘,与批次 1 行为等价)
244
+ private var assetResolver: Option<AssetResolver> = None
245
+
246
+ public init() {
247
+ this.windowLabel = DEFAULT_WINDOW_LABEL
248
+ }
249
+
250
+ /**
251
+ * 带 label 构造(多窗口):label 必须与 TauriApp 注册表里登记的那个一致,
252
+ * 页面侧的 `window.__CJ_TAURI_LABEL__` 也取同一个值(由 TauriApp 注入)。
253
+ */
254
+ public init(label: String) {
255
+ this.windowLabel = label
256
+ }
257
+
258
+ public func label(): String {
259
+ return this.windowLabel
260
+ }
261
+
262
+ public func setMessageHandler(handler: (String) -> Unit): Unit {
263
+ this.onMessage = handler
264
+ }
265
+
266
+ public func setWindowConfig(config: WindowConfig): Unit {
267
+ this.config = config
268
+ }
269
+
270
+ public func setDevTools(enabled: Bool): Unit {
271
+ this.config.devTools = enabled
272
+ }
273
+
274
+ public func addInitScript(js: String): Unit {
275
+ // 空串忽略(插件没写 shim 时不必占位,桥侧也会再挡一次)
276
+ if (js.size > 0) {
277
+ this.initScripts.add(js)
278
+ }
279
+ }
280
+
281
+ public func openDevTools(): Unit {
282
+ unsafe {
283
+ cj_bridge_open_devtools(this.handle)
284
+ }
285
+ }
286
+
287
+ // ===== 同窗内嵌视图(PLT-006):桥侧把动作投回 UI 线程,这里只是转发 =====
288
+
289
+ public func createEmbeddedView(url: String, x: Int32, y: Int32, width: Int32, height: Int32): Int64 {
290
+ // 窗口没就绪时桥会如实回 0 并打一行 stderr(不是崩溃路径)
291
+ this.ensureHandle()
292
+ unsafe {
293
+ return cj_bridge_view_create(this.handle, LibC.mallocCString(url), x, y, width, height)
294
+ }
295
+ }
296
+
297
+ public func setEmbeddedViewVisible(view: Int64, visible: Bool): Unit {
298
+ var flag: Int32 = 0
299
+ if (visible) {
300
+ flag = 1
301
+ }
302
+ unsafe {
303
+ cj_bridge_view_set_visible(this.handle, view, flag)
304
+ }
305
+ }
306
+
307
+ public func setEmbeddedViewBounds(view: Int64, x: Int32, y: Int32, width: Int32, height: Int32): Unit {
308
+ unsafe {
309
+ cj_bridge_view_set_bounds(this.handle, view, x, y, width, height)
310
+ }
311
+ }
312
+
313
+ public func navigateEmbeddedView(view: Int64, url: String): Unit {
314
+ unsafe {
315
+ cj_bridge_view_navigate(this.handle, view, LibC.mallocCString(url))
316
+ }
317
+ }
318
+
319
+ public func destroyEmbeddedView(view: Int64): Unit {
320
+ unsafe {
321
+ cj_bridge_view_destroy(this.handle, view)
322
+ }
323
+ }
324
+
325
+ public func setMenu(wire: String): Unit {
326
+ // 句柄先建出来:setMenu 允许在 start() 之前调——文本由桥记着,建窗时一次建好
327
+ this.ensureHandle()
328
+ unsafe {
329
+ cj_bridge_set_menu(this.handle, LibC.mallocCString(wire))
330
+ }
331
+ }
332
+
333
+ public func setMenuItemState(id: String, enabled: Bool, checked: Bool): Unit {
334
+ if (this.handle.isNull()) {
335
+ // 还没建宿主就谈不上「改状态」:静默返回,不拿空句柄去 FFI
336
+ return
337
+ }
338
+ var e: Int32 = 0
339
+ var c: Int32 = 0
340
+ if (enabled) {
341
+ e = 1
342
+ }
343
+ if (checked) {
344
+ c = 1
345
+ }
346
+ unsafe {
347
+ cj_bridge_set_menu_item_state(this.handle, LibC.mallocCString(id), e, c)
348
+ }
349
+ }
350
+
351
+ public func setTitlebarSubtitle(text: String): Unit {
352
+ if (this.handle.isNull()) {
353
+ return
354
+ }
355
+ unsafe {
356
+ // 本平台如实降级(桥返回 -4,能力位不含 CJ_CAP_SUBTITLE):这里不当作错误刷屏,
357
+ // 应用据 hostCapabilities().subtitle 自己就不会依赖这条通道
358
+ let _ = cj_bridge_set_titlebar_subtitle(this.handle, LibC.mallocCString(text))
359
+ }
360
+ }
361
+
362
+ public func setDragDropPolicy(enabled: Bool, files: Bool): Unit {
363
+ // 与 setMenu 同形:允许在 start() 之前调。Windows 侧桥当前是桩(能力位如实不置位),
364
+ // 但仓颉侧 API 与调用点不写平台分支——实机轮补 IDropTarget 后上层零改动
365
+ this.ensureHandle()
366
+ var en: Int32 = 0
367
+ var fi: Int32 = 0
368
+ if (enabled) {
369
+ en = 1
370
+ }
371
+ if (files) {
372
+ fi = 1
373
+ }
374
+ unsafe {
375
+ cj_bridge_set_drag_drop(this.handle, en, fi)
376
+ }
377
+ }
378
+
379
+ public func setGlobalHotkeys(wire: String): Unit {
380
+ // 与 setMenu 同形:允许在 start() 之前调。Windows 侧桥当前是桩(能力位如实不置位),
381
+ // 仓颉侧 API 不写平台分支——实机轮补 RegisterHotKey 后上层零改动
382
+ this.ensureHandle()
383
+ unsafe {
384
+ cj_bridge_set_global_hotkeys(this.handle, LibC.mallocCString(wire))
385
+ }
386
+ }
387
+
388
+ public func setTray(wire: String): Unit {
389
+ // 与 setMenu 同形:允许在 start() 之前调。Windows 侧桥当前是桩(能力位如实不置位),
390
+ // 仓颉侧 API 不写平台分支——实机轮补 Shell_NotifyIconW 后上层零改动
391
+ this.ensureHandle()
392
+ unsafe {
393
+ cj_bridge_set_tray(this.handle, LibC.mallocCString(wire))
394
+ }
395
+ }
396
+
397
+ public func setOwnerWindow(ownerLabel: String): Unit {
398
+ if (ownerLabel.size == 0) {
399
+ return
400
+ }
401
+ // 与 setWindowVisible 同形:允许在 start() 之前调(桥记 pending,建窗时定稿)
402
+ this.ensureHandle()
403
+ match (HostGlobals.handleOfLabel(ownerLabel)) {
404
+ case Some(ownerHandle) =>
405
+ unsafe {
406
+ cj_bridge_set_owner_window(this.handle, ownerHandle)
407
+ }
408
+ case None =>
409
+ // 外观修饰失败不该拦着开窗,但要在日志里留凭证
410
+ eprintln("[cj-tauri] setOwnerWindow(${ownerLabel}): 该窗口尚未登记,跳过")
411
+ }
412
+ }
413
+
414
+ public func setWindowVisible(visible: Bool): Unit {
415
+ // 与 setTray 同形:允许在 start() 之前调(桥记 pending,建窗时定稿);就绪后投宿主 UI 线程
416
+ this.ensureHandle()
417
+ var v: Int32 = 0
418
+ if (visible) {
419
+ v = 1
420
+ }
421
+ unsafe {
422
+ cj_bridge_set_window_visible(this.handle, v)
423
+ }
424
+ }
425
+
426
+ public func setMinimizeToTray(enabled: Bool): Unit {
427
+ // 策略位:窗口过程要在建窗时就拿到它,所以只在 start() 之前调有意义(与 setDragDropPolicy 同规)
428
+ this.ensureHandle()
429
+ var e: Int32 = 0
430
+ if (enabled) {
431
+ e = 1
432
+ }
433
+ unsafe {
434
+ cj_bridge_set_minimize_to_tray(this.handle, e)
435
+ }
436
+ }
437
+
438
+ public func hostCapabilities(): Int32 {
439
+ if (this.handle.isNull()) {
440
+ return 0
441
+ }
442
+ var caps: Int32 = 0
443
+ unsafe {
444
+ caps = cj_bridge_host_capabilities(this.handle)
445
+ }
446
+ return caps
447
+ }
448
+
449
+ public func setShellHandler(handler: (String) -> Unit): Unit {
450
+ this.shellHandler = handler
451
+ }
452
+
453
+ public func setDestroyHandler(handler: () -> Unit): Unit {
454
+ this.onDestroy = handler
455
+ }
456
+
457
+ public func showFileDialog(save: Bool, title: String, filter: String): String {
458
+ return this.showFileDialogPreset(save, title, filter, "")
459
+ }
460
+
461
+ public func showFileDialogPreset(save: Bool, title: String, filter: String,
462
+ defaultName: String): String {
463
+ var kind: Int32 = 0
464
+ if (save) {
465
+ kind = 1
466
+ }
467
+ var ok: Int32 = 0
468
+ unsafe {
469
+ // defaultName 经 message 参数通道交给桥(文件类对话框把它当预置文件名)
470
+ ok = cj_bridge_show_dialog(this.handle, kind, LibC.mallocCString(title),
471
+ LibC.mallocCString(defaultName), LibC.mallocCString(filter))
472
+ }
473
+ if (ok == 0) {
474
+ // 取消 / 宿主未就绪:上游按「空路径」处理,不再读结果位
475
+ return ""
476
+ }
477
+ return this.dialogResult
478
+ }
479
+
480
+ public func showFolderDialog(title: String): String {
481
+ var ok: Int32 = 0
482
+ unsafe {
483
+ // 选目录:kind=6(平台侧走 IFileOpenDialog + FOS_PICKFOLDERS);message 通道不参与
484
+ ok = cj_bridge_show_dialog(this.handle, 6, LibC.mallocCString(title),
485
+ LibC.mallocCString(""), LibC.mallocCString(""))
486
+ }
487
+ if (ok == 0) {
488
+ // 取消 / 宿主未就绪:上游按「空路径」处理
489
+ return ""
490
+ }
491
+ return this.dialogResult
492
+ }
493
+
494
+ public func showMessageDialog(kind: String, title: String, message: String): Bool {
495
+ var ok: Int32 = 0
496
+ unsafe {
497
+ ok = cj_bridge_show_dialog(this.handle, dialogKindCode(kind), LibC.mallocCString(title),
498
+ LibC.mallocCString(message), LibC.mallocCString(""))
499
+ }
500
+ return ok != 0
501
+ }
502
+
503
+ public func clipboardWriteText(text: String): Bool {
504
+ var rc: Int32 = -1
505
+ unsafe {
506
+ rc = cj_bridge_clipboard_write_text(this.handle, LibC.mallocCString(text))
507
+ }
508
+ if (rc != 0) {
509
+ eprintln("[cj-tauri] clipboard write failed: code=${rc}")
510
+ }
511
+ return rc == 0
512
+ }
513
+
514
+ public func clipboardReadText(): Option<String> {
515
+ let cs = unsafe { cj_bridge_clipboard_read_text(this.handle) }
516
+ if (cs.isNull()) {
517
+ return None<String> // 空 / 非文本 / 平台失败,协议层不区分
518
+ }
519
+ let text = cs.toString()
520
+ unsafe {
521
+ cj_bridge_clipboard_free(cs) // 归还缓冲:malloc 家族分配,必须走桥的 free
522
+ }
523
+ return Some(text)
524
+ }
525
+
526
+ public func notify(title: String, body: String): Bool {
527
+ var rc: Int32 = -1
528
+ unsafe {
529
+ rc = cj_bridge_notification_notify(this.handle, LibC.mallocCString(title),
530
+ LibC.mallocCString(body))
531
+ }
532
+ if (rc != 0) {
533
+ // Windows 桩阶段返回 -4(未实现):静态事实,stderr 一行说清,不算异常
534
+ eprintln("[cj-tauri] notification failed: code=${rc}")
535
+ }
536
+ return rc == 0
537
+ }
538
+
539
+ /**
540
+ * 创建宿主编(幂等):首次启动时分配,dispose() 后置空
541
+ */
542
+ private func ensureHandle(): Unit {
543
+ if (this.handle.isNull()) {
544
+ unsafe {
545
+ this.handle = cj_bridge_create()
546
+ }
547
+ }
548
+ }
549
+
550
+ /**
551
+ * 启动前的共同装配:宿主编、回调槽位、窗口配置、图标
552
+ */
553
+ private func prepareHost(): Unit {
554
+ this.ensureHandle()
555
+ HostGlobals.onMessage = this.onMessage
556
+ // 宿主级路由:「本句柄 → 本实例的三个回调」登记一条(表式,不覆盖别的窗口)
557
+ HostGlobals.registerHostRoute(this.handle, { json => this.shellHandler(json) },
558
+ { => this.onDestroy() },
559
+ { path => this.dialogResult = path })
560
+ // label → 句柄:附庸窗(setOwnerWindow)按 label 找属主窗,登记点与路由表同步
561
+ HostGlobals.registerHostHandle(this.windowLabel, this.handle)
562
+ var devTools: Int32 = 0
563
+ if (this.config.devTools) {
564
+ devTools = 1
565
+ }
566
+ unsafe {
567
+ cj_bridge_init(this.handle, hostOnMessage, hostOnDestroy)
568
+ // shell 事件回调(菜单点击等):与消息回调同一套路,多一个首参用来分辨窗口
569
+ cj_bridge_set_shell_callback(this.handle, shellOnEvent)
570
+ // 原生对话框的结果回调:与消息回调同一套路,多一个首参(结果只落发起这次对话框的窗口)
571
+ cj_bridge_set_dialog_callback(this.handle, dialogOnResult)
572
+ cj_bridge_set_window(this.handle, LibC.mallocCString(this.config.title),
573
+ Int32(this.config.width), Int32(this.config.height))
574
+ cj_bridge_set_devtools(this.handle, devTools)
575
+ // 窗口图标:空串表示系统默认图标
576
+ cj_bridge_set_icon(this.handle, LibC.mallocCString(this.config.iconPath))
577
+ // 预执行脚本(插件 jsShim 等):与 BRIDGE_JS 同一条 document-start 通道,按注册顺序排在桥之后
578
+ for (js in this.initScripts) {
579
+ cj_bridge_add_init_script(this.handle, LibC.mallocCString(js))
580
+ }
581
+ }
582
+ }
583
+
584
+ /**
585
+ * 加载页面(唯一入口,RFC §4.3):Inline/Url/DevServer 直接映射;
586
+ * Asset 经注入的 resolver 读盘,未注入时按 cwd 相对路径读(批次 1 的桌面行为)
587
+ */
588
+ public func load(source: PageSource): Unit {
589
+ match (resolvePage(source, this.assetResolver)) {
590
+ case StartHtml(html) => this.start(html)
591
+ case StartUrl(url) => this.startUrl(url)
592
+ }
593
+ }
594
+
595
+ /** 注入资产解析器(装配层在 load 前调用) */
596
+ public func setAssetResolver(resolver: Option<AssetResolver>): Unit {
597
+ this.assetResolver = resolver
598
+ }
599
+
600
+ public func start(html: String): Unit {
601
+ this.prepareHost()
602
+ unsafe {
603
+ cj_bridge_start(this.handle, LibC.mallocCString(html))
604
+ }
605
+ }
606
+
607
+ public func startUrl(url: String): Unit {
608
+ this.prepareHost()
609
+ unsafe {
610
+ cj_bridge_load_url(this.handle, LibC.mallocCString(url))
611
+ // 空串表示「页面由 cj_bridge_load_url 指定」(同一次启动里 URL 优先)
612
+ cj_bridge_start(this.handle, LibC.mallocCString(""))
613
+ }
614
+ }
615
+
616
+ public func reload(): Unit {
617
+ unsafe {
618
+ cj_bridge_reload(this.handle)
619
+ }
620
+ }
621
+
622
+ public func runJs(js: String): Unit {
623
+ unsafe {
624
+ cj_bridge_run_js(this.handle, LibC.mallocCString(js))
625
+ }
626
+ }
627
+
628
+ public func quit(): Unit {
629
+ unsafe {
630
+ cj_bridge_quit(this.handle)
631
+ }
632
+ }
633
+
634
+ public func shouldQuit(): Bool {
635
+ unsafe {
636
+ cj_bridge_should_quit(this.handle) != 0
637
+ }
638
+ }
639
+
640
+ /**
641
+ * 释放宿主编(幂等,未创建过则是空操作):事件循环退出后调用一次,之后句柄置空、本实例不可再启动
642
+ */
643
+ public func dispose(): Unit {
644
+ if (this.handle.isNull()) {
645
+ return
646
+ }
647
+ unsafe {
648
+ cj_bridge_destroy(this.handle)
649
+ }
650
+ this.handle = CPointer<Unit>()
651
+ }
652
+ }
653
+
654
+ // C 回调中转:C 桥线程 → 仓颉闭包(经全局槽位)
655
+ @When[os == "Windows"]
656
+ @C
657
+ func hostOnMessage(json: CString): Unit {
658
+ try {
659
+ HostGlobals.onMessage(json.toString())
660
+ } catch (e: Exception) {
661
+ eprintln("[cj-tauri] host callback error: ${e}")
662
+ }
663
+ }