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};
56use x11rb::protocol::xproto;
78use super::super::atoms::*;
9use super::{ffi, util, XConnection, XError};
1011static GLOBAL_LOCK: Mutex<()> = Mutex::new(());
1213unsafe fn open_im(xconn: &Arc<XConnection>, locale_modifiers: &CStr) -> Option<ffi::XIM> {
14let _lock = GLOBAL_LOCK.lock();
1516// 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.
21unsafe { (xconn.xlib.XSetLocaleModifiers)(locale_modifiers.as_ptr()) };
2223let im = unsafe {
24 (xconn.xlib.XOpenIM)(xconn.display, ptr::null_mut(), ptr::null_mut(), ptr::null_mut())
25 };
2627if im.is_null() {
28None
29} else {
30Some(im)
31 }
32}
3334#[derive(Debug)]
35pub struct InputMethod {
36pub im: ffi::XIM,
37pub preedit_style: Style,
38pub none_style: Style,
39 _name: String,
40}
4142impl InputMethod {
43fn new(xconn: &Arc<XConnection>, im: ffi::XIM, name: String) -> Option<Self> {
44let mut styles: *mut XIMStyles = std::ptr::null_mut();
4546// Query the styles supported by the XIM.
47unsafe {
48if !(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 {
56return None;
57 }
58 }
5960let mut preedit_style = None;
61let mut none_style = None;
6263unsafe {
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 });
7677 (xconn.xlib.XFree)(styles.cast());
78 };
7980if preedit_style.is_none() && none_style.is_none() {
81return None;
82 }
8384let preedit_style = preedit_style.unwrap_or_else(|| none_style.unwrap());
85let none_style = none_style.unwrap_or(preedit_style);
8687Some(InputMethod { im, _name: name, preedit_style, none_style })
88 }
89}
9091const 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;
9495/// Style of the IME context.
96#[derive(Debug, Clone, Copy, PartialEq, Eq)]
97pub enum Style {
98/// Preedit callbacks.
99Preedit(XIMStyle),
100101/// Nothing.
102Nothing(XIMStyle),
103104/// No IME.
105None(XIMStyle),
106}
107108impl Default for Style {
109fn default() -> Self {
110 Style::None(XIM_NONE_STYLE)
111 }
112}
113114#[repr(C)]
115#[derive(Debug)]
116struct XIMStyles {
117 count_styles: c_ushort,
118 supported_styles: *const XIMStyle,
119}
120121pub(crate) type XIMStyle = c_ulong;
122123#[derive(Debug)]
124pub enum InputMethodResult {
125/// Input method used locale modifier from `XMODIFIERS` environment variable.
126XModifiers(InputMethod),
127/// Input method used internal fallback locale modifier.
128Fallback(InputMethod),
129/// Input method could not be opened using any locale modifier tried.
130Failure,
131}
132133impl InputMethodResult {
134pub fn is_fallback(&self) -> bool {
135matches!(self, InputMethodResult::Fallback(_))
136 }
137138pub fn ok(self) -> Option<InputMethod> {
139use self::InputMethodResult::*;
140match self {
141 XModifiers(im) | Fallback(im) => Some(im),
142 Failure => None,
143 }
144 }
145}
146147#[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}
153154impl From<util::GetPropertyError> for GetXimServersError {
155fn from(error: util::GetPropertyError) -> Self {
156 GetXimServersError::GetPropertyError(error)
157 }
158}
159160// 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> {
167let atoms = xconn.atoms();
168let servers_atom = atoms[XIM_SERVERS];
169170let root = unsafe { (xconn.xlib.XDefaultRootWindow)(xconn.display) };
171172let 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<_>>();
182183let mut names: Vec<*const c_char> = Vec::with_capacity(atoms.len());
184unsafe {
185 (xconn.xlib.XGetAtomNames)(
186 xconn.display,
187 atoms.as_mut_ptr(),
188 atoms.len() as _,
189 names.as_mut_ptr() as _,
190 )
191 };
192unsafe { names.set_len(atoms.len()) };
193194let mut formatted_names = Vec::with_capacity(names.len());
195for name in names {
196let string = unsafe { CStr::from_ptr(name) }
197 .to_owned()
198 .into_string()
199 .map_err(GetXimServersError::InvalidUtf8)?;
200unsafe { (xconn.xlib.XFree)(name as _) };
201 formatted_names.push(string.replace("@server=", "@im="));
202 }
203 xconn.check_errors().map_err(GetXimServersError::XError)?;
204Ok(formatted_names)
205}
206207#[derive(Clone)]
208struct InputMethodName {
209 c_string: CString,
210 string: String,
211}
212213impl InputMethodName {
214pub fn from_string(string: String) -> Self {
215let c_string = CString::new(string.clone())
216 .expect("String used to construct CString contained null byte");
217 InputMethodName { c_string, string }
218 }
219220pub fn from_str(string: &str) -> Self {
221let 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}
226227impl fmt::Debug for InputMethodName {
228fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
229self.string.fmt(f)
230 }
231}
232233#[derive(Debug, Clone)]
234struct PotentialInputMethod {
235 name: InputMethodName,
236 successful: Option<bool>,
237}
238239impl PotentialInputMethod {
240pub fn from_string(string: String) -> Self {
241 PotentialInputMethod { name: InputMethodName::from_string(string), successful: None }
242 }
243244pub fn from_str(string: &str) -> Self {
245 PotentialInputMethod { name: InputMethodName::from_str(string), successful: None }
246 }
247248pub fn reset(&mut self) {
249self.successful = None;
250 }
251252pub fn open_im(&mut self, xconn: &Arc<XConnection>) -> Option<InputMethod> {
253let im = unsafe { open_im(xconn, &self.name.c_string) };
254self.successful = Some(im.is_some());
255 im.and_then(|im| InputMethod::new(xconn, im, self.name.string.clone()))
256 }
257}
258259// 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.
265xmodifiers: 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.
271fallbacks: [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}
276277impl PotentialInputMethods {
278pub fn new(xconn: &Arc<XConnection>) -> Self {
279let 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.
289xmodifiers,
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.
293PotentialInputMethod::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.
296PotentialInputMethod::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 }
306307// 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.
309fn reset(&mut self) {
310if let Some(ref mut input_method) = self.xmodifiers {
311 input_method.reset();
312 }
313314for input_method in &mut self.fallbacks {
315 input_method.reset();
316 }
317 }
318319pub fn open_im(
320&mut self,
321 xconn: &Arc<XConnection>,
322 callback: Option<&dyn Fn()>,
323 ) -> InputMethodResult {
324use self::InputMethodResult::*;
325326self.reset();
327328if let Some(ref mut input_method) = self.xmodifiers {
329let im = input_method.open_im(xconn);
330if let Some(im) = im {
331return XModifiers(im);
332 } else if let Some(ref callback) = callback {
333 callback();
334 }
335 }
336337for input_method in &mut self.fallbacks {
338let im = input_method.open_im(xconn);
339if let Some(im) = im {
340return Fallback(im);
341 }
342 }
343344 Failure
345 }
346}