/*
 * 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>
 * Reworked by Nigel Cunningham <nigel@suspend2.net>
 * Fixes to outline painting and constraining 
 *   code by Danny Baumann <maniac@beryl-project.org>
 *
 * Parts of this code are taken from metacity and written by
 *   Eliah Newren, Rob Adams and others
 */

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

#include <X11/cursorfont.h>

#include <beryl.h>

#define ResizeUpMask	(1L << 0)
#define ResizeDownMask  (1L << 1)
#define ResizeLeftMask  (1L << 2)
#define ResizeRightMask (1L << 3)

#define RESIZE_INITIATE_BUTTON_DEFAULT	Button2
#define RESIZE_INITIATE_BUTTON_MODIFIERS_DEFAULT CompAltMask

#define RESIZE_OPACITY_DEFAULT 100
#define RESIZE_OPACITY_MIN	 1
#define RESIZE_OPACITY_MAX	 100

#define RESIZE_OPACIFY_NON_OPAQUE_DEFAULT FALSE

#define RESIZE_OPACITY_DEFAULT 100
#define RESIZE_OPACITY_MIN	 1
#define RESIZE_OPACITY_MAX	 100

#define RESIZE_OPACIFY_MIN_OPACITY_DEFAULT 80
#define RESIZE_OPACIFY_MIN_OPACITY_MIN	 1
#define RESIZE_OPACIFY_MIN_OPACITY_MAX	 100

#define RESIZE_SYNC_WINDOW_DEFAULT FALSE
#define RESIZE_WARP_POINTER_DEFAULT FALSE

#define RESIZE_INITIATE_KEY_DEFAULT	"F8"
#define RESIZE_INITIATE_KEY_MODIFIERS_DEFAULT CompAltMask

struct _ResizeKeys
{
	char *name;
	int dx;
	int dy;
	unsigned int warpMask;
	unsigned int resizeMask;
} rKeys[] =
{
	{"Left", -1, 0, ResizeLeftMask | ResizeRightMask, ResizeLeftMask},
	{"Right", 1, 0, ResizeLeftMask | ResizeRightMask, ResizeRightMask},
	{"Up", 0, -1, ResizeUpMask | ResizeDownMask, ResizeUpMask},
	{"Down", 0, 1, ResizeUpMask | ResizeDownMask, ResizeDownMask}
};

#define NUM_KEYS (sizeof (rKeys) / sizeof (rKeys[0]))

#define MIN_KEY_WIDTH_INC  24
#define MIN_KEY_HEIGHT_INC 24

#define RESIZE_DISPLAY_OPTION_INITIATE			0
#define RESIZE_DISPLAY_OPTION_OPACITY			1
#define RESIZE_DISPLAY_OPTION_OPACIFY_MIN_OPACITY 	2
#define RESIZE_DISPLAY_OPTION_SYNC_WINDOW		3
#define RESIZE_DISPLAY_OPTION_WARP_POINTER 		4
#define RESIZE_DISPLAY_OPTION_MODE 			5
#define RESIZE_DISPLAY_OPTION_BORDER_COLOR 		6
#define RESIZE_DISPLAY_OPTION_FILL_COLOR 		7
#define RESIZE_DISPLAY_OPTION_NUM	  		8

static int displayPrivateIndex;

/* Order is important. In code, we check for > Stretch. */
typedef enum _ResizeMode
{
	ResizeModeNormal,
	ResizeModeStretch,
	ResizeModeOutline,
	ResizeModeFilled,
} ResizeMode;

char *resizeModes[] = {
	N_("Normal"),
	N_("Stretch"),
	N_("Outline"),
	N_("Filled Outline")
};

#define RESIZE_MODE_DEFAULT ResizeModeNormal
#define NUM_RESIZE_MODES 4

/* Ignore the NumLock and CapsLock keys. */
#define WARP_IGNORE_MASK (~(LockMask | Mod2Mask))

typedef struct _ResizeDisplay
{
	CompOption opt[RESIZE_DISPLAY_OPTION_NUM];

	int screenPrivateIndex;
	HandleEventProc handleEvent;

	CompWindow *w;
	XWindowAttributes savedAttrib;
	int releaseButton;
	unsigned int mask;
	int width;
	int height;
	KeyCode key[NUM_KEYS];

	int lastWidth;
	int lastHeight;
	int currentWidth;
	int currentHeight;
	int currentX;
	int currentY;
	int xdelta, ydelta;			/* Offset when resize started */

	int dx_to_apply, dy_to_apply, width_to_apply, height_to_apply;

	int right_edge, bottom_edge;

	GLushort resizeOpacity;
	GLushort opacifyMinOpacity;

	int resizeMode;
	Bool ungrabPending;

	GLushort border[4];
	GLushort fill[4];
} ResizeDisplay;

typedef struct _ResizeScreen
{
	int grabIndex;
	Bool painted;
	int x, y;

	PaintWindowProc paintWindow;
	PreparePaintScreenProc preparePaintScreen;
	PaintScreenProc paintScreen;
	PaintTransformedScreenProc paintTransformedScreen;
	DonePaintScreenProc donePaintScreen;

	Cursor leftCursor;
	Cursor rightCursor;
	Cursor upCursor;
	Cursor upLeftCursor;
	Cursor upRightCursor;
	Cursor downCursor;
	Cursor downLeftCursor;
	Cursor downRightCursor;
	Cursor middleCursor;
	Cursor cursor[NUM_KEYS];
} ResizeScreen;

#define GET_RESIZE_DISPLAY(d)					   \
	((ResizeDisplay *) (d)->privates[displayPrivateIndex].ptr)

#define RESIZE_DISPLAY(d)			   \
	ResizeDisplay *rd = GET_RESIZE_DISPLAY (d)

#define GET_RESIZE_SCREEN(s) \
	((ResizeScreen *) (s)->privates[(GET_RESIZE_DISPLAY(s->display))->screenPrivateIndex].ptr)

#define RESIZE_SCREEN(s) ResizeScreen *rs = GET_RESIZE_SCREEN(s)

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

