maa-framework 1.15.0

Rust bindings for MaaFramework
Documentation
//! Agent client for connecting to AgentServer.
//!
//! This module allows delegating custom recognition and action execution
//! to a separate process running an AgentServer.

use crate::{
    MaaError, MaaResult, buffer, common, controller::Controller, resource::Resource, sys,
    tasker::Tasker,
};
use std::ptr::NonNull;

/// Agent client for remote custom component execution.
///
/// Connects to an AgentServer to delegate custom recognition and action
/// execution to a separate process.
pub struct AgentClient {
    handle: NonNull<sys::MaaAgentClient>,
    _resource_guard: Option<Resource>,
    _controller_guard: Option<Controller>,
    _tasker_guard: Option<Tasker>,
}

unsafe impl Send for AgentClient {}
unsafe impl Sync for AgentClient {}

impl AgentClient {
    /// Create a new agent client.
    ///
    /// Uses IPC mode by default. On older Windows versions that don't support AF_UNIX
    /// (before Build 17063), it will automatically fall back to TCP mode.
    ///
    /// # Arguments
    /// * `identifier` - Optional connection identifier for matching specific AgentServer
    ///
    /// # Example
    /// ```no_run
    /// use maa_framework::agent_client::AgentClient;
    ///
    /// // Create with auto-generated identifier
    /// let client = AgentClient::new(None).expect("Failed to create client");
    ///
    /// // Create with specific identifier
    /// let client = AgentClient::new(Some("my_agent")).expect("Failed to create client");
    /// let id = client.identifier().expect("Failed to get identifier");
    /// println!("Identifier: {}", id);
    /// ```
    pub fn new(identifier: Option<&str>) -> MaaResult<Self> {
        let id_buffer = if let Some(id) = identifier {
            let mut buf = buffer::MaaStringBuffer::new()?;
            buf.set(id)?;
            Some(buf)
        } else {
            None
        };

        let handle = unsafe {
            sys::MaaAgentClientCreateV2(
                id_buffer
                    .as_ref()
                    .map(|b| b.raw())
                    .unwrap_or(std::ptr::null_mut()),
            )
        };

        NonNull::new(handle)
            .map(|ptr| Self {
                handle: ptr,
                _resource_guard: None,
                _controller_guard: None,
                _tasker_guard: None,
            })
            .ok_or(MaaError::FrameworkError(-1))
    }

    /// Create an agent client with TCP connection.
    ///
    /// The client listens on 127.0.0.1 at the specified port.
    /// If 0 is passed, an available port is automatically selected.
    /// AgentServer can use the port number from [`identifier()`](Self::identifier)
    /// to connect via TCP.
    ///
    /// # Arguments
    /// * `port` - TCP port (0-65535), 0 for auto-select
    ///
    /// # Example
    /// ```no_run
    /// use maa_framework::agent_client::AgentClient;
    ///
    /// let client = AgentClient::create_tcp(0).expect("Failed to create TCP client");
    /// let port = client.identifier().expect("Failed to get port");
    /// println!("Listening on port: {}", port);
    /// ```
    pub fn create_tcp(port: u16) -> MaaResult<Self> {
        let handle = unsafe { sys::MaaAgentClientCreateTcp(port) };

        NonNull::new(handle)
            .map(|ptr| Self {
                handle: ptr,
                _resource_guard: None,
                _controller_guard: None,
                _tasker_guard: None,
            })
            .ok_or(MaaError::FrameworkError(-1))
    }

    /// Get the connection identifier.
    pub fn identifier(&self) -> Option<String> {
        let buffer = buffer::MaaStringBuffer::new().ok()?;
        let ret = unsafe { sys::MaaAgentClientIdentifier(self.handle.as_ptr(), buffer.raw()) };
        if ret != 0 {
            Some(buffer.to_string())
        } else {
            None
        }
    }

    /// Bind a resource to receive custom recognitions and actions from AgentServer.
    ///
    /// Takes ownership of the Resource to ensure it stays alive while bound.
    pub fn bind(&mut self, resource: Resource) -> MaaResult<()> {
        let ret = unsafe { sys::MaaAgentClientBindResource(self.handle.as_ptr(), resource.raw()) };
        common::check_bool(ret)?;

        self._resource_guard = Some(resource);
        Ok(())
    }

