mirror of
https://github.com/libsdl-org/SDL.git
synced 2026-06-05 22:30:29 +00:00
Merge 0ea3074fa1 into fa2a726cc3
This commit is contained in:
commit
c1e1fc556a
7 changed files with 384 additions and 0 deletions
|
|
@ -262,6 +262,10 @@ typedef enum SDL_EventType
|
|||
SDL_EVENT_CAMERA_DEVICE_APPROVED, /**< A camera device has been approved for use by the user. */
|
||||
SDL_EVENT_CAMERA_DEVICE_DENIED, /**< A camera device has been denied for use by the user. */
|
||||
|
||||
/* File watch events */
|
||||
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 */
|
||||
SDL_EVENT_RENDER_DEVICE_RESET, /**< The device has been reset and all textures need to be recreated */
|
||||
|
|
@ -990,6 +994,21 @@ typedef struct SDL_SensorEvent
|
|||
Uint64 sensor_timestamp; /**< The timestamp of the sensor reading in nanoseconds, not necessarily synchronized with the system clock */
|
||||
} SDL_SensorEvent;
|
||||
|
||||
/**
|
||||
* File watch event structure (event.file_watch.*)
|
||||
*
|
||||
* You can add file to the watch list with SDL_AddPathWatch().
|
||||
*
|
||||
* \sa SDL_AddPathWatch
|
||||
*/
|
||||
typedef struct SDL_FileWatchEvent
|
||||
{
|
||||
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_DATA_WRITTEN, NULL for SDL_EVENT_FILE_WATCH_ERROR */
|
||||
} SDL_FileWatchEvent;
|
||||
|
||||
/**
|
||||
* The "quit requested" event
|
||||
*
|
||||
|
|
@ -1075,6 +1094,7 @@ typedef union SDL_Event
|
|||
SDL_RenderEvent render; /**< Render event data */
|
||||
SDL_DropEvent drop; /**< Drag and drop event data */
|
||||
SDL_ClipboardEvent clipboard; /**< Clipboard event data */
|
||||
SDL_FileWatchEvent file_watch; /**< File watch event data */
|
||||
|
||||
/* This is necessary for ABI compatibility between Visual C++ and GCC.
|
||||
Visual C++ will respect the push pack pragma and use 52 bytes (size of
|
||||
|
|
|
|||
|
|
@ -529,6 +529,55 @@ 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_AddPathWatch().
|
||||
* \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 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.
|
||||
* 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
|
||||
* \sa SDL_RemovePathWatch
|
||||
*/
|
||||
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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -142,6 +142,28 @@ bool SDL_GetPathInfo(const char *path, SDL_PathInfo *info)
|
|||
return SDL_SYS_GetPathInfo(path, info);
|
||||
}
|
||||
|
||||
bool SDL_AddPathWatch(const char *path, SDL_FileWatchCallback cb, void *userdata)
|
||||
{
|
||||
CHECK_PARAM(!path) {
|
||||
return SDL_InvalidParamError("path");
|
||||
}
|
||||
CHECK_PARAM(path[0] == '\0') {
|
||||
return SDL_InvalidParamError("path");
|
||||
}
|
||||
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)
|
||||
{
|
||||
SDL_assert(pattern == NULL);
|
||||
|
|
@ -565,5 +587,6 @@ void SDL_QuitFilesystem(void)
|
|||
CachedUserFolders[i] = NULL;
|
||||
}
|
||||
}
|
||||
SDL_SYS_QuitPathWatch();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -36,6 +36,10 @@ 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_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);
|
||||
typedef bool (*SDL_GlobGetPathInfoFunc)(const char *path, SDL_PathInfo *info, void *userdata);
|
||||
extern char **SDL_InternalGlobDirectory(const char *path, const char *pattern, SDL_GlobFlags flags, int *count, SDL_GlobEnumeratorFunc enumerator, SDL_GlobGetPathInfoFunc getpathinfo, void *userdata);
|
||||
|
|
|
|||
|
|
@ -58,5 +58,18 @@ bool SDL_SYS_GetPathInfo(const char *path, SDL_PathInfo *info)
|
|||
return SDL_Unsupported();
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
}
|
||||
|
||||
#endif // SDL_FSOPS_DUMMY
|
||||
|
||||
|
|
|
|||
|
|
@ -26,7 +26,11 @@
|
|||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||||
// 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"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
|
@ -39,6 +43,11 @@
|
|||
#include "../../core/android/SDL_android.h"
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_INOTIFY
|
||||
#include <sys/inotify.h>
|
||||
#include <fcntl.h>
|
||||
#include <limits.h>
|
||||
#endif
|
||||
|
||||
bool SDL_SYS_EnumerateDirectory(const char *path, SDL_EnumerateDirectoryCallback cb, void *userdata)
|
||||
{
|
||||
|
|
@ -424,6 +433,259 @@ bool SDL_SYS_GetPathInfo(const char *path, SDL_PathInfo *info)
|
|||
return true;
|
||||
}
|
||||
|
||||
#ifdef HAVE_INOTIFY
|
||||
static int inotify_fd = -1;
|
||||
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 SDLCALL 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)
|
||||
{
|
||||
return inotify_init1(IN_NONBLOCK | IN_CLOEXEC);
|
||||
}
|
||||
#else
|
||||
static int SDL_inotify_init1(void)
|
||||
{
|
||||
int fd = inotify_init();
|
||||
if (fd < 0) {
|
||||
return -1;
|
||||
}
|
||||
fcntl(fd, F_SETFL, O_NONBLOCK);
|
||||
fcntl(fd, F_SETFD, FD_CLOEXEC);
|
||||
return fd;
|
||||
}
|
||||
#endif // HAVE_INOTIFY_INIT1
|
||||
|
||||
#endif // HAVE_INOTIFY
|
||||
|
||||
bool SDL_SYS_AddPathWatch(const char *path, SDL_FileWatchCallback cb, void *user_data)
|
||||
{
|
||||
#ifdef HAVE_INOTIFY
|
||||
if (!watch_descriptor_table) {
|
||||
watch_descriptor_table = SDL_CreateHashTable(0, false, SDL_HashID, SDL_KeyMatchID, SDL_DestroyHashValue, NULL);
|
||||
if (!watch_descriptor_table) {
|
||||
return false;
|
||||
}
|
||||
inotify_fd = SDL_inotify_init1();
|
||||
if (inotify_fd == -1) {
|
||||
SDL_DestroyHashTable(watch_descriptor_table);
|
||||
watch_descriptor_table = NULL;
|
||||
return SDL_SetError("Could not initialize inotify: %s", strerror(errno));
|
||||
}
|
||||
file_watch_lock = SDL_CreateMutex();
|
||||
if (!file_watch_lock) {
|
||||
SDL_DestroyHashTable(watch_descriptor_table);
|
||||
watch_descriptor_table = NULL;
|
||||
close(inotify_fd);
|
||||
inotify_fd = -1;
|
||||
return false;
|
||||
}
|
||||
SDL_SetAtomicInt(&quit_watch_file, 0);
|
||||
file_watch_thread = SDL_CreateThread(SDL_FileWatchThread, "SDL_FileWatch", NULL);
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
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, it is added back when
|
||||
// concatenating directory path and file name
|
||||
if (watch_entry->path[slen - 1] == '/') {
|
||||
watch_entry->path[slen - 1] = '\0';
|
||||
}
|
||||
SDL_LockMutex(file_watch_lock);
|
||||
int wd = inotify_add_watch(inotify_fd, path, IN_MODIFY);
|
||||
if (wd == -1) {
|
||||
SDL_UnlockMutex(file_watch_lock);
|
||||
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)) {
|
||||
inotify_rm_watch(inotify_fd, wd);
|
||||
SDL_UnlockMutex(file_watch_lock);
|
||||
SDL_free(watch_entry);
|
||||
return false;
|
||||
}
|
||||
SDL_UnlockMutex(file_watch_lock);
|
||||
return true;
|
||||
#else
|
||||
return SDL_Unsupported();
|
||||
#endif // HAVE_INOTIFY
|
||||
}
|
||||
|
||||
#ifdef HAVE_INOTIFY
|
||||
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
|
||||
{
|
||||
struct inotify_event event;
|
||||
char storage[4096];
|
||||
char enough_for_inotify[sizeof(struct inotify_event) + NAME_MAX + 1];
|
||||
} buf;
|
||||
ssize_t bytes;
|
||||
size_t remain = 0;
|
||||
size_t len;
|
||||
char path[PATH_MAX];
|
||||
|
||||
bytes = read(inotify_fd, &buf, sizeof(buf));
|
||||
|
||||
if (bytes > 0) {
|
||||
remain = (size_t)bytes;
|
||||
}
|
||||
|
||||
while (remain > 0) {
|
||||
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) {
|
||||
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_DATA_WRITTEN, path);
|
||||
} else {
|
||||
if (watch_entry->callback) {
|
||||
watch_entry->callback(watch_entry->user_data, watch_entry->path);
|
||||
}
|
||||
SendFileWatchEvent(SDL_EVENT_FILE_DATA_WRITTEN, watch_entry->path);
|
||||
}
|
||||
}
|
||||
|
||||
len = sizeof(struct inotify_event) + buf.event.len;
|
||||
remain -= len;
|
||||
|
||||
if (remain != 0) {
|
||||
SDL_memmove(&buf.storage[0], &buf.storage[len], remain);
|
||||
}
|
||||
}
|
||||
SDL_UnlockMutex(file_watch_lock);
|
||||
}
|
||||
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
|
||||
if (inotify_fd >= 0) {
|
||||
SDL_SetAtomicInt(&quit_watch_file, 1);
|
||||
SDL_WaitThread(file_watch_thread, NULL);
|
||||
close(inotify_fd);
|
||||
inotify_fd = -1;
|
||||
SDL_DestroyMutex(file_watch_lock);
|
||||
file_watch_lock = NULL;
|
||||
SDL_DestroyHashTable(watch_descriptor_table);
|
||||
watch_descriptor_table = NULL;
|
||||
}
|
||||
#endif // HAVE_INOTIFY
|
||||
}
|
||||
|
||||
// Note that this is actually part of filesystem, not fsops, but everything that uses posix fsops uses this implementation, even with separate filesystem code.
|
||||
char *SDL_SYS_GetCurrentDirectory(void)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -231,5 +231,18 @@ bool SDL_SYS_GetPathInfo(const char *path, SDL_PathInfo *info)
|
|||
return true;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
}
|
||||
|
||||
#endif // SDL_FSOPS_WINDOWS
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue