Skip to main content

maa_framework/
agent_client.rs

1//! Agent client for connecting to AgentServer.
2//!
3//! This module allows delegating custom recognition and action execution
4//! to a separate process running an AgentServer.
5
6use crate::{
7    buffer, common, controller::Controller, resource::Resource, sys, tasker::Tasker, MaaError,
8    MaaResult,
9};
10use std::ptr::NonNull;
11
12/// Agent client for remote custom component execution.
13///
14/// Connects to an AgentServer to delegate custom recognition and action
15/// execution to a separate process.
16pub struct AgentClient {
17    handle: NonNull<sys::MaaAgentClient>,
18    _resource_guard: Option<Resource>,
19    _controller_guard: Option<Controller>,
20    _tasker_guard: Option<Tasker>,
21}
22
23unsafe impl Send for AgentClient {}
24unsafe impl Sync for AgentClient {}
25
26impl AgentClient {
27    /// Create a new agent client.
28    ///
29    /// Uses IPC mode by default. On older Windows versions that don't support AF_UNIX
30    /// (before Build 17063), it will automatically fall back to TCP mode.
31    ///
32    /// # Arguments
33    /// * `identifier` - Optional connection identifier for matching specific AgentServer
34    ///
35    /// # Example
36    /// ```no_run
37    /// use maa_framework::agent_client::AgentClient;
38    ///
39    /// // Create with auto-generated identifier
40    /// let client = AgentClient::new(None).expect("Failed to create client");
41    ///
42    /// // Create with specific identifier
43    /// let client = AgentClient::new(Some("my_agent")).expect("Failed to create client");
44    /// let id = client.identifier().expect("Failed to get identifier");
45    /// println!("Identifier: {}", id);
46    /// ```
47    pub fn new(identifier: Option<&str>) -> MaaResult<Self> {
48        let id_buffer = if let Some(id) = identifier {
49            let mut buf = buffer::MaaStringBuffer::new()?;
50            buf.set(id)?;
51            Some(buf)
52        } else {
53            None
54        };
55
56        let handle = unsafe {
57            sys::MaaAgentClientCreateV2(
58                id_buffer
59                    .as_ref()
60                    .map(|b| b.raw())
61                    .unwrap_or(std::ptr::null_mut()),
62            )
63        };
64
65        NonNull::new(handle)
66            .map(|ptr| Self {
67                handle: ptr,
68                _resource_guard: None,
69                _controller_guard: None,
70                _tasker_guard: None,
71            })
72            .ok_or(MaaError::FrameworkError(-1))
73    }
74
75    /// Create an agent client with TCP connection.
76    ///
77    /// The client listens on 127.0.0.1 at the specified port.
78    /// If 0 is passed, an available port is automatically selected.
79    /// AgentServer can use the port number from [`identifier()`](Self::identifier)
80    /// to connect via TCP.
81    ///
82    /// # Arguments
83    /// * `port` - TCP port (0-65535), 0 for auto-select
84    ///
85    /// # Example
86    /// ```no_run
87    /// use maa_framework::agent_client::AgentClient;
88    ///
89    /// let client = AgentClient::create_tcp(0).expect("Failed to create TCP client");
90    /// let port = client.identifier().expect("Failed to get port");
91    /// println!("Listening on port: {}", port);
92    /// ```
93    pub fn create_tcp(port: u16) -> MaaResult<Self> {
94        let handle = unsafe { sys::MaaAgentClientCreateTcp(port) };
95
96        NonNull::new(handle)
97            .map(|ptr| Self {
98                handle: ptr,
99                _resource_guard: None,
100                _controller_guard: None,
101                _tasker_guard: None,
102            })
103            .ok_or(MaaError::FrameworkError(-1))
104    }
105
106    /// Get the connection identifier.
107    pub fn identifier(&self) -> Option<String> {
108        let buffer = buffer::MaaStringBuffer::new().ok()?;
109        let ret = unsafe { sys::MaaAgentClientIdentifier(self.handle.as_ptr(), buffer.raw()) };
110        if ret != 0 {
111            Some(buffer.to_string())
112        } else {
113            None
114        }
115    }
116
117    /// Bind a resource to receive custom recognitions and actions from AgentServer.
118    ///
119    /// Takes ownership of the Resource to ensure it stays alive while bound.
120    pub fn bind(&mut self, resource: Resource) -> MaaResult<()> {
121        let ret = unsafe { sys::MaaAgentClientBindResource(self.handle.as_ptr(), resource.raw()) };
122        common::check_bool(ret)?;
123
124        self._resource_guard = Some(resource);
125        Ok(())
126    }
127
128    /// Register resource event sink to forward events to AgentServer.
129    pub fn register_resource_sink(&mut self, resource: Resource) -> MaaResult<()> {
130        let ret = unsafe {
131            sys::MaaAgentClientRegisterResourceSink(self.handle.as_ptr(), resource.raw())
132        };
133        common::check_bool(ret)?;
134        self._resource_guard = Some(resource);
135        Ok(())
136    }
137
138    /// Register controller event sink to forward events to AgentServer.
139    pub fn register_controller_sink(&mut self, controller: Controller) -> MaaResult<()> {
140        let ret = unsafe {
141            sys::MaaAgentClientRegisterControllerSink(self.handle.as_ptr(), controller.raw())
142        };
143        common::check_bool(ret)?;
144        self._controller_guard = Some(controller);
145        Ok(())
146    }
147
148    /// Register tasker event sink to forward events to AgentServer.
149    pub fn register_tasker_sink(&mut self, tasker: Tasker) -> MaaResult<()> {
150        let ret =
151            unsafe { sys::MaaAgentClientRegisterTaskerSink(self.handle.as_ptr(), tasker.raw()) };
152        common::check_bool(ret)?;
153        self._tasker_guard = Some(tasker);
154        Ok(())
155    }
156
157    /// Register all event sinks (resource, controller, tasker) at once.
158    pub fn register_sinks(
159        &mut self,
160        resource: Resource,
161        controller: Controller,
162        tasker: Tasker,
163    ) -> MaaResult<()> {
164        self.register_resource_sink(resource)?;
165        self.register_controller_sink(controller)?;
166        self.register_tasker_sink(tasker)
167    }
168
169    /// Connect to the AgentServer.
170    pub fn connect(&self) -> MaaResult<()> {
171        let ret = unsafe { sys::MaaAgentClientConnect(self.handle.as_ptr()) };
172        common::check_bool(ret)
173    }
174
175    /// Disconnect from the AgentServer.
176    pub fn disconnect(&self) -> MaaResult<()> {
177        let ret = unsafe { sys::MaaAgentClientDisconnect(self.handle.as_ptr()) };
178        common::check_bool(ret)
179    }
180
181    /// Check if currently connected to AgentServer.
182    pub fn connected(&self) -> bool {
183        unsafe { sys::MaaAgentClientConnected(self.handle.as_ptr()) != 0 }
184    }
185
186    /// Check if the connection is alive.
187    pub fn alive(&self) -> bool {
188        unsafe { sys::MaaAgentClientAlive(self.handle.as_ptr()) != 0 }
189    }
190
191    /// Set the connection timeout.
192    ///
193    /// # Arguments
194    /// * `milliseconds` - Timeout in milliseconds
195    pub fn set_timeout(&self, milliseconds: i64) -> MaaResult<()> {
196        let ret = unsafe { sys::MaaAgentClientSetTimeout(self.handle.as_ptr(), milliseconds) };
197        common::check_bool(ret)
198    }
199
200    /// Get the list of custom recognitions available on the AgentServer.
201    pub fn custom_recognition_list(&self) -> MaaResult<Vec<String>> {
202        let buffer = buffer::MaaStringListBuffer::new()?;
203        let ret = unsafe {
204            sys::MaaAgentClientGetCustomRecognitionList(self.handle.as_ptr(), buffer.raw())
205        };
206        if ret != 0 {
207            Ok(buffer.to_vec())
208        } else {
209            Err(MaaError::FrameworkError(0))
210        }
211    }
212
213    /// Get the list of custom actions available on the AgentServer.
214    pub fn custom_action_list(&self) -> MaaResult<Vec<String>> {
215        let buffer = buffer::MaaStringListBuffer::new()?;
216        let ret =
217            unsafe { sys::MaaAgentClientGetCustomActionList(self.handle.as_ptr(), buffer.raw()) };
218        if ret != 0 {
219            Ok(buffer.to_vec())
220        } else {
221            Err(MaaError::FrameworkError(0))
222        }
223    }
224
225    /// Get the raw handle pointer.
226    pub fn raw(&self) -> *mut sys::MaaAgentClient {
227        self.handle.as_ptr()
228    }
229}
230
231impl Drop for AgentClient {
232    fn drop(&mut self) {
233        unsafe { sys::MaaAgentClientDestroy(self.handle.as_ptr()) }
234    }
235}