Wayland compositor (wlroots)
git clone https://git.lucas.co/cce-compositor.git
src/server/input_relay.rs (12K)
1 // SPDX-FileCopyrightText: © 2021 The River Developers
2 // SPDX-License-Identifier: GPL-3.0-only
3
4 use crate::ffi;
5 use crate::server::{WlListener, wl_signal_add};
6 use crate::text_input::TextInput;
7 use crate::seat::Seat;
8
9 #[repr(C)]
10 pub struct InputRelay {
11 pub seat: *mut Seat,
12 pub text_inputs: ffi::wl_list,
13 pub input_method: *mut ffi::wlr_input_method_v2,
14 pub input_popups: ffi::wl_list,
15 pub text_input: *mut TextInput,
16
17 pub input_method_commit: ffi::wl_listener,
18 pub grab_keyboard: ffi::wl_listener,
19 pub input_method_destroy: ffi::wl_listener,
20 pub input_method_new_popup: ffi::wl_listener,
21
22 pub grab_keyboard_destroy: ffi::wl_listener,
23
24 /// The on-screen keyboard following a touched field (`osk.rs`).
25 pub osk: crate::osk::Osk,
26 }
27
28 unsafe fn connect_listener(
29 signal: *mut ffi::wl_signal,
30 listener: *mut ffi::wl_listener,
31 callback: unsafe extern "C" fn(listener: *mut ffi::wl_listener, data: *mut std::ffi::c_void),
32 ) {
33 let wl_lis = listener as *mut WlListener;
34 (*wl_lis).notify = Some(callback);
35 wl_signal_add(signal, listener);
36 }
37
38 unsafe fn wl_listener_remove_safe(listener: *mut ffi::wl_listener) {
39 let prev = (*listener).link.prev;
40 let next = (*listener).link.next;
41 if !prev.is_null() && !next.is_null() && prev != listener as *mut ffi::wl_list && next != listener as *mut ffi::wl_list {
42 ffi::wl_list_remove(&mut (*listener).link);
43 (*listener).link.prev = std::ptr::null_mut();
44 (*listener).link.next = std::ptr::null_mut();
45 }
46 }
47
48 impl InputRelay {
49 pub unsafe fn init(&mut self, seat: *mut Seat) {
50 self.seat = seat;
51 ffi::wl_list_init(&mut self.text_inputs);
52 ffi::wl_list_init(&mut self.input_popups);
53 self.input_method = std::ptr::null_mut();
54 self.text_input = std::ptr::null_mut();
55 self.input_method_commit = std::mem::zeroed();
56 self.grab_keyboard = std::mem::zeroed();
57 self.input_method_destroy = std::mem::zeroed();
58 self.input_method_new_popup = std::mem::zeroed();
59 self.grab_keyboard_destroy = std::mem::zeroed();
60 // The relay starts as zeroed memory: write, don't assign over it.
61 std::ptr::write(&mut self.osk, crate::osk::Osk::new((*seat).server));
62 }
63
64 pub unsafe fn new_input_method(&mut self, input_method: *mut ffi::wlr_input_method_v2) {
65 let seat = crate::container_of!(self, Seat, relay);
66 let seat_name = std::ffi::CStr::from_ptr(ffi::river_wlr_seat_get_name((*seat).wlr_seat))
67 .to_str()
68 .unwrap_or("unknown");
69 log::debug!("new input method on seat {}", seat_name);
70
71 if !self.input_method.is_null() {
72 log::info!("seat {} already has an input method", seat_name);
73 ffi::wlr_input_method_v2_send_unavailable(input_method);
74 return;
75 }
76
77 self.input_method = input_method;
78
79 connect_listener(&mut (*input_method).events.commit, &mut self.input_method_commit, handle_input_method_commit);
80 connect_listener(&mut (*input_method).events.grab_keyboard, &mut self.grab_keyboard, handle_input_method_grab_keyboard);
81 connect_listener(&mut (*input_method).events.destroy, &mut self.input_method_destroy, handle_input_method_destroy);
82 connect_listener(&mut (*input_method).events.new_popup_surface, &mut self.input_method_new_popup, handle_input_method_new_popup);
83
84 // Text inputs are entered whether or not an input method exists
85 // (`focus`), so one that arrives late only needs telling about the
86 // field already enabled.
87 if !self.text_input.is_null() {
88 ffi::wlr_input_method_v2_send_activate(input_method);
89 self.send_input_method_state();
90 }
91 }
92
93 pub unsafe fn disable_text_input(&mut self) {
94 assert!(!self.text_input.is_null());
95 self.text_input = std::ptr::null_mut();
96 self.osk.field_gone();
97
98 if !self.input_method.is_null() {
99 let mut pos = self.input_popups.next;
100 let head_ptr = &self.input_popups as *const ffi::wl_list;
101 while pos != head_ptr as *mut ffi::wl_list {
102 let next_pos = (*pos).next;
103 let popup = crate::container_of!(pos, crate::input_popup::InputPopup, link);
104 (*popup).update();
105 pos = next_pos;
106 }
107
108 ffi::wlr_input_method_v2_send_deactivate(self.input_method);
109 ffi::wlr_input_method_v2_send_done(self.input_method);
110 }
111 }
112
113 pub unsafe fn send_input_method_state(&mut self) {
114 let input_method = self.input_method;
115 let wlr_text_input = (*self.text_input).wlr_text_input;
116
117 let active_features = (*wlr_text_input).active_features;
118 let feature_surrounding = ffi::wlr_text_input_v3_features_WLR_TEXT_INPUT_V3_FEATURE_SURROUNDING_TEXT;
119 let feature_content = ffi::wlr_text_input_v3_features_WLR_TEXT_INPUT_V3_FEATURE_CONTENT_TYPE;
120
121 if (active_features & feature_surrounding) != 0 {
122 let text = (*wlr_text_input).current.surrounding.text;
123 if !text.is_null() {
124 ffi::wlr_input_method_v2_send_surrounding_text(
125 input_method,
126 text,
127 (*wlr_text_input).current.surrounding.cursor,
128 (*wlr_text_input).current.surrounding.anchor,
129 );
130 }
131 }
132
133 ffi::wlr_input_method_v2_send_text_change_cause(input_method, (*wlr_text_input).current.text_change_cause);
134
135 if (active_features & feature_content) != 0 {
136 ffi::wlr_input_method_v2_send_content_type(
137 input_method,
138 (*wlr_text_input).current.content_type.hint,
139 (*wlr_text_input).current.content_type.purpose,
140 );
141 }
142
143 let mut pos = self.input_popups.next;
144 let head_ptr = &self.input_popups as *const ffi::wl_list;
145 while pos != head_ptr as *mut ffi::wl_list {
146 let next_pos = (*pos).next;
147 let popup = crate::container_of!(pos, crate::input_popup::InputPopup, link);
148 (*popup).update();
149 pos = next_pos;
150 }
151
152 ffi::wlr_input_method_v2_send_done(input_method);
153 }
154
155 /// Keyboard focus moved to `new_focus` (null: nowhere). Text inputs of
156 /// the client that lost it are left, and those of the client that got it
157 /// entered — whether or not an input method is running. river entered
158 /// them only with one, but then no client ever enabled a field, and the
159 /// on-screen keyboard (`osk.rs`) is driven by exactly that enable.
160 pub unsafe fn focus(&mut self, new_focus: *mut ffi::wlr_surface) {
161 let head_ptr = &self.text_inputs as *const ffi::wl_list as *mut ffi::wl_list;
162
163 // Send leave events. A text input already on the new surface keeps
164 // it: a refocus of the same surface is not a leave.
165 let mut pos = self.text_inputs.next;
166 while pos != head_ptr {
167 let next_pos = (*pos).next;
168 let text_input = crate::container_of!(pos, TextInput, link);
169 let focused = (*(*text_input).wlr_text_input).focused_surface;
170 if !focused.is_null() && focused != new_focus {
171 ffi::wlr_text_input_v3_send_leave((*text_input).wlr_text_input);
172 }
173 pos = next_pos;
174 }
175
176 // Clear the enabled text input unless its surface kept focus
177 if !self.text_input.is_null() {
178 let focused = (*(*self.text_input).wlr_text_input).focused_surface;
179 if focused.is_null() || focused != new_focus {
180 self.disable_text_input();
181 }
182 }
183
184 if new_focus.is_null() {
185 return;
186 }
187 let new_client = ffi::wl_resource_get_client(ffi::river_wlr_surface_get_resource(new_focus));
188 let mut pos = self.text_inputs.next;
189 while pos != head_ptr {
190 let next_pos = (*pos).next;
191 let text_input = crate::container_of!(pos, TextInput, link);
192 let wlr_text_input = (*text_input).wlr_text_input;
193 if (*wlr_text_input).focused_surface.is_null()
194 && ffi::wl_resource_get_client((*wlr_text_input).resource) == new_client
195 {
196 ffi::wlr_text_input_v3_send_enter(wlr_text_input, new_focus);
197 }
198 pos = next_pos;
199 }
200 }
201 }
202
203 unsafe extern "C" fn handle_input_method_commit(listener: *mut ffi::wl_listener, _data: *mut std::ffi::c_void) {
204 let relay = crate::container_of!(listener, InputRelay, input_method_commit);
205 let input_method = (*relay).input_method;
206
207 if !(*input_method).client_active {
208 return;
209 }
210
211 let text_input = (*relay).text_input;
212 if text_input.is_null() {
213 return;
214 }
215
216 let preedit_text = (*input_method).current.preedit.text;
217 if !preedit_text.is_null() {
218 ffi::wlr_text_input_v3_send_preedit_string(
219 (*text_input).wlr_text_input,
220 preedit_text,
221 (*input_method).current.preedit.cursor_begin,
222 (*input_method).current.preedit.cursor_end,
223 );
224 }
225
226 let commit_text = (*input_method).current.commit_text;
227 if !commit_text.is_null() {
228 ffi::wlr_text_input_v3_send_commit_string((*text_input).wlr_text_input, commit_text);
229 }
230
231 if (*input_method).current.delete.before_length != 0
232 || (*input_method).current.delete.after_length != 0
233 {
234 ffi::wlr_text_input_v3_send_delete_surrounding_text(
235 (*text_input).wlr_text_input,
236 (*input_method).current.delete.before_length,
237 (*input_method).current.delete.after_length,
238 );
239 }
240
241 ffi::wlr_text_input_v3_send_done((*text_input).wlr_text_input);
242 }
243
244 unsafe extern "C" fn handle_input_method_destroy(listener: *mut ffi::wl_listener, _data: *mut std::ffi::c_void) {
245 let relay = crate::container_of!(listener, InputRelay, input_method_destroy);
246
247 wl_listener_remove_safe(&mut (*relay).input_method_commit);
248 wl_listener_remove_safe(&mut (*relay).grab_keyboard);
249 wl_listener_remove_safe(&mut (*relay).input_method_destroy);
250 wl_listener_remove_safe(&mut (*relay).input_method_new_popup);
251 (*relay).input_method = std::ptr::null_mut();
252 // The text inputs stay entered and enabled: they never depended on the
253 // input method (`InputRelay::focus`).
254 }
255
256 unsafe extern "C" fn handle_input_method_grab_keyboard(
257 listener: *mut ffi::wl_listener,
258 data: *mut std::ffi::c_void,
259 ) {
260 let relay = crate::container_of!(listener, InputRelay, grab_keyboard);
261 let keyboard_grab = data as *mut ffi::wlr_input_method_keyboard_grab_v2;
262 let seat = crate::container_of!(relay, Seat, relay);
263
264 let active_keyboard = ffi::river_wlr_seat_get_keyboard((*seat).wlr_seat);
265 ffi::wlr_input_method_keyboard_grab_v2_set_keyboard(keyboard_grab, active_keyboard);
266
267 connect_listener(
268 ffi::river_wlr_input_method_keyboard_grab_v2_get_destroy_signal(keyboard_grab),
269 &mut (*relay).grab_keyboard_destroy,
270 handle_input_method_grab_keyboard_destroy,
271 );
272 }
273
274 unsafe extern "C" fn handle_input_method_new_popup(
275 listener: *mut ffi::wl_listener,
276 data: *mut std::ffi::c_void,
277 ) {
278 let relay = crate::container_of!(listener, InputRelay, input_method_new_popup);
279 let wlr_popup = data as *mut ffi::wlr_input_popup_surface_v2;
280
281 if let Err(e) = crate::input_popup::InputPopup::create(wlr_popup, relay) {
282 log::error!("failed to create input popup: {}", e);
283 }
284 }
285
286 unsafe extern "C" fn handle_input_method_grab_keyboard_destroy(
287 listener: *mut ffi::wl_listener,
288 _data: *mut std::ffi::c_void,
289 ) {
290 let relay = crate::container_of!(listener, InputRelay, grab_keyboard_destroy);
291 let input_method = (*relay).input_method;
292 let keyboard_grab = (*input_method).keyboard_grab;
293 wl_listener_remove_safe(&mut (*relay).grab_keyboard_destroy);
294
295 let keyboard = ffi::river_wlr_input_method_keyboard_grab_v2_get_keyboard(keyboard_grab);
296 if !keyboard.is_null() {
297 let modifiers = ffi::river_wlr_keyboard_get_modifiers(keyboard);
298 ffi::wlr_seat_keyboard_notify_modifiers((*input_method).seat, modifiers);
299 }
300 }