ReactOS  0.4.15-dev-4869-g35a816a
CChangeNotifyServer.cpp
Go to the documentation of this file.
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 
13 
15 
16 // notification target item
17 struct 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 
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 
35  public CWindowImpl<CChangeNotifyServer, CWindow, CWorkerTraits>,
36  public CComObjectRootEx<CComMultiThreadModelNoCS>,
37  public IOleWindow
38 {
39 public:
41  virtual ~CChangeNotifyServer();
43 
44  // *** IOleWindow methods ***
45  virtual HRESULT STDMETHODCALLTYPE GetWindow(HWND *lphwnd);
47 
48  // Message handlers
49  LRESULT OnRegister(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled);
54  LRESULT OnDestroy(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled);
55 
57 
61  END_COM_MAP()
62 
63  DECLARE_WND_CLASS_EX(L"WorkerW", 0, 0)
64 
72  END_MSG_MAP()
73 
74 private:
77 
78  BOOL AddItem(CWatchItem *pItem);
80  BOOL RemoveItemsByProcess(DWORD dwUserPID);
81  void DestroyItem(CWatchItem *pItem, HWND *phwndBroker);
82  void DestroyAllItems();
83 
85  BOOL DeliverNotification(HANDLE hTicket, DWORD dwOwnerPID);
86  BOOL ShouldNotify(LPDELITICKET pTicket, LPREGENTRY pRegEntry);
87 };
88 
90  : m_nNextRegID(INVALID_REG_ID)
91 {
92 }
93 
95 {
96 }
97 
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 
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 
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 
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 
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
189 static CDirectoryWatcher *
191 {
192  if (pRegEntry->ibPidl == 0)
193  return NULL;
194 
195  // get the path
197  LPITEMIDLIST pidl = (LPITEMIDLIST)((LPBYTE)pRegEntry + pRegEntry->ibPidl);
199  return NULL;
200 
201  // create a CDirectoryWatcher
202  CDirectoryWatcher *pDirectoryWatcher = CDirectoryWatcher::Create(szPath, pRegEntry->fRecursive);
203  if (pDirectoryWatcher == NULL)
204  return NULL;
205 
206  return pDirectoryWatcher;
207 }
208 
209 // Message CN_REGISTER: Register the registration entry.
210 // wParam: The handle of registration entry.
211 // lParam: The owner PID of registration entry.
212 // return: TRUE if successful.
214 {
215  TRACE("OnRegister(%p, %u, %p, %p)\n", m_hWnd, uMsg, wParam, lParam);
216 
217  // lock the registration entry
218  HANDLE hRegEntry = (HANDLE)wParam;
219  DWORD dwOwnerPID = (DWORD)lParam;
220  LPREGENTRY pRegEntry = (LPREGENTRY)SHLockSharedEx(hRegEntry, dwOwnerPID, TRUE);
221  if (pRegEntry == NULL || pRegEntry->dwMagic != REGENTRY_MAGIC)
222  {
223  ERR("pRegEntry is invalid\n");
224  SHUnlockShared(pRegEntry);
225  return FALSE;
226  }
227 
228  // update registration ID if necessary
229  if (pRegEntry->nRegID == INVALID_REG_ID)
230  pRegEntry->nRegID = GetNextRegID();
231 
232  TRACE("pRegEntry->nRegID: %u\n", pRegEntry->nRegID);
233 
234  // get the user PID; that is the process ID of the target window
235  DWORD dwUserPID;
236  GetWindowThreadProcessId(pRegEntry->hwnd, &dwUserPID);
237 
238  // get broker if any
239  HWND hwndBroker = pRegEntry->hwndBroker;
240 
241  // clone the registration entry
242  LPREGENTRY pNewEntry = (LPREGENTRY)SHAlloc(pRegEntry->cbSize);
243  if (pNewEntry == NULL)
244  {
245  ERR("Out of memory\n");
246  pRegEntry->nRegID = INVALID_REG_ID;
247  SHUnlockShared(pRegEntry);
248  return FALSE;
249  }
250  CopyMemory(pNewEntry, pRegEntry, pRegEntry->cbSize);
251 
252  // create a directory watch if necessary
253  CDirectoryWatcher *pDirWatch = NULL;
254  if (pRegEntry->ibPidl && (pRegEntry->fSources & SHCNRF_InterruptLevel))
255  {
256  pDirWatch = CreateDirectoryWatcherFromRegEntry(pRegEntry);
257  if (pDirWatch && !pDirWatch->RequestAddWatcher())
258  {
259  ERR("RequestAddWatcher failed: %u\n", pRegEntry->nRegID);
260  pRegEntry->nRegID = INVALID_REG_ID;
261  SHUnlockShared(pRegEntry);
262  delete pDirWatch;
263  return FALSE;
264  }
265  }
266 
267  // unlock the registry entry
268  SHUnlockShared(pRegEntry);
269 
270  // add an item
271  CWatchItem *pItem = new CWatchItem { m_nNextRegID, dwUserPID, pNewEntry, hwndBroker, pDirWatch };
272  return AddItem(pItem);
273 }
274 
275 // Message CN_UNREGISTER: Unregister registration entries.
276 // wParam: The registration ID.
277 // lParam: Ignored.
278 // return: TRUE if successful.
280 {
281  TRACE("OnUnRegister(%p, %u, %p, %p)\n", m_hWnd, uMsg, wParam, lParam);
282 
283  // validate registration ID
284  UINT nRegID = (UINT)wParam;
285  if (nRegID == INVALID_REG_ID)
286  {
287  ERR("INVALID_REG_ID\n");
288  return FALSE;
289  }
290 
291  // remove it
292  return RemoveItemsByRegID(nRegID);
293 }
294 
295 // Message CN_DELIVER_NOTIFICATION: Perform a delivery.
296 // wParam: The handle of delivery ticket.
297 // lParam: The owner PID of delivery ticket.
298 // return: TRUE if necessary.
300 {
301  TRACE("OnDeliverNotification(%p, %u, %p, %p)\n", m_hWnd, uMsg, wParam, lParam);
302 
303  HANDLE hTicket = (HANDLE)wParam;
304  DWORD dwOwnerPID = (DWORD)lParam;
305 
306  // do delivery
307  BOOL ret = DeliverNotification(hTicket, dwOwnerPID);
308 
309  // free the ticket
310  SHFreeShared(hTicket, dwOwnerPID);
311  return ret;
312 }
313 
314 // Message CN_SUSPEND_RESUME: Suspend or resume the change notification.
315 // (specification is unknown)
317 {
318  TRACE("OnSuspendResume\n");
319 
320  // FIXME
321  return FALSE;
322 }
323 
324 // Message CN_UNREGISTER_PROCESS: Remove registration entries by PID.
325 // wParam: The user PID.
326 // lParam: Ignored.
327 // return: Zero.
329 {
330  DWORD dwUserPID = (DWORD)wParam;
331  RemoveItemsByProcess(dwUserPID);
332  return 0;
333 }
334 
336 {
337  DestroyAllItems();
339  return 0;
340 }
341 
342 // get next valid registration ID
344 {
345  m_nNextRegID++;
347  m_nNextRegID++;
348  return m_nNextRegID;
349 }
350 
351 // This function is called from CChangeNotifyServer::OnDeliverNotification.
352 // The function notifies to the registration entries that should be notified.
354 {
355  TRACE("DeliverNotification(%p, %p, 0x%lx)\n", m_hWnd, hTicket, dwOwnerPID);
356 
357  // lock the delivery ticket
358  LPDELITICKET pTicket = (LPDELITICKET)SHLockSharedEx(hTicket, dwOwnerPID, FALSE);
359  if (pTicket == NULL || pTicket->dwMagic != DELITICKET_MAGIC)
360  {
361  ERR("pTicket is invalid\n");
362  SHUnlockShared(pTicket);
363  return FALSE;
364  }
365 
366  // for all items
367  for (INT i = 0; i < m_items.GetSize(); ++i)
368  {
369  if (m_items[i] == NULL)
370  continue;
371 
372  LPREGENTRY pRegEntry = m_items[i]->pRegEntry;
373  if (pRegEntry == NULL || pRegEntry->dwMagic != REGENTRY_MAGIC)
374  {
375  ERR("pRegEntry is invalid\n");
376  continue;
377  }
378 
379  // should we notify for it?
380  BOOL bNotify = ShouldNotify(pTicket, pRegEntry);
381  if (bNotify)
382  {
383  // do notify
384  TRACE("Notifying: %p, 0x%x, %p, %lu\n",
385  pRegEntry->hwnd, pRegEntry->uMsg, hTicket, dwOwnerPID);
386  SendMessageW(pRegEntry->hwnd, pRegEntry->uMsg, (WPARAM)hTicket, dwOwnerPID);
387  TRACE("GetLastError(): %ld\n", ::GetLastError());
388  }
389  }
390 
391  // unlock the ticket
392  SHUnlockShared(pTicket);
393 
394  return TRUE;
395 }
396 
398 {
399 #define RETURN(x) do { \
400  TRACE("ShouldNotify return %d\n", (x)); \
401  return (x); \
402 } while (0)
403 
404  if (pTicket->wEventId & SHCNE_INTERRUPT)
405  {
406  if (!(pRegEntry->fSources & SHCNRF_InterruptLevel))
407  RETURN(FALSE);
408  if (!pRegEntry->ibPidl)
409  RETURN(FALSE);
410  }
411  else
412  {
413  if (!(pRegEntry->fSources & SHCNRF_ShellLevel))
414  RETURN(FALSE);
415  }
416 
417  if (!(pTicket->wEventId & pRegEntry->fEvents))
418  RETURN(FALSE);
419 
420  LPITEMIDLIST pidl = NULL, pidl1 = NULL, pidl2 = NULL;
421  if (pRegEntry->ibPidl)
422  pidl = (LPITEMIDLIST)((LPBYTE)pRegEntry + pRegEntry->ibPidl);
423  if (pTicket->ibOffset1)
424  pidl1 = (LPITEMIDLIST)((LPBYTE)pTicket + pTicket->ibOffset1);
425  if (pTicket->ibOffset2)
426  pidl2 = (LPITEMIDLIST)((LPBYTE)pTicket + pTicket->ibOffset2);
427 
428  if (pidl == NULL || (pTicket->wEventId & SHCNE_GLOBALEVENTS))
429  RETURN(TRUE);
430 
431  if (pRegEntry->fRecursive)
432  {
433  if (ILIsParent(pidl, pidl1, FALSE) ||
434  (pidl2 && ILIsParent(pidl, pidl2, FALSE)))
435  {
436  RETURN(TRUE);
437  }
438  }
439  else
440  {
441  if (ILIsEqual(pidl, pidl1) ||
442  ILIsParent(pidl, pidl1, TRUE) ||
443  (pidl2 && ILIsParent(pidl, pidl2, TRUE)))
444  {
445  RETURN(TRUE);
446  }
447  }
448 
449  RETURN(FALSE);
450 #undef RETURN
451 }
452 
454 {
455  if (!phwnd)
456  return E_INVALIDARG;
457  *phwnd = m_hWnd;
458  return S_OK;
459 }
460 
462 {
463  return E_NOTIMPL;
464 }
465 
467 {
468  // This is called by CChangeNotifyServer_CreateInstance right after instantiation.
470  if (!hwnd)
471  return E_FAIL;
473  return S_OK;
474 }
475 
477 {
478  return ShellObjectCreatorInit<CChangeNotifyServer>(riid, ppv);
479 }
BOOL RemoveItemsByProcess(DWORD dwUserPID)
BOOL WINAPI PathIsDirectoryW(LPCWSTR lpszPath)
Definition: path.c:1722
LRESULT OnRemoveByPID(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled)
#define SHCNE_GLOBALEVENTS
Definition: shlobj.h:1752
#define REFIID
Definition: guiddef.h:118
PVOID WINAPI SHLockSharedEx(HANDLE hData, DWORD dwProcessId, BOOL bWriteAccess)
LRESULT OnUnRegister(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled)
DWORD WINAPI GetWindowThreadProcessId(HWND hWnd, PDWORD lpdwProcessId)
HRESULT CChangeNotifyServer_CreateInstance(REFIID riid, void **ppv)
#define TRUE
Definition: types.h:120
REFIID riid
Definition: precomp.h:44
BOOL WINAPI SHGetPathFromIDListW(LPCITEMIDLIST pidl, LPWSTR pszPath)
Definition: pidl.c:1294
#define DECLARE_WND_CLASS_EX(WndClassName, style, bkgnd)
Definition: atlwin.h:1970
#define SHCNE_INTERRUPT
Definition: shlobj.h:1754
REFIID LPVOID * ppv
Definition: atlbase.h:39
#define DELITICKET_MAGIC
#define assert(x)
Definition: debug.h:53
DWORD WINAPI GetLastError(VOID)
Definition: except.c:1040
#define DECLARE_NOT_AGGREGATABLE(x)
Definition: atlcom.h:612
LRESULT OnRegister(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled)
BOOL WINAPI ILIsEqual(LPCITEMIDLIST pidl1, LPCITEMIDLIST pidl2)
Definition: pidl.c:535
UINT_PTR WPARAM
Definition: windef.h:207
#define REGENTRY_MAGIC
#define E_FAIL
Definition: ddrawi.h:102
LPREGENTRY pRegEntry
BOOL WINAPI DestroyWindow(_In_ HWND)
#define DWORD
Definition: nt_native.h:44
int32_t INT
Definition: typedefs.h:58
WPARAM wParam
Definition: combotst.c:138
#define RETURN(x)
BOOL AddItem(CWatchItem *pItem)
LRESULT OnSuspendResume(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled)
static CDirectoryWatcher * CreateDirectoryWatcherFromRegEntry(LPREGENTRY pRegEntry)
#define INVALID_REG_ID
#define L(x)
Definition: ntvdm.h:50
unsigned char * LPBYTE
Definition: typedefs.h:53
#define CN_REGISTER
#define FALSE
Definition: types.h:117
unsigned int BOOL
Definition: ntddk_ex.h:94
#define SubclassWindow(hwnd, lpfn)
Definition: windowsx.h:517
#define E_INVALIDARG
Definition: ddrawi.h:101
#define SHCNRF_InterruptLevel
Definition: shlobj.h:1774
#define CN_UNREGISTER_PROCESS
LONG_PTR LPARAM
Definition: windef.h:208
CDirectoryWatcher * pDirWatch
static void RequestAllWatchersTermination()
MESSAGE_HANDLER(CN_UNREGISTER_PROCESS, OnRemoveByPID)
#define WM_DESTROY
Definition: winuser.h:1596
virtual HRESULT STDMETHODCALLTYPE ContextSensitiveHelp(BOOL fEnterMode)
static CDirectoryWatcher * Create(LPCWSTR pszDirectoryPath, BOOL fSubTree)
#define BEGIN_COM_MAP(x)
Definition: atlcom.h:542
void DestroyItem(CWatchItem *pItem, HWND *phwndBroker)
#define TRACE(s)
Definition: solgame.cpp:4
virtual HRESULT STDMETHODCALLTYPE GetWindow(HWND *lphwnd)
LRESULT OnDeliverNotification(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled)
__wchar_t WCHAR
Definition: xmlstorage.h:180
LONG HRESULT
Definition: typedefs.h:79
#define END_MSG_MAP()
Definition: atlwin.h:1883
#define MAX_PATH
Definition: compat.h:34
#define WINAPI
Definition: msvc.h:6
#define SHCNRF_ShellLevel
Definition: shlobj.h:1775
#define CopyMemory
Definition: winbase.h:1665
#define STDMETHODCALLTYPE
Definition: bdasup.h:9
PVOID HANDLE
Definition: typedefs.h:73
unsigned long DWORD
Definition: ntddk_ex.h:95
BOOL WINAPI SHFreeShared(HANDLE hShared, DWORD dwProcId)
Definition: ordinal.c:308
int ret
BOOL RemoveItemsByRegID(UINT nRegID)
#define CN_DELIVER_NOTIFICATION
BOOL ShouldNotify(LPDELITICKET pTicket, LPREGENTRY pRegEntry)
#define ERR(fmt,...)
Definition: debug.h:110
#define CN_UNREGISTER
BOOL WINAPI ILIsParent(LPCITEMIDLIST pidlParent, LPCITEMIDLIST pidlChild, BOOL bImmediate)
Definition: pidl.c:592
#define S_OK
Definition: intsafe.h:52
CSimpleArray< CWatchItem * > m_items
GLsizei GLenum const GLvoid GLsizei GLenum GLbyte GLbyte GLbyte GLdouble GLdouble GLdouble GLfloat GLfloat GLfloat GLint GLint GLint GLshort GLshort GLshort GLubyte GLubyte GLubyte GLuint GLuint GLuint GLushort GLushort GLushort GLbyte GLbyte GLbyte GLbyte GLdouble GLdouble GLdouble GLdouble GLfloat GLfloat GLfloat GLfloat GLint GLint GLint GLint GLshort GLshort GLshort GLshort GLubyte GLubyte GLubyte GLubyte GLuint GLuint GLuint GLuint GLushort GLushort GLushort GLushort GLboolean const GLdouble const GLfloat const GLint const GLshort const GLbyte const GLdouble const GLfloat const GLint const GLshort const GLdouble const GLfloat const GLint const GLshort const GLdouble const GLfloat const GLint const GLshort const GLdouble const GLfloat const GLint const GLshort const GLdouble const GLdouble const GLfloat const GLfloat const GLint const GLint const GLshort const GLshort const GLdouble const GLfloat const GLint const GLshort const GLdouble const GLfloat const GLint const GLshort const GLdouble const GLfloat const GLint const GLshort const GLdouble const GLfloat const GLint const GLshort const GLdouble const GLfloat const GLint const GLshort const GLdouble const GLfloat const GLint const GLshort const GLdouble const GLfloat const GLint const GLshort GLenum GLenum GLenum GLfloat GLenum GLint GLenum GLenum GLenum GLfloat GLenum GLenum GLint GLenum GLfloat GLenum GLint GLint GLushort GLenum GLenum GLfloat GLenum GLenum GLint GLfloat const GLubyte GLenum GLenum GLenum const GLfloat GLenum GLenum const GLint GLenum GLint GLint GLsizei GLsizei GLint GLenum GLenum const GLvoid GLenum GLenum const GLfloat GLenum GLenum const GLint GLenum GLenum const GLdouble GLenum GLenum const GLfloat GLenum GLenum const GLint GLsizei GLuint GLfloat GLuint GLbitfield GLfloat GLint GLuint GLboolean GLenum GLfloat GLenum GLbitfield GLenum GLfloat GLfloat GLint GLint const GLfloat GLenum GLfloat GLfloat GLint GLint GLfloat GLfloat GLint GLint const GLfloat GLint GLfloat GLfloat GLint GLfloat GLfloat GLint GLfloat GLfloat const GLdouble const GLfloat const GLdouble const GLfloat GLint i
Definition: glfuncs.h:248
LPCWSTR szPath
Definition: env.c:37
BOOL DeliverNotification(HANDLE hTicket, DWORD dwOwnerPID)
#define COM_INTERFACE_ENTRY_IID(iid, x)
Definition: atlcom.h:562
LRESULT WINAPI SendMessageW(_In_ HWND, _In_ UINT, _In_ WPARAM, _In_ LPARAM)
#define E_NOTIMPL
Definition: ddrawi.h:99
unsigned int UINT
Definition: ndis.h:50
#define NULL
Definition: types.h:112
struct DELITICKET * LPDELITICKET
#define BEGIN_MSG_MAP(theClass)
Definition: atlwin.h:1864
#define DECLARE_PROTECT_FINAL_CONSTRUCT()
Definition: atlcom.h:640
void WINAPI SHFree(LPVOID pv)
Definition: shellole.c:326
WINE_DEFAULT_DEBUG_CHANNEL(shcn)
const GUID IID_IOleWindow
BOOL WINAPI SHUnlockShared(LPVOID lpView)
Definition: ordinal.c:288
#define END_COM_MAP()
Definition: atlcom.h:553
LRESULT OnDestroy(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled)
LONG_PTR LRESULT
Definition: windef.h:209
_In_ LONG _In_ HWND hwnd
Definition: winddi.h:4022
ITEMIDLIST UNALIGNED * LPITEMIDLIST
Definition: shtypes.idl:41
LPARAM lParam
Definition: combotst.c:139
HWND SHCreateDefaultWorkerWindow(VOID)
LPVOID WINAPI SHAlloc(SIZE_T len)
Definition: shellole.c:304
struct REGENTRY * LPREGENTRY
#define CN_SUSPEND_RESUME