From 0ea3074fa14fa43a6ddd6eaeb9f09c78eefbc114 Mon Sep 17 00:00:00 2001 From: meyraud705 Date: Thu, 7 May 2026 21:30:39 +0200 Subject: [PATCH] Add SDL_RemovePathWatch(). Rename SDL_WatchPathForChange() to SDL_AddPathWatch(). Improve documentation. --- include/SDL3/SDL_events.h | 12 ++--- include/SDL3/SDL_filesystem.h | 27 ++++++++-- src/filesystem/SDL_filesystem.c | 15 +++++- src/filesystem/SDL_sysfilesystem.h | 3 +- src/filesystem/dummy/SDL_sysfsops.c | 6 ++- src/filesystem/posix/SDL_sysfsops.c | 75 +++++++++++++++++++++++++-- src/filesystem/windows/SDL_sysfsops.c | 6 ++- 7 files changed, 123 insertions(+), 21 deletions(-) diff --git a/include/SDL3/SDL_events.h b/include/SDL3/SDL_events.h index 7a8d4dd774..baa5b5a35a 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_CHANGED = 0x1500, /**< A watched file was written. */ - SDL_EVENT_FILE_WATCH_ERROR, /**< Watched files may have been modified, but the events are lost. */ + SDL_EVENT_FILE_DATA_WRITTEN = 0x1500, /**< Data was written in a watched file. */ + 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 */ @@ -977,16 +977,16 @@ typedef struct SDL_SensorEvent /** * File watch event structure (event.file_watch.*) * - * You can add file to the watch list with SDL_WatchPathForChanges(). + * You can add file to the watch list with SDL_AddPathWatch(). * - * \sa SDL_WatchPathForChanges + * \sa SDL_AddPathWatch */ typedef struct SDL_FileWatchEvent { - SDL_EventType type; /**< SDL_EVENT_FILE_CHANGED or SDL_EVENT_FILE_WATCH_ERROR */ + SDL_EventType type; /**< SDL_EVENT_FILE_DATA_WRITTEN or SDL_EVENT_FILE_WATCH_ERROR */ Uint32 reserved; Uint64 timestamp; /**< In nanoseconds, populated using SDL_GetTicksNS() */ - const char *path; /**< Path of the modified file for SDL_EVENT_FILE_CHANGED, NULL for SDL_EVENT_FILE_WATCH_ERROR */ + const char *path; /**< Path of the modified file for SDL_EVENT_FILE_DATA_WRITTEN, NULL for SDL_EVENT_FILE_WATCH_ERROR */ } SDL_FileWatchEvent; /** diff --git a/include/SDL3/SDL_filesystem.h b/include/SDL3/SDL_filesystem.h index cf61e294cd..ec127c1857 100644 --- a/include/SDL3/SDL_filesystem.h +++ b/include/SDL3/SDL_filesystem.h @@ -532,7 +532,7 @@ 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_WatchPathForChanges(). + * \param userdata what was passed as `userdata` to SDL_AddPathWatch(). * \param path path of file that was modified. * * \threadsafety SDL may call this callback at any time from any thread; the @@ -543,9 +543,9 @@ 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. + * and send an SDL_EVENT_FILE_CHANGED event every time data is written in the + * file. If path is a directory, the callback will be called for every file + * in that directory that has data written into. * * \param path file or directory path to watch. * \param callback a function that is called when the watched file is modified. @@ -558,8 +558,25 @@ typedef void (SDLCALL *SDL_FileWatchCallback)(void *userdata, const char *path); * \threadsafety It is safe to call this function from any thread. * * \sa SDL_FileWatchEvent + * \sa SDL_RemovePathWatch */ -extern SDL_DECLSPEC bool SDLCALL SDL_WatchPathForChanges(const char *path, SDL_FileWatchCallback callback, void *userdata); +extern SDL_DECLSPEC bool SDLCALL SDL_AddPathWatch(const char *path, SDL_FileWatchCallback callback, void *userdata); + +/** + * Remove an file watch callback added with SDL_AddPathWatch(). + * + * This function takes the same input as SDL_AddPathWatch() to identify and + * delete the corresponding callback. + * + * \param path the path originally passed to SDL_AddPathWatch(). + * \param callback the callback originally passed to SDL_AddPathWatch(). + * \param userdata the pointer originally passed to SDL_AddPathWatch(). + * + * \threadsafety It is safe to call this function from any thread. + * + * \sa SDL_AddPathWatch + */ +extern SDL_DECLSPEC void SDLCALL SDL_RemovePathWatch(const char *path, SDL_FileWatchCallback callback, void *userdata); /* Ends C function definitions when using C++ */ #ifdef __cplusplus diff --git a/src/filesystem/SDL_filesystem.c b/src/filesystem/SDL_filesystem.c index a0568ff95d..9dce77b4a4 100644 --- a/src/filesystem/SDL_filesystem.c +++ b/src/filesystem/SDL_filesystem.c @@ -142,7 +142,7 @@ bool SDL_GetPathInfo(const char *path, SDL_PathInfo *info) return SDL_SYS_GetPathInfo(path, info); } -bool SDL_WatchPathForChanges(const char *path, SDL_FileWatchCallback cb, void *userdata) +bool SDL_AddPathWatch(const char *path, SDL_FileWatchCallback cb, void *userdata) { CHECK_PARAM(!path) { return SDL_InvalidParamError("path"); @@ -150,7 +150,18 @@ bool SDL_WatchPathForChanges(const char *path, SDL_FileWatchCallback cb, void *u CHECK_PARAM(path[0] == '\0') { return SDL_InvalidParamError("path"); } - return SDL_SYS_WatchPathForChanges(path, cb, userdata); + return SDL_SYS_AddPathWatch(path, cb, userdata); +} + +void SDL_RemovePathWatch(const char *path, SDL_FileWatchCallback cb, void *userdata) +{ + CHECK_PARAM(!path) { + return; + } + CHECK_PARAM(path[0] == '\0') { + return; + } + SDL_SYS_RemovePathWatch(path, cb, userdata); } static bool EverythingMatch(const char *pattern, const char *str, bool *matched_to_dir) diff --git a/src/filesystem/SDL_sysfilesystem.h b/src/filesystem/SDL_sysfilesystem.h index 31eb4fcffb..3af35e891d 100644 --- a/src/filesystem/SDL_sysfilesystem.h +++ b/src/filesystem/SDL_sysfilesystem.h @@ -35,7 +35,8 @@ 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_WatchPathForChanges(const char *path, SDL_FileWatchCallback cb, void *userdata); +extern bool SDL_SYS_AddPathWatch(const char *path, SDL_FileWatchCallback cb, void *userdata); +extern void SDL_SYS_RemovePathWatch(const char *path, SDL_FileWatchCallback cb, void *userdata); extern void SDL_SYS_QuitPathWatch(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 30ed7779ae..bf5b1af120 100644 --- a/src/filesystem/dummy/SDL_sysfsops.c +++ b/src/filesystem/dummy/SDL_sysfsops.c @@ -58,11 +58,15 @@ bool SDL_SYS_GetPathInfo(const char *path, SDL_PathInfo *info) return SDL_Unsupported(); } -bool SDL_SYS_WatchPathForChanges(const char *path, SDL_FileWatchCallback cb, void *userdata) +bool SDL_SYS_AddPathWatch(const char *path, SDL_FileWatchCallback cb, void *userdata) { return SDL_Unsupported(); } +void SDL_SYS_RemovePathWatch(const char *path, SDL_FileWatchCallback cb, void *userdata) +{ +} + void SDL_SYS_QuitPathWatch(void) { } diff --git a/src/filesystem/posix/SDL_sysfsops.c b/src/filesystem/posix/SDL_sysfsops.c index a440b93447..a2a1fe7974 100644 --- a/src/filesystem/posix/SDL_sysfsops.c +++ b/src/filesystem/posix/SDL_sysfsops.c @@ -451,9 +451,10 @@ static int SDL_inotify_init1(void) return fd; } #endif // HAVE_INOTIFY_INIT1 + #endif // HAVE_INOTIFY -bool SDL_SYS_WatchPathForChanges(const char *path, SDL_FileWatchCallback cb, void *user_data) +bool SDL_SYS_AddPathWatch(const char *path, SDL_FileWatchCallback cb, void *user_data) { #ifdef HAVE_INOTIFY if (!watch_descriptor_table) { @@ -487,7 +488,11 @@ bool SDL_SYS_WatchPathForChanges(const char *path, SDL_FileWatchCallback cb, voi return false; } } + const size_t slen = SDL_strlen(path); + if (slen >= PATH_MAX) { + return SDL_SetError("path too long"); + } WatchEntry *watch_entry = SDL_malloc(sizeof(*watch_entry) + slen + 1); if (!watch_entry) { return false; @@ -507,7 +512,7 @@ bool SDL_SYS_WatchPathForChanges(const char *path, SDL_FileWatchCallback cb, voi SDL_free(watch_entry); return SDL_SetError("inotify_add_watch failed: %s", strerror(errno)); } - if (!SDL_InsertIntoHashTable(watch_descriptor_table, (void *)(intptr_t)wd, watch_entry, 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(watch_entry); @@ -561,20 +566,24 @@ static int SDL_FileWatchThread(void *userdata) while (remain > 0) { const WatchEntry *watch_entry; - if (SDL_FindInHashTable(watch_descriptor_table, (void *)(intptr_t)buf.event.wd, (const void **)&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) { SendFileWatchEvent(SDL_EVENT_FILE_WATCH_ERROR, NULL); + } else if (buf.event.mask & IN_IGNORED) { + // removing a watch generate an IN_IGNORED event + } else if (buf.event.mask & IN_UNMOUNT) { + // file system containing watched path was unmounted } else if (buf.event.len != 0) { (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); + SendFileWatchEvent(SDL_EVENT_FILE_DATA_WRITTEN, path); } else { if (watch_entry->callback) { watch_entry->callback(watch_entry->user_data, watch_entry->path); } - SendFileWatchEvent(SDL_EVENT_FILE_CHANGED, watch_entry->path); + SendFileWatchEvent(SDL_EVENT_FILE_DATA_WRITTEN, watch_entry->path); } } @@ -589,8 +598,64 @@ static int SDL_FileWatchThread(void *userdata) } return 0; } + +typedef struct FindWatchEntryByValueData { + WatchEntry *entry_to_find; + const void *key_found; +} FindWatchEntryByValueData; + +static bool SDLCALL FindWatchEntryByValue(void *userdata, const SDL_HashTable *table, const void *key, const void *value) +{ + FindWatchEntryByValueData *d = (FindWatchEntryByValueData *) userdata; + const WatchEntry *iterator = (const WatchEntry *) value; + if (SDL_strcmp(iterator->path, d->entry_to_find->path) == 0 + && iterator->callback == d->entry_to_find->user_data + && iterator->user_data == d->entry_to_find->user_data) { + d->key_found = key; + return false; + } + return true; +} #endif // HAVE_INOTIFY +void SDL_SYS_RemovePathWatch(const char *path, SDL_FileWatchCallback cb, void *user_data) +{ +#ifdef HAVE_INOTIFY + if (!watch_descriptor_table) { + return; + } + const size_t slen = SDL_strlen(path); + if (slen >= PATH_MAX) { + return; + } + + union LongestWatchEntry{ + WatchEntry entry; + char enougn_for_longest_path[sizeof(WatchEntry) + PATH_MAX]; + } watch_entry; + watch_entry.entry.callback = cb; + watch_entry.entry.user_data = user_data; + SDL_memcpy(watch_entry.entry.path, path, slen + 1); + // remove separator at the end of the path, it is added back when + // concatenating directory path and file name + if (watch_entry.entry.path[slen - 1] == '/') { + watch_entry.entry.path[slen - 1] = '\0'; + } + + FindWatchEntryByValueData data = {&watch_entry.entry, (void *) (intptr_t) -1}; + SDL_LockMutex(file_watch_lock); + if (!SDL_IterateHashTable(watch_descriptor_table, FindWatchEntryByValue, &data)) { + SDL_UnlockMutex(file_watch_lock); + return; + } + if (data.key_found != (void *) (intptr_t) -1) { + SDL_RemoveFromHashTable(watch_descriptor_table, data.key_found); + inotify_rm_watch(inotify_fd, (int) (intptr_t) data.key_found); + } + SDL_UnlockMutex(file_watch_lock); +#endif // HAVE_INOTIFY +} + void SDL_SYS_QuitPathWatch(void) { #ifdef HAVE_INOTIFY diff --git a/src/filesystem/windows/SDL_sysfsops.c b/src/filesystem/windows/SDL_sysfsops.c index 7a6e9dd95f..b5dde464b6 100644 --- a/src/filesystem/windows/SDL_sysfsops.c +++ b/src/filesystem/windows/SDL_sysfsops.c @@ -231,11 +231,15 @@ bool SDL_SYS_GetPathInfo(const char *path, SDL_PathInfo *info) return true; } -bool SDL_SYS_WatchPathForChanges(const char *path, SDL_FileWatchCallback cb, void *userdata) +bool SDL_SYS_AddPathWatch(const char *path, SDL_FileWatchCallback cb, void *userdata) { return SDL_Unsupported(); } +void SDL_SYS_RemovePathWatch(const char *path, SDL_FileWatchCallback cb, void *userdata) +{ +} + void SDL_SYS_QuitPathWatch(void) { }