/*
 *
 * 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.
 *
 * Authors:
 *   - Diogo Ferreira (playerX) <diogo@beryl-project.org>
 *
 *
 * Copyright (c) 2007 Diogo "playerX" Ferreira
 *
 * This wouldn't have been possible without:
 *   - Ideas from the compiz community (mainly throughnothing's)
 *   - David Reveman's work
 *
 * */

#include <stdlib.h>
#include <string.h>

#include <beryl.h>
#include <beryl_ipcs.h>

#include <math.h>

#define WIN_X(w) ((w)->attrib.x - (w)->input.left)
#define WIN_Y(w) ((w)->attrib.y - (w)->input.top)
#define WIN_W(w) ((w)->width + (w)->input.left + (w)->input.right)
#define WIN_H(w) ((w)->height + (w)->input.top + (w)->input.bottom)

#define OFF_LEFT(w) ((w)->width + (w)->input.right)
#define OFF_RIGHT(w) ((w)->input.left)
#define OFF_TOP(w) ((w)->height + (w)->input.bottom)
#define OFF_BOTTOM(w) ((w)->input.top)

#define MOVE_LEFT(w) ((WIN_X(w) + (WIN_W(w)/2)) < ((w)->screen->width/2))
#define MOVE_UP(w) ((WIN_Y(w) + (WIN_H(w)/2)) < ((w)->screen->height/2))

#define SD_INITIATE_KEY_DEFAULT         "F6"
#define SD_INITIATE_MODIFIERS_DEFAULT   0

#define SD_DISPLAY_OPTION_INITIATE  0
#define SD_DISPLAY_OPTION_NUM       1

#define SD_SCREEN_OPTION_SPEED              0
#define SD_SCREEN_OPTION_TIMESTEP           1
#define SD_SCREEN_OPTION_DIRECTION          2
#define SD_SCREEN_OPTION_WINDOW_TYPE        3
#define SD_SCREEN_OPTION_USE_SCALE_SETTINGS 4
#define SD_SCREEN_OPTION_WINDOW_OPACITY     5
#define SD_SCREEN_OPTION_WINDOW_PART_SIZE 6
#define SD_SCREEN_OPTION_NUM                7

typedef enum _SdDirection
{
	SdDirectionUp,
	SdDirectionDown,
	SdDirectionLeft,
	SdDirectionRight,
	SdDirectionUpDown,
	SdDirectionLeftRight,
	SdDirectionCorners,
} SdDirection;

char *sdDirections[] = {
	N_("Up"),
	N_("Down"),
	N_("Left"),
	N_("Right"),
	N_("UpDown"),
	N_("LeftRight"),
	N_("Corners"),
};

#define SD_DIRECTION_DEFAULT SdDirectionCorners
#define NUM_SD_DIRECTIONS 7

#define SD_SPEED_DEFAULT        1.2f
#define SD_SPEED_MIN            0.1f
#define SD_SPEED_MAX            50.0f
#define SD_SPEED_PRECISION      0.1f

#define SD_TIMESTEP_DEFAULT   0.1f
#define SD_TIMESTEP_MIN       0.1f
#define SD_TIMESTEP_MAX       50.0f
#define SD_TIMESTEP_PRECISION 0.1f

#define SD_USE_SCALE_SETTINGS_DEFAULT FALSE

#define SD_WINDOW_OPACITY_DEFAULT   0.3
#define SD_WINDOW_OPACITY_MIN       0.1
#define SD_WINDOW_OPACITY_MAX       1.0
#define SD_WINDOW_OPACITY_PRECISION 0.01

#define SD_WINDOW_PART_SIZE_DEFAULT 20
#define SD_WINDOW_PART_SIZE_MIN 0
#define SD_WINDOW_PART_SIZE_MAX 300

#define SD_STATE_OFF          0
#define SD_STATE_ACTIVATING   1
#define SD_STATE_ON           2
#define SD_STATE_DEACTIVATING 3

#define NUM_OPTIONS(s) (sizeof ((s)->opt) / sizeof (CompOption))

/* window types */
static char *winType[] = {
	N_("Toolbar"),
	N_("Utility"),
	N_("Dialog"),
	N_("ModalDialog"),
	N_("Fullscreen"),
	N_("Normal")
};

#define N_WIN_TYPE (sizeof (winType) / sizeof (winType[0]))

