/*
 * Copyright © 2005 Novell, Inc.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 *
 * Author: David Reveman <davidr@novell.com>
 */

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

#include <X11/cursorfont.h>

#include <beryl.h>

#define MOVE_INITIATE_BUTTON_DEFAULT           Button1
#define MOVE_INITIATE_BUTTON_MODIFIERS_DEFAULT CompAltMask

#define MOVE_INITIATE_KEY_DEFAULT        "F7"
#define MOVE_INITIATE_KEY_MODIFIERS_DEFAULT CompAltMask

#define MOVE_OPACITY_DEFAULT 100
#define MOVE_OPACITY_MIN     1
#define MOVE_OPACITY_MAX     100

#define MOVE_CONSTRAIN_Y_TOP_DEFAULT TRUE
#define MOVE_CONSTRAIN_Y_BOTTOM_DEFAULT FALSE

#define MOVE_SNAPOFF_MAXIMIZED_DEFAULT FALSE

#define MOVE_OPACIFY_MIN_OPACITY_DEFAULT 80
#define MOVE_OPACIFY_MIN_OPACITY_MIN     1
#define MOVE_OPACIFY_MIN_OPACITY_MAX     100

#define MOVE_SNAPOFF_DISTANCE_DEFAULT    100
#define MOVE_SNAPOFF_DISTANCE_MIN	 1
#define MOVE_SNAPOFF_DISTANCE_MAX	 300

#define MOVE_SNAPBACK_DISTANCE_DEFAULT	 20
#define MOVE_SNAPBACK_DISTANCE_MIN	 1
#define MOVE_SNAPBACK_DISTANCE_MAX	 100

struct _MoveKeys
{
	char *name;
	int dx;
	int dy;
} mKeys[] =
{
	{
	"Left", -1, 0},
	{
	"Right", 1, 0},
	{
	"Up", 0, -1},
	{
	"Down", 0, 1}
};

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

#define KEY_MOVE_INC 24

static int displayPrivateIndex;

#define MOVE_DISPLAY_OPTION_INITIATE			0
#define MOVE_DISPLAY_OPTION_OPACITY				1
#define MOVE_DISPLAY_OPTION_CONSTRAIN_Y_TOP		2
#define MOVE_DISPLAY_OPTION_CONSTRAIN_Y_BOTTOM  3
#define MOVE_DISPLAY_OPTION_OPACIFY_MIN_OPACITY 4
#define MOVE_DISPLAY_OPTION_SNAPOFF_MAXIMIZED	5
#define MOVE_DISPLAY_OPTION_SNAPOFF_DISTANCE	6
#define MOVE_DISPLAY_OPTION_SNAPBACK_DISTANCE   7
#define MOVE_DISPLAY_OPTION_NUM					8

typedef struct _MoveDisplay
{
	int screenPrivateIndex;
	HandleEventProc handleEvent;

	CompOption opt[MOVE_DISPLAY_OPTION_NUM];

	CompWindow *w;
	int x;
	int y;
	Region region;
	int status;

	KeyCode key[NUM_KEYS];
	Bool moveOpacitySet;
	GLushort moveOpacity;
	GLushort opacifyMinOpacity;
} MoveDisplay;

typedef struct _MoveScreen
{
	int grabIndex;

	Cursor moveCursor;

	unsigned int origState;

	int snapOffY;
	int snapBackY;
} MoveScreen;

#define GET_MOVE_DISPLAY(d)                     \
    ((MoveDisplay *) (d)->privates[displayPrivateIndex].ptr)

#define MOVE_DISPLAY(d)                   \
    MoveDisplay *md = GET_MOVE_DISPLAY (d)

#define GET_MOVE_SCREEN(s, md)                         \
    ((MoveScreen *) (s)->privates[(md)->screenPrivateIndex].ptr)

