Evan's Blog
EvanZhu0721 / 公开仓库

SBMS

镜像已同步

SBMS complete the calculation rules for the default logical desktop topology relationship and the physical dimensions of the displays in the Windows multi-screen collaboration system.

上游 GitHub
READ-ONLY MIRROR

README、源码与 Release 已保存到本站;源码视图固定到 commit a37d6c4b。

main/ root / src
a37d6c4b· 固定源码快照
src/display.rs29.8 KB
1use std::error::Error;
2use std::fmt::{Display as FmtDisplay, Formatter};
3use std::mem::size_of;
4use std::thread;
5use std::time::Duration;
6
7use windows::Win32::Devices::DeviceAndDriverInstallation::{
8 DICS_FLAG_GLOBAL, DIGCF_DEVICEINTERFACE, DIGCF_PRESENT, DIREG_DEV, HDEVINFO,
9 SP_DEVICE_INTERFACE_DATA, SP_DEVICE_INTERFACE_DETAIL_DATA_W, SP_DEVINFO_DATA,
10 SetupDiDestroyDeviceInfoList, SetupDiGetClassDevsW, SetupDiGetDeviceInstanceIdW,
11 SetupDiGetDeviceInterfaceDetailW, SetupDiOpenDevRegKey, SetupDiOpenDeviceInterfaceW,
12};
13use windows::Win32::Devices::Display::{
14 DISPLAYCONFIG_ADAPTER_NAME, DISPLAYCONFIG_DEVICE_INFO_GET_ADAPTER_NAME,
15 DISPLAYCONFIG_DEVICE_INFO_GET_SOURCE_NAME, DISPLAYCONFIG_DEVICE_INFO_GET_TARGET_NAME,
16 DISPLAYCONFIG_DEVICE_INFO_HEADER, DISPLAYCONFIG_MODE_INFO, DISPLAYCONFIG_MODE_INFO_TYPE_SOURCE,
17 DISPLAYCONFIG_MODE_INFO_TYPE_TARGET, DISPLAYCONFIG_PATH_INFO, DISPLAYCONFIG_ROTATION_IDENTITY,
18 DISPLAYCONFIG_ROTATION_ROTATE90, DISPLAYCONFIG_ROTATION_ROTATE180,
19 DISPLAYCONFIG_ROTATION_ROTATE270, DISPLAYCONFIG_SOURCE_DEVICE_NAME,
20 DISPLAYCONFIG_TARGET_DEVICE_NAME, DisplayConfigGetDeviceInfo, GUID_DEVINTERFACE_MONITOR,
21 GetDisplayConfigBufferSizes, QDC_ONLY_ACTIVE_PATHS, QueryDisplayConfig,
22};
23use windows::Win32::Foundation::{ERROR_INSUFFICIENT_BUFFER, ERROR_SUCCESS, RECT};
24use windows::Win32::Graphics::Gdi::{
25 CDS_FULLSCREEN, CDS_NORESET, CDS_TYPE, CDS_UPDATEREGISTRY, ChangeDisplaySettingsExW, DEVMODEW,
26 DISP_CHANGE_SUCCESSFUL, DM_BITSPERPEL, DM_DISPLAYFREQUENCY, DM_DISPLAYORIENTATION,
27 DM_PELSHEIGHT, DM_PELSWIDTH, DM_POSITION, DMDO_DEFAULT, ENUM_DISPLAY_SETTINGS_FLAGS,
28 ENUM_DISPLAY_SETTINGS_MODE, EnumDisplaySettingsExW,
29};
30use windows::Win32::Security::Cryptography::{
31 BCRYPT_ALG_HANDLE, BCRYPT_SHA1_ALGORITHM, BCryptCloseAlgorithmProvider, BCryptHash,
32 BCryptOpenAlgorithmProvider,
33};
34use windows::Win32::System::Registry::{
35 HKEY, KEY_READ, REG_BINARY, REG_VALUE_TYPE, RegCloseKey, RegQueryValueExW,
36};
37use windows::core::{PCWSTR, w};
38
39use crate::geometry::Rotation;
40use crate::session_gate::VirtualMode;
41
42#[derive(Clone, Debug)]
43pub struct Display {
44 pub id: String,
45 pub connector_index: u32,
46 pub sunshine_id: Option<String>,
47 pub name: String,
48 pub device_name: String,
49 pub rect: RECT,
50 pub native_width: u32,
51 pub native_height: u32,
52 pub physical_width_mm: Option<f64>,
53 pub physical_height_mm: Option<f64>,
54 pub rotation: Rotation,
55 pub refresh_numerator: u32,
56 pub refresh_denominator: u32,
57 pub primary: bool,
58 pub virtual_display: bool,
59}
60
61#[derive(Debug)]
62pub struct DisplayError(String);
63
64impl FmtDisplay for DisplayError {
65 fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
66 formatter.write_str(&self.0)
67 }
68}
69
70impl Error for DisplayError {}
71
72pub fn active_displays() -> Result<Vec<Display>, DisplayError> {
73 active_displays_with_identity(true)
74}
75
76pub fn unique_physical_display(
77 displays: &[Display],
78 display_id: &str,
79) -> Result<Display, DisplayError> {
80 let matches: Vec<_> = displays
81 .iter()
82 .filter(|display| display.id.eq_ignore_ascii_case(display_id))
83 .collect();
84 let display = match matches.as_slice() {
85 [display] => (*display).clone(),
86 [] => {
87 return Err(DisplayError(format!(
88 "active display id not found: {display_id}"
89 )));
90 }
91 _ => {
92 return Err(DisplayError(format!(
93 "display id is ambiguous: {display_id}"
94 )));
95 }
96 };
97 if display.virtual_display {
98 return Err(DisplayError(
99 "the physical output cannot be the SBMS virtual display".into(),
100 ));
101 }
102 if displays
103 .iter()
104 .filter(|candidate| {
105 candidate
106 .device_name
107 .eq_ignore_ascii_case(&display.device_name)
108 })
109 .count()
110 != 1
111 {
112 return Err(DisplayError(
113 "cloned outputs sharing one GDI display name are not supported".into(),
114 ));
115 }
116 Ok(display)
117}
118
119pub(crate) fn active_display_topology() -> Result<Vec<Display>, DisplayError> {
120 active_displays_with_identity(false)
121}
122
123fn active_displays_with_identity(include_identity: bool) -> Result<Vec<Display>, DisplayError> {
124 let mut last_error = None;
125 for _ in 0..5 {
126 match read_active_displays(include_identity) {
127 Ok(displays) => return Ok(displays),
128 Err(error) => last_error = Some(error),
129 }
130 thread::sleep(Duration::from_millis(20));
131 }
132 Err(last_error.expect("display read retry ran at least once"))
133}
134
135pub fn apply_display_mode(display: &Display, requested: VirtualMode) -> Result<(), DisplayError> {
136 let requested_hz = u64::from(requested.refresh_numerator)
137 .checked_add(u64::from(requested.refresh_denominator) / 2)
138 .and_then(|value| value.checked_div(u64::from(requested.refresh_denominator)))
139 .and_then(|value| u32::try_from(value).ok())
140 .ok_or_else(|| DisplayError("requested refresh rate is invalid".into()))?;
141 let device_name = wide(&display.device_name);
142 let mut mode = find_display_mode(
143 PCWSTR(device_name.as_ptr()),
144 requested.width,
145 requested.height,
146 requested_hz,
147 )
148 .ok_or_else(|| {
149 DisplayError(format!(
150 "{} does not expose requested mode {}x{}@{}",
151 display.device_name, requested.width, requested.height, requested_hz
152 ))
153 })?;
154 mode.dmPelsWidth = requested.width;
155 mode.dmPelsHeight = requested.height;
156 mode.Anonymous1.Anonymous2.dmDisplayOrientation = DMDO_DEFAULT;
157 mode.dmFields =
158 DM_BITSPERPEL | DM_PELSWIDTH | DM_PELSHEIGHT | DM_DISPLAYFREQUENCY | DM_DISPLAYORIENTATION;
159 let result = unsafe {
160 ChangeDisplaySettingsExW(
161 PCWSTR(device_name.as_ptr()),
162 Some(&raw const mode),
163 None,
164 CDS_FULLSCREEN,
165 None,
166 )
167 };
168 if result != DISP_CHANGE_SUCCESSFUL {
169 return Err(DisplayError(format!(
170 "ChangeDisplaySettingsExW({}) rejected native mode {}x{}@{} with status {}",
171 display.device_name, requested.width, requested.height, requested_hz, result.0
172 )));
173 }
174 Ok(())
175}
176
177pub fn restore_display_topology(expected: &[Display]) -> Result<(), DisplayError> {
178 let current = active_displays()?;
179 let mut pending = false;
180 for display in expected.iter().filter(|display| !display.virtual_display) {
181 let Some(actual) = current
182 .iter()
183 .find(|actual| actual.id.eq_ignore_ascii_case(&display.id))
184 else {
185 continue;
186 };
187 if actual.rect == display.rect
188 && actual.refresh_numerator == display.refresh_numerator
189 && actual.refresh_denominator == display.refresh_denominator
190 {
191 continue;
192 }
193
194 let width = u32::try_from(display.rect.right - display.rect.left)
195 .map_err(|_| DisplayError("saved display width is invalid".into()))?;
196 let height = u32::try_from(display.rect.bottom - display.rect.top)
197 .map_err(|_| DisplayError("saved display height is invalid".into()))?;
198 let refresh_hz = u64::from(display.refresh_numerator)
199 .checked_add(u64::from(display.refresh_denominator) / 2)
200 .and_then(|value| value.checked_div(u64::from(display.refresh_denominator)))
201 .and_then(|value| u32::try_from(value).ok())
202 .ok_or_else(|| DisplayError("saved display refresh rate is invalid".into()))?;
203 let device_name = wide(&actual.device_name);
204 let mut mode = find_display_mode(PCWSTR(device_name.as_ptr()), width, height, refresh_hz)
205 .ok_or_else(|| {
206 DisplayError(format!(
207 "{} no longer exposes saved mode {}x{}@{}",
208 actual.device_name, width, height, refresh_hz
209 ))
210 })?;
211 let position = unsafe { &mut mode.Anonymous1.Anonymous2.dmPosition };
212 position.x = display.rect.left;
213 position.y = display.rect.top;
214 mode.dmFields =
215 DM_POSITION | DM_BITSPERPEL | DM_PELSWIDTH | DM_PELSHEIGHT | DM_DISPLAYFREQUENCY;
216 let result = unsafe {
217 ChangeDisplaySettingsExW(
218 PCWSTR(device_name.as_ptr()),
219 Some(&raw const mode),
220 None,
221 CDS_UPDATEREGISTRY | CDS_NORESET,
222 None,
223 )
224 };
225 if result != DISP_CHANGE_SUCCESSFUL {
226 return Err(DisplayError(format!(
227 "could not stage saved topology for {}: status {}",
228 actual.device_name, result.0
229 )));
230 }
231 pending = true;
232 }
233 if pending {
234 let result = unsafe {
235 ChangeDisplaySettingsExW(PCWSTR::null(), None, None, CDS_TYPE::default(), None)
236 };
237 if result != DISP_CHANGE_SUCCESSFUL {
238 return Err(DisplayError(format!(
239 "could not apply saved physical topology: status {}",
240 result.0
241 )));
242 }
243 }
244 Ok(())
245}
246
247fn find_display_mode(
248 device_name: PCWSTR,
249 width: u32,
250 height: u32,
251 refresh_hz: u32,
252) -> Option<DEVMODEW> {
253 for index in 0..u32::MAX {
254 let mut mode = DEVMODEW {
255 dmSize: size_of::<DEVMODEW>() as u16,
256 ..Default::default()
257 };
258 if !unsafe {
259 EnumDisplaySettingsExW(
260 device_name,
261 ENUM_DISPLAY_SETTINGS_MODE(index),
262 &mut mode,
263 ENUM_DISPLAY_SETTINGS_FLAGS::default(),
264 )
265 }
266 .as_bool()
267 {
268 break;
269 }
270 if mode.dmPelsWidth == width
271 && mode.dmPelsHeight == height
272 && mode.dmDisplayFrequency == refresh_hz
273 {
274 return Some(mode);
275 }
276 }
277 None
278}
279
280fn wide(value: &str) -> Vec<u16> {
281 value.encode_utf16().chain(std::iter::once(0)).collect()
282}
283
284fn read_active_displays(include_identity: bool) -> Result<Vec<Display>, DisplayError> {
285 for _ in 0..3 {
286 let mut path_count = 0;
287 let mut mode_count = 0;
288 let size_result = unsafe {
289 GetDisplayConfigBufferSizes(QDC_ONLY_ACTIVE_PATHS, &mut path_count, &mut mode_count)
290 };
291 if size_result != ERROR_SUCCESS {
292 return Err(win32_error("GetDisplayConfigBufferSizes", size_result.0));
293 }
294
295 let mut paths = vec![DISPLAYCONFIG_PATH_INFO::default(); path_count as usize];
296 let mut modes = vec![DISPLAYCONFIG_MODE_INFO::default(); mode_count as usize];
297 let query_result = unsafe {
298 QueryDisplayConfig(
299 QDC_ONLY_ACTIVE_PATHS,
300 &mut path_count,
301 paths.as_mut_ptr(),
302 &mut mode_count,
303 modes.as_mut_ptr(),
304 None,
305 )
306 };
307 if query_result == ERROR_INSUFFICIENT_BUFFER {
308 continue;
309 }
310 if query_result != ERROR_SUCCESS {
311 return Err(win32_error("QueryDisplayConfig", query_result.0));
312 }
313 paths.truncate(path_count as usize);
314 modes.truncate(mode_count as usize);
315
316 let mut displays = Vec::with_capacity(paths.len());
317 for path in paths {
318 let source = device_name(
319 DISPLAYCONFIG_DEVICE_INFO_GET_SOURCE_NAME,
320 path.sourceInfo.adapterId,
321 path.sourceInfo.id,
322 )?;
323 let target = target_name(path.targetInfo.adapterId, path.targetInfo.id)?;
324 let adapter = adapter_name(path.sourceInfo.adapterId)?;
325 let mode_index = unsafe { path.sourceInfo.Anonymous.modeInfoIdx } as usize;
326 let Some(mode) = modes.get(mode_index) else {
327 continue;
328 };
329 if mode.infoType != DISPLAYCONFIG_MODE_INFO_TYPE_SOURCE {
330 continue;
331 }
332 let mode = unsafe { mode.Anonymous.sourceMode };
333 let left = mode.position.x;
334 let top = mode.position.y;
335 let rotation = rotation(path.targetInfo.rotation);
336 let (native_width, native_height) =
337 target_active_size(&path, &modes).unwrap_or(match rotation {
338 Rotation::Deg90 | Rotation::Deg270 => (mode.height, mode.width),
339 Rotation::Deg0 | Rotation::Deg180 => (mode.width, mode.height),
340 });
341 let name = wide_string(&target.monitorFriendlyDeviceName);
342 let id = wide_string(&target.monitorDevicePath);
343 let (physical_size, sunshine_id) = if include_identity {
344 let monitor_identity = monitor_identity(&id);
345 (
346 monitor_identity
347 .as_ref()
348 .and_then(|identity| edid_dimensions_mm(&identity.edid)),
349 sunshine_display_id(&sunshine_device_id_payload(
350 &id,
351 monitor_identity.as_ref(),
352 )),
353 )
354 } else {
355 (None, None)
356 };
357 displays.push(Display {
358 id,
359 connector_index: target.connectorInstance,
360 sunshine_id,
361 name: name.clone(),
362 device_name: source,
363 rect: RECT {
364 left,
365 top,
366 right: left + mode.width as i32,
367 bottom: top + mode.height as i32,
368 },
369 native_width,
370 native_height,
371 physical_width_mm: physical_size.map(|size| size.0),
372 physical_height_mm: physical_size.map(|size| size.1),
373 rotation,
374 refresh_numerator: path.targetInfo.refreshRate.Numerator,
375 refresh_denominator: path.targetInfo.refreshRate.Denominator,
376 primary: left == 0 && top == 0,
377 virtual_display: is_sbms_adapter(&adapter),
378 });
379 }
380 return Ok(displays);
381 }
382
383 Err(DisplayError(
384 "display topology kept changing while it was read".into(),
385 ))
386}
387
388fn target_active_size(
389 path: &DISPLAYCONFIG_PATH_INFO,
390 modes: &[DISPLAYCONFIG_MODE_INFO],
391) -> Option<(u32, u32)> {
392 let index = unsafe { path.targetInfo.Anonymous.modeInfoIdx } as usize;
393 let mode = modes.get(index)?;
394 if mode.infoType != DISPLAYCONFIG_MODE_INFO_TYPE_TARGET {
395 return None;
396 }
397 let target = unsafe { mode.Anonymous.targetMode };
398 let active = target.targetVideoSignalInfo.activeSize;
399 (active.cx > 0 && active.cy > 0).then_some((active.cx, active.cy))
400}
401
402fn rotation(value: windows::Win32::Devices::Display::DISPLAYCONFIG_ROTATION) -> Rotation {
403 match value {
404 DISPLAYCONFIG_ROTATION_ROTATE90 => Rotation::Deg90,
405 DISPLAYCONFIG_ROTATION_ROTATE180 => Rotation::Deg180,
406 DISPLAYCONFIG_ROTATION_ROTATE270 => Rotation::Deg270,
407 DISPLAYCONFIG_ROTATION_IDENTITY => Rotation::Deg0,
408 _ => Rotation::Deg0,
409 }
410}
411
412struct DeviceInfoSet(HDEVINFO);
413
414impl Drop for DeviceInfoSet {
415 fn drop(&mut self) {
416 let _ = unsafe { SetupDiDestroyDeviceInfoList(self.0) };
417 }
418}
419
420struct RegistryKey(HKEY);
421
422impl Drop for RegistryKey {
423 fn drop(&mut self) {
424 let _ = unsafe { RegCloseKey(self.0) };
425 }
426}
427
428#[derive(Debug)]
429struct MonitorIdentity {
430 instance_id: String,
431 edid: Vec<u8>,
432}
433
434fn monitor_identity(monitor_device_path: &str) -> Option<MonitorIdentity> {
435 let device_path: Vec<u16> = monitor_device_path
436 .encode_utf16()
437 .chain(std::iter::once(0))
438 .collect();
439 let device_info_set = DeviceInfoSet(
440 unsafe {
441 SetupDiGetClassDevsW(
442 Some(&GUID_DEVINTERFACE_MONITOR),
443 PCWSTR::null(),
444 None,
445 DIGCF_PRESENT | DIGCF_DEVICEINTERFACE,
446 )
447 }
448 .ok()?,
449 );
450
451 let mut interface = SP_DEVICE_INTERFACE_DATA {
452 cbSize: size_of::<SP_DEVICE_INTERFACE_DATA>() as u32,
453 ..Default::default()
454 };
455 unsafe {
456 SetupDiOpenDeviceInterfaceW(
457 device_info_set.0,
458 PCWSTR(device_path.as_ptr()),
459 0,
460 Some(&mut interface),
461 )
462 }
463 .ok()?;
464
465 let mut required_size = 0;
466 let _ = unsafe {
467 SetupDiGetDeviceInterfaceDetailW(
468 device_info_set.0,
469 &interface,
470 None,
471 0,
472 Some(&mut required_size),
473 None,
474 )
475 };
476 if required_size < size_of::<SP_DEVICE_INTERFACE_DETAIL_DATA_W>() as u32 {
477 return None;
478 }
479
480 let word_count = (required_size as usize).div_ceil(size_of::<usize>());
481 let mut detail_buffer = vec![0usize; word_count];
482 let detail = detail_buffer
483 .as_mut_ptr()
484 .cast::<SP_DEVICE_INTERFACE_DETAIL_DATA_W>();
485 unsafe {
486 (*detail).cbSize = size_of::<SP_DEVICE_INTERFACE_DETAIL_DATA_W>() as u32;
487 }
488 let mut device_info = SP_DEVINFO_DATA {
489 cbSize: size_of::<SP_DEVINFO_DATA>() as u32,
490 ..Default::default()
491 };
492 unsafe {
493 SetupDiGetDeviceInterfaceDetailW(
494 device_info_set.0,
495 &interface,
496 Some(detail),
497 required_size,
498 None,
499 Some(&mut device_info),
500 )
501 }
502 .ok()?;
503
504 let instance_id = device_instance_id(device_info_set.0, &device_info).unwrap_or_default();
505 let edid = monitor_edid(device_info_set.0, &device_info).unwrap_or_default();
506 Some(MonitorIdentity { instance_id, edid })
507}
508
509fn device_instance_id(device_info_set: HDEVINFO, device_info: &SP_DEVINFO_DATA) -> Option<String> {
510 let mut required_size = 0;
511 let _ = unsafe {
512 SetupDiGetDeviceInstanceIdW(device_info_set, device_info, None, Some(&mut required_size))
513 };
514 if required_size == 0 {
515 return None;
516 }
517
518 let mut value = vec![0u16; required_size as usize];
519 unsafe {
520 SetupDiGetDeviceInstanceIdW(
521 device_info_set,
522 device_info,
523 Some(&mut value),
524 Some(&mut required_size),
525 )
526 }
527 .ok()?;
528 Some(wide_string(&value[..required_size as usize]))
529}
530
531fn monitor_edid(device_info_set: HDEVINFO, device_info: &SP_DEVINFO_DATA) -> Option<Vec<u8>> {
532 let registry_key = RegistryKey(
533 unsafe {
534 SetupDiOpenDevRegKey(
535 device_info_set,
536 device_info,
537 DICS_FLAG_GLOBAL.0,
538 0,
539 DIREG_DEV,
540 KEY_READ.0,
541 )
542 }
543 .ok()?,
544 );
545 let mut value_type = REG_VALUE_TYPE::default();
546 let mut byte_count = 0;
547 if unsafe {
548 RegQueryValueExW(
549 registry_key.0,
550 w!("EDID"),
551 None,
552 Some(&mut value_type),
553 None,
554 Some(&mut byte_count),
555 )
556 } != ERROR_SUCCESS
557 || value_type != REG_BINARY
558 || byte_count == 0
559 {
560 return None;
561 }
562
563 let mut edid = vec![0u8; byte_count as usize];
564 if unsafe {
565 RegQueryValueExW(
566 registry_key.0,
567 w!("EDID"),
568 None,
569 Some(&mut value_type),
570 Some(edid.as_mut_ptr()),
571 Some(&mut byte_count),
572 )
573 } != ERROR_SUCCESS
574 {
575 return None;
576 }
577 edid.truncate(byte_count as usize);
578 Some(edid)
579}
580
581fn sunshine_device_id_payload(
582 monitor_device_path: &str,
583 identity: Option<&MonitorIdentity>,
584) -> Vec<u8> {
585 if let Some(identity) = identity
586 && !identity.instance_id.is_empty()
587 && !identity.edid.is_empty()
588 && let Some((stable_prefix, stable_suffix)) =
589 stable_instance_id_parts(&identity.instance_id)
590 {
591 let mut payload = identity.edid.clone();
592 append_utf16le(&mut payload, stable_prefix);
593 append_utf16le(&mut payload, stable_suffix);
594 return payload;
595 }
596
597 utf16le_bytes(monitor_device_path)
598}
599
600fn stable_instance_id_parts(instance_id: &str) -> Option<(&str, &str)> {
601 let mut separators = instance_id.match_indices('&').map(|(index, _)| index);
602 separators.next()?;
603 let unstable_part = separators.next()?;
604 let semi_stable_part = separators.next()?;
605 Some((
606 &instance_id[..unstable_part],
607 &instance_id[semi_stable_part..],
608 ))
609}
610
611fn utf16le_bytes(value: &str) -> Vec<u8> {
612 let mut bytes = Vec::with_capacity(value.encode_utf16().count() * size_of::<u16>());
613 append_utf16le(&mut bytes, value);
614 bytes
615}
616
617fn append_utf16le(target: &mut Vec<u8>, value: &str) {
618 for word in value.encode_utf16() {
619 target.extend_from_slice(&word.to_le_bytes());
620 }
621}
622
623struct AlgorithmProvider(BCRYPT_ALG_HANDLE);
624
625impl Drop for AlgorithmProvider {
626 fn drop(&mut self) {
627 let _ = unsafe { BCryptCloseAlgorithmProvider(self.0, 0) };
628 }
629}
630
631fn sunshine_display_id(payload: &[u8]) -> Option<String> {
632 if payload.is_empty() {
633 return None;
634 }
635
636 let mut namespaced_payload = Vec::with_capacity(16 + payload.len());
637 namespaced_payload.resize(16, 0);
638 namespaced_payload.extend_from_slice(payload);
639
640 let mut handle = BCRYPT_ALG_HANDLE(std::ptr::null_mut());
641 if unsafe {
642 BCryptOpenAlgorithmProvider(
643 &mut handle,
644 BCRYPT_SHA1_ALGORITHM,
645 PCWSTR::null(),
646 Default::default(),
647 )
648 }
649 .is_err()
650 {
651 return None;
652 }
653 let provider = AlgorithmProvider(handle);
654 let mut digest = [0u8; 20];
655 if unsafe { BCryptHash(provider.0, None, &namespaced_payload, &mut digest) }.is_err() {
656 return None;
657 }
658
659 let mut uuid = [0u8; 16];
660 uuid.copy_from_slice(&digest[..16]);
661 uuid[6] = (uuid[6] & 0x0f) | 0x50;
662 uuid[8] = (uuid[8] & 0x3f) | 0x80;
663 Some(format!(
664 "{{{:02x}{:02x}{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}}}",
665 uuid[0],
666 uuid[1],
667 uuid[2],
668 uuid[3],
669 uuid[4],
670 uuid[5],
671 uuid[6],
672 uuid[7],
673 uuid[8],
674 uuid[9],
675 uuid[10],
676 uuid[11],
677 uuid[12],
678 uuid[13],
679 uuid[14],
680 uuid[15],
681 ))
682}
683
684fn edid_dimensions_mm(edid: &[u8]) -> Option<(f64, f64)> {
685 const HEADER: [u8; 8] = [0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00];
686 if edid.len() < 128 || edid[..8] != HEADER {
687 return None;
688 }
689
690 for offset in (54..=108).step_by(18) {
691 let descriptor = &edid[offset..offset + 18];
692 if descriptor[0] == 0 && descriptor[1] == 0 {
693 continue;
694 }
695 let width = u16::from(descriptor[12]) | (u16::from(descriptor[14] & 0xf0) << 4);
696 let height = u16::from(descriptor[13]) | (u16::from(descriptor[14] & 0x0f) << 8);
697 if width > 0 && height > 0 {
698 return Some((f64::from(width), f64::from(height)));
699 }
700 }
701
702 let width_cm = edid[21];
703 let height_cm = edid[22];
704 (width_cm > 0 && height_cm > 0)
705 .then_some((f64::from(width_cm) * 10.0, f64::from(height_cm) * 10.0))
706}
707
708fn adapter_name(adapter_id: windows::Win32::Foundation::LUID) -> Result<String, DisplayError> {
709 let mut packet = DISPLAYCONFIG_ADAPTER_NAME {
710 header: DISPLAYCONFIG_DEVICE_INFO_HEADER {
711 r#type: DISPLAYCONFIG_DEVICE_INFO_GET_ADAPTER_NAME,
712 size: size_of::<DISPLAYCONFIG_ADAPTER_NAME>() as u32,
713 adapterId: adapter_id,
714 id: 0,
715 },
716 ..Default::default()
717 };
718 let result = unsafe { DisplayConfigGetDeviceInfo(&mut packet.header) };
719 if result != 0 {
720 return Err(win32_error(
721 "DisplayConfigGetDeviceInfo(adapter)",
722 result as u32,
723 ));
724 }
725 Ok(wide_string(&packet.adapterDevicePath))
726}
727
728fn is_sbms_adapter(adapter: &str) -> bool {
729 let adapter = adapter.to_ascii_lowercase();
730 adapter.contains(r"swd#sbms#virtualdisplay-01")
731 || adapter.contains(r"swd\sbms\virtualdisplay-01")
732}
733
734fn device_name(
735 info_type: windows::Win32::Devices::Display::DISPLAYCONFIG_DEVICE_INFO_TYPE,
736 adapter_id: windows::Win32::Foundation::LUID,
737 id: u32,
738) -> Result<String, DisplayError> {
739 let mut packet = DISPLAYCONFIG_SOURCE_DEVICE_NAME {
740 header: DISPLAYCONFIG_DEVICE_INFO_HEADER {
741 r#type: info_type,
742 size: size_of::<DISPLAYCONFIG_SOURCE_DEVICE_NAME>() as u32,
743 adapterId: adapter_id,
744 id,
745 },
746 ..Default::default()
747 };
748 let result = unsafe { DisplayConfigGetDeviceInfo(&mut packet.header) };
749 if result != 0 {
750 return Err(win32_error(
751 "DisplayConfigGetDeviceInfo(source)",
752 result as u32,
753 ));
754 }
755 Ok(wide_string(&packet.viewGdiDeviceName))
756}
757
758fn target_name(
759 adapter_id: windows::Win32::Foundation::LUID,
760 id: u32,
761) -> Result<DISPLAYCONFIG_TARGET_DEVICE_NAME, DisplayError> {
762 let mut packet = DISPLAYCONFIG_TARGET_DEVICE_NAME {
763 header: DISPLAYCONFIG_DEVICE_INFO_HEADER {
764 r#type: DISPLAYCONFIG_DEVICE_INFO_GET_TARGET_NAME,
765 size: size_of::<DISPLAYCONFIG_TARGET_DEVICE_NAME>() as u32,
766 adapterId: adapter_id,
767 id,
768 },
769 ..Default::default()
770 };
771 let result = unsafe { DisplayConfigGetDeviceInfo(&mut packet.header) };
772 if result != 0 {
773 return Err(win32_error(
774 "DisplayConfigGetDeviceInfo(target)",
775 result as u32,
776 ));
777 }
778 Ok(packet)
779}
780
781fn wide_string(value: &[u16]) -> String {
782 let end = value
783 .iter()
784 .position(|character| *character == 0)
785 .unwrap_or(value.len());
786 String::from_utf16_lossy(&value[..end])
787}
788
789fn win32_error(operation: &str, code: u32) -> DisplayError {
790 DisplayError(format!("{operation} failed with Win32 error {code}"))
791}
792
793#[cfg(test)]
794mod tests {
795 use super::{
796 Display, MonitorIdentity, edid_dimensions_mm, stable_instance_id_parts,
797 sunshine_device_id_payload, sunshine_display_id, unique_physical_display, utf16le_bytes,
798 };
799 use crate::geometry::Rotation;
800 use windows::Win32::Foundation::RECT;
801
802 const HEADER: [u8; 8] = [0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00];
803
804 fn edid() -> Vec<u8> {
805 let mut value = vec![0u8; 128];
806 value[..8].copy_from_slice(&HEADER);
807 value
808 }
809
810 fn display(id: &str, device_name: &str, virtual_display: bool) -> Display {
811 Display {
812 id: id.into(),
813 connector_index: 0,
814 sunshine_id: None,
815 name: id.into(),
816 device_name: device_name.into(),
817 rect: RECT::default(),
818 native_width: 1920,
819 native_height: 1080,
820 physical_width_mm: None,
821 physical_height_mm: None,
822 rotation: Rotation::Deg0,
823 refresh_numerator: 60,
824 refresh_denominator: 1,
825 primary: false,
826 virtual_display,
827 }
828 }
829
830 #[test]
831 fn unique_physical_display_enforces_identity_and_physical_output() {
832 let physical = display("physical", r"\\.\DISPLAY1", false);
833 assert_eq!(
834 unique_physical_display(std::slice::from_ref(&physical), "PHYSICAL")
835 .unwrap()
836 .id,
837 "physical"
838 );
839 assert!(unique_physical_display(&[], "missing").is_err());
840 assert!(
841 unique_physical_display(
842 &[
843 physical.clone(),
844 display("duplicate", r"\\.\DISPLAY1", false),
845 ],
846 "physical",
847 )
848 .is_err()
849 );
850 assert!(
851 unique_physical_display(
852 &[
853 display("same", r"\\.\DISPLAY1", false),
854 display("SAME", r"\\.\DISPLAY2", false),
855 ],
856 "same",
857 )
858 .is_err()
859 );
860 assert!(
861 unique_physical_display(&[display("virtual", r"\\.\DISPLAY2", true)], "virtual",)
862 .is_err()
863 );
864 }
865
866 #[test]
867 fn edid_prefers_detailed_timing_dimensions() {
868 let mut value = edid();
869 value[21] = 52;
870 value[22] = 29;
871 let descriptor = &mut value[54..72];
872 descriptor[0] = 1;
873 descriptor[12] = (600u16 & 0xff) as u8;
874 descriptor[13] = (340u16 & 0xff) as u8;
875 descriptor[14] = ((600u16 >> 8) as u8) << 4 | (340u16 >> 8) as u8;
876
877 assert_eq!(edid_dimensions_mm(&value), Some((600.0, 340.0)));
878 }
879
880 #[test]
881 fn edid_falls_back_to_basic_centimeter_dimensions() {
882 let mut value = edid();
883 value[21] = 60;
884 value[22] = 34;
885
886 assert_eq!(edid_dimensions_mm(&value), Some((600.0, 340.0)));
887 }
888
889 #[test]
890 fn edid_rejects_missing_or_unidentified_dimensions() {
891 assert_eq!(edid_dimensions_mm(&[0; 128]), None);
892 assert_eq!(edid_dimensions_mm(&edid()), None);
893 }
894
895 #[test]
896 fn sunshine_payload_keeps_only_stable_instance_id_parts() {
897 let identity = MonitorIdentity {
898 instance_id: r"DISPLAY\ACR1234\5&10a&0&UID4352".into(),
899 edid: vec![0xaa, 0xbb],
900 };
901 let payload = sunshine_device_id_payload(r"\\?\fallback", Some(&identity));
902
903 let mut expected = vec![0xaa, 0xbb];
904 expected.extend_from_slice(&utf16le_bytes(r"DISPLAY\ACR1234\5&10a"));
905 expected.extend_from_slice(&utf16le_bytes("&UID4352"));
906 assert_eq!(payload, expected);
907 assert_eq!(
908 stable_instance_id_parts(&identity.instance_id),
909 Some((r"DISPLAY\ACR1234\5&10a", "&UID4352"))
910 );
911 }
912
913 #[test]
914 fn sunshine_payload_falls_back_to_monitor_path_without_full_identity() {
915 let identity = MonitorIdentity {
916 instance_id: r"DISPLAY\ACR1234\5&10a".into(),
917 edid: vec![0xaa, 0xbb],
918 };
919
920 assert_eq!(
921 sunshine_device_id_payload(r"\\?\DISPLAY#SBMS", Some(&identity)),
922 utf16le_bytes(r"\\?\DISPLAY#SBMS")
923 );
924 }
925
926 #[test]
927 fn sunshine_uuid_matches_libdisplaydevice_known_vector() {
928 let payload = utf16le_bytes(r"MONITOR\ABC");
929
930 assert_eq!(
931 sunshine_display_id(&payload).as_deref(),
932 Some("{b1f86899-5f83-5fa5-97e1-b11b5f9da8ed}")
933 );
934 }
935}
936