1 /*
2 * Authors: Rodney Dawes <dobey@ximian.com>
3 * Bastien Nocera <hadess@hadess.net>
4 *
5 * Copyright 2003-2006 Novell, Inc. (www.novell.com)
6 * Copyright 2011 Red Hat Inc.
7 *
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
12 *
13 * This program 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
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, see <http://www.gnu.org/licenses/>.
20 */
21
22 #include <gio/gio.h>
23 #include <string.h>
24 #include <libxml/parser.h>
25 #include <gdesktop-enums.h>
26
27 #include "gdesktop-enums-types.h"
28 #include "cc-background-item.h"
29 #include "cc-background-xml.h"
30
31 /* The number of items we signal as "added" before
32 * returning to the main loop */
33 #define NUM_ITEMS_PER_BATCH 1
34
35 struct _CcBackgroundXml
36 {
37 GObject parent_instance;
38
39 GHashTable *wp_hash;
40 GAsyncQueue *item_added_queue;
41 guint item_added_id;
42 GSList *monitors; /* GSList of GFileMonitor */
43 };
44
45 enum {
46 ADDED,
47 LAST_SIGNAL
48 };
49
50 static guint signals[LAST_SIGNAL] = { 0 };
51
G_DEFINE_TYPE(CcBackgroundXml,cc_background_xml,G_TYPE_OBJECT)52 G_DEFINE_TYPE (CcBackgroundXml, cc_background_xml, G_TYPE_OBJECT)
53
54 static gboolean
55 cc_background_xml_get_bool (const xmlNode *parent,
56 const gchar *prop_name)
57 {
58 xmlChar *prop;
59 gboolean ret_val = FALSE;
60
61 g_return_val_if_fail (parent != NULL, FALSE);
62 g_return_val_if_fail (prop_name != NULL, FALSE);
63
64 prop = xmlGetProp ((xmlNode *) parent, (xmlChar*)prop_name);
65 if (prop != NULL) {
66 if (!g_ascii_strcasecmp ((gchar *)prop, "true") || !g_ascii_strcasecmp ((gchar *)prop, "1")) {
67 ret_val = TRUE;
68 } else {
69 ret_val = FALSE;
70 }
71 xmlFree (prop);
72 }
73
74 return ret_val;
75 }
76
77 static struct {
78 int value;
79 const char *string;
80 } lookups[] = {
81 { G_DESKTOP_BACKGROUND_SHADING_HORIZONTAL, "horizontal-gradient" },
82 { G_DESKTOP_BACKGROUND_SHADING_VERTICAL, "vertical-gradient" },
83 };
84
85 static int
enum_string_to_value(GType type,const char * string)86 enum_string_to_value (GType type,
87 const char *string)
88 {
89 GEnumClass *eclass;
90 GEnumValue *value;
91
92 eclass = G_ENUM_CLASS (g_type_class_peek (type));
93 value = g_enum_get_value_by_nick (eclass, string);
94
95 /* Here's a bit of hand-made parsing, bad bad */
96 if (value == NULL) {
97 guint i;
98 for (i = 0; i < G_N_ELEMENTS (lookups); i++) {
99 if (g_str_equal (lookups[i].string, string))
100 return lookups[i].value;
101 }
102 g_warning ("Unhandled value '%s' for enum '%s'",
103 string, G_FLAGS_CLASS_TYPE_NAME (eclass));
104 return 0;
105 }
106
107 return value->value;
108 }
109
110 static gboolean
idle_emit(CcBackgroundXml * xml)111 idle_emit (CcBackgroundXml *xml)
112 {
113 gint i;
114
115 g_async_queue_lock (xml->item_added_queue);
116
117 for (i = 0; i < NUM_ITEMS_PER_BATCH; i++) {
118 g_autoptr(GObject) item = NULL;
119
120 item = g_async_queue_try_pop_unlocked (xml->item_added_queue);
121 if (item == NULL)
122 break;
123 g_signal_emit (G_OBJECT (xml), signals[ADDED], 0, item);
124 }
125
126 g_async_queue_unlock (xml->item_added_queue);
127
128 if (g_async_queue_length (xml->item_added_queue) > 0) {
129 return TRUE;
130 } else {
131 xml->item_added_id = 0;
132 return FALSE;
133 }
134 }
135
136 static void
emit_added_in_idle(CcBackgroundXml * xml,GObject * object)137 emit_added_in_idle (CcBackgroundXml *xml,
138 GObject *object)
139 {
140 g_async_queue_lock (xml->item_added_queue);
141 g_async_queue_push_unlocked (xml->item_added_queue, object);
142 if (xml->item_added_id == 0)
143 xml->item_added_id = g_idle_add ((GSourceFunc) idle_emit, xml);
144 g_async_queue_unlock (xml->item_added_queue);
145 }
146
147 #define NONE "(none)"
148 #define UNSET_FLAG(flag) G_STMT_START{ (flags&=~(flag)); }G_STMT_END
149 #define SET_FLAG(flag) G_STMT_START{ (flags|=flag); }G_STMT_END
150
151 static gboolean
cc_background_xml_load_xml_internal(CcBackgroundXml * xml,const gchar * filename,gboolean in_thread)152 cc_background_xml_load_xml_internal (CcBackgroundXml *xml,
153 const gchar *filename,
154 gboolean in_thread)
155 {
156 xmlDoc * wplist;
157 xmlNode * root, * list, * wpa;
158 xmlChar * nodelang;
159 const gchar * const * syslangs;
160 gint i;
161 gboolean retval;
162
163 wplist = xmlParseFile (filename);
164 retval = FALSE;
165
166 if (!wplist)
167 return retval;
168
169 syslangs = g_get_language_names ();
170
171 root = xmlDocGetRootElement (wplist);
172
173 for (list = root->children; list != NULL; list = list->next) {
174 if (!strcmp ((gchar *)list->name, "wallpaper")) {
175 g_autoptr(CcBackgroundItem) item = NULL;
176 CcBackgroundItemFlags flags;
177 g_autofree gchar *uri = NULL;
178 g_autofree gchar *cname = NULL;
179 g_autofree gchar *id = NULL;
180
181 flags = 0;
182 item = cc_background_item_new (NULL);
183
184 g_object_set (G_OBJECT (item),
185 "is-deleted", cc_background_xml_get_bool (list, "deleted"),
186 "source-xml", filename,
187 NULL);
188
189 for (wpa = list->children; wpa != NULL; wpa = wpa->next) {
190 if (wpa->type == XML_COMMENT_NODE) {
191 continue;
192 } else if (!strcmp ((gchar *)wpa->name, "filename")) {
193 if (wpa->last != NULL && wpa->last->content != NULL) {
194 gchar *content = g_strstrip ((gchar *)wpa->last->content);
195 g_autofree gchar *bg_uri = NULL;
196
197 /* FIXME same rubbish as in other parts of the code */
198 if (strcmp (content, NONE) == 0) {
199 bg_uri = NULL;
200 } else {
201 g_autoptr(GFile) file = NULL;
202 g_autofree gchar *dirname = NULL;
203
204 dirname = g_path_get_dirname (filename);
205 file = g_file_new_for_commandline_arg_and_cwd (content, dirname);
206 bg_uri = g_file_get_uri (file);
207 }
208 SET_FLAG(CC_BACKGROUND_ITEM_HAS_URI);
209 g_object_set (G_OBJECT (item), "uri", bg_uri, NULL);
210 } else {
211 break;
212 }
213 } else if (!strcmp ((gchar *)wpa->name, "name")) {
214 if (wpa->last != NULL && wpa->last->content != NULL) {
215 g_autofree gchar *name = NULL;
216 nodelang = xmlNodeGetLang (wpa->last);
217
218 g_object_get (G_OBJECT (item), "name", &name, NULL);
219
220 if (name == NULL && nodelang == NULL) {
221 g_free (cname);
222 cname = g_strdup (g_strstrip ((gchar *)wpa->last->content));
223 g_object_set (G_OBJECT (item), "name", cname, NULL);
224 } else {
225 for (i = 0; syslangs[i] != NULL; i++) {
226 if (!strcmp (syslangs[i], (gchar *)nodelang)) {
227 g_object_set (G_OBJECT (item), "name",
228 g_strstrip ((gchar *)wpa->last->content), NULL);
229 break;
230 }
231 }
232 }
233
234 xmlFree (nodelang);
235 } else {
236 break;
237 }
238 } else if (!strcmp ((gchar *)wpa->name, "options")) {
239 if (wpa->last != NULL) {
240 g_object_set (G_OBJECT (item), "placement",
241 enum_string_to_value (G_DESKTOP_TYPE_DESKTOP_BACKGROUND_STYLE,
242 g_strstrip ((gchar *)wpa->last->content)), NULL);
243 SET_FLAG(CC_BACKGROUND_ITEM_HAS_PLACEMENT);
244 }
245 } else if (!strcmp ((gchar *)wpa->name, "shade_type")) {
246 if (wpa->last != NULL) {
247 g_object_set (G_OBJECT (item), "shading",
248 enum_string_to_value (G_DESKTOP_TYPE_DESKTOP_BACKGROUND_SHADING,
249 g_strstrip ((gchar *)wpa->last->content)), NULL);
250 SET_FLAG(CC_BACKGROUND_ITEM_HAS_SHADING);
251 }
252 } else if (!strcmp ((gchar *)wpa->name, "pcolor")) {
253 if (wpa->last != NULL) {
254 g_object_set (G_OBJECT (item), "primary-color",
255 g_strstrip ((gchar *)wpa->last->content), NULL);
256 SET_FLAG(CC_BACKGROUND_ITEM_HAS_PCOLOR);
257 }
258 } else if (!strcmp ((gchar *)wpa->name, "scolor")) {
259 if (wpa->last != NULL) {
260 g_object_set (G_OBJECT (item), "secondary-color",
261 g_strstrip ((gchar *)wpa->last->content), NULL);
262 SET_FLAG(CC_BACKGROUND_ITEM_HAS_SCOLOR);
263 }
264 } else if (!strcmp ((gchar *)wpa->name, "source_url")) {
265 if (wpa->last != NULL) {
266 g_object_set (G_OBJECT (item),
267 "source-url", g_strstrip ((gchar *)wpa->last->content),
268 "needs-download", FALSE,
269 NULL);
270 }
271 } else if (!strcmp ((gchar *)wpa->name, "text")) {
272 /* Do nothing here, libxml2 is being weird */
273 } else {
274 g_debug ("Unknown Tag in %s: %s", filename, wpa->name);
275 }
276 }
277
278 /* Check whether the target file exists */
279 {
280 const char *uri;
281
282 uri = cc_background_item_get_uri (item);
283 if (uri != NULL)
284 {
285 g_autoptr(GFile) file = NULL;
286
287 file = g_file_new_for_uri (uri);
288 if (g_file_query_exists (file, NULL) == FALSE)
289 {
290 g_clear_pointer (&cname, g_free);
291 g_clear_object (&item);
292 continue;
293 }
294 }
295 }
296
297 /* FIXME, this is a broken way of doing,
298 * need to use proper code here */
299 uri = g_filename_to_uri (filename, NULL, NULL);
300 id = g_strdup_printf ("%s#%s", uri, cname);
301
302 /* Make sure we don't already have this one and that filename exists */
303 if (g_hash_table_lookup (xml->wp_hash, id) != NULL) {
304 continue;
305 }
306
307 g_object_set (G_OBJECT (item), "flags", flags, NULL);
308 g_hash_table_insert (xml->wp_hash,
309 g_strdup (id),
310 g_object_ref (item));
311 if (in_thread)
312 emit_added_in_idle (xml, g_object_ref (G_OBJECT (item)));
313 else
314 g_signal_emit (G_OBJECT (xml), signals[ADDED], 0, item);
315 retval = TRUE;
316 }
317 }
318 xmlFreeDoc (wplist);
319
320 return retval;
321 }
322
323 static void
gnome_wp_file_changed(GFileMonitor * monitor,GFile * file,GFile * other_file,GFileMonitorEvent event_type,CcBackgroundXml * data)324 gnome_wp_file_changed (GFileMonitor *monitor,
325 GFile *file,
326 GFile *other_file,
327 GFileMonitorEvent event_type,
328 CcBackgroundXml *data)
329 {
330 g_autofree gchar *filename = NULL;
331
332 switch (event_type) {
333 case G_FILE_MONITOR_EVENT_CHANGED:
334 case G_FILE_MONITOR_EVENT_CREATED:
335 filename = g_file_get_path (file);
336 cc_background_xml_load_xml_internal (data, filename, FALSE);
337 break;
338 default:
339 break;
340 }
341 }
342
343 static void
cc_background_xml_add_monitor(GFile * directory,CcBackgroundXml * data)344 cc_background_xml_add_monitor (GFile *directory,
345 CcBackgroundXml *data)
346 {
347 GFileMonitor *monitor;
348 g_autoptr(GError) error = NULL;
349
350 monitor = g_file_monitor_directory (directory,
351 G_FILE_MONITOR_NONE,
352 NULL,
353 &error);
354 if (error != NULL) {
355 g_autofree gchar *path = NULL;
356
357 path = g_file_get_parse_name (directory);
358 g_warning ("Unable to monitor directory %s: %s",
359 path, error->message);
360 return;
361 }
362
363 g_signal_connect (monitor, "changed",
364 G_CALLBACK (gnome_wp_file_changed),
365 data);
366
367 data->monitors = g_slist_prepend (data->monitors, monitor);
368 }
369
370 static void
cc_background_xml_load_from_dir(const gchar * path,CcBackgroundXml * data,gboolean in_thread)371 cc_background_xml_load_from_dir (const gchar *path,
372 CcBackgroundXml *data,
373 gboolean in_thread)
374 {
375 g_autoptr(GFile) directory = NULL;
376 g_autoptr(GFileEnumerator) enumerator = NULL;
377 g_autoptr(GError) error = NULL;
378
379 if (!g_file_test (path, G_FILE_TEST_IS_DIR)) {
380 return;
381 }
382
383 directory = g_file_new_for_path (path);
384 enumerator = g_file_enumerate_children (directory,
385 G_FILE_ATTRIBUTE_STANDARD_NAME,
386 G_FILE_QUERY_INFO_NONE,
387 NULL,
388 &error);
389 if (error != NULL) {
390 g_warning ("Unable to check directory %s: %s", path, error->message);
391 return;
392 }
393
394 while (TRUE) {
395 g_autoptr(GFileInfo) info = NULL;
396 const gchar *filename;
397 g_autofree gchar *fullpath = NULL;
398
399 info = g_file_enumerator_next_file (enumerator, NULL, NULL);
400 if (info == NULL) {
401 g_file_enumerator_close (enumerator, NULL, NULL);
402 cc_background_xml_add_monitor (directory, data);
403 return;
404 }
405
406 filename = g_file_info_get_name (info);
407 fullpath = g_build_filename (path, filename, NULL);
408
409 cc_background_xml_load_xml_internal (data, fullpath, in_thread);
410 }
411 }
412
413 static void
cc_background_xml_load_list(CcBackgroundXml * data,gboolean in_thread)414 cc_background_xml_load_list (CcBackgroundXml *data,
415 gboolean in_thread)
416 {
417 const char * const *system_data_dirs;
418 g_autofree gchar *datadir = NULL;
419 gint i;
420
421 datadir = g_build_filename (g_get_user_data_dir (),
422 "gnome-background-properties",
423 NULL);
424 cc_background_xml_load_from_dir (datadir, data, in_thread);
425
426 system_data_dirs = g_get_system_data_dirs ();
427 for (i = 0; system_data_dirs[i]; i++) {
428 g_autofree gchar *sdatadir = NULL;
429 sdatadir = g_build_filename (system_data_dirs[i],
430 "gnome-background-properties",
431 NULL);
432 cc_background_xml_load_from_dir (sdatadir, data, in_thread);
433 }
434 }
435
436 gboolean
cc_background_xml_load_list_finish(CcBackgroundXml * xml,GAsyncResult * result,GError ** error)437 cc_background_xml_load_list_finish (CcBackgroundXml *xml,
438 GAsyncResult *result,
439 GError **error)
440 {
441 g_return_val_if_fail (g_task_is_valid (result, xml), FALSE);
442 g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
443 return g_task_propagate_boolean (G_TASK (result), error);
444 }
445
446 static void
load_list_thread(GTask * task,gpointer source_object,gpointer task_data,GCancellable * cancellable)447 load_list_thread (GTask *task,
448 gpointer source_object,
449 gpointer task_data,
450 GCancellable *cancellable)
451 {
452 CcBackgroundXml *xml = CC_BACKGROUND_XML (source_object);
453 cc_background_xml_load_list (xml, TRUE);
454 g_task_return_boolean (task, TRUE);
455 }
456
457 void
cc_background_xml_load_list_async(CcBackgroundXml * xml,GCancellable * cancellable,GAsyncReadyCallback callback,gpointer user_data)458 cc_background_xml_load_list_async (CcBackgroundXml *xml,
459 GCancellable *cancellable,
460 GAsyncReadyCallback callback,
461 gpointer user_data)
462 {
463 g_autoptr(GTask) task = NULL;
464
465 g_return_if_fail (CC_IS_BACKGROUND_XML (xml));
466
467 task = g_task_new (xml, cancellable, callback, user_data);
468 g_task_run_in_thread (task, load_list_thread);
469 }
470
471 gboolean
cc_background_xml_load_xml(CcBackgroundXml * xml,const gchar * filename)472 cc_background_xml_load_xml (CcBackgroundXml *xml,
473 const gchar *filename)
474 {
475 g_return_val_if_fail (CC_IS_BACKGROUND_XML (xml), FALSE);
476
477 if (g_file_test (filename, G_FILE_TEST_IS_REGULAR) == FALSE)
478 return FALSE;
479
480 return cc_background_xml_load_xml_internal (xml, filename, FALSE);
481 }
482
483 static void
single_xml_added(CcBackgroundXml * xml,CcBackgroundItem * item,CcBackgroundItem ** ret)484 single_xml_added (CcBackgroundXml *xml,
485 CcBackgroundItem *item,
486 CcBackgroundItem **ret)
487 {
488 g_assert (*ret == NULL);
489 *ret = g_object_ref (item);
490 }
491
492 CcBackgroundItem *
cc_background_xml_get_item(const char * filename)493 cc_background_xml_get_item (const char *filename)
494 {
495 g_autoptr(CcBackgroundXml) xml = NULL;
496 CcBackgroundItem *item = NULL;
497
498 if (g_file_test (filename, G_FILE_TEST_IS_REGULAR) == FALSE)
499 return NULL;
500
501 xml = cc_background_xml_new ();
502 g_signal_connect (G_OBJECT (xml), "added",
503 G_CALLBACK (single_xml_added), &item);
504 if (cc_background_xml_load_xml (xml, filename) == FALSE)
505 return NULL;
506
507 return item;
508 }
509
510 static const char *
enum_to_str(GType type,int v)511 enum_to_str (GType type,
512 int v)
513 {
514 GEnumClass *eclass;
515 GEnumValue *value;
516
517 eclass = G_ENUM_CLASS (g_type_class_peek (type));
518 value = g_enum_get_value (eclass, v);
519
520 g_assert (value);
521
522 return value->value_nick;
523 }
524
525 void
cc_background_xml_save(CcBackgroundItem * item,const char * filename)526 cc_background_xml_save (CcBackgroundItem *item,
527 const char *filename)
528 {
529 xmlDoc *wp;
530 xmlNode *root, *wallpaper;
531 xmlNode *xml_item G_GNUC_UNUSED;
532 const char * none = "(none)";
533 const char *placement_str, *shading_str;
534 g_autofree gchar *name = NULL;
535 g_autofree gchar *pcolor = NULL;
536 g_autofree gchar *scolor = NULL;
537 g_autofree gchar *uri = NULL;
538 g_autofree gchar *source_url = NULL;
539 CcBackgroundItemFlags flags;
540 GDesktopBackgroundStyle placement;
541 GDesktopBackgroundShading shading;
542
543 xmlKeepBlanksDefault (0);
544
545 wp = xmlNewDoc ((xmlChar *)"1.0");
546 xmlCreateIntSubset (wp, (xmlChar *)"wallpapers", NULL, (xmlChar *)"gnome-wp-list.dtd");
547 root = xmlNewNode (NULL, (xmlChar *)"wallpapers");
548 xmlDocSetRootElement (wp, root);
549
550 g_object_get (G_OBJECT (item),
551 "name", &name,
552 "uri", &uri,
553 "shading", &shading,
554 "placement", &placement,
555 "primary-color", &pcolor,
556 "secondary-color", &scolor,
557 "source-url", &source_url,
558 "flags", &flags,
559 NULL);
560
561 placement_str = enum_to_str (G_DESKTOP_TYPE_DESKTOP_BACKGROUND_STYLE, placement);
562 shading_str = enum_to_str (G_DESKTOP_TYPE_DESKTOP_BACKGROUND_SHADING, shading);
563
564 wallpaper = xmlNewChild (root, NULL, (xmlChar *)"wallpaper", NULL);
565 xml_item = xmlNewTextChild (wallpaper, NULL, (xmlChar *)"name", (xmlChar *)name);
566 if (flags & CC_BACKGROUND_ITEM_HAS_URI &&
567 uri != NULL)
568 {
569 g_autoptr(GFile) file = NULL;
570 g_autofree gchar *fname = NULL;
571
572 file = g_file_new_for_commandline_arg (uri);
573 fname = g_file_get_path (file);
574 xml_item = xmlNewTextChild (wallpaper, NULL, (xmlChar *)"filename", (xmlChar *)fname);
575 }
576 else if (flags & CC_BACKGROUND_ITEM_HAS_URI)
577 {
578 xml_item = xmlNewTextChild (wallpaper, NULL, (xmlChar *)"filename", (xmlChar *)none);
579 }
580
581 if (flags & CC_BACKGROUND_ITEM_HAS_PLACEMENT)
582 xml_item = xmlNewTextChild (wallpaper, NULL, (xmlChar *)"options", (xmlChar *)placement_str);
583 if (flags & CC_BACKGROUND_ITEM_HAS_SHADING)
584 xml_item = xmlNewTextChild (wallpaper, NULL, (xmlChar *)"shade_type", (xmlChar *)shading_str);
585 if (flags & CC_BACKGROUND_ITEM_HAS_PCOLOR)
586 xml_item = xmlNewTextChild (wallpaper, NULL, (xmlChar *)"pcolor", (xmlChar *)pcolor);
587 if (flags & CC_BACKGROUND_ITEM_HAS_SCOLOR)
588 xml_item = xmlNewTextChild (wallpaper, NULL, (xmlChar *)"scolor", (xmlChar *)scolor);
589 if (source_url != NULL)
590 xml_item = xmlNewTextChild (wallpaper, NULL, (xmlChar *)"source_url", (xmlChar *)source_url);
591
592 xmlSaveFormatFile (filename, wp, 1);
593 xmlFreeDoc (wp);
594 }
595
596 static void
cc_background_xml_finalize(GObject * object)597 cc_background_xml_finalize (GObject *object)
598 {
599 CcBackgroundXml *xml;
600
601 g_return_if_fail (object != NULL);
602 g_return_if_fail (CC_IS_BACKGROUND_XML (object));
603
604 xml = CC_BACKGROUND_XML (object);
605
606 g_slist_free_full (xml->monitors, g_object_unref);
607
608 g_clear_pointer (&xml->wp_hash, g_hash_table_destroy);
609 if (xml->item_added_id != 0) {
610 g_source_remove (xml->item_added_id);
611 xml->item_added_id = 0;
612 }
613 g_clear_pointer (&xml->item_added_queue, g_async_queue_unref);
614
615 G_OBJECT_CLASS (cc_background_xml_parent_class)->finalize (object);
616 }
617
618 static void
cc_background_xml_class_init(CcBackgroundXmlClass * klass)619 cc_background_xml_class_init (CcBackgroundXmlClass *klass)
620 {
621 GObjectClass *object_class = G_OBJECT_CLASS (klass);
622
623 object_class->finalize = cc_background_xml_finalize;
624
625 signals[ADDED] = g_signal_new ("added",
626 G_OBJECT_CLASS_TYPE (object_class),
627 G_SIGNAL_RUN_LAST,
628 0,
629 NULL, NULL,
630 g_cclosure_marshal_VOID__OBJECT,
631 G_TYPE_NONE, 1, CC_TYPE_BACKGROUND_ITEM);
632 }
633
634 static void
cc_background_xml_init(CcBackgroundXml * xml)635 cc_background_xml_init (CcBackgroundXml *xml)
636 {
637 xml->wp_hash = g_hash_table_new_full (g_str_hash,
638 g_str_equal,
639 (GDestroyNotify) g_free,
640 (GDestroyNotify) g_object_unref);
641 xml->item_added_queue = g_async_queue_new_full ((GDestroyNotify) g_object_unref);
642 }
643
644 CcBackgroundXml *
cc_background_xml_new(void)645 cc_background_xml_new (void)
646 {
647 return CC_BACKGROUND_XML (g_object_new (CC_TYPE_BACKGROUND_XML, NULL));
648 }
649