/* necessary plugin structs */
typedef struct _ShowdesktopPlacer
{
	int placed;
	int onScreenX, onScreenY;
	int offScreenX, offScreenY;
	int origViewportX;
	int origViewportY;
} ShowdesktopPlacer;
typedef struct _ShowdesktopDisplay
{
	int screenPrivateIndex;
	CompOption opt[SD_DISPLAY_OPTION_NUM];

	HandleEventProc handleEvent;
} ShowdesktopDisplay;
typedef struct _ShowdesktopScreen
{
	int windowPrivateIndex;

	PreparePaintScreenProc preparePaintScreen;
	DonePaintScreenProc donePaintScreen;
	SetScreenOptionForPluginProc setScreenOptionForPlugin;

	CompOption opt[SD_SCREEN_OPTION_NUM];

	int state;
	int moreAdjust;

	float speed;
	float timestep;
	float windowOpacity;
	int windowPartSize;

	int direction;
	int wMask;

	int sdActiveAtom;
	int grabIndex;

	Bool ignoreNextTerminateEvent;
} ShowdesktopScreen;
typedef struct _ShowdesktopWindow
{
	int sid;
	int distance;

	ShowdesktopPlacer placer;

	GLfloat xVelocity, yVelocity;
	GLfloat tx, ty;
	GLfloat oldOpacity;

	float delta;
	Bool adjust;
} ShowdesktopWindow;

/* shortcut macros, usually named X_DISPLAY, X_SCREEN and X_WINDOW
 * these might seem overly complicated but they are shortcuts so we don't have to access the privates arrays all the time
 * */
#define GET_SHOWDESKTOP_DISPLAY(d)				     \
    ((ShowdesktopDisplay *) (d)->privates[displayPrivateIndex].ptr)

#define SD_DISPLAY(d)			   \
    ShowdesktopDisplay *sd = GET_SHOWDESKTOP_DISPLAY (d)

#define GET_SHOWDESKTOP_SCREEN(s, fd)					 \
    ((ShowdesktopScreen *) (s)->privates[(fd)->screenPrivateIndex].ptr)

#define SD_SCREEN(s)							\
    ShowdesktopScreen *ss = GET_SHOWDESKTOP_SCREEN (s, GET_SHOWDESKTOP_DISPLAY (s->display))

#define GET_SHOWDESKTOP_WINDOW(w, ss)					  \
    ((ShowdesktopWindow *) (w)->privates[(ss)->windowPrivateIndex].ptr)

#define SD_WINDOW(w)					       \
    ShowdesktopWindow *sw = GET_SHOWDESKTOP_WINDOW  (w,		       \
            GET_SHOWDESKTOP_SCREEN  (w->screen,	       \
                GET_SHOWDESKTOP_DISPLAY (w->screen->display)))
/* plugin private index */
static int displayPrivateIndex;


/* non interfacing code, aka the logic of the plugin */
static Bool isSDWin(CompWindow * w)
{
	SD_SCREEN(w->screen);

	if (!(*w->screen->focusWindow) (w))
		return FALSE;

	if (!(ss->wMask & w->type))
		return FALSE;

	if (w->wmType & (CompWindowTypeDockMask | CompWindowTypeDesktopMask))
		return FALSE;

	if (w->state & CompWindowStateSkipPagerMask)
		return FALSE;

	return TRUE;
}

static Bool
showdesktopTerminate(CompDisplay * d,
					 CompAction * action,
					 CompActionState state, CompOption * option, int nOption)
{
	CompScreen *s;
	Window xid;

	xid = getIntOptionNamed(option, nOption, "root", 0);
	s = findScreenAtDisplay(d, xid);

	if (s)
	{
		SD_SCREEN(s);

		if (ss->state == SD_STATE_ON || ss->state == SD_STATE_ACTIVATING)
		{
			CompWindow *w;

			for (w = s->windows; w; w = w->next)
			{
				SD_WINDOW(w);
				if (sw->placer.placed)
				{
					sw->adjust = TRUE;
					sw->xVelocity = sw->yVelocity = 0.0f;
					w->paint.opacity = sw->oldOpacity;
					/* adjust onscreen position to 
					   handle viewport changes
					 */
					sw->placer.onScreenX +=	(sw->placer.origViewportX - 
							w->screen->x) * w->screen->width;
					sw->placer.onScreenY += (sw->placer.origViewportY -
							w->screen->y) * w->screen->height;
				}
			}
			ss->state = SD_STATE_DEACTIVATING;

			if (ss->grabIndex)
				removeScreenGrab(s, ss->grabIndex, NULL);
			ss->grabIndex = pushScreenGrab(s, s->invisibleCursor, "showdesktop");
		}
		focusDefaultWindow(s->display);
	}
	return FALSE;
}

