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}