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