use std::sync::{Arc, OnceLock};
use tracing::error;
use windows::core::PCWSTR;
use windows::Win32::Foundation::{CloseHandle, ERROR_SUCCESS, HANDLE, WAIT_OBJECT_0};
use windows::Win32::System::Registry::{
RegCloseKey, RegNotifyChangeKeyValue, RegOpenKeyExW, HKEY, KEY_NOTIFY,
REG_NOTIFY_CHANGE_LAST_SET, REG_NOTIFY_CHANGE_NAME, REG_SAM_FLAGS,
};
use windows::Win32::System::Threading::{CreateEventW, ResetEvent, WaitForSingleObject, INFINITE};
use winreg::enums::HKEY_CURRENT_USER;
use winreg::RegKey;
use zerolaunch_plugin_api::host::HostApiError;
use zerolaunch_plugin_api::services::{Theme, ThemeProvider};
#[derive(Default)]
pub struct WindowsThemeProvider;
impl ThemeProvider for WindowsThemeProvider {
fn current_system_theme(&self) -> Result<Theme, HostApiError> {
let hkcu = RegKey::predef(HKEY_CURRENT_USER);
let key = hkcu
.open_subkey("Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize")
.map_err(|error| HostApiError::ExecutionFailed {
service: "theme".to_string(),
reason: error.to_string(),
})?;
let value: u32 =
key.get_value("AppsUseLightTheme")
.map_err(|error| HostApiError::ExecutionFailed {
service: "theme".to_string(),
reason: error.to_string(),
})?;
Ok(if value == 0 {
Theme::Dark
} else {
Theme::Light
})
}
}
pub fn start_system_theme_monitor(
callback: impl Fn(Theme) + Send + Sync + 'static,
) -> Result<(), String> {
static STATE: OnceLock<Result<(), String>> = OnceLock::new();
STATE
.get_or_init(|| WindowsThemeListener::new(callback).start())
.clone()
}
pub struct WindowsThemeListener {
callback: Arc<dyn Fn(Theme) + Send + Sync>,
}
impl WindowsThemeListener {
pub fn new(callback: impl Fn(Theme) + Send + Sync + 'static) -> Self {
Self {
callback: Arc::new(callback),
}
}
pub fn start(self) -> Result<(), String> {
const PERSONALIZE_PATH: &str =
"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize";
let path_wide: Vec<u16> = PERSONALIZE_PATH
.encode_utf16()
.chain(std::iter::once(0))
.collect();
let mut key = HKEY::default();
let status = unsafe {
RegOpenKeyExW(
windows::Win32::System::Registry::HKEY_CURRENT_USER,
PCWSTR::from_raw(path_wide.as_ptr()),
None,
REG_SAM_FLAGS(KEY_NOTIFY.0),
&mut key,
)
};
if status != ERROR_SUCCESS {
return Err(format!("RegOpenKeyExW 失败: {status:?}"));
}
let key_raw = key.0 as usize;
let notify_event = match unsafe { CreateEventW(None, true, false, PCWSTR::null()) } {
Ok(event) => event,
Err(error) => {
let _ = unsafe { RegCloseKey(key) };
return Err(format!("CreateEventW 失败: {error}"));
}
};
let notify_event_raw = notify_event.0 as usize;
let callback = self.callback;
std::thread::spawn(move || {
let key = HKEY(key_raw as *mut _);
let notify_event = HANDLE(notify_event_raw as *mut _);
let provider = WindowsThemeProvider;
let mut last_theme = provider.current_system_theme().ok();
loop {
let status = unsafe {
RegNotifyChangeKeyValue(
key,
false,
REG_NOTIFY_CHANGE_LAST_SET | REG_NOTIFY_CHANGE_NAME,
Some(notify_event),
true,
)
};
if status != ERROR_SUCCESS {
error!(?status, "RegNotifyChangeKeyValue 失败,停止系统主题监听");
break;
}
let wait = unsafe { WaitForSingleObject(notify_event, INFINITE) };
if wait != WAIT_OBJECT_0 {
error!(?wait, "等待系统主题通知失败,停止系统主题监听");
break;
}
let _ = unsafe { ResetEvent(notify_event) };
match provider.current_system_theme() {
Ok(theme) => {
if last_theme != Some(theme) {
last_theme = Some(theme);
callback(theme);
}
}
Err(error) => error!(error = %error, "读取系统主题失败"),
}
}
let _ = unsafe { RegCloseKey(key) };
let _ = unsafe { CloseHandle(notify_event) };
});
Ok(())
}
}