Blob Blame History Raw
============================================================
 Emit "transitioned" not "changed" for new frames

  Previously, "changed" would get emitted when switching
backgrounds AND when switching slides in an animated
background.  The two actions are conceptually different,
so this commit splits the signal into two signals. This
will allow us to add a cross fade effect when switching
backgrounds (and not add the cross fade effect when
switching slides that have their own transition effect)

diff --git a/libgnome-desktop/gnome-bg.c b/libgnome-desktop/gnome-bg.c
--- a/libgnome-desktop/gnome-bg.c
+++ b/libgnome-desktop/gnome-bg.c
@@ -98,7 +98,8 @@ struct _GnomeBG
 	GFileMonitor *		file_monitor;
 
 	guint                   changed_id;
-	
+	guint                   transitioned_id;
+
 	/* Cached information, only access through cache accessor functions */
         SlideShow *		slideshow;
 	time_t			file_mtime;
@@ -115,6 +116,7 @@ struct _GnomeBGClass
 
 enum {
 	CHANGED,
+	TRANSITIONED,
 	N_SIGNALS
 };
 
@@ -275,6 +277,30 @@ queue_changed (GnomeBG *bg)
 					     NULL);
 }
 
+static gboolean
+do_transitioned (GnomeBG *bg)
+{
+	bg->transitioned_id = 0;
+
+	g_signal_emit (G_OBJECT (bg), signals[TRANSITIONED], 0);
+
+	return FALSE;
+}
+
+static void
+queue_transitioned (GnomeBG *bg)
+{
+	if (bg->transitioned_id > 0) {
+		g_source_remove (bg->transitioned_id);
+	}
+
+	bg->transitioned_id = g_timeout_add_full (G_PRIORITY_LOW,
+					     100,
+					     (GSourceFunc)do_transitioned,
+					     bg,
+					     NULL);
+}
+
 void
 gnome_bg_load_from_preferences (GnomeBG     *bg,
 				GConfClient *client)
@@ -419,6 +445,14 @@ gnome_bg_class_init (GnomeBGClass *klass)
 					 NULL, NULL,
 					 g_cclosure_marshal_VOID__VOID,
 					 G_TYPE_NONE, 0);
+
+	signals[TRANSITIONED] = g_signal_new ("transitioned",
+					 G_OBJECT_CLASS_TYPE (object_class),
+					 G_SIGNAL_RUN_LAST,
+					 0,
+					 NULL, NULL,
+					 g_cclosure_marshal_VOID__VOID,
+					 G_TYPE_NONE, 0);
 }
 
 GnomeBG *
@@ -1352,7 +1386,7 @@ on_timeout (gpointer data)
 
 	bg->timeout_id = 0;
 	
-	queue_changed (bg);
+	queue_transitioned (bg);
 
 	return FALSE;
 }

============================================================
 Use gdk functions to grab server and flush client

  It looks a little nicer this way.

