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}