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