Tauri macOS 隐藏到托盘后 Dock 图标失效:必须处理 RunEvent::Reopen
做 Tauri 2 桌面应用时,一个常见需求是「关闭窗口不退出,藏到系统托盘让后台进程继续跑」。实现起来也不复杂:拦截 WindowEvent::CloseRequested,调用 window.hide() 并 api.prevent_close() 即可。托盘菜单里放一个「打开」按钮,调 show() 恢复。
但等用户测试的时候,你会发现一个让人抓狂的 bug:点击 Dock 图标,什么都没发生。窗口不出来,进程还活着,只有托盘菜单的「打开」能救场。macOS 用户对 Dock 有肌肉记忆,这读起来完全就是应用坏了。
现象
- 应用正常启动,主窗口显示。
- 点红色关闭按钮 → 窗口消失,托盘图标还在,后台进程继续运行(符合预期)。
- 点击 Dock 图标 → 毫无反应,窗口不出现。
- 右键托盘图标 → 点「打开」→ 窗口正常恢复。
根因
这是一个「写法合法、逻辑缺失」的典型陷阱,分三步拆解:
第一步:window.hide() 只是把窗口藏起来,进程仍在运行,没有可见窗口。
第二步:macOS 对「点击一个已在运行的应用的 Dock 图标」有明确的语义——触发 AppKit 的 applicationShouldHandleReopen:hasVisibleWindows:。Tauri 把这个系统事件映射成了 RunEvent::Reopen。
第三步:Tauri 项目里最常见的启动收尾写法是:
tauri::Builder::default()
// ...
.run(tauri::generate_context!())
.expect("error while running tauri application");这个 .run() 的签名接受一个事件回调,但上面这种「一行式」写法把回调省掉了。于是 RunEvent::Reopen 被触发之后,没有任何代码接住它,直接静默丢弃。托盘菜单的「打开」之所以还能用,是因为它走的是一条完全独立的代码路径(手动调 show()),跟 Reopen 事件毫无关系。
.run(generate_context!()) 是合法的 Rust,编译通过,运行时也不报错。但它把所有应用级事件(包括 RunEvent::Reopen)都丢进了黑洞。在隐藏到托盘的场景下,后果就是 Dock 图标彻底失效。
正解
把末尾的 .run(ctx) 换成 .build(ctx).run(closure),在闭包里处理 RunEvent::Reopen,把隐藏的主窗口重新 show 出来并 set_focus:
use tauri::Manager; // get_webview_window 需要这个 trait
tauri::Builder::default()
// ...setup、tray、on_window_event、invoke_handler 等配置不变...
.build(tauri::generate_context!())
.expect("error while building app")
.run(|app_handle, event| {
// macOS:点 Dock 图标唤起已隐藏到托盘的窗口
if let tauri::RunEvent::Reopen { .. } = event {
if let Some(w) = app_handle.get_webview_window("main") {
let _ = w.show();
let _ = w.set_focus();
}
}
});几个要点:
"main"必须和tauri.conf.json的app.windows[].label一致。RunEvent::Reopen是公开枚举变体,不需要#[cfg(target_os = "macos")]——它只在 macOS 上被触发,其他平台上这个if let永远不命中,代码跨平台编译没问题。set_focus()紧跟在show()之后,确保窗口弹到前台,而不是默默在后台出现。- 托盘菜单的
show()调用保留,两条路径都要有。
从 .run(ctx) 换到 .build(ctx).run(|handle, event| { ... }) 是零破坏性改动——其余所有 RunEvent 变体不处理就直接 fall through,行为不变。只需加三行即可让 Dock 图标恢复预期语义。
验证
- 重新构建并启动应用,确认主窗口正常显示。
- 点击窗口红色关闭按钮 → 窗口消失,托盘图标仍在,后台进程保持运行。
- 点击 Dock 图标 → 窗口重新出现并获得焦点。(修复前:无反应。)
- 托盘菜单「打开」→ 同样有效(保留原路径)。
一句话外卖
Tauri 的隐藏到托盘模式在 macOS 上必须显式处理
RunEvent::Reopen,否则 Dock 图标点击永远静默丢弃——.run(ctx)省略回调是合法写法,但代价是整条 AppKit reopen 语义链路断掉。