1 /* SPDX-License-Identifier: LGPL-2.1+ */
3 This file is part of systemd.
5 Copyright 2011 Lennart Poettering
6 Copyright 2013 Kay Sievers
8 systemd is free software; you can redistribute it and/or modify it
9 under the terms of the GNU Lesser General Public License as published by
10 the Free Software Foundation; either version 2.1 of the License, or
11 (at your option) any later version.
13 systemd is distributed in the hope that it will be useful, but
14 WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 Lesser General Public License for more details.
18 You should have received a copy of the GNU Lesser General Public License
19 along with systemd; If not, see <http://www.gnu.org/licenses/>.
29 #include "fileio-label.h"
31 #include "keymap-util.h"
32 #include "locale-util.h"
35 #include "string-util.h"
38 static bool startswith_comma(const char *s
, const char *prefix
) {
39 s
= startswith(s
, prefix
);
43 return IN_SET(*s
, ',', '\0');
46 static const char* strnulldash(const char *s
) {
47 return isempty(s
) || streq(s
, "-") ? NULL
: s
;
50 static const char* systemd_kbd_model_map(void) {
53 s
= getenv("SYSTEMD_KBD_MODEL_MAP");
57 return SYSTEMD_KBD_MODEL_MAP
;
60 static const char* systemd_language_fallback_map(void) {
63 s
= getenv("SYSTEMD_LANGUAGE_FALLBACK_MAP");
67 return SYSTEMD_LANGUAGE_FALLBACK_MAP
;
70 static void context_free_x11(Context
*c
) {
71 c
->x11_layout
= mfree(c
->x11_layout
);
72 c
->x11_options
= mfree(c
->x11_options
);
73 c
->x11_model
= mfree(c
->x11_model
);
74 c
->x11_variant
= mfree(c
->x11_variant
);
77 static void context_free_vconsole(Context
*c
) {
78 c
->vc_keymap
= mfree(c
->vc_keymap
);
79 c
->vc_keymap_toggle
= mfree(c
->vc_keymap_toggle
);
82 static void context_free_locale(Context
*c
) {
85 for (p
= 0; p
< _VARIABLE_LC_MAX
; p
++)
86 c
->locale
[p
] = mfree(c
->locale
[p
]);
89 void context_free(Context
*c
) {
90 context_free_locale(c
);
92 context_free_vconsole(c
);
95 void locale_simplify(Context
*c
) {
98 for (p
= VARIABLE_LANG
+1; p
< _VARIABLE_LC_MAX
; p
++)
99 if (isempty(c
->locale
[p
]) || streq_ptr(c
->locale
[VARIABLE_LANG
], c
->locale
[p
]))
100 c
->locale
[p
] = mfree(c
->locale
[p
]);
103 static int locale_read_data(Context
*c
) {
106 context_free_locale(c
);
108 r
= parse_env_file("/etc/locale.conf", NEWLINE
,
109 "LANG", &c
->locale
[VARIABLE_LANG
],
110 "LANGUAGE", &c
->locale
[VARIABLE_LANGUAGE
],
111 "LC_CTYPE", &c
->locale
[VARIABLE_LC_CTYPE
],
112 "LC_NUMERIC", &c
->locale
[VARIABLE_LC_NUMERIC
],
113 "LC_TIME", &c
->locale
[VARIABLE_LC_TIME
],
114 "LC_COLLATE", &c
->locale
[VARIABLE_LC_COLLATE
],
115 "LC_MONETARY", &c
->locale
[VARIABLE_LC_MONETARY
],
116 "LC_MESSAGES", &c
->locale
[VARIABLE_LC_MESSAGES
],
117 "LC_PAPER", &c
->locale
[VARIABLE_LC_PAPER
],
118 "LC_NAME", &c
->locale
[VARIABLE_LC_NAME
],
119 "LC_ADDRESS", &c
->locale
[VARIABLE_LC_ADDRESS
],
120 "LC_TELEPHONE", &c
->locale
[VARIABLE_LC_TELEPHONE
],
121 "LC_MEASUREMENT", &c
->locale
[VARIABLE_LC_MEASUREMENT
],
122 "LC_IDENTIFICATION", &c
->locale
[VARIABLE_LC_IDENTIFICATION
],
128 /* Fill in what we got passed from systemd. */
129 for (p
= 0; p
< _VARIABLE_LC_MAX
; p
++) {
132 name
= locale_variable_to_string(p
);
135 r
= free_and_strdup(&c
->locale
[p
], empty_to_null(getenv(name
)));
147 static int vconsole_read_data(Context
*c
) {
150 context_free_vconsole(c
);
152 r
= parse_env_file("/etc/vconsole.conf", NEWLINE
,
153 "KEYMAP", &c
->vc_keymap
,
154 "KEYMAP_TOGGLE", &c
->vc_keymap_toggle
,
157 if (r
< 0 && r
!= -ENOENT
)
163 static int x11_read_data(Context
*c
) {
164 _cleanup_fclose_
FILE *f
;
166 bool in_section
= false;
171 f
= fopen("/etc/X11/xorg.conf.d/00-keyboard.conf", "re");
173 return errno
== ENOENT
? 0 : -errno
;
175 while (fgets(line
, sizeof(line
), f
)) {
181 if (IN_SET(l
[0], 0, '#'))
184 if (in_section
&& first_word(l
, "Option")) {
185 _cleanup_strv_free_
char **a
= NULL
;
187 r
= strv_split_extract(&a
, l
, WHITESPACE
, EXTRACT_QUOTES
);
191 if (strv_length(a
) == 3) {
194 if (streq(a
[1], "XkbLayout"))
196 else if (streq(a
[1], "XkbModel"))
198 else if (streq(a
[1], "XkbVariant"))
200 else if (streq(a
[1], "XkbOptions"))
210 } else if (!in_section
&& first_word(l
, "Section")) {
211 _cleanup_strv_free_
char **a
= NULL
;
213 r
= strv_split_extract(&a
, l
, WHITESPACE
, EXTRACT_QUOTES
);
217 if (strv_length(a
) == 2 && streq(a
[1], "InputClass"))
220 } else if (in_section
&& first_word(l
, "EndSection"))
227 int context_read_data(Context
*c
) {
230 r
= locale_read_data(c
);
231 q
= vconsole_read_data(c
);
232 p
= x11_read_data(c
);
234 return r
< 0 ? r
: q
< 0 ? q
: p
;
237 int locale_write_data(Context
*c
, char ***settings
) {
239 _cleanup_strv_free_
char **l
= NULL
;
241 /* Set values will be returned as strv in *settings on success. */
243 r
= load_env_file(NULL
, "/etc/locale.conf", NULL
, &l
);
244 if (r
< 0 && r
!= -ENOENT
)
247 for (p
= 0; p
< _VARIABLE_LC_MAX
; p
++) {
248 _cleanup_free_
char *t
= NULL
;
252 name
= locale_variable_to_string(p
);
255 if (isempty(c
->locale
[p
])) {
256 l
= strv_env_unset(l
, name
);
260 if (asprintf(&t
, "%s=%s", name
, c
->locale
[p
]) < 0)
263 u
= strv_env_set(l
, t
);
271 if (strv_isempty(l
)) {
272 if (unlink("/etc/locale.conf") < 0)
273 return errno
== ENOENT
? 0 : -errno
;
278 r
= write_env_file_label("/etc/locale.conf", l
);
287 int vconsole_write_data(Context
*c
) {
289 _cleanup_strv_free_
char **l
= NULL
;
291 r
= load_env_file(NULL
, "/etc/vconsole.conf", NULL
, &l
);
292 if (r
< 0 && r
!= -ENOENT
)
295 if (isempty(c
->vc_keymap
))
296 l
= strv_env_unset(l
, "KEYMAP");
298 _cleanup_free_
char *s
= NULL
;
301 s
= strappend("KEYMAP=", c
->vc_keymap
);
305 u
= strv_env_set(l
, s
);
313 if (isempty(c
->vc_keymap_toggle
))
314 l
= strv_env_unset(l
, "KEYMAP_TOGGLE");
316 _cleanup_free_
char *s
= NULL
;
319 s
= strappend("KEYMAP_TOGGLE=", c
->vc_keymap_toggle
);
323 u
= strv_env_set(l
, s
);
331 if (strv_isempty(l
)) {
332 if (unlink("/etc/vconsole.conf") < 0)
333 return errno
== ENOENT
? 0 : -errno
;
338 return write_env_file_label("/etc/vconsole.conf", l
);
341 int x11_write_data(Context
*c
) {
342 _cleanup_fclose_
FILE *f
= NULL
;
343 _cleanup_free_
char *temp_path
= NULL
;
346 if (isempty(c
->x11_layout
) &&
347 isempty(c
->x11_model
) &&
348 isempty(c
->x11_variant
) &&
349 isempty(c
->x11_options
)) {
351 if (unlink("/etc/X11/xorg.conf.d/00-keyboard.conf") < 0)
352 return errno
== ENOENT
? 0 : -errno
;
357 mkdir_p_label("/etc/X11/xorg.conf.d", 0755);
359 r
= fopen_temporary("/etc/X11/xorg.conf.d/00-keyboard.conf", &f
, &temp_path
);
363 fchmod(fileno(f
), 0644);
365 fputs_unlocked("# Written by systemd-localed(8), read by systemd-localed and Xorg. It's\n"
366 "# probably wise not to edit this file manually. Use localectl(1) to\n"
367 "# instruct systemd-localed to update it.\n"
368 "Section \"InputClass\"\n"
369 " Identifier \"system-keyboard\"\n"
370 " MatchIsKeyboard \"on\"\n", f
);
372 if (!isempty(c
->x11_layout
))
373 fprintf(f
, " Option \"XkbLayout\" \"%s\"\n", c
->x11_layout
);
375 if (!isempty(c
->x11_model
))
376 fprintf(f
, " Option \"XkbModel\" \"%s\"\n", c
->x11_model
);
378 if (!isempty(c
->x11_variant
))
379 fprintf(f
, " Option \"XkbVariant\" \"%s\"\n", c
->x11_variant
);
381 if (!isempty(c
->x11_options
))
382 fprintf(f
, " Option \"XkbOptions\" \"%s\"\n", c
->x11_options
);
384 fputs_unlocked("EndSection\n", f
);
386 r
= fflush_sync_and_check(f
);
390 if (rename(temp_path
, "/etc/X11/xorg.conf.d/00-keyboard.conf") < 0) {
399 (void) unlink(temp_path
);
404 static int read_next_mapping(const char* filename
,
405 unsigned min_fields
, unsigned max_fields
,
406 FILE *f
, unsigned *n
, char ***a
) {
418 if (!fgets(line
, sizeof(line
), f
)) {
421 return errno
> 0 ? -errno
: -EIO
;
429 if (IN_SET(l
[0], 0, '#'))
432 r
= strv_split_extract(&b
, l
, WHITESPACE
, EXTRACT_QUOTES
);
436 length
= strv_length(b
);
437 if (length
< min_fields
|| length
> max_fields
) {
438 log_error("Invalid line %s:%u, ignoring.", filename
, *n
);
449 int vconsole_convert_to_x11(Context
*c
) {
453 map
= systemd_kbd_model_map();
455 if (isempty(c
->vc_keymap
)) {
457 !isempty(c
->x11_layout
) ||
458 !isempty(c
->x11_model
) ||
459 !isempty(c
->x11_variant
) ||
460 !isempty(c
->x11_options
);
464 _cleanup_fclose_
FILE *f
= NULL
;
467 f
= fopen(map
, "re");
472 _cleanup_strv_free_
char **a
= NULL
;
475 r
= read_next_mapping(map
, 5, UINT_MAX
, f
, &n
, &a
);
481 if (!streq(c
->vc_keymap
, a
[0]))
484 if (!streq_ptr(c
->x11_layout
, strnulldash(a
[1])) ||
485 !streq_ptr(c
->x11_model
, strnulldash(a
[2])) ||
486 !streq_ptr(c
->x11_variant
, strnulldash(a
[3])) ||
487 !streq_ptr(c
->x11_options
, strnulldash(a
[4]))) {
489 if (free_and_strdup(&c
->x11_layout
, strnulldash(a
[1])) < 0 ||
490 free_and_strdup(&c
->x11_model
, strnulldash(a
[2])) < 0 ||
491 free_and_strdup(&c
->x11_variant
, strnulldash(a
[3])) < 0 ||
492 free_and_strdup(&c
->x11_options
, strnulldash(a
[4])) < 0)
503 log_info("Changing X11 keyboard layout to '%s' model '%s' variant '%s' options '%s'",
504 strempty(c
->x11_layout
),
505 strempty(c
->x11_model
),
506 strempty(c
->x11_variant
),
507 strempty(c
->x11_options
));
508 else if (modified
< 0)
509 log_notice("X11 keyboard layout was not modified: no conversion found for \"%s\".",
512 log_debug("X11 keyboard layout did not need to be modified.");
517 int find_converted_keymap(const char *x11_layout
, const char *x11_variant
, char **new_keymap
) {
519 _cleanup_free_
char *n
;
522 n
= strjoin(x11_layout
, "-", x11_variant
);
524 n
= strdup(x11_layout
);
528 NULSTR_FOREACH(dir
, KBD_KEYMAP_DIRS
) {
529 _cleanup_free_
char *p
= NULL
, *pz
= NULL
;
532 p
= strjoin(dir
, "xkb/", n
, ".map");
533 pz
= strjoin(dir
, "xkb/", n
, ".map.gz");
537 uncompressed
= access(p
, F_OK
) == 0;
538 if (uncompressed
|| access(pz
, F_OK
) == 0) {
539 log_debug("Found converted keymap %s at %s",
540 n
, uncompressed
? p
: pz
);
551 int find_legacy_keymap(Context
*c
, char **new_keymap
) {
553 _cleanup_fclose_
FILE *f
= NULL
;
555 unsigned best_matching
= 0;
558 assert(!isempty(c
->x11_layout
));
560 map
= systemd_kbd_model_map();
562 f
= fopen(map
, "re");
567 _cleanup_strv_free_
char **a
= NULL
;
568 unsigned matching
= 0;
570 r
= read_next_mapping(map
, 5, UINT_MAX
, f
, &n
, &a
);
576 /* Determine how well matching this entry is */
577 if (streq(c
->x11_layout
, a
[1]))
578 /* If we got an exact match, this is best */
581 /* We have multiple X layouts, look for an
582 * entry that matches our key with everything
583 * but the first layout stripped off. */
584 if (startswith_comma(c
->x11_layout
, a
[1]))
589 /* If that didn't work, strip off the
590 * other layouts from the entry, too */
591 x
= strndupa(a
[1], strcspn(a
[1], ","));
592 if (startswith_comma(c
->x11_layout
, x
))
598 if (isempty(c
->x11_model
) || streq_ptr(c
->x11_model
, a
[2])) {
601 if (streq_ptr(c
->x11_variant
, a
[3])) {
604 if (streq_ptr(c
->x11_options
, a
[4]))
610 /* The best matching entry so far, then let's save that */
611 if (matching
>= MAX(best_matching
, 1u)) {
612 log_debug("Found legacy keymap %s with score %u",
615 if (matching
> best_matching
) {
616 best_matching
= matching
;
618 r
= free_and_strdup(new_keymap
, a
[0]);
625 if (best_matching
< 10 && c
->x11_layout
) {
626 /* The best match is only the first part of the X11
627 * keymap. Check if we have a converted map which
628 * matches just the first layout.
630 char *l
, *v
= NULL
, *converted
;
632 l
= strndupa(c
->x11_layout
, strcspn(c
->x11_layout
, ","));
634 v
= strndupa(c
->x11_variant
, strcspn(c
->x11_variant
, ","));
635 r
= find_converted_keymap(l
, v
, &converted
);
640 *new_keymap
= converted
;
644 return (bool) *new_keymap
;
647 int find_language_fallback(const char *lang
, char **language
) {
649 _cleanup_fclose_
FILE *f
= NULL
;
655 map
= systemd_language_fallback_map();
657 f
= fopen(map
, "re");
662 _cleanup_strv_free_
char **a
= NULL
;
665 r
= read_next_mapping(map
, 2, 2, f
, &n
, &a
);
669 if (streq(lang
, a
[0])) {
670 assert(strv_length(a
) == 2);
677 assert_not_reached("should not be here");
680 int x11_convert_to_vconsole(Context
*c
) {
681 bool modified
= false;
683 if (isempty(c
->x11_layout
)) {
685 !isempty(c
->vc_keymap
) ||
686 !isempty(c
->vc_keymap_toggle
);
688 context_free_vconsole(c
);
690 char *new_keymap
= NULL
;
693 r
= find_converted_keymap(c
->x11_layout
, c
->x11_variant
, &new_keymap
);
697 r
= find_legacy_keymap(c
, &new_keymap
);
702 /* We search for layout-variant match first, but then we also look
703 * for anything which matches just the layout. So it's accurate to say
704 * that we couldn't find anything which matches the layout. */
705 log_notice("No conversion to virtual console map found for \"%s\".",
708 if (!streq_ptr(c
->vc_keymap
, new_keymap
)) {
710 c
->vc_keymap
= new_keymap
;
711 c
->vc_keymap_toggle
= mfree(c
->vc_keymap_toggle
);
718 log_info("Changing virtual console keymap to '%s' toggle '%s'",
719 strempty(c
->vc_keymap
), strempty(c
->vc_keymap_toggle
));
721 log_debug("Virtual console keymap was not modified.");