maa-framework 1.16.0

Rust bindings for MaaFramework
Documentation
//! Device discovery and configuration utilities.

use serde::{Deserialize, Serialize};

use crate::{MaaError, MaaResult, common, sys};
use std::ffi::{CStr, CString};
use std::path::{Path, PathBuf};
use std::sync::Once;

/// Information about a connected ADB device.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AdbDevice {
    /// Device display name.
    pub name: String,
    /// Path to the ADB executable.
    pub adb_path: PathBuf,
    /// Device address (e.g., "127.0.0.1:5555").
    pub address: String,
    /// Supported screencap methods (bitflags).
    pub screencap_methods: u64,
    /// Supported input methods (bitflags).
    pub input_methods: u64,
    /// Device configuration as JSON.
    pub config: serde_json::Value,
}

/// Information about a desktop window (Win32).
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DesktopWindow {
    /// Window handle (HWND).
    pub hwnd: usize,
    /// Window class name.
    pub class_name: String,
    /// Window title.
    pub window_name: String,
}

/// macOS system permission types used by toolkit helpers.
#[repr(i32)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum MacOSPermission {
    /// Screen recording / screen capture permission.
    ScreenCapture = sys::MaaMacOSPermissionEnum_MaaMacOSPermissionScreenCapture as i32,
    /// Accessibility permission for input simulation.
    Accessibility = sys::MaaMacOSPermissionEnum_MaaMacOSPermissionAccessibility as i32,
}

/// Toolkit utilities for device discovery and configuration.
pub struct Toolkit;

static AGENT_SERVER_INIT_OPTION_WARNING: Once = Once::new();

impl Toolkit {
    #[inline]
    fn unsupported(api: &str) -> MaaError {
        MaaError::UnsupportedInAgentServer(api.to_string())
    }

    fn maybe_warn_init_option_in_agent_server() {
        if std::env::var_os("MAA_RUST_WARN_AGENTSERVER_TOOLKIT_INIT").is_none() {
            return;
        }

        AGENT_SERVER_INIT_OPTION_WARNING.call_once(|| {
            eprintln!(
                "Warning: Toolkit::init_option is deprecated in AgentServer; only log_dir is applied."
            );
        });
    }

    /// Initialize MAA framework options.
    ///
    /// # Arguments
    /// * `user_path` - Path to user data directory
    /// * `default_config` - Default configuration JSON string
    pub fn init_option(user_path: &str, default_config: &str) -> MaaResult<()> {
        if crate::is_agent_server_context() {
            let _ = default_config;
            Self::maybe_warn_init_option_in_agent_server();
            let log_dir = Path::new(user_path).join("debug");
            return crate::configure_logging(log_dir.to_string_lossy().as_ref());
        }

        let c_path = CString::new(user_path)?;
        let c_config = CString::new(default_config)?;
        let ret = unsafe { sys::MaaToolkitConfigInitOption(c_path.as_ptr(), c_config.as_ptr()) };
        common::check_bool(ret)
    }

    /// Find connected ADB devices.
    ///
    /// Scans for all known Android emulators and connected ADB devices.
    ///
    /// # Returns
    /// List of discovered ADB devices with their configurations.
    pub fn find_adb_devices() -> MaaResult<Vec<AdbDevice>> {
        if crate::is_agent_server_context() {
            return Err(Self::unsupported("Toolkit::find_adb_devices"));
        }
        Self::find_adb_devices_impl(None)
    }

    /// Find connected ADB devices using a specific ADB binary.
    ///
    /// # Arguments
    /// * `adb_path` - Path to the ADB binary to use for discovery
    ///
    /// # Returns
    /// List of discovered ADB devices with their configurations.
    pub fn find_adb_devices_with_adb(adb_path: &str) -> MaaResult<Vec<AdbDevice>> {
        if crate::is_agent_server_context() {
            return Err(Self::unsupported("Toolkit::find_adb_devices_with_adb"));
        }
        Self::find_adb_devices_impl(Some(adb_path))
    }

