x11: Only access XIM from the event loop thread (#439)

XIM isn't thread-safe at all. Any call made to it from another thread will result in the
event loop freezing (this is why the old implementation of Drop for Window had that
problem).

XIM is now confined to one thread, and the existing API is maintained using channels. In
testing this with Alacritty, I initially thought the occasional slight lag on updating the
spot location was due to this change, but it's present without it as well.
This commit is contained in:
Francesca Frangipane 2018-04-05 14:58:10 -04:00 committed by GitHub
parent 7cd440135a
commit 1c4973d5b7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 162 additions and 115 deletions

View file

@ -298,3 +298,114 @@ pub unsafe fn lookup_utf8(
str::from_utf8(&buffer[..count as usize]).unwrap_or("").to_string()
}
// 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");
}
}