1use crate::globals::GlobalData;
9
10use log::warn;
11
12use std::num::Wrapping;
13use std::sync::Mutex;
14
15use wayland_client::globals::{BindError, GlobalList};
16use wayland_client::protocol::wl_seat::WlSeat;
17use wayland_client::WEnum;
18
19use wayland_client::{Connection, Dispatch, Proxy, QueueHandle};
20use wayland_protocols::wp::text_input::zv3::client::zwp_text_input_v3::{
21 ChangeCause, ContentHint, ContentPurpose,
22};
23pub use wayland_protocols_misc::zwp_input_method_v2::client::zwp_input_method_v2::ZwpInputMethodV2;
24use wayland_protocols_misc::zwp_input_method_v2::client::{
25 zwp_input_method_manager_v2::{self, ZwpInputMethodManagerV2},
26 zwp_input_method_v2,
27};
28
29use crate::dispatch2::Dispatch2;
30
31#[derive(Debug)]
32pub struct InputMethodManager {
33 manager: ZwpInputMethodManagerV2,
34}
35
36impl InputMethodManager {
37 pub fn bind<D>(globals: &GlobalList, qh: &QueueHandle<D>) -> Result<Self, BindError>
39 where
40 D: Dispatch<ZwpInputMethodManagerV2, GlobalData> + 'static,
41 {
42 let manager = globals.bind(qh, 1..=1, GlobalData)?;
43 Ok(Self { manager })
44 }
45
46 pub fn get_input_method<State>(&self, qh: &QueueHandle<State>, seat: &WlSeat) -> InputMethod
49 where
50 State: Dispatch<ZwpInputMethodV2, InputMethodData<()>, State> + 'static,
51 {
52 self.get_input_method_with_data(qh, seat, ())
53 }
54
55 pub fn get_input_method_with_data<State, U>(
56 &self,
57 qh: &QueueHandle<State>,
58 seat: &WlSeat,
59 udata: U,
60 ) -> InputMethod
61 where
62 State: Dispatch<ZwpInputMethodV2, InputMethodData<U>, State> + 'static,
63 U: Send + Sync + 'static,
64 {
65 InputMethod {
66 input_method: self.manager.get_input_method(
67 seat,
68 qh,
69 InputMethodData::new(seat.clone(), udata),
70 ),
71 }
72 }
73}
74
75impl<D> Dispatch2<zwp_input_method_manager_v2::ZwpInputMethodManagerV2, D> for GlobalData
76where
77 D: InputMethodHandler,
78{
79 fn event(
80 &self,
81 _data: &mut D,
82 _manager: &zwp_input_method_manager_v2::ZwpInputMethodManagerV2,
83 _event: zwp_input_method_manager_v2::Event,
84 _conn: &Connection,
85 _qh: &QueueHandle<D>,
86 ) {
87 unreachable!()
88 }
89}
90
91#[derive(Debug)]
92pub struct InputMethod {
93 input_method: ZwpInputMethodV2,
94}
95
96impl InputMethod {
97 pub fn set_preedit_string(&self, text: String, cursor: CursorPosition) {
98 let (start, end) = match cursor {
100 CursorPosition::Hidden => (-1, -1),
101 CursorPosition::Visible { start, end } => (
102 start.try_into().unwrap_or(0),
106 end.try_into().unwrap_or(0),
107 ),
108 };
109 self.input_method.set_preedit_string(text, start, end)
110 }
111
112 pub fn commit_string(&self, text: String) {
113 self.input_method.commit_string(text)
114 }
115
116 pub fn delete_surrounding_text(&self, before_length: u32, after_length: u32) {
117 self.input_method.delete_surrounding_text(before_length, after_length)
123 }
124
125 pub fn commit<U: Send + Sync + 'static>(&self) {
126 let data = self.input_method.data::<InputMethodData<U>>().unwrap();
127 let inner = data.inner.lock().unwrap();
128 self.input_method.commit(inner.serial.0)
129 }
130}
131
132#[derive(Debug)]
133pub struct InputMethodData<U> {
134 seat: WlSeat,
135 inner: Mutex<InputMethodDataInner>,
136 udata: U,
137}
138
139impl<U> InputMethodData<U> {
140 pub fn new(seat: WlSeat, udata: U) -> Self {
142 Self {
143 seat,
144 inner: Mutex::new(InputMethodDataInner {
145 pending_state: Default::default(),
146 current_state: Default::default(),
147 serial: Wrapping(0),
148 }),
149 udata,
150 }
151 }
152
153 pub fn data(&self) -> &U {
154 &self.udata
155 }
156
157 pub fn data_mut(&mut self) -> &mut U {
158 &mut self.udata
159 }
160
161 pub fn seat(&self) -> &WlSeat {
163 &self.seat
164 }
165}
166
167#[derive(Debug)]
168struct InputMethodDataInner {
169 pending_state: InputMethodEventState,
170 current_state: InputMethodEventState,
171 serial: Wrapping<u32>,
172}
173
174#[derive(Debug, Clone, PartialEq)]
176pub struct InputMethodEventState {
177 pub surrounding: SurroundingText,
178 pub content_purpose: ContentPurpose,
179 pub content_hint: ContentHint,
180 pub text_change_cause: ChangeCause,
181 pub active: Active,
182}
183
184impl Default for InputMethodEventState {
185 fn default() -> Self {
186 Self {
187 surrounding: SurroundingText::default(),
188 content_hint: ContentHint::empty(),
189 content_purpose: ContentPurpose::Normal,
190 text_change_cause: ChangeCause::InputMethod,
191 active: Active::default(),
192 }
193 }
194}
195
196#[derive(Clone, Copy, Debug, PartialEq)]
197pub enum CursorPosition {
198 Hidden,
199 Visible { start: usize, end: usize },
200}
201
202#[derive(Default, Clone, Debug, PartialEq)]
203pub struct SurroundingText {
204 pub text: String,
205 pub cursor: u32,
206 pub anchor: u32,
207}
208
209#[derive(Clone, Debug, Default, Copy, PartialEq)]
211pub enum Active {
212 #[default]
213 Inactive,
214 NegotiatingCapabilities {
215 surrounding_text: bool,
216 content_type: bool,
217 },
218 Active {
219 surrounding_text: bool,
220 content_type: bool,
221 },
222}
223
224impl Active {
225 fn with_active(self) -> Self {
226 match self {
227 Self::Inactive => {
228 Self::NegotiatingCapabilities { content_type: false, surrounding_text: false }
229 }
230 other => other,
231 }
232 }
233
234 fn with_surrounding_text(self) -> Self {
235 match self {
236 Self::Inactive => Self::Inactive,
237 Self::NegotiatingCapabilities { content_type, .. } => {
238 Self::NegotiatingCapabilities { content_type, surrounding_text: true }
239 }
240 active @ Self::Active { .. } => active,
241 }
242 }
243
244 fn with_content_type(self) -> Self {
245 match self {
246 Self::Inactive => Self::Inactive,
247 Self::NegotiatingCapabilities { surrounding_text, .. } => {
248 Self::NegotiatingCapabilities { content_type: true, surrounding_text }
249 }
250 active @ Self::Active { .. } => active,
251 }
252 }
253
254 fn with_done(self) -> Self {
255 match self {
256 Self::Inactive => Self::Inactive,
257 Self::NegotiatingCapabilities { surrounding_text, content_type } => {
258 Self::Active { content_type, surrounding_text }
259 }
260 active @ Self::Active { .. } => active,
261 }
262 }
263}
264
265pub trait InputMethodHandler: Sized {
266 fn handle_done(
267 &self,
268 connection: &Connection,
269 qh: &QueueHandle<Self>,
270 input_method: &ZwpInputMethodV2,
271 state: &InputMethodEventState,
272 );
273 fn handle_unavailable(
274 &self,
275 connection: &Connection,
276 qh: &QueueHandle<Self>,
277 input_method: &ZwpInputMethodV2,
278 );
279}
280
281impl<D, U> Dispatch2<ZwpInputMethodV2, D> for InputMethodData<U>
282where
283 D: InputMethodHandler,
284{
285 fn event(
286 &self,
287 data: &mut D,
288 input_method: &ZwpInputMethodV2,
289 event: zwp_input_method_v2::Event,
290 conn: &Connection,
291 qh: &QueueHandle<D>,
292 ) {
293 let mut imdata: std::sync::MutexGuard<'_, InputMethodDataInner> =
294 self.inner.lock().unwrap();
295
296 use zwp_input_method_v2::Event;
297
298 match event {
299 Event::Activate => {
300 imdata.pending_state = InputMethodEventState {
301 active: imdata.pending_state.active.with_active(),
302 ..Default::default()
303 };
304 }
305 Event::Deactivate => {
306 imdata.pending_state = Default::default();
307 }
308 Event::SurroundingText { text, cursor, anchor } => {
309 imdata.pending_state = InputMethodEventState {
310 active: imdata.pending_state.active.with_surrounding_text(),
311 surrounding: SurroundingText { text, cursor, anchor },
312 ..imdata.pending_state.clone()
313 }
314 }
315 Event::TextChangeCause { cause } => {
316 imdata.pending_state = InputMethodEventState {
317 text_change_cause: match cause {
318 WEnum::Value(cause) => cause,
319 WEnum::Unknown(value) => {
320 warn!(
321 "Unknown `text_change_cause`: {}. Assuming not input method.",
322 value
323 );
324 ChangeCause::Other
325 }
326 },
327 ..imdata.pending_state.clone()
328 }
329 }
330 Event::ContentType { hint, purpose } => {
331 imdata.pending_state = InputMethodEventState {
332 active: imdata.pending_state.active.with_content_type(),
333 content_hint: match hint {
334 WEnum::Value(hint) => hint,
335 WEnum::Unknown(value) => {
336 warn!(
337 "Unknown content hints: 0b{:b}, ignoring.",
338 ContentHint::from_bits_retain(value)
339 - ContentHint::from_bits_truncate(value)
340 );
341 ContentHint::from_bits_truncate(value)
342 }
343 },
344 content_purpose: match purpose {
345 WEnum::Value(v) => v,
346 WEnum::Unknown(value) => {
347 warn!("Unknown `content_purpose`: {}. Assuming `normal`.", value);
348 ContentPurpose::Normal
349 }
350 },
351 ..imdata.pending_state.clone()
352 }
353 }
354 Event::Done => {
355 imdata.pending_state = InputMethodEventState {
356 active: imdata.pending_state.active.with_done(),
357 ..imdata.pending_state.clone()
358 };
359 imdata.current_state = imdata.pending_state.clone();
360 imdata.serial += 1;
361 data.handle_done(conn, qh, input_method, &imdata.current_state)
362 }
363 Event::Unavailable => data.handle_unavailable(conn, qh, input_method),
364 _ => unreachable!(),
365 };
366 }
367}
368
369#[cfg(test)]
370mod test {
371 use super::*;
372
373 struct Handler {}
374
375 impl InputMethodHandler for Handler {
376 fn handle_done(
377 &self,
378 _conn: &Connection,
379 _qh: &QueueHandle<Self>,
380 _input_method: &ZwpInputMethodV2,
381 _state: &InputMethodEventState,
382 ) {
383 }
384
385 fn handle_unavailable(
386 &self,
387 _conn: &Connection,
388 _qh: &QueueHandle<Self>,
389 _input_method: &ZwpInputMethodV2,
390 ) {
391 }
392 }
393
394 crate::delegate_dispatch2!(Handler);
395
396 fn assert_is_manager_delegate<T>()
397 where
398 T: wayland_client::Dispatch<ZwpInputMethodManagerV2, crate::globals::GlobalData>,
399 {
400 }
401
402 fn assert_is_delegate<T>()
403 where
404 T: wayland_client::Dispatch<ZwpInputMethodV2, InputMethodData<()>>,
405 {
406 }
407
408 #[test]
409 fn test_valid_assignment() {
410 assert_is_manager_delegate::<Handler>();
411 assert_is_delegate::<Handler>();
412 }
413}