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, CString};
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    fn relative_move(&self, _dx: i32, _dy: i32) -> bool {
64        false
65    }
66    fn shell(&self, _cmd: &str, _timeout: i64) -> Option<String> {
67        None
68    }
69    /// Set the controller to inactive state.
70    ///
71    /// For Win32 controllers, this restores window position (removes topmost) and unblocks user input.
72    /// For other controllers, this is a no-op that always succeeds.
73    fn inactive(&self) -> bool {
74        true
75    }
76    /// Get custom controller extra info as a JSON string.
77    ///
78    /// The returned JSON will be merged with base controller info.
79    /// Default implementation returns empty JSON object.
80    fn get_info(&self) -> String {
81        "{}".to_string()
82    }
83}
84
85type BoxedCallback = Box<dyn CustomControllerCallback>;
86
87// FFI trampolines
88unsafe extern "C" fn connect_trampoline(trans_arg: *mut c_void) -> sys::MaaBool {
89    let cb = unsafe { &*(trans_arg as *const BoxedCallback) };
90    if cb.connect() { 1 } else { 0 }
91}
92
93unsafe extern "C" fn connected_trampoline(trans_arg: *mut c_void) -> sys::MaaBool {
94    let cb = unsafe { &*(trans_arg as *const BoxedCallback) };
95    if cb.connected() { 1 } else { 0 }
96}
97
98unsafe extern "C" fn request_uuid_trampoline(
99    trans_arg: *mut c_void,
100    buffer: *mut sys::MaaStringBuffer,
101) -> sys::MaaBool {
102    let cb = unsafe { &*(trans_arg as *const BoxedCallback) };
103    if let Some(uuid) = cb.request_uuid() {
104        if let Ok(c_str) = std::ffi::CString::new(uuid) {
105            unsafe {
106                sys::MaaStringBufferSetEx(buffer, c_str.as_ptr(), c_str.as_bytes().len() as u64);
107            }
108            return 1;
109        }
110    }
111    0
112}
113
114unsafe extern "C" fn get_features_trampoline(trans_arg: *mut c_void) -> sys::MaaControllerFeature {
115    let cb = unsafe { &*(trans_arg as *const BoxedCallback) };
116    cb.get_features().bits()
117}
118
119unsafe extern "C" fn start_app_trampoline(
120    intent: *const std::os::raw::c_char,
121    trans_arg: *mut c_void,
122) -> sys::MaaBool {
123    let cb = unsafe { &*(trans_arg as *const BoxedCallback) };
124    let intent_str = if !intent.is_null() {
125        unsafe { CStr::from_ptr(intent).to_string_lossy() }
126    } else {
127        std::borrow::Cow::Borrowed("")
128    };
129    if cb.start_app(&intent_str) { 1 } else { 0 }
130}
131
132unsafe extern "C" fn stop_app_trampoline(
133    intent: *const std::os::raw::c_char,
134    trans_arg: *mut c_void,
135) -> sys::MaaBool {
136    let cb = unsafe { &*(trans_arg as *const BoxedCallback) };
137    let intent_str = if !intent.is_null() {
138        unsafe { CStr::from_ptr(intent).to_string_lossy() }
139    } else {
140        std::borrow::Cow::Borrowed("")
141    };
142    if cb.stop_app(&intent_str) { 1 } else { 0 }
143}
144
145unsafe extern "C" fn screencap_trampoline(
146    trans_arg: *mut c_void,
147    buffer: *mut sys::MaaImageBuffer,
148) -> sys::MaaBool {
149    let cb = unsafe { &*(trans_arg as *const BoxedCallback) };
150    if let Some(data) = cb.screencap() {
151        let ret = unsafe {
152            sys::MaaImageBufferSetEncoded(buffer, data.as_ptr() as *mut u8, data.len() as u64)
153        };
154        return ret;
155    }
156    0
157}
158
159unsafe extern "C" fn click_trampoline(x: i32, y: i32, trans_arg: *mut c_void) -> sys::MaaBool {
160    let cb = unsafe { &*(trans_arg as *const BoxedCallback) };
161    if cb.click(x, y) { 1 } else { 0 }
162}
163
164unsafe extern "C" fn swipe_trampoline(
165    x1: i32,
166    y1: i32,
167    x2: i32,
168    y2: i32,
169    duration: i32,
170    trans_arg: *mut c_void,
171) -> sys::MaaBool {
172    let cb = unsafe { &*(trans_arg as *const BoxedCallback) };
173    if cb.swipe(x1, y1, x2, y2, duration) {
174        1
175    } else {
176        0
177    }
178}
179
180unsafe extern "C" fn touch_down_trampoline(
181    contact: i32,
182    x: i32,
183    y: i32,
184    pressure: i32,
185    trans_arg: *mut c_void,
186) -> sys::MaaBool {
187    let cb = unsafe { &*(trans_arg as *const BoxedCallback) };
188    if cb.touch_down(contact, x, y, pressure) {
189        1
190    } else {
191        0
192    }
193}
194
195unsafe extern "C" fn touch_move_trampoline(
196    contact: i32,
197    x: i32,
198    y: i32,
199    pressure: i32,
200    trans_arg: *mut c_void,
201) -> sys::MaaBool {
202    let cb = unsafe { &*(trans_arg as *const BoxedCallback) };
203    if cb.touch_move(contact, x, y, pressure) {
204        1
205    } else {
206        0
207    }
208}
209
210unsafe extern "C" fn touch_up_trampoline(contact: i32, trans_arg: *mut c_void) -> sys::MaaBool {
211    let cb = unsafe { &*(trans_arg as *const BoxedCallback) };
212    if cb.touch_up(contact) { 1 } else { 0 }
213}
214
215unsafe extern "C" fn click_key_trampoline(keycode: i32, trans_arg: *mut c_void) -> sys::MaaBool {
216    let cb = unsafe { &*(trans_arg as *const BoxedCallback) };
217    if cb.click_key(keycode) { 1 } else { 0 }
218}
219
220unsafe extern "C" fn input_text_trampoline(
221    text: *const std::os::raw::c_char,
222    trans_arg: *mut c_void,
223) -> sys::MaaBool {
224    let cb = unsafe { &*(trans_arg as *const BoxedCallback) };
225    let text_str = if !text.is_null() {
226        unsafe { CStr::from_ptr(text).to_string_lossy() }
227    } else {
228        std::borrow::Cow::Borrowed("")
229    };
230    if cb.input_text(&text_str) { 1 } else { 0 }
231}
232
233unsafe extern "C" fn key_down_trampoline(keycode: i32, trans_arg: *mut c_void) -> sys::MaaBool {
234    let cb = unsafe { &*(trans_arg as *const BoxedCallback) };
235    if cb.key_down(keycode) { 1 } else { 0 }
236}
237
238unsafe extern "C" fn key_up_trampoline(keycode: i32, trans_arg: *mut c_void) -> sys::MaaBool {
239    let cb = unsafe { &*(trans_arg as *const BoxedCallback) };
240    if cb.key_up(keycode) { 1 } else { 0 }
241}
242
243unsafe extern "C" fn scroll_trampoline(dx: i32, dy: i32, trans_arg: *mut c_void) -> sys::MaaBool {
244    let cb = unsafe { &*(trans_arg as *const BoxedCallback) };
245    if cb.scroll(dx, dy) { 1 } else { 0 }
246}
247
248unsafe extern "C" fn relative_move_trampoline(
249    dx: i32,
250    dy: i32,
251    trans_arg: *mut c_void,
252) -> sys::MaaBool {
253    let cb = unsafe { &*(trans_arg as *const BoxedCallback) };
254    if cb.relative_move(dx, dy) { 1 } else { 0 }
255}
256
257unsafe extern "C" fn shell_trampoline(
258    cmd: *const std::os::raw::c_char,
259    timeout: i64,
260    trans_arg: *mut c_void,
261    buffer: *mut sys::MaaStringBuffer,
262) -> sys::MaaBool {
263    let cb = unsafe { &*(trans_arg as *const BoxedCallback) };
264    let cmd_str = if !cmd.is_null() {
265        unsafe { CStr::from_ptr(cmd).to_string_lossy() }
266    } else {
267        std::borrow::Cow::Borrowed("")
268    };
269
270    if let Some(output) = cb.shell(&cmd_str, timeout) {
271        if let Ok(c_str) = CString::new(output) {
272            unsafe {
273                sys::MaaStringBufferSetEx(buffer, c_str.as_ptr(), c_str.as_bytes().len() as u64);
274            }
275            return 1;
276        }
277    }
278    0
279}
280
281unsafe extern "C" fn inactive_trampoline(trans_arg: *mut c_void) -> sys::MaaBool {
282    let cb = unsafe { &*(trans_arg as *const BoxedCallback) };
283    if cb.inactive() { 1 } else { 0 }
284}
285
286unsafe extern "C" fn get_info_trampoline(
287    trans_arg: *mut c_void,
288    buffer: *mut sys::MaaStringBuffer,
289) -> sys::MaaBool {
290    let cb = unsafe { &*(trans_arg as *const BoxedCallback) };
291    let info = cb.get_info();
292    if let Ok(c_str) = std::ffi::CString::new(info) {
293        unsafe {
294            sys::MaaStringBufferSetEx(buffer, c_str.as_ptr(), c_str.as_bytes().len() as u64);
295        }
296        return 1;
297    }
298    0
299}
300
301pub fn create_custom_controller_callbacks() -> sys::MaaCustomControllerCallbacks {
302    sys::MaaCustomControllerCallbacks {
303        connect: Some(connect_trampoline),
304        connected: Some(connected_trampoline),
305        request_uuid: Some(request_uuid_trampoline),
306        get_features: Some(get_features_trampoline),
307        start_app: Some(start_app_trampoline),
308        stop_app: Some(stop_app_trampoline),
309        screencap: Some(screencap_trampoline),
310        click: Some(click_trampoline),
311        swipe: Some(swipe_trampoline),
312        touch_down: Some(touch_down_trampoline),
313        touch_move: Some(touch_move_trampoline),
314        touch_up: Some(touch_up_trampoline),
315        click_key: Some(click_key_trampoline),
316        input_text: Some(input_text_trampoline),
317        key_down: Some(key_down_trampoline),
318        key_up: Some(key_up_trampoline),
319        scroll: Some(scroll_trampoline),
320        relative_move: Some(relative_move_trampoline),
321        shell: Some(shell_trampoline),
322        inactive: Some(inactive_trampoline),
323        get_info: Some(get_info_trampoline),
324    }
325}
326
327static CALLBACKS: std::sync::OnceLock<sys::MaaCustomControllerCallbacks> =
328    std::sync::OnceLock::new();
329
330pub fn get_callbacks() -> &'static sys::MaaCustomControllerCallbacks {
331    CALLBACKS.get_or_init(create_custom_controller_callbacks)
332}