/*
 * 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>
 */

#define _GNU_SOURCE				/* for asprintf */

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

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

#include <beryl.h>

#define ROTATE_POINTER_INVERT_Y_DEFAULT FALSE

#define ROTATE_POINTER_SENSITIVITY_DEFAULT   1.0f
#define ROTATE_POINTER_SENSITIVITY_MIN       0.01f
#define ROTATE_POINTER_SENSITIVITY_MAX       100.0f
#define ROTATE_POINTER_SENSITIVITY_PRECISION 0.01f

#define ROTATE_POINTER_SENSITIVITY_FACTOR 0.05f

#define ROTATE_ACCELERATION_DEFAULT   6.0f
#define ROTATE_ACCELERATION_MIN       1.0f
#define ROTATE_ACCELERATION_MAX       20.0f
#define ROTATE_ACCELERATION_PRECISION 0.1f

#define ROTATE_INITIATE_BUTTON_DEFAULT    Button1
#define ROTATE_INITIATE_MODIFIERS_DEFAULT (ControlMask | CompAltMask)

#define ROTATE_INITIATE_STICKY_BUTTON_DEFAULT    Button2
#define ROTATE_INITIATE_STICKY_MODIFIERS_DEFAULT (ControlMask | CompAltMask)

#define ROTATE_INITIATEDESKTOP_BUTTON_DEFAULT    Button2
#define ROTATE_INITIATEDESKTOP_MODIFIERS_DEFAULT 0

#define ROTATE_WHEELINGS_DEFAULT 1
#define ROTATE_WHEELINGS_MIN 1
#define ROTATE_WHEELINGS_MAX 20

#define ROTATE_WHEEL_ON_EDGES_DEFAULT FALSE

#define ROTATE_WHEELMAXDIST_DEFAULT 5
#define ROTATE_WHEELMAXDIST_MIN 1
#define ROTATE_WHEELMAXDIST_MAX 300

#define ROTATE_LEFT_WHEEL_DEFAULT    Button4
#define ROTATE_LEFT_WHEEL_MODIFIERS_DEFAULT   0

#define ROTATE_RIGHT_WHEEL_DEFAULT    Button5
#define ROTATE_RIGHT_WHEEL_MODIFIERS_DEFAULT  0

#define ROTATE_LEFT_KEY_DEFAULT       "Left"
#define ROTATE_LEFT_MODIFIERS_DEFAULT (ControlMask | CompAltMask)

#define ROTATE_RIGHT_KEY_DEFAULT       "Right"
#define ROTATE_RIGHT_MODIFIERS_DEFAULT (ControlMask | CompAltMask)

#define ROTATE_UP_KEY_DEFAULT       "Up"
#define ROTATE_UP_MODIFIERS_DEFAULT (ControlMask | CompAltMask)

#define ROTATE_DOWN_KEY_DEFAULT       "Down"
#define ROTATE_DOWN_MODIFIERS_DEFAULT (ControlMask | CompAltMask)

#define ROTATE_LEFT_WINDOW_KEY_DEFAULT       "Left"
#define ROTATE_LEFT_WINDOW_MODIFIERS_DEFAULT \
    (ShiftMask | ControlMask | CompAltMask)

#define ROTATE_RIGHT_WINDOW_KEY_DEFAULT       "Right"
#define ROTATE_RIGHT_WINDOW_MODIFIERS_DEFAULT \
    (ShiftMask | ControlMask | CompAltMask)

#define ROTATE_SNAP_TOP_DEFAULT TRUE
#define ROTATE_SNAP_BOTTOM_DEFAULT TRUE

#define ROTATE_SPEED_DEFAULT   2.5f
#define ROTATE_SPEED_MIN       0.1f
#define ROTATE_SPEED_MAX       50.0f
#define ROTATE_SPEED_PRECISION 0.1f

#define ROTATE_TIMESTEP_DEFAULT   0.9f
#define ROTATE_TIMESTEP_MIN       0.1f
#define ROTATE_TIMESTEP_MAX       50.0f
#define ROTATE_TIMESTEP_PRECISION 0.1f

#define ROTATE_MANTIMESTEP_DEFAULT   2.5f
#define ROTATE_MANTIMESTEP_MIN       0.1f
#define ROTATE_MANTIMESTEP_MAX       50.0f
#define ROTATE_MANTIMESTEP_PRECISION 0.1f

#define ROTATE_EDGEFLIP_POINTER_DEFAULT FALSE
#define ROTATE_EDGEFLIP_WINDOW_DEFAULT  TRUE
#define ROTATE_EDGEFLIP_DND_DEFAULT     TRUE

#define ROTATE_FLIPTIME_DEFAULT 250
#define ROTATE_FLIPTIME_MIN     0
#define ROTATE_FLIPTIME_MAX     1000

#define ROTATE_ZOOM_DEFAULT   0.0f
#define ROTATE_ZOOM_MIN       0.0f
#define ROTATE_ZOOM_MAX       15.0f
#define ROTATE_ZOOM_PRECISION 0.1f

#define ROTATE_ZOOM_BEFORE_ROTATE_DEFAULT FALSE

static int displayPrivateIndex;

#define ROTATE_DISPLAY_OPTION_INITIATE          0
#define ROTATE_DISPLAY_OPTION_LEFT              1
#define ROTATE_DISPLAY_OPTION_RIGHT             2
#define ROTATE_DISPLAY_OPTION_LEFT_WINDOW       3
#define ROTATE_DISPLAY_OPTION_RIGHT_WINDOW      4
#define ROTATE_DISPLAY_OPTION_EDGEFLIP_POINTER  5
#define ROTATE_DISPLAY_OPTION_EDGEFLIP_WINDOW   6
#define ROTATE_DISPLAY_OPTION_EDGEFLIP_DND      7
#define ROTATE_DISPLAY_OPTION_FLIPTIME          8
#define ROTATE_DISPLAY_OPTION_TO_1              9
#define ROTATE_DISPLAY_OPTION_TO_2             10
#define ROTATE_DISPLAY_OPTION_TO_3             11
#define ROTATE_DISPLAY_OPTION_TO_4             12
#define ROTATE_DISPLAY_OPTION_TO_5             13
#define ROTATE_DISPLAY_OPTION_TO_6             14
#define ROTATE_DISPLAY_OPTION_TO_7             15
#define ROTATE_DISPLAY_OPTION_TO_8             16
#define ROTATE_DISPLAY_OPTION_TO_9             17
#define ROTATE_DISPLAY_OPTION_TO_10            18
#define ROTATE_DISPLAY_OPTION_TO_11            19
#define ROTATE_DISPLAY_OPTION_TO_12            20
#define ROTATE_DISPLAY_OPTION_TO_1_WINDOW      21
#define ROTATE_DISPLAY_OPTION_TO_2_WINDOW      22
#define ROTATE_DISPLAY_OPTION_TO_3_WINDOW      23
#define ROTATE_DISPLAY_OPTION_TO_4_WINDOW      24
#define ROTATE_DISPLAY_OPTION_TO_5_WINDOW      25
#define ROTATE_DISPLAY_OPTION_TO_6_WINDOW      26
#define ROTATE_DISPLAY_OPTION_TO_7_WINDOW      27
#define ROTATE_DISPLAY_OPTION_TO_8_WINDOW      28
#define ROTATE_DISPLAY_OPTION_TO_9_WINDOW      29
#define ROTATE_DISPLAY_OPTION_TO_10_WINDOW     30
#define ROTATE_DISPLAY_OPTION_TO_11_WINDOW     31
#define ROTATE_DISPLAY_OPTION_TO_12_WINDOW     32
#define ROTATE_DISPLAY_OPTION_TO               33
#define ROTATE_DISPLAY_OPTION_WINDOW           34
#define ROTATE_DISPLAY_OPTION_WHEELINGS        35
#define ROTATE_DISPLAY_OPTION_LEFT_WHEEL       36
#define ROTATE_DISPLAY_OPTION_RIGHT_WHEEL      37
#define ROTATE_DISPLAY_OPTION_UP               38
#define ROTATE_DISPLAY_OPTION_DOWN             39
#define ROTATE_DISPLAY_OPTION_INITIATEDESKTOP  40
#define ROTATE_DISPLAY_OPTION_STICKY_INITIATE  41
#define ROTATE_DISPLAY_OPTION_FLIP_LEFT        42
#define ROTATE_DISPLAY_OPTION_FLIP_RIGHT       43

#define ROTATE_DISPLAY_OPTION_NUM              44

typedef struct _RotateDisplay
{
	int screenPrivateIndex;
	HandleEventProc handleEvent;

	CompOption opt[ROTATE_DISPLAY_OPTION_NUM];

	unsigned int leftWheelings, rightWheelings;
} RotateDisplay;

#define ROTATE_SCREEN_OPTION_POINTER_INVERT_Y       0
#define ROTATE_SCREEN_OPTION_POINTER_SENSITIVITY    1
#define ROTATE_SCREEN_OPTION_ACCELERATION           2
#define ROTATE_SCREEN_OPTION_SNAP_TOP               3
#define ROTATE_SCREEN_OPTION_SPEED                  4
#define ROTATE_SCREEN_OPTION_TIMESTEP               5
#define ROTATE_SCREEN_OPTION_MANTIMESTEP            6
#define ROTATE_SCREEN_OPTION_ZOOM                   7
#define ROTATE_SCREEN_OPTION_SNAP_BOTTOM            8
#define ROTATE_SCREEN_OPTION_ZOOM_BEFORE_ROTATE     9
#define ROTATE_SCREEN_OPTION_ZOOM_ONLY_ON_INITIATE 10
#define ROTATE_SCREEN_OPTION_WHEEL_ON_EDGES        11
#define ROTATE_SCREEN_OPTION_WHEELMAXDIST          12
#define ROTATE_SCREEN_OPTION_NUM                   13


typedef struct _RotateScreen
{
	PreparePaintScreenProc preparePaintScreen;
	DonePaintScreenProc donePaintScreen;
	PaintScreenProc paintScreen;
	SetScreenOptionForPluginProc setScreenOptionForPlugin;
	WindowGrabNotifyProc windowGrabNotify;
	WindowUngrabNotifyProc windowUngrabNotify;

	CompOption opt[ROTATE_SCREEN_OPTION_NUM];

	float pointerSensitivity;
	Bool snapTop;
	Bool snapBottom;

	float zoom;

	int grabIndex;

	GLfloat xrot, xVelocity;
	GLfloat yrot, yVelocity;

	float zoomTranslate;
	GLfloat zoomVelocity;

	GLfloat baseXrot;
	GLfloat baseYrot;

	Bool moving;
	GLfloat moveTo;
	Bool zooming;
	Bool rotating;

	Bool movingVert;
	GLfloat moveToY;

	int invert;

	Window moveWindow;
	int moveWindowX;

	XPoint savedPointer;
	Bool grabbed;
	int manualAtom;
	CompTimeoutHandle rotateHandle;
	Bool slow;
	unsigned int grabMask;
	CompWindow *grabWindow;

	int previousRotationAtom;
	int snapTopBottomAtom;
} RotateScreen;

#define GET_ROTATE_DISPLAY(d)                       \
    ((RotateDisplay *) (d)->privates[displayPrivateIndex].ptr)

#define ROTATE_DISPLAY(d)               \
    RotateDisplay *rd = GET_ROTATE_DISPLAY (d)

#define GET_ROTATE_SCREEN(s, rd)                   \
    ((RotateScreen *) (s)->privates[(rd)->screenPrivateIndex].ptr)

