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 pub fn displays() -> Vec<Rc<dyn PlatformDisplay>> {
112 available_monitors()
113 .into_iter()
114 .enumerate()
115 .map(|(id, handle)| {
116 Rc::new(WindowsDisplay::new_with_handle_and_id(
117 handle,
118 DisplayId(id as _),
119 )) as Rc<dyn PlatformDisplay>
120 })
121 .collect()
122 }
123
124 pub(crate) fn frequency(&self) -> Option<u32> {
125 get_monitor_info(self.handle).ok().and_then(|info| {
126 let mut devmode = DEVMODEW::default();
127 unsafe {
128 EnumDisplaySettingsW(
129 PCWSTR(info.szDevice.as_ptr()),
130 ENUM_CURRENT_SETTINGS,
131 &mut devmode,
132 )
133 }
134 .as_bool()
135 .then(|| devmode.dmDisplayFrequency)
136 })
137 }
138}
139
140impl PlatformDisplay for WindowsDisplay {
141 fn id(&self) -> DisplayId {
142 self.display_id
143 }
144
145 fn uuid(&self) -> anyhow::Result<Uuid> {
146 Ok(self.uuid)
147 }
148
149 fn bounds(&self) -> Bounds<DevicePixels> {
150 self.bounds
151 }
152}
153
154fn available_monitors() -> SmallVec<[HMONITOR; 4]> {
155 let mut monitors: SmallVec<[HMONITOR; 4]> = SmallVec::new();
156 unsafe {
157 EnumDisplayMonitors(
158 HDC::default(),
159 None,
160 Some(monitor_enum_proc),
161 LPARAM(&mut monitors as *mut _ as _),
162 )
163 .ok()
164 .log_err();
165 }
166 monitors
167}
168
169unsafe extern "system" fn monitor_enum_proc(
170 hmonitor: HMONITOR,
171 _hdc: HDC,
172 _place: *mut RECT,
173 data: LPARAM,
174) -> BOOL {
175 let monitors = data.0 as *mut SmallVec<[HMONITOR; 4]>;
176 unsafe { (*monitors).push(hmonitor) };
177 BOOL(1)
178}
179
180fn get_monitor_info(hmonitor: HMONITOR) -> anyhow::Result<MONITORINFOEXW> {
181 let mut monitor_info: MONITORINFOEXW = unsafe { std::mem::zeroed() };
182 monitor_info.monitorInfo.cbSize = std::mem::size_of::<MONITORINFOEXW>() as u32;
183 let status = unsafe {
184 GetMonitorInfoW(
185 hmonitor,
186 &mut monitor_info as *mut MONITORINFOEXW as *mut MONITORINFO,
187 )
188 };
189 if status.as_bool() {
190 Ok(monitor_info)
191 } else {
192 Err(anyhow::anyhow!(std::io::Error::last_os_error()))
193 }
194}
195
196fn generate_uuid(device_name: &[u16]) -> Uuid {
197 let name = device_name
198 .iter()
199 .flat_map(|&a| a.to_be_bytes().to_vec())
200 .collect_vec();
201 Uuid::new_v5(&Uuid::NAMESPACE_DNS, &name)
202}