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 = 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. 213 LRESULT CChangeNotifyServer::OnRegister(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) 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. 279 LRESULT CChangeNotifyServer::OnUnRegister(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) 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. 299 LRESULT CChangeNotifyServer::OnDeliverNotification(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) 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) 316 LRESULT CChangeNotifyServer::OnSuspendResume(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) 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. 328 LRESULT CChangeNotifyServer::OnRemoveByPID(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) 329 { 330 DWORD dwUserPID = (DWORD)wParam; 331 RemoveItemsByProcess(dwUserPID); 332 return 0; 333 } 334 335 LRESULT CChangeNotifyServer::OnDestroy(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) 336 { 337 DestroyAllItems(); 338 CDirectoryWatcher::RequestAllWatchersTermination(); 339 return 0; 340 } 341 342 // get next valid registration ID 343 UINT CChangeNotifyServer::GetNextRegID() 344 { 345 m_nNextRegID++; 346 if (m_nNextRegID == INVALID_REG_ID) 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. 353 BOOL CChangeNotifyServer::DeliverNotification(HANDLE hTicket, DWORD dwOwnerPID) 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 397 BOOL CChangeNotifyServer::ShouldNotify(LPDELITICKET pTicket, LPREGENTRY pRegEntry) 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 453 HRESULT WINAPI CChangeNotifyServer::GetWindow(HWND* phwnd) 454 { 455 if (!phwnd) 456 return E_INVALIDARG; 457 *phwnd = m_hWnd; 458 return S_OK; 459 } 460 461 HRESULT WINAPI CChangeNotifyServer::ContextSensitiveHelp(BOOL fEnterMode) 462 { 463 return E_NOTIMPL; 464 } 465 466 HRESULT CChangeNotifyServer::Initialize() 467 { 468 // This is called by CChangeNotifyServer_CreateInstance right after instantiation. 469 HWND hwnd = SHCreateDefaultWorkerWindow(); 470 if (!hwnd) 471 return E_FAIL; 472 SubclassWindow(hwnd); 473 return S_OK; 474 } 475 476 HRESULT CChangeNotifyServer_CreateInstance(REFIID riid, void **ppv) 477 { 478 return ShellObjectCreatorInit<CChangeNotifyServer>(riid, ppv); 479 } 480