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