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