use serde::{Deserialize, Serialize};
use crate::{MaaError, MaaResult, common, sys};
use std::ffi::{CStr, CString};
use std::path::{Path, PathBuf};
use std::sync::Once;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AdbDevice {
pub name: String,
pub adb_path: PathBuf,
pub address: String,
pub screencap_methods: u64,
pub input_methods: u64,
pub config: serde_json::Value,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DesktopWindow {
pub hwnd: usize,
pub class_name: String,
pub window_name: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GamescopeInstance {
pub display_no: u32,
pub pipewire_node_id: u32,
pub eis_socket_path: String,
}
#[repr(i32)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum MacOSPermission {
ScreenCapture = sys::MaaMacOSPermissionEnum_MaaMacOSPermissionScreenCapture as i32,
Accessibility = sys::MaaMacOSPermissionEnum_MaaMacOSPermissionAccessibility as i32,
}
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."
);
});
}
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)
}
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)
}
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)
}
}
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)
}
}
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)
}
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)
}
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)
}
pub fn find_gamescope_instances() -> MaaResult<Vec<GamescopeInstance>> {
if crate::is_agent_server_context() {
return Err(Self::unsupported("Toolkit::find_gamescope_instances"));
}
let list = unsafe { sys::MaaToolkitGamescopeInstanceListCreate() };
if list.is_null() {
return Err(MaaError::NullPointer);
}
let _guard = GamescopeInstanceListGuard(list);
unsafe {
common::check_bool(sys::MaaToolkitGamescopeInstanceFindAll(list))?;
let count = sys::MaaToolkitGamescopeInstanceListSize(list);
let mut instances = Vec::with_capacity(count as usize);
for i in 0..count {
let instance_ptr = sys::MaaToolkitGamescopeInstanceListAt(list, i);
if instance_ptr.is_null() {
continue;
}
let display_no = sys::MaaToolkitGamescopeInstanceGetDisplayNo(instance_ptr);
let pipewire_node_id =
sys::MaaToolkitGamescopeInstanceGetPipeWireNodeId(instance_ptr);
let eis_socket_path =
sys::MaaToolkitGamescopeInstanceGetEisSocketPath(instance_ptr);
if eis_socket_path.is_null() {
return Err(MaaError::NullPointer);
}
let eis_socket_path = CStr::from_ptr(eis_socket_path)
.to_string_lossy()
.into_owned();
instances.push(GamescopeInstance {
display_no,
pipewire_node_id,
eis_socket_path,
});
}
Ok(instances)
}
}
}
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) }
}
}
struct GamescopeInstanceListGuard(*mut sys::MaaToolkitGamescopeInstanceList);
impl Drop for GamescopeInstanceListGuard {
fn drop(&mut self) {
unsafe { sys::MaaToolkitGamescopeInstanceListDestroy(self.0) }
}
}
pub struct PortalHelper {
handle: *mut sys::MaaToolkitPortalHelper,
}
impl PortalHelper {
pub fn new() -> MaaResult<Self> {
let handle = unsafe { sys::MaaToolkitPortalHelperCreate() };
if handle.is_null() {
return Err(MaaError::NullPointer);
}
Ok(Self { handle })
}
pub fn open_stream(&self) -> MaaResult<()> {
common::check_bool(unsafe { sys::MaaToolkitPortalHelperOpenStream(self.handle) })
}
pub fn get_persist(&self) -> bool {
unsafe { sys::MaaToolkitPortalHelperGetPersist(self.handle) != 0 }
}
pub fn set_persist(&self, enable: bool) {
unsafe { sys::MaaToolkitPortalHelperSetPersist(self.handle, enable as sys::MaaBool) };
}
pub fn get_pipewire_fd(&self) -> i32 {
unsafe { sys::MaaToolkitPortalHelperGetPipeWireFD(self.handle) }
}
pub fn get_pipewire_node_id(&self) -> u32 {
unsafe { sys::MaaToolkitPortalHelperGetPipeWireNodeID(self.handle) }
}
pub fn get_restore_token(&self) -> String {
let ptr = unsafe { sys::MaaToolkitPortalHelperGetRestoreToken(self.handle) };
if ptr.is_null() {
return String::new();
}
unsafe { CStr::from_ptr(ptr) }
.to_string_lossy()
.into_owned()
}
pub fn set_restore_token(&self, token: &str) -> MaaResult<()> {
let c_token = CString::new(token)?;
unsafe { sys::MaaToolkitPortalHelperSetRestoreToken(self.handle, c_token.as_ptr()) };
Ok(())
}
}
impl Drop for PortalHelper {
fn drop(&mut self) {
unsafe { sys::MaaToolkitPortalHelperDestroy(self.handle) };
}
}