static void resizeUpdateWindowRealSize(CompDisplay * d, int move_only)
{
	RESIZE_DISPLAY(d);
	XWindowChanges xwc;
	unsigned int xwcm = 0;

	if (!rd->w || rd->w->syncWait)
		return;

	if (move_only)
	{
		moveWindow(rd->w, rd->currentX - rd->w->attrib.x,
				   rd->currentY - rd->w->attrib.y, TRUE, TRUE);
		syncWindowPosition(rd->w);
	}
	else
	{
		if (rd->currentX != rd->w->serverX)
			xwcm |= CWX;
		if (rd->currentY != rd->w->serverY)
			xwcm |= CWY;
		if (rd->currentWidth != rd->w->serverWidth)
			xwcm |= CWWidth;
		if (rd->currentHeight != rd->w->serverHeight)
			xwcm |= CWHeight;

		xwc.x = rd->currentX;
		xwc.y = rd->currentY;
		xwc.width = rd->currentWidth;
		xwc.height = rd->currentHeight;

		if (rd->resizeMode == ResizeModeNormal
			&& rd->opt[RESIZE_DISPLAY_OPTION_SYNC_WINDOW].value.b)
			sendSyncRequest(rd->w);

		configureXWindow(rd->w, xwcm, &xwc);
	}
}

#define CLAMP(v, min, max) ((v) <= (min) ? (min) : (v) >= (max) ? (max) : (v))

static void
resizeConstrainMinMax(CompWindow * w,
					  int width, int height, int *newWidth, int *newHeight)
{
	const XSizeHints *hints = &w->sizeHints;
	int min_width = 0;
	int min_height = 0;
	int max_width = MAXSHORT;
	int max_height = MAXSHORT;

	if ((hints->flags & PBaseSize) && (hints->flags & PMinSize))
	{
		min_width = hints->min_width;
		min_height = hints->min_height;
	}
	else if (hints->flags & PBaseSize)
	{
		min_width = hints->base_width;
		min_height = hints->base_height;
	}
	else if (hints->flags & PMinSize)
	{
		min_width = hints->min_width;
		min_height = hints->min_height;
	}

	if (hints->flags & PMaxSize)
	{
		max_width = hints->max_width;
		max_height = hints->max_height;
	}

	/* clamp width and height to min and max values */
	width = CLAMP(width, min_width, max_width);
	height = CLAMP(height, min_height, max_height);

	*newWidth = width;
	*newHeight = height;
}

static void
resizeConstrainResizeIncrement(CompWindow * w, int width, int height, int *newWidth, int *newHeight)
{
	int baseWidth, baseHeight;
	const XSizeHints *hints = &w->sizeHints;

	if (hints->flags & PResizeInc) {
		if (hints->flags & PBaseSize) {
			baseWidth = hints->base_width;
			baseHeight = hints->base_height;
		} else {
			baseWidth = 0;
			baseHeight = 0;
		}

		if ((width - baseWidth) % hints->width_inc) 
			width = baseWidth + (((width - baseWidth) / hints->width_inc) * hints->width_inc);

		if ((height - baseHeight) % hints->height_inc) 
			height = baseHeight + (((height - baseHeight) / hints->height_inc) * hints->height_inc);
	}

	*newWidth = width;
	*newHeight = height;
}

/* this code has its origin in metacity's boxes.c - thanks! */
static void
resizeFindLinePointClosestToPoint (double x1, double y1, 
		double x2, double y2, double px, double py, double *valx, double *valy)
{
  /* I'll use the shorthand rx, ry for the return values, valx & valy.
   * Now, we need (rx,ry) to be on the line between (x1,y1) and (x2,y2).
   * For that to happen, we first need the slope of the line from (x1,y1)
   * to (rx,ry) must match the slope of (x1,y1) to (x2,y2), i.e.:
   *   (ry-y1)   (y2-y1)
   *   ------- = -------
   *   (rx-x1)   (x2-x1)
   * If x1==x2, though, this gives divide by zero errors, so we want to
   * rewrite the equation by multiplying both sides by (rx-x1)*(x2-x1):
   *   (ry-y1)(x2-x1) = (y2-y1)(rx-x1)
   * This is a valid requirement even when x1==x2 (when x1==x2, this latter
   * equation will basically just mean that rx must be equal to both x1 and
   * x2)
   *
   * The other requirement that we have is that the line from (rx,ry) to
   * (px,py) must be perpendicular to the line from (x1,y1) to (x2,y2).  So
   * we just need to get a vector in the direction of each line, take the
   * dot product of the two, and ensure that the result is 0:
   *   (rx-px)*(x2-x1) + (ry-py)*(y2-y1) = 0.
   *
   * This gives us two equations and two unknowns:
   *
   *   (ry-y1)(x2-x1) = (y2-y1)(rx-x1)
   *   (rx-px)*(x2-x1) + (ry-py)*(y2-y1) = 0.
   *
   * This particular pair of equations is always solvable so long as
   * (x1,y1) and (x2,y2) are not the same point (and note that anyone who
   * calls this function that way is braindead because it means that they
   * really didn't specify a line after all).  However, the caller should
   * be careful to avoid making (x1,y1) and (x2,y2) too close (e.g. like
   * 10^{-8} apart in each coordinate), otherwise roundoff error could
   * cause issues.  Solving these equations by hand (or using Maple(TM) or
   * Mathematica(TM) or whatever) results in slightly messy expressions,
   * but that's all the below few lines do.
   */

	double diffx, diffy, den;
	diffx = x2 - x1;
	diffy = y2 - y1;
	den = diffx * diffx + diffy * diffy;

	*valx = (py * diffx * diffy + px * diffx * diffx +
             y2 * x1 * diffy - y1 * x2 * diffy) / den;
	*valy = (px * diffx * diffy + py * diffy * diffy +
			 x2 * y1 * diffx - x1 * y2 * diffx) / den;
}

/* this function is heavily inspired by metacity's 
   constrain_aspect_ratio - thanks another time! */
static Bool
resizeConstrainAspectRatio(CompWindow * w, int width, int height, int *retWidth, int *retHeight)
{
	RESIZE_DISPLAY(w->screen->display);
	const XSizeHints *hints = &w->sizeHints;
	double minRatio, maxRatio;
	int newWidth, newHeight;
	double altWidth, altHeight;
	double bestWidth, bestHeight;
	int tolerance = 1;
	Bool resizeLeftRight = FALSE;
	Bool resizeUpDown = FALSE;

	newWidth = width;
	newHeight = height;

	if (hints->flags & PAspect) {
		minRatio = hints->min_aspect.x / (double) hints->min_aspect.y;
		maxRatio = hints->max_aspect.x / (double) hints->max_aspect.y;

		if (minRatio > maxRatio)
			return FALSE;

		resizeLeftRight = (rd->mask == (rd->mask & ResizeLeftMask)) ||
						  (rd->mask == (rd->mask & ResizeRightMask));
		resizeUpDown = (rd->mask == (rd->mask & ResizeUpMask)) ||
					   (rd->mask == (rd->mask & ResizeDownMask));

		if (resizeLeftRight || resizeUpDown)
			tolerance = 2;

		/* return if constraint already is satisfied */
		if (((width - (height * minRatio)) > (-minRatio * tolerance)) &&
			((width - (height * maxRatio)) < (maxRatio * tolerance)))
		{
			return FALSE;
		}

		if (resizeLeftRight)
			newHeight = CLAMP (newHeight, newWidth / maxRatio, newWidth / minRatio);
		else if (resizeUpDown)
			newWidth = CLAMP (newWidth, newHeight * minRatio, newHeight * maxRatio);
		else {
			altWidth = CLAMP (newWidth, newHeight * minRatio, newHeight * maxRatio);
			altHeight = CLAMP (newHeight, newWidth / maxRatio, newWidth / minRatio);

			resizeFindLinePointClosestToPoint(altWidth, newHeight, newWidth, altHeight,
					newWidth, newHeight, &bestWidth, &bestHeight);

			newWidth = bestWidth;
			newHeight = bestHeight;
		}
	}

	*retWidth = newWidth;
	*retHeight = newHeight;

	return TRUE;
}

