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 cancel_custom_animation(animation_id: u32) -> Result<(), ()> {
443 if custom_anim_callbacks().remove(&animation_id).is_none() {
444 let running = unsafe { &mut *std::ptr::addr_of_mut!(_RUNNING_CUSTOM_ANIM) };
447 if let Some((id, cancelled)) = running {
448 if *id == animation_id {
449 *cancelled = true;
450 }
451 }
452 }
453 let handle = unsafe { miracle_get_plugin_handle() };
454 let r = unsafe { crate::host::miracle_cancel_custom_animation(handle as i32, animation_id as i32) };
455 if r == 0 { Ok(()) } else { Err(()) }
456}
457
458pub fn register_window_shader(passes: &[&str]) -> Option<u8> {
474 let descriptors: Vec<[i32; 2]> = passes
477 .iter()
478 .map(|p| [p.as_ptr() as i32, p.len() as i32])
479 .collect();
480 let handle = unsafe { miracle_get_plugin_handle() };
481 let result = unsafe {
482 crate::host::miracle_register_window_sample_to_rgba(
483 handle as i32,
484 descriptors.as_ptr() as i32,
485 passes.len() as i32,
486 )
487 };
488 if result >= 0 {
489 Some(result as u8)
490 } else {
491 None
492 }
493}
494
495pub fn register_window_geometry_shader(source: &str) -> Option<u8> {
528 let handle = unsafe { miracle_get_plugin_handle() };
529 let result = unsafe {
531 crate::host::miracle_register_window_geometry_shader(
532 handle as i32,
533 source.as_ptr() as i32,
534 source.len() as i32,
535 )
536 };
537 if result >= 0 {
538 Some(result as u8)
539 } else {
540 None
541 }
542}
543
544pub fn set_screen_shader(passes: &[&str]) -> Result<(), ()> {
558 let handle = unsafe { miracle_get_plugin_handle() };
559 let descriptors: Vec<[i32; 2]> = passes
561 .iter()
562 .map(|p| [p.as_ptr() as i32, p.len() as i32])
563 .collect();
564 let r = unsafe {
565 crate::host::miracle_set_screen_shader(
566 handle as i32,
567 descriptors.as_ptr() as i32,
568 passes.len() as i32,
569 )
570 };
571 if r == 0 {
572 Ok(())
573 } else {
574 Err(())
575 }
576}
577
578static mut _CUSTOM_ANIM_CALLBACKS: Option<
579 std::collections::HashMap<u32, (Box<dyn FnMut(u32, f32, f32)>, f32)>,
580> = None;
581
582static mut _RUNNING_CUSTOM_ANIM: Option<(u32, bool)> = None;
585
586#[doc(hidden)]
591pub fn run_custom_animation(animation_id: u32, dt: f32, elapsed_seconds: f32) {
592 let Some((mut cb, duration)) = custom_anim_callbacks().remove(&animation_id) else {
593 return;
594 };
595
596 unsafe { _RUNNING_CUSTOM_ANIM = Some((animation_id, false)) };
597 cb(animation_id, dt, elapsed_seconds);
598 let cancelled = unsafe { (*std::ptr::addr_of_mut!(_RUNNING_CUSTOM_ANIM)).take() }
599 .is_some_and(|(_, cancelled)| cancelled);
600
601 if !cancelled && elapsed_seconds < duration {
602 custom_anim_callbacks().insert(animation_id, (cb, duration));
603 }
604}
605
606#[doc(hidden)]
611pub fn custom_anim_callbacks()
612-> &'static mut std::collections::HashMap<u32, (Box<dyn FnMut(u32, f32, f32)>, f32)> {
613 unsafe {
614 if (*std::ptr::addr_of!(_CUSTOM_ANIM_CALLBACKS)).is_none() {
615 _CUSTOM_ANIM_CALLBACKS = Some(std::collections::HashMap::new());
616 }
617 (*std::ptr::addr_of_mut!(_CUSTOM_ANIM_CALLBACKS))
618 .as_mut()
619 .unwrap()
620 }
621}
622
623#[macro_export]
638macro_rules! miracle_plugin {
639 ($plugin_type:ty) => {
640 static mut _MIRACLE_PLUGIN: Option<$plugin_type> = None;
641 static mut _MIRACLE_PLUGIN_HANDLE: u32 = 0;
642
643 #[unsafe(no_mangle)]
644 pub extern "C" fn miracle_get_plugin_handle() -> u32 {
645 unsafe { _MIRACLE_PLUGIN_HANDLE }
646 }
647
648 #[unsafe(no_mangle)]
649 pub extern "C" fn init(handle: i32) {
650 unsafe {
651 _MIRACLE_PLUGIN_HANDLE = handle as u32;
652 _MIRACLE_PLUGIN = Some(<$plugin_type>::default());
653 }
654 }
655
656 #[unsafe(no_mangle)]
657 pub extern "C" fn animate(data_ptr: i32, result_ptr: i32) -> i32 {
658 let plugin = unsafe {
659 match _MIRACLE_PLUGIN.as_mut() {
660 Some(p) => p,
661 None => return 0,
662 }
663 };
664
665 let c_data = unsafe {
666 &*(data_ptr as *const $crate::bindings::miracle_plugin_animation_frame_data_t)
667 };
668 let data: $crate::animation::AnimationFrameData = (*c_data).into();
669
670 let extract_window = || {
671 let bytes = unsafe {
672 core::slice::from_raw_parts(c_data.window_name.as_ptr() as *const u8, 256)
673 };
674 let len = bytes.iter().position(|&b| b == 0).unwrap_or(256);
675 let name = String::from_utf8_lossy(&bytes[..len]).into_owned();
676 unsafe { $crate::window::Window::from_c_with_name(&c_data.window_info, name) }
677 };
678
679 let extract_workspace = || {
680 let bytes = unsafe {
681 core::slice::from_raw_parts(c_data.workspace_name.as_ptr() as *const u8, 256)
682 };
683 let len = bytes.iter().position(|&b| b == 0).unwrap_or(256);
684 let name = String::from_utf8_lossy(&bytes[..len]).into_owned();
685 unsafe { $crate::workspace::Workspace::from_c_with_name(&c_data.workspace, name) }
686 };
687
688 let write_result = |result: $crate::animation::AnimationFrameResult| {
689 let c_result: $crate::bindings::miracle_plugin_animation_frame_result_t =
690 result.into();
691 unsafe {
692 let out = &mut *(result_ptr
693 as *mut $crate::bindings::miracle_plugin_animation_frame_result_t);
694 *out = c_result;
695 }
696 };
697
698 match c_data.type_ {
699 $crate::bindings::miracle_animation_type_miracle_animation_type_window_open => {
700 let window = extract_window();
701 match plugin.window_open_animation(&data, &window) {
702 Some(result) => { write_result(result); 1 }
703 None => 0,
704 }
705 },
706 $crate::bindings::miracle_animation_type_miracle_animation_type_window_close => {
707 let window = extract_window();
708 match plugin.window_close_animation(&data, &window) {
709 Some(result) => { write_result(result); 1 }
710 None => 0,
711 }
712 },
713 $crate::bindings::miracle_animation_type_miracle_animation_type_window_move => {
714 let window = extract_window();
715 match plugin.window_move_animation(&data, &window) {
716 Some(result) => { write_result(result); 1 }
717 None => 0,
718 }
719 },
720 $crate::bindings::miracle_animation_type_miracle_animation_type_workspace_switch => {
721 let workspace = extract_workspace();
722 match plugin.workspace_switch_animation(&data, &workspace) {
723 Some(result) => { write_result(result); 1 }
724 None => 0,
725 }
726 },
727 _ => 0
728 }
729
730 }
731
732 #[unsafe(no_mangle)]
733 pub extern "C" fn custom_animate(data_ptr: i32) -> i32 {
734 let raw = unsafe {
735 &*(data_ptr as *const $crate::animation::RawCustomAnimationData)
736 };
737
738 $crate::plugin::run_custom_animation(raw.animation_id, raw.dt, raw.elapsed_seconds);
739
740 0
742 }
743
744 #[unsafe(no_mangle)]
745 pub extern "C" fn place_new_window(
746 window_info_ptr: i32,
747 result_ptr: i32,
748 name_ptr: i32,
749 name_len: i32,
750 ) -> i32 {
751 let plugin = unsafe {
752 match _MIRACLE_PLUGIN.as_mut() {
753 Some(p) => p,
754 None => return 0,
755 }
756 };
757
758 let c_info = unsafe {
759 &*(window_info_ptr as *const $crate::bindings::miracle_window_info_t)
760 };
761
762 let name = if name_len > 0 {
763 let name_bytes = unsafe {
764 core::slice::from_raw_parts(name_ptr as *const u8, name_len as usize)
765 };
766 String::from_utf8_lossy(name_bytes).into_owned()
767 } else {
768 String::new()
769 };
770
771 let info = unsafe { $crate::window::Window::from_c_with_name(c_info, name) };
772
773 match plugin.place_new_window(&info) {
774 Some(placement) => {
775 let c_placement: $crate::bindings::miracle_placement_t = placement.into();
776 unsafe {
777 let out = &mut *(result_ptr as *mut $crate::bindings::miracle_placement_t);
778 *out = c_placement;
779 }
780 1
781 }
782 None => 0,
783 }
784 }
785
786 #[unsafe(no_mangle)]
787 pub extern "C" fn window_deleted(
788 window_info_ptr: i32,
789 name_ptr: i32,
790 name_len: i32,
791 ) {
792 let plugin = unsafe {
793 match _MIRACLE_PLUGIN.as_mut() {
794 Some(p) => p,
795 None => return,
796 }
797 };
798
799 let c_info = unsafe {
800 &*(window_info_ptr as *const $crate::bindings::miracle_window_info_t)
801 };
802
803 let name = if name_len > 0 {
804 let name_bytes = unsafe {
805 core::slice::from_raw_parts(name_ptr as *const u8, name_len as usize)
806 };
807 String::from_utf8_lossy(name_bytes).into_owned()
808 } else {
809 String::new()
810 };
811
812 let info = unsafe { $crate::window::Window::from_c_with_name(c_info, name) };
813
814 plugin.window_deleted(&info);
815 }
816
817 #[unsafe(no_mangle)]
818 pub extern "C" fn window_focused(
819 window_info_ptr: i32,
820 name_ptr: i32,
821 name_len: i32,
822 ) {
823 let plugin = unsafe {
824 match _MIRACLE_PLUGIN.as_mut() {
825 Some(p) => p,
826 None => return,
827 }
828 };
829
830 let c_info = unsafe {
831 &*(window_info_ptr as *const $crate::bindings::miracle_window_info_t)
832 };
833
834 let name = if name_len > 0 {
835 let name_bytes = unsafe {
836 core::slice::from_raw_parts(name_ptr as *const u8, name_len as usize)
837 };
838 String::from_utf8_lossy(name_bytes).into_owned()
839 } else {
840 String::new()
841 };
842
843 let info = unsafe { $crate::window::Window::from_c_with_name(c_info, name) };
844
845 plugin.window_focused(&info);
846 }
847
848 #[unsafe(no_mangle)]
849 pub extern "C" fn window_unfocused(
850 window_info_ptr: i32,
851 name_ptr: i32,
852 name_len: i32,
853 ) {
854 let plugin = unsafe {
855 match _MIRACLE_PLUGIN.as_mut() {
856 Some(p) => p,
857 None => return,
858 }
859 };
860
861 let c_info = unsafe {
862 &*(window_info_ptr as *const $crate::bindings::miracle_window_info_t)
863 };
864
865 let name = if name_len > 0 {
866 let name_bytes = unsafe {
867 core::slice::from_raw_parts(name_ptr as *const u8, name_len as usize)
868 };
869 String::from_utf8_lossy(name_bytes).into_owned()
870 } else {
871 String::new()
872 };
873
874 let info = unsafe { $crate::window::Window::from_c_with_name(c_info, name) };
875
876 plugin.window_unfocused(&info);
877 }
878
879 #[unsafe(no_mangle)]
880 pub extern "C" fn workspace_created(
881 workspace_info_ptr: i32,
882 name_ptr: i32,
883 name_len: i32,
884 ) {
885 let plugin = unsafe {
886 match _MIRACLE_PLUGIN.as_mut() {
887 Some(p) => p,
888 None => return,
889 }
890 };
891
892 let c_ws = unsafe {
893 &*(workspace_info_ptr as *const $crate::bindings::miracle_workspace_t)
894 };
895
896 let name = if name_len > 0 {
897 let name_bytes = unsafe {
898 core::slice::from_raw_parts(name_ptr as *const u8, name_len as usize)
899 };
900 String::from_utf8_lossy(name_bytes).into_owned()
901 } else {
902 String::new()
903 };
904
905 let ws = unsafe { $crate::workspace::Workspace::from_c_with_name(c_ws, name) };
906 plugin.workspace_created(&ws);
907 }
908
909 #[unsafe(no_mangle)]
910 pub extern "C" fn workspace_removed(
911 workspace_info_ptr: i32,
912 name_ptr: i32,
913 name_len: i32,
914 ) {
915 let plugin = unsafe {
916 match _MIRACLE_PLUGIN.as_mut() {
917 Some(p) => p,
918 None => return,
919 }
920 };
921
922 let c_ws = unsafe {
923 &*(workspace_info_ptr as *const $crate::bindings::miracle_workspace_t)
924 };
925
926 let name = if name_len > 0 {
927 let name_bytes = unsafe {
928 core::slice::from_raw_parts(name_ptr as *const u8, name_len as usize)
929 };
930 String::from_utf8_lossy(name_bytes).into_owned()
931 } else {
932 String::new()
933 };
934
935 let ws = unsafe { $crate::workspace::Workspace::from_c_with_name(c_ws, name) };
936 plugin.workspace_removed(&ws);
937 }
938
939 #[unsafe(no_mangle)]
940 pub extern "C" fn workspace_focused(
941 workspace_info_ptr: i32,
942 name_ptr: i32,
943 name_len: i32,
944 has_previous: i32,
945 previous_id: i64,
946 ) {
947 let plugin = unsafe {
948 match _MIRACLE_PLUGIN.as_mut() {
949 Some(p) => p,
950 None => return,
951 }
952 };
953
954 let c_ws = unsafe {
955 &*(workspace_info_ptr as *const $crate::bindings::miracle_workspace_t)
956 };
957
958 let name = if name_len > 0 {
959 let name_bytes = unsafe {
960 core::slice::from_raw_parts(name_ptr as *const u8, name_len as usize)
961 };
962 String::from_utf8_lossy(name_bytes).into_owned()
963 } else {
964 String::new()
965 };
966
967 let ws = unsafe { $crate::workspace::Workspace::from_c_with_name(c_ws, name) };
968 let prev = if has_previous != 0 { Some(previous_id as u64) } else { None };
969 plugin.workspace_focused(prev, &ws);
970 }
971
972 #[unsafe(no_mangle)]
973 pub extern "C" fn workspace_area_changed(
974 workspace_info_ptr: i32,
975 name_ptr: i32,
976 name_len: i32,
977 ) {
978 let plugin = unsafe {
979 match _MIRACLE_PLUGIN.as_mut() {
980 Some(p) => p,
981 None => return,
982 }
983 };
984
985 let c_ws = unsafe {
986 &*(workspace_info_ptr as *const $crate::bindings::miracle_workspace_t)
987 };
988
989 let name = if name_len > 0 {
990 let name_bytes = unsafe {
991 core::slice::from_raw_parts(name_ptr as *const u8, name_len as usize)
992 };
993 String::from_utf8_lossy(name_bytes).into_owned()
994 } else {
995 String::new()
996 };
997
998 let ws = unsafe { $crate::workspace::Workspace::from_c_with_name(c_ws, name) };
999 plugin.workspace_area_changed(&ws);
1000 }
1001
1002 #[unsafe(no_mangle)]
1003 pub extern "C" fn window_workspace_changed(
1004 window_info_ptr: i32,
1005 window_name_ptr: i32,
1006 window_name_len: i32,
1007 workspace_info_ptr: i32,
1008 workspace_name_ptr: i32,
1009 workspace_name_len: i32,
1010 ) {
1011 let plugin = unsafe {
1012 match _MIRACLE_PLUGIN.as_mut() {
1013 Some(p) => p,
1014 None => return,
1015 }
1016 };
1017
1018 let c_info = unsafe {
1019 &*(window_info_ptr as *const $crate::bindings::miracle_window_info_t)
1020 };
1021
1022 let window_name = if window_name_len > 0 {
1023 let name_bytes = unsafe {
1024 core::slice::from_raw_parts(window_name_ptr as *const u8, window_name_len as usize)
1025 };
1026 String::from_utf8_lossy(name_bytes).into_owned()
1027 } else {
1028 String::new()
1029 };
1030
1031 let info = unsafe { $crate::window::Window::from_c_with_name(c_info, window_name) };
1032
1033 let c_ws = unsafe {
1034 &*(workspace_info_ptr as *const $crate::bindings::miracle_workspace_t)
1035 };
1036
1037 let workspace_name = if workspace_name_len > 0 {
1038 let name_bytes = unsafe {
1039 core::slice::from_raw_parts(workspace_name_ptr as *const u8, workspace_name_len as usize)
1040 };
1041 String::from_utf8_lossy(name_bytes).into_owned()
1042 } else {
1043 String::new()
1044 };
1045
1046 let ws = unsafe { $crate::workspace::Workspace::from_c_with_name(c_ws, workspace_name) };
1047 plugin.window_workspace_changed(&info, &ws);
1048 }
1049
1050 #[unsafe(no_mangle)]
1051 pub extern "C" fn configure(buf_ptr: i32, buf_len: i32) -> i32 {
1052 let plugin = unsafe {
1053 match _MIRACLE_PLUGIN.as_mut() {
1054 Some(p) => p,
1055 None => return 0,
1056 }
1057 };
1058 $crate::__private::run_configure(plugin, buf_ptr, buf_len)
1059 }
1060
1061 #[unsafe(no_mangle)]
1062 pub extern "C" fn handle_keyboard_input(event_ptr: i32) -> i32 {
1063 let plugin = unsafe {
1064 match _MIRACLE_PLUGIN.as_mut() {
1065 Some(p) => p,
1066 None => return 0,
1067 }
1068 };
1069
1070 let c_event = unsafe {
1071 &*(event_ptr as *const $crate::bindings::miracle_keyboard_event_t)
1072 };
1073
1074 let event = $crate::input::KeyboardEvent {
1075 action: $crate::input::KeyboardAction::try_from(c_event.action)
1076 .unwrap_or_default(),
1077 keysym: c_event.keysym,
1078 scan_code: c_event.scan_code,
1079 modifiers: $crate::input::InputEventModifiers::from(c_event.modifiers),
1080 };
1081
1082 if plugin.handle_keyboard_input(event) { 1 } else { 0 }
1083 }
1084
1085 #[unsafe(no_mangle)]
1086 pub extern "C" fn handle_pointer_event(event_ptr: i32) -> i32 {
1087 let plugin = unsafe {
1088 match _MIRACLE_PLUGIN.as_mut() {
1089 Some(p) => p,
1090 None => return 0,
1091 }
1092 };
1093
1094 let c_event = unsafe {
1095 &*(event_ptr as *const $crate::bindings::miracle_pointer_event_t)
1096 };
1097
1098 let event = $crate::input::PointerEvent {
1099 x: c_event.x,
1100 y: c_event.y,
1101 action: $crate::input::PointerAction::try_from(c_event.action)
1102 .unwrap_or_default(),
1103 modifiers: $crate::input::InputEventModifiers::from(c_event.modifiers),
1104 buttons: $crate::input::PointerButtons::from(c_event.buttons),
1105 };
1106
1107 if plugin.handle_pointer_event(event) { 1 } else { 0 }
1108 }
1109
1110 #[unsafe(no_mangle)]
1111 pub extern "C" fn handle_plugin_command(buf_ptr: i32, in_len: i32, buf_cap: i32) -> i32 {
1112 let plugin = unsafe {
1113 match _MIRACLE_PLUGIN.as_mut() {
1114 Some(p) => p,
1115 None => return -1,
1116 }
1117 };
1118
1119 let payload = if in_len > 0 {
1121 let bytes = unsafe {
1122 core::slice::from_raw_parts(buf_ptr as *const u8, in_len as usize)
1123 };
1124 String::from_utf8_lossy(bytes).into_owned()
1125 } else {
1126 String::new()
1127 };
1128
1129 match plugin.handle_command(&payload) {
1130 Some(response) => {
1131 let bytes = response.as_bytes();
1132 if bytes.len() > buf_cap as usize {
1133 return -1;
1134 }
1135 unsafe {
1136 core::ptr::copy_nonoverlapping(
1137 bytes.as_ptr(),
1138 buf_ptr as *mut u8,
1139 bytes.len(),
1140 );
1141 }
1142 bytes.len() as i32
1143 }
1144 None => -1,
1145 }
1146 }
1147 };
1148}