/*
 * Copyright © 2005 Novell, 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 Street, Fifth Floor, Boston, MA  02110-1301, USA.
 *
 * Author: David Reveman <davidr@novell.com>
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <sys/time.h>

#include <X11/Xatom.h>
#include <X11/cursorfont.h>

#include <beryl.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 SCALE_SPACING_DEFAULT 25
#define SCALE_SPACING_MIN     0
#define SCALE_SPACING_MAX     250

#define SCALE_SLOPPY_FOCUS_DEFAULT TRUE

#define SCALE_INITIATE_CURRENT_HEAD_KEY_DEFAULT "F9"
#define SCALE_INITIATE_CURRENT_HEAD_MODIFIERS_DEFAULT 0

#define SCALE_INITIATE_KEY_DEFAULT       "Pause"
#define SCALE_INITIATE_MODIFIERS_DEFAULT CompSuperMask

#define SCALE_INITIATE_APP_KEY_DEFAULT       "F7"
#define SCALE_INITIATE_APP_MODIFIERS_DEFAULT CompSuperMask

#define SCALE_INITIATE_ALL_KEY_DEFAULT       "F8"
#define SCALE_INITIATE_ALL_MODIFIERS_DEFAULT 0

#define SCALE_SPEED_DEFAULT   2.5f
#define SCALE_SPEED_MIN       0.1f
#define SCALE_SPEED_MAX       50.0f
#define SCALE_SPEED_PRECISION 0.1f

#define SCALE_HOVER_TIME_DEFAULT 1000
#define SCALE_HOVER_TIME_MIN	 50
#define SCALE_HOVER_TIME_MAX     10000

#define SCALE_DARKEN_BACK_FACTOR_DEFAULT   0.67f
#define SCALE_DARKEN_BACK_FACTOR_MIN       0.00f
#define SCALE_DARKEN_BACK_FACTOR_MAX       1.00f
#define SCALE_DARKEN_BACK_FACTOR_PRECISION 0.01f

#define SCALE_TIMESTEP_DEFAULT   1.1f
#define SCALE_TIMESTEP_MIN       0.1f
#define SCALE_TIMESTEP_MAX       50.0f
#define SCALE_TIMESTEP_PRECISION 0.1f

#define SCALE_STATE_NONE 0
#define SCALE_STATE_OUT  1
#define SCALE_STATE_WAIT 2
#define SCALE_STATE_IN   3

#define SCALE_DARKEN_BACK_DEFAULT TRUE

#define SCALE_OPACITY_DEFAULT 75
#define SCALE_OPACITY_MIN     0
#define SCALE_OPACITY_MAX     100

#define SCALE_USE_CLASS_DEFAULT TRUE

#define SCALE_HEAD_DEFAULT 0
#define SCALE_HEAD_MIN     0
#define SCALE_HEAD_MAX     10

typedef enum _MultiMonitorMode
{
	AllHeads,
	Selected,
	Current
} MultiMonitorMode;

char *multiMonitorModes[] = {
	N_("On all monitors"),
	N_("On selected monitor"),
	N_("On current monitor")
};

#define MULTIMONITOR_MODE_DEFAULT AllHeads
#define NUM_MULTIMONITOR_MODES 3

#define SCALE_ORGANIC_DEFAULT FALSE

#define SCALE_SHOW_MINIMIZED_DEFAULT FALSE

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

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

typedef enum
{
	ScaleMethodStandard = 0,
	ScaleMethodEnhanced,
	ScaleMethodOrganic
} ScaleMethod;

static char *scaleMethodString[] = {
	N_("Standard"),
	N_("Enhanced"),
	N_("Organic")
};

static ScaleMethod scaleMethod[] = {
	ScaleMethodStandard,
	ScaleMethodEnhanced,
	ScaleMethodOrganic
};

#define N_SCALE_METHODS (sizeof (scaleMethodString) / sizeof (scaleMethodString[0]))
#define SCALE_METHOD_DEFAULT (scaleMethodString[1])
#define SCALE_METHOD_INT_DEFAULT ScaleMethodEnhanced

typedef enum
{
	ScaleIconNone = 0,
	ScaleIconEmblem,
	ScaleIconBig
} IconOverlay;

static char *iconOverlayString[] = {
	N_("None"),
	N_("Emblem"),
	N_("Big")
};

static IconOverlay iconOverlay[] = {
	ScaleIconNone,
	ScaleIconEmblem,
	ScaleIconBig
};

#define N_ICON_TYPE (sizeof (iconOverlayString) / sizeof (iconOverlayString[0]))
#define SCALE_ICON_DEFAULT (iconOverlayString[1])

#define SCALE_ALLOW_KEYBOARD_INPUT_DEFAULT FALSE

static int displayPrivateIndex;

typedef struct _ScaleSlot
{
	int x1, y1, x2, y2;
	int line;
	int filled;
	float scale;
} ScaleSlot;

typedef struct _SlotArea
{
	int nWindows;
	XRectangle workArea;
} SlotArea;

#define SCALE_DISPLAY_OPTION_INITIATE_NORMAL        0
#define SCALE_DISPLAY_OPTION_INITIATE_APP           1
#define SCALE_DISPLAY_OPTION_INITIATE_ALL           2
#define SCALE_DISPLAY_OPTION_INITIATE_CURRENT_HEAD  3
#define SCALE_DISPLAY_OPTION_NUM                    4

typedef struct _ScaleDisplay
{
	int screenPrivateIndex;
	HandleEventProc handleEvent;

	CompOption opt[SCALE_DISPLAY_OPTION_NUM];

	unsigned int lastActiveNum;
	KeyCode leftKeyCode, rightKeyCode, upKeyCode, downKeyCode;
} ScaleDisplay;

#define SCALE_SCREEN_OPTION_SPACING                  0
#define SCALE_SCREEN_OPTION_SLOPPY_FOCUS             1
#define SCALE_SCREEN_OPTION_ICON                     2
#define SCALE_SCREEN_OPTION_SPEED                    3
#define SCALE_SCREEN_OPTION_TIMESTEP                 4
#define SCALE_SCREEN_OPTION_WINDOW_TYPE              5
#define SCALE_SCREEN_OPTION_DARKEN_BACK              6
#define SCALE_SCREEN_OPTION_OPACITY                  7
#define SCALE_SCREEN_OPTION_USE_CLASS                8
#define SCALE_SCREEN_OPTION_DARKEN_BACK_FACTOR       9
#define SCALE_SCREEN_OPTION_MULTIMONITOR             10
#define SCALE_SCREEN_OPTION_HEAD                     11
#define SCALE_SCREEN_OPTION_SCALE_METHOD             12
#define SCALE_SCREEN_OPTION_ALLOW_KEYBOARD_INPUT     13
#define SCALE_SCREEN_OPTION_SHOW_MINIMIZED           14
#define SCALE_SCREEN_OPTION_HOVER_TIME		     15
#define SCALE_SCREEN_OPTION_NUM                      16


typedef struct _ScaleScreen
{
	int windowPrivateIndex;

	PreparePaintScreenProc preparePaintScreen;
	DonePaintScreenProc donePaintScreen;
	PaintScreenProc paintScreen;
	PaintWindowProc paintWindow;
	DamageWindowRectProc damageWindowRect;

	CompOption opt[SCALE_SCREEN_OPTION_NUM];

	int spacing;

	float speed;
	float timestep;
	float darkenBackFactor;

	unsigned int wMask;

	Bool grab;
	int grabIndex;

	Window dndTarget;
	CompTimeoutHandle hoverHandle;

	int state;
	int moreAdjust;

	Cursor cursor;

	ScaleSlot *slots;
	int slotsSize;
	int nSlots;

	int *line;
	int lineSize;
	int nLine;

	/* only used for sorting */
	CompWindow **windows;
	int windowsSize;
	int nWindows;

	GLfloat scale;

	Bool darkenBack;
	GLushort opacity;

	Bool currentHead;
	Bool allScreensMode;
	Bool onlyCurrent;
	Bool useClass;
	CompWindow *currentWindow;
	CompWindow *hoveredWindow;
	Window selectedWindow;
	Window cancelWindow;

	int head;
	int currentOutputDev;

	ScaleMethod scaleMethod;
	IconOverlay iconOverlay;
	Bool clicked;

	MultiMonitorMode mmMode;

	//helper for interaction with rotate
	int scaleStateAtom;
} ScaleScreen;

typedef struct _ScaleWindow
{
	ScaleSlot *slot;

	int sid;
	int distance;

	Bool rescaled;
	Bool wasMinimized;
	GLfloat oldScale;
	int workspace;
	int origX, origY;

	GLfloat xVelocity, yVelocity, scaleVelocity;
	GLfloat scale;
	GLfloat tx, ty;
	float delta;
	Bool adjust;

	int animationAtom;
} ScaleWindow;


#define GET_SCALE_DISPLAY(d)                      \
    ((ScaleDisplay *) (d)->privates[displayPrivateIndex].ptr)

#define SCALE_DISPLAY(d)             \
    ScaleDisplay *sd = GET_SCALE_DISPLAY (d)

#define GET_SCALE_SCREEN(s, sd)                      \
    ((ScaleScreen *) (s)->privates[(sd)->screenPrivateIndex].ptr)

#define SCALE_SCREEN(s)                               \
    ScaleScreen *ss = GET_SCALE_SCREEN (s, GET_SCALE_DISPLAY (s->display))

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

#define SCALE_WINDOW(w)                           \
    ScaleWindow *sw = GET_SCALE_WINDOW  (w,               \
            GET_SCALE_SCREEN  (w->screen,           \
                GET_SCALE_DISPLAY (w->screen->display)))

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

static Bool scaleSetHead(ScaleScreen * ss, CompScreen * s, int head)
{
	if (s->nOutputDev == 1)
	{
		if (head > 0)
			return FALSE;
		else
			ss->head = 0;
	}
	else
	{
		if (head == -1)
			ss->head = -1;
		else if (head > s->nOutputDev)
			ss->head = -1;
		else
			ss->head = head;
	}

	return TRUE;
}

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

	SCALE_SCREEN(screen);

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

	if (!o)
		return FALSE;

	switch (index)
	{
	case SCALE_SCREEN_OPTION_SPACING:
		if (compSetIntOption(o, value))
		{
			ss->spacing = o->value.i;
			return TRUE;
		}
		break;
	case SCALE_SCREEN_OPTION_SLOPPY_FOCUS:
		if (compSetBoolOption(o, value))
			return TRUE;
		break;
	case SCALE_SCREEN_OPTION_SCALE_METHOD:
		if (compSetStringOption(o, value))
		{
			int i;

			for (i = 0; i < N_SCALE_METHODS; i++)
			{
				if (strcmp(o->value.s, scaleMethodString[i]) == 0)
				{
					ss->scaleMethod = scaleMethod[i];
					return TRUE;
				}
			}
		}
		break;
	case SCALE_SCREEN_OPTION_SPEED:
		if (compSetFloatOption(o, value))
		{
			ss->speed = o->value.f;
			return TRUE;
		}
		break;
	case SCALE_SCREEN_OPTION_DARKEN_BACK_FACTOR:
		if (compSetFloatOption(o, value))
		{
			ss->darkenBackFactor = o->value.f;
			return TRUE;
		}
		break;
	case SCALE_SCREEN_OPTION_TIMESTEP:
		if (compSetFloatOption(o, value))
		{
			ss->timestep = o->value.f;
			return TRUE;
		}
		break;
	case SCALE_SCREEN_OPTION_WINDOW_TYPE:
		if (compSetOptionList(o, value))
		{
			ss->wMask = compWindowTypeMaskFromStringList(&o->value);
			return TRUE;
		}
		break;
	case SCALE_SCREEN_OPTION_DARKEN_BACK:
		if (compSetBoolOption(o, value))
		{
			ss->darkenBack = o->value.b;
			return TRUE;
		}
		break;
	case SCALE_SCREEN_OPTION_USE_CLASS:
		if (compSetBoolOption(o, value))
		{
			ss->useClass = o->value.b;
			return TRUE;
		}
	case SCALE_SCREEN_OPTION_OPACITY:
		if (compSetIntOption(o, value))
		{
			ss->opacity = (OPAQUE * o->value.i) / 100;
			return TRUE;
		}
		break;
	case SCALE_SCREEN_OPTION_ICON:
		if (compSetStringOption(o, value))
		{
			int i;

			for (i = 0; i < N_ICON_TYPE; i++)
			{
				if (strcmp(o->value.s, iconOverlayString[i]) == 0)
				{
					ss->iconOverlay = iconOverlay[i];
					return TRUE;
				}
			}
		}
		break;
	case SCALE_SCREEN_OPTION_HEAD:
		if (compSetIntOption(o, value))
		{
			return scaleSetHead(ss, screen, o->value.i);
		}
		break;
	case SCALE_SCREEN_OPTION_HOVER_TIME:
		if (compSetIntOption(o, value))
			return TRUE;
		break;
	case SCALE_SCREEN_OPTION_ALLOW_KEYBOARD_INPUT:
	case SCALE_SCREEN_OPTION_SHOW_MINIMIZED:
		if (compSetBoolOption(o, value))
			return TRUE;
		break;

	case SCALE_SCREEN_OPTION_MULTIMONITOR:
		if (compSetStringOption(o, value))
		{
			int i;
			MultiMonitorMode mode = MULTIMONITOR_MODE_DEFAULT;

			for (i = 0; i < o->rest.s.nString; i++)
				if (strcmp(multiMonitorModes[i], o->value.s) == 0)
					mode = (MultiMonitorMode) i;
			ss->mmMode = mode;
			return TRUE;
		}
		break;
	default:
		break;
	}

	return FALSE;
}

