git.lucas.co / cce-compositor
Wayland compositor (wlroots)
git clone https://git.lucas.co/cce-compositor.git

src/server/xkb_keyboard.rs (16K)

  1 // SPDX-FileCopyrightText: © 2026 The River Developers
  2 // SPDX-License-Identifier: GPL-3.0-only
  3 
  4 use crate::ffi;
  5 use crate::input_device::InputDevice;
  6 use crate::server::WlList;
  7 
  8 pub struct XkbKeyboardState {
  9     pub layout_index: Option<u32>,
 10     pub layout_name: Option<*const std::os::raw::c_char>,
 11     pub capslock: Option<bool>,
 12     pub numlock: Option<bool>,
 13 }
 14 
 15 pub struct XkbKeyboard {
 16     pub parent_device: *mut InputDevice,
 17     pub objects: ffi::wl_list, // list of XkbKeyboardObject
 18     pub link: ffi::wl_list,    // link inside XkbConfig::keyboards
 19     pub sent: XkbKeyboardState,
 20 }
 21 
 22 pub struct XkbKeyboardObject {
 23     pub keyboard: *mut XkbKeyboard,
 24     pub resource: *mut ffi::wl_resource,
 25     pub link: ffi::wl_list,
 26 }
 27 
 28 impl XkbKeyboard {
 29     pub unsafe fn init(
 30         parent_device: *mut InputDevice,
 31     ) -> Box<Self> {
 32         let mut xkb_kbd = Box::new(Self {
 33             parent_device,
 34             objects: std::mem::zeroed(),
 35             link: std::mem::zeroed(),
 36             sent: XkbKeyboardState {
 37                 layout_index: None,
 38                 layout_name: None,
 39                 capslock: None,
 40                 numlock: None,
 41             },
 42         });
 43 
 44         ffi::wl_list_init(&mut xkb_kbd.objects);
 45         ffi::wl_list_init(&mut xkb_kbd.link);
 46 
 47         let server = (*(*parent_device).seat).server;
 48         let keyboards_head = &mut (*server).xkb_config.keyboards as *mut ffi::wl_list as *mut WlList;
 49         crate::server::wl_list_insert((*keyboards_head).prev, &mut xkb_kbd.link as *mut ffi::wl_list as *mut WlList);
 50 
 51         let config_objects = &mut (*server).xkb_config.objects as *mut ffi::wl_list as *mut WlList;
 52         let mut curr = (*config_objects).next;
 53         while curr != config_objects {
 54             let next = (*curr).next;
 55             let config_obj = crate::container_of!(curr, crate::xkb_config::XkbConfigObject, link);
 56             xkb_kbd.create_object((*config_obj).resource);
 57             curr = next;
 58         }
 59 
 60         xkb_kbd
 61     }
 62 
 63     pub unsafe fn deinit(&mut self) {
 64         let objects_head = &mut self.objects as *mut ffi::wl_list as *mut WlList;
 65         let mut curr = (*objects_head).next;
 66         while curr != objects_head {
 67             let next = (*curr).next;
 68             let obj = crate::container_of!(curr, XkbKeyboardObject, link);
 69 
 70             crate::server::wl_list_remove(curr);
 71             ffi::wl_list_init(curr as *mut ffi::wl_list);
 72 
 73             ffi::wl_resource_post_event((*obj).resource, 0); // removed event
 74 
 75             ffi::wl_resource_set_implementation(
 76                 (*obj).resource,
 77                 &XKB_KEYBOARD_INERT_INTERFACE as *const _ as *const _,
 78                 obj as *mut _,
 79                 Some(handle_xkb_keyboard_object_destroy),
 80             );
 81 
 82             curr = next;
 83         }
 84 
 85         crate::server::wl_list_remove(&mut self.link as *mut ffi::wl_list as *mut WlList);
 86     }
 87 
 88     pub unsafe fn create_object(&mut self, config_v1_resource: *mut ffi::wl_resource) {
 89         let client = ffi::wl_resource_get_client(config_v1_resource);
 90         let version = ffi::wl_resource_get_version(config_v1_resource);
 91 
 92         let resource = ffi::wl_resource_create(
 93             client,
 94             &ffi::river_xkb_keyboard_v1_interface,
 95             version,
 96             0,
 97         );
 98         if resource.is_null() {
 99             log::error!("out of memory creating river_xkb_keyboard_v1");
100             ffi::wl_client_post_no_memory(client);
101             return;
102         }
103 
104         let obj = Box::into_raw(Box::new(XkbKeyboardObject {
105             keyboard: self,
106             resource,
107             link: std::mem::zeroed(),
108         }));
109 
110         ffi::wl_resource_set_implementation(
111             resource,
112             &XKB_KEYBOARD_INTERFACE as *const _ as *const _,
113             obj as *mut _,
114             Some(handle_xkb_keyboard_object_destroy),
115         );
116 
117         let list_head = &mut self.objects as *mut ffi::wl_list as *mut WlList;
118         crate::server::wl_list_insert(list_head, &mut (*obj).link as *mut ffi::wl_list as *mut WlList);
119 
120         // Send xkb_keyboard event to config_v1
121         ffi::wl_resource_post_event(config_v1_resource, 1, resource); // opcode 1: xkb_keyboard
122 
123         // Pair with input device resource
124         let parent_dev = self.parent_device;
125         let input_dev_objects = &mut (*parent_dev).objects as *mut ffi::wl_list as *mut WlList;
126         let mut curr = (*input_dev_objects).next;
127         while curr != input_dev_objects {
128             let next = (*curr).next;
129             let input_dev_obj = crate::container_of!(curr, crate::input_device::InputDeviceObject, link);
130             if ffi::wl_resource_get_client((*input_dev_obj).resource) == client {
131                 ffi::wl_resource_post_event(resource, 1, (*input_dev_obj).resource); // input_device event (opcode 1)
132             }
133             curr = next;
134         }
135 
136         // Send current cached state to client
137         let sent = &self.sent;
138         if let Some(layout_index) = sent.layout_index {
139             let name_ptr = sent.layout_name.unwrap_or(std::ptr::null());
140             ffi::wl_resource_post_event(resource, 2, layout_index, name_ptr); // layout event (opcode 2)
141         }
142         if let Some(capslock) = sent.capslock {
143             if capslock {
144                 ffi::wl_resource_post_event(resource, 3); // capslock_enabled (opcode 3)
145             } else {
146                 ffi::wl_resource_post_event(resource, 4); // capslock_disabled (opcode 4)
147             }
148         }
149         if let Some(numlock) = sent.numlock {
150             if numlock {
151                 ffi::wl_resource_post_event(resource, 5); // numlock_enabled (opcode 5)
152             } else {
153                 ffi::wl_resource_post_event(resource, 6); // numlock_disabled (opcode 6)
154             }
155         }
156 
157         if version >= 2 {
158             ffi::wl_resource_post_event(resource, 7); // done event (opcode 7)
159         }
160     }
161 
162     pub unsafe fn send_state(
163         &mut self,
164         layout_index: u32,
165         layout_name: *const std::os::raw::c_char,
166         capslock: bool,
167         numlock: bool,
168     ) {
169         let sent = &mut self.sent;
170         let objects_head = &mut self.objects as *mut ffi::wl_list as *mut WlList;
171         let mut curr = (*objects_head).next;
172         while curr != objects_head {
173             let next = (*curr).next;
174             let obj = crate::container_of!(curr, XkbKeyboardObject, link);
175             let resource = (*obj).resource;
176             let version = ffi::wl_resource_get_version(resource);
177 
178             let mut send_done = false;
179 
180             // Check layout changed
181             let layout_changed = match sent.layout_index {
182                 None => true,
183                 Some(idx) => idx != layout_index,
184             } || match (sent.layout_name, layout_name.is_null()) {
185                 (None, false) => true,
186                 (Some(_), true) => true,
187                 (Some(old_ptr), false) => {
188                     libc::strcmp(old_ptr, layout_name) != 0
189                 }
190                 (None, true) => false,
191             };
192 
193             if layout_changed {
194                 ffi::wl_resource_post_event(resource, 2, layout_index, layout_name); // layout event (opcode 2)
195                 send_done = true;
196             }
197 
198             // Check capslock changed
199             let capslock_changed = match sent.capslock {
200                 None => true,
201                 Some(state) => state != capslock,
202             };
203             if capslock_changed {
204                 if capslock {
205                     ffi::wl_resource_post_event(resource, 3); // capslock_enabled
206                 } else {
207                     ffi::wl_resource_post_event(resource, 4); // capslock_disabled
208                 }
209                 send_done = true;
210             }
211 
212             // Check numlock changed
213             let numlock_changed = match sent.numlock {
214                 None => true,
215                 Some(state) => state != numlock,
216             };
217             if numlock_changed {
218                 if numlock {
219                     ffi::wl_resource_post_event(resource, 5); // numlock_enabled
220                 } else {
221                     ffi::wl_resource_post_event(resource, 6); // numlock_disabled
222                 }
223                 send_done = true;
224             }
225 
226             if send_done && version >= 2 {
227                 ffi::wl_resource_post_event(resource, 7); // done event
228             }
229 
230             curr = next;
231         }
232 
233         sent.layout_index = Some(layout_index);
234         sent.layout_name = if layout_name.is_null() { None } else { Some(layout_name) };
235         sent.capslock = Some(capslock);
236         sent.numlock = Some(numlock);
237     }
238 }
239 
240 unsafe extern "C" fn handle_xkb_keyboard_object_destroy(resource: *mut ffi::wl_resource) {
241     let obj = ffi::wl_resource_get_user_data(resource) as *mut XkbKeyboardObject;
242     if !obj.is_null() {
243         crate::server::wl_list_remove(&mut (*obj).link as *mut ffi::wl_list as *mut WlList);
244         let _ = Box::from_raw(obj);
245     }
246 }
247 
248 static XKB_KEYBOARD_INTERFACE: ffi::river_xkb_keyboard_v1_interface = ffi::river_xkb_keyboard_v1_interface {
249     destroy: Some(xkb_keyboard_destroy),
250     set_keymap: Some(xkb_keyboard_set_keymap),
251     set_layout_by_index: Some(xkb_keyboard_set_layout_by_index),
252     set_layout_by_name: Some(xkb_keyboard_set_layout_by_name),
253     capslock_enable: Some(xkb_keyboard_capslock_enable),
254     capslock_disable: Some(xkb_keyboard_capslock_disable),
255     numlock_enable: Some(xkb_keyboard_numlock_enable),
256     numlock_disable: Some(xkb_keyboard_numlock_disable),
257 };
258 
259 static XKB_KEYBOARD_INERT_INTERFACE: ffi::river_xkb_keyboard_v1_interface = ffi::river_xkb_keyboard_v1_interface {
260     destroy: Some(xkb_keyboard_destroy),
261     set_keymap: None,
262     set_layout_by_index: None,
263     set_layout_by_name: None,
264     capslock_enable: None,
265     capslock_disable: None,
266     numlock_enable: None,
267     numlock_disable: None,
268 };
269 
270 unsafe extern "C" fn xkb_keyboard_destroy(
271     _client: *mut ffi::wl_client,
272     resource: *mut ffi::wl_resource,
273 ) {
274     ffi::wl_resource_destroy(resource);
275 }
276 
277 unsafe extern "C" fn xkb_keyboard_set_keymap(
278     _client: *mut ffi::wl_client,
279     resource: *mut ffi::wl_resource,
280     keymap_resource: *mut ffi::wl_resource,
281 ) {
282     let obj = ffi::wl_resource_get_user_data(resource) as *mut XkbKeyboardObject;
283     if obj.is_null() {
284         return;
285     }
286     let keyboard = (*(*obj).keyboard).parent_device;
287     let keymap_struct = ffi::wl_resource_get_user_data(keymap_resource) as *mut crate::xkb_config::XkbKeymap;
288     if keymap_struct.is_null() {
289         ffi::wl_resource_post_error(
290             resource,
291             ffi::river_xkb_keyboard_v1_error_RIVER_XKB_KEYBOARD_V1_ERROR_INVALID_KEYMAP,
292             b"client set invalid keymap\0".as_ptr() as *const _,
293         );
294         return;
295     }
296 
297     let kbd = (*keyboard).destroy_data as *mut crate::keyboard::Keyboard;
298     if !kbd.is_null() {
299         (*kbd).set_keymap((*keymap_struct).xkb_keymap);
300     }
301 }
302 
303 unsafe extern "C" fn xkb_keyboard_set_layout_by_index(
304     _client: *mut ffi::wl_client,
305     resource: *mut ffi::wl_resource,
306     index: i32,
307 ) {
308     let obj = ffi::wl_resource_get_user_data(resource) as *mut XkbKeyboardObject;
309     if obj.is_null() {
310         return;
311     }
312     let keyboard = (*(*obj).keyboard).parent_device;
313     let kbd = (*keyboard).destroy_data as *mut crate::keyboard::Keyboard;
314     if kbd.is_null() {
315         return;
316     }
317     let group = (*kbd).group;
318     if group.is_null() {
319         return;
320     }
321 
322     let keymap = (*group).config.keymap;
323     if keymap.is_null() {
324         return;
325     }
326 
327     let num_layouts = ffi::xkb_keymap_num_layouts(keymap);
328     if index < 0 || (index as u32) >= num_layouts {
329         return;
330     }
331 
332     let mut modifiers = (*group).wlr_keyboard.modifiers;
333     modifiers.group = index as u32;
334     (*group).process_modifiers(modifiers);
335 }
336 
337 unsafe extern "C" fn xkb_keyboard_set_layout_by_name(
338     _client: *mut ffi::wl_client,
339     resource: *mut ffi::wl_resource,
340     name: *const std::os::raw::c_char,
341 ) {
342     let obj = ffi::wl_resource_get_user_data(resource) as *mut XkbKeyboardObject;
343     if obj.is_null() || name.is_null() {
344         return;
345     }
346     let keyboard = (*(*obj).keyboard).parent_device;
347     let kbd = (*keyboard).destroy_data as *mut crate::keyboard::Keyboard;
348     if kbd.is_null() {
349         return;
350     }
351     let group = (*kbd).group;
352     if group.is_null() {
353         return;
354     }
355 
356     let keymap = (*group).config.keymap;
357     if keymap.is_null() {
358         return;
359     }
360 
361     let index = ffi::xkb_keymap_layout_get_index(keymap, name);
362     if index == ffi::XKB_LAYOUT_INVALID {
363         return;
364     }
365 
366     let mut modifiers = (*group).wlr_keyboard.modifiers;
367     modifiers.group = index;
368     (*group).process_modifiers(modifiers);
369 }
370 
371 unsafe extern "C" fn xkb_keyboard_capslock_enable(
372     _client: *mut ffi::wl_client,
373     resource: *mut ffi::wl_resource,
374 ) {
375     let obj = ffi::wl_resource_get_user_data(resource) as *mut XkbKeyboardObject;
376     if obj.is_null() {
377         return;
378     }
379     let keyboard = (*(*obj).keyboard).parent_device;
380     let kbd = (*keyboard).destroy_data as *mut crate::keyboard::Keyboard;
381     if kbd.is_null() {
382         return;
383     }
384     let group = (*kbd).group;
385     if group.is_null() {
386         return;
387     }
388 
389     let keymap = (*group).config.keymap;
390     if keymap.is_null() {
391         return;
392     }
393 
394     let caps_idx = ffi::xkb_keymap_mod_get_index(keymap, b"Caps Lock\0".as_ptr() as *const _);
395     if caps_idx != ffi::XKB_MOD_INVALID {
396         let mask = 1 << caps_idx;
397         let mut modifiers = (*group).wlr_keyboard.modifiers;
398         modifiers.locked |= mask;
399         (*group).process_modifiers(modifiers);
400     }
401 }
402 
403 unsafe extern "C" fn xkb_keyboard_capslock_disable(
404     _client: *mut ffi::wl_client,
405     resource: *mut ffi::wl_resource,
406 ) {
407     let obj = ffi::wl_resource_get_user_data(resource) as *mut XkbKeyboardObject;
408     if obj.is_null() {
409         return;
410     }
411     let keyboard = (*(*obj).keyboard).parent_device;
412     let kbd = (*keyboard).destroy_data as *mut crate::keyboard::Keyboard;
413     if kbd.is_null() {
414         return;
415     }
416     let group = (*kbd).group;
417     if group.is_null() {
418         return;
419     }
420 
421     let keymap = (*group).config.keymap;
422     if keymap.is_null() {
423         return;
424     }
425 
426     let caps_idx = ffi::xkb_keymap_mod_get_index(keymap, b"Caps Lock\0".as_ptr() as *const _);
427     if caps_idx != ffi::XKB_MOD_INVALID {
428         let mask = 1 << caps_idx;
429         let mut modifiers = (*group).wlr_keyboard.modifiers;
430         modifiers.locked &= !mask;
431         (*group).process_modifiers(modifiers);
432     }
433 }
434 
435 unsafe extern "C" fn xkb_keyboard_numlock_enable(
436     _client: *mut ffi::wl_client,
437     resource: *mut ffi::wl_resource,
438 ) {
439     let obj = ffi::wl_resource_get_user_data(resource) as *mut XkbKeyboardObject;
440     if obj.is_null() {
441         return;
442     }
443     let keyboard = (*(*obj).keyboard).parent_device;
444     let kbd = (*keyboard).destroy_data as *mut crate::keyboard::Keyboard;
445     if kbd.is_null() {
446         return;
447     }
448     let group = (*kbd).group;
449     if group.is_null() {
450         return;
451     }
452 
453     let keymap = (*group).config.keymap;
454     if keymap.is_null() {
455         return;
456     }
457 
458     let num_idx = ffi::xkb_keymap_mod_get_index(keymap, b"Num Lock\0".as_ptr() as *const _);
459     if num_idx != ffi::XKB_MOD_INVALID {
460         let mask = 1 << num_idx;
461         let mut modifiers = (*group).wlr_keyboard.modifiers;
462         modifiers.locked |= mask;
463         (*group).process_modifiers(modifiers);
464     }
465 }
466 
467 unsafe extern "C" fn xkb_keyboard_numlock_disable(
468     _client: *mut ffi::wl_client,
469     resource: *mut ffi::wl_resource,
470 ) {
471     let obj = ffi::wl_resource_get_user_data(resource) as *mut XkbKeyboardObject;
472     if obj.is_null() {
473         return;
474     }
475     let keyboard = (*(*obj).keyboard).parent_device;
476     let kbd = (*keyboard).destroy_data as *mut crate::keyboard::Keyboard;
477     if kbd.is_null() {
478         return;
479     }
480     let group = (*kbd).group;
481     if group.is_null() {
482         return;
483     }
484 
485     let keymap = (*group).config.keymap;
486     if keymap.is_null() {
487         return;
488     }
489 
490     let num_idx = ffi::xkb_keymap_mod_get_index(keymap, b"Num Lock\0".as_ptr() as *const _);
491     if num_idx != ffi::XKB_MOD_INVALID {
492         let mask = 1 << num_idx;
493         let mut modifiers = (*group).wlr_keyboard.modifiers;
494         modifiers.locked &= !mask;
495         (*group).process_modifiers(modifiers);
496     }
497 }