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