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    // === Image ===
475
476    /// Gets the most recently captured screenshot.
477    pub fn cached_image(&self) -> MaaResult<crate::buffer::MaaImageBuffer> {
478        let buffer = crate::buffer::MaaImageBuffer::new()?;
479        let ret =
480            unsafe { sys::MaaControllerCachedImage(self.inner.handle.as_ptr(), buffer.as_ptr()) };
481        if ret != 0 {
482            Ok(buffer)
483        } else {
484            Err(MaaError::FrameworkError(0))
485        }
486    }
487
488    // === Shell output ===
489
490    /// Gets the output from the most recent shell command (ADB only).
491    pub fn shell_output(&self) -> MaaResult<String> {
492        let buffer = crate::buffer::MaaStringBuffer::new()?;
493        let ret = unsafe {
494            sys::MaaControllerGetShellOutput(self.inner.handle.as_ptr(), buffer.as_ptr())
495        };
496        if ret != 0 {
497            Ok(buffer.to_string())
498        } else {
499            Err(MaaError::FrameworkError(0))
500        }
501    }
502
503    // === Status ===
504
505    /// Gets the status of a controller operation.
506    pub fn status(&self, ctrl_id: common::MaaId) -> common::MaaStatus {
507        let s = unsafe { sys::MaaControllerStatus(self.inner.handle.as_ptr(), ctrl_id) };
508        common::MaaStatus(s)
509    }
510
511    /// Blocks until a controller operation completes.
512    pub fn wait(&self, ctrl_id: common::MaaId) -> common::MaaStatus {
513        let s = unsafe { sys::MaaControllerWait(self.inner.handle.as_ptr(), ctrl_id) };
514        common::MaaStatus(s)
515    }
516
517    // === Screenshot options ===
518
519    /// Sets the target long side for screenshot scaling.
520    pub fn set_screenshot_target_long_side(&self, long_side: i32) -> MaaResult<()> {
521        let mut val = long_side;
522        let ret = unsafe {
523            sys::MaaControllerSetOption(
524                self.inner.handle.as_ptr(),
525                sys::MaaCtrlOptionEnum_MaaCtrlOption_ScreenshotTargetLongSide as i32,
526                &mut val as *mut _ as *mut c_void,
527                std::mem::size_of::<i32>() as u64,
528            )
529        };
530        common::check_bool(ret)
531    }
532
533    /// Sets the target short side for screenshot scaling.
534    pub fn set_screenshot_target_short_side(&self, short_side: i32) -> MaaResult<()> {
535        let mut val = short_side;
536        let ret = unsafe {
537            sys::MaaControllerSetOption(
538                self.inner.handle.as_ptr(),
539                sys::MaaCtrlOptionEnum_MaaCtrlOption_ScreenshotTargetShortSide as i32,
540                &mut val as *mut _ as *mut c_void,
541                std::mem::size_of::<i32>() as u64,
542            )
543        };
544        common::check_bool(ret)
545    }
546
547    /// Sets whether to use raw (unscaled) screenshot resolution.
548    pub fn set_screenshot_use_raw_size(&self, enable: bool) -> MaaResult<()> {
549        let mut val: u8 = if enable { 1 } else { 0 };
550        let ret = unsafe {
551            sys::MaaControllerSetOption(
552                self.inner.handle.as_ptr(),
553                sys::MaaCtrlOptionEnum_MaaCtrlOption_ScreenshotUseRawSize as i32,
554                &mut val as *mut _ as *mut c_void,
555                std::mem::size_of::<u8>() as u64,
556            )
557        };
558        common::check_bool(ret)
559    }
560
561    // === New controller types ===
562
563    #[cfg(feature = "dbg")]
564    pub fn new_dbg(
565        read_path: &str,
566        write_path: &str,
567        dbg_type: sys::MaaDbgControllerType,
568        config: &str,
569    ) -> MaaResult<Self> {
570        let c_read = CString::new(read_path)?;
571        let c_write = CString::new(write_path)?;
572        let c_cfg = CString::new(config)?;
573        let handle = unsafe {
574            sys::MaaDbgControllerCreate(c_read.as_ptr(), c_write.as_ptr(), dbg_type, c_cfg.as_ptr())
575        };
576        Self::from_handle(handle)
577    }
578
579    /// Create a virtual gamepad controller (Windows only).
580    #[cfg(feature = "win32")]
581    pub fn new_gamepad(
582        hwnd: *mut c_void,
583        gamepad_type: crate::common::GamepadType,
584        screencap_method: crate::common::Win32ScreencapMethod,
585    ) -> MaaResult<Self> {
586        let handle = unsafe {
587            sys::MaaGamepadControllerCreate(hwnd, gamepad_type as u64, screencap_method.bits())
588        };
589        Self::from_handle(handle)
590    }
591
592    // === EventSink ===
593
594    /// Returns sink_id for later removal. Callback lifetime managed by caller.
595    pub fn add_sink<F>(&self, callback: F) -> MaaResult<sys::MaaSinkId>
596    where
597        F: Fn(&str, &str) + Send + Sync + 'static,
598    {
599        let (cb_fn, cb_arg) = crate::callback::EventCallback::new(callback);
600        let sink_id =
601            unsafe { sys::MaaControllerAddSink(self.inner.handle.as_ptr(), cb_fn, cb_arg) };
602        if sink_id != 0 {
603            self.inner
604                .callbacks
605                .lock()
606                .unwrap()
607                .insert(sink_id, cb_arg as usize);
608            Ok(sink_id)
609        } else {
610            unsafe { crate::callback::EventCallback::drop_callback(cb_arg) };
611            Err(MaaError::FrameworkError(0))
612        }
613    }
614
615    /// Register a strongly-typed event sink.
616    ///
617    /// This method registers an implementation of the [`EventSink`](crate::event_sink::EventSink) trait
618    /// to receive structured notifications from this controller.
619    ///
620    /// # Arguments
621    /// * `sink` - The event sink implementation (must be boxed).
622    ///
623    /// # Returns
624    /// A `MaaSinkId` which can be used to manually remove the sink later via [`remove_sink`](Self::remove_sink).
625    /// The sink will be automatically unregistered and dropped when the `Controller` is dropped.
626    pub fn add_event_sink(
627        &self,
628        sink: Box<dyn crate::event_sink::EventSink>,
629    ) -> MaaResult<sys::MaaSinkId> {
630        let handle_id = self.inner.handle.as_ptr() as crate::common::MaaId;
631        let (cb, arg) = crate::callback::EventCallback::new_sink(handle_id, sink);
632        let id = unsafe { sys::MaaControllerAddSink(self.inner.handle.as_ptr(), cb, arg) };
633        if id > 0 {
634            self.inner
635                .event_sinks
636                .lock()
637                .unwrap()
638                .insert(id, arg as usize);
639            Ok(id)
640        } else {
641            unsafe { crate::callback::EventCallback::drop_sink(arg) };
642            Err(MaaError::FrameworkError(0))
643        }
644    }
645
646    pub fn remove_sink(&self, sink_id: sys::MaaSinkId) {
647        unsafe { sys::MaaControllerRemoveSink(self.inner.handle.as_ptr(), sink_id) };
648        if let Some(ptr) = self.inner.callbacks.lock().unwrap().remove(&sink_id) {
649            unsafe { crate::callback::EventCallback::drop_callback(ptr as *mut c_void) };
650        } else if let Some(ptr) = self.inner.event_sinks.lock().unwrap().remove(&sink_id) {
651            unsafe { crate::callback::EventCallback::drop_sink(ptr as *mut c_void) };
652        }
653    }
654
655    pub fn clear_sinks(&self) {
656        unsafe { sys::MaaControllerClearSinks(self.inner.handle.as_ptr()) };
657        let mut callbacks = self.inner.callbacks.lock().unwrap();
658        for (_, ptr) in callbacks.drain() {
659            unsafe { crate::callback::EventCallback::drop_callback(ptr as *mut c_void) };
660        }
661        let mut event_sinks = self.inner.event_sinks.lock().unwrap();
662        for (_, ptr) in event_sinks.drain() {
663            unsafe { crate::callback::EventCallback::drop_sink(ptr as *mut c_void) };
664        }
665    }
666}
667
668impl Drop for ControllerInner {
669    fn drop(&mut self) {
670        unsafe {
671            sys::MaaControllerClearSinks(self.handle.as_ptr());
672            let mut callbacks = self.callbacks.lock().unwrap();
673            for (_, ptr) in callbacks.drain() {
674                crate::callback::EventCallback::drop_callback(ptr as *mut c_void);
675            }
676            let mut event_sinks = self.event_sinks.lock().unwrap();
677            for (_, ptr) in event_sinks.drain() {
678                crate::callback::EventCallback::drop_sink(ptr as *mut c_void);
679            }
680            if self.owns_handle {
681                sys::MaaControllerDestroy(self.handle.as_ptr());
682            }
683        }
684    }
685}
686
687/// Builder for ADB controller configuration.
688///
689/// Provides a fluent API for configuring ADB controllers with sensible defaults.
690#[cfg(feature = "adb")]
691pub struct AdbControllerBuilder {
692    adb_path: String,
693    address: String,
694    screencap_methods: sys::MaaAdbScreencapMethod,
695    input_methods: sys::MaaAdbInputMethod,
696    config: String,
697    agent_path: String,
698}
699
700#[cfg(feature = "adb")]
701impl AdbControllerBuilder {
702    /// Create a new builder with required ADB path and device address.
703    pub fn new(adb_path: &str, address: &str) -> Self {
704        Self {
705            adb_path: adb_path.to_string(),
706            address: address.to_string(),
707            screencap_methods: sys::MaaAdbScreencapMethod_Default as sys::MaaAdbScreencapMethod,
708            input_methods: sys::MaaAdbInputMethod_Default as sys::MaaAdbInputMethod,
709            config: "{}".to_string(),
710            agent_path: String::new(),
711        }
712    }
713
714    /// Set the screencap methods to use.
715    pub fn screencap_methods(mut self, methods: sys::MaaAdbScreencapMethod) -> Self {
716        self.screencap_methods = methods;
717        self
718    }
719
720    /// Set the input methods to use.
721    pub fn input_methods(mut self, methods: sys::MaaAdbInputMethod) -> Self {
722        self.input_methods = methods;
723        self
724    }
725
726    /// Set additional configuration as JSON.
727    pub fn config(mut self, config: &str) -> Self {
728        self.config = config.to_string();
729        self
730    }
731
732    /// Set the path to MaaAgentBinary.
733    pub fn agent_path(mut self, path: &str) -> Self {
734        self.agent_path = path.to_string();
735        self
736    }
737
738    /// Build the controller with the configured options.
739    pub fn build(self) -> MaaResult<Controller> {
740        Controller::create_adb(
741            &self.adb_path,
742            &self.address,
743            self.screencap_methods,
744            self.input_methods,
745            &self.config,
746            &self.agent_path,
747        )
748    }
749}
750
751/// A borrowed reference to a Controller.
752///
753/// This is a non-owning view that can be used for read-only operations.
754/// It does NOT call destroy when dropped and should only be used while
755/// the underlying Controller is still alive.
756pub struct ControllerRef<'a> {
757    handle: *mut sys::MaaController,
758    _marker: std::marker::PhantomData<&'a ()>,
759}
760
761impl<'a> std::fmt::Debug for ControllerRef<'a> {
762    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
763        f.debug_struct("ControllerRef")
764            .field("handle", &self.handle)
765            .finish()
766    }
767}
768
769impl<'a> ControllerRef<'a> {
770    pub(crate) fn from_ptr(handle: *mut sys::MaaController) -> Option<Self> {
771        if handle.is_null() {
772            None
773        } else {
774            Some(Self {
775                handle,
776                _marker: std::marker::PhantomData,
777            })
778        }
779    }
780
781    /// Check if connected.
782    pub fn connected(&self) -> bool {
783        unsafe { sys::MaaControllerConnected(self.handle) != 0 }
784    }
785
786    /// Get device UUID.
787    pub fn uuid(&self) -> MaaResult<String> {
788        let buffer = crate::buffer::MaaStringBuffer::new()?;
789        let ret = unsafe { sys::MaaControllerGetUuid(self.handle, buffer.as_ptr()) };
790        if ret != 0 {
791            Ok(buffer.to_string())
792        } else {
793            Err(MaaError::FrameworkError(0))
794        }
795    }
796
797    /// Get device resolution.
798    pub fn resolution(&self) -> MaaResult<(i32, i32)> {
799        let mut width: i32 = 0;
800        let mut height: i32 = 0;
801        let ret = unsafe { sys::MaaControllerGetResolution(self.handle, &mut width, &mut height) };
802        if ret != 0 {
803            Ok((width, height))
804        } else {
805            Err(MaaError::FrameworkError(0))
806        }
807    }
808
809    /// Get operation status.
810    pub fn status(&self, ctrl_id: common::MaaId) -> common::MaaStatus {
811        let s = unsafe { sys::MaaControllerStatus(self.handle, ctrl_id) };
812        common::MaaStatus(s)
813    }
814
815    /// Wait for operation to complete.
816    pub fn wait(&self, ctrl_id: common::MaaId) -> common::MaaStatus {
817        let s = unsafe { sys::MaaControllerWait(self.handle, ctrl_id) };
818        common::MaaStatus(s)
819    }
820
821    /// Get cached screenshot.
822    pub fn cached_image(&self) -> MaaResult<crate::buffer::MaaImageBuffer> {
823        let buffer = crate::buffer::MaaImageBuffer::new()?;
824        let ret = unsafe { sys::MaaControllerCachedImage(self.handle, buffer.as_ptr()) };
825        if ret != 0 {
826            Ok(buffer)
827        } else {
828            Err(MaaError::FrameworkError(0))
829        }
830    }
831
832    /// Get raw handle.
833    pub fn raw(&self) -> *mut sys::MaaController {
834        self.handle
835    }
836}