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 /// Creates a clone of the current context.
386 ///
387 /// The new context can be used for independent execution threads, preventing
388 /// state interference with the original context.
389 pub fn clone_context(&self) -> MaaResult<Self> {
390 let cloned = unsafe { sys::MaaContextClone(self.handle.as_ptr()) };
391 NonNull::new(cloned)
392 .map(|handle| Self { handle })
393 .ok_or(crate::MaaError::NullPointer)
394 }
395
396 /// Returns the raw handle to the associated `Tasker` instance.
397 ///
398 /// # Safety
399 ///
400 /// The returned pointer is owned by the framework. The caller must not
401 /// attempt to destroy or free it.
402 pub fn tasker_handle(&self) -> *mut sys::MaaTasker {
403 unsafe { sys::MaaContextGetTasker(self.handle.as_ptr()) }
404 }
405
406 /// Overrides a global image resource with a provided buffer.
407 ///
408 /// # Arguments
409 ///
410 /// * `image_name` - The identifier of the image to override.
411 /// * `image` - The new image data buffer.
412 pub fn override_image(
413 &self,
414 image_name: &str,
415 image: &crate::buffer::MaaImageBuffer,
416 ) -> MaaResult<()> {
417 let c_name = CString::new(image_name)?;
418 let ret = unsafe {
419 sys::MaaContextOverrideImage(self.handle.as_ptr(), c_name.as_ptr(), image.as_ptr())
420 };
421 common::check_bool(ret)
422 }
423
424 /// Retrieves a job handle representing the current task's execution state.
425 ///
426 /// The returned job is bound to the lifetime of the `Context` to ensure safety.
427 pub fn get_task_job<'a>(&'a self) -> crate::job::JobWithResult<'a, common::TaskDetail> {
428 let task_id = self.task_id();
429 let tasker_raw = self.tasker_handle() as usize;
430
431 let status_fn: crate::job::StatusFn = Box::new(move |job_id| {
432 let ptr = tasker_raw as *mut sys::MaaTasker;
433 common::MaaStatus(unsafe { sys::MaaTaskerStatus(ptr, job_id) })
434 });
435
436 let wait_fn: crate::job::WaitFn = Box::new(move |job_id| {
437 let ptr = tasker_raw as *mut sys::MaaTasker;
438 common::MaaStatus(unsafe { sys::MaaTaskerWait(ptr, job_id) })
439 });
440
441 let get_fn = move |tid: common::MaaId| -> MaaResult<Option<common::TaskDetail>> {
442 let ptr = tasker_raw as *mut sys::MaaTasker;
443 crate::tasker::Tasker::fetch_task_detail(ptr, tid)
444 };
445
446 crate::job::JobWithResult::new(task_id, status_fn, wait_fn, get_fn)
447 }
448
449 /// Resets hit counts for all nodes in the context.
450 ///
451 /// This clears the execution history for all nodes, resetting their hit counts to zero.
452 pub fn clear_all_hit_counts(&self) -> MaaResult<()> {
453 let ret = unsafe { sys::MaaContextClearHitCount(self.handle.as_ptr(), std::ptr::null()) };
454 common::check_bool(ret)
455 }
456}