static void repositionSDPlacer(CompWindow * w, int oldState)
{
	SD_SCREEN(w->screen);
	SD_WINDOW(w);

	if (oldState == SD_STATE_OFF)
	{
		sw->placer.onScreenX = w->attrib.x;
		sw->placer.onScreenY = w->attrib.y;
		sw->placer.origViewportX = w->screen->x;
		sw->placer.origViewportY = w->screen->y;
	}

	switch (ss->direction)
	{
	case SdDirectionUp:
		sw->placer.offScreenX = w->attrib.x;
		sw->placer.offScreenY =
				w->screen->workArea.y - OFF_TOP(w) + ss->windowPartSize;
		break;
	case SdDirectionDown:
		sw->placer.offScreenX = w->attrib.x;
		sw->placer.offScreenY =
				w->screen->workArea.y + w->screen->workArea.height +
				OFF_BOTTOM(w) - ss->windowPartSize;
		break;
	case SdDirectionLeft:
		sw->placer.offScreenX =
				w->screen->workArea.x - OFF_LEFT(w) + ss->windowPartSize;
		sw->placer.offScreenY = w->attrib.y;
		break;
	case SdDirectionRight:
		sw->placer.offScreenX =
				w->screen->workArea.x + w->screen->workArea.width +
				OFF_RIGHT(w) - ss->windowPartSize;
		sw->placer.offScreenY = w->attrib.y;
		break;
	case SdDirectionUpDown:
		sw->placer.offScreenX = w->attrib.x;
		if (MOVE_UP(w))
			sw->placer.offScreenY =
					w->screen->workArea.y - OFF_TOP(w) + ss->windowPartSize;
		else
			sw->placer.offScreenY =
					w->screen->workArea.y +
					w->screen->workArea.height + OFF_BOTTOM(w) -
					ss->windowPartSize;
		break;
	case SdDirectionLeftRight:
		sw->placer.offScreenY = w->attrib.y;
		if (MOVE_LEFT(w))
			sw->placer.offScreenX =
					w->screen->workArea.x - OFF_LEFT(w) + ss->windowPartSize;
		else
			sw->placer.offScreenX =
					w->screen->workArea.x +
					w->screen->workArea.width + OFF_RIGHT(w) -
					ss->windowPartSize;
		break;
	case SdDirectionCorners:
		if (MOVE_LEFT(w))
			sw->placer.offScreenX =
					w->screen->workArea.x - OFF_LEFT(w) + ss->windowPartSize;
		else
			sw->placer.offScreenX =
					w->screen->workArea.x +
					w->screen->workArea.width + OFF_RIGHT(w) -
					ss->windowPartSize;
		if (MOVE_UP(w))
			sw->placer.offScreenY =
					w->screen->workArea.y - OFF_TOP(w) + ss->windowPartSize;
		else
			sw->placer.offScreenY =
					w->screen->workArea.y +
					w->screen->workArea.height + OFF_BOTTOM(w) -
					ss->windowPartSize;
		break;
	}
}

static Bool prepareSDWindows(CompScreen * s, int oldState)
{
	CompWindow *w;
	CompWindow *desktopWindow;

	SD_SCREEN(s);

	desktopWindow = 0;

	for (w = s->windows; w; w = w->next)
	{
		SD_WINDOW(w);

		if (getWindowType(s->display, w->id) == CompWindowTypeDesktopMask)
			desktopWindow = w;

		if (!isSDWin(w))
			continue;

		if (sw->placer.placed)
		{
			sw->tx = sw->ty = 0;
			syncWindowPosition(w);
			sw->placer.placed = FALSE;
		}

		repositionSDPlacer(w, oldState);

		sw->tx = sw->ty = sw->xVelocity = sw->yVelocity = 0.0f;
		sw->adjust = TRUE;
		sw->placer.placed = TRUE;

		sw->oldOpacity = w->paint.opacity;
		w->paint.opacity = ss->windowOpacity * OPAQUE;
	}

	if (desktopWindow)
		activateWindow(desktopWindow);

	return TRUE;
}

static Bool
showdesktopInitiate(CompDisplay * d,
					CompAction * action,
					CompActionState state, CompOption * option, int nOption)
{
	CompScreen *s;
	Window xid;

	xid = getIntOptionNamed(option, nOption, "root", 0);
	s = findScreenAtDisplay(d, xid);

	if (s && !otherScreenGrabExist(s, 0))
	{
		SD_SCREEN(s);

		if (ss->state == SD_STATE_OFF || ss->state == SD_STATE_DEACTIVATING)
		{
			if (prepareSDWindows(s, ss->state))
			{
				XSetInputFocus(d->display,
							   d->screens->root,
							   RevertToPointerRoot, CurrentTime);
				ss->state = SD_STATE_ACTIVATING;
				IPCS_SetBool(IPCS_OBJECT(s), ss->sdActiveAtom, TRUE);

				if (ss->grabIndex)
					removeScreenGrab(s, ss->grabIndex, NULL);
				ss->grabIndex = pushScreenGrab(s, s->invisibleCursor, "showdesktop");
			}
			if (state & CompActionStateInitButton)
				action->state |= CompActionStateTermButton;

			if (state & CompActionStateInitKey)
				action->state |= CompActionStateTermKey;
		}
		else
		{
			return showdesktopTerminate(d, action, state, option, nOption);
		}
	}

	return FALSE;
}

