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) -> anyhow::Result<Self> {
67 let info = get_monitor_info(monitor)?;
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 = get_scale_factor_for_monitor(monitor)?;
75 let physical_size = size(
76 (monitor_size.right - monitor_size.left).into(),
77 (monitor_size.bottom - monitor_size.top).into(),
78 );
79
80 Ok(WindowsDisplay {
81 handle: monitor,
82 display_id: DisplayId(display_id as _),
83 scale_factor,
84 bounds: Bounds {
85 origin: logical_point(
86 monitor_size.left as f32,
87 monitor_size.top as f32,
88 scale_factor,
89 ),
90 size: physical_size.to_pixels(scale_factor),
91 },
92 physical_bounds: Bounds {
93 origin: point(monitor_size.left.into(), monitor_size.top.into()),
94 size: physical_size,
95 },
96 uuid,
97 })
98 }
99
100 fn new_with_handle_and_id(handle: HMONITOR, display_id: DisplayId) -> anyhow::Result<Self> {
101 let info = get_monitor_info(handle)?;
102 let monitor_size = info.monitorInfo.rcMonitor;
103 let uuid = generate_uuid(&info.szDevice);
104 let scale_factor = get_scale_factor_for_monitor(handle)?;
105 let physical_size = size(
106 (monitor_size.right - monitor_size.left).into(),
107 (monitor_size.bottom - monitor_size.top).into(),
108 );
109
110 Ok(WindowsDisplay {
111 handle,
112 display_id,
113 scale_factor,
114 bounds: Bounds {
115 origin: logical_point(
116 monitor_size.left as f32,
117 monitor_size.top as f32,
118 scale_factor,
119 ),
120 size: physical_size.to_pixels(scale_factor),
121 },
122 physical_bounds: Bounds {
123 origin: point(monitor_size.left.into(), monitor_size.top.into()),
124 size: physical_size,
125 },
126 uuid,
127 })
128 }
129
130 pub fn primary_monitor() -> Option<Self> {
131 // https://devblogs.microsoft.com/oldnewthing/20070809-00/?p=25643
132 const POINT_ZERO: POINT = POINT { x: 0, y: 0 };
133 let monitor = unsafe { MonitorFromPoint(POINT_ZERO, MONITOR_DEFAULTTOPRIMARY) };
134 if monitor.is_invalid() {
135 log::error!(
136 "can not find the primary monitor: {}",
137 std::io::Error::last_os_error()
138 );
139 return None;
140 }
141 WindowsDisplay::new_with_handle(monitor).log_err()
142 }
143
144 /// Check if the center point of given bounds is inside this monitor
145 pub fn check_given_bounds(&self, bounds: Bounds<Pixels>) -> bool {
146 let center = bounds.center();
147 let center = POINT {
148 x: (center.x.0 * self.scale_factor) as i32,
149 y: (center.y.0 * self.scale_factor) as i32,
150 };
151 let monitor = unsafe { MonitorFromPoint(center, MONITOR_DEFAULTTONULL) };
152 if monitor.is_invalid() {
153 false
154 } else {
155 let Ok(display) = WindowsDisplay::new_with_handle(monitor) else {
156 return false;
157 };
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 .filter_map(|(id, handle)| {
167 Some(Rc::new(
168 WindowsDisplay::new_with_handle_and_id(handle, DisplayId(id as _)).ok()?,
169 ) as Rc<dyn PlatformDisplay>)
170 })
171 .collect()
172 }
173
174 /// Check if this monitor is still online
175 pub fn is_connected(hmonitor: HMONITOR) -> bool {
176 available_monitors().iter().contains(&hmonitor)
177 }
178
179 pub fn physical_bounds(&self) -> Bounds<DevicePixels> {
180 self.physical_bounds
181 }
182}
183
184impl PlatformDisplay for WindowsDisplay {
185 fn id(&self) -> DisplayId {
186 self.display_id
187 }
188
189 fn uuid(&self) -> anyhow::Result<Uuid> {
190 Ok(self.uuid)
191 }
192
193 fn bounds(&self) -> Bounds<Pixels> {
194 self.bounds
195 }
196}
197
198fn available_monitors() -> SmallVec<[HMONITOR; 4]> {
199 let mut monitors: SmallVec<[HMONITOR; 4]> = SmallVec::new();
200 unsafe {
201 EnumDisplayMonitors(
202 None,
203 None,
204 Some(monitor_enum_proc),
205 LPARAM(&mut monitors as *mut _ as _),
206 )
207 .ok()
208 .log_err();
209 }
210 monitors
211}
212
213unsafe extern "system" fn monitor_enum_proc(
214 hmonitor: HMONITOR,
215 _hdc: HDC,
216 _place: *mut RECT,
217 data: LPARAM,
218) -> BOOL {
219 let monitors = data.0 as *mut SmallVec<[HMONITOR; 4]>;
220 unsafe { (*monitors).push(hmonitor) };
221 BOOL(1)
222}
223
224fn get_monitor_info(hmonitor: HMONITOR) -> anyhow::Result<MONITORINFOEXW> {
225 let mut monitor_info: MONITORINFOEXW = unsafe { std::mem::zeroed() };
226 monitor_info.monitorInfo.cbSize = std::mem::size_of::<MONITORINFOEXW>() as u32;
227 let status = unsafe {
228 GetMonitorInfoW(
229 hmonitor,
230 &mut monitor_info as *mut MONITORINFOEXW as *mut MONITORINFO,
231 )
232 };
233 if status.as_bool() {
234 Ok(monitor_info)
235 } else {
236 Err(anyhow::anyhow!(std::io::Error::last_os_error()))
237 }
238}
239
240fn generate_uuid(device_name: &[u16]) -> Uuid {
241 let name = device_name
242 .iter()
243 .flat_map(|&a| a.to_be_bytes())
244 .collect_vec();
245 Uuid::new_v5(&Uuid::NAMESPACE_DNS, &name)
246}
247
248fn get_scale_factor_for_monitor(monitor: HMONITOR) -> Result<f32> {
249 let mut dpi_x = 0;
250 let mut dpi_y = 0;
251 unsafe { GetDpiForMonitor(monitor, MDT_EFFECTIVE_DPI, &mut dpi_x, &mut dpi_y) }?;
252 assert_eq!(dpi_x, dpi_y);
253 Ok(dpi_x as f32 / USER_DEFAULT_SCREEN_DPI as f32)
254}