diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 64a8090..c15aa00 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -3792,6 +3792,7 @@ pub fn run() { install_cli, uninstall_cli, get_cli_status, + set_title_bar_theme, ]) .build(tauri::generate_context!()) .expect("error while building tauri application"); @@ -3814,3 +3815,89 @@ pub fn run() { } }); } + +#[cfg(target_os = "windows")] +mod windows_title_bar { + use tauri::WebviewWindow; + + #[allow(non_snake_case)] + mod dwm { + pub const DWMWA_USE_IMMERSIVE_DARK_MODE: u32 = 20; + pub const DWMWA_CAPTION_COLOR: u32 = 35; + pub const DWMWA_BORDER_COLOR: u32 = 34; + + extern "system" { + pub fn DwmSetWindowAttribute( + hwnd: isize, + attr: u32, + value: *const std::ffi::c_void, + size: u32, + ) -> i32; + } + } + + pub fn apply_title_bar_theme(window: &WebviewWindow, is_dark: bool) { + let Ok(hwnd) = window.hwnd() else { + return; + }; + let hwnd = hwnd.0 as isize; + + unsafe { + let set_attr = + |attr: u32, value: *const std::ffi::c_void, size: u32| { + let _ = dwm::DwmSetWindowAttribute(hwnd, attr, value, size); + }; + + let dark_mode: i32 = if is_dark { 1 } else { 0 }; + set_attr( + dwm::DWMWA_USE_IMMERSIVE_DARK_MODE, + &dark_mode as *const _ as *const std::ffi::c_void, + std::mem::size_of::() as u32, + ); + + if is_dark { + let caption_color: u32 = 0x00_0E_0C_0B; + set_attr( + dwm::DWMWA_CAPTION_COLOR, + &caption_color as *const _ as *const std::ffi::c_void, + std::mem::size_of::() as u32, + ); + set_attr( + dwm::DWMWA_BORDER_COLOR, + &caption_color as *const _ as *const std::ffi::c_void, + std::mem::size_of::() as u32, + ); + } else { + let caption_color: u32 = 0x00_FA_FA_F9; + set_attr( + dwm::DWMWA_CAPTION_COLOR, + &caption_color as *const _ as *const std::ffi::c_void, + std::mem::size_of::() as u32, + ); + set_attr( + dwm::DWMWA_BORDER_COLOR, + &caption_color as *const _ as *const std::ffi::c_void, + std::mem::size_of::() as u32, + ); + } + } + } +} + +#[tauri::command] +fn set_title_bar_theme(app: AppHandle, is_dark: bool) -> Result<(), String> { + #[cfg(target_os = "windows")] + { + for (label, window) in app.webview_windows() { + if label == "main" || label.starts_with("preview-") { + windows_title_bar::apply_title_bar_theme(&window, is_dark); + } + } + } + #[cfg(not(target_os = "windows"))] + { + let _ = app; + let _ = is_dark; + } + Ok(()) +} diff --git a/src/context/ThemeContext.tsx b/src/context/ThemeContext.tsx index ee48436..c2c065c 100644 --- a/src/context/ThemeContext.tsx +++ b/src/context/ThemeContext.tsx @@ -6,6 +6,7 @@ import { useCallback, type ReactNode, } from "react"; +import { invoke } from "@tauri-apps/api/core"; import { getSettings, updateSettings } from "../services/notes"; import type { ThemeSettings, @@ -270,6 +271,10 @@ export function ThemeProvider({ children }: ThemeProviderProps) { } else { root.classList.remove("dark"); } + + invoke("set_title_bar_theme", { isDark: resolvedTheme === "dark" }).catch( + () => {}, + ); }, [resolvedTheme]); // Save theme mode to backend