Skip to main content

openloaf_rdev/linux/
keyboard.rs

1extern crate x11;
2use crate::keycodes::linux::code_from_key;
3use crate::rdev::{EventType, KeyboardState, UnicodeInfo};
4use std::convert::TryInto;
5use std::ffi::{CStr, CString};
6use std::os::raw::{c_char, c_int, c_uint, c_ulong, c_void};
7use std::ptr::{null, null_mut, NonNull};
8use x11::xlib::{self, KeySym, XKeyEvent, XKeysymToString, XSupportsLocale};
9
10#[derive(Debug)]
11pub struct MyXIM(xlib::XIM);
12unsafe impl Sync for MyXIM {}
13unsafe impl Send for MyXIM {}
14
15#[derive(Debug)]
16pub struct MyXIC(xlib::XIC);
17unsafe impl Sync for MyXIC {}
18unsafe impl Send for MyXIC {}
19
20#[derive(Debug)]
21pub struct MyDisplay(*mut xlib::Display);
22unsafe impl Sync for MyDisplay {}
23unsafe impl Send for MyDisplay {}
24
25#[derive(Debug)]
26pub struct Keyboard {
27    pub xim: Box<MyXIM>,
28    pub xic: Box<MyXIC>,
29    pub display: Box<MyDisplay>,
30    window: Box<xlib::Window>,
31    keysym: Box<c_ulong>,
32    status: Box<i32>,
33    serial: c_ulong,
34}
35
36impl Drop for Keyboard {
37    fn drop(&mut self) {
38        unsafe {
39            let MyDisplay(display) = *self.display;
40            xlib::XCloseDisplay(display);
41        }
42    }
43}
44
45impl Keyboard {
46    pub fn new() -> Option<Keyboard> {
47        unsafe {
48            let dpy = xlib::XOpenDisplay(null());
49            if dpy.is_null() {
50                return None;
51            }
52            // https://stackoverflow.com/questions/18246848/get-utf-8-input-with-x11-display#
53            // Try system localle first
54            let string = CString::new("").ok()?;
55            libc::setlocale(libc::LC_ALL, string.as_ptr());
56            // If not supported try C.UTF-8
57            if XSupportsLocale() == 0 {
58                let string = CString::new("C.UTF-8").ok()?;
59                libc::setlocale(libc::LC_ALL, string.as_ptr());
60            }
61            if XSupportsLocale() == 0 {
62                let string = CString::new("C").ok()?;
63                libc::setlocale(libc::LC_ALL, string.as_ptr());
64            }
65            let string = CString::new("@im=none").ok()?;
66            let ret = xlib::XSetLocaleModifiers(string.as_ptr());
67            NonNull::new(ret)?;
68
69            let xim = xlib::XOpenIM(dpy, null_mut(), null_mut(), null_mut());
70            NonNull::new(xim)?;
71
72            let mut win_attr = xlib::XSetWindowAttributes {
73                background_pixel: 0,
74                background_pixmap: 0,
75                border_pixel: 0,
76                border_pixmap: 0,
77                bit_gravity: 0,
78                win_gravity: 0,
79                backing_store: 0,
80                backing_planes: 0,
81                backing_pixel: 0,
82                event_mask: 0,
83                save_under: 0,
84                do_not_propagate_mask: 0,
85                override_redirect: 0,
86                colormap: 0,
87                cursor: 0,
88            };
89
90            let window = xlib::XCreateWindow(
91                dpy,
92                xlib::XDefaultRootWindow(dpy),
93                0,
94                0,
95                1,
96                1,
97                0,
98                xlib::CopyFromParent,
99                xlib::InputOnly as c_uint,
100                null_mut(),
101                xlib::CWOverrideRedirect,
102                &mut win_attr,
103            );
104
105            let input_style = CString::new(xlib::XNInputStyle).ok()?;
106            let window_client = CString::new(xlib::XNClientWindow).ok()?;
107            let style = xlib::XIMPreeditNothing | xlib::XIMStatusNothing;
108
109            let xic = xlib::XCreateIC(
110                xim,
111                input_style.as_ptr(),
112                style,
113                window_client.as_ptr(),
114                window,
115                null::<c_void>(),
116            );
117            NonNull::new(xic)?;
118
119            xlib::XSetICFocus(xic);
120
121            Some(Keyboard {
122                xim: Box::new(MyXIM(xim)),
123                xic: Box::new(MyXIC(xic)),
124                display: Box::new(MyDisplay(dpy)),
125                window: Box::new(window),
126                keysym: Box::new(0),
127                status: Box::new(0),
128                serial: 0,
129            })
130        }
131    }
132
133    pub(crate) unsafe fn get_current_modifiers(&mut self) -> Option<u32> {
134        let MyDisplay(display) = *self.display;
135        let screen_number = xlib::XDefaultScreen(display);
136        let screen = xlib::XScreenOfDisplay(display, screen_number);
137        let window = xlib::XRootWindowOfScreen(screen);
138        // Passing null pointers for the things we don't need results in a
139        // segfault.
140        let mut root_return: xlib::Window = 0;
141        let mut child_return: xlib::Window = 0;
142        let mut root_x_return = 0;
143        let mut root_y_return = 0;
144        let mut win_x_return = 0;
145        let mut win_y_return = 0;
146        let mut mask_return = 0;
147        xlib::XQueryPointer(
148            display,
149            window,
150            &mut root_return,
151            &mut child_return,
152            &mut root_x_return,
153            &mut root_y_return,
154            &mut win_x_return,
155            &mut win_y_return,
156            &mut mask_return,
157        );
158        Some(mask_return)
159    }
160
161    pub(crate) unsafe fn unicode_from_code(
162        &mut self,
163        keycode: c_uint,
164        state: c_uint,
165    ) -> Option<UnicodeInfo> {
166        let MyDisplay(display) = *self.display;
167        let MyXIC(xic) = *self.xic;
168        if display.is_null() || xic.is_null() {
169            println!("We don't seem to have a display or a xic");
170            return None;
171        }
172        const BUF_LEN: usize = 4;
173        let mut buf = [0_u8; BUF_LEN];
174        let MyDisplay(display) = *self.display;
175        let mut key = xlib::XKeyEvent {
176            display,
177            root: 0,
178            window: *self.window,
179            subwindow: 0,
180            x: 0,
181            y: 0,
182            x_root: 0,
183            y_root: 0,
184            state,
185            keycode,
186            same_screen: 0,
187            send_event: 0,
188            serial: self.serial,
189            type_: xlib::KeyPress,
190            time: xlib::CurrentTime,
191        };
192        self.serial += 1;
193
194        let mut event = xlib::XEvent { key };
195
196        // -----------------------------------------------------------------
197        // XXX: This is **OMEGA IMPORTANT** This is what enables us to receive
198        // the correct keyvalue from the utf8LookupString !!
199        // https://stackoverflow.com/questions/18246848/get-utf-8-input-with-x11-display#
200        // -----------------------------------------------------------------
201        xlib::XFilterEvent(&mut event, 0);
202
203        let MyXIC(xic) = *self.xic;
204        let ret = xlib::Xutf8LookupString(
205            xic,
206            &mut event.key,
207            buf.as_mut_ptr() as *mut c_char,
208            BUF_LEN as c_int,
209            &mut *self.keysym,
210            &mut *self.status,
211        );
212
213        let keysym = xlookup_string(&mut key);
214        self.keysym = Box::new(keysym);
215        if self.is_dead() {
216            return Some(UnicodeInfo {
217                name: None,
218                unicode: Vec::new(),
219                is_dead: true,
220            });
221        }
222        if ret == xlib::NoSymbol {
223            return None;
224        }
225
226        let len = buf.iter().position(|ch| ch == &0).unwrap_or(BUF_LEN);
227
228        // C0 controls
229        if len == 1 {
230            match String::from_utf8(buf[..len].to_vec()) {
231                Ok(s) => {
232                    if let Some(c) = s.chars().next() {
233                        if ('\u{1}'..='\u{1f}').contains(&c) {
234                            return None;
235                        }
236                    }
237                }
238                Err(_) => {}
239            }
240        }
241
242        Some(UnicodeInfo {
243            name: String::from_utf8(buf[..len].to_vec()).ok(),
244            unicode: Vec::new(),
245            is_dead: false,
246        })
247    }
248
249    pub fn is_dead(&mut self) -> bool {
250        let ptr = unsafe { XKeysymToString(*self.keysym) };
251        if ptr.is_null() {
252            false
253        } else {
254            let res = unsafe { CStr::from_ptr(ptr).to_str() };
255            res.unwrap_or_default().to_owned().starts_with("dead")
256        }
257    }
258
259    pub fn keysym(&self) -> u32 {
260        (*self.keysym).try_into().unwrap_or_default()
261    }
262}
263
264impl KeyboardState for Keyboard {
265    fn add(&mut self, event_type: &EventType) -> Option<UnicodeInfo> {
266        match event_type {
267            EventType::KeyPress(key) => {
268                let keycode = code_from_key(*key)?;
269                // let state = self.state.value();
270                let state = unsafe { self.get_current_modifiers().unwrap_or_default() };
271                // !!!: Igore Control
272                let state = state & 0xFFFB;
273                unsafe { self.unicode_from_code(keycode, state) }
274            }
275            EventType::KeyRelease(_key) => None,
276            _ => None,
277        }
278    }
279}
280
281/// refs:
282/// 1. https://github.com/mechpen/rterm/blob/b2d04defc13b5688bf75c5de72c0b8810f982dc1/src/x11_wrapper.rs#L357
283/// 2. https://github.com/freedesktop/xev/blob/a92082cb05bb3d6d3f0bebb951133774ca2dd412/xev.c#L125
284pub fn xlookup_string(event: &mut XKeyEvent) -> KeySym {
285    let mut buf = [0u8; 64];
286
287    let mut ksym: KeySym = 0;
288    let _len = unsafe {
289        xlib::XLookupString(
290            event,
291            buf.as_mut_ptr() as *mut _,
292            buf.len() as _,
293            &mut ksym,
294            null_mut(),
295        )
296    };
297    ksym
298}
299
300#[cfg(test)]
301mod tests {
302    use super::*;
303
304    #[test]
305    #[ignore]
306    /// If the following tests run, they *will* cause a crash because xlib
307    /// is *not* thread safe. Ignoring the tests for now.
308    /// XCB *could* be an option but not even sure we can get dead keys again.
309    /// XCB doc is sparse on the web let's say.
310    fn test_thread_safety() {
311        let mut keyboard = Keyboard::new().unwrap();
312        let char_s = keyboard
313            .add(&EventType::KeyPress(crate::rdev::Key::KeyS))
314            .unwrap()
315            .name
316            .unwrap();
317        assert_eq!(
318            char_s,
319            "s".to_string(),
320            "This test should pass only on Qwerty layout !"
321        );
322    }
323
324    #[test]
325    #[ignore]
326    fn test_thread_safety_2() {
327        let mut keyboard = Keyboard::new().unwrap();
328        let char_s = keyboard
329            .add(&EventType::KeyPress(crate::rdev::Key::KeyS))
330            .unwrap()
331            .name
332            .unwrap();
333        assert_eq!(
334            char_s,
335            "s".to_string(),
336            "This test should pass only on Qwerty layout !"
337        );
338    }
339}