/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
 *
 * Copyright (C) 2004-2009 William Jon McCann <mccann@jhu.edu>
 * Copyright (C) 2009      Red Hat, Inc.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 * Authors: William Jon McCann <mccann@jhu.edu>
 *
 */

#include "config.h"

#include <stdlib.h>
#include <stdio.h>
#include <time.h>
#include <errno.h>

#include <string.h>
#include <sys/time.h>
#include <sys/types.h>
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif /* HAVE_UNISTD_H */

#include <gdk/gdkx.h>
#include <gtk/gtk.h>

#include "gs-fade.h"
#include "gs-debug.h"

#define MATE_DESKTOP_USE_UNSTABLE_API

#include "libmate-desktop/mate-rr.h"

/* XFree86 4.x+ Gamma fading */


#ifdef HAVE_XF86VMODE_GAMMA

#include <X11/extensions/xf86vmode.h>

#define XF86_MIN_GAMMA  0.1

#endif /* HAVE_XF86VMODE_GAMMA */

static void     gs_fade_class_init (GSFadeClass *klass);
static void     gs_fade_init       (GSFade      *fade);
static void     gs_fade_finalize   (GObject        *object);

#define GS_FADE_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GS_TYPE_FADE, GSFadePrivate))

struct GSGammaInfo
{
	int              size;
	unsigned short  *r;
	unsigned short  *g;
	unsigned short  *b;
};

struct GSFadeScreenPrivate
{
	int                 fade_type;
	int                 num_ramps;
	/* one per crtc in randr mode */
	struct GSGammaInfo *info;
	/* one per screen in theory */
	MateRRScreen      *rrscreen;
#ifdef HAVE_XF86VMODE_GAMMA
	/* one per screen also */
	XF86VidModeGamma    vmg;
#endif /* HAVE_XF86VMODE_GAMMA */
	gboolean (*fade_setup)           (GSFade *fade,
	                                  int     screen);
	gboolean (*fade_set_alpha_gamma) (GSFade *fade,
	                                  int     screen,
	                                  gdouble alpha);
	void     (*fade_finish)          (GSFade *fade,
	                                  int     screen);
};

struct GSFadePrivate
{
	guint            enabled : 1;
	guint            active : 1;

	guint            timeout;

	guint            step;
	guint            num_steps;
	guint            timer_id;

	gdouble          alpha_per_iter;
	gdouble          current_alpha;

	int              num_screens;

	struct GSFadeScreenPrivate *screen_priv;
};

enum
{
    FADED,
    LAST_SIGNAL
};

enum
{
    FADE_TYPE_NONE,
    FADE_TYPE_GAMMA_NUMBER,
    FADE_TYPE_GAMMA_RAMP,
    FADE_TYPE_XRANDR,
};

static guint         signals [LAST_SIGNAL] = { 0, };

G_DEFINE_TYPE (GSFade, gs_fade, G_TYPE_OBJECT)

static gpointer fade_object = NULL;

#ifdef HAVE_XF86VMODE_GAMMA

/* This is needed because the VidMode extension doesn't work
   on remote displays -- but if the remote display has the extension
   at all, XF86VidModeQueryExtension returns true, and then
   XF86VidModeQueryVersion dies with an X error.
*/

static gboolean error_handler_hit = FALSE;

static int
ignore_all_errors_ehandler (Display     *dpy,
                            XErrorEvent *error)
{
	error_handler_hit = TRUE;

	return 0;
}

static Bool
safe_XF86VidModeQueryVersion (Display *dpy,
                              int     *majP,
                              int     *minP)
{
	Bool          result;
	XErrorHandler old_handler;

	XSync (dpy, False);
	error_handler_hit = FALSE;
	old_handler = XSetErrorHandler (ignore_all_errors_ehandler);

	result = XF86VidModeQueryVersion (dpy, majP, minP);

	XSync (dpy, False);
	XSetErrorHandler (old_handler);
	XSync (dpy, False);

	return (error_handler_hit
	        ? False
	        : result);
}

