use async_trait::async_trait;
use std::fs::File;
use std::io::Read;
use std::path::Path;
use tokio::task;
use serde::Deserialize;
use windows::Win32::UI::HiDpi::{
SetProcessDpiAwarenessContext, DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2,
};
use windows::Win32::Devices::Display as WinDisplay;
use crate::error::{DisplayError, DisplayResult};
use crate::traits::{OutputEditable, UniversalTopology};
use crate::types::{OutputState, DisplayId, Extent2D, Point2D, HdrState};
pub mod displmgr_ccd;
pub mod displmgr_gdi;
use self::displmgr_ccd::CcdTopology;
use self::displmgr_gdi::GdiTopology;
pub use self::displmgr_gdi::{force_activate_by_monitor_name, force_all};
pub use self::displmgr_ccd::{
DisplayTargetInfo,
query_all_display_targets,
find_display_target,
ccd_wake_display,
};
#[derive(Debug, Deserialize, Clone)]
pub struct OsLayerData {
pub target_id: u32,
pub gdi_name: String,
}
#[derive(Debug, Deserialize, Clone)]
pub struct MonitorConfig {
pub friendly_name: String,
pub os_layer: OsLayerData,
}
#[derive(Debug, Deserialize, Clone)]
pub struct EdidDump {
pub monitors: Vec<MonitorConfig>,
}
fn ensure_dpi_aware() {
unsafe {
let _ = SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2);
}
}
fn load_hardware_map() -> Option<EdidDump> {
let path = Path::new("hardware_map.json");
if !path.exists() {
return None;
}
let mut file = File::open(path).ok()?;
let mut contents = String::new();
file.read_to_string(&mut contents).ok()?;
serde_json::from_str(&contents).ok()
}
fn enrich_gdi_topology(gdi_backend: &mut GdiTopology, hardware_map: &Option<EdidDump>) {
if let Some(ref map) = hardware_map {
for monitor in &map.monitors {
if let Some(state) = gdi_backend.outputs.get_mut(&monitor.os_layer.gdi_name) {
state.identity.hardware_uuid = Some(monitor.os_layer.target_id.to_string());
}
}
}
}
pub struct WinDisplayManager {
ccd: Option<CcdTopology>,
gdi: GdiTopology,
use_ccd: bool,
hardware_map: Option<EdidDump>,
}
impl WinDisplayManager {
pub fn hardware_map(&self) -> Option<&EdidDump> {
self.hardware_map.as_ref()
}
pub fn uses_ccd(&self) -> bool {
self.use_ccd
}
pub fn auto_position_right_of(&self) -> Point2D {
let outputs = self.get_outputs();
let rightmost_x = outputs.iter()
.filter(|o| o.enabled && o.geometry.size.width > 0)
.map(|o| o.geometry.origin.x + o.geometry.size.width as i32)
.max()
.unwrap_or(1920);
Point2D { x: rightmost_x, y: 0 }
}
}
#[derive(Debug, Clone)]
pub struct ActivationResult {
pub target_id: u32,
pub monitor_name: String,
pub was_already_active: bool,
pub ccd_wake_performed: bool,
}
pub async fn activate_display(
manager: &mut WinDisplayManager,
monitor_name: &str,
resolution: Option<Extent2D>,
position: Option<Point2D>,
) -> DisplayResult<ActivationResult> {
let target = find_display_target(monitor_name)
.ok_or_else(|| DisplayError::NotFound(DisplayId(monitor_name.to_string())))?;
let target_id = target.target_id;
let was_already_active = target.is_active;
let mut ccd_wake_performed = false;
if !target.is_active {
ccd_wake_performed = ccd_wake_display(target_id)?;
}
let final_pos = position.unwrap_or_else(|| manager.auto_position_right_of());
let did = DisplayId(target_id.to_string());
{
let mut editor = manager.edit_output(&did)?;
if let Some(res) = resolution {
let _ = editor.set_resolution(res);
}
let _ = editor.set_position(final_pos);
let _ = editor.set_enabled(true);
}
manager.commit().await?;
Ok(ActivationResult {
target_id,
monitor_name: target.friendly_name,
was_already_active,
ccd_wake_performed,
})
}
fn apply_gdi_state_to_ccd(gdi: &GdiTopology, fresh_ccd: &CcdTopology) {
use crate::backends::windows::displmgr_ccd::displmgr_ccd_sys as ccd_sys;
for (_gdi_name, gdi_state) in &gdi.outputs {
let Some(ref hid) = gdi_state.identity.hardware_uuid else { continue };
let Ok(target_id) = hid.parse::<u32>() else { continue };
for path in &fresh_ccd.data.paths {
if path.targetInfo.id != target_id {
continue;
}
if (gdi_state.scale - 1.0).abs() > f64::EPSILON {
let scale_percent = (gdi_state.scale * 100.0).round() as i32;
let mut payload = ccd_sys::DISPLAYCONFIG_SOURCE_DPI_SCALE_SET {
header: WinDisplay::DISPLAYCONFIG_DEVICE_INFO_HEADER {
r#type: ccd_sys::DISPLAYCONFIG_DEVICE_INFO_SET_DPI_SCALE,
size: std::mem::size_of::<ccd_sys::DISPLAYCONFIG_SOURCE_DPI_SCALE_SET>() as u32,
adapterId: path.targetInfo.adapterId,
id: target_id,
},
scale_factor_as_percent: scale_percent,
};
let res = unsafe { WinDisplay::DisplayConfigSetDeviceInfo(&mut payload.header as *mut _ as *mut _) };
if res != 0 {
eprintln!("Warning: DPI scale set failed for target {}: 0x{:08X}", target_id, res);
}
}
if gdi_state.hdr_state != HdrState::Disabled {
let enabled = if gdi_state.hdr_state == HdrState::Enabled { 1 } else { 0 };
let mut payload = ccd_sys::DISPLAYCONFIG_SET_HDR_STATE {
header: WinDisplay::DISPLAYCONFIG_DEVICE_INFO_HEADER {
r#type: ccd_sys::DISPLAYCONFIG_DEVICE_INFO_SET_HDR_STATE,
size: std::mem::size_of::<ccd_sys::DISPLAYCONFIG_SET_HDR_STATE>() as u32,
adapterId: path.targetInfo.adapterId,
id: target_id,
},
enabled,
};
let res = unsafe { WinDisplay::DisplayConfigSetDeviceInfo(&mut payload.header as *mut _ as *mut _) };
if res != 0 {
eprintln!("Warning: HDR set failed for target {}: 0x{:08X}", target_id, res);
}
}
break; }
}
}
#[async_trait]
impl UniversalTopology for WinDisplayManager {
fn acquire() -> DisplayResult<Self> {
ensure_dpi_aware();
let hardware_map = load_hardware_map();
let mut use_ccd = true;
let ccd_backend = CcdTopology::acquire().map(Some).unwrap_or_else(|_| {
use_ccd = false;
None
});
let mut gdi_backend = GdiTopology::acquire()?;
enrich_gdi_topology(&mut gdi_backend, &hardware_map);
Ok(WinDisplayManager {
ccd: ccd_backend,
gdi: gdi_backend,
use_ccd,
hardware_map,
})
}
fn get_outputs(&self) -> Vec<OutputState> {
if self.use_ccd {
if let Some(ref ccd_backend) = self.ccd {
return ccd_backend.get_outputs();
}
}
self.gdi.get_outputs()
}
fn edit_output(&mut self, id: &DisplayId) -> DisplayResult<Box<dyn OutputEditable + '_>> {
if self.use_ccd {
if let Some(ref mut ccd_backend) = self.ccd {
return ccd_backend.edit_output(id);
}
}
self.gdi.edit_output(id)
}
fn set_persistence(&mut self, enabled: bool) -> &mut Self {
if let Some(ref mut ccd_backend) = self.ccd {
ccd_backend.set_persistence(enabled);
}
self.gdi.set_persistence(enabled);
self
}
async fn validate(&self) -> DisplayResult<()> {
if self.use_ccd {
if let Some(ref ccd_backend) = self.ccd {
return ccd_backend.validate().await;
}
}
Ok(())
}
async fn commit(&mut self) -> DisplayResult<()> {
if self.use_ccd {
let mut ccd_backend = match self.ccd.take() {
Some(backend) => backend,
None => return Err(DisplayError::BackendError("CCD backend uninitialized".into())),
};
let updated_ccd = task::spawn_blocking(move || -> DisplayResult<CcdTopology> {
futures::executor::block_on(async {
<CcdTopology as UniversalTopology>::commit(&mut ccd_backend).await
})?;
Ok(ccd_backend)
})
.await
.map_err(|e| DisplayError::BackendError(format!("CCD blocking task execution panic: {}", e)))??;
self.ccd = Some(updated_ccd);
if let Ok(mut fresh_gdi) = GdiTopology::acquire() {
enrich_gdi_topology(&mut fresh_gdi, &self.hardware_map);
self.gdi = fresh_gdi;
}
} else {
let mut gdi_backend = self.gdi.clone();
let updated_gdi = task::spawn_blocking(move || -> DisplayResult<GdiTopology> {
futures::executor::block_on(async {
<GdiTopology as UniversalTopology>::commit(&mut gdi_backend).await
})?;
Ok(gdi_backend)
})
.await
.map_err(|e| DisplayError::BackendError(format!("GDI blocking task execution panic: {}", e)))??;
self.gdi = updated_gdi;
if let Ok(fresh_ccd) = CcdTopology::acquire() {
apply_gdi_state_to_ccd(&self.gdi, &fresh_ccd);
self.ccd = Some(fresh_ccd);
self.use_ccd = true;
}
enrich_gdi_topology(&mut self.gdi, &self.hardware_map);
}
Ok(())
}
}