/* -*- mode: C; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ /* vim:set et sts=4: */ /* ibus - The Input Bus * Copyright (C) 2008-2010 Peng Huang * Copyright (C) 2011 Daiki Ueno * Copyright (C) 2008-2011 Red Hat, Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 * USA */ #include #include #include "config-private.h" #define DCONF_PREFIX "/desktop/ibus" #define DCONF_PRESERVE_NAME_PREFIXES_KEY \ DCONF_PREFIX"/general/dconf-preserve-name-prefixes" struct _IBusConfigDConf { IBusConfigService parent; DConfClient *client; /* if a dconf path matches one of preserve_name_prefixes, don't convert key names from/to GSettings key names (see _to_gsettings_name and _from_gsettings_name) */ GSList *preserve_name_prefixes; }; struct _IBusConfigDConfClass { IBusConfigServiceClass parent; }; /* functions prototype */ static void ibus_config_dconf_class_init (IBusConfigDConfClass *class); static void ibus_config_dconf_init (IBusConfigDConf *config); static void ibus_config_dconf_destroy (IBusConfigDConf *config); static gboolean ibus_config_dconf_set_value (IBusConfigService *config, const gchar *section, const gchar *name, GVariant *value, GError **error); static GVariant *ibus_config_dconf_get_value (IBusConfigService *config, const gchar *section, const gchar *name, GError **error); static GVariant *ibus_config_dconf_get_values (IBusConfigService *config, const gchar *section, GError **error); static gboolean ibus_config_dconf_unset_value (IBusConfigService *config, const gchar *section, const gchar *name, GError **error); G_DEFINE_TYPE (IBusConfigDConf, ibus_config_dconf, IBUS_TYPE_CONFIG_SERVICE) static void ibus_config_dconf_class_init (IBusConfigDConfClass *class) { IBusObjectClass *object_class = IBUS_OBJECT_CLASS (class); IBusConfigServiceClass *config_class = IBUS_CONFIG_SERVICE_CLASS (class); object_class->destroy = (IBusObjectDestroyFunc) ibus_config_dconf_destroy; config_class->set_value = ibus_config_dconf_set_value; config_class->get_value = ibus_config_dconf_get_value; config_class->get_values = ibus_config_dconf_get_values; config_class->unset_value = ibus_config_dconf_unset_value; } static gboolean _has_prefixes (const gchar *path, GSList *prefixes) { GSList *head = prefixes; for (; head; head = head->next) { if (g_str_has_prefix (path, head->data)) { return TRUE; } } return FALSE; } /* Convert key names from/to GSettings names. While GSettings only * accepts lowercase letters / numbers / and dash ('-'), IBus uses * underscore ('_') and some engines even use uppercase letters. * * To minimize the gap, we do the following conversion: * * - when converting from IBus names to GSettings names, first convert * all letters to lowercase and then replace underscores with dashes. * - when converting from GSettings names to IBus names, simply * replace dashes with underscores. * * Note that though the conversion does not always roundtrip, it does * in most cases. */ static gchar * _to_gsettings_name (const gchar *name) { return g_strcanon (g_ascii_strdown (name, -1), "abcdefghijklmnopqrstuvwxyz0123456789-", '-'); } static gchar * _from_gsettings_name (const gchar *name) { gchar *retval = g_strdup (name), *p; for (p = retval; *p != '\0'; p++) if (*p == '-') *p = '_'; return retval; } typedef gchar *(* NameConvFunc) (const gchar *); static gchar * _conv_path (const gchar *path, NameConvFunc conv_func) { gchar **strv = g_strsplit (path, "/", -1), **p; gchar *retval; for (p = strv; *p; p++) { gchar *canon; canon = (*conv_func) (*p); g_free (*p); *p = canon; } retval = g_strjoinv ("/", strv); g_strfreev (strv); return retval; } static gchar * _to_gsettings_path (const gchar *path) { return _conv_path (path, _to_gsettings_name); } static gchar * _from_gsettings_path (const gchar *gpath) { return _conv_path (gpath, _from_gsettings_name); } static void _watch_func (DConfClient *client, const gchar *gpath, const gchar * const *items, #ifndef DCONF_0_13_4 gint n_items, #endif const gchar *tag, IBusConfigDConf *config) { gchar **gkeys = NULL; gint i; #ifdef DCONF_0_13_4 gint n_items; n_items = g_strv_length ((gchar **)items); #endif g_return_if_fail (gpath != NULL); g_return_if_fail (n_items >= 0); if (dconf_is_key (gpath, NULL)) { /* If path is a key, the notification should be taken to mean that one key may have changed. */ n_items = 1; gkeys = g_malloc0_n (n_items + 1, sizeof (gchar *)); gkeys[0] = g_strdup (gpath); } else { if (n_items == 0) { /* If path is a dir and items is empty then it is an indication that any key under path may have changed. */ gkeys = dconf_client_list (config->client, gpath, &n_items); } else { gkeys = g_boxed_copy (G_TYPE_STRV, items); } for (i = 0; i < n_items; i++) { gchar *gname = gkeys[i]; gkeys[i] = g_strdup_printf ("%s/%s", gpath, gname); g_free (gname); } } for (i = 0; i < n_items; i++) { gchar *gname, *path, *name; GVariant *variant = dconf_client_read (config->client, gkeys[i]); if (variant == NULL) { /* Use a empty tuple for a unset value */ variant = g_variant_new_tuple (NULL, 0); g_variant_ref_sink (variant); } gname = strrchr (gkeys[i], '/'); g_assert (gname); *gname++ = '\0'; if (_has_prefixes (gkeys[i], config->preserve_name_prefixes)) { path = gkeys[i]; name = gname; } else { path = _from_gsettings_path (gkeys[i]); name = _from_gsettings_name (gname); } ibus_config_service_value_changed ((IBusConfigService *) config, path + sizeof (DCONF_PREFIX), name, variant); if (path != gkeys[i]) { g_free (path); } if (name != gname) { g_free (name); } g_variant_unref (variant); } g_strfreev (gkeys); } static void ibus_config_dconf_init (IBusConfigDConf *config) { GVariant *variant; #ifdef DCONF_0_13_4 config->client = dconf_client_new (); g_signal_connect (config->client, "changed", G_CALLBACK (_watch_func), config); dconf_client_watch_fast (config->client, DCONF_PREFIX"/"); #else GError *error; config->client = dconf_client_new ("ibus", (DConfWatchFunc)_watch_func, config, NULL); error = NULL; if (!dconf_client_watch (config->client, DCONF_PREFIX"/", NULL, &error)) { g_warning ("Can not watch dconf path %s: %s", DCONF_PREFIX"/", error->message); g_error_free (error); } #endif config->preserve_name_prefixes = NULL; variant = dconf_client_read (config->client, DCONF_PRESERVE_NAME_PREFIXES_KEY); if (variant != NULL) { GVariantIter iter; GVariant *child; g_variant_iter_init (&iter, variant); while ((child = g_variant_iter_next_value (&iter))) { char *prefix = g_variant_dup_string (child, NULL); config->preserve_name_prefixes = g_slist_prepend (config->preserve_name_prefixes, prefix); g_variant_unref (child); } g_variant_unref (variant); } } static void ibus_config_dconf_destroy (IBusConfigDConf *config) { if (config->client) { #ifdef DCONF_0_13_4 dconf_client_unwatch_fast (config->client, DCONF_PREFIX"/"); #else GError *error = NULL; if (!dconf_client_unwatch (config->client, DCONF_PREFIX"/", NULL, &error)) { g_warning ("Can not unwatch dconf path %s: %s", DCONF_PREFIX"/", error->message); g_error_free (error); } #endif g_object_unref (config->client); config->client = NULL; } g_slist_free_full (config->preserve_name_prefixes, (GDestroyNotify) g_free); config->preserve_name_prefixes = NULL; IBUS_OBJECT_CLASS (ibus_config_dconf_parent_class)-> destroy ((IBusObject *)config); } static gboolean ibus_config_dconf_set_value (IBusConfigService *config, const gchar *section, const gchar *name, GVariant *value, GError **error) { IBusConfigDConf *dconf = IBUS_CONFIG_DCONF (config); DConfClient *client = dconf->client; gchar *path, *gpath, *gname, *gkey; gboolean retval; path = g_strdup_printf (DCONF_PREFIX"/%s", section); gpath = _to_gsettings_path (path); g_free (path); if (_has_prefixes (gpath, dconf->preserve_name_prefixes)) { gname = (char *) name; } else { gname = _to_gsettings_name (name); } gkey = g_strconcat (gpath, "/", gname, NULL); g_free (gpath); if (gname != name) { g_free (gname); } #ifdef DCONF_0_13_4 /* Use dconf_client_write_sync() instead of dconf_client_write_fast() * because dconf_client_write_fast() does not sync the data when * ibus_config_get_values() is called immediately after * ibus_config_set_value() is called. * We won't add a new API for the sync only since IBusConfig is * now deprecated and GSettings is recommended. */ retval = dconf_client_write_sync (client, gkey, value, NULL, NULL, error); #else retval = dconf_client_write (client, gkey, value, NULL, /* tag */ NULL, /* cancellable */ error); #endif g_free (gkey); return retval; } static GVariant * ibus_config_dconf_get_value (IBusConfigService *config, const gchar *section, const gchar *name, GError **error) { IBusConfigDConf *dconf = IBUS_CONFIG_DCONF (config); DConfClient *client = dconf->client; gchar *path, *gpath, *gname, *gkey; GVariant *variant; path = g_strdup_printf (DCONF_PREFIX"/%s", section); gpath = _to_gsettings_path (path); g_free (path); if (_has_prefixes (gpath, dconf->preserve_name_prefixes)) { gname = (char *) name; } else { gname = _to_gsettings_name (name); } gkey = g_strconcat (gpath, "/", gname, NULL); g_free (gpath); if (gname != name) { g_free (gname); } variant = dconf_client_read (client, gkey); g_free (gkey); if (variant == NULL) { g_set_error (error, G_DBUS_ERROR, G_DBUS_ERROR_FAILED, "Config value [%s:%s] does not exist.", section, name); return NULL; } return variant; } static GVariant * ibus_config_dconf_get_values (IBusConfigService *config, const gchar *section, GError **error) { IBusConfigDConf *dconf = IBUS_CONFIG_DCONF (config); DConfClient *client = dconf->client; gchar *dir, *gdir; gint len; gchar **entries, **p; GVariantBuilder builder; gboolean preserve_name; dir = g_strdup_printf (DCONF_PREFIX"/%s/", section); gdir = _to_gsettings_path (dir); g_free (dir); preserve_name = _has_prefixes (gdir, dconf->preserve_name_prefixes); entries = dconf_client_list (client, gdir, &len); g_variant_builder_init (&builder, G_VARIANT_TYPE ("a{sv}")); for (p = entries; *p != NULL; p++) { gchar *gkey = g_strconcat (gdir, *p, NULL); GVariant *value = dconf_client_read (client, gkey); g_free (gkey); if (value) { gchar *name = *p; if (!preserve_name) { name = _from_gsettings_name (*p); } g_variant_builder_add (&builder, "{sv}", name, value); if (name != *p) { g_free (name); } g_variant_unref (value); } } g_strfreev (entries); g_free (gdir); return g_variant_builder_end (&builder); } static gboolean ibus_config_dconf_unset_value (IBusConfigService *config, const gchar *section, const gchar *name, GError **error) { return ibus_config_dconf_set_value (config, section, name, NULL, error); } IBusConfigDConf * ibus_config_dconf_new (GDBusConnection *connection) { IBusConfigDConf *config; config = (IBusConfigDConf *) g_object_new (IBUS_TYPE_CONFIG_DCONF, "object-path", IBUS_PATH_CONFIG, "connection", connection, NULL); return config; }