#undef CLAMP

static void resizeApply(CompDisplay * d)
{
	int move_only = 0, cw, ch;

	RESIZE_DISPLAY(d);

	if ((!rd->dx_to_apply && !rd->dy_to_apply) || rd->w->syncWait)
		return;

	/* Move window by delta if resizing from left/top */
	if (rd->mask & ResizeLeftMask && rd->width_to_apply != rd->currentWidth)
		rd->currentX += rd->dx_to_apply;

	if (rd->mask & ResizeUpMask && rd->height_to_apply != rd->currentHeight)
		rd->currentY += rd->dy_to_apply;

	cw = rd->currentWidth = rd->width_to_apply;
	ch = rd->currentHeight = rd->height_to_apply;

	rd->dx_to_apply = rd->dy_to_apply = 0;

	constrainNewWindowSize(rd->w, rd->currentWidth, rd->currentHeight,
						   &cw, &ch);

	if (rd->mask & ResizeLeftMask)
		rd->currentX = rd->right_edge - cw;

	if (rd->mask & ResizeUpMask)
		rd->currentY = rd->bottom_edge - ch;

	rd->currentWidth = cw;
	rd->currentHeight = ch;

	switch (rd->resizeMode)
	{
		case ResizeModeStretch:
			move_only = 1;
		case ResizeModeNormal:
			resizeUpdateWindowRealSize(d, move_only);
	}

	if (rd->resizeMode != ResizeModeNormal)
		resizeWindowPreview (rd->w, rd->currentX, rd->currentY,
							 rd->currentWidth, rd->currentHeight);
}

static void resizeUpdateWindowSize(CompDisplay * d, int dx, int dy)
{
	int w, h, new_height, new_width;

	RESIZE_DISPLAY(d);

	if (!rd->w)
		return;

	if (rd->resizeMode != ResizeModeNormal)
		damageScreen(rd->w->screen);

	if (rd->w->state & CompWindowStateMaximizedVertMask)
		rd->currentHeight = rd->w->serverHeight;

	if (rd->w->state & CompWindowStateMaximizedHorzMask)
		rd->currentWidth = rd->w->serverWidth;

	if (rd->w->state & CompWindowStateMaximizedVertMask &&
		rd->w->state & CompWindowStateMaximizedHorzMask)
		return;

	w = rd->currentWidth;
	h = rd->currentHeight;

	/* Ensure we don't push the window if constraints stop it being
	 * resized. */
	if (!(rd->mask & (ResizeLeftMask | ResizeRightMask)))
		dx = 0;

	if (!(rd->mask & (ResizeUpMask | ResizeDownMask)))
		dy = 0;

	/* Apply the width/height modifications */
	if (rd->mask & ResizeLeftMask)
		w -= dx;
	else if (rd->mask & ResizeRightMask)
		w += dx;

	if (rd->mask & ResizeUpMask)
		h -= dy;
	else if (rd->mask & ResizeDownMask)
		h += dy;

	/* Apply constraints */
	resizeConstrainResizeIncrement(rd->w, w, h, &w, &h);

	new_width = w;
	new_height = h;
	resizeConstrainAspectRatio(rd->w, w, h, &new_width, &new_height);
	resizeConstrainMinMax(rd->w, new_width, new_height, &new_width, &new_height);

	if ((w != new_width) || (h != new_height))
	{
		/* if the resizing hit constraints, move the mouse
		   pointer to the new border to avoid desynchronization */
		int pointerAdjustX = 0, pointerAdjustY = 0;

		if (rd->mask & ResizeRightMask)
			pointerAdjustX = new_width - w;
		else if (rd->mask & ResizeLeftMask)
			pointerAdjustX = w - new_width;

		if (rd->mask & ResizeDownMask)
			pointerAdjustY = new_height - h;
		else if (rd->mask & ResizeUpMask)
			pointerAdjustY = h - new_height;

		warpPointer(d, pointerAdjustX, pointerAdjustY);
	}

	dx -= new_width - w;
	dy -= new_height - h;

	/* Avoid annoying jiggling if we try to move 1 pixel */

	if (abs(dx) < 2)
		dx = 0;

	if (abs(dy) < 2)
		dy = 0;

	/* If no resizing, drop out now. */
	if (!dx && !dy)
		return;

	rd->dx_to_apply = dx;
	rd->dy_to_apply = dy;
	rd->width_to_apply = new_width;
	rd->height_to_apply = new_height;

	resizeApply(d);
}

