/*
 * Copyright © 2006 Red Hat, 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.
 *
 * Author: Soren Sandmann <sandmann@redhat.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>

static int displayPrivateIndex;

#define PLANE_LEFT_KEY_DEFAULT        "Left"
#define PLANE_LEFT_MODIFIERS_DEFAULT  (ControlMask | CompAltMask)

#define PLANE_RIGHT_KEY_DEFAULT       "Right"
#define PLANE_RIGHT_MODIFIERS_DEFAULT (ControlMask | CompAltMask)

#define PLANE_UP_KEY_DEFAULT          "Up"
#define PLANE_UP_MODIFIERS_DEFAULT    (ControlMask | CompAltMask)

#define PLANE_DOWN_KEY_DEFAULT        "Down"
#define PLANE_DOWN_MODIFIERS_DEFAULT  (ControlMask | CompAltMask)

#define PLANE_PREVIEW_KEY_DEFAULT 		"Next"
#define PLANE_PREVIEW_MODIFIERS_DEFAULT 	(ControlMask | CompAltMask)

enum
{
	PLANE_DISPLAY_OPTION_LEFT,
	PLANE_DISPLAY_OPTION_RIGHT,
	PLANE_DISPLAY_OPTION_DOWN,
	PLANE_DISPLAY_OPTION_UP,
	PLANE_DISPLAY_OPTION_PREVIEW,
	PLANE_DISPLAY_OPTION_TO_1,
	PLANE_DISPLAY_OPTION_TO_2,
	PLANE_DISPLAY_OPTION_TO_3,
	PLANE_DISPLAY_OPTION_TO_4,
	PLANE_DISPLAY_OPTION_TO_5,
	PLANE_DISPLAY_OPTION_TO_6,
	PLANE_DISPLAY_OPTION_TO_7,
	PLANE_DISPLAY_OPTION_TO_8,
	PLANE_DISPLAY_OPTION_TO_9,
	PLANE_DISPLAY_OPTION_TO_10,
	PLANE_DISPLAY_OPTION_TO_11,
	PLANE_DISPLAY_OPTION_TO_12,
	PLANE_N_DISPLAY_OPTIONS
};

typedef struct _PlaneDisplay
{
	int screenPrivateIndex;
	HandleEventProc handleEvent;

	CompOption opt[PLANE_N_DISPLAY_OPTIONS];

	int flipTime;
} PlaneDisplay;

#define PLANE_WRAP_DEFAULT FALSE

#define PLANE_SCROLL_TIME_DEFAULT 250
#define PLANE_SCROLL_TIME_MAX 2000
#define PLANE_SCROLL_TIME_MIN 1
#define PLANE_SCROLL_TIME_PRECISION 1

enum
{
	PLANE_SCREEN_OPTION_WRAP,
	PLANE_SCREEN_OPTION_TIME,
	PLANE_SCREEN_OPTION_NUM
};

typedef struct _PlaneScreen
{
	CompOption opt[PLANE_SCREEN_OPTION_NUM];
	PaintTransformedScreenProc paintTransformedScreen;
	PreparePaintScreenProc preparePaintScreen;
	DonePaintScreenProc donePaintScreen;
	PaintScreenProc paintScreen;

	SetScreenOptionForPluginProc setScreenOptionForPlugin;
	WindowGrabNotifyProc windowGrabNotify;
	WindowUngrabNotifyProc windowUngrabNotify;

	CompTimeoutHandle timeout_handle;
	int timer;
	int preview;
	double cur_x;
	double cur_y;
	double dest_x;
	double dest_y;
	float initZoom;
} PlaneScreen;

#define GET_PLANE_DISPLAY(d)				       \
    ((PlaneDisplay *) (d)->privates[displayPrivateIndex].ptr)

#define PLANE_DISPLAY(d)		       \
    PlaneDisplay *pd = GET_PLANE_DISPLAY (d)

#define GET_PLANE_SCREEN(s, pd)				   \
    ((PlaneScreen *) (s)->privates[(pd)->screenPrivateIndex].ptr)

#define PLANE_SCREEN(s)						      \
    PlaneScreen *ps = GET_PLANE_SCREEN (s, GET_PLANE_DISPLAY (s->display))

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

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

	PLANE_SCREEN(screen);

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

	switch (index)
	{
	case PLANE_SCREEN_OPTION_WRAP:
		if (compSetBoolOption(o, value))
			return TRUE;
		break;
	case PLANE_SCREEN_OPTION_TIME:
		if (compSetIntOption(o, value))
			return TRUE;
		break;
	default:
		break;
	}
	return FALSE;
}

static void planeScreenInitOptions(PlaneScreen * ps)
{
	CompOption *o;

	o = &ps->opt[PLANE_SCREEN_OPTION_WRAP];
	o->advanced = False;
	o->name = "wrap";
	o->group = N_("Misc. options");
	o->subGroup = N_("");
	o->displayHints = "";
	o->shortDesc = N_("Wrap Around");
	o->longDesc = N_("Enable Wrapping Around.");
	o->type = CompOptionTypeBool;
	o->value.b = PLANE_WRAP_DEFAULT;

	o = &ps->opt[PLANE_SCREEN_OPTION_TIME];
	o->advanced = False;
	o->name = "time";
	o->group = N_("Misc. options");
	o->subGroup = N_("");
	o->displayHints = "";
	o->shortDesc = N_("Scrolling Time");
	o->longDesc = N_("Scrolling Time when switching desktops.");
	o->type = CompOptionTypeInt;
	o->value.i = PLANE_SCROLL_TIME_DEFAULT;
	o->rest.i.min = PLANE_SCROLL_TIME_MIN;
	o->rest.i.max = PLANE_SCROLL_TIME_MAX;
}

static CompOption *planeGetScreenOptions(CompScreen * screen, int *count)
{
	if (screen)
	{
		PLANE_SCREEN(screen);
		*count = NUM_OPTIONS(ps);
		return ps->opt;
	}
	else
	{
		PlaneScreen *ps = malloc(sizeof(PlaneScreen));

		planeScreenInitOptions(ps);
		*count = NUM_OPTIONS(ps);
		return ps->opt;
	}
}

static Bool end_move(void *data)
{
	CompScreen *screen = data;

	PLANE_SCREEN(screen);

	//wait until de-scale is finished before setting viewport
	if (screenGrabExist(screen, "scale", 0))
	{
		ps->timeout_handle = compAddTimeout(20, end_move, screen);
		return FALSE;
	}

	moveScreenViewport(screen, -ps->dest_x, -ps->dest_y, TRUE);

	ps->dest_x = 0;
	ps->dest_y = 0;

	ps->timeout_handle = 0;

	focusDefaultWindow(screen->display);

	return FALSE;
}

static void compute_translation(PlaneScreen * ps, double *x, double *y)
{
	double dx, dy;
	double elapsed =
			1 -
			(ps->timer / (double)(ps->opt[PLANE_SCREEN_OPTION_TIME].value.i));

	if (elapsed < 0.0)
		elapsed = 0.0;
	if (elapsed > 1.0)
		elapsed = 1.0;

	/* Use temporary variables to you can pass in &ps->cur_x */
	dx = (ps->dest_x - ps->cur_x) * elapsed + ps->cur_x;
	dy = (ps->dest_y - ps->cur_y) * elapsed + ps->cur_y;

	*x = dx;
	*y = dy;
}

