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 c
->x11_layout
= mfree(c
->x11_layout
);
110 c
->x11_options
= mfree(c
->x11_options
);
111 c
->x11_model
= mfree(c
->x11_model
);
112 c
->x11_variant
= mfree(c
->x11_variant
);
115 static void context_free_vconsole(Context
*c
) {
116 c
->vc_keymap
= mfree(c
->vc_keymap
);
117 c
->vc_keymap_toggle
= mfree(c
->vc_keymap_toggle
);
120 static void context_free_locale(Context
*c
) {
123 for (p
= 0; p
< _LOCALE_MAX
; p
++)
124 c
->locale
[p
] = mfree(c
->locale
[p
]);
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 c
->locale
[p
] = mfree(c
->locale
[p
]);
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) {
232 if (streq(a
[1], "XkbLayout"))
234 else if (streq(a
[1], "XkbModel"))
236 else if (streq(a
[1], "XkbVariant"))
238 else if (streq(a
[1], "XkbOptions"))
248 } else if (!in_section
&& first_word(l
, "Section")) {
249 _cleanup_strv_free_
char **a
= NULL
;
251 r
= strv_split_extract(&a
, l
, WHITESPACE
, EXTRACT_QUOTES
);
255 if (strv_length(a
) == 2 && streq(a
[1], "InputClass"))
258 } else if (in_section
&& first_word(l
, "EndSection"))
265 static int context_read_data(Context
*c
) {
268 r
= locale_read_data(c
);
269 q
= vconsole_read_data(c
);
270 p
= x11_read_data(c
);
272 return r
< 0 ? r
: q
< 0 ? q
: p
;
275 static int locale_write_data(Context
*c
, char ***settings
) {
277 _cleanup_strv_free_
char **l
= NULL
;
279 /* Set values will be returned as strv in *settings on success. */
281 r
= load_env_file(NULL
, "/etc/locale.conf", NULL
, &l
);
282 if (r
< 0 && r
!= -ENOENT
)
285 for (p
= 0; p
< _LOCALE_MAX
; p
++) {
286 _cleanup_free_
char *t
= NULL
;
291 if (isempty(c
->locale
[p
])) {
292 l
= strv_env_unset(l
, names
[p
]);
296 if (asprintf(&t
, "%s=%s", names
[p
], c
->locale
[p
]) < 0)
299 u
= strv_env_set(l
, t
);
307 if (strv_isempty(l
)) {
308 if (unlink("/etc/locale.conf") < 0)
309 return errno
== ENOENT
? 0 : -errno
;
314 r
= write_env_file_label("/etc/locale.conf", l
);
323 static int locale_update_system_manager(Context
*c
, sd_bus
*bus
) {
324 _cleanup_free_
char **l_unset
= NULL
;
325 _cleanup_strv_free_
char **l_set
= NULL
;
326 _cleanup_bus_message_unref_ sd_bus_message
*m
= NULL
;
327 sd_bus_error error
= SD_BUS_ERROR_NULL
;
328 unsigned c_set
, c_unset
, p
;
333 l_unset
= new0(char*, _LOCALE_MAX
);
337 l_set
= new0(char*, _LOCALE_MAX
);
341 for (p
= 0, c_set
= 0, c_unset
= 0; p
< _LOCALE_MAX
; p
++) {
344 if (isempty(c
->locale
[p
]))
345 l_unset
[c_set
++] = (char*) names
[p
];
349 if (asprintf(&s
, "%s=%s", names
[p
], c
->locale
[p
]) < 0)
352 l_set
[c_unset
++] = s
;
356 assert(c_set
+ c_unset
== _LOCALE_MAX
);
357 r
= sd_bus_message_new_method_call(bus
, &m
,
358 "org.freedesktop.systemd1",
359 "/org/freedesktop/systemd1",
360 "org.freedesktop.systemd1.Manager",
361 "UnsetAndSetEnvironment");
365 r
= sd_bus_message_append_strv(m
, l_unset
);
369 r
= sd_bus_message_append_strv(m
, l_set
);
373 r
= sd_bus_call(bus
, m
, 0, &error
, NULL
);
375 log_error_errno(r
, "Failed to update the manager environment: %m");
380 static int vconsole_write_data(Context
*c
) {
382 _cleanup_strv_free_
char **l
= NULL
;
384 r
= load_env_file(NULL
, "/etc/vconsole.conf", NULL
, &l
);
385 if (r
< 0 && r
!= -ENOENT
)
388 if (isempty(c
->vc_keymap
))
389 l
= strv_env_unset(l
, "KEYMAP");
391 _cleanup_free_
char *s
= NULL
;
394 s
= strappend("KEYMAP=", c
->vc_keymap
);
398 u
= strv_env_set(l
, s
);
406 if (isempty(c
->vc_keymap_toggle
))
407 l
= strv_env_unset(l
, "KEYMAP_TOGGLE");
409 _cleanup_free_
char *s
= NULL
;
412 s
= strappend("KEYMAP_TOGGLE=", c
->vc_keymap_toggle
);
416 u
= strv_env_set(l
, s
);
424 if (strv_isempty(l
)) {
425 if (unlink("/etc/vconsole.conf") < 0)
426 return errno
== ENOENT
? 0 : -errno
;
431 return write_env_file_label("/etc/vconsole.conf", l
);
434 static int x11_write_data(Context
*c
) {
435 _cleanup_fclose_
FILE *f
= NULL
;
436 _cleanup_free_
char *temp_path
= NULL
;
439 if (isempty(c
->x11_layout
) &&
440 isempty(c
->x11_model
) &&
441 isempty(c
->x11_variant
) &&
442 isempty(c
->x11_options
)) {
444 if (unlink("/etc/X11/xorg.conf.d/00-keyboard.conf") < 0)
445 return errno
== ENOENT
? 0 : -errno
;
450 mkdir_p_label("/etc/X11/xorg.conf.d", 0755);
452 r
= fopen_temporary("/etc/X11/xorg.conf.d/00-keyboard.conf", &f
, &temp_path
);
456 fchmod(fileno(f
), 0644);
458 fputs("# Read and parsed by systemd-localed. It's probably wise not to edit this file\n"
459 "# manually too freely.\n"
460 "Section \"InputClass\"\n"
461 " Identifier \"system-keyboard\"\n"
462 " MatchIsKeyboard \"on\"\n", f
);
464 if (!isempty(c
->x11_layout
))
465 fprintf(f
, " Option \"XkbLayout\" \"%s\"\n", c
->x11_layout
);
467 if (!isempty(c
->x11_model
))
468 fprintf(f
, " Option \"XkbModel\" \"%s\"\n", c
->x11_model
);
470 if (!isempty(c
->x11_variant
))
471 fprintf(f
, " Option \"XkbVariant\" \"%s\"\n", c
->x11_variant
);
473 if (!isempty(c
->x11_options
))
474 fprintf(f
, " Option \"XkbOptions\" \"%s\"\n", c
->x11_options
);
476 fputs("EndSection\n", f
);
478 r
= fflush_and_check(f
);
482 if (rename(temp_path
, "/etc/X11/xorg.conf.d/00-keyboard.conf") < 0) {
490 (void) unlink("/etc/X11/xorg.conf.d/00-keyboard.conf");
493 (void) unlink(temp_path
);
498 static int vconsole_reload(sd_bus
*bus
) {
499 _cleanup_bus_error_free_ sd_bus_error error
= SD_BUS_ERROR_NULL
;
504 r
= sd_bus_call_method(bus
,
505 "org.freedesktop.systemd1",
506 "/org/freedesktop/systemd1",
507 "org.freedesktop.systemd1.Manager",
511 "ss", "systemd-vconsole-setup.service", "replace");
514 log_error("Failed to issue method call: %s", bus_error_message(&error
, -r
));
518 static const char* strnulldash(const char *s
) {
519 return isempty(s
) || streq(s
, "-") ? NULL
: s
;
522 static int read_next_mapping(const char* filename
,
523 unsigned min_fields
, unsigned max_fields
,
524 FILE *f
, unsigned *n
, char ***a
) {
536 if (!fgets(line
, sizeof(line
), f
)) {
539 return errno
? -errno
: -EIO
;
547 if (l
[0] == 0 || l
[0] == '#')
550 r
= strv_split_extract(&b
, l
, WHITESPACE
, EXTRACT_QUOTES
);
554 length
= strv_length(b
);
555 if (length
< min_fields
|| length
> max_fields
) {
556 log_error("Invalid line %s:%u, ignoring.", filename
, *n
);
567 static int vconsole_convert_to_x11(Context
*c
, sd_bus
*bus
) {
568 bool modified
= false;
572 if (isempty(c
->vc_keymap
)) {
575 !isempty(c
->x11_layout
) ||
576 !isempty(c
->x11_model
) ||
577 !isempty(c
->x11_variant
) ||
578 !isempty(c
->x11_options
);
582 _cleanup_fclose_
FILE *f
= NULL
;
585 f
= fopen(SYSTEMD_KBD_MODEL_MAP
, "re");
590 _cleanup_strv_free_
char **a
= NULL
;
593 r
= read_next_mapping(SYSTEMD_KBD_MODEL_MAP
, 5, UINT_MAX
, f
, &n
, &a
);
599 if (!streq(c
->vc_keymap
, a
[0]))
602 if (!streq_ptr(c
->x11_layout
, strnulldash(a
[1])) ||
603 !streq_ptr(c
->x11_model
, strnulldash(a
[2])) ||
604 !streq_ptr(c
->x11_variant
, strnulldash(a
[3])) ||
605 !streq_ptr(c
->x11_options
, strnulldash(a
[4]))) {
607 if (free_and_strdup(&c
->x11_layout
, strnulldash(a
[1])) < 0 ||
608 free_and_strdup(&c
->x11_model
, strnulldash(a
[2])) < 0 ||
609 free_and_strdup(&c
->x11_variant
, strnulldash(a
[3])) < 0 ||
610 free_and_strdup(&c
->x11_options
, strnulldash(a
[4])) < 0)
623 r
= x11_write_data(c
);
625 return log_error_errno(r
, "Failed to set X11 keyboard layout: %m");
627 log_info("Changed X11 keyboard layout to '%s' model '%s' variant '%s' options '%s'",
628 strempty(c
->x11_layout
),
629 strempty(c
->x11_model
),
630 strempty(c
->x11_variant
),
631 strempty(c
->x11_options
));
633 sd_bus_emit_properties_changed(bus
,
634 "/org/freedesktop/locale1",
635 "org.freedesktop.locale1",
636 "X11Layout", "X11Model", "X11Variant", "X11Options", NULL
);
638 log_debug("X11 keyboard layout was not modified.");
643 static int find_converted_keymap(const char *x11_layout
, const char *x11_variant
, char **new_keymap
) {
645 _cleanup_free_
char *n
;
648 n
= strjoin(x11_layout
, "-", x11_variant
, NULL
);
650 n
= strdup(x11_layout
);
654 NULSTR_FOREACH(dir
, KBD_KEYMAP_DIRS
) {
655 _cleanup_free_
char *p
= NULL
, *pz
= NULL
;
658 p
= strjoin(dir
, "xkb/", n
, ".map", NULL
);
659 pz
= strjoin(dir
, "xkb/", n
, ".map.gz", NULL
);
663 uncompressed
= access(p
, F_OK
) == 0;
664 if (uncompressed
|| access(pz
, F_OK
) == 0) {
665 log_debug("Found converted keymap %s at %s",
666 n
, uncompressed
? p
: pz
);
677 static int find_legacy_keymap(Context
*c
, char **new_keymap
) {
678 _cleanup_fclose_
FILE *f
;
680 unsigned best_matching
= 0;
683 f
= fopen(SYSTEMD_KBD_MODEL_MAP
, "re");
688 _cleanup_strv_free_
char **a
= NULL
;
689 unsigned matching
= 0;
691 r
= read_next_mapping(SYSTEMD_KBD_MODEL_MAP
, 5, UINT_MAX
, f
, &n
, &a
);
697 /* Determine how well matching this entry is */
698 if (streq_ptr(c
->x11_layout
, a
[1]))
699 /* If we got an exact match, this is best */
702 /* We have multiple X layouts, look for an
703 * entry that matches our key with everything
704 * but the first layout stripped off. */
705 if (startswith_comma(c
->x11_layout
, a
[1]))
710 /* If that didn't work, strip off the
711 * other layouts from the entry, too */
712 x
= strndupa(a
[1], strcspn(a
[1], ","));
713 if (startswith_comma(c
->x11_layout
, x
))
719 if (isempty(c
->x11_model
) || streq_ptr(c
->x11_model
, a
[2])) {
722 if (streq_ptr(c
->x11_variant
, a
[3])) {
725 if (streq_ptr(c
->x11_options
, a
[4]))
731 /* The best matching entry so far, then let's save that */
732 if (matching
>= MAX(best_matching
, 1u)) {
733 log_debug("Found legacy keymap %s with score %u",
736 if (matching
> best_matching
) {
737 best_matching
= matching
;
739 r
= free_and_strdup(new_keymap
, a
[0]);
746 if (best_matching
< 10 && c
->x11_layout
) {
747 /* The best match is only the first part of the X11
748 * keymap. Check if we have a converted map which
749 * matches just the first layout.
751 char *l
, *v
= NULL
, *converted
;
753 l
= strndupa(c
->x11_layout
, strcspn(c
->x11_layout
, ","));
755 v
= strndupa(c
->x11_variant
, strcspn(c
->x11_variant
, ","));
756 r
= find_converted_keymap(l
, v
, &converted
);
761 *new_keymap
= converted
;
768 static int find_language_fallback(const char *lang
, char **language
) {
769 _cleanup_fclose_
FILE *f
= NULL
;
774 f
= fopen(SYSTEMD_LANGUAGE_FALLBACK_MAP
, "re");
779 _cleanup_strv_free_
char **a
= NULL
;
782 r
= read_next_mapping(SYSTEMD_LANGUAGE_FALLBACK_MAP
, 2, 2, f
, &n
, &a
);
786 if (streq(lang
, a
[0])) {
787 assert(strv_length(a
) == 2);
794 assert_not_reached("should not be here");
797 static int x11_convert_to_vconsole(Context
*c
, sd_bus
*bus
) {
798 bool modified
= false;
803 if (isempty(c
->x11_layout
)) {
806 !isempty(c
->vc_keymap
) ||
807 !isempty(c
->vc_keymap_toggle
);
811 char *new_keymap
= NULL
;
813 r
= find_converted_keymap(c
->x11_layout
, c
->x11_variant
, &new_keymap
);
817 r
= find_legacy_keymap(c
, &new_keymap
);
822 if (!streq_ptr(c
->vc_keymap
, new_keymap
)) {
824 c
->vc_keymap
= new_keymap
;
825 c
->vc_keymap_toggle
= mfree(c
->vc_keymap_toggle
);
832 r
= vconsole_write_data(c
);
834 log_error_errno(r
, "Failed to set virtual console keymap: %m");
836 log_info("Changed virtual console keymap to '%s' toggle '%s'",
837 strempty(c
->vc_keymap
), strempty(c
->vc_keymap_toggle
));
839 sd_bus_emit_properties_changed(bus
,
840 "/org/freedesktop/locale1",
841 "org.freedesktop.locale1",
842 "VConsoleKeymap", "VConsoleKeymapToggle", NULL
);
844 return vconsole_reload(bus
);
846 log_debug("Virtual console keymap was not modified.");
851 static int property_get_locale(
854 const char *interface
,
855 const char *property
,
856 sd_bus_message
*reply
,
858 sd_bus_error
*error
) {
860 Context
*c
= userdata
;
861 _cleanup_strv_free_
char **l
= NULL
;
864 l
= new0(char*, _LOCALE_MAX
+1);
868 for (p
= 0, q
= 0; p
< _LOCALE_MAX
; p
++) {
871 if (isempty(c
->locale
[p
]))
874 if (asprintf(&t
, "%s=%s", names
[p
], c
->locale
[p
]) < 0)
880 return sd_bus_message_append_strv(reply
, l
);
883 static int method_set_locale(sd_bus_message
*m
, void *userdata
, sd_bus_error
*error
) {
884 Context
*c
= userdata
;
885 _cleanup_strv_free_
char **l
= NULL
;
887 const char *lang
= NULL
;
889 bool modified
= false;
890 bool have
[_LOCALE_MAX
] = {};
897 r
= bus_message_read_strv_extend(m
, &l
);
901 r
= sd_bus_message_read_basic(m
, 'b', &interactive
);
905 /* Check whether a variable changed and if it is valid */
909 for (p
= 0; p
< _LOCALE_MAX
; p
++) {
912 k
= strlen(names
[p
]);
913 if (startswith(*i
, names
[p
]) &&
915 locale_is_valid((*i
) + k
+ 1)) {
919 if (p
== LOCALE_LANG
)
922 if (!streq_ptr(*i
+ k
+ 1, c
->locale
[p
]))
930 return sd_bus_error_setf(error
, SD_BUS_ERROR_INVALID_ARGS
, "Invalid Locale data.");
933 /* If LANG was specified, but not LANGUAGE, check if we should
934 * set it based on the language fallback table. */
935 if (have
[LOCALE_LANG
] && !have
[LOCALE_LANGUAGE
]) {
936 _cleanup_free_
char *language
= NULL
;
940 (void) find_language_fallback(lang
, &language
);
942 log_debug("Converted LANG=%s to LANGUAGE=%s", lang
, language
);
943 if (!streq_ptr(language
, c
->locale
[LOCALE_LANGUAGE
])) {
944 r
= strv_extendf(&l
, "LANGUAGE=%s", language
);
948 have
[LOCALE_LANGUAGE
] = true;
954 /* Check whether a variable is unset */
956 for (p
= 0; p
< _LOCALE_MAX
; p
++)
957 if (!isempty(c
->locale
[p
]) && !have
[p
]) {
963 _cleanup_strv_free_
char **settings
= NULL
;
965 r
= bus_verify_polkit_async(
968 "org.freedesktop.locale1.set-locale",
977 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
980 for (p
= 0; p
< _LOCALE_MAX
; p
++) {
983 k
= strlen(names
[p
]);
984 if (startswith(*i
, names
[p
]) && (*i
)[k
] == '=') {
985 r
= free_and_strdup(&c
->locale
[p
], *i
+ k
+ 1);
992 for (p
= 0; p
< _LOCALE_MAX
; p
++) {
996 c
->locale
[p
] = mfree(c
->locale
[p
]);
1001 r
= locale_write_data(c
, &settings
);
1003 log_error_errno(r
, "Failed to set locale: %m");
1004 return sd_bus_error_set_errnof(error
, r
, "Failed to set locale: %s", strerror(-r
));
1007 locale_update_system_manager(c
, sd_bus_message_get_bus(m
));
1010 _cleanup_free_
char *line
;
1012 line
= strv_join(settings
, ", ");
1013 log_info("Changed locale to %s.", strnull(line
));
1015 log_info("Changed locale to unset.");
1017 (void) sd_bus_emit_properties_changed(
1018 sd_bus_message_get_bus(m
),
1019 "/org/freedesktop/locale1",
1020 "org.freedesktop.locale1",
1023 log_debug("Locale settings were not modified.");
1026 return sd_bus_reply_method_return(m
, NULL
);
1029 static int method_set_vc_keyboard(sd_bus_message
*m
, void *userdata
, sd_bus_error
*error
) {
1030 Context
*c
= userdata
;
1031 const char *keymap
, *keymap_toggle
;
1032 int convert
, interactive
;
1038 r
= sd_bus_message_read(m
, "ssbb", &keymap
, &keymap_toggle
, &convert
, &interactive
);
1042 if (isempty(keymap
))
1045 if (isempty(keymap_toggle
))
1046 keymap_toggle
= NULL
;
1048 if (!streq_ptr(keymap
, c
->vc_keymap
) ||
1049 !streq_ptr(keymap_toggle
, c
->vc_keymap_toggle
)) {
1051 if ((keymap
&& (!filename_is_valid(keymap
) || !string_is_safe(keymap
))) ||
1052 (keymap_toggle
&& (!filename_is_valid(keymap_toggle
) || !string_is_safe(keymap_toggle
))))
1053 return sd_bus_error_set_errnof(error
, -EINVAL
, "Received invalid keymap data");
1055 r
= bus_verify_polkit_async(
1058 "org.freedesktop.locale1.set-keyboard",
1062 &c
->polkit_registry
,
1067 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
1069 if (free_and_strdup(&c
->vc_keymap
, keymap
) < 0 ||
1070 free_and_strdup(&c
->vc_keymap_toggle
, keymap_toggle
) < 0)
1073 r
= vconsole_write_data(c
);
1075 log_error_errno(r
, "Failed to set virtual console keymap: %m");
1076 return sd_bus_error_set_errnof(error
, r
, "Failed to set virtual console keymap: %s", strerror(-r
));
1079 log_info("Changed virtual console keymap to '%s' toggle '%s'",
1080 strempty(c
->vc_keymap
), strempty(c
->vc_keymap_toggle
));
1082 r
= vconsole_reload(sd_bus_message_get_bus(m
));
1084 log_error_errno(r
, "Failed to request keymap reload: %m");
1086 (void) sd_bus_emit_properties_changed(
1087 sd_bus_message_get_bus(m
),
1088 "/org/freedesktop/locale1",
1089 "org.freedesktop.locale1",
1090 "VConsoleKeymap", "VConsoleKeymapToggle", NULL
);
1093 r
= vconsole_convert_to_x11(c
, sd_bus_message_get_bus(m
));
1095 log_error_errno(r
, "Failed to convert keymap data: %m");
1099 return sd_bus_reply_method_return(m
, NULL
);
1102 #ifdef HAVE_XKBCOMMON
1104 static void log_xkb(struct xkb_context
*ctx
, enum xkb_log_level lvl
, const char *format
, va_list args
) {
1107 fmt
= strjoina("libxkbcommon: ", format
);
1108 log_internalv(LOG_DEBUG
, 0, __FILE__
, __LINE__
, __func__
, fmt
, args
);
1111 static int verify_xkb_rmlvo(const char *model
, const char *layout
, const char *variant
, const char *options
) {
1112 const struct xkb_rule_names rmlvo
= {
1118 struct xkb_context
*ctx
= NULL
;
1119 struct xkb_keymap
*km
= NULL
;
1122 /* compile keymap from RMLVO information to check out its validity */
1124 ctx
= xkb_context_new(XKB_CONTEXT_NO_ENVIRONMENT_NAMES
);
1130 xkb_context_set_log_fn(ctx
, log_xkb
);
1132 km
= xkb_keymap_new_from_names(ctx
, &rmlvo
, XKB_KEYMAP_COMPILE_NO_FLAGS
);
1141 xkb_keymap_unref(km
);
1142 xkb_context_unref(ctx
);
1146 static int verify_xkb_rmlvo(const char *model
, const char *layout
, const char *variant
, const char *options
) {
1151 static int method_set_x11_keyboard(sd_bus_message
*m
, void *userdata
, sd_bus_error
*error
) {
1152 Context
*c
= userdata
;
1153 const char *layout
, *model
, *variant
, *options
;
1154 int convert
, interactive
;
1160 r
= sd_bus_message_read(m
, "ssssbb", &layout
, &model
, &variant
, &options
, &convert
, &interactive
);
1164 if (isempty(layout
))
1170 if (isempty(variant
))
1173 if (isempty(options
))
1176 if (!streq_ptr(layout
, c
->x11_layout
) ||
1177 !streq_ptr(model
, c
->x11_model
) ||
1178 !streq_ptr(variant
, c
->x11_variant
) ||
1179 !streq_ptr(options
, c
->x11_options
)) {
1181 if ((layout
&& !string_is_safe(layout
)) ||
1182 (model
&& !string_is_safe(model
)) ||
1183 (variant
&& !string_is_safe(variant
)) ||
1184 (options
&& !string_is_safe(options
)))
1185 return sd_bus_error_set_errnof(error
, -EINVAL
, "Received invalid keyboard data");
1187 r
= bus_verify_polkit_async(
1190 "org.freedesktop.locale1.set-keyboard",
1194 &c
->polkit_registry
,
1199 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
1201 r
= verify_xkb_rmlvo(model
, layout
, variant
, options
);
1203 log_error_errno(r
, "Cannot compile XKB keymap for new x11 keyboard layout ('%s' / '%s' / '%s' / '%s'): %m",
1204 strempty(model
), strempty(layout
), strempty(variant
), strempty(options
));
1205 return sd_bus_error_set(error
, SD_BUS_ERROR_INVALID_ARGS
, "Cannot compile XKB keymap, refusing");
1208 if (free_and_strdup(&c
->x11_layout
, layout
) < 0 ||
1209 free_and_strdup(&c
->x11_model
, model
) < 0 ||
1210 free_and_strdup(&c
->x11_variant
, variant
) < 0 ||
1211 free_and_strdup(&c
->x11_options
, options
) < 0)
1214 r
= x11_write_data(c
);
1216 log_error_errno(r
, "Failed to set X11 keyboard layout: %m");
1217 return sd_bus_error_set_errnof(error
, r
, "Failed to set X11 keyboard layout: %s", strerror(-r
));
1220 log_info("Changed X11 keyboard layout to '%s' model '%s' variant '%s' options '%s'",
1221 strempty(c
->x11_layout
),
1222 strempty(c
->x11_model
),
1223 strempty(c
->x11_variant
),
1224 strempty(c
->x11_options
));
1226 (void) sd_bus_emit_properties_changed(
1227 sd_bus_message_get_bus(m
),
1228 "/org/freedesktop/locale1",
1229 "org.freedesktop.locale1",
1230 "X11Layout", "X11Model", "X11Variant", "X11Options", NULL
);
1233 r
= x11_convert_to_vconsole(c
, sd_bus_message_get_bus(m
));
1235 log_error_errno(r
, "Failed to convert keymap data: %m");
1239 return sd_bus_reply_method_return(m
, NULL
);
1242 static const sd_bus_vtable locale_vtable
[] = {
1243 SD_BUS_VTABLE_START(0),
1244 SD_BUS_PROPERTY("Locale", "as", property_get_locale
, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
1245 SD_BUS_PROPERTY("X11Layout", "s", NULL
, offsetof(Context
, x11_layout
), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
1246 SD_BUS_PROPERTY("X11Model", "s", NULL
, offsetof(Context
, x11_model
), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
1247 SD_BUS_PROPERTY("X11Variant", "s", NULL
, offsetof(Context
, x11_variant
), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
1248 SD_BUS_PROPERTY("X11Options", "s", NULL
, offsetof(Context
, x11_options
), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
1249 SD_BUS_PROPERTY("VConsoleKeymap", "s", NULL
, offsetof(Context
, vc_keymap
), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
1250 SD_BUS_PROPERTY("VConsoleKeymapToggle", "s", NULL
, offsetof(Context
, vc_keymap_toggle
), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
1251 SD_BUS_METHOD("SetLocale", "asb", NULL
, method_set_locale
, SD_BUS_VTABLE_UNPRIVILEGED
),
1252 SD_BUS_METHOD("SetVConsoleKeyboard", "ssbb", NULL
, method_set_vc_keyboard
, SD_BUS_VTABLE_UNPRIVILEGED
),
1253 SD_BUS_METHOD("SetX11Keyboard", "ssssbb", NULL
, method_set_x11_keyboard
, SD_BUS_VTABLE_UNPRIVILEGED
),
1257 static int connect_bus(Context
*c
, sd_event
*event
, sd_bus
**_bus
) {
1258 _cleanup_bus_flush_close_unref_ sd_bus
*bus
= NULL
;
1265 r
= sd_bus_default_system(&bus
);
1267 return log_error_errno(r
, "Failed to get system bus connection: %m");
1269 r
= sd_bus_add_object_vtable(bus
, NULL
, "/org/freedesktop/locale1", "org.freedesktop.locale1", locale_vtable
, c
);
1271 return log_error_errno(r
, "Failed to register object: %m");
1273 r
= sd_bus_request_name(bus
, "org.freedesktop.locale1", 0);
1275 return log_error_errno(r
, "Failed to register name: %m");
1277 r
= sd_bus_attach_event(bus
, event
, 0);
1279 return log_error_errno(r
, "Failed to attach bus to event loop: %m");
1287 int main(int argc
, char *argv
[]) {
1288 _cleanup_(context_free
) Context context
= {};
1289 _cleanup_event_unref_ sd_event
*event
= NULL
;
1290 _cleanup_bus_flush_close_unref_ sd_bus
*bus
= NULL
;
1293 log_set_target(LOG_TARGET_AUTO
);
1294 log_parse_environment();
1298 mac_selinux_init("/etc");
1301 log_error("This program takes no arguments.");
1306 r
= sd_event_default(&event
);
1308 log_error_errno(r
, "Failed to allocate event loop: %m");
1312 sd_event_set_watchdog(event
, true);
1314 r
= connect_bus(&context
, event
, &bus
);
1318 r
= context_read_data(&context
);
1320 log_error_errno(r
, "Failed to read locale data: %m");
1324 r
= bus_event_loop_with_idle(event
, bus
, "org.freedesktop.locale1", DEFAULT_EXIT_USEC
, NULL
, NULL
);
1326 log_error_errno(r
, "Failed to run event loop: %m");
1331 return r
< 0 ? EXIT_FAILURE
: EXIT_SUCCESS
;