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
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
package cjTauri
|
|
2
|
+
|
|
3
|
+
import stdx.encoding.json.*
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* TauriApp 的事件收发与 shell 事件分发(`src/app.cj` 的同包分片)
|
|
7
|
+
*
|
|
8
|
+
* 为什么拆出来:这一组函数只围绕「事件」这一个职责——后端投前端(emit 三兄弟)、
|
|
9
|
+
* 前端订阅(listenEvent)、宿主 shell 事件(菜单 / 托盘 / 热键)的钩子与回投。
|
|
10
|
+
* 拆分只是同包内换文件,包边界、可见性对外、API 一行未变(`extend` 补齐成员)。
|
|
11
|
+
*
|
|
12
|
+
* 同包拆分的代价(有意接受):`extend` 碰不到 `private` 成员,所以被跨文件使用的字段
|
|
13
|
+
* 与少数内部方法在 `app.cj` 里放宽成包内可见——对包外(应用 / 插件 / 测试子包)完全不可见。
|
|
14
|
+
*/
|
|
15
|
+
extend TauriApp {
|
|
16
|
+
/**
|
|
17
|
+
* 向所有窗口推送事件(对标 Tauri 的 `emit`;经 capability 校验,未声明的事件不投递)
|
|
18
|
+
*/
|
|
19
|
+
public func emit(event: String, payload: JsonValue): Unit {
|
|
20
|
+
this.hub.emitAll(event, payload)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* 向所有窗口推送「只保最新」的事件:同一帧内同名事件只投最后一次 payload,中间值丢弃
|
|
25
|
+
*
|
|
26
|
+
* 给进度 / 动画 / 滚动位置这类高频且「中间值没有意义」的事件用;需要每条都到达的场合继续 `emit`。
|
|
27
|
+
* 能力校验与 `emit` 共用一份 `events` 白名单(不额外放行)。与 Tauri v2 的差异:它核心不做合并,
|
|
28
|
+
* 高频事件要么应用层自己限流、要么改用 `Channel`——我们的选择是给一条显式的 API,
|
|
29
|
+
* 理由与源码级对照见 docs/IPC-通信机制.md §6 第 5 项。
|
|
30
|
+
*/
|
|
31
|
+
public func emitLatest(event: String, payload: JsonValue): Unit {
|
|
32
|
+
this.hub.emitLatest(event, payload, "")
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* 向指定窗口推送事件(对标 Tauri 的 `app.emit_to(label, event, payload)`)
|
|
37
|
+
*
|
|
38
|
+
* 多窗口的定向投递入口。label 未注册时**什么都不投**并打一条 stderr——不回落成广播:
|
|
39
|
+
* 写错 label 的表现就该是「谁都没收到」,静默变广播比丢事件更难查(见 WindowRegistry.targets)。
|
|
40
|
+
* 空串 = 广播,与 `emit` 等价。
|
|
41
|
+
*/
|
|
42
|
+
public func emitToWindow(event: String, payload: JsonValue, window: String): Unit {
|
|
43
|
+
this.hub.emitToWindow(event, payload, window)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* 注册后端事件监听器:前端 `emit(event, payload)` 会调用到这里
|
|
48
|
+
* (对标 Tauri Rust 侧的 `app.listen(...)`)
|
|
49
|
+
*
|
|
50
|
+
* 与 `emit` 是相反方向:`emit` 是后端 → 前端,本方法收前端 → 后端。
|
|
51
|
+
* 后端自己 emit 出去的事件**不**回流到监听器(否则「收到就再广播」会自激)。
|
|
52
|
+
* 装配期调用(`run()` / `runUrl()` 之前,或插件的 setup() 里);注册本身不需要能力授权——
|
|
53
|
+
* 权限卡在前端那侧(emit 的事件必须在 capability 的 events 里声明)。
|
|
54
|
+
*/
|
|
55
|
+
public func listenEvent(event: String, handler: (JsonValue) -> Unit): TauriApp {
|
|
56
|
+
this.hub.listenEvent(event, handler)
|
|
57
|
+
return this
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* 应用层的 shell 事件钩子(对标 Tauri 的 `on_menu_event`)
|
|
62
|
+
*
|
|
63
|
+
* 回调参数是 `(信封 JSON, 发生事件的窗口 label)`。给原始 JSON 而不是解析后的对象:钩子多半
|
|
64
|
+
* 是应用自己写的诊断 / 日志(探针就靠它断言),原样透传最不容易丢信息。label 是必需的——
|
|
65
|
+
* 钩子是应用级的,一份回调要能分辨事件来自哪个窗口。
|
|
66
|
+
*
|
|
67
|
+
* 与前端事件**两条路都走**:先按注册顺序跑钩子(应用侧决策,例如改菜单项状态),再投前端
|
|
68
|
+
* 事件(页面自己 `listen("menu:click", …)`)。钩子里抛异常只留一行 stderr,不挡后面的钩子
|
|
69
|
+
* 与前端事件。
|
|
70
|
+
*/
|
|
71
|
+
public func onShellEvent(handler: (String, String) -> Unit): TauriApp {
|
|
72
|
+
this.shellHooks.add(handler)
|
|
73
|
+
return this
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* 装配本窗口的 shell 事件分发(应用层是唯一安装者)
|
|
78
|
+
*
|
|
79
|
+
* 必须在宿主 `start()` / `startUrl()` **之前**装:窗口一建出来菜单点击就可能来,装晚了会漏。
|
|
80
|
+
* 宿主层的 `setShellHandler` 是**可变槽位**(后装覆盖先装),所以应用要处理 shell 事件请用
|
|
81
|
+
* `onShellEvent()`(应用级钩子,可叠加多条),不要和框架抢宿主槽位——抢赢的那个会让框架的
|
|
82
|
+
* 前端事件分发失效。
|
|
83
|
+
*/
|
|
84
|
+
func wireShellEvents(host: WebViewHost, label: String): Unit {
|
|
85
|
+
host.setShellHandler({ json => this.dispatchShellEvent(label, json) })
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* shell 事件 → 应用层钩子 + 前端事件(按窗口投递)
|
|
90
|
+
*
|
|
91
|
+
* 路由靠**闭包捕获的 label**:C 侧已按宿主句柄把事件分给了本窗口的回调(见 host.cj 的
|
|
92
|
+
* `ShellRoute`),这里再按 label 投前端事件,多窗口下不会串。事件放不放行由 capability 清单
|
|
93
|
+
* 决定——没在清单里声明 `menu:click` 就是静默丢弃(最小权限不破)。
|
|
94
|
+
*
|
|
95
|
+
* 本函数跑在**宿主 UI 线程**上(C 桥的 shell 回调就在那儿),所以中途不碰 GTK / WebKit 之外
|
|
96
|
+
* 的东西:投递走 `emitToWindow` → `jsSink`,与其它事件同一条路。
|
|
97
|
+
*/
|
|
98
|
+
private func dispatchShellEvent(label: String, json: String): Unit {
|
|
99
|
+
for (hook in this.shellHooks) {
|
|
100
|
+
try {
|
|
101
|
+
hook(json, label)
|
|
102
|
+
} catch (e: Exception) {
|
|
103
|
+
eprintln("[cj-tauri] shell hook 抛出异常(已忽略):${e}")
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
match (parseShellEnvelope(json)) {
|
|
107
|
+
case None => eprintln("[cj-tauri] shell event 信封解析失败(已丢弃):${json}")
|
|
108
|
+
case Some(obj) =>
|
|
109
|
+
var kind = ""
|
|
110
|
+
if (let Some(k) <- shellString(obj, "kind")) {
|
|
111
|
+
kind = k
|
|
112
|
+
}
|
|
113
|
+
match (shellEventNameFor(kind)) {
|
|
114
|
+
case None => eprintln("[cj-tauri] shell event: 未知 kind \"${kind}\"(未投递):${json}")
|
|
115
|
+
case Some(event) =>
|
|
116
|
+
if (kind == "menu") {
|
|
117
|
+
this.syncMenuStateFromShellObj(label, obj)
|
|
118
|
+
}
|
|
119
|
+
try {
|
|
120
|
+
this.hub.emitToWindow(event, obj, label)
|
|
121
|
+
} catch (e: Exception) {
|
|
122
|
+
eprintln("[cj-tauri] shell event 投递失败(已忽略):${e}")
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* 菜单事件里的平台真值 → 应用层模型:id / enabled / checked 三者齐了才写
|
|
130
|
+
*
|
|
131
|
+
* 缺字段不拿默认值顶(见 shellBool 的注释)——这条路径写错的表现是「勾选莫名回退」,
|
|
132
|
+
* 比少同步一次难查得多。
|
|
133
|
+
*/
|
|
134
|
+
private func syncMenuStateFromShellObj(label: String, obj: JsonObject): Unit {
|
|
135
|
+
if (let Some(id) <- shellString(obj, "id")) {
|
|
136
|
+
if (let Some(enabled) <- shellBool(obj, "enabled")) {
|
|
137
|
+
if (let Some(checked) <- shellBool(obj, "checked")) {
|
|
138
|
+
this.syncMenuStateFromShell(label, id, enabled, checked)
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* 把一段回执 / 事件投递给目标窗口(批次 2 的 label 路由)
|
|
146
|
+
*
|
|
147
|
+
* @param window 目标窗口 label;空串 = 广播(按登记顺序投给每个窗口)
|
|
148
|
+
*
|
|
149
|
+
* 未注册的 label 打 stderr 而非静默丢弃:`emitToWindow` 写错 label 的表现是「前端什么都没
|
|
150
|
+
* 收到」,只剩这条日志能定位。空注册表(清单导出模式等)到不了这里——那时没有页面能发消息。
|
|
151
|
+
*/
|
|
152
|
+
func deliverJs(json: String, window: String): Unit {
|
|
153
|
+
// 直派:JSON 本身就是合法 JS 对象字面量,直接嵌进 eval 交给桥的 _dispatch。
|
|
154
|
+
// 不走 window.postMessage —— 那样每条响应要多一次结构化克隆 + 一次 message 事件派发,
|
|
155
|
+
// 而且那个监听器是全页面(含 iframe / opener)都能投递的伪造入口。
|
|
156
|
+
let js = "window.__CJ_TAURI__._dispatch(${json});"
|
|
157
|
+
let targets = this.windows.targets(window)
|
|
158
|
+
if (targets.size == 0 && window.size > 0) {
|
|
159
|
+
eprintln("[cj-tauri] 未注册的窗口 label:\"${window}\"(回执 / 事件未投递)")
|
|
160
|
+
return
|
|
161
|
+
}
|
|
162
|
+
for (rec in targets) {
|
|
163
|
+
rec.host.runJs(js)
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
package cjTauri
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* TauriApp 的宿主能力预置 API(`src/app.cj` 的同包分片)
|
|
5
|
+
*
|
|
6
|
+
* 这一组方法的共同点:都是「必须赶在页面加载前定稿」的预置类能力(RFC-002 §5.2),
|
|
7
|
+
* 或者是对它们做运行期微调(菜单项状态、窗口可见性)。
|
|
8
|
+
*
|
|
9
|
+
* 为什么拆出来:模型(`src/menu.cj` / `tray.cj` / `hotkey.cj` / `dragdrop.cj`)已经各自成文件,
|
|
10
|
+
* 而 TauriApp 里这些「把模型交给宿主」的薄方法原本和启动、事件、IPC 装配挤在同一个文件里。
|
|
11
|
+
* 拆开只是同包换文件:包边界与对外 API 一行未变(`extend` 补齐成员)。
|
|
12
|
+
*
|
|
13
|
+
* 同包拆分的代价(有意接受):`extend` 碰不到 `private` 成员,所以被跨文件使用的字段与少数
|
|
14
|
+
* 内部方法在 `app.cj` 里放宽成包内可见——对包外(应用 / 插件 / 测试子包)完全不可见。
|
|
15
|
+
*/
|
|
16
|
+
extend TauriApp {
|
|
17
|
+
/**
|
|
18
|
+
* 设置**默认窗口**的菜单栏(对标 Tauri 的 `Menu::set_as_app_menu` / `Window::set_menu`)
|
|
19
|
+
*
|
|
20
|
+
* 建议在 `run()` / `runUrl()` **之前**调用:预置类宿主能力要赶在页面加载前定稿
|
|
21
|
+
* (RFC-002 §5.2)。之后调用也能用(宿主会把整棵树重建到 UI 线程上),只是页面上会看到
|
|
22
|
+
* 菜单「晚一步」出现。
|
|
23
|
+
*
|
|
24
|
+
* 应用层会把模型留一份(见 `setMenuTo`),这样「只改某一维状态」的 API 才有
|
|
25
|
+
* 「另一维现在是什么」可查——平台侧一次调用会同时写 enabled 与 checked。
|
|
26
|
+
*/
|
|
27
|
+
public func setMenu(menu: MenuModel): TauriApp {
|
|
28
|
+
return this.setMenuTo(DEFAULT_WINDOW_LABEL, menu)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* 设置**指定窗口**的菜单栏(多窗口)
|
|
33
|
+
*
|
|
34
|
+
* 同一个 `MenuModel` 实例给多个窗口时,两窗留存的「模型状态」是**同一个对象**——运行期改
|
|
35
|
+
* 一个窗口的勾选会顺带改到另一个窗口的模型(平台上两份菜单仍是独立的)。要各自独立,
|
|
36
|
+
* 就每个窗口各建一份模型。
|
|
37
|
+
*/
|
|
38
|
+
public func setMenuTo(label: String, menu: MenuModel): TauriApp {
|
|
39
|
+
match (this.hostOf(label)) {
|
|
40
|
+
case Some(host) =>
|
|
41
|
+
this.menus.add(label, menu)
|
|
42
|
+
host.setMenu(menu.toWire())
|
|
43
|
+
case None =>
|
|
44
|
+
eprintln("[cj-tauri] setMenu: 未注册的窗口 label \"${label}\"(菜单未设置)")
|
|
45
|
+
}
|
|
46
|
+
return this
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* 运行期改默认窗口某个菜单项的状态(enabled 与 checked 一起写)
|
|
51
|
+
*
|
|
52
|
+
* 只想改其中一维就用 `setMenuItemEnabled` / `setMenuItemChecked`——它们会拿模型里的
|
|
53
|
+
* 另一维补齐,免得把用户刚点出来的勾选抹掉。
|
|
54
|
+
*/
|
|
55
|
+
public func setMenuItemState(id: String, enabled: Bool, checked: Bool): TauriApp {
|
|
56
|
+
return this.setMenuItemStateTo(DEFAULT_WINDOW_LABEL, id, enabled, checked)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** 指定窗口版本(多窗口):语义同 `setMenuItemState`,作用于 `label` 那个窗口 */
|
|
60
|
+
public func setMenuItemStateTo(label: String, id: String, enabled: Bool, checked: Bool): TauriApp {
|
|
61
|
+
this.applyMenuState(label, id, enabled, checked)
|
|
62
|
+
return this
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* 只改默认窗口某个菜单项的**启用**状态;勾选态取自应用层留存的模型
|
|
67
|
+
*
|
|
68
|
+
* 模型里没有这个 id 时抛 `CommandException`(**不**静默拿 false 顶上):静默写会抹掉
|
|
69
|
+
* 用户看到的勾选,报错至少说得清「哪个 id 在哪个窗口找不到」。
|
|
70
|
+
*/
|
|
71
|
+
public func setMenuItemEnabled(id: String, enabled: Bool): TauriApp {
|
|
72
|
+
return this.setMenuItemEnabledTo(DEFAULT_WINDOW_LABEL, id, enabled)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** 指定窗口版本(多窗口):语义同 `setMenuItemEnabled` */
|
|
76
|
+
public func setMenuItemEnabledTo(label: String, id: String, enabled: Bool): TauriApp {
|
|
77
|
+
let cur = this.requireMenuItem(label, id)
|
|
78
|
+
this.applyMenuState(label, id, enabled, cur.checked)
|
|
79
|
+
return this
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** 只改默认窗口某个菜单项的**勾选**状态;启用态取自应用层留存的模型 */
|
|
83
|
+
public func setMenuItemChecked(id: String, checked: Bool): TauriApp {
|
|
84
|
+
return this.setMenuItemCheckedTo(DEFAULT_WINDOW_LABEL, id, checked)
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** 指定窗口版本(多窗口):语义同 `setMenuItemChecked` */
|
|
88
|
+
public func setMenuItemCheckedTo(label: String, id: String, checked: Bool): TauriApp {
|
|
89
|
+
let cur = this.requireMenuItem(label, id)
|
|
90
|
+
this.applyMenuState(label, id, cur.enabled, checked)
|
|
91
|
+
return this
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* 取「另一维状态」的当前值(从应用层留存的模型里查)
|
|
96
|
+
*
|
|
97
|
+
* 模型不存在通常意味着应用用 `WebViewHost.setMenu()` 直设了菜单、绕过了应用层——
|
|
98
|
+
* 报错里点名这条出路,比让人对着「状态怎么被抹了」猜要好。
|
|
99
|
+
*/
|
|
100
|
+
private func requireMenuItem(label: String, id: String): MenuItem {
|
|
101
|
+
match (this.menus.get(label)) {
|
|
102
|
+
case None =>
|
|
103
|
+
throw CommandException(
|
|
104
|
+
"菜单模型不存在:窗口 \"${label}\" 的菜单是用 app.setMenu() 设的吗?" +
|
|
105
|
+
"(用 WebViewHost.setMenu 直设的菜单应用层没有模型,改单项状态请用 setMenuItemStateTo)"
|
|
106
|
+
)
|
|
107
|
+
case Some(model) =>
|
|
108
|
+
match (model.findById(id)) {
|
|
109
|
+
case Some(item) => return item
|
|
110
|
+
case None => throw CommandException("菜单项不存在:\"${id}\"(窗口 \"${label}\")")
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* 写菜单项状态:平台侧 + 应用层模型同步(模型里的 item 是可变对象,见 src/menu.cj)
|
|
117
|
+
*
|
|
118
|
+
* 模型不跟上会出错:勾选项在平台上一被点就自己翻了 checked,模型若还记旧值,
|
|
119
|
+
* 下一次「只改启用」就会拿旧值去写,把刚点出来的勾选抹回去。
|
|
120
|
+
* 模型缺失只提示、不拦——平台侧照写(与 `WebViewHost.setMenuItemState` 的「找不到静默忽略」一致)。
|
|
121
|
+
*/
|
|
122
|
+
private func applyMenuState(label: String, id: String, enabled: Bool, checked: Bool): Unit {
|
|
123
|
+
match (this.menus.get(label)) {
|
|
124
|
+
case Some(model) =>
|
|
125
|
+
match (model.findById(id)) {
|
|
126
|
+
case Some(item) =>
|
|
127
|
+
item.enabled = enabled
|
|
128
|
+
item.checked = checked
|
|
129
|
+
case None => eprintln("[cj-tauri] setMenuItemState: 模型里没有 \"${id}\"(只改平台侧)")
|
|
130
|
+
}
|
|
131
|
+
case None => eprintln("[cj-tauri] setMenuItemState: 窗口 \"${label}\" 无应用层菜单模型(只改平台侧)")
|
|
132
|
+
}
|
|
133
|
+
match (this.hostOf(label)) {
|
|
134
|
+
case Some(host) => host.setMenuItemState(id, enabled, checked)
|
|
135
|
+
case None => eprintln("[cj-tauri] setMenuItemState: 未注册的窗口 label \"${label}\"(未生效)")
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* 把 shell 事件里的**平台真值**回写到应用层模型(kind=menu、id 命中时)
|
|
141
|
+
*
|
|
142
|
+
* 为什么必须做:勾选项在平台上一被点就自己翻了 checked,模型还记着旧值——之后一次
|
|
143
|
+
* 「只改启用」拿着旧值去写,就把用户刚点出来的勾选抹回去了。
|
|
144
|
+
* 模型 / 项不存在时静默:事件也可能来自没用应用层菜单的窗口(宿主层直设菜单的老路径)。
|
|
145
|
+
*
|
|
146
|
+
* 从 `app_events.cj` 的 shell 分发调用,所以不写 `private`(同包跨文件可见)。
|
|
147
|
+
*/
|
|
148
|
+
func syncMenuStateFromShell(label: String, id: String, enabled: Bool, checked: Bool): Unit {
|
|
149
|
+
match (this.menus.get(label)) {
|
|
150
|
+
case Some(model) =>
|
|
151
|
+
match (model.findById(id)) {
|
|
152
|
+
case Some(item) =>
|
|
153
|
+
item.enabled = enabled
|
|
154
|
+
item.checked = checked
|
|
155
|
+
case None => ()
|
|
156
|
+
}
|
|
157
|
+
case None => ()
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* 同上:拖放 / 全局快捷键 / 托盘 / 窗口可见性 / 日志落盘
|
|
164
|
+
*
|
|
165
|
+
* 分成两个 `extend` 块只为控制单块长度,语义上同属「宿主能力预置」这一组。
|
|
166
|
+
*/
|
|
167
|
+
extend TauriApp {
|
|
168
|
+
/**
|
|
169
|
+
* 设置默认窗口的拖放策略(RFC-002 §5;必须在 `start()` / `startUrl()` 之前调)
|
|
170
|
+
*
|
|
171
|
+
* 打开后页面经 `dragdrop:files` 事件收到拖入的文件路径数组(`paths: [...]`),
|
|
172
|
+
* 该事件需在能力清单 `events` 里声明;不设 = 不注册 drop target(默认关)。
|
|
173
|
+
* 平台没有该能力(如 Windows 桩阶段)时宿主只记日志,不报错——能力位经 `system:host` 如实上报。
|
|
174
|
+
*/
|
|
175
|
+
public func setDragDrop(policy: DragDropPolicy): TauriApp {
|
|
176
|
+
return this.setDragDropTo(DEFAULT_WINDOW_LABEL, policy)
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/** 指定窗口版本(多窗口):语义同 `setDragDrop`,作用于 `label` 那个窗口 */
|
|
180
|
+
public func setDragDropTo(label: String, policy: DragDropPolicy): TauriApp {
|
|
181
|
+
match (this.hostOf(label)) {
|
|
182
|
+
case Some(host) =>
|
|
183
|
+
host.setDragDropPolicy(policy.enabled, policy.files)
|
|
184
|
+
case None =>
|
|
185
|
+
eprintln("[cj-tauri] setDragDrop: 未注册的窗口 label \"${label}\"(策略未设置)")
|
|
186
|
+
}
|
|
187
|
+
return this
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* 注册**默认窗口**的全局快捷键(RFC-003 第 5 期)
|
|
192
|
+
*
|
|
193
|
+
* 全局 = 焦点不在本应用时也生效(Linux = X11 grab,能力位 `hotkey` 如实上报)。
|
|
194
|
+
* 触发时页面收 `hotkey:press` 事件(载荷 `{"id":…}`,需在能力清单 `events` 声明);
|
|
195
|
+
* 应用侧钩子挂 `onShellEvent`(信封 `kind == "hotkey"`)。运行期再调 = 整表替换。
|
|
196
|
+
*/
|
|
197
|
+
public func setGlobalHotkeys(keys: HotkeyModel): TauriApp {
|
|
198
|
+
return this.setGlobalHotkeysTo(DEFAULT_WINDOW_LABEL, keys)
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/** 指定窗口版本(多窗口):语义同 `setGlobalHotkeys`,作用于 `label` 那个窗口 */
|
|
202
|
+
public func setGlobalHotkeysTo(label: String, keys: HotkeyModel): TauriApp {
|
|
203
|
+
match (this.hostOf(label)) {
|
|
204
|
+
case Some(host) =>
|
|
205
|
+
host.setGlobalHotkeys(keys.toWire())
|
|
206
|
+
case None =>
|
|
207
|
+
eprintln("[cj-tauri] setGlobalHotkeys: 未注册的窗口 label \"${label}\"(快捷键未设置)")
|
|
208
|
+
}
|
|
209
|
+
return this
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* 设置**托盘**(RFC-002 §7.C;托盘是应用级资源,走默认窗口的宿主)
|
|
214
|
+
*
|
|
215
|
+
* 图标 + 提示 + 右键菜单一次给定;重复调用 = 整体重建,`TrayModel("")` = 移除。
|
|
216
|
+
* 菜单项被点时页面收 `tray:click` 事件(载荷 `{"id":…}`,需在能力清单 `events` 声明);
|
|
217
|
+
* 应用侧钩子挂 `onShellEvent`(信封 `kind == "tray"`)。平台不支持时桥如实打日志、托盘不出现。
|
|
218
|
+
*/
|
|
219
|
+
public func setTray(tray: TrayModel): TauriApp {
|
|
220
|
+
match (this.hostOf(DEFAULT_WINDOW_LABEL)) {
|
|
221
|
+
case Some(host) =>
|
|
222
|
+
host.setTray(tray.toWire())
|
|
223
|
+
case None =>
|
|
224
|
+
eprintln("[cj-tauri] setTray: 默认窗口未注册(托盘未设置)")
|
|
225
|
+
}
|
|
226
|
+
return this
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* 开启日志落盘(RFC-003 第 5 期)
|
|
231
|
+
*
|
|
232
|
+
* 单文件产物是 Windows 的 GUI 子系统、双击时没有控制台,stderr 无处可去——开了它,C 桥与
|
|
233
|
+
* 仓颉侧**两侧**的诊断都落到这一个文件(`system:host` 的 `logFile` 回读生效路径)。
|
|
234
|
+
* **越早调越好**(宿主的装配期日志比 `run()` 还早):最晚在 `run()` / `runUrl()` 之前。
|
|
235
|
+
* 超预算按份轮转(`log.1` … `log.keep`);不调用 = 不落盘,行为与之前完全一致。
|
|
236
|
+
* `CJ_TAURI_LOG_MAX_KB` / `CJ_TAURI_LOG_KEEP` 可覆盖这里的预算——报障时让用户改环境变量,
|
|
237
|
+
* 不必动代码。被拒(路径打不开 / 已激活)时桥打一行 stderr 并**保持不落盘**,这里不假装成功。
|
|
238
|
+
*/
|
|
239
|
+
public func setLogFile(path: String): TauriApp {
|
|
240
|
+
return this.setLogFile(path, CJ_LOG_DEFAULT_MAX_KB, CJ_LOG_DEFAULT_KEEP)
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/** 显式给预算的版本(仓颉没有默认参数值,所以拆成两个重载) */
|
|
244
|
+
public func setLogFile(path: String, maxKB: Int64, keep: Int64): TauriApp {
|
|
245
|
+
let kb = logBudgetFromEnv("CJ_TAURI_LOG_MAX_KB", maxKB)
|
|
246
|
+
let kp = logBudgetFromEnv("CJ_TAURI_LOG_KEEP", keep)
|
|
247
|
+
let _ = enableLogFile(path, kb, kp) // 成败都由桥出声:成功落文件、被拒落 stderr
|
|
248
|
+
return this
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* 落到平台惯例目录的日志文件(`appName` 作文件名)
|
|
253
|
+
*
|
|
254
|
+
* Linux `$XDG_STATE_HOME/cj-tauri/<app>.log`(缺省 `~/.local/state/…`)、
|
|
255
|
+
* Windows `%LOCALAPPDATA%\cj-tauri\logs\<app>.log`;`CJ_TAURI_LOG` 给了路径时优先用它——
|
|
256
|
+
* 报障的人只需带一个环境变量启动,不必知道平台目录在哪(也不受应用是否设过名字影响)。
|
|
257
|
+
*/
|
|
258
|
+
public func setLogToDefaultFile(appName: String): TauriApp {
|
|
259
|
+
return this.setLogToDefaultFile(appName, CJ_LOG_DEFAULT_MAX_KB, CJ_LOG_DEFAULT_KEEP)
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/** 显式给预算的版本(同上,重载而非默认参数) */
|
|
263
|
+
public func setLogToDefaultFile(appName: String, maxKB: Int64, keep: Int64): TauriApp {
|
|
264
|
+
return this.setLogFile(logPathOrDefault(logEnvOr("CJ_TAURI_LOG", ""), appName), maxKB, keep)
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* 显示 / 隐藏**默认窗口**(对标 Tauri 2 的 `window.show()` / `window.hide()`)
|
|
269
|
+
*
|
|
270
|
+
* 典型用途是「最小化到托盘」的还原路径:托盘菜单项或全局快捷键触发时 `setWindowVisible(true)`。
|
|
271
|
+
* `run()` / `runUrl()` 之前调用同样有效(宿主记下,建窗时定稿)。
|
|
272
|
+
*/
|
|
273
|
+
public func setWindowVisible(visible: Bool): TauriApp {
|
|
274
|
+
return this.setWindowVisibleTo(DEFAULT_WINDOW_LABEL, visible)
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/** 指定窗口版本(多窗口):语义同 `setWindowVisible`,作用于 `label` 那个窗口 */
|
|
278
|
+
public func setWindowVisibleTo(label: String, visible: Bool): TauriApp {
|
|
279
|
+
match (this.hostOf(label)) {
|
|
280
|
+
case Some(host) =>
|
|
281
|
+
host.setWindowVisible(visible)
|
|
282
|
+
case None =>
|
|
283
|
+
eprintln("[cj-tauri] setWindowVisible: 未注册的窗口 label \"${label}\"(窗口未改可见性)")
|
|
284
|
+
}
|
|
285
|
+
return this
|
|
286
|
+
}
|
|
287
|
+
}
|