diff --git a/meson_options.txt b/meson_options.txt index 1cf3e22f..7850416e 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -2,3 +2,4 @@ option('bundle', type : 'boolean', value : false, description: 'Build a macOS bu option('source-only', type : 'boolean', value : false, description: 'Configure source files only, doesn\'t checks for dependencies') option('portable', type : 'boolean', value : false, description: 'Portable install') option('renderer', type : 'boolean', value : false, description: 'Use SDL renderer') +option('dirmonitor_backend', type : 'combo', value : '', choices : ['', 'inotify', 'kqueue', 'win32', 'dummy'], description: 'define what dirmonitor backend to use') \ No newline at end of file diff --git a/src/api/dirmonitor.c b/src/api/dirmonitor.c index 15b63a97..3c79c0d9 100644 --- a/src/api/dirmonitor.c +++ b/src/api/dirmonitor.c @@ -15,144 +15,31 @@ #include #include -/* -This is *slightly* a clusterfuck. Normally, we'd -have windows wait on a list of handles like inotify, -however, MAXIMUM_WAIT_OBJECTS is 64. Yes, seriously. - -So, for windows, we are recursive. -*/ -struct dirmonitor { - int fd; - #if _WIN32 - HANDLE handle; - char buffer[8192]; - OVERLAPPED overlapped; - bool running; - #endif -}; - -struct dirmonitor* init_dirmonitor() { - struct dirmonitor* monitor = calloc(sizeof(struct dirmonitor), 1); - #ifndef _WIN32 - #if __linux__ - monitor->fd = inotify_init1(IN_NONBLOCK); - #else - monitor->fd = kqueue(); - #endif - #endif - return monitor; -} - -#if _WIN32 -static void close_monitor_handle(struct dirmonitor* monitor) { - if (monitor->handle) { - if (monitor->running) { - BOOL result = CancelIoEx(monitor->handle, &monitor->overlapped); - DWORD error = GetLastError(); - if (result == TRUE || error != ERROR_NOT_FOUND) { - DWORD bytes_transferred; - GetOverlappedResult( monitor->handle, &monitor->overlapped, &bytes_transferred, TRUE ); - } - monitor->running = false; - } - CloseHandle(monitor->handle); - } - monitor->handle = NULL; -} +#ifndef DIRMONITOR_BACKEND +#error No dirmonitor backend defined #endif -void deinit_dirmonitor(struct dirmonitor* monitor) { - #if _WIN32 - close_monitor_handle(monitor); - #else - close(monitor->fd); - #endif - free(monitor); -} +#define GLUE_HELPER(x, y) x##y +#define GLUE(x, y) GLUE_HELPER(x, y) -int check_dirmonitor(struct dirmonitor* monitor, int (*change_callback)(int, const char*, void*), void* data) { - #if _WIN32 - if (!monitor->running) { - if (ReadDirectoryChangesW(monitor->handle, monitor->buffer, sizeof(monitor->buffer), TRUE, FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_DIR_NAME, NULL, &monitor->overlapped, NULL) == 0) - return GetLastError(); - monitor->running = true; - } - DWORD bytes_transferred; - if (!GetOverlappedResult(monitor->handle, &monitor->overlapped, &bytes_transferred, FALSE)) { - int error = GetLastError(); - return error == ERROR_IO_PENDING || error == ERROR_IO_INCOMPLETE ? 0 : error; - } - monitor->running = false; - for (FILE_NOTIFY_INFORMATION* info = (FILE_NOTIFY_INFORMATION*)monitor->buffer; (char*)info < monitor->buffer + sizeof(monitor->buffer); info = (FILE_NOTIFY_INFORMATION*)((char*)info) + info->NextEntryOffset) { - change_callback(info->FileNameLength, (char*)info->FileName, data); - if (!info->NextEntryOffset) - break; - } - monitor->running = false; - return 0; - #elif __linux__ - char buf[PATH_MAX + sizeof(struct inotify_event)]; - ssize_t offset = 0; - while (1) { - ssize_t len = read(monitor->fd, &buf[offset], sizeof(buf) - offset); - if (len == -1 && errno != EAGAIN) - return errno; - if (len <= 0) - return 0; - while (len > sizeof(struct inotify_event) && len >= ((struct inotify_event*)buf)->len + sizeof(struct inotify_event)) { - change_callback(((const struct inotify_event *)buf)->wd, NULL, data); - len -= sizeof(struct inotify_event) + ((struct inotify_event*)buf)->len; - memmove(buf, &buf[sizeof(struct inotify_event) + ((struct inotify_event*)buf)->len], len); - offset = len; - } - } - #else - struct kevent event; - while (1) { - struct timespec tm = {0}; - int nev = kevent(monitor->fd, NULL, 0, &event, 1, &tm); - if (nev == -1) - return errno; - if (nev <= 0) - return 0; - change_callback(event.ident, NULL, data); - } - #endif -} +#define init_dirmonitor GLUE(init_dirmonitor_, DIRMONITOR_BACKEND) +#define deinit_dirmonitor GLUE(deinit_dirmonitor_, DIRMONITOR_BACKEND) +#define check_dirmonitor GLUE(check_dirmonitor_, DIRMONITOR_BACKEND) +#define add_dirmonitor GLUE(add_dirmonitor_, DIRMONITOR_BACKEND) +#define remove_dirmonitor GLUE(remove_dirmonitor_, DIRMONITOR_BACKEND) -int add_dirmonitor(struct dirmonitor* monitor, const char* path) { - #if _WIN32 - close_monitor_handle(monitor); - monitor->handle = CreateFileA(path, FILE_LIST_DIRECTORY, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED, NULL); - if (monitor->handle && monitor->handle != INVALID_HANDLE_VALUE) - return 1; - monitor->handle = NULL; - return -1; - #elif __linux__ - return inotify_add_watch(monitor->fd, path, IN_CREATE | IN_DELETE | IN_MOVED_FROM | IN_MOVED_TO); - #else - int fd = open(path, O_RDONLY); - struct kevent change; - EV_SET(&change, fd, EVFILT_VNODE, EV_ADD | EV_CLEAR, NOTE_DELETE | NOTE_EXTEND | NOTE_WRITE | NOTE_ATTRIB | NOTE_LINK | NOTE_RENAME, 0, (void*)path); - kevent(monitor->fd, &change, 1, NULL, 0, NULL); - return fd; - #endif -} +struct dirmonitor {}; // dirmonitor struct is defined in each backend -void remove_dirmonitor(struct dirmonitor* monitor, int fd) { - #if _WIN32 - close_monitor_handle(monitor); - #elif __linux__ - inotify_rm_watch(monitor->fd, fd); - #else - close(fd); - #endif -} +// define functions so we know their signature +struct dirmonitor* init_dirmonitor(); +void deinit_dirmonitor(struct dirmonitor*); +int check_dirmonitor(struct dirmonitor*, int (*)(int, const char*, void*), void*); +int add_dirmonitor(struct dirmonitor*, const char*); +void remove_dirmonitor(struct dirmonitor*, int); static int f_check_dir_callback(int watch_id, const char* path, void* L) { lua_pushvalue(L, -1); - #if _WIN32 + #ifdef DIRMONITOR_WIN32 char buffer[PATH_MAX*4]; int count = WideCharToMultiByte(CP_UTF8, 0, (WCHAR*)path, watch_id, buffer, PATH_MAX*4 - 1, NULL, NULL); lua_pushlstring(L, buffer, count); diff --git a/src/api/dirmonitor/dummy.c b/src/api/dirmonitor/dummy.c new file mode 100644 index 00000000..cc0b41ee --- /dev/null +++ b/src/api/dirmonitor/dummy.c @@ -0,0 +1,22 @@ +#include + +struct dirmonitor { +}; + +struct dirmonitor* init_dirmonitor_dummy() { + return NULL; +} + +void deinit_dirmonitor_dummy(struct dirmonitor* monitor) { +} + +int check_dirmonitor_dummy(struct dirmonitor* monitor, int (*change_callback)(int, const char*, void*), void* data) { + return -1; +} + +int add_dirmonitor_dummy(struct dirmonitor* monitor, const char* path) { + return -1; +} + +void remove_dirmonitor_dummy(struct dirmonitor* monitor, int fd) { +} \ No newline at end of file diff --git a/src/api/dirmonitor/inotify.c b/src/api/dirmonitor/inotify.c new file mode 100644 index 00000000..c284e501 --- /dev/null +++ b/src/api/dirmonitor/inotify.c @@ -0,0 +1,58 @@ +#include +#include +#include +#include +#include +#include +#include + +struct dirmonitor { + int fd; +}; + +struct dirmonitor* init_dirmonitor_inotify() { + struct dirmonitor* monitor = calloc(sizeof(struct dirmonitor), 1); + + monitor->fd = inotify_init(); + fcntl(monitor->fd, F_SETFL, O_NONBLOCK); + + + return monitor; +} + +void deinit_dirmonitor_inotify(struct dirmonitor* monitor) { + close(monitor->fd); + free(monitor); +} + +int check_dirmonitor_inotify(struct dirmonitor* monitor, int (*change_callback)(int, const char*, void*), void* data) { + char buf[PATH_MAX + sizeof(struct inotify_event)]; + ssize_t offset = 0; + + while (1) { + ssize_t len = read(monitor->fd, &buf[offset], sizeof(buf) - offset); + + if (len == -1 && errno != EAGAIN) { + return errno; + } + + if (len <= 0) { + return 0; + } + + while (len > sizeof(struct inotify_event) && len >= ((struct inotify_event*)buf)->len + sizeof(struct inotify_event)) { + change_callback(((const struct inotify_event *)buf)->wd, NULL, data); + len -= sizeof(struct inotify_event) + ((struct inotify_event*)buf)->len; + memmove(buf, &buf[sizeof(struct inotify_event) + ((struct inotify_event*)buf)->len], len); + offset = len; + } + } +} + +int add_dirmonitor_inotify(struct dirmonitor* monitor, const char* path) { + return inotify_add_watch(monitor->fd, path, IN_CREATE | IN_DELETE | IN_MOVED_FROM | IN_MOVED_TO); +} + +void remove_dirmonitor_inotify(struct dirmonitor* monitor, int fd) { + inotify_rm_watch(monitor->fd, fd); +} \ No newline at end of file diff --git a/src/api/dirmonitor/kqueue.c b/src/api/dirmonitor/kqueue.c new file mode 100644 index 00000000..e7f041bd --- /dev/null +++ b/src/api/dirmonitor/kqueue.c @@ -0,0 +1,53 @@ +#include +#include +#include +#include +#include +#include + +struct dirmonitor { + int fd; +}; + +struct dirmonitor* init_dirmonitor_kqueue() { + struct dirmonitor* monitor = calloc(sizeof(struct dirmonitor), 1); + monitor->fd = kqueue(); + return monitor; +} + +void deinit_dirmonitor_kqueue(struct dirmonitor* monitor) { + close(monitor->fd); + free(monitor); +} + +int check_dirmonitor_kqueue(struct dirmonitor* monitor, int (*change_callback)(int, const char*, void*), void* data) { + struct kevent event; + while (1) { + struct timespec tm = {0}; + int nev = kevent(monitor->fd, NULL, 0, &event, 1, &tm); + + if (nev == -1) { + return errno; + } + + if (nev <= 0) { + return 0; + } + + change_callback(event.ident, NULL, data); + } +} + +int add_dirmonitor_kqueue(struct dirmonitor* monitor, const char* path) { + int fd = open(path, O_RDONLY); + struct kevent change; + + EV_SET(&change, fd, EVFILT_VNODE, EV_ADD | EV_CLEAR, NOTE_DELETE | NOTE_EXTEND | NOTE_WRITE | NOTE_ATTRIB | NOTE_LINK | NOTE_RENAME, 0, (void*)path); + kevent(monitor->fd, &change, 1, NULL, 0, NULL); + + return fd; +} + +void remove_dirmonitor_kqueue(struct dirmonitor* monitor, int fd) { + close(fd); +} \ No newline at end of file diff --git a/src/api/dirmonitor/win32.c b/src/api/dirmonitor/win32.c new file mode 100644 index 00000000..9dd2a254 --- /dev/null +++ b/src/api/dirmonitor/win32.c @@ -0,0 +1,77 @@ +#include +#include + +struct dirmonitor { + HANDLE handle; + char buffer[8192]; + OVERLAPPED overlapped; + bool running; +}; + +struct dirmonitor* init_dirmonitor_win32() { + struct dirmonitor* monitor = calloc(sizeof(struct dirmonitor), 1); + + return monitor; +} + +static void close_monitor_handle(struct dirmonitor* monitor) { + if (monitor->handle) { + if (monitor->running) { + BOOL result = CancelIoEx(monitor->handle, &monitor->overlapped); + DWORD error = GetLastError(); + if (result == TRUE || error != ERROR_NOT_FOUND) { + DWORD bytes_transferred; + GetOverlappedResult( monitor->handle, &monitor->overlapped, &bytes_transferred, TRUE ); + } + monitor->running = false; + } + CloseHandle(monitor->handle); + } + monitor->handle = NULL; +} + +void deinit_dirmonitor_win32(struct dirmonitor* monitor) { + close_monitor_handle(monitor); + free(monitor); +} + +int check_dirmonitor_win32(struct dirmonitor* monitor, int (*change_callback)(int, const char*, void*), void* data) { + if (!monitor->running) { + if (ReadDirectoryChangesW(monitor->handle, monitor->buffer, sizeof(monitor->buffer), TRUE, FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_DIR_NAME, NULL, &monitor->overlapped, NULL) == 0) { + return GetLastError(); + } + monitor->running = true; + } + + DWORD bytes_transferred; + + if (!GetOverlappedResult(monitor->handle, &monitor->overlapped, &bytes_transferred, FALSE)) { + int error = GetLastError(); + return error == ERROR_IO_PENDING || error == ERROR_IO_INCOMPLETE ? 0 : error; + } + + monitor->running = false; + + for (FILE_NOTIFY_INFORMATION* info = (FILE_NOTIFY_INFORMATION*)monitor->buffer; (char*)info < monitor->buffer + sizeof(monitor->buffer); info = (FILE_NOTIFY_INFORMATION*)((char*)info) + info->NextEntryOffset) { + change_callback(info->FileNameLength, (char*)info->FileName, data); + if (!info->NextEntryOffset) + break; + } + + monitor->running = false; + return 0; +} + +int add_dirmonitor_win32(struct dirmonitor* monitor, const char* path) { + close_monitor_handle(monitor); + monitor->handle = CreateFileA(path, FILE_LIST_DIRECTORY, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED, NULL); + if (monitor->handle && monitor->handle != INVALID_HANDLE_VALUE) { + return 1; + } + monitor->handle = NULL; + return -1; +} + +void remove_dirmonitor_win32(struct dirmonitor* monitor, int fd) { + close_monitor_handle(monitor); +} \ No newline at end of file diff --git a/src/meson.build b/src/meson.build index 8a4f1272..5ad95bdd 100644 --- a/src/meson.build +++ b/src/meson.build @@ -1,6 +1,5 @@ lite_sources = [ 'api/api.c', - 'api/dirmonitor.c', 'api/renderer.c', 'api/regex.c', 'api/system.c', @@ -11,6 +10,41 @@ lite_sources = [ 'main.c', ] +# dirmonitor backend +if get_option('dirmonitor_backend') == '' + if cc.has_function('inotify_init', prefix : '#include') + dirmonitor_backend = 'inotify' + elif cc.has_function('kqueue', prefix : '#include') + dirmonitor_backend = 'kqueue' + elif dependency('libkqueue', required : false).found() + dirmonitor_backend = 'kqueue' + elif host_machine.system() == 'windows' + dirmonitor_backend = 'win32' + else + dirmonitor_backend = 'dummy' + warning('no suitable backend found, defaulting to dummy backend') + endif +else + dirmonitor_backend = get_option('dirmonitor_backend') +endif + +message('dirmonitor_backend: @0@'.format(dirmonitor_backend)) + +if dirmonitor_backend == 'kqueue' + libkqueue_dep = dependency('libkqueue', required : false) + if libkqueue_dep.found() + lite_deps += libkqueue_dep + endif +endif + +lite_sources += [ + 'api/dirmonitor.c', + 'api/dirmonitor/' + dirmonitor_backend + '.c', +] +lite_cargs += '-DDIRMONITOR_BACKEND=' + dirmonitor_backend +lite_cargs += '-DDIRMONITOR_' + dirmonitor_backend.to_upper() + + lite_rc = [] if host_machine.system() == 'windows' windows = import('windows')