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