2022-07-30 17:15:57 +03:00
|
|
|
use std::ffi::CStr;
|
2022-05-07 05:29:25 +03:00
|
|
|
use std::os::raw::c_short;
|
|
|
|
|
use std::sync::Arc;
|
2022-07-30 17:15:57 +03:00
|
|
|
use std::{mem, ptr};
|
2018-04-10 22:18:30 -04:00
|
|
|
|
2022-05-07 05:29:25 +03:00
|
|
|
use x11_dl::xlib::{XIMCallback, XIMPreeditCaretCallbackStruct, XIMPreeditDrawCallbackStruct};
|
2018-04-10 22:18:30 -04:00
|
|
|
|
2022-09-11 19:36:56 +03:00
|
|
|
use crate::platform_impl::platform::x11::ime::input_method::{Style, XIMStyle};
|
2022-07-30 17:15:57 +03:00
|
|
|
use crate::platform_impl::platform::x11::ime::{ImeEvent, ImeEventSender};
|
|
|
|
|
|
|
|
|
|
use super::{ffi, util, XConnection, XError};
|
|
|
|
|
|
2022-05-07 05:29:25 +03:00
|
|
|
/// IME creation error.
|
2018-04-10 22:18:30 -04:00
|
|
|
#[derive(Debug)]
|
|
|
|
|
pub enum ImeContextCreationError {
|
2022-05-07 05:29:25 +03:00
|
|
|
/// Got the error from Xlib.
|
2018-04-10 22:18:30 -04:00
|
|
|
XError(XError),
|
2022-05-07 05:29:25 +03:00
|
|
|
|
|
|
|
|
/// Got null pointer from Xlib but without exact reason.
|
2018-04-10 22:18:30 -04:00
|
|
|
Null,
|
|
|
|
|
}
|
|
|
|
|
|
2022-05-07 05:29:25 +03:00
|
|
|
/// 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");
|
|
|
|
|
}
|
|
|
|
|
|
2022-06-10 13:43:33 +03:00
|
|
|
fn calc_byte_position(text: &[char], pos: usize) -> usize {
|
|
|
|
|
text.iter()
|
|
|
|
|
.take(pos)
|
|
|
|
|
.fold(0, |byte_pos, text| byte_pos + text.len_utf8())
|
2018-04-10 22:18:30 -04:00
|
|
|
}
|
|
|
|
|
|
2022-05-07 05:29:25 +03:00
|
|
|
/// 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() {
|
|
|
|
|
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;
|
|
|
|
|
}
|
2022-06-08 01:04:33 +03:00
|
|
|
|
|
|
|
|
let new_text = unsafe { xim_text.string.multi_byte };
|
|
|
|
|
|
|
|
|
|
if new_text.is_null() {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let new_text = unsafe { CStr::from_ptr(new_text) };
|
2022-05-07 05:29:25 +03:00
|
|
|
|
|
|
|
|
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 {
|
2022-07-30 17:15:57 +03:00
|
|
|
mem::transmute(preedit_start_callback as usize)
|
2022-05-07 05:29:25 +03:00
|
|
|
});
|
|
|
|
|
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.
|
2018-04-10 22:18:30 -04:00
|
|
|
// 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 {
|
2022-09-11 19:36:56 +03:00
|
|
|
pub(crate) ic: ffi::XIC,
|
|
|
|
|
pub(crate) ic_spot: ffi::XPoint,
|
|
|
|
|
pub(crate) style: Style,
|
2022-05-07 05:29:25 +03:00
|
|
|
// Since the data is passed shared between X11 XIM callbacks, but couldn't be direclty free from
|
|
|
|
|
// there we keep the pointer to automatically deallocate it.
|
|
|
|
|
_client_data: Box<ImeContextClientData>,
|
2018-04-10 22:18:30 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl ImeContext {
|
|
|
|
|
pub unsafe fn new(
|
|
|
|
|
xconn: &Arc<XConnection>,
|
|
|
|
|
im: ffi::XIM,
|
2022-09-11 19:36:56 +03:00
|
|
|
style: Style,
|
2018-04-10 22:18:30 -04:00
|
|
|
window: ffi::Window,
|
|
|
|
|
ic_spot: Option<ffi::XPoint>,
|
2022-05-07 05:29:25 +03:00
|
|
|
event_sender: ImeEventSender,
|
2018-04-10 22:18:30 -04:00
|
|
|
) -> Result<Self, ImeContextCreationError> {
|
2022-05-07 05:29:25 +03:00
|
|
|
let client_data = Box::into_raw(Box::new(ImeContextClientData {
|
|
|
|
|
window,
|
|
|
|
|
event_sender,
|
|
|
|
|
text: Vec::new(),
|
|
|
|
|
cursor_pos: 0,
|
|
|
|
|
}));
|
|
|
|
|
|
2022-09-11 19:36:56 +03:00
|
|
|
let ic = match style as _ {
|
|
|
|
|
Style::Preedit(style) => ImeContext::create_preedit_ic(
|
|
|
|
|
xconn,
|
|
|
|
|
im,
|
|
|
|
|
style,
|
|
|
|
|
window,
|
|
|
|
|
client_data as ffi::XPointer,
|
|
|
|
|
),
|
|
|
|
|
Style::Nothing(style) => ImeContext::create_nothing_ic(xconn, im, style, window),
|
|
|
|
|
Style::None(style) => ImeContext::create_none_ic(xconn, im, style, window),
|
|
|
|
|
}
|
|
|
|
|
.ok_or(ImeContextCreationError::Null)?;
|
2018-04-10 22:18:30 -04:00
|
|
|
|
2019-06-21 11:33:15 -04:00
|
|
|
xconn
|
|
|
|
|
.check_errors()
|
|
|
|
|
.map_err(ImeContextCreationError::XError)?;
|
2018-04-10 22:18:30 -04:00
|
|
|
|
2022-05-07 05:29:25 +03:00
|
|
|
let mut context = ImeContext {
|
2018-04-10 22:18:30 -04:00
|
|
|
ic,
|
2022-05-07 05:29:25 +03:00
|
|
|
ic_spot: ffi::XPoint { x: 0, y: 0 },
|
2022-09-11 19:36:56 +03:00
|
|
|
style,
|
2022-05-07 05:29:25 +03:00
|
|
|
_client_data: Box::from_raw(client_data),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Set the spot location, if it's present.
|
|
|
|
|
if let Some(ic_spot) = ic_spot {
|
|
|
|
|
context.set_spot(xconn, ic_spot.x, ic_spot.y)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Ok(context)
|
2018-04-10 22:18:30 -04:00
|
|
|
}
|
|
|
|
|
|
2022-05-07 05:29:25 +03:00
|
|
|
unsafe fn create_none_ic(
|
2018-04-10 22:18:30 -04:00
|
|
|
xconn: &Arc<XConnection>,
|
|
|
|
|
im: ffi::XIM,
|
2022-09-11 19:36:56 +03:00
|
|
|
style: XIMStyle,
|
2018-04-10 22:18:30 -04:00
|
|
|
window: ffi::Window,
|
|
|
|
|
) -> Option<ffi::XIC> {
|
|
|
|
|
let ic = (xconn.xlib.XCreateIC)(
|
|
|
|
|
im,
|
|
|
|
|
ffi::XNInputStyle_0.as_ptr() as *const _,
|
2022-09-11 19:36:56 +03:00
|
|
|
style,
|
2018-04-10 22:18:30 -04:00
|
|
|
ffi::XNClientWindow_0.as_ptr() as *const _,
|
|
|
|
|
window,
|
|
|
|
|
ptr::null_mut::<()>(),
|
|
|
|
|
);
|
2022-05-07 05:29:25 +03:00
|
|
|
|
|
|
|
|
(!ic.is_null()).then(|| ic)
|
2018-04-10 22:18:30 -04:00
|
|
|
}
|
|
|
|
|
|
2022-09-11 19:36:56 +03:00
|
|
|
unsafe fn create_preedit_ic(
|
2018-04-10 22:18:30 -04:00
|
|
|
xconn: &Arc<XConnection>,
|
|
|
|
|
im: ffi::XIM,
|
2022-09-11 19:36:56 +03:00
|
|
|
style: XIMStyle,
|
2018-04-10 22:18:30 -04:00
|
|
|
window: ffi::Window,
|
2022-05-07 05:29:25 +03:00
|
|
|
client_data: ffi::XPointer,
|
2018-04-10 22:18:30 -04:00
|
|
|
) -> Option<ffi::XIC> {
|
2022-05-07 05:29:25 +03:00
|
|
|
let preedit_callbacks = PreeditCallbacks::new(client_data);
|
|
|
|
|
let preedit_attr = util::XSmartPointer::new(
|
|
|
|
|
xconn,
|
|
|
|
|
(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");
|
|
|
|
|
|
2022-09-11 19:36:56 +03:00
|
|
|
let ic = (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::<()>(),
|
|
|
|
|
);
|
2022-05-07 05:29:25 +03:00
|
|
|
|
2022-09-11 19:36:56 +03:00
|
|
|
(!ic.is_null()).then(|| ic)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
unsafe fn create_nothing_ic(
|
|
|
|
|
xconn: &Arc<XConnection>,
|
|
|
|
|
im: ffi::XIM,
|
|
|
|
|
style: XIMStyle,
|
|
|
|
|
window: ffi::Window,
|
|
|
|
|
) -> Option<ffi::XIC> {
|
|
|
|
|
let ic = (xconn.xlib.XCreateIC)(
|
|
|
|
|
im,
|
|
|
|
|
ffi::XNInputStyle_0.as_ptr() as *const _,
|
|
|
|
|
style,
|
|
|
|
|
ffi::XNClientWindow_0.as_ptr() as *const _,
|
|
|
|
|
window,
|
|
|
|
|
ptr::null_mut::<()>(),
|
|
|
|
|
);
|
2022-05-07 05:29:25 +03:00
|
|
|
|
|
|
|
|
(!ic.is_null()).then(|| ic)
|
2018-04-10 22:18:30 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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()
|
|
|
|
|
}
|
|
|
|
|
|
2022-09-11 19:36:56 +03:00
|
|
|
pub fn is_allowed(&self) -> bool {
|
|
|
|
|
!matches!(self.style, Style::None(_))
|
|
|
|
|
}
|
|
|
|
|
|
2022-05-07 05:29:25 +03:00
|
|
|
// Set the spot for preedit text. Setting spot isn't working with libX11 when preedit callbacks
|
|
|
|
|
// are being used. Certain IMEs do show selection window, but it's placed in bottom left of the
|
|
|
|
|
// window and couldn't be changed.
|
|
|
|
|
//
|
|
|
|
|
// For me see: https://bugs.freedesktop.org/show_bug.cgi?id=1580.
|
2018-04-10 22:18:30 -04:00
|
|
|
pub fn set_spot(&mut self, xconn: &Arc<XConnection>, x: c_short, y: c_short) {
|
2022-09-11 19:36:56 +03:00
|
|
|
if !self.is_allowed() || self.ic_spot.x == x && self.ic_spot.y == y {
|
2018-04-10 22:18:30 -04:00
|
|
|
return;
|
|
|
|
|
}
|
2022-05-07 05:29:25 +03:00
|
|
|
|
2018-04-10 22:18:30 -04:00
|
|
|
self.ic_spot = ffi::XPoint { x, y };
|
|
|
|
|
|
|
|
|
|
unsafe {
|
2022-05-07 05:29:25 +03:00
|
|
|
let preedit_attr = util::XSmartPointer::new(
|
|
|
|
|
xconn,
|
|
|
|
|
(xconn.xlib.XVaCreateNestedList)(
|
|
|
|
|
0,
|
|
|
|
|
ffi::XNSpotLocation_0.as_ptr(),
|
|
|
|
|
&self.ic_spot,
|
|
|
|
|
ptr::null_mut::<()>(),
|
|
|
|
|
),
|
|
|
|
|
)
|
|
|
|
|
.expect("XVaCreateNestedList returned NULL");
|
|
|
|
|
|
2018-04-10 22:18:30 -04:00
|
|
|
(xconn.xlib.XSetICValues)(
|
|
|
|
|
self.ic,
|
|
|
|
|
ffi::XNPreeditAttributes_0.as_ptr() as *const _,
|
2022-05-07 05:29:25 +03:00
|
|
|
preedit_attr.ptr,
|
2018-04-10 22:18:30 -04:00
|
|
|
ptr::null_mut::<()>(),
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|