#define ROTATE_SCREEN(s)                              \
    RotateScreen *rs = GET_ROTATE_SCREEN (s, GET_ROTATE_DISPLAY (s->display))

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

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

	ROTATE_SCREEN(screen);

	o = compFindOption(rs->opt, NUM_OPTIONS(rs), name, &index);
	if (!o)
		return FALSE;

	switch (index)
	{
	case ROTATE_SCREEN_OPTION_POINTER_INVERT_Y:
	case ROTATE_SCREEN_OPTION_ZOOM_BEFORE_ROTATE:
	case ROTATE_SCREEN_OPTION_ZOOM_ONLY_ON_INITIATE:
	case ROTATE_SCREEN_OPTION_WHEEL_ON_EDGES:
		if (compSetBoolOption(o, value))
			return TRUE;
		break;
	case ROTATE_SCREEN_OPTION_POINTER_SENSITIVITY:
		if (compSetFloatOption(o, value))
		{
			rs->pointerSensitivity = o->value.f *
					ROTATE_POINTER_SENSITIVITY_FACTOR;
			return TRUE;
		}
		break;
	case ROTATE_SCREEN_OPTION_SNAP_TOP:
		if (compSetBoolOption(o, value))
		{
			rs->snapTop = o->value.b;
			return TRUE;
		}
		break;
	case ROTATE_SCREEN_OPTION_SNAP_BOTTOM:
		if (compSetBoolOption(o, value))
		{
			rs->snapBottom = o->value.b;
			return TRUE;
		}
		break;
	case ROTATE_SCREEN_OPTION_ACCELERATION:
	case ROTATE_SCREEN_OPTION_SPEED:
	case ROTATE_SCREEN_OPTION_TIMESTEP:
	case ROTATE_SCREEN_OPTION_MANTIMESTEP:
		if (compSetFloatOption(o, value))
			return TRUE;
		break;
	case ROTATE_SCREEN_OPTION_ZOOM:
		if (compSetFloatOption(o, value))
		{
			if (o->value.f < 0.05f)
			{
				rs->zooming = FALSE;
				rs->zoom = 0.0f;
			}
			else
			{
				rs->zooming = TRUE;
				rs->zoom = o->value.f / 30.0f;
			}

			return TRUE;
		}
		break;
	case ROTATE_SCREEN_OPTION_WHEELMAXDIST:
		if (compSetIntOption(o, value))
			return TRUE;
	default:
		break;
	}

	return FALSE;
}

static void rotateScreenInitOptions(RotateScreen * rs)
{
	CompOption *o;

	o = &rs->opt[ROTATE_SCREEN_OPTION_POINTER_INVERT_Y];
	o->advanced = False;
	o->name = "invert_y";
	o->group = N_("Behaviour");
	o->subGroup = N_("General");
	o->displayHints = "";
	o->shortDesc = N_("Pointer Invert Y");
	o->longDesc =
			N_("Invert Y axis for Pointer movement while Rotating the Cube.");
	o->type = CompOptionTypeBool;
	o->value.b = ROTATE_POINTER_INVERT_Y_DEFAULT;

	o = &rs->opt[ROTATE_SCREEN_OPTION_POINTER_SENSITIVITY];
	o->advanced = False;
	o->name = "sensitivity";
	o->group = N_("Misc. Options");
	o->subGroup = N_("");
	o->displayHints = "";
	o->shortDesc = N_("Pointer Sensitivity");
	o->longDesc = N_("Sensitivity of Pointer movement.");
	o->type = CompOptionTypeFloat;
	o->value.f = ROTATE_POINTER_SENSITIVITY_DEFAULT;
	o->rest.f.min = ROTATE_POINTER_SENSITIVITY_MIN;
	o->rest.f.max = ROTATE_POINTER_SENSITIVITY_MAX;
	o->rest.f.precision = ROTATE_POINTER_SENSITIVITY_PRECISION;

	o = &rs->opt[ROTATE_SCREEN_OPTION_ACCELERATION];
	o->advanced = False;
	o->name = "acceleration";
	o->group = N_("Misc. Options");
	o->subGroup = N_("");
	o->displayHints = "";
	o->shortDesc = N_("Acceleration");
	o->longDesc = N_("Acceleration of Cube Rotation.");
	o->type = CompOptionTypeFloat;
	o->value.f = ROTATE_ACCELERATION_DEFAULT;
	o->rest.f.min = ROTATE_ACCELERATION_MIN;
	o->rest.f.max = ROTATE_ACCELERATION_MAX;
	o->rest.f.precision = ROTATE_ACCELERATION_PRECISION;

	o = &rs->opt[ROTATE_SCREEN_OPTION_SNAP_TOP];
	o->advanced = False;
	o->name = "snap_top";
	o->group = N_("Behaviour");
	o->subGroup = N_("General");
	o->displayHints = "";
	o->shortDesc = N_("Snap To Top Face");
	o->longDesc = N_("Snap Cube Rotation to Top Face.");
	o->type = CompOptionTypeBool;
	o->value.b = ROTATE_SNAP_TOP_DEFAULT;

	o = &rs->opt[ROTATE_SCREEN_OPTION_SNAP_BOTTOM];
	o->advanced = False;
	o->name = "snap_bottom";
	o->group = N_("Behaviour");
	o->subGroup = N_("General");
	o->displayHints = "";
	o->shortDesc = N_("Snap To Bottom Face");
	o->longDesc = N_("Snap Cube Rotation to Bottom Face.");
	o->type = CompOptionTypeBool;
	o->value.b = ROTATE_SNAP_BOTTOM_DEFAULT;

	o = &rs->opt[ROTATE_SCREEN_OPTION_SPEED];
	o->advanced = False;
	o->name = "speed";
	o->group = N_("Misc. Options");
	o->subGroup = N_("");
	o->displayHints = "";
	o->shortDesc = N_("Rotation Speed");
	o->longDesc = N_("Rotation Speed.");
	o->type = CompOptionTypeFloat;
	o->value.f = ROTATE_SPEED_DEFAULT;
	o->rest.f.min = ROTATE_SPEED_MIN;
	o->rest.f.max = ROTATE_SPEED_MAX;
	o->rest.f.precision = ROTATE_SPEED_PRECISION;

	o = &rs->opt[ROTATE_SCREEN_OPTION_TIMESTEP];
	o->advanced = False;
	o->name = "timestep";
	o->group = N_("Misc. Options");
	o->subGroup = N_("");
	o->displayHints = "";
	o->shortDesc = N_("Auto Timestep");
	o->longDesc = N_("Rotation Timestep on Automatic Rotation.");
	o->type = CompOptionTypeFloat;
	o->value.f = ROTATE_TIMESTEP_DEFAULT;
	o->rest.f.min = ROTATE_TIMESTEP_MIN;
	o->rest.f.max = ROTATE_TIMESTEP_MAX;
	o->rest.f.precision = ROTATE_TIMESTEP_PRECISION;

	o = &rs->opt[ROTATE_SCREEN_OPTION_MANTIMESTEP];
	o->advanced = False;
	o->name = "mantimestep";
	o->group = N_("Misc. Options");
	o->subGroup = N_("");
	o->displayHints = "";
	o->shortDesc = N_("Manual Timestep");
	o->longDesc = N_("Rotation Timestep on Manual Rotation.");
	o->type = CompOptionTypeFloat;
	o->value.f = ROTATE_MANTIMESTEP_DEFAULT;
	o->rest.f.min = ROTATE_MANTIMESTEP_MIN;
	o->rest.f.max = ROTATE_MANTIMESTEP_MAX;
	o->rest.f.precision = ROTATE_MANTIMESTEP_PRECISION;

	o = &rs->opt[ROTATE_SCREEN_OPTION_ZOOM];
	o->advanced = False;
	o->name = "zoom";
	o->group = N_("Behaviour");
	o->subGroup = N_("General");
	o->displayHints = "";
	o->shortDesc = N_("Zoom");
	o->longDesc =
			N_("Distance desktop should be Zoomed out while Rotating.");
	o->type = CompOptionTypeFloat;
	o->value.f = ROTATE_ZOOM_DEFAULT;
	o->rest.f.min = ROTATE_ZOOM_MIN;
	o->rest.f.max = ROTATE_ZOOM_MAX;
	o->rest.f.precision = ROTATE_ZOOM_PRECISION;

	o = &rs->opt[ROTATE_SCREEN_OPTION_ZOOM_BEFORE_ROTATE];
	o->advanced = False;
	o->name = "zoom_before_rotate";
	o->group = N_("Behaviour");
	o->subGroup = N_("General");
	o->displayHints = "";
	o->shortDesc = N_("Zoom Before Rotate");
	o->longDesc = N_("Zoom out before doing Rotation.");
	o->type = CompOptionTypeBool;
	o->value.b = ROTATE_ZOOM_BEFORE_ROTATE_DEFAULT;

	o = &rs->opt[ROTATE_SCREEN_OPTION_ZOOM_ONLY_ON_INITIATE];
	o->advanced = False;
	o->name = "zoom_initiate_only";
	o->group = N_("Behaviour");
	o->subGroup = N_("General");
	o->displayHints = "";
	o->shortDesc = N_("Zoom Only on Initiate");
	o->longDesc =
			N_
			("Zoom out only when the 'Initiate' key/mouse combo is pressed.");
	o->type = CompOptionTypeBool;
	o->value.b = FALSE;

	o = &rs->opt[ROTATE_SCREEN_OPTION_WHEEL_ON_EDGES];
	o->advanced = False;
	o->name = "wheel_on_edges";
	o->group = N_("Behaviour");
	o->subGroup = N_("General");
	o->displayHints = "";
	o->shortDesc = N_("Rotate With Wheel Near Edges");
	o->longDesc = N_("Enable Rotate through Mouse Wheel near screen Edges.");
	o->type = CompOptionTypeBool;
	o->value.b = ROTATE_WHEEL_ON_EDGES_DEFAULT;

	o = &rs->opt[ROTATE_SCREEN_OPTION_WHEELMAXDIST];
	o->advanced = False;
	o->name = "wheel_max_dist";
	o->group = N_("Misc. Options");
	o->subGroup = N_("");
	o->displayHints = "";
	o->shortDesc = N_("Max Distance From Edge to Rotate");
	o->longDesc =
			N_
			("Maximum cursor Distance from Edges to initiate Rotate with wheel.");
	o->type = CompOptionTypeInt;
	o->value.i = ROTATE_WHEELMAXDIST_DEFAULT;
	o->rest.i.min = ROTATE_WHEELMAXDIST_MIN;
	o->rest.i.max = ROTATE_WHEELMAXDIST_MAX;

}

static CompOption *rotateGetScreenOptions(CompScreen * screen, int *count)
{
	if (screen)
	{
		ROTATE_SCREEN(screen);

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

		rotateScreenInitOptions(rs);
		*count = NUM_OPTIONS(rs);
		return rs->opt;
	}
}


static int adjustVelocity(CompScreen * s, RotateScreen * rs, int size)
{
	float xrot, yrot, adjust, amount;

	if (rs->moving)
	{
		xrot = rs->moveTo + (rs->xrot + rs->baseXrot);
	}
	else
	{
		xrot = rs->xrot;
		if (rs->xrot < -180.0f / size)
			xrot = 360.0f / size + rs->xrot;
		else if (rs->xrot > 180.0f / size)
			xrot = rs->xrot - 360.0f / size;
		IPCS_SetBool(IPCS_OBJECT(s), rs->snapTopBottomAtom, TRUE);
	}

	adjust = -xrot * 0.05f *
			rs->opt[ROTATE_SCREEN_OPTION_ACCELERATION].value.f;
	amount = fabs(xrot);
	if (amount < 10.0f)
		amount = 10.0f;
	else if (amount > 30.0f)
		amount = 30.0f;

	if (rs->slow)
		adjust *= 0.05f;

	rs->xVelocity = (amount * rs->xVelocity + adjust) / (amount + 2.0f);

	if (rs->movingVert)
	{
		yrot = rs->moveToY + (rs->yrot + rs->baseYrot);
	}
	else
	{
		if (rs->snapTop && s->hsize > 2 && rs->yrot > 50.0f)
		{
			yrot = -(90.f - rs->yrot);
		}
		else if (rs->snapBottom && s->hsize > 2 && rs->yrot < -50.0f)
		{
			yrot = (90.0f + rs->yrot);
		}
		else
		{
			yrot = rs->yrot;
		}
	}

	adjust = -yrot * 0.05f *
			rs->opt[ROTATE_SCREEN_OPTION_ACCELERATION].value.f;
	amount = fabs(rs->yrot);
	if (amount < 10.0f)
		amount = 10.0f;
	else if (amount > 30.0f)
		amount = 30.0f;

	rs->yVelocity = (amount * rs->yVelocity + adjust) / (amount + 2.0f);

	return (fabs(xrot) < 0.1f && fabs(rs->xVelocity) < 0.2f &&
			fabs(yrot) < 0.1f && fabs(rs->yVelocity) < 0.2f);
}

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

	ROTATE_SCREEN(s);

	w = findWindowAtScreen(s, rs->moveWindow);
	if (w)
		syncWindowPosition(w);

	rs->moveWindow = None;
}

