Skip to content

Commit 7b8d615

Browse files
committed
Add callback to file watch.
1 parent 5ed86c7 commit 7b8d615

9 files changed

Lines changed: 116 additions & 64 deletions

File tree

include/SDL3/SDL_events.h

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -260,8 +260,8 @@ typedef enum SDL_EventType
260260
SDL_EVENT_CAMERA_DEVICE_DENIED, /**< A camera device has been denied for use by the user. */
261261

262262
/* File watch events */
263-
SDL_EVENT_FILE_WATCH_ERROR = 0x1500, /**< Watched files may have been modified, but the events are lost. */
264-
SDL_EVENT_FILE_CHANGED, /**< A watched file was written. */
263+
SDL_EVENT_FILE_CHANGED = 0x1500, /**< A watched file was written. */
264+
SDL_EVENT_FILE_WATCH_ERROR, /**< Watched files may have been modified, but the events are lost. */
265265

266266
/* Render events */
267267
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
976976

977977
/**
978978
* File watch event structure (event.file_watch.*)
979+
*
980+
* You can add file to the watch list with SDL_WatchFileForChanges().
981+
*
982+
* \sa SDL_WatchFileForChanges
979983
*/
980984
typedef struct SDL_FileWatchEvent
981985
{

include/SDL3/SDL_filesystem.h

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -529,6 +529,38 @@ extern SDL_DECLSPEC char ** SDLCALL SDL_GlobDirectory(const char *path, const ch
529529
*/
530530
extern SDL_DECLSPEC char * SDLCALL SDL_GetCurrentDirectory(void);
531531

532+
/**
533+
* A function pointer used for callbacks that watch for files change.
534+
*
535+
* \param userdata what was passed as `userdata` to SDL_WatchFileForChanges().
536+
* \param path path of file that was modified.
537+
*
538+
* \threadsafety SDL may call this callback at any time from any thread; the
539+
* application is responsible for locking resources the callback
540+
* touches that need to be protected.
541+
*/
542+
typedef void (SDLCALL *SDL_FileWatchCallback)(void *userdata, const char *path);
543+
544+
/**
545+
* This function adds a file watcher that will fires an app-provided callback
546+
* and send an SDL_EVENT_FILE_CHANGED event every time the file is modified. If
547+
* path is a directory, the callback will be called for every file modified in
548+
* that directory.
549+
*
550+
* \param path file or directory path to watch.
551+
* \param callback a function that is called when the watched file is modified.
552+
* Can be NULL if you only want to receive event.
553+
* \param userdata a pointer that is passed to `callback`.
554+
*
555+
* \returns true on success or false on failure; call SDL_GetError() for more
556+
* information.
557+
*
558+
* \threadsafety It is safe to call this function from any thread.
559+
*
560+
* \sa SDL_FileWatchEvent
561+
*/
562+
extern SDL_DECLSPEC bool SDLCALL SDL_WatchFileForChanges(const char *path, SDL_FileWatchCallback callback, void *userdata);
563+
532564
/* Ends C function definitions when using C++ */
533565
#ifdef __cplusplus
534566
}

src/events/SDL_events.c

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@
2828
#include "../SDL_hints_c.h"
2929
#include "../audio/SDL_audio_c.h"
3030
#include "../camera/SDL_camera_c.h"
31-
#include "../filesystem/SDL_filesystem_c.h"
3231
#include "../timer/SDL_timer_c.h"
3332
#include "../core/linux/SDL_udev.h"
3433
#ifndef SDL_JOYSTICK_DISABLED
@@ -1459,8 +1458,6 @@ bool SDL_RunOnMainThread(SDL_MainThreadCallback callback, void *userdata, bool w
14591458

14601459
void SDL_PumpEventMaintenance(void)
14611460
{
1462-
SDL_UpdateFileWatch();
1463-
14641461
#ifdef SDL_USE_LIBUDEV
14651462
SDL_UDEV_Poll();
14661463
#endif

src/filesystem/SDL_filesystem.c

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -142,17 +142,12 @@ bool SDL_GetPathInfo(const char *path, SDL_PathInfo *info)
142142
return SDL_SYS_GetPathInfo(path, info);
143143
}
144144

145-
bool SDL_WatchFileForChanges(const char *path)
145+
bool SDL_WatchFileForChanges(const char *path, SDL_FileWatchCallback cb, void *userdata)
146146
{
147147
CHECK_PARAM(!path) {
148148
return SDL_InvalidParamError("path");
149149
}
150-
return SDL_SYS_WatchFileForChanges(path);
151-
}
152-
153-
void SDL_UpdateFileWatch(void)
154-
{
155-
SDL_SYS_UpdateFileWatch();
150+
return SDL_SYS_WatchFileForChanges(path, cb, userdata);
156151
}
157152

158153
static bool EverythingMatch(const char *pattern, const char *str, bool *matched_to_dir)

src/filesystem/SDL_filesystem_c.h

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,5 @@
2525
extern void SDL_InitFilesystem(void);
2626
extern void SDL_QuitFilesystem(void);
2727

28-
extern void SDL_UpdateFileWatch(void);
2928
#endif
3029

src/filesystem/SDL_sysfilesystem.h

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,7 @@ extern bool SDL_SYS_CopyFile(const char *oldpath, const char *newpath);
3535
extern bool SDL_SYS_CreateDirectory(const char *path);
3636
extern bool SDL_SYS_GetPathInfo(const char *path, SDL_PathInfo *info);
3737

38-
extern bool SDL_SYS_WatchFileForChanges(const char *path);
39-
extern void SDL_SYS_UpdateFileWatch(void);
38+
extern bool SDL_SYS_WatchFileForChanges(const char *path, SDL_FileWatchCallback cb, void *userdata);
4039
extern void SDL_SYS_QuitFileWatch(void);
4140

4241
typedef bool (*SDL_GlobEnumeratorFunc)(const char *path, SDL_EnumerateDirectoryCallback cb, void *cbuserdata, void *userdata);

src/filesystem/dummy/SDL_sysfsops.c

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -58,15 +58,11 @@ bool SDL_SYS_GetPathInfo(const char *path, SDL_PathInfo *info)
5858
return SDL_Unsupported();
5959
}
6060

61-
bool SDL_SYS_WatchFileForChanges(const char *path)
61+
bool SDL_SYS_WatchFileForChanges(const char *path, SDL_FileWatchCallback cb, void *userdata)
6262
{
6363
return SDL_Unsupported();
6464
}
6565

66-
void SDL_SYS_UpdateFileWatch(void)
67-
{
68-
}
69-
7066
void SDL_SYS_QuitFileWatch(void)
7167
{
7268
}

src/filesystem/posix/SDL_sysfsops.c

Lines changed: 73 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@
2626
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
2727
// System dependent filesystem routines
2828

29+
#include <SDL3/SDL_atomic.h>
30+
#include <SDL3/SDL_thread.h>
2931
#include "../SDL_sysfilesystem.h"
3032
#include "../../SDL_hashtable.h"
3133
#include "../../events/SDL_events_c.h"
@@ -419,8 +421,18 @@ bool SDL_SYS_GetPathInfo(const char *path, SDL_PathInfo *info)
419421

420422
#ifdef HAVE_INOTIFY
421423
static int inotify_fd = -1;
422-
static SDL_HashTable *watch_descriptor_table = NULL; // stores directory or file path for a watch descriptor
424+
typedef struct WatchEntry
425+
{
426+
SDL_FileWatchCallback callback;
427+
void *user_data;
428+
char path[]; // directory or file path
429+
} WatchEntry;
430+
static SDL_HashTable *watch_descriptor_table = NULL; // stores WatchEntry for a watch descriptor
431+
432+
static int SDL_FileWatchThread(void *user_data);
433+
static SDL_Thread *file_watch_thread = NULL;
423434
static SDL_Mutex *file_watch_lock = NULL;
435+
static SDL_AtomicInt quit_watch_file;
424436

425437
#ifdef HAVE_INOTIFY_INIT1
426438
static int SDL_inotify_init1(void)
@@ -441,7 +453,7 @@ static int SDL_inotify_init1(void)
441453
#endif // HAVE_INOTIFY_INIT1
442454
#endif // HAVE_INOTIFY
443455

444-
bool SDL_SYS_WatchFileForChanges(const char *path)
456+
bool SDL_SYS_WatchFileForChanges(const char *path, SDL_FileWatchCallback cb, void *user_data)
445457
{
446458
#ifdef HAVE_INOTIFY
447459
if (!watch_descriptor_table) {
@@ -463,29 +475,41 @@ bool SDL_SYS_WatchFileForChanges(const char *path)
463475
inotify_fd = -1;
464476
return false;
465477
}
478+
file_watch_thread = SDL_CreateThread(SDL_FileWatchThread, "SDL_FileWatch", NULL);
479+
SDL_SetAtomicInt(&quit_watch_file, 0);
480+
if (!file_watch_thread) {
481+
SDL_DestroyHashTable(watch_descriptor_table);
482+
watch_descriptor_table = NULL;
483+
close(inotify_fd);
484+
inotify_fd = -1;
485+
SDL_DestroyMutex(file_watch_lock);
486+
file_watch_lock = NULL;
487+
return false;
488+
}
466489
}
467-
468-
char *p = SDL_strdup(path);
469-
if (!p) {
490+
const size_t slen = SDL_strlen(path);
491+
WatchEntry *watch_entry = SDL_malloc(sizeof(*watch_entry) + slen + 1);
492+
if (!watch_entry) {
470493
return false;
471494
}
495+
watch_entry->callback = cb;
496+
watch_entry->user_data = user_data;
497+
SDL_memcpy(watch_entry->path, path, slen + 1);
472498
// remove separator at the end of the path
473-
const size_t slen = SDL_strlen(p);
474-
if (p[slen - 1] == '/') {
475-
p[slen - 1] = '\0';
499+
if (watch_entry->path[slen - 1] == '/') {
500+
watch_entry->path[slen - 1] = '\0';
476501
}
477-
478502
SDL_LockMutex(file_watch_lock);
479-
int wd = inotify_add_watch(inotify_fd, p, IN_MODIFY);
503+
int wd = inotify_add_watch(inotify_fd, path, IN_MODIFY);
480504
if (wd == -1) {
481505
SDL_UnlockMutex(file_watch_lock);
482-
SDL_free(p);
506+
SDL_free(watch_entry);
483507
return SDL_SetError("inotify_add_watch failed: %s", strerror(errno));
484508
}
485-
if (!SDL_InsertIntoHashTable(watch_descriptor_table, (void *)(intptr_t)wd, p, false)) {
509+
if (!SDL_InsertIntoHashTable(watch_descriptor_table, (void *)(intptr_t)wd, watch_entry, false)) {
486510
inotify_rm_watch(inotify_fd, wd);
487511
SDL_UnlockMutex(file_watch_lock);
488-
SDL_free(p);
512+
SDL_free(watch_entry);
489513
return false;
490514
}
491515
SDL_UnlockMutex(file_watch_lock);
@@ -495,10 +519,27 @@ bool SDL_SYS_WatchFileForChanges(const char *path)
495519
#endif // HAVE_INOTIFY
496520
}
497521

498-
void SDL_SYS_UpdateFileWatch(void)
499-
{
500522
#ifdef HAVE_INOTIFY
501-
if (inotify_fd >= 0) {
523+
static void SendFileWatchEvent(SDL_EventType event_type, const char *path) {
524+
if (SDL_EventEnabled(event_type)) {
525+
SDL_Event event;
526+
SDL_zero(event);
527+
event.type = event_type;
528+
event.common.timestamp = 0;
529+
if (path) {
530+
event.file_watch.path = SDL_CreateTemporaryString(path);
531+
if (!event.file_watch.path) {
532+
return;
533+
}
534+
}
535+
SDL_PushEvent(&event);
536+
}
537+
}
538+
539+
static int SDL_FileWatchThread(void *userdata)
540+
{
541+
while (SDL_GetAtomicInt(&quit_watch_file) == 0) {
542+
SDL_Delay(100);
502543
SDL_LockMutex(file_watch_lock);
503544
union
504545
{
@@ -518,31 +559,21 @@ void SDL_SYS_UpdateFileWatch(void)
518559
}
519560

520561
while (remain > 0) {
521-
const char *watched_path;
522-
if (SDL_FindInHashTable(watch_descriptor_table, (void *)(intptr_t)buf.event.wd, (const void **)&watched_path)) {
523-
const char *path_tmp;
524-
SDL_EventType event_type;
525-
bool post_event = true;
562+
const WatchEntry *watch_entry;
563+
if (SDL_FindInHashTable(watch_descriptor_table, (void *)(intptr_t)buf.event.wd, (const void **)&watch_entry)) {
526564
if (buf.event.mask & IN_Q_OVERFLOW) {
527-
event_type = SDL_EVENT_FILE_WATCH_ERROR;
528-
path_tmp = NULL;
565+
SendFileWatchEvent(SDL_EVENT_FILE_WATCH_ERROR, NULL);
529566
} else if (buf.event.len != 0) {
530-
(void)SDL_snprintf(path, SDL_arraysize(path), "%s/%s", watched_path, buf.event.name);
531-
event_type = SDL_EVENT_FILE_CHANGED;
532-
path_tmp = SDL_CreateTemporaryString(path);
533-
post_event = (path_tmp != NULL);
567+
(void)SDL_snprintf(path, SDL_arraysize(path), "%s/%s", watch_entry->path, buf.event.name);
568+
if (watch_entry->callback) {
569+
watch_entry->callback(watch_entry->user_data, path);
570+
}
571+
SendFileWatchEvent(SDL_EVENT_FILE_CHANGED, path);
534572
} else {
535-
event_type = SDL_EVENT_FILE_CHANGED;
536-
path_tmp = SDL_CreateTemporaryString(watched_path);
537-
post_event = (path_tmp != NULL);
538-
}
539-
if (post_event && SDL_EventEnabled(event_type)) {
540-
SDL_Event event;
541-
SDL_zero(event);
542-
event.type = event_type;
543-
event.common.timestamp = 0;
544-
event.file_watch.path = path_tmp;
545-
SDL_PushEvent(&event);
573+
if (watch_entry->callback) {
574+
watch_entry->callback(watch_entry->user_data, watch_entry->path);
575+
}
576+
SendFileWatchEvent(SDL_EVENT_FILE_CHANGED, watch_entry->path);
546577
}
547578
}
548579

@@ -555,13 +586,16 @@ void SDL_SYS_UpdateFileWatch(void)
555586
}
556587
SDL_UnlockMutex(file_watch_lock);
557588
}
558-
#endif // HAVE_INOTIFY
589+
return 0;
559590
}
591+
#endif // HAVE_INOTIFY
560592

561593
void SDL_SYS_QuitFileWatch(void)
562594
{
563595
#ifdef HAVE_INOTIFY
564596
if (inotify_fd >= 0) {
597+
SDL_SetAtomicInt(&quit_watch_file, 0);
598+
SDL_WaitThread(file_watch_thread, NULL);
565599
close(inotify_fd);
566600
inotify_fd = -1;
567601
SDL_DestroyMutex(file_watch_lock);

src/filesystem/windows/SDL_sysfsops.c

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -231,15 +231,11 @@ bool SDL_SYS_GetPathInfo(const char *path, SDL_PathInfo *info)
231231
return true;
232232
}
233233

234-
bool SDL_SYS_WatchFileForChanges(const char *path)
234+
bool SDL_SYS_WatchFileForChanges(const char *path, SDL_FileWatchCallback cb, void *userdata)
235235
{
236236
return SDL_Unsupported();
237237
}
238238

239-
void SDL_SYS_UpdateFileWatch(void)
240-
{
241-
}
242-
243239
void SDL_SYS_QuitFileWatch(void)
244240
{
245241
}

0 commit comments

Comments
 (0)