Expand description
Simple library to listen and send events to keyboard and mouse on MacOS, Windows and Linux(x11).
You can also check outEnigo which is anothercrate which helped me write this one.
This crate is so far a pet project for me to understand the rust ecosystem.
§Listening to global events
userdev::{listen, Event};// This will block.if letErr(error) = listen(callback) {println!("Error: {:?}", error)}fncallback(event: Event) {println!("My callback {:?}", event);matchevent.name {Some(string) =>println!("User wrote {:?}", string),None=> (), }}
§OS Caveats:
When using thelisten
function, the following caveats apply:
§Mac OS
The process running the blockinglisten
function (loop) needs to be the parent process (no fork before).The process needs to be granted access to the Accessibility API (ie. if you’re running your processinside Terminal.app, then Terminal.app needs to be added inSystem Preferences > Security & Privacy > Privacy > Accessibility)If the process is not granted access to the Accessibility API, MacOS will silently ignore rdev’slisten
calleback and will not trigger it with events. No error will be generated.
§Linux
Thelisten
function uses X11 APIs, and so will not work in Wayland or in the linux kernel virtual console
§Sending some events
userdev::{simulate, Button, EventType, Key, SimulateError};usestd::{thread, time};fnsend(event_type:&EventType) {letdelay = time::Duration::from_millis(20);matchsimulate(event_type) {Ok(()) => (),Err(SimulateError) => {println!("We could not send {:?}", event_type); } }// Let ths OS catchup (at least MacOS)thread::sleep(delay);}send(&EventType::KeyPress(Key::KeyS));send(&EventType::KeyRelease(Key::KeyS));send(&EventType::MouseMove { x:0.0, y:0.0});send(&EventType::MouseMove { x:400.0, y:400.0});send(&EventType::ButtonPress(Button::Left));send(&EventType::ButtonRelease(Button::Right));send(&EventType::Wheel { delta_x:0, delta_y:1,});
§Main structs
§Event
In order to detect what a user types, we need to plug to the OS level managementof keyboard state (modifiers like shift, ctrl, but also dead keys if they exist).
EventType
corresponds to aphysical event, corresponding to QWERTY layoutEvent
corresponds to an actual event that was received andEvent.name
reflectswhat key was interpreted by the OS at that time, it will respect the layout.
/// When events arrive from the system we can add some information/// time is when the event was received.#[derive(Debug)]pub structEvent {pubtime: SystemTime,pubname:Option<String>,pubevent_type: EventType,}
Be careful, Event::name, might be None, but also String::from(“”), and might containnot displayable unicode characters. We send exactly what the OS sends us so do some sanity checkingbefore using it.Caveat: Dead keys don’t function yet on Linux
§EventType
In order to manage different OS, the current EventType choices is a mix&matchto account for all possible events.There is a safe mechanism to detect events no matter what, which are theUnknown() variant of the enum which will contain some OS specific value.Also not that not all keys are mapped to an OS code, so simulate might fail if youtry to send an unmapped key. Sending Unknown() variants will always work (the OS mightstill reject it).
/// In order to manage different OS, the current EventType choices is a mix&match/// to account for all possible events.#[derive(Debug)]pub enumEventType {/// The keys correspond to a standard qwerty layout, they don't correspond /// To the actual letter a user would use, that requires some layout logic to be added.KeyPress(Key), KeyRelease(Key),/// Some mouse will have more than 3 buttons, these are not defined, and different OS will /// give different Unknown code.ButtonPress(Button), ButtonRelease(Button),/// Values in pixelsMouseMove { x: f64, y: f64, },/// Note: On Linux, there is no actual delta the actual values are ignored for delta_x /// and we only look at the sign of delta_y to simulate wheelup or wheeldown.Wheel { delta_x: i64, delta_y: i64, },}
§Getting the main screen size
userdev::{display_size};let(w, h) = display_size().unwrap();assert!(w >0);assert!(h >0);
§Keyboard state
We can define a dummy Keyboard, that we will use to detectwhat kind of EventType trigger some String. We get the currently usedlayout for now !Caveat : This is layout dependent. If your app needs to supportlayout switching don’t use this !Caveat: On Linux, the dead keys mechanism is not implemented.Caveat: Only shift and dead keys are implemented, Alt+unicode code on windowswon’t work.
userdev::{Keyboard, EventType, Key, KeyboardState};letmutkeyboard = Keyboard::new().unwrap();letstring = keyboard.add(&EventType::KeyPress(Key::KeyS));// string == Some("s")
§Grabbing global events. (Requiresunstable_grab
feature)
Installing this library with theunstable_grab
feature adds thegrab
functionwhich hooks into the global input device event stream.by suppling this function with a callback, you can interceptall keyboard and mouse events before they are delivered to applications / window managers.In the callback, returning None ignores the event and returning the event let’s it pass.There is no modification of the event possible here (yet).
Note: the use of the wordunstable
here refers specifically to the fact that thegrab
API is unstable and subject to change
#[cfg(feature ="unstable_grab")]userdev::{grab, Event, EventType, Key};#[cfg(feature ="unstable_grab")]letcallback = |event: Event| ->Option<Event> {if letEventType::KeyPress(Key::CapsLock) = event.event_type {println!("Consuming and cancelling CapsLock");None// CapsLock is now effectively disabled}else{Some(event) }};// This will block.#[cfg(feature ="unstable_grab")]if letErr(error) = grab(callback) {println!("Error: {:?}", error)}
§OS Caveats:
When using thelisten
and/orgrab
functions, the following caveats apply:
§Mac OS
The process running the blockinggrab
function (loop) needs to be the parent process (no fork before).The process needs to be granted access to the Accessibility API (ie. if you’re running your processinside Terminal.app, then Terminal.app needs to be added inSystem Preferences > Security & Privacy > Privacy > Accessibility)If the process is not granted access to the Accessibility API, thegrab
call will fail with anEventTapError (at least in MacOS 10.15, possibly other versions as well)
§Linux
Thegrab
function use theevdev
library to intercept events, so they will work with both X11 and WaylandIn order for this to work, the process runnign thelisten
orgrab
loop needs to either run as root (not recommended),or run as a user who’s a member of theinput
group (recommended)Note: on some distros, the group name for evdev access is calledplugdev
, and on some systems, both groups can exist.When in doubt, add your user to both groups if they exist.
§Serialization
Event data returned by thelisten
andgrab
functions can be serialized and de-serialized withSerde if you install this library with theserialize
feature.
Structs§
- Event
- When events arrive from the OS they get some additional information added fromEventType, which is the time when this event was received, and the name Optionwhich contains what characters should be emmitted from that event. This relieson the OS layout and keyboard state machinery.Caveat: Dead keys don’t function on Linux(X11) yet. You will receive None fora dead key, and the raw letter instead of accentuated letter.
- Keyboard
- Simulate
Error - Marking an error when we tried to simulate and event
Enums§
- Button
- Standard mouse buttonsSome mice have more than 3 buttons. These are not defined, and differentOSs will give different
Button::Unknown
values. - Display
Error - Errors that occur when trying to get display size.
- Event
Type - In order to manage different OSs, the current EventType choices are a mix andmatch to account for all possible events.
- Grab
Error - Errors that occur when trying to grab OS events.Be careful on Mac, not setting accessibility does not cause an errorit justs ignores events.
- Key
- Key names based on physical location on the deviceMerge Option(MacOS) and Alt(Windows, Linux) into AltMerge Windows (Windows), Meta(Linux), Command(MacOS) into MetaCharacters based on Qwerty layout, don’t use this for characters as it WILLdepend on the layout. Use Event.name instead. Key modifiers gives those keysa different value too.Careful, on Windows KpReturn does not exist, it’ s strictly equivalent to Return, also Keypad keysget modified if NumLock is Off and ARE pagedown and so on.
- Listen
Error - Errors that occur when trying to capture OS events.Be careful on Mac, not setting accessibility does not cause an errorit justs ignores events.
Traits§
- Keyboard
State - We can define a dummy Keyboard, that we will use to detectwhat kind of EventType trigger some String. We get the currently usedlayout for now !Caveat : This is layout dependent. If your app needs to supportlayout switching don’t use this !Caveat: On Linux, the dead keys mechanism is not implemented.Caveat: Only shift and dead keys are implemented, Alt+unicode code on windowswon’t work.
Functions§
- display_
size - Returns the size in pixels of the main screen.This is useful to use with x, y from MouseMove Event.
- listen
- Listening to global events. Caveat: On MacOS, you require the listenloop needs to be the primary app (no fork before) and need to have accessibilitysettings enabled.
- simulate
- Sending some events
Type Aliases§
- Grab
Callback - Callback type to send to grab function.