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