diff --git a/libgnome-desktop/gnome-bg.c b/libgnome-desktop/gnome-bg.c
--- a/libgnome-desktop/gnome-bg.c
+++ b/libgnome-desktop/gnome-bg.c
@@ -1056,7 +1056,7 @@ gnome_bg_create_thumbnail (GnomeBG               *bg,
 
 
 /* Set the root pixmap, and properties pointing to it. We
- * do this atomically with XGrabServer to make sure that
+ * do this atomically with a server grab to make sure that
  * we won't leak the pixmap if somebody else it setting
  * it at the same time. (This assumes that they follow the
  * same conventions we do)
@@ -1082,7 +1082,7 @@ gnome_bg_set_pixmap_as_root (GdkScreen *screen, GdkPixmap *pixmap)
 	data_esetroot = NULL;
 	display = GDK_DISPLAY_XDISPLAY (gdk_screen_get_display (screen));
 	
-	XGrabServer (display);
+	gdk_x11_display_grab (gdk_screen_get_display (screen));
 	
 	result = XGetWindowProperty (
 		display, RootWindow (display, screen_num),
@@ -1117,10 +1117,9 @@ gnome_bg_set_pixmap_as_root (GdkScreen *screen, GdkPixmap *pixmap)
 	XSetWindowBackgroundPixmap (display, RootWindow (display, screen_num),
 				    pixmap_id);
 	XClearWindow (display, RootWindow (display, screen_num));
-	
-	XUngrabServer (display);
-	
-	XFlush (display);
+
+	gdk_display_flush (gdk_screen_get_display (screen));
+	gdk_x11_display_ungrab (gdk_screen_get_display (screen));
 }
 
 

============================================================
 Move part of set_pixmap_as_root to set_root_pixmap_id

  The meatiest part of set_pixmap_as_root takes the passed
in pixmap and stores it on the root window in the
ESETROOT_PMAP_ID and _XROOTPMAP_ID properties.  That
functionality stands on its own, and should be factored
out so it can get reused later when adding crossfade
transitions on background changes.

diff --git a/libgnome-desktop/gnome-bg.c b/libgnome-desktop/gnome-bg.c
--- a/libgnome-desktop/gnome-bg.c
+++ b/libgnome-desktop/gnome-bg.c
@@ -1054,15 +1054,9 @@ gnome_bg_create_thumbnail (GnomeBG               *bg,
 	return result;
 }
 
-
-/* Set the root pixmap, and properties pointing to it. We
- * do this atomically with a server grab to make sure that
- * we won't leak the pixmap if somebody else it setting
- * it at the same time. (This assumes that they follow the
- * same conventions we do)
- */
-void 
-gnome_bg_set_pixmap_as_root (GdkScreen *screen, GdkPixmap *pixmap)
+static void
+gnome_bg_set_root_pixmap_id (GdkScreen *screen,
+			     GdkPixmap *pixmap)
 {
 	int      result;
 	gint     format;
@@ -1073,24 +1067,20 @@ gnome_bg_set_pixmap_as_root (GdkScreen *screen, GdkPixmap *pixmap)
 	Atom     type;
 	Display *display;
 	int      screen_num;
-	
-	g_return_if_fail (screen != NULL);
-	g_return_if_fail (pixmap != NULL);
-	
+
 	screen_num = gdk_screen_get_number (screen);
-	
 	data_esetroot = NULL;
+
 	display = GDK_DISPLAY_XDISPLAY (gdk_screen_get_display (screen));
-	
-	gdk_x11_display_grab (gdk_screen_get_display (screen));
-	
-	result = XGetWindowProperty (
-		display, RootWindow (display, screen_num),
-		gdk_x11_get_xatom_by_name ("ESETROOT_PMAP_ID"),
-		0L, 1L, False, XA_PIXMAP,
-		&type, &format, &nitems, &bytes_after,
-		&data_esetroot);
-	
+
+	result = XGetWindowProperty (display,
+				     RootWindow (display, screen_num),
+				     gdk_x11_get_xatom_by_name ("ESETROOT_PMAP_ID"),
+				     0L, 1L, False, XA_PIXMAP,
+				     &type, &format, &nitems,
+				     &bytes_after,
+				     &data_esetroot);
+
 	if (data_esetroot != NULL) {
 		if (result == Success && type == XA_PIXMAP &&
 		    format == 32 &&
@@ -1113,9 +1103,33 @@ gnome_bg_set_pixmap_as_root (GdkScreen *screen, GdkPixmap *pixmap)
 			 gdk_x11_get_xatom_by_name ("_XROOTPMAP_ID"), XA_PIXMAP,
 			 32, PropModeReplace,
 			 (guchar *) &pixmap_id, 1);
-	
+}
+
+/* Set the root pixmap, and properties pointing to it. We
+ * do this atomically with a server grab to make sure that
+ * we won't leak the pixmap if somebody else it setting
+ * it at the same time. (This assumes that they follow the
+ * same conventions we do)
+ */
+void
+gnome_bg_set_pixmap_as_root (GdkScreen *screen, GdkPixmap *pixmap)
+{
+	Display *display;
+	int      screen_num;
+
+	g_return_if_fail (screen != NULL);
+	g_return_if_fail (pixmap != NULL);
+
+	screen_num = gdk_screen_get_number (screen);
+
+	display = GDK_DISPLAY_XDISPLAY (gdk_screen_get_display (screen));
+
+	gdk_x11_display_grab (gdk_screen_get_display (screen));
+
+	gnome_bg_set_root_pixmap_id (screen, pixmap);
+
 	XSetWindowBackgroundPixmap (display, RootWindow (display, screen_num),
-				    pixmap_id);
+				    GDK_PIXMAP_XID (pixmap));
 	XClearWindow (display, RootWindow (display, screen_num));
 
 	gdk_display_flush (gdk_screen_get_display (screen));

============================================================
 Add Crossfade class

  This adds a helper class to manage doing crossfades on a window.
It will be leveraged by gnome_bg and nautilus to do a fade
transition when changing backgrounds on the desktop or in nautilus
windows.

diff --git a/libgnome-desktop/Makefile.am b/libgnome-desktop/Makefile.am
--- a/libgnome-desktop/Makefile.am
+++ b/libgnome-desktop/Makefile.am
@@ -22,6 +22,7 @@ libgnome_desktop_2_la_SOURCES = \
 	gnome-desktop-thumbnail.c \
 	gnome-thumbnail-pixbuf-utils.c \
 	gnome-bg.c		\
+	gnome-bg-crossfade.c	\
 	display-name.c		\
 	gnome-rr.c		\
 	gnome-rr-config.c	\
diff --git a/libgnome-desktop/gnome-bg-crossfade.c b/libgnome-desktop/gnome-bg-crossfade.c
new file mode 100644
--- /dev/null
+++ b/libgnome-desktop/gnome-bg-crossfade.c
@@ -0,0 +1,540 @@
+/* gnome-bg-crossfade.h - fade window background between two pixmaps
+ *
+ * Copyright (C) 2008 Red Hat, Inc.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public License
+ * as published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ *
+ * Author: Ray Strode <rstrode@redhat.com>
+*/
+
+#include <string.h>
+#include <math.h>
+#include <stdarg.h>
+
+#include <gio/gio.h>
+
+#include <gdk/gdk.h>
+#include <gdk/gdkx.h>
+#include <X11/Xlib.h>
+#include <X11/Xatom.h>
+#include <gtk/gtk.h>
+
+#include <cairo.h>
+#include <cairo-xlib.h>
+
+#define GNOME_DESKTOP_USE_UNSTABLE_API
+#include <libgnomeui/gnome-bg.h>
+#include "libgnomeui/gnome-bg-crossfade.h"
+
+struct _GnomeBGCrossfadePrivate
+{
+	GdkWindow *window;
+	int        width;
+	int        height;
+	GdkPixmap *fading_pixmap;
+	GdkPixmap *end_pixmap;
+	gdouble    start_time;
+	gdouble    total_duration;
+	guint      timeout_id;
+	guint      is_first_frame : 1;
+};
+
+enum {
+	PROP_0,
+	PROP_WIDTH,
+	PROP_HEIGHT,
+};
+
+enum {
+	FINISHED,
+	NUMBER_OF_SIGNALS
+};
+
+static guint signals[NUMBER_OF_SIGNALS] = { 0 };
+
+G_DEFINE_TYPE (GnomeBGCrossfade, gnome_bg_crossfade, G_TYPE_OBJECT)
+#define GNOME_BG_CROSSFADE_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o),\
+			                   GNOME_TYPE_BG_CROSSFADE,\
+			                   GnomeBGCrossfadePrivate))
+
+static void
+gnome_bg_crossfade_set_property (GObject      *object,
+				 guint         property_id,
+				 const GValue *value,
+				 GParamSpec   *pspec)
+{
+	GnomeBGCrossfade *fade;
+
+	g_assert (GNOME_IS_BG_CROSSFADE (object));
+
+	fade = GNOME_BG_CROSSFADE (object);
+
+	switch (property_id)
+	{
+	case PROP_WIDTH:
+		fade->priv->width = g_value_get_int (value);
+		break;
+	case PROP_HEIGHT:
+		fade->priv->height = g_value_get_int (value);
+		break;
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+		break;
+	}
+}
+
+static void
+gnome_bg_crossfade_get_property (GObject    *object,
+			     guint       property_id,
+			     GValue     *value,
+			     GParamSpec *pspec)
+{
+	GnomeBGCrossfade *fade;
+
+	g_assert (GNOME_IS_BG_CROSSFADE (object));
+
+	fade = GNOME_BG_CROSSFADE (object);
+
+	switch (property_id)
+	{
+	case PROP_WIDTH:
+		g_value_set_int (value, fade->priv->width);
+		break;
+	case PROP_HEIGHT:
+		g_value_set_int (value, fade->priv->height);
+		break;
+
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+		break;
+	}
+}
+
+static void
+gnome_bg_crossfade_finalize (GObject *object)
+{
+	GnomeBGCrossfade *fade;
+
+	fade = GNOME_BG_CROSSFADE (object);
+
+	gnome_bg_crossfade_stop (fade);
+
+	if (fade->priv->fading_pixmap != NULL) {
+		g_object_unref (fade->priv->fading_pixmap);
+		fade->priv->fading_pixmap = NULL;
+	}
+
+	if (fade->priv->end_pixmap != NULL) {
+		g_object_unref (fade->priv->end_pixmap);
+		fade->priv->end_pixmap = NULL;
+	}
+}
+
+static void
+gnome_bg_crossfade_class_init (GnomeBGCrossfadeClass *fade_class)
+{
+	GObjectClass *gobject_class;
+
+	gobject_class = G_OBJECT_CLASS (fade_class);
+
+	gobject_class->get_property = gnome_bg_crossfade_get_property;
+	gobject_class->set_property = gnome_bg_crossfade_set_property;
+	gobject_class->finalize = gnome_bg_crossfade_finalize;
+
+	/**
+	 * GnomeBGCrossfade:width:
+	 *
+	 * When a crossfade is running, this is width of the fading
+	 * pixmap.
+	 */
+	g_object_class_install_property (gobject_class,
+					 PROP_WIDTH,
+					 g_param_spec_int ("width",
+						           "Window Width",
+							    "Width of window to fade",
+							    0, G_MAXINT, 0,
+							    G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE));
+
+	/**
+	 * GnomeBGCrossfade:height:
+	 *
+	 * When a crossfade is running, this is height of the fading
+	 * pixmap.
+	 */
+	g_object_class_install_property (gobject_class,
+					 PROP_HEIGHT,
+					 g_param_spec_int ("height", "Window Height",
+						           "Height of window to fade on",
+							   0, G_MAXINT, 0,
+							   G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE));
+
+	/**
+	 * GnomeBGCrossfade::finished:
+	 * @fade: the #GnomeBGCrossfade that received the signal
+	 * @window: the #GdkWindow the crossfade happend on.
+	 *
+	 * When a crossfade finishes, @window will have a copy
+	 * of the end pixmap as its background, and this signal will
+	 * get emitted.
+	 */
+	signals[FINISHED] = g_signal_new ("finished",
+					  G_OBJECT_CLASS_TYPE (gobject_class),
+					  G_SIGNAL_RUN_LAST, 0, NULL, NULL,
+					  g_cclosure_marshal_VOID__OBJECT,
+					  G_TYPE_NONE, 1, G_TYPE_OBJECT);
+
+	g_type_class_add_private (gobject_class, sizeof (GnomeBGCrossfadePrivate));
+}
+
+static void
+gnome_bg_crossfade_init (GnomeBGCrossfade *fade)
+{
+	fade->priv = GNOME_BG_CROSSFADE_GET_PRIVATE (fade);
+
+	fade->priv->fading_pixmap = NULL;
+	fade->priv->end_pixmap = NULL;
+	fade->priv->timeout_id = 0;
+}
+
+/**
+ * gnome_bg_crossfade_new:
+ * @width: The width of the crossfading window
+ * @height: The height of the crossfading window
+ *
+ * Creates a new object to manage crossfading a
+ * window background between two #GdkPixmap drawables.
+ *
+ * Return value: the new #GnomeBGCrossfade
+ **/
+GnomeBGCrossfade *
+gnome_bg_crossfade_new (int width,
+			int height)
+{
+	GObject *object;
+
+	object = g_object_new (GNOME_TYPE_BG_CROSSFADE,
+			       "width", width,
+			       "height", height, NULL);
+
+	return (GnomeBGCrossfade *) object;
+}
+
+static GdkPixmap *
+tile_pixmap (GdkPixmap *pixmap,
+	     int        width,
+	     int        height)
+{
+	GdkPixmap *copy;
+	cairo_t *cr;
+
+	copy = gdk_pixmap_new (pixmap, width, height, pixmap == NULL? 24 : -1);
+
+	cr = gdk_cairo_create (copy);
+
+	if (pixmap != NULL) {
+		cairo_pattern_t *pattern;
+		gdk_cairo_set_source_pixmap (cr, pixmap, 0.0, 0.0);
+		pattern = cairo_get_source (cr);
+		cairo_pattern_set_extend (pattern, CAIRO_EXTEND_REPEAT);
+	} else {
+		GtkStyle *style;
+		style = gtk_widget_get_default_style ();
+		gdk_cairo_set_source_color (cr, &style->bg[GTK_STATE_NORMAL]);
+	}
+
+	cairo_paint (cr);
+
+	if (cairo_status (cr) != CAIRO_STATUS_SUCCESS) {
+		g_object_unref (copy);
+		copy = NULL;
+	}
+	cairo_destroy (cr);
+
+	return copy;
+}
+
+/**
+ * gnome_bg_crossfade_set_start_pixmap:
+ * @fade: a #GnomeBGCrossfade
+ * @pixmap: The #GdkPixmap to fade from
+ *
+ * Before initiating a crossfade with gnome_bg_crossfade_start()
+ * a start and end pixmap have to be set.  This function sets
+ * the pixmap shown at the beginning of the crossfade effect.
+ *
+ * Return value: %TRUE if successful, or %FALSE if the pixmap
+ * could not be copied.
+ **/
+gboolean
+gnome_bg_crossfade_set_start_pixmap (GnomeBGCrossfade *fade,
+				     GdkPixmap        *pixmap)
+{
+	g_return_val_if_fail (GNOME_IS_BG_CROSSFADE (fade), FALSE);
+
+	if (fade->priv->fading_pixmap != NULL) {
+		g_object_unref (fade->priv->fading_pixmap);
+		fade->priv->fading_pixmap = NULL;
+	}
+
+	fade->priv->fading_pixmap = tile_pixmap (pixmap,
+						 fade->priv->width,
+						 fade->priv->height);
+
+	return fade->priv->fading_pixmap != NULL;
+}
+
+/**
+ * gnome_bg_crossfade_set_end_pixmap:
+ * @fade: a #GnomeBGCrossfade
+ * @pixmap: The #GdkPixmap to fade to
+ *
+ * Before initiating a crossfade with gnome_bg_crossfade_start()
+ * a start and end pixmap have to be set.  This function sets
+ * the pixmap shown at the end of the crossfade effect.
+ *
+ * Return value: %TRUE if successful, or %FALSE if the pixmap
+ * could not be copied.
+ **/
+gboolean
+gnome_bg_crossfade_set_end_pixmap (GnomeBGCrossfade *fade,
+				   GdkPixmap        *pixmap)
+{
+	g_return_val_if_fail (GNOME_IS_BG_CROSSFADE (fade), FALSE);
+
+	if (fade->priv->end_pixmap != NULL) {
+		g_object_unref (fade->priv->end_pixmap);
+		fade->priv->end_pixmap = NULL;
+	}
+
+	fade->priv->end_pixmap = tile_pixmap (pixmap,
+					      fade->priv->width,
+					      fade->priv->height);
+
+	return fade->priv->end_pixmap != NULL;
+}
+
+static gdouble
+get_current_time (void)
+{
+	const double microseconds_per_second = (double) G_USEC_PER_SEC;
+	double timestamp;
+	GTimeVal now;
+
+	g_get_current_time (&now);
+
+	timestamp = ((microseconds_per_second * now.tv_sec) + now.tv_usec) /
+	            microseconds_per_second;
+
+	return timestamp;
+}
+
+static gboolean
+animations_are_disabled (GnomeBGCrossfade *fade)
+{
+	GtkSettings *settings;
+	GdkScreen *screen;
+	gboolean are_enabled;
+
+	g_assert (fade->priv->window != NULL);
+
+	screen = gdk_drawable_get_screen (fade->priv->window);
+
+	settings = gtk_settings_get_for_screen (screen);
+
+	g_object_get (settings, "gtk-enable-animations", &are_enabled, NULL);
+
+	return !are_enabled;
+}
+
+static void
+draw_background (GnomeBGCrossfade *fade)
+{
+	if (GDK_WINDOW_TYPE (fade->priv->window) == GDK_WINDOW_FOREIGN ||
+	    GDK_WINDOW_TYPE (fade->priv->window) == GDK_WINDOW_ROOT) {
+		GdkDisplay *display;
+		display = gdk_drawable_get_display (fade->priv->window);
+		gdk_window_clear (fade->priv->window);
+		gdk_flush ();
+	} else {
+		gdk_window_invalidate_rect (fade->priv->window, NULL, FALSE);
+		gdk_window_process_updates (fade->priv->window, FALSE);
+	}
+}
+
+static gboolean
+on_tick (GnomeBGCrossfade *fade)
+{
+	gdouble now, percent_done;
+	cairo_t *cr;
+	cairo_status_t status;
+
+	g_return_val_if_fail (GNOME_IS_BG_CROSSFADE (fade), FALSE);
+
+	now = get_current_time ();
+
+	percent_done = (now - fade->priv->start_time) / fade->priv->total_duration;
+	percent_done = CLAMP (percent_done, 0.0, 1.0);
+
+	/* If it's taking a long time to get to the first frame,
+	 * then lengthen the duration, so the user will get to see
+	 * the effect.
+	 */
+	if (fade->priv->is_first_frame && percent_done > .33) {
+		fade->priv->is_first_frame = FALSE;
+		fade->priv->total_duration *= 1.5;
+		return on_tick (fade);
+	}
+
+	if (fade->priv->fading_pixmap == NULL) {
+		return FALSE;
+	}
+
+	if (animations_are_disabled (fade)) {
+		return FALSE;
+	}
+
+	/* We accumulate the results in place for performance reasons.
+	 *
+	 * This means 1) The fade is exponential, not linear (looks good!)
+	 * 2) The rate of fade is not independent of frame rate. Slower machines
+	 * will get a slower fade (but never longer than .75 seconds), and
+	 * even the fastest machines will get *some* fade because the framerate
+	 * is capped.
+	 */
+	cr = gdk_cairo_create (fade->priv->fading_pixmap);
+
+	gdk_cairo_set_source_pixmap (cr, fade->priv->end_pixmap,
+				     0.0, 0.0);
+	cairo_paint_with_alpha (cr, percent_done);
+
+	status = cairo_status (cr);
+	cairo_destroy (cr);
+
+	if (status == CAIRO_STATUS_SUCCESS) {
+		draw_background (fade);
+	}
+	return percent_done <= .99;
+}
+
+static void
+on_finished (GnomeBGCrossfade *fade)
+{
+	if (fade->priv->timeout_id == 0)
+		return;
+
+	g_assert (fade->priv->end_pixmap != NULL);
+
+	gdk_window_set_back_pixmap (fade->priv->window,
+				    fade->priv->end_pixmap,
+				    FALSE);
+	draw_background (fade);
+
+	g_object_unref (fade->priv->end_pixmap);
+	fade->priv->end_pixmap = NULL;
+
+	g_assert (fade->priv->fading_pixmap != NULL);
+
+	g_object_unref (fade->priv->fading_pixmap);
+	fade->priv->fading_pixmap = NULL;
+
+	fade->priv->timeout_id = 0;
+	g_signal_emit (fade, signals[FINISHED], 0, fade->priv->window);
+}
+
+/**
+ * gnome_bg_crossfade_start:
+ * @fade: a #GnomeBGCrossfade
+ * @window: The #GdkWindow to draw crossfade on
+ * @context: a #GMainContext to handle animation timeouts
+ *           or %NULL for default context
+ *
+ * This function initiates a quick crossfade between two pixmaps on
+ * the background of @window.  Before initiating the crossfade both
+ * gnome_bg_crossfade_start() and gnome_bg_crossfade_end() need to
+ * be called. If animations are disabled, the crossfade is skipped,
+ * and the window background is set immediately to the end pixmap.
+ **/
+void
+gnome_bg_crossfade_start (GnomeBGCrossfade *fade,
+			  GdkWindow        *window,
+			  GMainContext     *context)
+{
+	GSource *source;
+
+	g_return_if_fail (GNOME_IS_BG_CROSSFADE (fade));
+	g_return_if_fail (window != NULL);
+	g_return_if_fail (fade->priv->fading_pixmap != NULL);
+	g_return_if_fail (fade->priv->end_pixmap != NULL);
+	g_return_if_fail (!gnome_bg_crossfade_is_started (fade));
+
+	source = g_timeout_source_new (1000 / 60.0);
+	g_source_set_callback (source,
+			       (GSourceFunc) on_tick,
+			       fade,
+			       (GDestroyNotify) on_finished);
+	fade->priv->timeout_id = g_source_attach (source, context);
+	g_source_unref (source);
+
+	fade->priv->window = window;
+	gdk_window_set_back_pixmap (fade->priv->window,
+				    fade->priv->fading_pixmap,
+				    FALSE);
+	draw_background (fade);
+
+	fade->priv->is_first_frame = TRUE;
+	fade->priv->total_duration = .75;
+	fade->priv->start_time = get_current_time ();
+}
+
+
+/**
+ * gnome_bg_crossfade_is_started:
+ * @fade: a #GnomeBGCrossfade
+ *
+ * This function reveals whether or not @fade is currently
+ * running on a window.  See gnome_bg_crossfade_start() for
+ * information on how to initiate a crossfade.
+ *
+ * Return value: %TRUE if fading, or %FALSE if not fading
+ **/
+gboolean
+gnome_bg_crossfade_is_started (GnomeBGCrossfade *fade)
+{
+	g_return_val_if_fail (GNOME_IS_BG_CROSSFADE (fade), FALSE);
+
+	return fade->priv->timeout_id != 0;
+}
+
+/**
+ * gnome_bg_crossfade_stop:
+ * @fade: a #GnomeBGCrossfade
+ *
+ * This function stops any in progress crossfades that may be
+ * happening.  It's harmless to call this function if @fade is
+ * already stopped.
+ **/
+void
+gnome_bg_crossfade_stop (GnomeBGCrossfade *fade)
+{
+	g_return_if_fail (GNOME_IS_BG_CROSSFADE (fade));
+
+	if (!gnome_bg_crossfade_is_started (fade))
+		return;
+
+	g_assert (fade->priv->timeout_id != 0);
+	g_source_remove (fade->priv->timeout_id);
+	fade->priv->timeout_id = 0;
+}
diff --git a/libgnome-desktop/libgnomeui/Makefile.am b/libgnome-desktop/libgnomeui/Makefile.am
--- a/libgnome-desktop/libgnomeui/Makefile.am
+++ b/libgnome-desktop/libgnomeui/Makefile.am
@@ -1,6 +1,7 @@
 libgnomeui_desktopdir = $(includedir)/gnome-desktop-2.0/libgnomeui
 libgnomeui_desktop_HEADERS =	\
 	gnome-bg.h		\
