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