/* gconf entries */
static void showdesktopDisplayInitOptions(ShowdesktopDisplay * sd)
{
	CompOption *o;

	o = &sd->opt[SD_DISPLAY_OPTION_INITIATE];
	o->advanced = False;
	o->name = "initiate";
	o->group = N_("Binding");
	o->subGroup = N_("");
	o->displayHints = "";
	o->shortDesc = N_("Initiate showdesktop mode");
	o->longDesc = N_("Initiate showdesktop mode");
	o->type = CompOptionTypeAction;
	o->value.action.initiate = showdesktopInitiate;
	o->value.action.terminate = showdesktopInitiate;
	o->value.action.bell = FALSE;
	o->value.action.edgeMask = (1 << SCREEN_EDGE_BOTTOMRIGHT);
	o->value.action.state = CompActionStateInitEdge;
	o->value.action.type = CompBindingTypeKey;
	o->value.action.state |= CompActionStateInitKey;
	o->value.action.key.modifiers = SD_INITIATE_MODIFIERS_DEFAULT;
	o->value.action.key.keysym = XStringToKeysym(SD_INITIATE_KEY_DEFAULT);
}

static void showdesktopScreenInitOptions(ShowdesktopScreen * ss)
{
	CompOption *o;
	int i;

	o = &ss->opt[SD_SCREEN_OPTION_SPEED];
	o->advanced = False;
	o->name = "speed";
	o->group = N_("Misc. Options");
	o->subGroup = N_("");
	o->displayHints = "";
	o->shortDesc = N_("Speed");
	o->longDesc = N_("Window speed");
	o->type = CompOptionTypeFloat;
	o->value.f = SD_SPEED_DEFAULT;
	o->rest.f.min = SD_SPEED_MIN;
	o->rest.f.max = SD_SPEED_MAX;
	o->rest.f.precision = SD_SPEED_PRECISION;

	o = &ss->opt[SD_SCREEN_OPTION_TIMESTEP];
	o->advanced = False;
	o->name = "timestep";
	o->group = N_("Misc. Options");
	o->subGroup = N_("");
	o->displayHints = "";
	o->shortDesc = N_("Timestep");
	o->longDesc = N_("Showdesktop timestep");
	o->type = CompOptionTypeFloat;
	o->value.f = SD_TIMESTEP_DEFAULT;
	o->rest.f.min = SD_TIMESTEP_MIN;
	o->rest.f.max = SD_TIMESTEP_MAX;
	o->rest.f.precision = SD_TIMESTEP_PRECISION;

	o = &ss->opt[SD_SCREEN_OPTION_DIRECTION];
	o->advanced = False;
	o->name = "direction";
	o->group = N_("Behaviour");
	o->subGroup = N_("");
	o->displayHints = "";
	o->shortDesc = N_("Window direction");
	o->longDesc =
			N_
			("0 - Up, 1 - Down, 2 - Left, 3 - Right, 4 - Up/Down, 5 - Left/Right");
	o->type = CompOptionTypeString;
	o->value.s = strdup(sdDirections[SD_DIRECTION_DEFAULT]);
	o->rest.s.string = sdDirections;
	o->rest.s.nString = NUM_SD_DIRECTIONS;

	o = &ss->opt[SD_SCREEN_OPTION_WINDOW_TYPE];
	o->advanced = False;
	o->name = "window_types";
	o->group = N_("Behaviour");
	o->subGroup = N_("");
	o->displayHints = "";
	o->shortDesc = N_("Window Types");
	o->longDesc = N_("Window types that should go away in showdesktop mode");
	o->type = CompOptionTypeList;
	o->value.list.type = CompOptionTypeString;
	o->value.list.nValue = N_WIN_TYPE;
	o->value.list.value = malloc(sizeof(CompOptionValue) * N_WIN_TYPE);
	for (i = 0; i < N_WIN_TYPE; i++)
		o->value.list.value[i].s = strdup(winType[i]);
	o->rest.s.string = (char **)windowTypeString;
	o->rest.s.nString = nWindowTypeString;
	ss->wMask = compWindowTypeMaskFromStringList(&o->value);

	o = &ss->opt[SD_SCREEN_OPTION_USE_SCALE_SETTINGS];
	o->advanced = False;
	o->name = "use_scale_settings";
	o->group = N_("Misc. Options");
	o->subGroup = N_("");
	o->displayHints = "";
	o->shortDesc = N_("Use scale settings");
	o->longDesc =
			N_
			("Use scale speed/timestep settings instead of the ones specified here");
	o->type = CompOptionTypeBool;
	o->value.b = SD_USE_SCALE_SETTINGS_DEFAULT;

	o = &ss->opt[SD_SCREEN_OPTION_WINDOW_OPACITY];
	o->advanced = False;
	o->name = "window_opacity";
	o->group = N_("Appearance");
	o->subGroup = N_("");
	o->displayHints = "";
	o->shortDesc = N_("Window opacity when showdesktop'd");
	o->longDesc = N_("Window opacity when showdesktop'd");
	o->type = CompOptionTypeFloat;
	o->value.f = SD_WINDOW_OPACITY_DEFAULT;
	o->rest.f.min = SD_WINDOW_OPACITY_MIN;
	o->rest.f.max = SD_WINDOW_OPACITY_MAX;
	o->rest.f.precision = SD_WINDOW_OPACITY_PRECISION;

	o = &ss->opt[SD_SCREEN_OPTION_WINDOW_PART_SIZE];
	o->advanced = False;
	o->name = "window_part_size";
	o->group = N_("Appearance");
	o->subGroup = N_("");
	o->displayHints = "";
	o->shortDesc = N_("Window part size when showdesktop'd");
	o->longDesc = N_("Window part size when showdesktop'd");
	o->type = CompOptionTypeInt;
	o->value.i = SD_WINDOW_PART_SIZE_DEFAULT;
	o->rest.i.min = SD_WINDOW_PART_SIZE_MIN;
	o->rest.i.max = SD_WINDOW_PART_SIZE_MAX;
}

