cj-tauri 2.0.0 → 2.1.1

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 (157) hide show
  1. package/README.md +206 -139
  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 +643 -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 +638 -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 +3745 -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/src/api_system.cj +67 -67
  77. package/src/app.cj +335 -969
  78. package/src/app_events.cj +166 -0
  79. package/src/app_host_ops.cj +287 -0
  80. package/src/app_run.cj +283 -0
  81. package/src/app_spec.cj +76 -76
  82. package/src/arkweb_menu.cj +216 -0
  83. package/src/arkweb_script.cj +142 -142
  84. package/src/capability.cj +409 -409
  85. package/src/clipboard_bridge.cj +178 -0
  86. package/src/dialog_spec.cj +79 -0
  87. package/src/dragdrop.cj +26 -26
  88. package/src/embedded_assets.cj +98 -98
  89. package/src/host.cj +752 -607
  90. package/src/host_arkweb.cj +924 -778
  91. package/src/host_webkit.cj +663 -570
  92. package/src/host_webview2.cj +663 -570
  93. package/src/hotkey.cj +66 -66
  94. package/src/ipc_hub.cj +401 -337
  95. package/src/ipc_message.cj +286 -286
  96. package/src/launch.cj +62 -62
  97. package/src/logging.cj +127 -127
  98. package/src/menu.cj +197 -197
  99. package/src/platform/platform_app_context.cj +77 -77
  100. package/src/platform/platform_assets.cj +33 -33
  101. package/src/platform/platform_discover.cj +176 -176
  102. package/src/platform/platform_env.cj +29 -29
  103. package/src/platform/platform_feature.cj +31 -31
  104. package/src/platform/platform_page_source.cj +87 -87
  105. package/src/platform/platform_target_info.cj +32 -32
  106. package/src/platform_host_provider.cj +87 -87
  107. package/src/plugin.cj +333 -333
  108. package/src/plugins/clipboard/plugin_clipboard.cj +152 -152
  109. package/src/plugins/dialog/plugin_dialog.cj +209 -209
  110. package/src/plugins/fs/plugin_fs.cj +105 -1
  111. package/src/plugins/http/http_backend.cj +131 -0
  112. package/src/plugins/http/http_desktop.cj +195 -0
  113. package/src/plugins/http/http_ohos.cj +204 -0
  114. package/src/plugins/http/plugin_http.cj +310 -459
  115. package/src/plugins/log/plugin_log.cj +105 -105
  116. package/src/plugins/menu/plugin_menu.cj +158 -158
  117. package/src/plugins/notification/plugin_notification.cj +154 -154
  118. package/src/plugins/serial/plugin_serial.cj +458 -454
  119. package/src/plugins/serial/serial_bridge.cj +194 -194
  120. package/src/plugins/serial/serial_manager_ohos.cj +92 -0
  121. package/src/plugins/serial/serial_ohos.cj +529 -0
  122. package/src/plugins/shell/plugin_shell.cj +258 -258
  123. package/src/plugins/sqlite/plugin_sqlite.cj +705 -710
  124. package/src/plugins/sqlite/sqlite_ffi.cj +206 -211
  125. package/src/plugins/ws/plugin_ws.cj +488 -470
  126. package/src/process_hidden.cj +174 -174
  127. package/src/scope.cj +391 -391
  128. package/src/shell_event.cj +69 -69
  129. package/src/tests/app_spec_test.cj +130 -127
  130. package/src/tests/capability_test.cj +243 -243
  131. package/src/tests/dialog_spec_test.cj +73 -0
  132. package/src/tests/fake_host.cj +201 -201
  133. package/src/tests/ipc_hub_test.cj +563 -487
  134. package/src/tests/logging_test.cj +62 -62
  135. package/src/tests/platform_contract_test.cj +264 -251
  136. package/src/tests/plugin_clipboard_test.cj +222 -222
  137. package/src/tests/plugin_dialog_test.cj +237 -237
  138. package/src/tests/plugin_fs_test.cj +207 -0
  139. package/src/tests/plugin_http_test.cj +124 -124
  140. package/src/tests/plugin_notification_test.cj +205 -205
  141. package/src/tests/plugin_serial_test.cj +48 -0
  142. package/src/tests/plugin_test.cj +605 -585
  143. package/src/tests/plugin_ws_test.cj +40 -40
  144. package/src/tests/process_hidden_test.cj +129 -129
  145. package/src/tests/scope_test.cj +350 -350
  146. package/src/tests/system_host_test.cj +99 -81
  147. package/src/tests/urlutil_test.cj +53 -53
  148. package/src/tests/version_test.cj +75 -75
  149. package/src/tests/window_registry_test.cj +123 -123
  150. package/src/tray.cj +90 -90
  151. package/src/urlutil.cj +159 -159
  152. package/src/version.cj +44 -44
  153. package/src/window.cj +51 -51
  154. package/src/window_registry.cj +116 -116
  155. package/prebuilt/win32-x64/cj-tauri.exe +0 -0
  156. package/src/plugins/serial/serial_ohos_stub.cj +0 -61
  157. package/src/plugins/sqlite/sqlite_ohos_stub.cj +0 -91
