diff options
Diffstat (limited to 'libmate-desktop/mate-bg.c')
-rw-r--r-- | libmate-desktop/mate-bg.c | 2930 |
1 files changed, 2930 insertions, 0 deletions
diff --git a/libmate-desktop/mate-bg.c b/libmate-desktop/mate-bg.c new file mode 100644 index 0000000..3b0f684 --- /dev/null +++ b/libmate-desktop/mate-bg.c @@ -0,0 +1,2930 @@ +/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- + +matebg.c: Object for the desktop background. + +Copyright (C) 2000 Eazel, Inc. +Copyright (C) 2007-2008 Red Hat, Inc. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU Library 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 +Library General Public License for more details. + +You should have received a copy of the GNU Library General Public +License along with this program; if not, write to the +Free Software Foundation, Inc., 59 Temple Place - Suite 330, +Boston, MA 02111-1307, USA. + +Derived from eel-background.c and eel-gdk-pixbuf-extensions.c by +Darin Adler <[email protected]> and Ramiro Estrugo <[email protected]> + +Author: Soren Sandmann <[email protected]> + +*/ + +#include <string.h> +#include <math.h> +#include <stdarg.h> +#include <stdlib.h> + +#include <gio/gio.h> + +#include <gdk/gdkx.h> +#include <X11/Xlib.h> +#include <X11/Xatom.h> + +#include <cairo.h> + +#include <mateconf/mateconf-client.h> + +#define MATE_DESKTOP_USE_UNSTABLE_API +#include <libmateui/mate-bg.h> +#include <libmateui/mate-bg-crossfade.h> + +#define BG_KEY_DRAW_BACKGROUND MATE_BG_KEY_DIR "/draw_background" +#define BG_KEY_PRIMARY_COLOR MATE_BG_KEY_DIR "/primary_color" +#define BG_KEY_SECONDARY_COLOR MATE_BG_KEY_DIR "/secondary_color" +#define BG_KEY_COLOR_TYPE MATE_BG_KEY_DIR "/color_shading_type" +#define BG_KEY_PICTURE_PLACEMENT MATE_BG_KEY_DIR "/picture_options" +#define BG_KEY_PICTURE_OPACITY MATE_BG_KEY_DIR "/picture_opacity" +#define BG_KEY_PICTURE_FILENAME MATE_BG_KEY_DIR "/picture_filename" + +/* We keep the large pixbufs around if the next update + in the slideshow is less than 60 seconds away */ +#define KEEP_EXPENSIVE_CACHE_SECS 60 + +typedef struct _SlideShow SlideShow; +typedef struct _Slide Slide; + +struct _Slide { + double duration; /* in seconds */ + gboolean fixed; + + GSList* file1; + GSList* file2; /* NULL if fixed is TRUE */ +}; + +typedef struct _FileSize FileSize; +struct _FileSize { + gint width; + gint height; + + char* file; +}; + +/* This is the size of the GdkRGB dither matrix, in order to avoid + * bad dithering when tiling the gradient + */ +#define GRADIENT_PIXMAP_TILE_SIZE 128 + +typedef struct FileCacheEntry FileCacheEntry; +#define CACHE_SIZE 4 + +/* + * Implementation of the MateBG class + */ +struct _MateBG { + GObject parent_instance; + char* filename; + MateBGPlacement placement; + MateBGColorType color_type; + GdkColor primary; + GdkColor secondary; + + gint last_pixmap_width; + gint last_pixmap_height; + + GFileMonitor* file_monitor; + + guint changed_id; + guint transitioned_id; + guint blow_caches_id; + + /* Cached information, only access through cache accessor functions */ + SlideShow* slideshow; + time_t file_mtime; + GdkPixbuf* pixbuf_cache; + int timeout_id; + + GList* file_cache; +}; + +struct _MateBGClass { + GObjectClass parent_class; +}; + +enum { + CHANGED, + TRANSITIONED, + N_SIGNALS +}; + +static guint signals[N_SIGNALS] = {0}; + +G_DEFINE_TYPE(MateBG, mate_bg, G_TYPE_OBJECT) + +static GdkPixmap* make_root_pixmap(GdkScreen* screen, gint width, gint height); + +/* Pixbuf utils */ +static guint32 pixbuf_average_value (GdkPixbuf *pixbuf); +static GdkPixbuf *pixbuf_scale_to_fit (GdkPixbuf *src, + int max_width, + int max_height); +static GdkPixbuf *pixbuf_scale_to_min (GdkPixbuf *src, + int min_width, + int min_height); +static void pixbuf_draw_gradient (GdkPixbuf *pixbuf, + gboolean horizontal, + GdkColor *c1, + GdkColor *c2, + GdkRectangle *rect); +static void pixbuf_tile (GdkPixbuf *src, + GdkPixbuf *dest); +static void pixbuf_blend (GdkPixbuf *src, + GdkPixbuf *dest, + int src_x, + int src_y, + int width, + int height, + int dest_x, + int dest_y, + double alpha); + +/* Thumbnail utilities */ +static GdkPixbuf *create_thumbnail_for_filename (MateDesktopThumbnailFactory *factory, + const char *filename); +static gboolean get_thumb_annotations (GdkPixbuf *thumb, + int *orig_width, + int *orig_height); + +/* Cache */ +static GdkPixbuf *get_pixbuf_for_size (MateBG *bg, + int width, + int height); +static void clear_cache (MateBG *bg); +static gboolean is_different (MateBG *bg, + const char *filename); +static time_t get_mtime (const char *filename); +static GdkPixbuf *create_img_thumbnail (MateBG *bg, + MateDesktopThumbnailFactory *factory, + GdkScreen *screen, + int dest_width, + int dest_height, + int frame_num); +static SlideShow * get_as_slideshow (MateBG *bg, + const char *filename); +static Slide * get_current_slide (SlideShow *show, + double *alpha); +static gboolean slideshow_has_multiple_sizes (SlideShow *show); + +static SlideShow *read_slideshow_file (const char *filename, + GError **err); +static SlideShow *slideshow_ref (SlideShow *show); +static void slideshow_unref (SlideShow *show); + +static FileSize *find_best_size (GSList *sizes, + gint width, + gint height); + +static void +color_from_string (const char *string, + GdkColor *colorp) +{ + /* If all else fails use black */ + gdk_color_parse ("black", colorp); + + if (!string) + return; + + gdk_color_parse (string, colorp); +} + +static char * +color_to_string (const GdkColor *color) +{ + return g_strdup_printf ("#%02x%02x%02x", + color->red >> 8, + color->green >> 8, + color->blue >> 8); +} + +static MateConfEnumStringPair placement_lookup[] = { + { MATE_BG_PLACEMENT_CENTERED, "centered" }, + { MATE_BG_PLACEMENT_FILL_SCREEN, "stretched" }, + { MATE_BG_PLACEMENT_SCALED, "scaled" }, + { MATE_BG_PLACEMENT_ZOOMED, "zoom" }, + { MATE_BG_PLACEMENT_TILED, "wallpaper" }, + { MATE_BG_PLACEMENT_SPANNED, "spanned" }, + { 0, NULL } +}; + +static MateConfEnumStringPair color_type_lookup[] = { + { MATE_BG_COLOR_SOLID, "solid" }, + { MATE_BG_COLOR_H_GRADIENT, "horizontal-gradient" }, + { MATE_BG_COLOR_V_GRADIENT, "vertical-gradient" }, + { 0, NULL } +}; + +static void +color_type_from_string (const char *string, + MateBGColorType *color_type) +{ + *color_type = MATE_BG_COLOR_SOLID; + + if (string) { + mateconf_string_to_enum (color_type_lookup, + string, (int *)color_type); + } +} + +static const char * +color_type_to_string (MateBGColorType color_type) +{ + return mateconf_enum_to_string (color_type_lookup, color_type); +} + +static void +placement_from_string (const char *string, + MateBGPlacement *placement) +{ + *placement = MATE_BG_PLACEMENT_ZOOMED; + + if (string) { + mateconf_string_to_enum (placement_lookup, + string, (int *)placement); + } +} + +static const char * +placement_to_string (MateBGPlacement placement) +{ + return mateconf_enum_to_string (placement_lookup, placement); +} + +static gboolean +do_changed (MateBG *bg) +{ + gboolean ignore_pending_change; + bg->changed_id = 0; + + ignore_pending_change = + GPOINTER_TO_INT (g_object_get_data (G_OBJECT (bg), + "ignore-pending-change")); + + if (!ignore_pending_change) { + g_signal_emit (G_OBJECT (bg), signals[CHANGED], 0); + } + + return FALSE; +} + +static void +queue_changed (MateBG *bg) +{ + if (bg->changed_id > 0) { + g_source_remove (bg->changed_id); + } + + /* We unset this here to allow apps to set it if they don't want + to get the change event. This is used by caja when it + gets the pixmap from the bg (due to a reason other than the changed + event). Because if there is no other change after this time the + pending changed event will just uselessly cause us to recreate + the pixmap. */ + g_object_set_data (G_OBJECT (bg), "ignore-pending-change", + GINT_TO_POINTER (FALSE)); + bg->changed_id = g_timeout_add_full (G_PRIORITY_LOW, + 100, + (GSourceFunc)do_changed, + bg, + NULL); +} + +static gboolean +do_transitioned (MateBG *bg) +{ + bg->transitioned_id = 0; + + if (bg->pixbuf_cache) { + g_object_unref (bg->pixbuf_cache); + bg->pixbuf_cache = NULL; + } + + g_signal_emit (G_OBJECT (bg), signals[TRANSITIONED], 0); + + return FALSE; +} + +static void +queue_transitioned (MateBG *bg) +{ + if (bg->transitioned_id > 0) { + g_source_remove (bg->transitioned_id); + } + + bg->transitioned_id = g_timeout_add_full (G_PRIORITY_LOW, + 100, + (GSourceFunc)do_transitioned, + bg, + NULL); +} + +void +mate_bg_load_from_preferences (MateBG *bg, + MateConfClient *client) +{ + char *tmp; + char *filename; + MateBGColorType ctype; + GdkColor c1, c2; + MateBGPlacement placement; + + g_return_if_fail (MATE_IS_BG (bg)); + g_return_if_fail (client != NULL); + + /* Filename */ + filename = NULL; + tmp = mateconf_client_get_string (client, BG_KEY_PICTURE_FILENAME, NULL); + if (tmp != NULL && *tmp != '\0') { + if (g_utf8_validate (tmp, -1, NULL) && + g_file_test (tmp, G_FILE_TEST_EXISTS)) { + filename = g_strdup (tmp); + } else { + filename = g_filename_from_utf8 (tmp, -1, NULL, NULL, NULL); + } + + /* Fall back to default background if filename was set + but no longer exists */ + if (!g_file_test (filename, G_FILE_TEST_EXISTS)) { + MateConfValue *default_value; + + g_free (filename); + filename = NULL; + + default_value = + mateconf_client_get_default_from_schema (client, + BG_KEY_PICTURE_FILENAME, + NULL); + if (default_value != NULL) { + filename = g_strdup (mateconf_value_get_string (default_value)); + mateconf_value_free (default_value); + } + } + } + g_free (tmp); + + /* Colors */ + tmp = mateconf_client_get_string (client, BG_KEY_PRIMARY_COLOR, NULL); + color_from_string (tmp, &c1); + g_free (tmp); + + tmp = mateconf_client_get_string (client, BG_KEY_SECONDARY_COLOR, NULL); + color_from_string (tmp, &c2); + g_free (tmp); + + /* Color type */ + tmp = mateconf_client_get_string (client, BG_KEY_COLOR_TYPE, NULL); + color_type_from_string (tmp, &ctype); + g_free (tmp); + + /* Placement */ + tmp = mateconf_client_get_string (client, BG_KEY_PICTURE_PLACEMENT, NULL); + placement_from_string (tmp, &placement); + g_free (tmp); + + mate_bg_set_color (bg, ctype, &c1, &c2); + mate_bg_set_placement (bg, placement); + mate_bg_set_filename (bg, filename); + + g_free (filename); +} + +void +mate_bg_save_to_preferences (MateBG *bg, + MateConfClient *client) +{ + const char *color_type; + const char *placement; + const gchar *filename; + gchar *primary; + gchar *secondary; + + primary = color_to_string (&bg->primary); + secondary = color_to_string (&bg->secondary); + + color_type = color_type_to_string (bg->color_type); + + if (bg->filename) { + filename = bg->filename; + placement = placement_to_string (bg->placement); + } + else { + filename = "(none)"; + placement = "none"; + } + + mateconf_client_set_string (client, BG_KEY_PICTURE_FILENAME, filename, NULL); + mateconf_client_set_string (client, BG_KEY_PRIMARY_COLOR, primary, NULL); + mateconf_client_set_string (client, BG_KEY_SECONDARY_COLOR, secondary, NULL); + mateconf_client_set_string (client, BG_KEY_COLOR_TYPE, color_type, NULL); + mateconf_client_set_string (client, BG_KEY_PICTURE_PLACEMENT, placement, NULL); + + g_free (primary); + g_free (secondary); +} + + +static void +mate_bg_init (MateBG *bg) +{ +} + +static void +mate_bg_dispose (GObject *object) +{ + MateBG *bg = MATE_BG (object); + + if (bg->file_monitor) { + g_object_unref (bg->file_monitor); + bg->file_monitor = NULL; + } + + clear_cache (bg); + + G_OBJECT_CLASS (mate_bg_parent_class)->dispose (object); +} + +static void +mate_bg_finalize (GObject *object) +{ + MateBG *bg = MATE_BG (object); + + if (bg->changed_id != 0) { + g_source_remove (bg->changed_id); + bg->changed_id = 0; + } + + if (bg->transitioned_id != 0) { + g_source_remove (bg->transitioned_id); + bg->transitioned_id = 0; + } + + if (bg->blow_caches_id != 0) { + g_source_remove (bg->blow_caches_id); + bg->blow_caches_id = 0; + } + + if (bg->filename) { + g_free (bg->filename); + bg->filename = NULL; + } + + G_OBJECT_CLASS (mate_bg_parent_class)->finalize (object); +} + +static void +mate_bg_class_init (MateBGClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->dispose = mate_bg_dispose; + object_class->finalize = mate_bg_finalize; + + signals[CHANGED] = g_signal_new ("changed", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + 0, + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + + signals[TRANSITIONED] = g_signal_new ("transitioned", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + 0, + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); +} + +MateBG * +mate_bg_new (void) +{ + return g_object_new (MATE_TYPE_BG, NULL); +} + +void +mate_bg_set_color (MateBG *bg, + MateBGColorType type, + GdkColor *primary, + GdkColor *secondary) +{ + g_return_if_fail (bg != NULL); + + if (bg->color_type != type || + !gdk_color_equal (&bg->primary, primary) || + (secondary && !gdk_color_equal (&bg->secondary, secondary))) { + + bg->color_type = type; + bg->primary = *primary; + if (secondary) { + bg->secondary = *secondary; + } + + queue_changed (bg); + } +} + +void +mate_bg_set_placement (MateBG *bg, + MateBGPlacement placement) +{ + g_return_if_fail (bg != NULL); + + if (bg->placement != placement) { + bg->placement = placement; + + queue_changed (bg); + } +} + +MateBGPlacement +mate_bg_get_placement (MateBG *bg) +{ + g_return_val_if_fail (bg != NULL, -1); + + return bg->placement; +} + +void +mate_bg_get_color (MateBG *bg, + MateBGColorType *type, + GdkColor *primary, + GdkColor *secondary) +{ + g_return_if_fail (bg != NULL); + + if (type) + *type = bg->color_type; + + if (primary) + *primary = bg->primary; + + if (secondary) + *secondary = bg->secondary; +} + +const gchar * +mate_bg_get_filename (MateBG *bg) +{ + g_return_val_if_fail (bg != NULL, NULL); + + return bg->filename; +} + +static void +file_changed (GFileMonitor *file_monitor, + GFile *child, + GFile *other_file, + GFileMonitorEvent event_type, + gpointer user_data) +{ + MateBG *bg = MATE_BG (user_data); + + clear_cache (bg); + queue_changed (bg); +} + +void +mate_bg_set_filename (MateBG *bg, + const char *filename) +{ + g_return_if_fail (bg != NULL); + + if (is_different (bg, filename)) { + char *tmp = g_strdup (filename); + + g_free (bg->filename); + + bg->filename = tmp; + bg->file_mtime = get_mtime (bg->filename); + + if (bg->file_monitor) { + g_object_unref (bg->file_monitor); + bg->file_monitor = NULL; + } + + if (bg->filename) { + GFile *f = g_file_new_for_path (bg->filename); + + bg->file_monitor = g_file_monitor_file (f, 0, NULL, NULL); + g_signal_connect (bg->file_monitor, "changed", + G_CALLBACK (file_changed), bg); + + g_object_unref (f); + } + + clear_cache (bg); + + queue_changed (bg); + } +} + +static void +draw_color_area (MateBG *bg, + GdkPixbuf *dest, + GdkRectangle *rect) +{ + guint32 pixel; + GdkRectangle extent; + + extent.x = 0; + extent.y = 0; + extent.width = gdk_pixbuf_get_width (dest); + extent.height = gdk_pixbuf_get_height (dest); + + gdk_rectangle_intersect (rect, &extent, rect); + + switch (bg->color_type) { + case MATE_BG_COLOR_SOLID: + /* not really a big deal to ignore the area of interest */ + pixel = ((bg->primary.red >> 8) << 24) | + ((bg->primary.green >> 8) << 16) | + ((bg->primary.blue >> 8) << 8) | + (0xff); + + gdk_pixbuf_fill (dest, pixel); + break; + + case MATE_BG_COLOR_H_GRADIENT: + pixbuf_draw_gradient (dest, TRUE, &(bg->primary), &(bg->secondary), rect); + break; + + case MATE_BG_COLOR_V_GRADIENT: + pixbuf_draw_gradient (dest, FALSE, &(bg->primary), &(bg->secondary), rect); + break; + + default: + break; + } +} + +static void +draw_color (MateBG *bg, + GdkPixbuf *dest, + GdkScreen *screen) +{ + GdkRectangle rect; + rect.x = 0; + rect.y = 0; + rect.width = gdk_pixbuf_get_width (dest); + rect.height = gdk_pixbuf_get_height (dest); + draw_color_area (bg, dest, &rect); +} + +static void +draw_color_each_monitor (MateBG *bg, + GdkPixbuf *dest, + GdkScreen *screen) +{ + GdkRectangle rect; + gint num_monitors; + int monitor; + + num_monitors = gdk_screen_get_n_monitors (screen); + for (monitor = 0; monitor < num_monitors; monitor++) { + gdk_screen_get_monitor_geometry (screen, monitor, &rect); + draw_color_area (bg, dest, &rect); + } +} + +static GdkPixbuf * +pixbuf_clip_to_fit (GdkPixbuf *src, + int max_width, + int max_height) +{ + int src_width, src_height; + int w, h; + int src_x, src_y; + GdkPixbuf *pixbuf; + + src_width = gdk_pixbuf_get_width (src); + src_height = gdk_pixbuf_get_height (src); + + if (src_width < max_width && src_height < max_height) + return g_object_ref (src); + + w = MIN(src_width, max_width); + h = MIN(src_height, max_height); + + pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB, + gdk_pixbuf_get_has_alpha (src), + 8, w, h); + + src_x = (src_width - w) / 2; + src_y = (src_height - h) / 2; + gdk_pixbuf_copy_area (src, + src_x, src_y, + w, h, + pixbuf, + 0, 0); + return pixbuf; +} + +static GdkPixbuf * +get_scaled_pixbuf (MateBGPlacement placement, + GdkPixbuf *pixbuf, + int width, int height, + int *x, int *y, + int *w, int *h) +{ + GdkPixbuf *new; + +#if 0 + g_print ("original_width: %d %d\n", + gdk_pixbuf_get_width (pixbuf), + gdk_pixbuf_get_height (pixbuf)); +#endif + + switch (placement) { + case MATE_BG_PLACEMENT_SPANNED: + new = pixbuf_scale_to_fit (pixbuf, width, height); + break; + case MATE_BG_PLACEMENT_ZOOMED: + new = pixbuf_scale_to_min (pixbuf, width, height); + break; + + case MATE_BG_PLACEMENT_FILL_SCREEN: + new = gdk_pixbuf_scale_simple (pixbuf, width, height, + GDK_INTERP_BILINEAR); + break; + + case MATE_BG_PLACEMENT_SCALED: + new = pixbuf_scale_to_fit (pixbuf, width, height); + break; + + case MATE_BG_PLACEMENT_CENTERED: + case MATE_BG_PLACEMENT_TILED: + default: + new = pixbuf_clip_to_fit (pixbuf, width, height); + break; + } + + *w = gdk_pixbuf_get_width (new); + *h = gdk_pixbuf_get_height (new); + *x = (width - *w) / 2; + *y = (height - *h) / 2; + + return new; +} + +static void +draw_image_area (MateBGPlacement placement, + GdkPixbuf *pixbuf, + GdkPixbuf *dest, + GdkRectangle *area) +{ + int dest_width = area->width; + int dest_height = area->height; + int x, y, w, h; + GdkPixbuf *scaled; + + if (!pixbuf) + return; + + scaled = get_scaled_pixbuf (placement, pixbuf, dest_width, dest_height, &x, &y, &w, &h); + + switch (placement) { + case MATE_BG_PLACEMENT_TILED: + pixbuf_tile (scaled, dest); + break; + case MATE_BG_PLACEMENT_ZOOMED: + case MATE_BG_PLACEMENT_CENTERED: + case MATE_BG_PLACEMENT_FILL_SCREEN: + case MATE_BG_PLACEMENT_SCALED: + pixbuf_blend (scaled, dest, 0, 0, w, h, x + area->x, y + area->y, 1.0); + break; + case MATE_BG_PLACEMENT_SPANNED: + pixbuf_blend (scaled, dest, 0, 0, w, h, x, y, 1.0); + break; + default: + g_assert_not_reached (); + break; + } + + g_object_unref (scaled); +} + +static void +draw_image (MateBGPlacement placement, + GdkPixbuf *pixbuf, + GdkPixbuf *dest) +{ + GdkRectangle rect; + + rect.x = 0; + rect.y = 0; + rect.width = gdk_pixbuf_get_width (dest); + rect.height = gdk_pixbuf_get_height (dest); + + draw_image_area (placement, pixbuf, dest, &rect); +} + +static void +draw_once (MateBG *bg, + GdkPixbuf *dest, + GdkScreen *screen) +{ + GdkRectangle rect; + GdkPixbuf *pixbuf; + + rect.x = 0; + rect.y = 0; + rect.width = gdk_pixbuf_get_width (dest); + rect.height = gdk_pixbuf_get_height (dest); + + pixbuf = get_pixbuf_for_size (bg, gdk_pixbuf_get_width (dest), gdk_pixbuf_get_height (dest)); + if (pixbuf) { + draw_image_area (bg->placement, + pixbuf, + dest, + &rect); + g_object_unref (pixbuf); + } +} + +static void +draw_each_monitor (MateBG *bg, + GdkPixbuf *dest, + GdkScreen *screen) +{ + GdkRectangle rect; + gint num_monitors; + int monitor; + + num_monitors = gdk_screen_get_n_monitors (screen); + for (monitor = 0; monitor < num_monitors; monitor++) { + GdkPixbuf *pixbuf; + gdk_screen_get_monitor_geometry (screen, monitor, &rect); + pixbuf = get_pixbuf_for_size (bg, rect.width, rect.height); + if (pixbuf) { + draw_image_area (bg->placement, + pixbuf, + dest, &rect); + g_object_unref (pixbuf); + } + } +} + +void +mate_bg_draw (MateBG *bg, + GdkPixbuf *dest, + GdkScreen *screen, + gboolean is_root) +{ + if (!bg) + return; + + if (is_root && (bg->placement != MATE_BG_PLACEMENT_SPANNED)) { + draw_color_each_monitor (bg, dest, screen); + draw_each_monitor (bg, dest, screen); + } else { + draw_color (bg, dest, screen); + draw_once (bg, dest, screen); + } +} + +gboolean +mate_bg_has_multiple_sizes (MateBG *bg) +{ + SlideShow *show; + gboolean ret; + + g_return_val_if_fail (bg != NULL, FALSE); + + ret = FALSE; + + show = get_as_slideshow (bg, bg->filename); + if (show) { + ret = slideshow_has_multiple_sizes (show); + slideshow_unref (show); + } + + return ret; +} + +static void +mate_bg_get_pixmap_size (MateBG *bg, + int width, + int height, + int *pixmap_width, + int *pixmap_height) +{ + int dummy; + + if (!pixmap_width) + pixmap_width = &dummy; + if (!pixmap_height) + pixmap_height = &dummy; + + *pixmap_width = width; + *pixmap_height = height; + + if (!bg->filename) { + switch (bg->color_type) { + case MATE_BG_COLOR_SOLID: + *pixmap_width = 1; + *pixmap_height = 1; + break; + + case MATE_BG_COLOR_H_GRADIENT: + case MATE_BG_COLOR_V_GRADIENT: + break; + } + + return; + } +} + +/** + * mate_bg_get_pixmap: + * @bg: MateBG + * @window: + * @width: + * @height: + * + * Create a pixmap that can be set as background for @window. If @root is TRUE, + * the pixmap created will be created by a temporary X server connection so + * that if someone calls XKillClient on it, it won't affect the application who + * created it. + * + * Since: 2.20 + **/ +GdkPixmap * +mate_bg_create_pixmap (MateBG *bg, + GdkWindow *window, + int width, + int height, + gboolean is_root) +{ + int pm_width, pm_height; + GdkPixmap *pixmap; + cairo_t *cr; + + g_return_val_if_fail (bg != NULL, NULL); + g_return_val_if_fail (window != NULL, NULL); + + if (bg->last_pixmap_width != width || + bg->last_pixmap_height != height) { + if (bg->pixbuf_cache) { + g_object_unref (bg->pixbuf_cache); + bg->pixbuf_cache = NULL; + } + } + bg->last_pixmap_width = width; + bg->last_pixmap_height = height; + + /* has the side effect of loading and caching pixbuf only when in tile mode */ + mate_bg_get_pixmap_size (bg, width, height, &pm_width, &pm_height); + + if (is_root) { + pixmap = make_root_pixmap (gdk_drawable_get_screen (window), + pm_width, pm_height); + } + else { + pixmap = gdk_pixmap_new (window, pm_width, pm_height, -1); + } + + cr = gdk_cairo_create (pixmap); + if (!bg->filename && bg->color_type == MATE_BG_COLOR_SOLID) { + gdk_cairo_set_source_color (cr, &(bg->primary)); + } + else { + GdkPixbuf *pixbuf; + + pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB, FALSE, 8, + width, height); + mate_bg_draw (bg, pixbuf, gdk_drawable_get_screen (GDK_DRAWABLE (window)), is_root); + gdk_cairo_set_source_pixbuf (cr, pixbuf, 0, 0); + g_object_unref (pixbuf); + } + + cairo_paint (cr); + + cairo_destroy (cr); + + return pixmap; +} + + +/* determine if a background is darker or lighter than average, to help + * clients know what colors to draw on top with + */ +gboolean +mate_bg_is_dark (MateBG *bg, + int width, + int height) +{ + GdkColor color; + int intensity; + GdkPixbuf *pixbuf; + + g_return_val_if_fail (bg != NULL, FALSE); + + if (bg->color_type == MATE_BG_COLOR_SOLID) { + color = bg->primary; + } else { + color.red = (bg->primary.red + bg->secondary.red) / 2; + color.green = (bg->primary.green + bg->secondary.green) / 2; + color.blue = (bg->primary.blue + bg->secondary.blue) / 2; + } + pixbuf = get_pixbuf_for_size (bg, width, height); + if (pixbuf) { + guint32 argb = pixbuf_average_value (pixbuf); + guchar a = (argb >> 24) & 0xff; + guchar r = (argb >> 16) & 0xff; + guchar g = (argb >> 8) & 0xff; + guchar b = (argb >> 0) & 0xff; + + color.red = (color.red * (0xFF - a) + r * 0x101 * a) / 0xFF; + color.green = (color.green * (0xFF - a) + g * 0x101 * a) / 0xFF; + color.blue = (color.blue * (0xFF - a) + b * 0x101 * a) / 0xFF; + g_object_unref (pixbuf); + } + + intensity = (color.red * 77 + + color.green * 150 + + color.blue * 28) >> 16; + + return intensity < 160; /* biased slightly to be dark */ +} + +/* + * Create a persistent pixmap. We create a separate display + * and set the closedown mode on it to RetainPermanent. + */ +static GdkPixmap * +make_root_pixmap (GdkScreen *screen, gint width, gint height) +{ + Display *display; + const char *display_name; + Pixmap result; + GdkPixmap *gdk_pixmap; + int screen_num; + int depth; + + screen_num = gdk_screen_get_number (screen); + + gdk_flush (); + + display_name = gdk_display_get_name (gdk_screen_get_display (screen)); + display = XOpenDisplay (display_name); + + if (display == NULL) { + g_warning ("Unable to open display '%s' when setting " + "background pixmap\n", + (display_name) ? display_name : "NULL"); + return NULL; + } + + /* Desktop background pixmap should be created from + * dummy X client since most applications will try to + * kill it with XKillClient later when changing pixmap + */ + + XSetCloseDownMode (display, RetainPermanent); + + depth = DefaultDepth (display, screen_num); + + result = XCreatePixmap (display, + RootWindow (display, screen_num), + width, height, depth); + + XCloseDisplay (display); + + gdk_pixmap = gdk_pixmap_foreign_new_for_screen (screen, result, + width, height, depth); + + gdk_drawable_set_colormap ( + GDK_DRAWABLE (gdk_pixmap), + gdk_drawable_get_colormap (gdk_screen_get_root_window (screen))); + + return gdk_pixmap; +} + +static gboolean +get_original_size (const char *filename, + int *orig_width, + int *orig_height) +{ + gboolean result; + + if (gdk_pixbuf_get_file_info (filename, orig_width, orig_height)) + result = TRUE; + else + result = FALSE; + + return result; +} + +static const char * +get_filename_for_size (MateBG *bg, gint best_width, gint best_height) +{ + SlideShow *show; + Slide *slide; + FileSize *size; + + if (!bg->filename) + return NULL; + + show = get_as_slideshow (bg, bg->filename); + if (!show) { + return bg->filename; + } + + slide = get_current_slide (show, NULL); + slideshow_unref (show); + size = find_best_size (slide->file1, best_width, best_height); + return size->file; +} + +gboolean +mate_bg_get_image_size (MateBG *bg, + MateDesktopThumbnailFactory *factory, + int best_width, + int best_height, + int *width, + int *height) +{ + GdkPixbuf *thumb; + gboolean result = FALSE; + const gchar *filename; + + g_return_val_if_fail (bg != NULL, FALSE); + g_return_val_if_fail (factory != NULL, FALSE); + + if (!bg->filename) + return FALSE; + + filename = get_filename_for_size (bg, best_width, best_height); + thumb = create_thumbnail_for_filename (factory, filename); + if (thumb) { + if (get_thumb_annotations (thumb, width, height)) + result = TRUE; + + g_object_unref (thumb); + } + + if (!result) { + if (get_original_size (filename, width, height)) + result = TRUE; + } + + return result; +} + +static double +fit_factor (int from_width, int from_height, + int to_width, int to_height) +{ + return MIN (to_width / (double) from_width, to_height / (double) from_height); +} + +GdkPixbuf * +mate_bg_create_thumbnail (MateBG *bg, + MateDesktopThumbnailFactory *factory, + GdkScreen *screen, + int dest_width, + int dest_height) +{ + GdkPixbuf *result; + GdkPixbuf *thumb; + + g_return_val_if_fail (bg != NULL, NULL); + + result = gdk_pixbuf_new (GDK_COLORSPACE_RGB, FALSE, 8, dest_width, dest_height); + + draw_color (bg, result, screen); + + thumb = create_img_thumbnail (bg, factory, screen, dest_width, dest_height, -1); + + if (thumb) { + draw_image (bg->placement, thumb, result); + g_object_unref (thumb); + } + + return result; +} + +/** + * mate_bg_get_pixmap_from_root: + * @screen: a #GdkScreen + * + * This function queries the _XROOTPMAP_ID property from + * the root window associated with @screen to determine + * the current root window background pixmap and returns + * a copy of it. If the _XROOTPMAP_ID is not set, then + * a black pixmap is returned. + * + * Return value: a #GdkPixmap if successful or %NULL + **/ +GdkPixmap * +mate_bg_get_pixmap_from_root (GdkScreen *screen) +{ + int result; + gint format; + gulong nitems; + gulong bytes_after; + guchar *data; + Atom type; + Display *display; + int screen_num; + GdkPixmap *pixmap; + GdkPixmap *source_pixmap; + int width, height; + cairo_t *cr; + cairo_pattern_t *pattern; + + display = GDK_DISPLAY_XDISPLAY (gdk_screen_get_display (screen)); + screen_num = gdk_screen_get_number (screen); + + result = XGetWindowProperty (display, + RootWindow (display, screen_num), + gdk_x11_get_xatom_by_name ("_XROOTPMAP_ID"), + 0L, 1L, False, XA_PIXMAP, + &type, &format, &nitems, &bytes_after, + &data); + pixmap = NULL; + source_pixmap = NULL; + + if (result != Success || type != XA_PIXMAP || + format != 32 || nitems != 1) { + XFree (data); + data = NULL; + } + + if (data != NULL) { + gdk_error_trap_push (); + source_pixmap = gdk_pixmap_foreign_new (*(Pixmap *) data); + gdk_error_trap_pop (); + + if (source_pixmap != NULL) { + gdk_drawable_set_colormap (source_pixmap, + gdk_screen_get_default_colormap (screen)); + } + } + + width = gdk_screen_get_width (screen); + height = gdk_screen_get_height (screen); + + pixmap = gdk_pixmap_new (source_pixmap != NULL? source_pixmap : + gdk_screen_get_root_window (screen), + width, height, -1); + + cr = gdk_cairo_create (pixmap); + if (source_pixmap != NULL) { + gdk_cairo_set_source_pixmap (cr, source_pixmap, 0, 0); + pattern = cairo_get_source (cr); + cairo_pattern_set_extend (pattern, CAIRO_EXTEND_REPEAT); + } else { + cairo_set_source_rgb (cr, 0.0, 0.0, 0.0); + } + cairo_paint (cr); + + if (cairo_status (cr) != CAIRO_STATUS_SUCCESS) { + g_object_unref (pixmap); + pixmap = NULL; + } + cairo_destroy (cr); + + if (source_pixmap != NULL) + g_object_unref (source_pixmap); + + if (data != NULL) + XFree (data); + + return pixmap; +} + +static void +mate_bg_set_root_pixmap_id (GdkScreen *screen, + GdkPixmap *pixmap) +{ + int result; + gint format; + gulong nitems; + gulong bytes_after; + guchar *data_esetroot; + Pixmap pixmap_id; + Atom type; + Display *display; + int screen_num; + + screen_num = gdk_screen_get_number (screen); + data_esetroot = NULL; + + display = GDK_DISPLAY_XDISPLAY (gdk_screen_get_display (screen)); + + result = XGetWindowProperty (display, + RootWindow (display, screen_num), + gdk_x11_get_xatom_by_name ("ESETROOT_PMAP_ID"), + 0L, 1L, False, XA_PIXMAP, + &type, &format, &nitems, + &bytes_after, + &data_esetroot); + + if (data_esetroot != NULL) { + if (result == Success && type == XA_PIXMAP && + format == 32 && + nitems == 1) { + gdk_error_trap_push (); + XKillClient (display, *(Pixmap *)data_esetroot); + gdk_flush (); + gdk_error_trap_pop (); + } + XFree (data_esetroot); + } + + pixmap_id = GDK_WINDOW_XWINDOW (pixmap); + + XChangeProperty (display, RootWindow (display, screen_num), + gdk_x11_get_xatom_by_name ("ESETROOT_PMAP_ID"), + XA_PIXMAP, 32, PropModeReplace, + (guchar *) &pixmap_id, 1); + XChangeProperty (display, RootWindow (display, screen_num), + gdk_x11_get_xatom_by_name ("_XROOTPMAP_ID"), XA_PIXMAP, + 32, PropModeReplace, + (guchar *) &pixmap_id, 1); +} + +/** + * mate_bg_set_pixmap_as_root: + * @screen: the #GdkScreen to change root background on + * @pixmap: the #GdkPixmap to set root background from + * + * Set the root pixmap, and properties pointing to it. We + * do this atomically with a server grab to make sure that + * we won't leak the pixmap if somebody else it setting + * it at the same time. (This assumes that they follow the + * same conventions we do). @pixmap should come from a call + * to mate_bg_create_pixmap(). + **/ +void +mate_bg_set_pixmap_as_root (GdkScreen *screen, GdkPixmap *pixmap) +{ + Display *display; + int screen_num; + + g_return_if_fail (screen != NULL); + g_return_if_fail (pixmap != NULL); + + screen_num = gdk_screen_get_number (screen); + + display = GDK_DISPLAY_XDISPLAY (gdk_screen_get_display (screen)); + + gdk_x11_display_grab (gdk_screen_get_display (screen)); + + mate_bg_set_root_pixmap_id (screen, pixmap); + + XSetWindowBackgroundPixmap (display, RootWindow (display, screen_num), + GDK_PIXMAP_XID (pixmap)); + XClearWindow (display, RootWindow (display, screen_num)); + + gdk_display_flush (gdk_screen_get_display (screen)); + gdk_x11_display_ungrab (gdk_screen_get_display (screen)); +} + +/** + * mate_bg_set_pixmap_as_root_with_crossfade: + * @screen: the #GdkScreen to change root background on + * @pixmap: the #GdkPixmap to set root background from + * @context: a #GMainContext or %NULL + * + * Set the root pixmap, and properties pointing to it. + * This function differs from mate_bg_set_pixmap_as_root() + * in that it adds a subtle crossfade animation from the + * current root pixmap to the new one. + * same conventions we do). + * + * Return value: a #MateBGCrossfade object + **/ +MateBGCrossfade * +mate_bg_set_pixmap_as_root_with_crossfade (GdkScreen *screen, + GdkPixmap *pixmap) +{ + GdkDisplay *display; + GdkWindow *root_window; + GdkPixmap *old_pixmap; + int width, height; + MateBGCrossfade *fade; + + g_return_val_if_fail (screen != NULL, NULL); + g_return_val_if_fail (pixmap != NULL, NULL); + + root_window = gdk_screen_get_root_window (screen); + + width = gdk_screen_get_width (screen); + height = gdk_screen_get_height (screen); + + fade = mate_bg_crossfade_new (width, height); + + display = gdk_screen_get_display (screen); + gdk_x11_display_grab (display); + old_pixmap = mate_bg_get_pixmap_from_root (screen); + mate_bg_set_root_pixmap_id (screen, pixmap); + mate_bg_crossfade_set_start_pixmap (fade, old_pixmap); + g_object_unref (old_pixmap); + mate_bg_crossfade_set_end_pixmap (fade, pixmap); + gdk_display_flush (display); + gdk_x11_display_ungrab (display); + + mate_bg_crossfade_start (fade, root_window); + + return fade; +} + +/* Implementation of the pixbuf cache */ +struct _SlideShow +{ + gint ref_count; + double start_time; + double total_duration; + + GQueue *slides; + + gboolean has_multiple_sizes; + + /* used during parsing */ + struct tm start_tm; + GQueue *stack; +}; + + +static double +now (void) +{ + GTimeVal tv; + + g_get_current_time (&tv); + + return (double)tv.tv_sec + (tv.tv_usec / 1000000.0); +} + +static Slide * +get_current_slide (SlideShow *show, + double *alpha) +{ + double delta = fmod (now() - show->start_time, show->total_duration); + GList *list; + double elapsed; + int i; + + if (delta < 0) + delta += show->total_duration; + + elapsed = 0; + i = 0; + for (list = show->slides->head; list != NULL; list = list->next) { + Slide *slide = list->data; + + if (elapsed + slide->duration > delta) { + if (alpha) + *alpha = (delta - elapsed) / (double)slide->duration; + return slide; + } + + i++; + elapsed += slide->duration; + } + + /* this should never happen since we have slides and we should always + * find a current slide for the elapsed time since beginning -- we're + * looping with fmod() */ + g_assert_not_reached (); + + return NULL; +} + +static GdkPixbuf * +blend (GdkPixbuf *p1, + GdkPixbuf *p2, + double alpha) +{ + GdkPixbuf *result = gdk_pixbuf_copy (p1); + GdkPixbuf *tmp; + + if (gdk_pixbuf_get_width (p2) != gdk_pixbuf_get_width (p1) || + gdk_pixbuf_get_height (p2) != gdk_pixbuf_get_height (p1)) { + tmp = gdk_pixbuf_scale_simple (p2, + gdk_pixbuf_get_width (p1), + gdk_pixbuf_get_height (p1), + GDK_INTERP_BILINEAR); + } + else { + tmp = g_object_ref (p2); + } + + pixbuf_blend (tmp, result, 0, 0, -1, -1, 0, 0, alpha); + + g_object_unref (tmp); + + return result; +} + +typedef enum { + PIXBUF, + SLIDESHOW, + THUMBNAIL +} FileType; + +struct FileCacheEntry +{ + FileType type; + char *filename; + union { + GdkPixbuf *pixbuf; + SlideShow *slideshow; + GdkPixbuf *thumbnail; + } u; +}; + +static void +file_cache_entry_delete (FileCacheEntry *ent) +{ + g_free (ent->filename); + + switch (ent->type) { + case PIXBUF: + g_object_unref (ent->u.pixbuf); + break; + case SLIDESHOW: + slideshow_unref (ent->u.slideshow); + break; + case THUMBNAIL: + g_object_unref (ent->u.thumbnail); + break; + } + + g_free (ent); +} + +static void +bound_cache (MateBG *bg) +{ + while (g_list_length (bg->file_cache) >= CACHE_SIZE) { + GList *last_link = g_list_last (bg->file_cache); + FileCacheEntry *ent = last_link->data; + + file_cache_entry_delete (ent); + + bg->file_cache = g_list_delete_link (bg->file_cache, last_link); + } +} + +static const FileCacheEntry * +file_cache_lookup (MateBG *bg, FileType type, const char *filename) +{ + GList *list; + + for (list = bg->file_cache; list != NULL; list = list->next) { + FileCacheEntry *ent = list->data; + + if (ent && ent->type == type && + strcmp (ent->filename, filename) == 0) { + return ent; + } + } + + return NULL; +} + +static FileCacheEntry * +file_cache_entry_new (MateBG *bg, + FileType type, + const char *filename) +{ + FileCacheEntry *ent = g_new0 (FileCacheEntry, 1); + + g_assert (!file_cache_lookup (bg, type, filename)); + + ent->type = type; + ent->filename = g_strdup (filename); + + bg->file_cache = g_list_prepend (bg->file_cache, ent); + + bound_cache (bg); + + return ent; +} + +static void +file_cache_add_pixbuf (MateBG *bg, + const char *filename, + GdkPixbuf *pixbuf) +{ + FileCacheEntry *ent = file_cache_entry_new (bg, PIXBUF, filename); + ent->u.pixbuf = g_object_ref (pixbuf); +} + +static void +file_cache_add_thumbnail (MateBG *bg, + const char *filename, + GdkPixbuf *pixbuf) +{ + FileCacheEntry *ent = file_cache_entry_new (bg, THUMBNAIL, filename); + ent->u.thumbnail = g_object_ref (pixbuf); +} + +static void +file_cache_add_slide_show (MateBG *bg, + const char *filename, + SlideShow *show) +{ + FileCacheEntry *ent = file_cache_entry_new (bg, SLIDESHOW, filename); + ent->u.slideshow = slideshow_ref (show); +} + +static GdkPixbuf * +get_as_pixbuf_for_size (MateBG *bg, + const char *filename, + int best_width, + int best_height) +{ + const FileCacheEntry *ent; + if ((ent = file_cache_lookup (bg, PIXBUF, filename))) { + return g_object_ref (ent->u.pixbuf); + } + else { + GdkPixbufFormat *format; + GdkPixbuf *pixbuf; + gchar *tmp; + + /* If scalable choose maximum size */ + format = gdk_pixbuf_get_file_info (filename, NULL, NULL); + tmp = gdk_pixbuf_format_get_name (format); + if (format != NULL && + strcmp (tmp, "svg") == 0 && + (best_width > 0 && best_height > 0) && + (bg->placement == MATE_BG_PLACEMENT_FILL_SCREEN || + bg->placement == MATE_BG_PLACEMENT_SCALED || + bg->placement == MATE_BG_PLACEMENT_ZOOMED)) + pixbuf = gdk_pixbuf_new_from_file_at_size (filename, best_width, best_height, NULL); + else + pixbuf = gdk_pixbuf_new_from_file (filename, NULL); + g_free (tmp); + + if (pixbuf) + file_cache_add_pixbuf (bg, filename, pixbuf); + + return pixbuf; + } +} + +static SlideShow * +get_as_slideshow (MateBG *bg, const char *filename) +{ + const FileCacheEntry *ent; + if ((ent = file_cache_lookup (bg, SLIDESHOW, filename))) { + return slideshow_ref (ent->u.slideshow); + } + else { + SlideShow *show = read_slideshow_file (filename, NULL); + + if (show) + file_cache_add_slide_show (bg, filename, show); + + return show; + } +} + +static GdkPixbuf * +get_as_thumbnail (MateBG *bg, MateDesktopThumbnailFactory *factory, const char *filename) +{ + const FileCacheEntry *ent; + if ((ent = file_cache_lookup (bg, THUMBNAIL, filename))) { + return g_object_ref (ent->u.thumbnail); + } + else { + GdkPixbuf *thumb = create_thumbnail_for_filename (factory, filename); + + if (thumb) + file_cache_add_thumbnail (bg, filename, thumb); + + return thumb; + } +} + +static gboolean +blow_expensive_caches (gpointer data) +{ + MateBG *bg = data; + GList *list, *next; + + bg->blow_caches_id = 0; + + for (list = bg->file_cache; list != NULL; list = next) { + FileCacheEntry *ent = list->data; + next = list->next; + + if (ent->type == PIXBUF) { + file_cache_entry_delete (ent); + bg->file_cache = g_list_delete_link (bg->file_cache, + list); + } + } + + if (bg->pixbuf_cache) { + g_object_unref (bg->pixbuf_cache); + bg->pixbuf_cache = NULL; + } + + return FALSE; +} + +static void +blow_expensive_caches_in_idle (MateBG *bg) +{ + if (bg->blow_caches_id == 0) { + bg->blow_caches_id = + g_idle_add (blow_expensive_caches, + bg); + } +} + + +static gboolean +on_timeout (gpointer data) +{ + MateBG *bg = data; + + bg->timeout_id = 0; + + queue_transitioned (bg); + + return FALSE; +} + +static double +get_slide_timeout (Slide *slide) +{ + double timeout; + if (slide->fixed) { + timeout = slide->duration; + } else { + /* Maybe the number of steps should be configurable? */ + + /* In the worst case we will do a fade from 0 to 256, which mean + * we will never use more than 255 steps, however in most cases + * the first and last value are similar and users can't percieve + * changes in pixel values as small as 1/255th. So, lets not waste + * CPU cycles on transitioning to often. + * + * 64 steps is enough for each step to be just detectable in a 16bit + * color mode in the worst case, so we'll use this as an approximation + * of whats detectable. + */ + timeout = slide->duration / 64.0; + } + return timeout; +} + +static void +ensure_timeout (MateBG *bg, + Slide *slide) +{ + if (!bg->timeout_id) { + double timeout = get_slide_timeout (slide); + bg->timeout_id = g_timeout_add_full ( + G_PRIORITY_LOW, + timeout * 1000, on_timeout, bg, NULL); + + } +} + +static time_t +get_mtime (const char *filename) +{ + GFile *file; + GFileInfo *info; + time_t mtime; + + mtime = (time_t)-1; + + if (filename) { + file = g_file_new_for_path (filename); + info = g_file_query_info (file, G_FILE_ATTRIBUTE_TIME_MODIFIED, + G_FILE_QUERY_INFO_NONE, NULL, NULL); + if (info) { + mtime = g_file_info_get_attribute_uint64 (info, + G_FILE_ATTRIBUTE_TIME_MODIFIED); + g_object_unref (info); + } + g_object_unref (file); + } + + return mtime; +} + +static GdkPixbuf * +scale_thumbnail (MateBGPlacement placement, + const char *filename, + GdkPixbuf *thumb, + GdkScreen *screen, + int dest_width, + int dest_height) +{ + int o_width; + int o_height; + + if (placement != MATE_BG_PLACEMENT_TILED && + placement != MATE_BG_PLACEMENT_CENTERED) { + + /* In this case, the pixbuf will be scaled to fit the screen anyway, + * so just return the pixbuf here + */ + return g_object_ref (thumb); + } + + if (get_thumb_annotations (thumb, &o_width, &o_height) || + (filename && get_original_size (filename, &o_width, &o_height))) { + + int scr_height = gdk_screen_get_height (screen); + int scr_width = gdk_screen_get_width (screen); + int thumb_width = gdk_pixbuf_get_width (thumb); + int thumb_height = gdk_pixbuf_get_height (thumb); + double screen_to_dest = fit_factor (scr_width, scr_height, + dest_width, dest_height); + double thumb_to_orig = fit_factor (thumb_width, thumb_height, + o_width, o_height); + double f = thumb_to_orig * screen_to_dest; + int new_width, new_height; + + new_width = floor (thumb_width * f + 0.5); + new_height = floor (thumb_height * f + 0.5); + + if (placement == MATE_BG_PLACEMENT_TILED) { + /* Heuristic to make sure tiles don't become so small that + * they turn into a blur. + * + * This is strictly speaking incorrect, but the resulting + * thumbnail gives a much better idea what the background + * will actually look like. + */ + + if ((new_width < 32 || new_height < 32) && + (new_width < o_width / 4 || new_height < o_height / 4)) { + new_width = o_width / 4; + new_height = o_height / 4; + } + } + + thumb = gdk_pixbuf_scale_simple (thumb, new_width, new_height, + GDK_INTERP_BILINEAR); + } + else + g_object_ref (thumb); + + return thumb; +} + +/* frame_num determines which slide to thumbnail. + * -1 means 'current slide'. + */ +static GdkPixbuf * +create_img_thumbnail (MateBG *bg, + MateDesktopThumbnailFactory *factory, + GdkScreen *screen, + int dest_width, + int dest_height, + int frame_num) +{ + if (bg->filename) { + GdkPixbuf *thumb; + + thumb = get_as_thumbnail (bg, factory, bg->filename); + + if (thumb) { + GdkPixbuf *result; + result = scale_thumbnail (bg->placement, + bg->filename, + thumb, + screen, + dest_width, + dest_height); + g_object_unref (thumb); + return result; + } + else { + SlideShow *show = get_as_slideshow (bg, bg->filename); + + if (show) { + double alpha; + Slide *slide; + + if (frame_num == -1) + slide = get_current_slide (show, &alpha); + else + slide = g_queue_peek_nth (show->slides, frame_num); + + if (slide->fixed) { + GdkPixbuf *tmp; + FileSize *fs; + fs = find_best_size (slide->file1, dest_width, dest_height); + tmp = get_as_thumbnail (bg, factory, fs->file); + if (tmp) { + thumb = scale_thumbnail (bg->placement, + fs->file, + tmp, + screen, + dest_width, + dest_height); + g_object_unref (tmp); + } + } + else { + FileSize *fs1, *fs2; + GdkPixbuf *p1, *p2; + fs1 = find_best_size (slide->file1, dest_width, dest_height); + p1 = get_as_thumbnail (bg, factory, fs1->file); + + fs2 = find_best_size (slide->file2, dest_width, dest_height); + p2 = get_as_thumbnail (bg, factory, fs2->file); + + if (p1 && p2) { + GdkPixbuf *thumb1, *thumb2; + + thumb1 = scale_thumbnail (bg->placement, + fs1->file, + p1, + screen, + dest_width, + dest_height); + + thumb2 = scale_thumbnail (bg->placement, + fs2->file, + p2, + screen, + dest_width, + dest_height); + + thumb = blend (thumb1, thumb2, alpha); + + g_object_unref (thumb1); + g_object_unref (thumb2); + } + if (p1) + g_object_unref (p1); + if (p2) + g_object_unref (p2); + } + + ensure_timeout (bg, slide); + + slideshow_unref (show); + } + } + + return thumb; + } + + return NULL; +} + +/* + * Find the FileSize that best matches the given size. + * Do two passes; the first pass only considers FileSizes + * that are larger than the given size. + * We are looking for the image that best matches the aspect ratio. + * When two images have the same aspect ratio, prefer the one whose + * width is closer to the given width. + */ +static FileSize * +find_best_size (GSList *sizes, gint width, gint height) +{ + GSList *s; + gdouble a, d, distance; + FileSize *best = NULL; + gint pass; + + a = width/(gdouble)height; + distance = 10000.0; + + for (pass = 0; pass < 2; pass++) { + for (s = sizes; s; s = s->next) { + FileSize *size = s->data; + + if (pass == 0 && (size->width < width || size->height < height)) + continue; + + d = fabs (a - size->width/(gdouble)size->height); + if (d < distance) { + distance = d; + best = size; + } + else if (d == distance) { + if (abs (size->width - width) < abs (best->width - width)) { + best = size; + } + } + } + + if (best) + break; + } + + return best; +} + +static GdkPixbuf * +get_pixbuf_for_size (MateBG *bg, + gint best_width, + gint best_height) +{ + guint time_until_next_change; + gboolean hit_cache = FALSE; + + /* only hit the cache if the aspect ratio matches */ + if (bg->pixbuf_cache) { + int width, height; + width = gdk_pixbuf_get_width (bg->pixbuf_cache); + height = gdk_pixbuf_get_height (bg->pixbuf_cache); + hit_cache = 0.2 > fabs ((best_width / (double)best_height) - (width / (double)height)); + if (!hit_cache) { + g_object_unref (bg->pixbuf_cache); + bg->pixbuf_cache = NULL; + } + } + + if (!hit_cache && bg->filename) { + bg->file_mtime = get_mtime (bg->filename); + + bg->pixbuf_cache = get_as_pixbuf_for_size (bg, bg->filename, best_width, best_height); + time_until_next_change = G_MAXUINT; + if (!bg->pixbuf_cache) { + SlideShow *show = get_as_slideshow (bg, bg->filename); + + if (show) { + double alpha; + Slide *slide; + + slideshow_ref (show); + + slide = get_current_slide (show, &alpha); + time_until_next_change = (guint)get_slide_timeout (slide); + if (slide->fixed) { + FileSize *size; + size = find_best_size (slide->file1, best_width, best_height); + bg->pixbuf_cache = get_as_pixbuf_for_size (bg, size->file, best_width, best_height); + } + else { + FileSize *size; + GdkPixbuf *p1, *p2; + size = find_best_size (slide->file1, best_width, best_height); + p1 = get_as_pixbuf_for_size (bg, size->file, best_width, best_height); + size = find_best_size (slide->file2, best_width, best_height); + p2 = get_as_pixbuf_for_size (bg, size->file, best_width, best_height); + + if (p1 && p2) { + bg->pixbuf_cache = blend (p1, p2, alpha); + } + if (p1) + g_object_unref (p1); + if (p2) + g_object_unref (p2); + } + + ensure_timeout (bg, slide); + + slideshow_unref (show); + } + } + + /* If the next slideshow step is a long time away then + we blow away the expensive stuff (large pixbufs) from + the cache */ + if (time_until_next_change > KEEP_EXPENSIVE_CACHE_SECS) + blow_expensive_caches_in_idle (bg); + } + + if (bg->pixbuf_cache) + g_object_ref (bg->pixbuf_cache); + + return bg->pixbuf_cache; +} + +static gboolean +is_different (MateBG *bg, + const char *filename) +{ + if (!filename && bg->filename) { + return TRUE; + } + else if (filename && !bg->filename) { + return TRUE; + } + else if (!filename && !bg->filename) { + return FALSE; + } + else { + time_t mtime = get_mtime (filename); + + if (mtime != bg->file_mtime) + return TRUE; + + if (strcmp (filename, bg->filename) != 0) + return TRUE; + + return FALSE; + } +} + +static void +clear_cache (MateBG *bg) +{ + GList *list; + + if (bg->file_cache) { + for (list = bg->file_cache; list != NULL; list = list->next) { + FileCacheEntry *ent = list->data; + + file_cache_entry_delete (ent); + } + g_list_free (bg->file_cache); + bg->file_cache = NULL; + } + + if (bg->pixbuf_cache) { + g_object_unref (bg->pixbuf_cache); + + bg->pixbuf_cache = NULL; + } + + if (bg->timeout_id) { + g_source_remove (bg->timeout_id); + + bg->timeout_id = 0; + } +} + +/* Pixbuf utilities */ +static guint32 +pixbuf_average_value (GdkPixbuf *pixbuf) +{ + guint64 a_total, r_total, g_total, b_total; + guint row, column; + int row_stride; + const guchar *pixels, *p; + int r, g, b, a; + guint64 dividend; + guint width, height; + + width = gdk_pixbuf_get_width (pixbuf); + height = gdk_pixbuf_get_height (pixbuf); + row_stride = gdk_pixbuf_get_rowstride (pixbuf); + pixels = gdk_pixbuf_get_pixels (pixbuf); + + /* iterate through the pixbuf, counting up each component */ + a_total = 0; + r_total = 0; + g_total = 0; + b_total = 0; + + if (gdk_pixbuf_get_has_alpha (pixbuf)) { + for (row = 0; row < height; row++) { + p = pixels + (row * row_stride); + for (column = 0; column < width; column++) { + r = *p++; + g = *p++; + b = *p++; + a = *p++; + + a_total += a; + r_total += r * a; + g_total += g * a; + b_total += b * a; + } + } + dividend = height * width * 0xFF; + a_total *= 0xFF; + } else { + for (row = 0; row < height; row++) { + p = pixels + (row * row_stride); + for (column = 0; column < width; column++) { + r = *p++; + g = *p++; + b = *p++; + + r_total += r; + g_total += g; + b_total += b; + } + } + dividend = height * width; + a_total = dividend * 0xFF; + } + + return ((a_total + dividend / 2) / dividend) << 24 + | ((r_total + dividend / 2) / dividend) << 16 + | ((g_total + dividend / 2) / dividend) << 8 + | ((b_total + dividend / 2) / dividend); +} + +static GdkPixbuf * +pixbuf_scale_to_fit (GdkPixbuf *src, int max_width, int max_height) +{ + double factor; + int src_width, src_height; + int new_width, new_height; + + src_width = gdk_pixbuf_get_width (src); + src_height = gdk_pixbuf_get_height (src); + + factor = MIN (max_width / (double) src_width, max_height / (double) src_height); + + new_width = floor (src_width * factor + 0.5); + new_height = floor (src_height * factor + 0.5); + + return gdk_pixbuf_scale_simple (src, new_width, new_height, GDK_INTERP_BILINEAR); +} + +static GdkPixbuf * +pixbuf_scale_to_min (GdkPixbuf *src, int min_width, int min_height) +{ + double factor; + int src_width, src_height; + int new_width, new_height; + GdkPixbuf *dest; + + src_width = gdk_pixbuf_get_width (src); + src_height = gdk_pixbuf_get_height (src); + + factor = MAX (min_width / (double) src_width, min_height / (double) src_height); + + new_width = floor (src_width * factor + 0.5); + new_height = floor (src_height * factor + 0.5); + + dest = gdk_pixbuf_new (GDK_COLORSPACE_RGB, + gdk_pixbuf_get_has_alpha (src), + 8, min_width, min_height); + if (!dest) + return NULL; + + /* crop the result */ + gdk_pixbuf_scale (src, dest, + 0, 0, + min_width, min_height, + (new_width - min_width) / -2, + (new_height - min_height) / -2, + factor, + factor, + GDK_INTERP_BILINEAR); + return dest; +} + +static guchar * +create_gradient (const GdkColor *primary, + const GdkColor *secondary, + int n_pixels) +{ + guchar *result = g_malloc (n_pixels * 3); + int i; + + for (i = 0; i < n_pixels; ++i) { + double ratio = (i + 0.5) / n_pixels; + + result[3 * i + 0] = ((guint16) (primary->red * (1 - ratio) + secondary->red * ratio)) >> 8; + result[3 * i + 1] = ((guint16) (primary->green * (1 - ratio) + secondary->green * ratio)) >> 8; + result[3 * i + 2] = ((guint16) (primary->blue * (1 - ratio) + secondary->blue * ratio)) >> 8; + } + + return result; +} + +static void +pixbuf_draw_gradient (GdkPixbuf *pixbuf, + gboolean horizontal, + GdkColor *primary, + GdkColor *secondary, + GdkRectangle *rect) +{ + int width; + int height; + int rowstride; + guchar *dst; + guchar *dst_limit; + int n_channels = 3; + + rowstride = gdk_pixbuf_get_rowstride (pixbuf); + width = rect->width; + height = rect->height; + dst = gdk_pixbuf_get_pixels (pixbuf) + rect->x * n_channels + rowstride * rect->y; + dst_limit = dst + height * rowstride; + + if (horizontal) { + guchar *gradient = create_gradient (primary, secondary, width); + int copy_bytes_per_row = width * n_channels; + int i; + + for (i = 0; i < height; i++) { + guchar *d; + d = dst + rowstride * i; + memcpy (d, gradient, copy_bytes_per_row); + } + g_free (gradient); + } else { + guchar *gb, *gradient; + int i; + + gradient = create_gradient (primary, secondary, height); + for (i = 0; i < height; i++) { + int j; + guchar *d; + + d = dst + rowstride * i; + gb = gradient + n_channels * i; + for (j = width; j > 0; j--) { + int k; + + for (k = 0; k < n_channels; k++) { + *(d++) = gb[k]; + } + } + } + + g_free (gradient); + } +} + +static void +pixbuf_blend (GdkPixbuf *src, + GdkPixbuf *dest, + int src_x, + int src_y, + int src_width, + int src_height, + int dest_x, + int dest_y, + double alpha) +{ + int dest_width = gdk_pixbuf_get_width (dest); + int dest_height = gdk_pixbuf_get_height (dest); + int offset_x = dest_x - src_x; + int offset_y = dest_y - src_y; + + if (src_width < 0) + src_width = gdk_pixbuf_get_width (src); + + if (src_height < 0) + src_height = gdk_pixbuf_get_height (src); + + if (dest_x < 0) + dest_x = 0; + + if (dest_y < 0) + dest_y = 0; + + if (dest_x + src_width > dest_width) { + src_width = dest_width - dest_x; + } + + if (dest_y + src_height > dest_height) { + src_height = dest_height - dest_y; + } + + gdk_pixbuf_composite (src, dest, + dest_x, dest_y, + src_width, src_height, + offset_x, offset_y, + 1, 1, GDK_INTERP_NEAREST, + alpha * 0xFF + 0.5); +} + +static void +pixbuf_tile (GdkPixbuf *src, GdkPixbuf *dest) +{ + int x, y; + int tile_width, tile_height; + int dest_width = gdk_pixbuf_get_width (dest); + int dest_height = gdk_pixbuf_get_height (dest); + + tile_width = gdk_pixbuf_get_width (src); + tile_height = gdk_pixbuf_get_height (src); + + for (y = 0; y < dest_height; y += tile_height) { + for (x = 0; x < dest_width; x += tile_width) { + pixbuf_blend (src, dest, 0, 0, + tile_width, tile_height, x, y, 1.0); + } + } +} + +static gboolean stack_is (SlideShow *parser, const char *s1, ...); + +/* Parser for fading background */ +static void +handle_start_element (GMarkupParseContext *context, + const gchar *name, + const gchar **attr_names, + const gchar **attr_values, + gpointer user_data, + GError **err) +{ + SlideShow *parser = user_data; + gint i; + + if (strcmp (name, "static") == 0 || strcmp (name, "transition") == 0) { + Slide *slide = g_new0 (Slide, 1); + + if (strcmp (name, "static") == 0) + slide->fixed = TRUE; + + g_queue_push_tail (parser->slides, slide); + } + else if (strcmp (name, "size") == 0) { + Slide *slide = parser->slides->tail->data; + FileSize *size = g_new0 (FileSize, 1); + for (i = 0; attr_names[i]; i++) { + if (strcmp (attr_names[i], "width") == 0) + size->width = atoi (attr_values[i]); + else if (strcmp (attr_names[i], "height") == 0) + size->height = atoi (attr_values[i]); + } + if (parser->stack->tail && + (strcmp (parser->stack->tail->data, "file") == 0 || + strcmp (parser->stack->tail->data, "from") == 0)) { + slide->file1 = g_slist_prepend (slide->file1, size); + } + else if (parser->stack->tail && + strcmp (parser->stack->tail->data, "to") == 0) { + slide->file2 = g_slist_prepend (slide->file2, size); + } + } + g_queue_push_tail (parser->stack, g_strdup (name)); +} + +static void +handle_end_element (GMarkupParseContext *context, + const gchar *name, + gpointer user_data, + GError **err) +{ + SlideShow *parser = user_data; + + g_free (g_queue_pop_tail (parser->stack)); +} + +static gboolean +stack_is (SlideShow *parser, + const char *s1, + ...) +{ + GList *stack = NULL; + const char *s; + GList *l1, *l2; + va_list args; + + stack = g_list_prepend (stack, (gpointer)s1); + + va_start (args, s1); + + s = va_arg (args, const char *); + while (s) { + stack = g_list_prepend (stack, (gpointer)s); + s = va_arg (args, const char *); + } + + l1 = stack; + l2 = parser->stack->head; + + while (l1 && l2) { + if (strcmp (l1->data, l2->data) != 0) { + g_list_free (stack); + return FALSE; + } + + l1 = l1->next; + l2 = l2->next; + } + + g_list_free (stack); + + return (!l1 && !l2); +} + +static int +parse_int (const char *text) +{ + return strtol (text, NULL, 0); +} + +static void +handle_text (GMarkupParseContext *context, + const gchar *text, + gsize text_len, + gpointer user_data, + GError **err) +{ + SlideShow *parser = user_data; + Slide *slide = parser->slides->tail? parser->slides->tail->data : NULL; + FileSize *fs; + gint i; + + if (stack_is (parser, "year", "starttime", "background", NULL)) { + parser->start_tm.tm_year = parse_int (text) - 1900; + } + else if (stack_is (parser, "month", "starttime", "background", NULL)) { + parser->start_tm.tm_mon = parse_int (text) - 1; + } + else if (stack_is (parser, "day", "starttime", "background", NULL)) { + parser->start_tm.tm_mday = parse_int (text); + } + else if (stack_is (parser, "hour", "starttime", "background", NULL)) { + parser->start_tm.tm_hour = parse_int (text) - 1; + } + else if (stack_is (parser, "minute", "starttime", "background", NULL)) { + parser->start_tm.tm_min = parse_int (text); + } + else if (stack_is (parser, "second", "starttime", "background", NULL)) { + parser->start_tm.tm_sec = parse_int (text); + } + else if (stack_is (parser, "duration", "static", "background", NULL) || + stack_is (parser, "duration", "transition", "background", NULL)) { + slide->duration = g_strtod (text, NULL); + parser->total_duration += slide->duration; + } + else if (stack_is (parser, "file", "static", "background", NULL) || + stack_is (parser, "from", "transition", "background", NULL)) { + for (i = 0; text[i]; i++) { + if (!g_ascii_isspace (text[i])) + break; + } + if (text[i] == 0) + return; + fs = g_new (FileSize, 1); + fs->width = -1; + fs->height = -1; + fs->file = g_strdup (text); + slide->file1 = g_slist_prepend (slide->file1, fs); + if (slide->file1->next != NULL) + parser->has_multiple_sizes = TRUE; + } + else if (stack_is (parser, "size", "file", "static", "background", NULL) || + stack_is (parser, "size", "from", "transition", "background", NULL)) { + fs = slide->file1->data; + fs->file = g_strdup (text); + if (slide->file1->next != NULL) + parser->has_multiple_sizes = TRUE; + } + else if (stack_is (parser, "to", "transition", "background", NULL)) { + for (i = 0; text[i]; i++) { + if (!g_ascii_isspace (text[i])) + break; + } + if (text[i] == 0) + return; + fs = g_new (FileSize, 1); + fs->width = -1; + fs->height = -1; + fs->file = g_strdup (text); + slide->file2 = g_slist_prepend (slide->file2, fs); + if (slide->file2->next != NULL) + parser->has_multiple_sizes = TRUE; + } + else if (stack_is (parser, "size", "to", "transition", "background", NULL)) { + fs = slide->file2->data; + fs->file = g_strdup (text); + if (slide->file2->next != NULL) + parser->has_multiple_sizes = TRUE; + } +} + +static SlideShow * +slideshow_ref (SlideShow *show) +{ + show->ref_count++; + return show; +} + +static void +slideshow_unref (SlideShow *show) +{ + GList *list; + GSList *slist; + FileSize *size; + + show->ref_count--; + if (show->ref_count > 0) + return; + + for (list = show->slides->head; list != NULL; list = list->next) { + Slide *slide = list->data; + + for (slist = slide->file1; slist != NULL; slist = slist->next) { + size = slist->data; + g_free (size->file); + g_free (size); + } + g_slist_free (slide->file1); + + for (slist = slide->file2; slist != NULL; slist = slist->next) { + size = slist->data; + g_free (size->file); + g_free (size); + } + g_slist_free (slide->file2); + + g_free (slide); + } + + g_queue_free (show->slides); + + g_list_foreach (show->stack->head, (GFunc) g_free, NULL); + g_queue_free (show->stack); + + g_free (show); +} + +static void +dump_bg (SlideShow *show) +{ +#if 0 + GList *list; + GSList *slist; + + for (list = show->slides->head; list != NULL; list = list->next) + { + Slide *slide = list->data; + + g_print ("\nSlide: %s\n", slide->fixed? "fixed" : "transition"); + g_print ("duration: %f\n", slide->duration); + g_print ("File1:\n"); + for (slist = slide->file1; slist != NULL; slist = slist->next) { + FileSize *size = slist->data; + g_print ("\t%s (%dx%d)\n", + size->file, size->width, size->height); + } + g_print ("File2:\n"); + for (slist = slide->file2; slist != NULL; slist = slist->next) { + FileSize *size = slist->data; + g_print ("\t%s (%dx%d)\n", + size->file, size->width, size->height); + } + } +#endif +} + +static void +threadsafe_localtime (time_t time, struct tm *tm) +{ + struct tm *res; + + G_LOCK_DEFINE_STATIC (localtime_mutex); + + G_LOCK (localtime_mutex); + + res = localtime (&time); + if (tm) { + *tm = *res; + } + + G_UNLOCK (localtime_mutex); +} + +static SlideShow * +read_slideshow_file (const char *filename, + GError **err) +{ + GMarkupParser parser = { + handle_start_element, + handle_end_element, + handle_text, + NULL, /* passthrough */ + NULL, /* error */ + }; + + GFile *file; + char *contents = NULL; + gsize len; + SlideShow *show = NULL; + GMarkupParseContext *context = NULL; + time_t t; + + if (!filename) + return NULL; + + file = g_file_new_for_path (filename); + if (!g_file_load_contents (file, NULL, &contents, &len, NULL, NULL)) { + g_object_unref (file); + return NULL; + } + g_object_unref (file); + + show = g_new0 (SlideShow, 1); + show->ref_count = 1; + threadsafe_localtime ((time_t)0, &show->start_tm); + show->stack = g_queue_new (); + show->slides = g_queue_new (); + + context = g_markup_parse_context_new (&parser, 0, show, NULL); + + if (!g_markup_parse_context_parse (context, contents, len, err)) { + slideshow_unref (show); + show = NULL; + } + + + if (show) { + if (!g_markup_parse_context_end_parse (context, err)) { + slideshow_unref (show); + show = NULL; + } + } + + g_markup_parse_context_free (context); + + if (show) { + t = mktime (&show->start_tm); + + show->start_time = (double)t; + + dump_bg (show); + + /* no slides, that's not a slideshow */ + if (g_queue_get_length (show->slides) == 0) { + slideshow_unref (show); + show = NULL; + } + } + + g_free (contents); + + return show; +} + +/* Thumbnail utilities */ +static GdkPixbuf * +create_thumbnail_for_filename (MateDesktopThumbnailFactory *factory, + const char *filename) +{ + char *thumb; + time_t mtime; + GdkPixbuf *orig, *result = NULL; + char *uri; + + mtime = get_mtime (filename); + + if (mtime == (time_t)-1) + return NULL; + + uri = g_filename_to_uri (filename, NULL, NULL); + + thumb = mate_desktop_thumbnail_factory_lookup (factory, uri, mtime); + + if (thumb) { + result = gdk_pixbuf_new_from_file (thumb, NULL); + g_free (thumb); + } + else { + orig = gdk_pixbuf_new_from_file (filename, NULL); + if (orig) { + int orig_width = gdk_pixbuf_get_width (orig); + int orig_height = gdk_pixbuf_get_height (orig); + + result = pixbuf_scale_to_fit (orig, 128, 128); + + g_object_set_data_full (G_OBJECT (result), "mate-thumbnail-height", + g_strdup_printf ("%d", orig_height), g_free); + g_object_set_data_full (G_OBJECT (result), "mate-thumbnail-width", + g_strdup_printf ("%d", orig_width), g_free); + + g_object_unref (orig); + + mate_desktop_thumbnail_factory_save_thumbnail (factory, result, uri, mtime); + } + else { + mate_desktop_thumbnail_factory_create_failed_thumbnail (factory, uri, mtime); + } + } + + g_free (uri); + + return result; +} + +static gboolean +get_thumb_annotations (GdkPixbuf *thumb, + int *orig_width, + int *orig_height) +{ + char *end; + const char *wstr, *hstr; + + wstr = gdk_pixbuf_get_option (thumb, "tEXt::Thumb::Image::Width"); + hstr = gdk_pixbuf_get_option (thumb, "tEXt::Thumb::Image::Height"); + + if (hstr && wstr) { + *orig_width = strtol (wstr, &end, 10); + if (*end != 0) + return FALSE; + + *orig_height = strtol (hstr, &end, 10); + if (*end != 0) + return FALSE; + + return TRUE; + } + + return FALSE; +} + +static gboolean +slideshow_has_multiple_sizes (SlideShow *show) +{ + return show->has_multiple_sizes; +} + +/* + * Returns whether the background is a slideshow. + */ +gboolean +mate_bg_changes_with_time (MateBG *bg) +{ + SlideShow *show; + + g_return_val_if_fail (bg != NULL, FALSE); + + show = get_as_slideshow (bg, bg->filename); + if (show) + return g_queue_get_length (show->slides) > 1; + + return FALSE; +} + +/* Creates a thumbnail for a certain frame, where 'frame' is somewhat + * vaguely defined as 'suitable point to show while single-stepping + * through the slideshow'. Returns NULL if frame_num is out of bounds. + */ +GdkPixbuf * +mate_bg_create_frame_thumbnail (MateBG *bg, + MateDesktopThumbnailFactory *factory, + GdkScreen *screen, + int dest_width, + int dest_height, + int frame_num) +{ + SlideShow *show; + GdkPixbuf *result; + GdkPixbuf *thumb; + GList *l; + int i, skipped; + gboolean found; + + g_return_val_if_fail (bg != NULL, FALSE); + + show = get_as_slideshow (bg, bg->filename); + + if (!show) + return NULL; + + + if (frame_num < 0 || frame_num >= g_queue_get_length (show->slides)) + return NULL; + + i = 0; + skipped = 0; + found = FALSE; + for (l = show->slides->head; l; l = l->next) { + Slide *slide = l->data; + if (!slide->fixed) { + skipped++; + continue; + } + if (i == frame_num) { + found = TRUE; + break; + } + i++; + } + if (!found) + return NULL; + + + result = gdk_pixbuf_new (GDK_COLORSPACE_RGB, FALSE, 8, dest_width, dest_height); + + draw_color (bg, result, screen); + + thumb = create_img_thumbnail (bg, factory, screen, dest_width, dest_height, frame_num + skipped); + + if (thumb) { + draw_image (bg->placement, thumb, result); + g_object_unref (thumb); + } + + return result; +} + |