static void rotatePreparePaintScreen(CompScreen * s, int msSinceLastPaint)
{
	ROTATE_SCREEN(s);

	Bool performZooming = FALSE;

	if (rs->zooming && (!otherScreenGrabExist(s, "rotate", 0) ||
						(rs->zoomTranslate != 0.0f
						 && rs->zoomTranslate != rs->zoom)))
		performZooming = TRUE;

	if (rs->grabIndex || rs->moving || rs->movingVert)
	{
		int steps, stepsCount;
		float amount, chunk;

		amount = msSinceLastPaint * 0.05f *
				rs->opt[ROTATE_SCREEN_OPTION_SPEED].value.f;
		steps = stepsCount =
				amount / (0.5f *
						  (rs->grabbed ? rs->
						   opt[ROTATE_SCREEN_OPTION_MANTIMESTEP].value.
						   f : rs->opt[ROTATE_SCREEN_OPTION_TIMESTEP].value.
						   f));

		if (!steps)
			steps = stepsCount = 1;
		chunk = amount / (float)steps;

		if (performZooming &&
			rs->opt[ROTATE_SCREEN_OPTION_ZOOM_BEFORE_ROTATE].value.
			b && rs->rotating)
		{
			steps = stepsCount;

			while (steps--)
			{
				float dt, adjust, tamount;

				dt = rs->zoom - rs->zoomTranslate;

				adjust = dt * 0.15f;
				tamount = fabs(dt) * 1.5f;
				if (tamount < 0.2f)
					tamount = 0.2f;
				else if (tamount > 2.0f)
					tamount = 2.0f;

				rs->zoomVelocity =
						(tamount * rs->zoomVelocity + adjust) / (tamount +
																 1.0f);

				if (fabs(dt) < 0.1f && fabs(rs->zoomVelocity) < 0.0005f)
				{
					rs->zoomTranslate = rs->zoom;
					break;
				}
				rs->zoomTranslate += rs->zoomVelocity * chunk;
			}

			if (rs->zoomTranslate != rs->zoom)
			{
				UNWRAP(rs, s, preparePaintScreen);
				(*s->preparePaintScreen) (s, msSinceLastPaint);
				WRAP(rs, s, preparePaintScreen, rotatePreparePaintScreen);
				return;
			}
		}

		steps = stepsCount;
		while (steps--)
		{
			rs->xrot += rs->xVelocity * chunk;
			rs->yrot += rs->yVelocity * chunk;

			if (rs->xrot > 360.0f / s->hsize)
			{
				rs->baseXrot += 360.0f / s->hsize;
				rs->xrot -= 360.0f / s->hsize;
			}
			else if (rs->xrot < 0.0f)
			{
				rs->baseXrot -= 360.0f / s->hsize;
				rs->xrot += 360.0f / s->hsize;
			}

			if (rs->yrot > 100.0f)
			{
				rs->yVelocity = 0.0f;
				rs->yrot = 100.0f;
			}
			else if (rs->yrot < -100.0f)
			{
				rs->yVelocity = 0.0f;
				rs->yrot = -100.0f;
			}

			if (rs->grabbed)
			{
				rs->xVelocity /= 1.25f;
				rs->yVelocity /= 1.25f;

				if (fabs(rs->xVelocity) < 0.01f)
					rs->xVelocity = 0.0f;
				if (fabs(rs->yVelocity) < 0.01f)
					rs->yVelocity = 0.0f;
			}
			else if (adjustVelocity(s, rs, s->hsize))
			{
				rs->xVelocity = 0.0f;
				rs->yVelocity = 0.0f;

				damageScreen(s);

				if ((fabs(rs->yrot) < 0.1f))
					//&& !screenGrabExist(s, "scale", 0))
				{
					float xrot;
					int tx;

					xrot = rs->baseXrot + rs->xrot;
					if (xrot < 0.0f)
						tx = (s->hsize * xrot / 360.0f) - 0.5f;
					else
						tx = (s->hsize * xrot / 360.0f) + 0.5f;

					if (s->hsize == 4)
					{
						float previousRotation = IPCS_GetFloat(IPCS_OBJECT(s),
															   rs->
															   previousRotationAtom);
						IPCS_SetFloat(IPCS_OBJECT(s),
									  rs->previousRotationAtom,
									  previousRotation + tx);
					}

					compDisplayClearRequestFlagForPlugin
							(s->display, "rotate", "ENABLE_3D");

					moveScreenViewport(s, tx, 0, TRUE);

					rs->xrot = 0.0f;
					rs->yrot = 0.0f;
					rs->baseXrot = rs->moveTo = 0.0f;
					rs->baseYrot = rs->moveToY = 0.0f;

					rs->moving = FALSE;
					rs->movingVert = FALSE;

					if (rs->grabIndex)
					{
						removeScreenGrab(s, rs->grabIndex, &rs->savedPointer);
						rs->grabIndex = 0;
					}

					if (rs->moveWindow)
					{
						CompWindow *w;

						w = findWindowAtScreen(s, rs->moveWindow);
						if (w)
						{
							moveWindow(w,
									   rs->
									   moveWindowX
									   - w->attrib.x, 0, TRUE, TRUE);
							syncWindowPosition(w);
						}
					}
					else
						focusDefaultWindow(s->display);

					rs->moveWindow = 0;
				}
				break;
			}
		}

		if (rs->moveWindow)
		{
			CompWindow *w;

			w = findWindowAtScreen(s, rs->moveWindow);
			if (w)
			{
				float xrot = (s->hsize * (rs->baseXrot + rs->xrot)) / 360.0f;

				moveWindowToViewportPosition(w,
											 rs->
											 moveWindowX -
											 xrot * s->width, FALSE);
			}
		}
	}

	if (fabs(rs->xrot) < 0.01f && fabs(rs->yrot) < 0.01f && !rs->grabbed)
		rs->rotating = FALSE;
	else
		rs->rotating = TRUE;

	if (performZooming
		&& !rs->opt[ROTATE_SCREEN_OPTION_ZOOM_BEFORE_ROTATE].value.b
		&& (rs->rotating || rs->zoomTranslate != 0.0f)
		&& !(rs->opt[ROTATE_SCREEN_OPTION_ZOOM_ONLY_ON_INITIATE].value.
			 b && !(rs->grabbed || rs->zoomTranslate != 0.0f)))
	{

		int steps, stepsCount;
		float amount, chunk;

		amount = msSinceLastPaint * 0.05f *
				rs->opt[ROTATE_SCREEN_OPTION_SPEED].value.f;
		steps = stepsCount =
				amount / (0.5f *
						  rs->opt[ROTATE_SCREEN_OPTION_TIMESTEP].value.f);
		if (!steps)
			steps = stepsCount = 1;
		chunk = amount / (float)steps;
		Bool zoomOut = FALSE;

		if (rs->moving
			&& fabs(rs->xrot + rs->baseXrot + rs->moveTo) >
			(360.0 / (s->hsize * 2.0)))
		{
			zoomOut = TRUE;
		}

		if (rs->movingVert
			&& fabs(rs->yrot + rs->baseYrot + rs->moveToY) > 45.0)
		{
			zoomOut = TRUE;
		}

		if (rs->grabbed)
			zoomOut = TRUE;

		while (steps--)
		{
			float dt, adjust, tamount;

			if (rs->rotating && zoomOut)
				dt = rs->zoom - rs->zoomTranslate;
			else
				dt = 0.0f - rs->zoomTranslate;

			adjust = dt * 0.15f;
			tamount = fabs(dt) * 1.5f;
			if (tamount < 0.2f)
				tamount = 0.2f;
			else if (tamount > 2.0f)
				tamount = 2.0f;

			rs->zoomVelocity =
					(tamount * rs->zoomVelocity + adjust) / (tamount + 1.0f);

			if (fabs(dt) < 0.1f && fabs(rs->zoomVelocity) < 0.0005f)
			{
				if (rs->rotating && zoomOut)
					rs->zoomTranslate = rs->zoom;
				else
					rs->zoomTranslate = 0.0f;
				break;
			}
			rs->zoomTranslate += rs->zoomVelocity * chunk;
		}
	}

	if (performZooming
		&& rs->opt[ROTATE_SCREEN_OPTION_ZOOM_BEFORE_ROTATE].value.b
		&& !rs->rotating && rs->zoomTranslate != 0.0f && rs->yrot == 0.0f)
	{
		int steps, stepsCount;
		float amount, chunk;

		amount = msSinceLastPaint * 0.05f *
				rs->opt[ROTATE_SCREEN_OPTION_SPEED].value.f;
		steps = stepsCount =
				amount / (0.5f *
						  rs->opt[ROTATE_SCREEN_OPTION_TIMESTEP].value.f);
		if (!steps)
			steps = stepsCount = 1;
		chunk = amount / (float)steps;

		while (steps--)
		{
			float dt, adjust, tamount;

			dt = 0.0f - rs->zoomTranslate;

			adjust = dt * 0.15f;
			tamount = fabs(dt) * 1.5f;
			if (tamount < 0.2f)
				tamount = 0.2f;
			else if (tamount > 2.0f)
				tamount = 2.0f;

			rs->zoomVelocity =
					(tamount * rs->zoomVelocity + adjust) / (tamount + 1.0f);

			if (fabs(dt) < 0.1f && fabs(rs->zoomVelocity) < 0.0005f)
			{
				rs->zoomTranslate = 0.0f;
				break;
			}
			rs->zoomTranslate += rs->zoomVelocity * chunk;
		}
		if (rs->zoomTranslate != 0.0f)
		{
			UNWRAP(rs, s, preparePaintScreen);
			(*s->preparePaintScreen) (s, msSinceLastPaint);
			WRAP(rs, s, preparePaintScreen, rotatePreparePaintScreen);
			return;
		}
	}

	UNWRAP(rs, s, preparePaintScreen);
	(*s->preparePaintScreen) (s, msSinceLastPaint);
	WRAP(rs, s, preparePaintScreen, rotatePreparePaintScreen);
}

static void rotateDonePaintScreen(CompScreen * s)
{
	ROTATE_SCREEN(s);

	if (rs->grabIndex || rs->moving || rs->movingVert)
	{
		if ((!rs->grabbed && !rs->snapTop && !rs->snapBottom)
			|| rs->xVelocity || rs->yVelocity || rs->zooming)
			damageScreen(s);
	}
	else if (rs->zooming && rs->zoomTranslate != 0.0f)
		damageScreen(s);

	UNWRAP(rs, s, donePaintScreen);
	(*s->donePaintScreen) (s);
	WRAP(rs, s, donePaintScreen, rotateDonePaintScreen);
}

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

	ROTATE_SCREEN(s);

	if (rs->grabIndex || rs->moving || rs->movingVert)
	{
		ScreenPaintAttrib sa = *sAttrib;

		if (rs->zooming)
			sa.zCamera -= rs->zoomTranslate;

		sa.xRotate += rs->baseXrot + rs->xrot;
		sa.vRotate += rs->baseYrot + rs->yrot;

		mask &= ~PAINT_SCREEN_REGION_MASK;
		mask |= PAINT_SCREEN_TRANSFORMED_MASK;

		UNWRAP(rs, s, paintScreen);
		status = (*s->paintScreen) (s, &sa, region, output, mask);
		WRAP(rs, s, paintScreen, rotatePaintScreen);
	}
	else if (rs->zooming && rs->zoomTranslate != 0.0f)
	{
		ScreenPaintAttrib sa = *sAttrib;

		sa.zCamera -= rs->zoomTranslate;
		mask &= ~PAINT_SCREEN_REGION_MASK;
		mask |= PAINT_SCREEN_TRANSFORMED_MASK;

		UNWRAP(rs, s, paintScreen);
		status = (*s->paintScreen) (s, &sa, region, output, mask);
		WRAP(rs, s, paintScreen, rotatePaintScreen);
	}
	else
	{
		UNWRAP(rs, s, paintScreen);
		status = (*s->paintScreen) (s, sAttrib, region, output, mask);
		WRAP(rs, s, paintScreen, rotatePaintScreen);
	}

	return status;
}

