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.
- package/README.md +113 -99
- package/bin/cj-tauri.js +287 -287
- package/cjpm.toml +63 -52
- package/cli/cjpm.toml +22 -22
- package/cli/src/doctor.cj +308 -306
- package/cli/src/main.cj +129 -129
- package/cli/src/manifest.cj +632 -558
- package/cli/src/manifest_test.cj +87 -0
- package/cli/src/project.cj +770 -770
- package/cli/src/resolve.cj +348 -348
- package/cli/src/scaffold.cj +369 -369
- package/cli/src/sync.cj +526 -526
- package/cli/src/util.cj +168 -168
- package/cli/templates/app/README.md +51 -48
- package/cli/templates/app/assets/capabilities/default.json +7 -7
- package/cli/templates/app/assets/ui/index.html +120 -118
- package/cli/templates/app/cj-tauri.toml +32 -22
- package/cli/templates/app/cjpm.toml +34 -34
- package/cli/templates/app/src/main.cj +101 -90
- package/cli/templates/app-react/README.md +56 -56
- package/cli/templates/app-react/assets/capabilities/default.json +7 -7
- package/cli/templates/app-react/assets/ui/index.html +25 -25
- package/cli/templates/app-react/assets/ui/package.json +19 -19
- package/cli/templates/app-react/assets/ui/src/App.css +67 -67
- package/cli/templates/app-react/assets/ui/src/App.jsx +124 -124
- package/cli/templates/app-react/assets/ui/src/main.jsx +6 -6
- package/cli/templates/app-react/assets/ui/vite.config.js +24 -24
- package/cli/templates/app-react/cj-tauri.toml +33 -23
- package/cli/templates/app-react/cjpm.toml +34 -34
- package/cli/templates/app-react/src/main.cj +91 -91
- package/cli/templates/app-vue/README.md +56 -56
- package/cli/templates/app-vue/assets/capabilities/default.json +7 -7
- package/cli/templates/app-vue/assets/ui/index.html +25 -25
- package/cli/templates/app-vue/assets/ui/package.json +18 -18
- package/cli/templates/app-vue/assets/ui/src/App.vue +176 -176
- package/cli/templates/app-vue/assets/ui/src/main.js +4 -4
- package/cli/templates/app-vue/assets/ui/vite.config.js +24 -24
- package/cli/templates/app-vue/cj-tauri.toml +33 -23
- package/cli/templates/app-vue/cjpm.toml +34 -34
- package/cli/templates/app-vue/src/main.cj +91 -91
- package/cli/templates/ohos-skeleton/AppScope/app.json5 +13 -13
- package/cli/templates/ohos-skeleton/AppScope/resources/base/element/string.json +8 -8
- package/cli/templates/ohos-skeleton/AppScope/resources/base/media/layered_image.json +6 -6
- package/cli/templates/ohos-skeleton/README.md +84 -79
- package/cli/templates/ohos-skeleton/build-hap.sh +15 -0
- package/cli/templates/ohos-skeleton/build-profile.json5 +46 -46
- package/cli/templates/ohos-skeleton/entry/build-profile.json5 +26 -26
- package/cli/templates/ohos-skeleton/entry/cjpm.toml +51 -43
- package/cli/templates/ohos-skeleton/entry/hvigorfile.ts +6 -6
- package/cli/templates/ohos-skeleton/entry/oh-package.json5 +9 -9
- package/cli/templates/ohos-skeleton/entry/src/main/cangjie/ability_stage.cj +17 -17
- package/cli/templates/ohos-skeleton/entry/src/main/cangjie/app_host.cj +94 -94
- 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 -167
- package/cli/templates/ohos-skeleton/entry/src/main/cangjie/main_ability.cj +139 -139
- package/cli/templates/ohos-skeleton/entry/src/main/module.json5 +57 -49
- package/cli/templates/ohos-skeleton/entry/src/main/resources/base/element/color.json +8 -8
- package/cli/templates/ohos-skeleton/entry/src/main/resources/base/element/string.json +16 -16
- package/cli/templates/ohos-skeleton/entry/src/main/resources/base/media/layered_image.json +6 -6
- package/cli/templates/ohos-skeleton/entry/src/main/resources/base/profile/main_pages.json +2 -2
- package/cli/templates/ohos-skeleton/entry/src/main/resources/rawfile/capabilities.json +6 -6
- package/cli/templates/ohos-skeleton/entry/src/main/resources/rawfile/index.html +92 -92
- package/cli/templates/ohos-skeleton/hvigor/hvigor-config.json5 +5 -5
- package/cli/templates/ohos-skeleton/hvigorfile.ts +6 -6
- package/cli/templates/ohos-skeleton/oh-package.json5 +10 -10
- 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 -53
- package/prebuilt/linux-x64/cj-tauri +0 -0
- package/src/api_system.cj +67 -67
- package/src/app.cj +325 -969
- 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 -76
- package/src/arkweb_menu.cj +216 -0
- package/src/arkweb_script.cj +142 -142
- 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 -607
- package/src/host_arkweb.cj +924 -778
- package/src/host_webkit.cj +663 -570
- package/src/host_webview2.cj +663 -570
- 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 -62
- package/src/logging.cj +127 -127
- package/src/menu.cj +197 -197
- package/src/platform/platform_app_context.cj +77 -77
- package/src/platform/platform_assets.cj +33 -33
- package/src/platform/platform_discover.cj +176 -176
- package/src/platform/platform_env.cj +29 -29
- package/src/platform/platform_feature.cj +31 -31
- package/src/platform/platform_page_source.cj +87 -87
- package/src/platform/platform_target_info.cj +32 -32
- package/src/platform_host_provider.cj +87 -87
- package/src/plugin.cj +333 -333
- package/src/plugins/clipboard/plugin_clipboard.cj +152 -152
- 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 +158 -158
- package/src/plugins/notification/plugin_notification.cj +154 -154
- package/src/plugins/serial/plugin_serial.cj +458 -454
- package/src/plugins/serial/serial_bridge.cj +194 -194
- 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 +258 -258
- package/src/plugins/sqlite/plugin_sqlite.cj +705 -710
- package/src/plugins/sqlite/sqlite_ffi.cj +206 -211
- 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 -127
- package/src/tests/capability_test.cj +243 -243
- package/src/tests/dialog_spec_test.cj +73 -0
- package/src/tests/fake_host.cj +201 -201
- 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 -251
- 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 +605 -585
- 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/prebuilt/win32-x64/cj-tauri.exe +0 -0
- package/src/plugins/serial/serial_ohos_stub.cj +0 -61
- package/src/plugins/sqlite/sqlite_ohos_stub.cj +0 -91
|
@@ -1,123 +1,123 @@
|
|
|
1
|
-
package cjTauri.tests
|
|
2
|
-
|
|
3
|
-
import cjTauri.*
|
|
4
|
-
import std.collection.*
|
|
5
|
-
import std.unittest.*
|
|
6
|
-
import std.unittest.testmacro.*
|
|
7
|
-
|
|
8
|
-
/*
|
|
9
|
-
* 窗口注册表单测:纯逻辑,不创建窗口,可在无 GUI 环境跑。
|
|
10
|
-
* 两条对外契约借用例钉住:① label 缺省值 "main"(现有示例与探针写死它);
|
|
11
|
-
* ② labels() 按登记顺序(广播据此保持确定顺序,HashMap 的迭代顺序不保证)。
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
|
-
/** 断言某段调用抛异常(此处要断的是「拒绝」这个行为本身,@Expect 断言不了) */
|
|
15
|
-
func thrownBy(body: () -> Unit): Bool {
|
|
16
|
-
try {
|
|
17
|
-
body()
|
|
18
|
-
return false
|
|
19
|
-
} catch (e: Exception) {
|
|
20
|
-
return true
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
func rec(label: String): WindowRecord {
|
|
25
|
-
return WindowRecord(label, FakeHost(label), WindowConfig())
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
@Test
|
|
29
|
-
func testRegistryAddAndGet(): Unit {
|
|
30
|
-
let reg = WindowRegistry()
|
|
31
|
-
reg.add(rec("main"))
|
|
32
|
-
@Expect(reg.size(), 1)
|
|
33
|
-
@Expect(reg.get("main").isSome(), true)
|
|
34
|
-
@Expect(reg.get("main").getOrThrow().label, "main")
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
@Test
|
|
38
|
-
func testRegistryRejectsDuplicateLabel(): Unit {
|
|
39
|
-
let reg = WindowRegistry()
|
|
40
|
-
reg.add(rec("main"))
|
|
41
|
-
// 覆盖会让「一个 label 挂着两个宿主」静默成立,必须抛
|
|
42
|
-
@Expect(thrownBy({ => reg.add(rec("main")) }), true)
|
|
43
|
-
@Expect(reg.size(), 1)
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
@Test
|
|
47
|
-
func testRegistryRejectsEmptyLabel(): Unit {
|
|
48
|
-
let reg = WindowRegistry()
|
|
49
|
-
@Expect(thrownBy({ => reg.add(rec("")) }), true)
|
|
50
|
-
@Expect(reg.size(), 0)
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
@Test
|
|
54
|
-
func testRegistryMissingLabelIsNone(): Unit {
|
|
55
|
-
let reg = WindowRegistry()
|
|
56
|
-
@Expect(reg.get("settings").isNone(), true)
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
@Test
|
|
60
|
-
func testRegistryLabelsKeepInsertionOrder(): Unit {
|
|
61
|
-
let reg = WindowRegistry()
|
|
62
|
-
reg.add(rec("main"))
|
|
63
|
-
reg.add(rec("settings"))
|
|
64
|
-
reg.add(rec("log"))
|
|
65
|
-
let ls = reg.labels()
|
|
66
|
-
@Expect(ls.size, 3)
|
|
67
|
-
@Expect(ls[0], "main")
|
|
68
|
-
@Expect(ls[1], "settings")
|
|
69
|
-
@Expect(ls[2], "log")
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
@Test
|
|
73
|
-
func testRegistryRemoveIsIdempotent(): Unit {
|
|
74
|
-
let reg = WindowRegistry()
|
|
75
|
-
reg.add(rec("main"))
|
|
76
|
-
reg.add(rec("settings"))
|
|
77
|
-
reg.remove("main")
|
|
78
|
-
@Expect(reg.size(), 1)
|
|
79
|
-
@Expect(reg.get("main").isNone(), true)
|
|
80
|
-
// 关窗路径可能重复触发:删不存在的 label 不该抛
|
|
81
|
-
reg.remove("main")
|
|
82
|
-
@Expect(reg.size(), 1)
|
|
83
|
-
let ls = reg.labels()
|
|
84
|
-
@Expect(ls.size, 1)
|
|
85
|
-
@Expect(ls[0], "settings")
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
@Test
|
|
89
|
-
func testRecordKeepsConfigSnapshot(): Unit {
|
|
90
|
-
let r = WindowRecord("main", FakeHost("main"), WindowConfig("编辑器", 1024, 768))
|
|
91
|
-
@Expect(r.config.title, "编辑器")
|
|
92
|
-
@Expect(r.config.width, 1024)
|
|
93
|
-
@Expect(r.config.height, 768)
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
@Test
|
|
97
|
-
func testTargetsBroadcastKeepsOrder(): Unit {
|
|
98
|
-
let reg = WindowRegistry()
|
|
99
|
-
reg.add(rec("main"))
|
|
100
|
-
reg.add(rec("settings"))
|
|
101
|
-
let ts = reg.targets("")
|
|
102
|
-
@Expect(ts.size, 2)
|
|
103
|
-
@Expect(ts[0].label, "main")
|
|
104
|
-
@Expect(ts[1].label, "settings")
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
@Test
|
|
108
|
-
func testTargetsSingleWindow(): Unit {
|
|
109
|
-
let reg = WindowRegistry()
|
|
110
|
-
reg.add(rec("main"))
|
|
111
|
-
reg.add(rec("settings"))
|
|
112
|
-
let ts = reg.targets("settings")
|
|
113
|
-
@Expect(ts.size, 1)
|
|
114
|
-
@Expect(ts[0].label, "settings")
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
@Test
|
|
118
|
-
func testTargetsUnknownLabelIsEmpty(): Unit {
|
|
119
|
-
let reg = WindowRegistry()
|
|
120
|
-
reg.add(rec("main"))
|
|
121
|
-
// 未注册的 label 不回落成广播:写错 label 就该听不到,由调用方打日志
|
|
122
|
-
@Expect(reg.targets("nope").size, 0)
|
|
123
|
-
}
|
|
1
|
+
package cjTauri.tests
|
|
2
|
+
|
|
3
|
+
import cjTauri.*
|
|
4
|
+
import std.collection.*
|
|
5
|
+
import std.unittest.*
|
|
6
|
+
import std.unittest.testmacro.*
|
|
7
|
+
|
|
8
|
+
/*
|
|
9
|
+
* 窗口注册表单测:纯逻辑,不创建窗口,可在无 GUI 环境跑。
|
|
10
|
+
* 两条对外契约借用例钉住:① label 缺省值 "main"(现有示例与探针写死它);
|
|
11
|
+
* ② labels() 按登记顺序(广播据此保持确定顺序,HashMap 的迭代顺序不保证)。
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/** 断言某段调用抛异常(此处要断的是「拒绝」这个行为本身,@Expect 断言不了) */
|
|
15
|
+
func thrownBy(body: () -> Unit): Bool {
|
|
16
|
+
try {
|
|
17
|
+
body()
|
|
18
|
+
return false
|
|
19
|
+
} catch (e: Exception) {
|
|
20
|
+
return true
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
func rec(label: String): WindowRecord {
|
|
25
|
+
return WindowRecord(label, FakeHost(label), WindowConfig())
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
@Test
|
|
29
|
+
func testRegistryAddAndGet(): Unit {
|
|
30
|
+
let reg = WindowRegistry()
|
|
31
|
+
reg.add(rec("main"))
|
|
32
|
+
@Expect(reg.size(), 1)
|
|
33
|
+
@Expect(reg.get("main").isSome(), true)
|
|
34
|
+
@Expect(reg.get("main").getOrThrow().label, "main")
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
@Test
|
|
38
|
+
func testRegistryRejectsDuplicateLabel(): Unit {
|
|
39
|
+
let reg = WindowRegistry()
|
|
40
|
+
reg.add(rec("main"))
|
|
41
|
+
// 覆盖会让「一个 label 挂着两个宿主」静默成立,必须抛
|
|
42
|
+
@Expect(thrownBy({ => reg.add(rec("main")) }), true)
|
|
43
|
+
@Expect(reg.size(), 1)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
@Test
|
|
47
|
+
func testRegistryRejectsEmptyLabel(): Unit {
|
|
48
|
+
let reg = WindowRegistry()
|
|
49
|
+
@Expect(thrownBy({ => reg.add(rec("")) }), true)
|
|
50
|
+
@Expect(reg.size(), 0)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
@Test
|
|
54
|
+
func testRegistryMissingLabelIsNone(): Unit {
|
|
55
|
+
let reg = WindowRegistry()
|
|
56
|
+
@Expect(reg.get("settings").isNone(), true)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
@Test
|
|
60
|
+
func testRegistryLabelsKeepInsertionOrder(): Unit {
|
|
61
|
+
let reg = WindowRegistry()
|
|
62
|
+
reg.add(rec("main"))
|
|
63
|
+
reg.add(rec("settings"))
|
|
64
|
+
reg.add(rec("log"))
|
|
65
|
+
let ls = reg.labels()
|
|
66
|
+
@Expect(ls.size, 3)
|
|
67
|
+
@Expect(ls[0], "main")
|
|
68
|
+
@Expect(ls[1], "settings")
|
|
69
|
+
@Expect(ls[2], "log")
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
@Test
|
|
73
|
+
func testRegistryRemoveIsIdempotent(): Unit {
|
|
74
|
+
let reg = WindowRegistry()
|
|
75
|
+
reg.add(rec("main"))
|
|
76
|
+
reg.add(rec("settings"))
|
|
77
|
+
reg.remove("main")
|
|
78
|
+
@Expect(reg.size(), 1)
|
|
79
|
+
@Expect(reg.get("main").isNone(), true)
|
|
80
|
+
// 关窗路径可能重复触发:删不存在的 label 不该抛
|
|
81
|
+
reg.remove("main")
|
|
82
|
+
@Expect(reg.size(), 1)
|
|
83
|
+
let ls = reg.labels()
|
|
84
|
+
@Expect(ls.size, 1)
|
|
85
|
+
@Expect(ls[0], "settings")
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
@Test
|
|
89
|
+
func testRecordKeepsConfigSnapshot(): Unit {
|
|
90
|
+
let r = WindowRecord("main", FakeHost("main"), WindowConfig("编辑器", 1024, 768))
|
|
91
|
+
@Expect(r.config.title, "编辑器")
|
|
92
|
+
@Expect(r.config.width, 1024)
|
|
93
|
+
@Expect(r.config.height, 768)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
@Test
|
|
97
|
+
func testTargetsBroadcastKeepsOrder(): Unit {
|
|
98
|
+
let reg = WindowRegistry()
|
|
99
|
+
reg.add(rec("main"))
|
|
100
|
+
reg.add(rec("settings"))
|
|
101
|
+
let ts = reg.targets("")
|
|
102
|
+
@Expect(ts.size, 2)
|
|
103
|
+
@Expect(ts[0].label, "main")
|
|
104
|
+
@Expect(ts[1].label, "settings")
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
@Test
|
|
108
|
+
func testTargetsSingleWindow(): Unit {
|
|
109
|
+
let reg = WindowRegistry()
|
|
110
|
+
reg.add(rec("main"))
|
|
111
|
+
reg.add(rec("settings"))
|
|
112
|
+
let ts = reg.targets("settings")
|
|
113
|
+
@Expect(ts.size, 1)
|
|
114
|
+
@Expect(ts[0].label, "settings")
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
@Test
|
|
118
|
+
func testTargetsUnknownLabelIsEmpty(): Unit {
|
|
119
|
+
let reg = WindowRegistry()
|
|
120
|
+
reg.add(rec("main"))
|
|
121
|
+
// 未注册的 label 不回落成广播:写错 label 就该听不到,由调用方打日志
|
|
122
|
+
@Expect(reg.targets("nope").size, 0)
|
|
123
|
+
}
|
package/src/tray.cj
CHANGED
|
@@ -1,90 +1,90 @@
|
|
|
1
|
-
package cjTauri
|
|
2
|
-
|
|
3
|
-
import std.collection.*
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* 托盘的**纯仓颉模型**(RFC-002 §5.1 / §7.C):只描述意图,不含任何平台概念。
|
|
7
|
-
*
|
|
8
|
-
* 分层与菜单同构:模型(本文件)→ `WebViewHost.setTray()` → C 桥 `cj_bridge_set_tray` →
|
|
9
|
-
* 平台翻译层(Linux = libayatana-appindicator3,可选编译;Windows 桩如实报未实现)。
|
|
10
|
-
*
|
|
11
|
-
* 托盘是**应用级**资源(一个进程一个图标):重复 setTray = 整体重建(图标 / 提示 / 菜单一起换);
|
|
12
|
-
* 空线路 = 移除托盘。菜单项复用 `MenuItem`(v1 只收 Normal / Checkbox / Separator,
|
|
13
|
-
* Submenu 整条跳过并打 stderr——托盘菜单平铺,别给子菜单)。
|
|
14
|
-
*
|
|
15
|
-
* 线路格式(与 `native/core/bridge_core.h` 的 `pending_tray` 注释逐字对应):
|
|
16
|
-
*
|
|
17
|
-
* icon\t<路径>\n (空路径 = 平台用主题默认图标兜底)
|
|
18
|
-
* tooltip\t<文本>\n
|
|
19
|
-
* <kind>\t<id>\t<label>\t<flags>\t<accel>\n (与菜单线路同格式,平铺一层)
|
|
20
|
-
*
|
|
21
|
-
* 菜单项被点以 `{"kind":"tray","id":"…"}` 走 shell 通道回投(前端事件 `tray:click`,
|
|
22
|
-
* 需在能力清单 `events` 声明)。图标 / 提示文本来自应用作者,不做转义;
|
|
23
|
-
* 字段含制表符 / 换行的头部值整体丢弃为空(wire 的结构字符,与菜单同一铁律)。
|
|
24
|
-
*/
|
|
25
|
-
public class TrayModel {
|
|
26
|
-
/** 图标路径(png/ico 绝对路径);空串 = 平台用主题默认图标兜底 */
|
|
27
|
-
public let icon: String
|
|
28
|
-
|
|
29
|
-
/** 悬停提示文本;空串 = 不设 */
|
|
30
|
-
public let tooltip: String
|
|
31
|
-
|
|
32
|
-
/** 托盘右键菜单(平铺一层;Submenu 项会被 toWire 丢弃并打 stderr) */
|
|
33
|
-
public let items: ArrayList<MenuItem>
|
|
34
|
-
|
|
35
|
-
public init(icon: String, tooltip: String, items: ArrayList<MenuItem>) {
|
|
36
|
-
this.icon = icon
|
|
37
|
-
this.tooltip = tooltip
|
|
38
|
-
this.items = items
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
/** 无菜单托盘(纯图标 + 提示) */
|
|
42
|
-
public init(icon: String, tooltip: String) {
|
|
43
|
-
this.icon = icon
|
|
44
|
-
this.tooltip = tooltip
|
|
45
|
-
this.items = ArrayList<MenuItem>()
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
/** 线路文本(三段:icon 行 / tooltip 行 / 菜单行走 MenuModel.emit 同一遍发射器) */
|
|
49
|
-
public func toWire(): String {
|
|
50
|
-
// 托盘菜单平铺:Submenu 整条出局(枚举不能写 ==,AGENTS §4,走 match)
|
|
51
|
-
let flat = ArrayList<MenuItem>()
|
|
52
|
-
for (it in this.items) {
|
|
53
|
-
if (TrayModel.isSubmenu(it.kind)) {
|
|
54
|
-
eprintln("[cj-tauri] tray: submenu item dropped (v1 tray menu is flat): ${it.label}")
|
|
55
|
-
continue
|
|
56
|
-
}
|
|
57
|
-
flat.add(it)
|
|
58
|
-
}
|
|
59
|
-
// 头部两行的结构字符会让整份线路解析错位:宁可丢提示也不装错
|
|
60
|
-
var icon = this.icon
|
|
61
|
-
var tooltip = this.tooltip
|
|
62
|
-
if (TrayModel.hasStructChar(icon) || TrayModel.hasStructChar(tooltip)) {
|
|
63
|
-
eprintln("[cj-tauri] tray: icon/tooltip contains tab or newline - dropped to empty")
|
|
64
|
-
icon = ""
|
|
65
|
-
tooltip = ""
|
|
66
|
-
}
|
|
67
|
-
var out = "icon\t" + icon + "\n"
|
|
68
|
-
out += "tooltip\t" + tooltip + "\n"
|
|
69
|
-
out += MenuModel.emit(flat, 0)
|
|
70
|
-
return out
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
/** 枚举判别只能走 match(`==` 编译不过,见 MenuModel.isSubmenu 的实测注释) */
|
|
74
|
-
private static func isSubmenu(kind: MenuItemKind): Bool {
|
|
75
|
-
match (kind) {
|
|
76
|
-
case MenuItemKind.Submenu => true
|
|
77
|
-
case _ => false
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
/** 结构字符(tab/LF/CR)检查——wire 的分隔符就是它们 */
|
|
82
|
-
private static func hasStructChar(s: String): Bool {
|
|
83
|
-
for (b in s) {
|
|
84
|
-
if (b == 9u8 || b == 10u8 || b == 13u8) {
|
|
85
|
-
return true
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
return false
|
|
89
|
-
}
|
|
90
|
-
}
|
|
1
|
+
package cjTauri
|
|
2
|
+
|
|
3
|
+
import std.collection.*
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* 托盘的**纯仓颉模型**(RFC-002 §5.1 / §7.C):只描述意图,不含任何平台概念。
|
|
7
|
+
*
|
|
8
|
+
* 分层与菜单同构:模型(本文件)→ `WebViewHost.setTray()` → C 桥 `cj_bridge_set_tray` →
|
|
9
|
+
* 平台翻译层(Linux = libayatana-appindicator3,可选编译;Windows 桩如实报未实现)。
|
|
10
|
+
*
|
|
11
|
+
* 托盘是**应用级**资源(一个进程一个图标):重复 setTray = 整体重建(图标 / 提示 / 菜单一起换);
|
|
12
|
+
* 空线路 = 移除托盘。菜单项复用 `MenuItem`(v1 只收 Normal / Checkbox / Separator,
|
|
13
|
+
* Submenu 整条跳过并打 stderr——托盘菜单平铺,别给子菜单)。
|
|
14
|
+
*
|
|
15
|
+
* 线路格式(与 `native/core/bridge_core.h` 的 `pending_tray` 注释逐字对应):
|
|
16
|
+
*
|
|
17
|
+
* icon\t<路径>\n (空路径 = 平台用主题默认图标兜底)
|
|
18
|
+
* tooltip\t<文本>\n
|
|
19
|
+
* <kind>\t<id>\t<label>\t<flags>\t<accel>\n (与菜单线路同格式,平铺一层)
|
|
20
|
+
*
|
|
21
|
+
* 菜单项被点以 `{"kind":"tray","id":"…"}` 走 shell 通道回投(前端事件 `tray:click`,
|
|
22
|
+
* 需在能力清单 `events` 声明)。图标 / 提示文本来自应用作者,不做转义;
|
|
23
|
+
* 字段含制表符 / 换行的头部值整体丢弃为空(wire 的结构字符,与菜单同一铁律)。
|
|
24
|
+
*/
|
|
25
|
+
public class TrayModel {
|
|
26
|
+
/** 图标路径(png/ico 绝对路径);空串 = 平台用主题默认图标兜底 */
|
|
27
|
+
public let icon: String
|
|
28
|
+
|
|
29
|
+
/** 悬停提示文本;空串 = 不设 */
|
|
30
|
+
public let tooltip: String
|
|
31
|
+
|
|
32
|
+
/** 托盘右键菜单(平铺一层;Submenu 项会被 toWire 丢弃并打 stderr) */
|
|
33
|
+
public let items: ArrayList<MenuItem>
|
|
34
|
+
|
|
35
|
+
public init(icon: String, tooltip: String, items: ArrayList<MenuItem>) {
|
|
36
|
+
this.icon = icon
|
|
37
|
+
this.tooltip = tooltip
|
|
38
|
+
this.items = items
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** 无菜单托盘(纯图标 + 提示) */
|
|
42
|
+
public init(icon: String, tooltip: String) {
|
|
43
|
+
this.icon = icon
|
|
44
|
+
this.tooltip = tooltip
|
|
45
|
+
this.items = ArrayList<MenuItem>()
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** 线路文本(三段:icon 行 / tooltip 行 / 菜单行走 MenuModel.emit 同一遍发射器) */
|
|
49
|
+
public func toWire(): String {
|
|
50
|
+
// 托盘菜单平铺:Submenu 整条出局(枚举不能写 ==,AGENTS §4,走 match)
|
|
51
|
+
let flat = ArrayList<MenuItem>()
|
|
52
|
+
for (it in this.items) {
|
|
53
|
+
if (TrayModel.isSubmenu(it.kind)) {
|
|
54
|
+
eprintln("[cj-tauri] tray: submenu item dropped (v1 tray menu is flat): ${it.label}")
|
|
55
|
+
continue
|
|
56
|
+
}
|
|
57
|
+
flat.add(it)
|
|
58
|
+
}
|
|
59
|
+
// 头部两行的结构字符会让整份线路解析错位:宁可丢提示也不装错
|
|
60
|
+
var icon = this.icon
|
|
61
|
+
var tooltip = this.tooltip
|
|
62
|
+
if (TrayModel.hasStructChar(icon) || TrayModel.hasStructChar(tooltip)) {
|
|
63
|
+
eprintln("[cj-tauri] tray: icon/tooltip contains tab or newline - dropped to empty")
|
|
64
|
+
icon = ""
|
|
65
|
+
tooltip = ""
|
|
66
|
+
}
|
|
67
|
+
var out = "icon\t" + icon + "\n"
|
|
68
|
+
out += "tooltip\t" + tooltip + "\n"
|
|
69
|
+
out += MenuModel.emit(flat, 0)
|
|
70
|
+
return out
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** 枚举判别只能走 match(`==` 编译不过,见 MenuModel.isSubmenu 的实测注释) */
|
|
74
|
+
private static func isSubmenu(kind: MenuItemKind): Bool {
|
|
75
|
+
match (kind) {
|
|
76
|
+
case MenuItemKind.Submenu => true
|
|
77
|
+
case _ => false
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** 结构字符(tab/LF/CR)检查——wire 的分隔符就是它们 */
|
|
82
|
+
private static func hasStructChar(s: String): Bool {
|
|
83
|
+
for (b in s) {
|
|
84
|
+
if (b == 9u8 || b == 10u8 || b == 13u8) {
|
|
85
|
+
return true
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return false
|
|
89
|
+
}
|
|
90
|
+
}
|