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. Set `MAA_INTERCEPTION_KEYBOARD_DEVICE` to select a keyboard by slot (0-9)
528        /// or hardware ID.
529        const INTERCEPTION = sys::MaaWin32InputMethod_Interception as u64;
530        /// Synthetic touch input via `WM_POINTER`.
531        ///
532        /// Does not move the cursor or target window. Activation is suppressed during touch,
533        /// but the target application can still bring itself to the foreground. An occluded
534        /// target may be temporarily raised at low opacity and intercept mouse clicks in its area.
535        /// Touch points must be on a monitor, and minimized targets must be restored first
536        /// (screencap methods with pseudo-minimize can do this). Supports clicking and swiping,
537        /// but not scrolling; keyboard input must use another method.
538        const ANCHORED_TOUCH = sys::MaaWin32InputMethod_AnchoredTouch as u64;
539    }
540}
541
542/// Details of a recognition operation result.
543#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
544pub struct RecognitionDetail {
545    /// Recognition ID this detail belongs to
546    pub reco_id: MaaId,
547    /// Name of the node that performed recognition
548    pub node_name: String,
549    /// Algorithm used
550    pub algorithm: AlgorithmEnum,
551    /// Whether recognition was successful
552    pub hit: bool,
553    /// Bounding box of the recognized region
554    pub box_rect: Rect,
555    /// Algorithm-specific detail JSON
556    pub detail: serde_json::Value,
557    /// Raw screenshot (PNG encoded, only valid in debug mode)
558    #[serde(skip)]
559    pub raw_image: Option<Vec<u8>>,
560    /// Debug draw images (PNG encoded, only valid in debug mode)
561    #[serde(skip)]
562    pub draw_images: Vec<Vec<u8>>,
563    /// Sub-process recognition details (for And/Or combinators)
564    #[serde(default)]
565    pub sub_details: Vec<RecognitionDetail>,
566}
567
568impl RecognitionDetail {
569    pub fn as_template_match_result(&self) -> Option<TemplateMatchResult> {
570        serde_json::from_value(self.detail.clone()).ok()
571    }
572
573    pub fn as_feature_match_result(&self) -> Option<FeatureMatchResult> {
574        serde_json::from_value(self.detail.clone()).ok()
575    }
576
577    pub fn as_color_match_result(&self) -> Option<ColorMatchResult> {
578        serde_json::from_value(self.detail.clone()).ok()
579    }
580
581    pub fn as_ocr_result(&self) -> Option<OCRResult> {
582        serde_json::from_value(self.detail.clone()).ok()
583    }
584
585    pub fn as_neural_network_result(&self) -> Option<NeuralNetworkResult> {
586        serde_json::from_value(self.detail.clone()).ok()
587    }
588
589    pub fn as_custom_result(&self) -> Option<CustomRecognitionResult> {
590        serde_json::from_value(self.detail.clone()).ok()
591    }
592}
593
594/// Details of an action operation result.
595#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
596pub struct ActionDetail {
597    /// Action ID this detail belongs to. Also populated for failed actions.
598    pub action_id: MaaId,
599    /// Name of the node that performed the action
600    pub node_name: String,
601    /// Action type
602    pub action: ActionEnum,
603    /// Target bounding box
604    pub box_rect: Rect,
605    /// Whether action was successful
606    pub success: bool,
607    /// Action-specific detail JSON
608    pub detail: serde_json::Value,
609}
610
611#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
612pub struct WaitFreezesDetail {
613    pub wf_id: MaaId,
614    pub name: String,
615    pub phase: String,
616    pub success: bool,
617    pub elapsed_ms: u64,
618    #[serde(default)]
619    pub reco_id_list: Vec<MaaId>,
620    pub roi: Rect,
621}
622
623impl ActionDetail {
624    pub fn as_click_result(&self) -> Option<ClickActionResult> {
625        serde_json::from_value(self.detail.clone()).ok()
626    }
627
628    pub fn as_long_press_result(&self) -> Option<LongPressActionResult> {
629        serde_json::from_value(self.detail.clone()).ok()
630    }
631
632    pub fn as_swipe_result(&self) -> Option<SwipeActionResult> {
633        serde_json::from_value(self.detail.clone()).ok()
634    }
635
636    pub fn as_multi_swipe_result(&self) -> Option<MultiSwipeActionResult> {
637        serde_json::from_value(self.detail.clone()).ok()
638    }
639
640    pub fn as_click_key_result(&self) -> Option<ClickKeyActionResult> {
641        serde_json::from_value(self.detail.clone()).ok()
642    }
643
644    pub fn as_input_text_result(&self) -> Option<InputTextActionResult> {
645        serde_json::from_value(self.detail.clone()).ok()
646    }
647
648    pub fn as_app_result(&self) -> Option<AppActionResult> {
649        serde_json::from_value(self.detail.clone()).ok()
650    }
651
652    pub fn as_scroll_result(&self) -> Option<ScrollActionResult> {
653        serde_json::from_value(self.detail.clone()).ok()
654    }
655
656    pub fn as_touch_result(&self) -> Option<TouchActionResult> {
657        serde_json::from_value(self.detail.clone()).ok()
658    }
659
660    pub fn as_shell_result(&self) -> Option<ShellActionResult> {
661        serde_json::from_value(self.detail.clone()).ok()
662    }
663}
664
665// ============================================================================
666// Action Result Types
667// ============================================================================
668
669/// Result of a Click action.
670#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
671pub struct ClickActionResult {
672    pub point: Point,
673    pub contact: i32,
674    #[serde(default)]
675    pub pressure: i32,
676}
677
678/// Result of a LongPress action.
679#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
680pub struct LongPressActionResult {
681    pub point: Point,
682    pub duration: i32,
683    pub contact: i32,
684    #[serde(default)]
685    pub pressure: i32,
686}
687
688/// Result of a Swipe action.
689#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
690pub struct SwipeActionResult {
691    pub begin: Point,
692    pub end: Vec<Point>,
693    #[serde(default)]
694    pub end_hold: Vec<i32>,
695    #[serde(default)]
696    pub duration: Vec<i32>,
697    #[serde(default)]
698    pub only_hover: bool,
699    #[serde(default)]
700    pub starting: i32,
701    pub contact: i32,
702    #[serde(default)]
703    pub pressure: i32,
704}
705
706/// Result of a MultiSwipe action.
707#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
708pub struct MultiSwipeActionResult {
709    pub swipes: Vec<SwipeActionResult>,
710}
711
712/// Result of a ClickKey, KeyDown, or KeyUp action.
713#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
714pub struct ClickKeyActionResult {
715    pub keycode: Vec<i32>,
716    /// Whether automatic release was requested for KeyDown. Default: false.
717    #[serde(default)]
718    pub auto_up: bool,
719}
720
721/// Result of a LongPressKey action.
722#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
723pub struct LongPressKeyActionResult {
724    pub keycode: Vec<i32>,
725    pub duration: i32,
726}
727
728/// Result of an InputText action.
729#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
730pub struct InputTextActionResult {
731    pub text: String,
732}
733
734/// Result of a StartApp or StopApp action.
735#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
736pub struct AppActionResult {
737    pub package: String,
738}
739
740/// Result of a Scroll action.
741#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
742pub struct ScrollActionResult {
743    #[serde(default)]
744    pub point: Point,
745    pub dx: i32,
746    pub dy: i32,
747}
748
749/// Result of a TouchDown, TouchMove, or TouchUp action.
750#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
751pub struct TouchActionResult {
752    pub contact: i32,
753    pub point: Point,
754    #[serde(default)]
755    pub pressure: i32,
756    /// Whether automatic release was requested for TouchDown. Default: false.
757    #[serde(default)]
758    pub auto_up: bool,
759}
760
761/// Result of a Shell action.
762#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
763pub struct ShellActionResult {
764    pub cmd: String,
765    pub shell_timeout: i32,
766    pub success: bool,
767    pub output: String,
768}
769
770/// Details of a pipeline node execution.
771#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
772pub struct NodeDetail {
773    pub node_name: String,
774    /// ID of the recognition operation
775    pub reco_id: MaaId,
776    /// ID of the action operation
777    pub act_id: MaaId,
778    /// Detailed recognition result
779    #[serde(default)]
780    pub recognition: Option<RecognitionDetail>,
781    /// Detailed action result
782    #[serde(default)]
783    pub action: Option<ActionDetail>,
784    /// Whether the node completed execution
785    pub completed: bool,
786}
787
788/// Details of a task execution.
789#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
790pub struct TaskDetail {
791    /// Entry point node name
792    pub entry: String,
793    /// List of node IDs that were executed
794    pub node_id_list: Vec<MaaId>,
795    /// Final status of the task
796    pub status: MaaStatus,
797    /// Detailed node information (hydrated from node_id_list)
798    #[serde(default)]
799    pub nodes: Vec<Option<NodeDetail>>,
800}
801
802/// Recognition algorithm types.
803#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
804#[serde(into = "String", from = "String")]
805pub enum AlgorithmEnum {
806    DirectHit,
807    TemplateMatch,
808    FeatureMatch,
809    ColorMatch,
810    OCR,
811    NeuralNetworkClassify,
812    NeuralNetworkDetect,
813    And,
814    Or,
815    Custom,
816    Other(String),
817}
818
819impl From<String> for AlgorithmEnum {
820    fn from(s: String) -> Self {
821        match s.as_str() {
822            "DirectHit" => Self::DirectHit,
823            "TemplateMatch" => Self::TemplateMatch,
824            "FeatureMatch" => Self::FeatureMatch,
825            "ColorMatch" => Self::ColorMatch,
826            "OCR" => Self::OCR,
827            "NeuralNetworkClassify" => Self::NeuralNetworkClassify,
828            "NeuralNetworkDetect" => Self::NeuralNetworkDetect,
829            "And" => Self::And,
830            "Or" => Self::Or,
831            "Custom" => Self::Custom,
832            _ => Self::Other(s),
833        }
834    }
835}
836
837impl From<AlgorithmEnum> for String {
838    fn from(algo: AlgorithmEnum) -> Self {
839        match algo {
840            AlgorithmEnum::DirectHit => "DirectHit".to_string(),
841            AlgorithmEnum::TemplateMatch => "TemplateMatch".to_string(),
842            AlgorithmEnum::FeatureMatch => "FeatureMatch".to_string(),
843            AlgorithmEnum::ColorMatch => "ColorMatch".to_string(),
844            AlgorithmEnum::OCR => "OCR".to_string(),
845            AlgorithmEnum::NeuralNetworkClassify => "NeuralNetworkClassify".to_string(),
846            AlgorithmEnum::NeuralNetworkDetect => "NeuralNetworkDetect".to_string(),
847            AlgorithmEnum::And => "And".to_string(),
848            AlgorithmEnum::Or => "Or".to_string(),
849            AlgorithmEnum::Custom => "Custom".to_string(),
850            AlgorithmEnum::Other(s) => s,
851        }
852    }
853}
854
855impl AlgorithmEnum {
856    pub fn as_str(&self) -> &str {
857        match self {
858            Self::DirectHit => "DirectHit",
859            Self::TemplateMatch => "TemplateMatch",
860            Self::FeatureMatch => "FeatureMatch",
861            Self::ColorMatch => "ColorMatch",
862            Self::OCR => "OCR",
863            Self::NeuralNetworkClassify => "NeuralNetworkClassify",
864            Self::NeuralNetworkDetect => "NeuralNetworkDetect",
865            Self::And => "And",
866            Self::Or => "Or",
867            Self::Custom => "Custom",
868            Self::Other(s) => s.as_str(),
869        }
870    }
871}
872
873/// Action types.
874#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
875#[serde(into = "String", from = "String")]
876pub enum ActionEnum {
877    DoNothing,
878    Click,
879    LongPress,
880    Swipe,
881    MultiSwipe,
882    TouchDown,
883    TouchMove,
884    TouchUp,
885    ClickKey,
886    LongPressKey,
887    KeyDown,
888    KeyUp,
889    InputText,
890    StartApp,
891    StopApp,
892    StopTask,
893    Scroll,
894    Command,
895    Shell,
896    Custom,
897    Other(String),
898}
899
900impl From<String> for ActionEnum {
901    fn from(s: String) -> Self {
902        match s.as_str() {
903            "DoNothing" => Self::DoNothing,
904            "Click" => Self::Click,
905            "LongPress" => Self::LongPress,
906            "Swipe" => Self::Swipe,
907            "MultiSwipe" => Self::MultiSwipe,
908            "TouchDown" => Self::TouchDown,
909            "TouchMove" => Self::TouchMove,
910            "TouchUp" => Self::TouchUp,
911            "ClickKey" => Self::ClickKey,
912            "LongPressKey" => Self::LongPressKey,
913            "KeyDown" => Self::KeyDown,
914            "KeyUp" => Self::KeyUp,
915            "InputText" => Self::InputText,
916            "StartApp" => Self::StartApp,
917            "StopApp" => Self::StopApp,
918            "StopTask" => Self::StopTask,
919            "Scroll" => Self::Scroll,
920            "Command" => Self::Command,
921            "Shell" => Self::Shell,
922            "Custom" => Self::Custom,
923            _ => Self::Other(s),
924        }
925    }
926}
927
928impl From<ActionEnum> for String {
929    fn from(act: ActionEnum) -> Self {
930        match act {
931            ActionEnum::DoNothing => "DoNothing".to_string(),
932            ActionEnum::Click => "Click".to_string(),
933            ActionEnum::LongPress => "LongPress".to_string(),
934            ActionEnum::Swipe => "Swipe".to_string(),
935            ActionEnum::MultiSwipe => "MultiSwipe".to_string(),
936            ActionEnum::TouchDown => "TouchDown".to_string(),
937            ActionEnum::TouchMove => "TouchMove".to_string(),
938            ActionEnum::TouchUp => "TouchUp".to_string(),
939            ActionEnum::ClickKey => "ClickKey".to_string(),
940            ActionEnum::LongPressKey => "LongPressKey".to_string(),
941            ActionEnum::KeyDown => "KeyDown".to_string(),
942            ActionEnum::KeyUp => "KeyUp".to_string(),
943            ActionEnum::InputText => "InputText".to_string(),
944            ActionEnum::StartApp => "StartApp".to_string(),
945            ActionEnum::StopApp => "StopApp".to_string(),
946            ActionEnum::StopTask => "StopTask".to_string(),
947            ActionEnum::Scroll => "Scroll".to_string(),
948            ActionEnum::Command => "Command".to_string(),
949            ActionEnum::Shell => "Shell".to_string(),
950            ActionEnum::Custom => "Custom".to_string(),
951            ActionEnum::Other(s) => s,
952        }
953    }
954}
955
956impl ActionEnum {
957    pub fn as_str(&self) -> &str {
958        match self {
959            Self::DoNothing => "DoNothing",
960            Self::Click => "Click",
961            Self::LongPress => "LongPress",
962            Self::Swipe => "Swipe",
963            Self::MultiSwipe => "MultiSwipe",
964            Self::TouchDown => "TouchDown",
965            Self::TouchMove => "TouchMove",
966            Self::TouchUp => "TouchUp",
967            Self::ClickKey => "ClickKey",
968            Self::LongPressKey => "LongPressKey",
969            Self::KeyDown => "KeyDown",
970            Self::KeyUp => "KeyUp",
971            Self::InputText => "InputText",
972            Self::StartApp => "StartApp",
973            Self::StopApp => "StopApp",
974            Self::StopTask => "StopTask",
975            Self::Scroll => "Scroll",
976            Self::Command => "Command",
977            Self::Shell => "Shell",
978            Self::Custom => "Custom",
979            Self::Other(s) => s.as_str(),
980        }
981    }
982}
983
984/// Notification type for event callbacks.
985#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
986#[non_exhaustive]
987pub enum NotificationType {
988    Starting,
989    Succeeded,
990    Failed,
991    Unknown,
992}
993
994impl NotificationType {
995    pub fn from_message(msg: &str) -> Self {
996        if msg.ends_with(".Starting") {
997            Self::Starting
998        } else if msg.ends_with(".Succeeded") {
999            Self::Succeeded
1000        } else if msg.ends_with(".Failed") {
1001            Self::Failed
1002        } else {
1003            Self::Unknown
1004        }
1005    }
1006}
1007
1008// --- Result types ---
1009
1010#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
1011pub struct BoxAndScore {
1012    #[serde(rename = "box")]
1013    pub box_rect: (i32, i32, i32, i32),
1014    pub score: f64,
1015}
1016
1017#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
1018pub struct BoxAndCount {
1019    #[serde(rename = "box")]
1020    pub box_rect: (i32, i32, i32, i32),
1021    pub count: i32,
1022}
1023
1024pub type TemplateMatchResult = BoxAndScore;
1025pub type FeatureMatchResult = BoxAndCount;
1026pub type ColorMatchResult = BoxAndCount;
1027
1028#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
1029pub struct OCRResult {
1030    #[serde(flatten)]
1031    pub base: BoxAndScore,
1032    pub text: String,
1033}
1034
1035#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
1036pub struct NeuralNetworkResult {
1037    #[serde(flatten)]
1038    pub base: BoxAndScore,
1039    pub cls_index: i32,
1040    pub label: String,
1041}
1042
1043pub type NeuralNetworkClassifyResult = NeuralNetworkResult;
1044pub type NeuralNetworkDetectResult = NeuralNetworkResult;
1045
1046#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
1047pub struct CustomRecognitionResult {
1048    #[serde(rename = "box")]
1049    pub box_rect: (i32, i32, i32, i32),
1050    pub detail: serde_json::Value,
1051}
1052
1053#[cfg(test)]
1054mod tests {
1055    use super::{
1056        AndroidNativeControllerConfig, AndroidScreenResolution, ClickKeyActionResult,
1057        LinuxControllerConfig, LinuxInputMethod, LinuxScreencapMethod, TouchActionResult,
1058    };
1059    use serde_json::json;
1060
1061    #[test]
1062    fn input_action_results_preserve_auto_up() {
1063        for auto_up in [None, Some(false), Some(true)] {
1064            let mut key = json!({"keycode": [65]});
1065            let mut touch = json!({"contact": 1, "point": [100, 200], "pressure": 2});
1066            if let Some(value) = auto_up {
1067                key["auto_up"] = json!(value);
1068                touch["auto_up"] = json!(value);
1069            }
1070            let key: ClickKeyActionResult = serde_json::from_value(key).unwrap();
1071            let touch: TouchActionResult = serde_json::from_value(touch).unwrap();
1072            assert_eq!(key.auto_up, auto_up.unwrap_or(false));
1073            assert_eq!(touch.auto_up, auto_up.unwrap_or(false));
1074            assert_eq!(
1075                serde_json::to_value(key).unwrap()["auto_up"],
1076                auto_up.unwrap_or(false)
1077            );
1078            assert_eq!(
1079                serde_json::to_value(touch).unwrap()["auto_up"],
1080                auto_up.unwrap_or(false)
1081            );
1082        }
1083    }
1084
1085    #[test]
1086    fn android_native_controller_config_serializes_expected_shape() {
1087        let config = AndroidNativeControllerConfig {
1088            library_path: "/data/local/tmp/libmaa_unit.so".to_string(),
1089            screen_resolution: AndroidScreenResolution {
1090                width: 1920,
1091                height: 1080,
1092            },
1093            display_id: Some(1),
1094            force_stop: Some(true),
1095        };
1096
1097        let value = serde_json::to_value(config).unwrap();
1098
1099        assert_eq!(
1100            value,
1101            json!({
1102                "library_path": "/data/local/tmp/libmaa_unit.so",
1103                "screen_resolution": {
1104                    "width": 1920,
1105                    "height": 1080
1106                },
1107                "display_id": 1,
1108                "force_stop": true
1109            })
1110        );
1111    }
1112
1113    #[test]
1114    fn linux_controller_config_serializes_expected_shape() {
1115        let config = LinuxControllerConfig {
1116            screencap_method: LinuxScreencapMethod::PIPEWIRE.bits(),
1117            input_method: LinuxInputMethod::UINPUT.bits(),
1118            wlr_socket_path: None,
1119            pw_socket_fd: Some(42),
1120            pw_node_id: Some(7),
1121            uinput_screen_width: Some(1920),
1122            uinput_screen_height: Some(1080),
1123            uinput_path: None,
1124            eis_socket_path: Some("/run/user/1000/gamescope-0-ei".into()),
1125            use_win32_vk_code: Some(true),
1126        };
1127
1128        let value = serde_json::to_value(config).unwrap();
1129
1130        assert_eq!(
1131            value,
1132            json!({
1133                "screencap_method": 4,
1134                "input_method": 2,
1135                "pw_socket_fd": 42,
1136                "pw_node_id": 7,
1137                "uinput_screen_width": 1920,
1138                "uinput_screen_height": 1080,
1139                "eis_socket_path": "/run/user/1000/gamescope-0-ei",
1140                "use_win32_vk_code": true
1141            })
1142        );
1143    }
1144
1145    #[test]
1146    fn linux_controller_config_accepts_legacy_size_keys() {
1147        let config: LinuxControllerConfig = serde_json::from_value(json!({
1148            "screencap_method": 4,
1149            "input_method": 2,
1150            "pw_screen_width": 1920,
1151            "pw_screen_height": 1080
1152        }))
1153        .unwrap();
1154
1155        assert_eq!(config.uinput_screen_width, Some(1920));
1156        assert_eq!(config.uinput_screen_height, Some(1080));
1157    }
1158}