static gboolean
xf86_whack_gamma (int              screen,
                  struct GSFadeScreenPrivate *screen_priv,
                  float            ratio)
{
	Bool status;
	struct GSGammaInfo *gamma_info;

	gamma_info = screen_priv->info;

	if (!gamma_info)
		return FALSE;

	if (ratio < 0)
	{
		ratio = 0;
	}
	if (ratio > 1)
	{
		ratio = 1;
	}

	if (gamma_info->size == 0)
	{
		/* we only have a gamma number, not a ramp. */

		XF86VidModeGamma g2;

		g2.red   = screen_priv->vmg.red   * ratio;
		g2.green = screen_priv->vmg.green * ratio;
		g2.blue  = screen_priv->vmg.blue  * ratio;

		if (g2.red < XF86_MIN_GAMMA)
		{
			g2.red = XF86_MIN_GAMMA;
		}
		if (g2.green < XF86_MIN_GAMMA)
		{
			g2.green = XF86_MIN_GAMMA;
		}
		if (g2.blue < XF86_MIN_GAMMA)
		{
			g2.blue = XF86_MIN_GAMMA;
		}

#if GTK_CHECK_VERSION (3, 0, 0)
		status = XF86VidModeSetGamma (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), screen, &g2);
#else
		status = XF86VidModeSetGamma (GDK_DISPLAY (), screen, &g2);
#endif
	}
	else
	{

# ifdef HAVE_XF86VMODE_GAMMA_RAMP
		unsigned short *r, *g, *b;
		int i;

		r = g_new0 (unsigned short, gamma_info->size);
		g = g_new0 (unsigned short, gamma_info->size);
		b = g_new0 (unsigned short, gamma_info->size);

		for (i = 0; i < gamma_info->size; i++)
		{
			r[i] = gamma_info->r[i] * ratio;
			g[i] = gamma_info->g[i] * ratio;
			b[i] = gamma_info->b[i] * ratio;
		}

#if GTK_CHECK_VERSION (3, 0, 0)
		status = XF86VidModeSetGammaRamp (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), screen, gamma_info->size, r, g, b);
#else
		status = XF86VidModeSetGammaRamp (GDK_DISPLAY (), screen, gamma_info->size, r, g, b);
#endif

		g_free (r);
		g_free (g);
		g_free (b);

# else  /* !HAVE_XF86VMODE_GAMMA_RAMP */
		abort ();
# endif /* !HAVE_XF86VMODE_GAMMA_RAMP */
	}

	gdk_flush ();

	return status;
}

#endif /* HAVE_XF86VMODE_GAMMA */

/* VidModeExtension version 2.0 or better is needed to do gamma.
   2.0 added gamma values; 2.1 added gamma ramps.
*/
# define XF86_VIDMODE_GAMMA_MIN_MAJOR 2
# define XF86_VIDMODE_GAMMA_MIN_MINOR 0
# define XF86_VIDMODE_GAMMA_RAMP_MIN_MAJOR 2
# define XF86_VIDMODE_GAMMA_RAMP_MIN_MINOR 1


gboolean
gs_fade_get_enabled (GSFade *fade)
{
	g_return_val_if_fail (GS_IS_FADE (fade), FALSE);

	return fade->priv->enabled;
}

void
gs_fade_set_enabled (GSFade  *fade,
                     gboolean enabled)
{
	g_return_if_fail (GS_IS_FADE (fade));

	if (fade->priv->enabled != enabled)
	{
		fade->priv->enabled = enabled;
	}
}

