2 This file is part of systemd.
4 Copyright 2011 Lennart Poettering
5 Copyright 2013 Kay Sievers
7 systemd is free software; you can redistribute it and/or modify it
8 under the terms of the GNU Lesser General Public License as published by
9 the Free Software Foundation; either version 2.1 of the License, or
10 (at your option) any later version.
12 systemd is distributed in the hope that it will be useful, but
13 WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 Lesser General Public License for more details.
17 You should have received a copy of the GNU Lesser General Public License
18 along with systemd; If not, see <http://www.gnu.org/licenses/>.
28 #include "fileio-label.h"
30 #include "keymap-util.h"
31 #include "locale-util.h"
34 #include "string-util.h"
37 static bool startswith_comma(const char *s
, const char *prefix
) {
38 s
= startswith(s
, prefix
);
42 return *s
== ',' || *s
== '\0';
45 static const char* strnulldash(const char *s
) {
46 return isempty(s
) || streq(s
, "-") ? NULL
: s
;
49 static const char* systemd_kbd_model_map(void) {
52 s
= getenv("SYSTEMD_KBD_MODEL_MAP");
56 return SYSTEMD_KBD_MODEL_MAP
;
59 static const char* systemd_language_fallback_map(void) {
62 s
= getenv("SYSTEMD_LANGUAGE_FALLBACK_MAP");
66 return SYSTEMD_LANGUAGE_FALLBACK_MAP
;
69 static void context_free_x11(Context
*c
) {
70 c
->x11_layout
= mfree(c
->x11_layout
);
71 c
->x11_options
= mfree(c
->x11_options
);
72 c
->x11_model
= mfree(c
->x11_model
);
73 c
->x11_variant
= mfree(c
->x11_variant
);
76 static void context_free_vconsole(Context
*c
) {
77 c
->vc_keymap
= mfree(c
->vc_keymap
);
78 c
->vc_keymap_toggle
= mfree(c
->vc_keymap_toggle
);
81 static void context_free_locale(Context
*c
) {
84 for (p
= 0; p
< _VARIABLE_LC_MAX
; p
++)
85 c
->locale
[p
] = mfree(c
->locale
[p
]);
88 void context_free(Context
*c
) {
89 context_free_locale(c
);
91 context_free_vconsole(c
);
94 void locale_simplify(Context
*c
) {
97 for (p
= VARIABLE_LANG
+1; p
< _VARIABLE_LC_MAX
; p
++)
98 if (isempty(c
->locale
[p
]) || streq_ptr(c
->locale
[VARIABLE_LANG
], c
->locale
[p
]))
99 c
->locale
[p
] = mfree(c
->locale
[p
]);
102 static int locale_read_data(Context
*c
) {
105 context_free_locale(c
);
107 r
= parse_env_file("/etc/locale.conf", NEWLINE
,
108 "LANG", &c
->locale
[VARIABLE_LANG
],
109 "LANGUAGE", &c
->locale
[VARIABLE_LANGUAGE
],
110 "LC_CTYPE", &c
->locale
[VARIABLE_LC_CTYPE
],
111 "LC_NUMERIC", &c
->locale
[VARIABLE_LC_NUMERIC
],
112 "LC_TIME", &c
->locale
[VARIABLE_LC_TIME
],
113 "LC_COLLATE", &c
->locale
[VARIABLE_LC_COLLATE
],
114 "LC_MONETARY", &c
->locale
[VARIABLE_LC_MONETARY
],
115 "LC_MESSAGES", &c
->locale
[VARIABLE_LC_MESSAGES
],
116 "LC_PAPER", &c
->locale
[VARIABLE_LC_PAPER
],
117 "LC_NAME", &c
->locale
[VARIABLE_LC_NAME
],
118 "LC_ADDRESS", &c
->locale
[VARIABLE_LC_ADDRESS
],
119 "LC_TELEPHONE", &c
->locale
[VARIABLE_LC_TELEPHONE
],
120 "LC_MEASUREMENT", &c
->locale
[VARIABLE_LC_MEASUREMENT
],
121 "LC_IDENTIFICATION", &c
->locale
[VARIABLE_LC_IDENTIFICATION
],
127 /* Fill in what we got passed from systemd. */
128 for (p
= 0; p
< _VARIABLE_LC_MAX
; p
++) {
131 name
= locale_variable_to_string(p
);
134 r
= free_and_strdup(&c
->locale
[p
], empty_to_null(getenv(name
)));
146 static int vconsole_read_data(Context
*c
) {
149 context_free_vconsole(c
);
151 r
= parse_env_file("/etc/vconsole.conf", NEWLINE
,
152 "KEYMAP", &c
->vc_keymap
,
153 "KEYMAP_TOGGLE", &c
->vc_keymap_toggle
,
156 if (r
< 0 && r
!= -ENOENT
)
162 static int x11_read_data(Context
*c
) {
163 _cleanup_fclose_
FILE *f
;
165 bool in_section
= false;
170 f
= fopen("/etc/X11/xorg.conf.d/00-keyboard.conf", "re");
172 return errno
== ENOENT
? 0 : -errno
;
174 while (fgets(line
, sizeof(line
), f
)) {
180 if (l
[0] == 0 || l
[0] == '#')
183 if (in_section
&& first_word(l
, "Option")) {
184 _cleanup_strv_free_
char **a
= NULL
;
186 r
= strv_split_extract(&a
, l
, WHITESPACE
, EXTRACT_QUOTES
);
190 if (strv_length(a
) == 3) {
193 if (streq(a
[1], "XkbLayout"))
195 else if (streq(a
[1], "XkbModel"))
197 else if (streq(a
[1], "XkbVariant"))
199 else if (streq(a
[1], "XkbOptions"))
209 } else if (!in_section
&& first_word(l
, "Section")) {
210 _cleanup_strv_free_
char **a
= NULL
;
212 r
= strv_split_extract(&a
, l
, WHITESPACE
, EXTRACT_QUOTES
);
216 if (strv_length(a
) == 2 && streq(a
[1], "InputClass"))
219 } else if (in_section
&& first_word(l
, "EndSection"))
226 int context_read_data(Context
*c
) {
229 r
= locale_read_data(c
);
230 q
= vconsole_read_data(c
);
231 p
= x11_read_data(c
);
233 return r
< 0 ? r
: q
< 0 ? q
: p
;
236 int locale_write_data(Context
*c
, char ***settings
) {
238 _cleanup_strv_free_
char **l
= NULL
;
240 /* Set values will be returned as strv in *settings on success. */
242 r
= load_env_file(NULL
, "/etc/locale.conf", NULL
, &l
);
243 if (r
< 0 && r
!= -ENOENT
)
246 for (p
= 0; p
< _VARIABLE_LC_MAX
; p
++) {
247 _cleanup_free_
char *t
= NULL
;
251 name
= locale_variable_to_string(p
);
254 if (isempty(c
->locale
[p
])) {
255 l
= strv_env_unset(l
, name
);
259 if (asprintf(&t
, "%s=%s", name
, c
->locale
[p
]) < 0)
262 u
= strv_env_set(l
, t
);
270 if (strv_isempty(l
)) {
271 if (unlink("/etc/locale.conf") < 0)
272 return errno
== ENOENT
? 0 : -errno
;
277 r
= write_env_file_label("/etc/locale.conf", l
);
286 int vconsole_write_data(Context
*c
) {
288 _cleanup_strv_free_
char **l
= NULL
;
290 r
= load_env_file(NULL
, "/etc/vconsole.conf", NULL
, &l
);
291 if (r
< 0 && r
!= -ENOENT
)
294 if (isempty(c
->vc_keymap
))
295 l
= strv_env_unset(l
, "KEYMAP");
297 _cleanup_free_
char *s
= NULL
;
300 s
= strappend("KEYMAP=", c
->vc_keymap
);
304 u
= strv_env_set(l
, s
);
312 if (isempty(c
->vc_keymap_toggle
))
313 l
= strv_env_unset(l
, "KEYMAP_TOGGLE");
315 _cleanup_free_
char *s
= NULL
;
318 s
= strappend("KEYMAP_TOGGLE=", c
->vc_keymap_toggle
);
322 u
= strv_env_set(l
, s
);
330 if (strv_isempty(l
)) {
331 if (unlink("/etc/vconsole.conf") < 0)
332 return errno
== ENOENT
? 0 : -errno
;
337 return write_env_file_label("/etc/vconsole.conf", l
);
340 int x11_write_data(Context
*c
) {
341 _cleanup_fclose_
FILE *f
= NULL
;
342 _cleanup_free_
char *temp_path
= NULL
;
345 if (isempty(c
->x11_layout
) &&
346 isempty(c
->x11_model
) &&
347 isempty(c
->x11_variant
) &&
348 isempty(c
->x11_options
)) {
350 if (unlink("/etc/X11/xorg.conf.d/00-keyboard.conf") < 0)
351 return errno
== ENOENT
? 0 : -errno
;
356 mkdir_p_label("/etc/X11/xorg.conf.d", 0755);
358 r
= fopen_temporary("/etc/X11/xorg.conf.d/00-keyboard.conf", &f
, &temp_path
);
362 fchmod(fileno(f
), 0644);
364 fputs_unlocked("# Written by systemd-localed(8), read by systemd-localed and Xorg. It's\n"
365 "# probably wise not to edit this file manually. Use localectl(1) to\n"
366 "# instruct systemd-localed to update it.\n"
367 "Section \"InputClass\"\n"
368 " Identifier \"system-keyboard\"\n"
369 " MatchIsKeyboard \"on\"\n", f
);
371 if (!isempty(c
->x11_layout
))
372 fprintf(f
, " Option \"XkbLayout\" \"%s\"\n", c
->x11_layout
);
374 if (!isempty(c
->x11_model
))
375 fprintf(f
, " Option \"XkbModel\" \"%s\"\n", c
->x11_model
);
377 if (!isempty(c
->x11_variant
))
378 fprintf(f
, " Option \"XkbVariant\" \"%s\"\n", c
->x11_variant
);
380 if (!isempty(c
->x11_options
))
381 fprintf(f
, " Option \"XkbOptions\" \"%s\"\n", c
->x11_options
);
383 fputs_unlocked("EndSection\n", f
);
385 r
= fflush_sync_and_check(f
);
389 if (rename(temp_path
, "/etc/X11/xorg.conf.d/00-keyboard.conf") < 0) {
398 (void) unlink(temp_path
);
403 static int read_next_mapping(const char* filename
,
404 unsigned min_fields
, unsigned max_fields
,
405 FILE *f
, unsigned *n
, char ***a
) {
417 if (!fgets(line
, sizeof(line
), f
)) {
420 return errno
> 0 ? -errno
: -EIO
;
428 if (l
[0] == 0 || l
[0] == '#')
431 r
= strv_split_extract(&b
, l
, WHITESPACE
, EXTRACT_QUOTES
);
435 length
= strv_length(b
);
436 if (length
< min_fields
|| length
> max_fields
) {
437 log_error("Invalid line %s:%u, ignoring.", filename
, *n
);
448 int vconsole_convert_to_x11(Context
*c
) {
452 map
= systemd_kbd_model_map();
454 if (isempty(c
->vc_keymap
)) {
456 !isempty(c
->x11_layout
) ||
457 !isempty(c
->x11_model
) ||
458 !isempty(c
->x11_variant
) ||
459 !isempty(c
->x11_options
);
463 _cleanup_fclose_
FILE *f
= NULL
;
466 f
= fopen(map
, "re");
471 _cleanup_strv_free_
char **a
= NULL
;
474 r
= read_next_mapping(map
, 5, UINT_MAX
, f
, &n
, &a
);
480 if (!streq(c
->vc_keymap
, a
[0]))
483 if (!streq_ptr(c
->x11_layout
, strnulldash(a
[1])) ||
484 !streq_ptr(c
->x11_model
, strnulldash(a
[2])) ||
485 !streq_ptr(c
->x11_variant
, strnulldash(a
[3])) ||
486 !streq_ptr(c
->x11_options
, strnulldash(a
[4]))) {
488 if (free_and_strdup(&c
->x11_layout
, strnulldash(a
[1])) < 0 ||
489 free_and_strdup(&c
->x11_model
, strnulldash(a
[2])) < 0 ||
490 free_and_strdup(&c
->x11_variant
, strnulldash(a
[3])) < 0 ||
491 free_and_strdup(&c
->x11_options
, strnulldash(a
[4])) < 0)
502 log_info("Changing X11 keyboard layout to '%s' model '%s' variant '%s' options '%s'",
503 strempty(c
->x11_layout
),
504 strempty(c
->x11_model
),
505 strempty(c
->x11_variant
),
506 strempty(c
->x11_options
));
507 else if (modified
< 0)
508 log_notice("X11 keyboard layout was not modified: no conversion found for \"%s\".",
511 log_debug("X11 keyboard layout did not need to be modified.");
516 int find_converted_keymap(const char *x11_layout
, const char *x11_variant
, char **new_keymap
) {
518 _cleanup_free_
char *n
;
521 n
= strjoin(x11_layout
, "-", x11_variant
);
523 n
= strdup(x11_layout
);
527 NULSTR_FOREACH(dir
, KBD_KEYMAP_DIRS
) {
528 _cleanup_free_
char *p
= NULL
, *pz
= NULL
;
531 p
= strjoin(dir
, "xkb/", n
, ".map");
532 pz
= strjoin(dir
, "xkb/", n
, ".map.gz");
536 uncompressed
= access(p
, F_OK
) == 0;
537 if (uncompressed
|| access(pz
, F_OK
) == 0) {
538 log_debug("Found converted keymap %s at %s",
539 n
, uncompressed
? p
: pz
);
550 int find_legacy_keymap(Context
*c
, char **new_keymap
) {
552 _cleanup_fclose_
FILE *f
= NULL
;
554 unsigned best_matching
= 0;
557 assert(!isempty(c
->x11_layout
));
559 map
= systemd_kbd_model_map();
561 f
= fopen(map
, "re");
566 _cleanup_strv_free_
char **a
= NULL
;
567 unsigned matching
= 0;
569 r
= read_next_mapping(map
, 5, UINT_MAX
, f
, &n
, &a
);
575 /* Determine how well matching this entry is */
576 if (streq(c
->x11_layout
, a
[1]))
577 /* If we got an exact match, this is best */
580 /* We have multiple X layouts, look for an
581 * entry that matches our key with everything
582 * but the first layout stripped off. */
583 if (startswith_comma(c
->x11_layout
, a
[1]))
588 /* If that didn't work, strip off the
589 * other layouts from the entry, too */
590 x
= strndupa(a
[1], strcspn(a
[1], ","));
591 if (startswith_comma(c
->x11_layout
, x
))
597 if (isempty(c
->x11_model
) || streq_ptr(c
->x11_model
, a
[2])) {
600 if (streq_ptr(c
->x11_variant
, a
[3])) {
603 if (streq_ptr(c
->x11_options
, a
[4]))
609 /* The best matching entry so far, then let's save that */
610 if (matching
>= MAX(best_matching
, 1u)) {
611 log_debug("Found legacy keymap %s with score %u",
614 if (matching
> best_matching
) {
615 best_matching
= matching
;
617 r
= free_and_strdup(new_keymap
, a
[0]);
624 if (best_matching
< 10 && c
->x11_layout
) {
625 /* The best match is only the first part of the X11
626 * keymap. Check if we have a converted map which
627 * matches just the first layout.
629 char *l
, *v
= NULL
, *converted
;
631 l
= strndupa(c
->x11_layout
, strcspn(c
->x11_layout
, ","));
633 v
= strndupa(c
->x11_variant
, strcspn(c
->x11_variant
, ","));
634 r
= find_converted_keymap(l
, v
, &converted
);
639 *new_keymap
= converted
;
643 return (bool) *new_keymap
;
646 int find_language_fallback(const char *lang
, char **language
) {
648 _cleanup_fclose_
FILE *f
= NULL
;
654 map
= systemd_language_fallback_map();
656 f
= fopen(map
, "re");
661 _cleanup_strv_free_
char **a
= NULL
;
664 r
= read_next_mapping(map
, 2, 2, f
, &n
, &a
);
668 if (streq(lang
, a
[0])) {
669 assert(strv_length(a
) == 2);
676 assert_not_reached("should not be here");
679 int x11_convert_to_vconsole(Context
*c
) {
680 bool modified
= false;
682 if (isempty(c
->x11_layout
)) {
684 !isempty(c
->vc_keymap
) ||
685 !isempty(c
->vc_keymap_toggle
);
687 context_free_vconsole(c
);
689 char *new_keymap
= NULL
;
692 r
= find_converted_keymap(c
->x11_layout
, c
->x11_variant
, &new_keymap
);
696 r
= find_legacy_keymap(c
, &new_keymap
);
701 /* We search for layout-variant match first, but then we also look
702 * for anything which matches just the layout. So it's accurate to say
703 * that we couldn't find anything which matches the layout. */
704 log_notice("No conversion to virtual console map found for \"%s\".",
707 if (!streq_ptr(c
->vc_keymap
, new_keymap
)) {
709 c
->vc_keymap
= new_keymap
;
710 c
->vc_keymap_toggle
= mfree(c
->vc_keymap_toggle
);
717 log_info("Changing virtual console keymap to '%s' toggle '%s'",
718 strempty(c
->vc_keymap
), strempty(c
->vc_keymap_toggle
));
720 log_debug("Virtual console keymap was not modified.");