Something went wrong. Try again.
Reactos
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481/* * PROJECT: shell32 * LICENSE: LGPL-2.1-or-later (https://spdx.org/licenses/LGPL-2.1-or-later) * PURPOSE: Shell change notification * COPYRIGHT: Copyright 2020 Katayama Hirofumi MZ (katayama.hirofumi.mz@gmail.com) */#include "shelldesktop.h"#include "shlwapi_undoc.h"#include "CDirectoryWatcher.h"#include <assert.h> // for assert
WINE_DEFAULT_DEBUG_CHANNEL(shcn);
//////////////////////////////////////////////////////////////////////////////
// notification target itemstruct CWatchItem{ UINT nRegID; // The registration ID. DWORD dwUserPID; // The user PID; that is the process ID of the target window. LPREGENTRY pRegEntry; // The registration entry. HWND hwndBroker; // Client broker window (if any). CDirectoryWatcher *pDirWatch; // for filesystem notification};
//////////////////////////////////////////////////////////////////////////////// CChangeNotifyServer//// CChangeNotifyServer implements a window that handles all shell change notifications.// It runs in the context of explorer and specifically in the thread of the shell desktop.// Shell change notification api exported from shell32 forwards all their calls// to this window where all processing takes place.
class CChangeNotifyServer : public CWindowImpl<CChangeNotifyServer, CWindow, CWorkerTraits>, public CComObjectRootEx<CComMultiThreadModelNoCS>, public IOleWindow{public: CChangeNotifyServer(); virtual ~CChangeNotifyServer(); HRESULT Initialize();
// *** IOleWindow methods *** STDMETHOD(GetWindow)(HWND *lphwnd) override; STDMETHOD(ContextSensitiveHelp)(BOOL fEnterMode) override;
// Message handlers LRESULT OnRegister(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled); LRESULT OnUnRegister(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled); LRESULT OnDeliverNotification(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled); LRESULT OnSuspendResume(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled); LRESULT OnRemoveByPID(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled); LRESULT OnDestroy(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled);
DECLARE_NOT_AGGREGATABLE(CChangeNotifyServer)
DECLARE_PROTECT_FINAL_CONSTRUCT() BEGIN_COM_MAP(CChangeNotifyServer) COM_INTERFACE_ENTRY_IID(IID_IOleWindow, IOleWindow) END_COM_MAP()
DECLARE_WND_CLASS_EX(L"WorkerW", 0, 0)
BEGIN_MSG_MAP(CChangeNotifyServer) MESSAGE_HANDLER(CN_REGISTER, OnRegister) MESSAGE_HANDLER(CN_UNREGISTER, OnUnRegister) MESSAGE_HANDLER(CN_DELIVER_NOTIFICATION, OnDeliverNotification) MESSAGE_HANDLER(CN_SUSPEND_RESUME, OnSuspendResume) MESSAGE_HANDLER(CN_UNREGISTER_PROCESS, OnRemoveByPID); MESSAGE_HANDLER(WM_DESTROY, OnDestroy); END_MSG_MAP()
private: UINT m_nNextRegID; CSimpleArray<CWatchItem*> m_items;
BOOL AddItem(CWatchItem *pItem); BOOL RemoveItemsByRegID(UINT nRegID); BOOL RemoveItemsByProcess(DWORD dwUserPID); void DestroyItem(CWatchItem *pItem, HWND *phwndBroker); void DestroyAllItems();
UINT GetNextRegID(); BOOL DeliverNotification(HANDLE hTicket, DWORD dwOwnerPID); BOOL ShouldNotify(LPDELITICKET pTicket, LPREGENTRY pRegEntry);};
CChangeNotifyServer::CChangeNotifyServer() : m_nNextRegID(INVALID_REG_ID){}
CChangeNotifyServer::~CChangeNotifyServer(){}
BOOL CChangeNotifyServer::AddItem(CWatchItem *pItem){ // find the empty room for (INT i = 0; i < m_items.GetSize(); ++i) { if (m_items[i] == NULL) { // found the room, populate it m_items[i] = pItem; return TRUE; } }
// no empty room found m_items.Add(pItem); return TRUE;}
void CChangeNotifyServer::DestroyItem(CWatchItem *pItem, HWND *phwndBroker){ assert(pItem);
// destroy broker if any and if first time HWND hwndBroker = pItem->hwndBroker; pItem->hwndBroker = NULL; if (hwndBroker && hwndBroker != *phwndBroker) { ::DestroyWindow(hwndBroker); *phwndBroker = hwndBroker; }
// request termination of pDirWatch if any CDirectoryWatcher *pDirWatch = pItem->pDirWatch; pItem->pDirWatch = NULL; if (pDirWatch) pDirWatch->RequestTermination();
// free SHFree(pItem->pRegEntry); delete pItem;}
void CChangeNotifyServer::DestroyAllItems(){ for (INT i = 0; i < m_items.GetSize(); ++i) { if (m_items[i]) { HWND hwndBroker = NULL; DestroyItem(m_items[i], &hwndBroker); m_items[i] = NULL; } } m_items.RemoveAll();}
BOOL CChangeNotifyServer::RemoveItemsByRegID(UINT nRegID){ BOOL bFound = FALSE; HWND hwndBroker = NULL; assert(nRegID != INVALID_REG_ID); for (INT i = 0; i < m_items.GetSize(); ++i) { if (m_items[i] && m_items[i]->nRegID == nRegID) { bFound = TRUE; DestroyItem(m_items[i], &hwndBroker); m_items[i] = NULL; } } return bFound;}
BOOL CChangeNotifyServer::RemoveItemsByProcess(DWORD dwUserPID){ BOOL bFound = FALSE; HWND hwndBroker = NULL; assert(dwUserPID != 0); for (INT i = 0; i < m_items.GetSize(); ++i) { if (m_items[i] && m_items[i]->dwUserPID == dwUserPID) { bFound = TRUE; DestroyItem(m_items[i], &hwndBroker); m_items[i] = NULL; } } return bFound;}
// create a CDirectoryWatcher from a REGENTRYstatic CDirectoryWatcher *CreateDirectoryWatcherFromRegEntry(LPREGENTRY pRegEntry){ if (pRegEntry->ibPidl == 0) return NULL;
// get the path WCHAR szPath[MAX_PATH]; LPITEMIDLIST pidl = (LPITEMIDLIST)((LPBYTE)pRegEntry + pRegEntry->ibPidl); if (!SHGetPathFromIDListW(pidl, szPath) || !PathIsDirectoryW(szPath)) return NULL;
// create a CDirectoryWatcher CDirectoryWatcher *pDirectoryWatcher = CDirectoryWatcher::Create(pRegEntry->hwnd, szPath, pRegEntry->fRecursive); if (pDirectoryWatcher == NULL) return NULL;
return pDirectoryWatcher;}
// Message CN_REGISTER: Register the registration entry.// wParam: The handle of registration entry.// lParam: The owner PID of registration entry.// return: TRUE if successful.LRESULT CChangeNotifyServer::OnRegister(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled){ TRACE("OnRegister(%p, %u, %p, %p)\n", m_hWnd, uMsg, wParam, lParam);
// lock the registration entry HANDLE hRegEntry = (HANDLE)wParam; DWORD dwOwnerPID = (DWORD)lParam; LPREGENTRY pRegEntry = (LPREGENTRY)SHLockSharedEx(hRegEntry, dwOwnerPID, TRUE); if (pRegEntry == NULL || pRegEntry->dwMagic != REGENTRY_MAGIC) { ERR("pRegEntry is invalid\n"); SHUnlockShared(pRegEntry); return FALSE; }
// update registration ID if necessary if (pRegEntry->nRegID == INVALID_REG_ID) pRegEntry->nRegID = GetNextRegID();
TRACE("pRegEntry->nRegID: %u\n", pRegEntry->nRegID);
// get the user PID; that is the process ID of the target window DWORD dwUserPID; GetWindowThreadProcessId(pRegEntry->hwnd, &dwUserPID);
// get broker if any HWND hwndBroker = pRegEntry->hwndBroker;
// clone the registration entry LPREGENTRY pNewEntry = (LPREGENTRY)SHAlloc(pRegEntry->cbSize); if (pNewEntry == NULL) { ERR("Out of memory\n"); pRegEntry->nRegID = INVALID_REG_ID; SHUnlockShared(pRegEntry); return FALSE; } CopyMemory(pNewEntry, pRegEntry, pRegEntry->cbSize);
// create a directory watch if necessary CDirectoryWatcher *pDirWatch = NULL; if (pRegEntry->ibPidl && (pRegEntry->fSources & SHCNRF_InterruptLevel)) { pDirWatch = CreateDirectoryWatcherFromRegEntry(pRegEntry); if (pDirWatch && !pDirWatch->RequestAddWatcher()) { ERR("RequestAddWatcher failed: %u\n", pRegEntry->nRegID); pRegEntry->nRegID = INVALID_REG_ID; SHUnlockShared(pRegEntry); delete pDirWatch; return FALSE; } }
// unlock the registry entry SHUnlockShared(pRegEntry);
// add an item CWatchItem *pItem = new CWatchItem { m_nNextRegID, dwUserPID, pNewEntry, hwndBroker, pDirWatch }; return AddItem(pItem);}
// Message CN_UNREGISTER: Unregister registration entries.// wParam: The registration ID.// lParam: Ignored.// return: TRUE if successful.LRESULT CChangeNotifyServer::OnUnRegister(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled){ TRACE("OnUnRegister(%p, %u, %p, %p)\n", m_hWnd, uMsg, wParam, lParam);
// validate registration ID UINT nRegID = (UINT)wParam; if (nRegID == INVALID_REG_ID) { ERR("INVALID_REG_ID\n"); return FALSE; }
// remove it return RemoveItemsByRegID(nRegID);}
// Message CN_DELIVER_NOTIFICATION: Perform a delivery.// wParam: The handle of delivery ticket.// lParam: The owner PID of delivery ticket.// return: TRUE if necessary.LRESULT CChangeNotifyServer::OnDeliverNotification(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled){ TRACE("OnDeliverNotification(%p, %u, %p, %p)\n", m_hWnd, uMsg, wParam, lParam);
HANDLE hTicket = (HANDLE)wParam; DWORD dwOwnerPID = (DWORD)lParam;
// do delivery BOOL ret = DeliverNotification(hTicket, dwOwnerPID);
// free the ticket SHFreeShared(hTicket, dwOwnerPID); return ret;}
// Message CN_SUSPEND_RESUME: Suspend or resume the change notification.// (specification is unknown)LRESULT CChangeNotifyServer::OnSuspendResume(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled){ TRACE("OnSuspendResume\n");
// FIXME return FALSE;}
// Message CN_UNREGISTER_PROCESS: Remove registration entries by PID.// wParam: The user PID.// lParam: Ignored.// return: Zero.LRESULT CChangeNotifyServer::OnRemoveByPID(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled){ DWORD dwUserPID = (DWORD)wParam; RemoveItemsByProcess(dwUserPID); return 0;}
LRESULT CChangeNotifyServer::OnDestroy(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled){ DestroyAllItems(); CDirectoryWatcher::RequestAllWatchersTermination(); return 0;}
// get next valid registration IDUINT CChangeNotifyServer::GetNextRegID(){ m_nNextRegID++; if (m_nNextRegID == INVALID_REG_ID) m_nNextRegID++; return m_nNextRegID;}
// This function is called from CChangeNotifyServer::OnDeliverNotification.// The function notifies to the registration entries that should be notified.BOOL CChangeNotifyServer::DeliverNotification(HANDLE hTicket, DWORD dwOwnerPID){ TRACE("DeliverNotification(%p, %p, 0x%lx)\n", m_hWnd, hTicket, dwOwnerPID);
// lock the delivery ticket LPDELITICKET pTicket = (LPDELITICKET)SHLockSharedEx(hTicket, dwOwnerPID, FALSE); if (pTicket == NULL || pTicket->dwMagic != DELITICKET_MAGIC) { ERR("pTicket is invalid\n"); SHUnlockShared(pTicket); return FALSE; }
// for all items for (INT i = 0; i < m_items.GetSize(); ++i) { if (m_items[i] == NULL) continue;
LPREGENTRY pRegEntry = m_items[i]->pRegEntry; if (pRegEntry == NULL || pRegEntry->dwMagic != REGENTRY_MAGIC) { ERR("pRegEntry is invalid\n"); continue; }
// should we notify for it? BOOL bNotify = ShouldNotify(pTicket, pRegEntry); if (bNotify) { // do notify TRACE("Notifying: %p, 0x%x, %p, %lu\n", pRegEntry->hwnd, pRegEntry->uMsg, hTicket, dwOwnerPID); SendMessageW(pRegEntry->hwnd, pRegEntry->uMsg, (WPARAM)hTicket, dwOwnerPID); TRACE("GetLastError(): %ld\n", ::GetLastError()); } }
// unlock the ticket SHUnlockShared(pTicket);
return TRUE;}
BOOL CChangeNotifyServer::ShouldNotify(LPDELITICKET pTicket, LPREGENTRY pRegEntry){#define RETURN(x) do { \ TRACE("ShouldNotify return %d\n", (x)); \ return (x); \} while (0)
if (pTicket->wEventId & SHCNE_INTERRUPT) { if (!(pRegEntry->fSources & SHCNRF_InterruptLevel)) RETURN(FALSE); if (!pRegEntry->ibPidl) RETURN(FALSE); } else { if (!(pRegEntry->fSources & SHCNRF_ShellLevel)) RETURN(FALSE); }
if (!(pTicket->wEventId & pRegEntry->fEvents)) RETURN(FALSE);
LPITEMIDLIST pidl = NULL, pidl1 = NULL, pidl2 = NULL; if (pRegEntry->ibPidl) pidl = (LPITEMIDLIST)((LPBYTE)pRegEntry + pRegEntry->ibPidl); if (pTicket->ibOffset1) pidl1 = (LPITEMIDLIST)((LPBYTE)pTicket + pTicket->ibOffset1); if (pTicket->ibOffset2) pidl2 = (LPITEMIDLIST)((LPBYTE)pTicket + pTicket->ibOffset2);
if (pidl == NULL || (pTicket->wEventId & SHCNE_GLOBALEVENTS)) RETURN(TRUE);
if (pRegEntry->fRecursive) { if (ILIsParent(pidl, pidl1, FALSE) || (pidl2 && ILIsParent(pidl, pidl2, FALSE))) { RETURN(TRUE); } } else { if (ILIsEqual(pidl, pidl1) || ILIsParent(pidl, pidl1, TRUE) || (pidl2 && ILIsParent(pidl, pidl2, TRUE))) { RETURN(TRUE); } }
RETURN(FALSE);#undef RETURN}
HRESULT WINAPI CChangeNotifyServer::GetWindow(HWND* phwnd){ if (!phwnd) return E_INVALIDARG; *phwnd = m_hWnd; return S_OK;}
HRESULT WINAPI CChangeNotifyServer::ContextSensitiveHelp(BOOL fEnterMode){ return E_NOTIMPL;}
HRESULT CChangeNotifyServer::Initialize(){ // This is called by CChangeNotifyServer_CreateInstance right after instantiation. HWND hwnd = SHCreateDefaultWorkerWindow(); if (!hwnd) return E_FAIL; SubclassWindow(hwnd); return S_OK;}
HRESULT CChangeNotifyServer_CreateInstance(REFIID riid, void **ppv){ return ShellObjectCreatorInit<CChangeNotifyServer>(riid, ppv);}