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(InlineRecognition),
177}
178
179/// Inline sub-recognition inside an `And` / `Or` node.
180///
181/// Wire format (MaaFramework >= 5.13.0-beta.6, PipelineDumper and PipelineParser agree):
182/// `{"sub_name": "...", "recognition": {"type": ..., "param": {...}}}`.
183/// `sub_name` is optional on input; the framework defaults it to the recognition type name.
184#[derive(Serialize, Deserialize, Debug, Clone)]
185pub struct InlineRecognition {
186    /// Optional label for this sub-recognition, used in reco detail output.
187    #[serde(default, skip_serializing_if = "Option::is_none")]
188    pub sub_name: Option<String>,
189    /// The inline recognition definition.
190    pub recognition: Recognition,
191}
192
193// --- Specific Recognition Structs ---
194
195/// Direct hit recognition - always matches without performing actual recognition.
196///
197/// Use when you want to execute an action without image matching.
198#[derive(Serialize, Deserialize, Debug, Clone, Default)]
199pub struct DirectHit {
200    /// Recognition region. Default: \\[0,0,0,0\\] (full screen).
201    #[serde(default = "default_roi_zero")]
202    pub roi: Target,
203    /// Offset applied to the ROI.
204    #[serde(default)]
205    pub roi_offset: Rect,
206}
207
208/// Template matching recognition - finds images using OpenCV template matching.
209///
210/// The most common recognition method for "finding images" on screen.
211#[derive(Serialize, Deserialize, Debug, Clone)]
212pub struct TemplateMatch {
213    /// Template image paths relative to `image` folder. Required.
214    #[serde(deserialize_with = "scalar_or_vec")]
215    pub template: Vec<String>,
216    /// Recognition region. Default: \\[0,0,0,0\\] (full screen).
217    #[serde(default = "default_roi_zero")]
218    pub roi: Target,
219    /// Offset applied to the ROI.
220    #[serde(default)]
221    pub roi_offset: Rect,
222    /// Matching threshold(s). Default: [0.7].
223    #[serde(default = "default_threshold", deserialize_with = "scalar_or_vec")]
224    pub threshold: Vec<f64>,
225    /// Result sorting: "Horizontal", "Vertical", "Score", "Random". Default: "Horizontal".
226    #[serde(default = "default_order_by")]
227    pub order_by: String,
228    /// Which result to select (0-indexed, negative for reverse). Default: 0.
229    #[serde(default)]
230    pub index: i32,
231    /// OpenCV matching method (cv::TemplateMatchModes). Default: 5 (TM_CCOEFF_NORMED).
232    #[serde(default = "default_template_method")]
233    pub method: i32,
234    /// Use green (0,255,0) as mask color. Default: false.
235    #[serde(default)]
236    pub green_mask: bool,
237}
238
239/// Feature-based matching - scale and rotation invariant image matching.
240///
241/// More robust than template matching for detecting objects under transformation.
242#[derive(Serialize, Deserialize, Debug, Clone)]
243pub struct FeatureMatch {
244    /// Template image paths relative to `image` folder. Required.
245    #[serde(deserialize_with = "scalar_or_vec")]
246    pub template: Vec<String>,
247    /// Recognition region. Default: \\[0,0,0,0\\] (full screen).
248    #[serde(default = "default_roi_zero")]
249    pub roi: Target,
250    /// Offset applied to the ROI.
251    #[serde(default)]
252    pub roi_offset: Rect,
253    /// Feature detector: "SIFT", "KAZE", "AKAZE", "BRISK", "ORB". Default: "SIFT".
254    #[serde(default = "default_detector")]
255    pub detector: String,
256    /// Result sorting method. Default: "Horizontal".
257    #[serde(default = "default_order_by")]
258    pub order_by: String,
259    /// Minimum feature point matches required. Default: 4.
260    #[serde(default = "default_feature_count")]
261    pub count: i32,
262    /// Which result to select. Default: 0.
263    #[serde(default)]
264    pub index: i32,
265    /// Use green (0,255,0) as mask color. Default: false.
266    #[serde(default)]
267    pub green_mask: bool,
268    /// KNN distance ratio threshold [0-1.0]. Default: 0.6.
269    #[serde(default = "default_feature_ratio")]
270    pub ratio: f64,
271}
272
273/// Color matching recognition - finds regions by color range.
274///
275/// Matches pixels within specified color bounds.
276#[derive(Serialize, Deserialize, Debug, Clone)]
277pub struct ColorMatch {
278    /// Lower color bounds. Format depends on method.
279    /// Omitted or empty falls back to the default pipeline value (empty when unset).
280    #[serde(default, deserialize_with = "scalar_or_vec")]
281    pub lower: Vec<Vec<i32>>,
282    /// Upper color bounds. Format depends on method.
283    /// Omitted or empty falls back to the default pipeline value (empty when unset).
284    #[serde(default, deserialize_with = "scalar_or_vec")]
285    pub upper: Vec<Vec<i32>>,
286    /// Recognition region. Default: \\[0,0,0,0\\] (full screen).
287    #[serde(default = "default_roi_zero")]
288    pub roi: Target,
289    /// Offset applied to the ROI.
290    #[serde(default)]
291    pub roi_offset: Rect,
292    /// Result sorting method. Default: "Horizontal".
293    #[serde(default = "default_order_by")]
294    pub order_by: String,
295    /// Color conversion code (cv::ColorConversionCodes). Default: 4 (RGB).
296    #[serde(default = "default_color_method")]
297    pub method: i32,
298    /// Minimum matching pixel count. Default: 1.
299    #[serde(default = "default_count_one")]
300    pub count: i32,
301    /// Which result to select. Default: 0.
302    #[serde(default)]
303    pub index: i32,
304    /// Only count connected pixels. Default: false.
305    #[serde(default)]
306    pub connected: bool,
307}
308
309/// Optical character recognition - finds and reads text.
310///
311/// Uses OCR model to detect and recognize text in the specified region.
312#[derive(Serialize, Deserialize, Debug, Clone, Default)]
313pub struct OCR {
314    /// Expected text patterns (supports regex). Default: match all.
315    #[serde(default, deserialize_with = "scalar_or_vec")]
316    pub expected: Vec<String>,
317    /// Recognition region. Default: \\[0,0,0,0\\] (full screen).
318    #[serde(default = "default_roi_zero")]
319    pub roi: Target,
320    /// Offset applied to the ROI.
321    #[serde(default)]
322    pub roi_offset: Rect,
323    /// Model confidence threshold. Default: 0.3.
324    #[serde(default = "default_ocr_threshold")]
325    pub threshold: f64,
326    /// Text replacement pairs [[from, to], ...] for fixing OCR errors.
327    #[serde(default)]
328    pub replace: Vec<Vec<String>>,
329    /// Result sorting method. Default: "Horizontal".
330    #[serde(default = "default_order_by")]
331    pub order_by: String,
332    /// Which result to select. Default: 0.
333    #[serde(default)]
334    pub index: i32,
335    /// Recognition only (skip detection, requires precise ROI). Default: false.
336    #[serde(default)]
337    pub only_rec: bool,
338    /// Model folder path relative to `model/ocr`. Default: root.
339    #[serde(default)]
340    pub model: String,
341    /// Color filter expression. Default: empty.
342    #[serde(default)]
343    pub color_filter: String,
344}
345
346/// A neural network class selected by its index or label name.
347#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
348#[serde(untagged)]
349pub enum NeuralNetworkExpected {
350    Index(i32),
351    Label(String),
352}
353
354impl From<i32> for NeuralNetworkExpected {
355    fn from(index: i32) -> Self {
356        Self::Index(index)
357    }
358}
359
360impl From<String> for NeuralNetworkExpected {
361    fn from(label: String) -> Self {
362        Self::Label(label)
363    }
364}
365
366impl From<&str> for NeuralNetworkExpected {
367    fn from(label: &str) -> Self {
368        Self::Label(label.to_owned())
369    }
370}
371
372/// Neural network classification - classifies fixed regions.
373///
374/// Uses ONNX model to classify images at fixed positions.
375#[derive(Serialize, Deserialize, Debug, Clone)]
376pub struct NeuralNetworkClassify {
377    /// Model file path relative to `model/classify`. Required.
378    pub model: String,
379    /// Expected class indices or label names, which may be mixed. Default: match all.
380    /// For example: `vec![0.into(), "Cat".into()]`.
381    #[serde(default, deserialize_with = "scalar_or_vec")]
382    pub expected: Vec<NeuralNetworkExpected>,
383    /// Recognition region. Default: \\[0,0,0,0\\] (full screen).
384    #[serde(default = "default_roi_zero")]
385    pub roi: Target,
386    /// Offset applied to the ROI.
387    #[serde(default)]
388    pub roi_offset: Rect,
389    /// Class labels used for named expectations and result output. Default: "Unknown".
390    #[serde(default)]
391    pub labels: Vec<String>,
392    /// Result sorting method. Default: "Horizontal".
393    #[serde(default = "default_order_by")]
394    pub order_by: String,
395    /// Which result to select. Default: 0.
396    #[serde(default)]
397    pub index: i32,
398}
399
400/// Neural network detection - detects objects anywhere on screen.
401///
402/// Uses YOLO-style ONNX model to detect and locate objects.
403#[derive(Serialize, Deserialize, Debug, Clone)]
404pub struct NeuralNetworkDetect {
405    /// Model file path relative to `model/detect`. Required.
406    pub model: String,
407    /// Expected class indices or label names, which may be mixed. Default: match all.
408    /// For example: `vec![0.into(), "Cat".into()]`.
409    #[serde(default, deserialize_with = "scalar_or_vec")]
410    pub expected: Vec<NeuralNetworkExpected>,
411    /// Recognition region. Default: \\[0,0,0,0\\] (full screen).
412    #[serde(default = "default_roi_zero")]
413    pub roi: Target,
414    /// Offset applied to the ROI.
415    #[serde(default)]
416    pub roi_offset: Rect,
417    /// Class labels used for named expectations and result output.
418    /// Auto-read from model metadata when empty. Default: "Unknown".
419    #[serde(default)]
420    pub labels: Vec<String>,
421    /// Confidence threshold(s). Default: [0.3].
422    #[serde(
423        default = "default_detect_threshold",
424        deserialize_with = "scalar_or_vec"
425    )]
426    pub threshold: Vec<f64>,
427    /// Result sorting method. Default: "Horizontal".
428    #[serde(default = "default_order_by")]
429    pub order_by: String,
430    /// Which result to select. Default: 0.
431    #[serde(default)]
432    pub index: i32,
433}
434
435/// Custom recognition - uses user-registered recognition handler.
436///
437/// Invokes a handler registered via `MaaResourceRegisterCustomRecognition`.
438#[derive(Serialize, Deserialize, Debug, Clone)]
439pub struct CustomRecognition {
440    /// Handler name (as registered). Required.
441    pub custom_recognition: String,
442    /// Recognition region. Default: \\[0,0,0,0\\] (full screen).
443    #[serde(default = "default_roi_zero")]
444    pub roi: Target,
445    /// Offset applied to the ROI.
446    #[serde(default)]
447    pub roi_offset: Rect,
448    /// Custom parameters passed to the handler.
449    #[serde(default)]
450    pub custom_recognition_param: Value,
451}
452
453/// Logical AND recognition - all sub-recognitions must match.
454///
455/// Combines multiple recognitions; succeeds only when all match.
456#[derive(Serialize, Deserialize, Debug, Clone, Default)]
457pub struct And {
458    /// Sub-recognition list. All must match. Required.
459    #[serde(default)]
460    pub all_of: Vec<RecognitionRef>,
461    /// Which sub-recognition's bounding box to use. Default: 0.
462    #[serde(default)]
463    pub box_index: i32,
464}
465
466/// Logical OR recognition - first matching sub-recognition wins.
467///
468/// Combines multiple recognitions; succeeds when any one matches.
469#[derive(Serialize, Deserialize, Debug, Clone, Default)]
470pub struct Or {
471    /// Sub-recognition list. First match wins. Required.
472    #[serde(default)]
473    pub any_of: Vec<RecognitionRef>,
474}
475
476// --- Action Enums ---
477
478/// Action types executed after successful recognition.
479///
480/// - [`DoNothing`] - No action
481/// - [`Click`] - Tap/click
482/// - [`LongPress`] - Long press
483/// - [`Swipe`] - Linear swipe
484/// - [`MultiSwipe`] - Multi-touch swipe
485/// - Touch actions: `TouchDown`, `TouchMove`, [`TouchUp`]
486/// - Key actions: `ClickKey`, [`LongPressKey`], `KeyDown`, `KeyUp`
487/// - [`InputText`] - Text input
488/// - App control: `StartApp`, `StopApp`
489/// - [`StopTask`] - Stop current task
490/// - [`Scroll`] - Mouse wheel scroll
491/// - [`Command`] - Execute local command
492/// - [`Shell`] - Execute ADB shell command
493/// - [`Screencap`] - Save screenshot to file
494/// - `Custom` - User-defined action
495#[derive(Serialize, Deserialize, Debug, Clone)]
496#[serde(tag = "type", content = "param")]
497pub enum Action {
498    DoNothing(DoNothing),
499    Click(Click),
500    LongPress(LongPress),
501    Swipe(Swipe),
502    MultiSwipe(MultiSwipe),
503    TouchDown(Touch),
504    TouchMove(Touch),
505    TouchUp(TouchUp),
506    ClickKey(KeyList),
507    LongPressKey(LongPressKey),
508    KeyDown(SingleKey),
509    KeyUp(SingleKey),
510    InputText(InputText),
511    StartApp(App),
512    StopApp(App),
513    StopTask(StopTask),
514    Scroll(Scroll),
515    Command(Command),
516    Shell(Shell),
517    Screencap(Screencap),
518    Custom(CustomAction),
519}
520
521// --- Action Structs ---
522
523/// Do nothing action.
524#[derive(Serialize, Deserialize, Debug, Clone, Default)]
525pub struct DoNothing {}
526
527/// Stop current task chain action.
528#[derive(Serialize, Deserialize, Debug, Clone, Default)]
529pub struct StopTask {}
530
531/// Click/tap action.
532///
533/// Performs a single tap at the target position.
534#[derive(Serialize, Deserialize, Debug, Clone, Default)]
535pub struct Click {
536    /// Click target position. Default: recognized position.
537    #[serde(default)]
538    pub target: Target,
539    /// Offset applied to target.
540    #[serde(default)]
541    pub target_offset: Rect,
542    /// Touch contact/button index. Default: 0.
543    #[serde(default)]
544    pub contact: i32,
545    /// Touch pressure. Default: 1.
546    #[serde(default = "default_pressure")]
547    pub pressure: i32,
548}
549
550/// Long press action.
551///
552/// Performs a sustained press at the target position.
553#[derive(Serialize, Deserialize, Debug, Clone)]
554pub struct LongPress {
555    /// Press target position. Default: recognized position.
556    #[serde(default)]
557    pub target: Target,
558    /// Offset applied to target.
559    #[serde(default)]
560    pub target_offset: Rect,
561    /// Press duration in milliseconds. Default: 1000.
562    #[serde(default = "default_long_press_duration")]
563    pub duration: i32,
564    /// Touch contact/button index. Default: 0.
565    #[serde(default)]
566    pub contact: i32,
567    /// Touch pressure. Default: 1.
568    #[serde(default = "default_pressure")]
569    pub pressure: i32,
570}
571
572/// Linear swipe action.
573///
574/// Swipes from begin to end position(s). Supports waypoints.
575#[derive(Serialize, Deserialize, Debug, Clone)]
576pub struct Swipe {
577    /// Start time offset in ms (for MultiSwipe). Default: 0.
578    #[serde(default)]
579    pub starting: i32,
580    /// Swipe start position. Default: recognized position.
581    #[serde(default)]
582    pub begin: Target,
583    /// Offset applied to begin.
584    #[serde(default)]
585    pub begin_offset: Rect,
586    /// Swipe end position(s). Supports waypoints. Default: recognized position.
587    #[serde(
588        default = "default_target_list_true",
589        deserialize_with = "scalar_or_vec"
590    )]
591    pub end: Vec<Target>,
592    /// Offset(s) applied to end.
593    #[serde(default = "default_rect_list_zero", deserialize_with = "scalar_or_vec")]
594    pub end_offset: Vec<Rect>,
595    /// Hold time at end position(s) in ms. Default: \\[0\\].
596    #[serde(default = "default_i32_list_zero", deserialize_with = "scalar_or_vec")]
597    pub end_hold: Vec<i32>,
598    /// Duration(s) in milliseconds. Default: \\[200\\].
599    #[serde(default = "default_duration_list", deserialize_with = "scalar_or_vec")]
600    pub duration: Vec<i32>,
601    /// Hover only (no press). Default: false.
602    #[serde(default)]
603    pub only_hover: bool,
604    /// Touch contact/button index. Default: 0.
605    #[serde(default)]
606    pub contact: i32,
607    /// Touch pressure. Default: 1.
608    #[serde(default = "default_pressure")]
609    pub pressure: i32,
610}
611
612/// Multi-finger swipe action.
613///
614/// Performs multiple simultaneous swipes (e.g., pinch gestures).
615#[derive(Serialize, Deserialize, Debug, Clone)]
616pub struct MultiSwipe {
617    /// List of swipe configurations.
618    #[serde(default)]
619    pub swipes: Vec<Swipe>,
620}
621
622/// Touch down/move action - initiates or moves a touch point.
623///
624/// Used for custom touch sequences. Pair with TouchUp to complete.
625#[derive(Serialize, Deserialize, Debug, Clone)]
626pub struct Touch {
627    /// Touch contact index. Default: 0.
628    #[serde(default)]
629    pub contact: i32,
630    /// Touch target position. Default: recognized position.
631    #[serde(default)]
632    pub target: Target,
633    /// Offset applied to target.
634    #[serde(default)]
635    pub target_offset: Rect,
636    /// Touch pressure. Default: 0.
637    #[serde(default)]
638    pub pressure: i32,
639}
640
641/// Touch up action - releases a touch point.
642#[derive(Serialize, Deserialize, Debug, Clone)]
643pub struct TouchUp {
644    /// Touch contact index to release. Default: 0.
645    #[serde(default)]
646    pub contact: i32,
647}
648
649/// Long press key action.
650#[derive(Serialize, Deserialize, Debug, Clone)]
651pub struct LongPressKey {
652    /// Virtual key code(s) to press. Required.
653    #[serde(deserialize_with = "scalar_or_vec")]
654    pub key: Vec<i32>,
655    /// Press duration in milliseconds. Default: 1000.
656    #[serde(default = "default_long_press_duration")]
657    pub duration: i32,
658}
659
660/// Click key action - single key press.
661#[derive(Serialize, Deserialize, Debug, Clone)]
662pub struct KeyList {
663    /// Virtual key code(s) to click. Required.
664    #[serde(deserialize_with = "scalar_or_vec")]
665    pub key: Vec<i32>,
666}
667
668/// Single key action - for KeyDown/KeyUp.
669#[derive(Serialize, Deserialize, Debug, Clone)]
670pub struct SingleKey {
671    /// Virtual key code. Required.
672    pub key: i32,
673}
674
675/// Text input action.
676#[derive(Serialize, Deserialize, Debug, Clone)]
677pub struct InputText {
678    /// Text to input (ASCII recommended). Required.
679    pub input_text: String,
680}
681
682/// App control action - for StartApp/StopApp.
683#[derive(Serialize, Deserialize, Debug, Clone)]
684pub struct App {
685    /// Package name or activity on ADB (e.g., "com.example.app"). Required.
686    /// On Win32, StartApp accepts an executable path or command line; StopApp accepts
687    /// a process name, or an empty string to stop the controller's window process.
688    pub package: String,
689}
690
691/// Mouse scroll action (Win32 only).
692#[derive(Serialize, Deserialize, Debug, Clone, Default)]
693pub struct Scroll {
694    /// Scroll target position. Default: recognized position.
695    #[serde(default)]
696    pub target: Target,
697    /// Offset applied to target.
698    #[serde(default)]
699    pub target_offset: Rect,
700    /// Horizontal scroll delta. Default: 0.
701    #[serde(default)]
702    pub dx: i32,
703    /// Vertical scroll delta. Default: 0.
704    #[serde(default)]
705    pub dy: i32,
706}
707
708/// Execute local command action.
709#[derive(Serialize, Deserialize, Debug, Clone)]
710pub struct Command {
711    /// Program path to execute. Required.
712    pub exec: String,
713    /// Command arguments. Supports runtime placeholders.
714    #[serde(default)]
715    pub args: Vec<String>,
716    /// Run in background (don't wait). Default: false.
717    #[serde(default)]
718    pub detach: bool,
719}
720
721/// Execute ADB shell command action.
722#[derive(Serialize, Deserialize, Debug, Clone)]
723pub struct Shell {
724    /// Shell command to execute. Required.
725    pub cmd: String,
726    /// Command timeout in milliseconds. Default: 20000.
727    #[serde(default = "default_timeout")]
728    pub shell_timeout: i32,
729}
730
731/// Screenshot capture action - saves the current screen to a file.
732#[derive(Serialize, Deserialize, Debug, Clone, Default)]
733pub struct Screencap {
734    /// Output filename (without extension). Default: empty.
735    #[serde(default)]
736    pub filename: String,
737    /// Image format: "png", "jpg", etc. Default: "png".
738    #[serde(default = "default_screencap_format")]
739    pub format: String,
740    /// Image quality (0-100). Default: 100.
741    #[serde(default = "default_screencap_quality")]
742    pub quality: i32,
743}
744
745/// Custom action - uses user-registered action handler.
746///
747/// Invokes a handler registered via `MaaResourceRegisterCustomAction`.
748#[derive(Serialize, Deserialize, Debug, Clone)]
749pub struct CustomAction {
750    /// Handler name (as registered). Required.
751    pub custom_action: String,
752    /// Target position passed to handler. Default: recognized position.
753    #[serde(default)]
754    pub target: Target,
755    /// Custom parameters passed to the handler.
756    #[serde(default)]
757    pub custom_action_param: Value,
758    /// Offset applied to target.
759    #[serde(default)]
760    pub target_offset: Rect,
761}
762
763// --- Pipeline Data ---
764
765/// Complete pipeline node configuration.
766///
767/// Defines a node's recognition, action, and flow control parameters.
768#[derive(Serialize, Deserialize, Debug, Clone)]
769pub struct PipelineData {
770    /// Recognition algorithm configuration.
771    pub recognition: Recognition,
772    /// Action to execute on match.
773    pub action: Action,
774    /// Next nodes to check after action. Default: [].
775    #[serde(default)]
776    pub next: Vec<NodeAttr>,
777    /// Recognition rate limit in ms. Default: 1000.
778    #[serde(default = "default_rate_limit")]
779    pub rate_limit: i32,
780    /// Overall timeout in ms. Default: 20000.
781    #[serde(default = "default_timeout")]
782    pub timeout: i32,
783    /// Nodes to check on timeout/error. Default: [].
784    #[serde(default)]
785    pub on_error: Vec<NodeAttr>,
786    /// Anchor names for this node. Default: [].
787    #[serde(default)]
788    pub anchor: Anchor,
789    /// Invert recognition result. Default: false.
790    #[serde(default)]
791    pub inverse: bool,
792    /// Enable this node. Default: true.
793    #[serde(default = "default_enabled")]
794    pub enabled: bool,
795    /// Delay before action in ms. Default: 200.
796    #[serde(default = "default_pre_delay")]
797    pub pre_delay: i32,
798    /// Delay after action in ms. Default: 200.
799    #[serde(default = "default_post_delay")]
800    pub post_delay: i32,
801    /// Wait for screen stability before action.
802    #[serde(default)]
803    pub pre_wait_freezes: Option<WaitFreezes>,
804    /// Wait for screen stability after action.
805    #[serde(default)]
806    pub post_wait_freezes: Option<WaitFreezes>,
807    /// Action repeat count. Default: 1.
808    #[serde(default = "default_repeat")]
809    pub repeat: i32,
810    /// Delay between repeats in ms. Default: 0.
811    #[serde(default)]
812    pub repeat_delay: i32,
813    /// Wait for stability between repeats.
814    #[serde(default)]
815    pub repeat_wait_freezes: Option<WaitFreezes>,
816    /// Maximum successful hits. Default: UINT_MAX.
817    #[serde(default = "default_max_hit")]
818    pub max_hit: u32,
819    /// Focus flag for extra callbacks. Default: null.
820    #[serde(default)]
821    pub focus: Option<Value>,
822    /// Attached custom data (merged with defaults).
823    #[serde(default)]
824    pub attach: Option<Value>,
825}
826
827// --- Defaults Helper Functions ---
828
829fn default_wait_time() -> i32 {
830    1
831}
832fn default_wait_threshold() -> f64 {
833    0.95
834}
835fn default_wait_method() -> i32 {
836    5
837}
838fn default_rate_limit() -> i32 {
839    1000
840}
841fn default_timeout() -> i32 {
842    20000
843}
844fn default_threshold() -> Vec<f64> {
845    vec![0.7]
846}
847fn default_order_by() -> String {
848    "Horizontal".to_string()
849}
850fn default_template_method() -> i32 {
851    5
852}
853fn default_detector() -> String {
854    "SIFT".to_string()
855}
856fn default_feature_count() -> i32 {
857    4
858}
859fn default_feature_ratio() -> f64 {
860    0.6
861}
862fn default_color_method() -> i32 {
863    4
864} // RGB
865fn default_count_one() -> i32 {
866    1
867}
868fn default_ocr_threshold() -> f64 {
869    0.3
870}
871fn default_detect_threshold() -> Vec<f64> {
872    vec![0.3]
873}
874fn default_pressure() -> i32 {
875    1
876}
877fn default_long_press_duration() -> i32 {
878    1000
879}
880fn default_target_list_true() -> Vec<Target> {
881    vec![Target::Bool(true)]
882}
883fn default_rect_list_zero() -> Vec<Rect> {
884    vec![(0, 0, 0, 0).into()]
885}
886fn default_i32_list_zero() -> Vec<i32> {
887    vec![0]
888}
889fn default_duration_list() -> Vec<i32> {
890    vec![200]
891}
892fn default_enabled() -> bool {
893    true
894}
895fn default_pre_delay() -> i32 {
896    200
897}
898fn default_post_delay() -> i32 {
899    200
900}
901fn default_repeat() -> i32 {
902    1
903}
904fn default_max_hit() -> u32 {
905    u32::MAX
906}
907fn default_screencap_format() -> String {
908    "png".to_string()
909}
910fn default_screencap_quality() -> i32 {
911    100
912}
913fn default_roi_zero() -> Target {
914    Target::Rect((0, 0, 0, 0).into())
915}