static void scaleScreenInitOptions(ScaleScreen * ss)
{
	CompOption *o;
	int i;

	o = &ss->opt[SCALE_SCREEN_OPTION_SPACING];
	o->advanced = False;
	o->name = "spacing";
	o->group = N_("Appearance");
	o->subGroup = N_("");
	o->displayHints = "";
	o->shortDesc = N_("Window Spacing");
	o->longDesc = N_("Space between Scaled Windows.");
	o->type = CompOptionTypeInt;
	o->value.i = SCALE_SPACING_DEFAULT;
	o->rest.i.min = SCALE_SPACING_MIN;
	o->rest.i.max = SCALE_SPACING_MAX;

	o = &ss->opt[SCALE_SCREEN_OPTION_SLOPPY_FOCUS];
	o->advanced = False;
	o->name = "sloppy_focus";
	o->group = N_("Behaviour");
	o->subGroup = N_("");
	o->displayHints = "";
	o->shortDesc = N_("Sloppy Focus");
	o->longDesc = N_("Focus follows mouse pointer in scaled mode.");
	o->type = CompOptionTypeBool;
	o->value.b = SCALE_SLOPPY_FOCUS_DEFAULT;

	o = &ss->opt[SCALE_SCREEN_OPTION_SPEED];
	o->advanced = False;
	o->name = "speed";
	o->group = N_("Misc. Options");
	o->subGroup = N_("");
	o->displayHints = "";
	o->shortDesc = N_("Scale Speed");
	o->longDesc = N_("Scale Speed.");
	o->type = CompOptionTypeFloat;
	o->value.f = SCALE_SPEED_DEFAULT;
	o->rest.f.min = SCALE_SPEED_MIN;
	o->rest.f.max = SCALE_SPEED_MAX;
	o->rest.f.precision = SCALE_SPEED_PRECISION;

	o = &ss->opt[SCALE_SCREEN_OPTION_DARKEN_BACK_FACTOR];
	o->advanced = False;
	o->name = "darken_back_factor";
	o->group = N_("Appearance");
	o->subGroup = N_("");
	o->displayHints = "";
	o->shortDesc = N_("Darken Background");
	o->longDesc = N_("Set how Dark the Background will become (0.0 to 1.0).");
	o->type = CompOptionTypeFloat;
	o->value.f = SCALE_DARKEN_BACK_FACTOR_DEFAULT;
	o->rest.f.min = SCALE_DARKEN_BACK_FACTOR_MIN;
	o->rest.f.max = SCALE_DARKEN_BACK_FACTOR_MAX;
	o->rest.f.precision = SCALE_DARKEN_BACK_FACTOR_PRECISION;

	o = &ss->opt[SCALE_SCREEN_OPTION_TIMESTEP];
	o->advanced = False;
	o->name = "timestep";
	o->group = N_("Misc. Options");
	o->subGroup = N_("");
	o->displayHints = "";
	o->shortDesc = N_("Scale Timestep");
	o->longDesc = N_("Scale Timestep.");
	o->type = CompOptionTypeFloat;
	o->value.f = SCALE_TIMESTEP_DEFAULT;
	o->rest.f.min = SCALE_TIMESTEP_MIN;
	o->rest.f.max = SCALE_TIMESTEP_MAX;
	o->rest.f.precision = SCALE_TIMESTEP_PRECISION;

	o = &ss->opt[SCALE_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 Scaled in Scale 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[SCALE_SCREEN_OPTION_DARKEN_BACK];
	o->advanced = False;
	o->name = "darken_back";
	o->group = N_("Appearance");
	o->subGroup = N_("");
	o->displayHints = "";
	o->shortDesc = N_("Darken Background");
	o->longDesc =
			N_
			("Darken Background when Scaling windows, to make windows stand out more.");
	o->type = CompOptionTypeBool;
	o->value.b = SCALE_DARKEN_BACK_DEFAULT;

	o = &ss->opt[SCALE_SCREEN_OPTION_OPACITY];
	o->advanced = False;
	o->name = "opacity";
	o->group = N_("Appearance");
	o->subGroup = N_("");
	o->displayHints = "";
	o->shortDesc = N_("Opacity");
	o->longDesc = N_("Amount of Opacity in percent.");
	o->type = CompOptionTypeInt;
	o->value.i = SCALE_OPACITY_DEFAULT;
	o->rest.i.min = SCALE_OPACITY_MIN;
	o->rest.i.max = SCALE_OPACITY_MAX;

	o = &ss->opt[SCALE_SCREEN_OPTION_USE_CLASS];
	o->advanced = False;
	o->name = "use_class";
	o->group = N_("Misc. Options");
	o->subGroup = N_("");
	o->displayHints = "";
	o->shortDesc = N_("Use WM_CLASS for Only-Current");
	o->longDesc =
			N_
			("Use WM_CLASS to find out whether a window should be Scaled in Only-Current.");
	o->type = CompOptionTypeBool;
	o->value.b = SCALE_USE_CLASS_DEFAULT;

	o = &ss->opt[SCALE_SCREEN_OPTION_MULTIMONITOR];
	o->advanced = False;
	o->name = "multimonitor_mode";
	o->group = N_("Behaviour");
	o->subGroup = N_("");
	o->displayHints = "";
	o->shortDesc = N_("MultiMonitor Mode");
	o->longDesc = N_("Multi Monitor Mode behavior.");
	o->type = CompOptionTypeString;
	o->value.s = strdup(multiMonitorModes[MULTIMONITOR_MODE_DEFAULT]);
	o->rest.s.string = multiMonitorModes;
	o->rest.s.nString = NUM_MULTIMONITOR_MODES;

	o = &ss->opt[SCALE_SCREEN_OPTION_HEAD];
	o->advanced = False;
	o->name = "head";
	o->group = N_("Behaviour");
	o->subGroup = N_("");
	o->displayHints = "";
	o->shortDesc = N_("Head");
	o->longDesc = N_("Head on Which windows are Scaled.");
	o->type = CompOptionTypeInt;
	o->value.i = SCALE_HEAD_DEFAULT;
	o->rest.i.min = SCALE_HEAD_MIN;
	o->rest.i.max = SCALE_HEAD_MAX;

	o = &ss->opt[SCALE_SCREEN_OPTION_ICON];
	o->advanced = False;
	o->name = "overlay_icon";
	o->group = N_("Appearance");
	o->subGroup = N_("");
	o->displayHints = "";
	o->shortDesc = N_("Overlay Icon");
	o->longDesc = N_("Overlay an Icon on windows once they are Scaled.");
	o->type = CompOptionTypeString;
	o->value.s = strdup(SCALE_ICON_DEFAULT);
	o->rest.s.string = iconOverlayString;
	o->rest.s.nString = N_ICON_TYPE;

	o = &ss->opt[SCALE_SCREEN_OPTION_SCALE_METHOD];
	o->advanced = False;
	o->name = "scale_method";
	o->group = N_("Behaviour");
	o->subGroup = N_("");
	o->displayHints = "";
	o->shortDesc = N_("Scaling Method");
	o->longDesc = N_("Algorithms for window Placement.");
	o->type = CompOptionTypeString;
	o->value.s = strdup(SCALE_METHOD_DEFAULT);
	o->rest.s.string = scaleMethodString;
	o->rest.s.nString = N_SCALE_METHODS;

	o = &ss->opt[SCALE_SCREEN_OPTION_ALLOW_KEYBOARD_INPUT];
	o->advanced = False;
	o->name = "allow_keyboard_input";
	o->group = N_("Behaviour");
	o->subGroup = N_("");
	o->displayHints = "";
	o->shortDesc = N_("Allow Keyboard Input");
	o->longDesc = N_("Allow Keyboard Input when Scaled.");
	o->type = CompOptionTypeBool;
	o->value.b = SCALE_ALLOW_KEYBOARD_INPUT_DEFAULT;

	o = &ss->opt[SCALE_SCREEN_OPTION_SHOW_MINIMIZED];
	o->advanced = False;
	o->name = "show_minimized";
	o->group = N_("Behaviour");
	o->subGroup = N_("");
	o->displayHints = "";
	o->shortDesc = N_("Show Minimized Windows");
	o->longDesc = N_("Show Minimized Windows.");
	o->type = CompOptionTypeBool;
	o->value.b = SCALE_SHOW_MINIMIZED_DEFAULT;

	o = &ss->opt[SCALE_SCREEN_OPTION_HOVER_TIME];
	o->advanced = False;
	o->name = "hover_time";
	o->group = N_("Misc. Options");
	o->subGroup = N_("");
	o->displayHints = "";
	o->shortDesc = N_("Hover Time");
	o->longDesc =
			N_("Time (in ms) before Scale mode is terminated when"
			   "Hovering over a window.");
	o->type = CompOptionTypeInt;
	o->value.i = SCALE_HOVER_TIME_DEFAULT;
	o->rest.i.min = SCALE_HOVER_TIME_MIN;
	o->rest.i.max = SCALE_HOVER_TIME_MAX;
}

static CompOption *scaleGetScreenOptions(CompScreen * screen, int *count)
{
	if (screen)
	{
		SCALE_SCREEN(screen);

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

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


static void gotoViewport(CompScreen * s, int x)
{
	SCALE_SCREEN(s);
	if (!ss->selectedWindow)
		return;
	ss->selectedWindow = 0;
	XEvent xev;

	xev.xclient.type = ClientMessage;
	xev.xclient.display = s->display->display;
	xev.xclient.format = 32;

	xev.xclient.message_type = s->display->desktopViewportAtom;
	xev.xclient.window = s->root;

	xev.xclient.data.l[0] = x * s->width;
	xev.xclient.data.l[1] = 0;
	xev.xclient.data.l[2] = 0;
	xev.xclient.data.l[3] = 0;
	xev.xclient.data.l[4] = 0;

	XSendEvent(s->display->display,
			   s->root,
			   FALSE,
			   SubstructureRedirectMask | SubstructureNotifyMask, &xev);

}
static void setWinPort(CompWindow * w)
{
	int x, y;

	SCALE_WINDOW(w);
	//FIXME - only works with x viewports
	defaultViewportForWindow(w, &x, &y);
	sw->workspace = x;
}

static Bool isNeverScaleWin(CompWindow * w)
{
	if (w->attrib.override_redirect)
		return TRUE;

	if (w->state & CompWindowStateOffscreenMask)
		return TRUE;

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

	return FALSE;
}

static Bool isScaleWin(CompWindow * w)
{
	SCALE_SCREEN(w->screen);

	if (isNeverScaleWin(w))
		return FALSE;

	if (!ss->allScreensMode && ss->currentHead
		&& !ss->onlyCurrent
		&& screenGetOutputDevForWindow(w) != ss->currentOutputDev)
	{
		return FALSE;
	}

	if (ss->allScreensMode)
	{
		if ((!w->mapNum || w->attrib.map_state != IsViewable))
			// && ((!w->minimized && !(w->state & CompWindowStateHiddenMask))))
			if (!w->minimized
				|| !ss->opt[SCALE_SCREEN_OPTION_SHOW_MINIMIZED].value.b)
				return FALSE;
	}
	else
	{
		int x, y;

		defaultViewportForWindow(w, &x, &y);
		if (x != w->screen->x)
			return FALSE;
		if ((!(*w->screen->focusWindow) (w))
			&& (!w->minimized
				|| !ss->opt[SCALE_SCREEN_OPTION_SHOW_MINIMIZED].value.b))
			return FALSE;
	}

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

	if (!w->mapNum || w->attrib.map_state != IsViewable)
		if (!w->minimized
			|| !ss->opt[SCALE_SCREEN_OPTION_SHOW_MINIMIZED].value.b)
			return FALSE;

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

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

	if (ss->onlyCurrent && ss->currentWindow)
	{
		if (!ss->useClass
			&& (w->clientLeader != ss->currentWindow->clientLeader))
			return FALSE;
		if (ss->useClass
			&& (strcmp(w->resClass, ss->currentWindow->resClass) != 0))
			return FALSE;
	}

	if (!(ss->currentWindow))
		return TRUE;

	if (ss->onlyCurrent && ss->currentWindow != w)
		restackWindowBelow(w, ss->currentWindow);

	return TRUE;
}

static Bool
isInWorkArea(CompWindow *w, int output)
{
	XRectangle workArea;
	screenGetOutputDevWorkArea( w->screen, output, &workArea);
	int wx1 = workArea.x;
	int wy1 = workArea.y;
	int wx2 = wx1 + workArea.width;
	int wy2 = wy1 + workArea.height;
	int x1 = WIN_X(w);
	int y1 = WIN_Y(w);
	int x2 = x1 + WIN_W(w);
	int y2 = y1 + WIN_H(w);

	if (x1 >= wx1 && x1 <= wx2)
	{
		if (y1 >= wy1 && y1 <= wy2)
			return TRUE;
		if (y2 >= wy1 && y2 <= wy2)
			return TRUE;
	}
	if (x2 >= wx1 && x2 <= wx2)
	{
		if (y1 >= wy1 && y1 <= wy2)
			return TRUE;
		if (y2 >= wy1 && y2 <= wy2)
			return TRUE;
	}

	if (x1 <= wx1 && x2 >= wx1)
	{
		if (y1 >= wy1 && y1 <= wy2)
			return TRUE;
		if (y2 >= wy1 && y2 <= wy2)
			return TRUE;
		if (y1 <= wy1 && y2 >= wy1)
			return TRUE;
	}

	if (y1 <= wy1 && y2 >= wy1)
	{
		if (x1 >= wx1 && x1 <= wx2)
			return TRUE;
		if (x2 >= wx1 && x2 <= wx2)
			return TRUE;
	}

	return FALSE;
}

static Bool
scalePaintWindow(CompWindow * w,
				 const WindowPaintAttrib * attrib,
				 Region region, unsigned int mask)
{
	CompScreen *s = w->screen;
	Bool status;

	SCALE_SCREEN(s);

	if (ss->grab)
	{
		WindowPaintAttrib sAttrib = *attrib;
		Bool scaled = FALSE;

		SCALE_WINDOW(w);

		if (sw->adjust || sw->slot)
		{
			if (w->id != ss->selectedWindow &&
				ss->opacity != OPAQUE && ss->state != SCALE_STATE_IN)
			{
				// hovered windows will be opaque, even with sloppy focus off
				if (w == ss->hoveredWindow)
					sAttrib.opacity = OPAQUE;
				else
					sAttrib.opacity = (sAttrib.opacity * ss->opacity) >> 16;
			}

			scaled = TRUE;

			mask |= PAINT_WINDOW_NO_CORE_INSTANCE_MASK;
		}
		else if (ss->state != SCALE_STATE_IN)
		{
			/* modify brightness of the other windows */
			if (ss->darkenBack)
				sAttrib.brightness =
						sAttrib.brightness * ss->darkenBackFactor;

			/* make non-scaled windows invisible */
			if (!(w->type & CompWindowTypeDesktopMask))
			{
				switch (ss->mmMode)
				{
					case Current:
						if (isInWorkArea(w,ss->currentOutputDev))
							sAttrib.opacity = 0;
						break;
					case Selected:
						if (isInWorkArea(w,ss->head))
							sAttrib.opacity = 0;
						break;
					case AllHeads:
						{
							Bool inside = FALSE;
							int i;
							for (i = 0; i < s->nOutputDev && !inside; i++)
								inside |= isInWorkArea(w,i);

							if (inside)
								sAttrib.opacity = 0;
						}
						break;
				}
			}
		}

		UNWRAP(ss, s, paintWindow);
		status = (*s->paintWindow) (w, &sAttrib, region, mask);
		WRAP(ss, s, paintWindow, scalePaintWindow);

		if (scaled)
		{
			sAttrib.xScale = sw->scale;
			sAttrib.yScale = sw->scale;
			sAttrib.xTranslate = sw->tx;
			sAttrib.yTranslate = sw->ty;

			(*s->drawWindow) (w, &sAttrib, region,
							  mask | PAINT_WINDOW_TRANSFORMED_MASK);
		}

		if ((ss->iconOverlay != ScaleIconNone) && scaled)
		{
			CompIcon *icon;

			icon = getWindowIcon(w, 96, 96);
			if (!icon)
				icon = getWindowIcon(w, 128, 128);
			if (!icon)
				icon = getWindowIcon(w, 256, 256);
			if (!icon)
				icon = getWindowIcon(w, 512, 512);

			if (!icon)
				icon = w->screen->defaultIcon;

			if (icon
				&& (icon->texture.name || iconToTexture(w->screen, icon)))
			{
				REGION iconReg;
				CompMatrix matrix;
				int wx, wy, width, height;
				int scaledWinWidth, scaledWinHeight;
				float ds;

				scaledWinWidth = w->width * sw->scale;
				scaledWinHeight = w->height * sw->scale;

				switch (ss->iconOverlay)
				{
				case ScaleIconNone:
				case ScaleIconEmblem:
					if (icon->width > 96)
					{
						sAttrib.xScale = sAttrib.yScale =
								MIN((48.0f / (float)icon->width),
									(48.0f / (float)icon->height));
					}
					else
						sAttrib.xScale = sAttrib.yScale = 1.0f;
					break;
				case ScaleIconBig:
					sAttrib.opacity /= 3;
					sAttrib.xScale = sAttrib.yScale =
							MIN(((float)scaledWinWidth /
								 (float)icon->width),
								((float)scaledWinHeight /
								 (float)icon->height));
				default:
					break;
				}

				width = icon->width;
				height = icon->height;

				switch (ss->iconOverlay)
				{
				case ScaleIconNone:
				case ScaleIconEmblem:
					if (sAttrib.xScale != 1.0f)
					{
						wx = w->attrib.x +
								((w->width * sw->scale) -
								 (icon->width * sAttrib.xScale)) /
								sAttrib.xScale;
						wy = w->attrib.y +
								((w->height * sw->scale) -
								 (icon->height * sAttrib.yScale)) /
								sAttrib.yScale;
					}
					else
					{
						wx = w->attrib.x +
								(w->width * sw->scale) - icon->width;
						wy = w->attrib.y +
								(w->height * sw->scale) - icon->height;
					}
					break;
				case ScaleIconBig:
					wx = w->attrib.x +
							((scaledWinWidth -
							  width * sAttrib.xScale) / 2) / sAttrib.xScale;
					wy = w->attrib.y +
							((scaledWinHeight -
							  height * sAttrib.yScale) / 2) / sAttrib.yScale;
					break;
				default:
					wx = wy = 0;
					break;
				}

				if (IPCS_GetBool(IPCS_OBJECT(w), sw->animationAtom))
				{
					/*wx-=w->attrib.x;
					   wx/=sw->scale;
					   wx+=w->attrib.x;
					   wy-=w->attrib.y;
					   wy/=sw->scale;
					   wy+=w->attrib.y; */
					sAttrib.xScale /= sw->scale;
					sAttrib.yScale /= sw->scale;
				}

				switch (ss->scaleMethod)
				{
				case ScaleMethodEnhanced:
					if (sw->slot)
					{
						sw->delta =
								fabs(sw->slot->x1 - w->serverX) +
								fabs(sw->slot->y1 - w->serverY) +
								fabs(1.0f - sw->slot->scale) * 500.0f;
					}


					if (sw->delta)
					{
						ds = fabs(sw->tx) + fabs(sw->ty) +
								fabs(1.0f - sw->scale) * 500.0f;

						if (ds > sw->delta)
							ds = sw->delta;

						sAttrib.opacity = (ds * sAttrib.opacity) / sw->delta;
					}
					break;
				default:
					ds = 1.0f - ss->scale;
					if (ds)
					{
						sAttrib.opacity =
								(fabs(1.0f - sw->scale) *
								 sAttrib.opacity) / ds;
					}
					else if (!sw->slot)
					{
						sAttrib.opacity = 0;
					}
				}

				mask |= PAINT_WINDOW_TRANSLUCENT_MASK |
						PAINT_WINDOW_TRANSFORMED_MASK;

				iconReg.rects = &iconReg.extents;
				iconReg.numRects = 1;

				iconReg.extents.x1 = wx;
				iconReg.extents.y1 = wy;
				iconReg.extents.x2 = iconReg.extents.x1 + width;
				iconReg.extents.y2 = iconReg.extents.y1 + height;

				matrix = icon->texture.matrix;
				matrix.x0 -= wx * icon->texture.matrix.xx;
				matrix.y0 -= wy * icon->texture.matrix.yy;

				w->vCount = 0;
				if (iconReg.extents.x1 < iconReg.extents.x2
					&& iconReg.extents.y1 < iconReg.extents.y2)
					(*w->screen->addWindowGeometry) (w,
													 &matrix, 1, &iconReg,
													 &iconReg);

				if (w->vCount)
					(*w->screen->drawWindowTexture) (w,
													 &icon->texture, &sAttrib,
													 mask);
			}
		}
	}
	else
	{
		UNWRAP(ss, s, paintWindow);
		status = (*s->paintWindow) (w, attrib, region, mask);
		WRAP(ss, s, paintWindow, scalePaintWindow);
	}

	return status;

}


static int compareWindows(const void *elem1, const void *elem2)
{
	CompWindow *w1 = *((CompWindow **) elem1);
	CompWindow *w2 = *((CompWindow **) elem2);

	if (!(WIN_X(w1) - WIN_X(w2)))
		return WIN_Y(w1) - WIN_Y(w2);
	return WIN_X(w1) - WIN_X(w2);
}

/**
 * experimental organic layout method
 * inspired by smallwindows (smallwindows.sf.net) by Jens Egeblad
 * */
#define ORGANIC_STEP 0.05

static int organicCompareWindows(const void *elem1, const void *elem2)
{
	CompWindow *w1 = *((CompWindow **) elem1);
	CompWindow *w2 = *((CompWindow **) elem2);

	return (WIN_X(w1) + WIN_Y(w1)) - (WIN_X(w2) + WIN_Y(w2));
}


static double
layoutOrganicCalculateOverlap(CompScreen * s, int win, int x, int y)
{
	SCALE_SCREEN(s);
	int i;
	int x1 = x;
	int y1 = y;
	int x2 = x1 + WIN_W(ss->windows[win]) * ss->scale;
	int y2 = y1 + WIN_H(ss->windows[win]) * ss->scale;
	int overlapx = 0, overlapy = 0;
	int xmin, xmax;
	int ymin, ymax;

	double result = -0.01;

	for (i = 0; i < ss->nWindows; i++)
	{
		if (i == win)
			continue;
		overlapx = overlapy = 0;
		xmax = MAX(ss->slots[i].x1, x1);
		xmin = MIN(ss->slots[i].x1 + WIN_W(ss->windows[i]) * ss->scale, x2);
		if (xmax <= xmin)
			overlapx = xmin - xmax;

		ymax = MAX(ss->slots[i].y1, y1);
		ymin = MIN(ss->slots[i].y1 + WIN_H(ss->windows[i]) * ss->scale, y2);

		if (ymax <= ymin)
			overlapy = ymin - ymax;

		result += (double)overlapx *overlapy;
	}
	return result;
}


static double
layoutOrganicFindBestHorizontalPosition(CompScreen * s, int win,
										int *bestx, int areaWidth)
{
	SCALE_SCREEN(s);
	int i;
	int y1 = ss->slots[win].y1;
	int y2 = ss->slots[win].y1 + WIN_H(ss->windows[win]) * ss->scale;

	double bestoverlap = 1e31, overlap;
	int w = WIN_W(ss->windows[win]) * ss->scale;

	*bestx = ss->slots[win].x1;

	for (i = 0; i < ss->nWindows; i++)
	{
		if (i == win)
			continue;

		if (ss->slots[i].y1 < y2
			&& ss->slots[i].y1 + WIN_H(ss->windows[i]) * ss->scale > y1)
		{
			if (ss->slots[i].x1 - w >= 0)
			{
				double overlap = layoutOrganicCalculateOverlap(s, win,
															   ss->slots[i].
															   x1 - w, y1);

				if (overlap < bestoverlap)
				{
					*bestx = ss->slots[i].x1 - w;
					bestoverlap = overlap;
				}
			}
			if (WIN_W(ss->windows[i]) * ss->scale +
				ss->slots[i].x1 + w < areaWidth)
			{
				double overlap = layoutOrganicCalculateOverlap(s, win,
															   ss->slots[i].
															   x1 +
															   WIN_W(ss->
																	 windows
																	 [i]) *
															   ss->scale, y1);

				if (overlap < bestoverlap)
				{
					*bestx = ss->slots[i].x1 +
							WIN_W(ss->windows[i]) * ss->scale;
					bestoverlap = overlap;
				}
			}
		}
	}

	overlap = layoutOrganicCalculateOverlap(s, win, 0, y1);
	if (overlap < bestoverlap)
	{
		*bestx = 0;
		bestoverlap = overlap;
	}

	overlap = layoutOrganicCalculateOverlap(s, win, areaWidth - w, y1);
	if (overlap < bestoverlap)
	{
		*bestx = areaWidth - w;
		bestoverlap = overlap;
	}

	return bestoverlap;
}

static double
layoutOrganicFindBestVerticalPosition(CompScreen * s, int win,
									  int *besty, int areaHeight)
{
	SCALE_SCREEN(s);
	int i;
	int x1 = ss->slots[win].x1;
	int x2 = ss->slots[win].x1 + WIN_W(ss->windows[win]) * ss->scale;

	double bestoverlap = 1e31, overlap;
	int h = WIN_H(ss->windows[win]) * ss->scale;

	*besty = ss->slots[win].y1;

	for (i = 0; i < ss->nWindows; i++)
	{
		if (i == win)
			continue;

		if (ss->slots[i].x1 < x2
			&& ss->slots[i].x1 + WIN_W(ss->windows[i]) * ss->scale > x1)
		{
			if (ss->slots[i].y1 - h >= 0 && ss->slots[i].y1 < areaHeight)
			{
				double overlap = layoutOrganicCalculateOverlap(s, win,
															   x1,
															   ss->slots[i].
															   y1 - h);
				if (overlap < bestoverlap)
				{
					*besty = ss->slots[i].y1 - h;
					bestoverlap = overlap;
				}
			}
			if (WIN_H(ss->windows[i]) * ss->scale + ss->slots[i].y1 > 0
				&& WIN_H(ss->windows[i]) * ss->scale + h +
				ss->slots[i].y1 < areaHeight)
			{
				double overlap = layoutOrganicCalculateOverlap(s, win,
															   x1,
															   WIN_H(ss->
																	 windows
																	 [i]) *
															   ss->scale +
															   ss->slots[i].
															   y1);

				if (overlap < bestoverlap)
				{
					*besty = ss->slots[i].y1 +
							WIN_H(ss->windows[i]) * ss->scale;
					bestoverlap = overlap;
				}
			}
		}
	}

	overlap = layoutOrganicCalculateOverlap(s, win, x1, 0);
	if (overlap < bestoverlap)
	{
		*besty = 0;
		bestoverlap = overlap;
	}

	overlap = layoutOrganicCalculateOverlap(s, win, x1, areaHeight - h);
	if (overlap < bestoverlap)
	{
		*besty = areaHeight - h;
		bestoverlap = overlap;
	}

	return bestoverlap;
}


static Bool
layoutOrganicLocalSearch(CompScreen * s, int areaWidth, int areaHeight)
{
	SCALE_SCREEN(s);
	Bool improvement = FALSE;
	int i;
	double totaloverlap;

	do
	{
		improvement = FALSE;
		for (i = 0; i < ss->nWindows; i++)
		{
			Bool improved = FALSE;

			do
			{
				int newx, newy;
				double oldoverlap, overlaph, overlapv;

				improved = FALSE;
				oldoverlap = layoutOrganicCalculateOverlap(s, i,
														   ss->slots[i].x1,
														   ss->slots[i].y1);

				overlaph =
						layoutOrganicFindBestHorizontalPosition(s, i, &newx,
																areaWidth);
				overlapv =
						layoutOrganicFindBestVerticalPosition(s, i, &newy,
															  areaHeight);

				if (overlaph < oldoverlap - 0.1
					|| overlapv < oldoverlap - 0.1)
				{
					improved = TRUE;
					improvement = TRUE;
					if (overlapv > overlaph)
						ss->slots[i].x1 = newx;
					else
						ss->slots[i].y1 = newy;
				}

			}
			while (improved);
		}
	}
	while (improvement);

	totaloverlap = 0.0;
	for (i = 0; i < ss->nWindows; i++)
	{
		totaloverlap += layoutOrganicCalculateOverlap(s,
													  i, ss->slots[i].x1,
													  ss->slots[i].y1);
	}
	return (totaloverlap > 0.1);
}

static void
layoutOrganicRemoveOverlap(CompScreen * s, int areaWidth, int areaHeight)
{
	int i;

	SCALE_SCREEN(s);
	CompWindow *w;

	while (layoutOrganicLocalSearch(s, areaWidth, areaHeight))
	{
		for (i = 0; i < ss->nWindows; i++)
		{
			int centerX, centerY;
			int newX, newY, newWidth, newHeight;

			w = ss->windows[i];

			centerX = ss->slots[i].x1 + WIN_W(w) / 2;
			centerY = ss->slots[i].y1 + WIN_H(w) / 2;

			newWidth = (int)((1.0 - ORGANIC_STEP) *
							 (double)WIN_W(w)) - ss->spacing / 2;
			newHeight = (int)((1.0 - ORGANIC_STEP) *
							  (double)WIN_H(w)) - ss->spacing / 2;
			newX = centerX - (newWidth / 2);
			newY = centerY - (newHeight / 2);

			ss->slots[i].x1 = newX;
			ss->slots[i].y1 = newY;
			ss->slots[i].x2 = newX + WIN_W(w);
			ss->slots[i].y2 = newY + WIN_H(w);
		}
		ss->scale -= ORGANIC_STEP;
	}
}

/* TODO: Place window thumbnails at smarter positions */
static Bool layoutStandardThumbs(CompScreen * s)
{
	CompWindow *w;
	int i, j, y2;
	int cx, cy;
	int head;
	int lineLength, itemsPerLine;
	float scaleW, scaleH;
	int totalWidth, totalHeight;
	XRectangle outputRect, workArea;

	SCALE_SCREEN(s);

	if (ss->mmMode == Current)
	{
		head = screenGetCurrentOutputDev(s);
		screenGetOutputDevRect(s, head, &outputRect);
		screenGetOutputDevWorkArea(s, head, &workArea);
	}
	else if (ss->mmMode == Selected)
	{
		head = ss->head;
		screenGetOutputDevRect(s, head, &outputRect);
		screenGetOutputDevWorkArea(s, head, &workArea);
	}
	else
	{
		outputRect.x = 0;
		outputRect.y = 0;
		outputRect.width = s->width;
		outputRect.height = s->height;
		workArea = s->workArea;
	}


	cx = cy = ss->nWindows = 0;

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

		if (sw->slot)
			sw->adjust = TRUE;

		sw->slot = 0;

		if (!isScaleWin(w))
			continue;

		if (ss->opt[SCALE_SCREEN_OPTION_SHOW_MINIMIZED].value.b
			&& w->minimized)
		{
			sw->wasMinimized = TRUE;
			unminimizeWindow(w);
		}

		if (!ss->grab)
			setWinPort(w);

		if (ss->windowsSize <= ss->nWindows)
		{
			ss->windows = realloc(ss->windows,
								  sizeof(CompWindow *) * (ss->nWindows + 32));
			if (!ss->windows)
				return FALSE;

			ss->windowsSize = ss->nWindows + 32;
		}

		ss->windows[ss->nWindows++] = w;
	}

	if (ss->nWindows == 0)
		return FALSE;

	qsort(ss->windows, ss->nWindows, sizeof(CompWindow *), compareWindows);

	itemsPerLine =
			(sqrt(ss->nWindows) * outputRect.width) / outputRect.height;
	if (itemsPerLine < 1)
		itemsPerLine = 1;

	if (ss->lineSize <= ss->nWindows / itemsPerLine + 1)
	{
		ss->line = realloc(ss->line, sizeof(int) *
						   (ss->nWindows / itemsPerLine + 2));
		if (!ss->line)
			return FALSE;

		ss->lineSize = ss->nWindows / itemsPerLine + 2;
	}

	totalWidth = totalHeight = 0;

	ss->line[0] = 0;
	ss->nLine = 1;
	lineLength = itemsPerLine;

	if (ss->slotsSize <= ss->nWindows)
	{
		ss->slots = realloc(ss->slots,
							sizeof(ScaleSlot) * (ss->nWindows + 1));
		if (!ss->slots)
			return FALSE;

		ss->slotsSize = ss->nWindows + 1;
	}
	ss->nSlots = 0;

	for (i = 0; i < ss->nWindows; i++)
	{
		SCALE_WINDOW(ss->windows[i]);

		w = ss->windows[i];

		if (ss->onlyCurrent)
			raiseWindow(w);

		/* find a good place between other elements */
		for (j = 0; j < ss->nSlots; j++)
		{
			y2 = ss->slots[j].y2 + ss->spacing + WIN_H(w);
			if (w->width < ss->slots[j].x2 - ss->slots[j].x1 &&
				y2 <= ss->line[ss->slots[j].line])
				break;
		}

		/* otherwise append or start a new line */
		if (j == ss->nSlots)
		{
			if (lineLength < itemsPerLine)
			{
				lineLength++;

				ss->slots[ss->nSlots].x1 = cx;
				ss->slots[ss->nSlots].y1 = cy;
				ss->slots[ss->nSlots].x2 = cx + WIN_W(w);
				ss->slots[ss->nSlots].y2 = cy + WIN_H(w);
				ss->slots[ss->nSlots].line = ss->nLine - 1;

				ss->line[ss->nLine - 1] =
						MAX(ss->line[ss->nLine - 1],
							ss->slots[ss->nSlots].y2);
			}
			else
			{
				lineLength = 1;

				cx = ss->spacing;
				cy = ss->line[ss->nLine - 1] + ss->spacing;

				ss->slots[ss->nSlots].x1 = cx;
				ss->slots[ss->nSlots].y1 = cy;
				ss->slots[ss->nSlots].x2 = cx + WIN_W(w);
				ss->slots[ss->nSlots].y2 = cy + WIN_H(w);
				ss->slots[ss->nSlots].line = ss->nLine - 1;

				ss->line[ss->nLine] = ss->slots[ss->nSlots].y2;

				ss->nLine++;
			}

			if (ss->slots[ss->nSlots].y2 > totalHeight)
				totalHeight = ss->slots[ss->nSlots].y2;
		}
		else
		{
			ss->slots[ss->nSlots].x1 = ss->slots[j].x1;
			ss->slots[ss->nSlots].y1 = ss->slots[j].y2 + ss->spacing;
			ss->slots[ss->nSlots].x2 = ss->slots[ss->nSlots].x1 + WIN_W(w);
			ss->slots[ss->nSlots].y2 = ss->slots[ss->nSlots].y1 + WIN_H(w);
			ss->slots[ss->nSlots].line = ss->slots[j].line;

			ss->slots[j].line = 0;
		}

		cx = ss->slots[ss->nSlots].x2;
		if (cx > totalWidth)
			totalWidth = cx;

		cx += ss->spacing;

		sw->slot = &ss->slots[ss->nSlots];
		sw->adjust = TRUE;

		ss->nSlots++;
	}

	totalWidth += ss->spacing;
	totalHeight += ss->spacing;

	scaleW = (float)workArea.width / totalWidth;
	scaleH = (float)workArea.height / totalHeight;

	ss->scale = MIN(MIN(scaleH, scaleW), 1.0f);

	for (i = 0; i < ss->nWindows; i++)
	{
		SCALE_WINDOW(ss->windows[i]);

		if (sw->slot)
		{
			ss->slots[i].y1 += ss->windows[i]->input.top;
			ss->slots[i].x1 += ss->windows[i]->input.left;
			ss->slots[i].y2 += ss->windows[i]->input.top;
			ss->slots[i].x2 += ss->windows[i]->input.left;
			ss->slots[i].y1 = (float)ss->slots[i].y1 * ss->scale;
			ss->slots[i].x1 = (float)ss->slots[i].x1 * ss->scale;
			ss->slots[i].y2 = (float)ss->slots[i].y2 * ss->scale;
			ss->slots[i].x2 = (float)ss->slots[i].x2 * ss->scale;
			ss->slots[i].x1 += workArea.x;
			ss->slots[i].y1 += workArea.y;
			ss->slots[i].x2 += workArea.x;
			ss->slots[i].y2 += workArea.y;
		}
	}

	return TRUE;
}

static int compareWindowsDistance(const void *elem1, const void *elem2)
{
	CompWindow *w1 = *((CompWindow **) elem1);
	CompWindow *w2 = *((CompWindow **) elem2);

	SCALE_SCREEN(w1->screen);

	return GET_SCALE_WINDOW(w1, ss)->distance -
			GET_SCALE_WINDOW(w2, ss)->distance;
}

static void layoutSlotsForArea(CompScreen * s, XRectangle workArea, int numWin)
{
	int i, j, x, y, width, height, lines, n, nS;
	lines = sqrt(numWin + 1);

	SCALE_SCREEN(s);

	if (!numWin)
		return;

	nS = 0;
	y = workArea.y + ss->spacing;
	height = (workArea.height - (lines + 1) * ss->spacing) / lines;

	for (i = 0; i < lines; i++)
	{
		n = MIN(numWin - nS,
				ceilf((float)numWin / lines));

		x = workArea.x + ss->spacing;
		width = (workArea.width - (n + 1) * ss->spacing) / n;

		for (j = 0; j < n; j++)
		{
			ss->slots[ss->nSlots].x1 = x;
			ss->slots[ss->nSlots].y1 = y;
			ss->slots[ss->nSlots].x2 = x + width;
			ss->slots[ss->nSlots].y2 = y + height;

			ss->slots[ss->nSlots].filled = FALSE;

			x += width + ss->spacing;

			ss->nSlots++;
			nS++;
		}

		y += height + ss->spacing;
	}
}

static SlotArea* getSlotAreas(CompScreen *s)
{
	SCALE_SCREEN(s);

	int i;
	XRectangle workArea;
	float *size = malloc(s->nOutputDev * sizeof(int));
	SlotArea* sA = malloc(s->nOutputDev * sizeof(SlotArea));

	float sum = 0;
	int left = ss->nWindows;

	for (i = 0; i < s->nOutputDev; i++)
	{
		// determinate the size of the workarea for each output device
		screenGetOutputDevWorkArea(s, i, &workArea);
		size[i] = workArea.width * workArea.height;
		sum += size[i];
		sA[i].nWindows = 0;
		sA[i].workArea = workArea;
	}

	// calculate size available for each window
	float spw = sum / ss->nWindows;

	for (i = 0; i < s->nOutputDev && left; i++)
	{
		// fill the areas with windows
		int nw = floor(size[i] / spw);
		nw = MIN(nw,left);
		size[i] -= nw * spw;
		sA[i].nWindows = nw;
		left -= nw;
	}

	// add left windows to output devices with the biggest free space
	while (left > 0)
	{
		int num = 0;
		float big = 0;
		for (i = 0; i < s->nOutputDev; i++)
			if (size[i] > big)
			{
				num = i;
				big = size[i];
			}
		size[num] -= spw;
		sA[num].nWindows++;
		left--;
	}

	free(size);
	return sA;
}

static void layoutSlots(CompScreen * s)
{
	int head = 0, i;
	XRectangle workArea;

	SCALE_SCREEN(s);

	ss->nSlots = 0;

	if (ss->mmMode == Current || s->nOutputDev == 1)
		head = screenGetCurrentOutputDev(s);
	else if (ss->mmMode == Selected)
		head = ss->head;

	if (ss->mmMode == Current || ss->mmMode == Selected || s->nOutputDev == 1)
	{
		screenGetOutputDevWorkArea(s, head, &workArea);
		layoutSlotsForArea(s, workArea, ss->nWindows);
	} else if (ss->mmMode == AllHeads)
	{
		SlotArea* sa = getSlotAreas(s);
		if (sa)
		{
			for (i = 0; i < s->nOutputDev; i++)
				layoutSlotsForArea(s, sa[i].workArea, sa[i].nWindows);
			free(sa);
		}
	}



}

static void findBestSlots(CompScreen * s)
{
	CompWindow *w;
	int i, j, d, d0 = 0;
	float sx, sy, cx, cy;

	SCALE_SCREEN(s);

	for (i = 0; i < ss->nWindows; i++)
	{
		w = ss->windows[i];

		SCALE_WINDOW(w);

		if (sw->slot)
			continue;

		sw->sid = 0;
		sw->distance = MAXSHORT;

		for (j = 0; j < ss->nSlots; j++)
		{
			if (!ss->slots[j].filled)
			{
				sx = (ss->slots[j].x2 + ss->slots[j].x1) / 2;
				sy = (ss->slots[j].y2 + ss->slots[j].y1) / 2;

				cx = w->serverX + w->width / 2;
				cy = w->serverY + w->height / 2;

				cx -= sx;
				cy -= sy;

				d = sqrt(cx * cx + cy * cy);
				if (d0 + d < sw->distance)
				{
					sw->sid = j;
					sw->distance = d0 + d;
				}
			}
		}

		d0 += sw->distance;
	}
}

static Bool fillInWindows(CompScreen * s)
{
	CompWindow *w;
	int i, width, height;
	float sx, sy, cx, cy;

	SCALE_SCREEN(s);

	for (i = 0; i < ss->nWindows; i++)
	{
		w = ss->windows[i];

		SCALE_WINDOW(w);

		if (!sw->slot)
		{
			if (ss->slots[sw->sid].filled)
				return TRUE;

			sw->slot = &ss->slots[sw->sid];

			width = w->width + w->input.left + w->input.right;
			height = w->height + w->input.top + w->input.bottom;

			sx = (float)(sw->slot->x2 - sw->slot->x1) / width;
			sy = (float)(sw->slot->y2 - sw->slot->y1) / height;

			sw->slot->scale = MIN(MIN(sx, sy), 1.0f);

			sx = w->width * sw->slot->scale;
			sy = w->height * sw->slot->scale;
			cx = (sw->slot->x1 + sw->slot->x2) / 2;
			cy = (sw->slot->y1 + sw->slot->y2) / 2;

			cx += (w->input.left - w->input.right) * sw->slot->scale;
			cy += (w->input.top - w->input.bottom) * sw->slot->scale;

			sw->slot->x1 = cx - sx / 2;
			sw->slot->y1 = cy - sy / 2;
			sw->slot->x2 = cx + sx / 2;
			sw->slot->y2 = cy + sy / 2;

			sw->slot->filled = TRUE;

			sw->adjust = TRUE;
		}
	}

	return FALSE;
}


static Bool layoutEnhancedThumbs(CompScreen * s)
{
	CompWindow *w;
	int i;

	SCALE_SCREEN(s);

	ss->nWindows = 0;

	/* add windows scale list, top most window first */
	for (w = s->reverseWindows; w; w = w->prev)
	{
		SCALE_WINDOW(w);

		if (sw->slot)
			sw->adjust = TRUE;

		sw->slot = 0;

		if (!isScaleWin(w))
			continue;

		if (ss->opt[SCALE_SCREEN_OPTION_SHOW_MINIMIZED].value.b
			&& w->minimized)
		{
			sw->wasMinimized = TRUE;
			unminimizeWindow(w);
		}

		if (!ss->grab)
			setWinPort(w);

		if (ss->windowsSize <= ss->nWindows)
		{
			ss->windows = realloc(ss->windows,
								  sizeof(CompWindow *) * (ss->nWindows + 32));
			if (!ss->windows)
				return FALSE;

			ss->windowsSize = ss->nWindows + 32;
		}

		ss->windows[ss->nWindows++] = w;
	}

	if (ss->nWindows == 0)
		return FALSE;

	if (ss->slotsSize < ss->nWindows)
	{
		ss->slots = realloc(ss->slots, sizeof(ScaleSlot) * ss->nWindows);
		if (!ss->slots)
			return FALSE;

		ss->slotsSize = ss->nWindows;
	}

	/* create a grid of slots */
	layoutSlots(s);

	do
	{
		/* find most appropriate slots for windows */
		findBestSlots(s);

		/* sort windows, window with closest distance to a slot first */
		qsort(ss->windows, ss->nWindows, sizeof(CompWindow *),
			  compareWindowsDistance);

	}
	while (fillInWindows(s));

	for (i = 0; i < ss->nWindows; i++)
	{
		SCALE_WINDOW(ss->windows[i]);

		if (ss->onlyCurrent)
			raiseWindow(ss->windows[i]);
		if (sw->slot)
			sw->adjust = TRUE;
	}

	return TRUE;
}

static Bool layoutOrganicThumbs(CompScreen * s)
{
	CompWindow *w;

	int i;
	int cx, cy;
	int head;
	XRectangle workArea;

	SCALE_SCREEN(s);

	if (ss->mmMode == Current)
	{
		head = screenGetCurrentOutputDev(s);
		screenGetOutputDevWorkArea(s, head, &workArea);
	}
	else if (ss->mmMode == Selected)
	{
		head = ss->head;
		screenGetOutputDevWorkArea(s, head, &workArea);
	}
	else
	{
		workArea = s->workArea;
	}

	ss->scale = 1.0f;

	cx = cy = ss->nWindows = 0;
	for (w = s->windows; w; w = w->next)
	{
		SCALE_WINDOW(w);

		if (sw->slot)
			sw->adjust = TRUE;

		sw->slot = 0;

		if (!isScaleWin(w))
			continue;

		if (ss->opt[SCALE_SCREEN_OPTION_SHOW_MINIMIZED].value.b
			&& w->minimized)
		{
			sw->wasMinimized = TRUE;
			unminimizeWindow(w);
		}

		if (!ss->grab)
			setWinPort(w);

		if (ss->windowsSize <= ss->nWindows)
		{
			ss->windows = realloc(ss->windows,
								  sizeof(CompWindow *) * (ss->nWindows + 32));
			if (!ss->windows)
				return FALSE;

			ss->windowsSize = ss->nWindows + 32;
		}

		ss->windows[ss->nWindows++] = w;
	}
	if (ss->nWindows == 0)
		return FALSE;

	qsort(ss->windows, ss->nWindows, sizeof(CompWindow *),
		  organicCompareWindows);

	if (ss->slotsSize <= ss->nWindows)
	{
		ss->slots = realloc(ss->slots, sizeof(ScaleSlot) *
							(ss->nWindows + 1));

		if (!ss->slots)
			return FALSE;

		ss->slotsSize = ss->nWindows + 1;
	}

	for (i = 0; i < ss->nWindows; i++)
	{
		SCALE_WINDOW(ss->windows[i]);
		w = ss->windows[i];

		sw->slot = &ss->slots[i];
		ss->slots[i].x1 = WIN_X(w) - workArea.x;
		ss->slots[i].y1 = WIN_Y(w) - workArea.y;
		ss->slots[i].x2 = WIN_X(w) + WIN_W(w) - workArea.x;
		ss->slots[i].y2 = WIN_Y(w) + WIN_H(w) - workArea.y;

		if (ss->slots[i].x1 < 0)
		{
			ss->slots[i].x2 += abs(ss->slots[i].x1);
			ss->slots[i].x1 = 0;
		}
		if (ss->slots[i].x2 > workArea.width - workArea.x)
		{
			ss->slots[i].x1 -= abs(ss->slots[i].x2 - workArea.width);
			ss->slots[i].x2 = workArea.width - workArea.x;
		}

		if (ss->slots[i].y1 < 0)
		{
			ss->slots[i].y2 += abs(ss->slots[i].y1);
			ss->slots[i].y1 = 0;
		}
		if (ss->slots[i].y2 > workArea.height - workArea.y)
		{
			ss->slots[i].y1 -= abs(ss->slots[i].y2 -
								   workArea.height - workArea.y);
			ss->slots[i].y2 = workArea.height - workArea.y;
		}
	}

	ss->nSlots = ss->nWindows;

	layoutOrganicRemoveOverlap(s, workArea.width - workArea.x,
							   workArea.height - workArea.y);
	for (i = 0; i < ss->nWindows; i++)
	{
		SCALE_WINDOW(ss->windows[i]);

		if (ss->onlyCurrent)
			raiseWindow(ss->windows[i]);

		ss->slots[i].x1 += ss->windows[i]->input.left + workArea.x;
		ss->slots[i].x2 += ss->windows[i]->input.left + workArea.x;
		ss->slots[i].y1 += ss->windows[i]->input.top + workArea.y;
		ss->slots[i].y2 += ss->windows[i]->input.top + workArea.y;
		sw->adjust = TRUE;
	}

	return TRUE;
}

static void zoomWindow(CompScreen * s, CompWindow * w)
{
	SCALE_WINDOW(w);

	XRectangle workArea, outputRect;
	int head;

	head = screenGetCurrentOutputDev(s);
	screenGetOutputDevRect(s, head, &outputRect);

	screenGetOutputDevWorkArea(s, screenGetCurrentOutputDev(s), &workArea);


	if (sw->rescaled == FALSE)
	{
		raiseWindow(w);
		//backup old values
		sw->oldScale = sw->scale;
		sw->origX = sw->slot->x1;
		sw->origY = sw->slot->y1;

		sw->scale = 1.0f;
		sw->adjust = TRUE;

		sw->rescaled = TRUE;

		sw->slot->x1 = (outputRect.width / 2) - (WIN_W(w) / 2) + workArea.x;
		sw->slot->y1 = (outputRect.height / 2) - (WIN_H(w) / 2) + workArea.y;
	}
	else
	{
		restackWindowAbove(w, s->windows);	// TODO make sure this is right
		sw->rescaled = FALSE;
		sw->scale = sw->oldScale;
		sw->slot->x1 = sw->origX;
		sw->slot->y1 = sw->origY;
		sw->adjust = TRUE;
	}
}

static void resetZoomValues(CompScreen * s)
{
	CompWindow *w;

	for (w = s->windows; w; w = w->next)
	{
		SCALE_WINDOW(w);
		if (sw->rescaled && isScaleWin(w))
			sw->rescaled = FALSE;
	}
}


/* TODO: Place window thumbnails at smarter positions */
static Bool layoutThumbs(CompScreen * s)
{
	SCALE_SCREEN(s);

	switch (ss->scaleMethod)
	{
	case ScaleMethodStandard:
		return layoutStandardThumbs(s);
	case ScaleMethodEnhanced:
		return layoutEnhancedThumbs(s);
	case ScaleMethodOrganic:
		return layoutOrganicThumbs(s);

	}
	return FALSE;
}

static int adjustScaleVelocity(CompWindow * w)
{
	float dx, dy, ds, adjust, amount;
	float x1, y1, scale;

	SCALE_SCREEN(w->screen);
	SCALE_WINDOW(w);

	if (sw->slot)
	{
		x1 = sw->slot->x1;
		y1 = sw->slot->y1;
		switch (ss->scaleMethod)
		{
		case ScaleMethodEnhanced:
			scale = sw->slot->scale;
			break;
		default:
			scale = ss->scale;
		}
	}
	else
	{
		x1 = w->serverX;
		y1 = w->serverY;
		scale = 1.0f;
	}

	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);

	ds = scale - sw->scale;

	adjust = ds * 0.1f;
	amount = fabs(ds) * 7.0f;
	if (amount < 0.01f)
		amount = 0.01f;
	else if (amount > 0.15f)
		amount = 0.15f;

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

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

		if (sw->rescaled == FALSE)
		{
			sw->scale = scale;
		}

		return 0;
	}

	return 1;
}

