1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
//! 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()) }
}
}