Skip to main content

display_profile_lib/
set_profile.rs

1use std::collections::btree_map::Entry;
2use std::collections::{BTreeMap, BTreeSet};
3
4use windows_ccd::util::from_windows_string;
5pub use windows_ccd::util::{PathInfoExt as _, U32Ext as _};
6use windows_ccd::windows::{
7    DISPLAYCONFIG_DESKTOP_IMAGE_INFO, DISPLAYCONFIG_MODE_INFO, DISPLAYCONFIG_MODE_INFO_0,
8    DISPLAYCONFIG_MODE_INFO_TYPE_DESKTOP_IMAGE, DISPLAYCONFIG_MODE_INFO_TYPE_SOURCE,
9    DISPLAYCONFIG_MODE_INFO_TYPE_TARGET, DISPLAYCONFIG_PATH_ACTIVE, DISPLAYCONFIG_PATH_INFO,
10    DISPLAYCONFIG_SCANLINE_ORDERING_PROGRESSIVE, DISPLAYCONFIG_SOURCE_DEVICE_NAME,
11    DISPLAYCONFIG_SOURCE_MODE, DISPLAYCONFIG_TARGET_DEVICE_NAME, DISPLAYCONFIG_TARGET_MODE,
12    DISPLAYCONFIG_VIDEO_SIGNAL_INFO, QDC_ALL_PATHS, QDC_VIRTUAL_MODE_AWARE, SDC_ALLOW_CHANGES,
13    SDC_APPLY, SDC_SAVE_TO_DATABASE, SDC_USE_SUPPLIED_DISPLAY_CONFIG, SDC_VALIDATE,
14    SDC_VIRTUAL_MODE_AWARE,
15};
16pub use windows_ccd::{
17    DeviceId, GetDeviceInfo, display_config_get_device_info, query_display_config,
18    set_display_config,
19};
20
21use crate::error::Error;
22use crate::util::TryFind as _;
23use crate::{Monitor, Profile, Result, VIRTUAL_MODE_AWARE};
24
25#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
26pub enum SetProfileAction {
27    Apply,
28    Validate,
29}
30
31struct DeviceNames {
32    cache: BTreeMap<DeviceId, String>,
33}
34impl DeviceNames {
35    fn new() -> Self {
36        Self {
37            cache: BTreeMap::new(),
38        }
39    }
40
41    fn source(&mut self, device_id: impl Into<DeviceId>) -> Result<String> {
42        self.get::<DISPLAYCONFIG_SOURCE_DEVICE_NAME>(device_id, |dev_name| {
43            &dev_name.viewGdiDeviceName
44        })
45    }
46
47    fn target(&mut self, device_id: impl Into<DeviceId>) -> Result<String> {
48        self.get::<DISPLAYCONFIG_TARGET_DEVICE_NAME>(device_id, |dev_name| {
49            &dev_name.monitorDevicePath
50        })
51    }
52
53    fn get<T: GetDeviceInfo>(
54        &mut self,
55        device_id: impl Into<DeviceId>,
56        extract: impl FnOnce(&T) -> &[u16],
57    ) -> Result<String> {
58        let device_id = device_id.into();
59
60        match self.cache.entry(device_id) {
61            Entry::Vacant(entry) => {
62                let device_name = display_config_get_device_info::<T>(device_id)?;
63                let device_name = from_windows_string(extract(&device_name));
64                entry.insert(device_name.clone());
65                Ok(device_name)
66            }
67            Entry::Occupied(entry) => Ok(entry.get().clone()),
68        }
69    }
70}
71
72/// Sets the current Windows display profile.
73pub fn set_profile(profile: &Profile, action: SetProfileAction) -> Result<()> {
74    let mut flags = QDC_ALL_PATHS;
75    if VIRTUAL_MODE_AWARE {
76        flags |= QDC_VIRTUAL_MODE_AWARE;
77    }
78    let (mut input_paths, _input_modes) = query_display_config(flags)?;
79    input_paths.retain(|path| path.targetInfo.targetAvailable.as_bool());
80
81    let solved_profile = solve_profile(profile, &input_paths)?;
82    let (paths, modes) = make_paths_and_modes(solved_profile);
83
84    let mut flags = SDC_USE_SUPPLIED_DISPLAY_CONFIG | SDC_ALLOW_CHANGES | SDC_SAVE_TO_DATABASE;
85    flags |= match action {
86        SetProfileAction::Apply => SDC_APPLY,
87        SetProfileAction::Validate => SDC_VALIDATE,
88    };
89    if VIRTUAL_MODE_AWARE {
90        flags |= SDC_VIRTUAL_MODE_AWARE;
91    }
92    set_display_config(Some(&paths), Some(&modes), flags)?;
93
94    Ok(())
95}
96
97fn solve_profile<'m, 'p>(
98    profile: &'m Profile,
99    paths: &'p [DISPLAYCONFIG_PATH_INFO],
100) -> Result<Vec<(&'m Monitor, &'p DISPLAYCONFIG_PATH_INFO)>> {
101    const ACTIVE_PATH: fn(&DISPLAYCONFIG_PATH_INFO) -> bool =
102        |path| path.flags.contains(DISPLAYCONFIG_PATH_ACTIVE);
103    const ANY_PATH: fn(&DISPLAYCONFIG_PATH_INFO) -> bool = |_path| true;
104
105    // The order of monitors is preserved throught the processing to keep the path order from when the profile was gotten.
106    let mut monitors: Vec<_> = profile.iter().map(|monitor| (monitor, None)).collect();
107
108    let mut device_names = DeviceNames::new();
109    let mut source_device_ids_in_use = BTreeSet::new();
110
111    let mut all_solved = false;
112    for path_condition in [ACTIVE_PATH, ANY_PATH] {
113        all_solved = solve_with_paths(
114            &mut monitors,
115            paths,
116            path_condition,
117            &mut device_names,
118            &mut source_device_ids_in_use,
119        )?;
120        if all_solved {
121            break;
122        }
123    }
124    if !all_solved {
125        return Err(Error::Custom(
126            "It was not possible to find paths for all monitors".to_string(),
127        ));
128    }
129
130    let profile = monitors
131        .into_iter()
132        .map(|(monitor, path)| (monitor, path.unwrap()))
133        .collect();
134    Ok(profile)
135}
136
137fn solve_with_paths<'p>(
138    monitors: &mut [(&Monitor, Option<&'p DISPLAYCONFIG_PATH_INFO>)],
139    paths: &'p [DISPLAYCONFIG_PATH_INFO],
140    mut path_condition: impl FnMut(&'p DISPLAYCONFIG_PATH_INFO) -> bool,
141    device_names: &mut DeviceNames,
142    source_device_ids_in_use: &mut BTreeSet<DeviceId>,
143) -> Result<bool> {
144    let mut all_solved = true;
145
146    let unsolved_monitors = monitors.iter_mut().filter(|(_, path)| path.is_none());
147
148    for (monitor, monitor_path) in unsolved_monitors {
149        #[expect(unstable_name_collisions)]
150        let active_path = paths.iter().try_find(|path| -> Result<bool> {
151            let condition = path_condition(path)
152                && !source_device_ids_in_use.contains(&path.sourceInfo.into())
153                && device_names.source(path.sourceInfo)? == monitor.source_device_name
154                && device_names.target(path.targetInfo)? == monitor.device_path;
155            Ok(condition)
156        })?;
157
158        if let Some(path) = active_path {
159            // Solved.
160            *monitor_path = Some(path);
161            source_device_ids_in_use.insert(path.sourceInfo.into());
162        } else {
163            // Still unsolved.
164            all_solved = false;
165        }
166    }
167
168    Ok(all_solved)
169}
170
171fn make_paths_and_modes<'m, 'p>(
172    solved_profile: impl IntoIterator<Item = (&'m Monitor, &'p DISPLAYCONFIG_PATH_INFO)>,
173) -> (Vec<DISPLAYCONFIG_PATH_INFO>, Vec<DISPLAYCONFIG_MODE_INFO>) {
174    let mut modes = Vec::new();
175    let mut add_mode = |mode: DISPLAYCONFIG_MODE_INFO| {
176        let idx = modes.len();
177        modes.push(mode);
178        idx
179    };
180
181    let paths = solved_profile
182        .into_iter()
183        .map(|(monitor, path)| {
184            let mut path = *path;
185
186            path.targetInfo.rotation = monitor.rotation.into();
187            path.targetInfo.scaling = monitor.scaling.into();
188            path.targetInfo.refreshRate = monitor.refresh_rate.into();
189
190            let source_mode = DISPLAYCONFIG_MODE_INFO {
191                infoType: DISPLAYCONFIG_MODE_INFO_TYPE_SOURCE,
192                id: path.sourceInfo.id,
193                adapterId: path.sourceInfo.adapterId,
194                Anonymous: DISPLAYCONFIG_MODE_INFO_0 {
195                    sourceMode: DISPLAYCONFIG_SOURCE_MODE {
196                        width: monitor.dimensions.width,
197                        height: monitor.dimensions.height,
198                        pixelFormat: monitor.pixel_format.into(),
199                        position: monitor.position.into(),
200                    },
201                },
202            };
203
204            let target_mode = DISPLAYCONFIG_MODE_INFO {
205                infoType: DISPLAYCONFIG_MODE_INFO_TYPE_TARGET,
206                id: path.targetInfo.id,
207                adapterId: path.targetInfo.adapterId,
208                Anonymous: DISPLAYCONFIG_MODE_INFO_0 {
209                    targetMode: DISPLAYCONFIG_TARGET_MODE {
210                        targetVideoSignalInfo: DISPLAYCONFIG_VIDEO_SIGNAL_INFO {
211                            vSyncFreq: monitor.refresh_rate.into(),
212                            activeSize: monitor.dimensions.clone().into(),
213                            scanLineOrdering: DISPLAYCONFIG_SCANLINE_ORDERING_PROGRESSIVE,
214                            ..Default::default()
215                        },
216                    },
217                },
218            };
219
220            let desktop_mode = DISPLAYCONFIG_MODE_INFO {
221                infoType: DISPLAYCONFIG_MODE_INFO_TYPE_DESKTOP_IMAGE,
222                id: path.targetInfo.id,
223                adapterId: path.targetInfo.adapterId,
224                Anonymous: DISPLAYCONFIG_MODE_INFO_0 {
225                    desktopImageInfo: DISPLAYCONFIG_DESKTOP_IMAGE_INFO {
226                        PathSourceSize: monitor.path_source_size.into(),
227                        DesktopImageRegion: monitor.desktop_image_region.into(),
228                        DesktopImageClip: monitor.desktop_image_clip.into(),
229                    },
230                },
231            };
232
233            let source_mode_idx = add_mode(source_mode);
234            let target_mode_idx = add_mode(target_mode);
235            let desktop_mode_idx = add_mode(desktop_mode);
236
237            path.set_source_mode_idx(Some(source_mode_idx));
238            path.set_target_mode_idx(Some(target_mode_idx));
239            path.set_desktop_mode_idx(Some(desktop_mode_idx));
240
241            path.flags |= DISPLAYCONFIG_PATH_ACTIVE;
242            path
243        })
244        .collect();
245
246    (paths, modes)
247}