#define MOVE_SCREEN(s)                                \
    MoveScreen *ms = GET_MOVE_SCREEN (s, GET_MOVE_DISPLAY (s->display))

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

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

	MOVE_DISPLAY(d);

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

	w = findWindowAtDisplay(d, xid);
	if (w)
	{
		XRectangle workArea;
		unsigned int mods;
		int x, y, head;

		MOVE_SCREEN(w->screen);

		head = screenGetOutputDevForWindow(w);
		screenGetOutputDevWorkArea(w->screen, head, &workArea);

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

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

		if (md->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 (md->region)
		{
			XDestroyRegion(md->region);
			md->region = NULL;
		}

		md->status = RectangleOut;

		md->x = 0;
		md->y = 0;

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

		ms->origState = w->state;

		ms->snapBackY = w->serverY - workArea.y;
		ms->snapOffY = y - workArea.y;

		if (!ms->grabIndex)
			ms->grabIndex = pushScreenGrab(w->screen, ms->moveCursor, "move");

		if (ms->grabIndex)
		{
			md->w = w;

			(w->screen->windowGrabNotify) (w, x, y, mods,
										   CompWindowGrabMoveMask
										   | CompWindowGrabButtonMask);

			if (md->moveOpacity != OPAQUE &&
				w->paint.opacity >= md->opacifyMinOpacity)
			{
				/* modify opacity of windows that are not active */
				setWindowOpacity(w, md->moveOpacity, PL_TEMP_HELLO);
				md->moveOpacitySet = True;
			}
			else
				md->moveOpacitySet = False;
			if (state & CompActionStateInitKey)
			{
				int xRoot, yRoot;

				xRoot = w->attrib.x + (w->width / 2);
				yRoot = w->attrib.y + (w->height / 2);

				warpPointer(d, xRoot - d->pointerX, yRoot - d->pointerY);
			}
		}
	}

	return FALSE;
}

static Bool
moveTerminate(CompDisplay * d,
			  CompAction * action,
			  CompActionState state, CompOption * option, int nOption)
{
	MOVE_DISPLAY(d);

	if (md->w)
	{
		MOVE_SCREEN(md->w->screen);

		(md->w->screen->windowUngrabNotify) (md->w);

		if (state & CompActionStateCancel)
			moveWindow(md->w,
					   md->w->serverX - md->w->attrib.x,
					   md->w->serverY - md->w->attrib.y, TRUE, FALSE);

		syncWindowPosition(md->w);
		if (md->moveOpacitySet)
			resetWindowOpacity(md->w, PL_TEMP_HELLO);
		damageScreen(md->w->screen);

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

		md->w = 0;
	}

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

	return FALSE;
}

static Region moveGetYConstrainRegion(CompScreen * s)
{
	CompWindow *w;
	Region region;
	REGION r;
	int i;

	region = XCreateRegion();
	if (!region)
		return NULL;

	r.rects = &r.extents;
	r.numRects = r.size = 1;

	r.extents.x1 = MINSHORT;
	r.extents.y1 = 0;
	r.extents.x2 = 0;
	r.extents.y2 = s->height;

	XUnionRegion(&r, region, region);

	r.extents.x1 = s->width;
	r.extents.x2 = MAXSHORT;

	XUnionRegion(&r, region, region);

	for (i = 0; i < s->nOutputDev; i++)
		XUnionRegion(&s->outputDev[i].region, region, region);

	for (w = s->windows; w; w = w->next)
	{
		if (!w->mapNum)
			continue;

		if (w->struts)
		{
			r.extents.x1 = w->struts->top.x;
			r.extents.y1 = w->struts->top.y;
			r.extents.x2 = r.extents.x1 + w->struts->top.width;
			r.extents.y2 = r.extents.y1 + w->struts->top.height;

			XSubtractRegion(region, &r, region);

			r.extents.x1 = w->struts->bottom.x;
			r.extents.y1 = w->struts->bottom.y;
			r.extents.x2 = r.extents.x1 + w->struts->bottom.width;
			r.extents.y2 = r.extents.y1 + w->struts->bottom.height;

			XSubtractRegion(region, &r, region);
		}
	}

	return region;
}


