1/*
2 * Support for callback functions/structs in the FSEvents frameework.
3 */
4#include <Python.h>
5#include "pyobjc-api.h"
6
7#import <CoreServices/CoreServices.h>
8
9static const void*
10m_retain_python(const void* value)
11{
12	PyGILState_STATE   state = PyGILState_Ensure();
13
14	Py_XINCREF((PyObject*)value);
15
16        PyGILState_Release(state);
17
18	return value;
19}
20
21static void
22m_release_python(const void* value)
23{
24	PyGILState_STATE   state = PyGILState_Ensure();
25
26	Py_XDECREF((PyObject*)value);
27
28        PyGILState_Release(state);
29}
30
31static CFStringRef
32m_copyDescription_python(const void* value)
33{
34	CFStringRef result;
35	PyObject* description;
36	int r;
37
38	PyGILState_STATE   state = PyGILState_Ensure();
39
40	description = PyObject_Repr((PyObject*)value);
41	if (description == NULL) {
42		PyObjCErr_ToObjCWithGILState(&state);
43	}
44
45	r = PyObjC_PythonToObjC(@encode(CFStringRef), description, &result);
46	Py_DECREF(description);
47	if (r == -1) {
48		PyObjCErr_ToObjCWithGILState(&state);
49	}
50
51	/* description is autoreleased, we should donate a reference to
52	 * our caller
53	 */
54	CFRetain(result);
55
56        PyGILState_Release(state);
57	return result;
58}
59
60static FSEventStreamContext m_python_context_template = {
61	0,
62	NULL,
63	m_retain_python,
64	m_release_python,
65	m_copyDescription_python
66};
67
68static void
69m_FSEVentStreamCallback(
70	ConstFSEventStreamRef streamRef,
71	void* clientCallbackInfo,
72	size_t numEvents,
73	void* eventPaths,
74	const FSEventStreamEventFlags eventFlags[],
75	const FSEventStreamEventId eventIds[])
76{
77	PyGILState_STATE   state = PyGILState_Ensure();
78	FSEventStreamCreateFlags flags;
79	PyObject* callback;
80	PyObject* info;
81	PyObject* v;
82	PyObject* paths;
83
84	v = PyTuple_GET_ITEM((PyObject*)clientCallbackInfo, 0);
85	if (PyObjC_PythonToObjC(
86			@encode(FSEventStreamCreateFlags), v, &flags) < 0) {
87		PyObjCErr_ToObjCWithGILState(&state);
88	}
89
90	info = PyTuple_GET_ITEM((PyObject*)clientCallbackInfo, 1);
91	callback = PyTuple_GET_ITEM((PyObject*)clientCallbackInfo, 2);
92
93	if (flags & kFSEventStreamCreateFlagUseCFTypes) {
94		/* The evenPaths are an CFArray */
95		paths = PyObjC_ObjCToPython(@encode(CFArrayRef), &eventPaths);
96		if (paths == NULL) {
97			PyObjCErr_ToObjCWithGILState(&state);
98		}
99	} else {
100		/* The evenPaths are a CArray of C strings */
101		paths = PyObjC_CArrayToPython(@encode(char*),
102				eventPaths, numEvents);
103		if (paths == NULL) {
104			PyObjCErr_ToObjCWithGILState(&state);
105		}
106	}
107
108	PyObject* py_streamRef = PyObjC_ObjCToPython(
109			@encode(ConstFSEventStreamRef),
110			&streamRef);
111	if (py_streamRef == NULL) {
112		Py_DECREF(paths);
113		PyObjCErr_ToObjCWithGILState(&state);
114	}
115	PyObject* py_eventFlags = PyObjC_CArrayToPython(
116			@encode(FSEventStreamCreateFlags),
117			(void*)eventFlags, numEvents);
118	if (py_eventFlags == NULL) {
119		Py_DECREF(paths);
120		Py_DECREF(py_streamRef);
121		PyObjCErr_ToObjCWithGILState(&state);
122	}
123	PyObject* py_eventIds = PyObjC_CArrayToPython(
124			@encode(FSEventStreamEventId),
125			(void*)eventIds, numEvents);
126	if (py_eventIds == NULL) {
127		Py_DECREF(paths);
128		Py_DECREF(py_streamRef);
129		Py_DECREF(py_eventFlags);
130		PyObjCErr_ToObjCWithGILState(&state);
131	}
132
133	PyObject* result = PyObject_CallFunction(callback,
134			"OO" Py_ARG_SIZE_T "OOO",
135			py_streamRef, info, numEvents, paths,
136			py_eventFlags,
137			py_eventIds);
138	Py_DECREF(paths);
139	Py_DECREF(py_streamRef);
140	Py_DECREF(py_eventFlags);
141	Py_DECREF(py_eventIds);
142	if (result == NULL) {
143		PyObjCErr_ToObjCWithGILState(&state);
144	}
145	Py_DECREF(result);
146
147        PyGILState_Release(state);
148}
149
150PyDoc_STRVAR(m_FSEventStreamCreate_doc,
151	"FSEventStreamCreate(allocator, callback, callback_info, \n"
152	"	pathsToWatch, sinceWhen, latency, flags) -> stream\n"
153	"\n"
154	"NOTE: the callback info is passed directly, it is not a structure as\n"
155	"it is in C");
156static PyObject*
157m_FSEventStreamCreate(PyObject* self __attribute__((__unused__)),
158		PyObject* args)
159{
160	PyObject* py_allocator;
161	PyObject* py_callback;
162	PyObject* py_callback_info;
163	PyObject* py_pathsToWatch;
164	PyObject* py_sinceWhen;
165	PyObject* py_latency;
166	PyObject* py_flags;
167
168	if (!PyArg_ParseTuple(args, "OOOOOOO",
169		&py_allocator, &py_callback, &py_callback_info,
170		&py_pathsToWatch, &py_sinceWhen, &py_latency, &py_flags)) {
171
172		return NULL;
173	}
174
175	CFAllocatorRef allocator;
176	if (PyObjC_PythonToObjC(@encode(CFAllocatorRef), py_allocator, &allocator) < 0) {
177		return NULL;
178	}
179
180	CFArrayRef pathsToWatch;
181	if (PyObjC_PythonToObjC(@encode(CFArrayRef), py_pathsToWatch, &pathsToWatch) < 0) {
182		return NULL;
183	}
184
185	FSEventStreamEventId sinceWhen;
186	if (PyObjC_PythonToObjC(@encode(FSEventStreamEventId), py_sinceWhen, &sinceWhen) < 0) {
187		return NULL;
188	}
189
190	CFTimeInterval latency;
191	if (PyObjC_PythonToObjC(@encode(CFTimeInterval), py_latency, &latency) < 0) {
192		return NULL;
193	}
194
195	FSEventStreamCreateFlags flags;
196	if (PyObjC_PythonToObjC(@encode(FSEventStreamCreateFlags), py_flags, &flags) < 0) {
197		return NULL;
198	}
199
200	/*
201	 * Build the actual callback info, which includes the flags because
202	 * the arguments passed to the callback vary based on the value of
203	 * flags.
204	 */
205	PyObject* info = Py_BuildValue("OOO",
206			py_flags, py_callback_info, py_callback);
207	if (info == NULL) {
208		return NULL;
209	}
210
211	FSEventStreamContext context = m_python_context_template;
212	context.info = info;
213
214	FSEventStreamRef stream = NULL;
215
216	PyObjC_DURING
217		stream = FSEventStreamCreate(
218				allocator,
219				m_FSEVentStreamCallback,
220				&context,
221				pathsToWatch,
222				sinceWhen,
223				latency,
224				flags);
225
226	PyObjC_HANDLER
227		stream = NULL;
228		PyObjCErr_FromObjC(localException);
229
230	PyObjC_ENDHANDLER
231
232	Py_DECREF(info);
233
234	if (stream == NULL && PyErr_Occurred()) {
235		return NULL;
236	}
237
238	if (stream == NULL) {
239		Py_INCREF(Py_None);
240		return Py_None;
241	}
242
243	PyObject* result =  PyObjC_ObjCToPython(@encode(FSEventStreamRef), &stream);
244	// FSEventStreamRef is not a CF type (AFAIK), hence the user is
245	// responsible for maintaining the refcount.
246	//FSEventStreamRelease(stream);
247	return result;
248}
249
250
251PyDoc_STRVAR(m_FSEventStreamCreateRelativeToDevice_doc,
252	"FSEventStreamCreate(allocator, callback, callback_info, \n"
253	"	deviceToWatch, pathsToWatchRelativeToDevice, sinceWhen, \n"
254	"	latency, flags) -> stream\n"
255	"\n"
256	"NOTE: the callback info is passed directly, it is not a structure as\n"
257	"it is in C");
258static PyObject*
259m_FSEventStreamCreateRelativeToDevice(PyObject* self __attribute__((__unused__)),
260		PyObject* args)
261{
262	PyObject* py_allocator;
263	PyObject* py_callback;
264	PyObject* py_callback_info;
265	PyObject* py_pathsToWatch;
266	PyObject* py_sinceWhen;
267	PyObject* py_latency;
268	PyObject* py_flags;
269	PyObject* py_deviceToWatch;
270
271	if (!PyArg_ParseTuple(args, "OOOOOOOO",
272		&py_allocator, &py_callback, &py_callback_info,
273		&py_deviceToWatch, &py_pathsToWatch, &py_sinceWhen,
274		&py_latency, &py_flags)) {
275
276		return NULL;
277	}
278
279	CFAllocatorRef allocator;
280	if (PyObjC_PythonToObjC(@encode(CFAllocatorRef), py_allocator, &allocator) < 0) {
281		return NULL;
282	}
283
284	dev_t deviceToWatch;
285	if (PyObjC_PythonToObjC(@encode(dev_t), py_deviceToWatch, &deviceToWatch) < 0) {
286		return NULL;
287	}
288
289	CFArrayRef pathsToWatch;
290	if (PyObjC_PythonToObjC(@encode(CFArrayRef), py_pathsToWatch, &pathsToWatch) < 0) {
291		return NULL;
292	}
293
294	FSEventStreamEventId sinceWhen;
295	if (PyObjC_PythonToObjC(@encode(FSEventStreamEventId), py_sinceWhen, &sinceWhen) < 0) {
296		return NULL;
297	}
298
299	CFTimeInterval latency;
300	if (PyObjC_PythonToObjC(@encode(CFTimeInterval), py_latency, &latency) < 0) {
301		return NULL;
302	}
303
304	FSEventStreamCreateFlags flags;
305	if (PyObjC_PythonToObjC(@encode(FSEventStreamCreateFlags), py_flags, &flags) < 0) {
306		return NULL;
307	}
308
309	/*
310	 * Build the actual callback info, which includes the flags because
311	 * the arguments passed to the callback vary based on the value of
312	 * flags.
313	 */
314	PyObject* info = Py_BuildValue("OOO",
315			py_flags, py_callback_info, py_callback);
316	if (info == NULL) {
317		return NULL;
318	}
319
320	FSEventStreamContext context = m_python_context_template;
321	context.info = info;
322
323	FSEventStreamRef stream = NULL;
324
325	PyObjC_DURING
326		stream = FSEventStreamCreateRelativeToDevice(
327				allocator,
328				m_FSEVentStreamCallback,
329				&context,
330				deviceToWatch,
331				pathsToWatch,
332				sinceWhen,
333				latency,
334				flags);
335	PyObjC_HANDLER
336		stream = NULL;
337		PyObjCErr_FromObjC(localException);
338
339	PyObjC_ENDHANDLER
340
341	Py_DECREF(info);
342
343	if (stream == NULL && PyErr_Occurred()) {
344		return NULL;
345	}
346
347	if (stream == NULL) {
348		Py_INCREF(Py_None);
349		return Py_None;
350	}
351
352	PyObject* result = PyObjC_ObjCToPython(
353			@encode(FSEventStreamRef), &stream);
354	// FSEventStreamRef is not a CF type (AFAIK), hence the user is
355	// responsible for maintaining the refcount.
356	//FSEventStreamRelease(stream);
357	return result;
358}
359
360
361static PyMethodDef m_methods[] = {
362	{
363		"FSEventStreamCreate",
364		(PyCFunction)m_FSEventStreamCreate,
365		METH_VARARGS,
366		m_FSEventStreamCreate_doc
367	},
368
369	{
370		"FSEventStreamCreateRelativeToDevice",
371		(PyCFunction)m_FSEventStreamCreateRelativeToDevice,
372		METH_VARARGS,
373		m_FSEventStreamCreateRelativeToDevice_doc
374	},
375
376
377	{ 0, 0, 0, }
378};
379
380void init_callbacks(void);
381void init_callbacks(void)
382{
383	PyObject* m = Py_InitModule4("_callbacks", m_methods,
384		NULL, NULL, PYTHON_API_VERSION);
385
386        if (PyObjC_ImportAPI(m) < 0) { return; }
387}
388