#ifdef HAVE_XF86VMODE_GAMMA
static gboolean
gamma_fade_setup (GSFade *fade, int screen_idx)
{
	gboolean         res;
	struct GSFadeScreenPrivate *screen_priv;

	screen_priv = &fade->priv->screen_priv[screen_idx];

	if (screen_priv->info)
		return TRUE;

# ifndef HAVE_XF86VMODE_GAMMA_RAMP
	if (FADE_TYPE_GAMMA_RAMP == screen_priv->fade_type)
	{
		/* server is newer than client! */
		screen_priv->fade_type = FADE_TYPE_GAMMA_NUMBER;
	}
# endif

# ifdef HAVE_XF86VMODE_GAMMA_RAMP

	screen_priv->info = g_new0(struct GSGammaInfo, 1);
	screen_priv->num_ramps = 1;

	if (FADE_TYPE_GAMMA_RAMP == screen_priv->fade_type)
	{
		/* have ramps */

#if GTK_CHECK_VERSION (3, 0, 0)
		res = XF86VidModeGetGammaRampSize (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), screen_idx, &screen_priv->info->size);
#else
		res = XF86VidModeGetGammaRampSize (GDK_DISPLAY (), screen_idx, &screen_priv->info->size);
#endif
		if (!res || screen_priv->info->size <= 0)
		{
			screen_priv->fade_type = FADE_TYPE_GAMMA_NUMBER;
			goto test_number;
		}

		screen_priv->info->r = g_new0 (unsigned short, screen_priv->info->size);
		screen_priv->info->g = g_new0 (unsigned short, screen_priv->info->size);
		screen_priv->info->b = g_new0 (unsigned short, screen_priv->info->size);

		if (! (screen_priv->info->r && screen_priv->info->g && screen_priv->info->b))
		{
			screen_priv->fade_type = FADE_TYPE_GAMMA_NUMBER;
			goto test_number;
		}

#if GTK_CHECK_VERSION (3, 0, 0)
		res = XF86VidModeGetGammaRamp (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()),
#else
		res = XF86VidModeGetGammaRamp (GDK_DISPLAY (),
#endif
		                               screen_idx,
		                               screen_priv->info->size,
		                               screen_priv->info->r,
		                               screen_priv->info->g,
		                               screen_priv->info->b);
		if (! res)
		{
			screen_priv->fade_type = FADE_TYPE_GAMMA_NUMBER;
			goto test_number;
		}
		gs_debug ("Initialized gamma ramp fade");
	}
# endif /* HAVE_XF86VMODE_GAMMA_RAMP */

test_number:
	if (FADE_TYPE_GAMMA_NUMBER == screen_priv->fade_type)
	{
		/* only have gamma parameter, not ramps. */

#if GTK_CHECK_VERSION (3, 0, 0)
		res = XF86VidModeGetGamma (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), screen_idx, &screen_priv->vmg);
#else
		res = XF86VidModeGetGamma (GDK_DISPLAY (), screen_idx, &screen_priv->vmg);
#endif
		if (! res)
		{
			screen_priv->fade_type = FADE_TYPE_NONE;
			goto test_none;
		}
		gs_debug ("Initialized gamma fade for screen %d: %f %f %f",
		          screen_idx,
		          screen_priv->vmg.red,
		          screen_priv->vmg.green,
		          screen_priv->vmg.blue);
	}

test_none:
	if (FADE_TYPE_NONE == screen_priv->fade_type)
	{
		goto FAIL;
	}

	return TRUE;
FAIL:

	return FALSE;
}
#endif /* HAVE_XF86VMODE_GAMMA */

static void
screen_fade_finish (GSFade *fade, int screen_idx)
{
	struct GSFadeScreenPrivate *screen_priv;
	int i;
	screen_priv = &fade->priv->screen_priv[screen_idx];

	if (!screen_priv->info)
		return;

	for (i = 0; i < screen_priv->num_ramps; i++)
	{
		if (screen_priv->info[i].r)
			g_free (screen_priv->info[i].r);
		if (screen_priv->info[i].g)
			g_free (screen_priv->info[i].g);
		if (screen_priv->info[i].b)
			g_free (screen_priv->info[i].b);
	}

	g_free (screen_priv->info);
	screen_priv->info = NULL;
	screen_priv->num_ramps = 0;
}

