use crate::{
ChromeSettings, Error, Result, WindowCornerPreference, WindowsBackdrop, WindowsCapabilities,
WindowsChromeSettings, WindowsVersion,
};
use iced::Color;
use raw_window_handle::Win32WindowHandle;
use std::ffi::c_void;
use std::mem::size_of;
use std::ptr::null_mut;
use windows_sys::Wdk::System::SystemServices::RtlGetVersion;
use windows_sys::Win32::Foundation::{GetLastError, HWND, STATUS_SUCCESS, SetLastError};
use windows_sys::Win32::Graphics::Dwm::{
DWMSBT_AUTO, DWMSBT_MAINWINDOW, DWMSBT_NONE, DWMSBT_TABBEDWINDOW, DWMSBT_TRANSIENTWINDOW,
DWMWA_BORDER_COLOR, DWMWA_CAPTION_COLOR, DWMWA_SYSTEMBACKDROP_TYPE, DWMWA_TEXT_COLOR,
DWMWA_WINDOW_CORNER_PREFERENCE, DwmSetWindowAttribute,
};
use windows_sys::Win32::System::SystemInformation::OSVERSIONINFOW;
use windows_sys::Win32::UI::WindowsAndMessaging::{
DrawMenuBar, EnableMenuItem, GWL_STYLE, GetSystemMenu, GetWindowLongPtrW, MF_BYCOMMAND,
MF_ENABLED, MF_GRAYED, SC_CLOSE, SWP_FRAMECHANGED, SWP_NOMOVE, SWP_NOSIZE, SWP_NOZORDER,
SetWindowLongPtrW, SetWindowPos, WS_CAPTION, WS_MAXIMIZEBOX, WS_MINIMIZEBOX, WS_SYSMENU,
WS_THICKFRAME,
};
const DWMWA_COLOR_DEFAULT: u32 = 0xFFFF_FFFF;
const DWMWA_COLOR_NONE: u32 = 0xFFFF_FFFE;
const DWMWCP_DEFAULT: u32 = 0;
const DWMWCP_DONOTROUND: u32 = 1;
const DWMWCP_ROUND: u32 = 2;
const DWMWCP_ROUNDSMALL: u32 = 3;
pub fn current_capabilities() -> Option<WindowsCapabilities> {
let mut info = OSVERSIONINFOW {
dwOSVersionInfoSize: size_of::<OSVERSIONINFOW>() as u32,
..OSVERSIONINFOW::default()
};
let status = unsafe { RtlGetVersion(&mut info) };
if status != STATUS_SUCCESS {
return None;
}
let version = WindowsVersion {
major: info.dwMajorVersion,
minor: info.dwMinorVersion,
build: info.dwBuildNumber,
};
let supports_dwm_visuals = version.is_windows_11_or_newer();
let supports_system_backdrop =
version.major > 10 || (version.major == 10 && version.build >= 22_621);
Some(WindowsCapabilities {
version,
corner_preference: supports_dwm_visuals,
border_color: supports_dwm_visuals,
title_background_color: supports_dwm_visuals,
title_text_color: supports_dwm_visuals,
system_backdrop: supports_system_backdrop,
})
}
pub fn apply(handle: Win32WindowHandle, settings: &ChromeSettings) -> Result<()> {
let hwnd = handle.hwnd.get() as HWND;
unsafe {
apply_style_bits(hwnd, &settings.windows)?;
apply_system_menu(hwnd, &settings.windows)?;
apply_dwm_attributes(hwnd, &settings.windows)?;
}
Ok(())
}
unsafe fn apply_style_bits(hwnd: HWND, settings: &WindowsChromeSettings) -> Result<()> {
let mut style = unsafe { GetWindowLongPtrW(hwnd, GWL_STYLE) as u32 };
style = with_flag(style, WS_CAPTION, settings.caption);
style = with_flag(style, WS_THICKFRAME, settings.border);
style = with_flag(style, WS_MINIMIZEBOX, settings.buttons.minimize);
style = with_flag(style, WS_MAXIMIZEBOX, settings.buttons.maximize);
let wants_system_menu =
settings.buttons.close || settings.buttons.minimize || settings.buttons.maximize;
style = with_flag(style, WS_SYSMENU, wants_system_menu);
unsafe { SetLastError(0) };
let previous = unsafe { SetWindowLongPtrW(hwnd, GWL_STYLE, style as isize) };
if previous == 0 && unsafe { GetLastError() } != 0 {
return Err(Error::Windows("SetWindowLongPtrW"));
}
let ok = unsafe {
SetWindowPos(
hwnd,
null_mut(),
0,
0,
0,
0,
SWP_FRAMECHANGED | SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER,
)
};
if ok == 0 {
return Err(Error::Windows("SetWindowPos"));
}
Ok(())
}
unsafe fn apply_system_menu(hwnd: HWND, settings: &WindowsChromeSettings) -> Result<()> {
let wants_system_menu =
settings.buttons.close || settings.buttons.minimize || settings.buttons.maximize;
if !wants_system_menu {
return Ok(());
}
let system_menu = unsafe { GetSystemMenu(hwnd, 0) };
if system_menu.is_null() {
return Err(Error::Windows("GetSystemMenu"));
}
let menu_state = if settings.buttons.close {
MF_BYCOMMAND | MF_ENABLED
} else {
MF_BYCOMMAND | MF_GRAYED
};
unsafe { EnableMenuItem(system_menu, SC_CLOSE, menu_state) };
if unsafe { DrawMenuBar(hwnd) } == 0 {
return Err(Error::Windows("DrawMenuBar"));
}
Ok(())
}
unsafe fn apply_dwm_attributes(hwnd: HWND, settings: &WindowsChromeSettings) -> Result<()> {
let Some(capabilities) = current_capabilities() else {
return Ok(());
};
let corner_preference = settings
.corner_preference
.unwrap_or(WindowCornerPreference::Default);
if capabilities.corner_preference {
let corner_value = match corner_preference {
WindowCornerPreference::Default => DWMWCP_DEFAULT,
WindowCornerPreference::DoNotRound => DWMWCP_DONOTROUND,
WindowCornerPreference::Round => DWMWCP_ROUND,
WindowCornerPreference::RoundSmall => DWMWCP_ROUNDSMALL,
};
unsafe { set_dwm_attribute(hwnd, DWMWA_WINDOW_CORNER_PREFERENCE as u32, &corner_value)? };
}
if capabilities.border_color {
let border_value = if !settings.border {
DWMWA_COLOR_NONE
} else {
settings
.border_color
.map(colorref)
.unwrap_or(DWMWA_COLOR_DEFAULT)
};
unsafe { set_dwm_attribute(hwnd, DWMWA_BORDER_COLOR as u32, &border_value)? };
}
if capabilities.title_background_color {
let title_background = settings
.title_background_color
.map(colorref)
.unwrap_or(DWMWA_COLOR_DEFAULT);
unsafe { set_dwm_attribute(hwnd, DWMWA_CAPTION_COLOR as u32, &title_background)? };
}
if capabilities.title_text_color {
let title_text = settings
.title_text_color
.map(colorref)
.unwrap_or(DWMWA_COLOR_DEFAULT);
unsafe { set_dwm_attribute(hwnd, DWMWA_TEXT_COLOR as u32, &title_text)? };
}
if capabilities.system_backdrop {
let backdrop = settings.backdrop.map(backdrop_type).unwrap_or(DWMSBT_AUTO);
unsafe { set_dwm_attribute(hwnd, DWMWA_SYSTEMBACKDROP_TYPE as u32, &backdrop)? };
}
Ok(())
}
unsafe fn set_dwm_attribute<T>(hwnd: HWND, attribute: u32, value: &T) -> Result<()> {
let result = unsafe {
DwmSetWindowAttribute(
hwnd,
attribute,
value as *const T as *const c_void,
size_of::<T>() as u32,
)
};
if result != 0 {
return Err(Error::Windows("DwmSetWindowAttribute"));
}
Ok(())
}
fn with_flag(style: u32, flag: u32, enabled: bool) -> u32 {
if enabled { style | flag } else { style & !flag }
}
fn colorref(color: Color) -> u32 {
let [red, green, blue, _] = color.into_rgba8();
u32::from(red) | (u32::from(green) << 8) | (u32::from(blue) << 16)
}
fn backdrop_type(backdrop: WindowsBackdrop) -> i32 {
match backdrop {
WindowsBackdrop::None => DWMSBT_NONE,
WindowsBackdrop::Mica => DWMSBT_MAINWINDOW,
WindowsBackdrop::Acrylic => DWMSBT_TRANSIENTWINDOW,
WindowsBackdrop::MicaAlt => DWMSBT_TABBEDWINDOW,
}
}