static void moveHandleMotionEvent(CompScreen * s, int xRoot, int yRoot)
{
	MOVE_SCREEN(s);

	if (ms->grabIndex)
	{
		CompWindow *w;
		int dx, dy;

		MOVE_DISPLAY(s->display);

		w = md->w;
		md->x += xRoot - s->display->lastPointerX;
		md->y += yRoot - s->display->lastPointerY;

		if (md->w->syncWait)
			return;

		if (w->type & CompWindowTypeFullscreenMask)
		{
			dx = dy = 0;
		}
		else
		{
			XRectangle workArea;
			int min, max;
			Bool constrainTop =
					md->opt[MOVE_DISPLAY_OPTION_CONSTRAIN_Y_TOP].value.b;
			Bool constrainBottom =
					md->opt[MOVE_DISPLAY_OPTION_CONSTRAIN_Y_BOTTOM].value.b;

			dx = md->x;
			dy = md->y;

			screenGetOutputDevWorkArea(s, screenGetOutputDevForWindow(w),
									   &workArea);

			if (constrainTop || constrainBottom)
			{
				if (!md->region)
					md->region = moveGetYConstrainRegion(s);

				/* make sure that the top frame extents or the top row of
				   pixels are within what is currently our valid screen
				   region */
				if (md->region)
				{
					int x, y, width, height;
					int status;

					if (constrainTop)
					{
						y = w->attrib.y + dy - w->input.top;
						height = w->input.top ? w->input.top : 1;
						if (constrainBottom)
							height += w->height + w->input.bottom;
					}
					else /* if (constrainBottom) redundant */
					{
						y = w->attrib.y + dy + w->height;
						height = w->input.bottom ? w->input.bottom : 1;
					}

					x = w->attrib.x + dx - w->input.left;
					width = w->width + w->input.left + w->input.right;

					status = XRectInRegion(md->region, x, y, width, height);

					/* only constrain movement if previous position was valid */
					if (md->status == RectangleIn)
					{
						int xStatus = status;

						while (dx && xStatus != RectangleIn)
						{
							xStatus =
									XRectInRegion(md->region, x, y - dy,
												  width, height);

							if (xStatus != RectangleIn)
								dx += (dx < 0) ? 1 : -1;

							x = w->attrib.x + dx - w->input.left;
						}

						while (dy && status != RectangleIn)
						{
							status = XRectInRegion(md->region,
												   x, y, width, height);

							if (status != RectangleIn)
								dy += (dy < 0) ? 1 : -1;

							y = w->attrib.y + dy +
									(constrainTop ? (-w->input.top) : w->
									 height);
						}
					}
					else
					{
						md->status = status;
					}
				}
			}

			if (md->opt[MOVE_DISPLAY_OPTION_SNAPOFF_MAXIMIZED].value.b)
			{
				if (w->state & CompWindowStateMaximizedVertMask)
				{
					if ((yRoot - workArea.y) -
						ms->snapOffY >=
						md->opt[MOVE_DISPLAY_OPTION_SNAPOFF_DISTANCE].value.i)
					{
						XWindowChanges xwc;

						syncWindowPosition(w);

						xwc.x = w->serverX;
						xwc.y = w->serverY;
						xwc.width = w->serverWidth;
						xwc.height = w->serverHeight;

						restoreHorzRestoreData(w, &xwc);
						restoreVertRestoreData(w, &xwc);

						xwc.x = xRoot - (xwc.width >> 1);
						xwc.y = yRoot + (w->input.top >> 1);

						saveHorzRestoreData(w, &xwc);
						saveVertRestoreData(w, &xwc);

						md->x = md->y = 0;

						maximizeWindow(w, 0);

						ms->snapOffY = ms->snapBackY;

						return;
					}
				}
				else if (ms->origState & CompWindowStateMaximizedVertMask)
				{
					if ((yRoot - workArea.y) -
						ms->snapBackY <
						md->
						opt[MOVE_DISPLAY_OPTION_SNAPBACK_DISTANCE].value.i)
					{
						if (!otherScreenGrabExist(s, "move", 0))
						{
							int wy;

							/* update server position before maximizing window
							   again so it is maximized on the correct output */
							syncWindowPosition(w);

							maximizeWindow(w, ms->origState);

							wy = workArea.y + (w->input.top >> 1);
							wy += w->sizeHints.height_inc >> 1;

							warpPointer(s->
										display,
										0, wy - s->display->pointerY);

							return;
						}
					}
				}
			}

			if (w->state & CompWindowStateMaximizedVertMask)
			{
				min = workArea.y + w->input.top;
				max = workArea.y + workArea.height -
						w->input.bottom - w->serverHeight -
						w->serverBorderWidth * 2;

				if (w->attrib.y + dy < min)
					dy = min - w->attrib.y;
				else if (w->attrib.y + dy > max)
					dy = max - w->attrib.y;
			}

			if (w->state & CompWindowStateMaximizedHorzMask)
			{
				if (w->attrib.x + w->serverWidth + w->serverBorderWidth < 0)
					return;

				min = workArea.x + w->input.left;
				max = workArea.x + workArea.width -
						w->input.right - w->serverWidth -
						w->serverBorderWidth * 2;

				if (w->attrib.x + dx < min)
					dx = min - w->attrib.x;
				else if (w->attrib.x + dx > max)
					dx = max - w->attrib.x;
			}
		}

		moveWindow(md->w, dx, dy, TRUE, FALSE);

		damageScreen(s);

		md->x -= dx;
		md->y -= dy;
	}
}

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

	MOVE_DISPLAY(d);

	switch (event->type)
	{
	case ButtonPress:
	case ButtonRelease:
		s = findScreenAtDisplay(d, event->xbutton.root);
		if (s)
		{
			MOVE_SCREEN(s);

			if (ms->grabIndex)
			{
				moveTerminate(d,
							  &md->
							  opt
							  [MOVE_DISPLAY_OPTION_INITIATE].
							  value.action, 0, NULL, 0);
			}
		}
		break;
	case KeyPress:
	case KeyRelease:
		s = findScreenAtDisplay(d, event->xkey.root);
		if (s)
		{
			MOVE_SCREEN(s);

			if (ms->grabIndex && event->type == KeyPress)
			{
				int i;

				for (i = 0; i < NUM_KEYS; i++)
				{
					if (event->xkey.keycode == md->key[i])
					{
						XWarpPointer(d->display,
									 None, None, 0,
									 0, 0, 0,
									 mKeys[i].dx *
									 KEY_MOVE_INC,
									 mKeys[i].dy * KEY_MOVE_INC);
						break;
					}
				}
			}
		}
		break;
	case MotionNotify:
		s = findScreenAtDisplay(d, event->xmotion.root);
		if (s)
			moveHandleMotionEvent(s, d->pointerX, d->pointerY);
		break;
	case EnterNotify:
	case LeaveNotify:
		s = findScreenAtDisplay(d, event->xcrossing.root);
		if (s)
			moveHandleMotionEvent(s, d->pointerX, d->pointerY);
		break;
	case ClientMessage:
		if (event->xclient.message_type == d->wmMoveResizeAtom)
		{
			CompWindow *w;

			if (event->xclient.data.l[2] == WmMoveResizeMove ||
				event->xclient.data.l[2] == WmMoveResizeMoveKeyboard)
			{
				w = findWindowAtDisplay(d, event->xclient.window);
				if (w)
				{
					CompOption o[4];
					int xRoot, yRoot;
					CompAction *action =
							&md->opt[MOVE_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] == WmMoveResizeMoveKeyboard)
					{
						moveInitiate(d, action, CompActionStateInitKey, o, 1);
					}
					else
					{
						unsigned int mods;
						Window root, child;
						int 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];

							moveInitiate(d,
										 action,
										 CompActionStateInitButton, o, 4);

							moveHandleMotionEvent(w->screen, xRoot, yRoot);
						}
					}
				}
			}
		}
		break;
	case DestroyNotify:
		if (md->w && md->w->id == event->xdestroywindow.window)
			moveTerminate(d,
						  &md->
						  opt[MOVE_DISPLAY_OPTION_INITIATE].
						  value.action, 0, NULL, 0);
		break;
	case UnmapNotify:
		if (md->w && md->w->id == event->xunmap.window)
			moveTerminate(d,
						  &md->
						  opt[MOVE_DISPLAY_OPTION_INITIATE].
						  value.action, 0, NULL, 0);
	default:
		break;
	}

	UNWRAP(md, d, handleEvent);
	(*d->handleEvent) (d, event);
	WRAP(md, d, handleEvent, moveHandleEvent);
}

