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