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
This commit is contained in:
john01dav 2021-11-15 01:45:44 -06:00 committed by GitHub
parent 05cb97aeb0
commit 2b2540067c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 90 additions and 22 deletions

View file

@ -39,7 +39,7 @@ features = [
[features] [features]
default = ["wayland", "x11"] default = ["wayland", "x11"]
x11 = ["x11-dl", "xkb"] x11 = ["x11-dl", "xkb", "libc"]
wayland = ["wayland-client", "wayland-protocols", "wayland-cursor", "tempfile", "xkb", "xkbcommon-sys"] wayland = ["wayland-client", "wayland-protocols", "wayland-cursor", "tempfile", "xkb", "xkbcommon-sys"]
[target.'cfg(not(any(target_os = "macos", target_os = "redox", windows)))'.dependencies] [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} tempfile = {version = "3.2", optional = true}
xkb = {version = "0.2.1", optional = true} xkb = {version = "0.2.1", optional = true}
xkbcommon-sys = {version = "0.7.5", 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] [target.x86_64-unknown-redox.dependencies]
orbclient = "0.3.20" orbclient = "0.3.20"

View file

@ -33,7 +33,7 @@ fn main() {
WindowOptions::default(), WindowOptions::default(),
) )
.unwrap_or_else(|e| { .unwrap_or_else(|e| {
panic!("{}", e); panic!("{:?}", e);
}); });
let keys_data = KeyVec::new(RefCell::new(Vec::new())); let keys_data = KeyVec::new(RefCell::new(Vec::new()));
@ -55,7 +55,7 @@ fn main() {
let mut keys = keys_data.borrow_mut(); let mut keys = keys_data.borrow_mut();
for t in keys.iter() { for t in keys.iter() {
println!("keys {}", t); println!("Code point: {}, Character: {:?}", *t, char::from_u32(*t));
} }
keys.clear(); keys.clear();

View file

@ -12,16 +12,20 @@ use crate::Result;
use crate::{CursorStyle, MenuHandle, UnixMenu}; use crate::{CursorStyle, MenuHandle, UnixMenu};
use std::convert::TryFrom; use std::convert::TryFrom;
use std::ffi::CString; use std::ffi::{c_void, CStr, CString};
use std::mem; use std::mem;
use std::os::raw; 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 std::ptr;
use crate::buffer_helper; use crate::buffer_helper;
use crate::mouse_handler; use crate::mouse_handler;
use super::common::Menu; 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 // NOTE: the x11-dl crate does not define Button6 or Button7
const Button6: c_uint = xlib::Button5 + 1; const Button6: c_uint = xlib::Button5 + 1;
@ -113,6 +117,8 @@ impl DisplayInfo {
fn setup(transparency: bool) -> Result<DisplayInfo> { fn setup(transparency: bool) -> Result<DisplayInfo> {
unsafe { unsafe {
libc::setlocale(libc::LC_ALL, "\0".as_ptr() as *const c_char); //needed to make compose key work
let lib = xlib::Xlib::open() let lib = xlib::Xlib::open()
.map_err(|e| Error::WindowCreate(format!("failed to load Xlib: {:?}", e)))?; .map_err(|e| Error::WindowCreate(format!("failed to load Xlib: {:?}", e)))?;
@ -286,6 +292,9 @@ pub struct Window {
d: DisplayInfo, d: DisplayInfo,
handle: xlib::Window, handle: xlib::Window,
xim: XIM,
xic: XIC,
ximage: *mut xlib::XImage, ximage: *mut xlib::XImage,
draw_buffer: Vec<u32>, draw_buffer: Vec<u32>,
@ -384,6 +393,43 @@ impl Window {
&mut attributes, &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::<c_void>(),
);
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()); d.gc = (d.lib.XCreateGC)(d.display, handle, 0, ptr::null_mut());
if handle == 0 { if handle == 0 {
@ -461,6 +507,8 @@ impl Window {
Ok(Window { Ok(Window {
d, d,
handle, handle,
xim,
xic,
ximage, ximage,
draw_buffer, draw_buffer,
width: width as u32, width: width as u32,
@ -904,6 +952,12 @@ impl Window {
(self.d.lib.XNextEvent)(self.d.display, &mut event); (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 // Don't process any more messages if we hit a termination event
if self.raw_process_one_event(event) == ProcessEventResult::Termination { if self.raw_process_one_event(event) == ProcessEventResult::Termination {
return; 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! // FIXME: we cannot handle multiple windows here!
if ev.any.window != self.handle { if ev.any.window != self.handle {
return ProcessEventResult::Ok; return ProcessEventResult::Ok;
@ -930,6 +984,7 @@ impl Window {
xlib::KeyPress => { xlib::KeyPress => {
self.process_key(ev, true /* is_down */); self.process_key(ev, true /* is_down */);
self.emit_code_point_chars_to_callback(&mut ev.key);
} }
xlib::KeyRelease => { xlib::KeyRelease => {
@ -1008,24 +1063,34 @@ impl Window {
} }
self.update_key_state(sym, is_down); self.update_key_state(sym, is_down);
// unicode callback...
if !is_down {
return;
} }
let keysym = xkb::Keysym(sym as u32); fn emit_code_point_chars_to_callback(&mut self, event: &mut XKeyEvent) {
let code_point = keysym.utf32(); const BUFFER_SIZE: usize = 32;
// 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 { if let Some(callback) = &mut self.key_handler.key_callback {
callback.add_char(code_point); 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)]
};
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 ] // probably pointless ]
// XSaveContext(s_display, info->window, s_context, (XPointer)0); // 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); (self.d.lib.XDestroyWindow)(self.d.display, self.handle);
} }
} }