smithay_client_toolkit/seat/
input_method.rs

1//! Implementation of the `input-method-unstable-v2` protocol.
2//!
3//! This protocol allows applications to act as input methods for compositors.
4//!
5//! ### Implementation status
6//! Currently only the input-method object is supported. No keyboard grab, no popup surface.
7
8use 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
29#[derive(Debug)]
30pub struct InputMethodManager {
31    manager: ZwpInputMethodManagerV2,
32}
33
34impl InputMethodManager {
35    /// Bind `zwp_input_method_v2` global, if it exists
36    pub fn bind<D>(globals: &GlobalList, qh: &QueueHandle<D>) -> Result<Self, BindError>
37    where
38        D: Dispatch<ZwpInputMethodManagerV2, GlobalData> + 'static,
39    {
40        let manager = globals.bind(qh, 1..=1, GlobalData)?;
41        Ok(Self { manager })
42    }
43
44    /// Request a new input zwp_input_method_v2 object associated with a given
45    /// seat.
46    pub fn get_input_method<State>(&self, qh: &QueueHandle<State>, seat: &WlSeat) -> InputMethod
47    where
48        State: Dispatch<ZwpInputMethodV2, InputMethodData, State> + 'static,
49    {
50        InputMethod {
51            input_method: self.manager.get_input_method(
52                seat,
53                qh,
54                InputMethodData::new(seat.clone()),
55            ),
56        }
57    }
58}
59
60impl<D> Dispatch<zwp_input_method_manager_v2::ZwpInputMethodManagerV2, GlobalData, D>
61    for InputMethodManager
62where
63    D: Dispatch<zwp_input_method_manager_v2::ZwpInputMethodManagerV2, GlobalData>
64        + InputMethodHandler,
65{
66    fn event(
67        _data: &mut D,
68        _manager: &zwp_input_method_manager_v2::ZwpInputMethodManagerV2,
69        _event: zwp_input_method_manager_v2::Event,
70        _: &GlobalData,
71        _conn: &Connection,
72        _qh: &QueueHandle<D>,
73    ) {
74        unreachable!()
75    }
76}
77
78#[derive(Debug)]
79pub struct InputMethod {
80    input_method: ZwpInputMethodV2,
81}
82
83impl InputMethod {
84    pub fn set_preedit_string(&self, text: String, cursor: CursorPosition) {
85        // TODO: should this enforce indices on codepoint boundaries?
86        let (start, end) = match cursor {
87            CursorPosition::Hidden => (-1, -1),
88            CursorPosition::Visible { start, end } => (
89                // This happens only for cursor values in the upper usize range.
90                // Such values are most likely bugs already,
91                // so it's not a problem if one of the cursors weirdly lands at 0 sometimes.
92                start.try_into().unwrap_or(0),
93                end.try_into().unwrap_or(0),
94            ),
95        };
96        self.input_method.set_preedit_string(text, start, end)
97    }
98
99    pub fn commit_string(&self, text: String) {
100        self.input_method.commit_string(text)
101    }
102
103    pub fn delete_surrounding_text(&self, before_length: u32, after_length: u32) {
104        // TODO: this has 2 separate behaviours:
105        // one when preedit text is supported,
106        // and a completely different one when it is not supported
107        // and the input method doesn't know what bytes it deletes.
108        // Not sure how or whether this should be reflected here.
109        self.input_method.delete_surrounding_text(before_length, after_length)
110    }
111
112    pub fn commit(&self) {
113        let data = self.input_method.data::<InputMethodData>().unwrap();
114        let inner = data.inner.lock().unwrap();
115        self.input_method.commit(inner.serial.0)
116    }
117}
118
119#[derive(Debug)]
120pub struct InputMethodData {
121    seat: WlSeat,
122
123    inner: Mutex<InputMethodDataInner>,
124}
125
126impl InputMethodData {
127    /// Create the new input method data associated with the given seat.
128    pub fn new(seat: WlSeat) -> Self {
129        Self {
130            seat,
131            inner: Mutex::new(InputMethodDataInner {
132                pending_state: Default::default(),
133                current_state: Default::default(),
134                serial: Wrapping(0),
135            }),
136        }
137    }
138
139    /// Get the associated seat from the data.
140    pub fn seat(&self) -> &WlSeat {
141        &self.seat
142    }
143}
144
145#[derive(Debug)]
146struct InputMethodDataInner {
147    pending_state: InputMethodEventState,
148    current_state: InputMethodEventState,
149    serial: Wrapping<u32>,
150}
151
152/// Stores incoming interface state.
153#[derive(Debug, Clone, PartialEq)]
154pub struct InputMethodEventState {
155    pub surrounding: SurroundingText,
156    pub content_purpose: ContentPurpose,
157    pub content_hint: ContentHint,
158    pub text_change_cause: ChangeCause,
159    pub active: Active,
160}
161
162impl Default for InputMethodEventState {
163    fn default() -> Self {
164        Self {
165            surrounding: SurroundingText::default(),
166            content_hint: ContentHint::empty(),
167            content_purpose: ContentPurpose::Normal,
168            text_change_cause: ChangeCause::InputMethod,
169            active: Active::default(),
170        }
171    }
172}
173
174#[derive(Clone, Copy, Debug, PartialEq)]
175pub enum CursorPosition {
176    Hidden,
177    Visible { start: usize, end: usize },
178}
179
180#[derive(Default, Clone, Debug, PartialEq)]
181pub struct SurroundingText {
182    pub text: String,
183    pub cursor: u32,
184    pub anchor: u32,
185}
186
187/// State machine for determining the capabilities of a text input
188#[derive(Clone, Debug, Copy, PartialEq)]
189pub enum Active {
190    Inactive,
191    NegotiatingCapabilities { surrounding_text: bool, content_type: bool },
192    Active { surrounding_text: bool, content_type: bool },
193}
194
195impl Default for Active {
196    fn default() -> Self {
197        Self::Inactive
198    }
199}
200
201impl Active {
202    fn with_active(self) -> Self {
203        match self {
204            Self::Inactive => {
205                Self::NegotiatingCapabilities { content_type: false, surrounding_text: false }
206            }
207            other => other,
208        }
209    }
210
211    fn with_surrounding_text(self) -> Self {
212        match self {
213            Self::Inactive => Self::Inactive,
214            Self::NegotiatingCapabilities { content_type, .. } => {
215                Self::NegotiatingCapabilities { content_type, surrounding_text: true }
216            }
217            active @ Self::Active { .. } => active,
218        }
219    }
220
221    fn with_content_type(self) -> Self {
222        match self {
223            Self::Inactive => Self::Inactive,
224            Self::NegotiatingCapabilities { surrounding_text, .. } => {
225                Self::NegotiatingCapabilities { content_type: true, surrounding_text }
226            }
227            active @ Self::Active { .. } => active,
228        }
229    }
230
231    fn with_done(self) -> Self {
232        match self {
233            Self::Inactive => Self::Inactive,
234            Self::NegotiatingCapabilities { surrounding_text, content_type } => {
235                Self::Active { content_type, surrounding_text }
236            }
237            active @ Self::Active { .. } => active,
238        }
239    }
240}
241
242#[macro_export]
243macro_rules! delegate_input_method {
244    ($(@<$( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? ),+>)? $ty: ty) => {
245        $crate::reexports::client::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [
246            $crate::reexports::protocols_misc::zwp_input_method_v2::client::zwp_input_method_manager_v2::ZwpInputMethodManagerV2: $crate::globals::GlobalData
247        ] => $crate::seat::input_method::InputMethodManager);
248        $crate::reexports::client::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [
249            $crate::reexports::protocols_misc::zwp_input_method_v2::client::zwp_input_method_v2::ZwpInputMethodV2: $crate::seat::input_method::InputMethodData
250        ] => $crate::seat::input_method::InputMethod);
251    };
252}
253
254pub trait InputMethodDataExt: Send + Sync {
255    fn input_method_data(&self) -> &InputMethodData;
256}
257
258impl InputMethodDataExt for InputMethodData {
259    fn input_method_data(&self) -> &InputMethodData {
260        self
261    }
262}
263
264pub trait InputMethodHandler: Sized {
265    fn handle_done(
266        &self,
267        connection: &Connection,
268        qh: &QueueHandle<Self>,
269        input_method: &ZwpInputMethodV2,
270        state: &InputMethodEventState,
271    );
272    fn handle_unavailable(
273        &self,
274        connection: &Connection,
275        qh: &QueueHandle<Self>,
276        input_method: &ZwpInputMethodV2,
277    );
278}
279
280impl<D, U> Dispatch<ZwpInputMethodV2, U, D> for InputMethod
281where
282    D: Dispatch<ZwpInputMethodV2, U> + InputMethodHandler,
283    U: InputMethodDataExt,
284{
285    fn event(
286        data: &mut D,
287        input_method: &ZwpInputMethodV2,
288        event: zwp_input_method_v2::Event,
289        udata: &U,
290        conn: &Connection,
291        qh: &QueueHandle<D>,
292    ) {
293        let mut imdata: std::sync::MutexGuard<'_, InputMethodDataInner> =
294            udata.input_method_data().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    delegate_input_method!(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}