static void move_viewport(CompScreen * screen, int dx, int dy)
{
	PLANE_SCREEN(screen);

	if (ps->timeout_handle)
	{
		compute_translation(ps, &ps->cur_x, &ps->cur_y);

		ps->dest_x += dx;
		ps->dest_y += dy;

		compRemoveTimeout(ps->timeout_handle);
	}
	else
	{
		ps->cur_x = 0.0;
		ps->cur_y = 0.0;
		ps->dest_x = dx;
		ps->dest_y = dy;
	}

	if (!ps->opt[PLANE_SCREEN_OPTION_WRAP].value.b)
	{
		if (ps->dest_x + screen->x > screen->hsize - 1)
			ps->dest_x = screen->hsize - screen->x - 1;

		if (ps->dest_x + screen->x < 0)
			ps->dest_x = -screen->x;

		if (ps->dest_y + screen->y > screen->vsize - 1)
			ps->dest_y = screen->vsize - screen->y - 1;

		if (ps->dest_y + screen->y < 0)
			ps->dest_y = -screen->y;
	}

	ps->timer = ps->opt[PLANE_SCREEN_OPTION_TIME].value.i;
	ps->timeout_handle =
			compAddTimeout(ps->opt[PLANE_SCREEN_OPTION_TIME].value.i,
						   end_move, screen);

	damageScreen(screen);
}

