Mercurial > minori
changeset 406:31ce85df55a8 default tip
filesystem: add mac os x directory watcher
this code is incredibly stinky
tbh we should probably be using C++ threads everywhere else just
because they are SO much easier to code for than the shitty Qt
threads API
| author | Paper <paper@tflc.us> |
|---|---|
| date | Mon, 19 Jan 2026 22:48:56 -0500 |
| parents | 4562bc8bfdff |
| children | |
| files | src/core/filesystem.cc |
| diffstat | 1 files changed, 141 insertions(+), 0 deletions(-) [+] |
line wrap: on
line diff
--- a/src/core/filesystem.cc Mon Jan 19 22:47:29 2026 -0500 +++ b/src/core/filesystem.cc Mon Jan 19 22:48:56 2026 -0500 @@ -14,8 +14,14 @@ # include <fcntl.h> # include <unistd.h> # include <sys/inotify.h> +#elif defined(__MACH__) && defined(__APPLE__) +# include <CoreFoundation/CoreFoundation.h> +# include <CoreServices/CoreServices.h> +# include <mutex> +# include <thread> #endif +#include <QDebug> #include <iostream> namespace Filesystem { @@ -585,6 +591,141 @@ }; using DefaultWatcher = InotifyWatcher; +#elif defined(__APPLE__) && defined(__MACH__) +class FSEventsWatcher : public StdFilesystemWatcher { +public: + FSEventsWatcher(void *opaque, const std::filesystem::path &path, IWatcher::EventHandler handler, bool recursive) + : StdFilesystemWatcher(opaque, path, handler, recursive) + , first_(true) + { + FSEventStreamContext ctx; + + ctx.copyDescription = NULL; + ctx.info = this; + ctx.release = NULL; + ctx.retain = NULL; + ctx.version = 0; + + CFStringRef str = Strings::ToCFString(path.u8string()); + CFArrayRef arr = CFArrayCreate(kCFAllocatorDefault, reinterpret_cast<const void **>(&str), 1, &kCFTypeArrayCallBacks); + + stream_ = FSEventStreamCreate(kCFAllocatorDefault, callback_static, &ctx, arr, kFSEventStreamEventIdSinceNow, 0.5, 0); + + // kill these off now + CFRelease(str); + CFRelease(arr); + + std::thread rl(runloop_thread_static, this); + std::swap(runloop_thread_, rl); + } + + virtual ~FSEventsWatcher() override + { + CFRunLoopStop(run_loop_); + runloop_thread_.join(); + + FSEventStreamUnscheduleFromRunLoop(stream_, run_loop_, kCFRunLoopDefaultMode); + FSEventStreamStop(stream_); + FSEventStreamRelease(stream_); + } + + virtual void Process() override + { + if (first_) { + StdFilesystemWatcher::Process(); + first_ = false; + } + + const std::lock_guard<std::recursive_mutex> lock(events_mutex_); + + // TODO do this properly + if (rescan_.size()) { + StdFilesystemWatcher::Process(); + rescan_.clear(); + } + + // send all the queued events then clear it + for (const auto &ev : events_) + Watcher::handler_(Watcher::opaque_, ev.path, ev.type); + events_.clear(); + } + +private: + void callback(ConstFSEventStreamRef streamRef, std::size_t numEvents, void *eventPaths, const FSEventStreamEventFlags *eventFlags, const FSEventStreamEventId *eventIds) + { + // assert(streamRef == stream_); + +#if 0 + for (std::size_t i = 0; i < numEvents; i++) { + if ((eventFlags[i] == 0) || (eventFlags[i] & (kFSEventStreamEventFlagItemCreated|kFSEventStreamEventFlagItemRemoved|kFSEventStreamEventFlagMustScanSubDirs))) { + EventInfo ev; + + ev.path = std::filesystem::path(reinterpret_cast<const char **>(eventPaths)[i]); + + const std::lock_guard<std::recursive_mutex> lock(events_mutex_); + + if ((eventFlags[i] == 0) || (eventFlags[i] & kFSEventStreamEventFlagMustScanSubDirs)) + rescan_.push_back(ev.path); + + if (eventFlags[i] & kFSEventStreamEventFlagItemCreated) { + ev.type = IWatcher::Created; + events_.push_back(ev); + } + + if (eventFlags[i] & kFSEventStreamEventFlagItemRemoved) { + ev.type = IWatcher::Deleted; + events_.push_back(ev); + } + } + } +#else + /* I only evr get eventFlags[i] == 0 so I think this is + * probably the best way ??? */ + rescan_.push_back(Watcher::path_); +#endif + } + + static void callback_static(ConstFSEventStreamRef streamRef, void *clientCallBackInfo, std::size_t numEvents, void *eventPaths, const FSEventStreamEventFlags *eventFlags, const FSEventStreamEventId *eventIds) + { + reinterpret_cast<FSEventsWatcher *>(clientCallBackInfo)->callback(streamRef, numEvents, eventPaths, eventFlags, eventIds); + } + + void runloop_thread() + { + { + const std::lock_guard<std::recursive_mutex> lock(events_mutex_); + run_loop_ = CFRunLoopGetCurrent(); + FSEventStreamScheduleWithRunLoop(stream_, run_loop_, kCFRunLoopDefaultMode); + FSEventStreamStart(stream_); + } + + CFRunLoopRun(); + + // now.. loop was stopped from destructor. + // we don't need to do anything; destructor + // will handle everything from here + } + + static void runloop_thread_static(FSEventsWatcher *e) + { + e->runloop_thread(); + } + + struct EventInfo { + enum Event type; + std::filesystem::path path; + }; + + std::vector<struct EventInfo> events_; + std::recursive_mutex events_mutex_; + FSEventStreamRef stream_; + std::thread runloop_thread_; + CFRunLoopRef run_loop_; + bool first_; + std::vector<std::filesystem::path> rescan_; +}; + +using DefaultWatcher = FSEventsWatcher; #else using DefaultWatcher = StdFilesystemWatcher; #endif
