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    // === EventSink ===
383
384    /// Register a raw callback to receive events.
385    ///
386    /// The callback receives event type and details as JSON strings.
387    pub fn add_sink<F>(&self, callback: F) -> MaaResult<sys::MaaSinkId>
388    where
389        F: Fn(&str, &str) + Send + Sync + 'static,
390    {
391        let (cb_fn, cb_arg) = crate::callback::EventCallback::new(callback);
392        let sink_id = unsafe { sys::MaaResourceAddSink(self.inner.handle.as_ptr(), cb_fn, cb_arg) };
393        if sink_id != 0 {
394            self.inner
395                .callbacks
396                .lock()
397                .unwrap()
398                .insert(sink_id, cb_arg as usize);
399            Ok(sink_id)
400        } else {
401            unsafe { crate::callback::EventCallback::drop_callback(cb_arg) };
402            Err(MaaError::FrameworkError(0))
403        }
404    }
405
406    /// Register a strongly-typed event sink.
407    ///
408    /// This method registers an implementation of the [`EventSink`](crate::event_sink::EventSink) trait
409    /// to receive structured notifications from this resource.
410    ///
411    /// # Arguments
412    /// * `sink` - The event sink implementation (must be boxed).
413    ///
414    /// # Returns
415    /// A `MaaSinkId` which can be used to manually remove the sink later via [`remove_sink`](Self::remove_sink).
416    /// The sink will be automatically unregistered and dropped when the `Resource` is dropped.
417    pub fn add_event_sink(
418        &self,
419        sink: Box<dyn crate::event_sink::EventSink>,
420    ) -> MaaResult<sys::MaaSinkId> {
421        let handle_id = self.inner.handle.as_ptr() as crate::common::MaaId;
422        let (cb, arg) = crate::callback::EventCallback::new_sink(handle_id, sink);
423        let id = unsafe { sys::MaaResourceAddSink(self.inner.handle.as_ptr(), cb, arg) };
424        if id > 0 {
425            self.inner
426                .event_sinks
427                .lock()
428                .unwrap()
429                .insert(id, arg as usize);
430            Ok(id)
431        } else {
432            unsafe { crate::callback::EventCallback::drop_sink(arg) };
433            Err(MaaError::FrameworkError(0))
434        }
435    }
436
437    /// Remove a previously registered event sink.
438    pub fn remove_sink(&self, sink_id: sys::MaaSinkId) {
439        unsafe { sys::MaaResourceRemoveSink(self.inner.handle.as_ptr(), sink_id) }
440        if let Some(ptr) = self.inner.callbacks.lock().unwrap().remove(&sink_id) {
441            unsafe { crate::callback::EventCallback::drop_callback(ptr as *mut std::ffi::c_void) };
442        } else if let Some(ptr) = self.inner.event_sinks.lock().unwrap().remove(&sink_id) {
443            unsafe { crate::callback::EventCallback::drop_sink(ptr as *mut std::ffi::c_void) };
444        }
445    }
446
447    /// Remove all registered event sinks.
448    pub fn clear_sinks(&self) {
449        unsafe { sys::MaaResourceClearSinks(self.inner.handle.as_ptr()) }
450        let mut callbacks = self.inner.callbacks.lock().unwrap();
451        for (_, ptr) in callbacks.drain() {
452            unsafe { crate::callback::EventCallback::drop_callback(ptr as *mut std::ffi::c_void) };
453        }
454        let mut event_sinks = self.inner.event_sinks.lock().unwrap();
455        for (_, ptr) in event_sinks.drain() {
456            unsafe { crate::callback::EventCallback::drop_sink(ptr as *mut std::ffi::c_void) };
457        }
458    }
459
460    // === Image override ===
461
462    /// Override an image resource at runtime.
463    ///
464    /// # Arguments
465    /// * `image_name` - The name of the image to override
466    /// * `image` - The new image buffer to use
467    pub fn override_image(
468        &self,
469        image_name: &str,
470        image: &crate::buffer::MaaImageBuffer,
471    ) -> MaaResult<()> {
472        let c_name = CString::new(image_name)?;
473        let ret = unsafe {
474            sys::MaaResourceOverrideImage(self.inner.handle.as_ptr(), c_name.as_ptr(), image.raw())
475        };
476        common::check_bool(ret)
477    }
478
479    // === Inference device (extended) ===
480
481    /// Auto-select the best inference execution provider.
482    ///
483    /// Tries CUDA, then DirectML, then WebGPU, then CoreML, and falls back to CPU.
484    pub fn use_auto_ep(&self) -> MaaResult<()> {
485        let mut ep: i32 =
486            sys::MaaInferenceExecutionProviderEnum_MaaInferenceExecutionProvider_Auto as i32;
487        let ret1 = unsafe {
488            sys::MaaResourceSetOption(
489                self.inner.handle.as_ptr(),
490                sys::MaaResOptionEnum_MaaResOption_InferenceExecutionProvider as i32,
491                &mut ep as *mut _ as *mut std::ffi::c_void,
492                std::mem::size_of::<i32>() as u64,
493            )
494        };
495        common::check_bool(ret1)?;
496
497        let mut device: i32 = sys::MaaInferenceDeviceEnum_MaaInferenceDevice_Auto as i32;
498        let ret2 = unsafe {
499            sys::MaaResourceSetOption(
500                self.inner.handle.as_ptr(),
501                sys::MaaResOptionEnum_MaaResOption_InferenceDevice as i32,
502                &mut device as *mut _ as *mut std::ffi::c_void,
503                std::mem::size_of::<i32>() as u64,
504            )
505        };
506        common::check_bool(ret2)
507    }
508
509    /// Use CoreML for inference (macOS only).
510    ///
511    /// MaaDeps no longer ships the CoreML execution provider since MaaFramework 5.14.3.
512    ///
513    /// # Arguments
514    /// * `coreml_flag` - CoreML configuration flag
515    #[deprecated(note = "MaaDeps no longer ships CoreML; use `use_webgpu` instead")]
516    pub fn use_coreml(&self, coreml_flag: i32) -> MaaResult<()> {
517        let mut ep: i32 =
518            sys::MaaInferenceExecutionProviderEnum_MaaInferenceExecutionProvider_CoreML as i32;
519        let ret1 = unsafe {
520            sys::MaaResourceSetOption(
521                self.inner.handle.as_ptr(),
522                sys::MaaResOptionEnum_MaaResOption_InferenceExecutionProvider as i32,
523                &mut ep as *mut _ as *mut std::ffi::c_void,
524                std::mem::size_of::<i32>() as u64,
525            )
526        };
527        common::check_bool(ret1)?;
528
529        let mut device = coreml_flag;
530        let ret2 = unsafe {
531            sys::MaaResourceSetOption(
532                self.inner.handle.as_ptr(),
533                sys::MaaResOptionEnum_MaaResOption_InferenceDevice as i32,
534                &mut device as *mut _ as *mut std::ffi::c_void,
535                std::mem::size_of::<i32>() as u64,
536            )
537        };
538        common::check_bool(ret2)
539    }
540
541    /// Use CUDA for inference (NVIDIA GPU only).
542    ///
543    /// # Arguments
544    /// * `nvidia_gpu_id` - NVIDIA GPU device ID (typically 0 for first GPU)
545    pub fn use_cuda(&self, nvidia_gpu_id: i32) -> MaaResult<()> {
546        let mut ep: i32 =
547            sys::MaaInferenceExecutionProviderEnum_MaaInferenceExecutionProvider_CUDA as i32;
548        let ret1 = unsafe {
549            sys::MaaResourceSetOption(
550                self.inner.handle.as_ptr(),
551                sys::MaaResOptionEnum_MaaResOption_InferenceExecutionProvider as i32,
552                &mut ep as *mut _ as *mut std::ffi::c_void,
553                std::mem::size_of::<i32>() as u64,
554            )
555        };
556        common::check_bool(ret1)?;
557
558        let mut device = nvidia_gpu_id;
559        let ret2 = unsafe {
560            sys::MaaResourceSetOption(
561                self.inner.handle.as_ptr(),
562                sys::MaaResOptionEnum_MaaResOption_InferenceDevice as i32,
563                &mut device as *mut _ as *mut std::ffi::c_void,
564                std::mem::size_of::<i32>() as u64,
565            )
566        };
567        common::check_bool(ret2)
568    }
569
570    // === Custom component management ===
571
572    /// Unregister a custom recognition by name.
573    pub fn unregister_custom_recognition(&self, name: &str) -> MaaResult<()> {
574        let c_name = CString::new(name)?;
575        let ret = unsafe {
576            sys::MaaResourceUnregisterCustomRecognition(self.inner.handle.as_ptr(), c_name.as_ptr())
577        };
578        if ret != 0 {
579            self.inner.custom_recognitions.lock().unwrap().remove(name);
580        }
581        common::check_bool(ret)
582    }
583
584    /// Unregister a custom action by name.
585    pub fn unregister_custom_action(&self, name: &str) -> MaaResult<()> {
586        let c_name = CString::new(name)?;
587        let ret = unsafe {
588            sys::MaaResourceUnregisterCustomAction(self.inner.handle.as_ptr(), c_name.as_ptr())
589        };
590        if ret != 0 {
591            self.inner.custom_actions.lock().unwrap().remove(name);
592        }
593        common::check_bool(ret)
594    }
595
596    /// Get the list of registered custom recognitions.
597    pub fn custom_recognition_list(&self) -> MaaResult<Vec<String>> {
598        let buffer = crate::buffer::MaaStringListBuffer::new()?;
599        let ret = unsafe {
600            sys::MaaResourceGetCustomRecognitionList(self.inner.handle.as_ptr(), buffer.raw())
601        };
602        if ret != 0 {
603            Ok(buffer.to_vec())
604        } else {
605            Err(MaaError::FrameworkError(0))
606        }
607    }
608
609    /// Get the list of registered custom actions.
610    pub fn custom_action_list(&self) -> MaaResult<Vec<String>> {
611        let buffer = crate::buffer::MaaStringListBuffer::new()?;
612        let ret = unsafe {
613            sys::MaaResourceGetCustomActionList(self.inner.handle.as_ptr(), buffer.raw())
614        };
615        if ret != 0 {
616            Ok(buffer.to_vec())
617        } else {
618            Err(MaaError::FrameworkError(0))
619        }
620    }
621
622    /// Clear all registered custom recognitions.
623    pub fn clear_custom_recognition(&self) -> MaaResult<()> {
624        let ret = unsafe { sys::MaaResourceClearCustomRecognition(self.inner.handle.as_ptr()) };
625        if ret != 0 {
626            let mut recos = self.inner.custom_recognitions.lock().unwrap();
627            for (_, ptr) in recos.drain() {
628                unsafe {
629                    let _ = Box::from_raw(ptr as *mut Box<dyn crate::custom::CustomRecognition>);
630                }
631            }
632        }
633        common::check_bool(ret)
634    }
635
636    /// Clear all registered custom actions.
637    pub fn clear_custom_action(&self) -> MaaResult<()> {
638        let ret = unsafe { sys::MaaResourceClearCustomAction(self.inner.handle.as_ptr()) };
639        if ret != 0 {
640            let mut actions = self.inner.custom_actions.lock().unwrap();
641            for (_, ptr) in actions.drain() {
642                unsafe {
643                    let _ = Box::from_raw(ptr as *mut Box<dyn crate::custom::CustomAction>);
644                }
645            }
646        }
647        common::check_bool(ret)
648    }
649    pub(crate) fn custom_recognitions(
650        &self,
651    ) -> &std::sync::Mutex<std::collections::HashMap<String, usize>> {
652        &self.inner.custom_recognitions
653    }
654
655    pub(crate) fn custom_actions(
656        &self,
657    ) -> &std::sync::Mutex<std::collections::HashMap<String, usize>> {
658        &self.inner.custom_actions
659    }
660}
661
662impl Drop for ResourceInner {
663    fn drop(&mut self) {
664        unsafe {
665            if self.owns_handle {
666                sys::MaaResourceClearSinks(self.handle.as_ptr());
667                {
668                    let mut callbacks = self.callbacks.lock().unwrap();
669                    for (_, ptr) in callbacks.drain() {
670                        let _ = crate::callback::EventCallback::drop_callback(
671                            ptr as *mut std::ffi::c_void,
672                        );
673                    }
674                }
675                {
676                    let mut event_sinks = self.event_sinks.lock().unwrap();
677                    for (_, ptr) in event_sinks.drain() {
678                        let _ =
679                            crate::callback::EventCallback::drop_sink(ptr as *mut std::ffi::c_void);
680                    }
681                }
682
683                sys::MaaResourceClearCustomAction(self.handle.as_ptr());
684                sys::MaaResourceClearCustomRecognition(self.handle.as_ptr());
685
686                {
687                    let mut actions = self.custom_actions.lock().unwrap();
688                    for (_, ptr) in actions.drain() {
689                        let _ = Box::from_raw(ptr as *mut Box<dyn crate::custom::CustomAction>);
690                    }
691                }
692                {
693                    let mut recos = self.custom_recognitions.lock().unwrap();
694                    for (_, ptr) in recos.drain() {
695                        let _ =
696                            Box::from_raw(ptr as *mut Box<dyn crate::custom::CustomRecognition>);
697                    }
698                }
699
700                sys::MaaResourceDestroy(self.handle.as_ptr());
701            }
702        }
703    }
704}
705
706/// A borrowed reference to a Resource.
707///
708/// This is a non-owning view that can be used for read-only operations.
709/// It does NOT call destroy when dropped and should only be used while
710/// the underlying Resource is still alive.
711///
712/// Obtained from `Tasker::resource()`.
713pub struct ResourceRef<'a> {
714    handle: *mut sys::MaaResource,
715    _marker: std::marker::PhantomData<&'a ()>,
716}
717
718impl<'a> ResourceRef<'a> {
719    pub(crate) fn from_ptr(handle: *mut sys::MaaResource) -> Option<Self> {
720        if handle.is_null() {
721            None
722        } else {
723            Some(Self {
724                handle,
725                _marker: std::marker::PhantomData,
726            })
727        }
728    }
729
730    /// Check if resources have been loaded.
731    pub fn loaded(&self) -> bool {
732        unsafe { sys::MaaResourceLoaded(self.handle) != 0 }
733    }
734
735    /// Get the status of a loading operation.
736    pub fn status(&self, id: common::MaaId) -> common::MaaStatus {
737        let status = unsafe { sys::MaaResourceStatus(self.handle, id) };
738        common::MaaStatus(status)
739    }
740
741    /// Wait for a loading operation to complete.
742    pub fn wait(&self, id: common::MaaId) -> common::MaaStatus {
743        let status = unsafe { sys::MaaResourceWait(self.handle, id) };
744        common::MaaStatus(status)
745    }
746
747    /// Get resource hash.
748    pub fn hash(&self) -> MaaResult<String> {
749        let buffer = crate::buffer::MaaStringBuffer::new()?;
750        let ret = unsafe { sys::MaaResourceGetHash(self.handle, buffer.raw()) };
751        if ret != 0 {
752            Ok(buffer.to_string())
753        } else {
754            Err(MaaError::FrameworkError(0))
755        }
756    }
757
758    /// Get valid default parameters for a recognition type as JSON.
759    pub fn get_default_recognition_param(
760        &self,
761        reco_type: &str,
762    ) -> MaaResult<Option<serde_json::Value>> {
763        let c_type = std::ffi::CString::new(reco_type)?;
764        let buffer = crate::buffer::MaaStringBuffer::new()?;
765        let ret = unsafe {
766            sys::MaaResourceGetDefaultRecognitionParam(self.handle, c_type.as_ptr(), buffer.raw())
767        };
768        if ret != 0 {
769            let json_str = buffer.to_string();
770            let val: serde_json::Value = serde_json::from_str(&json_str).map_err(|e| {
771                MaaError::InvalidConfig(format!("Failed to parse default params: {}", e))
772            })?;
773            Ok(Some(val))
774        } else {
775            Ok(None)
776        }
777    }
778
779    /// Get valid default parameters for an action type as JSON.
780    pub fn get_default_action_param(
781        &self,
782        action_type: &str,
783    ) -> MaaResult<Option<serde_json::Value>> {
784        let c_type = std::ffi::CString::new(action_type)?;
785        let buffer = crate::buffer::MaaStringBuffer::new()?;
786        let ret = unsafe {
787            sys::MaaResourceGetDefaultActionParam(self.handle, c_type.as_ptr(), buffer.raw())
788        };
789        if ret != 0 {
790            let json_str = buffer.to_string();
791            let val: serde_json::Value = serde_json::from_str(&json_str).map_err(|e| {
792                MaaError::InvalidConfig(format!("Failed to parse default params: {}", e))
793            })?;
794            Ok(Some(val))
795        } else {
796            Ok(None)
797        }
798    }
799
800    /// Get node list.
801    pub fn node_list(&self) -> MaaResult<Vec<String>> {
802        let buffer = crate::buffer::MaaStringListBuffer::new()?;
803        let ret = unsafe { sys::MaaResourceGetNodeList(self.handle, buffer.raw()) };
804        if ret != 0 {
805            Ok(buffer.to_vec())
806        } else {
807            Err(MaaError::FrameworkError(0))
808        }
809    }
810
811    /// Get node data as JSON string.
812    pub fn get_node_data(&self, node_name: &str) -> MaaResult<Option<String>> {
813        let c_name = std::ffi::CString::new(node_name)?;
814        let buffer = crate::buffer::MaaStringBuffer::new()?;
815        let ret =
816            unsafe { sys::MaaResourceGetNodeData(self.handle, c_name.as_ptr(), buffer.raw()) };
817        if ret != 0 {
818            Ok(Some(buffer.to_string()))
819        } else {
820            Ok(None)
821        }
822    }
823
824    /// Get the raw handle.
825    pub fn raw(&self) -> *mut sys::MaaResource {
826        self.handle
827    }
828}