// For licensing and usage information, read docs/release/winui_license.txt // MASTER //**************************************************************************** // standard windows headers #include // MAME/MAMEUI headers #include "dirwatch.h" #include "mui_util.h" typedef BOOL (WINAPI *READDIRECTORYCHANGESFUNC)(HANDLE hDirectory, LPVOID lpBuffer, DWORD nBufferLength, BOOL bWatchSubtree, DWORD dwNotifyFilter, LPDWORD lpBytesReturned, LPOVERLAPPED lpOverlapped, LPOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine); struct DirWatcherEntry { struct DirWatcherEntry *pNext; HANDLE hDir; WORD nIndex; WORD nSubIndex; BOOL bWatchSubtree; OVERLAPPED overlapped; union { FILE_NOTIFY_INFORMATION notify; BYTE buffer[1024]; } u; char szDirPath[1]; }; struct DirWatcher { HMODULE hKernelModule; READDIRECTORYCHANGESFUNC pfnReadDirectoryChanges; HWND hwndTarget; UINT nMessage; HANDLE hRequestEvent; HANDLE hResponseEvent; HANDLE hThread; CRITICAL_SECTION crit; struct DirWatcherEntry *pEntries; // These are posted externally BOOL bQuit; BOOL bWatchSubtree; WORD nIndex; LPCSTR pszPathList; }; static void DirWatcher_SetupWatch(PDIRWATCHER pWatcher, struct DirWatcherEntry *pEntry) { DWORD nDummy; memset(&pEntry->u, 0, sizeof(pEntry->u)); pWatcher->pfnReadDirectoryChanges(pEntry->hDir, &pEntry->u, sizeof(pEntry->u), pEntry->bWatchSubtree, FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_LAST_WRITE | FILE_NOTIFY_CHANGE_SIZE, &nDummy, &pEntry->overlapped, NULL); } static void DirWatcher_FreeEntry(struct DirWatcherEntry *pEntry) { if (pEntry->hDir) CloseHandle(pEntry->hDir); free(pEntry); } static BOOL DirWatcher_WatchDirectory(PDIRWATCHER pWatcher, int nIndex, int nSubIndex, LPCSTR pszPath, BOOL bWatchSubtree) { struct DirWatcherEntry *pEntry; HANDLE hDir; pEntry = (DirWatcherEntry *)malloc(sizeof(*pEntry) + strlen(pszPath)); if (!pEntry) goto error; memset(pEntry, 0, sizeof(*pEntry)); strcpy(pEntry->szDirPath, pszPath); pEntry->overlapped.hEvent = pWatcher->hRequestEvent; hDir = win_create_file_utf8(pszPath, FILE_LIST_DIRECTORY, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED, NULL); if (!hDir || (hDir == INVALID_HANDLE_VALUE)) goto error; // Populate the entry pEntry->hDir = hDir; pEntry->bWatchSubtree = bWatchSubtree; pEntry->nIndex = nIndex; pEntry->nSubIndex = nSubIndex; // Link in the entry pEntry->pNext = pWatcher->pEntries; pWatcher->pEntries = pEntry; DirWatcher_SetupWatch(pWatcher, pEntry); return true; error: if (pEntry) DirWatcher_FreeEntry(pEntry); return false; } static void DirWatcher_Signal(PDIRWATCHER pWatcher, struct DirWatcherEntry *pEntry) { LPSTR pszFileName; BOOL bPause = 0; HANDLE hFile; { int nLength = WideCharToMultiByte(CP_ACP, 0, pEntry->u.notify.FileName, -1, NULL, 0, NULL, NULL); pszFileName = (LPSTR) alloca(nLength * sizeof(*pszFileName)); WideCharToMultiByte(CP_ACP, 0, pEntry->u.notify.FileName, -1, pszFileName, nLength, NULL, NULL); } // get the full path to this new file LPSTR pszFullFileName = (LPSTR) alloca(strlen(pEntry->szDirPath) + strlen(pszFileName) + 2); strcpy(pszFullFileName, pEntry->szDirPath); strcat(pszFullFileName, "\\"); strcat(pszFullFileName, pszFileName); // attempt to busy wait until any result other than ERROR_SHARING_VIOLATION // is generated int nTries = 100; do { hFile = win_create_file_utf8(pszFullFileName, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL); if (hFile != INVALID_HANDLE_VALUE) CloseHandle(hFile); bPause = (nTries--) && (hFile == INVALID_HANDLE_VALUE) && (GetLastError() == ERROR_SHARING_VIOLATION); if (bPause) Sleep(10); } while(bPause); // send the message (assuming that we have a target) if (pWatcher->hwndTarget) { TCHAR* t_filename = ui_wstring_from_utf8(pszFileName); if( !t_filename ) return; SendMessage(pWatcher->hwndTarget, pWatcher->nMessage, (pEntry->nIndex << 16) | (pEntry->nSubIndex << 0), (LPARAM)(LPCTSTR) win_tstring_strdup(t_filename)); free(t_filename); } DirWatcher_SetupWatch(pWatcher, pEntry); } static DWORD WINAPI DirWatcher_ThreadProc(LPVOID lpParameter) { LPSTR pszPathList, s; int nSubIndex = 0; PDIRWATCHER pWatcher = (PDIRWATCHER) lpParameter; struct DirWatcherEntry *pEntry; struct DirWatcherEntry **ppEntry; do { WaitForSingleObject(pWatcher->hRequestEvent, INFINITE); if (pWatcher->pszPathList) { // remove any entries with the same nIndex ppEntry = &pWatcher->pEntries; while(*ppEntry) { if ((*ppEntry)->nIndex == pWatcher->nIndex) { pEntry = *ppEntry; *ppEntry = pEntry->pNext; DirWatcher_FreeEntry(pEntry); } else { ppEntry = &(*ppEntry)->pNext; } } // allocate our own copy of the path list pszPathList = (LPSTR) alloca(strlen(pWatcher->pszPathList) + 1); strcpy(pszPathList, pWatcher->pszPathList); nSubIndex = 0; do { s = strchr(pszPathList, ';'); if (s) *s = '\0'; if (*pszPathList) { DirWatcher_WatchDirectory(pWatcher, pWatcher->nIndex, nSubIndex++, pszPathList, pWatcher->bWatchSubtree); } pszPathList = s ? s + 1 : NULL; } while(pszPathList); pWatcher->pszPathList = NULL; pWatcher->bWatchSubtree = FALSE; } else { // we have to go through the list and find what has been hit for (pEntry = pWatcher->pEntries; pEntry; pEntry = pEntry->pNext) { if (pEntry->u.notify.Action != 0) { DirWatcher_Signal(pWatcher, pEntry); } } } SetEvent(pWatcher->hResponseEvent); } while(!pWatcher->bQuit); return 0; } PDIRWATCHER DirWatcher_Init(HWND hwndTarget, UINT nMessage) { DWORD nThreadID = 0; struct DirWatcher *pWatcher = NULL; // This feature does not exist on Win9x if (GetVersion() >= 0x80000000) goto error; pWatcher = (DirWatcher *)malloc(sizeof(struct DirWatcher)); if (!pWatcher) goto error; memset(pWatcher, 0, sizeof(*pWatcher)); InitializeCriticalSection(&pWatcher->crit); pWatcher->hKernelModule = LoadLibrary(TEXT("kernel32.dll")); if (!pWatcher->hKernelModule) goto error; pWatcher->pfnReadDirectoryChanges = (READDIRECTORYCHANGESFUNC) GetProcAddress(pWatcher->hKernelModule, "ReadDirectoryChangesW"); if (!pWatcher->pfnReadDirectoryChanges) goto error; pWatcher->hRequestEvent = CreateEvent(NULL, FALSE, FALSE, NULL); if (!pWatcher->hRequestEvent) goto error; pWatcher->hResponseEvent = CreateEvent(NULL, FALSE, FALSE, NULL); if (!pWatcher->hRequestEvent) goto error; pWatcher->hThread = CreateThread(NULL, 0, DirWatcher_ThreadProc, (LPVOID) pWatcher, 0, &nThreadID); pWatcher->hwndTarget = hwndTarget; pWatcher->nMessage = nMessage; return pWatcher; error: if (pWatcher) DirWatcher_Free(pWatcher); return NULL; } BOOL DirWatcher_Watch(PDIRWATCHER pWatcher, WORD nIndex, const std::string pszPathList, BOOL bWatchSubtrees) { EnterCriticalSection(&pWatcher->crit); pWatcher->nIndex = nIndex; pWatcher->pszPathList = pszPathList.c_str(); pWatcher->bWatchSubtree = bWatchSubtrees; SetEvent(pWatcher->hRequestEvent); WaitForSingleObject(pWatcher->hResponseEvent, INFINITE); LeaveCriticalSection(&pWatcher->crit); return true; } void DirWatcher_Free(PDIRWATCHER pWatcher) { struct DirWatcherEntry *pEntry; struct DirWatcherEntry *pNextEntry; if (pWatcher->hThread) { EnterCriticalSection(&pWatcher->crit); pWatcher->bQuit = true; SetEvent(pWatcher->hRequestEvent); WaitForSingleObject(pWatcher->hThread, 1000); LeaveCriticalSection(&pWatcher->crit); CloseHandle(pWatcher->hThread); } DeleteCriticalSection(&pWatcher->crit); pEntry = pWatcher->pEntries; while(pEntry) { pNextEntry = pEntry->pNext; DirWatcher_FreeEntry(pEntry); pEntry = pNextEntry; } if (pWatcher->hKernelModule) FreeLibrary(pWatcher->hKernelModule); if (pWatcher->hRequestEvent) CloseHandle(pWatcher->hRequestEvent); if (pWatcher->hResponseEvent) CloseHandle(pWatcher->hResponseEvent); free(pWatcher); }