static Bool
scalePaintScreen(CompScreen * s,
				 const ScreenPaintAttrib * sAttrib,
				 Region region, int output, unsigned int mask)
{
	Bool status;

	SCALE_SCREEN(s);

	if (ss->grab)
		mask |= PAINT_SCREEN_WITH_TRANSFORMED_WINDOWS_MASK;

	UNWRAP(ss, s, paintScreen);
	status = (*s->paintScreen) (s, sAttrib, region, output, mask);
	WRAP(ss, s, paintScreen, scalePaintScreen);

	return status;
}

static void scalePreparePaintScreen(CompScreen * s, int msSinceLastPaint)
{
	SCALE_SCREEN(s);

	ss->clicked = FALSE;

	if (ss->grab
		&& !IPCS_GetBoolND(IPCS_OBJECT(s), "SHOWDESKTOP_ACTIVE", FALSE))
	{
		CompWindow *w;
		int steps;
		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)
			{
				SCALE_WINDOW(w);

				if (sw->adjust)
				{
					sw->adjust = adjustScaleVelocity(w);

					ss->moreAdjust |= sw->adjust;

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

					if (sw->rescaled == FALSE)
					{
						sw->scale += sw->scaleVelocity * chunk;
					}
				}

			}

			if (!ss->moreAdjust)
				break;
		}
	}

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

