maa_framework/lib.rs
1//! # MaaFramework Rust Bindings
2//!
3//! High-performance, safe Rust bindings for [MaaFramework](https://github.com/MaaXYZ/MaaFramework),
4//! a game automation framework based on image recognition.
5//!
6//! ## Quick Start
7//!
8//! ```no_run
9//! use maa_framework::toolkit::Toolkit;
10//! use maa_framework::controller::Controller;
11//! use maa_framework::resource::Resource;
12//! use maa_framework::tasker::Tasker;
13//!
14//! fn main() -> Result<(), Box<dyn std::error::Error>> {
15//! // 0. Load library (Dynamic only)
16//! #[cfg(feature = "dynamic")]
17//! maa_framework::load_library(std::path::Path::new("MaaFramework.dll"))?;
18//!
19//! // 1. Find devices
20//! let devices = Toolkit::find_adb_devices()?;
21//! let device = devices.first().expect("No device found");
22//!
23//! // 2. Create controller (agent_path: "" to use MAA_AGENT_PATH or current dir)
24//! let adb_path = device.adb_path.to_str().ok_or_else(|| {
25//! std::io::Error::new(std::io::ErrorKind::InvalidData, "Invalid ADB path")
26//! })?;
27//! let controller = Controller::new_adb(adb_path, &device.address, "{}", "")?;
28//!
29//! // 3. Create resource and tasker
30//! let resource = Resource::new()?;
31//! let tasker = Tasker::new()?;
32//!
33//! // 4. Bind and run
34//! tasker.bind(&resource, &controller)?;
35//! let job = tasker.post_task("StartTask", "{}")?;
36//! job.wait();
37//!
38//! Ok(())
39//! }
40//! ```
41//!
42//! ## Core Modules
43//!
44//! | Module | Description |
45//! |--------|-------------|
46//! | [`tasker`] | Task execution and pipeline management |
47//! | [`resource`] | Resource loading (images, models, pipelines) |
48//! | [`controller`] | Device control (ADB, Win32, macOS, Android Native, PlayCover, Linux) |
49//! | [`context`] | Task execution context for custom components |
50//! | [`toolkit`] | Device discovery utilities |
51//! | [`pipeline`] | Pipeline configuration types |
52//! | [`job`] | Asynchronous job management |
53//! | [`event_sink`] | Event sink system for typed callbacks |
54//! | [`buffer`] | Safe data buffers for FFI |
55//! | [`custom`] | Custom recognizer and action traits |
56//! | [`custom_controller`] | Custom controller implementation |
57//! | [`notification`] | Structured event notification parsing |
58//! | [`common`] | Common types and data structures |
59//! | [`error`] | Error types and handling |
60//! | [`util`] | Miscellaneous utility functions |
61//! | [`agent_client`] | Remote custom component client |
62//! | [`agent_server`] | Remote custom component server |
63//!
64//! ## Feature Flags
65//!
66//! - `adb` - ADB controller support (default)
67//! - `win32` - Win32 controller support (Windows only)
68//! - `custom` - Custom recognizer/action/controller support
69//! - `toolkit` - Device discovery utilities
70//! - `image` - Integration with the `image` crate
71
72#![allow(non_upper_case_globals)]
73#![allow(non_camel_case_types)]
74#![allow(non_snake_case)]
75
76pub mod agent_client;
77pub mod agent_server;
78pub mod buffer;
79pub mod callback;
80pub mod common;
81pub mod context;
82pub mod controller;
83pub mod custom;
84pub mod custom_controller;
85pub mod error;
86pub mod event_sink;
87pub mod job;
88pub mod notification;
89pub mod pipeline;
90pub mod resource;
91pub mod tasker;
92pub mod toolkit;
93pub mod util;
94
95pub use common::AndroidNativeControllerConfig;
96pub use common::AndroidScreenResolution;
97pub use common::ControllerFeature;
98pub use common::LinuxControllerConfig;
99pub use common::LinuxInputMethod;
100pub use common::LinuxScreencapMethod;
101pub use common::MaaStatus;
102pub use error::{MaaError, MaaResult};
103
104pub use maa_framework_sys as sys;
105
106use std::ffi::CString;
107use std::sync::atomic::{AtomicU8, Ordering};
108
109const RUNTIME_CONTEXT_UNKNOWN: u8 = 0;
110#[cfg(feature = "dynamic")]
111const RUNTIME_CONTEXT_FRAMEWORK: u8 = 1;
112const RUNTIME_CONTEXT_AGENT_SERVER: u8 = 2;
113
114static RUNTIME_CONTEXT: AtomicU8 = AtomicU8::new(RUNTIME_CONTEXT_UNKNOWN);
115
116/// Get the MaaFramework version string.
117///
118/// # Example
119/// ```no_run
120/// println!("MaaFramework version: {}", maa_framework::maa_version());
121/// ```
122pub fn maa_version() -> &'static str {
123 unsafe {
124 std::ffi::CStr::from_ptr(sys::MaaVersion())
125 .to_str()
126 .unwrap_or("unknown")
127 }
128}
129
130/// Set a global framework option.
131///
132/// Low-level function for setting global options. Consider using the
133/// convenience wrappers like [`configure_logging`], [`set_debug_mode`], etc.
134pub fn set_global_option(
135 key: sys::MaaGlobalOption,
136 value: *mut std::ffi::c_void,
137 size: u64,
138) -> MaaResult<()> {
139 let ret = unsafe { sys::MaaGlobalSetOption(key, value, size) };
140 common::check_bool(ret)
141}
142
143/// Configure the log output directory.
144///
145/// # Arguments
146/// * `log_dir` - Path to the directory where logs should be stored
147pub fn configure_logging(log_dir: &str) -> MaaResult<()> {
148 let c_dir = CString::new(log_dir)?;
149 set_global_option(
150 sys::MaaGlobalOptionEnum_MaaGlobalOption_LogDir as i32,
151 c_dir.as_ptr() as *mut _,
152 c_dir.as_bytes().len() as u64,
153 )
154}
155
156/// Enable or disable debug mode.
157///
158/// In debug mode:
159/// - Recognition details include raw images and draws
160/// - All tasks are treated as focus tasks and produce callbacks
161///
162/// # Arguments
163/// * `enable` - `true` to enable debug mode
164pub fn set_debug_mode(enable: bool) -> MaaResult<()> {
165 let mut val_bool = if enable { 1u8 } else { 0u8 };
166 set_global_option(
167 sys::MaaGlobalOptionEnum_MaaGlobalOption_DebugMode as i32,
168 &mut val_bool as *mut _ as *mut _,
169 std::mem::size_of::<u8>() as u64,
170 )
171}
172
173/// Set the log level for stdout output.
174///
175/// # Arguments
176/// * `level` - Logging level (use `sys::MaaLoggingLevel*` constants)
177pub fn set_stdout_level(level: sys::MaaLoggingLevel) -> MaaResult<()> {
178 let mut val = level;
179 set_global_option(
180 sys::MaaGlobalOptionEnum_MaaGlobalOption_StdoutLevel as i32,
181 &mut val as *mut _ as *mut _,
182 std::mem::size_of::<sys::MaaLoggingLevel>() as u64,
183 )
184}
185
186/// Enable/disable saving recognition visualizations to log directory.
187pub fn set_save_draw(enable: bool) -> MaaResult<()> {
188 let mut val: u8 = if enable { 1 } else { 0 };
189 set_global_option(
190 sys::MaaGlobalOptionEnum_MaaGlobalOption_SaveDraw as i32,
191 &mut val as *mut _ as *mut _,
192 std::mem::size_of::<u8>() as u64,
193 )
194}
195
196/// Enable/disable saving screenshots on error.
197pub fn set_save_on_error(enable: bool) -> MaaResult<()> {
198 let mut val: u8 = if enable { 1 } else { 0 };
199 set_global_option(
200 sys::MaaGlobalOptionEnum_MaaGlobalOption_SaveOnError as i32,
201 &mut val as *mut _ as *mut _,
202 std::mem::size_of::<u8>() as u64,
203 )
204}
205
206/// Set JPEG quality for saved draw images (0-100, default 85).
207pub fn set_draw_quality(quality: i32) -> MaaResult<()> {
208 let mut val = quality;
209 set_global_option(
210 sys::MaaGlobalOptionEnum_MaaGlobalOption_DrawQuality as i32,
211 &mut val as *mut _ as *mut _,
212 std::mem::size_of::<i32>() as u64,
213 )
214}
215
216/// Set the recognition image cache limit (default 4096).
217pub fn set_reco_image_cache_limit(limit: u64) -> MaaResult<()> {
218 let mut val = limit;
219 set_global_option(
220 sys::MaaGlobalOptionEnum_MaaGlobalOption_RecoImageCacheLimit as i32,
221 &mut val as *mut _ as *mut _,
222 std::mem::size_of::<u64>() as u64,
223 )
224}
225
226/// Load a plugin from the specified path.
227pub fn load_plugin(path: &str) -> MaaResult<()> {
228 let c_path = CString::new(path)?;
229 let ret = unsafe { sys::MaaGlobalLoadPlugin(c_path.as_ptr()) };
230 common::check_bool(ret)
231}
232
233/// Loads the MaaFramework dynamic library.
234///
235/// You **must** call this function successfully before using any other APIs when the `dynamic`
236/// feature is enabled.
237///
238/// # Arguments
239///
240/// * `path` - Path to the dynamic library file (e.g., `MaaFramework.dll`, `libMaaFramework.so`).
241///
242/// # Errors
243///
244/// Returns an error if:
245/// * The library file cannot be found or loaded.
246/// * The library has already been loaded (multiple initialization is not supported).
247/// * Required symbols are missing from the library.
248///
249/// # Panics
250///
251/// Subsequent calls to any MaaFramework API will panic if the library has not been initialized.
252///
253/// # Safety
254///
255/// This function is `unsafe` because:
256/// * It executes arbitrary initialization code (e.g., `DllMain`) inside the loaded library.
257/// * The caller must ensure `path` points to a valid MaaFramework binary compatible with these bindings.
258#[cfg(feature = "dynamic")]
259pub fn load_library(path: &std::path::Path) -> Result<(), String> {
260 let context = runtime_context_from_library_path(path);
261 unsafe { sys::load_library(path) }?;
262 RUNTIME_CONTEXT.store(context, Ordering::Relaxed);
263 Ok(())
264}
265
266/// Finds and loads the MaaFramework dynamic library when using the `dynamic` feature.
267///
268/// Tries, in order: `MAA_SDK_PATH` (bin/lib), project `MAA-*` dirs (from `CARGO_MANIFEST_DIR`
269/// or current dir), `target/debug` or `target/release`, then current dir. Use this in examples
270/// or apps so that `cargo run --example main` works without setting env vars.
271///
272/// # Errors
273///
274/// Returns an error if no library file is found or loading fails.
275#[cfg(feature = "dynamic")]
276pub fn ensure_library_loaded() -> Result<(), String> {
277 let lib_name = if cfg!(target_os = "windows") {
278 "MaaFramework.dll"
279 } else if cfg!(target_os = "macos") {
280 "libMaaFramework.dylib"
281 } else {
282 "libMaaFramework.so"
283 };
284
285 let mut candidates: Vec<std::path::PathBuf> = Vec::new();
286
287 if let Ok(sdk) = std::env::var("MAA_SDK_PATH") {
288 let sdk = std::path::PathBuf::from(sdk);
289 candidates.push(sdk.join("bin").join(lib_name));
290 candidates.push(sdk.join("lib").join(lib_name));
291 }
292
293 let search_roots: Vec<std::path::PathBuf> = std::env::var("CARGO_MANIFEST_DIR")
294 .map(std::path::PathBuf::from)
295 .into_iter()
296 .chain(std::env::current_dir().ok())
297 .collect();
298
299 for root in &search_roots {
300 if let Ok(entries) = std::fs::read_dir(root) {
301 for e in entries.flatten() {
302 let p = e.path();
303 if p.is_dir() {
304 if let Some(name) = p.file_name() {
305 if name.to_string_lossy().starts_with("MAA-") {
306 candidates.push(p.join("bin").join(lib_name));
307 }
308 }
309 }
310 }
311 }
312 }
313
314 if let Ok(cwd) = std::env::current_dir() {
315 candidates.push(cwd.join("target/debug").join(lib_name));
316 candidates.push(cwd.join("target/release").join(lib_name));
317 candidates.push(cwd.join(lib_name));
318 }
319
320 let chosen = candidates.into_iter().find(|p| p.exists());
321 match chosen {
322 Some(path) => load_library(&path),
323 None => Err(
324 "MaaFramework library not found. Set MAA_SDK_PATH or place SDK (e.g. MAA-*/bin/)."
325 .to_string(),
326 ),
327 }
328}
329
330pub(crate) fn mark_agent_server_context() {
331 RUNTIME_CONTEXT.store(RUNTIME_CONTEXT_AGENT_SERVER, Ordering::Relaxed);
332}
333
334pub(crate) fn is_agent_server_context() -> bool {
335 RUNTIME_CONTEXT.load(Ordering::Relaxed) == RUNTIME_CONTEXT_AGENT_SERVER
336}
337
338#[cfg(feature = "dynamic")]
339fn runtime_context_from_library_path(path: &std::path::Path) -> u8 {
340 let Some(file_name) = path.file_name() else {
341 return RUNTIME_CONTEXT_UNKNOWN;
342 };
343 let file_name = file_name.to_string_lossy();
344 if file_name.is_empty() {
345 return RUNTIME_CONTEXT_UNKNOWN;
346 }
347 if file_name.contains("MaaAgentServer") {
348 RUNTIME_CONTEXT_AGENT_SERVER
349 } else {
350 RUNTIME_CONTEXT_FRAMEWORK
351 }
352}