winit/platform_impl/linux/x11/ime/
input_method.rs

1use 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    // XSetLocaleModifiers returns...
16    // * The current locale modifiers if it's given a NULL pointer.
17    // * The new locale modifiers if we succeeded in setting them.
18    // * NULL if the locale modifiers string is malformed or if the current locale is not supported
19    //   by Xlib.
20    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        // Query the styles supported by the XIM.
46        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/// Style of the IME context.
95#[derive(Debug, Clone, Copy, PartialEq, Eq)]
96pub enum Style {
97    /// Preedit callbacks.
98    Preedit(XIMStyle),
99
100    /// Nothing.
101    Nothing(XIMStyle),
102
103    /// No IME.
104    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    /// Input method used locale modifier from `XMODIFIERS` environment variable.
125    XModifiers(InputMethod),
126    /// Input method used internal fallback locale modifier.
127    Fallback(InputMethod),
128    /// Input method could not be opened using any locale modifier tried.
129    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
159// The root window has a property named XIM_SERVERS, which contains a list of atoms representing
160// the available XIM servers. For instance, if you're using ibus, it would contain an atom named
161// "@server=ibus". It's possible for this property to contain multiple atoms, though presumably
162// rare. Note that we replace "@server=" with "@im=" in order to match the format of locale
163// modifiers, since we don't want a user who's looking at logs to ask "am I supposed to set
164// XMODIFIERS to `@server=ibus`?!?"
165unsafe 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(ffi::Atom::from)
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// By logging this struct, you get a sequential listing of every locale modifier tried, where it
259// came from, and if it succeeded.
260#[derive(Debug, Clone)]
261pub(crate) struct PotentialInputMethods {
262    // On correctly configured systems, the XMODIFIERS environment variable tells us everything we
263    // need to know.
264    xmodifiers: Option<PotentialInputMethod>,
265    // We have some standard options at our disposal that should ostensibly always work. For users
266    // who only need compose sequences, this ensures that the program launches without a hitch
267    // For users who need more sophisticated IME features, this is more or less a silent failure.
268    // Logging features should be added in the future to allow both audiences to be effectively
269    // served.
270    fallbacks: [PotentialInputMethod; 2],
271    // For diagnostic purposes, we include the list of XIM servers that the server reports as
272    // being available.
273    _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            // Since passing "" to XSetLocaleModifiers results in it defaulting to the value of
281            // XMODIFIERS, it's worth noting what happens if XMODIFIERS is also "". If simply
282            // running the program with `XMODIFIERS="" cargo run`, then assuming XMODIFIERS is
283            // defined in the profile (or parent environment) then that parent XMODIFIERS is used.
284            // If that XMODIFIERS value is also "" (i.e. if you ran `export XMODIFIERS=""`), then
285            // XSetLocaleModifiers uses the default local input method. Note that defining
286            // XMODIFIERS as "" is different from XMODIFIERS not being defined at all, since in
287            // that case, we get `None` and end up skipping ahead to the next method.
288            xmodifiers,
289            fallbacks: [
290                // This is a standard input method that supports compose sequences, which should
291                // always be available. `@im=none` appears to mean the same thing.
292                PotentialInputMethod::from_str("@im=local"),
293                // This explicitly specifies to use the implementation-dependent default, though
294                // that seems to be equivalent to just using the local input method.
295                PotentialInputMethod::from_str("@im="),
296            ],
297            // The XIM_SERVERS property can have surprising values. For instance, when I exited
298            // ibus to run fcitx, it retained the value denoting ibus. Even more surprising is
299            // that the fcitx input method could only be successfully opened using "@im=ibus".
300            // Presumably due to this quirk, it's actually possible to alternate between ibus and
301            // fcitx in a running application.
302            _xim_servers: unsafe { get_xim_servers(xconn) },
303        }
304    }
305
306    // This resets the `successful` field of every potential input method, ensuring we have
307    // accurate information when this struct is re-used by the destruction/instantiation callbacks.
308    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}