PyObjC FSEvents Framework
This library provides Python wrappers for the macOS FSEvents framework, allowing applications to monitor file system events efficiently. It is part of the larger PyObjC project, which bridges Python and the Objective-C runtime on macOS. The project follows macOS SDK updates closely, often releasing new versions with each major OS release, and is currently at version 12.1.
Warnings
- breaking PyObjC 12.0 dropped support for Python 3.9. Ensure your project targets Python 3.10 or newer when upgrading to PyObjC 12.x.
- breaking PyObjC 11.0 dropped support for Python 3.8. Projects using older Python versions need to upgrade before migrating to PyObjC 11.x.
- breaking PyObjC 11.1 aligned its behavior with `clang's documentation for automatic reference counting` for initializer methods. This means methods in the 'init' family now correctly steal a reference to self and return a new reference, which might change memory management expectations for custom Objective-C wrappers.
- gotcha PyObjC and all `pyobjc-framework-*` packages are strictly macOS-only. Attempting to install or run them on other operating systems will fail.
- gotcha In PyObjC 10.3, calling `__init__` on Python subclasses of Objective-C classes, specifically when a custom `__new__` method is present, could behave unexpectedly or be disabled. While 10.3.1 partially reintroduced support, using `__init__` with PyObjC's `__new__` for Objective-C class instantiation is generally discouraged.
Install
-
pip install pyobjc-framework-fsevents
Imports
- FSEventStreamCreate
from FSEvents import FSEventStreamCreate
- objc
import objc
Quickstart
import objc
from FSEvents import (
FSEventStreamCreate,
FSEventStreamScheduleWithRunLoop,
FSEventStreamStart,
FSEventStreamStop,
FSEventStreamRelease,
kFSEventStreamEventFlagNone,
kFSEventStreamEventFlagFileEvents,
kFSEventStreamCreateFlagUseCFTypes
)
from Foundation import CFRunLoopGetCurrent, CFRunLoopRunInMode, kCFRunLoopDefaultMode, CFRunLoopStop
def my_callback(streamRef, clientCallBackInfo, numEvents, eventPaths, eventFlags, eventIds):
print(f"Detected {numEvents} events:")
for i in range(numEvents):
path = eventPaths[i]
flags = eventFlags[i]
print(f" Path: {path}, Flags: {flags}")
path_to_monitor = "./temp_monitor_dir" # Change to a directory you want to monitor
import os
if not os.path.exists(path_to_monitor):
os.makedirs(path_to_monitor)
cb = objc.selector(my_callback, signature='v@iiii@*Q')
stream = FSEventStreamCreate(
None, # allocator
cb, # callback
None, # context
[path_to_monitor], # pathsToWatch (array of CFStringRef)
0, # sinceWhen
1.0, # latency
kFSEventStreamCreateFlagUseCFTypes | kFSEventStreamEventFlagFileEvents # flags
)
if stream is None:
print("Failed to create FSEventStream")
else:
print(f"Monitoring '{path_to_monitor}' for file system events. Press Ctrl+C to stop...")
runLoop = CFRunLoopGetCurrent()
FSEventStreamScheduleWithRunLoop(stream, runLoop, kCFRunLoopDefaultMode)
FSEventStreamStart(stream)
try:
# Run the run loop for a short period, or forever for continuous monitoring
# For this example, we'll run for a fixed time or until stopped by user
CFRunLoopRunInMode(kCFRunLoopDefaultMode, 30.0, False) # Run for 30 seconds
except KeyboardInterrupt:
print("\nStopping monitoring...")
finally:
FSEventStreamStop(stream)
FSEventStreamRelease(stream)
CFRunLoopStop(runLoop)
print("FSEventStream stopped and released.")
# Clean up the temporary directory if created by this script
# if os.path.exists(path_to_monitor):
# os.rmdir(path_to_monitor)