133 lines
4.3 KiB
Rust
133 lines
4.3 KiB
Rust
use std::{ffi::c_void, mem::size_of, mem::transmute, ptr};
|
|
|
|
use hex_color::HexColor;
|
|
use tauri::{Listener, Runtime, WebviewWindow};
|
|
use windows::Win32::{
|
|
Foundation::{BOOL, COLORREF, HWND},
|
|
Graphics::Dwm::{DwmSetWindowAttribute, DWMWA_CAPTION_COLOR, DWMWA_USE_IMMERSIVE_DARK_MODE},
|
|
UI::Controls::{
|
|
SetWindowThemeAttribute, WTA_NONCLIENT, WTNCA_NODRAWCAPTION, WTNCA_NODRAWICON,
|
|
WTNCA_NOMIRRORHELP, WTNCA_NOSYSMENU,
|
|
},
|
|
};
|
|
use winver::WindowsVersion;
|
|
|
|
// Windows 11 Build 22000 is the minimum version required for
|
|
// DWMWA_USE_IMMERSIVE_DARK_MODE and DWMWA_CAPTION_COLOR.
|
|
//
|
|
// According to Microsoft documentation:
|
|
// https://learn.microsoft.com/en-us/windows/win32/api/dwmapi/ne-dwmapi-dwmwindowattribute
|
|
//
|
|
// "DWMWA_USE_IMMERSIVE_DARK_MODE: Use with DwmSetWindowAttribute.
|
|
// [...] This value is supported starting with Windows 11 Build 22000."
|
|
//
|
|
// "DWMWA_CAPTION_COLOR: Use with DwmSetWindowAttribute.
|
|
// [...] This value is supported starting with Windows 11 Build 22000."
|
|
const MIN_WIN11_BUILD: u32 = 22000;
|
|
|
|
#[derive(Debug)]
|
|
pub struct WindowsWindow<R: Runtime> {
|
|
window: WebviewWindow<R>,
|
|
hwnd: HWND,
|
|
}
|
|
|
|
impl<R: Runtime> WindowsWindow<R> {
|
|
pub fn new(window: WebviewWindow<R>) -> Self {
|
|
let hwnd = window.hwnd().expect("Failed to get window handle");
|
|
let hwnd = HWND(hwnd.0);
|
|
Self { window, hwnd }
|
|
}
|
|
|
|
pub fn setup(&self) {
|
|
self.update_theme(HexColor::rgb(23, 23, 23));
|
|
self.setup_theme_listener();
|
|
}
|
|
|
|
pub fn update_theme(&self, color: HexColor) {
|
|
self.set_dark_mode();
|
|
self.set_caption_color(color);
|
|
self.set_theme_attributes();
|
|
}
|
|
|
|
fn setup_theme_listener(&self) {
|
|
let window = self.window.clone();
|
|
self.window.listen("hopp-bg-changed", move |event| {
|
|
let color_str = event.payload();
|
|
|
|
if let Ok(color_str) = serde_json::from_str::<&str>(color_str) {
|
|
if let Ok(color) = HexColor::parse_rgb(color_str.trim()) {
|
|
let windows_window = WindowsWindow::new(window.clone());
|
|
windows_window.update_theme(color);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
fn set_dark_mode(&self) {
|
|
if let Some(version) = WindowsVersion::detect() {
|
|
if version.major >= 10 && version.build >= MIN_WIN11_BUILD {
|
|
unsafe {
|
|
let use_dark_mode = BOOL::from(true);
|
|
let _ = DwmSetWindowAttribute(
|
|
self.hwnd,
|
|
DWMWA_USE_IMMERSIVE_DARK_MODE,
|
|
ptr::addr_of!(use_dark_mode) as *const c_void,
|
|
size_of::<BOOL>().try_into().unwrap(),
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fn set_caption_color(&self, color: HexColor) {
|
|
if let Some(version) = WindowsVersion::detect() {
|
|
if version.major >= 10 && version.build >= MIN_WIN11_BUILD {
|
|
unsafe {
|
|
let color_ref = self.hex_color_to_colorref(color);
|
|
let _ = DwmSetWindowAttribute(
|
|
self.hwnd,
|
|
DWMWA_CAPTION_COLOR,
|
|
ptr::addr_of!(color_ref) as *const c_void,
|
|
size_of::<COLORREF>().try_into().unwrap(),
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fn set_theme_attributes(&self) {
|
|
unsafe {
|
|
let theme_attributes = WinThemeAttribute::new();
|
|
SetWindowThemeAttribute(
|
|
self.hwnd,
|
|
WTA_NONCLIENT,
|
|
ptr::addr_of!(theme_attributes) as *const c_void,
|
|
size_of::<WinThemeAttribute>().try_into().unwrap(),
|
|
)
|
|
.expect("Failed to set theme attributes");
|
|
}
|
|
}
|
|
|
|
fn hex_color_to_colorref(&self, color: HexColor) -> COLORREF {
|
|
unsafe { COLORREF(transmute::<[u8; 4], u32>([color.r, color.g, color.b, 0])) }
|
|
}
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
struct WinThemeAttribute {
|
|
flag: u32,
|
|
mask: u32,
|
|
}
|
|
|
|
impl WinThemeAttribute {
|
|
fn new() -> Self {
|
|
let flag = WTNCA_NODRAWCAPTION | WTNCA_NODRAWICON;
|
|
let mask = WTNCA_NODRAWCAPTION | WTNCA_NODRAWICON | WTNCA_NOSYSMENU | WTNCA_NOMIRRORHELP;
|
|
Self { flag, mask }
|
|
}
|
|
}
|
|
|
|
pub fn setup_window<R: Runtime>(window: WebviewWindow<R>) {
|
|
let windows_window = WindowsWindow::new(window);
|
|
windows_window.setup();
|
|
}
|