static Bool
resizeInitiate(CompDisplay * d,
			   CompAction * action,
			   CompActionState state, CompOption * option, int nOption)
{
	CompWindow *w;
	ResizeScreen *rs;
	Window xid;

	unsigned int mods;
	unsigned int mask;
	int x, y;
	int button;

	RESIZE_DISPLAY(d);

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

	w = findWindowAtDisplay(d, xid);
	if (!w)
		return FALSE;

	rs = GET_RESIZE_SCREEN(w->screen);

	rd->dx_to_apply = rd->dy_to_apply = 0;

	mods = getIntOptionNamed(option, nOption, "modifiers", 0);

	x = getIntOptionNamed(option, nOption, "x", w->attrib.x + (w->width / 2));
	y = getIntOptionNamed(option, nOption, "y",
						  w->attrib.y + (w->height / 2));

	button = getIntOptionNamed(option, nOption, "button", -1);

	mask = getIntOptionNamed(option, nOption, "direction", 0);

	/* Initiate the resize in the direction suggested by the
	 * quarter of the window the mouse is in, eg drag in top left
	 * will resize up and to the left.  Keyboard resize starts out
	 * with the cursor in the middle of the window and then starts
	 * resizing the edge corresponding to the next key press. */
	if (state & CompActionStateInitKey)
		mask = 0;
	else if (!mask)
	{
		mask |= ((x - w->attrib.x) < (w->width / 2)) ?
				ResizeLeftMask : ResizeRightMask;

		mask |= ((y - w->attrib.y) < (w->height / 2)) ?
				ResizeUpMask : ResizeDownMask;
	}

	if (otherScreenGrabExist(w->screen, "resize", 0))
		return FALSE;

	if (rd->w)
		return FALSE;

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

	if (w->attrib.override_redirect)
		return FALSE;

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

	if (w->shaded)
		mask &= ~(ResizeUpMask | ResizeDownMask);

	rd->w = w;
	rd->mask = mask;
	rd->currentWidth = rd->width = w->serverWidth;
	rd->currentHeight = rd->height = w->serverHeight;
	rd->currentX = w->attrib.x;
	rd->currentY = w->attrib.y;
	rd->savedAttrib = w->attrib;
	rd->right_edge = rd->currentX + rd->currentWidth;
	rd->bottom_edge = rd->currentY + rd->currentHeight;
	rd->xdelta = rd->ydelta = 0;

	if (rd->resizeMode != ResizeModeNormal)
		rd->lastWidth = rd->lastHeight = 0.0f;
	else
		addWindowDamage(rd->w);

	d->lastPointerX = x;
	d->lastPointerY = y;

	if (!rs->grabIndex)
	{
		Cursor cursor;

		if (state & CompActionStateInitKey)
			cursor = rs->middleCursor;
		else if (mask & ResizeLeftMask)
		{
			if (mask & ResizeDownMask)
				cursor = rs->downLeftCursor;
			else if (mask & ResizeUpMask)
				cursor = rs->upLeftCursor;
			else
				cursor = rs->leftCursor;
		}
		else if (mask & ResizeRightMask)
		{
			if (mask & ResizeDownMask)
				cursor = rs->downRightCursor;
			else if (mask & ResizeUpMask)
				cursor = rs->upRightCursor;
			else
				cursor = rs->rightCursor;
		}
		else if (mask & ResizeUpMask)
			cursor = rs->upCursor;
		else
			cursor = rs->downCursor;

		rs->grabIndex = pushScreenGrab(w->screen, cursor, "resize");
	}

	if (rs->grabIndex)
	{
		int mods_sought = virtualToRealModMask(d,
											   rd->
											   opt
											   [RESIZE_DISPLAY_OPTION_INITIATE].
											   value.action.button.modifiers);
		int button_sought =
				rd->opt[RESIZE_DISPLAY_OPTION_INITIATE].value.action.
				button.button;

		rd->releaseButton = button;

		/* FIXME: we use CompWindowGrabButtonMask only for the normal mode
		   to prevent wobbling for the other modes
		   this is fine for now, but may break the API (documentation would be nice...) */

		Bool wobbleDesired = (rd->resizeMode == ResizeModeNormal);

		wobbleDesired |= (!(mask & ~(ResizeUpMask | ResizeDownMask))) &&
				(w->state & CompWindowStateMaximizedVertMask);
		wobbleDesired |= (!(mask & ~(ResizeLeftMask | ResizeRightMask))) &&
				(w->state & CompWindowStateMaximizedHorzMask);

		(w->screen->windowGrabNotify) (w, x, y, state,
									   CompWindowGrabResizeMask |
									   (wobbleDesired ?
										CompWindowGrabButtonMask : 0));

		if (state & CompActionStateInitKey)
		{
			x = w->attrib.x + (w->width / 2);
			y = w->attrib.y + (w->height / 2);
			warpPointer(d, x - d->pointerX, y - d->pointerY);
		}
		else if ((mods & WARP_IGNORE_MASK) == mods_sought &&
				 button == button_sought &&
				 rd->opt[RESIZE_DISPLAY_OPTION_WARP_POINTER].value.b)
		{
			/* May warp if initiated with mouse combo */
			x = w->attrib.x - w->input.left +
					((mask & ResizeLeftMask) ? 0 : rd->width +
					 w->input.left + w->input.right);
			y = w->attrib.y - w->input.top +
					((mask & ResizeUpMask) ? 0 : rd->height +
					 w->input.top + w->input.bottom);
			warpPointer(d, x - d->pointerX, y - d->pointerY);
		}

		rd->xdelta = (x - rd->currentX);
		if (rd->mask & ResizeRightMask)
			rd->xdelta = rd->width - rd->xdelta;

		rd->ydelta = (y - rd->currentY);
		if (rd->mask & ResizeDownMask)
			rd->ydelta = rd->height - rd->ydelta;

	}

	return FALSE;
}

static Bool
resizeTerminate(CompDisplay * d,
				CompAction * action,
				CompActionState state, CompOption * option, int nOption)
{
	RESIZE_DISPLAY(d);
	ResizeScreen *rs;

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

	if (!rd->w)
		return FALSE;

	rs = GET_RESIZE_SCREEN(rd->w->screen);

	if (state & CompActionStateCancel)
	{
		XWindowChanges xwc;

		sendSyncRequest(rd->w);

		xwc.x = rd->savedAttrib.x;
		xwc.y = rd->savedAttrib.y;
		xwc.width = rd->savedAttrib.width;
		xwc.height = rd->savedAttrib.height;

		if ((xwc.x != rd->w->serverX) || (xwc.y != rd->w->serverY) ||
			(xwc.width != rd->w->serverWidth) || (xwc.height != rd->w->serverHeight))
		{
			rd->ungrabPending = TRUE;
		} else {
			(rd->w->screen->windowUngrabNotify) (rd->w);
		}
		configureXWindow(rd->w, CWX | CWY | CWWidth | CWHeight, &xwc);
	} else if (rd->resizeMode != ResizeModeNormal
			 && (rd->currentX != rd->w->serverX
			 	 || rd->currentY != rd->w->serverY
				 || rd->currentWidth != rd->w->serverWidth
				 || rd->currentHeight != rd->w->serverHeight)) {
		rd->ungrabPending = TRUE;
		resizeUpdateWindowRealSize(d, 0);
	} else {
		syncWindowPosition(rd->w);
		(rd->w->screen->windowUngrabNotify) (rd->w);
	}

	if (rs->grabIndex)
	{
		removeScreenGrab(rd->w->screen, rs->grabIndex, NULL);
		rs->grabIndex = 0;
	}

	addWindowDamage(rd->w);

	if (rd->resizeMode != ResizeModeNormal)
		damageScreen(rd->w->screen);

	if (!rd->ungrabPending)
		rd->w = NULL;

	rd->releaseButton = 0;

	return FALSE;
}

