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