static void planePreparePaintScreen(CompScreen * s, int msSinceLastPaint)
{
	PLANE_SCREEN(s);

	ps->timer -= msSinceLastPaint;

	UNWRAP(ps, s, preparePaintScreen);

	(*s->preparePaintScreen) (s, msSinceLastPaint);

	WRAP(ps, s, preparePaintScreen, planePreparePaintScreen);
}

static void
planePaintTransformedScreen(CompScreen * screen,
							const ScreenPaintAttrib * sAttrib,
							Region region, int output, unsigned int mask)
{
	PLANE_SCREEN(screen);
	static Bool notfirst;

	/* If it's the first movement store the zCamera, not sure what a sane value would be so this is a stopgap */
	if (!notfirst)
	{
		ps->initZoom = sAttrib->zCamera;

	}
	UNWRAP(ps, screen, paintTransformedScreen);
	mask &= ~PAINT_SCREEN_CLEAR_MASK;
	if (ps->preview && !ps->timeout_handle)
	{

		ScreenPaintAttrib sa = *sAttrib;

		(*screen->paintTransformedScreen) (screen, &sa, region, output, mask);
		if (sa.zCamera != ps->initZoom)
		{

			/* If we are not moving zoom back LESS when drawing the current viewport than the others, gives a zoom of sorts to the selected viewport */
			sa.zCamera = -1.5;

			(*screen->paintTransformedScreen) (screen,
											   &sa, region, output, mask);
			WRAP(ps, screen, paintTransformedScreen,
				 planePaintTransformedScreen);

			return;

		}

	}
	glPushMatrix();

	glClear(GL_COLOR_BUFFER_BIT);

	if (ps->timeout_handle)
	{
		double dx, dy, tx, ty;
		int vx, vy;

		clearTargetOutput(screen->display, GL_COLOR_BUFFER_BIT);

		/* Find what the x and y change should be as to ensure the movement takes the user specified amount of time */
		compute_translation(ps, &dx, &dy);

		dx *= -1;
		dy *= -1;

		tx = dy;
		ty = dy;
		vx = 0;
		vy = 0;
		/* Make sure dx and dy are less than 1 and greater than -1 but move the viewports accordingly if they are greater or less than respectively */
		while (dx > 1)
		{
			dx -= 1.0;
			moveScreenViewport(screen, 1, 0, FALSE);
			vx++;
		}

		while (dx < -1)
		{
			dx += 1.0;
			moveScreenViewport(screen, -1, 0, FALSE);
			vx--;
		}

		while (dy > 1)
		{
			dy -= 1.0;
			moveScreenViewport(screen, 0, 1, FALSE);
			vy++;
		}

		while (dy < -1)
		{
			dy += 1.0;
			moveScreenViewport(screen, 0, -1, FALSE);
			vy--;
		}

#if 0
		glPushMatrix();

		if (dx > 0)
		{
			glTranslatef(dx, dy, 0.0);

			(*screen->paintTransformedScreen) (screen, sAttrib, mask);

			glTranslatef(-1.0, 0.0, 0.0);

			moveScreenViewport(screen, 1, 0, FALSE);

			(*screen->paintTransformedScreen) (screen, sAttrib, mask);

			moveScreenViewport(screen, -1, 0, FALSE);
		}
		else
		{
			glTranslatef(dx, dy, 0.0);

			(*screen->paintTransformedScreen) (screen, sAttrib, mask);

			glTranslatef(1.0, 0.0, 0.0);

			moveScreenViewport(screen, -1, 0, FALSE);

			(*screen->paintTransformedScreen) (screen, sAttrib, mask);

			moveScreenViewport(screen, 1, 0, FALSE);
		}

		glPopMatrix();
#endif

		glPushMatrix();
		ScreenPaintAttrib *sa = sAttrib;

		if (ps->preview)
		{
			sa->zCamera = -2;
		}
		else
		{
			sa->zCamera = ps->initZoom;
		}
		sAttrib = sa;
		/* Provide the effect of scrolling rather than snapping */
		glTranslatef(dx, -dy, 0.0);

		(*screen->paintTransformedScreen) (screen, sAttrib, region,
										   output, mask);
		/* Draw viewports a bit further apart if we are 'zoomed' */
		float s1 = 1.05;
		float s2 = -1.05;

		if (!ps->preview)
		{
			s1 = 1;
			s2 = -1;
		}
		/* FIXME: This is a horrible and highly unscalable way to do things. */
		if (dx > 0)
		{
			glTranslatef(s2, 0.0, 0.0);
			moveScreenViewport(screen, 1, 0, FALSE);
		}
		else
		{
			glTranslatef(s1, 0.0, 0.0);
			moveScreenViewport(screen, -1, 0, FALSE);
		}
		(*screen->paintTransformedScreen) (screen, sAttrib, region,
										   output, mask);
		if (dy > 0)
		{
			glTranslatef(0.0, s1, 0.0);
			moveScreenViewport(screen, 0, 1, FALSE);
		}
		else
		{
			glTranslatef(0.0, s2, 0.0);
			moveScreenViewport(screen, 0, -1, FALSE);
		}
		(*screen->paintTransformedScreen) (screen, sAttrib, region,
										   output, mask);
		if (dx > 0)
		{
			glTranslatef(s1, 0.0, 0.0);
			moveScreenViewport(screen, -1, 0, FALSE);
		}
		else
		{
			glTranslatef(s2, 0.0, 0.0);
			moveScreenViewport(screen, 1, 0, FALSE);
		}
		(*screen->paintTransformedScreen) (screen, sAttrib, region,
										   output, mask);
		if (dy > 0)
		{
			glTranslatef(0.0, s2, 0.0);
			moveScreenViewport(screen, 0, -1, FALSE);
		}
		else
		{
			glTranslatef(0.0, s1, 0.0);
			moveScreenViewport(screen, 0, 1, FALSE);
		}
		glTranslatef(-dx, -dy, 0.0);
		glPopMatrix();

		moveScreenViewport(screen, -vx, -vy, FALSE);
	}
	else
	{
		int i, j;

		for (i = -1; i < 2; i++)
		{
			for (j = -1; j < 2; j++)
			{
				glTranslatef(i, j, 0);
				moveScreenViewport(screen, -i, -j, FALSE);
				(*screen->paintTransformedScreen) (screen,
												   sAttrib,
												   region, output, mask);
				glTranslatef(-i, -j, 0);
				moveScreenViewport(screen, i, j, FALSE);
			}
		}
	}
	notfirst = TRUE;
	WRAP(ps, screen, paintTransformedScreen, planePaintTransformedScreen);
	glPopMatrix();

}

