Skip to main content

maa_framework/
custom_controller.rs

1//! Custom controller implementation for user-defined device control.
2
3use crate::common::ControllerFeature;
4use crate::sys;
5use std::ffi::CStr;
6use std::os::raw::c_void;
7
8/// Custom controller callback trait for implementing custom device control.
9pub trait CustomControllerCallback: Send + Sync {
10    fn connect(&self) -> bool;
11    fn connected(&self) -> bool {
12        true
13    }
14    fn request_uuid(&self) -> Option<String> {
15        None
16    }
17    /// Get the features supported by this controller.
18    ///
19    /// Returns a combination of `ControllerFeature` flags.
20    fn get_features(&self) -> ControllerFeature {
21        ControllerFeature::empty()
22    }
23    fn start_app(&self, _intent: &str) -> bool {
24        false
25    }
26    fn stop_app(&self, _intent: &str) -> bool {
27        false
28    }
29    /// Returns PNG-encoded screenshot data.
30    fn screencap(&self) -> Option<Vec<u8>> {
31        None
32    }
33    fn click(&self, _x: i32, _y: i32) -> bool {
34        false
35    }
36    fn swipe(&self, _x1: i32, _y1: i32, _x2: i32, _y2: i32, _duration: i32) -> bool {
37        false
38    }
39    fn touch_down(&self, _contact: i32, _x: i32, _y: i32, _pressure: i32) -> bool {
40        false
41    }
42    fn touch_move(&self, _contact: i32, _x: i32, _y: i32, _pressure: i32) -> bool {
43        false
44    }
45    fn touch_up(&self, _contact: i32) -> bool {
46        false
47    }
48    fn click_key(&self, _keycode: i32) -> bool {
49        false
50    }
51    fn input_text(&self, _text: &str) -> bool {
52        false
53    }
54    fn key_down(&self, _keycode: i32) -> bool {
55        false
56    }
57    fn key_up(&self, _keycode: i32) -> bool {
58        false
59    }
60    fn scroll(&self, _dx: i32, _dy: i32) -> bool {
61        false
62    }
63    /// Set the controller to inactive state.
64    ///
65    /// For Win32 controllers, this restores window position (removes topmost) and unblocks user input.
66    /// For other controllers, this is a no-op that always succeeds.
67    fn inactive(&self) -> bool {
68        true
69    }
70}
71
72type BoxedCallback = Box<dyn CustomControllerCallback>;
73
74// FFI trampolines
75unsafe extern "C" fn connect_trampoline(trans_arg: *mut c_void) -> sys::MaaBool {
76    let cb = unsafe { &*(trans_arg as *const BoxedCallback) };
77    if cb.connect() {
78        1
79    } else {
80        0
81    }
82}
83
84unsafe extern "C" fn connected_trampoline(trans_arg: *mut c_void) -> sys::MaaBool {
85    let cb = unsafe { &*(trans_arg as *const BoxedCallback) };
86    if cb.connected() {
87        1
88    } else {
89        0
90    }
91}
92
93unsafe extern "C" fn request_uuid_trampoline(
94    trans_arg: *mut c_void,
95    buffer: *mut sys::MaaStringBuffer,
96) -> sys::MaaBool {
97    let cb = unsafe { &*(trans_arg as *const BoxedCallback) };
98    if let Some(uuid) = cb.request_uuid() {
99        if let Ok(c_str) = std::ffi::CString::new(uuid) {
100            unsafe { sys::MaaStringBufferSetEx(buffer, c_str.as_ptr(), c_str.as_bytes().len() as u64); }
101            return 1;
102        }
103    }
104    0
105}
106
107unsafe extern "C" fn get_features_trampoline(trans_arg: *mut c_void) -> sys::MaaControllerFeature {
108    let cb = unsafe { &*(trans_arg as *const BoxedCallback) };
109    cb.get_features().bits()
110}
111
112unsafe extern "C" fn start_app_trampoline(
113    intent: *const std::os::raw::c_char,
114    trans_arg: *mut c_void,
115) -> sys::MaaBool {
116    let cb = unsafe { &*(trans_arg as *const BoxedCallback) };
117    let intent_str = if !intent.is_null() {
118        unsafe { CStr::from_ptr(intent).to_string_lossy() }
119    } else {
120        std::borrow::Cow::Borrowed("")
121    };
122    if cb.start_app(&intent_str) {
123        1
124    } else {
125        0
126    }
127}
128
129unsafe extern "C" fn stop_app_trampoline(
130    intent: *const std::os::raw::c_char,
131    trans_arg: *mut c_void,
132) -> sys::MaaBool {
133    let cb = unsafe { &*(trans_arg as *const BoxedCallback) };
134    let intent_str = if !intent.is_null() {
135        unsafe { CStr::from_ptr(intent).to_string_lossy() }
136    } else {
137        std::borrow::Cow::Borrowed("")
138    };
139    if cb.stop_app(&intent_str) {
140        1
141    } else {
142        0
143    }
144}
145
146unsafe extern "C" fn screencap_trampoline(
147    trans_arg: *mut c_void,
148    buffer: *mut sys::MaaImageBuffer,
149) -> sys::MaaBool {
150    let cb = unsafe { &*(trans_arg as *const BoxedCallback) };
151    if let Some(data) = cb.screencap() {
152        let ret = unsafe {
153            sys::MaaImageBufferSetEncoded(buffer, data.as_ptr() as *mut u8, data.len() as u64)
154        };
155        return ret;
156    }
157    0
158}
159
160unsafe extern "C" fn click_trampoline(x: i32, y: i32, trans_arg: *mut c_void) -> sys::MaaBool {
161    let cb = unsafe { &*(trans_arg as *const BoxedCallback) };
162    if cb.click(x, y) {
163        1
164    } else {
165        0
166    }
167}
168
169unsafe extern "C" fn swipe_trampoline(
170    x1: i32,
171    y1: i32,
172    x2: i32,
173    y2: i32,
174    duration: i32,
175    trans_arg: *mut c_void,
176) -> sys::MaaBool {
177    let cb = unsafe { &*(trans_arg as *const BoxedCallback) };
178    if cb.swipe(x1, y1, x2, y2, duration) {
179        1
180    } else {
181        0
182    }
183}
184
185unsafe extern "C" fn touch_down_trampoline(
186    contact: i32,
187    x: i32,
188    y: i32,
189    pressure: i32,
190    trans_arg: *mut c_void,
191) -> sys::MaaBool {
192    let cb = unsafe { &*(trans_arg as *const BoxedCallback) };
193    if cb.touch_down(contact, x, y, pressure) {
194        1
195    } else {
196        0
197    }
198}
199
200unsafe extern "C" fn touch_move_trampoline(
201    contact: i32,
202    x: i32,
203    y: i32,
204    pressure: i32,
205    trans_arg: *mut c_void,
206) -> sys::MaaBool {
207    let cb = unsafe { &*(trans_arg as *const BoxedCallback) };
208    if cb.touch_move(contact, x, y, pressure) {
209        1
210    } else {
211        0
212    }
213}
214
215unsafe extern "C" fn touch_up_trampoline(contact: i32, trans_arg: *mut c_void) -> sys::MaaBool {
216    let cb = unsafe { &*(trans_arg as *const BoxedCallback) };
217    if cb.touch_up(contact) {
218        1
219    } else {
220        0
221    }
222}
223
224unsafe extern "C" fn click_key_trampoline(keycode: i32, trans_arg: *mut c_void) -> sys::MaaBool {
225    let cb = unsafe { &*(trans_arg as *const BoxedCallback) };
226    if cb.click_key(keycode) {
227        1
228    } else {
229        0
230    }
231}
232
233unsafe extern "C" fn input_text_trampoline(
234    text: *const std::os::raw::c_char,
235    trans_arg: *mut c_void,
236) -> sys::MaaBool {
237    let cb = unsafe { &*(trans_arg as *const BoxedCallback) };
238    let text_str = if !text.is_null() {
239        unsafe { CStr::from_ptr(text).to_string_lossy() }
240    } else {
241        std::borrow::Cow::Borrowed("")
242    };
243    if cb.input_text(&text_str) {
244        1
245    } else {
246        0
247    }
248}
249
250unsafe extern "C" fn key_down_trampoline(keycode: i32, trans_arg: *mut c_void) -> sys::MaaBool {
251    let cb = unsafe { &*(trans_arg as *const BoxedCallback) };
252    if cb.key_down(keycode) {
253        1
254    } else {
255        0
256    }
257}
258
259unsafe extern "C" fn key_up_trampoline(keycode: i32, trans_arg: *mut c_void) -> sys::MaaBool {
260    let cb = unsafe { &*(trans_arg as *const BoxedCallback) };
261    if cb.key_up(keycode) {
262        1
263    } else {
264        0
265    }
266}
267
268unsafe extern "C" fn scroll_trampoline(dx: i32, dy: i32, trans_arg: *mut c_void) -> sys::MaaBool {
269    let cb = unsafe { &*(trans_arg as *const BoxedCallback) };
270    if cb.scroll(dx, dy) {
271        1
272    } else {
273        0
274    }
275}
276
277unsafe extern "C" fn inactive_trampoline(trans_arg: *mut c_void) -> sys::MaaBool {
278    let cb = unsafe { &*(trans_arg as *const BoxedCallback) };
279    if cb.inactive() {
280        1
281    } else {
282        0
283    }
284}
285
286pub fn create_custom_controller_callbacks() -> sys::MaaCustomControllerCallbacks {
287    sys::MaaCustomControllerCallbacks {
288        connect: Some(connect_trampoline),
289        connected: Some(connected_trampoline),
290        request_uuid: Some(request_uuid_trampoline),
291        get_features: Some(get_features_trampoline),
292        start_app: Some(start_app_trampoline),
293        stop_app: Some(stop_app_trampoline),
294        screencap: Some(screencap_trampoline),
295        click: Some(click_trampoline),
296        swipe: Some(swipe_trampoline),
297        touch_down: Some(touch_down_trampoline),
298        touch_move: Some(touch_move_trampoline),
299        touch_up: Some(touch_up_trampoline),
300        click_key: Some(click_key_trampoline),
301        input_text: Some(input_text_trampoline),
302        key_down: Some(key_down_trampoline),
303        key_up: Some(key_up_trampoline),
304        scroll: Some(scroll_trampoline),
305        inactive: Some(inactive_trampoline),
306    }
307}
308
309static CALLBACKS: std::sync::OnceLock<sys::MaaCustomControllerCallbacks> =
310    std::sync::OnceLock::new();
311
312pub fn get_callbacks() -> &'static sys::MaaCustomControllerCallbacks {
313    CALLBACKS.get_or_init(create_custom_controller_callbacks)
314}