1 // Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
2 // Use of this source code is governed by the MIT license that can be
3 // found in the LICENSE file.
5 // +build darwin,!kqueue
10 #include <CoreServices/CoreServices.h>
12 typedef void (*CFRunLoopPerformCallBack)(void*);
14 void gosource(void *);
15 void gostream(uintptr_t, uintptr_t, size_t, uintptr_t, uintptr_t, uintptr_t);
17 static FSEventStreamRef EventStreamCreate(FSEventStreamContext * context, uintptr_t info, CFArrayRef paths, FSEventStreamEventId since, CFTimeInterval latency, FSEventStreamCreateFlags flags) {
18 context->info = (void*) info;
19 return FSEventStreamCreate(NULL, (FSEventStreamCallback) gostream, context, paths, since, latency, flags);
22 #cgo LDFLAGS: -framework CoreServices
35 var nilstream C.FSEventStreamRef
37 // Default arguments for FSEventStreamCreate function.
39 latency C.CFTimeInterval
40 flags = C.FSEventStreamCreateFlags(C.kFSEventStreamCreateFlagFileEvents | C.kFSEventStreamCreateFlagNoDefer)
41 since = uint64(C.FSEventsGetCurrentEventId())
44 var runloop C.CFRunLoopRef // global runloop which all streams are registered with
45 var wg sync.WaitGroup // used to wait until the runloop starts
47 // source is used for synchronization purposes - it signals when runloop has
48 // started and is ready via the wg. It also serves purpose of a dummy source,
49 // thanks to it the runloop does not return as it also has at least one source
51 var source = C.CFRunLoopSourceCreate(nil, 0, &C.CFRunLoopSourceContext{
52 perform: (C.CFRunLoopPerformCallBack)(C.gosource),
55 // Errors returned when FSEvents functions fail.
57 errCreate = os.NewSyscallError("FSEventStreamCreate", errors.New("NULL"))
58 errStart = os.NewSyscallError("FSEventStreamStart", errors.New("false"))
61 // initializes the global runloop and ensures any created stream awaits its
66 runloop = C.CFRunLoopGetCurrent()
67 C.CFRunLoopAddSource(runloop, source, C.kCFRunLoopDefaultMode)
69 panic("runloop has just unexpectedly stopped")
71 C.CFRunLoopSourceSignal(source)
75 func gosource(unsafe.Pointer) {
76 time.Sleep(time.Second)
81 func gostream(_, info uintptr, n C.size_t, paths, flags, ids uintptr) {
83 offchar = unsafe.Sizeof((*C.char)(nil))
84 offflag = unsafe.Sizeof(C.FSEventStreamEventFlags(0))
85 offid = unsafe.Sizeof(C.FSEventStreamEventId(0))
90 ev := make([]FSEvent, 0, int(n))
91 for i := uintptr(0); i < uintptr(n); i++ {
92 switch flags := *(*uint32)(unsafe.Pointer((flags + i*offflag))); {
93 case flags&uint32(FSEventsEventIdsWrapped) != 0:
94 atomic.StoreUint64(&since, uint64(C.FSEventsGetCurrentEventId()))
96 ev = append(ev, FSEvent{
97 Path: C.GoString(*(**C.char)(unsafe.Pointer(paths + i*offchar))),
99 ID: *(*uint64)(unsafe.Pointer(ids + i*offid)),
104 streamFuncs.get(info)(ev)
107 // StreamFunc is a callback called when stream receives file events.
108 type streamFunc func([]FSEvent)
110 var streamFuncs = streamFuncRegistry{m: map[uintptr]streamFunc{}}
112 type streamFuncRegistry struct {
114 m map[uintptr]streamFunc
118 func (r *streamFuncRegistry) get(id uintptr) streamFunc {
124 func (r *streamFuncRegistry) add(fn streamFunc) uintptr {
132 func (r *streamFuncRegistry) delete(id uintptr) {
138 // Stream represents single watch-point which listens for events scheduled by
139 // the global runloop.
142 ref C.FSEventStreamRef
146 // NewStream creates a stream for given path, listening for file events and
147 // calling fn upon receiving any.
148 func newStream(path string, fn streamFunc) *stream {
151 info: streamFuncs.add(fn),
155 // Start creates a FSEventStream for the given path and schedules it with
156 // global runloop. It's a nop if the stream was already started.
157 func (s *stream) Start() error {
158 if s.ref != nilstream {
162 p := C.CFStringCreateWithCStringNoCopy(nil, C.CString(s.path), C.kCFStringEncodingUTF8, nil)
163 path := C.CFArrayCreate(nil, (*unsafe.Pointer)(unsafe.Pointer(&p)), 1, nil)
164 ctx := C.FSEventStreamContext{}
165 ref := C.EventStreamCreate(&ctx, C.uintptr_t(s.info), path, C.FSEventStreamEventId(atomic.LoadUint64(&since)), latency, flags)
166 if ref == nilstream {
169 C.FSEventStreamScheduleWithRunLoop(ref, runloop, C.kCFRunLoopDefaultMode)
170 if C.FSEventStreamStart(ref) == C.Boolean(0) {
171 C.FSEventStreamInvalidate(ref)
174 C.CFRunLoopWakeUp(runloop)
179 // Stop stops underlying FSEventStream and unregisters it from global runloop.
180 func (s *stream) Stop() {
181 if s.ref == nilstream {
185 C.FSEventStreamStop(s.ref)
186 C.FSEventStreamInvalidate(s.ref)
187 C.CFRunLoopWakeUp(runloop)
189 streamFuncs.delete(s.info)