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,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
+ }
@@ -1,158 +1,158 @@
1
- package cjTauri.plugins.menu
2
-
3
- import cjTauri.*
4
-
5
- import std.collection.*
6
- import stdx.encoding.json.*
7
-
8
- /**
9
- * 官方插件 menu:运行期改菜单项状态 + 前端命名空间(RFC-002 §5.6 / §5.9)
10
- *
11
- * 命令(短名 → 全名):
12
- * setEnabled → menu:setEnabled `{ id, enabled }`
13
- * setChecked → menu:setChecked `{ id, checked }`
14
- * 事件:`click` → `menu:click`——本插件只**声明**它(给缺权限提示用),真正投递在
15
- * `TauriApp` 的 shell 分发里,页面侧写 `listen("menu:click", …)` 即可。
16
- * 权限:**不自动放行**,应用在 capabilities 里二选一(两种写法等价):
17
- * "commands": ["menu:setEnabled", "menu:setChecked"] // 明文逐条(旧写法)
18
- * "permissions": ["menu:state"] // 命名权限集(v1.1)
19
- * 前端:window.__CJ_TAURI__.menu.setChecked({ id: "view.sidebar", checked: true })
20
- *
21
- * 为什么命令要经 `TauriApp`、而不是直接用宿主:菜单项按 id 定位到**调用方那个窗口**
22
- * (`ipc.window`),而「另一维状态」存在应用层留存的菜单模型里——平台侧一次调用会同时写
23
- * enabled 与 checked(见 `WebViewHost.setMenuItemState`),拿不到另一维就会把用户刚点出来的
24
- * 勾选抹掉。所以处理器必须握有 app,装配时由 `setup()` 注入。
25
- */
26
- public class MenuPlugin <: Plugin {
27
- // 两个命令共用一个处理器实例:app 引用只要注入一次(commands() 早于 setup() 跑,见 attach)
28
- private let handler: MenuCommands = MenuCommands()
29
-
30
- public init() {}
31
-
32
- public func name(): String {
33
- return "menu"
34
- }
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
-
44
- public func commands(): HashMap<String, CommandHandler> {
45
- let m = HashMap<String, CommandHandler>()
46
- m.add("setEnabled", this.handler)
47
- m.add("setChecked", this.handler)
48
- return m
49
- }
50
-
51
- /** 事件只声明(`<插件名>:<短名>` = `menu:click`),投递在框架的 shell 分发里 */
52
- public func events(): ArrayList<String> {
53
- let e = ArrayList<String>()
54
- e.add("click")
55
- return e
56
- }
57
-
58
- /**
59
- * 命名权限集(v1.1):`menu:state` = 两个改状态的命令
60
- *
61
- * 与 fs 的 readonly 同理——**集声明不等于放行**,清单不引用它一条也不会生效。
62
- */
63
- public func permissions(): HashMap<String, ArrayList<String>> {
64
- let m = HashMap<String, ArrayList<String>>()
65
- let st = ArrayList<String>()
66
- st.add("setEnabled")
67
- st.add("setChecked")
68
- m.add("state", st)
69
- return m
70
- }
71
-
72
- public func setup(app: TauriApp): Unit {
73
- this.handler.attach(app)
74
- }
75
-
76
- /**
77
- * 前端 shim:往桥对象上挂一个 menu 命名空间。
78
- * 只动 __CJ_TAURI__ 的一个命名空间,不碰 invoke / listen / emit,也不注册全局变量。
79
- */
80
- public func jsShim(): String {
81
- return "(function () { var t = window.__CJ_TAURI__ || (window.__CJ_TAURI__ = {});" +
82
- " t.menu = {" +
83
- " setEnabled: function (args) { return window.__CJ_TAURI__.invoke('menu:setEnabled', args); }," +
84
- " setChecked: function (args) { return window.__CJ_TAURI__.invoke('menu:setChecked', args); }" +
85
- " }; })();\n"
86
- }
87
- }
88
-
89
- /**
90
- * menu 插件的命令处理器(两个命令共用一份,按全名分派,与内置 SystemCommands / FsCommands 同形)
91
- */
92
- public class MenuCommands <: CommandHandler {
93
- private var app: Option<TauriApp> = None
94
-
95
- public init() {}
96
-
97
- /**
98
- * 由 MenuPlugin.setup 注入
99
- *
100
- * 不能靠构造函数传:`commands()`(装配时先跑,负责建处理器)拿不到 app,
101
- * 而 `setup(app)` 后跑——所以只能事后注入。
102
- */
103
- func attach(app: TauriApp): Unit {
104
- this.app = Some(app)
105
- }
106
-
107
- public func handle(cmd: String, args: JsonObject, ipc: IpcContext): JsonValue {
108
- match (cmd) {
109
- case "menu:setEnabled" => this.setEnabled(args, ipc)
110
- case "menu:setChecked" => this.setChecked(args, ipc)
111
- case _ => throw CommandException("unknown menu command: ${cmd}")
112
- }
113
- }
114
-
115
- /** 只改启用状态;勾选态由应用层模型补齐(见 MenuPlugin 的类注释) */
116
- private func setEnabled(args: JsonObject, ipc: IpcContext): JsonValue {
117
- let id = this.requireString(args, "id")
118
- let enabled = this.requireBool(args, "enabled")
119
- this.requireApp().setMenuItemEnabledTo(ipc.window, id, enabled)
120
- return JsonBool(true)
121
- }
122
-
123
- /** 只改勾选状态;启用态由应用层模型补齐 */
124
- private func setChecked(args: JsonObject, ipc: IpcContext): JsonValue {
125
- let id = this.requireString(args, "id")
126
- let checked = this.requireBool(args, "checked")
127
- this.requireApp().setMenuItemCheckedTo(ipc.window, id, checked)
128
- return JsonBool(true)
129
- }
130
-
131
- private func requireApp(): TauriApp {
132
- match (this.app) {
133
- case Some(a) => return a
134
- case None => throw CommandException("menu: 插件未装配到应用(请 TauriApp().plugin(MenuPlugin()))")
135
- }
136
- }
137
-
138
- /** 取必需的字符串参数;缺失 / 类型不对时抛业务错误,前端拿到可读原文 */
139
- private func requireString(args: JsonObject, key: String): String {
140
- match (args.get(key)) {
141
- case Some(v) => match (v.kind()) {
142
- case JsonKind.JsString => return v.asString().getValue()
143
- case _ => throw CommandException("menu: argument \"${key}\" must be a string")
144
- }
145
- case None => throw CommandException("menu: missing argument \"${key}\"")
146
- }
147
- }
148
-
149
- private func requireBool(args: JsonObject, key: String): Bool {
150
- match (args.get(key)) {
151
- case Some(v) => match (v.kind()) {
152
- case JsonKind.JsBool => return v.asBool().getValue()
153
- case _ => throw CommandException("menu: argument \"${key}\" must be a boolean")
154
- }
155
- case None => throw CommandException("menu: missing argument \"${key}\"")
156
- }
157
- }
158
- }
1
+ package cjTauri.plugins.menu
2
+
3
+ import cjTauri.*
4
+
5
+ import std.collection.*
6
+ import stdx.encoding.json.*
7
+
8
+ /**
9
+ * 官方插件 menu:运行期改菜单项状态 + 前端命名空间(RFC-002 §5.6 / §5.9)
10
+ *
11
+ * 命令(短名 → 全名):
12
+ * setEnabled → menu:setEnabled `{ id, enabled }`
13
+ * setChecked → menu:setChecked `{ id, checked }`
14
+ * 事件:`click` → `menu:click`——本插件只**声明**它(给缺权限提示用),真正投递在
15
+ * `TauriApp` 的 shell 分发里,页面侧写 `listen("menu:click", …)` 即可。
16
+ * 权限:**不自动放行**,应用在 capabilities 里二选一(两种写法等价):
17
+ * "commands": ["menu:setEnabled", "menu:setChecked"] // 明文逐条(旧写法)
18
+ * "permissions": ["menu:state"] // 命名权限集(v1.1)
19
+ * 前端:window.__CJ_TAURI__.menu.setChecked({ id: "view.sidebar", checked: true })
20
+ *
21
+ * 为什么命令要经 `TauriApp`、而不是直接用宿主:菜单项按 id 定位到**调用方那个窗口**
22
+ * (`ipc.window`),而「另一维状态」存在应用层留存的菜单模型里——平台侧一次调用会同时写
23
+ * enabled 与 checked(见 `WebViewHost.setMenuItemState`),拿不到另一维就会把用户刚点出来的
24
+ * 勾选抹掉。所以处理器必须握有 app,装配时由 `setup()` 注入。
25
+ */
26
+ public class MenuPlugin <: Plugin {
27
+ // 两个命令共用一个处理器实例:app 引用只要注入一次(commands() 早于 setup() 跑,见 attach)
28
+ private let handler: MenuCommands = MenuCommands()
29
+
30
+ public init() {}
31
+
32
+ public func name(): String {
33
+ return "menu"
34
+ }
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
+
44
+ public func commands(): HashMap<String, CommandHandler> {
45
+ let m = HashMap<String, CommandHandler>()
46
+ m.add("setEnabled", this.handler)
47
+ m.add("setChecked", this.handler)
48
+ return m
49
+ }
50
+
51
+ /** 事件只声明(`<插件名>:<短名>` = `menu:click`),投递在框架的 shell 分发里 */
52
+ public func events(): ArrayList<String> {
53
+ let e = ArrayList<String>()
54
+ e.add("click")
55
+ return e
56
+ }
57
+
58
+ /**
59
+ * 命名权限集(v1.1):`menu:state` = 两个改状态的命令
60
+ *
61
+ * 与 fs 的 readonly 同理——**集声明不等于放行**,清单不引用它一条也不会生效。
62
+ */
63
+ public func permissions(): HashMap<String, ArrayList<String>> {
64
+ let m = HashMap<String, ArrayList<String>>()
65
+ let st = ArrayList<String>()
66
+ st.add("setEnabled")
67
+ st.add("setChecked")
68
+ m.add("state", st)
69
+ return m
70
+ }
71
+
72
+ public func setup(app: TauriApp): Unit {
73
+ this.handler.attach(app)
74
+ }
75
+
76
+ /**
77
+ * 前端 shim:往桥对象上挂一个 menu 命名空间。
78
+ * 只动 __CJ_TAURI__ 的一个命名空间,不碰 invoke / listen / emit,也不注册全局变量。
79
+ */
80
+ public func jsShim(): String {
81
+ return "(function () { var t = window.__CJ_TAURI__ || (window.__CJ_TAURI__ = {});" +
82
+ " t.menu = {" +
83
+ " setEnabled: function (args) { return window.__CJ_TAURI__.invoke('menu:setEnabled', args); }," +
84
+ " setChecked: function (args) { return window.__CJ_TAURI__.invoke('menu:setChecked', args); }" +
85
+ " }; })();\n"
86
+ }
87
+ }
88
+
89
+ /**
90
+ * menu 插件的命令处理器(两个命令共用一份,按全名分派,与内置 SystemCommands / FsCommands 同形)
91
+ */
92
+ public class MenuCommands <: CommandHandler {
93
+ private var app: Option<TauriApp> = None
94
+
95
+ public init() {}
96
+
97
+ /**
98
+ * 由 MenuPlugin.setup 注入
99
+ *
100
+ * 不能靠构造函数传:`commands()`(装配时先跑,负责建处理器)拿不到 app,
101
+ * 而 `setup(app)` 后跑——所以只能事后注入。
102
+ */
103
+ func attach(app: TauriApp): Unit {
104
+ this.app = Some(app)
105
+ }
106
+
107
+ public func handle(cmd: String, args: JsonObject, ipc: IpcContext): JsonValue {
108
+ match (cmd) {
109
+ case "menu:setEnabled" => this.setEnabled(args, ipc)
110
+ case "menu:setChecked" => this.setChecked(args, ipc)
111
+ case _ => throw CommandException("unknown menu command: ${cmd}")
112
+ }
113
+ }
114
+
115
+ /** 只改启用状态;勾选态由应用层模型补齐(见 MenuPlugin 的类注释) */
116
+ private func setEnabled(args: JsonObject, ipc: IpcContext): JsonValue {
117
+ let id = this.requireString(args, "id")
118
+ let enabled = this.requireBool(args, "enabled")
119
+ this.requireApp().setMenuItemEnabledTo(ipc.window, id, enabled)
120
+ return JsonBool(true)
121
+ }
122
+
123
+ /** 只改勾选状态;启用态由应用层模型补齐 */
124
+ private func setChecked(args: JsonObject, ipc: IpcContext): JsonValue {
125
+ let id = this.requireString(args, "id")
126
+ let checked = this.requireBool(args, "checked")
127
+ this.requireApp().setMenuItemCheckedTo(ipc.window, id, checked)
128
+ return JsonBool(true)
129
+ }
130
+
131
+ private func requireApp(): TauriApp {
132
+ match (this.app) {
133
+ case Some(a) => return a
134
+ case None => throw CommandException("menu: 插件未装配到应用(请 TauriApp().plugin(MenuPlugin()))")
135
+ }
136
+ }
137
+
138
+ /** 取必需的字符串参数;缺失 / 类型不对时抛业务错误,前端拿到可读原文 */
139
+ private func requireString(args: JsonObject, key: String): String {
140
+ match (args.get(key)) {
141
+ case Some(v) => match (v.kind()) {
142
+ case JsonKind.JsString => return v.asString().getValue()
143
+ case _ => throw CommandException("menu: argument \"${key}\" must be a string")
144
+ }
145
+ case None => throw CommandException("menu: missing argument \"${key}\"")
146
+ }
147
+ }
148
+
149
+ private func requireBool(args: JsonObject, key: String): Bool {
150
+ match (args.get(key)) {
151
+ case Some(v) => match (v.kind()) {
152
+ case JsonKind.JsBool => return v.asBool().getValue()
153
+ case _ => throw CommandException("menu: argument \"${key}\" must be a boolean")
154
+ }
155
+ case None => throw CommandException("menu: missing argument \"${key}\"")
156
+ }
157
+ }
158
+ }