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