1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
4 This file is part of systemd.
6 Copyright 2011 Lennart Poettering
7 Copyright 2013 Kay Sievers
9 systemd is free software; you can redistribute it and/or modify it
10 under the terms of the GNU Lesser General Public License as published by
11 the Free Software Foundation; either version 2.1 of the License, or
12 (at your option) any later version.
14 systemd is distributed in the hope that it will be useful, but
15 WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 Lesser General Public License for more details.
19 You should have received a copy of the GNU Lesser General Public License
20 along with systemd; If not, see <http://www.gnu.org/licenses/>.
35 #include "fileio-label.h"
38 #include "bus-error.h"
39 #include "bus-message.h"
40 #include "event-util.h"
41 #include "locale-util.h"
42 #include "selinux-util.h"
45 #include <xkbcommon/xkbcommon.h>
49 /* We don't list LC_ALL here on purpose. People should be
50 * using LANG instead. */
63 LOCALE_LC_MEASUREMENT,
64 LOCALE_LC_IDENTIFICATION,
68 static const char * const names[_LOCALE_MAX] = {
69 [LOCALE_LANG] = "LANG",
70 [LOCALE_LANGUAGE] = "LANGUAGE",
71 [LOCALE_LC_CTYPE] = "LC_CTYPE",
72 [LOCALE_LC_NUMERIC] = "LC_NUMERIC",
73 [LOCALE_LC_TIME] = "LC_TIME",
74 [LOCALE_LC_COLLATE] = "LC_COLLATE",
75 [LOCALE_LC_MONETARY] = "LC_MONETARY",
76 [LOCALE_LC_MESSAGES] = "LC_MESSAGES",
77 [LOCALE_LC_PAPER] = "LC_PAPER",
78 [LOCALE_LC_NAME] = "LC_NAME",
79 [LOCALE_LC_ADDRESS] = "LC_ADDRESS",
80 [LOCALE_LC_TELEPHONE] = "LC_TELEPHONE",
81 [LOCALE_LC_MEASUREMENT] = "LC_MEASUREMENT",
82 [LOCALE_LC_IDENTIFICATION] = "LC_IDENTIFICATION"
85 typedef struct Context {
86 char *locale[_LOCALE_MAX];
94 char *vc_keymap_toggle;
96 Hashmap *polkit_registry;
99 static const char* nonempty(const char *s) {
100 return isempty(s) ? NULL : s;
103 static void free_and_replace(char **s, char *v) {
108 static bool startswith_comma(const char *s, const char *prefix) {
111 return s && (t = startswith(s, prefix)) && (*t == ',');
114 static void context_free_x11(Context *c) {
115 free_and_replace(&c->x11_layout, NULL);
116 free_and_replace(&c->x11_model, NULL);
117 free_and_replace(&c->x11_variant, NULL);
118 free_and_replace(&c->x11_options, NULL);
121 static void context_free_vconsole(Context *c) {
122 free_and_replace(&c->vc_keymap, NULL);
123 free_and_replace(&c->vc_keymap_toggle, NULL);
126 static void context_free_locale(Context *c) {
129 for (p = 0; p < _LOCALE_MAX; p++)
130 free_and_replace(&c->locale[p], NULL);
133 static void context_free(Context *c) {
134 context_free_locale(c);
136 context_free_vconsole(c);
138 bus_verify_polkit_async_registry_free(c->polkit_registry);
141 static void locale_simplify(Context *c) {
144 for (p = LOCALE_LANG+1; p < _LOCALE_MAX; p++)
145 if (isempty(c->locale[p]) || streq_ptr(c->locale[LOCALE_LANG], c->locale[p]))
146 free_and_replace(&c->locale[p], NULL);
149 static int locale_read_data(Context *c) {
152 context_free_locale(c);
154 r = parse_env_file("/etc/locale.conf", NEWLINE,
155 "LANG", &c->locale[LOCALE_LANG],
156 "LANGUAGE", &c->locale[LOCALE_LANGUAGE],
157 "LC_CTYPE", &c->locale[LOCALE_LC_CTYPE],
158 "LC_NUMERIC", &c->locale[LOCALE_LC_NUMERIC],
159 "LC_TIME", &c->locale[LOCALE_LC_TIME],
160 "LC_COLLATE", &c->locale[LOCALE_LC_COLLATE],
161 "LC_MONETARY", &c->locale[LOCALE_LC_MONETARY],
162 "LC_MESSAGES", &c->locale[LOCALE_LC_MESSAGES],
163 "LC_PAPER", &c->locale[LOCALE_LC_PAPER],
164 "LC_NAME", &c->locale[LOCALE_LC_NAME],
165 "LC_ADDRESS", &c->locale[LOCALE_LC_ADDRESS],
166 "LC_TELEPHONE", &c->locale[LOCALE_LC_TELEPHONE],
167 "LC_MEASUREMENT", &c->locale[LOCALE_LC_MEASUREMENT],
168 "LC_IDENTIFICATION", &c->locale[LOCALE_LC_IDENTIFICATION],
174 /* Fill in what we got passed from systemd. */
175 for (p = 0; p < _LOCALE_MAX; p++) {
178 r = free_and_strdup(&c->locale[p],
179 nonempty(getenv(names[p])));
191 static int vconsole_read_data(Context *c) {
194 context_free_vconsole(c);
196 r = parse_env_file("/etc/vconsole.conf", NEWLINE,
197 "KEYMAP", &c->vc_keymap,
198 "KEYMAP_TOGGLE", &c->vc_keymap_toggle,
201 if (r < 0 && r != -ENOENT)
207 static int x11_read_data(Context *c) {
208 _cleanup_fclose_ FILE *f;
210 bool in_section = false;
215 f = fopen("/etc/X11/xorg.conf.d/00-keyboard.conf", "re");
217 return errno == ENOENT ? 0 : -errno;
219 while (fgets(line, sizeof(line), f)) {
225 if (l[0] == 0 || l[0] == '#')
228 if (in_section && first_word(l, "Option")) {
229 _cleanup_strv_free_ char **a = NULL;
231 r = strv_split_quoted(&a, l, false);
235 if (strv_length(a) == 3) {
236 if (streq(a[1], "XkbLayout")) {
237 free_and_replace(&c->x11_layout, a[2]);
239 } else if (streq(a[1], "XkbModel")) {
240 free_and_replace(&c->x11_model, a[2]);
242 } else if (streq(a[1], "XkbVariant")) {
243 free_and_replace(&c->x11_variant, a[2]);
245 } else if (streq(a[1], "XkbOptions")) {
246 free_and_replace(&c->x11_options, a[2]);
251 } else if (!in_section && first_word(l, "Section")) {
252 _cleanup_strv_free_ char **a = NULL;
254 r = strv_split_quoted(&a, l, false);
258 if (strv_length(a) == 2 && streq(a[1], "InputClass"))
261 } else if (in_section && first_word(l, "EndSection"))
268 static int context_read_data(Context *c) {
271 r = locale_read_data(c);
272 q = vconsole_read_data(c);
273 p = x11_read_data(c);
275 return r < 0 ? r : q < 0 ? q : p;
278 static int locale_write_data(Context *c, char ***settings) {
280 _cleanup_strv_free_ char **l = NULL;
282 /* Set values will be returned as strv in *settings on success. */
284 r = load_env_file(NULL, "/etc/locale.conf", NULL, &l);
285 if (r < 0 && r != -ENOENT)
288 for (p = 0; p < _LOCALE_MAX; p++) {
289 _cleanup_free_ char *t = NULL;
294 if (isempty(c->locale[p])) {
295 l = strv_env_unset(l, names[p]);
299 if (asprintf(&t, "%s=%s", names[p], c->locale[p]) < 0)
302 u = strv_env_set(l, t);
310 if (strv_isempty(l)) {
311 if (unlink("/etc/locale.conf") < 0)
312 return errno == ENOENT ? 0 : -errno;
317 r = write_env_file_label("/etc/locale.conf", l);
326 static int locale_update_system_manager(Context *c, sd_bus *bus) {
327 _cleanup_free_ char **l_unset = NULL;
328 _cleanup_strv_free_ char **l_set = NULL;
329 _cleanup_bus_message_unref_ sd_bus_message *m = NULL;
330 sd_bus_error error = SD_BUS_ERROR_NULL;
331 unsigned c_set, c_unset, p;
336 l_unset = new0(char*, _LOCALE_MAX);
340 l_set = new0(char*, _LOCALE_MAX);
344 for (p = 0, c_set = 0, c_unset = 0; p < _LOCALE_MAX; p++) {
347 if (isempty(c->locale[p]))
348 l_unset[c_set++] = (char*) names[p];
352 if (asprintf(&s, "%s=%s", names[p], c->locale[p]) < 0)
355 l_set[c_unset++] = s;
359 assert(c_set + c_unset == _LOCALE_MAX);
360 r = sd_bus_message_new_method_call(bus, &m,
361 "org.freedesktop.systemd1",
362 "/org/freedesktop/systemd1",
363 "org.freedesktop.systemd1.Manager",
364 "UnsetAndSetEnvironment");
368 r = sd_bus_message_append_strv(m, l_unset);
372 r = sd_bus_message_append_strv(m, l_set);
376 r = sd_bus_call(bus, m, 0, &error, NULL);
378 log_error_errno(r, "Failed to update the manager environment: %m");
383 static int vconsole_write_data(Context *c) {
385 _cleanup_strv_free_ char **l = NULL;
387 r = load_env_file(NULL, "/etc/vconsole.conf", NULL, &l);
388 if (r < 0 && r != -ENOENT)
391 if (isempty(c->vc_keymap))
392 l = strv_env_unset(l, "KEYMAP");
394 _cleanup_free_ char *s = NULL;
397 s = strappend("KEYMAP=", c->vc_keymap);
401 u = strv_env_set(l, s);
409 if (isempty(c->vc_keymap_toggle))
410 l = strv_env_unset(l, "KEYMAP_TOGGLE");
412 _cleanup_free_ char *s = NULL;
415 s = strappend("KEYMAP_TOGGLE=", c->vc_keymap_toggle);
419 u = strv_env_set(l, s);
427 if (strv_isempty(l)) {
428 if (unlink("/etc/vconsole.conf") < 0)
429 return errno == ENOENT ? 0 : -errno;
434 return write_env_file_label("/etc/vconsole.conf", l);
437 static int x11_write_data(Context *c) {
438 _cleanup_fclose_ FILE *f = NULL;
439 _cleanup_free_ char *temp_path = NULL;
442 if (isempty(c->x11_layout) &&
443 isempty(c->x11_model) &&
444 isempty(c->x11_variant) &&
445 isempty(c->x11_options)) {
447 if (unlink("/etc/X11/xorg.conf.d/00-keyboard.conf") < 0)
448 return errno == ENOENT ? 0 : -errno;
453 mkdir_p_label("/etc/X11/xorg.conf.d", 0755);
455 r = fopen_temporary("/etc/X11/xorg.conf.d/00-keyboard.conf", &f, &temp_path);
459 fchmod(fileno(f), 0644);
461 fputs("# Read and parsed by systemd-localed. It's probably wise not to edit this file\n"
462 "# manually too freely.\n"
463 "Section \"InputClass\"\n"
464 " Identifier \"system-keyboard\"\n"
465 " MatchIsKeyboard \"on\"\n", f);
467 if (!isempty(c->x11_layout))
468 fprintf(f, " Option \"XkbLayout\" \"%s\"\n", c->x11_layout);
470 if (!isempty(c->x11_model))
471 fprintf(f, " Option \"XkbModel\" \"%s\"\n", c->x11_model);
473 if (!isempty(c->x11_variant))
474 fprintf(f, " Option \"XkbVariant\" \"%s\"\n", c->x11_variant);
476 if (!isempty(c->x11_options))
477 fprintf(f, " Option \"XkbOptions\" \"%s\"\n", c->x11_options);
479 fputs("EndSection\n", f);
482 if (ferror(f) || rename(temp_path, "/etc/X11/xorg.conf.d/00-keyboard.conf") < 0) {
484 unlink("/etc/X11/xorg.conf.d/00-keyboard.conf");
491 static int vconsole_reload(sd_bus *bus) {
492 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
497 r = sd_bus_call_method(bus,
498 "org.freedesktop.systemd1",
499 "/org/freedesktop/systemd1",
500 "org.freedesktop.systemd1.Manager",
504 "ss", "systemd-vconsole-setup.service", "replace");
507 log_error("Failed to issue method call: %s", bus_error_message(&error, -r));
511 static const char* strnulldash(const char *s) {
512 return isempty(s) || streq(s, "-") ? NULL : s;
515 static int read_next_mapping(const char* filename,
516 unsigned min_fields, unsigned max_fields,
517 FILE *f, unsigned *n, char ***a) {
529 if (!fgets(line, sizeof(line), f)) {
532 return errno ? -errno : -EIO;
540 if (l[0] == 0 || l[0] == '#')
543 r = strv_split_quoted(&b, l, false);
547 length = strv_length(b);
548 if (length < min_fields || length > max_fields) {
549 log_error("Invalid line %s:%u, ignoring.", filename, *n);
560 static int vconsole_convert_to_x11(Context *c, sd_bus *bus) {
561 bool modified = false;
565 if (isempty(c->vc_keymap)) {
568 !isempty(c->x11_layout) ||
569 !isempty(c->x11_model) ||
570 !isempty(c->x11_variant) ||
571 !isempty(c->x11_options);
575 _cleanup_fclose_ FILE *f = NULL;
578 f = fopen(SYSTEMD_KBD_MODEL_MAP, "re");
583 _cleanup_strv_free_ char **a = NULL;
586 r = read_next_mapping(SYSTEMD_KBD_MODEL_MAP, 5, UINT_MAX, f, &n, &a);
592 if (!streq(c->vc_keymap, a[0]))
595 if (!streq_ptr(c->x11_layout, strnulldash(a[1])) ||
596 !streq_ptr(c->x11_model, strnulldash(a[2])) ||
597 !streq_ptr(c->x11_variant, strnulldash(a[3])) ||
598 !streq_ptr(c->x11_options, strnulldash(a[4]))) {
600 if (free_and_strdup(&c->x11_layout, strnulldash(a[1])) < 0 ||
601 free_and_strdup(&c->x11_model, strnulldash(a[2])) < 0 ||
602 free_and_strdup(&c->x11_variant, strnulldash(a[3])) < 0 ||
603 free_and_strdup(&c->x11_options, strnulldash(a[4])) < 0)
616 r = x11_write_data(c);
618 return log_error_errno(r, "Failed to set X11 keyboard layout: %m");
620 log_info("Changed X11 keyboard layout to '%s' model '%s' variant '%s' options '%s'",
621 strempty(c->x11_layout),
622 strempty(c->x11_model),
623 strempty(c->x11_variant),
624 strempty(c->x11_options));
626 sd_bus_emit_properties_changed(bus,
627 "/org/freedesktop/locale1",
628 "org.freedesktop.locale1",
629 "X11Layout", "X11Model", "X11Variant", "X11Options", NULL);
631 log_debug("X11 keyboard layout was not modified.");
636 static int find_converted_keymap(const char *x11_layout, const char *x11_variant, char **new_keymap) {
638 _cleanup_free_ char *n;
641 n = strjoin(x11_layout, "-", x11_variant, NULL);
643 n = strdup(x11_layout);
647 NULSTR_FOREACH(dir, KBD_KEYMAP_DIRS) {
648 _cleanup_free_ char *p = NULL, *pz = NULL;
651 p = strjoin(dir, "xkb/", n, ".map", NULL);
652 pz = strjoin(dir, "xkb/", n, ".map.gz", NULL);
656 uncompressed = access(p, F_OK) == 0;
657 if (uncompressed || access(pz, F_OK) == 0) {
658 log_debug("Found converted keymap %s at %s",
659 n, uncompressed ? p : pz);
670 static int find_legacy_keymap(Context *c, char **new_keymap) {
671 _cleanup_fclose_ FILE *f;
673 unsigned best_matching = 0;
676 f = fopen(SYSTEMD_KBD_MODEL_MAP, "re");
681 _cleanup_strv_free_ char **a = NULL;
682 unsigned matching = 0;
684 r = read_next_mapping(SYSTEMD_KBD_MODEL_MAP, 5, UINT_MAX, f, &n, &a);
690 /* Determine how well matching this entry is */
691 if (streq_ptr(c->x11_layout, a[1]))
692 /* If we got an exact match, this is best */
695 /* We have multiple X layouts, look for an
696 * entry that matches our key with everything
697 * but the first layout stripped off. */
698 if (startswith_comma(c->x11_layout, a[1]))
703 /* If that didn't work, strip off the
704 * other layouts from the entry, too */
705 x = strndupa(a[1], strcspn(a[1], ","));
706 if (startswith_comma(c->x11_layout, x))
712 if (isempty(c->x11_model) || streq_ptr(c->x11_model, a[2])) {
715 if (streq_ptr(c->x11_variant, a[3])) {
718 if (streq_ptr(c->x11_options, a[4]))
724 /* The best matching entry so far, then let's save that */
725 if (matching >= MAX(best_matching, 1u)) {
726 log_debug("Found legacy keymap %s with score %u",
729 if (matching > best_matching) {
730 best_matching = matching;
732 r = free_and_strdup(new_keymap, a[0]);
739 if (best_matching < 10 && c->x11_layout) {
740 /* The best match is only the first part of the X11
741 * keymap. Check if we have a converted map which
742 * matches just the first layout.
744 char *l, *v = NULL, *converted;
746 l = strndupa(c->x11_layout, strcspn(c->x11_layout, ","));
748 v = strndupa(c->x11_variant, strcspn(c->x11_variant, ","));
749 r = find_converted_keymap(l, v, &converted);
753 free_and_replace(new_keymap, converted);
759 static int find_language_fallback(const char *lang, char **language) {
760 _cleanup_fclose_ FILE *f = NULL;
765 f = fopen(SYSTEMD_LANGUAGE_FALLBACK_MAP, "re");
770 _cleanup_strv_free_ char **a = NULL;
773 r = read_next_mapping(SYSTEMD_LANGUAGE_FALLBACK_MAP, 2, 2, f, &n, &a);
777 if (streq(lang, a[0])) {
778 assert(strv_length(a) == 2);
785 assert_not_reached("should not be here");
788 static int x11_convert_to_vconsole(Context *c, sd_bus *bus) {
789 bool modified = false;
794 if (isempty(c->x11_layout)) {
797 !isempty(c->vc_keymap) ||
798 !isempty(c->vc_keymap_toggle);
802 char *new_keymap = NULL;
804 r = find_converted_keymap(c->x11_layout, c->x11_variant, &new_keymap);
808 r = find_legacy_keymap(c, &new_keymap);
813 if (!streq_ptr(c->vc_keymap, new_keymap)) {
814 free_and_replace(&c->vc_keymap, new_keymap);
815 free_and_replace(&c->vc_keymap_toggle, NULL);
822 r = vconsole_write_data(c);
824 log_error_errno(r, "Failed to set virtual console keymap: %m");
826 log_info("Changed virtual console keymap to '%s' toggle '%s'",
827 strempty(c->vc_keymap), strempty(c->vc_keymap_toggle));
829 sd_bus_emit_properties_changed(bus,
830 "/org/freedesktop/locale1",
831 "org.freedesktop.locale1",
832 "VConsoleKeymap", "VConsoleKeymapToggle", NULL);
834 return vconsole_reload(bus);
836 log_debug("Virtual console keymap was not modified.");
841 static int property_get_locale(
844 const char *interface,
845 const char *property,
846 sd_bus_message *reply,
848 sd_bus_error *error) {
850 Context *c = userdata;
851 _cleanup_strv_free_ char **l = NULL;
854 l = new0(char*, _LOCALE_MAX+1);
858 for (p = 0, q = 0; p < _LOCALE_MAX; p++) {
861 if (isempty(c->locale[p]))
864 if (asprintf(&t, "%s=%s", names[p], c->locale[p]) < 0)
870 return sd_bus_message_append_strv(reply, l);
873 static int method_set_locale(sd_bus *bus, sd_bus_message *m, void *userdata, sd_bus_error *error) {
874 Context *c = userdata;
875 _cleanup_strv_free_ char **l = NULL;
877 const char *lang = NULL;
879 bool modified = false;
880 bool have[_LOCALE_MAX] = {};
884 r = bus_message_read_strv_extend(m, &l);
888 r = sd_bus_message_read_basic(m, 'b', &interactive);
892 /* Check whether a variable changed and if it is valid */
896 for (p = 0; p < _LOCALE_MAX; p++) {
899 k = strlen(names[p]);
900 if (startswith(*i, names[p]) &&
902 locale_is_valid((*i) + k + 1)) {
906 if (p == LOCALE_LANG)
909 if (!streq_ptr(*i + k + 1, c->locale[p]))
917 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid Locale data.");
920 /* If LANG was specified, but not LANGUAGE, check if we should
921 * set it based on the language fallback table. */
922 if (have[LOCALE_LANG] && !have[LOCALE_LANGUAGE]) {
923 _cleanup_free_ char *language = NULL;
927 (void) find_language_fallback(lang, &language);
929 log_debug("Converted LANG=%s to LANGUAGE=%s", lang, language);
930 if (!streq_ptr(language, c->locale[LOCALE_LANGUAGE])) {
931 r = strv_extendf(&l, "LANGUAGE=%s", language);
935 have[LOCALE_LANGUAGE] = true;
941 /* Check whether a variable is unset */
943 for (p = 0; p < _LOCALE_MAX; p++)
944 if (!isempty(c->locale[p]) && !have[p]) {
950 _cleanup_strv_free_ char **settings = NULL;
952 r = bus_verify_polkit_async(
955 "org.freedesktop.locale1.set-locale",
963 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
966 for (p = 0; p < _LOCALE_MAX; p++) {
969 k = strlen(names[p]);
970 if (startswith(*i, names[p]) && (*i)[k] == '=') {
971 r = free_and_strdup(&c->locale[p], *i + k + 1);
978 for (p = 0; p < _LOCALE_MAX; p++) {
982 free_and_replace(&c->locale[p], NULL);
987 r = locale_write_data(c, &settings);
989 log_error_errno(r, "Failed to set locale: %m");
990 return sd_bus_error_set_errnof(error, r, "Failed to set locale: %s", strerror(-r));
993 locale_update_system_manager(c, bus);
996 _cleanup_free_ char *line;
998 line = strv_join(settings, ", ");
999 log_info("Changed locale to %s.", strnull(line));
1001 log_info("Changed locale to unset.");
1003 sd_bus_emit_properties_changed(bus,
1004 "/org/freedesktop/locale1",
1005 "org.freedesktop.locale1",
1008 log_debug("Locale settings were not modified.");
1011 return sd_bus_reply_method_return(m, NULL);
1014 static int method_set_vc_keyboard(sd_bus *bus, sd_bus_message *m, void *userdata, sd_bus_error *error) {
1015 Context *c = userdata;
1016 const char *keymap, *keymap_toggle;
1017 int convert, interactive;
1020 r = sd_bus_message_read(m, "ssbb", &keymap, &keymap_toggle, &convert, &interactive);
1024 if (isempty(keymap))
1027 if (isempty(keymap_toggle))
1028 keymap_toggle = NULL;
1030 if (!streq_ptr(keymap, c->vc_keymap) ||
1031 !streq_ptr(keymap_toggle, c->vc_keymap_toggle)) {
1033 if ((keymap && (!filename_is_valid(keymap) || !string_is_safe(keymap))) ||
1034 (keymap_toggle && (!filename_is_valid(keymap_toggle) || !string_is_safe(keymap_toggle))))
1035 return sd_bus_error_set_errnof(error, -EINVAL, "Received invalid keymap data");
1037 r = bus_verify_polkit_async(
1040 "org.freedesktop.locale1.set-keyboard",
1043 &c->polkit_registry,
1048 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
1050 if (free_and_strdup(&c->vc_keymap, keymap) < 0 ||
1051 free_and_strdup(&c->vc_keymap_toggle, keymap_toggle) < 0)
1054 r = vconsole_write_data(c);
1056 log_error_errno(r, "Failed to set virtual console keymap: %m");
1057 return sd_bus_error_set_errnof(error, r, "Failed to set virtual console keymap: %s", strerror(-r));
1060 log_info("Changed virtual console keymap to '%s' toggle '%s'",
1061 strempty(c->vc_keymap), strempty(c->vc_keymap_toggle));
1063 r = vconsole_reload(bus);
1065 log_error_errno(r, "Failed to request keymap reload: %m");
1067 sd_bus_emit_properties_changed(bus,
1068 "/org/freedesktop/locale1",
1069 "org.freedesktop.locale1",
1070 "VConsoleKeymap", "VConsoleKeymapToggle", NULL);
1073 r = vconsole_convert_to_x11(c, bus);
1075 log_error_errno(r, "Failed to convert keymap data: %m");
1079 return sd_bus_reply_method_return(m, NULL);
1082 #ifdef HAVE_XKBCOMMON
1083 static void log_xkb(struct xkb_context *ctx, enum xkb_log_level lvl, const char *format, va_list args) {
1086 fmt = strjoina("libxkbcommon: ", format);
1087 log_internalv(LOG_DEBUG, 0, __FILE__, __LINE__, __func__, fmt, args);
1090 static int verify_xkb_rmlvo(const char *model, const char *layout, const char *variant, const char *options) {
1091 const struct xkb_rule_names rmlvo = {
1097 struct xkb_context *ctx = NULL;
1098 struct xkb_keymap *km = NULL;
1101 /* compile keymap from RMLVO information to check out its validity */
1103 ctx = xkb_context_new(XKB_CONTEXT_NO_ENVIRONMENT_NAMES);
1109 xkb_context_set_log_fn(ctx, log_xkb);
1111 km = xkb_keymap_new_from_names(ctx, &rmlvo, XKB_KEYMAP_COMPILE_NO_FLAGS);
1120 xkb_keymap_unref(km);
1121 xkb_context_unref(ctx);
1125 static int verify_xkb_rmlvo(const char *model, const char *layout, const char *variant, const char *options) {
1130 static int method_set_x11_keyboard(sd_bus *bus, sd_bus_message *m, void *userdata, sd_bus_error *error) {
1131 Context *c = userdata;
1132 const char *layout, *model, *variant, *options;
1133 int convert, interactive;
1136 r = sd_bus_message_read(m, "ssssbb", &layout, &model, &variant, &options, &convert, &interactive);
1140 if (isempty(layout))
1146 if (isempty(variant))
1149 if (isempty(options))
1152 if (!streq_ptr(layout, c->x11_layout) ||
1153 !streq_ptr(model, c->x11_model) ||
1154 !streq_ptr(variant, c->x11_variant) ||
1155 !streq_ptr(options, c->x11_options)) {
1157 if ((layout && !string_is_safe(layout)) ||
1158 (model && !string_is_safe(model)) ||
1159 (variant && !string_is_safe(variant)) ||
1160 (options && !string_is_safe(options)))
1161 return sd_bus_error_set_errnof(error, -EINVAL, "Received invalid keyboard data");
1163 r = bus_verify_polkit_async(
1166 "org.freedesktop.locale1.set-keyboard",
1169 &c->polkit_registry,
1174 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
1176 r = verify_xkb_rmlvo(model, layout, variant, options);
1178 log_error_errno(r, "Cannot compile XKB keymap for new x11 keyboard layout ('%s' / '%s' / '%s' / '%s'): %m",
1179 strempty(model), strempty(layout), strempty(variant), strempty(options));
1180 return sd_bus_error_set(error, SD_BUS_ERROR_INVALID_ARGS, "Cannot compile XKB keymap, refusing");
1183 if (free_and_strdup(&c->x11_layout, layout) < 0 ||
1184 free_and_strdup(&c->x11_model, model) < 0 ||
1185 free_and_strdup(&c->x11_variant, variant) < 0 ||
1186 free_and_strdup(&c->x11_options, options) < 0)
1189 r = x11_write_data(c);
1191 log_error_errno(r, "Failed to set X11 keyboard layout: %m");
1192 return sd_bus_error_set_errnof(error, r, "Failed to set X11 keyboard layout: %s", strerror(-r));
1195 log_info("Changed X11 keyboard layout to '%s' model '%s' variant '%s' options '%s'",
1196 strempty(c->x11_layout),
1197 strempty(c->x11_model),
1198 strempty(c->x11_variant),
1199 strempty(c->x11_options));
1201 sd_bus_emit_properties_changed(bus,
1202 "/org/freedesktop/locale1",
1203 "org.freedesktop.locale1",
1204 "X11Layout", "X11Model", "X11Variant", "X11Options", NULL);
1207 r = x11_convert_to_vconsole(c, bus);
1209 log_error_errno(r, "Failed to convert keymap data: %m");
1213 return sd_bus_reply_method_return(m, NULL);
1216 static const sd_bus_vtable locale_vtable[] = {
1217 SD_BUS_VTABLE_START(0),
1218 SD_BUS_PROPERTY("Locale", "as", property_get_locale, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
1219 SD_BUS_PROPERTY("X11Layout", "s", NULL, offsetof(Context, x11_layout), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
1220 SD_BUS_PROPERTY("X11Model", "s", NULL, offsetof(Context, x11_model), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
1221 SD_BUS_PROPERTY("X11Variant", "s", NULL, offsetof(Context, x11_variant), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
1222 SD_BUS_PROPERTY("X11Options", "s", NULL, offsetof(Context, x11_options), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
1223 SD_BUS_PROPERTY("VConsoleKeymap", "s", NULL, offsetof(Context, vc_keymap), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
1224 SD_BUS_PROPERTY("VConsoleKeymapToggle", "s", NULL, offsetof(Context, vc_keymap_toggle), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
1225 SD_BUS_METHOD("SetLocale", "asb", NULL, method_set_locale, SD_BUS_VTABLE_UNPRIVILEGED),
1226 SD_BUS_METHOD("SetVConsoleKeyboard", "ssbb", NULL, method_set_vc_keyboard, SD_BUS_VTABLE_UNPRIVILEGED),
1227 SD_BUS_METHOD("SetX11Keyboard", "ssssbb", NULL, method_set_x11_keyboard, SD_BUS_VTABLE_UNPRIVILEGED),
1231 static int connect_bus(Context *c, sd_event *event, sd_bus **_bus) {
1232 _cleanup_bus_close_unref_ sd_bus *bus = NULL;
1239 r = sd_bus_default_system(&bus);
1241 return log_error_errno(r, "Failed to get system bus connection: %m");
1243 r = sd_bus_add_object_vtable(bus, NULL, "/org/freedesktop/locale1", "org.freedesktop.locale1", locale_vtable, c);
1245 return log_error_errno(r, "Failed to register object: %m");
1247 r = sd_bus_request_name(bus, "org.freedesktop.locale1", 0);
1249 return log_error_errno(r, "Failed to register name: %m");
1251 r = sd_bus_attach_event(bus, event, 0);
1253 return log_error_errno(r, "Failed to attach bus to event loop: %m");
1261 int main(int argc, char *argv[]) {
1262 _cleanup_(context_free) Context context = {};
1263 _cleanup_event_unref_ sd_event *event = NULL;
1264 _cleanup_bus_close_unref_ sd_bus *bus = NULL;
1267 log_set_target(LOG_TARGET_AUTO);
1268 log_parse_environment();
1272 mac_selinux_init("/etc");
1275 log_error("This program takes no arguments.");
1280 r = sd_event_default(&event);
1282 log_error_errno(r, "Failed to allocate event loop: %m");
1286 sd_event_set_watchdog(event, true);
1288 r = connect_bus(&context, event, &bus);
1292 r = context_read_data(&context);
1294 log_error_errno(r, "Failed to read locale data: %m");
1298 r = bus_event_loop_with_idle(event, bus, "org.freedesktop.locale1", DEFAULT_EXIT_USEC, NULL, NULL);
1300 log_error_errno(r, "Failed to run event loop: %m");
1305 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;