Tauri 暗色 App 全屏菜单栏发白——window theme 只管窗口,菜单栏得用 NSApp
做了一个深色玻璃风格的 Tauri 2 桌面应用,界面整体暗色,看起来和 Ghostty、iTerm2 的质感很接近。但进入全屏之后,把鼠标移到屏幕顶部,系统菜单栏从顶部滑下来——白色的,格外扎眼。
查了一圈文档,在 tauri.conf.json 里加了 "theme": "Dark",重启,菜单栏还是白的。这个坑比想象中深。
现象
- Tauri 2 应用,webview 内容深色主题,整体界面已经是暗色调。
tauri.conf.json中 window 块已设置"theme": "Dark",webview 的prefers-color-scheme也响应为 dark。- 进入全屏后,顶部自动隐藏的系统菜单栏(Apple 菜单 / 应用名 / Edit / View / Window 那一条)滑出时是浅色 Aqua 风格,与暗色 UI 明显不搭。
根因
macOS 的外观系统有两层概念,很多人没意识到它们是独立的:
- NSWindow.appearance(窗口级):控制窗口本身和 WKWebView 的明暗主题,对应
prefers-color-scheme。 - NSApplication.effectiveAppearance(应用级):控制系统菜单栏、原生对话框、上下文菜单等系统 UI。它来自
NSApp.appearance,如果未显式设置则跟随系统设置。
Tauri 的 theme: "Dark" 只设置了 NSWindow.appearance,完全没有动 NSApp.appearance。所以你的窗口暗了,但菜单栏依旧跟系统走——系统是浅色模式,菜单栏就是白的。
tauri.conf.json 的 theme: "Dark" 只写入 NSWindow.appearance,管不到系统菜单栏。
菜单栏属于 NSApp.effectiveAppearance(应用级),必须调用 NSApp.setAppearance() 才能影响。
此外,Info.plist 里的 NSRequiresAquaSystemAppearance = false 只是"允许 Dark",不是"强制 Dark",也无法解决问题。
正解
保留 window.theme: "Dark"(让 webview 正确响应暗色),同时在 Tauri 的 setup 钩子里通过 objc2 强制设置 NSApp.appearance。
Cargo.toml 加 macOS 条件依赖(版本与 tauri 2.11 依赖树对齐):
[target.'cfg(target_os = "macos")'.dependencies]
objc2 = "0.6"
objc2-app-kit = "0.3"
objc2-foundation = "0.3"src-tauri/src/lib.rs 增加函数并在 setup 钩子里尽早调用:
/// 全屏时系统菜单栏跟随 NSApp.appearance(应用级)。
/// window theme 只管窗口级,故必须在此强制设置。
#[cfg(target_os = "macos")]
fn force_dark_appearance() {
use objc2_app_kit::{NSAppearance, NSAppearanceNameDarkAqua, NSApplication};
use objc2_foundation::MainThreadMarker;
let Some(mtm) = MainThreadMarker::new() else { return };
let app = NSApplication::sharedApplication(mtm);
// appearanceNamed 是 unsafe,返回 Option<Retained<NSAppearance>>
let appearance = unsafe { NSAppearance::appearanceNamed(NSAppearanceNameDarkAqua) };
// setAppearance 接受 Option<&NSAppearance>,故用 as_deref()
app.setAppearance(appearance.as_deref());
}
// 在 .setup(|_app| { ... }) 里调用:
#[cfg(target_os = "macos")]
force_dark_appearance();tauri.conf.json window 块保持:
{ "titleBarStyle": "Overlay", "hiddenTitle": true, "theme": "Dark" }通过 objc2 在 setup 阶段调用 NSApp.setAppearance(darkAqua),一次调用即可覆盖菜单栏、原生对话框、系统上下文菜单——对一个常驻暗色的 App 来说效果是全局且持久的。
验证方法:cargo check 通过 → 重启 App → 进入全屏 → 上划调出菜单栏,应当是深色半透明,与 Ghostty/iTerm2 表现一致。
排查提示
objc2 API 签名随版本有细微变化(比如 appearanceNamed 的返回类型、setAppearance 的参数类型)。不要靠猜,直接跑 cargo check,编译器会精确告诉你类型不匹配在哪一行。
一句话外卖
macOS 系统菜单栏的明暗由
NSApp.appearance(应用级)控制,而非NSWindow.appearance;Tauri 的window.theme只写窗口级,想让菜单栏变暗必须用 objc2 显式设置应用级 appearance。