]>
Commit | Line | Data |
---|---|---|
53e1b683 | 1 | /* SPDX-License-Identifier: LGPL-2.1+ */ |
4897d1dc | 2 | /*** |
96b2fb93 | 3 | Copyright © 2013 Kay Sievers |
4897d1dc ZJS |
4 | ***/ |
5 | ||
6 | #include <errno.h> | |
0d536673 | 7 | #include <stdio_ext.h> |
4897d1dc ZJS |
8 | #include <string.h> |
9 | #include <unistd.h> | |
10 | ||
11 | #include "def.h" | |
12 | #include "env-util.h" | |
13 | #include "fd-util.h" | |
14 | #include "fileio-label.h" | |
15 | #include "fileio.h" | |
16 | #include "keymap-util.h" | |
17 | #include "locale-util.h" | |
18 | #include "macro.h" | |
19 | #include "mkdir.h" | |
20 | #include "string-util.h" | |
21 | #include "strv.h" | |
22 | ||
23 | static bool startswith_comma(const char *s, const char *prefix) { | |
5ad327dd ZJS |
24 | s = startswith(s, prefix); |
25 | if (!s) | |
26 | return false; | |
4897d1dc | 27 | |
4c701096 | 28 | return IN_SET(*s, ',', '\0'); |
4897d1dc ZJS |
29 | } |
30 | ||
31 | static const char* strnulldash(const char *s) { | |
32 | return isempty(s) || streq(s, "-") ? NULL : s; | |
33 | } | |
34 | ||
cabffaf8 ZJS |
35 | static const char* systemd_kbd_model_map(void) { |
36 | const char* s; | |
37 | ||
38 | s = getenv("SYSTEMD_KBD_MODEL_MAP"); | |
39 | if (s) | |
40 | return s; | |
41 | ||
42 | return SYSTEMD_KBD_MODEL_MAP; | |
43 | } | |
44 | ||
45 | static const char* systemd_language_fallback_map(void) { | |
46 | const char* s; | |
47 | ||
48 | s = getenv("SYSTEMD_LANGUAGE_FALLBACK_MAP"); | |
49 | if (s) | |
50 | return s; | |
51 | ||
52 | return SYSTEMD_LANGUAGE_FALLBACK_MAP; | |
53 | } | |
54 | ||
4897d1dc ZJS |
55 | static void context_free_x11(Context *c) { |
56 | c->x11_layout = mfree(c->x11_layout); | |
57 | c->x11_options = mfree(c->x11_options); | |
58 | c->x11_model = mfree(c->x11_model); | |
59 | c->x11_variant = mfree(c->x11_variant); | |
60 | } | |
61 | ||
62 | static void context_free_vconsole(Context *c) { | |
63 | c->vc_keymap = mfree(c->vc_keymap); | |
64 | c->vc_keymap_toggle = mfree(c->vc_keymap_toggle); | |
65 | } | |
66 | ||
67 | static void context_free_locale(Context *c) { | |
68 | int p; | |
69 | ||
70 | for (p = 0; p < _VARIABLE_LC_MAX; p++) | |
71 | c->locale[p] = mfree(c->locale[p]); | |
72 | } | |
73 | ||
74 | void context_free(Context *c) { | |
75 | context_free_locale(c); | |
76 | context_free_x11(c); | |
77 | context_free_vconsole(c); | |
78 | }; | |
79 | ||
df4fd2c7 | 80 | void locale_simplify(char *locale[_VARIABLE_LC_MAX]) { |
4897d1dc ZJS |
81 | int p; |
82 | ||
83 | for (p = VARIABLE_LANG+1; p < _VARIABLE_LC_MAX; p++) | |
df4fd2c7 YW |
84 | if (isempty(locale[p]) || streq_ptr(locale[VARIABLE_LANG], locale[p])) |
85 | locale[p] = mfree(locale[p]); | |
4897d1dc ZJS |
86 | } |
87 | ||
df4fd2c7 YW |
88 | int locale_read_data(Context *c, sd_bus_message *m) { |
89 | struct stat st; | |
4897d1dc ZJS |
90 | int r; |
91 | ||
df4fd2c7 YW |
92 | /* Do not try to re-read the file within single bus operation. */ |
93 | if (m && m == c->locale_cache) | |
94 | return 0; | |
4897d1dc | 95 | |
df4fd2c7 YW |
96 | /* To suppress multiple call of stat(), store the message to cache here. */ |
97 | c->locale_cache = m; | |
98 | ||
99 | r = stat("/etc/locale.conf", &st); | |
100 | if (r < 0 && errno != ENOENT) | |
101 | return -errno; | |
102 | ||
103 | if (r >= 0) { | |
104 | usec_t t; | |
105 | ||
106 | /* If mtime is not changed, then we do not need to re-read the file. */ | |
107 | t = timespec_load(&st.st_mtim); | |
108 | if (c->locale_mtime != USEC_INFINITY && t == c->locale_mtime) | |
109 | return 0; | |
4897d1dc | 110 | |
df4fd2c7 YW |
111 | c->locale_mtime = t; |
112 | context_free_locale(c); | |
113 | ||
114 | r = parse_env_file(NULL, "/etc/locale.conf", NEWLINE, | |
115 | "LANG", &c->locale[VARIABLE_LANG], | |
116 | "LANGUAGE", &c->locale[VARIABLE_LANGUAGE], | |
117 | "LC_CTYPE", &c->locale[VARIABLE_LC_CTYPE], | |
118 | "LC_NUMERIC", &c->locale[VARIABLE_LC_NUMERIC], | |
119 | "LC_TIME", &c->locale[VARIABLE_LC_TIME], | |
120 | "LC_COLLATE", &c->locale[VARIABLE_LC_COLLATE], | |
121 | "LC_MONETARY", &c->locale[VARIABLE_LC_MONETARY], | |
122 | "LC_MESSAGES", &c->locale[VARIABLE_LC_MESSAGES], | |
123 | "LC_PAPER", &c->locale[VARIABLE_LC_PAPER], | |
124 | "LC_NAME", &c->locale[VARIABLE_LC_NAME], | |
125 | "LC_ADDRESS", &c->locale[VARIABLE_LC_ADDRESS], | |
126 | "LC_TELEPHONE", &c->locale[VARIABLE_LC_TELEPHONE], | |
127 | "LC_MEASUREMENT", &c->locale[VARIABLE_LC_MEASUREMENT], | |
128 | "LC_IDENTIFICATION", &c->locale[VARIABLE_LC_IDENTIFICATION], | |
129 | NULL); | |
130 | if (r < 0) | |
131 | return r; | |
132 | } else { | |
4897d1dc ZJS |
133 | int p; |
134 | ||
df4fd2c7 YW |
135 | c->locale_mtime = USEC_INFINITY; |
136 | context_free_locale(c); | |
137 | ||
4897d1dc ZJS |
138 | /* Fill in what we got passed from systemd. */ |
139 | for (p = 0; p < _VARIABLE_LC_MAX; p++) { | |
140 | const char *name; | |
141 | ||
142 | name = locale_variable_to_string(p); | |
143 | assert(name); | |
144 | ||
145 | r = free_and_strdup(&c->locale[p], empty_to_null(getenv(name))); | |
146 | if (r < 0) | |
147 | return r; | |
148 | } | |
4897d1dc ZJS |
149 | } |
150 | ||
df4fd2c7 YW |
151 | locale_simplify(c->locale); |
152 | return 0; | |
4897d1dc ZJS |
153 | } |
154 | ||
df4fd2c7 YW |
155 | int vconsole_read_data(Context *c, sd_bus_message *m) { |
156 | struct stat st; | |
157 | usec_t t; | |
4897d1dc ZJS |
158 | int r; |
159 | ||
df4fd2c7 YW |
160 | /* Do not try to re-read the file within single bus operation. */ |
161 | if (m && m == c->vc_cache) | |
162 | return 0; | |
163 | ||
164 | /* To suppress multiple call of stat(), store the message to cache here. */ | |
165 | c->vc_cache = m; | |
166 | ||
167 | if (stat("/etc/vconsole.conf", &st) < 0) { | |
168 | if (errno != ENOENT) | |
169 | return -errno; | |
170 | ||
171 | c->vc_mtime = USEC_INFINITY; | |
172 | context_free_vconsole(c); | |
173 | return 0; | |
174 | } | |
175 | ||
176 | /* If mtime is not changed, then we do not need to re-read */ | |
177 | t = timespec_load(&st.st_mtim); | |
178 | if (c->vc_mtime != USEC_INFINITY && t == c->vc_mtime) | |
179 | return 0; | |
180 | ||
181 | c->vc_mtime = t; | |
4897d1dc ZJS |
182 | context_free_vconsole(c); |
183 | ||
1a5a177e | 184 | r = parse_env_file(NULL, "/etc/vconsole.conf", NEWLINE, |
4897d1dc ZJS |
185 | "KEYMAP", &c->vc_keymap, |
186 | "KEYMAP_TOGGLE", &c->vc_keymap_toggle, | |
187 | NULL); | |
df4fd2c7 | 188 | if (r < 0) |
4897d1dc ZJS |
189 | return r; |
190 | ||
191 | return 0; | |
192 | } | |
193 | ||
df4fd2c7 YW |
194 | int x11_read_data(Context *c, sd_bus_message *m) { |
195 | _cleanup_fclose_ FILE *f = NULL; | |
4897d1dc | 196 | bool in_section = false; |
df4fd2c7 YW |
197 | char line[LINE_MAX]; |
198 | struct stat st; | |
199 | usec_t t; | |
4897d1dc ZJS |
200 | int r; |
201 | ||
df4fd2c7 YW |
202 | /* Do not try to re-read the file within single bus operation. */ |
203 | if (m && m == c->x11_cache) | |
204 | return 0; | |
205 | ||
206 | /* To suppress multiple call of stat(), store the message to cache here. */ | |
207 | c->x11_cache = m; | |
208 | ||
209 | if (stat("/etc/X11/xorg.conf.d/00-keyboard.conf", &st) < 0) { | |
210 | if (errno != ENOENT) | |
211 | return -errno; | |
212 | ||
213 | c->x11_mtime = USEC_INFINITY; | |
214 | context_free_x11(c); | |
215 | return 0; | |
216 | } | |
217 | ||
218 | /* If mtime is not changed, then we do not need to re-read */ | |
219 | t = timespec_load(&st.st_mtim); | |
220 | if (c->x11_mtime != USEC_INFINITY && t == c->x11_mtime) | |
221 | return 0; | |
222 | ||
223 | c->x11_mtime = t; | |
4897d1dc ZJS |
224 | context_free_x11(c); |
225 | ||
226 | f = fopen("/etc/X11/xorg.conf.d/00-keyboard.conf", "re"); | |
227 | if (!f) | |
df4fd2c7 | 228 | return -errno; |
4897d1dc ZJS |
229 | |
230 | while (fgets(line, sizeof(line), f)) { | |
231 | char *l; | |
232 | ||
233 | char_array_0(line); | |
234 | l = strstrip(line); | |
235 | ||
4c701096 | 236 | if (IN_SET(l[0], 0, '#')) |
4897d1dc ZJS |
237 | continue; |
238 | ||
239 | if (in_section && first_word(l, "Option")) { | |
240 | _cleanup_strv_free_ char **a = NULL; | |
241 | ||
242 | r = strv_split_extract(&a, l, WHITESPACE, EXTRACT_QUOTES); | |
243 | if (r < 0) | |
244 | return r; | |
245 | ||
246 | if (strv_length(a) == 3) { | |
247 | char **p = NULL; | |
248 | ||
249 | if (streq(a[1], "XkbLayout")) | |
250 | p = &c->x11_layout; | |
251 | else if (streq(a[1], "XkbModel")) | |
252 | p = &c->x11_model; | |
253 | else if (streq(a[1], "XkbVariant")) | |
254 | p = &c->x11_variant; | |
255 | else if (streq(a[1], "XkbOptions")) | |
256 | p = &c->x11_options; | |
257 | ||
258 | if (p) { | |
f9ecfd3b | 259 | free_and_replace(*p, a[2]); |
4897d1dc ZJS |
260 | } |
261 | } | |
262 | ||
263 | } else if (!in_section && first_word(l, "Section")) { | |
264 | _cleanup_strv_free_ char **a = NULL; | |
265 | ||
266 | r = strv_split_extract(&a, l, WHITESPACE, EXTRACT_QUOTES); | |
267 | if (r < 0) | |
268 | return -ENOMEM; | |
269 | ||
270 | if (strv_length(a) == 2 && streq(a[1], "InputClass")) | |
271 | in_section = true; | |
272 | ||
273 | } else if (in_section && first_word(l, "EndSection")) | |
274 | in_section = false; | |
275 | } | |
276 | ||
277 | return 0; | |
278 | } | |
279 | ||
4897d1dc | 280 | int locale_write_data(Context *c, char ***settings) { |
4897d1dc | 281 | _cleanup_strv_free_ char **l = NULL; |
df4fd2c7 YW |
282 | struct stat st; |
283 | int r, p; | |
4897d1dc ZJS |
284 | |
285 | /* Set values will be returned as strv in *settings on success. */ | |
286 | ||
4897d1dc ZJS |
287 | for (p = 0; p < _VARIABLE_LC_MAX; p++) { |
288 | _cleanup_free_ char *t = NULL; | |
289 | char **u; | |
290 | const char *name; | |
291 | ||
292 | name = locale_variable_to_string(p); | |
293 | assert(name); | |
294 | ||
df4fd2c7 | 295 | if (isempty(c->locale[p])) |
4897d1dc | 296 | continue; |
4897d1dc ZJS |
297 | |
298 | if (asprintf(&t, "%s=%s", name, c->locale[p]) < 0) | |
299 | return -ENOMEM; | |
300 | ||
301 | u = strv_env_set(l, t); | |
302 | if (!u) | |
303 | return -ENOMEM; | |
304 | ||
130d3d22 | 305 | strv_free_and_replace(l, u); |
4897d1dc ZJS |
306 | } |
307 | ||
308 | if (strv_isempty(l)) { | |
309 | if (unlink("/etc/locale.conf") < 0) | |
310 | return errno == ENOENT ? 0 : -errno; | |
311 | ||
df4fd2c7 | 312 | c->locale_mtime = USEC_INFINITY; |
4897d1dc ZJS |
313 | return 0; |
314 | } | |
315 | ||
316 | r = write_env_file_label("/etc/locale.conf", l); | |
317 | if (r < 0) | |
318 | return r; | |
319 | ||
ae2a15bc | 320 | *settings = TAKE_PTR(l); |
df4fd2c7 YW |
321 | |
322 | if (stat("/etc/locale.conf", &st) >= 0) | |
323 | c->locale_mtime = timespec_load(&st.st_mtim); | |
324 | ||
4897d1dc ZJS |
325 | return 0; |
326 | } | |
327 | ||
328 | int vconsole_write_data(Context *c) { | |
4897d1dc | 329 | _cleanup_strv_free_ char **l = NULL; |
df4fd2c7 YW |
330 | struct stat st; |
331 | int r; | |
4897d1dc ZJS |
332 | |
333 | r = load_env_file(NULL, "/etc/vconsole.conf", NULL, &l); | |
334 | if (r < 0 && r != -ENOENT) | |
335 | return r; | |
336 | ||
337 | if (isempty(c->vc_keymap)) | |
338 | l = strv_env_unset(l, "KEYMAP"); | |
339 | else { | |
340 | _cleanup_free_ char *s = NULL; | |
341 | char **u; | |
342 | ||
343 | s = strappend("KEYMAP=", c->vc_keymap); | |
344 | if (!s) | |
345 | return -ENOMEM; | |
346 | ||
347 | u = strv_env_set(l, s); | |
348 | if (!u) | |
349 | return -ENOMEM; | |
350 | ||
130d3d22 | 351 | strv_free_and_replace(l, u); |
4897d1dc ZJS |
352 | } |
353 | ||
354 | if (isempty(c->vc_keymap_toggle)) | |
355 | l = strv_env_unset(l, "KEYMAP_TOGGLE"); | |
356 | else { | |
357 | _cleanup_free_ char *s = NULL; | |
358 | char **u; | |
359 | ||
360 | s = strappend("KEYMAP_TOGGLE=", c->vc_keymap_toggle); | |
361 | if (!s) | |
362 | return -ENOMEM; | |
363 | ||
364 | u = strv_env_set(l, s); | |
365 | if (!u) | |
366 | return -ENOMEM; | |
367 | ||
130d3d22 | 368 | strv_free_and_replace(l, u); |
4897d1dc ZJS |
369 | } |
370 | ||
371 | if (strv_isempty(l)) { | |
372 | if (unlink("/etc/vconsole.conf") < 0) | |
373 | return errno == ENOENT ? 0 : -errno; | |
374 | ||
df4fd2c7 | 375 | c->vc_mtime = USEC_INFINITY; |
4897d1dc ZJS |
376 | return 0; |
377 | } | |
378 | ||
df4fd2c7 YW |
379 | r = write_env_file_label("/etc/vconsole.conf", l); |
380 | if (r < 0) | |
381 | return r; | |
382 | ||
383 | if (stat("/etc/vconsole.conf", &st) >= 0) | |
384 | c->vc_mtime = timespec_load(&st.st_mtim); | |
385 | ||
386 | return 0; | |
4897d1dc ZJS |
387 | } |
388 | ||
389 | int x11_write_data(Context *c) { | |
390 | _cleanup_fclose_ FILE *f = NULL; | |
391 | _cleanup_free_ char *temp_path = NULL; | |
df4fd2c7 | 392 | struct stat st; |
4897d1dc ZJS |
393 | int r; |
394 | ||
395 | if (isempty(c->x11_layout) && | |
396 | isempty(c->x11_model) && | |
397 | isempty(c->x11_variant) && | |
398 | isempty(c->x11_options)) { | |
399 | ||
400 | if (unlink("/etc/X11/xorg.conf.d/00-keyboard.conf") < 0) | |
401 | return errno == ENOENT ? 0 : -errno; | |
402 | ||
df4fd2c7 | 403 | c->vc_mtime = USEC_INFINITY; |
4897d1dc ZJS |
404 | return 0; |
405 | } | |
406 | ||
407 | mkdir_p_label("/etc/X11/xorg.conf.d", 0755); | |
408 | ||
409 | r = fopen_temporary("/etc/X11/xorg.conf.d/00-keyboard.conf", &f, &temp_path); | |
410 | if (r < 0) | |
411 | return r; | |
412 | ||
0d536673 LP |
413 | (void) __fsetlocking(f, FSETLOCKING_BYCALLER); |
414 | (void) fchmod(fileno(f), 0644); | |
4897d1dc | 415 | |
0d536673 LP |
416 | fputs("# Written by systemd-localed(8), read by systemd-localed and Xorg. It's\n" |
417 | "# probably wise not to edit this file manually. Use localectl(1) to\n" | |
418 | "# instruct systemd-localed to update it.\n" | |
419 | "Section \"InputClass\"\n" | |
420 | " Identifier \"system-keyboard\"\n" | |
421 | " MatchIsKeyboard \"on\"\n", f); | |
4897d1dc ZJS |
422 | |
423 | if (!isempty(c->x11_layout)) | |
424 | fprintf(f, " Option \"XkbLayout\" \"%s\"\n", c->x11_layout); | |
425 | ||
426 | if (!isempty(c->x11_model)) | |
427 | fprintf(f, " Option \"XkbModel\" \"%s\"\n", c->x11_model); | |
428 | ||
429 | if (!isempty(c->x11_variant)) | |
430 | fprintf(f, " Option \"XkbVariant\" \"%s\"\n", c->x11_variant); | |
431 | ||
432 | if (!isempty(c->x11_options)) | |
433 | fprintf(f, " Option \"XkbOptions\" \"%s\"\n", c->x11_options); | |
434 | ||
0d536673 | 435 | fputs("EndSection\n", f); |
4897d1dc | 436 | |
0675e94a | 437 | r = fflush_sync_and_check(f); |
4897d1dc ZJS |
438 | if (r < 0) |
439 | goto fail; | |
440 | ||
441 | if (rename(temp_path, "/etc/X11/xorg.conf.d/00-keyboard.conf") < 0) { | |
442 | r = -errno; | |
443 | goto fail; | |
444 | } | |
445 | ||
df4fd2c7 YW |
446 | if (stat("/etc/X11/xorg.conf.d/00-keyboard.conf", &st) >= 0) |
447 | c->x11_mtime = timespec_load(&st.st_mtim); | |
448 | ||
4897d1dc ZJS |
449 | return 0; |
450 | ||
451 | fail: | |
4897d1dc ZJS |
452 | if (temp_path) |
453 | (void) unlink(temp_path); | |
454 | ||
455 | return r; | |
456 | } | |
457 | ||
458 | static int read_next_mapping(const char* filename, | |
459 | unsigned min_fields, unsigned max_fields, | |
460 | FILE *f, unsigned *n, char ***a) { | |
461 | assert(f); | |
462 | assert(n); | |
463 | assert(a); | |
464 | ||
465 | for (;;) { | |
466 | char line[LINE_MAX]; | |
467 | char *l, **b; | |
468 | int r; | |
469 | size_t length; | |
470 | ||
471 | errno = 0; | |
472 | if (!fgets(line, sizeof(line), f)) { | |
473 | ||
474 | if (ferror(f)) | |
475 | return errno > 0 ? -errno : -EIO; | |
476 | ||
477 | return 0; | |
478 | } | |
479 | ||
480 | (*n)++; | |
481 | ||
482 | l = strstrip(line); | |
4c701096 | 483 | if (IN_SET(l[0], 0, '#')) |
4897d1dc ZJS |
484 | continue; |
485 | ||
486 | r = strv_split_extract(&b, l, WHITESPACE, EXTRACT_QUOTES); | |
487 | if (r < 0) | |
488 | return r; | |
489 | ||
490 | length = strv_length(b); | |
491 | if (length < min_fields || length > max_fields) { | |
492 | log_error("Invalid line %s:%u, ignoring.", filename, *n); | |
493 | strv_free(b); | |
494 | continue; | |
495 | ||
496 | } | |
497 | ||
498 | *a = b; | |
499 | return 1; | |
500 | } | |
501 | } | |
502 | ||
503 | int vconsole_convert_to_x11(Context *c) { | |
cabffaf8 | 504 | const char *map; |
6f3287b3 | 505 | int modified = -1; |
4897d1dc | 506 | |
cabffaf8 ZJS |
507 | map = systemd_kbd_model_map(); |
508 | ||
4897d1dc | 509 | if (isempty(c->vc_keymap)) { |
4897d1dc ZJS |
510 | modified = |
511 | !isempty(c->x11_layout) || | |
512 | !isempty(c->x11_model) || | |
513 | !isempty(c->x11_variant) || | |
514 | !isempty(c->x11_options); | |
515 | ||
516 | context_free_x11(c); | |
517 | } else { | |
518 | _cleanup_fclose_ FILE *f = NULL; | |
519 | unsigned n = 0; | |
520 | ||
cabffaf8 | 521 | f = fopen(map, "re"); |
4897d1dc ZJS |
522 | if (!f) |
523 | return -errno; | |
524 | ||
525 | for (;;) { | |
526 | _cleanup_strv_free_ char **a = NULL; | |
527 | int r; | |
528 | ||
cabffaf8 | 529 | r = read_next_mapping(map, 5, UINT_MAX, f, &n, &a); |
4897d1dc ZJS |
530 | if (r < 0) |
531 | return r; | |
532 | if (r == 0) | |
533 | break; | |
534 | ||
535 | if (!streq(c->vc_keymap, a[0])) | |
536 | continue; | |
537 | ||
538 | if (!streq_ptr(c->x11_layout, strnulldash(a[1])) || | |
539 | !streq_ptr(c->x11_model, strnulldash(a[2])) || | |
540 | !streq_ptr(c->x11_variant, strnulldash(a[3])) || | |
541 | !streq_ptr(c->x11_options, strnulldash(a[4]))) { | |
542 | ||
543 | if (free_and_strdup(&c->x11_layout, strnulldash(a[1])) < 0 || | |
544 | free_and_strdup(&c->x11_model, strnulldash(a[2])) < 0 || | |
545 | free_and_strdup(&c->x11_variant, strnulldash(a[3])) < 0 || | |
546 | free_and_strdup(&c->x11_options, strnulldash(a[4])) < 0) | |
547 | return -ENOMEM; | |
548 | ||
549 | modified = true; | |
550 | } | |
551 | ||
552 | break; | |
553 | } | |
554 | } | |
555 | ||
6f3287b3 | 556 | if (modified > 0) |
4897d1dc ZJS |
557 | log_info("Changing X11 keyboard layout to '%s' model '%s' variant '%s' options '%s'", |
558 | strempty(c->x11_layout), | |
559 | strempty(c->x11_model), | |
560 | strempty(c->x11_variant), | |
561 | strempty(c->x11_options)); | |
6f3287b3 ZJS |
562 | else if (modified < 0) |
563 | log_notice("X11 keyboard layout was not modified: no conversion found for \"%s\".", | |
564 | c->vc_keymap); | |
4897d1dc | 565 | else |
6f3287b3 | 566 | log_debug("X11 keyboard layout did not need to be modified."); |
4897d1dc | 567 | |
6f3287b3 | 568 | return modified > 0; |
4897d1dc ZJS |
569 | } |
570 | ||
571 | int find_converted_keymap(const char *x11_layout, const char *x11_variant, char **new_keymap) { | |
572 | const char *dir; | |
573 | _cleanup_free_ char *n; | |
574 | ||
575 | if (x11_variant) | |
605405c6 | 576 | n = strjoin(x11_layout, "-", x11_variant); |
4897d1dc ZJS |
577 | else |
578 | n = strdup(x11_layout); | |
579 | if (!n) | |
580 | return -ENOMEM; | |
581 | ||
582 | NULSTR_FOREACH(dir, KBD_KEYMAP_DIRS) { | |
583 | _cleanup_free_ char *p = NULL, *pz = NULL; | |
584 | bool uncompressed; | |
585 | ||
605405c6 ZJS |
586 | p = strjoin(dir, "xkb/", n, ".map"); |
587 | pz = strjoin(dir, "xkb/", n, ".map.gz"); | |
4897d1dc ZJS |
588 | if (!p || !pz) |
589 | return -ENOMEM; | |
590 | ||
591 | uncompressed = access(p, F_OK) == 0; | |
592 | if (uncompressed || access(pz, F_OK) == 0) { | |
593 | log_debug("Found converted keymap %s at %s", | |
594 | n, uncompressed ? p : pz); | |
595 | ||
ae2a15bc | 596 | *new_keymap = TAKE_PTR(n); |
4897d1dc ZJS |
597 | return 1; |
598 | } | |
599 | } | |
600 | ||
601 | return 0; | |
602 | } | |
603 | ||
6a6e9c03 | 604 | int find_legacy_keymap(Context *c, char **ret) { |
cabffaf8 ZJS |
605 | const char *map; |
606 | _cleanup_fclose_ FILE *f = NULL; | |
6a6e9c03 | 607 | _cleanup_free_ char *new_keymap = NULL; |
4897d1dc ZJS |
608 | unsigned n = 0; |
609 | unsigned best_matching = 0; | |
610 | int r; | |
611 | ||
5ad327dd ZJS |
612 | assert(!isempty(c->x11_layout)); |
613 | ||
cabffaf8 ZJS |
614 | map = systemd_kbd_model_map(); |
615 | ||
616 | f = fopen(map, "re"); | |
4897d1dc ZJS |
617 | if (!f) |
618 | return -errno; | |
619 | ||
620 | for (;;) { | |
621 | _cleanup_strv_free_ char **a = NULL; | |
622 | unsigned matching = 0; | |
623 | ||
cabffaf8 | 624 | r = read_next_mapping(map, 5, UINT_MAX, f, &n, &a); |
4897d1dc ZJS |
625 | if (r < 0) |
626 | return r; | |
627 | if (r == 0) | |
628 | break; | |
629 | ||
630 | /* Determine how well matching this entry is */ | |
5ad327dd | 631 | if (streq(c->x11_layout, a[1])) |
4897d1dc ZJS |
632 | /* If we got an exact match, this is best */ |
633 | matching = 10; | |
634 | else { | |
635 | /* We have multiple X layouts, look for an | |
636 | * entry that matches our key with everything | |
637 | * but the first layout stripped off. */ | |
638 | if (startswith_comma(c->x11_layout, a[1])) | |
639 | matching = 5; | |
640 | else { | |
641 | char *x; | |
642 | ||
643 | /* If that didn't work, strip off the | |
644 | * other layouts from the entry, too */ | |
645 | x = strndupa(a[1], strcspn(a[1], ",")); | |
646 | if (startswith_comma(c->x11_layout, x)) | |
647 | matching = 1; | |
648 | } | |
649 | } | |
650 | ||
651 | if (matching > 0) { | |
652 | if (isempty(c->x11_model) || streq_ptr(c->x11_model, a[2])) { | |
653 | matching++; | |
654 | ||
655 | if (streq_ptr(c->x11_variant, a[3])) { | |
656 | matching++; | |
657 | ||
658 | if (streq_ptr(c->x11_options, a[4])) | |
659 | matching++; | |
660 | } | |
661 | } | |
662 | } | |
663 | ||
664 | /* The best matching entry so far, then let's save that */ | |
665 | if (matching >= MAX(best_matching, 1u)) { | |
666 | log_debug("Found legacy keymap %s with score %u", | |
667 | a[0], matching); | |
668 | ||
669 | if (matching > best_matching) { | |
670 | best_matching = matching; | |
671 | ||
6a6e9c03 | 672 | r = free_and_strdup(&new_keymap, a[0]); |
4897d1dc ZJS |
673 | if (r < 0) |
674 | return r; | |
675 | } | |
676 | } | |
677 | } | |
678 | ||
679 | if (best_matching < 10 && c->x11_layout) { | |
680 | /* The best match is only the first part of the X11 | |
681 | * keymap. Check if we have a converted map which | |
682 | * matches just the first layout. | |
683 | */ | |
684 | char *l, *v = NULL, *converted; | |
685 | ||
686 | l = strndupa(c->x11_layout, strcspn(c->x11_layout, ",")); | |
687 | if (c->x11_variant) | |
688 | v = strndupa(c->x11_variant, strcspn(c->x11_variant, ",")); | |
689 | r = find_converted_keymap(l, v, &converted); | |
690 | if (r < 0) | |
691 | return r; | |
6a6e9c03 ZJS |
692 | if (r > 0) |
693 | free_and_replace(new_keymap, converted); | |
4897d1dc ZJS |
694 | } |
695 | ||
6a6e9c03 ZJS |
696 | *ret = TAKE_PTR(new_keymap); |
697 | return (bool) *ret; | |
4897d1dc ZJS |
698 | } |
699 | ||
700 | int find_language_fallback(const char *lang, char **language) { | |
cabffaf8 | 701 | const char *map; |
4897d1dc ZJS |
702 | _cleanup_fclose_ FILE *f = NULL; |
703 | unsigned n = 0; | |
704 | ||
aa63b56f | 705 | assert(lang); |
4897d1dc ZJS |
706 | assert(language); |
707 | ||
cabffaf8 ZJS |
708 | map = systemd_language_fallback_map(); |
709 | ||
710 | f = fopen(map, "re"); | |
4897d1dc ZJS |
711 | if (!f) |
712 | return -errno; | |
713 | ||
714 | for (;;) { | |
715 | _cleanup_strv_free_ char **a = NULL; | |
716 | int r; | |
717 | ||
cabffaf8 | 718 | r = read_next_mapping(map, 2, 2, f, &n, &a); |
4897d1dc ZJS |
719 | if (r <= 0) |
720 | return r; | |
721 | ||
722 | if (streq(lang, a[0])) { | |
723 | assert(strv_length(a) == 2); | |
1cc6c93a | 724 | *language = TAKE_PTR(a[1]); |
4897d1dc ZJS |
725 | return 1; |
726 | } | |
727 | } | |
728 | ||
729 | assert_not_reached("should not be here"); | |
730 | } | |
731 | ||
732 | int x11_convert_to_vconsole(Context *c) { | |
733 | bool modified = false; | |
734 | ||
735 | if (isempty(c->x11_layout)) { | |
4897d1dc ZJS |
736 | modified = |
737 | !isempty(c->vc_keymap) || | |
738 | !isempty(c->vc_keymap_toggle); | |
739 | ||
aa63b56f | 740 | context_free_vconsole(c); |
4897d1dc | 741 | } else { |
6a837b03 | 742 | _cleanup_free_ char *new_keymap = NULL; |
4897d1dc ZJS |
743 | int r; |
744 | ||
745 | r = find_converted_keymap(c->x11_layout, c->x11_variant, &new_keymap); | |
746 | if (r < 0) | |
747 | return r; | |
748 | else if (r == 0) { | |
749 | r = find_legacy_keymap(c, &new_keymap); | |
750 | if (r < 0) | |
751 | return r; | |
752 | } | |
5ad327dd ZJS |
753 | if (r == 0) |
754 | /* We search for layout-variant match first, but then we also look | |
755 | * for anything which matches just the layout. So it's accurate to say | |
756 | * that we couldn't find anything which matches the layout. */ | |
757 | log_notice("No conversion to virtual console map found for \"%s\".", | |
758 | c->x11_layout); | |
4897d1dc ZJS |
759 | |
760 | if (!streq_ptr(c->vc_keymap, new_keymap)) { | |
6a837b03 | 761 | free_and_replace(c->vc_keymap, new_keymap); |
4897d1dc ZJS |
762 | c->vc_keymap_toggle = mfree(c->vc_keymap_toggle); |
763 | modified = true; | |
6a837b03 | 764 | } |
4897d1dc ZJS |
765 | } |
766 | ||
767 | if (modified) | |
768 | log_info("Changing virtual console keymap to '%s' toggle '%s'", | |
769 | strempty(c->vc_keymap), strempty(c->vc_keymap_toggle)); | |
770 | else | |
771 | log_debug("Virtual console keymap was not modified."); | |
772 | ||
773 | return modified; | |
774 | } |