Move X11 backend to winit-x11 (#4253)
This commit is contained in:
parent
1126e9ea2f
commit
256bbe949e
42 changed files with 232 additions and 227 deletions
206
winit-x11/src/ime/callbacks.rs
Normal file
206
winit-x11/src/ime/callbacks.rs
Normal file
|
|
@ -0,0 +1,206 @@
|
|||
use std::collections::HashMap;
|
||||
use std::os::raw::c_char;
|
||||
use std::ptr;
|
||||
use std::sync::Arc;
|
||||
|
||||
use super::context::{ImeContext, ImeContextCreationError};
|
||||
use super::ffi;
|
||||
use super::inner::{close_im, ImeInner};
|
||||
use super::input_method::PotentialInputMethods;
|
||||
use crate::xdisplay::{XConnection, XError};
|
||||
|
||||
pub(crate) 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.
|
||||
unsafe { (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(crate) unsafe fn set_instantiate_callback(
|
||||
xconn: &Arc<XConnection>,
|
||||
client_data: ffi::XPointer,
|
||||
) -> Result<(), XError> {
|
||||
unsafe {
|
||||
(xconn.xlib.XRegisterIMInstantiateCallback)(
|
||||
xconn.display,
|
||||
ptr::null_mut(),
|
||||
ptr::null_mut(),
|
||||
ptr::null_mut(),
|
||||
Some(xim_instantiate_callback),
|
||||
client_data,
|
||||
)
|
||||
};
|
||||
xconn.check_errors()
|
||||
}
|
||||
|
||||
pub(crate) unsafe fn unset_instantiate_callback(
|
||||
xconn: &Arc<XConnection>,
|
||||
client_data: ffi::XPointer,
|
||||
) -> Result<(), XError> {
|
||||
unsafe {
|
||||
(xconn.xlib.XUnregisterIMInstantiateCallback)(
|
||||
xconn.display,
|
||||
ptr::null_mut(),
|
||||
ptr::null_mut(),
|
||||
ptr::null_mut(),
|
||||
Some(xim_instantiate_callback),
|
||||
client_data,
|
||||
)
|
||||
};
|
||||
xconn.check_errors()
|
||||
}
|
||||
|
||||
pub(crate) unsafe fn set_destroy_callback(
|
||||
xconn: &Arc<XConnection>,
|
||||
im: ffi::XIM,
|
||||
inner: &ImeInner,
|
||||
) -> Result<(), XError> {
|
||||
unsafe {
|
||||
xim_set_callback(
|
||||
xconn,
|
||||
im,
|
||||
ffi::XNDestroyCallback_0.as_ptr() as *const _,
|
||||
&inner.destroy_callback as *const _ as *mut _,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[allow(clippy::enum_variant_names)]
|
||||
enum ReplaceImError {
|
||||
// Boxed to prevent large error type
|
||||
MethodOpenFailed(#[allow(dead_code)] Box<PotentialInputMethods>),
|
||||
ContextCreationFailed(#[allow(dead_code)] ImeContextCreationError),
|
||||
SetDestroyCallbackFailed(#[allow(dead_code)] 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 = unsafe { &(*inner).xconn };
|
||||
|
||||
let (new_im, is_fallback) = {
|
||||
let new_im = unsafe { (*inner).potential_input_methods.open_im(xconn, None) };
|
||||
let is_fallback = new_im.is_fallback();
|
||||
(
|
||||
new_im.ok().ok_or_else(|| {
|
||||
ReplaceImError::MethodOpenFailed(Box::new(unsafe {
|
||||
(*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 = unsafe { set_destroy_callback(xconn, new_im.im, &*inner) };
|
||||
if result.is_err() {
|
||||
let _ = unsafe { close_im(xconn, new_im.im) };
|
||||
}
|
||||
result
|
||||
}
|
||||
.map_err(ReplaceImError::SetDestroyCallbackFailed)?;
|
||||
|
||||
let mut new_contexts = HashMap::new();
|
||||
for (window, old_context) in unsafe { (*inner).contexts.iter() } {
|
||||
let area = old_context.as_ref().map(|old_context| old_context.ic_area);
|
||||
|
||||
// Check if the IME was allowed on that context.
|
||||
let is_allowed =
|
||||
old_context.as_ref().map(|old_context| old_context.is_allowed()).unwrap_or_default();
|
||||
|
||||
let new_context = {
|
||||
let result = unsafe {
|
||||
ImeContext::new(
|
||||
xconn,
|
||||
&new_im,
|
||||
*window,
|
||||
area,
|
||||
(*inner).event_sender.clone(),
|
||||
is_allowed,
|
||||
)
|
||||
};
|
||||
if result.is_err() {
|
||||
let _ = unsafe { 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.
|
||||
unsafe {
|
||||
let _ = (*inner).destroy_all_contexts_if_necessary();
|
||||
let _ = (*inner).close_im_if_necessary();
|
||||
(*inner).im = Some(new_im);
|
||||
(*inner).contexts = new_contexts;
|
||||
(*inner).is_destroyed = false;
|
||||
(*inner).is_fallback = is_fallback;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub unsafe extern "C" 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 = unsafe { &(*inner).xconn };
|
||||
match unsafe { replace_im(inner) } {
|
||||
Ok(()) => unsafe {
|
||||
let _ = unset_instantiate_callback(xconn, client_data);
|
||||
(*inner).is_fallback = false;
|
||||
},
|
||||
Err(err) => unsafe {
|
||||
if (*inner).is_destroyed {
|
||||
// We have no usable input methods!
|
||||
panic!("Failed to reopen input method: {err:?}");
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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 "C" 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() {
|
||||
unsafe { (*inner).is_destroyed = true };
|
||||
let xconn = unsafe { &(*inner).xconn };
|
||||
if unsafe { !(*inner).is_fallback } {
|
||||
let _ = unsafe { set_instantiate_callback(xconn, client_data) };
|
||||
// Attempt to open fallback input method.
|
||||
match unsafe { replace_im(inner) } {
|
||||
Ok(()) => unsafe { (*inner).is_fallback = true },
|
||||
Err(err) => {
|
||||
// We have no usable input methods!
|
||||
panic!("Failed to open fallback input method: {err:?}");
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
405
winit-x11/src/ime/context.rs
Normal file
405
winit-x11/src/ime/context.rs
Normal file
|
|
@ -0,0 +1,405 @@
|
|||
use std::error::Error;
|
||||
use std::ffi::CStr;
|
||||
use std::sync::Arc;
|
||||
use std::{fmt, mem, ptr};
|
||||
|
||||
use x11_dl::xlib::{XIMCallback, XIMPreeditCaretCallbackStruct, XIMPreeditDrawCallbackStruct};
|
||||
|
||||
use super::input_method::{InputMethod, Style, XIMStyle};
|
||||
use super::{ffi, util, ImeEvent, ImeEventSender};
|
||||
use crate::xdisplay::{XConnection, XError};
|
||||
|
||||
/// IME creation error.
|
||||
#[derive(Debug)]
|
||||
pub enum ImeContextCreationError {
|
||||
/// Got the error from Xlib.
|
||||
XError(XError),
|
||||
|
||||
/// Got null pointer from Xlib but without exact reason.
|
||||
Null,
|
||||
}
|
||||
|
||||
impl fmt::Display for ImeContextCreationError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
ImeContextCreationError::XError(err) => err.fmt(f),
|
||||
ImeContextCreationError::Null => {
|
||||
write!(f, "got null pointer from Xlib without exact reason")
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for ImeContextCreationError {}
|
||||
|
||||
/// The callback used by XIM preedit functions.
|
||||
type XIMProcNonnull = unsafe extern "C" fn(ffi::XIM, ffi::XPointer, ffi::XPointer);
|
||||
|
||||
/// Wrapper for creating XIM callbacks.
|
||||
#[inline]
|
||||
fn create_xim_callback(client_data: ffi::XPointer, callback: XIMProcNonnull) -> ffi::XIMCallback {
|
||||
XIMCallback { client_data, callback: Some(callback) }
|
||||
}
|
||||
|
||||
/// The server started preedit.
|
||||
extern "C" fn preedit_start_callback(
|
||||
_xim: ffi::XIM,
|
||||
client_data: ffi::XPointer,
|
||||
_call_data: ffi::XPointer,
|
||||
) -> i32 {
|
||||
let client_data = unsafe { &mut *(client_data as *mut ImeContextClientData) };
|
||||
|
||||
client_data.text.clear();
|
||||
client_data.cursor_pos = 0;
|
||||
client_data
|
||||
.event_sender
|
||||
.send((client_data.window, ImeEvent::Start))
|
||||
.expect("failed to send preedit start event");
|
||||
-1
|
||||
}
|
||||
|
||||
/// Done callback is used when the preedit should be hidden.
|
||||
extern "C" fn preedit_done_callback(
|
||||
_xim: ffi::XIM,
|
||||
client_data: ffi::XPointer,
|
||||
_call_data: ffi::XPointer,
|
||||
) {
|
||||
let client_data = unsafe { &mut *(client_data as *mut ImeContextClientData) };
|
||||
|
||||
// Drop text buffer and reset cursor position on done.
|
||||
client_data.text = Vec::new();
|
||||
client_data.cursor_pos = 0;
|
||||
|
||||
client_data
|
||||
.event_sender
|
||||
.send((client_data.window, ImeEvent::End))
|
||||
.expect("failed to send preedit end event");
|
||||
}
|
||||
|
||||
fn calc_byte_position(text: &[char], pos: usize) -> usize {
|
||||
text.iter().take(pos).fold(0, |byte_pos, text| byte_pos + text.len_utf8())
|
||||
}
|
||||
|
||||
/// Preedit text information to be drawn inline by the client.
|
||||
extern "C" fn preedit_draw_callback(
|
||||
_xim: ffi::XIM,
|
||||
client_data: ffi::XPointer,
|
||||
call_data: ffi::XPointer,
|
||||
) {
|
||||
let client_data = unsafe { &mut *(client_data as *mut ImeContextClientData) };
|
||||
let call_data = unsafe { &mut *(call_data as *mut XIMPreeditDrawCallbackStruct) };
|
||||
client_data.cursor_pos = call_data.caret as usize;
|
||||
|
||||
let chg_range =
|
||||
call_data.chg_first as usize..(call_data.chg_first + call_data.chg_length) as usize;
|
||||
if chg_range.start > client_data.text.len() || chg_range.end > client_data.text.len() {
|
||||
tracing::warn!(
|
||||
"invalid chg range: buffer length={}, but chg_first={} chg_lengthg={}",
|
||||
client_data.text.len(),
|
||||
call_data.chg_first,
|
||||
call_data.chg_length
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// NULL indicate text deletion
|
||||
let mut new_chars = if call_data.text.is_null() {
|
||||
Vec::new()
|
||||
} else {
|
||||
let xim_text = unsafe { &mut *(call_data.text) };
|
||||
if xim_text.encoding_is_wchar > 0 {
|
||||
return;
|
||||
}
|
||||
|
||||
let new_text = unsafe { xim_text.string.multi_byte };
|
||||
|
||||
if new_text.is_null() {
|
||||
return;
|
||||
}
|
||||
|
||||
let new_text = unsafe { CStr::from_ptr(new_text) };
|
||||
|
||||
String::from(new_text.to_str().expect("Invalid UTF-8 String from IME")).chars().collect()
|
||||
};
|
||||
let mut old_text_tail = client_data.text.split_off(chg_range.end);
|
||||
client_data.text.truncate(chg_range.start);
|
||||
client_data.text.append(&mut new_chars);
|
||||
client_data.text.append(&mut old_text_tail);
|
||||
let cursor_byte_pos = calc_byte_position(&client_data.text, client_data.cursor_pos);
|
||||
|
||||
client_data
|
||||
.event_sender
|
||||
.send((
|
||||
client_data.window,
|
||||
ImeEvent::Update(client_data.text.iter().collect(), cursor_byte_pos),
|
||||
))
|
||||
.expect("failed to send preedit update event");
|
||||
}
|
||||
|
||||
/// Handling of cursor movements in preedit text.
|
||||
extern "C" fn preedit_caret_callback(
|
||||
_xim: ffi::XIM,
|
||||
client_data: ffi::XPointer,
|
||||
call_data: ffi::XPointer,
|
||||
) {
|
||||
let client_data = unsafe { &mut *(client_data as *mut ImeContextClientData) };
|
||||
let call_data = unsafe { &mut *(call_data as *mut XIMPreeditCaretCallbackStruct) };
|
||||
|
||||
if call_data.direction == ffi::XIMCaretDirection::XIMAbsolutePosition {
|
||||
client_data.cursor_pos = call_data.position as usize;
|
||||
let cursor_byte_pos = calc_byte_position(&client_data.text, client_data.cursor_pos);
|
||||
|
||||
client_data
|
||||
.event_sender
|
||||
.send((
|
||||
client_data.window,
|
||||
ImeEvent::Update(client_data.text.iter().collect(), cursor_byte_pos),
|
||||
))
|
||||
.expect("failed to send preedit update event");
|
||||
}
|
||||
}
|
||||
|
||||
/// Struct to simplify callback creation and latter passing into Xlib XIM.
|
||||
struct PreeditCallbacks {
|
||||
start_callback: ffi::XIMCallback,
|
||||
done_callback: ffi::XIMCallback,
|
||||
draw_callback: ffi::XIMCallback,
|
||||
caret_callback: ffi::XIMCallback,
|
||||
}
|
||||
|
||||
impl PreeditCallbacks {
|
||||
pub fn new(client_data: ffi::XPointer) -> PreeditCallbacks {
|
||||
let start_callback = create_xim_callback(client_data, unsafe {
|
||||
mem::transmute::<usize, unsafe extern "C" fn(ffi::XIM, ffi::XPointer, ffi::XPointer)>(
|
||||
preedit_start_callback as usize,
|
||||
)
|
||||
});
|
||||
let done_callback = create_xim_callback(client_data, preedit_done_callback);
|
||||
let caret_callback = create_xim_callback(client_data, preedit_caret_callback);
|
||||
let draw_callback = create_xim_callback(client_data, preedit_draw_callback);
|
||||
|
||||
PreeditCallbacks { start_callback, done_callback, caret_callback, draw_callback }
|
||||
}
|
||||
}
|
||||
|
||||
struct ImeContextClientData {
|
||||
window: ffi::Window,
|
||||
event_sender: ImeEventSender,
|
||||
text: Vec<char>,
|
||||
cursor_pos: usize,
|
||||
}
|
||||
|
||||
// XXX: 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`.
|
||||
pub struct ImeContext {
|
||||
pub(crate) ic: ffi::XIC,
|
||||
pub(crate) ic_area: ffi::XRectangle,
|
||||
pub(crate) allowed: bool,
|
||||
// Since the data is passed shared between X11 XIM callbacks, but couldn't be directly free
|
||||
// from there we keep the pointer to automatically deallocate it.
|
||||
_client_data: Box<ImeContextClientData>,
|
||||
}
|
||||
|
||||
impl ImeContext {
|
||||
pub(crate) unsafe fn new(
|
||||
xconn: &Arc<XConnection>,
|
||||
im: &InputMethod,
|
||||
window: ffi::Window,
|
||||
ic_area: Option<ffi::XRectangle>,
|
||||
event_sender: ImeEventSender,
|
||||
allowed: bool,
|
||||
) -> Result<Self, ImeContextCreationError> {
|
||||
let client_data = Box::into_raw(Box::new(ImeContextClientData {
|
||||
window,
|
||||
event_sender,
|
||||
text: Vec::new(),
|
||||
cursor_pos: 0,
|
||||
}));
|
||||
|
||||
let style = if allowed { im.preedit_style } else { im.none_style };
|
||||
|
||||
let ic = match style as _ {
|
||||
Style::Preedit(style) => unsafe {
|
||||
ImeContext::create_preedit_ic(
|
||||
xconn,
|
||||
im.im,
|
||||
style,
|
||||
window,
|
||||
client_data as ffi::XPointer,
|
||||
)
|
||||
},
|
||||
Style::Nothing(style) => unsafe {
|
||||
ImeContext::create_nothing_ic(xconn, im.im, style, window)
|
||||
},
|
||||
Style::None(style) => unsafe {
|
||||
ImeContext::create_none_ic(xconn, im.im, style, window)
|
||||
},
|
||||
}
|
||||
.ok_or(ImeContextCreationError::Null)?;
|
||||
|
||||
xconn.check_errors().map_err(ImeContextCreationError::XError)?;
|
||||
|
||||
let mut context = ImeContext {
|
||||
ic,
|
||||
ic_area: ffi::XRectangle { x: 0, y: 0, width: 0, height: 0 },
|
||||
allowed,
|
||||
_client_data: unsafe { Box::from_raw(client_data) },
|
||||
};
|
||||
|
||||
// Set the preedit cursor area, if it's present.
|
||||
if let Some(ic_area) = ic_area {
|
||||
context.set_area(xconn, ic_area.x, ic_area.y, ic_area.width, ic_area.height);
|
||||
}
|
||||
|
||||
Ok(context)
|
||||
}
|
||||
|
||||
unsafe fn create_none_ic(
|
||||
xconn: &Arc<XConnection>,
|
||||
im: ffi::XIM,
|
||||
style: XIMStyle,
|
||||
window: ffi::Window,
|
||||
) -> Option<ffi::XIC> {
|
||||
let ic = unsafe {
|
||||
(xconn.xlib.XCreateIC)(
|
||||
im,
|
||||
ffi::XNInputStyle_0.as_ptr() as *const _,
|
||||
style,
|
||||
ffi::XNClientWindow_0.as_ptr() as *const _,
|
||||
window,
|
||||
ptr::null_mut::<()>(),
|
||||
)
|
||||
};
|
||||
|
||||
(!ic.is_null()).then_some(ic)
|
||||
}
|
||||
|
||||
unsafe fn create_preedit_ic(
|
||||
xconn: &Arc<XConnection>,
|
||||
im: ffi::XIM,
|
||||
style: XIMStyle,
|
||||
window: ffi::Window,
|
||||
client_data: ffi::XPointer,
|
||||
) -> Option<ffi::XIC> {
|
||||
let preedit_callbacks = PreeditCallbacks::new(client_data);
|
||||
let preedit_attr = util::memory::XSmartPointer::new(xconn, unsafe {
|
||||
(xconn.xlib.XVaCreateNestedList)(
|
||||
0,
|
||||
ffi::XNPreeditStartCallback_0.as_ptr() as *const _,
|
||||
&(preedit_callbacks.start_callback) as *const _,
|
||||
ffi::XNPreeditDoneCallback_0.as_ptr() as *const _,
|
||||
&(preedit_callbacks.done_callback) as *const _,
|
||||
ffi::XNPreeditCaretCallback_0.as_ptr() as *const _,
|
||||
&(preedit_callbacks.caret_callback) as *const _,
|
||||
ffi::XNPreeditDrawCallback_0.as_ptr() as *const _,
|
||||
&(preedit_callbacks.draw_callback) as *const _,
|
||||
ptr::null_mut::<()>(),
|
||||
)
|
||||
})
|
||||
.expect("XVaCreateNestedList returned NULL");
|
||||
|
||||
let ic = unsafe {
|
||||
(xconn.xlib.XCreateIC)(
|
||||
im,
|
||||
ffi::XNInputStyle_0.as_ptr() as *const _,
|
||||
style,
|
||||
ffi::XNClientWindow_0.as_ptr() as *const _,
|
||||
window,
|
||||
ffi::XNPreeditAttributes_0.as_ptr() as *const _,
|
||||
preedit_attr.ptr,
|
||||
ptr::null_mut::<()>(),
|
||||
)
|
||||
};
|
||||
|
||||
(!ic.is_null()).then_some(ic)
|
||||
}
|
||||
|
||||
unsafe fn create_nothing_ic(
|
||||
xconn: &Arc<XConnection>,
|
||||
im: ffi::XIM,
|
||||
style: XIMStyle,
|
||||
window: ffi::Window,
|
||||
) -> Option<ffi::XIC> {
|
||||
let ic = unsafe {
|
||||
(xconn.xlib.XCreateIC)(
|
||||
im,
|
||||
ffi::XNInputStyle_0.as_ptr() as *const _,
|
||||
style,
|
||||
ffi::XNClientWindow_0.as_ptr() as *const _,
|
||||
window,
|
||||
ptr::null_mut::<()>(),
|
||||
)
|
||||
};
|
||||
|
||||
(!ic.is_null()).then_some(ic)
|
||||
}
|
||||
|
||||
pub(crate) fn focus(&self, xconn: &Arc<XConnection>) -> Result<(), XError> {
|
||||
unsafe {
|
||||
(xconn.xlib.XSetICFocus)(self.ic);
|
||||
}
|
||||
xconn.check_errors()
|
||||
}
|
||||
|
||||
pub(crate) fn unfocus(&self, xconn: &Arc<XConnection>) -> Result<(), XError> {
|
||||
unsafe {
|
||||
(xconn.xlib.XUnsetICFocus)(self.ic);
|
||||
}
|
||||
xconn.check_errors()
|
||||
}
|
||||
|
||||
pub fn is_allowed(&self) -> bool {
|
||||
self.allowed
|
||||
}
|
||||
|
||||
/// Set the spot and area for preedit text.
|
||||
///
|
||||
/// This functionality depends on the libx11 version.
|
||||
/// - Until libx11 1.8.2, XNSpotLocation was blocked by libx11 in On-The-Spot mode.
|
||||
/// - Until libx11 1.8.11, XNArea was blocked by libx11 in On-The-Spot mode.
|
||||
///
|
||||
/// Use of this information is discretionary by input method servers,
|
||||
/// and some may not use it by default, even if they have support.
|
||||
pub(crate) fn set_area(
|
||||
&mut self,
|
||||
xconn: &Arc<XConnection>,
|
||||
x: i16,
|
||||
y: i16,
|
||||
width: u16,
|
||||
height: u16,
|
||||
) {
|
||||
let ic_area = ffi::XRectangle { x, y, width, height };
|
||||
|
||||
if !self.is_allowed() || self.ic_area == ic_area {
|
||||
return;
|
||||
}
|
||||
|
||||
self.ic_area = ic_area;
|
||||
let ic_spot =
|
||||
ffi::XPoint { x: x.saturating_add(width as i16), y: y.saturating_add(height as i16) };
|
||||
|
||||
unsafe {
|
||||
let preedit_attr = util::memory::XSmartPointer::new(
|
||||
xconn,
|
||||
(xconn.xlib.XVaCreateNestedList)(
|
||||
0,
|
||||
ffi::XNSpotLocation_0.as_ptr(),
|
||||
&ic_spot,
|
||||
ffi::XNArea_0.as_ptr(),
|
||||
&self.ic_area,
|
||||
ptr::null_mut::<()>(),
|
||||
),
|
||||
)
|
||||
.expect("XVaCreateNestedList returned NULL");
|
||||
|
||||
(xconn.xlib.XSetICValues)(
|
||||
self.ic,
|
||||
ffi::XNPreeditAttributes_0.as_ptr() as *const _,
|
||||
preedit_attr.ptr,
|
||||
ptr::null_mut::<()>(),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
74
winit-x11/src/ime/inner.rs
Normal file
74
winit-x11/src/ime/inner.rs
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
use std::collections::HashMap;
|
||||
use std::mem;
|
||||
use std::sync::Arc;
|
||||
|
||||
use super::context::ImeContext;
|
||||
use super::input_method::{InputMethod, PotentialInputMethods};
|
||||
use super::{ffi, ImeEventSender};
|
||||
use crate::xdisplay::{XConnection, XError};
|
||||
|
||||
pub(crate) unsafe fn close_im(xconn: &Arc<XConnection>, im: ffi::XIM) -> Result<(), XError> {
|
||||
unsafe { (xconn.xlib.XCloseIM)(im) };
|
||||
xconn.check_errors()
|
||||
}
|
||||
|
||||
pub(crate) unsafe fn destroy_ic(xconn: &Arc<XConnection>, ic: ffi::XIC) -> Result<(), XError> {
|
||||
unsafe { (xconn.xlib.XDestroyIC)(ic) };
|
||||
xconn.check_errors()
|
||||
}
|
||||
|
||||
pub(crate) struct ImeInner {
|
||||
pub xconn: Arc<XConnection>,
|
||||
pub im: Option<InputMethod>,
|
||||
pub potential_input_methods: PotentialInputMethods,
|
||||
pub contexts: HashMap<ffi::Window, Option<ImeContext>>,
|
||||
// WARNING: this is initially zeroed!
|
||||
pub destroy_callback: ffi::XIMCallback,
|
||||
pub event_sender: ImeEventSender,
|
||||
// Indicates whether or not 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(crate) fn new(
|
||||
xconn: Arc<XConnection>,
|
||||
potential_input_methods: PotentialInputMethods,
|
||||
event_sender: ImeEventSender,
|
||||
) -> Self {
|
||||
ImeInner {
|
||||
xconn,
|
||||
im: None,
|
||||
potential_input_methods,
|
||||
contexts: HashMap::new(),
|
||||
destroy_callback: unsafe { mem::zeroed() },
|
||||
event_sender,
|
||||
is_destroyed: false,
|
||||
is_fallback: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub unsafe fn close_im_if_necessary(&self) -> Result<bool, XError> {
|
||||
if !self.is_destroyed && self.im.is_some() {
|
||||
unsafe { close_im(&self.xconn, self.im.as_ref().unwrap().im) }.map(|_| true)
|
||||
} else {
|
||||
Ok(false)
|
||||
}
|
||||
}
|
||||
|
||||
pub unsafe fn destroy_ic_if_necessary(&self, ic: ffi::XIC) -> Result<bool, XError> {
|
||||
if !self.is_destroyed {
|
||||
unsafe { 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().flatten() {
|
||||
unsafe { self.destroy_ic_if_necessary(context.ic)? };
|
||||
}
|
||||
Ok(!self.is_destroyed)
|
||||
}
|
||||
}
|
||||
347
winit-x11/src/ime/input_method.rs
Normal file
347
winit-x11/src/ime/input_method.rs
Normal file
|
|
@ -0,0 +1,347 @@
|
|||
use std::ffi::{CStr, CString, IntoStringError};
|
||||
use std::os::raw::{c_char, c_ulong, c_ushort};
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::{env, fmt, ptr};
|
||||
|
||||
use x11rb::protocol::xproto;
|
||||
|
||||
use super::super::atoms::*;
|
||||
use super::{ffi, util};
|
||||
use crate::xdisplay::{XConnection, XError};
|
||||
|
||||
static GLOBAL_LOCK: Mutex<()> = Mutex::new(());
|
||||
|
||||
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 or if the current locale is not supported
|
||||
// by Xlib.
|
||||
unsafe { (xconn.xlib.XSetLocaleModifiers)(locale_modifiers.as_ptr()) };
|
||||
|
||||
let im = unsafe {
|
||||
(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,
|
||||
pub preedit_style: Style,
|
||||
pub none_style: Style,
|
||||
_name: String,
|
||||
}
|
||||
|
||||
impl InputMethod {
|
||||
fn new(xconn: &Arc<XConnection>, im: ffi::XIM, name: String) -> Option<Self> {
|
||||
let mut styles: *mut XIMStyles = std::ptr::null_mut();
|
||||
|
||||
// Query the styles supported by the XIM.
|
||||
unsafe {
|
||||
if !(xconn.xlib.XGetIMValues)(
|
||||
im,
|
||||
ffi::XNQueryInputStyle_0.as_ptr() as *const _,
|
||||
(&mut styles) as *mut _,
|
||||
std::ptr::null_mut::<()>(),
|
||||
)
|
||||
.is_null()
|
||||
{
|
||||
return None;
|
||||
}
|
||||
}
|
||||
|
||||
let mut preedit_style = None;
|
||||
let mut none_style = None;
|
||||
|
||||
unsafe {
|
||||
std::slice::from_raw_parts((*styles).supported_styles, (*styles).count_styles as _)
|
||||
.iter()
|
||||
.for_each(|style| match *style {
|
||||
XIM_PREEDIT_STYLE => {
|
||||
preedit_style = Some(Style::Preedit(*style));
|
||||
},
|
||||
XIM_NOTHING_STYLE if preedit_style.is_none() => {
|
||||
preedit_style = Some(Style::Nothing(*style))
|
||||
},
|
||||
XIM_NONE_STYLE => none_style = Some(Style::None(*style)),
|
||||
_ => (),
|
||||
});
|
||||
|
||||
(xconn.xlib.XFree)(styles.cast());
|
||||
};
|
||||
|
||||
if preedit_style.is_none() && none_style.is_none() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let preedit_style = preedit_style.unwrap_or_else(|| none_style.unwrap());
|
||||
let none_style = none_style.unwrap_or(preedit_style);
|
||||
|
||||
Some(InputMethod { im, _name: name, preedit_style, none_style })
|
||||
}
|
||||
}
|
||||
|
||||
const XIM_PREEDIT_STYLE: XIMStyle = (ffi::XIMPreeditCallbacks | ffi::XIMStatusNothing) as XIMStyle;
|
||||
const XIM_NOTHING_STYLE: XIMStyle = (ffi::XIMPreeditNothing | ffi::XIMStatusNothing) as XIMStyle;
|
||||
const XIM_NONE_STYLE: XIMStyle = (ffi::XIMPreeditNone | ffi::XIMStatusNone) as XIMStyle;
|
||||
|
||||
/// Style of the IME context.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum Style {
|
||||
/// Preedit callbacks.
|
||||
Preedit(XIMStyle),
|
||||
|
||||
/// Nothing.
|
||||
Nothing(XIMStyle),
|
||||
|
||||
/// No IME.
|
||||
None(XIMStyle),
|
||||
}
|
||||
|
||||
impl Default for Style {
|
||||
fn default() -> Self {
|
||||
Style::None(XIM_NONE_STYLE)
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug)]
|
||||
struct XIMStyles {
|
||||
count_styles: c_ushort,
|
||||
supported_styles: *const XIMStyle,
|
||||
}
|
||||
|
||||
pub(crate) type XIMStyle = c_ulong;
|
||||
|
||||
#[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 {
|
||||
matches!(self, InputMethodResult::Fallback(_))
|
||||
}
|
||||
|
||||
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(#[allow(dead_code)] XError),
|
||||
GetPropertyError(#[allow(dead_code)] util::GetPropertyError),
|
||||
InvalidUtf8(#[allow(dead_code)] IntoStringError),
|
||||
}
|
||||
|
||||
impl From<util::GetPropertyError> for GetXimServersError {
|
||||
fn from(error: util::GetPropertyError) -> Self {
|
||||
GetXimServersError::GetPropertyError(error)
|
||||
}
|
||||
}
|
||||
|
||||
// The root window has a property named XIM_SERVERS, which contains a list of atoms representing
|
||||
// the available 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 atoms = xconn.atoms();
|
||||
let servers_atom = atoms[XIM_SERVERS];
|
||||
|
||||
let root = unsafe { (xconn.xlib.XDefaultRootWindow)(xconn.display) };
|
||||
|
||||
let mut atoms: Vec<ffi::Atom> = xconn
|
||||
.get_property::<xproto::Atom>(
|
||||
root as xproto::Window,
|
||||
servers_atom,
|
||||
xproto::Atom::from(xproto::AtomEnum::ATOM),
|
||||
)
|
||||
.map_err(GetXimServersError::GetPropertyError)?
|
||||
.into_iter()
|
||||
.map(|atom| atom as _)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let mut names: Vec<*const c_char> = Vec::with_capacity(atoms.len());
|
||||
unsafe {
|
||||
(xconn.xlib.XGetAtomNames)(
|
||||
xconn.display,
|
||||
atoms.as_mut_ptr(),
|
||||
atoms.len() as _,
|
||||
names.as_mut_ptr() as _,
|
||||
)
|
||||
};
|
||||
unsafe { names.set_len(atoms.len()) };
|
||||
|
||||
let mut formatted_names = Vec::with_capacity(names.len());
|
||||
for name in names {
|
||||
let string = unsafe { CStr::from_ptr(name) }
|
||||
.to_owned()
|
||||
.into_string()
|
||||
.map_err(GetXimServersError::InvalidUtf8)?;
|
||||
unsafe { (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.and_then(|im| InputMethod::new(xconn, 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 succeeded.
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct PotentialInputMethods {
|
||||
// On correctly configured systems, the XMODIFIERS environment 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 sequences, 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<&dyn 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
|
||||
}
|
||||
}
|
||||
241
winit-x11/src/ime/mod.rs
Normal file
241
winit-x11/src/ime/mod.rs
Normal file
|
|
@ -0,0 +1,241 @@
|
|||
// Important: all XIM calls need to happen from the same thread!
|
||||
|
||||
mod callbacks;
|
||||
mod context;
|
||||
mod inner;
|
||||
mod input_method;
|
||||
|
||||
use std::fmt;
|
||||
use std::sync::mpsc::{Receiver, Sender};
|
||||
use std::sync::Arc;
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use self::callbacks::*;
|
||||
use self::context::ImeContext;
|
||||
pub use self::context::ImeContextCreationError;
|
||||
use self::inner::{close_im, ImeInner};
|
||||
use self::input_method::PotentialInputMethods;
|
||||
use crate::xdisplay::{XConnection, XError};
|
||||
use crate::{ffi, util};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
pub enum ImeEvent {
|
||||
Enabled,
|
||||
Start,
|
||||
Update(String, usize),
|
||||
End,
|
||||
Disabled,
|
||||
}
|
||||
|
||||
pub type ImeReceiver = Receiver<ImeRequest>;
|
||||
pub type ImeSender = Sender<ImeRequest>;
|
||||
pub type ImeEventReceiver = Receiver<(ffi::Window, ImeEvent)>;
|
||||
pub type ImeEventSender = Sender<(ffi::Window, ImeEvent)>;
|
||||
|
||||
/// Request to control XIM handler from the window.
|
||||
pub enum ImeRequest {
|
||||
/// Set IME preedit area for given `window_id`.
|
||||
Area(ffi::Window, i16, i16, u16, u16),
|
||||
|
||||
/// Allow IME input for the given `window_id`.
|
||||
Allow(ffi::Window, bool),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) enum ImeCreationError {
|
||||
// Boxed to prevent large error type
|
||||
OpenFailure(Box<PotentialInputMethods>),
|
||||
SetDestroyCallbackFailed(#[allow(dead_code)] XError),
|
||||
}
|
||||
|
||||
pub(crate) 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 fmt::Debug for Ime {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("Ime").finish_non_exhaustive()
|
||||
}
|
||||
}
|
||||
|
||||
impl Ime {
|
||||
pub fn new(
|
||||
xconn: Arc<XConnection>,
|
||||
event_sender: ImeEventSender,
|
||||
) -> 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, event_sender));
|
||||
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.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?;
|
||||
}
|
||||
inner.im = Some(input_method);
|
||||
Ok(Ime { xconn, inner })
|
||||
} else {
|
||||
Err(ImeCreationError::OpenFailure(Box::new(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,
|
||||
with_ime: bool,
|
||||
) -> 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 {
|
||||
let im = self.inner.im.as_ref().unwrap();
|
||||
|
||||
let context = unsafe {
|
||||
ImeContext::new(
|
||||
&self.inner.xconn,
|
||||
im,
|
||||
window,
|
||||
None,
|
||||
self.inner.event_sender.clone(),
|
||||
with_ime,
|
||||
)?
|
||||
};
|
||||
|
||||
let event = if context.is_allowed() { ImeEvent::Enabled } else { ImeEvent::Disabled };
|
||||
self.inner.event_sender.send((window, event)).expect("Failed to send enabled event");
|
||||
|
||||
Some(context)
|
||||
};
|
||||
|
||||
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(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_area(&mut self, window: ffi::Window, x: i16, y: i16, w: u16, h: u16) {
|
||||
if self.is_destroyed() {
|
||||
return;
|
||||
}
|
||||
if let Some(&mut Some(ref mut context)) = self.inner.contexts.get_mut(&window) {
|
||||
context.set_area(&self.xconn, x as _, y as _, w as _, h as _);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_ime_allowed(&mut self, window: ffi::Window, allowed: bool) {
|
||||
if self.is_destroyed() {
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some(&mut Some(ref mut context)) = self.inner.contexts.get_mut(&window) {
|
||||
if allowed == context.is_allowed() {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Remove context for that window.
|
||||
let _ = self.remove_context(window);
|
||||
|
||||
// Create new context supporting IME input.
|
||||
let _ = self.create_context(window, allowed);
|
||||
}
|
||||
|
||||
pub fn is_ime_allowed(&self, window: ffi::Window) -> bool {
|
||||
if self.is_destroyed() {
|
||||
false
|
||||
} else if let Some(Some(context)) = self.inner.contexts.get(&window) {
|
||||
context.is_allowed()
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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