use windows::Win32::Devices::Display as WinDisplay;
use windows::Win32::Devices::Display::{
DISPLAYCONFIG_DEVICE_INFO_GET_TARGET_NAME,
DISPLAYCONFIG_TARGET_DEVICE_NAME,
QDC_ALL_PATHS,
SDC_APPLY,
SDC_USE_SUPPLIED_DISPLAY_CONFIG,
SDC_SAVE_TO_DATABASE,
};
use crate::error::{DisplayError, DisplayResult};
use crate::types::{OutputState, DisplayRotation, HdrState, HdrMode, DisplayId, ConnectorId, AdapterId, DisplayIdentity, Rect, Point2D, Extent2D};
use super::displmgr_ccd_sys::{
DISPLAYCONFIG_PATH_ACTIVE,
DISPLAYCONFIG_PATH_MODE_IDX_INVALID,
DISPLAYCONFIG_MODE_INFO_TYPE_SOURCE,
DISPLAYCONFIG_MODE_INFO_TYPE_TARGET,
CcdRawData,
};
#[derive(Debug, Clone)]
pub struct DisplayTargetInfo {
pub friendly_name: String,
pub target_id: u32,
pub is_active: bool,
pub adapter_id: windows::Win32::Foundation::LUID,
}
pub fn query_all_display_targets() -> DisplayResult<Vec<DisplayTargetInfo>> {
let mut path_count = 0u32;
let mut mode_count = 0u32;
unsafe {
WinDisplay::GetDisplayConfigBufferSizes(
QDC_ALL_PATHS,
&mut path_count,
&mut mode_count,
)
}
.map_err(|e| DisplayError::BackendError(format!("GetDisplayConfigBufferSizes failed: {}", e)))?;
let mut paths = vec![WinDisplay::DISPLAYCONFIG_PATH_INFO::default(); path_count as usize];
let mut modes = vec![WinDisplay::DISPLAYCONFIG_MODE_INFO::default(); mode_count as usize];
unsafe {
WinDisplay::QueryDisplayConfig(
QDC_ALL_PATHS,
&mut path_count,
paths.as_mut_ptr(),
&mut mode_count,
modes.as_mut_ptr(),
None,
)
}
.map_err(|e| DisplayError::BackendError(format!("QueryDisplayConfig failed: {}", e)))?;
paths.truncate(path_count as usize);
let mut targets = Vec::new();
for path in &paths {
let target_id = path.targetInfo.id;
let adapter_id = path.targetInfo.adapterId;
let is_active = (path.flags & DISPLAYCONFIG_PATH_ACTIVE) != 0;
let friendly_name = get_target_name(adapter_id, target_id);
targets.push(DisplayTargetInfo {
friendly_name,
target_id,
is_active,
adapter_id,
});
}
Ok(targets)
}
pub fn find_display_target(query: &str) -> Option<DisplayTargetInfo> {
let qq = query.to_lowercase();
let targets = query_all_display_targets().ok()?;
targets.into_iter().find(|t| t.friendly_name.to_lowercase().contains(&qq))
}
pub fn ccd_wake_display(target_id: u32) -> DisplayResult<bool> {
unsafe {
let mut path_count = 0u32;
let mut mode_count = 0u32;
WinDisplay::GetDisplayConfigBufferSizes(
QDC_ALL_PATHS,
&mut path_count,
&mut mode_count,
)
.map_err(|e| DisplayError::BackendError(format!("GetDisplayConfigBufferSizes failed: {}", e)))?;
let mut paths = vec![WinDisplay::DISPLAYCONFIG_PATH_INFO::default(); path_count as usize];
let mut modes = vec![WinDisplay::DISPLAYCONFIG_MODE_INFO::default(); mode_count as usize];
WinDisplay::QueryDisplayConfig(
QDC_ALL_PATHS,
&mut path_count,
paths.as_mut_ptr(),
&mut mode_count,
modes.as_mut_ptr(),
None,
)
.map_err(|e| DisplayError::BackendError(format!("QueryDisplayConfig failed: {}", e)))?;
paths.truncate(path_count as usize);
let idx = paths.iter().position(|p| p.targetInfo.id == target_id)
.ok_or_else(|| DisplayError::NotFound(DisplayId(target_id.to_string())))?;
if (paths[idx].flags & DISPLAYCONFIG_PATH_ACTIVE) != 0 {
return Ok(false);
}
let src_idx = paths[idx].sourceInfo.Anonymous.modeInfoIdx;
if src_idx == DISPLAYCONFIG_PATH_MODE_IDX_INVALID {
return Err(DisplayError::BackendError(
format!("Target {} has no valid source mode index, cannot wake", target_id)
));
}
paths[idx].flags |= DISPLAYCONFIG_PATH_ACTIVE;
let status = WinDisplay::SetDisplayConfig(
Some(&paths),
Some(&modes),
SDC_APPLY | SDC_USE_SUPPLIED_DISPLAY_CONFIG | SDC_SAVE_TO_DATABASE,
);
if status != 0 {
return Err(DisplayError::BackendError(
format!("SetDisplayConfig wake failed for target {}: 0x{:08X}", target_id, status as u32)
));
}
Ok(true)
}
}
pub fn map_win32_code(result: windows::core::Result<()>) -> DisplayResult<()> {
result.map_err(|e| DisplayError::BackendError(format!("Win32 Error: {}", e)))
}
pub fn get_target_name(
adapter_id: windows::Win32::Foundation::LUID,
target_id: u32,
) -> String {
let mut payload = DISPLAYCONFIG_TARGET_DEVICE_NAME {
header: WinDisplay::DISPLAYCONFIG_DEVICE_INFO_HEADER {
r#type: DISPLAYCONFIG_DEVICE_INFO_GET_TARGET_NAME,
size: std::mem::size_of::<DISPLAYCONFIG_TARGET_DEVICE_NAME>() as u32,
adapterId: adapter_id,
id: target_id,
},
..Default::default()
};
let status = unsafe {
WinDisplay::DisplayConfigGetDeviceInfo(
&mut payload.header as *mut _ as *mut _,
)
};
if status == 0 && payload.monitorFriendlyDeviceName[0] != 0 {
let len = payload
.monitorFriendlyDeviceName
.iter()
.position(|&c| c == 0)
.unwrap_or(64);
String::from_utf16_lossy(&payload.monitorFriendlyDeviceName[..len]).to_string()
} else {
format!("Display_{:#010X}_{}", adapter_id.LowPart, target_id)
}
}
pub fn query_config_sync() -> DisplayResult<(CcdRawData, Vec<OutputState>)> {
let mut path_count = 0u32;
let mut mode_count = 0u32;
unsafe {
WinDisplay::GetDisplayConfigBufferSizes(
WinDisplay::QDC_ONLY_ACTIVE_PATHS,
&mut path_count,
&mut mode_count,
)
}
.map_err(|e| DisplayError::BackendError(format!("GetDisplayConfigBufferSizes failed: {}", e)))?;
let mut paths = vec![WinDisplay::DISPLAYCONFIG_PATH_INFO::default(); path_count as usize];
let mut modes = vec![WinDisplay::DISPLAYCONFIG_MODE_INFO::default(); mode_count as usize];
unsafe {
WinDisplay::QueryDisplayConfig(
WinDisplay::QDC_ONLY_ACTIVE_PATHS,
&mut path_count,
paths.as_mut_ptr(),
&mut mode_count,
modes.as_mut_ptr(),
None,
)
}
.map_err(|e| DisplayError::BackendError(format!("QueryDisplayConfig failed: {}", e)))?;
paths.truncate(path_count as usize);
modes.truncate(mode_count as usize);
let raw = CcdRawData {
paths: paths.clone(),
modes: modes.clone(),
};
let mut outputs = paths
.iter()
.map(|p| map_path_to_output(p, &modes))
.collect::<Vec<_>>();
finalize_primary_detection(&mut outputs);
Ok((raw, outputs))
}
fn map_path_to_output(
path: &WinDisplay::DISPLAYCONFIG_PATH_INFO,
modes: &[WinDisplay::DISPLAYCONFIG_MODE_INFO],
) -> OutputState {
let mut state = OutputState::default();
let target_id = path.targetInfo.id;
let adapter_id = path.targetInfo.adapterId;
state.identity = DisplayIdentity {
id: DisplayId(target_id.to_string()),
connector_id: ConnectorId(format!("{:#010X}", target_id)),
adapter_id: AdapterId(format!("{:#010X}", adapter_id.LowPart)),
hardware_uuid: None, monitor_name: get_target_name(adapter_id, target_id),
};
state.enabled = (path.flags & DISPLAYCONFIG_PATH_ACTIVE) != 0;
let source_idx = unsafe { path.sourceInfo.Anonymous.modeInfoIdx };
if source_idx != DISPLAYCONFIG_PATH_MODE_IDX_INVALID && (source_idx as usize) < modes.len() {
let mode = &modes[source_idx as usize];
if mode.infoType == DISPLAYCONFIG_MODE_INFO_TYPE_SOURCE {
unsafe {
state.geometry = Rect {
origin: Point2D {
x: mode.Anonymous.sourceMode.position.x,
y: mode.Anonymous.sourceMode.position.y,
},
size: Extent2D {
width: mode.Anonymous.sourceMode.width,
height: mode.Anonymous.sourceMode.height,
},
};
}
}
}
let target_idx = unsafe { path.targetInfo.Anonymous.modeInfoIdx };
if target_idx != DISPLAYCONFIG_PATH_MODE_IDX_INVALID && (target_idx as usize) < modes.len() {
let mode = &modes[target_idx as usize];
if mode.infoType == DISPLAYCONFIG_MODE_INFO_TYPE_TARGET {
unsafe {
let v = mode.Anonymous.targetMode.targetVideoSignalInfo.vSyncFreq;
if v.Denominator != 0 {
state.refresh_rate = ((v.Numerator as u64 * 1000) / v.Denominator as u64) as u32;
}
}
}
}
state.rotation = match path.targetInfo.rotation {
WinDisplay::DISPLAYCONFIG_ROTATION_ROTATE90 => DisplayRotation::Rotate90,
WinDisplay::DISPLAYCONFIG_ROTATION_ROTATE180 => DisplayRotation::Rotate180,
WinDisplay::DISPLAYCONFIG_ROTATION_ROTATE270 => DisplayRotation::Rotate270,
_ => DisplayRotation::Rotate0,
};
state.hdr_state = HdrState::Disabled;
state.hdr_mode = HdrMode::Default;
state.scale = 1.0;
state.native_resolution = Some(state.geometry.size);
state.is_primary = state.geometry.origin.x == 0 && state.geometry.origin.y == 0;
state
}
pub fn finalize_primary_detection(outputs: &mut [OutputState]) {
let mut primary_found = false;
for out in outputs.iter_mut() {
if out.is_primary {
if primary_found {
out.is_primary = false; } else {
primary_found = true;
}
}
}
if !primary_found {
if let Some(first) = outputs.iter_mut().find(|o| o.enabled) {
first.is_primary = true;
}
}
}