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"
44 #include <xkbcommon/xkbcommon.h>
48 /* We don't list LC_ALL here on purpose. People should be
49 * using LANG instead. */
62 LOCALE_LC_MEASUREMENT,
63 LOCALE_LC_IDENTIFICATION,
67 static const char * const names[_LOCALE_MAX] = {
68 [LOCALE_LANG] = "LANG",
69 [LOCALE_LANGUAGE] = "LANGUAGE",
70 [LOCALE_LC_CTYPE] = "LC_CTYPE",
71 [LOCALE_LC_NUMERIC] = "LC_NUMERIC",
72 [LOCALE_LC_TIME] = "LC_TIME",
73 [LOCALE_LC_COLLATE] = "LC_COLLATE",
74 [LOCALE_LC_MONETARY] = "LC_MONETARY",
75 [LOCALE_LC_MESSAGES] = "LC_MESSAGES",
76 [LOCALE_LC_PAPER] = "LC_PAPER",
77 [LOCALE_LC_NAME] = "LC_NAME",
78 [LOCALE_LC_ADDRESS] = "LC_ADDRESS",
79 [LOCALE_LC_TELEPHONE] = "LC_TELEPHONE",
80 [LOCALE_LC_MEASUREMENT] = "LC_MEASUREMENT",
81 [LOCALE_LC_IDENTIFICATION] = "LC_IDENTIFICATION"
84 typedef struct Context {
85 char *locale[_LOCALE_MAX];
93 char *vc_keymap_toggle;
95 Hashmap *polkit_registry;
98 static const char* nonempty(const char *s) {
99 return isempty(s) ? NULL : s;
102 static void free_and_replace(char **s, char *v) {
107 static bool startswith_comma(const char *s, const char *prefix) {
110 return s && (t = startswith(s, prefix)) && (*t == ',');
113 static void context_free_x11(Context *c) {
114 free_and_replace(&c->x11_layout, NULL);
115 free_and_replace(&c->x11_model, NULL);
116 free_and_replace(&c->x11_variant, NULL);
117 free_and_replace(&c->x11_options, NULL);
120 static void context_free_vconsole(Context *c) {
121 free_and_replace(&c->vc_keymap, NULL);
122 free_and_replace(&c->vc_keymap_toggle, NULL);
125 static void context_free_locale(Context *c) {
128 for (p = 0; p < _LOCALE_MAX; p++)
129 free_and_replace(&c->locale[p], NULL);
132 static void context_free(Context *c) {
133 context_free_locale(c);
135 context_free_vconsole(c);
137 bus_verify_polkit_async_registry_free(c->polkit_registry);
140 static void locale_simplify(Context *c) {
143 for (p = LOCALE_LANG+1; p < _LOCALE_MAX; p++)
144 if (isempty(c->locale[p]) || streq_ptr(c->locale[LOCALE_LANG], c->locale[p]))
145 free_and_replace(&c->locale[p], NULL);
148 static int locale_read_data(Context *c) {
151 context_free_locale(c);
153 r = parse_env_file("/etc/locale.conf", NEWLINE,
154 "LANG", &c->locale[LOCALE_LANG],
155 "LANGUAGE", &c->locale[LOCALE_LANGUAGE],
156 "LC_CTYPE", &c->locale[LOCALE_LC_CTYPE],
157 "LC_NUMERIC", &c->locale[LOCALE_LC_NUMERIC],
158 "LC_TIME", &c->locale[LOCALE_LC_TIME],
159 "LC_COLLATE", &c->locale[LOCALE_LC_COLLATE],
160 "LC_MONETARY", &c->locale[LOCALE_LC_MONETARY],
161 "LC_MESSAGES", &c->locale[LOCALE_LC_MESSAGES],
162 "LC_PAPER", &c->locale[LOCALE_LC_PAPER],
163 "LC_NAME", &c->locale[LOCALE_LC_NAME],
164 "LC_ADDRESS", &c->locale[LOCALE_LC_ADDRESS],
165 "LC_TELEPHONE", &c->locale[LOCALE_LC_TELEPHONE],
166 "LC_MEASUREMENT", &c->locale[LOCALE_LC_MEASUREMENT],
167 "LC_IDENTIFICATION", &c->locale[LOCALE_LC_IDENTIFICATION],
173 /* Fill in what we got passed from systemd. */
174 for (p = 0; p < _LOCALE_MAX; p++) {
177 r = free_and_strdup(&c->locale[p],
178 nonempty(getenv(names[p])));
190 static int vconsole_read_data(Context *c) {
193 context_free_vconsole(c);
195 r = parse_env_file("/etc/vconsole.conf", NEWLINE,
196 "KEYMAP", &c->vc_keymap,
197 "KEYMAP_TOGGLE", &c->vc_keymap_toggle,
200 if (r < 0 && r != -ENOENT)
206 static int x11_read_data(Context *c) {
207 _cleanup_fclose_ FILE *f;
209 bool in_section = false;
214 f = fopen("/etc/X11/xorg.conf.d/00-keyboard.conf", "re");
216 return errno == ENOENT ? 0 : -errno;
218 while (fgets(line, sizeof(line), f)) {
224 if (l[0] == 0 || l[0] == '#')
227 if (in_section && first_word(l, "Option")) {
228 _cleanup_strv_free_ char **a = NULL;
230 r = strv_split_quoted(&a, l, false);
234 if (strv_length(a) == 3) {
235 if (streq(a[1], "XkbLayout")) {
236 free_and_replace(&c->x11_layout, a[2]);
238 } else if (streq(a[1], "XkbModel")) {
239 free_and_replace(&c->x11_model, a[2]);
241 } else if (streq(a[1], "XkbVariant")) {
242 free_and_replace(&c->x11_variant, a[2]);
244 } else if (streq(a[1], "XkbOptions")) {
245 free_and_replace(&c->x11_options, a[2]);
250 } else if (!in_section && first_word(l, "Section")) {
251 _cleanup_strv_free_ char **a = NULL;
253 r = strv_split_quoted(&a, l, false);
257 if (strv_length(a) == 2 && streq(a[1], "InputClass"))
260 } else if (in_section && first_word(l, "EndSection"))
267 static int context_read_data(Context *c) {
270 r = locale_read_data(c);
271 q = vconsole_read_data(c);
272 p = x11_read_data(c);
274 return r < 0 ? r : q < 0 ? q : p;
277 static int locale_write_data(Context *c, char ***settings) {
279 _cleanup_strv_free_ char **l = NULL;
281 /* Set values will be returned as strv in *settings on success. */
283 r = load_env_file(NULL, "/etc/locale.conf", NULL, &l);
284 if (r < 0 && r != -ENOENT)
287 for (p = 0; p < _LOCALE_MAX; p++) {
288 _cleanup_free_ char *t = NULL;
293 if (isempty(c->locale[p])) {
294 l = strv_env_unset(l, names[p]);
298 if (asprintf(&t, "%s=%s", names[p], c->locale[p]) < 0)
301 u = strv_env_set(l, t);
309 if (strv_isempty(l)) {
310 if (unlink("/etc/locale.conf") < 0)
311 return errno == ENOENT ? 0 : -errno;
316 r = write_env_file_label("/etc/locale.conf", l);
325 static int locale_update_system_manager(Context *c, sd_bus *bus) {
326 _cleanup_free_ char **l_unset = NULL;
327 _cleanup_strv_free_ char **l_set = NULL;
328 _cleanup_bus_message_unref_ sd_bus_message *m = NULL;
329 sd_bus_error error = SD_BUS_ERROR_NULL;
330 unsigned c_set, c_unset, p;
335 l_unset = new0(char*, _LOCALE_MAX);
339 l_set = new0(char*, _LOCALE_MAX);
343 for (p = 0, c_set = 0, c_unset = 0; p < _LOCALE_MAX; p++) {
346 if (isempty(c->locale[p]))
347 l_unset[c_set++] = (char*) names[p];
351 if (asprintf(&s, "%s=%s", names[p], c->locale[p]) < 0)
354 l_set[c_unset++] = s;
358 assert(c_set + c_unset == _LOCALE_MAX);
359 r = sd_bus_message_new_method_call(bus, &m,
360 "org.freedesktop.systemd1",
361 "/org/freedesktop/systemd1",
362 "org.freedesktop.systemd1.Manager",
363 "UnsetAndSetEnvironment");
367 r = sd_bus_message_append_strv(m, l_unset);
371 r = sd_bus_message_append_strv(m, l_set);
375 r = sd_bus_call(bus, m, 0, &error, NULL);
377 log_error_errno(r, "Failed to update the manager environment: %m");
382 static int vconsole_write_data(Context *c) {
384 _cleanup_strv_free_ char **l = NULL;
386 r = load_env_file(NULL, "/etc/vconsole.conf", NULL, &l);
387 if (r < 0 && r != -ENOENT)
390 if (isempty(c->vc_keymap))
391 l = strv_env_unset(l, "KEYMAP");
393 _cleanup_free_ char *s = NULL;
396 s = strappend("KEYMAP=", c->vc_keymap);
400 u = strv_env_set(l, s);
408 if (isempty(c->vc_keymap_toggle))
409 l = strv_env_unset(l, "KEYMAP_TOGGLE");
411 _cleanup_free_ char *s = NULL;
414 s = strappend("KEYMAP_TOGGLE=", c->vc_keymap_toggle);
418 u = strv_env_set(l, s);
426 if (strv_isempty(l)) {
427 if (unlink("/etc/vconsole.conf") < 0)
428 return errno == ENOENT ? 0 : -errno;
433 return write_env_file_label("/etc/vconsole.conf", l);
436 static int x11_write_data(Context *c) {
437 _cleanup_fclose_ FILE *f = NULL;
438 _cleanup_free_ char *temp_path = NULL;
441 if (isempty(c->x11_layout) &&
442 isempty(c->x11_model) &&
443 isempty(c->x11_variant) &&
444 isempty(c->x11_options)) {
446 if (unlink("/etc/X11/xorg.conf.d/00-keyboard.conf") < 0)
447 return errno == ENOENT ? 0 : -errno;
452 mkdir_p_label("/etc/X11/xorg.conf.d", 0755);
454 r = fopen_temporary("/etc/X11/xorg.conf.d/00-keyboard.conf", &f, &temp_path);
458 fchmod(fileno(f), 0644);
460 fputs("# Read and parsed by systemd-localed. It's probably wise not to edit this file\n"
461 "# manually too freely.\n"
462 "Section \"InputClass\"\n"
463 " Identifier \"system-keyboard\"\n"
464 " MatchIsKeyboard \"on\"\n", f);
466 if (!isempty(c->x11_layout))
467 fprintf(f, " Option \"XkbLayout\" \"%s\"\n", c->x11_layout);
469 if (!isempty(c->x11_model))
470 fprintf(f, " Option \"XkbModel\" \"%s\"\n", c->x11_model);
472 if (!isempty(c->x11_variant))
473 fprintf(f, " Option \"XkbVariant\" \"%s\"\n", c->x11_variant);
475 if (!isempty(c->x11_options))
476 fprintf(f, " Option \"XkbOptions\" \"%s\"\n", c->x11_options);
478 fputs("EndSection\n", f);
481 if (ferror(f) || rename(temp_path, "/etc/X11/xorg.conf.d/00-keyboard.conf") < 0) {
483 unlink("/etc/X11/xorg.conf.d/00-keyboard.conf");
490 static int vconsole_reload(sd_bus *bus) {
491 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
496 r = sd_bus_call_method(bus,
497 "org.freedesktop.systemd1",
498 "/org/freedesktop/systemd1",
499 "org.freedesktop.systemd1.Manager",
503 "ss", "systemd-vconsole-setup.service", "replace");
506 log_error("Failed to issue method call: %s", bus_error_message(&error, -r));
510 static const char* strnulldash(const char *s) {
511 return isempty(s) || streq(s, "-") ? NULL : s;
514 static int read_next_mapping(FILE *f, unsigned *n, char ***a) {
525 if (!fgets(line, sizeof(line), f)) {
528 return errno ? -errno : -EIO;
536 if (l[0] == 0 || l[0] == '#')
539 r = strv_split_quoted(&b, l, false);
543 if (strv_length(b) < 5) {
544 log_error("Invalid line "SYSTEMD_KBD_MODEL_MAP":%u, ignoring.", *n);
555 static int vconsole_convert_to_x11(Context *c, sd_bus *bus) {
556 bool modified = false;
560 if (isempty(c->vc_keymap)) {
563 !isempty(c->x11_layout) ||
564 !isempty(c->x11_model) ||
565 !isempty(c->x11_variant) ||
566 !isempty(c->x11_options);
570 _cleanup_fclose_ FILE *f = NULL;
573 f = fopen(SYSTEMD_KBD_MODEL_MAP, "re");
578 _cleanup_strv_free_ char **a = NULL;
581 r = read_next_mapping(f, &n, &a);
587 if (!streq(c->vc_keymap, a[0]))
590 if (!streq_ptr(c->x11_layout, strnulldash(a[1])) ||
591 !streq_ptr(c->x11_model, strnulldash(a[2])) ||
592 !streq_ptr(c->x11_variant, strnulldash(a[3])) ||
593 !streq_ptr(c->x11_options, strnulldash(a[4]))) {
595 if (free_and_strdup(&c->x11_layout, strnulldash(a[1])) < 0 ||
596 free_and_strdup(&c->x11_model, strnulldash(a[2])) < 0 ||
597 free_and_strdup(&c->x11_variant, strnulldash(a[3])) < 0 ||
598 free_and_strdup(&c->x11_options, strnulldash(a[4])) < 0)
611 r = x11_write_data(c);
613 return log_error_errno(r, "Failed to set X11 keyboard layout: %m");
615 log_info("Changed X11 keyboard layout to '%s' model '%s' variant '%s' options '%s'",
616 strempty(c->x11_layout),
617 strempty(c->x11_model),
618 strempty(c->x11_variant),
619 strempty(c->x11_options));
621 sd_bus_emit_properties_changed(bus,
622 "/org/freedesktop/locale1",
623 "org.freedesktop.locale1",
624 "X11Layout", "X11Model", "X11Variant", "X11Options", NULL);
626 log_debug("X11 keyboard layout was not modified.");
631 static int find_converted_keymap(const char *x11_layout, const char *x11_variant, char **new_keymap) {
633 _cleanup_free_ char *n;
636 n = strjoin(x11_layout, "-", x11_variant, NULL);
638 n = strdup(x11_layout);
642 NULSTR_FOREACH(dir, KBD_KEYMAP_DIRS) {
643 _cleanup_free_ char *p = NULL, *pz = NULL;
646 p = strjoin(dir, "xkb/", n, ".map", NULL);
647 pz = strjoin(dir, "xkb/", n, ".map.gz", NULL);
651 uncompressed = access(p, F_OK) == 0;
652 if (uncompressed || access(pz, F_OK) == 0) {
653 log_debug("Found converted keymap %s at %s",
654 n, uncompressed ? p : pz);
665 static int find_legacy_keymap(Context *c, char **new_keymap) {
666 _cleanup_fclose_ FILE *f;
668 unsigned best_matching = 0;
671 f = fopen(SYSTEMD_KBD_MODEL_MAP, "re");
676 _cleanup_strv_free_ char **a = NULL;
677 unsigned matching = 0;
679 r = read_next_mapping(f, &n, &a);
685 /* Determine how well matching this entry is */
686 if (streq_ptr(c->x11_layout, a[1]))
687 /* If we got an exact match, this is best */
690 /* We have multiple X layouts, look for an
691 * entry that matches our key with everything
692 * but the first layout stripped off. */
693 if (startswith_comma(c->x11_layout, a[1]))
698 /* If that didn't work, strip off the
699 * other layouts from the entry, too */
700 x = strndupa(a[1], strcspn(a[1], ","));
701 if (startswith_comma(c->x11_layout, x))
707 if (isempty(c->x11_model) || streq_ptr(c->x11_model, a[2])) {
710 if (streq_ptr(c->x11_variant, a[3])) {
713 if (streq_ptr(c->x11_options, a[4]))
719 /* The best matching entry so far, then let's save that */
720 if (matching >= MAX(best_matching, 1u)) {
721 log_debug("Found legacy keymap %s with score %u",
724 if (matching > best_matching) {
725 best_matching = matching;
727 r = free_and_strdup(new_keymap, a[0]);
734 if (best_matching < 10 && c->x11_layout) {
735 /* The best match is only the first part of the X11
736 * keymap. Check if we have a converted map which
737 * matches just the first layout.
739 char *l, *v = NULL, *converted;
741 l = strndupa(c->x11_layout, strcspn(c->x11_layout, ","));
743 v = strndupa(c->x11_variant, strcspn(c->x11_variant, ","));
744 r = find_converted_keymap(l, v, &converted);
748 free_and_replace(new_keymap, converted);
754 static int x11_convert_to_vconsole(Context *c, sd_bus *bus) {
755 bool modified = false;
760 if (isempty(c->x11_layout)) {
763 !isempty(c->vc_keymap) ||
764 !isempty(c->vc_keymap_toggle);
768 char *new_keymap = NULL;
770 r = find_converted_keymap(c->x11_layout, c->x11_variant, &new_keymap);
774 r = find_legacy_keymap(c, &new_keymap);
779 if (!streq_ptr(c->vc_keymap, new_keymap)) {
780 free_and_replace(&c->vc_keymap, new_keymap);
781 free_and_replace(&c->vc_keymap_toggle, NULL);
788 r = vconsole_write_data(c);
790 log_error_errno(r, "Failed to set virtual console keymap: %m");
792 log_info("Changed virtual console keymap to '%s' toggle '%s'",
793 strempty(c->vc_keymap), strempty(c->vc_keymap_toggle));
795 sd_bus_emit_properties_changed(bus,
796 "/org/freedesktop/locale1",
797 "org.freedesktop.locale1",
798 "VConsoleKeymap", "VConsoleKeymapToggle", NULL);
800 return vconsole_reload(bus);
802 log_debug("Virtual console keymap was not modified.");
807 static int property_get_locale(
810 const char *interface,
811 const char *property,
812 sd_bus_message *reply,
814 sd_bus_error *error) {
816 Context *c = userdata;
817 _cleanup_strv_free_ char **l = NULL;
820 l = new0(char*, _LOCALE_MAX+1);
824 for (p = 0, q = 0; p < _LOCALE_MAX; p++) {
827 if (isempty(c->locale[p]))
830 if (asprintf(&t, "%s=%s", names[p], c->locale[p]) < 0)
836 return sd_bus_message_append_strv(reply, l);
839 static int method_set_locale(sd_bus *bus, sd_bus_message *m, void *userdata, sd_bus_error *error) {
840 Context *c = userdata;
841 _cleanup_strv_free_ char **l = NULL;
844 bool modified = false;
845 bool passed[_LOCALE_MAX] = {};
849 r = bus_message_read_strv_extend(m, &l);
853 r = sd_bus_message_read_basic(m, 'b', &interactive);
857 /* Check whether a variable changed and if it is valid */
861 for (p = 0; p < _LOCALE_MAX; p++) {
864 k = strlen(names[p]);
865 if (startswith(*i, names[p]) &&
867 locale_is_valid((*i) + k + 1)) {
871 if (!streq_ptr(*i + k + 1, c->locale[p]))
879 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid Locale data.");
882 /* Check whether a variable is unset */
884 for (p = 0; p < _LOCALE_MAX; p++)
885 if (!isempty(c->locale[p]) && !passed[p]) {
891 _cleanup_strv_free_ char **settings = NULL;
893 r = bus_verify_polkit_async(m, CAP_SYS_ADMIN, "org.freedesktop.locale1.set-locale", interactive, &c->polkit_registry, error);
897 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
900 for (p = 0; p < _LOCALE_MAX; p++) {
903 k = strlen(names[p]);
904 if (startswith(*i, names[p]) && (*i)[k] == '=') {
905 r = free_and_strdup(&c->locale[p], *i + k + 1);
912 for (p = 0; p < _LOCALE_MAX; p++) {
916 free_and_replace(&c->locale[p], NULL);
921 r = locale_write_data(c, &settings);
923 log_error_errno(r, "Failed to set locale: %m");
924 return sd_bus_error_set_errnof(error, r, "Failed to set locale: %s", strerror(-r));
927 locale_update_system_manager(c, bus);
930 _cleanup_free_ char *line;
932 line = strv_join(settings, ", ");
933 log_info("Changed locale to %s.", strnull(line));
935 log_info("Changed locale to unset.");
937 sd_bus_emit_properties_changed(bus,
938 "/org/freedesktop/locale1",
939 "org.freedesktop.locale1",
942 log_debug("Locale settings were not modified.");
945 return sd_bus_reply_method_return(m, NULL);
948 static int method_set_vc_keyboard(sd_bus *bus, sd_bus_message *m, void *userdata, sd_bus_error *error) {
949 Context *c = userdata;
950 const char *keymap, *keymap_toggle;
951 int convert, interactive;
954 r = sd_bus_message_read(m, "ssbb", &keymap, &keymap_toggle, &convert, &interactive);
961 if (isempty(keymap_toggle))
962 keymap_toggle = NULL;
964 if (!streq_ptr(keymap, c->vc_keymap) ||
965 !streq_ptr(keymap_toggle, c->vc_keymap_toggle)) {
967 if ((keymap && (!filename_is_valid(keymap) || !string_is_safe(keymap))) ||
968 (keymap_toggle && (!filename_is_valid(keymap_toggle) || !string_is_safe(keymap_toggle))))
969 return sd_bus_error_set_errnof(error, -EINVAL, "Received invalid keymap data");
971 r = bus_verify_polkit_async(m, CAP_SYS_ADMIN, "org.freedesktop.locale1.set-keyboard", interactive, &c->polkit_registry, error);
975 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
977 if (free_and_strdup(&c->vc_keymap, keymap) < 0 ||
978 free_and_strdup(&c->vc_keymap_toggle, keymap_toggle) < 0)
981 r = vconsole_write_data(c);
983 log_error_errno(r, "Failed to set virtual console keymap: %m");
984 return sd_bus_error_set_errnof(error, r, "Failed to set virtual console keymap: %s", strerror(-r));
987 log_info("Changed virtual console keymap to '%s' toggle '%s'",
988 strempty(c->vc_keymap), strempty(c->vc_keymap_toggle));
990 r = vconsole_reload(bus);
992 log_error_errno(r, "Failed to request keymap reload: %m");
994 sd_bus_emit_properties_changed(bus,
995 "/org/freedesktop/locale1",
996 "org.freedesktop.locale1",
997 "VConsoleKeymap", "VConsoleKeymapToggle", NULL);
1000 r = vconsole_convert_to_x11(c, bus);
1002 log_error_errno(r, "Failed to convert keymap data: %m");
1006 return sd_bus_reply_method_return(m, NULL);
1009 #ifdef HAVE_XKBCOMMON
1010 static void log_xkb(struct xkb_context *ctx, enum xkb_log_level lvl, const char *format, va_list args) {
1013 fmt = strappenda("libxkbcommon: ", format);
1014 log_internalv(LOG_DEBUG, 0, __FILE__, __LINE__, __func__, fmt, args);
1017 static int verify_xkb_rmlvo(const char *model, const char *layout, const char *variant, const char *options) {
1018 const struct xkb_rule_names rmlvo = {
1024 struct xkb_context *ctx = NULL;
1025 struct xkb_keymap *km = NULL;
1028 /* compile keymap from RMLVO information to check out its validity */
1030 ctx = xkb_context_new(XKB_CONTEXT_NO_ENVIRONMENT_NAMES);
1036 xkb_context_set_log_fn(ctx, log_xkb);
1038 km = xkb_keymap_new_from_names(ctx, &rmlvo, XKB_KEYMAP_COMPILE_NO_FLAGS);
1047 xkb_keymap_unref(km);
1048 xkb_context_unref(ctx);
1052 static int verify_xkb_rmlvo(const char *model, const char *layout, const char *variant, const char *options) {
1057 static int method_set_x11_keyboard(sd_bus *bus, sd_bus_message *m, void *userdata, sd_bus_error *error) {
1058 Context *c = userdata;
1059 const char *layout, *model, *variant, *options;
1060 int convert, interactive;
1063 r = sd_bus_message_read(m, "ssssbb", &layout, &model, &variant, &options, &convert, &interactive);
1067 if (isempty(layout))
1073 if (isempty(variant))
1076 if (isempty(options))
1079 if (!streq_ptr(layout, c->x11_layout) ||
1080 !streq_ptr(model, c->x11_model) ||
1081 !streq_ptr(variant, c->x11_variant) ||
1082 !streq_ptr(options, c->x11_options)) {
1084 if ((layout && !string_is_safe(layout)) ||
1085 (model && !string_is_safe(model)) ||
1086 (variant && !string_is_safe(variant)) ||
1087 (options && !string_is_safe(options)))
1088 return sd_bus_error_set_errnof(error, -EINVAL, "Received invalid keyboard data");
1090 r = bus_verify_polkit_async(m, CAP_SYS_ADMIN, "org.freedesktop.locale1.set-keyboard", interactive, &c->polkit_registry, error);
1094 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
1096 r = verify_xkb_rmlvo(model, layout, variant, options);
1098 log_error_errno(r, "Cannot compile XKB keymap for new x11 keyboard layout ('%s' / '%s' / '%s' / '%s'): %m",
1099 strempty(model), strempty(layout), strempty(variant), strempty(options));
1100 return sd_bus_error_set(error, SD_BUS_ERROR_INVALID_ARGS, "Cannot compile XKB keymap, refusing");
1103 if (free_and_strdup(&c->x11_layout, layout) < 0 ||
1104 free_and_strdup(&c->x11_model, model) < 0 ||
1105 free_and_strdup(&c->x11_variant, variant) < 0 ||
1106 free_and_strdup(&c->x11_options, options) < 0)
1109 r = x11_write_data(c);
1111 log_error_errno(r, "Failed to set X11 keyboard layout: %m");
1112 return sd_bus_error_set_errnof(error, r, "Failed to set X11 keyboard layout: %s", strerror(-r));
1115 log_info("Changed X11 keyboard layout to '%s' model '%s' variant '%s' options '%s'",
1116 strempty(c->x11_layout),
1117 strempty(c->x11_model),
1118 strempty(c->x11_variant),
1119 strempty(c->x11_options));
1121 sd_bus_emit_properties_changed(bus,
1122 "/org/freedesktop/locale1",
1123 "org.freedesktop.locale1",
1124 "X11Layout", "X11Model", "X11Variant", "X11Options", NULL);
1127 r = x11_convert_to_vconsole(c, bus);
1129 log_error_errno(r, "Failed to convert keymap data: %m");
1133 return sd_bus_reply_method_return(m, NULL);
1136 static const sd_bus_vtable locale_vtable[] = {
1137 SD_BUS_VTABLE_START(0),
1138 SD_BUS_PROPERTY("Locale", "as", property_get_locale, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
1139 SD_BUS_PROPERTY("X11Layout", "s", NULL, offsetof(Context, x11_layout), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
1140 SD_BUS_PROPERTY("X11Model", "s", NULL, offsetof(Context, x11_model), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
1141 SD_BUS_PROPERTY("X11Variant", "s", NULL, offsetof(Context, x11_variant), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
1142 SD_BUS_PROPERTY("X11Options", "s", NULL, offsetof(Context, x11_options), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
1143 SD_BUS_PROPERTY("VConsoleKeymap", "s", NULL, offsetof(Context, vc_keymap), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
1144 SD_BUS_PROPERTY("VConsoleKeymapToggle", "s", NULL, offsetof(Context, vc_keymap_toggle), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
1145 SD_BUS_METHOD("SetLocale", "asb", NULL, method_set_locale, SD_BUS_VTABLE_UNPRIVILEGED),
1146 SD_BUS_METHOD("SetVConsoleKeyboard", "ssbb", NULL, method_set_vc_keyboard, SD_BUS_VTABLE_UNPRIVILEGED),
1147 SD_BUS_METHOD("SetX11Keyboard", "ssssbb", NULL, method_set_x11_keyboard, SD_BUS_VTABLE_UNPRIVILEGED),
1151 static int connect_bus(Context *c, sd_event *event, sd_bus **_bus) {
1152 _cleanup_bus_close_unref_ sd_bus *bus = NULL;
1159 r = sd_bus_default_system(&bus);
1161 return log_error_errno(r, "Failed to get system bus connection: %m");
1163 r = sd_bus_add_object_vtable(bus, NULL, "/org/freedesktop/locale1", "org.freedesktop.locale1", locale_vtable, c);
1165 return log_error_errno(r, "Failed to register object: %m");
1167 r = sd_bus_request_name(bus, "org.freedesktop.locale1", 0);
1169 return log_error_errno(r, "Failed to register name: %m");
1171 r = sd_bus_attach_event(bus, event, 0);
1173 return log_error_errno(r, "Failed to attach bus to event loop: %m");
1181 int main(int argc, char *argv[]) {
1182 _cleanup_(context_free) Context context = {};
1183 _cleanup_event_unref_ sd_event *event = NULL;
1184 _cleanup_bus_close_unref_ sd_bus *bus = NULL;
1187 log_set_target(LOG_TARGET_AUTO);
1188 log_parse_environment();
1192 mac_selinux_init("/etc");
1195 log_error("This program takes no arguments.");
1200 r = sd_event_default(&event);
1202 log_error_errno(r, "Failed to allocate event loop: %m");
1206 sd_event_set_watchdog(event, true);
1208 r = connect_bus(&context, event, &bus);
1212 r = context_read_data(&context);
1214 log_error_errno(r, "Failed to read locale data: %m");
1218 r = bus_event_loop_with_idle(event, bus, "org.freedesktop.locale1", DEFAULT_EXIT_USEC, NULL, NULL);
1220 log_error_errno(r, "Failed to run event loop: %m");
1225 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;