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 }