/* plugin initialization */

static Bool showdesktopInit(CompPlugin * p)
{
	displayPrivateIndex = allocateDisplayPrivateIndex();

	if (displayPrivateIndex < 0)
		return FALSE;

	return TRUE;
}

/* plugin finalization */
static void showdesktopFini(CompPlugin * p)
{

	if (displayPrivateIndex >= 0)
		freeDisplayPrivateIndex(displayPrivateIndex);
}

/* Handle event function
 * I have intentions of using this in the future
 **/
static void showdesktopHandleEvent(CompDisplay * d, XEvent * event)
{
	SD_DISPLAY(d);
	CompWindow *w;

	switch (event->type)
	{
	case DestroyNotify:
		w = findWindowAtDisplay(d, event->xdestroywindow.window);
		if (w)
		{
			SD_SCREEN(w->screen);
			ss->ignoreNextTerminateEvent = TRUE;
		}
		break;
	case ReparentNotify:
	{
		w = findWindowAtDisplay(d, event->xreparent.window);
		if (w)
		{
			SD_SCREEN(w->screen);
			ss->ignoreNextTerminateEvent = TRUE;
		}
		break;
	}
	case FocusIn:
	{

		w = findWindowAtDisplay(d, event->xfocus.window);

		if (w && w->managed && w->id != d->activeWindow)
		{
			SD_SCREEN(w->screen);
			SD_WINDOW(w);

			if (ss->ignoreNextTerminateEvent)
			{
				ss->ignoreNextTerminateEvent = FALSE;
				break;
			}

			if (sw->placer.placed
				&& (ss->state == SD_STATE_ON
					|| ss->state == SD_STATE_ACTIVATING))
			{
				CompOption o[1];

				o[0].type = CompOptionTypeInt;
				o[0].name = "root";
				o[0].value.i = w->screen->root;

				showdesktopTerminate(d, NULL, 0, o, 1);
			}
		}
		break;
	}
	}


	UNWRAP(sd, d, handleEvent);
	(*d->handleEvent) (d, event);
	WRAP(sd, d, handleEvent, showdesktopHandleEvent);
}

/* adjust velocity for each animation step (adapted from the scale plugin) */
static int adjustSDVelocity(CompWindow * w)
{
	float dx, dy, adjust, amount;
	float x1, y1;

	SD_WINDOW(w);
	SD_SCREEN(w->screen);

	x1 = y1 = 0.0;

	if (!sw->placer.placed)
		return 0;

	if (ss->state == SD_STATE_ACTIVATING)
	{
		x1 = sw->placer.offScreenX;
		y1 = sw->placer.offScreenY;
	}
	else if (ss->state == SD_STATE_DEACTIVATING)
	{
		x1 = sw->placer.onScreenX;
		y1 = sw->placer.onScreenY;
	}

	dx = x1 - (w->serverX + sw->tx);

	adjust = dx * 0.15f;
	amount = fabs(dx) * 1.5f;
	if (amount < 0.5f)
		amount = 0.5f;
	else if (amount > 5.0f)
		amount = 5.0f;

	sw->xVelocity = (amount * sw->xVelocity + adjust) / (amount + 1.0f);

	dy = y1 - (w->serverY + sw->ty);

	adjust = dy * 0.15f;
	amount = fabs(dy) * 1.5f;
	if (amount < 0.5f)
		amount = 0.5f;
	else if (amount > 5.0f)
		amount = 5.0f;

	sw->yVelocity = (amount * sw->yVelocity + adjust) / (amount + 1.0f);

	if (fabs(dx) < 0.1f && fabs(sw->xVelocity) < 0.2f &&
		fabs(dy) < 0.1f && fabs(sw->yVelocity) < 0.2f)
	{
		sw->xVelocity = sw->yVelocity = 0.0f;
		sw->tx = x1 - w->serverX;
		sw->ty = y1 - w->serverY;

		return 0;
	}
	return 1;
}