static void resizeHandleKeyEvent(CompScreen * s, KeyCode keycode)
{
	RESIZE_SCREEN(s);
	RESIZE_DISPLAY(s->display);

	if (rs->grabIndex && rd->w)
	{
		CompWindow *w;
		int i, widthInc, heightInc;

		w = rd->w;

		widthInc = w->sizeHints.width_inc;
		heightInc = w->sizeHints.height_inc;

		/* if the resize increment hint is smaller than our
		   minimum amount, then resize by the next multiple
		   of the hint value large than our minimum */
		if (widthInc < MIN_KEY_WIDTH_INC)
			widthInc = ((MIN_KEY_WIDTH_INC / widthInc) + 1) * widthInc;

		if (heightInc < MIN_KEY_HEIGHT_INC)
			heightInc = ((MIN_KEY_HEIGHT_INC / heightInc) + 1) * heightInc;

		for (i = 0; i < NUM_KEYS; i++)
		{
			if (keycode != rd->key[i])
				continue;

			if (rd->mask & rKeys[i].warpMask)
				XWarpPointer(s->display->display, None,
							 None, 0, 0, 0, 0,
							 rKeys[i].dx * widthInc, rKeys[i].dy * heightInc);
			else
			{
				int x, y, left, top, width, height;

				left = w->attrib.x - w->input.left;
				top = w->attrib.y - w->input.top;
				width = w->input.left + w->serverWidth + w->input.right;
				height = w->input.top + w->serverHeight + w->input.bottom;

				x = left + width * (rKeys[i].dx + 1) / 2;
				y = top + height * (rKeys[i].dy + 1) / 2;

				rd->mask = rKeys[i].resizeMask;

				/* recalculate delta */
				rd->xdelta = (x - rd->currentX);
				if (rd->mask & ResizeRightMask)
					rd->xdelta = rd->width - rd->xdelta;

				rd->ydelta = (y - rd->currentY);
				if (rd->mask & ResizeDownMask)
					rd->ydelta = rd->height - rd->ydelta;

				warpPointer(s->display,
							x - s->display->pointerX,
							y - s->display->pointerY);
				updateScreenGrab(s, rs->grabIndex, rs->cursor[i]);
			}

			break;
		}
	}
}

static void resizeHandleMotionEvent(CompScreen * s, int xRoot, int yRoot)
{
	int pointerDx, pointerDy;

	RESIZE_SCREEN(s);
	RESIZE_DISPLAY(s->display);

	if (!rs->grabIndex)
		return;

	if (rd->mask == 0 && rd->w)
	{
		rd->mask =
				((xRoot - rd->w->attrib.x) <
				 (rd->w->width / 2)) ? ResizeLeftMask : ResizeRightMask;
		rd->mask |=
				((yRoot - rd->w->attrib.y) <
				 (rd->w->height / 2)) ? ResizeUpMask : ResizeDownMask;
	}

	pointerDx = xRoot - rd->currentX;
	pointerDy = yRoot - rd->currentY;

	if (rd->mask & ResizeRightMask)
		pointerDx -= (rd->currentWidth - rd->xdelta);

	if (rd->mask & ResizeLeftMask)
		pointerDx -= rd->xdelta;

	if (rd->mask & ResizeDownMask)
		pointerDy -= (rd->currentHeight - rd->ydelta);

	if (rd->mask & ResizeUpMask)
		pointerDy -= rd->ydelta;

	/* Any motion? */
	if (!pointerDx && !pointerDy)
		return;

	resizeUpdateWindowSize(s->display, pointerDx, pointerDy);
}

static void resizeHandleMoveResizeMessage(CompDisplay * d, XEvent * event)
{
	CompWindow *w;
	CompOption o[6];
	CompAction *action;

	RESIZE_DISPLAY(d);

	if (event->xclient.data.l[2] > WmMoveResizeSizeLeft &&
		event->xclient.data.l[2] != WmMoveResizeSizeKeyboard)
		return;

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

	if (!w)
		return;

	action = &rd->opt[RESIZE_DISPLAY_OPTION_INITIATE].value.action;

	o[0].type = CompOptionTypeInt;
	o[0].name = "window";
	o[0].value.i = event->xclient.window;

	if (event->xclient.data.l[2] == WmMoveResizeSizeKeyboard)
	{
		o[1].type = CompOptionTypeInt;
		o[1].name = "button";
		o[1].value.i = 0;

		resizeInitiate(d, action, CompActionStateInitKey, o, 2);
	}
	else
	{
		static unsigned int mask[] = {
			ResizeUpMask | ResizeLeftMask,
			ResizeUpMask,
			ResizeUpMask | ResizeRightMask,
			ResizeRightMask,
			ResizeDownMask | ResizeRightMask,
			ResizeDownMask,
			ResizeDownMask | ResizeLeftMask,
			ResizeLeftMask,
		};
		unsigned int mods;
		Window root, child;
		int xRoot, yRoot, i;

		XQueryPointer(d->display, w->screen->root,
					  &root, &child, &xRoot, &yRoot, &i, &i, &mods);

		/* TODO: not only button 1 */
		if (mods & Button1Mask)
		{
			o[1].type = CompOptionTypeInt;
			o[1].name = "modifiers";
			o[1].value.i = mods;

			o[2].type = CompOptionTypeInt;
			o[2].name = "x";
			o[2].value.i = event->xclient.data.l[0];

			o[3].type = CompOptionTypeInt;
			o[3].name = "y";
			o[3].value.i = event->xclient.data.l[1];

			o[4].type = CompOptionTypeInt;
			o[4].name = "direction";
			o[4].value.i = mask[event->xclient.data.l[2]];

			o[5].type = CompOptionTypeInt;
			o[5].name = "button";
			o[5].value.i = event->xclient.data.l[3] ?
					event->xclient.data.l[3] : -1;

			resizeInitiate(d, action, CompActionStateInitButton, o, 6);

			resizeHandleMotionEvent(w->screen, xRoot, yRoot);
		}
	}
}

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

	RESIZE_DISPLAY(d);

	switch (event->type)
	{

	case KeyPress:
		s = findScreenAtDisplay(d, event->xkey.root);
		if (s)
			resizeHandleKeyEvent(s, event->xkey.keycode);
		break;

	case KeyRelease:
		break;

	case ButtonPress:
		s = findScreenAtDisplay(d, event->xbutton.root);
		if (s)
		{
			RESIZE_SCREEN(s);

			if (rs->grabIndex)
			{
				resizeTerminate(d,
								&rd->
								opt
								[RESIZE_DISPLAY_OPTION_INITIATE].
								value.action, 0, NULL, 0);
			}
		}
		break;

	case ButtonRelease:
	{
		CompAction *action =
				&rd->opt[RESIZE_DISPLAY_OPTION_INITIATE].value.action;

		if (action->state & CompActionStateTermButton &&
			(rd->releaseButton == -1 ||
			 event->xbutton.button == rd->releaseButton))
			resizeTerminate(d, action, CompActionStateTermButton, NULL, 0);
	}
		break;
	case MotionNotify:
		s = findScreenAtDisplay(d, event->xmotion.root);
		if (s)
			resizeHandleMotionEvent(s, d->pointerX, d->pointerY);
		break;
	case EnterNotify:
	case LeaveNotify:
		s = findScreenAtDisplay(d, event->xcrossing.root);
		if (s)
			resizeHandleMotionEvent(s, d->pointerX, d->pointerY);
		break;
	case ClientMessage:
		if (event->xclient.message_type == d->wmMoveResizeAtom)
			resizeHandleMoveResizeMessage(d, event);
		break;
	case DestroyNotify:
		if (rd->w && rd->w->id == event->xdestroywindow.window)
		{
			CompAction *action =
					&rd->opt[RESIZE_DISPLAY_OPTION_INITIATE].value.action;
			resizeTerminate(d, action, 0, NULL, 0);
		}
		break;
	case UnmapNotify:
		if (rd->w && rd->w->id == event->xunmap.window)
		{
			CompAction *action =
					&rd->opt[RESIZE_DISPLAY_OPTION_INITIATE].value.action;
			resizeTerminate(d, action, 0, NULL, 0);
		}
		break;
	default:
		if (event->type == d->syncEvent + XSyncAlarmNotify && rd->w)
		{
			XSyncAlarmNotifyEvent *sa;

			sa = (XSyncAlarmNotifyEvent *) event;

			if (rd->w->syncAlarm == sa->alarm)
				resizeApply(d);
		}
		break;
	}

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

	switch (event->type) {
	case ConfigureNotify:
		if (rd->ungrabPending) {
			if (rd->w && (rd->w->id == event->xconfigure.window)) {
				(rd->w->screen->windowUngrabNotify) (rd->w);
				syncWindowPosition(rd->w);
				rd->w = NULL;
			}
			rd->ungrabPending = FALSE;
		}
		break;

	default:
		break;
	}
}

