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