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