#ifdef HAVE_XF86VMODE_GAMMA
static gboolean
gamma_fade_set_alpha_gamma (GSFade *fade,
                            int screen_idx,
                            gdouble alpha)
{
	struct GSFadeScreenPrivate *screen_priv;
	gboolean res;

	screen_priv = &fade->priv->screen_priv[screen_idx];
	res = xf86_whack_gamma (screen_idx, screen_priv, alpha);

	return TRUE;
}
#endif /* HAVE_XF86VMODE_GAMMA */

static void
check_gamma_extension (GSFade *fade, int screen_idx)
{
	struct GSFadeScreenPrivate *screen_priv;
#ifdef HAVE_XF86VMODE_GAMMA
	int      event;
	int      error;
	int      major;
	int      minor;
	gboolean res;
#endif /* HAVE_XF86VMODE_GAMMA */

	screen_priv = &fade->priv->screen_priv[screen_idx];

#ifdef HAVE_XF86VMODE_GAMMA
#if GTK_CHECK_VERSION (3, 0, 0)
	res = XF86VidModeQueryExtension (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), &event, &error);
#else
	res = XF86VidModeQueryExtension (GDK_DISPLAY (), &event, &error);
#endif
	if (! res)
		goto fade_none;

#if GTK_CHECK_VERSION (3, 0, 0)
	res = safe_XF86VidModeQueryVersion (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), &major, &minor);
#else
	res = safe_XF86VidModeQueryVersion (GDK_DISPLAY (), &major, &minor);
#endif
	if (! res)
		goto fade_none;

	if (major < XF86_VIDMODE_GAMMA_MIN_MAJOR ||
	        (major == XF86_VIDMODE_GAMMA_MIN_MAJOR &&
	         minor < XF86_VIDMODE_GAMMA_MIN_MINOR))
		goto fade_none;

	screen_priv->fade_setup = gamma_fade_setup;
	screen_priv->fade_finish = screen_fade_finish;
	screen_priv->fade_set_alpha_gamma = gamma_fade_set_alpha_gamma;

	if (major < XF86_VIDMODE_GAMMA_RAMP_MIN_MAJOR ||
	        (major == XF86_VIDMODE_GAMMA_RAMP_MIN_MAJOR &&
	         minor < XF86_VIDMODE_GAMMA_RAMP_MIN_MINOR))
	{
		screen_priv->fade_type = FADE_TYPE_GAMMA_NUMBER;
		return;
	}

	/* Copacetic */
	screen_priv->fade_type = FADE_TYPE_GAMMA_RAMP;
	return;
fade_none:
#endif
	screen_priv->fade_type = FADE_TYPE_NONE;
}

/* Xrandr support */

static gboolean xrandr_fade_setup (GSFade *fade, int screen_idx)
{
	struct GSFadeScreenPrivate *screen_priv;
	MateRRCrtc *crtc;
	MateRRCrtc **crtcs;
	int crtc_count = 0;
	struct GSGammaInfo *info;
	gboolean res;

	screen_priv = &fade->priv->screen_priv[screen_idx];

	if (screen_priv->info)
		return TRUE;

	/* refresh the screen info */
	mate_rr_screen_refresh (screen_priv->rrscreen, NULL);

	crtcs = mate_rr_screen_list_crtcs (screen_priv->rrscreen);
	while (*crtcs)
	{
		crtc_count++;
		crtcs++;
	};

	screen_priv->info = g_new0 (struct GSGammaInfo, crtc_count);
	screen_priv->num_ramps = crtc_count;

	crtc_count = 0;
	crtcs = mate_rr_screen_list_crtcs (screen_priv->rrscreen);
	while (*crtcs)
	{
		crtc = *crtcs;

		info = &screen_priv->info[crtc_count];

		/* if no mode ignore crtc */
		if (!mate_rr_crtc_get_current_mode (crtc))
		{
			info->size = 0;
			info->r = NULL;
			info->g = NULL;
			info->b = NULL;
		}
		else
		{
			res = mate_rr_crtc_get_gamma (crtc, &info->size,
			                              &info->r, &info->g,
			                              &info->b);
			if (res == FALSE)
				goto fail;
		}

		crtcs++;
		crtc_count++;
	}
	return TRUE;
fail:
	return FALSE;
}