static void planeDonePaintScreen(CompScreen * s)
{
	PLANE_SCREEN(s);

	if (ps->timeout_handle)
		damageScreen(s);

	UNWRAP(ps, s, donePaintScreen);

	(*s->donePaintScreen) (s);

	WRAP(ps, s, donePaintScreen, planeDonePaintScreen);
}

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

	PLANE_SCREEN(s);

	if (ps->timeout_handle || ps->preview)
	{
		mask &= ~PAINT_SCREEN_REGION_MASK;
		mask |= PAINT_SCREEN_TRANSFORMED_MASK;
	}

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

	return status;
}

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

	PLANE_DISPLAY(d);

	switch (event->type)
	{
	case ClientMessage:
		if (event->xclient.message_type == d->winActiveAtom)
		{
			CompWindow *w;

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

				s = w->screen;

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

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

				defaultViewportForWindow(w, &dx, &dy);
				dx -= s->x;
				dy -= s->y;

				move_viewport(s, dx, dy);
			}
		}
		else if (event->xclient.message_type == d->desktopViewportAtom)
		{
			int dx, dy;

			s = findScreenAtDisplay(d, event->xclient.window);
			if (!s)
				break;

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

			dx = event->xclient.data.l[0] / s->width - s->x;
			dy = event->xclient.data.l[1] / s->height - s->y;

			if (!dx && !dy)
				break;

			move_viewport(s, dx, dy);
		}
		break;

	default:
		break;
	}

	UNWRAP(pd, d, handleEvent);
	(*d->handleEvent) (d, event);
	WRAP(pd, d, handleEvent, planeHandleEvent);
}

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

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

