Add callback to file watch.

This commit is contained in:
meyraud705 2026-04-21 23:02:17 +02:00
parent 5ed86c7580
commit 7b8d6155cc
9 changed files with 116 additions and 64 deletions

View file

@ -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
{

View file

@ -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
}

View file

@ -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

View file

@ -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)

View file

@ -25,6 +25,5 @@
extern void SDL_InitFilesystem(void);
extern void SDL_QuitFilesystem(void);
extern void SDL_UpdateFileWatch(void);
#endif

View file

@ -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);

View file

@ -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)
{
}

View file

@ -26,6 +26,8 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
// System dependent filesystem routines
#include <SDL3/SDL_atomic.h>
#include <SDL3/SDL_thread.h>
#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);

View file

@ -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)
{
}