Skip to main content

maa_framework/
resource.rs

1//! Resource management for pipelines, models, and images.
2
3use std::ffi::CString;
4use std::ptr::NonNull;
5
6use crate::{MaaError, MaaResult, common, sys};
7
8/// Resource manager.
9///
10/// Handles loading and management of:
11/// - Image resources
12/// - OCR models
13/// - Task pipelines
14/// - Custom recognizers and actions
15use std::sync::Arc;
16
17struct ResourceInner {
18    handle: NonNull<sys::MaaResource>,
19    owns_handle: bool,
20    custom_actions: std::sync::Mutex<std::collections::HashMap<String, usize>>, // Store pointer address
21    custom_recognitions: std::sync::Mutex<std::collections::HashMap<String, usize>>,
22    callbacks: std::sync::Mutex<std::collections::HashMap<sys::MaaSinkId, usize>>,
23    event_sinks: std::sync::Mutex<std::collections::HashMap<sys::MaaSinkId, usize>>,
24}
25
26unsafe impl Send for ResourceInner {}
27unsafe impl Sync for ResourceInner {}
28
29#[derive(Clone)]
30pub struct Resource {
31    inner: Arc<ResourceInner>,
32}
33
34unsafe impl Send for Resource {}
35unsafe impl Sync for Resource {}
36
37impl Resource {
38    /// Create a new resource manager.
39    pub fn new() -> MaaResult<Self> {
40        let handle = unsafe { sys::MaaResourceCreate() };
41        if let Some(ptr) = NonNull::new(handle) {
42            Ok(Self {
43                inner: Arc::new(ResourceInner {
44                    handle: ptr,
45                    owns_handle: true,
46                    custom_actions: std::sync::Mutex::new(std::collections::HashMap::new()),
47                    custom_recognitions: std::sync::Mutex::new(std::collections::HashMap::new()),
48                    callbacks: std::sync::Mutex::new(std::collections::HashMap::new()),
49                    event_sinks: std::sync::Mutex::new(std::collections::HashMap::new()),
50                }),
51            })
52        } else {
53            Err(MaaError::NullPointer)
54        }
55    }
56
57    /// Create a Resource from a raw pointer, taking ownership.
58    ///
59    /// # Safety
60    /// The pointer must be valid and the caller transfers ownership to the Resource.
61    /// The Resource will call `MaaResourceDestroy` when dropped.
62    pub unsafe fn from_raw(ptr: *mut sys::MaaResource) -> MaaResult<Self> {
63        if let Some(handle) = NonNull::new(ptr) {
64            Ok(Self {
65                inner: Arc::new(ResourceInner {
66                    handle,
67                    owns_handle: true,
68                    custom_actions: std::sync::Mutex::new(std::collections::HashMap::new()),
69                    custom_recognitions: std::sync::Mutex::new(std::collections::HashMap::new()),
70                    callbacks: std::sync::Mutex::new(std::collections::HashMap::new()),
71                    event_sinks: std::sync::Mutex::new(std::collections::HashMap::new()),
72                }),
73            })
74        } else {
75            Err(MaaError::NullPointer)
76        }
77    }
78
79    /// Load a resource bundle from the specified directory.
80    ///
81    /// The bundle should contain pipeline definitions, images, and models.
82    pub fn post_bundle(&self, path: &str) -> MaaResult<crate::job::ResJob> {
83        let c_path = CString::new(path)?;
84        let id = unsafe { sys::MaaResourcePostBundle(self.inner.handle.as_ptr(), c_path.as_ptr()) };
85        Ok(crate::job::Job::for_resource(self, id))
86    }
87
88    /// Check if resources have been loaded.
89    pub fn loaded(&self) -> bool {
90        unsafe { sys::MaaResourceLoaded(self.inner.handle.as_ptr()) != 0 }
91    }
92
93    /// Clear all loaded resources.
94    pub fn clear(&self) -> MaaResult<()> {
95        let ret = unsafe { sys::MaaResourceClear(self.inner.handle.as_ptr()) };
96        common::check_bool(ret)
97    }
98
99    /// Get the status of a loading operation.
100    pub fn status(&self, id: common::MaaId) -> common::MaaStatus {
101        let status = unsafe { sys::MaaResourceStatus(self.inner.handle.as_ptr(), id) };
102        common::MaaStatus(status)
103    }
104
105    /// Wait for a loading operation to complete.
106    pub fn wait(&self, id: common::MaaId) -> common::MaaStatus {
107        let status = unsafe { sys::MaaResourceWait(self.inner.handle.as_ptr(), id) };
108        common::MaaStatus(status)
109    }
110
111    /// Get the raw resource handle.
112    pub fn raw(&self) -> *mut sys::MaaResource {
113        self.inner.handle.as_ptr()
114    }
115
116    // === Additional resource loading ===
117
118    /// Load an OCR model from the specified directory.
119    pub fn post_ocr_model(&self, path: &str) -> MaaResult<crate::job::ResJob> {
120        let c_path = CString::new(path)?;
121        let id =
122            unsafe { sys::MaaResourcePostOcrModel(self.inner.handle.as_ptr(), c_path.as_ptr()) };
123        Ok(crate::job::Job::for_resource(self, id))
124    }
125
126    /// Load additional pipeline definitions from the specified directory.
127    pub fn post_pipeline(&self, path: &str) -> MaaResult<crate::job::ResJob> {
128        let c_path = CString::new(path)?;
129        let id =
130            unsafe { sys::MaaResourcePostPipeline(self.inner.handle.as_ptr(), c_path.as_ptr()) };
131        Ok(crate::job::Job::for_resource(self, id))
132    }
133
134    /// Load image resources from the specified directory.
135    pub fn post_image(&self, path: &str) -> MaaResult<crate::job::ResJob> {
136        let c_path = CString::new(path)?;
137        let id = unsafe { sys::MaaResourcePostImage(self.inner.handle.as_ptr(), c_path.as_ptr()) };
138        Ok(crate::job::Job::for_resource(self, id))
139    }
140
141    // === Pipeline operations ===
142
143    /// Override pipeline parameters with a JSON string.
144    pub fn override_pipeline(&self, pipeline_override: &str) -> MaaResult<()> {
145        let c_json = CString::new(pipeline_override)?;
146        let ret = unsafe {
147            sys::MaaResourceOverridePipeline(self.inner.handle.as_ptr(), c_json.as_ptr())
148        };
149        common::check_bool(ret)
150    }
151
152    /// Override pipeline with JSON value.
153    ///
154    /// Convenience method that accepts `serde_json::Value` for pipeline overrides.
155    ///
156    /// # Example
157    /// ```ignore
158    /// use serde_json::json;
159    /// resource.override_pipeline_json(&json!({
160    ///     "MyNode": { "enabled": false }
161    /// }))?;
162    /// ```
163    pub fn override_pipeline_json(&self, pipeline_override: &serde_json::Value) -> MaaResult<()> {
164        self.override_pipeline(&pipeline_override.to_string())
165    }
166
167    /// Override the next node list for a specific node.
168    ///
169    /// # Arguments
170    /// * `node_name` - The name of the node to modify
171    /// * `next_list` - The new list of next nodes
172    pub fn override_next(&self, node_name: &str, next_list: &[&str]) -> MaaResult<()> {
173        let c_name = CString::new(node_name)?;
174        let list_buf = crate::buffer::MaaStringListBuffer::new()?;
175        for item in next_list {
176            list_buf.append(item)?;
177        }
178        let ret = unsafe {
179            sys::MaaResourceOverrideNext(
180                self.inner.handle.as_ptr(),
181                c_name.as_ptr(),
182                list_buf.raw(),
183            )
184        };
185        common::check_bool(ret)
186    }
187
188    /// Get node data as a JSON string.
189    pub fn get_node_data(&self, node_name: &str) -> MaaResult<Option<String>> {
190        let c_name = CString::new(node_name)?;
191        let buffer = crate::buffer::MaaStringBuffer::new()?;
192        let ret = unsafe {
193            sys::MaaResourceGetNodeData(self.inner.handle.as_ptr(), c_name.as_ptr(), buffer.raw())
194        };
195        if ret != 0 {
196            Ok(Some(buffer.to_string()))
197        } else {
198            Ok(None)
199        }
200    }
201
202    /// Get node data as a deserialized `PipelineData` object.
203    pub fn get_node_object(
204        &self,
205        node_name: &str,
206    ) -> MaaResult<Option<crate::pipeline::PipelineData>> {
207        if let Some(json_str) = self.get_node_data(node_name)? {
208            let data: crate::pipeline::PipelineData =
209                serde_json::from_str(&json_str).map_err(|e| {
210                    MaaError::InvalidConfig(format!("Failed to parse pipeline data: {}", e))
211                })?;
212            Ok(Some(data))
213        } else {
214            Ok(None)
215        }
216    }
217
218    /// Get a list of all node names in the loaded pipelines.
219    pub fn node_list(&self) -> MaaResult<Vec<String>> {
220        let buffer = crate::buffer::MaaStringListBuffer::new()?;
221        let ret = unsafe { sys::MaaResourceGetNodeList(self.inner.handle.as_ptr(), buffer.raw()) };
222        if ret != 0 {
223            Ok(buffer.to_vec())
224        } else {
225            Err(MaaError::FrameworkError(0))
226        }
227    }
228
229    /// Get a hash of the loaded resources for cache validation.
230    pub fn hash(&self) -> MaaResult<String> {
231        let buffer = crate::buffer::MaaStringBuffer::new()?;
232        let ret = unsafe { sys::MaaResourceGetHash(self.inner.handle.as_ptr(), buffer.raw()) };
233        if ret != 0 {
234            Ok(buffer.to_string())
235        } else {
236            Err(MaaError::FrameworkError(0))
237        }
238    }
239
240    /// Get valid default parameters for a recognition type as JSON.
241    ///
242    /// # Arguments
243    /// * `reco_type` - The recognition type (e.g. "TemplateMatch", "OCR").
244    pub fn get_default_recognition_param(
245        &self,
246        reco_type: &str,
247    ) -> MaaResult<Option<serde_json::Value>> {
248        let c_type = CString::new(reco_type)?;
249        let buffer = crate::buffer::MaaStringBuffer::new()?;
250        let ret = unsafe {
251            sys::MaaResourceGetDefaultRecognitionParam(
252                self.inner.handle.as_ptr(),
253                c_type.as_ptr(),
254                buffer.raw(),
255            )
256        };
257        if ret != 0 {
258            let json_str = buffer.to_string();
259            let val: serde_json::Value = serde_json::from_str(&json_str).map_err(|e| {
260                MaaError::InvalidConfig(format!("Failed to parse default params: {}", e))
261            })?;
262            Ok(Some(val))
263        } else {
264            Ok(None)
265        }
266    }
267
268    /// Get valid default parameters for an action type as JSON.
269    ///
270    /// # Arguments
271    /// * `action_type` - The action type (e.g. "Click", "Swipe").
272    pub fn get_default_action_param(
273        &self,
274        action_type: &str,
275    ) -> MaaResult<Option<serde_json::Value>> {
276        let c_type = CString::new(action_type)?;
277        let buffer = crate::buffer::MaaStringBuffer::new()?;
278        let ret = unsafe {
279            sys::MaaResourceGetDefaultActionParam(
280                self.inner.handle.as_ptr(),
281                c_type.as_ptr(),
282                buffer.raw(),
283            )
284        };
285        if ret != 0 {
286            let json_str = buffer.to_string();
287            let val: serde_json::Value = serde_json::from_str(&json_str).map_err(|e| {
288                MaaError::InvalidConfig(format!("Failed to parse default params: {}", e))
289            })?;
290            Ok(Some(val))
291        } else {
292            Ok(None)
293        }
294    }
295
296    // === Inference device ===
297
298    /// Use CPU for inference (default).
299    pub fn use_cpu(&self) -> MaaResult<()> {
300        let mut ep: i32 =
301            sys::MaaInferenceExecutionProviderEnum_MaaInferenceExecutionProvider_CPU as i32;
302        let ret1 = unsafe {
303            sys::MaaResourceSetOption(
304                self.inner.handle.as_ptr(),
305                sys::MaaResOptionEnum_MaaResOption_InferenceExecutionProvider as i32,
306                &mut ep as *mut _ as *mut std::ffi::c_void,
307                std::mem::size_of::<i32>() as u64,
308            )
309        };
310        common::check_bool(ret1)?;
311
312        let mut device: i32 = sys::MaaInferenceDeviceEnum_MaaInferenceDevice_CPU as i32;
313        let ret2 = unsafe {
314            sys::MaaResourceSetOption(
315                self.inner.handle.as_ptr(),
316                sys::MaaResOptionEnum_MaaResOption_InferenceDevice as i32,
317                &mut device as *mut _ as *mut std::ffi::c_void,
318                std::mem::size_of::<i32>() as u64,
319            )
320        };
321        common::check_bool(ret2)
322    }
323
324    /// Use DirectML for GPU-accelerated inference (Windows only).
325    ///
326    /// # Arguments
327    /// * `device_id` - GPU device index (0 for first GPU)
328    pub fn use_directml(&self, device_id: i32) -> MaaResult<()> {
329        let mut ep: i32 =
330            sys::MaaInferenceExecutionProviderEnum_MaaInferenceExecutionProvider_DirectML as i32;
331        let ret1 = unsafe {
332            sys::MaaResourceSetOption(
333                self.inner.handle.as_ptr(),
334                sys::MaaResOptionEnum_MaaResOption_InferenceExecutionProvider as i32,
335                &mut ep as *mut _ as *mut std::ffi::c_void,
336                std::mem::size_of::<i32>() as u64,
337            )
338        };
339        common::check_bool(ret1)?;
340
341        let mut device = device_id;
342        let ret2 = unsafe {
343            sys::MaaResourceSetOption(
344                self.inner.handle.as_ptr(),
345                sys::MaaResOptionEnum_MaaResOption_InferenceDevice as i32,
346                &mut device as *mut _ as *mut std::ffi::c_void,
347                std::mem::size_of::<i32>() as u64,
348            )
349        };
350        common::check_bool(ret2)
351    }
352
353    /// Use WebGPU for GPU-accelerated inference (Windows, Linux and macOS).
354    ///
355    /// # Arguments
356    /// * `device_id` - WebGPU device id (0 for the first device)
357    pub fn use_webgpu(&self, device_id: i32) -> MaaResult<()> {
358        let mut ep: i32 =
359            sys::MaaInferenceExecutionProviderEnum_MaaInferenceExecutionProvider_WebGPU as i32;
360        let ret1 = unsafe {
361            sys::MaaResourceSetOption(
362                self.inner.handle.as_ptr(),
363                sys::MaaResOptionEnum_MaaResOption_InferenceExecutionProvider as i32,
364                &mut ep as *mut _ as *mut std::ffi::c_void,
365                std::mem::size_of::<i32>() as u64,
366            )
367        };
368        common::check_bool(ret1)?;
369
370        let mut device = device_id;
371        let ret2 = unsafe {
372            sys::MaaResourceSetOption(
373                self.inner.handle.as_ptr(),
374                sys::MaaResOptionEnum_MaaResOption_InferenceDevice as i32,
375                &mut device as *mut _ as *mut std::ffi::c_void,
376                std::mem::size_of::<i32>() as u64,
377            )
378        };
379        common::check_bool(ret2)
380    }
381
382    /// Use NCNN for inference (Android builds only, CPU only).
383    ///
384    /// Fails on builds without NCNN, and when switching to or from NCNN after
385    /// resource loading has started (until [`clear`](Self::clear)).
386    /// Models are loaded from `<model stem>.ncnn.param/.ncnn.bin/.ncnn.json`
387    /// with no fallback to onnxruntime.
388    pub fn use_ncnn(&self) -> MaaResult<()> {
389        let mut ep: i32 =
390            sys::MaaInferenceExecutionProviderEnum_MaaInferenceExecutionProvider_NCNN as i32;
391        let ret1 = unsafe {
392            sys::MaaResourceSetOption(
393                self.inner.handle.as_ptr(),
394                sys::MaaResOptionEnum_MaaResOption_InferenceExecutionProvider as i32,
395                &mut ep as *mut _ as *mut std::ffi::c_void,
396                std::mem::size_of::<i32>() as u64,
397            )
398        };
399        common::check_bool(ret1)?;
400
401        let mut device: i32 = sys::MaaInferenceDeviceEnum_MaaInferenceDevice_CPU as i32;
402        let ret2 = unsafe {
403            sys::MaaResourceSetOption(
404                self.inner.handle.as_ptr(),
405                sys::MaaResOptionEnum_MaaResOption_InferenceDevice as i32,
406                &mut device as *mut _ as *mut std::ffi::c_void,
407                std::mem::size_of::<i32>() as u64,
408            )
409        };
410        common::check_bool(ret2)
411    }
412
413    // === EventSink ===
414
415    /// Register a raw callback to receive events.
416    ///
417    /// The callback receives event type and details as JSON strings.
418    pub fn add_sink<F>(&self, callback: F) -> MaaResult<sys::MaaSinkId>
419    where
420        F: Fn(&str, &str) + Send + Sync + 'static,
421    {
422        let (cb_fn, cb_arg) = crate::callback::EventCallback::new(callback);
423        let sink_id = unsafe { sys::MaaResourceAddSink(self.inner.handle.as_ptr(), cb_fn, cb_arg) };
424        if sink_id != 0 {
425            self.inner
426                .callbacks
427                .lock()
428                .unwrap()
429                .insert(sink_id, cb_arg as usize);
430            Ok(sink_id)
431        } else {
432            unsafe { crate::callback::EventCallback::drop_callback(cb_arg) };
433            Err(MaaError::FrameworkError(0))
434        }
435    }
436
437    /// Register a strongly-typed event sink.
438    ///
439    /// This method registers an implementation of the [`EventSink`](crate::event_sink::EventSink) trait
440    /// to receive structured notifications from this resource.
441    ///
442    /// # Arguments
443    /// * `sink` - The event sink implementation (must be boxed).
444    ///
445    /// # Returns
446    /// A `MaaSinkId` which can be used to manually remove the sink later via [`remove_sink`](Self::remove_sink).
447    /// The sink will be automatically unregistered and dropped when the `Resource` is dropped.
448    pub fn add_event_sink(
449        &self,
450        sink: Box<dyn crate::event_sink::EventSink>,
451    ) -> MaaResult<sys::MaaSinkId> {
452        let handle_id = self.inner.handle.as_ptr() as crate::common::MaaId;
453        let (cb, arg) = crate::callback::EventCallback::new_sink(handle_id, sink);
454        let id = unsafe { sys::MaaResourceAddSink(self.inner.handle.as_ptr(), cb, arg) };
455        if id > 0 {
456            self.inner
457                .event_sinks
458                .lock()
459                .unwrap()
460                .insert(id, arg as usize);
461            Ok(id)
462        } else {
463            unsafe { crate::callback::EventCallback::drop_sink(arg) };
464            Err(MaaError::FrameworkError(0))
465        }
466    }
467
468    /// Remove a previously registered event sink.
469    pub fn remove_sink(&self, sink_id: sys::MaaSinkId) {
470        unsafe { sys::MaaResourceRemoveSink(self.inner.handle.as_ptr(), sink_id) }
471        if let Some(ptr) = self.inner.callbacks.lock().unwrap().remove(&sink_id) {
472            unsafe { crate::callback::EventCallback::drop_callback(ptr as *mut std::ffi::c_void) };
473        } else if let Some(ptr) = self.inner.event_sinks.lock().unwrap().remove(&sink_id) {
474            unsafe { crate::callback::EventCallback::drop_sink(ptr as *mut std::ffi::c_void) };
475        }
476    }
477
478    /// Remove all registered event sinks.
479    pub fn clear_sinks(&self) {
480        unsafe { sys::MaaResourceClearSinks(self.inner.handle.as_ptr()) }
481        let mut callbacks = self.inner.callbacks.lock().unwrap();
482        for (_, ptr) in callbacks.drain() {
483            unsafe { crate::callback::EventCallback::drop_callback(ptr as *mut std::ffi::c_void) };
484        }
485        let mut event_sinks = self.inner.event_sinks.lock().unwrap();
486        for (_, ptr) in event_sinks.drain() {
487            unsafe { crate::callback::EventCallback::drop_sink(ptr as *mut std::ffi::c_void) };
488        }
489    }
490
491    // === Image override ===
492
493    /// Override an image resource at runtime.
494    ///
495    /// # Arguments
496    /// * `image_name` - The name of the image to override
497    /// * `image` - The new image buffer to use
498    pub fn override_image(
499        &self,
500        image_name: &str,
501        image: &crate::buffer::MaaImageBuffer,
502    ) -> MaaResult<()> {
503        let c_name = CString::new(image_name)?;
504        let ret = unsafe {
505            sys::MaaResourceOverrideImage(self.inner.handle.as_ptr(), c_name.as_ptr(), image.raw())
506        };
507        common::check_bool(ret)
508    }
509
510    // === Inference device (extended) ===
511
512    /// Auto-select the best inference execution provider.
513    ///
514    /// Tries CUDA, then DirectML, then WebGPU, then CoreML, and falls back to CPU.
515    pub fn use_auto_ep(&self) -> MaaResult<()> {
516        let mut ep: i32 =
517            sys::MaaInferenceExecutionProviderEnum_MaaInferenceExecutionProvider_Auto as i32;
518        let ret1 = unsafe {
519            sys::MaaResourceSetOption(
520                self.inner.handle.as_ptr(),
521                sys::MaaResOptionEnum_MaaResOption_InferenceExecutionProvider as i32,
522                &mut ep as *mut _ as *mut std::ffi::c_void,
523                std::mem::size_of::<i32>() as u64,
524            )
525        };
526        common::check_bool(ret1)?;
527
528        let mut device: i32 = sys::MaaInferenceDeviceEnum_MaaInferenceDevice_Auto as i32;
529        let ret2 = unsafe {
530            sys::MaaResourceSetOption(
531                self.inner.handle.as_ptr(),
532                sys::MaaResOptionEnum_MaaResOption_InferenceDevice as i32,
533                &mut device as *mut _ as *mut std::ffi::c_void,
534                std::mem::size_of::<i32>() as u64,
535            )
536        };
537        common::check_bool(ret2)
538    }
539
540    /// Use CoreML for inference (macOS only).
541    ///
542    /// MaaDeps no longer ships the CoreML execution provider since MaaFramework 5.14.3.
543    ///
544    /// # Arguments
545    /// * `coreml_flag` - CoreML configuration flag
546    #[deprecated(note = "MaaDeps no longer ships CoreML; use `use_webgpu` instead")]
547    pub fn use_coreml(&self, coreml_flag: i32) -> MaaResult<()> {
548        let mut ep: i32 =
549            sys::MaaInferenceExecutionProviderEnum_MaaInferenceExecutionProvider_CoreML as i32;
550        let ret1 = unsafe {
551            sys::MaaResourceSetOption(
552                self.inner.handle.as_ptr(),
553                sys::MaaResOptionEnum_MaaResOption_InferenceExecutionProvider as i32,
554                &mut ep as *mut _ as *mut std::ffi::c_void,
555                std::mem::size_of::<i32>() as u64,
556            )
557        };
558        common::check_bool(ret1)?;
559
560        let mut device = coreml_flag;
561        let ret2 = unsafe {
562            sys::MaaResourceSetOption(
563                self.inner.handle.as_ptr(),
564                sys::MaaResOptionEnum_MaaResOption_InferenceDevice as i32,
565                &mut device as *mut _ as *mut std::ffi::c_void,
566                std::mem::size_of::<i32>() as u64,
567            )
568        };
569        common::check_bool(ret2)
570    }
571
572    /// Use CUDA for inference (NVIDIA GPU only).
573    ///
574    /// # Arguments
575    /// * `nvidia_gpu_id` - NVIDIA GPU device ID (typically 0 for first GPU)
576    pub fn use_cuda(&self, nvidia_gpu_id: i32) -> MaaResult<()> {
577        let mut ep: i32 =
578            sys::MaaInferenceExecutionProviderEnum_MaaInferenceExecutionProvider_CUDA as i32;
579        let ret1 = unsafe {
580            sys::MaaResourceSetOption(
581                self.inner.handle.as_ptr(),
582                sys::MaaResOptionEnum_MaaResOption_InferenceExecutionProvider as i32,
583                &mut ep as *mut _ as *mut std::ffi::c_void,
584                std::mem::size_of::<i32>() as u64,
585            )
586        };
587        common::check_bool(ret1)?;
588
589        let mut device = nvidia_gpu_id;
590        let ret2 = unsafe {
591            sys::MaaResourceSetOption(
592                self.inner.handle.as_ptr(),
593                sys::MaaResOptionEnum_MaaResOption_InferenceDevice as i32,
594                &mut device as *mut _ as *mut std::ffi::c_void,
595                std::mem::size_of::<i32>() as u64,
596            )
597        };
598        common::check_bool(ret2)
599    }
600
601    // === Custom component management ===
602
603    /// Unregister a custom recognition by name.
604    pub fn unregister_custom_recognition(&self, name: &str) -> MaaResult<()> {
605        let c_name = CString::new(name)?;
606        let ret = unsafe {
607            sys::MaaResourceUnregisterCustomRecognition(self.inner.handle.as_ptr(), c_name.as_ptr())
608        };
609        if ret != 0 {
610            self.inner.custom_recognitions.lock().unwrap().remove(name);
611        }
612        common::check_bool(ret)
613    }
614
615    /// Unregister a custom action by name.
616    pub fn unregister_custom_action(&self, name: &str) -> MaaResult<()> {
617        let c_name = CString::new(name)?;
618        let ret = unsafe {
619            sys::MaaResourceUnregisterCustomAction(self.inner.handle.as_ptr(), c_name.as_ptr())
620        };
621        if ret != 0 {
622            self.inner.custom_actions.lock().unwrap().remove(name);
623        }
624        common::check_bool(ret)
625    }
626
627    /// Get the list of registered custom recognitions.
628    pub fn custom_recognition_list(&self) -> MaaResult<Vec<String>> {
629        let buffer = crate::buffer::MaaStringListBuffer::new()?;
630        let ret = unsafe {
631            sys::MaaResourceGetCustomRecognitionList(self.inner.handle.as_ptr(), buffer.raw())
632        };
633        if ret != 0 {
634            Ok(buffer.to_vec())
635        } else {
636            Err(MaaError::FrameworkError(0))
637        }
638    }
639
640    /// Get the list of registered custom actions.
641    pub fn custom_action_list(&self) -> MaaResult<Vec<String>> {
642        let buffer = crate::buffer::MaaStringListBuffer::new()?;
643        let ret = unsafe {
644            sys::MaaResourceGetCustomActionList(self.inner.handle.as_ptr(), buffer.raw())
645        };
646        if ret != 0 {
647            Ok(buffer.to_vec())
648        } else {
649            Err(MaaError::FrameworkError(0))
650        }
651    }
652
653    /// Clear all registered custom recognitions.
654    pub fn clear_custom_recognition(&self) -> MaaResult<()> {
655        let ret = unsafe { sys::MaaResourceClearCustomRecognition(self.inner.handle.as_ptr()) };
656        if ret != 0 {
657            let mut recos = self.inner.custom_recognitions.lock().unwrap();
658            for (_, ptr) in recos.drain() {
659                unsafe {
660                    let _ = Box::from_raw(ptr as *mut Box<dyn crate::custom::CustomRecognition>);
661                }
662            }
663        }
664        common::check_bool(ret)
665    }
666
667    /// Clear all registered custom actions.
668    pub fn clear_custom_action(&self) -> MaaResult<()> {
669        let ret = unsafe { sys::MaaResourceClearCustomAction(self.inner.handle.as_ptr()) };
670        if ret != 0 {
671            let mut actions = self.inner.custom_actions.lock().unwrap();
672            for (_, ptr) in actions.drain() {
673                unsafe {
674                    let _ = Box::from_raw(ptr as *mut Box<dyn crate::custom::CustomAction>);
675                }
676            }
677        }
678        common::check_bool(ret)
679    }
680    pub(crate) fn custom_recognitions(
681        &self,
682    ) -> &std::sync::Mutex<std::collections::HashMap<String, usize>> {
683        &self.inner.custom_recognitions
684    }
685
686    pub(crate) fn custom_actions(
687        &self,
688    ) -> &std::sync::Mutex<std::collections::HashMap<String, usize>> {
689        &self.inner.custom_actions
690    }
691}
692
693impl Drop for ResourceInner {
694    fn drop(&mut self) {
695        unsafe {
696            if self.owns_handle {
697                sys::MaaResourceClearSinks(self.handle.as_ptr());
698                {
699                    let mut callbacks = self.callbacks.lock().unwrap();
700                    for (_, ptr) in callbacks.drain() {
701                        let _ = crate::callback::EventCallback::drop_callback(
702                            ptr as *mut std::ffi::c_void,
703                        );
704                    }
705                }
706                {
707                    let mut event_sinks = self.event_sinks.lock().unwrap();
708                    for (_, ptr) in event_sinks.drain() {
709                        let _ =
710                            crate::callback::EventCallback::drop_sink(ptr as *mut std::ffi::c_void);
711                    }
712                }
713
714                sys::MaaResourceClearCustomAction(self.handle.as_ptr());
715                sys::MaaResourceClearCustomRecognition(self.handle.as_ptr());
716
717                {
718                    let mut actions = self.custom_actions.lock().unwrap();
719                    for (_, ptr) in actions.drain() {
720                        let _ = Box::from_raw(ptr as *mut Box<dyn crate::custom::CustomAction>);
721                    }
722                }
723                {
724                    let mut recos = self.custom_recognitions.lock().unwrap();
725                    for (_, ptr) in recos.drain() {
726                        let _ =
727                            Box::from_raw(ptr as *mut Box<dyn crate::custom::CustomRecognition>);
728                    }
729                }
730
731                sys::MaaResourceDestroy(self.handle.as_ptr());
732            }
733        }
734    }
735}
736
737/// A borrowed reference to a Resource.
738///
739/// This is a non-owning view that can be used for read-only operations.
740/// It does NOT call destroy when dropped and should only be used while
741/// the underlying Resource is still alive.
742///
743/// Obtained from `Tasker::resource()`.
744pub struct ResourceRef<'a> {
745    handle: *mut sys::MaaResource,
746    _marker: std::marker::PhantomData<&'a ()>,
747}
748
749impl<'a> ResourceRef<'a> {
750    pub(crate) fn from_ptr(handle: *mut sys::MaaResource) -> Option<Self> {
751        if handle.is_null() {
752            None
753        } else {
754            Some(Self {
755                handle,
756                _marker: std::marker::PhantomData,
757            })
758        }
759    }
760
761    /// Check if resources have been loaded.
762    pub fn loaded(&self) -> bool {
763        unsafe { sys::MaaResourceLoaded(self.handle) != 0 }
764    }
765
766    /// Get the status of a loading operation.
767    pub fn status(&self, id: common::MaaId) -> common::MaaStatus {
768        let status = unsafe { sys::MaaResourceStatus(self.handle, id) };
769        common::MaaStatus(status)
770    }
771
772    /// Wait for a loading operation to complete.
773    pub fn wait(&self, id: common::MaaId) -> common::MaaStatus {
774        let status = unsafe { sys::MaaResourceWait(self.handle, id) };
775        common::MaaStatus(status)
776    }
777
778    /// Get resource hash.
779    pub fn hash(&self) -> MaaResult<String> {
780        let buffer = crate::buffer::MaaStringBuffer::new()?;
781        let ret = unsafe { sys::MaaResourceGetHash(self.handle, buffer.raw()) };
782        if ret != 0 {
783            Ok(buffer.to_string())
784        } else {
785            Err(MaaError::FrameworkError(0))
786        }
787    }
788
789    /// Get valid default parameters for a recognition type as JSON.
790    pub fn get_default_recognition_param(
791        &self,
792        reco_type: &str,
793    ) -> MaaResult<Option<serde_json::Value>> {
794        let c_type = std::ffi::CString::new(reco_type)?;
795        let buffer = crate::buffer::MaaStringBuffer::new()?;
796        let ret = unsafe {
797            sys::MaaResourceGetDefaultRecognitionParam(self.handle, c_type.as_ptr(), buffer.raw())
798        };
799        if ret != 0 {
800            let json_str = buffer.to_string();
801            let val: serde_json::Value = serde_json::from_str(&json_str).map_err(|e| {
802                MaaError::InvalidConfig(format!("Failed to parse default params: {}", e))
803            })?;
804            Ok(Some(val))
805        } else {
806            Ok(None)
807        }
808    }
809
810    /// Get valid default parameters for an action type as JSON.
811    pub fn get_default_action_param(
812        &self,
813        action_type: &str,
814    ) -> MaaResult<Option<serde_json::Value>> {
815        let c_type = std::ffi::CString::new(action_type)?;
816        let buffer = crate::buffer::MaaStringBuffer::new()?;
817        let ret = unsafe {
818            sys::MaaResourceGetDefaultActionParam(self.handle, c_type.as_ptr(), buffer.raw())
819        };
820        if ret != 0 {
821            let json_str = buffer.to_string();
822            let val: serde_json::Value = serde_json::from_str(&json_str).map_err(|e| {
823                MaaError::InvalidConfig(format!("Failed to parse default params: {}", e))
824            })?;
825            Ok(Some(val))
826        } else {
827            Ok(None)
828        }
829    }
830
831    /// Get node list.
832    pub fn node_list(&self) -> MaaResult<Vec<String>> {
833        let buffer = crate::buffer::MaaStringListBuffer::new()?;
834        let ret = unsafe { sys::MaaResourceGetNodeList(self.handle, buffer.raw()) };
835        if ret != 0 {
836            Ok(buffer.to_vec())
837        } else {
838            Err(MaaError::FrameworkError(0))
839        }
840    }
841
842    /// Get node data as JSON string.
843    pub fn get_node_data(&self, node_name: &str) -> MaaResult<Option<String>> {
844        let c_name = std::ffi::CString::new(node_name)?;
845        let buffer = crate::buffer::MaaStringBuffer::new()?;
846        let ret =
847            unsafe { sys::MaaResourceGetNodeData(self.handle, c_name.as_ptr(), buffer.raw()) };
848        if ret != 0 {
849            Ok(Some(buffer.to_string()))
850        } else {
851            Ok(None)
852        }
853    }
854
855    /// Get the raw handle.
856    pub fn raw(&self) -> *mut sys::MaaResource {
857        self.handle
858    }
859}