static void scaleDonePaintScreen(CompScreen * s)
{
	SCALE_SCREEN(s);

	if (ss->grab)
	{
		if (ss->moreAdjust)
		{
			damageScreen(s);
		}
		else
		{
			if (ss->state == SCALE_STATE_IN)
			{
				ss->grab = FALSE;
				removeScreenGrabKeyboardOptional(s,
												 ss->grabIndex, 0,
												 !ss->
												 opt
												 [SCALE_SCREEN_OPTION_ALLOW_KEYBOARD_INPUT].
												 value.b);
				ss->grabIndex = 0;
				if (ss->selectedWindow)
				{

					CompWindow *w = findWindowAtScreen(s,
													   ss->selectedWindow);

					if (w && isScaleWin(w))
					{
						SCALE_WINDOW(w);
						raiseWindow(w);
						gotoViewport(s, sw->workspace);
					}
				}
			}
			else if (ss->state == SCALE_STATE_OUT)
				ss->state = SCALE_STATE_WAIT;
		}
	}

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

static CompWindow *scaleCheckForWindowAt(CompScreen * s, int x, int y)
{
	int x1, y1, x2, y2;
	CompWindow *w;

	for (w = s->reverseWindows; w; w = w->prev)
	{
		SCALE_WINDOW(w);

		if (sw->slot)
		{
			x1 = w->attrib.x - w->input.left * sw->scale;
			y1 = w->attrib.y - w->input.top * sw->scale;
			x2 = w->attrib.x + (w->width + w->input.right) * sw->scale;
			y2 = w->attrib.y + (w->height + w->input.bottom) * sw->scale;

			x1 += sw->tx;
			y1 += sw->ty;
			x2 += sw->tx;
			y2 += sw->ty;

			if (x1 <= x && y1 <= y && x2 > x && y2 > y)
				return w;
		}
	}

	return 0;
}

static void sendDndStatusMessage(CompScreen * s, Window source)
{
	XEvent xev;

	SCALE_SCREEN(s);

	xev.xclient.type = ClientMessage;
	xev.xclient.display = s->display->display;
	xev.xclient.format = 32;

	xev.xclient.message_type = s->display->xdndStatusAtom;
	xev.xclient.window = source;

	xev.xclient.data.l[0] = ss->dndTarget;
	xev.xclient.data.l[1] = 2;
	xev.xclient.data.l[2] = 0;
	xev.xclient.data.l[3] = 0;
	xev.xclient.data.l[4] = None;

	XSendEvent(s->display->display, source, FALSE, 0, &xev);
}

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

	SCALE_DISPLAY(d);

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

	for (s = d->screens; s; s = s->next)
	{
		SCALE_SCREEN(s);
		if (xid && s->root != xid)
			continue;

		ss->currentWindow = 0;
		ss->allScreensMode = FALSE;
		ss->onlyCurrent = FALSE;

		//clean up any sw->rescaled values that may still be true
		//causes odd behavior on next scale/zoom otherwise
		resetZoomValues(s);

		if (ss->grab)
		{
			if (ss->dndTarget)
			{
				XUnmapWindow(d->display, ss->dndTarget);
				ss->dndTarget = 0;
			}

			if (ss->state == SCALE_STATE_NONE)
			{
				if (ss->grabIndex)
				{
					removeScreenGrabKeyboardOptional(s,
													 ss->grabIndex, 0,
													 !ss->
													 opt
													 [SCALE_SCREEN_OPTION_ALLOW_KEYBOARD_INPUT].
													 value.b);
					ss->grabIndex = 0;
				}

				ss->grab = FALSE;

				if (ss->selectedWindow && !(state & CompActionStateCancel))
				{
					CompWindow *w = findWindowAtScreen(s,
													   ss->selectedWindow);

					if (w)
					{
						SCALE_WINDOW(w);
						raiseWindow(w);
						gotoViewport(s, sw->workspace);
					}
				}
			}
			else
			{
				CompWindow *w;

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

					if (sw->slot)
					{
						sw->slot = 0;
						sw->adjust = TRUE;
					}
					if (sw->wasMinimized && (w->id != ss->selectedWindow))
					{
						minimizeWindow(w);
						sw->wasMinimized = FALSE;
					}
					if (w->id == ss->selectedWindow)
					{
						raiseWindow(w);
						//goto viewport immediately
						gotoViewport(s, sw->workspace);
					}
				}

				ss->state = SCALE_STATE_IN;

				damageScreen(s);
			}

			if (state & CompActionStateCancel)
			{
				CompWindow *aw = findTopLevelWindowAtScreen(s,
															ss->cancelWindow);

				if (aw)
					sendWindowActivationRequest(s, aw->id);

				ss->cancelWindow = 0;
				ss->selectedWindow = 0;
			}
			sd->lastActiveNum = None;
		}
	}

	return FALSE;
}

