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