/src/systemd/src/shared/vconsole-util.c
Line | Count | Source |
1 | | /* SPDX-License-Identifier: LGPL-2.1-or-later */ |
2 | | |
3 | | #include <stdlib.h> |
4 | | #include <unistd.h> |
5 | | |
6 | | #include "alloc-util.h" |
7 | | #include "env-util.h" |
8 | | #include "extract-word.h" |
9 | | #include "fd-util.h" |
10 | | #include "fileio.h" |
11 | | #include "kbd-util.h" |
12 | | #include "log.h" |
13 | | #include "string-util.h" |
14 | | #include "strv.h" |
15 | | #include "vconsole-util.h" |
16 | | |
17 | 0 | static bool startswith_comma(const char *s, const char *prefix) { |
18 | 0 | assert(s); |
19 | 0 | assert(prefix); |
20 | |
|
21 | 0 | s = startswith(s, prefix); |
22 | 0 | if (!s) |
23 | 0 | return false; |
24 | | |
25 | 0 | return IN_SET(*s, ',', '\0'); |
26 | 0 | } |
27 | | |
28 | 0 | static const char* systemd_kbd_model_map(void) { |
29 | 0 | const char* s; |
30 | |
|
31 | 0 | s = getenv("SYSTEMD_KBD_MODEL_MAP"); |
32 | 0 | if (s) |
33 | 0 | return s; |
34 | | |
35 | 0 | return SYSTEMD_KBD_MODEL_MAP; |
36 | 0 | } |
37 | | |
38 | 0 | static const char* systemd_language_fallback_map(void) { |
39 | 0 | const char* s; |
40 | |
|
41 | 0 | s = getenv("SYSTEMD_LANGUAGE_FALLBACK_MAP"); |
42 | 0 | if (s) |
43 | 0 | return s; |
44 | | |
45 | 0 | return SYSTEMD_LANGUAGE_FALLBACK_MAP; |
46 | 0 | } |
47 | | |
48 | 0 | void x11_context_clear(X11Context *xc) { |
49 | 0 | assert(xc); |
50 | |
|
51 | 0 | xc->layout = mfree(xc->layout); |
52 | 0 | xc->options = mfree(xc->options); |
53 | 0 | xc->model = mfree(xc->model); |
54 | 0 | xc->variant = mfree(xc->variant); |
55 | 0 | } |
56 | | |
57 | 0 | void x11_context_replace(X11Context *dest, X11Context *src) { |
58 | 0 | assert(dest); |
59 | 0 | assert(src); |
60 | |
|
61 | 0 | x11_context_clear(dest); |
62 | 0 | *dest = TAKE_STRUCT(*src); |
63 | 0 | } |
64 | | |
65 | 0 | bool x11_context_isempty(const X11Context *xc) { |
66 | 0 | assert(xc); |
67 | |
|
68 | 0 | return |
69 | 0 | isempty(xc->layout) && |
70 | 0 | isempty(xc->model) && |
71 | 0 | isempty(xc->variant) && |
72 | 0 | isempty(xc->options); |
73 | 0 | } |
74 | | |
75 | 0 | void x11_context_empty_to_null(X11Context *xc) { |
76 | 0 | assert(xc); |
77 | | |
78 | | /* Do not call x11_context_clear() for the passed object. */ |
79 | |
|
80 | 0 | xc->layout = empty_to_null(xc->layout); |
81 | 0 | xc->model = empty_to_null(xc->model); |
82 | 0 | xc->variant = empty_to_null(xc->variant); |
83 | 0 | xc->options = empty_to_null(xc->options); |
84 | 0 | } |
85 | | |
86 | 0 | bool x11_context_is_safe(const X11Context *xc) { |
87 | 0 | assert(xc); |
88 | |
|
89 | 0 | return |
90 | 0 | (!xc->layout || string_is_safe(xc->layout)) && |
91 | 0 | (!xc->model || string_is_safe(xc->model)) && |
92 | 0 | (!xc->variant || string_is_safe(xc->variant)) && |
93 | 0 | (!xc->options || string_is_safe(xc->options)); |
94 | 0 | } |
95 | | |
96 | 0 | bool x11_context_equal(const X11Context *a, const X11Context *b) { |
97 | 0 | assert(a); |
98 | 0 | assert(b); |
99 | |
|
100 | 0 | return |
101 | 0 | streq_ptr(a->layout, b->layout) && |
102 | 0 | streq_ptr(a->model, b->model) && |
103 | 0 | streq_ptr(a->variant, b->variant) && |
104 | 0 | streq_ptr(a->options, b->options); |
105 | 0 | } |
106 | | |
107 | 0 | int x11_context_copy(X11Context *dest, const X11Context *src) { |
108 | 0 | bool modified; |
109 | 0 | int r; |
110 | |
|
111 | 0 | assert(dest); |
112 | |
|
113 | 0 | if (dest == src) |
114 | 0 | return 0; |
115 | | |
116 | 0 | if (!src) { |
117 | 0 | modified = !x11_context_isempty(dest); |
118 | 0 | x11_context_clear(dest); |
119 | 0 | return modified; |
120 | 0 | } |
121 | | |
122 | 0 | r = free_and_strdup(&dest->layout, src->layout); |
123 | 0 | if (r < 0) |
124 | 0 | return r; |
125 | 0 | modified = r > 0; |
126 | |
|
127 | 0 | r = free_and_strdup(&dest->model, src->model); |
128 | 0 | if (r < 0) |
129 | 0 | return r; |
130 | 0 | modified = modified || r > 0; |
131 | |
|
132 | 0 | r = free_and_strdup(&dest->variant, src->variant); |
133 | 0 | if (r < 0) |
134 | 0 | return r; |
135 | 0 | modified = modified || r > 0; |
136 | |
|
137 | 0 | r = free_and_strdup(&dest->options, src->options); |
138 | 0 | if (r < 0) |
139 | 0 | return r; |
140 | 0 | modified = modified || r > 0; |
141 | |
|
142 | 0 | return modified; |
143 | 0 | } |
144 | | |
145 | 0 | void vc_context_clear(VCContext *vc) { |
146 | 0 | assert(vc); |
147 | |
|
148 | 0 | vc->keymap = mfree(vc->keymap); |
149 | 0 | vc->toggle = mfree(vc->toggle); |
150 | 0 | } |
151 | | |
152 | 0 | void vc_context_replace(VCContext *dest, VCContext *src) { |
153 | 0 | assert(dest); |
154 | 0 | assert(src); |
155 | |
|
156 | 0 | vc_context_clear(dest); |
157 | 0 | *dest = TAKE_STRUCT(*src); |
158 | 0 | } |
159 | | |
160 | 0 | bool vc_context_isempty(const VCContext *vc) { |
161 | 0 | assert(vc); |
162 | |
|
163 | 0 | return |
164 | 0 | isempty(vc->keymap) && |
165 | 0 | isempty(vc->toggle); |
166 | 0 | } |
167 | | |
168 | 0 | void vc_context_empty_to_null(VCContext *vc) { |
169 | 0 | assert(vc); |
170 | | |
171 | | /* Do not call vc_context_clear() for the passed object. */ |
172 | |
|
173 | 0 | vc->keymap = empty_to_null(vc->keymap); |
174 | 0 | vc->toggle = empty_to_null(vc->toggle); |
175 | 0 | } |
176 | | |
177 | 0 | bool vc_context_equal(const VCContext *a, const VCContext *b) { |
178 | 0 | assert(a); |
179 | 0 | assert(b); |
180 | |
|
181 | 0 | return |
182 | 0 | streq_ptr(a->keymap, b->keymap) && |
183 | 0 | streq_ptr(a->toggle, b->toggle); |
184 | 0 | } |
185 | | |
186 | 0 | int vc_context_copy(VCContext *dest, const VCContext *src) { |
187 | 0 | bool modified; |
188 | 0 | int r; |
189 | |
|
190 | 0 | assert(dest); |
191 | |
|
192 | 0 | if (dest == src) |
193 | 0 | return 0; |
194 | | |
195 | 0 | if (!src) { |
196 | 0 | modified = !vc_context_isempty(dest); |
197 | 0 | vc_context_clear(dest); |
198 | 0 | return modified; |
199 | 0 | } |
200 | | |
201 | 0 | r = free_and_strdup(&dest->keymap, src->keymap); |
202 | 0 | if (r < 0) |
203 | 0 | return r; |
204 | 0 | modified = r > 0; |
205 | |
|
206 | 0 | r = free_and_strdup(&dest->toggle, src->toggle); |
207 | 0 | if (r < 0) |
208 | 0 | return r; |
209 | 0 | modified = modified || r > 0; |
210 | |
|
211 | 0 | return modified; |
212 | 0 | } |
213 | | |
214 | | static int read_next_mapping( |
215 | | const char *filename, |
216 | | unsigned min_fields, |
217 | | unsigned max_fields, |
218 | | FILE *f, |
219 | | unsigned *n, |
220 | 0 | char ***ret) { |
221 | |
|
222 | 0 | assert(f); |
223 | 0 | assert(n); |
224 | 0 | assert(ret); |
225 | |
|
226 | 0 | for (;;) { |
227 | 0 | _cleanup_strv_free_ char **b = NULL; |
228 | 0 | _cleanup_free_ char *line = NULL; |
229 | 0 | size_t length; |
230 | 0 | int r; |
231 | |
|
232 | 0 | r = read_stripped_line(f, LONG_LINE_MAX, &line); |
233 | 0 | if (r < 0) |
234 | 0 | return r; |
235 | 0 | if (r == 0) |
236 | 0 | break; |
237 | | |
238 | 0 | (*n)++; |
239 | |
|
240 | 0 | if (IN_SET(line[0], 0, '#')) |
241 | 0 | continue; |
242 | | |
243 | 0 | r = strv_split_full(&b, line, WHITESPACE, EXTRACT_UNQUOTE); |
244 | 0 | if (r < 0) |
245 | 0 | return r; |
246 | | |
247 | 0 | length = strv_length(b); |
248 | 0 | if (length < min_fields || length > max_fields) { |
249 | 0 | log_debug("Invalid line %s:%u, ignoring.", strna(filename), *n); |
250 | 0 | continue; |
251 | |
|
252 | 0 | } |
253 | | |
254 | 0 | *ret = TAKE_PTR(b); |
255 | 0 | return 1; |
256 | 0 | } |
257 | | |
258 | 0 | *ret = NULL; |
259 | 0 | return 0; |
260 | 0 | } |
261 | | |
262 | 0 | int vconsole_convert_to_x11(const VCContext *vc, X11VerifyCallback verify, X11Context *ret) { |
263 | 0 | _cleanup_fclose_ FILE *f = NULL; |
264 | 0 | const char *map; |
265 | 0 | X11Context xc; |
266 | 0 | int r; |
267 | |
|
268 | 0 | assert(vc); |
269 | 0 | assert(ret); |
270 | |
|
271 | 0 | if (isempty(vc->keymap)) { |
272 | 0 | *ret = (X11Context) {}; |
273 | 0 | return 0; |
274 | 0 | } |
275 | | |
276 | 0 | map = systemd_kbd_model_map(); |
277 | 0 | f = fopen(map, "re"); |
278 | 0 | if (!f) |
279 | 0 | return -errno; |
280 | | |
281 | 0 | for (unsigned n = 0;;) { |
282 | 0 | _cleanup_strv_free_ char **a = NULL; |
283 | |
|
284 | 0 | r = read_next_mapping(map, 5, UINT_MAX, f, &n, &a); |
285 | 0 | if (r < 0) |
286 | 0 | return r; |
287 | 0 | if (r == 0) |
288 | 0 | break; |
289 | | |
290 | 0 | if (!streq(vc->keymap, a[0])) |
291 | 0 | continue; |
292 | | |
293 | 0 | xc = (X11Context) { |
294 | 0 | .layout = empty_or_dash_to_null(a[1]), |
295 | 0 | .model = empty_or_dash_to_null(a[2]), |
296 | 0 | .variant = empty_or_dash_to_null(a[3]), |
297 | 0 | .options = empty_or_dash_to_null(a[4]), |
298 | 0 | }; |
299 | |
|
300 | 0 | if (verify && verify(&xc) < 0) |
301 | 0 | continue; |
302 | | |
303 | 0 | return x11_context_copy(ret, &xc); |
304 | 0 | } |
305 | | |
306 | | /* No custom mapping has been found, see if the keymap is a converted one. In such case deducing the |
307 | | * corresponding x11 layout is easy. */ |
308 | 0 | _cleanup_free_ char *xlayout = NULL, *converted = NULL; |
309 | 0 | char *xvariant; |
310 | |
|
311 | 0 | xlayout = strdup(vc->keymap); |
312 | 0 | if (!xlayout) |
313 | 0 | return -ENOMEM; |
314 | 0 | xvariant = strchr(xlayout, '-'); |
315 | 0 | if (xvariant) { |
316 | 0 | xvariant[0] = '\0'; |
317 | 0 | xvariant++; |
318 | 0 | } |
319 | | |
320 | | /* Note: by default we use keyboard model "microsoftpro" which should be equivalent to "pc105" but |
321 | | * with the internet/media key mapping added. */ |
322 | 0 | xc = (X11Context) { |
323 | 0 | .layout = xlayout, |
324 | 0 | .model = (char*) "microsoftpro", |
325 | 0 | .variant = xvariant, |
326 | 0 | .options = (char*) "terminate:ctrl_alt_bksp", |
327 | 0 | }; |
328 | | |
329 | | /* This sanity check seems redundant with the verification of the X11 layout done on the next |
330 | | * step. However xkbcommon is an optional dependency hence the verification might be a NOP. */ |
331 | 0 | r = find_converted_keymap(&xc, &converted); |
332 | 0 | if (r == 0 && xc.variant) { |
333 | | /* If we still haven't find a match, try with no variant, it's still better than nothing. */ |
334 | 0 | xc.variant = NULL; |
335 | 0 | r = find_converted_keymap(&xc, &converted); |
336 | 0 | } |
337 | 0 | if (r < 0) |
338 | 0 | return r; |
339 | | |
340 | 0 | if (r == 0 || (verify && verify(&xc) < 0)) { |
341 | 0 | *ret = (X11Context) {}; |
342 | 0 | return 0; |
343 | 0 | } |
344 | | |
345 | 0 | return x11_context_copy(ret, &xc); |
346 | 0 | } |
347 | | |
348 | 0 | int find_converted_keymap(const X11Context *xc, char **ret) { |
349 | 0 | _cleanup_free_ char *n = NULL, *p = NULL, *pz = NULL; |
350 | 0 | _cleanup_strv_free_ char **keymap_dirs = NULL; |
351 | 0 | int r; |
352 | |
|
353 | 0 | assert(xc); |
354 | 0 | assert(!isempty(xc->layout)); |
355 | 0 | assert(ret); |
356 | |
|
357 | 0 | if (xc->variant) |
358 | 0 | n = strjoin(xc->layout, "-", xc->variant); |
359 | 0 | else |
360 | 0 | n = strdup(xc->layout); |
361 | 0 | if (!n) |
362 | 0 | return -ENOMEM; |
363 | | |
364 | 0 | p = strjoin("xkb/", n, ".map"); |
365 | 0 | pz = strjoin("xkb/", n, ".map.gz"); |
366 | 0 | if (!p || !pz) |
367 | 0 | return -ENOMEM; |
368 | | |
369 | 0 | r = keymap_directories(&keymap_dirs); |
370 | 0 | if (r < 0) |
371 | 0 | return r; |
372 | | |
373 | 0 | STRV_FOREACH(dir, keymap_dirs) { |
374 | 0 | _cleanup_close_ int dir_fd = -EBADF; |
375 | 0 | bool uncompressed; |
376 | |
|
377 | 0 | dir_fd = open(*dir, O_CLOEXEC | O_DIRECTORY | O_PATH); |
378 | 0 | if (dir_fd < 0) { |
379 | 0 | if (errno != ENOENT) |
380 | 0 | log_debug_errno(errno, "Failed to open %s, ignoring: %m", *dir); |
381 | 0 | continue; |
382 | 0 | } |
383 | | |
384 | 0 | uncompressed = faccessat(dir_fd, p, F_OK, 0) >= 0; |
385 | 0 | if (uncompressed || faccessat(dir_fd, pz, F_OK, 0) >= 0) { |
386 | 0 | log_debug("Found converted keymap %s at %s/%s", n, *dir, uncompressed ? p : pz); |
387 | 0 | *ret = TAKE_PTR(n); |
388 | 0 | return 1; |
389 | 0 | } |
390 | 0 | } |
391 | | |
392 | 0 | *ret = NULL; |
393 | 0 | return 0; |
394 | 0 | } |
395 | | |
396 | 0 | int find_legacy_keymap(const X11Context *xc, char **ret) { |
397 | 0 | const char *map; |
398 | 0 | _cleanup_fclose_ FILE *f = NULL; |
399 | 0 | _cleanup_free_ char *new_keymap = NULL; |
400 | 0 | unsigned best_matching = 0; |
401 | 0 | int r; |
402 | |
|
403 | 0 | assert(xc); |
404 | 0 | assert(!isempty(xc->layout)); |
405 | |
|
406 | 0 | map = systemd_kbd_model_map(); |
407 | 0 | f = fopen(map, "re"); |
408 | 0 | if (!f) |
409 | 0 | return -errno; |
410 | | |
411 | 0 | for (unsigned n = 0;;) { |
412 | 0 | _cleanup_strv_free_ char **a = NULL; |
413 | 0 | unsigned matching = 0; |
414 | |
|
415 | 0 | r = read_next_mapping(map, 5, UINT_MAX, f, &n, &a); |
416 | 0 | if (r < 0) |
417 | 0 | return r; |
418 | 0 | if (r == 0) |
419 | 0 | break; |
420 | | |
421 | | /* Determine how well matching this entry is */ |
422 | 0 | if (streq(xc->layout, a[1])) |
423 | | /* If we got an exact match, this is the best */ |
424 | 0 | matching = 10; |
425 | 0 | else { |
426 | | /* see if we get an exact match with the order reversed */ |
427 | 0 | _cleanup_strv_free_ char **b = NULL; |
428 | 0 | _cleanup_free_ char *c = NULL; |
429 | 0 | r = strv_split_full(&b, a[1], ",", 0); |
430 | 0 | if (r < 0) |
431 | 0 | return r; |
432 | 0 | strv_reverse(b); |
433 | 0 | c = strv_join(b, ","); |
434 | 0 | if (!c) |
435 | 0 | return log_oom(); |
436 | 0 | if (streq(xc->layout, c)) |
437 | 0 | matching = 9; |
438 | 0 | else { |
439 | | /* We have multiple X layouts, look for an |
440 | | * entry that matches our key with everything |
441 | | * but the first layout stripped off. */ |
442 | 0 | if (startswith_comma(xc->layout, a[1])) |
443 | 0 | matching = 5; |
444 | 0 | else { |
445 | 0 | _cleanup_free_ char *x = NULL; |
446 | | |
447 | | /* If that didn't work, strip off the |
448 | | * other layouts from the entry, too */ |
449 | 0 | x = strdupcspn(a[1], ","); |
450 | 0 | if (!x) |
451 | 0 | return -ENOMEM; |
452 | 0 | if (startswith_comma(xc->layout, x)) |
453 | 0 | matching = 1; |
454 | 0 | } |
455 | 0 | } |
456 | 0 | } |
457 | | |
458 | 0 | if (matching > 0) { |
459 | 0 | if (isempty(xc->model) || streq_ptr(xc->model, a[2])) { |
460 | 0 | matching++; |
461 | |
|
462 | 0 | if (streq_ptr(xc->variant, a[3]) || ((isempty(xc->variant) || streq_skip_trailing_chars(xc->variant, "", ",")) && streq(a[3], "-"))) { |
463 | 0 | matching++; |
464 | |
|
465 | 0 | if (streq_ptr(xc->options, a[4])) |
466 | 0 | matching++; |
467 | 0 | } |
468 | 0 | } |
469 | 0 | } |
470 | | |
471 | | /* The best matching entry so far, then let's save that */ |
472 | 0 | if (matching >= MAX(best_matching, 1u)) { |
473 | 0 | log_debug("Found legacy keymap %s with score %u", a[0], matching); |
474 | |
|
475 | 0 | if (matching > best_matching) { |
476 | 0 | best_matching = matching; |
477 | |
|
478 | 0 | r = free_and_strdup(&new_keymap, a[0]); |
479 | 0 | if (r < 0) |
480 | 0 | return r; |
481 | 0 | } |
482 | 0 | } |
483 | 0 | } |
484 | | |
485 | 0 | if (best_matching < 9 && !isempty(xc->layout)) { |
486 | 0 | _cleanup_free_ char *l = NULL, *v = NULL, *converted = NULL; |
487 | | |
488 | | /* The best match is only the first part of the X11 |
489 | | * keymap. Check if we have a converted map which |
490 | | * matches just the first layout. |
491 | | */ |
492 | |
|
493 | 0 | l = strdupcspn(xc->layout, ","); |
494 | 0 | if (!l) |
495 | 0 | return -ENOMEM; |
496 | | |
497 | 0 | if (!isempty(xc->variant)) { |
498 | 0 | v = strdupcspn(xc->variant, ","); |
499 | 0 | if (!v) |
500 | 0 | return -ENOMEM; |
501 | 0 | } |
502 | | |
503 | 0 | r = find_converted_keymap( |
504 | 0 | &(X11Context) { |
505 | 0 | .layout = l, |
506 | 0 | .variant = v, |
507 | 0 | }, |
508 | 0 | &converted); |
509 | 0 | if (r < 0) |
510 | 0 | return r; |
511 | 0 | if (r > 0) |
512 | 0 | free_and_replace(new_keymap, converted); |
513 | 0 | } |
514 | | |
515 | 0 | *ret = TAKE_PTR(new_keymap); |
516 | 0 | return !!*ret; |
517 | 0 | } |
518 | | |
519 | 0 | int x11_convert_to_vconsole(const X11Context *xc, VCContext *ret) { |
520 | 0 | _cleanup_free_ char *keymap = NULL; |
521 | 0 | int r; |
522 | |
|
523 | 0 | assert(xc); |
524 | 0 | assert(ret); |
525 | |
|
526 | 0 | if (isempty(xc->layout)) { |
527 | 0 | *ret = (VCContext) {}; |
528 | 0 | return 0; |
529 | 0 | } |
530 | | |
531 | 0 | r = find_converted_keymap(xc, &keymap); |
532 | 0 | if (r == 0) { |
533 | 0 | r = find_legacy_keymap(xc, &keymap); |
534 | 0 | if (r == 0 && xc->variant) |
535 | | /* If we still haven't find a match, try with no variant, it's still better than |
536 | | * nothing. */ |
537 | 0 | r = find_converted_keymap( |
538 | 0 | &(X11Context) { |
539 | 0 | .layout = xc->layout, |
540 | 0 | }, |
541 | 0 | &keymap); |
542 | 0 | } |
543 | 0 | if (r < 0) |
544 | 0 | return r; |
545 | | |
546 | 0 | *ret = (VCContext) { |
547 | 0 | .keymap = TAKE_PTR(keymap), |
548 | 0 | }; |
549 | 0 | return 0; |
550 | 0 | } |
551 | | |
552 | 0 | int find_language_fallback(const char *lang, char **ret) { |
553 | 0 | const char *map; |
554 | 0 | _cleanup_fclose_ FILE *f = NULL; |
555 | 0 | unsigned n = 0; |
556 | 0 | int r; |
557 | |
|
558 | 0 | assert(lang); |
559 | 0 | assert(ret); |
560 | |
|
561 | 0 | map = systemd_language_fallback_map(); |
562 | 0 | f = fopen(map, "re"); |
563 | 0 | if (!f) |
564 | 0 | return -errno; |
565 | | |
566 | 0 | for (;;) { |
567 | 0 | _cleanup_strv_free_ char **a = NULL; |
568 | |
|
569 | 0 | r = read_next_mapping(map, 2, 2, f, &n, &a); |
570 | 0 | if (r <= 0) |
571 | 0 | return r; |
572 | | |
573 | 0 | if (streq(lang, a[0])) { |
574 | 0 | assert(strv_length(a) == 2); |
575 | 0 | *ret = TAKE_PTR(a[1]); |
576 | 0 | return 1; |
577 | 0 | } |
578 | 0 | } |
579 | 0 | } |
580 | | |
581 | 0 | int vconsole_serialize(const VCContext *vc, const X11Context *xc, char ***env) { |
582 | 0 | int r; |
583 | | |
584 | | /* This function modifies the passed strv in place. */ |
585 | |
|
586 | 0 | assert(vc); |
587 | 0 | assert(xc); |
588 | 0 | assert(env); |
589 | |
|
590 | 0 | r = strv_env_assign(env, "KEYMAP", empty_to_null(vc->keymap)); |
591 | 0 | if (r < 0) |
592 | 0 | return r; |
593 | | |
594 | 0 | r = strv_env_assign(env, "KEYMAP_TOGGLE", empty_to_null(vc->toggle)); |
595 | 0 | if (r < 0) |
596 | 0 | return r; |
597 | | |
598 | 0 | r = strv_env_assign(env, "XKBLAYOUT", empty_to_null(xc->layout)); |
599 | 0 | if (r < 0) |
600 | 0 | return r; |
601 | | |
602 | 0 | r = strv_env_assign(env, "XKBMODEL", empty_to_null(xc->model)); |
603 | 0 | if (r < 0) |
604 | 0 | return r; |
605 | | |
606 | 0 | r = strv_env_assign(env, "XKBVARIANT", empty_to_null(xc->variant)); |
607 | 0 | if (r < 0) |
608 | 0 | return r; |
609 | | |
610 | 0 | r = strv_env_assign(env, "XKBOPTIONS", empty_to_null(xc->options)); |
611 | 0 | if (r < 0) |
612 | 0 | return r; |
613 | | |
614 | 0 | return 0; |
615 | 0 | } |