diff --git a/dll/win32/shell32/CDefView.cpp b/dll/win32/shell32/CDefView.cpp index fbca53391f7..06d7c659f03 100644 --- a/dll/win32/shell32/CDefView.cpp +++ b/dll/win32/shell32/CDefView.cpp @@ -1184,8 +1184,11 @@ LRESULT CDefView::OnCreate(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandl ntreg[0].fRecursive = FALSE; ntreg[0].pidl = m_pidlParent; } - m_hNotify = SHChangeNotifyRegister(m_hWnd, SHCNRF_NewDelivery | SHCNRF_ShellLevel, - SHCNE_ALLEVENTS, SHV_CHANGE_NOTIFY, nRegCount, ntreg); + m_hNotify = SHChangeNotifyRegister(m_hWnd, + SHCNRF_InterruptLevel | SHCNRF_ShellLevel | + SHCNRF_NewDelivery, + SHCNE_ALLEVENTS, SHV_CHANGE_NOTIFY, + nRegCount, ntreg); if (nRegCount == 3) { ILFree(pidls[0]); diff --git a/dll/win32/shell32/changenotify.cpp b/dll/win32/shell32/changenotify.cpp index 3f2b7cf1f20..ae5e0e8bdb4 100644 --- a/dll/win32/shell32/changenotify.cpp +++ b/dll/win32/shell32/changenotify.cpp @@ -573,7 +573,7 @@ SHChangeNotification_Lock(HANDLE hTicket, DWORD dwOwnerPID, LPITEMIDLIST **lppid if (lppidls) *lppidls = pHandbag->pidls; if (lpwEventId) - *lpwEventId = pHandbag->pTicket->wEventId; + *lpwEventId = (pHandbag->pTicket->wEventId & ~SHCNE_INTERRUPT); // return the handbag return pHandbag; diff --git a/dll/win32/shell32/shelldesktop/CChangeNotifyServer.cpp b/dll/win32/shell32/shelldesktop/CChangeNotifyServer.cpp index 5c2184f7527..a5852976f1e 100644 --- a/dll/win32/shell32/shelldesktop/CChangeNotifyServer.cpp +++ b/dll/win32/shell32/shelldesktop/CChangeNotifyServer.cpp @@ -6,11 +6,619 @@ */ #include "shelldesktop.h" #include "shlwapi_undoc.h" -#include -#include +#include // for CSimpleArray +#include // for _beginthreadex +#include // for assert WINE_DEFAULT_DEBUG_CHANNEL(shcn); +static inline void +NotifyFileSystemChange(LONG wEventId, LPCWSTR path1, LPCWSTR path2) +{ + SHChangeNotify(wEventId | SHCNE_INTERRUPT, SHCNF_PATHW, path1, path2); +} + +////////////////////////////////////////////////////////////////////////////// +// DIRLIST --- directory list + +struct DIRLISTITEM +{ + WCHAR szPath[MAX_PATH]; + DWORD dwFileSize; + BOOL fDir; + + DIRLISTITEM(LPCWSTR pszPath, DWORD dwSize, BOOL is_dir) + { + lstrcpynW(szPath, pszPath, _countof(szPath)); + dwFileSize = dwSize; + fDir = is_dir; + } + + BOOL IsEmpty() const + { + return szPath[0] == 0; + } + + BOOL EqualPath(LPCWSTR pszPath) const + { + return lstrcmpiW(szPath, pszPath) == 0; + } +}; + +class DIRLIST +{ +public: + DIRLIST() + { + } + + DIRLIST(LPCWSTR pszDir, BOOL fRecursive) + { + GetDirList(pszDir, fRecursive); + } + + BOOL AddItem(LPCWSTR pszPath, DWORD dwFileSize, BOOL fDir); + BOOL GetDirList(LPCWSTR pszDir, BOOL fRecursive); + BOOL Contains(LPCWSTR pszPath, BOOL fDir) const; + BOOL RenameItem(LPCWSTR pszPath1, LPCWSTR pszPath2, BOOL fDir); + BOOL DeleteItem(LPCWSTR pszPath, BOOL fDir); + BOOL GetFirstChange(LPWSTR pszPath) const; + + void RemoveAll() + { + m_items.RemoveAll(); + } + +protected: + CSimpleArray m_items; +}; + +BOOL DIRLIST::Contains(LPCWSTR pszPath, BOOL fDir) const +{ + assert(!PathIsRelativeW(pszPath)); + + for (INT i = 0; i < m_items.GetSize(); ++i) + { + if (m_items[i].IsEmpty() || fDir != m_items[i].fDir) + continue; + + if (m_items[i].EqualPath(pszPath)) + return TRUE; + } + return FALSE; +} + +BOOL DIRLIST::AddItem(LPCWSTR pszPath, DWORD dwFileSize, BOOL fDir) +{ + assert(!PathIsRelativeW(pszPath)); + + if (dwFileSize == INVALID_FILE_SIZE) + { + WIN32_FIND_DATAW find; + HANDLE hFind = FindFirstFileW(pszPath, &find); + if (hFind == INVALID_HANDLE_VALUE) + return FALSE; + FindClose(hFind); + dwFileSize = find.nFileSizeLow; + } + + DIRLISTITEM item(pszPath, dwFileSize, fDir); + return m_items.Add(item); +} + +BOOL DIRLIST::RenameItem(LPCWSTR pszPath1, LPCWSTR pszPath2, BOOL fDir) +{ + assert(!PathIsRelativeW(pszPath1)); + assert(!PathIsRelativeW(pszPath2)); + + for (INT i = 0; i < m_items.GetSize(); ++i) + { + if (m_items[i].fDir == fDir && m_items[i].EqualPath(pszPath1)) + { + lstrcpynW(m_items[i].szPath, pszPath2, _countof(m_items[i].szPath)); + return TRUE; + } + } + return FALSE; +} + +BOOL DIRLIST::DeleteItem(LPCWSTR pszPath, BOOL fDir) +{ + assert(!PathIsRelativeW(pszPath)); + + for (INT i = 0; i < m_items.GetSize(); ++i) + { + if (m_items[i].fDir == fDir && m_items[i].EqualPath(pszPath)) + { + m_items[i].szPath[0] = 0; + return TRUE; + } + } + return FALSE; +} + +BOOL DIRLIST::GetDirList(LPCWSTR pszDir, BOOL fRecursive) +{ + // get the full path + WCHAR szPath[MAX_PATH]; + lstrcpynW(szPath, pszDir, _countof(szPath)); + assert(!PathIsRelativeW(szPath)); + + // is it a directory? + if (!PathIsDirectoryW(szPath)) + return FALSE; + + // add the path + if (!AddItem(szPath, 0, TRUE)) + return FALSE; + + // enumerate the file items to remember + PathAppendW(szPath, L"*"); + WIN32_FIND_DATAW find; + HANDLE hFind = FindFirstFileW(szPath, &find); + if (hFind == INVALID_HANDLE_VALUE) + { + ERR("FindFirstFileW failed\n"); + return FALSE; + } + + do + { + // ignore "." and ".." + if (lstrcmpW(find.cFileName, L".") == 0 || + lstrcmpW(find.cFileName, L"..") == 0) + { + continue; + } + + // build a path + PathRemoveFileSpecW(szPath); + if (lstrlenW(szPath) + lstrlenW(find.cFileName) + 1 > MAX_PATH) + { + ERR("szPath is too long\n"); + continue; + } + PathAppendW(szPath, find.cFileName); + + // add the path and do recurse + if (fRecursive && (find.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) + GetDirList(szPath, fRecursive); + else + AddItem(szPath, find.nFileSizeLow, FALSE); + } while (FindNextFileW(hFind, &find)); + + FindClose(hFind); + + return TRUE; +} + +BOOL DIRLIST::GetFirstChange(LPWSTR pszPath) const +{ + // validate paths + for (INT i = 0; i < m_items.GetSize(); ++i) + { + if (m_items[i].IsEmpty()) + continue; + + if (m_items[i].fDir) // item is a directory + { + if (!PathIsDirectoryW(m_items[i].szPath)) + { + // mismatched + lstrcpynW(pszPath, m_items[i].szPath, MAX_PATH); + return TRUE; + } + } + else // item is a normal file + { + if (!PathFileExistsW(m_items[i].szPath) || + PathIsDirectoryW(m_items[i].szPath)) + { + // mismatched + lstrcpynW(pszPath, m_items[i].szPath, MAX_PATH); + return TRUE; + } + } + } + + // check sizes + HANDLE hFind; + WIN32_FIND_DATAW find; + for (INT i = 0; i < m_items.GetSize(); ++i) + { + if (m_items[i].IsEmpty() || m_items[i].fDir) + continue; + + // get size + hFind = FindFirstFileW(m_items[i].szPath, &find); + FindClose(hFind); + + if (hFind == INVALID_HANDLE_VALUE || + find.nFileSizeLow != m_items[i].dwFileSize) + { + // different size + lstrcpynW(pszPath, m_items[i].szPath, MAX_PATH); + return TRUE; + } + } + + return FALSE; +} + +////////////////////////////////////////////////////////////////////////////// +// DirWatch --- directory watcher using ReadDirectoryChangesW + +static HANDLE s_hThreadAPC = NULL; +static BOOL s_fTerminateAllWatches = FALSE; + +// NOTE: Regard to asynchronous procedure call (APC), please see: +// https://docs.microsoft.com/en-us/windows/win32/api/synchapi/nf-synchapi-sleepex + +// The APC thread function for directory watch +static unsigned __stdcall DirWatchThreadFuncAPC(void *) +{ + while (!s_fTerminateAllWatches) + { +#if 1 // FIXME: This is a HACK + WaitForSingleObjectEx(GetCurrentThread(), INFINITE, TRUE); +#else + SleepEx(INFINITE, TRUE); +#endif + } + return 0; +} + +// the buffer for ReadDirectoryChangesW +#define BUFFER_SIZE 0x1000 +static BYTE s_buffer[BUFFER_SIZE]; + +class DirWatch +{ +public: + HANDLE m_hDir; + WCHAR m_szDir[MAX_PATH]; + BOOL m_fDeadWatch; + BOOL m_fRecursive; + OVERLAPPED m_overlapped; // for async I/O + DIRLIST m_DirList; + + static DirWatch *Create(LPCWSTR pszDir, BOOL fSubTree = FALSE); + ~DirWatch(); + +protected: + DirWatch(LPCWSTR pszDir, BOOL fSubTree); +}; + +DirWatch::DirWatch(LPCWSTR pszDir, BOOL fSubTree) + : m_fDeadWatch(FALSE) + , m_fRecursive(fSubTree) + , m_DirList(pszDir, fSubTree) +{ + TRACE("DirWatch::DirWatch: %p, '%S'\n", this, pszDir); + + lstrcpynW(m_szDir, pszDir, MAX_PATH); + + // open the directory to watch changes (for ReadDirectoryChangesW) + m_hDir = CreateFileW(pszDir, FILE_LIST_DIRECTORY, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + NULL, OPEN_EXISTING, + FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED, + NULL); +} + +/*static*/ DirWatch *DirWatch::Create(LPCWSTR pszDir, BOOL fSubTree) +{ + WCHAR szFullPath[MAX_PATH]; + GetFullPathNameW(pszDir, _countof(szFullPath), szFullPath, NULL); + + DirWatch *pDirWatch = new DirWatch(szFullPath, fSubTree); + if (pDirWatch->m_hDir == INVALID_HANDLE_VALUE) + { + ERR("CreateFileW failed\n"); + delete pDirWatch; + pDirWatch = NULL; + } + return pDirWatch; +} + +DirWatch::~DirWatch() +{ + TRACE("DirWatch::~DirWatch: %p, '%S'\n", this, m_szDir); + + if (m_hDir != INVALID_HANDLE_VALUE) + CloseHandle(m_hDir); +} + +static BOOL _BeginRead(DirWatch *pDirWatch); + +// The APC procedure to add a DirWatch and start the directory watch +static void NTAPI _AddDirectoryProcAPC(ULONG_PTR Parameter) +{ + DirWatch *pDirWatch = (DirWatch *)Parameter; + assert(pDirWatch != NULL); + + _BeginRead(pDirWatch); +} + +// The APC procedure to request termination of a DirWatch +static void NTAPI _RequestTerminationAPC(ULONG_PTR Parameter) +{ + DirWatch *pDirWatch = (DirWatch *)Parameter; + assert(pDirWatch != NULL); + + pDirWatch->m_fDeadWatch = TRUE; + CancelIo(pDirWatch->m_hDir); +} + +// The APC procedure to request termination of all the directory watches +static void NTAPI _RequestAllTerminationAPC(ULONG_PTR Parameter) +{ + s_fTerminateAllWatches = TRUE; + CloseHandle(s_hThreadAPC); + s_hThreadAPC = NULL; +} + +// convert the file action to an event +static DWORD +ConvertActionToEvent(DWORD Action, BOOL fDir) +{ + switch (Action) + { + case FILE_ACTION_ADDED: + return (fDir ? SHCNE_MKDIR : SHCNE_CREATE); + case FILE_ACTION_REMOVED: + return (fDir ? SHCNE_RMDIR : SHCNE_DELETE); + case FILE_ACTION_MODIFIED: + return (fDir ? SHCNE_UPDATEDIR : SHCNE_UPDATEITEM); + case FILE_ACTION_RENAMED_OLD_NAME: + break; + case FILE_ACTION_RENAMED_NEW_NAME: + return (fDir ? SHCNE_RENAMEFOLDER : SHCNE_RENAMEITEM); + default: + break; + } + return 0; +} + +// Notify a filesystem notification using pDirWatch. +static void _ProcessNotification(DirWatch *pDirWatch) +{ + PFILE_NOTIFY_INFORMATION pInfo = (PFILE_NOTIFY_INFORMATION)s_buffer; + WCHAR szName[MAX_PATH], szPath[MAX_PATH], szTempPath[MAX_PATH]; + DWORD dwEvent, cbName; + BOOL fDir; + + // if the watch is recursive + if (pDirWatch->m_fRecursive) + { + // get the first change + if (!pDirWatch->m_DirList.GetFirstChange(szPath)) + return; + + // then, notify a SHCNE_UPDATEDIR + if (lstrcmpiW(pDirWatch->m_szDir, szPath) != 0) + PathRemoveFileSpecW(szPath); + NotifyFileSystemChange(SHCNE_UPDATEDIR, szPath, NULL); + + // refresh directory list + pDirWatch->m_DirList.RemoveAll(); + pDirWatch->m_DirList.GetDirList(pDirWatch->m_szDir, TRUE); + return; + } + + // for each entry in s_buffer + szPath[0] = szTempPath[0] = 0; + for (;;) + { + // get name (relative from pDirWatch->m_szDir) + cbName = pInfo->FileNameLength; + if (sizeof(szName) - sizeof(UNICODE_NULL) < cbName) + { + ERR("pInfo->FileName is longer than szName\n"); + break; + } + // NOTE: FILE_NOTIFY_INFORMATION.FileName is not null-terminated. + ZeroMemory(szName, sizeof(szName)); + CopyMemory(szName, pInfo->FileName, cbName); + + // get full path + lstrcpynW(szPath, pDirWatch->m_szDir, _countof(szPath)); + PathAppendW(szPath, szName); + + // convert to long pathname if it contains '~' + if (StrChrW(szPath, L'~') != NULL) + { + GetLongPathNameW(szPath, szName, _countof(szName)); + lstrcpynW(szPath, szName, _countof(szPath)); + } + + // convert action to event + fDir = PathIsDirectoryW(szPath); + dwEvent = ConvertActionToEvent(pInfo->Action, fDir); + + // get the directory list of pDirWatch + DIRLIST& List = pDirWatch->m_DirList; + + // convert SHCNE_DELETE to SHCNE_RMDIR if the path is a directory + if (!fDir && (dwEvent == SHCNE_DELETE) && List.Contains(szPath, TRUE)) + { + fDir = TRUE; + dwEvent = SHCNE_RMDIR; + } + + // update List + switch (dwEvent) + { + case SHCNE_MKDIR: + if (!List.AddItem(szPath, 0, TRUE)) + dwEvent = 0; + break; + case SHCNE_CREATE: + if (!List.AddItem(szPath, INVALID_FILE_SIZE, FALSE)) + dwEvent = 0; + break; + case SHCNE_RENAMEFOLDER: + if (!List.RenameItem(szTempPath, szPath, TRUE)) + dwEvent = 0; + break; + case SHCNE_RENAMEITEM: + if (!List.RenameItem(szTempPath, szPath, FALSE)) + dwEvent = 0; + break; + case SHCNE_RMDIR: + if (!List.DeleteItem(szPath, TRUE)) + dwEvent = 0; + break; + case SHCNE_DELETE: + if (!List.DeleteItem(szPath, FALSE)) + dwEvent = 0; + break; + } + + if (dwEvent != 0) + { + // notify + if (pInfo->Action == FILE_ACTION_RENAMED_NEW_NAME) + NotifyFileSystemChange(dwEvent, szTempPath, szPath); + else + NotifyFileSystemChange(dwEvent, szPath, NULL); + } + else if (pInfo->Action == FILE_ACTION_RENAMED_OLD_NAME) + { + // save path for next FILE_ACTION_RENAMED_NEW_NAME + lstrcpynW(szTempPath, szPath, MAX_PATH); + } + + if (pInfo->NextEntryOffset == 0) + break; // there is no next entry + + // go next entry + pInfo = (PFILE_NOTIFY_INFORMATION)((LPBYTE)pInfo + pInfo->NextEntryOffset); + } +} + +// The completion routine of ReadDirectoryChangesW. +static void CALLBACK +_NotificationCompletion(DWORD dwErrorCode, + DWORD dwNumberOfBytesTransfered, + LPOVERLAPPED lpOverlapped) +{ + // MSDN: The hEvent member of the OVERLAPPED structure is not used by the + // system in this case, so you can use it yourself. We do just this, storing + // a pointer to the working struct in the overlapped structure. + DirWatch *pDirWatch = (DirWatch *)lpOverlapped->hEvent; + assert(pDirWatch != NULL); + + // If the FSD doesn't support directory change notifications, there's no + // no need to retry and requeue notification + if (dwErrorCode == ERROR_INVALID_FUNCTION) + { + ERR("ERROR_INVALID_FUNCTION\n"); + return; + } + + // Also, if the notify operation was canceled (like, user moved to another + // directory), then, don't requeue notification. + if (dwErrorCode == ERROR_OPERATION_ABORTED) + { + TRACE("ERROR_OPERATION_ABORTED\n"); + if (pDirWatch->m_fDeadWatch) + delete pDirWatch; + return; + } + + // is this watch dead? + if (pDirWatch->m_fDeadWatch) + { + TRACE("m_fDeadWatch\n"); + delete pDirWatch; + return; + } + + // This likely means overflow, so force whole directory refresh. + if (dwNumberOfBytesTransfered == 0) + { + // do notify a SHCNE_UPDATEDIR + NotifyFileSystemChange(SHCNE_UPDATEDIR, pDirWatch->m_szDir, NULL); + } + else + { + // do notify + _ProcessNotification(pDirWatch); + } + + // restart a watch + _BeginRead(pDirWatch); +} + +// convert events to notification filter +static DWORD +GetFilterFromEvents(DWORD fEvents) +{ + // FIXME + return (FILE_NOTIFY_CHANGE_FILE_NAME | + FILE_NOTIFY_CHANGE_DIR_NAME | + FILE_NOTIFY_CHANGE_CREATION | + FILE_NOTIFY_CHANGE_SIZE); +} + +// Restart a watch by using ReadDirectoryChangesW function +static BOOL _BeginRead(DirWatch *pDirWatch) +{ + assert(pDirWatch != NULL); + + if (pDirWatch->m_fDeadWatch) + { + delete pDirWatch; + return FALSE; // the watch is dead + } + + // initialize the buffer and the overlapped + ZeroMemory(s_buffer, sizeof(s_buffer)); + ZeroMemory(&pDirWatch->m_overlapped, sizeof(pDirWatch->m_overlapped)); + pDirWatch->m_overlapped.hEvent = (HANDLE)pDirWatch; + + // start the directory watch + DWORD dwFilter = GetFilterFromEvents(SHCNE_ALLEVENTS); + if (!ReadDirectoryChangesW(pDirWatch->m_hDir, s_buffer, sizeof(s_buffer), + pDirWatch->m_fRecursive, dwFilter, NULL, + &pDirWatch->m_overlapped, _NotificationCompletion)) + { + ERR("ReadDirectoryChangesW for '%S' failed (error: %ld)\n", + pDirWatch->m_szDir, GetLastError()); + return FALSE; // failure + } + + return TRUE; // success +} + +// create a DirWatch from a REGENTRY +static DirWatch * +CreateDirWatchFromRegEntry(LPREGENTRY pRegEntry) +{ + if (pRegEntry->ibPidl == 0) + return NULL; + + // it must be interrupt level if pRegEntry is a filesystem watch + if (!(pRegEntry->fSources & SHCNRF_InterruptLevel)) + 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 DirWatch + DirWatch *pDirWatch = DirWatch::Create(szPath, pRegEntry->fRecursive); + if (pDirWatch == NULL) + return NULL; + + return pDirWatch; +} + +////////////////////////////////////////////////////////////////////////////// + // notification target item struct ITEM { @@ -18,6 +626,7 @@ struct ITEM DWORD dwUserPID; // The user PID; that is the process ID of the target window. HANDLE hRegEntry; // The registration entry. HWND hwndBroker; // Client broker window (if any). + DirWatch *pDirWatch; // for filesystem notification (for SHCNRF_InterruptLevel) }; typedef CWinTraits < @@ -53,6 +662,7 @@ public: 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) @@ -69,13 +679,15 @@ public: 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 m_items; - BOOL AddItem(UINT nRegID, DWORD dwUserPID, HANDLE hRegEntry, HWND hwndBroker); + BOOL AddItem(UINT nRegID, DWORD dwUserPID, HANDLE hRegEntry, HWND hwndBroker, + DirWatch *pDirWatch); BOOL RemoveItemsByRegID(UINT nRegID, DWORD dwOwnerPID); void RemoveItemsByProcess(DWORD dwOwnerPID, DWORD dwUserPID); void DestroyItem(ITEM& item, DWORD dwOwnerPID, HWND *phwndBroker); @@ -94,7 +706,8 @@ CChangeNotifyServer::~CChangeNotifyServer() { } -BOOL CChangeNotifyServer::AddItem(UINT nRegID, DWORD dwUserPID, HANDLE hRegEntry, HWND hwndBroker) +BOOL CChangeNotifyServer::AddItem(UINT nRegID, DWORD dwUserPID, HANDLE hRegEntry, + HWND hwndBroker, DirWatch *pDirWatch) { // find the empty room for (INT i = 0; i < m_items.GetSize(); ++i) @@ -106,12 +719,13 @@ BOOL CChangeNotifyServer::AddItem(UINT nRegID, DWORD dwUserPID, HANDLE hRegEntry m_items[i].dwUserPID = dwUserPID; m_items[i].hRegEntry = hRegEntry; m_items[i].hwndBroker = hwndBroker; + m_items[i].pDirWatch = pDirWatch; return TRUE; } } // no empty room found - ITEM item = { nRegID, dwUserPID, hRegEntry, hwndBroker }; + ITEM item = { nRegID, dwUserPID, hRegEntry, hwndBroker, pDirWatch }; m_items.Add(item); return TRUE; } @@ -119,10 +733,20 @@ BOOL CChangeNotifyServer::AddItem(UINT nRegID, DWORD dwUserPID, HANDLE hRegEntry void CChangeNotifyServer::DestroyItem(ITEM& item, DWORD dwOwnerPID, HWND *phwndBroker) { // destroy broker if any and if first time - if (item.hwndBroker && item.hwndBroker != *phwndBroker) + HWND hwndBroker = item.hwndBroker; + item.hwndBroker = NULL; + if (hwndBroker && hwndBroker != *phwndBroker) { - ::DestroyWindow(item.hwndBroker); - *phwndBroker = item.hwndBroker; + ::DestroyWindow(hwndBroker); + *phwndBroker = hwndBroker; + } + + // request termination of pDirWatch if any + DirWatch *pDirWatch = item.pDirWatch; + item.pDirWatch = NULL; + if (pDirWatch && s_hThreadAPC) + { + QueueUserAPC(_RequestTerminationAPC, s_hThreadAPC, (ULONG_PTR)pDirWatch); } // free @@ -131,6 +755,7 @@ void CChangeNotifyServer::DestroyItem(ITEM& item, DWORD dwOwnerPID, HWND *phwndB item.dwUserPID = 0; item.hRegEntry = NULL; item.hwndBroker = NULL; + item.pDirWatch = NULL; } BOOL CChangeNotifyServer::RemoveItemsByRegID(UINT nRegID, DWORD dwOwnerPID) @@ -204,11 +829,34 @@ LRESULT CChangeNotifyServer::OnRegister(UINT uMsg, WPARAM wParam, LPARAM lParam, return FALSE; } + // create a directory watch if necessary + DirWatch *pDirWatch = CreateDirWatchFromRegEntry(pRegEntry); + if (pDirWatch) + { + // create an APC thread for directory watching + if (s_hThreadAPC == NULL) + { + unsigned tid; + s_fTerminateAllWatches = FALSE; + s_hThreadAPC = (HANDLE)_beginthreadex(NULL, 0, DirWatchThreadFuncAPC, NULL, 0, &tid); + if (s_hThreadAPC == NULL) + { + pRegEntry->nRegID = INVALID_REG_ID; + SHUnlockShared(pRegEntry); + delete pDirWatch; + return FALSE; + } + } + + // request adding the watch + QueueUserAPC(_AddDirectoryProcAPC, s_hThreadAPC, (ULONG_PTR)pDirWatch); + } + // unlock the registry entry SHUnlockShared(pRegEntry); // add an ITEM - return AddItem(m_nNextRegID, dwUserPID, hNewEntry, hwndBroker); + return AddItem(m_nNextRegID, dwUserPID, hNewEntry, hwndBroker, pDirWatch); } // Message CN_UNREGISTER: Unregister registration entries. @@ -274,6 +922,16 @@ LRESULT CChangeNotifyServer::OnRemoveByPID(UINT uMsg, WPARAM wParam, LPARAM lPar return 0; } +LRESULT CChangeNotifyServer::OnDestroy(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) +{ + if (s_hThreadAPC) + { + // request termination of all directory watches + QueueUserAPC(_RequestAllTerminationAPC, s_hThreadAPC, (ULONG_PTR)NULL); + } + return 0; +} + // get next valid registration ID UINT CChangeNotifyServer::GetNextRegID() { @@ -344,8 +1002,20 @@ BOOL CChangeNotifyServer::ShouldNotify(LPDELITICKET pTicket, LPREGENTRY pRegEntr WCHAR szPath[MAX_PATH], szPath1[MAX_PATH], szPath2[MAX_PATH]; INT cch, cch1, cch2; + // check fSources + if (pTicket->uFlags & SHCNE_INTERRUPT) + { + if (!(pRegEntry->fSources & SHCNRF_InterruptLevel)) + return FALSE; + } + else + { + if (!(pRegEntry->fSources & SHCNRF_ShellLevel)) + return FALSE; + } + if (pRegEntry->ibPidl == 0) - return TRUE; + return TRUE; // there is no PIDL // get the stored pidl pidl = (LPITEMIDLIST)((LPBYTE)pRegEntry + pRegEntry->ibPidl);