static void xrandr_crtc_whack_gamma (MateRRCrtc *crtc,
                                     struct GSGammaInfo *gamma_info,
                                     float            ratio)
{
	unsigned short *r, *g, *b;
	int i;

	if (gamma_info->size == 0)
		return;

	if (ratio < 0)
	{
		ratio = 0;
	}
	if (ratio > 1)
	{
		ratio = 1;
	}

	r = g_new0 (unsigned short, gamma_info->size);
	g = g_new0 (unsigned short, gamma_info->size);
	b = g_new0 (unsigned short, gamma_info->size);

	for (i = 0; i < gamma_info->size; i++)
	{
		r[i] = gamma_info->r[i] * ratio;
		g[i] = gamma_info->g[i] * ratio;
		b[i] = gamma_info->b[i] * ratio;
	}

	mate_rr_crtc_set_gamma (crtc, gamma_info->size,
	                        r, g, b);
	g_free (r);
	g_free (g);
	g_free (b);
}

static gboolean xrandr_fade_set_alpha_gamma (GSFade *fade,
        int screen_idx,
        gdouble alpha)
{
	struct GSFadeScreenPrivate *screen_priv;
	struct GSGammaInfo *info;
	MateRRCrtc **crtcs;
	int i;

	screen_priv = &fade->priv->screen_priv[screen_idx];

	if (!screen_priv->info)
		return FALSE;

	crtcs = mate_rr_screen_list_crtcs (screen_priv->rrscreen);
	i = 0;

	while (*crtcs)
	{
		info = &screen_priv->info[i];
		xrandr_crtc_whack_gamma (*crtcs, info, alpha);
		i++;
		crtcs++;
	}
	return TRUE;
}

static void
check_randr_extension (GSFade *fade, int screen_idx)
{
	GdkDisplay *display = gdk_display_get_default ();
	GdkScreen *screen = gdk_display_get_screen (display, screen_idx);
	struct GSFadeScreenPrivate *screen_priv;

	screen_priv = &fade->priv->screen_priv[screen_idx];

	screen_priv->rrscreen = mate_rr_screen_new (screen,
	                        NULL);
	if (!screen_priv->rrscreen)
	{
		screen_priv->fade_type = FADE_TYPE_NONE;
		return;
	}

	screen_priv->fade_type = FADE_TYPE_XRANDR;
	screen_priv->fade_setup = xrandr_fade_setup;
	screen_priv->fade_finish = screen_fade_finish;
	screen_priv->fade_set_alpha_gamma = xrandr_fade_set_alpha_gamma;
}

static gboolean
gs_fade_set_alpha (GSFade *fade,
                   gdouble alpha)
{
	gboolean ret = FALSE;
	int i;

	for (i = 0; i < fade->priv->num_screens; i++)
	{
		switch (fade->priv->screen_priv[i].fade_type)
		{
		case FADE_TYPE_GAMMA_RAMP:
		case FADE_TYPE_GAMMA_NUMBER:
		case FADE_TYPE_XRANDR:
			ret = fade->priv->screen_priv[i].fade_set_alpha_gamma (fade, i, alpha);
			break;
		case FADE_TYPE_NONE:
			ret = FALSE;
			break;
		default:
			g_warning ("Unknown fade type");
			ret = FALSE;
			break;
		}
	}
	return ret;
}

static gboolean
gs_fade_out_iter (GSFade *fade)
{
	gboolean ret;

	if (fade->priv->current_alpha < 0.01)
	{
		return FALSE;
	}

	fade->priv->current_alpha -= fade->priv->alpha_per_iter;

	ret = gs_fade_set_alpha (fade, fade->priv->current_alpha);

	return ret;
}

