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