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"))
204 free_and_replace(*p
, a
[2]);
208 } else if (!in_section
&& first_word(l
, "Section")) {
209 _cleanup_strv_free_
char **a
= NULL
;
211 r
= strv_split_extract(&a
, l
, WHITESPACE
, EXTRACT_QUOTES
);
215 if (strv_length(a
) == 2 && streq(a
[1], "InputClass"))
218 } else if (in_section
&& first_word(l
, "EndSection"))
225 int context_read_data(Context
*c
) {
228 r
= locale_read_data(c
);
229 q
= vconsole_read_data(c
);
230 p
= x11_read_data(c
);
232 return r
< 0 ? r
: q
< 0 ? q
: p
;
235 int locale_write_data(Context
*c
, char ***settings
) {
237 _cleanup_strv_free_
char **l
= NULL
;
239 /* Set values will be returned as strv in *settings on success. */
241 r
= load_env_file(NULL
, "/etc/locale.conf", NULL
, &l
);
242 if (r
< 0 && r
!= -ENOENT
)
245 for (p
= 0; p
< _VARIABLE_LC_MAX
; p
++) {
246 _cleanup_free_
char *t
= NULL
;
250 name
= locale_variable_to_string(p
);
253 if (isempty(c
->locale
[p
])) {
254 l
= strv_env_unset(l
, name
);
258 if (asprintf(&t
, "%s=%s", name
, c
->locale
[p
]) < 0)
261 u
= strv_env_set(l
, t
);
269 if (strv_isempty(l
)) {
270 if (unlink("/etc/locale.conf") < 0)
271 return errno
== ENOENT
? 0 : -errno
;
276 r
= write_env_file_label("/etc/locale.conf", l
);
285 int vconsole_write_data(Context
*c
) {
287 _cleanup_strv_free_
char **l
= NULL
;
289 r
= load_env_file(NULL
, "/etc/vconsole.conf", NULL
, &l
);
290 if (r
< 0 && r
!= -ENOENT
)
293 if (isempty(c
->vc_keymap
))
294 l
= strv_env_unset(l
, "KEYMAP");
296 _cleanup_free_
char *s
= NULL
;
299 s
= strappend("KEYMAP=", c
->vc_keymap
);
303 u
= strv_env_set(l
, s
);
311 if (isempty(c
->vc_keymap_toggle
))
312 l
= strv_env_unset(l
, "KEYMAP_TOGGLE");
314 _cleanup_free_
char *s
= NULL
;
317 s
= strappend("KEYMAP_TOGGLE=", c
->vc_keymap_toggle
);
321 u
= strv_env_set(l
, s
);
329 if (strv_isempty(l
)) {
330 if (unlink("/etc/vconsole.conf") < 0)
331 return errno
== ENOENT
? 0 : -errno
;
336 return write_env_file_label("/etc/vconsole.conf", l
);
339 int x11_write_data(Context
*c
) {
340 _cleanup_fclose_
FILE *f
= NULL
;
341 _cleanup_free_
char *temp_path
= NULL
;
344 if (isempty(c
->x11_layout
) &&
345 isempty(c
->x11_model
) &&
346 isempty(c
->x11_variant
) &&
347 isempty(c
->x11_options
)) {
349 if (unlink("/etc/X11/xorg.conf.d/00-keyboard.conf") < 0)
350 return errno
== ENOENT
? 0 : -errno
;
355 mkdir_p_label("/etc/X11/xorg.conf.d", 0755);
357 r
= fopen_temporary("/etc/X11/xorg.conf.d/00-keyboard.conf", &f
, &temp_path
);
361 fchmod(fileno(f
), 0644);
363 fputs_unlocked("# Written by systemd-localed(8), read by systemd-localed and Xorg. It's\n"
364 "# probably wise not to edit this file manually. Use localectl(1) to\n"
365 "# instruct systemd-localed to update it.\n"
366 "Section \"InputClass\"\n"
367 " Identifier \"system-keyboard\"\n"
368 " MatchIsKeyboard \"on\"\n", f
);
370 if (!isempty(c
->x11_layout
))
371 fprintf(f
, " Option \"XkbLayout\" \"%s\"\n", c
->x11_layout
);
373 if (!isempty(c
->x11_model
))
374 fprintf(f
, " Option \"XkbModel\" \"%s\"\n", c
->x11_model
);
376 if (!isempty(c
->x11_variant
))
377 fprintf(f
, " Option \"XkbVariant\" \"%s\"\n", c
->x11_variant
);
379 if (!isempty(c
->x11_options
))
380 fprintf(f
, " Option \"XkbOptions\" \"%s\"\n", c
->x11_options
);
382 fputs_unlocked("EndSection\n", f
);
384 r
= fflush_sync_and_check(f
);
388 if (rename(temp_path
, "/etc/X11/xorg.conf.d/00-keyboard.conf") < 0) {
397 (void) unlink(temp_path
);
402 static int read_next_mapping(const char* filename
,
403 unsigned min_fields
, unsigned max_fields
,
404 FILE *f
, unsigned *n
, char ***a
) {
416 if (!fgets(line
, sizeof(line
), f
)) {
419 return errno
> 0 ? -errno
: -EIO
;
427 if (IN_SET(l
[0], 0, '#'))
430 r
= strv_split_extract(&b
, l
, WHITESPACE
, EXTRACT_QUOTES
);
434 length
= strv_length(b
);
435 if (length
< min_fields
|| length
> max_fields
) {
436 log_error("Invalid line %s:%u, ignoring.", filename
, *n
);
447 int vconsole_convert_to_x11(Context
*c
) {
451 map
= systemd_kbd_model_map();
453 if (isempty(c
->vc_keymap
)) {
455 !isempty(c
->x11_layout
) ||
456 !isempty(c
->x11_model
) ||
457 !isempty(c
->x11_variant
) ||
458 !isempty(c
->x11_options
);
462 _cleanup_fclose_
FILE *f
= NULL
;
465 f
= fopen(map
, "re");
470 _cleanup_strv_free_
char **a
= NULL
;
473 r
= read_next_mapping(map
, 5, UINT_MAX
, f
, &n
, &a
);
479 if (!streq(c
->vc_keymap
, a
[0]))
482 if (!streq_ptr(c
->x11_layout
, strnulldash(a
[1])) ||
483 !streq_ptr(c
->x11_model
, strnulldash(a
[2])) ||
484 !streq_ptr(c
->x11_variant
, strnulldash(a
[3])) ||
485 !streq_ptr(c
->x11_options
, strnulldash(a
[4]))) {
487 if (free_and_strdup(&c
->x11_layout
, strnulldash(a
[1])) < 0 ||
488 free_and_strdup(&c
->x11_model
, strnulldash(a
[2])) < 0 ||
489 free_and_strdup(&c
->x11_variant
, strnulldash(a
[3])) < 0 ||
490 free_and_strdup(&c
->x11_options
, strnulldash(a
[4])) < 0)
501 log_info("Changing X11 keyboard layout to '%s' model '%s' variant '%s' options '%s'",
502 strempty(c
->x11_layout
),
503 strempty(c
->x11_model
),
504 strempty(c
->x11_variant
),
505 strempty(c
->x11_options
));
506 else if (modified
< 0)
507 log_notice("X11 keyboard layout was not modified: no conversion found for \"%s\".",
510 log_debug("X11 keyboard layout did not need to be modified.");
515 int find_converted_keymap(const char *x11_layout
, const char *x11_variant
, char **new_keymap
) {
517 _cleanup_free_
char *n
;
520 n
= strjoin(x11_layout
, "-", x11_variant
);
522 n
= strdup(x11_layout
);
526 NULSTR_FOREACH(dir
, KBD_KEYMAP_DIRS
) {
527 _cleanup_free_
char *p
= NULL
, *pz
= NULL
;
530 p
= strjoin(dir
, "xkb/", n
, ".map");
531 pz
= strjoin(dir
, "xkb/", n
, ".map.gz");
535 uncompressed
= access(p
, F_OK
) == 0;
536 if (uncompressed
|| access(pz
, F_OK
) == 0) {
537 log_debug("Found converted keymap %s at %s",
538 n
, uncompressed
? p
: pz
);
549 int find_legacy_keymap(Context
*c
, char **new_keymap
) {
551 _cleanup_fclose_
FILE *f
= NULL
;
553 unsigned best_matching
= 0;
556 assert(!isempty(c
->x11_layout
));
558 map
= systemd_kbd_model_map();
560 f
= fopen(map
, "re");
565 _cleanup_strv_free_
char **a
= NULL
;
566 unsigned matching
= 0;
568 r
= read_next_mapping(map
, 5, UINT_MAX
, f
, &n
, &a
);
574 /* Determine how well matching this entry is */
575 if (streq(c
->x11_layout
, a
[1]))
576 /* If we got an exact match, this is best */
579 /* We have multiple X layouts, look for an
580 * entry that matches our key with everything
581 * but the first layout stripped off. */
582 if (startswith_comma(c
->x11_layout
, a
[1]))
587 /* If that didn't work, strip off the
588 * other layouts from the entry, too */
589 x
= strndupa(a
[1], strcspn(a
[1], ","));
590 if (startswith_comma(c
->x11_layout
, x
))
596 if (isempty(c
->x11_model
) || streq_ptr(c
->x11_model
, a
[2])) {
599 if (streq_ptr(c
->x11_variant
, a
[3])) {
602 if (streq_ptr(c
->x11_options
, a
[4]))
608 /* The best matching entry so far, then let's save that */
609 if (matching
>= MAX(best_matching
, 1u)) {
610 log_debug("Found legacy keymap %s with score %u",
613 if (matching
> best_matching
) {
614 best_matching
= matching
;
616 r
= free_and_strdup(new_keymap
, a
[0]);
623 if (best_matching
< 10 && c
->x11_layout
) {
624 /* The best match is only the first part of the X11
625 * keymap. Check if we have a converted map which
626 * matches just the first layout.
628 char *l
, *v
= NULL
, *converted
;
630 l
= strndupa(c
->x11_layout
, strcspn(c
->x11_layout
, ","));
632 v
= strndupa(c
->x11_variant
, strcspn(c
->x11_variant
, ","));
633 r
= find_converted_keymap(l
, v
, &converted
);
638 *new_keymap
= converted
;
642 return (bool) *new_keymap
;
645 int find_language_fallback(const char *lang
, char **language
) {
647 _cleanup_fclose_
FILE *f
= NULL
;
653 map
= systemd_language_fallback_map();
655 f
= fopen(map
, "re");
660 _cleanup_strv_free_
char **a
= NULL
;
663 r
= read_next_mapping(map
, 2, 2, f
, &n
, &a
);
667 if (streq(lang
, a
[0])) {
668 assert(strv_length(a
) == 2);
675 assert_not_reached("should not be here");
678 int x11_convert_to_vconsole(Context
*c
) {
679 bool modified
= false;
681 if (isempty(c
->x11_layout
)) {
683 !isempty(c
->vc_keymap
) ||
684 !isempty(c
->vc_keymap_toggle
);
686 context_free_vconsole(c
);
688 char *new_keymap
= NULL
;
691 r
= find_converted_keymap(c
->x11_layout
, c
->x11_variant
, &new_keymap
);
695 r
= find_legacy_keymap(c
, &new_keymap
);
700 /* We search for layout-variant match first, but then we also look
701 * for anything which matches just the layout. So it's accurate to say
702 * that we couldn't find anything which matches the layout. */
703 log_notice("No conversion to virtual console map found for \"%s\".",
706 if (!streq_ptr(c
->vc_keymap
, new_keymap
)) {
708 c
->vc_keymap
= new_keymap
;
709 c
->vc_keymap_toggle
= mfree(c
->vc_keymap_toggle
);
716 log_info("Changing virtual console keymap to '%s' toggle '%s'",
717 strempty(c
->vc_keymap
), strempty(c
->vc_keymap_toggle
));
719 log_debug("Virtual console keymap was not modified.");