From 0637d0772aa36e9e49a0eabea587013165e1e41d Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Tue, 28 Oct 2025 13:38:30 +0100 Subject: [PATCH 1/3] Import from fsevent_sys directly --- src/lib.rs | 48 +++++++++++++++++++++++++----------------------- 1 file changed, 25 insertions(+), 23 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 4de85d5..deda1d1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -16,7 +16,13 @@ use core_foundation::{ }, string::CFString, }; -use fsevent_sys as fs; +use fsevent_sys::{ + kFSEventStreamCreateFlagFileEvents, kFSEventStreamCreateFlagNoDefer, + kFSEventStreamEventIdSinceNow, FSEventStreamContext, FSEventStreamCreate, + FSEventStreamCreateFlags, FSEventStreamEventFlags, FSEventStreamEventId, + FSEventStreamFlushSync, FSEventStreamRef, FSEventStreamScheduleWithRunLoop, FSEventStreamStart, + FSEventStreamStop, +}; use std::{ ffi::CStr, fmt::{Display, Formatter}, @@ -35,9 +41,9 @@ unsafe impl Send for CFRunLoopSendWrapper {} pub struct FsEvent { paths: Vec, - since_when: fs::FSEventStreamEventId, + since_when: FSEventStreamEventId, latency: CFTimeInterval, - flags: fs::FSEventStreamCreateFlags, + flags: FSEventStreamCreateFlags, runloop: Option, } @@ -155,9 +161,9 @@ impl Display for StreamFlags { } } -fn default_stream_context(event_sender: *const Sender) -> fs::FSEventStreamContext { +fn default_stream_context(event_sender: *const Sender) -> FSEventStreamContext { let ptr = event_sender as *mut c_void; - fs::FSEventStreamContext { + FSEventStreamContext { version: 0, info: ptr, retain: None, @@ -189,9 +195,9 @@ impl FsEvent { pub fn new(paths: Vec) -> Self { Self { paths, - since_when: fs::kFSEventStreamEventIdSinceNow, + since_when: kFSEventStreamEventIdSinceNow, latency: 0.0, - flags: fs::kFSEventStreamCreateFlagFileEvents | fs::kFSEventStreamCreateFlagNoDefer, + flags: kFSEventStreamCreateFlagFileEvents | kFSEventStreamCreateFlagNoDefer, runloop: None, } } @@ -208,9 +214,9 @@ impl FsEvent { } fn internal_observe( - since_when: fs::FSEventStreamEventId, + since_when: FSEventStreamEventId, latency: CFTimeInterval, - flags: fs::FSEventStreamCreateFlags, + flags: FSEventStreamCreateFlags, paths: CFArray, event_sender: Sender, runloop_sender: Option>, @@ -218,7 +224,7 @@ impl FsEvent { let stream_context = default_stream_context(&event_sender); unsafe { - let stream = fs::FSEventStreamCreate( + let stream = FSEventStreamCreate( kCFAllocatorDefault, callback, &stream_context, @@ -233,17 +239,13 @@ impl FsEvent { ret_tx.send(runloop).expect("unabe to send CFRunLoopRef"); } - fs::FSEventStreamScheduleWithRunLoop( - stream, - CFRunLoopGetCurrent(), - kCFRunLoopDefaultMode, - ); + FSEventStreamScheduleWithRunLoop(stream, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode); - fs::FSEventStreamStart(stream); + FSEventStreamStart(stream); CFRunLoopRun(); - fs::FSEventStreamFlushSync(stream); - fs::FSEventStreamStop(stream); + FSEventStreamFlushSync(stream); + FSEventStreamStop(stream); } Ok(()) @@ -304,12 +306,12 @@ impl FsEvent { } extern "C" fn callback( - _stream_ref: fs::FSEventStreamRef, + _stream_ref: FSEventStreamRef, info: *mut c_void, - num_events: usize, // size_t numEvents - event_paths: *mut c_void, // void *eventPaths - event_flags: *const fs::FSEventStreamEventFlags, // const FSEventStreamEventFlags eventFlags[] - event_ids: *const fs::FSEventStreamEventId, // const FSEventStreamEventId eventIds[] + num_events: usize, // size_t numEvents + event_paths: *mut c_void, // void *eventPaths + event_flags: *const FSEventStreamEventFlags, // const FSEventStreamEventFlags eventFlags[] + event_ids: *const FSEventStreamEventId, // const FSEventStreamEventId eventIds[] ) { let event_paths = unsafe { slice::from_raw_parts(event_paths as *const *const i8, num_events) }; let event_flags = unsafe { slice::from_raw_parts(event_flags, num_events) }; From 3dfebc7b36732bc8a7332ee6663c3ffda9bf4981 Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Tue, 28 Oct 2025 13:44:40 +0100 Subject: [PATCH 2/3] Use objc2-core-foundation and objc2-core-services `objc2-core-foundation` replaces the soft-deprecated `core-foundation`, and `objc2-core-services`` is an automatically generated replacement for `fsevent-sys` (amongst others). --- Cargo.toml | 16 ++++++-- fsevent-sys/src/lib.rs | 1 + src/lib.rs | 83 +++++++++++++++++++++--------------------- tests/fsevent.rs | 16 +++----- 4 files changed, 61 insertions(+), 55 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 0e5ca9b..e0a3291 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,7 +2,7 @@ name = "fsevent" version = "2.2.0" -authors = [ "Pierre Baillet " ] +authors = ["Pierre Baillet "] description = "Rust bindings to the fsevent-sys macOS API for file changes notifications" license = "MIT" repository = "https://github.com/octplane/fsevent-rust" @@ -10,9 +10,17 @@ edition = "2018" [dependencies] bitflags = "1" -fsevent-sys = "5.1.0" -# fsevent-sys = { path = "fsevent-sys" } -core-foundation = "0.10.1" +objc2-core-foundation = { version = "0.3.2", default-features = false, features = [ + "std", + "CFString", + "CFRunLoop", + "CFArray", + "CFDate", +] } +objc2-core-services = { version = "0.3.2", default-features = false, features = [ + "std", + "FSEvents", +] } [dev-dependencies] tempfile = "3" diff --git a/fsevent-sys/src/lib.rs b/fsevent-sys/src/lib.rs index ae10a35..41c5cf3 100644 --- a/fsevent-sys/src/lib.rs +++ b/fsevent-sys/src/lib.rs @@ -1,4 +1,5 @@ #![cfg(target_os = "macos")] +#![deprecated = "deprecated in favour of the `objc2-core-services` crate"] mod fsevent; pub use fsevent::*; diff --git a/src/lib.rs b/src/lib.rs index deda1d1..752d838 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,32 +7,29 @@ )] use bitflags::bitflags; -use core_foundation::{ - array::CFArray, - base::{kCFAllocatorDefault, TCFType}, - date::CFTimeInterval, - runloop::{ - kCFRunLoopDefaultMode, CFRunLoopGetCurrent, CFRunLoopRef, CFRunLoopRun, CFRunLoopStop, - }, - string::CFString, +use objc2_core_foundation::{ + kCFAllocatorDefault, kCFRunLoopDefaultMode, CFArray, CFRetained, CFRunLoop, CFString, + CFTimeInterval, }; -use fsevent_sys::{ +#[allow(deprecated)] +use objc2_core_services::FSEventStreamScheduleWithRunLoop; +use objc2_core_services::{ kFSEventStreamCreateFlagFileEvents, kFSEventStreamCreateFlagNoDefer, - kFSEventStreamEventIdSinceNow, FSEventStreamContext, FSEventStreamCreate, - FSEventStreamCreateFlags, FSEventStreamEventFlags, FSEventStreamEventId, - FSEventStreamFlushSync, FSEventStreamRef, FSEventStreamScheduleWithRunLoop, FSEventStreamStart, - FSEventStreamStop, + kFSEventStreamEventIdSinceNow, ConstFSEventStreamRef, FSEventStreamContext, + FSEventStreamCreate, FSEventStreamCreateFlags, FSEventStreamEventFlags, FSEventStreamEventId, + FSEventStreamFlushSync, FSEventStreamStart, FSEventStreamStop, }; use std::{ ffi::CStr, fmt::{Display, Formatter}, os::raw::c_void, + ptr::NonNull, slice, sync::mpsc::Sender, }; // Helper to send the runloop from an observer thread. -struct CFRunLoopSendWrapper(CFRunLoopRef); +struct CFRunLoopSendWrapper(CFRetained); // Safety: According to the Apple documentation, it is safe to send CFRef types across threads. // @@ -44,7 +41,7 @@ pub struct FsEvent { since_when: FSEventStreamEventId, latency: CFTimeInterval, flags: FSEventStreamCreateFlags, - runloop: Option, + runloop: Option>, } #[derive(Debug)] @@ -168,7 +165,7 @@ fn default_stream_context(event_sender: *const Sender) -> FSEventStreamCo info: ptr, retain: None, release: None, - copy_description: None, + copyDescription: None, } } @@ -208,16 +205,16 @@ impl FsEvent { Ok(()) } - fn build_native_paths(&self) -> CFArray { - let paths: Vec<_> = self.paths.iter().map(|x| CFString::new(x)).collect(); - CFArray::from_CFTypes(&paths) + fn build_native_paths(&self) -> CFRetained> { + let paths: Vec<_> = self.paths.iter().map(|x| CFString::from_str(x)).collect(); + CFArray::from_retained_objects(&paths) } fn internal_observe( since_when: FSEventStreamEventId, latency: CFTimeInterval, flags: FSEventStreamCreateFlags, - paths: CFArray, + paths: &CFArray, event_sender: Sender, runloop_sender: Option>, ) -> Result<()> { @@ -226,23 +223,28 @@ impl FsEvent { unsafe { let stream = FSEventStreamCreate( kCFAllocatorDefault, - callback, - &stream_context, - paths.as_concrete_TypeRef() as _, + Some(callback), + &stream_context as *const _ as *mut _, + paths.as_opaque(), since_when, latency, flags, ); if let Some(ret_tx) = runloop_sender { - let runloop = CFRunLoopSendWrapper(CFRunLoopGetCurrent()); + let runloop = CFRunLoopSendWrapper(CFRunLoop::current().unwrap()); ret_tx.send(runloop).expect("unabe to send CFRunLoopRef"); } - FSEventStreamScheduleWithRunLoop(stream, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode); + #[allow(deprecated)] + FSEventStreamScheduleWithRunLoop( + stream, + &CFRunLoop::current().unwrap(), + kCFRunLoopDefaultMode.unwrap(), + ); FSEventStreamStart(stream); - CFRunLoopRun(); + CFRunLoop::run(); FSEventStreamFlushSync(stream); FSEventStreamStop(stream); @@ -257,7 +259,7 @@ impl FsEvent { self.since_when, self.latency, self.flags, - native_paths, + &native_paths, event_sender, None, ) @@ -268,7 +270,7 @@ impl FsEvent { let (ret_tx, ret_rx) = std::sync::mpsc::channel(); let native_paths = self.build_native_paths(); - struct CFMutableArraySendWrapper(CFArray); + struct CFMutableArraySendWrapper(CFRetained>); // Safety // - See comment on `CFRunLoopSendWrapper @@ -284,7 +286,7 @@ impl FsEvent { since_when, latency, flags, - paths.0, + &paths.0, event_sender, Some(ret_tx), ) @@ -298,24 +300,23 @@ impl FsEvent { // Shut down the event stream. pub fn shutdown_observe(&mut self) { if let Some(runloop) = self.runloop.take() { - unsafe { - CFRunLoopStop(runloop); - } + runloop.stop(); } } } -extern "C" fn callback( - _stream_ref: FSEventStreamRef, +unsafe extern "C-unwind" fn callback( + _stream_ref: ConstFSEventStreamRef, info: *mut c_void, - num_events: usize, // size_t numEvents - event_paths: *mut c_void, // void *eventPaths - event_flags: *const FSEventStreamEventFlags, // const FSEventStreamEventFlags eventFlags[] - event_ids: *const FSEventStreamEventId, // const FSEventStreamEventId eventIds[] + num_events: usize, // size_t numEvents + event_paths: NonNull, // void *eventPaths + event_flags: NonNull, // const FSEventStreamEventFlags eventFlags[] + event_ids: NonNull, // const FSEventStreamEventId eventIds[] ) { - let event_paths = unsafe { slice::from_raw_parts(event_paths as *const *const i8, num_events) }; - let event_flags = unsafe { slice::from_raw_parts(event_flags, num_events) }; - let event_ids = unsafe { slice::from_raw_parts(event_ids, num_events) }; + let event_paths = + unsafe { slice::from_raw_parts(event_paths.as_ptr() as *const *const i8, num_events) }; + let event_flags = unsafe { slice::from_raw_parts(event_flags.as_ptr(), num_events) }; + let event_ids = unsafe { slice::from_raw_parts(event_ids.as_ptr(), num_events) }; let sender = unsafe { (info as *mut Sender) .as_mut() diff --git a/tests/fsevent.rs b/tests/fsevent.rs index 3fb0620..de61c22 100644 --- a/tests/fsevent.rs +++ b/tests/fsevent.rs @@ -1,7 +1,7 @@ #![cfg(target_os = "macos")] -use core_foundation::runloop::{CFRunLoopGetCurrent, CFRunLoopRef, CFRunLoopStop}; use fsevent::*; +use objc2_core_foundation::{CFRetained, CFRunLoop}; use std::{ fs, fs::{read_link, OpenOptions}, @@ -13,7 +13,7 @@ use std::{ }; // Helper to send the runloop from an observer thread. -struct CFRunLoopSendWrapper(CFRunLoopRef); +struct CFRunLoopSendWrapper(CFRetained); // Safety: According to the Apple documentation, it is safe to send CFRef types across threads. // @@ -121,7 +121,7 @@ fn internal_observe_folder(run_async: bool) { let (tx, rx) = std::sync::mpsc::channel(); let dst_clone = dst.clone(); let observe_thread = thread::spawn(move || { - let runloop = unsafe { CFRunLoopGetCurrent() }; + let runloop = CFRunLoop::current().unwrap(); tx.send(CFRunLoopSendWrapper(runloop)).unwrap(); let mut fsevent = fsevent::FsEvent::new(vec![]); @@ -163,9 +163,7 @@ fn internal_observe_folder(run_async: bool) { ); if let Some((runloop, thread)) = runloop_and_thread { - unsafe { - CFRunLoopStop(runloop); - } + runloop.stop(); thread.join().unwrap(); } else { @@ -204,7 +202,7 @@ fn internal_validate_watch_single_file(run_async: bool) { let dir_path_clone = dir_path.clone(); let observe_thread = thread::spawn(move || { - let runloop = unsafe { CFRunLoopGetCurrent() }; + let runloop = CFRunLoop::current().unwrap(); tx.send(CFRunLoopSendWrapper(runloop)).unwrap(); let mut fsevent = fsevent::FsEvent::new(vec![]); @@ -257,9 +255,7 @@ fn internal_validate_watch_single_file(run_async: bool) { ); if let Some((runloop, observe_thread)) = runloop_and_thread { - unsafe { - CFRunLoopStop(runloop); - } + runloop.stop(); observe_thread.join().unwrap(); } else { From 6f979212bb1b92409cc9d87971b995ddb442a636 Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Tue, 28 Oct 2025 13:51:38 +0100 Subject: [PATCH 3/3] Remove magic constant values --- src/lib.rs | 60 ++++++++++++++++++++++++++++++++---------------------- 1 file changed, 36 insertions(+), 24 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 752d838..bec6554 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -15,6 +15,18 @@ use objc2_core_foundation::{ use objc2_core_services::FSEventStreamScheduleWithRunLoop; use objc2_core_services::{ kFSEventStreamCreateFlagFileEvents, kFSEventStreamCreateFlagNoDefer, + kFSEventStreamEventFlagEventIdsWrapped, kFSEventStreamEventFlagHistoryDone, + kFSEventStreamEventFlagItemChangeOwner, kFSEventStreamEventFlagItemCloned, + kFSEventStreamEventFlagItemCreated, kFSEventStreamEventFlagItemFinderInfoMod, + kFSEventStreamEventFlagItemInodeMetaMod, kFSEventStreamEventFlagItemIsDir, + kFSEventStreamEventFlagItemIsFile, kFSEventStreamEventFlagItemIsHardlink, + kFSEventStreamEventFlagItemIsLastHardlink, kFSEventStreamEventFlagItemIsSymlink, + kFSEventStreamEventFlagItemModified, kFSEventStreamEventFlagItemRemoved, + kFSEventStreamEventFlagItemRenamed, kFSEventStreamEventFlagItemXattrMod, + kFSEventStreamEventFlagKernelDropped, kFSEventStreamEventFlagMount, + kFSEventStreamEventFlagMustScanSubDirs, kFSEventStreamEventFlagNone, + kFSEventStreamEventFlagOwnEvent, kFSEventStreamEventFlagRootChanged, + kFSEventStreamEventFlagUnmount, kFSEventStreamEventFlagUserDropped, kFSEventStreamEventIdSinceNow, ConstFSEventStreamRef, FSEventStreamContext, FSEventStreamCreate, FSEventStreamCreateFlags, FSEventStreamEventFlags, FSEventStreamEventId, FSEventStreamFlushSync, FSEventStreamStart, FSEventStreamStop, @@ -56,30 +68,30 @@ pub struct Event { bitflags! { #[repr(C)] pub struct StreamFlags: u32 { - const NONE = 0x00000000; - const MUST_SCAN_SUBDIRS = 0x00000001; - const USER_DROPPED = 0x00000002; - const KERNEL_DROPPED = 0x00000004; - const IDS_WRAPPED = 0x00000008; - const HISTORY_DONE = 0x00000010; - const ROOT_CHANGED = 0x00000020; - const MOUNT = 0x00000040; - const UNMOUNT = 0x00000080; - const ITEM_CREATED = 0x00000100; - const ITEM_REMOVED = 0x00000200; - const INODE_META_MOD = 0x00000400; - const ITEM_RENAMED = 0x00000800; - const ITEM_MODIFIED = 0x00001000; - const FINDER_INFO_MOD = 0x00002000; - const ITEM_CHANGE_OWNER = 0x00004000; - const ITEM_XATTR_MOD = 0x00008000; - const IS_FILE = 0x00010000; - const IS_DIR = 0x00020000; - const IS_SYMLINK = 0x00040000; - const OWN_EVENT = 0x00080000; - const IS_HARDLINK = 0x00100000; - const IS_LAST_HARDLINK = 0x00200000; - const ITEM_CLONED = 0x400000; + const NONE = kFSEventStreamEventFlagNone; + const MUST_SCAN_SUBDIRS = kFSEventStreamEventFlagMustScanSubDirs; + const USER_DROPPED = kFSEventStreamEventFlagUserDropped; + const KERNEL_DROPPED = kFSEventStreamEventFlagKernelDropped; + const IDS_WRAPPED = kFSEventStreamEventFlagEventIdsWrapped; + const HISTORY_DONE = kFSEventStreamEventFlagHistoryDone; + const ROOT_CHANGED = kFSEventStreamEventFlagRootChanged; + const MOUNT = kFSEventStreamEventFlagMount; + const UNMOUNT = kFSEventStreamEventFlagUnmount; + const ITEM_CREATED = kFSEventStreamEventFlagItemCreated; + const ITEM_REMOVED = kFSEventStreamEventFlagItemRemoved; + const INODE_META_MOD = kFSEventStreamEventFlagItemInodeMetaMod; + const ITEM_RENAMED = kFSEventStreamEventFlagItemRenamed; + const ITEM_MODIFIED = kFSEventStreamEventFlagItemModified; + const FINDER_INFO_MOD = kFSEventStreamEventFlagItemFinderInfoMod; + const ITEM_CHANGE_OWNER = kFSEventStreamEventFlagItemChangeOwner; + const ITEM_XATTR_MOD = kFSEventStreamEventFlagItemXattrMod; + const IS_FILE = kFSEventStreamEventFlagItemIsFile; + const IS_DIR = kFSEventStreamEventFlagItemIsDir; + const IS_SYMLINK = kFSEventStreamEventFlagItemIsSymlink; + const OWN_EVENT = kFSEventStreamEventFlagOwnEvent; + const IS_HARDLINK = kFSEventStreamEventFlagItemIsHardlink; + const IS_LAST_HARDLINK = kFSEventStreamEventFlagItemIsLastHardlink; + const ITEM_CLONED = kFSEventStreamEventFlagItemCloned; } }