static void resizePreparePaintScreen(CompScreen * s, int msSinceLastPaint)
{

	RESIZE_SCREEN(s);
	RESIZE_DISPLAY(s->display);

	if (rd->w && rd->resizeMode > ResizeModeNormal &&
		rs->grabIndex &&
		(rd->lastWidth != rd->currentWidth ||
		 rd->lastHeight != rd->currentHeight))
	{
		damageScreen(s);
	}

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

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

	RESIZE_SCREEN(s);
	RESIZE_DISPLAY(s->display);

	if ((rs->grabIndex || rd->ungrabPending) && (rd->w == w))
	{
		sAttrib = *attrib;

		if (rd->resizeMode != ResizeModeNormal)
			mask |= PAINT_WINDOW_TRANSFORMED_MASK;

		if (rd->resizeMode == ResizeModeStretch)
		{
			sAttrib.xScale =
					(float)(rd->currentWidth) /
					(float)(rd->w->attrib.width);
			sAttrib.yScale =
					(float)(rd->currentHeight) /
					(float)(rd->w->attrib.height);
		}

		if (rd->resizeOpacity != OPAQUE
			&& sAttrib.opacity >= rd->opacifyMinOpacity)
		{
			sAttrib.opacity = (sAttrib.opacity * rd->resizeOpacity) >> 16;
		}
		attrib = &sAttrib;
	}

	UNWRAP(rs, s, paintWindow);
	status = (*s->paintWindow) (w, attrib, region, mask);
	WRAP(rs, s, paintWindow, resizePaintWindow);

	return status;
}

static void
resizePaintOutline(CompScreen * s, const ScreenPaintAttrib * sa, int output,
				   Bool transformed)
{
	int x1 = 0, x2 = 0, y1 = 0, y2 = 0;

	RESIZE_SCREEN(s);
	RESIZE_DISPLAY(s->display);

	if (rd->resizeMode > ResizeModeStretch && rd->w &&
		!(rd->w->state & MAXIMIZE_STATE) && rs->grabIndex)
	{
		x1 = rd->currentX - rd->w->input.left;
		x2 = rd->currentX + rd->currentWidth + rd->w->input.right;
		y1 = rd->currentY - rd->w->input.top;
		y2 = rd->currentY +
				(rd->w->shaded ? rd->w->height : rd->currentHeight) +
				rd->w->input.bottom;

		glPushMatrix();

		if (transformed)
		{
			glLoadIdentity();
			(s->applyScreenTransform) (s, sa, output);
		}

		prepareXCoords(s, output,
					   transformed ? -sa->zTranslate : -DEFAULT_Z_CAMERA);
		glDisableClientState(GL_TEXTURE_COORD_ARRAY);
		glEnable(GL_BLEND);

		if (rd->resizeMode == ResizeModeFilled)
		{
			glColor4usv(rd->opt[RESIZE_DISPLAY_OPTION_FILL_COLOR].value.c);
			glRecti(x1, y2, x2, y1);
		}

		/* This section draws the outline */
		glColor4usv(rd->opt[RESIZE_DISPLAY_OPTION_BORDER_COLOR].value.c);
		glLineWidth(2.0);
		glBegin(GL_LINE_LOOP);
		glVertex2i(x1, y1);
		glVertex2i(x2, y1);
		glVertex2i(x2, y2);
		glVertex2i(x1, y2);
		glEnd();

		/* Clean up */
		glColor4usv(defaultColor);
		glDisable(GL_BLEND);
		glEnableClientState(GL_TEXTURE_COORD_ARRAY);
		glPopMatrix();
	}
}

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

	RESIZE_SCREEN(s);
	RESIZE_DISPLAY(s->display);

	rs->painted = FALSE;
	rs->x = s->x;
	rs->y = s->y;

	if (rd->w && rs->grabIndex && rd->resizeMode == ResizeModeStretch)
		mask |= PAINT_SCREEN_WITH_TRANSFORMED_WINDOWS_MASK;

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

	if (status && !rs->painted)
		resizePaintOutline(s, sAttrib, output, FALSE);

	return status;
}