static void planeWindowUngrabNotify(CompWindow * w)
{
	PLANE_SCREEN(w->screen);

	UNWRAP(ps, w->screen, windowUngrabNotify);
	(*w->screen->windowUngrabNotify) (w);
	WRAP(ps, w->screen, windowUngrabNotify, planeWindowUngrabNotify);
}

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

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

	return status;
}

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

	PLANE_DISPLAY(display);

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

	switch (index)
	{
	case PLANE_DISPLAY_OPTION_LEFT:
	case PLANE_DISPLAY_OPTION_RIGHT:
	case PLANE_DISPLAY_OPTION_UP:
	case PLANE_DISPLAY_OPTION_DOWN:
	case PLANE_DISPLAY_OPTION_PREVIEW:
	case PLANE_DISPLAY_OPTION_TO_1:
	case PLANE_DISPLAY_OPTION_TO_2:
	case PLANE_DISPLAY_OPTION_TO_3:
	case PLANE_DISPLAY_OPTION_TO_4:
	case PLANE_DISPLAY_OPTION_TO_5:
	case PLANE_DISPLAY_OPTION_TO_6:
	case PLANE_DISPLAY_OPTION_TO_7:
	case PLANE_DISPLAY_OPTION_TO_8:
	case PLANE_DISPLAY_OPTION_TO_9:
	case PLANE_DISPLAY_OPTION_TO_10:
	case PLANE_DISPLAY_OPTION_TO_11:
	case PLANE_DISPLAY_OPTION_TO_12:
		if (setDisplayAction(display, o, value))
			return TRUE;
		break;
	default:
		break;
	}

	return FALSE;
}

static CompScreen *get_screen(CompDisplay * d, CompOption * option,
							  int n_options)
{
	XID root_window = getIntOptionNamed(option, n_options, "root", 0);

	return findScreenAtDisplay(d, root_window);
}

static Bool
planeLeft(CompDisplay * d,
		  CompAction * action,
		  CompActionState state, CompOption * option, int n_options)
{
	CompScreen *screen = get_screen(d, option, n_options);

	move_viewport(screen, -1, 0);
	return FALSE;
}

static Bool
planeRight(CompDisplay * d,
		   CompAction * action,
		   CompActionState state, CompOption * option, int n_options)
{
	CompScreen *screen = get_screen(d, option, n_options);

	move_viewport(screen, 1, 0);
	return FALSE;
}

static Bool
planeUp(CompDisplay * d,
		CompAction * action,
		CompActionState state, CompOption * option, int n_options)
{
	CompScreen *screen = get_screen(d, option, n_options);

	move_viewport(screen, 0, -1);
	return FALSE;
}

static Bool
planeDown(CompDisplay * d,
		  CompAction * action,
		  CompActionState state, CompOption * option, int n_options)
{
	CompScreen *screen = get_screen(d, option, n_options);

	move_viewport(screen, 0, 1);
	return FALSE;
}

static Bool
planePreview(CompDisplay * d, CompAction * action, CompActionState state,
			 CompOption * option, int n_options)
{
	static int xView;
	static int yView;

	CompScreen *screen = get_screen(d, option, n_options);

	PLANE_SCREEN(screen);
	if (!ps->preview)
	{
		xView = screen->x / screen->width;
		yView = screen->y / screen->height;
	}
	ps->preview = !ps->preview;
	/*if (!ps->preview){
	   moveScreenViewport(screen,xView,yView,TRUE);
	   } */
	damageScreen(screen);

	return FALSE;
}

