1/*
2 * Special wrappers for pointer values
3 *
4 * Pointer arguments to methods can be split into 3 classes:
5 * - Pass-by-reference arguments (in, out, inout).
6 *   This is supported by using the modifiers _C_IN, _C_OUT and _C_INOUT
7 * - Pointers to buffers
8 *   This requires special support, you can't detect the size of the buffer
9 *   from the signature
10 * - Opaque values
11 *   Types like FSRef are for all intents and purposes opaque values,
12 *   that happen to be represented using pointers (to structs). The functions
13 *   in this file allow extension modules to register functions to convert these
14 *   values to and from their Python representation.
15 *
16 * NOTE:
17 * - The pythonify and depythonify functions have the same interface as
18 *   the *_New and *_Convert functions in MacPython (pymactoolbox.h), this
19 *   makes it easier to interface with that packages.
20 */
21#include "pyobjc.h"
22
23
24#import <CoreFoundation/CoreFoundation.h>
25
26struct wrapper {
27	const char* signature;
28	int offset;
29	PyObject* (*pythonify)(void*);
30	int (*depythonify)(PyObject*, void*);
31};
32
33/* Using an array is pretty lame, this needs to be replaced by a more
34 * efficient datastructure. However: As long as their is only a limited
35 * number of custom wrappers this should not be a problem.
36 */
37static struct wrapper* items = 0;
38static Py_ssize_t item_count = 0;
39
40/*
41 * If signature is a pointer to a structure return the index of the character
42 * just beyond the end of the struct name. This information is needed because
43 * @encode(struct foo*) can return two different strings:
44 * 1) ^{foo} if the compiler has not yet seen a full definition of struct foo
45 * 2) ^{foo=...} if the compiler has not yet seen a full definition of
46 *    the struct
47 * We want to treat those two pointer as the same type, therefore we need to
48 * ignore everything beyond the end of the struct name.
49 */
50static int find_end_of_structname(const char* signature) {
51	if (signature[1] == _C_CONST && signature[2] == _C_STRUCT_B) {
52		char* end1;
53		char* end2;
54
55		end1 = strchr(signature, _C_STRUCT_E);
56		end2 = strchr(signature, '=');
57
58		if (end2 == NULL) {
59			return end1 - signature;
60		} else {
61			return end2 - signature;
62		}
63
64	} else if (signature[1] == _C_STRUCT_B) {
65		char* end1;
66		char* end2;
67
68		end1 = strchr(signature, _C_STRUCT_E);
69		end2 = strchr(signature, '=');
70
71		if (end2 == NULL) {
72			return end1 - signature;
73		} else {
74			return end2 - signature;
75		}
76	}
77	return strlen(signature);
78}
79
80static struct wrapper*
81FindWrapper(const char* signature)
82{
83	Py_ssize_t i;
84	Py_ssize_t len;
85
86	len = strlen(signature);
87
88	for (i = 0; i < item_count; i++) {
89		if (strncmp(signature, items[i].signature, items[i].offset) == 0) {
90			/* See comment just above find_end_of_structname */
91			if (signature[1] == _C_CONST && signature[2] == _C_STRUCT_B) {
92				char ch = signature[items[i].offset];
93				if (ch == '=' || ch == _C_STRUCT_E) {
94					return items + i;
95				}
96
97			} else if (signature[1] == _C_STRUCT_B) {
98				char ch = signature[items[i].offset];
99				if (ch == '=' || ch == _C_STRUCT_E) {
100					return items + i;
101				}
102
103			} else {
104				if (signature[items[i].offset] == '\0') {
105					return items + i;
106				}
107			}
108		}
109	}
110	return NULL;
111}
112
113static PyObject*
114ID_to_py(void* idValue)
115{
116	return pythonify_c_value(@encode(id), &idValue);
117}
118
119static int
120py_to_ID(PyObject* obj, void* output)
121{
122	return depythonify_c_value(@encode(id), obj, output);
123}
124
125int PyObjCPointerWrapper_RegisterID(const char *signature) {
126	return PyObjCPointerWrapper_Register(signature,
127		(PyObjCPointerWrapper_ToPythonFunc)&ID_to_py,
128		(PyObjCPointerWrapper_FromPythonFunc)&py_to_ID);
129}
130
131int
132PyObjCPointerWrapper_Register(
133	const char* signature,
134	PyObjCPointerWrapper_ToPythonFunc pythonify,
135	PyObjCPointerWrapper_FromPythonFunc depythonify
136
137	)
138{
139	struct wrapper* value;
140
141	/*
142	 * Check if we already have a wrapper, if so replace that.
143	 * This makes it possible to replace a default wrapper by something
144	 * better.
145	 */
146	if (signature == NULL) {
147		return -1;
148	}
149	value = FindWrapper(signature);
150	if (value != NULL) {
151		value->pythonify = pythonify;
152		value->depythonify = depythonify;
153		return 0;
154	}
155
156	if (items == NULL) {
157		items = PyMem_Malloc(sizeof(struct wrapper));
158		if (items == NULL) {
159			PyErr_NoMemory();
160			return -1;
161		}
162		item_count = 1;
163	} else {
164		struct wrapper* tmp;
165
166		tmp = PyMem_Realloc(
167			items, sizeof(struct wrapper) *  (item_count+1));
168		if (tmp == NULL) {
169			PyErr_NoMemory();
170			return -1;
171		}
172		items = tmp;
173		item_count ++;
174	}
175
176	value = items + (item_count-1);
177
178	value->signature = PyObjCUtil_Strdup(signature);
179	if (value->signature == NULL) {
180		PyErr_NoMemory();
181		item_count --;
182		return -1;
183	}
184
185	value->offset = find_end_of_structname(value->signature);
186
187	value->pythonify = pythonify;
188	value->depythonify = depythonify;
189
190	return 0;
191}
192
193
194
195PyObject*
196PyObjCPointerWrapper_ToPython(const char* type, void* datum)
197{
198	struct wrapper* item;
199	PyObject* result;
200
201	item = FindWrapper(type);
202	if (item == NULL) {
203		return NULL;
204	}
205
206	if (item->pythonify == ID_to_py) {
207		result = PyObjC_FindPythonProxy(*(id*)datum);
208		if (result != NULL) {
209			return result;
210
211		} else if (*(void**)datum == kCFAllocatorUseContext) {
212			/* kCFAllocatorUseContext is a bit too magic for its
213			 * own good.
214			 *
215			 * Note that this is a crude hack, but as long as this
216			 * is the only such object I don't think its worthwhile
217			 * to add generic support for this.
218			 */
219			result = PyObjCCF_NewSpecial2(
220				CFAllocatorGetTypeID(), *(void**)datum);
221
222			PyObjC_RegisterPythonProxy(*(id*)datum, result);
223			return result;
224		}
225	}
226
227	result = item->pythonify(*(void**)datum);
228	return result;
229}
230
231
232int
233PyObjCPointerWrapper_FromPython(
234	const char* type, PyObject* value, void* datum)
235{
236	struct wrapper* item;
237	int r;
238
239	if (value == PyObjC_NULL) {
240		*(void**)datum = NULL;
241		return 0;
242	}
243
244	item = FindWrapper(type);
245	if (item == NULL) {
246		return -1;
247	}
248
249	r = item->depythonify(value, datum);
250	if (r == 0) {
251		return 0;
252	} else {
253		return -1;
254	}
255}
256
257int PyObjCPointerWrapper_HaveWrapper(const char* type)
258{
259	return (FindWrapper(type) != NULL);
260}
261
262
263static PyObject*
264PyObjectPtr_New(void *obj)
265{
266	return (PyObject*)obj;
267}
268
269static int
270PyObjectPtr_Convert(PyObject* obj, void* pObj)
271{
272	*(void**)pObj = (void *)obj;
273	return 0;
274}
275
276static int dontClose(FILE* fp __attribute__((__unused__)))
277{
278	return 0;
279}
280static PyObject*
281FILE_New(void *obj)
282{
283	FILE* fp = (FILE*)obj;
284	char* mode = "r";
285
286#if defined(__SRW)
287	/* This is a hack, but allows us to pass the right file mode into
288	 * Python.
289	 */
290	if (fp->_flags & __SWR) {
291		mode = "w";
292	} else if (fp->_flags & __SRW) {
293		mode = "w+";
294	}
295
296#endif
297	return PyFile_FromFile(fp, "<objc-file>", mode, dontClose);
298}
299
300static int
301FILE_Convert(PyObject* obj, void* pObj)
302{
303	*(FILE**)pObj = PyFile_AsFile(obj);
304	if (*(FILE**)pObj == NULL) {
305		return 1;
306	}
307
308	return 0;
309}
310
311/*
312 * Generic CF type support
313 */
314static PyObject*
315CF_to_py(void* cfValue)
316{
317	return PyObjC_IDToCFType((id)cfValue);
318}
319
320static int
321py_to_CF(PyObject* obj, void* output)
322{
323	id tmp = PyObjC_CFTypeToID(obj);
324	if (tmp == NULL && PyErr_Occurred()) {
325		return -1;
326	}
327	*(void**)output = tmp;
328	return 0;
329}
330
331int PyObjCPointerWrapper_RegisterCF(const char *signature) {
332	return PyObjCPointerWrapper_Register(signature,
333		(PyObjCPointerWrapper_ToPythonFunc)&CF_to_py,
334		(PyObjCPointerWrapper_FromPythonFunc)&py_to_CF);
335}
336
337
338int
339PyObjCPointerWrapper_Init(void)
340{
341	int r = 0;
342
343	r = PyObjCPointerWrapper_RegisterCF(@encode(CFURLRef));
344	if (r == -1) return -1;
345
346	r = PyObjCPointerWrapper_RegisterCF(@encode(CFSetRef));
347	if (r == -1) return -1;
348
349#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_2
350	r = PyObjCPointerWrapper_RegisterCF(@encode(CFNetServiceRef));
351	if (r == -1) return -1;
352#endif
353
354	r = PyObjCPointerWrapper_RegisterCF(@encode(CFReadStreamRef));
355	if (r == -1) return -1;
356
357	r = PyObjCPointerWrapper_RegisterCF(@encode(CFRunLoopRef));
358	if (r == -1) return -1;
359
360	r = PyObjCPointerWrapper_Register(@encode(PyObject*),
361		PyObjectPtr_New, PyObjectPtr_Convert);
362	if (r == -1) return -1;
363
364	r = PyObjCPointerWrapper_Register(@encode(FILE*),
365		FILE_New, FILE_Convert);
366	if (r == -1) return -1;
367
368	return 0;
369}
370