summaryrefslogtreecommitdiffstats
path: root/panels/background
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--panels/background/background-selected-symbolic.svg1
-rw-r--r--panels/background/background.gresource.xml14
-rw-r--r--panels/background/bg-colors-source.c219
-rw-r--r--panels/background/bg-colors-source.h38
-rw-r--r--panels/background/bg-recent-source.c459
-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.c164
-rw-r--r--panels/background/bg-wallpapers-source.h34
-rw-r--r--panels/background/cc-background-chooser.c337
-rw-r--r--panels/background/cc-background-chooser.h32
-rw-r--r--panels/background/cc-background-chooser.ui64
-rw-r--r--panels/background/cc-background-item.c1062
-rw-r--r--panels/background/cc-background-item.h88
-rw-r--r--panels/background/cc-background-paintable.c314
-rw-r--r--panels/background/cc-background-paintable.h35
-rw-r--r--panels/background/cc-background-panel.c455
-rw-r--r--panels/background/cc-background-panel.h30
-rw-r--r--panels/background/cc-background-panel.ui134
-rw-r--r--panels/background/cc-background-preview.c351
-rw-r--r--panels/background/cc-background-preview.h36
-rw-r--r--panels/background/cc-background-preview.ui55
-rw-r--r--panels/background/cc-background-xml.c669
-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/icons/meson.build4
-rw-r--r--panels/background/icons/scalable/org.gnome.Settings-appearance-symbolic.svg9
-rw-r--r--panels/background/meson.build99
-rw-r--r--panels/background/noise-texture-light.pngbin0 -> 69136 bytes
-rw-r--r--panels/background/preview.css96
-rw-r--r--panels/background/slideshow-symbolic.svg1
32 files changed, 5158 insertions, 0 deletions
diff --git a/panels/background/background-selected-symbolic.svg b/panels/background/background-selected-symbolic.svg
new file mode 100644
index 0000000..9e820d1
--- /dev/null
+++ b/panels/background/background-selected-symbolic.svg
@@ -0,0 +1 @@
+<svg width="16" height="16" viewBox="0 0 4.233 4.233" xmlns="http://www.w3.org/2000/svg"><path d="M3.843.627a.397.397 0 0 0-.56.034L1.45 2.73l-.775-.763a.397.397 0 0 0-.56.004.397.397 0 0 0 .003.562L1.191 3.59a.397.397 0 0 0 .576-.02l2.11-2.382a.397.397 0 0 0-.034-.56Z" style="fill:#3d3846"/></svg> \ No newline at end of file
diff --git a/panels/background/background.gresource.xml b/panels/background/background.gresource.xml
new file mode 100644
index 0000000..1699244
--- /dev/null
+++ b/panels/background/background.gresource.xml
@@ -0,0 +1,14 @@
+<?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>
+
+ <gresource prefix="/org/gnome/Settings/icons/scalable/actions">
+ <file preprocess="xml-stripblanks">background-selected-symbolic.svg</file>
+ <file preprocess="xml-stripblanks">slideshow-symbolic.svg</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-recent-source.c b/panels/background/bg-recent-source.c
new file mode 100644
index 0000000..32d1854
--- /dev/null
+++ b/panels/background/bg-recent-source.c
@@ -0,0 +1,459 @@
+/* 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 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_content_type_is_a (content_type, "image/*"))
+ 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..dacf82f
--- /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 144
+#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..20ca4b7
--- /dev/null
+++ b/panels/background/bg-wallpapers-source.c
@@ -0,0 +1,164 @@
+/* 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 int
+sort_func (gconstpointer a,
+ gconstpointer b,
+ gpointer user_data)
+{
+ CcBackgroundItem *item_a;
+ CcBackgroundItem *item_b;
+ const char *name_a;
+ const char *name_b;
+
+ item_a = (CcBackgroundItem *) a;
+ item_b = (CcBackgroundItem *) b;
+
+ name_a = cc_background_item_get_name (item_a);
+ name_b = cc_background_item_get_name (item_b);
+
+ if (name_a && strcmp (name_a, "Default Background") == 0)
+ return -1;
+ if (name_b && strcmp (name_b, "Default Background") == 0)
+ return 1;
+
+
+ return strcmp (cc_background_item_get_name (item_a),
+ cc_background_item_get_name (item_b));
+}
+
+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_insert_sorted (store, item, sort_func, NULL);
+}
+
+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..8d24e45
--- /dev/null
+++ b/panels/background/cc-background-chooser.c
@@ -0,0 +1,337 @@
+/* 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-recent-source.h"
+#include "bg-wallpapers-source.h"
+#include "cc-background-chooser.h"
+#include "cc-background-paintable.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 void
+direction_changed_cb (GtkWidget *widget,
+ GtkTextDirection *previous_direction,
+ GdkPaintable *paintable)
+{
+ g_object_set (paintable,
+ "text-direction", gtk_widget_get_direction (widget),
+ NULL);
+}
+
+static GtkWidget*
+create_widget_func (gpointer model_item,
+ gpointer user_data)
+{
+ g_autoptr(CcBackgroundPaintable) paintable = NULL;
+ CcBackgroundItem *item;
+ GtkWidget *overlay;
+ GtkWidget *child;
+ GtkWidget *picture;
+ GtkWidget *icon;
+ GtkWidget *check;
+ GtkWidget *button = NULL;
+ BgSource *source;
+
+ source = BG_SOURCE (user_data);
+ item = CC_BACKGROUND_ITEM (model_item);
+
+ paintable = cc_background_paintable_new (source, item);
+
+ picture = gtk_picture_new_for_paintable (GDK_PAINTABLE (paintable));
+ gtk_picture_set_can_shrink (GTK_PICTURE (picture), FALSE);
+
+ g_object_bind_property (picture, "scale-factor",
+ paintable, "scale-factor", G_BINDING_SYNC_CREATE);
+ g_signal_connect_object (picture, "direction-changed",
+ G_CALLBACK (direction_changed_cb), paintable, 0);
+
+ icon = gtk_image_new_from_icon_name ("slideshow-symbolic");
+ gtk_widget_set_halign (icon, GTK_ALIGN_START);
+ gtk_widget_set_valign (icon, GTK_ALIGN_END);
+ gtk_widget_set_visible (icon, cc_background_item_changes_with_time (item));
+ gtk_widget_add_css_class (icon, "slideshow-icon");
+
+ check = gtk_image_new_from_icon_name ("background-selected-symbolic");
+ gtk_widget_set_halign (check, GTK_ALIGN_END);
+ gtk_widget_set_valign (check, GTK_ALIGN_END);
+ gtk_widget_add_css_class (check, "selected-check");
+
+ if (BG_IS_RECENT_SOURCE (source))
+ {
+ button = gtk_button_new_from_icon_name ("window-close-symbolic");
+ gtk_widget_set_halign (button, GTK_ALIGN_END);
+ gtk_widget_set_valign (button, GTK_ALIGN_START);
+
+ gtk_widget_add_css_class (button, "osd");
+ gtk_widget_add_css_class (button, "circular");
+ gtk_widget_add_css_class (button, "remove-button");
+
+ g_signal_connect (button,
+ "clicked",
+ G_CALLBACK (on_delete_background_clicked_cb),
+ source);
+ }
+
+ overlay = gtk_overlay_new ();
+ gtk_widget_set_overflow (overlay, GTK_OVERFLOW_HIDDEN);
+ gtk_widget_add_css_class (overlay, "background-thumbnail");
+ gtk_overlay_set_child (GTK_OVERLAY (overlay), picture);
+ gtk_overlay_add_overlay (GTK_OVERLAY (overlay), icon);
+ gtk_overlay_add_overlay (GTK_OVERLAY (overlay), check);
+ if (button)
+ gtk_overlay_add_overlay (GTK_OVERLAY (overlay), button);
+ gtk_accessible_update_property (GTK_ACCESSIBLE (overlay),
+ GTK_ACCESSIBLE_PROPERTY_LABEL,
+ cc_background_item_get_name (item),
+ -1);
+
+
+ child = gtk_flow_box_child_new ();
+ gtk_widget_set_halign (child, GTK_ALIGN_CENTER);
+ gtk_widget_set_valign (child, GTK_ALIGN_CENTER);
+ gtk_flow_box_child_set_child (GTK_FLOW_BOX_CHILD (child), overlay);
+
+ 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(GListModel) files = NULL;
+ guint i;
+
+ files = gtk_file_chooser_get_files (GTK_FILE_CHOOSER (filechooser));
+ for (i = 0; i < g_list_model_get_n_items (files); i++)
+ {
+ g_autoptr(GFile) file = g_list_model_get_item (files, i);
+ g_autofree gchar *filename = g_file_get_path (file);
+
+ bg_recent_source_add_file (self->recent_source, filename);
+ }
+ }
+
+ gtk_window_destroy (GTK_WINDOW (filechooser));
+}
+
+/* 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(GFile) pictures_folder = NULL;
+ GtkFileFilter *filter;
+ GtkWidget *filechooser;
+ GtkWindow *toplevel;
+
+ g_return_if_fail (CC_IS_BACKGROUND_CHOOSER (self));
+
+ toplevel = (GtkWindow*) gtk_widget_get_native (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);
+
+ 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_select_multiple (GTK_FILE_CHOOSER (filechooser), TRUE);
+
+ pictures_folder = g_file_new_for_path (g_get_user_special_dir (G_USER_DIRECTORY_PICTURES));
+ gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (filechooser),
+ pictures_folder,
+ NULL);
+
+ 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..edaf1dd
--- /dev/null
+++ b/panels/background/cc-background-chooser.ui
@@ -0,0 +1,64 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <template class="CcBackgroundChooser" parent="GtkBox">
+ <property name="orientation">vertical</property>
+
+ <!-- Recent -->
+ <child>
+ <object class="GtkBox" id="recent_box">
+ <property name="orientation">vertical</property>
+ <property name="halign">center</property>
+
+ <child>
+ <object class="GtkFlowBox" id="recent_flowbox">
+ <property name="margin-top">12</property>
+ <property name="margin-bottom">12</property>
+ <property name="margin-start">12</property>
+ <property name="margin-end">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" />
+ <style>
+ <class name="background-flowbox"/>
+ </style>
+ </object>
+ </child>
+
+ <child>
+ <object class="GtkSeparator">
+ <property name="margin-top">12</property>
+ <property name="margin-bottom">12</property>
+ </object>
+ </child>
+ </object>
+ </child>
+
+ <child>
+ <object class="GtkFlowBox" id="flowbox">
+ <property name="margin-top">12</property>
+ <property name="margin-bottom">12</property>
+ <property name="margin-start">12</property>
+ <property name="margin-end">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" />
+ <style>
+ <class name="background-flowbox"/>
+ </style>
+ </object>
+ </child>
+
+ </template>
+</interface>
diff --git a/panels/background/cc-background-item.c b/panels/background/cc-background-item.c
new file mode 100644
index 0000000..e25c8c4
--- /dev/null
+++ b/panels/background/cc-background-item.c
@@ -0,0 +1,1062 @@
+/* -*- 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 <gnome-bg/gnome-bg.h>
+#include <gdesktop-enums.h>
+
+#include "cc-background-item.h"
+#include "gdesktop-enums-types.h"
+
+typedef struct {
+ int width;
+ int height;
+ int frame;
+ int scale_factor;
+ GdkPixbuf *thumbnail;
+} CachedThumbnail;
+
+struct _CcBackgroundItem
+{
+ GObject parent_instance;
+
+ /* properties */
+ char *name;
+ char *uri;
+ char *uri_dark;
+ 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;
+
+ GnomeBG *bg_dark;
+
+ CachedThumbnail cached_thumbnail;
+ CachedThumbnail cached_thumbnail_dark;
+};
+
+enum {
+ PROP_0,
+ PROP_NAME,
+ PROP_URI,
+ PROP_URI_DARK,
+ 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->uri_dark) {
+ g_autoptr(GFile) file = NULL;
+ g_autofree gchar *filename = NULL;
+
+ file = g_file_new_for_commandline_arg (item->uri_dark);
+ filename = g_file_get_path (file);
+ gnome_bg_set_filename (item->bg_dark, 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_rgba (item->bg_dark, item->shading, &pcolor, &scolor);
+ gnome_bg_set_placement (item->bg, item->placement);
+ gnome_bg_set_placement (item->bg_dark, 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);
+ }
+ if (item->bg_dark != NULL) {
+ changes |= gnome_bg_changes_with_time (item->bg_dark);
+ }
+ return changes;
+}
+
+gboolean
+cc_background_item_has_dark_version (CcBackgroundItem *item)
+{
+ g_return_val_if_fail (CC_IS_BACKGROUND_ITEM (item), FALSE);
+
+ return item->uri && item->uri_dark;
+}
+
+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);
+ gnome_bg_draw (bg, pixbuf);
+
+ 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,
+ gboolean dark)
+{
+ g_autoptr(GdkPixbuf) pixbuf = NULL;
+ g_autoptr(GdkPixbuf) retval = NULL;
+ CachedThumbnail *thumbnail;
+ GnomeBG *bg;
+
+ g_return_val_if_fail (CC_IS_BACKGROUND_ITEM (item), NULL);
+ g_return_val_if_fail (width > 0 && height > 0, NULL);
+
+ thumbnail = dark ? &item->cached_thumbnail_dark : &item->cached_thumbnail;
+ bg = dark ? item->bg_dark : item->bg;
+
+ /* Use the cached thumbnail if the sizes match */
+ if (thumbnail->thumbnail &&
+ thumbnail->width == width &&
+ thumbnail->height == height &&
+ thumbnail->scale_factor == scale_factor &&
+ thumbnail->frame == frame)
+ return g_object_ref (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 (bg, width, height);
+ } else {
+ g_autoptr(GdkMonitor) monitor = NULL;
+ GdkDisplay *display;
+ GListModel *monitors;
+ GdkRectangle monitor_layout;
+
+
+ display = gdk_display_get_default ();
+ monitors = gdk_display_get_monitors (display);
+ monitor = g_list_model_get_item (monitors, 0);
+ gdk_monitor_get_geometry (monitor, &monitor_layout);
+
+ if (frame >= 0) {
+ pixbuf = gnome_bg_create_frame_thumbnail (bg,
+ thumbs,
+ &monitor_layout,
+ width,
+ height,
+ frame);
+ } else {
+ pixbuf = gnome_bg_create_thumbnail (bg,
+ thumbs,
+ &monitor_layout,
+ width,
+ height);
+ }
+ }
+
+ retval = g_steal_pointer (&pixbuf);
+
+ gnome_bg_get_image_size (bg,
+ thumbs,
+ width,
+ height,
+ &item->width,
+ &item->height);
+
+ update_size (item);
+
+ /* Cache the new thumbnail */
+ g_set_object (&thumbnail->thumbnail, retval);
+ thumbnail->width = width;
+ thumbnail->height = height;
+ thumbnail->scale_factor = scale_factor;
+ 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,
+ gboolean dark)
+{
+ return cc_background_item_get_frame_thumbnail (item, thumbs, width, height, scale_factor, -1, FALSE, dark);
+}
+
+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);
+ }
+}
+
+
+static void
+_set_uri_dark (CcBackgroundItem *item,
+ const char *value)
+{
+ g_free (item->uri_dark);
+ if (value && *value == '\0') {
+ item->uri_dark = NULL;
+ } else {
+ if (value && strstr (value, "://") == NULL)
+ g_warning ("URI '%s' is invalid", value);
+ item->uri_dark = 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;
+}
+
+const char *
+cc_background_item_get_uri_dark (CcBackgroundItem *item)
+{
+ g_return_val_if_fail (CC_IS_BACKGROUND_ITEM (item), NULL);
+
+ return item->uri_dark;
+}
+
+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_URI_DARK:
+ _set_uri_dark (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_URI_DARK:
+ g_value_set_string (value, self->uri_dark);
+ 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_URI_DARK,
+ g_param_spec_string ("uri-dark",
+ "uri-dark",
+ "uri-dark",
+ 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->bg_dark = 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_clear_object (&item->cached_thumbnail_dark.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);
+
+ g_clear_object (&item->bg);
+ g_clear_object (&item->bg_dark);
+
+ 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_URI_DARK) {
+ if (files_equal (saved->uri_dark, configured->uri_dark) == 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..80fd5a2
--- /dev/null
+++ b/panels/background/cc-background-item.h
@@ -0,0 +1,88 @@
+/* -*- 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 <gnome-bg/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,
+ CC_BACKGROUND_ITEM_HAS_URI_DARK = 1 << 5
+} 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);
+gboolean cc_background_item_has_dark_version (CcBackgroundItem *item);
+
+GdkPixbuf * cc_background_item_get_thumbnail (CcBackgroundItem *item,
+ GnomeDesktopThumbnailFactory *thumbs,
+ int width,
+ int height,
+ int scale_factor,
+ gboolean dark);
+GdkPixbuf * cc_background_item_get_frame_thumbnail (CcBackgroundItem *item,
+ GnomeDesktopThumbnailFactory *thumbs,
+ int width,
+ int height,
+ int scale_factor,
+ int frame,
+ gboolean force_size,
+ gboolean dark);
+
+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_uri_dark (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-paintable.c b/panels/background/cc-background-paintable.c
new file mode 100644
index 0000000..e510736
--- /dev/null
+++ b/panels/background/cc-background-paintable.c
@@ -0,0 +1,314 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2021 Alexander Mikhaylenko <alexm@gnome.org>
+ *
+ * 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 "cc-background-paintable.h"
+
+struct _CcBackgroundPaintable
+{
+ GObject parent_instance;
+
+ BgSource *source;
+ CcBackgroundItem *item;
+ int scale_factor;
+ GtkTextDirection text_direction;
+
+ GdkPaintable *texture;
+ GdkPaintable *dark_texture;
+};
+
+enum
+{
+ PROP_0,
+ PROP_SOURCE,
+ PROP_ITEM,
+ PROP_SCALE_FACTOR,
+ PROP_TEXT_DIRECTION,
+ N_PROPS
+};
+
+static GParamSpec *properties [N_PROPS];
+
+static void cc_background_paintable_paintable_init (GdkPaintableInterface *iface);
+
+G_DEFINE_TYPE_WITH_CODE (CcBackgroundPaintable, cc_background_paintable, G_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE (GDK_TYPE_PAINTABLE,
+ cc_background_paintable_paintable_init))
+
+static void
+update_cache (CcBackgroundPaintable *self)
+{
+ g_autoptr (GdkPixbuf) pixbuf = NULL;
+ GnomeDesktopThumbnailFactory *factory;
+ int width, height;
+
+ g_clear_object (&self->texture);
+ g_clear_object (&self->dark_texture);
+
+ factory = bg_source_get_thumbnail_factory (self->source);
+ width = bg_source_get_thumbnail_width (self->source);
+ height = bg_source_get_thumbnail_height (self->source);
+
+ pixbuf = cc_background_item_get_thumbnail (self->item,
+ factory,
+ width,
+ height,
+ self->scale_factor,
+ FALSE);
+
+ self->texture = GDK_PAINTABLE (gdk_texture_new_for_pixbuf (pixbuf));
+
+ if (cc_background_item_has_dark_version (self->item))
+ {
+ g_autoptr (GdkPixbuf) dark_pixbuf = NULL;
+
+ dark_pixbuf = cc_background_item_get_thumbnail (self->item,
+ factory,
+ width,
+ height,
+ self->scale_factor,
+ TRUE);
+ self->dark_texture = GDK_PAINTABLE (gdk_texture_new_for_pixbuf (dark_pixbuf));
+ }
+
+ gdk_paintable_invalidate_size (GDK_PAINTABLE (self));
+}
+
+static void
+cc_background_paintable_dispose (GObject *object)
+{
+ CcBackgroundPaintable *self = CC_BACKGROUND_PAINTABLE (object);
+
+ g_clear_object (&self->item);
+ g_clear_object (&self->source);
+ g_clear_object (&self->texture);
+ g_clear_object (&self->dark_texture);
+
+ G_OBJECT_CLASS (cc_background_paintable_parent_class)->dispose (object);
+}
+
+static void
+cc_background_paintable_constructed (GObject *object)
+{
+ CcBackgroundPaintable *self = CC_BACKGROUND_PAINTABLE (object);
+
+ G_OBJECT_CLASS (cc_background_paintable_parent_class)->constructed (object);
+
+ update_cache (self);
+}
+
+static void
+cc_background_paintable_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ CcBackgroundPaintable *self = CC_BACKGROUND_PAINTABLE (object);
+
+ switch (prop_id)
+ {
+ case PROP_SOURCE:
+ g_value_set_object (value, self->source);
+ break;
+
+ case PROP_ITEM:
+ g_value_set_object (value, self->item);
+ break;
+
+ case PROP_SCALE_FACTOR:
+ g_value_set_int (value, self->scale_factor);
+ break;
+
+ case PROP_TEXT_DIRECTION:
+ g_value_set_enum (value, self->text_direction);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+cc_background_paintable_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ CcBackgroundPaintable *self = CC_BACKGROUND_PAINTABLE (object);
+
+ switch (prop_id)
+ {
+ case PROP_SOURCE:
+ g_set_object (&self->source, g_value_get_object (value));
+ break;
+
+ case PROP_ITEM:
+ g_set_object (&self->item, g_value_get_object (value));
+ break;
+
+ case PROP_SCALE_FACTOR:
+ self->scale_factor = g_value_get_int (value);
+ update_cache (self);
+ break;
+
+ case PROP_TEXT_DIRECTION:
+ self->text_direction = g_value_get_enum (value);
+ gdk_paintable_invalidate_contents (GDK_PAINTABLE (self));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+cc_background_paintable_class_init (CcBackgroundPaintableClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->dispose = cc_background_paintable_dispose;
+ object_class->constructed = cc_background_paintable_constructed;
+ object_class->get_property = cc_background_paintable_get_property;
+ object_class->set_property = cc_background_paintable_set_property;
+
+ properties[PROP_SOURCE] =
+ g_param_spec_object ("source",
+ "Source",
+ "Source",
+ BG_TYPE_SOURCE,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_STRINGS);
+
+ properties[PROP_ITEM] =
+ g_param_spec_object ("item",
+ "Item",
+ "Item",
+ CC_TYPE_BACKGROUND_ITEM,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_STRINGS);
+
+ properties[PROP_SCALE_FACTOR] =
+ g_param_spec_int ("scale-factor",
+ "Scale Factor",
+ "Scale Factor",
+ 1, G_MAXINT, 1,
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS);
+
+ properties[PROP_TEXT_DIRECTION] =
+ g_param_spec_enum ("text-direction",
+ "Text Direction",
+ "Text Direction",
+ GTK_TYPE_TEXT_DIRECTION,
+ GTK_TEXT_DIR_LTR,
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS);
+
+ g_object_class_install_properties (object_class, N_PROPS, properties);
+}
+
+static void
+cc_background_paintable_init (CcBackgroundPaintable *self)
+{
+ self->scale_factor = 1;
+ self->text_direction = GTK_TEXT_DIR_LTR;
+}
+
+static void
+cc_background_paintable_snapshot (GdkPaintable *paintable,
+ GdkSnapshot *snapshot,
+ double width,
+ double height)
+{
+ CcBackgroundPaintable *self = CC_BACKGROUND_PAINTABLE (paintable);
+ gboolean is_rtl;
+
+ if (!self->dark_texture)
+ {
+ gdk_paintable_snapshot (self->texture, snapshot, width, height);
+ return;
+ }
+
+ is_rtl = self->text_direction == GTK_TEXT_DIR_RTL;
+
+ gtk_snapshot_push_clip (GTK_SNAPSHOT (snapshot),
+ &GRAPHENE_RECT_INIT (is_rtl ? width / 2.0f : 0.0f,
+ 0.0f,
+ width / 2.0f,
+ height));
+ gdk_paintable_snapshot (self->texture, snapshot, width, height);
+ gtk_snapshot_pop (GTK_SNAPSHOT (snapshot));
+
+ gtk_snapshot_push_clip (GTK_SNAPSHOT (snapshot),
+ &GRAPHENE_RECT_INIT (is_rtl ? 0.0f : width / 2.0f,
+ 0.0f,
+ width / 2.0f,
+ height));
+ gdk_paintable_snapshot (self->dark_texture, snapshot, width, height);
+ gtk_snapshot_pop (GTK_SNAPSHOT (snapshot));
+}
+
+static int
+cc_background_paintable_get_intrinsic_width (GdkPaintable *paintable)
+{
+ CcBackgroundPaintable *self = CC_BACKGROUND_PAINTABLE (paintable);
+
+ return gdk_paintable_get_intrinsic_width (self->texture) / self->scale_factor;
+}
+
+static int
+cc_background_paintable_get_intrinsic_height (GdkPaintable *paintable)
+{
+ CcBackgroundPaintable *self = CC_BACKGROUND_PAINTABLE (paintable);
+
+ return gdk_paintable_get_intrinsic_height (self->texture) / self->scale_factor;
+}
+
+static double
+cc_background_paintable_get_intrinsic_aspect_ratio (GdkPaintable *paintable)
+{
+ CcBackgroundPaintable *self = CC_BACKGROUND_PAINTABLE (paintable);
+
+ return gdk_paintable_get_intrinsic_aspect_ratio (self->texture);
+}
+
+static void
+cc_background_paintable_paintable_init (GdkPaintableInterface *iface)
+{
+ iface->snapshot = cc_background_paintable_snapshot;
+ iface->get_intrinsic_width = cc_background_paintable_get_intrinsic_width;
+ iface->get_intrinsic_height = cc_background_paintable_get_intrinsic_height;
+ iface->get_intrinsic_aspect_ratio = cc_background_paintable_get_intrinsic_aspect_ratio;
+}
+
+CcBackgroundPaintable *
+cc_background_paintable_new (BgSource *source,
+ CcBackgroundItem *item)
+{
+ g_return_val_if_fail (BG_IS_SOURCE (source), NULL);
+ g_return_val_if_fail (CC_IS_BACKGROUND_ITEM (item), NULL);
+
+ return g_object_new (CC_TYPE_BACKGROUND_PAINTABLE,
+ "source", source,
+ "item", item,
+ NULL);
+}
diff --git a/panels/background/cc-background-paintable.h b/panels/background/cc-background-paintable.h
new file mode 100644
index 0000000..75360dc
--- /dev/null
+++ b/panels/background/cc-background-paintable.h
@@ -0,0 +1,35 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2021 Alexander Mikhaylenko <alexm@gnome.org>
+ *
+ * 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 "bg-source.h"
+#include "cc-background-item.h"
+
+G_BEGIN_DECLS
+
+#define CC_TYPE_BACKGROUND_PAINTABLE (cc_background_paintable_get_type ())
+G_DECLARE_FINAL_TYPE (CcBackgroundPaintable, cc_background_paintable, CC, BACKGROUND_PAINTABLE, GObject)
+
+CcBackgroundPaintable * cc_background_paintable_new (BgSource *source,
+ 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..f748e9a
--- /dev/null
+++ b/panels/background/cc-background-panel.c
@@ -0,0 +1,455 @@
+/*
+ * 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"
+
+#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_URI_DARK_KEY "picture-uri-dark"
+#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"
+
+#define INTERFACE_PATH_ID "org.gnome.desktop.interface"
+#define INTERFACE_COLOR_SCHEME_KEY "color-scheme"
+
+struct _CcBackgroundPanel
+{
+ CcPanel parent_instance;
+
+ GDBusConnection *connection;
+
+ GSettings *settings;
+ GSettings *lock_settings;
+ GSettings *interface_settings;
+
+ GnomeDesktopThumbnailFactory *thumb_factory;
+ GDBusProxy *proxy;
+
+ CcBackgroundItem *current_background;
+
+ CcBackgroundChooser *background_chooser;
+ CcBackgroundPreview *default_preview;
+ CcBackgroundPreview *dark_preview;
+ GtkToggleButton *default_toggle;
+ GtkToggleButton *dark_toggle;
+};
+
+CC_PANEL_REGISTER (CcBackgroundPanel, cc_background_panel)
+
+static void
+load_custom_css (CcBackgroundPanel *self)
+{
+ g_autoptr(GtkCssProvider) provider = NULL;
+
+ 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_display (gdk_display_get_default (),
+ GTK_STYLE_PROVIDER (provider),
+ GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
+}
+
+static void
+reload_color_scheme_toggles (CcBackgroundPanel *self)
+{
+ GDesktopColorScheme scheme;
+
+ scheme = g_settings_get_enum (self->interface_settings, INTERFACE_COLOR_SCHEME_KEY);
+
+ if (scheme == G_DESKTOP_COLOR_SCHEME_DEFAULT)
+ {
+ gtk_toggle_button_set_active (self->default_toggle, TRUE);
+ }
+ else if (scheme == G_DESKTOP_COLOR_SCHEME_PREFER_DARK)
+ {
+ gtk_toggle_button_set_active (self->dark_toggle, TRUE);
+ }
+ else
+ {
+ gtk_toggle_button_set_active (self->default_toggle, FALSE);
+ gtk_toggle_button_set_active (self->dark_toggle, FALSE);
+ }
+}
+
+static void
+transition_screen (CcBackgroundPanel *self)
+{
+ g_autoptr (GError) error = NULL;
+
+ if (!self->proxy)
+ return;
+
+ g_dbus_proxy_call_sync (self->proxy,
+ "ScreenTransition",
+ NULL,
+ G_DBUS_CALL_FLAGS_NONE,
+ -1,
+ NULL,
+ &error);
+
+ if (error)
+ g_warning ("Couldn't transition screen: %s", error->message);
+}
+
+static void
+set_color_scheme (CcBackgroundPanel *self,
+ GDesktopColorScheme color_scheme)
+{
+ GDesktopColorScheme scheme;
+
+ scheme = g_settings_get_enum (self->interface_settings,
+ INTERFACE_COLOR_SCHEME_KEY);
+
+ /* We have to check the equality manually to avoid starting an unnecessary
+ * screen transition */
+ if (color_scheme == scheme)
+ return;
+
+ transition_screen (self);
+
+ g_settings_set_enum (self->interface_settings,
+ INTERFACE_COLOR_SCHEME_KEY,
+ color_scheme);
+}
+
+/* Color schemes */
+
+static void
+on_color_scheme_toggle_active_cb (CcBackgroundPanel *self)
+{
+ if (gtk_toggle_button_get_active (self->default_toggle))
+ set_color_scheme (self, G_DESKTOP_COLOR_SCHEME_DEFAULT);
+ else if (gtk_toggle_button_get_active (self->dark_toggle))
+ set_color_scheme (self, G_DESKTOP_COLOR_SCHEME_PREFER_DARK);
+}
+
+static void
+got_transition_proxy_cb (GObject *source_object,
+ GAsyncResult *res,
+ gpointer data)
+{
+ g_autoptr(GError) error = NULL;
+ CcBackgroundPanel *self = data;
+
+ self->proxy = g_dbus_proxy_new_for_bus_finish (res, &error);
+
+ if (self->proxy == NULL)
+ {
+ g_warning ("Error creating proxy: %s", error->message);
+ return;
+ }
+}
+
+/* Background */
+
+static void
+update_preview (CcBackgroundPanel *panel)
+{
+ CcBackgroundItem *current_background;
+
+ current_background = panel->current_background;
+ cc_background_preview_set_item (panel->default_preview, current_background);
+ cc_background_preview_set_item (panel->dark_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 *dark_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);
+
+ dark_uri = g_settings_get_string (settings, WP_URI_DARK_KEY);
+ 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"),
+ "uri-dark", dark_uri,
+ "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,
+ gboolean set_dark)
+{
+ 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);
+
+ if (set_dark)
+ {
+ const char *uri_dark;
+
+ uri_dark = cc_background_item_get_uri_dark (item);
+
+ if (uri_dark && uri_dark[0])
+ g_settings_set_string (settings, WP_URI_DARK_KEY, uri_dark);
+ else
+ g_settings_set_string (settings, WP_URI_DARK_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, TRUE);
+ set_background (self, self->lock_settings, item, FALSE);
+}
+
+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_dispose (GObject *object)
+{
+ CcBackgroundPanel *panel = CC_BACKGROUND_PANEL (object);
+
+ g_clear_object (&panel->settings);
+ g_clear_object (&panel->lock_settings);
+ g_clear_object (&panel->interface_settings);
+ g_clear_object (&panel->thumb_factory);
+ g_clear_object (&panel->proxy);
+
+ 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->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, background_chooser);
+ gtk_widget_class_bind_template_child (widget_class, CcBackgroundPanel, default_preview);
+ gtk_widget_class_bind_template_child (widget_class, CcBackgroundPanel, dark_preview);
+ gtk_widget_class_bind_template_child (widget_class, CcBackgroundPanel, default_toggle);
+ gtk_widget_class_bind_template_child (widget_class, CcBackgroundPanel, dark_toggle);
+
+ gtk_widget_class_bind_template_callback (widget_class, on_color_scheme_toggle_active_cb);
+ 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);
+
+ panel->interface_settings = g_settings_new (INTERFACE_PATH_ID);
+
+ /* 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);
+
+ /* Interface settings */
+ reload_color_scheme_toggles (panel);
+
+ g_signal_connect_object (panel->interface_settings,
+ "changed::" INTERFACE_COLOR_SCHEME_KEY,
+ G_CALLBACK (reload_color_scheme_toggles),
+ panel,
+ G_CONNECT_SWAPPED);
+
+ g_dbus_proxy_new_for_bus (G_BUS_TYPE_SESSION,
+ G_DBUS_PROXY_FLAGS_NONE,
+ NULL,
+ "org.gnome.Shell",
+ "/org/gnome/Shell",
+ "org.gnome.Shell",
+ NULL,
+ got_transition_proxy_cb,
+ panel);
+
+ load_custom_css (panel);
+}
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..33a8638
--- /dev/null
+++ b/panels/background/cc-background-panel.ui
@@ -0,0 +1,134 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <template class="CcBackgroundPanel" parent="CcPanel">
+ <child type="content">
+ <object class="AdwPreferencesPage">
+
+ <child>
+ <object class="AdwPreferencesGroup">
+ <property name="title" translatable="yes">Style</property>
+
+ <child>
+ <object class="AdwPreferencesRow">
+ <property name="activatable">False</property>
+ <property name="focusable">False</property>
+ <child>
+ <object class="AdwClamp">
+ <property name="maximum_size">400</property>
+ <property name="tightening_threshold">300</property>
+ <child>
+ <object class="GtkGrid">
+ <property name="column-homogeneous">True</property>
+ <property name="column-spacing">24</property>
+ <property name="row-spacing">12</property>
+ <property name="margin-start">12</property>
+ <property name="margin-end">12</property>
+ <property name="margin-top">18</property>
+ <property name="margin-bottom">12</property>
+ <property name="hexpand">True</property>
+ <child>
+ <object class="GtkToggleButton" id="default_toggle">
+ <accessibility>
+ <relation name="labelled-by">default_label</relation>
+ </accessibility>
+ <signal name="notify::active" handler="on_color_scheme_toggle_active_cb" swapped="true"/>
+ <child>
+ <object class="CcBackgroundPreview" id="default_preview"/>
+ </child>
+ <style>
+ <class name="background-preview-button"/>
+ </style>
+ <layout>
+ <property name="column">0</property>
+ <property name="row">0</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel" id="default_label">
+ <property name="label" translatable="yes">Default</property>
+ <layout>
+ <property name="column">0</property>
+ <property name="row">1</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkToggleButton" id="dark_toggle">
+ <property name="group">default_toggle</property>
+ <accessibility>
+ <relation name="labelled-by">dark_label</relation>
+ </accessibility>
+ <signal name="notify::active" handler="on_color_scheme_toggle_active_cb" swapped="true"/>
+ <child>
+ <object class="CcBackgroundPreview" id="dark_preview">
+ <property name="is-dark">True</property>
+ </object>
+ </child>
+ <style>
+ <class name="background-preview-button"/>
+ </style>
+ <layout>
+ <property name="column">1</property>
+ <property name="row">0</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel" id="dark_label">
+ <property name="label" translatable="yes">Dark</property>
+ <layout>
+ <property name="column">1</property>
+ <property name="row">1</property>
+ </layout>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+
+ </object>
+ </child>
+
+ <child>
+ <object class="AdwPreferencesGroup">
+ <property name="title" translatable="yes">Background</property>
+ <property name="header-suffix">
+ <object class="GtkButton">
+ <child>
+ <object class="AdwButtonContent">
+ <property name="icon-name">list-add-symbolic</property>
+ <property name="label" translatable="yes">Add Picture…</property>
+ </object>
+ </child>
+ <signal name="clicked" handler="on_add_picture_button_clicked_cb" object="CcBackgroundPanel" swapped="yes" />
+ <style>
+ <class name="flat"/>
+ </style>
+ </object>
+ </property>
+
+ <child>
+ <object class="AdwBin">
+ <style>
+ <class name="card"/>
+ </style>
+ <child>
+ <object class="CcBackgroundChooser" id="background_chooser">
+ <property name="hexpand">True</property>
+ <signal name="background-chosen" handler="on_chooser_background_chosen_cb" object="CcBackgroundPanel" swapped="yes" />
+ </object>
+ </child>
+ </object>
+ </child>
+
+ </object>
+ </child>
+
+ </object>
+ </child>
+ </template>
+</interface>
diff --git a/panels/background/cc-background-preview.c b/panels/background/cc-background-preview.c
new file mode 100644
index 0000000..428c44f
--- /dev/null
+++ b/panels/background/cc-background-preview.c
@@ -0,0 +1,351 @@
+/* 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
+{
+ GtkWidget parent;
+
+ GtkWidget *drawing_area;
+ GtkWidget *light_dark_window;
+ GtkWidget *dark_window;
+
+ GnomeDesktopThumbnailFactory *thumbnail_factory;
+
+ gboolean is_dark;
+ CcBackgroundItem *item;
+};
+
+G_DEFINE_TYPE (CcBackgroundPreview, cc_background_preview, GTK_TYPE_WIDGET)
+
+enum
+{
+ PROP_0,
+ PROP_IS_DARK,
+ PROP_ITEM,
+ N_PROPS
+};
+
+static GParamSpec *properties [N_PROPS];
+
+/* Callbacks */
+
+static void
+draw_preview_func (GtkDrawingArea *drawing_area,
+ cairo_t *cr,
+ gint width,
+ gint height,
+ gpointer user_data)
+{
+ CcBackgroundPreview *self = CC_BACKGROUND_PREVIEW (user_data);
+ g_autoptr(GdkPixbuf) pixbuf = NULL;
+ gint scale_factor;
+
+ if (!self->item)
+ return;
+
+ scale_factor = gtk_widget_get_scale_factor (GTK_WIDGET (drawing_area));
+ pixbuf = cc_background_item_get_frame_thumbnail (self->item,
+ self->thumbnail_factory,
+ width,
+ height,
+ scale_factor,
+ 0,
+ TRUE,
+ self->is_dark &&
+ cc_background_item_has_dark_version (self->item));
+
+
+ gdk_cairo_set_source_pixbuf (cr, pixbuf, 0, 0);
+ cairo_paint (cr);
+}
+
+/* GObject overrides */
+
+static void
+cc_background_preview_dispose (GObject *object)
+{
+ CcBackgroundPreview *self = (CcBackgroundPreview *)object;
+
+ g_clear_pointer (&self->drawing_area, gtk_widget_unparent);
+ g_clear_pointer (&self->light_dark_window, gtk_widget_unparent);
+ g_clear_pointer (&self->dark_window, gtk_widget_unparent);
+
+ G_OBJECT_CLASS (cc_background_preview_parent_class)->dispose (object);
+}
+
+static void
+cc_background_preview_finalize (GObject *object)
+{
+ CcBackgroundPreview *self = (CcBackgroundPreview *)object;
+
+ g_clear_object (&self->item);
+ g_clear_object (&self->thumbnail_factory);
+
+ G_OBJECT_CLASS (cc_background_preview_parent_class)->finalize (object);
+}
+
+static void
+set_is_dark (CcBackgroundPreview *self,
+ gboolean is_dark)
+{
+ self->is_dark = is_dark;
+
+ if (self->is_dark)
+ {
+ gtk_widget_add_css_class (self->light_dark_window, "dark");
+ gtk_widget_remove_css_class (self->light_dark_window, "light");
+ }
+ else
+ {
+ gtk_widget_add_css_class (self->light_dark_window, "light");
+ gtk_widget_remove_css_class (self->light_dark_window, "dark");
+ }
+}
+
+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_DARK:
+ g_value_set_boolean (value, self->is_dark);
+ 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_DARK:
+ set_is_dark (self, g_value_get_boolean (value));
+ 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 void
+get_primary_monitor_geometry (int *width, int *height)
+{
+ GdkDisplay *display;
+ GListModel *monitors;
+
+ display = gdk_display_get_default ();
+
+ monitors = gdk_display_get_monitors (display);
+ if (monitors)
+ {
+ g_autoptr(GdkMonitor) primary_monitor = NULL;
+ GdkRectangle monitor_layout;
+
+ primary_monitor = g_list_model_get_item (monitors, 0);
+ gdk_monitor_get_geometry (primary_monitor, &monitor_layout);
+ if (width)
+ *width = monitor_layout.width;
+ if (height)
+ *height = monitor_layout.height;
+
+ return;
+ }
+
+ if (width)
+ *width = 1920;
+ if (height)
+ *height = 1080;
+}
+
+static void
+cc_background_preview_measure (GtkWidget *widget,
+ GtkOrientation orientation,
+ gint for_size,
+ gint *minimum,
+ gint *natural,
+ gint *minimum_baseline,
+ gint *natural_baseline)
+{
+ GtkWidget *child;
+ int width;
+
+ get_primary_monitor_geometry (&width, NULL);
+
+ if (orientation == GTK_ORIENTATION_HORIZONTAL)
+ *natural = width;
+ else if (for_size < 0)
+ *natural = 0;
+ else
+ *natural = floor ((double) for_size * 0.75); /* 4:3 aspect ratio */
+
+ if (orientation == GTK_ORIENTATION_VERTICAL)
+ *minimum = *natural;
+ else
+ *minimum = 0;
+
+ for (child = gtk_widget_get_first_child (widget);
+ child;
+ child = gtk_widget_get_next_sibling (child))
+ {
+ int child_min, child_nat;
+
+ gtk_widget_measure (child, orientation, for_size,
+ &child_min, &child_nat, NULL, NULL);
+
+ *minimum = MAX (*minimum, child_min);
+ *natural = MAX (*natural, child_nat);
+ }
+}
+
+static void
+cc_background_preview_size_allocate (GtkWidget *widget,
+ gint width,
+ gint height,
+ gint baseline)
+{
+ CcBackgroundPreview *self = CC_BACKGROUND_PREVIEW (widget);
+ int window_width, window_height, margin_x, margin_y;
+ int opposite_margin_x, opposite_margin_y;
+ GskTransform *front_transform, *back_transform;
+ gboolean is_rtl;
+
+ window_width = ceil (width * 0.5);
+ window_height = ceil (height * 0.5);
+ margin_x = floor (width * 0.15);
+ margin_y = floor (height * 0.15);
+ opposite_margin_x = width - window_width - margin_x;
+ opposite_margin_y = height - window_height - margin_y;
+ is_rtl = gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL;
+
+ front_transform =
+ gsk_transform_translate (NULL, &GRAPHENE_POINT_INIT (is_rtl ? opposite_margin_x : margin_x,
+ opposite_margin_y));
+ back_transform =
+ gsk_transform_translate (NULL, &GRAPHENE_POINT_INIT (is_rtl ? margin_x : opposite_margin_x,
+ margin_y));
+
+ gtk_widget_allocate (self->drawing_area, width, height, baseline, NULL);
+ gtk_widget_allocate (self->dark_window, window_width, window_height,
+ baseline, back_transform);
+ gtk_widget_allocate (self->light_dark_window, window_width, window_height,
+ baseline, front_transform);
+}
+
+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->dispose = cc_background_preview_dispose;
+ 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->measure = cc_background_preview_measure;
+ widget_class->size_allocate = cc_background_preview_size_allocate;
+
+ properties[PROP_IS_DARK] = g_param_spec_boolean ("is-dark",
+ "Is dark",
+ "Whether the preview is dark",
+ 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, drawing_area);
+ gtk_widget_class_bind_template_child (widget_class, CcBackgroundPreview, light_dark_window);
+ gtk_widget_class_bind_template_child (widget_class, CcBackgroundPreview, dark_window);
+
+ gtk_widget_class_set_css_name (widget_class, "background-preview");
+}
+
+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);
+}
+
+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_drawing_area_set_draw_func (GTK_DRAWING_AREA (self->drawing_area),
+ draw_preview_func, self, NULL);
+ gtk_widget_queue_draw (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..40393c0
--- /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, GtkWidget)
+
+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..4c4f573
--- /dev/null
+++ b/panels/background/cc-background-preview.ui
@@ -0,0 +1,55 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <!-- interface-requires gtk+ 3.0 -->
+ <template class="CcBackgroundPreview" parent="GtkWidget">
+ <property name="overflow">hidden</property>
+ <property name="width-request">2</property>
+ <property name="height-request">2</property>
+
+ <!-- Wallpaper -->
+ <child>
+ <object class="GtkDrawingArea" id="drawing_area"/>
+ </child>
+
+ <!-- Always dark window -->
+ <child>
+ <object class="AdwBin" id="dark_window">
+ <property name="overflow">hidden</property>
+ <style>
+ <class name="window"/>
+ <class name="back"/>
+ <class name="dark"/>
+ </style>
+ <child>
+ <object class="AdwBin">
+ <style>
+ <class name="header-bar"/>
+ </style>
+ <property name="valign">start</property>
+ </object>
+ </child>
+ </object>
+ </child>
+
+ <!-- Light/dark window -->
+ <child>
+ <object class="AdwBin" id="light_dark_window">
+ <property name="overflow">hidden</property>
+ <style>
+ <class name="window"/>
+ <class name="front"/>
+ <class name="light"/>
+ </style>
+ <child>
+ <object class="AdwBin">
+ <style>
+ <class name="header-bar"/>
+ </style>
+ <property name="valign">start</property>
+ </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..84f0a04
--- /dev/null
+++ b/panels/background/cc-background-xml.c
@@ -0,0 +1,669 @@
+/*
+ * 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, "filename-dark")) {
+ 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_DARK);
+ g_object_set (G_OBJECT (item), "uri-dark", 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..a6a044c
--- /dev/null
+++ b/panels/background/gnome-background-panel.desktop.in.in
@@ -0,0 +1,14 @@
+[Desktop Entry]
+Name=Appearance
+Comment=Change your background image or the UI colors
+Exec=gnome-control-center background
+# Translators: Do NOT translate or transliterate this text (this is an icon file name)!
+Icon=org.gnome.Settings-appearance-symbolic
+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 Appearance panel. Do NOT translate or localize the semicolons! The list MUST also end with a semicolon!
+Keywords=Background;Wallpaper;Screen;Desktop;Style;Light;Dark;Appearance;
diff --git a/panels/background/icons/meson.build b/panels/background/icons/meson.build
new file mode 100644
index 0000000..53082c1
--- /dev/null
+++ b/panels/background/icons/meson.build
@@ -0,0 +1,4 @@
+install_data(
+ 'scalable/org.gnome.Settings-appearance-symbolic.svg',
+ install_dir: join_paths(control_center_icondir, 'hicolor', 'scalable', 'apps')
+)
diff --git a/panels/background/icons/scalable/org.gnome.Settings-appearance-symbolic.svg b/panels/background/icons/scalable/org.gnome.Settings-appearance-symbolic.svg
new file mode 100644
index 0000000..7190bbd
--- /dev/null
+++ b/panels/background/icons/scalable/org.gnome.Settings-appearance-symbolic.svg
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg height="16px" viewBox="0 0 16 16" width="16px" xmlns="http://www.w3.org/2000/svg">
+ <g fill="#2e3436">
+ <path d="m 3.011719 1 c -1.644531 0 -3.0000002 1.355469 -3.0000002 3 v 6 c 0 1.644531 1.3554692 3 3.0000002 3 h 10 c 1.644531 0 3 -1.355469 3 -3 v -6 c 0 -0.570312 -0.167969 -1.101562 -0.449219 -1.558594 l -1.550781 1.554688 v 6.003906 c 0 0.570312 -0.429688 1 -1 1 h -10 c -0.570313 0 -1 -0.429688 -1 -1 v -6 c 0 -0.570312 0.429687 -1 1 -1 h 5.96875 l 2.007812 -2 z m 0 0"/>
+ <path d="m 11.011719 7 c 0 1.65625 -1.339844 3.007812 -3 3 h -3 v -3 c 0 -1.660156 1.34375 -3 3 -3 c 1.660156 0 3 1.339844 3 3 z m 0 0"/>
+ <path d="m 13.410156 0 l -3.46875 3.457031 c 0.683594 0.355469 1.234375 0.910157 1.589844 1.589844 l 0.171875 -0.171875 l 0.007813 0.007812 l 4.300781 -4.300781 v -0.582031 z m 0 0"/>
+ <path d="m 5.011719 14 c -1.105469 0 -2 0.894531 -2 2 h 10 c 0 -1.105469 -0.894531 -2 -2 -2 z m 0 0"/>
+ </g>
+</svg>
diff --git a/panels/background/meson.build b/panels/background/meson.build
new file mode 100644
index 0000000..3634c47
--- /dev/null
+++ b/panels/background/meson.build
@@ -0,0 +1,99 @@
+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(
+ type: 'desktop',
+ input: desktop_in,
+ output: desktop,
+ po_dir: po_dir,
+ install: true,
+ install_dir: control_center_desktopdir
+)
+
+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-recent-source.c',
+ 'bg-source.c',
+ 'bg-wallpapers-source.c',
+ 'cc-background-chooser.c',
+ 'cc-background-item.c',
+ 'cc-background-paintable.c',
+ 'cc-background-panel.c',
+ 'cc-background-preview.c',
+ 'cc-background-xml.c',
+)
+
+deps = common_deps + [
+ gdk_pixbuf_dep,
+ gnome_bg_dep,
+ libxml_dep,
+ dependency('cairo-gobject'),
+]
+
+cflags += [
+ '-DDATADIR="@0@"'.format(control_center_datadir),
+ '-DGNOME_DESKTOP_USE_UNSTABLE_API'
+]
+
+panels_libs += static_library(
+ cappletname,
+ sources: sources,
+ include_directories: top_inc,
+ dependencies: deps,
+ c_args: cflags,
+)
+
+subdir('icons')
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..e949734
--- /dev/null
+++ b/panels/background/preview.css
@@ -0,0 +1,96 @@
+background-preview {
+ border-radius: 6px;
+}
+
+background-preview .window {
+ border-radius: 6px;
+ box-shadow: 0 1px 4px 1px alpha(black, 0.13),
+ 0 1px 10px 5px alpha(black, 0.09),
+ 0 3px 16px 8px alpha(black, 0.04),
+ 0 0 0 1px alpha(black, .05);
+}
+
+background-preview .window .header-bar {
+ min-height: 15px;
+}
+
+background-preview .window.light {
+ background-color: #fafafa;
+ color: alpha(black, .8);
+}
+
+background-preview .window.light .header-bar {
+ box-shadow: inset 0 -1px alpha(black, .07);
+}
+
+background-preview .window.front.light .header-bar {
+ background-color: #ebebeb;
+}
+
+background-preview .window.dark {
+ background-color: #242424;
+ color: white;
+}
+
+background-preview .window.dark .header-bar {
+ box-shadow: inset 0 -1px alpha(black, .36);
+}
+
+background-preview .window.front.dark .header-bar {
+ background-color: #303030;
+}
+
+.background-preview-button {
+ background: none;
+ border-radius: 9px;
+ padding: 3px;
+ box-shadow: none;
+ outline: none;
+}
+
+.background-preview-button:checked {
+ box-shadow: 0 0 0 3px @accent_color;
+}
+
+.background-preview-button:focus:focus-visible {
+ box-shadow: 0 0 0 3px alpha(@accent_color, .3);
+}
+
+.background-preview-button:checked:focus:focus-visible {
+ box-shadow: 0 0 0 3px @accent_color, 0 0 0 6px alpha(@accent_color, .3);
+}
+
+.background-flowbox > flowboxchild {
+ background: none;
+ border-radius: 9px;
+}
+
+.background-thumbnail {
+ border-radius: 6px;
+}
+
+.slideshow-icon {
+ color: white;
+ -gtk-icon-shadow: 0 1px 2px rgba(0, 0, 0, 0.33);
+ margin: 8px;
+}
+
+.selected-check {
+ color: @accent_fg_color;
+ background: @accent_bg_color;
+ border-radius: 100px;
+ padding: 2px;
+ opacity: 0;
+ margin: 6px;
+}
+
+flowboxchild:selected .selected-check {
+ opacity: 1;
+}
+
+.remove-button {
+ padding: 2px;
+ min-width: 0;
+ min-height: 0;
+ margin: 6px;
+}
diff --git a/panels/background/slideshow-symbolic.svg b/panels/background/slideshow-symbolic.svg
new file mode 100644
index 0000000..77350c3
--- /dev/null
+++ b/panels/background/slideshow-symbolic.svg
@@ -0,0 +1 @@
+<svg width="16" height="16" xmlns="http://www.w3.org/2000/svg"><path d="M7.486.02A7.492 7.492 0 0 0 0 7.508a7.492 7.492 0 0 0 7.486 7.484 7.492 7.492 0 0 0 7.487-7.484A7.492 7.492 0 0 0 7.486.02zm0 1.972A5.508 5.508 0 0 1 13 7.508a5.508 5.508 0 0 1-5.514 5.512 5.508 5.508 0 0 1-5.513-5.512 5.508 5.508 0 0 1 5.513-5.516zm3.01 2.01a.5.5 0 0 0-.103.006.5.5 0 0 0-.25.154L7.486 6.818 5.83 5.162a.5.5 0 1 0-.687.688l2 2a.5.5 0 0 0 .687 0l3-3a.5.5 0 0 0-.334-.848z" style="fill:#000"/></svg> \ No newline at end of file