Blender V4.3
bpy_rna_callback.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2023 Blender Authors
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
12#include <Python.h>
13
16
17#include "DNA_space_types.h"
18
19#include "RNA_access.hh"
20#include "RNA_enum_types.hh"
21#include "RNA_prototypes.hh"
22
23#include "BKE_screen.hh"
24
25#include "WM_api.hh"
26
27#include "ED_space_api.hh"
28
29#include "BPY_extern.hh" /* For public API. */
30
31#include "bpy_capi_utils.hh"
32#include "bpy_rna.hh"
33#include "bpy_rna_callback.hh" /* Own include. */
34
35/* Use this to stop other capsules from being mis-used. */
36static const char *rna_capsual_id = "RNA_HANDLE";
37static const char *rna_capsual_id_invalid = "RNA_HANDLE_REMOVED";
38
40 {REGION_DRAW_POST_PIXEL, "POST_PIXEL", 0, "Post Pixel", ""},
41 {REGION_DRAW_POST_VIEW, "POST_VIEW", 0, "Post View", ""},
42 {REGION_DRAW_PRE_VIEW, "PRE_VIEW", 0, "Pre View", ""},
43 {REGION_DRAW_BACKDROP, "BACKDROP", 0, "Backdrop", ""},
44 {0, nullptr, 0, nullptr, nullptr},
45};
46
47static void cb_region_draw(const bContext *C, ARegion * /*region*/, void *customdata)
48{
49 PyObject *cb_func, *cb_args, *result;
50 PyGILState_STATE gilstate;
51
52 bpy_context_set((bContext *)C, &gilstate);
53
54 cb_func = PyTuple_GET_ITEM((PyObject *)customdata, 1);
55 cb_args = PyTuple_GET_ITEM((PyObject *)customdata, 2);
56 result = PyObject_CallObject(cb_func, cb_args);
57
58 if (result) {
59 Py_DECREF(result);
60 }
61 else {
62 PyErr_Print();
63 PyErr_Clear();
64 }
65
66 bpy_context_clear((bContext *)C, &gilstate);
67}
68
69/* We could make generic utility */
70static PyObject *PyC_Tuple_CopySized(PyObject *src, int len_dst)
71{
72 PyObject *dst = PyTuple_New(len_dst);
73 const int len_src = PyTuple_GET_SIZE(src);
74 BLI_assert(len_src <= len_dst);
75 for (int i = 0; i < len_src; i++) {
76 PyObject *item = PyTuple_GET_ITEM(src, i);
77 PyTuple_SET_ITEM(dst, i, item);
78 Py_INCREF(item);
79 }
80 return dst;
81}
82
83static void cb_wm_cursor_draw(bContext *C, int x, int y, void *customdata)
84{
85 PyObject *cb_func, *cb_args, *result;
86 PyGILState_STATE gilstate;
87
88 bpy_context_set((bContext *)C, &gilstate);
89
90 cb_func = PyTuple_GET_ITEM((PyObject *)customdata, 1);
91 cb_args = PyTuple_GET_ITEM((PyObject *)customdata, 2);
92
93 const int cb_args_len = PyTuple_GET_SIZE(cb_args);
94
95 PyObject *cb_args_xy = PyTuple_New(2);
96 PyTuple_SET_ITEMS(cb_args_xy, PyLong_FromLong(x), PyLong_FromLong(y));
97
98 PyObject *cb_args_with_xy = PyC_Tuple_CopySized(cb_args, cb_args_len + 1);
99 PyTuple_SET_ITEM(cb_args_with_xy, cb_args_len, cb_args_xy);
100
101 result = PyObject_CallObject(cb_func, cb_args_with_xy);
102
103 Py_DECREF(cb_args_with_xy);
104
105 if (result) {
106 Py_DECREF(result);
107 }
108 else {
109 PyErr_Print();
110 PyErr_Clear();
111 }
112
113 bpy_context_clear((bContext *)C, &gilstate);
114}
115
116#if 0
117PyObject *pyrna_callback_add(BPy_StructRNA *self, PyObject *args)
118{
119 void *handle;
120
121 PyObject *cb_func, *cb_args;
122 char *cb_event_str = nullptr;
123 int cb_event;
124
125 if (!PyArg_ParseTuple(
126 args, "OO!|s:bpy_struct.callback_add", &cb_func, &PyTuple_Type, &cb_args, &cb_event_str))
127 {
128 return nullptr;
129 }
130
131 if (!PyCallable_Check(cb_func)) {
132 PyErr_SetString(PyExc_TypeError, "callback_add(): first argument isn't callable");
133 return nullptr;
134 }
135
136 if (RNA_struct_is_a(self->ptr.type, &RNA_Region)) {
137 if (cb_event_str) {
139 region_draw_mode_items, cb_event_str, &cb_event, "bpy_struct.callback_add()") == -1)
140 {
141 return nullptr;
142 }
143 }
144 else {
145 cb_event = REGION_DRAW_POST_PIXEL;
146 }
147
149 ((ARegion *)self->ptr.data)->type, cb_region_draw, (void *)args, cb_event);
150 Py_INCREF(args);
151 }
152 else {
153 PyErr_SetString(PyExc_TypeError, "callback_add(): type does not support callbacks");
154 return nullptr;
155 }
156
157 return PyCapsule_New((void *)handle, rna_capsual_id, nullptr);
158}
159
160PyObject *pyrna_callback_remove(BPy_StructRNA *self, PyObject *args)
161{
162 PyObject *py_handle;
163 void *handle;
164 void *customdata;
165
166 if (!PyArg_ParseTuple(args, "O!:callback_remove", &PyCapsule_Type, &py_handle)) {
167 return nullptr;
168 }
169
170 handle = PyCapsule_GetPointer(py_handle, rna_capsual_id);
171
172 if (handle == nullptr) {
173 PyErr_SetString(PyExc_ValueError,
174 "callback_remove(handle): nullptr handle given, invalid or already removed");
175 return nullptr;
176 }
177
178 if (RNA_struct_is_a(self->ptr.type, &RNA_Region)) {
179 customdata = ED_region_draw_cb_customdata(handle);
180 Py_DECREF((PyObject *)customdata);
181
182 ED_region_draw_cb_exit(((ARegion *)self->ptr.data)->type, handle);
183 }
184 else {
185 PyErr_SetString(PyExc_TypeError, "callback_remove(): type does not support callbacks");
186 return nullptr;
187 }
188
189 /* don't allow reuse */
190 PyCapsule_SetName(py_handle, rna_capsual_id_invalid);
191
192 Py_RETURN_NONE;
193}
194#endif
195
196/* reverse of rna_Space_refine() */
198{
199 if (srna == &RNA_SpaceView3D) {
200 return SPACE_VIEW3D;
201 }
202 if (srna == &RNA_SpaceGraphEditor) {
203 return SPACE_GRAPH;
204 }
205 if (srna == &RNA_SpaceOutliner) {
206 return SPACE_OUTLINER;
207 }
208 if (srna == &RNA_SpaceProperties) {
209 return SPACE_PROPERTIES;
210 }
211 if (srna == &RNA_SpaceFileBrowser) {
212 return SPACE_FILE;
213 }
214 if (srna == &RNA_SpaceImageEditor) {
215 return SPACE_IMAGE;
216 }
217 if (srna == &RNA_SpaceInfo) {
218 return SPACE_INFO;
219 }
220 if (srna == &RNA_SpaceSequenceEditor) {
221 return SPACE_SEQ;
222 }
223 if (srna == &RNA_SpaceTextEditor) {
224 return SPACE_TEXT;
225 }
226 if (srna == &RNA_SpaceDopeSheetEditor) {
227 return SPACE_ACTION;
228 }
229 if (srna == &RNA_SpaceNLA) {
230 return SPACE_NLA;
231 }
232 if (srna == &RNA_SpaceNodeEditor) {
233 return SPACE_NODE;
234 }
235 if (srna == &RNA_SpaceConsole) {
236 return SPACE_CONSOLE;
237 }
238 if (srna == &RNA_SpacePreferences) {
239 return SPACE_USERPREF;
240 }
241 if (srna == &RNA_SpaceClipEditor) {
242 return SPACE_CLIP;
243 }
244 if (srna == &RNA_SpaceSpreadsheet) {
245 return SPACE_SPREADSHEET;
246 }
247 return SPACE_EMPTY;
248}
249
250static void cb_rna_capsule_destructor(PyObject *capsule)
251{
252 PyObject *args = static_cast<PyObject *>(PyCapsule_GetContext(capsule));
253 Py_DECREF(args);
254}
255
256PyObject *pyrna_callback_classmethod_add(PyObject * /*self*/, PyObject *args)
257{
258 void *handle;
259 PyObject *cls;
260 PyObject *cb_func, *cb_args;
261 StructRNA *srna;
262
263 if (PyTuple_GET_SIZE(args) < 2) {
264 PyErr_SetString(PyExc_ValueError, "handler_add(handler): expected at least 2 args");
265 return nullptr;
266 }
267
268 cls = PyTuple_GET_ITEM(args, 0);
269 if (!(srna = pyrna_struct_as_srna(cls, false, "handler_add"))) {
270 return nullptr;
271 }
272 cb_func = PyTuple_GET_ITEM(args, 1);
273 if (!PyCallable_Check(cb_func)) {
274 PyErr_SetString(PyExc_TypeError, "first argument isn't callable");
275 return nullptr;
276 }
277
278 /* class specific callbacks */
279
280 if (srna == &RNA_WindowManager) {
281 struct {
282 BPy_EnumProperty_Parse space_type_enum;
283 BPy_EnumProperty_Parse region_type_enum;
284 } params{};
285 params.space_type_enum.items = rna_enum_space_type_items;
286 params.space_type_enum.value = SPACE_TYPE_ANY;
287 params.region_type_enum.items = rna_enum_region_type_items;
288 params.region_type_enum.value = RGN_TYPE_ANY;
289
290 if (!PyArg_ParseTuple(args,
291 "OOO!|O&O&:WindowManager.draw_cursor_add",
292 &cls,
293 &cb_func, /* already assigned, no matter */
294 &PyTuple_Type,
295 &cb_args,
297 &params.space_type_enum,
299 &params.region_type_enum))
300 {
301 return nullptr;
302 }
303
304 handle = WM_paint_cursor_activate(params.space_type_enum.value,
305 params.region_type_enum.value,
306 nullptr,
308 (void *)args);
309 }
310 else if (RNA_struct_is_a(srna, &RNA_Space)) {
311 struct {
312 BPy_EnumProperty_Parse region_type_enum;
313 BPy_EnumProperty_Parse event_enum;
314 } params{};
315 params.region_type_enum.items = rna_enum_region_type_items;
316 params.event_enum.items = region_draw_mode_items;
317
318 if (!PyArg_ParseTuple(args,
319 "OOO!O&O&:Space.draw_handler_add",
320 &cls,
321 &cb_func, /* already assigned, no matter */
322 &PyTuple_Type,
323 &cb_args,
325 &params.region_type_enum,
327 &params.event_enum))
328 {
329 return nullptr;
330 }
331
332 const eSpace_Type spaceid = rna_Space_refine_reverse(srna);
333 if (spaceid == SPACE_EMPTY) {
334 PyErr_Format(PyExc_TypeError, "unknown space type '%.200s'", RNA_struct_identifier(srna));
335 return nullptr;
336 }
337
338 SpaceType *st = BKE_spacetype_from_id(spaceid);
339 ARegionType *art = BKE_regiontype_from_id(st, params.region_type_enum.value);
340 if (art == nullptr) {
341 PyErr_Format(
342 PyExc_TypeError, "region type %R not in space", params.region_type_enum.value_orig);
343 return nullptr;
344 }
346 art, cb_region_draw, (void *)args, params.event_enum.value);
347 }
348 else {
349 PyErr_SetString(PyExc_TypeError, "callback_add(): type does not support callbacks");
350 return nullptr;
351 }
352
353 /* Keep the 'args' reference as long as the callback exists.
354 * This reference is decremented in #BPY_callback_screen_free and #BPY_callback_wm_free. */
355 Py_INCREF(args);
356
357 PyObject *ret = PyCapsule_New((void *)handle, rna_capsual_id, nullptr);
358
359 /* Store 'args' in context as well for simple access. */
360 PyCapsule_SetDestructor(ret, cb_rna_capsule_destructor);
361 PyCapsule_SetContext(ret, args);
362 Py_INCREF(args);
363
364 return ret;
365}
366
367PyObject *pyrna_callback_classmethod_remove(PyObject * /*self*/, PyObject *args)
368{
369 PyObject *cls;
370 PyObject *py_handle;
371 void *handle;
372 StructRNA *srna;
373 bool capsule_clear = false;
374 bool handle_removed = false;
375
376 if (PyTuple_GET_SIZE(args) < 2) {
377 PyErr_SetString(PyExc_ValueError, "callback_remove(handler): expected at least 2 args");
378 return nullptr;
379 }
380
381 cls = PyTuple_GET_ITEM(args, 0);
382 if (!(srna = pyrna_struct_as_srna(cls, false, "callback_remove"))) {
383 return nullptr;
384 }
385 py_handle = PyTuple_GET_ITEM(args, 1);
386 handle = PyCapsule_GetPointer(py_handle, rna_capsual_id);
387 if (handle == nullptr) {
388 PyErr_SetString(PyExc_ValueError,
389 "callback_remove(handler): nullptr handler given, invalid or already removed");
390 return nullptr;
391 }
392
393 if (srna == &RNA_WindowManager) {
394 if (!PyArg_ParseTuple(
395 args, "OO!:WindowManager.draw_cursor_remove", &cls, &PyCapsule_Type, &py_handle))
396 {
397 return nullptr;
398 }
399 handle_removed = WM_paint_cursor_end(static_cast<wmPaintCursor *>(handle));
400 capsule_clear = true;
401 }
402 else if (RNA_struct_is_a(srna, &RNA_Space)) {
403 const char *error_prefix = "Space.draw_handler_remove";
404 struct {
405 BPy_EnumProperty_Parse region_type_enum;
406 } params{};
407 params.region_type_enum.items = rna_enum_region_type_items;
408
409 if (!PyArg_ParseTuple(args,
410 "OO!O&:Space.draw_handler_remove",
411 &cls,
412 &PyCapsule_Type,
413 &py_handle, /* already assigned, no matter */
415 &params.region_type_enum))
416 {
417 return nullptr;
418 }
419
420 const eSpace_Type spaceid = rna_Space_refine_reverse(srna);
421 if (spaceid == SPACE_EMPTY) {
422 PyErr_Format(PyExc_TypeError,
423 "%s: unknown space type '%.200s'",
424 error_prefix,
426 return nullptr;
427 }
428
429 SpaceType *st = BKE_spacetype_from_id(spaceid);
430 ARegionType *art = BKE_regiontype_from_id(st, params.region_type_enum.value);
431 if (art == nullptr) {
432 PyErr_Format(PyExc_TypeError,
433 "%s: region type %R not in space",
434 error_prefix,
435 params.region_type_enum.value_orig);
436 return nullptr;
437 }
438 handle_removed = ED_region_draw_cb_exit(art, handle);
439 capsule_clear = true;
440 }
441 else {
442 PyErr_SetString(PyExc_TypeError, "callback_remove(): type does not support callbacks");
443 return nullptr;
444 }
445
446 /* When `handle_removed == false`: Blender has already freed the data
447 * (freeing screen data when loading a new file for example).
448 * This will have already decremented the user, so don't decrement twice. */
449 if (handle_removed == true) {
450 /* The handle has been removed, so decrement its custom-data. */
451 PyObject *handle_args = static_cast<PyObject *>(PyCapsule_GetContext(py_handle));
452 Py_DECREF(handle_args);
453 }
454
455 /* don't allow reuse */
456 if (capsule_clear) {
457 PyCapsule_Destructor destructor_fn = PyCapsule_GetDestructor(py_handle);
458 if (destructor_fn) {
459 destructor_fn(py_handle);
460 PyCapsule_SetDestructor(py_handle, nullptr);
461 }
462 PyCapsule_SetName(py_handle, rna_capsual_id_invalid);
463 }
464
465 Py_RETURN_NONE;
466}
467
468/* -------------------------------------------------------------------- */
472static void cb_customdata_free(void *customdata)
473{
474 PyObject *tuple = static_cast<PyObject *>(customdata);
475 bool use_gil = true; /* !PyC_IsInterpreterActive(); */
476
477 PyGILState_STATE gilstate;
478 if (use_gil) {
479 gilstate = PyGILState_Ensure();
480 }
481
482 Py_DECREF(tuple);
483
484 if (use_gil) {
485 PyGILState_Release(gilstate);
486 }
487}
488
490{
492 art, reinterpret_cast<void *>(cb_region_draw), cb_customdata_free);
493}
494
500
SpaceType * BKE_spacetype_from_id(int spaceid)
Definition screen.cc:243
ARegionType * BKE_regiontype_from_id(const SpaceType *st, int regionid)
Definition screen.cc:253
#define BLI_assert(a)
Definition BLI_assert.h:50
#define RGN_TYPE_ANY
eSpace_Type
@ SPACE_TEXT
@ SPACE_CLIP
@ SPACE_ACTION
@ SPACE_CONSOLE
@ SPACE_OUTLINER
@ SPACE_NODE
@ SPACE_SPREADSHEET
@ SPACE_USERPREF
@ SPACE_FILE
@ SPACE_PROPERTIES
@ SPACE_NLA
@ SPACE_SEQ
@ SPACE_EMPTY
@ SPACE_IMAGE
@ SPACE_GRAPH
@ SPACE_VIEW3D
@ SPACE_INFO
#define SPACE_TYPE_ANY
void * ED_region_draw_cb_activate(ARegionType *art, void(*draw)(const bContext *, ARegion *, void *), void *customdata, int type)
void ED_region_draw_cb_remove_by_type(ARegionType *art, void *draw_fn, void(*free)(void *))
#define REGION_DRAW_POST_VIEW
#define REGION_DRAW_BACKDROP
bool ED_region_draw_cb_exit(ARegionType *art, void *handle)
#define REGION_DRAW_POST_PIXEL
#define REGION_DRAW_PRE_VIEW
void bpy_context_clear(struct bContext *C, const PyGILState_STATE *gilstate)
void bpy_context_set(struct bContext *C, PyGILState_STATE *gilstate)
PyObject * self
StructRNA * pyrna_struct_as_srna(PyObject *self, const bool parent, const char *error_prefix)
Definition bpy_rna.cc:8124
static eSpace_Type rna_Space_refine_reverse(StructRNA *srna)
static PyObject * PyC_Tuple_CopySized(PyObject *src, int len_dst)
static const char * rna_capsual_id_invalid
static void cb_customdata_free(void *customdata)
void BPY_callback_screen_free(ARegionType *art)
static void cb_region_draw(const bContext *C, ARegion *, void *customdata)
static void cb_rna_capsule_destructor(PyObject *capsule)
PyObject * pyrna_callback_classmethod_remove(PyObject *, PyObject *args)
static void cb_wm_cursor_draw(bContext *C, int x, int y, void *customdata)
PyObject * pyrna_callback_classmethod_add(PyObject *, PyObject *args)
static const EnumPropertyItem region_draw_mode_items[]
static const char * rna_capsual_id
void BPY_callback_wm_free(wmWindowManager *wm)
uiWidgetBaseParameters params[MAX_WIDGET_BASE_BATCH]
int pyrna_enum_value_from_id(const EnumPropertyItem *item, const char *identifier, int *r_value, const char *error_prefix)
int pyrna_enum_value_parse_string(PyObject *o, void *p)
header-only utilities
#define PyTuple_SET_ITEMS(op_arg,...)
return ret
bool RNA_struct_is_a(const StructRNA *type, const StructRNA *srna)
const char * RNA_struct_identifier(const StructRNA *type)
const EnumPropertyItem rna_enum_region_type_items[]
Definition rna_screen.cc:24
const EnumPropertyItem rna_enum_space_type_items[]
Definition rna_space.cc:97
bool WM_paint_cursor_end(wmPaintCursor *handle)
void WM_paint_cursor_remove_by_type(wmWindowManager *wm, void *draw_fn, void(*free)(void *))
wmPaintCursor * WM_paint_cursor_activate(short space_type, short region_type, bool(*poll)(bContext *C), wmPaintCursorDraw draw, void *customdata)