1use num_enum::{
4 FromPrimitive,
5 IntoPrimitive,
6};
7use windows::Win32::UI::Shell::Common::ITEMIDLIST;
8use windows::Win32::UI::Shell::{
9 ILCreateFromPathW,
10 SHCNRF_InterruptLevel,
11 SHCNRF_NewDelivery,
12 SHCNRF_RecursiveInterrupt,
13 SHCNRF_ShellLevel,
14 SHChangeNotification_Lock,
15 SHChangeNotification_Unlock,
16 SHChangeNotify,
17 SHChangeNotifyDeregister,
18 SHChangeNotifyEntry,
19 SHChangeNotifyRegister,
20 SHGetPathFromIDListEx,
21 SHCNE_ASSOCCHANGED,
22 SHCNE_CREATE,
23 SHCNE_DELETE,
24 SHCNE_ID,
25 SHCNE_MKDIR,
26 SHCNE_RENAMEFOLDER,
27 SHCNE_RENAMEITEM,
28 SHCNE_RMDIR,
29 SHCNE_UPDATEDIR,
30 SHCNE_UPDATEITEM,
31 SHCNF_IDLIST,
32};
33use windows::Win32::UI::WindowsAndMessaging::WM_APP;
34
35use std::cell::Cell;
36use std::ops::{
37 BitOr,
38 BitOrAssign,
39};
40use std::path::{
41 Path,
42 PathBuf,
43};
44use std::{
45 io,
46 ptr,
47};
48use windows::Win32::Foundation::{
49 HANDLE,
50 HWND,
51 LPARAM,
52 WPARAM,
53};
54
55use crate::com::ComTaskMemory;
56use crate::internal::{
57 CustomAutoDrop,
58 ReturnValue,
59};
60use crate::messaging::ThreadMessageLoop;
61use crate::string::{
62 max_path_extend,
63 ZeroTerminatedWideString,
64};
65use crate::ui::messaging::WindowMessageListener;
66use crate::ui::{
67 Window,
68 WindowClass,
69 WindowClassAppearance,
70 WindowHandle,
71};
72
73#[allow(dead_code)]
74#[derive(Clone, Debug)]
75pub(crate) struct PathChangeEvent {
76 pub event: FsChangeEvent,
77 pub path_1: Option<PathBuf>,
78 pub path_2: Option<PathBuf>,
79}
80
81impl Default for PathChangeEvent {
82 fn default() -> Self {
83 Self {
84 event: FsChangeEvent::Other(0),
85 path_1: None,
86 path_2: None,
87 }
88 }
89}
90
91#[derive(IntoPrimitive, FromPrimitive, Copy, Clone, Eq, PartialEq, Debug)]
92#[repr(u32)]
93pub(crate) enum FsChangeEvent {
94 ItemCreated = SHCNE_CREATE.0,
95 ItemRenamed = SHCNE_RENAMEITEM.0,
96 ItemUpdated = SHCNE_UPDATEITEM.0,
97 ItemDeleted = SHCNE_DELETE.0,
98 FolderCreated = SHCNE_MKDIR.0,
99 FolderRenamed = SHCNE_RENAMEFOLDER.0,
100 FolderUpdated = SHCNE_UPDATEDIR.0,
101 FolderDeleted = SHCNE_RMDIR.0,
102 #[num_enum(catch_all)]
103 Other(u32),
104}
105
106impl BitOr for FsChangeEvent {
107 type Output = FsChangeEvent;
108
109 fn bitor(self, rhs: Self) -> Self::Output {
110 Self::from(u32::from(self) | u32::from(rhs))
111 }
112}
113
114impl BitOrAssign for FsChangeEvent {
115 fn bitor_assign(&mut self, rhs: Self) {
116 *self = *self | rhs
117 }
118}
119
120impl From<FsChangeEvent> for SHCNE_ID {
121 fn from(value: FsChangeEvent) -> Self {
122 SHCNE_ID(value.into())
123 }
124}
125
126pub(crate) struct MonitoredPath<'a> {
127 pub path: &'a Path,
128 pub recursive: bool,
129}
130
131#[allow(dead_code)]
132pub(crate) fn monitor_path_changes<F>(
133 monitored_paths: &[MonitoredPath],
134 event_type: FsChangeEvent,
135 mut callback: F,
136) -> io::Result<()>
137where
138 F: FnMut(&PathChangeEvent) -> io::Result<()>,
139{
140 #[derive(Default)]
141 struct Listener {
142 data: Cell<PathChangeEvent>,
143 }
144 impl WindowMessageListener for Listener {
145 fn handle_custom_user_message(
146 &self,
147 _window: &WindowHandle,
148 message_id: u8,
149 w_param: WPARAM,
150 l_param: LPARAM,
151 ) {
152 assert_eq!(message_id, 0);
153 let mut raw_ppp_idl = ptr::null_mut();
155 let mut raw_event = 0;
156 let lock = unsafe {
157 SHChangeNotification_Lock(
158 HANDLE(w_param.0 as *mut std::ffi::c_void),
159 l_param.0 as u32,
160 Some(&mut raw_ppp_idl),
161 Some(&mut raw_event),
162 )
163 };
164 let _unlock_guard = CustomAutoDrop {
165 value: lock,
166 drop_fn: |x| unsafe {
167 SHChangeNotification_Unlock(HANDLE(x.0))
168 .if_null_panic_else_drop("Improper lock usage");
169 },
170 };
171
172 let raw_pid_list_pair = unsafe { std::slice::from_raw_parts(raw_ppp_idl, 2) };
173 let event_type = FsChangeEvent::from(raw_event as u32);
174
175 fn get_path_from_id_list(raw_id_list: &ITEMIDLIST) -> PathBuf {
176 let mut raw_path_buffer: ZeroTerminatedWideString =
177 ZeroTerminatedWideString(vec![0; 32000]);
178 unsafe {
179 SHGetPathFromIDListEx(
181 raw_id_list,
182 raw_path_buffer.0.as_mut_slice(),
183 Default::default(),
184 )
185 .if_null_panic_else_drop("Cannot get path from ID list");
186 }
187 raw_path_buffer.to_os_string().into()
188 }
189
190 let path_1 = unsafe { raw_pid_list_pair[0].as_ref() }.map(get_path_from_id_list);
191 let path_2 = unsafe { raw_pid_list_pair[1].as_ref() }.map(get_path_from_id_list);
192 self.data.replace(PathChangeEvent {
193 event: event_type,
194 path_1,
195 path_2,
196 });
197 }
198 }
199
200 let recursive = monitored_paths.iter().any(|x| x.recursive);
202 let path_id_lists: Vec<(SHChangeNotifyEntry, ComTaskMemory<_>)> = monitored_paths
203 .iter()
204 .map(|monitored_path| {
205 let path_as_id_list: ComTaskMemory<_> = unsafe {
206 ILCreateFromPathW(
208 ZeroTerminatedWideString::from_os_str(max_path_extend(
209 monitored_path.path.as_os_str(),
210 ))
211 .as_raw_pcwstr(),
212 )
213 .into()
214 };
215 let raw_entry = SHChangeNotifyEntry {
216 pidl: path_as_id_list.0,
217 fRecursive: monitored_path.recursive.into(),
218 };
219 (raw_entry, path_as_id_list)
220 })
221 .collect();
222 let raw_entries: Vec<SHChangeNotifyEntry> = path_id_lists.iter().map(|x| x.0).collect();
223
224 let listener = Listener::default();
225
226 let window_class = WindowClass::register_new(
227 "Shell Change Listener Class",
228 WindowClassAppearance::empty(),
229 )?;
230 let window = Window::create_new(&window_class, &listener, "Shell Change Listener")?;
231 let reg_id = unsafe {
232 SHChangeNotifyRegister(
233 HWND::from(window.as_ref()),
234 SHCNRF_InterruptLevel
235 | SHCNRF_ShellLevel
236 | SHCNRF_NewDelivery
237 | if recursive {
238 SHCNRF_RecursiveInterrupt
239 } else {
240 Default::default()
241 },
242 u32::from(event_type).try_into().unwrap(),
243 WM_APP,
244 raw_entries.len().try_into().unwrap(),
245 raw_entries.as_ptr(),
246 )
247 .if_null_get_last_error()?
248 };
249 let _deregister_guard = CustomAutoDrop {
250 value: reg_id,
251 drop_fn: |x| unsafe {
252 SHChangeNotifyDeregister(*x)
253 .if_null_panic_else_drop("Notification listener not registered properly");
254 },
255 };
256
257 ThreadMessageLoop::run_thread_message_loop(|| callback(&listener.data.take()))?;
258 Ok(())
259}
260
261pub fn refresh_icon_cache() {
263 unsafe {
264 SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, None, None);
265 }
266}