summaryrefslogtreecommitdiffstats
path: root/panels/background
diff options
context:
space:
mode:
Diffstat (limited to 'panels/background')
-rw-r--r--panels/background/background.gresource.xml9
-rw-r--r--panels/background/bg-colors-source.c219
-rw-r--r--panels/background/bg-colors-source.h38
-rw-r--r--panels/background/bg-pictures-source.c855
-rw-r--r--panels/background/bg-pictures-source.h46
-rw-r--r--panels/background/bg-recent-source.c470
-rw-r--r--panels/background/bg-recent-source.h39
-rw-r--r--panels/background/bg-source.c213
-rw-r--r--panels/background/bg-source.h46
-rw-r--r--panels/background/bg-wallpapers-source.c138
-rw-r--r--panels/background/bg-wallpapers-source.h34
-rw-r--r--panels/background/cc-background-chooser.c386
-rw-r--r--panels/background/cc-background-chooser.h32
-rw-r--r--panels/background/cc-background-chooser.ui90
-rw-r--r--panels/background/cc-background-grilo-miner.c315
-rw-r--r--panels/background/cc-background-grilo-miner.h31
-rw-r--r--panels/background/cc-background-item.c977
-rw-r--r--panels/background/cc-background-item.h83
-rw-r--r--panels/background/cc-background-panel.c314
-rw-r--r--panels/background/cc-background-panel.h30
-rw-r--r--panels/background/cc-background-panel.ui58
-rw-r--r--panels/background/cc-background-preview.c382
-rw-r--r--panels/background/cc-background-preview.h36
-rw-r--r--panels/background/cc-background-preview.ui152
-rw-r--r--panels/background/cc-background-xml.c648
-rw-r--r--panels/background/cc-background-xml.h46
-rw-r--r--panels/background/gnome-background-panel.desktop.in.in14
-rw-r--r--panels/background/meson.build115
-rw-r--r--panels/background/noise-texture-light.pngbin0 -> 69136 bytes
-rw-r--r--panels/background/preview.css40
-rw-r--r--panels/background/slideshow-emblem.svg104
-rw-r--r--panels/background/slideshow-symbolic.svg70
32 files changed, 6030 insertions, 0 deletions
diff --git a/panels/background/background.gresource.xml b/panels/background/background.gresource.xml
new file mode 100644
index 0000000..c715aad
--- /dev/null
+++ b/panels/background/background.gresource.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<gresources>
+ <gresource prefix="/org/gnome/control-center/background">
+ <file preprocess="xml-stripblanks">cc-background-chooser.ui</file>
+ <file preprocess="xml-stripblanks">cc-background-panel.ui</file>
+ <file preprocess="xml-stripblanks">cc-background-preview.ui</file>
+ <file>preview.css</file>
+ </gresource>
+</gresources>
diff --git a/panels/background/bg-colors-source.c b/panels/background/bg-colors-source.c
new file mode 100644
index 0000000..fbf116a
--- /dev/null
+++ b/panels/background/bg-colors-source.c
@@ -0,0 +1,219 @@
+/* bg-colors-source.c */
+/*
+ * Copyright (C) 2010 Intel, Inc
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * Author: Thomas Wood <thomas.wood@intel.com>
+ *
+ */
+
+#include <config.h>
+#include "bg-colors-source.h"
+
+#include "cc-background-item.h"
+
+#include <cairo-gobject.h>
+#include <glib/gi18n-lib.h>
+#include <gdesktop-enums.h>
+
+struct _BgColorsSource
+{
+ BgSource parent_instance;
+};
+
+G_DEFINE_TYPE (BgColorsSource, bg_colors_source, BG_TYPE_SOURCE)
+
+struct {
+ GDesktopBackgroundShading type;
+ int orientation;
+ const char *pcolor;
+} items[] = {
+ { G_DESKTOP_BACKGROUND_SHADING_SOLID, -1, "#000000" },
+ { G_DESKTOP_BACKGROUND_SHADING_SOLID, -1, "#db5d33" },
+ { G_DESKTOP_BACKGROUND_SHADING_SOLID, -1, "#008094" },
+ { G_DESKTOP_BACKGROUND_SHADING_SOLID, -1, "#5d479d" },
+ { G_DESKTOP_BACKGROUND_SHADING_SOLID, -1, "#ab2876" },
+ { G_DESKTOP_BACKGROUND_SHADING_SOLID, -1, "#fad166" },
+ { G_DESKTOP_BACKGROUND_SHADING_SOLID, -1, "#437740" },
+ { G_DESKTOP_BACKGROUND_SHADING_SOLID, -1, "#d272c4" },
+ { G_DESKTOP_BACKGROUND_SHADING_SOLID, -1, "#ed9116" },
+ { G_DESKTOP_BACKGROUND_SHADING_SOLID, -1, "#ff89a9" },
+ { G_DESKTOP_BACKGROUND_SHADING_SOLID, -1, "#7a8aa2" },
+ { G_DESKTOP_BACKGROUND_SHADING_SOLID, -1, "#888888" },
+ { G_DESKTOP_BACKGROUND_SHADING_SOLID, -1, "#475b52" },
+ { G_DESKTOP_BACKGROUND_SHADING_SOLID, -1, "#425265" },
+ { G_DESKTOP_BACKGROUND_SHADING_SOLID, -1, "#7a634b" },
+};
+
+static gchar *
+get_colors_path (void)
+{
+ return g_build_filename (g_get_user_config_dir (), "gnome-control-center", "backgrounds", "colors.ini", NULL);
+}
+
+static char *
+get_colors_dir (void)
+{
+ return g_build_filename (g_get_user_config_dir (), "gnome-control-center", "backgrounds", NULL);
+}
+
+static void
+bg_colors_source_add_color (BgColorsSource *self,
+ GListStore *store,
+ const char *color)
+{
+ CcBackgroundItemFlags flags;
+ g_autoptr(CcBackgroundItem) item = NULL;
+
+ item = cc_background_item_new (NULL);
+ flags = CC_BACKGROUND_ITEM_HAS_PCOLOR |
+ CC_BACKGROUND_ITEM_HAS_SCOLOR |
+ CC_BACKGROUND_ITEM_HAS_SHADING |
+ CC_BACKGROUND_ITEM_HAS_PLACEMENT |
+ CC_BACKGROUND_ITEM_HAS_URI;
+ /* It does have a URI, it's "none" */
+
+ g_object_set (G_OBJECT (item),
+ "uri", "file:///" DATADIR "/gnome-control-center/pixmaps/noise-texture-light.png",
+ "primary-color", color,
+ "secondary-color", color,
+ "shading", G_DESKTOP_BACKGROUND_SHADING_SOLID,
+ "placement", G_DESKTOP_BACKGROUND_STYLE_WALLPAPER,
+ "flags", flags,
+ NULL);
+ cc_background_item_load (item, NULL);
+
+ /* insert the item into the liststore */
+ g_list_store_append (store, item);
+}
+
+static void
+bg_colors_source_constructed (GObject *object)
+{
+ BgColorsSource *self = BG_COLORS_SOURCE (object);
+ guint i;
+ GListStore *store;
+ g_autoptr(GKeyFile) keyfile = NULL;
+ g_autofree gchar *path = NULL;
+
+ G_OBJECT_CLASS (bg_colors_source_parent_class)->constructed (object);
+
+ store = bg_source_get_liststore (BG_SOURCE (self));
+
+ for (i = 0; i < G_N_ELEMENTS (items); i++)
+ bg_colors_source_add_color (self, store, items[i].pcolor);
+
+ keyfile = g_key_file_new ();
+ path = get_colors_path ();
+ if (g_key_file_load_from_file (keyfile, path, G_KEY_FILE_NONE, NULL))
+ {
+ g_auto(GStrv) colors = NULL;
+
+ colors = g_key_file_get_string_list (keyfile, "Colors", "custom-colors", NULL, NULL);
+ for (i = 0; colors != NULL && colors[i] != NULL; i++)
+ bg_colors_source_add_color (self, store, colors[i]);
+ }
+}
+
+gboolean
+bg_colors_source_add (BgColorsSource *self,
+ GdkRGBA *rgba,
+ GtkTreeRowReference **ret_row_ref)
+{
+ GListStore *store;
+ g_autofree gchar *c = NULL;
+ g_auto(GStrv) colors = NULL;
+ gsize len;
+ g_autoptr(GKeyFile) keyfile = NULL;
+ g_autoptr(GError) error = NULL;
+ g_autofree gchar *dir = NULL;
+ g_autofree gchar *path = NULL;
+
+ c = g_strdup_printf ("#%02x%02x%02x",
+ (int)(255*rgba->red),
+ (int)(255*rgba->green),
+ (int)(255*rgba->blue));
+
+ store = bg_source_get_liststore (BG_SOURCE (self));
+
+ bg_colors_source_add_color (self, store, c);
+
+ /* Save to the keyfile */
+ dir = get_colors_dir ();
+ g_mkdir_with_parents (dir, 0700);
+
+ path = get_colors_path ();
+ colors = NULL;
+ len = 0;
+
+ keyfile = g_key_file_new ();
+ if (g_key_file_load_from_file (keyfile, path, G_KEY_FILE_NONE, NULL))
+ colors = g_key_file_get_string_list (keyfile, "Colors", "custom-colors", &len, NULL);
+
+ if (len == 0 && colors != NULL)
+ g_clear_pointer (&colors, g_strfreev);
+
+ if (colors == NULL)
+ {
+ colors = g_new0 (char *, 2);
+ colors[0] = g_steal_pointer (&c);
+ len = 1;
+ }
+ else
+ {
+ char **new_colors;
+ guint i;
+
+ new_colors = g_new0 (char *, len + 2);
+ for (i = 0; colors[i] != NULL; i++)
+ {
+ new_colors[i] = colors[i];
+ colors[i] = NULL;
+ }
+
+ new_colors[len] = g_steal_pointer (&c);
+ len++;
+
+ g_strfreev (colors);
+ colors = new_colors;
+ }
+
+ g_key_file_set_string_list (keyfile, "Colors", "custom-colors", (const gchar * const*) colors, len);
+
+ if (!g_key_file_save_to_file (keyfile, path, &error))
+ g_warning ("Could not save custom color: %s", error->message);
+
+ return TRUE;
+}
+
+static void
+bg_colors_source_init (BgColorsSource *self)
+{
+}
+
+static void
+bg_colors_source_class_init (BgColorsSourceClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->constructed = bg_colors_source_constructed;
+}
+
+BgColorsSource *
+bg_colors_source_new (GtkWidget *widget)
+{
+ return g_object_new (BG_TYPE_COLORS_SOURCE, "widget", widget, NULL);
+}
+
diff --git a/panels/background/bg-colors-source.h b/panels/background/bg-colors-source.h
new file mode 100644
index 0000000..8e2575d
--- /dev/null
+++ b/panels/background/bg-colors-source.h
@@ -0,0 +1,38 @@
+/* bg-colors-source.h */
+/*
+ * Copyright (C) 2010 Intel, Inc
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * Author: Thomas Wood <thomas.wood@intel.com>
+ *
+ */
+
+#pragma once
+
+#include <gtk/gtk.h>
+#include "bg-source.h"
+
+G_BEGIN_DECLS
+
+#define BG_TYPE_COLORS_SOURCE (bg_colors_source_get_type ())
+G_DECLARE_FINAL_TYPE (BgColorsSource, bg_colors_source, BG, COLORS_SOURCE, BgSource)
+
+BgColorsSource *bg_colors_source_new (GtkWidget *widget);
+
+gboolean bg_colors_source_add (BgColorsSource *self,
+ GdkRGBA *rgba,
+ GtkTreeRowReference **ret_row_ref);
+
+G_END_DECLS
diff --git a/panels/background/bg-pictures-source.c b/panels/background/bg-pictures-source.c
new file mode 100644
index 0000000..3a3027b
--- /dev/null
+++ b/panels/background/bg-pictures-source.c
@@ -0,0 +1,855 @@
+/* bg-pictures-source.c */
+/*
+ * Copyright (C) 2010 Intel, Inc
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * Author: Thomas Wood <thomas.wood@intel.com>
+ *
+ */
+
+#include <config.h>
+
+#include "bg-pictures-source.h"
+
+#include "cc-background-grilo-miner.h"
+#include "cc-background-item.h"
+
+#include <string.h>
+#include <cairo-gobject.h>
+#include <gio/gio.h>
+#include <grilo.h>
+#include <libgnome-desktop/gnome-desktop-thumbnail.h>
+#include <gdesktop-enums.h>
+
+#define ATTRIBUTES G_FILE_ATTRIBUTE_STANDARD_NAME "," \
+ G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE "," \
+ G_FILE_ATTRIBUTE_TIME_MODIFIED
+
+struct _BgPicturesSource
+{
+ BgSource parent_instance;
+
+ GCancellable *cancellable;
+
+ CcBackgroundGriloMiner *grl_miner;
+
+ GFileMonitor *picture_dir_monitor;
+ GFileMonitor *cache_dir_monitor;
+
+ GHashTable *known_items;
+};
+
+G_DEFINE_TYPE (BgPicturesSource, bg_pictures_source, BG_TYPE_SOURCE)
+
+const char * const content_types[] = {
+ "image/png",
+ "image/jp2",
+ "image/jpeg",
+ "image/bmp",
+ "image/svg+xml",
+ "image/x-portable-anymap",
+ NULL
+};
+
+const char * const screenshot_types[] = {
+ "image/png",
+ NULL
+};
+
+static char *bg_pictures_source_get_unique_filename (const char *uri);
+
+static void picture_opened_for_read (GObject *source_object, GAsyncResult *res, gpointer user_data);
+
+static void
+bg_pictures_source_dispose (GObject *object)
+{
+ BgPicturesSource *source = BG_PICTURES_SOURCE (object);
+
+ if (source->cancellable)
+ {
+ g_cancellable_cancel (source->cancellable);
+ g_clear_object (&source->cancellable);
+ }
+
+ g_clear_object (&source->grl_miner);
+
+ G_OBJECT_CLASS (bg_pictures_source_parent_class)->dispose (object);
+}
+
+static void
+bg_pictures_source_finalize (GObject *object)
+{
+ BgPicturesSource *bg_source = BG_PICTURES_SOURCE (object);
+
+ g_clear_pointer (&bg_source->known_items, g_hash_table_destroy);
+
+ g_clear_object (&bg_source->picture_dir_monitor);
+ g_clear_object (&bg_source->cache_dir_monitor);
+
+ G_OBJECT_CLASS (bg_pictures_source_parent_class)->finalize (object);
+}
+
+static void
+bg_pictures_source_class_init (BgPicturesSourceClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->dispose = bg_pictures_source_dispose;
+ object_class->finalize = bg_pictures_source_finalize;
+}
+
+static void
+remove_placeholder (BgPicturesSource *bg_source,
+ CcBackgroundItem *item)
+{
+ GListStore *store;
+ guint i;
+
+ store = bg_source_get_liststore (BG_SOURCE (bg_source));
+
+ for (i = 0; i < g_list_model_get_n_items (G_LIST_MODEL (store)); i++)
+ {
+ g_autoptr(CcBackgroundItem) item_n = NULL;
+
+ item_n = g_list_model_get_item (G_LIST_MODEL (store), i);
+
+ if (item_n == item)
+ {
+ g_list_store_remove (store, i);
+ break;
+ }
+ }
+}
+
+static gboolean
+picture_needs_rotation (GdkPixbuf *pixbuf)
+{
+ const gchar *str;
+
+ str = gdk_pixbuf_get_option (pixbuf, "orientation");
+ if (str == NULL)
+ return FALSE;
+
+ if (*str == '5' || *str == '6' || *str == '7' || *str == '8')
+ return TRUE;
+
+ return FALSE;
+}
+
+static GdkPixbuf *
+swap_rotated_pixbuf (GdkPixbuf *pixbuf)
+{
+ GdkPixbuf *tmp_pixbuf;
+
+ tmp_pixbuf = gdk_pixbuf_apply_embedded_orientation (pixbuf);
+ if (tmp_pixbuf == NULL)
+ return pixbuf;
+
+ g_object_unref (pixbuf);
+ return tmp_pixbuf;
+}
+
+static int
+sort_func (gconstpointer a,
+ gconstpointer b,
+ gpointer user_data)
+{
+ CcBackgroundItem *item_a;
+ CcBackgroundItem *item_b;
+ guint64 modified_a;
+ guint64 modified_b;
+ int retval;
+
+ item_a = (CcBackgroundItem *) a;
+ item_b = (CcBackgroundItem *) b;
+ modified_a = cc_background_item_get_modified (item_a);
+ modified_b = cc_background_item_get_modified (item_b);
+
+ retval = modified_b - modified_a;
+
+ return retval;
+}
+
+static void
+picture_scaled (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ BgPicturesSource *bg_source;
+ CcBackgroundItem *item;
+ g_autoptr(GError) error = NULL;
+ g_autoptr(GdkPixbuf) pixbuf = NULL;
+ const char *software;
+ const char *uri;
+ GListStore *store;
+ cairo_surface_t *surface = NULL;
+ int scale_factor;
+ gboolean rotation_applied;
+
+ item = g_object_get_data (source_object, "item");
+ pixbuf = gdk_pixbuf_new_from_stream_finish (res, &error);
+ if (pixbuf == NULL)
+ {
+ if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ {
+ g_warning ("Failed to load image: %s", error->message);
+ remove_placeholder (BG_PICTURES_SOURCE (user_data), item);
+ }
+
+ return;
+ }
+
+ /* since we were not cancelled, we can now cast user_data
+ * back to BgPicturesSource.
+ */
+ bg_source = BG_PICTURES_SOURCE (user_data);
+ store = bg_source_get_liststore (BG_SOURCE (bg_source));
+ uri = cc_background_item_get_uri (item);
+ if (uri == NULL)
+ uri = cc_background_item_get_source_url (item);
+
+ /* Ignore screenshots */
+ software = gdk_pixbuf_get_option (pixbuf, "tEXt::Software");
+ if (software != NULL &&
+ g_str_equal (software, "gnome-screenshot"))
+ {
+ g_debug ("Ignored URL '%s' as it's a screenshot from gnome-screenshot", uri);
+ remove_placeholder (BG_PICTURES_SOURCE (user_data), item);
+ return;
+ }
+
+ /* Process embedded orientation */
+ rotation_applied = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (item), "rotation-applied"));
+
+ if (!rotation_applied && picture_needs_rotation (pixbuf))
+ {
+ /* the width and height of pixbuf we requested are wrong for EXIF
+ * orientations 5, 6, 7 and 8. the file has to be reloaded. */
+ g_autoptr(GFile) file = NULL;
+
+ file = g_file_new_for_uri (uri);
+ g_object_set_data (G_OBJECT (item), "needs-rotation", GINT_TO_POINTER (TRUE));
+ g_object_set_data_full (G_OBJECT (file), "item", g_object_ref (item), g_object_unref);
+ g_file_read_async (G_FILE (file), G_PRIORITY_DEFAULT,
+ bg_source->cancellable,
+ picture_opened_for_read, bg_source);
+ return;
+ }
+
+ pixbuf = swap_rotated_pixbuf (pixbuf);
+
+ scale_factor = bg_source_get_scale_factor (BG_SOURCE (bg_source));
+ surface = gdk_cairo_surface_create_from_pixbuf (pixbuf, scale_factor, NULL);
+ cc_background_item_load (item, NULL);
+
+ /* insert the item into the liststore */
+ g_list_store_insert_sorted (store, item, sort_func, bg_source);
+
+ g_hash_table_insert (bg_source->known_items,
+ bg_pictures_source_get_unique_filename (uri),
+ GINT_TO_POINTER (TRUE));
+
+ g_clear_pointer (&surface, cairo_surface_destroy);
+}
+
+static void
+picture_opened_for_read (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ BgPicturesSource *bg_source;
+ CcBackgroundItem *item;
+ g_autoptr(GFileInputStream) stream = NULL;
+ g_autoptr(GError) error = NULL;
+ gint thumbnail_height;
+ gint thumbnail_width;
+ gboolean needs_rotation;
+
+ item = g_object_get_data (source_object, "item");
+ stream = g_file_read_finish (G_FILE (source_object), res, &error);
+ if (stream == NULL)
+ {
+ if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ {
+ g_autofree gchar *filename = g_file_get_path (G_FILE (source_object));
+ g_warning ("Failed to load picture '%s': %s", filename, error->message);
+ remove_placeholder (BG_PICTURES_SOURCE (user_data), item);
+ }
+
+ return;
+ }
+
+ /* since we were not cancelled, we can now cast user_data
+ * back to BgPicturesSource.
+ */
+ bg_source = BG_PICTURES_SOURCE (user_data);
+
+ needs_rotation = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (item), "needs-rotation"));
+ if (needs_rotation)
+ {
+ /* swap width and height for EXIF orientations that need it */
+ thumbnail_width = bg_source_get_thumbnail_height (BG_SOURCE (bg_source));
+ thumbnail_height = bg_source_get_thumbnail_width (BG_SOURCE (bg_source));
+ g_object_set_data (G_OBJECT (item), "rotation-applied", GINT_TO_POINTER (TRUE));
+ }
+ else
+ {
+ thumbnail_width = bg_source_get_thumbnail_width (BG_SOURCE (bg_source));
+ thumbnail_height = bg_source_get_thumbnail_height (BG_SOURCE (bg_source));
+ }
+
+ g_object_set_data_full (G_OBJECT (stream), "item", g_object_ref (item), g_object_unref);
+ gdk_pixbuf_new_from_stream_at_scale_async (G_INPUT_STREAM (stream),
+ thumbnail_width, thumbnail_height,
+ TRUE,
+ bg_source->cancellable,
+ picture_scaled, bg_source);
+}
+
+static void
+picture_copied_for_read (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ BgPicturesSource *bg_source;
+ CcBackgroundItem *item;
+ g_autoptr(GError) error = NULL;
+ GFile *thumbnail_file = G_FILE (source_object);
+ GFile *native_file;
+
+ if (!g_file_copy_finish (thumbnail_file, res, &error))
+ {
+ if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ return;
+ else if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_EXISTS))
+ {
+ g_autofree gchar *uri = NULL;
+
+ uri = g_file_get_uri (thumbnail_file);
+ g_warning ("Failed to download '%s': %s", uri, error->message);
+ return;
+ }
+ }
+
+ bg_source = BG_PICTURES_SOURCE (user_data);
+
+ native_file = g_object_get_data (G_OBJECT (thumbnail_file), "native-file");
+ item = g_object_get_data (G_OBJECT (thumbnail_file), "item");
+ g_object_set_data_full (G_OBJECT (native_file), "item", g_object_ref (item), g_object_unref);
+ g_file_read_async (native_file,
+ G_PRIORITY_DEFAULT,
+ bg_source->cancellable,
+ picture_opened_for_read,
+ bg_source);
+}
+
+static gboolean
+in_content_types (const char *content_type)
+{
+ guint i;
+ for (i = 0; content_types[i]; i++)
+ if (g_str_equal (content_types[i], content_type))
+ return TRUE;
+ return FALSE;
+}
+
+static GFile *
+bg_pictures_source_get_cache_file (void)
+{
+ g_autofree gchar *path = NULL;
+ GFile *file;
+
+ path = bg_pictures_source_get_cache_path ();
+ file = g_file_new_for_path (path);
+
+ return file;
+}
+
+static gboolean
+add_single_file (BgPicturesSource *bg_source,
+ GFile *file,
+ const gchar *content_type,
+ guint64 mtime)
+{
+ g_autoptr(CcBackgroundItem) item = NULL;
+ CcBackgroundItemFlags flags = 0;
+ g_autofree gchar *source_uri = NULL;
+ g_autofree gchar *uri = NULL;
+ gboolean needs_download;
+ gboolean retval = FALSE;
+ const gchar *pictures_path;
+ g_autoptr(GFile) pictures_dir = NULL;
+ g_autoptr(GFile) cache_dir = NULL;
+ GrlMedia *media;
+
+ /* find png and jpeg files */
+ if (!content_type)
+ goto out;
+ if (!in_content_types (content_type))
+ goto out;
+
+ /* create a new CcBackgroundItem */
+ uri = g_file_get_uri (file);
+
+ pictures_path = g_get_user_special_dir (G_USER_DIRECTORY_PICTURES);
+ if (pictures_path == NULL)
+ pictures_path = g_get_home_dir ();
+ pictures_dir = g_file_new_for_path (pictures_path);
+ cache_dir = bg_pictures_source_get_cache_file ();
+ needs_download = !g_file_has_parent (file, pictures_dir) &&
+ !g_file_has_parent (file, cache_dir);
+
+ if (!needs_download)
+ {
+ source_uri = g_strdup (uri);
+ flags |= CC_BACKGROUND_ITEM_HAS_URI;
+ }
+ else
+ {
+ source_uri = g_steal_pointer (&uri);
+ }
+
+ item = cc_background_item_new (uri);
+ flags |= CC_BACKGROUND_ITEM_HAS_SHADING | CC_BACKGROUND_ITEM_HAS_PLACEMENT;
+ g_object_set (G_OBJECT (item),
+ "flags", flags,
+ "shading", G_DESKTOP_BACKGROUND_SHADING_SOLID,
+ "placement", G_DESKTOP_BACKGROUND_STYLE_ZOOM,
+ "modified", mtime,
+ "needs-download", needs_download,
+ "source-url", source_uri,
+ NULL);
+
+ media = g_object_get_data (G_OBJECT (file), "grl-media");
+ if (media == NULL)
+ {
+ g_object_set_data_full (G_OBJECT (file), "item", g_object_ref (item), g_object_unref);
+ g_file_read_async (file, G_PRIORITY_DEFAULT,
+ bg_source->cancellable,
+ picture_opened_for_read, bg_source);
+ }
+ else
+ {
+ g_autoptr(GFile) native_file = NULL;
+ g_autoptr(GFile) thumbnail_file = NULL;
+ g_autofree gchar *native_dir = NULL;
+ g_autofree gchar *native_path = NULL;
+ const gchar *title;
+ const gchar *thumbnail_uri;
+
+ title = grl_media_get_title (media);
+ g_object_set (G_OBJECT (item), "name", title, NULL);
+
+ thumbnail_uri = grl_media_get_thumbnail (media);
+ thumbnail_file = g_file_new_for_uri (thumbnail_uri);
+
+ native_path = gnome_desktop_thumbnail_path_for_uri (source_uri, GNOME_DESKTOP_THUMBNAIL_SIZE_LARGE);
+ native_file = g_file_new_for_path (native_path);
+
+ native_dir = g_path_get_dirname (native_path);
+ g_mkdir_with_parents (native_dir, USER_DIR_MODE);
+
+ g_object_set_data_full (G_OBJECT (thumbnail_file), "item", g_object_ref (item), g_object_unref);
+ g_object_set_data_full (G_OBJECT (thumbnail_file),
+ "native-file",
+ g_object_ref (native_file),
+ g_object_unref);
+ g_file_copy_async (thumbnail_file,
+ native_file,
+ G_FILE_COPY_ALL_METADATA,
+ G_PRIORITY_DEFAULT,
+ bg_source->cancellable,
+ NULL,
+ NULL,
+ picture_copied_for_read,
+ bg_source);
+ }
+
+ retval = TRUE;
+
+ out:
+ return retval;
+}
+
+static gboolean
+add_single_file_from_info (BgPicturesSource *bg_source,
+ GFile *file,
+ GFileInfo *info)
+{
+ const gchar *content_type;
+ guint64 mtime;
+
+ content_type = g_file_info_get_content_type (info);
+ mtime = g_file_info_get_attribute_uint64 (info, G_FILE_ATTRIBUTE_TIME_MODIFIED);
+ return add_single_file (bg_source, file, content_type, mtime);
+}
+
+static gboolean
+add_single_file_from_media (BgPicturesSource *bg_source,
+ GFile *file,
+ GrlMedia *media)
+{
+ GDateTime *mtime;
+ const gchar *content_type;
+ gint64 mtime_unix;
+
+ content_type = grl_media_get_mime (media);
+
+ /* only GRL_METADATA_KEY_CREATION_DATE is implemented in the Flickr
+ * plugin, GRL_METADATA_KEY_MODIFICATION_DATE is not
+ */
+ mtime = grl_media_get_creation_date (media);
+ if (!mtime)
+ mtime = grl_media_get_modification_date (media);
+ if (mtime)
+ mtime_unix = g_date_time_to_unix (mtime);
+ else
+ mtime_unix = g_get_real_time () / G_USEC_PER_SEC;
+
+ return add_single_file (bg_source, file, content_type, (guint64) mtime_unix);
+}
+
+gboolean
+bg_pictures_source_add (BgPicturesSource *bg_source,
+ const char *uri,
+ GtkTreeRowReference **ret_row_ref)
+{
+ g_autoptr(GFile) file = NULL;
+ GFileInfo *info;
+ gboolean retval;
+
+ file = g_file_new_for_uri (uri);
+ info = g_file_query_info (file, ATTRIBUTES, G_FILE_QUERY_INFO_NONE, NULL, NULL);
+ if (info == NULL)
+ return FALSE;
+
+ retval = add_single_file_from_info (bg_source, file, info);
+
+ return retval;
+}
+
+gboolean
+bg_pictures_source_remove (BgPicturesSource *bg_source,
+ const char *uri)
+{
+ GListStore *store;
+ gboolean retval;
+ guint i;
+
+ retval = FALSE;
+ store = bg_source_get_liststore (BG_SOURCE (bg_source));
+
+ for (i = 0; i < g_list_model_get_n_items (G_LIST_MODEL (store)); i++)
+ {
+ g_autoptr(CcBackgroundItem) tmp_item = NULL;
+ const char *tmp_uri;
+
+ tmp_item = g_list_model_get_item (G_LIST_MODEL (store), i);
+ tmp_uri = cc_background_item_get_uri (tmp_item);
+ if (g_str_equal (tmp_uri, uri))
+ {
+ char *uuid;
+ uuid = bg_pictures_source_get_unique_filename (uri);
+ g_hash_table_insert (bg_source->known_items,
+ uuid, NULL);
+
+ g_list_store_remove (store, i);
+ retval = TRUE;
+ break;
+ }
+ }
+ return retval;
+}
+
+static int
+file_sort_func (gconstpointer a,
+ gconstpointer b)
+{
+ GFileInfo *file_a = G_FILE_INFO (a);
+ GFileInfo *file_b = G_FILE_INFO (b);
+ guint64 modified_a, modified_b;
+
+ modified_a = g_file_info_get_attribute_uint64 (file_a, G_FILE_ATTRIBUTE_TIME_MODIFIED);
+
+ modified_b = g_file_info_get_attribute_uint64 (file_b, G_FILE_ATTRIBUTE_TIME_MODIFIED);
+
+ return modified_b - modified_a;
+}
+
+static void
+file_info_async_ready (GObject *source,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ BgPicturesSource *bg_source;
+ GList *files, *l;
+ g_autoptr(GError) err = NULL;
+ GFile *parent;
+
+ files = g_file_enumerator_next_files_finish (G_FILE_ENUMERATOR (source),
+ res,
+ &err);
+ if (err)
+ {
+ if (!g_error_matches (err, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ g_warning ("Could not get pictures file information: %s", err->message);
+
+ g_list_foreach (files, (GFunc) g_object_unref, NULL);
+ g_list_free (files);
+ return;
+ }
+
+ bg_source = BG_PICTURES_SOURCE (user_data);
+
+ parent = g_file_enumerator_get_container (G_FILE_ENUMERATOR (source));
+
+ files = g_list_sort (files, file_sort_func);
+
+ /* iterate over the available files */
+ for (l = files; l; l = g_list_next (l))
+ {
+ GFileInfo *info = l->data;
+ g_autoptr(GFile) file = NULL;
+
+ file = g_file_get_child (parent, g_file_info_get_name (info));
+
+ add_single_file_from_info (bg_source, file, info);
+ }
+
+ g_list_foreach (files, (GFunc) g_object_unref, NULL);
+ g_list_free (files);
+}
+
+static void
+dir_enum_async_ready (GObject *s,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ BgPicturesSource *source = (BgPicturesSource *) user_data;
+ g_autoptr(GFileEnumerator) enumerator = NULL;
+ g_autoptr(GError) err = NULL;
+
+ enumerator = g_file_enumerate_children_finish (G_FILE (s), res, &err);
+
+ if (err)
+ {
+ if (!g_error_matches (err, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ g_warning ("Could not fill pictures source: %s", err->message);
+ return;
+ }
+
+ /* get the files */
+ g_file_enumerator_next_files_async (enumerator,
+ G_MAXINT,
+ G_PRIORITY_LOW,
+ source->cancellable,
+ file_info_async_ready,
+ user_data);
+}
+
+char *
+bg_pictures_source_get_cache_path (void)
+{
+ return g_build_filename (g_get_user_cache_dir (),
+ "gnome-control-center",
+ "backgrounds",
+ NULL);
+}
+
+static char *
+bg_pictures_source_get_unique_filename (const char *uri)
+{
+ g_autoptr(GChecksum) csum = NULL;
+ char *ret;
+
+ csum = g_checksum_new (G_CHECKSUM_SHA256);
+ g_checksum_update (csum, (guchar *) uri, -1);
+ ret = g_strdup (g_checksum_get_string (csum));
+
+ return ret;
+}
+
+char *
+bg_pictures_source_get_unique_path (const char *uri)
+{
+ g_autoptr(GFile) parent = NULL;
+ g_autoptr(GFile) file = NULL;
+ g_autofree gchar *cache_path = NULL;
+ g_autofree gchar *filename = NULL;
+
+ cache_path = bg_pictures_source_get_cache_path ();
+ parent = g_file_new_for_path (cache_path);
+
+ filename = bg_pictures_source_get_unique_filename (uri);
+ file = g_file_get_child (parent, filename);
+
+ return g_file_get_path (file);
+}
+
+gboolean
+bg_pictures_source_is_known (BgPicturesSource *bg_source,
+ const char *uri)
+{
+ g_autofree gchar *uuid = NULL;
+
+ uuid = bg_pictures_source_get_unique_filename (uri);
+
+ return GPOINTER_TO_INT (g_hash_table_lookup (bg_source->known_items, uuid));
+}
+
+static void
+file_info_ready (GObject *object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ GFileInfo *info;
+ GError *error = NULL;
+ GFile *file = G_FILE (object);
+
+ info = g_file_query_info_finish (file, res, &error);
+
+ if (!info)
+ {
+ if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ g_warning ("Problem looking up file info: %s", error->message);
+ g_clear_error (&error);
+ return;
+ }
+
+ add_single_file_from_info (BG_PICTURES_SOURCE (user_data), file, info);
+}
+
+static void
+file_added (GFile *file,
+ BgPicturesSource *self)
+{
+ g_autofree gchar *uri = NULL;
+ uri = g_file_get_uri (file);
+
+ if (!bg_pictures_source_is_known (self, uri))
+ {
+ g_file_query_info_async (file,
+ ATTRIBUTES,
+ G_FILE_QUERY_INFO_NONE,
+ G_PRIORITY_LOW,
+ NULL,
+ file_info_ready,
+ self);
+ }
+}
+
+static void
+files_changed_cb (BgPicturesSource *self,
+ GFile *file,
+ GFile *other_file,
+ GFileMonitorEvent event_type)
+{
+ g_autofree gchar *uri = NULL;
+
+ switch (event_type)
+ {
+ case G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT:
+ file_added (file, self);
+ break;
+
+ case G_FILE_MONITOR_EVENT_DELETED:
+ uri = g_file_get_uri (file);
+ bg_pictures_source_remove (self, uri);
+ break;
+
+ default:
+ return;
+ }
+}
+
+static GFileMonitor *
+monitor_path (BgPicturesSource *self,
+ const char *path)
+{
+ GFileMonitor *monitor;
+ g_autoptr(GFile) dir = NULL;
+
+ g_mkdir_with_parents (path, USER_DIR_MODE);
+
+ dir = g_file_new_for_path (path);
+ g_file_enumerate_children_async (dir,
+ ATTRIBUTES,
+ G_FILE_QUERY_INFO_NONE,
+ G_PRIORITY_LOW, self->cancellable,
+ dir_enum_async_ready, self);
+
+ monitor = g_file_monitor_directory (dir,
+ G_FILE_MONITOR_NONE,
+ self->cancellable,
+ NULL);
+
+ if (monitor)
+ g_signal_connect_object (monitor,
+ "changed",
+ G_CALLBACK (files_changed_cb),
+ self, G_CONNECT_SWAPPED);
+
+ return monitor;
+}
+
+static void
+media_found_cb (BgPicturesSource *self, GrlMedia *media)
+{
+ g_autoptr(GFile) file = NULL;
+ const gchar *uri;
+
+ uri = grl_media_get_url (media);
+ file = g_file_new_for_uri (uri);
+ g_object_set_data_full (G_OBJECT (file), "grl-media", g_object_ref (media), g_object_unref);
+ add_single_file_from_media (self, file, media);
+}
+
+static void
+bg_pictures_source_init (BgPicturesSource *self)
+{
+ const gchar *pictures_path;
+ g_autofree gchar *cache_path = NULL;
+
+ self->cancellable = g_cancellable_new ();
+ self->known_items = g_hash_table_new_full (g_str_hash,
+ g_str_equal,
+ (GDestroyNotify) g_free,
+ NULL);
+
+ pictures_path = g_get_user_special_dir (G_USER_DIRECTORY_PICTURES);
+ if (pictures_path == NULL)
+ pictures_path = g_get_home_dir ();
+
+ self->picture_dir_monitor = monitor_path (self, pictures_path);
+
+ cache_path = bg_pictures_source_get_cache_path ();
+ self->cache_dir_monitor = monitor_path (self, cache_path);
+
+ self->grl_miner = cc_background_grilo_miner_new ();
+ g_signal_connect_object (self->grl_miner, "media-found", G_CALLBACK (media_found_cb), self, G_CONNECT_SWAPPED);
+ cc_background_grilo_miner_start (self->grl_miner);
+}
+
+BgPicturesSource *
+bg_pictures_source_new (GtkWidget *widget)
+{
+ return g_object_new (BG_TYPE_PICTURES_SOURCE, "widget", widget, NULL);
+}
+
+const char * const *
+bg_pictures_get_support_content_types (void)
+{
+ return content_types;
+}
diff --git a/panels/background/bg-pictures-source.h b/panels/background/bg-pictures-source.h
new file mode 100644
index 0000000..f62cbe5
--- /dev/null
+++ b/panels/background/bg-pictures-source.h
@@ -0,0 +1,46 @@
+/* bg-pictures-source.h */
+/*
+ * Copyright (C) 2010 Intel, Inc
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * Author: Thomas Wood <thomas.wood@intel.com>
+ *
+ */
+
+#pragma once
+
+#include <gtk/gtk.h>
+#include "bg-source.h"
+#include "cc-background-item.h"
+
+G_BEGIN_DECLS
+
+#define BG_TYPE_PICTURES_SOURCE (bg_pictures_source_get_type ())
+G_DECLARE_FINAL_TYPE (BgPicturesSource, bg_pictures_source, BG, PICTURES_SOURCE, BgSource)
+
+BgPicturesSource *bg_pictures_source_new (GtkWidget *widget);
+char *bg_pictures_source_get_cache_path (void);
+char *bg_pictures_source_get_unique_path(const char *uri);
+gboolean bg_pictures_source_add (BgPicturesSource *bg_source,
+ const char *uri,
+ GtkTreeRowReference **ret_row_ref);
+gboolean bg_pictures_source_remove (BgPicturesSource *bg_source,
+ const char *uri);
+gboolean bg_pictures_source_is_known (BgPicturesSource *bg_source,
+ const char *uri);
+
+const char * const * bg_pictures_get_support_content_types (void);
+
+G_END_DECLS
diff --git a/panels/background/bg-recent-source.c b/panels/background/bg-recent-source.c
new file mode 100644
index 0000000..1177327
--- /dev/null
+++ b/panels/background/bg-recent-source.c
@@ -0,0 +1,470 @@
+/* bg-recent-source.c
+ *
+ * Copyright 2019 Georges Basile Stavracas Neto <georges.stavracas@gmail.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "bg-recent-source"
+
+#include "bg-recent-source.h"
+#include "cc-background-item.h"
+
+#define ATTRIBUTES G_FILE_ATTRIBUTE_STANDARD_NAME "," \
+ G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE "," \
+ G_FILE_ATTRIBUTE_TIME_MODIFIED
+
+struct _BgRecentSource
+{
+ BgSource parent;
+
+ GFile *backgrounds_folder;
+ GFileMonitor *monitor;
+
+ GCancellable *cancellable;
+ GHashTable *items;
+};
+
+G_DEFINE_TYPE (BgRecentSource, bg_recent_source, BG_TYPE_SOURCE)
+
+
+static const gchar * const content_types[] = {
+ "image/png",
+ "image/jp2",
+ "image/jpeg",
+ "image/bmp",
+ "image/svg+xml",
+ "image/x-portable-anymap",
+ NULL
+};
+
+static int
+sort_func (gconstpointer a,
+ gconstpointer b,
+ gpointer user_data)
+{
+ CcBackgroundItem *item_a;
+ CcBackgroundItem *item_b;
+ guint64 modified_a;
+ guint64 modified_b;
+ int retval;
+
+ item_a = (CcBackgroundItem *) a;
+ item_b = (CcBackgroundItem *) b;
+ modified_a = cc_background_item_get_modified (item_a);
+ modified_b = cc_background_item_get_modified (item_b);
+
+ retval = modified_b - modified_a;
+
+ return retval;
+}
+
+static void
+add_file_from_info (BgRecentSource *self,
+ GFile *file,
+ GFileInfo *info)
+{
+ g_autoptr(CcBackgroundItem) item = NULL;
+ CcBackgroundItemFlags flags = 0;
+ g_autofree gchar *source_uri = NULL;
+ g_autofree gchar *uri = NULL;
+ GListStore *store;
+ const gchar *content_type;
+ guint64 mtime;
+
+ content_type = g_file_info_get_content_type (info);
+ mtime = g_file_info_get_attribute_uint64 (info, G_FILE_ATTRIBUTE_TIME_MODIFIED);
+
+ if (!content_type || !g_strv_contains (content_types, content_type))
+ return;
+
+ uri = g_file_get_uri (file);
+ item = cc_background_item_new (uri);
+ flags |= CC_BACKGROUND_ITEM_HAS_SHADING | CC_BACKGROUND_ITEM_HAS_PLACEMENT;
+ g_object_set (G_OBJECT (item),
+ "flags", flags,
+ "shading", G_DESKTOP_BACKGROUND_SHADING_SOLID,
+ "placement", G_DESKTOP_BACKGROUND_STYLE_ZOOM,
+ "modified", mtime,
+ "needs-download", FALSE,
+ "source-url", source_uri,
+ NULL);
+
+ store = bg_source_get_liststore (BG_SOURCE (self));
+ g_list_store_insert_sorted (store, item, sort_func, self);
+
+ g_hash_table_insert (self->items, g_strdup (uri), g_object_ref (item));
+}
+
+static void
+remove_item (BgRecentSource *self,
+ CcBackgroundItem *item)
+{
+ GListStore *store;
+ const gchar *uri;
+ guint i;
+
+ g_return_if_fail (BG_IS_RECENT_SOURCE (self));
+ g_return_if_fail (CC_IS_BACKGROUND_ITEM (item));
+
+ uri = cc_background_item_get_uri (item);
+ store = bg_source_get_liststore (BG_SOURCE (self));
+
+ g_debug ("Removing wallpaper %s", uri);
+
+ for (i = 0; i < g_list_model_get_n_items (G_LIST_MODEL (store)); i++)
+ {
+ g_autoptr(CcBackgroundItem) tmp = NULL;
+
+ tmp = g_list_model_get_item (G_LIST_MODEL (store), i);
+
+ if (tmp == item)
+ {
+ g_list_store_remove (store, i);
+ break;
+ }
+ }
+
+ g_hash_table_remove (self->items, cc_background_item_get_uri (item));
+}
+
+static void
+query_info_finished_cb (GObject *source,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ BgRecentSource *self;
+ g_autoptr(GFileInfo) file_info = NULL;
+ g_autoptr(GError) error = NULL;
+ GFile *file = NULL;
+
+ file = G_FILE (source);
+ file_info = g_file_query_info_finish (file, result, &error);
+ if (error)
+ {
+ if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ g_warning ("Could not get pictures file information: %s", error->message);
+ return;
+ }
+
+ self = BG_RECENT_SOURCE (user_data);
+
+ g_debug ("Adding wallpaper %s (%d)",
+ g_file_info_get_name (file_info),
+ G_IS_FILE (self->backgrounds_folder));
+
+ add_file_from_info (self, file, file_info);
+}
+
+static void
+on_file_changed_cb (BgRecentSource *self,
+ GFile *file,
+ GFile *other_file,
+ GFileMonitorEvent event_type)
+{
+ g_autofree gchar *uri = NULL;
+
+ switch (event_type)
+ {
+ case G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT:
+ g_file_query_info_async (file,
+ ATTRIBUTES,
+ G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
+ G_PRIORITY_DEFAULT,
+ self->cancellable,
+ query_info_finished_cb,
+ self);
+ break;
+
+ case G_FILE_MONITOR_EVENT_DELETED:
+ uri = g_file_get_uri (file);
+ remove_item (self, g_hash_table_lookup (self->items, uri));
+ break;
+
+ default:
+ return;
+ }
+}
+
+static int
+file_sort_func (gconstpointer a,
+ gconstpointer b)
+{
+ GFileInfo *file_a = G_FILE_INFO (a);
+ GFileInfo *file_b = G_FILE_INFO (b);
+ guint64 modified_a, modified_b;
+
+ modified_a = g_file_info_get_attribute_uint64 (file_a, G_FILE_ATTRIBUTE_TIME_MODIFIED);
+ modified_b = g_file_info_get_attribute_uint64 (file_b, G_FILE_ATTRIBUTE_TIME_MODIFIED);
+
+ return modified_b - modified_a;
+}
+
+static void
+file_info_async_ready_cb (GObject *source,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ BgRecentSource *self;
+ g_autolist(GFileInfo) file_infos = NULL;
+ g_autoptr(GError) error = NULL;
+ GFile *parent = NULL;
+ GList *l;
+
+ file_infos = g_file_enumerator_next_files_finish (G_FILE_ENUMERATOR (source),
+ result,
+ &error);
+ if (error)
+ {
+ if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ g_warning ("Could not get pictures file information: %s", error->message);
+ return;
+ }
+
+ self = BG_RECENT_SOURCE (user_data);
+ parent = g_file_enumerator_get_container (G_FILE_ENUMERATOR (source));
+
+ file_infos = g_list_sort (file_infos, file_sort_func);
+
+ for (l = file_infos; l; l = l->next)
+ {
+ g_autoptr(GFile) file = NULL;
+ GFileInfo *info;
+
+ info = l->data;
+ file = g_file_get_child (parent, g_file_info_get_name (info));
+
+ g_debug ("Found recent wallpaper %s", g_file_info_get_name (info));
+
+ add_file_from_info (self, file, info);
+ }
+
+ g_file_enumerator_close (G_FILE_ENUMERATOR (source), self->cancellable, &error);
+
+ if (error)
+ g_warning ("Error closing file enumerator: %s", error->message);
+}
+
+static void
+enumerate_children_finished_cb (GObject *source,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ BgRecentSource *self;
+ g_autoptr(GFileEnumerator) enumerator = NULL;
+ g_autoptr(GError) error = NULL;
+
+ enumerator = g_file_enumerate_children_finish (G_FILE (source), result, &error);
+
+ if (error)
+ {
+ if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ g_warning ("Could not fill pictures source: %s", error->message);
+ return;
+ }
+
+ self = BG_RECENT_SOURCE (user_data);
+ g_file_enumerator_next_files_async (enumerator,
+ G_MAXINT,
+ G_PRIORITY_DEFAULT,
+ self->cancellable,
+ file_info_async_ready_cb,
+ self);
+}
+
+static void
+load_backgrounds (BgRecentSource *self)
+{
+ g_autofree gchar *backgrounds_path = NULL;
+ g_autoptr(GError) error = NULL;
+
+ if (!g_file_make_directory_with_parents (self->backgrounds_folder, self->cancellable, &error) &&
+ !g_error_matches (error, G_IO_ERROR, G_IO_ERROR_EXISTS))
+ {
+ g_critical ("Failed to create local background directory: %s", error->message);
+ return;
+ }
+
+ backgrounds_path = g_file_get_path (self->backgrounds_folder);
+ g_debug ("Enumerating wallpapers under %s", backgrounds_path);
+
+ g_file_enumerate_children_async (self->backgrounds_folder,
+ ATTRIBUTES,
+ G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
+ G_PRIORITY_DEFAULT,
+ self->cancellable,
+ enumerate_children_finished_cb,
+ self);
+
+ self->monitor = g_file_monitor_directory (self->backgrounds_folder,
+ G_FILE_MONITOR_WATCH_MOVES,
+ self->cancellable,
+ &error);
+
+ if (!self->monitor)
+ {
+ g_critical ("Failed to monitor background directory: %s", error->message);
+ return;
+ }
+
+ g_signal_connect_object (self->monitor, "changed", G_CALLBACK (on_file_changed_cb), self, G_CONNECT_SWAPPED);
+}
+
+/* Callbacks */
+
+static void
+on_file_copied_cb (GObject *source,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ g_autoptr(BgRecentSource) self = BG_RECENT_SOURCE (user_data);
+ g_autofree gchar *original_file = NULL;
+ g_autoptr(GError) error = NULL;
+
+ g_file_copy_finish (G_FILE (source), result, &error);
+
+ if (error)
+ {
+ if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ g_critical ("Failed to copy file: %s", error->message);
+ return;
+ }
+
+ original_file = g_file_get_path (G_FILE (source));
+ g_debug ("Successfully copied wallpaper: %s", original_file);
+}
+
+static void
+on_file_deleted_cb (GObject *source,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ g_autoptr(BgRecentSource) self = BG_RECENT_SOURCE (user_data);
+ g_autofree gchar *original_file = NULL;
+ g_autoptr(GError) error = NULL;
+
+ g_file_delete_finish (G_FILE (source), result, &error);
+
+ if (error)
+ {
+ if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ g_critical ("Failed to delete wallpaper: %s", error->message);
+ return;
+ }
+
+ original_file = g_file_get_path (G_FILE (source));
+ g_debug ("Successfully deleted wallpaper: %s", original_file);
+}
+
+/* GObject overrides */
+
+static void
+bg_recent_source_finalize (GObject *object)
+{
+ BgRecentSource *self = (BgRecentSource *)object;
+
+ g_cancellable_cancel (self->cancellable);
+ g_clear_object (&self->cancellable);
+ g_clear_object (&self->monitor);
+
+ G_OBJECT_CLASS (bg_recent_source_parent_class)->finalize (object);
+}
+
+static void
+bg_recent_source_class_init (BgRecentSourceClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->finalize = bg_recent_source_finalize;
+}
+
+static void
+bg_recent_source_init (BgRecentSource *self)
+{
+ g_autofree gchar *backgrounds_path = NULL;
+
+ backgrounds_path = g_build_filename (g_get_user_data_dir (), "backgrounds", NULL);
+
+ self->items = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref);
+ self->cancellable = g_cancellable_new ();
+ self->backgrounds_folder = g_file_new_for_path (backgrounds_path);
+
+ load_backgrounds (self);
+}
+
+BgRecentSource*
+bg_recent_source_new (GtkWidget *widget)
+{
+ return g_object_new (BG_TYPE_RECENT_SOURCE,
+ "widget", widget,
+ NULL);
+}
+
+void
+bg_recent_source_add_file (BgRecentSource *self,
+ const gchar *path)
+{
+ g_autoptr(GDateTime) now = NULL;
+ g_autofree gchar *destination_name = NULL;
+ g_autofree gchar *formatted_now = NULL;
+ g_autofree gchar *basename = NULL;
+ g_autoptr(GFile) destination = NULL;
+ g_autoptr(GFile) file = NULL;
+
+ g_return_if_fail (BG_IS_RECENT_SOURCE (self));
+ g_return_if_fail (path && *path);
+
+ g_debug ("Importing wallpaper %s", path);
+
+ now = g_date_time_new_now_local ();
+ formatted_now = g_date_time_format (now, "%Y-%m-%d-%H-%M-%S");
+
+ file = g_file_new_for_path (path);
+
+ basename = g_file_get_basename (file);
+ destination_name = g_strdup_printf ("%s-%s", formatted_now, basename);
+ destination = g_file_get_child (self->backgrounds_folder, destination_name);
+
+ g_file_copy_async (file,
+ destination,
+ G_FILE_COPY_NONE,
+ G_PRIORITY_DEFAULT,
+ self->cancellable,
+ NULL, NULL,
+ on_file_copied_cb,
+ g_object_ref (self));
+}
+
+void
+bg_recent_source_remove_item (BgRecentSource *self,
+ CcBackgroundItem *item)
+{
+ g_autoptr(GFile) file = NULL;
+ const gchar *uri;
+
+ g_return_if_fail (BG_IS_RECENT_SOURCE (self));
+ g_return_if_fail (CC_IS_BACKGROUND_ITEM (item));
+
+ uri = cc_background_item_get_uri (item);
+ file = g_file_new_for_uri (uri);
+
+ g_file_delete_async (file,
+ G_PRIORITY_DEFAULT,
+ self->cancellable,
+ on_file_deleted_cb,
+ g_object_ref (self));
+}
diff --git a/panels/background/bg-recent-source.h b/panels/background/bg-recent-source.h
new file mode 100644
index 0000000..58d4c04
--- /dev/null
+++ b/panels/background/bg-recent-source.h
@@ -0,0 +1,39 @@
+/* bg-recent-source.h
+ *
+ * Copyright 2019 Georges Basile Stavracas Neto <georges.stavracas@gmail.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#pragma once
+
+#include "bg-source.h"
+#include "cc-background-item.h"
+
+G_BEGIN_DECLS
+
+#define BG_TYPE_RECENT_SOURCE (bg_recent_source_get_type())
+G_DECLARE_FINAL_TYPE (BgRecentSource, bg_recent_source, BG, RECENT_SOURCE, BgSource)
+
+BgRecentSource* bg_recent_source_new (GtkWidget *widget);
+
+void bg_recent_source_add_file (BgRecentSource *self,
+ const gchar *path);
+
+void bg_recent_source_remove_item (BgRecentSource *self,
+ CcBackgroundItem *item);
+
+G_END_DECLS
diff --git a/panels/background/bg-source.c b/panels/background/bg-source.c
new file mode 100644
index 0000000..b199257
--- /dev/null
+++ b/panels/background/bg-source.c
@@ -0,0 +1,213 @@
+/*
+ * Copyright (C) 2010 Intel, Inc
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * Author: Thomas Wood <thomas.wood@intel.com>
+ *
+ */
+
+#include "bg-source.h"
+#include "cc-background-item.h"
+
+#include <cairo-gobject.h>
+
+#define THUMBNAIL_WIDTH 154
+#define THUMBNAIL_HEIGHT (THUMBNAIL_WIDTH * 3 / 4)
+
+typedef struct
+{
+ GnomeDesktopThumbnailFactory *thumbnail_factory;
+ GListStore *store;
+ GtkWidget *widget;
+ gint thumbnail_height;
+ gint thumbnail_width;
+} BgSourcePrivate;
+
+G_DEFINE_ABSTRACT_TYPE_WITH_PRIVATE (BgSource, bg_source, G_TYPE_OBJECT)
+
+enum
+{
+ PROP_LISTSTORE = 1,
+ PROP_WIDGET
+};
+
+
+static void
+bg_source_calculate_thumbnail_dimensions (BgSource *source)
+{
+ BgSourcePrivate *priv = bg_source_get_instance_private (source);
+ gint scale_factor;
+
+ priv->thumbnail_height = THUMBNAIL_HEIGHT;
+ priv->thumbnail_width = THUMBNAIL_WIDTH;
+
+ if (priv->widget == NULL)
+ return;
+
+ scale_factor = gtk_widget_get_scale_factor (priv->widget);
+ if (scale_factor > 1)
+ {
+ priv->thumbnail_height *= scale_factor;
+ priv->thumbnail_width *= scale_factor;
+ }
+}
+
+static void
+bg_source_constructed (GObject *object)
+{
+ G_OBJECT_CLASS (bg_source_parent_class)->constructed (object);
+
+ bg_source_calculate_thumbnail_dimensions (BG_SOURCE (object));
+}
+
+static void
+bg_source_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ BgSource *source = BG_SOURCE (object);
+
+ switch (property_id)
+ {
+ case PROP_LISTSTORE:
+ g_value_set_object (value, bg_source_get_liststore (source));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ }
+}
+
+static void
+bg_source_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ BgSource *source = BG_SOURCE (object);
+ BgSourcePrivate *priv = bg_source_get_instance_private (source);
+
+ switch (property_id)
+ {
+ case PROP_WIDGET:
+ priv->widget = GTK_WIDGET (g_value_get_object (value));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ }
+}
+
+static void
+bg_source_dispose (GObject *object)
+{
+ BgSource *source = BG_SOURCE (object);
+ BgSourcePrivate *priv = bg_source_get_instance_private (source);
+
+ g_clear_object (&priv->thumbnail_factory);
+ g_clear_object (&priv->store);
+
+ G_OBJECT_CLASS (bg_source_parent_class)->dispose (object);
+}
+
+static void
+bg_source_class_init (BgSourceClass *klass)
+{
+ GParamSpec *pspec;
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->constructed = bg_source_constructed;
+ object_class->get_property = bg_source_get_property;
+ object_class->set_property = bg_source_set_property;
+ object_class->dispose = bg_source_dispose;
+
+ pspec = g_param_spec_object ("liststore",
+ "Liststore",
+ "Liststore used in the source",
+ G_TYPE_LIST_STORE,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_LISTSTORE, pspec);
+
+ pspec = g_param_spec_object ("widget",
+ "Widget",
+ "Widget used to view the source",
+ GTK_TYPE_WIDGET,
+ G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_WIDGET, pspec);
+}
+
+static void
+bg_source_init (BgSource *self)
+{
+ BgSourcePrivate *priv = bg_source_get_instance_private (self);
+ priv->store = g_list_store_new (CC_TYPE_BACKGROUND_ITEM);
+ priv->thumbnail_factory = gnome_desktop_thumbnail_factory_new (GNOME_DESKTOP_THUMBNAIL_SIZE_LARGE);
+}
+
+GListStore*
+bg_source_get_liststore (BgSource *source)
+{
+ BgSourcePrivate *priv;
+
+ g_return_val_if_fail (BG_IS_SOURCE (source), NULL);
+
+ priv = bg_source_get_instance_private (source);
+ return priv->store;
+}
+
+gint
+bg_source_get_scale_factor (BgSource *source)
+{
+ BgSourcePrivate *priv;
+
+ g_return_val_if_fail (BG_IS_SOURCE (source), 1);
+
+ priv = bg_source_get_instance_private (source);
+ return gtk_widget_get_scale_factor (priv->widget);
+}
+
+gint
+bg_source_get_thumbnail_height (BgSource *source)
+{
+ BgSourcePrivate *priv;
+
+ g_return_val_if_fail (BG_IS_SOURCE (source), THUMBNAIL_HEIGHT);
+
+ priv = bg_source_get_instance_private (source);
+ return priv->thumbnail_height;
+}
+
+gint
+bg_source_get_thumbnail_width (BgSource *source)
+{
+ BgSourcePrivate *priv;
+
+ g_return_val_if_fail (BG_IS_SOURCE (source), THUMBNAIL_WIDTH);
+
+ priv = bg_source_get_instance_private (source);
+ return priv->thumbnail_width;
+}
+
+GnomeDesktopThumbnailFactory*
+bg_source_get_thumbnail_factory (BgSource *source)
+{
+ BgSourcePrivate *priv;
+
+ g_return_val_if_fail (BG_IS_SOURCE (source), NULL);
+
+ priv = bg_source_get_instance_private (source);
+ return priv->thumbnail_factory;
+}
diff --git a/panels/background/bg-source.h b/panels/background/bg-source.h
new file mode 100644
index 0000000..ff86a85
--- /dev/null
+++ b/panels/background/bg-source.h
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2010 Intel, Inc
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * Author: Thomas Wood <thomas.wood@intel.com>
+ *
+ */
+
+#pragma once
+
+#include <gtk/gtk.h>
+#include <libgnome-desktop/gnome-desktop-thumbnail.h>
+
+G_BEGIN_DECLS
+
+#define BG_TYPE_SOURCE (bg_source_get_type ())
+G_DECLARE_DERIVABLE_TYPE (BgSource, bg_source, BG, SOURCE, GObject)
+
+struct _BgSourceClass
+{
+ GObjectClass parent_class;
+};
+
+GListStore* bg_source_get_liststore (BgSource *source);
+
+gint bg_source_get_scale_factor (BgSource *source);
+
+gint bg_source_get_thumbnail_height (BgSource *source);
+
+gint bg_source_get_thumbnail_width (BgSource *source);
+
+GnomeDesktopThumbnailFactory* bg_source_get_thumbnail_factory (BgSource *source);
+
+G_END_DECLS
diff --git a/panels/background/bg-wallpapers-source.c b/panels/background/bg-wallpapers-source.c
new file mode 100644
index 0000000..7d3b644
--- /dev/null
+++ b/panels/background/bg-wallpapers-source.c
@@ -0,0 +1,138 @@
+/* bg-wallpapers-source.c */
+/*
+ * Copyright (C) 2010 Intel, Inc
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * Author: Thomas Wood <thomas.wood@intel.com>
+ *
+ */
+
+#include "bg-wallpapers-source.h"
+
+#include "cc-background-item.h"
+#include "cc-background-xml.h"
+
+#include <cairo-gobject.h>
+#include <gio/gio.h>
+
+struct _BgWallpapersSource
+{
+ BgSource parent_instance;
+ CcBackgroundXml *xml;
+};
+
+G_DEFINE_TYPE (BgWallpapersSource, bg_wallpapers_source, BG_TYPE_SOURCE)
+
+static void
+load_wallpapers (gchar *key,
+ CcBackgroundItem *item,
+ BgWallpapersSource *source)
+{
+ GListStore *store = bg_source_get_liststore (BG_SOURCE (source));
+ gboolean deleted;
+
+ g_object_get (G_OBJECT (item), "is-deleted", &deleted, NULL);
+
+ if (deleted)
+ return;
+
+ g_list_store_append (store, item);
+}
+
+static void
+list_load_cb (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ g_autoptr(GError) error = NULL;
+ if (!cc_background_xml_load_list_finish (CC_BACKGROUND_XML (source_object), res, &error))
+ g_warning ("Failed to load background list: %s", error->message);
+}
+
+static void
+item_added (BgWallpapersSource *self,
+ CcBackgroundItem *item)
+{
+ load_wallpapers (NULL, item, self);
+}
+
+static void
+load_default_bg (BgWallpapersSource *self)
+{
+ const char * const *system_data_dirs;
+ guint i;
+
+ /* FIXME We could do this nicer if we had the XML source in GSettings */
+
+ system_data_dirs = g_get_system_data_dirs ();
+ for (i = 0; system_data_dirs[i]; i++) {
+ g_autofree gchar *filename = NULL;
+
+ filename = g_build_filename (system_data_dirs[i],
+ "gnome-background-properties",
+ "adwaita.xml",
+ NULL);
+ if (cc_background_xml_load_xml (self->xml, filename))
+ break;
+ }
+}
+
+static void
+bg_wallpapers_source_constructed (GObject *object)
+{
+ BgWallpapersSource *self = BG_WALLPAPERS_SOURCE (object);
+
+ G_OBJECT_CLASS (bg_wallpapers_source_parent_class)->constructed (object);
+
+ g_signal_connect_object (G_OBJECT (self->xml), "added",
+ G_CALLBACK (item_added), self, G_CONNECT_SWAPPED);
+
+ /* Try adding the default background first */
+ load_default_bg (self);
+
+ cc_background_xml_load_list_async (self->xml, NULL, list_load_cb, self);
+}
+
+static void
+bg_wallpapers_source_dispose (GObject *object)
+{
+ BgWallpapersSource *self = BG_WALLPAPERS_SOURCE (object);
+
+ g_clear_object (&self->xml);
+
+ G_OBJECT_CLASS (bg_wallpapers_source_parent_class)->dispose (object);
+}
+
+static void
+bg_wallpapers_source_init (BgWallpapersSource *self)
+{
+ self->xml = cc_background_xml_new ();
+}
+
+static void
+bg_wallpapers_source_class_init (BgWallpapersSourceClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->constructed = bg_wallpapers_source_constructed;
+ object_class->dispose = bg_wallpapers_source_dispose;
+}
+
+BgWallpapersSource *
+bg_wallpapers_source_new (GtkWidget *widget)
+{
+ return g_object_new (BG_TYPE_WALLPAPERS_SOURCE, "widget", widget, NULL);
+}
+
diff --git a/panels/background/bg-wallpapers-source.h b/panels/background/bg-wallpapers-source.h
new file mode 100644
index 0000000..3ca222b
--- /dev/null
+++ b/panels/background/bg-wallpapers-source.h
@@ -0,0 +1,34 @@
+/* bg-wallpapers-source.h */
+/*
+ * Copyright (C) 2010 Intel, Inc
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * Author: Thomas Wood <thomas.wood@intel.com>
+ *
+ */
+
+#pragma once
+
+#include <gtk/gtk.h>
+#include "bg-source.h"
+
+G_BEGIN_DECLS
+
+#define BG_TYPE_WALLPAPERS_SOURCE (bg_wallpapers_source_get_type ())
+G_DECLARE_FINAL_TYPE (BgWallpapersSource, bg_wallpapers_source, BG, WALLPAPERS_SOURCE, BgSource)
+
+BgWallpapersSource *bg_wallpapers_source_new (GtkWidget *widget);
+
+G_END_DECLS
diff --git a/panels/background/cc-background-chooser.c b/panels/background/cc-background-chooser.c
new file mode 100644
index 0000000..b28a5ee
--- /dev/null
+++ b/panels/background/cc-background-chooser.c
@@ -0,0 +1,386 @@
+/* cc-background-chooser.c
+ *
+ * Copyright 2019 Georges Basile Stavracas Neto <georges.stavracas@gmail.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "cc-background-chooser"
+
+#include <glib/gi18n.h>
+#include <libgnome-desktop/gnome-desktop-thumbnail.h>
+
+#include "bg-colors-source.h"
+#include "bg-pictures-source.h"
+#include "bg-recent-source.h"
+#include "bg-wallpapers-source.h"
+#include "cc-background-chooser.h"
+
+struct _CcBackgroundChooser
+{
+ GtkBox parent;
+
+ GtkFlowBox *flowbox;
+ GtkWidget *recent_box;
+ GtkFlowBox *recent_flowbox;
+
+ gboolean recent_selected;
+
+ BgWallpapersSource *wallpapers_source;
+ BgRecentSource *recent_source;
+};
+
+G_DEFINE_TYPE (CcBackgroundChooser, cc_background_chooser, GTK_TYPE_BOX)
+
+enum
+{
+ BACKGROUND_CHOSEN,
+ N_SIGNALS,
+};
+
+static guint signals [N_SIGNALS];
+
+static void
+emit_background_chosen (CcBackgroundChooser *self)
+{
+ g_autoptr(GList) list = NULL;
+ CcBackgroundItem *item;
+ GtkFlowBox *flowbox;
+
+ flowbox = self->recent_selected ? self->recent_flowbox : self->flowbox;
+ list = gtk_flow_box_get_selected_children (flowbox);
+ g_assert (g_list_length (list) == 1);
+
+ item = g_object_get_data (list->data, "item");
+
+ g_signal_emit (self, signals[BACKGROUND_CHOSEN], 0, item);
+}
+
+static void
+on_delete_background_clicked_cb (GtkButton *button,
+ BgRecentSource *source)
+{
+ GtkWidget *parent;
+ CcBackgroundItem *item;
+
+ parent = gtk_widget_get_parent (gtk_widget_get_parent (GTK_WIDGET (button)));
+ g_assert (GTK_IS_FLOW_BOX_CHILD (parent));
+
+ item = g_object_get_data (G_OBJECT (parent), "item");
+
+ bg_recent_source_remove_item (source, item);
+}
+
+static GtkWidget*
+create_widget_func (gpointer model_item,
+ gpointer user_data)
+{
+ g_autoptr(GdkPixbuf) pixbuf = NULL;
+ CcBackgroundItem *item;
+ GtkWidget *overlay;
+ GtkWidget *child;
+ GtkWidget *image;
+ GtkWidget *icon;
+ GtkWidget *button_image;
+ GtkWidget *button = NULL;
+ BgSource *source;
+
+ source = BG_SOURCE (user_data);
+ item = CC_BACKGROUND_ITEM (model_item);
+ pixbuf = cc_background_item_get_thumbnail (item,
+ bg_source_get_thumbnail_factory (source),
+ bg_source_get_thumbnail_width (source),
+ bg_source_get_thumbnail_height (source),
+ bg_source_get_scale_factor (source));
+ image = gtk_image_new_from_pixbuf (pixbuf);
+ gtk_widget_show (image);
+
+ icon = g_object_new (GTK_TYPE_IMAGE,
+ "icon-name", "slideshow-emblem",
+ "pixel-size", 16,
+ "margin", 8,
+ "halign", GTK_ALIGN_END,
+ "valign", GTK_ALIGN_END,
+ "visible", cc_background_item_changes_with_time (item),
+ NULL);
+ gtk_style_context_add_class (gtk_widget_get_style_context (icon), "slideshow-emblem");
+
+
+ if (BG_IS_RECENT_SOURCE (source))
+ {
+ button_image = gtk_image_new_from_icon_name ("window-close-symbolic", GTK_ICON_SIZE_BUTTON);
+ button = g_object_new (GTK_TYPE_BUTTON,
+ "image", button_image,
+ "halign", GTK_ALIGN_END,
+ "valign", GTK_ALIGN_START,
+ "margin", 6,
+ "visible", TRUE,
+ NULL);
+
+ gtk_style_context_add_class (gtk_widget_get_style_context (button), "osd");
+ gtk_style_context_add_class (gtk_widget_get_style_context (button), "remove-button");
+
+ g_signal_connect (button,
+ "clicked",
+ G_CALLBACK (on_delete_background_clicked_cb),
+ source);
+ }
+
+ overlay = gtk_overlay_new ();
+ gtk_container_add (GTK_CONTAINER (overlay), image);
+ gtk_overlay_add_overlay (GTK_OVERLAY (overlay), icon);
+ if (button)
+ gtk_overlay_add_overlay (GTK_OVERLAY (overlay), button);
+ gtk_widget_show (overlay);
+
+ child = g_object_new (GTK_TYPE_FLOW_BOX_CHILD,
+ "halign", GTK_ALIGN_CENTER,
+ "valign", GTK_ALIGN_CENTER,
+ NULL);
+ gtk_container_add (GTK_CONTAINER (child), overlay);
+ gtk_widget_show (child);
+
+ g_object_set_data_full (G_OBJECT (child), "item", g_object_ref (item), g_object_unref);
+
+ return child;
+}
+
+static void
+update_recent_visibility (CcBackgroundChooser *self)
+{
+ GListStore *store;
+ gboolean has_items;
+
+ store = bg_source_get_liststore (BG_SOURCE (self->recent_source));
+ has_items = g_list_model_get_n_items (G_LIST_MODEL (store)) != 0;
+
+ gtk_widget_set_visible (self->recent_box, has_items);
+}
+
+static void
+setup_flowbox (CcBackgroundChooser *self)
+{
+ GListStore *store;
+
+ store = bg_source_get_liststore (BG_SOURCE (self->wallpapers_source));
+
+ gtk_flow_box_bind_model (self->flowbox,
+ G_LIST_MODEL (store),
+ create_widget_func,
+ self->wallpapers_source,
+ NULL);
+
+ store = bg_source_get_liststore (BG_SOURCE (self->recent_source));
+
+ gtk_flow_box_bind_model (self->recent_flowbox,
+ G_LIST_MODEL (store),
+ create_widget_func,
+ self->recent_source,
+ NULL);
+
+ update_recent_visibility (self);
+ g_signal_connect_object (store,
+ "items-changed",
+ G_CALLBACK (update_recent_visibility),
+ self,
+ G_CONNECT_SWAPPED);
+}
+
+static void
+on_item_activated_cb (GtkFlowBox *flowbox,
+ GtkFlowBoxChild *child,
+ CcBackgroundChooser *self)
+{
+ self->recent_selected = flowbox == self->recent_flowbox;
+ if (self->recent_selected)
+ gtk_flow_box_unselect_all (self->flowbox);
+ else
+ gtk_flow_box_unselect_all (self->recent_flowbox);
+ emit_background_chosen (self);
+}
+
+static void
+on_file_chooser_response_cb (GtkDialog *filechooser,
+ gint response,
+ CcBackgroundChooser *self)
+{
+ if (response == GTK_RESPONSE_ACCEPT)
+ {
+ g_autoptr(GSList) filenames = NULL;
+ GSList *l;
+
+ filenames = gtk_file_chooser_get_filenames (GTK_FILE_CHOOSER (filechooser));
+ for (l = filenames; l != NULL; l = l->next)
+ {
+ g_autofree gchar *filename = l->data;
+
+ bg_recent_source_add_file (self->recent_source, filename);
+ }
+ }
+
+ gtk_widget_destroy (GTK_WIDGET (filechooser));
+}
+
+static void
+on_file_chooser_selection_changed_cb (GtkFileChooser *chooser,
+ GnomeDesktopThumbnailFactory *thumbnail_factory)
+{
+ g_autofree gchar *uri = NULL;
+
+ uri = gtk_file_chooser_get_uri (chooser);
+
+ if (uri)
+ {
+ g_autoptr(GFileInfo) file_info = NULL;
+ g_autoptr(GdkPixbuf) pixbuf = NULL;
+ g_autofree gchar *mime_type = NULL;
+ g_autoptr(GFile) file = NULL;
+ GtkWidget *preview;
+
+ preview = gtk_file_chooser_get_preview_widget (chooser);
+
+ file = g_file_new_for_uri (uri);
+ file_info = g_file_query_info (file,
+ "standard::*",
+ G_FILE_QUERY_INFO_NONE,
+ NULL,
+ NULL);
+
+ if (file_info && g_file_info_get_file_type (file_info) != G_FILE_TYPE_DIRECTORY)
+ mime_type = g_strdup (g_file_info_get_content_type (file_info));
+
+ if (mime_type)
+ {
+ pixbuf = gnome_desktop_thumbnail_factory_generate_thumbnail (thumbnail_factory,
+ uri,
+ mime_type);
+ }
+
+ gtk_dialog_set_response_sensitive (GTK_DIALOG (chooser),
+ GTK_RESPONSE_ACCEPT,
+ pixbuf != NULL);
+
+ if (pixbuf)
+ gtk_image_set_from_pixbuf (GTK_IMAGE (preview), pixbuf);
+ else
+ gtk_image_set_from_icon_name (GTK_IMAGE (preview), "dialog-question", GTK_ICON_SIZE_DIALOG);
+ }
+
+ gtk_file_chooser_set_preview_widget_active (chooser, TRUE);
+}
+
+/* GObject overrides */
+
+static void
+cc_background_chooser_finalize (GObject *object)
+{
+ CcBackgroundChooser *self = (CcBackgroundChooser *)object;
+
+ g_clear_object (&self->recent_source);
+ g_clear_object (&self->wallpapers_source);
+
+ G_OBJECT_CLASS (cc_background_chooser_parent_class)->finalize (object);
+}
+
+static void
+cc_background_chooser_class_init (CcBackgroundChooserClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+ object_class->finalize = cc_background_chooser_finalize;
+
+ signals[BACKGROUND_CHOSEN] = g_signal_new ("background-chosen",
+ CC_TYPE_BACKGROUND_CHOOSER,
+ G_SIGNAL_RUN_FIRST,
+ 0, NULL, NULL, NULL,
+ G_TYPE_NONE,
+ 1,
+ CC_TYPE_BACKGROUND_ITEM);
+
+ gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/control-center/background/cc-background-chooser.ui");
+
+ gtk_widget_class_bind_template_child (widget_class, CcBackgroundChooser, flowbox);
+ gtk_widget_class_bind_template_child (widget_class, CcBackgroundChooser, recent_box);
+ gtk_widget_class_bind_template_child (widget_class, CcBackgroundChooser, recent_flowbox);
+
+ gtk_widget_class_bind_template_callback (widget_class, on_item_activated_cb);
+}
+
+static void
+cc_background_chooser_init (CcBackgroundChooser *self)
+{
+ gtk_widget_init_template (GTK_WIDGET (self));
+
+ self->recent_source = bg_recent_source_new (GTK_WIDGET (self));
+ self->wallpapers_source = bg_wallpapers_source_new (GTK_WIDGET (self));
+ setup_flowbox (self);
+}
+
+void
+cc_background_chooser_select_file (CcBackgroundChooser *self)
+{
+ g_autoptr(GnomeDesktopThumbnailFactory) factory = NULL;
+ GtkFileFilter *filter;
+ GtkWidget *filechooser;
+ GtkWindow *toplevel;
+ GtkWidget *preview;
+
+ g_return_if_fail (CC_IS_BACKGROUND_CHOOSER (self));
+
+ toplevel = (GtkWindow*) gtk_widget_get_toplevel (GTK_WIDGET (self));
+ filechooser = gtk_file_chooser_dialog_new (_("Select a picture"),
+ toplevel,
+ GTK_FILE_CHOOSER_ACTION_OPEN,
+ _("_Cancel"), GTK_RESPONSE_CANCEL,
+ _("_Open"), GTK_RESPONSE_ACCEPT,
+ NULL);
+ gtk_window_set_modal (GTK_WINDOW (filechooser), TRUE);
+
+ preview = gtk_image_new ();
+ gtk_widget_set_size_request (preview, 154, -1);
+ gtk_file_chooser_set_preview_widget (GTK_FILE_CHOOSER (filechooser), preview);
+ gtk_file_chooser_set_use_preview_label (GTK_FILE_CHOOSER (filechooser), FALSE);
+ gtk_file_chooser_set_select_multiple (GTK_FILE_CHOOSER (filechooser), TRUE);
+ gtk_widget_show (preview);
+
+ factory = gnome_desktop_thumbnail_factory_new (GNOME_DESKTOP_THUMBNAIL_SIZE_LARGE);
+ g_signal_connect_after (filechooser,
+ "selection-changed",
+ G_CALLBACK (on_file_chooser_selection_changed_cb),
+ factory);
+
+ g_object_set_data_full (G_OBJECT (filechooser),
+ "factory",
+ g_object_ref (factory),
+ g_object_unref);
+
+ filter = gtk_file_filter_new ();
+ gtk_file_filter_add_pixbuf_formats (filter);
+ gtk_file_chooser_set_filter (GTK_FILE_CHOOSER (filechooser), filter);
+
+ gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (filechooser),
+ g_get_user_special_dir (G_USER_DIRECTORY_PICTURES));
+
+ g_signal_connect_object (filechooser,
+ "response",
+ G_CALLBACK (on_file_chooser_response_cb),
+ self,
+ 0);
+
+ gtk_window_present (GTK_WINDOW (filechooser));
+}
diff --git a/panels/background/cc-background-chooser.h b/panels/background/cc-background-chooser.h
new file mode 100644
index 0000000..1751098
--- /dev/null
+++ b/panels/background/cc-background-chooser.h
@@ -0,0 +1,32 @@
+/* cc-background-chooser.h
+ *
+ * Copyright 2019 Georges Basile Stavracas Neto <georges.stavracas@gmail.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#pragma once
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define CC_TYPE_BACKGROUND_CHOOSER (cc_background_chooser_get_type())
+G_DECLARE_FINAL_TYPE (CcBackgroundChooser, cc_background_chooser, CC, BACKGROUND_CHOOSER, GtkBox)
+
+void cc_background_chooser_select_file (CcBackgroundChooser *self);
+
+G_END_DECLS
diff --git a/panels/background/cc-background-chooser.ui b/panels/background/cc-background-chooser.ui
new file mode 100644
index 0000000..aa7da28
--- /dev/null
+++ b/panels/background/cc-background-chooser.ui
@@ -0,0 +1,90 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <template class="CcBackgroundChooser" parent="GtkBox">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+
+ <child>
+ <object class="GtkSeparator">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ </object>
+ </child>
+
+ <child>
+ <object class="GtkScrolledWindow">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="expand">True</property>
+ <property name="shadow-type">none</property>
+ <property name="hscrollbar-policy">never</property>
+ <property name="vscrollbar-policy">automatic</property>
+ <child>
+ <object class="GtkBox">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <property name="expand">True</property>
+ <style>
+ <class name="view" />
+ </style>
+
+ <!-- Recent -->
+ <child>
+ <object class="GtkBox" id="recent_box">
+ <property name="visible">True</property>
+ <property name="can-focus">True</property>
+ <property name="orientation">vertical</property>
+ <property name="halign">center</property>
+
+ <child>
+ <object class="GtkFlowBox" id="recent_flowbox">
+ <property name="visible">True</property>
+ <property name="margin">12</property>
+ <property name="column-spacing">12</property>
+ <property name="row-spacing">12</property>
+ <property name="homogeneous">True</property>
+ <property name="halign">center</property>
+ <property name="min-children-per-line">1</property>
+ <property name="max-children-per-line">8</property>
+ <property name="activate-on-single-click">True</property>
+ <property name="selection-mode">single</property>
+ <signal name="child-activated" handler="on_item_activated_cb" object="CcBackgroundChooser" swapped="no" />
+ </object>
+ </child>
+
+ <child>
+ <object class="GtkSeparator">
+ <property name="visible">True</property>
+ <property name="can-focus">True</property>
+ <property name="margin-top">12</property>
+ <property name="margin-bottom">12</property>
+ </object>
+ </child>
+
+ </object>
+ </child>
+
+ <child>
+ <object class="GtkFlowBox" id="flowbox">
+ <property name="visible">True</property>
+ <property name="margin">12</property>
+ <property name="column-spacing">12</property>
+ <property name="row-spacing">12</property>
+ <property name="homogeneous">True</property>
+ <property name="halign">center</property>
+ <property name="min-children-per-line">1</property>
+ <property name="max-children-per-line">8</property>
+ <property name="activate-on-single-click">True</property>
+ <property name="selection-mode">single</property>
+ <signal name="child-activated" handler="on_item_activated_cb" object="CcBackgroundChooser" swapped="no" />
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+
+ </template>
+</interface>
diff --git a/panels/background/cc-background-grilo-miner.c b/panels/background/cc-background-grilo-miner.c
new file mode 100644
index 0000000..85c09da
--- /dev/null
+++ b/panels/background/cc-background-grilo-miner.c
@@ -0,0 +1,315 @@
+/*
+ * Copyright (C) 2014 Red Hat, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+
+#include <gio/gio.h>
+#include <grilo.h>
+
+#define GOA_API_IS_SUBJECT_TO_CHANGE
+#include <goa/goa.h>
+
+#include "bg-pictures-source.h"
+#include "cc-background-grilo-miner.h"
+
+struct _CcBackgroundGriloMiner
+{
+ GObject parent_instance;
+
+ GCancellable *cancellable;
+ GList *accounts;
+};
+
+G_DEFINE_TYPE (CcBackgroundGriloMiner, cc_background_grilo_miner, G_TYPE_OBJECT)
+
+enum
+{
+ MEDIA_FOUND,
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = { 0 };
+
+#define REMOTE_ITEM_COUNT 50
+
+static gchar *
+get_grilo_id (GoaObject *goa_object)
+{
+ GoaAccount *account;
+
+ account = goa_object_peek_account (goa_object);
+ return g_strdup_printf ("grl-flickr-%s", goa_account_get_id (account));
+}
+
+static void
+is_online_data_cached (GObject *object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ CcBackgroundGriloMiner *self;
+ GError *error = NULL;
+ GFileInfo *info = NULL;
+ GFile *cache_file = G_FILE (object);
+ GrlMedia *media;
+ const gchar *uri;
+
+ info = g_file_query_info_finish (cache_file, res, &error);
+ if (info == NULL)
+ {
+ if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ goto out;
+ }
+
+ self = CC_BACKGROUND_GRILO_MINER (user_data);
+
+ media = g_object_get_data (G_OBJECT (cache_file), "grl-media");
+ uri = grl_media_get_url (media);
+
+ if (info != NULL)
+ {
+ g_debug ("Ignored URL '%s' as it is already in the cache", uri);
+ goto out;
+ }
+
+ g_signal_emit (self, signals[MEDIA_FOUND], 0, media);
+
+ out:
+ g_clear_object (&info);
+ g_clear_error (&error);
+}
+
+static void
+searched_online_source (GrlSource *source,
+ guint operation_id,
+ GrlMedia *media,
+ guint remaining,
+ gpointer user_data,
+ const GError *error)
+{
+ CcBackgroundGriloMiner *self = CC_BACKGROUND_GRILO_MINER (user_data);
+ g_autoptr(GFile) cache_file = NULL;
+ const gchar *uri;
+ g_autofree gchar *cache_path = NULL;
+
+ if (error != NULL)
+ {
+ const gchar *source_id;
+
+ source_id = grl_source_get_id (source);
+ g_warning ("Error searching %s: %s", source_id, error->message);
+ grl_operation_cancel (operation_id);
+ remaining = 0;
+ goto out;
+ }
+
+ uri = grl_media_get_url (media);
+ cache_path = bg_pictures_source_get_unique_path (uri);
+ cache_file = g_file_new_for_path (cache_path);
+ g_object_set_data_full (G_OBJECT (cache_file), "grl-media", media, g_object_unref);
+ g_file_query_info_async (cache_file,
+ G_FILE_ATTRIBUTE_STANDARD_TYPE,
+ G_FILE_QUERY_INFO_NONE,
+ G_PRIORITY_DEFAULT,
+ self->cancellable,
+ is_online_data_cached,
+ self);
+
+ out:
+ if (remaining == 0)
+ g_object_unref (self);
+}
+
+static void
+query_online_source (CcBackgroundGriloMiner *self, GrlSource *source)
+{
+ const GList *keys;
+ GrlCaps *caps;
+ GrlOperationOptions *options;
+
+ keys = grl_source_supported_keys (source);
+ caps = grl_source_get_caps (source, GRL_OP_BROWSE);
+ options = grl_operation_options_new (caps);
+ grl_operation_options_set_count (options, REMOTE_ITEM_COUNT);
+ grl_operation_options_set_resolution_flags (options, GRL_RESOLVE_FAST_ONLY);
+ grl_operation_options_set_type_filter (options, GRL_TYPE_FILTER_IMAGE);
+
+ grl_source_search (source, NULL, keys, options, searched_online_source, g_object_ref (self));
+ g_object_unref (options);
+}
+
+static void
+add_online_source_cb (CcBackgroundGriloMiner *self,
+ GrlSource *source)
+{
+ GList *l;
+ gboolean found = FALSE;
+ const gchar *source_id;
+
+ source_id = grl_source_get_id (source);
+ for (l = self->accounts; l != NULL && !found; l = l->next)
+ {
+ GoaObject *goa_object = GOA_OBJECT (l->data);
+ g_autofree gchar *account_id = NULL;
+
+ account_id = get_grilo_id (goa_object);
+ if (g_strcmp0 (source_id, account_id) == 0)
+ {
+ query_online_source (self, source);
+ found = TRUE;
+ }
+ }
+}
+
+static void
+client_async_ready (GObject *source,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ CcBackgroundGriloMiner *self;
+ g_autoptr(GError) error = NULL;
+ GList *accounts = NULL;
+ GList *photo_accounts = NULL;
+ GList *l;
+ GoaClient *client = NULL;
+ GrlRegistry *registry;
+
+ client = goa_client_new_finish (res, &error);
+ if (client == NULL)
+ {
+ if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ g_warning ("Failed to create GoaClient: %s", error->message);
+ goto out;
+ }
+
+ self = CC_BACKGROUND_GRILO_MINER (user_data);
+
+ accounts = goa_client_get_accounts (client);
+ for (l = accounts; l != NULL; l = l->next)
+ {
+ GoaObject *goa_object = GOA_OBJECT (l->data);
+ GoaAccount *account;
+ GoaPhotos *photos;
+ const gchar *provider_type;
+
+ account = goa_object_peek_account (goa_object);
+ provider_type = goa_account_get_provider_type (account);
+
+ photos = goa_object_peek_photos (goa_object);
+ if (photos != NULL && g_strcmp0 (provider_type, "flickr") == 0)
+ photo_accounts = g_list_prepend (photo_accounts, g_object_ref (goa_object));
+ }
+
+ if (photo_accounts == NULL)
+ goto out;
+
+ registry = grl_registry_get_default ();
+
+ for (l = photo_accounts; l != NULL; l = l->next)
+ {
+ GoaObject *goa_object = GOA_OBJECT (l->data);
+ GrlSource *source;
+ g_autofree gchar *account_id = NULL;
+
+ account_id = get_grilo_id (goa_object);
+ source = grl_registry_lookup_source (registry, account_id);
+ if (source != NULL)
+ query_online_source (self, source);
+ }
+
+ self->accounts = photo_accounts;
+ photo_accounts = NULL;
+
+ g_signal_connect_object (registry, "source-added", G_CALLBACK (add_online_source_cb), self, G_CONNECT_SWAPPED);
+
+ out:
+ g_list_free_full (photo_accounts, g_object_unref);
+ g_list_free_full (accounts, g_object_unref);
+ g_clear_object (&client);
+}
+
+static void
+setup_online_accounts (CcBackgroundGriloMiner *self)
+{
+ goa_client_new (self->cancellable, client_async_ready, self);
+}
+
+static void
+cc_background_grilo_miner_dispose (GObject *object)
+{
+ CcBackgroundGriloMiner *self = CC_BACKGROUND_GRILO_MINER (object);
+
+ if (self->cancellable)
+ {
+ g_cancellable_cancel (self->cancellable);
+ g_clear_object (&self->cancellable);
+ }
+
+ if (self->accounts)
+ {
+ g_list_free_full (self->accounts, g_object_unref);
+ self->accounts = NULL;
+ }
+
+ G_OBJECT_CLASS (cc_background_grilo_miner_parent_class)->dispose (object);
+}
+
+static void
+cc_background_grilo_miner_init (CcBackgroundGriloMiner *self)
+{
+ self->cancellable = g_cancellable_new ();
+}
+
+static void
+cc_background_grilo_miner_class_init (CcBackgroundGriloMinerClass *klass)
+{
+ g_autoptr(GError) error = NULL;
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GrlRegistry *registry;
+
+ object_class->dispose = cc_background_grilo_miner_dispose;
+
+ signals[MEDIA_FOUND] = g_signal_new ("media-found",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ 0, /* class_offset */
+ NULL, /* accumulator */
+ NULL, /* accu_data */
+ g_cclosure_marshal_VOID__OBJECT,
+ G_TYPE_NONE,
+ 1,
+ GRL_TYPE_MEDIA);
+
+ grl_init (NULL, NULL);
+ registry = grl_registry_get_default ();
+
+ error = NULL;
+ if (!grl_registry_load_all_plugins (registry, FALSE, &error) ||
+ !grl_registry_activate_plugin_by_id (registry, "grl-flickr", &error))
+ g_warning ("%s", error->message);
+}
+
+CcBackgroundGriloMiner *
+cc_background_grilo_miner_new (void)
+{
+ return g_object_new (CC_TYPE_BACKGROUND_GRILO_MINER, NULL);
+}
+
+void
+cc_background_grilo_miner_start (CcBackgroundGriloMiner *self)
+{
+ setup_online_accounts (self);
+}
diff --git a/panels/background/cc-background-grilo-miner.h b/panels/background/cc-background-grilo-miner.h
new file mode 100644
index 0000000..b018129
--- /dev/null
+++ b/panels/background/cc-background-grilo-miner.h
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2014 Red Hat, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define CC_TYPE_BACKGROUND_GRILO_MINER (cc_background_grilo_miner_get_type ())
+G_DECLARE_FINAL_TYPE (CcBackgroundGriloMiner, cc_background_grilo_miner, CC, BACKGROUND_GRILO_MINER, GObject);
+
+CcBackgroundGriloMiner *cc_background_grilo_miner_new (void);
+
+void cc_background_grilo_miner_start (CcBackgroundGriloMiner *self);
+
+G_END_DECLS
diff --git a/panels/background/cc-background-item.c b/panels/background/cc-background-item.c
new file mode 100644
index 0000000..cb6bea2
--- /dev/null
+++ b/panels/background/cc-background-item.c
@@ -0,0 +1,977 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2010-2011 Red Hat, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "config.h"
+
+#include <stdlib.h>
+#include <stdio.h>
+
+#include <gtk/gtk.h>
+#include <gio/gio.h>
+#include <glib/gi18n-lib.h>
+
+#include <libgnome-desktop/gnome-bg.h>
+#include <gdesktop-enums.h>
+
+#include "cc-background-item.h"
+#include "gdesktop-enums-types.h"
+
+struct _CcBackgroundItem
+{
+ GObject parent_instance;
+
+ /* properties */
+ char *name;
+ char *uri;
+ char *size;
+ GDesktopBackgroundStyle placement;
+ GDesktopBackgroundShading shading;
+ char *primary_color;
+ char *secondary_color;
+ char *source_url; /* Used by the Flickr source */
+ char *source_xml; /* Used by the Wallpapers source */
+ gboolean is_deleted;
+ gboolean needs_download;
+ CcBackgroundItemFlags flags;
+ guint64 modified;
+
+ /* internal */
+ GnomeBG *bg;
+ char *mime_type;
+ int width;
+ int height;
+
+ struct {
+ int width;
+ int height;
+ int frame;
+ int scale_factor;
+ GdkPixbuf *thumbnail;
+ } cached_thumbnail;
+};
+
+enum {
+ PROP_0,
+ PROP_NAME,
+ PROP_URI,
+ PROP_PLACEMENT,
+ PROP_SHADING,
+ PROP_PRIMARY_COLOR,
+ PROP_SECONDARY_COLOR,
+ PROP_IS_DELETED,
+ PROP_SOURCE_URL,
+ PROP_SOURCE_XML,
+ PROP_FLAGS,
+ PROP_SIZE,
+ PROP_NEEDS_DOWNLOAD,
+ PROP_MODIFIED
+};
+
+static void cc_background_item_finalize (GObject *object);
+
+G_DEFINE_TYPE (CcBackgroundItem, cc_background_item, G_TYPE_OBJECT)
+
+static void
+set_bg_properties (CcBackgroundItem *item)
+{
+ GdkRGBA pcolor = { 0, 0, 0, 0 };
+ GdkRGBA scolor = { 0, 0, 0, 0 };
+
+ if (item->uri) {
+ g_autoptr(GFile) file = NULL;
+ g_autofree gchar *filename = NULL;
+
+ file = g_file_new_for_commandline_arg (item->uri);
+ filename = g_file_get_path (file);
+ gnome_bg_set_filename (item->bg, filename);
+ }
+
+ if (item->primary_color != NULL) {
+ gdk_rgba_parse (&pcolor, item->primary_color);
+ }
+ if (item->secondary_color != NULL) {
+ gdk_rgba_parse (&scolor, item->secondary_color);
+ }
+
+ gnome_bg_set_rgba (item->bg, item->shading, &pcolor, &scolor);
+ gnome_bg_set_placement (item->bg, item->placement);
+}
+
+
+gboolean
+cc_background_item_changes_with_time (CcBackgroundItem *item)
+{
+ gboolean changes;
+
+ g_return_val_if_fail (CC_IS_BACKGROUND_ITEM (item), FALSE);
+
+ changes = FALSE;
+ if (item->bg != NULL) {
+ changes = gnome_bg_changes_with_time (item->bg);
+ }
+ return changes;
+}
+
+static void
+update_size (CcBackgroundItem *item)
+{
+ g_clear_pointer (&item->size, g_free);
+
+ if (item->uri == NULL) {
+ item->size = g_strdup ("");
+ } else {
+ if (gnome_bg_has_multiple_sizes (item->bg) || gnome_bg_changes_with_time (item->bg)) {
+ item->size = g_strdup (_("multiple sizes"));
+ } else {
+ /* translators: 100 × 100px
+ * Note that this is not an "x", but U+00D7 MULTIPLICATION SIGN */
+ item->size = g_strdup_printf (_("%d × %d"),
+ item->width,
+ item->height);
+ }
+ }
+}
+
+static GdkPixbuf *
+render_at_size (GnomeBG *bg,
+ gint width,
+ gint height)
+{
+ GdkPixbuf *pixbuf;
+
+ pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB, FALSE, 8, width, height);
+#ifdef GNOME_DESKTOP_BG_API_BREAK
+ gnome_bg_draw (bg, pixbuf);
+#else
+ gnome_bg_draw (bg, pixbuf, gdk_screen_get_default (), FALSE);
+#endif
+
+ return pixbuf;
+}
+
+GdkPixbuf *
+cc_background_item_get_frame_thumbnail (CcBackgroundItem *item,
+ GnomeDesktopThumbnailFactory *thumbs,
+ int width,
+ int height,
+ int scale_factor,
+ int frame,
+ gboolean force_size)
+{
+ g_autoptr(GdkPixbuf) pixbuf = NULL;
+ g_autoptr(GdkPixbuf) retval = NULL;
+
+ g_return_val_if_fail (CC_IS_BACKGROUND_ITEM (item), NULL);
+ g_return_val_if_fail (width > 0 && height > 0, NULL);
+
+ /* Use the cached thumbnail if the sizes match */
+ if (item->cached_thumbnail.thumbnail &&
+ item->cached_thumbnail.width == width &&
+ item->cached_thumbnail.height == height &&
+ item->cached_thumbnail.scale_factor == scale_factor &&
+ item->cached_thumbnail.frame == frame)
+ return g_object_ref (item->cached_thumbnail.thumbnail);
+
+ set_bg_properties (item);
+
+ if (force_size) {
+ /* FIXME: this doesn't play nice with slideshow stepping at all,
+ * because it will always render the current slideshow frame, which
+ * might not be what we want.
+ * We're lacking an API to draw a high-res GnomeBG manually choosing
+ * the slideshow frame though, so we can't do much better than this
+ * for now.
+ */
+ pixbuf = render_at_size (item->bg, width, height);
+ } else {
+ if (frame >= 0) {
+ pixbuf = gnome_bg_create_frame_thumbnail (item->bg,
+ thumbs,
+ gdk_screen_get_default (),
+ width,
+ height,
+ frame);
+ } else {
+ pixbuf = gnome_bg_create_thumbnail (item->bg,
+ thumbs,
+ gdk_screen_get_default (),
+ width,
+ height);
+ }
+ }
+
+ retval = g_steal_pointer (&pixbuf);
+
+ gnome_bg_get_image_size (item->bg,
+ thumbs,
+ width,
+ height,
+ &item->width,
+ &item->height);
+
+ update_size (item);
+
+ /* Cache the new thumbnail */
+ g_set_object (&item->cached_thumbnail.thumbnail, retval);
+ item->cached_thumbnail.width = width;
+ item->cached_thumbnail.height = height;
+ item->cached_thumbnail.scale_factor = scale_factor;
+ item->cached_thumbnail.frame = frame;
+
+ return g_steal_pointer (&retval);
+}
+
+
+GdkPixbuf *
+cc_background_item_get_thumbnail (CcBackgroundItem *item,
+ GnomeDesktopThumbnailFactory *thumbs,
+ int width,
+ int height,
+ int scale_factor)
+{
+ return cc_background_item_get_frame_thumbnail (item, thumbs, width, height, scale_factor, -1, FALSE);
+}
+
+static void
+update_info (CcBackgroundItem *item,
+ GFileInfo *_info)
+{
+ g_autoptr(GFileInfo) info = NULL;
+
+ if (_info == NULL) {
+ g_autoptr(GFile) file = NULL;
+
+ file = g_file_new_for_uri (item->uri);
+
+ info = g_file_query_info (file,
+ G_FILE_ATTRIBUTE_STANDARD_NAME ","
+ G_FILE_ATTRIBUTE_STANDARD_SIZE ","
+ G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE ","
+ G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME ","
+ G_FILE_ATTRIBUTE_TIME_MODIFIED,
+ G_FILE_QUERY_INFO_NONE,
+ NULL,
+ NULL);
+ } else {
+ info = g_object_ref (_info);
+ }
+
+ g_clear_pointer (&item->mime_type, g_free);
+
+ if (info == NULL
+ || g_file_info_get_content_type (info) == NULL) {
+ if (item->uri == NULL) {
+ item->mime_type = g_strdup ("image/x-no-data");
+ g_free (item->name);
+ item->name = g_strdup (_("No Desktop Background"));
+ }
+ } else {
+ if (item->name == NULL)
+ item->name = g_strdup (g_file_info_get_display_name (info));
+
+ item->mime_type = g_strdup (g_file_info_get_content_type (info));
+ if (item->modified == 0)
+ item->modified = g_file_info_get_attribute_uint64 (info, G_FILE_ATTRIBUTE_TIME_MODIFIED);
+ }
+}
+
+gboolean
+cc_background_item_load (CcBackgroundItem *item,
+ GFileInfo *info)
+{
+ g_return_val_if_fail (CC_IS_BACKGROUND_ITEM (item), FALSE);
+
+ if (item->uri == NULL)
+ return TRUE;
+
+ update_info (item, info);
+
+ if (item->mime_type != NULL
+ && (g_str_has_prefix (item->mime_type, "image/")
+ || strcmp (item->mime_type, "application/xml") == 0)) {
+ set_bg_properties (item);
+ } else {
+ return FALSE;
+ }
+
+ /* FIXME we should handle XML files as well */
+ if (item->mime_type != NULL &&
+ g_str_has_prefix (item->mime_type, "image/")) {
+ g_autofree gchar *filename = NULL;
+
+ filename = g_filename_from_uri (item->uri, NULL, NULL);
+ gdk_pixbuf_get_file_info (filename,
+ &item->width,
+ &item->height);
+ update_size (item);
+ }
+
+ return TRUE;
+}
+
+static void
+_set_name (CcBackgroundItem *item,
+ const char *value)
+{
+ g_free (item->name);
+ item->name = g_strdup (value);
+}
+
+const char *
+cc_background_item_get_name (CcBackgroundItem *item)
+{
+ g_return_val_if_fail (CC_IS_BACKGROUND_ITEM (item), NULL);
+
+ return item->name;
+}
+
+static void
+_set_uri (CcBackgroundItem *item,
+ const char *value)
+{
+ g_free (item->uri);
+ if (value && *value == '\0') {
+ item->uri = NULL;
+ } else {
+ if (value && strstr (value, "://") == NULL)
+ g_warning ("URI '%s' is invalid", value);
+ item->uri = g_strdup (value);
+ }
+}
+
+const char *
+cc_background_item_get_uri (CcBackgroundItem *item)
+{
+ g_return_val_if_fail (CC_IS_BACKGROUND_ITEM (item), NULL);
+
+ return item->uri;
+}
+
+static void
+_set_placement (CcBackgroundItem *item,
+ GDesktopBackgroundStyle value)
+{
+ item->placement = value;
+}
+
+static void
+_set_shading (CcBackgroundItem *item,
+ GDesktopBackgroundShading value)
+{
+ item->shading = value;
+}
+
+static void
+_set_primary_color (CcBackgroundItem *item,
+ const char *value)
+{
+ g_free (item->primary_color);
+ item->primary_color = g_strdup (value);
+}
+
+const char *
+cc_background_item_get_pcolor (CcBackgroundItem *item)
+{
+ g_return_val_if_fail (CC_IS_BACKGROUND_ITEM (item), NULL);
+
+ return item->primary_color;
+}
+
+static void
+_set_secondary_color (CcBackgroundItem *item,
+ const char *value)
+{
+ g_free (item->secondary_color);
+ item->secondary_color = g_strdup (value);
+}
+
+const char *
+cc_background_item_get_scolor (CcBackgroundItem *item)
+{
+ g_return_val_if_fail (CC_IS_BACKGROUND_ITEM (item), NULL);
+
+ return item->secondary_color;
+}
+
+GDesktopBackgroundStyle
+cc_background_item_get_placement (CcBackgroundItem *item)
+{
+ g_return_val_if_fail (CC_IS_BACKGROUND_ITEM (item), G_DESKTOP_BACKGROUND_STYLE_SCALED);
+
+ return item->placement;
+}
+
+GDesktopBackgroundShading
+cc_background_item_get_shading (CcBackgroundItem *item)
+{
+ g_return_val_if_fail (CC_IS_BACKGROUND_ITEM (item), G_DESKTOP_BACKGROUND_SHADING_SOLID);
+
+ return item->shading;
+}
+
+static void
+_set_is_deleted (CcBackgroundItem *item,
+ gboolean value)
+{
+ item->is_deleted = value;
+}
+
+static void
+_set_source_url (CcBackgroundItem *item,
+ const char *value)
+{
+ g_free (item->source_url);
+ item->source_url = g_strdup (value);
+}
+
+const char *
+cc_background_item_get_source_url (CcBackgroundItem *item)
+{
+ g_return_val_if_fail (CC_IS_BACKGROUND_ITEM (item), NULL);
+
+ return item->source_url;
+}
+
+static void
+_set_source_xml (CcBackgroundItem *item,
+ const char *value)
+{
+ g_free (item->source_xml);
+ item->source_xml = g_strdup (value);
+}
+
+const char *
+cc_background_item_get_source_xml (CcBackgroundItem *item)
+{
+ g_return_val_if_fail (CC_IS_BACKGROUND_ITEM (item), NULL);
+
+ return item->source_xml;
+}
+
+static void
+_set_flags (CcBackgroundItem *item,
+ CcBackgroundItemFlags value)
+{
+ item->flags = value;
+}
+
+CcBackgroundItemFlags
+cc_background_item_get_flags (CcBackgroundItem *item)
+{
+ g_return_val_if_fail (CC_IS_BACKGROUND_ITEM (item), 0);
+
+ return item->flags;
+}
+
+const char *
+cc_background_item_get_size (CcBackgroundItem *item)
+{
+ g_return_val_if_fail (CC_IS_BACKGROUND_ITEM (item), NULL);
+
+ return item->size;
+}
+
+static void
+_set_needs_download (CcBackgroundItem *item,
+ gboolean value)
+{
+ item->needs_download = value;
+}
+
+gboolean
+cc_background_item_get_needs_download (CcBackgroundItem *item)
+{
+ g_return_val_if_fail (CC_IS_BACKGROUND_ITEM (item), 0);
+
+ return item->needs_download;
+}
+
+static void
+_set_modified (CcBackgroundItem *item,
+ guint64 value)
+{
+ item->modified = value;
+}
+
+guint64
+cc_background_item_get_modified (CcBackgroundItem *item)
+{
+ g_return_val_if_fail (CC_IS_BACKGROUND_ITEM (item), 0);
+
+ return item->modified;
+}
+
+static void
+cc_background_item_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ CcBackgroundItem *self;
+
+ self = CC_BACKGROUND_ITEM (object);
+
+ switch (prop_id) {
+ case PROP_NAME:
+ _set_name (self, g_value_get_string (value));
+ break;
+ case PROP_URI:
+ _set_uri (self, g_value_get_string (value));
+ break;
+ case PROP_PLACEMENT:
+ _set_placement (self, g_value_get_enum (value));
+ break;
+ case PROP_SHADING:
+ _set_shading (self, g_value_get_enum (value));
+ break;
+ case PROP_PRIMARY_COLOR:
+ _set_primary_color (self, g_value_get_string (value));
+ break;
+ case PROP_SECONDARY_COLOR:
+ _set_secondary_color (self, g_value_get_string (value));
+ break;
+ case PROP_IS_DELETED:
+ _set_is_deleted (self, g_value_get_boolean (value));
+ break;
+ case PROP_SOURCE_URL:
+ _set_source_url (self, g_value_get_string (value));
+ break;
+ case PROP_SOURCE_XML:
+ _set_source_xml (self, g_value_get_string (value));
+ break;
+ case PROP_FLAGS:
+ _set_flags (self, g_value_get_flags (value));
+ break;
+ case PROP_NEEDS_DOWNLOAD:
+ _set_needs_download (self, g_value_get_boolean (value));
+ break;
+ case PROP_MODIFIED:
+ _set_modified (self, g_value_get_uint64 (value));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+cc_background_item_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ CcBackgroundItem *self;
+
+ self = CC_BACKGROUND_ITEM (object);
+
+ switch (prop_id) {
+ case PROP_NAME:
+ g_value_set_string (value, self->name);
+ break;
+ case PROP_URI:
+ g_value_set_string (value, self->uri);
+ break;
+ case PROP_PLACEMENT:
+ g_value_set_enum (value, self->placement);
+ break;
+ case PROP_SHADING:
+ g_value_set_enum (value, self->shading);
+ break;
+ case PROP_PRIMARY_COLOR:
+ g_value_set_string (value, self->primary_color);
+ break;
+ case PROP_SECONDARY_COLOR:
+ g_value_set_string (value, self->secondary_color);
+ break;
+ case PROP_IS_DELETED:
+ g_value_set_boolean (value, self->is_deleted);
+ break;
+ case PROP_SOURCE_URL:
+ g_value_set_string (value, self->source_url);
+ break;
+ case PROP_SOURCE_XML:
+ g_value_set_string (value, self->source_xml);
+ break;
+ case PROP_FLAGS:
+ g_value_set_flags (value, self->flags);
+ break;
+ case PROP_SIZE:
+ g_value_set_string (value, self->size);
+ break;
+ case PROP_NEEDS_DOWNLOAD:
+ g_value_set_boolean (value, self->needs_download);
+ break;
+ case PROP_MODIFIED:
+ g_value_set_uint64 (value, self->modified);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static GObject *
+cc_background_item_constructor (GType type,
+ guint n_construct_properties,
+ GObjectConstructParam *construct_properties)
+{
+ CcBackgroundItem *background_item;
+
+ background_item = CC_BACKGROUND_ITEM (G_OBJECT_CLASS (cc_background_item_parent_class)->constructor (type,
+ n_construct_properties,
+ construct_properties));
+
+ return G_OBJECT (background_item);
+}
+
+static void
+cc_background_item_class_init (CcBackgroundItemClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->get_property = cc_background_item_get_property;
+ object_class->set_property = cc_background_item_set_property;
+ object_class->constructor = cc_background_item_constructor;
+ object_class->finalize = cc_background_item_finalize;
+
+ g_object_class_install_property (object_class,
+ PROP_NAME,
+ g_param_spec_string ("name",
+ "name",
+ "name",
+ NULL,
+ G_PARAM_READWRITE));
+ g_object_class_install_property (object_class,
+ PROP_URI,
+ g_param_spec_string ("uri",
+ "uri",
+ "uri",
+ NULL,
+ G_PARAM_READWRITE));
+ g_object_class_install_property (object_class,
+ PROP_PLACEMENT,
+ g_param_spec_enum ("placement",
+ "placement",
+ "placement",
+ G_DESKTOP_TYPE_DESKTOP_BACKGROUND_STYLE,
+ G_DESKTOP_BACKGROUND_STYLE_SCALED,
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class,
+ PROP_SHADING,
+ g_param_spec_enum ("shading",
+ "shading",
+ "shading",
+ G_DESKTOP_TYPE_DESKTOP_BACKGROUND_SHADING,
+ G_DESKTOP_BACKGROUND_SHADING_SOLID,
+ G_PARAM_READWRITE));
+ g_object_class_install_property (object_class,
+ PROP_PRIMARY_COLOR,
+ g_param_spec_string ("primary-color",
+ "primary-color",
+ "primary-color",
+ "#000000000000",
+ G_PARAM_READWRITE));
+ g_object_class_install_property (object_class,
+ PROP_SECONDARY_COLOR,
+ g_param_spec_string ("secondary-color",
+ "secondary-color",
+ "secondary-color",
+ "#000000000000",
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class,
+ PROP_IS_DELETED,
+ g_param_spec_boolean ("is-deleted",
+ NULL,
+ NULL,
+ FALSE,
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class,
+ PROP_SOURCE_URL,
+ g_param_spec_string ("source-url",
+ "source-url",
+ "source-url",
+ NULL,
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class,
+ PROP_SOURCE_XML,
+ g_param_spec_string ("source-xml",
+ "source-xml",
+ "source-xml",
+ NULL,
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class,
+ PROP_FLAGS,
+ g_param_spec_flags ("flags",
+ "flags",
+ "flags",
+ G_DESKTOP_TYPE_BACKGROUND_ITEM_FLAGS,
+ 0,
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class,
+ PROP_SIZE,
+ g_param_spec_string ("size",
+ "size",
+ "size",
+ NULL,
+ G_PARAM_READABLE));
+
+ g_object_class_install_property (object_class,
+ PROP_NEEDS_DOWNLOAD,
+ g_param_spec_boolean ("needs-download",
+ NULL,
+ NULL,
+ TRUE,
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class,
+ PROP_MODIFIED,
+ g_param_spec_uint64 ("modified",
+ "modified",
+ NULL,
+ 0,
+ G_MAXUINT64,
+ 0,
+ G_PARAM_READWRITE));
+}
+
+static void
+cc_background_item_init (CcBackgroundItem *item)
+{
+ item->bg = gnome_bg_new ();
+
+ item->shading = G_DESKTOP_BACKGROUND_SHADING_SOLID;
+ item->placement = G_DESKTOP_BACKGROUND_STYLE_SCALED;
+ item->primary_color = g_strdup ("#000000000000");
+ item->secondary_color = g_strdup ("#000000000000");
+ item->needs_download = TRUE;
+ item->flags = 0;
+ item->modified = 0;
+}
+
+static void
+cc_background_item_finalize (GObject *object)
+{
+ CcBackgroundItem *item;
+
+ g_return_if_fail (object != NULL);
+ g_return_if_fail (CC_IS_BACKGROUND_ITEM (object));
+
+ item = CC_BACKGROUND_ITEM (object);
+
+ g_return_if_fail (item != NULL);
+
+ g_clear_object (&item->cached_thumbnail.thumbnail);
+ g_free (item->name);
+ g_free (item->uri);
+ g_free (item->primary_color);
+ g_free (item->secondary_color);
+ g_free (item->mime_type);
+ g_free (item->size);
+ g_free (item->source_url);
+ g_free (item->source_xml);
+
+ if (item->bg != NULL)
+ g_object_unref (item->bg);
+
+ G_OBJECT_CLASS (cc_background_item_parent_class)->finalize (object);
+}
+
+CcBackgroundItem *
+cc_background_item_new (const char *uri)
+{
+ GObject *object;
+
+ object = g_object_new (CC_TYPE_BACKGROUND_ITEM,
+ "uri", uri,
+ NULL);
+
+ return CC_BACKGROUND_ITEM (object);
+}
+
+CcBackgroundItem *
+cc_background_item_copy (CcBackgroundItem *item)
+{
+ CcBackgroundItem *ret;
+
+ ret = cc_background_item_new (item->uri);
+ ret->name = g_strdup (item->name);
+ ret->size = g_strdup (item->size);
+ ret->placement = item->placement;
+ ret->shading = item->shading;
+ ret->primary_color = g_strdup (item->primary_color);
+ ret->secondary_color = g_strdup (item->secondary_color);
+ ret->source_url = g_strdup (item->source_url);
+ ret->source_xml = g_strdup (item->source_xml);
+ ret->is_deleted = item->is_deleted;
+ ret->needs_download = item->needs_download;
+ ret->flags = item->flags;
+
+ return ret;
+}
+
+static const char *
+flags_to_str (CcBackgroundItemFlags flag)
+{
+ GFlagsClass *fclass;
+ GFlagsValue *value;
+
+ fclass = G_FLAGS_CLASS (g_type_class_peek (G_DESKTOP_TYPE_BACKGROUND_ITEM_FLAGS));
+ value = g_flags_get_first_value (fclass, flag);
+
+ g_assert (value);
+
+ return value->value_nick;
+}
+
+static const char *
+enum_to_str (GType type,
+ int v)
+{
+ GEnumClass *eclass;
+ GEnumValue *value;
+
+ eclass = G_ENUM_CLASS (g_type_class_peek (type));
+ value = g_enum_get_value (eclass, v);
+
+ g_assert (value);
+
+ return value->value_nick;
+}
+
+void
+cc_background_item_dump (CcBackgroundItem *item)
+{
+ g_autoptr(GString) flags = NULL;
+ int i;
+
+ g_return_if_fail (CC_IS_BACKGROUND_ITEM (item));
+
+ g_debug ("name:\t\t\t%s", item->name);
+ g_debug ("URI:\t\t\t%s", item->uri ? item->uri : "NULL");
+ if (item->size)
+ g_debug ("size:\t\t\t'%s'", item->size);
+ flags = g_string_new (NULL);
+ for (i = 0; i < 5; i++) {
+ if (item->flags & (1 << i)) {
+ g_string_append (flags, flags_to_str (1 << i));
+ g_string_append_c (flags, ' ');
+ }
+ }
+ if (flags->len == 0)
+ g_string_append (flags, "-none-");
+ g_debug ("flags:\t\t\t%s", flags->str);
+ if (item->primary_color)
+ g_debug ("pcolor:\t\t\t%s", item->primary_color);
+ if (item->secondary_color)
+ g_debug ("scolor:\t\t\t%s", item->secondary_color);
+ g_debug ("placement:\t\t%s", enum_to_str (G_DESKTOP_TYPE_DESKTOP_BACKGROUND_STYLE, item->placement));
+ g_debug ("shading:\t\t%s", enum_to_str (G_DESKTOP_TYPE_DESKTOP_BACKGROUND_SHADING, item->shading));
+ if (item->source_url)
+ g_debug ("source URL:\t\t%s", item->source_url);
+ if (item->source_xml)
+ g_debug ("source XML:\t\t%s", item->source_xml);
+ g_debug ("deleted:\t\t%s", item->is_deleted ? "yes" : "no");
+ if (item->mime_type)
+ g_debug ("mime-type:\t\t%s", item->mime_type);
+ g_debug ("dimensions:\t\t%d x %d", item->width, item->height);
+ g_debug ("modified: %"G_GUINT64_FORMAT, item->modified);
+ g_debug (" ");
+}
+
+static gboolean
+files_equal (const char *a,
+ const char *b)
+{
+ g_autoptr(GFile) file1 = NULL;
+ g_autoptr(GFile) file2 = NULL;
+ gboolean retval;
+
+ if (a == NULL &&
+ b == NULL)
+ return TRUE;
+
+ if (a == NULL ||
+ b == NULL)
+ return FALSE;
+
+ file1 = g_file_new_for_commandline_arg (a);
+ file2 = g_file_new_for_commandline_arg (b);
+ if (g_file_equal (file1, file2) == FALSE)
+ retval = FALSE;
+ else
+ retval = TRUE;
+
+ return retval;
+}
+
+static gboolean
+colors_equal (const char *a,
+ const char *b)
+{
+ GdkRGBA color1, color2;
+
+ gdk_rgba_parse (&color1, a);
+ gdk_rgba_parse (&color2, b);
+
+ return gdk_rgba_equal (&color1, &color2);
+}
+
+gboolean
+cc_background_item_compare (CcBackgroundItem *saved,
+ CcBackgroundItem *configured)
+{
+ CcBackgroundItemFlags flags;
+
+ flags = saved->flags;
+ if (flags == 0)
+ return FALSE;
+
+ if (flags & CC_BACKGROUND_ITEM_HAS_URI) {
+ if (files_equal (saved->uri, configured->uri) == FALSE)
+ return FALSE;
+ }
+ if (flags & CC_BACKGROUND_ITEM_HAS_SHADING) {
+ if (saved->shading != configured->shading)
+ return FALSE;
+ }
+ if (flags & CC_BACKGROUND_ITEM_HAS_PLACEMENT) {
+ if (saved->placement != configured->placement)
+ return FALSE;
+ }
+ if (flags & CC_BACKGROUND_ITEM_HAS_PCOLOR) {
+ if (colors_equal (saved->primary_color,
+ configured->primary_color) == FALSE) {
+ return FALSE;
+ }
+ }
+ if (flags & CC_BACKGROUND_ITEM_HAS_SCOLOR) {
+ if (colors_equal (saved->secondary_color,
+ configured->secondary_color) == FALSE) {
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
diff --git a/panels/background/cc-background-item.h b/panels/background/cc-background-item.h
new file mode 100644
index 0000000..b7b8df6
--- /dev/null
+++ b/panels/background/cc-background-item.h
@@ -0,0 +1,83 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2010-2011 Red Hat, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#pragma once
+
+#include <glib-object.h>
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <libgnome-desktop/gnome-desktop-thumbnail.h>
+#include <gdesktop-enums.h>
+#include <libgnome-desktop/gnome-bg.h>
+
+G_BEGIN_DECLS
+
+#define CC_TYPE_BACKGROUND_ITEM (cc_background_item_get_type ())
+G_DECLARE_FINAL_TYPE (CcBackgroundItem, cc_background_item, CC, BACKGROUND_ITEM, GObject)
+
+typedef enum {
+ CC_BACKGROUND_ITEM_HAS_SHADING = 1 << 0,
+ CC_BACKGROUND_ITEM_HAS_PLACEMENT = 1 << 1,
+ CC_BACKGROUND_ITEM_HAS_PCOLOR = 1 << 2,
+ CC_BACKGROUND_ITEM_HAS_SCOLOR = 1 << 3,
+ CC_BACKGROUND_ITEM_HAS_URI = 1 << 4
+} CcBackgroundItemFlags;
+
+#define CC_BACKGROUND_ITEM_HAS_ALL (CC_BACKGROUND_ITEM_HAS_SHADING & \
+ CC_BACKGROUND_ITEM_HAS_PLACEMENT & \
+ CC_BACKGROUND_ITEM_HAS_PCOLOR & \
+ CC_BACKGROUND_ITEM_HAS_SCOLOR & \
+ CC_BACKGROUND_ITEM_HAS_FNAME)
+
+CcBackgroundItem * cc_background_item_new (const char *uri);
+CcBackgroundItem * cc_background_item_copy (CcBackgroundItem *item);
+gboolean cc_background_item_load (CcBackgroundItem *item,
+ GFileInfo *info);
+gboolean cc_background_item_changes_with_time (CcBackgroundItem *item);
+
+GdkPixbuf * cc_background_item_get_thumbnail (CcBackgroundItem *item,
+ GnomeDesktopThumbnailFactory *thumbs,
+ int width,
+ int height,
+ int scale_factor);
+GdkPixbuf * cc_background_item_get_frame_thumbnail (CcBackgroundItem *item,
+ GnomeDesktopThumbnailFactory *thumbs,
+ int width,
+ int height,
+ int scale_factor,
+ int frame,
+ gboolean force_size);
+
+GDesktopBackgroundStyle cc_background_item_get_placement (CcBackgroundItem *item);
+GDesktopBackgroundShading cc_background_item_get_shading (CcBackgroundItem *item);
+const char * cc_background_item_get_uri (CcBackgroundItem *item);
+const char * cc_background_item_get_source_url (CcBackgroundItem *item);
+const char * cc_background_item_get_source_xml (CcBackgroundItem *item);
+CcBackgroundItemFlags cc_background_item_get_flags (CcBackgroundItem *item);
+const char * cc_background_item_get_pcolor (CcBackgroundItem *item);
+const char * cc_background_item_get_scolor (CcBackgroundItem *item);
+const char * cc_background_item_get_name (CcBackgroundItem *item);
+const char * cc_background_item_get_size (CcBackgroundItem *item);
+gboolean cc_background_item_get_needs_download (CcBackgroundItem *item);
+guint64 cc_background_item_get_modified (CcBackgroundItem *item);
+
+gboolean cc_background_item_compare (CcBackgroundItem *saved,
+ CcBackgroundItem *configured);
+void cc_background_item_dump (CcBackgroundItem *item);
+
+G_END_DECLS
diff --git a/panels/background/cc-background-panel.c b/panels/background/cc-background-panel.c
new file mode 100644
index 0000000..29dedf1
--- /dev/null
+++ b/panels/background/cc-background-panel.c
@@ -0,0 +1,314 @@
+/*
+ * Copyright (C) 2010 Intel, Inc
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * Author: Thomas Wood <thomas.wood@intel.com>
+ *
+ */
+
+#include <config.h>
+
+#include <string.h>
+#include <glib.h>
+#include <glib/gi18n-lib.h>
+#include <glib/gstdio.h>
+
+#include <gdesktop-enums.h>
+
+#include "cc-background-panel.h"
+
+#include "cc-background-chooser.h"
+#include "cc-background-item.h"
+#include "cc-background-preview.h"
+#include "cc-background-resources.h"
+#include "cc-background-xml.h"
+
+#include "bg-pictures-source.h"
+
+#define WP_PATH_ID "org.gnome.desktop.background"
+#define WP_LOCK_PATH_ID "org.gnome.desktop.screensaver"
+#define WP_URI_KEY "picture-uri"
+#define WP_OPTIONS_KEY "picture-options"
+#define WP_SHADING_KEY "color-shading-type"
+#define WP_PCOLOR_KEY "primary-color"
+#define WP_SCOLOR_KEY "secondary-color"
+
+struct _CcBackgroundPanel
+{
+ CcPanel parent_instance;
+
+ GDBusConnection *connection;
+
+ GSettings *settings;
+ GSettings *lock_settings;
+
+ GnomeDesktopThumbnailFactory *thumb_factory;
+
+ CcBackgroundItem *current_background;
+
+ CcBackgroundChooser *background_chooser;
+ GtkButton *add_picture_button;
+ CcBackgroundPreview *desktop_preview;
+};
+
+CC_PANEL_REGISTER (CcBackgroundPanel, cc_background_panel)
+
+static void
+update_preview (CcBackgroundPanel *panel)
+{
+ CcBackgroundItem *current_background;
+
+ current_background = panel->current_background;
+ cc_background_preview_set_item (panel->desktop_preview, current_background);
+}
+
+static gchar *
+get_save_path (void)
+{
+ return g_build_filename (g_get_user_config_dir (),
+ "gnome-control-center",
+ "backgrounds",
+ "last-edited.xml",
+ NULL);
+}
+
+static void
+reload_current_bg (CcBackgroundPanel *panel)
+{
+ g_autoptr(CcBackgroundItem) saved = NULL;
+ CcBackgroundItem *configured;
+ GSettings *settings = NULL;
+ g_autofree gchar *uri = NULL;
+ g_autofree gchar *pcolor = NULL;
+ g_autofree gchar *scolor = NULL;
+
+ /* Load the saved configuration */
+ uri = get_save_path ();
+ saved = cc_background_xml_get_item (uri);
+
+ /* initalise the current background information from settings */
+ settings = panel->settings;
+ uri = g_settings_get_string (settings, WP_URI_KEY);
+ if (uri && *uri == '\0')
+ g_clear_pointer (&uri, g_free);
+
+ configured = cc_background_item_new (uri);
+
+ pcolor = g_settings_get_string (settings, WP_PCOLOR_KEY);
+ scolor = g_settings_get_string (settings, WP_SCOLOR_KEY);
+ g_object_set (G_OBJECT (configured),
+ "name", _("Current background"),
+ "placement", g_settings_get_enum (settings, WP_OPTIONS_KEY),
+ "shading", g_settings_get_enum (settings, WP_SHADING_KEY),
+ "primary-color", pcolor,
+ "secondary-color", scolor,
+ NULL);
+
+ if (saved != NULL && cc_background_item_compare (saved, configured))
+ {
+ CcBackgroundItemFlags flags;
+ flags = cc_background_item_get_flags (saved);
+ /* Special case for colours */
+ if (cc_background_item_get_placement (saved) == G_DESKTOP_BACKGROUND_STYLE_NONE)
+ flags &=~ (CC_BACKGROUND_ITEM_HAS_PCOLOR | CC_BACKGROUND_ITEM_HAS_SCOLOR);
+ g_object_set (G_OBJECT (configured),
+ "name", cc_background_item_get_name (saved),
+ "flags", flags,
+ "source-url", cc_background_item_get_source_url (saved),
+ "source-xml", cc_background_item_get_source_xml (saved),
+ NULL);
+ }
+
+ g_clear_object (&panel->current_background);
+ panel->current_background = configured;
+ cc_background_item_load (configured, NULL);
+}
+
+static gboolean
+create_save_dir (void)
+{
+ g_autofree char *path = NULL;
+
+ path = g_build_filename (g_get_user_config_dir (),
+ "gnome-control-center",
+ "backgrounds",
+ NULL);
+ if (g_mkdir_with_parents (path, USER_DIR_MODE) < 0)
+ {
+ g_warning ("Failed to create directory '%s'", path);
+ return FALSE;
+ }
+ return TRUE;
+}
+
+static void
+set_background (CcBackgroundPanel *panel,
+ GSettings *settings,
+ CcBackgroundItem *item)
+{
+ GDesktopBackgroundStyle style;
+ CcBackgroundItemFlags flags;
+ g_autofree gchar *filename = NULL;
+ const char *uri;
+
+ if (item == NULL)
+ return;
+
+ uri = cc_background_item_get_uri (item);
+ flags = cc_background_item_get_flags (item);
+
+ g_settings_set_string (settings, WP_URI_KEY, uri);
+
+ /* Also set the placement if we have a URI and the previous value was none */
+ if (flags & CC_BACKGROUND_ITEM_HAS_PLACEMENT)
+ {
+ g_settings_set_enum (settings, WP_OPTIONS_KEY, cc_background_item_get_placement (item));
+ }
+ else if (uri != NULL)
+ {
+ style = g_settings_get_enum (settings, WP_OPTIONS_KEY);
+ if (style == G_DESKTOP_BACKGROUND_STYLE_NONE)
+ g_settings_set_enum (settings, WP_OPTIONS_KEY, cc_background_item_get_placement (item));
+ }
+
+ if (flags & CC_BACKGROUND_ITEM_HAS_SHADING)
+ g_settings_set_enum (settings, WP_SHADING_KEY, cc_background_item_get_shading (item));
+
+ g_settings_set_string (settings, WP_PCOLOR_KEY, cc_background_item_get_pcolor (item));
+ g_settings_set_string (settings, WP_SCOLOR_KEY, cc_background_item_get_scolor (item));
+
+ /* Apply all changes */
+ g_settings_apply (settings);
+
+ /* Save the source XML if there is one */
+ filename = get_save_path ();
+ if (create_save_dir ())
+ cc_background_xml_save (panel->current_background, filename);
+}
+
+
+static void
+on_chooser_background_chosen_cb (CcBackgroundPanel *self,
+ CcBackgroundItem *item)
+{
+ set_background (self, self->settings, item);
+ set_background (self, self->lock_settings, item);
+}
+
+static void
+on_add_picture_button_clicked_cb (CcBackgroundPanel *self)
+{
+ cc_background_chooser_select_file (self->background_chooser);
+}
+
+static const char *
+cc_background_panel_get_help_uri (CcPanel *panel)
+{
+ return "help:gnome-help/look-background";
+}
+
+static void
+cc_background_panel_constructed (GObject *object)
+{
+ CcBackgroundPanel *self;
+ CcShell *shell;
+
+ self = CC_BACKGROUND_PANEL (object);
+ shell = cc_panel_get_shell (CC_PANEL (self));
+
+ cc_shell_embed_widget_in_header (shell, GTK_WIDGET (self->add_picture_button), GTK_POS_RIGHT);
+
+ G_OBJECT_CLASS (cc_background_panel_parent_class)->constructed (object);
+}
+
+static void
+cc_background_panel_dispose (GObject *object)
+{
+ CcBackgroundPanel *panel = CC_BACKGROUND_PANEL (object);
+
+ g_clear_object (&panel->settings);
+ g_clear_object (&panel->lock_settings);
+ g_clear_object (&panel->thumb_factory);
+
+ G_OBJECT_CLASS (cc_background_panel_parent_class)->dispose (object);
+}
+
+static void
+cc_background_panel_finalize (GObject *object)
+{
+ CcBackgroundPanel *panel = CC_BACKGROUND_PANEL (object);
+
+ g_clear_object (&panel->current_background);
+
+ G_OBJECT_CLASS (cc_background_panel_parent_class)->finalize (object);
+}
+
+static void
+cc_background_panel_class_init (CcBackgroundPanelClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ CcPanelClass *panel_class = CC_PANEL_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+ g_type_ensure (CC_TYPE_BACKGROUND_CHOOSER);
+ g_type_ensure (CC_TYPE_BACKGROUND_PREVIEW);
+
+ panel_class->get_help_uri = cc_background_panel_get_help_uri;
+
+ object_class->constructed = cc_background_panel_constructed;
+ object_class->dispose = cc_background_panel_dispose;
+ object_class->finalize = cc_background_panel_finalize;
+
+ gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/control-center/background/cc-background-panel.ui");
+
+ gtk_widget_class_bind_template_child (widget_class, CcBackgroundPanel, add_picture_button);
+ gtk_widget_class_bind_template_child (widget_class, CcBackgroundPanel, background_chooser);
+ gtk_widget_class_bind_template_child (widget_class, CcBackgroundPanel, desktop_preview);
+
+ gtk_widget_class_bind_template_callback (widget_class, on_chooser_background_chosen_cb);
+ gtk_widget_class_bind_template_callback (widget_class, on_add_picture_button_clicked_cb);
+}
+
+static void
+on_settings_changed (CcBackgroundPanel *panel)
+{
+ reload_current_bg (panel);
+ update_preview (panel);
+}
+
+static void
+cc_background_panel_init (CcBackgroundPanel *panel)
+{
+ g_resources_register (cc_background_get_resource ());
+
+ gtk_widget_init_template (GTK_WIDGET (panel));
+
+ panel->connection = g_application_get_dbus_connection (g_application_get_default ());
+
+ panel->thumb_factory = gnome_desktop_thumbnail_factory_new (GNOME_DESKTOP_THUMBNAIL_SIZE_LARGE);
+
+ panel->settings = g_settings_new (WP_PATH_ID);
+ g_settings_delay (panel->settings);
+
+ panel->lock_settings = g_settings_new (WP_LOCK_PATH_ID);
+ g_settings_delay (panel->lock_settings);
+
+ /* Load the background */
+ reload_current_bg (panel);
+ update_preview (panel);
+
+ /* Background settings */
+ g_signal_connect_object (panel->settings, "changed", G_CALLBACK (on_settings_changed), panel, G_CONNECT_SWAPPED);
+}
diff --git a/panels/background/cc-background-panel.h b/panels/background/cc-background-panel.h
new file mode 100644
index 0000000..abc894a
--- /dev/null
+++ b/panels/background/cc-background-panel.h
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2010 Intel, Inc
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * Author: Thomas Wood <thomas.wood@intel.com>
+ *
+ */
+
+#pragma once
+
+#include <shell/cc-panel.h>
+
+G_BEGIN_DECLS
+
+#define CC_TYPE_BACKGROUND_PANEL (cc_background_panel_get_type ())
+G_DECLARE_FINAL_TYPE (CcBackgroundPanel, cc_background_panel, CC, BACKGROUND_PANEL, CcPanel)
+
+G_END_DECLS
diff --git a/panels/background/cc-background-panel.ui b/panels/background/cc-background-panel.ui
new file mode 100644
index 0000000..7b5c3d3
--- /dev/null
+++ b/panels/background/cc-background-panel.ui
@@ -0,0 +1,58 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <!-- interface-requires gtk+ 3.0 -->
+ <template class="CcBackgroundPanel" parent="CcPanel">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkBox">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+
+ <child>
+ <object class="HdyClamp">
+ <property name="visible">1</property>
+ <property name="maximum_size">300</property>
+ <property name="tightening_threshold">200</property>
+ <child>
+ <object class="GtkBox">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="spacing">24</property>
+ <property name="margin-start">12</property>
+ <property name="margin-end">12</property>
+ <property name="margin-top">18</property>
+ <property name="margin-bottom">18</property>
+ <property name="hexpand">True</property>
+ <child>
+ <object class="CcBackgroundPreview" id="desktop_preview">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="valign">center</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+
+ <child>
+ <object class="CcBackgroundChooser" id="background_chooser">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="expand">True</property>
+ <signal name="background-chosen" handler="on_chooser_background_chosen_cb" object="CcBackgroundPanel" swapped="yes" />
+ </object>
+ </child>
+ </object>
+ </child>
+ </template>
+
+ <!-- Header button -->
+ <object class="GtkButton" id="add_picture_button">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Add Picture…</property>
+ <signal name="clicked" handler="on_add_picture_button_clicked_cb" object="CcBackgroundPanel" swapped="yes" />
+ </object>
+</interface>
diff --git a/panels/background/cc-background-preview.c b/panels/background/cc-background-preview.c
new file mode 100644
index 0000000..9fef9be
--- /dev/null
+++ b/panels/background/cc-background-preview.c
@@ -0,0 +1,382 @@
+/* cc-background-preview.c
+ *
+ * Copyright 2019 Georges Basile Stavracas Neto <georges.stavracas@gmail.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#include <libgnome-desktop/gnome-desktop-thumbnail.h>
+
+#include "cc-background-preview.h"
+
+struct _CcBackgroundPreview
+{
+ GtkBox parent;
+
+ GtkImage *animated_background_icon;
+ GtkLabel *desktop_clock_label;
+ GtkFrame *desktop_frame;
+ GtkDrawingArea *drawing_area;
+ GtkFrame *lock_frame;
+ GtkLabel *lock_screen_label;
+ GtkStack *stack;
+
+ GnomeDesktopThumbnailFactory *thumbnail_factory;
+
+ CcBackgroundItem *item;
+ GSettings *desktop_settings;
+
+ guint lock_screen_time_timeout_id;
+ gboolean is_lock_screen;
+ GDateTime *previous_time;
+ gboolean is_24h_format;
+};
+
+G_DEFINE_TYPE (CcBackgroundPreview, cc_background_preview, GTK_TYPE_BOX)
+
+enum
+{
+ PROP_0,
+ PROP_IS_LOCK_SCREEN,
+ PROP_ITEM,
+ N_PROPS
+};
+
+static GParamSpec *properties [N_PROPS];
+
+/* Auxiliary methods */
+
+static void
+update_lock_screen_label (CcBackgroundPreview *self,
+ gboolean force)
+{
+ g_autoptr(GDateTime) now = NULL;
+ g_autofree gchar *label = NULL;
+
+ now = g_date_time_new_now_local ();
+
+ /* Don't update the label if the hour:minute pair did not change */
+ if (!force && self->previous_time &&
+ g_date_time_get_hour (now) == g_date_time_get_hour (self->previous_time) &&
+ g_date_time_get_minute (now) == g_date_time_get_minute (self->previous_time))
+ {
+ return;
+ }
+
+ if (self->is_24h_format)
+ label = g_date_time_format (now, "%R");
+ else
+ label = g_date_time_format (now, "%I:%M %p");
+
+ gtk_label_set_label (self->lock_screen_label, label);
+ gtk_label_set_label (self->desktop_clock_label, label);
+
+ g_clear_pointer (&self->previous_time, g_date_time_unref);
+ self->previous_time = g_steal_pointer (&now);
+}
+
+static void
+update_clock_format (CcBackgroundPreview *self)
+{
+ g_autofree gchar *clock_format = NULL;
+ gboolean is_24h_format;
+
+ clock_format = g_settings_get_string (self->desktop_settings, "clock-format");
+ is_24h_format = g_strcmp0 (clock_format, "24h") == 0;
+
+ if (is_24h_format != self->is_24h_format)
+ {
+ self->is_24h_format = is_24h_format;
+ update_lock_screen_label (self, TRUE);
+ }
+}
+
+static void
+load_custom_css (CcBackgroundPreview *self)
+{
+ g_autoptr(GtkCssProvider) provider = NULL;
+
+ /* use custom CSS */
+ provider = gtk_css_provider_new ();
+ gtk_css_provider_load_from_resource (provider, "/org/gnome/control-center/background/preview.css");
+ gtk_style_context_add_provider_for_screen (gdk_screen_get_default (),
+ GTK_STYLE_PROVIDER (provider),
+ GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
+
+}
+
+static gboolean
+update_clock_cb (gpointer data)
+{
+ CcBackgroundPreview *self = data;
+
+ update_lock_screen_label (self, FALSE);
+
+ return G_SOURCE_CONTINUE;
+}
+
+static void
+start_monitor_time (CcBackgroundPreview *self)
+{
+ if (self->lock_screen_time_timeout_id > 0)
+ return;
+
+ self->lock_screen_time_timeout_id = g_timeout_add_seconds (1,
+ update_clock_cb,
+ self);
+}
+
+static void
+stop_monitor_time (CcBackgroundPreview *self)
+{
+ if (self->lock_screen_time_timeout_id > 0)
+ {
+ g_source_remove (self->lock_screen_time_timeout_id);
+ self->lock_screen_time_timeout_id = 0;
+ }
+}
+
+
+/* Callbacks */
+
+static gboolean
+on_preview_draw_cb (CcBackgroundPreview *self,
+ cairo_t *cr)
+{
+ g_autoptr(GdkPixbuf) pixbuf = NULL;
+ GtkAllocation allocation;
+ gint scale_factor;
+
+ if (!self->item)
+ return FALSE;
+
+ scale_factor = gtk_widget_get_scale_factor (GTK_WIDGET (self->drawing_area));
+ gtk_widget_get_allocation (GTK_WIDGET (self->drawing_area), &allocation);
+ pixbuf = cc_background_item_get_frame_thumbnail (self->item,
+ self->thumbnail_factory,
+ allocation.width,
+ allocation.height,
+ scale_factor,
+ 0,
+ TRUE);
+
+
+ gdk_cairo_set_source_pixbuf (cr, pixbuf, 0, 0);
+ cairo_paint (cr);
+
+ return TRUE;
+}
+
+/* GObject overrides */
+
+static void
+cc_background_preview_finalize (GObject *object)
+{
+ CcBackgroundPreview *self = (CcBackgroundPreview *)object;
+
+ g_clear_object (&self->desktop_settings);
+ g_clear_object (&self->item);
+ g_clear_object (&self->thumbnail_factory);
+
+ g_clear_pointer (&self->previous_time, g_date_time_unref);
+
+ stop_monitor_time (self);
+
+ G_OBJECT_CLASS (cc_background_preview_parent_class)->finalize (object);
+}
+
+static void
+cc_background_preview_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ CcBackgroundPreview *self = CC_BACKGROUND_PREVIEW (object);
+
+ switch (prop_id)
+ {
+ case PROP_IS_LOCK_SCREEN:
+ g_value_set_boolean (value, self->is_lock_screen);
+ break;
+
+ case PROP_ITEM:
+ g_value_set_object (value, self->item);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+cc_background_preview_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ CcBackgroundPreview *self = CC_BACKGROUND_PREVIEW (object);
+
+ switch (prop_id)
+ {
+ case PROP_IS_LOCK_SCREEN:
+ self->is_lock_screen = g_value_get_boolean (value);
+ gtk_stack_set_visible_child (self->stack,
+ self->is_lock_screen ? GTK_WIDGET (self->lock_frame) : GTK_WIDGET (self->desktop_frame));
+ break;
+
+ case PROP_ITEM:
+ cc_background_preview_set_item (self, g_value_get_object (value));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static GtkSizeRequestMode
+cc_background_preview_get_request_mode (GtkWidget *widget)
+{
+ return GTK_SIZE_REQUEST_HEIGHT_FOR_WIDTH;
+}
+
+static gfloat
+get_primary_monitor_aspect_ratio (void)
+{
+ GdkDisplay *display;
+ GdkMonitor *primary_monitor;
+ gfloat aspect_ratio;
+
+ display = gdk_display_get_default ();
+ primary_monitor = gdk_display_get_primary_monitor (display);
+ aspect_ratio = 16.0 / 9.0;
+
+ if (primary_monitor)
+ {
+ GdkRectangle monitor_layout;
+ gdk_monitor_get_geometry (primary_monitor, &monitor_layout);
+ aspect_ratio = monitor_layout.width / (gfloat) monitor_layout.height;
+ }
+
+ return aspect_ratio;
+}
+
+static void
+cc_background_preview_get_preferred_height_for_width (GtkWidget *widget,
+ gint width,
+ gint *minimum,
+ gint *natural)
+{
+ gfloat aspect_ratio = get_primary_monitor_aspect_ratio ();
+
+ *minimum = *natural = MAX (2, width / aspect_ratio);
+}
+
+static void
+cc_background_preview_get_preferred_width_for_height (GtkWidget *widget,
+ gint height,
+ gint *minimum,
+ gint *natural)
+{
+ gfloat aspect_ratio = get_primary_monitor_aspect_ratio ();
+
+ *minimum = *natural = MAX (2, height * aspect_ratio);
+}
+
+static void
+cc_background_preview_class_init (CcBackgroundPreviewClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+ object_class->finalize = cc_background_preview_finalize;
+ object_class->get_property = cc_background_preview_get_property;
+ object_class->set_property = cc_background_preview_set_property;
+
+ widget_class->get_request_mode = cc_background_preview_get_request_mode;
+ widget_class->get_preferred_height_for_width = cc_background_preview_get_preferred_height_for_width;
+ widget_class->get_preferred_width_for_height = cc_background_preview_get_preferred_width_for_height;
+
+ properties[PROP_IS_LOCK_SCREEN] = g_param_spec_boolean ("is-lock-screen",
+ "Lock screen",
+ "Whether the preview is of the lock screen",
+ FALSE,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+
+ properties[PROP_ITEM] = g_param_spec_object ("item",
+ "Item",
+ "Background item",
+ CC_TYPE_BACKGROUND_ITEM,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+
+ g_object_class_install_properties (object_class, N_PROPS, properties);
+
+ gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/control-center/background/cc-background-preview.ui");
+
+ gtk_widget_class_bind_template_child (widget_class, CcBackgroundPreview, animated_background_icon);
+ gtk_widget_class_bind_template_child (widget_class, CcBackgroundPreview, desktop_clock_label);
+ gtk_widget_class_bind_template_child (widget_class, CcBackgroundPreview, desktop_frame);
+ gtk_widget_class_bind_template_child (widget_class, CcBackgroundPreview, drawing_area);
+ gtk_widget_class_bind_template_child (widget_class, CcBackgroundPreview, lock_frame);
+ gtk_widget_class_bind_template_child (widget_class, CcBackgroundPreview, lock_screen_label);
+ gtk_widget_class_bind_template_child (widget_class, CcBackgroundPreview, stack);
+
+ gtk_widget_class_bind_template_callback (widget_class, on_preview_draw_cb);
+}
+
+static void
+cc_background_preview_init (CcBackgroundPreview *self)
+{
+ gtk_widget_init_template (GTK_WIDGET (self));
+
+ self->thumbnail_factory = gnome_desktop_thumbnail_factory_new (GNOME_DESKTOP_THUMBNAIL_SIZE_LARGE);
+ self->desktop_settings = g_settings_new ("org.gnome.desktop.interface");
+
+ g_signal_connect_object (self->desktop_settings,
+ "changed::clock-format",
+ G_CALLBACK (update_clock_format),
+ self,
+ G_CONNECT_SWAPPED);
+
+ update_clock_format (self);
+ load_custom_css (self);
+ start_monitor_time (self);
+}
+
+CcBackgroundItem*
+cc_background_preview_get_item (CcBackgroundPreview *self)
+{
+ g_return_val_if_fail (CC_IS_BACKGROUND_PREVIEW (self), NULL);
+
+ return self->item;
+}
+
+void
+cc_background_preview_set_item (CcBackgroundPreview *self,
+ CcBackgroundItem *item)
+{
+ g_return_if_fail (CC_IS_BACKGROUND_PREVIEW (self));
+ g_return_if_fail (CC_IS_BACKGROUND_ITEM (item));
+
+ if (!g_set_object (&self->item, item))
+ return;
+
+ gtk_widget_set_visible (GTK_WIDGET (self->animated_background_icon),
+ cc_background_item_changes_with_time (item));
+
+ gtk_widget_queue_draw (GTK_WIDGET (self->drawing_area));
+
+ g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_ITEM]);
+}
diff --git a/panels/background/cc-background-preview.h b/panels/background/cc-background-preview.h
new file mode 100644
index 0000000..e8e20d4
--- /dev/null
+++ b/panels/background/cc-background-preview.h
@@ -0,0 +1,36 @@
+/* cc-background-preview.h
+ *
+ * Copyright 2019 Georges Basile Stavracas Neto <georges.stavracas@gmail.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#pragma once
+
+#include <gtk/gtk.h>
+
+#include "cc-background-item.h"
+
+G_BEGIN_DECLS
+
+#define CC_TYPE_BACKGROUND_PREVIEW (cc_background_preview_get_type())
+G_DECLARE_FINAL_TYPE (CcBackgroundPreview, cc_background_preview, CC, BACKGROUND_PREVIEW, GtkBox)
+
+CcBackgroundItem* cc_background_preview_get_item (CcBackgroundPreview *self);
+void cc_background_preview_set_item (CcBackgroundPreview *self,
+ CcBackgroundItem *item);
+
+G_END_DECLS
diff --git a/panels/background/cc-background-preview.ui b/panels/background/cc-background-preview.ui
new file mode 100644
index 0000000..30f7902
--- /dev/null
+++ b/panels/background/cc-background-preview.ui
@@ -0,0 +1,152 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <!-- interface-requires gtk+ 3.0 -->
+ <template class="CcBackgroundPreview" parent="GtkBox">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="hexpand">True</property>
+ <property name="vexpand">False</property>
+ <style>
+ <class name="frame" />
+ </style>
+
+ <child>
+ <object class="GtkOverlay">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="hexpand">True</property>
+
+ <!-- Wallpaper -->
+ <child>
+ <object class="GtkDrawingArea" id="drawing_area">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="expand">True</property>
+ <signal name="draw" handler="on_preview_draw_cb" object="CcBackgroundPreview" swapped="yes" />
+ </object>
+ </child>
+
+ <!-- Desktop / Lock Screen widgets -->
+ <child type="overlay">
+ <object class="GtkStack" id="stack">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="expand">True</property>
+
+ <child>
+ <object class="GtkFrame" id="desktop_frame">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="shadow-type">none</property>
+ <property name="valign">start</property>
+ <style>
+ <class name="desktop-preview" />
+ </style>
+
+ <child>
+ <object class="GtkBox">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="label" translatable="yes">Activities</property>
+ </object>
+ </child>
+
+ <child type="center">
+ <object class="GtkLabel" id="desktop_clock_label">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ </object>
+ </child>
+
+ <child>
+ <object class="GtkBox">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="spacing">4</property>
+
+ <child>
+ <object class="GtkImage">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="icon-name">network-wireless-symbolic</property>
+ <property name="pixel-size">6</property>
+ </object>
+ </child>
+
+ <child>
+ <object class="GtkImage">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="icon-name">audio-volume-high-symbolic</property>
+ <property name="pixel-size">6</property>
+ </object>
+ </child>
+
+ <child>
+ <object class="GtkImage">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="icon-name">battery-low-symbolic</property>
+ <property name="pixel-size">6</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="pack-type">end</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+
+ </object>
+ </child>
+
+
+ <child>
+ <object class="GtkFrame" id="lock_frame">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="shadow-type">none</property>
+ <child>
+ <object class="GtkLabel" id="lock_screen_label">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="expand">True</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ </object>
+ </child>
+ <style>
+ <class name="lock-screen-preview" />
+ </style>
+ </object>
+ </child>
+ </object>
+ </child>
+
+ <!-- Wallpaper -->
+ <child type="overlay">
+ <object class="GtkImage" id="animated_background_icon">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="halign">end</property>
+ <property name="valign">end</property>
+ <property name="margin-end">8</property>
+ <property name="margin-bottom">8</property>
+ <property name="pixel-size">16</property>
+ <property name="icon-name">slideshow-emblem</property>
+ <style>
+ <class name="slideshow-icon" />
+ </style>
+ </object>
+ </child>
+
+ </object>
+ </child>
+ </template>
+</interface>
diff --git a/panels/background/cc-background-xml.c b/panels/background/cc-background-xml.c
new file mode 100644
index 0000000..270fcc2
--- /dev/null
+++ b/panels/background/cc-background-xml.c
@@ -0,0 +1,648 @@
+/*
+ * Authors: Rodney Dawes <dobey@ximian.com>
+ * Bastien Nocera <hadess@hadess.net>
+ *
+ * Copyright 2003-2006 Novell, Inc. (www.novell.com)
+ * Copyright 2011 Red Hat Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <gio/gio.h>
+#include <string.h>
+#include <libxml/parser.h>
+#include <gdesktop-enums.h>
+
+#include "gdesktop-enums-types.h"
+#include "cc-background-item.h"
+#include "cc-background-xml.h"
+
+/* The number of items we signal as "added" before
+ * returning to the main loop */
+#define NUM_ITEMS_PER_BATCH 1
+
+struct _CcBackgroundXml
+{
+ GObject parent_instance;
+
+ GHashTable *wp_hash;
+ GAsyncQueue *item_added_queue;
+ guint item_added_id;
+ GSList *monitors; /* GSList of GFileMonitor */
+};
+
+enum {
+ ADDED,
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = { 0 };
+
+G_DEFINE_TYPE (CcBackgroundXml, cc_background_xml, G_TYPE_OBJECT)
+
+static gboolean
+cc_background_xml_get_bool (const xmlNode *parent,
+ const gchar *prop_name)
+{
+ xmlChar *prop;
+ gboolean ret_val = FALSE;
+
+ g_return_val_if_fail (parent != NULL, FALSE);
+ g_return_val_if_fail (prop_name != NULL, FALSE);
+
+ prop = xmlGetProp ((xmlNode *) parent, (xmlChar*)prop_name);
+ if (prop != NULL) {
+ if (!g_ascii_strcasecmp ((gchar *)prop, "true") || !g_ascii_strcasecmp ((gchar *)prop, "1")) {
+ ret_val = TRUE;
+ } else {
+ ret_val = FALSE;
+ }
+ xmlFree (prop);
+ }
+
+ return ret_val;
+}
+
+static struct {
+ int value;
+ const char *string;
+} lookups[] = {
+ { G_DESKTOP_BACKGROUND_SHADING_HORIZONTAL, "horizontal-gradient" },
+ { G_DESKTOP_BACKGROUND_SHADING_VERTICAL, "vertical-gradient" },
+};
+
+static int
+enum_string_to_value (GType type,
+ const char *string)
+{
+ GEnumClass *eclass;
+ GEnumValue *value;
+
+ eclass = G_ENUM_CLASS (g_type_class_peek (type));
+ value = g_enum_get_value_by_nick (eclass, string);
+
+ /* Here's a bit of hand-made parsing, bad bad */
+ if (value == NULL) {
+ guint i;
+ for (i = 0; i < G_N_ELEMENTS (lookups); i++) {
+ if (g_str_equal (lookups[i].string, string))
+ return lookups[i].value;
+ }
+ g_warning ("Unhandled value '%s' for enum '%s'",
+ string, G_FLAGS_CLASS_TYPE_NAME (eclass));
+ return 0;
+ }
+
+ return value->value;
+}
+
+static gboolean
+idle_emit (CcBackgroundXml *xml)
+{
+ gint i;
+
+ g_async_queue_lock (xml->item_added_queue);
+
+ for (i = 0; i < NUM_ITEMS_PER_BATCH; i++) {
+ g_autoptr(GObject) item = NULL;
+
+ item = g_async_queue_try_pop_unlocked (xml->item_added_queue);
+ if (item == NULL)
+ break;
+ g_signal_emit (G_OBJECT (xml), signals[ADDED], 0, item);
+ }
+
+ g_async_queue_unlock (xml->item_added_queue);
+
+ if (g_async_queue_length (xml->item_added_queue) > 0) {
+ return TRUE;
+ } else {
+ xml->item_added_id = 0;
+ return FALSE;
+ }
+}
+
+static void
+emit_added_in_idle (CcBackgroundXml *xml,
+ GObject *object)
+{
+ g_async_queue_lock (xml->item_added_queue);
+ g_async_queue_push_unlocked (xml->item_added_queue, object);
+ if (xml->item_added_id == 0)
+ xml->item_added_id = g_idle_add ((GSourceFunc) idle_emit, xml);
+ g_async_queue_unlock (xml->item_added_queue);
+}
+
+#define NONE "(none)"
+#define UNSET_FLAG(flag) G_STMT_START{ (flags&=~(flag)); }G_STMT_END
+#define SET_FLAG(flag) G_STMT_START{ (flags|=flag); }G_STMT_END
+
+static gboolean
+cc_background_xml_load_xml_internal (CcBackgroundXml *xml,
+ const gchar *filename,
+ gboolean in_thread)
+{
+ xmlDoc * wplist;
+ xmlNode * root, * list, * wpa;
+ xmlChar * nodelang;
+ const gchar * const * syslangs;
+ gint i;
+ gboolean retval;
+
+ wplist = xmlParseFile (filename);
+ retval = FALSE;
+
+ if (!wplist)
+ return retval;
+
+ syslangs = g_get_language_names ();
+
+ root = xmlDocGetRootElement (wplist);
+
+ for (list = root->children; list != NULL; list = list->next) {
+ if (!strcmp ((gchar *)list->name, "wallpaper")) {
+ g_autoptr(CcBackgroundItem) item = NULL;
+ CcBackgroundItemFlags flags;
+ g_autofree gchar *uri = NULL;
+ g_autofree gchar *cname = NULL;
+ g_autofree gchar *id = NULL;
+
+ flags = 0;
+ item = cc_background_item_new (NULL);
+
+ g_object_set (G_OBJECT (item),
+ "is-deleted", cc_background_xml_get_bool (list, "deleted"),
+ "source-xml", filename,
+ NULL);
+
+ for (wpa = list->children; wpa != NULL; wpa = wpa->next) {
+ if (wpa->type == XML_COMMENT_NODE) {
+ continue;
+ } else if (!strcmp ((gchar *)wpa->name, "filename")) {
+ if (wpa->last != NULL && wpa->last->content != NULL) {
+ gchar *content = g_strstrip ((gchar *)wpa->last->content);
+ g_autofree gchar *bg_uri = NULL;
+
+ /* FIXME same rubbish as in other parts of the code */
+ if (strcmp (content, NONE) == 0) {
+ bg_uri = NULL;
+ } else {
+ g_autoptr(GFile) file = NULL;
+ g_autofree gchar *dirname = NULL;
+
+ dirname = g_path_get_dirname (filename);
+ file = g_file_new_for_commandline_arg_and_cwd (content, dirname);
+ bg_uri = g_file_get_uri (file);
+ }
+ SET_FLAG(CC_BACKGROUND_ITEM_HAS_URI);
+ g_object_set (G_OBJECT (item), "uri", bg_uri, NULL);
+ } else {
+ break;
+ }
+ } else if (!strcmp ((gchar *)wpa->name, "name")) {
+ if (wpa->last != NULL && wpa->last->content != NULL) {
+ g_autofree gchar *name = NULL;
+ nodelang = xmlNodeGetLang (wpa->last);
+
+ g_object_get (G_OBJECT (item), "name", &name, NULL);
+
+ if (name == NULL && nodelang == NULL) {
+ g_free (cname);
+ cname = g_strdup (g_strstrip ((gchar *)wpa->last->content));
+ g_object_set (G_OBJECT (item), "name", cname, NULL);
+ } else {
+ for (i = 0; syslangs[i] != NULL; i++) {
+ if (!strcmp (syslangs[i], (gchar *)nodelang)) {
+ g_object_set (G_OBJECT (item), "name",
+ g_strstrip ((gchar *)wpa->last->content), NULL);
+ break;
+ }
+ }
+ }
+
+ xmlFree (nodelang);
+ } else {
+ break;
+ }
+ } else if (!strcmp ((gchar *)wpa->name, "options")) {
+ if (wpa->last != NULL) {
+ g_object_set (G_OBJECT (item), "placement",
+ enum_string_to_value (G_DESKTOP_TYPE_DESKTOP_BACKGROUND_STYLE,
+ g_strstrip ((gchar *)wpa->last->content)), NULL);
+ SET_FLAG(CC_BACKGROUND_ITEM_HAS_PLACEMENT);
+ }
+ } else if (!strcmp ((gchar *)wpa->name, "shade_type")) {
+ if (wpa->last != NULL) {
+ g_object_set (G_OBJECT (item), "shading",
+ enum_string_to_value (G_DESKTOP_TYPE_DESKTOP_BACKGROUND_SHADING,
+ g_strstrip ((gchar *)wpa->last->content)), NULL);
+ SET_FLAG(CC_BACKGROUND_ITEM_HAS_SHADING);
+ }
+ } else if (!strcmp ((gchar *)wpa->name, "pcolor")) {
+ if (wpa->last != NULL) {
+ g_object_set (G_OBJECT (item), "primary-color",
+ g_strstrip ((gchar *)wpa->last->content), NULL);
+ SET_FLAG(CC_BACKGROUND_ITEM_HAS_PCOLOR);
+ }
+ } else if (!strcmp ((gchar *)wpa->name, "scolor")) {
+ if (wpa->last != NULL) {
+ g_object_set (G_OBJECT (item), "secondary-color",
+ g_strstrip ((gchar *)wpa->last->content), NULL);
+ SET_FLAG(CC_BACKGROUND_ITEM_HAS_SCOLOR);
+ }
+ } else if (!strcmp ((gchar *)wpa->name, "source_url")) {
+ if (wpa->last != NULL) {
+ g_object_set (G_OBJECT (item),
+ "source-url", g_strstrip ((gchar *)wpa->last->content),
+ "needs-download", FALSE,
+ NULL);
+ }
+ } else if (!strcmp ((gchar *)wpa->name, "text")) {
+ /* Do nothing here, libxml2 is being weird */
+ } else {
+ g_debug ("Unknown Tag in %s: %s", filename, wpa->name);
+ }
+ }
+
+ /* Check whether the target file exists */
+ {
+ const char *uri;
+
+ uri = cc_background_item_get_uri (item);
+ if (uri != NULL)
+ {
+ g_autoptr(GFile) file = NULL;
+
+ file = g_file_new_for_uri (uri);
+ if (g_file_query_exists (file, NULL) == FALSE)
+ {
+ g_clear_pointer (&cname, g_free);
+ g_clear_object (&item);
+ continue;
+ }
+ }
+ }
+
+ /* FIXME, this is a broken way of doing,
+ * need to use proper code here */
+ uri = g_filename_to_uri (filename, NULL, NULL);
+ id = g_strdup_printf ("%s#%s", uri, cname);
+
+ /* Make sure we don't already have this one and that filename exists */
+ if (g_hash_table_lookup (xml->wp_hash, id) != NULL) {
+ continue;
+ }
+
+ g_object_set (G_OBJECT (item), "flags", flags, NULL);
+ g_hash_table_insert (xml->wp_hash,
+ g_strdup (id),
+ g_object_ref (item));
+ if (in_thread)
+ emit_added_in_idle (xml, g_object_ref (G_OBJECT (item)));
+ else
+ g_signal_emit (G_OBJECT (xml), signals[ADDED], 0, item);
+ retval = TRUE;
+ }
+ }
+ xmlFreeDoc (wplist);
+
+ return retval;
+}
+
+static void
+gnome_wp_file_changed (GFileMonitor *monitor,
+ GFile *file,
+ GFile *other_file,
+ GFileMonitorEvent event_type,
+ CcBackgroundXml *data)
+{
+ g_autofree gchar *filename = NULL;
+
+ switch (event_type) {
+ case G_FILE_MONITOR_EVENT_CHANGED:
+ case G_FILE_MONITOR_EVENT_CREATED:
+ filename = g_file_get_path (file);
+ cc_background_xml_load_xml_internal (data, filename, FALSE);
+ break;
+ default:
+ break;
+ }
+}
+
+static void
+cc_background_xml_add_monitor (GFile *directory,
+ CcBackgroundXml *data)
+{
+ GFileMonitor *monitor;
+ g_autoptr(GError) error = NULL;
+
+ monitor = g_file_monitor_directory (directory,
+ G_FILE_MONITOR_NONE,
+ NULL,
+ &error);
+ if (error != NULL) {
+ g_autofree gchar *path = NULL;
+
+ path = g_file_get_parse_name (directory);
+ g_warning ("Unable to monitor directory %s: %s",
+ path, error->message);
+ return;
+ }
+
+ g_signal_connect (monitor, "changed",
+ G_CALLBACK (gnome_wp_file_changed),
+ data);
+
+ data->monitors = g_slist_prepend (data->monitors, monitor);
+}
+
+static void
+cc_background_xml_load_from_dir (const gchar *path,
+ CcBackgroundXml *data,
+ gboolean in_thread)
+{
+ g_autoptr(GFile) directory = NULL;
+ g_autoptr(GFileEnumerator) enumerator = NULL;
+ g_autoptr(GError) error = NULL;
+
+ if (!g_file_test (path, G_FILE_TEST_IS_DIR)) {
+ return;
+ }
+
+ directory = g_file_new_for_path (path);
+ enumerator = g_file_enumerate_children (directory,
+ G_FILE_ATTRIBUTE_STANDARD_NAME,
+ G_FILE_QUERY_INFO_NONE,
+ NULL,
+ &error);
+ if (error != NULL) {
+ g_warning ("Unable to check directory %s: %s", path, error->message);
+ return;
+ }
+
+ while (TRUE) {
+ g_autoptr(GFileInfo) info = NULL;
+ const gchar *filename;
+ g_autofree gchar *fullpath = NULL;
+
+ info = g_file_enumerator_next_file (enumerator, NULL, NULL);
+ if (info == NULL) {
+ g_file_enumerator_close (enumerator, NULL, NULL);
+ cc_background_xml_add_monitor (directory, data);
+ return;
+ }
+
+ filename = g_file_info_get_name (info);
+ fullpath = g_build_filename (path, filename, NULL);
+
+ cc_background_xml_load_xml_internal (data, fullpath, in_thread);
+ }
+}
+
+static void
+cc_background_xml_load_list (CcBackgroundXml *data,
+ gboolean in_thread)
+{
+ const char * const *system_data_dirs;
+ g_autofree gchar *datadir = NULL;
+ gint i;
+
+ datadir = g_build_filename (g_get_user_data_dir (),
+ "gnome-background-properties",
+ NULL);
+ cc_background_xml_load_from_dir (datadir, data, in_thread);
+
+ system_data_dirs = g_get_system_data_dirs ();
+ for (i = 0; system_data_dirs[i]; i++) {
+ g_autofree gchar *sdatadir = NULL;
+ sdatadir = g_build_filename (system_data_dirs[i],
+ "gnome-background-properties",
+ NULL);
+ cc_background_xml_load_from_dir (sdatadir, data, in_thread);
+ }
+}
+
+gboolean
+cc_background_xml_load_list_finish (CcBackgroundXml *xml,
+ GAsyncResult *result,
+ GError **error)
+{
+ g_return_val_if_fail (g_task_is_valid (result, xml), FALSE);
+ g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+ return g_task_propagate_boolean (G_TASK (result), error);
+}
+
+static void
+load_list_thread (GTask *task,
+ gpointer source_object,
+ gpointer task_data,
+ GCancellable *cancellable)
+{
+ CcBackgroundXml *xml = CC_BACKGROUND_XML (source_object);
+ cc_background_xml_load_list (xml, TRUE);
+ g_task_return_boolean (task, TRUE);
+}
+
+void
+cc_background_xml_load_list_async (CcBackgroundXml *xml,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ g_autoptr(GTask) task = NULL;
+
+ g_return_if_fail (CC_IS_BACKGROUND_XML (xml));
+
+ task = g_task_new (xml, cancellable, callback, user_data);
+ g_task_run_in_thread (task, load_list_thread);
+}
+
+gboolean
+cc_background_xml_load_xml (CcBackgroundXml *xml,
+ const gchar *filename)
+{
+ g_return_val_if_fail (CC_IS_BACKGROUND_XML (xml), FALSE);
+
+ if (g_file_test (filename, G_FILE_TEST_IS_REGULAR) == FALSE)
+ return FALSE;
+
+ return cc_background_xml_load_xml_internal (xml, filename, FALSE);
+}
+
+static void
+single_xml_added (CcBackgroundXml *xml,
+ CcBackgroundItem *item,
+ CcBackgroundItem **ret)
+{
+ g_assert (*ret == NULL);
+ *ret = g_object_ref (item);
+}
+
+CcBackgroundItem *
+cc_background_xml_get_item (const char *filename)
+{
+ g_autoptr(CcBackgroundXml) xml = NULL;
+ CcBackgroundItem *item = NULL;
+
+ if (g_file_test (filename, G_FILE_TEST_IS_REGULAR) == FALSE)
+ return NULL;
+
+ xml = cc_background_xml_new ();
+ g_signal_connect (G_OBJECT (xml), "added",
+ G_CALLBACK (single_xml_added), &item);
+ if (cc_background_xml_load_xml (xml, filename) == FALSE)
+ return NULL;
+
+ return item;
+}
+
+static const char *
+enum_to_str (GType type,
+ int v)
+{
+ GEnumClass *eclass;
+ GEnumValue *value;
+
+ eclass = G_ENUM_CLASS (g_type_class_peek (type));
+ value = g_enum_get_value (eclass, v);
+
+ g_assert (value);
+
+ return value->value_nick;
+}
+
+void
+cc_background_xml_save (CcBackgroundItem *item,
+ const char *filename)
+{
+ xmlDoc *wp;
+ xmlNode *root, *wallpaper;
+ xmlNode *xml_item G_GNUC_UNUSED;
+ const char * none = "(none)";
+ const char *placement_str, *shading_str;
+ g_autofree gchar *name = NULL;
+ g_autofree gchar *pcolor = NULL;
+ g_autofree gchar *scolor = NULL;
+ g_autofree gchar *uri = NULL;
+ g_autofree gchar *source_url = NULL;
+ CcBackgroundItemFlags flags;
+ GDesktopBackgroundStyle placement;
+ GDesktopBackgroundShading shading;
+
+ xmlKeepBlanksDefault (0);
+
+ wp = xmlNewDoc ((xmlChar *)"1.0");
+ xmlCreateIntSubset (wp, (xmlChar *)"wallpapers", NULL, (xmlChar *)"gnome-wp-list.dtd");
+ root = xmlNewNode (NULL, (xmlChar *)"wallpapers");
+ xmlDocSetRootElement (wp, root);
+
+ g_object_get (G_OBJECT (item),
+ "name", &name,
+ "uri", &uri,
+ "shading", &shading,
+ "placement", &placement,
+ "primary-color", &pcolor,
+ "secondary-color", &scolor,
+ "source-url", &source_url,
+ "flags", &flags,
+ NULL);
+
+ placement_str = enum_to_str (G_DESKTOP_TYPE_DESKTOP_BACKGROUND_STYLE, placement);
+ shading_str = enum_to_str (G_DESKTOP_TYPE_DESKTOP_BACKGROUND_SHADING, shading);
+
+ wallpaper = xmlNewChild (root, NULL, (xmlChar *)"wallpaper", NULL);
+ xml_item = xmlNewTextChild (wallpaper, NULL, (xmlChar *)"name", (xmlChar *)name);
+ if (flags & CC_BACKGROUND_ITEM_HAS_URI &&
+ uri != NULL)
+ {
+ g_autoptr(GFile) file = NULL;
+ g_autofree gchar *fname = NULL;
+
+ file = g_file_new_for_commandline_arg (uri);
+ fname = g_file_get_path (file);
+ xml_item = xmlNewTextChild (wallpaper, NULL, (xmlChar *)"filename", (xmlChar *)fname);
+ }
+ else if (flags & CC_BACKGROUND_ITEM_HAS_URI)
+ {
+ xml_item = xmlNewTextChild (wallpaper, NULL, (xmlChar *)"filename", (xmlChar *)none);
+ }
+
+ if (flags & CC_BACKGROUND_ITEM_HAS_PLACEMENT)
+ xml_item = xmlNewTextChild (wallpaper, NULL, (xmlChar *)"options", (xmlChar *)placement_str);
+ if (flags & CC_BACKGROUND_ITEM_HAS_SHADING)
+ xml_item = xmlNewTextChild (wallpaper, NULL, (xmlChar *)"shade_type", (xmlChar *)shading_str);
+ if (flags & CC_BACKGROUND_ITEM_HAS_PCOLOR)
+ xml_item = xmlNewTextChild (wallpaper, NULL, (xmlChar *)"pcolor", (xmlChar *)pcolor);
+ if (flags & CC_BACKGROUND_ITEM_HAS_SCOLOR)
+ xml_item = xmlNewTextChild (wallpaper, NULL, (xmlChar *)"scolor", (xmlChar *)scolor);
+ if (source_url != NULL)
+ xml_item = xmlNewTextChild (wallpaper, NULL, (xmlChar *)"source_url", (xmlChar *)source_url);
+
+ xmlSaveFormatFile (filename, wp, 1);
+ xmlFreeDoc (wp);
+}
+
+static void
+cc_background_xml_finalize (GObject *object)
+{
+ CcBackgroundXml *xml;
+
+ g_return_if_fail (object != NULL);
+ g_return_if_fail (CC_IS_BACKGROUND_XML (object));
+
+ xml = CC_BACKGROUND_XML (object);
+
+ g_slist_free_full (xml->monitors, g_object_unref);
+
+ g_clear_pointer (&xml->wp_hash, g_hash_table_destroy);
+ if (xml->item_added_id != 0) {
+ g_source_remove (xml->item_added_id);
+ xml->item_added_id = 0;
+ }
+ g_clear_pointer (&xml->item_added_queue, g_async_queue_unref);
+
+ G_OBJECT_CLASS (cc_background_xml_parent_class)->finalize (object);
+}
+
+static void
+cc_background_xml_class_init (CcBackgroundXmlClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->finalize = cc_background_xml_finalize;
+
+ signals[ADDED] = g_signal_new ("added",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL,
+ g_cclosure_marshal_VOID__OBJECT,
+ G_TYPE_NONE, 1, CC_TYPE_BACKGROUND_ITEM);
+}
+
+static void
+cc_background_xml_init (CcBackgroundXml *xml)
+{
+ xml->wp_hash = g_hash_table_new_full (g_str_hash,
+ g_str_equal,
+ (GDestroyNotify) g_free,
+ (GDestroyNotify) g_object_unref);
+ xml->item_added_queue = g_async_queue_new_full ((GDestroyNotify) g_object_unref);
+}
+
+CcBackgroundXml *
+cc_background_xml_new (void)
+{
+ return CC_BACKGROUND_XML (g_object_new (CC_TYPE_BACKGROUND_XML, NULL));
+}
diff --git a/panels/background/cc-background-xml.h b/panels/background/cc-background-xml.h
new file mode 100644
index 0000000..3d2038c
--- /dev/null
+++ b/panels/background/cc-background-xml.h
@@ -0,0 +1,46 @@
+/*
+ * Authors: Rodney Dawes <dobey@ximian.com>
+ *
+ * Copyright 2003-2012 Novell, Inc. (www.novell.com)
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include <gtk/gtk.h>
+#include <gio/gio.h>
+
+G_BEGIN_DECLS
+
+#define CC_TYPE_BACKGROUND_XML (cc_background_xml_get_type ())
+G_DECLARE_FINAL_TYPE (CcBackgroundXml, cc_background_xml, CC, BACKGROUND_XML, GObject)
+
+CcBackgroundXml *cc_background_xml_new (void);
+
+void cc_background_xml_save (CcBackgroundItem *item,
+ const char *filename);
+
+CcBackgroundItem *cc_background_xml_get_item (const char *filename);
+gboolean cc_background_xml_load_xml (CcBackgroundXml *data,
+ const char *filename);
+void cc_background_xml_load_list_async (CcBackgroundXml *xml,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gboolean cc_background_xml_load_list_finish (CcBackgroundXml *xml,
+ GAsyncResult *result,
+ GError **error);
+
+G_END_DECLS
diff --git a/panels/background/gnome-background-panel.desktop.in.in b/panels/background/gnome-background-panel.desktop.in.in
new file mode 100644
index 0000000..76534b0
--- /dev/null
+++ b/panels/background/gnome-background-panel.desktop.in.in
@@ -0,0 +1,14 @@
+[Desktop Entry]
+Name=Background
+Comment=Change your background image to a wallpaper or photo
+Exec=gnome-control-center background
+# Translators: Do NOT translate or transliterate this text (this is an icon file name)!
+Icon=preferences-desktop-wallpaper
+Terminal=false
+Type=Application
+NoDisplay=true
+StartupNotify=true
+Categories=GNOME;GTK;Settings;DesktopSettings;X-GNOME-Settings-Panel;X-GNOME-PersonalizationSettings;
+OnlyShowIn=GNOME;
+# Translators: Search terms to find the Background panel. Do NOT translate or localize the semicolons! The list MUST also end with a semicolon!
+Keywords=Wallpaper;Screen;Desktop;
diff --git a/panels/background/meson.build b/panels/background/meson.build
new file mode 100644
index 0000000..e9fa398
--- /dev/null
+++ b/panels/background/meson.build
@@ -0,0 +1,115 @@
+panels_list += cappletname
+desktop = 'gnome-@0@-panel.desktop'.format(cappletname)
+
+desktop_in = configure_file(
+ input: desktop + '.in.in',
+ output: desktop + '.in',
+ configuration: desktop_conf
+)
+
+i18n.merge_file(
+ desktop,
+ type: 'desktop',
+ input: desktop_in,
+ output: desktop,
+ po_dir: po_dir,
+ install: true,
+ install_dir: control_center_desktopdir
+)
+
+install_data(
+ 'slideshow-symbolic.svg',
+ install_dir: join_paths(control_center_icondir, 'hicolor', 'scalable', 'categories')
+)
+
+install_data(
+ 'slideshow-emblem.svg',
+ install_dir: join_paths(control_center_icondir, 'hicolor', 'scalable', 'emblems')
+)
+
+install_data(
+ 'noise-texture-light.png',
+ install_dir: join_paths(control_center_pkgdatadir, 'pixmaps')
+)
+
+common_sources = []
+
+enums = 'gdesktop-enums-types'
+enums_header = files(
+ gsettings_desktop_dep.get_pkgconfig_variable('prefix') + '/include/gsettings-desktop-schemas/gdesktop-enums.h',
+ 'cc-background-item.h'
+)
+
+common_sources += gnome.mkenums(
+ enums + '.h',
+ sources: enums_header,
+ fhead: '#pragma once\n\n#include <glib-object.h>\n\nG_BEGIN_DECLS\n',
+ fprod: '/* enumerations from "@filename@" */\n',
+ vhead: 'GType @enum_name@_get_type (void) G_GNUC_CONST;\n#define G_DESKTOP_TYPE_@ENUMSHORT@ (@enum_name@_get_type())\n',
+ ftail: 'G_END_DECLS\n'
+)
+
+common_sources += gnome.mkenums(
+ enums + '.c',
+ sources: enums_header,
+ fhead: '#include <gdesktop-enums.h>\n#include "gdesktop-enums-types.h"\n#include "cc-background-item.h"',
+ fprod: '\n/* enumerations from "@filename@" */',
+ vhead: 'GType\n@enum_name@_get_type (void)\n{\n static GType etype = 0;\n if (etype == 0) {\n static const G@Type@Value values[] = {',
+ vprod: ' { @VALUENAME@, "@VALUENAME@", "@valuenick@" },',
+ vtail: ' { 0, NULL, NULL }\n };\n etype = g_@type@_register_static ("@EnumName@", values);\n }\n return etype;\n}\n'
+)
+
+resource_data = files(
+ 'cc-background-chooser.ui',
+ 'cc-background-panel.ui',
+ 'cc-background-preview.ui',
+ 'preview.css',
+)
+
+common_sources += gnome.compile_resources(
+ 'cc-@0@-resources'.format(cappletname),
+ cappletname + '.gresource.xml',
+ c_name: 'cc_' + cappletname,
+ dependencies: resource_data,
+ export: true
+)
+
+sources = common_sources + files(
+ 'bg-colors-source.c',
+ 'bg-pictures-source.c',
+ 'bg-recent-source.c',
+ 'bg-source.c',
+ 'bg-wallpapers-source.c',
+ 'cc-background-chooser.c',
+ 'cc-background-grilo-miner.c',
+ 'cc-background-item.c',
+ 'cc-background-panel.c',
+ 'cc-background-preview.c',
+ 'cc-background-xml.c',
+)
+
+deps = common_deps + [
+ gdk_pixbuf_dep,
+ gnome_desktop_dep,
+ goa_dep,
+ libxml_dep,
+ dependency('cairo-gobject'),
+ dependency('grilo-0.3', version: '>= 0.3.0')
+]
+
+cflags += [
+ '-DDATADIR="@0@"'.format(control_center_datadir),
+ '-DGNOME_DESKTOP_USE_UNSTABLE_API'
+]
+
+if gnome_desktop_dep.version().version_compare('>=3.35.4')
+ cflags += '-DGNOME_DESKTOP_BG_API_BREAK'
+endif
+
+panels_libs += static_library(
+ cappletname,
+ sources: sources,
+ include_directories: top_inc,
+ dependencies: deps,
+ c_args: cflags,
+)
diff --git a/panels/background/noise-texture-light.png b/panels/background/noise-texture-light.png
new file mode 100644
index 0000000..f73293e
--- /dev/null
+++ b/panels/background/noise-texture-light.png
Binary files differ
diff --git a/panels/background/preview.css b/panels/background/preview.css
new file mode 100644
index 0000000..c741659
--- /dev/null
+++ b/panels/background/preview.css
@@ -0,0 +1,40 @@
+frame.desktop-preview {
+ min-height: 10px;
+ padding: 0 4px;
+ background-color: black;
+}
+
+frame.desktop-preview image {
+ color: white;
+}
+
+frame.desktop-preview label {
+ color: white;
+ font-weight: bold;
+ font-size: 6px;
+}
+
+frame.lock-screen-preview {
+ border: solid rgba(0, 0, 0, 0.33);
+ border-width: 10px 0 0 0;
+}
+
+frame.lock-screen-preview label {
+ color: white;
+ font-weight: bold;
+ text-shadow: 0 1px 2px rgba(0, 0, 0, 0.6);
+ font-size: 1.2em;
+}
+
+image.slideshow-icon {
+ color: white;
+ -gtk-icon-shadow: 0 1px 2px rgba(0, 0, 0, 0.33);
+}
+
+button.remove-button {
+ border-radius: 9999px;
+ -gtk-outline-radius: 9999px;
+ padding: 1px 0px; /* circles instead of ellipses */
+ background-origin: padding-box, border-box;
+ background-clip: padding-box, border-box;
+}
diff --git a/panels/background/slideshow-emblem.svg b/panels/background/slideshow-emblem.svg
new file mode 100644
index 0000000..4ba3a09
--- /dev/null
+++ b/panels/background/slideshow-emblem.svg
@@ -0,0 +1,104 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="128"
+ height="128"
+ id="svg4978"
+ version="1.1"
+ inkscape:version="0.48.0 r9654"
+ sodipodi:docname="slideshow-emblem.svg">
+ <defs
+ id="defs4980" />
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="0.98994949"
+ inkscape:cx="41.944792"
+ inkscape:cy="39.155574"
+ inkscape:document-units="px"
+ inkscape:current-layer="layer1"
+ showgrid="false"
+ inkscape:window-width="962"
+ inkscape:window-height="817"
+ inkscape:window-x="4"
+ inkscape:window-y="51"
+ inkscape:window-maximized="0" />
+ <metadata
+ id="metadata4983">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title />
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer"
+ id="layer1"
+ transform="translate(0,-924.36215)">
+ <g
+ style="display:inline"
+ id="g14317"
+ transform="matrix(7.1989829,0,0,7.1989829,-772.01578,-783.40043)">
+ <g
+ style="stroke:#000000;stroke-opacity:1"
+ transform="translate(69,-449)"
+ id="g14258"
+ inkscape:label="document-open-recent">
+ <path
+ sodipodi:type="arc"
+ style="color:#000000;fill:none;stroke:#000000;stroke-width:2.15384626;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+ id="path14260"
+ sodipodi:cx="48"
+ sodipodi:cy="696"
+ sodipodi:rx="7"
+ sodipodi:ry="7"
+ d="m 55,696 c 0,3.86599 -3.134007,7 -7,7 -3.865993,0 -7,-3.13401 -7,-7 0,-3.86599 3.134007,-7 7,-7 3.865993,0 7,3.13401 7,7 z"
+ transform="matrix(0.92857143,0,0,0.92857143,2.9285714,49.21429)" />
+ <path
+ style="fill:none;stroke:#000000;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+ d="m 45.5,693.5 2,2 3,-3"
+ id="path14262"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="ccc" />
+ </g>
+ <g
+ inkscape:label="document-open-recent"
+ id="g4692"
+ transform="translate(69,-450)">
+ <path
+ transform="matrix(0.92857143,0,0,0.92857143,2.9285714,49.21429)"
+ d="m 55,696 c 0,3.86599 -3.134007,7 -7,7 -3.865993,0 -7,-3.13401 -7,-7 0,-3.86599 3.134007,-7 7,-7 3.865993,0 7,3.13401 7,7 z"
+ sodipodi:ry="7"
+ sodipodi:rx="7"
+ sodipodi:cy="696"
+ sodipodi:cx="48"
+ id="path3869"
+ style="color:#000000;fill:none;stroke:#ffffff;stroke-width:2.15384626;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+ sodipodi:type="arc" />
+ <path
+ sodipodi:nodetypes="ccc"
+ inkscape:connector-curvature="0"
+ id="path4639"
+ d="m 45.5,693.5 2,2 3,-3"
+ style="fill:none;stroke:#ffffff;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ </g>
+ </g>
+ </g>
+</svg>
diff --git a/panels/background/slideshow-symbolic.svg b/panels/background/slideshow-symbolic.svg
new file mode 100644
index 0000000..815115c
--- /dev/null
+++ b/panels/background/slideshow-symbolic.svg
@@ -0,0 +1,70 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="16"
+ height="16"
+ id="svg5594"
+ version="1.1"
+ inkscape:version="0.48.0 r9654"
+ sodipodi:docname="slideshow-emblem-symbolic.svg">
+ <defs
+ id="defs5596" />
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="22.4"
+ inkscape:cx="10.858512"
+ inkscape:cy="10.780448"
+ inkscape:document-units="px"
+ inkscape:current-layer="layer1"
+ showgrid="false"
+ inkscape:window-width="789"
+ inkscape:window-height="774"
+ inkscape:window-x="4"
+ inkscape:window-y="51"
+ inkscape:window-maximized="0" />
+ <metadata
+ id="metadata5599">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title />
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer"
+ id="layer1"
+ transform="translate(0,-1036.3622)">
+ <g
+ id="g3763"
+ transform="matrix(0.86221939,0,0,0.86221939,1.0303599,144.13347)">
+ <path
+ inkscape:connector-curvature="0"
+ id="path14584"
+ d="m 7.9642862,1038.3322 c -3.3085937,0 -5.9942603,2.6857 -5.9942603,5.9943 0,3.3086 2.6856666,5.9942 5.9942603,5.9942 3.3085938,0 5.9942608,-2.6856 5.9942608,-5.9942 0,-3.3086 -2.685667,-5.9943 -5.9942608,-5.9943 z m 0,0.8457 c 2.8454368,0 5.1485968,2.3031 5.1485968,5.1486 0,2.8454 -2.30316,5.1486 -5.1485968,5.1486 -2.8454368,0 -5.148597,-2.3032 -5.148597,-5.1486 0,-2.8455 2.3031602,-5.1486 5.148597,-5.1486 z"
+ style="font-size:medium;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-indent:0;text-align:start;text-decoration:none;line-height:normal;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;text-anchor:start;baseline-shift:baseline;color:#000000;fill:#808080;fill-opacity:1;stroke:none;stroke-width:1.0767436;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate;font-family:Sans;-inkscape-font-specification:Sans" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path14586"
+ d="m 10.5,1041.3125 a 0.42854288,0.42854288 0 0 0 -0.28125,0.125 l -2.25,2.2813 -1.40625,-1.4063 a 0.4310176,0.4310176 0 1 0 -0.625,0.5937 l 1.71875,1.7188 a 0.42854288,0.42854288 0 0 0 0.625,0 l 2.5625,-2.5625 a 0.42854288,0.42854288 0 0 0 -0.34375,-0.75 z"
+ style="font-size:medium;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-indent:0;text-align:start;text-decoration:none;line-height:normal;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;text-anchor:start;baseline-shift:baseline;color:#000000;fill:#999999;fill-opacity:1;stroke:none;stroke-width:0.85700005;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate;font-family:Sans;-inkscape-font-specification:Sans" />
+ </g>
+ </g>
+</svg>