1use std::{
4 collections::HashMap,
5 ffi::{OsStr, OsString, c_void},
6 os::windows::ffi::OsStringExt,
7 sync::{Arc, Mutex},
8};
9
10use dpi::{LogicalPosition, LogicalSize};
11use smallvec::SmallVec;
12use windows::{
13 Win32::{
14 Devices::Display::*,
15 Foundation::*,
16 Graphics::Gdi::*,
17 System::LibraryLoader::*,
18 UI::{HiDpi::*, WindowsAndMessaging::*},
19 },
20 core::{BOOL, w},
21};
22
23use crate::{Display, DisplayEventCallback, Event};
24
25pub type WindowsError = windows::core::Error;
30
31pub fn set_process_per_monitor_dpi_aware() -> Result<(), WindowsError> {
48 unsafe { SetProcessDpiAwareness(PROCESS_PER_MONITOR_DPI_AWARE) }
49}
50
51#[derive(Debug, Clone)]
57pub struct WindowsDisplayId {
58 name: Arc<OsString>,
59}
60
61impl std::hash::Hash for WindowsDisplayId {
62 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
63 self.name.hash(state)
64 }
65}
66
67impl PartialEq for WindowsDisplayId {
68 fn eq(&self, other: &Self) -> bool {
69 self.name.eq(&other.name)
70 }
71}
72
73impl Eq for WindowsDisplayId {}
74
75impl WindowsDisplayId {
76 pub fn new(name: OsString) -> Self {
78 Self {
79 name: Arc::new(name),
80 }
81 }
82
83 pub fn from_handle(handle: HMONITOR) -> Result<Self, WindowsError> {
88 let mut monitor_info = MONITORINFOEXW::default();
89 monitor_info.monitorInfo.cbSize = std::mem::size_of::<MONITORINFOEXW>() as _;
90
91 unsafe { GetMonitorInfoW(handle, &raw mut monitor_info as _).ok()? };
92
93 let name_slice = &monitor_info.szDevice;
94 let len = name_slice
95 .iter()
96 .position(|&c| c == 0)
97 .unwrap_or(name_slice.len());
98 let name = OsString::from_wide(&monitor_info.szDevice[..len]);
99
100 Ok(Self {
101 name: Arc::new(name),
102 })
103 }
104
105 pub fn device_name(&self) -> &OsStr {
112 &self.name
113 }
114}
115
116fn is_display_mirrored(device_name: &OsStr) -> Result<bool, WindowsError> {
117 let mut path_count = 0;
118 let mut mode_count = 0;
119
120 unsafe {
121 GetDisplayConfigBufferSizes(QDC_ONLY_ACTIVE_PATHS, &mut path_count, &mut mode_count)
122 .ok()?;
123 }
124
125 let mut paths = vec![DISPLAYCONFIG_PATH_INFO::default(); path_count as usize];
126 let mut modes = vec![DISPLAYCONFIG_MODE_INFO::default(); mode_count as usize];
127
128 unsafe {
129 QueryDisplayConfig(
130 QDC_ONLY_ACTIVE_PATHS,
131 &mut path_count,
132 paths.as_mut_ptr(),
133 &mut mode_count,
134 modes.as_mut_ptr(),
135 None,
136 )
137 .ok()?;
138 }
139
140 let mut match_count = 0;
141 for path in paths.iter().take(path_count as usize) {
142 let mut source_name = DISPLAYCONFIG_SOURCE_DEVICE_NAME::default();
143
144 source_name.header.r#type = DISPLAYCONFIG_DEVICE_INFO_GET_SOURCE_NAME;
145 source_name.header.size = std::mem::size_of::<DISPLAYCONFIG_SOURCE_DEVICE_NAME>() as u32;
146 source_name.header.adapterId = path.sourceInfo.adapterId;
147 source_name.header.id = path.sourceInfo.id;
148
149 if unsafe { DisplayConfigGetDeviceInfo(&mut source_name.header as *mut _) }
150 == ERROR_SUCCESS.0 as i32
151 {
152 let name_slice = &source_name.viewGdiDeviceName;
153 let len = name_slice
154 .iter()
155 .position(|&c| c == 0)
156 .unwrap_or(name_slice.len());
157 let name = OsString::from_wide(&name_slice[..len]);
158
159 if name == device_name {
160 match_count += 1;
161 }
162 }
163 }
164
165 Ok(match_count > 1)
166}
167
168fn get_scale_factor(hdc: HDC, h_monitor: HMONITOR) -> f64 {
169 const USER_DEFAULT_SCREEN_DPI: u32 = 96;
171
172 let mut dpi_x = 0;
173 let mut dpi_y = 0;
174 let result = unsafe { GetDpiForMonitor(h_monitor, MDT_EFFECTIVE_DPI, &mut dpi_x, &mut dpi_y) };
175
176 if result.is_err() {
177 dpi_x = if unsafe { IsProcessDPIAware().as_bool() } {
178 unsafe { GetDeviceCaps(Some(hdc), LOGPIXELSX) as _ }
179 } else {
180 USER_DEFAULT_SCREEN_DPI
181 };
182 };
183
184 dpi_x as f64 / USER_DEFAULT_SCREEN_DPI as f64
185}
186
187struct EnumDisplayMonitorsUserData {
188 displays: Vec<Display>,
189 result: Result<(), WindowsError>,
190}
191
192unsafe extern "system" fn monitor_enum_proc(
193 h_monitor: HMONITOR,
194 hdc: HDC,
195 _rect: *mut RECT,
196 user_data: LPARAM,
197) -> BOOL {
198 let monitors_ptr = user_data.0 as *mut EnumDisplayMonitorsUserData;
199 if monitors_ptr.is_null() {
200 return false.into();
201 }
202
203 let user_data = unsafe { &mut *monitors_ptr };
204
205 let mut monitor_info = MONITORINFOEXW::default();
207 monitor_info.monitorInfo.cbSize = std::mem::size_of::<MONITORINFOEXW>() as _;
208
209 if let Err(e) = unsafe { GetMonitorInfoW(h_monitor, &raw mut monitor_info as _) }.ok() {
210 user_data.result = Err(e);
211 return true.into(); }
213
214 let name_slice = &monitor_info.szDevice;
215 let len = name_slice
216 .iter()
217 .position(|&c| c == 0)
218 .unwrap_or(name_slice.len());
219 let device_name = OsString::from_wide(&monitor_info.szDevice[..len]);
220 let id = WindowsDisplayId::new(device_name);
221
222 let origin = LogicalPosition::new(
223 monitor_info.monitorInfo.rcMonitor.left,
224 monitor_info.monitorInfo.rcMonitor.top,
225 );
226 let size = LogicalSize::new(
227 (monitor_info.monitorInfo.rcMonitor.right - monitor_info.monitorInfo.rcMonitor.left) as u32,
228 (monitor_info.monitorInfo.rcMonitor.bottom - monitor_info.monitorInfo.rcMonitor.top) as u32,
229 );
230 let is_primary = (monitor_info.monitorInfo.dwFlags & MONITORINFOF_PRIMARY) != 0;
231
232 let is_mirrored = match is_display_mirrored(id.device_name()) {
233 Ok(value) => value,
234 Err(e) => {
235 user_data.result = Err(e);
236 return false.into();
237 }
238 };
239 let scale_factor = get_scale_factor(hdc, h_monitor);
240
241 user_data.displays.push(Display {
242 id: id.into(),
243 origin,
244 size,
245 scale_factor,
246 is_primary,
247 is_mirrored,
248 });
249
250 true.into()
251}
252
253pub(crate) fn get_windows_displays() -> Result<Vec<Display>, WindowsError> {
255 let mut user_data: EnumDisplayMonitorsUserData = EnumDisplayMonitorsUserData {
256 displays: Vec::new(),
257 result: Ok(()),
258 };
259
260 unsafe {
261 EnumDisplayMonitors(
262 None,
263 None,
264 Some(monitor_enum_proc),
265 LPARAM(&raw mut user_data as isize),
266 )
267 .ok()?;
268 };
269
270 user_data.result.map(|_| user_data.displays)
271}
272
273struct EventTracker {
274 cached_displays: HashMap<WindowsDisplayId, Display>,
275}
276
277impl EventTracker {
278 fn new() -> Result<Self, WindowsError> {
279 let mut tracker = Self {
280 cached_displays: HashMap::new(),
281 };
282 tracker.cached_displays = tracker.collect_new_cached_state()?;
283
284 Ok(tracker)
285 }
286
287 fn collect_new_cached_state(&self) -> Result<HashMap<WindowsDisplayId, Display>, WindowsError> {
288 let displays = get_windows_displays()?;
289 let mut cached_state = HashMap::new();
290
291 for display in displays {
292 let win_id = display.id.windows_id();
293 cached_state.insert(win_id.clone(), display);
294 }
295
296 Ok(cached_state)
297 }
298
299 fn track_events(&mut self) -> Result<SmallVec<[Event; 10]>, WindowsError> {
300 let new_cached_state = self.collect_new_cached_state()?;
301 let before = std::mem::replace(&mut self.cached_displays, new_cached_state);
302 let mut events = SmallVec::new();
303
304 for (id, before_display) in before.iter() {
305 if let Some(after_display) = self.cached_displays.get(id) {
306 if before_display.size != after_display.size {
307 events.push(Event::SizeChanged {
308 display: (*after_display).clone(),
309 before: before_display.size,
310 after: after_display.size,
311 });
312 };
313
314 if before_display.origin != after_display.origin {
315 events.push(Event::OriginChanged {
316 display: (*after_display).clone(),
317 before: before_display.origin,
318 after: after_display.origin,
319 });
320 }
321
322 if before_display.is_mirrored != after_display.is_mirrored {
323 let event = if after_display.is_mirrored {
324 Event::Mirrored((*after_display).clone())
325 } else {
326 Event::UnMirrored((*after_display).clone())
327 };
328
329 events.push(event);
330 }
331 } else {
332 events.push(Event::Removed(id.clone().into()));
333 }
334 }
335
336 for (id, after_display) in &self.cached_displays {
337 if !before.contains_key(id) {
338 events.push(Event::Added((*after_display).clone()));
339 }
340 }
341
342 Ok(events)
343 }
344}
345
346struct ObserverContext {
347 callback: Option<DisplayEventCallback>,
348 tracker: EventTracker,
349}
350
351pub(crate) struct WindowsDisplayObserver {
356 hwnd: HWND,
357 h_notify: HDEVNOTIFY,
358 ctx: Arc<Mutex<ObserverContext>>,
359}
360
361impl WindowsDisplayObserver {
362 pub fn new() -> Result<Self, WindowsError> {
371 let h_instance = unsafe { GetModuleHandleW(None)? };
372 let window_class_name = w!("DisplayMonitorClass");
373 let window_class = WNDCLASSW {
374 style: CS_HREDRAW | CS_VREDRAW,
375 lpfnWndProc: Some(wnd_proc),
376 hInstance: h_instance.into(),
377 lpszClassName: window_class_name,
378 ..Default::default()
379 };
380
381 unsafe {
382 RegisterClassW(&window_class);
383 }
384
385 let ctx = Arc::new(Mutex::new(ObserverContext {
386 callback: None,
387 tracker: EventTracker::new()?,
388 }));
389 let state_ptr = Arc::as_ptr(&ctx) as *mut c_void;
390
391 let hwnd = unsafe {
392 CreateWindowExW(
393 WINDOW_EX_STYLE::default(),
394 window_class_name,
395 w!("DisplayMonitorWindow"),
396 WS_OVERLAPPEDWINDOW,
397 0,
398 0,
399 0,
400 0,
401 None,
402 None,
403 Some(h_instance.into()),
404 Some(state_ptr),
405 )?
406 };
407
408 let mut filter = DEV_BROADCAST_DEVICEINTERFACE_W {
409 dbcc_size: std::mem::size_of::<DEV_BROADCAST_DEVICEINTERFACE_W>() as u32,
410 dbcc_devicetype: DBT_DEVTYP_DEVICEINTERFACE.0,
411 dbcc_classguid: GUID_DEVINTERFACE_MONITOR,
412 ..Default::default()
413 };
414
415 let h_notify = unsafe {
416 match RegisterDeviceNotificationW(
417 hwnd.into(),
418 &mut filter as *mut _ as *const c_void,
419 DEVICE_NOTIFY_WINDOW_HANDLE,
420 ) {
421 Ok(handle) => handle,
422 Err(e) => {
423 _ = DestroyWindow(hwnd);
424 return Err(e);
425 }
426 }
427 };
428
429 unsafe {
432 SetWindowLongPtrW(hwnd, GWLP_USERDATA, state_ptr as isize);
433 }
434
435 Ok(Self {
436 hwnd,
437 h_notify,
438 ctx,
439 })
440 }
441
442 pub fn set_callback(&self, callback: DisplayEventCallback) {
447 let mut state = self.ctx.lock().unwrap();
448 state.callback = Some(callback);
449 }
450
451 pub fn remove_callback(&self) {
454 let mut state = self.ctx.lock().unwrap();
455 state.callback = None;
456 }
457
458 pub fn run(&self) -> Result<(), WindowsError> {
465 unsafe {
466 let mut msg = MSG::default();
467 loop {
468 let message_state = GetMessageW(&mut msg, None, 0, 0).0;
469 if message_state == -1 {
470 return Err(WindowsError::from_thread());
471 }
472 if message_state == 0 {
473 break;
474 }
475
476 _ = TranslateMessage(&msg);
477 DispatchMessageW(&msg);
478 }
479 }
480
481 Ok(())
482 }
483}
484
485impl Drop for WindowsDisplayObserver {
486 fn drop(&mut self) {
487 unsafe {
488 if !self.h_notify.is_invalid() {
489 _ = UnregisterDeviceNotification(self.h_notify);
490 }
491 _ = DestroyWindow(self.hwnd);
492 }
493 }
494}
495
496#[inline]
497fn process_window_message(
498 msg: u32,
499 _wparam: WPARAM,
500 _lparam: LPARAM,
501 ctx: &mut ObserverContext,
502) -> Result<Option<SmallVec<[Event; 10]>>, WindowsError> {
503 Ok(match msg {
504 WM_DISPLAYCHANGE => Some(ctx.tracker.track_events()?),
505 _ => None,
506 })
507}
508
509unsafe extern "system" fn wnd_proc(
510 hwnd: HWND,
511 msg: u32,
512 wparam: WPARAM,
513 lparam: LPARAM,
514) -> LRESULT {
515 let default_window_proc = || unsafe { DefWindowProcW(hwnd, msg, wparam, lparam) };
516
517 let ctx = unsafe {
518 let user_data = GetWindowLongPtrW(hwnd, GWLP_USERDATA);
519 let user_data_ptr = user_data as *const Mutex<ObserverContext>;
520
521 if user_data_ptr.is_null() {
522 return default_window_proc();
523 }
524
525 &*(user_data_ptr)
526 };
527
528 if let Ok(mut ctx) = ctx.lock()
529 && let Ok(Some(events)) = process_window_message(msg, wparam, lparam, &mut ctx)
530 && let Some(callback) = ctx.callback.as_mut()
531 {
532 for event in events {
533 (callback)(event);
534 }
535 }
536
537 default_window_proc()
538}