diff --git a/include/SDL3/SDL_events.h b/include/SDL3/SDL_events.h index 71b0eabf15..9d84574c83 100644 --- a/include/SDL3/SDL_events.h +++ b/include/SDL3/SDL_events.h @@ -260,8 +260,8 @@ typedef enum SDL_EventType SDL_EVENT_CAMERA_DEVICE_DENIED, /**< A camera device has been denied for use by the user. */ /* File watch events */ - SDL_EVENT_FILE_WATCH_ERROR = 0x1500, /**< Watched files may have been modified, but the events are lost. */ - SDL_EVENT_FILE_CHANGED, /**< A watched file was written. */ + SDL_EVENT_FILE_CHANGED = 0x1500, /**< A watched file was written. */ + SDL_EVENT_FILE_WATCH_ERROR, /**< Watched files may have been modified, but the events are lost. */ /* Render events */ SDL_EVENT_RENDER_TARGETS_RESET = 0x2000, /**< The render targets have been reset and their contents need to be updated */ @@ -976,6 +976,10 @@ typedef struct SDL_SensorEvent /** * File watch event structure (event.file_watch.*) + * + * You can add file to the watch list with SDL_WatchFileForChanges(). + * + * \sa SDL_WatchFileForChanges */ typedef struct SDL_FileWatchEvent { diff --git a/include/SDL3/SDL_filesystem.h b/include/SDL3/SDL_filesystem.h index 53e7ac9b43..94cb272030 100644 --- a/include/SDL3/SDL_filesystem.h +++ b/include/SDL3/SDL_filesystem.h @@ -529,6 +529,38 @@ extern SDL_DECLSPEC char ** SDLCALL SDL_GlobDirectory(const char *path, const ch */ extern SDL_DECLSPEC char * SDLCALL SDL_GetCurrentDirectory(void); +/** + * A function pointer used for callbacks that watch for files change. + * + * \param userdata what was passed as `userdata` to SDL_WatchFileForChanges(). + * \param path path of file that was modified. + * + * \threadsafety SDL may call this callback at any time from any thread; the + * application is responsible for locking resources the callback + * touches that need to be protected. + */ +typedef void (SDLCALL *SDL_FileWatchCallback)(void *userdata, const char *path); + +/** + * This function adds a file watcher that will fires an app-provided callback + * and send an SDL_EVENT_FILE_CHANGED event every time the file is modified. If + * path is a directory, the callback will be called for every file modified in + * that directory. + * + * \param path file or directory path to watch. + * \param callback a function that is called when the watched file is modified. + * Can be NULL if you only want to receive event. + * \param userdata a pointer that is passed to `callback`. + * + * \returns true on success or false on failure; call SDL_GetError() for more + * information. + * + * \threadsafety It is safe to call this function from any thread. + * + * \sa SDL_FileWatchEvent + */ +extern SDL_DECLSPEC bool SDLCALL SDL_WatchFileForChanges(const char *path, SDL_FileWatchCallback callback, void *userdata); + /* Ends C function definitions when using C++ */ #ifdef __cplusplus } diff --git a/src/events/SDL_events.c b/src/events/SDL_events.c index 162d26e134..c909a2a583 100644 --- a/src/events/SDL_events.c +++ b/src/events/SDL_events.c @@ -28,7 +28,6 @@ #include "../SDL_hints_c.h" #include "../audio/SDL_audio_c.h" #include "../camera/SDL_camera_c.h" -#include "../filesystem/SDL_filesystem_c.h" #include "../timer/SDL_timer_c.h" #include "../core/linux/SDL_udev.h" #ifndef SDL_JOYSTICK_DISABLED @@ -1459,8 +1458,6 @@ bool SDL_RunOnMainThread(SDL_MainThreadCallback callback, void *userdata, bool w void SDL_PumpEventMaintenance(void) { - SDL_UpdateFileWatch(); - #ifdef SDL_USE_LIBUDEV SDL_UDEV_Poll(); #endif diff --git a/src/filesystem/SDL_filesystem.c b/src/filesystem/SDL_filesystem.c index c89be22f1a..646c78b606 100644 --- a/src/filesystem/SDL_filesystem.c +++ b/src/filesystem/SDL_filesystem.c @@ -142,17 +142,12 @@ bool SDL_GetPathInfo(const char *path, SDL_PathInfo *info) return SDL_SYS_GetPathInfo(path, info); } -bool SDL_WatchFileForChanges(const char *path) +bool SDL_WatchFileForChanges(const char *path, SDL_FileWatchCallback cb, void *userdata) { CHECK_PARAM(!path) { return SDL_InvalidParamError("path"); } - return SDL_SYS_WatchFileForChanges(path); -} - -void SDL_UpdateFileWatch(void) -{ - SDL_SYS_UpdateFileWatch(); + return SDL_SYS_WatchFileForChanges(path, cb, userdata); } static bool EverythingMatch(const char *pattern, const char *str, bool *matched_to_dir) diff --git a/src/filesystem/SDL_filesystem_c.h b/src/filesystem/SDL_filesystem_c.h index 5f52280963..685cda2822 100644 --- a/src/filesystem/SDL_filesystem_c.h +++ b/src/filesystem/SDL_filesystem_c.h @@ -25,6 +25,5 @@ extern void SDL_InitFilesystem(void); extern void SDL_QuitFilesystem(void); -extern void SDL_UpdateFileWatch(void); #endif diff --git a/src/filesystem/SDL_sysfilesystem.h b/src/filesystem/SDL_sysfilesystem.h index 4584f789d4..8b9d1baa13 100644 --- a/src/filesystem/SDL_sysfilesystem.h +++ b/src/filesystem/SDL_sysfilesystem.h @@ -35,8 +35,7 @@ extern bool SDL_SYS_CopyFile(const char *oldpath, const char *newpath); extern bool SDL_SYS_CreateDirectory(const char *path); extern bool SDL_SYS_GetPathInfo(const char *path, SDL_PathInfo *info); -extern bool SDL_SYS_WatchFileForChanges(const char *path); -extern void SDL_SYS_UpdateFileWatch(void); +extern bool SDL_SYS_WatchFileForChanges(const char *path, SDL_FileWatchCallback cb, void *userdata); extern void SDL_SYS_QuitFileWatch(void); typedef bool (*SDL_GlobEnumeratorFunc)(const char *path, SDL_EnumerateDirectoryCallback cb, void *cbuserdata, void *userdata); diff --git a/src/filesystem/dummy/SDL_sysfsops.c b/src/filesystem/dummy/SDL_sysfsops.c index f3aac0281b..d52bf6ef55 100644 --- a/src/filesystem/dummy/SDL_sysfsops.c +++ b/src/filesystem/dummy/SDL_sysfsops.c @@ -58,15 +58,11 @@ bool SDL_SYS_GetPathInfo(const char *path, SDL_PathInfo *info) return SDL_Unsupported(); } -bool SDL_SYS_WatchFileForChanges(const char *path) +bool SDL_SYS_WatchFileForChanges(const char *path, SDL_FileWatchCallback cb, void *userdata) { return SDL_Unsupported(); } -void SDL_SYS_UpdateFileWatch(void) -{ -} - void SDL_SYS_QuitFileWatch(void) { } diff --git a/src/filesystem/posix/SDL_sysfsops.c b/src/filesystem/posix/SDL_sysfsops.c index 3dde451ec2..72a566e4c8 100644 --- a/src/filesystem/posix/SDL_sysfsops.c +++ b/src/filesystem/posix/SDL_sysfsops.c @@ -26,6 +26,8 @@ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ // System dependent filesystem routines +#include +#include #include "../SDL_sysfilesystem.h" #include "../../SDL_hashtable.h" #include "../../events/SDL_events_c.h" @@ -419,8 +421,18 @@ bool SDL_SYS_GetPathInfo(const char *path, SDL_PathInfo *info) #ifdef HAVE_INOTIFY static int inotify_fd = -1; -static SDL_HashTable *watch_descriptor_table = NULL; // stores directory or file path for a watch descriptor +typedef struct WatchEntry +{ + SDL_FileWatchCallback callback; + void *user_data; + char path[]; // directory or file path +} WatchEntry; +static SDL_HashTable *watch_descriptor_table = NULL; // stores WatchEntry for a watch descriptor + +static int SDL_FileWatchThread(void *user_data); +static SDL_Thread *file_watch_thread = NULL; static SDL_Mutex *file_watch_lock = NULL; +static SDL_AtomicInt quit_watch_file; #ifdef HAVE_INOTIFY_INIT1 static int SDL_inotify_init1(void) @@ -441,7 +453,7 @@ static int SDL_inotify_init1(void) #endif // HAVE_INOTIFY_INIT1 #endif // HAVE_INOTIFY -bool SDL_SYS_WatchFileForChanges(const char *path) +bool SDL_SYS_WatchFileForChanges(const char *path, SDL_FileWatchCallback cb, void *user_data) { #ifdef HAVE_INOTIFY if (!watch_descriptor_table) { @@ -463,29 +475,41 @@ bool SDL_SYS_WatchFileForChanges(const char *path) inotify_fd = -1; return false; } + file_watch_thread = SDL_CreateThread(SDL_FileWatchThread, "SDL_FileWatch", NULL); + SDL_SetAtomicInt(&quit_watch_file, 0); + if (!file_watch_thread) { + SDL_DestroyHashTable(watch_descriptor_table); + watch_descriptor_table = NULL; + close(inotify_fd); + inotify_fd = -1; + SDL_DestroyMutex(file_watch_lock); + file_watch_lock = NULL; + return false; + } } - - char *p = SDL_strdup(path); - if (!p) { + const size_t slen = SDL_strlen(path); + WatchEntry *watch_entry = SDL_malloc(sizeof(*watch_entry) + slen + 1); + if (!watch_entry) { return false; } + watch_entry->callback = cb; + watch_entry->user_data = user_data; + SDL_memcpy(watch_entry->path, path, slen + 1); // remove separator at the end of the path - const size_t slen = SDL_strlen(p); - if (p[slen - 1] == '/') { - p[slen - 1] = '\0'; + if (watch_entry->path[slen - 1] == '/') { + watch_entry->path[slen - 1] = '\0'; } - SDL_LockMutex(file_watch_lock); - int wd = inotify_add_watch(inotify_fd, p, IN_MODIFY); + int wd = inotify_add_watch(inotify_fd, path, IN_MODIFY); if (wd == -1) { SDL_UnlockMutex(file_watch_lock); - SDL_free(p); + SDL_free(watch_entry); return SDL_SetError("inotify_add_watch failed: %s", strerror(errno)); } - if (!SDL_InsertIntoHashTable(watch_descriptor_table, (void *)(intptr_t)wd, p, false)) { + if (!SDL_InsertIntoHashTable(watch_descriptor_table, (void *)(intptr_t)wd, watch_entry, false)) { inotify_rm_watch(inotify_fd, wd); SDL_UnlockMutex(file_watch_lock); - SDL_free(p); + SDL_free(watch_entry); return false; } SDL_UnlockMutex(file_watch_lock); @@ -495,10 +519,27 @@ bool SDL_SYS_WatchFileForChanges(const char *path) #endif // HAVE_INOTIFY } -void SDL_SYS_UpdateFileWatch(void) -{ #ifdef HAVE_INOTIFY - if (inotify_fd >= 0) { +static void SendFileWatchEvent(SDL_EventType event_type, const char *path) { + if (SDL_EventEnabled(event_type)) { + SDL_Event event; + SDL_zero(event); + event.type = event_type; + event.common.timestamp = 0; + if (path) { + event.file_watch.path = SDL_CreateTemporaryString(path); + if (!event.file_watch.path) { + return; + } + } + SDL_PushEvent(&event); + } +} + +static int SDL_FileWatchThread(void *userdata) +{ + while (SDL_GetAtomicInt(&quit_watch_file) == 0) { + SDL_Delay(100); SDL_LockMutex(file_watch_lock); union { @@ -518,31 +559,21 @@ void SDL_SYS_UpdateFileWatch(void) } while (remain > 0) { - const char *watched_path; - if (SDL_FindInHashTable(watch_descriptor_table, (void *)(intptr_t)buf.event.wd, (const void **)&watched_path)) { - const char *path_tmp; - SDL_EventType event_type; - bool post_event = true; + const WatchEntry *watch_entry; + if (SDL_FindInHashTable(watch_descriptor_table, (void *)(intptr_t)buf.event.wd, (const void **)&watch_entry)) { if (buf.event.mask & IN_Q_OVERFLOW) { - event_type = SDL_EVENT_FILE_WATCH_ERROR; - path_tmp = NULL; + SendFileWatchEvent(SDL_EVENT_FILE_WATCH_ERROR, NULL); } else if (buf.event.len != 0) { - (void)SDL_snprintf(path, SDL_arraysize(path), "%s/%s", watched_path, buf.event.name); - event_type = SDL_EVENT_FILE_CHANGED; - path_tmp = SDL_CreateTemporaryString(path); - post_event = (path_tmp != NULL); + (void)SDL_snprintf(path, SDL_arraysize(path), "%s/%s", watch_entry->path, buf.event.name); + if (watch_entry->callback) { + watch_entry->callback(watch_entry->user_data, path); + } + SendFileWatchEvent(SDL_EVENT_FILE_CHANGED, path); } else { - event_type = SDL_EVENT_FILE_CHANGED; - path_tmp = SDL_CreateTemporaryString(watched_path); - post_event = (path_tmp != NULL); - } - if (post_event && SDL_EventEnabled(event_type)) { - SDL_Event event; - SDL_zero(event); - event.type = event_type; - event.common.timestamp = 0; - event.file_watch.path = path_tmp; - SDL_PushEvent(&event); + if (watch_entry->callback) { + watch_entry->callback(watch_entry->user_data, watch_entry->path); + } + SendFileWatchEvent(SDL_EVENT_FILE_CHANGED, watch_entry->path); } } @@ -555,13 +586,16 @@ void SDL_SYS_UpdateFileWatch(void) } SDL_UnlockMutex(file_watch_lock); } -#endif // HAVE_INOTIFY + return 0; } +#endif // HAVE_INOTIFY void SDL_SYS_QuitFileWatch(void) { #ifdef HAVE_INOTIFY if (inotify_fd >= 0) { + SDL_SetAtomicInt(&quit_watch_file, 0); + SDL_WaitThread(file_watch_thread, NULL); close(inotify_fd); inotify_fd = -1; SDL_DestroyMutex(file_watch_lock); diff --git a/src/filesystem/windows/SDL_sysfsops.c b/src/filesystem/windows/SDL_sysfsops.c index f8b4f1e3ea..a8a115c8b5 100644 --- a/src/filesystem/windows/SDL_sysfsops.c +++ b/src/filesystem/windows/SDL_sysfsops.c @@ -231,15 +231,11 @@ bool SDL_SYS_GetPathInfo(const char *path, SDL_PathInfo *info) return true; } -bool SDL_SYS_WatchFileForChanges(const char *path) +bool SDL_SYS_WatchFileForChanges(const char *path, SDL_FileWatchCallback cb, void *userdata) { return SDL_Unsupported(); } -void SDL_SYS_UpdateFileWatch(void) -{ -} - void SDL_SYS_QuitFileWatch(void) { }