static Bool
rotateInitiate(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)
	{
		ROTATE_SCREEN(s);
		IPCS_SetBool(IPCS_OBJECT(s), rs->snapTopBottomAtom, FALSE);

		if (s->hsize < 2)
			return FALSE;

		if (rs->rotateHandle && rs->grabWindow)
		{
			if (otherScreenGrabExist(s, "rotate", "move", 0))
				return FALSE;
		}
		else
		{
			if (otherScreenGrabExist(s, "rotate", "switcher", "cube", 0))
				return FALSE;
		}

		rs->moving = FALSE;
		rs->slow = FALSE;
		rs->movingVert = FALSE;
		rs->moveTo = 0.0f;
		rs->moveToY = 0.0f;

		compDisplaySetRequestFlagForPlugin(s->display, "rotate", "ENABLE_3D");

		rs->rotating = FALSE;
		if ((state & CompActionStateInitButton))
		{
			IPCS_SetBool(IPCS_OBJECT(s), rs->manualAtom, TRUE);
		}
		else
		{
			IPCS_SetBool(IPCS_OBJECT(s), rs->manualAtom, FALSE);
		}
		if (!rs->grabIndex)
		{
			rs->grabIndex = pushScreenGrab(s, s->invisibleCursor, "rotate");
			if (rs->grabIndex)
			{
				int x, y;

				x = getIntOptionNamed(option, nOption, "x", 0);
				y = getIntOptionNamed(option, nOption, "y", 0);

				rs->savedPointer.x = x;
				rs->savedPointer.y = y;
			}
		}

		if (rs->grabIndex)
		{
			rs->moveTo = 0.0f;
			rs->moveToY = 0.0f;

			rs->grabbed = TRUE;
			rs->snapTop = rs->opt[ROTATE_SCREEN_OPTION_SNAP_TOP].value.b;
			rs->snapBottom =
					rs->opt[ROTATE_SCREEN_OPTION_SNAP_BOTTOM].value.b;

			if (state & CompActionStateInitButton)
				action->state |= CompActionStateTermButton;

			if (state & CompActionStateInitKey)
				action->state |= CompActionStateTermKey;
		}
	}

	return TRUE;
}

static Bool
rotateInitiateDesktop(CompDisplay * d,
					  CompAction * action,
					  CompActionState state, CompOption * option, int nOption)
{
	CompScreen *s = findScreenAtDisplay(d,
										getIntOptionNamed(option, nOption,
														  "root", 0));

	if (s)
	{
		int x, y;

		x = getIntOptionNamed(option, nOption, "x", 0);
		y = getIntOptionNamed(option, nOption, "y", 0);

		if (pointerOnlyOnDesktop(s, x, y) || screenGrabExist(s, "rotate", 0))
			return rotateInitiate(d, action, state, option, nOption);
	}
	return FALSE;
}


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

	xid = getIntOptionNamed(option, nOption, "root", 0);
	for (s = d->screens; s; s = s->next)
	{
		ROTATE_SCREEN(s);

		if (xid && s->root != xid)
			continue;

		rs->rotating = FALSE;

		if (rs->grabIndex)
		{
			if (!xid)
			{
				rs->snapTop = FALSE;
				rs->snapBottom = FALSE;
			}

			rs->grabbed = FALSE;
			damageScreen(s);
		}
	}

	action->state &= ~(CompActionStateTermButton | CompActionStateTermKey);

	return FALSE;
}

static Bool
rotate(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)
	{
		int direction;

		ROTATE_SCREEN(s);

		if (s->hsize < 2)
			return FALSE;

		if (otherScreenGrabExist
			(s, "rotate", "move", "switcher", "cube", "scale", "group-drag",
			 0))
			return FALSE;

		/* FIXME: this is sort of a hack - we check if
		   the state of scale is "waiting for user
		   we definitely need a better communication 
		   mechanism here... */

		int *scaleState = (int *)IPCS_GetVPtrND(IPCS_OBJECT(s),
												"SCALE_STATE_INT_PTR", NULL);

		if (scaleState && (*scaleState == 2))
			return FALSE;

		direction = getIntOptionNamed(option, nOption, "direction", 0);
		if (!direction)
			return FALSE;

		if (rs->moveWindow)
			rotateReleaseMoveWindow(s);

		/* we allow the grab to fail here so that we can rotate on
		   drag-and-drop */
		if (!rs->grabIndex)
		{
			CompOption o[3];

			o[0].type = CompOptionTypeInt;
			o[0].name = "x";
			o[0].value.i = getIntOptionNamed(option, nOption, "x", 0);

			o[1].type = CompOptionTypeInt;
			o[1].name = "y";
			o[1].value.i = getIntOptionNamed(option, nOption, "y", 0);

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

			rotateInitiate(d, NULL, 0, o, 3);
		}

		rs->moving = TRUE;
		rs->moveTo += (360.0f / s->hsize) * direction;
		rs->grabbed = FALSE;

		damageScreen(s);
	}

	return FALSE;
}

static Bool
rotateVert(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 && s->hsize > 2)
	{
		int direction;

		ROTATE_SCREEN(s);

		if (otherScreenGrabExist(s, "rotate", "move", "switcher", "cube", 0))
			return FALSE;

		direction = getIntOptionNamed(option, nOption, "direction", 0);
		if (!direction)
			return FALSE;

		if (rs->moveWindow)
			rotateReleaseMoveWindow(s);

		/* we allow the grab to fail here so that we can rotate on
		   drag-and-drop */
		if (!rs->grabIndex)
		{
			CompOption o[3];

			o[0].type = CompOptionTypeInt;
			o[0].name = "x";
			o[0].value.i = getIntOptionNamed(option, nOption, "x", 0);

			o[1].type = CompOptionTypeInt;
			o[1].name = "y";
			o[1].value.i = getIntOptionNamed(option, nOption, "y", 0);

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

			rotateInitiate(d, NULL, 0, o, 3);
		}

		if (fabs(rs->moveToY) == 90.0f
			&& (rs->moveToY + -90.0f * direction) != 0.0f)
		{
			direction = -direction;
			rs->moving = TRUE;
			rs->moveTo += 180.0f * direction;
		}

		rs->movingVert = TRUE;
		rs->moveToY += -90.0f * direction;
		rs->grabbed = FALSE;

		damageScreen(s);
	}

	return FALSE;
}

static Bool
rotateWithWindow(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)
	{
		int direction;

		ROTATE_SCREEN(s);

		if (s->hsize < 2)
			return FALSE;

		direction = getIntOptionNamed(option, nOption, "direction", 0);
		if (!direction)
			return FALSE;

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

		if (rs->moveWindow != xid)
		{
			CompWindow *w;

			rotateReleaseMoveWindow(s);

			w = findWindowAtScreen(s, xid);
			if (w)
			{
				if (!
					(w->
					 type & (CompWindowTypeDesktopMask |
							 CompWindowTypeDockMask)))
				{
					if (!(w->state & CompWindowStateStickyMask))
					{
						rs->moveWindow = w->id;
						rs->moveWindowX = w->attrib.x;
						raiseWindow(w);
					}
				}
			}
		}

		if (!rs->grabIndex)
		{
			CompOption o[3];

			o[0].type = CompOptionTypeInt;
			o[0].name = "x";
			o[0].value.i = getIntOptionNamed(option, nOption, "x", 0);

			o[1].type = CompOptionTypeInt;
			o[1].name = "y";
			o[1].value.i = getIntOptionNamed(option, nOption, "y", 0);

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

			rotateInitiate(d, NULL, 0, o, 3);
		}

		if (rs->grabIndex)
		{
			rs->moving = TRUE;
			rs->moveTo += (360.0f / s->hsize) * direction;
			rs->grabbed = FALSE;

			damageScreen(s);
		}
	}

	return TRUE;
}

static Bool
pointerNearScreenEdge(CompScreen * s, int x, int y, int maxDistance)
{
	if (((x - s->x) >= -maxDistance && (x - s->x) <= maxDistance)
		|| ((s->x + s->width - x) >= -maxDistance
			&& (s->x + s->width - x) <= maxDistance)
		|| ((y - s->y) >= -maxDistance && (y - s->y) <= maxDistance)
		|| ((s->y + s->height - y) >= -maxDistance
			&& (s->y + s->height - y) <= maxDistance))
		return TRUE;
	return FALSE;
}

static Bool
rotateWheel(CompDisplay * d, int direction, CompOption * option, int nOption)
{
	ROTATE_DISPLAY(d);
	CompScreen *s = findScreenAtDisplay(d,
										getIntOptionNamed(option,
														  nOption,
														  "root", 0));

	if (s)
	{
		ROTATE_SCREEN(s);
		int x, y;
		unsigned int *wheelings;

		if (direction == -1)
		{
			wheelings = &rd->rightWheelings;
			rd->leftWheelings = 0;
		}
		else
		{
			wheelings = &rd->leftWheelings;
			rd->rightWheelings = 0;
		}

		x = getIntOptionNamed(option, nOption, "x", 0);
		y = getIntOptionNamed(option, nOption, "y", 0);

		if (pointerOnlyOnDesktop(s, x, y)
			|| (rs->opt[ROTATE_SCREEN_OPTION_WHEEL_ON_EDGES].value.
				b
				&& pointerNearScreenEdge(s, x, y,
										 rs->
										 opt
										 [ROTATE_SCREEN_OPTION_WHEELMAXDIST].
										 value.i)))
		{
			if (++(*wheelings) ==
				rd->opt[ROTATE_DISPLAY_OPTION_WHEELINGS].value.i)
			{
				*wheelings = 0;

				CompOption o[4];

				o[0].type = CompOptionTypeInt;
				o[0].name = "x";
				o[0].value.i = getIntOptionNamed(option, nOption, "x", 0);

				o[1].type = CompOptionTypeInt;
				o[1].name = "y";
				o[1].value.i = getIntOptionNamed(option, nOption, "y", 0);

				o[2].type = CompOptionTypeInt;
				o[2].name = "root";
				o[2].value.i = getIntOptionNamed(option, nOption, "root", 0);

				o[3].type = CompOptionTypeInt;
				o[3].name = "direction";
				o[3].value.i = direction;

				rotate(d, NULL, 0, o, 4);
			}
		}
		else
			*wheelings = 0;
	}

	return FALSE;
}

static Bool
rotateRightWheel(CompDisplay * d,
				 CompAction * action,
				 CompActionState state, CompOption * option, int nOption)
{
	return rotateWheel(d, 1, option, nOption);
}

static Bool
rotateLeftWheel(CompDisplay * d,
				CompAction * action,
				CompActionState state, CompOption * option, int nOption)
{
	return rotateWheel(d, -1, option, nOption);
}

static Bool
rotateReal(CompDisplay * d, Bool vert, int direction,
		   CompOption * option, int nOption)
{
	CompOption o[4];

	o[0].type = CompOptionTypeInt;
	o[0].name = "x";
	o[0].value.i = getIntOptionNamed(option, nOption, "x", 0);

	o[1].type = CompOptionTypeInt;
	o[1].name = "y";
	o[1].value.i = getIntOptionNamed(option, nOption, "y", 0);

	o[2].type = CompOptionTypeInt;
	o[2].name = "root";
	o[2].value.i = getIntOptionNamed(option, nOption, "root", 0);

	o[3].type = CompOptionTypeInt;
	o[3].name = "direction";
	o[3].value.i = direction;

	if (vert)
		rotateVert(d, NULL, 0, o, 4);
	else
		rotate(d, NULL, 0, o, 4);

	return FALSE;
}

static Bool
rotateLeft(CompDisplay * d,
		   CompAction * action,
		   CompActionState state, CompOption * option, int nOption)
{
	return rotateReal(d, FALSE, -1, option, nOption);
}

static Bool
rotateRight(CompDisplay * d,
			CompAction * action,
			CompActionState state, CompOption * option, int nOption)
{
	return rotateReal(d, FALSE, 1, option, nOption);
}

