/* -*- 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 <shawn.p.huang@gmail.com>
* Copyright (C) 2011 Daiki Ueno <ueno@unixuser.org>
* 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 <string.h>
#include <ibus.h>
#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;
}