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