Wayland compositor (wlroots)
git clone https://git.lucas.co/cce-compositor.git
src/server/xkb_config.rs (10.4K)
1 // SPDX-FileCopyrightText: © 2026 The River Developers
2 // SPDX-License-Identifier: GPL-3.0-only
3
4 use crate::ffi;
5 use crate::server::{Server, WlList, wl_list_insert, wl_list_remove};
6
7 pub struct XkbConfig {
8 pub server: *mut Server,
9 pub global: *mut ffi::wl_global,
10 pub context: *mut ffi::xkb_context,
11 pub default_keymap: *mut ffi::xkb_keymap,
12 pub objects: ffi::wl_list,
13 pub keyboards: ffi::wl_list,
14 }
15
16 pub struct XkbConfigObject {
17 pub config: *mut XkbConfig,
18 pub resource: *mut ffi::wl_resource,
19 pub link: ffi::wl_list,
20 }
21
22 impl Default for XkbConfig {
23 fn default() -> Self {
24 unsafe { std::mem::zeroed() }
25 }
26 }
27
28 impl XkbConfig {
29 pub unsafe fn init(&mut self, server: *mut Server) -> Result<(), &'static str> {
30 self.server = server;
31 ffi::wl_list_init(&mut self.objects);
32 ffi::wl_list_init(&mut self.keyboards);
33
34 let context = ffi::xkb_context_new(ffi::xkb_context_flags_XKB_CONTEXT_NO_FLAGS);
35 if context.is_null() {
36 return Err("Failed to create xkb_context");
37 }
38 self.context = context;
39
40 let default_keymap = ffi::xkb_keymap_new_from_names(
41 context,
42 std::ptr::null(),
43 ffi::xkb_keymap_compile_flags_XKB_KEYMAP_COMPILE_NO_FLAGS,
44 );
45 if default_keymap.is_null() {
46 ffi::xkb_context_unref(context);
47 self.context = std::ptr::null_mut();
48 return Err("Failed to create default xkb_keymap");
49 }
50 self.default_keymap = default_keymap;
51
52 self.global = ffi::wl_global_create(
53 (*server).wl_server,
54 &ffi::river_xkb_config_v1_interface,
55 2,
56 self as *mut XkbConfig as *mut _,
57 Some(bind_xkb_config),
58 );
59 if self.global.is_null() {
60 ffi::xkb_keymap_unref(self.default_keymap);
61 ffi::xkb_context_unref(self.context);
62 self.default_keymap = std::ptr::null_mut();
63 self.context = std::ptr::null_mut();
64 return Err("Failed to create river_xkb_config_v1 global");
65 }
66
67 Ok(())
68 }
69
70 pub unsafe fn deinit(&mut self) {
71 if !self.global.is_null() {
72 ffi::wl_global_destroy(self.global);
73 self.global = std::ptr::null_mut();
74 }
75
76 let objects_head = &mut self.objects as *mut ffi::wl_list as *mut WlList;
77 let mut curr = (*objects_head).next;
78 while curr != objects_head {
79 let next = (*curr).next;
80 let obj = crate::container_of!(curr, XkbConfigObject, link);
81 ffi::wl_resource_destroy((*obj).resource);
82 curr = next;
83 }
84
85 if !self.default_keymap.is_null() {
86 ffi::xkb_keymap_unref(self.default_keymap);
87 self.default_keymap = std::ptr::null_mut();
88 }
89 if !self.context.is_null() {
90 ffi::xkb_context_unref(self.context);
91 self.context = std::ptr::null_mut();
92 }
93 }
94 }
95
96 unsafe extern "C" fn bind_xkb_config(
97 client: *mut ffi::wl_client,
98 data: *mut std::ffi::c_void,
99 version: u32,
100 id: u32,
101 ) {
102 let config = data as *mut XkbConfig;
103 let resource = ffi::wl_resource_create(client, &ffi::river_xkb_config_v1_interface, version as i32, id);
104 if resource.is_null() {
105 ffi::wl_client_post_no_memory(client);
106 return;
107 }
108
109 let obj = Box::into_raw(Box::new(XkbConfigObject {
110 config,
111 resource,
112 link: std::mem::zeroed(),
113 }));
114
115 ffi::wl_resource_set_implementation(
116 resource,
117 &XKB_CONFIG_INTERFACE as *const _ as *const _,
118 obj as *mut _,
119 Some(handle_object_destroy),
120 );
121
122 let list_head = &mut (*config).objects as *mut ffi::wl_list as *mut WlList;
123 wl_list_insert((*list_head).prev, &mut (*obj).link as *mut ffi::wl_list as *mut WlList);
124
125 let keyboards_head = &mut (*config).keyboards as *mut ffi::wl_list as *mut WlList;
126 let mut curr = (*keyboards_head).next;
127 while curr != keyboards_head {
128 let next = (*curr).next;
129 let xkb_kbd = crate::container_of!(curr, crate::xkb_keyboard::XkbKeyboard, link);
130 (*xkb_kbd).create_object(resource);
131 curr = next;
132 }
133 }
134
135 unsafe extern "C" fn handle_object_destroy(resource: *mut ffi::wl_resource) {
136 let obj = ffi::wl_resource_get_user_data(resource) as *mut XkbConfigObject;
137 if !obj.is_null() {
138 wl_list_remove(&mut (*obj).link as *mut ffi::wl_list as *mut WlList);
139 let _ = Box::from_raw(obj);
140 }
141 }
142
143 static XKB_CONFIG_INTERFACE: ffi::river_xkb_config_v1_interface = ffi::river_xkb_config_v1_interface {
144 stop: Some(xkb_config_stop),
145 destroy: Some(xkb_config_destroy),
146 create_keymap: Some(xkb_config_create_keymap),
147 };
148
149 static XKB_CONFIG_INERT_INTERFACE: ffi::river_xkb_config_v1_interface = ffi::river_xkb_config_v1_interface {
150 stop: None,
151 destroy: Some(xkb_config_inert_destroy),
152 create_keymap: None,
153 };
154
155 unsafe extern "C" fn xkb_config_stop(_client: *mut ffi::wl_client, resource: *mut ffi::wl_resource) {
156 let obj = ffi::wl_resource_get_user_data(resource) as *mut XkbConfigObject;
157 if obj.is_null() {
158 return;
159 }
160
161 wl_list_remove(&mut (*obj).link as *mut ffi::wl_list as *mut WlList);
162 ffi::wl_list_init(&mut (*obj).link);
163
164 ffi::wl_resource_post_event(resource, 0); // finished event
165
166 ffi::wl_resource_set_implementation(
167 resource,
168 &XKB_CONFIG_INERT_INTERFACE as *const _ as *const _,
169 obj as *mut _,
170 Some(handle_object_destroy),
171 );
172 }
173
174 unsafe extern "C" fn xkb_config_destroy(_client: *mut ffi::wl_client, resource: *mut ffi::wl_resource) {
175 ffi::wl_resource_post_error(
176 resource,
177 ffi::river_xkb_config_v1_error_RIVER_XKB_CONFIG_V1_ERROR_INVALID_DESTROY,
178 b"destroy before finished event sent\0".as_ptr() as *const _,
179 );
180 }
181
182 unsafe extern "C" fn xkb_config_inert_destroy(_client: *mut ffi::wl_client, resource: *mut ffi::wl_resource) {
183 ffi::wl_resource_destroy(resource);
184 }
185
186 struct FdGuard(std::os::raw::c_int);
187 impl Drop for FdGuard {
188 fn drop(&mut self) {
189 unsafe {
190 libc::close(self.0);
191 }
192 }
193 }
194
195 pub struct XkbKeymap {
196 pub xkb_keymap: *mut ffi::xkb_keymap,
197 }
198
199 static XKB_KEYMAP_INTERFACE: ffi::river_xkb_keymap_v1_interface = ffi::river_xkb_keymap_v1_interface {
200 destroy: Some(xkb_keymap_destroy),
201 };
202
203 unsafe extern "C" fn xkb_keymap_destroy(_client: *mut ffi::wl_client, resource: *mut ffi::wl_resource) {
204 ffi::wl_resource_destroy(resource);
205 }
206
207 unsafe extern "C" fn xkb_keymap_destroy_func(resource: *mut ffi::wl_resource) {
208 let keymap = ffi::wl_resource_get_user_data(resource) as *mut XkbKeymap;
209 if !keymap.is_null() {
210 ffi::xkb_keymap_unref((*keymap).xkb_keymap);
211 let _ = Box::from_raw(keymap);
212 }
213 }
214
215 unsafe extern "C" fn xkb_config_create_keymap(
216 client: *mut ffi::wl_client,
217 resource: *mut ffi::wl_resource,
218 id: u32,
219 fd: i32,
220 format: u32,
221 ) {
222 let _fd_guard = FdGuard(fd);
223 let obj = ffi::wl_resource_get_user_data(resource) as *mut XkbConfigObject;
224 if obj.is_null() {
225 return;
226 }
227 let config = (*obj).config;
228
229 let keymap_res = ffi::wl_resource_create(
230 client,
231 &ffi::river_xkb_keymap_v1_interface,
232 ffi::wl_resource_get_version(resource),
233 id,
234 );
235 if keymap_res.is_null() {
236 ffi::wl_client_post_no_memory(client);
237 return;
238 }
239
240 if format != ffi::river_xkb_config_v1_keymap_format_RIVER_XKB_CONFIG_V1_KEYMAP_FORMAT_TEXT_V1 {
241 ffi::wl_resource_post_error(
242 resource,
243 ffi::river_xkb_config_v1_error_RIVER_XKB_CONFIG_V1_ERROR_INVALID_FORMAT,
244 b"invalid format enum value\0".as_ptr() as *const _,
245 );
246 return;
247 }
248
249 let mut statbuf = std::mem::MaybeUninit::<libc::stat>::uninit();
250 if libc::fstat(fd, statbuf.as_mut_ptr()) != 0 {
251 ffi::wl_resource_set_implementation(
252 keymap_res,
253 &XKB_KEYMAP_INTERFACE as *const _ as *const _,
254 std::ptr::null_mut(),
255 Some(xkb_keymap_destroy_func),
256 );
257 ffi::wl_resource_post_event(keymap_res, 1, b"failed to stat keymap fd\0".as_ptr() as *const _);
258 return;
259 }
260 let stat = statbuf.assume_init();
261 if stat.st_size < 1 {
262 ffi::wl_resource_set_implementation(
263 keymap_res,
264 &XKB_KEYMAP_INTERFACE as *const _ as *const _,
265 std::ptr::null_mut(),
266 Some(xkb_keymap_destroy_func),
267 );
268 ffi::wl_resource_post_event(keymap_res, 1, b"keymap too small\0".as_ptr() as *const _);
269 return;
270 }
271 if stat.st_size > 1024 * 1024 {
272 ffi::wl_resource_set_implementation(
273 keymap_res,
274 &XKB_KEYMAP_INTERFACE as *const _ as *const _,
275 std::ptr::null_mut(),
276 Some(xkb_keymap_destroy_func),
277 );
278 ffi::wl_resource_post_event(keymap_res, 1, b"keymap too large\0".as_ptr() as *const _);
279 return;
280 }
281
282 let keymap_len = stat.st_size as usize;
283 let map = libc::mmap(
284 std::ptr::null_mut(),
285 keymap_len,
286 libc::PROT_READ,
287 libc::MAP_PRIVATE,
288 fd,
289 0,
290 );
291 if map == libc::MAP_FAILED {
292 ffi::wl_resource_set_implementation(
293 keymap_res,
294 &XKB_KEYMAP_INTERFACE as *const _ as *const _,
295 std::ptr::null_mut(),
296 Some(xkb_keymap_destroy_func),
297 );
298 ffi::wl_resource_post_event(keymap_res, 1, b"failed to mmap() keymap fd\0".as_ptr() as *const _);
299 return;
300 }
301
302 let context = (*config).context;
303 let xkb_keymap = ffi::xkb_keymap_new_from_buffer(
304 context,
305 map as *const _,
306 keymap_len - 1,
307 ffi::xkb_keymap_format_XKB_KEYMAP_FORMAT_TEXT_V1,
308 ffi::xkb_keymap_compile_flags_XKB_KEYMAP_COMPILE_NO_FLAGS,
309 );
310
311 libc::munmap(map, keymap_len);
312
313 if xkb_keymap.is_null() {
314 ffi::wl_resource_set_implementation(
315 keymap_res,
316 &XKB_KEYMAP_INTERFACE as *const _ as *const _,
317 std::ptr::null_mut(),
318 Some(xkb_keymap_destroy_func),
319 );
320 ffi::wl_resource_post_event(keymap_res, 1, b"failed to parse xkb keymap\0".as_ptr() as *const _);
321 return;
322 }
323
324 let xkb_keymap_struct = Box::into_raw(Box::new(XkbKeymap {
325 xkb_keymap,
326 }));
327
328 ffi::wl_resource_set_implementation(
329 keymap_res,
330 &XKB_KEYMAP_INTERFACE as *const _ as *const _,
331 xkb_keymap_struct as *mut _,
332 Some(xkb_keymap_destroy_func),
333 );
334
335 ffi::wl_resource_post_event(keymap_res, 0); // success event
336 }