+	gnome-bg-crossfade.h	\
 	gnome-desktop-thumbnail.h \
 	gnome-rr.h		\
 	gnome-rr-config.h	\
diff --git a/libgnome-desktop/libgnomeui/gnome-bg-crossfade.h b/libgnome-desktop/libgnomeui/gnome-bg-crossfade.h
new file mode 100644
--- /dev/null
+++ b/libgnome-desktop/libgnomeui/gnome-bg-crossfade.h
@@ -0,0 +1,75 @@
+/* gnome-bg-crossfade.h - fade window background between two pixmaps
+
+   Copyright 2008, Red Hat, Inc.
+
+   This file is part of the Gnome Library.
+
+   The Gnome Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Library General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   The Gnome Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Library General Public License for more details.
+
+   You should have received a copy of the GNU Library General Public
+   License along with the Gnome Library; see the file COPYING.LIB.  If not,
+   write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+   Boston, MA 02111-1307, USA.
+
+   Author: Ray Strode <rstrode@redhat.com>
+*/
+
+#ifndef __GNOME_BG_CROSSFADE_H__
+#define __GNOME_BG_CROSSFADE_H__
+
+#ifndef GNOME_DESKTOP_USE_UNSTABLE_API
+#error    GnomeBGCrossfade is unstable API. You must define GNOME_DESKTOP_USE_UNSTABLE_API before including gnome-bg-crossfade.h
+#endif
+
+#include <gdk/gdk.h>
+
+G_BEGIN_DECLS
+
+#define GNOME_TYPE_BG_CROSSFADE            (gnome_bg_crossfade_get_type ())
+#define GNOME_BG_CROSSFADE(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), GNOME_TYPE_BG_CROSSFADE, GnomeBGCrossfade))
+#define GNOME_BG_CROSSFADE_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass),  GNOME_TYPE_BG_CROSSFADE, GnomeBGCrossfadeClass))
+#define GNOME_IS_BG_CROSSFADE(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GNOME_TYPE_BG_CROSSFADE))
+#define GNOME_IS_BG_CROSSFADE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass),  GNOME_TYPE_BG_CROSSFADE))
+#define GNOME_BG_CROSSFADE_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj),  GNOME_TYPE_BG_CROSSFADE, GnomeBGCrossfadeClass))
+
+typedef struct _GnomeBGCrossfadePrivate GnomeBGCrossfadePrivate;
+typedef struct _GnomeBGCrossfade GnomeBGCrossfade;
+typedef struct _GnomeBGCrossfadeClass GnomeBGCrossfadeClass;
+
+struct _GnomeBGCrossfade
+{
+	GObject parent_object;
+
+	GnomeBGCrossfadePrivate *priv;
+};
+
+struct _GnomeBGCrossfadeClass
+{
+	GObjectClass parent_class;
+
+	void (* finished) (GnomeBGCrossfade *fade, GdkWindow *window);
+};
+
+GType             gnome_bg_crossfade_get_type              (void);
+GnomeBGCrossfade *gnome_bg_crossfade_new (int width, int height);
+gboolean          gnome_bg_crossfade_set_start_pixmap (GnomeBGCrossfade *fade,
+                                                      GdkPixmap *pixmap);
+gboolean          gnome_bg_crossfade_set_end_pixmap (GnomeBGCrossfade *fade,
+                                                    GdkPixmap *pixmap);
+void              gnome_bg_crossfade_start (GnomeBGCrossfade *fade,
+                                            GdkWindow        *window,
+                                            GMainContext     *context);
+gboolean          gnome_bg_crossfade_is_started (GnomeBGCrossfade *fade);
+void              gnome_bg_crossfade_stop (GnomeBGCrossfade *fade);
+
+G_END_DECLS
+
+#endif

