1use super::{
2 TextInputMethodClient, TextInputMethodClientEvents, TextInputMethodEdit, TextInputMethodState,
3};
4use crate::input::raw::RawTextInputEventArgs;
5use crate::input::{IInputDevice, InputElement, InputManager, InputMethod, KeyboardDevice};
6use crate::{Ref, WeakRef};
7use std::cell::Cell;
8use std::fmt;
9use std::rc::{Rc, Weak};
10
11#[derive(Clone, Copy, Debug, PartialEq, Eq)]
14#[non_exhaustive]
15pub enum TextInputMethodError {
16 Unavailable,
17 Unsupported,
18 StaleConnection,
19 StateChanged,
20 ReadOnly,
21 ReentrantEdit,
22 Rejected,
23 Intercepted,
24 InvalidRange,
25}
26
27impl fmt::Display for TextInputMethodError {
28 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
29 f.write_str(match self {
30 Self::Unavailable => "The input owner or input services are unavailable.",
31 Self::Unsupported => "The input client does not support this operation.",
32 Self::StaleConnection => "The input connection or composition has been superseded.",
33 Self::StateChanged => "The input client's captured state is no longer current.",
34 Self::ReadOnly => "The input client is read-only.",
35 Self::ReentrantEdit => "An input edit is already being published.",
36 Self::Rejected => "The input client rejected the edit.",
37 Self::Intercepted => "The input event was intercepted before its owner accepted it.",
38 Self::InvalidRange => {
39 "An input range is outside the text or splits a UTF-16 surrogate pair."
40 }
41 })
42 }
43}
44
45impl std::error::Error for TextInputMethodError {}
46
47pub trait TextInputMethodEditing {
51 fn begin_composition(&self) -> Result<(), TextInputMethodError>;
52 fn update_composition(
53 &self,
54 text: &str,
55 cursor: Option<i32>,
56 ) -> Result<(), TextInputMethodError>;
57 fn cancel_composition(&self) -> Result<bool, TextInputMethodError>;
58
59 fn supports_exact_edits(&self) -> bool {
61 false
62 }
63 fn edit_state(&self) -> Result<TextInputMethodState, TextInputMethodError> {
64 Err(TextInputMethodError::Unsupported)
65 }
66 fn apply_edit(
67 &self,
68 _edit: &TextInputMethodEdit,
69 _composing: bool,
70 ) -> Result<bool, TextInputMethodError> {
71 Err(TextInputMethodError::Unsupported)
72 }
73 fn complete_composition(&self) -> Result<bool, TextInputMethodError> {
74 Err(TextInputMethodError::Unsupported)
75 }
76 fn select_exact(&self, _selection: super::TextSelection) -> Result<(), TextInputMethodError> {
77 Err(TextInputMethodError::Unsupported)
78 }
79}
80
81pub struct TextInputMethodConnection {
92 owner: WeakRef<InputElement>,
93 client: Weak<dyn TextInputMethodClient>,
94 active: Cell<bool>,
95 composition: Cell<u64>,
96}
97
98#[derive(Clone, Copy, PartialEq, Eq)]
99struct ClientStateVersion {
100 events: u64,
101 intrinsic: Option<u64>,
102 composition: u64,
103}
104
105impl TextInputMethodConnection {
106 pub(crate) fn new(
107 owner: &Ref<InputElement>,
108 client: &Rc<dyn TextInputMethodClient>,
109 ) -> Rc<Self> {
110 Rc::new(Self {
111 owner: owner.downgrade(),
112 client: Rc::downgrade(client),
113 active: Cell::new(true),
114 composition: Cell::new(0),
115 })
116 }
117
118 pub(crate) fn invalidate(&self) {
119 self.active.set(false);
120 self.composition.set(self.composition.get().wrapping_add(1));
121 }
122
123 pub fn owner(&self) -> Option<Ref<InputElement>> {
125 self.active
126 .get()
127 .then(|| self.owner.upgrade())
128 .flatten()
129 .filter(|owner| {
130 owner.is_attached_to_visual_tree()
131 && owner.is_effectively_enabled()
132 && InputMethod::get_is_input_method_enabled(owner)
133 })
134 }
135
136 pub fn is_current(&self) -> bool {
137 self.owner().is_some() && self.client.upgrade().is_some()
138 }
139
140 pub fn input_scope_names(&self) -> Option<Rc<[crate::input::InputScopeNameValue]>> {
144 self.owner().and_then(|owner| super::TextInputOptions::get_input_scope_names(&owner))
145 }
146
147 pub fn effective_input_scope(&self) -> Option<crate::input::InputScopeNameValue> {
149 self.input_scope_names().and_then(|names| names.first().copied())
150 }
151
152 pub fn effective_content_type(&self, options: &super::TextInputOptions) -> super::TextInputContentType {
155 self.effective_input_scope()
156 .map_or(options.content_type, crate::input::InputScopeNameValue::content_type)
157 }
158
159 fn client(&self) -> Result<Rc<dyn TextInputMethodClient>, TextInputMethodError> {
160 if !self.is_current() {
161 return Err(TextInputMethodError::StaleConnection);
162 }
163 self.client
164 .upgrade()
165 .ok_or(TextInputMethodError::StaleConnection)
166 }
167
168 fn state_version(
169 &self,
170 client: &dyn TextInputMethodClient,
171 ) -> Result<ClientStateVersion, TextInputMethodError> {
172 if !self.is_current() {
173 return Err(TextInputMethodError::StaleConnection);
174 }
175 let composition = self.composition.get();
176 let events = client.events();
177 let version = events.state_version();
178 let intrinsic = client.input_method_state_revision();
179 if !self.is_current() || self.composition.get() != composition {
183 return Err(TextInputMethodError::StaleConnection);
184 }
185 if events.state_version() != version {
186 return Err(TextInputMethodError::StateChanged);
187 }
188 Ok(ClientStateVersion {
189 events: version,
190 intrinsic,
191 composition,
192 })
193 }
194
195 fn check_state(
196 &self,
197 client: &dyn TextInputMethodClient,
198 expected: ClientStateVersion,
199 ) -> Result<(), TextInputMethodError> {
200 if self.state_version(client)? != expected {
201 return Err(TextInputMethodError::StateChanged);
202 }
203 Ok(())
204 }
205
206 pub fn supports_composition(&self) -> bool {
207 self.client()
208 .is_ok_and(|client| client.input_method_editing().is_some())
209 }
210
211 pub fn supports_exact_edits(&self) -> bool {
212 self.client().is_ok_and(|client| {
213 client
214 .input_method_editing()
215 .is_some_and(TextInputMethodEditing::supports_exact_edits)
216 })
217 }
218
219 pub fn edit_state(&self) -> Result<TextInputMethodState, TextInputMethodError> {
220 let client = self.client()?;
221 client
222 .input_method_editing()
223 .ok_or(TextInputMethodError::Unsupported)?
224 .edit_state()
225 }
226
227 pub fn apply_edit(
230 self: &Rc<Self>,
231 edit: &TextInputMethodEdit,
232 timestamp: u64,
233 ) -> Result<bool, TextInputMethodError> {
234 let client = self.client()?;
235 let version = self.state_version(&*client)?;
236 let editing = client
237 .input_method_editing()
238 .ok_or(TextInputMethodError::Unsupported)?;
239 if !editing.supports_exact_edits() {
240 return Err(TextInputMethodError::Unsupported);
241 }
242 edit.apply_to(&editing.edit_state()?.text)?;
243 self.check_state(&*client, version)?;
244 self.dispatch(
245 text_for_edit(edit),
246 timestamp,
247 version,
248 CommitAction::Edit(edit.clone()),
249 )
250 }
251
252 pub fn select_exact(
253 &self,
254 selection: super::TextSelection,
255 ) -> Result<(), TextInputMethodError> {
256 let client = self.client()?;
257 client
258 .input_method_editing()
259 .ok_or(TextInputMethodError::Unsupported)?
260 .select_exact(selection)?;
261 self.client()?;
262 Ok(())
263 }
264
265 pub fn begin_composition(
268 self: &Rc<Self>,
269 ) -> Result<TextInputMethodComposition, TextInputMethodError> {
270 let client = self.client()?;
271 let editing = client
272 .input_method_editing()
273 .ok_or(TextInputMethodError::Unsupported)?;
274 let id = self.composition.get().wrapping_add(1);
275 self.composition.set(id);
276 editing.begin_composition()?;
277 let version = self.state_version(&*client)?;
278 if !self.is_current() || self.composition.get() != id {
279 return Err(TextInputMethodError::StaleConnection);
280 }
281 Ok(TextInputMethodComposition(Rc::new(CompositionState {
282 connection: self.clone(),
283 id,
284 version: Cell::new(version),
285 finished: Cell::new(false),
286 })))
287 }
288
289 pub fn commit_text(
293 self: &Rc<Self>,
294 text: &str,
295 timestamp: u64,
296 ) -> Result<bool, TextInputMethodError> {
297 let client = self.client()?;
298 client
299 .input_method_editing()
300 .ok_or(TextInputMethodError::Unsupported)?;
301 self.dispatch_commit(text, timestamp, self.state_version(&*client)?)
302 }
303
304 fn dispatch_commit(
305 self: &Rc<Self>,
306 text: &str,
307 timestamp: u64,
308 version: ClientStateVersion,
309 ) -> Result<bool, TextInputMethodError> {
310 self.dispatch(text, timestamp, version, CommitAction::Text)
311 }
312
313 fn dispatch(
314 self: &Rc<Self>,
315 text: &str,
316 timestamp: u64,
317 version: ClientStateVersion,
318 action: CommitAction,
319 ) -> Result<bool, TextInputMethodError> {
320 let owner = self.owner().ok_or(TextInputMethodError::StaleConnection)?;
321 let root = owner
322 .get_input_root()
323 .ok_or(TextInputMethodError::Unavailable)?;
324 let keyboard = KeyboardDevice::instance().ok_or(TextInputMethodError::Unavailable)?;
325 let manager = InputManager::instance().ok_or(TextInputMethodError::Unavailable)?;
326 let commit = Rc::new(TextInputMethodCommit {
327 connection: self.clone(),
328 version,
329 result: Cell::new(None),
330 action,
331 });
332 let device: Rc<dyn IInputDevice> = keyboard;
333 let event = Rc::new(RawTextInputEventArgs::for_input_method(
334 device,
335 timestamp,
336 root,
337 text,
338 commit.clone(),
339 ));
340 manager.process_input(event);
341 commit
342 .result
343 .get()
344 .unwrap_or(Err(TextInputMethodError::Intercepted))
345 }
346}
347
348pub struct TextInputMethodCommit {
352 connection: Rc<TextInputMethodConnection>,
353 version: ClientStateVersion,
354 result: Cell<Option<Result<bool, TextInputMethodError>>>,
355 action: CommitAction,
356}
357
358enum CommitAction {
359 Text,
360 Edit(TextInputMethodEdit),
361 CompleteComposition,
362}
363
364fn text_for_edit(edit: &TextInputMethodEdit) -> &str {
365 &edit.text
366}
367
368impl TextInputMethodCommit {
369 pub fn edit(&self) -> Option<&TextInputMethodEdit> {
370 match &self.action {
371 CommitAction::Edit(edit) => Some(edit),
372 _ => None,
373 }
374 }
375 pub fn completes_composition(&self) -> bool {
376 matches!(self.action, CommitAction::CompleteComposition)
377 }
378 pub fn owner(&self) -> Option<Ref<InputElement>> {
379 self.connection.owner()
380 }
381
382 pub fn is_current(&self) -> bool {
383 self.connection
384 .client()
385 .is_ok_and(|client| self.connection.check_state(&*client, self.version).is_ok())
386 }
387
388 pub fn complete(&self, result: Result<bool, TextInputMethodError>) {
389 if self.result.get().is_none() {
390 self.result.set(Some(result));
391 }
392 }
393}
394
395struct CompositionState {
396 connection: Rc<TextInputMethodConnection>,
397 id: u64,
398 version: Cell<ClientStateVersion>,
399 finished: Cell<bool>,
400}
401
402#[derive(Clone)]
406pub struct TextInputMethodComposition(Rc<CompositionState>);
407
408impl TextInputMethodComposition {
409 fn check_lifetime(&self) -> Result<(), TextInputMethodError> {
410 if self.0.finished.get() || self.0.connection.composition.get() != self.0.id {
411 return Err(TextInputMethodError::StaleConnection);
412 }
413 Ok(())
414 }
415
416 fn client(&self) -> Result<Rc<dyn TextInputMethodClient>, TextInputMethodError> {
417 self.check_lifetime()?;
418 let client = self.0.connection.client()?;
419 let version = self.0.version.get();
420 self.0.connection.check_state(&*client, version)?;
421 self.check_lifetime()?;
422 if self.0.version.get() != version {
423 return Err(TextInputMethodError::StateChanged);
424 }
425 Ok(client)
426 }
427
428 pub fn is_current(&self) -> bool {
429 self.client().is_ok()
430 }
431
432 fn finish_operation(
433 &self,
434 client: &dyn TextInputMethodClient,
435 ) -> Result<(), TextInputMethodError> {
436 self.check_lifetime()?;
437 let version = self.0.connection.state_version(client)?;
438 self.check_lifetime()?;
439 self.0.version.set(version);
440 Ok(())
441 }
442
443 pub fn update(&self, text: &str, cursor: Option<i32>) -> Result<(), TextInputMethodError> {
444 let client = self.client()?;
445 client
446 .input_method_editing()
447 .ok_or(TextInputMethodError::Unsupported)?
448 .update_composition(text, cursor)?;
449 self.finish_operation(&*client)
450 }
451
452 pub fn apply_edit(&self, edit: &TextInputMethodEdit) -> Result<bool, TextInputMethodError> {
455 let client = self.client()?;
456 let result = client
457 .input_method_editing()
458 .ok_or(TextInputMethodError::Unsupported)?
459 .apply_edit(edit, true)?;
460 self.finish_operation(&*client)?;
461 Ok(result)
462 }
463
464 pub fn select_exact(
465 &self,
466 selection: super::TextSelection,
467 ) -> Result<(), TextInputMethodError> {
468 let client = self.client()?;
469 client
470 .input_method_editing()
471 .ok_or(TextInputMethodError::Unsupported)?
472 .select_exact(selection)?;
473 self.finish_operation(&*client)
474 }
475
476 pub fn complete(&self, timestamp: u64) -> Result<bool, TextInputMethodError> {
479 let client = self.client()?;
480 let result = self.0.connection.dispatch(
481 "",
482 timestamp,
483 self.0.version.get(),
484 CommitAction::CompleteComposition,
485 )?;
486 self.finish_operation(&*client)?;
487 self.0.finished.set(true);
488 Ok(result)
489 }
490
491 pub fn commit(&self, text: &str, timestamp: u64) -> Result<bool, TextInputMethodError> {
492 let client = self.client()?;
493 let result = self
494 .0
495 .connection
496 .dispatch_commit(text, timestamp, self.0.version.get())?;
497 self.finish_operation(&*client)?;
498 self.0.finished.set(true);
499 Ok(result)
500 }
501
502 pub fn cancel(&self) -> Result<bool, TextInputMethodError> {
503 let client = self.client()?;
504 let result = client
505 .input_method_editing()
506 .ok_or(TextInputMethodError::Unsupported)?
507 .cancel_composition()?;
508 self.finish_operation(&*client)?;
509 self.0.finished.set(true);
510 Ok(result)
511 }
512}
513
514impl TextInputMethodClientEvents {
515 pub(crate) fn set_connection(&self, connection: Option<&Rc<TextInputMethodConnection>>) {
516 *self.connection.borrow_mut() = connection.map(Rc::downgrade);
517 }
518
519 pub(crate) fn connection(&self) -> Option<Rc<TextInputMethodConnection>> {
520 self.connection.borrow().as_ref().and_then(Weak::upgrade)
521 }
522}