Reactos
1/*
2 * PROJECT: shell32
3 * LICENSE: LGPL-2.1-or-later (https://spdx.org/licenses/LGPL-2.1-or-later)
4 * PURPOSE: Shell change notification
5 * COPYRIGHT: Copyright 2020 Katayama Hirofumi MZ (katayama.hirofumi.mz@gmail.com)
6 */
7#include "shelldesktop.h"
8#include "shlwapi_undoc.h"
9#include "CDirectoryWatcher.h"
10#include <assert.h> // for assert
11
12WINE_DEFAULT_DEBUG_CHANNEL(shcn);
13
14//////////////////////////////////////////////////////////////////////////////
15
16// notification target item
17struct CWatchItem
18{
19 UINT nRegID; // The registration ID.
20 DWORD dwUserPID; // The user PID; that is the process ID of the target window.
21 LPREGENTRY pRegEntry; // The registration entry.
22 HWND hwndBroker; // Client broker window (if any).
23 CDirectoryWatcher *pDirWatch; // for filesystem notification
24};
25
26//////////////////////////////////////////////////////////////////////////////
27// CChangeNotifyServer
28//
29// CChangeNotifyServer implements a window that handles all shell change notifications.
30// It runs in the context of explorer and specifically in the thread of the shell desktop.
31// Shell change notification api exported from shell32 forwards all their calls
32// to this window where all processing takes place.
33
34class CChangeNotifyServer :
35 public CWindowImpl<CChangeNotifyServer, CWindow, CWorkerTraits>,
36 public CComObjectRootEx<CComMultiThreadModelNoCS>,
37 public IOleWindow
38{
39public:
40 CChangeNotifyServer();
41 virtual ~CChangeNotifyServer();
42 HRESULT Initialize();
43
44 // *** IOleWindow methods ***
45 STDMETHOD(GetWindow)(HWND *lphwnd) override;
46 STDMETHOD(ContextSensitiveHelp)(BOOL fEnterMode) override;
47
48 // Message handlers
49 LRESULT OnRegister(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled);
50 LRESULT OnUnRegister(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled);
51 LRESULT OnDeliverNotification(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled);
52 LRESULT OnSuspendResume(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled);
53 LRESULT OnRemoveByPID(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled);
54 LRESULT OnDestroy(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled);
55
56 DECLARE_NOT_AGGREGATABLE(CChangeNotifyServer)
57
58 DECLARE_PROTECT_FINAL_CONSTRUCT()
59 BEGIN_COM_MAP(CChangeNotifyServer)
60 COM_INTERFACE_ENTRY_IID(IID_IOleWindow, IOleWindow)
61 END_COM_MAP()
62
63 DECLARE_WND_CLASS_EX(L"WorkerW", 0, 0)
64
65 BEGIN_MSG_MAP(CChangeNotifyServer)
66 MESSAGE_HANDLER(CN_REGISTER, OnRegister)
67 MESSAGE_HANDLER(CN_UNREGISTER, OnUnRegister)
68 MESSAGE_HANDLER(CN_DELIVER_NOTIFICATION, OnDeliverNotification)
69 MESSAGE_HANDLER(CN_SUSPEND_RESUME, OnSuspendResume)
70 MESSAGE_HANDLER(CN_UNREGISTER_PROCESS, OnRemoveByPID);
71 MESSAGE_HANDLER(WM_DESTROY, OnDestroy);
72 END_MSG_MAP()
73
74private:
75 UINT m_nNextRegID;
76 CSimpleArray<CWatchItem*> m_items;
77
78 BOOL AddItem(CWatchItem *pItem);
79 BOOL RemoveItemsByRegID(UINT nRegID);
80 BOOL RemoveItemsByProcess(DWORD dwUserPID);
81 void DestroyItem(CWatchItem *pItem, HWND *phwndBroker);
82 void DestroyAllItems();
83
84 UINT GetNextRegID();
85 BOOL DeliverNotification(HANDLE hTicket, DWORD dwOwnerPID);
86 BOOL ShouldNotify(LPDELITICKET pTicket, LPREGENTRY pRegEntry);
87};
88
89CChangeNotifyServer::CChangeNotifyServer()
90 : m_nNextRegID(INVALID_REG_ID)
91{
92}
93
94CChangeNotifyServer::~CChangeNotifyServer()
95{
96}
97
98BOOL CChangeNotifyServer::AddItem(CWatchItem *pItem)
99{
100 // find the empty room
101 for (INT i = 0; i < m_items.GetSize(); ++i)
102 {
103 if (m_items[i] == NULL)
104 {
105 // found the room, populate it
106 m_items[i] = pItem;
107 return TRUE;
108 }
109 }
110
111 // no empty room found
112 m_items.Add(pItem);
113 return TRUE;
114}
115
116void CChangeNotifyServer::DestroyItem(CWatchItem *pItem, HWND *phwndBroker)
117{
118 assert(pItem);
119
120 // destroy broker if any and if first time
121 HWND hwndBroker = pItem->hwndBroker;
122 pItem->hwndBroker = NULL;
123 if (hwndBroker && hwndBroker != *phwndBroker)
124 {
125 ::DestroyWindow(hwndBroker);
126 *phwndBroker = hwndBroker;
127 }
128
129 // request termination of pDirWatch if any
130 CDirectoryWatcher *pDirWatch = pItem->pDirWatch;
131 pItem->pDirWatch = NULL;
132 if (pDirWatch)
133 pDirWatch->RequestTermination();
134
135 // free
136 SHFree(pItem->pRegEntry);
137 delete pItem;
138}
139
140void CChangeNotifyServer::DestroyAllItems()
141{
142 for (INT i = 0; i < m_items.GetSize(); ++i)
143 {
144 if (m_items[i])
145 {
146 HWND hwndBroker = NULL;
147 DestroyItem(m_items[i], &hwndBroker);
148 m_items[i] = NULL;
149 }
150 }
151 m_items.RemoveAll();
152}
153
154BOOL CChangeNotifyServer::RemoveItemsByRegID(UINT nRegID)
155{
156 BOOL bFound = FALSE;
157 HWND hwndBroker = NULL;
158 assert(nRegID != INVALID_REG_ID);
159 for (INT i = 0; i < m_items.GetSize(); ++i)
160 {
161 if (m_items[i] && m_items[i]->nRegID == nRegID)
162 {
163 bFound = TRUE;
164 DestroyItem(m_items[i], &hwndBroker);
165 m_items[i] = NULL;
166 }
167 }
168 return bFound;
169}
170
171BOOL CChangeNotifyServer::RemoveItemsByProcess(DWORD dwUserPID)
172{
173 BOOL bFound = FALSE;
174 HWND hwndBroker = NULL;
175 assert(dwUserPID != 0);
176 for (INT i = 0; i < m_items.GetSize(); ++i)
177 {
178 if (m_items[i] && m_items[i]->dwUserPID == dwUserPID)
179 {
180 bFound = TRUE;
181 DestroyItem(m_items[i], &hwndBroker);
182 m_items[i] = NULL;
183 }
184 }
185 return bFound;
186}
187
188// create a CDirectoryWatcher from a REGENTRY
189static CDirectoryWatcher *
190CreateDirectoryWatcherFromRegEntry(LPREGENTRY pRegEntry)
191{
192 if (pRegEntry->ibPidl == 0)
193 return NULL;
194
195 // get the path
196 WCHAR szPath[MAX_PATH];
197 LPITEMIDLIST pidl = (LPITEMIDLIST)((LPBYTE)pRegEntry + pRegEntry->ibPidl);
198 if (!SHGetPathFromIDListW(pidl, szPath) || !PathIsDirectoryW(szPath))
199 return NULL;
200
201 // create a CDirectoryWatcher
202 CDirectoryWatcher *pDirectoryWatcher =
203 CDirectoryWatcher::Create(pRegEntry->hwnd, szPath, pRegEntry->fRecursive);
204 if (pDirectoryWatcher == NULL)
205 return NULL;
206
207 return pDirectoryWatcher;
208}
209
210// Message CN_REGISTER: Register the registration entry.
211// wParam: The handle of registration entry.
212// lParam: The owner PID of registration entry.
213// return: TRUE if successful.
214LRESULT CChangeNotifyServer::OnRegister(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
215{
216 TRACE("OnRegister(%p, %u, %p, %p)\n", m_hWnd, uMsg, wParam, lParam);
217
218 // lock the registration entry
219 HANDLE hRegEntry = (HANDLE)wParam;
220 DWORD dwOwnerPID = (DWORD)lParam;
221 LPREGENTRY pRegEntry = (LPREGENTRY)SHLockSharedEx(hRegEntry, dwOwnerPID, TRUE);
222 if (pRegEntry == NULL || pRegEntry->dwMagic != REGENTRY_MAGIC)
223 {
224 ERR("pRegEntry is invalid\n");
225 SHUnlockShared(pRegEntry);
226 return FALSE;
227 }
228
229 // update registration ID if necessary
230 if (pRegEntry->nRegID == INVALID_REG_ID)
231 pRegEntry->nRegID = GetNextRegID();
232
233 TRACE("pRegEntry->nRegID: %u\n", pRegEntry->nRegID);
234
235 // get the user PID; that is the process ID of the target window
236 DWORD dwUserPID;
237 GetWindowThreadProcessId(pRegEntry->hwnd, &dwUserPID);
238
239 // get broker if any
240 HWND hwndBroker = pRegEntry->hwndBroker;
241
242 // clone the registration entry
243 LPREGENTRY pNewEntry = (LPREGENTRY)SHAlloc(pRegEntry->cbSize);
244 if (pNewEntry == NULL)
245 {
246 ERR("Out of memory\n");
247 pRegEntry->nRegID = INVALID_REG_ID;
248 SHUnlockShared(pRegEntry);
249 return FALSE;
250 }
251 CopyMemory(pNewEntry, pRegEntry, pRegEntry->cbSize);
252
253 // create a directory watch if necessary
254 CDirectoryWatcher *pDirWatch = NULL;
255 if (pRegEntry->ibPidl && (pRegEntry->fSources & SHCNRF_InterruptLevel))
256 {
257 pDirWatch = CreateDirectoryWatcherFromRegEntry(pRegEntry);
258 if (pDirWatch && !pDirWatch->RequestAddWatcher())
259 {
260 ERR("RequestAddWatcher failed: %u\n", pRegEntry->nRegID);
261 pRegEntry->nRegID = INVALID_REG_ID;
262 SHUnlockShared(pRegEntry);
263 delete pDirWatch;
264 return FALSE;
265 }
266 }
267
268 // unlock the registry entry
269 SHUnlockShared(pRegEntry);
270
271 // add an item
272 CWatchItem *pItem = new CWatchItem { m_nNextRegID, dwUserPID, pNewEntry, hwndBroker, pDirWatch };
273 return AddItem(pItem);
274}
275
276// Message CN_UNREGISTER: Unregister registration entries.
277// wParam: The registration ID.
278// lParam: Ignored.
279// return: TRUE if successful.
280LRESULT CChangeNotifyServer::OnUnRegister(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
281{
282 TRACE("OnUnRegister(%p, %u, %p, %p)\n", m_hWnd, uMsg, wParam, lParam);
283
284 // validate registration ID
285 UINT nRegID = (UINT)wParam;
286 if (nRegID == INVALID_REG_ID)
287 {
288 ERR("INVALID_REG_ID\n");
289 return FALSE;
290 }
291
292 // remove it
293 return RemoveItemsByRegID(nRegID);
294}
295
296// Message CN_DELIVER_NOTIFICATION: Perform a delivery.
297// wParam: The handle of delivery ticket.
298// lParam: The owner PID of delivery ticket.
299// return: TRUE if necessary.
300LRESULT CChangeNotifyServer::OnDeliverNotification(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
301{
302 TRACE("OnDeliverNotification(%p, %u, %p, %p)\n", m_hWnd, uMsg, wParam, lParam);
303
304 HANDLE hTicket = (HANDLE)wParam;
305 DWORD dwOwnerPID = (DWORD)lParam;
306
307 // do delivery
308 BOOL ret = DeliverNotification(hTicket, dwOwnerPID);
309
310 // free the ticket
311 SHFreeShared(hTicket, dwOwnerPID);
312 return ret;
313}
314
315// Message CN_SUSPEND_RESUME: Suspend or resume the change notification.
316// (specification is unknown)
317LRESULT CChangeNotifyServer::OnSuspendResume(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
318{
319 TRACE("OnSuspendResume\n");
320
321 // FIXME
322 return FALSE;
323}
324
325// Message CN_UNREGISTER_PROCESS: Remove registration entries by PID.
326// wParam: The user PID.
327// lParam: Ignored.
328// return: Zero.
329LRESULT CChangeNotifyServer::OnRemoveByPID(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
330{
331 DWORD dwUserPID = (DWORD)wParam;
332 RemoveItemsByProcess(dwUserPID);
333 return 0;
334}
335
336LRESULT CChangeNotifyServer::OnDestroy(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
337{
338 DestroyAllItems();
339 CDirectoryWatcher::RequestAllWatchersTermination();
340 return 0;
341}
342
343// get next valid registration ID
344UINT CChangeNotifyServer::GetNextRegID()
345{
346 m_nNextRegID++;
347 if (m_nNextRegID == INVALID_REG_ID)
348 m_nNextRegID++;
349 return m_nNextRegID;
350}
351
352// This function is called from CChangeNotifyServer::OnDeliverNotification.
353// The function notifies to the registration entries that should be notified.
354BOOL CChangeNotifyServer::DeliverNotification(HANDLE hTicket, DWORD dwOwnerPID)
355{
356 TRACE("DeliverNotification(%p, %p, 0x%lx)\n", m_hWnd, hTicket, dwOwnerPID);
357
358 // lock the delivery ticket
359 LPDELITICKET pTicket = (LPDELITICKET)SHLockSharedEx(hTicket, dwOwnerPID, FALSE);
360 if (pTicket == NULL || pTicket->dwMagic != DELITICKET_MAGIC)
361 {
362 ERR("pTicket is invalid\n");
363 SHUnlockShared(pTicket);
364 return FALSE;
365 }
366
367 // for all items
368 for (INT i = 0; i < m_items.GetSize(); ++i)
369 {
370 if (m_items[i] == NULL)
371 continue;
372
373 LPREGENTRY pRegEntry = m_items[i]->pRegEntry;
374 if (pRegEntry == NULL || pRegEntry->dwMagic != REGENTRY_MAGIC)
375 {
376 ERR("pRegEntry is invalid\n");
377 continue;
378 }
379
380 // should we notify for it?
381 BOOL bNotify = ShouldNotify(pTicket, pRegEntry);
382 if (bNotify)
383 {
384 // do notify
385 TRACE("Notifying: %p, 0x%x, %p, %lu\n",
386 pRegEntry->hwnd, pRegEntry->uMsg, hTicket, dwOwnerPID);
387 SendMessageW(pRegEntry->hwnd, pRegEntry->uMsg, (WPARAM)hTicket, dwOwnerPID);
388 TRACE("GetLastError(): %ld\n", ::GetLastError());
389 }
390 }
391
392 // unlock the ticket
393 SHUnlockShared(pTicket);
394
395 return TRUE;
396}
397
398BOOL CChangeNotifyServer::ShouldNotify(LPDELITICKET pTicket, LPREGENTRY pRegEntry)
399{
400#define RETURN(x) do { \
401 TRACE("ShouldNotify return %d\n", (x)); \
402 return (x); \
403} while (0)
404
405 if (pTicket->wEventId & SHCNE_INTERRUPT)
406 {
407 if (!(pRegEntry->fSources & SHCNRF_InterruptLevel))
408 RETURN(FALSE);
409 if (!pRegEntry->ibPidl)
410 RETURN(FALSE);
411 }
412 else
413 {
414 if (!(pRegEntry->fSources & SHCNRF_ShellLevel))
415 RETURN(FALSE);
416 }
417
418 if (!(pTicket->wEventId & pRegEntry->fEvents))
419 RETURN(FALSE);
420
421 LPITEMIDLIST pidl = NULL, pidl1 = NULL, pidl2 = NULL;
422 if (pRegEntry->ibPidl)
423 pidl = (LPITEMIDLIST)((LPBYTE)pRegEntry + pRegEntry->ibPidl);
424 if (pTicket->ibOffset1)
425 pidl1 = (LPITEMIDLIST)((LPBYTE)pTicket + pTicket->ibOffset1);
426 if (pTicket->ibOffset2)
427 pidl2 = (LPITEMIDLIST)((LPBYTE)pTicket + pTicket->ibOffset2);
428
429 if (pidl == NULL || (pTicket->wEventId & SHCNE_GLOBALEVENTS))
430 RETURN(TRUE);
431
432 if (pRegEntry->fRecursive)
433 {
434 if (ILIsParent(pidl, pidl1, FALSE) ||
435 (pidl2 && ILIsParent(pidl, pidl2, FALSE)))
436 {
437 RETURN(TRUE);
438 }
439 }
440 else
441 {
442 if (ILIsEqual(pidl, pidl1) ||
443 ILIsParent(pidl, pidl1, TRUE) ||
444 (pidl2 && ILIsParent(pidl, pidl2, TRUE)))
445 {
446 RETURN(TRUE);
447 }
448 }
449
450 RETURN(FALSE);
451#undef RETURN
452}
453
454HRESULT WINAPI CChangeNotifyServer::GetWindow(HWND* phwnd)
455{
456 if (!phwnd)
457 return E_INVALIDARG;
458 *phwnd = m_hWnd;
459 return S_OK;
460}
461
462HRESULT WINAPI CChangeNotifyServer::ContextSensitiveHelp(BOOL fEnterMode)
463{
464 return E_NOTIMPL;
465}
466
467HRESULT CChangeNotifyServer::Initialize()
468{
469 // This is called by CChangeNotifyServer_CreateInstance right after instantiation.
470 HWND hwnd = SHCreateDefaultWorkerWindow();
471 if (!hwnd)
472 return E_FAIL;
473 SubclassWindow(hwnd);
474 return S_OK;
475}
476
477HRESULT CChangeNotifyServer_CreateInstance(REFIID riid, void **ppv)
478{
479 return ShellObjectCreatorInit<CChangeNotifyServer>(riid, ppv);
480}