winit/platform_impl/linux/x11/ime/
input_method.rs1use std::ffi::{CStr, CString, IntoStringError};
2use std::os::raw::{c_char, c_ulong, c_ushort};
3use std::sync::{Arc, Mutex};
4use std::{env, fmt, ptr};
5
6use super::super::atoms::*;
7use super::{ffi, util, XConnection, XError};
8use x11rb::protocol::xproto;
9
10static GLOBAL_LOCK: Mutex<()> = Mutex::new(());
11
12unsafe fn open_im(xconn: &Arc<XConnection>, locale_modifiers: &CStr) -> Option<ffi::XIM> {
13 let _lock = GLOBAL_LOCK.lock();
14
15 unsafe { (xconn.xlib.XSetLocaleModifiers)(locale_modifiers.as_ptr()) };
21
22 let im = unsafe {
23 (xconn.xlib.XOpenIM)(xconn.display, ptr::null_mut(), ptr::null_mut(), ptr::null_mut())
24 };
25
26 if im.is_null() {
27 None
28 } else {
29 Some(im)
30 }
31}
32
33#[derive(Debug)]
34pub struct InputMethod {
35 pub im: ffi::XIM,
36 pub preedit_style: Style,
37 pub none_style: Style,
38 _name: String,
39}
40
41impl InputMethod {
42 fn new(xconn: &Arc<XConnection>, im: ffi::XIM, name: String) -> Option<Self> {
43 let mut styles: *mut XIMStyles = std::ptr::null_mut();
44
45 unsafe {
47 if !(xconn.xlib.XGetIMValues)(
48 im,
49 ffi::XNQueryInputStyle_0.as_ptr() as *const _,
50 (&mut styles) as *mut _,
51 std::ptr::null_mut::<()>(),
52 )
53 .is_null()
54 {
55 return None;
56 }
57 }
58
59 let mut preedit_style = None;
60 let mut none_style = None;
61
62 unsafe {
63 std::slice::from_raw_parts((*styles).supported_styles, (*styles).count_styles as _)
64 .iter()
65 .for_each(|style| match *style {
66 XIM_PREEDIT_STYLE => {
67 preedit_style = Some(Style::Preedit(*style));
68 },
69 XIM_NOTHING_STYLE if preedit_style.is_none() => {
70 preedit_style = Some(Style::Nothing(*style))
71 },
72 XIM_NONE_STYLE => none_style = Some(Style::None(*style)),
73 _ => (),
74 });
75
76 (xconn.xlib.XFree)(styles.cast());
77 };
78
79 if preedit_style.is_none() && none_style.is_none() {
80 return None;
81 }
82
83 let preedit_style = preedit_style.unwrap_or_else(|| none_style.unwrap());
84 let none_style = none_style.unwrap_or(preedit_style);
85
86 Some(InputMethod { im, _name: name, preedit_style, none_style })
87 }
88}
89
90const XIM_PREEDIT_STYLE: XIMStyle = (ffi::XIMPreeditCallbacks | ffi::XIMStatusNothing) as XIMStyle;
91const XIM_NOTHING_STYLE: XIMStyle = (ffi::XIMPreeditNothing | ffi::XIMStatusNothing) as XIMStyle;
92const XIM_NONE_STYLE: XIMStyle = (ffi::XIMPreeditNone | ffi::XIMStatusNone) as XIMStyle;
93
94#[derive(Debug, Clone, Copy, PartialEq, Eq)]
96pub enum Style {
97 Preedit(XIMStyle),
99
100 Nothing(XIMStyle),
102
103 None(XIMStyle),
105}
106
107impl Default for Style {
108 fn default() -> Self {
109 Style::None(XIM_NONE_STYLE)
110 }
111}
112
113#[repr(C)]
114#[derive(Debug)]
115struct XIMStyles {
116 count_styles: c_ushort,
117 supported_styles: *const XIMStyle,
118}
119
120pub(crate) type XIMStyle = c_ulong;
121
122#[derive(Debug)]
123pub enum InputMethodResult {
124 XModifiers(InputMethod),
126 Fallback(InputMethod),
128 Failure,
130}
131
132impl InputMethodResult {
133 pub fn is_fallback(&self) -> bool {
134 matches!(self, InputMethodResult::Fallback(_))
135 }
136
137 pub fn ok(self) -> Option<InputMethod> {
138 use self::InputMethodResult::*;
139 match self {
140 XModifiers(im) | Fallback(im) => Some(im),
141 Failure => None,
142 }
143 }
144}
145
146#[derive(Debug, Clone)]
147enum GetXimServersError {
148 XError(#[allow(dead_code)] XError),
149 GetPropertyError(#[allow(dead_code)] util::GetPropertyError),
150 InvalidUtf8(#[allow(dead_code)] IntoStringError),
151}
152
153impl From<util::GetPropertyError> for GetXimServersError {
154 fn from(error: util::GetPropertyError) -> Self {
155 GetXimServersError::GetPropertyError(error)
156 }
157}
158
159unsafe fn get_xim_servers(xconn: &Arc<XConnection>) -> Result<Vec<String>, GetXimServersError> {
166 let atoms = xconn.atoms();
167 let servers_atom = atoms[XIM_SERVERS];
168
169 let root = unsafe { (xconn.xlib.XDefaultRootWindow)(xconn.display) };
170
171 let mut atoms: Vec<ffi::Atom> = xconn
172 .get_property::<xproto::Atom>(
173 root as xproto::Window,
174 servers_atom,
175 xproto::Atom::from(xproto::AtomEnum::ATOM),
176 )
177 .map_err(GetXimServersError::GetPropertyError)?
178 .into_iter()
179 .map(|atom| atom as _)
180 .collect::<Vec<_>>();
181
182 let mut names: Vec<*const c_char> = Vec::with_capacity(atoms.len());
183 unsafe {
184 (xconn.xlib.XGetAtomNames)(
185 xconn.display,
186 atoms.as_mut_ptr(),
187 atoms.len() as _,
188 names.as_mut_ptr() as _,
189 )
190 };
191 unsafe { names.set_len(atoms.len()) };
192
193 let mut formatted_names = Vec::with_capacity(names.len());
194 for name in names {
195 let string = unsafe { CStr::from_ptr(name) }
196 .to_owned()
197 .into_string()
198 .map_err(GetXimServersError::InvalidUtf8)?;
199 unsafe { (xconn.xlib.XFree)(name as _) };
200 formatted_names.push(string.replace("@server=", "@im="));
201 }
202 xconn.check_errors().map_err(GetXimServersError::XError)?;
203 Ok(formatted_names)
204}
205
206#[derive(Clone)]
207struct InputMethodName {
208 c_string: CString,
209 string: String,
210}
211
212impl InputMethodName {
213 pub fn from_string(string: String) -> Self {
214 let c_string = CString::new(string.clone())
215 .expect("String used to construct CString contained null byte");
216 InputMethodName { c_string, string }
217 }
218
219 pub fn from_str(string: &str) -> Self {
220 let c_string =
221 CString::new(string).expect("String used to construct CString contained null byte");
222 InputMethodName { c_string, string: string.to_owned() }
223 }
224}
225
226impl fmt::Debug for InputMethodName {
227 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
228 self.string.fmt(f)
229 }
230}
231
232#[derive(Debug, Clone)]
233struct PotentialInputMethod {
234 name: InputMethodName,
235 successful: Option<bool>,
236}
237
238impl PotentialInputMethod {
239 pub fn from_string(string: String) -> Self {
240 PotentialInputMethod { name: InputMethodName::from_string(string), successful: None }
241 }
242
243 pub fn from_str(string: &str) -> Self {
244 PotentialInputMethod { name: InputMethodName::from_str(string), successful: None }
245 }
246
247 pub fn reset(&mut self) {
248 self.successful = None;
249 }
250
251 pub fn open_im(&mut self, xconn: &Arc<XConnection>) -> Option<InputMethod> {
252 let im = unsafe { open_im(xconn, &self.name.c_string) };
253 self.successful = Some(im.is_some());
254 im.and_then(|im| InputMethod::new(xconn, im, self.name.string.clone()))
255 }
256}
257
258#[derive(Debug, Clone)]
261pub(crate) struct PotentialInputMethods {
262 xmodifiers: Option<PotentialInputMethod>,
265 fallbacks: [PotentialInputMethod; 2],
271 _xim_servers: Result<Vec<String>, GetXimServersError>,
274}
275
276impl PotentialInputMethods {
277 pub fn new(xconn: &Arc<XConnection>) -> Self {
278 let xmodifiers = env::var("XMODIFIERS").ok().map(PotentialInputMethod::from_string);
279 PotentialInputMethods {
280 xmodifiers,
289 fallbacks: [
290 PotentialInputMethod::from_str("@im=local"),
293 PotentialInputMethod::from_str("@im="),
296 ],
297 _xim_servers: unsafe { get_xim_servers(xconn) },
303 }
304 }
305
306 fn reset(&mut self) {
309 if let Some(ref mut input_method) = self.xmodifiers {
310 input_method.reset();
311 }
312
313 for input_method in &mut self.fallbacks {
314 input_method.reset();
315 }
316 }
317
318 pub fn open_im(
319 &mut self,
320 xconn: &Arc<XConnection>,
321 callback: Option<&dyn Fn()>,
322 ) -> InputMethodResult {
323 use self::InputMethodResult::*;
324
325 self.reset();
326
327 if let Some(ref mut input_method) = self.xmodifiers {
328 let im = input_method.open_im(xconn);
329 if let Some(im) = im {
330 return XModifiers(im);
331 } else if let Some(ref callback) = callback {
332 callback();
333 }
334 }
335
336 for input_method in &mut self.fallbacks {
337 let im = input_method.open_im(xconn);
338 if let Some(im) = im {
339 return Fallback(im);
340 }
341 }
342
343 Failure
344 }
345}