extern crate x11;
use crate::linux::keycodes::code_from_key;
use crate::rdev::{EventType, Key, KeyboardState};
use std::ffi::CString;
use std::os::raw::{c_char, c_int, c_uchar, c_uint, c_void};
use std::ptr::{null, null_mut, NonNull};
use x11::xlib;
#[derive(Debug)]
struct State {
alt: bool,
ctrl: bool,
caps_lock: bool,
shift: bool,
meta: bool,
}
impl State {
fn new() -> State {
State {
alt: false,
ctrl: false,
caps_lock: false,
meta: false,
shift: false,
}
}
fn value(&self) -> c_uint {
let mut res: c_uint = 0;
if self.alt {
res += xlib::Mod1Mask;
}
if self.ctrl {
res += xlib::ControlMask;
}
if self.caps_lock {
res += xlib::LockMask;
}
if self.meta {
res += xlib::Mod4Mask;
}
if self.shift {
res += xlib::ShiftMask;
}
res
}
}
#[derive(Debug)]
pub struct Keyboard {
pub xim: Box<xlib::XIM>,
pub xic: Box<xlib::XIC>,
pub display: Box<*mut xlib::Display>,
window: Box<xlib::Window>,
keysym: Box<u64>,
status: Box<i32>,
state: State,
serial: u64,
}
impl Drop for Keyboard {
fn drop(&mut self) {
unsafe {
xlib::XCloseDisplay(*self.display);
}
}
}
impl Keyboard {
pub fn new() -> Option<Keyboard> {
unsafe {
let string = CString::new("@im=none").expect("Can't creat CString");
let ret = xlib::XSetLocaleModifiers(string.as_ptr());
NonNull::new(ret)?;
let dpy = xlib::XOpenDisplay(null());
if dpy.is_null() {
return None;
}
let xim = xlib::XOpenIM(dpy, null_mut(), null_mut(), null_mut());
NonNull::new(xim)?;
let mut win_attr = xlib::XSetWindowAttributes {
background_pixel: 0,
background_pixmap: 0,
border_pixel: 0,
border_pixmap: 0,
bit_gravity: 0,
win_gravity: 0,
backing_store: 0,
backing_planes: 0,
backing_pixel: 0,
event_mask: 0,
save_under: 0,
do_not_propagate_mask: 0,
override_redirect: 0,
colormap: 0,
cursor: 0,
};
let window = xlib::XCreateWindow(
dpy,
xlib::XDefaultRootWindow(dpy),
0,
0,
1,
1,
0,
xlib::CopyFromParent,
xlib::InputOnly as c_uint,
null_mut(),
xlib::CWOverrideRedirect,
&mut win_attr,
);
let input_style = CString::new(xlib::XNInputStyle).expect("CString::new failed");
let window_client = CString::new(xlib::XNClientWindow).expect("CString::new failed");
let style = xlib::XIMPreeditNothing | xlib::XIMStatusNothing;
let xic = xlib::XCreateIC(
xim,
window_client.as_ptr(),
window,
input_style.as_ptr(),
style,
null::<c_void>(),
);
NonNull::new(xic)?;
xlib::XSetICFocus(xic);
Some(Keyboard {
xim: Box::new(xim),
xic: Box::new(xic),
display: Box::new(dpy),
window: Box::new(window),
keysym: Box::new(0),
status: Box::new(0),
state: State::new(),
serial: 0,
})
}
}
pub(crate) unsafe fn name_from_code(
&mut self,
keycode: c_uint,
state: c_uint,
) -> Option<String> {
if self.display.is_null() || self.xic.is_null() {
println!("We don't seem to have a display or a xic");
return None;
}
const BUF_LEN: usize = 4;
let mut buf = [0 as c_uchar; BUF_LEN];
let key = xlib::XKeyEvent {
display: *self.display,
root: 0,
window: *self.window,
subwindow: 0,
x: 0,
y: 0,
x_root: 0,
y_root: 0,
state,
keycode,
same_screen: 0,
send_event: 0,
serial: self.serial,
type_: xlib::KeyPress,
time: xlib::CurrentTime,
};
self.serial += 1;
let mut event = xlib::XEvent { key };
xlib::XFilterEvent(&mut event, 0);
let ret = xlib::Xutf8LookupString(
*self.xic,
&mut event.key,
buf.as_mut_ptr() as *mut c_char,
BUF_LEN as c_int,
&mut *self.keysym,
&mut *self.status,
);
if ret == xlib::NoSymbol {
return None;
}
let len = buf.iter().position(|ch| ch == &0).unwrap_or(BUF_LEN);
String::from_utf8(buf[..len].to_vec()).ok()
}
}
impl KeyboardState for Keyboard {
fn add(&mut self, event_type: &EventType) -> Option<String> {
match event_type {
EventType::KeyPress(key) => match key {
Key::ShiftLeft | Key::ShiftRight => {
self.state.shift = true;
None
}
Key::CapsLock => {
self.state.caps_lock = !self.state.caps_lock;
None
}
key => {
let keycode = code_from_key(*key)?;
let state = self.state.value();
unsafe { self.name_from_code(keycode, state) }
}
},
EventType::KeyRelease(key) => match key {
Key::ShiftLeft | Key::ShiftRight => {
self.state.shift = false;
None
}
_ => None,
},
_ => None,
}
}
fn reset(&mut self) {
self.state = State::new();
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
#[ignore]
fn test_thread_safety() {
let mut keyboard = Keyboard::new().unwrap();
let char_s = keyboard.add(&EventType::KeyPress(Key::KeyS)).unwrap();
assert_eq!(
char_s,
"s".to_string(),
"This test should pass only on Qwerty layout !"
);
}
#[test]
#[ignore]
fn test_thread_safety_2() {
let mut keyboard = Keyboard::new().unwrap();
let char_s = keyboard.add(&EventType::KeyPress(Key::KeyS)).unwrap();
assert_eq!(
char_s,
"s".to_string(),
"This test should pass only on Qwerty layout !"
);
}
}