1use crate::animation::{AnimationFrameData, AnimationFrameResult};
2use crate::config::Configuration;
3use crate::host::*;
4use crate::input::{KeyboardEvent, PointerEvent};
5use crate::output::*;
6use crate::placement::Placement;
7use crate::window::{PluginWindow, WindowInfo};
8use crate::workspace::*;
9
10unsafe extern "C" {
11 fn miracle_get_plugin_handle() -> u32;
12}
13
14pub fn get_userdata_json() -> Option<String> {
19 let handle = unsafe { miracle_get_plugin_handle() };
20 let mut buf = vec![0u8; 4096];
21 loop {
22 let result = unsafe {
23 crate::host::miracle_get_plugin_userdata(
24 handle,
25 buf.as_mut_ptr() as i32,
26 buf.len() as i32,
27 )
28 };
29 if result == 0 {
30 return None;
31 } else if result == -1 {
32 buf.resize(buf.len() * 2, 0);
33 } else {
34 return std::str::from_utf8(&buf[..result as usize])
35 .ok()
36 .map(|s| s.to_owned());
37 }
38 }
39}
40
41pub trait Plugin {
55 fn window_open_animation(
59 &mut self,
60 _data: &AnimationFrameData,
61 _window: &WindowInfo,
62 ) -> Option<AnimationFrameResult> {
63 None
64 }
65
66 fn window_close_animation(
70 &mut self,
71 _data: &AnimationFrameData,
72 _window: &WindowInfo,
73 ) -> Option<AnimationFrameResult> {
74 None
75 }
76
77 fn window_move_animation(
81 &mut self,
82 _data: &AnimationFrameData,
83 _window: &WindowInfo,
84 ) -> Option<AnimationFrameResult> {
85 None
86 }
87
88 fn workspace_switch_animation(
92 &mut self,
93 _data: &AnimationFrameData,
94 _workspace: &Workspace,
95 ) -> Option<AnimationFrameResult> {
96 None
97 }
98
99 fn place_new_window(&mut self, _info: &WindowInfo) -> Option<Placement> {
104 None
105 }
106
107 fn window_deleted(&mut self, _info: &WindowInfo) {}
112
113 fn window_focused(&mut self, _info: &WindowInfo) {}
115
116 fn window_unfocused(&mut self, _info: &WindowInfo) {}
118
119 fn workspace_created(&mut self, _workspace: &Workspace) {}
121
122 fn workspace_removed(&mut self, _workspace: &Workspace) {}
124
125 fn workspace_focused(&mut self, _previous_id: Option<u64>, _current: &Workspace) {}
129
130 fn workspace_area_changed(&mut self, _workspace: &Workspace) {}
132
133 fn window_workspace_changed(&mut self, _info: &WindowInfo, _workspace: &Workspace) {}
138
139 fn configure(&mut self) -> Option<Configuration> {
149 None
150 }
151
152 fn handle_keyboard_input(&mut self, _event: KeyboardEvent) -> bool {
158 false
159 }
160
161 fn handle_pointer_event(&mut self, _event: PointerEvent) -> bool {
167 false
168 }
169}
170
171pub fn managed_windows() -> Vec<PluginWindow> {
181 let handle = unsafe { miracle_get_plugin_handle() };
182 let count = unsafe { miracle_num_managed_windows(handle) };
183
184 (0..count)
185 .filter_map(|i| {
186 const NAME_BUF_LEN: usize = 256;
187 let mut window_info =
188 std::mem::MaybeUninit::<crate::bindings::miracle_window_info_t>::uninit();
189 let mut name_buf: [u8; NAME_BUF_LEN] = [0; NAME_BUF_LEN];
190
191 unsafe {
192 let result = miracle_get_managed_window_at(
193 handle,
194 i,
195 window_info.as_mut_ptr() as i32,
196 name_buf.as_mut_ptr() as i32,
197 NAME_BUF_LEN as i32,
198 );
199
200 if result != 0 {
201 return None;
202 }
203
204 let window_info = window_info.assume_init();
205 let name_len = name_buf
206 .iter()
207 .position(|&c| c == 0)
208 .unwrap_or(NAME_BUF_LEN);
209 let name = String::from_utf8_lossy(&name_buf[..name_len]).into_owned();
210
211 Some(PluginWindow::from_window_info(
212 WindowInfo::from_c_with_name(&window_info, name),
213 ))
214 }
215 })
216 .collect()
217}
218
219pub fn num_outputs() -> u32 {
221 unsafe { miracle_num_outputs() }
222}
223
224pub fn get_output_at(index: u32) -> Option<Output> {
228 if index >= num_outputs() {
229 return None;
230 }
231
232 const NAME_BUF_LEN: usize = 256;
233 let mut output = std::mem::MaybeUninit::<crate::bindings::miracle_output_t>::uninit();
234 let mut name_buf: [u8; NAME_BUF_LEN] = [0; NAME_BUF_LEN];
235
236 unsafe {
237 let result = miracle_get_output_at(
238 index,
239 output.as_mut_ptr() as i32,
240 name_buf.as_mut_ptr() as i32,
241 NAME_BUF_LEN as i32,
242 );
243
244 if result != 0 {
245 return None;
246 }
247
248 let output = output.assume_init();
249
250 let name_len = name_buf
252 .iter()
253 .position(|&c| c == 0)
254 .unwrap_or(NAME_BUF_LEN);
255 let name = String::from_utf8_lossy(&name_buf[..name_len]).into_owned();
256
257 Some(Output::from_c_with_name(&output, name))
258 }
259}
260
261pub fn get_outputs() -> Vec<Output> {
263 let count = num_outputs();
264 (0..count).filter_map(get_output_at).collect()
265}
266
267pub fn get_active_workspace() -> Option<Workspace> {
271 const NAME_BUF_LEN: usize = 256;
272 let mut workspace = std::mem::MaybeUninit::<crate::bindings::miracle_workspace_t>::uninit();
273 let mut name_buf: [u8; NAME_BUF_LEN] = [0; NAME_BUF_LEN];
274
275 unsafe {
276 let result = miracle_get_active_workspace(
277 workspace.as_mut_ptr() as i32,
278 name_buf.as_mut_ptr() as i32,
279 NAME_BUF_LEN as i32,
280 );
281
282 if result != 0 {
283 return None;
284 }
285
286 let workspace = workspace.assume_init();
287 if workspace.is_set == 0 {
288 return None;
289 }
290
291 let name_len = name_buf
292 .iter()
293 .position(|&c| c == 0)
294 .unwrap_or(NAME_BUF_LEN);
295 let name = String::from_utf8_lossy(&name_buf[..name_len]).into_owned();
296
297 Some(Workspace::from_c_with_name(&workspace, name))
298 }
299}
300
301pub fn request_workspace(
310 number: Option<u32>,
311 name: Option<&str>,
312 focus: bool,
313) -> Option<Workspace> {
314 const NAME_BUF_LEN: usize = 256;
315 let mut workspace = std::mem::MaybeUninit::<crate::bindings::miracle_workspace_t>::uninit();
316 let mut out_name_buf: [u8; NAME_BUF_LEN] = [0; NAME_BUF_LEN];
317
318 let has_number: i32 = if number.is_some() { 1 } else { 0 };
319 let number_val: i32 = number.unwrap_or(0) as i32;
320
321 let name_ptr: i32 = match name {
322 Some(s) => s.as_ptr() as i32,
323 None => 0,
324 };
325 let name_len: i32 = match name {
326 Some(s) => s.len() as i32,
327 None => 0,
328 };
329
330 unsafe {
331 let result = miracle_request_workspace(
332 has_number,
333 number_val,
334 name_ptr,
335 name_len,
336 workspace.as_mut_ptr() as i32,
337 out_name_buf.as_mut_ptr() as i32,
338 NAME_BUF_LEN as i32,
339 if focus { 1 } else { 0 },
340 );
341
342 if result != 0 {
343 return None;
344 }
345
346 let workspace = workspace.assume_init();
347 if workspace.is_set == 0 {
348 return None;
349 }
350
351 let out_name_len = out_name_buf
352 .iter()
353 .position(|&c| c == 0)
354 .unwrap_or(NAME_BUF_LEN);
355 let ws_name = String::from_utf8_lossy(&out_name_buf[..out_name_len]).into_owned();
356
357 Some(Workspace::from_c_with_name(&workspace, ws_name))
358 }
359}
360
361pub fn queue_custom_animation<F>(callback: F, duration_seconds: f32) -> Option<u32>
370where
371 F: FnMut(u32, f32, f32) + 'static,
372{
373 let handle = unsafe { miracle_get_plugin_handle() };
374 let mut animation_id: u32 = 0;
375 let mut dur = duration_seconds;
376 let result = unsafe {
377 crate::host::miracle_queue_custom_animation(
378 handle as i32,
379 &mut animation_id as *mut u32 as i32,
380 &mut dur as *mut f32 as i32,
381 )
382 };
383 if result == 0 {
384 custom_anim_callbacks().insert(animation_id, (Box::new(callback), duration_seconds));
385 Some(animation_id)
386 } else {
387 None
388 }
389}
390
391static mut _CUSTOM_ANIM_CALLBACKS: Option<
392 std::collections::HashMap<u32, (Box<dyn FnMut(u32, f32, f32)>, f32)>,
393> = None;
394
395#[doc(hidden)]
400pub fn custom_anim_callbacks()
401-> &'static mut std::collections::HashMap<u32, (Box<dyn FnMut(u32, f32, f32)>, f32)> {
402 unsafe {
403 if (*std::ptr::addr_of!(_CUSTOM_ANIM_CALLBACKS)).is_none() {
404 _CUSTOM_ANIM_CALLBACKS = Some(std::collections::HashMap::new());
405 }
406 (*std::ptr::addr_of_mut!(_CUSTOM_ANIM_CALLBACKS))
407 .as_mut()
408 .unwrap()
409 }
410}
411
412#[macro_export]
427macro_rules! miracle_plugin {
428 ($plugin_type:ty) => {
429 static mut _MIRACLE_PLUGIN: Option<$plugin_type> = None;
430 static mut _MIRACLE_PLUGIN_HANDLE: u32 = 0;
431
432 #[unsafe(no_mangle)]
433 pub extern "C" fn miracle_get_plugin_handle() -> u32 {
434 unsafe { _MIRACLE_PLUGIN_HANDLE }
435 }
436
437 #[unsafe(no_mangle)]
438 pub extern "C" fn init(handle: i32) {
439 unsafe {
440 _MIRACLE_PLUGIN_HANDLE = handle as u32;
441 _MIRACLE_PLUGIN = Some(<$plugin_type>::default());
442 }
443 }
444
445 #[unsafe(no_mangle)]
446 pub extern "C" fn animate(data_ptr: i32, result_ptr: i32) -> i32 {
447 let plugin = unsafe {
448 match _MIRACLE_PLUGIN.as_mut() {
449 Some(p) => p,
450 None => return 0,
451 }
452 };
453
454 let c_data = unsafe {
455 &*(data_ptr as *const $crate::bindings::miracle_plugin_animation_frame_data_t)
456 };
457 let data: $crate::animation::AnimationFrameData = (*c_data).into();
458
459 let extract_window = || {
460 let bytes = unsafe {
461 core::slice::from_raw_parts(c_data.window_name.as_ptr() as *const u8, 256)
462 };
463 let len = bytes.iter().position(|&b| b == 0).unwrap_or(256);
464 let name = String::from_utf8_lossy(&bytes[..len]).into_owned();
465 unsafe { $crate::window::WindowInfo::from_c_with_name(&c_data.window_info, name) }
466 };
467
468 let extract_workspace = || {
469 let bytes = unsafe {
470 core::slice::from_raw_parts(c_data.workspace_name.as_ptr() as *const u8, 256)
471 };
472 let len = bytes.iter().position(|&b| b == 0).unwrap_or(256);
473 let name = String::from_utf8_lossy(&bytes[..len]).into_owned();
474 unsafe { $crate::workspace::Workspace::from_c_with_name(&c_data.workspace, name) }
475 };
476
477 let write_result = |result: $crate::animation::AnimationFrameResult| {
478 let c_result: $crate::bindings::miracle_plugin_animation_frame_result_t =
479 result.into();
480 unsafe {
481 let out = &mut *(result_ptr
482 as *mut $crate::bindings::miracle_plugin_animation_frame_result_t);
483 *out = c_result;
484 }
485 };
486
487 match c_data.type_ {
488 $crate::bindings::miracle_animation_type_miracle_animation_type_window_open => {
489 let window = extract_window();
490 match plugin.window_open_animation(&data, &window) {
491 Some(result) => { write_result(result); 1 }
492 None => 0,
493 }
494 },
495 $crate::bindings::miracle_animation_type_miracle_animation_type_window_close => {
496 let window = extract_window();
497 match plugin.window_close_animation(&data, &window) {
498 Some(result) => { write_result(result); 1 }
499 None => 0,
500 }
501 },
502 $crate::bindings::miracle_animation_type_miracle_animation_type_window_move => {
503 let window = extract_window();
504 match plugin.window_move_animation(&data, &window) {
505 Some(result) => { write_result(result); 1 }
506 None => 0,
507 }
508 },
509 $crate::bindings::miracle_animation_type_miracle_animation_type_workspace_switch => {
510 let workspace = extract_workspace();
511 match plugin.workspace_switch_animation(&data, &workspace) {
512 Some(result) => { write_result(result); 1 }
513 None => 0,
514 }
515 },
516 _ => 0
517 }
518
519 }
520
521 #[unsafe(no_mangle)]
522 pub extern "C" fn custom_animate(data_ptr: i32) -> i32 {
523 let raw = unsafe {
524 &*(data_ptr as *const $crate::animation::RawCustomAnimationData)
525 };
526
527 let callbacks = $crate::plugin::custom_anim_callbacks();
528 let done = if let Some((cb, dur)) = callbacks.get_mut(&raw.animation_id) {
529 cb(raw.animation_id, raw.dt, raw.elapsed_seconds);
530 raw.elapsed_seconds >= *dur
531 } else {
532 false
533 };
534 if done {
535 callbacks.remove(&raw.animation_id);
536 }
537
538 0
540 }
541
542 #[unsafe(no_mangle)]
543 pub extern "C" fn place_new_window(
544 window_info_ptr: i32,
545 result_ptr: i32,
546 name_ptr: i32,
547 name_len: i32,
548 ) -> i32 {
549 let plugin = unsafe {
550 match _MIRACLE_PLUGIN.as_mut() {
551 Some(p) => p,
552 None => return 0,
553 }
554 };
555
556 let c_info = unsafe {
557 &*(window_info_ptr as *const $crate::bindings::miracle_window_info_t)
558 };
559
560 let name = if name_len > 0 {
561 let name_bytes = unsafe {
562 core::slice::from_raw_parts(name_ptr as *const u8, name_len as usize)
563 };
564 String::from_utf8_lossy(name_bytes).into_owned()
565 } else {
566 String::new()
567 };
568
569 let info = unsafe { $crate::window::WindowInfo::from_c_with_name(c_info, name) };
570
571 match plugin.place_new_window(&info) {
572 Some(placement) => {
573 let c_placement: $crate::bindings::miracle_placement_t = placement.into();
574 unsafe {
575 let out = &mut *(result_ptr as *mut $crate::bindings::miracle_placement_t);
576 *out = c_placement;
577 }
578 1
579 }
580 None => 0,
581 }
582 }
583
584 #[unsafe(no_mangle)]
585 pub extern "C" fn window_deleted(
586 window_info_ptr: i32,
587 name_ptr: i32,
588 name_len: i32,
589 ) {
590 let plugin = unsafe {
591 match _MIRACLE_PLUGIN.as_mut() {
592 Some(p) => p,
593 None => return,
594 }
595 };
596
597 let c_info = unsafe {
598 &*(window_info_ptr as *const $crate::bindings::miracle_window_info_t)
599 };
600
601 let name = if name_len > 0 {
602 let name_bytes = unsafe {
603 core::slice::from_raw_parts(name_ptr as *const u8, name_len as usize)
604 };
605 String::from_utf8_lossy(name_bytes).into_owned()
606 } else {
607 String::new()
608 };
609
610 let info = unsafe { $crate::window::WindowInfo::from_c_with_name(c_info, name) };
611
612 plugin.window_deleted(&info);
613 }
614
615 #[unsafe(no_mangle)]
616 pub extern "C" fn window_focused(
617 window_info_ptr: i32,
618 name_ptr: i32,
619 name_len: i32,
620 ) {
621 let plugin = unsafe {
622 match _MIRACLE_PLUGIN.as_mut() {
623 Some(p) => p,
624 None => return,
625 }
626 };
627
628 let c_info = unsafe {
629 &*(window_info_ptr as *const $crate::bindings::miracle_window_info_t)
630 };
631
632 let name = if name_len > 0 {
633 let name_bytes = unsafe {
634 core::slice::from_raw_parts(name_ptr as *const u8, name_len as usize)
635 };
636 String::from_utf8_lossy(name_bytes).into_owned()
637 } else {
638 String::new()
639 };
640
641 let info = unsafe { $crate::window::WindowInfo::from_c_with_name(c_info, name) };
642
643 plugin.window_focused(&info);
644 }
645
646 #[unsafe(no_mangle)]
647 pub extern "C" fn window_unfocused(
648 window_info_ptr: i32,
649 name_ptr: i32,
650 name_len: i32,
651 ) {
652 let plugin = unsafe {
653 match _MIRACLE_PLUGIN.as_mut() {
654 Some(p) => p,
655 None => return,
656 }
657 };
658
659 let c_info = unsafe {
660 &*(window_info_ptr as *const $crate::bindings::miracle_window_info_t)
661 };
662
663 let name = if name_len > 0 {
664 let name_bytes = unsafe {
665 core::slice::from_raw_parts(name_ptr as *const u8, name_len as usize)
666 };
667 String::from_utf8_lossy(name_bytes).into_owned()
668 } else {
669 String::new()
670 };
671
672 let info = unsafe { $crate::window::WindowInfo::from_c_with_name(c_info, name) };
673
674 plugin.window_unfocused(&info);
675 }
676
677 #[unsafe(no_mangle)]
678 pub extern "C" fn workspace_created(
679 workspace_info_ptr: i32,
680 name_ptr: i32,
681 name_len: i32,
682 ) {
683 let plugin = unsafe {
684 match _MIRACLE_PLUGIN.as_mut() {
685 Some(p) => p,
686 None => return,
687 }
688 };
689
690 let c_ws = unsafe {
691 &*(workspace_info_ptr as *const $crate::bindings::miracle_workspace_t)
692 };
693
694 let name = if name_len > 0 {
695 let name_bytes = unsafe {
696 core::slice::from_raw_parts(name_ptr as *const u8, name_len as usize)
697 };
698 String::from_utf8_lossy(name_bytes).into_owned()
699 } else {
700 String::new()
701 };
702
703 let ws = unsafe { $crate::workspace::Workspace::from_c_with_name(c_ws, name) };
704 plugin.workspace_created(&ws);
705 }
706
707 #[unsafe(no_mangle)]
708 pub extern "C" fn workspace_removed(
709 workspace_info_ptr: i32,
710 name_ptr: i32,
711 name_len: i32,
712 ) {
713 let plugin = unsafe {
714 match _MIRACLE_PLUGIN.as_mut() {
715 Some(p) => p,
716 None => return,
717 }
718 };
719
720 let c_ws = unsafe {
721 &*(workspace_info_ptr as *const $crate::bindings::miracle_workspace_t)
722 };
723
724 let name = if name_len > 0 {
725 let name_bytes = unsafe {
726 core::slice::from_raw_parts(name_ptr as *const u8, name_len as usize)
727 };
728 String::from_utf8_lossy(name_bytes).into_owned()
729 } else {
730 String::new()
731 };
732
733 let ws = unsafe { $crate::workspace::Workspace::from_c_with_name(c_ws, name) };
734 plugin.workspace_removed(&ws);
735 }
736
737 #[unsafe(no_mangle)]
738 pub extern "C" fn workspace_focused(
739 workspace_info_ptr: i32,
740 name_ptr: i32,
741 name_len: i32,
742 has_previous: i32,
743 previous_id: i64,
744 ) {
745 let plugin = unsafe {
746 match _MIRACLE_PLUGIN.as_mut() {
747 Some(p) => p,
748 None => return,
749 }
750 };
751
752 let c_ws = unsafe {
753 &*(workspace_info_ptr as *const $crate::bindings::miracle_workspace_t)
754 };
755
756 let name = if name_len > 0 {
757 let name_bytes = unsafe {
758 core::slice::from_raw_parts(name_ptr as *const u8, name_len as usize)
759 };
760 String::from_utf8_lossy(name_bytes).into_owned()
761 } else {
762 String::new()
763 };
764
765 let ws = unsafe { $crate::workspace::Workspace::from_c_with_name(c_ws, name) };
766 let prev = if has_previous != 0 { Some(previous_id as u64) } else { None };
767 plugin.workspace_focused(prev, &ws);
768 }
769
770 #[unsafe(no_mangle)]
771 pub extern "C" fn workspace_area_changed(
772 workspace_info_ptr: i32,
773 name_ptr: i32,
774 name_len: i32,
775 ) {
776 let plugin = unsafe {
777 match _MIRACLE_PLUGIN.as_mut() {
778 Some(p) => p,
779 None => return,
780 }
781 };
782
783 let c_ws = unsafe {
784 &*(workspace_info_ptr as *const $crate::bindings::miracle_workspace_t)
785 };
786
787 let name = if name_len > 0 {
788 let name_bytes = unsafe {
789 core::slice::from_raw_parts(name_ptr as *const u8, name_len as usize)
790 };
791 String::from_utf8_lossy(name_bytes).into_owned()
792 } else {
793 String::new()
794 };
795
796 let ws = unsafe { $crate::workspace::Workspace::from_c_with_name(c_ws, name) };
797 plugin.workspace_area_changed(&ws);
798 }
799
800 #[unsafe(no_mangle)]
801 pub extern "C" fn window_workspace_changed(
802 window_info_ptr: i32,
803 window_name_ptr: i32,
804 window_name_len: i32,
805 workspace_info_ptr: i32,
806 workspace_name_ptr: i32,
807 workspace_name_len: i32,
808 ) {
809 let plugin = unsafe {
810 match _MIRACLE_PLUGIN.as_mut() {
811 Some(p) => p,
812 None => return,
813 }
814 };
815
816 let c_info = unsafe {
817 &*(window_info_ptr as *const $crate::bindings::miracle_window_info_t)
818 };
819
820 let window_name = if window_name_len > 0 {
821 let name_bytes = unsafe {
822 core::slice::from_raw_parts(window_name_ptr as *const u8, window_name_len as usize)
823 };
824 String::from_utf8_lossy(name_bytes).into_owned()
825 } else {
826 String::new()
827 };
828
829 let info = unsafe { $crate::window::WindowInfo::from_c_with_name(c_info, window_name) };
830
831 let c_ws = unsafe {
832 &*(workspace_info_ptr as *const $crate::bindings::miracle_workspace_t)
833 };
834
835 let workspace_name = if workspace_name_len > 0 {
836 let name_bytes = unsafe {
837 core::slice::from_raw_parts(workspace_name_ptr as *const u8, workspace_name_len as usize)
838 };
839 String::from_utf8_lossy(name_bytes).into_owned()
840 } else {
841 String::new()
842 };
843
844 let ws = unsafe { $crate::workspace::Workspace::from_c_with_name(c_ws, workspace_name) };
845 plugin.window_workspace_changed(&info, &ws);
846 }
847
848 #[unsafe(no_mangle)]
849 pub extern "C" fn configure(buf_ptr: i32, buf_len: i32) -> i32 {
850 let plugin = unsafe {
851 match _MIRACLE_PLUGIN.as_mut() {
852 Some(p) => p,
853 None => return 0,
854 }
855 };
856 $crate::__private::run_configure(plugin, buf_ptr, buf_len)
857 }
858
859 #[unsafe(no_mangle)]
860 pub extern "C" fn handle_keyboard_input(event_ptr: i32) -> i32 {
861 let plugin = unsafe {
862 match _MIRACLE_PLUGIN.as_mut() {
863 Some(p) => p,
864 None => return 0,
865 }
866 };
867
868 let c_event = unsafe {
869 &*(event_ptr as *const $crate::bindings::miracle_keyboard_event_t)
870 };
871
872 let event = $crate::input::KeyboardEvent {
873 action: $crate::input::KeyboardAction::try_from(c_event.action)
874 .unwrap_or_default(),
875 keysym: c_event.keysym,
876 scan_code: c_event.scan_code,
877 modifiers: $crate::input::InputEventModifiers::from(c_event.modifiers),
878 };
879
880 if plugin.handle_keyboard_input(event) { 1 } else { 0 }
881 }
882
883 #[unsafe(no_mangle)]
884 pub extern "C" fn handle_pointer_event(event_ptr: i32) -> i32 {
885 let plugin = unsafe {
886 match _MIRACLE_PLUGIN.as_mut() {
887 Some(p) => p,
888 None => return 0,
889 }
890 };
891
892 let c_event = unsafe {
893 &*(event_ptr as *const $crate::bindings::miracle_pointer_event_t)
894 };
895
896 let event = $crate::input::PointerEvent {
897 x: c_event.x,
898 y: c_event.y,
899 action: $crate::input::PointerAction::try_from(c_event.action)
900 .unwrap_or_default(),
901 modifiers: $crate::input::InputEventModifiers::from(c_event.modifiers),
902 buttons: $crate::input::PointerButtons::from(c_event.buttons),
903 };
904
905 if plugin.handle_pointer_event(event) { 1 } else { 0 }
906 }
907 };
908}