/* this function gets called periodically (about every 15ms on this machine),
 * animation takes place here */
static void
showdesktopPreparePaintScreen(CompScreen * s, int msSinceLastPaint)
{
	SD_SCREEN(s);

	if ((ss->state != SD_STATE_OFF) && screenGrabExist(s, "scale", 0))
	{
		CompOption o[1];

		o[0].type = CompOptionTypeInt;
		o[0].name = "root";
		o[0].value.i = s->root;

		showdesktopTerminate(s->display, NULL, 0, o, 1);
	}

	UNWRAP(ss, s, preparePaintScreen);
	(*s->preparePaintScreen) (s, msSinceLastPaint);
	WRAP(ss, s, preparePaintScreen, showdesktopPreparePaintScreen);

	if (ss->state == SD_STATE_ACTIVATING
		|| ss->state == SD_STATE_DEACTIVATING)
	{
		CompWindow *w;
		int steps, dx, dy;
		float amount, chunk;

		amount = msSinceLastPaint * 0.05f * ss->speed;
		steps = amount / (0.5f * ss->timestep);
		if (!steps)
			steps = 1;
		chunk = amount / (float)steps;

		while (steps--)
		{
			ss->moreAdjust = 0;

			for (w = s->windows; w; w = w->next)
			{
				SD_WINDOW(w);

				if (sw->placer.placed && sw->adjust)
				{
					sw->adjust = adjustSDVelocity(w);

					ss->moreAdjust |= sw->adjust;

					sw->tx += sw->xVelocity * chunk;
					sw->ty += sw->yVelocity * chunk;

					dx = (w->serverX + sw->tx) - w->attrib.x;
					dy = (w->serverY + sw->ty) - w->attrib.y;

					moveWindow(w, dx, dy, FALSE, FALSE);
				}
			}
			if (!ss->moreAdjust)
				break;
		}

	}
}

/* this one gets called after the one above and periodically,
 * here the plugin checks if windows reached the end */
static void showdesktopDonePaintScreen(CompScreen * s)
{
	SD_SCREEN(s);

	if (ss->moreAdjust)
	{
		damageScreen(s);
	}
	else
	{
		if (ss->state == SD_STATE_ACTIVATING
			|| ss->state == SD_STATE_DEACTIVATING)
		{
			CompWindow *w;

			for (w = s->windows; w; w = w->next)
			{
				SD_WINDOW(w);
				syncWindowPosition(w);
				if (ss->state == SD_STATE_DEACTIVATING)
					sw->placer.placed = FALSE;
			}
			if (ss->state == SD_STATE_ACTIVATING)
				ss->state = SD_STATE_ON;

			if (ss->state == SD_STATE_DEACTIVATING)
			{
				ss->state = SD_STATE_OFF;
				IPCS_SetBool(IPCS_OBJECT(s), ss->sdActiveAtom, FALSE);
			}

			if (ss->grabIndex)
			{
				removeScreenGrab(s, ss->grabIndex, NULL);
				ss->grabIndex = 0;
			}
		}
	}

	UNWRAP(ss, s, donePaintScreen);
	(*s->donePaintScreen) (s);
	WRAP(ss, s, donePaintScreen, showdesktopDonePaintScreen);
}

/* display initialization */

static Bool showdesktopInitDisplay(CompPlugin * p, CompDisplay * d)
{
	ShowdesktopDisplay *sd;

	sd = malloc(sizeof(ShowdesktopDisplay));	/* allocate the display */
	if (!sd)
		return FALSE;

	sd->screenPrivateIndex = allocateScreenPrivateIndex(d);
	if (sd->screenPrivateIndex < 0)
	{
		free(sd);
		return FALSE;
	}
	showdesktopDisplayInitOptions(sd);

	WRAP(sd, d, handleEvent, showdesktopHandleEvent);

	d->privates[displayPrivateIndex].ptr = sd;

	return TRUE;
}

/* display finalization
 * free resources
 * */
static void showdesktopFiniDisplay(CompPlugin * p, CompDisplay * d)
{
	SD_DISPLAY(d);

	freeScreenPrivateIndex(d, sd->screenPrivateIndex);

	UNWRAP(sd, d, handleEvent);

	free(sd);
}

static void showdesktopUpdateScaleOptions(CompScreen * s)
{
	CompPlugin *p;

	SD_SCREEN(s);

	if (!ss->opt[SD_SCREEN_OPTION_USE_SCALE_SETTINGS].value.b)
		return;

	p = findActivePlugin("scale");
	if (p && p->vTable->getScreenOptions)
	{
		CompOption *options, *option;
		int nOptions;

		options = (*p->vTable->getScreenOptions) (s, &nOptions);

		option = compFindOption(options, nOptions, "speed", 0);
		if (option)
			ss->speed = option->value.f;

		option = compFindOption(options, nOptions, "timestep", 0);
		if (option)
			ss->timestep = option->value.f;

		option = compFindOption(options, nOptions, "window_types", 0);
		if (option)
			ss->wMask = compWindowTypeMaskFromStringList(&option->value);

	}
}

