1// Important: all XIM calls need to happen from the same thread!
2
3mod callbacks;
4mod context;
5mod inner;
6mod input_method;
7
8use std::sync::{
9 mpsc::{Receiver, Sender},
10 Arc,
11};
12
13use super::{ffi, util, XConnection, XError};
14
15pub use self::context::ImeContextCreationError;
16use self::{
17 callbacks::*,
18 context::ImeContext,
19 inner::{close_im, ImeInner},
20 input_method::{PotentialInputMethods, Style},
21};
22
23#[derive(Debug, Clone, PartialEq, Eq, Hash)]
24#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
25pub enum ImeEvent {
26 Enabled,
27 Start,
28 Update(String, usize),
29 End,
30 Disabled,
31}
32
33pub type ImeReceiver = Receiver<ImeRequest>;
34pub type ImeSender = Sender<ImeRequest>;
35pub type ImeEventReceiver = Receiver<(ffi::Window, ImeEvent)>;
36pub type ImeEventSender = Sender<(ffi::Window, ImeEvent)>;
37
38/// Request to control XIM handler from the window.
39pub enum ImeRequest {
40 /// Set IME spot position for given `window_id`.
41 Position(ffi::Window, i16, i16),
42
43 /// Allow IME input for the given `window_id`.
44 Allow(ffi::Window, bool),
45}
46
47#[derive(Debug)]
48pub(crate) enum ImeCreationError {
49 // Boxed to prevent large error type
50 OpenFailure(Box<PotentialInputMethods>),
51 SetDestroyCallbackFailed(#[allow(dead_code)] XError),
52}
53
54pub(crate) struct Ime {
55 xconn: Arc<XConnection>,
56 // The actual meat of this struct is boxed away, since it needs to have a fixed location in
57 // memory so we can pass a pointer to it around.
58 inner: Box<ImeInner>,
59}
60
61impl Ime {
62 pub fn new(
63 xconn: Arc<XConnection>,
64 event_sender: ImeEventSender,
65 ) -> Result<Self, ImeCreationError> {
66 let potential_input_methods = PotentialInputMethods::new(&xconn);
67
68 let (mut inner, client_data) = {
69 let mut inner = Box::new(ImeInner::new(xconn, potential_input_methods, event_sender));
70 let inner_ptr = Box::into_raw(inner);
71 let client_data = inner_ptr as _;
72 let destroy_callback = ffi::XIMCallback {
73 client_data,
74 callback: Some(xim_destroy_callback),
75 };
76 inner = unsafe { Box::from_raw(inner_ptr) };
77 inner.destroy_callback = destroy_callback;
78 (inner, client_data)
79 };
80
81 let xconn = Arc::clone(&inner.xconn);
82
83 let input_method = inner.potential_input_methods.open_im(
84 &xconn,
85 Some(&|| {
86 let _ = unsafe { set_instantiate_callback(&xconn, client_data) };
87 }),
88 );
89
90 let is_fallback = input_method.is_fallback();
91 if let Some(input_method) = input_method.ok() {
92 inner.is_fallback = is_fallback;
93 unsafe {
94 let result = set_destroy_callback(&xconn, input_method.im, &inner)
95 .map_err(ImeCreationError::SetDestroyCallbackFailed);
96 if result.is_err() {
97 let _ = close_im(&xconn, input_method.im);
98 }
99 result?;
100 }
101 inner.im = Some(input_method);
102 Ok(Ime { xconn, inner })
103 } else {
104 Err(ImeCreationError::OpenFailure(Box::new(
105 inner.potential_input_methods,
106 )))
107 }
108 }
109
110 pub fn is_destroyed(&self) -> bool {
111 self.inner.is_destroyed
112 }
113
114 // This pattern is used for various methods here:
115 // Ok(_) indicates that nothing went wrong internally
116 // Ok(true) indicates that the action was actually performed
117 // Ok(false) indicates that the action is not presently applicable
118 pub fn create_context(
119 &mut self,
120 window: ffi::Window,
121 with_preedit: bool,
122 ) -> Result<bool, ImeContextCreationError> {
123 let context = if self.is_destroyed() {
124 // Create empty entry in map, so that when IME is rebuilt, this window has a context.
125 None
126 } else {
127 let im = self.inner.im.as_ref().unwrap();
128 let style = if with_preedit {
129 im.preedit_style
130 } else {
131 im.none_style
132 };
133
134 let context = unsafe {
135 ImeContext::new(
136 &self.inner.xconn,
137 im.im,
138 style,
139 window,
140 None,
141 self.inner.event_sender.clone(),
142 )?
143 };
144
145 // Check the state on the context, since it could fail to enable or disable preedit.
146 let event = if matches!(style, Style::None(_)) {
147 if with_preedit {
148 debug!("failed to create IME context with preedit support.")
149 }
150 ImeEvent::Disabled
151 } else {
152 if !with_preedit {
153 debug!("failed to create IME context without preedit support.")
154 }
155 ImeEvent::Enabled
156 };
157
158 self.inner
159 .event_sender
160 .send((window, event))
161 .expect("Failed to send enabled event");
162
163 Some(context)
164 };
165
166 self.inner.contexts.insert(window, context);
167 Ok(!self.is_destroyed())
168 }
169
170 pub fn get_context(&self, window: ffi::Window) -> Option<ffi::XIC> {
171 if self.is_destroyed() {
172 return None;
173 }
174 if let Some(Some(context)) = self.inner.contexts.get(&window) {
175 Some(context.ic)
176 } else {
177 None
178 }
179 }
180
181 pub fn remove_context(&mut self, window: ffi::Window) -> Result<bool, XError> {
182 if let Some(Some(context)) = self.inner.contexts.remove(&window) {
183 unsafe {
184 self.inner.destroy_ic_if_necessary(context.ic)?;
185 }
186 Ok(true)
187 } else {
188 Ok(false)
189 }
190 }
191
192 pub fn focus(&mut self, window: ffi::Window) -> Result<bool, XError> {
193 if self.is_destroyed() {
194 return Ok(false);
195 }
196 if let Some(&mut Some(ref mut context)) = self.inner.contexts.get_mut(&window) {
197 context.focus(&self.xconn).map(|_| true)
198 } else {
199 Ok(false)
200 }
201 }
202
203 pub fn unfocus(&mut self, window: ffi::Window) -> Result<bool, XError> {
204 if self.is_destroyed() {
205 return Ok(false);
206 }
207 if let Some(&mut Some(ref mut context)) = self.inner.contexts.get_mut(&window) {
208 context.unfocus(&self.xconn).map(|_| true)
209 } else {
210 Ok(false)
211 }
212 }
213
214 pub fn send_xim_spot(&mut self, window: ffi::Window, x: i16, y: i16) {
215 if self.is_destroyed() {
216 return;
217 }
218 if let Some(&mut Some(ref mut context)) = self.inner.contexts.get_mut(&window) {
219 context.set_spot(&self.xconn, x as _, y as _);
220 }
221 }
222
223 pub fn set_ime_allowed(&mut self, window: ffi::Window, allowed: bool) {
224 if self.is_destroyed() {
225 return;
226 }
227
228 if let Some(&mut Some(ref mut context)) = self.inner.contexts.get_mut(&window) {
229 if allowed == context.is_allowed() {
230 return;
231 }
232 }
233
234 // Remove context for that window.
235 let _ = self.remove_context(window);
236
237 // Create new context supporting IME input.
238 let _ = self.create_context(window, allowed);
239 }
240}
241
242impl Drop for Ime {
243 fn drop(&mut self) {
244 unsafe {
245 let _ = self.inner.destroy_all_contexts_if_necessary();
246 let _ = self.inner.close_im_if_necessary();
247 }
248 }
249}
250