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