Skip to main content

winit/platform_impl/windows/
monitor.rs

1use std::collections::{BTreeSet, VecDeque};
2use std::hash::Hash;
3use std::{io, mem, ptr};
4
5use windows_sys::Win32::Foundation::{BOOL, HWND, LPARAM, POINT, RECT};
6use windows_sys::Win32::Graphics::Gdi::{
7    EnumDisplayMonitors, EnumDisplaySettingsExW, GetMonitorInfoW, MonitorFromPoint,
8    MonitorFromWindow, DEVMODEW, DM_BITSPERPEL, DM_DISPLAYFREQUENCY, DM_PELSHEIGHT, DM_PELSWIDTH,
9    ENUM_CURRENT_SETTINGS, HDC, HMONITOR, MONITORINFO, MONITORINFOEXW, MONITOR_DEFAULTTONEAREST,
10    MONITOR_DEFAULTTOPRIMARY,
11};
12
13use super::util::decode_wide;
14use crate::dpi::{PhysicalPosition, PhysicalSize};
15use crate::monitor::VideoModeHandle as RootVideoModeHandle;
16use crate::platform_impl::platform::dpi::{dpi_to_scale_factor, get_monitor_dpi};
17use crate::platform_impl::platform::util::has_flag;
18use crate::platform_impl::platform::window::Window;
19
20#[derive(Clone)]
21pub struct VideoModeHandle {
22    pub(crate) size: (u32, u32),
23    pub(crate) bit_depth: u16,
24    pub(crate) refresh_rate_millihertz: u32,
25    pub(crate) monitor: MonitorHandle,
26    // DEVMODEW is huge so we box it to avoid blowing up the size of winit::window::Fullscreen
27    pub(crate) native_video_mode: Box<DEVMODEW>,
28}
29
30impl PartialEq for VideoModeHandle {
31    fn eq(&self, other: &Self) -> bool {
32        self.size == other.size
33            && self.bit_depth == other.bit_depth
34            && self.refresh_rate_millihertz == other.refresh_rate_millihertz
35            && self.monitor == other.monitor
36    }
37}
38
39impl Eq for VideoModeHandle {}
40
41impl std::hash::Hash for VideoModeHandle {
42    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
43        self.size.hash(state);
44        self.bit_depth.hash(state);
45        self.refresh_rate_millihertz.hash(state);
46        self.monitor.hash(state);
47    }
48}
49
50impl std::fmt::Debug for VideoModeHandle {
51    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
52        f.debug_struct("VideoModeHandle")
53            .field("size", &self.size)
54            .field("bit_depth", &self.bit_depth)
55            .field("refresh_rate_millihertz", &self.refresh_rate_millihertz)
56            .field("monitor", &self.monitor)
57            .finish()
58    }
59}
60
61impl VideoModeHandle {
62    pub fn size(&self) -> PhysicalSize<u32> {
63        self.size.into()
64    }
65
66    pub fn bit_depth(&self) -> u16 {
67        self.bit_depth
68    }
69
70    pub fn refresh_rate_millihertz(&self) -> u32 {
71        self.refresh_rate_millihertz
72    }
73
74    pub fn monitor(&self) -> MonitorHandle {
75        self.monitor.clone()
76    }
77}
78
79#[derive(Debug, Clone, Eq, PartialEq, Hash, PartialOrd, Ord)]
80pub struct MonitorHandle(HMONITOR);
81
82// Send is not implemented for HMONITOR, we have to wrap it and implement it manually.
83// For more info see:
84// https://github.com/retep998/winapi-rs/issues/360
85// https://github.com/retep998/winapi-rs/issues/396
86
87unsafe impl Send for MonitorHandle {}
88
89unsafe extern "system" fn monitor_enum_proc(
90    hmonitor: HMONITOR,
91    _hdc: HDC,
92    _place: *mut RECT,
93    data: LPARAM,
94) -> BOOL {
95    let monitors = data as *mut VecDeque<MonitorHandle>;
96    unsafe { (*monitors).push_back(MonitorHandle::new(hmonitor)) };
97    true.into() // continue enumeration
98}
99
100pub fn available_monitors() -> VecDeque<MonitorHandle> {
101    let mut monitors: VecDeque<MonitorHandle> = VecDeque::new();
102    unsafe {
103        EnumDisplayMonitors(
104            0,
105            ptr::null(),
106            Some(monitor_enum_proc),
107            &mut monitors as *mut _ as LPARAM,
108        );
109    }
110    monitors
111}
112
113pub fn primary_monitor() -> MonitorHandle {
114    const ORIGIN: POINT = POINT { x: 0, y: 0 };
115    let hmonitor = unsafe { MonitorFromPoint(ORIGIN, MONITOR_DEFAULTTOPRIMARY) };
116    MonitorHandle::new(hmonitor)
117}
118
119pub fn current_monitor(hwnd: HWND) -> MonitorHandle {
120    let hmonitor = unsafe { MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST) };
121    MonitorHandle::new(hmonitor)
122}
123
124impl Window {
125    pub fn available_monitors(&self) -> VecDeque<MonitorHandle> {
126        available_monitors()
127    }
128
129    pub fn primary_monitor(&self) -> Option<MonitorHandle> {
130        let monitor = primary_monitor();
131        Some(monitor)
132    }
133}
134
135pub(crate) fn get_monitor_info(hmonitor: HMONITOR) -> Result<MONITORINFOEXW, io::Error> {
136    let mut monitor_info: MONITORINFOEXW = unsafe { mem::zeroed() };
137    monitor_info.monitorInfo.cbSize = mem::size_of::<MONITORINFOEXW>() as u32;
138    let status = unsafe {
139        GetMonitorInfoW(hmonitor, &mut monitor_info as *mut MONITORINFOEXW as *mut MONITORINFO)
140    };
141    if status == false.into() {
142        Err(io::Error::last_os_error())
143    } else {
144        Ok(monitor_info)
145    }
146}
147
148impl MonitorHandle {
149    pub(crate) fn new(hmonitor: HMONITOR) -> Self {
150        MonitorHandle(hmonitor)
151    }
152
153    #[inline]
154    pub fn name(&self) -> Option<String> {
155        let monitor_info = get_monitor_info(self.0).unwrap();
156        Some(decode_wide(&monitor_info.szDevice).to_string_lossy().to_string())
157    }
158
159    #[inline]
160    pub fn native_identifier(&self) -> String {
161        self.name().unwrap()
162    }
163
164    #[inline]
165    pub fn hmonitor(&self) -> HMONITOR {
166        self.0
167    }
168
169    #[inline]
170    pub fn size(&self) -> PhysicalSize<u32> {
171        let rc_monitor = get_monitor_info(self.0).unwrap().monitorInfo.rcMonitor;
172        PhysicalSize {
173            width: (rc_monitor.right - rc_monitor.left) as u32,
174            height: (rc_monitor.bottom - rc_monitor.top) as u32,
175        }
176    }
177
178    #[inline]
179    pub fn refresh_rate_millihertz(&self) -> Option<u32> {
180        let monitor_info = get_monitor_info(self.0).ok()?;
181        let device_name = monitor_info.szDevice.as_ptr();
182        unsafe {
183            let mut mode: DEVMODEW = mem::zeroed();
184            mode.dmSize = mem::size_of_val(&mode) as u16;
185            if EnumDisplaySettingsExW(device_name, ENUM_CURRENT_SETTINGS, &mut mode, 0)
186                == false.into()
187            {
188                None
189            } else {
190                Some(mode.dmDisplayFrequency * 1000)
191            }
192        }
193    }
194
195    #[inline]
196    pub fn position(&self) -> PhysicalPosition<i32> {
197        get_monitor_info(self.0)
198            .map(|info| {
199                let rc_monitor = info.monitorInfo.rcMonitor;
200                PhysicalPosition { x: rc_monitor.left, y: rc_monitor.top }
201            })
202            .unwrap_or(PhysicalPosition { x: 0, y: 0 })
203    }
204
205    #[inline]
206    pub fn scale_factor(&self) -> f64 {
207        dpi_to_scale_factor(get_monitor_dpi(self.0).unwrap_or(96))
208    }
209
210    #[inline]
211    pub fn video_modes(&self) -> impl Iterator<Item = VideoModeHandle> {
212        // EnumDisplaySettingsExW can return duplicate values (or some of the
213        // fields are probably changing, but we aren't looking at those fields
214        // anyway), so we're using a BTreeSet deduplicate
215        let mut modes = BTreeSet::<RootVideoModeHandle>::new();
216        let mod_map = |mode: RootVideoModeHandle| mode.video_mode;
217
218        let monitor_info = match get_monitor_info(self.0) {
219            Ok(monitor_info) => monitor_info,
220            Err(error) => {
221                tracing::warn!("Error from get_monitor_info: {error}");
222                return modes.into_iter().map(mod_map);
223            },
224        };
225
226        let device_name = monitor_info.szDevice.as_ptr();
227
228        let mut i = 0;
229        loop {
230            let mut mode: DEVMODEW = unsafe { mem::zeroed() };
231            mode.dmSize = mem::size_of_val(&mode) as u16;
232            if unsafe { EnumDisplaySettingsExW(device_name, i, &mut mode, 0) } == false.into() {
233                break;
234            }
235
236            const REQUIRED_FIELDS: u32 =
237                DM_BITSPERPEL | DM_PELSWIDTH | DM_PELSHEIGHT | DM_DISPLAYFREQUENCY;
238            assert!(has_flag(mode.dmFields, REQUIRED_FIELDS));
239
240            // Use Ord impl of RootVideoModeHandle
241            modes.insert(RootVideoModeHandle {
242                video_mode: VideoModeHandle {
243                    size: (mode.dmPelsWidth, mode.dmPelsHeight),
244                    bit_depth: mode.dmBitsPerPel as u16,
245                    refresh_rate_millihertz: mode.dmDisplayFrequency * 1000,
246                    monitor: self.clone(),
247                    native_video_mode: Box::new(mode),
248                },
249            });
250
251            i += 1;
252        }
253
254        modes.into_iter().map(mod_map)
255    }
256}