cj-tauri 1.1.2 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +137 -77
- package/bin/cj-tauri.js +287 -287
- package/cjpm.toml +13 -2
- package/cli/cjpm.toml +22 -13
- package/cli/src/doctor.cj +308 -304
- package/cli/src/main.cj +129 -121
- package/cli/src/manifest.cj +632 -0
- package/cli/src/manifest_test.cj +87 -0
- package/cli/src/project.cj +770 -754
- package/cli/src/resolve.cj +348 -348
- package/cli/src/scaffold.cj +46 -38
- package/cli/src/sync.cj +526 -0
- package/cli/src/util.cj +9 -0
- package/cli/templates/app/README.md +30 -59
- package/cli/templates/app/{ui → assets/ui}/index.html +2 -0
- package/cli/templates/app/cj-tauri.toml +32 -0
- package/cli/templates/app/cjpm.toml +21 -4
- package/cli/templates/app/src/main.cj +29 -16
- package/cli/templates/app-react/README.md +34 -45
- package/cli/templates/app-react/{ui → assets/ui}/index.html +25 -25
- package/cli/templates/app-react/{ui → assets/ui}/src/App.css +67 -67
- package/cli/templates/app-react/{ui → assets/ui}/src/App.jsx +124 -124
- package/cli/templates/app-react/cj-tauri.toml +33 -0
- package/cli/templates/app-react/cjpm.toml +21 -4
- package/cli/templates/app-react/src/main.cj +18 -29
- package/cli/templates/app-vue/README.md +34 -38
- package/cli/templates/app-vue/{ui → assets/ui}/index.html +25 -25
- package/cli/templates/app-vue/{ui → assets/ui}/src/App.vue +176 -176
- package/cli/templates/app-vue/cj-tauri.toml +33 -0
- package/cli/templates/app-vue/cjpm.toml +21 -4
- package/cli/templates/app-vue/src/main.cj +18 -29
- package/cli/templates/{ohos-pc → ohos-skeleton}/README.md +5 -0
- package/cli/templates/{ohos-pc → ohos-skeleton}/build-hap.sh +15 -0
- package/cli/templates/{ohos-pc → ohos-skeleton}/entry/cjpm.toml +8 -0
- package/cli/templates/ohos-skeleton/entry/src/main/cangjie/dialog_host.cj +217 -0
- package/cli/templates/ohos-skeleton/entry/src/main/cangjie/index.cj +527 -0
- package/cli/templates/{ohos-pc → ohos-skeleton}/entry/src/main/module.json5 +8 -0
- package/native/build_win.bat +62 -62
- package/native/core/bridge_core.c +1681 -1626
- package/native/core/bridge_core.h +675 -618
- package/native/core/bridge_js.h +108 -108
- package/native/platforms/harmonyos/README.md +115 -6
- package/native/platforms/linux/bridge_linux.c +2787 -2485
- package/native/platforms/windows/bridge_win.c +3740 -3248
- package/native/tests/gtk_threading_probe.c +143 -143
- package/native/tests/test_bridge_core.c +1339 -1228
- package/package.json +53 -51
- package/prebuilt/linux-x64/cj-tauri +0 -0
- package/src/api_system.cj +67 -67
- package/src/app.cj +325 -940
- package/src/app_events.cj +166 -0
- package/src/app_host_ops.cj +287 -0
- package/src/app_run.cj +283 -0
- package/src/app_spec.cj +76 -0
- package/src/arkweb_menu.cj +216 -0
- package/src/arkweb_script.cj +0 -12
- package/src/capability.cj +409 -409
- package/src/clipboard_bridge.cj +178 -0
- package/src/dialog_spec.cj +79 -0
- package/src/dragdrop.cj +26 -26
- package/src/embedded_assets.cj +98 -98
- package/src/host.cj +752 -604
- package/src/host_arkweb.cj +201 -17
- package/src/host_webkit.cj +663 -548
- package/src/host_webview2.cj +663 -548
- package/src/hotkey.cj +66 -66
- package/src/ipc_hub.cj +337 -337
- package/src/ipc_message.cj +286 -286
- package/src/launch.cj +62 -0
- package/src/logging.cj +127 -127
- package/src/menu.cj +197 -197
- package/src/platform/platform_app_context.cj +77 -0
- package/src/platform/platform_assets.cj +33 -0
- package/src/platform/platform_discover.cj +176 -0
- package/src/platform/platform_env.cj +29 -0
- package/src/platform/platform_feature.cj +31 -0
- package/src/platform/platform_page_source.cj +87 -0
- package/src/platform/platform_target_info.cj +32 -0
- package/src/platform_host_provider.cj +87 -0
- package/src/plugin.cj +333 -299
- package/src/plugins/clipboard/plugin_clipboard.cj +152 -144
- package/src/plugins/dialog/plugin_dialog.cj +209 -209
- package/src/plugins/fs/plugin_fs.cj +105 -1
- package/src/plugins/http/http_backend.cj +131 -0
- package/src/plugins/http/http_desktop.cj +195 -0
- package/src/plugins/http/http_ohos.cj +204 -0
- package/src/plugins/http/plugin_http.cj +310 -459
- package/src/plugins/log/plugin_log.cj +105 -105
- package/src/plugins/menu/plugin_menu.cj +8 -0
- package/src/plugins/notification/plugin_notification.cj +154 -146
- package/src/plugins/serial/plugin_serial.cj +17 -1
- package/src/plugins/serial/serial_bridge.cj +9 -1
- package/src/plugins/serial/serial_manager_ohos.cj +92 -0
- package/src/plugins/serial/serial_ohos.cj +529 -0
- package/src/plugins/shell/plugin_shell.cj +29 -1
- package/src/plugins/sqlite/plugin_sqlite.cj +8 -0
- package/src/plugins/ws/plugin_ws.cj +488 -470
- package/src/process_hidden.cj +174 -174
- package/src/scope.cj +391 -391
- package/src/shell_event.cj +69 -69
- package/src/tests/app_spec_test.cj +126 -0
- package/src/tests/capability_test.cj +243 -243
- package/src/tests/dialog_spec_test.cj +73 -0
- package/src/tests/fake_host.cj +201 -186
- package/src/tests/ipc_hub_test.cj +487 -487
- package/src/tests/logging_test.cj +62 -62
- package/src/tests/platform_contract_test.cj +251 -0
- package/src/tests/plugin_clipboard_test.cj +222 -222
- package/src/tests/plugin_dialog_test.cj +237 -237
- package/src/tests/plugin_fs_test.cj +207 -0
- package/src/tests/plugin_http_test.cj +124 -124
- package/src/tests/plugin_notification_test.cj +205 -205
- package/src/tests/plugin_serial_test.cj +48 -0
- package/src/tests/plugin_test.cj +66 -3
- package/src/tests/plugin_ws_test.cj +40 -40
- package/src/tests/process_hidden_test.cj +129 -129
- package/src/tests/scope_test.cj +350 -350
- package/src/tests/system_host_test.cj +99 -81
- package/src/tests/urlutil_test.cj +53 -53
- package/src/tests/version_test.cj +75 -75
- package/src/tests/window_registry_test.cj +123 -123
- package/src/tray.cj +90 -90
- package/src/urlutil.cj +159 -159
- package/src/version.cj +44 -44
- package/src/window.cj +51 -51
- package/src/window_registry.cj +116 -116
- package/cli/templates/ohos-pc/entry/src/main/cangjie/index.cj +0 -167
- package/prebuilt/win32-x64/cj-tauri.exe +0 -0
- package/src/host_factory.cj +0 -48
- /package/cli/templates/app/{capabilities → assets/capabilities}/default.json +0 -0
- /package/cli/templates/app-react/{capabilities → assets/capabilities}/default.json +0 -0
- /package/cli/templates/app-react/{ui → assets/ui}/package.json +0 -0
- /package/cli/templates/app-react/{ui → assets/ui}/src/main.jsx +0 -0
- /package/cli/templates/app-react/{ui → assets/ui}/vite.config.js +0 -0
- /package/cli/templates/app-vue/{capabilities → assets/capabilities}/default.json +0 -0
- /package/cli/templates/app-vue/{ui → assets/ui}/package.json +0 -0
- /package/cli/templates/app-vue/{ui → assets/ui}/src/main.js +0 -0
- /package/cli/templates/app-vue/{ui → assets/ui}/vite.config.js +0 -0
- /package/cli/templates/{ohos-pc → ohos-skeleton}/AppScope/app.json5 +0 -0
- /package/cli/templates/{ohos-pc → ohos-skeleton}/AppScope/resources/base/element/string.json +0 -0
- /package/cli/templates/{ohos-pc → ohos-skeleton}/AppScope/resources/base/media/background.png +0 -0
- /package/cli/templates/{ohos-pc → ohos-skeleton}/AppScope/resources/base/media/foreground.png +0 -0
- /package/cli/templates/{ohos-pc → ohos-skeleton}/AppScope/resources/base/media/layered_image.json +0 -0
- /package/cli/templates/{ohos-pc → ohos-skeleton}/build-profile.json5 +0 -0
- /package/cli/templates/{ohos-pc → ohos-skeleton}/entry/build-profile.json5 +0 -0
- /package/cli/templates/{ohos-pc → ohos-skeleton}/entry/hvigorfile.ts +0 -0
- /package/cli/templates/{ohos-pc → ohos-skeleton}/entry/oh-package.json5 +0 -0
- /package/cli/templates/{ohos-pc → ohos-skeleton}/entry/src/main/cangjie/ability_stage.cj +0 -0
- /package/cli/templates/{ohos-pc → ohos-skeleton}/entry/src/main/cangjie/app_host.cj +0 -0
- /package/cli/templates/{ohos-pc → ohos-skeleton}/entry/src/main/cangjie/main_ability.cj +0 -0
- /package/cli/templates/{ohos-pc → ohos-skeleton}/entry/src/main/resources/base/element/color.json +0 -0
- /package/cli/templates/{ohos-pc → ohos-skeleton}/entry/src/main/resources/base/element/string.json +0 -0
- /package/cli/templates/{ohos-pc → ohos-skeleton}/entry/src/main/resources/base/media/background.png +0 -0
- /package/cli/templates/{ohos-pc → ohos-skeleton}/entry/src/main/resources/base/media/foreground.png +0 -0
- /package/cli/templates/{ohos-pc → ohos-skeleton}/entry/src/main/resources/base/media/layered_image.json +0 -0
- /package/cli/templates/{ohos-pc → ohos-skeleton}/entry/src/main/resources/base/media/startIcon.png +0 -0
- /package/cli/templates/{ohos-pc → ohos-skeleton}/entry/src/main/resources/base/profile/main_pages.json +0 -0
- /package/cli/templates/{ohos-pc → ohos-skeleton}/entry/src/main/resources/rawfile/capabilities.json +0 -0
- /package/cli/templates/{ohos-pc → ohos-skeleton}/entry/src/main/resources/rawfile/index.html +0 -0
- /package/cli/templates/{ohos-pc → ohos-skeleton}/hvigor/hvigor-config.json5 +0 -0
- /package/cli/templates/{ohos-pc → ohos-skeleton}/hvigorfile.ts +0 -0
- /package/cli/templates/{ohos-pc → ohos-skeleton}/oh-package.json5 +0 -0
|
@@ -1,105 +1,105 @@
|
|
|
1
|
-
package cjTauri.plugins.log
|
|
2
|
-
|
|
3
|
-
import cjTauri.*
|
|
4
|
-
|
|
5
|
-
import std.collection.*
|
|
6
|
-
import stdx.encoding.json.*
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* 官方插件 log:把**前端**的诊断并进宿主日志(RFC-003 第 5 期)
|
|
10
|
-
*
|
|
11
|
-
* 命令:log:write(追加一行)/ log:path(回读生效中的日志文件,空串=没开)
|
|
12
|
-
* 权限:**不自动放行**,应用在 capabilities 里显式声明,两种写法等价:
|
|
13
|
-
* "commands": ["log:write", "log:path"] // 明文逐条
|
|
14
|
-
* "permissions": ["log:default"] // 命名权限集(两条都放行)
|
|
15
|
-
* 前端:window.__CJ_TAURI__.log.write("...") / log.path()
|
|
16
|
-
*
|
|
17
|
-
* 为什么需要它:框架的落盘接管的是 **C 桥与仓颉侧**的 stderr,而页面里的 JS 报错在交付给
|
|
18
|
-
* 用户的单文件产物里同样无处可去(GUI 子系统、双击没有控制台)。插件把页面诊断经命令
|
|
19
|
-
* 送回宿主,落进**同一份**文件——报障时只看一个文件,不用两台机器对着时间戳拼。
|
|
20
|
-
*
|
|
21
|
-
* shim 顺带装上 `error` / `unhandledrejection` 两个钩子:页面自己没写日志也能留下异常。
|
|
22
|
-
* 钩子里**必须吞掉** invoke 的失败(命令未放行时它会 reject),否则钩子自己又造出一轮
|
|
23
|
-
* 未捕获拒绝,转成停不下来的自激。
|
|
24
|
-
*/
|
|
25
|
-
public class LogPlugin <: Plugin {
|
|
26
|
-
public init() {}
|
|
27
|
-
|
|
28
|
-
public func name(): String {
|
|
29
|
-
return "log"
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
public func commands(): HashMap<String, CommandHandler> {
|
|
33
|
-
let m = HashMap<String, CommandHandler>()
|
|
34
|
-
// 键写**短名**:前缀由框架补(写全名会得到 log:log:write,且所有命令都成未注册)
|
|
35
|
-
m.add("write", LogCommands())
|
|
36
|
-
m.add("path", LogCommands())
|
|
37
|
-
return m
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
/** 命名权限集(v1.1):default = 写 + 回读路径(回读能问到本机目录布局,不该默认放宽) */
|
|
41
|
-
public func permissions(): HashMap<String, ArrayList<String>> {
|
|
42
|
-
let m = HashMap<String, ArrayList<String>>()
|
|
43
|
-
let all = ArrayList<String>()
|
|
44
|
-
all.add("write")
|
|
45
|
-
all.add("path")
|
|
46
|
-
m.add("default", all)
|
|
47
|
-
return m
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* 前端 shim:`__CJ_TAURI__.log` + 两个全局异常钩子(都在 document-start 装,早于页面脚本)
|
|
52
|
-
*
|
|
53
|
-
* 内联 JS 里**不出现反斜杠**(转义会先被仓颉吃掉,AGENTS §4);拼接用 `+`,
|
|
54
|
-
* 不用模板串,免得 `${}` 撞上仓颉的字符串插值。
|
|
55
|
-
*/
|
|
56
|
-
public func jsShim(): String {
|
|
57
|
-
return "(function () { var t = window.__CJ_TAURI__ || (window.__CJ_TAURI__ = {});" +
|
|
58
|
-
" t.log = {" +
|
|
59
|
-
" write: function (line) { return window.__CJ_TAURI__.invoke('log:write', { line: String(line) }); }," +
|
|
60
|
-
" path: function () { return window.__CJ_TAURI__.invoke('log:path', {}); }" +
|
|
61
|
-
" };" +
|
|
62
|
-
" var push = function (line) { try { var p = t.log.write(line);" +
|
|
63
|
-
" if (p && p.catch) { p.catch(function () {}); } } catch (e) {} };" +
|
|
64
|
-
" window.addEventListener('error', function (ev) {" +
|
|
65
|
-
" var where = ev.filename ? (ev.filename + ':' + ev.lineno + ':' + ev.colno) : 'unknown';" +
|
|
66
|
-
" push('[frontend] error: ' + (ev.message || 'unknown') + ' @ ' + where); });" +
|
|
67
|
-
" window.addEventListener('unhandledrejection', function (ev) {" +
|
|
68
|
-
" push('[frontend] unhandled rejection: ' + String(ev.reason)); });" +
|
|
69
|
-
" })();\n"
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
/** 日志命令处理器:write 收 { line },path 无参 */
|
|
74
|
-
public class LogCommands <: CommandHandler {
|
|
75
|
-
public init() {}
|
|
76
|
-
|
|
77
|
-
public func handle(cmd: String, args: JsonObject, ipc: IpcContext): JsonValue {
|
|
78
|
-
let _ = ipc
|
|
79
|
-
match (cmd) {
|
|
80
|
-
case "log:write" =>
|
|
81
|
-
let line = this.requireString(args, "line")
|
|
82
|
-
if (line.size > CJ_LOG_MAX_LINE) {
|
|
83
|
-
// 不按字节切字符串(会把汉字切碎):超长就只记一条说明,
|
|
84
|
-
// 免得页面用一行天文数字把内存和日志文件一起撑爆
|
|
85
|
-
let note = "[frontend] log line dropped: ${line.size} bytes exceeds ${CJ_LOG_MAX_LINE}"
|
|
86
|
-
return JsonBool(writeLogLine(note))
|
|
87
|
-
}
|
|
88
|
-
// false = 没开日志(这一轮没落盘),不是错误:页面照常跑,别让它变成 reject
|
|
89
|
-
return JsonBool(writeLogLine(line))
|
|
90
|
-
case "log:path" =>
|
|
91
|
-
return JsonString(currentLogFilePath()) // 空串 = 没开
|
|
92
|
-
case _ => throw CommandException("unknown log command: ${cmd}")
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
private func requireString(args: JsonObject, key: String): String {
|
|
97
|
-
match (args.get(key)) {
|
|
98
|
-
case Some(v) => match (v.kind()) {
|
|
99
|
-
case JsonKind.JsString => return v.asString().getValue()
|
|
100
|
-
case _ => throw CommandException("log: argument \"${key}\" must be a string")
|
|
101
|
-
}
|
|
102
|
-
case None => throw CommandException("log: missing argument \"${key}\"")
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
}
|
|
1
|
+
package cjTauri.plugins.log
|
|
2
|
+
|
|
3
|
+
import cjTauri.*
|
|
4
|
+
|
|
5
|
+
import std.collection.*
|
|
6
|
+
import stdx.encoding.json.*
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* 官方插件 log:把**前端**的诊断并进宿主日志(RFC-003 第 5 期)
|
|
10
|
+
*
|
|
11
|
+
* 命令:log:write(追加一行)/ log:path(回读生效中的日志文件,空串=没开)
|
|
12
|
+
* 权限:**不自动放行**,应用在 capabilities 里显式声明,两种写法等价:
|
|
13
|
+
* "commands": ["log:write", "log:path"] // 明文逐条
|
|
14
|
+
* "permissions": ["log:default"] // 命名权限集(两条都放行)
|
|
15
|
+
* 前端:window.__CJ_TAURI__.log.write("...") / log.path()
|
|
16
|
+
*
|
|
17
|
+
* 为什么需要它:框架的落盘接管的是 **C 桥与仓颉侧**的 stderr,而页面里的 JS 报错在交付给
|
|
18
|
+
* 用户的单文件产物里同样无处可去(GUI 子系统、双击没有控制台)。插件把页面诊断经命令
|
|
19
|
+
* 送回宿主,落进**同一份**文件——报障时只看一个文件,不用两台机器对着时间戳拼。
|
|
20
|
+
*
|
|
21
|
+
* shim 顺带装上 `error` / `unhandledrejection` 两个钩子:页面自己没写日志也能留下异常。
|
|
22
|
+
* 钩子里**必须吞掉** invoke 的失败(命令未放行时它会 reject),否则钩子自己又造出一轮
|
|
23
|
+
* 未捕获拒绝,转成停不下来的自激。
|
|
24
|
+
*/
|
|
25
|
+
public class LogPlugin <: Plugin {
|
|
26
|
+
public init() {}
|
|
27
|
+
|
|
28
|
+
public func name(): String {
|
|
29
|
+
return "log"
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
public func commands(): HashMap<String, CommandHandler> {
|
|
33
|
+
let m = HashMap<String, CommandHandler>()
|
|
34
|
+
// 键写**短名**:前缀由框架补(写全名会得到 log:log:write,且所有命令都成未注册)
|
|
35
|
+
m.add("write", LogCommands())
|
|
36
|
+
m.add("path", LogCommands())
|
|
37
|
+
return m
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** 命名权限集(v1.1):default = 写 + 回读路径(回读能问到本机目录布局,不该默认放宽) */
|
|
41
|
+
public func permissions(): HashMap<String, ArrayList<String>> {
|
|
42
|
+
let m = HashMap<String, ArrayList<String>>()
|
|
43
|
+
let all = ArrayList<String>()
|
|
44
|
+
all.add("write")
|
|
45
|
+
all.add("path")
|
|
46
|
+
m.add("default", all)
|
|
47
|
+
return m
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* 前端 shim:`__CJ_TAURI__.log` + 两个全局异常钩子(都在 document-start 装,早于页面脚本)
|
|
52
|
+
*
|
|
53
|
+
* 内联 JS 里**不出现反斜杠**(转义会先被仓颉吃掉,AGENTS §4);拼接用 `+`,
|
|
54
|
+
* 不用模板串,免得 `${}` 撞上仓颉的字符串插值。
|
|
55
|
+
*/
|
|
56
|
+
public func jsShim(): String {
|
|
57
|
+
return "(function () { var t = window.__CJ_TAURI__ || (window.__CJ_TAURI__ = {});" +
|
|
58
|
+
" t.log = {" +
|
|
59
|
+
" write: function (line) { return window.__CJ_TAURI__.invoke('log:write', { line: String(line) }); }," +
|
|
60
|
+
" path: function () { return window.__CJ_TAURI__.invoke('log:path', {}); }" +
|
|
61
|
+
" };" +
|
|
62
|
+
" var push = function (line) { try { var p = t.log.write(line);" +
|
|
63
|
+
" if (p && p.catch) { p.catch(function () {}); } } catch (e) {} };" +
|
|
64
|
+
" window.addEventListener('error', function (ev) {" +
|
|
65
|
+
" var where = ev.filename ? (ev.filename + ':' + ev.lineno + ':' + ev.colno) : 'unknown';" +
|
|
66
|
+
" push('[frontend] error: ' + (ev.message || 'unknown') + ' @ ' + where); });" +
|
|
67
|
+
" window.addEventListener('unhandledrejection', function (ev) {" +
|
|
68
|
+
" push('[frontend] unhandled rejection: ' + String(ev.reason)); });" +
|
|
69
|
+
" })();\n"
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** 日志命令处理器:write 收 { line },path 无参 */
|
|
74
|
+
public class LogCommands <: CommandHandler {
|
|
75
|
+
public init() {}
|
|
76
|
+
|
|
77
|
+
public func handle(cmd: String, args: JsonObject, ipc: IpcContext): JsonValue {
|
|
78
|
+
let _ = ipc
|
|
79
|
+
match (cmd) {
|
|
80
|
+
case "log:write" =>
|
|
81
|
+
let line = this.requireString(args, "line")
|
|
82
|
+
if (line.size > CJ_LOG_MAX_LINE) {
|
|
83
|
+
// 不按字节切字符串(会把汉字切碎):超长就只记一条说明,
|
|
84
|
+
// 免得页面用一行天文数字把内存和日志文件一起撑爆
|
|
85
|
+
let note = "[frontend] log line dropped: ${line.size} bytes exceeds ${CJ_LOG_MAX_LINE}"
|
|
86
|
+
return JsonBool(writeLogLine(note))
|
|
87
|
+
}
|
|
88
|
+
// false = 没开日志(这一轮没落盘),不是错误:页面照常跑,别让它变成 reject
|
|
89
|
+
return JsonBool(writeLogLine(line))
|
|
90
|
+
case "log:path" =>
|
|
91
|
+
return JsonString(currentLogFilePath()) // 空串 = 没开
|
|
92
|
+
case _ => throw CommandException("unknown log command: ${cmd}")
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
private func requireString(args: JsonObject, key: String): String {
|
|
97
|
+
match (args.get(key)) {
|
|
98
|
+
case Some(v) => match (v.kind()) {
|
|
99
|
+
case JsonKind.JsString => return v.asString().getValue()
|
|
100
|
+
case _ => throw CommandException("log: argument \"${key}\" must be a string")
|
|
101
|
+
}
|
|
102
|
+
case None => throw CommandException("log: missing argument \"${key}\"")
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
@@ -33,6 +33,14 @@ public class MenuPlugin <: Plugin {
|
|
|
33
33
|
return "menu"
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
+
/** 自描述:菜单状态改动是宿主能力,仅桌面两端实现(ohos 宿主无菜单栏,命令如实报错,P-1/P-2) */
|
|
37
|
+
public func describe(): PluginManifest {
|
|
38
|
+
let hosts = ArrayList<String>()
|
|
39
|
+
hosts.add("webkitgtk")
|
|
40
|
+
hosts.add("webview2")
|
|
41
|
+
return PluginManifest(hosts, ArrayList<String>())
|
|
42
|
+
}
|
|
43
|
+
|
|
36
44
|
public func commands(): HashMap<String, CommandHandler> {
|
|
37
45
|
let m = HashMap<String, CommandHandler>()
|
|
38
46
|
m.add("setEnabled", this.handler)
|
|
@@ -1,146 +1,154 @@
|
|
|
1
|
-
package cjTauri.plugins.notification
|
|
2
|
-
|
|
3
|
-
import cjTauri.*
|
|
4
|
-
|
|
5
|
-
import std.collection.*
|
|
6
|
-
import stdx.encoding.json.*
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* 官方插件 notification:系统通知(对标 Tauri 的 notification 插件,RFC-003 第 3 期)
|
|
10
|
-
*
|
|
11
|
-
* 命令:notification:notify
|
|
12
|
-
* 权限:**不自动放行**,应用要在 capabilities 里显式声明:
|
|
13
|
-
* "commands": ["notification:notify"]
|
|
14
|
-
* "permissions": ["notification:default"]
|
|
15
|
-
* 前端:window.__CJ_TAURI__.notification.notify({ title, body })
|
|
16
|
-
*
|
|
17
|
-
* 实现落 C 桥 `cj_bridge_notification_notify`:Linux 走 freedesktop Notifications
|
|
18
|
-
* (会话总线);**Windows 桩阶段返回 -4 未实现**——命令如实报失败,`system:host` 的能力位
|
|
19
|
-
* 同口径不置 CJ_CAP_NOTIFICATION,不假装成功(AGENTS §1 的如实交付口径)。
|
|
20
|
-
*
|
|
21
|
-
* 「发后即忘」:提交即返回,通知显示与否不回传(freedesktop 协议本就没有同步回执)。
|
|
22
|
-
*/
|
|
23
|
-
public class NotificationPlugin <: Plugin {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
public
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
public
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
case
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
match (this.
|
|
119
|
-
case Some(
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
match (
|
|
127
|
-
case Some(
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
1
|
+
package cjTauri.plugins.notification
|
|
2
|
+
|
|
3
|
+
import cjTauri.*
|
|
4
|
+
|
|
5
|
+
import std.collection.*
|
|
6
|
+
import stdx.encoding.json.*
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* 官方插件 notification:系统通知(对标 Tauri 的 notification 插件,RFC-003 第 3 期)
|
|
10
|
+
*
|
|
11
|
+
* 命令:notification:notify
|
|
12
|
+
* 权限:**不自动放行**,应用要在 capabilities 里显式声明:
|
|
13
|
+
* "commands": ["notification:notify"]
|
|
14
|
+
* "permissions": ["notification:default"]
|
|
15
|
+
* 前端:window.__CJ_TAURI__.notification.notify({ title, body })
|
|
16
|
+
*
|
|
17
|
+
* 实现落 C 桥 `cj_bridge_notification_notify`:Linux 走 freedesktop Notifications
|
|
18
|
+
* (会话总线);**Windows 桩阶段返回 -4 未实现**——命令如实报失败,`system:host` 的能力位
|
|
19
|
+
* 同口径不置 CJ_CAP_NOTIFICATION,不假装成功(AGENTS §1 的如实交付口径)。
|
|
20
|
+
*
|
|
21
|
+
* 「发后即忘」:提交即返回,通知显示与否不回传(freedesktop 协议本就没有同步回执)。
|
|
22
|
+
*/
|
|
23
|
+
public class NotificationPlugin <: Plugin {
|
|
24
|
+
|
|
25
|
+
/** 自描述:原生桥仅桌面两端实现(ohos 侧按 P-1 落桩,批次 3-PR3) */
|
|
26
|
+
public func describe(): PluginManifest {
|
|
27
|
+
let hosts = ArrayList<String>()
|
|
28
|
+
hosts.add("webkitgtk")
|
|
29
|
+
hosts.add("webview2")
|
|
30
|
+
return PluginManifest(hosts, ArrayList<String>())
|
|
31
|
+
}
|
|
32
|
+
private let handler: NotificationCommands = NotificationCommands()
|
|
33
|
+
|
|
34
|
+
public init() {}
|
|
35
|
+
|
|
36
|
+
public func name(): String {
|
|
37
|
+
return "notification"
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
public func commands(): HashMap<String, CommandHandler> {
|
|
41
|
+
let m = HashMap<String, CommandHandler>()
|
|
42
|
+
m.add("notify", this.handler)
|
|
43
|
+
return m
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* 命名权限集(v1.1):只有一条命令,default 与明文等价——保留集名是为了
|
|
48
|
+
* 应用清单写法统一(全部走 permissions 数组)。
|
|
49
|
+
*/
|
|
50
|
+
public func permissions(): HashMap<String, ArrayList<String>> {
|
|
51
|
+
let m = HashMap<String, ArrayList<String>>()
|
|
52
|
+
let all = ArrayList<String>()
|
|
53
|
+
all.add("notify")
|
|
54
|
+
m.add("default", all)
|
|
55
|
+
return m
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* 前端 shim:往桥对象上挂一个 notification 命名空间(与 dialog 同款)
|
|
60
|
+
*/
|
|
61
|
+
public func jsShim(): String {
|
|
62
|
+
return "(function () { var t = window.__CJ_TAURI__ || (window.__CJ_TAURI__ = {});" +
|
|
63
|
+
" t.notification = {" +
|
|
64
|
+
" notify: function (o) { return window.__CJ_TAURI__.invoke('notification:notify', o || {}); }" +
|
|
65
|
+
" }; })();\n"
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
public func setup(app: TauriApp): Unit {
|
|
69
|
+
// 多窗口按报文里的 label 解析宿主(与 dialog 插件同一口径):固定 `app.webviewHost()`
|
|
70
|
+
// 会让第二扇窗的通知全部落到默认窗那一个宿主上(2026-10-20 dialog 实机取证同款)。
|
|
71
|
+
this.handler.attachHostResolver({ label => app.hostOf(label) })
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* 通知命令处理器:notify 收 { title, body? }。body 省略 / null 都合法。
|
|
77
|
+
*/
|
|
78
|
+
public class NotificationCommands <: CommandHandler {
|
|
79
|
+
private var host: Option<WebViewHost> = None
|
|
80
|
+
|
|
81
|
+
/* 按 label 解析宿主(多窗口的正路):单槽 `host` 只记着装配时那一个宿主,
|
|
82
|
+
多窗口下用它就是「所有窗的通知都发给默认窗那一个宿主」。 */
|
|
83
|
+
private var hostByWindow: Option<(String) -> Option<WebViewHost>> = None
|
|
84
|
+
|
|
85
|
+
public init() {}
|
|
86
|
+
|
|
87
|
+
/** 由 NotificationPlugin.setup() 调用(单窗口 / 单测用;这个槽不带窗口身份) */
|
|
88
|
+
public func attachHost(h: WebViewHost): Unit {
|
|
89
|
+
this.host = Some(h)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** 装配「label → 宿主」解析器;由 NotificationPlugin.setup() 传入 `app.hostOf` */
|
|
93
|
+
public func attachHostResolver(resolve: (String) -> Option<WebViewHost>): Unit {
|
|
94
|
+
this.hostByWindow = Some(resolve)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
public func handle(cmd: String, args: JsonObject, ipc: IpcContext): JsonValue {
|
|
98
|
+
match (cmd) {
|
|
99
|
+
case "notification:notify" =>
|
|
100
|
+
let title = this.requireString(args, "title")
|
|
101
|
+
let body = this.optionalString(args, "body")
|
|
102
|
+
if (this.requireHost(ipc).notify(title, body)) {
|
|
103
|
+
return JsonNull()
|
|
104
|
+
}
|
|
105
|
+
throw CommandException(
|
|
106
|
+
"notification: 发送失败(宿主未就绪 / 本平台未实现 / 桌面环境不可用)")
|
|
107
|
+
case _ => throw CommandException("unknown notification command: ${cmd}")
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* 取**发起这条命令的窗口**的宿主
|
|
113
|
+
*
|
|
114
|
+
* 多窗口下必须按 `ipc.window` 解析:用装配时那一个宿主,第二扇窗的通知会落到默认窗的宿主上。
|
|
115
|
+
* 解析器在场而窗口没登记时**如实报错**,不回落到单槽(与 dialog 插件同一口径)。
|
|
116
|
+
*/
|
|
117
|
+
private func requireHost(ipc: IpcContext): WebViewHost {
|
|
118
|
+
match (this.hostByWindow) {
|
|
119
|
+
case Some(resolve) => match (resolve(ipc.window)) {
|
|
120
|
+
case Some(h) => return h
|
|
121
|
+
case None => throw CommandException(
|
|
122
|
+
"notification: 窗口 \"${ipc.window}\" 没有登记宿主(addWindow(); 默认窗在 run() 之前也可用)")
|
|
123
|
+
}
|
|
124
|
+
case None => ()
|
|
125
|
+
}
|
|
126
|
+
match (this.host) {
|
|
127
|
+
case Some(h) => h
|
|
128
|
+
case None => throw CommandException(
|
|
129
|
+
"notification: 宿主未装配(用 .plugin(NotificationPlugin()) 装配插件后才可用)")
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
private func requireString(args: JsonObject, key: String): String {
|
|
134
|
+
match (args.get(key)) {
|
|
135
|
+
case Some(v) => match (v.kind()) {
|
|
136
|
+
case JsonKind.JsString => return v.asString().getValue()
|
|
137
|
+
case _ => throw CommandException("notification: argument \"${key}\" must be a string")
|
|
138
|
+
}
|
|
139
|
+
case None => throw CommandException("notification: missing argument \"${key}\"")
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/** 可选字符串参数:缺失 / null 都按空串(body 可省) */
|
|
144
|
+
private func optionalString(args: JsonObject, key: String): String {
|
|
145
|
+
match (args.get(key)) {
|
|
146
|
+
case Some(v) => match (v.kind()) {
|
|
147
|
+
case JsonKind.JsString => return v.asString().getValue()
|
|
148
|
+
case JsonKind.JsNull => return ""
|
|
149
|
+
case _ => throw CommandException("notification: argument \"${key}\" must be a string")
|
|
150
|
+
}
|
|
151
|
+
case None => return ""
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
@@ -3,7 +3,10 @@ package cjTauri.plugins.serial
|
|
|
3
3
|
import cjTauri.*
|
|
4
4
|
|
|
5
5
|
import std.collection.*
|
|
6
|
+
// std.fs / std.sort 被 serialPortList 用到(桌面走 C 桥枚举回落 + 目录扫描;ohos 走 serial_ohos.cj 的扫描)
|
|
7
|
+
@When[env != "ohos"]
|
|
6
8
|
import std.fs.*
|
|
9
|
+
@When[env != "ohos"]
|
|
7
10
|
import std.sort.*
|
|
8
11
|
import stdx.encoding.json.*
|
|
9
12
|
|
|
@@ -37,6 +40,7 @@ let SERIAL_LIST_PREFIXES: Array<String> = [
|
|
|
37
40
|
* Windows 优先走 C 桥的 QueryDosDeviceW 枚举(返回的是「COM 名」,页面直接当 path 用,
|
|
38
41
|
* C 桥 open 会自动补 \\.\\ 前缀);C 桥返回 -4(未实现)或异常时回落到 /dev 目录扫描——
|
|
39
42
|
* 后者在 Windows 下天然为空,不算错误。 */
|
|
43
|
+
@When[env != "ohos"]
|
|
40
44
|
public func serialPortList(): ArrayList<String> {
|
|
41
45
|
let viaC = serialListC()
|
|
42
46
|
if (let Some(list) <- viaC) {
|
|
@@ -78,6 +82,15 @@ public func serialPortList(): ArrayList<String> {
|
|
|
78
82
|
public class SerialPlugin <: Plugin {
|
|
79
83
|
public init() {}
|
|
80
84
|
|
|
85
|
+
/** 自描述:桌面两端走 C 桥、ohos 走 NDK FFI 直连(serial_ohos.cj),三端都实现 */
|
|
86
|
+
public func describe(): PluginManifest {
|
|
87
|
+
let hosts = ArrayList<String>()
|
|
88
|
+
hosts.add("webkitgtk")
|
|
89
|
+
hosts.add("webview2")
|
|
90
|
+
hosts.add("arkweb")
|
|
91
|
+
return PluginManifest(hosts, ArrayList<String>())
|
|
92
|
+
}
|
|
93
|
+
|
|
81
94
|
public func name(): String {
|
|
82
95
|
return "serial"
|
|
83
96
|
}
|
|
@@ -140,10 +153,13 @@ let SERIAL_BAUDS: Array<Int32> = [300, 600, 1200, 2400, 4800, 9600, 19200, 38400
|
|
|
140
153
|
230400, 460800, 500000, 921600, 1000000]
|
|
141
154
|
|
|
142
155
|
/**
|
|
143
|
-
* 设备路径前缀白名单:与 native/bridge_core.c 的 CJ_SERIAL_PREFIXES
|
|
156
|
+
* 设备路径前缀白名单:与 native/bridge_core.c 的 CJ_SERIAL_PREFIXES 对齐;
|
|
157
|
+
* /dev/bus/tty* 是鸿蒙特有的 USB 串口落点(桌面 C 桥白名单没有它们,但桌面侧不会出现
|
|
158
|
+
* 这类路径,加了不影响;ohos 侧的真正边界在 serial_ohos.cj 的 open,白名单同源)
|
|
144
159
|
*/
|
|
145
160
|
let SERIAL_PATH_PREFIXES: Array<String> = ["/dev/ttyUSB", "/dev/ttyACM", "/dev/ttyS", "/dev/ttyAMA",
|
|
146
161
|
"/dev/ttyPS", "/dev/ttyTHS", "/dev/ttyXRUSB", "/dev/ttyV", "/dev/tty.", "/dev/cu.",
|
|
162
|
+
"/dev/bus/ttyUSB", "/dev/bus/ttyACM", "/dev/bus/ttyS",
|
|
147
163
|
"/dev/serial/by-id/", "/dev/serial/by-path/", "/dev/pts/", "COM", "\\\\.\\COM"]
|
|
148
164
|
|
|
149
165
|
public func serialBaudSupported(baud: Int32): Bool {
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
package cjTauri.plugins.serial
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
// 桌面两端包装函数用(ohos 走 NDK FFI 直连,实现在 serial_ohos.cj,批次 4);
|
|
4
|
+
// foreign 声明不带门禁:ohos 侧无引用即不要求符号,不产生链接错误
|
|
5
|
+
@When[env != "ohos"]
|
|
4
6
|
import std.collection.*
|
|
5
7
|
|
|
6
8
|
/**
|
|
@@ -57,6 +59,7 @@ foreign func cj_bridge_serial_list(buf: CPointer<UInt8>, bufLen: Int32): Int32
|
|
|
57
59
|
* 打开串口:成功返回 >0 句柄,失败返回负码(-1 参数/白名单、-2 句柄表满、-3 平台打不开)。
|
|
58
60
|
* 失败明细用 serialLastError() 取(C 侧线程局部,同一线程内取才有意义)。
|
|
59
61
|
*/
|
|
62
|
+
@When[env != "ohos"]
|
|
60
63
|
public func serialOpen(path: String, baud: Int32, dataBits: Int32, parity: Int32,
|
|
61
64
|
stopBits: Int32): Int64 {
|
|
62
65
|
var h: Int64 = -1
|
|
@@ -73,6 +76,7 @@ public func serialOpen(path: String, baud: Int32, dataBits: Int32, parity: Int32
|
|
|
73
76
|
/**
|
|
74
77
|
* C 侧最近一次失败的明细;取不到时返回空串(调用方自行降级成数字码)
|
|
75
78
|
*/
|
|
79
|
+
@When[env != "ohos"]
|
|
76
80
|
public func serialLastError(): String {
|
|
77
81
|
unsafe {
|
|
78
82
|
return cj_bridge_serial_last_error().toString()
|
|
@@ -83,6 +87,7 @@ public func serialLastError(): String {
|
|
|
83
87
|
* C 桥枚举本机串口:Windows 下走 QueryDosDeviceW(返回 COM 名字列表);
|
|
84
88
|
* Linux 返回 None(-4 未实现,serial:list 在 Linux 走 /dev 扫描)。C 侧异常时也回 None。
|
|
85
89
|
*/
|
|
90
|
+
@When[env != "ohos"]
|
|
86
91
|
public func serialListC(): Option<ArrayList<String>> {
|
|
87
92
|
let cap: Int32 = 8192
|
|
88
93
|
let buf = LibC.malloc<UInt8>(count: Int64(cap))
|
|
@@ -128,6 +133,7 @@ public func serialListC(): Option<ArrayList<String>> {
|
|
|
128
133
|
* 读:最多 maxBytes 字节。返回实际读到的字节(空数组 = 超时无数据)。
|
|
129
134
|
* 缓冲在这里现分配现释放——每次读的用量都很小,不值得为它引一层对象池。
|
|
130
135
|
*/
|
|
136
|
+
@When[env != "ohos"]
|
|
131
137
|
public func serialRead(h: Int64, maxBytes: Int32, timeoutMs: Int32): Array<UInt8> {
|
|
132
138
|
let cap = Int64(maxBytes)
|
|
133
139
|
let buf = LibC.malloc<UInt8>(count: cap)
|
|
@@ -153,6 +159,7 @@ public func serialRead(h: Int64, maxBytes: Int32, timeoutMs: Int32): Array<UInt8
|
|
|
153
159
|
/**
|
|
154
160
|
* 写:在 timeoutMs 预算内尽量写完,返回实际写入字节数(可能 < data.size;0 = 一个字节都没出去)
|
|
155
161
|
*/
|
|
162
|
+
@When[env != "ohos"]
|
|
156
163
|
public func serialWrite(h: Int64, data: Array<UInt8>, timeoutMs: Int32): Int32 {
|
|
157
164
|
if (data.size == 0) {
|
|
158
165
|
return 0
|
|
@@ -177,6 +184,7 @@ public func serialWrite(h: Int64, data: Array<UInt8>, timeoutMs: Int32): Int32 {
|
|
|
177
184
|
/**
|
|
178
185
|
* 关句柄:0 = 成功,负码 = 失败(重复关返回 -4)
|
|
179
186
|
*/
|
|
187
|
+
@When[env != "ohos"]
|
|
180
188
|
public func serialClose(h: Int64): Int32 {
|
|
181
189
|
var rc: Int32 = 0
|
|
182
190
|
unsafe {
|