static Bool
planeTo(CompDisplay * d,
		CompAction * action,
		CompActionState state, CompOption * option, int n_options)
{
	int i, new_x, new_y, cur_x, cur_y;
	CompScreen *screen = get_screen(d, option, n_options);

	PLANE_DISPLAY(d);

	new_x = new_y = -1;
	for (i = PLANE_DISPLAY_OPTION_TO_1; i <= PLANE_DISPLAY_OPTION_TO_12; ++i)
	{
		if (action == &pd->opt[i].value.action)
		{
			int viewport_no = i - PLANE_DISPLAY_OPTION_TO_1;

			new_x = viewport_no % screen->hsize;
			new_y = viewport_no / screen->hsize;

			break;
		}
	}

	if (new_x == -1 || new_y == -1)
		return FALSE;

	cur_x = screen->x;
	cur_y = screen->y;

	move_viewport(screen, new_x - cur_x, new_y - cur_y);

	return FALSE;
}

static void planeDisplayInitOptions(PlaneDisplay * pd)
{
	CompOption *o;
	char *str;

	o = &pd->opt[PLANE_DISPLAY_OPTION_LEFT];
	o->advanced = False;
	o->name = "plane_left";
	o->group = N_("Bindings");
	o->subGroup = N_("Navigation - Left");
	o->displayHints = "";
	o->shortDesc = N_("Plane Left");
	o->longDesc = N_("Plane Left.");
	o->type = CompOptionTypeAction;
	o->value.action.initiate = planeLeft;
	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 = PLANE_LEFT_MODIFIERS_DEFAULT;
	o->value.action.key.keysym = XStringToKeysym(PLANE_LEFT_KEY_DEFAULT);

	o = &pd->opt[PLANE_DISPLAY_OPTION_RIGHT];
	o->advanced = False;
	o->name = "plane_right";
	o->group = N_("Bindings");
	o->subGroup = N_("Navigation - Right");
	o->displayHints = "";
	o->shortDesc = N_("Plane Right");
	o->longDesc = N_("Plane Right.");
	o->type = CompOptionTypeAction;
	o->value.action.initiate = planeRight;
	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 = PLANE_RIGHT_MODIFIERS_DEFAULT;
	o->value.action.key.keysym = XStringToKeysym(PLANE_RIGHT_KEY_DEFAULT);

	o = &pd->opt[PLANE_DISPLAY_OPTION_DOWN];
	o->advanced = False;
	o->name = "plane_down";
	o->group = N_("Bindings");
	o->subGroup = N_("Navigation - Down");
	o->displayHints = "";
	o->shortDesc = N_("Plane Down");
	o->longDesc = N_("Plane Down.");
	o->type = CompOptionTypeAction;
	o->value.action.initiate = planeDown;
	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 = PLANE_DOWN_MODIFIERS_DEFAULT;
	o->value.action.key.keysym = XStringToKeysym(PLANE_DOWN_KEY_DEFAULT);

	o = &pd->opt[PLANE_DISPLAY_OPTION_PREVIEW];
	o->advanced = False;
	o->name = "plane_preview";
	o->group = N_("Bindings");
	o->subGroup = N_("Plane preview");
	o->displayHints = "";
	o->shortDesc = N_("Toggle Plane Preview");
	o->longDesc = N_("Toggle Plane Preview mode.");
	o->type = CompOptionTypeAction;
	o->value.action.initiate = planePreview;
	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 = PLANE_PREVIEW_MODIFIERS_DEFAULT;
	o->value.action.key.keysym = XStringToKeysym(PLANE_PREVIEW_KEY_DEFAULT);

	o = &pd->opt[PLANE_DISPLAY_OPTION_UP];
	o->advanced = False;
	o->name = "plane_up";
	o->group = N_("Bindings");
	o->subGroup = N_("Navigation - Up");
	o->displayHints = "";
	o->shortDesc = N_("Plane Up");
	o->longDesc = N_("Plane Up.");
	o->type = CompOptionTypeAction;
	o->value.action.initiate = planeUp;
	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 = PLANE_UP_MODIFIERS_DEFAULT;
	o->value.action.key.keysym = XStringToKeysym(PLANE_UP_KEY_DEFAULT);

#define PLANE_TO_SHORT        N_("Plane To Face %d")
#define PLANE_TO_LONG         N_("Plane to face %d")
#define PLANE_TO_WINDOW_SHORT N_("Plane To Face %d with Window")
#define PLANE_TO_WINDOW_LONG  N_("Plane to face %d and bring active " \
        "window along")

