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"
37 #include "bus-error.h"
38 #include "bus-message.h"
39 #include "event-util.h"
40 #include "locale-util.h"
41 #include "selinux-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 bool startswith_comma(const char *s
, const char *prefix
) {
105 return s
&& (t
= startswith(s
, prefix
)) && (*t
== ',');
108 static void context_free_x11(Context
*c
) {
109 free_and_replace(&c
->x11_layout
, NULL
);
110 free_and_replace(&c
->x11_model
, NULL
);
111 free_and_replace(&c
->x11_variant
, NULL
);
112 free_and_replace(&c
->x11_options
, NULL
);
115 static void context_free_vconsole(Context
*c
) {
116 free_and_replace(&c
->vc_keymap
, NULL
);
117 free_and_replace(&c
->vc_keymap_toggle
, NULL
);
120 static void context_free_locale(Context
*c
) {
123 for (p
= 0; p
< _LOCALE_MAX
; p
++)
124 free_and_replace(&c
->locale
[p
], NULL
);
127 static void context_free(Context
*c
) {
128 context_free_locale(c
);
130 context_free_vconsole(c
);
132 bus_verify_polkit_async_registry_free(c
->polkit_registry
);
135 static void locale_simplify(Context
*c
) {
138 for (p
= LOCALE_LANG
+1; p
< _LOCALE_MAX
; p
++)
139 if (isempty(c
->locale
[p
]) || streq_ptr(c
->locale
[LOCALE_LANG
], c
->locale
[p
]))
140 free_and_replace(&c
->locale
[p
], NULL
);
143 static int locale_read_data(Context
*c
) {
146 context_free_locale(c
);
148 r
= parse_env_file("/etc/locale.conf", NEWLINE
,
149 "LANG", &c
->locale
[LOCALE_LANG
],
150 "LANGUAGE", &c
->locale
[LOCALE_LANGUAGE
],
151 "LC_CTYPE", &c
->locale
[LOCALE_LC_CTYPE
],
152 "LC_NUMERIC", &c
->locale
[LOCALE_LC_NUMERIC
],
153 "LC_TIME", &c
->locale
[LOCALE_LC_TIME
],
154 "LC_COLLATE", &c
->locale
[LOCALE_LC_COLLATE
],
155 "LC_MONETARY", &c
->locale
[LOCALE_LC_MONETARY
],
156 "LC_MESSAGES", &c
->locale
[LOCALE_LC_MESSAGES
],
157 "LC_PAPER", &c
->locale
[LOCALE_LC_PAPER
],
158 "LC_NAME", &c
->locale
[LOCALE_LC_NAME
],
159 "LC_ADDRESS", &c
->locale
[LOCALE_LC_ADDRESS
],
160 "LC_TELEPHONE", &c
->locale
[LOCALE_LC_TELEPHONE
],
161 "LC_MEASUREMENT", &c
->locale
[LOCALE_LC_MEASUREMENT
],
162 "LC_IDENTIFICATION", &c
->locale
[LOCALE_LC_IDENTIFICATION
],
168 /* Fill in what we got passed from systemd. */
169 for (p
= 0; p
< _LOCALE_MAX
; p
++) {
172 r
= free_and_strdup(&c
->locale
[p
],
173 nonempty(getenv(names
[p
])));
185 static int vconsole_read_data(Context
*c
) {
188 context_free_vconsole(c
);
190 r
= parse_env_file("/etc/vconsole.conf", NEWLINE
,
191 "KEYMAP", &c
->vc_keymap
,
192 "KEYMAP_TOGGLE", &c
->vc_keymap_toggle
,
195 if (r
< 0 && r
!= -ENOENT
)
201 static int x11_read_data(Context
*c
) {
202 _cleanup_fclose_
FILE *f
;
204 bool in_section
= false;
209 f
= fopen("/etc/X11/xorg.conf.d/00-keyboard.conf", "re");
211 return errno
== ENOENT
? 0 : -errno
;
213 while (fgets(line
, sizeof(line
), f
)) {
219 if (l
[0] == 0 || l
[0] == '#')
222 if (in_section
&& first_word(l
, "Option")) {
223 _cleanup_strv_free_
char **a
= NULL
;
225 r
= strv_split_extract(&a
, l
, WHITESPACE
, EXTRACT_QUOTES
);
229 if (strv_length(a
) == 3) {
230 if (streq(a
[1], "XkbLayout")) {
231 free_and_replace(&c
->x11_layout
, a
[2]);
233 } else if (streq(a
[1], "XkbModel")) {
234 free_and_replace(&c
->x11_model
, a
[2]);
236 } else if (streq(a
[1], "XkbVariant")) {
237 free_and_replace(&c
->x11_variant
, a
[2]);
239 } else if (streq(a
[1], "XkbOptions")) {
240 free_and_replace(&c
->x11_options
, a
[2]);
245 } else if (!in_section
&& first_word(l
, "Section")) {
246 _cleanup_strv_free_
char **a
= NULL
;
248 r
= strv_split_extract(&a
, l
, WHITESPACE
, EXTRACT_QUOTES
);
252 if (strv_length(a
) == 2 && streq(a
[1], "InputClass"))
255 } else if (in_section
&& first_word(l
, "EndSection"))
262 static int context_read_data(Context
*c
) {
265 r
= locale_read_data(c
);
266 q
= vconsole_read_data(c
);
267 p
= x11_read_data(c
);
269 return r
< 0 ? r
: q
< 0 ? q
: p
;
272 static int locale_write_data(Context
*c
, char ***settings
) {
274 _cleanup_strv_free_
char **l
= NULL
;
276 /* Set values will be returned as strv in *settings on success. */
278 r
= load_env_file(NULL
, "/etc/locale.conf", NULL
, &l
);
279 if (r
< 0 && r
!= -ENOENT
)
282 for (p
= 0; p
< _LOCALE_MAX
; p
++) {
283 _cleanup_free_
char *t
= NULL
;
288 if (isempty(c
->locale
[p
])) {
289 l
= strv_env_unset(l
, names
[p
]);
293 if (asprintf(&t
, "%s=%s", names
[p
], c
->locale
[p
]) < 0)
296 u
= strv_env_set(l
, t
);
304 if (strv_isempty(l
)) {
305 if (unlink("/etc/locale.conf") < 0)
306 return errno
== ENOENT
? 0 : -errno
;
311 r
= write_env_file_label("/etc/locale.conf", l
);
320 static int locale_update_system_manager(Context
*c
, sd_bus
*bus
) {
321 _cleanup_free_
char **l_unset
= NULL
;
322 _cleanup_strv_free_
char **l_set
= NULL
;
323 _cleanup_bus_message_unref_ sd_bus_message
*m
= NULL
;
324 sd_bus_error error
= SD_BUS_ERROR_NULL
;
325 unsigned c_set
, c_unset
, p
;
330 l_unset
= new0(char*, _LOCALE_MAX
);
334 l_set
= new0(char*, _LOCALE_MAX
);
338 for (p
= 0, c_set
= 0, c_unset
= 0; p
< _LOCALE_MAX
; p
++) {
341 if (isempty(c
->locale
[p
]))
342 l_unset
[c_set
++] = (char*) names
[p
];
346 if (asprintf(&s
, "%s=%s", names
[p
], c
->locale
[p
]) < 0)
349 l_set
[c_unset
++] = s
;
353 assert(c_set
+ c_unset
== _LOCALE_MAX
);
354 r
= sd_bus_message_new_method_call(bus
, &m
,
355 "org.freedesktop.systemd1",
356 "/org/freedesktop/systemd1",
357 "org.freedesktop.systemd1.Manager",
358 "UnsetAndSetEnvironment");
362 r
= sd_bus_message_append_strv(m
, l_unset
);
366 r
= sd_bus_message_append_strv(m
, l_set
);
370 r
= sd_bus_call(bus
, m
, 0, &error
, NULL
);
372 log_error_errno(r
, "Failed to update the manager environment: %m");
377 static int vconsole_write_data(Context
*c
) {
379 _cleanup_strv_free_
char **l
= NULL
;
381 r
= load_env_file(NULL
, "/etc/vconsole.conf", NULL
, &l
);
382 if (r
< 0 && r
!= -ENOENT
)
385 if (isempty(c
->vc_keymap
))
386 l
= strv_env_unset(l
, "KEYMAP");
388 _cleanup_free_
char *s
= NULL
;
391 s
= strappend("KEYMAP=", c
->vc_keymap
);
395 u
= strv_env_set(l
, s
);
403 if (isempty(c
->vc_keymap_toggle
))
404 l
= strv_env_unset(l
, "KEYMAP_TOGGLE");
406 _cleanup_free_
char *s
= NULL
;
409 s
= strappend("KEYMAP_TOGGLE=", c
->vc_keymap_toggle
);
413 u
= strv_env_set(l
, s
);
421 if (strv_isempty(l
)) {
422 if (unlink("/etc/vconsole.conf") < 0)
423 return errno
== ENOENT
? 0 : -errno
;
428 return write_env_file_label("/etc/vconsole.conf", l
);
431 static int x11_write_data(Context
*c
) {
432 _cleanup_fclose_
FILE *f
= NULL
;
433 _cleanup_free_
char *temp_path
= NULL
;
436 if (isempty(c
->x11_layout
) &&
437 isempty(c
->x11_model
) &&
438 isempty(c
->x11_variant
) &&
439 isempty(c
->x11_options
)) {
441 if (unlink("/etc/X11/xorg.conf.d/00-keyboard.conf") < 0)
442 return errno
== ENOENT
? 0 : -errno
;
447 mkdir_p_label("/etc/X11/xorg.conf.d", 0755);
449 r
= fopen_temporary("/etc/X11/xorg.conf.d/00-keyboard.conf", &f
, &temp_path
);
453 fchmod(fileno(f
), 0644);
455 fputs("# Read and parsed by systemd-localed. It's probably wise not to edit this file\n"
456 "# manually too freely.\n"
457 "Section \"InputClass\"\n"
458 " Identifier \"system-keyboard\"\n"
459 " MatchIsKeyboard \"on\"\n", f
);
461 if (!isempty(c
->x11_layout
))
462 fprintf(f
, " Option \"XkbLayout\" \"%s\"\n", c
->x11_layout
);
464 if (!isempty(c
->x11_model
))
465 fprintf(f
, " Option \"XkbModel\" \"%s\"\n", c
->x11_model
);
467 if (!isempty(c
->x11_variant
))
468 fprintf(f
, " Option \"XkbVariant\" \"%s\"\n", c
->x11_variant
);
470 if (!isempty(c
->x11_options
))
471 fprintf(f
, " Option \"XkbOptions\" \"%s\"\n", c
->x11_options
);
473 fputs("EndSection\n", f
);
475 r
= fflush_and_check(f
);
479 if (rename(temp_path
, "/etc/X11/xorg.conf.d/00-keyboard.conf") < 0) {
487 (void) unlink("/etc/X11/xorg.conf.d/00-keyboard.conf");
490 (void) unlink(temp_path
);
495 static int vconsole_reload(sd_bus
*bus
) {
496 _cleanup_bus_error_free_ sd_bus_error error
= SD_BUS_ERROR_NULL
;
501 r
= sd_bus_call_method(bus
,
502 "org.freedesktop.systemd1",
503 "/org/freedesktop/systemd1",
504 "org.freedesktop.systemd1.Manager",
508 "ss", "systemd-vconsole-setup.service", "replace");
511 log_error("Failed to issue method call: %s", bus_error_message(&error
, -r
));
515 static const char* strnulldash(const char *s
) {
516 return isempty(s
) || streq(s
, "-") ? NULL
: s
;
519 static int read_next_mapping(const char* filename
,
520 unsigned min_fields
, unsigned max_fields
,
521 FILE *f
, unsigned *n
, char ***a
) {
533 if (!fgets(line
, sizeof(line
), f
)) {
536 return errno
? -errno
: -EIO
;
544 if (l
[0] == 0 || l
[0] == '#')
547 r
= strv_split_extract(&b
, l
, WHITESPACE
, EXTRACT_QUOTES
);
551 length
= strv_length(b
);
552 if (length
< min_fields
|| length
> max_fields
) {
553 log_error("Invalid line %s:%u, ignoring.", filename
, *n
);
564 static int vconsole_convert_to_x11(Context
*c
, sd_bus
*bus
) {
565 bool modified
= false;
569 if (isempty(c
->vc_keymap
)) {
572 !isempty(c
->x11_layout
) ||
573 !isempty(c
->x11_model
) ||
574 !isempty(c
->x11_variant
) ||
575 !isempty(c
->x11_options
);
579 _cleanup_fclose_
FILE *f
= NULL
;
582 f
= fopen(SYSTEMD_KBD_MODEL_MAP
, "re");
587 _cleanup_strv_free_
char **a
= NULL
;
590 r
= read_next_mapping(SYSTEMD_KBD_MODEL_MAP
, 5, UINT_MAX
, f
, &n
, &a
);
596 if (!streq(c
->vc_keymap
, a
[0]))
599 if (!streq_ptr(c
->x11_layout
, strnulldash(a
[1])) ||
600 !streq_ptr(c
->x11_model
, strnulldash(a
[2])) ||
601 !streq_ptr(c
->x11_variant
, strnulldash(a
[3])) ||
602 !streq_ptr(c
->x11_options
, strnulldash(a
[4]))) {
604 if (free_and_strdup(&c
->x11_layout
, strnulldash(a
[1])) < 0 ||
605 free_and_strdup(&c
->x11_model
, strnulldash(a
[2])) < 0 ||
606 free_and_strdup(&c
->x11_variant
, strnulldash(a
[3])) < 0 ||
607 free_and_strdup(&c
->x11_options
, strnulldash(a
[4])) < 0)
620 r
= x11_write_data(c
);
622 return log_error_errno(r
, "Failed to set X11 keyboard layout: %m");
624 log_info("Changed X11 keyboard layout to '%s' model '%s' variant '%s' options '%s'",
625 strempty(c
->x11_layout
),
626 strempty(c
->x11_model
),
627 strempty(c
->x11_variant
),
628 strempty(c
->x11_options
));
630 sd_bus_emit_properties_changed(bus
,
631 "/org/freedesktop/locale1",
632 "org.freedesktop.locale1",
633 "X11Layout", "X11Model", "X11Variant", "X11Options", NULL
);
635 log_debug("X11 keyboard layout was not modified.");
640 static int find_converted_keymap(const char *x11_layout
, const char *x11_variant
, char **new_keymap
) {
642 _cleanup_free_
char *n
;
645 n
= strjoin(x11_layout
, "-", x11_variant
, NULL
);
647 n
= strdup(x11_layout
);
651 NULSTR_FOREACH(dir
, KBD_KEYMAP_DIRS
) {
652 _cleanup_free_
char *p
= NULL
, *pz
= NULL
;
655 p
= strjoin(dir
, "xkb/", n
, ".map", NULL
);
656 pz
= strjoin(dir
, "xkb/", n
, ".map.gz", NULL
);
660 uncompressed
= access(p
, F_OK
) == 0;
661 if (uncompressed
|| access(pz
, F_OK
) == 0) {
662 log_debug("Found converted keymap %s at %s",
663 n
, uncompressed
? p
: pz
);
674 static int find_legacy_keymap(Context
*c
, char **new_keymap
) {
675 _cleanup_fclose_
FILE *f
;
677 unsigned best_matching
= 0;
680 f
= fopen(SYSTEMD_KBD_MODEL_MAP
, "re");
685 _cleanup_strv_free_
char **a
= NULL
;
686 unsigned matching
= 0;
688 r
= read_next_mapping(SYSTEMD_KBD_MODEL_MAP
, 5, UINT_MAX
, f
, &n
, &a
);
694 /* Determine how well matching this entry is */
695 if (streq_ptr(c
->x11_layout
, a
[1]))
696 /* If we got an exact match, this is best */
699 /* We have multiple X layouts, look for an
700 * entry that matches our key with everything
701 * but the first layout stripped off. */
702 if (startswith_comma(c
->x11_layout
, a
[1]))
707 /* If that didn't work, strip off the
708 * other layouts from the entry, too */
709 x
= strndupa(a
[1], strcspn(a
[1], ","));
710 if (startswith_comma(c
->x11_layout
, x
))
716 if (isempty(c
->x11_model
) || streq_ptr(c
->x11_model
, a
[2])) {
719 if (streq_ptr(c
->x11_variant
, a
[3])) {
722 if (streq_ptr(c
->x11_options
, a
[4]))
728 /* The best matching entry so far, then let's save that */
729 if (matching
>= MAX(best_matching
, 1u)) {
730 log_debug("Found legacy keymap %s with score %u",
733 if (matching
> best_matching
) {
734 best_matching
= matching
;
736 r
= free_and_strdup(new_keymap
, a
[0]);
743 if (best_matching
< 10 && c
->x11_layout
) {
744 /* The best match is only the first part of the X11
745 * keymap. Check if we have a converted map which
746 * matches just the first layout.
748 char *l
, *v
= NULL
, *converted
;
750 l
= strndupa(c
->x11_layout
, strcspn(c
->x11_layout
, ","));
752 v
= strndupa(c
->x11_variant
, strcspn(c
->x11_variant
, ","));
753 r
= find_converted_keymap(l
, v
, &converted
);
757 free_and_replace(new_keymap
, converted
);
763 static int find_language_fallback(const char *lang
, char **language
) {
764 _cleanup_fclose_
FILE *f
= NULL
;
769 f
= fopen(SYSTEMD_LANGUAGE_FALLBACK_MAP
, "re");
774 _cleanup_strv_free_
char **a
= NULL
;
777 r
= read_next_mapping(SYSTEMD_LANGUAGE_FALLBACK_MAP
, 2, 2, f
, &n
, &a
);
781 if (streq(lang
, a
[0])) {
782 assert(strv_length(a
) == 2);
789 assert_not_reached("should not be here");
792 static int x11_convert_to_vconsole(Context
*c
, sd_bus
*bus
) {
793 bool modified
= false;
798 if (isempty(c
->x11_layout
)) {
801 !isempty(c
->vc_keymap
) ||
802 !isempty(c
->vc_keymap_toggle
);
806 char *new_keymap
= NULL
;
808 r
= find_converted_keymap(c
->x11_layout
, c
->x11_variant
, &new_keymap
);
812 r
= find_legacy_keymap(c
, &new_keymap
);
817 if (!streq_ptr(c
->vc_keymap
, new_keymap
)) {
818 free_and_replace(&c
->vc_keymap
, new_keymap
);
819 free_and_replace(&c
->vc_keymap_toggle
, NULL
);
826 r
= vconsole_write_data(c
);
828 log_error_errno(r
, "Failed to set virtual console keymap: %m");
830 log_info("Changed virtual console keymap to '%s' toggle '%s'",
831 strempty(c
->vc_keymap
), strempty(c
->vc_keymap_toggle
));
833 sd_bus_emit_properties_changed(bus
,
834 "/org/freedesktop/locale1",
835 "org.freedesktop.locale1",
836 "VConsoleKeymap", "VConsoleKeymapToggle", NULL
);
838 return vconsole_reload(bus
);
840 log_debug("Virtual console keymap was not modified.");
845 static int property_get_locale(
848 const char *interface
,
849 const char *property
,
850 sd_bus_message
*reply
,
852 sd_bus_error
*error
) {
854 Context
*c
= userdata
;
855 _cleanup_strv_free_
char **l
= NULL
;
858 l
= new0(char*, _LOCALE_MAX
+1);
862 for (p
= 0, q
= 0; p
< _LOCALE_MAX
; p
++) {
865 if (isempty(c
->locale
[p
]))
868 if (asprintf(&t
, "%s=%s", names
[p
], c
->locale
[p
]) < 0)
874 return sd_bus_message_append_strv(reply
, l
);
877 static int method_set_locale(sd_bus_message
*m
, void *userdata
, sd_bus_error
*error
) {
878 Context
*c
= userdata
;
879 _cleanup_strv_free_
char **l
= NULL
;
881 const char *lang
= NULL
;
883 bool modified
= false;
884 bool have
[_LOCALE_MAX
] = {};
891 r
= bus_message_read_strv_extend(m
, &l
);
895 r
= sd_bus_message_read_basic(m
, 'b', &interactive
);
899 /* Check whether a variable changed and if it is valid */
903 for (p
= 0; p
< _LOCALE_MAX
; p
++) {
906 k
= strlen(names
[p
]);
907 if (startswith(*i
, names
[p
]) &&
909 locale_is_valid((*i
) + k
+ 1)) {
913 if (p
== LOCALE_LANG
)
916 if (!streq_ptr(*i
+ k
+ 1, c
->locale
[p
]))
924 return sd_bus_error_setf(error
, SD_BUS_ERROR_INVALID_ARGS
, "Invalid Locale data.");
927 /* If LANG was specified, but not LANGUAGE, check if we should
928 * set it based on the language fallback table. */
929 if (have
[LOCALE_LANG
] && !have
[LOCALE_LANGUAGE
]) {
930 _cleanup_free_
char *language
= NULL
;
934 (void) find_language_fallback(lang
, &language
);
936 log_debug("Converted LANG=%s to LANGUAGE=%s", lang
, language
);
937 if (!streq_ptr(language
, c
->locale
[LOCALE_LANGUAGE
])) {
938 r
= strv_extendf(&l
, "LANGUAGE=%s", language
);
942 have
[LOCALE_LANGUAGE
] = true;
948 /* Check whether a variable is unset */
950 for (p
= 0; p
< _LOCALE_MAX
; p
++)
951 if (!isempty(c
->locale
[p
]) && !have
[p
]) {
957 _cleanup_strv_free_
char **settings
= NULL
;
959 r
= bus_verify_polkit_async(
962 "org.freedesktop.locale1.set-locale",
970 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
973 for (p
= 0; p
< _LOCALE_MAX
; p
++) {
976 k
= strlen(names
[p
]);
977 if (startswith(*i
, names
[p
]) && (*i
)[k
] == '=') {
978 r
= free_and_strdup(&c
->locale
[p
], *i
+ k
+ 1);
985 for (p
= 0; p
< _LOCALE_MAX
; p
++) {
989 free_and_replace(&c
->locale
[p
], NULL
);
994 r
= locale_write_data(c
, &settings
);
996 log_error_errno(r
, "Failed to set locale: %m");
997 return sd_bus_error_set_errnof(error
, r
, "Failed to set locale: %s", strerror(-r
));
1000 locale_update_system_manager(c
, sd_bus_message_get_bus(m
));
1003 _cleanup_free_
char *line
;
1005 line
= strv_join(settings
, ", ");
1006 log_info("Changed locale to %s.", strnull(line
));
1008 log_info("Changed locale to unset.");
1010 (void) sd_bus_emit_properties_changed(
1011 sd_bus_message_get_bus(m
),
1012 "/org/freedesktop/locale1",
1013 "org.freedesktop.locale1",
1016 log_debug("Locale settings were not modified.");
1019 return sd_bus_reply_method_return(m
, NULL
);
1022 static int method_set_vc_keyboard(sd_bus_message
*m
, void *userdata
, sd_bus_error
*error
) {
1023 Context
*c
= userdata
;
1024 const char *keymap
, *keymap_toggle
;
1025 int convert
, interactive
;
1031 r
= sd_bus_message_read(m
, "ssbb", &keymap
, &keymap_toggle
, &convert
, &interactive
);
1035 if (isempty(keymap
))
1038 if (isempty(keymap_toggle
))
1039 keymap_toggle
= NULL
;
1041 if (!streq_ptr(keymap
, c
->vc_keymap
) ||
1042 !streq_ptr(keymap_toggle
, c
->vc_keymap_toggle
)) {
1044 if ((keymap
&& (!filename_is_valid(keymap
) || !string_is_safe(keymap
))) ||
1045 (keymap_toggle
&& (!filename_is_valid(keymap_toggle
) || !string_is_safe(keymap_toggle
))))
1046 return sd_bus_error_set_errnof(error
, -EINVAL
, "Received invalid keymap data");
1048 r
= bus_verify_polkit_async(
1051 "org.freedesktop.locale1.set-keyboard",
1054 &c
->polkit_registry
,
1059 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
1061 if (free_and_strdup(&c
->vc_keymap
, keymap
) < 0 ||
1062 free_and_strdup(&c
->vc_keymap_toggle
, keymap_toggle
) < 0)
1065 r
= vconsole_write_data(c
);
1067 log_error_errno(r
, "Failed to set virtual console keymap: %m");
1068 return sd_bus_error_set_errnof(error
, r
, "Failed to set virtual console keymap: %s", strerror(-r
));
1071 log_info("Changed virtual console keymap to '%s' toggle '%s'",
1072 strempty(c
->vc_keymap
), strempty(c
->vc_keymap_toggle
));
1074 r
= vconsole_reload(sd_bus_message_get_bus(m
));
1076 log_error_errno(r
, "Failed to request keymap reload: %m");
1078 (void) sd_bus_emit_properties_changed(
1079 sd_bus_message_get_bus(m
),
1080 "/org/freedesktop/locale1",
1081 "org.freedesktop.locale1",
1082 "VConsoleKeymap", "VConsoleKeymapToggle", NULL
);
1085 r
= vconsole_convert_to_x11(c
, sd_bus_message_get_bus(m
));
1087 log_error_errno(r
, "Failed to convert keymap data: %m");
1091 return sd_bus_reply_method_return(m
, NULL
);
1094 #ifdef HAVE_XKBCOMMON
1095 static void log_xkb(struct xkb_context
*ctx
, enum xkb_log_level lvl
, const char *format
, va_list args
) {
1098 fmt
= strjoina("libxkbcommon: ", format
);
1099 log_internalv(LOG_DEBUG
, 0, __FILE__
, __LINE__
, __func__
, fmt
, args
);
1102 static int verify_xkb_rmlvo(const char *model
, const char *layout
, const char *variant
, const char *options
) {
1103 const struct xkb_rule_names rmlvo
= {
1109 struct xkb_context
*ctx
= NULL
;
1110 struct xkb_keymap
*km
= NULL
;
1113 /* compile keymap from RMLVO information to check out its validity */
1115 ctx
= xkb_context_new(XKB_CONTEXT_NO_ENVIRONMENT_NAMES
);
1121 xkb_context_set_log_fn(ctx
, log_xkb
);
1123 km
= xkb_keymap_new_from_names(ctx
, &rmlvo
, XKB_KEYMAP_COMPILE_NO_FLAGS
);
1132 xkb_keymap_unref(km
);
1133 xkb_context_unref(ctx
);
1137 static int verify_xkb_rmlvo(const char *model
, const char *layout
, const char *variant
, const char *options
) {
1142 static int method_set_x11_keyboard(sd_bus_message
*m
, void *userdata
, sd_bus_error
*error
) {
1143 Context
*c
= userdata
;
1144 const char *layout
, *model
, *variant
, *options
;
1145 int convert
, interactive
;
1151 r
= sd_bus_message_read(m
, "ssssbb", &layout
, &model
, &variant
, &options
, &convert
, &interactive
);
1155 if (isempty(layout
))
1161 if (isempty(variant
))
1164 if (isempty(options
))
1167 if (!streq_ptr(layout
, c
->x11_layout
) ||
1168 !streq_ptr(model
, c
->x11_model
) ||
1169 !streq_ptr(variant
, c
->x11_variant
) ||
1170 !streq_ptr(options
, c
->x11_options
)) {
1172 if ((layout
&& !string_is_safe(layout
)) ||
1173 (model
&& !string_is_safe(model
)) ||
1174 (variant
&& !string_is_safe(variant
)) ||
1175 (options
&& !string_is_safe(options
)))
1176 return sd_bus_error_set_errnof(error
, -EINVAL
, "Received invalid keyboard data");
1178 r
= bus_verify_polkit_async(
1181 "org.freedesktop.locale1.set-keyboard",
1184 &c
->polkit_registry
,
1189 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
1191 r
= verify_xkb_rmlvo(model
, layout
, variant
, options
);
1193 log_error_errno(r
, "Cannot compile XKB keymap for new x11 keyboard layout ('%s' / '%s' / '%s' / '%s'): %m",
1194 strempty(model
), strempty(layout
), strempty(variant
), strempty(options
));
1195 return sd_bus_error_set(error
, SD_BUS_ERROR_INVALID_ARGS
, "Cannot compile XKB keymap, refusing");
1198 if (free_and_strdup(&c
->x11_layout
, layout
) < 0 ||
1199 free_and_strdup(&c
->x11_model
, model
) < 0 ||
1200 free_and_strdup(&c
->x11_variant
, variant
) < 0 ||
1201 free_and_strdup(&c
->x11_options
, options
) < 0)
1204 r
= x11_write_data(c
);
1206 log_error_errno(r
, "Failed to set X11 keyboard layout: %m");
1207 return sd_bus_error_set_errnof(error
, r
, "Failed to set X11 keyboard layout: %s", strerror(-r
));
1210 log_info("Changed X11 keyboard layout to '%s' model '%s' variant '%s' options '%s'",
1211 strempty(c
->x11_layout
),
1212 strempty(c
->x11_model
),
1213 strempty(c
->x11_variant
),
1214 strempty(c
->x11_options
));
1216 (void) sd_bus_emit_properties_changed(
1217 sd_bus_message_get_bus(m
),
1218 "/org/freedesktop/locale1",
1219 "org.freedesktop.locale1",
1220 "X11Layout", "X11Model", "X11Variant", "X11Options", NULL
);
1223 r
= x11_convert_to_vconsole(c
, sd_bus_message_get_bus(m
));
1225 log_error_errno(r
, "Failed to convert keymap data: %m");
1229 return sd_bus_reply_method_return(m
, NULL
);
1232 static const sd_bus_vtable locale_vtable
[] = {
1233 SD_BUS_VTABLE_START(0),
1234 SD_BUS_PROPERTY("Locale", "as", property_get_locale
, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
1235 SD_BUS_PROPERTY("X11Layout", "s", NULL
, offsetof(Context
, x11_layout
), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
1236 SD_BUS_PROPERTY("X11Model", "s", NULL
, offsetof(Context
, x11_model
), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
1237 SD_BUS_PROPERTY("X11Variant", "s", NULL
, offsetof(Context
, x11_variant
), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
1238 SD_BUS_PROPERTY("X11Options", "s", NULL
, offsetof(Context
, x11_options
), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
1239 SD_BUS_PROPERTY("VConsoleKeymap", "s", NULL
, offsetof(Context
, vc_keymap
), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
1240 SD_BUS_PROPERTY("VConsoleKeymapToggle", "s", NULL
, offsetof(Context
, vc_keymap_toggle
), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
1241 SD_BUS_METHOD("SetLocale", "asb", NULL
, method_set_locale
, SD_BUS_VTABLE_UNPRIVILEGED
),
1242 SD_BUS_METHOD("SetVConsoleKeyboard", "ssbb", NULL
, method_set_vc_keyboard
, SD_BUS_VTABLE_UNPRIVILEGED
),
1243 SD_BUS_METHOD("SetX11Keyboard", "ssssbb", NULL
, method_set_x11_keyboard
, SD_BUS_VTABLE_UNPRIVILEGED
),
1247 static int connect_bus(Context
*c
, sd_event
*event
, sd_bus
**_bus
) {
1248 _cleanup_bus_flush_close_unref_ sd_bus
*bus
= NULL
;
1255 r
= sd_bus_default_system(&bus
);
1257 return log_error_errno(r
, "Failed to get system bus connection: %m");
1259 r
= sd_bus_add_object_vtable(bus
, NULL
, "/org/freedesktop/locale1", "org.freedesktop.locale1", locale_vtable
, c
);
1261 return log_error_errno(r
, "Failed to register object: %m");
1263 r
= sd_bus_request_name(bus
, "org.freedesktop.locale1", 0);
1265 return log_error_errno(r
, "Failed to register name: %m");
1267 r
= sd_bus_attach_event(bus
, event
, 0);
1269 return log_error_errno(r
, "Failed to attach bus to event loop: %m");
1277 int main(int argc
, char *argv
[]) {
1278 _cleanup_(context_free
) Context context
= {};
1279 _cleanup_event_unref_ sd_event
*event
= NULL
;
1280 _cleanup_bus_flush_close_unref_ sd_bus
*bus
= NULL
;
1283 log_set_target(LOG_TARGET_AUTO
);
1284 log_parse_environment();
1288 mac_selinux_init("/etc");
1291 log_error("This program takes no arguments.");
1296 r
= sd_event_default(&event
);
1298 log_error_errno(r
, "Failed to allocate event loop: %m");
1302 sd_event_set_watchdog(event
, true);
1304 r
= connect_bus(&context
, event
, &bus
);
1308 r
= context_read_data(&context
);
1310 log_error_errno(r
, "Failed to read locale data: %m");
1314 r
= bus_event_loop_with_idle(event
, bus
, "org.freedesktop.locale1", DEFAULT_EXIT_USEC
, NULL
, NULL
);
1316 log_error_errno(r
, "Failed to run event loop: %m");
1321 return r
< 0 ? EXIT_FAILURE
: EXIT_SUCCESS
;