1 /* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*-
2 
3    eel-background.c: Object for the background of a widget.
4 
5    Copyright (C) 2000 Eazel, Inc.
6    Copyright (C) 2012 Jasmine Hassan <jasmine.aura@gmail.com>
7    Copyright (C) 2012-2021 The MATE developers
8 
9    This program is free software; you can redistribute it and/or
10    modify it under the terms of the GNU Library General Public License as
11    published by the Free Software Foundation; either version 2 of the
12    License, or (at your option) any later version.
13 
14    This program is distributed in the hope that it will be useful,
15    but WITHOUT ANY WARRANTY; without even the implied warranty of
16    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
17    Library General Public License for more details.
18 
19    You should have received a copy of the GNU Library General Public
20    License along with this program; if not, write to the
21    Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
22    Boston, MA 02110-1301, USA.
23 
24    Authors: Darin Adler <darin@eazel.com>
25             Jasmine Hassan <jasmine.aura@gmail.com>
26 */
27 
28 #include <config.h>
29 #include <gtk/gtk.h>
30 #include <cairo-xlib.h>
31 #include <gdk/gdkx.h>
32 #include <gio/gio.h>
33 #include <math.h>
34 #include <stdio.h>
35 
36 #include "eel-background.h"
37 #include "eel-gdk-extensions.h"
38 #include "eel-glib-extensions.h"
39 #include "eel-lib-self-check-functions.h"
40 #include "eel-canvas.h"
41 
42 enum
43 {
44     APPEARANCE_CHANGED,
45     SETTINGS_CHANGED,
46     RESET,
47     LAST_SIGNAL
48 };
49 
50 static guint signals[LAST_SIGNAL] = { 0 };
51 
52 struct EelBackgroundPrivate
53 {
54     GtkWidget *widget;
55     GtkWidget *front_widget;
56     MateBG *bg;
57     char *color;
58 
59     /* Realized data: */
60     cairo_surface_t *bg_surface;
61     gboolean unset_root_surface;
62     MateBGCrossfade *fade;
63     int bg_entire_width;
64     int bg_entire_height;
65     GdkRGBA default_color;
66     gboolean use_base;
67 
68     /* Is this background attached to desktop window */
69     gboolean is_desktop;
70     /* Desktop screen size watcher */
71     gulong screen_size_handler;
72     /* Desktop monitors configuration watcher */
73     gulong screen_monitors_handler;
74     /* Can we use common surface for root window and desktop window */
75     gboolean use_common_surface;
76     guint change_idle_id;
77 
78     /* activity status */
79     gboolean is_active;
80 };
81 
82 static GList *desktop_bg_objects = NULL;
83 
G_DEFINE_TYPE_WITH_PRIVATE(EelBackground,eel_background,G_TYPE_OBJECT)84 G_DEFINE_TYPE_WITH_PRIVATE (EelBackground, eel_background, G_TYPE_OBJECT)
85 
86 static void
87 free_fade (EelBackground *self)
88 {
89     if (self->details->fade != NULL)
90     {
91         g_object_unref (self->details->fade);
92         self->details->fade = NULL;
93     }
94 }
95 
96 static void
free_background_surface(EelBackground * self)97 free_background_surface (EelBackground *self)
98 {
99     cairo_surface_t *surface;
100 
101     surface = self->details->bg_surface;
102     if (surface != NULL)
103     {
104         /* If we created a root surface and didn't set it as background
105            it will live forever, so we need to kill it manually.
106            If set as root background it will be killed next time the
107            background is changed. */
108         if (self->details->unset_root_surface)
109         {
110             XKillClient (cairo_xlib_surface_get_display (surface),
111                          cairo_xlib_surface_get_drawable (surface));
112         }
113         cairo_surface_destroy (surface);
114         self->details->bg_surface = NULL;
115     }
116 }
117 
118 static void
eel_background_finalize(GObject * object)119 eel_background_finalize (GObject *object)
120 {
121     EelBackground *self = EEL_BACKGROUND (object);
122 
123     g_free (self->details->color);
124 
125     if (self->details->bg != NULL) {
126         g_object_unref (G_OBJECT (self->details->bg));
127         self->details->bg = NULL;
128     }
129 
130     free_background_surface (self);
131     free_fade (self);
132 
133     if (self->details->is_desktop)
134     {
135         desktop_bg_objects = g_list_remove (desktop_bg_objects,
136                                             G_OBJECT (self));
137     }
138 
139     G_OBJECT_CLASS (eel_background_parent_class)->finalize (object);
140 }
141 
142 static void
eel_background_unrealize(EelBackground * self)143 eel_background_unrealize (EelBackground *self)
144 {
145     free_background_surface (self);
146 
147     self->details->bg_entire_width = 0;
148     self->details->bg_entire_height = 0;
149     self->details->default_color.red = 1.0;
150     self->details->default_color.green = 1.0;
151     self->details->default_color.blue = 1.0;
152     self->details->default_color.alpha = 1.0;
153 }
154 
155 #define CLAMP_COLOR(v) (CLAMP ((v), 0, 1))
156 #define SATURATE(v) ((1.0 - saturation) * intensity + saturation * (v))
157 
158 static void
make_color_inactive(EelBackground * self,GdkRGBA * color)159 make_color_inactive (EelBackground *self,
160                      GdkRGBA       *color)
161 {
162     if (!self->details->is_active) {
163         double intensity, saturation;
164 
165         saturation = 0.7;
166         intensity = color->red * 0.30 + color->green * 0.59 + color->blue * 0.11;
167         color->red = SATURATE (color->red);
168         color->green = SATURATE (color->green);
169         color->blue = SATURATE (color->blue);
170 
171         if (intensity > 0.5)
172         {
173            color->red *= 0.9;
174            color->green *= 0.9;
175            color->blue *= 0.9;
176         }
177         else
178         {
179            color->red *= 1.25;
180            color->green *= 1.25;
181            color->blue *= 1.25;
182         }
183 
184         color->red = CLAMP_COLOR (color->red);
185         color->green = CLAMP_COLOR (color->green);
186         color->blue = CLAMP_COLOR (color->blue);
187     }
188 }
189 
190 gchar *
eel_bg_get_desktop_color(EelBackground * self)191 eel_bg_get_desktop_color (EelBackground *self)
192 {
193     MateBGColorType type;
194     GdkRGBA    primary, secondary;
195     char      *start_color, *color_spec;
196     gboolean   use_gradient = TRUE;
197     gboolean   is_horizontal = FALSE;
198 
199     mate_bg_get_color (self->details->bg, &type, &primary, &secondary);
200 
201     if (type == MATE_BG_COLOR_V_GRADIENT)
202     {
203         is_horizontal = FALSE;
204     }
205     else if (type == MATE_BG_COLOR_H_GRADIENT)
206     {
207         is_horizontal = TRUE;
208     }
209     else	/* implicit (type == MATE_BG_COLOR_SOLID) */
210     {
211         use_gradient = FALSE;
212     }
213 
214     start_color = eel_gdk_rgb_to_color_spec (eel_gdk_rgba_to_rgb (&primary));
215 
216     if (use_gradient)
217     {
218         char *end_color;
219 
220         end_color  = eel_gdk_rgb_to_color_spec (eel_gdk_rgba_to_rgb (&secondary));
221         color_spec = eel_gradient_new (start_color, end_color, is_horizontal);
222         g_free (end_color);
223     }
224     else
225     {
226         color_spec = g_strdup (start_color);
227     }
228     g_free (start_color);
229 
230     return color_spec;
231 }
232 
233 static void
set_image_properties(EelBackground * self)234 set_image_properties (EelBackground *self)
235 {
236     GdkRGBA c;
237 
238     if (self->details->is_desktop && !self->details->color)
239         self->details->color = eel_bg_get_desktop_color (self);
240 
241     if (!self->details->color)
242     {
243         c = self->details->default_color;
244         make_color_inactive (self, &c);
245         mate_bg_set_color (self->details->bg, MATE_BG_COLOR_SOLID, &c, NULL);
246     }
247     else if (!eel_gradient_is_gradient (self->details->color))
248     {
249         eel_gdk_rgba_parse_with_white_default (&c, self->details->color);
250         make_color_inactive (self, &c);
251         mate_bg_set_color (self->details->bg, MATE_BG_COLOR_SOLID, &c, NULL);
252     }
253     else
254     {
255         GdkRGBA c1, c2;
256         char *spec;
257 
258         spec = eel_gradient_get_start_color_spec (self->details->color);
259         eel_gdk_rgba_parse_with_white_default (&c1, spec);
260         make_color_inactive (self, &c1);
261         g_free (spec);
262 
263         spec = eel_gradient_get_end_color_spec (self->details->color);
264         eel_gdk_rgba_parse_with_white_default (&c2, spec);
265         make_color_inactive (self, &c2);
266         g_free (spec);
267 
268         if (eel_gradient_is_horizontal (self->details->color)) {
269             mate_bg_set_color (self->details->bg, MATE_BG_COLOR_H_GRADIENT, &c1, &c2);
270         } else {
271             mate_bg_set_color (self->details->bg, MATE_BG_COLOR_V_GRADIENT, &c1, &c2);
272         }
273     }
274 }
275 
276 gchar *
eel_background_get_color(EelBackground * self)277 eel_background_get_color (EelBackground *self)
278 {
279     g_return_val_if_fail (EEL_IS_BACKGROUND (self), NULL);
280 
281     return g_strdup (self->details->color);
282 }
283 
284 /* @color: color or gradient to set */
285 void
eel_background_set_color(EelBackground * self,const gchar * color)286 eel_background_set_color (EelBackground *self,
287                           const gchar   *color)
288 {
289     if (g_strcmp0 (self->details->color, color) != 0)
290     {
291         g_free (self->details->color);
292         self->details->color = g_strdup (color);
293 
294         set_image_properties (self);
295     }
296 }
297 
298 /* Use style->base as the default color instead of bg */
299 void
eel_background_set_use_base(EelBackground * self,gboolean use_base)300 eel_background_set_use_base (EelBackground *self,
301                              gboolean       use_base)
302 {
303     self->details->use_base = use_base;
304 }
305 
306 static void
drawable_get_adjusted_size(EelBackground * self,int * width,int * height)307 drawable_get_adjusted_size (EelBackground *self,
308                             int		  *width,
309                             int	          *height)
310 {
311     if (self->details->is_desktop)
312     {
313         GdkScreen *screen = gtk_widget_get_screen (self->details->widget);
314         gint scale = gtk_widget_get_scale_factor (self->details->widget);
315         *width = WidthOfScreen (gdk_x11_screen_get_xscreen (screen)) / scale;
316         *height = HeightOfScreen (gdk_x11_screen_get_xscreen (screen)) / scale;
317     }
318     else
319     {
320         GdkWindow *window = gtk_widget_get_window (self->details->widget);
321         *width = gdk_window_get_width (window);
322         *height = gdk_window_get_height (window);
323     }
324 }
325 
326 static gboolean
eel_background_ensure_realized(EelBackground * self)327 eel_background_ensure_realized (EelBackground *self)
328 {
329     int width, height;
330     GdkWindow *window;
331     GtkStyleContext *style;
332     GdkRGBA *c;
333 
334     /* Set the default color */
335     style = gtk_widget_get_style_context (self->details->widget);
336     gtk_style_context_save (style);
337     gtk_style_context_set_state (style, GTK_STATE_FLAG_NORMAL);
338     if (self->details->use_base) {
339         gtk_style_context_add_class (style, GTK_STYLE_CLASS_VIEW);
340     }
341 
342     gtk_style_context_get (style, gtk_style_context_get_state (style),
343                            GTK_STYLE_PROPERTY_BACKGROUND_COLOR,
344                            &c, NULL);
345     self->details->default_color = *c;
346     gdk_rgba_free (c);
347 
348     gtk_style_context_restore (style);
349 
350     /* If the window size is the same as last time, don't update */
351     drawable_get_adjusted_size (self, &width, &height);
352     if (width == self->details->bg_entire_width &&
353         height == self->details->bg_entire_height)
354     {
355         return FALSE;
356     }
357 
358     free_background_surface (self);
359 
360     set_image_properties (self);
361 
362     window = gtk_widget_get_window (self->details->widget);
363     self->details->bg_surface = mate_bg_create_surface (self->details->bg,
364         						window, width, height,
365         						self->details->is_desktop);
366     self->details->unset_root_surface = self->details->is_desktop;
367 
368     self->details->bg_entire_width = width;
369     self->details->bg_entire_height = height;
370 
371     return TRUE;
372 }
373 
374 void
eel_background_draw(GtkWidget * widget,cairo_t * cr)375 eel_background_draw (GtkWidget *widget,
376                      cairo_t   *cr)
377 {
378     EelBackground *self = eel_get_widget_background (widget);
379     GdkRGBA color;
380     int width, height;
381 
382     if (self->details->fade != NULL &&
383         mate_bg_crossfade_is_started (self->details->fade))
384     {
385         return;
386     }
387 
388     drawable_get_adjusted_size (self, &width, &height);
389 
390     eel_background_ensure_realized (self);
391     color = self->details->default_color;
392     make_color_inactive (self, &color);
393 
394     cairo_save (cr);
395 
396     if (self->details->bg_surface != NULL)
397     {
398         cairo_set_source_surface (cr, self->details->bg_surface, 0, 0);
399         cairo_pattern_set_extend (cairo_get_source (cr), CAIRO_EXTEND_REPEAT);
400     } else {
401         gdk_cairo_set_source_rgba (cr, &color);
402     }
403 
404     cairo_rectangle (cr, 0, 0, width, height);
405     cairo_fill (cr);
406 
407     cairo_restore (cr);
408 }
409 
410 static void
set_root_surface(EelBackground * self,GdkWindow * window,GdkScreen * screen)411 set_root_surface (EelBackground *self,
412                   GdkWindow     *window,
413                   GdkScreen     *screen)
414 {
415     eel_background_ensure_realized (self);
416 
417     if (self->details->use_common_surface) {
418         self->details->unset_root_surface = FALSE;
419     } else {
420         int width, height;
421         drawable_get_adjusted_size (self, &width, &height);
422         self->details->bg_surface = mate_bg_create_surface (self->details->bg, window,
423         						    width, height, TRUE);
424     }
425 
426     if (self->details->bg_surface != NULL)
427         mate_bg_set_surface_as_root (screen, self->details->bg_surface);
428 }
429 
430 static void
init_fade(EelBackground * self)431 init_fade (EelBackground *self)
432 {
433     GSettings *mate_background_preferences;
434     GtkWidget *widget = self->details->widget;
435     gboolean do_fade;
436 
437     if (!self->details->is_desktop || widget == NULL || !gtk_widget_get_realized (widget)) {
438         return;
439     }
440 
441     mate_background_preferences = g_settings_new ("org.mate.background");
442     do_fade = g_settings_get_boolean (mate_background_preferences,
443                                       MATE_BG_KEY_BACKGROUND_FADE);
444     g_object_unref (mate_background_preferences);
445 
446     if (!do_fade) {
447     	return;
448     }
449 
450     if (self->details->fade == NULL)
451     {
452         int width, height;
453 
454         /* If this was the result of a screen size change,
455          * we don't want to crossfade
456          */
457         drawable_get_adjusted_size (self, &width, &height);
458         if (width == self->details->bg_entire_width &&
459             height == self->details->bg_entire_height)
460         {
461             self->details->fade = mate_bg_crossfade_new (width, height);
462             g_signal_connect_swapped (self->details->fade,
463                                       "finished",
464                                       G_CALLBACK (free_fade),
465                                       self);
466         }
467     }
468 
469     if (self->details->fade != NULL && !mate_bg_crossfade_is_started (self->details->fade))
470     {
471         if (self->details->bg_surface == NULL)
472         {
473             cairo_surface_t *start_surface;
474             start_surface = mate_bg_get_surface_from_root (gtk_widget_get_screen (widget));
475             mate_bg_crossfade_set_start_surface (self->details->fade, start_surface);
476             cairo_surface_destroy (start_surface);
477         }
478         else
479         {
480             mate_bg_crossfade_set_start_surface (self->details->fade, self->details->bg_surface);
481         }
482     }
483 }
484 
485 static void
on_fade_finished(MateBGCrossfade * fade,GdkWindow * window,gpointer user_data)486 on_fade_finished (MateBGCrossfade *fade,
487                   GdkWindow       *window,
488 		  gpointer         user_data)
489 {
490     EelBackground *self = EEL_BACKGROUND (user_data);
491 
492     set_root_surface (self, window, gdk_window_get_screen (window));
493 }
494 
495 static gboolean
fade_to_surface(EelBackground * self,GtkWidget * widget,cairo_surface_t * surface)496 fade_to_surface (EelBackground   *self,
497                  GtkWidget       *widget,
498                  cairo_surface_t *surface)
499 {
500     if (self->details->fade == NULL ||
501         !mate_bg_crossfade_set_end_surface (self->details->fade, surface))
502     {
503         return FALSE;
504     }
505 
506     if (!mate_bg_crossfade_is_started (self->details->fade))
507     {
508         mate_bg_crossfade_start_widget (self->details->fade, widget);
509 
510         if (self->details->is_desktop)
511         {
512             g_signal_connect (self->details->fade,
513                               "finished",
514                               G_CALLBACK (on_fade_finished), self);
515         }
516     }
517 
518     return mate_bg_crossfade_is_started (self->details->fade);
519 }
520 
521 static void
eel_background_set_up_widget(EelBackground * self)522 eel_background_set_up_widget (EelBackground *self)
523 {
524     GtkWidget *widget = self->details->widget;
525 
526     gboolean in_fade = FALSE;
527 
528     if (!gtk_widget_get_realized (widget))
529         return;
530 
531     eel_background_ensure_realized (self);
532 
533     if (self->details->bg_surface == NULL)
534         return;
535 
536     gtk_widget_queue_draw (widget);
537 
538     if (self->details->fade != NULL)
539         in_fade = fade_to_surface (self, widget, self->details->bg_surface);
540 
541     if (!in_fade)
542     {
543         GdkWindow *window;
544 
545         if (EEL_IS_CANVAS (widget))
546         {
547             window = gtk_layout_get_bin_window (GTK_LAYOUT (widget));
548         }
549         else
550         {
551             window = gtk_widget_get_window (widget);
552         }
553 
554         if (self->details->is_desktop)
555         {
556             set_root_surface (self, window, gtk_widget_get_screen (widget));
557         }
558     }
559 }
560 
561 static gboolean
background_changed_cb(EelBackground * self)562 background_changed_cb (EelBackground *self)
563 {
564     if (self->details->change_idle_id == 0) {
565         return FALSE;
566     }
567     self->details->change_idle_id = 0;
568 
569     eel_background_unrealize (self);
570     eel_background_set_up_widget (self);
571 
572     return FALSE;
573 }
574 
575 static void
widget_queue_background_change(GtkWidget * widget,gpointer user_data)576 widget_queue_background_change (GtkWidget *widget,
577                                 gpointer   user_data)
578 {
579     EelBackground *self = EEL_BACKGROUND (user_data);
580 
581     if (self->details->change_idle_id != 0)
582     {
583         g_source_remove (self->details->change_idle_id);
584     }
585 
586     self->details->change_idle_id =
587           g_idle_add ((GSourceFunc) background_changed_cb, self);
588 }
589 
590 /* Callback used when the style of a widget changes.  We have to regenerate its
591  * EelBackgroundStyle so that it will match the chosen GTK+ theme.
592  */
593 static void
widget_style_updated_cb(GtkWidget * widget,gpointer user_data)594 widget_style_updated_cb (GtkWidget *widget,
595                          gpointer   user_data)
596 {
597     widget_queue_background_change (widget, user_data);
598 }
599 
600 static void
eel_background_changed(MateBG * bg,gpointer user_data)601 eel_background_changed (MateBG *bg,
602                         gpointer user_data)
603 {
604     EelBackground *self = EEL_BACKGROUND (user_data);
605 
606     init_fade (self);
607     g_signal_emit (G_OBJECT (self), signals[APPEARANCE_CHANGED], 0);
608 }
609 
610 static void
eel_background_transitioned(MateBG * bg,gpointer user_data)611 eel_background_transitioned (MateBG *bg, gpointer user_data)
612 {
613     EelBackground *self = EEL_BACKGROUND (user_data);
614 
615     free_fade (self);
616     g_signal_emit (G_OBJECT (self), signals[APPEARANCE_CHANGED], 0);
617 }
618 
619 static void
screen_size_changed(GdkScreen * screen,EelBackground * background)620 screen_size_changed (GdkScreen *screen, EelBackground *background)
621 {
622     int w, h;
623 
624     drawable_get_adjusted_size (background, &w, &h);
625     if (w != background->details->bg_entire_width ||
626         h != background->details->bg_entire_height)
627     {
628         g_signal_emit (background, signals[APPEARANCE_CHANGED], 0);
629     }
630 }
631 
632 static void
widget_realized_setup(GtkWidget * widget,EelBackground * self)633 widget_realized_setup (GtkWidget     *widget,
634                        EelBackground *self)
635 {
636     if (!self->details->is_desktop) {
637         return;
638     }
639 
640     GdkScreen *screen = gtk_widget_get_screen (widget);
641     GdkWindow *window = gdk_screen_get_root_window (screen);
642 
643     if (self->details->screen_size_handler > 0)
644     {
645         g_signal_handler_disconnect (screen, self->details->screen_size_handler);
646     }
647     self->details->screen_size_handler =
648         g_signal_connect (screen, "size_changed", G_CALLBACK (screen_size_changed), self);
649 
650     if (self->details->screen_monitors_handler > 0)
651     {
652         g_signal_handler_disconnect (screen, self->details->screen_monitors_handler);
653     }
654     self->details->screen_monitors_handler =
655         g_signal_connect (screen, "monitors-changed", G_CALLBACK (screen_size_changed), self);
656 
657     self->details->use_common_surface =
658         (gdk_window_get_visual (window) == gtk_widget_get_visual (widget)) ? TRUE : FALSE;
659 
660     init_fade (self);
661 }
662 
663 static void
widget_realize_cb(GtkWidget * widget,gpointer user_data)664 widget_realize_cb (GtkWidget *widget,
665                    gpointer   user_data)
666 {
667     EelBackground *self = EEL_BACKGROUND (user_data);
668 
669     widget_realized_setup (widget, self);
670 
671     eel_background_set_up_widget (self);
672 }
673 
674 static void
widget_unrealize_cb(GtkWidget * widget,gpointer user_data)675 widget_unrealize_cb (GtkWidget *widget,
676                      gpointer   user_data)
677 {
678     EelBackground *self = EEL_BACKGROUND (user_data);
679 
680     if (self->details->screen_size_handler > 0) {
681         g_signal_handler_disconnect (gtk_widget_get_screen (GTK_WIDGET (widget)),
682                                      self->details->screen_size_handler);
683         self->details->screen_size_handler = 0;
684     }
685 
686     if (self->details->screen_monitors_handler > 0) {
687         g_signal_handler_disconnect (gtk_widget_get_screen (GTK_WIDGET (widget)),
688                                      self->details->screen_monitors_handler);
689         self->details->screen_monitors_handler = 0;
690     }
691 
692     self->details->use_common_surface = FALSE;
693 }
694 
695 static void
on_widget_destroyed(GtkWidget * widget,gpointer user_data)696 on_widget_destroyed (GtkWidget *widget,
697                      gpointer   user_data)
698 {
699     EelBackground *self = EEL_BACKGROUND (user_data);
700 
701     if (self->details->change_idle_id != 0) {
702         g_source_remove (self->details->change_idle_id);
703         self->details->change_idle_id = 0;
704     }
705 
706     free_fade (self);
707 
708     self->details->widget = NULL;
709     self->details->front_widget = NULL;
710 }
711 
712 /* Gets the background attached to a widget.
713 
714    If the widget doesn't already have a EelBackground object,
715    this will create one. To change the widget's background, you can
716    just call eel_background methods on the widget.
717 
718    You need to call eel_background_draw() from your draw event handler to
719    draw the background.
720 
721    Later, we might want a call to find out if we already have a background,
722    or a way to share the same background among multiple widgets; both would
723    be straightforward.
724 */
725 EelBackground *
eel_get_widget_background(GtkWidget * widget)726 eel_get_widget_background (GtkWidget *widget)
727 {
728     EelBackground *self;
729     gpointer data;
730     GList *l;
731 
732     g_return_val_if_fail (GTK_IS_WIDGET (widget), NULL);
733 
734     /* Check for an existing background. */
735     data = g_object_get_data (G_OBJECT (widget), "eel_background");
736     if (data != NULL)
737     {
738         g_assert (EEL_IS_BACKGROUND (data));
739         return data;
740     }
741 
742     /* Check for an existing desktop window background. */
743     for (l = desktop_bg_objects; l != NULL; l = l->next)
744     {
745         g_assert (EEL_IS_BACKGROUND (l->data));
746         self = EEL_BACKGROUND (l->data);
747         if (widget == self->details->widget)
748         {
749             return self;
750         }
751     }
752 
753     self = eel_background_new ();
754     self->details->widget = widget;
755     self->details->front_widget = widget;
756 
757     /* Store the background in the widget's data. */
758     g_object_set_data_full (G_OBJECT (widget), "eel_background",
759                             self, g_object_unref);
760 
761     g_signal_connect_object (widget, "destroy",
762                              G_CALLBACK (on_widget_destroyed), self, 0);
763     g_signal_connect_object (widget, "realize",
764                              G_CALLBACK (widget_realize_cb), self, 0);
765     g_signal_connect_object (widget, "unrealize",
766                              G_CALLBACK (widget_unrealize_cb), self, 0);
767 
768     g_signal_connect_object (widget, "style-updated",
769                              G_CALLBACK (widget_style_updated_cb), self, 0);
770 
771     /* Arrange to get the signal whenever the background changes. */
772     g_signal_connect_object (self, "appearance_changed",
773                              G_CALLBACK (widget_queue_background_change),
774                              widget, G_CONNECT_SWAPPED);
775     widget_queue_background_change (widget, self);
776 
777     return self;
778 }
779 
780 static void
eel_background_class_init(EelBackgroundClass * klass)781 eel_background_class_init (EelBackgroundClass *klass)
782 {
783     GObjectClass *object_class = G_OBJECT_CLASS (klass);
784 
785     signals[APPEARANCE_CHANGED] =
786         g_signal_new ("appearance_changed", G_TYPE_FROM_CLASS (object_class),
787                       G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE,
788                       G_STRUCT_OFFSET (EelBackgroundClass, appearance_changed),
789                       NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0);
790 
791     signals[SETTINGS_CHANGED] =
792         g_signal_new ("settings_changed", G_TYPE_FROM_CLASS (object_class),
793                       G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE,
794                       G_STRUCT_OFFSET (EelBackgroundClass, settings_changed),
795                       NULL, NULL, g_cclosure_marshal_VOID__INT, G_TYPE_NONE, 1, G_TYPE_INT);
796 
797     signals[RESET] =
798         g_signal_new ("reset", G_TYPE_FROM_CLASS (object_class),
799                       G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE,
800                       G_STRUCT_OFFSET (EelBackgroundClass, reset),
801                       NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0);
802 
803     object_class->finalize = eel_background_finalize;
804 }
805 
806 static void
eel_background_init(EelBackground * self)807 eel_background_init (EelBackground *self)
808 {
809     self->details = eel_background_get_instance_private(self);
810 
811     self->details->bg = mate_bg_new ();
812     self->details->default_color.red = 1.0;
813     self->details->default_color.green = 1.0;
814     self->details->default_color.blue = 1.0;
815     self->details->default_color.alpha = 1.0;
816     self->details->is_active = TRUE;
817 
818     g_signal_connect (self->details->bg, "changed",
819                       G_CALLBACK (eel_background_changed), self);
820     g_signal_connect (self->details->bg, "transitioned",
821                       G_CALLBACK (eel_background_transitioned), self);
822 
823 }
824 
825 /**
826  * eel_background_is_set:
827  *
828  * Check whether the background's color or image has been set.
829  */
830 gboolean
eel_background_is_set(EelBackground * self)831 eel_background_is_set (EelBackground *self)
832 {
833     g_assert (EEL_IS_BACKGROUND (self));
834 
835     return self->details->color != NULL ||
836            mate_bg_get_filename (self->details->bg) != NULL;
837 }
838 
839 /**
840  * eel_background_reset:
841  *
842  * Emit the reset signal to forget any color or image that has been
843  * set previously.
844  */
845 void
eel_background_reset(EelBackground * self)846 eel_background_reset (EelBackground *self)
847 {
848     g_return_if_fail (EEL_IS_BACKGROUND (self));
849 
850     g_signal_emit (self, signals[RESET], 0);
851 }
852 
853 void
eel_background_set_desktop(EelBackground * self,gboolean is_desktop)854 eel_background_set_desktop (EelBackground *self,
855                             gboolean       is_desktop)
856 {
857     self->details->is_desktop = is_desktop;
858 
859     if (is_desktop)
860     {
861         self->details->widget =
862           gtk_widget_get_toplevel (self->details->front_widget);
863 
864         desktop_bg_objects = g_list_prepend (desktop_bg_objects,
865                                              G_OBJECT (self));
866 
867         if (gtk_widget_get_realized (self->details->widget))
868         {
869             widget_realized_setup (self->details->widget, self);
870         }
871     }
872     else
873     {
874         desktop_bg_objects = g_list_remove (desktop_bg_objects,
875                                             G_OBJECT (self));
876         self->details->widget = self->details->front_widget;
877     }
878 }
879 
880 gboolean
eel_background_is_desktop(EelBackground * self)881 eel_background_is_desktop (EelBackground *self)
882 {
883     return self->details->is_desktop;
884 }
885 
886 void
eel_background_set_active(EelBackground * self,gboolean is_active)887 eel_background_set_active (EelBackground *self,
888                            gboolean is_active)
889 {
890     if (self->details->is_active != is_active)
891     {
892         self->details->is_active = is_active;
893         set_image_properties (self);
894         gtk_widget_queue_draw (self->details->widget);
895     }
896 }
897 
898 /* determine if a background is darker or lighter than average, to help clients know what
899    colors to draw on top with */
900 gboolean
eel_background_is_dark(EelBackground * self)901 eel_background_is_dark (EelBackground *self)
902 {
903     GdkRectangle rect;
904 
905     /* only check for the background on the 0th monitor */
906     GdkScreen *screen = gdk_screen_get_default ();
907     GdkDisplay *display = gdk_screen_get_display (screen);
908     gdk_monitor_get_geometry (gdk_display_get_monitor (display, 0), &rect);
909 
910     return mate_bg_is_dark (self->details->bg, rect.width, rect.height);
911 }
912 
913 gchar *
eel_background_get_image_uri(EelBackground * self)914 eel_background_get_image_uri (EelBackground *self)
915 {
916     g_return_val_if_fail (EEL_IS_BACKGROUND (self), NULL);
917 
918     const gchar *filename = mate_bg_get_filename (self->details->bg);
919 
920     if (filename) {
921         return g_filename_to_uri (filename, NULL, NULL);
922     }
923     return NULL;
924 }
925 
926 static void
eel_bg_set_image_uri_helper(EelBackground * self,const gchar * image_uri,gboolean emit_signal)927 eel_bg_set_image_uri_helper (EelBackground *self,
928                              const gchar   *image_uri,
929                              gboolean       emit_signal)
930 {
931     gchar *filename;
932 
933     if (image_uri != NULL) {
934         filename = g_filename_from_uri (image_uri, NULL, NULL);
935     } else {
936         filename = g_strdup ("");    /* GSettings expects a string, not NULL */
937     }
938 
939     mate_bg_set_filename (self->details->bg, filename);
940     g_free (filename);
941 
942     if (emit_signal)
943         g_signal_emit (self, signals[SETTINGS_CHANGED], 0, GDK_ACTION_COPY);
944 
945     set_image_properties (self);
946 }
947 
948 /* Use this function to set an image only (no color).
949  * Also, to unset an image, use: eel_bg_set_image_uri (background, NULL)
950  */
951 void
eel_background_set_image_uri(EelBackground * self,const gchar * image_uri)952 eel_background_set_image_uri (EelBackground *self,
953                       const gchar   *image_uri)
954 {
955     eel_bg_set_image_uri_helper (self, image_uri, TRUE);
956 }
957 
958 /* Use this fn to set both the image and color and avoid flash. The color isn't
959  * changed till after the image is done loading, that way if an update occurs
960  * before then, it will use the old color and image.
961  */
962 static void
eel_bg_set_image_uri_and_color(EelBackground * self,GdkDragAction action,const gchar * image_uri,const gchar * color)963 eel_bg_set_image_uri_and_color (EelBackground *self,
964                                 GdkDragAction  action,
965                                 const gchar   *image_uri,
966                                 const gchar   *color)
967 {
968     if (self->details->is_desktop &&
969         !mate_bg_get_draw_background (self->details->bg))
970     {
971         mate_bg_set_draw_background (self->details->bg, TRUE);
972     }
973 
974     eel_bg_set_image_uri_helper (self, image_uri, FALSE);
975     eel_background_set_color (self, color);
976 
977     /* We always emit, even if the color didn't change, because the image change
978        relies on us doing it here. */
979     g_signal_emit (self, signals[SETTINGS_CHANGED], 0, action);
980 }
981 
982 void
eel_bg_set_placement(EelBackground * self,MateBGPlacement placement)983 eel_bg_set_placement (EelBackground   *self,
984 		      MateBGPlacement  placement)
985 {
986     if (self->details->bg)
987         mate_bg_set_placement (self->details->bg,
988         		       placement);
989 }
990 
991 void
eel_bg_save_to_gsettings(EelBackground * self,GSettings * settings)992 eel_bg_save_to_gsettings (EelBackground *self,
993 			  GSettings     *settings)
994 {
995     if (self->details->bg)
996         mate_bg_save_to_gsettings (self->details->bg,
997         			   settings);
998 }
999 
1000 void
eel_bg_load_from_gsettings(EelBackground * self,GSettings * settings)1001 eel_bg_load_from_gsettings (EelBackground *self,
1002 			    GSettings     *settings)
1003 {
1004     char *keyfile = g_settings_get_string (settings, MATE_BG_KEY_PICTURE_FILENAME);
1005 
1006     if (!g_file_test (keyfile, G_FILE_TEST_EXISTS) && (*keyfile != '\0'))
1007     {
1008         *keyfile = '\0';
1009         g_settings_set_string (settings, MATE_BG_KEY_PICTURE_FILENAME, keyfile);
1010     }
1011 
1012     if (self->details->bg)
1013         mate_bg_load_from_gsettings (self->details->bg,
1014         			     settings);
1015 }
1016 
1017 void
eel_bg_load_from_system_gsettings(EelBackground * self,GSettings * settings,gboolean apply)1018 eel_bg_load_from_system_gsettings (EelBackground *self,
1019 				   GSettings     *settings,
1020 				   gboolean       apply)
1021 {
1022     if (self->details->bg)
1023         mate_bg_load_from_system_gsettings (self->details->bg,
1024         				    settings,
1025         				    apply);
1026 }
1027 
1028 /* handle dropped images */
1029 void
eel_background_set_dropped_image(EelBackground * self,GdkDragAction action,const gchar * image_uri)1030 eel_background_set_dropped_image (EelBackground *self,
1031                                   GdkDragAction  action,
1032                                   const gchar   *image_uri)
1033 {
1034     /* Currently, we only support tiled images. So we set the placement. */
1035     mate_bg_set_placement (self->details->bg, MATE_BG_PLACEMENT_TILED);
1036 
1037     eel_bg_set_image_uri_and_color (self, action, image_uri, NULL);
1038 }
1039 
1040 /* handle dropped colors */
1041 void
eel_background_set_dropped_color(EelBackground * self,GtkWidget * widget,GdkDragAction action,int drop_location_x,int drop_location_y,const GtkSelectionData * selection_data)1042 eel_background_set_dropped_color (EelBackground *self,
1043                                   GtkWidget     *widget,
1044                                   GdkDragAction  action,
1045                                   int            drop_location_x,
1046                                   int            drop_location_y,
1047                                   const GtkSelectionData *selection_data)
1048 {
1049     guint16 *channels;
1050     char *color_spec, *gradient_spec;
1051     char *new_gradient_spec;
1052     int left_border, right_border, top_border, bottom_border;
1053     GtkAllocation allocation;
1054 
1055     g_return_if_fail (EEL_IS_BACKGROUND (self));
1056     g_return_if_fail (GTK_IS_WIDGET (widget));
1057     g_return_if_fail (selection_data != NULL);
1058 
1059     /* Convert the selection data into a color spec. */
1060     if (gtk_selection_data_get_length ((GtkSelectionData *) selection_data) != 8 ||
1061             gtk_selection_data_get_format ((GtkSelectionData *) selection_data) != 16)
1062     {
1063         g_warning ("received invalid color data");
1064         return;
1065     }
1066     channels = (guint16 *) gtk_selection_data_get_data ((GtkSelectionData *) selection_data);
1067     color_spec = g_strdup_printf ("#%02X%02X%02X",
1068                                   channels[0] >> 8,
1069                                   channels[1] >> 8,
1070                                   channels[2] >> 8);
1071 
1072     /* Figure out if the color was dropped close enough to an edge to create a gradient.
1073        For the moment, this is hard-wired, but later the widget will have to have some
1074        say in where the borders are.
1075     */
1076     gtk_widget_get_allocation (widget, &allocation);
1077     left_border = 32;
1078     right_border = allocation.width - 32;
1079     top_border = 32;
1080     bottom_border = allocation.height - 32;
1081 
1082     /* If a custom background color isn't set, get the GtkStyleContext's bg color. */
1083 
1084     if (!self->details->color)
1085     {
1086 
1087         GtkStyleContext *style = gtk_widget_get_style_context (widget);
1088         GdkRGBA bg;
1089         GdkRGBA *c;
1090 
1091         gtk_style_context_get (style, GTK_STATE_FLAG_NORMAL,
1092                                GTK_STYLE_PROPERTY_BACKGROUND_COLOR,
1093                                &c, NULL);
1094         bg = *c;
1095         gdk_rgba_free (c);
1096 
1097         gradient_spec = gdk_rgba_to_string (&bg);
1098 
1099     }
1100     else
1101     {
1102         gradient_spec = g_strdup (self->details->color);
1103     }
1104 
1105     if (drop_location_x < left_border && drop_location_x <= right_border)
1106     {
1107         new_gradient_spec = eel_gradient_set_left_color_spec (gradient_spec, color_spec);
1108     }
1109     else if (drop_location_x >= left_border && drop_location_x > right_border)
1110     {
1111         new_gradient_spec = eel_gradient_set_right_color_spec (gradient_spec, color_spec);
1112     }
1113     else if (drop_location_y < top_border && drop_location_y <= bottom_border)
1114     {
1115         new_gradient_spec = eel_gradient_set_top_color_spec (gradient_spec, color_spec);
1116     }
1117     else if (drop_location_y >= top_border && drop_location_y > bottom_border)
1118     {
1119         new_gradient_spec = eel_gradient_set_bottom_color_spec (gradient_spec, color_spec);
1120     }
1121     else
1122     {
1123         new_gradient_spec = g_strdup (color_spec);
1124     }
1125 
1126     g_free (color_spec);
1127     g_free (gradient_spec);
1128 
1129     eel_bg_set_image_uri_and_color (self, action, NULL, new_gradient_spec);
1130 
1131     g_free (new_gradient_spec);
1132 }
1133 
1134 EelBackground *
eel_background_new(void)1135 eel_background_new (void)
1136 {
1137     return EEL_BACKGROUND (g_object_new (EEL_TYPE_BACKGROUND, NULL));
1138 }
1139 
1140 
1141 /*
1142  * self check code
1143  */
1144 
1145 #if !defined (EEL_OMIT_SELF_CHECK)
1146 
1147 void
eel_self_check_background(void)1148 eel_self_check_background (void)
1149 {
1150     EelBackground *self = eel_background_new ();
1151 
1152     eel_background_set_color (self, NULL);
1153     eel_background_set_color (self, "");
1154     eel_background_set_color (self, "red");
1155     eel_background_set_color (self, "red-blue");
1156     eel_background_set_color (self, "red-blue:h");
1157 
1158     g_object_ref_sink (self);
1159     g_object_unref (self);
1160 }
1161 
1162 #endif
1163