2 This file is part of systemd.
4 Copyright 2011 Lennart Poettering
5 Copyright 2013 Kay Sievers
7 systemd is free software; you can redistribute it and/or modify it
8 under the terms of the GNU Lesser General Public License as published by
9 the Free Software Foundation; either version 2.1 of the License, or
10 (at your option) any later version.
12 systemd is distributed in the hope that it will be useful, but
13 WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 Lesser General Public License for more details.
17 You should have received a copy of the GNU Lesser General Public License
18 along with systemd; If not, see <http://www.gnu.org/licenses/>.
26 #include <xkbcommon/xkbcommon.h>
32 #include "alloc-util.h"
33 #include "bus-error.h"
34 #include "bus-message.h"
39 #include "fileio-label.h"
41 #include "locale-util.h"
43 #include "path-util.h"
44 #include "selinux-util.h"
46 #include "user-util.h"
50 /* We don't list LC_ALL here on purpose. People should be
51 * using LANG instead. */
64 LOCALE_LC_MEASUREMENT
,
65 LOCALE_LC_IDENTIFICATION
,
69 static const char * const names
[_LOCALE_MAX
] = {
70 [LOCALE_LANG
] = "LANG",
71 [LOCALE_LANGUAGE
] = "LANGUAGE",
72 [LOCALE_LC_CTYPE
] = "LC_CTYPE",
73 [LOCALE_LC_NUMERIC
] = "LC_NUMERIC",
74 [LOCALE_LC_TIME
] = "LC_TIME",
75 [LOCALE_LC_COLLATE
] = "LC_COLLATE",
76 [LOCALE_LC_MONETARY
] = "LC_MONETARY",
77 [LOCALE_LC_MESSAGES
] = "LC_MESSAGES",
78 [LOCALE_LC_PAPER
] = "LC_PAPER",
79 [LOCALE_LC_NAME
] = "LC_NAME",
80 [LOCALE_LC_ADDRESS
] = "LC_ADDRESS",
81 [LOCALE_LC_TELEPHONE
] = "LC_TELEPHONE",
82 [LOCALE_LC_MEASUREMENT
] = "LC_MEASUREMENT",
83 [LOCALE_LC_IDENTIFICATION
] = "LC_IDENTIFICATION"
86 typedef struct Context
{
87 char *locale
[_LOCALE_MAX
];
95 char *vc_keymap_toggle
;
97 Hashmap
*polkit_registry
;
100 static const char* nonempty(const char *s
) {
101 return isempty(s
) ? NULL
: s
;
104 static bool startswith_comma(const char *s
, const char *prefix
) {
107 return s
&& (t
= startswith(s
, prefix
)) && (*t
== ',');
110 static void context_free_x11(Context
*c
) {
111 c
->x11_layout
= mfree(c
->x11_layout
);
112 c
->x11_options
= mfree(c
->x11_options
);
113 c
->x11_model
= mfree(c
->x11_model
);
114 c
->x11_variant
= mfree(c
->x11_variant
);
117 static void context_free_vconsole(Context
*c
) {
118 c
->vc_keymap
= mfree(c
->vc_keymap
);
119 c
->vc_keymap_toggle
= mfree(c
->vc_keymap_toggle
);
122 static void context_free_locale(Context
*c
) {
125 for (p
= 0; p
< _LOCALE_MAX
; p
++)
126 c
->locale
[p
] = mfree(c
->locale
[p
]);
129 static void context_free(Context
*c
) {
130 context_free_locale(c
);
132 context_free_vconsole(c
);
134 bus_verify_polkit_async_registry_free(c
->polkit_registry
);
137 static void locale_simplify(Context
*c
) {
140 for (p
= LOCALE_LANG
+1; p
< _LOCALE_MAX
; p
++)
141 if (isempty(c
->locale
[p
]) || streq_ptr(c
->locale
[LOCALE_LANG
], c
->locale
[p
]))
142 c
->locale
[p
] = mfree(c
->locale
[p
]);
145 static int locale_read_data(Context
*c
) {
148 context_free_locale(c
);
150 r
= parse_env_file("/etc/locale.conf", NEWLINE
,
151 "LANG", &c
->locale
[LOCALE_LANG
],
152 "LANGUAGE", &c
->locale
[LOCALE_LANGUAGE
],
153 "LC_CTYPE", &c
->locale
[LOCALE_LC_CTYPE
],
154 "LC_NUMERIC", &c
->locale
[LOCALE_LC_NUMERIC
],
155 "LC_TIME", &c
->locale
[LOCALE_LC_TIME
],
156 "LC_COLLATE", &c
->locale
[LOCALE_LC_COLLATE
],
157 "LC_MONETARY", &c
->locale
[LOCALE_LC_MONETARY
],
158 "LC_MESSAGES", &c
->locale
[LOCALE_LC_MESSAGES
],
159 "LC_PAPER", &c
->locale
[LOCALE_LC_PAPER
],
160 "LC_NAME", &c
->locale
[LOCALE_LC_NAME
],
161 "LC_ADDRESS", &c
->locale
[LOCALE_LC_ADDRESS
],
162 "LC_TELEPHONE", &c
->locale
[LOCALE_LC_TELEPHONE
],
163 "LC_MEASUREMENT", &c
->locale
[LOCALE_LC_MEASUREMENT
],
164 "LC_IDENTIFICATION", &c
->locale
[LOCALE_LC_IDENTIFICATION
],
170 /* Fill in what we got passed from systemd. */
171 for (p
= 0; p
< _LOCALE_MAX
; p
++) {
174 r
= free_and_strdup(&c
->locale
[p
],
175 nonempty(getenv(names
[p
])));
187 static int vconsole_read_data(Context
*c
) {
190 context_free_vconsole(c
);
192 r
= parse_env_file("/etc/vconsole.conf", NEWLINE
,
193 "KEYMAP", &c
->vc_keymap
,
194 "KEYMAP_TOGGLE", &c
->vc_keymap_toggle
,
197 if (r
< 0 && r
!= -ENOENT
)
203 static int x11_read_data(Context
*c
) {
204 _cleanup_fclose_
FILE *f
;
206 bool in_section
= false;
211 f
= fopen("/etc/X11/xorg.conf.d/00-keyboard.conf", "re");
213 return errno
== ENOENT
? 0 : -errno
;
215 while (fgets(line
, sizeof(line
), f
)) {
221 if (l
[0] == 0 || l
[0] == '#')
224 if (in_section
&& first_word(l
, "Option")) {
225 _cleanup_strv_free_
char **a
= NULL
;
227 r
= strv_split_extract(&a
, l
, WHITESPACE
, EXTRACT_QUOTES
);
231 if (strv_length(a
) == 3) {
234 if (streq(a
[1], "XkbLayout"))
236 else if (streq(a
[1], "XkbModel"))
238 else if (streq(a
[1], "XkbVariant"))
240 else if (streq(a
[1], "XkbOptions"))
250 } else if (!in_section
&& first_word(l
, "Section")) {
251 _cleanup_strv_free_
char **a
= NULL
;
253 r
= strv_split_extract(&a
, l
, WHITESPACE
, EXTRACT_QUOTES
);
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_(sd_bus_message_unrefp
) 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
);
480 r
= fflush_and_check(f
);
484 if (rename(temp_path
, "/etc/X11/xorg.conf.d/00-keyboard.conf") < 0) {
492 (void) unlink("/etc/X11/xorg.conf.d/00-keyboard.conf");
495 (void) unlink(temp_path
);
500 static int vconsole_reload(sd_bus
*bus
) {
501 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
506 r
= sd_bus_call_method(bus
,
507 "org.freedesktop.systemd1",
508 "/org/freedesktop/systemd1",
509 "org.freedesktop.systemd1.Manager",
513 "ss", "systemd-vconsole-setup.service", "replace");
516 log_error("Failed to issue method call: %s", bus_error_message(&error
, -r
));
520 static const char* strnulldash(const char *s
) {
521 return isempty(s
) || streq(s
, "-") ? NULL
: s
;
524 static int read_next_mapping(const char* filename
,
525 unsigned min_fields
, unsigned max_fields
,
526 FILE *f
, unsigned *n
, char ***a
) {
538 if (!fgets(line
, sizeof(line
), f
)) {
541 return errno
> 0 ? -errno
: -EIO
;
549 if (l
[0] == 0 || l
[0] == '#')
552 r
= strv_split_extract(&b
, l
, WHITESPACE
, EXTRACT_QUOTES
);
556 length
= strv_length(b
);
557 if (length
< min_fields
|| length
> max_fields
) {
558 log_error("Invalid line %s:%u, ignoring.", filename
, *n
);
569 static int vconsole_convert_to_x11(Context
*c
, sd_bus
*bus
) {
570 bool modified
= false;
574 if (isempty(c
->vc_keymap
)) {
577 !isempty(c
->x11_layout
) ||
578 !isempty(c
->x11_model
) ||
579 !isempty(c
->x11_variant
) ||
580 !isempty(c
->x11_options
);
584 _cleanup_fclose_
FILE *f
= NULL
;
587 f
= fopen(SYSTEMD_KBD_MODEL_MAP
, "re");
592 _cleanup_strv_free_
char **a
= NULL
;
595 r
= read_next_mapping(SYSTEMD_KBD_MODEL_MAP
, 5, UINT_MAX
, f
, &n
, &a
);
601 if (!streq(c
->vc_keymap
, a
[0]))
604 if (!streq_ptr(c
->x11_layout
, strnulldash(a
[1])) ||
605 !streq_ptr(c
->x11_model
, strnulldash(a
[2])) ||
606 !streq_ptr(c
->x11_variant
, strnulldash(a
[3])) ||
607 !streq_ptr(c
->x11_options
, strnulldash(a
[4]))) {
609 if (free_and_strdup(&c
->x11_layout
, strnulldash(a
[1])) < 0 ||
610 free_and_strdup(&c
->x11_model
, strnulldash(a
[2])) < 0 ||
611 free_and_strdup(&c
->x11_variant
, strnulldash(a
[3])) < 0 ||
612 free_and_strdup(&c
->x11_options
, strnulldash(a
[4])) < 0)
625 r
= x11_write_data(c
);
627 return log_error_errno(r
, "Failed to set X11 keyboard layout: %m");
629 log_info("Changed X11 keyboard layout to '%s' model '%s' variant '%s' options '%s'",
630 strempty(c
->x11_layout
),
631 strempty(c
->x11_model
),
632 strempty(c
->x11_variant
),
633 strempty(c
->x11_options
));
635 sd_bus_emit_properties_changed(bus
,
636 "/org/freedesktop/locale1",
637 "org.freedesktop.locale1",
638 "X11Layout", "X11Model", "X11Variant", "X11Options", NULL
);
640 log_debug("X11 keyboard layout was not modified.");
645 static int find_converted_keymap(const char *x11_layout
, const char *x11_variant
, char **new_keymap
) {
647 _cleanup_free_
char *n
;
650 n
= strjoin(x11_layout
, "-", x11_variant
, NULL
);
652 n
= strdup(x11_layout
);
656 NULSTR_FOREACH(dir
, KBD_KEYMAP_DIRS
) {
657 _cleanup_free_
char *p
= NULL
, *pz
= NULL
;
660 p
= strjoin(dir
, "xkb/", n
, ".map", NULL
);
661 pz
= strjoin(dir
, "xkb/", n
, ".map.gz", NULL
);
665 uncompressed
= access(p
, F_OK
) == 0;
666 if (uncompressed
|| access(pz
, F_OK
) == 0) {
667 log_debug("Found converted keymap %s at %s",
668 n
, uncompressed
? p
: pz
);
679 static int find_legacy_keymap(Context
*c
, char **new_keymap
) {
680 _cleanup_fclose_
FILE *f
;
682 unsigned best_matching
= 0;
685 f
= fopen(SYSTEMD_KBD_MODEL_MAP
, "re");
690 _cleanup_strv_free_
char **a
= NULL
;
691 unsigned matching
= 0;
693 r
= read_next_mapping(SYSTEMD_KBD_MODEL_MAP
, 5, UINT_MAX
, f
, &n
, &a
);
699 /* Determine how well matching this entry is */
700 if (streq_ptr(c
->x11_layout
, a
[1]))
701 /* If we got an exact match, this is best */
704 /* We have multiple X layouts, look for an
705 * entry that matches our key with everything
706 * but the first layout stripped off. */
707 if (startswith_comma(c
->x11_layout
, a
[1]))
712 /* If that didn't work, strip off the
713 * other layouts from the entry, too */
714 x
= strndupa(a
[1], strcspn(a
[1], ","));
715 if (startswith_comma(c
->x11_layout
, x
))
721 if (isempty(c
->x11_model
) || streq_ptr(c
->x11_model
, a
[2])) {
724 if (streq_ptr(c
->x11_variant
, a
[3])) {
727 if (streq_ptr(c
->x11_options
, a
[4]))
733 /* The best matching entry so far, then let's save that */
734 if (matching
>= MAX(best_matching
, 1u)) {
735 log_debug("Found legacy keymap %s with score %u",
738 if (matching
> best_matching
) {
739 best_matching
= matching
;
741 r
= free_and_strdup(new_keymap
, a
[0]);
748 if (best_matching
< 10 && c
->x11_layout
) {
749 /* The best match is only the first part of the X11
750 * keymap. Check if we have a converted map which
751 * matches just the first layout.
753 char *l
, *v
= NULL
, *converted
;
755 l
= strndupa(c
->x11_layout
, strcspn(c
->x11_layout
, ","));
757 v
= strndupa(c
->x11_variant
, strcspn(c
->x11_variant
, ","));
758 r
= find_converted_keymap(l
, v
, &converted
);
763 *new_keymap
= converted
;
770 static int find_language_fallback(const char *lang
, char **language
) {
771 _cleanup_fclose_
FILE *f
= NULL
;
776 f
= fopen(SYSTEMD_LANGUAGE_FALLBACK_MAP
, "re");
781 _cleanup_strv_free_
char **a
= NULL
;
784 r
= read_next_mapping(SYSTEMD_LANGUAGE_FALLBACK_MAP
, 2, 2, f
, &n
, &a
);
788 if (streq(lang
, a
[0])) {
789 assert(strv_length(a
) == 2);
796 assert_not_reached("should not be here");
799 static int x11_convert_to_vconsole(Context
*c
, sd_bus
*bus
) {
800 bool modified
= false;
805 if (isempty(c
->x11_layout
)) {
808 !isempty(c
->vc_keymap
) ||
809 !isempty(c
->vc_keymap_toggle
);
813 char *new_keymap
= NULL
;
815 r
= find_converted_keymap(c
->x11_layout
, c
->x11_variant
, &new_keymap
);
819 r
= find_legacy_keymap(c
, &new_keymap
);
824 if (!streq_ptr(c
->vc_keymap
, new_keymap
)) {
826 c
->vc_keymap
= new_keymap
;
827 c
->vc_keymap_toggle
= mfree(c
->vc_keymap_toggle
);
834 r
= vconsole_write_data(c
);
836 log_error_errno(r
, "Failed to set virtual console keymap: %m");
838 log_info("Changed virtual console keymap to '%s' toggle '%s'",
839 strempty(c
->vc_keymap
), strempty(c
->vc_keymap_toggle
));
841 sd_bus_emit_properties_changed(bus
,
842 "/org/freedesktop/locale1",
843 "org.freedesktop.locale1",
844 "VConsoleKeymap", "VConsoleKeymapToggle", NULL
);
846 return vconsole_reload(bus
);
848 log_debug("Virtual console keymap was not modified.");
853 static int property_get_locale(
856 const char *interface
,
857 const char *property
,
858 sd_bus_message
*reply
,
860 sd_bus_error
*error
) {
862 Context
*c
= userdata
;
863 _cleanup_strv_free_
char **l
= NULL
;
866 l
= new0(char*, _LOCALE_MAX
+1);
870 for (p
= 0, q
= 0; p
< _LOCALE_MAX
; p
++) {
873 if (isempty(c
->locale
[p
]))
876 if (asprintf(&t
, "%s=%s", names
[p
], c
->locale
[p
]) < 0)
882 return sd_bus_message_append_strv(reply
, l
);
885 static int method_set_locale(sd_bus_message
*m
, void *userdata
, sd_bus_error
*error
) {
886 Context
*c
= userdata
;
887 _cleanup_strv_free_
char **l
= NULL
;
889 const char *lang
= NULL
;
891 bool modified
= false;
892 bool have
[_LOCALE_MAX
] = {};
899 r
= bus_message_read_strv_extend(m
, &l
);
903 r
= sd_bus_message_read_basic(m
, 'b', &interactive
);
907 /* Check whether a variable changed and if it is valid */
911 for (p
= 0; p
< _LOCALE_MAX
; p
++) {
914 k
= strlen(names
[p
]);
915 if (startswith(*i
, names
[p
]) &&
917 locale_is_valid((*i
) + k
+ 1)) {
921 if (p
== LOCALE_LANG
)
924 if (!streq_ptr(*i
+ k
+ 1, c
->locale
[p
]))
932 return sd_bus_error_setf(error
, SD_BUS_ERROR_INVALID_ARGS
, "Invalid Locale data.");
935 /* If LANG was specified, but not LANGUAGE, check if we should
936 * set it based on the language fallback table. */
937 if (have
[LOCALE_LANG
] && !have
[LOCALE_LANGUAGE
]) {
938 _cleanup_free_
char *language
= NULL
;
942 (void) find_language_fallback(lang
, &language
);
944 log_debug("Converted LANG=%s to LANGUAGE=%s", lang
, language
);
945 if (!streq_ptr(language
, c
->locale
[LOCALE_LANGUAGE
])) {
946 r
= strv_extendf(&l
, "LANGUAGE=%s", language
);
950 have
[LOCALE_LANGUAGE
] = true;
956 /* Check whether a variable is unset */
958 for (p
= 0; p
< _LOCALE_MAX
; p
++)
959 if (!isempty(c
->locale
[p
]) && !have
[p
]) {
965 _cleanup_strv_free_
char **settings
= NULL
;
967 r
= bus_verify_polkit_async(
970 "org.freedesktop.locale1.set-locale",
979 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
982 for (p
= 0; p
< _LOCALE_MAX
; p
++) {
985 k
= strlen(names
[p
]);
986 if (startswith(*i
, names
[p
]) && (*i
)[k
] == '=') {
987 r
= free_and_strdup(&c
->locale
[p
], *i
+ k
+ 1);
994 for (p
= 0; p
< _LOCALE_MAX
; p
++) {
998 c
->locale
[p
] = mfree(c
->locale
[p
]);
1003 r
= locale_write_data(c
, &settings
);
1005 log_error_errno(r
, "Failed to set locale: %m");
1006 return sd_bus_error_set_errnof(error
, r
, "Failed to set locale: %s", strerror(-r
));
1009 locale_update_system_manager(c
, sd_bus_message_get_bus(m
));
1012 _cleanup_free_
char *line
;
1014 line
= strv_join(settings
, ", ");
1015 log_info("Changed locale to %s.", strnull(line
));
1017 log_info("Changed locale to unset.");
1019 (void) sd_bus_emit_properties_changed(
1020 sd_bus_message_get_bus(m
),
1021 "/org/freedesktop/locale1",
1022 "org.freedesktop.locale1",
1025 log_debug("Locale settings were not modified.");
1028 return sd_bus_reply_method_return(m
, NULL
);
1031 static int method_set_vc_keyboard(sd_bus_message
*m
, void *userdata
, sd_bus_error
*error
) {
1032 Context
*c
= userdata
;
1033 const char *keymap
, *keymap_toggle
;
1034 int convert
, interactive
;
1040 r
= sd_bus_message_read(m
, "ssbb", &keymap
, &keymap_toggle
, &convert
, &interactive
);
1044 if (isempty(keymap
))
1047 if (isempty(keymap_toggle
))
1048 keymap_toggle
= NULL
;
1050 if (!streq_ptr(keymap
, c
->vc_keymap
) ||
1051 !streq_ptr(keymap_toggle
, c
->vc_keymap_toggle
)) {
1053 if ((keymap
&& (!filename_is_valid(keymap
) || !string_is_safe(keymap
))) ||
1054 (keymap_toggle
&& (!filename_is_valid(keymap_toggle
) || !string_is_safe(keymap_toggle
))))
1055 return sd_bus_error_set_errnof(error
, -EINVAL
, "Received invalid keymap data");
1057 r
= bus_verify_polkit_async(
1060 "org.freedesktop.locale1.set-keyboard",
1064 &c
->polkit_registry
,
1069 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
1071 if (free_and_strdup(&c
->vc_keymap
, keymap
) < 0 ||
1072 free_and_strdup(&c
->vc_keymap_toggle
, keymap_toggle
) < 0)
1075 r
= vconsole_write_data(c
);
1077 log_error_errno(r
, "Failed to set virtual console keymap: %m");
1078 return sd_bus_error_set_errnof(error
, r
, "Failed to set virtual console keymap: %s", strerror(-r
));
1081 log_info("Changed virtual console keymap to '%s' toggle '%s'",
1082 strempty(c
->vc_keymap
), strempty(c
->vc_keymap_toggle
));
1084 r
= vconsole_reload(sd_bus_message_get_bus(m
));
1086 log_error_errno(r
, "Failed to request keymap reload: %m");
1088 (void) sd_bus_emit_properties_changed(
1089 sd_bus_message_get_bus(m
),
1090 "/org/freedesktop/locale1",
1091 "org.freedesktop.locale1",
1092 "VConsoleKeymap", "VConsoleKeymapToggle", NULL
);
1095 r
= vconsole_convert_to_x11(c
, sd_bus_message_get_bus(m
));
1097 log_error_errno(r
, "Failed to convert keymap data: %m");
1101 return sd_bus_reply_method_return(m
, NULL
);
1104 #ifdef HAVE_XKBCOMMON
1107 static void log_xkb(struct xkb_context
*ctx
, enum xkb_log_level lvl
, const char *format
, va_list args
) {
1110 fmt
= strjoina("libxkbcommon: ", format
);
1111 log_internalv(LOG_DEBUG
, 0, __FILE__
, __LINE__
, __func__
, fmt
, args
);
1114 #define LOAD_SYMBOL(symbol, dl, name) \
1116 (symbol) = (typeof(symbol)) dlvsym((dl), (name), "V_0.5.0"); \
1117 (symbol) ? 0 : -EOPNOTSUPP; \
1120 static int verify_xkb_rmlvo(const char *model
, const char *layout
, const char *variant
, const char *options
) {
1122 /* We dlopen() the library in order to make the dependency soft. The library (and what it pulls in) is huge
1123 * after all, hence let's support XKB maps when the library is around, and refuse otherwise. The function
1124 * pointers to the shared library are below: */
1126 struct xkb_context
* (*symbol_xkb_context_new
)(enum xkb_context_flags flags
) = NULL
;
1127 void (*symbol_xkb_context_unref
)(struct xkb_context
*context
) = NULL
;
1128 void (*symbol_xkb_context_set_log_fn
)(struct xkb_context
*context
, void (*log_fn
)(struct xkb_context
*context
, enum xkb_log_level level
, const char *format
, va_list args
)) = NULL
;
1129 struct xkb_keymap
* (*symbol_xkb_keymap_new_from_names
)(struct xkb_context
*context
, const struct xkb_rule_names
*names
, enum xkb_keymap_compile_flags flags
) = NULL
;
1130 void (*symbol_xkb_keymap_unref
)(struct xkb_keymap
*keymap
) = NULL
;
1132 const struct xkb_rule_names rmlvo
= {
1138 struct xkb_context
*ctx
= NULL
;
1139 struct xkb_keymap
*km
= NULL
;
1143 /* Compile keymap from RMLVO information to check out its validity */
1145 dl
= dlopen("libxkbcommon.so.0", RTLD_LAZY
);
1149 r
= LOAD_SYMBOL(symbol_xkb_context_new
, dl
, "xkb_context_new");
1153 r
= LOAD_SYMBOL(symbol_xkb_context_unref
, dl
, "xkb_context_unref");
1157 r
= LOAD_SYMBOL(symbol_xkb_context_set_log_fn
, dl
, "xkb_context_set_log_fn");
1161 r
= LOAD_SYMBOL(symbol_xkb_keymap_new_from_names
, dl
, "xkb_keymap_new_from_names");
1165 r
= LOAD_SYMBOL(symbol_xkb_keymap_unref
, dl
, "xkb_keymap_unref");
1169 ctx
= symbol_xkb_context_new(XKB_CONTEXT_NO_ENVIRONMENT_NAMES
);
1175 symbol_xkb_context_set_log_fn(ctx
, log_xkb
);
1177 km
= symbol_xkb_keymap_new_from_names(ctx
, &rmlvo
, XKB_KEYMAP_COMPILE_NO_FLAGS
);
1186 if (symbol_xkb_keymap_unref
&& km
)
1187 symbol_xkb_keymap_unref(km
);
1189 if (symbol_xkb_context_unref
&& ctx
)
1190 symbol_xkb_context_unref(ctx
);
1198 static int verify_xkb_rmlvo(const char *model
, const char *layout
, const char *variant
, const char *options
) {
1204 static int method_set_x11_keyboard(sd_bus_message
*m
, void *userdata
, sd_bus_error
*error
) {
1205 Context
*c
= userdata
;
1206 const char *layout
, *model
, *variant
, *options
;
1207 int convert
, interactive
;
1213 r
= sd_bus_message_read(m
, "ssssbb", &layout
, &model
, &variant
, &options
, &convert
, &interactive
);
1217 if (isempty(layout
))
1223 if (isempty(variant
))
1226 if (isempty(options
))
1229 if (!streq_ptr(layout
, c
->x11_layout
) ||
1230 !streq_ptr(model
, c
->x11_model
) ||
1231 !streq_ptr(variant
, c
->x11_variant
) ||
1232 !streq_ptr(options
, c
->x11_options
)) {
1234 if ((layout
&& !string_is_safe(layout
)) ||
1235 (model
&& !string_is_safe(model
)) ||
1236 (variant
&& !string_is_safe(variant
)) ||
1237 (options
&& !string_is_safe(options
)))
1238 return sd_bus_error_set_errnof(error
, -EINVAL
, "Received invalid keyboard data");
1240 r
= bus_verify_polkit_async(
1243 "org.freedesktop.locale1.set-keyboard",
1247 &c
->polkit_registry
,
1252 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
1254 r
= verify_xkb_rmlvo(model
, layout
, variant
, options
);
1256 log_error_errno(r
, "Cannot compile XKB keymap for new x11 keyboard layout ('%s' / '%s' / '%s' / '%s'): %m",
1257 strempty(model
), strempty(layout
), strempty(variant
), strempty(options
));
1259 if (r
== -EOPNOTSUPP
)
1260 return sd_bus_error_setf(error
, SD_BUS_ERROR_NOT_SUPPORTED
, "Local keyboard configuration not supported on this system.");
1262 return sd_bus_error_set(error
, SD_BUS_ERROR_INVALID_ARGS
, "Specified keymap cannot be compiled, refusing as invalid.");
1265 if (free_and_strdup(&c
->x11_layout
, layout
) < 0 ||
1266 free_and_strdup(&c
->x11_model
, model
) < 0 ||
1267 free_and_strdup(&c
->x11_variant
, variant
) < 0 ||
1268 free_and_strdup(&c
->x11_options
, options
) < 0)
1271 r
= x11_write_data(c
);
1273 log_error_errno(r
, "Failed to set X11 keyboard layout: %m");
1274 return sd_bus_error_set_errnof(error
, r
, "Failed to set X11 keyboard layout: %s", strerror(-r
));
1277 log_info("Changed X11 keyboard layout to '%s' model '%s' variant '%s' options '%s'",
1278 strempty(c
->x11_layout
),
1279 strempty(c
->x11_model
),
1280 strempty(c
->x11_variant
),
1281 strempty(c
->x11_options
));
1283 (void) sd_bus_emit_properties_changed(
1284 sd_bus_message_get_bus(m
),
1285 "/org/freedesktop/locale1",
1286 "org.freedesktop.locale1",
1287 "X11Layout", "X11Model", "X11Variant", "X11Options", NULL
);
1290 r
= x11_convert_to_vconsole(c
, sd_bus_message_get_bus(m
));
1292 log_error_errno(r
, "Failed to convert keymap data: %m");
1296 return sd_bus_reply_method_return(m
, NULL
);
1299 static const sd_bus_vtable locale_vtable
[] = {
1300 SD_BUS_VTABLE_START(0),
1301 SD_BUS_PROPERTY("Locale", "as", property_get_locale
, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
1302 SD_BUS_PROPERTY("X11Layout", "s", NULL
, offsetof(Context
, x11_layout
), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
1303 SD_BUS_PROPERTY("X11Model", "s", NULL
, offsetof(Context
, x11_model
), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
1304 SD_BUS_PROPERTY("X11Variant", "s", NULL
, offsetof(Context
, x11_variant
), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
1305 SD_BUS_PROPERTY("X11Options", "s", NULL
, offsetof(Context
, x11_options
), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
1306 SD_BUS_PROPERTY("VConsoleKeymap", "s", NULL
, offsetof(Context
, vc_keymap
), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
1307 SD_BUS_PROPERTY("VConsoleKeymapToggle", "s", NULL
, offsetof(Context
, vc_keymap_toggle
), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
1308 SD_BUS_METHOD("SetLocale", "asb", NULL
, method_set_locale
, SD_BUS_VTABLE_UNPRIVILEGED
),
1309 SD_BUS_METHOD("SetVConsoleKeyboard", "ssbb", NULL
, method_set_vc_keyboard
, SD_BUS_VTABLE_UNPRIVILEGED
),
1310 SD_BUS_METHOD("SetX11Keyboard", "ssssbb", NULL
, method_set_x11_keyboard
, SD_BUS_VTABLE_UNPRIVILEGED
),
1314 static int connect_bus(Context
*c
, sd_event
*event
, sd_bus
**_bus
) {
1315 _cleanup_(sd_bus_flush_close_unrefp
) sd_bus
*bus
= NULL
;
1322 r
= sd_bus_default_system(&bus
);
1324 return log_error_errno(r
, "Failed to get system bus connection: %m");
1326 r
= sd_bus_add_object_vtable(bus
, NULL
, "/org/freedesktop/locale1", "org.freedesktop.locale1", locale_vtable
, c
);
1328 return log_error_errno(r
, "Failed to register object: %m");
1330 r
= sd_bus_request_name(bus
, "org.freedesktop.locale1", 0);
1332 return log_error_errno(r
, "Failed to register name: %m");
1334 r
= sd_bus_attach_event(bus
, event
, 0);
1336 return log_error_errno(r
, "Failed to attach bus to event loop: %m");
1344 int main(int argc
, char *argv
[]) {
1345 _cleanup_(context_free
) Context context
= {};
1346 _cleanup_(sd_event_unrefp
) sd_event
*event
= NULL
;
1347 _cleanup_(sd_bus_flush_close_unrefp
) sd_bus
*bus
= NULL
;
1350 log_set_target(LOG_TARGET_AUTO
);
1351 log_parse_environment();
1358 log_error("This program takes no arguments.");
1363 r
= sd_event_default(&event
);
1365 log_error_errno(r
, "Failed to allocate event loop: %m");
1369 sd_event_set_watchdog(event
, true);
1371 r
= connect_bus(&context
, event
, &bus
);
1375 r
= context_read_data(&context
);
1377 log_error_errno(r
, "Failed to read locale data: %m");
1381 r
= bus_event_loop_with_idle(event
, bus
, "org.freedesktop.locale1", DEFAULT_EXIT_USEC
, NULL
, NULL
);
1383 log_error_errno(r
, "Failed to run event loop: %m");
1388 return r
< 0 ? EXIT_FAILURE
: EXIT_SUCCESS
;