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.
- package/README.md +3 -3
- package/cjpm.toml +52 -25
- package/cli/cjpm.toml +1 -1
- package/cli/src/doctor.cj +304 -0
- package/cli/src/main.cj +33 -7
- package/cli/src/project.cj +59 -1
- package/cli/src/resolve.cj +348 -304
- package/cli/src/scaffold.cj +139 -22
- package/cli/templates/ohos-pc/AppScope/app.json5 +13 -0
- package/cli/templates/ohos-pc/AppScope/resources/base/element/string.json +8 -0
- package/cli/templates/ohos-pc/AppScope/resources/base/media/background.png +0 -0
- package/cli/templates/ohos-pc/AppScope/resources/base/media/foreground.png +0 -0
- package/cli/templates/ohos-pc/AppScope/resources/base/media/layered_image.json +6 -0
- package/cli/templates/ohos-pc/README.md +79 -0
- package/cli/templates/ohos-pc/build-hap.sh +190 -0
- package/cli/templates/ohos-pc/build-profile.json5 +46 -0
- package/cli/templates/ohos-pc/entry/build-profile.json5 +26 -0
- package/cli/templates/ohos-pc/entry/cjpm.toml +43 -0
- package/cli/templates/ohos-pc/entry/hvigorfile.ts +6 -0
- package/cli/templates/ohos-pc/entry/oh-package.json5 +9 -0
- package/cli/templates/ohos-pc/entry/src/main/cangjie/ability_stage.cj +17 -0
- package/cli/templates/ohos-pc/entry/src/main/cangjie/app_host.cj +94 -0
- package/cli/templates/ohos-pc/entry/src/main/cangjie/index.cj +167 -0
- package/cli/templates/ohos-pc/entry/src/main/cangjie/main_ability.cj +139 -0
- package/cli/templates/ohos-pc/entry/src/main/module.json5 +49 -0
- package/cli/templates/ohos-pc/entry/src/main/resources/base/element/color.json +8 -0
- package/cli/templates/ohos-pc/entry/src/main/resources/base/element/string.json +16 -0
- package/cli/templates/ohos-pc/entry/src/main/resources/base/media/background.png +0 -0
- package/cli/templates/ohos-pc/entry/src/main/resources/base/media/foreground.png +0 -0
- package/cli/templates/ohos-pc/entry/src/main/resources/base/media/layered_image.json +6 -0
- package/cli/templates/ohos-pc/entry/src/main/resources/base/media/startIcon.png +0 -0
- package/cli/templates/ohos-pc/entry/src/main/resources/base/profile/main_pages.json +2 -0
- package/cli/templates/ohos-pc/entry/src/main/resources/rawfile/capabilities.json +6 -0
- package/cli/templates/ohos-pc/entry/src/main/resources/rawfile/index.html +92 -0
- package/cli/templates/ohos-pc/hvigor/hvigor-config.json5 +5 -0
- package/cli/templates/ohos-pc/hvigorfile.ts +6 -0
- package/cli/templates/ohos-pc/oh-package.json5 +10 -0
- package/native/build_linux.sh +30 -2
- package/native/build_win.bat +62 -61
- package/native/core/bridge_core.c +1626 -0
- package/native/{bridge_core.h → core/bridge_core.h} +246 -6
- package/native/platforms/harmonyos/README.md +106 -0
- package/native/platforms/linux/bridge_linux.c +2485 -0
- package/native/platforms/windows/bridge_win.c +3248 -0
- package/native/tests/test_bridge_core.c +528 -1
- package/package.json +12 -3
- package/prebuilt/win32-x64/cj-tauri.exe +0 -0
- package/src/api_system.cj +10 -2
- package/src/app.cj +248 -85
- package/src/arkweb_proxy.cj +56 -0
- package/src/arkweb_script.cj +154 -0
- package/src/capability.cj +140 -0
- package/src/clipboard_bridge.cj +55 -0
- package/src/dragdrop.cj +26 -0
- package/src/embedded_assets.cj +98 -0
- package/src/hexutil.cj +53 -0
- package/src/host.cj +197 -26
- package/src/host_arkweb.cj +740 -0
- package/src/host_factory.cj +48 -0
- package/src/host_webkit.cj +202 -26
- package/src/host_webview2.cj +176 -3
- package/src/hotkey.cj +66 -0
- package/src/ipc_hub.cj +49 -0
- package/src/ipc_message.cj +2 -1
- package/src/jsutil.cj +33 -0
- package/src/log_bridge.cj +107 -0
- package/src/logging.cj +127 -0
- package/src/menu.cj +197 -196
- package/src/plugin.cj +40 -0
- package/src/plugins/clipboard/plugin_clipboard.cj +144 -0
- package/src/{plugin_dialog.cj → plugins/dialog/plugin_dialog.cj} +74 -17
- package/src/plugins/fs/plugin_fs.cj +194 -0
- package/src/plugins/http/plugin_http.cj +459 -0
- package/src/plugins/log/plugin_log.cj +105 -0
- package/src/{plugin_menu.cj → plugins/menu/plugin_menu.cj} +4 -2
- package/src/plugins/mqtt/mqtt_codec.cj +661 -0
- package/src/plugins/mqtt/plugin_mqtt.cj +1446 -0
- package/src/plugins/notification/plugin_notification.cj +146 -0
- package/src/plugins/plugins.cj +5 -0
- package/src/plugins/redis/plugin_redis.cj +2409 -0
- package/src/plugins/redis/redis_codec.cj +556 -0
- package/src/{plugin_serial.cj → plugins/serial/plugin_serial.cj} +442 -489
- package/src/{serial_bridge.cj → plugins/serial/serial_bridge.cj} +185 -184
- package/src/{plugin_shell.cj → plugins/shell/plugin_shell.cj} +230 -228
- package/src/plugins/sqlite/plugin_sqlite.cj +697 -0
- package/src/plugins/sqlite/sqlite_ffi.cj +206 -0
- package/src/plugins/store/plugin_store.cj +287 -0
- package/src/plugins/ws/plugin_ws.cj +470 -0
- package/src/process_hidden.cj +174 -0
- package/src/scope.cj +391 -0
- package/src/shell_event.cj +69 -68
- package/src/tests/capability_test.cj +53 -0
- package/src/tests/fake_host.cj +85 -20
- package/src/tests/ipc_hub_test.cj +13 -0
- package/src/tests/jsutil_test.cj +62 -0
- package/src/tests/logging_test.cj +62 -0
- package/src/tests/mqtt_codec_test.cj +405 -0
- package/src/tests/plugin_clipboard_test.cj +222 -0
- package/src/tests/plugin_dialog_test.cj +98 -4
- package/src/tests/plugin_http_test.cj +124 -0
- package/src/tests/plugin_menu_test.cj +1 -0
- package/src/tests/plugin_notification_test.cj +205 -0
- package/src/tests/plugin_serial_test.cj +269 -267
- package/src/tests/plugin_shell_test.cj +189 -188
- package/src/tests/plugin_sqlite_test.cj +230 -0
- package/src/tests/plugin_store_test.cj +260 -0
- package/src/tests/plugin_test.cj +542 -524
- package/src/tests/plugin_ws_test.cj +40 -0
- package/src/tests/process_hidden_test.cj +129 -0
- package/src/tests/redis_codec_test.cj +396 -0
- package/src/tests/scope_test.cj +350 -0
- package/src/tests/shell_event_test.cj +0 -1
- package/src/tests/system_host_test.cj +81 -79
- package/src/tests/urlutil_test.cj +53 -0
- package/src/tests/version_test.cj +75 -65
- package/src/tray.cj +90 -0
- package/src/urlutil.cj +159 -0
- package/src/version.cj +23 -2
- package/src/window.cj +51 -42
- package/native/bridge_core.c +0 -797
- package/native/bridge_linux.c +0 -1268
- package/native/bridge_win.c +0 -1633
- package/src/plugin_fs.cj +0 -118
- /package/native/{bridge_js.h → core/bridge_js.h} +0 -0
package/src/capability.cj
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
package cjTauri
|
|
2
2
|
|
|
3
3
|
import std.collection.*
|
|
4
|
+
import std.sync.*
|
|
4
5
|
import stdx.encoding.json.*
|
|
5
6
|
|
|
6
7
|
/**
|
|
@@ -29,6 +30,12 @@ public class Capability {
|
|
|
29
30
|
public let events: HashSet<String>
|
|
30
31
|
/** 引用到的命名权限集(原始名,如 "fs:readonly");展开规则见 CapabilityRegistry */
|
|
31
32
|
public let permissions: HashSet<String>
|
|
33
|
+
/**
|
|
34
|
+
* 路径 scope(RFC-003 第 4 期):插件名 → 路径白名单规则。
|
|
35
|
+
* 没有某插件的条目 = 该插件按旧行为全量放行(启动时有 stderr 提示);
|
|
36
|
+
* 有条目就走「先 deny 后 allow」的二次校验,见 src/scope.cj。
|
|
37
|
+
*/
|
|
38
|
+
public let scopes: HashMap<String, ScopeRule>
|
|
32
39
|
|
|
33
40
|
public init(identifier: String) {
|
|
34
41
|
this.identifier = identifier
|
|
@@ -36,6 +43,7 @@ public class Capability {
|
|
|
36
43
|
this.commands = HashSet<String>()
|
|
37
44
|
this.events = HashSet<String>()
|
|
38
45
|
this.permissions = HashSet<String>()
|
|
46
|
+
this.scopes = HashMap<String, ScopeRule>()
|
|
39
47
|
}
|
|
40
48
|
|
|
41
49
|
public func allowCommand(cmd: String): Unit {
|
|
@@ -147,6 +155,20 @@ public class Capability {
|
|
|
147
155
|
case _ => ()
|
|
148
156
|
}
|
|
149
157
|
}
|
|
158
|
+
// scopes:插件名 → 路径白名单(RFC-003 第 4 期)。键不认识也照收——
|
|
159
|
+
// 判定只在「插件真的来问」时发生,写错的键不会放行任何东西,只会让本该严的规则不生效
|
|
160
|
+
if (let Some(scopes) <- obj.get("scopes")) {
|
|
161
|
+
match (scopes.kind()) {
|
|
162
|
+
case JsonKind.JsObject =>
|
|
163
|
+
// JsonObject 没有 keys(),取 fields 这张表来遍历(plugin_store.cj 同样写法)
|
|
164
|
+
for ((k, v) in scopes.asObject().getFields()) {
|
|
165
|
+
if (let Some(rule) <- parseScopeRule(v)) {
|
|
166
|
+
cap.scopes.add(k, rule)
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
case _ => ()
|
|
170
|
+
}
|
|
171
|
+
}
|
|
150
172
|
return Some(cap)
|
|
151
173
|
} catch (e: Exception) {
|
|
152
174
|
return None
|
|
@@ -161,6 +183,12 @@ public class CapabilityRegistry {
|
|
|
161
183
|
private var caps: ArrayList<Capability> = ArrayList<Capability>()
|
|
162
184
|
// 命名权限集:集名("fs:readonly")→ 成员全名。由插件装配时注册(plugin.cj 的 registerPluginPermissionSets)
|
|
163
185
|
private var permissionSets: HashMap<String, HashSet<String>> = HashMap<String, HashSet<String>>()
|
|
186
|
+
// 运行期叠加的 scope 白名单(插件名 → 模式):由应用**取得用户明确确认后**经
|
|
187
|
+
// TauriApp.allowScopePattern 追加(如原生确认框问到「是」)。只在内存里,重启即失效——
|
|
188
|
+
// 持久化是应用自己的事(决定权在应用:写哪、写不写)。命令分发是异步的(worker 线程读
|
|
189
|
+
// scope),所以叠加与读取共用同一把锁。
|
|
190
|
+
private var runtimeScopeAllow: HashMap<String, ArrayList<String>> = HashMap<String, ArrayList<String>>()
|
|
191
|
+
private let scopeLock: Mutex = Mutex()
|
|
164
192
|
|
|
165
193
|
public init() {}
|
|
166
194
|
|
|
@@ -249,6 +277,118 @@ public class CapabilityRegistry {
|
|
|
249
277
|
return this.coveredByPermissionSet(event)
|
|
250
278
|
}
|
|
251
279
|
|
|
280
|
+
/**
|
|
281
|
+
* 某个插件的路径 scope(RFC-003 第 4 期):把所有清单里给它的规则**并集**起来。
|
|
282
|
+
* 返回 None = 没有任何清单为它写 scope → 调用方按旧行为全量放行。
|
|
283
|
+
*
|
|
284
|
+
* 为什么是并集而不是「逐清单分别判定」:与 canInvoke / canEmit 的口径一致——
|
|
285
|
+
* 现有能力模型就是「任一清单放行即放行」,scope 跟着同一条口径走才解释得清。
|
|
286
|
+
* (列级 + 窗口级 scopes 是 Tauri v2 更细的模型,本轮不做,见 RFC-003 第 4 期。)
|
|
287
|
+
*/
|
|
288
|
+
public func scopeForPlugin(plugin: String): Option<ScopeRule> {
|
|
289
|
+
var found = false
|
|
290
|
+
let merged = ScopeRule()
|
|
291
|
+
for (c in this.caps) {
|
|
292
|
+
match (c.scopes.get(plugin)) {
|
|
293
|
+
case Some(rule) =>
|
|
294
|
+
found = true
|
|
295
|
+
for (a in rule.allow) {
|
|
296
|
+
merged.allow.add(a)
|
|
297
|
+
}
|
|
298
|
+
for (d in rule.deny) {
|
|
299
|
+
merged.deny.add(d)
|
|
300
|
+
}
|
|
301
|
+
case None => ()
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
if (!found) {
|
|
305
|
+
return None
|
|
306
|
+
}
|
|
307
|
+
// 运行期叠加的白名单与清单成员同权(放在这里,是为了守住「注册中心是唯一判定口径」——
|
|
308
|
+
// 各插件的 scope 判定全都经 scopeForPlugin,不必各自再查一遍运行期表)。
|
|
309
|
+
this.mergeRuntimeAllow(plugin, merged)
|
|
310
|
+
return Some(merged)
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* 运行期给某插件的 scope 追加一条 allow 模式(插件名如 "http",模式写法同 `scope.cj`)。
|
|
315
|
+
*
|
|
316
|
+
* 用途:应用**取得用户明确确认后**(如弹原生确认框)放行一个清单里没有的主机,让本次会话当场可用。
|
|
317
|
+
* 判定顺序不变:**清单里的 deny 命中即拒,运行期加白掀不掉它**。
|
|
318
|
+
*
|
|
319
|
+
* **只收「具体值」,不收通配**:模式里出现 `*` 或 `?` 一律拒绝(返回 false + 一行 stderr)。
|
|
320
|
+
* 加白的语义是「往已有白名单里加一条」,而模式是页面可控字符串——一个通配透传进来就等价于全放行,
|
|
321
|
+
* 整条防线当场作废。这条不变量放在注册中心里强制,任何应用都绕不过去(D-021 的代价第 1 条)。
|
|
322
|
+
*/
|
|
323
|
+
public func addRuntimeScopeAllow(plugin: String, pattern: String): Bool {
|
|
324
|
+
if (!isConcreteScopeValue(pattern)) {
|
|
325
|
+
eprintln("[cj-tauri] runtime scope allow refused (${plugin}): pattern must be a concrete value, got '${pattern}'")
|
|
326
|
+
return false
|
|
327
|
+
}
|
|
328
|
+
synchronized(this.scopeLock) {
|
|
329
|
+
if (!this.runtimeScopeAllow.contains(plugin)) {
|
|
330
|
+
this.runtimeScopeAllow.add(plugin, ArrayList<String>())
|
|
331
|
+
}
|
|
332
|
+
let list = this.runtimeScopeAllow.get(plugin).getOrThrow()
|
|
333
|
+
if (!list.contains(pattern)) {
|
|
334
|
+
list.add(pattern)
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
return true
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* 运行期加白只收「不含通配的具体值」:`*` / `?` 会让模式退化成「全放行」,制表 / 换行 / 回车
|
|
342
|
+
* 则几乎总是「从文件或页面读回来时带了脏字符」,两类都拒(口径见 `addRuntimeScopeAllow`)。
|
|
343
|
+
* 注意 `String` 迭代出来的是 UTF-8 **字节**而不是码点,这里判的正是字节字面量。
|
|
344
|
+
*/
|
|
345
|
+
private func isConcreteScopeValue(pattern: String): Bool {
|
|
346
|
+
if (pattern.size == 0) {
|
|
347
|
+
return false
|
|
348
|
+
}
|
|
349
|
+
for (b in pattern) {
|
|
350
|
+
if (b == 42u8 || b == 63u8 || b == 9u8 || b == 10u8 || b == 13u8) {
|
|
351
|
+
return false
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
return true
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
/** 某插件的 scope 此刻是否放行某个值(清单 + 运行期叠加;没写 scope 的插件恒放行=旧行为) */
|
|
358
|
+
public func scopeAllowsValue(plugin: String, value: String): Bool {
|
|
359
|
+
match (this.scopeForPlugin(plugin)) {
|
|
360
|
+
case Some(rule) => return scopeAllows(rule, value)
|
|
361
|
+
case None => return true
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
/**
|
|
366
|
+
* 把运行期叠加项并进合并结果。**只有清单已为该插件写过 scope 时才并**:
|
|
367
|
+
* 没写 scope 的插件本来是全量放行(scopeForPlugin 返 None,调用方走旧行为),
|
|
368
|
+
* 这时把运行期条目并进去会把它意外收窄成「只放行这一条」。
|
|
369
|
+
*/
|
|
370
|
+
private func mergeRuntimeAllow(plugin: String, merged: ScopeRule): Unit {
|
|
371
|
+
synchronized(this.scopeLock) {
|
|
372
|
+
match (this.runtimeScopeAllow.get(plugin)) {
|
|
373
|
+
case Some(extra) =>
|
|
374
|
+
for (a in extra) {
|
|
375
|
+
merged.allow.add(a)
|
|
376
|
+
}
|
|
377
|
+
case None => ()
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
/** 是否有任何清单为某插件声明过 scope(启动提示与判定分支共用这一个口径) */
|
|
383
|
+
public func hasScopeForPlugin(plugin: String): Bool {
|
|
384
|
+
for (c in this.caps) {
|
|
385
|
+
if (c.scopes.contains(plugin)) {
|
|
386
|
+
return true
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
return false
|
|
390
|
+
}
|
|
391
|
+
|
|
252
392
|
public func size(): Int64 {
|
|
253
393
|
return this.caps.size
|
|
254
394
|
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
package cjTauri
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* 剪贴板 / 通知桥(plugin_clipboard、plugin_notification 的 FFI 面):
|
|
6
|
+
* C 桥 `cj_bridge_clipboard_*` / `cj_bridge_notification_*` 的声明与薄包装。
|
|
7
|
+
*
|
|
8
|
+
* 桌面两平台导出同名同签名(AGENTS §2 的硬约束),平台差异全在
|
|
9
|
+
* `native/bridge_*.c` 里——Linux 是 GTK clipboard + GDBus,Windows 是 Win32 clipboard +
|
|
10
|
+
* 通知桩(返回 -4 未实现)。所以仓颉侧一份声明就能同时编两个平台,上层不需要平台分支。
|
|
11
|
+
*
|
|
12
|
+
* 这组导出**收宿主句柄**:剪贴板与通知是宿主能力(GTK 环境依赖 / UI 线程投递),
|
|
13
|
+
* 与对话框同一类——必须等宿主就绪,句柄由 WebViewHost 持有。
|
|
14
|
+
*
|
|
15
|
+
* ## 鸿蒙侧为什么整段门禁掉(2026-10-19)
|
|
16
|
+
*
|
|
17
|
+
* 鸿蒙没有 C 桥产物(`native/platforms/harmonyos/` 目前只有契约),这四个符号无处解析。
|
|
18
|
+
* 留着的风险不是「编译报错」而是**模块加载期 SIGSEGV**——与 `log_bridge.cj` 那次
|
|
19
|
+
* 白屏三轮同源(AGENTS §4 最后一条:不带 `@When` 的核心 `foreign func` 在无桥平台
|
|
20
|
+
* 会留成未解析符号,`llvm-nm -D --undefined-only` 能一眼看出)。
|
|
21
|
+
*
|
|
22
|
+
* 门禁后鸿蒙侧**没有**这组导出,插件要剪贴板能力得走 `WebViewHost.clipboardWriteText` /
|
|
23
|
+
* `clipboardReadText`(`ArkWebHost` 目前如实返回 false / None,等 pasteboard 模块接上);
|
|
24
|
+
* 谁在这儿补回同名导出,就等于把「宿主能力位 = 0」这条如实约定破坏掉。
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* 写文本进系统剪贴板(8 MiB 上限在 C 侧 core 校验)。0 = 成功;-1 参数非法,-3 宿主未就绪 / 平台失败。
|
|
29
|
+
*/
|
|
30
|
+
@When[env != "ohos"]
|
|
31
|
+
@C
|
|
32
|
+
foreign func cj_bridge_clipboard_write_text(h: CPointer<Unit>, text: CString): Int32
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* 读系统剪贴板文本:返回 malloc 的 UTF-8 字符串(NULL = 剪贴板为空 / 非文本 / 平台失败)。
|
|
36
|
+
* 返回值必须经 `cj_bridge_clipboard_free` 释放——**不许**在仓颉侧 free(跨 CRT 堆不安全)。
|
|
37
|
+
*/
|
|
38
|
+
@When[env != "ohos"]
|
|
39
|
+
@C
|
|
40
|
+
foreign func cj_bridge_clipboard_read_text(h: CPointer<Unit>): CString
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* 释放 read_text 返回的缓冲(NULL 安全,幂等)
|
|
44
|
+
*/
|
|
45
|
+
@When[env != "ohos"]
|
|
46
|
+
@C
|
|
47
|
+
foreign func cj_bridge_clipboard_free(s: CString): Unit
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* 发一条系统通知(发后即忘:投递即返回,显示成败不回传)。
|
|
51
|
+
* 0 = 已提交;-1 参数非法(title 空 / 超长),-3 宿主未就绪 / 平台失败,-4 本平台未实现。
|
|
52
|
+
*/
|
|
53
|
+
@When[env != "ohos"]
|
|
54
|
+
@C
|
|
55
|
+
foreign func cj_bridge_notification_notify(h: CPointer<Unit>, title: CString, body: CString): Int32
|
package/src/dragdrop.cj
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
package cjTauri
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 拖放策略(RFC-002 §5 的 v1 落地):框架只负责「把拖进来的文件路径交出去」,
|
|
5
|
+
* 动作由页面决定(监听 `dragdrop:files` 事件,事件需在能力清单 events 里声明)。
|
|
6
|
+
*
|
|
7
|
+
* 策略必须在 `start()` / `startUrl()` 之前通过 `TauriApp.setDragDrop(...)` 定稿——
|
|
8
|
+
* Linux 侧要在建 view 时决定挂不挂 drag dest;不设置 = 不注册(默认关,现有应用零改动)。
|
|
9
|
+
*/
|
|
10
|
+
public class DragDropPolicy {
|
|
11
|
+
/** 关掉则平台层不注册 drop target(默认关) */
|
|
12
|
+
public let enabled: Bool
|
|
13
|
+
|
|
14
|
+
/** 接受文件路径(v1 唯一载荷;text 载荷留给后续版本,先不设开关) */
|
|
15
|
+
public let files: Bool
|
|
16
|
+
|
|
17
|
+
public init(enabled: Bool, files: Bool) {
|
|
18
|
+
this.enabled = enabled
|
|
19
|
+
this.files = files
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** 便捷构造:开文件拖入(v1 最常用形态) */
|
|
23
|
+
public static func filesEnabled(): DragDropPolicy {
|
|
24
|
+
return DragDropPolicy(true, true)
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
package cjTauri
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 内嵌资源释放(单文件包专用)。
|
|
5
|
+
*
|
|
6
|
+
* 背景:应用产物可以打成**单个 exe**(`scripts/pack-win-single.sh`),页面 / 能力清单 / vendor JS
|
|
7
|
+
* 都编成字符串,但**随包的外部程序**(例如 cjnote 的 cjh 后端)没法静态链接进 exe,只能作为 PE
|
|
8
|
+
* 资源(RT_RCDATA)跟着走;运行期由本函数取出写盘,应用再按得到的路径去起它。
|
|
9
|
+
*
|
|
10
|
+
* `dir` 由调用方给(框架不认识应用名),典型是 `%LOCALAPPDATA%\<应用名>\runtime`;
|
|
11
|
+
* **调用方负责先把目录建好**(桥只建最后一级)。
|
|
12
|
+
*
|
|
13
|
+
* 返回值:> 0 = 就绪的文件数(本次释放的 + 本来就在的);0 = 这个产物里没有内嵌资源
|
|
14
|
+
* (普通构建就是这样,**不是错误**);-1 = 参数为空或写盘失败(原因在 stderr)。
|
|
15
|
+
* 幂等:目标已在且大小与资源一致时桥会跳过,所以每次启动都可以调。
|
|
16
|
+
*/
|
|
17
|
+
public func extractEmbeddedAssets(dir: String): Int64 {
|
|
18
|
+
if (dir.isEmpty()) {
|
|
19
|
+
eprintln("[cj-tauri] extractEmbeddedAssets: 目录为空")
|
|
20
|
+
return -1
|
|
21
|
+
}
|
|
22
|
+
let n = extractAssetsBridge(dir)
|
|
23
|
+
if (n < 0) {
|
|
24
|
+
eprintln("[cj-tauri] 释放内嵌资源失败:${dir}")
|
|
25
|
+
}
|
|
26
|
+
return n
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* 鸿蒙侧没有 C 桥产物(`native/platforms/harmonyos/` 目前只有契约),照桌面那份写声明会让模块
|
|
31
|
+
* 加载期就 SIGSEGV(与 `log_bridge.cj` / `process_hidden.cj` 同一条坑,AGENTS §4)——按 `env` 分叉,
|
|
32
|
+
* 如实降级。判据用 `env` 而不是 `arch`:桌面 aarch64 Linux 也有 C 桥。
|
|
33
|
+
*/
|
|
34
|
+
@When[env != "ohos"]
|
|
35
|
+
@C
|
|
36
|
+
foreign func cj_bridge_extract_assets(dir: CString): Int32
|
|
37
|
+
|
|
38
|
+
@When[env != "ohos"]
|
|
39
|
+
func extractAssetsBridge(dir: String): Int64 {
|
|
40
|
+
var n: Int32 = 0
|
|
41
|
+
unsafe {
|
|
42
|
+
n = cj_bridge_extract_assets(LibC.mallocCString(dir))
|
|
43
|
+
}
|
|
44
|
+
return Int64(n)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
@When[env == "ohos"]
|
|
48
|
+
func extractAssetsBridge(dir: String): Int64 {
|
|
49
|
+
let _ = dir
|
|
50
|
+
eprintln("[cj-tauri] 本平台没有 C 桥,无内嵌资源可释放")
|
|
51
|
+
return 0
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* 按**绝对路径**预加载一个随包释放出来的动态库(单文件包专用)。
|
|
56
|
+
*
|
|
57
|
+
* 为什么需要它(2026-10-21 实测,另一台机器):仓颉运行时的随机数默认实现走 OpenSSL,而它按
|
|
58
|
+
* **裸名**找 `libcrypto-3-x64.dll` / `libssl-3-x64.dll`——SDK 与 stdx 都不带这两个,开发机上只是
|
|
59
|
+
* 恰好命中 PATH 里 Git 自带的那份。于是同一条 `ws://` 连接线,本机全通、别人双击就挂:
|
|
60
|
+
* `ws: connect failed: Can not load openssl library or function RAND_bytes.`
|
|
61
|
+
* (RFC6455 升级要算 `Sec-WebSocket-Key` 的随机 nonce,**明文 ws 也逃不掉**)。
|
|
62
|
+
*
|
|
63
|
+
* 只要进程内先按绝对路径加载一次,之后同类裸名加载就命中已加载模块:不必改 PATH,
|
|
64
|
+
* 也不必把 DLL 摆到 exe 旁边(单文件承诺不破)。
|
|
65
|
+
*
|
|
66
|
+
* `path` 必须是释放后的真实路径;返回 true = 已在进程内(含本来就在的)。
|
|
67
|
+
* 平台没有 C 桥时如实返回 false(`src/log_bridge.cj` 同款门禁)。
|
|
68
|
+
*/
|
|
69
|
+
public func preloadEmbeddedLibrary(path: String): Bool {
|
|
70
|
+
if (path.isEmpty()) {
|
|
71
|
+
eprintln("[cj-tauri] preloadEmbeddedLibrary: 路径为空")
|
|
72
|
+
return false
|
|
73
|
+
}
|
|
74
|
+
let ok = preloadLibraryBridge(path)
|
|
75
|
+
if (!ok) {
|
|
76
|
+
eprintln("[cj-tauri] 预加载动态库失败:${path}")
|
|
77
|
+
}
|
|
78
|
+
return ok
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
@When[env != "ohos"]
|
|
82
|
+
@C
|
|
83
|
+
foreign func cj_bridge_preload_library(path: CString): Int32
|
|
84
|
+
|
|
85
|
+
@When[env != "ohos"]
|
|
86
|
+
func preloadLibraryBridge(path: String): Bool {
|
|
87
|
+
var ok: Int32 = 0
|
|
88
|
+
unsafe {
|
|
89
|
+
ok = cj_bridge_preload_library(LibC.mallocCString(path))
|
|
90
|
+
}
|
|
91
|
+
return ok == 1
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
@When[env == "ohos"]
|
|
95
|
+
func preloadLibraryBridge(path: String): Bool {
|
|
96
|
+
let _ = path
|
|
97
|
+
return false
|
|
98
|
+
}
|
package/src/hexutil.cj
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
package cjTauri
|
|
2
|
+
|
|
3
|
+
// 共享的 hex 工具:serial / mqtt / redis 三个插件都要做「字节 ↔ 十六进制」换算,
|
|
4
|
+
// 放核心共享层(与 scope.cj 同判据:判定逻辑只在共享层,避免插件间互相引用导致连坐链接)。
|
|
5
|
+
|
|
6
|
+
let HEX_DIGITS: String = "0123456789abcdef"
|
|
7
|
+
|
|
8
|
+
/** 字节 → 小写十六进制(二进制帧给前端看的就是它) */
|
|
9
|
+
public func bytesToHex(bytes: Array<UInt8>): String {
|
|
10
|
+
let digits = HEX_DIGITS.toArray()
|
|
11
|
+
let out = Array<UInt8>(bytes.size * 2, repeat: 0)
|
|
12
|
+
var i = 0
|
|
13
|
+
while (i < bytes.size) {
|
|
14
|
+
let b = bytes[i]
|
|
15
|
+
out[i * 2] = digits[Int64((b >> 4) & UInt8(0x0F))]
|
|
16
|
+
out[i * 2 + 1] = digits[Int64(b & UInt8(0x0F))]
|
|
17
|
+
i++
|
|
18
|
+
}
|
|
19
|
+
return String.fromUtf8(out)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** 十六进制 → 字节;奇数长度或含非十六进制字符返回 None(调用方报可读错误) */
|
|
23
|
+
public func hexToBytes(text: String): Option<Array<UInt8>> {
|
|
24
|
+
let src = text.toArray()
|
|
25
|
+
if (src.size % 2 != 0) {
|
|
26
|
+
return None
|
|
27
|
+
}
|
|
28
|
+
let out = Array<UInt8>(src.size / 2, repeat: 0)
|
|
29
|
+
var i = 0
|
|
30
|
+
while (i < out.size) {
|
|
31
|
+
let hi = hexDigit(src[i * 2])
|
|
32
|
+
let lo = hexDigit(src[i * 2 + 1])
|
|
33
|
+
if (hi < 0 || lo < 0) {
|
|
34
|
+
return None
|
|
35
|
+
}
|
|
36
|
+
out[i] = UInt8(hi * 16 + lo)
|
|
37
|
+
i++
|
|
38
|
+
}
|
|
39
|
+
return Some(out)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
private func hexDigit(b: UInt8): Int64 {
|
|
43
|
+
if (b >= UInt8(48) && b <= UInt8(57)) {
|
|
44
|
+
return Int64(b - UInt8(48))
|
|
45
|
+
}
|
|
46
|
+
if (b >= UInt8(97) && b <= UInt8(102)) {
|
|
47
|
+
return Int64(b - UInt8(87))
|
|
48
|
+
}
|
|
49
|
+
if (b >= UInt8(65) && b <= UInt8(70)) {
|
|
50
|
+
return Int64(b - UInt8(55))
|
|
51
|
+
}
|
|
52
|
+
return -1
|
|
53
|
+
}
|