Wayland compositor (wlroots)
git clone https://git.lucas.co/cce-compositor.git
src/server/xkb_bindings.rs (18.1K)
1 // SPDX-FileCopyrightText: © 2026 The River Developers
2 // SPDX-License-Identifier: GPL-3.0-only
3
4 use crate::ffi;
5 use crate::seat::Seat;
6 use crate::server::Server;
7
8 #[derive(Clone, Copy, PartialEq, Eq, Debug)]
9 pub enum XkbBindingStateChange {
10 None,
11 Pressed,
12 StopRepeat,
13 Released,
14 }
15
16 #[derive(Clone, Copy, PartialEq, Eq, Debug)]
17 pub enum XkbBindingsSeatRequestedNextKeyChange {
18 None,
19 EnsureEaten,
20 CancelEnsureEaten,
21 }
22
23 #[derive(Clone, Copy, Debug)]
24 pub struct XkbBindingsSeatModsUpdate {
25 pub old: u32,
26 pub new: u32,
27 }
28
29 pub struct XkbBindingsSeat {
30 pub object: *mut ffi::wl_resource,
31 pub scheduled_ate_unbound_key: bool,
32 pub scheduled_mods_update: Option<XkbBindingsSeatModsUpdate>,
33 pub requested_next_key_change: XkbBindingsSeatRequestedNextKeyChange,
34 pub requested_mods_watched: u32,
35 pub ensure_next_key_eaten: bool,
36 }
37
38 impl Default for XkbBindingsSeat {
39 fn default() -> Self {
40 Self {
41 object: std::ptr::null_mut(),
42 scheduled_ate_unbound_key: false,
43 scheduled_mods_update: None,
44 requested_next_key_change: XkbBindingsSeatRequestedNextKeyChange::None,
45 requested_mods_watched: 0,
46 ensure_next_key_eaten: false,
47 }
48 }
49 }
50
51 impl XkbBindingsSeat {
52 pub unsafe fn create_object(&mut self, client: *mut ffi::wl_client, version: u32, id: u32) {
53 assert!(self.object.is_null());
54 let resource = ffi::wl_resource_create(client, &ffi::river_xkb_bindings_seat_v1_interface, version as i32, id);
55 if resource.is_null() {
56 ffi::wl_client_post_no_memory(client);
57 return;
58 }
59 self.object = resource;
60
61 ffi::wl_resource_set_implementation(
62 resource,
63 &XKB_BINDINGS_SEAT_INTERFACE as *const _ as *const _,
64 self as *mut XkbBindingsSeat as *mut _,
65 Some(handle_bindings_seat_resource_destroy),
66 );
67 }
68
69 pub unsafe fn make_inert(&mut self) {
70 if !self.object.is_null() {
71 ffi::wl_resource_set_implementation(
72 self.object,
73 &INERT_XKB_BINDINGS_SEAT_INTERFACE as *const _ as *const _,
74 std::ptr::null_mut(),
75 None,
76 );
77 self.object = std::ptr::null_mut();
78 self.requested_next_key_change = XkbBindingsSeatRequestedNextKeyChange::None;
79 self.requested_mods_watched = 0;
80 }
81 }
82
83 pub unsafe fn manage_start(&mut self) {
84 if self.scheduled_ate_unbound_key {
85 if !self.object.is_null() {
86 if ffi::wl_resource_get_version(self.object) >= 2 {
87 ffi::wl_resource_post_event(self.object, 0);
88 }
89 }
90 self.scheduled_ate_unbound_key = false;
91 }
92
93 if let Some(mods) = self.scheduled_mods_update {
94 if !self.object.is_null() {
95 if ffi::wl_resource_get_version(self.object) >= 3 {
96 ffi::wl_resource_post_event(self.object, 1, mods.old, mods.new);
97 }
98 }
99 self.scheduled_mods_update = None;
100 }
101 }
102
103 pub unsafe fn manage_finish(&mut self) {
104 match self.requested_next_key_change {
105 XkbBindingsSeatRequestedNextKeyChange::None => {},
106 XkbBindingsSeatRequestedNextKeyChange::EnsureEaten => {
107 self.ensure_next_key_eaten = true;
108 },
109 XkbBindingsSeatRequestedNextKeyChange::CancelEnsureEaten => {
110 self.ensure_next_key_eaten = false;
111 },
112 }
113 self.requested_next_key_change = XkbBindingsSeatRequestedNextKeyChange::None;
114 }
115 }
116
117 unsafe extern "C" fn handle_bindings_seat_resource_destroy(resource: *mut ffi::wl_resource) {
118 let seat = ffi::wl_resource_get_user_data(resource) as *mut XkbBindingsSeat;
119 if !seat.is_null() {
120 (*seat).object = std::ptr::null_mut();
121 (*seat).requested_next_key_change = XkbBindingsSeatRequestedNextKeyChange::None;
122 (*seat).requested_mods_watched = 0;
123 }
124 }
125
126 static XKB_BINDINGS_SEAT_INTERFACE: ffi::river_xkb_bindings_seat_v1_interface = ffi::river_xkb_bindings_seat_v1_interface {
127 destroy: Some(bindings_seat_destroy),
128 ensure_next_key_eaten: Some(bindings_seat_ensure_next_key_eaten),
129 cancel_ensure_next_key_eaten: Some(bindings_seat_cancel_ensure_next_key_eaten),
130 modifiers_watch: Some(bindings_seat_modifiers_watch),
131 };
132
133 unsafe extern "C" fn bindings_seat_inert_ensure_next_key_eaten(
134 _client: *mut ffi::wl_client,
135 _resource: *mut ffi::wl_resource,
136 ) {}
137
138 unsafe extern "C" fn bindings_seat_inert_cancel_ensure_next_key_eaten(
139 _client: *mut ffi::wl_client,
140 _resource: *mut ffi::wl_resource,
141 ) {}
142
143 unsafe extern "C" fn bindings_seat_inert_modifiers_watch(
144 _client: *mut ffi::wl_client,
145 _resource: *mut ffi::wl_resource,
146 _modifiers: u32,
147 ) {}
148
149 static INERT_XKB_BINDINGS_SEAT_INTERFACE: ffi::river_xkb_bindings_seat_v1_interface = ffi::river_xkb_bindings_seat_v1_interface {
150 destroy: Some(bindings_seat_destroy),
151 ensure_next_key_eaten: Some(bindings_seat_inert_ensure_next_key_eaten),
152 cancel_ensure_next_key_eaten: Some(bindings_seat_inert_cancel_ensure_next_key_eaten),
153 modifiers_watch: Some(bindings_seat_inert_modifiers_watch),
154 };
155
156 unsafe extern "C" fn bindings_seat_destroy(
157 _client: *mut ffi::wl_client,
158 resource: *mut ffi::wl_resource,
159 ) {
160 ffi::wl_resource_destroy(resource);
161 }
162
163 unsafe extern "C" fn bindings_seat_ensure_next_key_eaten(
164 _client: *mut ffi::wl_client,
165 resource: *mut ffi::wl_resource,
166 ) {
167 let bindings_seat = ffi::wl_resource_get_user_data(resource) as *mut XkbBindingsSeat;
168 if bindings_seat.is_null() {
169 return;
170 }
171 let seat = crate::container_of!(bindings_seat, Seat, xkb_bindings_seat);
172 if !(*(*seat).server).wm.ensure_windowing() {
173 return;
174 }
175 (*bindings_seat).requested_next_key_change = XkbBindingsSeatRequestedNextKeyChange::EnsureEaten;
176 }
177
178 unsafe extern "C" fn bindings_seat_cancel_ensure_next_key_eaten(
179 _client: *mut ffi::wl_client,
180 resource: *mut ffi::wl_resource,
181 ) {
182 let bindings_seat = ffi::wl_resource_get_user_data(resource) as *mut XkbBindingsSeat;
183 if bindings_seat.is_null() {
184 return;
185 }
186 let seat = crate::container_of!(bindings_seat, Seat, xkb_bindings_seat);
187 if !(*(*seat).server).wm.ensure_windowing() {
188 return;
189 }
190 (*bindings_seat).requested_next_key_change = XkbBindingsSeatRequestedNextKeyChange::CancelEnsureEaten;
191 }
192
193 unsafe extern "C" fn bindings_seat_modifiers_watch(
194 _client: *mut ffi::wl_client,
195 resource: *mut ffi::wl_resource,
196 modifiers: u32,
197 ) {
198 let bindings_seat = ffi::wl_resource_get_user_data(resource) as *mut XkbBindingsSeat;
199 if bindings_seat.is_null() {
200 return;
201 }
202 let seat = crate::container_of!(bindings_seat, Seat, xkb_bindings_seat);
203 if !(*(*seat).server).wm.ensure_windowing() {
204 return;
205 }
206 (*bindings_seat).requested_mods_watched = modifiers;
207 }
208
209 pub struct XkbBindings {
210 pub global: *mut ffi::wl_global,
211 pub server: *mut Server,
212 }
213
214 impl XkbBindings {
215 pub unsafe fn init(&mut self, server: *mut Server, wl_display: *mut ffi::wl_display) -> Result<(), ()> {
216 self.server = server;
217 self.global = ffi::wl_global_create(
218 wl_display,
219 &ffi::river_xkb_bindings_v1_interface,
220 3,
221 self as *mut XkbBindings as *mut _,
222 Some(bind_xkb_bindings),
223 );
224 if self.global.is_null() {
225 return Err(());
226 }
227 Ok(())
228 }
229
230 pub unsafe fn deinit(&mut self) {
231 if !self.global.is_null() {
232 ffi::wl_global_destroy(self.global);
233 self.global = std::ptr::null_mut();
234 }
235 }
236 }
237
238 unsafe extern "C" fn bind_xkb_bindings(
239 client: *mut ffi::wl_client,
240 data: *mut std::ffi::c_void,
241 version: u32,
242 id: u32,
243 ) {
244 let bindings = data as *mut XkbBindings;
245 let resource = ffi::wl_resource_create(client, &ffi::river_xkb_bindings_v1_interface, version as i32, id);
246 if resource.is_null() {
247 ffi::wl_client_post_no_memory(client);
248 return;
249 }
250
251 ffi::wl_resource_set_implementation(
252 resource,
253 &XKB_BINDINGS_INTERFACE as *const _ as *const _,
254 bindings as *mut _,
255 None,
256 );
257 }
258
259 static XKB_BINDINGS_INTERFACE: ffi::river_xkb_bindings_v1_interface = ffi::river_xkb_bindings_v1_interface {
260 destroy: Some(bindings_destroy_request),
261 get_xkb_binding: Some(bindings_get_xkb_binding),
262 get_seat: Some(bindings_get_seat),
263 };
264
265 unsafe extern "C" fn bindings_destroy_request(
266 _client: *mut ffi::wl_client,
267 resource: *mut ffi::wl_resource,
268 ) {
269 ffi::wl_resource_destroy(resource);
270 }
271
272 unsafe extern "C" fn bindings_get_xkb_binding(
273 client: *mut ffi::wl_client,
274 resource: *mut ffi::wl_resource,
275 seat_resource: *mut ffi::wl_resource,
276 id: u32,
277 keysym: u32,
278 modifiers: u32,
279 ) {
280 let seat = ffi::wl_resource_get_user_data(seat_resource) as *mut Seat;
281 if seat.is_null() {
282 return;
283 }
284
285 let version = ffi::wl_resource_get_version(resource) as u32;
286 if XkbBinding::create(seat, client, version, id, keysym, modifiers).is_err() {
287 log::error!("failed to create xkb binding");
288 }
289 }
290
291 unsafe extern "C" fn bindings_get_seat(
292 client: *mut ffi::wl_client,
293 resource: *mut ffi::wl_resource,
294 id: u32,
295 seat_resource: *mut ffi::wl_resource,
296 ) {
297 let seat = ffi::wl_resource_get_user_data(seat_resource) as *mut Seat;
298 if seat.is_null() {
299 return;
300 }
301
302 if !(*seat).xkb_bindings_seat.object.is_null() {
303 ffi::wl_resource_post_error(
304 resource,
305 ffi::river_xkb_bindings_v1_error_RIVER_XKB_BINDINGS_V1_ERROR_OBJECT_ALREADY_CREATED,
306 b"river_xkb_bindings_seat_v1 already created\0".as_ptr() as *const _,
307 );
308 return;
309 }
310
311 let version = ffi::wl_resource_get_version(resource) as u32;
312 (*seat).xkb_bindings_seat.create_object(client, version, id);
313 }
314
315 pub struct XkbBindingScheduled {
316 pub state_changes: Vec<XkbBindingStateChange>,
317 }
318
319 pub struct XkbBindingRequested {
320 pub enabled: bool,
321 pub layout: Option<u32>,
322 }
323
324 pub struct XkbBinding {
325 pub seat: *mut Seat,
326 pub object: *mut ffi::wl_resource,
327 pub keysym: u32,
328 pub modifiers: u32,
329 pub wm_scheduled: XkbBindingScheduled,
330 pub wm_requested: XkbBindingRequested,
331 pub sent_pressed: bool,
332 pub link: ffi::wl_list,
333 }
334
335 impl XkbBinding {
336 pub unsafe fn create(
337 seat: *mut Seat,
338 client: *mut ffi::wl_client,
339 version: u32,
340 id: u32,
341 keysym: u32,
342 modifiers: u32,
343 ) -> Result<(), ()> {
344 let binding_ptr = Box::into_raw(Box::new(Self {
345 seat,
346 object: std::ptr::null_mut(),
347 keysym,
348 modifiers,
349 wm_scheduled: XkbBindingScheduled {
350 state_changes: Vec::new(),
351 },
352 wm_requested: XkbBindingRequested {
353 enabled: false,
354 layout: None,
355 },
356 sent_pressed: false,
357 link: std::mem::zeroed(),
358 }));
359
360 let resource = ffi::wl_resource_create(client, &ffi::river_xkb_binding_v1_interface, version as i32, id);
361 if resource.is_null() {
362 let _ = Box::from_raw(binding_ptr);
363 return Err(());
364 }
365
366 (*binding_ptr).object = resource;
367 ffi::wl_resource_set_implementation(
368 resource,
369 &XKB_BINDING_INTERFACE as *const _ as *const _,
370 binding_ptr as *mut _,
371 Some(handle_binding_resource_destroy),
372 );
373
374 let bindings_list = &mut (*seat).xkb_bindings as *mut ffi::wl_list as *mut crate::server::WlList;
375 crate::server::wl_list_insert((*bindings_list).prev, &mut (*binding_ptr).link as *mut ffi::wl_list as *mut crate::server::WlList);
376
377 log::debug!("new river_xkb_binding_v1: keysym: {}, modifiers: {}", keysym, modifiers);
378 Ok(())
379 }
380
381 pub unsafe fn destroy(binding: *mut Self) {
382 ffi::wl_resource_set_implementation(
383 (*binding).object,
384 std::ptr::null(),
385 std::ptr::null_mut(),
386 None,
387 );
388 handle_binding_resource_destroy((*binding).object);
389 }
390
391 pub unsafe fn pressed(&mut self) {
392 if (*(*self.seat).server).wm.object.is_null() {
393 log::warn!("Pressed keybind while window manager is disconnected");
394 return;
395 }
396 self.wm_scheduled.state_changes.push(XkbBindingStateChange::Pressed);
397 (*(*self.seat).server).wm.dirty_windowing();
398 }
399
400 pub unsafe fn released(&mut self) {
401 self.wm_scheduled.state_changes.push(XkbBindingStateChange::Released);
402 (*(*self.seat).server).wm.dirty_windowing();
403 }
404
405 pub unsafe fn stop_repeat(&mut self) {
406 let already_releasing_or_stopping = self.wm_scheduled.state_changes.iter().any(|&s|
407 matches!(s, XkbBindingStateChange::Released | XkbBindingStateChange::StopRepeat)
408 );
409 if self.sent_pressed && !already_releasing_or_stopping {
410 self.wm_scheduled.state_changes.push(XkbBindingStateChange::StopRepeat);
411 (*(*self.seat).server).wm.dirty_windowing();
412 }
413 }
414
415 pub unsafe fn match_keycode(
416 &self,
417 keycode: u32,
418 modifiers: u32,
419 xkb_state: *mut ffi::xkb_state,
420 translate: bool,
421 ) -> bool {
422 if !self.wm_requested.enabled {
423 return false;
424 }
425
426 let keymap = ffi::xkb_state_get_keymap(xkb_state);
427 if keymap.is_null() {
428 return false;
429 }
430
431 let layout = match self.wm_requested.layout {
432 Some(l) => l,
433 None => ffi::xkb_state_key_get_layout(xkb_state, keycode),
434 };
435
436 if !translate {
437 let mut syms_ptr: *const ffi::xkb_keysym_t = std::ptr::null();
438 let num_syms = ffi::xkb_keymap_key_get_syms_by_level(keymap, keycode, layout, 0, &mut syms_ptr);
439 if num_syms <= 0 || syms_ptr.is_null() {
440 return false;
441 }
442 let syms = std::slice::from_raw_parts(syms_ptr, num_syms as usize);
443
444 if modifiers == self.modifiers {
445 for &sym in syms {
446 if sym == self.keysym {
447 return true;
448 }
449 }
450 }
451 } else {
452 let level = ffi::xkb_state_key_get_level(xkb_state, keycode, layout);
453 let mut syms_ptr: *const ffi::xkb_keysym_t = std::ptr::null();
454 let num_syms = ffi::xkb_keymap_key_get_syms_by_level(keymap, keycode, layout, level, &mut syms_ptr);
455 if num_syms <= 0 || syms_ptr.is_null() {
456 return false;
457 }
458 let syms = std::slice::from_raw_parts(syms_ptr, num_syms as usize);
459
460 let consumed = ffi::xkb_state_key_get_consumed_mods2(xkb_state, keycode, ffi::xkb_consumed_mode_XKB_CONSUMED_MODE_XKB);
461 let modifiers_translated = modifiers & !consumed;
462
463 if modifiers_translated == self.modifiers {
464 for &sym in syms {
465 if sym == self.keysym {
466 return true;
467 }
468 }
469 }
470 }
471
472 false
473 }
474 }
475
476 unsafe extern "C" fn handle_binding_resource_destroy(resource: *mut ffi::wl_resource) {
477 let binding = ffi::wl_resource_get_user_data(resource) as *mut XkbBinding;
478 if !binding.is_null() {
479 let seat = (*binding).seat;
480 if !seat.is_null() {
481 let seat_groups_head = &mut (*seat).keyboard_groups as *mut ffi::wl_list as *mut crate::server::WlList;
482 let mut curr_g = (*seat_groups_head).next;
483 while curr_g != seat_groups_head {
484 let next_g = (*curr_g).next;
485 let g = crate::container_of!(curr_g, crate::keyboard_group::KeyboardGroup, link);
486 for press in (*g).pressed.values_mut() {
487 if let crate::keyboard_group::KeyConsumer::Binding(b) = press.consumer {
488 if b == binding {
489 press.consumer = crate::keyboard_group::KeyConsumer::Binding(std::ptr::null_mut());
490 }
491 }
492 }
493 curr_g = next_g;
494 }
495 }
496 crate::server::wl_list_remove(&mut (*binding).link as *mut ffi::wl_list as *mut crate::server::WlList);
497 let _ = Box::from_raw(binding);
498 }
499 }
500
501 static XKB_BINDING_INTERFACE: ffi::river_xkb_binding_v1_interface = ffi::river_xkb_binding_v1_interface {
502 destroy: Some(binding_destroy),
503 set_layout_override: Some(binding_set_layout_override),
504 enable: Some(binding_enable),
505 disable: Some(binding_disable),
506 };
507
508 unsafe extern "C" fn binding_destroy(
509 _client: *mut ffi::wl_client,
510 resource: *mut ffi::wl_resource,
511 ) {
512 ffi::wl_resource_destroy(resource);
513 }
514
515 unsafe extern "C" fn binding_set_layout_override(
516 _client: *mut ffi::wl_client,
517 resource: *mut ffi::wl_resource,
518 layout: u32,
519 ) {
520 let binding = ffi::wl_resource_get_user_data(resource) as *mut XkbBinding;
521 if binding.is_null() {
522 return;
523 }
524 let server = (*(*binding).seat).server;
525 if !(*server).wm.ensure_windowing() {
526 return;
527 }
528 if layout == 0xffffffff {
529 (*binding).wm_requested.layout = None;
530 } else {
531 (*binding).wm_requested.layout = Some(layout);
532 }
533 }
534
535 unsafe extern "C" fn binding_enable(
536 _client: *mut ffi::wl_client,
537 resource: *mut ffi::wl_resource,
538 ) {
539 let binding = ffi::wl_resource_get_user_data(resource) as *mut XkbBinding;
540 if binding.is_null() {
541 return;
542 }
543 let server = (*(*binding).seat).server;
544 if !(*server).wm.ensure_windowing() {
545 return;
546 }
547 (*binding).wm_requested.enabled = true;
548 }
549
550 unsafe extern "C" fn binding_disable(
551 _client: *mut ffi::wl_client,
552 resource: *mut ffi::wl_resource,
553 ) {
554 let binding = ffi::wl_resource_get_user_data(resource) as *mut XkbBinding;
555 if binding.is_null() {
556 return;
557 }
558 let server = (*(*binding).seat).server;
559 if !(*server).wm.ensure_windowing() {
560 return;
561 }
562 (*binding).wm_requested.enabled = false;
563 }