Blender V4.5
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
11
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 PyGILState_STATE gilstate;
50 bpy_context_set((bContext *)C, &gilstate);
51
52 PyObject *cb_func, *cb_args, *result;
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
84 const blender::int2 &xy,
85 const blender::float2 & /*tilt*/,
86 void *customdata)
87{
88 PyGILState_STATE gilstate;
89 bpy_context_set(C, &gilstate);
90
91 PyObject *cb_func, *cb_args, *result;
92 cb_func = PyTuple_GET_ITEM((PyObject *)customdata, 1);
93 cb_args = PyTuple_GET_ITEM((PyObject *)customdata, 2);
94
95 const int cb_args_len = PyTuple_GET_SIZE(cb_args);
96
97 PyObject *cb_args_xy = PyTuple_New(2);
98 PyTuple_SET_ITEMS(cb_args_xy, PyLong_FromLong(xy.x), PyLong_FromLong(xy.y));
99
100 PyObject *cb_args_with_xy = PyC_Tuple_CopySized(cb_args, cb_args_len + 1);
101 PyTuple_SET_ITEM(cb_args_with_xy, cb_args_len, cb_args_xy);
102
103 result = PyObject_CallObject(cb_func, cb_args_with_xy);
104
105 Py_DECREF(cb_args_with_xy);
106
107 if (result) {
108 Py_DECREF(result);
109 }
110 else {
111 PyErr_Print();
112 PyErr_Clear();
113 }
114
115 bpy_context_clear(C, &gilstate);
116}
117
118#if 0
119PyObject *pyrna_callback_add(BPy_StructRNA *self, PyObject *args)
120{
121 void *handle;
122
123 PyObject *cb_func, *cb_args;
124 char *cb_event_str = nullptr;
125 int cb_event;
126
127 if (!PyArg_ParseTuple(
128 args, "OO!|s:bpy_struct.callback_add", &cb_func, &PyTuple_Type, &cb_args, &cb_event_str))
129 {
130 return nullptr;
131 }
132
133 if (!PyCallable_Check(cb_func)) {
134 PyErr_SetString(PyExc_TypeError, "callback_add(): first argument isn't callable");
135 return nullptr;
136 }
137
138 if (RNA_struct_is_a(self->ptr.type, &RNA_Region)) {
139 if (cb_event_str) {
141 region_draw_mode_items, cb_event_str, &cb_event, "bpy_struct.callback_add()") == -1)
142 {
143 return nullptr;
144 }
145 }
146 else {
147 cb_event = REGION_DRAW_POST_PIXEL;
148 }
149
151 ((ARegion *)self->ptr.data)->type, cb_region_draw, (void *)args, cb_event);
152 Py_INCREF(args);
153 }
154 else {
155 PyErr_SetString(PyExc_TypeError, "callback_add(): type does not support callbacks");
156 return nullptr;
157 }
158
159 return PyCapsule_New((void *)handle, rna_capsual_id, nullptr);
160}
161
162PyObject *pyrna_callback_remove(BPy_StructRNA *self, PyObject *args)
163{
164 PyObject *py_handle;
165 void *handle;
166 void *customdata;
167
168 if (!PyArg_ParseTuple(args, "O!:callback_remove", &PyCapsule_Type, &py_handle)) {
169 return nullptr;
170 }
171
172 handle = PyCapsule_GetPointer(py_handle, rna_capsual_id);
173
174 if (handle == nullptr) {
175 PyErr_SetString(PyExc_ValueError,
176 "callback_remove(handle): nullptr handle given, invalid or already removed");
177 return nullptr;
178 }
179
180 if (RNA_struct_is_a(self->ptr.type, &RNA_Region)) {
181 customdata = ED_region_draw_cb_customdata(handle);
182 Py_DECREF((PyObject *)customdata);
183
184 ED_region_draw_cb_exit(((ARegion *)self->ptr.data)->type, handle);
185 }
186 else {
187 PyErr_SetString(PyExc_TypeError, "callback_remove(): type does not support callbacks");
188 return nullptr;
189 }
190
191 /* don't allow reuse */
192 PyCapsule_SetName(py_handle, rna_capsual_id_invalid);
193
194 Py_RETURN_NONE;
195}
196#endif
197
198/* reverse of rna_Space_refine() */
200{
201 if (srna == &RNA_SpaceView3D) {
202 return SPACE_VIEW3D;
203 }
204 if (srna == &RNA_SpaceGraphEditor) {
205 return SPACE_GRAPH;
206 }
207 if (srna == &RNA_SpaceOutliner) {
208 return SPACE_OUTLINER;
209 }
210 if (srna == &RNA_SpaceProperties) {
211 return SPACE_PROPERTIES;
212 }
213 if (srna == &RNA_SpaceFileBrowser) {
214 return SPACE_FILE;
215 }
216 if (srna == &RNA_SpaceImageEditor) {
217 return SPACE_IMAGE;
218 }
219 if (srna == &RNA_SpaceInfo) {
220 return SPACE_INFO;
221 }
222 if (srna == &RNA_SpaceSequenceEditor) {
223 return SPACE_SEQ;
224 }
225 if (srna == &RNA_SpaceTextEditor) {
226 return SPACE_TEXT;
227 }
228 if (srna == &RNA_SpaceDopeSheetEditor) {
229 return SPACE_ACTION;
230 }
231 if (srna == &RNA_SpaceNLA) {
232 return SPACE_NLA;
233 }
234 if (srna == &RNA_SpaceNodeEditor) {
235 return SPACE_NODE;
236 }
237 if (srna == &RNA_SpaceConsole) {
238 return SPACE_CONSOLE;
239 }
240 if (srna == &RNA_SpacePreferences) {
241 return SPACE_USERPREF;
242 }
243 if (srna == &RNA_SpaceClipEditor) {
244 return SPACE_CLIP;
245 }
246 if (srna == &RNA_SpaceSpreadsheet) {
247 return SPACE_SPREADSHEET;
248 }
249 return SPACE_EMPTY;
250}
251
252static void cb_rna_capsule_destructor(PyObject *capsule)
253{
254 PyObject *args = static_cast<PyObject *>(PyCapsule_GetContext(capsule));
255 Py_DECREF(args);
256}
257
258PyObject *pyrna_callback_classmethod_add(PyObject * /*self*/, PyObject *args)
259{
260 void *handle;
261 PyObject *cls;
262 PyObject *cb_func, *cb_args;
263 StructRNA *srna;
264
265 if (PyTuple_GET_SIZE(args) < 2) {
266 PyErr_SetString(PyExc_ValueError, "handler_add(handler): expected at least 2 args");
267 return nullptr;
268 }
269
270 cls = PyTuple_GET_ITEM(args, 0);
271 if (!(srna = pyrna_struct_as_srna(cls, false, "handler_add"))) {
272 return nullptr;
273 }
274 cb_func = PyTuple_GET_ITEM(args, 1);
275 if (!PyCallable_Check(cb_func)) {
276 PyErr_SetString(PyExc_TypeError, "first argument isn't callable");
277 return nullptr;
278 }
279
280 /* class specific callbacks */
281
282 if (srna == &RNA_WindowManager) {
283 struct {
284 BPy_EnumProperty_Parse space_type_enum;
285 BPy_EnumProperty_Parse region_type_enum;
286 } params{};
287 params.space_type_enum.items = rna_enum_space_type_items;
288 params.space_type_enum.value = SPACE_TYPE_ANY;
289 params.region_type_enum.items = rna_enum_region_type_items;
290 params.region_type_enum.value = RGN_TYPE_ANY;
291
292 if (!PyArg_ParseTuple(args,
293 "OOO!|O&O&:WindowManager.draw_cursor_add",
294 &cls,
295 &cb_func, /* already assigned, no matter */
296 &PyTuple_Type,
297 &cb_args,
299 &params.space_type_enum,
301 &params.region_type_enum))
302 {
303 return nullptr;
304 }
305
306 handle = WM_paint_cursor_activate(params.space_type_enum.value,
307 params.region_type_enum.value,
308 nullptr,
310 (void *)args);
311 }
312 else if (RNA_struct_is_a(srna, &RNA_Space)) {
313 struct {
314 BPy_EnumProperty_Parse region_type_enum;
315 BPy_EnumProperty_Parse event_enum;
316 } params{};
317 params.region_type_enum.items = rna_enum_region_type_items;
318 params.event_enum.items = region_draw_mode_items;
319
320 if (!PyArg_ParseTuple(args,
321 "OOO!O&O&:Space.draw_handler_add",
322 &cls,
323 &cb_func, /* already assigned, no matter */
324 &PyTuple_Type,
325 &cb_args,
327 &params.region_type_enum,
329 &params.event_enum))
330 {
331 return nullptr;
332 }
333
334 const eSpace_Type spaceid = rna_Space_refine_reverse(srna);
335 if (spaceid == SPACE_EMPTY) {
336 PyErr_Format(PyExc_TypeError, "unknown space type '%.200s'", RNA_struct_identifier(srna));
337 return nullptr;
338 }
339
340 SpaceType *st = BKE_spacetype_from_id(spaceid);
341 ARegionType *art = BKE_regiontype_from_id(st, params.region_type_enum.value);
342 if (art == nullptr) {
343 PyErr_Format(
344 PyExc_TypeError, "region type %R not in space", params.region_type_enum.value_orig);
345 return nullptr;
346 }
348 art, cb_region_draw, (void *)args, params.event_enum.value);
349 }
350 else {
351 PyErr_SetString(PyExc_TypeError, "callback_add(): type does not support callbacks");
352 return nullptr;
353 }
354
355 /* Keep the 'args' reference as long as the callback exists.
356 * This reference is decremented in #BPY_callback_screen_free and #BPY_callback_wm_free. */
357 Py_INCREF(args);
358
359 PyObject *ret = PyCapsule_New(handle, rna_capsual_id, nullptr);
360
361 /* Store 'args' in context as well for simple access. */
362 PyCapsule_SetDestructor(ret, cb_rna_capsule_destructor);
363 PyCapsule_SetContext(ret, args);
364 Py_INCREF(args);
365
366 return ret;
367}
368
369PyObject *pyrna_callback_classmethod_remove(PyObject * /*self*/, PyObject *args)
370{
371 PyObject *cls;
372 PyObject *py_handle;
373 void *handle;
374 StructRNA *srna;
375 bool capsule_clear = false;
376 bool handle_removed = false;
377
378 if (PyTuple_GET_SIZE(args) < 2) {
379 PyErr_SetString(PyExc_ValueError, "callback_remove(handler): expected at least 2 args");
380 return nullptr;
381 }
382
383 cls = PyTuple_GET_ITEM(args, 0);
384 if (!(srna = pyrna_struct_as_srna(cls, false, "callback_remove"))) {
385 return nullptr;
386 }
387 py_handle = PyTuple_GET_ITEM(args, 1);
388 handle = PyCapsule_GetPointer(py_handle, rna_capsual_id);
389 if (handle == nullptr) {
390 PyErr_SetString(PyExc_ValueError,
391 "callback_remove(handler): nullptr handler given, invalid or already removed");
392 return nullptr;
393 }
394
395 if (srna == &RNA_WindowManager) {
396 if (!PyArg_ParseTuple(
397 args, "OO!:WindowManager.draw_cursor_remove", &cls, &PyCapsule_Type, &py_handle))
398 {
399 return nullptr;
400 }
401 handle_removed = WM_paint_cursor_end(static_cast<wmPaintCursor *>(handle));
402 capsule_clear = true;
403 }
404 else if (RNA_struct_is_a(srna, &RNA_Space)) {
405 const char *error_prefix = "Space.draw_handler_remove";
406 struct {
407 BPy_EnumProperty_Parse region_type_enum;
408 } params{};
409 params.region_type_enum.items = rna_enum_region_type_items;
410
411 if (!PyArg_ParseTuple(args,
412 "OO!O&:Space.draw_handler_remove",
413 &cls,
414 &PyCapsule_Type,
415 &py_handle, /* already assigned, no matter */
417 &params.region_type_enum))
418 {
419 return nullptr;
420 }
421
422 const eSpace_Type spaceid = rna_Space_refine_reverse(srna);
423 if (spaceid == SPACE_EMPTY) {
424 PyErr_Format(PyExc_TypeError,
425 "%s: unknown space type '%.200s'",
426 error_prefix,
428 return nullptr;
429 }
430
431 SpaceType *st = BKE_spacetype_from_id(spaceid);
432 ARegionType *art = BKE_regiontype_from_id(st, params.region_type_enum.value);
433 if (art == nullptr) {
434 PyErr_Format(PyExc_TypeError,
435 "%s: region type %R not in space",
436 error_prefix,
437 params.region_type_enum.value_orig);
438 return nullptr;
439 }
440 handle_removed = ED_region_draw_cb_exit(art, handle);
441 capsule_clear = true;
442 }
443 else {
444 PyErr_SetString(PyExc_TypeError, "callback_remove(): type does not support callbacks");
445 return nullptr;
446 }
447
448 /* When `handle_removed == false`: Blender has already freed the data
449 * (freeing screen data when loading a new file for example).
450 * This will have already decremented the user, so don't decrement twice. */
451 if (handle_removed == true) {
452 /* The handle has been removed, so decrement its custom-data. */
453 PyObject *handle_args = static_cast<PyObject *>(PyCapsule_GetContext(py_handle));
454 Py_DECREF(handle_args);
455 }
456
457 /* don't allow reuse */
458 if (capsule_clear) {
459 PyCapsule_Destructor destructor_fn = PyCapsule_GetDestructor(py_handle);
460 if (destructor_fn) {
461 destructor_fn(py_handle);
462 PyCapsule_SetDestructor(py_handle, nullptr);
463 }
464 PyCapsule_SetName(py_handle, rna_capsual_id_invalid);
465 }
466
467 Py_RETURN_NONE;
468}
469
470/* -------------------------------------------------------------------- */
473
474static void cb_customdata_free(void *customdata)
475{
476 PyGILState_STATE gilstate = PyGILState_Ensure();
477
478 PyObject *tuple = static_cast<PyObject *>(customdata);
479 Py_DECREF(tuple);
480
481 PyGILState_Release(gilstate);
482}
483
485{
487 art, reinterpret_cast<void *>(cb_region_draw), cb_customdata_free);
488}
489
495
SpaceType * BKE_spacetype_from_id(int spaceid)
Definition screen.cc:251
ARegionType * BKE_regiontype_from_id(const SpaceType *st, int regionid)
Definition screen.cc:261
#define BLI_assert(a)
Definition BLI_assert.h:46
#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
#define C
Definition RandGen.cpp:29
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:8847
static eSpace_Type rna_Space_refine_reverse(StructRNA *srna)
static PyObject * PyC_Tuple_CopySized(PyObject *src, int len_dst)
static void cb_wm_cursor_draw(bContext *C, const blender::int2 &xy, const blender::float2 &, void *customdata)
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)
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]
VecBase< int32_t, 2 > int2
VecBase< float, 2 > float2
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:21
const EnumPropertyItem rna_enum_space_type_items[]
Definition rna_space.cc:72
i
Definition text_draw.cc:230
int xy[2]
Definition wm_draw.cc:174
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)