From 2b2540067cc01e061e1716e9a43712eba463fd45 Mon Sep 17 00:00:00 2001 From: john01dav Date: Mon, 15 Nov 2021 01:45:44 -0600 Subject: [PATCH] Support typed keys on x11 * Added not-yet-working draft implementation of proper X11 support for reading correct typed characters * Made shift work for capital letters with X11, but compose key is still broken * Compose key and numpad now work correctly in X11 * XIM and XIC are now freed when a window destructor runs * Ran cargo fmt on x11.rs * Removed commented-out empty-string Closes #200 --- Cargo.toml | 5 +- examples/char_callback.rs | 4 +- src/os/posix/x11.rs | 103 +++++++++++++++++++++++++++++++------- 3 files changed, 90 insertions(+), 22 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 1ac674c..d773760 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -39,7 +39,7 @@ features = [ [features] default = ["wayland", "x11"] -x11 = ["x11-dl", "xkb"] +x11 = ["x11-dl", "xkb", "libc"] wayland = ["wayland-client", "wayland-protocols", "wayland-cursor", "tempfile", "xkb", "xkbcommon-sys"] [target.'cfg(not(any(target_os = "macos", target_os = "redox", windows)))'.dependencies] @@ -49,7 +49,8 @@ wayland-cursor = {version = "0.28", optional = true} tempfile = {version = "3.2", optional = true} xkb = {version = "0.2.1", optional = true} xkbcommon-sys = {version = "0.7.5", optional = true} -x11-dl = {version = "2.18.3", optional = true} +x11-dl = {version = "2.19.1", optional = true} +libc = {version = "0.2.107", optional = true} [target.x86_64-unknown-redox.dependencies] orbclient = "0.3.20" diff --git a/examples/char_callback.rs b/examples/char_callback.rs index bc5f06f..bee41df 100644 --- a/examples/char_callback.rs +++ b/examples/char_callback.rs @@ -33,7 +33,7 @@ fn main() { WindowOptions::default(), ) .unwrap_or_else(|e| { - panic!("{}", e); + panic!("{:?}", e); }); let keys_data = KeyVec::new(RefCell::new(Vec::new())); @@ -55,7 +55,7 @@ fn main() { let mut keys = keys_data.borrow_mut(); for t in keys.iter() { - println!("keys {}", t); + println!("Code point: {}, Character: {:?}", *t, char::from_u32(*t)); } keys.clear(); diff --git a/src/os/posix/x11.rs b/src/os/posix/x11.rs index 6c64200..c412788 100644 --- a/src/os/posix/x11.rs +++ b/src/os/posix/x11.rs @@ -12,16 +12,20 @@ use crate::Result; use crate::{CursorStyle, MenuHandle, UnixMenu}; use std::convert::TryFrom; -use std::ffi::CString; +use std::ffi::{c_void, CStr, CString}; use std::mem; use std::os::raw; -use std::os::raw::{c_char, c_long, c_uchar, c_uint, c_ulong}; +use std::os::raw::{c_char, c_int, c_long, c_uchar, c_uint, c_ulong}; use std::ptr; use crate::buffer_helper; use crate::mouse_handler; use super::common::Menu; +use x11_dl::xlib::{ + KeyPressMask, KeyReleaseMask, KeySym, Status, XEvent, XIMPreeditNothing, XIMStatusNothing, + XKeyEvent, XNClientWindow, XNFocusWindow, XNInputStyle, XrmDatabase, XIC, XIM, +}; // NOTE: the x11-dl crate does not define Button6 or Button7 const Button6: c_uint = xlib::Button5 + 1; @@ -113,6 +117,8 @@ impl DisplayInfo { fn setup(transparency: bool) -> Result { unsafe { + libc::setlocale(libc::LC_ALL, "\0".as_ptr() as *const c_char); //needed to make compose key work + let lib = xlib::Xlib::open() .map_err(|e| Error::WindowCreate(format!("failed to load Xlib: {:?}", e)))?; @@ -286,6 +292,9 @@ pub struct Window { d: DisplayInfo, handle: xlib::Window, + xim: XIM, + xic: XIC, + ximage: *mut xlib::XImage, draw_buffer: Vec, @@ -384,6 +393,43 @@ impl Window { &mut attributes, ); + let empty_string = b"\0"; + (d.lib.XSetLocaleModifiers)(empty_string.as_ptr() as *const i8); + + let xim = (d.lib.XOpenIM)( + d.display, + 0 as XrmDatabase, + 0 as *mut c_char, + 0 as *mut c_char, + ); + if (xim as usize) == 0 { + return Err(Error::WindowCreate( + "Failed to setup X IM via XOpenIM.".to_owned(), + )); + } + + let xn_input_style = CString::new(XNInputStyle).unwrap(); + let xn_client_window = CString::new(XNClientWindow).unwrap(); + let xn_focus_window = CString::new(XNFocusWindow).unwrap(); + let xic = (d.lib.XCreateIC)( + xim, + xn_input_style.as_ptr(), + XIMPreeditNothing | XIMStatusNothing, + xn_client_window.as_ptr(), + handle as c_ulong, + xn_focus_window.as_ptr(), + handle as c_ulong, + std::ptr::null_mut::(), + ); + if (xic as usize) == 0 { + return Err(Error::WindowCreate( + "Failed to setup X IC via XCreateIC.".to_owned(), + )); + } + + (d.lib.XSetICFocus)(xic); + (d.lib.XSelectInput)(d.display, handle, KeyPressMask | KeyReleaseMask); + d.gc = (d.lib.XCreateGC)(d.display, handle, 0, ptr::null_mut()); if handle == 0 { @@ -461,6 +507,8 @@ impl Window { Ok(Window { d, handle, + xim, + xic, ximage, draw_buffer, width: width as u32, @@ -904,6 +952,12 @@ impl Window { (self.d.lib.XNextEvent)(self.d.display, &mut event); + //skip any events that need to get eaten by X to do compose key, e.g. if the user types compose key + a + ' then all of these events need to get eaten and processed in xlib + //XFilterEvent will do the processing for these cases, and returns whether or not it handled an event + if (self.d.lib.XFilterEvent)(&mut event as *mut XEvent, 0) != 0 { + continue; + } + // Don't process any more messages if we hit a termination event if self.raw_process_one_event(event) == ProcessEventResult::Termination { return; @@ -911,7 +965,7 @@ impl Window { } } - unsafe fn raw_process_one_event(&mut self, ev: xlib::XEvent) -> ProcessEventResult { + unsafe fn raw_process_one_event(&mut self, mut ev: xlib::XEvent) -> ProcessEventResult { // FIXME: we cannot handle multiple windows here! if ev.any.window != self.handle { return ProcessEventResult::Ok; @@ -930,6 +984,7 @@ impl Window { xlib::KeyPress => { self.process_key(ev, true /* is_down */); + self.emit_code_point_chars_to_callback(&mut ev.key); } xlib::KeyRelease => { @@ -1008,24 +1063,34 @@ impl Window { } self.update_key_state(sym, is_down); + } - // unicode callback... + fn emit_code_point_chars_to_callback(&mut self, event: &mut XKeyEvent) { + const BUFFER_SIZE: usize = 32; - if !is_down { - return; - } + if let Some(callback) = &mut self.key_handler.key_callback { + let mut buff: [u8; BUFFER_SIZE] = [0; BUFFER_SIZE]; + let str = unsafe { + let mut keysym: KeySym = std::mem::zeroed(); + let mut status: Status = 0; + let length_in_bytes = (self.d.lib.Xutf8LookupString)( + self.xic, + event as *mut XKeyEvent, + buff.as_mut_ptr() as *mut c_char, + (BUFFER_SIZE - 1) as c_int, + (&mut keysym) as *mut KeySym, + (&mut status) as *mut Status, + ); + &buff[0..(length_in_bytes as usize + 1)] + }; - let keysym = xkb::Keysym(sym as u32); - let code_point = keysym.utf32(); - // Taken from GLFW - if code_point == 0 { - return; - } else if code_point < 32 || (code_point > 126 && code_point < 160) { - return; - } - - if let Some(ref mut callback) = self.key_handler.key_callback { - callback.add_char(code_point); + if let Ok(cstr) = CStr::from_bytes_with_nul(str) { + if let Ok(str) = cstr.to_str() { + for c in str.chars() { + callback.add_char(c as u32); + } + } + } } } @@ -1199,6 +1264,8 @@ impl Drop for Window { // probably pointless ] // XSaveContext(s_display, info->window, s_context, (XPointer)0); + (self.d.lib.XDestroyIC)(self.xic); + (self.d.lib.XCloseIM)(self.xim); (self.d.lib.XDestroyWindow)(self.d.display, self.handle); } }