1 /* -*- mode: C; c-basic-offset: 4; indent-tabs-mode: nil; -*- */
2 /* vim:set et sts=4: */
3 /* ibus - The Input Bus
4  * Copyright (C) 2008-2010 Peng Huang <shawn.p.huang@gmail.com>
5  * Copyright (C) 2011 Daiki Ueno <ueno@unixuser.org>
6  * Copyright (C) 2008-2011 Red Hat, Inc.
7  *
8  * This library is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU Lesser General Public
10  * License as published by the Free Software Foundation; either
11  * version 2.1 of the License, or (at your option) any later version.
12  *
13  * This library is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16  * Lesser General Public License for more details.
17  *
18  * You should have received a copy of the GNU Lesser General Public
19  * License along with this library; if not, write to the Free Software
20  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301
21  * USA
22  */
23 
24 #include <string.h>
25 #include <ibus.h>
26 #include "config-private.h"
27 
28 #define DCONF_PREFIX "/desktop/ibus"
29 #define DCONF_PRESERVE_NAME_PREFIXES_KEY \
30     DCONF_PREFIX"/general/dconf-preserve-name-prefixes"
31 
32 struct _IBusConfigDConf {
33     IBusConfigService parent;
34     DConfClient *client;
35 
36     /* if a dconf path matches one of preserve_name_prefixes, don't convert
37        key names from/to GSettings key names (see _to_gsettings_name
38        and _from_gsettings_name) */
39     GSList *preserve_name_prefixes;
40 };
41 
42 struct _IBusConfigDConfClass {
43     IBusConfigServiceClass parent;
44 };
45 
46 /* functions prototype */
47 static void      ibus_config_dconf_class_init  (IBusConfigDConfClass *class);
48 static void      ibus_config_dconf_init        (IBusConfigDConf      *config);
49 static void      ibus_config_dconf_destroy     (IBusConfigDConf      *config);
50 static gboolean  ibus_config_dconf_set_value   (IBusConfigService    *config,
51                                                 const gchar          *section,
52                                                 const gchar          *name,
53                                                 GVariant             *value,
54                                                 GError              **error);
55 static GVariant *ibus_config_dconf_get_value   (IBusConfigService    *config,
56                                                 const gchar          *section,
57                                                 const gchar          *name,
58                                                 GError              **error);
59 static GVariant *ibus_config_dconf_get_values  (IBusConfigService    *config,
60                                                 const gchar          *section,
61                                                 GError              **error);
62 static gboolean  ibus_config_dconf_unset_value (IBusConfigService    *config,
63                                                 const gchar          *section,
64                                                 const gchar          *name,
65                                                 GError              **error);
66 
G_DEFINE_TYPE(IBusConfigDConf,ibus_config_dconf,IBUS_TYPE_CONFIG_SERVICE)67 G_DEFINE_TYPE (IBusConfigDConf, ibus_config_dconf, IBUS_TYPE_CONFIG_SERVICE)
68 
69 static void
70 ibus_config_dconf_class_init (IBusConfigDConfClass *class)
71 {
72     IBusObjectClass *object_class = IBUS_OBJECT_CLASS (class);
73     IBusConfigServiceClass *config_class = IBUS_CONFIG_SERVICE_CLASS (class);
74 
75     object_class->destroy = (IBusObjectDestroyFunc) ibus_config_dconf_destroy;
76     config_class->set_value = ibus_config_dconf_set_value;
77     config_class->get_value = ibus_config_dconf_get_value;
78     config_class->get_values = ibus_config_dconf_get_values;
79     config_class->unset_value = ibus_config_dconf_unset_value;
80 }
81 
82 static gboolean
_has_prefixes(const gchar * path,GSList * prefixes)83 _has_prefixes (const gchar *path, GSList *prefixes)
84 {
85     GSList *head = prefixes;
86     for (; head; head = head->next) {
87         if (g_str_has_prefix (path, head->data)) {
88             return TRUE;
89         }
90     }
91     return FALSE;
92 }
93 
94 /* Convert key names from/to GSettings names.  While GSettings only
95  * accepts lowercase letters / numbers / and dash ('-'), IBus uses
96  * underscore ('_') and some engines even use uppercase letters.
97  *
98  * To minimize the gap, we do the following conversion:
99  *
100  * - when converting from IBus names to GSettings names, first convert
101  *   all letters to lowercase and then replace underscores with dashes.
102  * - when converting from GSettings names to IBus names, simply
103  *   replace dashes with underscores.
104  *
105  * Note that though the conversion does not always roundtrip, it does
106  * in most cases.
107  */
108 static gchar *
_to_gsettings_name(const gchar * name)109 _to_gsettings_name (const gchar *name)
110 {
111     return g_strcanon (g_ascii_strdown (name, -1),
112                        "abcdefghijklmnopqrstuvwxyz0123456789-",
113                        '-');
114 }
115 
116 static gchar *
_from_gsettings_name(const gchar * name)117 _from_gsettings_name (const gchar *name)
118 {
119     gchar *retval = g_strdup (name), *p;
120     for (p = retval; *p != '\0'; p++)
121         if (*p == '-')
122             *p = '_';
123     return retval;
124 }
125 
126 typedef gchar *(* NameConvFunc) (const gchar *);
127 
128 static gchar *
_conv_path(const gchar * path,NameConvFunc conv_func)129 _conv_path (const gchar *path, NameConvFunc conv_func)
130 {
131     gchar **strv = g_strsplit (path, "/", -1), **p;
132     gchar *retval;
133 
134     for (p = strv; *p; p++) {
135         gchar *canon;
136         canon = (*conv_func) (*p);
137         g_free (*p);
138         *p = canon;
139     }
140 
141     retval = g_strjoinv ("/", strv);
142     g_strfreev (strv);
143     return retval;
144 }
145 
146 static gchar *
_to_gsettings_path(const gchar * path)147 _to_gsettings_path (const gchar *path)
148 {
149     return _conv_path (path, _to_gsettings_name);
150 }
151 
152 static gchar *
_from_gsettings_path(const gchar * gpath)153 _from_gsettings_path (const gchar *gpath)
154 {
155     return _conv_path (gpath, _from_gsettings_name);
156 }
157 
158 static void
_watch_func(DConfClient * client,const gchar * gpath,const gchar * const * items,gint n_items,const gchar * tag,IBusConfigDConf * config)159 _watch_func (DConfClient         *client,
160              const gchar         *gpath,
161              const gchar * const *items,
162 #ifndef DCONF_0_13_4
163              gint                 n_items,
164 #endif
165              const gchar         *tag,
166              IBusConfigDConf     *config)
167 {
168     gchar **gkeys = NULL;
169     gint i;
170 #ifdef DCONF_0_13_4
171     gint n_items;
172 
173     n_items = g_strv_length ((gchar **)items);
174 #endif
175 
176     g_return_if_fail (gpath != NULL);
177     g_return_if_fail (n_items >= 0);
178 
179     if (dconf_is_key (gpath, NULL)) {
180         /* If path is a key, the notification should be taken to mean
181            that one key may have changed. */
182         n_items = 1;
183         gkeys = g_malloc0_n (n_items + 1, sizeof (gchar *));
184         gkeys[0] = g_strdup (gpath);
185     } else {
186         if (n_items == 0) {
187             /* If path is a dir and items is empty then it is an
188                indication that any key under path may have
189                changed. */
190             gkeys = dconf_client_list (config->client, gpath, &n_items);
191         } else {
192             gkeys = g_boxed_copy (G_TYPE_STRV, items);
193         }
194         for (i = 0; i < n_items; i++) {
195             gchar *gname = gkeys[i];
196             gkeys[i] = g_strdup_printf ("%s/%s", gpath, gname);
197             g_free (gname);
198         }
199     }
200 
201     for (i = 0; i < n_items; i++) {
202         gchar *gname, *path, *name;
203         GVariant *variant = dconf_client_read (config->client, gkeys[i]);
204 
205         if (variant == NULL) {
206             /* Use a empty tuple for a unset value */
207             variant = g_variant_new_tuple (NULL, 0);
208             g_variant_ref_sink (variant);
209         }
210 
211         gname = strrchr (gkeys[i], '/');
212         g_assert (gname);
213         *gname++ = '\0';
214 
215         if (_has_prefixes (gkeys[i], config->preserve_name_prefixes)) {
216             path = gkeys[i];
217             name = gname;
218         } else {
219             path = _from_gsettings_path (gkeys[i]);
220             name = _from_gsettings_name (gname);
221         }
222 
223         ibus_config_service_value_changed ((IBusConfigService *) config,
224                                            path + sizeof (DCONF_PREFIX),
225                                            name,
226                                            variant);
227         if (path != gkeys[i]) {
228             g_free (path);
229         }
230         if (name != gname) {
231             g_free (name);
232         }
233         g_variant_unref (variant);
234     }
235     g_strfreev (gkeys);
236 }
237 
238 static void
ibus_config_dconf_init(IBusConfigDConf * config)239 ibus_config_dconf_init (IBusConfigDConf *config)
240 {
241     GVariant *variant;
242 #ifdef DCONF_0_13_4
243     config->client = dconf_client_new ();
244 
245     g_signal_connect (config->client, "changed",
246                       G_CALLBACK (_watch_func), config);
247 
248     dconf_client_watch_fast (config->client, DCONF_PREFIX"/");
249 #else
250     GError *error;
251 
252     config->client = dconf_client_new ("ibus",
253                                        (DConfWatchFunc)_watch_func,
254                                        config,
255                                        NULL);
256 
257     error = NULL;
258     if (!dconf_client_watch (config->client, DCONF_PREFIX"/", NULL, &error)) {
259         g_warning ("Can not watch dconf path %s: %s",
260                    DCONF_PREFIX"/", error->message);
261         g_error_free (error);
262     }
263 #endif
264 
265     config->preserve_name_prefixes = NULL;
266     variant = dconf_client_read (config->client,
267                                  DCONF_PRESERVE_NAME_PREFIXES_KEY);
268     if (variant != NULL) {
269         GVariantIter iter;
270         GVariant *child;
271 
272         g_variant_iter_init (&iter, variant);
273         while ((child = g_variant_iter_next_value (&iter))) {
274             char *prefix = g_variant_dup_string (child, NULL);
275             config->preserve_name_prefixes =
276                 g_slist_prepend (config->preserve_name_prefixes,
277                                  prefix);
278             g_variant_unref (child);
279         }
280         g_variant_unref (variant);
281     }
282 }
283 
284 static void
ibus_config_dconf_destroy(IBusConfigDConf * config)285 ibus_config_dconf_destroy (IBusConfigDConf *config)
286 {
287     if (config->client) {
288 #ifdef DCONF_0_13_4
289         dconf_client_unwatch_fast (config->client, DCONF_PREFIX"/");
290 #else
291         GError *error = NULL;
292         if (!dconf_client_unwatch (config->client, DCONF_PREFIX"/", NULL, &error)) {
293             g_warning ("Can not unwatch dconf path %s: %s",
294                        DCONF_PREFIX"/", error->message);
295             g_error_free (error);
296         }
297 #endif
298 
299         g_object_unref (config->client);
300         config->client = NULL;
301     }
302 
303     g_slist_free_full (config->preserve_name_prefixes, (GDestroyNotify) g_free);
304     config->preserve_name_prefixes = NULL;
305 
306     IBUS_OBJECT_CLASS (ibus_config_dconf_parent_class)->
307         destroy ((IBusObject *)config);
308 }
309 
310 static gboolean
ibus_config_dconf_set_value(IBusConfigService * config,const gchar * section,const gchar * name,GVariant * value,GError ** error)311 ibus_config_dconf_set_value (IBusConfigService *config,
312                              const gchar       *section,
313                              const gchar       *name,
314                              GVariant          *value,
315                              GError           **error)
316 {
317     IBusConfigDConf *dconf = IBUS_CONFIG_DCONF (config);
318     DConfClient *client = dconf->client;
319     gchar *path, *gpath, *gname, *gkey;
320     gboolean retval;
321 
322     path = g_strdup_printf (DCONF_PREFIX"/%s", section);
323     gpath = _to_gsettings_path (path);
324     g_free (path);
325 
326     if (_has_prefixes (gpath, dconf->preserve_name_prefixes)) {
327         gname = (char *) name;
328     } else {
329         gname = _to_gsettings_name (name);
330     }
331     gkey = g_strconcat (gpath, "/", gname, NULL);
332     g_free (gpath);
333     if (gname != name) {
334         g_free (gname);
335     }
336 
337 #ifdef DCONF_0_13_4
338     /* Use dconf_client_write_sync() instead of dconf_client_write_fast()
339      * because dconf_client_write_fast() does not sync the data when
340      * ibus_config_get_values() is called immediately after
341      * ibus_config_set_value() is called.
342      * We won't add a new API for the sync only since IBusConfig is
343      * now deprecated and GSettings is recommended.
344      */
345     retval = dconf_client_write_sync (client,
346                                       gkey,
347                                       value,
348                                       NULL,
349                                       NULL,
350                                       error);
351 #else
352     retval = dconf_client_write (client,
353                                  gkey,
354                                  value,
355                                  NULL,   /* tag */
356                                  NULL,   /* cancellable */
357                                  error);
358 #endif
359     g_free (gkey);
360 
361     return retval;
362 }
363 
364 static GVariant *
ibus_config_dconf_get_value(IBusConfigService * config,const gchar * section,const gchar * name,GError ** error)365 ibus_config_dconf_get_value (IBusConfigService *config,
366                              const gchar       *section,
367                              const gchar       *name,
368                              GError           **error)
369 {
370     IBusConfigDConf *dconf = IBUS_CONFIG_DCONF (config);
371     DConfClient *client = dconf->client;
372     gchar *path, *gpath, *gname, *gkey;
373     GVariant *variant;
374 
375     path = g_strdup_printf (DCONF_PREFIX"/%s", section);
376     gpath = _to_gsettings_path (path);
377     g_free (path);
378 
379     if (_has_prefixes (gpath, dconf->preserve_name_prefixes)) {
380         gname = (char *) name;
381     } else {
382         gname = _to_gsettings_name (name);
383     }
384     gkey = g_strconcat (gpath, "/", gname, NULL);
385     g_free (gpath);
386     if (gname != name) {
387         g_free (gname);
388     }
389 
390     variant = dconf_client_read (client, gkey);
391     g_free (gkey);
392 
393     if (variant == NULL) {
394         g_set_error (error,
395                      G_DBUS_ERROR,
396                      G_DBUS_ERROR_FAILED,
397                      "Config value [%s:%s] does not exist.",
398                      section, name);
399         return NULL;
400     }
401 
402     return variant;
403 }
404 
405 static GVariant *
ibus_config_dconf_get_values(IBusConfigService * config,const gchar * section,GError ** error)406 ibus_config_dconf_get_values (IBusConfigService *config,
407                               const gchar       *section,
408                               GError           **error)
409 {
410     IBusConfigDConf *dconf = IBUS_CONFIG_DCONF (config);
411     DConfClient *client = dconf->client;
412     gchar *dir, *gdir;
413     gint len;
414     gchar **entries, **p;
415     GVariantBuilder builder;
416     gboolean preserve_name;
417 
418     dir = g_strdup_printf (DCONF_PREFIX"/%s/", section);
419     gdir = _to_gsettings_path (dir);
420     g_free (dir);
421 
422     preserve_name = _has_prefixes (gdir, dconf->preserve_name_prefixes);
423 
424     entries = dconf_client_list (client, gdir, &len);
425     g_variant_builder_init (&builder, G_VARIANT_TYPE ("a{sv}"));
426     for (p = entries; *p != NULL; p++) {
427         gchar *gkey = g_strconcat (gdir, *p, NULL);
428         GVariant *value = dconf_client_read (client, gkey);
429         g_free (gkey);
430         if (value) {
431             gchar *name = *p;
432             if (!preserve_name) {
433                 name = _from_gsettings_name (*p);
434             }
435             g_variant_builder_add (&builder, "{sv}", name, value);
436             if (name != *p) {
437                 g_free (name);
438             }
439             g_variant_unref (value);
440         }
441     }
442     g_strfreev (entries);
443     g_free (gdir);
444 
445     return g_variant_builder_end (&builder);
446 }
447 
448 static gboolean
ibus_config_dconf_unset_value(IBusConfigService * config,const gchar * section,const gchar * name,GError ** error)449 ibus_config_dconf_unset_value (IBusConfigService *config,
450                                const gchar       *section,
451                                const gchar       *name,
452                                GError           **error)
453 {
454     return ibus_config_dconf_set_value (config, section, name, NULL, error);
455 }
456 
457 IBusConfigDConf *
ibus_config_dconf_new(GDBusConnection * connection)458 ibus_config_dconf_new (GDBusConnection *connection)
459 {
460     IBusConfigDConf *config;
461     config = (IBusConfigDConf *) g_object_new (IBUS_TYPE_CONFIG_DCONF,
462                                                "object-path", IBUS_PATH_CONFIG,
463                                                "connection", connection,
464                                                NULL);
465     return config;
466 }
467