系统托盘、菜单与全局快捷键
效率类桌面应用的三件套:系统托盘让应用常驻后台、原生菜单提供标准操作入口、全局快捷键随时唤起。Tauri 2.x 把这些能力统一进核心 API,本篇给出托盘图标、应用菜单与全局快捷键的实现方式与联动设计。
一、系统托盘
托盘图标在 Tauri 2 中由 TrayIconBuilder 创建,并挂到 Builder 上:
use tauri::{
menu::{Menu, MenuItem},
tray::TrayIconBuilder,
Manager,
};
fn main() {
tauri::Builder::default()
.setup(|app| {
let quit = MenuItem::with_id(app, "quit", "退出", true, None::<&str>)?;
let show = MenuItem::with_id(app, "show", "显示主窗口", true, None::<&str>)?;
let menu = Menu::with_items(app, &[&show, &quit])?;
TrayIconBuilder::new()
.icon(app.default_window_icon().unwrap().clone())
.menu(&menu)
.on_menu_event(|app, event| match event.id.as_ref() {
"quit" => app.exit(0),
"show" => {
if let Some(window) = app.get_webview_window("main") {
let _ = window.show();
let _ = window.set_focus();
}
}
_ => {}
})
.build(app)?;
Ok(())
})
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
托盘图标资源放在 src-tauri/icons 下,图标路径与多尺寸格式见「图标、资源与本地化」。API 形态以官方文档为准。
| 常见交互 | 实现要点 |
|---|---|
| 点击托盘显示窗口 | on_tray_icon_event 中 show 加 set_focus |
| 关闭按钮只隐藏 | 拦截 CloseRequested 事件,隐藏而非退出 |
| 右键菜单 | Menu 挂到 TrayIconBuilder |
| 动态更新菜单 | 持有 menu 引用并 rebuild |
窗口拦截关闭的写法见「窗口管理:创建、多窗口与装饰」。
二、应用菜单
应用菜单(菜单栏)用 Menu 与 MenuItem 组合,可以为单个窗口设置,也可以设为全局应用菜单:
use tauri::menu::{Menu, MenuItem, Submenu};
fn main() {
tauri::Builder::default()
.menu(|app| {
let new_item = MenuItem::with_id(app, "new", "新建", true, Some("CmdOrCtrl+N"))?;
let file_menu = Submenu::with_items(app, "文件", true, &[&new_item])?;
Menu::with_items(app, &[&file_menu])
})
.on_menu_event(|app, event| {
if event.id.as_ref() == "new" {
let _ = app.emit("menu-new-file", ());
}
})
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
菜单事件推给前端用事件系统最顺手,前端订阅 menu-new-file 即可响应,事件写法见「事件系统:Rust 主动通知前端」。macOS 上应用菜单是系统级行为,Electron 用户对此熟悉,差异对比见「Tauri 是什么:与 Electron 的架构对比」。
三、全局快捷键
全局快捷键(系统范围内生效)由 tauri-plugin-global-shortcut 提供:
use tauri_plugin_global_shortcut::{Code, Modifiers, Shortcut, ShortcutState};
fn main() {
tauri::Builder::default()
.plugin(
tauri_plugin_global_shortcut::Builder::new()
.with_shortcut("CmdOrCtrl+Shift+P")?
.with_handler(|app, _shortcut, event| {
if event.state == ShortcutState::Pressed {
if let Some(window) = app.get_webview_window("main") {
let _ = window.show();
let _ = window.set_focus();
}
}
})
.build(),
)
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
| 注意点 | 说明 |
|---|---|
| 快捷键冲突 | 与系统或输入法冲突时不生效 |
| 注册时机 | 应用启动后注册,退出时自动注销 |
| 动态注册 | 支持运行时增删,需管理映射 |
| 权限 | 在 Capability 中声明插件权限 |
快捷键设置页是常见需求:把用户自定义的快捷键存数据库或配置文件,启动时读取注册,存储方案见「全局状态:tauri::State 与共享数据」。
四、三者联动设计
典型组合:托盘常驻加全局快捷键唤起加菜单提供标准操作。设计建议用一张表梳理数据流:
| 触发源 | Rust 响应 | 前端响应 |
|---|---|---|
| 托盘菜单项 | 窗口管理 | emit 事件 |
| 应用菜单 | emit 事件 | 监听后执行操作 |
| 全局快捷键 | 显示聚焦窗口 | 路由跳转到对应页面 |
统一原则:Rust 负责系统能力与窗口,前端负责业务响应,中间用事件解耦。这样菜单项文案与页面状态完全由前端管理,多语言时尤其方便。
托盘与菜单 API 在 2.x 中变动较频繁,写代码时以官方文档为准,遇到 Manager、AppHandle 等类型不熟悉可以翻 Rust 入门;样板代码生成可参考 Claude Code 指南;批量调试日志中的事件名时 正则 mastery 是好帮手。
六、托盘应用的设计陷阱
常驻类应用看似简单,实际上有一堆细节:托盘菜单的国际化、图标的深浅色适配、macOS 上应用退出与窗口隐藏的行为差异。重点问题列表:
| 陷阱 | 说明 |
|---|---|
| macOS 退出习惯 | Dock 右键退出与 Cmd 加 Q 的处理 |
| 单实例感知 | 已运行时再启动应聚焦而不是开第二个 |
| 图标模板化 | macOS 托盘图标应为模板图自动适配深浅色 |
| 菜单文案换行 | 不同语言长度差异 |
| 快捷键失效反馈 | 冲突时静默失败,需要可视化提示 |
全局快捷键冲突检测的建议做法:维护一份常见系统快捷键黑名单文档,注册失败时给出明确提示而不是忽略错误。用户自己挑快捷键时,提供输入框按下即录入的交互,比手工填字符串友好得多。
把托盘、菜单、快捷键三件套视为「应用的系统侧门面」,它们是用户与应用建立肌肉记忆的地方,值得在设计稿里同主界面一起打磨。
延伸阅读
- 窗口管理:创建、多窗口与装饰——托盘联动的窗口侧
- 事件系统:Rust 主动通知前端——菜单事件到前端的桥梁
- 自动更新与版本分发——常驻后台应用的升级方式
