Skip to content

Commit 6a27687

Browse files
committed
Cleanup X11 IME handling
This commit also fixes crash on start on XWayland due to creation failure of IMEs with preedit callbacks.
1 parent 69f9d66 commit 6a27687

File tree

1 file changed

+122
-134
lines changed

1 file changed

+122
-134
lines changed

src/platform_impl/linux/x11/ime/context.rs

+122-134
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,36 @@
1-
use std::{
2-
mem::transmute,
3-
os::raw::{c_short, c_void},
4-
ptr,
5-
sync::Arc,
6-
};
1+
use std::mem::transmute;
2+
use std::os::raw::c_short;
3+
use std::ptr;
4+
use std::sync::Arc;
75

86
use super::{ffi, util, XConnection, XError};
97
use crate::platform_impl::platform::x11::ime::{ImeEvent, ImeEventSender};
108
use std::ffi::CStr;
119
use x11_dl::xlib::{XIMCallback, XIMPreeditCaretCallbackStruct, XIMPreeditDrawCallbackStruct};
1210

11+
/// IME creation error.
1312
#[derive(Debug)]
1413
pub enum ImeContextCreationError {
14+
/// Got the error from Xlib.
1515
XError(XError),
16+
17+
/// Got null pointer from Xlib but without exact reason.
1618
Null,
1719
}
20+
21+
/// The callback used by XIM preedit functions.
1822
type XIMProcNonnull = unsafe extern "C" fn(ffi::XIM, ffi::XPointer, ffi::XPointer);
23+
24+
/// Wrapper for creating XIM callbacks.
25+
#[inline]
26+
fn create_xim_callback(client_data: ffi::XPointer, callback: XIMProcNonnull) -> ffi::XIMCallback {
27+
XIMCallback {
28+
client_data,
29+
callback: Some(callback),
30+
}
31+
}
32+
33+
/// The server started preedit.
1934
extern "C" fn preedit_start_callback(
2035
_xim: ffi::XIM,
2136
client_data: ffi::XPointer,
@@ -32,6 +47,8 @@ extern "C" fn preedit_start_callback(
3247
-1
3348
}
3449

50+
/// Done callback is called when the user finished preedit and the text is about to be inserted into
51+
/// underlying widget.
3552
extern "C" fn preedit_done_callback(
3653
_xim: ffi::XIM,
3754
client_data: ffi::XPointer,
@@ -53,6 +70,7 @@ fn calc_byte_position(text: &Vec<char>, pos: usize) -> usize {
5370
byte_pos
5471
}
5572

73+
/// Preedit text information to be drawn inline by the client.
5674
extern "C" fn preedit_draw_callback(
5775
_xim: ffi::XIM,
5876
client_data: ffi::XPointer,
@@ -103,6 +121,7 @@ extern "C" fn preedit_draw_callback(
103121
.expect("failed to send preedit update event");
104122
}
105123

124+
/// Handling of cursor movements in preedit text.
106125
extern "C" fn preedit_caret_callback(
107126
_xim: ffi::XIM,
108127
client_data: ffi::XPointer,
@@ -122,65 +141,12 @@ extern "C" fn preedit_caret_callback(
122141
.expect("failed to send preedit update event");
123142
}
124143

125-
unsafe fn create_pre_edit_attr<'a>(
126-
xconn: &'a Arc<XConnection>,
127-
preedit_callbacks: &'a PreeditCallbacks,
128-
) -> util::XSmartPointer<'a, c_void> {
129-
util::XSmartPointer::new(
130-
xconn,
131-
(xconn.xlib.XVaCreateNestedList)(
132-
0,
133-
ffi::XNPreeditStartCallback_0.as_ptr() as *const _,
134-
&(preedit_callbacks.start_callback) as *const _,
135-
ffi::XNPreeditDoneCallback_0.as_ptr() as *const _,
136-
&(preedit_callbacks.done_callback) as *const _,
137-
ffi::XNPreeditCaretCallback_0.as_ptr() as *const _,
138-
&(preedit_callbacks.caret_callback) as *const _,
139-
ffi::XNPreeditDrawCallback_0.as_ptr() as *const _,
140-
&(preedit_callbacks.draw_callback) as *const _,
141-
ptr::null_mut::<()>(),
142-
),
143-
)
144-
.expect("XVaCreateNestedList returned NULL")
145-
}
146-
147-
unsafe fn create_pre_edit_attr_with_spot<'a>(
148-
xconn: &'a Arc<XConnection>,
149-
ic_spot: &'a ffi::XPoint,
150-
preedit_callbacks: &'a PreeditCallbacks,
151-
) -> util::XSmartPointer<'a, c_void> {
152-
util::XSmartPointer::new(
153-
xconn,
154-
(xconn.xlib.XVaCreateNestedList)(
155-
0,
156-
ffi::XNSpotLocation_0.as_ptr() as *const _,
157-
ic_spot,
158-
ffi::XNPreeditStartCallback_0.as_ptr() as *const _,
159-
&preedit_callbacks.start_callback as *const _,
160-
ffi::XNPreeditDoneCallback_0.as_ptr() as *const _,
161-
&preedit_callbacks.done_callback as *const _,
162-
ffi::XNPreeditCaretCallback_0.as_ptr() as *const _,
163-
&preedit_callbacks.caret_callback as *const _,
164-
ffi::XNPreeditDrawCallback_0.as_ptr() as *const _,
165-
&preedit_callbacks.draw_callback as *const _,
166-
ptr::null_mut::<()>(),
167-
),
168-
)
169-
.expect("XVaCreateNestedList returned NULL")
170-
}
171-
172-
fn create_xim_callback(client_data: ffi::XPointer, callback: XIMProcNonnull) -> ffi::XIMCallback {
173-
XIMCallback {
174-
client_data,
175-
callback: Some(callback),
176-
}
177-
}
178-
179-
pub struct PreeditCallbacks {
180-
pub start_callback: ffi::XIMCallback,
181-
pub done_callback: ffi::XIMCallback,
182-
pub draw_callback: ffi::XIMCallback,
183-
pub caret_callback: ffi::XIMCallback,
144+
/// Struct to simplify callback creation and latter passing into Xlib XIM.
145+
struct PreeditCallbacks {
146+
start_callback: ffi::XIMCallback,
147+
done_callback: ffi::XIMCallback,
148+
draw_callback: ffi::XIMCallback,
149+
caret_callback: ffi::XIMCallback,
184150
}
185151

186152
impl PreeditCallbacks {
@@ -201,22 +167,23 @@ impl PreeditCallbacks {
201167
}
202168
}
203169

204-
pub struct ImeContextClientData {
205-
pub window: ffi::Window,
206-
pub event_sender: ImeEventSender,
207-
pub text: Vec<char>,
208-
pub cursor_pos: usize,
170+
struct ImeContextClientData {
171+
window: ffi::Window,
172+
event_sender: ImeEventSender,
173+
text: Vec<char>,
174+
cursor_pos: usize,
209175
}
210176

211-
// WARNING: this struct doesn't destroy its XIC resource when dropped.
177+
// XXX: this struct doesn't destroy its XIC resource when dropped.
212178
// This is intentional, as it doesn't have enough information to know whether or not the context
213179
// still exists on the server. Since `ImeInner` has that awareness, destruction must be handled
214180
// through `ImeInner`.
215181
pub struct ImeContext {
216-
pub ic: ffi::XIC,
217-
pub ic_spot: ffi::XPoint,
218-
pub preedit_callbacks: PreeditCallbacks,
219-
pub client_data: Box<ImeContextClientData>,
182+
pub(super) ic: ffi::XIC,
183+
pub(super) ic_spot: ffi::XPoint,
184+
// Since the data is passed shared between X11 XIM callbacks, but couldn't be direclty free from
185+
// there we keep the pointer to automatically deallocate it.
186+
_client_data: Box<ImeContextClientData>,
220187
}
221188

222189
impl ImeContext {
@@ -227,80 +194,86 @@ impl ImeContext {
227194
ic_spot: Option<ffi::XPoint>,
228195
event_sender: ImeEventSender,
229196
) -> Result<Self, ImeContextCreationError> {
230-
let client_data = Box::new(ImeContextClientData {
197+
let client_data = Box::into_raw(Box::new(ImeContextClientData {
231198
window,
232199
event_sender,
233200
text: Vec::new(),
234201
cursor_pos: 0,
235-
});
236-
let client_data_ptr = Box::into_raw(client_data);
237-
let preedit_callbacks = PreeditCallbacks::new(client_data_ptr as ffi::XPointer);
238-
let ic = if let Some(ic_spot) = ic_spot {
239-
ImeContext::create_ic_with_spot(xconn, im, window, ic_spot, &preedit_callbacks)
240-
} else {
241-
ImeContext::create_ic(xconn, im, window, &preedit_callbacks)
242-
};
202+
}));
203+
204+
let ic = ImeContext::create_ic(xconn, im, window, client_data as ffi::XPointer)
205+
.ok_or(ImeContextCreationError::Null)?;
243206

244-
let ic = ic.ok_or(ImeContextCreationError::Null)?;
245207
xconn
246208
.check_errors()
247209
.map_err(ImeContextCreationError::XError)?;
248210

249-
Ok(ImeContext {
211+
let mut context = ImeContext {
250212
ic,
251-
ic_spot: ic_spot.unwrap_or(ffi::XPoint { x: 0, y: 0 }),
252-
preedit_callbacks,
253-
client_data: Box::from_raw(client_data_ptr),
254-
})
255-
}
213+
ic_spot: ffi::XPoint { x: 0, y: 0 },
214+
_client_data: Box::from_raw(client_data),
215+
};
256216

257-
unsafe fn create_ic(
258-
xconn: &Arc<XConnection>,
259-
im: ffi::XIM,
260-
window: ffi::Window,
261-
preedit_callbacks: &PreeditCallbacks,
262-
) -> Option<ffi::XIC> {
263-
let pre_edit_attr = create_pre_edit_attr(xconn, preedit_callbacks);
264-
let ic = (xconn.xlib.XCreateIC)(
265-
im,
266-
ffi::XNInputStyle_0.as_ptr() as *const _,
267-
ffi::XIMPreeditCallbacks | ffi::XIMStatusNothing,
268-
ffi::XNClientWindow_0.as_ptr() as *const _,
269-
window,
270-
ffi::XNPreeditAttributes_0.as_ptr(),
271-
pre_edit_attr.ptr,
272-
ptr::null_mut::<()>(),
273-
);
274-
if ic.is_null() {
275-
None
276-
} else {
277-
Some(ic)
217+
// Set the spot location, if it's present.
218+
if let Some(ic_spot) = ic_spot {
219+
context.set_spot(xconn, ic_spot.x, ic_spot.y)
278220
}
221+
222+
Ok(context)
279223
}
280224

281-
unsafe fn create_ic_with_spot(
225+
unsafe fn create_ic(
282226
xconn: &Arc<XConnection>,
283227
im: ffi::XIM,
284228
window: ffi::Window,
285-
ic_spot: ffi::XPoint,
286-
preedit_callbacks: &PreeditCallbacks,
229+
client_data: ffi::XPointer,
287230
) -> Option<ffi::XIC> {
288-
let pre_edit_attr = create_pre_edit_attr_with_spot(xconn, &ic_spot, preedit_callbacks);
289-
let ic = (xconn.xlib.XCreateIC)(
290-
im,
291-
ffi::XNInputStyle_0.as_ptr() as *const _,
292-
ffi::XIMPreeditCallbacks | ffi::XIMStatusNothing,
293-
ffi::XNClientWindow_0.as_ptr() as *const _,
294-
window,
295-
ffi::XNPreeditAttributes_0.as_ptr() as *const _,
296-
pre_edit_attr.ptr,
297-
ptr::null_mut::<()>(),
298-
);
299-
if ic.is_null() {
300-
None
301-
} else {
302-
Some(ic)
303-
}
231+
let preedit_callbacks = PreeditCallbacks::new(client_data);
232+
let preedit_attr = util::XSmartPointer::new(
233+
xconn,
234+
(xconn.xlib.XVaCreateNestedList)(
235+
0,
236+
ffi::XNPreeditStartCallback_0.as_ptr() as *const _,
237+
&(preedit_callbacks.start_callback) as *const _,
238+
ffi::XNPreeditDoneCallback_0.as_ptr() as *const _,
239+
&(preedit_callbacks.done_callback) as *const _,
240+
ffi::XNPreeditCaretCallback_0.as_ptr() as *const _,
241+
&(preedit_callbacks.caret_callback) as *const _,
242+
ffi::XNPreeditDrawCallback_0.as_ptr() as *const _,
243+
&(preedit_callbacks.draw_callback) as *const _,
244+
ptr::null_mut::<()>(),
245+
),
246+
)
247+
.expect("XVaCreateNestedList returned NULL");
248+
249+
let ic = {
250+
let ic = (xconn.xlib.XCreateIC)(
251+
im,
252+
ffi::XNInputStyle_0.as_ptr() as *const _,
253+
ffi::XIMPreeditCallbacks | ffi::XIMStatusNothing,
254+
ffi::XNClientWindow_0.as_ptr() as *const _,
255+
window,
256+
ffi::XNPreeditAttributes_0.as_ptr() as *const _,
257+
preedit_attr.ptr,
258+
ptr::null_mut::<()>(),
259+
);
260+
261+
// If we've failed to create IC with preedit callbacks fallback to normal one.
262+
if ic.is_null() {
263+
(xconn.xlib.XCreateIC)(
264+
im,
265+
ffi::XNInputStyle_0.as_ptr() as *const _,
266+
ffi::XIMPreeditNothing | ffi::XIMStatusNothing,
267+
ffi::XNClientWindow_0.as_ptr() as *const _,
268+
window,
269+
ptr::null_mut::<()>(),
270+
)
271+
} else {
272+
ic
273+
}
274+
};
275+
276+
(!ic.is_null()).then(|| ic)
304277
}
305278

306279
pub fn focus(&self, xconn: &Arc<XConnection>) -> Result<(), XError> {
@@ -317,19 +290,34 @@ impl ImeContext {
317290
xconn.check_errors()
318291
}
319292

293+
// Set the spot for preedit text. Setting spot isn't working with libX11 when preedit callbacks
294+
// are being used. Certain IMEs do show selection window, but it's placed in bottom left of the
295+
// window and couldn't be changed.
296+
//
297+
// For me see: https://bugs.freedesktop.org/show_bug.cgi?id=1580.
320298
pub fn set_spot(&mut self, xconn: &Arc<XConnection>, x: c_short, y: c_short) {
321299
if self.ic_spot.x == x && self.ic_spot.y == y {
322300
return;
323301
}
302+
324303
self.ic_spot = ffi::XPoint { x, y };
325304

326305
unsafe {
327-
let pre_edit_attr =
328-
create_pre_edit_attr_with_spot(xconn, &self.ic_spot, &self.preedit_callbacks);
306+
let preedit_attr = util::XSmartPointer::new(
307+
xconn,
308+
(xconn.xlib.XVaCreateNestedList)(
309+
0,
310+
ffi::XNSpotLocation_0.as_ptr(),
311+
&self.ic_spot,
312+
ptr::null_mut::<()>(),
313+
),
314+
)
315+
.expect("XVaCreateNestedList returned NULL");
316+
329317
(xconn.xlib.XSetICValues)(
330318
self.ic,
331319
ffi::XNPreeditAttributes_0.as_ptr() as *const _,
332-
pre_edit_attr.ptr,
320+
preedit_attr.ptr,
333321
ptr::null_mut::<()>(),
334322
);
335323
}

0 commit comments

Comments
 (0)