static Bool scaleEnsureDndRedirectWindow(CompScreen * s)
{
	SCALE_SCREEN(s);

	if (!ss->dndTarget)
	{
		XSetWindowAttributes attr;
		long xdndVersion = 3;

		attr.override_redirect = TRUE;

		ss->dndTarget = XCreateWindow(s->display->display,
									  s->root, 0, 0, 1, 1, 0, CopyFromParent,
									  InputOnly, CopyFromParent,
									  CWOverrideRedirect, &attr);

		XChangeProperty(s->display->display, ss->dndTarget,
						s->display->xdndAwareAtom,
						XA_ATOM, 32, PropModeReplace,
						(unsigned char *)&xdndVersion, 1);
	}

	XMoveResizeWindow(s->display->display, ss->dndTarget,
					  0, 0, s->width, s->height);
	XMapRaised(s->display->display, ss->dndTarget);

	return TRUE;
}

#define SCALE_KIND_NORMAL 0
#define SCALE_KIND_APP 1
#define SCALE_KIND_ALL 2
#define SCALE_KIND_CURRENT_HEAD 3

static Bool
scaleInitiateReal(CompDisplay * d,
				  CompAction * action,
				  CompActionState state,
				  CompOption * option, int nOption, int scaleKind)
{
	CompScreen *s;
	CompWindow *w;
	Window xid;
	Window xid2;

	xid = getIntOptionNamed(option, nOption, "root", 0);
	s = findScreenAtDisplay(d, xid);
	if (s)
	{
		SCALE_SCREEN(s);
		if (ss->clicked)
		{
			ss->clicked = FALSE;
			return FALSE;
		}
		w = findTopLevelWindowAtScreen(s, s->display->activeWindow);
		xid2 = w ? w->id : 0;

		SCALE_DISPLAY(s->display);

		if (ss->state != SCALE_STATE_WAIT && ss->state != SCALE_STATE_OUT)
		{
			//ensure we are facing the proper viewport
			gotoViewport(s, s->x);

			ss->cancelWindow = ss->selectedWindow = 0;
			if (s->display->activeWindow)
			{
				CompWindow *aw = findTopLevelWindowAtScreen(s,
															s->display->
															activeWindow);

				//make sure that the selected window is a valid one
				if (aw && isScaleWin(aw))
				{
					ss->cancelWindow = aw->id;
					ss->selectedWindow = aw->id;
				}
			}

			ss->onlyCurrent = (scaleKind == SCALE_KIND_APP);
			if (ss->onlyCurrent)
			{
				ss->currentWindow = findWindowAtDisplay(d, xid2);
			}
			ss->allScreensMode = (scaleKind == SCALE_KIND_ALL);
			ss->currentHead = (scaleKind == SCALE_KIND_CURRENT_HEAD);


			ss->currentOutputDev = screenGetCurrentOutputDev(s);

			if (!layoutThumbs(s))
				return FALSE;

			if (otherScreenGrabExist(s, "scale", 0))
				return FALSE;

			if (state & CompActionStateInitEdgeDnd)
			{
				if (scaleEnsureDndRedirectWindow(s))
					ss->grab = TRUE;
			}
			else if (!ss->grabIndex)
			{
				ss->grabIndex = pushScreenGrabKeyboardOptional(s,
															   ss->cursor,
															   "scale",
															   !ss->
															   opt
															   [SCALE_SCREEN_OPTION_ALLOW_KEYBOARD_INPUT].
															   value.b);
				if (ss->grabIndex)
					ss->grab = TRUE;
			}

			if (ss->grab)
			{
				if (!sd->lastActiveNum)
					sd->lastActiveNum = s->activeNum - 1;

				ss->state = SCALE_STATE_OUT;

				damageScreen(s);
			}
		}
		else
		{
			state |= CompActionStateCancel;
			scaleTerminate(d, action, state, option, nOption);
		}
	}

	return FALSE;
}