#define PLANE_TO_OPTION(n)						 \
    o = &pd->opt[PLANE_DISPLAY_OPTION_TO_ ## n];			 \
    o->advanced=False;\
o->name			  = "plane_to_" #n;			 \
    asprintf (&str, PLANE_TO_SHORT, n);				 \
    o->group=N_("Bindings");\
o->subGroup=N_("Face shortcuts");\
o->displayHints="";\
o->shortDesc		  = str;				 \
    asprintf (&str, PLANE_TO_LONG, n);					 \
    o->longDesc			  = str;				 \
    o->type			  = CompOptionTypeAction;		 \
    o->value.action.initiate	  = planeTo;				 \
    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;

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

static CompOption *planeGetDisplayOptions(CompDisplay * display, int *count)
{
	if (display)
	{
		PLANE_DISPLAY(display);

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

		planeDisplayInitOptions(pd);
		*count = NUM_OPTIONS(pd);
		return pd->opt;
	}
}


static Bool planeInitDisplay(CompPlugin * p, CompDisplay * d)
{
	PlaneDisplay *pd;

	pd = malloc(sizeof(PlaneDisplay));
	if (!pd)
		return FALSE;

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

	planeDisplayInitOptions(pd);

	WRAP(pd, d, handleEvent, planeHandleEvent);

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

	return TRUE;
}

static void planeFiniDisplay(CompPlugin * p, CompDisplay * d)
{
	PLANE_DISPLAY(d);

	freeScreenPrivateIndex(d, pd->screenPrivateIndex);

	UNWRAP(pd, d, handleEvent);

	free(pd);
}

static Bool planeInitScreen(CompPlugin * p, CompScreen * s)
{
	PlaneScreen *ps;

	PLANE_DISPLAY(s->display);

	ps = malloc(sizeof(PlaneScreen));
	if (!ps)
		return FALSE;
	ps->preview = 0;
	ps->timeout_handle = 0;

	planeScreenInitOptions(ps);

	addScreenAction(s, &pd->opt[PLANE_DISPLAY_OPTION_LEFT].value.action);
	addScreenAction(s, &pd->opt[PLANE_DISPLAY_OPTION_RIGHT].value.action);
	addScreenAction(s, &pd->opt[PLANE_DISPLAY_OPTION_DOWN].value.action);
	addScreenAction(s, &pd->opt[PLANE_DISPLAY_OPTION_UP].value.action);
	addScreenAction(s, &pd->opt[PLANE_DISPLAY_OPTION_PREVIEW].value.action);
	addScreenAction(s, &pd->opt[PLANE_DISPLAY_OPTION_TO_1].value.action);
	addScreenAction(s, &pd->opt[PLANE_DISPLAY_OPTION_TO_2].value.action);
	addScreenAction(s, &pd->opt[PLANE_DISPLAY_OPTION_TO_3].value.action);
	addScreenAction(s, &pd->opt[PLANE_DISPLAY_OPTION_TO_4].value.action);
	addScreenAction(s, &pd->opt[PLANE_DISPLAY_OPTION_TO_5].value.action);
	addScreenAction(s, &pd->opt[PLANE_DISPLAY_OPTION_TO_6].value.action);
	addScreenAction(s, &pd->opt[PLANE_DISPLAY_OPTION_TO_7].value.action);
	addScreenAction(s, &pd->opt[PLANE_DISPLAY_OPTION_TO_8].value.action);
	addScreenAction(s, &pd->opt[PLANE_DISPLAY_OPTION_TO_9].value.action);
	addScreenAction(s, &pd->opt[PLANE_DISPLAY_OPTION_TO_10].value.action);
	addScreenAction(s, &pd->opt[PLANE_DISPLAY_OPTION_TO_11].value.action);
	addScreenAction(s, &pd->opt[PLANE_DISPLAY_OPTION_TO_12].value.action);

	WRAP(ps, s, paintTransformedScreen, planePaintTransformedScreen);
	WRAP(ps, s, preparePaintScreen, planePreparePaintScreen);
	WRAP(ps, s, donePaintScreen, planeDonePaintScreen);
	WRAP(ps, s, paintScreen, planePaintScreen);
	WRAP(ps, s, setScreenOptionForPlugin, planeSetScreenOptionForPlugin);
	WRAP(ps, s, windowGrabNotify, planeWindowGrabNotify);
	WRAP(ps, s, windowUngrabNotify, planeWindowUngrabNotify);

	s->privates[pd->screenPrivateIndex].ptr = ps;

	return TRUE;
}

static void planeFiniScreen(CompPlugin * p, CompScreen * s)
{
	PLANE_SCREEN(s);
	PLANE_DISPLAY(s->display);

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

	removeScreenAction(s, &pd->opt[PLANE_DISPLAY_OPTION_LEFT].value.action);
	removeScreenAction(s, &pd->opt[PLANE_DISPLAY_OPTION_RIGHT].value.action);
	removeScreenAction(s, &pd->opt[PLANE_DISPLAY_OPTION_DOWN].value.action);
	removeScreenAction(s, &pd->opt[PLANE_DISPLAY_OPTION_UP].value.action);
	removeScreenAction(s,
					   &pd->opt[PLANE_DISPLAY_OPTION_PREVIEW].value.action);
	removeScreenAction(s, &pd->opt[PLANE_DISPLAY_OPTION_TO_1].value.action);
	removeScreenAction(s, &pd->opt[PLANE_DISPLAY_OPTION_TO_2].value.action);
	removeScreenAction(s, &pd->opt[PLANE_DISPLAY_OPTION_TO_3].value.action);
	removeScreenAction(s, &pd->opt[PLANE_DISPLAY_OPTION_TO_4].value.action);
	removeScreenAction(s, &pd->opt[PLANE_DISPLAY_OPTION_TO_5].value.action);
	removeScreenAction(s, &pd->opt[PLANE_DISPLAY_OPTION_TO_6].value.action);
	removeScreenAction(s, &pd->opt[PLANE_DISPLAY_OPTION_TO_7].value.action);
	removeScreenAction(s, &pd->opt[PLANE_DISPLAY_OPTION_TO_8].value.action);
	removeScreenAction(s, &pd->opt[PLANE_DISPLAY_OPTION_TO_9].value.action);
	removeScreenAction(s, &pd->opt[PLANE_DISPLAY_OPTION_TO_10].value.action);
	removeScreenAction(s, &pd->opt[PLANE_DISPLAY_OPTION_TO_11].value.action);
	removeScreenAction(s, &pd->opt[PLANE_DISPLAY_OPTION_TO_12].value.action);

	free(ps);
}

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

	return TRUE;
}

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

CompPluginFeature planeFeatures[] = {
	{"largedesktop"}
};

CompPluginVTable planeVTable = {
	"plane",
	N_("Desktop Plane"),
	N_("Place windows on a plane"),
	planeInit,					/* planeInit, */
	planeFini,					/* planeFini, */
	planeInitDisplay,
	planeFiniDisplay,
	planeInitScreen,
	planeFiniScreen,
	0,							/* InitWindow */
	0,							/* FiniWindow */
	planeGetDisplayOptions,
	planeSetDisplayOption,
	planeGetScreenOptions,
	planeSetScreenOption,
	0,
	0,
	planeFeatures,
	sizeof(planeFeatures) / sizeof(planeFeatures[0]),
	BERYL_ABI_INFO,
	"beryl-plugins-unsupported",
	"desktop",
	0,
	0,
	False,
};

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


syntax highlighted by Code2HTML, v. 0.9.1