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/>.
28 #include <xkbcommon/xkbcommon.h>
33 #include "alloc-util.h"
34 #include "bus-error.h"
35 #include "bus-message.h"
39 #include "event-util.h"
41 #include "fileio-label.h"
43 #include "locale-util.h"
45 #include "path-util.h"
46 #include "selinux-util.h"
48 #include "user-util.h"
52 /* We don't list LC_ALL here on purpose. People should be
53 * using LANG instead. */
66 LOCALE_LC_MEASUREMENT
,
67 LOCALE_LC_IDENTIFICATION
,
71 static const char * const names
[_LOCALE_MAX
] = {
72 [LOCALE_LANG
] = "LANG",
73 [LOCALE_LANGUAGE
] = "LANGUAGE",
74 [LOCALE_LC_CTYPE
] = "LC_CTYPE",
75 [LOCALE_LC_NUMERIC
] = "LC_NUMERIC",
76 [LOCALE_LC_TIME
] = "LC_TIME",
77 [LOCALE_LC_COLLATE
] = "LC_COLLATE",
78 [LOCALE_LC_MONETARY
] = "LC_MONETARY",
79 [LOCALE_LC_MESSAGES
] = "LC_MESSAGES",
80 [LOCALE_LC_PAPER
] = "LC_PAPER",
81 [LOCALE_LC_NAME
] = "LC_NAME",
82 [LOCALE_LC_ADDRESS
] = "LC_ADDRESS",
83 [LOCALE_LC_TELEPHONE
] = "LC_TELEPHONE",
84 [LOCALE_LC_MEASUREMENT
] = "LC_MEASUREMENT",
85 [LOCALE_LC_IDENTIFICATION
] = "LC_IDENTIFICATION"
88 typedef struct Context
{
89 char *locale
[_LOCALE_MAX
];
97 char *vc_keymap_toggle
;
99 Hashmap
*polkit_registry
;
102 static const char* nonempty(const char *s
) {
103 return isempty(s
) ? NULL
: s
;
106 static bool startswith_comma(const char *s
, const char *prefix
) {
109 return s
&& (t
= startswith(s
, prefix
)) && (*t
== ',');
112 static void context_free_x11(Context
*c
) {
113 c
->x11_layout
= mfree(c
->x11_layout
);
114 c
->x11_options
= mfree(c
->x11_options
);
115 c
->x11_model
= mfree(c
->x11_model
);
116 c
->x11_variant
= mfree(c
->x11_variant
);
119 static void context_free_vconsole(Context
*c
) {
120 c
->vc_keymap
= mfree(c
->vc_keymap
);
121 c
->vc_keymap_toggle
= mfree(c
->vc_keymap_toggle
);
124 static void context_free_locale(Context
*c
) {
127 for (p
= 0; p
< _LOCALE_MAX
; p
++)
128 c
->locale
[p
] = mfree(c
->locale
[p
]);
131 static void context_free(Context
*c
) {
132 context_free_locale(c
);
134 context_free_vconsole(c
);
136 bus_verify_polkit_async_registry_free(c
->polkit_registry
);
139 static void locale_simplify(Context
*c
) {
142 for (p
= LOCALE_LANG
+1; p
< _LOCALE_MAX
; p
++)
143 if (isempty(c
->locale
[p
]) || streq_ptr(c
->locale
[LOCALE_LANG
], c
->locale
[p
]))
144 c
->locale
[p
] = mfree(c
->locale
[p
]);
147 static int locale_read_data(Context
*c
) {
150 context_free_locale(c
);
152 r
= parse_env_file("/etc/locale.conf", NEWLINE
,
153 "LANG", &c
->locale
[LOCALE_LANG
],
154 "LANGUAGE", &c
->locale
[LOCALE_LANGUAGE
],
155 "LC_CTYPE", &c
->locale
[LOCALE_LC_CTYPE
],
156 "LC_NUMERIC", &c
->locale
[LOCALE_LC_NUMERIC
],
157 "LC_TIME", &c
->locale
[LOCALE_LC_TIME
],
158 "LC_COLLATE", &c
->locale
[LOCALE_LC_COLLATE
],
159 "LC_MONETARY", &c
->locale
[LOCALE_LC_MONETARY
],
160 "LC_MESSAGES", &c
->locale
[LOCALE_LC_MESSAGES
],
161 "LC_PAPER", &c
->locale
[LOCALE_LC_PAPER
],
162 "LC_NAME", &c
->locale
[LOCALE_LC_NAME
],
163 "LC_ADDRESS", &c
->locale
[LOCALE_LC_ADDRESS
],
164 "LC_TELEPHONE", &c
->locale
[LOCALE_LC_TELEPHONE
],
165 "LC_MEASUREMENT", &c
->locale
[LOCALE_LC_MEASUREMENT
],
166 "LC_IDENTIFICATION", &c
->locale
[LOCALE_LC_IDENTIFICATION
],
172 /* Fill in what we got passed from systemd. */
173 for (p
= 0; p
< _LOCALE_MAX
; p
++) {
176 r
= free_and_strdup(&c
->locale
[p
],
177 nonempty(getenv(names
[p
])));
189 static int vconsole_read_data(Context
*c
) {
192 context_free_vconsole(c
);
194 r
= parse_env_file("/etc/vconsole.conf", NEWLINE
,
195 "KEYMAP", &c
->vc_keymap
,
196 "KEYMAP_TOGGLE", &c
->vc_keymap_toggle
,
199 if (r
< 0 && r
!= -ENOENT
)
205 static int x11_read_data(Context
*c
) {
206 _cleanup_fclose_
FILE *f
;
208 bool in_section
= false;
213 f
= fopen("/etc/X11/xorg.conf.d/00-keyboard.conf", "re");
215 return errno
== ENOENT
? 0 : -errno
;
217 while (fgets(line
, sizeof(line
), f
)) {
223 if (l
[0] == 0 || l
[0] == '#')
226 if (in_section
&& first_word(l
, "Option")) {
227 _cleanup_strv_free_
char **a
= NULL
;
229 r
= strv_split_extract(&a
, l
, WHITESPACE
, EXTRACT_QUOTES
);
233 if (strv_length(a
) == 3) {
236 if (streq(a
[1], "XkbLayout"))
238 else if (streq(a
[1], "XkbModel"))
240 else if (streq(a
[1], "XkbVariant"))
242 else if (streq(a
[1], "XkbOptions"))
252 } else if (!in_section
&& first_word(l
, "Section")) {
253 _cleanup_strv_free_
char **a
= NULL
;
255 r
= strv_split_extract(&a
, l
, WHITESPACE
, EXTRACT_QUOTES
);
259 if (strv_length(a
) == 2 && streq(a
[1], "InputClass"))
262 } else if (in_section
&& first_word(l
, "EndSection"))
269 static int context_read_data(Context
*c
) {
272 r
= locale_read_data(c
);
273 q
= vconsole_read_data(c
);
274 p
= x11_read_data(c
);
276 return r
< 0 ? r
: q
< 0 ? q
: p
;
279 static int locale_write_data(Context
*c
, char ***settings
) {
281 _cleanup_strv_free_
char **l
= NULL
;
283 /* Set values will be returned as strv in *settings on success. */
285 r
= load_env_file(NULL
, "/etc/locale.conf", NULL
, &l
);
286 if (r
< 0 && r
!= -ENOENT
)
289 for (p
= 0; p
< _LOCALE_MAX
; p
++) {
290 _cleanup_free_
char *t
= NULL
;
295 if (isempty(c
->locale
[p
])) {
296 l
= strv_env_unset(l
, names
[p
]);
300 if (asprintf(&t
, "%s=%s", names
[p
], c
->locale
[p
]) < 0)
303 u
= strv_env_set(l
, t
);
311 if (strv_isempty(l
)) {
312 if (unlink("/etc/locale.conf") < 0)
313 return errno
== ENOENT
? 0 : -errno
;
318 r
= write_env_file_label("/etc/locale.conf", l
);
327 static int locale_update_system_manager(Context
*c
, sd_bus
*bus
) {
328 _cleanup_free_
char **l_unset
= NULL
;
329 _cleanup_strv_free_
char **l_set
= NULL
;
330 _cleanup_bus_message_unref_ sd_bus_message
*m
= NULL
;
331 sd_bus_error error
= SD_BUS_ERROR_NULL
;
332 unsigned c_set
, c_unset
, p
;
337 l_unset
= new0(char*, _LOCALE_MAX
);
341 l_set
= new0(char*, _LOCALE_MAX
);
345 for (p
= 0, c_set
= 0, c_unset
= 0; p
< _LOCALE_MAX
; p
++) {
348 if (isempty(c
->locale
[p
]))
349 l_unset
[c_set
++] = (char*) names
[p
];
353 if (asprintf(&s
, "%s=%s", names
[p
], c
->locale
[p
]) < 0)
356 l_set
[c_unset
++] = s
;
360 assert(c_set
+ c_unset
== _LOCALE_MAX
);
361 r
= sd_bus_message_new_method_call(bus
, &m
,
362 "org.freedesktop.systemd1",
363 "/org/freedesktop/systemd1",
364 "org.freedesktop.systemd1.Manager",
365 "UnsetAndSetEnvironment");
369 r
= sd_bus_message_append_strv(m
, l_unset
);
373 r
= sd_bus_message_append_strv(m
, l_set
);
377 r
= sd_bus_call(bus
, m
, 0, &error
, NULL
);
379 log_error_errno(r
, "Failed to update the manager environment: %m");
384 static int vconsole_write_data(Context
*c
) {
386 _cleanup_strv_free_
char **l
= NULL
;
388 r
= load_env_file(NULL
, "/etc/vconsole.conf", NULL
, &l
);
389 if (r
< 0 && r
!= -ENOENT
)
392 if (isempty(c
->vc_keymap
))
393 l
= strv_env_unset(l
, "KEYMAP");
395 _cleanup_free_
char *s
= NULL
;
398 s
= strappend("KEYMAP=", c
->vc_keymap
);
402 u
= strv_env_set(l
, s
);
410 if (isempty(c
->vc_keymap_toggle
))
411 l
= strv_env_unset(l
, "KEYMAP_TOGGLE");
413 _cleanup_free_
char *s
= NULL
;
416 s
= strappend("KEYMAP_TOGGLE=", c
->vc_keymap_toggle
);
420 u
= strv_env_set(l
, s
);
428 if (strv_isempty(l
)) {
429 if (unlink("/etc/vconsole.conf") < 0)
430 return errno
== ENOENT
? 0 : -errno
;
435 return write_env_file_label("/etc/vconsole.conf", l
);
438 static int x11_write_data(Context
*c
) {
439 _cleanup_fclose_
FILE *f
= NULL
;
440 _cleanup_free_
char *temp_path
= NULL
;
443 if (isempty(c
->x11_layout
) &&
444 isempty(c
->x11_model
) &&
445 isempty(c
->x11_variant
) &&
446 isempty(c
->x11_options
)) {
448 if (unlink("/etc/X11/xorg.conf.d/00-keyboard.conf") < 0)
449 return errno
== ENOENT
? 0 : -errno
;
454 mkdir_p_label("/etc/X11/xorg.conf.d", 0755);
456 r
= fopen_temporary("/etc/X11/xorg.conf.d/00-keyboard.conf", &f
, &temp_path
);
460 fchmod(fileno(f
), 0644);
462 fputs("# Read and parsed by systemd-localed. It's probably wise not to edit this file\n"
463 "# manually too freely.\n"
464 "Section \"InputClass\"\n"
465 " Identifier \"system-keyboard\"\n"
466 " MatchIsKeyboard \"on\"\n", f
);
468 if (!isempty(c
->x11_layout
))
469 fprintf(f
, " Option \"XkbLayout\" \"%s\"\n", c
->x11_layout
);
471 if (!isempty(c
->x11_model
))
472 fprintf(f
, " Option \"XkbModel\" \"%s\"\n", c
->x11_model
);
474 if (!isempty(c
->x11_variant
))
475 fprintf(f
, " Option \"XkbVariant\" \"%s\"\n", c
->x11_variant
);
477 if (!isempty(c
->x11_options
))
478 fprintf(f
, " Option \"XkbOptions\" \"%s\"\n", c
->x11_options
);
480 fputs("EndSection\n", f
);
482 r
= fflush_and_check(f
);
486 if (rename(temp_path
, "/etc/X11/xorg.conf.d/00-keyboard.conf") < 0) {
494 (void) unlink("/etc/X11/xorg.conf.d/00-keyboard.conf");
497 (void) unlink(temp_path
);
502 static int vconsole_reload(sd_bus
*bus
) {
503 _cleanup_bus_error_free_ sd_bus_error error
= SD_BUS_ERROR_NULL
;
508 r
= sd_bus_call_method(bus
,
509 "org.freedesktop.systemd1",
510 "/org/freedesktop/systemd1",
511 "org.freedesktop.systemd1.Manager",
515 "ss", "systemd-vconsole-setup.service", "replace");
518 log_error("Failed to issue method call: %s", bus_error_message(&error
, -r
));
522 static const char* strnulldash(const char *s
) {
523 return isempty(s
) || streq(s
, "-") ? NULL
: s
;
526 static int read_next_mapping(const char* filename
,
527 unsigned min_fields
, unsigned max_fields
,
528 FILE *f
, unsigned *n
, char ***a
) {
540 if (!fgets(line
, sizeof(line
), f
)) {
543 return errno
? -errno
: -EIO
;
551 if (l
[0] == 0 || l
[0] == '#')
554 r
= strv_split_extract(&b
, l
, WHITESPACE
, EXTRACT_QUOTES
);
558 length
= strv_length(b
);
559 if (length
< min_fields
|| length
> max_fields
) {
560 log_error("Invalid line %s:%u, ignoring.", filename
, *n
);
571 static int vconsole_convert_to_x11(Context
*c
, sd_bus
*bus
) {
572 bool modified
= false;
576 if (isempty(c
->vc_keymap
)) {
579 !isempty(c
->x11_layout
) ||
580 !isempty(c
->x11_model
) ||
581 !isempty(c
->x11_variant
) ||
582 !isempty(c
->x11_options
);
586 _cleanup_fclose_
FILE *f
= NULL
;
589 f
= fopen(SYSTEMD_KBD_MODEL_MAP
, "re");
594 _cleanup_strv_free_
char **a
= NULL
;
597 r
= read_next_mapping(SYSTEMD_KBD_MODEL_MAP
, 5, UINT_MAX
, f
, &n
, &a
);
603 if (!streq(c
->vc_keymap
, a
[0]))
606 if (!streq_ptr(c
->x11_layout
, strnulldash(a
[1])) ||
607 !streq_ptr(c
->x11_model
, strnulldash(a
[2])) ||
608 !streq_ptr(c
->x11_variant
, strnulldash(a
[3])) ||
609 !streq_ptr(c
->x11_options
, strnulldash(a
[4]))) {
611 if (free_and_strdup(&c
->x11_layout
, strnulldash(a
[1])) < 0 ||
612 free_and_strdup(&c
->x11_model
, strnulldash(a
[2])) < 0 ||
613 free_and_strdup(&c
->x11_variant
, strnulldash(a
[3])) < 0 ||
614 free_and_strdup(&c
->x11_options
, strnulldash(a
[4])) < 0)
627 r
= x11_write_data(c
);
629 return log_error_errno(r
, "Failed to set X11 keyboard layout: %m");
631 log_info("Changed X11 keyboard layout to '%s' model '%s' variant '%s' options '%s'",
632 strempty(c
->x11_layout
),
633 strempty(c
->x11_model
),
634 strempty(c
->x11_variant
),
635 strempty(c
->x11_options
));
637 sd_bus_emit_properties_changed(bus
,
638 "/org/freedesktop/locale1",
639 "org.freedesktop.locale1",
640 "X11Layout", "X11Model", "X11Variant", "X11Options", NULL
);
642 log_debug("X11 keyboard layout was not modified.");
647 static int find_converted_keymap(const char *x11_layout
, const char *x11_variant
, char **new_keymap
) {
649 _cleanup_free_
char *n
;
652 n
= strjoin(x11_layout
, "-", x11_variant
, NULL
);
654 n
= strdup(x11_layout
);
658 NULSTR_FOREACH(dir
, KBD_KEYMAP_DIRS
) {
659 _cleanup_free_
char *p
= NULL
, *pz
= NULL
;
662 p
= strjoin(dir
, "xkb/", n
, ".map", NULL
);
663 pz
= strjoin(dir
, "xkb/", n
, ".map.gz", NULL
);
667 uncompressed
= access(p
, F_OK
) == 0;
668 if (uncompressed
|| access(pz
, F_OK
) == 0) {
669 log_debug("Found converted keymap %s at %s",
670 n
, uncompressed
? p
: pz
);
681 static int find_legacy_keymap(Context
*c
, char **new_keymap
) {
682 _cleanup_fclose_
FILE *f
;
684 unsigned best_matching
= 0;
687 f
= fopen(SYSTEMD_KBD_MODEL_MAP
, "re");
692 _cleanup_strv_free_
char **a
= NULL
;
693 unsigned matching
= 0;
695 r
= read_next_mapping(SYSTEMD_KBD_MODEL_MAP
, 5, UINT_MAX
, f
, &n
, &a
);
701 /* Determine how well matching this entry is */
702 if (streq_ptr(c
->x11_layout
, a
[1]))
703 /* If we got an exact match, this is best */
706 /* We have multiple X layouts, look for an
707 * entry that matches our key with everything
708 * but the first layout stripped off. */
709 if (startswith_comma(c
->x11_layout
, a
[1]))
714 /* If that didn't work, strip off the
715 * other layouts from the entry, too */
716 x
= strndupa(a
[1], strcspn(a
[1], ","));
717 if (startswith_comma(c
->x11_layout
, x
))
723 if (isempty(c
->x11_model
) || streq_ptr(c
->x11_model
, a
[2])) {
726 if (streq_ptr(c
->x11_variant
, a
[3])) {
729 if (streq_ptr(c
->x11_options
, a
[4]))
735 /* The best matching entry so far, then let's save that */
736 if (matching
>= MAX(best_matching
, 1u)) {
737 log_debug("Found legacy keymap %s with score %u",
740 if (matching
> best_matching
) {
741 best_matching
= matching
;
743 r
= free_and_strdup(new_keymap
, a
[0]);
750 if (best_matching
< 10 && c
->x11_layout
) {
751 /* The best match is only the first part of the X11
752 * keymap. Check if we have a converted map which
753 * matches just the first layout.
755 char *l
, *v
= NULL
, *converted
;
757 l
= strndupa(c
->x11_layout
, strcspn(c
->x11_layout
, ","));
759 v
= strndupa(c
->x11_variant
, strcspn(c
->x11_variant
, ","));
760 r
= find_converted_keymap(l
, v
, &converted
);
765 *new_keymap
= converted
;
772 static int find_language_fallback(const char *lang
, char **language
) {
773 _cleanup_fclose_
FILE *f
= NULL
;
778 f
= fopen(SYSTEMD_LANGUAGE_FALLBACK_MAP
, "re");
783 _cleanup_strv_free_
char **a
= NULL
;
786 r
= read_next_mapping(SYSTEMD_LANGUAGE_FALLBACK_MAP
, 2, 2, f
, &n
, &a
);
790 if (streq(lang
, a
[0])) {
791 assert(strv_length(a
) == 2);
798 assert_not_reached("should not be here");
801 static int x11_convert_to_vconsole(Context
*c
, sd_bus
*bus
) {
802 bool modified
= false;
807 if (isempty(c
->x11_layout
)) {
810 !isempty(c
->vc_keymap
) ||
811 !isempty(c
->vc_keymap_toggle
);
815 char *new_keymap
= NULL
;
817 r
= find_converted_keymap(c
->x11_layout
, c
->x11_variant
, &new_keymap
);
821 r
= find_legacy_keymap(c
, &new_keymap
);
826 if (!streq_ptr(c
->vc_keymap
, new_keymap
)) {
828 c
->vc_keymap
= new_keymap
;
829 c
->vc_keymap_toggle
= mfree(c
->vc_keymap_toggle
);
836 r
= vconsole_write_data(c
);
838 log_error_errno(r
, "Failed to set virtual console keymap: %m");
840 log_info("Changed virtual console keymap to '%s' toggle '%s'",
841 strempty(c
->vc_keymap
), strempty(c
->vc_keymap_toggle
));
843 sd_bus_emit_properties_changed(bus
,
844 "/org/freedesktop/locale1",
845 "org.freedesktop.locale1",
846 "VConsoleKeymap", "VConsoleKeymapToggle", NULL
);
848 return vconsole_reload(bus
);
850 log_debug("Virtual console keymap was not modified.");
855 static int property_get_locale(
858 const char *interface
,
859 const char *property
,
860 sd_bus_message
*reply
,
862 sd_bus_error
*error
) {
864 Context
*c
= userdata
;
865 _cleanup_strv_free_
char **l
= NULL
;
868 l
= new0(char*, _LOCALE_MAX
+1);
872 for (p
= 0, q
= 0; p
< _LOCALE_MAX
; p
++) {
875 if (isempty(c
->locale
[p
]))
878 if (asprintf(&t
, "%s=%s", names
[p
], c
->locale
[p
]) < 0)
884 return sd_bus_message_append_strv(reply
, l
);
887 static int method_set_locale(sd_bus_message
*m
, void *userdata
, sd_bus_error
*error
) {
888 Context
*c
= userdata
;
889 _cleanup_strv_free_
char **l
= NULL
;
891 const char *lang
= NULL
;
893 bool modified
= false;
894 bool have
[_LOCALE_MAX
] = {};
901 r
= bus_message_read_strv_extend(m
, &l
);
905 r
= sd_bus_message_read_basic(m
, 'b', &interactive
);
909 /* Check whether a variable changed and if it is valid */
913 for (p
= 0; p
< _LOCALE_MAX
; p
++) {
916 k
= strlen(names
[p
]);
917 if (startswith(*i
, names
[p
]) &&
919 locale_is_valid((*i
) + k
+ 1)) {
923 if (p
== LOCALE_LANG
)
926 if (!streq_ptr(*i
+ k
+ 1, c
->locale
[p
]))
934 return sd_bus_error_setf(error
, SD_BUS_ERROR_INVALID_ARGS
, "Invalid Locale data.");
937 /* If LANG was specified, but not LANGUAGE, check if we should
938 * set it based on the language fallback table. */
939 if (have
[LOCALE_LANG
] && !have
[LOCALE_LANGUAGE
]) {
940 _cleanup_free_
char *language
= NULL
;
944 (void) find_language_fallback(lang
, &language
);
946 log_debug("Converted LANG=%s to LANGUAGE=%s", lang
, language
);
947 if (!streq_ptr(language
, c
->locale
[LOCALE_LANGUAGE
])) {
948 r
= strv_extendf(&l
, "LANGUAGE=%s", language
);
952 have
[LOCALE_LANGUAGE
] = true;
958 /* Check whether a variable is unset */
960 for (p
= 0; p
< _LOCALE_MAX
; p
++)
961 if (!isempty(c
->locale
[p
]) && !have
[p
]) {
967 _cleanup_strv_free_
char **settings
= NULL
;
969 r
= bus_verify_polkit_async(
972 "org.freedesktop.locale1.set-locale",
981 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
984 for (p
= 0; p
< _LOCALE_MAX
; p
++) {
987 k
= strlen(names
[p
]);
988 if (startswith(*i
, names
[p
]) && (*i
)[k
] == '=') {
989 r
= free_and_strdup(&c
->locale
[p
], *i
+ k
+ 1);
996 for (p
= 0; p
< _LOCALE_MAX
; p
++) {
1000 c
->locale
[p
] = mfree(c
->locale
[p
]);
1005 r
= locale_write_data(c
, &settings
);
1007 log_error_errno(r
, "Failed to set locale: %m");
1008 return sd_bus_error_set_errnof(error
, r
, "Failed to set locale: %s", strerror(-r
));
1011 locale_update_system_manager(c
, sd_bus_message_get_bus(m
));
1014 _cleanup_free_
char *line
;
1016 line
= strv_join(settings
, ", ");
1017 log_info("Changed locale to %s.", strnull(line
));
1019 log_info("Changed locale to unset.");
1021 (void) sd_bus_emit_properties_changed(
1022 sd_bus_message_get_bus(m
),
1023 "/org/freedesktop/locale1",
1024 "org.freedesktop.locale1",
1027 log_debug("Locale settings were not modified.");
1030 return sd_bus_reply_method_return(m
, NULL
);
1033 static int method_set_vc_keyboard(sd_bus_message
*m
, void *userdata
, sd_bus_error
*error
) {
1034 Context
*c
= userdata
;
1035 const char *keymap
, *keymap_toggle
;
1036 int convert
, interactive
;
1042 r
= sd_bus_message_read(m
, "ssbb", &keymap
, &keymap_toggle
, &convert
, &interactive
);
1046 if (isempty(keymap
))
1049 if (isempty(keymap_toggle
))
1050 keymap_toggle
= NULL
;
1052 if (!streq_ptr(keymap
, c
->vc_keymap
) ||
1053 !streq_ptr(keymap_toggle
, c
->vc_keymap_toggle
)) {
1055 if ((keymap
&& (!filename_is_valid(keymap
) || !string_is_safe(keymap
))) ||
1056 (keymap_toggle
&& (!filename_is_valid(keymap_toggle
) || !string_is_safe(keymap_toggle
))))
1057 return sd_bus_error_set_errnof(error
, -EINVAL
, "Received invalid keymap data");
1059 r
= bus_verify_polkit_async(
1062 "org.freedesktop.locale1.set-keyboard",
1066 &c
->polkit_registry
,
1071 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
1073 if (free_and_strdup(&c
->vc_keymap
, keymap
) < 0 ||
1074 free_and_strdup(&c
->vc_keymap_toggle
, keymap_toggle
) < 0)
1077 r
= vconsole_write_data(c
);
1079 log_error_errno(r
, "Failed to set virtual console keymap: %m");
1080 return sd_bus_error_set_errnof(error
, r
, "Failed to set virtual console keymap: %s", strerror(-r
));
1083 log_info("Changed virtual console keymap to '%s' toggle '%s'",
1084 strempty(c
->vc_keymap
), strempty(c
->vc_keymap_toggle
));
1086 r
= vconsole_reload(sd_bus_message_get_bus(m
));
1088 log_error_errno(r
, "Failed to request keymap reload: %m");
1090 (void) sd_bus_emit_properties_changed(
1091 sd_bus_message_get_bus(m
),
1092 "/org/freedesktop/locale1",
1093 "org.freedesktop.locale1",
1094 "VConsoleKeymap", "VConsoleKeymapToggle", NULL
);
1097 r
= vconsole_convert_to_x11(c
, sd_bus_message_get_bus(m
));
1099 log_error_errno(r
, "Failed to convert keymap data: %m");
1103 return sd_bus_reply_method_return(m
, NULL
);
1106 #ifdef HAVE_XKBCOMMON
1108 static void log_xkb(struct xkb_context
*ctx
, enum xkb_log_level lvl
, const char *format
, va_list args
) {
1111 fmt
= strjoina("libxkbcommon: ", format
);
1112 log_internalv(LOG_DEBUG
, 0, __FILE__
, __LINE__
, __func__
, fmt
, args
);
1115 static int verify_xkb_rmlvo(const char *model
, const char *layout
, const char *variant
, const char *options
) {
1116 const struct xkb_rule_names rmlvo
= {
1122 struct xkb_context
*ctx
= NULL
;
1123 struct xkb_keymap
*km
= NULL
;
1126 /* compile keymap from RMLVO information to check out its validity */
1128 ctx
= xkb_context_new(XKB_CONTEXT_NO_ENVIRONMENT_NAMES
);
1134 xkb_context_set_log_fn(ctx
, log_xkb
);
1136 km
= xkb_keymap_new_from_names(ctx
, &rmlvo
, XKB_KEYMAP_COMPILE_NO_FLAGS
);
1145 xkb_keymap_unref(km
);
1146 xkb_context_unref(ctx
);
1150 static int verify_xkb_rmlvo(const char *model
, const char *layout
, const char *variant
, const char *options
) {
1155 static int method_set_x11_keyboard(sd_bus_message
*m
, void *userdata
, sd_bus_error
*error
) {
1156 Context
*c
= userdata
;
1157 const char *layout
, *model
, *variant
, *options
;
1158 int convert
, interactive
;
1164 r
= sd_bus_message_read(m
, "ssssbb", &layout
, &model
, &variant
, &options
, &convert
, &interactive
);
1168 if (isempty(layout
))
1174 if (isempty(variant
))
1177 if (isempty(options
))
1180 if (!streq_ptr(layout
, c
->x11_layout
) ||
1181 !streq_ptr(model
, c
->x11_model
) ||
1182 !streq_ptr(variant
, c
->x11_variant
) ||
1183 !streq_ptr(options
, c
->x11_options
)) {
1185 if ((layout
&& !string_is_safe(layout
)) ||
1186 (model
&& !string_is_safe(model
)) ||
1187 (variant
&& !string_is_safe(variant
)) ||
1188 (options
&& !string_is_safe(options
)))
1189 return sd_bus_error_set_errnof(error
, -EINVAL
, "Received invalid keyboard data");
1191 r
= bus_verify_polkit_async(
1194 "org.freedesktop.locale1.set-keyboard",
1198 &c
->polkit_registry
,
1203 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
1205 r
= verify_xkb_rmlvo(model
, layout
, variant
, options
);
1207 log_error_errno(r
, "Cannot compile XKB keymap for new x11 keyboard layout ('%s' / '%s' / '%s' / '%s'): %m",
1208 strempty(model
), strempty(layout
), strempty(variant
), strempty(options
));
1209 return sd_bus_error_set(error
, SD_BUS_ERROR_INVALID_ARGS
, "Cannot compile XKB keymap, refusing");
1212 if (free_and_strdup(&c
->x11_layout
, layout
) < 0 ||
1213 free_and_strdup(&c
->x11_model
, model
) < 0 ||
1214 free_and_strdup(&c
->x11_variant
, variant
) < 0 ||
1215 free_and_strdup(&c
->x11_options
, options
) < 0)
1218 r
= x11_write_data(c
);
1220 log_error_errno(r
, "Failed to set X11 keyboard layout: %m");
1221 return sd_bus_error_set_errnof(error
, r
, "Failed to set X11 keyboard layout: %s", strerror(-r
));
1224 log_info("Changed X11 keyboard layout to '%s' model '%s' variant '%s' options '%s'",
1225 strempty(c
->x11_layout
),
1226 strempty(c
->x11_model
),
1227 strempty(c
->x11_variant
),
1228 strempty(c
->x11_options
));
1230 (void) sd_bus_emit_properties_changed(
1231 sd_bus_message_get_bus(m
),
1232 "/org/freedesktop/locale1",
1233 "org.freedesktop.locale1",
1234 "X11Layout", "X11Model", "X11Variant", "X11Options", NULL
);
1237 r
= x11_convert_to_vconsole(c
, sd_bus_message_get_bus(m
));
1239 log_error_errno(r
, "Failed to convert keymap data: %m");
1243 return sd_bus_reply_method_return(m
, NULL
);
1246 static const sd_bus_vtable locale_vtable
[] = {
1247 SD_BUS_VTABLE_START(0),
1248 SD_BUS_PROPERTY("Locale", "as", property_get_locale
, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
1249 SD_BUS_PROPERTY("X11Layout", "s", NULL
, offsetof(Context
, x11_layout
), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
1250 SD_BUS_PROPERTY("X11Model", "s", NULL
, offsetof(Context
, x11_model
), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
1251 SD_BUS_PROPERTY("X11Variant", "s", NULL
, offsetof(Context
, x11_variant
), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
1252 SD_BUS_PROPERTY("X11Options", "s", NULL
, offsetof(Context
, x11_options
), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
1253 SD_BUS_PROPERTY("VConsoleKeymap", "s", NULL
, offsetof(Context
, vc_keymap
), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
1254 SD_BUS_PROPERTY("VConsoleKeymapToggle", "s", NULL
, offsetof(Context
, vc_keymap_toggle
), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
1255 SD_BUS_METHOD("SetLocale", "asb", NULL
, method_set_locale
, SD_BUS_VTABLE_UNPRIVILEGED
),
1256 SD_BUS_METHOD("SetVConsoleKeyboard", "ssbb", NULL
, method_set_vc_keyboard
, SD_BUS_VTABLE_UNPRIVILEGED
),
1257 SD_BUS_METHOD("SetX11Keyboard", "ssssbb", NULL
, method_set_x11_keyboard
, SD_BUS_VTABLE_UNPRIVILEGED
),
1261 static int connect_bus(Context
*c
, sd_event
*event
, sd_bus
**_bus
) {
1262 _cleanup_bus_flush_close_unref_ sd_bus
*bus
= NULL
;
1269 r
= sd_bus_default_system(&bus
);
1271 return log_error_errno(r
, "Failed to get system bus connection: %m");
1273 r
= sd_bus_add_object_vtable(bus
, NULL
, "/org/freedesktop/locale1", "org.freedesktop.locale1", locale_vtable
, c
);
1275 return log_error_errno(r
, "Failed to register object: %m");
1277 r
= sd_bus_request_name(bus
, "org.freedesktop.locale1", 0);
1279 return log_error_errno(r
, "Failed to register name: %m");
1281 r
= sd_bus_attach_event(bus
, event
, 0);
1283 return log_error_errno(r
, "Failed to attach bus to event loop: %m");
1291 int main(int argc
, char *argv
[]) {
1292 _cleanup_(context_free
) Context context
= {};
1293 _cleanup_event_unref_ sd_event
*event
= NULL
;
1294 _cleanup_bus_flush_close_unref_ sd_bus
*bus
= NULL
;
1297 log_set_target(LOG_TARGET_AUTO
);
1298 log_parse_environment();
1302 mac_selinux_init("/etc");
1305 log_error("This program takes no arguments.");
1310 r
= sd_event_default(&event
);
1312 log_error_errno(r
, "Failed to allocate event loop: %m");
1316 sd_event_set_watchdog(event
, true);
1318 r
= connect_bus(&context
, event
, &bus
);
1322 r
= context_read_data(&context
);
1324 log_error_errno(r
, "Failed to read locale data: %m");
1328 r
= bus_event_loop_with_idle(event
, bus
, "org.freedesktop.locale1", DEFAULT_EXIT_USEC
, NULL
, NULL
);
1330 log_error_errno(r
, "Failed to run event loop: %m");
1335 return r
< 0 ? EXIT_FAILURE
: EXIT_SUCCESS
;