static Bool
scaleInitiate(CompDisplay * d,
			  CompAction * action,
			  CompActionState state, CompOption * option, int nOption)
{
	return scaleInitiateReal(d, action, state, option, nOption,
							 SCALE_KIND_NORMAL);
}

static Bool
scaleInitiateApp(CompDisplay * d,
				 CompAction * action,
				 CompActionState state, CompOption * option, int nOption)
{
	return scaleInitiateReal(d, action, state, option, nOption,
							 SCALE_KIND_APP);
}

static Bool
scaleInitiateAll(CompDisplay * d,
				 CompAction * action,
				 CompActionState state, CompOption * option, int nOption)
{
	return scaleInitiateReal(d, action, state, option, nOption,
							 SCALE_KIND_ALL);
}

static Bool
scaleInitiateCurrentHead(CompDisplay * d,
						 CompAction * action,
						 CompActionState state,
						 CompOption * option, int nOption)
{
	return scaleInitiateReal(d, action, state, option, nOption,
							 SCALE_KIND_CURRENT_HEAD);
}


static void scaleSelectWindow(CompWindow * w)
{
	SCALE_SCREEN(w->screen);
	//raiseWindow (w);
	moveInputFocusToWindow(w);
	ss->selectedWindow = w->id;
	damageScreen(w->screen);
}

