use async_trait::async_trait;
use std::collections::HashMap;
use windows::Win32::Devices::Display as WinDisplay;
use windows::Win32::Devices::Display::{
SetDisplayConfig, SDC_APPLY, SDC_SAVE_TO_DATABASE, SDC_VALIDATE, SDC_USE_SUPPLIED_DISPLAY_CONFIG,
};
use crate::error::{DisplayError, DisplayResult};
use crate::traits::{UniversalTopology, OutputEditable};
use crate::types::{OutputState, HdrState, HdrMode, DisplayId};
pub mod displmgr_ccd_api;
pub mod displmgr_ccd_sys;
pub mod displmgr_ccd_editor;
use self::displmgr_ccd_api::query_config_sync;
use self::displmgr_ccd_editor::CcdOutputEditor;
pub use self::displmgr_ccd_sys::*;
pub use self::displmgr_ccd_api::{
DisplayTargetInfo,
query_all_display_targets,
find_display_target,
ccd_wake_display,
};
#[derive(Debug, Clone)]
pub struct CcdTopology {
pub data: CcdRawData,
pub outputs: Vec<OutputState>,
pub persist: bool,
pub(crate) staged_scales: HashMap<u32, i32>,
pub(crate) staged_hdr: HashMap<u32, (HdrState, HdrMode)>,
pub(crate) dirty: bool,
}
#[async_trait]
impl UniversalTopology for CcdTopology {
fn acquire() -> DisplayResult<Self> {
let (raw_data, outputs) = query_config_sync().map_err(|e| {
DisplayError::BackendError(format!("Initial QueryDisplayConfig failed: {:?}", e))
})?;
Ok(CcdTopology {
data: raw_data,
outputs,
persist: true,
staged_scales: HashMap::new(),
staged_hdr: HashMap::new(),
dirty: false,
})
}
fn get_outputs(&self) -> Vec<OutputState> {
self.outputs.clone()
}
fn edit_output(&mut self, id: &DisplayId) -> DisplayResult<Box<dyn OutputEditable + '_>> {
let target_id = id.0.parse::<u32>().map_err(|_| DisplayError::NotFound(id.clone()))?;
if !self.data.paths.iter().any(|p| p.targetInfo.id == target_id) {
return Err(DisplayError::NotFound(id.clone()));
}
Ok(Box::new(CcdOutputEditor::new(self, target_id)))
}
fn set_persistence(&mut self, enabled: bool) -> &mut Self {
self.persist = enabled;
self
}
async fn validate(&self) -> DisplayResult<()> {
let has_work = self.dirty || !self.staged_scales.is_empty() || !self.staged_hdr.is_empty();
if !has_work || self.data.paths.is_empty() { return Ok(()); }
let active_outputs: Vec<&OutputState> = self.outputs.iter()
.filter(|o| o.enabled && o.geometry.size.width > 0 && o.geometry.size.height > 0).collect();
for i in 0..active_outputs.len() {
for j in (i + 1)..active_outputs.len() {
let (o1, o2) = (active_outputs[i], active_outputs[j]);
if o1.geometry.origin.x < o2.geometry.origin.x + o2.geometry.size.width as i32 &&
o1.geometry.origin.x + o1.geometry.size.width as i32 > o2.geometry.origin.x &&
o1.geometry.origin.y < o2.geometry.origin.y + o2.geometry.size.height as i32 &&
o1.geometry.origin.y + o1.geometry.size.height as i32 > o2.geometry.origin.y {
return Err(DisplayError::ConfigurationRejected);
}
}
}
let status = unsafe { SetDisplayConfig(Some(&self.data.paths), Some(&self.data.modes), SDC_VALIDATE | SDC_USE_SUPPLIED_DISPLAY_CONFIG) };
if status != 0 { return Err(DisplayError::ConfigurationRejected); }
Ok(())
}
async fn commit(&mut self) -> DisplayResult<()> {
if !self.dirty && self.staged_scales.is_empty() && self.staged_hdr.is_empty() { return Ok(()); }
if self.data.paths.is_empty() { return Ok(()); }
self.validate().await?;
let mut flags = SDC_APPLY | SDC_USE_SUPPLIED_DISPLAY_CONFIG;
if self.persist { flags |= SDC_SAVE_TO_DATABASE; }
if self.dirty {
let status = unsafe { SetDisplayConfig(Some(&self.data.paths), Some(&self.data.modes), flags) };
if status != 0 {
self.staged_scales.clear(); self.staged_hdr.clear(); self.dirty = false;
return Err(DisplayError::BackendError(format!("SetDisplayConfig failed: {}", status)));
}
}
for (&target_id, &scale) in &self.staged_scales {
if let Some(path) = self.data.paths.iter().find(|p| p.targetInfo.id == target_id) {
let mut payload = DISPLAYCONFIG_SOURCE_DPI_SCALE_SET {
header: WinDisplay::DISPLAYCONFIG_DEVICE_INFO_HEADER {
r#type: DISPLAYCONFIG_DEVICE_INFO_SET_DPI_SCALE,
size: std::mem::size_of::<DISPLAYCONFIG_SOURCE_DPI_SCALE_SET>() as u32,
adapterId: path.targetInfo.adapterId,
id: target_id,
},
scale_factor_as_percent: scale,
};
unsafe { let _ = WinDisplay::DisplayConfigSetDeviceInfo(&mut payload.header as *mut _ as *mut _); }
}
}
for (&target_id, &(hdr_state, _)) in &self.staged_hdr {
if let Some(path) = self.data.paths.iter().find(|p| p.targetInfo.id == target_id) {
let mut payload = DISPLAYCONFIG_SET_HDR_STATE {
header: WinDisplay::DISPLAYCONFIG_DEVICE_INFO_HEADER {
r#type: DISPLAYCONFIG_DEVICE_INFO_SET_HDR_STATE,
size: std::mem::size_of::<DISPLAYCONFIG_SET_HDR_STATE>() as u32,
adapterId: path.targetInfo.adapterId,
id: target_id,
},
enabled: if hdr_state == HdrState::Enabled { 1 } else { 0 },
};
unsafe { let _ = WinDisplay::DisplayConfigSetDeviceInfo(&mut payload.header as *mut _ as *mut _); }
}
}
self.staged_scales.clear(); self.staged_hdr.clear(); self.dirty = false;
if let Ok((raw_data, outputs)) = query_config_sync() {
self.data = raw_data; self.outputs = outputs;
}
Ok(())
}
}