Skip to main content

maa_framework/
controller.rs

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