wayland_protocols_async/zwp_input_method_v2/
handler.rs1use tokio::{sync::mpsc::{Sender, Receiver}, io::unix::AsyncFd};
2use wayland_client::{EventQueue, protocol::{wl_seat::{WlSeat, self}, wl_registry::{WlRegistry, self}, wl_output::{WlOutput, self}}, globals::{self, GlobalListContents}, Connection, QueueHandle, Dispatch, WEnum, backend::protocol::WEnumError};
3use wayland_protocols::wp::text_input::zv3::client::zwp_text_input_v3::{ChangeCause, ContentHint, ContentPurpose};
4use wayland_protocols_misc::zwp_input_method_v2::client::{zwp_input_method_manager_v2::ZwpInputMethodManagerV2, zwp_input_method_v2::{self, ZwpInputMethodV2}};
5
6#[derive(Debug)]
7pub enum InputMethodMessage {
8 CommitString { text: String },
9 SetPreeditString { text: String, cursor_begin: i32, cursor_end: i32 },
10 DeleteSurroundingText { before_length: u32, after_length: u32 },
11 Commit { serial: u32 },
12 }
15
16#[derive(Debug)]
17pub enum InputMethodEvent {
18 Activate,
19 Deactivate,
20 Done,
21 SurroundingText { text: String, cursor: u32, anchor: u32 },
22 TextChangeCause { cause: Result<ChangeCause, WEnumError> },
23 ContentType { hint: Result<ContentHint, WEnumError>, purpose: Result<ContentPurpose, WEnumError>},
24 Unavailable,
25}
26
27pub struct InputMethodState {
28 event_tx: Sender<InputMethodEvent>,
29 input_method: ZwpInputMethodV2,
30 seat: Option<WlSeat>,
31 output: Option<WlOutput>,
32}
33
34pub struct InputMethodHandler {
35 event_queue: EventQueue<InputMethodState>,
36 state: InputMethodState,
37}
38
39impl InputMethodHandler {
40 pub fn new(event_tx: Sender<InputMethodEvent>) -> Self {
41 let conn = wayland_client::Connection::connect_to_env()
42 .map_err(|_| "could not connect to wayland socket, try setting WAYLAND_DISPLAY.")
43 .unwrap();
44 let (globals, mut event_queue) = globals::registry_queue_init::<InputMethodState>(&conn).unwrap();
45 let qh = event_queue.handle();
46
47 let _input_manager = globals
48 .bind::<ZwpInputMethodManagerV2, _, _>(&qh, core::ops::RangeInclusive::new(1, 1), ())
49 .map_err(|_| "compositor does not implement input method manager (v1).").unwrap();
50 let seat = globals
51 .bind::<WlSeat, _, _>(&qh, core::ops::RangeInclusive::new(1, 1), ())
52 .map_err(|_| "failed to retrieve the seat from global.")
53 .unwrap();
54 let output = globals
55 .bind::<WlOutput, _, _>(&qh, core::ops::RangeInclusive::new(1, 1), ())
56 .map_err(|_| "failed to retrieve the output from global.")
57 .unwrap();
58
59 let input_method = _input_manager.get_input_method(&seat, &qh, ());
61
62 let mut state = InputMethodState {
63 event_tx,
64 input_method,
65 seat: Some(seat),
66 output: Some(output),
67 };
68
69 event_queue.roundtrip(&mut state).unwrap();
70
71 InputMethodHandler {
72 event_queue,
73 state,
74 }
75 }
76
77 pub async fn run(&mut self, mut msg_rx: Receiver<InputMethodMessage>) {
78 let event_queue = &mut self.event_queue;
79 let mut input_method_state = &mut self.state;
80
81 loop {
82 event_queue.dispatch_pending(&mut input_method_state).unwrap();
84 let read_guard = event_queue.prepare_read().unwrap();
85 let fd = read_guard.connection_fd();
86 let async_fd = AsyncFd::new(fd).unwrap();
87
88 tokio::select! {
89 async_guard = async_fd.readable() => {
90 async_guard.unwrap().clear_ready();
91 std::mem::drop(async_fd);
94 let event = read_guard.read();
96 match event {
97 Ok(0) => {},
99 Ok(_) => {
101 event_queue.dispatch_pending(&mut input_method_state).unwrap();
102 },
103 Err(_) => {}
105 }
107 },
108 msg = msg_rx.recv() => {
109 if msg.is_none() {
110 continue;
111 }
112 match msg.unwrap() {
113 InputMethodMessage::CommitString{ text } => {
114 input_method_state.input_method.commit_string(text)
115 },
116 InputMethodMessage::SetPreeditString { text, cursor_begin, cursor_end } => {
117 input_method_state.input_method.set_preedit_string(text, cursor_begin, cursor_end)
118 },
119 InputMethodMessage::DeleteSurroundingText { before_length, after_length } => {
120 input_method_state.input_method.delete_surrounding_text(before_length, after_length)
121 },
122 InputMethodMessage::Commit { serial } => {
123 input_method_state.input_method.commit(serial)
124 },
125 }
126 }
127 }
128
129 let _ = event_queue.flush().expect("wayland connection closed");
131 }
132 }
133}
134
135impl InputMethodState {
136 fn dispatch_event(&self, event: InputMethodEvent) {
137 let tx: Sender<InputMethodEvent> = self.event_tx.clone();
138 tokio::task::spawn(async move {
139 let _ = tx.send(event).await;
140 });
141 }
142}
143
144impl Dispatch<WlRegistry, GlobalListContents> for InputMethodState {
145 fn event(
146 _: &mut Self,
147 registry: &wl_registry::WlRegistry,
148 event: wl_registry::Event,
149 _: &GlobalListContents,
150 _: &Connection,
151 qh: &QueueHandle<InputMethodState>,
152 ) {
153 if let wl_registry::Event::Global {
154 name,
155 interface,
156 version,
157 } = event
158 {
159 if interface == "wl_seat" {
160 registry.bind::<WlSeat, (), InputMethodState>(name, version, qh, ());
161 }
162 }
163 }
164}
165
166impl Dispatch<WlSeat, ()> for InputMethodState {
167 fn event(
168 state: &mut Self,
169 seat: &WlSeat,
170 event: wl_seat::Event,
171 _: &(),
172 _: &Connection,
173 _: &QueueHandle<InputMethodState>,
174 ) {
175 if let wl_seat::Event::Name { .. } = event {
176 state.seat = Some(seat.to_owned());
177 }
178 }
179}
180
181impl Dispatch<WlOutput, ()> for InputMethodState {
182 fn event(
183 state: &mut Self,
184 output: &WlOutput,
185 event: wl_output::Event,
186 _: &(),
187 _: &Connection,
188 _: &QueueHandle<InputMethodState>,
189 ) {
190 if let wl_output::Event::Name { .. } = event {
191 state.output = Some(output.to_owned());
192 }
193 }
194}
195
196impl Dispatch<ZwpInputMethodManagerV2, ()> for InputMethodState {
197 fn event(
198 _: &mut Self,
199 _: &ZwpInputMethodManagerV2,
200 event: <ZwpInputMethodManagerV2 as wayland_client::Proxy>::Event,
201 _: &(),
202 _: &Connection,
203 _: &QueueHandle<Self>,
204 ) {
205 match event {
206 _ => (),
207 }
208 }
209}
210
211
212impl Dispatch<ZwpInputMethodV2, ()> for InputMethodState {
213 fn event(
214 state: &mut Self,
215 _: &ZwpInputMethodV2,
216 event: zwp_input_method_v2::Event,
217 _: &(),
218 _: &Connection,
219 _: &QueueHandle<InputMethodState>,
220 ) {
221 match event {
222 zwp_input_method_v2::Event::Activate =>
223 state.dispatch_event(InputMethodEvent::Activate),
224 zwp_input_method_v2::Event::Deactivate =>
225 state.dispatch_event(InputMethodEvent::Deactivate),
226 zwp_input_method_v2::Event::Done =>
227 state.dispatch_event(InputMethodEvent::Done),
228 zwp_input_method_v2::Event::SurroundingText { text, cursor, anchor } =>
229 state.dispatch_event(InputMethodEvent::SurroundingText { text, cursor, anchor }),
230 zwp_input_method_v2::Event::TextChangeCause { cause } => {
231 state.dispatch_event(InputMethodEvent::TextChangeCause {
232 cause: cause.into_result()
233 })
234 },
235 zwp_input_method_v2::Event::ContentType { hint, purpose } => {
236 state.dispatch_event(InputMethodEvent::ContentType {
237 hint: hint.into_result(),
238 purpose: purpose.into_result(),
239 })
240 },
241 zwp_input_method_v2::Event::Unavailable =>
242 state.dispatch_event(InputMethodEvent::Unavailable),
243 _ => {}
244 }
245 }
246}