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