mirror of
https://github.com/italicsjenga/winit-sonoma-fix.git
synced 2024-12-24 06:11:30 +11:00
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:
parent
f08bf44670
commit
09c809003b
|
@ -3,6 +3,7 @@
|
||||||
- Overhauled X11 window geometry calculations. `get_position` and `set_position` are more universally accurate across different window managers, and `get_outer_size` actually works now.
|
- Overhauled X11 window geometry calculations. `get_position` and `set_position` are more universally accurate across different window managers, and `get_outer_size` actually works now.
|
||||||
- Fixed SIGSEGV/SIGILL crashes on macOS caused by stabilization of the `!` (never) type.
|
- Fixed SIGSEGV/SIGILL crashes on macOS caused by stabilization of the `!` (never) type.
|
||||||
- Implement `WindowEvent::HiDPIFactorChanged` for macOS
|
- Implement `WindowEvent::HiDPIFactorChanged` for macOS
|
||||||
|
- On X11, input methods now work completely out of the box, no longer requiring application developers to manually call `setlocale`. Additionally, when input methods are started, stopped, or restarted on the server end, it's correctly handled.
|
||||||
|
|
||||||
# Version 0.12.0 (2018-04-06)
|
# Version 0.12.0 (2018-04-06)
|
||||||
|
|
||||||
|
|
185
src/platform/linux/x11/ime/callbacks.rs
Normal file
185
src/platform/linux/x11/ime/callbacks.rs
Normal file
|
@ -0,0 +1,185 @@
|
||||||
|
use std::ptr;
|
||||||
|
use std::sync::Arc;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::os::raw::c_char;
|
||||||
|
|
||||||
|
use super::{ffi, XConnection, XError};
|
||||||
|
|
||||||
|
use super::inner::{close_im, ImeInner};
|
||||||
|
use super::input_method::PotentialInputMethods;
|
||||||
|
use super::context::{ImeContextCreationError, ImeContext};
|
||||||
|
|
||||||
|
pub unsafe fn xim_set_callback(
|
||||||
|
xconn: &Arc<XConnection>,
|
||||||
|
xim: ffi::XIM,
|
||||||
|
field: *const c_char,
|
||||||
|
callback: *mut ffi::XIMCallback,
|
||||||
|
) -> Result<(), XError> {
|
||||||
|
// It's advisable to wrap variadic FFI functions in our own functions, as we want to minimize
|
||||||
|
// access that isn't type-checked.
|
||||||
|
(xconn.xlib.XSetIMValues)(
|
||||||
|
xim,
|
||||||
|
field,
|
||||||
|
callback,
|
||||||
|
ptr::null_mut::<()>(),
|
||||||
|
);
|
||||||
|
xconn.check_errors()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set a callback for when an input method matching the current locale modifiers becomes
|
||||||
|
// available. Note that this has nothing to do with what input methods are open or able to be
|
||||||
|
// opened, and simply uses the modifiers that are set when the callback is set.
|
||||||
|
// * This is called per locale modifier, not per input method opened with that locale modifier.
|
||||||
|
// * Trying to set this for multiple locale modifiers causes problems, i.e. one of the rebuilt
|
||||||
|
// input contexts would always silently fail to use the input method.
|
||||||
|
pub unsafe fn set_instantiate_callback(
|
||||||
|
xconn: &Arc<XConnection>,
|
||||||
|
client_data: ffi::XPointer,
|
||||||
|
) -> Result<(), XError> {
|
||||||
|
(xconn.xlib.XRegisterIMInstantiateCallback)(
|
||||||
|
xconn.display,
|
||||||
|
ptr::null_mut(),
|
||||||
|
ptr::null_mut(),
|
||||||
|
ptr::null_mut(),
|
||||||
|
Some(xim_instantiate_callback),
|
||||||
|
client_data,
|
||||||
|
);
|
||||||
|
xconn.check_errors()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub unsafe fn unset_instantiate_callback(
|
||||||
|
xconn: &Arc<XConnection>,
|
||||||
|
client_data: ffi::XPointer,
|
||||||
|
) -> Result<(), XError> {
|
||||||
|
(xconn.xlib.XUnregisterIMInstantiateCallback)(
|
||||||
|
xconn.display,
|
||||||
|
ptr::null_mut(),
|
||||||
|
ptr::null_mut(),
|
||||||
|
ptr::null_mut(),
|
||||||
|
Some(xim_instantiate_callback),
|
||||||
|
client_data,
|
||||||
|
);
|
||||||
|
xconn.check_errors()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub unsafe fn set_destroy_callback(
|
||||||
|
xconn: &Arc<XConnection>,
|
||||||
|
im: ffi::XIM,
|
||||||
|
inner: &ImeInner,
|
||||||
|
) -> Result<(), XError> {
|
||||||
|
xim_set_callback(
|
||||||
|
&xconn,
|
||||||
|
im,
|
||||||
|
ffi::XNDestroyCallback_0.as_ptr() as *const _,
|
||||||
|
&inner.destroy_callback as *const _ as *mut _,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
enum ReplaceImError {
|
||||||
|
MethodOpenFailed(PotentialInputMethods),
|
||||||
|
ContextCreationFailed(ImeContextCreationError),
|
||||||
|
SetDestroyCallbackFailed(XError),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attempt to replace current IM (which may or may not be presently valid) with a new one. This
|
||||||
|
// includes replacing all existing input contexts and free'ing resources as necessary. This only
|
||||||
|
// modifies existing state if all operations succeed.
|
||||||
|
unsafe fn replace_im(inner: *mut ImeInner) -> Result<(), ReplaceImError> {
|
||||||
|
let xconn = &(*inner).xconn;
|
||||||
|
|
||||||
|
let (new_im, is_fallback) = {
|
||||||
|
let new_im = (*inner).potential_input_methods.open_im(xconn, None);
|
||||||
|
let is_fallback = new_im.is_fallback();
|
||||||
|
(
|
||||||
|
new_im.ok().ok_or_else(|| {
|
||||||
|
ReplaceImError::MethodOpenFailed((*inner).potential_input_methods.clone())
|
||||||
|
})?,
|
||||||
|
is_fallback,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
// It's important to always set a destroy callback, since there's otherwise potential for us
|
||||||
|
// to try to use or free a resource that's already been destroyed on the server.
|
||||||
|
{
|
||||||
|
let result = set_destroy_callback(xconn, new_im.im, &*inner);
|
||||||
|
if result.is_err() {
|
||||||
|
let _ = close_im(xconn, new_im.im);
|
||||||
|
}
|
||||||
|
result
|
||||||
|
}.map_err(ReplaceImError::SetDestroyCallbackFailed)?;
|
||||||
|
|
||||||
|
let mut new_contexts = HashMap::new();
|
||||||
|
for (window, old_context) in (*inner).contexts.iter() {
|
||||||
|
let spot = old_context.as_ref().map(|old_context| old_context.ic_spot);
|
||||||
|
let new_context = {
|
||||||
|
let result = ImeContext::new(
|
||||||
|
xconn,
|
||||||
|
new_im.im,
|
||||||
|
*window,
|
||||||
|
spot,
|
||||||
|
);
|
||||||
|
if result.is_err() {
|
||||||
|
let _ = close_im(xconn, new_im.im);
|
||||||
|
}
|
||||||
|
result.map_err(ReplaceImError::ContextCreationFailed)?
|
||||||
|
};
|
||||||
|
new_contexts.insert(*window, Some(new_context));
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we've made it this far, everything succeeded.
|
||||||
|
let _ = (*inner).destroy_all_contexts_if_necessary();
|
||||||
|
let _ = (*inner).close_im_if_necessary();
|
||||||
|
(*inner).im = new_im.im;
|
||||||
|
(*inner).contexts = new_contexts;
|
||||||
|
(*inner).is_destroyed = false;
|
||||||
|
(*inner).is_fallback = is_fallback;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub unsafe extern fn xim_instantiate_callback(
|
||||||
|
_display: *mut ffi::Display,
|
||||||
|
client_data: ffi::XPointer,
|
||||||
|
// This field is unsupplied.
|
||||||
|
_call_data: ffi::XPointer,
|
||||||
|
) {
|
||||||
|
let inner: *mut ImeInner = client_data as _;
|
||||||
|
if !inner.is_null() {
|
||||||
|
let xconn = &(*inner).xconn;
|
||||||
|
let result = replace_im(inner);
|
||||||
|
if result.is_ok() {
|
||||||
|
let _ = unset_instantiate_callback(xconn, client_data);
|
||||||
|
(*inner).is_fallback = false;
|
||||||
|
} else if result.is_err() && (*inner).is_destroyed {
|
||||||
|
// We have no usable input methods!
|
||||||
|
result.expect("Failed to reopen input method");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// This callback is triggered when the input method is closed on the server end. When this
|
||||||
|
// happens, XCloseIM/XDestroyIC doesn't need to be called, as the resources have already been
|
||||||
|
// free'd (attempting to do so causes our connection to freeze).
|
||||||
|
pub unsafe extern fn xim_destroy_callback(
|
||||||
|
_xim: ffi::XIM,
|
||||||
|
client_data: ffi::XPointer,
|
||||||
|
// This field is unsupplied.
|
||||||
|
_call_data: ffi::XPointer,
|
||||||
|
) {
|
||||||
|
let inner: *mut ImeInner = client_data as _;
|
||||||
|
if !inner.is_null() {
|
||||||
|
(*inner).is_destroyed = true;
|
||||||
|
let xconn = &(*inner).xconn;
|
||||||
|
if !(*inner).is_fallback {
|
||||||
|
let _ = set_instantiate_callback(xconn, client_data);
|
||||||
|
// Attempt to open fallback input method.
|
||||||
|
let result = replace_im(inner);
|
||||||
|
if result.is_ok() {
|
||||||
|
(*inner).is_fallback = true;
|
||||||
|
} else {
|
||||||
|
// We have no usable input methods!
|
||||||
|
result.expect("Failed to open fallback input method");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
134
src/platform/linux/x11/ime/context.rs
Normal file
134
src/platform/linux/x11/ime/context.rs
Normal file
|
@ -0,0 +1,134 @@
|
||||||
|
use std::ptr;
|
||||||
|
use std::sync::Arc;
|
||||||
|
use std::os::raw::{c_short, c_void};
|
||||||
|
|
||||||
|
use super::{ffi, util, XConnection, XError};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum ImeContextCreationError {
|
||||||
|
XError(XError),
|
||||||
|
Null,
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe fn create_pre_edit_attr<'a>(
|
||||||
|
xconn: &'a Arc<XConnection>,
|
||||||
|
ic_spot: &'a ffi::XPoint,
|
||||||
|
) -> util::XSmartPointer<'a, c_void> {
|
||||||
|
util::XSmartPointer::new(
|
||||||
|
xconn,
|
||||||
|
(xconn.xlib.XVaCreateNestedList)(
|
||||||
|
0,
|
||||||
|
ffi::XNSpotLocation_0.as_ptr() as *const _,
|
||||||
|
ic_spot,
|
||||||
|
ptr::null_mut::<()>(),
|
||||||
|
),
|
||||||
|
).expect("XVaCreateNestedList returned NULL")
|
||||||
|
}
|
||||||
|
|
||||||
|
// WARNING: this struct doesn't destroy its XIC resource when dropped.
|
||||||
|
// This is intentional, as it doesn't have enough information to know whether or not the context
|
||||||
|
// still exists on the server. Since `ImeInner` has that awareness, destruction must be handled
|
||||||
|
// through `ImeInner`.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct ImeContext {
|
||||||
|
pub ic: ffi::XIC,
|
||||||
|
pub ic_spot: ffi::XPoint,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ImeContext {
|
||||||
|
pub unsafe fn new(
|
||||||
|
xconn: &Arc<XConnection>,
|
||||||
|
im: ffi::XIM,
|
||||||
|
window: ffi::Window,
|
||||||
|
ic_spot: Option<ffi::XPoint>,
|
||||||
|
) -> Result<Self, ImeContextCreationError> {
|
||||||
|
let ic = if let Some(ic_spot) = ic_spot {
|
||||||
|
ImeContext::create_ic_with_spot(xconn, im, window, ic_spot)
|
||||||
|
} else {
|
||||||
|
ImeContext::create_ic(xconn, im, window)
|
||||||
|
};
|
||||||
|
|
||||||
|
let ic = ic.ok_or(ImeContextCreationError::Null)?;
|
||||||
|
xconn.check_errors().map_err(ImeContextCreationError::XError)?;
|
||||||
|
|
||||||
|
Ok(ImeContext {
|
||||||
|
ic,
|
||||||
|
ic_spot: ic_spot.unwrap_or_else(|| ffi::XPoint { x: 0, y: 0 }),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe fn create_ic(
|
||||||
|
xconn: &Arc<XConnection>,
|
||||||
|
im: ffi::XIM,
|
||||||
|
window: ffi::Window,
|
||||||
|
) -> Option<ffi::XIC> {
|
||||||
|
let ic = (xconn.xlib.XCreateIC)(
|
||||||
|
im,
|
||||||
|
ffi::XNInputStyle_0.as_ptr() as *const _,
|
||||||
|
ffi::XIMPreeditNothing | ffi::XIMStatusNothing,
|
||||||
|
ffi::XNClientWindow_0.as_ptr() as *const _,
|
||||||
|
window,
|
||||||
|
ptr::null_mut::<()>(),
|
||||||
|
);
|
||||||
|
if ic.is_null() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(ic)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe fn create_ic_with_spot(
|
||||||
|
xconn: &Arc<XConnection>,
|
||||||
|
im: ffi::XIM,
|
||||||
|
window: ffi::Window,
|
||||||
|
ic_spot: ffi::XPoint,
|
||||||
|
) -> Option<ffi::XIC> {
|
||||||
|
let pre_edit_attr = create_pre_edit_attr(xconn, &ic_spot);
|
||||||
|
let ic = (xconn.xlib.XCreateIC)(
|
||||||
|
im,
|
||||||
|
ffi::XNInputStyle_0.as_ptr() as *const _,
|
||||||
|
ffi::XIMPreeditNothing | ffi::XIMStatusNothing,
|
||||||
|
ffi::XNClientWindow_0.as_ptr() as *const _,
|
||||||
|
window,
|
||||||
|
ffi::XNPreeditAttributes_0.as_ptr() as *const _,
|
||||||
|
pre_edit_attr.ptr,
|
||||||
|
ptr::null_mut::<()>(),
|
||||||
|
);
|
||||||
|
if ic.is_null() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(ic)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn focus(&self, xconn: &Arc<XConnection>) -> Result<(), XError> {
|
||||||
|
unsafe {
|
||||||
|
(xconn.xlib.XSetICFocus)(self.ic);
|
||||||
|
}
|
||||||
|
xconn.check_errors()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn unfocus(&self, xconn: &Arc<XConnection>) -> Result<(), XError> {
|
||||||
|
unsafe {
|
||||||
|
(xconn.xlib.XUnsetICFocus)(self.ic);
|
||||||
|
}
|
||||||
|
xconn.check_errors()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_spot(&mut self, xconn: &Arc<XConnection>, x: c_short, y: c_short) {
|
||||||
|
if self.ic_spot.x == x && self.ic_spot.y == y {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
self.ic_spot = ffi::XPoint { x, y };
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
let pre_edit_attr = create_pre_edit_attr(xconn, &self.ic_spot);
|
||||||
|
(xconn.xlib.XSetICValues)(
|
||||||
|
self.ic,
|
||||||
|
ffi::XNPreeditAttributes_0.as_ptr() as *const _,
|
||||||
|
pre_edit_attr.ptr,
|
||||||
|
ptr::null_mut::<()>(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
75
src/platform/linux/x11/ime/inner.rs
Normal file
75
src/platform/linux/x11/ime/inner.rs
Normal file
|
@ -0,0 +1,75 @@
|
||||||
|
use std::mem;
|
||||||
|
use std::ptr;
|
||||||
|
use std::sync::Arc;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use super::{ffi, XConnection, XError};
|
||||||
|
|
||||||
|
use super::input_method::PotentialInputMethods;
|
||||||
|
use super::context::ImeContext;
|
||||||
|
|
||||||
|
pub unsafe fn close_im(xconn: &Arc<XConnection>, im: ffi::XIM) -> Result<(), XError> {
|
||||||
|
(xconn.xlib.XCloseIM)(im);
|
||||||
|
xconn.check_errors()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub unsafe fn destroy_ic(xconn: &Arc<XConnection>, ic: ffi::XIC) -> Result<(), XError> {
|
||||||
|
(xconn.xlib.XDestroyIC)(ic);
|
||||||
|
xconn.check_errors()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ImeInner {
|
||||||
|
pub xconn: Arc<XConnection>,
|
||||||
|
// WARNING: this is initially null!
|
||||||
|
pub im: ffi::XIM,
|
||||||
|
pub potential_input_methods: PotentialInputMethods,
|
||||||
|
pub contexts: HashMap<ffi::Window, Option<ImeContext>>,
|
||||||
|
// WARNING: this is initially zeroed!
|
||||||
|
pub destroy_callback: ffi::XIMCallback,
|
||||||
|
// Indicates whether or not the the input method was destroyed on the server end
|
||||||
|
// (i.e. if ibus/fcitx/etc. was terminated/restarted)
|
||||||
|
pub is_destroyed: bool,
|
||||||
|
pub is_fallback: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ImeInner {
|
||||||
|
pub fn new(
|
||||||
|
xconn: Arc<XConnection>,
|
||||||
|
potential_input_methods: PotentialInputMethods,
|
||||||
|
) -> Self {
|
||||||
|
ImeInner {
|
||||||
|
xconn,
|
||||||
|
im: ptr::null_mut(),
|
||||||
|
potential_input_methods,
|
||||||
|
contexts: HashMap::new(),
|
||||||
|
destroy_callback: unsafe { mem::zeroed() },
|
||||||
|
is_destroyed: false,
|
||||||
|
is_fallback: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub unsafe fn close_im_if_necessary(&self) -> Result<bool, XError> {
|
||||||
|
if !self.is_destroyed {
|
||||||
|
close_im(&self.xconn, self.im).map(|_| true)
|
||||||
|
} else {
|
||||||
|
Ok(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub unsafe fn destroy_ic_if_necessary(&self, ic: ffi::XIC) -> Result<bool, XError> {
|
||||||
|
if !self.is_destroyed {
|
||||||
|
destroy_ic(&self.xconn, ic).map(|_| true)
|
||||||
|
} else {
|
||||||
|
Ok(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub unsafe fn destroy_all_contexts_if_necessary(&self) -> Result<bool, XError> {
|
||||||
|
for context in self.contexts.values() {
|
||||||
|
if let &Some(ref context) = context {
|
||||||
|
self.destroy_ic_if_necessary(context.ic)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(!self.is_destroyed)
|
||||||
|
}
|
||||||
|
}
|
277
src/platform/linux/x11/ime/input_method.rs
Normal file
277
src/platform/linux/x11/ime/input_method.rs
Normal file
|
@ -0,0 +1,277 @@
|
||||||
|
use std::env;
|
||||||
|
use std::fmt;
|
||||||
|
use std::ptr;
|
||||||
|
use std::sync::Arc;
|
||||||
|
use std::os::raw::c_char;
|
||||||
|
use std::ffi::{CStr, CString, IntoStringError};
|
||||||
|
|
||||||
|
use super::{ffi, util, XConnection, XError};
|
||||||
|
|
||||||
|
unsafe fn open_im(
|
||||||
|
xconn: &Arc<XConnection>,
|
||||||
|
locale_modifiers: &CStr,
|
||||||
|
) -> Option<ffi::XIM> {
|
||||||
|
// XSetLocaleModifiers returns...
|
||||||
|
// * The current locale modifiers if it's given a NULL pointer.
|
||||||
|
// * The new locale modifiers if we succeeded in setting them.
|
||||||
|
// * NULL if the locale modifiers string is malformed.
|
||||||
|
(xconn.xlib.XSetLocaleModifiers)(locale_modifiers.as_ptr());
|
||||||
|
|
||||||
|
let im = (xconn.xlib.XOpenIM)(
|
||||||
|
xconn.display,
|
||||||
|
ptr::null_mut(),
|
||||||
|
ptr::null_mut(),
|
||||||
|
ptr::null_mut(),
|
||||||
|
);
|
||||||
|
|
||||||
|
if im.is_null() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(im)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct InputMethod {
|
||||||
|
pub im: ffi::XIM,
|
||||||
|
name: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl InputMethod {
|
||||||
|
fn new(im: ffi::XIM, name: String) -> Self {
|
||||||
|
InputMethod { im, name }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum InputMethodResult {
|
||||||
|
/// Input method used locale modifier from `XMODIFIERS` environment variable.
|
||||||
|
XModifiers(InputMethod),
|
||||||
|
/// Input method used internal fallback locale modifier.
|
||||||
|
Fallback(InputMethod),
|
||||||
|
/// Input method could not be opened using any locale modifier tried.
|
||||||
|
Failure,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl InputMethodResult {
|
||||||
|
pub fn is_fallback(&self) -> bool {
|
||||||
|
if let &InputMethodResult::Fallback(_) = self {
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn ok(self) -> Option<InputMethod> {
|
||||||
|
use self::InputMethodResult::*;
|
||||||
|
match self {
|
||||||
|
XModifiers(im) | Fallback(im) => Some(im),
|
||||||
|
Failure => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
enum GetXimServersError {
|
||||||
|
XError(XError),
|
||||||
|
GetPropertyError(util::GetPropertyError),
|
||||||
|
InvalidUtf8(IntoStringError),
|
||||||
|
}
|
||||||
|
|
||||||
|
// The root window has a property named XIM_SERVERS, which contains a list of atoms represeting
|
||||||
|
// the availabile XIM servers. For instance, if you're using ibus, it would contain an atom named
|
||||||
|
// "@server=ibus". It's possible for this property to contain multiple atoms, though presumably
|
||||||
|
// rare. Note that we replace "@server=" with "@im=" in order to match the format of locale
|
||||||
|
// modifiers, since we don't want a user who's looking at logs to ask "am I supposed to set
|
||||||
|
// XMODIFIERS to `@server=ibus`?!?"
|
||||||
|
unsafe fn get_xim_servers(xconn: &Arc<XConnection>) -> Result<Vec<String>, GetXimServersError> {
|
||||||
|
let servers_atom = util::get_atom(&xconn, b"XIM_SERVERS\0")
|
||||||
|
.map_err(GetXimServersError::XError)?;
|
||||||
|
|
||||||
|
let root = (xconn.xlib.XDefaultRootWindow)(xconn.display);
|
||||||
|
|
||||||
|
let mut atoms: Vec<ffi::Atom> = util::get_property(
|
||||||
|
&xconn,
|
||||||
|
root,
|
||||||
|
servers_atom,
|
||||||
|
ffi::XA_ATOM,
|
||||||
|
).map_err(GetXimServersError::GetPropertyError)?;
|
||||||
|
|
||||||
|
let mut names: Vec<*const c_char> = Vec::with_capacity(atoms.len());
|
||||||
|
(xconn.xlib.XGetAtomNames)(
|
||||||
|
xconn.display,
|
||||||
|
atoms.as_mut_ptr(),
|
||||||
|
atoms.len() as _,
|
||||||
|
names.as_mut_ptr() as _,
|
||||||
|
);
|
||||||
|
names.set_len(atoms.len());
|
||||||
|
|
||||||
|
let mut formatted_names = Vec::with_capacity(names.len());
|
||||||
|
for name in names {
|
||||||
|
let string = CStr::from_ptr(name)
|
||||||
|
.to_owned()
|
||||||
|
.into_string()
|
||||||
|
.map_err(GetXimServersError::InvalidUtf8)?;
|
||||||
|
(xconn.xlib.XFree)(name as _);
|
||||||
|
formatted_names.push(string.replace("@server=", "@im="));
|
||||||
|
}
|
||||||
|
xconn.check_errors().map_err(GetXimServersError::XError)?;
|
||||||
|
Ok(formatted_names)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
struct InputMethodName {
|
||||||
|
c_string: CString,
|
||||||
|
string: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl InputMethodName {
|
||||||
|
pub fn from_string(string: String) -> Self {
|
||||||
|
let c_string = CString::new(string.clone())
|
||||||
|
.expect("String used to construct CString contained null byte");
|
||||||
|
InputMethodName {
|
||||||
|
c_string,
|
||||||
|
string,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_str(string: &str) -> Self {
|
||||||
|
let c_string = CString::new(string)
|
||||||
|
.expect("String used to construct CString contained null byte");
|
||||||
|
InputMethodName {
|
||||||
|
c_string,
|
||||||
|
string: string.to_owned(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Debug for InputMethodName {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
self.string.fmt(f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
struct PotentialInputMethod {
|
||||||
|
name: InputMethodName,
|
||||||
|
successful: Option<bool>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PotentialInputMethod {
|
||||||
|
pub fn from_string(string: String) -> Self {
|
||||||
|
PotentialInputMethod {
|
||||||
|
name: InputMethodName::from_string(string),
|
||||||
|
successful: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_str(string: &str) -> Self {
|
||||||
|
PotentialInputMethod {
|
||||||
|
name: InputMethodName::from_str(string),
|
||||||
|
successful: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn reset(&mut self) {
|
||||||
|
self.successful = None;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn open_im(&mut self, xconn: &Arc<XConnection>) -> Option<InputMethod> {
|
||||||
|
let im = unsafe { open_im(xconn, &self.name.c_string) };
|
||||||
|
self.successful = Some(im.is_some());
|
||||||
|
im.map(|im| InputMethod::new(im, self.name.string.clone()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// By logging this struct, you get a sequential listing of every locale modifier tried, where it
|
||||||
|
// came from, and if it succceeded.
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct PotentialInputMethods {
|
||||||
|
// On correctly configured systems, the XMODIFIERS environemnt variable tells us everything we
|
||||||
|
// need to know.
|
||||||
|
xmodifiers: Option<PotentialInputMethod>,
|
||||||
|
// We have some standard options at our disposal that should ostensibly always work. For users
|
||||||
|
// who only need compose sequences, this ensures that the program launches without a hitch
|
||||||
|
// For users who need more sophisticated IME features, this is more or less a silent failure.
|
||||||
|
// Logging features should be added in the future to allow both audiences to be effectively
|
||||||
|
// served.
|
||||||
|
fallbacks: [PotentialInputMethod; 2],
|
||||||
|
// For diagnostic purposes, we include the list of XIM servers that the server reports as
|
||||||
|
// being available.
|
||||||
|
_xim_servers: Result<Vec<String>, GetXimServersError>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PotentialInputMethods {
|
||||||
|
pub fn new(xconn: &Arc<XConnection>) -> Self {
|
||||||
|
let xmodifiers = env::var("XMODIFIERS")
|
||||||
|
.ok()
|
||||||
|
.map(PotentialInputMethod::from_string);
|
||||||
|
PotentialInputMethods {
|
||||||
|
// Since passing "" to XSetLocaleModifiers results in it defaulting to the value of
|
||||||
|
// XMODIFIERS, it's worth noting what happens if XMODIFIERS is also "". If simply
|
||||||
|
// running the program with `XMODIFIERS="" cargo run`, then assuming XMODIFIERS is
|
||||||
|
// defined in the profile (or parent environment) then that parent XMODIFIERS is used.
|
||||||
|
// If that XMODIFIERS value is also "" (i.e. if you ran `export XMODIFIERS=""`), then
|
||||||
|
// XSetLocaleModifiers uses the default local input method. Note that defining
|
||||||
|
// XMODIFIERS as "" is different from XMODIFIERS not being defined at all, since in
|
||||||
|
// that case, we get `None` and end up skipping ahead to the next method.
|
||||||
|
xmodifiers,
|
||||||
|
fallbacks: [
|
||||||
|
// This is a standard input method that supports compose equences, which should
|
||||||
|
// always be available. `@im=none` appears to mean the same thing.
|
||||||
|
PotentialInputMethod::from_str("@im=local"),
|
||||||
|
// This explicitly specifies to use the implementation-dependent default, though
|
||||||
|
// that seems to be equivalent to just using the local input method.
|
||||||
|
PotentialInputMethod::from_str("@im="),
|
||||||
|
],
|
||||||
|
// The XIM_SERVERS property can have surprising values. For instance, when I exited
|
||||||
|
// ibus to run fcitx, it retained the value denoting ibus. Even more surprising is
|
||||||
|
// that the fcitx input method could only be successfully opened using "@im=ibus".
|
||||||
|
// Presumably due to this quirk, it's actually possible to alternate between ibus and
|
||||||
|
// fcitx in a running application.
|
||||||
|
_xim_servers: unsafe { get_xim_servers(xconn) },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// This resets the `successful` field of every potential input method, ensuring we have
|
||||||
|
// accurate information when this struct is re-used by the destruction/instantiation callbacks.
|
||||||
|
fn reset(&mut self) {
|
||||||
|
if let Some(ref mut input_method) = self.xmodifiers {
|
||||||
|
input_method.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
for input_method in &mut self.fallbacks {
|
||||||
|
input_method.reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn open_im(
|
||||||
|
&mut self,
|
||||||
|
xconn: &Arc<XConnection>,
|
||||||
|
callback: Option<&Fn() -> ()>,
|
||||||
|
) -> InputMethodResult {
|
||||||
|
use self::InputMethodResult::*;
|
||||||
|
|
||||||
|
self.reset();
|
||||||
|
|
||||||
|
if let Some(ref mut input_method) = self.xmodifiers {
|
||||||
|
let im = input_method.open_im(xconn);
|
||||||
|
if let Some(im) = im {
|
||||||
|
return XModifiers(im);
|
||||||
|
} else {
|
||||||
|
if let Some(ref callback) = callback {
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for input_method in &mut self.fallbacks {
|
||||||
|
let im = input_method.open_im(xconn);
|
||||||
|
if let Some(im) = im {
|
||||||
|
return Fallback(im);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Failure
|
||||||
|
}
|
||||||
|
}
|
165
src/platform/linux/x11/ime/mod.rs
Normal file
165
src/platform/linux/x11/ime/mod.rs
Normal file
|
@ -0,0 +1,165 @@
|
||||||
|
// Important: all XIM calls need to happen from the same thread!
|
||||||
|
|
||||||
|
mod inner;
|
||||||
|
mod input_method;
|
||||||
|
mod context;
|
||||||
|
mod callbacks;
|
||||||
|
|
||||||
|
use std::sync::Arc;
|
||||||
|
use std::sync::mpsc::{Receiver, Sender};
|
||||||
|
|
||||||
|
use super::{ffi, util, XConnection, XError};
|
||||||
|
|
||||||
|
use self::inner::{close_im, ImeInner};
|
||||||
|
use self::input_method::PotentialInputMethods;
|
||||||
|
use self::context::{ImeContextCreationError, ImeContext};
|
||||||
|
use self::callbacks::*;
|
||||||
|
|
||||||
|
pub type ImeReceiver = Receiver<(ffi::Window, i16, i16)>;
|
||||||
|
pub type ImeSender = Sender<(ffi::Window, i16, i16)>;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum ImeCreationError {
|
||||||
|
OpenFailure(PotentialInputMethods),
|
||||||
|
SetDestroyCallbackFailed(XError),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Ime {
|
||||||
|
xconn: Arc<XConnection>,
|
||||||
|
// The actual meat of this struct is boxed away, since it needs to have a fixed location in
|
||||||
|
// memory so we can pass a pointer to it around.
|
||||||
|
inner: Box<ImeInner>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Ime {
|
||||||
|
pub fn new(xconn: Arc<XConnection>) -> Result<Self, ImeCreationError> {
|
||||||
|
let potential_input_methods = PotentialInputMethods::new(&xconn);
|
||||||
|
|
||||||
|
let (mut inner, client_data) = {
|
||||||
|
let mut inner = Box::new(ImeInner::new(
|
||||||
|
xconn,
|
||||||
|
potential_input_methods,
|
||||||
|
));
|
||||||
|
let inner_ptr = Box::into_raw(inner);
|
||||||
|
let client_data = inner_ptr as _;
|
||||||
|
let destroy_callback = ffi::XIMCallback {
|
||||||
|
client_data,
|
||||||
|
callback: Some(xim_destroy_callback),
|
||||||
|
};
|
||||||
|
inner = unsafe { Box::from_raw(inner_ptr) };
|
||||||
|
inner.destroy_callback = destroy_callback;
|
||||||
|
(inner, client_data)
|
||||||
|
};
|
||||||
|
|
||||||
|
let xconn = Arc::clone(&inner.xconn);
|
||||||
|
|
||||||
|
let input_method = inner.potential_input_methods.open_im(&xconn, Some(&|| {
|
||||||
|
let _ = unsafe { set_instantiate_callback(&xconn, client_data) };
|
||||||
|
}));
|
||||||
|
|
||||||
|
let is_fallback = input_method.is_fallback();
|
||||||
|
if let Some(input_method) = input_method.ok() {
|
||||||
|
inner.im = input_method.im;
|
||||||
|
inner.is_fallback = is_fallback;
|
||||||
|
unsafe {
|
||||||
|
let result = set_destroy_callback(&xconn, input_method.im, &*inner)
|
||||||
|
.map_err(ImeCreationError::SetDestroyCallbackFailed);
|
||||||
|
if result.is_err() {
|
||||||
|
let _ = close_im(&xconn, input_method.im);
|
||||||
|
}
|
||||||
|
result?;
|
||||||
|
}
|
||||||
|
Ok(Ime { xconn, inner })
|
||||||
|
} else {
|
||||||
|
Err(ImeCreationError::OpenFailure(inner.potential_input_methods))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_destroyed(&self) -> bool {
|
||||||
|
self.inner.is_destroyed
|
||||||
|
}
|
||||||
|
|
||||||
|
// This pattern is used for various methods here:
|
||||||
|
// Ok(_) indicates that nothing went wrong internally
|
||||||
|
// Ok(true) indicates that the action was actually performed
|
||||||
|
// Ok(false) indicates that the action is not presently applicable
|
||||||
|
pub fn create_context(&mut self, window: ffi::Window)
|
||||||
|
-> Result<bool, ImeContextCreationError>
|
||||||
|
{
|
||||||
|
let context = if self.is_destroyed() {
|
||||||
|
// Create empty entry in map, so that when IME is rebuilt, this window has a context.
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(unsafe { ImeContext::new(
|
||||||
|
&self.inner.xconn,
|
||||||
|
self.inner.im,
|
||||||
|
window,
|
||||||
|
None,
|
||||||
|
) }?)
|
||||||
|
};
|
||||||
|
self.inner.contexts.insert(window, context);
|
||||||
|
Ok(!self.is_destroyed())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_context(&self, window: ffi::Window) -> Option<ffi::XIC> {
|
||||||
|
if self.is_destroyed() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
if let Some(&Some(ref context)) = self.inner.contexts.get(&window) {
|
||||||
|
Some(context.ic)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn remove_context(&mut self, window: ffi::Window) -> Result<bool, XError> {
|
||||||
|
if let Some(Some(context)) = self.inner.contexts.remove(&window) {
|
||||||
|
unsafe {
|
||||||
|
self.inner.destroy_ic_if_necessary(context.ic)?;
|
||||||
|
}
|
||||||
|
Ok(true)
|
||||||
|
} else {
|
||||||
|
Ok(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn focus(&mut self, window: ffi::Window) -> Result<bool, XError> {
|
||||||
|
if self.is_destroyed() {
|
||||||
|
return Ok(false);
|
||||||
|
}
|
||||||
|
if let Some(&mut Some(ref mut context)) = self.inner.contexts.get_mut(&window) {
|
||||||
|
context.focus(&self.xconn).map(|_| true)
|
||||||
|
} else {
|
||||||
|
Ok(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn unfocus(&mut self, window: ffi::Window) -> Result<bool, XError> {
|
||||||
|
if self.is_destroyed() {
|
||||||
|
return Ok(false);
|
||||||
|
}
|
||||||
|
if let Some(&mut Some(ref mut context)) = self.inner.contexts.get_mut(&window) {
|
||||||
|
context.unfocus(&self.xconn).map(|_| true)
|
||||||
|
} else {
|
||||||
|
Ok(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn send_xim_spot(&mut self, window: ffi::Window, x: i16, y: i16) {
|
||||||
|
if self.is_destroyed() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if let Some(&mut Some(ref mut context)) = self.inner.contexts.get_mut(&window) {
|
||||||
|
context.set_spot(&self.xconn, x as _, y as _);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for Ime {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
unsafe {
|
||||||
|
let _ = self.inner.destroy_all_contexts_if_necessary();
|
||||||
|
let _ = self.inner.close_im_if_necessary();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -14,22 +14,24 @@ use events::ModifiersState;
|
||||||
use std::{mem, ptr, slice};
|
use std::{mem, ptr, slice};
|
||||||
use std::sync::{Arc, Mutex, Weak};
|
use std::sync::{Arc, Mutex, Weak};
|
||||||
use std::sync::atomic::{self, AtomicBool};
|
use std::sync::atomic::{self, AtomicBool};
|
||||||
use std::sync::mpsc::{channel, Receiver, Sender};
|
use std::sync::mpsc;
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::ffi::CStr;
|
use std::ffi::CStr;
|
||||||
use std::os::raw::{c_char, c_int, 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 libc;
|
use libc::{self, setlocale, LC_CTYPE};
|
||||||
|
|
||||||
mod events;
|
mod events;
|
||||||
mod monitor;
|
mod monitor;
|
||||||
mod window;
|
mod window;
|
||||||
mod xdisplay;
|
mod xdisplay;
|
||||||
mod dnd;
|
mod dnd;
|
||||||
|
mod ime;
|
||||||
mod util;
|
mod util;
|
||||||
|
|
||||||
use self::dnd::{Dnd, DndState};
|
use self::dnd::{Dnd, DndState};
|
||||||
|
use self::ime::{ImeReceiver, ImeSender, ImeCreationError, Ime};
|
||||||
|
|
||||||
// API TRANSITION
|
// API TRANSITION
|
||||||
//
|
//
|
||||||
|
@ -42,9 +44,9 @@ pub struct EventsLoop {
|
||||||
display: Arc<XConnection>,
|
display: Arc<XConnection>,
|
||||||
wm_delete_window: ffi::Atom,
|
wm_delete_window: ffi::Atom,
|
||||||
dnd: Dnd,
|
dnd: Dnd,
|
||||||
ime_receiver: Receiver<(WindowId, i16, i16)>,
|
ime_receiver: ImeReceiver,
|
||||||
ime_sender: Sender<(WindowId, i16, i16)>,
|
ime_sender: ImeSender,
|
||||||
ime: RefCell<HashMap<WindowId, util::Ime>>,
|
ime: RefCell<Ime>,
|
||||||
windows: Arc<Mutex<HashMap<WindowId, WindowData>>>,
|
windows: Arc<Mutex<HashMap<WindowId, WindowData>>>,
|
||||||
devices: Mutex<HashMap<DeviceId, Device>>,
|
devices: Mutex<HashMap<DeviceId, Device>>,
|
||||||
xi2ext: XExtension,
|
xi2ext: XExtension,
|
||||||
|
@ -70,7 +72,17 @@ impl EventsLoop {
|
||||||
let dnd = Dnd::new(Arc::clone(&display))
|
let dnd = Dnd::new(Arc::clone(&display))
|
||||||
.expect("Failed to call XInternAtoms when initializing drag and drop");
|
.expect("Failed to call XInternAtoms when initializing drag and drop");
|
||||||
|
|
||||||
let (ime_sender, ime_receiver) = channel();
|
let (ime_sender, ime_receiver) = mpsc::channel();
|
||||||
|
// Input methods will open successfully without setting the locale, but it won't be
|
||||||
|
// possible to actually commit pre-edit sequences.
|
||||||
|
unsafe { setlocale(LC_CTYPE, b"\0".as_ptr() as *const _); }
|
||||||
|
let ime = RefCell::new({
|
||||||
|
let result = Ime::new(Arc::clone(&display));
|
||||||
|
if let Err(ImeCreationError::OpenFailure(ref state)) = result {
|
||||||
|
panic!(format!("Failed to open input method: {:#?}", state));
|
||||||
|
}
|
||||||
|
result.expect("Failed to set input method destruction callback")
|
||||||
|
});
|
||||||
|
|
||||||
let xi2ext = unsafe {
|
let xi2ext = unsafe {
|
||||||
let mut result = XExtension {
|
let mut result = XExtension {
|
||||||
|
@ -115,7 +127,7 @@ impl EventsLoop {
|
||||||
dnd,
|
dnd,
|
||||||
ime_receiver,
|
ime_receiver,
|
||||||
ime_sender,
|
ime_sender,
|
||||||
ime: RefCell::new(HashMap::new()),
|
ime,
|
||||||
windows: Arc::new(Mutex::new(HashMap::new())),
|
windows: Arc::new(Mutex::new(HashMap::new())),
|
||||||
devices: Mutex::new(HashMap::new()),
|
devices: Mutex::new(HashMap::new()),
|
||||||
xi2ext,
|
xi2ext,
|
||||||
|
@ -419,7 +431,10 @@ impl EventsLoop {
|
||||||
|
|
||||||
let window = xev.window;
|
let window = xev.window;
|
||||||
|
|
||||||
self.ime.borrow_mut().remove(&WindowId(window));
|
self.ime
|
||||||
|
.borrow_mut()
|
||||||
|
.remove_context(window)
|
||||||
|
.expect("Failed to destroy input context");
|
||||||
}
|
}
|
||||||
|
|
||||||
ffi::Expose => {
|
ffi::Expose => {
|
||||||
|
@ -485,8 +500,8 @@ impl EventsLoop {
|
||||||
}
|
}
|
||||||
|
|
||||||
if state == Pressed {
|
if state == Pressed {
|
||||||
let written = if let Some(ime) = self.ime.borrow().get(&WindowId(window)) {
|
let written = if let Some(ic) = self.ime.borrow().get_context(window) {
|
||||||
unsafe { util::lookup_utf8(&self.display, ime.ic, xkev) }
|
unsafe { util::lookup_utf8(&self.display, ic, xkev) }
|
||||||
} else {
|
} else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
@ -736,11 +751,13 @@ impl EventsLoop {
|
||||||
|
|
||||||
let window_id = mkwid(xev.event);
|
let window_id = mkwid(xev.event);
|
||||||
|
|
||||||
if let Some(ime) = self.ime.borrow().get(&WindowId(xev.event)) {
|
if let None = self.windows.lock().unwrap().get(&WindowId(xev.event)) {
|
||||||
ime.focus().expect("Failed to focus input context");
|
|
||||||
} else {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
self.ime
|
||||||
|
.borrow_mut()
|
||||||
|
.focus(xev.event)
|
||||||
|
.expect("Failed to focus input context");
|
||||||
|
|
||||||
callback(Event::WindowEvent { window_id, event: Focused(true) });
|
callback(Event::WindowEvent { window_id, event: Focused(true) });
|
||||||
|
|
||||||
|
@ -764,11 +781,13 @@ impl EventsLoop {
|
||||||
ffi::XI_FocusOut => {
|
ffi::XI_FocusOut => {
|
||||||
let xev: &ffi::XIFocusOutEvent = unsafe { &*(xev.data as *const _) };
|
let xev: &ffi::XIFocusOutEvent = unsafe { &*(xev.data as *const _) };
|
||||||
|
|
||||||
if let Some(ime) = self.ime.borrow().get(&WindowId(xev.event)) {
|
if let None = self.windows.lock().unwrap().get(&WindowId(xev.event)) {
|
||||||
ime.unfocus().expect("Failed to unfocus input context");
|
|
||||||
} else {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
self.ime
|
||||||
|
.borrow_mut()
|
||||||
|
.unfocus(xev.event)
|
||||||
|
.expect("Failed to unfocus input context");
|
||||||
|
|
||||||
callback(Event::WindowEvent {
|
callback(Event::WindowEvent {
|
||||||
window_id: mkwid(xev.event),
|
window_id: mkwid(xev.event),
|
||||||
|
@ -888,9 +907,7 @@ impl EventsLoop {
|
||||||
|
|
||||||
match self.ime_receiver.try_recv() {
|
match self.ime_receiver.try_recv() {
|
||||||
Ok((window_id, x, y)) => {
|
Ok((window_id, x, y)) => {
|
||||||
if let Some(ime) = self.ime.borrow_mut().get_mut(&window_id) {
|
self.ime.borrow_mut().send_xim_spot(window_id, x, y);
|
||||||
ime.send_xim_spot(x, y);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Err(_) => ()
|
Err(_) => ()
|
||||||
}
|
}
|
||||||
|
@ -987,7 +1004,7 @@ pub struct Window {
|
||||||
pub window: Arc<Window2>,
|
pub window: Arc<Window2>,
|
||||||
display: Weak<XConnection>,
|
display: Weak<XConnection>,
|
||||||
windows: Weak<Mutex<HashMap<WindowId, WindowData>>>,
|
windows: Weak<Mutex<HashMap<WindowId, WindowData>>>,
|
||||||
ime_sender: Sender<(WindowId, i16, i16)>,
|
ime_sender: ImeSender,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ::std::ops::Deref for Window {
|
impl ::std::ops::Deref for Window {
|
||||||
|
@ -1006,9 +1023,10 @@ impl Window {
|
||||||
) -> Result<Self, CreationError> {
|
) -> Result<Self, CreationError> {
|
||||||
let win = Arc::new(try!(Window2::new(&x_events_loop, window, pl_attribs)));
|
let win = Arc::new(try!(Window2::new(&x_events_loop, window, pl_attribs)));
|
||||||
|
|
||||||
let ime = util::Ime::new(Arc::clone(&x_events_loop.display), win.id().0)
|
x_events_loop.ime
|
||||||
.expect("Failed to initialize IME");
|
.borrow_mut()
|
||||||
x_events_loop.ime.borrow_mut().insert(win.id(), ime);
|
.create_context(win.id().0)
|
||||||
|
.expect("Failed to create input context");
|
||||||
|
|
||||||
x_events_loop.windows.lock().unwrap().insert(win.id(), WindowData {
|
x_events_loop.windows.lock().unwrap().insert(win.id(), WindowData {
|
||||||
config: None,
|
config: None,
|
||||||
|
@ -1031,7 +1049,7 @@ impl Window {
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn send_xim_spot(&self, x: i16, y: i16) {
|
pub fn send_xim_spot(&self, x: i16, y: i16) {
|
||||||
let _ = self.ime_sender.send((self.window.id(), x, y));
|
let _ = self.ime_sender.send((self.window.id().0, x, y));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -90,7 +90,7 @@ pub unsafe fn send_client_msg(
|
||||||
xconn.check_errors().map(|_| ())
|
xconn.check_errors().map(|_| ())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum GetPropertyError {
|
pub enum GetPropertyError {
|
||||||
XError(XError),
|
XError(XError),
|
||||||
TypeMismatch(ffi::Atom),
|
TypeMismatch(ffi::Atom),
|
||||||
|
@ -309,7 +309,6 @@ pub unsafe fn lookup_utf8(
|
||||||
str::from_utf8(&buffer[..count as usize]).unwrap_or("").to_string()
|
str::from_utf8(&buffer[..count as usize]).unwrap_or("").to_string()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct FrameExtents {
|
pub struct FrameExtents {
|
||||||
pub left: c_ulong,
|
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");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
Loading…
Reference in a new issue