============================================================
 Add docstring for gnome_bg_set_pixmap_as_root

  I'm going to be adding a parallel api that references
this function in the docs, so we should document this
one as well.

diff --git a/libgnome-desktop/gnome-bg.c b/libgnome-desktop/gnome-bg.c
--- a/libgnome-desktop/gnome-bg.c
+++ b/libgnome-desktop/gnome-bg.c
@@ -1105,12 +1105,18 @@ gnome_bg_set_root_pixmap_id (GdkScreen *screen,
 			 (guchar *) &pixmap_id, 1);
 }
 
-/* Set the root pixmap, and properties pointing to it. We
+/**
+ * gnome_bg_set_pixmap_as_root:
+ * @screen: the #GdkScreen to change root background on
+ * @pixmap: the #GdkPixmap to set root background from
+ *
+ * Set the root pixmap, and properties pointing to it. We
  * do this atomically with a server grab to make sure that
  * we won't leak the pixmap if somebody else it setting
  * it at the same time. (This assumes that they follow the
- * same conventions we do)
- */
+ * same conventions we do).  @pixmap should come from a call
+ * to gnome_bg_create_pixmap().
+ **/
 void
 gnome_bg_set_pixmap_as_root (GdkScreen *screen, GdkPixmap *pixmap)
 {

============================================================
 Add gnome_bg_set_pixmap_as_root_with_crossfade

  This implements a crossfade transition when switching
backgrounds.  To actually get the fade, though, requires
changes to gnome-settings-daemon to use this function
instead of gnome_bg_set_pixmap_as_root.

We also expose gnome_bg_get_pixmap_from_root which will
be useful for eel when it gets changed to do fading.

diff --git a/libgnome-desktop/gnome-bg.c b/libgnome-desktop/gnome-bg.c
--- a/libgnome-desktop/gnome-bg.c
+++ b/libgnome-desktop/gnome-bg.c
@@ -38,10 +38,13 @@ Author: Soren Sandmann <sandmann@redhat.com>
 #include <X11/Xlib.h>
 #include <X11/Xatom.h>
 
+#include <cairo.h>
+
 #include <gconf/gconf-client.h>
 
 #define GNOME_DESKTOP_USE_UNSTABLE_API
 #include <libgnomeui/gnome-bg.h>
+#include <libgnomeui/gnome-bg-crossfade.h>
 
 #define BG_KEY_DRAW_BACKGROUND    GNOME_BG_KEY_DIR "/draw_background"
 #define BG_KEY_PRIMARY_COLOR      GNOME_BG_KEY_DIR "/primary_color"
@@ -1054,6 +1057,91 @@ gnome_bg_create_thumbnail (GnomeBG               *bg,
 	return result;
 }
 
+/**
+ * gnome_bg_get_pixmap_from_root:
+ * @screen: a #GdkScreen
+ *
+ * This function queries the _XROOTPMAP_ID property from
+ * the root window associated with @screen to determine
+ * the current root window background pixmap and returns
+ * a copy of it. If the _XROOTPMAP_ID is not set, then
+ * a black pixmap is returned.
+ *
+ * Return value: a #GdkPixmap if successful or %NULL
+ **/
+GdkPixmap *
+gnome_bg_get_pixmap_from_root (GdkScreen *screen)
+{
+	int result;
+	gint format;
+	gulong nitems;
+	gulong bytes_after;
+	guchar *data;
+	Atom type;
+	Display *display;
+	int screen_num;
+	GdkPixmap *pixmap;
+	GdkPixmap *source_pixmap;
+	int width, height;
+	cairo_t *cr;
+	cairo_pattern_t *pattern;
+
+	display = GDK_DISPLAY_XDISPLAY (gdk_screen_get_display (screen));
+	screen_num = gdk_screen_get_number (screen);
+
+	result = XGetWindowProperty (display,
+				     RootWindow (display, screen_num),
+				     gdk_x11_get_xatom_by_name ("_XROOTPMAP_ID"),
+				     0L, 1L, False, XA_PIXMAP,
+				     &type, &format, &nitems, &bytes_after,
+				     &data);
+	pixmap = NULL;
+	source_pixmap = NULL;
+
+	if (result != Success || type != XA_PIXMAP ||
+	    format != 32 || nitems != 1) {
+		XFree (data);
+		data = NULL;
+	}
+
+	if (data != NULL) {
+		source_pixmap = gdk_pixmap_foreign_new (*(Pixmap *) data);
+		gdk_drawable_set_colormap (source_pixmap,
+					   gdk_screen_get_default_colormap (screen));
+	}
+
+	width = gdk_screen_get_width (screen);
+	height = gdk_screen_get_width (screen);
+
+	pixmap = gdk_pixmap_new (source_pixmap != NULL? source_pixmap :
+				 gdk_screen_get_root_window (screen),
+				 width, height, -1);
+
+	cr = gdk_cairo_create (pixmap);
+	if (source_pixmap != NULL) {
+		gdk_cairo_set_source_pixmap (cr, source_pixmap, 0, 0);
+		pattern = cairo_get_source (cr);
+		cairo_pattern_set_extend (pattern, CAIRO_EXTEND_REPEAT);
+	} else {
+		cairo_set_source_rgb (cr, 0.0, 0.0, 0.0);
+	}
+	cairo_paint (cr);
+
+	if (cairo_status (cr) != CAIRO_STATUS_SUCCESS) {
+		g_object_unref (pixmap);
+		pixmap = NULL;
+	}
+	cairo_destroy (cr);
+
+	if (source_pixmap != NULL)
+		g_object_unref (source_pixmap);
+
+	if (data != NULL)
+		XFree (data);
+
+	return pixmap;
+}
+
 static void
 gnome_bg_set_root_pixmap_id (GdkScreen *screen,
 			     GdkPixmap *pixmap)
@@ -1142,6 +1230,55 @@ gnome_bg_set_pixmap_as_root (GdkScreen *screen, GdkPixmap *pixmap)
 	gdk_x11_display_ungrab (gdk_screen_get_display (screen));
 }
 
