1use crate::compositor::Surface;
6use crate::globals::GlobalData;
7
8use log::{debug, warn};
9
10use std::collections::HashMap;
11use std::num::Wrapping;
12use std::ops::Deref;
13use std::sync::{Arc, Mutex, MutexGuard, Weak};
14
15use wayland_client::globals::{BindError, GlobalList};
16use wayland_client::protocol::wl_seat::WlSeat;
17use wayland_client::protocol::wl_surface;
18use wayland_client::WEnum;
19use wayland_client::{Connection, Dispatch, Proxy, QueueHandle};
20use wayland_protocols::wp::text_input::zv3::client::zwp_text_input_v3::{
21 ChangeCause, ContentHint, ContentPurpose,
22};
23
24use wayland_protocols_experimental::input_method::v1::client as protocol;
25
26pub use protocol::xx_input_method_v1::XxInputMethodV1;
27pub use protocol::xx_input_popup_positioner_v1::XxInputPopupPositionerV1;
28pub use protocol::xx_input_popup_surface_v2::XxInputPopupSurfaceV2;
29
30use protocol::{
31 xx_input_method_manager_v2::{self, XxInputMethodManagerV2},
32 xx_input_method_v1, xx_input_popup_positioner_v1, xx_input_popup_surface_v2,
33};
34
35pub use xx_input_popup_positioner_v1::{Anchor, Gravity};
36
37#[derive(Debug, PartialEq, Eq, Clone)]
38pub struct Size {
39 pub width: u32,
40 pub height: u32,
41}
42
43#[derive(Debug, PartialEq, Eq, Clone)]
44pub struct Rectangle {
45 pub x: i32,
46 pub y: i32,
47 pub width: u32,
48 pub height: u32,
49}
50
51#[derive(Debug)]
52pub struct InputMethodManager {
53 manager: XxInputMethodManagerV2,
54}
55
56impl InputMethodManager {
57 pub fn bind<D>(globals: &GlobalList, qh: &QueueHandle<D>) -> Result<Self, BindError>
59 where
60 D: Dispatch<XxInputMethodManagerV2, GlobalData> + 'static,
61 {
62 let manager = globals.bind(qh, 2..=2, GlobalData)?;
63 Ok(Self { manager })
64 }
65
66 pub fn get_input_method<State>(&self, qh: &QueueHandle<State>, seat: &WlSeat) -> InputMethod
69 where
70 State: Dispatch<XxInputMethodV1, InputMethodData, State> + 'static,
71 {
72 InputMethod {
73 input_method: self.manager.get_input_method(
74 seat,
75 qh,
76 InputMethodData::new(seat.clone()),
77 ),
78 }
79 }
80
81 pub fn get_positioner<State>(&self, qh: &QueueHandle<State>) -> PopupPositioner
82 where
83 State: Dispatch<XxInputPopupPositionerV1, PositionerData, State> + 'static,
84 {
85 PopupPositioner(self.manager.get_positioner(qh, PositionerData))
86 }
87}
88
89impl<D> Dispatch<xx_input_method_manager_v2::XxInputMethodManagerV2, GlobalData, D>
90 for InputMethodManager
91where
92 D: Dispatch<xx_input_method_manager_v2::XxInputMethodManagerV2, GlobalData>
93 + InputMethodHandler,
94{
95 fn event(
96 _data: &mut D,
97 _manager: &xx_input_method_manager_v2::XxInputMethodManagerV2,
98 _event: xx_input_method_manager_v2::Event,
99 _: &GlobalData,
100 _conn: &Connection,
101 _qh: &QueueHandle<D>,
102 ) {
103 unreachable!()
104 }
105}
106
107#[derive(Debug)]
112pub struct PopupPositioner(XxInputPopupPositionerV1);
113
114impl Deref for PopupPositioner {
115 type Target = XxInputPopupPositionerV1;
116
117 fn deref(&self) -> &Self::Target {
118 &self.0
119 }
120}
121
122impl Drop for PopupPositioner {
123 fn drop(&mut self) {
124 self.0.destroy()
125 }
126}
127
128impl<D> Dispatch<XxInputPopupPositionerV1, PositionerData, D> for PopupPositioner
129where
130 D: Dispatch<XxInputPopupPositionerV1, PositionerData> + InputMethodHandler,
131{
132 fn event(
133 _data: &mut D,
134 _manager: &XxInputPopupPositionerV1,
135 _event: xx_input_popup_positioner_v1::Event,
136 _: &PositionerData,
137 _conn: &Connection,
138 _qh: &QueueHandle<D>,
139 ) {
140 unreachable!("Positioner has no events")
141 }
142}
143
144#[derive(Debug)]
145pub struct PositionerData;
146
147#[derive(Debug)]
148pub struct InputMethod {
149 input_method: XxInputMethodV1,
150}
151
152#[derive(Debug)]
154pub enum InvalidIndex {
155 Start,
157 End,
159 Both,
161}
162
163impl InputMethod {
164 pub fn input_method(&self) -> &XxInputMethodV1 {
165 &self.input_method
166 }
167
168 pub fn set_preedit_string(
169 &self,
170 text: String,
171 cursor: CursorPosition,
172 ) -> Result<(), InvalidIndex> {
173 let (start, end) = match cursor {
174 CursorPosition::Hidden => (-1, -1),
175 CursorPosition::Visible { start, end } => {
176 match (text.is_char_boundary(start), text.is_char_boundary(end)) {
177 (true, true) => (
178 start.try_into().unwrap_or(0),
182 end.try_into().unwrap_or(0),
183 ),
184 (true, false) => {
185 return Err(InvalidIndex::End);
186 }
187 (false, true) => {
188 return Err(InvalidIndex::Start);
189 }
190 (false, false) => {
191 return Err(InvalidIndex::Both);
192 }
193 }
194 }
195 };
196 self.input_method.set_preedit_string(text, start, end);
197 Ok(())
198 }
199
200 pub fn commit_string(&self, text: String) {
201 self.input_method.commit_string(text)
202 }
203
204 pub fn delete_surrounding_text(&self, before_length: u32, after_length: u32) {
205 self.input_method.delete_surrounding_text(before_length, after_length)
211 }
212
213 pub fn commit(&self) {
214 let data = self.input_method.data::<InputMethodData>().unwrap();
215 let inner = &data.inner.lock().unwrap();
216 self.input_method.commit(inner.serial.0)
217 }
218
219 pub fn get_input_popup_surface<D>(
220 &self,
221 qh: &QueueHandle<D>,
222 surface: impl Into<Surface>,
223 positioner: &PopupPositioner,
224 ) -> Popup
225 where
226 D: Dispatch<XxInputPopupSurfaceV2, PopupData> + 'static,
227 {
228 let data = self.input_method.data::<InputMethodData>().unwrap();
229 let surface = surface.into();
230 Popup {
231 input_method: self.input_method.clone(),
232 popup: self.input_method.get_input_popup_surface(
233 surface.wl_surface(),
234 &positioner.0,
235 qh,
236 PopupData { inner: Mutex::new(PopupDataInner::new(Arc::downgrade(&data.inner))) },
237 ),
238 surface,
239 }
240 }
241}
242
243#[derive(Debug)]
244pub struct InputMethodData {
245 seat: WlSeat,
246
247 inner: Arc<Mutex<InputMethodDataInner>>,
248}
249
250impl InputMethodData {
251 pub fn new(seat: WlSeat) -> Self {
253 Self {
254 seat,
255 inner: Arc::new(Mutex::new(InputMethodDataInner {
256 pending_state: Default::default(),
257 current_state: Default::default(),
258 serial: Wrapping(0),
259 })),
260 }
261 }
262
263 pub fn seat(&self) -> &WlSeat {
265 &self.seat
266 }
267}
268
269#[derive(Debug)]
270struct InputMethodDataInner {
271 pending_state: InputMethodEventState,
272 current_state: InputMethodEventState,
273 serial: Wrapping<u32>,
274}
275
276#[derive(Debug, Clone, PartialEq)]
278pub struct InputMethodEventState {
279 pub surrounding: SurroundingText,
280 pub content_purpose: ContentPurpose,
281 pub content_hint: ContentHint,
282 pub text_change_cause: ChangeCause,
283 pub active: Active,
284 pub popups: HashMap<XxInputPopupSurfaceV2, PopupState>,
285}
286
287impl Default for InputMethodEventState {
288 fn default() -> Self {
289 Self {
290 surrounding: SurroundingText::default(),
291 content_hint: ContentHint::empty(),
292 content_purpose: ContentPurpose::Normal,
293 text_change_cause: ChangeCause::InputMethod,
294 active: Active::default(),
295 popups: Default::default(),
296 }
297 }
298}
299
300#[derive(Clone, Debug, PartialEq)]
302pub struct PopupState {
303 pub anchor: Rectangle,
305 pub size: Size,
306 pub serial: Option<u32>,
308 pub repositioned: Option<u32>,
310}
311
312impl PopupState {
313 fn new_uninit() -> Self {
315 Self {
316 anchor: Rectangle { x: 0, y: 0, width: 0, height: 0 },
318 size: Size { width: 0, height: 0 },
319 serial: None,
320 repositioned: None,
321 }
322 }
323
324 fn reset_on_done(&self) -> Self {
326 Self { serial: None, repositioned: None, ..self.clone() }
327 }
328}
329
330#[derive(Clone, Copy, Debug, PartialEq)]
331pub enum CursorPosition {
332 Hidden,
333 Visible { start: usize, end: usize },
335}
336
337#[derive(Default, Clone, Debug, PartialEq)]
338pub struct SurroundingText {
339 pub text: String,
340 pub cursor: u32,
341 pub anchor: u32,
342}
343
344#[derive(Clone, Debug, Copy, PartialEq)]
346pub enum Active {
347 Inactive,
348 NegotiatingCapabilities { surrounding_text: bool, content_type: bool },
349 Active { surrounding_text: bool, content_type: bool },
350}
351
352impl Default for Active {
353 fn default() -> Self {
354 Self::Inactive
355 }
356}
357
358impl Active {
359 fn with_active(self) -> Self {
360 match self {
361 Self::Inactive => {
362 Self::NegotiatingCapabilities { content_type: false, surrounding_text: false }
363 }
364 other => other,
365 }
366 }
367
368 fn with_surrounding_text(self) -> Self {
369 match self {
370 Self::Inactive => Self::Inactive,
371 Self::NegotiatingCapabilities { content_type, .. } => {
372 Self::NegotiatingCapabilities { content_type, surrounding_text: true }
373 }
374 active @ Self::Active { .. } => active,
375 }
376 }
377
378 fn with_content_type(self) -> Self {
379 match self {
380 Self::Inactive => Self::Inactive,
381 Self::NegotiatingCapabilities { surrounding_text, .. } => {
382 Self::NegotiatingCapabilities { content_type: true, surrounding_text }
383 }
384 active @ Self::Active { .. } => active,
385 }
386 }
387
388 fn with_done(self) -> Self {
389 match self {
390 Self::Inactive => Self::Inactive,
391 Self::NegotiatingCapabilities { surrounding_text, content_type } => {
392 Self::Active { content_type, surrounding_text }
393 }
394 active @ Self::Active { .. } => active,
395 }
396 }
397}
398
399#[derive(Debug)]
400pub struct Popup {
401 input_method: XxInputMethodV1,
403 popup: XxInputPopupSurfaceV2,
404 surface: Surface,
405}
406
407impl Popup {
408 pub fn wl_surface(&self) -> &wl_surface::WlSurface {
409 self.surface.wl_surface()
410 }
411
412 pub fn input_method(&self) -> &XxInputMethodV1 {
413 &self.input_method
414 }
415
416 pub fn popup(&self) -> &XxInputPopupSurfaceV2 {
417 &self.popup
418 }
419
420 pub fn reposition(&self, positioner: &PopupPositioner) {
421 let data = self.popup.data::<PopupData>().unwrap();
422 let mut inner: MutexGuard<'_, PopupDataInner> = data.inner.lock().unwrap();
423 let token = inner.next_token;
424 inner.next_token = inner.next_token.wrapping_add(1);
425 inner.outstanding_reposition_token = Some(token);
426 self.popup.reposition(positioner, token);
427 }
428}
429
430impl<D> Dispatch<XxInputPopupSurfaceV2, PopupData, D> for Popup
431where
432 D: Dispatch<XxInputPopupSurfaceV2, PopupData> + InputMethodHandler,
433{
434 fn event(
435 _data: &mut D,
436 popup: &XxInputPopupSurfaceV2,
437 event: xx_input_popup_surface_v2::Event,
438 udata: &PopupData,
439 _conn: &Connection,
440 _qh: &QueueHandle<D>,
441 ) {
442 let inner: MutexGuard<'_, PopupDataInner> = udata.inner.lock().unwrap();
443 if let Some(im) = inner.im.upgrade() {
444 let mut im = im.lock().unwrap();
445
446 use xx_input_popup_surface_v2::Event;
447 match event {
448 Event::Repositioned { token } => {
449 let state = im
450 .pending_state
451 .popups
452 .entry(popup.clone())
453 .or_insert(PopupState::new_uninit());
454 if state.serial.is_some() {
455 state.repositioned = Some(token);
456 } else {
457 warn!(
458 "Repositioned received after im.done but before popup.start_configure"
459 );
460 }
461 }
462 Event::StartConfigure {
463 width,
464 height,
465 anchor_x,
466 anchor_y,
467 anchor_width,
468 anchor_height,
469 serial,
470 } => {
471 let uninit = PopupState::new_uninit();
472 let prev_state = im.pending_state.popups.get(popup).unwrap_or(&uninit);
473 let anchor = Rectangle {
474 x: anchor_x,
475 y: anchor_y,
476 width: anchor_width,
477 height: anchor_height,
478 };
479 let popup_state = PopupState {
480 anchor,
481 serial: Some(serial),
482 size: Size { width, height },
483 ..prev_state.clone()
484 };
485 im.pending_state.popups.insert(popup.clone(), popup_state);
486 }
487 _ => unreachable!(),
488 };
489 } else {
490 warn!("received event for an input method that already disappeared");
491 }
492 }
493}
494
495#[derive(Debug)]
497pub struct PopupData {
498 inner: Mutex<PopupDataInner>,
500}
501
502#[derive(Debug)]
504struct PopupDataInner {
505 im: Weak<Mutex<InputMethodDataInner>>,
506 next_token: u32,
507 outstanding_reposition_token: Option<u32>,
508}
509
510impl PopupDataInner {
511 fn new(im: Weak<Mutex<InputMethodDataInner>>) -> Self {
513 Self { im, next_token: 0, outstanding_reposition_token: None }
514 }
515
516 fn update_repositioned(&mut self, state: &PopupState) -> Option<u32> {
518 match (state.repositioned, self.outstanding_reposition_token) {
519 (Some(_), None) => {
520 warn!("Received a repositioned token even though all were already processed. Did one arrive out of order?");
521 None
522 }
523 (None, _) => None,
524 (received, Some(outstanding)) => {
525 if received == Some(outstanding) {
526 self.outstanding_reposition_token = None
527 } else {
528 debug!(
529 "Received a reposition token that is not the most recently requested one."
530 )
531 };
532 received
533 }
534 }
535 }
536}
537
538#[macro_export]
539macro_rules! delegate_input_method_v3 {
540 ($(@<$( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? ),+>)? $ty: ty) => {
541 $crate::reexports::client::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [
542 $crate::reexports::protocols_experimental::input_method::v1::client::xx_input_method_manager_v2::XxInputMethodManagerV2: $crate::globals::GlobalData
543 ] => $crate::seat::input_method_v3::InputMethodManager);
544 $crate::reexports::client::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [
545 $crate::reexports::protocols_experimental::input_method::v1::client::xx_input_method_v1::XxInputMethodV1: $crate::seat::input_method_v3::InputMethodData
546 ] => $crate::seat::input_method_v3::InputMethod);
547 $crate::reexports::client::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [
548 $crate::reexports::protocols_experimental::input_method::v1::client::xx_input_popup_surface_v2::XxInputPopupSurfaceV2: $crate::seat::input_method_v3::PopupData
549 ] => $crate::seat::input_method_v3::Popup);
550 $crate::reexports::client::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [
551 $crate::reexports::protocols_experimental::input_method::v1::client::xx_input_popup_positioner_v1::XxInputPopupPositionerV1: $crate::seat::input_method_v3::PositionerData
552 ] => $crate::seat::input_method_v3::PopupPositioner);
553 };
554}
555
556pub trait InputMethodDataExt: Send + Sync {
557 fn input_method_data(&self) -> &InputMethodData;
558}
559
560impl InputMethodDataExt for InputMethodData {
561 fn input_method_data(&self) -> &InputMethodData {
562 self
563 }
564}
565
566pub trait InputMethodHandler: Sized {
567 fn handle_done(
568 &mut self,
569 qh: &QueueHandle<Self>,
570 input_method: &XxInputMethodV1,
571 state: &InputMethodEventState,
572 );
573 fn handle_unavailable(&mut self, qh: &QueueHandle<Self>, input_method: &XxInputMethodV1);
581}
582
583impl<D, U> Dispatch<XxInputMethodV1, U, D> for InputMethod
584where
585 D: Dispatch<XxInputMethodV1, U> + InputMethodHandler,
586 U: InputMethodDataExt,
587{
588 fn event(
589 data: &mut D,
590 input_method: &XxInputMethodV1,
591 event: xx_input_method_v1::Event,
592 udata: &U,
593 _conn: &Connection,
594 qh: &QueueHandle<D>,
595 ) {
596 let mut imdata: MutexGuard<'_, InputMethodDataInner> =
597 udata.input_method_data().inner.lock().unwrap();
598
599 use xx_input_method_v1::Event;
600
601 match event {
602 Event::Activate => {
603 imdata.pending_state = InputMethodEventState {
604 active: imdata.pending_state.active.with_active(),
605 ..Default::default()
606 };
607 }
608 Event::Deactivate => {
609 imdata.pending_state = Default::default();
610 }
611 Event::SurroundingText { text, cursor, anchor } => {
612 imdata.pending_state = InputMethodEventState {
613 active: imdata.pending_state.active.with_surrounding_text(),
614 surrounding: SurroundingText { text, cursor, anchor },
615 ..imdata.pending_state.clone()
616 }
617 }
618 Event::TextChangeCause { cause } => {
619 imdata.pending_state = InputMethodEventState {
620 text_change_cause: match cause {
621 WEnum::Value(cause) => cause,
622 WEnum::Unknown(value) => {
623 warn!(
624 "Unknown `text_change_cause`: {}. Assuming not input method.",
625 value
626 );
627 ChangeCause::Other
628 }
629 },
630 ..imdata.pending_state.clone()
631 }
632 }
633 Event::ContentType { hint, purpose } => {
634 imdata.pending_state = InputMethodEventState {
635 active: imdata.pending_state.active.with_content_type(),
636 content_hint: match hint {
637 WEnum::Value(hint) => hint,
638 WEnum::Unknown(value) => {
639 warn!(
640 "Unknown content hints: 0b{:b}, ignoring.",
641 ContentHint::from_bits_retain(value)
642 - ContentHint::from_bits_truncate(value)
643 );
644 ContentHint::from_bits_truncate(value)
645 }
646 },
647 content_purpose: match purpose {
648 WEnum::Value(v) => v,
649 WEnum::Unknown(value) => {
650 warn!("Unknown `content_purpose`: {}. Assuming `normal`.", value);
651 ContentPurpose::Normal
652 }
653 },
654 ..imdata.pending_state.clone()
655 }
656 }
657 Event::Done => {
658 imdata.pending_state = InputMethodEventState {
659 active: imdata.pending_state.active.with_done(),
660 ..imdata.pending_state.clone()
661 };
662 for (popup, state) in imdata.pending_state.popups.iter_mut() {
663 if let Some(serial) = state.serial {
664 popup.ack_configure(serial);
665 }
666 let data = popup.data::<PopupData>().unwrap();
667 {
668 let mut inner: MutexGuard<'_, PopupDataInner> = data.inner.lock().unwrap();
669 inner.update_repositioned(state);
670 }
671 *state = state.clone().reset_on_done();
672 }
673 imdata.current_state = imdata.pending_state.clone();
674 imdata.serial += 1;
675 data.handle_done(qh, input_method, &imdata.current_state)
676 }
677 Event::Unavailable => data.handle_unavailable(qh, input_method),
678 _ => unreachable!(),
679 };
680 }
681}
682
683#[cfg(test)]
684mod test {
685 use super::*;
686
687 struct Handler {}
688
689 impl InputMethodHandler for Handler {
690 fn handle_done(
691 &mut self,
692 _qh: &QueueHandle<Self>,
693 _input_method: &XxInputMethodV1,
694 _state: &InputMethodEventState,
695 ) {
696 }
697
698 fn handle_unavailable(&mut self, _qh: &QueueHandle<Self>, _input_method: &XxInputMethodV1) {
699 }
700 }
701
702 delegate_input_method_v3!(Handler);
703
704 fn assert_is_manager_delegate<T>()
705 where
706 T: wayland_client::Dispatch<
707 protocol::xx_input_method_manager_v2::XxInputMethodManagerV2,
708 crate::globals::GlobalData,
709 >,
710 {
711 }
712
713 fn assert_is_delegate<T>()
714 where
715 T: wayland_client::Dispatch<protocol::xx_input_method_v1::XxInputMethodV1, InputMethodData>,
716 {
717 }
718
719 fn assert_is_popup_delegate<T>()
720 where
721 T: wayland_client::Dispatch<
722 protocol::xx_input_popup_surface_v2::XxInputPopupSurfaceV2,
723 PopupData,
724 >,
725 {
726 }
727
728 fn assert_is_positioner_delegate<T>()
729 where
730 T: wayland_client::Dispatch<
731 protocol::xx_input_popup_positioner_v1::XxInputPopupPositionerV1,
732 PositionerData,
733 >,
734 {
735 }
736
737 #[test]
738 fn test_valid_assignment() {
739 assert_is_manager_delegate::<Handler>();
740 assert_is_delegate::<Handler>();
741 assert_is_popup_delegate::<Handler>();
742 assert_is_positioner_delegate::<Handler>();
743 }
744}