static Bool scaleHoverWindowAt(CompScreen * s, int x, int y, Bool sloppyFocus)
{
	CompWindow *w;

	SCALE_SCREEN(s);

	w = scaleCheckForWindowAt(s, x, y);
	if (w && isScaleWin(w))
	{
		if (sloppyFocus)
			scaleSelectWindow(w);
		else
		{
			ss->hoveredWindow = w;
			damageScreen(w->screen);
		}

		return TRUE;
	}
	else
		ss->hoveredWindow = NULL;

	return FALSE;
}

static Bool scaleSelectWindowAt(CompScreen * s, int x, int y)
{
	CompWindow *w;

	w = scaleCheckForWindowAt(s, x, y);
	if (w && isScaleWin(w))
	{
		scaleSelectWindow(w);
		return TRUE;
	}

	return FALSE;
}

static void scaleMoveFocusWindow(CompScreen * s, int dx, int dy)
{
	CompWindow *active;

	active = findWindowAtScreen(s, s->display->activeWindow);
	if (active)
	{
		CompWindow *w, *focus = NULL;
		ScaleSlot *slot;
		int x, y, cx, cy, d, min = MAXSHORT;

		SCALE_SCREEN(s);
		SCALE_WINDOW(active);

		if (!sw->slot)
			return;

		cx = (sw->slot->x1 + sw->slot->x2) / 2;
		cy = (sw->slot->y1 + sw->slot->y2) / 2;

		for (w = s->windows; w; w = w->next)
		{
			if (w->id == active->id)
				continue;
			slot = GET_SCALE_WINDOW(w, ss)->slot;
			if (!slot)
				continue;

			x = (slot->x1 + slot->x2) / 2;
			y = (slot->y1 + slot->y2) / 2;

			d = sqrt((x - cx) * (x - cx) + (y - cy) * (y - cy));
			if (d < min)
			{
				if ((dx > 0 && slot->x1 < sw->slot->x2) ||
					(dx < 0 && slot->x2 > sw->slot->x1) ||
					(dy > 0 && slot->y1 < sw->slot->y2) ||
					(dy < 0 && slot->y2 > sw->slot->y1))
					/*
					   if ((dx > 0 && x < cx) ||
					   (dx < 0 && x > cx) ||
					   (dy > 0 && y < cy) ||
					   (dy < 0 && y > cy)) */
					continue;

				min = d;
				focus = w;
			}
		}

		if (focus)
		{
			SCALE_DISPLAY(s->display);

			sd->lastActiveNum = focus->activeNum;

			scaleSelectWindow(focus);
		}
	}
}

static void scaleWindowRemove(CompDisplay * d, Window id)
{
	CompWindow *w;

	w = findWindowAtDisplay(d, id);
	if (w)
	{
		SCALE_SCREEN(w->screen);

		if (ss->grab && ss->state != SCALE_STATE_IN)
		{
			int i;

			for (i = 0; i < ss->nWindows; i++)
			{
				if (ss->windows[i] == w)
				{
					if (layoutThumbs(w->screen))
					{
						ss->state = SCALE_STATE_OUT;
						damageScreen(w->screen);
						break;
					}
				}
			}
		}
	}
}

static Bool scaleHoverTimeout(void *closure)
{
	CompScreen *s = closure;

	SCALE_SCREEN(s);
	SCALE_DISPLAY(s->display);

	if (ss->grab && ss->state != SCALE_STATE_IN)
	{
		CompOption o;
		CompAction *action =
				&sd->opt[SCALE_DISPLAY_OPTION_INITIATE_NORMAL].value.action;

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

		scaleTerminate(s->display, action, 0, &o, 1);
	}

	ss->hoverHandle = 0;

	return FALSE;
}

static void scaleHandleEvent(CompDisplay * d, XEvent * event)
{
	CompScreen *s;

	SCALE_DISPLAY(d);

	switch (event->type)
	{
	case KeyPress:
		s = findScreenAtDisplay(d, event->xkey.root);
		if (s)
		{
			SCALE_SCREEN(s);

			if (ss->grab)
			{
				if (event->xkey.keycode == sd->leftKeyCode)
					scaleMoveFocusWindow(s, -1, 0);
				else if (event->xkey.keycode == sd->rightKeyCode)
					scaleMoveFocusWindow(s, 1, 0);
				else if (event->xkey.keycode == sd->upKeyCode)
					scaleMoveFocusWindow(s, 0, -1);
				else if (event->xkey.keycode == sd->downKeyCode)
					scaleMoveFocusWindow(s, 0, 1);
			}
		}
		break;
	case ButtonPress:
		s = findScreenAtDisplay(d, event->xbutton.root);
		if (s)
		{
			CompAction *action =
					&sd->opt[SCALE_DISPLAY_OPTION_INITIATE_NORMAL].value.
					action;

			SCALE_SCREEN(s);


			if (ss->grab && ss->state != SCALE_STATE_IN)
			{
				ss->clicked = TRUE;
				CompOption o;

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

				if (event->xbutton.button == Button3)
				{
					CompWindow *w;

					w = scaleCheckForWindowAt(s,
											  event->xbutton.x_root,
											  event->xbutton.y_root);

					if (w)
						zoomWindow(s, w);
				}
				else if (event->xbutton.button == Button2)
				{
					CompWindow *w;

					w = scaleCheckForWindowAt(s,
											  event->xbutton.x_root,
											  event->xbutton.y_root);

					if (w)
						closeWindow(w, event->xkey.time);

				}
				else if (scaleSelectWindowAt(s,
											 event->xbutton.x_root,
											 event->xbutton.y_root) &&
						 event->xbutton.button == Button1)
				{
					scaleTerminate(d, action, 0, &o, 1);
				}
				else if (event->xbutton.button == Button1)
				{
					scaleTerminate(d, action, 0, &o, 1);
				}
				else
					ss->clicked = FALSE;
				/*else if (event->xbutton.x_root > s->workArea.x &&
				   event->xbutton.x_root < (s->workArea.x +
				   s->workArea.width) &&
				   event->xbutton.y_root > s->workArea.y &&
				   event->xbutton.y_root < (s->workArea.y +
				   s->workArea.height))
				   {
				   scaleTerminate (d, action, 0, &o, 1);
				   } */
				//disabled exit-scale-by-invalid-click-into-show-desktop
			}
		}
		break;
	case MotionNotify:
		s = findScreenAtDisplay(d, event->xmotion.root);
		if (s)
		{
			SCALE_SCREEN(s);

			if (ss->grab && ss->state != SCALE_STATE_IN)
				scaleHoverWindowAt(s,
								   event->xmotion.x_root,
								   event->xmotion.y_root,
								   ss->opt[SCALE_SCREEN_OPTION_SLOPPY_FOCUS].
								   value.b);
		}
		break;
	case ClientMessage:
		if (event->xclient.message_type == d->xdndPositionAtom)
		{
			CompWindow *w;

			w = findWindowAtDisplay(d, event->xclient.window);
			if (w)
			{
				SCALE_SCREEN(w->screen);

				s = w->screen;

				if (w->id == ss->dndTarget)
					sendDndStatusMessage(w->screen, event->xclient.data.l[0]);

				if (ss->grab && ss->state != SCALE_STATE_IN &&
					w->id == ss->dndTarget)
				{
					int x = event->xclient.data.l[2] >> 16;
					int y = event->xclient.data.l[2] & 0xffff;

					w = scaleCheckForWindowAt(s, x, y);
					if (w && isScaleWin(w))
					{
						if (ss->hoverHandle)
						{
							if (w->activeNum != sd->lastActiveNum)
							{
								compRemoveTimeout(ss->hoverHandle);
								ss->hoverHandle = 0;
							}
						}

						if (!ss->hoverHandle)
							ss->hoverHandle =
									compAddTimeout(ss->
												   opt
												   [SCALE_SCREEN_OPTION_HOVER_TIME].
												   value.i, scaleHoverTimeout,
												   s);

						scaleSelectWindowAt(s, x, y);
					}
					else
					{
						if (ss->hoverHandle)
							compRemoveTimeout(ss->hoverHandle);

						ss->hoverHandle = 0;
					}
				}
			}
		}
		else if (event->xclient.message_type == d->xdndDropAtom
				 || event->xclient.message_type == d->xdndLeaveAtom)
		{
			CompWindow *w;

			w = findWindowAtDisplay(d, event->xclient.window);
			if (w)
			{
				CompAction *action =
						&sd->
						opt[SCALE_DISPLAY_OPTION_INITIATE_NORMAL].value.
						action;

				SCALE_SCREEN(w->screen);

				if (ss->grab &&
					ss->state != SCALE_STATE_IN && w->id == ss->dndTarget)
				{
					CompOption o;

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

					scaleTerminate(d, action, 0, &o, 1);
				}
			}
		}
		break;
	default:
		break;
	}

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

	switch (event->type)
	{
	case UnmapNotify:
		scaleWindowRemove(d, event->xunmap.window);
		break;
	case DestroyNotify:
		scaleWindowRemove(d, event->xdestroywindow.window);
		break;
	}

}

static Bool scaleDamageWindowRect(CompWindow * w, Bool initial, BoxPtr rect)
{
	Bool status = FALSE;

	SCALE_SCREEN(w->screen);

	if (initial)
	{
		if (ss->grab && isScaleWin(w))
		{
			if (layoutThumbs(w->screen))
			{
				ss->state = SCALE_STATE_OUT;
				damageScreen(w->screen);
			}
		}
	}
	else if (ss->state == SCALE_STATE_WAIT)
	{
		SCALE_WINDOW(w);

		if (sw->slot)
		{
			damageTransformedWindowRect(w, sw->scale,
										sw->scale, sw->tx, sw->ty, rect);

			status = TRUE;
		}
	}

	UNWRAP(ss, w->screen, damageWindowRect);
	status |= (*w->screen->damageWindowRect) (w, initial, rect);
	WRAP(ss, w->screen, damageWindowRect, scaleDamageWindowRect);

	return status;
}

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

	SCALE_DISPLAY(display);

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

	if (!o)
		return FALSE;

	switch (index)
	{
	case SCALE_DISPLAY_OPTION_INITIATE_NORMAL:
	case SCALE_DISPLAY_OPTION_INITIATE_APP:
	case SCALE_DISPLAY_OPTION_INITIATE_CURRENT_HEAD:
	case SCALE_DISPLAY_OPTION_INITIATE_ALL:
		if (setDisplayAction(display, o, value))
			return TRUE;
	default:
		break;
	}

	return FALSE;
}

