1use itertools::Itertools;
2use smallvec::SmallVec;
3use std::rc::Rc;
4use util::ResultExt;
5use uuid::Uuid;
6use windows::{
7 Win32::{
8 Foundation::*,
9 Graphics::Gdi::*,
10 UI::{
11 HiDpi::{GetDpiForMonitor, MDT_EFFECTIVE_DPI},
12 WindowsAndMessaging::USER_DEFAULT_SCREEN_DPI,
13 },
14 },
15 core::*,
16};
17
18use crate::logical_point;
19use gpui::{Bounds, DevicePixels, DisplayId, Pixels, PlatformDisplay, point, size};
20
21#[derive(Debug, Clone, Copy)]
22pub(crate) struct WindowsDisplay {
23 pub handle: HMONITOR,
24 pub display_id: DisplayId,
25 scale_factor: f32,
26 bounds: Bounds<Pixels>,
27 visible_bounds: Bounds<Pixels>,
28 physical_bounds: Bounds<DevicePixels>,
29 uuid: Uuid,
30}
31
32// The `HMONITOR` is thread-safe.
33unsafe impl Send for WindowsDisplay {}
34unsafe impl Sync for WindowsDisplay {}
35
36impl WindowsDisplay {
37 pub(crate) fn new(display_id: DisplayId) -> Option<Self> {
38 let screen = available_monitors()
39 .into_iter()
40 .nth(u32::from(display_id) as _)?;
41 let info = get_monitor_info(screen).log_err()?;
42 let monitor_size = info.monitorInfo.rcMonitor;
43 let work_area = info.monitorInfo.rcWork;
44 let uuid = generate_uuid(&info.szDevice);
45 let scale_factor = get_scale_factor_for_monitor(screen).log_err()?;
46 let physical_size = size(
47 (monitor_size.right - monitor_size.left).into(),
48 (monitor_size.bottom - monitor_size.top).into(),
49 );
50
51 Some(WindowsDisplay {
52 handle: screen,
53 display_id,
54 scale_factor,
55 bounds: Bounds {
56 origin: logical_point(
57 monitor_size.left as f32,
58 monitor_size.top as f32,
59 scale_factor,
60 ),
61 size: physical_size.to_pixels(scale_factor),
62 },
63 visible_bounds: Bounds {
64 origin: logical_point(work_area.left as f32, work_area.top as f32, scale_factor),
65 size: size(
66 (work_area.right - work_area.left) as f32 / scale_factor,
67 (work_area.bottom - work_area.top) as f32 / scale_factor,
68 )
69 .map(gpui::px),
70 },
71 physical_bounds: Bounds {
72 origin: point(monitor_size.left.into(), monitor_size.top.into()),
73 size: physical_size,
74 },
75 uuid,
76 })
77 }
78
79 pub fn new_with_handle(monitor: HMONITOR) -> anyhow::Result<Self> {
80 let info = get_monitor_info(monitor)?;
81 let monitor_size = info.monitorInfo.rcMonitor;
82 let work_area = info.monitorInfo.rcWork;
83 let uuid = generate_uuid(&info.szDevice);
84 let display_id = available_monitors()
85 .iter()
86 .position(|handle| handle.0 == monitor.0)
87 .unwrap();
88 let scale_factor = get_scale_factor_for_monitor(monitor)?;
89 let physical_size = size(
90 (monitor_size.right - monitor_size.left).into(),
91 (monitor_size.bottom - monitor_size.top).into(),
92 );
93
94 Ok(WindowsDisplay {
95 handle: monitor,
96 display_id: DisplayId::new(display_id as _),
97 scale_factor,
98 bounds: Bounds {
99 origin: logical_point(
100 monitor_size.left as f32,
101 monitor_size.top as f32,
102 scale_factor,
103 ),
104 size: physical_size.to_pixels(scale_factor),
105 },
106 visible_bounds: Bounds {
107 origin: logical_point(work_area.left as f32, work_area.top as f32, scale_factor),
108 size: size(
109 (work_area.right - work_area.left) as f32 / scale_factor,
110 (work_area.bottom - work_area.top) as f32 / scale_factor,
111 )
112 .map(gpui::px),
113 },
114 physical_bounds: Bounds {
115 origin: point(monitor_size.left.into(), monitor_size.top.into()),
116 size: physical_size,
117 },
118 uuid,
119 })
120 }
121
122 fn new_with_handle_and_id(handle: HMONITOR, display_id: DisplayId) -> anyhow::Result<Self> {
123 let info = get_monitor_info(handle)?;
124 let monitor_size = info.monitorInfo.rcMonitor;
125 let work_area = info.monitorInfo.rcWork;
126 let uuid = generate_uuid(&info.szDevice);
127 let scale_factor = get_scale_factor_for_monitor(handle)?;
128 let physical_size = size(
129 (monitor_size.right - monitor_size.left).into(),
130 (monitor_size.bottom - monitor_size.top).into(),
131 );
132
133 Ok(WindowsDisplay {
134 handle,
135 display_id,
136 scale_factor,
137 bounds: Bounds {
138 origin: logical_point(
139 monitor_size.left as f32,
140 monitor_size.top as f32,
141 scale_factor,
142 ),
143 size: physical_size.to_pixels(scale_factor),
144 },
145 visible_bounds: Bounds {
146 origin: logical_point(work_area.left as f32, work_area.top as f32, scale_factor),
147 size: size(
148 (work_area.right - work_area.left) as f32 / scale_factor,
149 (work_area.bottom - work_area.top) as f32 / scale_factor,
150 )
151 .map(gpui::px),
152 },
153 physical_bounds: Bounds {
154 origin: point(monitor_size.left.into(), monitor_size.top.into()),
155 size: physical_size,
156 },
157 uuid,
158 })
159 }
160
161 pub fn primary_monitor() -> Option<Self> {
162 // https://devblogs.microsoft.com/oldnewthing/20070809-00/?p=25643
163 const POINT_ZERO: POINT = POINT { x: 0, y: 0 };
164 let monitor = unsafe { MonitorFromPoint(POINT_ZERO, MONITOR_DEFAULTTOPRIMARY) };
165 if monitor.is_invalid() {
166 log::error!(
167 "can not find the primary monitor: {}",
168 std::io::Error::last_os_error()
169 );
170 return None;
171 }
172 WindowsDisplay::new_with_handle(monitor).log_err()
173 }
174
175 /// Check if the center point of given bounds is inside this monitor
176 pub fn check_given_bounds(&self, bounds: Bounds<Pixels>) -> bool {
177 let center = bounds.center();
178 let center = POINT {
179 x: (center.x.as_f32() * self.scale_factor) as i32,
180 y: (center.y.as_f32() * self.scale_factor) as i32,
181 };
182 let monitor = unsafe { MonitorFromPoint(center, MONITOR_DEFAULTTONULL) };
183 if monitor.is_invalid() {
184 false
185 } else {
186 let Ok(display) = WindowsDisplay::new_with_handle(monitor) else {
187 return false;
188 };
189 display.uuid == self.uuid
190 }
191 }
192
193 pub fn displays() -> Vec<Rc<dyn PlatformDisplay>> {
194 available_monitors()
195 .into_iter()
196 .enumerate()
197 .filter_map(|(id, handle)| {
198 Some(Rc::new(
199 WindowsDisplay::new_with_handle_and_id(handle, DisplayId::new(id as _)).ok()?,
200 ) as Rc<dyn PlatformDisplay>)
201 })
202 .collect()
203 }
204
205 pub fn physical_bounds(&self) -> Bounds<DevicePixels> {
206 self.physical_bounds
207 }
208}
209
210impl PlatformDisplay for WindowsDisplay {
211 fn id(&self) -> DisplayId {
212 self.display_id
213 }
214
215 fn uuid(&self) -> anyhow::Result<Uuid> {
216 Ok(self.uuid)
217 }
218
219 fn bounds(&self) -> Bounds<Pixels> {
220 self.bounds
221 }
222
223 fn visible_bounds(&self) -> Bounds<Pixels> {
224 self.visible_bounds
225 }
226}
227
228fn available_monitors() -> SmallVec<[HMONITOR; 4]> {
229 let mut monitors: SmallVec<[HMONITOR; 4]> = SmallVec::new();
230 unsafe {
231 EnumDisplayMonitors(
232 None,
233 None,
234 Some(monitor_enum_proc),
235 LPARAM(&mut monitors as *mut _ as _),
236 )
237 .ok()
238 .log_err();
239 }
240 monitors
241}
242
243unsafe extern "system" fn monitor_enum_proc(
244 hmonitor: HMONITOR,
245 _hdc: HDC,
246 _place: *mut RECT,
247 data: LPARAM,
248) -> BOOL {
249 let monitors = data.0 as *mut SmallVec<[HMONITOR; 4]>;
250 unsafe { (*monitors).push(hmonitor) };
251 BOOL(1)
252}
253
254fn get_monitor_info(hmonitor: HMONITOR) -> anyhow::Result<MONITORINFOEXW> {
255 let mut monitor_info: MONITORINFOEXW = unsafe { std::mem::zeroed() };
256 monitor_info.monitorInfo.cbSize = std::mem::size_of::<MONITORINFOEXW>() as u32;
257 let status = unsafe {
258 GetMonitorInfoW(
259 hmonitor,
260 &mut monitor_info as *mut MONITORINFOEXW as *mut MONITORINFO,
261 )
262 };
263 if status.as_bool() {
264 Ok(monitor_info)
265 } else {
266 Err(anyhow::anyhow!(std::io::Error::last_os_error()))
267 }
268}
269
270fn generate_uuid(device_name: &[u16]) -> Uuid {
271 let name = device_name
272 .iter()
273 .flat_map(|&a| a.to_be_bytes())
274 .collect_vec();
275 Uuid::new_v5(&Uuid::NAMESPACE_DNS, &name)
276}
277
278fn get_scale_factor_for_monitor(monitor: HMONITOR) -> Result<f32> {
279 let mut dpi_x = 0;
280 let mut dpi_y = 0;
281 unsafe { GetDpiForMonitor(monitor, MDT_EFFECTIVE_DPI, &mut dpi_x, &mut dpi_y) }?;
282 assert_eq!(dpi_x, dpi_y);
283 Ok(dpi_x as f32 / USER_DEFAULT_SCREEN_DPI as f32)
284}