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
29#[derive(Debug)]
30pub struct InputMethodManager {
31 manager: ZwpInputMethodManagerV2,
32}
33
34impl InputMethodManager {
35 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 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 let (start, end) = match cursor {
87 CursorPosition::Hidden => (-1, -1),
88 CursorPosition::Visible { start, end } => (
89 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 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 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 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#[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#[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}