Skip to main content

openloaf_rdev/
lib.rs

1//! Simple library to listen and send events to keyboard and mouse on MacOS, Windows and Linux
2//! (x11).
3//!
4//! You can also check out [Enigo](https://github.com/Enigo-rs/Enigo) which is another
5//! crate which helped me write this one.
6//!
7//! This crate is so far a pet project for me to understand the rust ecosystem.
8//!
9//! # Listening to global events
10//!
11//! ```no_run
12//! use rdev::{listen, Event};
13//!
14//! // This will block.
15//! if let Err(error) = listen(callback) {
16//!     println!("Error: {:?}", error)
17//! }
18//!
19//! fn callback(event: Event) {
20//!     println!("My callback {:?}", event);
21//!     match event.name {
22//!         Some(string) => println!("User wrote {:?}", string),
23//!         None => (),
24//!     }
25//! }
26//! ```
27//!
28//! ## OS Caveats:
29//! When using the `listen` function, the following caveats apply:
30//!
31//! ## Mac OS
32//! The process running the blocking `listen` function (loop) needs to be the parent process (no fork before).
33//! The process needs to be granted access to the Accessibility API (ie. if you're running your process
34//! inside Terminal.app, then Terminal.app needs to be added in
35//! System Preferences > Security & Privacy > Privacy > Accessibility)
36//! If the process is not granted access to the Accessibility API, MacOS will silently ignore rdev's
37//! `listen` calleback and will not trigger it with events. No error will be generated.
38//!
39//! ## Linux
40//! The `listen` function uses X11 APIs, and so will not work in Wayland or in the linux kernel virtual console
41//!
42//! # Sending some events
43//!
44//! ```no_run
45//! use rdev::{simulate, Button, EventType, Key, SimulateError};
46//! use std::{thread, time};
47//!
48//! fn send(event_type: &EventType) {
49//!     let delay = time::Duration::from_millis(20);
50//!     match simulate(event_type) {
51//!         Ok(()) => (),
52//!         Err(SimulateError) => {
53//!             println!("We could not send {:?}", event_type);
54//!         }
55//!     }
56//!     // Let ths OS catchup (at least MacOS)
57//!     thread::sleep(delay);
58//! }
59//!
60//! send(&EventType::KeyPress(Key::KeyS));
61//! send(&EventType::KeyRelease(Key::KeyS));
62//!
63//! send(&EventType::MouseMove { x: 0.0, y: 0.0 });
64//! send(&EventType::MouseMove { x: 400.0, y: 400.0 });
65//! send(&EventType::ButtonPress(Button::Left));
66//! send(&EventType::ButtonRelease(Button::Right));
67//! send(&EventType::Wheel {
68//!     delta_x: 0,
69//!     delta_y: 1,
70//! });
71//! ```
72//! # Main structs
73//! ## Event
74//!
75//! In order to detect what a user types, we need to plug to the OS level management
76//! of keyboard state (modifiers like shift, ctrl, but also dead keys if they exist).
77//!
78//! `EventType` corresponds to a *physical* event, corresponding to QWERTY layout
79//! `Event` corresponds to an actual event that was received and `Event.name` reflects
80//! what key was interpreted by the OS at that time, it will respect the layout.
81//!
82//! ```no_run
83//! # use crate::rdev::EventType;
84//! # use std::time::SystemTime;
85//! /// When events arrive from the system we can add some information
86//! /// time is when the event was received.
87//! #[derive(Debug)]
88//! pub struct Event {
89//!     pub time: SystemTime,
90//!     pub name: Option<String>,
91//!     pub event_type: EventType,
92//! }
93//! ```
94//!
95//! Be careful, Event::name, might be None, but also String::from(""), and might contain
96//! not displayable unicode characters. We send exactly what the OS sends us so do some sanity checking
97//! before using it.
98//! Caveat: Dead keys don't function yet on Linux
99//!
100//! ## EventType
101//!
102//! In order to manage different OS, the current EventType choices is a mix&match
103//! to account for all possible events.
104//! There is a safe mechanism to detect events no matter what, which are the
105//! Unknown() variant of the enum which will contain some OS specific value.
106//! Also not that not all keys are mapped to an OS code, so simulate might fail if you
107//! try to send an unmapped key. Sending Unknown() variants will always work (the OS might
108//! still reject it).
109//!
110//! ```no_run
111//! # use crate::rdev::{Key, Button};
112//! /// In order to manage different OS, the current EventType choices is a mix&match
113//! /// to account for all possible events.
114//! #[derive(Debug)]
115//! pub enum EventType {
116//!     /// The keys correspond to a standard qwerty layout, they don't correspond
117//!     /// To the actual letter a user would use, that requires some layout logic to be added.
118//!     KeyPress(Key),
119//!     KeyRelease(Key),
120//!     /// Some mouse will have more than 3 buttons, these are not defined, and different OS will
121//!     /// give different Unknown code.
122//!     ButtonPress(Button),
123//!     ButtonRelease(Button),
124//!     /// Values in pixels
125//!     MouseMove {
126//!         x: f64,
127//!         y: f64,
128//!     },
129//!     /// Note: On Linux, there is no actual delta the actual values are ignored for delta_x
130//!     /// and we only look at the sign of delta_y to simulate wheelup or wheeldown.
131//!     Wheel {
132//!         delta_x: i64,
133//!         delta_y: i64,
134//!     },
135//! }
136//! ```
137//!
138//!
139//! # Getting the main screen size
140//!
141//! ```no_run
142//! use rdev::{display_size};
143//!
144//! let (w, h) = display_size().unwrap();
145//! assert!(w > 0);
146//! assert!(h > 0);
147//! ```
148//!
149//! # Keyboard state
150//!
151//! We can define a dummy Keyboard, that we will use to detect
152//! what kind of EventType trigger some String. We get the currently used
153//! layout for now !
154//! Caveat : This is layout dependent. If your app needs to support
155//! layout switching don't use this !
156//! Caveat: On Linux, the dead keys mechanism is not implemented.
157//! Caveat: Only shift and dead keys are implemented, Alt+unicode code on windows
158//! won't work.
159//!
160//! ```no_run
161//! use rdev::{Keyboard, EventType, Key, KeyboardState};
162//!
163//! let mut keyboard = Keyboard::new().unwrap();
164//! let string = keyboard.add(&EventType::KeyPress(Key::KeyS));
165//! // string == Some("s")
166//! ```
167//!
168//! # Grabbing global events. (Requires `unstable_grab` feature)
169//!
170//! Installing this library with the `unstable_grab` feature adds the `grab` function
171//! which hooks into the global input device event stream.
172//! by suppling this function with a callback, you can intercept
173//! all keyboard and mouse events before they are delivered to applications / window managers.
174//! In the callback, returning None ignores the event and returning the event let's it pass.
175//! There is no modification of the event possible here (yet).
176//!
177//! Note: the use of the word `unstable` here refers specifically to the fact that the `grab` API is unstable and subject to change
178//!
179//! ```no_run
180//! #[cfg(feature = "unstable_grab")]
181//! use rdev::{grab, Event, EventType, Key};
182//!
183//! #[cfg(feature = "unstable_grab")]
184//! let callback = |event: Event| -> Option<Event> {
185//!     if let EventType::KeyPress(Key::CapsLock) = event.event_type {
186//!         println!("Consuming and cancelling CapsLock");
187//!         None  // CapsLock is now effectively disabled
188//!     }
189//!     else { Some(event) }
190//! };
191//! // This will block.
192//! #[cfg(feature = "unstable_grab")]
193//! if let Err(error) = grab(callback) {
194//!     println!("Error: {:?}", error)
195//! }
196//! ```
197//!
198//! ## OS Caveats:
199//! When using the `listen` and/or `grab` functions, the following caveats apply:
200//!
201//! ### Mac OS
202//! The process running the blocking `grab` function (loop) needs to be the parent process (no fork before).
203//! The process needs to be granted access to the Accessibility API (ie. if you're running your process
204//! inside Terminal.app, then Terminal.app needs to be added in
205//! System Preferences > Security & Privacy > Privacy > Accessibility)
206//! If the process is not granted access to the Accessibility API, the `grab` call will fail with an
207//! EventTapError (at least in MacOS 10.15, possibly other versions as well)
208//!
209//! ### Linux
210//! The `grab` function use the `evdev` library to intercept events, so they will work with both X11 and Wayland
211//! In order for this to work, the process runnign the `listen` or `grab` loop needs to either run as root (not recommended),
212//! or run as a user who's a member of the `input` group (recommended)
213//! Note: on some distros, the group name for evdev access is called `plugdev`, and on some systems, both groups can exist.
214//! When in doubt, add your user to both groups if they exist.
215//!
216//! # Serialization
217//!
218//! Event data returned by the `listen` and `grab` functions can be serialized and de-serialized with
219//! Serde if you install this library with the `serialize` feature.
220mod rdev;
221pub use crate::rdev::{
222    Button, DisplayError, Event, EventType, GrabCallback, GrabError, Key, KeyCode, KeyboardState,
223    ListenError, RawKey, SimulateError,
224};
225
226mod keycodes;
227#[cfg(target_os = "linux")]
228mod linux;
229#[cfg(target_os = "macos")]
230mod macos;
231#[cfg(target_os = "windows")]
232mod windows;
233
234mod codes_conv;
235
236pub use crate::codes_conv::*;
237
238pub use keycodes::android::{
239    code_from_key as android_keycode_from_key, key_from_code as android_key_from_code,
240};
241pub use keycodes::linux::{
242    code_from_key as linux_keycode_from_key, key_from_code as linux_key_from_code,
243};
244pub use keycodes::macos::{
245    code_from_key as macos_keycode_from_key, key_from_code as macos_key_from_code,
246};
247pub use keycodes::usb_hid::{
248    code_from_key as usb_hid_keycode_from_key, key_from_code as usb_hid_key_from_code,
249};
250pub use keycodes::windows::{
251    code_from_key as win_code_from_key, code_from_key as win_keycode_from_key, get_win_codes,
252    get_win_key, key_from_code as win_key_from_keycode, key_from_scancode as win_key_from_scancode,
253    scancode_from_key as win_scancode_from_key,
254};
255pub use keycodes::chrome::{
256    code_from_key as chrome_keycode_from_key, key_from_code as chrome_key_from_code,
257};
258
259#[cfg(target_os = "macos")]
260pub use crate::keycodes::macos::{code_from_key, key_from_code, virtual_keycodes::*};
261#[cfg(target_os = "macos")]
262use crate::macos::{display_size as _display_size, listen as _listen, simulate as _simulate};
263#[cfg(target_os = "macos")]
264pub use crate::macos::{set_is_main_thread, Keyboard, VirtualInput};
265#[cfg(target_os = "macos")]
266pub use core_graphics::{event::CGEventTapLocation, event_source::CGEventSourceStateID};
267
268#[cfg(any(target_os = "android", target_os = "linux"))]
269pub use crate::keycodes::linux::{code_from_key, key_from_code};
270#[cfg(target_os = "linux")]
271use crate::linux::{display_size as _display_size, listen as _listen, simulate as _simulate};
272#[cfg(target_os = "linux")]
273pub use crate::linux::{simulate_char, simulate_unicode, Keyboard};
274
275#[cfg(target_os = "windows")]
276pub use crate::keycodes::windows::key_from_scancode;
277#[cfg(target_os = "windows")]
278pub use crate::windows::{
279    display_size as _display_size, get_modifier, listen as _listen, set_modifier,
280    simulate as _simulate, simulate_char, simulate_code, simulate_key_unicode, simulate_unicode,
281    simulate_unistr, vk_to_scancode, Keyboard,
282};
283
284pub use crate::rdev::UnicodeInfo;
285
286/// Listening to global events. Caveat: On MacOS, you require the listen
287/// loop needs to be the primary app (no fork before) and need to have accessibility
288/// settings enabled.
289///
290/// ```no_run
291/// use rdev::{listen, Event};
292///
293/// fn callback(event: Event) {
294///     println!("My callback {:?}", event);
295///     match event.name{
296///         Some(string) => println!("User wrote {:?}", string),
297///         None => ()
298///     }
299/// }
300/// fn main(){
301///     // This will block.
302///     if let Err(error) = listen(callback) {
303///         println!("Error: {:?}", error)
304///     }
305/// }
306/// ```
307#[cfg(not(any(target_os = "android", target_os = "ios")))]
308pub fn listen<T>(callback: T) -> Result<(), ListenError>
309where
310    T: FnMut(Event) + 'static,
311{
312    _listen(callback)
313}
314
315/// Sending some events
316///
317/// ```no_run
318/// use rdev::{simulate, Button, EventType, Key, SimulateError};
319/// use std::{thread, time};
320///
321/// fn send(event_type: &EventType) {
322///     let delay = time::Duration::from_millis(20);
323///     match simulate(event_type) {
324///         Ok(()) => (),
325///         Err(SimulateError) => {
326///             println!("We could not send {:?}", event_type);
327///         }
328///     }
329///     // Let ths OS catchup (at least MacOS)
330///     thread::sleep(delay);
331/// }
332///
333/// fn my_shortcut() {
334///     send(&EventType::KeyPress(Key::KeyS));
335///     send(&EventType::KeyRelease(Key::KeyS));
336///
337///     send(&EventType::MouseMove { x: 0.0, y: 0.0 });
338///     send(&EventType::MouseMove { x: 400.0, y: 400.0 });
339///     send(&EventType::ButtonPress(Button::Left));
340///     send(&EventType::ButtonRelease(Button::Right));
341///     send(&EventType::Wheel {
342///         delta_x: 0,
343///         delta_y: 1,
344///     });
345/// }
346/// ```
347#[cfg(not(any(target_os = "android", target_os = "ios")))]
348pub fn simulate(event_type: &EventType) -> Result<(), SimulateError> {
349    _simulate(event_type)
350}
351
352/// Returns the size in pixels of the main screen.
353/// This is useful to use with x, y from MouseMove Event.
354///
355/// ```no_run
356/// use rdev::{display_size};
357///
358/// let (w, h) = display_size().unwrap();
359/// println!("My screen size : {:?}x{:?}", w, h);
360/// ```
361#[cfg(not(any(target_os = "android", target_os = "ios")))]
362pub fn display_size() -> Result<(u64, u64), DisplayError> {
363    _display_size()
364}
365
366#[cfg(target_os = "linux")]
367pub use crate::linux::{
368    disable_grab, enable_grab, exit_grab_listen, is_grabbed, start_grab_listen,
369};
370#[cfg(target_os = "macos")]
371pub use crate::macos::set_keyboard_extra_info;
372#[cfg(target_os = "macos")]
373pub use crate::macos::set_mouse_extra_info;
374#[cfg(target_os = "macos")]
375pub use crate::macos::{exit_grab, grab as _grab, is_grabbed};
376#[cfg(target_os = "windows")]
377pub use crate::windows::set_keyboard_extra_info;
378#[cfg(target_os = "windows")]
379pub use crate::windows::set_mouse_extra_info;
380#[cfg(target_os = "windows")]
381pub use crate::windows::{exit_grab, grab as _grab, is_grabbed};
382#[cfg(target_os = "windows")]
383pub use crate::windows::{set_event_popup, set_get_key_unicode};
384
385/// Grabbing global events. In the callback, returning None ignores the event
386/// and returning the event let's it pass. There is no modification of the event
387/// possible here.
388/// Caveat: On MacOS, you require the grab
389/// loop needs to be the primary app (no fork before) and need to have accessibility
390/// settings enabled.
391/// On Linux, you need rw access to evdev devices in /etc/input/ (usually group membership in `input` group is enough)
392///
393/// ```no_run
394/// use rdev::{grab, Event, EventType, Key};
395///
396/// fn callback(event: Event) -> Option<Event> {
397///     println!("My callback {:?}", event);
398///     match event.event_type{
399///         EventType::KeyPress(Key::Tab) => None,
400///         _ => Some(event),
401///     }
402/// }
403/// fn main(){
404///     // This will block.
405///     if let Err(error) = grab(callback) {
406///         println!("Error: {:?}", error)
407///     }
408/// }
409/// ```
410#[cfg(not(any(target_os = "android", target_os = "ios", target_os = "linux")))]
411pub fn grab<T>(callback: T) -> Result<(), GrabError>
412where
413    T: Fn(Event) -> Option<Event> + 'static,
414{
415    _grab(callback)
416}
417
418#[cfg(not(any(target_os = "android", target_os = "ios")))]
419pub(crate) fn keyboard_only() -> bool {
420    !std::env::var("KEYBOARD_ONLY")
421        .unwrap_or_default()
422        .is_empty()
423}
424
425#[cfg(test)]
426mod tests {
427    // use super::*;
428
429    #[test]
430    fn test_keyboard_state() {
431        // // S
432        // let mut keyboard = Keyboard::new();
433        // let char_s = keyboard
434        //     .add(&EventType::KeyPress(Key::KeyS))
435        //     .unwrap()
436        //     .name
437        //     .unwrap();
438        // assert_eq!(
439        //     char_s,
440        //     "s".to_string(),
441        //     "This test should pass only on Qwerty layout !"
442        // );
443        // let n = keyboard.add(&EventType::KeyRelease(Key::KeyS));
444        // assert_eq!(n, None);
445
446        // // Shift + S
447        // keyboard.add(&EventType::KeyPress(Key::ShiftLeft));
448        // let char_s = keyboard
449        //     .add(&EventType::KeyPress(Key::KeyS))
450        //     .unwrap()
451        //     .name
452        //     .unwrap();
453        // assert_eq!(char_s, "S".to_string());
454        // let n = keyboard.add(&EventType::KeyRelease(Key::KeyS));
455        // assert_eq!(n, None);
456        // keyboard.add(&EventType::KeyRelease(Key::ShiftLeft));
457
458        // // Reset
459        // keyboard.add(&EventType::KeyPress(Key::ShiftLeft));
460        // let char_s = keyboard
461        //     .add(&EventType::KeyPress(Key::KeyS))
462        //     .unwrap()
463        //     .name
464        //     .unwrap();
465        // assert_eq!(char_s, "s".to_string());
466        // let n = keyboard.add(&EventType::KeyRelease(Key::KeyS));
467        // assert_eq!(n, None);
468        // keyboard.add(&EventType::KeyRelease(Key::ShiftLeft));
469
470        // UsIntl layout required
471        // let n = keyboard.add(&EventType::KeyPress(Key::Quote));
472        // assert_eq!(n, Some("".to_string()));
473        // let m = keyboard.add(&EventType::KeyRelease(Key::Quote));
474        // assert_eq!(m, None);
475        // let e = keyboard.add(&EventType::KeyPress(Key::KeyE)).unwrap();
476        // assert_eq!(e, "é".to_string());
477        // keyboard.add(&EventType::KeyRelease(Key::KeyE));
478    }
479}