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 x11rb::protocol::xproto;
7
8use super::super::atoms::*;
9use super::{ffi, util, XConnection, XError};
10
11static GLOBAL_LOCK: Mutex<()> = Mutex::new(());
12
13unsafe fn open_im(xconn: &Arc<XConnection>, locale_modifiers: &CStr) -> Option<ffi::XIM> {
14    let _lock = GLOBAL_LOCK.lock();
15
16    // XSetLocaleModifiers returns...
17    // * The current locale modifiers if it's given a NULL pointer.
18    // * The new locale modifiers if we succeeded in setting them.
19    // * NULL if the locale modifiers string is malformed or if the current locale is not supported
20    //   by Xlib.
21    unsafe { (xconn.xlib.XSetLocaleModifiers)(locale_modifiers.as_ptr()) };
22
23    let im = unsafe {
24        (xconn.xlib.XOpenIM)(xconn.display, ptr::null_mut(), ptr::null_mut(), ptr::null_mut())
25    };
26
27    if im.is_null() {
28        None
29    } else {
30        Some(im)
31    }
32}
33
34#[derive(Debug)]
35pub struct InputMethod {
36    pub im: ffi::XIM,
37    pub preedit_style: Style,
38    pub none_style: Style,
39    _name: String,
40}
41
42impl InputMethod {
43    fn new(xconn: &Arc<XConnection>, im: ffi::XIM, name: String) -> Option<Self> {
44        let mut styles: *mut XIMStyles = std::ptr::null_mut();
45
46        // Query the styles supported by the XIM.
47        unsafe {
48            if !(xconn.xlib.XGetIMValues)(
49                im,
50                ffi::XNQueryInputStyle_0.as_ptr() as *const _,
51                (&mut styles) as *mut _,
52                std::ptr::null_mut::<()>(),
53            )
54            .is_null()
55            {
56                return None;
57            }
58        }
59
60        let mut preedit_style = None;
61        let mut none_style = None;
62
63        unsafe {
64            std::slice::from_raw_parts((*styles).supported_styles, (*styles).count_styles as _)
65                .iter()
66                .for_each(|style| match *style {
67                    XIM_PREEDIT_STYLE => {
68                        preedit_style = Some(Style::Preedit(*style));
69                    },
70                    XIM_NOTHING_STYLE if preedit_style.is_none() => {
71                        preedit_style = Some(Style::Nothing(*style))
72                    },
73                    XIM_NONE_STYLE => none_style = Some(Style::None(*style)),
74                    _ => (),
75                });
76
77            (xconn.xlib.XFree)(styles.cast());
78        };
79
80        if preedit_style.is_none() && none_style.is_none() {
81            return None;
82        }
83
84        let preedit_style = preedit_style.unwrap_or_else(|| none_style.unwrap());
85        let none_style = none_style.unwrap_or(preedit_style);
86
87        Some(InputMethod { im, _name: name, preedit_style, none_style })
88    }
89}
90
91const XIM_PREEDIT_STYLE: XIMStyle = (ffi::XIMPreeditCallbacks | ffi::XIMStatusNothing) as XIMStyle;
92const XIM_NOTHING_STYLE: XIMStyle = (ffi::XIMPreeditNothing | ffi::XIMStatusNothing) as XIMStyle;
93const XIM_NONE_STYLE: XIMStyle = (ffi::XIMPreeditNone | ffi::XIMStatusNone) as XIMStyle;
94
95/// Style of the IME context.
96#[derive(Debug, Clone, Copy, PartialEq, Eq)]
97pub enum Style {
98    /// Preedit callbacks.
99    Preedit(XIMStyle),
100
101    /// Nothing.
102    Nothing(XIMStyle),
103
104    /// No IME.
105    None(XIMStyle),
106}
107
108impl Default for Style {
109    fn default() -> Self {
110        Style::None(XIM_NONE_STYLE)
111    }
112}
113
114#[repr(C)]
115#[derive(Debug)]
116struct XIMStyles {
117    count_styles: c_ushort,
118    supported_styles: *const XIMStyle,
119}
120
121pub(crate) type XIMStyle = c_ulong;
122
123#[derive(Debug)]
124pub enum InputMethodResult {
125    /// Input method used locale modifier from `XMODIFIERS` environment variable.
126    XModifiers(InputMethod),
127    /// Input method used internal fallback locale modifier.
128    Fallback(InputMethod),
129    /// Input method could not be opened using any locale modifier tried.
130    Failure,
131}
132
133impl InputMethodResult {
134    pub fn is_fallback(&self) -> bool {
135        matches!(self, InputMethodResult::Fallback(_))
136    }
137
138    pub fn ok(self) -> Option<InputMethod> {
139        use self::InputMethodResult::*;
140        match self {
141            XModifiers(im) | Fallback(im) => Some(im),
142            Failure => None,
143        }
144    }
145}
146
147#[derive(Debug, Clone)]
148enum GetXimServersError {
149    XError(#[allow(dead_code)] XError),
150    GetPropertyError(#[allow(dead_code)] util::GetPropertyError),
151    InvalidUtf8(#[allow(dead_code)] IntoStringError),
152}
153
154impl From<util::GetPropertyError> for GetXimServersError {
155    fn from(error: util::GetPropertyError) -> Self {
156        GetXimServersError::GetPropertyError(error)
157    }
158}
159
160// The root window has a property named XIM_SERVERS, which contains a list of atoms representing
161// the available XIM servers. For instance, if you're using ibus, it would contain an atom named
162// "@server=ibus". It's possible for this property to contain multiple atoms, though presumably
163// rare. Note that we replace "@server=" with "@im=" in order to match the format of locale
164// modifiers, since we don't want a user who's looking at logs to ask "am I supposed to set
165// XMODIFIERS to `@server=ibus`?!?"
166unsafe fn get_xim_servers(xconn: &Arc<XConnection>) -> Result<Vec<String>, GetXimServersError> {
167    let atoms = xconn.atoms();
168    let servers_atom = atoms[XIM_SERVERS];
169
170    let root = unsafe { (xconn.xlib.XDefaultRootWindow)(xconn.display) };
171
172    let mut atoms: Vec<ffi::Atom> = xconn
173        .get_property::<xproto::Atom>(
174            root as xproto::Window,
175            servers_atom,
176            xproto::Atom::from(xproto::AtomEnum::ATOM),
177        )
178        .map_err(GetXimServersError::GetPropertyError)?
179        .into_iter()
180        .map(ffi::Atom::from)
181        .collect::<Vec<_>>();
182
183    let mut names: Vec<*const c_char> = Vec::with_capacity(atoms.len());
184    unsafe {
185        (xconn.xlib.XGetAtomNames)(
186            xconn.display,
187            atoms.as_mut_ptr(),
188            atoms.len() as _,
189            names.as_mut_ptr() as _,
190        )
191    };
192    unsafe { names.set_len(atoms.len()) };
193
194    let mut formatted_names = Vec::with_capacity(names.len());
195    for name in names {
196        let string = unsafe { CStr::from_ptr(name) }
197            .to_owned()
198            .into_string()
199            .map_err(GetXimServersError::InvalidUtf8)?;
200        unsafe { (xconn.xlib.XFree)(name as _) };
201        formatted_names.push(string.replace("@server=", "@im="));
202    }
203    xconn.check_errors().map_err(GetXimServersError::XError)?;
204    Ok(formatted_names)
205}
206
207#[derive(Clone)]
208struct InputMethodName {
209    c_string: CString,
210    string: String,
211}
212
213impl InputMethodName {
214    pub fn from_string(string: String) -> Self {
215        let c_string = CString::new(string.clone())
216            .expect("String used to construct CString contained null byte");
217        InputMethodName { c_string, string }
218    }
219
220    pub fn from_str(string: &str) -> Self {
221        let c_string =
222            CString::new(string).expect("String used to construct CString contained null byte");
223        InputMethodName { c_string, string: string.to_owned() }
224    }
225}
226
227impl fmt::Debug for InputMethodName {
228    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
229        self.string.fmt(f)
230    }
231}
232
233#[derive(Debug, Clone)]
234struct PotentialInputMethod {
235    name: InputMethodName,
236    successful: Option<bool>,
237}
238
239impl PotentialInputMethod {
240    pub fn from_string(string: String) -> Self {
241        PotentialInputMethod { name: InputMethodName::from_string(string), successful: None }
242    }
243
244    pub fn from_str(string: &str) -> Self {
245        PotentialInputMethod { name: InputMethodName::from_str(string), successful: None }
246    }
247
248    pub fn reset(&mut self) {
249        self.successful = None;
250    }
251
252    pub fn open_im(&mut self, xconn: &Arc<XConnection>) -> Option<InputMethod> {
253        let im = unsafe { open_im(xconn, &self.name.c_string) };
254        self.successful = Some(im.is_some());
255        im.and_then(|im| InputMethod::new(xconn, im, self.name.string.clone()))
256    }
257}
258
259// By logging this struct, you get a sequential listing of every locale modifier tried, where it
260// came from, and if it succeeded.
261#[derive(Debug, Clone)]
262pub(crate) struct PotentialInputMethods {
263    // On correctly configured systems, the XMODIFIERS environment variable tells us everything we
264    // need to know.
265    xmodifiers: Option<PotentialInputMethod>,
266    // We have some standard options at our disposal that should ostensibly always work. For users
267    // who only need compose sequences, this ensures that the program launches without a hitch
268    // For users who need more sophisticated IME features, this is more or less a silent failure.
269    // Logging features should be added in the future to allow both audiences to be effectively
270    // served.
271    fallbacks: [PotentialInputMethod; 2],
272    // For diagnostic purposes, we include the list of XIM servers that the server reports as
273    // being available.
274    _xim_servers: Result<Vec<String>, GetXimServersError>,
275}
276
277impl PotentialInputMethods {
278    pub fn new(xconn: &Arc<XConnection>) -> Self {
279        let xmodifiers = env::var("XMODIFIERS").ok().map(PotentialInputMethod::from_string);
280        PotentialInputMethods {
281            // Since passing "" to XSetLocaleModifiers results in it defaulting to the value of
282            // XMODIFIERS, it's worth noting what happens if XMODIFIERS is also "". If simply
283            // running the program with `XMODIFIERS="" cargo run`, then assuming XMODIFIERS is
284            // defined in the profile (or parent environment) then that parent XMODIFIERS is used.
285            // If that XMODIFIERS value is also "" (i.e. if you ran `export XMODIFIERS=""`), then
286            // XSetLocaleModifiers uses the default local input method. Note that defining
287            // XMODIFIERS as "" is different from XMODIFIERS not being defined at all, since in
288            // that case, we get `None` and end up skipping ahead to the next method.
289            xmodifiers,
290            fallbacks: [
291                // This is a standard input method that supports compose sequences, which should
292                // always be available. `@im=none` appears to mean the same thing.
293                PotentialInputMethod::from_str("@im=local"),
294                // This explicitly specifies to use the implementation-dependent default, though
295                // that seems to be equivalent to just using the local input method.
296                PotentialInputMethod::from_str("@im="),
297            ],
298            // The XIM_SERVERS property can have surprising values. For instance, when I exited
299            // ibus to run fcitx, it retained the value denoting ibus. Even more surprising is
300            // that the fcitx input method could only be successfully opened using "@im=ibus".
301            // Presumably due to this quirk, it's actually possible to alternate between ibus and
302            // fcitx in a running application.
303            _xim_servers: unsafe { get_xim_servers(xconn) },
304        }
305    }
306
307    // This resets the `successful` field of every potential input method, ensuring we have
308    // accurate information when this struct is re-used by the destruction/instantiation callbacks.
309    fn reset(&mut self) {
310        if let Some(ref mut input_method) = self.xmodifiers {
311            input_method.reset();
312        }
313
314        for input_method in &mut self.fallbacks {
315            input_method.reset();
316        }
317    }
318
319    pub fn open_im(
320        &mut self,
321        xconn: &Arc<XConnection>,
322        callback: Option<&dyn Fn()>,
323    ) -> InputMethodResult {
324        use self::InputMethodResult::*;
325
326        self.reset();
327
328        if let Some(ref mut input_method) = self.xmodifiers {
329            let im = input_method.open_im(xconn);
330            if let Some(im) = im {
331                return XModifiers(im);
332            } else if let Some(ref callback) = callback {
333                callback();
334            }
335        }
336
337        for input_method in &mut self.fallbacks {
338            let im = input_method.open_im(xconn);
339            if let Some(im) = im {
340                return Fallback(im);
341            }
342        }
343
344        Failure
345    }
346}