x11: Overhaul XIM code (#451)

Fixes #195
Fixes #277
Fixes #455

* Read `XMODIFIERS` explicitly/directly instead of calling `XSetLocaleModifiers` with an
empty string. This is useful for debugging purposes, and more clear to read and handle.
* Fallback to local input method if the one specified in `XMODIFIERS` is later closed on the
server end (i.e. if ibus/fcitx is terminated). Previously, that would cause the event loop
to freeze and usually also segfault.
* If using the fallback input method, respond to the `XMODIFIERS` input method later
becoming available. This means that the input method restarting is handled, and that even if
the program was started while ibus/fcitx/etc. was unavailable, it will start using it as
soon as it becomes available.
* Only one input method is opened for the whole event loop, with each window having its own
input context.
* IME works completely out of the box now, no longer requiring application developers to
call `setlocale` or `XSetLocaleModifiers`.
* Detailed error messages are provided if no input method could be opened. However, no
information is provided to the user if their intended `XMODIFIERS` input method failed to
open but the fallbacks (which will ostensibly always succeed) succeeded; in my opinion, this
is something that is best filled by adding a logging feature to winit.
This commit is contained in:
Francesca Frangipane 2018-04-10 22:18:30 -04:00 committed by GitHub
parent f08bf44670
commit 09c809003b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 880 additions and 137 deletions

View file

@ -90,7 +90,7 @@ pub unsafe fn send_client_msg(
xconn.check_errors().map(|_| ())
}
#[derive(Debug)]
#[derive(Debug, Clone)]
pub enum GetPropertyError {
XError(XError),
TypeMismatch(ffi::Atom),
@ -309,7 +309,6 @@ pub unsafe fn lookup_utf8(
str::from_utf8(&buffer[..count as usize]).unwrap_or("").to_string()
}
#[derive(Debug)]
pub struct FrameExtents {
pub left: c_ulong,
@ -364,114 +363,3 @@ impl WindowGeometry {
)
}
}
// Important: all XIM calls need to happen from the same thread!
pub struct Ime {
xconn: Arc<XConnection>,
pub im: ffi::XIM,
pub ic: ffi::XIC,
ic_spot: ffi::XPoint,
}
impl Ime {
pub fn new(xconn: Arc<XConnection>, window: ffi::Window) -> Option<Self> {
let im = unsafe {
let mut im: ffi::XIM = ptr::null_mut();
// Setting an empty string as the locale modifier results in the user's XMODIFIERS
// environment variable being read, which should result in the user's configured input
// method (ibus, fcitx, etc.) being used. If that fails, we fall back to internal
// input methods which should always be available, though only support compose keys.
for modifiers in &[b"\0" as &[u8], b"@im=local\0", b"@im=\0"] {
if !im.is_null() {
break;
}
(xconn.xlib.XSetLocaleModifiers)(modifiers.as_ptr() as *const _);
im = (xconn.xlib.XOpenIM)(
xconn.display,
ptr::null_mut(),
ptr::null_mut(),
ptr::null_mut(),
);
}
if im.is_null() {
return None;
}
im
};
let ic = unsafe {
let ic = (xconn.xlib.XCreateIC)(
im,
b"inputStyle\0".as_ptr() as *const _,
ffi::XIMPreeditNothing | ffi::XIMStatusNothing,
b"clientWindow\0".as_ptr() as *const _,
window,
ptr::null::<()>(),
);
if ic.is_null() {
return None;
}
(xconn.xlib.XSetICFocus)(ic);
xconn.check_errors().expect("Failed to call XSetICFocus");
ic
};
Some(Ime {
xconn,
im,
ic,
ic_spot: ffi::XPoint { x: 0, y: 0 },
})
}
pub fn focus(&self) -> Result<(), XError> {
unsafe {
(self.xconn.xlib.XSetICFocus)(self.ic);
}
self.xconn.check_errors()
}
pub fn unfocus(&self) -> Result<(), XError> {
unsafe {
(self.xconn.xlib.XUnsetICFocus)(self.ic);
}
self.xconn.check_errors()
}
pub fn send_xim_spot(&mut self, x: i16, y: i16) {
let nspot = ffi::XPoint { x: x as _, y: y as _ };
if self.ic_spot.x == x && self.ic_spot.y == y {
return;
}
self.ic_spot = nspot;
unsafe {
let preedit_attr = (self.xconn.xlib.XVaCreateNestedList)(
0,
b"spotLocation\0",
&nspot,
ptr::null::<()>(),
);
(self.xconn.xlib.XSetICValues)(
self.ic,
b"preeditAttributes\0",
preedit_attr,
ptr::null::<()>(),
);
(self.xconn.xlib.XFree)(preedit_attr);
}
}
}
impl Drop for Ime {
fn drop(&mut self) {
unsafe {
(self.xconn.xlib.XDestroyIC)(self.ic);
(self.xconn.xlib.XCloseIM)(self.im);
}
self.xconn.check_errors().expect("Failed to close input method");
}
}