Blender V4.3
sequencer_clipboard.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2001-2002 NaN Holding BV. All rights reserved.
2 * SPDX-FileCopyrightText: 2003-2009 Blender Authors
3 * SPDX-FileCopyrightText: 2005-2006 Peter Schlaile <peter [at] schlaile [dot] de>
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later */
6
11#include <cstring>
12
13#include "BLO_readfile.hh"
14#include "BLO_writefile.hh"
15#include "MEM_guardedalloc.h"
16
17#include "ED_outliner.hh"
18#include "ED_sequencer.hh"
19
20#include "DNA_anim_types.h"
21#include "DNA_scene_types.h"
22#include "DNA_sequence_types.h"
23#include "DNA_space_types.h"
25
26#include "BLI_ghash.h"
27#include "BLI_listbase.h"
28#include "BLI_path_utils.hh"
29
30#include "BKE_anim_data.hh"
31#include "BKE_appdir.hh"
33#include "BKE_blendfile.hh"
34#include "BKE_context.hh"
35#include "BKE_fcurve.hh"
36#include "BKE_lib_id.hh"
37#include "BKE_lib_query.hh"
38#include "BKE_lib_remap.hh"
39#include "BKE_main.hh"
40#include "BKE_report.hh"
41#include "BKE_scene.hh"
42
43#include "SEQ_animation.hh"
44#include "SEQ_select.hh"
45#include "SEQ_sequencer.hh"
46#include "SEQ_time.hh"
47#include "SEQ_transform.hh"
48#include "SEQ_utils.hh"
49
50#include "DEG_depsgraph.hh"
52
53#include "ANIM_animdata.hh"
54
55#include "WM_api.hh"
56#include "WM_types.hh"
57
58#ifdef WITH_AUDASPACE
59# include <AUD_Special.h>
60#endif
61
62/* Own include. */
63#include "sequencer_intern.hh"
64
65using namespace blender::bke::blendfile;
66
67/* -------------------------------------------------------------------- */
68/* Copy Operator Helper functions
69 */
70
72 Sequence *seq_dst,
73 ListBase *clipboard_dst,
74 ListBase *fcurve_base_src)
75{
76 /* Add curves for strips inside meta strip. */
77 if (seq_dst->type == SEQ_TYPE_META) {
78 LISTBASE_FOREACH (Sequence *, meta_child, &seq_dst->seqbase) {
79 sequencer_copy_animation_listbase(scene_src, meta_child, clipboard_dst, fcurve_base_src);
80 }
81 }
82
83 GSet *fcurves_src = SEQ_fcurves_by_strip_get(seq_dst, fcurve_base_src);
84 if (fcurves_src == nullptr) {
85 return;
86 }
87
88 GSET_FOREACH_BEGIN (FCurve *, fcu_src, fcurves_src) {
89 BLI_addtail(clipboard_dst, BKE_fcurve_copy(fcu_src));
90 }
92
93 BLI_gset_free(fcurves_src, nullptr);
94}
95
96static void sequencer_copy_animation(Scene *scene_src,
97 ListBase *fcurves_dst,
98 ListBase *drivers_dst,
99 Sequence *seq_dst)
100{
101 if (SEQ_animation_curves_exist(scene_src)) {
103 scene_src, seq_dst, fcurves_dst, &scene_src->adt->action->curves);
104 }
105 if (SEQ_animation_drivers_exist(scene_src)) {
106 sequencer_copy_animation_listbase(scene_src, seq_dst, drivers_dst, &scene_src->adt->drivers);
107 }
108}
109
110static void sequencer_copybuffer_filepath_get(char filepath[FILE_MAX], size_t filepath_maxncpy)
111{
112 BLI_path_join(filepath, filepath_maxncpy, BKE_tempdir_base(), "copybuffer_vse.blend");
113}
114
116 Scene *scene_src,
117 const char *filepath,
118 ReportList &reports)
119
120{
121 /* NOTE: Setting the same current file path as G_MAIN is necessary for now to get correct
122 * external filepaths when writing the partial write context on disk. otherwise, filepaths from
123 * the scene's sequencer strips (e.g. image ones) would also need to be remapped in this code. */
124 PartialWriteContext copy_buffer{bmain_src->filepath};
125 const char *scene_name = "copybuffer_vse_scene";
126
127 /* Add a dummy empty scene to the temporary Main copy buffer. */
128 Scene *scene_dst = reinterpret_cast<Scene *>(
129 copy_buffer.id_create(ID_SCE,
130 scene_name,
131 nullptr,
133 PartialWriteContext::IDAddOperations::SET_FAKE_USER |
134 PartialWriteContext::IDAddOperations::SET_CLIPBOARD_MARK)}));
135
136 /* Create an empty sequence editor data to store all copied strips. */
137 scene_dst->ed = MEM_cnew<Editing>(__func__);
138 scene_dst->ed->seqbasep = &scene_dst->ed->seqbase;
140 scene_src, scene_dst, &scene_dst->ed->seqbase, scene_src->ed->seqbasep, 0, 0);
141
142 BLI_duplicatelist(&scene_dst->ed->channels, &scene_src->ed->channels);
143 scene_dst->ed->displayed_channels = &scene_dst->ed->channels;
144
145 /* Save current frame and active strip. */
146 scene_dst->r.cfra = scene_src->r.cfra;
147 Sequence *active_seq_src = SEQ_select_active_get(scene_src);
148 if (active_seq_src) {
149 Sequence *seq_dst = static_cast<Sequence *>(
150 BLI_findstring(&scene_dst->ed->seqbase, active_seq_src->name, offsetof(Sequence, name)));
151 if (seq_dst) {
152 SEQ_select_active_set(scene_dst, seq_dst);
153 }
154 }
155
156 ListBase fcurves_dst = {nullptr, nullptr};
157 ListBase drivers_dst = {nullptr, nullptr};
158 LISTBASE_FOREACH (Sequence *, seq_dst, &scene_dst->ed->seqbase) {
159 /* Copy animation curves from seq_dst (if any). */
160 sequencer_copy_animation(scene_src, &fcurves_dst, &drivers_dst, seq_dst);
161 }
162
163 if (!BLI_listbase_is_empty(&fcurves_dst) || !BLI_listbase_is_empty(&drivers_dst)) {
164 BLI_assert(scene_dst->adt == nullptr);
165 scene_dst->adt = BKE_animdata_ensure_id(&scene_dst->id);
166 scene_dst->adt->action = reinterpret_cast<bAction *>(copy_buffer.id_create(
167 ID_AC, scene_name, nullptr, {PartialWriteContext::IDAddOperations::SET_FAKE_USER}));
168 BLI_movelisttolist(&scene_dst->adt->action->curves, &fcurves_dst);
169 BLI_movelisttolist(&scene_dst->adt->drivers, &drivers_dst);
170 }
171
172 /* Only add to the paste buffer some dependency ID types. For example, scenes are ignored/cleared
173 * (how to copy and paste scene strips is not clear currently).
174 */
175 /* NOTE: since a special Scene root ID needs to be forged for the VSE copy/paste (instead of
176 * directly using the current scene and adding it to the paste buffer), the first level of
177 * dependencies (IDs directly used by the scene) need to be processed manually here.
178 *
179 * All other indirect dependencies will then be handled automatically by the partial write
180 * context code.
181 */
182#define VSE_COPYBUFFER_IDTYPES ID_SO, ID_MC, ID_IM, ID_TXT, ID_VF, ID_AC
183 auto add_scene_ids_dependencies_cb = [&copy_buffer,
184 scene_dst](LibraryIDLinkCallbackData *cb_data) -> int {
185 ID *id_src = *cb_data->id_pointer;
186
187 /* Embedded or null IDs usages can be ignored here. */
188 if (cb_data->cb_flag & (IDWALK_CB_EMBEDDED | IDWALK_CB_EMBEDDED_NOT_OWNING)) {
189 return IDWALK_RET_NOP;
190 }
191 if (!id_src) {
192 return IDWALK_RET_NOP;
193 }
194
195 /* The Action ID of the destination scene has already been added (created actually) in the copy
196 * buffer. This is necessary to ensure that only the relevant sequencer-related animation data
197 * is copied into the destination paste buffer, and not the whole scene's animation. See the
198 * code around the call to #sequencer_copy_animation above.
199 *
200 * So trying to add it again here would lead to serious issues. */
201 if (scene_dst->adt && scene_dst->adt->action == reinterpret_cast<bAction *>(id_src)) {
202 BLI_assert(GS(id_src->name) == ID_AC);
203 return IDWALK_RET_NOP;
204 }
205
206 ID *id_dst = nullptr;
207 const ID_Type id_type = GS((id_src)->name);
208 /* Only add (and follow) IDs which usage is marked as 'never null', or are from following
209 * types: #bSound, #MovieClip, #Image, #Text, #VFont, #bAction. */
210 if (ELEM(id_type, VSE_COPYBUFFER_IDTYPES) || (cb_data->cb_flag & IDWALK_CB_NEVER_NULL)) {
211 /* The partial write context handle dependencies of ID added to it. This callback will tell
212 * it whether a given dependency ID should be skipped/cleared, or also added in the context.
213 */
214 auto partial_write_dependencies_filter_cb = [](LibraryIDLinkCallbackData *cb_deps_data,
217 ID *id_deps_src = *cb_deps_data->id_pointer;
218 const ID_Type id_type = GS((id_deps_src)->name);
219 if (ELEM(id_type, VSE_COPYBUFFER_IDTYPES) ||
220 (cb_deps_data->cb_flag & IDWALK_CB_NEVER_NULL))
221 {
222 return PartialWriteContext::IDAddOperations::ADD_DEPENDENCIES;
223 }
224 return PartialWriteContext::IDAddOperations::CLEAR_DEPENDENCIES;
225 };
226 id_dst = copy_buffer.id_add(id_src,
227 {PartialWriteContext::IDAddOperations::NOP},
228 partial_write_dependencies_filter_cb);
229 }
230 *cb_data->id_pointer = id_dst;
231 return IDWALK_RET_NOP;
232 };
234 nullptr, &scene_dst->id, add_scene_ids_dependencies_cb, nullptr, IDWALK_NOP);
235#undef VSE_COPYBUFFER_IDTYPES
236
237 BLI_assert(copy_buffer.is_valid());
238
239 const bool retval = copy_buffer.write(filepath, reports);
240
241 return retval;
242}
243
245{
246 Main *bmain = CTX_data_main(C);
247 Scene *scene = CTX_data_scene(C);
248 Editing *ed = SEQ_editing_get(scene);
249
251 BKE_report(op->reports, RPT_ERROR, "Please select all related strips");
252 return OPERATOR_CANCELLED;
253 }
254
255 char filepath[FILE_MAX];
256 sequencer_copybuffer_filepath_get(filepath, sizeof(filepath));
257 bool success = sequencer_write_copy_paste_file(bmain, scene, filepath, *op->reports);
258 if (!success) {
259 BKE_report(op->reports, RPT_ERROR, "Could not create the copy paste file!");
260 return OPERATOR_CANCELLED;
261 }
262
263 /* We are all done! */
265 op->reports, RPT_INFO, "Copied the selected Video Sequencer strips to internal clipboard");
266 return OPERATOR_FINISHED;
267}
268
269/* -------------------------------------------------------------------- */
270/* Paste Operator Helper functions
271 */
272
273static bool sequencer_paste_animation(Main *bmain_dst, Scene *scene_dst, Scene *scene_src)
274{
275 if (!SEQ_animation_curves_exist(scene_src) && !SEQ_animation_drivers_exist(scene_src)) {
276 return false;
277 }
278
279 bAction *act_dst;
280
281 if (scene_dst->adt != nullptr && scene_dst->adt->action != nullptr) {
282 act_dst = scene_dst->adt->action;
283 }
284 else {
285 /* get action to add F-Curve+keyframe to */
286 act_dst = blender::animrig::id_action_ensure(bmain_dst, &scene_dst->id);
287 }
288
289 LISTBASE_FOREACH (FCurve *, fcu, &scene_src->adt->action->curves) {
290 BLI_addtail(&act_dst->curves, BKE_fcurve_copy(fcu));
291 }
292 LISTBASE_FOREACH (FCurve *, fcu, &scene_src->adt->drivers) {
293 BLI_addtail(&scene_dst->adt->drivers, BKE_fcurve_copy(fcu));
294 }
295
296 return true;
297}
298
300{
301 char filepath[FILE_MAX];
302 sequencer_copybuffer_filepath_get(filepath, sizeof(filepath));
304 BlendFileReadReport bf_reports{};
305 BlendFileData *bfd = BKE_blendfile_read(filepath, &params, &bf_reports);
306
307 if (bfd == nullptr) {
308 BKE_report(op->reports, RPT_INFO, "No data to paste");
309 return OPERATOR_CANCELLED;
310 }
311
312 Main *bmain_src = bfd->main;
313 bfd->main = nullptr;
315
316 Scene *scene_src = nullptr;
317 /* Find the scene we pasted that contains the strips. It should be tagged. */
318 LISTBASE_FOREACH (Scene *, scene_iter, &bmain_src->scenes) {
319 if (scene_iter->id.flag & ID_FLAG_CLIPBOARD_MARK) {
320 scene_src = scene_iter;
321 break;
322 }
323 }
324
325 if (!scene_src || !scene_src->ed) {
326 BKE_report(op->reports, RPT_ERROR, "No clipboard scene to paste Video Sequencer data from");
327 BKE_main_free(bmain_src);
328 return OPERATOR_CANCELLED;
329 }
330
331 const int num_strips_to_paste = BLI_listbase_count(&scene_src->ed->seqbase);
332 if (num_strips_to_paste == 0) {
333 BKE_report(op->reports, RPT_INFO, "No strips to paste");
334 BKE_main_free(bmain_src);
335 return OPERATOR_CANCELLED;
336 }
337
338 Scene *scene_dst = CTX_data_scene(C);
339 Editing *ed_dst = SEQ_editing_ensure(scene_dst); /* Creates "ed" if it's missing. */
340 int ofs;
341
342 ED_sequencer_deselect_all(scene_dst);
343 if (RNA_boolean_get(op->ptr, "keep_offset")) {
344 ofs = scene_dst->r.cfra - scene_src->r.cfra;
345 }
346 else {
347 int min_seq_startdisp = INT_MAX;
348 LISTBASE_FOREACH (Sequence *, seq, &scene_src->ed->seqbase) {
349 if (SEQ_time_left_handle_frame_get(scene_src, seq) < min_seq_startdisp) {
350 min_seq_startdisp = SEQ_time_left_handle_frame_get(scene_src, seq);
351 }
352 }
353 /* Paste strips relative to the current-frame. */
354 ofs = scene_dst->r.cfra - min_seq_startdisp;
355 }
356
357 Sequence *prev_active_seq = SEQ_select_active_get(scene_src);
358 std::string active_seq_name;
359 if (prev_active_seq) {
360 active_seq_name.assign(prev_active_seq->name);
361 }
362
363 /* Make sure we have all data IDs we need in bmain_dst. Remap the IDs if we already have them.
364 * This has to happen BEFORE we move the strip over to scene_dst. their ID mapping will not be
365 * correct otherwise. */
366 Main *bmain_dst = CTX_data_main(C);
367 MainMergeReport merge_reports = {};
368 /* NOTE: BKE_main_merge will free bmain_src! */
369 BKE_main_merge(bmain_dst, &bmain_src, merge_reports);
370
371 /* Paste animation.
372 * NOTE: Only fcurves and drivers are copied. NLA action strips are not copied.
373 * First backup original curves from scene and move curves from clipboard into scene. This way,
374 * when pasted strips are renamed, pasted fcurves are renamed with them. Finally restore original
375 * curves from backup.
376 */
377 SeqAnimationBackup animation_backup = {{nullptr}};
378 SEQ_animation_backup_original(scene_dst, &animation_backup);
379 bool has_animation = sequencer_paste_animation(bmain_dst, scene_dst, scene_src);
380
381 ListBase nseqbase = {nullptr, nullptr};
382 /* NOTE: SEQ_sequence_base_dupli_recursive() takes care of generating
383 * new UIDs for sequences in the new list. */
385 scene_src, scene_dst, &nseqbase, &scene_src->ed->seqbase, 0, 0);
386
387 /* BKE_main_merge will copy the scene_src and its action into bmain_dst. Remove them as
388 * we merge the data from these manually.
389 */
390 if (has_animation) {
391 BKE_id_delete(bmain_dst, scene_src->adt->action);
392 }
393 BKE_id_delete(bmain_dst, scene_src);
394
395 Sequence *iseq_first = static_cast<Sequence *>(nseqbase.first);
396 BLI_movelisttolist(ed_dst->seqbasep, &nseqbase);
397 /* Restore "first" pointer as BLI_movelisttolist sets it to nullptr */
398 nseqbase.first = iseq_first;
399
400 LISTBASE_FOREACH (Sequence *, iseq, &nseqbase) {
401 if (iseq->name == active_seq_name) {
402 SEQ_select_active_set(scene_dst, iseq);
403 }
404 /* Make sure, that pasted strips have unique names. This has to be done after
405 * adding strips to seqbase, for lookup cache to work correctly. */
406 SEQ_ensure_unique_name(iseq, scene_dst);
407 }
408
409 LISTBASE_FOREACH (Sequence *, iseq, &nseqbase) {
410 /* Translate after name has been changed, otherwise this will affect animdata of original
411 * strip. */
412 SEQ_transform_translate_sequence(scene_dst, iseq, ofs);
413 /* Ensure, that pasted strips don't overlap. */
414 if (SEQ_transform_test_overlap(scene_dst, ed_dst->seqbasep, iseq)) {
415 SEQ_transform_seqbase_shuffle(ed_dst->seqbasep, iseq, scene_dst);
416 }
417 }
418
419 SEQ_animation_restore_original(scene_dst, &animation_backup);
420
422 DEG_relations_tag_update(bmain_dst);
425
426 BKE_reportf(op->reports, RPT_INFO, "%d strips pasted", num_strips_to_paste);
427
428 return OPERATOR_FINISHED;
429}
Functions to work with AnimData.
AnimData * BKE_animdata_ensure_id(ID *id)
Definition anim_data.cc:103
const char * BKE_tempdir_base() ATTR_WARN_UNUSED_RESULT ATTR_RETURNS_NONNULL
Definition appdir.cc:1211
BlendFileData * BKE_blendfile_read(const char *filepath, const BlendFileReadParams *params, BlendFileReadReport *reports)
Scene * CTX_data_scene(const bContext *C)
Main * CTX_data_main(const bContext *C)
FCurve * BKE_fcurve_copy(const FCurve *fcu)
void BKE_id_delete(Main *bmain, void *idv) ATTR_NONNULL()
@ IDWALK_RET_NOP
@ IDWALK_CB_EMBEDDED_NOT_OWNING
@ IDWALK_CB_EMBEDDED
@ IDWALK_CB_NEVER_NULL
void BKE_library_foreach_ID_link(Main *bmain, ID *id, blender::FunctionRef< LibraryIDLinkCallback > callback, void *user_data, int flag)
Definition lib_query.cc:416
@ IDWALK_NOP
void BKE_main_merge(Main *bmain_dst, Main **r_bmain_src, MainMergeReport &reports)
Definition main.cc:321
void BKE_main_free(Main *bmain)
Definition main.cc:175
void BKE_reportf(ReportList *reports, eReportType type, const char *format,...) ATTR_PRINTF_FORMAT(3
void BKE_report(ReportList *reports, eReportType type, const char *message)
Definition report.cc:125
#define BLI_assert(a)
Definition BLI_assert.h:50
struct GSet GSet
Definition BLI_ghash.h:341
#define GSET_FOREACH_END()
Definition BLI_ghash.h:541
#define GSET_FOREACH_BEGIN(type, var, what)
Definition BLI_ghash.h:535
void BLI_gset_free(GSet *gs, GSetKeyFreeFP keyfreefp)
Definition BLI_ghash.c:1034
BLI_INLINE bool BLI_listbase_is_empty(const struct ListBase *lb)
void * BLI_findstring(const struct ListBase *listbase, const char *id, int offset) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1)
#define LISTBASE_FOREACH(type, var, list)
void void void void void void BLI_duplicatelist(struct ListBase *dst, const struct ListBase *src) ATTR_NONNULL(1
void void void BLI_movelisttolist(struct ListBase *dst, struct ListBase *src) ATTR_NONNULL(1
void BLI_addtail(struct ListBase *listbase, void *vlink) ATTR_NONNULL(1)
Definition listbase.cc:110
int BLI_listbase_count(const struct ListBase *listbase) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1)
#define FILE_MAX
#define BLI_path_join(...)
#define ELEM(...)
external readfile function prototypes.
void BLO_blendfiledata_free(BlendFileData *bfd)
external writefile.cc function prototypes.
void DEG_id_tag_update(ID *id, unsigned int flags)
void DEG_relations_tag_update(Main *bmain)
@ ID_RECALC_SEQUENCER_STRIPS
Definition DNA_ID.h:1089
@ ID_FLAG_CLIPBOARD_MARK
Definition DNA_ID.h:750
ID_Type
@ ID_SCE
@ ID_AC
@ SEQ_TYPE_META
void ED_outliner_select_sync_from_sequence_tag(bContext *C)
bool ED_sequencer_deselect_all(Scene *scene)
Read Guarded memory(de)allocation.
#define ND_SEQUENCER
Definition WM_types.hh:404
#define NC_SCENE
Definition WM_types.hh:345
void SEQ_animation_backup_original(Scene *scene, SeqAnimationBackup *backup)
Definition animation.cc:120
bool SEQ_animation_drivers_exist(Scene *scene)
Definition animation.cc:31
bool SEQ_animation_curves_exist(Scene *scene)
Definition animation.cc:25
GSet * SEQ_fcurves_by_strip_get(const Sequence *seq, ListBase *fcurve_base)
Definition animation.cc:48
void SEQ_animation_restore_original(Scene *scene, SeqAnimationBackup *backup)
Definition animation.cc:130
#define offsetof(t, d)
uiWidgetBaseParameters params[MAX_WIDGET_BASE_BATCH]
#define GS(x)
Definition iris.cc:202
bAction * id_action_ensure(Main *bmain, ID *id)
Definition animdata.cc:195
bool RNA_boolean_get(PointerRNA *ptr, const char *name)
void SEQ_ensure_unique_name(Sequence *seq, Scene *scene)
void SEQ_sequence_base_dupli_recursive(const Scene *scene_src, Scene *scene_dst, ListBase *nseqbase, const ListBase *seqbase, int dupe_flag, const int flag)
Definition sequencer.cc:653
Editing * SEQ_editing_ensure(Scene *scene)
Definition sequencer.cc:267
Editing * SEQ_editing_get(const Scene *scene)
Definition sequencer.cc:262
#define VSE_COPYBUFFER_IDTYPES
static bool sequencer_paste_animation(Main *bmain_dst, Scene *scene_dst, Scene *scene_src)
static bool sequencer_write_copy_paste_file(Main *bmain_src, Scene *scene_src, const char *filepath, ReportList &reports)
static void sequencer_copy_animation(Scene *scene_src, ListBase *fcurves_dst, ListBase *drivers_dst, Sequence *seq_dst)
int sequencer_clipboard_paste_exec(bContext *C, wmOperator *op)
static void sequencer_copy_animation_listbase(Scene *scene_src, Sequence *seq_dst, ListBase *clipboard_dst, ListBase *fcurve_base_src)
int sequencer_clipboard_copy_exec(bContext *C, wmOperator *op)
static void sequencer_copybuffer_filepath_get(char filepath[FILE_MAX], size_t filepath_maxncpy)
void SEQ_select_active_set(Scene *scene, Sequence *seq)
Sequence * SEQ_select_active_get(const Scene *scene)
int SEQ_time_left_handle_frame_get(const Scene *, const Sequence *seq)
bool SEQ_transform_seqbase_isolated_sel_check(ListBase *seqbase)
bool SEQ_transform_seqbase_shuffle(ListBase *seqbasep, Sequence *test, Scene *evil_scene)
bool SEQ_transform_test_overlap(const Scene *scene, ListBase *seqbasep, Sequence *test)
void SEQ_transform_translate_sequence(Scene *evil_scene, Sequence *seq, int delta)
bAction * action
ListBase drivers
ListBase seqbase
ListBase * seqbasep
ListBase channels
ListBase * displayed_channels
Definition DNA_ID.h:413
char name[66]
Definition DNA_ID.h:425
void * first
ListBase scenes
Definition BKE_main.hh:210
char filepath[1024]
Definition BKE_main.hh:136
struct Editing * ed
struct RenderData r
struct AnimData * adt
ListBase curves
struct ReportList * reports
struct PointerRNA * ptr
void WM_event_add_notifier(const bContext *C, uint type, void *reference)