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