static void moveDisplayInitOptions(MoveDisplay * md)
{
	CompOption *o;

	o = &md->opt[MOVE_DISPLAY_OPTION_INITIATE];
	o->advanced = False;
	o->name = "initiate";
	o->group = N_("Bindings");
	o->subGroup = N_("Move window");
	o->displayHints = "";
	o->shortDesc = N_("Initiate Window Move");
	o->longDesc = N_("Start moving Window.");
	o->type = CompOptionTypeAction;
	o->value.action.initiate = moveInitiate;
	o->value.action.terminate = moveTerminate;
	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 = MOVE_INITIATE_BUTTON_MODIFIERS_DEFAULT;
	o->value.action.button.button = MOVE_INITIATE_BUTTON_DEFAULT;
	o->value.action.type |= CompBindingTypeKey;
	o->value.action.state |= CompActionStateInitKey;
	o->value.action.key.modifiers = MOVE_INITIATE_KEY_MODIFIERS_DEFAULT;
	o->value.action.key.keysym = XStringToKeysym(MOVE_INITIATE_KEY_DEFAULT);

	o = &md->opt[MOVE_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 = MOVE_OPACIFY_MIN_OPACITY_DEFAULT;
	o->rest.i.min = MOVE_OPACIFY_MIN_OPACITY_MIN;
	o->rest.i.max = MOVE_OPACIFY_MIN_OPACITY_MAX;

	o = &md->opt[MOVE_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 Moving windows.");
	o->type = CompOptionTypeInt;
	o->value.i = MOVE_OPACITY_DEFAULT;
	o->rest.i.min = MOVE_OPACITY_MIN;
	o->rest.i.max = MOVE_OPACITY_MAX;

	o = &md->opt[MOVE_DISPLAY_OPTION_CONSTRAIN_Y_TOP];
	o->advanced = False;
	o->name = "constrain_y_top";
	o->group = N_("Misc. options");
	o->subGroup = N_("Movement constraints");
	o->displayHints = "";
	o->shortDesc = N_("Constrain Y to Top of Screen");
	o->longDesc =
			N_("Prevent windows from Moving over the Top of the Screen.");
	o->type = CompOptionTypeBool;
	o->value.b = MOVE_CONSTRAIN_Y_TOP_DEFAULT;

	o = &md->opt[MOVE_DISPLAY_OPTION_CONSTRAIN_Y_BOTTOM];
	o->advanced = False;
	o->name = "constrain_y_bottom";
	o->group = N_("Misc. options");
	o->subGroup = N_("Movement constraints");
	o->displayHints = "";
	o->shortDesc = N_("Constrain Y to Bottom of Screen");
	o->longDesc =
			N_("Prevent windows from Moving past the Bottom of the Screen.");
	o->type = CompOptionTypeBool;
	o->value.b = MOVE_CONSTRAIN_Y_BOTTOM_DEFAULT;

	o = &md->opt[MOVE_DISPLAY_OPTION_SNAPOFF_MAXIMIZED];
	o->advanced = False;
	o->name = "snapoff_maximized";
	o->group = N_("Misc. options");
	o->subGroup = N_("Snapoff/snapback");
	o->displayHints = "";
	o->shortDesc = N_("Snapoff Maximized Windows");
	o->longDesc = N_("Snap off and unMaximize Maximized Windows "
					 "when dragging.");
	o->type = CompOptionTypeBool;
	o->value.b = MOVE_SNAPOFF_MAXIMIZED_DEFAULT;

	o = &md->opt[MOVE_DISPLAY_OPTION_SNAPOFF_DISTANCE];
	o->advanced = False;
	o->name = "snapoff_distance";
	o->group = N_("Misc. options");
	o->subGroup = N_("Snapoff/snapback");
	o->displayHints = "";
	o->shortDesc = N_("Snapoff Distance");
	o->longDesc =
			N_("Pointer movement Distance after which window is Snapped off.");
	o->type = CompOptionTypeInt;
	o->value.i = MOVE_SNAPOFF_DISTANCE_DEFAULT;
	o->rest.i.min = MOVE_SNAPOFF_DISTANCE_MIN;
	o->rest.i.max = MOVE_SNAPOFF_DISTANCE_MAX;

	o = &md->opt[MOVE_DISPLAY_OPTION_SNAPBACK_DISTANCE];
	o->advanced = False;
	o->name = "snapback_distance";
	o->group = N_("Misc. options");
	o->subGroup = N_("Snapoff/snapback");
	o->displayHints = "";
	o->shortDesc = N_("Snapback Distance");
	o->longDesc = N_("Pointer movement Distance to Snap window back.");
	o->type = CompOptionTypeInt;
	o->value.i = MOVE_SNAPBACK_DISTANCE_DEFAULT;
	o->rest.i.min = MOVE_SNAPBACK_DISTANCE_MIN;
	o->rest.i.max = MOVE_SNAPBACK_DISTANCE_MAX;
}

static CompOption *moveGetDisplayOptions(CompDisplay * display, int *count)
{
	if (display)
	{
		MOVE_DISPLAY(display);

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

		moveDisplayInitOptions(md);
		*count = NUM_OPTIONS(md);
		return md->opt;
	}
}

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

	MOVE_DISPLAY(display);

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

	switch (index)
	{
	case MOVE_DISPLAY_OPTION_INITIATE:
		if (setDisplayAction(display, o, value))
			return TRUE;
		break;
	case MOVE_DISPLAY_OPTION_OPACITY:
		if (compSetIntOption(o, value))
		{
			md->moveOpacity = (o->value.i * OPAQUE) / 100;
			return TRUE;
		}
		break;
	case MOVE_DISPLAY_OPTION_OPACIFY_MIN_OPACITY:
		if (compSetIntOption(o, value))
		{
			md->opacifyMinOpacity = (o->value.i * OPAQUE) / 100;
			return TRUE;
		}
		break;
	case MOVE_DISPLAY_OPTION_SNAPOFF_DISTANCE:
	case MOVE_DISPLAY_OPTION_SNAPBACK_DISTANCE:
		if (compSetIntOption(o, value))
			return TRUE;
		break;
	case MOVE_DISPLAY_OPTION_CONSTRAIN_Y_TOP:
	case MOVE_DISPLAY_OPTION_CONSTRAIN_Y_BOTTOM:
	case MOVE_DISPLAY_OPTION_SNAPOFF_MAXIMIZED:
		if (compSetBoolOption(o, value))
			return TRUE;
		break;
	default:
		break;
	}

	return FALSE;
}

static Bool moveInitDisplay(CompPlugin * p, CompDisplay * d)
{
	MoveDisplay *md;
	int i;

	md = malloc(sizeof(MoveDisplay));
	if (!md)
		return FALSE;

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

	md->moveOpacity = (MOVE_OPACITY_DEFAULT * OPAQUE) / 100;

	md->opacifyMinOpacity = (MOVE_OPACIFY_MIN_OPACITY_DEFAULT * OPAQUE) / 100;

	moveDisplayInitOptions(md);

	md->w = 0;
	md->region = NULL;

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

	WRAP(md, d, handleEvent, moveHandleEvent);

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

	return TRUE;
}

static void moveFiniDisplay(CompPlugin * p, CompDisplay * d)
{
	MOVE_DISPLAY(d);

	freeScreenPrivateIndex(d, md->screenPrivateIndex);

	UNWRAP(md, d, handleEvent);

	free(md);
}

static Bool moveInitScreen(CompPlugin * p, CompScreen * s)
{
	MoveScreen *ms;

	MOVE_DISPLAY(s->display);

	ms = malloc(sizeof(MoveScreen));
	if (!ms)
		return FALSE;

	ms->grabIndex = 0;

	ms->moveCursor = XCreateFontCursor(s->display->display, XC_fleur);

	addScreenAction(s, &md->opt[MOVE_DISPLAY_OPTION_INITIATE].value.action);

	s->privates[md->screenPrivateIndex].ptr = ms;

	return TRUE;
}

static void moveFiniScreen(CompPlugin * p, CompScreen * s)
{
	MOVE_SCREEN(s);
	MOVE_DISPLAY(s->display);

	removeScreenAction(s,
					   &md->opt[MOVE_DISPLAY_OPTION_INITIATE].value.action);

	XFreeCursor(s->display->display, ms->moveCursor);

	free(ms);
}

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

	return TRUE;
}

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

CompPluginVTable moveVTable = {
	"move",
	N_("Move Window"),
	N_("Move window"),
	moveInit,
	moveFini,
	moveInitDisplay,
	moveFiniDisplay,
	moveInitScreen,
	moveFiniScreen,
	0,							/* InitWindow */
	0,							/* FiniWindow */
	moveGetDisplayOptions,
	moveSetDisplayOption,
	0,							/* GetScreenOptions */
	0,							/* SetScreenOption */
	NULL,
	0,
	0,
	0,
	BERYL_ABI_INFO,
	"beryl-plugins",
	"wm",
	0,
	0,
	True,
};

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


syntax highlighted by Code2HTML, v. 0.9.1