ib_shell_item/item/mod.rs
1use std::cmp;
2
3use num_enum::TryFromPrimitive;
4use widestring::U16CString;
5use windows::{
6 Win32::{
7 System::Com::CoTaskMemFree,
8 UI::Shell::{
9 SHCreateItemFromIDList, SHCreateItemFromParsingName, SIGDN,
10 SIGDN_DESKTOPABSOLUTEEDITING, SIGDN_DESKTOPABSOLUTEPARSING, SIGDN_FILESYSPATH,
11 SIGDN_NORMALDISPLAY, SIGDN_PARENTRELATIVE, SIGDN_PARENTRELATIVEEDITING,
12 SIGDN_PARENTRELATIVEFORADDRESSBAR, SIGDN_PARENTRELATIVEFORUI,
13 SIGDN_PARENTRELATIVEPARSING, SIGDN_URL,
14 },
15 },
16 core::{PCWSTR, Result},
17};
18
19#[cfg(feature = "prop")]
20pub mod item2;
21
22pub use windows::Win32::UI::Shell::IShellItem;
23
24use crate::{id_list::AbsoluteIDList, prop::attribute::ItemAttributes};
25
26/// Requests the form of an item's display name to retrieve through [`IShellItem::GetDisplayName`] and [`SHGetNameFromIDList`].
27///
28/// [SIGDN (shobjidl_core.h) - Win32 apps | Microsoft Learn](https://learn.microsoft.com/en-us/windows/win32/api/shobjidl_core/ne-shobjidl_core-sigdn)
29#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, TryFromPrimitive)]
30#[repr(i32)]
31pub enum ShellItemDisplayName {
32 /// 0x00000000. Returns the display name relative to the parent folder.
33 ///
34 /// In UI this name is generally ideal for display to the user.
35 NormalDisplay = SIGDN_NORMALDISPLAY.0,
36
37 /// 0x80018001. Returns the parsing name relative to the parent folder.
38 ///
39 /// This name is not suitable for use in UI.
40 ParentRelativeParsing = SIGDN_PARENTRELATIVEPARSING.0,
41
42 /// 0x80028000. Returns the parsing name relative to the desktop.
43 ///
44 /// This name is not suitable for use in UI.
45 DesktopAbsoluteParsing = SIGDN_DESKTOPABSOLUTEPARSING.0,
46
47 /// 0x80031001. Returns the editing name relative to the parent folder.
48 ///
49 /// In UI this name is suitable for display to the user.
50 ParentRelativeEditing = SIGDN_PARENTRELATIVEEDITING.0,
51
52 /// 0x8004c000. Returns the editing name relative to the desktop.
53 ///
54 /// In UI this name is suitable for display to the user.
55 DesktopAbsoluteEditing = SIGDN_DESKTOPABSOLUTEEDITING.0,
56
57 /// 0x80058000. Returns the item's file system path, if it has one.
58 ///
59 /// Only items that report [`SFGAO_FILESYSTEM`] have a file system path. When an item does not have a file system path, a call to [`IShellItem::GetDisplayName`] on that item will fail.
60 ///
61 /// In UI this name is suitable for display to the user in some cases, but note that it might not be specified for all items.
62 ///
63 /// e.g. `C:\Users\Ib`
64 FileSystemPath = SIGDN_FILESYSPATH.0,
65
66 /// 0x80068000. Returns the item's URL, if it has one.
67 ///
68 /// Some items do not have a URL, and in those cases a call to [`IShellItem::GetDisplayName`] will fail.
69 ///
70 /// This name is suitable for display to the user in some cases, but note that it might not be specified for all items.
71 Url = SIGDN_URL.0,
72
73 /// 0x8007c001. Returns the path relative to the parent folder in a friendly format as displayed in an address bar.
74 ///
75 /// This name is suitable for display to the user.
76 ParentRelativeForAddressBar = SIGDN_PARENTRELATIVEFORADDRESSBAR.0,
77
78 /// 0x80080001. Returns the path relative to the parent folder.
79 ParentRelative = SIGDN_PARENTRELATIVE.0,
80
81 /// 0x80094001. Introduced in Windows 8. Returns the path relative to the parent folder for UI purposes.
82 ParentRelativeForUI = SIGDN_PARENTRELATIVEFORUI.0,
83}
84
85impl ShellItemDisplayName {
86 /// Returns `true` if this display name is meant for parsing.
87 pub fn is_for_parse(&self) -> bool {
88 use ShellItemDisplayName::*;
89 matches!(
90 self,
91 ParentRelativeParsing | DesktopAbsoluteParsing | FileSystemPath | Url | ParentRelative
92 )
93 }
94
95 /// Returns `true` if this display name is meant for displaying in UI.
96 pub fn is_for_display(&self) -> bool {
97 use ShellItemDisplayName::*;
98 matches!(
99 self,
100 NormalDisplay | ParentRelativeForAddressBar | ParentRelativeForUI
101 )
102 }
103
104 /// Returns `true` if this display name is meant for editing in UI.
105 pub fn is_for_edit(&self) -> bool {
106 use ShellItemDisplayName::*;
107 matches!(self, ParentRelativeEditing | DesktopAbsoluteEditing)
108 }
109}
110
111/// [IShellItem (shobjidl_core.h) - Win32 apps | Microsoft Learn](https://learn.microsoft.com/en-us/windows/win32/api/shobjidl_core/nn-shobjidl_core-ishellitem)
112pub trait ShellItem {
113 /// [SHCreateItemFromParsingName function (shobjidl_core.h)](https://learn.microsoft.com/en-us/windows/win32/api/shobjidl_core/nf-shobjidl_core-shcreateitemfromparsingname)
114 ///
115 /// Although not documented, this requires `CoInitialize()`.
116 fn from_path_w(path: PCWSTR) -> Result<IShellItem> {
117 unsafe { SHCreateItemFromParsingName::<_, _, IShellItem>(path, None) }
118 }
119
120 /// [SHCreateItemFromIDList function (shobjidl_core.h) - Win32 apps | Microsoft Learn](https://learn.microsoft.com/en-us/windows/win32/api/shobjidl_core/nf-shobjidl_core-shcreateitemfromidlist)
121 ///
122 /// ## References
123 /// https://github.com/Hau-Hau/restart-explorer/blob/230ed6dd78ac656a86e07310c3afc62f03057a36/src/infrastructure/windows_os/persist_id_list.rs
124 #[doc(alias = "from_pidl")]
125 fn from_id_list(id_list: &AbsoluteIDList) -> Result<IShellItem> {
126 unsafe { SHCreateItemFromIDList::<IShellItem>(id_list.0) }
127 }
128
129 /// [IShellItem::GetDisplayName (shobjidl_core.h) - Win32 apps | Microsoft Learn](https://learn.microsoft.com/en-us/windows/win32/api/shobjidl_core/nf-shobjidl_core-ishellitem-getdisplayname)
130 fn get_display_name(&self, name: ShellItemDisplayName) -> Result<U16CString>;
131
132 /// - `flags`: [`SICHINTF_*` (shobjidl_core.h)](https://learn.microsoft.com/en-us/windows/win32/api/shobjidl_core/ne-shobjidl_core-_sichintf)
133 ///
134 /// Different from [`ShellFolder::compare_ids()`], specifying the column doesn't work.
135 /// (Cleared by `& 0xf0000000`)
136 ///
137 /// Internally, this usually calls [`ShellFolder::compare_ids()`].
138 ///
139 /// [IShellItem::Compare (shobjidl_core.h)](https://learn.microsoft.com/en-us/windows/win32/api/shobjidl_core/nf-shobjidl_core-ishellitem-compare)
140 ///
141 /// ## Returns
142 /// Although the doc just says
143 /// "If the two items are the same this parameter equals zero; if they are different the parameter is nonzero."
144 /// The sign actually defines the order like [`ShellFolder::compare_ids()`] does.
145 ///
146 /// [`ShellFolder::compare_ids()`]: crate::folder::ShellFolder::compare_ids
147 fn compare(&self, psi: &IShellItem, flags: u32) -> Result<cmp::Ordering>;
148
149 /// [IShellItem::GetAttributes (shobjidl_core.h)](https://learn.microsoft.com/en-us/windows/win32/api/shobjidl_core/nf-shobjidl_core-ishellitem-getattributes)
150 fn get_attributes(&self, mask: ItemAttributes) -> Result<ItemAttributes>;
151
152 /// Tests if the item is a Shell folder (not necessarily a file system directory).
153 ///
154 /// This can also be implemented via [`ShellItem::get_display_name()`].
155 /// But it's probably slower as attributes are already stored in the ID list.
156 fn is_folder(&self) -> bool {
157 self.get_attributes(ItemAttributes::Folder)
158 .is_ok_and(|attrs| attrs.contains(ItemAttributes::Folder))
159 }
160}
161
162impl ShellItem for IShellItem {
163 /// Ref: https://github.com/Hau-Hau/restart-explorer/blob/230ed6dd78ac656a86e07310c3afc62f03057a36/src/infrastructure/windows_os/shell_item.rs
164 fn get_display_name(&self, name: ShellItemDisplayName) -> Result<U16CString> {
165 let name = unsafe { self.GetDisplayName(SIGDN(name as i32)) }?;
166 let name_u16 = unsafe { U16CString::from_ptr_str(name.0) };
167 unsafe { CoTaskMemFree(Some(name.0 as _)) };
168 Ok(name_u16)
169 }
170
171 fn get_attributes(&self, mask: ItemAttributes) -> Result<ItemAttributes> {
172 unsafe { self.GetAttributes(mask.into()).map(Into::into) }
173 }
174
175 fn compare(&self, psi: &IShellItem, hint: u32) -> Result<cmp::Ordering> {
176 let order = unsafe { self.Compare(psi, hint)? };
177 Ok(order.cmp(&0))
178 }
179}
180
181#[cfg(test)]
182mod tests {
183 use super::*;
184 use windows::core::w;
185
186 use crate::{init, prop::column::FSColumn};
187
188 #[test]
189 fn compare() {
190 _ = init();
191 let windows = IShellItem::from_path_w(w!(r"C:\Windows")).unwrap();
192 let users = IShellItem::from_path_w(w!(r"C:\Users")).unwrap();
193
194 let result = windows.compare(&users, 0).unwrap();
195 assert_eq!(result, cmp::Ordering::Greater);
196 assert_eq!(users.compare(&windows, 0).unwrap(), cmp::Ordering::Less);
197 }
198
199 #[test]
200 fn compare_size() {
201 _ = init();
202 let explorer = IShellItem::from_path_w(w!(r"C:\Windows\explorer.exe")).unwrap();
203 let notepad = IShellItem::from_path_w(w!(r"C:\Windows\notepad.exe")).unwrap();
204
205 let result = explorer.compare(¬epad, 0).unwrap();
206 assert_eq!(result, cmp::Ordering::Less);
207
208 // Different from [`ShellFolder::compare_ids()`], specifying the column doesn't work.
209 let result = explorer.compare(¬epad, FSColumn::Size as u32).unwrap();
210 assert_eq!(result, cmp::Ordering::Less);
211 }
212
213 #[test]
214 fn compare_same() {
215 _ = init();
216 let windows = IShellItem::from_path_w(w!(r"C:\Windows")).unwrap();
217 let windows2 = IShellItem::from_path_w(w!(r"C:\Windows")).unwrap();
218
219 let result = windows.compare(&windows2, 0).unwrap();
220 assert_eq!(result, cmp::Ordering::Equal);
221 }
222}