use async_trait::async_trait;
use std::collections::HashMap;
use std::fmt;
use windows::core::PCWSTR;
use windows::Win32::Graphics::Gdi::{
ChangeDisplaySettingsExW, DEVMODEW, DEVMODE_DISPLAY_ORIENTATION,
DISP_CHANGE_SUCCESSFUL,
CDS_TYPE, CDS_UPDATEREGISTRY, CDS_NORESET, CDS_SET_PRIMARY,
DM_PELSWIDTH, DM_PELSHEIGHT, DM_POSITION, DM_DISPLAYORIENTATION,
DMDO_DEFAULT, DMDO_90, DMDO_180, DMDO_270,
};
use crate::error::{DisplayError, DisplayResult};
use crate::traits::{OutputEditable, UniversalTopology};
use crate::types::{OutputState, DisplayId, DisplayRotation};
pub mod displmgr_gdi_api;
pub mod displmgr_gdi_sys;
pub mod displmgr_gdi_editor;
pub use self::displmgr_gdi_api::{force_activate_by_monitor_name, force_all};
use self::displmgr_gdi_api::query_gdi_outputs;
use self::displmgr_gdi_sys::to_wide;
use self::displmgr_gdi_editor::GdiOutputEditor;
#[derive(Clone)]
pub struct GdiTopology {
pub outputs: HashMap<String, OutputState>,
pub persistence_enabled: bool,
pub(crate) saved_modes: HashMap<String, DEVMODEW>,
pub(crate) target_primary_id: Option<String>,
}
pub struct DisplayRestorer {
pub snapshot: Vec<(Vec<u16>, bool, DEVMODEW)>,
}
impl Drop for DisplayRestorer {
fn drop(&mut self) {
for (name_u16, was_active, old_dm) in &self.snapshot {
let pcw_name = PCWSTR(name_u16.as_ptr());
let mut reset_dm = *old_dm;
let flags = CDS_UPDATEREGISTRY | CDS_NORESET;
if !*was_active {
reset_dm.dmPelsWidth = 0;
reset_dm.dmPelsHeight = 0;
reset_dm.dmFields = DM_PELSWIDTH | DM_PELSHEIGHT | DM_POSITION;
}
unsafe {
let _ = ChangeDisplaySettingsExW(pcw_name, Some(&reset_dm), None, flags, None);
}
}
unsafe {
let _ = ChangeDisplaySettingsExW(None, None, None, CDS_TYPE(0), None);
}
}
}
impl fmt::Debug for GdiTopology {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("GdiTopology")
.field("outputs", &self.outputs)
.field("persistence_enabled", &self.persistence_enabled)
.field("target_primary_id", &self.target_primary_id)
.finish()
}
}
#[async_trait]
impl UniversalTopology for GdiTopology {
fn acquire() -> DisplayResult<Self> {
let (saved_modes, outputs_vec) = query_gdi_outputs()?;
let mut outputs = HashMap::new();
for state in outputs_vec {
outputs.insert(state.identity.id.0.clone(), state);
}
Ok(Self {
outputs,
persistence_enabled: true,
saved_modes,
target_primary_id: None,
})
}
fn get_outputs(&self) -> Vec<OutputState> {
self.outputs.values().cloned().collect()
}
fn edit_output(&mut self, id: &DisplayId) -> DisplayResult<Box<dyn OutputEditable + '_>> {
if !self.outputs.contains_key(&id.0) {
return Err(DisplayError::NotFound(id.clone()));
}
Ok(Box::new(GdiOutputEditor::new(self, id.0.clone())))
}
fn set_persistence(&mut self, enabled: bool) -> &mut Self {
self.persistence_enabled = enabled;
self
}
async fn validate(&self) -> DisplayResult<()> {
Ok(())
}
async fn commit(&mut self) -> DisplayResult<()> {
if let Some(primary_id) = self.target_primary_id.clone() {
if let (Some(state), Some(&saved)) = (
self.outputs.get(&primary_id).cloned(),
self.saved_modes.get(&primary_id),
) {
let mut devmode = saved;
devmode.dmFields |=
DM_PELSWIDTH | DM_PELSHEIGHT | DM_POSITION | DM_DISPLAYORIENTATION;
devmode.dmPelsWidth = state.geometry.size.width;
devmode.dmPelsHeight = state.geometry.size.height;
devmode.Anonymous1.Anonymous2.dmPosition.x = 0;
devmode.Anonymous1.Anonymous2.dmPosition.y = 0;
devmode.Anonymous1.Anonymous2.dmDisplayOrientation = rotation_to_dmdo(state.rotation);
let flags = if self.persistence_enabled {
CDS_UPDATEREGISTRY | CDS_NORESET | CDS_SET_PRIMARY
} else {
CDS_NORESET | CDS_SET_PRIMARY
};
let wide_id = to_wide(&primary_id);
if unsafe {
ChangeDisplaySettingsExW(
PCWSTR(wide_id.as_ptr()),
Some(&devmode),
None,
flags,
None,
)
} != DISP_CHANGE_SUCCESSFUL
{
return Err(DisplayError::BackendError("Primary staging failed".into()));
}
}
}
for (id, state) in &self.outputs {
if Some(id) == self.target_primary_id.as_ref() {
continue;
}
let mut devmode = *self
.saved_modes
.get(id)
.ok_or_else(|| DisplayError::NotFound(DisplayId(id.clone())))?;
if state.enabled {
devmode.dmFields |=
DM_PELSWIDTH | DM_PELSHEIGHT | DM_POSITION | DM_DISPLAYORIENTATION;
devmode.dmPelsWidth = state.geometry.size.width;
devmode.dmPelsHeight = state.geometry.size.height;
devmode.Anonymous1.Anonymous2.dmPosition.x = state.geometry.origin.x;
devmode.Anonymous1.Anonymous2.dmPosition.y = state.geometry.origin.y;
devmode.Anonymous1.Anonymous2.dmDisplayOrientation = rotation_to_dmdo(state.rotation);
} else {
devmode.dmFields |= DM_PELSWIDTH | DM_PELSHEIGHT;
devmode.dmPelsWidth = 0;
devmode.dmPelsHeight = 0;
}
let flags = if self.persistence_enabled {
CDS_UPDATEREGISTRY | CDS_NORESET
} else {
CDS_NORESET
};
let wide_id = to_wide(id);
if unsafe {
ChangeDisplaySettingsExW(
PCWSTR(wide_id.as_ptr()),
Some(&devmode),
None,
flags,
None,
)
} != DISP_CHANGE_SUCCESSFUL
{
return Err(DisplayError::BackendError("Secondary staging failed".into()));
}
}
if unsafe {
ChangeDisplaySettingsExW(PCWSTR::null(), None, None, CDS_TYPE(0), None)
} != DISP_CHANGE_SUCCESSFUL
{
return Err(DisplayError::BackendError("Global flush failed".into()));
}
self.target_primary_id = None;
Ok(())
}
}
impl GdiTopology {
pub fn snapshot_wake_inactive_outputs(
&mut self,
keep_active: Option<&DisplayId>,
) -> Vec<String> {
let previously_inactive: Vec<String> = self
.outputs
.iter()
.filter_map(|(id, state)| {
if !state.enabled && keep_active.map_or(true, |keep| &keep.0 != id) {
Some(id.clone())
} else {
None
}
})
.collect();
for state in self.outputs.values_mut() {
if !state.enabled {
state.enabled = true;
}
}
previously_inactive
}
pub fn restore_inactive_outputs(&mut self, inactive_ids: &[String]) {
for id in inactive_ids {
if let Some(state) = self.outputs.get_mut(id) {
state.enabled = false;
}
}
}
}
#[inline]
fn rotation_to_dmdo(rotation: DisplayRotation) -> DEVMODE_DISPLAY_ORIENTATION {
match rotation {
DisplayRotation::Rotate0 => DMDO_DEFAULT,
DisplayRotation::Rotate90 => DMDO_90,
DisplayRotation::Rotate180 => DMDO_180,
DisplayRotation::Rotate270 => DMDO_270,
}
}