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