static Bool
rotateTop(CompDisplay * d,
		  CompAction * action,
		  CompActionState state, CompOption * option, int nOption)
{
	return rotateReal(d, TRUE, 1, option, nOption);
}

static Bool
rotateDown(CompDisplay * d,
		   CompAction * action,
		   CompActionState state, CompOption * option, int nOption)
{
	return rotateReal(d, TRUE, -1, option, nOption);
}

static Bool
rotateWithWindowReal(CompDisplay * d, int direction, CompOption * option,
					 int nOption)
{
	CompOption o[5];

	o[0].type = CompOptionTypeInt;
	o[0].name = "x";
	o[0].value.i = getIntOptionNamed(option, nOption, "x", 0);

	o[1].type = CompOptionTypeInt;
	o[1].name = "y";
	o[1].value.i = getIntOptionNamed(option, nOption, "y", 0);

	o[2].type = CompOptionTypeInt;
	o[2].name = "root";
	o[2].value.i = getIntOptionNamed(option, nOption, "root", 0);

	o[3].type = CompOptionTypeInt;
	o[3].name = "direction";
	o[3].value.i = direction;

	o[4].type = CompOptionTypeInt;
	o[4].name = "window";
	o[4].value.i = getIntOptionNamed(option, nOption, "window", 0);

	rotateWithWindow(d, NULL, 0, o, 5);

	return FALSE;
}

static Bool
rotateLeftWithWindow(CompDisplay * d,
					 CompAction * action,
					 CompActionState state, CompOption * option, int nOption)
{
	return rotateWithWindowReal(d, -1, option, nOption);
}

static Bool
rotateRightWithWindow(CompDisplay * d,
					  CompAction * action,
					  CompActionState state, CompOption * option, int nOption)
{
	return rotateWithWindowReal(d, 1, option, nOption);
}

static Bool rotateFlip(CompScreen * s, int direction)
{
	int warpX;
	CompOption o[4];

	ROTATE_SCREEN(s);

	rs->moveTo = 0.0f;
	rs->slow = FALSE;

	if (otherScreenGrabExist(s, "rotate", "move", "group-drag", 0))
		return FALSE;

	if (direction == 1)
	{
		//rotation to right
		warpX = s->display->pointerX - s->width;
		warpPointer(s->display, 10 - s->width, 0);
		s->display->lastPointerX = warpX;
	}
	else
	{
		warpX = s->display->pointerX + s->width;
		warpPointer(s->display, s->width - 10, 0);
		s->display->lastPointerX = warpX;
	}

	o[0].type = CompOptionTypeInt;
	o[0].name = "x";
	o[0].value.i = 0;

	o[1].type = CompOptionTypeInt;
	o[1].name = "y";
	o[1].value.i = s->display->pointerY;

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

	o[3].type = CompOptionTypeInt;
	o[3].name = "direction";
	o[3].value.i = direction;

	rotate(s->display, NULL, 0, o, 4);

	XWarpPointer(s->display->display, None, None, 0, 0, 0, 0, direction, 0);

	if (direction == 1)
		rs->savedPointer.x = s->display->lastPointerX + 9;
	else
		rs->savedPointer.x = s->display->lastPointerX - 9;

	rs->rotateHandle = 0;

	return FALSE;
}

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

	return rotateFlip(s, -1);
}

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

	return rotateFlip(s, 1);
}

static void
rotateEdgeFlip(CompScreen * s,
			   int edge,
			   CompAction * action,
			   CompActionState state, CompOption * option, int nOption)
{
	CompOption o[4];
	int direction;

	ROTATE_DISPLAY(s->display);
	ROTATE_SCREEN(s);

	if (s->hsize < 2)
		return;

	if (otherScreenGrabExist(s, "rotate", "move", "group-drag", 0))
		return;

	if (state & CompActionStateInitEdgeDnd)
	{
		if (!rd->opt[ROTATE_DISPLAY_OPTION_EDGEFLIP_DND].value.b)
			return;

		if (otherScreenGrabExist(s, "rotate", 0))
			return;
	}
	else if (screenGrabExist(s, "move", 0))
	{
		if (!rd->opt[ROTATE_DISPLAY_OPTION_EDGEFLIP_WINDOW].value.b)
			return;

		if (!rs->grabWindow)
			return;

		/* bail out if window is horizontally maximized or fullscreen */
		if (rs->grabWindow->
			state & (CompWindowStateMaximizedHorzMask |
					 CompWindowStateFullscreenMask))
			return;
	}
	else if (screenGrabExist(s, "group-drag", 0))
	{
		if (!rd->opt[ROTATE_DISPLAY_OPTION_EDGEFLIP_DND].value.b)
			return;
	}
	else
	{
		if (!rd->opt[ROTATE_DISPLAY_OPTION_EDGEFLIP_POINTER].value.b)
			return;
	}

	o[0].type = CompOptionTypeInt;
	o[0].name = "x";
	o[0].value.i = 0;

	o[1].type = CompOptionTypeInt;
	o[1].name = "y";
	o[1].value.i = s->display->pointerY;

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

	o[3].type = CompOptionTypeInt;
	o[3].name = "direction";

	if (edge == SCREEN_EDGE_LEFT)
		direction = -1;
	else
		direction = 1;

	if (rd->opt[ROTATE_DISPLAY_OPTION_FLIPTIME].value.i == 0
		|| (rs->moving && !rs->slow))
	{
		int pointerDx, warpX;

		if (edge == SCREEN_EDGE_LEFT)
		{
			pointerDx = s->display->pointerX - s->display->lastPointerX;
			warpX = s->display->pointerX + s->width;
			warpPointer(s->display, s->width - 10, 0);
			s->display->lastPointerX = warpX - pointerDx;
		}
		else
		{
			pointerDx = s->display->pointerX - s->display->lastPointerX;
			warpX = s->display->pointerX - s->width;
			warpPointer(s->display, 10 - s->width, 0);
			s->display->lastPointerX = warpX - pointerDx;
		}

		o[3].value.i = direction;
		rotate(s->display, NULL, 0, o, 4);

		XWarpPointer(s->display->display, None, None, 0, 0, 0, 0,
					 direction, 0);
		rs->savedPointer.x = s->display->lastPointerX + (direction * 9);
	}
	else
	{
		if (!rs->rotateHandle)
			rs->rotateHandle =
					compAddTimeout(rd->
								   opt
								   [ROTATE_DISPLAY_OPTION_FLIPTIME].
								   value.i,
								   (edge ==
									SCREEN_EDGE_LEFT) ?
								   rotateFlipLeft : rotateFlipRight, s);

		rs->moving = TRUE;
		rs->moveTo += (direction * 360.0f / s->hsize);
		rs->slow = TRUE;

		if (state & CompActionStateInitEdge)
			action->state |= CompActionStateTermEdge;

		if (state & CompActionStateInitEdgeDnd)
			action->state |= CompActionStateTermEdgeDnd;

		damageScreen(s);
	}
}

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

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

	for (s = d->screens; s; s = s->next)
	{
		ROTATE_SCREEN(s);

		if (xid && s->root != xid)
			continue;

		if (rs->rotateHandle)
		{
			compRemoveTimeout(rs->rotateHandle);
			rs->rotateHandle = 0;

			if (rs->slow)
			{
				rs->moveTo = 0.0f;
				rs->slow = FALSE;
			}

			damageScreen(s);
		}

		action->state &= ~(CompActionStateTermEdge |
						   CompActionStateTermEdgeDnd);
	}

	return FALSE;
}

static Bool
rotateEdgeFlipLeft(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)
		rotateEdgeFlip(s, SCREEN_EDGE_LEFT, action, state, option, nOption);

	return FALSE;
}

static Bool
rotateEdgeFlipRight(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)
		rotateEdgeFlip(s, SCREEN_EDGE_RIGHT, action, state, option, nOption);

	return FALSE;
}

static int rotateRotationTo(CompScreen * s, int face)
{
	int delta;

	ROTATE_SCREEN(s);

	delta = face - s->x - (rs->moveTo / (360.0f / s->hsize));
	if (delta > s->hsize / 2)
		delta -= s->hsize;
	else if (delta < -(s->hsize / 2))
		delta += s->hsize;

	return delta;
}

static Bool
rotateTo(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)
	{
		CompOption o[4];
		int face = -1;
		int i = ROTATE_DISPLAY_OPTION_TO_1;

		ROTATE_DISPLAY(s->display);

		while (i <= ROTATE_DISPLAY_OPTION_TO_12)
		{
			if (action == &rd->opt[i].value.action)
			{
				face = i - ROTATE_DISPLAY_OPTION_TO_1;
				break;
			}

			i++;
		}

		if (face < 0)
			face = getIntOptionNamed(option, nOption, "face", s->x);

		o[0].type = CompOptionTypeInt;
		o[0].name = "x";
		o[0].value.i =
				getIntOptionNamed(option, nOption, "x", s->display->pointerX);

		o[1].type = CompOptionTypeInt;
		o[1].name = "y";
		o[1].value.i =
				getIntOptionNamed(option, nOption, "y", s->display->pointerY);

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

		o[3].type = CompOptionTypeInt;
		o[3].name = "direction";
		o[3].value.i = rotateRotationTo(s, face);

		rotate(d, NULL, 0, o, 4);
	}

	return FALSE;
}

static Bool
rotateToWithWindow(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)
	{
		CompOption o[5];
		int face = -1;
		int i = ROTATE_DISPLAY_OPTION_TO_1_WINDOW;

		ROTATE_DISPLAY(s->display);

		while (i <= ROTATE_DISPLAY_OPTION_TO_12_WINDOW)
		{
			if (action == &rd->opt[i].value.action)
			{
				face = i - ROTATE_DISPLAY_OPTION_TO_1_WINDOW;
				break;
			}

			i++;
		}

		if (face < 0)
			face = getIntOptionNamed(option, nOption, "face", s->x);

		o[0].type = CompOptionTypeInt;
		o[0].name = "x";
		o[0].value.i =
				getIntOptionNamed(option, nOption, "x", s->display->pointerX);

		o[1].type = CompOptionTypeInt;
		o[1].name = "y";
		o[1].value.i =
				getIntOptionNamed(option, nOption, "y", s->display->pointerY);

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

		o[3].type = CompOptionTypeInt;
		o[3].name = "direction";
		o[3].value.i = rotateRotationTo(s, face);

		o[4].type = CompOptionTypeInt;
		o[4].name = "window";
		o[4].value.i = getIntOptionNamed(option, nOption, "window", 0);

		rotateWithWindow(d, NULL, 0, o, 5);
	}

	return FALSE;
}

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

	ROTATE_DISPLAY(d);

	switch (event->type)
	{
	case MotionNotify:
		s = findScreenAtDisplay(d, event->xmotion.root);
		if (s)
		{
			ROTATE_SCREEN(s);

			if (rs->grabIndex)
			{
				if (rs->grabbed)
				{
					GLfloat pointerDx, pointerDy;

					pointerDx = d->pointerX - d->lastPointerX;
					pointerDy = d->pointerY - d->lastPointerY;

					if (event->xmotion.x_root < 50 ||
						event->xmotion.y_root < 50 ||
						event->xmotion.x_root >
						s->width - 50
						|| event->xmotion.y_root > s->height - 50)
					{
						warpPointer(d,
									(s->width /
									 2) -
									d->pointerX,
									(s->height / 2) - d->pointerY);
					}

					if (rs->
						opt[ROTATE_SCREEN_OPTION_POINTER_INVERT_Y].value.b)
						pointerDy = -pointerDy;

					rs->xVelocity +=
							pointerDx * rs->pointerSensitivity * rs->invert;
					rs->yVelocity += pointerDy * rs->pointerSensitivity;

					damageScreen(s);
				}
				else
				{
					rs->savedPointer.x += d->pointerX - d->lastPointerX;
					rs->savedPointer.y += d->pointerY - d->lastPointerY;
				}
			}
		}
		break;
	case ClientMessage:
		if (event->xclient.message_type == d->winActiveAtom)
		{
			CompWindow *w;

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

				ROTATE_SCREEN(w->screen);

				s = w->screen;

				/* window must be placed */
				if (!w->placed)
					break;

				if (w->state & CompWindowStateOffscreenMask)
					break;

				if (otherScreenGrabExist
					(s, "rotate", "switcher", "cube", "scale", 0))
					break;

				/* reset movement */
				rs->moving = FALSE;
				rs->moveTo = 0.0f;
				rs->movingVert = FALSE;
				rs->moveToY = 0.0f;

				defaultViewportForWindow(w, &dx, NULL);
				dx -= s->x;
				if (dx)
				{
					Window win;
					int i, x, y;
					unsigned int ui;
					CompOption o[4];

					XQueryPointer(d->display, s->root,
								  &win, &win, &x, &y, &i, &i, &ui);

					if (dx > (s->hsize + 1) / 2)
						dx -= s->hsize;
					else if (dx < -(s->hsize + 1) / 2)
						dx += s->hsize;

					o[0].type = CompOptionTypeInt;
					o[0].name = "x";
					o[0].value.i = x;

					o[1].type = CompOptionTypeInt;
					o[1].name = "y";
					o[1].value.i = y;

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

					o[3].type = CompOptionTypeInt;
					o[3].name = "direction";
					o[3].value.i = dx;

					rotate(d, NULL, 0, o, 4);
				}
			}
		}
		else if (event->xclient.message_type == d->desktopViewportAtom)
		{
			s = findScreenAtDisplay(d, event->xclient.window);
			if (s)
			{
				int dx;

				if (otherScreenGrabExist
					(s, "rotate", "switcher", "cube", "scale", 0))
					break;

				dx = event->xclient.data.l[0] / s->width - s->x;
				if (dx)
				{
					Window win;
					int i, x, y;
					unsigned int ui;
					CompOption o[4];

					XQueryPointer(d->display, s->root,
								  &win, &win, &x, &y, &i, &i, &ui);

					if (dx > (s->hsize + 1) / 2)
						dx -= s->hsize;
					else if (dx < -(s->hsize + 1) / 2)
						dx += s->hsize;

					o[0].type = CompOptionTypeInt;
					o[0].name = "x";
					o[0].value.i = x;

					o[1].type = CompOptionTypeInt;
					o[1].name = "y";
					o[1].value.i = y;

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

					o[3].type = CompOptionTypeInt;
					o[3].name = "direction";
					o[3].value.i = dx;

					rotate(d, NULL, 0, o, 4);
				}
			}
		}
	default:
		break;
	}

	UNWRAP(rd, d, handleEvent);
	(*d->handleEvent) (d, event);
	WRAP(rd, d, handleEvent, rotateHandleEvent);
}