static gboolean
gs_fade_stop (GSFade *fade)
{
	if (fade->priv->timer_id > 0)
	{
		g_source_remove (fade->priv->timer_id);
		fade->priv->timer_id = 0;
	}

	fade->priv->step = 0;
	fade->priv->active = FALSE;

	return TRUE;
}

void
gs_fade_finish (GSFade *fade)
{
	g_return_if_fail (GS_IS_FADE (fade));

	if (! fade->priv->active)
	{
		return;
	}

	gs_fade_stop (fade);

	g_signal_emit (fade, signals [FADED], 0);

	fade->priv->active = FALSE;
}

static gboolean
fade_out_timer (GSFade *fade)
{
	gboolean res;

	res = gs_fade_out_iter (fade);

	/* if failed then fade is complete */
	if (! res)
	{
		gs_fade_finish (fade);
		return FALSE;
	}

	return TRUE;
}

gboolean
gs_fade_get_active (GSFade *fade)
{
	g_return_val_if_fail (GS_IS_FADE (fade), FALSE);

	return fade->priv->active;
}

static void
gs_fade_set_timeout (GSFade   *fade,
                     guint     timeout)
{
	g_return_if_fail (GS_IS_FADE (fade));

	fade->priv->timeout = timeout;
}

static void
gs_fade_start (GSFade *fade,
               guint   timeout)
{
	guint steps_per_sec = 30;
	guint msecs_per_step;
	struct GSFadeScreenPrivate *screen_priv;
	gboolean active_fade, res;
	int i;

	g_return_if_fail (GS_IS_FADE (fade));

	for (i = 0; i < fade->priv->num_screens; i++)
	{
		screen_priv = &fade->priv->screen_priv[i];
		if (screen_priv->fade_type != FADE_TYPE_NONE)
		{
			res = screen_priv->fade_setup (fade, i);
			if (res == FALSE)
				return;
		}
	}

	if (fade->priv->timer_id > 0)
	{
		gs_fade_stop (fade);
	}

	fade->priv->active = TRUE;

	gs_fade_set_timeout (fade, timeout);

	active_fade = FALSE;
	for (i = 0; i < fade->priv->num_screens; i++)
	{
		screen_priv = &fade->priv->screen_priv[i];
		if (screen_priv->fade_type != FADE_TYPE_NONE)
			active_fade = TRUE;
	}
	if (active_fade)
	{
		guint num_steps;

		num_steps = (fade->priv->timeout / 1000) * steps_per_sec;
		msecs_per_step = 1000 / steps_per_sec;
		fade->priv->alpha_per_iter = 1.0 / (gdouble)num_steps;

		fade->priv->timer_id = g_timeout_add (msecs_per_step, (GSourceFunc)fade_out_timer, fade);
	}
	else
	{
		gs_fade_finish (fade);
	}
}

typedef struct
{
	GSFadeDoneFunc done_cb;
	gpointer       data;
} FadedCallbackData;

static void
gs_fade_async_callback (GSFade            *fade,
                        FadedCallbackData *cdata)
{
	g_signal_handlers_disconnect_by_func (fade,
	                                      gs_fade_async_callback,
	                                      cdata);

	if (cdata->done_cb)
	{
		cdata->done_cb (fade, cdata->data);
	}

	g_free (cdata);
}

void
gs_fade_async (GSFade        *fade,
               guint          timeout,
               GSFadeDoneFunc func,
               gpointer       data)
{
	g_return_if_fail (GS_IS_FADE (fade));

	/* if fade is active then pause it */
	if (fade->priv->active)
	{
		gs_fade_stop (fade);
	}

	if (func)
	{
		FadedCallbackData *cb_data;

		cb_data = g_new0 (FadedCallbackData, 1);
		cb_data->done_cb = func;
		cb_data->data = data;

		g_signal_connect (fade, "faded",
		                  G_CALLBACK (gs_fade_async_callback),
		                  cb_data);
	}

	gs_fade_start (fade, timeout);
}