+/**
+ * gnome_bg_set_pixmap_as_root_with_crossfade:
+ * @screen: the #GdkScreen to change root background on
+ * @pixmap: the #GdkPixmap to set root background from
+ * @context: a #GMainContext or %NULL
+ *
+ * Set the root pixmap, and properties pointing to it.
+ * This function differs from gnome_bg_set_pixmap_as_root()
+ * in that it adds a subtle crossfade animation from the
+ * current root pixmap to the new one.
+ * same conventions we do).
+ *
+ * Return value: a #GnomeBGCrossfade object
+ **/
+GnomeBGCrossfade *
+gnome_bg_set_pixmap_as_root_with_crossfade (GdkScreen    *screen,
+					    GdkPixmap    *pixmap,
+					    GMainContext *context)
+{
+	GdkDisplay *display;
+	GdkWindow *root_window;
+	GdkPixmap *old_pixmap;
+	int      width, height;
+	GnomeBGCrossfade *fade;
+
+	g_return_val_if_fail (screen != NULL, NULL);
+	g_return_val_if_fail (pixmap != NULL, NULL);
+
+	root_window = gdk_screen_get_root_window (screen);
+
+	width = gdk_screen_get_width (screen);
+	height = gdk_screen_get_height (screen);
+
+	fade = gnome_bg_crossfade_new (width, height);
+
+	display = gdk_screen_get_display (screen);
+	gdk_x11_display_grab (display);
+	old_pixmap = gnome_bg_get_pixmap_from_root (screen);
+	gnome_bg_set_root_pixmap_id (screen, pixmap);
+	gnome_bg_crossfade_set_start_pixmap (fade, old_pixmap);
+	g_object_unref (old_pixmap);
+	gnome_bg_crossfade_set_end_pixmap (fade, pixmap);
+	gdk_display_flush (display);
+	gdk_x11_display_ungrab (display);
+
+	gnome_bg_crossfade_start (fade, root_window, context);
+
+	return fade;
+}
 
 /* Implementation of the pixbuf cache */
 struct _SlideShow
diff --git a/libgnome-desktop/libgnomeui/gnome-bg.h b/libgnome-desktop/libgnomeui/gnome-bg.h
--- a/libgnome-desktop/libgnomeui/gnome-bg.h
+++ b/libgnome-desktop/libgnomeui/gnome-bg.h
@@ -32,6 +32,7 @@
 #include <gdk/gdk.h>
 #include <gconf/gconf-client.h>
 #include <libgnomeui/gnome-desktop-thumbnail.h>
+#include <libgnomeui/gnome-bg-crossfade.h>
 
 G_BEGIN_DECLS
 
@@ -109,6 +110,10 @@ gboolean         gnome_bg_changes_with_size     (GnomeBG               *bg);
 void             gnome_bg_set_pixmap_as_root    (GdkScreen             *screen,
 						 GdkPixmap             *pixmap);
 
+GnomeBGCrossfade *gnome_bg_set_pixmap_as_root_with_crossfade (GdkScreen *screen,
+                                                              GdkPixmap *pixmap,
+                                                              GMainContext *context);
+GdkPixmap *gnome_bg_get_pixmap_from_root (GdkScreen *screen);
 
 G_END_DECLS