static Bool
showdesktopSetScreenOptionForPlugin(CompScreen * s,
									char *plugin,
									char *name, CompOptionValue * value)
{
	Bool status;

	SD_SCREEN(s);

	UNWRAP(ss, s, setScreenOptionForPlugin);
	status = (*s->setScreenOptionForPlugin) (s, plugin, name, value);
	WRAP(ss, s, setScreenOptionForPlugin,
		 showdesktopSetScreenOptionForPlugin);

	if (status && strcmp(plugin, "scale") == 0)
		if (strcmp(name, "speed") == 0
			|| strcmp(name, "timestep") == 0
			|| strcmp(name, "window_types") == 0)
			showdesktopUpdateScaleOptions(s);

	return status;
}

/* screen initialization
 * */
static Bool showdesktopInitScreen(CompPlugin * p, CompScreen * s)
{
	ShowdesktopScreen *ss;

	SD_DISPLAY(s->display);

	ss = malloc(sizeof(ShowdesktopScreen));
	if (!ss)
		return FALSE;

	ss->windowPrivateIndex = allocateWindowPrivateIndex(s);
	if (ss->windowPrivateIndex < 0)
	{
		free(ss);
		return FALSE;
	}

	showdesktopScreenInitOptions(ss);

	ss->state = SD_STATE_OFF;

	ss->moreAdjust = 0;

	ss->speed = SD_SPEED_DEFAULT;
	ss->timestep = SD_TIMESTEP_DEFAULT;
	ss->direction = SD_DIRECTION_DEFAULT;
	ss->windowOpacity = SD_WINDOW_OPACITY_DEFAULT;
	ss->windowPartSize = SD_WINDOW_PART_SIZE_DEFAULT;

	ss->ignoreNextTerminateEvent = FALSE;
	ss->grabIndex = 0;

	ss->sdActiveAtom =
			IPCS_GetAtom(IPCS_OBJECT(s), IPCS_BOOL, "SHOWDESKTOP_ACTIVE",
						 TRUE);
	IPCS_SetBool(IPCS_OBJECT(s), ss->sdActiveAtom, FALSE);

	addScreenAction(s, &sd->opt[SD_DISPLAY_OPTION_INITIATE].value.action);

	WRAP(ss, s, preparePaintScreen, showdesktopPreparePaintScreen);
	WRAP(ss, s, donePaintScreen, showdesktopDonePaintScreen);
	WRAP(ss, s, setScreenOptionForPlugin,
		 showdesktopSetScreenOptionForPlugin);

	s->privates[sd->screenPrivateIndex].ptr = ss;

	showdesktopUpdateScaleOptions(s);

	return TRUE;

}

/* Free screen resources */
static void showdesktopFiniScreen(CompPlugin * p, CompScreen * s)
{
	SD_SCREEN(s);
	SD_DISPLAY(s->display);

	UNWRAP(ss, s, preparePaintScreen);
	UNWRAP(ss, s, donePaintScreen);
	UNWRAP(ss, s, setScreenOptionForPlugin);

	removeScreenAction(s, &sd->opt[SD_DISPLAY_OPTION_INITIATE].value.action);

	IPCS_Unset(IPCS_OBJECT(s), ss->sdActiveAtom);

	freeWindowPrivateIndex(s, ss->windowPrivateIndex);

	free(ss);
}


/* window init */
static Bool showdesktopInitWindow(CompPlugin * p, CompWindow * w)
{
	ShowdesktopWindow *sw;

	SD_SCREEN(w->screen);

	sw = malloc(sizeof(ShowdesktopWindow));
	if (!sw)
		return FALSE;

	sw->tx = sw->ty = 0.0f;
	sw->adjust = FALSE;
	sw->xVelocity = sw->yVelocity = 0.0f;
	sw->delta = 1.0f;
	sw->placer.placed = FALSE;
	sw->placer.offScreenX = sw->placer.offScreenY = 0;
	sw->placer.onScreenX = sw->placer.onScreenY = 0;

	w->privates[ss->windowPrivateIndex].ptr = sw;

	return TRUE;
}

/* Free window resources */
static void showdesktopFiniWindow(CompPlugin * p, CompWindow * w)
{
	SD_WINDOW(w);

	free(sw);
}


static CompOption *showdesktopGetDisplayOptions(CompDisplay * display,
												int *count)
{
	if (display)
	{
		SD_DISPLAY(display);

		*count = NUM_OPTIONS(sd);
		return sd->opt;
	}
	else
	{
		ShowdesktopDisplay *sd = malloc(sizeof(ShowdesktopDisplay));

		showdesktopDisplayInitOptions(sd);
		*count = NUM_OPTIONS(sd);
		return sd->opt;
	}
}

