Skip to main content

maa_framework/
tasker.rs

1//! Task execution and pipeline management.
2
3use crate::resource::Resource;
4use crate::{MaaError, MaaResult, common, sys};
5use std::ptr::NonNull;
6
7use std::collections::HashMap;
8use std::ffi::c_void;
9use std::sync::Mutex;
10
11/// Task manager for executing pipelines.
12///
13/// Tasker is the central component that coordinates:
14/// - Resource binding (images, models)
15/// - Controller binding (device connection)
16/// - Task execution (pipelines)
17/// - Event handling (callbacks)
18use std::sync::Arc;
19
20struct TaskerInner {
21    handle: NonNull<sys::MaaTasker>,
22    owns_handle: bool,
23    callbacks: Mutex<HashMap<sys::MaaSinkId, usize>>, // Store pointer address
24    event_sinks: Mutex<HashMap<sys::MaaSinkId, usize>>,
25    resource: Mutex<Option<Resource>>,
26    controller: Mutex<Option<crate::controller::Controller>>,
27}
28
29unsafe impl Send for TaskerInner {}
30unsafe impl Sync for TaskerInner {}
31
32/// Task manager for executing pipelines.
33///
34/// Tasker is the central component that coordinates:
35/// - Resource binding (images, models)
36/// - Controller binding (device connection)
37/// - Task execution (pipelines)
38/// - Event handling (callbacks)
39#[derive(Clone)]
40pub struct Tasker {
41    inner: Arc<TaskerInner>,
42}
43
44unsafe impl Send for Tasker {}
45unsafe impl Sync for Tasker {}
46
47impl Tasker {
48    /// Create a new Tasker instance.
49    pub fn new() -> MaaResult<Self> {
50        let handle = unsafe { sys::MaaTaskerCreate() };
51        if let Some(ptr) = NonNull::new(handle) {
52            Ok(Self {
53                inner: Arc::new(TaskerInner {
54                    handle: ptr,
55                    owns_handle: true,
56                    callbacks: Mutex::new(HashMap::new()),
57                    event_sinks: Mutex::new(HashMap::new()),
58                    resource: Mutex::new(None),
59                    controller: Mutex::new(None),
60                }),
61            })
62        } else {
63            Err(MaaError::NullPointer)
64        }
65    }
66
67    /// Create a Tasker from a raw pointer.
68    ///
69    /// # Safety
70    /// The pointer must be valid.
71    /// If `owns` is true, the Tasker will destroy the handle when dropped.
72    pub unsafe fn from_raw(ptr: *mut sys::MaaTasker, owns: bool) -> MaaResult<Self> {
73        if let Some(handle) = NonNull::new(ptr) {
74            Ok(Self {
75                inner: Arc::new(TaskerInner {
76                    handle,
77                    owns_handle: owns,
78                    callbacks: Mutex::new(HashMap::new()),
79                    event_sinks: Mutex::new(HashMap::new()),
80                    resource: Mutex::new(None),
81                    controller: Mutex::new(None),
82                }),
83            })
84        } else {
85            Err(MaaError::NullPointer)
86        }
87    }
88
89    /// Bind a resource to this tasker.
90    pub fn bind_resource(&self, res: &Resource) -> MaaResult<()> {
91        let ret = unsafe { sys::MaaTaskerBindResource(self.inner.handle.as_ptr(), res.raw()) };
92        common::check_bool(ret)?;
93        *self.inner.resource.lock().unwrap() = Some(res.clone());
94        Ok(())
95    }
96
97    /// Bind a controller to this tasker.
98    pub fn bind_controller(&self, ctrl: &crate::controller::Controller) -> MaaResult<()> {
99        let ret = unsafe { sys::MaaTaskerBindController(self.inner.handle.as_ptr(), ctrl.raw()) };
100        common::check_bool(ret)?;
101        *self.inner.controller.lock().unwrap() = Some(ctrl.clone());
102        Ok(())
103    }
104
105    /// Get recognition details by ID.
106    pub fn get_recognition_detail(
107        &self,
108        reco_id: crate::common::MaaId,
109    ) -> MaaResult<Option<crate::common::RecognitionDetail>> {
110        Self::fetch_recognition_detail(self.inner.handle.as_ptr(), reco_id)
111    }
112
113    /// Get action details by ID.
114    pub fn get_action_detail(
115        &self,
116        act_id: crate::common::MaaId,
117    ) -> MaaResult<Option<crate::common::ActionDetail>> {
118        Self::fetch_action_detail(self.inner.handle.as_ptr(), act_id)
119    }
120
121    /// Get wait-freezes details by ID.
122    pub fn get_wait_freezes_detail(
123        &self,
124        wf_id: crate::common::MaaId,
125    ) -> MaaResult<Option<crate::common::WaitFreezesDetail>> {
126        Self::fetch_wait_freezes_detail(self.inner.handle.as_ptr(), wf_id)
127    }
128
129    /// Get node details by ID.
130    pub fn get_node_detail(
131        &self,
132        node_id: crate::common::MaaId,
133    ) -> MaaResult<Option<crate::common::NodeDetail>> {
134        Self::fetch_node_detail(self.inner.handle.as_ptr(), node_id)
135    }
136
137    /// Get task details by ID.
138    pub fn get_task_detail(
139        &self,
140        task_id: crate::common::MaaId,
141    ) -> MaaResult<Option<crate::common::TaskDetail>> {
142        Self::fetch_task_detail(self.inner.handle.as_ptr(), task_id)
143    }
144
145    // Static helpers for fetching details (used by both methods and jobs)
146
147    pub(crate) fn fetch_recognition_detail(
148        handle: *mut sys::MaaTasker,
149        reco_id: crate::common::MaaId,
150    ) -> MaaResult<Option<crate::common::RecognitionDetail>> {
151        let node_name = crate::buffer::MaaStringBuffer::new()?;
152        let algorithm = crate::buffer::MaaStringBuffer::new()?;
153        let mut hit = 0;
154        let mut box_rect = sys::MaaRect {
155            x: 0,
156            y: 0,
157            width: 0,
158            height: 0,
159        };
160        let detail = crate::buffer::MaaStringBuffer::new()?;
161        let raw = crate::buffer::MaaImageBuffer::new()?;
162        let draws = crate::buffer::MaaImageListBuffer::new()?;
163
164        let ret = unsafe {
165            sys::MaaTaskerGetRecognitionDetail(
166                handle,
167                reco_id,
168                node_name.raw(),
169                algorithm.raw(),
170                &mut hit,
171                &mut box_rect,
172                detail.raw(),
173                raw.raw(),
174                draws.raw(),
175            )
176        };
177
178        if ret == 0 {
179            return Ok(None);
180        }
181
182        let algorithm_str = algorithm.as_str().to_string();
183        let algorithm_enum = crate::common::AlgorithmEnum::from(algorithm_str);
184        let detail_val: serde_json::Value =
185            serde_json::from_str(detail.as_str()).unwrap_or(serde_json::Value::Null);
186
187        let mut sub_details = Vec::new();
188
189        if matches!(
190            algorithm_enum,
191            crate::common::AlgorithmEnum::And | crate::common::AlgorithmEnum::Or
192        ) {
193            // Logic A: Recursive parsing for And/Or
194            if let Some(arr) = detail_val.as_array() {
195                for item in arr {
196                    if let Some(sub_id) = item.get("reco_id").and_then(|v| v.as_i64()) {
197                        if let Ok(Some(sub)) = Self::fetch_recognition_detail(handle, sub_id) {
198                            sub_details.push(sub);
199                        }
200                    }
201                }
202            }
203        }
204
205        Ok(Some(crate::common::RecognitionDetail {
206            node_name: node_name.as_str().to_string(),
207            algorithm: algorithm_enum,
208            hit: hit != 0,
209            box_rect: crate::common::Rect::from(box_rect),
210            detail: detail_val,
211            raw_image: raw.to_vec(),
212            draw_images: draws.to_vec_of_vec(),
213            sub_details,
214        }))
215    }
216
217    pub(crate) fn fetch_action_detail(
218        handle: *mut sys::MaaTasker,
219        act_id: crate::common::MaaId,
220    ) -> MaaResult<Option<crate::common::ActionDetail>> {
221        let node_name = crate::buffer::MaaStringBuffer::new()?;
222        let action = crate::buffer::MaaStringBuffer::new()?;
223        let mut result_box = sys::MaaRect {
224            x: 0,
225            y: 0,
226            width: 0,
227            height: 0,
228        };
229        let mut success = 0;
230        let detail = crate::buffer::MaaStringBuffer::new()?;
231
232        let ret = unsafe {
233            sys::MaaTaskerGetActionDetail(
234                handle,
235                act_id,
236                node_name.raw(),
237                action.raw(),
238                &mut result_box,
239                &mut success,
240                detail.raw(),
241            )
242        };
243
244        if ret == 0 {
245            return Ok(None);
246        }
247
248        Ok(Some(crate::common::ActionDetail {
249            node_name: node_name.as_str().to_string(),
250            action: crate::common::ActionEnum::from(action.as_str().to_string()),
251            box_rect: crate::common::Rect::from(result_box),
252            success: success != 0,
253            detail: serde_json::from_str(detail.as_str()).unwrap_or(serde_json::Value::Null),
254        }))
255    }
256
257    pub(crate) fn fetch_wait_freezes_detail(
258        handle: *mut sys::MaaTasker,
259        wf_id: crate::common::MaaId,
260    ) -> MaaResult<Option<crate::common::WaitFreezesDetail>> {
261        let name = crate::buffer::MaaStringBuffer::new()?;
262        let phase = crate::buffer::MaaStringBuffer::new()?;
263        let mut success = 0;
264        let mut elapsed_ms: sys::MaaSize = 0;
265        let mut reco_id_list_size: sys::MaaSize = 0;
266        let mut roi = sys::MaaRect {
267            x: 0,
268            y: 0,
269            width: 0,
270            height: 0,
271        };
272
273        let ret = unsafe {
274            sys::MaaTaskerGetWaitFreezesDetail(
275                handle,
276                wf_id,
277                name.raw(),
278                phase.raw(),
279                &mut success,
280                &mut elapsed_ms,
281                std::ptr::null_mut(),
282                &mut reco_id_list_size,
283                &mut roi,
284            )
285        };
286
287        if ret == 0 {
288            return Ok(None);
289        }
290
291        let mut reco_id_list = vec![0; reco_id_list_size as usize];
292        let reco_id_list_ptr = if reco_id_list.is_empty() {
293            std::ptr::null_mut()
294        } else {
295            reco_id_list.as_mut_ptr()
296        };
297
298        let ret = unsafe {
299            sys::MaaTaskerGetWaitFreezesDetail(
300                handle,
301                wf_id,
302                name.raw(),
303                phase.raw(),
304                &mut success,
305                &mut elapsed_ms,
306                reco_id_list_ptr,
307                &mut reco_id_list_size,
308                &mut roi,
309            )
310        };
311
312        if ret == 0 {
313            return Ok(None);
314        }
315
316        reco_id_list.truncate(reco_id_list_size as usize);
317
318        Ok(Some(crate::common::WaitFreezesDetail {
319            wf_id,
320            name: name.as_str().to_string(),
321            phase: phase.as_str().to_string(),
322            success: success != 0,
323            elapsed_ms,
324            reco_id_list,
325            roi: crate::common::Rect::from(roi),
326        }))
327    }
328
329    pub(crate) fn fetch_node_detail(
330        handle: *mut sys::MaaTasker,
331        node_id: crate::common::MaaId,
332    ) -> MaaResult<Option<crate::common::NodeDetail>> {
333        let node_name = crate::buffer::MaaStringBuffer::new()?;
334        let mut reco_id = 0;
335        let mut act_id = 0;
336        let mut completed = 0;
337
338        let ret = unsafe {
339            sys::MaaTaskerGetNodeDetail(
340                handle,
341                node_id,
342                node_name.raw(),
343                &mut reco_id,
344                &mut act_id,
345                &mut completed,
346            )
347        };
348
349        if ret == 0 {
350            return Ok(None);
351        }
352
353        // Logic A/B: Hydration implies we might want reco/act details in NodeDetail?
354        // common::NodeDetail only has ids. Python wraps calls.
355
356        let recognition = if reco_id > 0 {
357            Self::fetch_recognition_detail(handle, reco_id)?
358        } else {
359            None
360        };
361
362        let action = if act_id > 0 {
363            Self::fetch_action_detail(handle, act_id)?
364        } else {
365            None
366        };
367
368        Ok(Some(crate::common::NodeDetail {
369            node_name: node_name.as_str().to_string(),
370            reco_id,
371            act_id,
372            completed: completed != 0,
373            recognition,
374            action,
375        }))
376    }
377
378    pub(crate) fn fetch_task_detail(
379        handle: *mut sys::MaaTasker,
380        task_id: crate::common::MaaId,
381    ) -> MaaResult<Option<crate::common::TaskDetail>> {
382        let entry = crate::buffer::MaaStringBuffer::new()?;
383        let mut node_id_list_size: sys::MaaSize = 0;
384        let mut status: sys::MaaStatus = 0;
385
386        let ret = unsafe {
387            sys::MaaTaskerGetTaskDetail(
388                handle,
389                task_id,
390                entry.raw(),
391                std::ptr::null_mut(),
392                &mut node_id_list_size,
393                &mut status,
394            )
395        };
396
397        if ret == 0 {
398            return Ok(None);
399        }
400
401        let mut node_id_list = vec![0; node_id_list_size as usize];
402        let ret = unsafe {
403            sys::MaaTaskerGetTaskDetail(
404                handle,
405                task_id,
406                entry.raw(),
407                node_id_list.as_mut_ptr(),
408                &mut node_id_list_size,
409                &mut status,
410            )
411        };
412
413        if ret == 0 {
414            return Ok(None);
415        }
416
417        // Logic B: Hydrate nodes
418        let mut nodes = Vec::with_capacity(node_id_list.len());
419        for &nid in &node_id_list {
420            nodes.push(Self::fetch_node_detail(handle, nid)?);
421        }
422
423        Ok(Some(crate::common::TaskDetail {
424            entry: entry.as_str().to_string(),
425            node_id_list,
426            status: crate::common::MaaStatus(status as i32),
427            nodes,
428        }))
429    }
430
431    /// Post a task for execution.
432    ///
433    /// # Arguments
434    /// * `entry` - The task entry point name
435    /// * `pipeline_override` - JSON string for parameter overrides
436    ///
437    /// # Returns
438    /// A `TaskJob` that can be used to wait for completion and get results.
439    pub fn post_task(
440        &self,
441        entry: &str,
442        pipeline_override: &str,
443    ) -> MaaResult<crate::job::TaskJob<'static, crate::common::TaskDetail>> {
444        let c_entry = std::ffi::CString::new(entry)?;
445        let c_pipeline = std::ffi::CString::new(pipeline_override)?;
446        let id = unsafe {
447            sys::MaaTaskerPostTask(
448                self.inner.handle.as_ptr(),
449                c_entry.as_ptr(),
450                c_pipeline.as_ptr(),
451            )
452        };
453
454        let inner = self.inner.clone();
455        let status_fn: crate::job::StatusFn = Box::new(move |job_id| {
456            crate::common::MaaStatus(unsafe { sys::MaaTaskerStatus(inner.handle.as_ptr(), job_id) })
457        });
458
459        let inner = self.inner.clone();
460        let wait_fn: crate::job::WaitFn = Box::new(move |job_id| {
461            crate::common::MaaStatus(unsafe { sys::MaaTaskerWait(inner.handle.as_ptr(), job_id) })
462        });
463
464        let inner = self.inner.clone();
465        let get_fn =
466            move |task_id: crate::common::MaaId| -> MaaResult<Option<crate::common::TaskDetail>> {
467                Tasker::fetch_task_detail(inner.handle.as_ptr(), task_id)
468            };
469
470        // Prepare override function for TaskJob
471        let inner = self.inner.clone();
472        let override_fn: crate::job::OverridePipelineFn = Box::new(move |job_id, pipeline| {
473            let c_pipeline = std::ffi::CString::new(pipeline)?;
474            let ret = unsafe {
475                sys::MaaTaskerOverridePipeline(inner.handle.as_ptr(), job_id, c_pipeline.as_ptr())
476            };
477            Ok(ret != 0)
478        });
479
480        Ok(crate::job::TaskJob::new(
481            crate::job::JobWithResult::new(id, status_fn, wait_fn, get_fn),
482            override_fn,
483        ))
484    }
485
486    /// Post a task with JSON pipeline override.
487    ///
488    /// Convenience method that accepts `serde_json::Value` for pipeline overrides.
489    ///
490    /// # Example
491    /// ```ignore
492    /// use serde_json::json;
493    /// let job = tasker.post_task_json("StartTask", &json!({
494    ///     "StartTask": { "next": ["SecondTask"] }
495    /// }))?;
496    /// ```
497    pub fn post_task_json(
498        &self,
499        entry: &str,
500        pipeline_override: &serde_json::Value,
501    ) -> MaaResult<crate::job::TaskJob<'static, crate::common::TaskDetail>> {
502        self.post_task(entry, &pipeline_override.to_string())
503    }
504
505    /// Check if the tasker has been initialized (resource and controller bound).
506    pub fn inited(&self) -> bool {
507        unsafe { sys::MaaTaskerInited(self.inner.handle.as_ptr()) != 0 }
508    }
509
510    /// Get the underlying raw tasker handle.
511    #[inline]
512    pub fn raw(&self) -> *mut sys::MaaTasker {
513        self.inner.handle.as_ptr()
514    }
515
516    /// Add a tasker event sink callback.
517    ///
518    /// This registers a callback that will be invoked for all tasker events
519    /// including task start, task completion, and status changes.
520    ///
521    /// # Arguments
522    /// * `callback` - Closure that receives (message, detail_json) for each event
523    ///
524    /// # Returns
525    /// Sink ID for later removal via `remove_sink()`
526    pub fn add_sink<F>(&self, callback: F) -> MaaResult<sys::MaaSinkId>
527    where
528        F: Fn(&str, &str) + Send + Sync + 'static,
529    {
530        let (cb, arg) = crate::callback::EventCallback::new(callback);
531        let id = unsafe { sys::MaaTaskerAddSink(self.inner.handle.as_ptr(), cb, arg) };
532        if id > 0 {
533            self.inner
534                .callbacks
535                .lock()
536                .unwrap()
537                .insert(id, arg as usize);
538            Ok(id)
539        } else {
540            unsafe { crate::callback::EventCallback::drop_callback(arg) };
541            Err(MaaError::FrameworkError(0))
542        }
543    }
544
545    /// Register a strongly-typed event sink.
546    ///
547    /// This method registers an implementation of the [`EventSink`](crate::event_sink::EventSink) trait
548    /// to receive structured notifications from this tasker.
549    ///
550    /// # Arguments
551    /// * `sink` - The event sink implementation (must be boxed).
552    ///
553    /// # Returns
554    /// A `MaaSinkId` which can be used to manually remove the sink later via [`remove_sink`](Self::remove_sink).
555    /// The sink will be automatically unregistered and dropped when the `Tasker` is dropped.
556    pub fn add_event_sink(
557        &self,
558        sink: Box<dyn crate::event_sink::EventSink>,
559    ) -> MaaResult<sys::MaaSinkId> {
560        let handle_id = self.inner.handle.as_ptr() as crate::common::MaaId;
561        let (cb, arg) = crate::callback::EventCallback::new_sink(handle_id, sink);
562        let id = unsafe { sys::MaaTaskerAddSink(self.inner.handle.as_ptr(), cb, arg) };
563        if id > 0 {
564            self.inner
565                .event_sinks
566                .lock()
567                .unwrap()
568                .insert(id, arg as usize);
569            Ok(id)
570        } else {
571            unsafe { crate::callback::EventCallback::drop_sink(arg) };
572            Err(MaaError::FrameworkError(0))
573        }
574    }
575
576    /// Remove a tasker sink by ID.
577    ///
578    /// # Arguments
579    /// * `sink_id` - ID returned from `add_sink()`
580    pub fn remove_sink(&self, sink_id: sys::MaaSinkId) {
581        unsafe { sys::MaaTaskerRemoveSink(self.inner.handle.as_ptr(), sink_id) };
582        if let Some(ptr) = self.inner.callbacks.lock().unwrap().remove(&sink_id) {
583            unsafe { crate::callback::EventCallback::drop_callback(ptr as *mut c_void) };
584        } else if let Some(ptr) = self.inner.event_sinks.lock().unwrap().remove(&sink_id) {
585            unsafe { crate::callback::EventCallback::drop_sink(ptr as *mut c_void) };
586        }
587    }
588
589    /// Clear all tasker sinks.
590    pub fn clear_sinks(&self) {
591        unsafe { sys::MaaTaskerClearSinks(self.inner.handle.as_ptr()) };
592        let mut callbacks = self.inner.callbacks.lock().unwrap();
593        for (_, ptr) in callbacks.drain() {
594            unsafe { crate::callback::EventCallback::drop_callback(ptr as *mut c_void) };
595        }
596        let mut event_sinks = self.inner.event_sinks.lock().unwrap();
597        for (_, ptr) in event_sinks.drain() {
598            unsafe { crate::callback::EventCallback::drop_sink(ptr as *mut c_void) };
599        }
600    }
601
602    #[deprecated(since = "0.5.1", note = "Use add_sink() instead")]
603    pub fn register_callback<F>(&self, callback: F) -> MaaResult<crate::common::MaaId>
604    where
605        F: Fn(&str, &str) + Send + Sync + 'static,
606    {
607        self.add_sink(callback)
608    }
609
610    /// Request the tasker to stop all running tasks.
611    pub fn post_stop(&self) -> MaaResult<crate::common::MaaId> {
612        unsafe {
613            let id = sys::MaaTaskerPostStop(self.inner.handle.as_ptr());
614            Ok(id)
615        }
616    }
617
618    /// Check if the tasker is currently running.
619    pub fn is_running(&self) -> bool {
620        unsafe { sys::MaaTaskerRunning(self.inner.handle.as_ptr()) != 0 }
621    }
622
623    /// Check if the tasker is currently running (alias for `is_running`).
624    pub fn running(&self) -> bool {
625        self.is_running()
626    }
627
628    /// Check if the tasker is currently stopping.
629    pub fn stopping(&self) -> bool {
630        unsafe { sys::MaaTaskerStopping(self.inner.handle.as_ptr()) != 0 }
631    }
632
633    // === Context Sink ===
634
635    /// Add a context event sink callback.
636    /// Returns a sink ID for later removal.
637    pub fn add_context_sink<F>(&self, callback: F) -> MaaResult<sys::MaaSinkId>
638    where
639        F: Fn(&str, &str) + Send + Sync + 'static,
640    {
641        let (cb, arg) = crate::callback::EventCallback::new(callback);
642        let id = unsafe { sys::MaaTaskerAddContextSink(self.inner.handle.as_ptr(), cb, arg) };
643        if id > 0 {
644            self.inner
645                .callbacks
646                .lock()
647                .unwrap()
648                .insert(id, arg as usize);
649            Ok(id)
650        } else {
651            unsafe { crate::callback::EventCallback::drop_callback(arg) };
652            Err(MaaError::FrameworkError(0))
653        }
654    }
655
656    /// Register a strongly-typed context event sink.
657    ///
658    /// This receives detailed execution events like Node.Recognition, Node.Action, etc.
659    ///
660    /// # Arguments
661    /// * `sink` - The event sink implementation (must be boxed).
662    ///
663    /// # Returns
664    /// A `MaaSinkId` which can be used to manually remove the sink later via [`remove_context_sink`](Self::remove_context_sink).
665    pub fn add_context_event_sink(
666        &self,
667        sink: Box<dyn crate::event_sink::EventSink>,
668    ) -> MaaResult<sys::MaaSinkId> {
669        let handle_id = self.inner.handle.as_ptr() as crate::common::MaaId;
670        let (cb, arg) = crate::callback::EventCallback::new_sink(handle_id, sink);
671        let id = unsafe { sys::MaaTaskerAddContextSink(self.inner.handle.as_ptr(), cb, arg) };
672        if id > 0 {
673            self.inner
674                .event_sinks
675                .lock()
676                .unwrap()
677                .insert(id, arg as usize);
678            Ok(id)
679        } else {
680            unsafe { crate::callback::EventCallback::drop_sink(arg) };
681            Err(MaaError::FrameworkError(0))
682        }
683    }
684
685    /// Remove a context sink by ID.
686    pub fn remove_context_sink(&self, sink_id: sys::MaaSinkId) {
687        unsafe { sys::MaaTaskerRemoveContextSink(self.inner.handle.as_ptr(), sink_id) };
688        if let Some(ptr) = self.inner.callbacks.lock().unwrap().remove(&sink_id) {
689            unsafe { crate::callback::EventCallback::drop_callback(ptr as *mut c_void) };
690        } else if let Some(ptr) = self.inner.event_sinks.lock().unwrap().remove(&sink_id) {
691            unsafe { crate::callback::EventCallback::drop_sink(ptr as *mut c_void) };
692        }
693    }
694
695    /// Clear all context sinks.
696    pub fn clear_context_sinks(&self) {
697        unsafe { sys::MaaTaskerClearContextSinks(self.inner.handle.as_ptr()) };
698        // Note: callbacks registered via add_context_sink will be cleaned up
699    }
700
701    /// Clear all cached data.
702    pub fn clear_cache(&self) -> MaaResult<()> {
703        let ret = unsafe { sys::MaaTaskerClearCache(self.inner.handle.as_ptr()) };
704        crate::common::check_bool(ret)
705    }
706
707    /// Override pipeline configuration for a specific task.
708    ///
709    /// # Arguments
710    /// * `task_id` - The ID of the task to update.
711    /// * `pipeline_override` - The JSON string containing the new configuration.
712    pub fn override_pipeline(
713        &self,
714        task_id: crate::common::MaaId,
715        pipeline_override: &str,
716    ) -> MaaResult<bool> {
717        let c_pipeline = std::ffi::CString::new(pipeline_override)?;
718        let ret = unsafe {
719            sys::MaaTaskerOverridePipeline(self.inner.handle.as_ptr(), task_id, c_pipeline.as_ptr())
720        };
721        Ok(ret != 0)
722    }
723
724    /// Get the latest node ID for a given node name.
725    pub fn get_latest_node(&self, node_name: &str) -> MaaResult<Option<crate::common::MaaId>> {
726        let c_name = std::ffi::CString::new(node_name)?;
727        let mut node_id: crate::common::MaaId = 0;
728        let ret = unsafe {
729            sys::MaaTaskerGetLatestNode(self.inner.handle.as_ptr(), c_name.as_ptr(), &mut node_id)
730        };
731        if ret != 0 && node_id != 0 {
732            Ok(Some(node_id))
733        } else {
734            Ok(None)
735        }
736    }
737
738    /// Convenience method to bind both resource and controller at once.
739    pub fn bind(
740        &self,
741        resource: &Resource,
742        controller: &crate::controller::Controller,
743    ) -> MaaResult<()> {
744        self.bind_resource(resource)?;
745        self.bind_controller(controller)
746    }
747
748    /// Get a borrowed view of the bound resource.
749    ///
750    /// Returns `None` if no resource is bound.
751    ///
752    /// # Example
753    /// ```ignore
754    /// if let Some(res) = tasker.resource() {
755    ///     println!("Loaded: {}", res.loaded());
756    /// }
757    /// ```
758    pub fn resource(&self) -> Option<crate::resource::ResourceRef<'_>> {
759        let ptr = unsafe { sys::MaaTaskerGetResource(self.inner.handle.as_ptr()) };
760        crate::resource::ResourceRef::from_ptr(ptr)
761    }
762
763    /// Get a borrowed view of the bound controller.
764    ///
765    /// Returns `None` if no controller is bound.
766    ///
767    /// # Example
768    /// ```ignore
769    /// if let Some(ctrl) = tasker.controller() {
770    ///     println!("Connected: {}", ctrl.connected());
771    /// }
772    /// ```
773    pub fn controller(&self) -> Option<crate::controller::ControllerRef<'_>> {
774        let ptr = unsafe { sys::MaaTaskerGetController(self.inner.handle.as_ptr()) };
775        crate::controller::ControllerRef::from_ptr(ptr)
776    }
777
778    /// Get the bound resource handle (raw pointer).
779    ///
780    /// Returns the raw pointer to the resource. The caller should not destroy this handle.
781    pub fn resource_handle(&self) -> *mut sys::MaaResource {
782        unsafe { sys::MaaTaskerGetResource(self.inner.handle.as_ptr()) }
783    }
784
785    /// Get the bound controller handle (raw pointer).
786    ///
787    /// Returns the raw pointer to the controller. The caller should not destroy this handle.
788    pub fn controller_handle(&self) -> *mut sys::MaaController {
789        unsafe { sys::MaaTaskerGetController(self.inner.handle.as_ptr()) }
790    }
791
792    /// Post a recognition task directly without executing through a pipeline.
793    ///
794    /// # Arguments
795    /// * `reco_type` - Recognition type (e.g., "TemplateMatch", "OCR")
796    /// * `reco_param` - Recognition parameters as JSON string
797    /// * `image` - The image to perform recognition on
798    pub fn post_recognition(
799        &self,
800        reco_type: &str,
801        reco_param: &str,
802        image: &crate::buffer::MaaImageBuffer,
803    ) -> MaaResult<crate::job::TaskJob<'static, crate::common::RecognitionDetail>> {
804        let c_type = std::ffi::CString::new(reco_type)?;
805        let c_param = std::ffi::CString::new(reco_param)?;
806        let id = unsafe {
807            sys::MaaTaskerPostRecognition(
808                self.inner.handle.as_ptr(),
809                c_type.as_ptr(),
810                c_param.as_ptr(),
811                image.raw(),
812            )
813        };
814
815        let inner = self.inner.clone();
816        let status_fn: crate::job::StatusFn = Box::new(move |job_id| {
817            common::MaaStatus(unsafe { sys::MaaTaskerStatus(inner.handle.as_ptr(), job_id) })
818        });
819
820        let inner = self.inner.clone();
821        let wait_fn: crate::job::WaitFn = Box::new(move |job_id| {
822            common::MaaStatus(unsafe { sys::MaaTaskerWait(inner.handle.as_ptr(), job_id) })
823        });
824
825        let inner = self.inner.clone();
826        let get_fn = move |reco_id: common::MaaId| -> MaaResult<Option<common::RecognitionDetail>> {
827            Tasker::fetch_recognition_detail(inner.handle.as_ptr(), reco_id)
828        };
829
830        // Prepare override function for TaskJob
831        let inner = self.inner.clone();
832        let override_fn: crate::job::OverridePipelineFn = Box::new(move |job_id, pipeline| {
833            let c_pipeline = std::ffi::CString::new(pipeline)?;
834            let ret = unsafe {
835                sys::MaaTaskerOverridePipeline(inner.handle.as_ptr(), job_id, c_pipeline.as_ptr())
836            };
837            Ok(ret != 0)
838        });
839
840        Ok(crate::job::TaskJob::new(
841            crate::job::JobWithResult::new(id, status_fn, wait_fn, get_fn),
842            override_fn,
843        ))
844    }
845
846    /// Post an action task directly without executing through a pipeline.
847    ///
848    /// # Arguments
849    /// * `action_type` - Action type (e.g., "Click", "Swipe")  
850    /// * `action_param` - Action parameters as JSON string
851    /// * `box_rect` - The target rectangle for the action
852    /// * `reco_detail` - Recognition detail from previous recognition (can be empty)
853    pub fn post_action(
854        &self,
855        action_type: &str,
856        action_param: &str,
857        box_rect: &common::Rect,
858        reco_detail: &str,
859    ) -> MaaResult<crate::job::TaskJob<'static, crate::common::ActionDetail>> {
860        let c_type = std::ffi::CString::new(action_type)?;
861        let c_param = std::ffi::CString::new(action_param)?;
862        let c_detail = std::ffi::CString::new(reco_detail)?;
863        let maa_rect = sys::MaaRect {
864            x: box_rect.x,
865            y: box_rect.y,
866            width: box_rect.width,
867            height: box_rect.height,
868        };
869
870        let id = unsafe {
871            sys::MaaTaskerPostAction(
872                self.inner.handle.as_ptr(),
873                c_type.as_ptr(),
874                c_param.as_ptr(),
875                &maa_rect,
876                c_detail.as_ptr(),
877            )
878        };
879
880        let inner = self.inner.clone();
881        let status_fn: crate::job::StatusFn = Box::new(move |job_id| {
882            common::MaaStatus(unsafe { sys::MaaTaskerStatus(inner.handle.as_ptr(), job_id) })
883        });
884
885        let inner = self.inner.clone();
886        let wait_fn: crate::job::WaitFn = Box::new(move |job_id| {
887            common::MaaStatus(unsafe { sys::MaaTaskerWait(inner.handle.as_ptr(), job_id) })
888        });
889
890        let inner = self.inner.clone();
891        let get_fn = move |act_id: common::MaaId| -> MaaResult<Option<common::ActionDetail>> {
892            Tasker::fetch_action_detail(inner.handle.as_ptr(), act_id)
893        };
894
895        // Prepare override function for TaskJob
896        let inner = self.inner.clone();
897        let override_fn: crate::job::OverridePipelineFn = Box::new(move |job_id, pipeline| {
898            let c_pipeline = std::ffi::CString::new(pipeline)?;
899            let ret = unsafe {
900                sys::MaaTaskerOverridePipeline(inner.handle.as_ptr(), job_id, c_pipeline.as_ptr())
901            };
902            Ok(ret != 0)
903        });
904
905        Ok(crate::job::TaskJob::new(
906            crate::job::JobWithResult::new(id, status_fn, wait_fn, get_fn),
907            override_fn,
908        ))
909    }
910
911    // === Global Methods ===
912
913    /// Set the global log directory.
914    pub fn set_log_dir<P: AsRef<std::path::Path>>(path: P) -> MaaResult<()> {
915        let path_str = path.as_ref().to_string_lossy();
916        let c_path = std::ffi::CString::new(path_str.as_ref())?;
917        let ret = unsafe {
918            sys::MaaGlobalSetOption(
919                sys::MaaGlobalOptionEnum_MaaGlobalOption_LogDir as i32,
920                c_path.as_ptr() as *mut _,
921                c_path.as_bytes().len() as u64,
922            )
923        };
924        common::check_bool(ret)
925    }
926
927    /// Set whether to save debug drawings.
928    pub fn set_save_draw(save: bool) -> MaaResult<()> {
929        let val = if save { 1 } else { 0 };
930        let ret = unsafe {
931            sys::MaaGlobalSetOption(
932                sys::MaaGlobalOptionEnum_MaaGlobalOption_SaveDraw as i32,
933                &val as *const _ as *mut _,
934                std::mem::size_of_val(&val) as u64,
935            )
936        };
937        common::check_bool(ret)
938    }
939
940    /// Set the stdout logging level.
941    pub fn set_stdout_level(level: sys::MaaLoggingLevel) -> MaaResult<()> {
942        let ret = unsafe {
943            sys::MaaGlobalSetOption(
944                sys::MaaGlobalOptionEnum_MaaGlobalOption_StdoutLevel as i32,
945                &level as *const _ as *mut _,
946                std::mem::size_of_val(&level) as u64,
947            )
948        };
949        common::check_bool(ret)
950    }
951
952    /// Enable or disable debug mode (raw image capture, etc).
953    pub fn set_debug_mode(debug: bool) -> MaaResult<()> {
954        let val = if debug { 1 } else { 0 };
955        let ret = unsafe {
956            sys::MaaGlobalSetOption(
957                sys::MaaGlobalOptionEnum_MaaGlobalOption_DebugMode as i32,
958                &val as *const _ as *mut _,
959                std::mem::size_of_val(&val) as u64,
960            )
961        };
962        common::check_bool(ret)
963    }
964
965    /// Set whether to save screenshots on error.
966    pub fn set_save_on_error(save: bool) -> MaaResult<()> {
967        let val = if save { 1 } else { 0 };
968        let ret = unsafe {
969            sys::MaaGlobalSetOption(
970                sys::MaaGlobalOptionEnum_MaaGlobalOption_SaveOnError as i32,
971                &val as *const _ as *mut _,
972                std::mem::size_of_val(&val) as u64,
973            )
974        };
975        common::check_bool(ret)
976    }
977
978    /// Set the JPEG quality for debug drawings (0-100).
979    pub fn set_draw_quality(quality: i32) -> MaaResult<()> {
980        let ret = unsafe {
981            sys::MaaGlobalSetOption(
982                sys::MaaGlobalOptionEnum_MaaGlobalOption_DrawQuality as i32,
983                &quality as *const _ as *mut _,
984                std::mem::size_of_val(&quality) as u64,
985            )
986        };
987        common::check_bool(ret)
988    }
989
990    /// Set the limit for recognition image cache.
991    pub fn set_reco_image_cache_limit(limit: usize) -> MaaResult<()> {
992        let ret = unsafe {
993            sys::MaaGlobalSetOption(
994                sys::MaaGlobalOptionEnum_MaaGlobalOption_RecoImageCacheLimit as i32,
995                &limit as *const _ as *mut _,
996                std::mem::size_of_val(&limit) as u64,
997            )
998        };
999        common::check_bool(ret)
1000    }
1001
1002    /// Load a plugin from the specified path.
1003    pub fn load_plugin<P: AsRef<std::path::Path>>(path: P) -> MaaResult<()> {
1004        let path_str = path.as_ref().to_string_lossy();
1005        let c_path = std::ffi::CString::new(path_str.as_ref())?;
1006        let ret = unsafe { sys::MaaGlobalLoadPlugin(c_path.as_ptr()) };
1007        common::check_bool(ret)
1008    }
1009}
1010
1011impl Drop for TaskerInner {
1012    fn drop(&mut self) {
1013        unsafe {
1014            if self.owns_handle {
1015                sys::MaaTaskerClearSinks(self.handle.as_ptr());
1016            } else {
1017                let callbacks_keys: Vec<_> =
1018                    self.callbacks.lock().unwrap().keys().copied().collect();
1019                for id in callbacks_keys {
1020                    sys::MaaTaskerRemoveSink(self.handle.as_ptr(), id);
1021                }
1022                let event_sinks_keys: Vec<_> =
1023                    self.event_sinks.lock().unwrap().keys().copied().collect();
1024                for id in event_sinks_keys {
1025                    sys::MaaTaskerRemoveSink(self.handle.as_ptr(), id);
1026                }
1027            }
1028
1029            let mut callbacks = self.callbacks.lock().unwrap();
1030            for (_, ptr) in callbacks.drain() {
1031                crate::callback::EventCallback::drop_callback(ptr as *mut c_void);
1032            }
1033            let mut event_sinks = self.event_sinks.lock().unwrap();
1034            for (_, ptr) in event_sinks.drain() {
1035                crate::callback::EventCallback::drop_sink(ptr as *mut c_void);
1036            }
1037
1038            if self.owns_handle {
1039                sys::MaaTaskerDestroy(self.handle.as_ptr());
1040            }
1041        }
1042    }
1043}