static void scaleDisplayInitOptions(ScaleDisplay * sd)
{
	CompOption *o;

	o = &sd->opt[SCALE_DISPLAY_OPTION_INITIATE_NORMAL];
	o->advanced = False;
	o->name = "initiate";
	o->group = N_("Bindings");
	o->subGroup = N_("Initiate Window Picker");
	o->displayHints = "";
	o->shortDesc = N_("Initiate Window Picker");
	o->longDesc = N_("Layout and start transforming Windows.");
	o->type = CompOptionTypeAction;
	o->value.action.initiate = scaleInitiate;
	o->value.action.terminate = scaleTerminate;
	o->value.action.bell = FALSE;
	o->value.action.edgeMask = 0;
	o->value.action.state = CompActionStateInitEdge;;
	o->value.action.state |= CompActionStateInitEdgeDnd;
	o->value.action.type = CompBindingTypeKey;
	o->value.action.state |= CompActionStateInitKey;
	o->value.action.state |= CompActionStateInitButton;
	o->value.action.key.modifiers = SCALE_INITIATE_MODIFIERS_DEFAULT;
	o->value.action.key.keysym = XStringToKeysym(SCALE_INITIATE_KEY_DEFAULT);

	o = &sd->opt[SCALE_DISPLAY_OPTION_INITIATE_APP];
	o->advanced = False;
	o->name = "initiate_app";
	o->group = N_("Bindings");
	o->subGroup = N_("Initiate Window Picker for Current App");
	o->displayHints = "";
	o->shortDesc = N_("Initiate Window Picker for Current App");
	o->longDesc =
			N_
			("Layout and start transforming Windows of Current Application.");
	o->type = CompOptionTypeAction;
	o->value.action.initiate = scaleInitiateApp;
	o->value.action.terminate = scaleTerminate;
	o->value.action.bell = FALSE;
	o->value.action.edgeMask = 0;
	o->value.action.state = CompActionStateInitEdge;
	o->value.action.state |= CompActionStateInitEdgeDnd;
	o->value.action.type = CompBindingTypeKey;
	o->value.action.state |= CompActionStateInitKey;
	o->value.action.state |= CompActionStateInitButton;
	o->value.action.key.modifiers = SCALE_INITIATE_APP_MODIFIERS_DEFAULT;
	o->value.action.key.keysym =
			XStringToKeysym(SCALE_INITIATE_APP_KEY_DEFAULT);

	o = &sd->opt[SCALE_DISPLAY_OPTION_INITIATE_ALL];
	o->advanced = False;
	o->name = "initiate_all";
	o->group = N_("Bindings");
	o->subGroup = N_("Initiate Window Picker for All Workspaces");
	o->displayHints = "";
	o->shortDesc = N_("Initiate Window Picker for All Workspaces");
	o->longDesc =
			N_("Layout and start transforming Windows from All Workspaces.");
	o->type = CompOptionTypeAction;
	o->value.action.initiate = scaleInitiateAll;
	o->value.action.terminate = scaleTerminate;
	o->value.action.bell = FALSE;
	o->value.action.edgeMask = (1 << SCREEN_EDGE_TOPRIGHT);
	o->value.action.state = CompActionStateInitEdge;
	o->value.action.state |= CompActionStateInitEdgeDnd;
	o->value.action.type = CompBindingTypeKey;
	o->value.action.state |= CompActionStateInitKey;
	o->value.action.state |= CompActionStateInitButton;
	o->value.action.key.modifiers = SCALE_INITIATE_ALL_MODIFIERS_DEFAULT;
	o->value.action.key.keysym =
			XStringToKeysym(SCALE_INITIATE_ALL_KEY_DEFAULT);

	o = &sd->opt[SCALE_DISPLAY_OPTION_INITIATE_CURRENT_HEAD];
	o->advanced = False;
	o->name = "initiate_current_head";
	o->group = N_("Bindings");
	o->subGroup = N_("Initiate current head scale only");
	o->displayHints = "";
	o->shortDesc = N_("Initiate Current Head Scale Only");
	o->longDesc = N_("Only Scale windows on the Current Monitor.");
	o->type = CompOptionTypeAction;
	o->value.action.initiate = scaleInitiateCurrentHead;
	o->value.action.terminate = scaleTerminate;
	o->value.action.bell = FALSE;
	o->value.action.edgeMask = 0;
	o->value.action.state = CompActionStateInitEdge;
	o->value.action.state |= CompActionStateInitEdgeDnd;
	o->value.action.type = CompBindingTypeKey;
	o->value.action.state |= CompActionStateInitKey;
	o->value.action.state |= CompActionStateInitButton;
	o->value.action.key.modifiers =
			SCALE_INITIATE_CURRENT_HEAD_MODIFIERS_DEFAULT;
	o->value.action.key.keysym =
			XStringToKeysym(SCALE_INITIATE_CURRENT_HEAD_KEY_DEFAULT);

}

static CompOption *scaleGetDisplayOptions(CompDisplay * display, int *count)
{
	if (display)
	{
		SCALE_DISPLAY(display);

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

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


static Bool scaleInitDisplay(CompPlugin * p, CompDisplay * d)
{
	ScaleDisplay *sd;

	sd = malloc(sizeof(ScaleDisplay));
	if (!sd)
		return FALSE;

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

	sd->lastActiveNum = None;

	scaleDisplayInitOptions(sd);

	sd->leftKeyCode = XKeysymToKeycode(d->display, XStringToKeysym("Left"));
	sd->rightKeyCode = XKeysymToKeycode(d->display, XStringToKeysym("Right"));
	sd->upKeyCode = XKeysymToKeycode(d->display, XStringToKeysym("Up"));
	sd->downKeyCode = XKeysymToKeycode(d->display, XStringToKeysym("Down"));

	WRAP(sd, d, handleEvent, scaleHandleEvent);

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

	return TRUE;
}

static void scaleFiniDisplay(CompPlugin * p, CompDisplay * d)
{
	SCALE_DISPLAY(d);

	freeScreenPrivateIndex(d, sd->screenPrivateIndex);

	UNWRAP(sd, d, handleEvent);

	free(sd);
}

static Bool scaleInitScreen(CompPlugin * p, CompScreen * s)
{
	ScaleScreen *ss;

	SCALE_DISPLAY(s->display);

	ss = malloc(sizeof(ScaleScreen));
	if (!ss)
		return FALSE;
	ss->clicked = FALSE;

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

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

	ss->hoverHandle = 0;
	ss->dndTarget = None;

	ss->state = SCALE_STATE_NONE;
	ss->useClass = SCALE_USE_CLASS_DEFAULT;

	ss->slots = 0;
	ss->slotsSize = 0;

	ss->windows = 0;
	ss->windowsSize = 0;

	ss->line = 0;
	ss->lineSize = 0;

	ss->scale = 1.0f;

	ss->spacing = SCALE_SPACING_DEFAULT;
	ss->speed = SCALE_SPEED_DEFAULT;
	ss->timestep = SCALE_TIMESTEP_DEFAULT;
	ss->opacity = (OPAQUE * SCALE_OPACITY_DEFAULT) / 100;
	ss->darkenBackFactor = SCALE_DARKEN_BACK_FACTOR_DEFAULT;

	ss->onlyCurrent = FALSE;
	ss->currentWindow = 0;
	ss->selectedWindow = 0;
	ss->allScreensMode = FALSE;

	ss->darkenBack = SCALE_DARKEN_BACK_DEFAULT;
	ss->currentHead = FALSE;

	ss->head = 0;

	scaleSetHead(ss, s, SCALE_HEAD_DEFAULT);

	ss->iconOverlay = ScaleIconEmblem;
	ss->scaleMethod = SCALE_METHOD_INT_DEFAULT;

	ss->mmMode = MULTIMONITOR_MODE_DEFAULT;

	ss->scaleStateAtom =
			IPCS_GetAtom(IPCS_OBJECT(s), IPCS_VPTR, "SCALE_STATE_INT_PTR",
						 TRUE);
	IPCS_SetVPtr(IPCS_OBJECT(s), ss->scaleStateAtom, (void *)&ss->state);

	scaleScreenInitOptions(ss);

	addScreenAction(s,
					&sd->opt[SCALE_DISPLAY_OPTION_INITIATE_NORMAL].
					value.action);
	addScreenAction(s,
					&sd->opt[SCALE_DISPLAY_OPTION_INITIATE_APP].value.action);
	addScreenAction(s,
					&sd->opt[SCALE_DISPLAY_OPTION_INITIATE_ALL].value.action);
	addScreenAction(s,
					&sd->
					opt[SCALE_DISPLAY_OPTION_INITIATE_CURRENT_HEAD].
					value.action);

	WRAP(ss, s, preparePaintScreen, scalePreparePaintScreen);
	WRAP(ss, s, donePaintScreen, scaleDonePaintScreen);
	WRAP(ss, s, paintScreen, scalePaintScreen);
	WRAP(ss, s, paintWindow, scalePaintWindow);
	WRAP(ss, s, damageWindowRect, scaleDamageWindowRect);

	ss->cursor = XCreateFontCursor(s->display->display, XC_left_ptr);

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

	return TRUE;
}

static void scaleFiniScreen(CompPlugin * p, CompScreen * s)
{
	SCALE_SCREEN(s);
	SCALE_DISPLAY(s->display);

	UNWRAP(ss, s, preparePaintScreen);
	UNWRAP(ss, s, donePaintScreen);
	UNWRAP(ss, s, paintScreen);
	UNWRAP(ss, s, paintWindow);
	UNWRAP(ss, s, damageWindowRect);

	removeScreenAction(s,
					   &sd->opt[SCALE_DISPLAY_OPTION_INITIATE_NORMAL].
					   value.action);
	removeScreenAction(s,
					   &sd->opt[SCALE_DISPLAY_OPTION_INITIATE_APP].
					   value.action);
	removeScreenAction(s,
					   &sd->opt[SCALE_DISPLAY_OPTION_INITIATE_ALL].
					   value.action);
	removeScreenAction(s,
					   &sd->
					   opt[SCALE_DISPLAY_OPTION_INITIATE_CURRENT_HEAD].
					   value.action);

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

	if (ss->slotsSize)
		free(ss->slots);

	if (ss->lineSize)
		free(ss->line);

	if (ss->windowsSize)
		free(ss->windows);

	XFreeCursor(s->display->display, ss->cursor);

	freeWindowPrivateIndex(s, ss->windowPrivateIndex);

	free(ss);
}

static Bool scaleInitWindow(CompPlugin * p, CompWindow * w)
{
	ScaleWindow *sw;

	SCALE_SCREEN(w->screen);

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

	sw->animationAtom =
			IPCS_GetAtom(IPCS_OBJECT(w), IPCS_BOOL, "IS_ANIMATED", True);
	sw->slot = 0;
	sw->scale = 1.0f;
	sw->tx = sw->ty = 0.0f;
	sw->adjust = FALSE;
	sw->xVelocity = sw->yVelocity = 0.0f;
	sw->scaleVelocity = 1.0f;
	sw->delta = 1.0f;
	sw->rescaled = FALSE;
	sw->wasMinimized = FALSE;
	sw->oldScale = -1.0f;

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

	setWinPort(w);

	return TRUE;
}

static void scaleFiniWindow(CompPlugin * p, CompWindow * w)
{
	SCALE_WINDOW(w);

	free(sw);
}

static Bool scaleInit(CompPlugin * p)
{
	displayPrivateIndex = allocateDisplayPrivateIndex();
	if (displayPrivateIndex < 0)
		return FALSE;

	return TRUE;
}

static void scaleFini(CompPlugin * p)
{
	if (displayPrivateIndex >= 0)
		freeDisplayPrivateIndex(displayPrivateIndex);
}

CompPluginDep setDeps[] = {
	{CompPluginRuleAfter, "decoration"}
	,
	{CompPluginRuleAfter, "animation"}
	,
	{CompPluginRuleAfter, "cube"}
	,
	{CompPluginRuleAfter, "wobbly"}
	,
	{CompPluginRuleAfter, "fade"}
	,
};

CompPluginVTable scaleVTable = {
	"scale",
	N_("Scale"),
	N_("Scale windows"),
	scaleInit,
	scaleFini,
	scaleInitDisplay,
	scaleFiniDisplay,
	scaleInitScreen,
	scaleFiniScreen,
	scaleInitWindow,
	scaleFiniWindow,
	scaleGetDisplayOptions,
	scaleSetDisplayOption,
	scaleGetScreenOptions,
	scaleSetScreenOption,
	setDeps,
	sizeof(setDeps) / sizeof(setDeps[0]),
	0,
	0,
	BERYL_ABI_INFO,
	"beryl-plugins",
	"wm",
	0,
	0,
	True,
};

CompPluginVTable *getCompPluginInfo(void)
{
	return &scaleVTable;
}


syntax highlighted by Code2HTML, v. 0.9.1