Skip to main content

ib_shell_item/prop/
store.rs

1use windows::{
2    Win32::{
3        Foundation::PROPERTYKEY,
4        Storage::EnhancedStorage::{
5            PKEY_ItemPathDisplay, PKEY_ParsingName, PKEY_ParsingPath, PKEY_Size,
6        },
7        System::Com::StructuredStorage::PROPVARIANT,
8        UI::Shell::{IShellItem2, PropertiesSystem::GETPROPERTYSTOREFLAGS},
9    },
10    core::{PCWSTR, Result},
11};
12
13pub use windows::Win32::UI::Shell::PropertiesSystem::IPropertyStore;
14
15use crate::item2::ShellItem2;
16
17/// [IPropertyStore (propsys.h) - Win32 apps | Microsoft Learn](https://learn.microsoft.com/en-us/windows/win32/api/propsys/nn-propsys-ipropertystore)
18/**
19## Thread safety
20In Explorer on Windows 11 24H2, [`PropertyStore::get_value()`] may be called from different threads
21for different Explorer windows of the same process.
22*/
23pub trait PropertyStore {
24    /// [SHCreateItemFromParsingName function (shobjidl_core.h)](https://learn.microsoft.com/en-us/windows/win32/api/shobjidl_core/nf-shobjidl_core-shcreateitemfromparsingname)
25    ///
26    /// Although not documented, this requires `CoInitialize()`.
27    fn from_path_w(path: PCWSTR, flags: GETPROPERTYSTOREFLAGS) -> Result<IPropertyStore>;
28
29    /// [IPropertyStore::GetValue (propsys.h)](https://learn.microsoft.com/en-us/windows/win32/api/propsys/nf-propsys-ipropertystore-getvalue)
30    ///
31    /// ## Returns
32    /// [PROPVARIANT (propidlbase.h)](https://learn.microsoft.com/en-us/windows/win32/api/propidlbase/ns-propidlbase-propvariant)
33    fn get_value(&self, key: &PROPERTYKEY) -> Result<PROPVARIANT>;
34
35    /// Get the size of the item.
36    ///
37    /// ## Returns
38    /// Of type [`VT_UI8`].
39    fn get_size(&self) -> Result<PROPVARIANT>;
40
41    /// Get the size of the item.
42    fn get_size_u64(&self) -> Result<u64>;
43
44    /// Get the parsing (non-UI) path of the item.
45    ///
46    /// [System.ParsingPath](https://learn.microsoft.com/en-us/windows/win32/properties/props-system-parsingpath)
47    ///
48    /// [`PKEY_ItemPathDisplay`] is UI path, [`PKEY_ItemUrl`] is broken, but [`PKEY_ParsingPath`] seems fixed.
49    ///
50    /// Ref: [Vista: How to retrieve the pIDL of a localized path?](https://microsoft.public.platformsdk.shell.narkive.com/Lt8tnJRx/vista-how-to-retrieve-the-pidl-of-a-localized-path)
51    ///
52    /// ## Examples
53    /**
54    ```
55    use ib_shell_item::{
56        init,
57        prop::store::{IPropertyStore, PropertyStore},
58    };
59    use windows::core::w;
60    _ = init();
61
62    let prop_store = IPropertyStore::from_path_w(
63        w!(r"C:\Users\Public\Documents\desktop.ini"),
64        Default::default(),
65    )
66    .unwrap();
67
68    let path = prop_store.get_parsing_path().unwrap().to_string();
69    assert_eq!(path, r"C:\Users\Public\Documents\desktop.ini");
70
71    let path = prop_store.get_item_path_display().unwrap().to_string();
72    assert_eq!(path, r"C:\Users\Public\Public Documents\desktop.ini");
73    ```
74    */
75    fn get_parsing_path(&self) -> Result<PROPVARIANT>;
76
77    /// Get the parsing name of the item (e.g., "explorer.exe").
78    fn get_parsing_name(&self) -> Result<PROPVARIANT>;
79
80    /// Get the display (UI) path of the item.
81    ///
82    /// [System.ItemPathDisplay](https://learn.microsoft.com/en-us/windows/win32/properties/props-system-itempathdisplay)
83
84    /// ## Examples
85    /**
86    ```
87    use ib_shell_item::{
88        init,
89        prop::store::{IPropertyStore, PropertyStore},
90    };
91    use windows::core::w;
92    _ = init();
93
94    let prop_store = IPropertyStore::from_path_w(
95        w!(r"C:\Users\Public\Documents\desktop.ini"),
96        Default::default(),
97    )
98    .unwrap();
99
100    let path = prop_store.get_parsing_path().unwrap().to_string();
101    assert_eq!(path, r"C:\Users\Public\Documents\desktop.ini");
102
103    let path = prop_store.get_item_path_display().unwrap().to_string();
104    assert_eq!(path, r"C:\Users\Public\Public Documents\desktop.ini");
105    ```
106    */
107    fn get_item_path_display(&self) -> Result<PROPVARIANT>;
108}
109
110impl PropertyStore for IPropertyStore {
111    fn from_path_w(path: PCWSTR, flags: GETPROPERTYSTOREFLAGS) -> Result<IPropertyStore> {
112        IShellItem2::from_path_w(path)?.get_property_store(flags)
113    }
114
115    fn get_value(&self, key: &PROPERTYKEY) -> Result<PROPVARIANT> {
116        unsafe { self.GetValue(key) }
117    }
118
119    fn get_size(&self) -> Result<PROPVARIANT> {
120        self.get_value(&PKEY_Size)
121    }
122
123    fn get_size_u64(&self) -> Result<u64> {
124        (&self.get_size()?).try_into()
125    }
126
127    fn get_parsing_path(&self) -> Result<PROPVARIANT> {
128        self.get_value(&PKEY_ParsingPath)
129    }
130
131    fn get_parsing_name(&self) -> Result<PROPVARIANT> {
132        self.get_value(&PKEY_ParsingName)
133    }
134
135    fn get_item_path_display(&self) -> Result<PROPVARIANT> {
136        self.get_value(&PKEY_ItemPathDisplay)
137    }
138}
139
140#[cfg(test)]
141mod tests {
142    use std::assert_matches;
143    use windows::{
144        Win32::Storage::EnhancedStorage::{
145            PKEY_ImageParsingName, PKEY_ItemUrl, PKEY_Link_TargetParsingPath,
146        },
147        core::w,
148    };
149
150    use super::*;
151
152    use crate::{id_list::AbsoluteIDList, init};
153
154    #[test]
155    fn get_value() {
156        _ = init();
157
158        let prop_store =
159            IPropertyStore::from_path_w(w!(r"C:\Windows\explorer.exe"), Default::default())
160                .expect("Failed to create property store from path");
161
162        // Verify we can extract the size value
163        let size: u64 = prop_store.get_size_u64().unwrap();
164        dbg!(size);
165        assert!(size > 0);
166
167        let name = prop_store.get_parsing_name().unwrap().to_string();
168        assert_eq!(name, "explorer.exe");
169
170        let name = prop_store
171            .get_value(&PKEY_ImageParsingName)
172            .unwrap()
173            .to_string();
174        assert_eq!(name, "");
175
176        let path = prop_store.get_parsing_path().unwrap().to_string();
177        assert_eq!(path, r"C:\Windows\explorer.exe");
178
179        let path = prop_store.get_item_path_display().unwrap().to_string();
180        assert_eq!(path, r"C:\Windows\explorer.exe");
181
182        let url = prop_store.get_value(&PKEY_ItemUrl).unwrap().to_string();
183        assert_eq!(url, "");
184
185        let path = prop_store
186            .get_value(&PKEY_Link_TargetParsingPath)
187            .unwrap()
188            .to_string();
189        assert_eq!(path, "");
190    }
191
192    #[test]
193    fn get_parsing_path() {
194        _ = init();
195
196        let prop_store = IPropertyStore::from_path_w(
197            w!(r"C:\Users\Public\Documents\desktop.ini"),
198            Default::default(),
199        )
200        .unwrap();
201
202        let path = prop_store.get_parsing_path().unwrap().to_string();
203        assert_eq!(path, r"C:\Users\Public\Documents\desktop.ini");
204
205        let path = prop_store.get_item_path_display().unwrap().to_string();
206        assert_eq!(path, r"C:\Users\Public\Public Documents\desktop.ini");
207
208        assert_matches!(AbsoluteIDList::from_object(&prop_store), Err(_));
209    }
210}