package/src/menu.cj CHANGED
@@ -1,197 +1,197 @@
1
- package cjTauri
2
-
3
- import std.collection.*
4
-
5
- /**
6
- * 窗口菜单栏的**纯仓颉模型**(RFC-002 §5.1):只描述意图,不含任何平台概念。
7
- *
8
- * 分层:模型(本文件)→ `WebViewHost.setMenu()`(src/host.cj)→ C 桥 `cj_bridge_set_menu` →
9
- * 平台翻译层(native/bridge_win.c 的 CreateMenu 族 / bridge_linux.c 的 GtkMenuBar)。
10
- * 菜单文本来自**应用作者**(不是页面可控的不可信输入),所以这里不做转义——
11
- * 非法字段让那一项出局并出声,而不是写一套转义/反转义。
12
- */
13
- public enum MenuItemKind {
14
- | Normal // 普通项:点击回调自己的 id
15
- | Checkbox // 可勾选项:回调里带 checked
16
- | Separator // 分隔线:无 id、不可点
17
- | Submenu // 子菜单:自身不可点,只承载 children
18
- }
19
-
20
- public class MenuItem {
21
- public let id: String
22
- public let label: String
23
- public let kind: MenuItemKind
24
- /** 快捷键**提示**文本(如 "Ctrl+S")。v1 只显示,不抢页面按键 */
25
- public let accelerator: String
26
- public var enabled: Bool
27
- public var checked: Bool
28
- /** 仅 Submenu 用;其它 kind 恒为空表 */
29
- public let children: ArrayList<MenuItem>
30
-
31
- public init(id: String, label: String, kind: MenuItemKind, accelerator: String,
32
- enabled: Bool, checked: Bool, children: ArrayList<MenuItem>) {
33
- this.id = id
34
- this.label = label
35
- this.kind = kind
36
- this.accelerator = accelerator
37
- this.enabled = enabled
38
- this.checked = checked
39
- this.children = children
40
- }
41
-
42
- /** 普通菜单项(最常用) */
43
- public static func normal(id: String, label: String): MenuItem {
44
- return MenuItem(id, label, MenuItemKind.Normal, "", true, false, ArrayList<MenuItem>())
45
- }
46
-
47
- /** 带快捷键提示的普通项 */
48
- public static func normalWithAccel(id: String, label: String, accelerator: String): MenuItem {
49
- return MenuItem(id, label, MenuItemKind.Normal, accelerator, true, false, ArrayList<MenuItem>())
50
- }
51
-
52
- /** 勾选项(初始 checked) */
53
- public static func checkbox(id: String, label: String, checked: Bool): MenuItem {
54
- return MenuItem(id, label, MenuItemKind.Checkbox, "", true, checked, ArrayList<MenuItem>())
55
- }
56
-
57
- /** 子菜单:id 留空(自身不回调),动作交给 children */
58
- public static func submenu(label: String, children: ArrayList<MenuItem>): MenuItem {
59
- return MenuItem("", label, MenuItemKind.Submenu, "", true, false, children)
60
- }
61
-
62
- /** 分隔线 */
63
- public static func separator(): MenuItem {
64
- return MenuItem("", "", MenuItemKind.Separator, "", true, false, ArrayList<MenuItem>())
65
- }
66
- }
67
-
68
- /**
69
- * 菜单模型:`items` 是菜单栏的顶层,子菜单靠 `MenuItem.children` 递归。
70
- *
71
- * **不可变**(items 只读):运行期改状态走 `WebViewHost.setMenuItemState(id, …)`,不在这里改——
72
- * 状态的真身在平台对象上(Windows 的 HMENU、GTK 的 GtkCheckMenuItem),只改模型平台不会自己变。
73
- */
74
- public class MenuModel {
75
- public let items: ArrayList<MenuItem>
76
-
77
- public init(items: ArrayList<MenuItem>) {
78
- this.items = items
79
- }
80
-
81
- /**
82
- * 按 id 找菜单项(本层优先,再下钻子菜单);找不到返回 None
83
- *
84
- * 为什么要它:运行期改状态按 id 定位(`setMenuItemState`),而**应用层的模型状态**必须
85
- * 能反查出「这个 id 现在是什么状态」——平台侧一次写两位(enabled + checked,见
86
- * `WebViewHost.setMenuItemState`),拿不到另一维就会把用户刚点出来的勾选抹掉。
87
- */
88
- public func findById(id: String): Option<MenuItem> {
89
- return MenuModel.lookup(this.items, id)
90
- }
91
-
92
- private static func lookup(items: ArrayList<MenuItem>, id: String): Option<MenuItem> {
93
- for (item in items) {
94
- if (item.id == id) {
95
- return Some(item)
96
- }
97
- match (MenuModel.lookup(item.children, id)) {
98
- case Some(found) => return Some(found)
99
- case None => ()
100
- }
101
- }
102
- return None
103
- }
104
-
105
- /**
106
- * 线路格式(RFC-002 §5.4):一行一项,字段用制表符分隔,**行首制表符数 = 层级**。
107
- *
108
- * <tabs><kind>\t<id>\t<label>\t<flags>\t<accel>\n
109
- *
110
- * kind:`n` 普通 / `c` 勾选 / `s` 分隔线 / `m` 子菜单;
111
- * flags:十进制,bit0 = 可用、bit1 = 勾选(`3` = 又可用又勾选);
112
- * 分隔线与子菜单也走满 5 个字段(id / label / accel 为空),C 侧因此不必为它们开特例。
113
- *
114
- * 为什么 kind 放最前:C 侧先分派再取值,日志里一眼能看出这行是什么。
115
- * 为什么不是 JSON:C 侧(bridge_core.c)是纯 C、不引第三方,行式文本解析几十行就够,
116
- * 且坏了能直接 `fprintf` 到 stderr(诊断走 stderr 是项目铁律)。JSON 留在仓颉侧给 CLI / 清单用。
117
- * 为什么不做转义:字段里出现制表符 / 换行会破坏「一行一项」的结构——v1 的口径是
118
- * **丢弃该项 + stderr 逐项提示**(见 emit),而不是引入转义/反转义这一对必须同时正确的函数。
119
- */
120
- public func toWire(): String {
121
- return MenuModel.emit(this.items, 0)
122
- }
123
-
124
- /** 包内共享(TrayModel 的托盘菜单复用同一遍发射器,别再抄一份格式) */
125
- static func emit(items: ArrayList<MenuItem>, depth: Int64): String {
126
- var out = ""
127
- for (item in items) {
128
- if (!MenuModel.wireSafe(item.id) || !MenuModel.wireSafe(item.label) ||
129
- !MenuModel.wireSafe(item.accelerator)) {
130
- eprintln("[cj-tauri] menu: item dropped from wire (id/label/accelerator contains tab or newline)")
131
- continue
132
- }
133
- out += MenuModel.tabs(depth) + MenuModel.kindCode(item.kind) + "\t" + item.id + "\t" +
134
- item.label + "\t" + MenuModel.flags(item).toString() + "\t" + item.accelerator + "\n"
135
- if (MenuModel.isSubmenu(item.kind)) {
136
- out += MenuModel.emit(item.children, depth + 1)
137
- }
138
- }
139
- return out
140
- }
141
-
142
- /**
143
- * 枚举值**不能用 `==` 比较**(实测:Cangjie 1.2.0 编译期报
144
- * `invalid binary operator '==' on type 'Enum-MenuItemKind' and 'Enum-MenuItemKind'`),只有 match 能判。
145
- */
146
- private static func isSubmenu(kind: MenuItemKind): Bool {
147
- match (kind) {
148
- case MenuItemKind.Submenu => true
149
- case _ => false
150
- }
151
- }
152
-
153
- /**
154
- * 字段里不许有制表符 / 换行 / 回车:它们是线路格式的结构字符。
155
- * 注意本 SDK 里 `for (cp in someString)` 迭代出的是 **UInt8 字节**(不是 UInt32 码点,AGENTS §4 那条已更正):
156
- * 三个结构字符都在 ASCII 范围内,按字节比对既够用、也不受多字节字符影响。
157
- */
158
- private static func wireSafe(s: String): Bool {
159
- for (cp in s) {
160
- if (cp == 9u8 || cp == 10u8 || cp == 13u8) {
161
- return false
162
- }
163
- }
164
- return true
165
- }
166
-
167
- private static func tabs(n: Int64): String {
168
- var out = ""
169
- var i = 0
170
- while (i < n) {
171
- out += "\t"
172
- i++
173
- }
174
- return out
175
- }
176
-
177
- private static func kindCode(kind: MenuItemKind): String {
178
- match (kind) {
179
- case MenuItemKind.Normal => "n"
180
- case MenuItemKind.Checkbox => "c"
181
- case MenuItemKind.Separator => "s"
182
- case MenuItemKind.Submenu => "m"
183
- }
184
- }
185
-
186
- /** flags:bit0 = 可用、bit1 = 勾选(十进制传输,C 侧只要 atoi) */
187
- private static func flags(item: MenuItem): Int64 {
188
- var f = 0
189
- if (item.enabled) {
190
- f += 1
191
- }
192
- if (item.checked) {
193
- f += 2
194
- }
195
- return f
196
- }
197
- }
1
+ package cjTauri
2
+
3
+ import std.collection.*
4
+
5
+ /**
6
+ * 窗口菜单栏的**纯仓颉模型**(RFC-002 §5.1):只描述意图,不含任何平台概念。
7
+ *
8
+ * 分层:模型(本文件)→ `WebViewHost.setMenu()`(src/host.cj)→ C 桥 `cj_bridge_set_menu` →
9
+ * 平台翻译层(native/bridge_win.c 的 CreateMenu 族 / bridge_linux.c 的 GtkMenuBar)。
10
+ * 菜单文本来自**应用作者**(不是页面可控的不可信输入),所以这里不做转义——
11
+ * 非法字段让那一项出局并出声,而不是写一套转义/反转义。
12
+ */
13
+ public enum MenuItemKind {
14
+ | Normal // 普通项:点击回调自己的 id
15
+ | Checkbox // 可勾选项:回调里带 checked
16
+ | Separator // 分隔线:无 id、不可点
17
+ | Submenu // 子菜单:自身不可点,只承载 children
18
+ }
19
+
20
+ public class MenuItem {
21
+ public let id: String
22
+ public let label: String
23
+ public let kind: MenuItemKind
24
+ /** 快捷键**提示**文本(如 "Ctrl+S")。v1 只显示,不抢页面按键 */
25
+ public let accelerator: String
26
+ public var enabled: Bool
27
+ public var checked: Bool
28
+ /** 仅 Submenu 用;其它 kind 恒为空表 */
29
+ public let children: ArrayList<MenuItem>
30
+
31
+ public init(id: String, label: String, kind: MenuItemKind, accelerator: String,
32
+ enabled: Bool, checked: Bool, children: ArrayList<MenuItem>) {
33
+ this.id = id
34
+ this.label = label
35
+ this.kind = kind
36
+ this.accelerator = accelerator
37
+ this.enabled = enabled
38
+ this.checked = checked
39
+ this.children = children
40
+ }
41
+
42
+ /** 普通菜单项(最常用) */
43
+ public static func normal(id: String, label: String): MenuItem {
44
+ return MenuItem(id, label, MenuItemKind.Normal, "", true, false, ArrayList<MenuItem>())
45
+ }
46
+
47
+ /** 带快捷键提示的普通项 */
48
+ public static func normalWithAccel(id: String, label: String, accelerator: String): MenuItem {
49
+ return MenuItem(id, label, MenuItemKind.Normal, accelerator, true, false, ArrayList<MenuItem>())
50
+ }
51
+
52
+ /** 勾选项(初始 checked) */
53
+ public static func checkbox(id: String, label: String, checked: Bool): MenuItem {
54
+ return MenuItem(id, label, MenuItemKind.Checkbox, "", true, checked, ArrayList<MenuItem>())
55
+ }
56
+
57
+ /** 子菜单:id 留空(自身不回调),动作交给 children */
58
+ public static func submenu(label: String, children: ArrayList<MenuItem>): MenuItem {
59
+ return MenuItem("", label, MenuItemKind.Submenu, "", true, false, children)
60
+ }
61
+
62
+ /** 分隔线 */
63
+ public static func separator(): MenuItem {
64
+ return MenuItem("", "", MenuItemKind.Separator, "", true, false, ArrayList<MenuItem>())
65
+ }
66
+ }
67
+
68
+ /**
69
+ * 菜单模型:`items` 是菜单栏的顶层,子菜单靠 `MenuItem.children` 递归。
70
+ *
71
+ * **不可变**(items 只读):运行期改状态走 `WebViewHost.setMenuItemState(id, …)`,不在这里改——
72
+ * 状态的真身在平台对象上(Windows 的 HMENU、GTK 的 GtkCheckMenuItem),只改模型平台不会自己变。
73
+ */
74
+ public class MenuModel {
75
+ public let items: ArrayList<MenuItem>
76
+
77
+ public init(items: ArrayList<MenuItem>) {
78
+ this.items = items
79
+ }
80
+
81
+ /**
82
+ * 按 id 找菜单项(本层优先,再下钻子菜单);找不到返回 None
83
+ *
84
+ * 为什么要它:运行期改状态按 id 定位(`setMenuItemState`),而**应用层的模型状态**必须
85
+ * 能反查出「这个 id 现在是什么状态」——平台侧一次写两位(enabled + checked,见
86
+ * `WebViewHost.setMenuItemState`),拿不到另一维就会把用户刚点出来的勾选抹掉。
87
+ */
88
+ public func findById(id: String): Option<MenuItem> {
89
+ return MenuModel.lookup(this.items, id)
90
+ }
91
+
92
+ private static func lookup(items: ArrayList<MenuItem>, id: String): Option<MenuItem> {
93
+ for (item in items) {
94
+ if (item.id == id) {
95
+ return Some(item)
96
+ }
97
+ match (MenuModel.lookup(item.children, id)) {
98
+ case Some(found) => return Some(found)
99
+ case None => ()
100
+ }
101
+ }
102
+ return None
103
+ }
104
+
105
+ /**
106
+ * 线路格式(RFC-002 §5.4):一行一项,字段用制表符分隔,**行首制表符数 = 层级**。
107
+ *
108
+ * <tabs><kind>\t<id>\t<label>\t<flags>\t<accel>\n
109
+ *
110
+ * kind:`n` 普通 / `c` 勾选 / `s` 分隔线 / `m` 子菜单;
111
+ * flags:十进制,bit0 = 可用、bit1 = 勾选(`3` = 又可用又勾选);
112
+ * 分隔线与子菜单也走满 5 个字段(id / label / accel 为空),C 侧因此不必为它们开特例。
113
+ *
114
+ * 为什么 kind 放最前:C 侧先分派再取值,日志里一眼能看出这行是什么。
115
+ * 为什么不是 JSON:C 侧(bridge_core.c)是纯 C、不引第三方,行式文本解析几十行就够,
116
+ * 且坏了能直接 `fprintf` 到 stderr(诊断走 stderr 是项目铁律)。JSON 留在仓颉侧给 CLI / 清单用。
117
+ * 为什么不做转义:字段里出现制表符 / 换行会破坏「一行一项」的结构——v1 的口径是
118
+ * **丢弃该项 + stderr 逐项提示**(见 emit),而不是引入转义/反转义这一对必须同时正确的函数。
119
+ */
120
+ public func toWire(): String {
121
+ return MenuModel.emit(this.items, 0)
122
+ }
123
+
124
+ /** 包内共享(TrayModel 的托盘菜单复用同一遍发射器,别再抄一份格式) */
125
+ static func emit(items: ArrayList<MenuItem>, depth: Int64): String {
126
+ var out = ""
127
+ for (item in items) {
128
+ if (!MenuModel.wireSafe(item.id) || !MenuModel.wireSafe(item.label) ||
129
+ !MenuModel.wireSafe(item.accelerator)) {
130
+ eprintln("[cj-tauri] menu: item dropped from wire (id/label/accelerator contains tab or newline)")
131
+ continue
132
+ }
133
+ out += MenuModel.tabs(depth) + MenuModel.kindCode(item.kind) + "\t" + item.id + "\t" +
134
+ item.label + "\t" + MenuModel.flags(item).toString() + "\t" + item.accelerator + "\n"
135
+ if (MenuModel.isSubmenu(item.kind)) {
136
+ out += MenuModel.emit(item.children, depth + 1)
137
+ }
138
+ }
139
+ return out
140
+ }
141
+
142
+ /**
143
+ * 枚举值**不能用 `==` 比较**(实测:Cangjie 1.2.0 编译期报
144
+ * `invalid binary operator '==' on type 'Enum-MenuItemKind' and 'Enum-MenuItemKind'`),只有 match 能判。
145
+ */
146
+ private static func isSubmenu(kind: MenuItemKind): Bool {
147
+ match (kind) {
148
+ case MenuItemKind.Submenu => true
149
+ case _ => false
150
+ }
151
+ }
152
+
153
+ /**
154
+ * 字段里不许有制表符 / 换行 / 回车:它们是线路格式的结构字符。
155
+ * 注意本 SDK 里 `for (cp in someString)` 迭代出的是 **UInt8 字节**(不是 UInt32 码点,AGENTS §4 那条已更正):
156
+ * 三个结构字符都在 ASCII 范围内,按字节比对既够用、也不受多字节字符影响。
157
+ */
158
+ private static func wireSafe(s: String): Bool {
159
+ for (cp in s) {
160
+ if (cp == 9u8 || cp == 10u8 || cp == 13u8) {
161
+ return false
162
+ }
163
+ }
164
+ return true
165
+ }
166
+
167
+ private static func tabs(n: Int64): String {
168
+ var out = ""
169
+ var i = 0
170
+ while (i < n) {
171
+ out += "\t"
172
+ i++
173
+ }
174
+ return out
175
+ }
176
+
177
+ private static func kindCode(kind: MenuItemKind): String {
178
+ match (kind) {
179
+ case MenuItemKind.Normal => "n"
180
+ case MenuItemKind.Checkbox => "c"
181
+ case MenuItemKind.Separator => "s"
182
+ case MenuItemKind.Submenu => "m"
183
+ }
184
+ }
185
+
186
+ /** flags:bit0 = 可用、bit1 = 勾选(十进制传输,C 侧只要 atoi) */
187
+ private static func flags(item: MenuItem): Int64 {
188
+ var f = 0
189
+ if (item.enabled) {
190
+ f += 1
191
+ }
192
+ if (item.checked) {
193
+ f += 2
194
+ }
195
+ return f
196
+ }
197
+ }
@@ -1,77 +1,77 @@
1
- package cjTauri.platform
2
-
3
- import std.collection.ArrayList
4
-
5
- /**
6
- * 应用可写目录(RFC-002 §5.2)
7
- *
8
- * 默认跟应用 id 走平台规范目录(XDG / AppData / filesDir),不落可执行文件旁边;
9
- * 便携版(数据跟 exe)是显式选项,由骨架构建期注入,见 RFC §5.2。
10
- * Windows 下统一用 "/" 分隔符(Windows API 均接受),减少按端拼路径的分支。
11
- */
12
- public class AppPaths {
13
- public let dataDir: String // 收藏库、状态等持久数据
14
- public let cacheDir: String // 可清理缓存
15
- public let tempDir: String // 本次运行临时文件
16
-
17
- public init(dataDir: String, cacheDir: String, tempDir: String) {
18
- this.dataDir = dataDir
19
- this.cacheDir = cacheDir
20
- this.tempDir = tempDir
21
- }
22
- }
23
-
24
- /**
25
- * 运行时上下文(RFC-002 §4.5)——框架在 launch() 里构建,业务只读
26
- *
27
- * supports() 是业务唯一的平台判断入口;target 字段仅供日志与埋点,
28
- * code review 里出现「按 target.os 分支」应被拒绝(RFC 硬约束 1)。
29
- */
30
- public class AppContext {
31
- public let paths: AppPaths
32
- public let target: TargetInfo
33
- /** 开发期:资产优先读盘、允许 DevServer */
34
- public let dev: Bool
35
- private let features: ArrayList<PlatformFeature>
36
-
37
- public init(paths: AppPaths, target: TargetInfo, dev: Bool, features: ArrayList<PlatformFeature>) {
38
- this.paths = paths
39
- this.target = target
40
- this.dev = dev
41
- this.features = features
42
- }
43
-
44
- /** 特性探测:批次 0 用线性扫(特性位数 ≤ 十几,不值得上哈希表) */
45
- public func supports(f: PlatformFeature): Bool {
46
- for (item in features) {
47
- if (PlatformFeature.same(item, f)) {
48
- return true
49
- }
50
- }
51
- return false
52
- }
53
-
54
- /** 供日志输出:本端支持哪些特性位(降级行为要可观测,见 RFC §8) */
55
- public func describeFeatures(): String {
56
- let sb = StringBuilder()
57
- var first = true
58
- for (item in features) {
59
- if (!first) {
60
- sb.append(", ")
61
- }
62
- first = false
63
- sb.append(featureName(item))
64
- }
65
- return sb.toString()
66
- }
67
-
68
- private static func featureName(f: PlatformFeature): String {
69
- match (f) {
70
- case Tray => "Tray"
71
- case MultiWindow => "MultiWindow"
72
- case FilePicker => "FilePicker"
73
- case ClipboardImage => "ClipboardImage"
74
- case SoftKeyboardInset => "SoftKeyboardInset"
75
- }
76
- }
77
- }
1
+ package cjTauri.platform
2
+
3
+ import std.collection.ArrayList
4
+
5
+ /**
6
+ * 应用可写目录(RFC-002 §5.2)
7
+ *
8
+ * 默认跟应用 id 走平台规范目录(XDG / AppData / filesDir),不落可执行文件旁边;
9
+ * 便携版(数据跟 exe)是显式选项,由骨架构建期注入,见 RFC §5.2。
10
+ * Windows 下统一用 "/" 分隔符(Windows API 均接受),减少按端拼路径的分支。
11
+ */
12
+ public class AppPaths {
13
+ public let dataDir: String // 收藏库、状态等持久数据
14
+ public let cacheDir: String // 可清理缓存
15
+ public let tempDir: String // 本次运行临时文件
16
+
17
+ public init(dataDir: String, cacheDir: String, tempDir: String) {
18
+ this.dataDir = dataDir
19
+ this.cacheDir = cacheDir
20
+ this.tempDir = tempDir
21
+ }
22
+ }
23
+
24
+ /**
25
+ * 运行时上下文(RFC-002 §4.5)——框架在 launch() 里构建,业务只读
26
+ *
27
+ * supports() 是业务唯一的平台判断入口;target 字段仅供日志与埋点,
28
+ * code review 里出现「按 target.os 分支」应被拒绝(RFC 硬约束 1)。
29
+ */
30
+ public class AppContext {
31
+ public let paths: AppPaths
32
+ public let target: TargetInfo
33
+ /** 开发期:资产优先读盘、允许 DevServer */
34
+ public let dev: Bool
35
+ private let features: ArrayList<PlatformFeature>
36
+
37
+ public init(paths: AppPaths, target: TargetInfo, dev: Bool, features: ArrayList<PlatformFeature>) {
38
+ this.paths = paths
39
+ this.target = target
40
+ this.dev = dev
41
+ this.features = features
42
+ }
43
+
44
+ /** 特性探测:批次 0 用线性扫(特性位数 ≤ 十几,不值得上哈希表) */
45
+ public func supports(f: PlatformFeature): Bool {
46
+ for (item in features) {
47
+ if (PlatformFeature.same(item, f)) {
48
+ return true
49
+ }
50
+ }
51
+ return false
52
+ }
53
+
54
+ /** 供日志输出:本端支持哪些特性位(降级行为要可观测,见 RFC §8) */
55
+ public func describeFeatures(): String {
56
+ let sb = StringBuilder()
57
+ var first = true
58
+ for (item in features) {
59
+ if (!first) {
60
+ sb.append(", ")
61
+ }
62
+ first = false
63
+ sb.append(featureName(item))
64
+ }
65
+ return sb.toString()
66
+ }
67
+
68
+ private static func featureName(f: PlatformFeature): String {
69
+ match (f) {
70
+ case Tray => "Tray"
71
+ case MultiWindow => "MultiWindow"
72
+ case FilePicker => "FilePicker"
73
+ case ClipboardImage => "ClipboardImage"
74
+ case SoftKeyboardInset => "SoftKeyboardInset"
75
+ }
76
+ }
77
+ }
@@ -1,33 +1,33 @@
1
- package cjTauri.platform
2
-
3
- import std.fs.*
4
-
5
- /**
6
- * 目录型资产解析器(RFC §6.2):桌面开发期 / 发布读盘路径的默认实现
7
- *
8
- * root 为资产根(清单 [assets] root),逻辑名按 <root>/<name> 相对寻址。
9
- * 沙箱端实现(鸿蒙 rawfile / 内嵌常量)在各自适配层,批次 3 随清单派生接入。
10
- */
11
- public class DirectoryAssetResolver <: AssetResolver {
12
- private let root: String
13
-
14
- public init(root: String) {
15
- this.root = root
16
- }
17
-
18
- public func readText(name: String): Option<String> {
19
- try {
20
- return Some(String.fromUtf8(File.readFrom(root + "/" + name)))
21
- } catch (e: Exception) {
22
- return None
23
- }
24
- }
25
-
26
- /** 以「能否读到」为准(批次 2 只服务文本资产;图标等二进制走各端资源目录,不进常量表) */
27
- public func exists(name: String): Bool {
28
- match (readText(name)) {
29
- case Some(_) => return true
30
- case None => return false
31
- }
32
- }
33
- }
1
+ package cjTauri.platform
2
+
3
+ import std.fs.*
4
+
5
+ /**
6
+ * 目录型资产解析器(RFC §6.2):桌面开发期 / 发布读盘路径的默认实现
7
+ *
8
+ * root 为资产根(清单 [assets] root),逻辑名按 <root>/<name> 相对寻址。
9
+ * 沙箱端实现(鸿蒙 rawfile / 内嵌常量)在各自适配层,批次 3 随清单派生接入。
10
+ */
11
+ public class DirectoryAssetResolver <: AssetResolver {
12
+ private let root: String
13
+
14
+ public init(root: String) {
15
+ this.root = root
16
+ }
17
+
18
+ public func readText(name: String): Option<String> {
19
+ try {
20
+ return Some(String.fromUtf8(File.readFrom(root + "/" + name)))
21
+ } catch (e: Exception) {
22
+ return None
23
+ }
24
+ }
25
+
26
+ /** 以「能否读到」为准(批次 2 只服务文本资产;图标等二进制走各端资源目录,不进常量表) */
27
+ public func exists(name: String): Bool {
28
+ match (readText(name)) {
29
+ case Some(_) => return true
30
+ case None => return false
31
+ }
32
+ }
33
+ }