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 bool startswith_comma(const char *s
, const char *prefix
) {
103 return s
&& (t
= startswith(s
, prefix
)) && (*t
== ',');
106 static void context_free_x11(Context
*c
) {
107 c
->x11_layout
= mfree(c
->x11_layout
);
108 c
->x11_options
= mfree(c
->x11_options
);
109 c
->x11_model
= mfree(c
->x11_model
);
110 c
->x11_variant
= mfree(c
->x11_variant
);
113 static void context_free_vconsole(Context
*c
) {
114 c
->vc_keymap
= mfree(c
->vc_keymap
);
115 c
->vc_keymap_toggle
= mfree(c
->vc_keymap_toggle
);
118 static void context_free_locale(Context
*c
) {
121 for (p
= 0; p
< _LOCALE_MAX
; p
++)
122 c
->locale
[p
] = mfree(c
->locale
[p
]);
125 static void context_free(Context
*c
) {
126 context_free_locale(c
);
128 context_free_vconsole(c
);
130 bus_verify_polkit_async_registry_free(c
->polkit_registry
);
133 static void locale_simplify(Context
*c
) {
136 for (p
= LOCALE_LANG
+1; p
< _LOCALE_MAX
; p
++)
137 if (isempty(c
->locale
[p
]) || streq_ptr(c
->locale
[LOCALE_LANG
], c
->locale
[p
]))
138 c
->locale
[p
] = mfree(c
->locale
[p
]);
141 static int locale_read_data(Context
*c
) {
144 context_free_locale(c
);
146 r
= parse_env_file("/etc/locale.conf", NEWLINE
,
147 "LANG", &c
->locale
[LOCALE_LANG
],
148 "LANGUAGE", &c
->locale
[LOCALE_LANGUAGE
],
149 "LC_CTYPE", &c
->locale
[LOCALE_LC_CTYPE
],
150 "LC_NUMERIC", &c
->locale
[LOCALE_LC_NUMERIC
],
151 "LC_TIME", &c
->locale
[LOCALE_LC_TIME
],
152 "LC_COLLATE", &c
->locale
[LOCALE_LC_COLLATE
],
153 "LC_MONETARY", &c
->locale
[LOCALE_LC_MONETARY
],
154 "LC_MESSAGES", &c
->locale
[LOCALE_LC_MESSAGES
],
155 "LC_PAPER", &c
->locale
[LOCALE_LC_PAPER
],
156 "LC_NAME", &c
->locale
[LOCALE_LC_NAME
],
157 "LC_ADDRESS", &c
->locale
[LOCALE_LC_ADDRESS
],
158 "LC_TELEPHONE", &c
->locale
[LOCALE_LC_TELEPHONE
],
159 "LC_MEASUREMENT", &c
->locale
[LOCALE_LC_MEASUREMENT
],
160 "LC_IDENTIFICATION", &c
->locale
[LOCALE_LC_IDENTIFICATION
],
166 /* Fill in what we got passed from systemd. */
167 for (p
= 0; p
< _LOCALE_MAX
; p
++) {
170 r
= free_and_strdup(&c
->locale
[p
], empty_to_null(getenv(names
[p
])));
182 static int vconsole_read_data(Context
*c
) {
185 context_free_vconsole(c
);
187 r
= parse_env_file("/etc/vconsole.conf", NEWLINE
,
188 "KEYMAP", &c
->vc_keymap
,
189 "KEYMAP_TOGGLE", &c
->vc_keymap_toggle
,
192 if (r
< 0 && r
!= -ENOENT
)
198 static int x11_read_data(Context
*c
) {
199 _cleanup_fclose_
FILE *f
;
201 bool in_section
= false;
206 f
= fopen("/etc/X11/xorg.conf.d/00-keyboard.conf", "re");
208 return errno
== ENOENT
? 0 : -errno
;
210 while (fgets(line
, sizeof(line
), f
)) {
216 if (l
[0] == 0 || l
[0] == '#')
219 if (in_section
&& first_word(l
, "Option")) {
220 _cleanup_strv_free_
char **a
= NULL
;
222 r
= strv_split_extract(&a
, l
, WHITESPACE
, EXTRACT_QUOTES
);
226 if (strv_length(a
) == 3) {
229 if (streq(a
[1], "XkbLayout"))
231 else if (streq(a
[1], "XkbModel"))
233 else if (streq(a
[1], "XkbVariant"))
235 else if (streq(a
[1], "XkbOptions"))
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_(sd_bus_message_unrefp
) 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_(sd_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
> 0 ? -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
);
758 *new_keymap
= converted
;
765 static int find_language_fallback(const char *lang
, char **language
) {
766 _cleanup_fclose_
FILE *f
= NULL
;
771 f
= fopen(SYSTEMD_LANGUAGE_FALLBACK_MAP
, "re");
776 _cleanup_strv_free_
char **a
= NULL
;
779 r
= read_next_mapping(SYSTEMD_LANGUAGE_FALLBACK_MAP
, 2, 2, f
, &n
, &a
);
783 if (streq(lang
, a
[0])) {
784 assert(strv_length(a
) == 2);
791 assert_not_reached("should not be here");
794 static int x11_convert_to_vconsole(Context
*c
, sd_bus
*bus
) {
795 bool modified
= false;
800 if (isempty(c
->x11_layout
)) {
803 !isempty(c
->vc_keymap
) ||
804 !isempty(c
->vc_keymap_toggle
);
808 char *new_keymap
= NULL
;
810 r
= find_converted_keymap(c
->x11_layout
, c
->x11_variant
, &new_keymap
);
814 r
= find_legacy_keymap(c
, &new_keymap
);
819 if (!streq_ptr(c
->vc_keymap
, new_keymap
)) {
821 c
->vc_keymap
= new_keymap
;
822 c
->vc_keymap_toggle
= mfree(c
->vc_keymap_toggle
);
829 r
= vconsole_write_data(c
);
831 log_error_errno(r
, "Failed to set virtual console keymap: %m");
833 log_info("Changed virtual console keymap to '%s' toggle '%s'",
834 strempty(c
->vc_keymap
), strempty(c
->vc_keymap_toggle
));
836 sd_bus_emit_properties_changed(bus
,
837 "/org/freedesktop/locale1",
838 "org.freedesktop.locale1",
839 "VConsoleKeymap", "VConsoleKeymapToggle", NULL
);
841 return vconsole_reload(bus
);
843 log_debug("Virtual console keymap was not modified.");
848 static int property_get_locale(
851 const char *interface
,
852 const char *property
,
853 sd_bus_message
*reply
,
855 sd_bus_error
*error
) {
857 Context
*c
= userdata
;
858 _cleanup_strv_free_
char **l
= NULL
;
861 l
= new0(char*, _LOCALE_MAX
+1);
865 for (p
= 0, q
= 0; p
< _LOCALE_MAX
; p
++) {
868 if (isempty(c
->locale
[p
]))
871 if (asprintf(&t
, "%s=%s", names
[p
], c
->locale
[p
]) < 0)
877 return sd_bus_message_append_strv(reply
, l
);
880 static int method_set_locale(sd_bus_message
*m
, void *userdata
, sd_bus_error
*error
) {
881 Context
*c
= userdata
;
882 _cleanup_strv_free_
char **l
= NULL
;
884 const char *lang
= NULL
;
886 bool modified
= false;
887 bool have
[_LOCALE_MAX
] = {};
894 r
= bus_message_read_strv_extend(m
, &l
);
898 r
= sd_bus_message_read_basic(m
, 'b', &interactive
);
902 /* Check whether a variable changed and if it is valid */
906 for (p
= 0; p
< _LOCALE_MAX
; p
++) {
909 k
= strlen(names
[p
]);
910 if (startswith(*i
, names
[p
]) &&
912 locale_is_valid((*i
) + k
+ 1)) {
916 if (p
== LOCALE_LANG
)
919 if (!streq_ptr(*i
+ k
+ 1, c
->locale
[p
]))
927 return sd_bus_error_setf(error
, SD_BUS_ERROR_INVALID_ARGS
, "Invalid Locale data.");
930 /* If LANG was specified, but not LANGUAGE, check if we should
931 * set it based on the language fallback table. */
932 if (have
[LOCALE_LANG
] && !have
[LOCALE_LANGUAGE
]) {
933 _cleanup_free_
char *language
= NULL
;
937 (void) find_language_fallback(lang
, &language
);
939 log_debug("Converted LANG=%s to LANGUAGE=%s", lang
, language
);
940 if (!streq_ptr(language
, c
->locale
[LOCALE_LANGUAGE
])) {
941 r
= strv_extendf(&l
, "LANGUAGE=%s", language
);
945 have
[LOCALE_LANGUAGE
] = true;
951 /* Check whether a variable is unset */
953 for (p
= 0; p
< _LOCALE_MAX
; p
++)
954 if (!isempty(c
->locale
[p
]) && !have
[p
]) {
960 _cleanup_strv_free_
char **settings
= NULL
;
962 r
= bus_verify_polkit_async(
965 "org.freedesktop.locale1.set-locale",
974 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
977 for (p
= 0; p
< _LOCALE_MAX
; p
++) {
980 k
= strlen(names
[p
]);
981 if (startswith(*i
, names
[p
]) && (*i
)[k
] == '=') {
982 r
= free_and_strdup(&c
->locale
[p
], *i
+ k
+ 1);
989 for (p
= 0; p
< _LOCALE_MAX
; p
++) {
993 c
->locale
[p
] = mfree(c
->locale
[p
]);
998 r
= locale_write_data(c
, &settings
);
1000 log_error_errno(r
, "Failed to set locale: %m");
1001 return sd_bus_error_set_errnof(error
, r
, "Failed to set locale: %s", strerror(-r
));
1004 locale_update_system_manager(c
, sd_bus_message_get_bus(m
));
1007 _cleanup_free_
char *line
;
1009 line
= strv_join(settings
, ", ");
1010 log_info("Changed locale to %s.", strnull(line
));
1012 log_info("Changed locale to unset.");
1014 (void) sd_bus_emit_properties_changed(
1015 sd_bus_message_get_bus(m
),
1016 "/org/freedesktop/locale1",
1017 "org.freedesktop.locale1",
1020 log_debug("Locale settings were not modified.");
1023 return sd_bus_reply_method_return(m
, NULL
);
1026 static int method_set_vc_keyboard(sd_bus_message
*m
, void *userdata
, sd_bus_error
*error
) {
1027 Context
*c
= userdata
;
1028 const char *keymap
, *keymap_toggle
;
1029 int convert
, interactive
;
1035 r
= sd_bus_message_read(m
, "ssbb", &keymap
, &keymap_toggle
, &convert
, &interactive
);
1039 keymap
= empty_to_null(keymap
);
1040 keymap_toggle
= empty_to_null(keymap_toggle
);
1042 if (!streq_ptr(keymap
, c
->vc_keymap
) ||
1043 !streq_ptr(keymap_toggle
, c
->vc_keymap_toggle
)) {
1045 if ((keymap
&& (!filename_is_valid(keymap
) || !string_is_safe(keymap
))) ||
1046 (keymap_toggle
&& (!filename_is_valid(keymap_toggle
) || !string_is_safe(keymap_toggle
))))
1047 return sd_bus_error_set_errnof(error
, -EINVAL
, "Received invalid keymap data");
1049 r
= bus_verify_polkit_async(
1052 "org.freedesktop.locale1.set-keyboard",
1056 &c
->polkit_registry
,
1061 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
1063 if (free_and_strdup(&c
->vc_keymap
, keymap
) < 0 ||
1064 free_and_strdup(&c
->vc_keymap_toggle
, keymap_toggle
) < 0)
1067 r
= vconsole_write_data(c
);
1069 log_error_errno(r
, "Failed to set virtual console keymap: %m");
1070 return sd_bus_error_set_errnof(error
, r
, "Failed to set virtual console keymap: %s", strerror(-r
));
1073 log_info("Changed virtual console keymap to '%s' toggle '%s'",
1074 strempty(c
->vc_keymap
), strempty(c
->vc_keymap_toggle
));
1076 r
= vconsole_reload(sd_bus_message_get_bus(m
));
1078 log_error_errno(r
, "Failed to request keymap reload: %m");
1080 (void) sd_bus_emit_properties_changed(
1081 sd_bus_message_get_bus(m
),
1082 "/org/freedesktop/locale1",
1083 "org.freedesktop.locale1",
1084 "VConsoleKeymap", "VConsoleKeymapToggle", NULL
);
1087 r
= vconsole_convert_to_x11(c
, sd_bus_message_get_bus(m
));
1089 log_error_errno(r
, "Failed to convert keymap data: %m");
1093 return sd_bus_reply_method_return(m
, NULL
);
1096 #ifdef HAVE_XKBCOMMON
1099 static void log_xkb(struct xkb_context
*ctx
, enum xkb_log_level lvl
, const char *format
, va_list args
) {
1102 fmt
= strjoina("libxkbcommon: ", format
);
1103 log_internalv(LOG_DEBUG
, 0, __FILE__
, __LINE__
, __func__
, fmt
, args
);
1106 #define LOAD_SYMBOL(symbol, dl, name) \
1108 (symbol) = (typeof(symbol)) dlvsym((dl), (name), "V_0.5.0"); \
1109 (symbol) ? 0 : -EOPNOTSUPP; \
1112 static int verify_xkb_rmlvo(const char *model
, const char *layout
, const char *variant
, const char *options
) {
1114 /* We dlopen() the library in order to make the dependency soft. The library (and what it pulls in) is huge
1115 * after all, hence let's support XKB maps when the library is around, and refuse otherwise. The function
1116 * pointers to the shared library are below: */
1118 struct xkb_context
* (*symbol_xkb_context_new
)(enum xkb_context_flags flags
) = NULL
;
1119 void (*symbol_xkb_context_unref
)(struct xkb_context
*context
) = NULL
;
1120 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
;
1121 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
;
1122 void (*symbol_xkb_keymap_unref
)(struct xkb_keymap
*keymap
) = NULL
;
1124 const struct xkb_rule_names rmlvo
= {
1130 struct xkb_context
*ctx
= NULL
;
1131 struct xkb_keymap
*km
= NULL
;
1135 /* Compile keymap from RMLVO information to check out its validity */
1137 dl
= dlopen("libxkbcommon.so.0", RTLD_LAZY
);
1141 r
= LOAD_SYMBOL(symbol_xkb_context_new
, dl
, "xkb_context_new");
1145 r
= LOAD_SYMBOL(symbol_xkb_context_unref
, dl
, "xkb_context_unref");
1149 r
= LOAD_SYMBOL(symbol_xkb_context_set_log_fn
, dl
, "xkb_context_set_log_fn");
1153 r
= LOAD_SYMBOL(symbol_xkb_keymap_new_from_names
, dl
, "xkb_keymap_new_from_names");
1157 r
= LOAD_SYMBOL(symbol_xkb_keymap_unref
, dl
, "xkb_keymap_unref");
1161 ctx
= symbol_xkb_context_new(XKB_CONTEXT_NO_ENVIRONMENT_NAMES
);
1167 symbol_xkb_context_set_log_fn(ctx
, log_xkb
);
1169 km
= symbol_xkb_keymap_new_from_names(ctx
, &rmlvo
, XKB_KEYMAP_COMPILE_NO_FLAGS
);
1178 if (symbol_xkb_keymap_unref
&& km
)
1179 symbol_xkb_keymap_unref(km
);
1181 if (symbol_xkb_context_unref
&& ctx
)
1182 symbol_xkb_context_unref(ctx
);
1190 static int verify_xkb_rmlvo(const char *model
, const char *layout
, const char *variant
, const char *options
) {
1196 static int method_set_x11_keyboard(sd_bus_message
*m
, void *userdata
, sd_bus_error
*error
) {
1197 Context
*c
= userdata
;
1198 const char *layout
, *model
, *variant
, *options
;
1199 int convert
, interactive
;
1205 r
= sd_bus_message_read(m
, "ssssbb", &layout
, &model
, &variant
, &options
, &convert
, &interactive
);
1209 layout
= empty_to_null(layout
);
1210 model
= empty_to_null(model
);
1211 variant
= empty_to_null(variant
);
1212 options
= empty_to_null(options
);
1214 if (!streq_ptr(layout
, c
->x11_layout
) ||
1215 !streq_ptr(model
, c
->x11_model
) ||
1216 !streq_ptr(variant
, c
->x11_variant
) ||
1217 !streq_ptr(options
, c
->x11_options
)) {
1219 if ((layout
&& !string_is_safe(layout
)) ||
1220 (model
&& !string_is_safe(model
)) ||
1221 (variant
&& !string_is_safe(variant
)) ||
1222 (options
&& !string_is_safe(options
)))
1223 return sd_bus_error_set_errnof(error
, -EINVAL
, "Received invalid keyboard data");
1225 r
= bus_verify_polkit_async(
1228 "org.freedesktop.locale1.set-keyboard",
1232 &c
->polkit_registry
,
1237 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
1239 r
= verify_xkb_rmlvo(model
, layout
, variant
, options
);
1241 log_error_errno(r
, "Cannot compile XKB keymap for new x11 keyboard layout ('%s' / '%s' / '%s' / '%s'): %m",
1242 strempty(model
), strempty(layout
), strempty(variant
), strempty(options
));
1244 if (r
== -EOPNOTSUPP
)
1245 return sd_bus_error_setf(error
, SD_BUS_ERROR_NOT_SUPPORTED
, "Local keyboard configuration not supported on this system.");
1247 return sd_bus_error_set(error
, SD_BUS_ERROR_INVALID_ARGS
, "Specified keymap cannot be compiled, refusing as invalid.");
1250 if (free_and_strdup(&c
->x11_layout
, layout
) < 0 ||
1251 free_and_strdup(&c
->x11_model
, model
) < 0 ||
1252 free_and_strdup(&c
->x11_variant
, variant
) < 0 ||
1253 free_and_strdup(&c
->x11_options
, options
) < 0)
1256 r
= x11_write_data(c
);
1258 log_error_errno(r
, "Failed to set X11 keyboard layout: %m");
1259 return sd_bus_error_set_errnof(error
, r
, "Failed to set X11 keyboard layout: %s", strerror(-r
));
1262 log_info("Changed X11 keyboard layout to '%s' model '%s' variant '%s' options '%s'",
1263 strempty(c
->x11_layout
),
1264 strempty(c
->x11_model
),
1265 strempty(c
->x11_variant
),
1266 strempty(c
->x11_options
));
1268 (void) sd_bus_emit_properties_changed(
1269 sd_bus_message_get_bus(m
),
1270 "/org/freedesktop/locale1",
1271 "org.freedesktop.locale1",
1272 "X11Layout", "X11Model", "X11Variant", "X11Options", NULL
);
1275 r
= x11_convert_to_vconsole(c
, sd_bus_message_get_bus(m
));
1277 log_error_errno(r
, "Failed to convert keymap data: %m");
1281 return sd_bus_reply_method_return(m
, NULL
);
1284 static const sd_bus_vtable locale_vtable
[] = {
1285 SD_BUS_VTABLE_START(0),
1286 SD_BUS_PROPERTY("Locale", "as", property_get_locale
, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
1287 SD_BUS_PROPERTY("X11Layout", "s", NULL
, offsetof(Context
, x11_layout
), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
1288 SD_BUS_PROPERTY("X11Model", "s", NULL
, offsetof(Context
, x11_model
), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
1289 SD_BUS_PROPERTY("X11Variant", "s", NULL
, offsetof(Context
, x11_variant
), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
1290 SD_BUS_PROPERTY("X11Options", "s", NULL
, offsetof(Context
, x11_options
), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
1291 SD_BUS_PROPERTY("VConsoleKeymap", "s", NULL
, offsetof(Context
, vc_keymap
), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
1292 SD_BUS_PROPERTY("VConsoleKeymapToggle", "s", NULL
, offsetof(Context
, vc_keymap_toggle
), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
1293 SD_BUS_METHOD("SetLocale", "asb", NULL
, method_set_locale
, SD_BUS_VTABLE_UNPRIVILEGED
),
1294 SD_BUS_METHOD("SetVConsoleKeyboard", "ssbb", NULL
, method_set_vc_keyboard
, SD_BUS_VTABLE_UNPRIVILEGED
),
1295 SD_BUS_METHOD("SetX11Keyboard", "ssssbb", NULL
, method_set_x11_keyboard
, SD_BUS_VTABLE_UNPRIVILEGED
),
1299 static int connect_bus(Context
*c
, sd_event
*event
, sd_bus
**_bus
) {
1300 _cleanup_(sd_bus_flush_close_unrefp
) sd_bus
*bus
= NULL
;
1307 r
= sd_bus_default_system(&bus
);
1309 return log_error_errno(r
, "Failed to get system bus connection: %m");
1311 r
= sd_bus_add_object_vtable(bus
, NULL
, "/org/freedesktop/locale1", "org.freedesktop.locale1", locale_vtable
, c
);
1313 return log_error_errno(r
, "Failed to register object: %m");
1315 r
= sd_bus_request_name(bus
, "org.freedesktop.locale1", 0);
1317 return log_error_errno(r
, "Failed to register name: %m");
1319 r
= sd_bus_attach_event(bus
, event
, 0);
1321 return log_error_errno(r
, "Failed to attach bus to event loop: %m");
1329 int main(int argc
, char *argv
[]) {
1330 _cleanup_(context_free
) Context context
= {};
1331 _cleanup_(sd_event_unrefp
) sd_event
*event
= NULL
;
1332 _cleanup_(sd_bus_flush_close_unrefp
) sd_bus
*bus
= NULL
;
1335 log_set_target(LOG_TARGET_AUTO
);
1336 log_parse_environment();
1343 log_error("This program takes no arguments.");
1348 r
= sd_event_default(&event
);
1350 log_error_errno(r
, "Failed to allocate event loop: %m");
1354 sd_event_set_watchdog(event
, true);
1356 r
= connect_bus(&context
, event
, &bus
);
1360 r
= context_read_data(&context
);
1362 log_error_errno(r
, "Failed to read locale data: %m");
1366 r
= bus_event_loop_with_idle(event
, bus
, "org.freedesktop.locale1", DEFAULT_EXIT_USEC
, NULL
, NULL
);
1368 log_error_errno(r
, "Failed to run event loop: %m");
1373 return r
< 0 ? EXIT_FAILURE
: EXIT_SUCCESS
;