static void
resizePaintTransformedScreen(CompScreen * s, const ScreenPaintAttrib * sa,
							 Region region, int output, unsigned int mask)
{
	RESIZE_SCREEN(s);

	UNWRAP(rs, s, paintTransformedScreen);
	(*s->paintTransformedScreen) (s, sa, region, output, mask);
	WRAP(rs, s, paintTransformedScreen, resizePaintTransformedScreen);

	if ((rs->x == s->x) && (rs->y == s->y)) {
		rs->painted = TRUE;
		resizePaintOutline(s, sa, output, TRUE);
	}
}

static void resizeDonePaintScreen(CompScreen * s)
{
	RESIZE_SCREEN(s);
	RESIZE_DISPLAY(s->display);

	if (rd->resizeMode != ResizeModeNormal && rd->w && rs->grabIndex)
	{
		rd->lastWidth = rd->currentWidth;
		rd->lastHeight = rd->currentHeight;
	}

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

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

	RESIZE_DISPLAY(display);

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

	switch (index)
	{

	case RESIZE_DISPLAY_OPTION_INITIATE:
		if (setDisplayAction(display, o, value))
			return TRUE;
		break;

	case RESIZE_DISPLAY_OPTION_OPACITY:
		if (compSetIntOption(o, value))
		{
			rd->resizeOpacity = (o->value.i * OPAQUE) / 100;
			return TRUE;
		}
		break;

	case RESIZE_DISPLAY_OPTION_OPACIFY_MIN_OPACITY:
		if (compSetIntOption(o, value))
		{
			rd->opacifyMinOpacity = (o->value.i * OPAQUE) / 100;
			return TRUE;
		}
		break;

	case RESIZE_DISPLAY_OPTION_SYNC_WINDOW:
	case RESIZE_DISPLAY_OPTION_WARP_POINTER:
		if (compSetBoolOption(o, value))
			return TRUE;
		break;

	case RESIZE_DISPLAY_OPTION_MODE:
		if (compSetStringOption(o, value))
		{
			int i;

			for (i = 0; i < o->rest.s.nString; i++)
				if (strcmp(resizeModes[i], o->value.s) == 0)
					rd->resizeMode = (ResizeMode) i;

			return TRUE;
		}
		break;
	case RESIZE_DISPLAY_OPTION_BORDER_COLOR:
	case RESIZE_DISPLAY_OPTION_FILL_COLOR:
		if (compSetColorOption(o, value))
			return TRUE;
		break;
	default:
		break;
	}

	return FALSE;
}

static void resizeDisplayInitOptions(ResizeDisplay * rd)
{
	CompOption *o;

	o = &rd->opt[RESIZE_DISPLAY_OPTION_INITIATE];
	o->advanced = False;
	o->name = "initiate";
	o->group = N_("Binding");
	o->subGroup = N_("");
	o->displayHints = "";
	o->shortDesc = N_("Initiate Window Resize");
	o->longDesc = N_("Start Resizing a Window.");
	o->type = CompOptionTypeAction;
	o->value.action.initiate = resizeInitiate;
	o->value.action.terminate = resizeTerminate;
	o->value.action.bell = FALSE;
	o->value.action.edgeMask = 0;
	o->value.action.type = CompBindingTypeButton;
	o->value.action.state = CompActionStateInitButton;
	o->value.action.button.modifiers =
			RESIZE_INITIATE_BUTTON_MODIFIERS_DEFAULT;
	o->value.action.button.button = RESIZE_INITIATE_BUTTON_DEFAULT;
	o->value.action.type |= CompBindingTypeKey;
	o->value.action.state |= CompActionStateInitKey;
	o->value.action.key.modifiers = RESIZE_INITIATE_KEY_MODIFIERS_DEFAULT;
	o->value.action.key.keysym = XStringToKeysym(RESIZE_INITIATE_KEY_DEFAULT);

	o = &rd->opt[RESIZE_DISPLAY_OPTION_WARP_POINTER];
	o->advanced = False;
	o->name = "warp_pointer";
	o->group = N_("Behaviour");
	o->subGroup = N_("");
	o->displayHints = "";
	o->shortDesc =
			N_
			("Warp Pointer When Starting a Resize With Mouse Initiate Combo.");
	o->longDesc =
			N_("If this is set, the Pointer will be Warped to the "
			   "closest corner when you start a Resize.");
	o->type = CompOptionTypeBool;
	o->value.b = RESIZE_WARP_POINTER_DEFAULT;

	o = &rd->opt[RESIZE_DISPLAY_OPTION_OPACITY];
	o->advanced = False;
	o->name = "opacity";
	o->group = N_("Appearance");
	o->subGroup = N_("Opacity");
	o->displayHints = "";
	o->shortDesc = N_("Opacity");
	o->longDesc = N_("Opacity level of resizing windows.");
	o->type = CompOptionTypeInt;
	o->value.i = RESIZE_OPACITY_DEFAULT;
	o->rest.i.min = RESIZE_OPACITY_MIN;
	o->rest.i.max = RESIZE_OPACITY_MAX;

	o = &rd->opt[RESIZE_DISPLAY_OPTION_OPACIFY_MIN_OPACITY];
	o->advanced = False;
	o->name = "opacify_min_opacity";
	o->group = N_("Appearance");
	o->subGroup = N_("Opacity");
	o->displayHints = "";
	o->shortDesc = N_("Minimum Opacity for Opacify");
	o->longDesc =
			N_
			("Opacify only Windows whose Opacity is higher than this value.");
	o->type = CompOptionTypeInt;
	o->value.i = RESIZE_OPACIFY_MIN_OPACITY_DEFAULT;
	o->rest.i.min = RESIZE_OPACIFY_MIN_OPACITY_MIN;
	o->rest.i.max = RESIZE_OPACIFY_MIN_OPACITY_MAX;

	o = &rd->opt[RESIZE_DISPLAY_OPTION_SYNC_WINDOW];
	o->advanced = False;
	o->name = "sync_window";
	o->group = N_("Behaviour");
	o->subGroup = N_("");
	o->displayHints = "";
	o->shortDesc = N_("Repaints the Window on Each Resize Step");
	o->longDesc =
			N_("If this is set to true the Window will Repaint itself "
			   "during Resize, which may cause some lag.");
	o->type = CompOptionTypeBool;
	o->value.b = RESIZE_SYNC_WINDOW_DEFAULT;

	o = &rd->opt[RESIZE_DISPLAY_OPTION_MODE];
	o->advanced = False;
	o->name = "resize_mode";
	o->group = N_("Behaviour");
	o->subGroup = N_("");
	o->displayHints = "";
	o->shortDesc = N_("Resize Display Mode");
	o->longDesc =
			N_
			("Select between 'Normal', 'Stretched Texture', "
			 "'Outline and Filled' outline modes.");
	o->type = CompOptionTypeString;
	o->value.s = strdup(resizeModes[RESIZE_MODE_DEFAULT]);
	o->rest.s.string = resizeModes;
	o->rest.s.nString = NUM_RESIZE_MODES;

	o = &rd->opt[RESIZE_DISPLAY_OPTION_BORDER_COLOR];
	o->advanced = False;
	o->name = "border_color";
	o->group = N_("Appearance");
	o->subGroup = N_("Outline Mode");
	o->displayHints = "";
	o->shortDesc = N_("Outline Color");
	o->longDesc = N_("Outline Color for Outline and Filled Outline modes.");
	o->type = CompOptionTypeColor;
	o->value.c[0] = 0x2fff;
	o->value.c[1] = 0x2fff;
	o->value.c[2] = 0x4fff;
	o->value.c[3] = 0x9fff;

	o = &rd->opt[RESIZE_DISPLAY_OPTION_FILL_COLOR];
	o->advanced = False;
	o->name = "fill_color";
	o->group = N_("Appearance");
	o->subGroup = N_("Outline Mode");
	o->displayHints = "";
	o->shortDesc = N_("Fill Color");
	o->longDesc = N_("Fill Color for Filled Outline mode.");
	o->type = CompOptionTypeColor;
	o->value.c[0] = 0x2fff;
	o->value.c[1] = 0x2fff;
	o->value.c[2] = 0x4fff;
	o->value.c[3] = 0x4fff;
}

