mirror of
https://github.com/clash-verge-rev/clash-verge-rev.git
synced 2026-04-13 13:30:31 +08:00
fix(theme/windows): switch to dark-light based theme detection
This commit is contained in:
parent
712b8ff19b
commit
1c044f053f
@ -7,6 +7,7 @@
|
|||||||
- 修复设置代理端口时检查端口占用
|
- 修复设置代理端口时检查端口占用
|
||||||
- 修复 Monaco 编辑器初始化卡 Loading
|
- 修复 Monaco 编辑器初始化卡 Loading
|
||||||
- 修复恢复备份时 `config.yaml` / `profiles.yaml` 文件内字段未正确恢复
|
- 修复恢复备份时 `config.yaml` / `profiles.yaml` 文件内字段未正确恢复
|
||||||
|
- 修复 Windows 下系统主题同步问题
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
<summary><strong> ✨ 新增功能 </strong></summary>
|
<summary><strong> ✨ 新增功能 </strong></summary>
|
||||||
|
|||||||
@ -22,6 +22,8 @@ use clash_verge_signal;
|
|||||||
|
|
||||||
pub mod dns;
|
pub mod dns;
|
||||||
pub mod scheme;
|
pub mod scheme;
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
|
pub mod theme;
|
||||||
pub mod ui;
|
pub mod ui;
|
||||||
pub mod window;
|
pub mod window;
|
||||||
pub mod window_script;
|
pub mod window_script;
|
||||||
@ -64,6 +66,8 @@ pub fn resolve_setup_async() {
|
|||||||
init_verge_config().await;
|
init_verge_config().await;
|
||||||
Config::verify_config_initialization().await;
|
Config::verify_config_initialization().await;
|
||||||
init_window().await;
|
init_window().await;
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
|
theme::start_windows_app_theme_watcher();
|
||||||
|
|
||||||
let core_init = AsyncHandler::spawn(|| async {
|
let core_init = AsyncHandler::spawn(|| async {
|
||||||
init_service_manager().await;
|
init_service_manager().await;
|
||||||
|
|||||||
55
src-tauri/src/utils/resolve/theme.rs
Normal file
55
src-tauri/src/utils/resolve/theme.rs
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
//! Windows app theme watcher.
|
||||||
|
//!
|
||||||
|
//! NOTE:
|
||||||
|
//! Tauri's theme API is unreliable on Windows and may miss or delay
|
||||||
|
//! system theme change events. As a workaround, we poll the system
|
||||||
|
//! theme via the `dark-light` crate and emit a custom
|
||||||
|
//! `verge://app-theme-changed` event to keep the frontend in sync.
|
||||||
|
//!
|
||||||
|
//! Windows-only, best-effort.
|
||||||
|
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use dark_light::{Mode as SystemTheme, detect as detect_system_theme};
|
||||||
|
use tauri::Emitter as _;
|
||||||
|
|
||||||
|
use crate::{core::handle, process::AsyncHandler};
|
||||||
|
|
||||||
|
const APP_THEME_EVENT: &str = "verge://app-theme-changed";
|
||||||
|
|
||||||
|
fn resolve_apps_theme_mode() -> Option<&'static str> {
|
||||||
|
match detect_system_theme().ok()? {
|
||||||
|
SystemTheme::Dark => Some("dark"),
|
||||||
|
SystemTheme::Light => Some("light"),
|
||||||
|
SystemTheme::Unspecified => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn start_windows_app_theme_watcher() {
|
||||||
|
AsyncHandler::spawn(|| async move {
|
||||||
|
let app_handle = handle::Handle::app_handle().clone();
|
||||||
|
let mut last_theme = resolve_apps_theme_mode();
|
||||||
|
|
||||||
|
if let Some(theme) = last_theme {
|
||||||
|
let _ = app_handle.emit(APP_THEME_EVENT, theme);
|
||||||
|
}
|
||||||
|
|
||||||
|
loop {
|
||||||
|
if handle::Handle::global().is_exiting() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
tokio::time::sleep(Duration::from_millis(500)).await;
|
||||||
|
let Some(theme) = resolve_apps_theme_mode() else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
|
if last_theme.as_ref() == Some(&theme) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
last_theme = Some(theme);
|
||||||
|
let _ = app_handle.emit(APP_THEME_EVENT, theme);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
@ -1,4 +1,6 @@
|
|||||||
import { alpha, createTheme, Theme as MuiTheme, Shadows } from "@mui/material";
|
import { alpha, createTheme, Theme as MuiTheme, Shadows } from "@mui/material";
|
||||||
|
import { isTauri as isTauriApp } from "@tauri-apps/api/core";
|
||||||
|
import { listen } from "@tauri-apps/api/event";
|
||||||
import {
|
import {
|
||||||
getCurrentWebviewWindow,
|
getCurrentWebviewWindow,
|
||||||
WebviewWindow,
|
WebviewWindow,
|
||||||
@ -9,6 +11,7 @@ import { useEffect, useMemo } from "react";
|
|||||||
import { useVerge } from "@/hooks/use-verge";
|
import { useVerge } from "@/hooks/use-verge";
|
||||||
import { defaultDarkTheme, defaultTheme } from "@/pages/_theme";
|
import { defaultDarkTheme, defaultTheme } from "@/pages/_theme";
|
||||||
import { useSetThemeMode, useThemeMode } from "@/services/states";
|
import { useSetThemeMode, useThemeMode } from "@/services/states";
|
||||||
|
import getSystem from "@/utils/get-system";
|
||||||
|
|
||||||
const CSS_INJECTION_SCOPE_ROOT = "[data-css-injection-root]";
|
const CSS_INJECTION_SCOPE_ROOT = "[data-css-injection-root]";
|
||||||
const CSS_INJECTION_SCOPE_LIMIT =
|
const CSS_INJECTION_SCOPE_LIMIT =
|
||||||
@ -26,6 +29,7 @@ const TOP_LEVEL_AT_RULES = [
|
|||||||
"@color-profile",
|
"@color-profile",
|
||||||
];
|
];
|
||||||
let cssScopeSupport: boolean | null = null;
|
let cssScopeSupport: boolean | null = null;
|
||||||
|
const OS = getSystem();
|
||||||
|
|
||||||
const canUseCssScope = () => {
|
const canUseCssScope = () => {
|
||||||
if (cssScopeSupport !== null) {
|
if (cssScopeSupport !== null) {
|
||||||
@ -76,6 +80,8 @@ export const useCustomTheme = () => {
|
|||||||
const setMode = useSetThemeMode();
|
const setMode = useSetThemeMode();
|
||||||
const userBackgroundImage = theme_setting?.background_image || "";
|
const userBackgroundImage = theme_setting?.background_image || "";
|
||||||
const hasUserBackground = !!userBackgroundImage;
|
const hasUserBackground = !!userBackgroundImage;
|
||||||
|
const isTauri = typeof window !== "undefined" && isTauriApp();
|
||||||
|
const isWindows = OS === "windows";
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (theme_mode === "light" || theme_mode === "dark") {
|
if (theme_mode === "light" || theme_mode === "dark") {
|
||||||
@ -84,17 +90,7 @@ export const useCustomTheme = () => {
|
|||||||
}, [theme_mode, setMode]);
|
}, [theme_mode, setMode]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (theme_mode !== "system") {
|
if (theme_mode !== "system" || !isTauri || isWindows) {
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const preferBrowserMatchMedia =
|
|
||||||
typeof window !== "undefined" &&
|
|
||||||
typeof window.matchMedia === "function" &&
|
|
||||||
// Skip Tauri flow when running purely in browser.
|
|
||||||
!("__TAURI__" in window);
|
|
||||||
|
|
||||||
if (preferBrowserMatchMedia) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -133,7 +129,40 @@ export const useCustomTheme = () => {
|
|||||||
console.error("Failed to unlisten from theme changes:", err);
|
console.error("Failed to unlisten from theme changes:", err);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
}, [theme_mode, appWindow, setMode]);
|
}, [theme_mode, appWindow, setMode, isTauri, isWindows]);
|
||||||
|
|
||||||
|
// Windows-only: Tauri's theme API is unreliable.
|
||||||
|
// Theme changes are detected in Rust and propagated via a custom event.
|
||||||
|
useEffect(() => {
|
||||||
|
if (theme_mode !== "system" || !isTauri || !isWindows) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let isMounted = true;
|
||||||
|
let unlisten: (() => void) | null = null;
|
||||||
|
|
||||||
|
listen<string>("verge://app-theme-changed", (event) => {
|
||||||
|
if (!isMounted) return;
|
||||||
|
if (event.payload === "dark" || event.payload === "light") {
|
||||||
|
setMode(event.payload);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then((unlistenFn) => {
|
||||||
|
if (typeof unlistenFn === "function") {
|
||||||
|
unlisten = unlistenFn;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
console.error("Failed to listen to app theme changes:", err);
|
||||||
|
});
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
isMounted = false;
|
||||||
|
if (typeof unlisten === "function") {
|
||||||
|
unlisten();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}, [theme_mode, setMode, isTauri, isWindows]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (theme_mode !== "system") {
|
if (theme_mode !== "system") {
|
||||||
@ -147,6 +176,10 @@ export const useCustomTheme = () => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isTauri && isWindows) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
|
const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
|
||||||
const syncMode = (isDark: boolean) => setMode(isDark ? "dark" : "light");
|
const syncMode = (isDark: boolean) => setMode(isDark ? "dark" : "light");
|
||||||
const handleChange = (event: MediaQueryListEvent) =>
|
const handleChange = (event: MediaQueryListEvent) =>
|
||||||
@ -190,7 +223,7 @@ export const useCustomTheme = () => {
|
|||||||
).removeListener;
|
).removeListener;
|
||||||
legacyRemoveListener?.call(legacyQuery, handleChange);
|
legacyRemoveListener?.call(legacyQuery, handleChange);
|
||||||
};
|
};
|
||||||
}, [theme_mode, setMode]);
|
}, [theme_mode, setMode, isTauri, isWindows]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (theme_mode === undefined) {
|
if (theme_mode === undefined) {
|
||||||
@ -198,18 +231,16 @@ export const useCustomTheme = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (theme_mode === "system") {
|
if (theme_mode === "system") {
|
||||||
appWindow.setTheme(null).catch((err) => {
|
const preferredTheme = isWindows ? (mode as TauriOsTheme) : null;
|
||||||
console.error(
|
appWindow.setTheme(preferredTheme).catch((err) => {
|
||||||
"Failed to set window theme to follow system (setTheme(null)):",
|
console.error("Failed to set window theme for system mode:", err);
|
||||||
err,
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
} else if (mode) {
|
} else if (mode) {
|
||||||
appWindow.setTheme(mode as TauriOsTheme).catch((err) => {
|
appWindow.setTheme(mode as TauriOsTheme).catch((err) => {
|
||||||
console.error(`Failed to set window theme to ${mode}:`, err);
|
console.error(`Failed to set window theme to ${mode}:`, err);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [mode, appWindow, theme_mode]);
|
}, [mode, appWindow, theme_mode, isWindows]);
|
||||||
|
|
||||||
const theme = useMemo(() => {
|
const theme = useMemo(() => {
|
||||||
const setting = theme_setting || {};
|
const setting = theme_setting || {};
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user