fsevent_stream/
ffi.rs

1//! Raw `FSEvents` ffi bindings.
2//!
3//! Some of the bindings are lightly wrapped to adapt to Rust's ownership model and provide a more ergonomic experience.
4
5#![allow(
6    non_snake_case,
7    non_upper_case_globals,
8    clippy::unreadable_literal,
9    clippy::declare_interior_mutable_const
10)]
11
12use std::ffi::c_void;
13use std::io;
14use std::marker::{PhantomData, PhantomPinned};
15use std::os::raw::c_uint;
16use std::path::Path;
17use std::time::Duration;
18
19use core_foundation::array::{CFArray, CFArrayRef};
20use core_foundation::base::{
21    kCFAllocatorDefault, Boolean, CFAllocatorCopyDescriptionCallBack, CFAllocatorRef,
22    CFAllocatorReleaseCallBack, CFAllocatorRetainCallBack, CFIndex, TCFType,
23};
24use core_foundation::date::CFTimeInterval;
25use core_foundation::runloop::{CFRunLoop, CFRunLoopIsWaiting, CFRunLoopMode, CFRunLoopRef};
26use core_foundation::string::{CFString, CFStringRef};
27use core_foundation::url::{kCFURLPOSIXPathStyle, CFURL};
28use once_cell::unsync::Lazy;
29
30fn str_path_to_cfstring_ref(source: &Path) -> io::Result<CFString> {
31    CFURL::from_path(source, source.is_dir())
32        .ok_or_else(|| io::Error::from(io::ErrorKind::NotFound))
33        .map(|path| path.absolute().get_file_system_path(kCFURLPOSIXPathStyle))
34}
35
36pub(crate) trait CFRunLoopExt {
37    fn is_waiting(&self) -> bool;
38}
39
40impl CFRunLoopExt for CFRunLoop {
41    fn is_waiting(&self) -> bool {
42        unsafe { CFRunLoopIsWaiting(self.as_concrete_TypeRef()) != 0 }
43    }
44}
45
46#[doc(hidden)]
47#[repr(C)]
48pub struct __FSEventStream {
49    _data: [u8; 0],
50    _marker: PhantomData<(*mut u8, PhantomPinned)>,
51}
52
53pub type SysFSEventStreamRef = *mut __FSEventStream;
54
55/// An ergonomic wrapper of [`SysFSEventStreamRef`](SysFSEventStreamRef).
56///
57/// This wrapper complies with Rust's ownership model, and releases its resource when dropped.
58pub struct SysFSEventStream(SysFSEventStreamRef);
59
60// Safety:
61// - According to the Apple documentation, it's safe to move `CFRef`s across threads.
62//   https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Multithreading/ThreadSafetySummary/ThreadSafetySummary.html
63unsafe impl Send for SysFSEventStream {}
64
65pub type FSEventStreamCallback = extern "C" fn(
66    SysFSEventStreamRef,            // ConstFSEventStreamRef streamRef
67    *mut c_void,                    // void *clientCallBackInfo
68    usize,                          // size_t numEvents
69    *mut c_void,                    // void *eventPaths
70    *const FSEventStreamEventFlags, // const FSEventStreamEventFlags eventFlags[]
71    *const FSEventStreamEventId,    // const FSEventStreamEventId eventIds[]
72);
73
74pub type FSEventStreamEventId = u64;
75
76pub type FSEventStreamCreateFlags = c_uint;
77
78pub type FSEventStreamEventFlags = c_uint;
79
80pub const kFSEventStreamEventIdSinceNow: FSEventStreamEventId = 0xFFFFFFFFFFFFFFFF;
81
82pub const kFSEventStreamCreateFlagNone: FSEventStreamCreateFlags = 0x00000000;
83pub const kFSEventStreamCreateFlagUseCFTypes: FSEventStreamCreateFlags = 0x00000001;
84pub const kFSEventStreamCreateFlagNoDefer: FSEventStreamCreateFlags = 0x00000002;
85pub const kFSEventStreamCreateFlagWatchRoot: FSEventStreamCreateFlags = 0x00000004;
86pub const kFSEventStreamCreateFlagIgnoreSelf: FSEventStreamCreateFlags = 0x00000008;
87pub const kFSEventStreamCreateFlagFileEvents: FSEventStreamCreateFlags = 0x00000010;
88pub const kFSEventStreamCreateFlagMarkSelf: FSEventStreamCreateFlags = 0x00000020;
89pub const kFSEventStreamCreateFlagUseExtendedData: FSEventStreamCreateFlags = 0x00000040;
90
91pub const kFSEventStreamEventFlagNone: FSEventStreamEventFlags = 0x00000000;
92pub const kFSEventStreamEventFlagMustScanSubDirs: FSEventStreamEventFlags = 0x00000001;
93pub const kFSEventStreamEventFlagUserDropped: FSEventStreamEventFlags = 0x00000002;
94pub const kFSEventStreamEventFlagKernelDropped: FSEventStreamEventFlags = 0x00000004;
95pub const kFSEventStreamEventFlagEventIdsWrapped: FSEventStreamEventFlags = 0x00000008;
96pub const kFSEventStreamEventFlagHistoryDone: FSEventStreamEventFlags = 0x00000010;
97pub const kFSEventStreamEventFlagRootChanged: FSEventStreamEventFlags = 0x00000020;
98pub const kFSEventStreamEventFlagMount: FSEventStreamEventFlags = 0x00000040;
99pub const kFSEventStreamEventFlagUnmount: FSEventStreamEventFlags = 0x00000080;
100pub const kFSEventStreamEventFlagItemCreated: FSEventStreamEventFlags = 0x00000100;
101pub const kFSEventStreamEventFlagItemRemoved: FSEventStreamEventFlags = 0x00000200;
102pub const kFSEventStreamEventFlagItemInodeMetaMod: FSEventStreamEventFlags = 0x00000400;
103pub const kFSEventStreamEventFlagItemRenamed: FSEventStreamEventFlags = 0x00000800;
104pub const kFSEventStreamEventFlagItemModified: FSEventStreamEventFlags = 0x00001000;
105pub const kFSEventStreamEventFlagItemFinderInfoMod: FSEventStreamEventFlags = 0x00002000;
106pub const kFSEventStreamEventFlagItemChangeOwner: FSEventStreamEventFlags = 0x00004000;
107pub const kFSEventStreamEventFlagItemXattrMod: FSEventStreamEventFlags = 0x00008000;
108pub const kFSEventStreamEventFlagItemIsFile: FSEventStreamEventFlags = 0x00010000;
109pub const kFSEventStreamEventFlagItemIsDir: FSEventStreamEventFlags = 0x00020000;
110pub const kFSEventStreamEventFlagItemIsSymlink: FSEventStreamEventFlags = 0x00040000;
111pub const kFSEventStreamEventFlagOwnEvent: FSEventStreamEventFlags = 0x00080000;
112pub const kFSEventStreamEventFlagItemIsHardlink: FSEventStreamEventFlags = 0x00100000;
113pub const kFSEventStreamEventFlagItemIsLastHardlink: FSEventStreamEventFlags = 0x00200000;
114pub const kFSEventStreamEventFlagItemCloned: FSEventStreamEventFlags = 0x00400000;
115
116pub const kFSEventStreamEventExtendedDataPathKey: Lazy<CFString> =
117    Lazy::new(|| CFString::new("path"));
118pub const kFSEventStreamEventExtendedFileIDKey: Lazy<CFString> =
119    Lazy::new(|| CFString::new("fileID"));
120
121#[repr(C)]
122pub struct SysFSEventStreamContext {
123    pub version: CFIndex,
124    pub info: *mut c_void,
125    pub retain: Option<CFAllocatorRetainCallBack>,
126    pub release: Option<CFAllocatorReleaseCallBack>,
127    pub copy_description: Option<CFAllocatorCopyDescriptionCallBack>,
128}
129
130/// Generate a callback that free the context when the stream created by [`SysFSEventStream::new`](SysFSEventStream::new) is released.
131///
132/// Usage: `impl_release_callback!(release_ctx, YourCtxType)`
133// Safety:
134// - The [documentation] for `FSEventStreamContext` states that `release` is only
135//   called when the stream is deallocated, so it is safe to convert `info` back into a
136//   box and drop it.
137//
138// [docs]: https://developer.apple.com/documentation/coreservices/fseventstreamcontext?language=objc
139#[macro_export]
140macro_rules! impl_release_callback {
141    ($name: ident, $ctx_ty: ty) => {
142        extern "C" fn $name(ctx: *mut std::ffi::c_void) {
143            unsafe {
144                drop(Box::from_raw(ctx as *mut $ctx_ty));
145            }
146        }
147    };
148    ($name: ident, const $ctx_ty: ty) => {
149        extern "C" fn $name(ctx: *const std::ffi::c_void) {
150            unsafe {
151                drop(Box::from_raw(ctx as *mut $ctx_ty));
152            }
153        }
154    };
155}
156
157impl SysFSEventStreamContext {
158    /// Create a new [`SysFSEventStreamContext`](SysFSEventStreamContext).
159    ///
160    /// `release_callback` can be constructed using [`impl_release_callback`](impl_release_callback) macro.
161    pub fn new<T>(ctx: T, release_callback: CFAllocatorReleaseCallBack) -> Self {
162        let ctx = Box::into_raw(Box::new(ctx));
163        Self {
164            version: 0,
165            info: ctx.cast(),
166            retain: None,
167            release: Some(release_callback),
168            copy_description: None,
169        }
170    }
171}
172
173impl SysFSEventStream {
174    /// Create a new [`SysFSEventStream`](SysFSEventStream).
175    ///
176    /// # Errors
177    /// Return error when there's any invalid path in `paths_to_watch`.
178    pub fn new<P: AsRef<Path>>(
179        callback: FSEventStreamCallback,
180        context: &SysFSEventStreamContext,
181        paths_to_watch: impl IntoIterator<Item = P>,
182        since_when: FSEventStreamEventId,
183        latency: Duration,
184        flags: FSEventStreamCreateFlags,
185    ) -> io::Result<Self> {
186        let cf_paths: Vec<_> = paths_to_watch
187            .into_iter()
188            .map(|item| str_path_to_cfstring_ref(item.as_ref()))
189            .collect::<Result<_, _>>()?;
190        let cf_path_array = CFArray::from_CFTypes(&*cf_paths);
191        Ok(Self(unsafe {
192            FSEventStreamCreate(
193                kCFAllocatorDefault,
194                callback,
195                context,
196                cf_path_array.as_concrete_TypeRef(),
197                since_when,
198                latency.as_secs_f64() as CFTimeInterval,
199                flags,
200            )
201        }))
202    }
203    pub fn show(&mut self) {
204        unsafe { FSEventStreamShow(self.0) }
205    }
206    pub fn schedule(&mut self, run_loop: &CFRunLoop, run_loop_mode: CFStringRef) {
207        unsafe {
208            FSEventStreamScheduleWithRunLoop(self.0, run_loop.as_concrete_TypeRef(), run_loop_mode);
209        }
210    }
211    pub fn unschedule(&mut self, run_loop: &CFRunLoop, run_loop_mode: CFStringRef) {
212        unsafe {
213            FSEventStreamUnscheduleFromRunLoop(
214                self.0,
215                run_loop.as_concrete_TypeRef(),
216                run_loop_mode,
217            );
218        }
219    }
220    pub fn start(&mut self) -> bool {
221        unsafe { FSEventStreamStart(self.0) != 0 }
222    }
223    pub fn flush_sync(&mut self) {
224        unsafe { FSEventStreamFlushSync(self.0) };
225    }
226    pub fn stop(&mut self) {
227        unsafe { FSEventStreamStop(self.0) };
228    }
229    pub fn invalidate(&mut self) {
230        unsafe { FSEventStreamInvalidate(self.0) };
231    }
232}
233
234impl Drop for SysFSEventStream {
235    fn drop(&mut self) {
236        unsafe { FSEventStreamRelease(self.0) };
237    }
238}
239
240#[link(name = "CoreServices", kind = "framework")]
241extern "C" {
242    fn FSEventStreamCreate(
243        allocator: CFAllocatorRef,
244        callback: FSEventStreamCallback,
245        context: *const SysFSEventStreamContext,
246        pathsToWatch: CFArrayRef,
247        sinceWhen: FSEventStreamEventId,
248        latency: CFTimeInterval,
249        flags: FSEventStreamCreateFlags,
250    ) -> SysFSEventStreamRef;
251
252    fn FSEventStreamShow(stream_ref: SysFSEventStreamRef);
253    fn FSEventStreamScheduleWithRunLoop(
254        stream_ref: SysFSEventStreamRef,
255        run_loop: CFRunLoopRef,
256        run_loop_mode: CFRunLoopMode,
257    );
258
259    fn FSEventStreamUnscheduleFromRunLoop(
260        stream_ref: SysFSEventStreamRef,
261        run_loop: CFRunLoopRef,
262        run_loop_mode: CFRunLoopMode,
263    );
264
265    fn FSEventStreamStart(stream_ref: SysFSEventStreamRef) -> Boolean;
266    fn FSEventStreamFlushSync(stream_ref: SysFSEventStreamRef);
267    fn FSEventStreamStop(stream_ref: SysFSEventStreamRef);
268    fn FSEventStreamInvalidate(stream_ref: SysFSEventStreamRef);
269    fn FSEventStreamRelease(stream_ref: SysFSEventStreamRef);
270
271    pub fn FSEventsGetCurrentEventId() -> FSEventStreamEventId;
272}