    fn find_adb_devices_impl(specified_adb: Option<&str>) -> MaaResult<Vec<AdbDevice>> {
        let list = unsafe { sys::MaaToolkitAdbDeviceListCreate() };
        if list.is_null() {
            return Err(MaaError::NullPointer);
        }

        let _guard = AdbDeviceListGuard(list);

        unsafe {
            let ret = if let Some(adb_path) = specified_adb {
                let c_path = CString::new(adb_path)?;
                sys::MaaToolkitAdbDeviceFindSpecified(c_path.as_ptr(), list)
            } else {
                sys::MaaToolkitAdbDeviceFind(list)
            };
            common::check_bool(ret)?;

            let count = sys::MaaToolkitAdbDeviceListSize(list);
            let mut devices = Vec::with_capacity(count as usize);

            for i in 0..count {
                let device_ptr = sys::MaaToolkitAdbDeviceListAt(list, i);
                if device_ptr.is_null() {
                    continue;
                }

                let name = CStr::from_ptr(sys::MaaToolkitAdbDeviceGetName(device_ptr))
                    .to_string_lossy()
                    .into_owned();

                let adb_path_str = CStr::from_ptr(sys::MaaToolkitAdbDeviceGetAdbPath(device_ptr))
                    .to_string_lossy()
                    .into_owned();

                let address = CStr::from_ptr(sys::MaaToolkitAdbDeviceGetAddress(device_ptr))
                    .to_string_lossy()
                    .into_owned();

                let screencap_methods =
                    sys::MaaToolkitAdbDeviceGetScreencapMethods(device_ptr) as u64;
                let input_methods = sys::MaaToolkitAdbDeviceGetInputMethods(device_ptr) as u64;

                let config_str =
                    CStr::from_ptr(sys::MaaToolkitAdbDeviceGetConfig(device_ptr)).to_string_lossy();
                let config = serde_json::from_str(&config_str).unwrap_or(serde_json::Value::Null);

                devices.push(AdbDevice {
                    name,
                    adb_path: PathBuf::from(adb_path_str),
                    address,
                    screencap_methods,
                    input_methods,
                    config,
                });
            }
            Ok(devices)
        }
    }

    /// Find all desktop windows (Win32 only).
    ///
    /// # Returns
    /// List of visible desktop windows.
    pub fn find_desktop_windows() -> MaaResult<Vec<DesktopWindow>> {
        if crate::is_agent_server_context() {
            return Err(Self::unsupported("Toolkit::find_desktop_windows"));
        }

        let list = unsafe { sys::MaaToolkitDesktopWindowListCreate() };
        if list.is_null() {
            return Err(MaaError::NullPointer);
        }

        let _guard = DesktopWindowListGuard(list);

        unsafe {
            let ret = sys::MaaToolkitDesktopWindowFindAll(list);
            common::check_bool(ret)?;

            let count = sys::MaaToolkitDesktopWindowListSize(list);
            let mut windows = Vec::with_capacity(count as usize);

            for i in 0..count {
                let win_ptr = sys::MaaToolkitDesktopWindowListAt(list, i);
                if win_ptr.is_null() {
                    continue;
                }

                let hwnd = sys::MaaToolkitDesktopWindowGetHandle(win_ptr) as usize;

                let class_name = CStr::from_ptr(sys::MaaToolkitDesktopWindowGetClassName(win_ptr))
                    .to_string_lossy()
                    .into_owned();

                let window_name =
                    CStr::from_ptr(sys::MaaToolkitDesktopWindowGetWindowName(win_ptr))
                        .to_string_lossy()
                        .into_owned();

                windows.push(DesktopWindow {
                    hwnd,
                    class_name,
                    window_name,
                });
            }
            Ok(windows)
        }
    }

    /// Check whether the current process has the specified macOS permission.
    pub fn macos_check_permission(permission: MacOSPermission) -> MaaResult<bool> {
        if crate::is_agent_server_context() {
            return Err(Self::unsupported("Toolkit::macos_check_permission"));
        }

        let ret =
            unsafe { sys::MaaToolkitMacOSCheckPermission(permission as sys::MaaMacOSPermission) };
        Ok(ret != 0)
    }

    /// Request the specified macOS permission from the system.
    ///
    /// A successful return means the request API call succeeded. It does not
    /// necessarily mean the user has already granted the permission.
    pub fn macos_request_permission(permission: MacOSPermission) -> MaaResult<bool> {
        if crate::is_agent_server_context() {
            return Err(Self::unsupported("Toolkit::macos_request_permission"));
        }

        let ret =
            unsafe { sys::MaaToolkitMacOSRequestPermission(permission as sys::MaaMacOSPermission) };
        Ok(ret != 0)
    }

    /// Open the corresponding macOS settings page for the permission.
    pub fn macos_reveal_permission_settings(permission: MacOSPermission) -> MaaResult<bool> {
        if crate::is_agent_server_context() {
            return Err(Self::unsupported(
                "Toolkit::macos_reveal_permission_settings",
            ));
        }

        let ret = unsafe {
            sys::MaaToolkitMacOSRevealPermissionSettings(permission as sys::MaaMacOSPermission)
        };
        Ok(ret != 0)
    }
}

struct AdbDeviceListGuard(*mut sys::MaaToolkitAdbDeviceList);
impl Drop for AdbDeviceListGuard {
    fn drop(&mut self) {
        unsafe { sys::MaaToolkitAdbDeviceListDestroy(self.0) }
    }
}

struct DesktopWindowListGuard(*mut sys::MaaToolkitDesktopWindowList);
impl Drop for DesktopWindowListGuard {
    fn drop(&mut self) {
        unsafe { sys::MaaToolkitDesktopWindowListDestroy(self.0) }
    }
}