逐文件精读源码生成。行号基于当前工作区版本,仅用于快速定位。 本文件覆盖:应用入口链(main → lib::run)、托盘、构建脚本、Cargo 依赖、Tauri 配置、ACL capability,以及签名测试示例。
职责:二进制入口(6 行)。仅设置 Windows 子系统属性后委托给库 crate 的 run(),不含任何业务逻辑。
导出:无(binary crate)。
主要依赖:endprotocol_lib(即 src-tauri/src/lib.rs,lib 名为 endprotocol_lib)。
| 符号 | 位置 | 说明 |
|---|---|---|
windows_subsystem 属性 |
main.rs:5 | 无条件 #:release 与 debug 都不再创建控制台,被 Steam / 快捷方式等无控制台父进程拉起时不会闪现黑窗口;debug 下 run() 开头用 AttachConsole(ATTACH_PARENT_PROCESS) 附接父终端,cargo run 的控制台日志仍可输出 |
main |
main.rs:4 | 调用 endprotocol_lib::run()(main.rs:5) |
备注:cargo run/pnpm tauri dev 的实际逻辑全部在 lib.rs。
职责:应用核心装配点。入口首先截获已有提权 helper,安装全局 panic hook,再由单实例插件处理后续启动请求。--silent 分支不创建 WebView、不注册完整应用插件和业务服务,只初始化配置、单实例和图标托盘;普通分支才初始化完整服务并动态创建主窗口/托盘面板。游戏启动继续复用 launcher_start_game。两种模式都在 setup 中保存 AppHandle 供崩溃广播,并调用 utils::game_playtime::init() 启动游戏在线时长统计,在 RunEvent::Exit 时 utils::game_playtime::finalize() 落盘。
导出:pub fn run()(唯一公开项);其余为私有模块声明。
主要依赖:commands、models、services、tray、utils 模块;services::{account_service, avatar_cache_service, config_service, gacha_service, game_launcher_service, network_service, skland_service};tauri、tokio::sync::Mutex、std::sync::Arc。
| 符号 | 位置 | 说明 |
|---|---|---|
mod commands/models/services/tray/utils |
lib.rs:7-11 | 后端模块声明(全部私有) |
use services::* |
lib.rs:14-21 | 导入 8 个服务类型(Account/AvatarCache/Config/Gacha/GameLauncher/Network/Skland/SystemResource) |
run |
lib.rs:28 | 解析 --silent / --launch-game / --dx11:仅 --silent → 直接返回(不做任何事);--silent --launch-game → 不可见驻守实例(无托盘/无窗口/不计时/不注册单实例插件);仅 --launch-game 或普通启动 → 完整模式(单实例)并在需要时启动游戏。完整模式在 RunEvent::Exit 调用 utils::game_playtime::finalize() |
utils::crash::install_panic_hook() |
lib.rs:54 | 在 helper 截获与 --silent 空操作检查之后、业务初始化之前安装 panic hook;崩溃写入 logs/crashes/crash-*.log 并广播 app:crash |
create_config_service |
lib.rs:335 | 初始化现有 ConfigService,并校验/更新配置键 main_exe_path |
crash::set_app_handle / api_health::init |
lib.rs:65 / 246 | 驻守/完整模式设置崩溃广播 handle;完整模式额外初始化 API 熔断器,二者均在 .setup() 最前调用 |
attach_parent_console(私有,仅 Windows debug) |
lib.rs:327 | main.rs 已无条件使用 GUI 子系统,进程本身不再创建控制台(根除黑窗口闪现:控制台在进程创建期生成,进程内 ShowWindow(SW_HIDE) 只能事后隐藏、仍会闪一瞬间)。此函数在 run() 最开头(logger / Tauri 初始化之前)尝试 AttachConsole(ATTACH_PARENT_PROCESS):手动 cargo run 时附接父终端使日志可见,被 Steam / 快捷方式拉起时附接失败即静默无窗口。附接必须发生在首次 stdio 写入前(eprintln! 在 GUI 进程无控制台时会 panic,所有 stderr 写入均需经 logger::safe_eprintln!) |
app_ready_for_full_instance / dispatch_pending_requests |
lib.rs:431 / 440 | 完整模式 setup 完成后标记就绪,并处理插件在 setup 完成前排队的第二实例请求 |
dispatch_instance_request |
lib.rs:459 | 主实例收到第二实例转交的参数:含 --launch-game 时以 --dx11 转发启动游戏(exit_when_game_ends=false),随后显示主窗口 |
wait_for_game_session(async) |
lib.rs:474 | 驻守实例的驻留等待:每 2 秒通过 utils::process::is_process_running(窗口/90 秒启动宽限判定,排除无窗口挂起残留)探测传入的游戏进程名(终末地 Endfield.exe / 明日方舟 Arknights.exe,由 commands::launcher::channel_process_name 从渠道解析);检测到运行后等待其退出;若 120 秒内未出现(启动失败/被取消)也结束等待,供 Steam 判定「游戏中」并记录时长 |
start_game_from_config |
lib.rs:493 | 按 --game <endfield|arknights>(缺省终末地)读该游戏自己那一组配置键(launcher_install_path[_arknights] / launcher_channel[_arknights] / launcher_dx11[_arknights])并调用 commands::launcher::launch_game;dx11: Option<bool> 优先取命令行 --dx11,None 时回退该游戏的 DX11 配置(默认 false);exit_when_game_ends=true(仅驻守实例)时先按渠道算出要等的进程名,启动成功后调用 wait_for_game_session,游戏退出后才退出本进程;启动失败时同样退出 |
create_full_windows / exclude_from_alt_tab / create_main_window / app_webview_url / dev_asset_url |
lib.rs:584 / 614 / 654 / 712 / 760 | 完整模式按需创建窗口;debug 优先连 Vite,开发服务器不可用时改由自定义协议 endprotocol 提供 frontendDist 资源。create_main_window 返回 tauri::Window<R> 而非 WebviewWindow<R>,且判重用 app.get_window("main"):get_webview_window 要求窗口下所有 webview 的 label 都等于窗口 label,in-app browser 的子 webview 会让它返回 None。create_full_windows 在创建托盘面板后立即调用 exclude_from_alt_tab(见下一行) |
exclude_from_alt_tab(私有,仅 Windows 有实现) |
lib.rs:624 | 把辅助窗口移出 Windows 的 Alt+Tab 列表:skip_taskbar(true) 只让 tao 调 ITaskbarList::DeleteTab(摘任务栏按钮),不碰扩展样式;tao 又默认给窗口加 WS_EX_APPWINDOW,explorer(IsAltTabWindow 只认 WS_EX_TOOLWINDOW)会在窗口第一次显示时把它注册成 Alt+Tab 条目 —— 之后窗口隐藏条目仍在,表现为一张没有界面预览的空白卡片。做法:GetWindowLongW(GWL_EXSTYLE) → 去掉 WS_EX_APPWINDOW、加上 WS_EX_TOOLWINDOW → SetWindowLongW + SetWindowPos(SWP_FRAMECHANGED|SWP_NOMOVE|SWP_NOSIZE|SWP_NOZORDER|SWP_NOOWNERZORDER|SWP_NOACTIVATE)。非 Windows 平台是空实现。注意已注册进 explorer 的旧条目由 explorer 持有,改样式不会追回,需重启应用(进程退出时条目随之消失) |
register_dev_asset_protocol / dev_asset_error_response |
lib.rs:782 / 706 | debug 专用:注册 endprotocol URI scheme,经 asset_resolver() 提供构建产物(含 .html、目录 index.html 与入口 index.html 回退) |
.on_page_load(...)(Builder 钩子) |
lib.rs:268-285 | in-app browser 事件桥:把 label 以 inapp-browser- 开头的子 Webview 的 PageLoadEvent::Started/Finished 转成 browser://page-load 事件({ label, url, event })emit 给前端;其它 webview(主窗、tray-panel)直接 return,不产生事件 |
| 行号区间 | 内容 |
|---|---|
| lib.rs:27 | #[cfg_attr(mobile, tauri::mobile_entry_point)] 属性 |
| lib.rs:35-42 | 提权启动 helper 截获:utils::official_launcher::try_run_helper() 命中 --endfield-launch-helper 时以游戏退出码退出,不做 Tauri 初始化 |
| lib.rs:44-57 | 解析 --silent / --launch-game;仅 --silent 直接返回(不做任何事);随后安装 panic hook 并解析 --dx11(Option<bool>,仅与 --launch-game 组合生效) |
| lib.rs:59-75 | 驻守实例(--silent --launch-game):不注册单实例插件、不创建窗口/托盘、不初始化 game_playtime;初始化 config 后启动游戏,由 wait_for_game_session 驻留到游戏退出,再随进程结束退出 |
| lib.rs:77-241 | 完整模式:初始化日志、创建单实例插件、注册 updater/process/opener/fs 插件与完整命令集 |
| lib.rs:243-317 | 完整模式 setup:动态窗口、全量服务、原托盘面板和账户刷新;--launch-game 时启动游戏 |
| lib.rs:325-491 | 配置初始化、第二实例请求转发(仅主实例)、按需启动游戏与驻留等待(wait_for_game_session) |
| lib.rs:574-652 | 完整模式窗口动态创建与关闭行为(create_full_windows / exclude_from_alt_tab) |
| lib.rs:654-710 | create_main_window:主窗口创建与「关闭→隐藏/退出」行为 |
| lib.rs:712-799 | debug 专用:endprotocol 自定义协议,经 asset_resolver() 提供 frontendDist 构建产物 |
插件注册:
| 插件 | 行 | 对应 capability |
|---|---|---|
tauri_plugin_single_instance |
lib.rs:89(注册 lib.rs:100) | 仅完整模式启用;保证同时只有一个主实例;驻守实例(--silent --launch-game)不注册插件,可同时存在任意多个;Windows 命名互斥体默认按当前 Windows 会话隔离 |
tauri_plugin_updater |
lib.rs:101 | 仅完整模式,updater:default(desktop.json) |
tauri_plugin_process |
lib.rs:102 | 仅完整模式,process:allow-restart(desktop.json) |
tauri_plugin_opener |
lib.rs:103 | 仅完整模式,opener:default / opener:allow-open-url(default.json)、opener:allow-open-path(desktop.json) |
tauri_plugin_fs |
lib.rs:104 | 仅完整模式;未授予任何 fs:* 权限(见文末异常) |
稳定命令行参数:--game <endfield\|arknights> 选择要启动的游戏(缺省终末地);--silent 单独出现时不启动应用、不创建窗口/托盘/驻守进程,直接退出;--silent --launch-game 启动一个不可见驻守实例(无窗口、无托盘、不计时、不注册单实例插件)并启动 config 中配置的游戏,每 2 秒探测该渠道对应的游戏进程(终末地 Endfield.exe / 明日方舟 Arknights.exe),游戏退出(或 120 秒内未检测到游戏进程)后退出;仅 --launch-game 时以正常完整模式启动应用并一并启动游戏;--dx11 仅与 --launch-game 组合生效——存在时给游戏进程追加 -force-d3d11,不存在时使用应用内设置 launcher_dx11(默认 false)。完整模式注册单实例插件,因此同时只允许一个主实例;驻守实例彼此独立,且不受主实例或其它驻守实例启停影响。第二实例按普通启动方式转发给主实例并显示主窗口(--dx11 随请求转发)。debug 模式优先使用配置的 Vite devUrl;若开发服务器未运行,则在同一进程内注册自定义协议 endprotocol(Windows 为 http://endprotocol.localhost,其余平台为 endprotocol://localhost),由 asset_resolver() 回退读取 frontendDist 中构建好的文件(注册于 register_dev_asset_protocol),二者均不可用时返回明确错误。注意不能用 tauri://localhost / http://tauri.localhost:dev 构建不含内嵌资源,Tauri 协议处理器会直接报 asset not found。
| 步骤 | 行号 | 内容 |
|---|---|---|
| 1 | lib.rs:247-250 | 初始化 ConfigService,校验并更新 main_exe_path,.manage() 注册;随后 utils::game_playtime::init(config_service.clone()) 启动时长统计;再动态创建完整 WebView 窗口 |
| 2 | lib.rs:252-267 | 读配置键 capture_autostart(默认 false),为 true 时调用 utils::capture::start() 自动录制;成功/失败只记日志 |
| 3 | lib.rs:269-270 | tray::setup_tray(app.handle(), config_service),沿用完整模式的右键托盘面板 |
| 4 | lib.rs:273 | SklandService::new(config_service) — 不 manage,作为依赖注入下游 |
| 5 | lib.rs:276-277 | AvatarCacheService::new() — 不 manage |
| 6 | lib.rs:280-281 | GachaService::new(app.handle()) → .manage() |
| 7 | lib.rs:284 | NetworkService::new(skland, avatar_cache) — 不 manage |
| 8 | lib.rs:287-296 | AccountService::new(config, skland, avatar_cache, network, app_handle) 包进 Arc<tokio::sync::Mutex<>> → .manage() |
| 9 | lib.rs:299-300 | Arc::new(Mutex::new(GameLauncherService::new())) → .manage()(tokio Mutex) |
| 10 | lib.rs:302-305 | Arc::new(std::sync::Mutex::new(SystemResourceService::new())) → .manage() |
| 11 | lib.rs:308 | AccountService::start_auto_refresh(timer_service) 启动账户自动刷新定时器 |
| 12 | lib.rs:543 | 创建 main 窗口时挂 CloseRequested:按 close_action 配置决定退出或最小化托盘 |
静默/驻守模式初始化(lib.rs:59-75):仅 --silent 在进入 Tauri 之前直接返回(lib.rs:48-51),不做任何事;--silent --launch-game 创建不可见驻守实例:只初始化配置服务、记录/更新 main_exe_path 并启动游戏,不注册 IPC 命令与单实例插件,不创建窗口/托盘,不启动 logger、game_playtime、账户自动刷新、缓存、抽卡/系统资源服务;启动成功后由 wait_for_game_session 驻留等待游戏退出,再结束进程。
| State 类型 | 注册处 | 说明 |
|---|---|---|
Arc<std::sync::Mutex<ConfigService>> |
lib.rs:248 | 全局配置(JSON 文件 + 内存缓存),完整/驻守模式共用 |
Arc<GachaService> |
lib.rs:266 | 抽卡记录服务(持有 AppHandle) |
Arc<tokio::sync::Mutex<AccountService>> |
lib.rs:284 | 账户服务(异步方法,tokio 锁) |
Arc<GameLauncherService> |
lib.rs:379 | 游戏启动器服务(仅完整模式);内部 active_operation 自带 Arc<Mutex<…>>,无外层锁,写安装目录的长任务由内部 write_gate(容量 1 信号量)串行 |
Arc<std::sync::Mutex<SystemResourceService>> |
lib.rs:293 | 系统资源采集服务(仅完整模式) |
未 manage 的依赖服务:
SklandService、AvatarCacheService、NetworkService,仅在 setup 内部传递。
备注:lib.rs 本身没有任何 emit/listen,也没有全局应用菜单;完整模式右键托盘仍打开原有托盘面板。
全仓共 145 个
#[tauri::command]注解(其中 3 个未在完整模式注册),逐命令对照见 ipc_index.md。 《明日方舟》数据命令(get_arknights_player_info/get_arknights_cultivate/get_arknights_attendance/do_arknights_attendance)注册在 attendance 命令组之后。 抽卡命令中,get_arknights_gacha_categories注册在武器寻访命令组之后;其余 7 个抽卡命令按角色所属游戏在运行时自动分流(终末地GachaService/ 明日方舟ArknightsGachaService),注册表本身不区分游戏。
完整命令清单与定义/调用关系见 commands.md 和 ipc_index.md;驻守分支为纯 Builder::default().setup(...)(lib.rs:59-75),不注册单实例插件,也无 .invoke_handler。
| 事件名 | 发出位置 | 载荷 |
|---|---|---|
tray-user-info-updated |
tray.rs:173(仅发给 tray-panel 窗口) | TrayUserInfo |
launcher-progress |
commands/launcher.rs:74/117/168 | 启动器下载/校验进度 |
accounts-refreshed |
services/account_service.rs:812 | 账户刷新结果 |
download-progress |
commands/updater.rs:106 | 更新包下载进度 |
gacha-sync-progress |
services/gacha_service.rs:746(常量 GACHA_SYNC_PROGRESS_EVENT,定义于 gacha_service.rs:22) |
抽卡同步进度 |
launcher://switch-progress |
services/game_launcher_service.rs:2471 | 换渠道进度 |
launcher://progress |
services/game_launcher_service.rs:2472 | ActiveOperation::Switching(progress) |
后端 Rust 侧没有任何
listen(...);其余监听均在前端(如custom-titlebar.tsx监听tauri://resize、tauri://move)。
职责:系统托盘图标与托盘悬浮面板(tray-panel 窗口)的创建、定位、显隐与 tooltip 更新,并记录托盘是否真的可用(289 行)。仅在完整模式创建;--silent --launch-game 驻守实例不显示托盘。
导出:TrayUserInfo、show_main_window_focus、setup_tray、update_tray_menu、show_tray_panel、hide_tray_panel、is_tray_available;私有 show_panel_at_position、build_tray、tray_effectively_available、status_notifier_host_available。
主要依赖:tauri::tray::{TrayIconBuilder, TrayIconEvent, MouseButton}、tauri::{Emitter, Manager, Runtime}、windows-sys(仅 Windows 分支)、crate::services::config_service::ConfigService。
| 符号 | 位置 | 说明 |
|---|---|---|
TRAY_ID |
tray.rs:10 | "main-tray",托盘图标唯一 ID(app.tray_by_id 用) |
TRAY_PANEL_LABEL |
tray.rs:11 | "tray-panel",与动态创建的托盘面板窗口 label 对应 |
PANEL_WIDTH / PANEL_HEIGHT |
tray.rs:15-16 | 288.0 × 376.0,回退尺寸:仅在 outer_size() 取不到真实窗口尺寸时用于定位;实际窗口尺寸由 lib.rs::create_full_windows 的 inner_size(288.0, 376.0) 决定,改一处需同步另一处 |
TRAY_AVAILABLE / is_tray_available |
tray.rs:23 / :26 | 托盘可用性标志:setup_tray 成功后写入 tray_effectively_available() 的结果。minimize_to_tray、窗口关闭兜底与前端 tray_available 命令都读它;不可用时禁止隐藏驻留 |
tray_effectively_available / status_notifier_host_available(私有) |
tray.rs:228 / :245 | Windows/macOS 恒可用;Linux 用 gdbus(回退 dbus-send)查询会话总线是否存在 org.kde.StatusNotifierWatcher。两者都不可用时按「无托盘」处理,避免 GNOME 等环境把窗口藏进后台 |
struct TrayUserInfo |
tray.rs:30 | 托盘展示的角色数据,serde camelCase,13 个 Option 字段:role_id、nickname、avatar、account_level、cur_stamina、max_stamina、max_ts、daily_activation、max_daily_activation、weekly_score、weekly_total、bp_cur_level、bp_max_level;配置键 tray_user_info。account_level 是游戏账号等级(char_detail 的 base.level),供面板右上角徽标显示;bp_cur_level/bp_max_level 是通行证等级,二者不可混用 |
show_main_window_focus |
tray.rs:55 | 取或动态创建 main 窗口 → unminimize() + show();Windows 分支额外 ShowWindow(SW_SHOW) + SetForegroundWindow。取窗用 app.get_window("main")(非 get_webview_window),否则 in-app browser 打开时托盘唤不回主窗口 |
show_panel_at_position(私有) |
tray.rs:85 | 面板可见则隐藏;否则按真实窗口物理尺寸(panel.outer_size())居中定位于托盘图标上方 8px,并用 app.monitor_from_point 取所在显示器工作区做 clamp,防止面板越出屏幕;随后 show + focus |
setup_tray |
tray.rs:128 | 完整模式读配置 tray_user_info,创建唯一托盘图标;左键双击显示主窗并关面板,右键抬起弹出原有面板。创建成功才把 TRAY_AVAILABLE 置为 tray_effectively_available(),失败则置 false(lib.rs 不再因托盘失败而中止启动) |
build_tray |
tray.rs:153 | 唯一图标构建逻辑;左键双击显示主窗并隐藏面板,右键抬起弹出面板 |
update_tray_menu |
tray.rs:189 | 按 TrayUserInfo.nickname 更新 tooltip,并向 tray-panel 窗口发送 tray-user-info-updated |
show_tray_panel |
tray.rs:206 | 命令入口的 toggle 显隐(可见→hide,否则 show+focus) |
hide_tray_panel |
tray.rs:219 | 强制隐藏面板(无错误时返回 Ok) |
备注:托盘只在完整模式创建;--silent 实例不显示托盘、不进入托盘交接流程。update_tray_menu、show_tray_panel、hide_tray_panel 被 commands/tray.rs 调用。TrayUserInfo 由前端 src/utils/trayService.ts 组装后写入,新增字段需同时改该服务与 src/components/tray-panel.tsx 复用的同名类型。
定位不再直接用 PANEL_WIDTH/PANEL_HEIGHT 与物理坐标相减:inner_size 是逻辑尺寸,在 125%/150% 缩放下按常量硬算会明显偏移,故改为读取窗口真实的物理 outer_size()。
职责:构建脚本(3 行),仅调用 tauri_build::build() 生成 Tauri 的代码生成产物(IPC 模式、ACL schema 到 gen/schemas/、上下文等)。
导出:无(build script)。
| 符号 | 位置 | 说明 |
|---|---|---|
main |
build.rs:1 | 调用 tauri_build::build()(build.rs:2) |
备注:gen/schemas/(acl-manifests.json、capabilities.json、desktop-schema.json、windows-schema.json)由它在编译期生成。
职责:Rust 包清单(65 行):定义 lib/bin 双 target、构建依赖、全部运行时依赖、(无效的)链接参数与 dev profile。
导出:crate endprotocol_lib(staticlib + cdylib + rlib)与 bin endprotocol。
| Key | 行号 | 用途 |
|---|---|---|
package.name |
Cargo.toml:2 | endprotocol(bin 名) |
package.version |
:3 | 0.1.0,与 tauri.conf.json version 一致 |
package.description / authors / edition |
:4-6 | "A Tauri App" / ["you"] / 2021 |
lib.name |
:14 | endprotocol_lib — _lib 后缀避免与 bin 同名冲突(Windows 特有问题,见 :11-13 注释引用 rust-lang/cargo#8519) |
lib.crate-type |
:15 | ["staticlib", "cdylib", "rlib"](支持移动端静态链接 + 常规 Rust 引用) |
build-dependencies.tauri-build |
:17-18 | 2,features 空 |
dependencies.tauri |
:21 | ~2.12 + features tray-icon(托盘必需)、unstable(in-app browser 必需:new Webview() 走 `plugin:webview |
dependencies.tauri-plugin-opener / -fs |
:22-23 | 打开 URL/路径、文件系统插件(完整模式注册) |
dependencies.serde / serde_json |
:24-25 | 序列化(serde 带 derive) |
dependencies.reqwest |
:26 | 0.12 + json、stream(流式下载) |
dependencies.futures-util / urlencoding |
:27-28 | 异步流工具、URL 编码 |
dependencies.tokio |
:29 | 1 + full(异步运行时) |
dependencies.thiserror |
:30 | 错误类型派生 |
dependencies.dirs / chrono |
:31-32 | 系统目录、时间(chrono 带 serde) |
dependencies.tracing / tracing-subscriber |
:33-34 | 日志(subscriber 带 fmt) |
dependencies.aes/cbc/ecb/des |
:35-37,:42 | 对称加密(游戏资源解密) |
dependencies.hmac/sha2/md-5/digest |
:38-41 | 森空岛签名(HMAC-SHA256 + MD5)与通用摘要 |
dependencies.rsa |
:43 | RSA 加密(登录/凭证) |
dependencies.flate2 |
:44 | zlib 后端的 gzip 压缩/解压 |
dependencies.base64 / bytes / http |
:45-47 | 编码与 HTTP 基础类型 |
dependencies.windows-sys |
Cargo.toml | 0.61,仅 cfg(windows) 目标依赖;features:Win32_Foundation、Win32_Graphics_Gdi、Win32_UI_WindowsAndMessaging、Win32_System_Com、Win32_UI_Shell、Win32_System_Threading(托盘取前台窗口、取色、进程检测/终止)、Win32_System_Power / Win32_System_Performance(system_resource_service 采集实测 CPU 频率)、Win32_System_Diagnostics_ToolHelp(utils/process.rs 的 CreateToolhelp32Snapshot 进程枚举);免启动器启动额外需要:Win32_Security(CreateProcessW 的 SECURITY_ATTRIBUTES 参数门控)、Win32_Security_Cryptography + Win32_Security_Cryptography_Catalog + Win32_Security_Cryptography_Sip(WinVerifyTrust 数据结构与两个 WTHelper* 函数)、Win32_Security_WinTrust、Win32_System_Registry(Reg* 与 SHELLEXECUTEINFOW)、Win32_System_Environment、Win32_System_Console、Win32_System_Diagnostics_Debug、Win32_Storage_FileSystem(见 utils/official_launcher.rs) |
dependencies.sysinfo |
Cargo.toml | 系统资源采集(CPU/内存/磁盘/网络/温度传感器),见 services/system_resource_service.rs |
dependencies.uuid / hex / rand |
:49-51 | UUID v4、十六进制、随机数 |
dependencies.indexmap |
:52 | 有序 map(带 serde) |
dependencies.tauri-plugin-process |
:52 | 2.3.1,应用重启 |
dependencies.tauri-plugin-single-instance |
:53 | 2,第二实例参数转发;Windows 命名互斥体按登录会话命名空间隔离 |
dependencies.walkdir / rfd |
:55-56 | 目录遍历(校验/扫描)、原生文件对话框(游戏目录与用户选定的快捷方式保存位置) |
target.x86_64-pc-windows-msvc.rustflags |
:57-58 | 意图是 -fuse-ld=lld(lld 链接器)——该 key 在 Cargo.toml 中无效,见文末异常 |
target.'cfg(not(android,ios))'.dependencies |
:61-62 | tauri-plugin-updater = "2",仅桌面平台启用(完整模式注册) |
profile.dev.debug |
:64 | "line-tables-only" — 在 workspace 中被忽略(见异常) |
profile.dev.incremental |
:65 | true — 同上 |
| 用途 | crate |
|---|---|
| 框架/IPC | tauri、tauri-plugin-{opener,fs,process,updater}、tauri-build |
| 网络 | reqwest、futures-util、http、urlencoding |
| 数据 | serde、serde_json、chrono、indexmap、bytes、uuid、hex、rand |
| 加密/签名 | aes、cbc、ecb、des、rsa、hmac、sha2、md-5、digest、base64、flate2 |
| 系统/IO | dirs、walkdir、rfd;Windows 专用 windows-sys |
| 日志/错误 | tracing、tracing-subscriber、thiserror |
职责:Tauri v2 应用配置:应用标识、构建命令、安全策略、NSIS 打包与更新端点;app.windows 留空,由 Rust 入口按模式动态创建 WebView。
导出:无(JSON 配置)。
| Key | 行号 | 用途 |
|---|---|---|
$schema |
:2 | https://schema.tauri.app/config/2(Tauri 2 schema) |
productName |
:3 | endprotocol — 产品名(安装包名) |
version |
:4 | 0.1.0,与 Cargo.toml 同步 |
identifier |
:5 | cn.msk-network.endprotocol — 应用唯一标识(决定配置/数据目录) |
build.beforeDevCommand |
:7 | pnpm dev(启动 Vite) |
build.devUrl |
:8 | http://localhost:1420 |
build.beforeBuildCommand |
:9 | pnpm build |
build.frontendDist |
:10 | ../dist |
app.windows |
:13 | 空数组;主窗口与 tray-panel 仅在完整模式由 lib.rs:create_full_windows 创建,驻守模式不创建 WebView |
app.security.csp |
:15 | null — 关闭 CSP(未设置内容安全策略) |
bundle.active |
:19 | true — 启用打包 |
bundle.createUpdaterArtifacts |
:20 | true — 生成 updater 用的签名产物 |
bundle.targets |
未配置 | 使用 Tauri 当前构建平台的默认打包目标;保留 bundle.windows.nsis 的 Windows 安装器配置 |
bundle.icon |
:21-27 | 32/128/256 png、icon.icns(macOS)、icon.ico(Windows) |
bundle.windows.nsis.installerIcon / uninstallerIcon |
:31-32 | 均为 icons/icon.ico |
bundle.windows.nsis.installMode |
:34 | currentUser — 无需管理员权限的按用户安装 |
bundle.windows.nsis.startMenuFolder |
:36 | ENDPROTOCOL |
bundle.windows.nsis.languages |
:38 | ["SimpChinese", "English"] |
plugins.updater.pubkey |
:44 | minisign 公钥(dW50cnVzdGVkIGNvbW1lbnQ6... base64) |
plugins.updater.endpoints |
:45-49 | 三个更新源,按序回退:https://updates.msk-network.cn/stable/latest.json → https://updates.msk-network.cn/preview/latest.json → https://github.com/mon3tr-dev/endprotocol/releases/latest/download/latest.json |
职责:主窗口与托盘面板共用的基础 ACL 权限(30 行)。 导出:无。
| Key | 行号 | 用途 |
|---|---|---|
$schema |
:2 | ../gen/schemas/desktop-schema.json(由 build.rs 生成) |
identifier |
:3 | default |
description |
:4 | "Capability for the main window and tray panel" |
windows |
:5 | ["main", "tray-panel"] — 对两个窗口同时生效 |
permissions |
:6-28 | 见下表 |
| 权限 | 行 | 用途 |
|---|---|---|
core:default |
:7 | Tauri core 默认集(事件、资源等基础能力;含 core:webview:default 的 allow-get-all-webviews/allow-webview-position/allow-webview-size) |
core:window:allow-minimize |
:8 | 最小化 |
core:window:allow-maximize / allow-unmaximize |
:9-10 | 最大化/还原(配合 toggle_maximize_window 之外的前端 API) |
core:window:allow-close |
:11 | 关闭窗口 |
core:window:allow-show / allow-hide |
:12-13 | 显示/隐藏(托盘隐藏主窗、面板显隐) |
core:window:allow-is-visible |
:14 | 查询可见性 |
core:window:allow-set-focus |
:15 | 聚焦 |
core:window:allow-set-position |
:16 | 设定位置 |
core:window:allow-start-dragging |
:17 | 自绘标题栏/浏览器工具栏的 data-tauri-drag-region 拖动(Tauri 的 drag.js 经此 API 发起窗口拖动) |
core:window:allow-internal-toggle-maximize |
:18 | 拖动区双击切换最大化/还原(drag.js 内部走 internal_toggle_maximize) |
core:window:allow-primary-monitor |
:19 | 查询主显示器 |
core:window:allow-outer-position / allow-outer-size |
:20-21 | 查询窗口外框位置/尺寸(最大化状态判断等) |
core:webview:allow-create-webview |
:22 | in-app browser:new Webview() → plugin:webview|create_webview(不在 core:webview:default 内) |
core:webview:allow-webview-close |
:23 | 卸载/关闭时销毁子 Webview(Webview#close()) |
core:webview:allow-webview-hide |
:24 | 关闭 in-app browser 时先隐藏子 Webview 再卸载 DOM(Webview#hide(),不在 core:webview:default 内;缺它时 hide 被 ACL 拒绝且被 catch 吞掉,表现为「工具栏先消失、网页内容卡一下才消失」) |
core:webview:allow-set-webview-position |
:25 | 同步子 Webview 位置(Webview#setPosition()) |
core:webview:allow-set-webview-size |
:26 | 同步子 Webview 尺寸(Webview#setSize()) |
opener:default |
:27 | opener 插件默认集(含 reveal_item_in_dir、mailto/tel/http(s) URL) |
opener:allow-open-url |
:28 | 无 scope 限制地打开任意 URL |
备注:
- 窗口拖动走
data-tauri-drag-region(标题栏custom-titlebar.tsx、in-app browser 工具栏in-app-browser.tsx),因此需要allow-start-dragging(拖动)与allow-internal-toggle-maximize(拖动区双击最大化)。不要退回 CSS-webkit-app-region:它只在 Windows WebView2 123+ 生效,Linux 发行版用的 WebKitGTK 完全不认,那两块拖拽区在 Linux 上会毫无反应;drag.js会自动排除按钮等可点击元素,组件里因此不再逐个标no-drag(只有需要显式禁止的容器才写data-tauri-drag-region="false")。 - 未授予
core:webview:allow-webview-show:重新打开浏览器总是新建子 Webview(in-app-browser.tsx生命周期 effect),没有「隐藏后复用」路径,无需 show。 - 子 Webview 的
url/backgroundColor走创建参数(create_webview一次带上),因此不需要allow-set-webview-background-color;后退/前进/刷新/导航/取地址由commands/browser.rs的 app command 承担,而 app command 不经 capabilities 授权(本项目无__app-acl__manifest,local origin 下直接放行),所以此处无需为browser_*添加权限项。 windows只列main/tray-panel:能力按窗口匹配后对该窗口的全部 webview 生效,故inapp-browser-*子 webview 自动继承这几条core:webview:*权限(本会话只用主 webview 调它们,子 webview 是 remote origin,无法调用任何 app command)。
职责:桌面平台专属的补充权限(17 行):更新器、打开路径、重启。 导出:无。
| Key | 行号 | 用途 |
|---|---|---|
$schema |
— | 缺失(default.json 有,此文件没有;不影响生成) |
identifier |
:2 | desktop-capability |
platforms |
:3-7 | ["macOS", "windows", "linux"] — 仅桌面端生效(移动端不加载) |
windows |
:8-11 | ["main", "tray-panel"] |
permissions |
:12-16 | updater:default(检查/下载/安装更新全流程,配合 updateService.ts:1 check)、opener:allow-open-path(无 scope 打开路径)、process:allow-restart(配合 updateService.ts:837 relaunch()) |
备注:未授予 process:allow-exit;fs 插件已注册但两个 capability 文件均无任何 fs:* 权限。
职责:独立可运行示例(58 行,cargo run --example sign_test),用固定测试向量验证森空岛请求签名算法(HMAC-SHA256 → hex → MD5),并与 Python 实现的 header 顺序/紧凑格式对齐;用于调试对照,不参与应用编译产物。
导出:无(example binary)。
主要依赖:hmac、sha2、md-5(以 md5 名义)、hex、serde_json。
| 符号 | 位置 | 说明 |
|---|---|---|
type HmacSha256 |
sign_test.rs:8 | Hmac<Sha256> 别名 |
calculate_sign |
sign_test.rs:10 | 拼 h_ca header JSON(键序固定 platform/timestamp/dId/vName,vName: "1.45.1"、platform: "1",sign_test.rs:12-15)→ raw = path + body + ts + h_ca_str(:18)→ HMAC-SHA256(token) 取 hex(:19-21)→ MD5(hex) 输出小写 hex(:24-25) |
main |
sign_test.rs:28 | 固定向量:token 9f192562b31b3d6222a22591fda002e8、path /api/v1/game/attendance、body {"uid":45235032,"gameId":"1"}、ts 1683100800、did Bexampledid==(:30-34);重算一遍 raw/hex 并打印调试信息(:38-57) |
备注:与生产实现 services/skland_service.rs::calculate_sign(skland_service.rs:422,同为 HMAC-SHA256 + MD5,带 log_debug!)算法一致;改签名规则时两处需同步。vName 硬编码为 1.45.1。
- Cargo.toml:57-58
rustflags是无效 key —cargo check实测报warning: F:\endprotocol\src-tauri\Cargo.toml: unused manifest key: target.x86_64-pc-windows-msvc.rustflags。rustflags属于.cargo/config.toml(本仓库不存在.cargo/目录),因此-fuse-ld=lld实际未生效,链接仍用默认 MSVC link。 - Cargo.toml:63-65
[profile.dev]被忽略 — 仓库根Cargo.toml是 virtual workspace(仅members = ["src-tauri"]),cargo警告profiles for the non root package will be ignored,debug = "line-tables-only"/incremental = true未应用。 - workspace 缺
resolver = "2"—cargo警告 virtual workspace 默认 resolver 1,与 edition 2021 成员不匹配。 tauri_plugin_fs已注册(lib.rs:33)但无fs:*权限 — default.json/desktop.json 均未授予任何 fs 权限,且前端未引用@tauri-apps/plugin-fs,该插件目前对 webview 不可用(Rust 侧直接用 std::fs 不受影响)。已修复:该回退分支已移除(process:allow-exit未授予 — tray-panel.tsx 的回退分支getCurrentWindow().app.exit(0)非法handleQuit失败只记日志),不再依赖不存在的Window.appAPI。- 能力文件缺 schema —
capabilities/desktop.json无$schema字段(default.json有)。 cargo check通过但有 46 个 warning — 大量未使用导入/死代码(如commands/tray.rs:2的Emitter、Manager),另有error finalizing incremental compilation session directory ... (os error 5)的 target 目录权限问题(仅影响增量编译缓存,不阻断编译)。- 托盘面板尺寸双重定义(已收敛为回退值) — 窗口尺寸唯一来源是
lib.rs::create_full_windows的inner_size(288.0, 376.0);tray.rs的PANEL_WIDTH/PANEL_HEIGHT只在outer_size()取不到真实尺寸时作为定位回退,不再是「必须与窗口尺寸逐值一致」的硬性重复。tauri.conf.json的app.windows为空数组,面板窗口并不在那里定义(此前文档所述tauri.conf.json:28,31-32已过时)。 - 托盘面板窗口不设
shadow— 窗口是transparent(true)+ 系统 Acrylic,开启窗口阴影会在透明边缘补一块灰色底板,破坏「真透明」观感;立体层次改由前端.glass-native的圆角与色调层提供。 - Alt+Tab 里出现多个「EndProtocol」条目(已修复) —
EnumWindows实测进程只有 2 个 Tauri 顶层窗口:main(可见、WS_EX_APPWINDOW,Alt+Tab 正常条目)与tray-panel(隐藏、WS_EX_APPWINDOW、无WS_EX_TOOLWINDOW);另外 5 个是tray_icon_app、单实例插件的cn.msk-network.endprotocol-sic、Tao Thread Event Target(三者都带WS_EX_TOOLWINDOW)以及IME/MSCTFIME UI两个有 owner 的子窗口。用IApplicationViewCollection::GetViewForHwnd(explorer 生成 Alt+Tab 列表的同一份数据)核对,并观察到steamwebhelper的SDL_app在窗口隐藏后仍留在列表里 —— explorer 的条目一旦注册就不会随窗口隐藏而消失。根因即tray-panel:skip_taskbar(true)只让 tao 调ITaskbarList::DeleteTab,而 tao 给它加了WS_EX_APPWINDOW,面板每次被显示(右键托盘图标)都会被注册成一个没有界面预览的 Alt+Tab 条目。修复见lib.rs::exclude_from_alt_tab。in-app browser 的子 webview 与此无关:tauri-runtime-wry走WebviewKind::WindowChild → wry::build_as_child,容器窗口是WS_CHILD,压根不是顶层窗口。