Skip to main content

maa_framework/
common.rs

1//! Common types and data structures used throughout the SDK.
2
3use crate::sys;
4use serde::{Deserialize, Serialize};
5use std::fmt;
6
7/// Status of an asynchronous operation.
8///
9/// Most SDK operations are asynchronous and return immediately with an ID.
10/// Use this status to check completion state.
11#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
12pub struct MaaStatus(pub i32);
13
14/// Unique identifier for operations, tasks, and nodes.
15pub type MaaId = i64;
16
17impl MaaStatus {
18    pub const INVALID: Self = Self(sys::MaaStatusEnum_MaaStatus_Invalid as i32);
19    pub const PENDING: Self = Self(sys::MaaStatusEnum_MaaStatus_Pending as i32);
20    pub const RUNNING: Self = Self(sys::MaaStatusEnum_MaaStatus_Running as i32);
21    pub const SUCCEEDED: Self = Self(sys::MaaStatusEnum_MaaStatus_Succeeded as i32);
22    pub const FAILED: Self = Self(sys::MaaStatusEnum_MaaStatus_Failed as i32);
23
24    /// Check if the operation succeeded.
25    pub fn is_success(&self) -> bool {
26        *self == Self::SUCCEEDED
27    }
28
29    /// Check if the operation succeeded (alias for is_success).
30    pub fn succeeded(&self) -> bool {
31        *self == Self::SUCCEEDED
32    }
33
34    /// Check if the operation failed.
35    pub fn is_failed(&self) -> bool {
36        *self == Self::FAILED
37    }
38
39    /// Check if the operation failed (alias for is_failed).
40    pub fn failed(&self) -> bool {
41        *self == Self::FAILED
42    }
43
44    /// Check if the operation is done (succeeded or failed).
45    pub fn done(&self) -> bool {
46        *self == Self::SUCCEEDED || *self == Self::FAILED
47    }
48
49    /// Check if the operation is pending.
50    pub fn pending(&self) -> bool {
51        *self == Self::PENDING
52    }
53
54    /// Check if the operation is running.
55    pub fn running(&self) -> bool {
56        *self == Self::RUNNING
57    }
58}
59
60impl fmt::Display for MaaStatus {
61    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
62        match *self {
63            Self::INVALID => write!(f, "Invalid"),
64            Self::PENDING => write!(f, "Pending"),
65            Self::RUNNING => write!(f, "Running"),
66            Self::SUCCEEDED => write!(f, "Succeeded"),
67            Self::FAILED => write!(f, "Failed"),
68            _ => write!(f, "Unknown({})", self.0),
69        }
70    }
71}
72
73pub fn check_bool(ret: sys::MaaBool) -> crate::MaaResult<()> {
74    if ret != 0 {
75        Ok(())
76    } else {
77        Err(crate::MaaError::FrameworkError(0))
78    }
79}
80
81impl From<i32> for MaaStatus {
82    fn from(value: i32) -> Self {
83        Self(value)
84    }
85}
86
87// ============================================================================
88// Rect Implementation
89// ============================================================================
90
91/// A rectangle representing a region on screen.
92/// Compatible with both array [x, y, w, h] and object {"x": 0, "y": 0, "w": 0, "h": 0} formats.
93#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Default)]
94#[serde(from = "RectDef")]
95pub struct Rect {
96    pub x: i32,
97    pub y: i32,
98    pub width: i32,
99    pub height: i32,
100}
101
102impl Serialize for Rect {
103    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
104    where
105        S: serde::Serializer,
106    {
107        (self.x, self.y, self.width, self.height).serialize(serializer)
108    }
109}
110
111/// Private proxy for deserialization
112#[derive(Deserialize)]
113#[serde(untagged)]
114enum RectDef {
115    Map {
116        x: i32,
117        y: i32,
118        #[serde(alias = "w")]
119        width: i32,
120        #[serde(alias = "h")]
121        height: i32,
122    },
123    Array(i32, i32, i32, i32),
124}
125
126impl From<RectDef> for Rect {
127    fn from(def: RectDef) -> Self {
128        match def {
129            RectDef::Map {
130                x,
131                y,
132                width,
133                height,
134            } => Rect {
135                x,
136                y,
137                width,
138                height,
139            },
140            RectDef::Array(x, y, w, h) => Rect {
141                x,
142                y,
143                width: w,
144                height: h,
145            },
146        }
147    }
148}
149
150impl From<(i32, i32, i32, i32)> for Rect {
151    fn from(tuple: (i32, i32, i32, i32)) -> Self {
152        Self {
153            x: tuple.0,
154            y: tuple.1,
155            width: tuple.2,
156            height: tuple.3,
157        }
158    }
159}
160
161impl From<sys::MaaRect> for Rect {
162    fn from(r: sys::MaaRect) -> Self {
163        Self {
164            x: r.x,
165            y: r.y,
166            width: r.width,
167            height: r.height,
168        }
169    }
170}
171
172impl Rect {
173    pub fn to_tuple(&self) -> (i32, i32, i32, i32) {
174        (self.x, self.y, self.width, self.height)
175    }
176}
177
178impl PartialEq<(i32, i32, i32, i32)> for Rect {
179    fn eq(&self, other: &(i32, i32, i32, i32)) -> bool {
180        self.x == other.0 && self.y == other.1 && self.width == other.2 && self.height == other.3
181    }
182}
183
184impl PartialEq<Rect> for (i32, i32, i32, i32) {
185    fn eq(&self, other: &Rect) -> bool {
186        self.0 == other.x && self.1 == other.y && self.2 == other.width && self.3 == other.height
187    }
188}
189
190/// A point representing a location on screen.
191#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, serde::Serialize, serde::Deserialize)]
192pub struct Point {
193    pub x: i32,
194    pub y: i32,
195}
196
197impl Point {
198    pub fn new(x: i32, y: i32) -> Self {
199        Self { x, y }
200    }
201}
202
203// ============================================================================
204// Gamepad Types (Windows only, requires ViGEm Bus Driver)
205// ============================================================================
206
207/// Virtual gamepad type for GamepadController.
208#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
209#[repr(u64)]
210#[non_exhaustive]
211pub enum GamepadType {
212    /// Microsoft Xbox 360 Controller (wired)
213    Xbox360 = 0,
214    /// Sony DualShock 4 Controller (wired)
215    DualShock4 = 1,
216}
217
218/// Gamepad contact (analog stick or trigger) for touch mapping.
219#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
220#[repr(i32)]
221#[non_exhaustive]
222pub enum GamepadContact {
223    /// Left analog stick: x/y range -32768~32767
224    LeftStick = 0,
225    /// Right analog stick: x/y range -32768~32767
226    RightStick = 1,
227    /// Left trigger: pressure 0~255
228    LeftTrigger = 2,
229    /// Right trigger: pressure 0~255
230    RightTrigger = 3,
231}
232
233bitflags::bitflags! {
234    /// Gamepad button flags (XUSB protocol values).
235    ///
236    /// Use bitwise OR to combine multiple buttons.
237    #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
238    pub struct GamepadButton: u32 {
239        // D-pad
240        const DPAD_UP = 0x0001;
241        const DPAD_DOWN = 0x0002;
242        const DPAD_LEFT = 0x0004;
243        const DPAD_RIGHT = 0x0008;
244
245        // Control buttons
246        const START = 0x0010;
247        const BACK = 0x0020;
248        const LEFT_THUMB = 0x0040;  // L3
249        const RIGHT_THUMB = 0x0080; // R3
250
251        // Shoulder buttons
252        const LB = 0x0100; // Left Bumper / L1
253        const RB = 0x0200; // Right Bumper / R1
254
255        // Guide button
256        const GUIDE = 0x0400;
257
258        // Face buttons (Xbox layout)
259        const A = 0x1000;
260        const B = 0x2000;
261        const X = 0x4000;
262        const Y = 0x8000;
263
264        // DS4 special buttons
265        const PS = 0x10000;
266        const TOUCHPAD = 0x20000;
267    }
268}
269
270impl GamepadButton {
271    // DS4 face button aliases
272    pub const CROSS: Self = Self::A;
273    pub const CIRCLE: Self = Self::B;
274    pub const SQUARE: Self = Self::X;
275    pub const TRIANGLE: Self = Self::Y;
276    pub const L1: Self = Self::LB;
277    pub const R1: Self = Self::RB;
278    pub const L3: Self = Self::LEFT_THUMB;
279    pub const R3: Self = Self::RIGHT_THUMB;
280    pub const OPTIONS: Self = Self::START;
281    pub const SHARE: Self = Self::BACK;
282}
283
284// ============================================================================
285// Controller Feature Flags
286// ============================================================================
287
288bitflags::bitflags! {
289    /// Controller feature flags for CustomController.
290    ///
291    /// These flags indicate which input methods the controller supports/prefers.
292    #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
293    pub struct ControllerFeature: u64 {
294        /// Controller prefers touch_down/touch_move/touch_up instead of click/swipe.
295        /// When set, ControllerAgent will use touch_down/touch_up to simulate click,
296        /// and touch_down/touch_move/touch_up to simulate swipe.
297        const USE_MOUSE_DOWN_UP_INSTEAD_OF_CLICK = 1;
298        /// Controller prefers key_down/key_up instead of click_key.
299        /// When set, ControllerAgent will use key_down + key_up to simulate click_key.
300        const USE_KEY_DOWN_UP_INSTEAD_OF_CLICK = 1 << 1;
301        /// Controller does not scale touch points automatically.
302        /// When set, ControllerAgent will skip coordinate scaling for touch operations.
303        const NO_SCALING_TOUCH_POINTS = 1 << 2;
304    }
305}
306
307// ============================================================================
308// ADB Controller Methods
309// ============================================================================
310
311/// Raw screen resolution used by Android native control units.
312#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
313pub struct AndroidScreenResolution {
314    /// Raw screenshot width reported by the control unit.
315    pub width: i32,
316    /// Raw screenshot height reported by the control unit.
317    pub height: i32,
318}
319
320/// Configuration for [`crate::controller::Controller::new_android_native`].
321#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
322pub struct AndroidNativeControllerConfig {
323    /// Path to the Android native control unit shared library.
324    pub library_path: String,
325    /// Raw screenshot/touch coordinate resolution exposed by the control unit.
326    pub screen_resolution: AndroidScreenResolution,
327    /// Target Android display id. Defaults to `0` when omitted by MaaFramework.
328    #[serde(default, skip_serializing_if = "Option::is_none")]
329    pub display_id: Option<u32>,
330    /// Whether to force-stop before `start_app`. Defaults to `false` when omitted.
331    #[serde(default, skip_serializing_if = "Option::is_none")]
332    pub force_stop: Option<bool>,
333}
334
335bitflags::bitflags! {
336    /// ADB screencap method flags.
337    ///
338    /// Use bitwise OR to set the methods you need.
339    /// MaaFramework will test all provided methods and use the fastest available one.
340    #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
341    pub struct AdbScreencapMethod: u64 {
342        const ENCODE_TO_FILE_AND_PULL = 1;
343        const ENCODE = 1 << 1;
344        const RAW_WITH_GZIP = 1 << 2;
345        const RAW_BY_NETCAT = 1 << 3;
346        const MINICAP_DIRECT = 1 << 4;
347        const MINICAP_STREAM = 1 << 5;
348        const EMULATOR_EXTRAS = 1 << 6;
349        const ALL = !0;
350    }
351}
352
353impl AdbScreencapMethod {
354    /// Default methods (all except RawByNetcat, MinicapDirect, MinicapStream)
355    pub const DEFAULT: Self = Self::from_bits_truncate(
356        Self::ALL.bits()
357            & !Self::RAW_BY_NETCAT.bits()
358            & !Self::MINICAP_DIRECT.bits()
359            & !Self::MINICAP_STREAM.bits(),
360    );
361}
362
363impl Default for AdbScreencapMethod {
364    fn default() -> Self {
365        Self::DEFAULT
366    }
367}
368
369bitflags::bitflags! {
370    /// ADB input method flags.
371    ///
372    /// Use bitwise OR to set the methods you need.
373    /// MaaFramework will select the first available method according to priority.
374    #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
375    pub struct AdbInputMethod: u64 {
376        const ADB_SHELL = 1;
377        const MINITOUCH_AND_ADB_KEY = 1 << 1;
378        const MAATOUCH = 1 << 2;
379        const EMULATOR_EXTRAS = 1 << 3;
380        const ALL = !0;
381    }
382}
383
384impl AdbInputMethod {
385    /// Default methods (all except EmulatorExtras)
386    pub const DEFAULT: Self =
387        Self::from_bits_truncate(Self::ALL.bits() & !Self::EMULATOR_EXTRAS.bits());
388}
389
390impl Default for AdbInputMethod {
391    fn default() -> Self {
392        Self::DEFAULT
393    }
394}
395
396// ============================================================================
397// Win32 Controller Methods
398// ============================================================================
399
400bitflags::bitflags! {
401    /// Win32 screencap method flags.
402    ///
403    /// Use bitwise OR to set the methods you need.
404    /// MaaFramework will test all provided methods and use the fastest available one.
405    ///
406    /// Predefined combinations:
407    /// - [`FOREGROUND`](Self::FOREGROUND): `DXGI_DESKTOP_DUP_WINDOW | SCREEN_DC`
408    /// - [`BACKGROUND`](Self::BACKGROUND): `FRAME_POOL | PRINT_WINDOW`
409    ///
410    /// `FRAME_POOL` and `PRINT_WINDOW` support pseudo-minimize. Other methods
411    /// still fail when the target window is minimized.
412    #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
413    pub struct Win32ScreencapMethod: u64 {
414        const GDI = 1;
415        const FRAME_POOL = 1 << 1;
416        const DXGI_DESKTOP_DUP = 1 << 2;
417        const DXGI_DESKTOP_DUP_WINDOW = 1 << 3;
418        const PRINT_WINDOW = 1 << 4;
419        const SCREEN_DC = 1 << 5;
420        const ALL = !0;
421        const FOREGROUND = Self::DXGI_DESKTOP_DUP_WINDOW.bits() | Self::SCREEN_DC.bits();
422        const BACKGROUND = Self::FRAME_POOL.bits() | Self::PRINT_WINDOW.bits();
423    }
424}
425
426bitflags::bitflags! {
427    /// Win32 input method (select ONE only, no bitwise OR).
428    #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
429    pub struct Win32InputMethod: u64 {
430        const SEIZE = 1;
431        const SEND_MESSAGE = 1 << 1;
432        const POST_MESSAGE = 1 << 2;
433        const LEGACY_EVENT = 1 << 3;
434        /// Deprecated and no longer implemented by MaaFramework.
435        const POST_THREAD_MESSAGE = 1 << 4;
436        const SEND_MESSAGE_WITH_CURSOR_POS = 1 << 5;
437        const POST_MESSAGE_WITH_CURSOR_POS = 1 << 6;
438        const SEND_MESSAGE_WITH_WINDOW_POS = 1 << 7;
439        const POST_MESSAGE_WITH_WINDOW_POS = 1 << 8;
440        /// Driver-level input injection via the Interception driver. Requires administrator
441        /// rights.
442        const INTERCEPTION = sys::MaaWin32InputMethod_Interception as u64;
443    }
444}
445
446/// Details of a recognition operation result.
447#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
448pub struct RecognitionDetail {
449    /// Name of the node that performed recognition
450    pub node_name: String,
451    /// Algorithm used
452    pub algorithm: AlgorithmEnum,
453    /// Whether recognition was successful
454    pub hit: bool,
455    /// Bounding box of the recognized region
456    pub box_rect: Rect,
457    /// Algorithm-specific detail JSON
458    pub detail: serde_json::Value,
459    /// Raw screenshot (PNG encoded, only valid in debug mode)
460    #[serde(skip)]
461    pub raw_image: Option<Vec<u8>>,
462    /// Debug draw images (PNG encoded, only valid in debug mode)
463    #[serde(skip)]
464    pub draw_images: Vec<Vec<u8>>,
465    /// Sub-process recognition details (for And/Or combinators)
466    #[serde(default)]
467    pub sub_details: Vec<RecognitionDetail>,
468}
469
470impl RecognitionDetail {
471    pub fn as_template_match_result(&self) -> Option<TemplateMatchResult> {
472        serde_json::from_value(self.detail.clone()).ok()
473    }
474
475    pub fn as_feature_match_result(&self) -> Option<FeatureMatchResult> {
476        serde_json::from_value(self.detail.clone()).ok()
477    }
478
479    pub fn as_color_match_result(&self) -> Option<ColorMatchResult> {
480        serde_json::from_value(self.detail.clone()).ok()
481    }
482
483    pub fn as_ocr_result(&self) -> Option<OCRResult> {
484        serde_json::from_value(self.detail.clone()).ok()
485    }
486
487    pub fn as_neural_network_result(&self) -> Option<NeuralNetworkResult> {
488        serde_json::from_value(self.detail.clone()).ok()
489    }
490
491    pub fn as_custom_result(&self) -> Option<CustomRecognitionResult> {
492        serde_json::from_value(self.detail.clone()).ok()
493    }
494}
495
496/// Details of an action operation result.
497#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
498pub struct ActionDetail {
499    /// Name of the node that performed the action
500    pub node_name: String,
501    /// Action type
502    pub action: ActionEnum,
503    /// Target bounding box
504    pub box_rect: Rect,
505    /// Whether action was successful
506    pub success: bool,
507    /// Action-specific detail JSON
508    pub detail: serde_json::Value,
509}
510
511#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
512pub struct WaitFreezesDetail {
513    pub wf_id: MaaId,
514    pub name: String,
515    pub phase: String,
516    pub success: bool,
517    pub elapsed_ms: u64,
518    #[serde(default)]
519    pub reco_id_list: Vec<MaaId>,
520    pub roi: Rect,
521}
522
523impl ActionDetail {
524    pub fn as_click_result(&self) -> Option<ClickActionResult> {
525        serde_json::from_value(self.detail.clone()).ok()
526    }
527
528    pub fn as_long_press_result(&self) -> Option<LongPressActionResult> {
529        serde_json::from_value(self.detail.clone()).ok()
530    }
531
532    pub fn as_swipe_result(&self) -> Option<SwipeActionResult> {
533        serde_json::from_value(self.detail.clone()).ok()
534    }
535
536    pub fn as_multi_swipe_result(&self) -> Option<MultiSwipeActionResult> {
537        serde_json::from_value(self.detail.clone()).ok()
538    }
539
540    pub fn as_click_key_result(&self) -> Option<ClickKeyActionResult> {
541        serde_json::from_value(self.detail.clone()).ok()
542    }
543
544    pub fn as_input_text_result(&self) -> Option<InputTextActionResult> {
545        serde_json::from_value(self.detail.clone()).ok()
546    }
547
548    pub fn as_app_result(&self) -> Option<AppActionResult> {
549        serde_json::from_value(self.detail.clone()).ok()
550    }
551
552    pub fn as_scroll_result(&self) -> Option<ScrollActionResult> {
553        serde_json::from_value(self.detail.clone()).ok()
554    }
555
556    pub fn as_touch_result(&self) -> Option<TouchActionResult> {
557        serde_json::from_value(self.detail.clone()).ok()
558    }
559
560    pub fn as_shell_result(&self) -> Option<ShellActionResult> {
561        serde_json::from_value(self.detail.clone()).ok()
562    }
563}
564
565// ============================================================================
566// Action Result Types
567// ============================================================================
568
569/// Result of a Click action.
570#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
571pub struct ClickActionResult {
572    pub point: Point,
573    pub contact: i32,
574    #[serde(default)]
575    pub pressure: i32,
576}
577
578/// Result of a LongPress action.
579#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
580pub struct LongPressActionResult {
581    pub point: Point,
582    pub duration: i32,
583    pub contact: i32,
584    #[serde(default)]
585    pub pressure: i32,
586}
587
588/// Result of a Swipe action.
589#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
590pub struct SwipeActionResult {
591    pub begin: Point,
592    pub end: Vec<Point>,
593    #[serde(default)]
594    pub end_hold: Vec<i32>,
595    #[serde(default)]
596    pub duration: Vec<i32>,
597    #[serde(default)]
598    pub only_hover: bool,
599    #[serde(default)]
600    pub starting: i32,
601    pub contact: i32,
602    #[serde(default)]
603    pub pressure: i32,
604}
605
606/// Result of a MultiSwipe action.
607#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
608pub struct MultiSwipeActionResult {
609    pub swipes: Vec<SwipeActionResult>,
610}
611
612/// Result of a ClickKey action.
613#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
614pub struct ClickKeyActionResult {
615    pub keycode: Vec<i32>,
616}
617
618/// Result of a LongPressKey action.
619#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
620pub struct LongPressKeyActionResult {
621    pub keycode: Vec<i32>,
622    pub duration: i32,
623}
624
625/// Result of an InputText action.
626#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
627pub struct InputTextActionResult {
628    pub text: String,
629}
630
631/// Result of a StartApp or StopApp action.
632#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
633pub struct AppActionResult {
634    pub package: String,
635}
636
637/// Result of a Scroll action.
638#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
639pub struct ScrollActionResult {
640    #[serde(default)]
641    pub point: Point,
642    pub dx: i32,
643    pub dy: i32,
644}
645
646/// Result of a TouchDown, TouchMove, or TouchUp action.
647#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
648pub struct TouchActionResult {
649    pub contact: i32,
650    pub point: Point,
651    #[serde(default)]
652    pub pressure: i32,
653}
654
655/// Result of a Shell action.
656#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
657pub struct ShellActionResult {
658    pub cmd: String,
659    pub shell_timeout: i32,
660    pub success: bool,
661    pub output: String,
662}
663
664/// Details of a pipeline node execution.
665#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
666pub struct NodeDetail {
667    pub node_name: String,
668    /// ID of the recognition operation
669    pub reco_id: MaaId,
670    /// ID of the action operation
671    pub act_id: MaaId,
672    /// Detailed recognition result
673    #[serde(default)]
674    pub recognition: Option<RecognitionDetail>,
675    /// Detailed action result
676    #[serde(default)]
677    pub action: Option<ActionDetail>,
678    /// Whether the node completed execution
679    pub completed: bool,
680}
681
682/// Details of a task execution.
683#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
684pub struct TaskDetail {
685    /// Entry point node name
686    pub entry: String,
687    /// List of node IDs that were executed
688    pub node_id_list: Vec<MaaId>,
689    /// Final status of the task
690    pub status: MaaStatus,
691    /// Detailed node information (hydrated from node_id_list)
692    #[serde(default)]
693    pub nodes: Vec<Option<NodeDetail>>,
694}
695
696/// Recognition algorithm types.
697#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
698#[serde(into = "String", from = "String")]
699pub enum AlgorithmEnum {
700    DirectHit,
701    TemplateMatch,
702    FeatureMatch,
703    ColorMatch,
704    OCR,
705    NeuralNetworkClassify,
706    NeuralNetworkDetect,
707    And,
708    Or,
709    Custom,
710    Other(String),
711}
712
713impl From<String> for AlgorithmEnum {
714    fn from(s: String) -> Self {
715        match s.as_str() {
716            "DirectHit" => Self::DirectHit,
717            "TemplateMatch" => Self::TemplateMatch,
718            "FeatureMatch" => Self::FeatureMatch,
719            "ColorMatch" => Self::ColorMatch,
720            "OCR" => Self::OCR,
721            "NeuralNetworkClassify" => Self::NeuralNetworkClassify,
722            "NeuralNetworkDetect" => Self::NeuralNetworkDetect,
723            "And" => Self::And,
724            "Or" => Self::Or,
725            "Custom" => Self::Custom,
726            _ => Self::Other(s),
727        }
728    }
729}
730
731impl From<AlgorithmEnum> for String {
732    fn from(algo: AlgorithmEnum) -> Self {
733        match algo {
734            AlgorithmEnum::DirectHit => "DirectHit".to_string(),
735            AlgorithmEnum::TemplateMatch => "TemplateMatch".to_string(),
736            AlgorithmEnum::FeatureMatch => "FeatureMatch".to_string(),
737            AlgorithmEnum::ColorMatch => "ColorMatch".to_string(),
738            AlgorithmEnum::OCR => "OCR".to_string(),
739            AlgorithmEnum::NeuralNetworkClassify => "NeuralNetworkClassify".to_string(),
740            AlgorithmEnum::NeuralNetworkDetect => "NeuralNetworkDetect".to_string(),
741            AlgorithmEnum::And => "And".to_string(),
742            AlgorithmEnum::Or => "Or".to_string(),
743            AlgorithmEnum::Custom => "Custom".to_string(),
744            AlgorithmEnum::Other(s) => s,
745        }
746    }
747}
748
749impl AlgorithmEnum {
750    pub fn as_str(&self) -> &str {
751        match self {
752            Self::DirectHit => "DirectHit",
753            Self::TemplateMatch => "TemplateMatch",
754            Self::FeatureMatch => "FeatureMatch",
755            Self::ColorMatch => "ColorMatch",
756            Self::OCR => "OCR",
757            Self::NeuralNetworkClassify => "NeuralNetworkClassify",
758            Self::NeuralNetworkDetect => "NeuralNetworkDetect",
759            Self::And => "And",
760            Self::Or => "Or",
761            Self::Custom => "Custom",
762            Self::Other(s) => s.as_str(),
763        }
764    }
765}
766
767/// Action types.
768#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
769#[serde(into = "String", from = "String")]
770pub enum ActionEnum {
771    DoNothing,
772    Click,
773    LongPress,
774    Swipe,
775    MultiSwipe,
776    TouchDown,
777    TouchMove,
778    TouchUp,
779    ClickKey,
780    LongPressKey,
781    KeyDown,
782    KeyUp,
783    InputText,
784    StartApp,
785    StopApp,
786    StopTask,
787    Scroll,
788    Command,
789    Shell,
790    Custom,
791    Other(String),
792}
793
794impl From<String> for ActionEnum {
795    fn from(s: String) -> Self {
796        match s.as_str() {
797            "DoNothing" => Self::DoNothing,
798            "Click" => Self::Click,
799            "LongPress" => Self::LongPress,
800            "Swipe" => Self::Swipe,
801            "MultiSwipe" => Self::MultiSwipe,
802            "TouchDown" => Self::TouchDown,
803            "TouchMove" => Self::TouchMove,
804            "TouchUp" => Self::TouchUp,
805            "ClickKey" => Self::ClickKey,
806            "LongPressKey" => Self::LongPressKey,
807            "KeyDown" => Self::KeyDown,
808            "KeyUp" => Self::KeyUp,
809            "InputText" => Self::InputText,
810            "StartApp" => Self::StartApp,
811            "StopApp" => Self::StopApp,
812            "StopTask" => Self::StopTask,
813            "Scroll" => Self::Scroll,
814            "Command" => Self::Command,
815            "Shell" => Self::Shell,
816            "Custom" => Self::Custom,
817            _ => Self::Other(s),
818        }
819    }
820}
821
822impl From<ActionEnum> for String {
823    fn from(act: ActionEnum) -> Self {
824        match act {
825            ActionEnum::DoNothing => "DoNothing".to_string(),
826            ActionEnum::Click => "Click".to_string(),
827            ActionEnum::LongPress => "LongPress".to_string(),
828            ActionEnum::Swipe => "Swipe".to_string(),
829            ActionEnum::MultiSwipe => "MultiSwipe".to_string(),
830            ActionEnum::TouchDown => "TouchDown".to_string(),
831            ActionEnum::TouchMove => "TouchMove".to_string(),
832            ActionEnum::TouchUp => "TouchUp".to_string(),
833            ActionEnum::ClickKey => "ClickKey".to_string(),
834            ActionEnum::LongPressKey => "LongPressKey".to_string(),
835            ActionEnum::KeyDown => "KeyDown".to_string(),
836            ActionEnum::KeyUp => "KeyUp".to_string(),
837            ActionEnum::InputText => "InputText".to_string(),
838            ActionEnum::StartApp => "StartApp".to_string(),
839            ActionEnum::StopApp => "StopApp".to_string(),
840            ActionEnum::StopTask => "StopTask".to_string(),
841            ActionEnum::Scroll => "Scroll".to_string(),
842            ActionEnum::Command => "Command".to_string(),
843            ActionEnum::Shell => "Shell".to_string(),
844            ActionEnum::Custom => "Custom".to_string(),
845            ActionEnum::Other(s) => s,
846        }
847    }
848}
849
850impl ActionEnum {
851    pub fn as_str(&self) -> &str {
852        match self {
853            Self::DoNothing => "DoNothing",
854            Self::Click => "Click",
855            Self::LongPress => "LongPress",
856            Self::Swipe => "Swipe",
857            Self::MultiSwipe => "MultiSwipe",
858            Self::TouchDown => "TouchDown",
859            Self::TouchMove => "TouchMove",
860            Self::TouchUp => "TouchUp",
861            Self::ClickKey => "ClickKey",
862            Self::LongPressKey => "LongPressKey",
863            Self::KeyDown => "KeyDown",
864            Self::KeyUp => "KeyUp",
865            Self::InputText => "InputText",
866            Self::StartApp => "StartApp",
867            Self::StopApp => "StopApp",
868            Self::StopTask => "StopTask",
869            Self::Scroll => "Scroll",
870            Self::Command => "Command",
871            Self::Shell => "Shell",
872            Self::Custom => "Custom",
873            Self::Other(s) => s.as_str(),
874        }
875    }
876}
877
878/// Notification type for event callbacks.
879#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
880#[non_exhaustive]
881pub enum NotificationType {
882    Starting,
883    Succeeded,
884    Failed,
885    Unknown,
886}
887
888impl NotificationType {
889    pub fn from_message(msg: &str) -> Self {
890        if msg.ends_with(".Starting") {
891            Self::Starting
892        } else if msg.ends_with(".Succeeded") {
893            Self::Succeeded
894        } else if msg.ends_with(".Failed") {
895            Self::Failed
896        } else {
897            Self::Unknown
898        }
899    }
900}
901
902// --- Result types ---
903
904#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
905pub struct BoxAndScore {
906    #[serde(rename = "box")]
907    pub box_rect: (i32, i32, i32, i32),
908    pub score: f64,
909}
910
911#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
912pub struct BoxAndCount {
913    #[serde(rename = "box")]
914    pub box_rect: (i32, i32, i32, i32),
915    pub count: i32,
916}
917
918pub type TemplateMatchResult = BoxAndScore;
919pub type FeatureMatchResult = BoxAndCount;
920pub type ColorMatchResult = BoxAndCount;
921
922#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
923pub struct OCRResult {
924    #[serde(flatten)]
925    pub base: BoxAndScore,
926    pub text: String,
927}
928
929#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
930pub struct NeuralNetworkResult {
931    #[serde(flatten)]
932    pub base: BoxAndScore,
933    pub cls_index: i32,
934    pub label: String,
935}
936
937pub type NeuralNetworkClassifyResult = NeuralNetworkResult;
938pub type NeuralNetworkDetectResult = NeuralNetworkResult;
939
940#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
941pub struct CustomRecognitionResult {
942    #[serde(rename = "box")]
943    pub box_rect: (i32, i32, i32, i32),
944    pub detail: serde_json::Value,
945}
946
947#[cfg(test)]
948mod tests {
949    use super::{AndroidNativeControllerConfig, AndroidScreenResolution};
950    use serde_json::json;
951
952    #[test]
953    fn android_native_controller_config_serializes_expected_shape() {
954        let config = AndroidNativeControllerConfig {
955            library_path: "/data/local/tmp/libmaa_unit.so".to_string(),
956            screen_resolution: AndroidScreenResolution {
957                width: 1920,
958                height: 1080,
959            },
960            display_id: Some(1),
961            force_stop: Some(true),
962        };
963
964        let value = serde_json::to_value(config).unwrap();
965
966        assert_eq!(
967            value,
968            json!({
969                "library_path": "/data/local/tmp/libmaa_unit.so",
970                "screen_resolution": {
971                    "width": 1920,
972                    "height": 1080
973                },
974                "display_id": 1,
975                "force_stop": true
976            })
977        );
978    }
979}