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