use crate::compat::{String, Vec};
use crate::core::Rect;
use crate::platform::types::{Display, DisplayId};
use winapi::shared::minwindef::{BOOL, LPARAM, TRUE};
use winapi::shared::windef::{HDC, HMONITOR, HWND, LPRECT, RECT};
use winapi::um::wingdi::{CreateDCW, DeleteDC, GetDeviceCaps, LOGPIXELSX};
use winapi::um::winuser::{
EnumDisplayMonitors, GetMonitorInfoW, MonitorFromWindow, MONITORINFO, MONITORINFOEXW,
MONITORINFOF_PRIMARY, MONITOR_DEFAULTTONEAREST,
};
const FNV_OFFSET: u64 = 0xcbf2_9ce4_8422_2325;
const FNV_PRIME: u64 = 0x0000_0100_0000_01b3;
fn rect_of(rect: &RECT) -> Rect {
Rect::new(
rect.left,
rect.top,
(rect.right - rect.left).max(0) as u32,
(rect.bottom - rect.top).max(0) as u32,
)
}
fn device_name(raw: &[u16]) -> String {
let len = raw.iter().position(|&c| c == 0).unwrap_or(raw.len());
String::from_utf16_lossy(&raw[..len])
}
fn display_id(device: &str) -> DisplayId {
let mut hash = FNV_OFFSET;
for byte in device.as_bytes() {
hash ^= *byte as u64;
hash = hash.wrapping_mul(FNV_PRIME);
}
DisplayId::new(hash)
}
unsafe fn scale_factor_of(device: &str) -> f32 {
use std::os::windows::ffi::OsStrExt;
let name: Vec<u16> =
std::ffi::OsStr::new(device).encode_wide().chain(std::iter::once(0)).collect();
let hdc = CreateDCW(std::ptr::null(), name.as_ptr(), std::ptr::null(), std::ptr::null());
if hdc.is_null() {
return 1.0;
}
let dpi = GetDeviceCaps(hdc, LOGPIXELSX);
DeleteDC(hdc);
if dpi > 0 {
dpi as f32 / 96.0
} else {
1.0
}
}
unsafe fn display_of_monitor(monitor: HMONITOR) -> Option<Display> {
let mut info: MONITORINFOEXW = std::mem::zeroed();
info.cbSize = std::mem::size_of::<MONITORINFOEXW>() as u32;
let as_base = &mut info as *mut MONITORINFOEXW as *mut MONITORINFO;
if GetMonitorInfoW(monitor, as_base) == 0 {
return None;
}
let device = device_name(&info.szDevice);
Some(Display {
id: display_id(&device),
bounds: rect_of(&info.rcMonitor),
work_area: rect_of(&info.rcWork),
scale: scale_factor_of(&device),
is_primary: info.dwFlags & MONITORINFOF_PRIMARY != 0,
})
}
unsafe extern "system" fn collect_monitor(
monitor: HMONITOR,
_hdc: HDC,
_clip: LPRECT,
data: LPARAM,
) -> BOOL {
if let Some(display) = display_of_monitor(monitor) {
(*(data as *mut Vec<Display>)).push(display);
}
TRUE
}
pub(crate) fn displays() -> Vec<Display> {
let mut out: Vec<Display> = Vec::new();
let data = &mut out as *mut Vec<Display> as LPARAM;
let ok = unsafe {
EnumDisplayMonitors(std::ptr::null_mut(), std::ptr::null(), Some(collect_monitor), data)
};
if ok == 0 {
log::debug!(
"[windows] EnumDisplayMonitors failed; no displays are observable from this session"
);
}
out
}
pub(crate) fn display_of_window_handle(hwnd: HWND) -> Option<DisplayId> {
if hwnd.is_null() {
return None;
}
unsafe {
let monitor = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST);
display_of_monitor(monitor).map(|display| display.id)
}
}
pub(crate) fn generation() -> u64 {
let displays = displays();
if displays.is_empty() {
return 0;
}
let mut hash = FNV_OFFSET;
let mut mix = |value: u64| {
hash ^= value;
hash = hash.wrapping_mul(FNV_PRIME);
};
mix(displays.len() as u64);
for display in displays {
for part in [
display.id.get(),
display.bounds.x as i64 as u64,
display.bounds.y as i64 as u64,
display.bounds.width as u64,
display.bounds.height as u64,
display.work_area.x as i64 as u64,
display.work_area.y as i64 as u64,
display.work_area.width as u64,
display.work_area.height as u64,
display.scale.to_bits() as u64,
display.is_primary as u64,
] {
mix(part);
}
}
hash
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_device_name_is_decoded_up_to_its_nul() {
let mut raw = [0u16; 32];
for (slot, unit) in raw.iter_mut().zip("\\\\.\\DISPLAY1".encode_utf16()) {
*slot = unit;
}
assert_eq!(device_name(&raw), "\\\\.\\DISPLAY1");
}
#[test]
fn the_id_is_stable_and_distinct_per_device_name() {
assert_eq!(display_id("\\\\.\\DISPLAY1"), display_id("\\\\.\\DISPLAY1"));
assert_ne!(display_id("\\\\.\\DISPLAY1"), display_id("\\\\.\\DISPLAY2"));
}
#[test]
fn a_degenerate_rect_clamps_to_zero_extent() {
let rect = RECT { left: 10, top: 20, right: 5, bottom: 4 };
assert_eq!(rect_of(&rect), Rect::new(10, 20, 0, 0));
}
}