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
72pub 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 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 *monitor_path = Some(path);
161 source_device_ids_in_use.insert(path.sourceInfo.into());
162 } else {
163 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}