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