Skip to main content

maa_framework/
toolkit.rs

1//! Device discovery and configuration utilities.
2
3use serde::{Deserialize, Serialize};
4
5use crate::{MaaError, MaaResult, common, sys};
6use std::ffi::{CStr, CString};
7use std::path::{Path, PathBuf};
8use std::sync::Once;
9
10/// Information about a connected ADB device.
11#[derive(Debug, Clone, Serialize, Deserialize)]
12pub struct AdbDevice {
13    /// Device display name.
14    pub name: String,
15    /// Path to the ADB executable.
16    pub adb_path: PathBuf,
17    /// Device address (e.g., "127.0.0.1:5555").
18    pub address: String,
19    /// Supported screencap methods (bitflags).
20    pub screencap_methods: u64,
21    /// Supported input methods (bitflags).
22    pub input_methods: u64,
23    /// Device configuration as JSON.
24    pub config: serde_json::Value,
25}
26
27/// Information about a desktop window (Win32).
28#[derive(Debug, Clone, Serialize, Deserialize)]
29pub struct DesktopWindow {
30    /// Window handle (HWND).
31    pub hwnd: usize,
32    /// Window class name.
33    pub class_name: String,
34    /// Window title.
35    pub window_name: String,
36}
37
38/// Toolkit utilities for device discovery and configuration.
39pub struct Toolkit;
40
41static AGENT_SERVER_INIT_OPTION_WARNING: Once = Once::new();
42
43impl Toolkit {
44    #[inline]
45    fn unsupported(api: &str) -> MaaError {
46        MaaError::UnsupportedInAgentServer(api.to_string())
47    }
48
49    fn maybe_warn_init_option_in_agent_server() {
50        if std::env::var_os("MAA_RUST_WARN_AGENTSERVER_TOOLKIT_INIT").is_none() {
51            return;
52        }
53
54        AGENT_SERVER_INIT_OPTION_WARNING.call_once(|| {
55            eprintln!(
56                "Warning: Toolkit::init_option is deprecated in AgentServer; only log_dir is applied."
57            );
58        });
59    }
60
61    /// Initialize MAA framework options.
62    ///
63    /// # Arguments
64    /// * `user_path` - Path to user data directory
65    /// * `default_config` - Default configuration JSON string
66    pub fn init_option(user_path: &str, default_config: &str) -> MaaResult<()> {
67        if crate::is_agent_server_context() {
68            let _ = default_config;
69            Self::maybe_warn_init_option_in_agent_server();
70            let log_dir = Path::new(user_path).join("debug");
71            return crate::configure_logging(log_dir.to_string_lossy().as_ref());
72        }
73
74        let c_path = CString::new(user_path)?;
75        let c_config = CString::new(default_config)?;
76        let ret = unsafe { sys::MaaToolkitConfigInitOption(c_path.as_ptr(), c_config.as_ptr()) };
77        common::check_bool(ret)
78    }
79
80    /// Find connected ADB devices.
81    ///
82    /// Scans for all known Android emulators and connected ADB devices.
83    ///
84    /// # Returns
85    /// List of discovered ADB devices with their configurations.
86    pub fn find_adb_devices() -> MaaResult<Vec<AdbDevice>> {
87        if crate::is_agent_server_context() {
88            return Err(Self::unsupported("Toolkit::find_adb_devices"));
89        }
90        Self::find_adb_devices_impl(None)
91    }
92
93    /// Find connected ADB devices using a specific ADB binary.
94    ///
95    /// # Arguments
96    /// * `adb_path` - Path to the ADB binary to use for discovery
97    ///
98    /// # Returns
99    /// List of discovered ADB devices with their configurations.
100    pub fn find_adb_devices_with_adb(adb_path: &str) -> MaaResult<Vec<AdbDevice>> {
101        if crate::is_agent_server_context() {
102            return Err(Self::unsupported("Toolkit::find_adb_devices_with_adb"));
103        }
104        Self::find_adb_devices_impl(Some(adb_path))
105    }
106
107    fn find_adb_devices_impl(specified_adb: Option<&str>) -> MaaResult<Vec<AdbDevice>> {
108        let list = unsafe { sys::MaaToolkitAdbDeviceListCreate() };
109        if list.is_null() {
110            return Err(MaaError::NullPointer);
111        }
112
113        let _guard = AdbDeviceListGuard(list);
114
115        unsafe {
116            let ret = if let Some(adb_path) = specified_adb {
117                let c_path = CString::new(adb_path)?;
118                sys::MaaToolkitAdbDeviceFindSpecified(c_path.as_ptr(), list)
119            } else {
120                sys::MaaToolkitAdbDeviceFind(list)
121            };
122            common::check_bool(ret)?;
123
124            let count = sys::MaaToolkitAdbDeviceListSize(list);
125            let mut devices = Vec::with_capacity(count as usize);
126
127            for i in 0..count {
128                let device_ptr = sys::MaaToolkitAdbDeviceListAt(list, i);
129                if device_ptr.is_null() {
130                    continue;
131                }
132
133                let name = CStr::from_ptr(sys::MaaToolkitAdbDeviceGetName(device_ptr))
134                    .to_string_lossy()
135                    .into_owned();
136
137                let adb_path_str = CStr::from_ptr(sys::MaaToolkitAdbDeviceGetAdbPath(device_ptr))
138                    .to_string_lossy()
139                    .into_owned();
140
141                let address = CStr::from_ptr(sys::MaaToolkitAdbDeviceGetAddress(device_ptr))
142                    .to_string_lossy()
143                    .into_owned();
144
145                let screencap_methods =
146                    sys::MaaToolkitAdbDeviceGetScreencapMethods(device_ptr) as u64;
147                let input_methods = sys::MaaToolkitAdbDeviceGetInputMethods(device_ptr) as u64;
148
149                let config_str =
150                    CStr::from_ptr(sys::MaaToolkitAdbDeviceGetConfig(device_ptr)).to_string_lossy();
151                let config = serde_json::from_str(&config_str).unwrap_or(serde_json::Value::Null);
152
153                devices.push(AdbDevice {
154                    name,
155                    adb_path: PathBuf::from(adb_path_str),
156                    address,
157                    screencap_methods,
158                    input_methods,
159                    config,
160                });
161            }
162            Ok(devices)
163        }
164    }
165
166    /// Find all desktop windows (Win32 only).
167    ///
168    /// # Returns
169    /// List of visible desktop windows.
170    pub fn find_desktop_windows() -> MaaResult<Vec<DesktopWindow>> {
171        if crate::is_agent_server_context() {
172            return Err(Self::unsupported("Toolkit::find_desktop_windows"));
173        }
174
175        let list = unsafe { sys::MaaToolkitDesktopWindowListCreate() };
176        if list.is_null() {
177            return Err(MaaError::NullPointer);
178        }
179
180        let _guard = DesktopWindowListGuard(list);
181
182        unsafe {
183            let ret = sys::MaaToolkitDesktopWindowFindAll(list);
184            common::check_bool(ret)?;
185
186            let count = sys::MaaToolkitDesktopWindowListSize(list);
187            let mut windows = Vec::with_capacity(count as usize);
188
189            for i in 0..count {
190                let win_ptr = sys::MaaToolkitDesktopWindowListAt(list, i);
191                if win_ptr.is_null() {
192                    continue;
193                }
194
195                let hwnd = sys::MaaToolkitDesktopWindowGetHandle(win_ptr) as usize;
196
197                let class_name = CStr::from_ptr(sys::MaaToolkitDesktopWindowGetClassName(win_ptr))
198                    .to_string_lossy()
199                    .into_owned();
200
201                let window_name =
202                    CStr::from_ptr(sys::MaaToolkitDesktopWindowGetWindowName(win_ptr))
203                        .to_string_lossy()
204                        .into_owned();
205
206                windows.push(DesktopWindow {
207                    hwnd,
208                    class_name,
209                    window_name,
210                });
211            }
212            Ok(windows)
213        }
214    }
215}
216
217struct AdbDeviceListGuard(*mut sys::MaaToolkitAdbDeviceList);
218impl Drop for AdbDeviceListGuard {
219    fn drop(&mut self) {
220        unsafe { sys::MaaToolkitAdbDeviceListDestroy(self.0) }
221    }
222}
223
224struct DesktopWindowListGuard(*mut sys::MaaToolkitDesktopWindowList);
225impl Drop for DesktopWindowListGuard {
226    fn drop(&mut self) {
227        unsafe { sys::MaaToolkitDesktopWindowListDestroy(self.0) }
228    }
229}