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.
文件
../ bininputuiconfig.rs81.2 KBcontrol.rs4.1 KBcontroller.rs43.6 KBdiagnostics.rs24.2 KBdisplay.rs29.8 KBgeometry.rs21.4 KBgpu_renderer.rs37.4 KBinput.rs79.7 KBlaunch_broker.rs10.3 KBlib.rs350 Blimits.rs714 Bmain.rs24.4 KBmapping.rs46.5 KBnetwork.rs9.2 KBrenderer.rs20.6 KBsession_gate.rs14.9 KBsunshine.rs12.2 KBui.rs68.5 KBvirtual_display.rs7.2 KBwin32_flyout.rs3.4 KBwin32_tray.rs22.3 KBwindow_migration.rs43.8 KBsrc/display.rs29.8 KB
1
use std::error::Error;2
use std::fmt::{Display as FmtDisplay, Formatter};3
use std::mem::size_of;4
use std::thread;5
use std::time::Duration;6
7
use 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
};13
use 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
};23
use windows::Win32::Foundation::{ERROR_INSUFFICIENT_BUFFER, ERROR_SUCCESS, RECT};24
use 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
};30
use windows::Win32::Security::Cryptography::{31
BCRYPT_ALG_HANDLE, BCRYPT_SHA1_ALGORITHM, BCryptCloseAlgorithmProvider, BCryptHash,32
BCryptOpenAlgorithmProvider,33
};34
use windows::Win32::System::Registry::{35
HKEY, KEY_READ, REG_BINARY, REG_VALUE_TYPE, RegCloseKey, RegQueryValueExW,36
};37
use windows::core::{PCWSTR, w};38
39
use crate::geometry::Rotation;40
use crate::session_gate::VirtualMode;41
42
#[derive(Clone, Debug)]43
pub 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)]62
pub struct DisplayError(String);63
64
impl FmtDisplay for DisplayError {65
fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {66
formatter.write_str(&self.0)67
}68
}69
70
impl Error for DisplayError {}71
72
pub fn active_displays() -> Result<Vec<Display>, DisplayError> {73
active_displays_with_identity(true)74
}75
76
pub fn unique_physical_display(77
displays: &[Display],78
display_id: &str,79
) -> Result<Display, DisplayError> {80
let matches: Vec<_> = displays81
.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 displays103
.iter()104
.filter(|candidate| {105
candidate106
.device_name107
.eq_ignore_ascii_case(&display.device_name)108
})109
.count()110
!= 1111
{112
return Err(DisplayError(113
"cloned outputs sharing one GDI display name are not supported".into(),114
));115
}116
Ok(display)117
}118
119
pub(crate) fn active_display_topology() -> Result<Vec<Display>, DisplayError> {120
active_displays_with_identity(false)121
}122
123
fn 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
135
pub 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_hz152
))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.0172
)));173
}174
Ok(())175
}176
177
pub 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) = current182
.iter()183
.find(|actual| actual.id.eq_ignore_ascii_case(&display.id))184
else {185
continue;186
};187
if actual.rect == display.rect188
&& actual.refresh_numerator == display.refresh_numerator189
&& actual.refresh_denominator == display.refresh_denominator190
{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_hz209
))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.0229
)));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.0241
)));242
}243
}244
Ok(())245
}246
247
fn 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 == width271
&& mode.dmPelsHeight == height272
&& mode.dmDisplayFrequency == refresh_hz273
{274
return Some(mode);275
}276
}277
None278
}279
280
fn wide(value: &str) -> Vec<u16> {281
value.encode_utf16().chain(std::iter::once(0)).collect()282
}283
284
fn 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_identity347
.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
388
fn 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
402
fn 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
412
struct DeviceInfoSet(HDEVINFO);413
414
impl Drop for DeviceInfoSet {415
fn drop(&mut self) {416
let _ = unsafe { SetupDiDestroyDeviceInfoList(self.0) };417
}418
}419
420
struct RegistryKey(HKEY);421
422
impl Drop for RegistryKey {423
fn drop(&mut self) {424
let _ = unsafe { RegCloseKey(self.0) };425
}426
}427
428
#[derive(Debug)]429
struct MonitorIdentity {430
instance_id: String,431
edid: Vec<u8>,432
}433
434
fn monitor_identity(monitor_device_path: &str) -> Option<MonitorIdentity> {435
let device_path: Vec<u16> = monitor_device_path436
.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_buffer483
.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
509
fn 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
531
fn 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_SUCCESS557
|| value_type != REG_BINARY558
|| byte_count == 0559
{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_SUCCESS574
{575
return None;576
}577
edid.truncate(byte_count as usize);578
Some(edid)579
}580
581
fn sunshine_device_id_payload(582
monitor_device_path: &str,583
identity: Option<&MonitorIdentity>,584
) -> Vec<u8> {585
if let Some(identity) = identity586
&& !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
600
fn 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
611
fn 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
bytes615
}616
617
fn 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
623
struct AlgorithmProvider(BCRYPT_ALG_HANDLE);624
625
impl Drop for AlgorithmProvider {626
fn drop(&mut self) {627
let _ = unsafe { BCryptCloseAlgorithmProvider(self.0, 0) };628
}629
}630
631
fn 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
684
fn 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
708
fn 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
728
fn 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
734
fn 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
758
fn 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
781
fn wide_string(value: &[u16]) -> String {782
let end = value783
.iter()784
.position(|character| *character == 0)785
.unwrap_or(value.len());786
String::from_utf16_lossy(&value[..end])787
}788
789
fn win32_error(operation: &str, code: u32) -> DisplayError {790
DisplayError(format!("{operation} failed with Win32 error {code}"))791
}792
793
#[cfg(test)]794
mod 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
value808
}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