1use itertools::Itertools;
2use smallvec::SmallVec;
3use std::rc::Rc;
4use util::ResultExt;
5use uuid::Uuid;
6use windows::{
7 Win32::{
8 Foundation::*,
9 Graphics::Gdi::*,
10 UI::{
11 HiDpi::{GetDpiForMonitor, MDT_EFFECTIVE_DPI},
12 WindowsAndMessaging::USER_DEFAULT_SCREEN_DPI,
13 },
14 },
15 core::*,
16};
17
18use crate::{Bounds, DevicePixels, DisplayId, Pixels, PlatformDisplay, logical_point, point, size};
19
20#[derive(Debug, Clone, Copy)]
21pub(crate) struct WindowsDisplay {
22 pub handle: HMONITOR,
23 pub display_id: DisplayId,
24 scale_factor: f32,
25 bounds: Bounds<Pixels>,
26 physical_bounds: Bounds<DevicePixels>,
27 uuid: Uuid,
28}
29
30// The `HMONITOR` is thread-safe.
31unsafe impl Send for WindowsDisplay {}
32unsafe impl Sync for WindowsDisplay {}
33
34impl WindowsDisplay {
35 pub(crate) fn new(display_id: DisplayId) -> Option<Self> {
36 let screen = available_monitors().into_iter().nth(display_id.0 as _)?;
37 let info = get_monitor_info(screen).log_err()?;
38 let monitor_size = info.monitorInfo.rcMonitor;
39 let uuid = generate_uuid(&info.szDevice);
40 let scale_factor = get_scale_factor_for_monitor(screen).log_err()?;
41 let physical_size = size(
42 (monitor_size.right - monitor_size.left).into(),
43 (monitor_size.bottom - monitor_size.top).into(),
44 );
45
46 Some(WindowsDisplay {
47 handle: screen,
48 display_id,
49 scale_factor,
50 bounds: Bounds {
51 origin: logical_point(
52 monitor_size.left as f32,
53 monitor_size.top as f32,
54 scale_factor,
55 ),
56 size: physical_size.to_pixels(scale_factor),
57 },
58 physical_bounds: Bounds {
59 origin: point(monitor_size.left.into(), monitor_size.top.into()),
60 size: physical_size,
61 },
62 uuid,
63 })
64 }
65
66 pub fn new_with_handle(monitor: HMONITOR) -> Self {
67 let info = get_monitor_info(monitor).expect("unable to get monitor info");
68 let monitor_size = info.monitorInfo.rcMonitor;
69 let uuid = generate_uuid(&info.szDevice);
70 let display_id = available_monitors()
71 .iter()
72 .position(|handle| handle.0 == monitor.0)
73 .unwrap();
74 let scale_factor =
75 get_scale_factor_for_monitor(monitor).expect("unable to get scale factor for monitor");
76 let physical_size = size(
77 (monitor_size.right - monitor_size.left).into(),
78 (monitor_size.bottom - monitor_size.top).into(),
79 );
80
81 WindowsDisplay {
82 handle: monitor,
83 display_id: DisplayId(display_id as _),
84 scale_factor,
85 bounds: Bounds {
86 origin: logical_point(
87 monitor_size.left as f32,
88 monitor_size.top as f32,
89 scale_factor,
90 ),
91 size: physical_size.to_pixels(scale_factor),
92 },
93 physical_bounds: Bounds {
94 origin: point(monitor_size.left.into(), monitor_size.top.into()),
95 size: physical_size,
96 },
97 uuid,
98 }
99 }
100
101 fn new_with_handle_and_id(handle: HMONITOR, display_id: DisplayId) -> Self {
102 let info = get_monitor_info(handle).expect("unable to get monitor info");
103 let monitor_size = info.monitorInfo.rcMonitor;
104 let uuid = generate_uuid(&info.szDevice);
105 let scale_factor =
106 get_scale_factor_for_monitor(handle).expect("unable to get scale factor for monitor");
107 let physical_size = size(
108 (monitor_size.right - monitor_size.left).into(),
109 (monitor_size.bottom - monitor_size.top).into(),
110 );
111
112 WindowsDisplay {
113 handle,
114 display_id,
115 scale_factor,
116 bounds: Bounds {
117 origin: logical_point(
118 monitor_size.left as f32,
119 monitor_size.top as f32,
120 scale_factor,
121 ),
122 size: physical_size.to_pixels(scale_factor),
123 },
124 physical_bounds: Bounds {
125 origin: point(monitor_size.left.into(), monitor_size.top.into()),
126 size: physical_size,
127 },
128 uuid,
129 }
130 }
131
132 pub fn primary_monitor() -> Option<Self> {
133 // https://devblogs.microsoft.com/oldnewthing/20070809-00/?p=25643
134 const POINT_ZERO: POINT = POINT { x: 0, y: 0 };
135 let monitor = unsafe { MonitorFromPoint(POINT_ZERO, MONITOR_DEFAULTTOPRIMARY) };
136 if monitor.is_invalid() {
137 log::error!(
138 "can not find the primary monitor: {}",
139 std::io::Error::last_os_error()
140 );
141 return None;
142 }
143 Some(WindowsDisplay::new_with_handle(monitor))
144 }
145
146 /// Check if the center point of given bounds is inside this monitor
147 pub fn check_given_bounds(&self, bounds: Bounds<Pixels>) -> bool {
148 let center = bounds.center();
149 let center = POINT {
150 x: (center.x.0 * self.scale_factor) as i32,
151 y: (center.y.0 * self.scale_factor) as i32,
152 };
153 let monitor = unsafe { MonitorFromPoint(center, MONITOR_DEFAULTTONULL) };
154 if monitor.is_invalid() {
155 false
156 } else {
157 let display = WindowsDisplay::new_with_handle(monitor);
158 display.uuid == self.uuid
159 }
160 }
161
162 pub fn displays() -> Vec<Rc<dyn PlatformDisplay>> {
163 available_monitors()
164 .into_iter()
165 .enumerate()
166 .map(|(id, handle)| {
167 Rc::new(WindowsDisplay::new_with_handle_and_id(
168 handle,
169 DisplayId(id as _),
170 )) as Rc<dyn PlatformDisplay>
171 })
172 .collect()
173 }
174
175 /// Check if this monitor is still online
176 pub fn is_connected(hmonitor: HMONITOR) -> bool {
177 available_monitors().iter().contains(&hmonitor)
178 }
179
180 pub fn physical_bounds(&self) -> Bounds<DevicePixels> {
181 self.physical_bounds
182 }
183}
184
185impl PlatformDisplay for WindowsDisplay {
186 fn id(&self) -> DisplayId {
187 self.display_id
188 }
189
190 fn uuid(&self) -> anyhow::Result<Uuid> {
191 Ok(self.uuid)
192 }
193
194 fn bounds(&self) -> Bounds<Pixels> {
195 self.bounds
196 }
197}
198
199fn available_monitors() -> SmallVec<[HMONITOR; 4]> {
200 let mut monitors: SmallVec<[HMONITOR; 4]> = SmallVec::new();
201 unsafe {
202 EnumDisplayMonitors(
203 None,
204 None,
205 Some(monitor_enum_proc),
206 LPARAM(&mut monitors as *mut _ as _),
207 )
208 .ok()
209 .log_err();
210 }
211 monitors
212}
213
214unsafe extern "system" fn monitor_enum_proc(
215 hmonitor: HMONITOR,
216 _hdc: HDC,
217 _place: *mut RECT,
218 data: LPARAM,
219) -> BOOL {
220 let monitors = data.0 as *mut SmallVec<[HMONITOR; 4]>;
221 unsafe { (*monitors).push(hmonitor) };
222 BOOL(1)
223}
224
225fn get_monitor_info(hmonitor: HMONITOR) -> anyhow::Result<MONITORINFOEXW> {
226 let mut monitor_info: MONITORINFOEXW = unsafe { std::mem::zeroed() };
227 monitor_info.monitorInfo.cbSize = std::mem::size_of::<MONITORINFOEXW>() as u32;
228 let status = unsafe {
229 GetMonitorInfoW(
230 hmonitor,
231 &mut monitor_info as *mut MONITORINFOEXW as *mut MONITORINFO,
232 )
233 };
234 if status.as_bool() {
235 Ok(monitor_info)
236 } else {
237 Err(anyhow::anyhow!(std::io::Error::last_os_error()))
238 }
239}
240
241fn generate_uuid(device_name: &[u16]) -> Uuid {
242 let name = device_name
243 .iter()
244 .flat_map(|&a| a.to_be_bytes())
245 .collect_vec();
246 Uuid::new_v5(&Uuid::NAMESPACE_DNS, &name)
247}
248
249fn get_scale_factor_for_monitor(monitor: HMONITOR) -> Result<f32> {
250 let mut dpi_x = 0;
251 let mut dpi_y = 0;
252 unsafe { GetDpiForMonitor(monitor, MDT_EFFECTIVE_DPI, &mut dpi_x, &mut dpi_y) }?;
253 assert_eq!(dpi_x, dpi_y);
254 Ok(dpi_x as f32 / USER_DEFAULT_SCREEN_DPI as f32)
255}