Skip to main content

ib_shell_item/prop/
system.rs

1use std::ptr;
2
3use widestring::U16CStr;
4use windows::{
5    Win32::{
6        Foundation::PROPERTYKEY,
7        System::Com::StructuredStorage::PROPVARIANT,
8        UI::Shell::PropertiesSystem::{PROPDESC_FORMAT_FLAGS, PSGetPropertySystem},
9    },
10    core::{Interface, PWSTR, Result},
11};
12
13pub use windows::Win32::UI::Shell::PropertiesSystem::IPropertySystem;
14
15use crate::init;
16
17/// [IPropertySystem (propsys.h)](https://learn.microsoft.com/en-us/windows/win32/api/propsys/nn-propsys-ipropertysystem)
18///
19/// Exposes methods that get property descriptions, register and unregister property schemas, enumerate property descriptions, and format property values in a type-strict way.
20pub trait PropertySystem {
21    /// Call `CoInitialize()` before [`PropertySystem::new()`].
22    fn new_init() -> Result<IPropertySystem>;
23
24    /// [PSGetPropertySystem function (propsys.h)](https://learn.microsoft.com/en-us/windows/win32/api/propsys/nf-propsys-psgetpropertysystem)
25    fn new() -> Result<IPropertySystem>;
26
27    /// [IPropertySystem::FormatForDisplay (propsys.h)](https://learn.microsoft.com/en-us/windows/win32/api/propsys/nf-propsys-ipropertysystem-formatfordisplay)
28    ///
29    /// Formats a property value for display into a user-provided buffer.
30    ///
31    /// ## Arguments
32    /// - `key`: The property key to format
33    /// - `propvar`: The property value to format
34    /// - `pdff`: Format flags
35    /// - `buffer`: Mutable buffer to receive the formatted string (UTF-16)
36    fn format_for_display<'b>(
37        &self,
38        key: &PROPERTYKEY,
39        propvar: &PROPVARIANT,
40        pdff: PROPDESC_FORMAT_FLAGS,
41        buffer: &'b mut [u16],
42    ) -> Result<&'b mut U16CStr>;
43
44    /// [IPropertySystem::FormatForDisplayAlloc (propsys.h)](https://learn.microsoft.com/en-us/windows/win32/api/propsys/nf-propsys-ipropertysystem-formatfordisplayalloc)
45    ///
46    /// ## Arguments
47    /// - `key`: The property key to format
48    ///
49    ///   Can be controlled by [`displayInfo`](https://learn.microsoft.com/en-us/windows/win32/properties/propdesc-schema-displayinfo).
50    /// - `propvar`: The property value to format
51    /// - `pdff`: Format flags
52    ///
53    /// ## Returns
54    /// The calling application must use [`CoTaskMemFree`] to release the returned string when it is no longer needed.
55    ///
56    /// ## Implementation
57    /// ```cpp
58    /// CFormatForDisplay::FormatForDisplay() {
59    ///   CFormatForDisplay::_FormatSize() {
60    ///     PSStrFormatKBSizeW()
61    ///   }
62    /// }
63    /// ```
64    fn format_for_display_alloc(
65        &self,
66        key: &PROPERTYKEY,
67        propvar: &PROPVARIANT,
68        pdff: PROPDESC_FORMAT_FLAGS,
69    ) -> Result<PWSTR>;
70}
71
72impl PropertySystem for IPropertySystem {
73    fn new_init() -> Result<IPropertySystem> {
74        _ = init();
75        Self::new()
76    }
77
78    fn new() -> Result<IPropertySystem> {
79        let mut pv = ptr::null_mut();
80        unsafe { PSGetPropertySystem(&IPropertySystem::IID, &mut pv)? };
81        Ok(unsafe { IPropertySystem::from_raw(pv) })
82    }
83
84    fn format_for_display<'b>(
85        &self,
86        key: &PROPERTYKEY,
87        propvar: &PROPVARIANT,
88        pdff: PROPDESC_FORMAT_FLAGS,
89        buffer: &'b mut [u16],
90    ) -> Result<&'b mut U16CStr> {
91        unsafe { self.FormatForDisplay(key, propvar, pdff, buffer) }?;
92        Ok(U16CStr::from_slice_truncate_mut(buffer).unwrap())
93    }
94
95    fn format_for_display_alloc(
96        &self,
97        key: &PROPERTYKEY,
98        propvar: &PROPVARIANT,
99        pdff: PROPDESC_FORMAT_FLAGS,
100    ) -> Result<PWSTR> {
101        unsafe { self.FormatForDisplayAlloc(key, propvar, pdff) }
102    }
103}
104
105#[cfg(test)]
106mod tests {
107    use super::*;
108
109    use windows::Win32::Storage::EnhancedStorage::PKEY_Size;
110
111    #[test]
112    fn format_for_display() {
113        let prop_system = IPropertySystem::new_init().expect("Failed to create IPropertySystem");
114        let key = PKEY_Size;
115        let propvar = PROPVARIANT::from(1024_i64);
116        let pdff = PROPDESC_FORMAT_FLAGS::default();
117
118        // Create a buffer to receive the formatted string
119        let mut buffer: [u16; 256] = [0; 256];
120
121        let display_value = prop_system
122            .format_for_display(&key, &propvar, pdff, &mut buffer)
123            .expect("Failed to format for display");
124        assert_eq!(display_value, widestring::u16cstr!("1.00 KB"));
125    }
126
127    #[test]
128    fn format_for_display_alloc_with_pkey_size() {
129        let prop_system = IPropertySystem::new_init().expect("Failed to create IPropertySystem");
130
131        // Use PKEY_Size as the key
132        let key = PKEY_Size;
133
134        // Create a PROPVARIANT from a number (e.g., 1024 for 1KB)
135        let propvar = PROPVARIANT::from(1024_i64);
136
137        // Format with default flags
138        let pdff = PROPDESC_FORMAT_FLAGS::default();
139
140        let result = prop_system
141            .format_for_display_alloc(&key, &propvar, pdff)
142            .expect("Failed to format for display");
143
144        // Convert PWSTR to Rust string for verification
145        let display_value: String =
146            unsafe { result.to_string() }.expect("Failed to convert PWSTR to String");
147
148        // Assert with expected format (e.g., "1 KB", "1,024 bytes", etc.)
149        assert!(!display_value.is_empty());
150        // The exact format depends on system locale and format flags
151        // Common formats: "1 KB", "1,024 bytes", "1,024 B", etc.
152        // Log the actual format for reference
153        eprintln!("Formatted size value: {display_value}");
154
155        // For 1024 bytes, the expected format is "1.00 KB" (with default flags)
156        assert_eq!(display_value, "1.00 KB");
157    }
158
159    #[test]
160    fn format_for_display_alloc_with_various_sizes() {
161        let prop_system = IPropertySystem::new_init().expect("Failed to create IPropertySystem");
162
163        let key = PKEY_Size;
164
165        let test_cases = [
166            (1024_i64, "1.00 KB"),
167            (1024 * 1024, "1.00 MB"),
168            (1024 * 1024 * 1024, "1.00 GB"),
169        ];
170
171        for (size, expected) in test_cases {
172            let propvar = PROPVARIANT::from(size);
173            let result = prop_system
174                .format_for_display_alloc(&key, &propvar, PROPDESC_FORMAT_FLAGS::default())
175                .expect("Failed to format for display");
176
177            let display_value: String =
178                unsafe { result.to_string() }.expect("Failed to convert PWSTR");
179            assert_eq!(
180                display_value, expected,
181                "Size {size} should format to '{expected}'"
182            );
183        }
184    }
185}