Skip to content

Commit 858152d

Browse files
authored
Adding keyboard state accessible for Linux. (#48)
* Adding keyboard state accessible for Linux. * Adding windows version. * Forgot fmt. * Adding MacOS. - Reworked KeyboardState to trait - Keyboard is now the struct (won't be documented) that initializes the trait. - In the future Keyboard should be able to implement any kind of layout which should help with testing. For now testing depends on the current layout to work (errors messages should at least convey that !) * cargo fmt. * Renamed keyboard_state.rs to keyboard.rs * A bit of clippy and fix windows grab. * Small fixes to windows. * Last fix from windows grab? * New minor version (Keyboard capabilities).
1 parent 2a82c3c commit 858152d

File tree

19 files changed

+519
-128
lines changed

19 files changed

+519
-128
lines changed

Cargo.toml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "rdev"
3-
version = "0.3.6"
3+
version = "0.4.0"
44
authors = ["Nicolas Patry <[email protected]>"]
55
edition = "2018"
66

@@ -21,6 +21,7 @@ serialize = ["serde"]
2121
unstable_grab = []
2222

2323
[target.'cfg(target_os = "macos")'.dependencies]
24+
lazy_static = "1.4.0"
2425
cocoa = "0.20"
2526
core-graphics = {version = "0.19.0", features = ["highsierra"]}
2627
core-foundation = {version = "0.7"}
@@ -33,6 +34,7 @@ x11 = {version = "2.18", features = ["xlib", "xrecord", "xinput"]}
3334
xproto = "1.1"
3435

3536
[target.'cfg(target_os = "windows")'.dependencies]
37+
lazy_static = "1.4.0"
3638
winapi = { version = "0.3", features = ["winuser", "errhandlingapi", "processthreadsapi"] }
3739

3840
[dev-dependencies]

README.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,25 @@ fn main() {
7777
}
7878
```
7979

80+
### Keyboard state
81+
82+
We can define a dummy Keyboard, that we will use to detect
83+
what kind of EventType trigger some String. We get the currently used
84+
layout for now !
85+
Caveat : This is layout dependent. If your app needs to support
86+
layout switching don't use this !
87+
Caveat: On Linux, the dead keys mechanism is not implemented.
88+
Caveat: Only shift and dead keys are implemented, Alt+unicode code on windows
89+
won't work.
90+
91+
```rust
92+
use rdev::{Keyboard, EventType, Key, KeyboardState};
93+
94+
let mut keyboard = Keyboard::new().unwrap();
95+
let string = keyboard.add(&EventType::KeyPress(Key::KeyS));
96+
// string == Some("s")
97+
```
98+
8099
### Grabbing global events.
81100

82101
In the callback, returning None ignores the event

examples/keyboard_state.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
use rdev::{EventType, Key, Keyboard, KeyboardState};
2+
3+
fn main() {
4+
let mut keyboard = Keyboard::new().unwrap();
5+
let char_s = keyboard.add(&EventType::KeyPress(Key::KeyS)).unwrap();
6+
assert_eq!(char_s, "s".to_string());
7+
println!("Pressing S gives: {:?}", char_s);
8+
let n = keyboard.add(&EventType::KeyRelease(Key::KeyS));
9+
assert_eq!(n, None);
10+
11+
keyboard.add(&EventType::KeyPress(Key::ShiftLeft));
12+
let char_s = keyboard.add(&EventType::KeyPress(Key::KeyS)).unwrap();
13+
println!("Pressing Shift+S gives: {:?}", char_s);
14+
assert_eq!(char_s, "S".to_string());
15+
let n = keyboard.add(&EventType::KeyRelease(Key::KeyS));
16+
assert_eq!(n, None);
17+
keyboard.add(&EventType::KeyRelease(Key::ShiftLeft));
18+
}

src/lib.rs

Lines changed: 72 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,25 @@
6767
//! assert!(h > 0);
6868
//! ```
6969
//!
70+
//! ### Keyboard state
71+
//!
72+
//! We can define a dummy Keyboard, that we will use to detect
73+
//! what kind of EventType trigger some String. We get the currently used
74+
//! layout for now !
75+
//! Caveat : This is layout dependent. If your app needs to support
76+
//! layout switching don't use this !
77+
//! Caveat: On Linux, the dead keys mechanism is not implemented.
78+
//! Caveat: Only shift and dead keys are implemented, Alt+unicode code on windows
79+
//! won't work.
80+
//!
81+
//! ```no_run
82+
//! use rdev::{Keyboard, EventType, Key, KeyboardState};
83+
//!
84+
//! let mut keyboard = Keyboard::new().unwrap();
85+
//! let string = keyboard.add(&EventType::KeyPress(Key::KeyS));
86+
//! // string == Some("s")
87+
//! ```
88+
//!
7089
//! ### Grabbing global events. (Requires `unstable_grab` feature)
7190
//!
7291
//! In the callback, returning None ignores the event
@@ -82,24 +101,28 @@
82101
//! Serialization and deserialization. (Requires `serialize` feature).
83102
mod rdev;
84103
pub use crate::rdev::{
85-
Button, Callback, DisplayError, Event, EventType, GrabCallback, GrabError, Key, ListenError,
86-
SimulateError,
104+
Button, Callback, DisplayError, Event, EventType, GrabCallback, GrabError, Key, KeyboardState,
105+
ListenError, SimulateError,
87106
};
88107

89108
#[cfg(target_os = "macos")]
90109
mod macos;
91110
#[cfg(target_os = "macos")]
111+
pub use crate::macos::Keyboard;
112+
#[cfg(target_os = "macos")]
92113
use crate::macos::{display_size as _display_size, listen as _listen, simulate as _simulate};
93114

94115
#[cfg(target_os = "linux")]
95116
mod linux;
96-
117+
#[cfg(target_os = "linux")]
118+
pub use crate::linux::Keyboard;
97119
#[cfg(target_os = "linux")]
98120
use crate::linux::{display_size as _display_size, listen as _listen, simulate as _simulate};
99121

100122
#[cfg(target_os = "windows")]
101123
mod windows;
102-
124+
#[cfg(target_os = "windows")]
125+
pub use crate::windows::Keyboard;
103126
#[cfg(target_os = "windows")]
104127
use crate::windows::{display_size as _display_size, listen as _listen, simulate as _simulate};
105128

@@ -216,3 +239,48 @@ pub use crate::windows::grab as _grab;
216239
pub fn grab(callback: GrabCallback) -> Result<(), GrabError> {
217240
_grab(callback)
218241
}
242+
243+
#[cfg(test)]
244+
mod tests {
245+
use super::*;
246+
247+
#[test]
248+
fn test_keyboard_state() {
249+
// S
250+
let mut keyboard = Keyboard::new().unwrap();
251+
let char_s = keyboard.add(&EventType::KeyPress(Key::KeyS)).unwrap();
252+
assert_eq!(
253+
char_s,
254+
"s".to_string(),
255+
"This test should pass only on Qwerty layout !"
256+
);
257+
let n = keyboard.add(&EventType::KeyRelease(Key::KeyS));
258+
assert_eq!(n, None);
259+
260+
// Shift + S
261+
keyboard.add(&EventType::KeyPress(Key::ShiftLeft));
262+
let char_s = keyboard.add(&EventType::KeyPress(Key::KeyS)).unwrap();
263+
assert_eq!(char_s, "S".to_string());
264+
let n = keyboard.add(&EventType::KeyRelease(Key::KeyS));
265+
assert_eq!(n, None);
266+
keyboard.add(&EventType::KeyRelease(Key::ShiftLeft));
267+
268+
// Reset
269+
keyboard.add(&EventType::KeyPress(Key::ShiftLeft));
270+
keyboard.reset();
271+
let char_s = keyboard.add(&EventType::KeyPress(Key::KeyS)).unwrap();
272+
assert_eq!(char_s, "s".to_string());
273+
let n = keyboard.add(&EventType::KeyRelease(Key::KeyS));
274+
assert_eq!(n, None);
275+
keyboard.add(&EventType::KeyRelease(Key::ShiftLeft));
276+
277+
// UsIntl layout required
278+
// let n = keyboard.add(&EventType::KeyPress(Key::Quote));
279+
// assert_eq!(n, Some("".to_string()));
280+
// let m = keyboard.add(&EventType::KeyRelease(Key::Quote));
281+
// assert_eq!(m, None);
282+
// let e = keyboard.add(&EventType::KeyPress(Key::KeyE)).unwrap();
283+
// assert_eq!(e, "é".to_string());
284+
// keyboard.add(&EventType::KeyRelease(Key::KeyE));
285+
}
286+
}

src/linux/common.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use crate::linux::keyboard_state::KeyboardState;
1+
use crate::linux::keyboard::Keyboard;
22
use crate::linux::keycodes::key_from_code;
33
use crate::rdev::{Button, Event, EventType};
44
use std::convert::TryInto;
@@ -55,7 +55,7 @@ pub fn convert(code: c_uint, state: c_uint, type_: c_int, x: f64, y: f64) -> Opt
5555
let event_type = convert_event(code as c_uchar, type_, x, y)?;
5656
let name = match event_type {
5757
EventType::KeyPress(_) => {
58-
KeyboardState::new().map(|mut kboard| kboard.name_from_code(code, state))
58+
Keyboard::new().map(|mut kboard| kboard.name_from_code(code, state))
5959
}
6060
_ => None,
6161
}

src/linux/keyboard_state.rs renamed to src/linux/keyboard.rs

Lines changed: 88 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,75 @@
11
extern crate x11;
2+
use crate::linux::keycodes::code_from_key;
3+
use crate::rdev::{EventType, Key, KeyboardState};
24
use std::ffi::CString;
35
use std::os::raw::{c_char, c_int, c_uchar, c_uint, c_void};
46
use std::ptr::{null, null_mut, NonNull};
57
use x11::xlib;
68

9+
#[derive(Debug)]
10+
struct State {
11+
alt: bool,
12+
ctrl: bool,
13+
caps_lock: bool,
14+
shift: bool,
15+
meta: bool,
16+
}
17+
718
// Inspired from https://github.com/wavexx/screenkey
819
// But without remitting events to custom windows, instead we recreate XKeyEvent
920
// from xEvent data received via xrecord.
1021
// Other source of inspiration https://gist.github.com/baines/5a49f1334281b2685af5dcae81a6fa8a
1122
// Needed xproto crate as x11 does not implement _xevent.
23+
impl State {
24+
fn new() -> State {
25+
State {
26+
alt: false,
27+
ctrl: false,
28+
caps_lock: false,
29+
meta: false,
30+
shift: false,
31+
}
32+
}
33+
34+
fn value(&self) -> c_uint {
35+
let mut res: c_uint = 0;
36+
if self.alt {
37+
res += xlib::Mod1Mask;
38+
}
39+
if self.ctrl {
40+
res += xlib::ControlMask;
41+
}
42+
if self.caps_lock {
43+
res += xlib::LockMask;
44+
}
45+
if self.meta {
46+
res += xlib::Mod4Mask;
47+
}
48+
if self.shift {
49+
res += xlib::ShiftMask;
50+
}
51+
res
52+
}
53+
}
54+
1255
#[derive(Debug)]
13-
pub struct KeyboardState {
56+
pub struct Keyboard {
1457
pub xic: Box<xlib::XIC>,
1558
pub display: Box<*mut xlib::Display>,
1659
keysym: Box<u64>,
1760
status: Box<i32>,
61+
state: State,
1862
}
19-
impl Drop for KeyboardState {
63+
impl Drop for Keyboard {
2064
fn drop(&mut self) {
2165
unsafe {
2266
xlib::XCloseDisplay(*self.display);
2367
}
2468
}
2569
}
2670

27-
impl KeyboardState {
28-
pub fn new() -> Option<KeyboardState> {
71+
impl Keyboard {
72+
pub fn new() -> Option<Keyboard> {
2973
unsafe {
3074
let dpy = xlib::XOpenDisplay(null());
3175
if dpy.is_null() {
@@ -81,16 +125,21 @@ impl KeyboardState {
81125
null::<c_void>(),
82126
);
83127
NonNull::new(xic)?;
84-
Some(KeyboardState {
128+
Some(Keyboard {
85129
xic: Box::new(xic),
86130
display: Box::new(dpy),
87131
keysym: Box::new(0),
88132
status: Box::new(0),
133+
state: State::new(),
89134
})
90135
}
91136
}
92137

93-
pub unsafe fn name_from_code(&mut self, keycode: c_uint, state: c_uint) -> Option<String> {
138+
pub(crate) unsafe fn name_from_code(
139+
&mut self,
140+
keycode: c_uint,
141+
state: c_uint,
142+
) -> Option<String> {
94143
if self.display.is_null() || self.xic.is_null() {
95144
println!("We don't seem to have a display or a xic");
96145
return None;
@@ -127,3 +176,36 @@ impl KeyboardState {
127176
String::from_utf8(buf[..len].to_vec()).ok()
128177
}
129178
}
179+
180+
impl KeyboardState for Keyboard {
181+
fn add(&mut self, event_type: &EventType) -> Option<String> {
182+
match event_type {
183+
EventType::KeyPress(key) => match key {
184+
Key::ShiftLeft | Key::ShiftRight => {
185+
self.state.shift = true;
186+
None
187+
}
188+
Key::CapsLock => {
189+
self.state.caps_lock = !self.state.caps_lock;
190+
None
191+
}
192+
key => {
193+
let keycode = code_from_key(*key)?;
194+
let state = self.state.value();
195+
unsafe { self.name_from_code(keycode, state) }
196+
}
197+
},
198+
EventType::KeyRelease(key) => match key {
199+
Key::ShiftLeft | Key::ShiftRight => {
200+
self.state.shift = false;
201+
None
202+
}
203+
_ => None,
204+
},
205+
_ => None,
206+
}
207+
}
208+
fn reset(&mut self) {
209+
self.state = State::new();
210+
}
211+
}

src/linux/mod.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,14 @@ mod common;
55
mod display;
66
#[cfg(feature = "unstable_grab")]
77
mod grab;
8-
mod keyboard_state;
8+
mod keyboard;
99
mod keycodes;
1010
mod listen;
1111
mod simulate;
1212

1313
pub use crate::linux::display::display_size;
1414
#[cfg(feature = "unstable_grab")]
1515
pub use crate::linux::grab::grab;
16+
pub use crate::linux::keyboard::Keyboard;
1617
pub use crate::linux::listen::listen;
1718
pub use crate::linux::simulate::simulate;

src/macos/common.rs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
1-
use crate::macos::keyboard_state::KeyboardState;
1+
use crate::macos::keyboard::Keyboard;
22
use crate::rdev::{Button, Event, EventType};
33
use cocoa::base::id;
44
use core_graphics::event::{CGEvent, CGEventFlags, CGEventTapLocation, CGEventType, EventField};
5+
use lazy_static::lazy_static;
56
use std::convert::TryInto;
67
use std::os::raw::c_void;
8+
use std::sync::Mutex;
79
use std::time::SystemTime;
810

911
use crate::macos::keycodes::key_from_code;
@@ -32,7 +34,9 @@ pub enum CGEventTapOption {
3234
}
3335

3436
pub static mut LAST_FLAGS: CGEventFlags = CGEventFlags::CGEventFlagNull;
35-
pub static mut KEYBOARD_STATE: KeyboardState = KeyboardState { dead_state: 0 };
37+
lazy_static! {
38+
pub static ref KEYBOARD_STATE: Mutex<Keyboard> = Mutex::new(Keyboard::new().unwrap());
39+
}
3640

3741
// https://developer.apple.com/documentation/coregraphics/cgeventmask?language=objc
3842
pub type CGEventMask = u64;
@@ -84,7 +88,7 @@ pub type QCallback = unsafe extern "C" fn(
8488
pub unsafe fn convert(
8589
_type: CGEventType,
8690
cg_event: &CGEvent,
87-
keyboard_state: &mut KeyboardState,
91+
keyboard_state: &mut Keyboard,
8892
) -> Option<Event> {
8993
let option_type = match _type {
9094
CGEventType::LeftMouseDown => Some(EventType::ButtonPress(Button::Left)),

src/macos/grab.rs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,12 @@ unsafe extern "C" fn raw_callback(
1919
) -> CGEventRef {
2020
// println!("Event ref {:?}", cg_event_ptr);
2121
// let cg_event: CGEvent = transmute_copy::<*mut c_void, CGEvent>(&cg_event_ptr);
22-
if let Some(event) = convert(_type, &cg_event, &mut KEYBOARD_STATE) {
23-
if GLOBAL_CALLBACK(event).is_none() {
24-
cg_event.set_type(CGEventType::Null);
22+
let opt = KEYBOARD_STATE.lock();
23+
if let Ok(mut keyboard) = opt {
24+
if let Some(event) = convert(_type, &cg_event, &mut keyboard) {
25+
if GLOBAL_CALLBACK(event).is_none() {
26+
cg_event.set_type(CGEventType::Null);
27+
}
2528
}
2629
}
2730
cg_event

0 commit comments

Comments
 (0)