static CompOption *resizeGetDisplayOptions(CompDisplay * display, int *count)
{
	if (display)
	{
		RESIZE_DISPLAY(display);

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

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

static Bool resizeInitDisplay(CompPlugin * p, CompDisplay * d)
{
	ResizeDisplay *rd;
	int i;

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

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

	rd->resizeMode = ResizeModeNormal;
	rd->resizeOpacity = (RESIZE_OPACITY_DEFAULT * OPAQUE) / 100;
	rd->opacifyMinOpacity =
			(RESIZE_OPACIFY_MIN_OPACITY_DEFAULT * OPAQUE) / 100;

	resizeDisplayInitOptions(rd);

	rd->w = 0;
	rd->ungrabPending = FALSE;

	rd->releaseButton = 0;

	for (i = 0; i < NUM_KEYS; i++)
		rd->key[i] = XKeysymToKeycode(d->display,
									  XStringToKeysym(rKeys[i].name));

	WRAP(rd, d, handleEvent, resizeHandleEvent);

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

	return TRUE;
}

static void resizeFiniDisplay(CompPlugin * p, CompDisplay * d)
{
	RESIZE_DISPLAY(d);

	freeScreenPrivateIndex(d, rd->screenPrivateIndex);

	UNWRAP(rd, d, handleEvent);

	free(rd);
}

static Bool resizeInitScreen(CompPlugin * p, CompScreen * s)
{
	ResizeScreen *rs;

	RESIZE_DISPLAY(s->display);

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

	rs->grabIndex = 0;

	rs->leftCursor = XCreateFontCursor(s->display->display, XC_left_side);
	rs->rightCursor = XCreateFontCursor(s->display->display, XC_right_side);
	rs->upCursor = XCreateFontCursor(s->display->display, XC_top_side);
	rs->upLeftCursor = XCreateFontCursor(s->display->display,
										 XC_top_left_corner);
	rs->upRightCursor = XCreateFontCursor(s->display->display,
										  XC_top_right_corner);
	rs->downCursor = XCreateFontCursor(s->display->display, XC_bottom_side);
	rs->downLeftCursor = XCreateFontCursor(s->display->display,
										   XC_bottom_left_corner);
	rs->downRightCursor = XCreateFontCursor(s->display->display,
											XC_bottom_right_corner);
	rs->middleCursor = XCreateFontCursor(s->display->display, XC_fleur);

	rs->cursor[0] = rs->leftCursor;
	rs->cursor[1] = rs->rightCursor;
	rs->cursor[2] = rs->upCursor;
	rs->cursor[3] = rs->downCursor;

	addScreenAction(s, &rd->opt[RESIZE_DISPLAY_OPTION_INITIATE].value.action);

	WRAP(rs, s, paintWindow, resizePaintWindow);
	WRAP(rs, s, preparePaintScreen, resizePreparePaintScreen);
	WRAP(rs, s, paintScreen, resizePaintScreen);
	WRAP(rs, s, paintTransformedScreen, resizePaintTransformedScreen);
	WRAP(rs, s, donePaintScreen, resizeDonePaintScreen);

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

	return TRUE;
}

static void resizeFiniScreen(CompPlugin * p, CompScreen * s)
{
	RESIZE_SCREEN(s);
	RESIZE_DISPLAY(s->display);

	UNWRAP(rs, s, paintWindow);
	UNWRAP(rs, s, preparePaintScreen);
	UNWRAP(rs, s, paintScreen);
	UNWRAP(rs, s, paintTransformedScreen);
	UNWRAP(rs, s, donePaintScreen);

	removeScreenAction(s,
					   &rd->opt[RESIZE_DISPLAY_OPTION_INITIATE].value.action);

	XFreeCursor(s->display->display, rs->leftCursor);
	XFreeCursor(s->display->display, rs->rightCursor);
	XFreeCursor(s->display->display, rs->upCursor);
	XFreeCursor(s->display->display, rs->upLeftCursor);
	XFreeCursor(s->display->display, rs->upRightCursor);
	XFreeCursor(s->display->display, rs->downCursor);
	XFreeCursor(s->display->display, rs->downLeftCursor);
	XFreeCursor(s->display->display, rs->downRightCursor);
	XFreeCursor(s->display->display, rs->middleCursor);

	free(rs);
}

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

	return TRUE;
}

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

CompPluginFeature resizeFeatures[] = {
	{"resize"}
};

CompPluginVTable resizeVTable = {
	"resize",
	N_("Resize Window"),
	N_("Resize window"),
	resizeInit,
	resizeFini,
	resizeInitDisplay,
	resizeFiniDisplay,
	resizeInitScreen,
	resizeFiniScreen,
	0,							/* InitWindow */
	0,							/* FiniWindow */
	resizeGetDisplayOptions,
	resizeSetDisplayOption,
	0,							/* GetScreenOptions */
	0,							/* SetScreenOption */
	NULL,
	0,
	resizeFeatures,
	sizeof(resizeFeatures) / sizeof(resizeFeatures[0]),
	BERYL_ABI_INFO,
	"beryl-plugins",
	"wm",
	0,
	0,
	True,
};

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


syntax highlighted by Code2HTML, v. 0.9.1