inputflow_native/
lib.rs

1//! Example plugin library.
2//!
3//! This plugin crate will not be known to the user, both parties will interact with the help of
4//! the shared plugin API.
5
6use enigo::{Button, Direction, Enigo, Keyboard, Mouse, Settings};
7use inputflow::prelude::*;
8
9#[derive(Default)]
10struct NativePluginRoot {
11    controller: InputFlowNative,
12}
13
14impl<'a> PluginInner<'a> for NativePluginRoot {
15    type BorrowedType = Fwd<&'a mut InputFlowNative>;
16
17    type OwnedType = InputFlowNative;
18    type OwnedTypeMut = InputFlowNative;
19
20    fn borrow_features(&'a mut self) -> Self::BorrowedType {
21        self.controller.forward_mut()
22    }
23
24    fn into_features(self) -> Self::OwnedType {
25        self.controller
26    }
27
28    fn mut_features(&'a mut self) -> &'a mut Self::OwnedTypeMut {
29        &mut self.controller
30    }
31}
32
33#[derive(Debug)]
34pub struct InputFlowNative {
35    enigo: Enigo,
36}
37
38impl Clone for InputFlowNative {
39    fn clone(&self) -> Self {
40        Self::default()
41    }
42}
43
44impl Loadable for InputFlowNative {
45    fn name(&self) -> abi_stable::std_types::RString {
46        "inputflow_native".into()
47    }
48
49    fn capabilities(&self) -> u8 {
50        IF_PLUGIN_HEAD.features.bits()
51    }
52}
53
54impl Default for InputFlowNative {
55    fn default() -> Self {
56        Self {
57            enigo: Enigo::new(&Settings {
58                release_keys_when_dropped: true,
59                ..Default::default()
60            })
61            .expect("setting up enigo"),
62        }
63    }
64}
65
66/// VK_LBUTTON	0x01	Left mouse button
67/// VK_RBUTTON	0x02	Right mouse button
68/// VK_CANCEL	0x03	Control-break processing
69/// VK_MBUTTON	0x04	Middle mouse button
70/// VK_XBUTTON1	0x05	X1 mouse button
71/// VK_XBUTTON2	0x06	X2 mouse button
72/// https://learn.microsoft.com/en-us/windows/win32/inputdev/virtual-key-codes
73/// Buttons align with windows VKEY spec, scroll is appended
74/// TODO: move this to the library crate so the interface is standadized for all plugins
75fn keycode_to_button(btn: MouseButton) -> Option<Button> {
76    match btn {
77        MouseButton::Left => Some(Button::Left),
78        MouseButton::Right => Some(Button::Right),
79        // MouseButton::UNUSED01 => None, // Reserved in vkey spec. Maybe we will use it for something?
80        MouseButton::Middle => Some(Button::Middle),
81        MouseButton::XButton1 => Some(Button::Back),
82        MouseButton::XButton2 => Some(Button::Forward),
83        MouseButton::ScrollUp => Some(Button::ScrollUp),
84        MouseButton::ScrollDown => Some(Button::ScrollDown),
85        MouseButton::ScrollLeft => Some(Button::ScrollLeft),
86        MouseButton::ScrollRight => Some(Button::ScrollRight),
87        _ => None,
88    }
89}
90
91// impl KeyboardWriter for InputFlowNative {
92//     #[doc = r" Sends keyboard press down event"]
93//     fn send_key_down(&mut self, key: KeyboardKey) -> Result<()> {
94//         todo!()
95//     }
96
97//     #[doc = r" Releases a key that was set to down previously"]
98//     fn send_key_up(&mut self, key: KeyboardKey) -> Result<()> {
99//         todo!()
100//     }
101
102//     #[doc = r" Presses a key and lets it go all in one for when users do not care about specific timings"]
103//     fn press_key(&mut self, key: KeyboardKey) -> Result<()> {
104//         todo!()
105//     }
106
107//     #[doc = r" clears all active pressed keys. Useful for cleaning up multiple keys presses in one go."]
108//     #[doc = r" Ensures that keyboard writer is set back into a neutral state."]
109//     fn clear_keys(&mut self) -> Result<()> {
110//         todo!()
111//     }
112// }
113
114impl MouseWriter for InputFlowNative {
115    #[doc = r" Sends mouse button press down event"]
116    fn send_button_down(&mut self, button: MouseButton) -> Result<()> {
117        if let Some(key) = keycode_to_button(button) {
118            self.enigo
119                .button(key, Direction::Press)
120                .map_err(|_| InputFlowError::SendError)
121        } else {
122            Err(InputFlowError::InvalidKey)
123        }
124    }
125
126    #[doc = r" Releases a mouse button that was set to down previously"]
127    fn send_button_up(&mut self, button: MouseButton) -> Result<()> {
128        if let Some(button) = keycode_to_button(button) {
129            self.enigo
130                .button(button, Direction::Release)
131                .map_err(|_| InputFlowError::SendError)
132        } else {
133            Err(InputFlowError::InvalidKey)
134        }
135    }
136
137    #[doc = r" Presses a  mouse button and lets it go all in one for when users do not care about specific timings"]
138    fn click_button(&mut self, button: MouseButton) -> Result<()> {
139        if let Some(button) = keycode_to_button(button) {
140            self.enigo
141                .button(button, Direction::Click)
142                .map_err(|_| InputFlowError::SendError)
143        } else {
144            Err(InputFlowError::InvalidKey)
145        }
146    }
147
148    #[doc = r" clears all active pressed  mouse buttons. Useful for cleaning up multiple mouse button presses in one go."]
149    #[doc = r" Ensures that mouse writer is set back into a neutral state."]
150    fn clear_buttons(&mut self) -> Result<()> {
151        let (held_keys, held_keycodes) = self.enigo.held();
152        for key in held_keys {
153            if self.enigo.key(key, Direction::Release).is_err() {
154                println!("WARN: unable to release {key:?}");
155            };
156        }
157        for keycode in held_keycodes {
158            if self.enigo.raw(keycode, Direction::Release).is_err() {
159                println!("WARN: unable to release {keycode:?}");
160            };
161        }
162        Ok(())
163    }
164
165    #[doc = r" Sends a mouse move command to move it x dpi-pixels horizontally, and y vertically"]
166    fn mouse_move_relative(&mut self, x: i32, y: i32) -> Result<()> {
167        self.enigo
168            .move_mouse(x, y, enigo::Coordinate::Rel)
169            .map_err(|_| InputFlowError::SendError)
170    }
171}
172
173// ================================================================================================================= 
174// =================================== CGlue Plugin init and Header definitions ====================================
175// ================================================================================================================= 
176
177cglue_impl_group!(InputFlowNative, ControllerFeatures,{MouseWriter}, {MouseWriter} );
178#[allow(improper_ctypes_definitions)] // the linter is being stupid and not noticing the repr(u8)
179extern "C" fn create_plugin(lib: &CArc<cglue::trait_group::c_void>, _args: *const std::ffi::c_char) -> Result<PluginInnerArcBox<'static>> {
180    // type_identity!();
181    Ok(trait_obj!((NativePluginRoot::default(), lib.clone()) as PluginInner))
182}
183
184#[no_mangle]
185pub static IF_PLUGIN_HEAD: PluginHeader = PluginHeader {
186    features: FeatureSupport::from_bits_retain(
187        FeatureSupport::WRITE_MOUSE.bits(),
188    ),
189    layout: ROOT_LAYOUT,
190    create: create_plugin,
191};