static void
rotateWindowGrabNotify(CompWindow * w,
					   int x, int y, unsigned int state, unsigned int mask)
{
	ROTATE_SCREEN(w->screen);

	if (!rs->grabWindow)
	{
		rs->grabMask = mask;
		rs->grabWindow = w;
	}

	UNWRAP(rs, w->screen, windowGrabNotify);
	(*w->screen->windowGrabNotify) (w, x, y, state, mask);
	WRAP(rs, w->screen, windowGrabNotify, rotateWindowGrabNotify);
}

static void rotateWindowUngrabNotify(CompWindow * w)
{
	ROTATE_SCREEN(w->screen);

	if (w == rs->grabWindow)
	{
		rs->grabMask = 0;
		rs->grabWindow = NULL;
	}

	UNWRAP(rs, w->screen, windowUngrabNotify);
	(*w->screen->windowUngrabNotify) (w);
	WRAP(rs, w->screen, windowUngrabNotify, rotateWindowUngrabNotify);
}

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

	ROTATE_SCREEN(s);

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

		options = (*p->vTable->getScreenOptions) (s, &nOptions);
		option = compFindOption(options, nOptions, "in", 0);
		if (option)
			rs->invert = option->value.b ? -1 : 1;
	}
}

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

	ROTATE_SCREEN(s);

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

	if (status && strcmp(plugin, "cube") == 0 && strcmp(name, "in") == 0)
		rotateUpdateCubeOptions(s);

	return status;
}

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

	ROTATE_DISPLAY(display);

	o = compFindOption(rd->opt, NUM_OPTIONS(rd), name, &index);
	if (!o)
		return FALSE;

	switch (index)
	{
	case ROTATE_DISPLAY_OPTION_INITIATE:
	case ROTATE_DISPLAY_OPTION_LEFT:
	case ROTATE_DISPLAY_OPTION_RIGHT:
	case ROTATE_DISPLAY_OPTION_UP:
	case ROTATE_DISPLAY_OPTION_DOWN:
	case ROTATE_DISPLAY_OPTION_TO_1:
	case ROTATE_DISPLAY_OPTION_TO_2:
	case ROTATE_DISPLAY_OPTION_TO_3:
	case ROTATE_DISPLAY_OPTION_TO_4:
	case ROTATE_DISPLAY_OPTION_TO_5:
	case ROTATE_DISPLAY_OPTION_TO_6:
	case ROTATE_DISPLAY_OPTION_TO_7:
	case ROTATE_DISPLAY_OPTION_TO_8:
	case ROTATE_DISPLAY_OPTION_TO_9:
	case ROTATE_DISPLAY_OPTION_TO_10:
	case ROTATE_DISPLAY_OPTION_TO_11:
	case ROTATE_DISPLAY_OPTION_TO_12:
	case ROTATE_DISPLAY_OPTION_LEFT_WINDOW:
	case ROTATE_DISPLAY_OPTION_RIGHT_WINDOW:
	case ROTATE_DISPLAY_OPTION_LEFT_WHEEL:
	case ROTATE_DISPLAY_OPTION_RIGHT_WHEEL:
	case ROTATE_DISPLAY_OPTION_TO_1_WINDOW:
	case ROTATE_DISPLAY_OPTION_TO_2_WINDOW:
	case ROTATE_DISPLAY_OPTION_TO_3_WINDOW:
	case ROTATE_DISPLAY_OPTION_TO_4_WINDOW:
	case ROTATE_DISPLAY_OPTION_TO_5_WINDOW:
	case ROTATE_DISPLAY_OPTION_TO_6_WINDOW:
	case ROTATE_DISPLAY_OPTION_TO_7_WINDOW:
	case ROTATE_DISPLAY_OPTION_TO_8_WINDOW:
	case ROTATE_DISPLAY_OPTION_TO_9_WINDOW:
	case ROTATE_DISPLAY_OPTION_TO_10_WINDOW:
	case ROTATE_DISPLAY_OPTION_TO_11_WINDOW:
	case ROTATE_DISPLAY_OPTION_TO_12_WINDOW:
	case ROTATE_DISPLAY_OPTION_FLIP_LEFT:
	case ROTATE_DISPLAY_OPTION_FLIP_RIGHT:
	case ROTATE_DISPLAY_OPTION_INITIATEDESKTOP:
	case ROTATE_DISPLAY_OPTION_STICKY_INITIATE:
		if (setDisplayAction(display, o, value))
			return TRUE;
		break;
	case ROTATE_DISPLAY_OPTION_WHEELINGS:
	case ROTATE_DISPLAY_OPTION_FLIPTIME:
		if (compSetIntOption(o, value))
			return TRUE;
		break;
	case ROTATE_DISPLAY_OPTION_TO:
	case ROTATE_DISPLAY_OPTION_WINDOW:
		if (compSetActionOption(o, value))
			return TRUE;
		break;
	case ROTATE_DISPLAY_OPTION_EDGEFLIP_POINTER:
	case ROTATE_DISPLAY_OPTION_EDGEFLIP_WINDOW:
	case ROTATE_DISPLAY_OPTION_EDGEFLIP_DND:
		if (compSetBoolOption(o, value))
			return TRUE;
		break;
	default:
		break;
	}

	return FALSE;
}

