Skip to main content

maa_framework/
pipeline.rs

1//! Pipeline configuration types for recognition and action definitions.
2
3use serde::{Deserialize, Deserializer, Serialize, de::DeserializeOwned};
4use serde_json::Value;
5use std::collections::HashMap;
6
7pub use crate::common::Rect;
8
9// --- Custom Deserializers for Scalar/Array Polymorphism ---
10// The C API may return either a scalar or an array for some fields.
11
12/// Deserialize a value that can be either T or Vec<T> into Vec<T>
13fn scalar_or_vec<'de, D, T>(deserializer: D) -> Result<Vec<T>, D::Error>
14where
15    D: Deserializer<'de>,
16    T: DeserializeOwned,
17{
18    let value = Value::deserialize(deserializer)?;
19
20    // Try to parse as Vec<T> first
21    if let Ok(vec) = serde_json::from_value::<Vec<T>>(value.clone()) {
22        return Ok(vec);
23    }
24
25    // Fallback to T
26    if let Ok(single) = serde_json::from_value::<T>(value) {
27        return Ok(vec![single]);
28    }
29
30    Err(serde::de::Error::custom("Expected T or Vec<T>"))
31}
32
33// --- Common Types ---
34
35/// Region of interest: (x, y, width, height). Use [0, 0, 0, 0] for full screen.
36pub type Roi = (i32, i32, i32, i32);
37
38/// Target can be:
39/// - true: recognized position
40/// - "NodeName": position from previously executed node
41/// - \[ x, y \]: point (2 elements)
42/// - \[ x, y, w, h \]: area (4 elements)
43#[derive(Serialize, Deserialize, Debug, Clone)]
44#[serde(untagged)]
45pub enum Target {
46    Bool(bool),
47    Name(String),
48    Point((i32, i32)),
49    Rect(Rect),
50}
51
52impl Default for Target {
53    fn default() -> Self {
54        Target::Bool(true)
55    }
56}
57
58/// Anchor configuration.
59///
60/// Can be:
61/// - String: Set anchor to current node.
62/// - List of strings: Set multiple anchors to current node.
63/// - Map: Set anchors to specific nodes (or clear if empty).
64#[derive(Serialize, Deserialize, Debug, Clone)]
65#[serde(untagged)]
66pub enum Anchor {
67    Name(String),
68    List(Vec<String>),
69    Map(HashMap<String, String>),
70}
71
72impl Default for Anchor {
73    fn default() -> Self {
74        Anchor::List(Vec::new())
75    }
76}
77
78// --- Node Attribute ---
79
80/// Node attribute for specifying behavior in `next` and `on_error` lists.
81///
82/// Allows setting additional control parameters when referencing nodes.
83#[derive(Serialize, Deserialize, Debug, Clone, Default)]
84pub struct NodeAttr {
85    /// Node name to reference.
86    #[serde(default)]
87    pub name: String,
88    /// Whether to return to this node after the referenced node completes.
89    #[serde(default)]
90    pub jump_back: bool,
91    /// Whether to use an anchor reference.
92    #[serde(default)]
93    pub anchor: bool,
94}
95
96// --- Wait Freezes ---
97
98/// Configuration for waiting until the screen stops changing.
99///
100/// Used in `pre_wait_freezes`, `post_wait_freezes`, and `repeat_wait_freezes`
101/// to wait for the screen to stabilize before/after actions.
102#[derive(Serialize, Deserialize, Debug, Clone)]
103pub struct WaitFreezes {
104    /// Duration in milliseconds the screen must remain stable. Default: 1.
105    #[serde(default = "default_wait_time")]
106    pub time: i32,
107    /// Target area to monitor for changes.
108    #[serde(default)]
109    pub target: Target,
110    /// Offset applied to the target area.
111    #[serde(default)]
112    pub target_offset: Rect,
113    /// Similarity threshold for detecting changes. Default: 0.95.
114    #[serde(default = "default_wait_threshold")]
115    pub threshold: f64,
116    /// Comparison method (cv::TemplateMatchModes). Default: 5.
117    #[serde(default = "default_wait_method")]
118    pub method: i32,
119    /// Minimum interval between checks in milliseconds. Default: 1000.
120    #[serde(default = "default_rate_limit")]
121    pub rate_limit: i32,
122    /// Overall timeout in milliseconds. Default: 20000.
123    #[serde(default = "default_timeout")]
124    pub timeout: i32,
125}
126
127impl Default for WaitFreezes {
128    fn default() -> Self {
129        Self {
130            time: default_wait_time(),
131            target: Target::default(),
132            target_offset: Rect::default(),
133            threshold: default_wait_threshold(),
134            method: default_wait_method(),
135            rate_limit: default_rate_limit(),
136            timeout: default_timeout(),
137        }
138    }
139}
140
141// --- Recognition Enums ---
142
143/// Recognition algorithm types.
144///
145/// Determines how the framework identifies targets on screen:
146/// - [`DirectHit`] - No recognition, always matches
147/// - [`TemplateMatch`] - Image template matching
148/// - [`FeatureMatch`] - Feature-based matching (rotation/scale invariant)
149/// - [`ColorMatch`] - Color-based matching
150/// - [`OCR`] - Optical character recognition
151/// - [`NeuralNetworkClassify`] - Deep learning classification
152/// - [`NeuralNetworkDetect`] - Deep learning object detection
153/// - [`And`] - Logical AND of multiple recognitions
154/// - [`Or`] - Logical OR of multiple recognitions
155/// - `Custom` - User-defined recognition
156#[derive(Serialize, Deserialize, Debug, Clone)]
157#[serde(tag = "type", content = "param")]
158pub enum Recognition {
159    DirectHit(DirectHit),
160    TemplateMatch(TemplateMatch),
161    FeatureMatch(FeatureMatch),
162    ColorMatch(ColorMatch),
163    OCR(OCR),
164    NeuralNetworkClassify(NeuralNetworkClassify),
165    NeuralNetworkDetect(NeuralNetworkDetect),
166    And(And),
167    Or(Or),
168    Custom(CustomRecognition),
169}
170
171/// Reference to a recognition: either an inline definition or a node name.
172#[derive(Serialize, Deserialize, Debug, Clone)]
173#[serde(untagged)]
174pub enum RecognitionRef {
175    NodeName(String),
176    Inline(Recognition),
177}
178
179// --- Specific Recognition Structs ---
180
181/// Direct hit recognition - always matches without performing actual recognition.
182///
183/// Use when you want to execute an action without image matching.
184#[derive(Serialize, Deserialize, Debug, Clone, Default)]
185pub struct DirectHit {
186    /// Recognition region. Default: \\[0,0,0,0\\] (full screen).
187    #[serde(default = "default_roi_zero")]
188    pub roi: Target,
189    /// Offset applied to the ROI.
190    #[serde(default)]
191    pub roi_offset: Rect,
192}
193
194/// Template matching recognition - finds images using OpenCV template matching.
195///
196/// The most common recognition method for "finding images" on screen.
197#[derive(Serialize, Deserialize, Debug, Clone)]
198pub struct TemplateMatch {
199    /// Template image paths relative to `image` folder. Required.
200    #[serde(deserialize_with = "scalar_or_vec")]
201    pub template: Vec<String>,
202    /// Recognition region. Default: \\[0,0,0,0\\] (full screen).
203    #[serde(default = "default_roi_zero")]
204    pub roi: Target,
205    /// Offset applied to the ROI.
206    #[serde(default)]
207    pub roi_offset: Rect,
208    /// Matching threshold(s). Default: [0.7].
209    #[serde(default = "default_threshold", deserialize_with = "scalar_or_vec")]
210    pub threshold: Vec<f64>,
211    /// Result sorting: "Horizontal", "Vertical", "Score", "Random". Default: "Horizontal".
212    #[serde(default = "default_order_by")]
213    pub order_by: String,
214    /// Which result to select (0-indexed, negative for reverse). Default: 0.
215    #[serde(default)]
216    pub index: i32,
217    /// OpenCV matching method (cv::TemplateMatchModes). Default: 5 (TM_CCOEFF_NORMED).
218    #[serde(default = "default_template_method")]
219    pub method: i32,
220    /// Use green (0,255,0) as mask color. Default: false.
221    #[serde(default)]
222    pub green_mask: bool,
223}
224
225/// Feature-based matching - scale and rotation invariant image matching.
226///
227/// More robust than template matching for detecting objects under transformation.
228#[derive(Serialize, Deserialize, Debug, Clone)]
229pub struct FeatureMatch {
230    /// Template image paths relative to `image` folder. Required.
231    #[serde(deserialize_with = "scalar_or_vec")]
232    pub template: Vec<String>,
233    /// Recognition region. Default: \\[0,0,0,0\\] (full screen).
234    #[serde(default = "default_roi_zero")]
235    pub roi: Target,
236    /// Offset applied to the ROI.
237    #[serde(default)]
238    pub roi_offset: Rect,
239    /// Feature detector: "SIFT", "KAZE", "AKAZE", "BRISK", "ORB". Default: "SIFT".
240    #[serde(default = "default_detector")]
241    pub detector: String,
242    /// Result sorting method. Default: "Horizontal".
243    #[serde(default = "default_order_by")]
244    pub order_by: String,
245    /// Minimum feature point matches required. Default: 4.
246    #[serde(default = "default_feature_count")]
247    pub count: i32,
248    /// Which result to select. Default: 0.
249    #[serde(default)]
250    pub index: i32,
251    /// Use green (0,255,0) as mask color. Default: false.
252    #[serde(default)]
253    pub green_mask: bool,
254    /// KNN distance ratio threshold [0-1.0]. Default: 0.6.
255    #[serde(default = "default_feature_ratio")]
256    pub ratio: f64,
257}
258
259/// Color matching recognition - finds regions by color range.
260///
261/// Matches pixels within specified color bounds.
262#[derive(Serialize, Deserialize, Debug, Clone)]
263pub struct ColorMatch {
264    /// Lower color bounds. Required. Format depends on method.
265    #[serde(deserialize_with = "scalar_or_vec")]
266    pub lower: Vec<Vec<i32>>,
267    /// Upper color bounds. Required. Format depends on method.
268    #[serde(deserialize_with = "scalar_or_vec")]
269    pub upper: Vec<Vec<i32>>,
270    /// Recognition region. Default: \\[0,0,0,0\\] (full screen).
271    #[serde(default = "default_roi_zero")]
272    pub roi: Target,
273    /// Offset applied to the ROI.
274    #[serde(default)]
275    pub roi_offset: Rect,
276    /// Result sorting method. Default: "Horizontal".
277    #[serde(default = "default_order_by")]
278    pub order_by: String,
279    /// Color conversion code (cv::ColorConversionCodes). Default: 4 (RGB).
280    #[serde(default = "default_color_method")]
281    pub method: i32,
282    /// Minimum matching pixel count. Default: 1.
283    #[serde(default = "default_count_one")]
284    pub count: i32,
285    /// Which result to select. Default: 0.
286    #[serde(default)]
287    pub index: i32,
288    /// Only count connected pixels. Default: false.
289    #[serde(default)]
290    pub connected: bool,
291}
292
293/// Optical character recognition - finds and reads text.
294///
295/// Uses OCR model to detect and recognize text in the specified region.
296#[derive(Serialize, Deserialize, Debug, Clone, Default)]
297pub struct OCR {
298    /// Expected text patterns (supports regex). Default: match all.
299    #[serde(default, deserialize_with = "scalar_or_vec")]
300    pub expected: Vec<String>,
301    /// Recognition region. Default: \\[0,0,0,0\\] (full screen).
302    #[serde(default = "default_roi_zero")]
303    pub roi: Target,
304    /// Offset applied to the ROI.
305    #[serde(default)]
306    pub roi_offset: Rect,
307    /// Model confidence threshold. Default: 0.3.
308    #[serde(default = "default_ocr_threshold")]
309    pub threshold: f64,
310    /// Text replacement pairs [[from, to], ...] for fixing OCR errors.
311    #[serde(default)]
312    pub replace: Vec<Vec<String>>,
313    /// Result sorting method. Default: "Horizontal".
314    #[serde(default = "default_order_by")]
315    pub order_by: String,
316    /// Which result to select. Default: 0.
317    #[serde(default)]
318    pub index: i32,
319    /// Recognition only (skip detection, requires precise ROI). Default: false.
320    #[serde(default)]
321    pub only_rec: bool,
322    /// Model folder path relative to `model/ocr`. Default: root.
323    #[serde(default)]
324    pub model: String,
325    /// Color filter expression. Default: empty.
326    #[serde(default)]
327    pub color_filter: String,
328}
329
330/// Neural network classification - classifies fixed regions.
331///
332/// Uses ONNX model to classify images at fixed positions.
333#[derive(Serialize, Deserialize, Debug, Clone)]
334pub struct NeuralNetworkClassify {
335    /// Model file path relative to `model/classify`. Required.
336    pub model: String,
337    /// Expected class indices to match. Default: match all.
338    #[serde(default, deserialize_with = "scalar_or_vec")]
339    pub expected: Vec<i32>,
340    /// Recognition region. Default: \\[0,0,0,0\\] (full screen).
341    #[serde(default = "default_roi_zero")]
342    pub roi: Target,
343    /// Offset applied to the ROI.
344    #[serde(default)]
345    pub roi_offset: Rect,
346    /// Class labels for debugging. Default: "Unknown".
347    #[serde(default)]
348    pub labels: Vec<String>,
349    /// Result sorting method. Default: "Horizontal".
350    #[serde(default = "default_order_by")]
351    pub order_by: String,
352    /// Which result to select. Default: 0.
353    #[serde(default)]
354    pub index: i32,
355}
356
357/// Neural network detection - detects objects anywhere on screen.
358///
359/// Uses YOLO-style ONNX model to detect and locate objects.
360#[derive(Serialize, Deserialize, Debug, Clone)]
361pub struct NeuralNetworkDetect {
362    /// Model file path relative to `model/detect`. Required.
363    pub model: String,
364    /// Expected class indices to match. Default: match all.
365    #[serde(default, deserialize_with = "scalar_or_vec")]
366    pub expected: Vec<i32>,
367    /// Recognition region. Default: \\[0,0,0,0\\] (full screen).
368    #[serde(default = "default_roi_zero")]
369    pub roi: Target,
370    /// Offset applied to the ROI.
371    #[serde(default)]
372    pub roi_offset: Rect,
373    /// Class labels (auto-read from model metadata). Default: "Unknown".
374    #[serde(default)]
375    pub labels: Vec<String>,
376    /// Confidence threshold(s). Default: [0.3].
377    #[serde(
378        default = "default_detect_threshold",
379        deserialize_with = "scalar_or_vec"
380    )]
381    pub threshold: Vec<f64>,
382    /// Result sorting method. Default: "Horizontal".
383    #[serde(default = "default_order_by")]
384    pub order_by: String,
385    /// Which result to select. Default: 0.
386    #[serde(default)]
387    pub index: i32,
388}
389
390/// Custom recognition - uses user-registered recognition handler.
391///
392/// Invokes a handler registered via `MaaResourceRegisterCustomRecognition`.
393#[derive(Serialize, Deserialize, Debug, Clone)]
394pub struct CustomRecognition {
395    /// Handler name (as registered). Required.
396    pub custom_recognition: String,
397    /// Recognition region. Default: \\[0,0,0,0\\] (full screen).
398    #[serde(default = "default_roi_zero")]
399    pub roi: Target,
400    /// Offset applied to the ROI.
401    #[serde(default)]
402    pub roi_offset: Rect,
403    /// Custom parameters passed to the handler.
404    #[serde(default)]
405    pub custom_recognition_param: Value,
406}
407
408/// Logical AND recognition - all sub-recognitions must match.
409///
410/// Combines multiple recognitions; succeeds only when all match.
411#[derive(Serialize, Deserialize, Debug, Clone, Default)]
412pub struct And {
413    /// Sub-recognition list. All must match. Required.
414    #[serde(default)]
415    pub all_of: Vec<RecognitionRef>,
416    /// Which sub-recognition's bounding box to use. Default: 0.
417    #[serde(default)]
418    pub box_index: i32,
419}
420
421/// Logical OR recognition - first matching sub-recognition wins.
422///
423/// Combines multiple recognitions; succeeds when any one matches.
424#[derive(Serialize, Deserialize, Debug, Clone, Default)]
425pub struct Or {
426    /// Sub-recognition list. First match wins. Required.
427    #[serde(default)]
428    pub any_of: Vec<RecognitionRef>,
429}
430
431// --- Action Enums ---
432
433/// Action types executed after successful recognition.
434///
435/// - [`DoNothing`] - No action
436/// - [`Click`] - Tap/click
437/// - [`LongPress`] - Long press
438/// - [`Swipe`] - Linear swipe
439/// - [`MultiSwipe`] - Multi-touch swipe
440/// - Touch actions: `TouchDown`, `TouchMove`, [`TouchUp`]
441/// - Key actions: `ClickKey`, [`LongPressKey`], `KeyDown`, `KeyUp`
442/// - [`InputText`] - Text input
443/// - App control: `StartApp`, `StopApp`
444/// - [`StopTask`] - Stop current task
445/// - [`Scroll`] - Mouse wheel scroll
446/// - [`Command`] - Execute local command
447/// - [`Shell`] - Execute ADB shell command
448/// - [`Screencap`] - Save screenshot to file
449/// - `Custom` - User-defined action
450#[derive(Serialize, Deserialize, Debug, Clone)]
451#[serde(tag = "type", content = "param")]
452pub enum Action {
453    DoNothing(DoNothing),
454    Click(Click),
455    LongPress(LongPress),
456    Swipe(Swipe),
457    MultiSwipe(MultiSwipe),
458    TouchDown(Touch),
459    TouchMove(Touch),
460    TouchUp(TouchUp),
461    ClickKey(KeyList),
462    LongPressKey(LongPressKey),
463    KeyDown(SingleKey),
464    KeyUp(SingleKey),
465    InputText(InputText),
466    StartApp(App),
467    StopApp(App),
468    StopTask(StopTask),
469    Scroll(Scroll),
470    Command(Command),
471    Shell(Shell),
472    Screencap(Screencap),
473    Custom(CustomAction),
474}
475
476// --- Action Structs ---
477
478/// Do nothing action.
479#[derive(Serialize, Deserialize, Debug, Clone, Default)]
480pub struct DoNothing {}
481
482/// Stop current task chain action.
483#[derive(Serialize, Deserialize, Debug, Clone, Default)]
484pub struct StopTask {}
485
486/// Click/tap action.
487///
488/// Performs a single tap at the target position.
489#[derive(Serialize, Deserialize, Debug, Clone, Default)]
490pub struct Click {
491    /// Click target position. Default: recognized position.
492    #[serde(default)]
493    pub target: Target,
494    /// Offset applied to target.
495    #[serde(default)]
496    pub target_offset: Rect,
497    /// Touch contact/button index. Default: 0.
498    #[serde(default)]
499    pub contact: i32,
500    /// Touch pressure. Default: 1.
501    #[serde(default = "default_pressure")]
502    pub pressure: i32,
503}
504
505/// Long press action.
506///
507/// Performs a sustained press at the target position.
508#[derive(Serialize, Deserialize, Debug, Clone)]
509pub struct LongPress {
510    /// Press target position. Default: recognized position.
511    #[serde(default)]
512    pub target: Target,
513    /// Offset applied to target.
514    #[serde(default)]
515    pub target_offset: Rect,
516    /// Press duration in milliseconds. Default: 1000.
517    #[serde(default = "default_long_press_duration")]
518    pub duration: i32,
519    /// Touch contact/button index. Default: 0.
520    #[serde(default)]
521    pub contact: i32,
522    /// Touch pressure. Default: 1.
523    #[serde(default = "default_pressure")]
524    pub pressure: i32,
525}
526
527/// Linear swipe action.
528///
529/// Swipes from begin to end position(s). Supports waypoints.
530#[derive(Serialize, Deserialize, Debug, Clone)]
531pub struct Swipe {
532    /// Start time offset in ms (for MultiSwipe). Default: 0.
533    #[serde(default)]
534    pub starting: i32,
535    /// Swipe start position. Default: recognized position.
536    #[serde(default)]
537    pub begin: Target,
538    /// Offset applied to begin.
539    #[serde(default)]
540    pub begin_offset: Rect,
541    /// Swipe end position(s). Supports waypoints. Default: recognized position.
542    #[serde(
543        default = "default_target_list_true",
544        deserialize_with = "scalar_or_vec"
545    )]
546    pub end: Vec<Target>,
547    /// Offset(s) applied to end.
548    #[serde(default = "default_rect_list_zero", deserialize_with = "scalar_or_vec")]
549    pub end_offset: Vec<Rect>,
550    /// Hold time at end position(s) in ms. Default: \\[0\\].
551    #[serde(default = "default_i32_list_zero", deserialize_with = "scalar_or_vec")]
552    pub end_hold: Vec<i32>,
553    /// Duration(s) in milliseconds. Default: \\[200\\].
554    #[serde(default = "default_duration_list", deserialize_with = "scalar_or_vec")]
555    pub duration: Vec<i32>,
556    /// Hover only (no press). Default: false.
557    #[serde(default)]
558    pub only_hover: bool,
559    /// Touch contact/button index. Default: 0.
560    #[serde(default)]
561    pub contact: i32,
562    /// Touch pressure. Default: 1.
563    #[serde(default = "default_pressure")]
564    pub pressure: i32,
565}
566
567/// Multi-finger swipe action.
568///
569/// Performs multiple simultaneous swipes (e.g., pinch gestures).
570#[derive(Serialize, Deserialize, Debug, Clone)]
571pub struct MultiSwipe {
572    /// List of swipe configurations.
573    #[serde(default)]
574    pub swipes: Vec<Swipe>,
575}
576
577/// Touch down/move action - initiates or moves a touch point.
578///
579/// Used for custom touch sequences. Pair with TouchUp to complete.
580#[derive(Serialize, Deserialize, Debug, Clone)]
581pub struct Touch {
582    /// Touch contact index. Default: 0.
583    #[serde(default)]
584    pub contact: i32,
585    /// Touch target position. Default: recognized position.
586    #[serde(default)]
587    pub target: Target,
588    /// Offset applied to target.
589    #[serde(default)]
590    pub target_offset: Rect,
591    /// Touch pressure. Default: 0.
592    #[serde(default)]
593    pub pressure: i32,
594}
595
596/// Touch up action - releases a touch point.
597#[derive(Serialize, Deserialize, Debug, Clone)]
598pub struct TouchUp {
599    /// Touch contact index to release. Default: 0.
600    #[serde(default)]
601    pub contact: i32,
602}
603
604/// Long press key action.
605#[derive(Serialize, Deserialize, Debug, Clone)]
606pub struct LongPressKey {
607    /// Virtual key code(s) to press. Required.
608    #[serde(deserialize_with = "scalar_or_vec")]
609    pub key: Vec<i32>,
610    /// Press duration in milliseconds. Default: 1000.
611    #[serde(default = "default_long_press_duration")]
612    pub duration: i32,
613}
614
615/// Click key action - single key press.
616#[derive(Serialize, Deserialize, Debug, Clone)]
617pub struct KeyList {
618    /// Virtual key code(s) to click. Required.
619    #[serde(deserialize_with = "scalar_or_vec")]
620    pub key: Vec<i32>,
621}
622
623/// Single key action - for KeyDown/KeyUp.
624#[derive(Serialize, Deserialize, Debug, Clone)]
625pub struct SingleKey {
626    /// Virtual key code. Required.
627    pub key: i32,
628}
629
630/// Text input action.
631#[derive(Serialize, Deserialize, Debug, Clone)]
632pub struct InputText {
633    /// Text to input (ASCII recommended). Required.
634    pub input_text: String,
635}
636
637/// App control action - for StartApp/StopApp.
638#[derive(Serialize, Deserialize, Debug, Clone)]
639pub struct App {
640    /// Package name or activity (e.g., "com.example.app"). Required.
641    pub package: String,
642}
643
644/// Mouse scroll action (Win32 only).
645#[derive(Serialize, Deserialize, Debug, Clone, Default)]
646pub struct Scroll {
647    /// Scroll target position. Default: recognized position.
648    #[serde(default)]
649    pub target: Target,
650    /// Offset applied to target.
651    #[serde(default)]
652    pub target_offset: Rect,
653    /// Horizontal scroll delta. Default: 0.
654    #[serde(default)]
655    pub dx: i32,
656    /// Vertical scroll delta. Default: 0.
657    #[serde(default)]
658    pub dy: i32,
659}
660
661/// Execute local command action.
662#[derive(Serialize, Deserialize, Debug, Clone)]
663pub struct Command {
664    /// Program path to execute. Required.
665    pub exec: String,
666    /// Command arguments. Supports runtime placeholders.
667    #[serde(default)]
668    pub args: Vec<String>,
669    /// Run in background (don't wait). Default: false.
670    #[serde(default)]
671    pub detach: bool,
672}
673
674/// Execute ADB shell command action.
675#[derive(Serialize, Deserialize, Debug, Clone)]
676pub struct Shell {
677    /// Shell command to execute. Required.
678    pub cmd: String,
679    /// Command timeout in milliseconds. Default: 20000.
680    #[serde(default = "default_timeout")]
681    pub shell_timeout: i32,
682}
683
684/// Screenshot capture action - saves the current screen to a file.
685#[derive(Serialize, Deserialize, Debug, Clone, Default)]
686pub struct Screencap {
687    /// Output filename (without extension). Default: empty.
688    #[serde(default)]
689    pub filename: String,
690    /// Image format: "png", "jpg", etc. Default: "png".
691    #[serde(default = "default_screencap_format")]
692    pub format: String,
693    /// Image quality (0-100). Default: 100.
694    #[serde(default = "default_screencap_quality")]
695    pub quality: i32,
696}
697
698/// Custom action - uses user-registered action handler.
699///
700/// Invokes a handler registered via `MaaResourceRegisterCustomAction`.
701#[derive(Serialize, Deserialize, Debug, Clone)]
702pub struct CustomAction {
703    /// Handler name (as registered). Required.
704    pub custom_action: String,
705    /// Target position passed to handler. Default: recognized position.
706    #[serde(default)]
707    pub target: Target,
708    /// Custom parameters passed to the handler.
709    #[serde(default)]
710    pub custom_action_param: Value,
711    /// Offset applied to target.
712    #[serde(default)]
713    pub target_offset: Rect,
714}
715
716// --- Pipeline Data ---
717
718/// Complete pipeline node configuration.
719///
720/// Defines a node's recognition, action, and flow control parameters.
721#[derive(Serialize, Deserialize, Debug, Clone)]
722pub struct PipelineData {
723    /// Recognition algorithm configuration.
724    pub recognition: Recognition,
725    /// Action to execute on match.
726    pub action: Action,
727    /// Next nodes to check after action. Default: [].
728    #[serde(default)]
729    pub next: Vec<NodeAttr>,
730    /// Recognition rate limit in ms. Default: 1000.
731    #[serde(default = "default_rate_limit")]
732    pub rate_limit: i32,
733    /// Overall timeout in ms. Default: 20000.
734    #[serde(default = "default_timeout")]
735    pub timeout: i32,
736    /// Nodes to check on timeout/error. Default: [].
737    #[serde(default)]
738    pub on_error: Vec<NodeAttr>,
739    /// Anchor names for this node. Default: [].
740    #[serde(default)]
741    pub anchor: Anchor,
742    /// Invert recognition result. Default: false.
743    #[serde(default)]
744    pub inverse: bool,
745    /// Enable this node. Default: true.
746    #[serde(default = "default_enabled")]
747    pub enabled: bool,
748    /// Delay before action in ms. Default: 200.
749    #[serde(default = "default_pre_delay")]
750    pub pre_delay: i32,
751    /// Delay after action in ms. Default: 200.
752    #[serde(default = "default_post_delay")]
753    pub post_delay: i32,
754    /// Wait for screen stability before action.
755    #[serde(default)]
756    pub pre_wait_freezes: Option<WaitFreezes>,
757    /// Wait for screen stability after action.
758    #[serde(default)]
759    pub post_wait_freezes: Option<WaitFreezes>,
760    /// Action repeat count. Default: 1.
761    #[serde(default = "default_repeat")]
762    pub repeat: i32,
763    /// Delay between repeats in ms. Default: 0.
764    #[serde(default)]
765    pub repeat_delay: i32,
766    /// Wait for stability between repeats.
767    #[serde(default)]
768    pub repeat_wait_freezes: Option<WaitFreezes>,
769    /// Maximum successful hits. Default: UINT_MAX.
770    #[serde(default = "default_max_hit")]
771    pub max_hit: u32,
772    /// Focus flag for extra callbacks. Default: null.
773    #[serde(default)]
774    pub focus: Option<Value>,
775    /// Attached custom data (merged with defaults).
776    #[serde(default)]
777    pub attach: Option<Value>,
778}
779
780// --- Defaults Helper Functions ---
781
782fn default_wait_time() -> i32 {
783    1
784}
785fn default_wait_threshold() -> f64 {
786    0.95
787}
788fn default_wait_method() -> i32 {
789    5
790}
791fn default_rate_limit() -> i32 {
792    1000
793}
794fn default_timeout() -> i32 {
795    20000
796}
797fn default_threshold() -> Vec<f64> {
798    vec![0.7]
799}
800fn default_order_by() -> String {
801    "Horizontal".to_string()
802}
803fn default_template_method() -> i32 {
804    5
805}
806fn default_detector() -> String {
807    "SIFT".to_string()
808}
809fn default_feature_count() -> i32 {
810    4
811}
812fn default_feature_ratio() -> f64 {
813    0.6
814}
815fn default_color_method() -> i32 {
816    4
817} // RGB
818fn default_count_one() -> i32 {
819    1
820}
821fn default_ocr_threshold() -> f64 {
822    0.3
823}
824fn default_detect_threshold() -> Vec<f64> {
825    vec![0.3]
826}
827fn default_pressure() -> i32 {
828    1
829}
830fn default_long_press_duration() -> i32 {
831    1000
832}
833fn default_target_list_true() -> Vec<Target> {
834    vec![Target::Bool(true)]
835}
836fn default_rect_list_zero() -> Vec<Rect> {
837    vec![(0, 0, 0, 0).into()]
838}
839fn default_i32_list_zero() -> Vec<i32> {
840    vec![0]
841}
842fn default_duration_list() -> Vec<i32> {
843    vec![200]
844}
845fn default_enabled() -> bool {
846    true
847}
848fn default_pre_delay() -> i32 {
849    200
850}
851fn default_post_delay() -> i32 {
852    200
853}
854fn default_repeat() -> i32 {
855    1
856}
857fn default_max_hit() -> u32 {
858    u32::MAX
859}
860fn default_screencap_format() -> String {
861    "png".to_string()
862}
863fn default_screencap_quality() -> i32 {
864    100
865}
866fn default_roi_zero() -> Target {
867    Target::Rect((0, 0, 0, 0).into())
868}