1#[macro_use]
13extern crate lazy_static;
14
15use std::os::raw::{c_char, c_void};
16use std::pin::Pin;
17use std::sync::{Arc, Mutex};
18
19use bitflags::bitflags;
20
21use clib::*;
22
23mod clib;
24
25type LogFn = dyn for<'a> FnMut(&'a str) + Send;
26
27lazy_static! {
28 static ref LOGGER: Mutex<Option<Box<LogFn>>> = Mutex::default();
29}
30
31extern "C" {
32 fn xcb_log_wrapper(msg: *const c_char, ...);
33}
34
35#[no_mangle]
36fn rust_log(msg: *const c_char) {
37 let msg = unsafe { std::ffi::CStr::from_ptr(msg) }.to_string_lossy();
38 let msg = msg.trim();
39 if let Some(logger) = LOGGER.lock().unwrap().as_mut() {
40 logger(msg);
41 }
42}
43
44extern "C" fn create_ic_callback(im: *mut xcb_xim_t, new_ic: xcb_xic_t, user_data: *mut c_void) {
45 let ime = unsafe { ime_from_user_data(user_data) };
46 ime.ic = Some(new_ic);
47 unsafe {
48 xcb_xim_set_ic_focus(im, new_ic);
49 }
50}
51
52extern "C" fn open_callback(im: *mut xcb_xim_t, user_data: *mut c_void) {
53 let ime = unsafe { ime_from_user_data(user_data) };
54 let input_style = ime.input_style.bits();
55 let spot = xcb_point_t {
56 x: ime.pos_req.x,
57 y: ime.pos_req.y,
58 };
59 let w = &mut ime.pos_req.win as *mut u32;
60 unsafe {
61 let nested = xcb_xim_create_nested_list(
62 im,
63 XCB_XIM_XNSpotLocation,
64 &spot,
65 std::ptr::null_mut::<c_void>(),
66 );
67 xcb_xim_create_ic(
68 im,
69 Some(create_ic_callback),
70 user_data,
71 XCB_XIM_XNInputStyle,
72 &input_style,
73 XCB_XIM_XNClientWindow,
74 w,
75 XCB_XIM_XNFocusWindow,
76 w,
77 XCB_XIM_XNPreeditAttributes,
78 &nested,
79 std::ptr::null_mut::<c_void>(),
80 );
81 free(nested.data as _);
82 }
83 ime.pos_cur = ime.pos_req;
84}
85
86unsafe fn xim_encoding_to_utf8(
87 im: *mut xcb_xim_t,
88 xim_str: *const c_char,
89 length: usize,
90) -> String {
91 let mut buf: Vec<u8> = vec![];
92 if xcb_xim_get_encoding(im) == _xcb_xim_encoding_t_XCB_XIM_UTF8_STRING {
93 buf.extend(std::slice::from_raw_parts(
94 xim_str as *const u8,
95 length as usize,
96 ));
97 } else if xcb_xim_get_encoding(im) == _xcb_xim_encoding_t_XCB_XIM_COMPOUND_TEXT {
98 let mut new_length = 0usize;
99 let utf8 = xcb_compound_text_to_utf8(xim_str, length as usize, &mut new_length);
100 if !utf8.is_null() {
101 buf.extend(std::slice::from_raw_parts(utf8 as _, new_length));
102 free(utf8 as _);
103 }
104 }
105 String::from_utf8_unchecked(buf)
106}
107
108unsafe fn ime_from_user_data(user_data: *mut c_void) -> &'static mut ImeClient {
109 &mut *(user_data as *mut ImeClient)
110}
111
112extern "C" fn commit_string_callback(
113 im: *mut xcb_xim_t,
114 _ic: xcb_xic_t,
115 _flag: u32,
116 input: *mut c_char,
117 length: u32,
118 _keysym: *mut u32,
119 _n_keysym: usize,
120 user_data: *mut c_void,
121) {
122 let input = unsafe { xim_encoding_to_utf8(im, input, length as usize) };
123 let ime = unsafe { ime_from_user_data(user_data) };
124 let win = ime.pos_req.win;
125 ime.callbacks.commit_string.as_mut().map(|f| f(win, &input));
126}
127
128extern "C" fn update_pos_callback(_im: *mut xcb_xim_t, ic: xcb_xic_t, user_data: *mut c_void) {
129 let ime = unsafe { ime_from_user_data(user_data) };
130 if ime.pos_update_queued {
131 ime.pos_update_queued = false;
132 ime.send_pos_update(ic);
133 } else {
134 ime.is_processing_pos_update = false;
135 }
136}
137
138extern "C" fn forward_event_callback(
139 _im: *mut xcb_xim_t,
140 _ic: xcb_xic_t,
141 event: *mut xcb_key_press_event_t,
142 user_data: *mut c_void,
143) {
144 let ptr = event as *const xcb::ffi::xcb_key_press_event_t;
145 let event = xcb::KeyPressEvent { ptr: ptr as _ };
146 let ime = unsafe { ime_from_user_data(user_data) };
147 let win = ime.pos_req.win;
148 ime.callbacks.forward_event.as_mut().map(|f| f(win, &event));
149
150 std::mem::forget(event);
153}
154
155extern "C" fn preedit_start_callback(_im: *mut xcb_xim_t, _ic: xcb_xic_t, user_data: *mut c_void) {
156 let ime = unsafe { ime_from_user_data(user_data) };
157 let win = ime.pos_req.win;
158 ime.callbacks.preedit_start.as_mut().map(|f| f(win));
159}
160
161extern "C" fn preedit_draw_callback(
162 im: *mut xcb_xim_t,
163 _ic: xcb_xic_t,
164 frame: *mut xcb_im_preedit_draw_fr_t,
165 user_data: *mut c_void,
166) {
167 let frame = unsafe { &*frame };
168 let preedit_info = PreeditInfo { inner: frame, im };
169 let ime = unsafe { ime_from_user_data(user_data) };
170 let win = ime.pos_req.win;
171 ime.callbacks
172 .preedit_draw
173 .as_mut()
174 .map(|f| f(win, preedit_info));
175}
176
177extern "C" fn preedit_done_callback(_im: *mut xcb_xim_t, _ic: xcb_xic_t, user_data: *mut c_void) {
178 let ime = unsafe { ime_from_user_data(user_data) };
179 let win = ime.pos_req.win;
180 ime.callbacks.preedit_done.as_mut().map(|f| f(win));
181}
182
183bitflags! {
184 pub struct InputStyle: u32 {
186 const DEFAULT = 0;
189
190 const PREEDIT_CALLBACKS = _xcb_im_style_t_XCB_IM_PreeditCallbacks;
195 }
196}
197
198type StringCB = dyn for<'a> FnMut(u32, &'a str);
199type KeyPressCB = dyn for<'a> FnMut(u32, &'a xcb::KeyPressEvent);
200type PreeditDrawCB = dyn for<'a> FnMut(u32, PreeditInfo<'a>);
201type NotifyCB = dyn FnMut(u32);
202
203#[derive(Default)]
204struct Callbacks {
205 commit_string: Option<Box<StringCB>>,
206 forward_event: Option<Box<KeyPressCB>>,
207 preedit_start: Option<Box<NotifyCB>>,
208 preedit_draw: Option<Box<PreeditDrawCB>>,
209 preedit_done: Option<Box<NotifyCB>>,
210}
211
212#[derive(Debug, Clone, Copy)]
213struct ImePos {
214 win: u32,
215 x: i16,
216 y: i16,
217}
218
219pub struct PreeditInfo<'a> {
223 im: *mut xcb_xim_t,
224 inner: &'a xcb_im_preedit_draw_fr_t,
225}
226
227impl<'a> PreeditInfo<'a> {
228 pub fn status(&self) -> u32 {
237 self.inner.status
238 }
239
240 pub fn caret(&self) -> u32 {
242 self.inner.caret
243 }
244
245 pub fn chg_first(&self) -> u32 {
247 self.inner.chg_first
248 }
249
250 pub fn chg_length(&self) -> u32 {
252 self.inner.chg_length
253 }
254
255 pub fn text(&self) -> String {
257 unsafe {
258 xim_encoding_to_utf8(
259 self.im,
260 self.inner.preedit_string as _,
261 self.inner.length_of_preedit_string as usize,
262 )
263 }
264 }
265}
266
267impl<'a> std::fmt::Debug for PreeditInfo<'a> {
268 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
269 f.debug_struct("PreeditInfo")
270 .field("status", &self.status())
271 .field("caret", &self.caret())
272 .field("chg_first", &self.chg_first())
273 .field("chg_length", &self.chg_length())
274 .field("text", &self.text());
275 Ok(())
276 }
277}
278
279pub struct ImeClient {
285 conn: Option<Arc<xcb::Connection>>,
286 im: *mut xcb_xim_t,
287 ic: Option<xcb_xic_t>,
288 callbacks: Callbacks,
289 input_style: InputStyle,
290 pos_cur: ImePos,
291 pos_req: ImePos,
292 is_processing_pos_update: bool,
293 pos_update_queued: bool,
294}
295
296impl ImeClient {
297 pub fn set_logger<F>(f: F)
302 where
303 F: for<'a> FnMut(&'a str) + Send + 'static,
304 {
305 LOGGER.lock().unwrap().replace(Box::new(f));
306 }
307
308 pub fn new(
319 conn: Arc<xcb::Connection>,
320 screen_id: i32,
321 input_style: InputStyle,
322 im_name: Option<&str>,
323 ) -> Pin<Box<Self>> {
324 let mut res = unsafe { Self::unsafe_new(&conn, screen_id, input_style, im_name) };
325 res.conn = Some(conn);
326 res
327 }
328
329 pub unsafe fn unsafe_new(
341 conn: &xcb::Connection,
342 screen_id: i32,
343 input_style: InputStyle,
344 im_name: Option<&str>,
345 ) -> Pin<Box<Self>> {
346 xcb_compound_text_init();
347 let im = xcb_xim_create(
348 conn.get_raw_conn() as _,
349 screen_id,
350 im_name.map_or(std::ptr::null(), |name| name.as_ptr() as _),
351 );
352 let mut res = Box::pin(Self {
353 conn: None,
354 im,
355 ic: None,
356 callbacks: Callbacks::default(),
357 input_style,
358 pos_cur: ImePos { win: 0, x: 0, y: 0 },
359 pos_req: ImePos { win: 0, x: 0, y: 0 },
360 is_processing_pos_update: false,
361 pos_update_queued: false,
362 });
363 let callbacks = xcb_xim_im_callback {
364 commit_string: Some(commit_string_callback),
365 forward_event: Some(forward_event_callback),
366 preedit_start: Some(preedit_start_callback),
367 preedit_draw: Some(preedit_draw_callback),
368 preedit_done: Some(preedit_done_callback),
369 ..Default::default()
370 };
371 let data: *mut Self = res.as_mut().get_mut();
372 xcb_xim_set_im_callback(im, &callbacks, data as _);
373 xcb_xim_set_log_handler(im, Some(xcb_log_wrapper));
374 xcb_xim_set_use_compound_text(im, true);
375 xcb_xim_set_use_utf8_string(im, true);
376 res
377 }
378
379 fn try_open_ic(&mut self) {
380 if self.ic.is_some() {
381 return;
382 }
383 let data: *mut ImeClient = self as _;
384 unsafe { xcb_xim_open(self.im, Some(open_callback), true, data as _) };
385 }
386
387 pub fn process_event(&mut self, event: &xcb::GenericEvent) -> bool {
407 if !unsafe { xcb_xim_filter_event(self.im, event.ptr as _) } {
408 let mask = event.response_type() & !0x80;
409 if (mask == xcb::ffi::XCB_KEY_PRESS) || (mask == xcb::ffi::XCB_KEY_RELEASE) {
410 match self.ic {
411 Some(ic) => {
412 unsafe {
413 xcb_xim_forward_event(self.im, ic, event.ptr as _);
414 }
415 return true;
416 }
417 _ => {
418 self.try_open_ic();
419 }
420 }
421 }
422 }
423 false
424 }
425
426 pub fn update_pos(&mut self, win: u32, x: i16, y: i16) -> bool {
435 self.pos_req = ImePos { win, x, y };
436 match self.ic {
437 Some(ic) => {
438 if self.is_processing_pos_update {
439 self.pos_update_queued = true;
440 return false;
441 }
442 self.send_pos_update(ic);
443 true
444 }
445 _ => {
446 self.try_open_ic();
447 false
448 }
449 }
450 }
451
452 fn send_pos_update(&mut self, ic: xcb_xic_t) {
453 self.is_processing_pos_update = true;
454 let spot = xcb_point_t {
455 x: self.pos_req.x,
456 y: self.pos_req.y,
457 };
458 let nested = unsafe {
459 xcb_xim_create_nested_list(
460 self.im,
461 XCB_XIM_XNSpotLocation,
462 &spot,
463 std::ptr::null_mut::<c_void>(),
464 )
465 };
466 if self.pos_req.win != self.pos_cur.win {
467 let w = &mut self.pos_req.win as *mut _;
468 unsafe {
469 xcb_xim_set_ic_values(
470 self.im,
471 ic,
472 Some(update_pos_callback),
473 self as *mut _ as _,
474 XCB_XIM_XNClientWindow,
475 w,
476 XCB_XIM_XNFocusWindow,
477 w,
478 XCB_XIM_XNPreeditAttributes,
479 &nested,
480 std::ptr::null_mut::<c_void>(),
481 );
482 }
483 } else {
484 unsafe {
485 xcb_xim_set_ic_values(
486 self.im,
487 ic,
488 Some(update_pos_callback),
489 self as *mut _ as _,
490 XCB_XIM_XNPreeditAttributes,
491 &nested,
492 std::ptr::null_mut::<c_void>(),
493 );
494 }
495 }
496 unsafe { free(nested.data as _) };
497 self.pos_cur = self.pos_req;
498 }
499
500 pub fn set_commit_string_cb<F>(&mut self, f: F)
506 where
507 F: for<'a> FnMut(u32, &'a str) + 'static,
508 {
509 self.callbacks.commit_string = Some(Box::new(f));
510 }
511
512 pub fn set_forward_event_cb<F>(&mut self, f: F)
522 where
523 F: for<'a> FnMut(u32, &'a xcb::KeyPressEvent) + 'static,
524 {
525 self.callbacks.forward_event = Some(Box::new(f));
526 }
527
528 pub fn set_preedit_start_cb<F>(&mut self, f: F)
535 where
536 F: FnMut(u32) + 'static,
537 {
538 self.callbacks.preedit_start = Some(Box::new(f));
539 }
540
541 pub fn set_preedit_draw_cb<F>(&mut self, f: F)
549 where
550 F: for<'a> FnMut(u32, PreeditInfo<'a>) + 'static,
551 {
552 self.callbacks.preedit_draw = Some(Box::new(f));
553 }
554
555 pub fn set_preedit_done_cb<F>(&mut self, f: F)
562 where
563 F: FnMut(u32) + 'static,
564 {
565 self.callbacks.preedit_done = Some(Box::new(f));
566 }
567}
568
569impl Drop for ImeClient {
570 fn drop(&mut self) {
571 unsafe {
572 if let Some(ic) = self.ic {
573 xcb_xim_destroy_ic(self.im, ic, None, std::ptr::null_mut());
574 }
575 xcb_xim_close(self.im);
576 xcb_xim_destroy(self.im);
577 }
578 }
579}