static void rotateDisplayInitOptions(RotateDisplay * rd)
{
	CompOption *o;
	char *str;

	o = &rd->opt[ROTATE_DISPLAY_OPTION_INITIATE];
	o->advanced = False;
	o->name = "initiate";
	o->group = N_("Bindings");
	o->subGroup = N_("Initiate");
	o->displayHints = "";
	o->shortDesc = N_("Initiate Rotation");
	o->longDesc = N_("Start Rotation.");
	o->type = CompOptionTypeAction;
	o->value.action.initiate = rotateInitiate;
	o->value.action.terminate = rotateTerminate;
	o->value.action.bell = FALSE;
	o->value.action.edgeMask = 0;
	o->value.action.state = CompActionStateInitKey;
	o->value.action.state |= CompActionStateInitButton;
	o->value.action.type = CompBindingTypeButton;
	o->value.action.button.modifiers = ROTATE_INITIATE_MODIFIERS_DEFAULT;
	o->value.action.button.button = ROTATE_INITIATE_BUTTON_DEFAULT;

	o = &rd->opt[ROTATE_DISPLAY_OPTION_STICKY_INITIATE];
	o->advanced = False;
	o->name = "initiate_sticky";
	o->group = N_("Bindings");
	o->subGroup = N_("Initiate");
	o->displayHints = "";
	o->shortDesc = N_("Initiate Sticky");
	o->longDesc =
			N_
			("Start Rotation and don't finish it until specifily terminated by"
			 " the normal Initiate.");
	o->type = CompOptionTypeAction;
	o->value.action.initiate = rotateInitiate;
	o->value.action.terminate = 0;
	o->value.action.bell = FALSE;
	o->value.action.edgeMask = 0;
	o->value.action.state = CompActionStateInitKey;
	o->value.action.state |= CompActionStateInitButton;
	o->value.action.type = CompBindingTypeButton;
	o->value.action.button.modifiers =
			ROTATE_INITIATE_STICKY_MODIFIERS_DEFAULT;
	o->value.action.button.button = ROTATE_INITIATE_STICKY_BUTTON_DEFAULT;

	o = &rd->opt[ROTATE_DISPLAY_OPTION_WHEELINGS];
	o->advanced = False;
	o->name = "rotate_wheelings";
	o->group = N_("Behaviour");
	o->subGroup = N_("General");
	o->displayHints = "";
	o->shortDesc = N_("Mouse Wheel Scrolls for Rotation");
	o->longDesc = N_("Number of Mouse Wheel Scrolls required for Rotation.");
	o->type = CompOptionTypeInt;
	o->value.i = ROTATE_WHEELINGS_DEFAULT;
	o->rest.i.min = ROTATE_WHEELINGS_MIN;
	o->rest.i.max = ROTATE_WHEELINGS_MAX;

	o = &rd->opt[ROTATE_DISPLAY_OPTION_LEFT_WHEEL];
	o->advanced = False;
	o->name = "rotate_left_wheel";
	o->group = N_("Bindings");
	o->subGroup = N_("Rotate Left");
	o->displayHints = "";
	o->shortDesc = N_("Rotate Left with Mouse Wheel");
	o->longDesc = N_("Rotate Left with Mouse Wheel.");
	o->type = CompOptionTypeAction;
	o->value.action.initiate = rotateLeftWheel;
	o->value.action.terminate = 0;
	o->value.action.bell = FALSE;
	o->value.action.edgeMask = 0;
	o->value.action.state = CompActionStateInitButton;
	o->value.action.type = CompBindingTypeButton;
	o->value.action.button.modifiers = ROTATE_LEFT_WHEEL_MODIFIERS_DEFAULT;
	o->value.action.button.button = ROTATE_LEFT_WHEEL_DEFAULT;

	o = &rd->opt[ROTATE_DISPLAY_OPTION_RIGHT_WHEEL];
	o->advanced = False;
	o->name = "rotate_right_wheel";
	o->group = N_("Bindings");
	o->subGroup = N_("Rotate Right");
	o->displayHints = "";
	o->shortDesc = N_("Rotate Right with Mouse Wheel");
	o->longDesc = N_("Rotate Right with Mouse Wheel.");
	o->type = CompOptionTypeAction;
	o->value.action.initiate = rotateRightWheel;
	o->value.action.terminate = 0;
	o->value.action.bell = FALSE;
	o->value.action.edgeMask = 0;
	o->value.action.state = CompActionStateInitButton;
	o->value.action.type = CompBindingTypeButton;
	o->value.action.button.modifiers = ROTATE_RIGHT_WHEEL_MODIFIERS_DEFAULT;
	o->value.action.button.button = ROTATE_RIGHT_WHEEL_DEFAULT;

	o = &rd->opt[ROTATE_DISPLAY_OPTION_LEFT];
	o->advanced = False;
	o->name = "rotate_left";
	o->group = N_("Bindings");
	o->subGroup = N_("Rotate Left");
	o->displayHints = "";
	o->shortDesc = N_("Rotate Left");
	o->longDesc = N_("Rotate Left.");
	o->type = CompOptionTypeAction;
	o->value.action.initiate = rotateLeft;
	o->value.action.terminate = 0;
	o->value.action.bell = FALSE;
	o->value.action.edgeMask = 0;
	o->value.action.state = CompActionStateInitEdge;
	o->value.action.state |= CompActionStateInitEdgeDnd;
	o->value.action.state |= CompActionStateInitKey;
	o->value.action.state |= CompActionStateInitButton;
	o->value.action.type = CompBindingTypeKey;
	o->value.action.key.modifiers = ROTATE_LEFT_MODIFIERS_DEFAULT;
	o->value.action.key.keysym = XStringToKeysym(ROTATE_LEFT_KEY_DEFAULT);

	o = &rd->opt[ROTATE_DISPLAY_OPTION_RIGHT];
	o->advanced = False;
	o->name = "rotate_right";
	o->group = N_("Bindings");
	o->subGroup = N_("Rotate Right");
	o->displayHints = "";
	o->shortDesc = N_("Rotate Right");
	o->longDesc = N_("Rotate Right.");
	o->type = CompOptionTypeAction;
	o->value.action.initiate = rotateRight;
	o->value.action.terminate = 0;
	o->value.action.bell = FALSE;
	o->value.action.edgeMask = 0;
	o->value.action.state = CompActionStateInitEdge;
	o->value.action.state |= CompActionStateInitEdgeDnd;
	o->value.action.state |= CompActionStateInitKey;
	o->value.action.state |= CompActionStateInitButton;
	o->value.action.type = CompBindingTypeKey;
	o->value.action.key.modifiers = ROTATE_RIGHT_MODIFIERS_DEFAULT;
	o->value.action.key.keysym = XStringToKeysym(ROTATE_RIGHT_KEY_DEFAULT);

	o = &rd->opt[ROTATE_DISPLAY_OPTION_UP];
	o->advanced = False;
	o->name = "rotate_up";
	o->group = N_("Bindings");
	o->subGroup = N_("Rotate Up");
	o->displayHints = "";
	o->shortDesc = N_("Rotate Up");
	o->longDesc = N_("Rotate Up.");
	o->type = CompOptionTypeAction;
	o->value.action.initiate = rotateTop;
	o->value.action.terminate = 0;
	o->value.action.bell = FALSE;
	o->value.action.edgeMask = 0;
	o->value.action.state = CompActionStateInitEdge;
	o->value.action.state |= CompActionStateInitEdgeDnd;
	o->value.action.state |= CompActionStateInitKey;
	o->value.action.state |= CompActionStateInitButton;
	o->value.action.type = CompBindingTypeKey;
	o->value.action.key.modifiers = ROTATE_UP_MODIFIERS_DEFAULT;
	o->value.action.key.keysym = XStringToKeysym(ROTATE_UP_KEY_DEFAULT);

	o = &rd->opt[ROTATE_DISPLAY_OPTION_DOWN];
	o->advanced = False;
	o->name = "rotate_down";
	o->group = N_("Bindings");
	o->subGroup = N_("Rotate Down");
	o->displayHints = "";
	o->shortDesc = N_("Rotate Down");
	o->longDesc = N_("Rotate Down.");
	o->type = CompOptionTypeAction;
	o->value.action.initiate = rotateDown;
	o->value.action.terminate = 0;
	o->value.action.bell = FALSE;
	o->value.action.edgeMask = 0;
	o->value.action.state = CompActionStateInitEdge;
	o->value.action.state |= CompActionStateInitEdgeDnd;
	o->value.action.state |= CompActionStateInitKey;
	o->value.action.state |= CompActionStateInitButton;
	o->value.action.type = CompBindingTypeKey;
	o->value.action.key.modifiers = ROTATE_DOWN_MODIFIERS_DEFAULT;
	o->value.action.key.keysym = XStringToKeysym(ROTATE_DOWN_KEY_DEFAULT);

	o = &rd->opt[ROTATE_DISPLAY_OPTION_LEFT_WINDOW];
	o->advanced = False;
	o->name = "rotate_left_window";
	o->group = N_("Bindings");
	o->subGroup = N_("Rotate Left");
	o->displayHints = "";
	o->shortDesc = N_("Rotate Left with Window");
	o->longDesc = N_("Rotate Left and bring the active Window along.");
	o->type = CompOptionTypeAction;
	o->value.action.initiate = rotateLeftWithWindow;
	o->value.action.terminate = 0;
	o->value.action.bell = FALSE;
	o->value.action.edgeMask = 0;
	o->value.action.state = CompActionStateInitEdge;
	o->value.action.state |= CompActionStateInitEdgeDnd;
	o->value.action.state |= CompActionStateInitKey;
	o->value.action.state |= CompActionStateInitButton;
	o->value.action.type = CompBindingTypeKey;
	o->value.action.key.modifiers = ROTATE_LEFT_WINDOW_MODIFIERS_DEFAULT;
	o->value.action.key.keysym =
			XStringToKeysym(ROTATE_LEFT_WINDOW_KEY_DEFAULT);

	o = &rd->opt[ROTATE_DISPLAY_OPTION_RIGHT_WINDOW];
	o->advanced = False;
	o->name = "rotate_right_window";
	o->group = N_("Bindings");
	o->subGroup = N_("Rotate Right");
	o->displayHints = "";
	o->shortDesc = N_("Rotate Right with Window");
	o->longDesc = N_("Rotate Right and bring the active Window along.");
	o->type = CompOptionTypeAction;
	o->value.action.initiate = rotateRightWithWindow;
	o->value.action.terminate = 0;
	o->value.action.bell = FALSE;
	o->value.action.edgeMask = 0;
	o->value.action.state = CompActionStateInitEdge;
	o->value.action.state |= CompActionStateInitEdgeDnd;
	o->value.action.state |= CompActionStateInitKey;
	o->value.action.state |= CompActionStateInitButton;
	o->value.action.type = CompBindingTypeKey;
	o->value.action.key.modifiers = ROTATE_RIGHT_WINDOW_MODIFIERS_DEFAULT;
	o->value.action.key.keysym =
			XStringToKeysym(ROTATE_RIGHT_WINDOW_KEY_DEFAULT);

#define ROTATE_TO_SHORT        N_("Rotate To Face %d")
#define ROTATE_TO_LONG         N_("Rotate to face %d")
#define ROTATE_TO_WINDOW_SHORT N_("Rotate To Face %d with Window")
#define ROTATE_TO_WINDOW_LONG  N_("Rotate to face %d and bring active " \
        "window along")

