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 std::collections::HashMap;
5use std::ffi::CString;
6use std::os::raw::c_void;
7use std::ptr::NonNull;
8use std::sync::{Arc, Mutex};
9
10/// Device controller interface.
11///
12/// Handles interaction with the target device, including:
13/// - Input events (click, swipe, key press)
14/// - Screen capture
15/// - App management (start/stop)
16/// - Connection management
17///
18/// See also: [`AdbControllerBuilder`] for advanced ADB configuration.
19#[derive(Clone)]
20pub struct Controller {
21    inner: Arc<ControllerInner>,
22}
23
24struct ControllerInner {
25    handle: NonNull<sys::MaaController>,
26    owns_handle: bool,
27    callbacks: Mutex<HashMap<sys::MaaSinkId, usize>>,
28    event_sinks: Mutex<HashMap<sys::MaaSinkId, usize>>,
29}
30
31unsafe impl Send for ControllerInner {}
32unsafe impl Sync for ControllerInner {}
33
34// Controller is Send/Sync because it holds Arc<ControllerInner> which is Send/Sync
35unsafe impl Send for Controller {}
36unsafe impl Sync for Controller {}
37
38impl std::fmt::Debug for Controller {
39    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
40        f.debug_struct("Controller")
41            .field("handle", &self.inner.handle)
42            .finish()
43    }
44}
45
46impl Controller {
47    /// Create a new ADB controller for Android device control.
48    ///
49    /// # Arguments
50    /// * `adb_path` - Path to the ADB executable
51    /// * `address` - Device address (e.g., "127.0.0.1:5555" or "emulator-5554")
52    /// * `config` - JSON configuration string for advanced options
53    /// * `agent_path` - Path to MaaAgent binary; pass `""` to use current directory (may return `Err` if resolution fails).
54    #[cfg(feature = "adb")]
55    pub fn new_adb(
56        adb_path: &str,
57        address: &str,
58        config: &str,
59        agent_path: &str,
60    ) -> MaaResult<Self> {
61        Self::create_adb(
62            adb_path,
63            address,
64            sys::MaaAdbScreencapMethod_Default as sys::MaaAdbScreencapMethod,
65            sys::MaaAdbInputMethod_Default as sys::MaaAdbInputMethod,
66            config,
67            agent_path,
68        )
69    }
70
71    /// Resolves to the current directory if the string is empty; otherwise, uses it as-is. Returns Err if parsing fails.
72    #[cfg(feature = "adb")]
73    fn resolve_agent_path(agent_path: &str) -> MaaResult<String> {
74        if !agent_path.is_empty() {
75            return Ok(agent_path.to_string());
76        }
77        let cur = std::env::current_dir().map_err(|e| {
78            MaaError::InvalidArgument(format!("agent_path empty and current_dir failed: {}", e))
79        })?;
80        let s = cur.to_str().ok_or_else(|| {
81            MaaError::InvalidArgument(
82                "agent_path empty and current directory is not valid UTF-8".to_string(),
83            )
84        })?;
85        Ok(s.to_string())
86    }
87
88    #[cfg(feature = "adb")]
89    pub(crate) fn create_adb(
90        adb_path: &str,
91        address: &str,
92        screencap_method: sys::MaaAdbScreencapMethod,
93        input_method: sys::MaaAdbInputMethod,
94        config: &str,
95        agent_path: &str,
96    ) -> MaaResult<Self> {
97        let path = Self::resolve_agent_path(agent_path)?;
98        let c_adb = CString::new(adb_path)?;
99        let c_addr = CString::new(address)?;
100        let c_cfg = CString::new(config)?;
101        let c_agent = CString::new(path.as_str())?;
102
103        let handle = unsafe {
104            sys::MaaAdbControllerCreate(
105                c_adb.as_ptr(),
106                c_addr.as_ptr(),
107                screencap_method,
108                input_method,
109                c_cfg.as_ptr(),
110                c_agent.as_ptr(),
111            )
112        };
113
114        if let Some(ptr) = NonNull::new(handle) {
115            Ok(Self {
116                inner: Arc::new(ControllerInner {
117                    handle: ptr,
118                    owns_handle: true,
119                    callbacks: Mutex::new(HashMap::new()),
120                    event_sinks: Mutex::new(HashMap::new()),
121                }),
122            })
123        } else {
124            Err(MaaError::FrameworkError(-1))
125        }
126    }
127
128    /// Create a new Win32 controller for Windows window control.
129    #[cfg(feature = "win32")]
130    pub fn new_win32(
131        hwnd: *mut c_void,
132        screencap_method: sys::MaaWin32ScreencapMethod,
133        mouse_method: sys::MaaWin32InputMethod,
134        keyboard_method: sys::MaaWin32InputMethod,
135    ) -> MaaResult<Self> {
136        let handle = unsafe {
137            sys::MaaWin32ControllerCreate(hwnd, screencap_method, mouse_method, keyboard_method)
138        };
139
140        Self::from_handle(handle)
141    }
142
143    /// Create a new PlayCover controller for iOS app control on macOS.
144
145    pub fn new_playcover(address: &str, uuid: &str) -> MaaResult<Self> {
146        let c_addr = CString::new(address)?;
147        let c_uuid = CString::new(uuid)?;
148        let handle = unsafe { sys::MaaPlayCoverControllerCreate(c_addr.as_ptr(), c_uuid.as_ptr()) };
149
150        Self::from_handle(handle)
151    }
152
153    /// Create a new WlRoots controller for apps running in wlroots compositor on Linux.
154    ///
155    /// # Arguments
156    /// * `wlr_socket_path` - Wayland socket path
157    pub fn new_wlroots(wlr_socket_path: &str) -> MaaResult<Self> {
158        let c_path = CString::new(wlr_socket_path)?;
159        let handle = unsafe { sys::MaaWlRootsControllerCreate(c_path.as_ptr()) };
160
161        Self::from_handle(handle)
162    }
163
164    /// Create a custom controller with user-defined callbacks.
165    #[cfg(feature = "custom")]
166    pub fn new_custom<T: crate::custom_controller::CustomControllerCallback + 'static>(
167        callback: T,
168    ) -> MaaResult<Self> {
169        let boxed: Box<Box<dyn crate::custom_controller::CustomControllerCallback>> =
170            Box::new(Box::new(callback));
171        let cb_ptr = Box::into_raw(boxed) as *mut c_void;
172        let callbacks = crate::custom_controller::get_callbacks();
173        let handle =
174            unsafe { sys::MaaCustomControllerCreate(callbacks as *const _ as *mut _, cb_ptr) };
175
176        NonNull::new(handle)
177            .map(|ptr| Self {
178                inner: Arc::new(ControllerInner {
179                    handle: ptr,
180                    owns_handle: true,
181                    callbacks: Mutex::new(HashMap::new()),
182                    event_sinks: Mutex::new(HashMap::new()),
183                }),
184            })
185            .ok_or_else(|| {
186                unsafe {
187                    let _ = Box::from_raw(
188                        cb_ptr as *mut Box<dyn crate::custom_controller::CustomControllerCallback>,
189                    );
190                }
191                MaaError::FrameworkError(-1)
192            })
193    }
194
195    /// Helper to create controller from raw handle.
196    fn from_handle(handle: *mut sys::MaaController) -> MaaResult<Self> {
197        if let Some(ptr) = NonNull::new(handle) {
198            Ok(Self {
199                inner: Arc::new(ControllerInner {
200                    handle: ptr,
201                    owns_handle: true,
202                    callbacks: Mutex::new(HashMap::new()),
203                    event_sinks: Mutex::new(HashMap::new()),
204                }),
205            })
206        } else {
207            Err(MaaError::FrameworkError(-1))
208        }
209    }
210
211    /// Post a click action at the specified coordinates.
212    pub fn post_click(&self, x: i32, y: i32) -> MaaResult<common::MaaId> {
213        let id = unsafe { sys::MaaControllerPostClick(self.inner.handle.as_ptr(), x, y) };
214        Ok(id)
215    }
216
217    /// Post a screenshot capture request.
218    pub fn post_screencap(&self) -> MaaResult<common::MaaId> {
219        let id = unsafe { sys::MaaControllerPostScreencap(self.inner.handle.as_ptr()) };
220        Ok(id)
221    }
222
223    /// Post a click action with contact and pressure parameters.
224    ///
225    /// # Arguments
226    /// * `x`, `y` - Click coordinates
227    /// * `contact` - Contact/finger index (for multi-touch)
228    /// * `pressure` - Touch pressure (1 = normal)
229    pub fn post_click_v2(
230        &self,
231        x: i32,
232        y: i32,
233        contact: i32,
234        pressure: i32,
235    ) -> MaaResult<common::MaaId> {
236        let id = unsafe {
237            sys::MaaControllerPostClickV2(self.inner.handle.as_ptr(), x, y, contact, pressure)
238        };
239        Ok(id)
240    }
241
242    /// Post a swipe action from one point to another.
243    ///
244    /// # Arguments
245    /// * `x1`, `y1` - Start coordinates
246    /// * `x2`, `y2` - End coordinates
247    /// * `duration` - Swipe duration in milliseconds
248    pub fn post_swipe(
249        &self,
250        x1: i32,
251        y1: i32,
252        x2: i32,
253        y2: i32,
254        duration: i32,
255    ) -> MaaResult<common::MaaId> {
256        let id = unsafe {
257            sys::MaaControllerPostSwipe(self.inner.handle.as_ptr(), x1, y1, x2, y2, duration)
258        };
259        Ok(id)
260    }
261
262    /// Post a key click action.
263    ///
264    /// # Arguments
265    /// * `keycode` - Virtual key code (ADB keycode for Android, VK for Win32)
266    pub fn post_click_key(&self, keycode: i32) -> MaaResult<common::MaaId> {
267        let id = unsafe { sys::MaaControllerPostClickKey(self.inner.handle.as_ptr(), keycode) };
268        Ok(id)
269    }
270
271    /// Alias for [`post_click_key`](Self::post_click_key).
272    #[deprecated(note = "Use post_click_key instead")]
273    pub fn post_press(&self, keycode: i32) -> MaaResult<common::MaaId> {
274        self.post_click_key(keycode)
275    }
276
277    /// Post a text input action.
278    ///
279    /// # Arguments
280    /// * `text` - Text to input
281    pub fn post_input_text(&self, text: &str) -> MaaResult<common::MaaId> {
282        let c_text = CString::new(text)?;
283        let id =
284            unsafe { sys::MaaControllerPostInputText(self.inner.handle.as_ptr(), c_text.as_ptr()) };
285        Ok(id)
286    }
287
288    /// Post a shell command execution (ADB only).
289    ///
290    /// # Arguments
291    /// * `cmd` - Shell command to execute
292    /// * `timeout` - Timeout in milliseconds
293    pub fn post_shell(&self, cmd: &str, timeout: i64) -> MaaResult<common::MaaId> {
294        let c_cmd = CString::new(cmd)?;
295        let id = unsafe {
296            sys::MaaControllerPostShell(self.inner.handle.as_ptr(), c_cmd.as_ptr(), timeout)
297        };
298        Ok(id)
299    }
300
301    /// Post a touch down event.
302    ///
303    /// # Arguments
304    /// * `contact` - Contact/finger index
305    /// * `x`, `y` - Touch coordinates
306    /// * `pressure` - Touch pressure
307    pub fn post_touch_down(
308        &self,
309        contact: i32,
310        x: i32,
311        y: i32,
312        pressure: i32,
313    ) -> MaaResult<common::MaaId> {
314        let id = unsafe {
315            sys::MaaControllerPostTouchDown(self.inner.handle.as_ptr(), contact, x, y, pressure)
316        };
317        Ok(id)
318    }
319
320    /// Post a touch move event.
321    ///
322    /// # Arguments
323    /// * `contact` - Contact/finger index
324    /// * `x`, `y` - New touch coordinates
325    /// * `pressure` - Touch pressure
326    pub fn post_touch_move(
327        &self,
328        contact: i32,
329        x: i32,
330        y: i32,
331        pressure: i32,
332    ) -> MaaResult<common::MaaId> {
333        let id = unsafe {
334            sys::MaaControllerPostTouchMove(self.inner.handle.as_ptr(), contact, x, y, pressure)
335        };
336        Ok(id)
337    }
338
339    /// Post a touch up event.
340    ///
341    /// # Arguments
342    /// * `contact` - Contact/finger index to release
343    pub fn post_touch_up(&self, contact: i32) -> MaaResult<common::MaaId> {
344        let id = unsafe { sys::MaaControllerPostTouchUp(self.inner.handle.as_ptr(), contact) };
345        Ok(id)
346    }
347
348    /// Post a relative mouse movement action (currently Win32 only).
349    ///
350    /// # Arguments
351    /// * `dx` - Relative horizontal movement offset
352    /// * `dy` - Relative vertical movement offset
353    pub fn post_relative_move(&self, dx: i32, dy: i32) -> MaaResult<common::MaaId> {
354        let id =
355            unsafe { sys::MaaControllerPostRelativeMove(self.inner.handle.as_ptr(), dx, dy) };
356        Ok(id)
357    }
358
359    /// Returns the underlying raw controller handle.
360    #[inline]
361    pub fn raw(&self) -> *mut sys::MaaController {
362        self.inner.handle.as_ptr()
363    }
364
365    // === Connection ===
366
367    /// Post a connection request to the device.
368    ///
369    /// Returns a job ID that can be used with [`wait`](Self::wait) to block until connected.
370    pub fn post_connection(&self) -> MaaResult<common::MaaId> {
371        let id = unsafe { sys::MaaControllerPostConnection(self.inner.handle.as_ptr()) };
372        Ok(id)
373    }
374
375    /// Returns `true` if the controller is connected to the device.
376    pub fn connected(&self) -> bool {
377        unsafe { sys::MaaControllerConnected(self.inner.handle.as_ptr()) != 0 }
378    }
379
380    /// Gets the unique identifier (UUID) of the connected device.
381    pub fn uuid(&self) -> MaaResult<String> {
382        let buffer = crate::buffer::MaaStringBuffer::new()?;
383        let ret = unsafe { sys::MaaControllerGetUuid(self.inner.handle.as_ptr(), buffer.as_ptr()) };
384        if ret != 0 {
385            Ok(buffer.to_string())
386        } else {
387            Err(MaaError::FrameworkError(0))
388        }
389    }
390
391    /// Gets the controller information as a JSON value.
392    ///
393    /// Returns controller-specific information including type, constructor parameters
394    /// and current state. The returned JSON always contains a "type" field.
395    pub fn info(&self) -> MaaResult<serde_json::Value> {
396        let buffer = crate::buffer::MaaStringBuffer::new()?;
397        let ret = unsafe { sys::MaaControllerGetInfo(self.inner.handle.as_ptr(), buffer.as_ptr()) };
398        if ret != 0 {
399            serde_json::from_str(&buffer.to_string()).map_err(|e| {
400                MaaError::InvalidArgument(format!("Failed to parse controller info: {}", e))
401            })
402        } else {
403            Err(MaaError::FrameworkError(0))
404        }
405    }
406
407    /// Gets the device screen resolution as (width, height).
408    pub fn resolution(&self) -> MaaResult<(i32, i32)> {
409        let mut width: i32 = 0;
410        let mut height: i32 = 0;
411        let ret = unsafe {
412            sys::MaaControllerGetResolution(self.inner.handle.as_ptr(), &mut width, &mut height)
413        };
414        if ret != 0 {
415            Ok((width, height))
416        } else {
417            Err(MaaError::FrameworkError(0))
418        }
419    }
420
421    // === Swipe V2 ===
422
423    /// Post a swipe action with contact and pressure parameters.
424    ///
425    /// # Arguments
426    /// * `x1`, `y1` - Start coordinates
427    /// * `x2`, `y2` - End coordinates
428    /// * `duration` - Swipe duration in milliseconds
429    /// * `contact` - Contact/finger index
430    /// * `pressure` - Touch pressure
431    pub fn post_swipe_v2(
432        &self,
433        x1: i32,
434        y1: i32,
435        x2: i32,
436        y2: i32,
437        duration: i32,
438        contact: i32,
439        pressure: i32,
440    ) -> MaaResult<common::MaaId> {
441        let id = unsafe {
442            sys::MaaControllerPostSwipeV2(
443                self.inner.handle.as_ptr(),
444                x1,
445                y1,
446                x2,
447                y2,
448                duration,
449                contact,
450                pressure,
451            )
452        };
453        Ok(id)
454    }
455
456    // === Key control ===
457
458    /// Post a key down event.
459    pub fn post_key_down(&self, keycode: i32) -> MaaResult<common::MaaId> {
460        let id = unsafe { sys::MaaControllerPostKeyDown(self.inner.handle.as_ptr(), keycode) };
461        Ok(id)
462    }
463
464    /// Post a key up event.
465    pub fn post_key_up(&self, keycode: i32) -> MaaResult<common::MaaId> {
466        let id = unsafe { sys::MaaControllerPostKeyUp(self.inner.handle.as_ptr(), keycode) };
467        Ok(id)
468    }
469
470    // === App control ===
471
472    /// Start an application.
473    ///
474    /// # Arguments
475    /// * `intent` - Package name or activity (ADB), app identifier (Win32)
476    pub fn post_start_app(&self, intent: &str) -> MaaResult<common::MaaId> {
477        let c_intent = CString::new(intent)?;
478        let id = unsafe {
479            sys::MaaControllerPostStartApp(self.inner.handle.as_ptr(), c_intent.as_ptr())
480        };
481        Ok(id)
482    }
483
484    /// Stop an application.
485    ///
486    /// # Arguments
487    /// * `intent` - Package name (ADB)
488    pub fn post_stop_app(&self, intent: &str) -> MaaResult<common::MaaId> {
489        let c_intent = CString::new(intent)?;
490        let id =
491            unsafe { sys::MaaControllerPostStopApp(self.inner.handle.as_ptr(), c_intent.as_ptr()) };
492        Ok(id)
493    }
494
495    // === Scroll ===
496
497    /// Post a scroll action (Win32 only).
498    ///
499    /// # Arguments
500    /// * `dx` - Horizontal scroll delta (positive = right)
501    /// * `dy` - Vertical scroll delta (positive = down)
502    pub fn post_scroll(&self, dx: i32, dy: i32) -> MaaResult<common::MaaId> {
503        let id = unsafe { sys::MaaControllerPostScroll(self.inner.handle.as_ptr(), dx, dy) };
504        Ok(id)
505    }
506
507    // === Inactive ===
508
509    /// Post an inactive request to the controller.
510    ///
511    /// For Win32 controllers, this restores window position (removes topmost) and unblocks user input.
512    /// For other controllers, this is a no-op that always succeeds.
513    pub fn post_inactive(&self) -> MaaResult<common::MaaId> {
514        let id = unsafe { sys::MaaControllerPostInactive(self.inner.handle.as_ptr()) };
515        Ok(id)
516    }
517
518    // === Image ===
519
520    /// Gets the most recently captured screenshot.
521    pub fn cached_image(&self) -> MaaResult<crate::buffer::MaaImageBuffer> {
522        let buffer = crate::buffer::MaaImageBuffer::new()?;
523        let ret =
524            unsafe { sys::MaaControllerCachedImage(self.inner.handle.as_ptr(), buffer.as_ptr()) };
525        if ret != 0 {
526            Ok(buffer)
527        } else {
528            Err(MaaError::FrameworkError(0))
529        }
530    }
531
532    // === Shell output ===
533
534    /// Gets the output from the most recent shell command (ADB only).
535    pub fn shell_output(&self) -> MaaResult<String> {
536        let buffer = crate::buffer::MaaStringBuffer::new()?;
537        let ret = unsafe {
538            sys::MaaControllerGetShellOutput(self.inner.handle.as_ptr(), buffer.as_ptr())
539        };
540        if ret != 0 {
541            Ok(buffer.to_string())
542        } else {
543            Err(MaaError::FrameworkError(0))
544        }
545    }
546
547    // === Status ===
548
549    /// Gets the status of a controller operation.
550    pub fn status(&self, ctrl_id: common::MaaId) -> common::MaaStatus {
551        let s = unsafe { sys::MaaControllerStatus(self.inner.handle.as_ptr(), ctrl_id) };
552        common::MaaStatus(s)
553    }
554
555    /// Blocks until a controller operation completes.
556    pub fn wait(&self, ctrl_id: common::MaaId) -> common::MaaStatus {
557        let s = unsafe { sys::MaaControllerWait(self.inner.handle.as_ptr(), ctrl_id) };
558        common::MaaStatus(s)
559    }
560
561    // === Screenshot options ===
562
563    /// Sets the target long side for screenshot scaling.
564    pub fn set_screenshot_target_long_side(&self, long_side: i32) -> MaaResult<()> {
565        let mut val = long_side;
566        let ret = unsafe {
567            sys::MaaControllerSetOption(
568                self.inner.handle.as_ptr(),
569                sys::MaaCtrlOptionEnum_MaaCtrlOption_ScreenshotTargetLongSide as i32,
570                &mut val as *mut _ as *mut c_void,
571                std::mem::size_of::<i32>() as u64,
572            )
573        };
574        common::check_bool(ret)
575    }
576
577    /// Sets the target short side for screenshot scaling.
578    pub fn set_screenshot_target_short_side(&self, short_side: i32) -> MaaResult<()> {
579        let mut val = short_side;
580        let ret = unsafe {
581            sys::MaaControllerSetOption(
582                self.inner.handle.as_ptr(),
583                sys::MaaCtrlOptionEnum_MaaCtrlOption_ScreenshotTargetShortSide as i32,
584                &mut val as *mut _ as *mut c_void,
585                std::mem::size_of::<i32>() as u64,
586            )
587        };
588        common::check_bool(ret)
589    }
590
591    /// Sets whether to use raw (unscaled) screenshot resolution.
592    pub fn set_screenshot_use_raw_size(&self, enable: bool) -> MaaResult<()> {
593        let mut val: u8 = if enable { 1 } else { 0 };
594        let ret = unsafe {
595            sys::MaaControllerSetOption(
596                self.inner.handle.as_ptr(),
597                sys::MaaCtrlOptionEnum_MaaCtrlOption_ScreenshotUseRawSize as i32,
598                &mut val as *mut _ as *mut c_void,
599                std::mem::size_of::<u8>() as u64,
600            )
601        };
602        common::check_bool(ret)
603    }
604
605    // === New controller types ===
606
607    #[cfg(feature = "dbg")]
608    pub fn new_dbg(
609        read_path: &str,
610        write_path: &str,
611        dbg_type: sys::MaaDbgControllerType,
612        config: &str,
613    ) -> MaaResult<Self> {
614        let c_read = CString::new(read_path)?;
615        let c_write = CString::new(write_path)?;
616        let c_cfg = CString::new(config)?;
617        let handle = unsafe {
618            sys::MaaDbgControllerCreate(c_read.as_ptr(), c_write.as_ptr(), dbg_type, c_cfg.as_ptr())
619        };
620        Self::from_handle(handle)
621    }
622
623    /// Create a virtual gamepad controller (Windows only).
624    #[cfg(feature = "win32")]
625    pub fn new_gamepad(
626        hwnd: *mut c_void,
627        gamepad_type: crate::common::GamepadType,
628        screencap_method: crate::common::Win32ScreencapMethod,
629    ) -> MaaResult<Self> {
630        let handle = unsafe {
631            sys::MaaGamepadControllerCreate(hwnd, gamepad_type as u64, screencap_method.bits())
632        };
633        Self::from_handle(handle)
634    }
635
636    // === EventSink ===
637
638    /// Returns sink_id for later removal. Callback lifetime managed by caller.
639    pub fn add_sink<F>(&self, callback: F) -> MaaResult<sys::MaaSinkId>
640    where
641        F: Fn(&str, &str) + Send + Sync + 'static,
642    {
643        let (cb_fn, cb_arg) = crate::callback::EventCallback::new(callback);
644        let sink_id =
645            unsafe { sys::MaaControllerAddSink(self.inner.handle.as_ptr(), cb_fn, cb_arg) };
646        if sink_id != 0 {
647            self.inner
648                .callbacks
649                .lock()
650                .unwrap()
651                .insert(sink_id, cb_arg as usize);
652            Ok(sink_id)
653        } else {
654            unsafe { crate::callback::EventCallback::drop_callback(cb_arg) };
655            Err(MaaError::FrameworkError(0))
656        }
657    }
658
659    /// Register a strongly-typed event sink.
660    ///
661    /// This method registers an implementation of the [`EventSink`](crate::event_sink::EventSink) trait
662    /// to receive structured notifications from this controller.
663    ///
664    /// # Arguments
665    /// * `sink` - The event sink implementation (must be boxed).
666    ///
667    /// # Returns
668    /// A `MaaSinkId` which can be used to manually remove the sink later via [`remove_sink`](Self::remove_sink).
669    /// The sink will be automatically unregistered and dropped when the `Controller` is dropped.
670    pub fn add_event_sink(
671        &self,
672        sink: Box<dyn crate::event_sink::EventSink>,
673    ) -> MaaResult<sys::MaaSinkId> {
674        let handle_id = self.inner.handle.as_ptr() as crate::common::MaaId;
675        let (cb, arg) = crate::callback::EventCallback::new_sink(handle_id, sink);
676        let id = unsafe { sys::MaaControllerAddSink(self.inner.handle.as_ptr(), cb, arg) };
677        if id > 0 {
678            self.inner
679                .event_sinks
680                .lock()
681                .unwrap()
682                .insert(id, arg as usize);
683            Ok(id)
684        } else {
685            unsafe { crate::callback::EventCallback::drop_sink(arg) };
686            Err(MaaError::FrameworkError(0))
687        }
688    }
689
690    pub fn remove_sink(&self, sink_id: sys::MaaSinkId) {
691        unsafe { sys::MaaControllerRemoveSink(self.inner.handle.as_ptr(), sink_id) };
692        if let Some(ptr) = self.inner.callbacks.lock().unwrap().remove(&sink_id) {
693            unsafe { crate::callback::EventCallback::drop_callback(ptr as *mut c_void) };
694        } else if let Some(ptr) = self.inner.event_sinks.lock().unwrap().remove(&sink_id) {
695            unsafe { crate::callback::EventCallback::drop_sink(ptr as *mut c_void) };
696        }
697    }
698
699    pub fn clear_sinks(&self) {
700        unsafe { sys::MaaControllerClearSinks(self.inner.handle.as_ptr()) };
701        let mut callbacks = self.inner.callbacks.lock().unwrap();
702        for (_, ptr) in callbacks.drain() {
703            unsafe { crate::callback::EventCallback::drop_callback(ptr as *mut c_void) };
704        }
705        let mut event_sinks = self.inner.event_sinks.lock().unwrap();
706        for (_, ptr) in event_sinks.drain() {
707            unsafe { crate::callback::EventCallback::drop_sink(ptr as *mut c_void) };
708        }
709    }
710}
711
712impl Drop for ControllerInner {
713    fn drop(&mut self) {
714        unsafe {
715            if self.owns_handle {
716                sys::MaaControllerClearSinks(self.handle.as_ptr());
717                let mut callbacks = self.callbacks.lock().unwrap();
718                for (_, ptr) in callbacks.drain() {
719                    crate::callback::EventCallback::drop_callback(ptr as *mut c_void);
720                }
721                let mut event_sinks = self.event_sinks.lock().unwrap();
722                for (_, ptr) in event_sinks.drain() {
723                    crate::callback::EventCallback::drop_sink(ptr as *mut c_void);
724                }
725                sys::MaaControllerDestroy(self.handle.as_ptr());
726            }
727        }
728    }
729}
730
731/// Builder for ADB controller configuration.
732///
733/// Provides a fluent API for configuring ADB controllers with sensible defaults.
734#[cfg(feature = "adb")]
735pub struct AdbControllerBuilder {
736    adb_path: String,
737    address: String,
738    screencap_methods: sys::MaaAdbScreencapMethod,
739    input_methods: sys::MaaAdbInputMethod,
740    config: String,
741    agent_path: String,
742}
743
744#[cfg(feature = "adb")]
745impl AdbControllerBuilder {
746    /// Create a new builder with required ADB path and device address.
747    pub fn new(adb_path: &str, address: &str) -> Self {
748        Self {
749            adb_path: adb_path.to_string(),
750            address: address.to_string(),
751            screencap_methods: sys::MaaAdbScreencapMethod_Default as sys::MaaAdbScreencapMethod,
752            input_methods: sys::MaaAdbInputMethod_Default as sys::MaaAdbInputMethod,
753            config: "{}".to_string(),
754            agent_path: String::new(),
755        }
756    }
757
758    /// Set the screencap methods to use.
759    pub fn screencap_methods(mut self, methods: sys::MaaAdbScreencapMethod) -> Self {
760        self.screencap_methods = methods;
761        self
762    }
763
764    /// Set the input methods to use.
765    pub fn input_methods(mut self, methods: sys::MaaAdbInputMethod) -> Self {
766        self.input_methods = methods;
767        self
768    }
769
770    /// Set additional configuration as JSON.
771    pub fn config(mut self, config: &str) -> Self {
772        self.config = config.to_string();
773        self
774    }
775
776    /// Set the path to MaaAgentBinary.
777    pub fn agent_path(mut self, path: &str) -> Self {
778        self.agent_path = path.to_string();
779        self
780    }
781
782    /// Build the controller with the configured options.
783    pub fn build(self) -> MaaResult<Controller> {
784        Controller::create_adb(
785            &self.adb_path,
786            &self.address,
787            self.screencap_methods,
788            self.input_methods,
789            &self.config,
790            &self.agent_path,
791        )
792    }
793}
794
795/// A borrowed reference to a Controller.
796///
797/// This is a non-owning view that can be used for read-only operations.
798/// It does NOT call destroy when dropped and should only be used while
799/// the underlying Controller is still alive.
800pub struct ControllerRef<'a> {
801    handle: *mut sys::MaaController,
802    _marker: std::marker::PhantomData<&'a ()>,
803}
804
805impl<'a> std::fmt::Debug for ControllerRef<'a> {
806    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
807        f.debug_struct("ControllerRef")
808            .field("handle", &self.handle)
809            .finish()
810    }
811}
812
813impl<'a> ControllerRef<'a> {
814    pub(crate) fn from_ptr(handle: *mut sys::MaaController) -> Option<Self> {
815        if handle.is_null() {
816            None
817        } else {
818            Some(Self {
819                handle,
820                _marker: std::marker::PhantomData,
821            })
822        }
823    }
824
825    /// Check if connected.
826    pub fn connected(&self) -> bool {
827        unsafe { sys::MaaControllerConnected(self.handle) != 0 }
828    }
829
830    /// Get device UUID.
831    pub fn uuid(&self) -> MaaResult<String> {
832        let buffer = crate::buffer::MaaStringBuffer::new()?;
833        let ret = unsafe { sys::MaaControllerGetUuid(self.handle, buffer.as_ptr()) };
834        if ret != 0 {
835            Ok(buffer.to_string())
836        } else {
837            Err(MaaError::FrameworkError(0))
838        }
839    }
840
841    /// Get controller information as a JSON value.
842    pub fn info(&self) -> MaaResult<serde_json::Value> {
843        let buffer = crate::buffer::MaaStringBuffer::new()?;
844        let ret = unsafe { sys::MaaControllerGetInfo(self.handle, buffer.as_ptr()) };
845        if ret != 0 {
846            serde_json::from_str(&buffer.to_string()).map_err(|e| {
847                MaaError::InvalidArgument(format!("Failed to parse controller info: {}", e))
848            })
849        } else {
850            Err(MaaError::FrameworkError(0))
851        }
852    }
853
854    /// Get device resolution.
855    pub fn resolution(&self) -> MaaResult<(i32, i32)> {
856        let mut width: i32 = 0;
857        let mut height: i32 = 0;
858        let ret = unsafe { sys::MaaControllerGetResolution(self.handle, &mut width, &mut height) };
859        if ret != 0 {
860            Ok((width, height))
861        } else {
862            Err(MaaError::FrameworkError(0))
863        }
864    }
865
866    /// Get operation status.
867    pub fn status(&self, ctrl_id: common::MaaId) -> common::MaaStatus {
868        let s = unsafe { sys::MaaControllerStatus(self.handle, ctrl_id) };
869        common::MaaStatus(s)
870    }
871
872    /// Wait for operation to complete.
873    pub fn wait(&self, ctrl_id: common::MaaId) -> common::MaaStatus {
874        let s = unsafe { sys::MaaControllerWait(self.handle, ctrl_id) };
875        common::MaaStatus(s)
876    }
877
878    /// Get cached screenshot.
879    pub fn cached_image(&self) -> MaaResult<crate::buffer::MaaImageBuffer> {
880        let buffer = crate::buffer::MaaImageBuffer::new()?;
881        let ret = unsafe { sys::MaaControllerCachedImage(self.handle, buffer.as_ptr()) };
882        if ret != 0 {
883            Ok(buffer)
884        } else {
885            Err(MaaError::FrameworkError(0))
886        }
887    }
888
889    /// Get raw handle.
890    pub fn raw(&self) -> *mut sys::MaaController {
891        self.handle
892    }
893}