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