#define ROTATE_TO_OPTION(n)                         \
    o = &rd->opt[ROTATE_DISPLAY_OPTION_TO_ ## n];             \
    o->advanced=False;\
o->name              = "rotate_to_" #n;             \
    asprintf (&str, ROTATE_TO_SHORT, n);                 \
    o->group=N_("Bindings");\
o->subGroup=N_("Face shortcuts");\
o->displayHints="";\
o->shortDesc          = str;                 \
    asprintf (&str, ROTATE_TO_LONG, n);                     \
    o->longDesc              = str;                 \
    o->type              = CompOptionTypeAction;         \
    o->value.action.initiate      = rotateTo;                 \
    o->value.action.terminate      = 0;                     \
    o->value.action.bell      = FALSE;                 \
    o->value.action.edgeMask      = 0;                     \
    o->value.action.state      = CompActionStateInitKey;         \
    o->value.action.state     |= CompActionStateInitButton;         \
    o->value.action.type      = CompBindingTypeNone;         \
    \
    o = &rd->opt[ROTATE_DISPLAY_OPTION_TO_ ## n ## _WINDOW];         \
    o->advanced=False;\
o->name              = "rotate_to_" #n "_window";         \
    asprintf (&str, ROTATE_TO_WINDOW_SHORT, n);                 \
    o->group=N_("Bindings");\
o->subGroup=N_("Face shortcuts");\
o->displayHints="";\
o->shortDesc          = str;                 \
    asprintf (&str, ROTATE_TO_WINDOW_LONG, n);                 \
    o->longDesc              = str;                 \
    o->type              = CompOptionTypeAction;         \
    o->value.action.initiate      = rotateToWithWindow;             \
    o->value.action.terminate      = 0;                     \
    o->value.action.bell      = FALSE;                 \
    o->value.action.edgeMask      = 0;                     \
    o->value.action.state      = CompActionStateInitKey;         \
    o->value.action.state     |= CompActionStateInitButton;         \
    o->value.action.type      = CompBindingTypeNone

	ROTATE_TO_OPTION(1);
	ROTATE_TO_OPTION(2);
	ROTATE_TO_OPTION(3);
	ROTATE_TO_OPTION(4);
	ROTATE_TO_OPTION(5);
	ROTATE_TO_OPTION(6);
	ROTATE_TO_OPTION(7);
	ROTATE_TO_OPTION(8);
	ROTATE_TO_OPTION(9);
	ROTATE_TO_OPTION(10);
	ROTATE_TO_OPTION(11);
	ROTATE_TO_OPTION(12);

	o = &rd->opt[ROTATE_DISPLAY_OPTION_TO];
	o->advanced = False;
	o->name = "rotate_to";
	o->group = N_("");
	o->subGroup = N_("");
	o->displayHints = "";
	o->shortDesc = N_("Rotate To");
	o->longDesc = N_("Rotate to Viewport.");
	o->type = CompOptionTypeAction;
	o->value.action.initiate = rotateTo;
	o->value.action.terminate = 0;
	o->value.action.bell = FALSE;
	o->value.action.edgeMask = 0;
	o->value.action.state = 0;
	o->value.action.type = CompBindingTypeNone;

	o = &rd->opt[ROTATE_DISPLAY_OPTION_WINDOW];
	o->advanced = False;
	o->name = "rotate_window";
	o->group = N_("");
	o->subGroup = N_("");
	o->displayHints = "";
	o->shortDesc = N_("Rotate Window");
	o->longDesc = N_("Rotate with Window.");
	o->type = CompOptionTypeAction;
	o->value.action.initiate = rotateToWithWindow;
	o->value.action.terminate = 0;
	o->value.action.bell = FALSE;
	o->value.action.edgeMask = 0;
	o->value.action.state = 0;
	o->value.action.type = CompBindingTypeNone;

	o = &rd->opt[ROTATE_DISPLAY_OPTION_FLIP_LEFT];
	o->advanced = False;
	o->name = "rotate_flip_left";
	o->group = N_("Bindings");
	o->subGroup = N_("Edge Flip");
	o->displayHints = "";
	o->shortDesc = N_("Rotate Flip Left");
	o->longDesc = N_("Flip to Left Viewport and warp pointer.");
	o->type = CompOptionTypeAction;
	o->value.action.initiate = rotateEdgeFlipLeft;
	o->value.action.terminate = rotateFlipTerminate;
	o->value.action.bell = FALSE;
	o->value.action.edgeMask = 1 << SCREEN_EDGE_LEFT;
	o->value.action.state = CompActionStateInitEdge;
	o->value.action.state |= CompActionStateInitEdgeDnd;
	o->value.action.state |= CompActionStateInitKey;
	o->value.action.state |= CompActionStateInitButton;
	o->value.action.type = CompBindingTypeNone;

	o = &rd->opt[ROTATE_DISPLAY_OPTION_FLIP_RIGHT];
	o->advanced = False;
	o->name = "rotate_flip_right";
	o->group = N_("Bindings");
	o->subGroup = N_("Edge Flip");
	o->displayHints = "";
	o->shortDesc = N_("Rotate Flip Right");
	o->longDesc = N_("Flip to Right Viewport and warp pointer.");
	o->type = CompOptionTypeAction;
	o->value.action.initiate = rotateEdgeFlipRight;
	o->value.action.terminate = rotateFlipTerminate;
	o->value.action.bell = FALSE;
	o->value.action.edgeMask = 1 << SCREEN_EDGE_RIGHT;
	o->value.action.state = CompActionStateInitEdge;
	o->value.action.state |= CompActionStateInitEdgeDnd;
	o->value.action.state |= CompActionStateInitKey;
	o->value.action.state |= CompActionStateInitButton;
	o->value.action.type = CompBindingTypeNone;

	o = &rd->opt[ROTATE_DISPLAY_OPTION_EDGEFLIP_POINTER];
	o->advanced = False;
	o->name = "edge_flip_pointer";
	o->group = N_("Behaviour");
	o->subGroup = N_("Edge Flip");
	o->displayHints = "";
	o->shortDesc = N_("Edge Flip Pointer");
	o->longDesc =
			N_("Flip to Next Viewport when moving Pointer to Screen Edge.");
	o->type = CompOptionTypeBool;
	o->value.b = ROTATE_EDGEFLIP_POINTER_DEFAULT;

	o = &rd->opt[ROTATE_DISPLAY_OPTION_EDGEFLIP_WINDOW];
	o->advanced = False;
	o->name = "edge_flip_move";
	o->group = N_("Behaviour");
	o->subGroup = N_("Edge Flip");
	o->displayHints = "";
	o->shortDesc = N_("Edge Flip Move");
	o->longDesc =
			N_("Flip to Next Viewport when moving Window to Screen Edge.");
	o->type = CompOptionTypeBool;
	o->value.b = ROTATE_EDGEFLIP_WINDOW_DEFAULT;

	o = &rd->opt[ROTATE_DISPLAY_OPTION_EDGEFLIP_DND];
	o->advanced = False;
	o->name = "edge_flip_dnd";
	o->group = N_("Behaviour");
	o->subGroup = N_("Edge Flip");
	o->displayHints = "";
	o->shortDesc = N_("Edge Flip DnD");
	o->longDesc =
			N_("Flip to Next Viewport when Dragging object "
				"to Screen Edge.");
	o->type = CompOptionTypeBool;
	o->value.b = ROTATE_EDGEFLIP_DND_DEFAULT;

	o = &rd->opt[ROTATE_DISPLAY_OPTION_FLIPTIME];
	o->advanced = False;
	o->name = "flip_time";
	o->group = N_("Behaviour");
	o->subGroup = N_("Edge Flip");
	o->displayHints = "";
	o->shortDesc = N_("Flip Time");
	o->longDesc = N_("Timeout before Flipping viewport.");
	o->type = CompOptionTypeInt;
	o->value.i = ROTATE_FLIPTIME_DEFAULT;
	o->rest.i.min = ROTATE_FLIPTIME_MIN;
	o->rest.i.max = ROTATE_FLIPTIME_MAX;

	o = &rd->opt[ROTATE_DISPLAY_OPTION_INITIATEDESKTOP];
	o->advanced = False;
	o->name = "initiatedesktop";
	o->group = N_("Bindings");
	o->subGroup = N_("Initiate");
	o->displayHints = "";
	o->shortDesc = N_("Initiate on Desktop");
	o->longDesc = N_("Start Rotation, but only when issued over Desktop.");
	o->type = CompOptionTypeAction;
	o->value.action.initiate = rotateInitiateDesktop;
	o->value.action.terminate = rotateTerminate;
	o->value.action.bell = FALSE;
	o->value.action.edgeMask = 0;
	o->value.action.state = CompActionStateInitKey;
	o->value.action.state |= CompActionStateInitButton;
	o->value.action.type = CompBindingTypeButton;
	o->value.action.button.modifiers =
			ROTATE_INITIATEDESKTOP_MODIFIERS_DEFAULT;
	o->value.action.button.button = ROTATE_INITIATEDESKTOP_BUTTON_DEFAULT;


}

static CompOption *rotateGetDisplayOptions(CompDisplay * display, int *count)
{
	if (display)
	{
		ROTATE_DISPLAY(display);

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

		rotateDisplayInitOptions(rd);
		*count = NUM_OPTIONS(rd) - 2;
		return rd->opt;
	}
}

static Bool rotateInitDisplay(CompPlugin * p, CompDisplay * d)
{
	RotateDisplay *rd;

	rd = malloc(sizeof(RotateDisplay));
	if (!rd)
		return FALSE;

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

	rd->leftWheelings = 0;
	rd->rightWheelings = 0;

	rotateDisplayInitOptions(rd);

	WRAP(rd, d, handleEvent, rotateHandleEvent);

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

	return TRUE;
}

static void rotateFiniDisplay(CompPlugin * p, CompDisplay * d)
{
	ROTATE_DISPLAY(d);

	freeScreenPrivateIndex(d, rd->screenPrivateIndex);

	UNWRAP(rd, d, handleEvent);

	free(rd);
}

static Bool rotateInitScreen(CompPlugin * p, CompScreen * s)
{
	RotateScreen *rs;

	ROTATE_DISPLAY(s->display);

	rs = malloc(sizeof(RotateScreen));
	if (!rs)
		return FALSE;

	rs->grabIndex = 0;

	rs->xrot = 0.0f;
	rs->xVelocity = 0.0f;
	rs->yrot = 0.0f;
	rs->yVelocity = 0.0f;

	rs->baseXrot = 0.0f;
	rs->baseYrot = 0.0f;

	rs->moving = FALSE;
	rs->moveTo = 0.0f;

	rs->movingVert = FALSE;
	rs->moveToY = 0.0f;

	rs->moveWindow = 0;

	rs->savedPointer.x = 0;
	rs->savedPointer.y = 0;

	rs->grabbed = FALSE;
	rs->snapTop = FALSE;
	rs->snapBottom = FALSE;

	rs->slow = FALSE;
	rs->grabMask = FALSE;
	rs->grabWindow = NULL;
	rs->manualAtom =
			IPCS_GetAtom(IPCS_OBJECT(s), IPCS_BOOL, "MOUSE_INITIATED_ROTATE",
						 TRUE);
	rs->pointerSensitivity =
			ROTATE_POINTER_SENSITIVITY_DEFAULT *
			ROTATE_POINTER_SENSITIVITY_FACTOR;

	rs->zoom = ROTATE_ZOOM_DEFAULT / 30.0f;
	rs->zooming = (ROTATE_ZOOM_DEFAULT > 0.05f) ? TRUE : FALSE;
	rs->zoomTranslate = 0.0f;
	rs->zoomVelocity = 0.0f;

	rs->rotateHandle = 0;
	rs->rotating = FALSE;

	rs->previousRotationAtom = IPCS_GetAtom(IPCS_OBJECT(s), IPCS_FLOAT,
											"PREVIOUS_ROTATION", TRUE);
	rs->snapTopBottomAtom = IPCS_GetAtom(IPCS_OBJECT(s), IPCS_BOOL,
										 "CUBE_SNAP_TOP_BOTTOM", TRUE);

	rotateScreenInitOptions(rs);

	addScreenAction(s, &rd->opt[ROTATE_DISPLAY_OPTION_INITIATE].value.action);
	addScreenAction(s, &rd->opt[ROTATE_DISPLAY_OPTION_LEFT].value.action);
	addScreenAction(s, &rd->opt[ROTATE_DISPLAY_OPTION_RIGHT].value.action);
	addScreenAction(s, &rd->opt[ROTATE_DISPLAY_OPTION_UP].value.action);
	addScreenAction(s, &rd->opt[ROTATE_DISPLAY_OPTION_DOWN].value.action);
	addScreenAction(s,
					&rd->opt[ROTATE_DISPLAY_OPTION_LEFT_WINDOW].value.action);
	addScreenAction(s,
					&rd->opt[ROTATE_DISPLAY_OPTION_RIGHT_WINDOW].value.
					action);
	addScreenAction(s,
					&rd->opt[ROTATE_DISPLAY_OPTION_FLIP_LEFT].value.action);
	addScreenAction(s,
					&rd->opt[ROTATE_DISPLAY_OPTION_FLIP_RIGHT].value.action);
	addScreenAction(s,
					&rd->opt[ROTATE_DISPLAY_OPTION_LEFT_WHEEL].value.action);
	addScreenAction(s,
					&rd->opt[ROTATE_DISPLAY_OPTION_RIGHT_WHEEL].value.action);
	addScreenAction(s,
					&rd->opt[ROTATE_DISPLAY_OPTION_INITIATEDESKTOP].value.
					action);

	WRAP(rs, s, preparePaintScreen, rotatePreparePaintScreen);
	WRAP(rs, s, donePaintScreen, rotateDonePaintScreen);
	WRAP(rs, s, paintScreen, rotatePaintScreen);
	WRAP(rs, s, setScreenOptionForPlugin, rotateSetScreenOptionForPlugin);
	WRAP(rs, s, windowGrabNotify, rotateWindowGrabNotify);
	WRAP(rs, s, windowUngrabNotify, rotateWindowUngrabNotify);

	s->privates[rd->screenPrivateIndex].ptr = rs;

	rotateUpdateCubeOptions(s);

	return TRUE;
}

static void rotateFiniScreen(CompPlugin * p, CompScreen * s)
{
	ROTATE_SCREEN(s);
	ROTATE_DISPLAY(s->display);

	UNWRAP(rs, s, preparePaintScreen);
	UNWRAP(rs, s, donePaintScreen);
	UNWRAP(rs, s, paintScreen);
	UNWRAP(rs, s, setScreenOptionForPlugin);
	UNWRAP(rs, s, windowGrabNotify);
	UNWRAP(rs, s, windowUngrabNotify);

	removeScreenAction(s,
					   &rd->opt[ROTATE_DISPLAY_OPTION_INITIATE].value.action);
	removeScreenAction(s, &rd->opt[ROTATE_DISPLAY_OPTION_LEFT].value.action);
	removeScreenAction(s, &rd->opt[ROTATE_DISPLAY_OPTION_RIGHT].value.action);
	removeScreenAction(s, &rd->opt[ROTATE_DISPLAY_OPTION_UP].value.action);
	removeScreenAction(s, &rd->opt[ROTATE_DISPLAY_OPTION_DOWN].value.action);
	removeScreenAction(s,
					   &rd->opt[ROTATE_DISPLAY_OPTION_LEFT_WINDOW].
					   value.action);
	removeScreenAction(s,
					   &rd->opt[ROTATE_DISPLAY_OPTION_RIGHT_WINDOW].
					   value.action);
	removeScreenAction(s,
					   &rd->opt[ROTATE_DISPLAY_OPTION_FLIP_LEFT].value.
					   action);
	removeScreenAction(s,
					   &rd->opt[ROTATE_DISPLAY_OPTION_FLIP_RIGHT].
					   value.action);
	removeScreenAction(s,
					   &rd->opt[ROTATE_DISPLAY_OPTION_LEFT_WHEEL].
					   value.action);
	removeScreenAction(s,
					   &rd->opt[ROTATE_DISPLAY_OPTION_RIGHT_WHEEL].
					   value.action);
	removeScreenAction(s,
					   &rd->opt[ROTATE_DISPLAY_OPTION_INITIATEDESKTOP].value.
					   action);

	free(rs);
}

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

	return TRUE;
}

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

CompPluginDep rotateDeps[] = {
	{CompPluginRuleAfter, "cube"}
	,
	{CompPluginRuleRequire, "cube"}
};

CompPluginVTable rotateVTable = {
	"rotate",
	N_("Rotate Cube"),
	N_("Rotate desktop cube"),
	rotateInit,
	rotateFini,
	rotateInitDisplay,
	rotateFiniDisplay,
	rotateInitScreen,
	rotateFiniScreen,
	0,							/* InitWindow */
	0,							/* FiniWindow */
	rotateGetDisplayOptions,
	rotateSetDisplayOption,
	rotateGetScreenOptions,
	rotateSetScreenOption,
	rotateDeps,
	sizeof(rotateDeps) / sizeof(rotateDeps[0]),
	0,
	0,
	BERYL_ABI_INFO,
	"beryl-plugins",
	"desktop",
	0,
	0,
	True,
};

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


syntax highlighted by Code2HTML, v. 0.9.1