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