Skip to main content

windows_ccd/
util.rs

1//! Convenience extension methods traits and functions.
2
3use crate::windows::{
4    DISPLAYCONFIG_PATH_CLONE_GROUP_INVALID, DISPLAYCONFIG_PATH_DESKTOP_IMAGE_IDX_INVALID,
5    DISPLAYCONFIG_PATH_INFO, DISPLAYCONFIG_PATH_MODE_IDX_INVALID, DISPLAYCONFIG_PATH_SOURCE_INFO,
6    DISPLAYCONFIG_PATH_SOURCE_MODE_IDX_INVALID, DISPLAYCONFIG_PATH_SUPPORT_VIRTUAL_MODE,
7    DISPLAYCONFIG_PATH_TARGET_INFO, DISPLAYCONFIG_PATH_TARGET_MODE_IDX_INVALID,
8};
9
10/// Convenience extension methods for [`DISPLAYCONFIG_PATH_INFO`].
11pub trait PathInfoExt {
12    /// Tells wether the `flags` contain the [`DISPLAYCONFIG_PATH_SUPPORT_VIRTUAL_MODE`] flag.
13    fn support_virtual_mode(&self) -> bool;
14
15    /// Obtains the clone group id.
16    ///
17    /// It checks for virtual mode support and handles the bits.
18    fn clone_group_id(&self) -> Option<usize>;
19
20    /// Obtains the source mode index.
21    ///
22    /// It checks for virtual mode support and handles the bits, including testing against
23    /// [`DISPLAYCONFIG_PATH_SOURCE_MODE_IDX_INVALID`] or [`DISPLAYCONFIG_PATH_MODE_IDX_INVALID`]
24    /// appropriately.
25    fn source_mode_idx(&self) -> Option<usize>;
26
27    /// Obtains the target mode index.
28    ///
29    /// It checks for virtual mode support and handles the bits, including testing against
30    /// [`DISPLAYCONFIG_PATH_TARGET_MODE_IDX_INVALID`] or [`DISPLAYCONFIG_PATH_MODE_IDX_INVALID`]
31    /// appropriately.
32    fn target_mode_idx(&self) -> Option<usize>;
33
34    /// Obtains the desktop mode id.
35    ///
36    /// It checks for virtual mode support and handles the bits.
37    fn desktop_mode_idx(&self) -> Option<usize>;
38
39    /// Sets the clone group id.
40    ///
41    /// It checks for virtual mode support and handles the bits.
42    ///
43    /// It panics if virtual mode is not supported and `id` is not [`None`].
44    fn set_clone_group_id(&mut self, id: Option<usize>);
45
46    /// Sets the source mode index.
47    ///
48    /// It checks for virtual mode support and handles the bits, including using
49    /// [`DISPLAYCONFIG_PATH_SOURCE_MODE_IDX_INVALID`] or [`DISPLAYCONFIG_PATH_MODE_IDX_INVALID`]
50    /// appropriately.
51    fn set_source_mode_idx(&mut self, idx: Option<usize>);
52
53    /// Sets the target mode index.
54    ///
55    /// It checks for virtual mode support and handles the bits, including using
56    /// [`DISPLAYCONFIG_PATH_TARGET_MODE_IDX_INVALID`] or [`DISPLAYCONFIG_PATH_MODE_IDX_INVALID`]
57    /// appropriately.
58    fn set_target_mode_idx(&mut self, idx: Option<usize>);
59
60    /// Sets the desktop mode id.
61    ///
62    /// It checks for virtual mode support and handles the bits.
63    ///
64    /// It panics if virtual mode is not supported and `id` is not [`None`].
65    fn set_desktop_mode_idx(&mut self, idx: Option<usize>);
66}
67
68impl PathInfoExt for DISPLAYCONFIG_PATH_INFO {
69    fn support_virtual_mode(&self) -> bool {
70        self.flags.contains(DISPLAYCONFIG_PATH_SUPPORT_VIRTUAL_MODE)
71    }
72
73    fn clone_group_id(&self) -> Option<usize> {
74        self.sourceInfo.clone_group_id(self.support_virtual_mode())
75    }
76
77    fn source_mode_idx(&self) -> Option<usize> {
78        self.sourceInfo.source_mode_idx(self.support_virtual_mode())
79    }
80
81    fn target_mode_idx(&self) -> Option<usize> {
82        self.targetInfo.target_mode_idx(self.support_virtual_mode())
83    }
84
85    fn desktop_mode_idx(&self) -> Option<usize> {
86        self.targetInfo
87            .desktop_mode_idx(self.support_virtual_mode())
88    }
89
90    fn set_clone_group_id(&mut self, id: Option<usize>) {
91        self.sourceInfo
92            .set_clone_group_id(id, self.support_virtual_mode());
93    }
94
95    fn set_source_mode_idx(&mut self, idx: Option<usize>) {
96        self.sourceInfo
97            .set_source_mode_idx(idx, self.support_virtual_mode());
98    }
99
100    fn set_target_mode_idx(&mut self, idx: Option<usize>) {
101        self.targetInfo
102            .set_target_mode_idx(idx, self.support_virtual_mode());
103    }
104
105    fn set_desktop_mode_idx(&mut self, idx: Option<usize>) {
106        self.targetInfo
107            .set_desktop_mode_idx(idx, self.support_virtual_mode());
108    }
109}
110
111/// Convenience extension methods for [`DISPLAYCONFIG_PATH_SOURCE_INFO`].
112pub trait PathSourceInfoExt {
113    /// Obtains the source mode index.
114    ///
115    /// It handles the bits, including testing against
116    /// [`DISPLAYCONFIG_PATH_SOURCE_MODE_IDX_INVALID`] or [`DISPLAYCONFIG_PATH_MODE_IDX_INVALID`]
117    /// appropriately.
118    fn source_mode_idx(&self, path_support_virtual_mode: bool) -> Option<usize>;
119
120    /// Obtains the clone group id.
121    fn clone_group_id(&self, path_support_virtual_mode: bool) -> Option<usize>;
122
123    /// Sets the source mode index.
124    ///
125    /// It handles the bits, including using
126    /// [`DISPLAYCONFIG_PATH_SOURCE_MODE_IDX_INVALID`] or [`DISPLAYCONFIG_PATH_MODE_IDX_INVALID`]
127    /// appropriately.
128    fn set_source_mode_idx(&mut self, idx: Option<usize>, path_support_virtual_mode: bool);
129
130    /// Sets the clone group id.
131    ///
132    /// It panics if `path_support_virtual_mode` is [`false`] and `id` is not [`None`].
133    fn set_clone_group_id(&mut self, id: Option<usize>, path_support_virtual_mode: bool);
134}
135
136impl PathSourceInfoExt for DISPLAYCONFIG_PATH_SOURCE_INFO {
137    fn source_mode_idx(&self, path_support_virtual_mode: bool) -> Option<usize> {
138        self.get_value(path_support_virtual_mode, PathInfoUnionValueKind::Primary)
139    }
140
141    fn clone_group_id(&self, path_support_virtual_mode: bool) -> Option<usize> {
142        self.get_value(path_support_virtual_mode, PathInfoUnionValueKind::Secondary)
143    }
144
145    fn set_source_mode_idx(&mut self, idx: Option<usize>, path_support_virtual_mode: bool) {
146        self.set_value(
147            path_support_virtual_mode,
148            PathInfoUnionValueKind::Primary,
149            idx,
150        );
151    }
152
153    fn set_clone_group_id(&mut self, id: Option<usize>, path_support_virtual_mode: bool) {
154        self.set_value(
155            path_support_virtual_mode,
156            PathInfoUnionValueKind::Secondary,
157            id,
158        );
159    }
160}
161
162/// Convenience extension methods for [`DISPLAYCONFIG_PATH_TARGET_INFO`].
163pub trait PathTargetInfoExt {
164    /// Obtains the target mode index.
165    ///
166    /// It handles the bits, including testing against
167    /// [`DISPLAYCONFIG_PATH_TARGET_MODE_IDX_INVALID`] or [`DISPLAYCONFIG_PATH_MODE_IDX_INVALID`]
168    /// appropriately.
169    fn target_mode_idx(&self, path_support_virtual_mode: bool) -> Option<usize>;
170
171    /// Obtains the desktop mode id.
172    fn desktop_mode_idx(&self, path_support_virtual_mode: bool) -> Option<usize>;
173
174    /// Sets the target mode index.
175    ///
176    /// It handles the bits, including using
177    /// [`DISPLAYCONFIG_PATH_TARGET_MODE_IDX_INVALID`] or [`DISPLAYCONFIG_PATH_MODE_IDX_INVALID`]
178    /// appropriately.
179    fn set_target_mode_idx(&mut self, idx: Option<usize>, path_support_virtual_mode: bool);
180
181    /// Sets the desktop mode id.
182    ///
183    /// It panics if `path_support_virtual_mode` is [`false`] and `id` is not [`None`].
184    fn set_desktop_mode_idx(&mut self, idx: Option<usize>, path_support_virtual_mode: bool);
185}
186
187impl PathTargetInfoExt for DISPLAYCONFIG_PATH_TARGET_INFO {
188    fn target_mode_idx(&self, path_support_virtual_mode: bool) -> Option<usize> {
189        self.get_value(path_support_virtual_mode, PathInfoUnionValueKind::Primary)
190    }
191
192    fn desktop_mode_idx(&self, path_support_virtual_mode: bool) -> Option<usize> {
193        self.get_value(path_support_virtual_mode, PathInfoUnionValueKind::Secondary)
194    }
195
196    fn set_target_mode_idx(&mut self, idx: Option<usize>, path_support_virtual_mode: bool) {
197        self.set_value(
198            path_support_virtual_mode,
199            PathInfoUnionValueKind::Primary,
200            idx,
201        );
202    }
203
204    fn set_desktop_mode_idx(&mut self, idx: Option<usize>, path_support_virtual_mode: bool) {
205        self.set_value(
206            path_support_virtual_mode,
207            PathInfoUnionValueKind::Secondary,
208            idx,
209        );
210    }
211}
212
213#[derive(Debug, Clone, Copy, PartialEq, Eq)]
214enum PathInfoUnionValueKind {
215    Primary,
216    Secondary,
217}
218
219trait PathInfoUnionValuesAccessor {
220    const INVALID_PRIMARY: u32;
221    const INVALID_SECONDARY: u32;
222
223    const SECONDARY_DESCRIPTION: &'static str;
224
225    fn mode_info_idx(&self) -> u32;
226    fn mode_info_idx_mut(&mut self) -> &mut u32;
227
228    fn bitfield(&self) -> u32;
229    fn bitfield_mut(&mut self) -> &mut u32;
230
231    fn get_value(
232        &self,
233        path_support_virtual_mode: bool,
234        kind: PathInfoUnionValueKind,
235    ) -> Option<usize> {
236        if path_support_virtual_mode {
237            let (value, invalid) = match kind {
238                PathInfoUnionValueKind::Primary => {
239                    (self.bitfield().higher_u16(), Self::INVALID_PRIMARY)
240                }
241                PathInfoUnionValueKind::Secondary => {
242                    (self.bitfield().lower_u16(), Self::INVALID_SECONDARY)
243                }
244            };
245            (u32::from(value) != invalid).then_some(value as usize)
246        } else {
247            match kind {
248                PathInfoUnionValueKind::Primary => {
249                    let value = self.mode_info_idx();
250                    (value != DISPLAYCONFIG_PATH_MODE_IDX_INVALID).then_some(value as usize)
251                }
252                PathInfoUnionValueKind::Secondary => None,
253            }
254        }
255    }
256
257    fn set_value(
258        &mut self,
259        path_support_virtual_mode: bool,
260        kind: PathInfoUnionValueKind,
261        value: Option<usize>,
262    ) {
263        if path_support_virtual_mode {
264            #[expect(clippy::cast_possible_truncation)]
265            let value = match value {
266                Some(val) => val as u16,
267                None => Self::INVALID_PRIMARY as u16,
268            };
269
270            match kind {
271                PathInfoUnionValueKind::Primary => {
272                    self.bitfield_mut().set_higher_u16(value);
273                }
274                PathInfoUnionValueKind::Secondary => {
275                    self.bitfield_mut().set_lower_u16(value);
276                }
277            }
278        } else {
279            match kind {
280                PathInfoUnionValueKind::Primary => {
281                    *self.mode_info_idx_mut() = match value {
282                        #[expect(clippy::cast_possible_truncation)]
283                        Some(val) => val as u32,
284                        None => DISPLAYCONFIG_PATH_MODE_IDX_INVALID,
285                    };
286                }
287                PathInfoUnionValueKind::Secondary => {
288                    assert!(
289                        value.is_none(),
290                        "{} cannot be set",
291                        Self::SECONDARY_DESCRIPTION
292                    );
293                }
294            }
295        }
296    }
297}
298impl PathInfoUnionValuesAccessor for DISPLAYCONFIG_PATH_SOURCE_INFO {
299    const INVALID_PRIMARY: u32 = DISPLAYCONFIG_PATH_SOURCE_MODE_IDX_INVALID;
300    const INVALID_SECONDARY: u32 = DISPLAYCONFIG_PATH_CLONE_GROUP_INVALID;
301
302    const SECONDARY_DESCRIPTION: &'static str = "Clone group id";
303
304    fn mode_info_idx(&self) -> u32 {
305        unsafe { self.Anonymous.modeInfoIdx }
306    }
307
308    fn mode_info_idx_mut(&mut self) -> &mut u32 {
309        unsafe { &mut self.Anonymous.modeInfoIdx }
310    }
311
312    fn bitfield(&self) -> u32 {
313        unsafe { self.Anonymous.Anonymous._bitfield }
314    }
315
316    fn bitfield_mut(&mut self) -> &mut u32 {
317        unsafe { &mut self.Anonymous.Anonymous._bitfield }
318    }
319}
320impl PathInfoUnionValuesAccessor for DISPLAYCONFIG_PATH_TARGET_INFO {
321    const INVALID_PRIMARY: u32 = DISPLAYCONFIG_PATH_TARGET_MODE_IDX_INVALID;
322    const INVALID_SECONDARY: u32 = DISPLAYCONFIG_PATH_DESKTOP_IMAGE_IDX_INVALID;
323
324    const SECONDARY_DESCRIPTION: &'static str = "Desktop mode idx";
325
326    fn mode_info_idx(&self) -> u32 {
327        unsafe { self.Anonymous.modeInfoIdx }
328    }
329
330    fn mode_info_idx_mut(&mut self) -> &mut u32 {
331        unsafe { &mut self.Anonymous.modeInfoIdx }
332    }
333
334    fn bitfield(&self) -> u32 {
335        unsafe { self.Anonymous.Anonymous._bitfield }
336    }
337
338    fn bitfield_mut(&mut self) -> &mut u32 {
339        unsafe { &mut self.Anonymous.Anonymous._bitfield }
340    }
341}
342
343/// Convenience extension methods for [`u32`] (for bit handling).
344pub trait U32Ext {
345    /// Checks whether this value contains the bits set in `flag`.
346    fn contains(self, flag: u32) -> bool;
347
348    /// Obtains the lower 16 bits.
349    fn lower_u16(self) -> u16;
350
351    /// Obtains the higher 16 bits.
352    fn higher_u16(self) -> u16;
353
354    /// Sets the lower 16 bits.
355    fn set_lower_u16(&mut self, lower: u16);
356
357    /// Sets the higher 16 bits.
358    fn set_higher_u16(&mut self, higher: u16);
359}
360impl U32Ext for u32 {
361    fn contains(self, flag: u32) -> bool {
362        self & flag == flag
363    }
364
365    fn lower_u16(self) -> u16 {
366        (self & 0x0000_FFFF) as u16
367    }
368
369    fn higher_u16(self) -> u16 {
370        (self >> 16) as u16
371    }
372
373    fn set_lower_u16(&mut self, lower: u16) {
374        *self &= 0xFFFF_0000;
375        *self |= u32::from(lower) & 0x0000_FFFF;
376    }
377
378    fn set_higher_u16(&mut self, higher: u16) {
379        *self &= 0x0000_FFFF;
380        *self |= u32::from(higher) << 16;
381    }
382}
383
384/// Creates a [`String`] from a nul-terminated UTF-16-encoded [`u16`] buffer.
385#[must_use]
386pub fn from_windows_string(s: &[u16]) -> String {
387    let len = s.iter().position(|&c| c == 0).unwrap_or(s.len());
388    String::from_utf16_lossy(&s[..len])
389}