use std::sync::{
atomic::{AtomicBool, Ordering},
OnceLock,
};
use windows::{
core::PCSTR,
Win32::{
Foundation::{NTSTATUS, RECT},
System::{
LibraryLoader::{GetProcAddress, LoadLibraryA},
SystemInformation::OSVERSIONINFOW,
},
UI::WindowsAndMessaging::{
self,
ClipCursor,
ShowCursor,
WINDOW_EX_STYLE,
WINDOW_STYLE,
},
},
UI::ViewManagement::{UIColorType, UISettings},
};
use crate::window::state::{Fullscreen, Visibility};
pub fn signed_lo_word(dword: i32) -> i16 {
dword as i16
}
pub fn lo_word(dword: u32) -> u16 {
dword as u16
}
pub fn signed_hi_word(dword: i32) -> i16 {
(dword >> 16) as i16
}
pub fn hi_word(dword: u32) -> u16 {
(dword >> 16) as u16
}
pub fn signed_lo_byte(word: i16) -> i8 {
word as i8
}
pub fn lo_byte(word: u16) -> u8 {
word as u8
}
pub fn signed_hi_byte(word: i16) -> i8 {
(word >> 8) as i8
}
pub fn hi_byte(word: u16) -> u8 {
(word >> 8) as u8
}
pub(crate) fn get_function_impl(
library: &str,
function: &str,
) -> Option<*const std::ffi::c_void> {
assert_eq!(library.chars().last(), Some('\0'));
assert_eq!(function.chars().last(), Some('\0'));
let module = match unsafe { LoadLibraryA(PCSTR::from_raw(library.as_ptr())) } {
Ok(module) => module,
Err(_) => return None,
};
unsafe { GetProcAddress(module, PCSTR::from_raw(function.as_ptr())) }
.map(|function_ptr| function_ptr as _)
}
macro_rules! get_function {
($lib:expr, $func:ident) => {
crate::utilities::get_function_impl(
concat!($lib, '\0'),
concat!(stringify!($func), '\0'),
)
.map(|f| unsafe { std::mem::transmute::<*const _, $func>(f) })
};
}
pub fn windows_10_build_version() -> Option<u32> {
static WIN10_BUILD_VERSION: OnceLock<Option<u32>> = OnceLock::new();
*WIN10_BUILD_VERSION.get_or_init(|| {
type RtlGetVersion = unsafe extern "system" fn(*mut OSVERSIONINFOW) -> NTSTATUS;
let handle = get_function!("ntdll.dll", RtlGetVersion);
if let Some(rtl_get_version) = handle {
unsafe {
let mut vi = OSVERSIONINFOW {
dwOSVersionInfoSize: 0,
dwMajorVersion: 0,
dwMinorVersion: 0,
dwBuildNumber: 0,
dwPlatformId: 0,
szCSDVersion: [0; 128],
};
let status = (rtl_get_version)(&mut vi);
if status.0 >= 0 && vi.dwMajorVersion == 10 && vi.dwMinorVersion == 0 {
Some(vi.dwBuildNumber)
} else {
None
}
}
} else {
None
}
})
}
pub fn is_dark_mode_supported() -> bool {
static DARK_MODE_SUPPORTED: OnceLock<bool> = OnceLock::new();
*DARK_MODE_SUPPORTED.get_or_init(|| {
match windows_10_build_version() {
Some(v) => v >= 17763,
None => false,
}
})
}
pub fn is_system_dark_mode_enabled() -> bool {
static IS_SYSTEM_DARK_MODE: OnceLock<bool> = OnceLock::new();
*IS_SYSTEM_DARK_MODE.get_or_init(|| {
let settings = UISettings::new().unwrap();
let foreground = settings
.GetColorValue(UIColorType::Foreground)
.unwrap_or_default();
is_color_light(&foreground)
})
}
#[inline]
fn is_color_light(clr: &windows::UI::Color) -> bool {
((5 * clr.G as u32) + (2 * clr.R as u32) + clr.B as u32) > (8 * 128)
}
pub(crate) fn get_window_style(
fullscreen: Option<Fullscreen>,
visible: Visibility,
) -> WINDOW_STYLE {
static NORMAL_STYLE: OnceLock<WINDOW_STYLE> = OnceLock::new();
static BORDERLESS_STYLE: OnceLock<WINDOW_STYLE> = OnceLock::new();
match fullscreen {
Some(Fullscreen::Borderless) => {
let style = BORDERLESS_STYLE.get_or_init(|| {
WindowsAndMessaging::WS_POPUP
| WindowsAndMessaging::WS_CLIPCHILDREN
| WindowsAndMessaging::WS_CLIPSIBLINGS
});
match visible {
Visibility::Shown => *style | WindowsAndMessaging::WS_VISIBLE,
Visibility::Hidden => *style,
}
}
None => {
let style = NORMAL_STYLE.get_or_init(|| {
WindowsAndMessaging::WS_OVERLAPPEDWINDOW
| WindowsAndMessaging::WS_CLIPCHILDREN
| WindowsAndMessaging::WS_CLIPSIBLINGS
});
match visible {
Visibility::Shown => *style | WindowsAndMessaging::WS_VISIBLE,
Visibility::Hidden => *style,
}
}
}
}
pub(crate) fn get_window_ex_style(fullscreen: Option<Fullscreen>) -> WINDOW_EX_STYLE {
static NORMAL_EX_STYLE: OnceLock<WINDOW_EX_STYLE> = OnceLock::new();
static BORDERLESS_EX_STYLE: OnceLock<WINDOW_EX_STYLE> = OnceLock::new();
match fullscreen {
Some(Fullscreen::Borderless) => {
let style =
BORDERLESS_EX_STYLE.get_or_init(|| WindowsAndMessaging::WS_EX_APPWINDOW);
*style
}
None => {
let style = NORMAL_EX_STYLE.get_or_init(|| {
WindowsAndMessaging::WS_EX_OVERLAPPEDWINDOW | WindowsAndMessaging::WS_EX_APPWINDOW
});
*style
}
}
}
pub(crate) fn set_cursor_clip(rect: Option<&RECT>) {
if let Err(_e) = unsafe { ClipCursor(rect.map(|r| r as _)) } {
tracing::error!("{_e}");
}
}
pub(crate) fn set_cursor_visibility(visible: Visibility) {
let hidden = visible == Visibility::Hidden;
static HIDDEN: AtomicBool = AtomicBool::new(false);
let changed = HIDDEN.swap(hidden, Ordering::SeqCst) ^ hidden;
if changed {
unsafe { ShowCursor(!hidden) };
}
}