1 /*
2  * This library is free software; you can redistribute it and/or
3  * modify it under the terms of the GNU General Public License as
4  * published by the Free Software Foundation; either version 2 of the
5  * License, or (at your option) version 3.
6  *
7  * This library is distributed in the hope that it will be useful, but
8  * WITHOUT ANY WARRANTY; without even the implied warranty of
9  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
10  * Library General Public License for more details.
11  *
12  * You should have received a copy of the GNU Library General Public
13  * License along with this library; if not, write to the Free Software
14  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301
15  * USA.
16  */
17 
18 #include <string.h>
19 
20 struct _GOConfNode {
21 	gchar *path;
22 	gchar *id;
23 	gchar *key;
24 	GSettings *settings;
25 	unsigned ref_count;
26 };
27 
28 static GHashTable *installed_schemas, *closures;
29 void
_go_conf_init(void)30 _go_conf_init (void)
31 {
32 	char const * const *schemas = g_settings_list_schemas ();
33 	char const * const *cur = schemas;
34 	installed_schemas = g_hash_table_new (g_str_hash, g_str_equal);
35 	while (*cur) {
36 		g_hash_table_insert (installed_schemas, (gpointer) *cur, GUINT_TO_POINTER (TRUE));
37 		cur++;
38 	}
39 	closures = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, (GDestroyNotify) go_conf_closure_free);
40 }
41 
42 void
_go_conf_shutdown(void)43 _go_conf_shutdown (void)
44 {
45 	g_hash_table_destroy (installed_schemas);
46 	g_hash_table_destroy (closures);
47 }
48 
49 static gchar *
go_conf_format_id(gchar const * key)50 go_conf_format_id (gchar const *key)
51 {
52 	char *cur, *res;
53 	if (!key)
54 		return NULL;
55 	res = g_strdup (key);
56 	/* replace all slashes by dots */
57 	cur = res;
58 	while ((cur = strchr (cur, '/')) && *cur)
59 		*cur = '.';
60 	/* replace all underscores by hyphens */
61 	cur = res;
62 	while ((cur = strchr (cur, '_')) && *cur)
63 		*cur = '-';
64 	cur = res;
65 	while (*cur) {
66 		*cur = g_ascii_tolower (*cur);
67 		cur++;
68 	}
69 	return res;
70 }
71 
72 GOConfNode *
go_conf_get_node(GOConfNode * parent,gchar const * key)73 go_conf_get_node (GOConfNode *parent, gchar const *key)
74 {
75 	GOConfNode *node;
76 	char *formatted;
77 
78 	g_return_val_if_fail (parent || key, NULL);
79 
80 	formatted = go_conf_format_id (key);
81 	node = g_new0 (GOConfNode, 1);
82 	node->ref_count = 1;
83 	if (parent) {
84 		if (key && !parent->key) {
85 			node->path = g_strconcat (parent->path, "/", key, NULL);
86 			node->id = g_strconcat (parent->id, ".", formatted, NULL);
87 		} else {
88 			node->path = g_strdup (parent->path);
89 			node->id = g_strdup (parent->id);
90 			node->key = g_strdup (key? key: parent->key);
91 		}
92 	} else {
93 		if (key[0] == '/') {
94 			node->path = g_strdup (key);
95 			node->id = g_strconcat ("org.gnome", formatted, NULL);
96 		} else {
97 			node->path = g_strconcat ("/apps/", key, NULL);
98 			node->id = g_strconcat ("org.gnome.", formatted, NULL);
99 		}
100 	}
101 	node->settings = g_hash_table_lookup (installed_schemas, node->id)? g_settings_new (node->id): NULL;
102 	g_free (formatted);
103 	if (!node->settings) {
104 		char *last_dot = strrchr (node->id, '.');
105 		*last_dot = 0;
106 		node->settings = g_hash_table_lookup (installed_schemas, node->id)? g_settings_new (node->id): NULL;
107 		if (node->settings) {
108 			g_free (node->key);
109 			node->key = g_strdup (last_dot + 1);
110 		} else {
111 			go_conf_free_node (node);
112 			node = NULL;
113 		}
114 	}
115 	return node;
116 }
117 
118 void
go_conf_free_node(GOConfNode * node)119 go_conf_free_node (GOConfNode *node)
120 {
121 	if (!node)
122 		return;
123 
124 	g_return_if_fail (node->ref_count > 0);
125 
126 	node->ref_count--;
127 	if (node->ref_count > 0)
128 		return;
129 
130 	if (node->settings)
131 		g_object_unref (node->settings);
132 	g_free (node->path);
133 	g_free (node->id);
134 	g_free (node->key);
135 	g_free (node);
136 }
137 
138 void
go_conf_sync(GOConfNode * node)139 go_conf_sync (GOConfNode *node)
140 {
141 	g_settings_sync ();
142 }
143 
144 void
go_conf_set_bool(GOConfNode * node,gchar const * key,gboolean val)145 go_conf_set_bool (GOConfNode *node, gchar const *key, gboolean val)
146 {
147 	GOConfNode *real_node = go_conf_get_node (node, key);
148 	if (!real_node) {
149 		d (g_warning ("Unable to set key '%s'", key));
150 		return;
151 	}
152 	g_settings_set_boolean (real_node->settings, real_node->key, val);
153 	go_conf_free_node (real_node);
154 }
155 
156 void
go_conf_set_int(GOConfNode * node,gchar const * key,gint val)157 go_conf_set_int (GOConfNode *node, gchar const *key, gint val)
158 {
159 	GOConfNode *real_node = go_conf_get_node (node, key);
160 	if (!real_node) {
161 		d (g_warning ("Unable to set key '%s'", key));
162 		return;
163 	}
164 	g_settings_set_int (real_node->settings, real_node->key, val);
165 	go_conf_free_node (real_node);
166 }
167 
168 void
go_conf_set_double(GOConfNode * node,gchar const * key,gdouble val)169 go_conf_set_double (GOConfNode *node, gchar const *key, gdouble val)
170 {
171 	GOConfNode *real_node = go_conf_get_node (node, key);
172 	if (!real_node) {
173 		d (g_warning ("Unable to set key '%s'", key));
174 		return;
175 	}
176 	g_settings_set_double (real_node->settings, real_node->key, val);
177 	go_conf_free_node (real_node);
178 }
179 
180 void
go_conf_set_string(GOConfNode * node,gchar const * key,gchar const * str)181 go_conf_set_string (GOConfNode *node, gchar const *key, gchar const *str)
182 {
183 	GOConfNode *real_node = go_conf_get_node (node, key);
184 	if (!real_node) {
185 		d (g_warning ("Unable to set key '%s'", key));
186 		return;
187 	}
188 	g_settings_set_string (real_node->settings, real_node->key, str);
189 	go_conf_free_node (real_node);
190 }
191 
192 void
go_conf_set_str_list(GOConfNode * node,gchar const * key,GSList * list)193 go_conf_set_str_list (GOConfNode *node, gchar const *key, GSList *list)
194 {
195 	GOConfNode *real_node = go_conf_get_node (node, key);
196 	gssize n = g_slist_length (list);
197 	char const ** strs;
198 	GSList *ptr = list;
199 	unsigned i = 0;
200 
201 	if (!real_node) {
202 		d (g_warning ("Unable to set key '%s'", key));
203 		return;
204 	}
205 	strs = g_new (char const*, n + 1);
206 	for (; ptr; ptr = ptr->next)
207 		strs[i++] = (char const *) ptr->data;
208 	strs[n] = NULL;
209 	g_settings_set_strv (real_node->settings, real_node->key, strs);
210 	g_free (strs);
211 	go_conf_free_node (real_node);
212 }
213 
214 static GVariant *
go_conf_get(GOConfNode * node,gchar const * key,GVariantType const * t)215 go_conf_get (GOConfNode *node, gchar const *key, GVariantType const *t)
216 {
217 	GVariant *res = g_settings_get_value (node->settings, key);
218 	if (res == NULL) {
219 		d (g_warning ("Unable to load key '%s'", key));
220 		return NULL;
221 	}
222 
223 	if (!g_variant_is_of_type (res, t)) {
224 		char *tstr = g_variant_type_dup_string (t);
225 		g_warning ("Expected `%s' got `%s' for key %s",
226 			   tstr,
227 			   g_variant_get_type_string (res),
228 			   key);
229 		g_free (tstr);
230 		g_variant_unref (res);
231 		return NULL;
232 	}
233 
234 	return res;
235 }
236 
237 gboolean
go_conf_load_bool(GOConfNode * node,gchar const * key,gboolean default_val)238 go_conf_load_bool (GOConfNode *node, gchar const *key, gboolean default_val)
239 {
240 	gboolean res;
241 	GVariant *val = NULL;
242 	if (node) {
243 		if (key && !strchr (key, '/') &&  !strchr (key, '.'))
244 			val = go_conf_get (node, key, G_VARIANT_TYPE_BOOLEAN);
245 		else if (node->key)
246 			val = go_conf_get (node, node->key, G_VARIANT_TYPE_BOOLEAN);
247 	}
248 	if (val == NULL) {
249 		GOConfNode *real_node = go_conf_get_node (node, key);
250 		val = real_node? go_conf_get (real_node, real_node->key, G_VARIANT_TYPE_BOOLEAN): NULL;
251 		go_conf_free_node (real_node);
252 	}
253 
254 	if (val != NULL) {
255 		res = g_variant_get_boolean (val);
256 		g_variant_unref (val);
257 	} else {
258 		d (g_warning ("Using default value '%s'", default_val ? "true" : "false"));
259 		return default_val;
260 	}
261 	return res;
262 }
263 
264 gint
go_conf_load_int(GOConfNode * node,gchar const * key,gint minima,gint maxima,gint default_val)265 go_conf_load_int (GOConfNode *node, gchar const *key, gint minima, gint maxima, gint default_val)
266 {
267 	gint res = -1;
268 	GVariant *val = NULL;
269 	if (node) {
270 		if (key && !strchr (key, '/') &&  !strchr (key, '.'))
271 			val = go_conf_get (node, key, G_VARIANT_TYPE_INT32);
272 		else if (node->key)
273 			val = go_conf_get (node, node->key, G_VARIANT_TYPE_INT32);
274 	}
275 	if (val == NULL) {
276 		GOConfNode *real_node = go_conf_get_node (node, key);
277 		val = real_node? go_conf_get (real_node, real_node->key, G_VARIANT_TYPE_INT32): NULL;
278 		go_conf_free_node (real_node);
279 	}
280 	if (val != NULL) {
281 		res = g_variant_get_int32 (val);
282 		g_variant_unref (val);
283 		if (res < minima || maxima < res) {
284 			g_warning ("Invalid value '%d' for %s.  If should be >= %d and <= %d",
285 				   res, key, minima, maxima);
286 			val = NULL;
287 		}
288 	} else {
289 		d (g_warning ("Using default value '%d'", default_val));
290 		return default_val;
291 	}
292 	return res;
293 }
294 
295 gdouble
go_conf_load_double(GOConfNode * node,gchar const * key,gdouble minima,gdouble maxima,gdouble default_val)296 go_conf_load_double (GOConfNode *node, gchar const *key,
297 		     gdouble minima, gdouble maxima, gdouble default_val)
298 {
299         gdouble res = -1;
300 	GVariant *val = NULL;
301 	if (node) {
302 		if (key && !strchr (key, '/') &&  !strchr (key, '.'))
303 			val = go_conf_get (node, key, G_VARIANT_TYPE_DOUBLE);
304 		else if (node->key)
305 			val = go_conf_get (node, node->key, G_VARIANT_TYPE_DOUBLE);
306 	}
307 	if (val == NULL) {
308 		GOConfNode *real_node = go_conf_get_node (node, key);
309 		val = real_node? go_conf_get (real_node, real_node->key, G_VARIANT_TYPE_DOUBLE): NULL;
310 		go_conf_free_node (real_node);
311 	}
312 	if (val != NULL) {
313 		res = g_variant_get_double (val);
314 		g_variant_unref (val);
315 		if (res < minima || maxima < res) {
316 			g_warning ("Invalid value '%g' for %s.  If should be >= %g and <= %g",
317 				   res, key, minima, maxima);
318 			val = NULL;
319 		}
320 	} else {
321 		d (g_warning ("Using default value '%g'", default_val));
322 		return default_val;
323 	}
324 	return res;
325 }
326 
327 gchar *
go_conf_load_string(GOConfNode * node,gchar const * key)328 go_conf_load_string (GOConfNode *node, gchar const *key)
329 {
330 	char *res = NULL;
331 	if (node) {
332 		if (key && !strchr (key, '/') &&  !strchr (key, '.'))
333 			res = g_settings_get_string (node->settings, key);
334 		else if (node->key)
335 			res = g_settings_get_string (node->settings, node->key);
336 	}
337 	if (res == NULL) {
338 		GOConfNode *real_node = go_conf_get_node (node, key);
339 		res = (real_node)? g_settings_get_string (real_node->settings, real_node->key): NULL;
340 		go_conf_free_node (real_node);
341 	}
342 	return res;
343 }
344 
345 GSList *
go_conf_load_str_list(GOConfNode * node,gchar const * key)346 go_conf_load_str_list (GOConfNode *node, gchar const *key)
347 {
348 	char **strs = NULL, **ptr;
349 	GSList *list = NULL;
350 
351 	if (node) {
352 		if (key && !strchr (key, '/') &&  !strchr (key, '.'))
353 			strs = g_settings_get_strv (node->settings, key);
354 		else if (node->key)
355 			strs = g_settings_get_strv (node->settings, node->key);
356 	}
357 	if (strs == NULL) {
358 		GOConfNode *real_node = go_conf_get_node (node, key);
359 		strs = real_node? g_settings_get_strv (node->settings, real_node->key): NULL;
360 		go_conf_free_node (real_node);
361 	}
362 	if (strs) {
363 		for (ptr = strs; *ptr; ptr++)
364 			list = g_slist_prepend (list, g_strdup (*ptr));
365 		g_strfreev (strs);
366 		list = g_slist_reverse (list);
367 	}
368 
369 	return list;
370 }
371 
372 gboolean
go_conf_get_bool(GOConfNode * node,gchar const * key)373 go_conf_get_bool (GOConfNode *node, gchar const *key)
374 {
375 	gboolean res, failed = node == NULL;
376 	if (!failed) {
377 		if (key && !strchr (key, '/') &&  !strchr (key, '.'))
378 			res = g_settings_get_boolean (node->settings, key);
379 		else if (node->key)
380 			res = g_settings_get_boolean (node->settings, node->key);
381 		else
382 			failed = TRUE;
383 	}
384 	if (failed) {
385 		GOConfNode *real_node = go_conf_get_node (node, key);
386 		res = (real_node)? g_settings_get_boolean (real_node->settings, real_node->key): FALSE;
387 		go_conf_free_node (real_node);
388 	}
389 	return res;
390 }
391 
392 gint
go_conf_get_int(GOConfNode * node,gchar const * key)393 go_conf_get_int	(GOConfNode *node, gchar const *key)
394 {
395 	GOConfNode *real_node = go_conf_get_node (node, key);
396 	gint res = (real_node)? g_settings_get_int (real_node->settings, real_node->key): 0;
397 	go_conf_free_node (real_node);
398 	return res;
399 }
400 
401 gdouble
go_conf_get_double(GOConfNode * node,gchar const * key)402 go_conf_get_double (GOConfNode *node, gchar const *key)
403 {
404 	GOConfNode *real_node = go_conf_get_node (node, key);
405 	gdouble res = (real_node)? g_settings_get_double (real_node->settings, real_node->key): 0.;
406 	go_conf_free_node (real_node);
407 	return res;
408 }
409 
410 gchar *
go_conf_get_string(GOConfNode * node,gchar const * key)411 go_conf_get_string (GOConfNode *node, gchar const *key)
412 {
413 	GOConfNode *real_node = go_conf_get_node (node, key);
414 	gchar *res = (real_node)? g_settings_get_string (real_node->settings, real_node->key): NULL;
415 	go_conf_free_node (real_node);
416 	return res;
417 }
418 
419 GSList *
go_conf_get_str_list(GOConfNode * node,gchar const * key)420 go_conf_get_str_list (GOConfNode *node, gchar const *key)
421 {
422 	return go_conf_load_str_list (node, key);
423 }
424 
425 void
go_conf_remove_monitor(guint monitor_id)426 go_conf_remove_monitor (guint monitor_id)
427 {
428 	GOConfClosure *cls = g_hash_table_lookup (closures, GUINT_TO_POINTER (monitor_id));
429 	if (cls) {
430 		g_signal_handler_disconnect (cls->node->settings, monitor_id);
431 		g_hash_table_remove (closures, GUINT_TO_POINTER (monitor_id));
432 	} else
433 		g_warning ("unknown GOConfMonitor id.");
434 }
435 
436 static void
cb_key_changed(GSettings * settings,char * key,GOConfClosure * cls)437 cb_key_changed (GSettings *settings,
438 		char *key,
439 		GOConfClosure *cls)
440 {
441 	char *real_key;
442 	if (cls->key) {
443 		if (strcmp (cls->key, key))
444 			return; /* not the watched key */
445 		real_key = g_strdup (cls->real_key);
446 	} else
447 		real_key = g_strconcat (cls->real_key, "/", key, NULL);
448 	cls->monitor (cls->node, real_key , cls->data);
449 	g_free (real_key);
450 }
451 
452 guint
go_conf_add_monitor(GOConfNode * node,G_GNUC_UNUSED gchar const * key,GOConfMonitorFunc monitor,gpointer data)453 go_conf_add_monitor (GOConfNode *node, G_GNUC_UNUSED gchar const *key,
454 		     GOConfMonitorFunc monitor, gpointer data)
455 {
456 	guint ret;
457 	GOConfClosure *cls;
458 
459 	g_return_val_if_fail (node || key, 0);
460 	g_return_val_if_fail (monitor != NULL, 0);
461 
462 	cls = g_new (GOConfClosure, 1);
463 	cls->monitor = monitor;
464 	cls->node = node;
465 	cls->data = data;
466 	cls->key = g_strdup (key? key: node->key);
467 	cls->real_key = (key)? g_strconcat (node->path, '/', key, NULL): g_strdup (node->path);
468 	ret = g_signal_connect
469 		(node->settings,
470 		 "changed", G_CALLBACK (cb_key_changed),
471 		 cls);
472 
473 	g_hash_table_insert (closures, GUINT_TO_POINTER (ret), cls);
474 	return ret;
475 }
476