Skip to main content

maa_framework/
controller.rs

1//! Device controller for input, screen capture, and app management.
2
3use crate::{MaaError, MaaResult, common, sys};
4use serde::Serialize;
5use std::collections::HashMap;
6use std::ffi::CString;
7use std::os::raw::c_void;
8#[cfg(feature = "dynamic")]
9use std::panic::AssertUnwindSafe;
10use std::ptr::NonNull;
11use std::sync::{Arc, Mutex};
12
13/// Device controller interface.
14///
15/// Handles interaction with the target device, including:
16/// - Input events (click, swipe, key press)
17/// - Screen capture
18/// - App management (start/stop)
19/// - Connection management
20///
21/// See also: [`AdbControllerBuilder`] for advanced ADB configuration.
22#[derive(Clone)]
23pub struct Controller {
24    inner: Arc<ControllerInner>,
25}
26
27struct ControllerInner {
28    handle: NonNull<sys::MaaController>,
29    owns_handle: bool,
30    _retained_handles: Vec<Arc<ControllerInner>>,
31    callbacks: Mutex<HashMap<sys::MaaSinkId, usize>>,
32    event_sinks: Mutex<HashMap<sys::MaaSinkId, usize>>,
33}
34
35unsafe impl Send for ControllerInner {}
36unsafe impl Sync for ControllerInner {}
37
38// Controller is Send/Sync because it holds Arc<ControllerInner> which is Send/Sync
39unsafe impl Send for Controller {}
40unsafe impl Sync for Controller {}
41
42impl std::fmt::Debug for Controller {
43    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
44        f.debug_struct("Controller")
45            .field("handle", &self.inner.handle)
46            .finish()
47    }
48}
49
50impl Controller {
51    /// Create a new ADB controller for Android device control.
52    ///
53    /// # Arguments
54    /// * `adb_path` - Path to the ADB executable
55    /// * `address` - Device address (e.g., "127.0.0.1:5555" or "emulator-5554")
56    /// * `config` - JSON configuration string for advanced options
57    /// * `agent_path` - Path to MaaAgent binary; pass `""` to use current directory (may return `Err` if resolution fails).
58    #[cfg(feature = "adb")]
59    pub fn new_adb(
60        adb_path: &str,
61        address: &str,
62        config: &str,
63        agent_path: &str,
64    ) -> MaaResult<Self> {
65        Self::create_adb(
66            adb_path,
67            address,
68            sys::MaaAdbScreencapMethod_Default as sys::MaaAdbScreencapMethod,
69            sys::MaaAdbInputMethod_Default as sys::MaaAdbInputMethod,
70            config,
71            agent_path,
72        )
73    }
74
75    /// Resolves to the current directory if the string is empty; otherwise, uses it as-is. Returns Err if parsing fails.
76    #[cfg(feature = "adb")]
77    fn resolve_agent_path(agent_path: &str) -> MaaResult<String> {
78        if !agent_path.is_empty() {
79            return Ok(agent_path.to_string());
80        }
81        let cur = std::env::current_dir().map_err(|e| {
82            MaaError::InvalidArgument(format!("agent_path empty and current_dir failed: {}", e))
83        })?;
84        let s = cur.to_str().ok_or_else(|| {
85            MaaError::InvalidArgument(
86                "agent_path empty and current directory is not valid UTF-8".to_string(),
87            )
88        })?;
89        Ok(s.to_string())
90    }
91
92    #[cfg(feature = "adb")]
93    pub(crate) fn create_adb(
94        adb_path: &str,
95        address: &str,
96        screencap_method: sys::MaaAdbScreencapMethod,
97        input_method: sys::MaaAdbInputMethod,
98        config: &str,
99        agent_path: &str,
100    ) -> MaaResult<Self> {
101        let path = Self::resolve_agent_path(agent_path)?;
102        let c_adb = CString::new(adb_path)?;
103        let c_addr = CString::new(address)?;
104        let c_cfg = CString::new(config)?;
105        let c_agent = CString::new(path.as_str())?;
106
107        let handle = unsafe {
108            sys::MaaAdbControllerCreate(
109                c_adb.as_ptr(),
110                c_addr.as_ptr(),
111                screencap_method,
112                input_method,
113                c_cfg.as_ptr(),
114                c_agent.as_ptr(),
115            )
116        };
117
118        if let Some(ptr) = NonNull::new(handle) {
119            Ok(Self::new_owned(ptr))
120        } else {
121            Err(MaaError::FrameworkError(-1))
122        }
123    }
124
125    /// Create a new Win32 controller for Windows window control.
126    #[cfg(feature = "win32")]
127    pub fn new_win32(
128        hwnd: *mut c_void,
129        screencap_method: sys::MaaWin32ScreencapMethod,
130        mouse_method: sys::MaaWin32InputMethod,
131        keyboard_method: sys::MaaWin32InputMethod,
132    ) -> MaaResult<Self> {
133        let handle = unsafe {
134            sys::MaaWin32ControllerCreate(hwnd, screencap_method, mouse_method, keyboard_method)
135        };
136
137        Self::from_handle(handle)
138    }
139
140    /// Create a new macOS controller for native macOS window control.
141    ///
142    /// # Arguments
143    /// * `window_id` - Target `CGWindowID` (use `0` for desktop)
144    /// * `screencap_method` - macOS screenshot method
145    /// * `input_method` - macOS input method
146    pub fn new_macos(
147        window_id: u32,
148        screencap_method: sys::MaaMacOSScreencapMethod,
149        input_method: sys::MaaMacOSInputMethod,
150    ) -> MaaResult<Self> {
151        let handle =
152            unsafe { sys::MaaMacOSControllerCreate(window_id, screencap_method, input_method) };
153
154        Self::from_handle(handle)
155    }
156
157    /// Create a new Android native controller.
158    ///
159    /// The config is serialized to JSON and passed to
160    /// `MaaAndroidNativeControllerCreate`. You can pass either a
161    /// [`common::AndroidNativeControllerConfig`] value or any other serializable
162    /// type that matches the expected JSON schema.
163    pub fn new_android_native<T: Serialize>(config: &T) -> MaaResult<Self> {
164        let config_json = serde_json::to_string(config).map_err(|e| {
165            MaaError::InvalidConfig(format!(
166                "Failed to serialize Android native controller config: {}",
167                e
168            ))
169        })?;
170        let c_config = CString::new(config_json)?;
171
172        #[cfg(feature = "dynamic")]
173        let handle = std::panic::catch_unwind(AssertUnwindSafe(|| unsafe {
174            sys::MaaAndroidNativeControllerCreate(c_config.as_ptr())
175        }))
176        .map_err(|_| {
177            MaaError::InvalidArgument(
178                "Android native controller is not available in this MaaFramework build".to_string(),
179            )
180        })?;
181
182        #[cfg(not(feature = "dynamic"))]
183        let handle = unsafe { sys::MaaAndroidNativeControllerCreate(c_config.as_ptr()) };
184
185        Self::from_handle(handle)
186    }
187
188    /// Create a new PlayCover controller for iOS app control on macOS.
189
190    pub fn new_playcover(address: &str, uuid: &str) -> MaaResult<Self> {
191        let c_addr = CString::new(address)?;
192        let c_uuid = CString::new(uuid)?;
193        let handle = unsafe { sys::MaaPlayCoverControllerCreate(c_addr.as_ptr(), c_uuid.as_ptr()) };
194
195        Self::from_handle(handle)
196    }
197
198    /// Create a new WlRoots controller for apps running in wlroots compositor on Linux.
199    ///
200    /// # Arguments
201    /// * `wlr_socket_path` - Wayland socket path
202    pub fn new_wlroots(wlr_socket_path: &str) -> MaaResult<Self> {
203        Self::new_wlroots_with_vk_code(wlr_socket_path, false)
204    }
205
206    /// Create a new WlRoots controller for apps running in wlroots compositor on Linux.
207    ///
208    /// # Arguments
209    /// * `wlr_socket_path` - Wayland socket path
210    /// * `use_win32_vk_code` - Interpret key codes as Win32 Virtual-Key codes and translate
211    ///   them to Linux evdev codes internally when set to `true`
212    pub fn new_wlroots_with_vk_code(
213        wlr_socket_path: &str,
214        use_win32_vk_code: bool,
215    ) -> MaaResult<Self> {
216        let c_path = CString::new(wlr_socket_path)?;
217        let handle = unsafe {
218            sys::MaaWlRootsControllerCreate(c_path.as_ptr(), use_win32_vk_code as sys::MaaBool)
219        };
220
221        Self::from_handle(handle)
222    }
223
224    /// Create a new KWin (pure Wayland) controller for Linux.
225    ///
226    /// Input is simulated via `/dev/uinput` (kernel-level virtual touchscreen) and
227    /// screencap is implemented via PipeWire / xdg-desktop-portal (KDE/KWin),
228    /// capturing the foreground monitor in fullscreen mode.
229    ///
230    /// # Arguments
231    /// * `device_node` - The uinput device node path (e.g. `/dev/uinput`)
232    /// * `screen_width` - The screen width in pixels
233    /// * `screen_height` - The screen height in pixels
234    ///
235    /// # Notes
236    /// * Requires user authorization via the screen sharing dialog (xdg-desktop-portal).
237    /// * Requires write permission to `/dev/uinput` (typically via the `input` group).
238    /// * Only single touch is supported (contact must be `0`).
239    ///
240    /// Only available on Linux: the underlying `MaaKWinControllerCreate` symbol is
241    /// not exported by the Windows/macOS builds of MaaFramework.
242    #[cfg(target_os = "linux")]
243    pub fn new_kwin(device_node: &str, screen_width: i32, screen_height: i32) -> MaaResult<Self> {
244        Self::new_kwin_with_vk_code(device_node, screen_width, screen_height, false)
245    }
246
247    /// Create a new KWin (pure Wayland) controller for Linux.
248    ///
249    /// Same as [`new_kwin`](Self::new_kwin), but lets you control how key codes are
250    /// interpreted.
251    ///
252    /// # Arguments
253    /// * `device_node` - The uinput device node path (e.g. `/dev/uinput`)
254    /// * `screen_width` - The screen width in pixels
255    /// * `screen_height` - The screen height in pixels
256    /// * `use_win32_vk_code` - Interpret key codes as Win32 Virtual-Key codes and translate
257    ///   them to Linux evdev codes internally when set to `true`
258    #[cfg(target_os = "linux")]
259    pub fn new_kwin_with_vk_code(
260        device_node: &str,
261        screen_width: i32,
262        screen_height: i32,
263        use_win32_vk_code: bool,
264    ) -> MaaResult<Self> {
265        let c_node = CString::new(device_node)?;
266        let handle = unsafe {
267            sys::MaaKWinControllerCreate(
268                c_node.as_ptr(),
269                screen_width,
270                screen_height,
271                use_win32_vk_code as sys::MaaBool,
272            )
273        };
274
275        Self::from_handle(handle)
276    }
277
278    /// Create a custom controller with user-defined callbacks.
279    #[cfg(feature = "custom")]
280    pub fn new_custom<T: crate::custom_controller::CustomControllerCallback + 'static>(
281        callback: T,
282    ) -> MaaResult<Self> {
283        let boxed: Box<Box<dyn crate::custom_controller::CustomControllerCallback>> =
284            Box::new(Box::new(callback));
285        let cb_ptr = Box::into_raw(boxed) as *mut c_void;
286        let callbacks = crate::custom_controller::get_callbacks();
287        let handle =
288            unsafe { sys::MaaCustomControllerCreate(callbacks as *const _ as *mut _, cb_ptr) };
289
290        NonNull::new(handle).map(Self::new_owned).ok_or_else(|| {
291            unsafe {
292                let _ = Box::from_raw(
293                    cb_ptr as *mut Box<dyn crate::custom_controller::CustomControllerCallback>,
294                );
295            }
296            MaaError::FrameworkError(-1)
297        })
298    }
299
300    /// Helper to create controller from raw handle.
301    fn from_handle(handle: *mut sys::MaaController) -> MaaResult<Self> {
302        if let Some(ptr) = NonNull::new(handle) {
303            Ok(Self::new_owned(ptr))
304        } else {
305            Err(MaaError::FrameworkError(-1))
306        }
307    }
308
309    fn new_owned(handle: NonNull<sys::MaaController>) -> Self {
310        Self::new_with_retained(handle, Vec::new())
311    }
312
313    fn new_with_retained(
314        handle: NonNull<sys::MaaController>,
315        retained_handles: Vec<Arc<ControllerInner>>,
316    ) -> Self {
317        Self {
318            inner: Arc::new(ControllerInner {
319                handle,
320                owns_handle: true,
321                _retained_handles: retained_handles,
322                callbacks: Mutex::new(HashMap::new()),
323                event_sinks: Mutex::new(HashMap::new()),
324            }),
325        }
326    }
327
328    /// Post a click action at the specified coordinates.
329    pub fn post_click(&self, x: i32, y: i32) -> MaaResult<common::MaaId> {
330        let id = unsafe { sys::MaaControllerPostClick(self.inner.handle.as_ptr(), x, y) };
331        Ok(id)
332    }
333
334    /// Post a screenshot capture request.
335    pub fn post_screencap(&self) -> MaaResult<common::MaaId> {
336        let id = unsafe { sys::MaaControllerPostScreencap(self.inner.handle.as_ptr()) };
337        Ok(id)
338    }
339
340    /// Post a click action with contact and pressure parameters.
341    ///
342    /// # Arguments
343    /// * `x`, `y` - Click coordinates
344    /// * `contact` - Contact/finger index (for multi-touch)
345    /// * `pressure` - Touch pressure (1 = normal)
346    pub fn post_click_v2(
347        &self,
348        x: i32,
349        y: i32,
350        contact: i32,
351        pressure: i32,
352    ) -> MaaResult<common::MaaId> {
353        let id = unsafe {
354            sys::MaaControllerPostClickV2(self.inner.handle.as_ptr(), x, y, contact, pressure)
355        };
356        Ok(id)
357    }
358
359    /// Post a swipe action from one point to another.
360    ///
361    /// # Arguments
362    /// * `x1`, `y1` - Start coordinates
363    /// * `x2`, `y2` - End coordinates
364    /// * `duration` - Swipe duration in milliseconds
365    pub fn post_swipe(
366        &self,
367        x1: i32,
368        y1: i32,
369        x2: i32,
370        y2: i32,
371        duration: i32,
372    ) -> MaaResult<common::MaaId> {
373        let id = unsafe {
374            sys::MaaControllerPostSwipe(self.inner.handle.as_ptr(), x1, y1, x2, y2, duration)
375        };
376        Ok(id)
377    }
378
379    /// Post a key click action.
380    ///
381    /// # Arguments
382    /// * `keycode` - Virtual key code (ADB keycode for Android, VK for Win32)
383    pub fn post_click_key(&self, keycode: i32) -> MaaResult<common::MaaId> {
384        let id = unsafe { sys::MaaControllerPostClickKey(self.inner.handle.as_ptr(), keycode) };
385        Ok(id)
386    }
387
388    /// Alias for [`post_click_key`](Self::post_click_key).
389    #[deprecated(note = "Use post_click_key instead")]
390    pub fn post_press(&self, keycode: i32) -> MaaResult<common::MaaId> {
391        self.post_click_key(keycode)
392    }
393
394    /// Post a text input action.
395    ///
396    /// # Arguments
397    /// * `text` - Text to input
398    pub fn post_input_text(&self, text: &str) -> MaaResult<common::MaaId> {
399        let c_text = CString::new(text)?;
400        let id =
401            unsafe { sys::MaaControllerPostInputText(self.inner.handle.as_ptr(), c_text.as_ptr()) };
402        Ok(id)
403    }
404
405    /// Post a shell command execution on controllers that support shell access.
406    ///
407    /// # Arguments
408    /// * `cmd` - Shell command to execute
409    /// * `timeout` - Timeout in milliseconds
410    pub fn post_shell(&self, cmd: &str, timeout: i64) -> MaaResult<common::MaaId> {
411        let c_cmd = CString::new(cmd)?;
412        let id = unsafe {
413            sys::MaaControllerPostShell(self.inner.handle.as_ptr(), c_cmd.as_ptr(), timeout)
414        };
415        Ok(id)
416    }
417
418    /// Post a touch down event.
419    ///
420    /// # Arguments
421    /// * `contact` - Contact/finger index
422    /// * `x`, `y` - Touch coordinates
423    /// * `pressure` - Touch pressure
424    pub fn post_touch_down(
425        &self,
426        contact: i32,
427        x: i32,
428        y: i32,
429        pressure: i32,
430    ) -> MaaResult<common::MaaId> {
431        let id = unsafe {
432            sys::MaaControllerPostTouchDown(self.inner.handle.as_ptr(), contact, x, y, pressure)
433        };
434        Ok(id)
435    }
436
437    /// Post a touch move event.
438    ///
439    /// # Arguments
440    /// * `contact` - Contact/finger index
441    /// * `x`, `y` - New touch coordinates
442    /// * `pressure` - Touch pressure
443    pub fn post_touch_move(
444        &self,
445        contact: i32,
446        x: i32,
447        y: i32,
448        pressure: i32,
449    ) -> MaaResult<common::MaaId> {
450        let id = unsafe {
451            sys::MaaControllerPostTouchMove(self.inner.handle.as_ptr(), contact, x, y, pressure)
452        };
453        Ok(id)
454    }
455
456    /// Post a touch up event.
457    ///
458    /// # Arguments
459    /// * `contact` - Contact/finger index to release
460    pub fn post_touch_up(&self, contact: i32) -> MaaResult<common::MaaId> {
461        let id = unsafe { sys::MaaControllerPostTouchUp(self.inner.handle.as_ptr(), contact) };
462        Ok(id)
463    }
464
465    /// Post a relative movement action on controllers that support it.
466    ///
467    /// # Arguments
468    /// * `dx` - Relative horizontal movement offset
469    /// * `dy` - Relative vertical movement offset
470    pub fn post_relative_move(&self, dx: i32, dy: i32) -> MaaResult<common::MaaId> {
471        let id = unsafe { sys::MaaControllerPostRelativeMove(self.inner.handle.as_ptr(), dx, dy) };
472        Ok(id)
473    }
474
475    /// Returns the underlying raw controller handle.
476    #[inline]
477    pub fn raw(&self) -> *mut sys::MaaController {
478        self.inner.handle.as_ptr()
479    }
480
481    // === Connection ===
482
483    /// Post a connection request to the device.
484    ///
485    /// Returns a job ID that can be used with [`wait`](Self::wait) to block until connected.
486    pub fn post_connection(&self) -> MaaResult<common::MaaId> {
487        let id = unsafe { sys::MaaControllerPostConnection(self.inner.handle.as_ptr()) };
488        Ok(id)
489    }
490
491    /// Returns `true` if the controller is connected to the device.
492    pub fn connected(&self) -> bool {
493        unsafe { sys::MaaControllerConnected(self.inner.handle.as_ptr()) != 0 }
494    }
495
496    /// Gets the unique identifier (UUID) of the connected device.
497    pub fn uuid(&self) -> MaaResult<String> {
498        let buffer = crate::buffer::MaaStringBuffer::new()?;
499        let ret = unsafe { sys::MaaControllerGetUuid(self.inner.handle.as_ptr(), buffer.as_ptr()) };
500        if ret != 0 {
501            Ok(buffer.to_string())
502        } else {
503            Err(MaaError::FrameworkError(0))
504        }
505    }
506
507    /// Gets the controller information as a JSON value.
508    ///
509    /// Returns controller-specific information including type, constructor parameters
510    /// and current state. The returned JSON always contains a "type" field.
511    pub fn info(&self) -> MaaResult<serde_json::Value> {
512        let buffer = crate::buffer::MaaStringBuffer::new()?;
513        let ret = unsafe { sys::MaaControllerGetInfo(self.inner.handle.as_ptr(), buffer.as_ptr()) };
514        if ret != 0 {
515            serde_json::from_str(&buffer.to_string()).map_err(|e| {
516                MaaError::InvalidArgument(format!("Failed to parse controller info: {}", e))
517            })
518        } else {
519            Err(MaaError::FrameworkError(0))
520        }
521    }
522
523    /// Gets the device screen resolution as (width, height).
524    pub fn resolution(&self) -> MaaResult<(i32, i32)> {
525        let mut width: i32 = 0;
526        let mut height: i32 = 0;
527        let ret = unsafe {
528            sys::MaaControllerGetResolution(self.inner.handle.as_ptr(), &mut width, &mut height)
529        };
530        if ret != 0 {
531            Ok((width, height))
532        } else {
533            Err(MaaError::FrameworkError(0))
534        }
535    }
536
537    // === Swipe V2 ===
538
539    /// Post a swipe action with contact and pressure parameters.
540    ///
541    /// # Arguments
542    /// * `x1`, `y1` - Start coordinates
543    /// * `x2`, `y2` - End coordinates
544    /// * `duration` - Swipe duration in milliseconds
545    /// * `contact` - Contact/finger index
546    /// * `pressure` - Touch pressure
547    pub fn post_swipe_v2(
548        &self,
549        x1: i32,
550        y1: i32,
551        x2: i32,
552        y2: i32,
553        duration: i32,
554        contact: i32,
555        pressure: i32,
556    ) -> MaaResult<common::MaaId> {
557        let id = unsafe {
558            sys::MaaControllerPostSwipeV2(
559                self.inner.handle.as_ptr(),
560                x1,
561                y1,
562                x2,
563                y2,
564                duration,
565                contact,
566                pressure,
567            )
568        };
569        Ok(id)
570    }
571
572    // === Key control ===
573
574    /// Post a key down event.
575    pub fn post_key_down(&self, keycode: i32) -> MaaResult<common::MaaId> {
576        let id = unsafe { sys::MaaControllerPostKeyDown(self.inner.handle.as_ptr(), keycode) };
577        Ok(id)
578    }
579
580    /// Post a key up event.
581    pub fn post_key_up(&self, keycode: i32) -> MaaResult<common::MaaId> {
582        let id = unsafe { sys::MaaControllerPostKeyUp(self.inner.handle.as_ptr(), keycode) };
583        Ok(id)
584    }
585
586    // === App control ===
587
588    /// Start an application.
589    ///
590    /// # Arguments
591    /// * `intent` - Package name or activity (ADB), app identifier (Win32)
592    pub fn post_start_app(&self, intent: &str) -> MaaResult<common::MaaId> {
593        let c_intent = CString::new(intent)?;
594        let id = unsafe {
595            sys::MaaControllerPostStartApp(self.inner.handle.as_ptr(), c_intent.as_ptr())
596        };
597        Ok(id)
598    }
599
600    /// Stop an application.
601    ///
602    /// # Arguments
603    /// * `intent` - Package name (ADB)
604    pub fn post_stop_app(&self, intent: &str) -> MaaResult<common::MaaId> {
605        let c_intent = CString::new(intent)?;
606        let id =
607            unsafe { sys::MaaControllerPostStopApp(self.inner.handle.as_ptr(), c_intent.as_ptr()) };
608        Ok(id)
609    }
610
611    // === Scroll ===
612
613    /// Post a scroll action on controllers that support it.
614    ///
615    /// # Arguments
616    /// * `dx` - Horizontal scroll delta (positive = right)
617    /// * `dy` - Vertical scroll delta (positive = down)
618    pub fn post_scroll(&self, dx: i32, dy: i32) -> MaaResult<common::MaaId> {
619        let id = unsafe { sys::MaaControllerPostScroll(self.inner.handle.as_ptr(), dx, dy) };
620        Ok(id)
621    }
622
623    // === Inactive ===
624
625    /// Post an inactive request to the controller.
626    ///
627    /// For Win32 controllers, this restores window position (removes topmost) and unblocks user input.
628    /// For other controllers, this is a no-op that always succeeds.
629    pub fn post_inactive(&self) -> MaaResult<common::MaaId> {
630        let id = unsafe { sys::MaaControllerPostInactive(self.inner.handle.as_ptr()) };
631        Ok(id)
632    }
633
634    // === Image ===
635
636    /// Gets the most recently captured screenshot.
637    pub fn cached_image(&self) -> MaaResult<crate::buffer::MaaImageBuffer> {
638        let buffer = crate::buffer::MaaImageBuffer::new()?;
639        let ret =
640            unsafe { sys::MaaControllerCachedImage(self.inner.handle.as_ptr(), buffer.as_ptr()) };
641        if ret != 0 {
642            Ok(buffer)
643        } else {
644            Err(MaaError::FrameworkError(0))
645        }
646    }
647
648    // === Shell output ===
649
650    /// Gets the output from the most recent shell command.
651    pub fn shell_output(&self) -> MaaResult<String> {
652        let buffer = crate::buffer::MaaStringBuffer::new()?;
653        let ret = unsafe {
654            sys::MaaControllerGetShellOutput(self.inner.handle.as_ptr(), buffer.as_ptr())
655        };
656        if ret != 0 {
657            Ok(buffer.to_string())
658        } else {
659            Err(MaaError::FrameworkError(0))
660        }
661    }
662
663    // === Status ===
664
665    /// Gets the status of a controller operation.
666    pub fn status(&self, ctrl_id: common::MaaId) -> common::MaaStatus {
667        let s = unsafe { sys::MaaControllerStatus(self.inner.handle.as_ptr(), ctrl_id) };
668        common::MaaStatus(s)
669    }
670
671    /// Blocks until a controller operation completes.
672    pub fn wait(&self, ctrl_id: common::MaaId) -> common::MaaStatus {
673        let s = unsafe { sys::MaaControllerWait(self.inner.handle.as_ptr(), ctrl_id) };
674        common::MaaStatus(s)
675    }
676
677    // === Screenshot options ===
678
679    /// Sets the target long side for screenshot scaling.
680    pub fn set_screenshot_target_long_side(&self, long_side: i32) -> MaaResult<()> {
681        let mut val = long_side;
682        let ret = unsafe {
683            sys::MaaControllerSetOption(
684                self.inner.handle.as_ptr(),
685                sys::MaaCtrlOptionEnum_MaaCtrlOption_ScreenshotTargetLongSide as i32,
686                &mut val as *mut _ as *mut c_void,
687                std::mem::size_of::<i32>() as u64,
688            )
689        };
690        common::check_bool(ret)
691    }
692
693    /// Sets the target short side for screenshot scaling.
694    pub fn set_screenshot_target_short_side(&self, short_side: i32) -> MaaResult<()> {
695        let mut val = short_side;
696        let ret = unsafe {
697            sys::MaaControllerSetOption(
698                self.inner.handle.as_ptr(),
699                sys::MaaCtrlOptionEnum_MaaCtrlOption_ScreenshotTargetShortSide as i32,
700                &mut val as *mut _ as *mut c_void,
701                std::mem::size_of::<i32>() as u64,
702            )
703        };
704        common::check_bool(ret)
705    }
706
707    /// Sets whether to use raw (unscaled) screenshot resolution.
708    pub fn set_screenshot_use_raw_size(&self, enable: bool) -> MaaResult<()> {
709        let mut val: u8 = if enable { 1 } else { 0 };
710        let ret = unsafe {
711            sys::MaaControllerSetOption(
712                self.inner.handle.as_ptr(),
713                sys::MaaCtrlOptionEnum_MaaCtrlOption_ScreenshotUseRawSize as i32,
714                &mut val as *mut _ as *mut c_void,
715                std::mem::size_of::<u8>() as u64,
716            )
717        };
718        common::check_bool(ret)
719    }
720
721    /// Sets the interpolation method used when resizing screenshots.
722    ///
723    /// Values correspond to OpenCV interpolation flags:
724    /// 0 = INTER_NEAREST
725    /// 1 = INTER_LINEAR
726    /// 2 = INTER_CUBIC
727    /// 3 = INTER_AREA
728    /// 4 = INTER_LANCZOS4
729    pub fn set_screenshot_resize_method(&self, method: i32) -> MaaResult<()> {
730        let mut val = method;
731        let ret = unsafe {
732            sys::MaaControllerSetOption(
733                self.inner.handle.as_ptr(),
734                sys::MaaCtrlOptionEnum_MaaCtrlOption_ScreenshotResizeMethod as i32,
735                &mut val as *mut _ as *mut c_void,
736                std::mem::size_of::<i32>() as u64,
737            )
738        };
739        common::check_bool(ret)
740    }
741
742    /// Sets whether to enable mouse-lock-follow mode.
743    ///
744    /// For Win32 controllers, useful for TPS/FPS games that lock the mouse
745    /// to their window while running in the background.
746    pub fn set_mouse_lock_follow(&self, enable: bool) -> MaaResult<()> {
747        let mut val: u8 = if enable { 1 } else { 0 };
748        let ret = unsafe {
749            sys::MaaControllerSetOption(
750                self.inner.handle.as_ptr(),
751                sys::MaaCtrlOptionEnum_MaaCtrlOption_MouseLockFollow as i32,
752                &mut val as *mut _ as *mut c_void,
753                std::mem::size_of::<u8>() as u64,
754            )
755        };
756        common::check_bool(ret)
757    }
758
759    /// Configure background managed key domain for Win32 controllers.
760    ///
761    /// Must be set before connection. After setting, matching ClickKey / LongPressKey / KeyDown / KeyUp
762    /// operations automatically route through the background guardian path.
763    /// Only supported by Win32 controllers; other controllers will fail.
764    ///
765    /// Pass an empty slice to clear managed keys.
766    pub fn set_background_managed_keys(&self, keys: &[i32]) -> MaaResult<()> {
767        let ret = unsafe {
768            sys::MaaControllerSetOption(
769                self.inner.handle.as_ptr(),
770                sys::MaaCtrlOptionEnum_MaaCtrlOption_BackgroundManagedKeys as i32,
771                keys.as_ptr() as *mut c_void,
772                std::mem::size_of_val(keys) as u64,
773            )
774        };
775        common::check_bool(ret)
776    }
777
778    // === New controller types ===
779
780    pub fn new_dbg(read_path: &str) -> MaaResult<Self> {
781        let c_read = CString::new(read_path)?;
782        let handle = unsafe { sys::MaaDbgControllerCreate(c_read.as_ptr()) };
783        Self::from_handle(handle)
784    }
785
786    /// Create a replay controller for replaying recorded controller operations.
787    pub fn new_replay(recording_path: &str) -> MaaResult<Self> {
788        let c_recording = CString::new(recording_path)?;
789        let handle = unsafe { sys::MaaReplayControllerCreate(c_recording.as_ptr()) };
790        Self::from_handle(handle)
791    }
792
793    /// Create a record controller that wraps another controller and records all operations.
794    pub fn new_record(inner: &Controller, recording_path: &str) -> MaaResult<Self> {
795        let c_recording = CString::new(recording_path)?;
796        let handle = unsafe { sys::MaaRecordControllerCreate(inner.raw(), c_recording.as_ptr()) };
797
798        if let Some(ptr) = NonNull::new(handle) {
799            Ok(Self::new_with_retained(ptr, vec![Arc::clone(&inner.inner)]))
800        } else {
801            Err(MaaError::FrameworkError(-1))
802        }
803    }
804
805    /// Create a virtual gamepad controller (Windows only).
806    #[cfg(feature = "win32")]
807    pub fn new_gamepad(
808        hwnd: *mut c_void,
809        gamepad_type: crate::common::GamepadType,
810        screencap_method: crate::common::Win32ScreencapMethod,
811    ) -> MaaResult<Self> {
812        let handle = unsafe {
813            sys::MaaGamepadControllerCreate(hwnd, gamepad_type as u64, screencap_method.bits())
814        };
815        Self::from_handle(handle)
816    }
817
818    // === EventSink ===
819
820    /// Returns sink_id for later removal. Callback lifetime managed by caller.
821    pub fn add_sink<F>(&self, callback: F) -> MaaResult<sys::MaaSinkId>
822    where
823        F: Fn(&str, &str) + Send + Sync + 'static,
824    {
825        let (cb_fn, cb_arg) = crate::callback::EventCallback::new(callback);
826        let sink_id =
827            unsafe { sys::MaaControllerAddSink(self.inner.handle.as_ptr(), cb_fn, cb_arg) };
828        if sink_id != 0 {
829            self.inner
830                .callbacks
831                .lock()
832                .unwrap()
833                .insert(sink_id, cb_arg as usize);
834            Ok(sink_id)
835        } else {
836            unsafe { crate::callback::EventCallback::drop_callback(cb_arg) };
837            Err(MaaError::FrameworkError(0))
838        }
839    }
840
841    /// Register a strongly-typed event sink.
842    ///
843    /// This method registers an implementation of the [`EventSink`](crate::event_sink::EventSink) trait
844    /// to receive structured notifications from this controller.
845    ///
846    /// # Arguments
847    /// * `sink` - The event sink implementation (must be boxed).
848    ///
849    /// # Returns
850    /// A `MaaSinkId` which can be used to manually remove the sink later via [`remove_sink`](Self::remove_sink).
851    /// The sink will be automatically unregistered and dropped when the `Controller` is dropped.
852    pub fn add_event_sink(
853        &self,
854        sink: Box<dyn crate::event_sink::EventSink>,
855    ) -> MaaResult<sys::MaaSinkId> {
856        let handle_id = self.inner.handle.as_ptr() as crate::common::MaaId;
857        let (cb, arg) = crate::callback::EventCallback::new_sink(handle_id, sink);
858        let id = unsafe { sys::MaaControllerAddSink(self.inner.handle.as_ptr(), cb, arg) };
859        if id > 0 {
860            self.inner
861                .event_sinks
862                .lock()
863                .unwrap()
864                .insert(id, arg as usize);
865            Ok(id)
866        } else {
867            unsafe { crate::callback::EventCallback::drop_sink(arg) };
868            Err(MaaError::FrameworkError(0))
869        }
870    }
871
872    pub fn remove_sink(&self, sink_id: sys::MaaSinkId) {
873        unsafe { sys::MaaControllerRemoveSink(self.inner.handle.as_ptr(), sink_id) };
874        if let Some(ptr) = self.inner.callbacks.lock().unwrap().remove(&sink_id) {
875            unsafe { crate::callback::EventCallback::drop_callback(ptr as *mut c_void) };
876        } else if let Some(ptr) = self.inner.event_sinks.lock().unwrap().remove(&sink_id) {
877            unsafe { crate::callback::EventCallback::drop_sink(ptr as *mut c_void) };
878        }
879    }
880
881    pub fn clear_sinks(&self) {
882        unsafe { sys::MaaControllerClearSinks(self.inner.handle.as_ptr()) };
883        let mut callbacks = self.inner.callbacks.lock().unwrap();
884        for (_, ptr) in callbacks.drain() {
885            unsafe { crate::callback::EventCallback::drop_callback(ptr as *mut c_void) };
886        }
887        let mut event_sinks = self.inner.event_sinks.lock().unwrap();
888        for (_, ptr) in event_sinks.drain() {
889            unsafe { crate::callback::EventCallback::drop_sink(ptr as *mut c_void) };
890        }
891    }
892}
893
894impl Drop for ControllerInner {
895    fn drop(&mut self) {
896        unsafe {
897            if self.owns_handle {
898                sys::MaaControllerClearSinks(self.handle.as_ptr());
899                let mut callbacks = self.callbacks.lock().unwrap();
900                for (_, ptr) in callbacks.drain() {
901                    crate::callback::EventCallback::drop_callback(ptr as *mut c_void);
902                }
903                let mut event_sinks = self.event_sinks.lock().unwrap();
904                for (_, ptr) in event_sinks.drain() {
905                    crate::callback::EventCallback::drop_sink(ptr as *mut c_void);
906                }
907                sys::MaaControllerDestroy(self.handle.as_ptr());
908            }
909        }
910    }
911}
912
913/// Builder for ADB controller configuration.
914///
915/// Provides a fluent API for configuring ADB controllers with sensible defaults.
916#[cfg(feature = "adb")]
917pub struct AdbControllerBuilder {
918    adb_path: String,
919    address: String,
920    screencap_methods: sys::MaaAdbScreencapMethod,
921    input_methods: sys::MaaAdbInputMethod,
922    config: String,
923    agent_path: String,
924}
925
926#[cfg(feature = "adb")]
927impl AdbControllerBuilder {
928    /// Create a new builder with required ADB path and device address.
929    pub fn new(adb_path: &str, address: &str) -> Self {
930        Self {
931            adb_path: adb_path.to_string(),
932            address: address.to_string(),
933            screencap_methods: sys::MaaAdbScreencapMethod_Default as sys::MaaAdbScreencapMethod,
934            input_methods: sys::MaaAdbInputMethod_Default as sys::MaaAdbInputMethod,
935            config: "{}".to_string(),
936            agent_path: String::new(),
937        }
938    }
939
940    /// Set the screencap methods to use.
941    pub fn screencap_methods(mut self, methods: sys::MaaAdbScreencapMethod) -> Self {
942        self.screencap_methods = methods;
943        self
944    }
945
946    /// Set the input methods to use.
947    pub fn input_methods(mut self, methods: sys::MaaAdbInputMethod) -> Self {
948        self.input_methods = methods;
949        self
950    }
951
952    /// Set additional configuration as JSON.
953    pub fn config(mut self, config: &str) -> Self {
954        self.config = config.to_string();
955        self
956    }
957
958    /// Set the path to MaaAgentBinary.
959    pub fn agent_path(mut self, path: &str) -> Self {
960        self.agent_path = path.to_string();
961        self
962    }
963
964    /// Build the controller with the configured options.
965    pub fn build(self) -> MaaResult<Controller> {
966        Controller::create_adb(
967            &self.adb_path,
968            &self.address,
969            self.screencap_methods,
970            self.input_methods,
971            &self.config,
972            &self.agent_path,
973        )
974    }
975}
976
977/// A borrowed reference to a Controller.
978///
979/// This is a non-owning view that can be used for read-only operations.
980/// It does NOT call destroy when dropped and should only be used while
981/// the underlying Controller is still alive.
982pub struct ControllerRef<'a> {
983    handle: *mut sys::MaaController,
984    _marker: std::marker::PhantomData<&'a ()>,
985}
986
987impl<'a> std::fmt::Debug for ControllerRef<'a> {
988    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
989        f.debug_struct("ControllerRef")
990            .field("handle", &self.handle)
991            .finish()
992    }
993}
994
995impl<'a> ControllerRef<'a> {
996    pub(crate) fn from_ptr(handle: *mut sys::MaaController) -> Option<Self> {
997        if handle.is_null() {
998            None
999        } else {
1000            Some(Self {
1001                handle,
1002                _marker: std::marker::PhantomData,
1003            })
1004        }
1005    }
1006
1007    /// Check if connected.
1008    pub fn connected(&self) -> bool {
1009        unsafe { sys::MaaControllerConnected(self.handle) != 0 }
1010    }
1011
1012    /// Get device UUID.
1013    pub fn uuid(&self) -> MaaResult<String> {
1014        let buffer = crate::buffer::MaaStringBuffer::new()?;
1015        let ret = unsafe { sys::MaaControllerGetUuid(self.handle, buffer.as_ptr()) };
1016        if ret != 0 {
1017            Ok(buffer.to_string())
1018        } else {
1019            Err(MaaError::FrameworkError(0))
1020        }
1021    }
1022
1023    /// Get controller information as a JSON value.
1024    pub fn info(&self) -> MaaResult<serde_json::Value> {
1025        let buffer = crate::buffer::MaaStringBuffer::new()?;
1026        let ret = unsafe { sys::MaaControllerGetInfo(self.handle, buffer.as_ptr()) };
1027        if ret != 0 {
1028            serde_json::from_str(&buffer.to_string()).map_err(|e| {
1029                MaaError::InvalidArgument(format!("Failed to parse controller info: {}", e))
1030            })
1031        } else {
1032            Err(MaaError::FrameworkError(0))
1033        }
1034    }
1035
1036    /// Get device resolution.
1037    pub fn resolution(&self) -> MaaResult<(i32, i32)> {
1038        let mut width: i32 = 0;
1039        let mut height: i32 = 0;
1040        let ret = unsafe { sys::MaaControllerGetResolution(self.handle, &mut width, &mut height) };
1041        if ret != 0 {
1042            Ok((width, height))
1043        } else {
1044            Err(MaaError::FrameworkError(0))
1045        }
1046    }
1047
1048    /// Get operation status.
1049    pub fn status(&self, ctrl_id: common::MaaId) -> common::MaaStatus {
1050        let s = unsafe { sys::MaaControllerStatus(self.handle, ctrl_id) };
1051        common::MaaStatus(s)
1052    }
1053
1054    /// Wait for operation to complete.
1055    pub fn wait(&self, ctrl_id: common::MaaId) -> common::MaaStatus {
1056        let s = unsafe { sys::MaaControllerWait(self.handle, ctrl_id) };
1057        common::MaaStatus(s)
1058    }
1059
1060    /// Get cached screenshot.
1061    pub fn cached_image(&self) -> MaaResult<crate::buffer::MaaImageBuffer> {
1062        let buffer = crate::buffer::MaaImageBuffer::new()?;
1063        let ret = unsafe { sys::MaaControllerCachedImage(self.handle, buffer.as_ptr()) };
1064        if ret != 0 {
1065            Ok(buffer)
1066        } else {
1067            Err(MaaError::FrameworkError(0))
1068        }
1069    }
1070
1071    /// Get raw handle.
1072    pub fn raw(&self) -> *mut sys::MaaController {
1073        self.handle
1074    }
1075}