1use std::error::Error;
2use std::ffi::CStr;
3use std::os::raw::c_short;
4use std::sync::Arc;
5use std::{fmt, mem, ptr};
6
7use x11_dl::xlib::{XIMCallback, XIMPreeditCaretCallbackStruct, XIMPreeditDrawCallbackStruct};
8
9use super::{ffi, util, XConnection, XError};
10use crate::platform_impl::platform::x11::ime::input_method::{Style, XIMStyle};
11use crate::platform_impl::platform::x11::ime::{ImeEvent, ImeEventSender};
12
13#[derive(Debug)]
15pub enum ImeContextCreationError {
16 XError(XError),
18
19 Null,
21}
22
23impl fmt::Display for ImeContextCreationError {
24 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
25 match self {
26 ImeContextCreationError::XError(err) => err.fmt(f),
27 ImeContextCreationError::Null => {
28 write!(f, "got null pointer from Xlib without exact reason")
29 },
30 }
31 }
32}
33
34impl Error for ImeContextCreationError {}
35
36type XIMProcNonnull = unsafe extern "C" fn(ffi::XIM, ffi::XPointer, ffi::XPointer);
38
39#[inline]
41fn create_xim_callback(client_data: ffi::XPointer, callback: XIMProcNonnull) -> ffi::XIMCallback {
42 XIMCallback { client_data, callback: Some(callback) }
43}
44
45extern "C" fn preedit_start_callback(
47 _xim: ffi::XIM,
48 client_data: ffi::XPointer,
49 _call_data: ffi::XPointer,
50) -> i32 {
51 let client_data = unsafe { &mut *(client_data as *mut ImeContextClientData) };
52
53 client_data.text.clear();
54 client_data.cursor_pos = 0;
55 client_data
56 .event_sender
57 .send((client_data.window, ImeEvent::Start))
58 .expect("failed to send preedit start event");
59 -1
60}
61
62extern "C" fn preedit_done_callback(
64 _xim: ffi::XIM,
65 client_data: ffi::XPointer,
66 _call_data: ffi::XPointer,
67) {
68 let client_data = unsafe { &mut *(client_data as *mut ImeContextClientData) };
69
70 client_data.text = Vec::new();
72 client_data.cursor_pos = 0;
73
74 client_data
75 .event_sender
76 .send((client_data.window, ImeEvent::End))
77 .expect("failed to send preedit end event");
78}
79
80fn calc_byte_position(text: &[char], pos: usize) -> usize {
81 text.iter().take(pos).fold(0, |byte_pos, text| byte_pos + text.len_utf8())
82}
83
84extern "C" fn preedit_draw_callback(
86 _xim: ffi::XIM,
87 client_data: ffi::XPointer,
88 call_data: ffi::XPointer,
89) {
90 let client_data = unsafe { &mut *(client_data as *mut ImeContextClientData) };
91 let call_data = unsafe { &mut *(call_data as *mut XIMPreeditDrawCallbackStruct) };
92 client_data.cursor_pos = call_data.caret as usize;
93
94 let chg_range =
95 call_data.chg_first as usize..(call_data.chg_first + call_data.chg_length) as usize;
96 if chg_range.start > client_data.text.len() || chg_range.end > client_data.text.len() {
97 tracing::warn!(
98 "invalid chg range: buffer length={}, but chg_first={} chg_lengthg={}",
99 client_data.text.len(),
100 call_data.chg_first,
101 call_data.chg_length
102 );
103 return;
104 }
105
106 let mut new_chars = if call_data.text.is_null() {
108 Vec::new()
109 } else {
110 let xim_text = unsafe { &mut *(call_data.text) };
111 if xim_text.encoding_is_wchar > 0 {
112 return;
113 }
114
115 let new_text = unsafe { xim_text.string.multi_byte };
116
117 if new_text.is_null() {
118 return;
119 }
120
121 let new_text = unsafe { CStr::from_ptr(new_text) };
122
123 String::from(new_text.to_str().expect("Invalid UTF-8 String from IME")).chars().collect()
124 };
125 let mut old_text_tail = client_data.text.split_off(chg_range.end);
126 client_data.text.truncate(chg_range.start);
127 client_data.text.append(&mut new_chars);
128 client_data.text.append(&mut old_text_tail);
129 let cursor_byte_pos = calc_byte_position(&client_data.text, client_data.cursor_pos);
130
131 client_data
132 .event_sender
133 .send((
134 client_data.window,
135 ImeEvent::Update(client_data.text.iter().collect(), cursor_byte_pos),
136 ))
137 .expect("failed to send preedit update event");
138}
139
140extern "C" fn preedit_caret_callback(
142 _xim: ffi::XIM,
143 client_data: ffi::XPointer,
144 call_data: ffi::XPointer,
145) {
146 let client_data = unsafe { &mut *(client_data as *mut ImeContextClientData) };
147 let call_data = unsafe { &mut *(call_data as *mut XIMPreeditCaretCallbackStruct) };
148
149 if call_data.direction == ffi::XIMCaretDirection::XIMAbsolutePosition {
150 client_data.cursor_pos = call_data.position as usize;
151 let cursor_byte_pos = calc_byte_position(&client_data.text, client_data.cursor_pos);
152
153 client_data
154 .event_sender
155 .send((
156 client_data.window,
157 ImeEvent::Update(client_data.text.iter().collect(), cursor_byte_pos),
158 ))
159 .expect("failed to send preedit update event");
160 }
161}
162
163struct PreeditCallbacks {
165 start_callback: ffi::XIMCallback,
166 done_callback: ffi::XIMCallback,
167 draw_callback: ffi::XIMCallback,
168 caret_callback: ffi::XIMCallback,
169}
170
171impl PreeditCallbacks {
172 pub fn new(client_data: ffi::XPointer) -> PreeditCallbacks {
173 let start_callback = create_xim_callback(client_data, unsafe {
174 mem::transmute::<usize, unsafe extern "C" fn(ffi::XIM, ffi::XPointer, ffi::XPointer)>(
175 preedit_start_callback as usize,
176 )
177 });
178 let done_callback = create_xim_callback(client_data, preedit_done_callback);
179 let caret_callback = create_xim_callback(client_data, preedit_caret_callback);
180 let draw_callback = create_xim_callback(client_data, preedit_draw_callback);
181
182 PreeditCallbacks { start_callback, done_callback, caret_callback, draw_callback }
183 }
184}
185
186struct ImeContextClientData {
187 window: ffi::Window,
188 event_sender: ImeEventSender,
189 text: Vec<char>,
190 cursor_pos: usize,
191}
192
193pub struct ImeContext {
198 pub(crate) ic: ffi::XIC,
199 pub(crate) ic_spot: ffi::XPoint,
200 pub(crate) style: Style,
201 _client_data: Box<ImeContextClientData>,
204}
205
206impl ImeContext {
207 pub(crate) unsafe fn new(
208 xconn: &Arc<XConnection>,
209 im: ffi::XIM,
210 style: Style,
211 window: ffi::Window,
212 ic_spot: Option<ffi::XPoint>,
213 event_sender: ImeEventSender,
214 ) -> Result<Self, ImeContextCreationError> {
215 let client_data = Box::into_raw(Box::new(ImeContextClientData {
216 window,
217 event_sender,
218 text: Vec::new(),
219 cursor_pos: 0,
220 }));
221
222 let ic = match style as _ {
223 Style::Preedit(style) => unsafe {
224 ImeContext::create_preedit_ic(
225 xconn,
226 im,
227 style,
228 window,
229 client_data as ffi::XPointer,
230 )
231 },
232 Style::Nothing(style) => unsafe {
233 ImeContext::create_nothing_ic(xconn, im, style, window)
234 },
235 Style::None(style) => unsafe { ImeContext::create_none_ic(xconn, im, style, window) },
236 }
237 .ok_or(ImeContextCreationError::Null)?;
238
239 xconn.check_errors().map_err(ImeContextCreationError::XError)?;
240
241 let mut context = ImeContext {
242 ic,
243 ic_spot: ffi::XPoint { x: 0, y: 0 },
244 style,
245 _client_data: unsafe { Box::from_raw(client_data) },
246 };
247
248 if let Some(ic_spot) = ic_spot {
250 context.set_spot(xconn, ic_spot.x, ic_spot.y)
251 }
252
253 Ok(context)
254 }
255
256 unsafe fn create_none_ic(
257 xconn: &Arc<XConnection>,
258 im: ffi::XIM,
259 style: XIMStyle,
260 window: ffi::Window,
261 ) -> Option<ffi::XIC> {
262 let ic = unsafe {
263 (xconn.xlib.XCreateIC)(
264 im,
265 ffi::XNInputStyle_0.as_ptr() as *const _,
266 style,
267 ffi::XNClientWindow_0.as_ptr() as *const _,
268 window,
269 ptr::null_mut::<()>(),
270 )
271 };
272
273 (!ic.is_null()).then_some(ic)
274 }
275
276 unsafe fn create_preedit_ic(
277 xconn: &Arc<XConnection>,
278 im: ffi::XIM,
279 style: XIMStyle,
280 window: ffi::Window,
281 client_data: ffi::XPointer,
282 ) -> Option<ffi::XIC> {
283 let preedit_callbacks = PreeditCallbacks::new(client_data);
284 let preedit_attr = util::memory::XSmartPointer::new(xconn, unsafe {
285 (xconn.xlib.XVaCreateNestedList)(
286 0,
287 ffi::XNPreeditStartCallback_0.as_ptr() as *const _,
288 &(preedit_callbacks.start_callback) as *const _,
289 ffi::XNPreeditDoneCallback_0.as_ptr() as *const _,
290 &(preedit_callbacks.done_callback) as *const _,
291 ffi::XNPreeditCaretCallback_0.as_ptr() as *const _,
292 &(preedit_callbacks.caret_callback) as *const _,
293 ffi::XNPreeditDrawCallback_0.as_ptr() as *const _,
294 &(preedit_callbacks.draw_callback) as *const _,
295 ptr::null_mut::<()>(),
296 )
297 })
298 .expect("XVaCreateNestedList returned NULL");
299
300 let ic = unsafe {
301 (xconn.xlib.XCreateIC)(
302 im,
303 ffi::XNInputStyle_0.as_ptr() as *const _,
304 style,
305 ffi::XNClientWindow_0.as_ptr() as *const _,
306 window,
307 ffi::XNPreeditAttributes_0.as_ptr() as *const _,
308 preedit_attr.ptr,
309 ptr::null_mut::<()>(),
310 )
311 };
312
313 (!ic.is_null()).then_some(ic)
314 }
315
316 unsafe fn create_nothing_ic(
317 xconn: &Arc<XConnection>,
318 im: ffi::XIM,
319 style: XIMStyle,
320 window: ffi::Window,
321 ) -> Option<ffi::XIC> {
322 let ic = unsafe {
323 (xconn.xlib.XCreateIC)(
324 im,
325 ffi::XNInputStyle_0.as_ptr() as *const _,
326 style,
327 ffi::XNClientWindow_0.as_ptr() as *const _,
328 window,
329 ptr::null_mut::<()>(),
330 )
331 };
332
333 (!ic.is_null()).then_some(ic)
334 }
335
336 pub(crate) fn focus(&self, xconn: &Arc<XConnection>) -> Result<(), XError> {
337 unsafe {
338 (xconn.xlib.XSetICFocus)(self.ic);
339 }
340 xconn.check_errors()
341 }
342
343 pub(crate) fn unfocus(&self, xconn: &Arc<XConnection>) -> Result<(), XError> {
344 unsafe {
345 (xconn.xlib.XUnsetICFocus)(self.ic);
346 }
347 xconn.check_errors()
348 }
349
350 pub fn is_allowed(&self) -> bool {
351 !matches!(self.style, Style::None(_))
352 }
353
354 pub(crate) fn set_spot(&mut self, xconn: &Arc<XConnection>, x: c_short, y: c_short) {
360 if !self.is_allowed() || self.ic_spot.x == x && self.ic_spot.y == y {
361 return;
362 }
363
364 self.ic_spot = ffi::XPoint { x, y };
365
366 unsafe {
367 let preedit_attr = util::memory::XSmartPointer::new(
368 xconn,
369 (xconn.xlib.XVaCreateNestedList)(
370 0,
371 ffi::XNSpotLocation_0.as_ptr(),
372 &self.ic_spot,
373 ptr::null_mut::<()>(),
374 ),
375 )
376 .expect("XVaCreateNestedList returned NULL");
377
378 (xconn.xlib.XSetICValues)(
379 self.ic,
380 ffi::XNPreeditAttributes_0.as_ptr() as *const _,
381 preedit_attr.ptr,
382 ptr::null_mut::<()>(),
383 );
384 }
385 }
386}