1 /* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*-
2 
3 matebg.c: Object for the desktop background.
4 
5 Copyright (C) 2000 Eazel, Inc.
6 Copyright (C) 2007-2008 Red Hat, Inc.
7 Copyright (C) 2012 Jasmine Hassan <jasmine.aura@gmail.com>
8 Copyright (C) 2012-2021 MATE Developers
9 
10 This program is free software; you can redistribute it and/or
11 modify it under the terms of the GNU Library General Public License as
12 published by the Free Software Foundation; either version 2 of the
13 License, or (at your option) any later version.
14 
15 This program is distributed in the hope that it will be useful,
16 but WITHOUT ANY WARRANTY; without even the implied warranty of
17 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
18 Library General Public License for more details.
19 
20 You should have received a copy of the GNU Library General Public
21 License along with this program; if not, write to the
22 Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
23 Boston, MA 02110-1301, USA.
24 
25 Derived from eel-background.c and eel-gdk-pixbuf-extensions.c by
26 Darin Adler <darin@eazel.com> and Ramiro Estrugo <ramiro@eazel.com>
27 
28 Authors: Soren Sandmann <sandmann@redhat.com>
29 	 Jasmine Hassan <jasmine.aura@gmail.com>
30 
31 */
32 
33 #include <string.h>
34 #include <math.h>
35 #include <stdarg.h>
36 #include <stdlib.h>
37 
38 #include <glib/gstdio.h>
39 #include <gio/gio.h>
40 
41 #include <gdk/gdkx.h>
42 #include <X11/Xlib.h>
43 #include <X11/Xatom.h>
44 
45 #include <cairo.h>
46 
47 #define MATE_DESKTOP_USE_UNSTABLE_API
48 #include <mate-bg.h>
49 #include <mate-bg-crossfade.h>
50 
51 # include <cairo-xlib.h>
52 
53 #define MATE_BG_CACHE_DIR "mate/background"
54 
55 /* We keep the large pixbufs around if the next update
56    in the slideshow is less than 60 seconds away */
57 #define KEEP_EXPENSIVE_CACHE_SECS 60
58 
59 typedef struct _SlideShow SlideShow;
60 typedef struct _Slide Slide;
61 
62 struct _Slide {
63 	double duration; /* in seconds */
64 	gboolean fixed;
65 
66 	GSList* file1;
67 	GSList* file2; /* NULL if fixed is TRUE */
68 };
69 
70 typedef struct _FileSize FileSize;
71 struct _FileSize {
72 	gint width;
73 	gint height;
74 
75 	char* file;
76 };
77 
78 /* This is the size of the GdkRGB dither matrix, in order to avoid
79  * bad dithering when tiling the gradient
80  */
81 #define GRADIENT_PIXMAP_TILE_SIZE 128
82 #define THUMBNAIL_SIZE 256
83 
84 typedef struct FileCacheEntry FileCacheEntry;
85 #define CACHE_SIZE 4
86 
87 /*
88  *   Implementation of the MateBG class
89  */
90 struct _MateBG {
91 	GObject		 parent_instance;
92 	char		*filename;
93 	MateBGPlacement	 placement;
94 	MateBGColorType	 color_type;
95 	GdkRGBA	 	 primary;
96 	GdkRGBA	 	 secondary;
97 	gboolean	 is_enabled;
98 
99 	GFileMonitor* file_monitor;
100 
101 	guint changed_id;
102 	guint transitioned_id;
103 	guint blow_caches_id;
104 
105 	/* Cached information, only access through cache accessor functions */
106 	SlideShow* slideshow;
107 	time_t file_mtime;
108 	GdkPixbuf* pixbuf_cache;
109 	int timeout_id;
110 
111 	GList* file_cache;
112 };
113 
114 struct _MateBGClass {
115 	GObjectClass parent_class;
116 };
117 
118 enum {
119 	CHANGED,
120 	TRANSITIONED,
121 	N_SIGNALS
122 };
123 
124 static guint signals[N_SIGNALS] = {0};
125 
126 G_DEFINE_TYPE(MateBG, mate_bg, G_TYPE_OBJECT)
127 
128 static cairo_surface_t *make_root_pixmap     (GdkWindow  *window,
129                                               gint        width,
130                                               gint        height);
131 
132 /* Pixbuf utils */
133 static void       pixbuf_average_value (GdkPixbuf  *pixbuf,
134                                         GdkRGBA    *result);
135 static GdkPixbuf *pixbuf_scale_to_fit  (GdkPixbuf  *src,
136 					int         max_width,
137 					int         max_height);
138 static GdkPixbuf *pixbuf_scale_to_min  (GdkPixbuf  *src,
139 					int         min_width,
140 					int         min_height);
141 
142 static void       pixbuf_draw_gradient (GdkPixbuf    *pixbuf,
143 					gboolean      horizontal,
144 					GdkRGBA     *c1,
145 					GdkRGBA     *c2,
146 					GdkRectangle *rect);
147 
148 static void       pixbuf_tile          (GdkPixbuf  *src,
149 					GdkPixbuf  *dest);
150 static void       pixbuf_blend         (GdkPixbuf  *src,
151 					GdkPixbuf  *dest,
152 					int         src_x,
153 					int         src_y,
154 					int         width,
155 					int         height,
156 					int         dest_x,
157 					int         dest_y,
158 					double      alpha);
159 
160 /* Thumbnail utilities */
161 static GdkPixbuf *create_thumbnail_for_filename (MateDesktopThumbnailFactory *factory,
162 						 const char            *filename);
163 static gboolean   get_thumb_annotations (GdkPixbuf             *thumb,
164 					 int                   *orig_width,
165 					 int                   *orig_height);
166 
167 /* Cache */
168 static GdkPixbuf *get_pixbuf_for_size  (MateBG               *bg,
169 					gint                  num_monitor,
170 					int                   width,
171 					int                   height);
172 static void       clear_cache          (MateBG               *bg);
173 static gboolean   is_different         (MateBG               *bg,
174 					const char            *filename);
175 static time_t     get_mtime            (const char            *filename);
176 static GdkPixbuf *create_img_thumbnail (MateBG               *bg,
177 					MateDesktopThumbnailFactory *factory,
178 					GdkScreen             *screen,
179 					int                    dest_width,
180 					int                    dest_height,
181 					int		       frame_num);
182 static SlideShow * get_as_slideshow    (MateBG               *bg,
183 					const char 	      *filename);
184 static Slide *     get_current_slide   (SlideShow 	      *show,
185 		   			double    	      *alpha);
186 static gboolean    slideshow_has_multiple_sizes (SlideShow *show);
187 
188 static SlideShow *read_slideshow_file (const char *filename,
189 				       GError     **err);
190 static SlideShow *slideshow_ref       (SlideShow  *show);
191 static void       slideshow_unref     (SlideShow  *show);
192 
193 static FileSize   *find_best_size      (GSList                *sizes,
194 					gint                   width,
195 					gint                   height);
196 
197 static void
color_from_string(const char * string,GdkRGBA * colorp)198 color_from_string (const char *string,
199                    GdkRGBA    *colorp)
200 {
201 	if (!string || *string == '\0' || !gdk_rgba_parse (colorp, string))
202 		gdk_rgba_parse (colorp, "#000000"); /* If all else fails use black */
203 }
204 
205 static char *
color_to_string(const GdkRGBA * color)206 color_to_string (const GdkRGBA *color)
207 {
208 	return g_strdup_printf ("#%02x%02x%02x",
209 				((guint) (color->red * 65535)) >> 8,
210 				((guint) (color->green * 65535)) >> 8,
211 				((guint) (color->blue * 65535)) >> 8);
212 }
213 
214 static gboolean
do_changed(MateBG * bg)215 do_changed (MateBG *bg)
216 {
217 	bg->changed_id = 0;
218 
219 	g_signal_emit (G_OBJECT (bg), signals[CHANGED], 0);
220 
221 	return FALSE;
222 }
223 
224 static void
queue_changed(MateBG * bg)225 queue_changed (MateBG *bg)
226 {
227 	if (bg->changed_id > 0) {
228 		g_source_remove (bg->changed_id);
229 	}
230 
231 	bg->changed_id = g_timeout_add_full (G_PRIORITY_LOW,
232 					     100,
233 					     (GSourceFunc)do_changed,
234 					     bg,
235 					     NULL);
236 }
237 
238 static gboolean
do_transitioned(MateBG * bg)239 do_transitioned (MateBG *bg)
240 {
241 	bg->transitioned_id = 0;
242 
243 	if (bg->pixbuf_cache) {
244 		g_object_unref (bg->pixbuf_cache);
245 		bg->pixbuf_cache = NULL;
246 	}
247 
248 	g_signal_emit (G_OBJECT (bg), signals[TRANSITIONED], 0);
249 
250 	return FALSE;
251 }
252 
253 static void
queue_transitioned(MateBG * bg)254 queue_transitioned (MateBG *bg)
255 {
256 	if (bg->transitioned_id > 0) {
257 		g_source_remove (bg->transitioned_id);
258 	}
259 
260 	bg->transitioned_id = g_timeout_add_full (G_PRIORITY_LOW,
261 						  100,
262 						  (GSourceFunc)do_transitioned,
263 						  bg,
264 						  NULL);
265 }
266 
267 /* This function loads the user's preferences */
268 void
mate_bg_load_from_preferences(MateBG * bg)269 mate_bg_load_from_preferences (MateBG *bg)
270 {
271 	GSettings *settings;
272 	settings = g_settings_new (MATE_BG_SCHEMA);
273 
274 	mate_bg_load_from_gsettings (bg, settings);
275 	g_object_unref (settings);
276 
277 	/* Queue change to force background redraw */
278 	queue_changed (bg);
279 }
280 
281 /* This function loads default system settings */
282 void
mate_bg_load_from_system_preferences(MateBG * bg)283 mate_bg_load_from_system_preferences (MateBG *bg)
284 {
285 	GSettings *settings;
286 
287 	/* FIXME: we need to bind system settings instead of user but
288 	* that's currently impossible, not implemented yet.
289 	* Hence, reset to system default values.
290 	*/
291 	settings = g_settings_new (MATE_BG_SCHEMA);
292 
293 	mate_bg_load_from_system_gsettings (bg, settings, FALSE);
294 
295 	g_object_unref (settings);
296 }
297 
298 /* This function loads (and optionally resets to) default system settings */
299 void
mate_bg_load_from_system_gsettings(MateBG * bg,GSettings * settings,gboolean reset_apply)300 mate_bg_load_from_system_gsettings (MateBG    *bg,
301 				    GSettings *settings,
302 				    gboolean   reset_apply)
303 {
304 	GSettingsSchema *schema;
305 	gchar **keys;
306 	gchar **k;
307 
308 	g_return_if_fail (MATE_IS_BG (bg));
309 	g_return_if_fail (G_IS_SETTINGS (settings));
310 
311 	g_settings_delay (settings);
312 
313 	g_object_get (settings, "settings-schema", &schema, NULL);
314 	keys = g_settings_schema_list_keys (schema);
315 	g_settings_schema_unref (schema);
316 
317 	for (k = keys; *k; k++) {
318 		g_settings_reset (settings, *k);
319 	}
320 	g_strfreev (keys);
321 
322 	if (reset_apply) {
323 		/* Apply changes atomically. */
324 		g_settings_apply (settings);
325 	} else {
326 		mate_bg_load_from_gsettings (bg, settings);
327 		g_settings_revert (settings);
328 	}
329 }
330 
331 void
mate_bg_load_from_gsettings(MateBG * bg,GSettings * settings)332 mate_bg_load_from_gsettings (MateBG    *bg,
333 			     GSettings *settings)
334 {
335 	char    *tmp;
336 	char    *filename;
337 	MateBGColorType ctype;
338 	GdkRGBA c1, c2;
339 	MateBGPlacement placement;
340 
341 	g_return_if_fail (MATE_IS_BG (bg));
342 	g_return_if_fail (G_IS_SETTINGS (settings));
343 
344 	bg->is_enabled = g_settings_get_boolean (settings, MATE_BG_KEY_DRAW_BACKGROUND);
345 
346 	/* Filename */
347 	filename = NULL;
348 	tmp = g_settings_get_string (settings, MATE_BG_KEY_PICTURE_FILENAME);
349 	if (tmp && *tmp != '\0') {
350 		/* FIXME: UTF-8 checks should go away.
351 		 * picture-filename is of type string, which can only be used for
352 		 * UTF-8 strings, and some filenames are not, dependending on the
353 		 * locale used.
354 		 * It would be better (and simpler) to change to a URI instead,
355 		 * as URIs are UTF-8 encoded strings.
356 		 */
357 		if (g_utf8_validate (tmp, -1, NULL) &&
358 		    g_file_test (tmp, G_FILE_TEST_EXISTS)) {
359 			filename = g_strdup (tmp);
360 		} else {
361 			filename = g_filename_from_utf8 (tmp, -1, NULL, NULL, NULL);
362 		}
363 
364 		/* Fallback to default BG if the filename set is non-existent */
365 		if (filename != NULL && !g_file_test (filename, G_FILE_TEST_EXISTS)) {
366 
367 			g_free (filename);
368 
369 			g_settings_delay (settings);
370 			g_settings_reset (settings, MATE_BG_KEY_PICTURE_FILENAME);
371 			filename = g_settings_get_string (settings, MATE_BG_KEY_PICTURE_FILENAME);
372 			g_settings_revert (settings);
373 
374 			//* Check if default background exists, also */
375 			if (filename != NULL && !g_file_test (filename, G_FILE_TEST_EXISTS)) {
376 				g_free (filename);
377 				filename = NULL;
378 			}
379 		}
380 	}
381 	g_free (tmp);
382 
383 	/* Colors */
384 	tmp = g_settings_get_string (settings, MATE_BG_KEY_PRIMARY_COLOR);
385 	color_from_string (tmp, &c1);
386 	g_free (tmp);
387 
388 	tmp = g_settings_get_string (settings, MATE_BG_KEY_SECONDARY_COLOR);
389 	color_from_string (tmp, &c2);
390 	g_free (tmp);
391 
392 	/* Color type */
393 	ctype = g_settings_get_enum (settings, MATE_BG_KEY_COLOR_TYPE);
394 
395 	/* Placement */
396 	placement = g_settings_get_enum (settings, MATE_BG_KEY_PICTURE_PLACEMENT);
397 
398 	mate_bg_set_color (bg, ctype, &c1, &c2);
399 	mate_bg_set_placement (bg, placement);
400 	mate_bg_set_filename (bg, filename);
401 
402 	g_free (filename);
403 }
404 
405 void
mate_bg_save_to_preferences(MateBG * bg)406 mate_bg_save_to_preferences (MateBG *bg)
407 {
408 	GSettings *settings;
409 	settings = g_settings_new (MATE_BG_SCHEMA);
410 
411 	mate_bg_save_to_gsettings (bg, settings);
412 	g_object_unref (settings);
413 }
414 
415 void
mate_bg_save_to_gsettings(MateBG * bg,GSettings * settings)416 mate_bg_save_to_gsettings (MateBG    *bg,
417 			   GSettings *settings)
418 {
419 	gchar *primary;
420 	gchar *secondary;
421 
422 	g_return_if_fail (MATE_IS_BG (bg));
423 	g_return_if_fail (G_IS_SETTINGS (settings));
424 
425 	primary = color_to_string (&bg->primary);
426 	secondary = color_to_string (&bg->secondary);
427 
428 	g_settings_delay (settings);
429 
430 	g_settings_set_boolean (settings, MATE_BG_KEY_DRAW_BACKGROUND, bg->is_enabled);
431 	g_settings_set_string (settings, MATE_BG_KEY_PICTURE_FILENAME, bg->filename);
432 	g_settings_set_enum (settings, MATE_BG_KEY_PICTURE_PLACEMENT, bg->placement);
433 	g_settings_set_string (settings, MATE_BG_KEY_PRIMARY_COLOR, primary);
434 	g_settings_set_string (settings, MATE_BG_KEY_SECONDARY_COLOR, secondary);
435 	g_settings_set_enum (settings, MATE_BG_KEY_COLOR_TYPE, bg->color_type);
436 
437 	/* Apply changes atomically. */
438 	g_settings_apply (settings);
439 
440 	g_free (primary);
441 	g_free (secondary);
442 }
443 
444 
445 static void
mate_bg_init(MateBG * bg)446 mate_bg_init (MateBG *bg)
447 {
448 }
449 
450 static void
mate_bg_dispose(GObject * object)451 mate_bg_dispose (GObject *object)
452 {
453 	MateBG *bg = MATE_BG (object);
454 
455 	if (bg->file_monitor) {
456 		g_object_unref (bg->file_monitor);
457 		bg->file_monitor = NULL;
458 	}
459 
460 	clear_cache (bg);
461 
462 	G_OBJECT_CLASS (mate_bg_parent_class)->dispose (object);
463 }
464 
465 static void
mate_bg_finalize(GObject * object)466 mate_bg_finalize (GObject *object)
467 {
468 	MateBG *bg = MATE_BG (object);
469 
470 	if (bg->changed_id != 0) {
471 		g_source_remove (bg->changed_id);
472 		bg->changed_id = 0;
473 	}
474 
475 	if (bg->transitioned_id != 0) {
476 		g_source_remove (bg->transitioned_id);
477 		bg->transitioned_id = 0;
478 	}
479 
480 	if (bg->blow_caches_id != 0) {
481 		g_source_remove (bg->blow_caches_id);
482 		bg->blow_caches_id = 0;
483 	}
484 
485 	g_free (bg->filename);
486 	bg->filename = NULL;
487 
488 	G_OBJECT_CLASS (mate_bg_parent_class)->finalize (object);
489 }
490 
491 static void
mate_bg_class_init(MateBGClass * klass)492 mate_bg_class_init (MateBGClass *klass)
493 {
494 	GObjectClass *object_class = G_OBJECT_CLASS (klass);
495 
496 	object_class->dispose = mate_bg_dispose;
497 	object_class->finalize = mate_bg_finalize;
498 
499 	signals[CHANGED] = g_signal_new ("changed",
500 					 G_OBJECT_CLASS_TYPE (object_class),
501 					 G_SIGNAL_RUN_LAST,
502 					 0,
503 					 NULL, NULL,
504 					 g_cclosure_marshal_VOID__VOID,
505 					 G_TYPE_NONE, 0);
506 
507 	signals[TRANSITIONED] = g_signal_new ("transitioned",
508 					 G_OBJECT_CLASS_TYPE (object_class),
509 					 G_SIGNAL_RUN_LAST,
510 					 0,
511 					 NULL, NULL,
512 					 g_cclosure_marshal_VOID__VOID,
513 					 G_TYPE_NONE, 0);
514 }
515 
516 MateBG *
mate_bg_new(void)517 mate_bg_new (void)
518 {
519 	return g_object_new (MATE_TYPE_BG, NULL);
520 }
521 
522 void
mate_bg_set_color(MateBG * bg,MateBGColorType type,GdkRGBA * primary,GdkRGBA * secondary)523 mate_bg_set_color (MateBG *bg,
524 		    MateBGColorType type,
525 		    GdkRGBA *primary,
526 		    GdkRGBA *secondary)
527 {
528 	g_return_if_fail (bg != NULL);
529 	g_return_if_fail (primary != NULL);
530 
531 	if (bg->color_type != type			||
532 	    !gdk_rgba_equal (&bg->primary, primary)			||
533 	    (secondary && !gdk_rgba_equal (&bg->secondary, secondary))) {
534 		bg->color_type = type;
535 		bg->primary = *primary;
536 		if (secondary) {
537 			bg->secondary = *secondary;
538 		}
539 
540 		queue_changed (bg);
541 	}
542 }
543 
544 void
mate_bg_set_placement(MateBG * bg,MateBGPlacement placement)545 mate_bg_set_placement (MateBG		*bg,
546 		       MateBGPlacement	 placement)
547 {
548 	g_return_if_fail (bg != NULL);
549 
550 	if (bg->placement != placement) {
551 		bg->placement = placement;
552 
553 		queue_changed (bg);
554 	}
555 }
556 
557 MateBGPlacement
mate_bg_get_placement(MateBG * bg)558 mate_bg_get_placement (MateBG *bg)
559 {
560 	g_return_val_if_fail (bg != NULL, -1);
561 
562 	return bg->placement;
563 }
564 
565 void
mate_bg_get_color(MateBG * bg,MateBGColorType * type,GdkRGBA * primary,GdkRGBA * secondary)566 mate_bg_get_color (MateBG		*bg,
567 		   MateBGColorType	*type,
568 		   GdkRGBA		*primary,
569 		   GdkRGBA		*secondary)
570 {
571 	g_return_if_fail (bg != NULL);
572 
573 	if (type)
574 		*type = bg->color_type;
575 
576 	if (primary)
577 		*primary = bg->primary;
578 
579 	if (secondary)
580 		*secondary = bg->secondary;
581 }
582 
583 void
mate_bg_set_draw_background(MateBG * bg,gboolean draw_background)584 mate_bg_set_draw_background (MateBG	*bg,
585 			     gboolean	 draw_background)
586 {
587 	g_return_if_fail (bg != NULL);
588 
589 	if (bg->is_enabled != draw_background) {
590 		bg->is_enabled = draw_background;
591 
592 		queue_changed (bg);
593 	}
594 }
595 
596 gboolean
mate_bg_get_draw_background(MateBG * bg)597 mate_bg_get_draw_background (MateBG *bg)
598 {
599 	g_return_val_if_fail (bg != NULL, FALSE);
600 
601 	return bg->is_enabled;
602 }
603 
604 const gchar *
mate_bg_get_filename(MateBG * bg)605 mate_bg_get_filename (MateBG *bg)
606 {
607 	g_return_val_if_fail (bg != NULL, NULL);
608 
609 	return bg->filename;
610 }
611 
612 static inline gchar *
get_wallpaper_cache_dir(void)613 get_wallpaper_cache_dir (void)
614 {
615 	return g_build_filename (g_get_user_cache_dir(), MATE_BG_CACHE_DIR, NULL);
616 }
617 
618 static inline gchar *
get_wallpaper_cache_prefix_name(gint num_monitor,MateBGPlacement placement,gint width,gint height)619 get_wallpaper_cache_prefix_name (gint                     num_monitor,
620 				 MateBGPlacement          placement,
621 				 gint                     width,
622 				 gint                     height)
623 {
624 	return g_strdup_printf ("%i_%i_%i_%i", num_monitor, (gint) placement, width, height);
625 }
626 
627 static char *
get_wallpaper_cache_filename(const char * filename,gint num_monitor,MateBGPlacement placement,gint width,gint height)628 get_wallpaper_cache_filename (const char              *filename,
629 			      gint                     num_monitor,
630 			      MateBGPlacement          placement,
631 			      gint                     width,
632 			      gint                     height)
633 {
634 	gchar *cache_filename;
635 	gchar *cache_prefix_name;
636 	gchar *md5_filename;
637 	gchar *cache_basename;
638 	gchar *cache_dir;
639 
640 	md5_filename = g_compute_checksum_for_data (G_CHECKSUM_MD5, (const guchar *) filename,
641 						    strlen (filename));
642 	cache_prefix_name = get_wallpaper_cache_prefix_name (num_monitor, placement, width, height);
643 	cache_basename = g_strdup_printf ("%s_%s", cache_prefix_name, md5_filename);
644 	cache_dir = get_wallpaper_cache_dir ();
645 	cache_filename = g_build_filename (cache_dir, cache_basename, NULL);
646 
647 	g_free (cache_prefix_name);
648 	g_free (md5_filename);
649 	g_free (cache_basename);
650 	g_free (cache_dir);
651 
652 	return cache_filename;
653 }
654 
655 static void
cleanup_cache_for_monitor(gchar * cache_dir,gint num_monitor)656 cleanup_cache_for_monitor (gchar *cache_dir,
657 			   gint   num_monitor)
658 {
659 	GDir            *g_cache_dir;
660 	gchar           *monitor_prefix;
661 	const gchar     *file;
662 
663 	g_cache_dir = g_dir_open (cache_dir, 0, NULL);
664 	monitor_prefix = g_strdup_printf ("%i_", num_monitor);
665 
666 	file = g_dir_read_name (g_cache_dir);
667 	while (file != NULL) {
668 		gchar *path = g_build_filename (cache_dir, file, NULL);
669 
670 		/* purge files with same monitor id */
671 		if (g_str_has_prefix (file, monitor_prefix) &&
672 		    g_file_test (path, G_FILE_TEST_IS_REGULAR))
673 			g_unlink (path);
674 
675 		g_free (path);
676 
677 		file = g_dir_read_name (g_cache_dir);
678 	}
679 
680 	g_free (monitor_prefix);
681 	g_dir_close (g_cache_dir);
682 }
683 
684 static gboolean
cache_file_is_valid(const char * filename,const char * cache_filename)685 cache_file_is_valid (const char *filename,
686 		     const char *cache_filename)
687 {
688 	time_t mtime;
689 	time_t cache_mtime;
690 
691 	if (!g_file_test (cache_filename, G_FILE_TEST_IS_REGULAR))
692 		return FALSE;
693 
694 	mtime = get_mtime (filename);
695 	cache_mtime = get_mtime (cache_filename);
696 
697 	return (mtime < cache_mtime);
698 }
699 
700 static void
refresh_cache_file(MateBG * bg,GdkPixbuf * new_pixbuf,gint num_monitor,gint width,gint height)701 refresh_cache_file (MateBG     *bg,
702 		    GdkPixbuf  *new_pixbuf,
703 		    gint        num_monitor,
704 		    gint        width,
705 		    gint        height)
706 {
707 	gchar           *cache_filename;
708 	gchar           *cache_dir;
709 	GdkPixbufFormat *format;
710 	gchar           *format_name;
711 
712 	if ((num_monitor == -1) || (width <= 300) || (height <= 300))
713 		return;
714 
715 	cache_filename = get_wallpaper_cache_filename (bg->filename, num_monitor,
716 							bg->placement, width, height);
717 	cache_dir = get_wallpaper_cache_dir ();
718 
719 	/* Only refresh scaled file on disk if useful (and don't cache slideshow) */
720 	if (!cache_file_is_valid (bg->filename, cache_filename)) {
721 		format = gdk_pixbuf_get_file_info (bg->filename, NULL, NULL);
722 
723 		if (format != NULL) {
724 			if (!g_file_test (cache_dir, G_FILE_TEST_IS_DIR)) {
725 				g_mkdir_with_parents (cache_dir, 0700);
726 			} else {
727 				cleanup_cache_for_monitor (cache_dir, num_monitor);
728 			}
729 
730 			format_name = gdk_pixbuf_format_get_name (format);
731 
732 			if (strcmp (format_name, "jpeg") == 0)
733 				gdk_pixbuf_save (new_pixbuf, cache_filename, format_name,
734 						 NULL, "quality", "100", NULL);
735 			else
736 				gdk_pixbuf_save (new_pixbuf, cache_filename, format_name,
737 						 NULL, NULL);
738 
739 			g_free (format_name);
740 		}
741 	}
742 
743 	g_free (cache_filename);
744 	g_free (cache_dir);
745 }
746 
747 static void
file_changed(GFileMonitor * file_monitor,GFile * child,GFile * other_file,GFileMonitorEvent event_type,gpointer user_data)748 file_changed (GFileMonitor     *file_monitor,
749 	      GFile            *child,
750 	      GFile            *other_file,
751 	      GFileMonitorEvent event_type,
752 	      gpointer          user_data)
753 {
754 	MateBG *bg = MATE_BG (user_data);
755 
756 	clear_cache (bg);
757 	queue_changed (bg);
758 }
759 
760 void
mate_bg_set_filename(MateBG * bg,const char * filename)761 mate_bg_set_filename (MateBG	 *bg,
762 		      const char *filename)
763 {
764 	g_return_if_fail (bg != NULL);
765 
766 	if (is_different (bg, filename)) {
767 		g_free (bg->filename);
768 
769 		bg->filename = g_strdup (filename);
770 		bg->file_mtime = get_mtime (bg->filename);
771 
772 		if (bg->file_monitor) {
773 			g_object_unref (bg->file_monitor);
774 			bg->file_monitor = NULL;
775 		}
776 
777 		if (bg->filename) {
778 			GFile *f = g_file_new_for_path (bg->filename);
779 
780 			bg->file_monitor = g_file_monitor_file (f, 0, NULL, NULL);
781 			g_signal_connect (bg->file_monitor, "changed",
782 					  G_CALLBACK (file_changed), bg);
783 
784 			g_object_unref (f);
785 		}
786 
787 		clear_cache (bg);
788 
789 		queue_changed (bg);
790 	}
791 }
792 
793 static void
draw_color_area(MateBG * bg,GdkPixbuf * dest,GdkRectangle * rect)794 draw_color_area (MateBG       *bg,
795 		 GdkPixbuf    *dest,
796 		 GdkRectangle *rect)
797 {
798 	guint32 pixel;
799 	GdkRectangle extent;
800 
801         extent.x = 0;
802         extent.y = 0;
803         extent.width = gdk_pixbuf_get_width (dest);
804         extent.height = gdk_pixbuf_get_height (dest);
805 
806 	gdk_rectangle_intersect (rect, &extent, rect);
807 
808 	switch (bg->color_type) {
809 	case MATE_BG_COLOR_SOLID:
810 		/* not really a big deal to ignore the area of interest */
811 		pixel = ((guint) (bg->primary.red * 0xff) << 24)   |
812 			((guint) (bg->primary.green * 0xff) << 16) |
813 			((guint) (bg->primary.blue * 0xff) << 8)   |
814 			(0xff);
815 
816 		gdk_pixbuf_fill (dest, pixel);
817 		break;
818 
819 	case MATE_BG_COLOR_H_GRADIENT:
820 		pixbuf_draw_gradient (dest, TRUE, &(bg->primary), &(bg->secondary), rect);
821 		break;
822 
823 	case MATE_BG_COLOR_V_GRADIENT:
824 		pixbuf_draw_gradient (dest, FALSE, &(bg->primary), &(bg->secondary), rect);
825 		break;
826 
827 	default:
828 		break;
829 	}
830 }
831 
832 static void
draw_color(MateBG * bg,GdkPixbuf * dest)833 draw_color (MateBG    *bg,
834 	    GdkPixbuf *dest)
835 {
836 	GdkRectangle rect;
837 
838 	rect.x = 0;
839 	rect.y = 0;
840 	rect.width = gdk_pixbuf_get_width (dest);
841 	rect.height = gdk_pixbuf_get_height (dest);
842 	draw_color_area (bg, dest, &rect);
843 }
844 
845 static void
draw_color_each_monitor(MateBG * bg,GdkPixbuf * dest,GdkScreen * screen)846 draw_color_each_monitor (MateBG    *bg,
847 			 GdkPixbuf *dest,
848 			 GdkScreen *screen)
849 {
850 	GdkDisplay *display;
851 	GdkRectangle rect;
852 	gint num_monitors;
853 	int monitor;
854 
855 	display = gdk_screen_get_display (screen);
856 	num_monitors = gdk_display_get_n_monitors (display);
857 	for (monitor = 0; monitor < num_monitors; monitor++) {
858 		gdk_monitor_get_geometry (gdk_display_get_monitor (display, monitor), &rect);
859 		draw_color_area (bg, dest, &rect);
860 	}
861 }
862 
863 static GdkPixbuf *
pixbuf_clip_to_fit(GdkPixbuf * src,int max_width,int max_height)864 pixbuf_clip_to_fit (GdkPixbuf *src,
865 		    int        max_width,
866 		    int        max_height)
867 {
868 	int src_width, src_height;
869 	int w, h;
870 	int src_x, src_y;
871 	GdkPixbuf *pixbuf;
872 
873 	src_width = gdk_pixbuf_get_width (src);
874 	src_height = gdk_pixbuf_get_height (src);
875 
876 	if (src_width < max_width && src_height < max_height)
877 		return g_object_ref (src);
878 
879 	w = MIN(src_width, max_width);
880 	h = MIN(src_height, max_height);
881 
882 	pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB,
883 				 gdk_pixbuf_get_has_alpha (src),
884 				 8, w, h);
885 
886 	src_x = (src_width - w) / 2;
887 	src_y = (src_height - h) / 2;
888 	gdk_pixbuf_copy_area (src,
889 			      src_x, src_y,
890 			      w, h,
891 			      pixbuf,
892 			      0, 0);
893 	return pixbuf;
894 }
895 
896 static GdkPixbuf *
get_scaled_pixbuf(MateBGPlacement placement,GdkPixbuf * pixbuf,int width,int height,int * x,int * y,int * w,int * h)897 get_scaled_pixbuf (MateBGPlacement  placement,
898 		   GdkPixbuf       *pixbuf,
899 		   int width, int height,
900 		   int *x, int *y,
901 		   int *w, int *h)
902 {
903 	GdkPixbuf *new;
904 
905 #if 0
906 	g_print ("original_width: %d %d\n",
907 		 gdk_pixbuf_get_width (pixbuf),
908 		 gdk_pixbuf_get_height (pixbuf));
909 #endif
910 
911 	switch (placement) {
912 	case MATE_BG_PLACEMENT_SPANNED:
913                 new = pixbuf_scale_to_fit (pixbuf, width, height);
914 		break;
915 	case MATE_BG_PLACEMENT_ZOOMED:
916 		new = pixbuf_scale_to_min (pixbuf, width, height);
917 		break;
918 
919 	case MATE_BG_PLACEMENT_FILL_SCREEN:
920 		new = gdk_pixbuf_scale_simple (pixbuf, width, height,
921 					       GDK_INTERP_BILINEAR);
922 		break;
923 
924 	case MATE_BG_PLACEMENT_SCALED:
925 		new = pixbuf_scale_to_fit (pixbuf, width, height);
926 		break;
927 
928 	case MATE_BG_PLACEMENT_CENTERED:
929 	case MATE_BG_PLACEMENT_TILED:
930 	default:
931 		new = pixbuf_clip_to_fit (pixbuf, width, height);
932 		break;
933 	}
934 
935 	*w = gdk_pixbuf_get_width (new);
936 	*h = gdk_pixbuf_get_height (new);
937 	*x = (width - *w) / 2;
938 	*y = (height - *h) / 2;
939 
940 	return new;
941 }
942 
943 
944 static void
draw_image_area(MateBG * bg,gint num_monitor,GdkPixbuf * pixbuf,GdkPixbuf * dest,GdkRectangle * area)945 draw_image_area (MateBG        *bg,
946 		 gint           num_monitor,
947 		 GdkPixbuf     *pixbuf,
948 		 GdkPixbuf     *dest,
949 		 GdkRectangle  *area)
950 {
951 	int dest_width = area->width;
952 	int dest_height = area->height;
953 	int x, y, w, h;
954 	GdkPixbuf *scaled;
955 
956 	if (!pixbuf)
957 		return;
958 
959 	scaled = get_scaled_pixbuf (bg->placement, pixbuf, dest_width, dest_height, &x, &y, &w, &h);
960 
961 	switch (bg->placement) {
962 	case MATE_BG_PLACEMENT_TILED:
963 		pixbuf_tile (scaled, dest);
964 		break;
965 	case MATE_BG_PLACEMENT_ZOOMED:
966 	case MATE_BG_PLACEMENT_CENTERED:
967 	case MATE_BG_PLACEMENT_FILL_SCREEN:
968 	case MATE_BG_PLACEMENT_SCALED:
969 		pixbuf_blend (scaled, dest, 0, 0, w, h, x + area->x, y + area->y, 1.0);
970 		break;
971 	case MATE_BG_PLACEMENT_SPANNED:
972 		pixbuf_blend (scaled, dest, 0, 0, w, h, x, y, 1.0);
973 		break;
974 	default:
975 		g_assert_not_reached ();
976 		break;
977 	}
978 
979 	refresh_cache_file (bg, scaled, num_monitor, dest_width, dest_height);
980 
981 	g_object_unref (scaled);
982 }
983 
984 static void
draw_image_for_thumb(MateBG * bg,GdkPixbuf * pixbuf,GdkPixbuf * dest)985 draw_image_for_thumb (MateBG     *bg,
986 		      GdkPixbuf  *pixbuf,
987 		      GdkPixbuf  *dest)
988 {
989 	GdkRectangle rect;
990 
991 	rect.x = 0;
992 	rect.y = 0;
993 	rect.width = gdk_pixbuf_get_width (dest);
994 	rect.height = gdk_pixbuf_get_height (dest);
995 
996 	draw_image_area (bg, -1, pixbuf, dest, &rect);
997 }
998 
999 static void
draw_once(MateBG * bg,GdkPixbuf * dest,gboolean is_root)1000 draw_once (MateBG    *bg,
1001 	   GdkPixbuf *dest,
1002 	   gboolean   is_root)
1003 {
1004 	GdkRectangle rect;
1005 	GdkPixbuf   *pixbuf;
1006 	gint         monitor;
1007 
1008 	/* whether we're drawing on root window or normal (Caja) window */
1009 	monitor = (is_root) ? 0 : -1;
1010 
1011 	rect.x = 0;
1012 	rect.y = 0;
1013 	rect.width = gdk_pixbuf_get_width (dest);
1014 	rect.height = gdk_pixbuf_get_height (dest);
1015 
1016 	pixbuf = get_pixbuf_for_size (bg, monitor, rect.width, rect.height);
1017 	if (pixbuf) {
1018 		draw_image_area (bg, monitor, pixbuf, dest, &rect);
1019 
1020 		g_object_unref (pixbuf);
1021 	}
1022 }
1023 
1024 static void
draw_each_monitor(MateBG * bg,GdkPixbuf * dest,GdkScreen * screen)1025 draw_each_monitor (MateBG    *bg,
1026 		   GdkPixbuf *dest,
1027 		   GdkScreen *screen)
1028 {
1029 	GdkDisplay *display;
1030 
1031 	display = gdk_screen_get_display (screen);
1032 	gint num_monitors = gdk_display_get_n_monitors (display);
1033 	gint monitor = 0;
1034 
1035 	for (; monitor < num_monitors; monitor++) {
1036 		GdkRectangle rect;
1037 		GdkPixbuf *pixbuf;
1038 
1039 		gdk_monitor_get_geometry (gdk_display_get_monitor (display, monitor), &rect);
1040 
1041 		pixbuf = get_pixbuf_for_size (bg, monitor, rect.width, rect.height);
1042 		if (pixbuf) {
1043 			draw_image_area (bg, monitor, pixbuf, dest, &rect);
1044 
1045 			g_object_unref (pixbuf);
1046 		}
1047 	}
1048 }
1049 
1050 void
mate_bg_draw(MateBG * bg,GdkPixbuf * dest,GdkScreen * screen,gboolean is_root)1051 mate_bg_draw (MateBG     *bg,
1052 	       GdkPixbuf *dest,
1053 	       GdkScreen *screen,
1054 	       gboolean   is_root)
1055 {
1056 	if (!bg)
1057 		return;
1058 
1059 	if (is_root && (bg->placement != MATE_BG_PLACEMENT_SPANNED)) {
1060 		draw_color_each_monitor (bg, dest, screen);
1061 		if (bg->filename) {
1062 			draw_each_monitor (bg, dest, screen);
1063 		}
1064 	} else {
1065 		draw_color (bg, dest);
1066 		if (bg->filename) {
1067 			draw_once (bg, dest, is_root);
1068 		}
1069 	}
1070 }
1071 
1072 gboolean
mate_bg_has_multiple_sizes(MateBG * bg)1073 mate_bg_has_multiple_sizes (MateBG *bg)
1074 {
1075 	SlideShow *show;
1076 	gboolean ret;
1077 
1078 	g_return_val_if_fail (bg != NULL, FALSE);
1079 
1080 	ret = FALSE;
1081 
1082 	show = get_as_slideshow (bg, bg->filename);
1083 	if (show) {
1084 		ret = slideshow_has_multiple_sizes (show);
1085 		slideshow_unref (show);
1086 	}
1087 
1088 	return ret;
1089 }
1090 
1091 static void
mate_bg_get_pixmap_size(MateBG * bg,int width,int height,int * pixmap_width,int * pixmap_height)1092 mate_bg_get_pixmap_size (MateBG   *bg,
1093 			  int        width,
1094 			  int        height,
1095 			  int       *pixmap_width,
1096 			  int       *pixmap_height)
1097 {
1098 	int dummy;
1099 
1100 	if (!pixmap_width)
1101 		pixmap_width = &dummy;
1102 	if (!pixmap_height)
1103 		pixmap_height = &dummy;
1104 
1105 	*pixmap_width = width;
1106 	*pixmap_height = height;
1107 
1108 	if (!bg->filename) {
1109 		switch (bg->color_type) {
1110 		case MATE_BG_COLOR_SOLID:
1111 			*pixmap_width = 1;
1112 			*pixmap_height = 1;
1113 			break;
1114 
1115 		case MATE_BG_COLOR_H_GRADIENT:
1116 		case MATE_BG_COLOR_V_GRADIENT:
1117 			break;
1118 		}
1119 
1120 		return;
1121 	}
1122 }
1123 
1124 /**
1125  * mate_bg_create_surface:
1126  * @bg: MateBG
1127  * @window:
1128  * @width:
1129  * @height:
1130  * @root:
1131  *
1132  * Create a surface that can be set as background for @window. If @root is
1133  * TRUE, the surface created will be created by a temporary X server connection
1134  * so that if someone calls XKillClient on it, it won't affect the application
1135  * who created it.
1136  **/
1137 cairo_surface_t *
mate_bg_create_surface(MateBG * bg,GdkWindow * window,int width,int height,gboolean root)1138 mate_bg_create_surface (MateBG      *bg,
1139 		 	GdkWindow   *window,
1140 			int	     width,
1141 			int	     height,
1142 			gboolean     root)
1143 {
1144 	return mate_bg_create_surface_scale (bg,
1145 					     window,
1146 					     width,
1147 					     height,
1148 					     1,
1149 					     root);
1150 }
1151 
1152 /**
1153  * mate_bg_create_surface_scale:
1154  * @bg: MateBG
1155  * @window:
1156  * @width:
1157  * @height:
1158  * @scale:
1159  * @root:
1160  *
1161  * Create a scaled surface that can be set as background for @window. If @root is
1162  * TRUE, the surface created will be created by a temporary X server connection
1163  * so that if someone calls XKillClient on it, it won't affect the application
1164  * who created it.
1165  **/
1166 cairo_surface_t *
mate_bg_create_surface_scale(MateBG * bg,GdkWindow * window,int width,int height,int scale,gboolean root)1167 mate_bg_create_surface_scale (MateBG      *bg,
1168 			      GdkWindow   *window,
1169 			      int          width,
1170 			      int          height,
1171 			      int          scale,
1172 			      gboolean     root)
1173 {
1174 	int pm_width, pm_height;
1175 
1176 	cairo_surface_t *surface;
1177 	cairo_t *cr;
1178 
1179 	g_return_val_if_fail (bg != NULL, NULL);
1180 	g_return_val_if_fail (window != NULL, NULL);
1181 
1182 	if (bg->pixbuf_cache &&
1183 	    (gdk_pixbuf_get_width (bg->pixbuf_cache) != width ||
1184 	     gdk_pixbuf_get_height (bg->pixbuf_cache) != height))
1185 	{
1186 		g_object_unref (bg->pixbuf_cache);
1187 		bg->pixbuf_cache = NULL;
1188 	}
1189 
1190 	mate_bg_get_pixmap_size (bg, width, height, &pm_width, &pm_height);
1191 
1192 	if (root)
1193 	{
1194 		surface = make_root_pixmap (window, pm_width * scale, pm_height * scale);
1195 	}
1196 	else
1197 	{
1198 		surface = gdk_window_create_similar_surface (window, CAIRO_CONTENT_COLOR,
1199 							     pm_width, pm_height);
1200 	}
1201 
1202 	cr = cairo_create (surface);
1203 	cairo_scale (cr, (double)scale, (double)scale);
1204 
1205 	if (!bg->filename && bg->color_type == MATE_BG_COLOR_SOLID) {
1206 		gdk_cairo_set_source_rgba (cr, &(bg->primary));
1207 	}
1208 	else
1209 	{
1210 		GdkPixbuf *pixbuf;
1211 
1212 		pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB, FALSE, 8,
1213 					 width, height);
1214 		mate_bg_draw (bg, pixbuf, gdk_window_get_screen (window), root);
1215 		gdk_cairo_set_source_pixbuf (cr, pixbuf, 0, 0);
1216 		g_object_unref (pixbuf);
1217 	}
1218 
1219 	cairo_paint (cr);
1220 
1221 	cairo_destroy (cr);
1222 
1223 	return surface;
1224 }
1225 
1226 
1227 /* determine if a background is darker or lighter than average, to help
1228  * clients know what colors to draw on top with
1229  */
1230 gboolean
mate_bg_is_dark(MateBG * bg,int width,int height)1231 mate_bg_is_dark (MateBG *bg,
1232 		  int      width,
1233 		  int      height)
1234 {
1235 	GdkRGBA color;
1236 	int intensity;
1237 	GdkPixbuf *pixbuf;
1238 
1239 	g_return_val_if_fail (bg != NULL, FALSE);
1240 
1241 	if (bg->color_type == MATE_BG_COLOR_SOLID) {
1242 		color = bg->primary;
1243 	} else {
1244 		color.red = (bg->primary.red + bg->secondary.red) / 2;
1245 		color.green = (bg->primary.green + bg->secondary.green) / 2;
1246 		color.blue = (bg->primary.blue + bg->secondary.blue) / 2;
1247 	}
1248 	pixbuf = get_pixbuf_for_size (bg, -1, width, height);
1249 	if (pixbuf) {
1250 		GdkRGBA argb;
1251 		guchar a, r, g, b;
1252 
1253 		pixbuf_average_value (pixbuf, &argb);
1254 		a = argb.alpha * 0xff;
1255 		r = argb.red * 0xff;
1256 		g = argb.green * 0xff;
1257 		b = argb.blue * 0xff;
1258 
1259 		color.red = (color.red * (0xFF - a) + r * 0x101 * a) / 0xFF;
1260 		color.green = (color.green * (0xFF - a) + g * 0x101 * a) / 0xFF;
1261 		color.blue = (color.blue * (0xFF - a) + b * 0x101 * a) / 0xFF;
1262 		g_object_unref (pixbuf);
1263 	}
1264 
1265 	intensity = ((guint) (color.red * 65535) * 77 +
1266 		     (guint) (color.green * 65535) * 150 +
1267 		     (guint) (color.blue * 65535) * 28) >> 16;
1268 
1269 	return intensity < 160; /* biased slightly to be dark */
1270 }
1271 
1272 /*
1273  * Create a persistent pixmap. We create a separate display
1274  * and set the closedown mode on it to RetainPermanent.
1275  */
1276 static cairo_surface_t *
make_root_pixmap(GdkWindow * window,gint width,gint height)1277 make_root_pixmap (GdkWindow *window, gint width, gint height)
1278 {
1279 	GdkScreen *screen = gdk_window_get_screen(window);
1280 	char *disp_name = DisplayString (GDK_WINDOW_XDISPLAY (window));
1281 	Display *display;
1282 	Pixmap xpixmap;
1283 	cairo_surface_t *surface;
1284 	int depth;
1285 
1286 	/* Desktop background pixmap should be created from dummy X client since most
1287 	 * applications will try to kill it with XKillClient later when changing pixmap
1288 	 */
1289 	display = XOpenDisplay (disp_name);
1290 
1291 	if (display == NULL) {
1292 		g_warning ("Unable to open display '%s' when setting background pixmap\n",
1293 		           (disp_name) ? disp_name : "NULL");
1294 		return NULL;
1295 	}
1296 
1297 	depth = DefaultDepth (display, gdk_x11_screen_get_screen_number (screen));
1298 	xpixmap = XCreatePixmap (display, GDK_WINDOW_XID (window), width, height, depth);
1299 
1300 	XFlush (display);
1301 	XSetCloseDownMode (display, RetainPermanent);
1302 	XCloseDisplay (display);
1303 
1304 	surface = cairo_xlib_surface_create (GDK_SCREEN_XDISPLAY (screen), xpixmap,
1305                                              GDK_VISUAL_XVISUAL (gdk_screen_get_system_visual (screen)),
1306         				     width, height);
1307 
1308 	return surface;
1309 }
1310 
1311 static gboolean
get_original_size(const char * filename,int * orig_width,int * orig_height)1312 get_original_size (const char *filename,
1313 		   int        *orig_width,
1314 		   int        *orig_height)
1315 {
1316 	gboolean result;
1317 
1318         if (gdk_pixbuf_get_file_info (filename, orig_width, orig_height))
1319 		result = TRUE;
1320 	else
1321 		result = FALSE;
1322 
1323 	return result;
1324 }
1325 
1326 static const char *
get_filename_for_size(MateBG * bg,gint best_width,gint best_height)1327 get_filename_for_size (MateBG *bg, gint best_width, gint best_height)
1328 {
1329 	SlideShow *show;
1330 	Slide *slide;
1331 	FileSize *size;
1332 
1333 	if (!bg->filename)
1334 		return NULL;
1335 
1336 	show = get_as_slideshow (bg, bg->filename);
1337 	if (!show) {
1338 		return bg->filename;
1339 	}
1340 
1341 	slide = get_current_slide (show, NULL);
1342 	slideshow_unref (show);
1343 	size = find_best_size (slide->file1, best_width, best_height);
1344 	return size->file;
1345 }
1346 
1347 gboolean
mate_bg_get_image_size(MateBG * bg,MateDesktopThumbnailFactory * factory,int best_width,int best_height,int * width,int * height)1348 mate_bg_get_image_size (MateBG	       *bg,
1349 			 MateDesktopThumbnailFactory *factory,
1350 			 int                    best_width,
1351 			 int                    best_height,
1352 			 int		       *width,
1353 			 int		       *height)
1354 {
1355 	GdkPixbuf *thumb;
1356 	gboolean result = FALSE;
1357 	const gchar *filename;
1358 
1359 	g_return_val_if_fail (bg != NULL, FALSE);
1360 	g_return_val_if_fail (factory != NULL, FALSE);
1361 
1362 	if (!bg->filename)
1363 		return FALSE;
1364 
1365 	filename = get_filename_for_size (bg, best_width, best_height);
1366 	thumb = create_thumbnail_for_filename (factory, filename);
1367 	if (thumb) {
1368 		if (get_thumb_annotations (thumb, width, height))
1369 			result = TRUE;
1370 
1371 		g_object_unref (thumb);
1372 	}
1373 
1374 	if (!result) {
1375 		if (get_original_size (filename, width, height))
1376 			result = TRUE;
1377 	}
1378 
1379 	return result;
1380 }
1381 
1382 static double
fit_factor(int from_width,int from_height,int to_width,int to_height)1383 fit_factor (int from_width, int from_height,
1384 	    int to_width,   int to_height)
1385 {
1386 	return MIN (to_width  / (double) from_width, to_height / (double) from_height);
1387 }
1388 
1389 /**
1390  * mate_bg_create_thumbnail:
1391  *
1392  * Returns: (transfer full): a #GdkPixbuf showing the background as a thumbnail
1393  */
1394 GdkPixbuf *
mate_bg_create_thumbnail(MateBG * bg,MateDesktopThumbnailFactory * factory,GdkScreen * screen,int dest_width,int dest_height)1395 mate_bg_create_thumbnail (MateBG               *bg,
1396 		           MateDesktopThumbnailFactory *factory,
1397 			   GdkScreen             *screen,
1398 			   int                    dest_width,
1399 			   int                    dest_height)
1400 {
1401 	GdkPixbuf *result;
1402 	GdkPixbuf *thumb;
1403 
1404 	g_return_val_if_fail (bg != NULL, NULL);
1405 
1406 	result = gdk_pixbuf_new (GDK_COLORSPACE_RGB, FALSE, 8, dest_width, dest_height);
1407 
1408 	draw_color (bg, result);
1409 
1410 	if (bg->filename) {
1411 		thumb = create_img_thumbnail (bg, factory, screen, dest_width, dest_height, -1);
1412 
1413 		if (thumb) {
1414 			draw_image_for_thumb (bg, thumb, result);
1415 			g_object_unref (thumb);
1416 		}
1417 	}
1418 
1419 	return result;
1420 }
1421 
1422 /**
1423  * mate_bg_get_surface_from_root:
1424  * @screen: a #GdkScreen
1425  *
1426  * This function queries the _XROOTPMAP_ID property from
1427  * the root window associated with @screen to determine
1428  * the current root window background surface and returns
1429  * a copy of it. If the _XROOTPMAP_ID is not set, then
1430  * a black surface is returned.
1431  *
1432  * Return value: a #cairo_surface_t if successful or %NULL
1433  **/
1434 cairo_surface_t *
mate_bg_get_surface_from_root(GdkScreen * screen)1435 mate_bg_get_surface_from_root (GdkScreen *screen)
1436 {
1437 	int result;
1438 	gint format;
1439 	gulong nitems;
1440 	gulong bytes_after;
1441 	guchar *data;
1442 	Atom type;
1443 	Display *display;
1444 	int screen_num;
1445 	cairo_surface_t *surface;
1446 	cairo_surface_t *source_pixmap;
1447 	GdkDisplay *gdkdisplay;
1448 	int width, height;
1449 	cairo_t *cr;
1450 
1451 	display = GDK_DISPLAY_XDISPLAY (gdk_screen_get_display (screen));
1452 	screen_num = gdk_x11_screen_get_screen_number (screen);
1453 
1454 	result = XGetWindowProperty (display,
1455 				     RootWindow (display, screen_num),
1456 				     gdk_x11_get_xatom_by_name ("_XROOTPMAP_ID"),
1457 				     0L, 1L, False, XA_PIXMAP,
1458 				     &type, &format, &nitems, &bytes_after,
1459 				     &data);
1460 	surface = NULL;
1461 	source_pixmap = NULL;
1462 
1463 	if (result != Success || type != XA_PIXMAP ||
1464 	    format != 32 || nitems != 1) {
1465 		XFree (data);
1466 		data = NULL;
1467 	}
1468 
1469 	if (data != NULL) {
1470 		gdkdisplay = gdk_screen_get_display (screen);
1471 		gdk_x11_display_error_trap_push (gdkdisplay);
1472 
1473 		Pixmap xpixmap = *(Pixmap *) data;
1474 		Window root_return;
1475 		int x_ret, y_ret;
1476 		unsigned int w_ret, h_ret, bw_ret, depth_ret;
1477 
1478 		if (XGetGeometry (GDK_SCREEN_XDISPLAY (screen),
1479 				  xpixmap,
1480 				  &root_return,
1481 				  &x_ret, &y_ret, &w_ret, &h_ret, &bw_ret, &depth_ret))
1482 		{
1483 			source_pixmap = cairo_xlib_surface_create (GDK_SCREEN_XDISPLAY (screen),
1484 								   xpixmap,
1485 								   GDK_VISUAL_XVISUAL (gdk_screen_get_system_visual (screen)),
1486 								   w_ret, h_ret);
1487 		}
1488 
1489 		gdk_x11_display_error_trap_pop_ignored (gdkdisplay);
1490 	}
1491 
1492 	width = WidthOfScreen (gdk_x11_screen_get_xscreen (screen));
1493 	height = HeightOfScreen (gdk_x11_screen_get_xscreen (screen));
1494 
1495 	if (source_pixmap) {
1496 		surface = cairo_surface_create_similar (source_pixmap,
1497 							CAIRO_CONTENT_COLOR,
1498 							width, height);
1499 
1500 		cr = cairo_create (surface);
1501 		cairo_set_source_surface (cr, source_pixmap, 0, 0);
1502 		cairo_paint (cr);
1503 
1504 		if (cairo_status (cr) != CAIRO_STATUS_SUCCESS) {
1505 			cairo_surface_destroy (surface);
1506 			surface = NULL;
1507 		}
1508 
1509 		cairo_destroy (cr);
1510 	}
1511 
1512 	if (surface == NULL) {
1513 		surface = gdk_window_create_similar_surface (gdk_screen_get_root_window (screen),
1514 							     CAIRO_CONTENT_COLOR,
1515 							     width, height);
1516 	}
1517 
1518 	if (source_pixmap != NULL)
1519 		cairo_surface_destroy (source_pixmap);
1520 
1521 	if (data != NULL)
1522 		XFree (data);
1523 
1524 	return surface;
1525 }
1526 
1527 /* Sets the "ESETROOT_PMAP_ID" property to later be used to free the pixmap,
1528  */
1529 static void
mate_bg_set_root_pixmap_id(GdkScreen * screen,Display * display,Pixmap xpixmap)1530 mate_bg_set_root_pixmap_id (GdkScreen       *screen,
1531 			    Display         *display,
1532 			    Pixmap           xpixmap)
1533 {
1534 	Window   xroot   = RootWindow (display, gdk_x11_screen_get_screen_number (screen));
1535 	char    *atom_names[] = {"_XROOTPMAP_ID", "ESETROOT_PMAP_ID"};
1536 	Atom     atoms[G_N_ELEMENTS(atom_names)] = {0};
1537 
1538 	Atom     type;
1539 	int      format, result;
1540 	unsigned long nitems, after;
1541 	unsigned char *data_root, *data_esetroot;
1542 	GdkDisplay *gdkdisplay;
1543 
1544 	/* Get atoms for both properties in an array, only if they exist.
1545 	 * This method is to avoid multiple round-trips to Xserver
1546 	 */
1547 	if (XInternAtoms (display, atom_names, G_N_ELEMENTS(atom_names), True, atoms) &&
1548 	    atoms[0] != None && atoms[1] != None) {
1549 		result = XGetWindowProperty (display, xroot, atoms[0], 0L, 1L,
1550 		                             False, AnyPropertyType,
1551 		                             &type, &format, &nitems, &after,
1552 		                             &data_root);
1553 
1554 		if (data_root != NULL && result == Success &&
1555 		    type == XA_PIXMAP && format == 32 && nitems == 1) {
1556 			result = XGetWindowProperty (display, xroot, atoms[1],
1557 			                             0L, 1L, False,
1558 			                             AnyPropertyType,
1559 			                             &type, &format, &nitems,
1560 			                             &after, &data_esetroot);
1561 
1562 			if (data_esetroot != NULL && result == Success &&
1563 			    type == XA_PIXMAP && format == 32 && nitems == 1) {
1564 				Pixmap xrootpmap = *((Pixmap *) data_root);
1565 				Pixmap esetrootpmap = *((Pixmap *) data_esetroot);
1566 
1567 				gdkdisplay = gdk_screen_get_display (screen);
1568 				gdk_x11_display_error_trap_push (gdkdisplay);
1569 				if (xrootpmap && xrootpmap == esetrootpmap) {
1570 					XKillClient (display, xrootpmap);
1571 				}
1572 				if (esetrootpmap && esetrootpmap != xrootpmap) {
1573 					XKillClient (display, esetrootpmap);
1574 				}
1575 				gdk_x11_display_error_trap_pop_ignored (gdkdisplay);
1576 			}
1577 			if (data_esetroot != NULL) {
1578 				XFree (data_esetroot);
1579 			}
1580 		}
1581 		if (data_root != NULL) {
1582 			XFree (data_root);
1583 		}
1584 	}
1585 
1586 	/* Get atoms for both properties in an array, create them if needed.
1587 	 * This method is to avoid multiple round-trips to Xserver
1588 	 */
1589 	if (!XInternAtoms (display, atom_names, G_N_ELEMENTS(atom_names), False, atoms) ||
1590 	    atoms[0] == None || atoms[1] == None) {
1591 	    g_warning ("Could not create atoms needed to set root pixmap id/properties.\n");
1592 	    return;
1593 	}
1594 
1595 	/* Set new _XROOTMAP_ID and ESETROOT_PMAP_ID properties */
1596 	XChangeProperty (display, xroot, atoms[0], XA_PIXMAP, 32,
1597 			 PropModeReplace, (unsigned char *) &xpixmap, 1);
1598 
1599 	XChangeProperty (display, xroot, atoms[1], XA_PIXMAP, 32,
1600 			 PropModeReplace, (unsigned char *) &xpixmap, 1);
1601 }
1602 
1603 /**
1604  * mate_bg_set_surface_as_root:
1605  * @screen: the #GdkScreen to change root background on
1606  * @surface: the #cairo_surface_t to set root background from.
1607  *   Must be an xlib surface backing a pixmap.
1608  *
1609  * Set the root pixmap, and properties pointing to it. We
1610  * do this atomically with a server grab to make sure that
1611  * we won't leak the pixmap if somebody else it setting
1612  * it at the same time. (This assumes that they follow the
1613  * same conventions we do).  @surface should come from a call
1614  * to mate_bg_create_surface().
1615  **/
1616 void
mate_bg_set_surface_as_root(GdkScreen * screen,cairo_surface_t * surface)1617 mate_bg_set_surface_as_root (GdkScreen *screen, cairo_surface_t *surface)
1618 {
1619 	g_return_if_fail (screen != NULL);
1620 	g_return_if_fail (cairo_surface_get_type (surface) == CAIRO_SURFACE_TYPE_XLIB);
1621 
1622 	/* Desktop background pixmap should be created from dummy X client since most
1623 	 * applications will try to kill it with XKillClient later when changing pixmap
1624 	 */
1625 	Display    *display      = GDK_DISPLAY_XDISPLAY (gdk_screen_get_display (screen));
1626 	Pixmap      pixmap_id    = cairo_xlib_surface_get_drawable (surface);
1627 	Window      xroot        = RootWindow (display, gdk_x11_screen_get_screen_number (screen));
1628 
1629 	XGrabServer (display);
1630 	mate_bg_set_root_pixmap_id (screen, display, pixmap_id);
1631 
1632 	XSetWindowBackgroundPixmap (display, xroot, pixmap_id);
1633 	XClearWindow (display, xroot);
1634 
1635 	XFlush (display);
1636 	XUngrabServer (display);
1637 }
1638 
1639 /**
1640  * mate_bg_set_surface_as_root_with_crossfade:
1641  * @screen: the #GdkScreen to change root background on
1642  * @surface: the cairo xlib surface to set root background from
1643  *
1644  * Set the root pixmap, and properties pointing to it.
1645  * This function differs from mate_bg_set_surface_as_root()
1646  * in that it adds a subtle crossfade animation from the
1647  * current root pixmap to the new one.
1648  *
1649  * Return value: (transfer full): a #MateBGCrossfade object
1650  **/
1651 MateBGCrossfade *
mate_bg_set_surface_as_root_with_crossfade(GdkScreen * screen,cairo_surface_t * surface)1652 mate_bg_set_surface_as_root_with_crossfade (GdkScreen       *screen,
1653 		 			    cairo_surface_t *surface)
1654 {
1655 	GdkWindow       *root_window;
1656 	int              width, height;
1657 	MateBGCrossfade *fade;
1658 	cairo_t         *cr;
1659 	cairo_surface_t *old_surface;
1660 
1661 	g_return_val_if_fail (screen != NULL, NULL);
1662 	g_return_val_if_fail (surface != NULL, NULL);
1663 
1664 	root_window = gdk_screen_get_root_window (screen);
1665 	width       = gdk_window_get_width (root_window);
1666 	height      = gdk_window_get_height (root_window);
1667 	fade        = mate_bg_crossfade_new (width, height);
1668 	old_surface = mate_bg_get_surface_from_root (screen);
1669 
1670 	mate_bg_crossfade_set_start_surface (fade, old_surface);
1671 	mate_bg_crossfade_set_end_surface (fade, surface);
1672 
1673 	/* Before setting the surface as a root pixmap, let's have it draw
1674 	 * the old stuff, just so it won't be noticable
1675 	 * (crossfade will later get it back)
1676 	 */
1677 	cr = cairo_create (surface);
1678 	cairo_set_source_surface (cr, old_surface, 0, 0);
1679 	cairo_pattern_set_extend (cairo_get_source (cr), CAIRO_EXTEND_REPEAT);
1680 	cairo_paint (cr);
1681 	cairo_destroy (cr);
1682 	cairo_surface_destroy (old_surface);
1683 
1684 	mate_bg_set_surface_as_root (screen, surface);
1685 	mate_bg_crossfade_start (fade, root_window);
1686 
1687 	return fade;
1688 }
1689 
1690 /* Implementation of the pixbuf cache */
1691 struct _SlideShow
1692 {
1693 	gint ref_count;
1694 	double start_time;
1695 	double total_duration;
1696 
1697 	GQueue *slides;
1698 
1699 	gboolean has_multiple_sizes;
1700 
1701 	/* used during parsing */
1702 	struct tm start_tm;
1703 	GQueue *stack;
1704 };
1705 
1706 
1707 #if GLIB_CHECK_VERSION(2,61,2)
1708 static double
now(void)1709 now (void)
1710 {
1711 	const double microseconds_per_second = (double) G_USEC_PER_SEC;
1712 	gint64 tv;
1713 
1714 	tv = g_get_real_time ();
1715 
1716 	return (double) (tv / microseconds_per_second);
1717 }
1718 #else
1719 static double
now(void)1720 now (void)
1721 {
1722 	const double microseconds_per_second = (double) G_USEC_PER_SEC;
1723 	GTimeVal tv;
1724 
1725 	g_get_current_time (&tv);
1726 
1727 	return (double)tv.tv_sec + (tv.tv_usec / microseconds_per_second);
1728 }
1729 #endif
1730 
1731 static Slide *
get_current_slide(SlideShow * show,double * alpha)1732 get_current_slide (SlideShow *show,
1733 		   double    *alpha)
1734 {
1735 	double delta = fmod (now() - show->start_time, show->total_duration);
1736 	GList *list;
1737 	double elapsed;
1738 	int i;
1739 
1740 	if (delta < 0)
1741 		delta += show->total_duration;
1742 
1743 	elapsed = 0;
1744 	i = 0;
1745 	for (list = show->slides->head; list != NULL; list = list->next) {
1746 		Slide *slide = list->data;
1747 
1748 		if (elapsed + slide->duration > delta) {
1749 			if (alpha)
1750 				*alpha = (delta - elapsed) / (double)slide->duration;
1751 			return slide;
1752 		}
1753 
1754 		i++;
1755 		elapsed += slide->duration;
1756 	}
1757 
1758 	/* this should never happen since we have slides and we should always
1759 	 * find a current slide for the elapsed time since beginning -- we're
1760 	 * looping with fmod() */
1761 	g_assert_not_reached ();
1762 
1763 	return NULL;
1764 }
1765 
1766 static GdkPixbuf *
blend(GdkPixbuf * p1,GdkPixbuf * p2,double alpha)1767 blend (GdkPixbuf *p1,
1768        GdkPixbuf *p2,
1769        double alpha)
1770 {
1771 	GdkPixbuf *result = gdk_pixbuf_copy (p1);
1772 	GdkPixbuf *tmp;
1773 
1774 	if (gdk_pixbuf_get_width (p2) != gdk_pixbuf_get_width (p1) ||
1775             gdk_pixbuf_get_height (p2) != gdk_pixbuf_get_height (p1)) {
1776 		tmp = gdk_pixbuf_scale_simple (p2,
1777 					       gdk_pixbuf_get_width (p1),
1778 					       gdk_pixbuf_get_height (p1),
1779 					       GDK_INTERP_BILINEAR);
1780 	}
1781         else {
1782 		tmp = g_object_ref (p2);
1783 	}
1784 
1785 	pixbuf_blend (tmp, result, 0, 0, -1, -1, 0, 0, alpha);
1786 
1787         g_object_unref (tmp);
1788 
1789 	return result;
1790 }
1791 
1792 typedef	enum {
1793 	PIXBUF,
1794 	SLIDESHOW,
1795 	THUMBNAIL
1796 } FileType;
1797 
1798 struct FileCacheEntry
1799 {
1800 	FileType type;
1801 	char *filename;
1802 	union {
1803 		GdkPixbuf *pixbuf;
1804 		SlideShow *slideshow;
1805 		GdkPixbuf *thumbnail;
1806 	} u;
1807 };
1808 
1809 static void
file_cache_entry_delete(FileCacheEntry * ent)1810 file_cache_entry_delete (FileCacheEntry *ent)
1811 {
1812 	g_free (ent->filename);
1813 
1814 	switch (ent->type) {
1815 	case PIXBUF:
1816 		g_object_unref (ent->u.pixbuf);
1817 		break;
1818 	case SLIDESHOW:
1819 		slideshow_unref (ent->u.slideshow);
1820 		break;
1821 	case THUMBNAIL:
1822 		g_object_unref (ent->u.thumbnail);
1823 		break;
1824 	}
1825 
1826 	g_free (ent);
1827 }
1828 
1829 static void
bound_cache(MateBG * bg)1830 bound_cache (MateBG *bg)
1831 {
1832       while (g_list_length (bg->file_cache) >= CACHE_SIZE) {
1833 	      GList *last_link = g_list_last (bg->file_cache);
1834 	      FileCacheEntry *ent = last_link->data;
1835 
1836 	      file_cache_entry_delete (ent);
1837 
1838 	      bg->file_cache = g_list_delete_link (bg->file_cache, last_link);
1839       }
1840 }
1841 
1842 static const FileCacheEntry *
file_cache_lookup(MateBG * bg,FileType type,const char * filename)1843 file_cache_lookup (MateBG *bg, FileType type, const char *filename)
1844 {
1845 	GList *list;
1846 
1847 	for (list = bg->file_cache; list != NULL; list = list->next) {
1848 		FileCacheEntry *ent = list->data;
1849 
1850 		if (ent && ent->type == type &&
1851 		    strcmp (ent->filename, filename) == 0) {
1852 			return ent;
1853 		}
1854 	}
1855 
1856 	return NULL;
1857 }
1858 
1859 static FileCacheEntry *
file_cache_entry_new(MateBG * bg,FileType type,const char * filename)1860 file_cache_entry_new (MateBG *bg,
1861 		      FileType type,
1862 		      const char *filename)
1863 {
1864 	FileCacheEntry *ent = g_new0 (FileCacheEntry, 1);
1865 
1866 	g_assert (!file_cache_lookup (bg, type, filename));
1867 
1868 	ent->type = type;
1869 	ent->filename = g_strdup (filename);
1870 
1871 	bg->file_cache = g_list_prepend (bg->file_cache, ent);
1872 
1873 	bound_cache (bg);
1874 
1875 	return ent;
1876 }
1877 
1878 static void
file_cache_add_pixbuf(MateBG * bg,const char * filename,GdkPixbuf * pixbuf)1879 file_cache_add_pixbuf (MateBG *bg,
1880 		       const char *filename,
1881 		       GdkPixbuf *pixbuf)
1882 {
1883 	FileCacheEntry *ent = file_cache_entry_new (bg, PIXBUF, filename);
1884 	ent->u.pixbuf = g_object_ref (pixbuf);
1885 }
1886 
1887 static void
file_cache_add_thumbnail(MateBG * bg,const char * filename,GdkPixbuf * pixbuf)1888 file_cache_add_thumbnail (MateBG *bg,
1889 			  const char *filename,
1890 			  GdkPixbuf *pixbuf)
1891 {
1892 	FileCacheEntry *ent = file_cache_entry_new (bg, THUMBNAIL, filename);
1893 	ent->u.thumbnail = g_object_ref (pixbuf);
1894 }
1895 
1896 static void
file_cache_add_slide_show(MateBG * bg,const char * filename,SlideShow * show)1897 file_cache_add_slide_show (MateBG *bg,
1898 			   const char *filename,
1899 			   SlideShow *show)
1900 {
1901 	FileCacheEntry *ent = file_cache_entry_new (bg, SLIDESHOW, filename);
1902 	ent->u.slideshow = slideshow_ref (show);
1903 }
1904 
1905 static GdkPixbuf *
load_from_cache_file(MateBG * bg,const char * filename,gint num_monitor,gint best_width,gint best_height)1906 load_from_cache_file (MateBG     *bg,
1907 		      const char *filename,
1908 		      gint        num_monitor,
1909 		      gint        best_width,
1910 		      gint        best_height)
1911 {
1912 	GdkPixbuf *pixbuf = NULL;
1913 	gchar *cache_filename;
1914 
1915 	cache_filename = get_wallpaper_cache_filename (filename, num_monitor, bg->placement,
1916 							best_width, best_height);
1917 
1918 	if (cache_file_is_valid (filename, cache_filename))
1919 		pixbuf = gdk_pixbuf_new_from_file (cache_filename, NULL);
1920 
1921 	g_free (cache_filename);
1922 
1923 	return pixbuf;
1924 }
1925 
1926 static GdkPixbuf *
get_as_pixbuf_for_size(MateBG * bg,const char * filename,gint monitor,gint best_width,gint best_height)1927 get_as_pixbuf_for_size (MateBG    *bg,
1928 			const char *filename,
1929 			gint         monitor,
1930 			gint         best_width,
1931 			gint         best_height)
1932 {
1933 	const FileCacheEntry *ent;
1934 	if ((ent = file_cache_lookup (bg, PIXBUF, filename))) {
1935 		return g_object_ref (ent->u.pixbuf);
1936 	} else {
1937 		GdkPixbufFormat *format;
1938 		GdkPixbuf *pixbuf = NULL;
1939 		gchar *tmp = NULL;
1940 		GdkPixbuf *tmp_pixbuf;
1941 
1942 		/* Try to hit local cache first if relevant */
1943 		if (monitor != -1)
1944 			pixbuf = load_from_cache_file (bg, filename, monitor,
1945 							best_width, best_height);
1946 
1947 		if (!pixbuf) {
1948 			/* If scalable choose maximum size */
1949 			format = gdk_pixbuf_get_file_info (filename, NULL, NULL);
1950 			if (format != NULL)
1951 				tmp = gdk_pixbuf_format_get_name (format);
1952 
1953 			if (g_strcmp0 (tmp, "svg") == 0 &&
1954 			    (best_width > 0 && best_height > 0) &&
1955 			    (bg->placement == MATE_BG_PLACEMENT_FILL_SCREEN ||
1956 			     bg->placement == MATE_BG_PLACEMENT_SCALED ||
1957 			     bg->placement == MATE_BG_PLACEMENT_ZOOMED))
1958 			{
1959 				pixbuf = gdk_pixbuf_new_from_file_at_size (filename,
1960 									   best_width,
1961 									   best_height, NULL);
1962 			} else {
1963 				pixbuf = gdk_pixbuf_new_from_file (filename, NULL);
1964 			}
1965 
1966 			if (tmp != NULL)
1967 				g_free (tmp);
1968 		}
1969 
1970 		if (pixbuf) {
1971 			tmp_pixbuf = gdk_pixbuf_apply_embedded_orientation (pixbuf);
1972 			g_object_unref (pixbuf);
1973 			pixbuf = tmp_pixbuf;
1974 			file_cache_add_pixbuf (bg, filename, pixbuf);
1975 		}
1976 
1977 		return pixbuf;
1978 	}
1979 }
1980 
1981 static SlideShow *
get_as_slideshow(MateBG * bg,const char * filename)1982 get_as_slideshow (MateBG *bg, const char *filename)
1983 {
1984 	const FileCacheEntry *ent;
1985 	if ((ent = file_cache_lookup (bg, SLIDESHOW, filename))) {
1986 		return slideshow_ref (ent->u.slideshow);
1987 	}
1988 	else {
1989 		SlideShow *show = read_slideshow_file (filename, NULL);
1990 
1991 		if (show)
1992 			file_cache_add_slide_show (bg, filename, show);
1993 
1994 		return show;
1995 	}
1996 }
1997 
1998 static GdkPixbuf *
get_as_thumbnail(MateBG * bg,MateDesktopThumbnailFactory * factory,const char * filename)1999 get_as_thumbnail (MateBG *bg, MateDesktopThumbnailFactory *factory, const char *filename)
2000 {
2001 	const FileCacheEntry *ent;
2002 	if ((ent = file_cache_lookup (bg, THUMBNAIL, filename))) {
2003 		return g_object_ref (ent->u.thumbnail);
2004 	}
2005 	else {
2006 		GdkPixbuf *thumb = create_thumbnail_for_filename (factory, filename);
2007 
2008 		if (thumb)
2009 			file_cache_add_thumbnail (bg, filename, thumb);
2010 
2011 		return thumb;
2012 	}
2013 }
2014 
2015 static gboolean
blow_expensive_caches(gpointer data)2016 blow_expensive_caches (gpointer data)
2017 {
2018 	MateBG *bg = data;
2019 	GList *list;
2020 
2021 	bg->blow_caches_id = 0;
2022 
2023 	if (bg->file_cache) {
2024 		for (list = bg->file_cache; list != NULL; list = list->next) {
2025 			FileCacheEntry *ent = list->data;
2026 
2027 			if (ent->type == PIXBUF) {
2028 				file_cache_entry_delete (ent);
2029 				bg->file_cache = g_list_delete_link (bg->file_cache,
2030 								     list);
2031 			}
2032 		}
2033 	}
2034 
2035 	if (bg->pixbuf_cache) {
2036 		g_object_unref (bg->pixbuf_cache);
2037 		bg->pixbuf_cache = NULL;
2038 	}
2039 
2040 	return FALSE;
2041 }
2042 
2043 static void
blow_expensive_caches_in_idle(MateBG * bg)2044 blow_expensive_caches_in_idle (MateBG *bg)
2045 {
2046 	if (bg->blow_caches_id == 0) {
2047 		bg->blow_caches_id =
2048 			g_idle_add (blow_expensive_caches,
2049 				    bg);
2050 	}
2051 }
2052 
2053 
2054 static gboolean
on_timeout(gpointer data)2055 on_timeout (gpointer data)
2056 {
2057 	MateBG *bg = data;
2058 
2059 	bg->timeout_id = 0;
2060 
2061 	queue_transitioned (bg);
2062 
2063 	return FALSE;
2064 }
2065 
2066 static double
get_slide_timeout(Slide * slide)2067 get_slide_timeout (Slide   *slide)
2068 {
2069 	double timeout;
2070 	if (slide->fixed) {
2071 		timeout = slide->duration;
2072 	} else {
2073 		/* Maybe the number of steps should be configurable? */
2074 
2075 		/* In the worst case we will do a fade from 0 to 256, which mean
2076 		 * we will never use more than 255 steps, however in most cases
2077 		 * the first and last value are similar and users can't percieve
2078 		 * changes in pixel values as small as 1/255th. So, lets not waste
2079 		 * CPU cycles on transitioning to often.
2080 		 *
2081 		 * 64 steps is enough for each step to be just detectable in a 16bit
2082 		 * color mode in the worst case, so we'll use this as an approximation
2083 		 * of whats detectable.
2084 		 */
2085 		timeout = slide->duration / 64.0;
2086 	}
2087 	return timeout;
2088 }
2089 
2090 static void
ensure_timeout(MateBG * bg,Slide * slide)2091 ensure_timeout (MateBG *bg,
2092 		Slide   *slide)
2093 {
2094 	if (!bg->timeout_id) {
2095 		double timeout = get_slide_timeout (slide);
2096 
2097 		/* G_MAXUINT means "only one slide" */
2098 		if (timeout < G_MAXUINT) {
2099 			bg->timeout_id = g_timeout_add_full (
2100 				G_PRIORITY_LOW,
2101 				timeout * 1000, on_timeout, bg, NULL);
2102 		}
2103 
2104 	}
2105 }
2106 
2107 static time_t
get_mtime(const char * filename)2108 get_mtime (const char *filename)
2109 {
2110 	GFile     *file;
2111 	GFileInfo *info;
2112 	time_t     mtime;
2113 
2114 	mtime = (time_t)-1;
2115 
2116 	if (filename) {
2117 		file = g_file_new_for_path (filename);
2118 		info = g_file_query_info (file, G_FILE_ATTRIBUTE_TIME_MODIFIED,
2119 					  G_FILE_QUERY_INFO_NONE, NULL, NULL);
2120 		if (info) {
2121 			mtime = g_file_info_get_attribute_uint64 (info,
2122 								  G_FILE_ATTRIBUTE_TIME_MODIFIED);
2123 			g_object_unref (info);
2124 		}
2125 		g_object_unref (file);
2126 	}
2127 
2128 	return mtime;
2129 }
2130 
2131 static GdkPixbuf *
scale_thumbnail(MateBGPlacement placement,const char * filename,GdkPixbuf * thumb,GdkScreen * screen,int dest_width,int dest_height)2132 scale_thumbnail (MateBGPlacement placement,
2133 		 const char *filename,
2134 		 GdkPixbuf *thumb,
2135 		 GdkScreen *screen,
2136 		 int	    dest_width,
2137 		 int	    dest_height)
2138 {
2139 	int o_width;
2140 	int o_height;
2141 
2142 	if (placement != MATE_BG_PLACEMENT_TILED &&
2143 	    placement != MATE_BG_PLACEMENT_CENTERED) {
2144 
2145 		/* In this case, the pixbuf will be scaled to fit the screen anyway,
2146 		 * so just return the pixbuf here
2147 		 */
2148 		return g_object_ref (thumb);
2149 	}
2150 
2151 	if (get_thumb_annotations (thumb, &o_width, &o_height)		||
2152 	    (filename && get_original_size (filename, &o_width, &o_height))) {
2153 
2154 		int scr_height = HeightOfScreen (gdk_x11_screen_get_xscreen (screen));
2155 		int scr_width = WidthOfScreen (gdk_x11_screen_get_xscreen (screen));
2156 		int thumb_width = gdk_pixbuf_get_width (thumb);
2157 		int thumb_height = gdk_pixbuf_get_height (thumb);
2158 		double screen_to_dest = fit_factor (scr_width, scr_height,
2159 						    dest_width, dest_height);
2160 		double thumb_to_orig  = fit_factor (thumb_width, thumb_height,
2161 						    o_width, o_height);
2162 		double f = thumb_to_orig * screen_to_dest;
2163 		int new_width, new_height;
2164 
2165 		new_width = floor (thumb_width * f + 0.5);
2166 		new_height = floor (thumb_height * f + 0.5);
2167 
2168 		if (placement == MATE_BG_PLACEMENT_TILED) {
2169 			/* Heuristic to make sure tiles don't become so small that
2170 			 * they turn into a blur.
2171 			 *
2172 			 * This is strictly speaking incorrect, but the resulting
2173 			 * thumbnail gives a much better idea what the background
2174 			 * will actually look like.
2175 			 */
2176 
2177 			if ((new_width < 32 || new_height < 32) &&
2178 			    (new_width < o_width / 4 || new_height < o_height / 4)) {
2179 				new_width = o_width / 4;
2180 				new_height = o_height / 4;
2181 			}
2182 		}
2183 
2184 		thumb = gdk_pixbuf_scale_simple (thumb, new_width, new_height,
2185 						 GDK_INTERP_BILINEAR);
2186 	}
2187 	else
2188 		g_object_ref (thumb);
2189 
2190 	return thumb;
2191 }
2192 
2193 /* frame_num determines which slide to thumbnail.
2194  * -1 means 'current slide'.
2195  */
2196 static GdkPixbuf *
create_img_thumbnail(MateBG * bg,MateDesktopThumbnailFactory * factory,GdkScreen * screen,int dest_width,int dest_height,int frame_num)2197 create_img_thumbnail (MateBG                      *bg,
2198 		      MateDesktopThumbnailFactory *factory,
2199 		      GdkScreen                    *screen,
2200 		      int                           dest_width,
2201 		      int                           dest_height,
2202 		      int                           frame_num)
2203 {
2204 	if (bg->filename) {
2205 		GdkPixbuf *thumb;
2206 
2207 		thumb = get_as_thumbnail (bg, factory, bg->filename);
2208 
2209 		if (thumb) {
2210 			GdkPixbuf *result;
2211 			result = scale_thumbnail (bg->placement,
2212 						  bg->filename,
2213 						  thumb,
2214 						  screen,
2215 						  dest_width,
2216 						  dest_height);
2217 			g_object_unref (thumb);
2218 			return result;
2219 		}
2220 		else {
2221 			SlideShow *show = get_as_slideshow (bg, bg->filename);
2222 
2223 			if (show) {
2224 				double alpha;
2225 				Slide *slide;
2226 
2227 				if (frame_num == -1)
2228 					slide = get_current_slide (show, &alpha);
2229 				else
2230 					slide = g_queue_peek_nth (show->slides, frame_num);
2231 
2232 				if (slide->fixed) {
2233 					GdkPixbuf *tmp;
2234 					FileSize *fs;
2235 					fs = find_best_size (slide->file1, dest_width, dest_height);
2236 					tmp = get_as_thumbnail (bg, factory, fs->file);
2237 					if (tmp) {
2238 						thumb = scale_thumbnail (bg->placement,
2239 									 fs->file,
2240 									 tmp,
2241 									 screen,
2242 									 dest_width,
2243 									 dest_height);
2244 						g_object_unref (tmp);
2245 					}
2246 				}
2247 				else {
2248 					FileSize *fs1, *fs2;
2249 					GdkPixbuf *p1, *p2;
2250 					fs1 = find_best_size (slide->file1, dest_width, dest_height);
2251 					p1 = get_as_thumbnail (bg, factory, fs1->file);
2252 
2253 					fs2 = find_best_size (slide->file2, dest_width, dest_height);
2254 					p2 = get_as_thumbnail (bg, factory, fs2->file);
2255 
2256 					if (p1 && p2) {
2257 						GdkPixbuf *thumb1, *thumb2;
2258 
2259 						thumb1 = scale_thumbnail (bg->placement,
2260 									  fs1->file,
2261 									  p1,
2262 									  screen,
2263 									  dest_width,
2264 									  dest_height);
2265 
2266 						thumb2 = scale_thumbnail (bg->placement,
2267 									  fs2->file,
2268 									  p2,
2269 									  screen,
2270 									  dest_width,
2271 									  dest_height);
2272 
2273 						thumb = blend (thumb1, thumb2, alpha);
2274 
2275 						g_object_unref (thumb1);
2276 						g_object_unref (thumb2);
2277 					}
2278 					if (p1)
2279 						g_object_unref (p1);
2280 					if (p2)
2281 						g_object_unref (p2);
2282 				}
2283 
2284 				ensure_timeout (bg, slide);
2285 
2286 				slideshow_unref (show);
2287 			}
2288 		}
2289 
2290 		return thumb;
2291 	}
2292 
2293 	return NULL;
2294 }
2295 
2296 /*
2297  * Find the FileSize that best matches the given size.
2298  * Do two passes; the first pass only considers FileSizes
2299  * that are larger than the given size.
2300  * We are looking for the image that best matches the aspect ratio.
2301  * When two images have the same aspect ratio, prefer the one whose
2302  * width is closer to the given width.
2303  */
2304 static FileSize *
find_best_size(GSList * sizes,gint width,gint height)2305 find_best_size (GSList *sizes, gint width, gint height)
2306 {
2307 	GSList *s;
2308 	gdouble a, d, distance;
2309 	FileSize *best = NULL;
2310 	gint pass;
2311 
2312 	a = width/(gdouble)height;
2313 	distance = 10000.0;
2314 
2315 	for (pass = 0; pass < 2; pass++) {
2316 		for (s = sizes; s; s = s->next) {
2317 			FileSize *size = s->data;
2318 
2319 			if (pass == 0 && (size->width < width || size->height < height))
2320 				continue;
2321 
2322 			d = fabs (a - size->width/(gdouble)size->height);
2323 			if (d < distance) {
2324 				distance = d;
2325 				best = size;
2326 			}
2327 			else if (d == distance) {
2328 				if (abs (size->width - width) < abs (best->width - width)) {
2329 					best = size;
2330 				}
2331 			}
2332 		}
2333 
2334 		if (best)
2335 			break;
2336 	}
2337 
2338 	return best;
2339 }
2340 
2341 static GdkPixbuf *
get_pixbuf_for_size(MateBG * bg,gint monitor,gint best_width,gint best_height)2342 get_pixbuf_for_size (MateBG *bg,
2343 		     gint monitor,
2344 		     gint best_width,
2345 		     gint best_height)
2346 {
2347 	guint time_until_next_change;
2348 	gboolean hit_cache = FALSE;
2349 
2350 	/* only hit the cache if the aspect ratio matches */
2351 	if (bg->pixbuf_cache) {
2352 		int width, height;
2353 		width = gdk_pixbuf_get_width (bg->pixbuf_cache);
2354 		height = gdk_pixbuf_get_height (bg->pixbuf_cache);
2355 		hit_cache = 0.2 > fabs ((best_width / (double)best_height) - (width / (double)height));
2356 		if (!hit_cache) {
2357 			g_object_unref (bg->pixbuf_cache);
2358 			bg->pixbuf_cache = NULL;
2359 		}
2360 	}
2361 
2362 	if (!hit_cache && bg->filename) {
2363 		bg->file_mtime = get_mtime (bg->filename);
2364 
2365 		bg->pixbuf_cache = get_as_pixbuf_for_size (bg, bg->filename, monitor,
2366 							   best_width, best_height);
2367 		time_until_next_change = G_MAXUINT;
2368 		if (!bg->pixbuf_cache) {
2369 			SlideShow *show = get_as_slideshow (bg, bg->filename);
2370 
2371 			if (show) {
2372 				double alpha;
2373 				double timeout;
2374 				Slide *slide;
2375 
2376 				slideshow_ref (show);
2377 
2378 				slide = get_current_slide (show, &alpha);
2379 				timeout = get_slide_timeout (slide);
2380 				time_until_next_change = (guint) timeout;
2381 				if (slide->fixed) {
2382 					FileSize *size = find_best_size (slide->file1,
2383 									 best_width, best_height);
2384 					bg->pixbuf_cache =
2385 						get_as_pixbuf_for_size (bg, size->file, monitor,
2386 									best_width, best_height);
2387 				} else {
2388 					FileSize *size;
2389 					GdkPixbuf *p1, *p2;
2390 
2391 					size = find_best_size (slide->file1,
2392 								best_width, best_height);
2393 					p1 = get_as_pixbuf_for_size (bg, size->file, monitor,
2394 								     best_width, best_height);
2395 
2396 					size = find_best_size (slide->file2,
2397 								best_width, best_height);
2398 					p2 = get_as_pixbuf_for_size (bg, size->file, monitor,
2399 								     best_width, best_height);
2400 
2401 					if (p1 && p2)
2402 						bg->pixbuf_cache = blend (p1, p2, alpha);
2403 					if (p1)
2404 						g_object_unref (p1);
2405 					if (p2)
2406 						g_object_unref (p2);
2407 				}
2408 
2409 				ensure_timeout (bg, slide);
2410 
2411 				slideshow_unref (show);
2412 			}
2413 		}
2414 
2415 		/* If the next slideshow step is a long time away then
2416 		   we blow away the expensive stuff (large pixbufs) from
2417 		   the cache */
2418 		if (time_until_next_change > KEEP_EXPENSIVE_CACHE_SECS)
2419 		    blow_expensive_caches_in_idle (bg);
2420 	}
2421 
2422 	if (bg->pixbuf_cache)
2423 		g_object_ref (bg->pixbuf_cache);
2424 
2425 	return bg->pixbuf_cache;
2426 }
2427 
2428 static gboolean
is_different(MateBG * bg,const char * filename)2429 is_different (MateBG    *bg,
2430 	      const char *filename)
2431 {
2432 	if (!filename && bg->filename) {
2433 		return TRUE;
2434 	}
2435 	else if (filename && !bg->filename) {
2436 		return TRUE;
2437 	}
2438 	else if (!filename && !bg->filename) {
2439 		return FALSE;
2440 	}
2441 	else {
2442 		time_t mtime = get_mtime (filename);
2443 
2444 		if (mtime != bg->file_mtime)
2445 			return TRUE;
2446 
2447 		if (strcmp (filename, bg->filename) != 0)
2448 			return TRUE;
2449 
2450 		return FALSE;
2451 	}
2452 }
2453 
2454 static void
clear_cache(MateBG * bg)2455 clear_cache (MateBG *bg)
2456 {
2457 	GList *list;
2458 
2459 	if (bg->file_cache) {
2460 		for (list = bg->file_cache; list != NULL; list = list->next) {
2461 			FileCacheEntry *ent = list->data;
2462 
2463 			file_cache_entry_delete (ent);
2464 		}
2465 		g_list_free (bg->file_cache);
2466 		bg->file_cache = NULL;
2467 	}
2468 
2469 	if (bg->pixbuf_cache) {
2470 		g_object_unref (bg->pixbuf_cache);
2471 
2472 		bg->pixbuf_cache = NULL;
2473 	}
2474 
2475 	if (bg->timeout_id) {
2476 		g_source_remove (bg->timeout_id);
2477 
2478 		bg->timeout_id = 0;
2479 	}
2480 }
2481 
2482 /* Pixbuf utilities */
2483 static void
pixbuf_average_value(GdkPixbuf * pixbuf,GdkRGBA * result)2484 pixbuf_average_value (GdkPixbuf *pixbuf,
2485                       GdkRGBA   *result)
2486 {
2487 	guint64 a_total, r_total, g_total, b_total;
2488 	guint row, column;
2489 	int row_stride;
2490 	const guchar *pixels, *p;
2491 	int r, g, b, a;
2492 	guint64 dividend;
2493 	guint width, height;
2494 	gdouble dd;
2495 
2496 	width = gdk_pixbuf_get_width (pixbuf);
2497 	height = gdk_pixbuf_get_height (pixbuf);
2498 	row_stride = gdk_pixbuf_get_rowstride (pixbuf);
2499 	pixels = gdk_pixbuf_get_pixels (pixbuf);
2500 
2501 	/* iterate through the pixbuf, counting up each component */
2502 	a_total = 0;
2503 	r_total = 0;
2504 	g_total = 0;
2505 	b_total = 0;
2506 
2507 	if (gdk_pixbuf_get_has_alpha (pixbuf)) {
2508 		for (row = 0; row < height; row++) {
2509 			p = pixels + (row * row_stride);
2510 			for (column = 0; column < width; column++) {
2511 				r = *p++;
2512 				g = *p++;
2513 				b = *p++;
2514 				a = *p++;
2515 
2516 				a_total += a;
2517 				r_total += r * a;
2518 				g_total += g * a;
2519 				b_total += b * a;
2520 			}
2521 		}
2522 		dividend = height * width * 0xFF;
2523 		a_total *= 0xFF;
2524 	} else {
2525 		for (row = 0; row < height; row++) {
2526 			p = pixels + (row * row_stride);
2527 			for (column = 0; column < width; column++) {
2528 				r = *p++;
2529 				g = *p++;
2530 				b = *p++;
2531 
2532 				r_total += r;
2533 				g_total += g;
2534 				b_total += b;
2535 			}
2536 		}
2537 		dividend = height * width;
2538 		a_total = dividend * 0xFF;
2539 	}
2540 
2541 	dd = dividend * 0xFF;
2542 	result->alpha = a_total / dd;
2543 	result->red = r_total / dd;
2544 	result->green = g_total / dd;
2545 	result->blue = b_total / dd;
2546 }
2547 
2548 static GdkPixbuf *
pixbuf_scale_to_fit(GdkPixbuf * src,int max_width,int max_height)2549 pixbuf_scale_to_fit (GdkPixbuf *src, int max_width, int max_height)
2550 {
2551 	double factor;
2552 	int src_width, src_height;
2553 	int new_width, new_height;
2554 
2555 	src_width = gdk_pixbuf_get_width (src);
2556 	src_height = gdk_pixbuf_get_height (src);
2557 
2558 	factor = MIN (max_width  / (double) src_width, max_height / (double) src_height);
2559 
2560 	new_width  = floor (src_width * factor + 0.5);
2561 	new_height = floor (src_height * factor + 0.5);
2562 
2563 	return gdk_pixbuf_scale_simple (src, new_width, new_height, GDK_INTERP_BILINEAR);
2564 }
2565 
2566 static GdkPixbuf *
pixbuf_scale_to_min(GdkPixbuf * src,int min_width,int min_height)2567 pixbuf_scale_to_min (GdkPixbuf *src, int min_width, int min_height)
2568 {
2569 	double factor;
2570 	int src_width, src_height;
2571 	int new_width, new_height;
2572 	GdkPixbuf *dest;
2573 
2574 	src_width = gdk_pixbuf_get_width (src);
2575 	src_height = gdk_pixbuf_get_height (src);
2576 
2577 	factor = MAX (min_width / (double) src_width, min_height / (double) src_height);
2578 
2579 	new_width = floor (src_width * factor + 0.5);
2580 	new_height = floor (src_height * factor + 0.5);
2581 
2582 	dest = gdk_pixbuf_new (GDK_COLORSPACE_RGB,
2583 			       gdk_pixbuf_get_has_alpha (src),
2584 			       8, min_width, min_height);
2585 	if (!dest)
2586 		return NULL;
2587 
2588 	/* crop the result */
2589 	gdk_pixbuf_scale (src, dest,
2590 			  0, 0,
2591 			  min_width, min_height,
2592 			  (new_width - min_width) / -2,
2593 			  (new_height - min_height) / -2,
2594 			  factor,
2595 			  factor,
2596 			  GDK_INTERP_BILINEAR);
2597 	return dest;
2598 }
2599 
2600 static guchar *
create_gradient(const GdkRGBA * primary,const GdkRGBA * secondary,int n_pixels)2601 create_gradient (const GdkRGBA *primary,
2602 		 const GdkRGBA *secondary,
2603 		 int	         n_pixels)
2604 {
2605 	guchar *result = g_malloc (n_pixels * 3);
2606 	int i;
2607 
2608 	for (i = 0; i < n_pixels; ++i) {
2609 		double ratio = (i + 0.5) / n_pixels;
2610 
2611 		result[3 * i + 0] = (guchar) ((primary->red * (1 - ratio) + secondary->red * ratio) * 0x100);
2612 		result[3 * i + 1] = (guchar) ((primary->green * (1 - ratio) + secondary->green * ratio) * 0x100);
2613 		result[3 * i + 2] = (guchar) ((primary->blue * (1 - ratio) + secondary->blue * ratio) * 0x100);
2614 	}
2615 
2616 	return result;
2617 }
2618 
2619 static void
pixbuf_draw_gradient(GdkPixbuf * pixbuf,gboolean horizontal,GdkRGBA * primary,GdkRGBA * secondary,GdkRectangle * rect)2620 pixbuf_draw_gradient (GdkPixbuf    *pixbuf,
2621 		      gboolean      horizontal,
2622 		      GdkRGBA      *primary,
2623 		      GdkRGBA      *secondary,
2624 		      GdkRectangle *rect)
2625 {
2626 	int width;
2627 	int height;
2628 	int rowstride;
2629 	guchar *dst;
2630 	int n_channels = 3;
2631 
2632 	rowstride = gdk_pixbuf_get_rowstride (pixbuf);
2633 	width = rect->width;
2634 	height = rect->height;
2635 	dst = gdk_pixbuf_get_pixels (pixbuf) + rect->x * n_channels + rowstride * rect->y;
2636 
2637 	if (horizontal) {
2638 		guchar *gradient = create_gradient (primary, secondary, width);
2639 		int copy_bytes_per_row = width * n_channels;
2640 		int i;
2641 
2642 		for (i = 0; i < height; i++) {
2643 			guchar *d;
2644 			d = dst + rowstride * i;
2645 			memcpy (d, gradient, copy_bytes_per_row);
2646 		}
2647 		g_free (gradient);
2648 	} else {
2649 		guchar *gb, *gradient;
2650 		int i;
2651 
2652 		gradient = create_gradient (primary, secondary, height);
2653 		for (i = 0; i < height; i++) {
2654 			int j;
2655 			guchar *d;
2656 
2657 			d = dst + rowstride * i;
2658 			gb = gradient + n_channels * i;
2659 			for (j = width; j > 0; j--) {
2660 				int k;
2661 
2662 				for (k = 0; k < n_channels; k++) {
2663 					*(d++) = gb[k];
2664 				}
2665 			}
2666 		}
2667 
2668 		g_free (gradient);
2669 	}
2670 }
2671 
2672 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)2673 pixbuf_blend (GdkPixbuf *src,
2674 	      GdkPixbuf *dest,
2675 	      int	 src_x,
2676 	      int	 src_y,
2677 	      int	 src_width,
2678 	      int        src_height,
2679 	      int	 dest_x,
2680 	      int	 dest_y,
2681 	      double	 alpha)
2682 {
2683 	int dest_width = gdk_pixbuf_get_width (dest);
2684 	int dest_height = gdk_pixbuf_get_height (dest);
2685 	int offset_x = dest_x - src_x;
2686 	int offset_y = dest_y - src_y;
2687 
2688 	if (src_width < 0)
2689 		src_width = gdk_pixbuf_get_width (src);
2690 
2691 	if (src_height < 0)
2692 		src_height = gdk_pixbuf_get_height (src);
2693 
2694 	if (dest_x < 0)
2695 		dest_x = 0;
2696 
2697 	if (dest_y < 0)
2698 		dest_y = 0;
2699 
2700 	if (dest_x + src_width > dest_width) {
2701 		src_width = dest_width - dest_x;
2702 	}
2703 
2704 	if (dest_y + src_height > dest_height) {
2705 		src_height = dest_height - dest_y;
2706 	}
2707 
2708 	gdk_pixbuf_composite (src, dest,
2709 			      dest_x, dest_y,
2710 			      src_width, src_height,
2711 			      offset_x, offset_y,
2712 			      1, 1, GDK_INTERP_NEAREST,
2713 			      alpha * 0xFF + 0.5);
2714 }
2715 
2716 static void
pixbuf_tile(GdkPixbuf * src,GdkPixbuf * dest)2717 pixbuf_tile (GdkPixbuf *src, GdkPixbuf *dest)
2718 {
2719 	int x, y;
2720 	int tile_width, tile_height;
2721 	int dest_width = gdk_pixbuf_get_width (dest);
2722 	int dest_height = gdk_pixbuf_get_height (dest);
2723 
2724 	tile_width = gdk_pixbuf_get_width (src);
2725 	tile_height = gdk_pixbuf_get_height (src);
2726 
2727 	for (y = 0; y < dest_height; y += tile_height) {
2728 		for (x = 0; x < dest_width; x += tile_width) {
2729 			pixbuf_blend (src, dest, 0, 0,
2730 				      tile_width, tile_height, x, y, 1.0);
2731 		}
2732 	}
2733 }
2734 
2735 static gboolean stack_is (SlideShow *parser, const char *s1, ...);
2736 
2737 /* Parser for fading background */
2738 static void
handle_start_element(GMarkupParseContext * context,const gchar * name,const gchar ** attr_names,const gchar ** attr_values,gpointer user_data,GError ** err)2739 handle_start_element (GMarkupParseContext *context,
2740 		      const gchar         *name,
2741 		      const gchar        **attr_names,
2742 		      const gchar        **attr_values,
2743 		      gpointer             user_data,
2744 		      GError             **err)
2745 {
2746 	SlideShow *parser = user_data;
2747 	gint i;
2748 
2749 	if (strcmp (name, "static") == 0 || strcmp (name, "transition") == 0) {
2750 		Slide *slide = g_new0 (Slide, 1);
2751 
2752 		if (strcmp (name, "static") == 0)
2753 			slide->fixed = TRUE;
2754 
2755 		g_queue_push_tail (parser->slides, slide);
2756 	}
2757 	else if (strcmp (name, "size") == 0) {
2758 		Slide *slide = parser->slides->tail->data;
2759 		FileSize *size = g_new0 (FileSize, 1);
2760 		for (i = 0; attr_names[i]; i++) {
2761 			if (strcmp (attr_names[i], "width") == 0)
2762 				size->width = atoi (attr_values[i]);
2763 			else if (strcmp (attr_names[i], "height") == 0)
2764 				size->height = atoi (attr_values[i]);
2765 		}
2766 		if (parser->stack->tail &&
2767 		    (strcmp (parser->stack->tail->data, "file") == 0 ||
2768 		     strcmp (parser->stack->tail->data, "from") == 0)) {
2769 			slide->file1 = g_slist_prepend (slide->file1, size);
2770 		}
2771 		else if (parser->stack->tail &&
2772 			 strcmp (parser->stack->tail->data, "to") == 0) {
2773 			slide->file2 = g_slist_prepend (slide->file2, size);
2774 		}
2775 		else
2776 			g_free (size);
2777 	}
2778 	g_queue_push_tail (parser->stack, g_strdup (name));
2779 }
2780 
2781 static void
handle_end_element(GMarkupParseContext * context,const gchar * name,gpointer user_data,GError ** err)2782 handle_end_element (GMarkupParseContext *context,
2783 		    const gchar         *name,
2784 		    gpointer             user_data,
2785 		    GError             **err)
2786 {
2787 	SlideShow *parser = user_data;
2788 
2789 	g_free (g_queue_pop_tail (parser->stack));
2790 }
2791 
2792 static gboolean
stack_is(SlideShow * parser,const char * s1,...)2793 stack_is (SlideShow *parser,
2794 	  const char *s1,
2795 	  ...)
2796 {
2797 	GList *stack = NULL;
2798 	const char *s;
2799 	GList *l1, *l2;
2800 	va_list args;
2801 
2802 	stack = g_list_prepend (stack, (gpointer)s1);
2803 
2804 	va_start (args, s1);
2805 
2806 	s = va_arg (args, const char *);
2807 	while (s) {
2808 		stack = g_list_prepend (stack, (gpointer)s);
2809 		s = va_arg (args, const char *);
2810 	}
2811 
2812 	va_end (args);
2813 
2814 	l1 = stack;
2815 	l2 = parser->stack->head;
2816 
2817 	while (l1 && l2) {
2818 		if (strcmp (l1->data, l2->data) != 0) {
2819 			g_list_free (stack);
2820 			return FALSE;
2821 		}
2822 
2823 		l1 = l1->next;
2824 		l2 = l2->next;
2825 	}
2826 
2827 	g_list_free (stack);
2828 
2829 	return (!l1 && !l2);
2830 }
2831 
2832 static int
parse_int(const char * text)2833 parse_int (const char *text)
2834 {
2835 	return strtol (text, NULL, 0);
2836 }
2837 
2838 static void
handle_text(GMarkupParseContext * context,const gchar * text,gsize text_len,gpointer user_data,GError ** err)2839 handle_text (GMarkupParseContext *context,
2840 	     const gchar         *text,
2841 	     gsize                text_len,
2842 	     gpointer             user_data,
2843 	     GError             **err)
2844 {
2845 	SlideShow *parser = user_data;
2846 	FileSize *fs;
2847 	gint i;
2848 
2849 	g_return_if_fail (parser != NULL);
2850 	g_return_if_fail (parser->slides != NULL);
2851 
2852 	Slide *slide = parser->slides->tail ? parser->slides->tail->data : NULL;
2853 
2854 	if (stack_is (parser, "year", "starttime", "background", NULL)) {
2855 		parser->start_tm.tm_year = parse_int (text) - 1900;
2856 	}
2857 	else if (stack_is (parser, "month", "starttime", "background", NULL)) {
2858 		parser->start_tm.tm_mon = parse_int (text) - 1;
2859 	}
2860 	else if (stack_is (parser, "day", "starttime", "background", NULL)) {
2861 		parser->start_tm.tm_mday = parse_int (text);
2862 	}
2863 	else if (stack_is (parser, "hour", "starttime", "background", NULL)) {
2864 		parser->start_tm.tm_hour = parse_int (text) - 1;
2865 	}
2866 	else if (stack_is (parser, "minute", "starttime", "background", NULL)) {
2867 		parser->start_tm.tm_min = parse_int (text);
2868 	}
2869 	else if (stack_is (parser, "second", "starttime", "background", NULL)) {
2870 		parser->start_tm.tm_sec = parse_int (text);
2871 	}
2872 	else if (stack_is (parser, "duration", "static", "background", NULL) ||
2873 		 stack_is (parser, "duration", "transition", "background", NULL)) {
2874 		g_return_if_fail (slide != NULL);
2875 
2876 		slide->duration = g_strtod (text, NULL);
2877 		parser->total_duration += slide->duration;
2878 	}
2879 	else if (stack_is (parser, "file", "static", "background", NULL) ||
2880 		 stack_is (parser, "from", "transition", "background", NULL)) {
2881 		g_return_if_fail (slide != NULL);
2882 
2883 		for (i = 0; text[i]; i++) {
2884 			if (!g_ascii_isspace (text[i]))
2885 				break;
2886 		}
2887 		if (text[i] == 0)
2888 			return;
2889 		fs = g_new (FileSize, 1);
2890 		fs->width = -1;
2891 		fs->height = -1;
2892 		fs->file = g_strdup (text);
2893 		slide->file1 = g_slist_prepend (slide->file1, fs);
2894 		if (slide->file1->next != NULL)
2895 			parser->has_multiple_sizes = TRUE;
2896 	}
2897 	else if (stack_is (parser, "size", "file", "static", "background", NULL) ||
2898 		 stack_is (parser, "size", "from", "transition", "background", NULL)) {
2899 		g_return_if_fail (slide != NULL);
2900 
2901 		fs = slide->file1->data;
2902 		fs->file = g_strdup (text);
2903 		if (slide->file1->next != NULL)
2904 			parser->has_multiple_sizes = TRUE;
2905 	}
2906 	else if (stack_is (parser, "to", "transition", "background", NULL)) {
2907 		g_return_if_fail (slide != NULL);
2908 
2909 		for (i = 0; text[i]; i++) {
2910 			if (!g_ascii_isspace (text[i]))
2911 				break;
2912 		}
2913 		if (text[i] == 0)
2914 			return;
2915 		fs = g_new (FileSize, 1);
2916 		fs->width = -1;
2917 		fs->height = -1;
2918 		fs->file = g_strdup (text);
2919 		slide->file2 = g_slist_prepend (slide->file2, fs);
2920 		if (slide->file2->next != NULL)
2921 			parser->has_multiple_sizes = TRUE;
2922 	}
2923 	else if (stack_is (parser, "size", "to", "transition", "background", NULL)) {
2924 		g_return_if_fail (slide != NULL);
2925 
2926 		fs = slide->file2->data;
2927 		fs->file = g_strdup (text);
2928 		if (slide->file2->next != NULL)
2929 			parser->has_multiple_sizes = TRUE;
2930 	}
2931 }
2932 
2933 static SlideShow *
slideshow_ref(SlideShow * show)2934 slideshow_ref (SlideShow *show)
2935 {
2936 	show->ref_count++;
2937 	return show;
2938 }
2939 
2940 static void
slideshow_unref(SlideShow * show)2941 slideshow_unref (SlideShow *show)
2942 {
2943 	GList *list;
2944 	GSList *slist;
2945 	FileSize *size;
2946 
2947 	show->ref_count--;
2948 	if (show->ref_count > 0)
2949 		return;
2950 
2951 	for (list = show->slides->head; list != NULL; list = list->next) {
2952 		Slide *slide = list->data;
2953 
2954 		for (slist = slide->file1; slist != NULL; slist = slist->next) {
2955 			size = slist->data;
2956 			g_free (size->file);
2957 			g_free (size);
2958 		}
2959 		g_slist_free (slide->file1);
2960 
2961 		for (slist = slide->file2; slist != NULL; slist = slist->next) {
2962 			size = slist->data;
2963 			g_free (size->file);
2964 			g_free (size);
2965 		}
2966 		g_slist_free (slide->file2);
2967 
2968 		g_free (slide);
2969 	}
2970 
2971 	g_queue_free (show->slides);
2972 	g_queue_free_full (show->stack, g_free);
2973 	g_free (show);
2974 }
2975 
2976 static void
dump_bg(SlideShow * show)2977 dump_bg (SlideShow *show)
2978 {
2979 #if 0
2980 	GList *list;
2981 	GSList *slist;
2982 
2983 	for (list = show->slides->head; list != NULL; list = list->next)
2984 	{
2985 		Slide *slide = list->data;
2986 
2987 		g_print ("\nSlide: %s\n", slide->fixed? "fixed" : "transition");
2988 		g_print ("duration: %f\n", slide->duration);
2989 		g_print ("File1:\n");
2990 		for (slist = slide->file1; slist != NULL; slist = slist->next) {
2991 			FileSize *size = slist->data;
2992 			g_print ("\t%s (%dx%d)\n",
2993 				 size->file, size->width, size->height);
2994 		}
2995 		g_print ("File2:\n");
2996 		for (slist = slide->file2; slist != NULL; slist = slist->next) {
2997 			FileSize *size = slist->data;
2998 			g_print ("\t%s (%dx%d)\n",
2999 				 size->file, size->width, size->height);
3000 		}
3001 	}
3002 #endif
3003 }
3004 
3005 static void
threadsafe_localtime(time_t time,struct tm * tm)3006 threadsafe_localtime (time_t time, struct tm *tm)
3007 {
3008 	struct tm *res;
3009 
3010 	G_LOCK_DEFINE_STATIC (localtime_mutex);
3011 
3012 	G_LOCK (localtime_mutex);
3013 
3014 	res = localtime (&time);
3015 	if (tm) {
3016 		*tm = *res;
3017 	}
3018 
3019 	G_UNLOCK (localtime_mutex);
3020 }
3021 
3022 static SlideShow *
read_slideshow_file(const char * filename,GError ** err)3023 read_slideshow_file (const char *filename,
3024 		     GError     **err)
3025 {
3026 	GMarkupParser parser = {
3027 		handle_start_element,
3028 		handle_end_element,
3029 		handle_text,
3030 		NULL, /* passthrough */
3031 		NULL, /* error */
3032 	};
3033 
3034 	GFile *file;
3035 	char *contents = NULL;
3036 	gsize len;
3037 	SlideShow *show = NULL;
3038 	GMarkupParseContext *context = NULL;
3039 	time_t t;
3040 
3041 	if (!filename)
3042 		return NULL;
3043 
3044 	file = g_file_new_for_path (filename);
3045 	if (!g_file_load_contents (file, NULL, &contents, &len, NULL, NULL)) {
3046 		g_object_unref (file);
3047 		return NULL;
3048 	}
3049 	g_object_unref (file);
3050 
3051 	show = g_new0 (SlideShow, 1);
3052 	show->ref_count = 1;
3053 	threadsafe_localtime ((time_t)0, &show->start_tm);
3054 	show->stack = g_queue_new ();
3055 	show->slides = g_queue_new ();
3056 
3057 	context = g_markup_parse_context_new (&parser, 0, show, NULL);
3058 
3059 	if (!g_markup_parse_context_parse (context, contents, len, err)) {
3060 		slideshow_unref (show);
3061 		show = NULL;
3062 	}
3063 
3064 
3065 	if (show) {
3066 		if (!g_markup_parse_context_end_parse (context, err)) {
3067 			slideshow_unref (show);
3068 			show = NULL;
3069 		}
3070 	}
3071 
3072 	g_markup_parse_context_free (context);
3073 
3074 	if (show) {
3075 		guint num_items;
3076 
3077 		t = mktime (&show->start_tm);
3078 
3079 		show->start_time = (double)t;
3080 
3081 		dump_bg (show);
3082 
3083 		num_items = g_queue_get_length (show->slides);
3084 
3085 		/* no slides, that's not a slideshow */
3086 		if (num_items == 0) {
3087 			slideshow_unref (show);
3088 			show = NULL;
3089 		/* one slide, there's no transition */
3090 		} else if (num_items == 1) {
3091 			Slide *slide = show->slides->head->data;
3092 			slide->duration = show->total_duration = G_MAXUINT;
3093 		}
3094 	}
3095 
3096 	g_free (contents);
3097 
3098 	return show;
3099 }
3100 
3101 /* Thumbnail utilities */
3102 static GdkPixbuf *
create_thumbnail_for_filename(MateDesktopThumbnailFactory * factory,const char * filename)3103 create_thumbnail_for_filename (MateDesktopThumbnailFactory *factory,
3104 			       const char            *filename)
3105 {
3106 	char *thumb;
3107 	time_t mtime;
3108 	GdkPixbuf *orig, *result = NULL;
3109 	char *uri;
3110 
3111 	mtime = get_mtime (filename);
3112 
3113 	if (mtime == (time_t)-1)
3114 		return NULL;
3115 
3116 	uri = g_filename_to_uri (filename, NULL, NULL);
3117 
3118 	if (uri == NULL)
3119 		return NULL;
3120 
3121 	thumb = mate_desktop_thumbnail_factory_lookup (factory, uri, mtime);
3122 
3123 	if (thumb) {
3124 		result = gdk_pixbuf_new_from_file (thumb, NULL);
3125 		g_free (thumb);
3126 	}
3127 	else {
3128 		orig = gdk_pixbuf_new_from_file (filename, NULL);
3129 		if (orig) {
3130 			int orig_width = gdk_pixbuf_get_width (orig);
3131 			int orig_height = gdk_pixbuf_get_height (orig);
3132 
3133 			result = pixbuf_scale_to_fit (orig, THUMBNAIL_SIZE, THUMBNAIL_SIZE);
3134 
3135 
3136 			g_object_set_data_full (G_OBJECT (result), "mate-thumbnail-height",
3137 						g_strdup_printf ("%d", orig_height), g_free);
3138 			g_object_set_data_full (G_OBJECT (result), "mate-thumbnail-width",
3139 						g_strdup_printf ("%d", orig_width), g_free);
3140 
3141 			g_object_unref (orig);
3142 
3143 			mate_desktop_thumbnail_factory_save_thumbnail (factory, result, uri, mtime);
3144 		}
3145 		else {
3146 			mate_desktop_thumbnail_factory_create_failed_thumbnail (factory, uri, mtime);
3147 		}
3148 	}
3149 
3150 	g_free (uri);
3151 
3152 	return result;
3153 }
3154 
3155 static gboolean
get_thumb_annotations(GdkPixbuf * thumb,int * orig_width,int * orig_height)3156 get_thumb_annotations (GdkPixbuf *thumb,
3157 		       int	 *orig_width,
3158 		       int	 *orig_height)
3159 {
3160 	char *end;
3161 	const char *wstr, *hstr;
3162 
3163 	wstr = gdk_pixbuf_get_option (thumb, "tEXt::Thumb::Image::Width");
3164 	hstr = gdk_pixbuf_get_option (thumb, "tEXt::Thumb::Image::Height");
3165 
3166 	if (hstr && wstr) {
3167 		*orig_width = strtol (wstr, &end, 10);
3168 		if (*end != 0)
3169 			return FALSE;
3170 
3171 		*orig_height = strtol (hstr, &end, 10);
3172 		if (*end != 0)
3173 			return FALSE;
3174 
3175 		return TRUE;
3176 	}
3177 
3178 	return FALSE;
3179 }
3180 
3181 static gboolean
slideshow_has_multiple_sizes(SlideShow * show)3182 slideshow_has_multiple_sizes (SlideShow *show)
3183 {
3184 	return show->has_multiple_sizes;
3185 }
3186 
3187 /*
3188  * Returns whether the background is a slideshow.
3189  */
3190 gboolean
mate_bg_changes_with_time(MateBG * bg)3191 mate_bg_changes_with_time (MateBG *bg)
3192 {
3193 	SlideShow *show;
3194 
3195 	g_return_val_if_fail (bg != NULL, FALSE);
3196 
3197 	if (!bg->filename)
3198 		return FALSE;
3199 
3200 	show = get_as_slideshow (bg, bg->filename);
3201 	if (show)
3202 		return g_queue_get_length (show->slides) > 1;
3203 
3204 	return FALSE;
3205 }
3206 
3207 /**
3208  * mate_bg_create_frame_thumbnail:
3209  *
3210  * Creates a thumbnail for a certain frame, where 'frame' is somewhat
3211  * vaguely defined as 'suitable point to show while single-stepping
3212  * through the slideshow'.
3213  *
3214  * Returns: (transfer full): the newly created thumbnail or
3215  * or NULL if frame_num is out of bounds.
3216  */
3217 GdkPixbuf *
mate_bg_create_frame_thumbnail(MateBG * bg,MateDesktopThumbnailFactory * factory,GdkScreen * screen,int dest_width,int dest_height,int frame_num)3218 mate_bg_create_frame_thumbnail (MateBG			*bg,
3219 				 MateDesktopThumbnailFactory	*factory,
3220 				 GdkScreen			*screen,
3221 				 int				 dest_width,
3222 				 int				 dest_height,
3223 				 int				 frame_num)
3224 {
3225 	SlideShow *show;
3226 	GdkPixbuf *result;
3227 	GdkPixbuf *thumb;
3228         GList *l;
3229         int i, skipped;
3230         gboolean found;
3231 
3232 	g_return_val_if_fail (bg != NULL, FALSE);
3233 
3234 	show = get_as_slideshow (bg, bg->filename);
3235 
3236 	if (!show)
3237 		return NULL;
3238 
3239 
3240 	if (frame_num < 0 || frame_num >= g_queue_get_length (show->slides))
3241 		return NULL;
3242 
3243 	i = 0;
3244 	skipped = 0;
3245 	found = FALSE;
3246 	for (l = show->slides->head; l; l = l->next) {
3247 		Slide *slide = l->data;
3248 		if (!slide->fixed) {
3249 			skipped++;
3250 			continue;
3251 		}
3252 		if (i == frame_num) {
3253 			found = TRUE;
3254 			break;
3255 		}
3256 		i++;
3257 	}
3258 	if (!found)
3259 		return NULL;
3260 
3261 
3262 	result = gdk_pixbuf_new (GDK_COLORSPACE_RGB, FALSE, 8, dest_width, dest_height);
3263 
3264 	draw_color (bg, result);
3265 
3266 	if (bg->filename) {
3267 		thumb = create_img_thumbnail (bg, factory, screen,
3268 					      dest_width, dest_height,
3269 					      frame_num + skipped);
3270 
3271 		if (thumb) {
3272 			draw_image_for_thumb (bg, thumb, result);
3273 			g_object_unref (thumb);
3274 		}
3275 	}
3276 
3277 	return result;
3278 }
3279 
3280