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>
31 #include "alloc-util.h"
32 #include "bus-error.h"
33 #include "bus-message.h"
38 #include "fileio-label.h"
40 #include "locale-util.h"
42 #include "path-util.h"
43 #include "selinux-util.h"
45 #include "user-util.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 bool startswith_comma(const char *s
, const char *prefix
) {
106 return s
&& (t
= startswith(s
, prefix
)) && (*t
== ',');
109 static void context_free_x11(Context
*c
) {
110 c
->x11_layout
= mfree(c
->x11_layout
);
111 c
->x11_options
= mfree(c
->x11_options
);
112 c
->x11_model
= mfree(c
->x11_model
);
113 c
->x11_variant
= mfree(c
->x11_variant
);
116 static void context_free_vconsole(Context
*c
) {
117 c
->vc_keymap
= mfree(c
->vc_keymap
);
118 c
->vc_keymap_toggle
= mfree(c
->vc_keymap_toggle
);
121 static void context_free_locale(Context
*c
) {
124 for (p
= 0; p
< _LOCALE_MAX
; p
++)
125 c
->locale
[p
] = mfree(c
->locale
[p
]);
128 static void context_free(Context
*c
) {
129 context_free_locale(c
);
131 context_free_vconsole(c
);
133 bus_verify_polkit_async_registry_free(c
->polkit_registry
);
136 static void locale_simplify(Context
*c
) {
139 for (p
= LOCALE_LANG
+1; p
< _LOCALE_MAX
; p
++)
140 if (isempty(c
->locale
[p
]) || streq_ptr(c
->locale
[LOCALE_LANG
], c
->locale
[p
]))
141 c
->locale
[p
] = mfree(c
->locale
[p
]);
144 static int locale_read_data(Context
*c
) {
147 context_free_locale(c
);
149 r
= parse_env_file("/etc/locale.conf", NEWLINE
,
150 "LANG", &c
->locale
[LOCALE_LANG
],
151 "LANGUAGE", &c
->locale
[LOCALE_LANGUAGE
],
152 "LC_CTYPE", &c
->locale
[LOCALE_LC_CTYPE
],
153 "LC_NUMERIC", &c
->locale
[LOCALE_LC_NUMERIC
],
154 "LC_TIME", &c
->locale
[LOCALE_LC_TIME
],
155 "LC_COLLATE", &c
->locale
[LOCALE_LC_COLLATE
],
156 "LC_MONETARY", &c
->locale
[LOCALE_LC_MONETARY
],
157 "LC_MESSAGES", &c
->locale
[LOCALE_LC_MESSAGES
],
158 "LC_PAPER", &c
->locale
[LOCALE_LC_PAPER
],
159 "LC_NAME", &c
->locale
[LOCALE_LC_NAME
],
160 "LC_ADDRESS", &c
->locale
[LOCALE_LC_ADDRESS
],
161 "LC_TELEPHONE", &c
->locale
[LOCALE_LC_TELEPHONE
],
162 "LC_MEASUREMENT", &c
->locale
[LOCALE_LC_MEASUREMENT
],
163 "LC_IDENTIFICATION", &c
->locale
[LOCALE_LC_IDENTIFICATION
],
169 /* Fill in what we got passed from systemd. */
170 for (p
= 0; p
< _LOCALE_MAX
; p
++) {
173 r
= free_and_strdup(&c
->locale
[p
],
174 nonempty(getenv(names
[p
])));
186 static int vconsole_read_data(Context
*c
) {
189 context_free_vconsole(c
);
191 r
= parse_env_file("/etc/vconsole.conf", NEWLINE
,
192 "KEYMAP", &c
->vc_keymap
,
193 "KEYMAP_TOGGLE", &c
->vc_keymap_toggle
,
196 if (r
< 0 && r
!= -ENOENT
)
202 static int x11_read_data(Context
*c
) {
203 _cleanup_fclose_
FILE *f
;
205 bool in_section
= false;
210 f
= fopen("/etc/X11/xorg.conf.d/00-keyboard.conf", "re");
212 return errno
== ENOENT
? 0 : -errno
;
214 while (fgets(line
, sizeof(line
), f
)) {
220 if (l
[0] == 0 || l
[0] == '#')
223 if (in_section
&& first_word(l
, "Option")) {
224 _cleanup_strv_free_
char **a
= NULL
;
226 r
= strv_split_extract(&a
, l
, WHITESPACE
, EXTRACT_QUOTES
);
230 if (strv_length(a
) == 3) {
233 if (streq(a
[1], "XkbLayout"))
235 else if (streq(a
[1], "XkbModel"))
237 else if (streq(a
[1], "XkbVariant"))
239 else if (streq(a
[1], "XkbOptions"))
249 } else if (!in_section
&& first_word(l
, "Section")) {
250 _cleanup_strv_free_
char **a
= NULL
;
252 r
= strv_split_extract(&a
, l
, WHITESPACE
, EXTRACT_QUOTES
);
256 if (strv_length(a
) == 2 && streq(a
[1], "InputClass"))
259 } else if (in_section
&& first_word(l
, "EndSection"))
266 static int context_read_data(Context
*c
) {
269 r
= locale_read_data(c
);
270 q
= vconsole_read_data(c
);
271 p
= x11_read_data(c
);
273 return r
< 0 ? r
: q
< 0 ? q
: p
;
276 static int locale_write_data(Context
*c
, char ***settings
) {
278 _cleanup_strv_free_
char **l
= NULL
;
280 /* Set values will be returned as strv in *settings on success. */
282 r
= load_env_file(NULL
, "/etc/locale.conf", NULL
, &l
);
283 if (r
< 0 && r
!= -ENOENT
)
286 for (p
= 0; p
< _LOCALE_MAX
; p
++) {
287 _cleanup_free_
char *t
= NULL
;
292 if (isempty(c
->locale
[p
])) {
293 l
= strv_env_unset(l
, names
[p
]);
297 if (asprintf(&t
, "%s=%s", names
[p
], c
->locale
[p
]) < 0)
300 u
= strv_env_set(l
, t
);
308 if (strv_isempty(l
)) {
309 if (unlink("/etc/locale.conf") < 0)
310 return errno
== ENOENT
? 0 : -errno
;
315 r
= write_env_file_label("/etc/locale.conf", l
);
324 static int locale_update_system_manager(Context
*c
, sd_bus
*bus
) {
325 _cleanup_free_
char **l_unset
= NULL
;
326 _cleanup_strv_free_
char **l_set
= NULL
;
327 _cleanup_(sd_bus_message_unrefp
) sd_bus_message
*m
= NULL
;
328 sd_bus_error error
= SD_BUS_ERROR_NULL
;
329 unsigned c_set
, c_unset
, p
;
334 l_unset
= new0(char*, _LOCALE_MAX
);
338 l_set
= new0(char*, _LOCALE_MAX
);
342 for (p
= 0, c_set
= 0, c_unset
= 0; p
< _LOCALE_MAX
; p
++) {
345 if (isempty(c
->locale
[p
]))
346 l_unset
[c_set
++] = (char*) names
[p
];
350 if (asprintf(&s
, "%s=%s", names
[p
], c
->locale
[p
]) < 0)
353 l_set
[c_unset
++] = s
;
357 assert(c_set
+ c_unset
== _LOCALE_MAX
);
358 r
= sd_bus_message_new_method_call(bus
, &m
,
359 "org.freedesktop.systemd1",
360 "/org/freedesktop/systemd1",
361 "org.freedesktop.systemd1.Manager",
362 "UnsetAndSetEnvironment");
366 r
= sd_bus_message_append_strv(m
, l_unset
);
370 r
= sd_bus_message_append_strv(m
, l_set
);
374 r
= sd_bus_call(bus
, m
, 0, &error
, NULL
);
376 log_error_errno(r
, "Failed to update the manager environment: %m");
381 static int vconsole_write_data(Context
*c
) {
383 _cleanup_strv_free_
char **l
= NULL
;
385 r
= load_env_file(NULL
, "/etc/vconsole.conf", NULL
, &l
);
386 if (r
< 0 && r
!= -ENOENT
)
389 if (isempty(c
->vc_keymap
))
390 l
= strv_env_unset(l
, "KEYMAP");
392 _cleanup_free_
char *s
= NULL
;
395 s
= strappend("KEYMAP=", c
->vc_keymap
);
399 u
= strv_env_set(l
, s
);
407 if (isempty(c
->vc_keymap_toggle
))
408 l
= strv_env_unset(l
, "KEYMAP_TOGGLE");
410 _cleanup_free_
char *s
= NULL
;
413 s
= strappend("KEYMAP_TOGGLE=", c
->vc_keymap_toggle
);
417 u
= strv_env_set(l
, s
);
425 if (strv_isempty(l
)) {
426 if (unlink("/etc/vconsole.conf") < 0)
427 return errno
== ENOENT
? 0 : -errno
;
432 return write_env_file_label("/etc/vconsole.conf", l
);
435 static int x11_write_data(Context
*c
) {
436 _cleanup_fclose_
FILE *f
= NULL
;
437 _cleanup_free_
char *temp_path
= NULL
;
440 if (isempty(c
->x11_layout
) &&
441 isempty(c
->x11_model
) &&
442 isempty(c
->x11_variant
) &&
443 isempty(c
->x11_options
)) {
445 if (unlink("/etc/X11/xorg.conf.d/00-keyboard.conf") < 0)
446 return errno
== ENOENT
? 0 : -errno
;
451 mkdir_p_label("/etc/X11/xorg.conf.d", 0755);
453 r
= fopen_temporary("/etc/X11/xorg.conf.d/00-keyboard.conf", &f
, &temp_path
);
457 fchmod(fileno(f
), 0644);
459 fputs("# Read and parsed by systemd-localed. It's probably wise not to edit this file\n"
460 "# manually too freely.\n"
461 "Section \"InputClass\"\n"
462 " Identifier \"system-keyboard\"\n"
463 " MatchIsKeyboard \"on\"\n", f
);
465 if (!isempty(c
->x11_layout
))
466 fprintf(f
, " Option \"XkbLayout\" \"%s\"\n", c
->x11_layout
);
468 if (!isempty(c
->x11_model
))
469 fprintf(f
, " Option \"XkbModel\" \"%s\"\n", c
->x11_model
);
471 if (!isempty(c
->x11_variant
))
472 fprintf(f
, " Option \"XkbVariant\" \"%s\"\n", c
->x11_variant
);
474 if (!isempty(c
->x11_options
))
475 fprintf(f
, " Option \"XkbOptions\" \"%s\"\n", c
->x11_options
);
477 fputs("EndSection\n", f
);
479 r
= fflush_and_check(f
);
483 if (rename(temp_path
, "/etc/X11/xorg.conf.d/00-keyboard.conf") < 0) {
491 (void) unlink("/etc/X11/xorg.conf.d/00-keyboard.conf");
494 (void) unlink(temp_path
);
499 static int vconsole_reload(sd_bus
*bus
) {
500 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
505 r
= sd_bus_call_method(bus
,
506 "org.freedesktop.systemd1",
507 "/org/freedesktop/systemd1",
508 "org.freedesktop.systemd1.Manager",
512 "ss", "systemd-vconsole-setup.service", "replace");
515 log_error("Failed to issue method call: %s", bus_error_message(&error
, -r
));
519 static const char* strnulldash(const char *s
) {
520 return isempty(s
) || streq(s
, "-") ? NULL
: s
;
523 static int read_next_mapping(const char* filename
,
524 unsigned min_fields
, unsigned max_fields
,
525 FILE *f
, unsigned *n
, char ***a
) {
537 if (!fgets(line
, sizeof(line
), f
)) {
540 return errno
> 0 ? -errno
: -EIO
;
548 if (l
[0] == 0 || l
[0] == '#')
551 r
= strv_split_extract(&b
, l
, WHITESPACE
, EXTRACT_QUOTES
);
555 length
= strv_length(b
);
556 if (length
< min_fields
|| length
> max_fields
) {
557 log_error("Invalid line %s:%u, ignoring.", filename
, *n
);
568 static int vconsole_convert_to_x11(Context
*c
, sd_bus
*bus
) {
569 bool modified
= false;
573 if (isempty(c
->vc_keymap
)) {
576 !isempty(c
->x11_layout
) ||
577 !isempty(c
->x11_model
) ||
578 !isempty(c
->x11_variant
) ||
579 !isempty(c
->x11_options
);
583 _cleanup_fclose_
FILE *f
= NULL
;
586 f
= fopen(SYSTEMD_KBD_MODEL_MAP
, "re");
591 _cleanup_strv_free_
char **a
= NULL
;
594 r
= read_next_mapping(SYSTEMD_KBD_MODEL_MAP
, 5, UINT_MAX
, f
, &n
, &a
);
600 if (!streq(c
->vc_keymap
, a
[0]))
603 if (!streq_ptr(c
->x11_layout
, strnulldash(a
[1])) ||
604 !streq_ptr(c
->x11_model
, strnulldash(a
[2])) ||
605 !streq_ptr(c
->x11_variant
, strnulldash(a
[3])) ||
606 !streq_ptr(c
->x11_options
, strnulldash(a
[4]))) {
608 if (free_and_strdup(&c
->x11_layout
, strnulldash(a
[1])) < 0 ||
609 free_and_strdup(&c
->x11_model
, strnulldash(a
[2])) < 0 ||
610 free_and_strdup(&c
->x11_variant
, strnulldash(a
[3])) < 0 ||
611 free_and_strdup(&c
->x11_options
, strnulldash(a
[4])) < 0)
624 r
= x11_write_data(c
);
626 return log_error_errno(r
, "Failed to set X11 keyboard layout: %m");
628 log_info("Changed X11 keyboard layout to '%s' model '%s' variant '%s' options '%s'",
629 strempty(c
->x11_layout
),
630 strempty(c
->x11_model
),
631 strempty(c
->x11_variant
),
632 strempty(c
->x11_options
));
634 sd_bus_emit_properties_changed(bus
,
635 "/org/freedesktop/locale1",
636 "org.freedesktop.locale1",
637 "X11Layout", "X11Model", "X11Variant", "X11Options", NULL
);
639 log_debug("X11 keyboard layout was not modified.");
644 static int find_converted_keymap(const char *x11_layout
, const char *x11_variant
, char **new_keymap
) {
646 _cleanup_free_
char *n
;
649 n
= strjoin(x11_layout
, "-", x11_variant
, NULL
);
651 n
= strdup(x11_layout
);
655 NULSTR_FOREACH(dir
, KBD_KEYMAP_DIRS
) {
656 _cleanup_free_
char *p
= NULL
, *pz
= NULL
;
659 p
= strjoin(dir
, "xkb/", n
, ".map", NULL
);
660 pz
= strjoin(dir
, "xkb/", n
, ".map.gz", NULL
);
664 uncompressed
= access(p
, F_OK
) == 0;
665 if (uncompressed
|| access(pz
, F_OK
) == 0) {
666 log_debug("Found converted keymap %s at %s",
667 n
, uncompressed
? p
: pz
);
678 static int find_legacy_keymap(Context
*c
, char **new_keymap
) {
679 _cleanup_fclose_
FILE *f
;
681 unsigned best_matching
= 0;
684 f
= fopen(SYSTEMD_KBD_MODEL_MAP
, "re");
689 _cleanup_strv_free_
char **a
= NULL
;
690 unsigned matching
= 0;
692 r
= read_next_mapping(SYSTEMD_KBD_MODEL_MAP
, 5, UINT_MAX
, f
, &n
, &a
);
698 /* Determine how well matching this entry is */
699 if (streq_ptr(c
->x11_layout
, a
[1]))
700 /* If we got an exact match, this is best */
703 /* We have multiple X layouts, look for an
704 * entry that matches our key with everything
705 * but the first layout stripped off. */
706 if (startswith_comma(c
->x11_layout
, a
[1]))
711 /* If that didn't work, strip off the
712 * other layouts from the entry, too */
713 x
= strndupa(a
[1], strcspn(a
[1], ","));
714 if (startswith_comma(c
->x11_layout
, x
))
720 if (isempty(c
->x11_model
) || streq_ptr(c
->x11_model
, a
[2])) {
723 if (streq_ptr(c
->x11_variant
, a
[3])) {
726 if (streq_ptr(c
->x11_options
, a
[4]))
732 /* The best matching entry so far, then let's save that */
733 if (matching
>= MAX(best_matching
, 1u)) {
734 log_debug("Found legacy keymap %s with score %u",
737 if (matching
> best_matching
) {
738 best_matching
= matching
;
740 r
= free_and_strdup(new_keymap
, a
[0]);
747 if (best_matching
< 10 && c
->x11_layout
) {
748 /* The best match is only the first part of the X11
749 * keymap. Check if we have a converted map which
750 * matches just the first layout.
752 char *l
, *v
= NULL
, *converted
;
754 l
= strndupa(c
->x11_layout
, strcspn(c
->x11_layout
, ","));
756 v
= strndupa(c
->x11_variant
, strcspn(c
->x11_variant
, ","));
757 r
= find_converted_keymap(l
, v
, &converted
);
762 *new_keymap
= converted
;
769 static int find_language_fallback(const char *lang
, char **language
) {
770 _cleanup_fclose_
FILE *f
= NULL
;
775 f
= fopen(SYSTEMD_LANGUAGE_FALLBACK_MAP
, "re");
780 _cleanup_strv_free_
char **a
= NULL
;
783 r
= read_next_mapping(SYSTEMD_LANGUAGE_FALLBACK_MAP
, 2, 2, f
, &n
, &a
);
787 if (streq(lang
, a
[0])) {
788 assert(strv_length(a
) == 2);
795 assert_not_reached("should not be here");
798 static int x11_convert_to_vconsole(Context
*c
, sd_bus
*bus
) {
799 bool modified
= false;
804 if (isempty(c
->x11_layout
)) {
807 !isempty(c
->vc_keymap
) ||
808 !isempty(c
->vc_keymap_toggle
);
812 char *new_keymap
= NULL
;
814 r
= find_converted_keymap(c
->x11_layout
, c
->x11_variant
, &new_keymap
);
818 r
= find_legacy_keymap(c
, &new_keymap
);
823 if (!streq_ptr(c
->vc_keymap
, new_keymap
)) {
825 c
->vc_keymap
= new_keymap
;
826 c
->vc_keymap_toggle
= mfree(c
->vc_keymap_toggle
);
833 r
= vconsole_write_data(c
);
835 log_error_errno(r
, "Failed to set virtual console keymap: %m");
837 log_info("Changed virtual console keymap to '%s' toggle '%s'",
838 strempty(c
->vc_keymap
), strempty(c
->vc_keymap_toggle
));
840 sd_bus_emit_properties_changed(bus
,
841 "/org/freedesktop/locale1",
842 "org.freedesktop.locale1",
843 "VConsoleKeymap", "VConsoleKeymapToggle", NULL
);
845 return vconsole_reload(bus
);
847 log_debug("Virtual console keymap was not modified.");
852 static int property_get_locale(
855 const char *interface
,
856 const char *property
,
857 sd_bus_message
*reply
,
859 sd_bus_error
*error
) {
861 Context
*c
= userdata
;
862 _cleanup_strv_free_
char **l
= NULL
;
865 l
= new0(char*, _LOCALE_MAX
+1);
869 for (p
= 0, q
= 0; p
< _LOCALE_MAX
; p
++) {
872 if (isempty(c
->locale
[p
]))
875 if (asprintf(&t
, "%s=%s", names
[p
], c
->locale
[p
]) < 0)
881 return sd_bus_message_append_strv(reply
, l
);
884 static int method_set_locale(sd_bus_message
*m
, void *userdata
, sd_bus_error
*error
) {
885 Context
*c
= userdata
;
886 _cleanup_strv_free_
char **l
= NULL
;
888 const char *lang
= NULL
;
890 bool modified
= false;
891 bool have
[_LOCALE_MAX
] = {};
898 r
= bus_message_read_strv_extend(m
, &l
);
902 r
= sd_bus_message_read_basic(m
, 'b', &interactive
);
906 /* Check whether a variable changed and if it is valid */
910 for (p
= 0; p
< _LOCALE_MAX
; p
++) {
913 k
= strlen(names
[p
]);
914 if (startswith(*i
, names
[p
]) &&
916 locale_is_valid((*i
) + k
+ 1)) {
920 if (p
== LOCALE_LANG
)
923 if (!streq_ptr(*i
+ k
+ 1, c
->locale
[p
]))
931 return sd_bus_error_setf(error
, SD_BUS_ERROR_INVALID_ARGS
, "Invalid Locale data.");
934 /* If LANG was specified, but not LANGUAGE, check if we should
935 * set it based on the language fallback table. */
936 if (have
[LOCALE_LANG
] && !have
[LOCALE_LANGUAGE
]) {
937 _cleanup_free_
char *language
= NULL
;
941 (void) find_language_fallback(lang
, &language
);
943 log_debug("Converted LANG=%s to LANGUAGE=%s", lang
, language
);
944 if (!streq_ptr(language
, c
->locale
[LOCALE_LANGUAGE
])) {
945 r
= strv_extendf(&l
, "LANGUAGE=%s", language
);
949 have
[LOCALE_LANGUAGE
] = true;
955 /* Check whether a variable is unset */
957 for (p
= 0; p
< _LOCALE_MAX
; p
++)
958 if (!isempty(c
->locale
[p
]) && !have
[p
]) {
964 _cleanup_strv_free_
char **settings
= NULL
;
966 r
= bus_verify_polkit_async(
969 "org.freedesktop.locale1.set-locale",
978 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
981 for (p
= 0; p
< _LOCALE_MAX
; p
++) {
984 k
= strlen(names
[p
]);
985 if (startswith(*i
, names
[p
]) && (*i
)[k
] == '=') {
986 r
= free_and_strdup(&c
->locale
[p
], *i
+ k
+ 1);
993 for (p
= 0; p
< _LOCALE_MAX
; p
++) {
997 c
->locale
[p
] = mfree(c
->locale
[p
]);
1002 r
= locale_write_data(c
, &settings
);
1004 log_error_errno(r
, "Failed to set locale: %m");
1005 return sd_bus_error_set_errnof(error
, r
, "Failed to set locale: %s", strerror(-r
));
1008 locale_update_system_manager(c
, sd_bus_message_get_bus(m
));
1011 _cleanup_free_
char *line
;
1013 line
= strv_join(settings
, ", ");
1014 log_info("Changed locale to %s.", strnull(line
));
1016 log_info("Changed locale to unset.");
1018 (void) sd_bus_emit_properties_changed(
1019 sd_bus_message_get_bus(m
),
1020 "/org/freedesktop/locale1",
1021 "org.freedesktop.locale1",
1024 log_debug("Locale settings were not modified.");
1027 return sd_bus_reply_method_return(m
, NULL
);
1030 static int method_set_vc_keyboard(sd_bus_message
*m
, void *userdata
, sd_bus_error
*error
) {
1031 Context
*c
= userdata
;
1032 const char *keymap
, *keymap_toggle
;
1033 int convert
, interactive
;
1039 r
= sd_bus_message_read(m
, "ssbb", &keymap
, &keymap_toggle
, &convert
, &interactive
);
1043 if (isempty(keymap
))
1046 if (isempty(keymap_toggle
))
1047 keymap_toggle
= NULL
;
1049 if (!streq_ptr(keymap
, c
->vc_keymap
) ||
1050 !streq_ptr(keymap_toggle
, c
->vc_keymap_toggle
)) {
1052 if ((keymap
&& (!filename_is_valid(keymap
) || !string_is_safe(keymap
))) ||
1053 (keymap_toggle
&& (!filename_is_valid(keymap_toggle
) || !string_is_safe(keymap_toggle
))))
1054 return sd_bus_error_set_errnof(error
, -EINVAL
, "Received invalid keymap data");
1056 r
= bus_verify_polkit_async(
1059 "org.freedesktop.locale1.set-keyboard",
1063 &c
->polkit_registry
,
1068 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
1070 if (free_and_strdup(&c
->vc_keymap
, keymap
) < 0 ||
1071 free_and_strdup(&c
->vc_keymap_toggle
, keymap_toggle
) < 0)
1074 r
= vconsole_write_data(c
);
1076 log_error_errno(r
, "Failed to set virtual console keymap: %m");
1077 return sd_bus_error_set_errnof(error
, r
, "Failed to set virtual console keymap: %s", strerror(-r
));
1080 log_info("Changed virtual console keymap to '%s' toggle '%s'",
1081 strempty(c
->vc_keymap
), strempty(c
->vc_keymap_toggle
));
1083 r
= vconsole_reload(sd_bus_message_get_bus(m
));
1085 log_error_errno(r
, "Failed to request keymap reload: %m");
1087 (void) sd_bus_emit_properties_changed(
1088 sd_bus_message_get_bus(m
),
1089 "/org/freedesktop/locale1",
1090 "org.freedesktop.locale1",
1091 "VConsoleKeymap", "VConsoleKeymapToggle", NULL
);
1094 r
= vconsole_convert_to_x11(c
, sd_bus_message_get_bus(m
));
1096 log_error_errno(r
, "Failed to convert keymap data: %m");
1100 return sd_bus_reply_method_return(m
, NULL
);
1103 #ifdef HAVE_XKBCOMMON
1105 static void log_xkb(struct xkb_context
*ctx
, enum xkb_log_level lvl
, const char *format
, va_list args
) {
1108 fmt
= strjoina("libxkbcommon: ", format
);
1109 log_internalv(LOG_DEBUG
, 0, __FILE__
, __LINE__
, __func__
, fmt
, args
);
1112 static int verify_xkb_rmlvo(const char *model
, const char *layout
, const char *variant
, const char *options
) {
1113 const struct xkb_rule_names rmlvo
= {
1119 struct xkb_context
*ctx
= NULL
;
1120 struct xkb_keymap
*km
= NULL
;
1123 /* compile keymap from RMLVO information to check out its validity */
1125 ctx
= xkb_context_new(XKB_CONTEXT_NO_ENVIRONMENT_NAMES
);
1131 xkb_context_set_log_fn(ctx
, log_xkb
);
1133 km
= xkb_keymap_new_from_names(ctx
, &rmlvo
, XKB_KEYMAP_COMPILE_NO_FLAGS
);
1142 xkb_keymap_unref(km
);
1143 xkb_context_unref(ctx
);
1147 static int verify_xkb_rmlvo(const char *model
, const char *layout
, const char *variant
, const char *options
) {
1152 static int method_set_x11_keyboard(sd_bus_message
*m
, void *userdata
, sd_bus_error
*error
) {
1153 Context
*c
= userdata
;
1154 const char *layout
, *model
, *variant
, *options
;
1155 int convert
, interactive
;
1161 r
= sd_bus_message_read(m
, "ssssbb", &layout
, &model
, &variant
, &options
, &convert
, &interactive
);
1165 if (isempty(layout
))
1171 if (isempty(variant
))
1174 if (isempty(options
))
1177 if (!streq_ptr(layout
, c
->x11_layout
) ||
1178 !streq_ptr(model
, c
->x11_model
) ||
1179 !streq_ptr(variant
, c
->x11_variant
) ||
1180 !streq_ptr(options
, c
->x11_options
)) {
1182 if ((layout
&& !string_is_safe(layout
)) ||
1183 (model
&& !string_is_safe(model
)) ||
1184 (variant
&& !string_is_safe(variant
)) ||
1185 (options
&& !string_is_safe(options
)))
1186 return sd_bus_error_set_errnof(error
, -EINVAL
, "Received invalid keyboard data");
1188 r
= bus_verify_polkit_async(
1191 "org.freedesktop.locale1.set-keyboard",
1195 &c
->polkit_registry
,
1200 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
1202 r
= verify_xkb_rmlvo(model
, layout
, variant
, options
);
1204 log_error_errno(r
, "Cannot compile XKB keymap for new x11 keyboard layout ('%s' / '%s' / '%s' / '%s'): %m",
1205 strempty(model
), strempty(layout
), strempty(variant
), strempty(options
));
1206 return sd_bus_error_set(error
, SD_BUS_ERROR_INVALID_ARGS
, "Cannot compile XKB keymap, refusing");
1209 if (free_and_strdup(&c
->x11_layout
, layout
) < 0 ||
1210 free_and_strdup(&c
->x11_model
, model
) < 0 ||
1211 free_and_strdup(&c
->x11_variant
, variant
) < 0 ||
1212 free_and_strdup(&c
->x11_options
, options
) < 0)
1215 r
= x11_write_data(c
);
1217 log_error_errno(r
, "Failed to set X11 keyboard layout: %m");
1218 return sd_bus_error_set_errnof(error
, r
, "Failed to set X11 keyboard layout: %s", strerror(-r
));
1221 log_info("Changed X11 keyboard layout to '%s' model '%s' variant '%s' options '%s'",
1222 strempty(c
->x11_layout
),
1223 strempty(c
->x11_model
),
1224 strempty(c
->x11_variant
),
1225 strempty(c
->x11_options
));
1227 (void) sd_bus_emit_properties_changed(
1228 sd_bus_message_get_bus(m
),
1229 "/org/freedesktop/locale1",
1230 "org.freedesktop.locale1",
1231 "X11Layout", "X11Model", "X11Variant", "X11Options", NULL
);
1234 r
= x11_convert_to_vconsole(c
, sd_bus_message_get_bus(m
));
1236 log_error_errno(r
, "Failed to convert keymap data: %m");
1240 return sd_bus_reply_method_return(m
, NULL
);
1243 static const sd_bus_vtable locale_vtable
[] = {
1244 SD_BUS_VTABLE_START(0),
1245 SD_BUS_PROPERTY("Locale", "as", property_get_locale
, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
1246 SD_BUS_PROPERTY("X11Layout", "s", NULL
, offsetof(Context
, x11_layout
), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
1247 SD_BUS_PROPERTY("X11Model", "s", NULL
, offsetof(Context
, x11_model
), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
1248 SD_BUS_PROPERTY("X11Variant", "s", NULL
, offsetof(Context
, x11_variant
), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
1249 SD_BUS_PROPERTY("X11Options", "s", NULL
, offsetof(Context
, x11_options
), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
1250 SD_BUS_PROPERTY("VConsoleKeymap", "s", NULL
, offsetof(Context
, vc_keymap
), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
1251 SD_BUS_PROPERTY("VConsoleKeymapToggle", "s", NULL
, offsetof(Context
, vc_keymap_toggle
), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
1252 SD_BUS_METHOD("SetLocale", "asb", NULL
, method_set_locale
, SD_BUS_VTABLE_UNPRIVILEGED
),
1253 SD_BUS_METHOD("SetVConsoleKeyboard", "ssbb", NULL
, method_set_vc_keyboard
, SD_BUS_VTABLE_UNPRIVILEGED
),
1254 SD_BUS_METHOD("SetX11Keyboard", "ssssbb", NULL
, method_set_x11_keyboard
, SD_BUS_VTABLE_UNPRIVILEGED
),
1258 static int connect_bus(Context
*c
, sd_event
*event
, sd_bus
**_bus
) {
1259 _cleanup_(sd_bus_flush_close_unrefp
) sd_bus
*bus
= NULL
;
1266 r
= sd_bus_default_system(&bus
);
1268 return log_error_errno(r
, "Failed to get system bus connection: %m");
1270 r
= sd_bus_add_object_vtable(bus
, NULL
, "/org/freedesktop/locale1", "org.freedesktop.locale1", locale_vtable
, c
);
1272 return log_error_errno(r
, "Failed to register object: %m");
1274 r
= sd_bus_request_name(bus
, "org.freedesktop.locale1", 0);
1276 return log_error_errno(r
, "Failed to register name: %m");
1278 r
= sd_bus_attach_event(bus
, event
, 0);
1280 return log_error_errno(r
, "Failed to attach bus to event loop: %m");
1288 int main(int argc
, char *argv
[]) {
1289 _cleanup_(context_free
) Context context
= {};
1290 _cleanup_(sd_event_unrefp
) sd_event
*event
= NULL
;
1291 _cleanup_(sd_bus_flush_close_unrefp
) sd_bus
*bus
= NULL
;
1294 log_set_target(LOG_TARGET_AUTO
);
1295 log_parse_environment();
1302 log_error("This program takes no arguments.");
1307 r
= sd_event_default(&event
);
1309 log_error_errno(r
, "Failed to allocate event loop: %m");
1313 sd_event_set_watchdog(event
, true);
1315 r
= connect_bus(&context
, event
, &bus
);
1319 r
= context_read_data(&context
);
1321 log_error_errno(r
, "Failed to read locale data: %m");
1325 r
= bus_event_loop_with_idle(event
, bus
, "org.freedesktop.locale1", DEFAULT_EXIT_USEC
, NULL
, NULL
);
1327 log_error_errno(r
, "Failed to run event loop: %m");
1332 return r
< 0 ? EXIT_FAILURE
: EXIT_SUCCESS
;