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