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/>.
26 #include <sys/capability.h>
36 #include "fileio-label.h"
39 #include "bus-error.h"
40 #include "bus-message.h"
41 #include "event-util.h"
42 #include "locale-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(FILE *f, unsigned *n, char ***a) {
526 if (!fgets(line, sizeof(line), f)) {
529 return errno ? -errno : -EIO;
537 if (l[0] == 0 || l[0] == '#')
540 r = strv_split_quoted(&b, l, false);
544 if (strv_length(b) < 5) {
545 log_error("Invalid line "SYSTEMD_KBD_MODEL_MAP":%u, ignoring.", *n);
556 static int vconsole_convert_to_x11(Context *c, sd_bus *bus) {
557 bool modified = false;
561 if (isempty(c->vc_keymap)) {
564 !isempty(c->x11_layout) ||
565 !isempty(c->x11_model) ||
566 !isempty(c->x11_variant) ||
567 !isempty(c->x11_options);
571 _cleanup_fclose_ FILE *f = NULL;
574 f = fopen(SYSTEMD_KBD_MODEL_MAP, "re");
579 _cleanup_strv_free_ char **a = NULL;
582 r = read_next_mapping(f, &n, &a);
588 if (!streq(c->vc_keymap, a[0]))
591 if (!streq_ptr(c->x11_layout, strnulldash(a[1])) ||
592 !streq_ptr(c->x11_model, strnulldash(a[2])) ||
593 !streq_ptr(c->x11_variant, strnulldash(a[3])) ||
594 !streq_ptr(c->x11_options, strnulldash(a[4]))) {
596 if (free_and_strdup(&c->x11_layout, strnulldash(a[1])) < 0 ||
597 free_and_strdup(&c->x11_model, strnulldash(a[2])) < 0 ||
598 free_and_strdup(&c->x11_variant, strnulldash(a[3])) < 0 ||
599 free_and_strdup(&c->x11_options, strnulldash(a[4])) < 0)
612 r = x11_write_data(c);
614 return log_error_errno(r, "Failed to set X11 keyboard layout: %m");
616 log_info("Changed X11 keyboard layout to '%s' model '%s' variant '%s' options '%s'",
617 strempty(c->x11_layout),
618 strempty(c->x11_model),
619 strempty(c->x11_variant),
620 strempty(c->x11_options));
622 sd_bus_emit_properties_changed(bus,
623 "/org/freedesktop/locale1",
624 "org.freedesktop.locale1",
625 "X11Layout", "X11Model", "X11Variant", "X11Options", NULL);
627 log_debug("X11 keyboard layout was not modified.");
632 static int find_converted_keymap(const char *x11_layout, const char *x11_variant, char **new_keymap) {
634 _cleanup_free_ char *n;
637 n = strjoin(x11_layout, "-", x11_variant, NULL);
639 n = strdup(x11_layout);
643 NULSTR_FOREACH(dir, KBD_KEYMAP_DIRS) {
644 _cleanup_free_ char *p = NULL, *pz = NULL;
647 p = strjoin(dir, "xkb/", n, ".map", NULL);
648 pz = strjoin(dir, "xkb/", n, ".map.gz", NULL);
652 uncompressed = access(p, F_OK) == 0;
653 if (uncompressed || access(pz, F_OK) == 0) {
654 log_debug("Found converted keymap %s at %s",
655 n, uncompressed ? p : pz);
666 static int find_legacy_keymap(Context *c, char **new_keymap) {
667 _cleanup_fclose_ FILE *f;
669 unsigned best_matching = 0;
672 f = fopen(SYSTEMD_KBD_MODEL_MAP, "re");
677 _cleanup_strv_free_ char **a = NULL;
678 unsigned matching = 0;
680 r = read_next_mapping(f, &n, &a);
686 /* Determine how well matching this entry is */
687 if (streq_ptr(c->x11_layout, a[1]))
688 /* If we got an exact match, this is best */
691 /* We have multiple X layouts, look for an
692 * entry that matches our key with everything
693 * but the first layout stripped off. */
694 if (startswith_comma(c->x11_layout, a[1]))
699 /* If that didn't work, strip off the
700 * other layouts from the entry, too */
701 x = strndupa(a[1], strcspn(a[1], ","));
702 if (startswith_comma(c->x11_layout, x))
708 if (isempty(c->x11_model) || streq_ptr(c->x11_model, a[2])) {
711 if (streq_ptr(c->x11_variant, a[3])) {
714 if (streq_ptr(c->x11_options, a[4]))
720 /* The best matching entry so far, then let's save that */
721 if (matching >= MAX(best_matching, 1u)) {
722 log_debug("Found legacy keymap %s with score %u",
725 if (matching > best_matching) {
726 best_matching = matching;
728 r = free_and_strdup(new_keymap, a[0]);
735 if (best_matching < 10 && c->x11_layout) {
736 /* The best match is only the first part of the X11
737 * keymap. Check if we have a converted map which
738 * matches just the first layout.
740 char *l, *v = NULL, *converted;
742 l = strndupa(c->x11_layout, strcspn(c->x11_layout, ","));
744 v = strndupa(c->x11_variant, strcspn(c->x11_variant, ","));
745 r = find_converted_keymap(l, v, &converted);
749 free_and_replace(new_keymap, converted);
755 static int x11_convert_to_vconsole(Context *c, sd_bus *bus) {
756 bool modified = false;
761 if (isempty(c->x11_layout)) {
764 !isempty(c->vc_keymap) ||
765 !isempty(c->vc_keymap_toggle);
769 char *new_keymap = NULL;
771 r = find_converted_keymap(c->x11_layout, c->x11_variant, &new_keymap);
775 r = find_legacy_keymap(c, &new_keymap);
780 if (!streq_ptr(c->vc_keymap, new_keymap)) {
781 free_and_replace(&c->vc_keymap, new_keymap);
782 free_and_replace(&c->vc_keymap_toggle, NULL);
789 r = vconsole_write_data(c);
791 log_error_errno(r, "Failed to set virtual console keymap: %m");
793 log_info("Changed virtual console keymap to '%s' toggle '%s'",
794 strempty(c->vc_keymap), strempty(c->vc_keymap_toggle));
796 sd_bus_emit_properties_changed(bus,
797 "/org/freedesktop/locale1",
798 "org.freedesktop.locale1",
799 "VConsoleKeymap", "VConsoleKeymapToggle", NULL);
801 return vconsole_reload(bus);
803 log_debug("Virtual console keymap was not modified.");
808 static int property_get_locale(
811 const char *interface,
812 const char *property,
813 sd_bus_message *reply,
815 sd_bus_error *error) {
817 Context *c = userdata;
818 _cleanup_strv_free_ char **l = NULL;
821 l = new0(char*, _LOCALE_MAX+1);
825 for (p = 0, q = 0; p < _LOCALE_MAX; p++) {
828 if (isempty(c->locale[p]))
831 if (asprintf(&t, "%s=%s", names[p], c->locale[p]) < 0)
837 return sd_bus_message_append_strv(reply, l);
840 static int method_set_locale(sd_bus *bus, sd_bus_message *m, void *userdata, sd_bus_error *error) {
841 Context *c = userdata;
842 _cleanup_strv_free_ char **l = NULL;
845 bool modified = false;
846 bool passed[_LOCALE_MAX] = {};
850 r = bus_message_read_strv_extend(m, &l);
854 r = sd_bus_message_read_basic(m, 'b', &interactive);
858 /* Check whether a variable changed and if it is valid */
862 for (p = 0; p < _LOCALE_MAX; p++) {
865 k = strlen(names[p]);
866 if (startswith(*i, names[p]) &&
868 locale_is_valid((*i) + k + 1)) {
872 if (!streq_ptr(*i + k + 1, c->locale[p]))
880 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid Locale data.");
883 /* Check whether a variable is unset */
885 for (p = 0; p < _LOCALE_MAX; p++)
886 if (!isempty(c->locale[p]) && !passed[p]) {
892 _cleanup_strv_free_ char **settings = NULL;
894 r = bus_verify_polkit_async(m, CAP_SYS_ADMIN, "org.freedesktop.locale1.set-locale", interactive, &c->polkit_registry, error);
898 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
901 for (p = 0; p < _LOCALE_MAX; p++) {
904 k = strlen(names[p]);
905 if (startswith(*i, names[p]) && (*i)[k] == '=') {
906 r = free_and_strdup(&c->locale[p], *i + k + 1);
913 for (p = 0; p < _LOCALE_MAX; p++) {
917 free_and_replace(&c->locale[p], NULL);
922 r = locale_write_data(c, &settings);
924 log_error_errno(r, "Failed to set locale: %m");
925 return sd_bus_error_set_errnof(error, r, "Failed to set locale: %s", strerror(-r));
928 locale_update_system_manager(c, bus);
931 _cleanup_free_ char *line;
933 line = strv_join(settings, ", ");
934 log_info("Changed locale to %s.", strnull(line));
936 log_info("Changed locale to unset.");
938 sd_bus_emit_properties_changed(bus,
939 "/org/freedesktop/locale1",
940 "org.freedesktop.locale1",
943 log_debug("Locale settings were not modified.");
946 return sd_bus_reply_method_return(m, NULL);
949 static int method_set_vc_keyboard(sd_bus *bus, sd_bus_message *m, void *userdata, sd_bus_error *error) {
950 Context *c = userdata;
951 const char *keymap, *keymap_toggle;
952 int convert, interactive;
955 r = sd_bus_message_read(m, "ssbb", &keymap, &keymap_toggle, &convert, &interactive);
962 if (isempty(keymap_toggle))
963 keymap_toggle = NULL;
965 if (!streq_ptr(keymap, c->vc_keymap) ||
966 !streq_ptr(keymap_toggle, c->vc_keymap_toggle)) {
968 if ((keymap && (!filename_is_safe(keymap) || !string_is_safe(keymap))) ||
969 (keymap_toggle && (!filename_is_safe(keymap_toggle) || !string_is_safe(keymap_toggle))))
970 return sd_bus_error_set_errnof(error, -EINVAL, "Received invalid keymap data");
972 r = bus_verify_polkit_async(m, CAP_SYS_ADMIN, "org.freedesktop.locale1.set-keyboard", interactive, &c->polkit_registry, error);
976 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
978 if (free_and_strdup(&c->vc_keymap, keymap) < 0 ||
979 free_and_strdup(&c->vc_keymap_toggle, keymap_toggle) < 0)
982 r = vconsole_write_data(c);
984 log_error_errno(r, "Failed to set virtual console keymap: %m");
985 return sd_bus_error_set_errnof(error, r, "Failed to set virtual console keymap: %s", strerror(-r));
988 log_info("Changed virtual console keymap to '%s' toggle '%s'",
989 strempty(c->vc_keymap), strempty(c->vc_keymap_toggle));
991 r = vconsole_reload(bus);
993 log_error_errno(r, "Failed to request keymap reload: %m");
995 sd_bus_emit_properties_changed(bus,
996 "/org/freedesktop/locale1",
997 "org.freedesktop.locale1",
998 "VConsoleKeymap", "VConsoleKeymapToggle", NULL);
1001 r = vconsole_convert_to_x11(c, bus);
1003 log_error_errno(r, "Failed to convert keymap data: %m");
1007 return sd_bus_reply_method_return(m, NULL);
1010 #ifdef HAVE_XKBCOMMON
1011 static void log_xkb(struct xkb_context *ctx, enum xkb_log_level lvl, const char *format, va_list args) {
1014 fmt = strappenda("libxkbcommon: ", format);
1015 log_internalv(LOG_DEBUG, 0, __FILE__, __LINE__, __func__, fmt, args);
1018 static int verify_xkb_rmlvo(const char *model, const char *layout, const char *variant, const char *options) {
1019 const struct xkb_rule_names rmlvo = {
1025 struct xkb_context *ctx = NULL;
1026 struct xkb_keymap *km = NULL;
1029 /* compile keymap from RMLVO information to check out its validity */
1031 ctx = xkb_context_new(XKB_CONTEXT_NO_ENVIRONMENT_NAMES);
1037 xkb_context_set_log_fn(ctx, log_xkb);
1039 km = xkb_keymap_new_from_names(ctx, &rmlvo, XKB_KEYMAP_COMPILE_NO_FLAGS);
1048 xkb_keymap_unref(km);
1049 xkb_context_unref(ctx);
1053 static int verify_xkb_rmlvo(const char *model, const char *layout, const char *variant, const char *options) {
1058 static int method_set_x11_keyboard(sd_bus *bus, sd_bus_message *m, void *userdata, sd_bus_error *error) {
1059 Context *c = userdata;
1060 const char *layout, *model, *variant, *options;
1061 int convert, interactive;
1064 r = sd_bus_message_read(m, "ssssbb", &layout, &model, &variant, &options, &convert, &interactive);
1068 if (isempty(layout))
1074 if (isempty(variant))
1077 if (isempty(options))
1080 if (!streq_ptr(layout, c->x11_layout) ||
1081 !streq_ptr(model, c->x11_model) ||
1082 !streq_ptr(variant, c->x11_variant) ||
1083 !streq_ptr(options, c->x11_options)) {
1085 if ((layout && !string_is_safe(layout)) ||
1086 (model && !string_is_safe(model)) ||
1087 (variant && !string_is_safe(variant)) ||
1088 (options && !string_is_safe(options)))
1089 return sd_bus_error_set_errnof(error, -EINVAL, "Received invalid keyboard data");
1091 r = bus_verify_polkit_async(m, CAP_SYS_ADMIN, "org.freedesktop.locale1.set-keyboard", interactive, &c->polkit_registry, error);
1095 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
1097 r = verify_xkb_rmlvo(model, layout, variant, options);
1099 log_error_errno(r, "Cannot compile XKB keymap for new x11 keyboard layout ('%s' / '%s' / '%s' / '%s'): %m",
1100 strempty(model), strempty(layout), strempty(variant), strempty(options));
1101 return sd_bus_error_set(error, SD_BUS_ERROR_INVALID_ARGS, "Cannot compile XKB keymap, refusing");
1104 if (free_and_strdup(&c->x11_layout, layout) < 0 ||
1105 free_and_strdup(&c->x11_model, model) < 0 ||
1106 free_and_strdup(&c->x11_variant, variant) < 0 ||
1107 free_and_strdup(&c->x11_options, options) < 0)
1110 r = x11_write_data(c);
1112 log_error_errno(r, "Failed to set X11 keyboard layout: %m");
1113 return sd_bus_error_set_errnof(error, r, "Failed to set X11 keyboard layout: %s", strerror(-r));
1116 log_info("Changed X11 keyboard layout to '%s' model '%s' variant '%s' options '%s'",
1117 strempty(c->x11_layout),
1118 strempty(c->x11_model),
1119 strempty(c->x11_variant),
1120 strempty(c->x11_options));
1122 sd_bus_emit_properties_changed(bus,
1123 "/org/freedesktop/locale1",
1124 "org.freedesktop.locale1",
1125 "X11Layout", "X11Model", "X11Variant", "X11Options", NULL);
1128 r = x11_convert_to_vconsole(c, bus);
1130 log_error_errno(r, "Failed to convert keymap data: %m");
1134 return sd_bus_reply_method_return(m, NULL);
1137 static const sd_bus_vtable locale_vtable[] = {
1138 SD_BUS_VTABLE_START(0),
1139 SD_BUS_PROPERTY("Locale", "as", property_get_locale, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
1140 SD_BUS_PROPERTY("X11Layout", "s", NULL, offsetof(Context, x11_layout), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
1141 SD_BUS_PROPERTY("X11Model", "s", NULL, offsetof(Context, x11_model), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
1142 SD_BUS_PROPERTY("X11Variant", "s", NULL, offsetof(Context, x11_variant), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
1143 SD_BUS_PROPERTY("X11Options", "s", NULL, offsetof(Context, x11_options), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
1144 SD_BUS_PROPERTY("VConsoleKeymap", "s", NULL, offsetof(Context, vc_keymap), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
1145 SD_BUS_PROPERTY("VConsoleKeymapToggle", "s", NULL, offsetof(Context, vc_keymap_toggle), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
1146 SD_BUS_METHOD("SetLocale", "asb", NULL, method_set_locale, SD_BUS_VTABLE_UNPRIVILEGED),
1147 SD_BUS_METHOD("SetVConsoleKeyboard", "ssbb", NULL, method_set_vc_keyboard, SD_BUS_VTABLE_UNPRIVILEGED),
1148 SD_BUS_METHOD("SetX11Keyboard", "ssssbb", NULL, method_set_x11_keyboard, SD_BUS_VTABLE_UNPRIVILEGED),
1152 static int connect_bus(Context *c, sd_event *event, sd_bus **_bus) {
1153 _cleanup_bus_close_unref_ sd_bus *bus = NULL;
1160 r = sd_bus_default_system(&bus);
1162 return log_error_errno(r, "Failed to get system bus connection: %m");
1164 r = sd_bus_add_object_vtable(bus, NULL, "/org/freedesktop/locale1", "org.freedesktop.locale1", locale_vtable, c);
1166 return log_error_errno(r, "Failed to register object: %m");
1168 r = sd_bus_request_name(bus, "org.freedesktop.locale1", 0);
1170 return log_error_errno(r, "Failed to register name: %m");
1172 r = sd_bus_attach_event(bus, event, 0);
1174 return log_error_errno(r, "Failed to attach bus to event loop: %m");
1182 int main(int argc, char *argv[]) {
1183 _cleanup_(context_free) Context context = {};
1184 _cleanup_event_unref_ sd_event *event = NULL;
1185 _cleanup_bus_close_unref_ sd_bus *bus = NULL;
1188 log_set_target(LOG_TARGET_AUTO);
1189 log_parse_environment();
1193 mac_selinux_init("/etc");
1196 log_error("This program takes no arguments.");
1201 r = sd_event_default(&event);
1203 log_error_errno(r, "Failed to allocate event loop: %m");
1207 sd_event_set_watchdog(event, true);
1209 r = connect_bus(&context, event, &bus);
1213 r = context_read_data(&context);
1215 log_error_errno(r, "Failed to read locale data: %m");
1219 r = bus_event_loop_with_idle(event, bus, "org.freedesktop.locale1", DEFAULT_EXIT_USEC, NULL, NULL);
1221 log_error_errno(r, "Failed to run event loop: %m");
1226 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;