diff options
-rw-r--r-- | src/localed.c | 471 |
1 files changed, 471 insertions, 0 deletions
diff --git a/src/localed.c b/src/localed.c index 12d9ad7..0dc8765 100644 --- a/src/localed.c +++ b/src/localed.c @@ -17,6 +17,7 @@ */ #include <stdlib.h> +#include <string.h> #include <dbus/dbus-protocol.h> #include <glib.h> @@ -53,8 +54,455 @@ static gchar *x11_layout = NULL; static gchar *x11_model = NULL; static gchar *x11_variant = NULL; static gchar *x11_options = NULL; +static GFile *x11_gentoo_file = NULL; +static GFile *x11_systemd_file = NULL; G_LOCK_DEFINE_STATIC (xorg_conf); +/* Trivial /etc/X11/xorg.conf.d/30-keyboard.conf parser */ + +enum XORG_CONFD_LINE_TYPE { + XORG_CONFD_LINE_TYPE_UNKNOWN, + XORG_CONFD_LINE_TYPE_COMMENT, + XORG_CONFD_LINE_TYPE_SECTION_INPUT_CLASS, + XORG_CONFD_LINE_TYPE_SECTION_OTHER, + XORG_CONFD_LINE_TYPE_END_SECTION, + XORG_CONFD_LINE_TYPE_MATCH_IS_KEYBOARD, + XORG_CONFD_LINE_TYPE_XKB_LAYOUT, + XORG_CONFD_LINE_TYPE_XKB_MODEL, + XORG_CONFD_LINE_TYPE_XKB_VARIANT, + XORG_CONFD_LINE_TYPE_XKB_OPTIONS, +}; + +GRegex *xorg_confd_line_comment_re = NULL; +GRegex *xorg_confd_line_section_input_class_re = NULL; +GRegex *xorg_confd_line_section_re = NULL; +GRegex *xorg_confd_line_end_section_re = NULL; +GRegex *xorg_confd_line_match_is_keyboard_re = NULL; +GRegex *xorg_confd_line_xkb_layout_re = NULL; +GRegex *xorg_confd_line_xkb_model_re = NULL; +GRegex *xorg_confd_line_xkb_variant_re = NULL; +GRegex *xorg_confd_line_xkb_options_re = NULL; + +struct xorg_confd_line_entry { + gchar *string; + gchar *value; /* for one of the options we are interested in */ + enum XORG_CONFD_LINE_TYPE type; +}; + +struct xorg_confd_parser { + GFile *file; + gchar *filename; + GList *line_list; + GList *section; /* start of relevant InputClass section */ +}; + +static void +xorg_confd_regex_destroy () +{ + if (xorg_confd_line_comment_re != NULL) { + g_regex_unref (xorg_confd_line_comment_re); + xorg_confd_line_comment_re = NULL; + } + if (xorg_confd_line_section_input_class_re != NULL) { + g_regex_unref (xorg_confd_line_section_input_class_re); + xorg_confd_line_section_input_class_re = NULL; + } + if (xorg_confd_line_section_re != NULL) { + g_regex_unref (xorg_confd_line_section_re); + xorg_confd_line_section_re = NULL; + } + if (xorg_confd_line_end_section_re != NULL) { + g_regex_unref (xorg_confd_line_end_section_re); + xorg_confd_line_end_section_re = NULL; + } + if (xorg_confd_line_match_is_keyboard_re != NULL) { + g_regex_unref (xorg_confd_line_match_is_keyboard_re); + xorg_confd_line_match_is_keyboard_re = NULL; + } + if (xorg_confd_line_xkb_layout_re != NULL) { + g_regex_unref (xorg_confd_line_xkb_layout_re); + xorg_confd_line_xkb_layout_re = NULL; + } + if (xorg_confd_line_xkb_model_re != NULL) { + g_regex_unref (xorg_confd_line_xkb_model_re); + xorg_confd_line_xkb_model_re = NULL; + } + if (xorg_confd_line_xkb_variant_re != NULL) { + g_regex_unref (xorg_confd_line_xkb_variant_re); + xorg_confd_line_xkb_variant_re = NULL; + } + if (xorg_confd_line_xkb_options_re != NULL) { + g_regex_unref (xorg_confd_line_xkb_options_re); + xorg_confd_line_xkb_options_re = NULL; + } +} + +static void +xorg_confd_regex_init () +{ + if (xorg_confd_line_comment_re == NULL) { + xorg_confd_line_comment_re = g_regex_new ("^\\s*#", G_REGEX_ANCHORED|G_REGEX_CASELESS, 0, NULL); + g_assert (xorg_confd_line_comment_re != NULL); + } + if (xorg_confd_line_section_input_class_re == NULL) { + xorg_confd_line_section_input_class_re = g_regex_new ("^\\s*Section\\s+\"InputClass\"", G_REGEX_ANCHORED|G_REGEX_CASELESS, 0, NULL); + g_assert (xorg_confd_line_section_input_class_re != NULL); + } + if (xorg_confd_line_section_re == NULL) { + xorg_confd_line_section_re = g_regex_new ("^\\s*Section\\s+\"([^\"])\"", G_REGEX_ANCHORED|G_REGEX_CASELESS, 0, NULL); + g_assert (xorg_confd_line_section_re != NULL); + } + if (xorg_confd_line_end_section_re == NULL) { + xorg_confd_line_end_section_re = g_regex_new ("^\\s*EndSection", G_REGEX_ANCHORED|G_REGEX_CASELESS, 0, NULL); + g_assert (xorg_confd_line_end_section_re != NULL); + } + if (xorg_confd_line_match_is_keyboard_re == NULL) { + xorg_confd_line_match_is_keyboard_re = g_regex_new ("^\\s*MatchIsKeyboard(?:\\s*$|\\s+\"(?:1|on|true|yes)\")", G_REGEX_ANCHORED|G_REGEX_CASELESS, 0, NULL); + g_assert (xorg_confd_line_match_is_keyboard_re != NULL); + } + if (xorg_confd_line_xkb_layout_re == NULL) { + xorg_confd_line_xkb_layout_re = g_regex_new ("^(\\s*Option\\s+\"XkbLayout\"\\s+)\"([^\"]*)\"", G_REGEX_ANCHORED|G_REGEX_CASELESS, 0, NULL); + g_assert (xorg_confd_line_xkb_layout_re != NULL); + } + if (xorg_confd_line_xkb_model_re == NULL) { + xorg_confd_line_xkb_model_re = g_regex_new ("^(\\s*Option\\s+\"XkbModel\"\\s+)\"([^\"]*)\"", G_REGEX_ANCHORED|G_REGEX_CASELESS, 0, NULL); + g_assert (xorg_confd_line_xkb_model_re != NULL); + } + if (xorg_confd_line_xkb_variant_re == NULL) { + xorg_confd_line_xkb_variant_re = g_regex_new ("^(\\s*Option\\s+\"XkbVariant\"\\s+)\"([^\"]*)\"", G_REGEX_ANCHORED|G_REGEX_CASELESS, 0, NULL); + g_assert (xorg_confd_line_xkb_variant_re != NULL); + } + if (xorg_confd_line_xkb_options_re == NULL) { + xorg_confd_line_xkb_options_re = g_regex_new ("^(\\s*Option\\s+\"XkbOptions\"\\s+)\"([^\"]*)\"", G_REGEX_ANCHORED|G_REGEX_CASELESS, 0, NULL); + g_assert (xorg_confd_line_xkb_options_re != NULL); + } +} + +static void +xorg_confd_line_entry_free (struct xorg_confd_line_entry *entry) +{ + if (entry == NULL) + return; + + g_free (entry->string); + g_free (entry->value); + + g_free (entry); +} + +static void +xorg_confd_parser_free (struct xorg_confd_parser *parser) +{ + if (parser == NULL) + return; + + if (parser->file != NULL) + g_object_unref (parser->file); + + g_free (parser->filename); + + if (parser->line_list != NULL) + g_list_free_full (parser->line_list, (GDestroyNotify)xorg_confd_line_entry_free); + + g_free (parser); +} + +static struct xorg_confd_parser * +xorg_confd_parser_new (GFile *xorg_confd_file, + GError **error) +{ + struct xorg_confd_parser *parser = NULL; + gchar *filebuf = NULL; + gchar **linebuf = NULL; + gchar **lines = NULL; + GList *input_class_section_start = NULL; + gboolean in_section = FALSE, in_xkb_section = FALSE; + + if (xorg_confd_file == NULL) + return NULL; + + parser = g_new0 (struct xorg_confd_parser, 1); + parser->file = g_object_ref (xorg_confd_file); + parser->filename = g_file_get_path (xorg_confd_file); + g_debug ("Parsing xorg.conf.d file: '%s'", parser->filename); + if (!g_file_load_contents (xorg_confd_file, NULL, &filebuf, NULL, NULL, error)) { + g_prefix_error (error, "Unable to read '%s':", parser->filename); + goto fail; + } + + lines = g_strsplit (filebuf, "\n", 0); + if (lines == NULL) + goto out; + + for (linebuf = lines; *linebuf != NULL; linebuf++) { + struct xorg_confd_line_entry *entry = NULL; + GMatchInfo *match_info = NULL; + gboolean matched = FALSE; + + entry = g_new0 (struct xorg_confd_line_entry, 1); + entry->string = *linebuf; + entry->type = XORG_CONFD_LINE_TYPE_UNKNOWN; + + if (g_regex_match (xorg_confd_line_comment_re, *linebuf, 0, &match_info)) { + g_debug ("Parsed line '%s' as comment", *linebuf); + entry->type = XORG_CONFD_LINE_TYPE_COMMENT; + } else if (g_regex_match (xorg_confd_line_section_input_class_re, *linebuf, 0, &match_info)) { + g_debug ("Parsed line '%s' as InputClass section", *linebuf); + if (in_section) + goto no_match; + in_section = TRUE; + entry->type = XORG_CONFD_LINE_TYPE_SECTION_INPUT_CLASS; + } else if (g_regex_match (xorg_confd_line_section_re, *linebuf, 0, &match_info)) { + g_debug ("Parsed line '%s' as non-InputClass section", *linebuf); + if (in_section) + goto no_match; + in_section = TRUE; + entry->type = XORG_CONFD_LINE_TYPE_SECTION_OTHER; + } else if (g_regex_match (xorg_confd_line_end_section_re, *linebuf, 0, &match_info)) { + g_debug ("Parsed line '%s' as end of section", *linebuf); + if (!in_section) + goto no_match; + entry->type = XORG_CONFD_LINE_TYPE_END_SECTION; + } else if (g_regex_match (xorg_confd_line_match_is_keyboard_re, *linebuf, 0, &match_info)) { + g_debug ("Parsed line '%s' as MatchIsKeyboard declaration", *linebuf); + if (!in_section) + goto no_match; + entry->type = XORG_CONFD_LINE_TYPE_MATCH_IS_KEYBOARD; + in_xkb_section = TRUE; + } else if (g_regex_match (xorg_confd_line_xkb_layout_re, *linebuf, 0, &match_info)) { + g_debug ("Parsed line '%s' as XkbLayout option", *linebuf); + if (!in_section) + goto no_match; + entry->type = XORG_CONFD_LINE_TYPE_XKB_LAYOUT; + entry->value = g_match_info_fetch (match_info, 2); + } else if (g_regex_match (xorg_confd_line_xkb_model_re, *linebuf, 0, &match_info)) { + g_debug ("Parsed line '%s' as XkbModel option", *linebuf); + if (!in_section) + goto no_match; + entry->type = XORG_CONFD_LINE_TYPE_XKB_MODEL; + entry->value = g_match_info_fetch (match_info, 2); + } else if (g_regex_match (xorg_confd_line_xkb_variant_re, *linebuf, 0, &match_info)) { + g_debug ("Parsed line '%s' as XkbVariant option", *linebuf); + if (!in_section) + goto no_match; + entry->type = XORG_CONFD_LINE_TYPE_XKB_VARIANT; + entry->value = g_match_info_fetch (match_info, 2); + } else if (g_regex_match (xorg_confd_line_xkb_options_re, *linebuf, 0, &match_info)) { + g_debug ("Parsed line '%s' as XkbOptions option", *linebuf); + if (!in_section) + goto no_match; + entry->type = XORG_CONFD_LINE_TYPE_XKB_OPTIONS; + entry->value = g_match_info_fetch (match_info, 2); + } + + if (entry->type == XORG_CONFD_LINE_TYPE_UNKNOWN) + g_debug ("Parsing line '%s' as unknown", *linebuf); + + g_match_info_free (match_info); + parser->line_list = g_list_prepend (parser->line_list, entry); + if (in_section) { + if (entry->type == XORG_CONFD_LINE_TYPE_SECTION_INPUT_CLASS) + input_class_section_start = parser->line_list; + else if (entry->type == XORG_CONFD_LINE_TYPE_END_SECTION) { + if (in_xkb_section) + parser->section = input_class_section_start; + + input_class_section_start = NULL; + in_section = FALSE; + in_xkb_section = FALSE; + } + } + continue; + + no_match: + /* Nothing matched... */ + g_free (entry); + g_match_info_free (match_info); + goto parse_fail; + } + + if (in_section) { + /* Unterminated section */ + goto parse_fail; + } + + parser->line_list = g_list_reverse (parser->line_list); + + out: + g_free (filebuf); + return parser; + + parse_fail: + g_propagate_error (error, + g_error_new (G_FILE_ERROR, G_FILE_ERROR_FAILED, + "Unable to parse '%s'", parser->filename)); + fail: + g_free (filebuf); + g_strfreev (lines); + xorg_confd_parser_free (parser); + return NULL; +} + +static void +xorg_confd_parser_get_xkb (const struct xorg_confd_parser *parser, + gchar **layout_p, + gchar **model_p, + gchar **variant_p, + gchar **options_p) +{ + GList *curr = NULL; + gchar *layout = NULL, *model = NULL, *variant = NULL, *options = NULL; + + if (parser == NULL) + return; + for (curr = parser->section; curr != NULL; curr = curr->next) { + GMatchInfo *match_info = NULL; + struct xorg_confd_line_entry *entry = (struct xorg_confd_line_entry *) curr->data; + + if (entry->type == XORG_CONFD_LINE_TYPE_END_SECTION) + break; + else if (entry->type == XORG_CONFD_LINE_TYPE_XKB_LAYOUT) + layout = entry->value; + else if (entry->type == XORG_CONFD_LINE_TYPE_XKB_MODEL) + model = entry->value; + else if (entry->type == XORG_CONFD_LINE_TYPE_XKB_VARIANT) + variant = entry->value; + else if (entry->type == XORG_CONFD_LINE_TYPE_XKB_OPTIONS) + options = entry->value; + } + *layout_p = g_strdup (layout); + *model_p = g_strdup (model); + *variant_p = g_strdup (variant); + *options_p = g_strdup (options); +} + +static GList * +xorg_confd_parser_line_set_or_delete (GList *line, + const gchar *value, + const GRegex *re) +{ + gchar *replacement = NULL, *replaced = NULL; + + g_assert (line != NULL); + + struct xorg_confd_line_entry *entry = (struct xorg_confd_line_entry *) line->data; + + if (value == NULL || !g_strcmp0 (value, "")) { + /* If value is null, we delete the line and return previous one */ + GList *prev = line->prev; + prev->next = line->next; + prev->next->prev = prev; + line->prev = NULL; + line->next = NULL; + g_list_free_full (line, (GDestroyNotify)xorg_confd_line_entry_free); + return prev; + } + entry->value = g_strdup (value); + replacement = g_strdup_printf ("\1\"%s\"", value); + replaced = g_regex_replace (re, entry->string, 0, 0, replacement, 0, NULL); + g_free (replacement); + g_free (entry->string); + entry->string = replaced; + + return line; +} + +static void +xorg_confd_parser_set_xkb (struct xorg_confd_parser *parser, + const gchar *layout, + const gchar *model, + const gchar *variant, + const gchar *options) +{ + GList *curr = NULL; + gboolean layout_found = FALSE, model_found = FALSE, variant_found = FALSE, options_found = FALSE; + + if (parser == NULL) + return; + + if (parser->section == NULL) { + struct xorg_confd_line_entry *entry = NULL; + GList *section = NULL; + + entry = g_new0 (struct xorg_confd_line_entry, 1); + entry->string = g_strdup("Section \"InputClass\"\n"); + entry->type = XORG_CONFD_LINE_TYPE_SECTION_INPUT_CLASS; + section = g_list_prepend (section, entry); + + entry = g_new0 (struct xorg_confd_line_entry, 1); + entry->string = g_strdup("MatchIsKeyboard \"on\"\n"); + entry->type = XORG_CONFD_LINE_TYPE_MATCH_IS_KEYBOARD; + section = g_list_prepend (section, entry); + + entry = g_new0 (struct xorg_confd_line_entry, 1); + entry->string = g_strdup("EndSection\n"); + entry->type = XORG_CONFD_LINE_TYPE_END_SECTION; + section = g_list_prepend (section, entry); + + section = g_list_reverse (section); + parser->section = section; + parser->line_list = g_list_concat (parser->line_list, section); + } + + for (curr = parser->section; curr != NULL; curr = curr->next) { + struct xorg_confd_line_entry *entry = (struct xorg_confd_line_entry *) curr->data; + + if (entry->type == XORG_CONFD_LINE_TYPE_END_SECTION) + break; + else if (entry->type == XORG_CONFD_LINE_TYPE_XKB_LAYOUT) + curr = xorg_confd_parser_line_set_or_delete (curr, layout, xorg_confd_line_xkb_layout_re); + else if (entry->type == XORG_CONFD_LINE_TYPE_XKB_MODEL) + curr = xorg_confd_parser_line_set_or_delete (curr, model, xorg_confd_line_xkb_model_re); + else if (entry->type == XORG_CONFD_LINE_TYPE_XKB_VARIANT) + curr = xorg_confd_parser_line_set_or_delete (curr, variant, xorg_confd_line_xkb_variant_re); + else if (entry->type == XORG_CONFD_LINE_TYPE_XKB_OPTIONS) + curr = xorg_confd_parser_line_set_or_delete (curr, options, xorg_confd_line_xkb_options_re); + } +} + +static gboolean +xorg_confd_parser_save (const struct xorg_confd_parser *parser, + GError **error) +{ + gboolean ret = FALSE; + GList *curr = NULL; + GFileOutputStream *os; + + g_assert (parser != NULL && parser->file != NULL && parser->filename != NULL); + if ((os = g_file_replace (parser->file, NULL, FALSE, G_FILE_CREATE_NONE, NULL, error)) == NULL) { + g_prefix_error (error, "Unable to save '%s': ", parser->filename); + goto out; + } + + for (curr = parser->line_list; curr != NULL; curr = curr->next) { + struct xorg_confd_line_entry *entry = (struct xorg_confd_line_entry *) curr->data; + gsize written; + + if (!g_output_stream_write_all (G_OUTPUT_STREAM (os), entry->string, strlen (entry->string), &written, NULL, error)) { + g_prefix_error (error, "Unable to save '%s': ", parser->filename); + goto out; + } + if (!g_output_stream_write_all (G_OUTPUT_STREAM (os), "\n", 1, &written, NULL, error)) { + g_prefix_error (error, "Unable to save '%s': ", parser->filename); + goto out; + } + } + + if (!g_output_stream_close (G_OUTPUT_STREAM (os), NULL, error)) { + g_prefix_error (error, "Unable to save '%s': ", parser->filename); + g_output_stream_close (G_OUTPUT_STREAM (os), NULL, NULL); + goto out; + } + ret = TRUE; + + out: + if (os) + g_object_unref (os); + return ret; +} + +/* End of trivial /etc/X11/xorg.conf.d/30-keyboard.conf parser */ + static gboolean on_handle_set_locale (OpenrcSettingsdLocaledLocale1 *locale1, GDBusMethodInvocation *invocation, @@ -163,11 +611,16 @@ localed_init (gboolean _read_only) { GError *err = NULL; gchar **locale_values = NULL; + struct xorg_confd_parser *x11_parser = NULL; read_only = _read_only; locale_file = g_file_new_for_path (SYSCONFDIR "/env.d/02locale"); keymaps_file = g_file_new_for_path (SYSCONFDIR "/conf.d/keymaps"); + /* See http://www.gentoo.org/doc/en/xorg-config.xml */ + x11_gentoo_file = g_file_new_for_path (SYSCONFDIR "/X11/xorg.conf.d/30-keyboard.conf"); + x11_systemd_file = g_file_new_for_path (SYSCONFDIR "/X11/xorg.conf.d/00-keyboard.conf"); + locale = g_new0 (gchar *, g_strv_length (locale_variables) + 1); locale_values = shell_utils_trivial_source_var_list (locale_file, (const gchar * const *)locale_variables, &err); if (locale_values != NULL) { @@ -198,6 +651,21 @@ localed_init (gboolean _read_only) /* We don't have a good equivalent for this in openrc at the moment */ vconsole_keymap_toggle = g_strdup (""); + xorg_confd_regex_init (); + + if (!g_file_query_exists (x11_gentoo_file, NULL) && g_file_query_exists (x11_systemd_file, NULL)) + x11_parser = xorg_confd_parser_new (x11_systemd_file, &err); + else + x11_parser = xorg_confd_parser_new (x11_gentoo_file, &err); + + if (x11_parser != NULL) { + xorg_confd_parser_get_xkb (x11_parser, &x11_layout, &x11_model, &x11_variant, &x11_options); + xorg_confd_parser_free (x11_parser); + } else { + g_debug ("%s", err->message); + g_clear_error (&err); + } + bus_id = g_bus_own_name (G_BUS_TYPE_SYSTEM, "org.freedesktop.locale1", G_BUS_NAME_OWNER_FLAGS_NONE, @@ -215,6 +683,7 @@ localed_destroy (void) bus_id = 0; read_only = FALSE; g_strfreev (locale); + xorg_confd_regex_destroy (); g_free (vconsole_keymap); g_free (vconsole_keymap_toggle); g_free (x11_layout); @@ -224,4 +693,6 @@ localed_destroy (void) g_object_unref (locale_file); g_object_unref (keymaps_file); + g_object_unref (x11_gentoo_file); + g_object_unref (x11_systemd_file); } |