1use itertools::Itertools;
2use smallvec::SmallVec;
3use std::rc::Rc;
4use uuid::Uuid;
5use windows::{
6 core::*,
7 Win32::{Foundation::*, Graphics::Gdi::*},
8};
9
10use crate::{Bounds, DisplayId, GlobalPixels, PlatformDisplay, Point, Size};
11
12#[derive(Debug)]
13pub(crate) struct WindowsDisplay {
14 pub handle: HMONITOR,
15 pub display_id: DisplayId,
16 bounds: Bounds<GlobalPixels>,
17 uuid: Uuid,
18}
19
20impl WindowsDisplay {
21 pub(crate) fn new(display_id: DisplayId) -> Option<Self> {
22 let Some(screen) = available_monitors().into_iter().nth(display_id.0 as _) else {
23 return None;
24 };
25 let Ok(info) = get_monitor_info(screen).inspect_err(|e| log::error!("{}", e)) else {
26 return None;
27 };
28 let size = info.monitorInfo.rcMonitor;
29 let uuid = generate_uuid(&info.szDevice);
30
31 Some(WindowsDisplay {
32 handle: screen,
33 display_id,
34 bounds: Bounds {
35 origin: Point {
36 x: GlobalPixels(size.left as f32),
37 y: GlobalPixels(size.top as f32),
38 },
39 size: Size {
40 width: GlobalPixels((size.right - size.left) as f32),
41 height: GlobalPixels((size.bottom - size.top) as f32),
42 },
43 },
44 uuid,
45 })
46 }
47
48 pub fn new_with_handle(monitor: HMONITOR) -> Self {
49 let info = get_monitor_info(monitor).expect("unable to get monitor info");
50 let size = info.monitorInfo.rcMonitor;
51 let uuid = generate_uuid(&info.szDevice);
52 let display_id = available_monitors()
53 .iter()
54 .position(|handle| handle.0 == monitor.0)
55 .unwrap();
56
57 WindowsDisplay {
58 handle: monitor,
59 display_id: DisplayId(display_id as _),
60 bounds: Bounds {
61 origin: Point {
62 x: GlobalPixels(size.left as f32),
63 y: GlobalPixels(size.top as f32),
64 },
65 size: Size {
66 width: GlobalPixels((size.right - size.left) as f32),
67 height: GlobalPixels((size.bottom - size.top) as f32),
68 },
69 },
70 uuid,
71 }
72 }
73
74 fn new_with_handle_and_id(handle: HMONITOR, display_id: DisplayId) -> Self {
75 let info = get_monitor_info(handle).expect("unable to get monitor info");
76 let size = info.monitorInfo.rcMonitor;
77 let uuid = generate_uuid(&info.szDevice);
78
79 WindowsDisplay {
80 handle,
81 display_id,
82 bounds: Bounds {
83 origin: Point {
84 x: GlobalPixels(size.left as f32),
85 y: GlobalPixels(size.top as f32),
86 },
87 size: Size {
88 width: GlobalPixels((size.right - size.left) as f32),
89 height: GlobalPixels((size.bottom - size.top) as f32),
90 },
91 },
92 uuid,
93 }
94 }
95
96 pub fn primary_monitor() -> Option<Self> {
97 // https://devblogs.microsoft.com/oldnewthing/20070809-00/?p=25643
98 const POINT_ZERO: POINT = POINT { x: 0, y: 0 };
99 let monitor = unsafe { MonitorFromPoint(POINT_ZERO, MONITOR_DEFAULTTOPRIMARY) };
100 if monitor.is_invalid() {
101 log::error!(
102 "can not find the primary monitor: {}",
103 std::io::Error::last_os_error()
104 );
105 return None;
106 }
107 Some(WindowsDisplay::new_with_handle(monitor))
108 }
109
110 pub fn displays() -> Vec<Rc<dyn PlatformDisplay>> {
111 available_monitors()
112 .into_iter()
113 .enumerate()
114 .map(|(id, handle)| {
115 Rc::new(WindowsDisplay::new_with_handle_and_id(
116 handle,
117 DisplayId(id as _),
118 )) as Rc<dyn PlatformDisplay>
119 })
120 .collect()
121 }
122
123 pub(crate) fn frequency(&self) -> Option<u32> {
124 available_monitors()
125 .get(self.display_id.0 as usize)
126 .and_then(|hmonitor| get_monitor_info(*hmonitor).ok())
127 .and_then(|info| {
128 let mut devmode = DEVMODEW::default();
129 unsafe {
130 EnumDisplaySettingsW(
131 PCWSTR(info.szDevice.as_ptr()),
132 ENUM_CURRENT_SETTINGS,
133 &mut devmode,
134 )
135 }
136 .as_bool()
137 .then(|| devmode.dmDisplayFrequency)
138 })
139 }
140}
141
142impl PlatformDisplay for WindowsDisplay {
143 fn id(&self) -> DisplayId {
144 self.display_id
145 }
146
147 fn uuid(&self) -> anyhow::Result<Uuid> {
148 Ok(self.uuid)
149 }
150
151 fn bounds(&self) -> Bounds<GlobalPixels> {
152 self.bounds
153 }
154}
155
156fn available_monitors() -> SmallVec<[HMONITOR; 4]> {
157 let mut monitors: SmallVec<[HMONITOR; 4]> = SmallVec::new();
158 unsafe {
159 EnumDisplayMonitors(
160 HDC::default(),
161 None,
162 Some(monitor_enum_proc),
163 LPARAM(&mut monitors as *mut _ as _),
164 );
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}