use core::fmt;
use std::cell::{Cell, RefCell};
use thiserror::Error;
use windows::Win32::Devices::Display::{
DISPLAYCONFIG_DEVICE_INFO_GET_SOURCE_NAME, DISPLAYCONFIG_DEVICE_INFO_GET_TARGET_NAME,
DISPLAYCONFIG_DEVICE_INFO_HEADER, DISPLAYCONFIG_MODE_INFO, DISPLAYCONFIG_PATH_INFO,
DISPLAYCONFIG_SOURCE_DEVICE_NAME, DISPLAYCONFIG_TARGET_DEVICE_NAME, DisplayConfigGetDeviceInfo,
};
use windows::Win32::Foundation::POINTL;
use windows::Win32::Graphics::Gdi::{
DEVMODEW, DISPLAY_DEVICEW, ENUM_CURRENT_SETTINGS, EnumDisplaySettingsW,
};
use windows::core::PCWSTR;
use crate::types::{
BitDepth, ConnectorType, Frequency, Orientation, Position, Resolution, Scaling,
ScanlineOrdering,
};
#[derive(Error, Debug)]
pub enum DisplayPropertiesError {
#[error("Display {0} has no settings")]
NoSettings(String),
#[error("Error when calling the Windows API: {0}")]
WinAPI(String),
}
type Result<T = ()> = std::result::Result<T, DisplayPropertiesError>;
#[derive(Clone, Debug)]
pub struct DisplayProperties {
pub name: String,
pub string: String,
pub key: String,
pub active: bool,
pub primary: Cell<bool>,
pub settings: Option<RefCell<DisplaySettings>>,
pub connector_type: Option<ConnectorType>,
pub target_available: bool,
}
impl fmt::Display for DisplayProperties {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"Display {{ key: {}, name: {}, string: {}, active: {}, primary: {} }}",
self.key,
self.name,
self.string,
self.active,
self.primary.get()
)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct DisplaySettings {
pub position: Position,
pub resolution: Resolution,
pub frequency: Frequency,
pub orientation: Orientation,
pub scaling: Scaling,
pub bit_depth: BitDepth,
pub scanline_ordering: ScanlineOrdering,
}
impl DisplayProperties {
pub fn from_windows(device: &DISPLAY_DEVICEW) -> Result<DisplayProperties> {
use windows::Win32::Graphics::Gdi::DISPLAY_DEVICE_STATE_FLAGS;
let active = (device.StateFlags & DISPLAY_DEVICE_STATE_FLAGS(0x00000001))
!= DISPLAY_DEVICE_STATE_FLAGS(0);
let name = {
let len = device
.DeviceName
.iter()
.position(|&c| c == 0)
.unwrap_or(device.DeviceName.len());
String::from_utf16_lossy(&device.DeviceName[..len])
};
let settings = if active {
Some(RefCell::new(Self::fetch_settings(&name)?))
} else {
None
};
let string = {
let len = device
.DeviceString
.iter()
.position(|&c| c == 0)
.unwrap_or(device.DeviceString.len());
String::from_utf16_lossy(&device.DeviceString[..len])
};
let key = {
let len = device
.DeviceKey
.iter()
.position(|&c| c == 0)
.unwrap_or(device.DeviceKey.len());
String::from_utf16_lossy(&device.DeviceKey[..len])
};
Ok(DisplayProperties {
name,
string,
key,
active,
primary: Cell::new(
(device.StateFlags & DISPLAY_DEVICE_STATE_FLAGS(0x00000004))
!= DISPLAY_DEVICE_STATE_FLAGS(0),
), settings,
connector_type: None, target_available: active, })
}
fn fetch_settings(name: &str) -> Result<DisplaySettings> {
let mut devmode: DEVMODEW = unsafe { std::mem::zeroed() };
devmode.dmSize = std::mem::size_of::<DEVMODEW>() as u16;
let wide_name: Vec<u16> = name.encode_utf16().chain(std::iter::once(0)).collect();
let result = unsafe {
EnumDisplaySettingsW(
PCWSTR(wide_name.as_ptr()),
ENUM_CURRENT_SETTINGS,
&mut devmode,
)
};
if !result.as_bool() {
return Err(DisplayPropertiesError::WinAPI(format!(
"EnumDisplaySettingsW failed for display {}",
name
)));
}
Ok(DisplaySettings {
position: Position(POINTL {
x: unsafe { devmode.Anonymous1.Anonymous2.dmPosition.x },
y: unsafe { devmode.Anonymous1.Anonymous2.dmPosition.y },
}),
resolution: Resolution::new(devmode.dmPelsWidth, devmode.dmPelsHeight),
frequency: Frequency(devmode.dmDisplayFrequency),
orientation: Orientation::from_rotation(unsafe {
devmode.Anonymous1.Anonymous2.dmDisplayOrientation.0
}),
scaling: Scaling::default(), bit_depth: BitDepth::Bpp32, scanline_ordering: ScanlineOrdering::Progressive, })
}
pub fn from_display_config(
path: &DISPLAYCONFIG_PATH_INFO,
modes: &[DISPLAYCONFIG_MODE_INFO],
) -> Result<DisplayProperties> {
let active = (path.flags & 0x00000001) != 0;
let source_mode_idx = unsafe { path.sourceInfo.Anonymous.modeInfoIdx as usize };
let target_mode_idx = unsafe { path.targetInfo.Anonymous.modeInfoIdx as usize };
if source_mode_idx >= modes.len() || target_mode_idx >= modes.len() {
return Err(DisplayPropertiesError::WinAPI(
"Invalid mode index in display path".to_string(),
));
}
let source_mode = &modes[source_mode_idx];
let target_mode = &modes[target_mode_idx];
let position = unsafe { source_mode.Anonymous.sourceMode.position };
let is_primary = position.x == 0 && position.y == 0;
let name = Self::get_source_device_name(path)?;
let (string, key) = Self::get_target_device_info(path)?;
let settings = if active {
Some(RefCell::new(Self::fetch_settings_from_mode(
path,
source_mode,
target_mode,
)?))
} else {
None
};
let connector_type = Some(ConnectorType::from_value(
path.targetInfo.outputTechnology.0,
));
let target_available = path.targetInfo.targetAvailable.as_bool();
Ok(DisplayProperties {
name,
string,
key,
active,
primary: Cell::new(is_primary),
settings,
connector_type,
target_available,
})
}
pub fn get_source_device_name(path: &DISPLAYCONFIG_PATH_INFO) -> Result<String> {
let mut source_name: DISPLAYCONFIG_SOURCE_DEVICE_NAME = unsafe { std::mem::zeroed() };
source_name.header.r#type = DISPLAYCONFIG_DEVICE_INFO_GET_SOURCE_NAME;
source_name.header.size = std::mem::size_of::<DISPLAYCONFIG_SOURCE_DEVICE_NAME>() as u32;
source_name.header.adapterId = path.sourceInfo.adapterId;
source_name.header.id = path.sourceInfo.id;
let result = unsafe {
DisplayConfigGetDeviceInfo(
&mut source_name.header as *mut DISPLAYCONFIG_DEVICE_INFO_HEADER,
)
};
if result != 0 {
return Err(DisplayPropertiesError::WinAPI(format!(
"DisplayConfigGetDeviceInfo (source) failed: {}",
result
)));
}
let len = source_name
.viewGdiDeviceName
.iter()
.position(|&c| c == 0)
.unwrap_or(source_name.viewGdiDeviceName.len());
Ok(String::from_utf16_lossy(
&source_name.viewGdiDeviceName[..len],
))
}
fn get_target_device_info(path: &DISPLAYCONFIG_PATH_INFO) -> Result<(String, String)> {
let mut target_name: DISPLAYCONFIG_TARGET_DEVICE_NAME = unsafe { std::mem::zeroed() };
target_name.header.r#type = DISPLAYCONFIG_DEVICE_INFO_GET_TARGET_NAME;
target_name.header.size = std::mem::size_of::<DISPLAYCONFIG_TARGET_DEVICE_NAME>() as u32;
target_name.header.adapterId = path.targetInfo.adapterId;
target_name.header.id = path.targetInfo.id;
let result = unsafe {
DisplayConfigGetDeviceInfo(
&mut target_name.header as *mut DISPLAYCONFIG_DEVICE_INFO_HEADER,
)
};
if result != 0 {
return Err(DisplayPropertiesError::WinAPI(format!(
"DisplayConfigGetDeviceInfo (target) failed: {}",
result
)));
}
let friendly_len = target_name
.monitorFriendlyDeviceName
.iter()
.position(|&c| c == 0)
.unwrap_or(target_name.monitorFriendlyDeviceName.len());
let friendly_name =
String::from_utf16_lossy(&target_name.monitorFriendlyDeviceName[..friendly_len]);
let path_len = target_name
.monitorDevicePath
.iter()
.position(|&c| c == 0)
.unwrap_or(target_name.monitorDevicePath.len());
let device_path = String::from_utf16_lossy(&target_name.monitorDevicePath[..path_len]);
Ok((friendly_name, device_path))
}
fn fetch_settings_from_mode(
path: &DISPLAYCONFIG_PATH_INFO,
source_mode: &DISPLAYCONFIG_MODE_INFO,
target_mode: &DISPLAYCONFIG_MODE_INFO,
) -> Result<DisplaySettings> {
let source = unsafe { &source_mode.Anonymous.sourceMode };
let target = unsafe { &target_mode.Anonymous.targetMode };
let rotation = path.targetInfo.rotation;
let orientation = Orientation::from_rotation(rotation.0 as u32);
let scaling = Scaling::from_value(path.targetInfo.scaling.0);
let bit_depth = BitDepth::from_pixel_format(source.pixelFormat.0);
let scanline_ordering =
ScanlineOrdering::from_value(target.targetVideoSignalInfo.scanLineOrdering.0);
Ok(DisplaySettings {
position: Position(source.position),
resolution: Resolution::new(source.width, source.height),
frequency: Frequency::new(Self::calculate_frequency(&target.targetVideoSignalInfo)),
orientation,
scaling,
bit_depth,
scanline_ordering,
})
}
fn calculate_frequency(
signal_info: &windows::Win32::Devices::Display::DISPLAYCONFIG_VIDEO_SIGNAL_INFO,
) -> u32 {
if signal_info.vSyncFreq.Denominator != 0 {
signal_info.vSyncFreq.Numerator / signal_info.vSyncFreq.Denominator
} else {
60 }
}
}