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// Linux Controller Methods
398// ============================================================================
399
400bitflags::bitflags! {
401    /// Linux screencap method (select ONE only, no bitwise OR).
402    #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
403    pub struct LinuxScreencapMethod: u64 {
404        const NONE = sys::MaaLinuxScreencapMethod_None as u64;
405        /// Screencap using the `wlr-screencopy-unstable-v1` protocol.
406        const WLR = sys::MaaLinuxScreencapMethod_Wlr as u64;
407        /// Screencap using PipeWire.
408        const PIPEWIRE = sys::MaaLinuxScreencapMethod_PipeWire as u64;
409    }
410}
411
412bitflags::bitflags! {
413    /// Linux input method (select ONE only, no bitwise OR).
414    #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
415    pub struct LinuxInputMethod: u64 {
416        const NONE = sys::MaaLinuxInputMethod_None as u64;
417        /// Input using the Wayland virtual keyboard and pointer protocols.
418        const WLR = sys::MaaLinuxInputMethod_Wlr as u64;
419        /// Input using `/dev/uinput`.
420        const UINPUT = sys::MaaLinuxInputMethod_UInput as u64;
421    }
422}
423
424/// Configuration for [`crate::controller::Controller::new_linux`].
425#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
426pub struct LinuxControllerConfig {
427    /// Screencap method. Use [`LinuxScreencapMethod::bits`] to obtain the value.
428    pub screencap_method: sys::MaaLinuxScreencapMethod,
429    /// Input method. Use [`LinuxInputMethod::bits`] to obtain the value.
430    pub input_method: sys::MaaLinuxInputMethod,
431    /// Wayland socket path required by WLR screencap or input.
432    #[serde(default, skip_serializing_if = "Option::is_none")]
433    pub wlr_socket_path: Option<String>,
434    /// PipeWire socket file descriptor.
435    #[serde(default, skip_serializing_if = "Option::is_none")]
436    pub pw_socket_fd: Option<i32>,
437    /// PipeWire node ID.
438    #[serde(default, skip_serializing_if = "Option::is_none")]
439    pub pw_node_id: Option<u32>,
440    /// PipeWire stream width in pixels.
441    #[serde(default, skip_serializing_if = "Option::is_none")]
442    pub pw_screen_width: Option<i32>,
443    /// PipeWire stream height in pixels.
444    #[serde(default, skip_serializing_if = "Option::is_none")]
445    pub pw_screen_height: Option<i32>,
446    /// UInput device path. MaaFramework defaults to `/dev/uinput` when omitted.
447    #[serde(default, skip_serializing_if = "Option::is_none")]
448    pub uinput_path: Option<String>,
449    /// Interpret key codes as Win32 virtual-key codes instead of raw evdev codes.
450    #[serde(default, skip_serializing_if = "Option::is_none")]
451    pub use_win32_vk_code: Option<bool>,
452}
453
454// ============================================================================
455// Win32 Controller Methods
456// ============================================================================
457
458bitflags::bitflags! {
459    /// Win32 screencap method flags.
460    ///
461    /// Use bitwise OR to set the methods you need.
462    /// MaaFramework will test all provided methods and use the fastest available one.
463    ///
464    /// Predefined combinations:
465    /// - [`FOREGROUND`](Self::FOREGROUND): `DXGI_DESKTOP_DUP_WINDOW | SCREEN_DC`
466    /// - [`BACKGROUND`](Self::BACKGROUND): `FRAME_POOL | PRINT_WINDOW`
467    ///
468    /// `FRAME_POOL` and `PRINT_WINDOW` support pseudo-minimize. Other methods
469    /// still fail when the target window is minimized.
470    #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
471    pub struct Win32ScreencapMethod: u64 {
472        const GDI = 1;
473        const FRAME_POOL = 1 << 1;
474        const DXGI_DESKTOP_DUP = 1 << 2;
475        const DXGI_DESKTOP_DUP_WINDOW = 1 << 3;
476        const PRINT_WINDOW = 1 << 4;
477        const SCREEN_DC = 1 << 5;
478        const ALL = !0;
479        const FOREGROUND = Self::DXGI_DESKTOP_DUP_WINDOW.bits() | Self::SCREEN_DC.bits();
480        const BACKGROUND = Self::FRAME_POOL.bits() | Self::PRINT_WINDOW.bits();
481    }
482}
483
484bitflags::bitflags! {
485    /// Win32 input method (select ONE only, no bitwise OR).
486    #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
487    pub struct Win32InputMethod: u64 {
488        const SEIZE = 1;
489        const SEND_MESSAGE = 1 << 1;
490        const POST_MESSAGE = 1 << 2;
491        const LEGACY_EVENT = 1 << 3;
492        /// Deprecated and no longer implemented by MaaFramework.
493        const POST_THREAD_MESSAGE = 1 << 4;
494        const SEND_MESSAGE_WITH_CURSOR_POS = 1 << 5;
495        const POST_MESSAGE_WITH_CURSOR_POS = 1 << 6;
496        const SEND_MESSAGE_WITH_WINDOW_POS = 1 << 7;
497        const POST_MESSAGE_WITH_WINDOW_POS = 1 << 8;
498        /// Driver-level input injection via the Interception driver. Requires administrator
499        /// rights.
500        const INTERCEPTION = sys::MaaWin32InputMethod_Interception as u64;
501    }
502}
503
504/// Details of a recognition operation result.
505#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
506pub struct RecognitionDetail {
507    /// Name of the node that performed recognition
508    pub node_name: String,
509    /// Algorithm used
510    pub algorithm: AlgorithmEnum,
511    /// Whether recognition was successful
512    pub hit: bool,
513    /// Bounding box of the recognized region
514    pub box_rect: Rect,
515    /// Algorithm-specific detail JSON
516    pub detail: serde_json::Value,
517    /// Raw screenshot (PNG encoded, only valid in debug mode)
518    #[serde(skip)]
519    pub raw_image: Option<Vec<u8>>,
520    /// Debug draw images (PNG encoded, only valid in debug mode)
521    #[serde(skip)]
522    pub draw_images: Vec<Vec<u8>>,
523    /// Sub-process recognition details (for And/Or combinators)
524    #[serde(default)]
525    pub sub_details: Vec<RecognitionDetail>,
526}
527
528impl RecognitionDetail {
529    pub fn as_template_match_result(&self) -> Option<TemplateMatchResult> {
530        serde_json::from_value(self.detail.clone()).ok()
531    }
532
533    pub fn as_feature_match_result(&self) -> Option<FeatureMatchResult> {
534        serde_json::from_value(self.detail.clone()).ok()
535    }
536
537    pub fn as_color_match_result(&self) -> Option<ColorMatchResult> {
538        serde_json::from_value(self.detail.clone()).ok()
539    }
540
541    pub fn as_ocr_result(&self) -> Option<OCRResult> {
542        serde_json::from_value(self.detail.clone()).ok()
543    }
544
545    pub fn as_neural_network_result(&self) -> Option<NeuralNetworkResult> {
546        serde_json::from_value(self.detail.clone()).ok()
547    }
548
549    pub fn as_custom_result(&self) -> Option<CustomRecognitionResult> {
550        serde_json::from_value(self.detail.clone()).ok()
551    }
552}
553
554/// Details of an action operation result.
555#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
556pub struct ActionDetail {
557    /// Name of the node that performed the action
558    pub node_name: String,
559    /// Action type
560    pub action: ActionEnum,
561    /// Target bounding box
562    pub box_rect: Rect,
563    /// Whether action was successful
564    pub success: bool,
565    /// Action-specific detail JSON
566    pub detail: serde_json::Value,
567}
568
569#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
570pub struct WaitFreezesDetail {
571    pub wf_id: MaaId,
572    pub name: String,
573    pub phase: String,
574    pub success: bool,
575    pub elapsed_ms: u64,
576    #[serde(default)]
577    pub reco_id_list: Vec<MaaId>,
578    pub roi: Rect,
579}
580
581impl ActionDetail {
582    pub fn as_click_result(&self) -> Option<ClickActionResult> {
583        serde_json::from_value(self.detail.clone()).ok()
584    }
585
586    pub fn as_long_press_result(&self) -> Option<LongPressActionResult> {
587        serde_json::from_value(self.detail.clone()).ok()
588    }
589
590    pub fn as_swipe_result(&self) -> Option<SwipeActionResult> {
591        serde_json::from_value(self.detail.clone()).ok()
592    }
593
594    pub fn as_multi_swipe_result(&self) -> Option<MultiSwipeActionResult> {
595        serde_json::from_value(self.detail.clone()).ok()
596    }
597
598    pub fn as_click_key_result(&self) -> Option<ClickKeyActionResult> {
599        serde_json::from_value(self.detail.clone()).ok()
600    }
601
602    pub fn as_input_text_result(&self) -> Option<InputTextActionResult> {
603        serde_json::from_value(self.detail.clone()).ok()
604    }
605
606    pub fn as_app_result(&self) -> Option<AppActionResult> {
607        serde_json::from_value(self.detail.clone()).ok()
608    }
609
610    pub fn as_scroll_result(&self) -> Option<ScrollActionResult> {
611        serde_json::from_value(self.detail.clone()).ok()
612    }
613
614    pub fn as_touch_result(&self) -> Option<TouchActionResult> {
615        serde_json::from_value(self.detail.clone()).ok()
616    }
617
618    pub fn as_shell_result(&self) -> Option<ShellActionResult> {
619        serde_json::from_value(self.detail.clone()).ok()
620    }
621}
622
623// ============================================================================
624// Action Result Types
625// ============================================================================
626
627/// Result of a Click action.
628#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
629pub struct ClickActionResult {
630    pub point: Point,
631    pub contact: i32,
632    #[serde(default)]
633    pub pressure: i32,
634}
635
636/// Result of a LongPress action.
637#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
638pub struct LongPressActionResult {
639    pub point: Point,
640    pub duration: i32,
641    pub contact: i32,
642    #[serde(default)]
643    pub pressure: i32,
644}
645
646/// Result of a Swipe action.
647#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
648pub struct SwipeActionResult {
649    pub begin: Point,
650    pub end: Vec<Point>,
651    #[serde(default)]
652    pub end_hold: Vec<i32>,
653    #[serde(default)]
654    pub duration: Vec<i32>,
655    #[serde(default)]
656    pub only_hover: bool,
657    #[serde(default)]
658    pub starting: i32,
659    pub contact: i32,
660    #[serde(default)]
661    pub pressure: i32,
662}
663
664/// Result of a MultiSwipe action.
665#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
666pub struct MultiSwipeActionResult {
667    pub swipes: Vec<SwipeActionResult>,
668}
669
670/// Result of a ClickKey action.
671#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
672pub struct ClickKeyActionResult {
673    pub keycode: Vec<i32>,
674}
675
676/// Result of a LongPressKey action.
677#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
678pub struct LongPressKeyActionResult {
679    pub keycode: Vec<i32>,
680    pub duration: i32,
681}
682
683/// Result of an InputText action.
684#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
685pub struct InputTextActionResult {
686    pub text: String,
687}
688
689/// Result of a StartApp or StopApp action.
690#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
691pub struct AppActionResult {
692    pub package: String,
693}
694
695/// Result of a Scroll action.
696#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
697pub struct ScrollActionResult {
698    #[serde(default)]
699    pub point: Point,
700    pub dx: i32,
701    pub dy: i32,
702}
703
704/// Result of a TouchDown, TouchMove, or TouchUp action.
705#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
706pub struct TouchActionResult {
707    pub contact: i32,
708    pub point: Point,
709    #[serde(default)]
710    pub pressure: i32,
711}
712
713/// Result of a Shell action.
714#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
715pub struct ShellActionResult {
716    pub cmd: String,
717    pub shell_timeout: i32,
718    pub success: bool,
719    pub output: String,
720}
721
722/// Details of a pipeline node execution.
723#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
724pub struct NodeDetail {
725    pub node_name: String,
726    /// ID of the recognition operation
727    pub reco_id: MaaId,
728    /// ID of the action operation
729    pub act_id: MaaId,
730    /// Detailed recognition result
731    #[serde(default)]
732    pub recognition: Option<RecognitionDetail>,
733    /// Detailed action result
734    #[serde(default)]
735    pub action: Option<ActionDetail>,
736    /// Whether the node completed execution
737    pub completed: bool,
738}
739
740/// Details of a task execution.
741#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
742pub struct TaskDetail {
743    /// Entry point node name
744    pub entry: String,
745    /// List of node IDs that were executed
746    pub node_id_list: Vec<MaaId>,
747    /// Final status of the task
748    pub status: MaaStatus,
749    /// Detailed node information (hydrated from node_id_list)
750    #[serde(default)]
751    pub nodes: Vec<Option<NodeDetail>>,
752}
753
754/// Recognition algorithm types.
755#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
756#[serde(into = "String", from = "String")]
757pub enum AlgorithmEnum {
758    DirectHit,
759    TemplateMatch,
760    FeatureMatch,
761    ColorMatch,
762    OCR,
763    NeuralNetworkClassify,
764    NeuralNetworkDetect,
765    And,
766    Or,
767    Custom,
768    Other(String),
769}
770
771impl From<String> for AlgorithmEnum {
772    fn from(s: String) -> Self {
773        match s.as_str() {
774            "DirectHit" => Self::DirectHit,
775            "TemplateMatch" => Self::TemplateMatch,
776            "FeatureMatch" => Self::FeatureMatch,
777            "ColorMatch" => Self::ColorMatch,
778            "OCR" => Self::OCR,
779            "NeuralNetworkClassify" => Self::NeuralNetworkClassify,
780            "NeuralNetworkDetect" => Self::NeuralNetworkDetect,
781            "And" => Self::And,
782            "Or" => Self::Or,
783            "Custom" => Self::Custom,
784            _ => Self::Other(s),
785        }
786    }
787}
788
789impl From<AlgorithmEnum> for String {
790    fn from(algo: AlgorithmEnum) -> Self {
791        match algo {
792            AlgorithmEnum::DirectHit => "DirectHit".to_string(),
793            AlgorithmEnum::TemplateMatch => "TemplateMatch".to_string(),
794            AlgorithmEnum::FeatureMatch => "FeatureMatch".to_string(),
795            AlgorithmEnum::ColorMatch => "ColorMatch".to_string(),
796            AlgorithmEnum::OCR => "OCR".to_string(),
797            AlgorithmEnum::NeuralNetworkClassify => "NeuralNetworkClassify".to_string(),
798            AlgorithmEnum::NeuralNetworkDetect => "NeuralNetworkDetect".to_string(),
799            AlgorithmEnum::And => "And".to_string(),
800            AlgorithmEnum::Or => "Or".to_string(),
801            AlgorithmEnum::Custom => "Custom".to_string(),
802            AlgorithmEnum::Other(s) => s,
803        }
804    }
805}
806
807impl AlgorithmEnum {
808    pub fn as_str(&self) -> &str {
809        match self {
810            Self::DirectHit => "DirectHit",
811            Self::TemplateMatch => "TemplateMatch",
812            Self::FeatureMatch => "FeatureMatch",
813            Self::ColorMatch => "ColorMatch",
814            Self::OCR => "OCR",
815            Self::NeuralNetworkClassify => "NeuralNetworkClassify",
816            Self::NeuralNetworkDetect => "NeuralNetworkDetect",
817            Self::And => "And",
818            Self::Or => "Or",
819            Self::Custom => "Custom",
820            Self::Other(s) => s.as_str(),
821        }
822    }
823}
824
825/// Action types.
826#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
827#[serde(into = "String", from = "String")]
828pub enum ActionEnum {
829    DoNothing,
830    Click,
831    LongPress,
832    Swipe,
833    MultiSwipe,
834    TouchDown,
835    TouchMove,
836    TouchUp,
837    ClickKey,
838    LongPressKey,
839    KeyDown,
840    KeyUp,
841    InputText,
842    StartApp,
843    StopApp,
844    StopTask,
845    Scroll,
846    Command,
847    Shell,
848    Custom,
849    Other(String),
850}
851
852impl From<String> for ActionEnum {
853    fn from(s: String) -> Self {
854        match s.as_str() {
855            "DoNothing" => Self::DoNothing,
856            "Click" => Self::Click,
857            "LongPress" => Self::LongPress,
858            "Swipe" => Self::Swipe,
859            "MultiSwipe" => Self::MultiSwipe,
860            "TouchDown" => Self::TouchDown,
861            "TouchMove" => Self::TouchMove,
862            "TouchUp" => Self::TouchUp,
863            "ClickKey" => Self::ClickKey,
864            "LongPressKey" => Self::LongPressKey,
865            "KeyDown" => Self::KeyDown,
866            "KeyUp" => Self::KeyUp,
867            "InputText" => Self::InputText,
868            "StartApp" => Self::StartApp,
869            "StopApp" => Self::StopApp,
870            "StopTask" => Self::StopTask,
871            "Scroll" => Self::Scroll,
872            "Command" => Self::Command,
873            "Shell" => Self::Shell,
874            "Custom" => Self::Custom,
875            _ => Self::Other(s),
876        }
877    }
878}
879
880impl From<ActionEnum> for String {
881    fn from(act: ActionEnum) -> Self {
882        match act {
883            ActionEnum::DoNothing => "DoNothing".to_string(),
884            ActionEnum::Click => "Click".to_string(),
885            ActionEnum::LongPress => "LongPress".to_string(),
886            ActionEnum::Swipe => "Swipe".to_string(),
887            ActionEnum::MultiSwipe => "MultiSwipe".to_string(),
888            ActionEnum::TouchDown => "TouchDown".to_string(),
889            ActionEnum::TouchMove => "TouchMove".to_string(),
890            ActionEnum::TouchUp => "TouchUp".to_string(),
891            ActionEnum::ClickKey => "ClickKey".to_string(),
892            ActionEnum::LongPressKey => "LongPressKey".to_string(),
893            ActionEnum::KeyDown => "KeyDown".to_string(),
894            ActionEnum::KeyUp => "KeyUp".to_string(),
895            ActionEnum::InputText => "InputText".to_string(),
896            ActionEnum::StartApp => "StartApp".to_string(),
897            ActionEnum::StopApp => "StopApp".to_string(),
898            ActionEnum::StopTask => "StopTask".to_string(),
899            ActionEnum::Scroll => "Scroll".to_string(),
900            ActionEnum::Command => "Command".to_string(),
901            ActionEnum::Shell => "Shell".to_string(),
902            ActionEnum::Custom => "Custom".to_string(),
903            ActionEnum::Other(s) => s,
904        }
905    }
906}
907
908impl ActionEnum {
909    pub fn as_str(&self) -> &str {
910        match self {
911            Self::DoNothing => "DoNothing",
912            Self::Click => "Click",
913            Self::LongPress => "LongPress",
914            Self::Swipe => "Swipe",
915            Self::MultiSwipe => "MultiSwipe",
916            Self::TouchDown => "TouchDown",
917            Self::TouchMove => "TouchMove",
918            Self::TouchUp => "TouchUp",
919            Self::ClickKey => "ClickKey",
920            Self::LongPressKey => "LongPressKey",
921            Self::KeyDown => "KeyDown",
922            Self::KeyUp => "KeyUp",
923            Self::InputText => "InputText",
924            Self::StartApp => "StartApp",
925            Self::StopApp => "StopApp",
926            Self::StopTask => "StopTask",
927            Self::Scroll => "Scroll",
928            Self::Command => "Command",
929            Self::Shell => "Shell",
930            Self::Custom => "Custom",
931            Self::Other(s) => s.as_str(),
932        }
933    }
934}
935
936/// Notification type for event callbacks.
937#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
938#[non_exhaustive]
939pub enum NotificationType {
940    Starting,
941    Succeeded,
942    Failed,
943    Unknown,
944}
945
946impl NotificationType {
947    pub fn from_message(msg: &str) -> Self {
948        if msg.ends_with(".Starting") {
949            Self::Starting
950        } else if msg.ends_with(".Succeeded") {
951            Self::Succeeded
952        } else if msg.ends_with(".Failed") {
953            Self::Failed
954        } else {
955            Self::Unknown
956        }
957    }
958}
959
960// --- Result types ---
961
962#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
963pub struct BoxAndScore {
964    #[serde(rename = "box")]
965    pub box_rect: (i32, i32, i32, i32),
966    pub score: f64,
967}
968
969#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
970pub struct BoxAndCount {
971    #[serde(rename = "box")]
972    pub box_rect: (i32, i32, i32, i32),
973    pub count: i32,
974}
975
976pub type TemplateMatchResult = BoxAndScore;
977pub type FeatureMatchResult = BoxAndCount;
978pub type ColorMatchResult = BoxAndCount;
979
980#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
981pub struct OCRResult {
982    #[serde(flatten)]
983    pub base: BoxAndScore,
984    pub text: String,
985}
986
987#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
988pub struct NeuralNetworkResult {
989    #[serde(flatten)]
990    pub base: BoxAndScore,
991    pub cls_index: i32,
992    pub label: String,
993}
994
995pub type NeuralNetworkClassifyResult = NeuralNetworkResult;
996pub type NeuralNetworkDetectResult = NeuralNetworkResult;
997
998#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
999pub struct CustomRecognitionResult {
1000    #[serde(rename = "box")]
1001    pub box_rect: (i32, i32, i32, i32),
1002    pub detail: serde_json::Value,
1003}
1004
1005#[cfg(test)]
1006mod tests {
1007    use super::{
1008        AndroidNativeControllerConfig, AndroidScreenResolution, LinuxControllerConfig,
1009        LinuxInputMethod, LinuxScreencapMethod,
1010    };
1011    use serde_json::json;
1012
1013    #[test]
1014    fn android_native_controller_config_serializes_expected_shape() {
1015        let config = AndroidNativeControllerConfig {
1016            library_path: "/data/local/tmp/libmaa_unit.so".to_string(),
1017            screen_resolution: AndroidScreenResolution {
1018                width: 1920,
1019                height: 1080,
1020            },
1021            display_id: Some(1),
1022            force_stop: Some(true),
1023        };
1024
1025        let value = serde_json::to_value(config).unwrap();
1026
1027        assert_eq!(
1028            value,
1029            json!({
1030                "library_path": "/data/local/tmp/libmaa_unit.so",
1031                "screen_resolution": {
1032                    "width": 1920,
1033                    "height": 1080
1034                },
1035                "display_id": 1,
1036                "force_stop": true
1037            })
1038        );
1039    }
1040
1041    #[test]
1042    fn linux_controller_config_serializes_expected_shape() {
1043        let config = LinuxControllerConfig {
1044            screencap_method: LinuxScreencapMethod::PIPEWIRE.bits(),
1045            input_method: LinuxInputMethod::UINPUT.bits(),
1046            wlr_socket_path: None,
1047            pw_socket_fd: Some(42),
1048            pw_node_id: Some(7),
1049            pw_screen_width: Some(1920),
1050            pw_screen_height: Some(1080),
1051            uinput_path: None,
1052            use_win32_vk_code: Some(true),
1053        };
1054
1055        let value = serde_json::to_value(config).unwrap();
1056
1057        assert_eq!(
1058            value,
1059            json!({
1060                "screencap_method": 4,
1061                "input_method": 2,
1062                "pw_socket_fd": 42,
1063                "pw_node_id": 7,
1064                "pw_screen_width": 1920,
1065                "pw_screen_height": 1080,
1066                "use_win32_vk_code": true
1067            })
1068        );
1069    }
1070}