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