static Bool
showdesktopSetDisplayOption(CompDisplay * display,
							char *name, CompOptionValue * value)
{
	CompOption *o;
	int index;

	SD_DISPLAY(display);

	o = compFindOption(sd->opt, NUM_OPTIONS(sd), name, &index);

	if (!o)
		return FALSE;

	switch (index)
	{
	case SD_DISPLAY_OPTION_INITIATE:
		if (setDisplayAction(display, o, value))
			return TRUE;
	default:
		break;
	}

	return FALSE;
}

static CompOption *showdesktopGetScreenOptions(CompScreen * screen,
											   int *count)
{
	if (screen)
	{
		SD_SCREEN(screen);

		*count = NUM_OPTIONS(ss);
		return ss->opt;
	}
	else
	{
		ShowdesktopScreen *ss = malloc(sizeof(ShowdesktopScreen));

		showdesktopScreenInitOptions(ss);
		*count = NUM_OPTIONS(ss);
		return ss->opt;
	}
}

static Bool
showdesktopSetScreenOption(CompScreen * screen,
						   char *name, CompOptionValue * value)
{
	CompOption *o;
	int index;

	SD_SCREEN(screen);

	o = compFindOption(ss->opt, NUM_OPTIONS(ss), name, &index);

	if (!o)
		return FALSE;

	switch (index)
	{
	case SD_SCREEN_OPTION_SPEED:
		if (compSetFloatOption(o, value))
		{
			if (!ss->opt[SD_SCREEN_OPTION_USE_SCALE_SETTINGS].value.b)
				ss->speed = o->value.f;
			return TRUE;
		}
		break;
	case SD_SCREEN_OPTION_TIMESTEP:
		if (compSetFloatOption(o, value))
		{
			if (!ss->opt[SD_SCREEN_OPTION_USE_SCALE_SETTINGS].value.b)
				ss->timestep = o->value.f;
			return TRUE;
		}
		break;
	case SD_SCREEN_OPTION_DIRECTION:
		if (compSetStringOption(o, value))
		{
			int i;

			for (i = 0; i < o->rest.s.nString; i++)
			{
				if (strcmp(sdDirections[i], o->value.s) == 0)
					ss->direction = (SdDirection) i;
			}
			return TRUE;
		}
		break;
	case SD_SCREEN_OPTION_WINDOW_TYPE:
		if (compSetOptionList(o, value))
		{
			if (!ss->opt[SD_SCREEN_OPTION_USE_SCALE_SETTINGS].value.b)
				ss->wMask = compWindowTypeMaskFromStringList(&o->value);
			return TRUE;
		}
		break;
	case SD_SCREEN_OPTION_USE_SCALE_SETTINGS:
		if (compSetBoolOption(o, value))
		{
			if (o->value.b)
			{
				showdesktopUpdateScaleOptions(screen);
			}
			else
			{
				ss->speed = ss->opt[SD_SCREEN_OPTION_SPEED].value.f;
				ss->timestep = ss->opt[SD_SCREEN_OPTION_TIMESTEP].value.f;
				ss->wMask =
						compWindowTypeMaskFromStringList(&
														 (ss->
														  opt
														  [SD_SCREEN_OPTION_WINDOW_TYPE].
														  value));
			}
			return TRUE;
		}
		break;
	case SD_SCREEN_OPTION_WINDOW_OPACITY:
		if (compSetFloatOption(o, value))
		{
			ss->windowOpacity = o->value.f;
			return TRUE;
		}
		break;
	case SD_SCREEN_OPTION_WINDOW_PART_SIZE:
		if (compSetIntOption(o, value))
		{
			ss->windowPartSize = o->value.i;
			return TRUE;
		}
		break;
	}
	return FALSE;
}

CompPluginFeature sdFeatures[] = {
	{"showdesktop"}
	,
};

/* plugin vtable */
static CompPluginVTable showdesktopVTable = {
	"showdesktop",
	N_("Show desktop"),
	N_("Access your desktop easily"),
	showdesktopInit,
	showdesktopFini,
	showdesktopInitDisplay,
	showdesktopFiniDisplay,
	showdesktopInitScreen,
	showdesktopFiniScreen,
	showdesktopInitWindow,
	showdesktopFiniWindow,
	showdesktopGetDisplayOptions,
	showdesktopSetDisplayOption,
	showdesktopGetScreenOptions,
	showdesktopSetScreenOption,
	0,							/* deps fade */
	0,							/* sizeof (fadeDeps) / sizeof (fadeDeps[0]) */
	sdFeatures,
	sizeof(sdFeatures)/sizeof(sdFeatures[0]),
	BERYL_ABI_INFO,
	"beryl-plugins-unsupported",
	"desktop",
	0,
	0,
	False,
};

/* send plugin info */
CompPluginVTable *getCompPluginInfo(void)
{
	return &showdesktopVTable;
}


syntax highlighted by Code2HTML, v. 0.9.1