diff --git a/src-tauri/src/commands/misc.rs b/src-tauri/src/commands/misc.rs index f848fe6bf..630e2ecda 100644 --- a/src-tauri/src/commands/misc.rs +++ b/src-tauri/src/commands/misc.rs @@ -954,3 +954,18 @@ fn run_windows_start_command(args: &[&str], terminal_name: &str) -> Result<(), S Ok(()) } + +/// 设置窗口主题(Windows/macOS 标题栏颜色) +/// theme: "dark" | "light" | "system" +#[tauri::command] +pub async fn set_window_theme(window: tauri::Window, theme: String) -> Result<(), String> { + use tauri::Theme; + + let tauri_theme = match theme.as_str() { + "dark" => Some(Theme::Dark), + "light" => Some(Theme::Light), + _ => None, // system default + }; + + window.set_theme(tauri_theme).map_err(|e| e.to_string()) +} diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index de307b856..f244e0506 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -956,6 +956,8 @@ pub fn run() { commands::test_proxy_url, commands::get_upstream_proxy_status, commands::scan_local_proxies, + // Window theme control + commands::set_window_theme, ]); let app = builder diff --git a/src/components/theme-provider.tsx b/src/components/theme-provider.tsx index b1a45072d..0a1fec2ae 100644 --- a/src/components/theme-provider.tsx +++ b/src/components/theme-provider.tsx @@ -5,6 +5,7 @@ import React, { useMemo, useState, } from "react"; +import { invoke } from "@tauri-apps/api/core"; type Theme = "light" | "dark" | "system"; @@ -94,6 +95,54 @@ export function ThemeProvider({ return () => mediaQuery.removeEventListener("change", handleChange); }, [theme]); + // Sync native window theme (Windows/macOS title bar) + useEffect(() => { + if (typeof window === "undefined") { + return; + } + + let isCancelled = false; + + const updateNativeTheme = async (nativeTheme: string) => { + if (isCancelled) return; + try { + await invoke("set_window_theme", { theme: nativeTheme }); + } catch (e) { + // Ignore errors (e.g., when not running in Tauri) + console.debug("Failed to set native window theme:", e); + } + }; + + // Determine current effective theme + if (theme === "system") { + const isDark = + window.matchMedia && + window.matchMedia("(prefers-color-scheme: dark)").matches; + updateNativeTheme(isDark ? "dark" : "light"); + } else { + updateNativeTheme(theme); + } + + // Listen to system theme changes for native window when in "system" mode + const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)"); + const handleChange = () => { + if (theme === "system" && !isCancelled) { + updateNativeTheme(mediaQuery.matches ? "dark" : "light"); + } + }; + + if (theme === "system") { + mediaQuery.addEventListener("change", handleChange); + } + + return () => { + isCancelled = true; + if (theme === "system") { + mediaQuery.removeEventListener("change", handleChange); + } + }; + }, [theme]); + const value = useMemo( () => ({ theme,