1use ::std::time::Duration;
5
6use dataview::PodMethods;
7use inputflow::prelude::*;
8use keycodes::KMBoxKeyboardKeyCode;
9use serialport::{SerialPort, SerialPortType, UsbPortInfo};
10use format_bytes::format_bytes;
11
12mod args;
13pub mod keycodes;
14
15struct KMBoxPluginRoot {
16 controller: InputFlowKMBox,
17}
18
19impl KMBoxPluginRoot {
20 pub fn new(args: args::Args) -> std::result::Result<Self, Box<dyn std::error::Error>> {
21 log::info!("Initializing KMBox plugin with config {}", ron::to_string(&args)?);
22
23 let mut port_path = args.com_port;
24
25 if args.auto_select {
26 let ports = serialport::available_ports()?;
27
28 for port in ports {
29 log::trace!("Found serial port {} : {:?}", port.port_name, port.port_type);
30
31 match port.port_type {
32 SerialPortType::UsbPort(UsbPortInfo{product: Some(product_name),..}) => {
33 if product_name.starts_with(&args.device_name) {
34 log::info!("Automatically loaded port {} from device {}", port.port_name, product_name);
35 port_path = port.port_name;
36 break;
37 }
38 },
39 _=> {}
40 }
41 }
42
43 }
44
45 Ok(KMBoxPluginRoot {
46 controller: InputFlowKMBox {
47 port: serialport::new(port_path, args.baud_rate)
48 .timeout(Duration::from_millis(args.timeout_ms))
49 .open()?,
50 }
51 })
52 }
53}
54
55impl<'a> PluginInner<'a> for KMBoxPluginRoot {
56 type BorrowedType = Fwd<&'a mut InputFlowKMBox>;
57
58 type OwnedType = InputFlowKMBox;
59 type OwnedTypeMut = InputFlowKMBox;
60
61 fn borrow_features(&'a mut self) -> Self::BorrowedType {
62 self.controller.forward_mut()
63 }
64
65 fn into_features(self) -> Self::OwnedType {
66 self.controller
67 }
68
69 fn mut_features(&'a mut self) -> &'a mut Self::OwnedTypeMut {
70 &mut self.controller
71 }
72}
73
74#[derive(Debug)]
75pub struct InputFlowKMBox {
76 pub port: Box<dyn SerialPort>,
77}
78
79impl InputFlowKMBox {
80
81 pub fn km_set_left(&mut self, is_down: i32) -> Result<()> {
85 let cmd = format_bytes!(b"km.left({})\r\n", is_down);
86
87 self.port.write(cmd.as_bytes()).map_err(|e| {
88 log::warn!("command km.left({is_down}) \"{cmd:?}\" failed: {e:?}.");
90 InputFlowError::SendError
92 })?;
93 Ok(())
94 }
95
96 pub fn km_set_key(&mut self, key: KeyboardKey, is_down: bool) -> Result<()> {
97 let km_key = KMBoxKeyboardKeyCode::try_from(key)?;
98
99 let cmd = if is_down {
100 format_bytes!(b"km.down({})\r\n", km_key)
101 } else {
102 format_bytes!(b"km.up({})\r\n", km_key)
103 };
104
105 self.port.write(cmd.as_bytes()).map_err(|e| {
106 log::warn!("command km.down/km.up \"{cmd:?}\" failed: {e:?}.");
108 InputFlowError::SendError
110 })?;
111 Ok(())
112 }
113
114 pub fn km_press_key(&mut self, key: KeyboardKey) -> Result<()> {
115 let km_key = KMBoxKeyboardKeyCode::try_from(key)?;
116
117 let cmd = format_bytes!(b"km.press({},15,50)\r\n", km_key);
119
120 self.port.write(cmd.as_bytes()).map_err(|e| {
121 log::warn!("command km.press({km_key}) \"{cmd:?}\" failed: {e:?}.");
123 InputFlowError::SendError
125 })?;
126 Ok(())
127 }
128}
129
130impl Loadable for InputFlowKMBox {
131 fn name(&self) -> abi_stable::std_types::RString {
132 "inputflow_kmbox".into()
133 }
134
135 fn capabilities(&self) -> u8 {
136 IF_PLUGIN_HEAD.features.bits()
137 }
138}
139
140impl KeyboardWriter for InputFlowKMBox {
141 #[doc = r"Sends keyboard press down event"]
142 fn send_key_down(&mut self, key: KeyboardKey) -> Result<()> {
143 self.km_set_key(key, true)
144 }
145
146 #[doc = r" Releases a key that was set to down previously"]
147 fn send_key_up(&mut self, key: KeyboardKey) -> Result<()> {
148 self.km_set_key(key, false)
149 }
150
151 #[doc = r" Presses a key and lets it go all in one for when users do not care about specific timings"]
152 fn press_key(&mut self, key: KeyboardKey) -> Result<()> {
153 self.km_press_key(key)
154 }
155
156 #[doc = r" clears all active pressed keys. Useful for cleaning up multiple keys presses in one go."]
157 #[doc = r" Ensures that keyboard writer is set back into a neutral state."]
158 fn clear_keys(&mut self) -> Result<()> {
159 log::info!("kmbox clear_keys not implemented yet...");
161 Ok(())
162 }
163}
164
165fn mouse_button_to_km(button: MouseButton) -> Option<u32> {
168 Some(match button {
169 MouseButton::Left => 0,
170 MouseButton::Right => 1,
171 MouseButton::Middle => 3,
172 MouseButton::XButton1 => 4,
173 MouseButton::XButton2 => 5,
174 _ => {
175 return None;
176 }
177 })
178}
179
180impl MouseWriter for InputFlowKMBox {
181 #[doc = r" Sends mouse button press down event"]
182 fn send_button_down(&mut self, button: MouseButton) -> Result<()> {
183 match button {
184 MouseButton::Left => {
185 self.km_set_left(1)
186 },
187 _=> {Err(InputFlowError::Parameter)}
188 }
189 }
190
191 #[doc = r" Releases a mouse button that was set to down previously"]
192 fn send_button_up(&mut self, button: MouseButton) -> Result<()> {
193 match button {
194 MouseButton::Left => {
195 self.km_set_left(0)
196 },
197 _=> {Err(InputFlowError::Parameter)}
198 }
199 }
200
201 #[doc = r" Presses a mouse button and lets it go all in one for when users do not care about specific timings"]
202 fn click_button(&mut self, button: MouseButton) -> Result<()> {
203
204 let Some(km_button) = mouse_button_to_km(button) else {
205 return Err(InputFlowError::InvalidKey);
206 };
207
208 let cmd = match button {
209 MouseButton::Left => {
210 format_bytes!(b"km.click({})\r\n", km_button)
211 },
212 _=> {return Err(InputFlowError::Parameter);}
213 };
214
215 self.port.write(cmd.as_bytes()).map_err(|e| {
217 log::warn!("command km.click({button:?}) \"{cmd:?}\" failed: {e:?}.");
219 InputFlowError::SendError
221 })?;
222
223 Ok(())
224 }
225
226 #[doc = r" clears all active pressed mouse buttons. Useful for cleaning up multiple mouse button presses in one go."]
227 #[doc = r" Ensures that mouse writer is set back into a neutral state."]
228 fn clear_buttons(&mut self) -> Result<()> {
229 Ok(())
230 }
231
232 #[doc = r" Sends a mouse move command to move it x dpi-pixels horizontally, and y vertically"]
233 fn mouse_move_relative(&mut self, x: i32, y: i32) -> Result<()> {
234 let cmd = format_bytes!(b"km.move({},{})\r\n", x,y);
235 self.port.write(cmd.as_bytes()).map_err(|e| {
236 log::warn!("command km.move({x},{y}) \"{cmd:?}\" failed: {e:?}.");
238 InputFlowError::SendError
240 })?;
241 Ok(())
242 }
243}
244
245cglue_impl_group!(InputFlowKMBox, ControllerFeatures,{KeyboardWriter, MouseWriter}, {KeyboardWriter, MouseWriter} );
250
251#[allow(improper_ctypes_definitions)] extern "C" fn create_plugin(lib: &CArc<cglue::trait_group::c_void>, args: *const std::ffi::c_char) -> Result<PluginInnerArcBox<'static>> {
254 env_logger::builder()
255 .init();
257 Ok(trait_obj!(
258 (
259 KMBoxPluginRoot::new(
260 args::parse_args(args).map_err(|e| {
261 log::error!("Invalid parameters were passed to inputflow_kmbox: {e:?}.");
262 InputFlowError::Parameter
263 })?
264 ).map_err(|e| {
265 log::error!("Failed to load KMBox device: {e:?}.");
266 InputFlowError::Loading
267 })?,
268 lib.clone()
269 ) as PluginInner
270 ))
271}
272
273#[no_mangle]
275pub static IF_PLUGIN_HEAD: PluginHeader = PluginHeader {
276 features: FeatureSupport::from_bits_retain(
277 FeatureSupport::WRITE_KEYBOARD.bits() | FeatureSupport::WRITE_MOUSE.bits(),
278 ),
279 layout: ROOT_LAYOUT,
280 create: create_plugin,
281};