Wayland compositor (wlroots)
git clone https://git.lucas.co/cce-compositor.git
Implement XKB Keyboard Status Broadcasting (river_xkb_keyboard_v1)
src/input_device.rs | 11 ++
src/keyboard_group.rs | 21 ++-
src/main.rs | 1 +
src/xkb_config.rs | 11 ++
src/xkb_keyboard.rs | 497 ++++++++++++++++++++++++++++++++++++++++++++++++++
5 files changed, 536 insertions(+), 5 deletions(-)
diff --git a/src/input_device.rs b/src/input_device.rs
index 1ca38f8..15c6c41 100644
--- a/src/input_device.rs
+++ b/src/input_device.rs
@@ -23,6 +23,7 @@ pub struct InputDevice {
pub objects: ffi::wl_list, // list of InputDeviceObject
pub link: ffi::wl_list, // link inside InputManager::devices
pub libinput: Option<Box<crate::libinput_device::LibinputDevice>>,
+ pub xkb_keyboard: Option<Box<crate::xkb_keyboard::XkbKeyboard>>,
}
pub struct InputDeviceObject {
@@ -54,6 +55,7 @@ impl InputDevice {
objects: std::mem::zeroed(),
link: std::mem::zeroed(),
libinput: None,
+ xkb_keyboard: None,
}));
ffi::wl_list_init(&mut (*device).objects);
@@ -85,6 +87,11 @@ impl InputDevice {
if !handle.is_null() {
(*device).libinput = Some(crate::libinput_device::LibinputDevice::init(device, handle));
}
+
+ let dev_type = ffi::river_wlr_input_device_get_type(wlr_device);
+ if dev_type == ffi::wlr_input_device_type_WLR_INPUT_DEVICE_KEYBOARD {
+ (*device).xkb_keyboard = Some(crate::xkb_keyboard::XkbKeyboard::init(device));
+ }
}
// Output mapping logic for pointers and touch screens (as done in Zig version)
@@ -272,6 +279,10 @@ unsafe extern "C" fn handle_device_destroy(listener: *mut ffi::wl_listener, _dat
libinput.deinit();
}
+ if let Some(mut xkb_kbd) = device.xkb_keyboard.take() {
+ xkb_kbd.deinit();
+ }
+
// Call custom destroy callback if set (e.g. to clean up Tablet/Keyboard wrappers)
if let Some(destroy_fn) = device.destroy_fn {
destroy_fn(device.destroy_data);
diff --git a/src/keyboard_group.rs b/src/keyboard_group.rs
index 7d885e7..b92621d 100644
--- a/src/keyboard_group.rs
+++ b/src/keyboard_group.rs
@@ -243,24 +243,35 @@ impl KeyboardGroup {
return;
}
let layout_index = self.wlr_keyboard.modifiers.group;
- let _layout_name = ffi::xkb_keymap_layout_get_name(keymap, layout_index);
+ let layout_name = ffi::xkb_keymap_layout_get_name(keymap, layout_index);
let caps_idx = ffi::xkb_keymap_mod_get_index(keymap, b"Caps Lock\0".as_ptr() as *const _);
- let _capslock = if caps_idx != ffi::XKB_MOD_INVALID {
+ let capslock = if caps_idx != ffi::XKB_MOD_INVALID {
let caps_mask = 1 << caps_idx;
(self.wlr_keyboard.modifiers.locked & caps_mask) != 0
} else {
false
};
let num_idx = ffi::xkb_keymap_mod_get_index(keymap, b"Num Lock\0".as_ptr() as *const _);
- let _numlock = if num_idx != ffi::XKB_MOD_INVALID {
+ let numlock = if num_idx != ffi::XKB_MOD_INVALID {
let num_mask = 1 << num_idx;
(self.wlr_keyboard.modifiers.locked & num_mask) != 0
} else {
false
};
- // Note: Layout configurations broadcasting (river_xkb_keyboard_v1) is stubbed out
- // in xkb_config.rs and will be fully ported in a later phase.
+ let server = (*self.seat).server;
+ let keyboards_head = &mut (*server).xkb_config.keyboards as *mut ffi::wl_list as *mut crate::server::WlList;
+ let mut curr = (*keyboards_head).next;
+ while curr != keyboards_head {
+ let next = (*curr).next;
+ let xkb_kbd = crate::container_of!(curr, crate::xkb_keyboard::XkbKeyboard, link);
+ let parent_dev = (*xkb_kbd).parent_device;
+ let kbd = (*parent_dev).destroy_data as *mut crate::keyboard::Keyboard;
+ if !kbd.is_null() && (*kbd).group == self as *mut KeyboardGroup {
+ (*xkb_kbd).send_state(layout_index, layout_name, capslock, numlock);
+ }
+ curr = next;
+ }
}
}
diff --git a/src/main.rs b/src/main.rs
index 58be2a4..5acbd4d 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -17,6 +17,7 @@ mod input_manager;
mod libinput_config;
pub mod libinput_device;
pub mod libinput_accel_config;
+pub mod xkb_keyboard;
mod xkb_config;
mod idle_inhibit_manager;
mod lock_manager;
diff --git a/src/xkb_config.rs b/src/xkb_config.rs
index 49d940d..f814215 100644
--- a/src/xkb_config.rs
+++ b/src/xkb_config.rs
@@ -10,6 +10,7 @@ pub struct XkbConfig {
pub context: *mut ffi::xkb_context,
pub default_keymap: *mut ffi::xkb_keymap,
pub objects: ffi::wl_list,
+ pub keyboards: ffi::wl_list,
}
pub struct XkbConfigObject {
@@ -28,6 +29,7 @@ impl XkbConfig {
pub unsafe fn init(&mut self, server: *mut Server) -> Result<(), &'static str> {
self.server = server;
ffi::wl_list_init(&mut self.objects);
+ ffi::wl_list_init(&mut self.keyboards);
let context = ffi::xkb_context_new(ffi::xkb_context_flags_XKB_CONTEXT_NO_FLAGS);
if context.is_null() {
@@ -119,6 +121,15 @@ unsafe extern "C" fn bind_xkb_config(
let list_head = &mut (*config).objects as *mut ffi::wl_list as *mut WlList;
wl_list_insert((*list_head).prev, &mut (*obj).link as *mut ffi::wl_list as *mut WlList);
+
+ let keyboards_head = &mut (*config).keyboards as *mut ffi::wl_list as *mut WlList;
+ let mut curr = (*keyboards_head).next;
+ while curr != keyboards_head {
+ let next = (*curr).next;
+ let xkb_kbd = crate::container_of!(curr, crate::xkb_keyboard::XkbKeyboard, link);
+ (*xkb_kbd).create_object(resource);
+ curr = next;
+ }
}
unsafe extern "C" fn handle_object_destroy(resource: *mut ffi::wl_resource) {
diff --git a/src/xkb_keyboard.rs b/src/xkb_keyboard.rs
new file mode 100644
index 0000000..682df0f
--- /dev/null
+++ b/src/xkb_keyboard.rs
@@ -0,0 +1,497 @@
+// SPDX-FileCopyrightText: © 2026 The River Developers
+// SPDX-License-Identifier: GPL-3.0-only
+
+use crate::ffi;
+use crate::input_device::InputDevice;
+use crate::server::WlList;
+
+pub struct XkbKeyboardState {
+ pub layout_index: Option<u32>,
+ pub layout_name: Option<*const std::os::raw::c_char>,
+ pub capslock: Option<bool>,
+ pub numlock: Option<bool>,
+}
+
+pub struct XkbKeyboard {
+ pub parent_device: *mut InputDevice,
+ pub objects: ffi::wl_list, // list of XkbKeyboardObject
+ pub link: ffi::wl_list, // link inside XkbConfig::keyboards
+ pub sent: XkbKeyboardState,
+}
+
+pub struct XkbKeyboardObject {
+ pub keyboard: *mut XkbKeyboard,
+ pub resource: *mut ffi::wl_resource,
+ pub link: ffi::wl_list,
+}
+
+impl XkbKeyboard {
+ pub unsafe fn init(
+ parent_device: *mut InputDevice,
+ ) -> Box<Self> {
+ let mut xkb_kbd = Box::new(Self {
+ parent_device,
+ objects: std::mem::zeroed(),
+ link: std::mem::zeroed(),
+ sent: XkbKeyboardState {
+ layout_index: None,
+ layout_name: None,
+ capslock: None,
+ numlock: None,
+ },
+ });
+
+ ffi::wl_list_init(&mut xkb_kbd.objects);
+ ffi::wl_list_init(&mut xkb_kbd.link);
+
+ let server = (*(*parent_device).seat).server;
+ let keyboards_head = &mut (*server).xkb_config.keyboards as *mut ffi::wl_list as *mut WlList;
+ crate::server::wl_list_insert((*keyboards_head).prev, &mut xkb_kbd.link as *mut ffi::wl_list as *mut WlList);
+
+ let config_objects = &mut (*server).xkb_config.objects as *mut ffi::wl_list as *mut WlList;
+ let mut curr = (*config_objects).next;
+ while curr != config_objects {
+ let next = (*curr).next;
+ let config_obj = crate::container_of!(curr, crate::xkb_config::XkbConfigObject, link);
+ xkb_kbd.create_object((*config_obj).resource);
+ curr = next;
+ }
+
+ xkb_kbd
+ }
+
+ pub unsafe fn deinit(&mut self) {
+ let objects_head = &mut self.objects as *mut ffi::wl_list as *mut WlList;
+ let mut curr = (*objects_head).next;
+ while curr != objects_head {
+ let next = (*curr).next;
+ let obj = crate::container_of!(curr, XkbKeyboardObject, link);
+
+ crate::server::wl_list_remove(curr);
+ ffi::wl_list_init(curr as *mut ffi::wl_list);
+
+ ffi::wl_resource_post_event((*obj).resource, 0); // removed event
+
+ ffi::wl_resource_set_implementation(
+ (*obj).resource,
+ &XKB_KEYBOARD_INERT_INTERFACE as *const _ as *const _,
+ obj as *mut _,
+ Some(handle_xkb_keyboard_object_destroy),
+ );
+
+ curr = next;
+ }
+
+ crate::server::wl_list_remove(&mut self.link as *mut ffi::wl_list as *mut WlList);
+ }
+
+ pub unsafe fn create_object(&mut self, config_v1_resource: *mut ffi::wl_resource) {
+ let client = ffi::wl_resource_get_client(config_v1_resource);
+ let version = ffi::wl_resource_get_version(config_v1_resource);
+
+ let resource = ffi::wl_resource_create(
+ client,
+ &ffi::river_xkb_keyboard_v1_interface,
+ version,
+ 0,
+ );
+ if resource.is_null() {
+ log::error!("out of memory creating river_xkb_keyboard_v1");
+ ffi::wl_client_post_no_memory(client);
+ return;
+ }
+
+ let obj = Box::into_raw(Box::new(XkbKeyboardObject {
+ keyboard: self,
+ resource,
+ link: std::mem::zeroed(),
+ }));
+
+ ffi::wl_resource_set_implementation(
+ resource,
+ &XKB_KEYBOARD_INTERFACE as *const _ as *const _,
+ obj as *mut _,
+ Some(handle_xkb_keyboard_object_destroy),
+ );
+
+ let list_head = &mut self.objects as *mut ffi::wl_list as *mut WlList;
+ crate::server::wl_list_insert(list_head, &mut (*obj).link as *mut ffi::wl_list as *mut WlList);
+
+ // Send xkb_keyboard event to config_v1
+ ffi::wl_resource_post_event(config_v1_resource, 1, resource); // opcode 1: xkb_keyboard
+
+ // Pair with input device resource
+ let parent_dev = self.parent_device;
+ let input_dev_objects = &mut (*parent_dev).objects as *mut ffi::wl_list as *mut WlList;
+ let mut curr = (*input_dev_objects).next;
+ while curr != input_dev_objects {
+ let next = (*curr).next;
+ let input_dev_obj = crate::container_of!(curr, crate::input_device::InputDeviceObject, link);
+ if ffi::wl_resource_get_client((*input_dev_obj).resource) == client {
+ ffi::wl_resource_post_event(resource, 1, (*input_dev_obj).resource); // input_device event (opcode 1)
+ }
+ curr = next;
+ }
+
+ // Send current cached state to client
+ let sent = &self.sent;
+ if let Some(layout_index) = sent.layout_index {
+ let name_ptr = sent.layout_name.unwrap_or(std::ptr::null());
+ ffi::wl_resource_post_event(resource, 2, layout_index, name_ptr); // layout event (opcode 2)
+ }
+ if let Some(capslock) = sent.capslock {
+ if capslock {
+ ffi::wl_resource_post_event(resource, 3); // capslock_enabled (opcode 3)
+ } else {
+ ffi::wl_resource_post_event(resource, 4); // capslock_disabled (opcode 4)
+ }
+ }
+ if let Some(numlock) = sent.numlock {
+ if numlock {
+ ffi::wl_resource_post_event(resource, 5); // numlock_enabled (opcode 5)
+ } else {
+ ffi::wl_resource_post_event(resource, 6); // numlock_disabled (opcode 6)
+ }
+ }
+
+ if version >= 2 {
+ ffi::wl_resource_post_event(resource, 7); // done event (opcode 7)
+ }
+ }
+
+ pub unsafe fn send_state(
+ &mut self,
+ layout_index: u32,
+ layout_name: *const std::os::raw::c_char,
+ capslock: bool,
+ numlock: bool,
+ ) {
+ let sent = &mut self.sent;
+ let objects_head = &mut self.objects as *mut ffi::wl_list as *mut WlList;
+ let mut curr = (*objects_head).next;
+ while curr != objects_head {
+ let next = (*curr).next;
+ let obj = crate::container_of!(curr, XkbKeyboardObject, link);
+ let resource = (*obj).resource;
+ let version = ffi::wl_resource_get_version(resource);
+
+ let mut send_done = false;
+
+ // Check layout changed
+ let layout_changed = match sent.layout_index {
+ None => true,
+ Some(idx) => idx != layout_index,
+ } || match (sent.layout_name, layout_name.is_null()) {
+ (None, false) => true,
+ (Some(_), true) => true,
+ (Some(old_ptr), false) => {
+ libc::strcmp(old_ptr, layout_name) != 0
+ }
+ (None, true) => false,
+ };
+
+ if layout_changed {
+ ffi::wl_resource_post_event(resource, 2, layout_index, layout_name); // layout event (opcode 2)
+ send_done = true;
+ }
+
+ // Check capslock changed
+ let capslock_changed = match sent.capslock {
+ None => true,
+ Some(state) => state != capslock,
+ };
+ if capslock_changed {
+ if capslock {
+ ffi::wl_resource_post_event(resource, 3); // capslock_enabled
+ } else {
+ ffi::wl_resource_post_event(resource, 4); // capslock_disabled
+ }
+ send_done = true;
+ }
+
+ // Check numlock changed
+ let numlock_changed = match sent.numlock {
+ None => true,
+ Some(state) => state != numlock,
+ };
+ if numlock_changed {
+ if numlock {
+ ffi::wl_resource_post_event(resource, 5); // numlock_enabled
+ } else {
+ ffi::wl_resource_post_event(resource, 6); // numlock_disabled
+ }
+ send_done = true;
+ }
+
+ if send_done && version >= 2 {
+ ffi::wl_resource_post_event(resource, 7); // done event
+ }
+
+ curr = next;
+ }
+
+ sent.layout_index = Some(layout_index);
+ sent.layout_name = if layout_name.is_null() { None } else { Some(layout_name) };
+ sent.capslock = Some(capslock);
+ sent.numlock = Some(numlock);
+ }
+}
+
+unsafe extern "C" fn handle_xkb_keyboard_object_destroy(resource: *mut ffi::wl_resource) {
+ let obj = ffi::wl_resource_get_user_data(resource) as *mut XkbKeyboardObject;
+ if !obj.is_null() {
+ crate::server::wl_list_remove(&mut (*obj).link as *mut ffi::wl_list as *mut WlList);
+ let _ = Box::from_raw(obj);
+ }
+}
+
+static XKB_KEYBOARD_INTERFACE: ffi::river_xkb_keyboard_v1_interface = ffi::river_xkb_keyboard_v1_interface {
+ destroy: Some(xkb_keyboard_destroy),
+ set_keymap: Some(xkb_keyboard_set_keymap),
+ set_layout_by_index: Some(xkb_keyboard_set_layout_by_index),
+ set_layout_by_name: Some(xkb_keyboard_set_layout_by_name),
+ capslock_enable: Some(xkb_keyboard_capslock_enable),
+ capslock_disable: Some(xkb_keyboard_capslock_disable),
+ numlock_enable: Some(xkb_keyboard_numlock_enable),
+ numlock_disable: Some(xkb_keyboard_numlock_disable),
+};
+
+static XKB_KEYBOARD_INERT_INTERFACE: ffi::river_xkb_keyboard_v1_interface = ffi::river_xkb_keyboard_v1_interface {
+ destroy: Some(xkb_keyboard_destroy),
+ set_keymap: None,
+ set_layout_by_index: None,
+ set_layout_by_name: None,
+ capslock_enable: None,
+ capslock_disable: None,
+ numlock_enable: None,
+ numlock_disable: None,
+};
+
+unsafe extern "C" fn xkb_keyboard_destroy(
+ _client: *mut ffi::wl_client,
+ resource: *mut ffi::wl_resource,
+) {
+ ffi::wl_resource_destroy(resource);
+}
+
+unsafe extern "C" fn xkb_keyboard_set_keymap(
+ _client: *mut ffi::wl_client,
+ resource: *mut ffi::wl_resource,
+ keymap_resource: *mut ffi::wl_resource,
+) {
+ let obj = ffi::wl_resource_get_user_data(resource) as *mut XkbKeyboardObject;
+ if obj.is_null() {
+ return;
+ }
+ let keyboard = (*(*obj).keyboard).parent_device;
+ let keymap_struct = ffi::wl_resource_get_user_data(keymap_resource) as *mut crate::xkb_config::XkbKeymap;
+ if keymap_struct.is_null() {
+ ffi::wl_resource_post_error(
+ resource,
+ ffi::river_xkb_keyboard_v1_error_RIVER_XKB_KEYBOARD_V1_ERROR_INVALID_KEYMAP,
+ b"client set invalid keymap\0".as_ptr() as *const _,
+ );
+ return;
+ }
+
+ let kbd = (*keyboard).destroy_data as *mut crate::keyboard::Keyboard;
+ if !kbd.is_null() {
+ (*kbd).set_keymap((*keymap_struct).xkb_keymap);
+ }
+}
+
+unsafe extern "C" fn xkb_keyboard_set_layout_by_index(
+ _client: *mut ffi::wl_client,
+ resource: *mut ffi::wl_resource,
+ index: i32,
+) {
+ let obj = ffi::wl_resource_get_user_data(resource) as *mut XkbKeyboardObject;
+ if obj.is_null() {
+ return;
+ }
+ let keyboard = (*(*obj).keyboard).parent_device;
+ let kbd = (*keyboard).destroy_data as *mut crate::keyboard::Keyboard;
+ if kbd.is_null() {
+ return;
+ }
+ let group = (*kbd).group;
+ if group.is_null() {
+ return;
+ }
+
+ let keymap = (*group).config.keymap;
+ if keymap.is_null() {
+ return;
+ }
+
+ let num_layouts = ffi::xkb_keymap_num_layouts(keymap);
+ if index < 0 || (index as u32) >= num_layouts {
+ return;
+ }
+
+ let mut modifiers = (*group).wlr_keyboard.modifiers;
+ modifiers.group = index as u32;
+ (*group).process_modifiers(modifiers);
+}
+
+unsafe extern "C" fn xkb_keyboard_set_layout_by_name(
+ _client: *mut ffi::wl_client,
+ resource: *mut ffi::wl_resource,
+ name: *const std::os::raw::c_char,
+) {
+ let obj = ffi::wl_resource_get_user_data(resource) as *mut XkbKeyboardObject;
+ if obj.is_null() || name.is_null() {
+ return;
+ }
+ let keyboard = (*(*obj).keyboard).parent_device;
+ let kbd = (*keyboard).destroy_data as *mut crate::keyboard::Keyboard;
+ if kbd.is_null() {
+ return;
+ }
+ let group = (*kbd).group;
+ if group.is_null() {
+ return;
+ }
+
+ let keymap = (*group).config.keymap;
+ if keymap.is_null() {
+ return;
+ }
+
+ let index = ffi::xkb_keymap_layout_get_index(keymap, name);
+ if index == ffi::XKB_LAYOUT_INVALID {
+ return;
+ }
+
+ let mut modifiers = (*group).wlr_keyboard.modifiers;
+ modifiers.group = index;
+ (*group).process_modifiers(modifiers);
+}
+
+unsafe extern "C" fn xkb_keyboard_capslock_enable(
+ _client: *mut ffi::wl_client,
+ resource: *mut ffi::wl_resource,
+) {
+ let obj = ffi::wl_resource_get_user_data(resource) as *mut XkbKeyboardObject;
+ if obj.is_null() {
+ return;
+ }
+ let keyboard = (*(*obj).keyboard).parent_device;
+ let kbd = (*keyboard).destroy_data as *mut crate::keyboard::Keyboard;
+ if kbd.is_null() {
+ return;
+ }
+ let group = (*kbd).group;
+ if group.is_null() {
+ return;
+ }
+
+ let keymap = (*group).config.keymap;
+ if keymap.is_null() {
+ return;
+ }
+
+ let caps_idx = ffi::xkb_keymap_mod_get_index(keymap, b"Caps Lock\0".as_ptr() as *const _);
+ if caps_idx != ffi::XKB_MOD_INVALID {
+ let mask = 1 << caps_idx;
+ let mut modifiers = (*group).wlr_keyboard.modifiers;
+ modifiers.locked |= mask;
+ (*group).process_modifiers(modifiers);
+ }
+}
+
+unsafe extern "C" fn xkb_keyboard_capslock_disable(
+ _client: *mut ffi::wl_client,
+ resource: *mut ffi::wl_resource,
+) {
+ let obj = ffi::wl_resource_get_user_data(resource) as *mut XkbKeyboardObject;
+ if obj.is_null() {
+ return;
+ }
+ let keyboard = (*(*obj).keyboard).parent_device;
+ let kbd = (*keyboard).destroy_data as *mut crate::keyboard::Keyboard;
+ if kbd.is_null() {
+ return;
+ }
+ let group = (*kbd).group;
+ if group.is_null() {
+ return;
+ }
+
+ let keymap = (*group).config.keymap;
+ if keymap.is_null() {
+ return;
+ }
+
+ let caps_idx = ffi::xkb_keymap_mod_get_index(keymap, b"Caps Lock\0".as_ptr() as *const _);
+ if caps_idx != ffi::XKB_MOD_INVALID {
+ let mask = 1 << caps_idx;
+ let mut modifiers = (*group).wlr_keyboard.modifiers;
+ modifiers.locked &= !mask;
+ (*group).process_modifiers(modifiers);
+ }
+}
+
+unsafe extern "C" fn xkb_keyboard_numlock_enable(
+ _client: *mut ffi::wl_client,
+ resource: *mut ffi::wl_resource,
+) {
+ let obj = ffi::wl_resource_get_user_data(resource) as *mut XkbKeyboardObject;
+ if obj.is_null() {
+ return;
+ }
+ let keyboard = (*(*obj).keyboard).parent_device;
+ let kbd = (*keyboard).destroy_data as *mut crate::keyboard::Keyboard;
+ if kbd.is_null() {
+ return;
+ }
+ let group = (*kbd).group;
+ if group.is_null() {
+ return;
+ }
+
+ let keymap = (*group).config.keymap;
+ if keymap.is_null() {
+ return;
+ }
+
+ let num_idx = ffi::xkb_keymap_mod_get_index(keymap, b"Num Lock\0".as_ptr() as *const _);
+ if num_idx != ffi::XKB_MOD_INVALID {
+ let mask = 1 << num_idx;
+ let mut modifiers = (*group).wlr_keyboard.modifiers;
+ modifiers.locked |= mask;
+ (*group).process_modifiers(modifiers);
+ }
+}
+
+unsafe extern "C" fn xkb_keyboard_numlock_disable(
+ _client: *mut ffi::wl_client,
+ resource: *mut ffi::wl_resource,
+) {
+ let obj = ffi::wl_resource_get_user_data(resource) as *mut XkbKeyboardObject;
+ if obj.is_null() {
+ return;
+ }
+ let keyboard = (*(*obj).keyboard).parent_device;
+ let kbd = (*keyboard).destroy_data as *mut crate::keyboard::Keyboard;
+ if kbd.is_null() {
+ return;
+ }
+ let group = (*kbd).group;
+ if group.is_null() {
+ return;
+ }
+
+ let keymap = (*group).config.keymap;
+ if keymap.is_null() {
+ return;
+ }
+
+ let num_idx = ffi::xkb_keymap_mod_get_index(keymap, b"Num Lock\0".as_ptr() as *const _);
+ if num_idx != ffi::XKB_MOD_INVALID {
+ let mask = 1 << num_idx;
+ let mut modifiers = (*group).wlr_keyboard.modifiers;
+ modifiers.locked &= !mask;
+ (*group).process_modifiers(modifiers);
+ }
+}