Skip to main content

maa_framework/
context.rs

1//! Task execution context for pipeline runtime operations.
2
3use std::ffi::CString;
4use std::ptr::NonNull;
5
6use crate::{common, sys, MaaError, MaaResult};
7
8/// Represents the execution context of a task.
9///
10/// This struct provides an interface for interacting with the current task's runtime state.
11/// Capabilities include:
12/// - Executing sub-tasks.
13/// - Overriding pipeline configurations dynamically.
14/// - Performing direct recognition and actions.
15/// - Managing node hit counts and control flow anchors.
16///
17/// # Safety
18///
19/// `Context` is a wrapper around a non-owning pointer (`MaaContext`).
20/// The underlying resources are managed by the `Tasker`. Users must ensure the `Context`
21/// does not outlive the validity of the underlying task or callback scope.
22pub struct Context {
23    handle: NonNull<sys::MaaContext>,
24}
25
26unsafe impl Send for Context {}
27
28impl std::fmt::Debug for Context {
29    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
30        f.debug_struct("Context")
31            .field("handle", &self.handle)
32            .finish()
33    }
34}
35
36impl Context {
37    pub(crate) unsafe fn from_raw(ptr: *mut sys::MaaContext) -> Option<Self> {
38        NonNull::new(ptr).map(|handle| Self { handle })
39    }
40
41    /// Submits a new task for execution.
42    ///
43    /// # Arguments
44    ///
45    /// * `entry` - The name of the task entry point.
46    /// * `pipeline_override` - A JSON string specifying pipeline parameter overrides.
47    ///
48    /// # Returns
49    ///
50    /// Returns the job ID (`MaaId`) associated with the submitted task.
51    pub fn run_task(&self, entry: &str, pipeline_override: &str) -> MaaResult<i64> {
52        let c_entry = CString::new(entry)?;
53        let c_pipeline = CString::new(pipeline_override)?;
54        let id = unsafe {
55            sys::MaaContextRunTask(self.handle.as_ptr(), c_entry.as_ptr(), c_pipeline.as_ptr())
56        };
57        Ok(id)
58    }
59
60    /// Overrides pipeline parameters for the current context.
61    ///
62    /// # Arguments
63    ///
64    /// * `override_json` - A JSON string containing the parameters to override.
65    pub fn override_pipeline(&self, override_json: &str) -> MaaResult<()> {
66        let c_json = CString::new(override_json)?;
67        let ret = unsafe { sys::MaaContextOverridePipeline(self.handle.as_ptr(), c_json.as_ptr()) };
68        common::check_bool(ret)
69    }
70
71    /// Returns the underlying raw `MaaContext` pointer.
72    ///
73    /// Useful for advanced FFI interop scenarios.
74    #[inline]
75    pub fn raw(&self) -> *mut sys::MaaContext {
76        self.handle.as_ptr()
77    }
78
79    /// Runs a specific recognition task with an input image.
80    ///
81    /// # Arguments
82    ///
83    /// * `entry` - The task entry name.
84    /// * `pipeline_override` - A JSON string for parameter overrides.
85    /// * `image` - The input image buffer.
86    ///
87    /// # Returns
88    ///
89    /// Returns the job ID associated with the recognition task.
90    pub fn run_recognition(
91        &self,
92        entry: &str,
93        pipeline_override: &str,
94        image: &crate::buffer::MaaImageBuffer,
95    ) -> MaaResult<i64> {
96        let c_entry = CString::new(entry)?;
97        let c_pipeline = CString::new(pipeline_override)?;
98        let id = unsafe {
99            sys::MaaContextRunRecognition(
100                self.handle.as_ptr(),
101                c_entry.as_ptr(),
102                c_pipeline.as_ptr(),
103                image.as_ptr(),
104            )
105        };
106        Ok(id)
107    }
108
109    /// Performs a direct recognition operation.
110    ///
111    /// This executes a specific recognition algorithm immediately, bypassing the pipeline structure.
112    ///
113    /// # Arguments
114    ///
115    /// * `reco_type` - The specific recognition algorithm type (e.g., "TemplateMatch", "OCR").
116    /// * `reco_param` - A JSON string containing the recognition parameters.
117    /// * `image` - The image buffer to perform recognition on.
118    ///
119    /// # Returns
120    ///
121    /// Returns `Some(RecognitionDetail)` if successful, or `None` if the operation failed
122    /// to initiate or yielded no result.
123    pub fn run_recognition_direct(
124        &self,
125        reco_type: &str,
126        reco_param: &str,
127        image: &crate::buffer::MaaImageBuffer,
128    ) -> MaaResult<Option<crate::common::RecognitionDetail>> {
129        let c_type = CString::new(reco_type)?;
130        let c_param = CString::new(reco_param)?;
131        let id = unsafe {
132            sys::MaaContextRunRecognitionDirect(
133                self.handle.as_ptr(),
134                c_type.as_ptr(),
135                c_param.as_ptr(),
136                image.as_ptr(),
137            )
138        };
139
140        if id == 0 {
141            return Ok(None);
142        }
143
144        let tasker_ptr = self.tasker_handle();
145        crate::tasker::Tasker::fetch_recognition_detail(tasker_ptr, id)
146    }
147
148    /// Runs a specific action with context parameters.
149    ///
150    /// # Arguments
151    ///
152    /// * `entry` - The task entry name.
153    /// * `pipeline_override` - A JSON string for parameter overrides.
154    /// * `box_rect` - The target region for the action.
155    /// * `reco_detail` - A string containing details from a previous recognition step.
156    ///
157    /// # Returns
158    ///
159    /// Returns the job ID associated with the action.
160    pub fn run_action(
161        &self,
162        entry: &str,
163        pipeline_override: &str,
164        box_rect: &common::Rect,
165        reco_detail: &str,
166    ) -> MaaResult<i64> {
167        let c_entry = CString::new(entry)?;
168        let c_pipeline = CString::new(pipeline_override)?;
169        let c_detail = CString::new(reco_detail)?;
170        let maa_rect = sys::MaaRect {
171            x: box_rect.x,
172            y: box_rect.y,
173            width: box_rect.width,
174            height: box_rect.height,
175        };
176        let id = unsafe {
177            sys::MaaContextRunAction(
178                self.handle.as_ptr(),
179                c_entry.as_ptr(),
180                c_pipeline.as_ptr(),
181                &maa_rect,
182                c_detail.as_ptr(),
183            )
184        };
185        Ok(id)
186    }
187
188    /// Performs a direct action operation.
189    ///
190    /// This executes a specific action immediately, bypassing the pipeline structure.
191    ///
192    /// # Arguments
193    ///
194    /// * `action_type` - The type of action to perform (e.g., "Click", "Swipe").
195    /// * `action_param` - A JSON string containing the action parameters.
196    /// * `box_rect` - The target region for the action (e.g., derived from recognition results).
197    /// * `reco_detail` - Contextual details from a previous recognition step.
198    ///
199    /// # Returns
200    ///
201    /// Returns `Some(ActionDetail)` on success, or `None` if the operation failed.
202    pub fn run_action_direct(
203        &self,
204        action_type: &str,
205        action_param: &str,
206        box_rect: &common::Rect,
207        reco_detail: &str,
208    ) -> MaaResult<Option<crate::common::ActionDetail>> {
209        let c_type = CString::new(action_type)?;
210        let c_param = CString::new(action_param)?;
211        let c_detail = CString::new(reco_detail)?;
212        let maa_rect = sys::MaaRect {
213            x: box_rect.x,
214            y: box_rect.y,
215            width: box_rect.width,
216            height: box_rect.height,
217        };
218
219        let id = unsafe {
220            sys::MaaContextRunActionDirect(
221                self.handle.as_ptr(),
222                c_type.as_ptr(),
223                c_param.as_ptr(),
224                &maa_rect,
225                c_detail.as_ptr(),
226            )
227        };
228
229        if id == 0 {
230            return Ok(None);
231        }
232
233        let tasker_ptr = self.tasker_handle();
234        crate::tasker::Tasker::fetch_action_detail(tasker_ptr, id)
235    }
236
237    /// Overrides the execution list for a specific node.
238    ///
239    /// # Arguments
240    ///
241    /// * `node_name` - The name of the target node.
242    /// * `next_list` - A slice of strings representing the new next list.
243    ///   Supports special signals like `[JumpBack]` and `[Anchor]`.
244    ///
245    /// # Returns
246    ///
247    /// * `Ok(true)` if the override was successful.
248    /// * `Ok(false)` if the operation failed.
249    pub fn override_next(&self, node_name: &str, next_list: &[&str]) -> MaaResult<bool> {
250        let c_name = CString::new(node_name)?;
251        let list_buf = crate::buffer::MaaStringListBuffer::new()?;
252        list_buf.set(next_list)?;
253
254        let ret = unsafe {
255            sys::MaaContextOverrideNext(self.handle.as_ptr(), c_name.as_ptr(), list_buf.as_ptr())
256        };
257        Ok(ret != 0)
258    }
259
260    /// Retrieves data associated with a specific node.
261    ///
262    /// # Arguments
263    ///
264    /// * `node_name` - The name of the node.
265    ///
266    /// # Returns
267    ///
268    /// Returns the node data as a `String` if available, or `None` otherwise.
269    pub fn get_node_data(&self, node_name: &str) -> MaaResult<Option<String>> {
270        let c_name = CString::new(node_name)?;
271        let buffer = crate::buffer::MaaStringBuffer::new()?;
272        let ret = unsafe {
273            sys::MaaContextGetNodeData(self.handle.as_ptr(), c_name.as_ptr(), buffer.as_ptr())
274        };
275        if ret != 0 {
276            Ok(Some(buffer.to_string()))
277        } else {
278            Ok(None)
279        }
280    }
281
282    /// Retrieves and deserializes the object associated with a node.
283    ///
284    /// This is a convenience wrapper around `get_node_data` that parses the result into `PipelineData`.
285    ///
286    /// # Arguments
287    ///
288    /// * `node_name` - The name of the node.
289    ///
290    /// # Errors
291    ///
292    /// Returns `MaaError::InvalidConfig` if the data cannot be parsed.
293    pub fn get_node_object(
294        &self,
295        node_name: &str,
296    ) -> MaaResult<Option<crate::pipeline::PipelineData>> {
297        if let Some(json_str) = self.get_node_data(node_name)? {
298            let data: crate::pipeline::PipelineData =
299                serde_json::from_str(&json_str).map_err(|e| {
300                    MaaError::InvalidConfig(format!("Failed to parse pipeline data: {}", e))
301                })?;
302            Ok(Some(data))
303        } else {
304            Ok(None)
305        }
306    }
307
308    /// Returns the ID of the current task.
309    pub fn task_id(&self) -> common::MaaId {
310        unsafe { sys::MaaContextGetTaskId(self.handle.as_ptr()) }
311    }
312
313    /// Associates an anchor with a specific node.
314    ///
315    /// # Arguments
316    ///
317    /// * `anchor_name` - The name of the anchor.
318    /// * `node_name` - The name of the target node.
319    pub fn set_anchor(&self, anchor_name: &str, node_name: &str) -> MaaResult<()> {
320        let c_anchor = CString::new(anchor_name)?;
321        let c_node = CString::new(node_name)?;
322        let ret = unsafe {
323            sys::MaaContextSetAnchor(self.handle.as_ptr(), c_anchor.as_ptr(), c_node.as_ptr())
324        };
325        common::check_bool(ret)
326    }
327
328    /// Retrieves the node name associated with an anchor.
329    ///
330    /// # Arguments
331    ///
332    /// * `anchor_name` - The name of the anchor.
333    ///
334    /// # Returns
335    ///
336    /// Returns the node name as a `String` if the anchor exists.
337    pub fn get_anchor(&self, anchor_name: &str) -> MaaResult<Option<String>> {
338        let c_anchor = CString::new(anchor_name)?;
339        let buffer = crate::buffer::MaaStringBuffer::new()?;
340        let ret = unsafe {
341            sys::MaaContextGetAnchor(self.handle.as_ptr(), c_anchor.as_ptr(), buffer.as_ptr())
342        };
343        if ret != 0 {
344            Ok(Some(buffer.to_string()))
345        } else {
346            Ok(None)
347        }
348    }
349
350    /// Retrieves the hit count for a specific node.
351    ///
352    /// Hit count tracks how many times a node has been executed.
353    ///
354    /// # Arguments
355    ///
356    /// * `node_name` - The name of the node to query.
357    ///
358    /// # Returns
359    ///
360    /// The number of times the node has been executed, or 0 if not found.
361    pub fn get_hit_count(&self, node_name: &str) -> MaaResult<u64> {
362        let c_name = CString::new(node_name)?;
363        let mut count: u64 = 0;
364        let ret = unsafe {
365            sys::MaaContextGetHitCount(self.handle.as_ptr(), c_name.as_ptr(), &mut count)
366        };
367        if ret != 0 {
368            Ok(count)
369        } else {
370            Ok(0)
371        }
372    }
373
374    /// Resets the hit count for a specific node to zero.
375    ///
376    /// # Arguments
377    ///
378    /// * `node_name` - The name of the node to reset.
379    pub fn clear_hit_count(&self, node_name: &str) -> MaaResult<()> {
380        let c_name = CString::new(node_name)?;
381        let ret = unsafe { sys::MaaContextClearHitCount(self.handle.as_ptr(), c_name.as_ptr()) };
382        common::check_bool(ret)
383    }
384
385    /// Waits for the screen to freeze (stop changing).
386    ///
387    /// This method blocks until the specified screen region remains stable for the given time,
388    /// or until the timeout is reached.
389    ///
390    /// # Arguments
391    ///
392    /// * `time` - Time in milliseconds the screen must remain stable (0 to use param).
393    /// * `hit_box` - Optional region of interest as (x, y, width, height). `None` uses full screen.
394    /// * `wait_freezes_param` - Optional detailed wait configuration.
395    ///
396    /// # Returns
397    ///
398    /// Returns `Ok(())` if the screen froze as expected, or an error otherwise.
399    ///
400    /// # Note
401    ///
402    /// - `time` and `wait_freezes_param.time` are mutually exclusive.
403    /// - `hit_box` and `wait_freezes_param.target` are mutually exclusive.
404    pub fn wait_freezes(
405        &self,
406        time: u64,
407        hit_box: Option<(i32, i32, i32, i32)>,
408        wait_freezes_param: Option<&crate::pipeline::WaitFreezes>,
409    ) -> MaaResult<()> {
410        let rect_storage = hit_box.map(|(x, y, w, h)| sys::MaaRect {
411            x,
412            y,
413            width: w,
414            height: h,
415        });
416
417        let rect_ptr = rect_storage
418            .as_ref()
419            .map(|r| r as *const _)
420            .unwrap_or(std::ptr::null());
421
422        let param_json = if let Some(param) = wait_freezes_param {
423            serde_json::to_string(param)
424                .map_err(|e| MaaError::InvalidConfig(format!("Failed to serialize: {}", e)))?
425        } else {
426            "{}".to_string()
427        };
428
429        let c_param = CString::new(param_json)?;
430
431        let ret = unsafe {
432            sys::MaaContextWaitFreezes(self.handle.as_ptr(), time, rect_ptr, c_param.as_ptr())
433        };
434
435        common::check_bool(ret)
436    }
437
438    /// Creates a clone of the current context.
439    ///
440    /// The new context can be used for independent execution threads, preventing
441    /// state interference with the original context.
442    pub fn clone_context(&self) -> MaaResult<Self> {
443        let cloned = unsafe { sys::MaaContextClone(self.handle.as_ptr()) };
444        NonNull::new(cloned)
445            .map(|handle| Self { handle })
446            .ok_or(crate::MaaError::NullPointer)
447    }
448
449    /// Returns the raw handle to the associated `Tasker` instance.
450    ///
451    /// # Safety
452    ///
453    /// The returned pointer is owned by the framework. The caller must not
454    /// attempt to destroy or free it.
455    pub fn tasker_handle(&self) -> *mut sys::MaaTasker {
456        unsafe { sys::MaaContextGetTasker(self.handle.as_ptr()) }
457    }
458
459    /// Overrides a global image resource with a provided buffer.
460    ///
461    /// # Arguments
462    ///
463    /// * `image_name` - The identifier of the image to override.
464    /// * `image` - The new image data buffer.
465    pub fn override_image(
466        &self,
467        image_name: &str,
468        image: &crate::buffer::MaaImageBuffer,
469    ) -> MaaResult<()> {
470        let c_name = CString::new(image_name)?;
471        let ret = unsafe {
472            sys::MaaContextOverrideImage(self.handle.as_ptr(), c_name.as_ptr(), image.as_ptr())
473        };
474        common::check_bool(ret)
475    }
476
477    /// Retrieves a job handle representing the current task's execution state.
478    ///
479    /// The returned job is bound to the lifetime of the `Context` to ensure safety.
480    pub fn get_task_job<'a>(&'a self) -> crate::job::JobWithResult<'a, common::TaskDetail> {
481        let task_id = self.task_id();
482        let tasker_raw = self.tasker_handle() as usize;
483
484        let status_fn: crate::job::StatusFn = Box::new(move |job_id| {
485            let ptr = tasker_raw as *mut sys::MaaTasker;
486            common::MaaStatus(unsafe { sys::MaaTaskerStatus(ptr, job_id) })
487        });
488
489        let wait_fn: crate::job::WaitFn = Box::new(move |job_id| {
490            let ptr = tasker_raw as *mut sys::MaaTasker;
491            common::MaaStatus(unsafe { sys::MaaTaskerWait(ptr, job_id) })
492        });
493
494        let get_fn = move |tid: common::MaaId| -> MaaResult<Option<common::TaskDetail>> {
495            let ptr = tasker_raw as *mut sys::MaaTasker;
496            crate::tasker::Tasker::fetch_task_detail(ptr, tid)
497        };
498
499        crate::job::JobWithResult::new(task_id, status_fn, wait_fn, get_fn)
500    }
501
502    /// Resets hit counts for all nodes in the context.
503    ///
504    /// This clears the execution history for all nodes, resetting their hit counts to zero.
505    pub fn clear_all_hit_counts(&self) -> MaaResult<()> {
506        let ret = unsafe { sys::MaaContextClearHitCount(self.handle.as_ptr(), std::ptr::null()) };
507        common::check_bool(ret)
508    }
509}