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