1 | // Important: all XIM calls need to happen from the same thread! |
2 | |
3 | mod callbacks; |
4 | mod context; |
5 | mod inner; |
6 | mod input_method; |
7 | |
8 | use std::sync::{ |
9 | mpsc::{Receiver, Sender}, |
10 | Arc, |
11 | }; |
12 | |
13 | use super::{ffi, util, XConnection, XError}; |
14 | |
15 | pub use self::context::ImeContextCreationError; |
16 | use 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))] |
25 | pub enum ImeEvent { |
26 | Enabled, |
27 | Start, |
28 | Update(String, usize), |
29 | End, |
30 | Disabled, |
31 | } |
32 | |
33 | pub type ImeReceiver = Receiver<ImeRequest>; |
34 | pub type ImeSender = Sender<ImeRequest>; |
35 | pub type ImeEventReceiver = Receiver<(ffi::Window, ImeEvent)>; |
36 | pub type ImeEventSender = Sender<(ffi::Window, ImeEvent)>; |
37 | |
38 | /// Request to control XIM handler from the window. |
39 | pub 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)] |
48 | pub(crate) enum ImeCreationError { |
49 | // Boxed to prevent large error type |
50 | OpenFailure(Box<PotentialInputMethods>), |
51 | SetDestroyCallbackFailed(#[allow (dead_code)] XError), |
52 | } |
53 | |
54 | pub(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 | |
61 | impl 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 | |
242 | impl 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 | |