1use crate::context::Context;
13use crate::resource::Resource;
14use crate::{common, sys, MaaError, MaaResult};
15use std::ffi::{CStr, CString};
16use std::os::raw::c_void;
17
18pub trait CustomAction: Send + Sync {
25 fn run(
39 &self,
40 context: &Context,
41 task_id: common::MaaId,
42 node_name: &str,
43 custom_action_name: &str,
44 custom_action_param: &str,
45 reco_id: common::MaaId,
46 box_rect: &common::Rect,
47 ) -> bool;
48}
49
50pub trait CustomRecognition: Send + Sync {
54 fn analyze(
68 &self,
69 context: &Context,
70 task_id: common::MaaId,
71 node_name: &str,
72 custom_recognition_name: &str,
73 custom_recognition_param: &str,
74 image: &crate::buffer::MaaImageBuffer,
75 roi: &common::Rect,
76 ) -> Option<(common::Rect, String)>;
77}
78
79pub struct FnRecognition<F>
85where
86 F: Fn(&Context, &RecognitionArgs) -> Option<(common::Rect, String)> + Send + Sync,
87{
88 func: F,
89}
90
91pub struct RecognitionArgs<'a> {
93 pub task_id: common::MaaId,
94 pub node_name: &'a str,
95 pub name: &'a str,
96 pub param: &'a str,
97 pub image: &'a crate::buffer::MaaImageBuffer,
98 pub roi: &'a common::Rect,
99}
100
101impl<F> FnRecognition<F>
102where
103 F: Fn(&Context, &RecognitionArgs) -> Option<(common::Rect, String)> + Send + Sync,
104{
105 pub fn new(func: F) -> Self {
106 Self { func }
107 }
108}
109
110impl<F> CustomRecognition for FnRecognition<F>
111where
112 F: Fn(&Context, &RecognitionArgs) -> Option<(common::Rect, String)> + Send + Sync,
113{
114 fn analyze(
115 &self,
116 context: &Context,
117 task_id: common::MaaId,
118 node_name: &str,
119 custom_recognition_name: &str,
120 custom_recognition_param: &str,
121 image: &crate::buffer::MaaImageBuffer,
122 roi: &common::Rect,
123 ) -> Option<(common::Rect, String)> {
124 let args = RecognitionArgs {
125 task_id,
126 node_name,
127 name: custom_recognition_name,
128 param: custom_recognition_param,
129 image,
130 roi,
131 };
132 (self.func)(context, &args)
133 }
134}
135
136pub struct FnAction<F>
140where
141 F: Fn(&Context, &ActionArgs) -> bool + Send + Sync,
142{
143 func: F,
144}
145
146pub struct ActionArgs<'a> {
148 pub task_id: common::MaaId,
149 pub node_name: &'a str,
150 pub name: &'a str,
151 pub param: &'a str,
152 pub reco_id: common::MaaId,
153 pub box_rect: &'a common::Rect,
154}
155
156impl<F> FnAction<F>
157where
158 F: Fn(&Context, &ActionArgs) -> bool + Send + Sync,
159{
160 pub fn new(func: F) -> Self {
161 Self { func }
162 }
163}
164
165impl<F> CustomAction for FnAction<F>
166where
167 F: Fn(&Context, &ActionArgs) -> bool + Send + Sync,
168{
169 fn run(
170 &self,
171 context: &Context,
172 task_id: common::MaaId,
173 node_name: &str,
174 custom_action_name: &str,
175 custom_action_param: &str,
176 reco_id: common::MaaId,
177 box_rect: &common::Rect,
178 ) -> bool {
179 let args = ActionArgs {
180 task_id,
181 node_name,
182 name: custom_action_name,
183 param: custom_action_param,
184 reco_id,
185 box_rect,
186 };
187 (self.func)(context, &args)
188 }
189}
190
191pub(crate) unsafe extern "C" fn custom_action_trampoline(
194 context: *mut sys::MaaContext,
195 task_id: sys::MaaTaskId,
196 node_name: *const std::os::raw::c_char,
197 custom_action_name: *const std::os::raw::c_char,
198 custom_action_param: *const std::os::raw::c_char,
199 reco_id: sys::MaaRecoId,
200 box_: *const sys::MaaRect,
201 trans_arg: *mut std::os::raw::c_void,
202) -> sys::MaaBool {
203 if trans_arg.is_null() {
204 return 0; }
206
207 let action = &*(trans_arg as *mut Box<dyn CustomAction>);
208
209 let ctx = match Context::from_raw(context) {
210 Some(c) => c,
211 None => return 0,
212 };
213
214 let get_str = |ptr: *const std::os::raw::c_char| -> &str {
215 if ptr.is_null() {
216 ""
217 } else {
218 CStr::from_ptr(ptr).to_str().unwrap_or("")
219 }
220 };
221
222 let node = get_str(node_name);
223 let name = get_str(custom_action_name);
224 let param = get_str(custom_action_param);
225
226 let rect = if !box_.is_null() {
227 crate::buffer::MaaRectBuffer::from_handle(box_ as *mut sys::MaaRect)
228 .map(|buf| buf.get())
229 .unwrap_or(common::Rect::default())
230 } else {
231 common::Rect::default()
232 };
233
234 let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
235 action.run(&ctx, task_id, node, name, param, reco_id, &rect)
236 }));
237
238 match result {
239 Ok(true) => 1,
240 Ok(false) => 0,
241 Err(_) => {
242 eprintln!("MaaFramework Rust Binding: Panic caught in custom action callback");
243 0
244 }
245 }
246}
247
248pub(crate) unsafe extern "C" fn custom_recognition_trampoline(
249 context: *mut sys::MaaContext,
250 task_id: sys::MaaTaskId,
251 node_name: *const std::os::raw::c_char,
252 custom_recognition_name: *const std::os::raw::c_char,
253 custom_recognition_param: *const std::os::raw::c_char,
254 image: *const sys::MaaImageBuffer,
255 roi: *const sys::MaaRect,
256 trans_arg: *mut std::os::raw::c_void,
257 out_box: *mut sys::MaaRect,
258 out_detail: *mut sys::MaaStringBuffer,
259) -> sys::MaaBool {
260 if trans_arg.is_null() {
261 return 0;
262 }
263 let reco = &*(trans_arg as *mut Box<dyn CustomRecognition>);
264
265 let ctx = match Context::from_raw(context) {
266 Some(c) => c,
267 None => return 0,
268 };
269
270 let get_str = |ptr: *const std::os::raw::c_char| -> &str {
271 if ptr.is_null() {
272 ""
273 } else {
274 CStr::from_ptr(ptr).to_str().unwrap_or("")
275 }
276 };
277
278 let node = get_str(node_name);
279 let name = get_str(custom_recognition_name);
280 let param = get_str(custom_recognition_param);
281
282 let img_buf = crate::buffer::MaaImageBuffer::from_handle(image as *mut sys::MaaImageBuffer);
285 if img_buf.is_none() {
286 return 0;
287 }
288 let img_buf = img_buf.unwrap();
289
290 let roi_rect = if !roi.is_null() {
292 crate::buffer::MaaRectBuffer::from_handle(roi as *mut sys::MaaRect)
293 .map(|buf| buf.get())
294 .unwrap_or(common::Rect::default())
295 } else {
296 common::Rect::default()
297 };
298
299 let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
300 reco.analyze(&ctx, task_id, node, name, param, &img_buf, &roi_rect)
301 }));
302
303 match result {
304 Ok(Some((res_rect, res_detail))) => {
305 if !out_box.is_null() {
306 if let Some(mut out_rect_buf) = crate::buffer::MaaRectBuffer::from_handle(out_box) {
307 let _ = out_rect_buf.set(&res_rect);
308 }
309 }
310 if !out_detail.is_null() {
311 if let Some(mut out_str_buf) =
312 crate::buffer::MaaStringBuffer::from_handle(out_detail)
313 {
314 let _ = out_str_buf.set(&res_detail);
315 }
316 }
317 1
318 }
319 Ok(None) => 0,
320 Err(_) => {
321 eprintln!("MaaFramework Rust Binding: Panic caught in custom recognition callback");
322 0
323 }
324 }
325}
326
327impl Resource {
330 pub fn register_custom_action(
335 &self,
336 name: &str,
337 action: Box<dyn CustomAction>,
338 ) -> MaaResult<()> {
339 let c_name = CString::new(name)?;
340 let action_ptr = Box::into_raw(Box::new(action));
341 let action_ptr_void = action_ptr as *mut c_void;
342
343 unsafe {
344 let ret = sys::MaaResourceRegisterCustomAction(
345 self.raw(),
346 c_name.as_ptr(),
347 Some(custom_action_trampoline),
348 action_ptr_void,
349 );
350 if ret == 0 {
351 let _ = Box::from_raw(action_ptr);
352 return Err(MaaError::FrameworkError(0));
353 }
354 }
355
356 let mut map = self.custom_actions().lock().unwrap();
357 if let Some(old_ptr) = map.insert(name.to_string(), action_ptr as usize) {
358 unsafe {
359 let _ = Box::from_raw(old_ptr as *mut Box<dyn CustomAction>);
360 }
361 }
362
363 Ok(())
364 }
365
366 pub fn register_custom_recognition(
371 &self,
372 name: &str,
373 reco: Box<dyn CustomRecognition>,
374 ) -> MaaResult<()> {
375 let c_name = CString::new(name)?;
376 let reco_ptr = Box::into_raw(Box::new(reco));
377 let reco_ptr_void = reco_ptr as *mut c_void;
378
379 unsafe {
380 let ret = sys::MaaResourceRegisterCustomRecognition(
381 self.raw(),
382 c_name.as_ptr(),
383 Some(custom_recognition_trampoline),
384 reco_ptr_void,
385 );
386 if ret == 0 {
387 let _ = Box::from_raw(reco_ptr);
388 return Err(MaaError::FrameworkError(0));
389 }
390 }
391
392 let mut map = self.custom_recognitions().lock().unwrap();
393 if let Some(old_ptr) = map.insert(name.to_string(), reco_ptr as usize) {
394 unsafe {
395 let _ = Box::from_raw(old_ptr as *mut Box<dyn CustomRecognition>);
396 }
397 }
398
399 Ok(())
400 }
401}