Skip to main content

maa_framework/
callback.rs

1//! Internal callback infrastructure for FFI event handling.
2
3use crate::sys;
4use std::ffi::CStr;
5use std::os::raw::c_void;
6
7pub type EventCallbackFn = Box<dyn Fn(&str, &str) + Send + Sync>;
8
9unsafe extern "C" fn event_callback_trampoline(
10    _handle: *mut c_void,
11    msg: *const std::os::raw::c_char,
12    details: *const std::os::raw::c_char,
13    trans_arg: *mut c_void,
14) {
15    if trans_arg.is_null() {
16        return;
17    }
18    let callback = &*(trans_arg as *const EventCallbackFn);
19
20    let msg_str = if !msg.is_null() {
21        CStr::from_ptr(msg).to_string_lossy()
22    } else {
23        std::borrow::Cow::Borrowed("")
24    };
25
26    let details_str = if !details.is_null() {
27        CStr::from_ptr(details).to_string_lossy()
28    } else {
29        std::borrow::Cow::Borrowed("")
30    };
31
32    let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
33        callback(&msg_str, &details_str);
34    }));
35
36    if let Err(_) = result {
37        eprintln!("MaaFramework Rust Binding: Panic caught in event callback");
38    }
39}
40
41pub struct EventCallback {
42    _ptr: *mut c_void,
43}
44
45impl EventCallback {
46    pub fn new(
47        cb: impl Fn(&str, &str) + Send + Sync + 'static,
48    ) -> (sys::MaaEventCallback, *mut c_void) {
49        let boxed: EventCallbackFn = Box::new(cb);
50        let ptr = Box::into_raw(Box::new(boxed)) as *mut c_void;
51        (Some(event_callback_trampoline), ptr)
52    }
53
54    pub unsafe fn drop_callback(ptr: *mut c_void) {
55        if !ptr.is_null() {
56            let _ = Box::from_raw(ptr as *mut EventCallbackFn);
57        }
58    }
59
60    pub fn new_sink(
61        handle: crate::common::MaaId,
62        sink: Box<dyn crate::event_sink::EventSink>,
63    ) -> (sys::MaaEventCallback, *mut c_void) {
64        let wrapper = Box::new(EventSinkWrapper { handle, sink });
65        let ptr = Box::into_raw(wrapper) as *mut c_void;
66        (Some(event_sink_trampoline), ptr)
67    }
68
69    pub unsafe fn drop_sink(ptr: *mut c_void) {
70        if !ptr.is_null() {
71            let _ = Box::from_raw(ptr as *mut EventSinkWrapper);
72        }
73    }
74}
75
76/// Wrapper to hold the event sink and its associated instance handle.
77///
78/// This struct is boxed and converted to a raw pointer to be passed as the `callback_arg`
79/// to the C API. It ensures the `handle` is available when the callback is invoked.
80struct EventSinkWrapper {
81    handle: crate::common::MaaId,
82    sink: Box<dyn crate::event_sink::EventSink>,
83}
84
85/// Trampoline function for safe FFI callback dispatch.
86///
87/// This function acts as the bridge between the C callback interface and the Rust `EventSink` trait.
88/// It:
89/// 1. Casts the `callback_arg` back to `EventSinkWrapper`.
90/// 2. Converts C-style message strings to Rust `Cow<str>`.
91/// 3. Parses the message and details into a typed `MaaEvent`.
92/// 4. Dispatches the event to the user's sink implementation with the correct handle.
93unsafe extern "C" fn event_sink_trampoline(
94    _handle: *mut c_void,
95    _msg: *const std::os::raw::c_char,
96    _detail: *const std::os::raw::c_char,
97    callback_arg: *mut c_void,
98) {
99    if callback_arg.is_null() {
100        return;
101    }
102    let wrapper = &*(callback_arg as *const EventSinkWrapper);
103
104    let msg_str = if !_msg.is_null() {
105        CStr::from_ptr(_msg).to_string_lossy()
106    } else {
107        std::borrow::Cow::Borrowed("")
108    };
109
110    let detail_str = if !_detail.is_null() {
111        CStr::from_ptr(_detail).to_string_lossy()
112    } else {
113        std::borrow::Cow::Borrowed("")
114    };
115
116    let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
117        let event = crate::notification::MaaEvent::from_json(&msg_str, &detail_str);
118        wrapper.sink.on_event(wrapper.handle, &event);
119    }));
120
121    if let Err(_) = result {
122        eprintln!("MaaFramework Rust Binding: Panic caught in event sink callback");
123    }
124}