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