static void
gs_fade_sync_callback (GSFade *fade,
                       int    *flag)
{
	*flag = TRUE;
	g_signal_handlers_disconnect_by_func (fade,
	                                      gs_fade_sync_callback,
	                                      flag);
}

void
gs_fade_sync (GSFade        *fade,
              guint          timeout)
{
	int      flag = FALSE;

	g_return_if_fail (GS_IS_FADE (fade));

	/* if fade is active then pause it */
	if (fade->priv->active)
	{
		gs_fade_stop (fade);
	}

	g_signal_connect (fade, "faded",
	                  G_CALLBACK (gs_fade_sync_callback),
	                  &flag);

	gs_fade_start (fade, timeout);

	while (! flag)
	{
		gtk_main_iteration ();
	}
}

void
gs_fade_reset (GSFade *fade)
{
	int i;
	g_return_if_fail (GS_IS_FADE (fade));

	gs_debug ("Resetting fade");

	if (fade->priv->active)
	{
		gs_fade_stop (fade);
	}

	fade->priv->current_alpha = 1.0;

	gs_fade_set_alpha (fade, fade->priv->current_alpha);

	for (i = 0; i < fade->priv->num_screens; i++)
		if (fade->priv->screen_priv[i].fade_type != FADE_TYPE_NONE)
			fade->priv->screen_priv[i].fade_finish (fade, i);
}

static void
gs_fade_class_init (GSFadeClass *klass)
{
	GObjectClass   *object_class = G_OBJECT_CLASS (klass);

	object_class->finalize = gs_fade_finalize;

	signals [FADED] =
	    g_signal_new ("faded",
	                  G_TYPE_FROM_CLASS (object_class),
	                  G_SIGNAL_RUN_LAST,
	                  G_STRUCT_OFFSET (GSFadeClass, faded),
	                  NULL,
	                  NULL,
	                  g_cclosure_marshal_VOID__VOID,
	                  G_TYPE_NONE,
	                  0, G_TYPE_NONE);

	g_type_class_add_private (klass, sizeof (GSFadePrivate));
}

static void
gs_fade_init (GSFade *fade)
{
	GdkDisplay *display;
	int i;

	fade->priv = GS_FADE_GET_PRIVATE (fade);

	fade->priv->timeout = 1000;
	fade->priv->current_alpha = 1.0;

	display = gdk_display_get_default ();
	fade->priv->num_screens = gdk_display_get_n_screens (display);

	fade->priv->screen_priv = g_new0 (struct GSFadeScreenPrivate, fade->priv->num_screens);

	for (i = 0; i < fade->priv->num_screens; i++)
	{
		check_randr_extension (fade, i);
		if (!fade->priv->screen_priv[i].fade_type)
			check_gamma_extension (fade, i);
		gs_debug ("Fade type: %d", fade->priv->screen_priv[i].fade_type);
	}
}

static void
gs_fade_finalize (GObject *object)
{
	GSFade *fade;
	int i;

	g_return_if_fail (object != NULL);
	g_return_if_fail (GS_IS_FADE (object));

	fade = GS_FADE (object);

	g_return_if_fail (fade->priv != NULL);

	for (i = 0; i < fade->priv->num_screens; i++)
		fade->priv->screen_priv[i].fade_finish(fade, i);

	if (fade->priv->screen_priv)
	{
		for (i = 0; i < fade->priv->num_screens; i++)
		{
			if (!fade->priv->screen_priv[i].rrscreen)
				continue;
			g_object_unref (fade->priv->screen_priv[i].rrscreen);
		}

		g_free (fade->priv->screen_priv);
		fade->priv->screen_priv = NULL;
	}

	G_OBJECT_CLASS (gs_fade_parent_class)->finalize (object);
}

GSFade *
gs_fade_new (void)
{
	if (fade_object)
	{
		g_object_ref (fade_object);
	}
	else
	{
		fade_object = g_object_new (GS_TYPE_FADE, NULL);
		g_object_add_weak_pointer (fade_object,
		                           (gpointer *) &fade_object);
	}

	return GS_FADE (fade_object);
}