use std::{ collections::{BTreeSet, VecDeque}, hash::Hash, io, mem, ptr, }; use windows_sys::Win32::{ Foundation::{BOOL, HWND, LPARAM, POINT, RECT}, Graphics::Gdi::{ EnumDisplayMonitors, EnumDisplaySettingsExW, GetMonitorInfoW, MonitorFromPoint, MonitorFromWindow, DEVMODEW, DM_BITSPERPEL, DM_DISPLAYFREQUENCY, DM_PELSHEIGHT, DM_PELSWIDTH, HDC, HMONITOR, MONITORINFO, MONITORINFOEXW, MONITOR_DEFAULTTONEAREST, MONITOR_DEFAULTTOPRIMARY, }, }; use super::util::decode_wide; use crate::{ dpi::{PhysicalPosition, PhysicalSize}, monitor::{MonitorHandle as RootMonitorHandle, VideoMode as RootVideoMode}, platform_impl::platform::{ dpi::{dpi_to_scale_factor, get_monitor_dpi}, util::has_flag, window::Window, }, }; #[derive(Clone)] pub struct VideoMode { pub(crate) size: (u32, u32), pub(crate) bit_depth: u16, pub(crate) refresh_rate: u16, pub(crate) monitor: MonitorHandle, // DEVMODEW is huge so we box it to avoid blowing up the size of winit::window::Fullscreen pub(crate) native_video_mode: Box, } impl PartialEq for VideoMode { fn eq(&self, other: &Self) -> bool { self.size == other.size && self.bit_depth == other.bit_depth && self.refresh_rate == other.refresh_rate && self.monitor == other.monitor } } impl Eq for VideoMode {} impl std::hash::Hash for VideoMode { fn hash(&self, state: &mut H) { self.size.hash(state); self.bit_depth.hash(state); self.refresh_rate.hash(state); self.monitor.hash(state); } } impl std::fmt::Debug for VideoMode { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("VideoMode") .field("size", &self.size) .field("bit_depth", &self.bit_depth) .field("refresh_rate", &self.refresh_rate) .field("monitor", &self.monitor) .finish() } } impl VideoMode { pub fn size(&self) -> PhysicalSize { self.size.into() } pub fn bit_depth(&self) -> u16 { self.bit_depth } pub fn refresh_rate(&self) -> u16 { self.refresh_rate } pub fn monitor(&self) -> RootMonitorHandle { RootMonitorHandle { inner: self.monitor.clone(), } } } #[derive(Debug, Clone, Eq, PartialEq, Hash, PartialOrd, Ord)] pub struct MonitorHandle(HMONITOR); // Send is not implemented for HMONITOR, we have to wrap it and implement it manually. // For more info see: // https://github.com/retep998/winapi-rs/issues/360 // https://github.com/retep998/winapi-rs/issues/396 unsafe impl Send for MonitorHandle {} unsafe extern "system" fn monitor_enum_proc( hmonitor: HMONITOR, _hdc: HDC, _place: *mut RECT, data: LPARAM, ) -> BOOL { let monitors = data as *mut VecDeque; (*monitors).push_back(MonitorHandle::new(hmonitor)); true.into() // continue enumeration } pub fn available_monitors() -> VecDeque { let mut monitors: VecDeque = VecDeque::new(); unsafe { EnumDisplayMonitors( 0, ptr::null(), Some(monitor_enum_proc), &mut monitors as *mut _ as LPARAM, ); } monitors } pub fn primary_monitor() -> MonitorHandle { const ORIGIN: POINT = POINT { x: 0, y: 0 }; let hmonitor = unsafe { MonitorFromPoint(ORIGIN, MONITOR_DEFAULTTOPRIMARY) }; MonitorHandle::new(hmonitor) } pub fn current_monitor(hwnd: HWND) -> MonitorHandle { let hmonitor = unsafe { MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST) }; MonitorHandle::new(hmonitor) } impl Window { pub fn available_monitors(&self) -> VecDeque { available_monitors() } pub fn primary_monitor(&self) -> Option { let monitor = primary_monitor(); Some(RootMonitorHandle { inner: monitor }) } } pub(crate) fn get_monitor_info(hmonitor: HMONITOR) -> Result { let mut monitor_info: MONITORINFOEXW = unsafe { mem::zeroed() }; monitor_info.monitorInfo.cbSize = mem::size_of::() as u32; let status = unsafe { GetMonitorInfoW( hmonitor, &mut monitor_info as *mut MONITORINFOEXW as *mut MONITORINFO, ) }; if status == false.into() { Err(io::Error::last_os_error()) } else { Ok(monitor_info) } } impl MonitorHandle { pub(crate) fn new(hmonitor: HMONITOR) -> Self { MonitorHandle(hmonitor) } #[inline] pub fn name(&self) -> Option { let monitor_info = get_monitor_info(self.0).unwrap(); Some( decode_wide(&monitor_info.szDevice) .to_string_lossy() .to_string(), ) } #[inline] pub fn native_identifier(&self) -> String { self.name().unwrap() } #[inline] pub fn hmonitor(&self) -> HMONITOR { self.0 } #[inline] pub fn size(&self) -> PhysicalSize { let rc_monitor = get_monitor_info(self.0).unwrap().monitorInfo.rcMonitor; PhysicalSize { width: (rc_monitor.right - rc_monitor.left) as u32, height: (rc_monitor.bottom - rc_monitor.top) as u32, } } #[inline] pub fn position(&self) -> PhysicalPosition { let rc_monitor = get_monitor_info(self.0).unwrap().monitorInfo.rcMonitor; PhysicalPosition { x: rc_monitor.left, y: rc_monitor.top, } } #[inline] pub fn scale_factor(&self) -> f64 { dpi_to_scale_factor(get_monitor_dpi(self.0).unwrap_or(96)) } #[inline] pub fn video_modes(&self) -> impl Iterator { // EnumDisplaySettingsExW can return duplicate values (or some of the // fields are probably changing, but we aren't looking at those fields // anyway), so we're using a BTreeSet deduplicate let mut modes = BTreeSet::new(); let mut i = 0; loop { unsafe { let monitor_info = get_monitor_info(self.0).unwrap(); let device_name = monitor_info.szDevice.as_ptr(); let mut mode: DEVMODEW = mem::zeroed(); mode.dmSize = mem::size_of_val(&mode) as u16; if EnumDisplaySettingsExW(device_name, i, &mut mode, 0) == false.into() { break; } i += 1; const REQUIRED_FIELDS: u32 = (DM_BITSPERPEL | DM_PELSWIDTH | DM_PELSHEIGHT | DM_DISPLAYFREQUENCY) as u32; assert!(has_flag(mode.dmFields, REQUIRED_FIELDS)); modes.insert(RootVideoMode { video_mode: VideoMode { size: (mode.dmPelsWidth, mode.dmPelsHeight), bit_depth: mode.dmBitsPerPel as u16, refresh_rate: mode.dmDisplayFrequency as u16, monitor: self.clone(), native_video_mode: Box::new(mode), }, }); } } modes.into_iter() } }