1//! XKB compose handling.
2
3use std::env;
4use std::ffi::CString;
5use std::ops::Deref;
6use std::os::unix::ffi::OsStringExt;
7use std::ptr::NonNull;
8
9use super::XkbContext;
10use super::XKBCH;
11use smol_str::SmolStr;
12use xkbcommon_dl::{
13 xkb_compose_compile_flags, xkb_compose_feed_result, xkb_compose_state, xkb_compose_state_flags,
14 xkb_compose_status, xkb_compose_table, xkb_keysym_t,
15};
16
17#[derive(Debug)]
18pub struct XkbComposeTable {
19 table: NonNull<xkb_compose_table>,
20}
21
22impl XkbComposeTable {
23 pub fn new(context: &XkbContext) -> Option<Self> {
24 let locale = env::var_os("LC_ALL")
25 .and_then(|v| if v.is_empty() { None } else { Some(v) })
26 .or_else(|| env::var_os("LC_CTYPE"))
27 .and_then(|v| if v.is_empty() { None } else { Some(v) })
28 .or_else(|| env::var_os("LANG"))
29 .and_then(|v| if v.is_empty() { None } else { Some(v) })
30 .unwrap_or_else(|| "C".into());
31 let locale = CString::new(locale.into_vec()).unwrap();
32
33 let table = unsafe {
34 (XKBCH.xkb_compose_table_new_from_locale)(
35 context.as_ptr(),
36 locale.as_ptr(),
37 xkb_compose_compile_flags::XKB_COMPOSE_COMPILE_NO_FLAGS,
38 )
39 };
40
41 let table = NonNull::new(table)?;
42 Some(Self { table })
43 }
44
45 /// Create new state with the given compose table.
46 pub fn new_state(&self) -> Option<XkbComposeState> {
47 let state = unsafe {
48 (XKBCH.xkb_compose_state_new)(
49 self.table.as_ptr(),
50 xkb_compose_state_flags::XKB_COMPOSE_STATE_NO_FLAGS,
51 )
52 };
53
54 let state = NonNull::new(state)?;
55 Some(XkbComposeState { state })
56 }
57}
58
59impl Deref for XkbComposeTable {
60 type Target = NonNull<xkb_compose_table>;
61
62 fn deref(&self) -> &Self::Target {
63 &self.table
64 }
65}
66
67impl Drop for XkbComposeTable {
68 fn drop(&mut self) {
69 unsafe {
70 (XKBCH.xkb_compose_table_unref)(self.table.as_ptr());
71 }
72 }
73}
74
75#[derive(Debug)]
76pub struct XkbComposeState {
77 state: NonNull<xkb_compose_state>,
78}
79
80impl XkbComposeState {
81 pub fn get_string(&mut self, scratch_buffer: &mut Vec<u8>) -> Option<SmolStr> {
82 super::make_string_with(scratch_buffer, |ptr, len| unsafe {
83 (XKBCH.xkb_compose_state_get_utf8)(self.state.as_ptr(), ptr, len)
84 })
85 }
86
87 #[inline]
88 pub fn feed(&mut self, keysym: xkb_keysym_t) -> ComposeStatus {
89 let feed_result = unsafe { (XKBCH.xkb_compose_state_feed)(self.state.as_ptr(), keysym) };
90 match feed_result {
91 xkb_compose_feed_result::XKB_COMPOSE_FEED_IGNORED => ComposeStatus::Ignored,
92 xkb_compose_feed_result::XKB_COMPOSE_FEED_ACCEPTED => {
93 ComposeStatus::Accepted(self.status())
94 }
95 }
96 }
97
98 #[inline]
99 pub fn reset(&mut self) {
100 unsafe {
101 (XKBCH.xkb_compose_state_reset)(self.state.as_ptr());
102 }
103 }
104
105 #[inline]
106 pub fn status(&mut self) -> xkb_compose_status {
107 unsafe { (XKBCH.xkb_compose_state_get_status)(self.state.as_ptr()) }
108 }
109}
110
111impl Drop for XkbComposeState {
112 fn drop(&mut self) {
113 unsafe {
114 (XKBCH.xkb_compose_state_unref)(self.state.as_ptr());
115 };
116 }
117}
118
119#[derive(Copy, Clone, Debug)]
120pub enum ComposeStatus {
121 Accepted(xkb_compose_status),
122 Ignored,
123 None,
124}
125