Skip to main content

plat/
ime.rs

1//! Bounded preedit snapshots. Composition is separate from committed E_TEXT events.
2use alloc::{string::String, vec::Vec};
3use rt::sync::SpinLock;
4#[derive(Clone, Default, PartialEq, Eq)]
5pub struct Preedit { pub text: String, pub cursor: usize }
6static PREEDIT: SpinLock<Vec<(u8,Preedit)>> = SpinLock::new(Vec::new());
7static COMMITS: SpinLock<(u32,Vec<(u32,String)>)> = SpinLock::new((0,Vec::new()));
8/// One composition commit is one event, avoiding truncation by the character ring.
9pub fn ime_commit_set(win:u8,text:&str) -> bool {
10    if win as usize>=crate::PW_MAX || text.len()>4096 { return false; }
11    if text.is_empty() { return true; }
12    let mut q=COMMITS.lock();
13    // Keep already queued input intact when the bounded payload queue is full.
14    if q.1.len()>=16 { return false; }
15    q.0=q.0.wrapping_add(1); let token=q.0;
16    q.1.push((token,String::from(text)));
17    if crate::evq_push(crate::Event {typ:crate::E_IME_COMMIT,win,data:token,..Default::default()}) { true }
18    else { q.1.pop(); false }
19}
20/// Committed text of any length: commits of at most 4096 bytes, split between characters ->
21/// the bytes queued (fewer: the commit queue or the event ring is full, the rest can follow
22/// once the app took these).
23pub fn ime_commit_text(win:u8,text:&str) -> usize {
24    let mut at=0;
25    while at<text.len() {
26        let mut end=(at+4096).min(text.len());
27        while !text.is_char_boundary(end) { end-=1; }
28        if !ime_commit_set(win,&text[at..end]) { break; }
29        at=end;
30    }
31    at
32}
33// ---- (E4) what the text being typed is (ime_purpose): the input method and an on-screen
34// keyboard choose their mode by it
35pub const IP_TEXT: u32 = 0;
36pub const IP_URL: u32 = 1;
37pub const IP_EMAIL: u32 = 2;
38pub const IP_NUMBER: u32 = 3;
39pub const IP_PHONE: u32 = 4;
40/// no input method (Windows: the window's input context is taken away; X11: no IBus; Wayland:
41/// the password purpose); ui's TB_PASSWORD boxes set it themselves
42pub const IP_PASSWORD: u32 = 5;
43pub const IP_SEARCH: u32 = 6;
44
45static INLINE: core::sync::atomic::AtomicBool = core::sync::atomic::AtomicBool::new(false);
46static PURPOSE: core::sync::atomic::AtomicU32 = core::sync::atomic::AtomicU32::new(IP_TEXT);
47
48/// (E4) the app draws the composition (ime_preedit) at its caret itself: Windows hides the
49/// input method's own composition window (its candidates stay; WM_IME_SETCONTEXT without
50/// ISC_SHOWUICOMPOSITIONWINDOW, sent again to the window with the keyboard on a change).  IBus
51/// and Wayland's text input have no window of their own for it: nothing changes there
52pub fn ime_inline(on: bool) {
53    if INLINE.swap(on, core::sync::atomic::Ordering::Relaxed) != on {
54        #[cfg(windows)]
55        crate::ime_context_refresh();
56    }
57}
58
59/// (E4) ime_inline's state
60pub fn ime_inline_on() -> bool {
61    INLINE.load(core::sync::atomic::Ordering::Relaxed)
62}
63
64/// (E4) the purpose of the text being typed (IP_*): Windows takes the input method away for
65/// IP_PASSWORD and gives it back for the others (also when a window gets the keyboard); X11 keeps
66/// the keys of an IP_PASSWORD field from IBus; Wayland's text input gets it as its content
67/// purpose (again when it changes while enabled); elsewhere it is only kept (ime_purpose_of).
68/// ui's TB_PASSWORD boxes set IP_PASSWORD themselves while they have the keyboard
69pub fn ime_purpose(p: u32) {
70    if PURPOSE.swap(p, core::sync::atomic::Ordering::Relaxed) != p {
71        #[cfg(windows)]
72        crate::ime_context_refresh();
73    }
74}
75
76/// (E4) ime_purpose's value
77pub fn ime_purpose_of() -> u32 {
78    PURPOSE.load(core::sync::atomic::Ordering::Relaxed)
79}
80
81pub fn ime_commit_take(token:u32) -> Option<String> {
82    let mut q=COMMITS.lock(); let i=q.1.iter().position(|v|v.0==token)?; Some(q.1.remove(i).1)
83}
84pub fn ime_preedit(win: u8) -> Preedit { PREEDIT.lock().iter().find(|v| v.0==win).map_or_else(Default::default,|v| v.1.clone()) }
85/// Publish valid UTF-8 and a byte caret; invalid/oversized updates clear composition.
86pub fn ime_preedit_set(win: u8, text: &str, cursor: usize) {
87    if win as usize >= crate::PW_MAX { return; }
88    let value=if text.len()<=4096 && text.is_char_boundary(cursor.min(text.len())) { Preedit { text:String::from(text),cursor:cursor.min(text.len()) } } else { Preedit::default() };
89    let mut slots=PREEDIT.lock();
90    if let Some(slot)=slots.iter_mut().find(|v| v.0==win) { slot.1=value; } else { slots.push((win,value)); }
91    drop(slots);
92    crate::evq_push(crate::Event { typ:crate::E_PREEDIT,win,..Default::default() });
93}