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

1use std::collections::HashMap;
2use std::os::raw::c_char;
3use std::ptr;
4use std::sync::Arc;
5
6use super::context::{ImeContext, ImeContextCreationError};
7use super::inner::{close_im, ImeInner};
8use super::input_method::PotentialInputMethods;
9use super::{ffi, XConnection, XError};
10
11pub(crate) unsafe fn xim_set_callback(
12    xconn: &Arc<XConnection>,
13    xim: ffi::XIM,
14    field: *const c_char,
15    callback: *mut ffi::XIMCallback,
16) -> Result<(), XError> {
17    // It's advisable to wrap variadic FFI functions in our own functions, as we want to minimize
18    // access that isn't type-checked.
19    unsafe { (xconn.xlib.XSetIMValues)(xim, field, callback, ptr::null_mut::<()>()) };
20    xconn.check_errors()
21}
22
23// Set a callback for when an input method matching the current locale modifiers becomes
24// available. Note that this has nothing to do with what input methods are open or able to be
25// opened, and simply uses the modifiers that are set when the callback is set.
26// * This is called per locale modifier, not per input method opened with that locale modifier.
27// * Trying to set this for multiple locale modifiers causes problems, i.e. one of the rebuilt input
28//   contexts would always silently fail to use the input method.
29pub(crate) unsafe fn set_instantiate_callback(
30    xconn: &Arc<XConnection>,
31    client_data: ffi::XPointer,
32) -> Result<(), XError> {
33    unsafe {
34        (xconn.xlib.XRegisterIMInstantiateCallback)(
35            xconn.display,
36            ptr::null_mut(),
37            ptr::null_mut(),
38            ptr::null_mut(),
39            Some(xim_instantiate_callback),
40            client_data,
41        )
42    };
43    xconn.check_errors()
44}
45
46pub(crate) unsafe fn unset_instantiate_callback(
47    xconn: &Arc<XConnection>,
48    client_data: ffi::XPointer,
49) -> Result<(), XError> {
50    unsafe {
51        (xconn.xlib.XUnregisterIMInstantiateCallback)(
52            xconn.display,
53            ptr::null_mut(),
54            ptr::null_mut(),
55            ptr::null_mut(),
56            Some(xim_instantiate_callback),
57            client_data,
58        )
59    };
60    xconn.check_errors()
61}
62
63pub(crate) unsafe fn set_destroy_callback(
64    xconn: &Arc<XConnection>,
65    im: ffi::XIM,
66    inner: &ImeInner,
67) -> Result<(), XError> {
68    unsafe {
69        xim_set_callback(
70            xconn,
71            im,
72            ffi::XNDestroyCallback_0.as_ptr() as *const _,
73            &inner.destroy_callback as *const _ as *mut _,
74        )
75    }
76}
77
78#[derive(Debug)]
79#[allow(clippy::enum_variant_names)]
80enum ReplaceImError {
81    // Boxed to prevent large error type
82    MethodOpenFailed(#[allow(dead_code)] Box<PotentialInputMethods>),
83    ContextCreationFailed(#[allow(dead_code)] ImeContextCreationError),
84    SetDestroyCallbackFailed(#[allow(dead_code)] XError),
85}
86
87// Attempt to replace current IM (which may or may not be presently valid) with a new one. This
88// includes replacing all existing input contexts and free'ing resources as necessary. This only
89// modifies existing state if all operations succeed.
90unsafe fn replace_im(inner: *mut ImeInner) -> Result<(), ReplaceImError> {
91    let xconn = unsafe { &(*inner).xconn };
92
93    let (new_im, is_fallback) = {
94        let new_im = unsafe { (*inner).potential_input_methods.open_im(xconn, None) };
95        let is_fallback = new_im.is_fallback();
96        (
97            new_im.ok().ok_or_else(|| {
98                ReplaceImError::MethodOpenFailed(Box::new(unsafe {
99                    (*inner).potential_input_methods.clone()
100                }))
101            })?,
102            is_fallback,
103        )
104    };
105
106    // It's important to always set a destroy callback, since there's otherwise potential for us
107    // to try to use or free a resource that's already been destroyed on the server.
108    {
109        let result = unsafe { set_destroy_callback(xconn, new_im.im, &*inner) };
110        if result.is_err() {
111            let _ = unsafe { close_im(xconn, new_im.im) };
112        }
113        result
114    }
115    .map_err(ReplaceImError::SetDestroyCallbackFailed)?;
116
117    let mut new_contexts = HashMap::new();
118    for (window, old_context) in unsafe { (*inner).contexts.iter() } {
119        let spot = old_context.as_ref().map(|old_context| old_context.ic_spot);
120
121        // Check if the IME was allowed on that context.
122        let is_allowed =
123            old_context.as_ref().map(|old_context| old_context.is_allowed()).unwrap_or_default();
124
125        // We can't use the style from the old context here, since it may change on reload, so
126        // pick style from the new XIM based on the old state.
127        let style = if is_allowed { new_im.preedit_style } else { new_im.none_style };
128
129        let new_context = {
130            let result = unsafe {
131                ImeContext::new(
132                    xconn,
133                    new_im.im,
134                    style,
135                    *window,
136                    spot,
137                    (*inner).event_sender.clone(),
138                )
139            };
140            if result.is_err() {
141                let _ = unsafe { close_im(xconn, new_im.im) };
142            }
143            result.map_err(ReplaceImError::ContextCreationFailed)?
144        };
145        new_contexts.insert(*window, Some(new_context));
146    }
147
148    // If we've made it this far, everything succeeded.
149    unsafe {
150        let _ = (*inner).destroy_all_contexts_if_necessary();
151        let _ = (*inner).close_im_if_necessary();
152        (*inner).im = Some(new_im);
153        (*inner).contexts = new_contexts;
154        (*inner).is_destroyed = false;
155        (*inner).is_fallback = is_fallback;
156    }
157    Ok(())
158}
159
160pub unsafe extern "C" fn xim_instantiate_callback(
161    _display: *mut ffi::Display,
162    client_data: ffi::XPointer,
163    // This field is unsupplied.
164    _call_data: ffi::XPointer,
165) {
166    let inner: *mut ImeInner = client_data as _;
167    if !inner.is_null() {
168        let xconn = unsafe { &(*inner).xconn };
169        match unsafe { replace_im(inner) } {
170            Ok(()) => unsafe {
171                let _ = unset_instantiate_callback(xconn, client_data);
172                (*inner).is_fallback = false;
173            },
174            Err(err) => unsafe {
175                if (*inner).is_destroyed {
176                    // We have no usable input methods!
177                    panic!("Failed to reopen input method: {err:?}");
178                }
179            },
180        }
181    }
182}
183
184// This callback is triggered when the input method is closed on the server end. When this
185// happens, XCloseIM/XDestroyIC doesn't need to be called, as the resources have already been
186// free'd (attempting to do so causes our connection to freeze).
187pub unsafe extern "C" fn xim_destroy_callback(
188    _xim: ffi::XIM,
189    client_data: ffi::XPointer,
190    // This field is unsupplied.
191    _call_data: ffi::XPointer,
192) {
193    let inner: *mut ImeInner = client_data as _;
194    if !inner.is_null() {
195        unsafe { (*inner).is_destroyed = true };
196        let xconn = unsafe { &(*inner).xconn };
197        if unsafe { !(*inner).is_fallback } {
198            let _ = unsafe { set_instantiate_callback(xconn, client_data) };
199            // Attempt to open fallback input method.
200            match unsafe { replace_im(inner) } {
201                Ok(()) => unsafe { (*inner).is_fallback = true },
202                Err(err) => {
203                    // We have no usable input methods!
204                    panic!("Failed to open fallback input method: {err:?}");
205                },
206            }
207        }
208    }
209}