    /// Register resource event sink to forward events to AgentServer.
    pub fn register_resource_sink(&mut self, resource: Resource) -> MaaResult<()> {
        let ret = unsafe {
            sys::MaaAgentClientRegisterResourceSink(self.handle.as_ptr(), resource.raw())
        };
        common::check_bool(ret)?;
        self._resource_guard = Some(resource);
        Ok(())
    }

    /// Register controller event sink to forward events to AgentServer.
    pub fn register_controller_sink(&mut self, controller: Controller) -> MaaResult<()> {
        let ret = unsafe {
            sys::MaaAgentClientRegisterControllerSink(self.handle.as_ptr(), controller.raw())
        };
        common::check_bool(ret)?;
        self._controller_guard = Some(controller);
        Ok(())
    }

    /// Register tasker event sink to forward events to AgentServer.
    pub fn register_tasker_sink(&mut self, tasker: Tasker) -> MaaResult<()> {
        let ret =
            unsafe { sys::MaaAgentClientRegisterTaskerSink(self.handle.as_ptr(), tasker.raw()) };
        common::check_bool(ret)?;
        self._tasker_guard = Some(tasker);
        Ok(())
    }

    /// Register all event sinks (resource, controller, tasker) at once.
    pub fn register_sinks(
        &mut self,
        resource: Resource,
        controller: Controller,
        tasker: Tasker,
    ) -> MaaResult<()> {
        self.register_resource_sink(resource)?;
        self.register_controller_sink(controller)?;
        self.register_tasker_sink(tasker)
    }

    /// Connect to the AgentServer.
    pub fn connect(&self) -> MaaResult<()> {
        let ret = unsafe { sys::MaaAgentClientConnect(self.handle.as_ptr()) };
        common::check_bool(ret)
    }

    /// Disconnect from the AgentServer.
    pub fn disconnect(&self) -> MaaResult<()> {
        let ret = unsafe { sys::MaaAgentClientDisconnect(self.handle.as_ptr()) };
        common::check_bool(ret)
    }

    /// Check if currently connected to AgentServer.
    pub fn connected(&self) -> bool {
        unsafe { sys::MaaAgentClientConnected(self.handle.as_ptr()) != 0 }
    }

    /// Check if the connection is alive.
    pub fn alive(&self) -> bool {
        unsafe { sys::MaaAgentClientAlive(self.handle.as_ptr()) != 0 }
    }

    /// Set the connection timeout.
    ///
    /// # Arguments
    /// * `milliseconds` - Timeout in milliseconds
    pub fn set_timeout(&self, milliseconds: i64) -> MaaResult<()> {
        let ret = unsafe { sys::MaaAgentClientSetTimeout(self.handle.as_ptr(), milliseconds) };
        common::check_bool(ret)
    }

    /// Get the list of custom recognitions available on the AgentServer.
    pub fn custom_recognition_list(&self) -> MaaResult<Vec<String>> {
        let buffer = buffer::MaaStringListBuffer::new()?;
        let ret = unsafe {
            sys::MaaAgentClientGetCustomRecognitionList(self.handle.as_ptr(), buffer.raw())
        };
        if ret != 0 {
            Ok(buffer.to_vec())
        } else {
            Err(MaaError::FrameworkError(0))
        }
    }

    /// Get the list of custom actions available on the AgentServer.
    pub fn custom_action_list(&self) -> MaaResult<Vec<String>> {
        let buffer = buffer::MaaStringListBuffer::new()?;
        let ret =
            unsafe { sys::MaaAgentClientGetCustomActionList(self.handle.as_ptr(), buffer.raw()) };
        if ret != 0 {
            Ok(buffer.to_vec())
        } else {
            Err(MaaError::FrameworkError(0))
        }
    }

    /// Get the raw handle pointer.
    pub fn raw(&self) -> *mut sys::MaaAgentClient {
        self.handle.as_ptr()
    }
}

impl Drop for AgentClient {
    fn drop(&mut self) {
        unsafe { sys::MaaAgentClientDestroy(self.handle.as_ptr()) }
    }
}