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 12 WINE_DEFAULT_DEBUG_CHANNEL(shcn); 13 14 ////////////////////////////////////////////////////////////////////////////// 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 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 34 class CChangeNotifyServer : 35 public CWindowImpl<CChangeNotifyServer, CWindow, CWorkerTraits>, 36 public CComObjectRootEx<CComMultiThreadModelNoCS>, 37 public IOleWindow 38 { 39 public: 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 74 private: 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 89 CChangeNotifyServer::CChangeNotifyServer() 90 : m_nNextRegID(INVALID_REG_ID) 91 { 92 } 93 94 CChangeNotifyServer::~CChangeNotifyServer() 95 { 96 } 97 98 BOOL 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 116 void 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 140 void 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 154 BOOL 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 171 BOOL 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 189 static CDirectoryWatcher * 190 CreateDirectoryWatcherFromRegEntry(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. 214 LRESULT 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. 280 LRESULT 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. 300 LRESULT 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) 317 LRESULT 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. 329 LRESULT CChangeNotifyServer::OnRemoveByPID(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) 330 { 331 DWORD dwUserPID = (DWORD)wParam; 332 RemoveItemsByProcess(dwUserPID); 333 return 0; 334 } 335 336 LRESULT 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 344 UINT 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. 354 BOOL 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 398 BOOL 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 454 HRESULT WINAPI CChangeNotifyServer::GetWindow(HWND* phwnd) 455 { 456 if (!phwnd) 457 return E_INVALIDARG; 458 *phwnd = m_hWnd; 459 return S_OK; 460 } 461 462 HRESULT WINAPI CChangeNotifyServer::ContextSensitiveHelp(BOOL fEnterMode) 463 { 464 return E_NOTIMPL; 465 } 466 467 HRESULT 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 477 HRESULT CChangeNotifyServer_CreateInstance(REFIID riid, void **ppv) 478 { 479 return ShellObjectCreatorInit<CChangeNotifyServer>(riid, ppv); 480 } 481