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::Window;
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: &Window,
62 ) -> Option<AnimationFrameResult> {
63 None
64 }
65
66 fn window_close_animation(
70 &mut self,
71 _data: &AnimationFrameData,
72 _window: &Window,
73 ) -> Option<AnimationFrameResult> {
74 None
75 }
76
77 fn window_move_animation(
81 &mut self,
82 _data: &AnimationFrameData,
83 _window: &Window,
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: &Window) -> Option<Placement> {
104 None
105 }
106
107 fn window_deleted(&mut self, _info: &Window) {}
112
113 fn window_focused(&mut self, _info: &Window) {}
115
116 fn window_unfocused(&mut self, _info: &Window) {}
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: &Window, _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 fn handle_command(&mut self, _payload: &str) -> Option<String> {
179 None
180 }
181}
182
183pub fn managed_windows() -> Vec<Window> {
192 let handle = unsafe { miracle_get_plugin_handle() };
193 let count = unsafe { miracle_num_managed_windows(handle) };
194
195 (0..count)
196 .filter_map(|i| {
197 const NAME_BUF_LEN: usize = 256;
198 let mut window_info =
199 std::mem::MaybeUninit::<crate::bindings::miracle_window_info_t>::uninit();
200 let mut name_buf: [u8; NAME_BUF_LEN] = [0; NAME_BUF_LEN];
201
202 unsafe {
203 let result = miracle_get_managed_window_at(
204 handle,
205 i,
206 window_info.as_mut_ptr() as i32,
207 name_buf.as_mut_ptr() as i32,
208 NAME_BUF_LEN as i32,
209 );
210
211 if result != 0 {
212 return None;
213 }
214
215 let window_info = window_info.assume_init();
216 let name_len = name_buf
217 .iter()
218 .position(|&c| c == 0)
219 .unwrap_or(NAME_BUF_LEN);
220 let name = String::from_utf8_lossy(&name_buf[..name_len]).into_owned();
221
222 Some(Window::from_c_with_name(&window_info, name))
223 }
224 })
225 .collect()
226}
227
228pub fn register_namespace(ns: &str) -> Result<(), ()> {
237 let handle = unsafe { miracle_get_plugin_handle() };
238 let result = unsafe {
239 miracle_plugin_register_namespace(handle as i32, ns.as_ptr() as i32, ns.len() as i32)
240 };
241 if result == 0 {
242 Ok(())
243 } else {
244 Err(())
245 }
246}
247
248pub fn publish_event(payload: &str) -> Result<(), ()> {
255 let handle = unsafe { miracle_get_plugin_handle() };
256 let result = unsafe {
257 miracle_plugin_publish_event(handle as i32, payload.as_ptr() as i32, payload.len() as i32)
258 };
259 if result == 0 {
260 Ok(())
261 } else {
262 Err(())
263 }
264}
265
266pub fn num_outputs() -> u32 {
268 unsafe { miracle_num_outputs() }
269}
270
271pub fn get_output_at(index: u32) -> Option<Output> {
275 if index >= num_outputs() {
276 return None;
277 }
278
279 const NAME_BUF_LEN: usize = 256;
280 let mut output = std::mem::MaybeUninit::<crate::bindings::miracle_output_t>::uninit();
281 let mut name_buf: [u8; NAME_BUF_LEN] = [0; NAME_BUF_LEN];
282
283 unsafe {
284 let result = miracle_get_output_at(
285 index,
286 output.as_mut_ptr() as i32,
287 name_buf.as_mut_ptr() as i32,
288 NAME_BUF_LEN as i32,
289 );
290
291 if result != 0 {
292 return None;
293 }
294
295 let output = output.assume_init();
296
297 let name_len = name_buf
299 .iter()
300 .position(|&c| c == 0)
301 .unwrap_or(NAME_BUF_LEN);
302 let name = String::from_utf8_lossy(&name_buf[..name_len]).into_owned();
303
304 Some(Output::from_c_with_name(&output, name))
305 }
306}
307
308pub fn get_outputs() -> Vec<Output> {
310 let count = num_outputs();
311 (0..count).filter_map(get_output_at).collect()
312}
313
314pub fn get_active_workspace() -> Option<Workspace> {
318 const NAME_BUF_LEN: usize = 256;
319 let mut workspace = std::mem::MaybeUninit::<crate::bindings::miracle_workspace_t>::uninit();
320 let mut name_buf: [u8; NAME_BUF_LEN] = [0; NAME_BUF_LEN];
321
322 unsafe {
323 let result = miracle_get_active_workspace(
324 workspace.as_mut_ptr() as i32,
325 name_buf.as_mut_ptr() as i32,
326 NAME_BUF_LEN as i32,
327 );
328
329 if result != 0 {
330 return None;
331 }
332
333 let workspace = workspace.assume_init();
334 if workspace.is_set == 0 {
335 return None;
336 }
337
338 let name_len = name_buf
339 .iter()
340 .position(|&c| c == 0)
341 .unwrap_or(NAME_BUF_LEN);
342 let name = String::from_utf8_lossy(&name_buf[..name_len]).into_owned();
343
344 Some(Workspace::from_c_with_name(&workspace, name))
345 }
346}
347
348pub fn request_workspace(
357 number: Option<u32>,
358 name: Option<&str>,
359 focus: bool,
360) -> Option<Workspace> {
361 const NAME_BUF_LEN: usize = 256;
362 let mut workspace = std::mem::MaybeUninit::<crate::bindings::miracle_workspace_t>::uninit();
363 let mut out_name_buf: [u8; NAME_BUF_LEN] = [0; NAME_BUF_LEN];
364
365 let has_number: i32 = if number.is_some() { 1 } else { 0 };
366 let number_val: i32 = number.unwrap_or(0) as i32;
367
368 let name_ptr: i32 = match name {
369 Some(s) => s.as_ptr() as i32,
370 None => 0,
371 };
372 let name_len: i32 = match name {
373 Some(s) => s.len() as i32,
374 None => 0,
375 };
376
377 unsafe {
378 let result = miracle_request_workspace(
379 has_number,
380 number_val,
381 name_ptr,
382 name_len,
383 workspace.as_mut_ptr() as i32,
384 out_name_buf.as_mut_ptr() as i32,
385 NAME_BUF_LEN as i32,
386 if focus { 1 } else { 0 },
387 );
388
389 if result != 0 {
390 return None;
391 }
392
393 let workspace = workspace.assume_init();
394 if workspace.is_set == 0 {
395 return None;
396 }
397
398 let out_name_len = out_name_buf
399 .iter()
400 .position(|&c| c == 0)
401 .unwrap_or(NAME_BUF_LEN);
402 let ws_name = String::from_utf8_lossy(&out_name_buf[..out_name_len]).into_owned();
403
404 Some(Workspace::from_c_with_name(&workspace, ws_name))
405 }
406}
407
408pub fn queue_custom_animation<F>(callback: F, duration_seconds: f32) -> Option<u32>
417where
418 F: FnMut(u32, f32, f32) + 'static,
419{
420 let handle = unsafe { miracle_get_plugin_handle() };
421 let mut animation_id: u32 = 0;
422 let mut dur = duration_seconds;
423 let result = unsafe {
424 crate::host::miracle_queue_custom_animation(
425 handle as i32,
426 &mut animation_id as *mut u32 as i32,
427 &mut dur as *mut f32 as i32,
428 )
429 };
430 if result == 0 {
431 custom_anim_callbacks().insert(animation_id, (Box::new(callback), duration_seconds));
432 Some(animation_id)
433 } else {
434 None
435 }
436}
437
438pub fn register_window_shader(passes: &[&str]) -> Option<u8> {
454 let descriptors: Vec<[i32; 2]> = passes
457 .iter()
458 .map(|p| [p.as_ptr() as i32, p.len() as i32])
459 .collect();
460 let handle = unsafe { miracle_get_plugin_handle() };
461 let result = unsafe {
462 crate::host::miracle_register_window_sample_to_rgba(
463 handle as i32,
464 descriptors.as_ptr() as i32,
465 passes.len() as i32,
466 )
467 };
468 if result >= 0 {
469 Some(result as u8)
470 } else {
471 None
472 }
473}
474
475pub fn set_screen_shader(passes: &[&str]) -> Result<(), ()> {
489 let handle = unsafe { miracle_get_plugin_handle() };
490 let descriptors: Vec<[i32; 2]> = passes
492 .iter()
493 .map(|p| [p.as_ptr() as i32, p.len() as i32])
494 .collect();
495 let r = unsafe {
496 crate::host::miracle_set_screen_shader(
497 handle as i32,
498 descriptors.as_ptr() as i32,
499 passes.len() as i32,
500 )
501 };
502 if r == 0 {
503 Ok(())
504 } else {
505 Err(())
506 }
507}
508
509static mut _CUSTOM_ANIM_CALLBACKS: Option<
510 std::collections::HashMap<u32, (Box<dyn FnMut(u32, f32, f32)>, f32)>,
511> = None;
512
513#[doc(hidden)]
518pub fn custom_anim_callbacks()
519-> &'static mut std::collections::HashMap<u32, (Box<dyn FnMut(u32, f32, f32)>, f32)> {
520 unsafe {
521 if (*std::ptr::addr_of!(_CUSTOM_ANIM_CALLBACKS)).is_none() {
522 _CUSTOM_ANIM_CALLBACKS = Some(std::collections::HashMap::new());
523 }
524 (*std::ptr::addr_of_mut!(_CUSTOM_ANIM_CALLBACKS))
525 .as_mut()
526 .unwrap()
527 }
528}
529
530#[macro_export]
545macro_rules! miracle_plugin {
546 ($plugin_type:ty) => {
547 static mut _MIRACLE_PLUGIN: Option<$plugin_type> = None;
548 static mut _MIRACLE_PLUGIN_HANDLE: u32 = 0;
549
550 #[unsafe(no_mangle)]
551 pub extern "C" fn miracle_get_plugin_handle() -> u32 {
552 unsafe { _MIRACLE_PLUGIN_HANDLE }
553 }
554
555 #[unsafe(no_mangle)]
556 pub extern "C" fn init(handle: i32) {
557 unsafe {
558 _MIRACLE_PLUGIN_HANDLE = handle as u32;
559 _MIRACLE_PLUGIN = Some(<$plugin_type>::default());
560 }
561 }
562
563 #[unsafe(no_mangle)]
564 pub extern "C" fn animate(data_ptr: i32, result_ptr: i32) -> i32 {
565 let plugin = unsafe {
566 match _MIRACLE_PLUGIN.as_mut() {
567 Some(p) => p,
568 None => return 0,
569 }
570 };
571
572 let c_data = unsafe {
573 &*(data_ptr as *const $crate::bindings::miracle_plugin_animation_frame_data_t)
574 };
575 let data: $crate::animation::AnimationFrameData = (*c_data).into();
576
577 let extract_window = || {
578 let bytes = unsafe {
579 core::slice::from_raw_parts(c_data.window_name.as_ptr() as *const u8, 256)
580 };
581 let len = bytes.iter().position(|&b| b == 0).unwrap_or(256);
582 let name = String::from_utf8_lossy(&bytes[..len]).into_owned();
583 unsafe { $crate::window::Window::from_c_with_name(&c_data.window_info, name) }
584 };
585
586 let extract_workspace = || {
587 let bytes = unsafe {
588 core::slice::from_raw_parts(c_data.workspace_name.as_ptr() as *const u8, 256)
589 };
590 let len = bytes.iter().position(|&b| b == 0).unwrap_or(256);
591 let name = String::from_utf8_lossy(&bytes[..len]).into_owned();
592 unsafe { $crate::workspace::Workspace::from_c_with_name(&c_data.workspace, name) }
593 };
594
595 let write_result = |result: $crate::animation::AnimationFrameResult| {
596 let c_result: $crate::bindings::miracle_plugin_animation_frame_result_t =
597 result.into();
598 unsafe {
599 let out = &mut *(result_ptr
600 as *mut $crate::bindings::miracle_plugin_animation_frame_result_t);
601 *out = c_result;
602 }
603 };
604
605 match c_data.type_ {
606 $crate::bindings::miracle_animation_type_miracle_animation_type_window_open => {
607 let window = extract_window();
608 match plugin.window_open_animation(&data, &window) {
609 Some(result) => { write_result(result); 1 }
610 None => 0,
611 }
612 },
613 $crate::bindings::miracle_animation_type_miracle_animation_type_window_close => {
614 let window = extract_window();
615 match plugin.window_close_animation(&data, &window) {
616 Some(result) => { write_result(result); 1 }
617 None => 0,
618 }
619 },
620 $crate::bindings::miracle_animation_type_miracle_animation_type_window_move => {
621 let window = extract_window();
622 match plugin.window_move_animation(&data, &window) {
623 Some(result) => { write_result(result); 1 }
624 None => 0,
625 }
626 },
627 $crate::bindings::miracle_animation_type_miracle_animation_type_workspace_switch => {
628 let workspace = extract_workspace();
629 match plugin.workspace_switch_animation(&data, &workspace) {
630 Some(result) => { write_result(result); 1 }
631 None => 0,
632 }
633 },
634 _ => 0
635 }
636
637 }
638
639 #[unsafe(no_mangle)]
640 pub extern "C" fn custom_animate(data_ptr: i32) -> i32 {
641 let raw = unsafe {
642 &*(data_ptr as *const $crate::animation::RawCustomAnimationData)
643 };
644
645 let callbacks = $crate::plugin::custom_anim_callbacks();
646 let done = if let Some((cb, dur)) = callbacks.get_mut(&raw.animation_id) {
647 cb(raw.animation_id, raw.dt, raw.elapsed_seconds);
648 raw.elapsed_seconds >= *dur
649 } else {
650 false
651 };
652 if done {
653 callbacks.remove(&raw.animation_id);
654 }
655
656 0
658 }
659
660 #[unsafe(no_mangle)]
661 pub extern "C" fn place_new_window(
662 window_info_ptr: i32,
663 result_ptr: i32,
664 name_ptr: i32,
665 name_len: i32,
666 ) -> i32 {
667 let plugin = unsafe {
668 match _MIRACLE_PLUGIN.as_mut() {
669 Some(p) => p,
670 None => return 0,
671 }
672 };
673
674 let c_info = unsafe {
675 &*(window_info_ptr as *const $crate::bindings::miracle_window_info_t)
676 };
677
678 let name = if name_len > 0 {
679 let name_bytes = unsafe {
680 core::slice::from_raw_parts(name_ptr as *const u8, name_len as usize)
681 };
682 String::from_utf8_lossy(name_bytes).into_owned()
683 } else {
684 String::new()
685 };
686
687 let info = unsafe { $crate::window::Window::from_c_with_name(c_info, name) };
688
689 match plugin.place_new_window(&info) {
690 Some(placement) => {
691 let c_placement: $crate::bindings::miracle_placement_t = placement.into();
692 unsafe {
693 let out = &mut *(result_ptr as *mut $crate::bindings::miracle_placement_t);
694 *out = c_placement;
695 }
696 1
697 }
698 None => 0,
699 }
700 }
701
702 #[unsafe(no_mangle)]
703 pub extern "C" fn window_deleted(
704 window_info_ptr: i32,
705 name_ptr: i32,
706 name_len: i32,
707 ) {
708 let plugin = unsafe {
709 match _MIRACLE_PLUGIN.as_mut() {
710 Some(p) => p,
711 None => return,
712 }
713 };
714
715 let c_info = unsafe {
716 &*(window_info_ptr as *const $crate::bindings::miracle_window_info_t)
717 };
718
719 let name = if name_len > 0 {
720 let name_bytes = unsafe {
721 core::slice::from_raw_parts(name_ptr as *const u8, name_len as usize)
722 };
723 String::from_utf8_lossy(name_bytes).into_owned()
724 } else {
725 String::new()
726 };
727
728 let info = unsafe { $crate::window::Window::from_c_with_name(c_info, name) };
729
730 plugin.window_deleted(&info);
731 }
732
733 #[unsafe(no_mangle)]
734 pub extern "C" fn window_focused(
735 window_info_ptr: i32,
736 name_ptr: i32,
737 name_len: i32,
738 ) {
739 let plugin = unsafe {
740 match _MIRACLE_PLUGIN.as_mut() {
741 Some(p) => p,
742 None => return,
743 }
744 };
745
746 let c_info = unsafe {
747 &*(window_info_ptr as *const $crate::bindings::miracle_window_info_t)
748 };
749
750 let name = if name_len > 0 {
751 let name_bytes = unsafe {
752 core::slice::from_raw_parts(name_ptr as *const u8, name_len as usize)
753 };
754 String::from_utf8_lossy(name_bytes).into_owned()
755 } else {
756 String::new()
757 };
758
759 let info = unsafe { $crate::window::Window::from_c_with_name(c_info, name) };
760
761 plugin.window_focused(&info);
762 }
763
764 #[unsafe(no_mangle)]
765 pub extern "C" fn window_unfocused(
766 window_info_ptr: i32,
767 name_ptr: i32,
768 name_len: i32,
769 ) {
770 let plugin = unsafe {
771 match _MIRACLE_PLUGIN.as_mut() {
772 Some(p) => p,
773 None => return,
774 }
775 };
776
777 let c_info = unsafe {
778 &*(window_info_ptr as *const $crate::bindings::miracle_window_info_t)
779 };
780
781 let name = if name_len > 0 {
782 let name_bytes = unsafe {
783 core::slice::from_raw_parts(name_ptr as *const u8, name_len as usize)
784 };
785 String::from_utf8_lossy(name_bytes).into_owned()
786 } else {
787 String::new()
788 };
789
790 let info = unsafe { $crate::window::Window::from_c_with_name(c_info, name) };
791
792 plugin.window_unfocused(&info);
793 }
794
795 #[unsafe(no_mangle)]
796 pub extern "C" fn workspace_created(
797 workspace_info_ptr: i32,
798 name_ptr: i32,
799 name_len: i32,
800 ) {
801 let plugin = unsafe {
802 match _MIRACLE_PLUGIN.as_mut() {
803 Some(p) => p,
804 None => return,
805 }
806 };
807
808 let c_ws = unsafe {
809 &*(workspace_info_ptr as *const $crate::bindings::miracle_workspace_t)
810 };
811
812 let name = if name_len > 0 {
813 let name_bytes = unsafe {
814 core::slice::from_raw_parts(name_ptr as *const u8, name_len as usize)
815 };
816 String::from_utf8_lossy(name_bytes).into_owned()
817 } else {
818 String::new()
819 };
820
821 let ws = unsafe { $crate::workspace::Workspace::from_c_with_name(c_ws, name) };
822 plugin.workspace_created(&ws);
823 }
824
825 #[unsafe(no_mangle)]
826 pub extern "C" fn workspace_removed(
827 workspace_info_ptr: i32,
828 name_ptr: i32,
829 name_len: i32,
830 ) {
831 let plugin = unsafe {
832 match _MIRACLE_PLUGIN.as_mut() {
833 Some(p) => p,
834 None => return,
835 }
836 };
837
838 let c_ws = unsafe {
839 &*(workspace_info_ptr as *const $crate::bindings::miracle_workspace_t)
840 };
841
842 let name = if name_len > 0 {
843 let name_bytes = unsafe {
844 core::slice::from_raw_parts(name_ptr as *const u8, name_len as usize)
845 };
846 String::from_utf8_lossy(name_bytes).into_owned()
847 } else {
848 String::new()
849 };
850
851 let ws = unsafe { $crate::workspace::Workspace::from_c_with_name(c_ws, name) };
852 plugin.workspace_removed(&ws);
853 }
854
855 #[unsafe(no_mangle)]
856 pub extern "C" fn workspace_focused(
857 workspace_info_ptr: i32,
858 name_ptr: i32,
859 name_len: i32,
860 has_previous: i32,
861 previous_id: i64,
862 ) {
863 let plugin = unsafe {
864 match _MIRACLE_PLUGIN.as_mut() {
865 Some(p) => p,
866 None => return,
867 }
868 };
869
870 let c_ws = unsafe {
871 &*(workspace_info_ptr as *const $crate::bindings::miracle_workspace_t)
872 };
873
874 let name = if name_len > 0 {
875 let name_bytes = unsafe {
876 core::slice::from_raw_parts(name_ptr as *const u8, name_len as usize)
877 };
878 String::from_utf8_lossy(name_bytes).into_owned()
879 } else {
880 String::new()
881 };
882
883 let ws = unsafe { $crate::workspace::Workspace::from_c_with_name(c_ws, name) };
884 let prev = if has_previous != 0 { Some(previous_id as u64) } else { None };
885 plugin.workspace_focused(prev, &ws);
886 }
887
888 #[unsafe(no_mangle)]
889 pub extern "C" fn workspace_area_changed(
890 workspace_info_ptr: i32,
891 name_ptr: i32,
892 name_len: i32,
893 ) {
894 let plugin = unsafe {
895 match _MIRACLE_PLUGIN.as_mut() {
896 Some(p) => p,
897 None => return,
898 }
899 };
900
901 let c_ws = unsafe {
902 &*(workspace_info_ptr as *const $crate::bindings::miracle_workspace_t)
903 };
904
905 let name = if name_len > 0 {
906 let name_bytes = unsafe {
907 core::slice::from_raw_parts(name_ptr as *const u8, name_len as usize)
908 };
909 String::from_utf8_lossy(name_bytes).into_owned()
910 } else {
911 String::new()
912 };
913
914 let ws = unsafe { $crate::workspace::Workspace::from_c_with_name(c_ws, name) };
915 plugin.workspace_area_changed(&ws);
916 }
917
918 #[unsafe(no_mangle)]
919 pub extern "C" fn window_workspace_changed(
920 window_info_ptr: i32,
921 window_name_ptr: i32,
922 window_name_len: i32,
923 workspace_info_ptr: i32,
924 workspace_name_ptr: i32,
925 workspace_name_len: i32,
926 ) {
927 let plugin = unsafe {
928 match _MIRACLE_PLUGIN.as_mut() {
929 Some(p) => p,
930 None => return,
931 }
932 };
933
934 let c_info = unsafe {
935 &*(window_info_ptr as *const $crate::bindings::miracle_window_info_t)
936 };
937
938 let window_name = if window_name_len > 0 {
939 let name_bytes = unsafe {
940 core::slice::from_raw_parts(window_name_ptr as *const u8, window_name_len as usize)
941 };
942 String::from_utf8_lossy(name_bytes).into_owned()
943 } else {
944 String::new()
945 };
946
947 let info = unsafe { $crate::window::Window::from_c_with_name(c_info, window_name) };
948
949 let c_ws = unsafe {
950 &*(workspace_info_ptr as *const $crate::bindings::miracle_workspace_t)
951 };
952
953 let workspace_name = if workspace_name_len > 0 {
954 let name_bytes = unsafe {
955 core::slice::from_raw_parts(workspace_name_ptr as *const u8, workspace_name_len as usize)
956 };
957 String::from_utf8_lossy(name_bytes).into_owned()
958 } else {
959 String::new()
960 };
961
962 let ws = unsafe { $crate::workspace::Workspace::from_c_with_name(c_ws, workspace_name) };
963 plugin.window_workspace_changed(&info, &ws);
964 }
965
966 #[unsafe(no_mangle)]
967 pub extern "C" fn configure(buf_ptr: i32, buf_len: i32) -> i32 {
968 let plugin = unsafe {
969 match _MIRACLE_PLUGIN.as_mut() {
970 Some(p) => p,
971 None => return 0,
972 }
973 };
974 $crate::__private::run_configure(plugin, buf_ptr, buf_len)
975 }
976
977 #[unsafe(no_mangle)]
978 pub extern "C" fn handle_keyboard_input(event_ptr: i32) -> i32 {
979 let plugin = unsafe {
980 match _MIRACLE_PLUGIN.as_mut() {
981 Some(p) => p,
982 None => return 0,
983 }
984 };
985
986 let c_event = unsafe {
987 &*(event_ptr as *const $crate::bindings::miracle_keyboard_event_t)
988 };
989
990 let event = $crate::input::KeyboardEvent {
991 action: $crate::input::KeyboardAction::try_from(c_event.action)
992 .unwrap_or_default(),
993 keysym: c_event.keysym,
994 scan_code: c_event.scan_code,
995 modifiers: $crate::input::InputEventModifiers::from(c_event.modifiers),
996 };
997
998 if plugin.handle_keyboard_input(event) { 1 } else { 0 }
999 }
1000
1001 #[unsafe(no_mangle)]
1002 pub extern "C" fn handle_pointer_event(event_ptr: i32) -> i32 {
1003 let plugin = unsafe {
1004 match _MIRACLE_PLUGIN.as_mut() {
1005 Some(p) => p,
1006 None => return 0,
1007 }
1008 };
1009
1010 let c_event = unsafe {
1011 &*(event_ptr as *const $crate::bindings::miracle_pointer_event_t)
1012 };
1013
1014 let event = $crate::input::PointerEvent {
1015 x: c_event.x,
1016 y: c_event.y,
1017 action: $crate::input::PointerAction::try_from(c_event.action)
1018 .unwrap_or_default(),
1019 modifiers: $crate::input::InputEventModifiers::from(c_event.modifiers),
1020 buttons: $crate::input::PointerButtons::from(c_event.buttons),
1021 };
1022
1023 if plugin.handle_pointer_event(event) { 1 } else { 0 }
1024 }
1025
1026 #[unsafe(no_mangle)]
1027 pub extern "C" fn handle_plugin_command(buf_ptr: i32, in_len: i32, buf_cap: i32) -> i32 {
1028 let plugin = unsafe {
1029 match _MIRACLE_PLUGIN.as_mut() {
1030 Some(p) => p,
1031 None => return -1,
1032 }
1033 };
1034
1035 let payload = if in_len > 0 {
1037 let bytes = unsafe {
1038 core::slice::from_raw_parts(buf_ptr as *const u8, in_len as usize)
1039 };
1040 String::from_utf8_lossy(bytes).into_owned()
1041 } else {
1042 String::new()
1043 };
1044
1045 match plugin.handle_command(&payload) {
1046 Some(response) => {
1047 let bytes = response.as_bytes();
1048 if bytes.len() > buf_cap as usize {
1049 return -1;
1050 }
1051 unsafe {
1052 core::ptr::copy_nonoverlapping(
1053 bytes.as_ptr(),
1054 buf_ptr as *mut u8,
1055 bytes.len(),
1056 );
1057 }
1058 bytes.len() as i32
1059 }
1060 None => -1,
1061 }
1062 }
1063 };
1064}