git.lucas.co / cce-compositor
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 }