Event Loop 2.0 API and Windows implementation (#638)
* Rename EventsLoop and associated types to EventLoop * Rename WindowEvent::Refresh to WindowEvent::Redraw * Remove second thread from win32 backend * Update run_forever to hijack thread * Replace windows Mutex with parking_lot Mutex * Implement new ControlFlow and associated events * Add StartCause::Init support, timer example * Add ability to send custom user events * Fully invert windows control flow so win32 calls into winit's callback * Add request_redraw * Rename platform to platform_impl * Rename os to platform, add Ext trait postfixes * Add platform::desktop module with EventLoopExt::run_return * Re-organize into module structure * Improve documentation * Small changes to examples * Improve docs for run and run_return * Change instances of "events_loop" to "event_loop" * Rename MonitorId to MonitorHandle * Add CHANGELOG entry * Improve WaitUntil timer precision * When SendEvent is called during event closure, buffer events * Fix resize lag when waiting in some situations * Update send test and errors that broke some examples/APIs * Improve clarity/fix typos in docs * Fix unreachable panic after setting ControlFlow to Poll during some RedrawRequested events. * Fix crash when running in release mode * Remove crossbeam dependency and make drop events work again * Remove serde implementations from ControlFlow * Fix 1.24.1 build * Fix freeze when setting decorations * Replace &EventLoop in callback with &EventLoopWindowTarget * Document and implement Debug for EventLoopWindowTarget * Fix some deadlocks that could occur when changing window state * Fix thread executor not executing closure when called from non-loop thread * Fix buffered events not getting dispatched * Fix crash with runner refcell not getting dropped * Address review feedback * Fix CHANGELOG typo * Catch panics in user callback
This commit is contained in:
parent
7be1d16263
commit
9602716ed2
92 changed files with 3467 additions and 1260 deletions
185
src/platform_impl/linux/x11/ime/callbacks.rs
Normal file
185
src/platform_impl/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_impl/linux/x11/ime/context.rs
Normal file
134
src/platform_impl/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_impl/linux/x11/ime/inner.rs
Normal file
75
src/platform_impl/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)
|
||||
}
|
||||
}
|
||||
283
src/platform_impl/linux/x11/ime/input_method.rs
Normal file
283
src/platform_impl/linux/x11/ime/input_method.rs
Normal file
|
|
@ -0,0 +1,283 @@
|
|||
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 parking_lot::Mutex;
|
||||
|
||||
use super::{ffi, util, XConnection, XError};
|
||||
|
||||
lazy_static! {
|
||||
static ref GLOBAL_LOCK: Mutex<()> = Default::default();
|
||||
}
|
||||
|
||||
unsafe fn open_im(
|
||||
xconn: &Arc<XConnection>,
|
||||
locale_modifiers: &CStr,
|
||||
) -> Option<ffi::XIM> {
|
||||
let _lock = GLOBAL_LOCK.lock();
|
||||
|
||||
// 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 = xconn.get_atom_unchecked(b"XIM_SERVERS\0");
|
||||
|
||||
let root = (xconn.xlib.XDefaultRootWindow)(xconn.display);
|
||||
|
||||
let mut atoms: Vec<ffi::Atom> = xconn.get_property(
|
||||
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_impl/linux/x11/ime/mod.rs
Normal file
165
src/platform_impl/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();
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue