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

/*
 * Spring model implemented by Kristian Hogsberg.
 */

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

#include <beryl.h>

#define WIN_X(w) ((w)->attrib.x - (w)->output.left)
#define WIN_Y(w) ((w)->attrib.y - (w)->output.top)
#define WIN_W(w) ((w)->width + (w)->output.left + (w)->output.right)
#define WIN_H(w) ((w)->height + (w)->output.top + (w)->output.bottom)

#define GRID_WIDTH  4
#define GRID_HEIGHT 4

#define MODEL_MAX_SPRINGS (GRID_WIDTH * GRID_HEIGHT * 2)

#define MASS 15.0f

typedef struct _xy_pair
{
	float x, y;
} Point, Vector;

#define NorthEdgeMask (1L << 0)
#define SouthEdgeMask (1L << 1)
#define WestEdgeMask  (1L << 2)
#define EastEdgeMask  (1L << 3)

#define WOBBLY_EDGE_DISTANCE_DEFAULT 25.0f
#define WOBBLY_EDGE_DISTANCE_MIN 1.0f
#define WOBBLY_EDGE_DISTANCE_MAX 50.0f
#define WOBBLY_EDGE_DISTANCE_PRECISION 0.5f

#define WOBBLY_EDGE_VELOCITY_DEFAULT 13.0f
#define WOBBLY_EDGE_VELOCITY_MIN 1.0f
#define WOBBLY_EDGE_VELOCITY_MAX 50.0f
#define WOBBLY_EDGE_VELOCITY_PRECISION 0.5f

typedef struct _Edge
{
	float next, prev;

	float start;
	float end;

	float attract;
	float velocity;

	Bool snapped;
} Edge;

typedef struct _Object
{
	Vector force;
	Point position;
	Vector velocity;
	float theta;
	Bool immobile;
	unsigned int edgeMask;
	Edge vertEdge;
	Edge horzEdge;
} Object;

typedef struct _Spring
{
	Object *a;
	Object *b;
	Vector offset;
} Spring;

#define NORTH 0
#define SOUTH 1
#define WEST  2
#define EAST  3

typedef struct _Model
{
	Object *objects;
	int numObjects;
	Spring springs[MODEL_MAX_SPRINGS];
	int numSprings;
	Object *anchorObject;
	float steps;
	Vector scale;
	Point scaleOrigin;
	Bool transformed;
	Point topLeft;
	Point bottomRight;
	unsigned int edgeMask;
	unsigned int snapCnt[4];
} Model;

#define WOBBLY_FRICTION_DEFAULT    4.0f
#define WOBBLY_FRICTION_MIN        0.1f
#define WOBBLY_FRICTION_MAX       10.0f
#define WOBBLY_FRICTION_PRECISION  0.1f

#define WOBBLY_SPRING_K_DEFAULT    10.0f
#define WOBBLY_SPRING_K_MIN        0.1f
#define WOBBLY_SPRING_K_MAX       10.0f
#define WOBBLY_SPRING_K_PRECISION  0.1f

#define WOBBLY_GRID_RESOLUTION_DEFAULT  48
#define WOBBLY_GRID_RESOLUTION_MIN      1
#define WOBBLY_GRID_RESOLUTION_MAX      64

#define WOBBLY_MIN_GRID_SIZE_DEFAULT  4
#define WOBBLY_MIN_GRID_SIZE_MIN      4
#define WOBBLY_MIN_GRID_SIZE_MAX      128

#define WOBBLY_MAXIMIZE_FRICTION_DEFAULT 4.0f
#define WOBBLY_MAXIMIZE_SPRING_K_DEFAULT 10.0f
#define WOBBLY_MAP_FRICTION_DEFAULT      4.0f
#define WOBBLY_MAP_SPRING_K_DEFAULT      10.0f

#define WOBBLY_URGENT_VELOCITY_DEFAULT   1.0f

#define WOBBLY_RELEASE_DEFAULT          FALSE
#define WOBBLY_DEFAULT_SNAP_DEFAULT     FALSE
#define WOBBLY_EDGE_ATTRACTION_DEFAULT  FALSE
#define WOBBLY_URGENT_EFFECT_DEFAULT    FALSE
#define WOBBLY_URGENT_IN_DEFAULT        FALSE
#define WOBBLY_URGENT_UNIFORM_DEFAULT   TRUE

typedef enum
{
	WobblyEffectNone = 0,
	WobblyEffectShiver
} WobblyEffect;

static char *effectName[] = {
	N_("None"),
	N_("Shiver")
};

static WobblyEffect effectType[] = {
	WobblyEffectNone,
	WobblyEffectShiver
};

#define NUM_EFFECT (sizeof (effectType) / sizeof (effectType[0]))

#define WOBBLY_MAP_DEFAULT   (effectName[1])
#define WOBBLY_FOCUS_DEFAULT (effectName[0])

static char *mapWinType[] = {
	N_("Splash"),
	N_("Notification")
};

#define N_MAP_WIN_TYPE (sizeof (mapWinType) / sizeof (mapWinType[0]))
#define N_FOCUS_WIN_TYPE (0)

static char *moveWinType[] = {
	N_("Unknown"),
	N_("Splash"),
	N_("Utility"),
	N_("Dialog"),
	N_("ModalDialog"),
	N_("Normal")
};

#define nMods 4
static char *Mods[] = {
	N_("Shift"),
	N_("Alt"),
	N_("Control"),
	N_("Meta")
};
static int ModMask[] = {
	ShiftMask,
	CompAltMask,
	ControlMask,
	CompMetaMask,
};

#define N_MOVE_WIN_TYPE (sizeof (moveWinType) / sizeof (moveWinType[0]))
#define N_GRAB_WIN_TYPE (0)

#define WOBBLY_SNAP_MODIFIERS_DEFAULT ShiftMask

#define WOBBLY_MAXIMIZE_EFFECT_DEFAULT    TRUE
#define WOBBLY_MOVE_EFFECT_DEFAULT TRUE

static int displayPrivateIndex;

#define WOBBLY_DISPLAY_OPTION_SNAP   0
#define WOBBLY_DISPLAY_OPTION_SHIVER 1
#define WOBBLY_DISPLAY_OPTION_DEFAULT_SNAP      2
#define WOBBLY_DISPLAY_OPTION_NUM    3

typedef struct _WobblyDisplay
{
	int screenPrivateIndex;
	Atom wmHintsAtom;
	HandleEventProc handleEvent;

	CompOption opt[WOBBLY_DISPLAY_OPTION_NUM];

	int snapMask;

	Bool snapping;
} WobblyDisplay;

#define WOBBLY_SCREEN_OPTION_MOVE_FRICTION        0
#define WOBBLY_SCREEN_OPTION_MOVE_SPRING_K        1
#define WOBBLY_SCREEN_OPTION_GRID_RESOLUTION      2
#define WOBBLY_SCREEN_OPTION_MIN_GRID_SIZE        3
#define WOBBLY_SCREEN_OPTION_MAP_EFFECT           4
#define WOBBLY_SCREEN_OPTION_FOCUS_EFFECT         5
#define WOBBLY_SCREEN_OPTION_MAP_WINDOW_TYPE      6
#define WOBBLY_SCREEN_OPTION_FOCUS_WINDOW_TYPE    7
#define WOBBLY_SCREEN_OPTION_GRAB_WINDOW_TYPE     8
#define WOBBLY_SCREEN_OPTION_MOVE_WINDOW_TYPE     9
#define WOBBLY_SCREEN_OPTION_MAXIMIZE_EFFECT      10
#define WOBBLY_SCREEN_OPTION_MOVE_EFFECT          11
#define WOBBLY_SCREEN_OPTION_MAP_FRICTION         12
#define WOBBLY_SCREEN_OPTION_MAP_SPRING_K         13
#define WOBBLY_SCREEN_OPTION_GRAB_FRICTION        14
#define WOBBLY_SCREEN_OPTION_GRAB_SPRING_K        15
#define WOBBLY_SCREEN_OPTION_FOCUS_FRICTION       16
#define WOBBLY_SCREEN_OPTION_FOCUS_SPRING_K       17
#define WOBBLY_SCREEN_OPTION_MAXIMIZE_FRICTION    18
#define WOBBLY_SCREEN_OPTION_MAXIMIZE_SPRING_K    19
#define WOBBLY_SCREEN_OPTION_BELL_FRICTION        20
#define WOBBLY_SCREEN_OPTION_BELL_SPRING_K        21
#define WOBBLY_SCREEN_OPTION_RELEASE_FRICTION     22
#define WOBBLY_SCREEN_OPTION_RELEASE_SPRING_K     23
#define WOBBLY_SCREEN_OPTION_USE_RELEASE          24
#define WOBBLY_SCREEN_OPTION_URGENT_EFFECT        25
#define WOBBLY_SCREEN_OPTION_URGENT_FRICTION      26
#define WOBBLY_SCREEN_OPTION_URGENT_SPRING_K      27
#define WOBBLY_SCREEN_OPTION_URGENT_VELOCITY      28
#define WOBBLY_SCREEN_OPTION_URGENT_IN            29
#define WOBBLY_SCREEN_OPTION_URGENT_UNIFORM       30
#define WOBBLY_SCREEN_OPTION_EDGE_DISTANCE        31
#define WOBBLY_SCREEN_OPTION_EDGE_VELOCITY        32
#define WOBBLY_SCREEN_OPTION_EDGE_ATTRACTION      33
#define WOBBLY_SCREEN_OPTION_NUM                  34

typedef struct _WobblyScreen
{
	int windowPrivateIndex;

	PreparePaintScreenProc preparePaintScreen;
	DonePaintScreenProc donePaintScreen;
	PaintScreenProc paintScreen;
	PaintWindowProc paintWindow;
	DamageWindowRectProc damageWindowRect;
	AddWindowGeometryProc addWindowGeometry;
	DrawWindowGeometryProc drawWindowGeometry;
	DrawWindowTextureProc drawWindowTexture;

	WindowResizeNotifyProc windowResizeNotify;
	WindowMoveNotifyProc windowMoveNotify;
	WindowGrabNotifyProc windowGrabNotify;
	WindowUngrabNotifyProc windowUngrabNotify;

	CompOption opt[WOBBLY_SCREEN_OPTION_NUM];

	Bool wobblyWindows;

	WobblyEffect mapEffect;
	WobblyEffect focusEffect;

	unsigned int mapWMask;
	unsigned int focusWMask;
	unsigned int moveWMask;
	unsigned int grabWMask;

	unsigned int grabMask;
	CompWindow *grabWindow;

	float edgeDistance;
	float edgeVelocity;
} WobblyScreen;

#define WobblyInitial  (1L << 0)
#define WobblyForce    (1L << 1)
#define WobblyVelocity (1L << 2)

typedef struct _WobblyWindow
{
	Model *model;
	int wobbly;
	Bool grabbed;
	Bool velocity;
	unsigned int state;
	float friction;
	float spring_k;
} WobblyWindow;

#define GET_WOBBLY_DISPLAY(d)                                       \
    ((WobblyDisplay *) (d)->privates[displayPrivateIndex].ptr)

#define WOBBLY_DISPLAY(d)                       \
    WobblyDisplay *wd = GET_WOBBLY_DISPLAY (d)

#define GET_WOBBLY_SCREEN(s, wd)                                   \
    ((WobblyScreen *) (s)->privates[(wd)->screenPrivateIndex].ptr)

#define WOBBLY_SCREEN(s)                                                      \
    WobblyScreen *ws = GET_WOBBLY_SCREEN (s, GET_WOBBLY_DISPLAY (s->display))

#define GET_WOBBLY_WINDOW(w, ws)                                   \
    ((WobblyWindow *) (w)->privates[(ws)->windowPrivateIndex].ptr)

#define WOBBLY_WINDOW(w)                                         \
    WobblyWindow *ww = GET_WOBBLY_WINDOW  (w,                         \
            GET_WOBBLY_SCREEN  (w->screen,                 \
                GET_WOBBLY_DISPLAY (w->screen->display)))

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

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

	WOBBLY_SCREEN(screen);

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

	switch (index)
	{
		/*
		   case WOBBLY_SCREEN_OPTION_FRICTION:
		   case WOBBLY_SCREEN_OPTION_SPRING_K:
		 */
	case WOBBLY_SCREEN_OPTION_MOVE_FRICTION:
	case WOBBLY_SCREEN_OPTION_MOVE_SPRING_K:
	case WOBBLY_SCREEN_OPTION_MAP_FRICTION:
	case WOBBLY_SCREEN_OPTION_MAP_SPRING_K:
	case WOBBLY_SCREEN_OPTION_GRAB_FRICTION:
	case WOBBLY_SCREEN_OPTION_GRAB_SPRING_K:
	case WOBBLY_SCREEN_OPTION_FOCUS_FRICTION:
	case WOBBLY_SCREEN_OPTION_FOCUS_SPRING_K:
	case WOBBLY_SCREEN_OPTION_MAXIMIZE_FRICTION:
	case WOBBLY_SCREEN_OPTION_MAXIMIZE_SPRING_K:
	case WOBBLY_SCREEN_OPTION_BELL_FRICTION:
	case WOBBLY_SCREEN_OPTION_BELL_SPRING_K:
	case WOBBLY_SCREEN_OPTION_RELEASE_FRICTION:
	case WOBBLY_SCREEN_OPTION_RELEASE_SPRING_K:
	case WOBBLY_SCREEN_OPTION_URGENT_FRICTION:
	case WOBBLY_SCREEN_OPTION_URGENT_SPRING_K:
	case WOBBLY_SCREEN_OPTION_URGENT_VELOCITY:
		if (compSetFloatOption(o, value))
			return TRUE;
		break;
	case WOBBLY_SCREEN_OPTION_GRID_RESOLUTION:
		if (compSetIntOption(o, value))
			return TRUE;
		break;
	case WOBBLY_SCREEN_OPTION_MIN_GRID_SIZE:
		if (compSetIntOption(o, value))
			return TRUE;
		break;
	case WOBBLY_SCREEN_OPTION_MAP_EFFECT:
		if (compSetStringOption(o, value))
		{
			int i;

			for (i = 0; i < NUM_EFFECT; i++)
			{
				if (strcmp(o->value.s, effectName[i]) == 0)
				{
					ws->mapEffect = effectType[i];
					return TRUE;
				}
			}
		}
		break;
	case WOBBLY_SCREEN_OPTION_FOCUS_EFFECT:
		if (compSetStringOption(o, value))
		{
			int i;

			for (i = 0; i < NUM_EFFECT; i++)
			{
				if (strcmp(o->value.s, effectName[i]) == 0)
				{
					ws->focusEffect = effectType[i];
					return TRUE;
				}
			}
		}
		break;
	case WOBBLY_SCREEN_OPTION_MAP_WINDOW_TYPE:
		if (compSetOptionList(o, value))
		{
			ws->mapWMask = compWindowTypeMaskFromStringList(&o->value);
			return TRUE;
		}
		break;
	case WOBBLY_SCREEN_OPTION_FOCUS_WINDOW_TYPE:
		if (compSetOptionList(o, value))
		{
			ws->focusWMask = compWindowTypeMaskFromStringList(&o->value);
			return TRUE;
		}
		break;
	case WOBBLY_SCREEN_OPTION_MOVE_WINDOW_TYPE:
		if (compSetOptionList(o, value))
		{
			ws->moveWMask = compWindowTypeMaskFromStringList(&o->value);
			return TRUE;
		}
		break;
	case WOBBLY_SCREEN_OPTION_GRAB_WINDOW_TYPE:
		if (compSetOptionList(o, value))
		{
			ws->grabWMask = compWindowTypeMaskFromStringList(&o->value);
			return TRUE;
		}
		break;
	case WOBBLY_SCREEN_OPTION_MAXIMIZE_EFFECT:
	case WOBBLY_SCREEN_OPTION_MOVE_EFFECT:
	case WOBBLY_SCREEN_OPTION_USE_RELEASE:
	case WOBBLY_SCREEN_OPTION_URGENT_EFFECT:
	case WOBBLY_SCREEN_OPTION_URGENT_IN:
	case WOBBLY_SCREEN_OPTION_URGENT_UNIFORM:
	case WOBBLY_SCREEN_OPTION_EDGE_ATTRACTION:
		if (compSetBoolOption(o, value))
			return TRUE;
		break;
	case WOBBLY_SCREEN_OPTION_EDGE_DISTANCE:
		if (compSetFloatOption(o, value))
		{
			ws->edgeDistance = o->value.f;
			return TRUE;
		}
		break;
	case WOBBLY_SCREEN_OPTION_EDGE_VELOCITY:
		if (compSetFloatOption(o, value))
		{
			ws->edgeVelocity = o->value.f;
			return TRUE;
		}
		break;
	default:
		break;
	}

	return FALSE;
}

static void wobblyScreenInitOptions(WobblyScreen * ws)
{
	CompOption *o;
	int i;

	o = &ws->opt[WOBBLY_SCREEN_OPTION_MAXIMIZE_FRICTION];
	o->advanced = False;
	o->name = "maximize_friction";
	o->group = N_("Advanced");
	o->subGroup = N_("Maximize");
	o->displayHints = "";
	o->shortDesc = N_("Maximize Friction");
	o->longDesc = N_("Spring Friction for Maximize effect.");
	o->type = CompOptionTypeFloat;
	o->value.f = WOBBLY_MAXIMIZE_FRICTION_DEFAULT;
	o->rest.f.min = WOBBLY_FRICTION_MIN;
	o->rest.f.max = WOBBLY_FRICTION_MAX;
	o->rest.f.precision = WOBBLY_FRICTION_PRECISION;

	o = &ws->opt[WOBBLY_SCREEN_OPTION_MAXIMIZE_SPRING_K];
	o->advanced = False;
	o->name = "maximize_spring_k";
	o->group = N_("Advanced");
	o->subGroup = N_("Maximize");
	o->displayHints = "";
	o->shortDesc = N_("Maximize Spring K");
	o->longDesc = N_("Spring Konstant for Maximize effect.");
	o->type = CompOptionTypeFloat;
	o->value.f = WOBBLY_MAXIMIZE_SPRING_K_DEFAULT;
	o->rest.f.min = WOBBLY_SPRING_K_MIN;
	o->rest.f.max = WOBBLY_SPRING_K_MAX;
	o->rest.f.precision = WOBBLY_SPRING_K_PRECISION;

	o = &ws->opt[WOBBLY_SCREEN_OPTION_MOVE_FRICTION];
	o->advanced = False;
	o->name = "move_friction";
	o->group = N_("Advanced");
	o->subGroup = N_("Move");
	o->displayHints = "";
	o->shortDesc = N_("Move Friction");
	o->longDesc = N_("Spring Friction for Move types.");
	o->type = CompOptionTypeFloat;
	o->value.f = WOBBLY_FRICTION_DEFAULT;
	o->rest.f.min = WOBBLY_FRICTION_MIN;
	o->rest.f.max = WOBBLY_FRICTION_MAX;
	o->rest.f.precision = WOBBLY_FRICTION_PRECISION;

	o = &ws->opt[WOBBLY_SCREEN_OPTION_MOVE_SPRING_K];
	o->advanced = False;
	o->name = "move_spring_k";
	o->group = N_("Advanced");
	o->subGroup = N_("Move");
	o->displayHints = "";
	o->shortDesc = N_("Move Spring K");
	o->longDesc = N_("Spring Konstant for Move types.");
	o->type = CompOptionTypeFloat;
	o->value.f = WOBBLY_SPRING_K_DEFAULT;
	o->rest.f.min = WOBBLY_SPRING_K_MIN;
	o->rest.f.max = WOBBLY_SPRING_K_MAX;
	o->rest.f.precision = WOBBLY_SPRING_K_PRECISION;

	o = &ws->opt[WOBBLY_SCREEN_OPTION_MAP_FRICTION];
	o->advanced = False;
	o->name = "map_friction";
	o->group = N_("Advanced");
	o->subGroup = N_("Map");
	o->displayHints = "";
	o->shortDesc = N_("Map Friction");
	o->longDesc = N_("Spring Friction for Map types.");
	o->type = CompOptionTypeFloat;
	o->value.f = WOBBLY_MAP_FRICTION_DEFAULT;
	o->rest.f.min = WOBBLY_FRICTION_MIN;
	o->rest.f.max = WOBBLY_FRICTION_MAX;
	o->rest.f.precision = WOBBLY_FRICTION_PRECISION;

	o = &ws->opt[WOBBLY_SCREEN_OPTION_MAP_SPRING_K];
	o->advanced = False;
	o->name = "map_spring_k";
	o->group = N_("Advanced");
	o->subGroup = N_("Map");
	o->displayHints = "";
	o->shortDesc = N_("Map Spring K");
	o->longDesc = N_("Spring Konstant for Map types.");
	o->type = CompOptionTypeFloat;
	o->value.f = WOBBLY_MAP_SPRING_K_DEFAULT;
	o->rest.f.min = WOBBLY_SPRING_K_MIN;
	o->rest.f.max = WOBBLY_SPRING_K_MAX;
	o->rest.f.precision = WOBBLY_SPRING_K_PRECISION;

	o = &ws->opt[WOBBLY_SCREEN_OPTION_GRAB_FRICTION];
	o->advanced = False;
	o->name = "grab_friction";
	o->group = N_("Advanced");
	o->subGroup = N_("Grab");
	o->displayHints = "";
	o->shortDesc = N_("Grab Friction");
	o->longDesc = N_("Spring Friction for Grab types.");
	o->type = CompOptionTypeFloat;
	o->value.f = WOBBLY_FRICTION_DEFAULT;
	o->rest.f.min = WOBBLY_FRICTION_MIN;
	o->rest.f.max = WOBBLY_FRICTION_MAX;
	o->rest.f.precision = WOBBLY_FRICTION_PRECISION;

	o = &ws->opt[WOBBLY_SCREEN_OPTION_GRAB_SPRING_K];
	o->advanced = False;
	o->name = "grab_spring_k";
	o->group = N_("Advanced");
	o->subGroup = N_("Grab");
	o->displayHints = "";
	o->shortDesc = N_("Grab Spring K");
	o->longDesc = N_("Spring Konstant for Grab types.");
	o->type = CompOptionTypeFloat;
	o->value.f = WOBBLY_SPRING_K_DEFAULT;
	o->rest.f.min = WOBBLY_SPRING_K_MIN;
	o->rest.f.max = WOBBLY_SPRING_K_MAX;
	o->rest.f.precision = WOBBLY_SPRING_K_PRECISION;

	o = &ws->opt[WOBBLY_SCREEN_OPTION_FOCUS_FRICTION];
	o->advanced = False;
	o->name = "focus_friction";
	o->group = N_("Advanced");
	o->subGroup = N_("Focus");
	o->displayHints = "";
	o->shortDesc = N_("Focus Friction");
	o->longDesc = N_("Spring Friction for Focus types.");
	o->type = CompOptionTypeFloat;
	o->value.f = WOBBLY_FRICTION_DEFAULT;
	o->rest.f.min = WOBBLY_FRICTION_MIN;
	o->rest.f.max = WOBBLY_FRICTION_MAX;
	o->rest.f.precision = WOBBLY_FRICTION_PRECISION;

	o = &ws->opt[WOBBLY_SCREEN_OPTION_FOCUS_SPRING_K];
	o->advanced = False;
	o->name = "focus_spring_k";
	o->group = N_("Advanced");
	o->subGroup = N_("Focus");
	o->displayHints = "";
	o->shortDesc = N_("Focus Spring K");
	o->longDesc = N_("Spring Konstant for Focus types.");
	o->type = CompOptionTypeFloat;
	o->value.f = WOBBLY_SPRING_K_DEFAULT;
	o->rest.f.min = WOBBLY_SPRING_K_MIN;
	o->rest.f.max = WOBBLY_SPRING_K_MAX;
	o->rest.f.precision = WOBBLY_SPRING_K_PRECISION;

	o = &ws->opt[WOBBLY_SCREEN_OPTION_GRID_RESOLUTION];
	o->advanced = False;
	o->name = "grid_resolution";
	o->group = N_("Advanced");
	o->subGroup = N_("Grid");
	o->displayHints = "";
	o->shortDesc = N_("Grid Resolution");
	o->longDesc = N_("Vertex Grid Resolution.");
	o->type = CompOptionTypeInt;
	o->value.i = WOBBLY_GRID_RESOLUTION_DEFAULT;
	o->rest.i.min = WOBBLY_GRID_RESOLUTION_MIN;
	o->rest.i.max = WOBBLY_GRID_RESOLUTION_MAX;

	o = &ws->opt[WOBBLY_SCREEN_OPTION_MIN_GRID_SIZE];
	o->advanced = False;
	o->name = "min_grid_size";
	o->group = N_("Advanced");
	o->subGroup = N_("Grid");
	o->displayHints = "";
	o->shortDesc = N_("Minimum Grid Size");
	o->longDesc = N_("Minimum Vertex Grid Size.");
	o->type = CompOptionTypeInt;
	o->value.i = WOBBLY_MIN_GRID_SIZE_DEFAULT;
	o->rest.i.min = WOBBLY_MIN_GRID_SIZE_MIN;
	o->rest.i.max = WOBBLY_MIN_GRID_SIZE_MAX;

	o = &ws->opt[WOBBLY_SCREEN_OPTION_MAP_EFFECT];
	o->advanced = False;
	o->name = "map_effect";
	o->group = N_("Advanced");
	o->subGroup = N_("Map");
	o->displayHints = "";
	o->shortDesc = N_("Map Effect");
	o->longDesc = N_("Map Window Effect.");
	o->type = CompOptionTypeString;
	o->value.s = strdup(WOBBLY_MAP_DEFAULT);
	o->rest.s.string = effectName;
	o->rest.s.nString = NUM_EFFECT;

	o = &ws->opt[WOBBLY_SCREEN_OPTION_FOCUS_EFFECT];
	o->advanced = False;
	o->name = "focus_effect";
	o->group = N_("Advanced");
	o->subGroup = N_("Focus");
	o->displayHints = "";
	o->shortDesc = N_("Focus Effect");
	o->longDesc = N_("Focus Window Effect.");
	o->type = CompOptionTypeString;
	o->value.s = strdup(WOBBLY_FOCUS_DEFAULT);
	o->rest.s.string = effectName;
	o->rest.s.nString = NUM_EFFECT;

	o = &ws->opt[WOBBLY_SCREEN_OPTION_MAP_WINDOW_TYPE];
	o->advanced = False;
	o->name = "map_window_types";
	o->group = N_("Advanced");
	o->subGroup = N_("Map");
	o->displayHints = "";
	o->shortDesc = N_("Map Window Types");
	o->longDesc = N_("Window Types that should Wobble when Mapped.");
	o->type = CompOptionTypeList;
	o->value.list.type = CompOptionTypeString;
	o->value.list.nValue = N_MAP_WIN_TYPE;
	o->value.list.value = malloc(sizeof(CompOptionValue) * N_MAP_WIN_TYPE);
	for (i = 0; i < N_MAP_WIN_TYPE; i++)
		o->value.list.value[i].s = strdup(mapWinType[i]);
	o->rest.s.string = (char **)windowTypeString;
	o->rest.s.nString = nWindowTypeString;

	ws->mapWMask = compWindowTypeMaskFromStringList(&o->value);

	o = &ws->opt[WOBBLY_SCREEN_OPTION_FOCUS_WINDOW_TYPE];
	o->advanced = False;
	o->name = "focus_window_types";
	o->group = N_("Advanced");
	o->subGroup = N_("Focus");
	o->displayHints = "";
	o->shortDesc = N_("Focus Window Types");
	o->longDesc = N_("Window Types that should Wobble when Focused.");
	o->type = CompOptionTypeList;
	o->value.list.type = CompOptionTypeString;
	o->value.list.nValue = N_FOCUS_WIN_TYPE;
	o->value.list.value = NULL;
	o->rest.s.string = (char **)windowTypeString;
	o->rest.s.nString = nWindowTypeString;

	ws->focusWMask = compWindowTypeMaskFromStringList(&o->value);

	o = &ws->opt[WOBBLY_SCREEN_OPTION_MOVE_WINDOW_TYPE];
	o->advanced = False;
	o->name = "move_window_types";
	o->group = N_("Advanced");
	o->subGroup = N_("Move");
	o->displayHints = "";
	o->shortDesc = N_("Move Window Types");
	o->longDesc = N_("Window Types that should Wobble when Moved.");
	o->type = CompOptionTypeList;
	o->value.list.type = CompOptionTypeString;
	o->value.list.nValue = N_MOVE_WIN_TYPE;
	o->value.list.value = malloc(sizeof(CompOptionValue) * N_MOVE_WIN_TYPE);
	for (i = 0; i < N_MOVE_WIN_TYPE; i++)
		o->value.list.value[i].s = strdup(moveWinType[i]);
	o->rest.s.string = (char **)windowTypeString;
	o->rest.s.nString = nWindowTypeString;

	ws->moveWMask = compWindowTypeMaskFromStringList(&o->value);

	o = &ws->opt[WOBBLY_SCREEN_OPTION_GRAB_WINDOW_TYPE];
	o->advanced = False;
	o->name = "grab_window_types";
	o->group = N_("Advanced");
	o->subGroup = N_("Grab");
	o->displayHints = "";
	o->shortDesc = N_("Grab Window Types");
	o->longDesc = N_("Window Types that should Wobble when Grabbed.");
	o->type = CompOptionTypeList;
	o->value.list.type = CompOptionTypeString;
	o->value.list.nValue = N_GRAB_WIN_TYPE;
	o->value.list.value = NULL;
	o->rest.s.string = (char **)windowTypeString;
	o->rest.s.nString = nWindowTypeString;

	ws->grabWMask = compWindowTypeMaskFromStringList(&o->value);

	o = &ws->opt[WOBBLY_SCREEN_OPTION_MAXIMIZE_EFFECT];
	o->advanced = False;
	o->name = "maximize_effect";
	o->group = N_("Advanced");
	o->subGroup = N_("Maximize");
	o->displayHints = "";
	o->shortDesc = N_("Maximize Effect");
	o->longDesc =
			N_("Wobble Effect when Maximizing and unMaximizing windows.");
	o->type = CompOptionTypeBool;
	o->value.b = WOBBLY_MAXIMIZE_EFFECT_DEFAULT;

	o = &ws->opt[WOBBLY_SCREEN_OPTION_MOVE_EFFECT];
	o->advanced = False;
	o->name = "move_effect";
	o->group = N_("Advanced");
	o->subGroup = N_("Move");
	o->displayHints = "";
	o->shortDesc = N_("Move Effect");
	o->longDesc = N_("Wobble windows when you Move them.");
	o->type = CompOptionTypeBool;
	o->value.b = WOBBLY_MOVE_EFFECT_DEFAULT;

	o = &ws->opt[WOBBLY_SCREEN_OPTION_BELL_FRICTION];
	o->advanced = False;
	o->name = "visual_bell_friction";
	o->group = N_("Advanced");
	o->subGroup = N_("Visual bell");
	o->displayHints = "";
	o->shortDesc = N_("Visual Bell Friction");
	o->longDesc = N_("Spring Friction for Bell effect.");
	o->type = CompOptionTypeFloat;
	o->value.f = WOBBLY_FRICTION_DEFAULT;
	o->rest.f.min = WOBBLY_FRICTION_MIN;
	o->rest.f.max = WOBBLY_FRICTION_MAX;
	o->rest.f.precision = WOBBLY_FRICTION_PRECISION;

	o = &ws->opt[WOBBLY_SCREEN_OPTION_BELL_SPRING_K];
	o->advanced = False;
	o->name = "visual_bell_spring_k";
	o->group = N_("Advanced");
	o->subGroup = N_("Visual bell");
	o->displayHints = "";
	o->shortDesc = N_("Visual Bell Spring K");
	o->longDesc = N_("Spring Konstant for Bell effect.");
	o->type = CompOptionTypeFloat;
	o->value.f = WOBBLY_SPRING_K_DEFAULT;
	o->rest.f.min = WOBBLY_SPRING_K_MIN;
	o->rest.f.max = WOBBLY_SPRING_K_MAX;
	o->rest.f.precision = WOBBLY_SPRING_K_PRECISION;

	o = &ws->opt[WOBBLY_SCREEN_OPTION_USE_RELEASE];
	o->advanced = False;
	o->name = "release_effect";
	o->group = N_("Advanced");
	o->subGroup = N_("Release");
	o->displayHints = "";
	o->shortDesc = N_("Release Effect");
	o->longDesc = N_("Use settings for Releasing windows.");
	o->type = CompOptionTypeBool;
	o->value.b = WOBBLY_RELEASE_DEFAULT;

	o = &ws->opt[WOBBLY_SCREEN_OPTION_RELEASE_FRICTION];
	o->advanced = False;
	o->name = "release_friction";
	o->group = N_("Advanced");
	o->subGroup = N_("Release");
	o->displayHints = "";
	o->shortDesc = N_("Release Friction");
	o->longDesc = N_("Spring Friction for Release effect.");
	o->type = CompOptionTypeFloat;
	o->value.f = WOBBLY_FRICTION_DEFAULT;
	o->rest.f.min = WOBBLY_FRICTION_MIN;
	o->rest.f.max = WOBBLY_FRICTION_MAX;
	o->rest.f.precision = WOBBLY_FRICTION_PRECISION;

	o = &ws->opt[WOBBLY_SCREEN_OPTION_RELEASE_SPRING_K];
	o->advanced = False;
	o->name = "release_spring_k";
	o->group = N_("Advanced");
	o->subGroup = N_("Release");
	o->displayHints = "";
	o->shortDesc = N_("Release Spring K");
	o->longDesc = N_("Spring Konstant for Release effect.");
	o->type = CompOptionTypeFloat;
	o->value.f = WOBBLY_SPRING_K_DEFAULT;
	o->rest.f.min = WOBBLY_SPRING_K_MIN;
	o->rest.f.max = WOBBLY_SPRING_K_MAX;
	o->rest.f.precision = WOBBLY_SPRING_K_PRECISION;


	o = &ws->opt[WOBBLY_SCREEN_OPTION_URGENT_EFFECT];
	o->advanced = False;
	o->name = "urgent_effect";
	o->group = N_("Advanced");
	o->subGroup = N_("Urgent");
	o->displayHints = "";
	o->shortDesc = N_("Use Urgent Effect");
	o->longDesc = N_("Make Urgent windows Wobble.");
	o->type = CompOptionTypeBool;
	o->value.b = WOBBLY_URGENT_EFFECT_DEFAULT;

	o = &ws->opt[WOBBLY_SCREEN_OPTION_URGENT_FRICTION];
	o->advanced = False;
	o->name = "urgent_friction";
	o->group = N_("Advanced");
	o->subGroup = N_("Urgent");
	o->displayHints = "";
	o->shortDesc = N_("Urgent Friction");
	o->longDesc = N_("Spring Friction for Urgent effect.");
	o->type = CompOptionTypeFloat;
	o->value.f = WOBBLY_FRICTION_DEFAULT;
	o->rest.f.min = WOBBLY_FRICTION_MIN;
	o->rest.f.max = WOBBLY_FRICTION_MAX;
	o->rest.f.precision = WOBBLY_FRICTION_PRECISION;

	o = &ws->opt[WOBBLY_SCREEN_OPTION_URGENT_SPRING_K];
	o->advanced = False;
	o->name = "urgent_spring_k";
	o->group = N_("Advanced");
	o->subGroup = N_("Urgent");
	o->displayHints = "";
	o->shortDesc = N_("Urgent Spring K");
	o->longDesc = N_("Spring Konstant for Urgent effect.");
	o->type = CompOptionTypeFloat;
	o->value.f = WOBBLY_SPRING_K_DEFAULT;
	o->rest.f.min = WOBBLY_SPRING_K_MIN;
	o->rest.f.max = WOBBLY_SPRING_K_MAX;
	o->rest.f.precision = WOBBLY_SPRING_K_PRECISION;

	o = &ws->opt[WOBBLY_SCREEN_OPTION_URGENT_VELOCITY];
	o->advanced = False;
	o->name = "urgent_velocity";
	o->group = N_("Advanced");
	o->subGroup = N_("Urgent");
	o->displayHints = "";
	o->shortDesc = N_("Urgent Velocity");
	o->longDesc = N_("Window velocity for Urgent effect.");
	o->type = CompOptionTypeFloat;
	o->value.f = WOBBLY_URGENT_VELOCITY_DEFAULT;
	o->rest.f.min = WOBBLY_SPRING_K_MIN;
	o->rest.f.max = WOBBLY_SPRING_K_MAX;
	o->rest.f.precision = WOBBLY_SPRING_K_PRECISION;

	o = &ws->opt[WOBBLY_SCREEN_OPTION_URGENT_IN];
	o->advanced = False;
	o->name = "urgent_effect_in";
	o->group = N_("Advanced");
	o->subGroup = N_("Urgent");
	o->displayHints = "";
	o->shortDesc = N_("Urgent Effect In");
	o->longDesc = N_("Make Urgent windows Wobble Inward.");
	o->type = CompOptionTypeBool;
	o->value.b = WOBBLY_URGENT_IN_DEFAULT;

	o = &ws->opt[WOBBLY_SCREEN_OPTION_URGENT_UNIFORM];
	o->advanced = False;
	o->name = "urgent_effect_uniform";
	o->group = N_("Advanced");
	o->subGroup = N_("Urgent");
	o->displayHints = "";
	o->shortDesc = N_("Urgent Effect Uniform");
	o->longDesc = N_("Make Urgent windows Wobble Uniformly.");
	o->type = CompOptionTypeBool;
	o->value.b = WOBBLY_URGENT_UNIFORM_DEFAULT;

	o = &ws->opt[WOBBLY_SCREEN_OPTION_EDGE_DISTANCE];
	o->advanced = False;
	o->name = "edge_distance";
	o->group = N_("Advanced");
	o->subGroup = N_("Edge");
	o->displayHints = "";
	o->shortDesc = N_("Edge Snapping Distance");
	o->longDesc = N_("Edge Snapping Distance.");
	o->type = CompOptionTypeFloat;
	o->value.f = WOBBLY_EDGE_DISTANCE_DEFAULT;
	o->rest.f.min = WOBBLY_EDGE_DISTANCE_MIN;
	o->rest.f.max = WOBBLY_EDGE_DISTANCE_MAX;
	o->rest.f.precision = WOBBLY_EDGE_DISTANCE_PRECISION;

	o = &ws->opt[WOBBLY_SCREEN_OPTION_EDGE_VELOCITY];
	o->advanced = False;
	o->name = "edge_velocity";
	o->group = N_("Advanced");
	o->subGroup = N_("Edge");
	o->displayHints = "";
	o->shortDesc = N_("Edge Snapping Velocity");
	o->longDesc = N_("Edge Snapping Velocity.");
	o->type = CompOptionTypeFloat;
	o->value.f = WOBBLY_EDGE_VELOCITY_DEFAULT;
	o->rest.f.min = WOBBLY_EDGE_VELOCITY_MIN;
	o->rest.f.max = WOBBLY_EDGE_VELOCITY_MAX;
	o->rest.f.precision = WOBBLY_EDGE_VELOCITY_PRECISION;

	o = &ws->opt[WOBBLY_SCREEN_OPTION_EDGE_ATTRACTION];
	o->advanced = False;
	o->name = "edge_attraction";
	o->group = N_("Basic");
	o->subGroup = N_("Snap");
	o->displayHints = "";
	o->shortDesc = N_("Edge Attraction");
	o->longDesc = N_("Enable Edge Attraction.");
	o->type = CompOptionTypeBool;
	o->value.b = WOBBLY_EDGE_ATTRACTION_DEFAULT;
}

static CompOption *wobblyGetScreenOptions(CompScreen * screen, int *count)
{
	if (screen)
	{
		WOBBLY_SCREEN(screen);

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

		wobblyScreenInitOptions(ws);
		*count = NUM_OPTIONS(ws);
		return ws->opt;
	}
}

#define SNAP_WINDOW_TYPE (CompWindowTypeNormalMask | \
			  CompWindowTypeToolbarMask | \
			  CompWindowTypeMenuMask | \
			  CompWindowTypeUtilMask)

static void findNextWestEdge(CompWindow * w, Object * object)
{
	int v, v1, v2;
	int s, start;
	int e, end;
	int x;
	int output;

	start = -65535.0f;
	end = 65535.0f;

	v1 = -65535.0f;
	v2 = 65535.0f;

	x = object->position.x + w->output.left - w->input.left;

	output = outputDeviceForPoint(w->screen, x, object->position.y);

	if (x >= w->screen->outputDev[output].region.extents.x1)
	{
		CompWindow *p;

		v1 = w->screen->outputDev[output].region.extents.x1;

		for (p = w->screen->windows; p; p = p->next)
		{
			if (w == p)
				continue;

			if (p->mapNum && p->struts)
			{
				s = p->struts->left.y - w->output.top;
				e = p->struts->left.y + p->struts->left.height +
						w->output.bottom;
			}
			else if (!p->invisible && (p->type & SNAP_WINDOW_TYPE))
			{
				s = p->attrib.y - p->output.top - w->output.top;
				e = p->attrib.y + p->height + p->output.bottom +
						w->output.bottom;
			}
			else
				continue;

			if (s > object->position.y)
			{
				if (s < end)
					end = s;
			}
			else if (e < object->position.y)
			{
				if (e > start)
					start = e;
			}
			else
			{
				if (s > start)
					start = s;

				if (e < end)
					end = e;

				if (p->mapNum && p->struts)
					v = p->struts->left.x + p->struts->left.width;
				else
					v = p->attrib.x + p->width + p->input.right;

				if (v <= x)
				{
					if (v > v1)
						v1 = v;
				}
				else
				{
					if (v < v2)
						v2 = v;
				}
			}
		}
	}
	else
	{
		v2 = w->screen->outputDev[output].region.extents.x2;
	}

	v1 = v1 - w->output.left + w->input.left;
	v2 = v2 - w->output.left + w->input.left;

	if (v1 != (int)object->vertEdge.next)
		object->vertEdge.snapped = FALSE;

	object->vertEdge.start = start;
	object->vertEdge.end = end;

	object->vertEdge.next = v1;
	object->vertEdge.prev = v2;

	WOBBLY_SCREEN(w->screen);

	if (ws->opt[WOBBLY_SCREEN_OPTION_EDGE_ATTRACTION].value.b)
		object->vertEdge.attract = v1 + ws->edgeDistance;
	else object->vertEdge.attract = v1;
	object->vertEdge.velocity = ws->edgeVelocity;
}

static void findNextEastEdge(CompWindow * w, Object * object)
{
	int v, v1, v2;
	int s, start;
	int e, end;
	int x;
	int output;

	start = -65535.0f;
	end = 65535.0f;

	v1 = 65535.0f;
	v2 = -65535.0f;

	x = object->position.x - w->output.right + w->input.right;

	output = outputDeviceForPoint(w->screen, x, object->position.y);

	if (x <= w->screen->outputDev[output].region.extents.x2)
	{
		CompWindow *p;

		v1 = w->screen->outputDev[output].region.extents.x2;

		for (p = w->screen->windows; p; p = p->next)
		{
			if (w == p)
				continue;

			if (p->mapNum && p->struts)
			{
				s = p->struts->right.y - w->output.top;
				e = p->struts->right.y + p->struts->right.height +
						w->output.bottom;
			}
			else if (!p->invisible && (p->type & SNAP_WINDOW_TYPE))
			{
				s = p->attrib.y - p->output.top - w->output.top;
				e = p->attrib.y + p->height + p->output.bottom +
						w->output.bottom;
			}
			else
				continue;

			if (s > object->position.y)
			{
				if (s < end)
					end = s;
			}
			else if (e < object->position.y)
			{
				if (e > start)
					start = e;
			}
			else
			{
				if (s > start)
					start = s;

				if (e < end)
					end = e;

				if (p->mapNum && p->struts)
					v = p->struts->right.x;
				else
					v = p->attrib.x - p->input.left;

				if (v >= x)
				{
					if (v < v1)
						v1 = v;
				}
				else
				{
					if (v > v2)
						v2 = v;
				}
			}
		}
	}
	else
	{
		v2 = w->screen->outputDev[output].region.extents.x2;
	}

	v1 = v1 + w->output.right - w->input.right;
	v2 = v2 + w->output.right - w->input.right;

	if (v1 != (int)object->vertEdge.next)
		object->vertEdge.snapped = FALSE;

	object->vertEdge.start = start;
	object->vertEdge.end = end;

	object->vertEdge.next = v1;
	object->vertEdge.prev = v2;

	WOBBLY_SCREEN(w->screen);

	if (ws->opt[WOBBLY_SCREEN_OPTION_EDGE_ATTRACTION].value.b)
		object->vertEdge.attract = v1 - ws->edgeDistance;
	else object->vertEdge.attract = v1;
	object->vertEdge.velocity = ws->edgeVelocity;
}

static void findNextNorthEdge(CompWindow * w, Object * object)
{
	int v, v1, v2;
	int s, start;
	int e, end;
	int y;
	int output;

	start = -65535.0f;
	end = 65535.0f;

	v1 = -65535.0f;
	v2 = 65535.0f;

	y = object->position.y + w->output.top - w->input.top;

	output = outputDeviceForPoint(w->screen, object->position.x, y);

	if (y >= w->screen->outputDev[output].region.extents.y1)
	{
		CompWindow *p;

		v1 = w->screen->outputDev[output].region.extents.y1;

		for (p = w->screen->windows; p; p = p->next)
		{
			if (w == p)
				continue;

			if (p->mapNum && p->struts)
			{
				s = p->struts->top.x - w->output.left;
				e = p->struts->top.x + p->struts->top.width + w->output.right;
			}
			else if (!p->invisible && (p->type & SNAP_WINDOW_TYPE))
			{
				s = p->attrib.x - p->output.left - w->output.left;
				e = p->attrib.x + p->width + p->output.right +
						w->output.right;
			}
			else
				continue;

			if (s > object->position.x)
			{
				if (s < end)
					end = s;
			}
			else if (e < object->position.x)
			{
				if (e > start)
					start = e;
			}
			else
			{
				if (s > start)
					start = s;

				if (e < end)
					end = e;

				if (p->mapNum && p->struts)
					v = p->struts->top.y + p->struts->top.height;
				else
					v = p->attrib.y + p->height + p->input.bottom;

				if (v <= y)
				{
					if (v > v1)
						v1 = v;
				}
				else
				{
					if (v < v2)
						v2 = v;
				}
			}
		}
	}
	else
	{
		v2 = w->screen->outputDev[output].region.extents.y1;
	}

	v1 = v1 - w->output.top + w->input.top;
	v2 = v2 - w->output.top + w->input.top;

	if (v1 != (int)object->horzEdge.next)
		object->horzEdge.snapped = FALSE;

	object->horzEdge.start = start;
	object->horzEdge.end = end;

	object->horzEdge.next = v1;
	object->horzEdge.prev = v2;

	WOBBLY_SCREEN(w->screen);

	if (ws->opt[WOBBLY_SCREEN_OPTION_EDGE_ATTRACTION].value.b)
		object->horzEdge.attract = v1 + ws->edgeDistance;
	else object->horzEdge.attract = v1;
	object->horzEdge.velocity = ws->edgeVelocity;
}

static void findNextSouthEdge(CompWindow * w, Object * object)
{
	int v, v1, v2;
	int s, start;
	int e, end;
	int y;
	int output;

	start = -65535.0f;
	end = 65535.0f;

	v1 = 65535.0f;
	v2 = -65535.0f;

	y = object->position.y - w->output.bottom + w->input.bottom;

	output = outputDeviceForPoint(w->screen, object->position.x, y);

	if (y <= w->screen->outputDev[output].region.extents.y2)
	{
		CompWindow *p;

		v1 = w->screen->outputDev[output].region.extents.y2;

		for (p = w->screen->windows; p; p = p->next)
		{
			if (w == p)
				continue;

			if (p->mapNum && p->struts)
			{
				s = p->struts->bottom.x - w->output.left;
				e = p->struts->bottom.x + p->struts->bottom.width +
						w->output.right;
			}
			else if (!p->invisible && (p->type & SNAP_WINDOW_TYPE))
			{
				s = p->attrib.x - p->output.left - w->output.left;
				e = p->attrib.x + p->width + p->output.right +
						w->output.right;
			}
			else
				continue;

			if (s > object->position.x)
			{
				if (s < end)
					end = s;
			}
			else if (e < object->position.x)
			{
				if (e > start)
					start = e;
			}
			else
			{
				if (s > start)
					start = s;

				if (e < end)
					end = e;

				if (p->mapNum && p->struts)
					v = p->struts->bottom.y;
				else
					v = p->attrib.y - p->input.top;

				if (v >= y)
				{
					if (v < v1)
						v1 = v;
				}
				else
				{
					if (v > v2)
						v2 = v;
				}
			}
		}
	}
	else
	{
		v2 = w->screen->outputDev[output].region.extents.y2;
	}

	v1 = v1 + w->output.bottom - w->input.bottom;
	v2 = v2 + w->output.bottom - w->input.bottom;

	if (v1 != (int)object->horzEdge.next)
		object->horzEdge.snapped = FALSE;

	object->horzEdge.start = start;
	object->horzEdge.end = end;

	object->horzEdge.next = v1;
	object->horzEdge.prev = v2;

	WOBBLY_SCREEN(w->screen);

	if (ws->opt[WOBBLY_SCREEN_OPTION_EDGE_ATTRACTION].value.b)
		object->horzEdge.attract = v1 - ws->edgeDistance;
	else object->horzEdge.attract = v1;
	object->horzEdge.velocity = ws->edgeVelocity;
}

static void
objectInit(Object * object,
		   float positionX, float positionY, float velocityX, float velocityY)
{
	object->force.x = 0;
	object->force.y = 0;

	object->position.x = positionX;
	object->position.y = positionY;

	object->velocity.x = velocityX;
	object->velocity.y = velocityY;

	object->theta = 0;
	object->immobile = FALSE;

	object->edgeMask = 0;

	object->vertEdge.snapped = FALSE;
	object->horzEdge.snapped = FALSE;

	object->vertEdge.next = 0.0f;
	object->horzEdge.next = 0.0f;
}

static void
springInit(Spring * spring,
		   Object * a, Object * b, float offsetX, float offsetY)
{
	spring->a = a;
	spring->b = b;
	spring->offset.x = offsetX;
	spring->offset.y = offsetY;
}

static void modelCalcBounds(Model * model)
{
	int i;

	model->topLeft.x = MAXSHORT;
	model->topLeft.y = MAXSHORT;
	model->bottomRight.x = MINSHORT;
	model->bottomRight.y = MINSHORT;

	for (i = 0; i < model->numObjects; i++)
	{
		if (model->objects[i].position.x < model->topLeft.x)
			model->topLeft.x = model->objects[i].position.x;
		else if (model->objects[i].position.x > model->bottomRight.x)
			model->bottomRight.x = model->objects[i].position.x;

		if (model->objects[i].position.y < model->topLeft.y)
			model->topLeft.y = model->objects[i].position.y;
		else if (model->objects[i].position.y > model->bottomRight.y)
			model->bottomRight.y = model->objects[i].position.y;
	}
}

static void
modelAddSpring(Model * model,
			   Object * a, Object * b, float offsetX, float offsetY)
{
	Spring *spring;

	spring = &model->springs[model->numSprings];
	model->numSprings++;

	springInit(spring, a, b, offsetX, offsetY);
}

static void
modelSetMiddleAnchor(Model * model, int x, int y, int width, int height)
{
	float gx, gy;

	gx = ((GRID_WIDTH - 1) / 2 * width) / (float)(GRID_WIDTH - 1);
	gy = ((GRID_HEIGHT - 1) / 2 * height) / (float)(GRID_HEIGHT - 1);

	if (model->anchorObject)
		model->anchorObject->immobile = FALSE;

	model->anchorObject = &model->objects[GRID_WIDTH *
										  ((GRID_HEIGHT - 1) / 2) +
										  (GRID_WIDTH - 1) / 2];
	model->anchorObject->position.x = x + gx;
	model->anchorObject->position.y = y + gy;

	model->anchorObject->immobile = TRUE;
}

static void
modelAddEdgeAnchors(Model * model, int x, int y, int width, int height)
{
	Object *o;

	o = &model->objects[0];
	o->position.x = x;
	o->position.y = y;
	o->immobile = TRUE;

	o = &model->objects[GRID_WIDTH - 1];
	o->position.x = x + width;
	o->position.y = y;
	o->immobile = TRUE;

	o = &model->objects[GRID_WIDTH * (GRID_HEIGHT - 1)];
	o->position.x = x;
	o->position.y = y + height;
	o->immobile = TRUE;

	o = &model->objects[model->numObjects - 1];
	o->position.x = x + width;
	o->position.y = y + height;
	o->immobile = TRUE;

	if (!model->anchorObject)
		model->anchorObject = &model->objects[0];
}

static void
modelRemoveEdgeAnchors(Model * model, int x, int y, int width, int height)
{
	Object *o;

	o = &model->objects[0];
	o->position.x = x;
	o->position.y = y;
	if (o != model->anchorObject)
		o->immobile = FALSE;

	o = &model->objects[GRID_WIDTH - 1];
	o->position.x = x + width;
	o->position.y = y;
	if (o != model->anchorObject)
		o->immobile = FALSE;

	o = &model->objects[GRID_WIDTH * (GRID_HEIGHT - 1)];
	o->position.x = x;
	o->position.y = y + height;
	if (o != model->anchorObject)
		o->immobile = FALSE;

	o = &model->objects[model->numObjects - 1];
	o->position.x = x + width;
	o->position.y = y + height;
	if (o != model->anchorObject)
		o->immobile = FALSE;
}

static void
modelAdjustObjectPosition(Model * model,
						  Object * object,
						  int x, int y, int width, int height)
{
	Object *o;
	int gridX, gridY, i = 0;

	for (gridY = 0; gridY < GRID_HEIGHT; gridY++)
	{
		for (gridX = 0; gridX < GRID_WIDTH; gridX++)
		{
			o = &model->objects[i];
			if (o == object)
			{
				o->position.x = x + (gridX * width) / (GRID_WIDTH - 1);
				o->position.y = y + (gridY * height) / (GRID_HEIGHT - 1);

				return;
			}

			i++;
		}
	}
}

static void
modelInitObjects(Model * model, int x, int y, int width, int height)
{
	int gridX, gridY, i = 0;
	float gw, gh;

	gw = GRID_WIDTH - 1;
	gh = GRID_HEIGHT - 1;

	for (gridY = 0; gridY < GRID_HEIGHT; gridY++)
	{
		for (gridX = 0; gridX < GRID_WIDTH; gridX++)
		{
			objectInit(&model->objects[i],
					   x + (gridX * width) / gw,
					   y + (gridY * height) / gh, 0, 0);
			i++;
		}
	}

	modelSetMiddleAnchor(model, x, y, width, height);
}

static void modelUpdateSnapping(CompWindow * window, Model * model)
{
	unsigned int edgeMask, gridMask, mask;
	int gridX, gridY, i = 0;

	edgeMask = model->edgeMask;

	if (model->snapCnt[NORTH])
		edgeMask &= ~SouthEdgeMask;
	else if (model->snapCnt[SOUTH])
		edgeMask &= ~NorthEdgeMask;

	if (model->snapCnt[WEST])
		edgeMask &= ~EastEdgeMask;
	else if (model->snapCnt[EAST])
		edgeMask &= ~WestEdgeMask;

	for (gridY = 0; gridY < GRID_HEIGHT; gridY++)
	{
		if (gridY == 0)
			gridMask = edgeMask & NorthEdgeMask;
		else if (gridY == GRID_HEIGHT - 1)
			gridMask = edgeMask & SouthEdgeMask;
		else
			gridMask = 0;

		for (gridX = 0; gridX < GRID_WIDTH; gridX++)
		{
			mask = gridMask;

			if (gridX == 0)
				mask |= edgeMask & WestEdgeMask;
			else if (gridX == GRID_WIDTH - 1)
				mask |= edgeMask & EastEdgeMask;

			if (mask != model->objects[i].edgeMask)
			{
				model->objects[i].edgeMask = mask;

				if (mask & WestEdgeMask)
				{
					if (!model->objects[i].vertEdge.snapped)
						findNextWestEdge(window, &model->objects[i]);
				}
				else if (mask & EastEdgeMask)
				{
					if (!model->objects[i].vertEdge.snapped)
						findNextEastEdge(window, &model->objects[i]);
				}
				else
					model->objects[i].vertEdge.snapped = FALSE;

				if (mask & NorthEdgeMask)
				{
					if (!model->objects[i].horzEdge.snapped)
						findNextNorthEdge(window, &model->objects[i]);
				}
				else if (mask & SouthEdgeMask)
				{
					if (!model->objects[i].horzEdge.snapped)
						findNextSouthEdge(window, &model->objects[i]);
				}
				else
					model->objects[i].horzEdge.snapped = FALSE;
			}

			i++;
		}
	}
}

static void modelReduceEdgeEscapeVelocity(Model * model)
{
	int gridX, gridY, i = 0;

	for (gridY = 0; gridY < GRID_HEIGHT; gridY++)
	{
		for (gridX = 0; gridX < GRID_WIDTH; gridX++)
		{
			if (model->objects[i].vertEdge.snapped)
				model->objects[i].vertEdge.velocity *= drand48() * 0.25f;

			if (model->objects[i].horzEdge.snapped)
				model->objects[i].horzEdge.velocity *= drand48() * 0.25f;

			i++;
		}
	}
}

static Bool modelDisableSnapping(CompWindow * window, Model * model)
{
	int gridX, gridY, i = 0;
	Bool snapped = FALSE;

	for (gridY = 0; gridY < GRID_HEIGHT; gridY++)
	{
		for (gridX = 0; gridX < GRID_WIDTH; gridX++)
		{
			if (model->objects[i].vertEdge.snapped ||
				model->objects[i].horzEdge.snapped)
				snapped = TRUE;

			model->objects[i].vertEdge.snapped = FALSE;
			model->objects[i].horzEdge.snapped = FALSE;

			model->objects[i].edgeMask = 0;

			i++;
		}
	}

	memset(model->snapCnt, 0, sizeof(model->snapCnt));

	return snapped;
}

static void
modelAdjustObjectsForShiver(Model * model,
							int x, int y, int width, int height)
{
	int gridX, gridY, i = 0;
	float vX, vY;
	float scale;
	float w, h;

	w = width;
	h = height;

	for (gridY = 0; gridY < GRID_HEIGHT; gridY++)
	{
		for (gridX = 0; gridX < GRID_WIDTH; gridX++)
		{
			if (!model->objects[i].immobile)
			{
				vX = model->objects[i].position.x - (x + w / 2);
				vY = model->objects[i].position.y - (y + h / 2);

				vX /= w;
				vY /= h;

				scale = ((float)rand() * 7.5f) / RAND_MAX;

				model->objects[i].velocity.x += vX * scale;
				model->objects[i].velocity.y += vY * scale;
			}

			i++;
		}
	}
}

static void
modelInitSprings(Model * model, int x, int y, int width, int height)
{
	int gridX, gridY, i = 0;
	float hpad, vpad;

	model->numSprings = 0;

	hpad = ((float)width) / (GRID_WIDTH - 1);
	vpad = ((float)height) / (GRID_HEIGHT - 1);

	for (gridY = 0; gridY < GRID_HEIGHT; gridY++)
	{
		for (gridX = 0; gridX < GRID_WIDTH; gridX++)
		{
			if (gridX > 0)
				modelAddSpring(model,
							   &model->objects[i - 1],
							   &model->objects[i], hpad, 0);

			if (gridY > 0)
				modelAddSpring(model,
							   &model->objects[i -
											   GRID_WIDTH],
							   &model->objects[i], 0, vpad);

			i++;
		}
	}
}

static void modelMove(Model * model, float tx, float ty)
{
	int i;

	for (i = 0; i < model->numObjects; i++)
	{
		model->objects[i].position.x += tx;
		model->objects[i].position.y += ty;
	}
}

static Model *createModel(int x, int y, int width, int height,
						  unsigned int edgeMask)
{
	Model *model;

	model = malloc(sizeof(Model));
	if (!model)
		return 0;

	model->numObjects = GRID_WIDTH * GRID_HEIGHT;
	model->objects = malloc(sizeof(Object) * model->numObjects);
	if (!model->objects)
	{
		free(model);
		return 0;
	}

	model->anchorObject = 0;
	model->numSprings = 0;

	model->steps = 0;

	memset(model->snapCnt, 0, sizeof(model->snapCnt));

	model->edgeMask = edgeMask;

	modelInitObjects(model, x, y, width, height);
	modelInitSprings(model, x, y, width, height);

	modelCalcBounds(model);

	return model;
}

static void objectApplyForce(Object * object, float fx, float fy)
{
	object->force.x += fx;
	object->force.y += fy;
}

static void springExertForces(Spring * spring, float k)
{
	Vector da, db;
	Vector a, b;

	a = spring->a->position;
	b = spring->b->position;

	da.x = 0.5f * (b.x - a.x - spring->offset.x);
	da.y = 0.5f * (b.y - a.y - spring->offset.y);

	db.x = 0.5f * (a.x - b.x + spring->offset.x);
	db.y = 0.5f * (a.y - b.y + spring->offset.y);

	objectApplyForce(spring->a, k * da.x, k * da.y);
	objectApplyForce(spring->b, k * db.x, k * db.y);
}

static Bool
objectReleaseEdge(CompWindow * w, Model * model, Object * object,
				  short int edge)
{
	if (((NORTH == edge || edge == SOUTH)
		 && (fabs(object->velocity.y) > object->horzEdge.velocity))
		|| ((WEST == edge || edge == EAST)
			&& (fabs(object->velocity.x) > object->vertEdge.velocity)))
	{

		model->snapCnt[edge]--;

		if (edge == WEST || edge == EAST)
		{
			object->position.x += object->velocity.x * 2.0f;
			object->vertEdge.snapped = FALSE;
		}
		else
		{
			object->position.y += object->velocity.y * 2.0f;
			object->horzEdge.snapped = FALSE;
		}
		object->edgeMask = 0;
		modelUpdateSnapping(w, model);
		return TRUE;
	}

	if (edge == WEST || edge == EAST)
		object->velocity.x = 0.0f;
	else
		object->velocity.y = 0.0f;

	return FALSE;
}

static float
modelStepObject(CompWindow * window,
				Model * model, Object * object, float friction, float *force)
{
	object->theta += 0.05f;

	if (object->immobile)
	{
		object->velocity.x = 0.0f;
		object->velocity.y = 0.0f;

		object->force.x = 0.0f;
		object->force.y = 0.0f;

		*force = 0.0f;

		return 0.0f;
	}
	else
	{
		object->force.x -= friction * object->velocity.x;
		object->force.y -= friction * object->velocity.y;

		object->velocity.x += object->force.x / MASS;
		object->velocity.y += object->force.y / MASS;

		if (object->edgeMask)
		{
			if (object->edgeMask & WestEdgeMask)
			{
				if (object->position.y <
					object->vertEdge.start
					|| object->position.y > object->vertEdge.end)
					findNextWestEdge(window, object);

				if (object->vertEdge.snapped == FALSE ||
					objectReleaseEdge(window, model, object, WEST))
				{
					object->position.x += object->velocity.x;

					if (object->velocity.x < 0.0f &&
						object->position.x < object->vertEdge.attract)
					{
						if (object->position.x < object->vertEdge.next)
						{
							object->vertEdge.snapped = TRUE;
							object->position.x = object->vertEdge.next;
							object->velocity.x = 0.0f;

							model->snapCnt[WEST]++;

							modelUpdateSnapping(window, model);
						}
						else
						{
							object->velocity.
									x -=
									object->vertEdge.attract -
									object->position.x;
						}
					}

					if (object->position.x > object->vertEdge.prev)
						findNextWestEdge(window, object);
				}
			}
			else if (object->edgeMask & EastEdgeMask)
			{
				if (object->position.y <
					object->vertEdge.start
					|| object->position.y > object->vertEdge.end)
					findNextEastEdge(window, object);

				if (object->vertEdge.snapped == FALSE ||
					objectReleaseEdge(window, model, object, EAST))
				{
					object->position.x += object->velocity.x;

					if (object->velocity.x > 0.0f &&
						object->position.x > object->vertEdge.attract)
					{
						if (object->position.x > object->vertEdge.next)
						{
							object->vertEdge.snapped = TRUE;
							object->position.x = object->vertEdge.next;
							object->velocity.x = 0.0f;

							model->snapCnt[EAST]++;

							modelUpdateSnapping(window, model);
						}
						else
						{
							object->velocity.
									x =
									object->position.x -
									object->vertEdge.attract;
						}
					}

					if (object->position.x < object->vertEdge.prev)
						findNextEastEdge(window, object);
				}
			}
			else
				object->position.x += object->velocity.x;

			if (object->edgeMask & NorthEdgeMask)
			{
				if (object->position.x <
					object->horzEdge.start
					|| object->position.x > object->horzEdge.end)
					findNextNorthEdge(window, object);

				if (object->horzEdge.snapped == FALSE ||
					objectReleaseEdge(window, model, object, NORTH))
				{
					object->position.y += object->velocity.y;

					if (object->velocity.y < 0.0f &&
						object->position.y < object->horzEdge.attract)
					{
						if (object->position.y < object->horzEdge.next)
						{
							object->horzEdge.snapped = TRUE;
							object->position.y = object->horzEdge.next;
							object->velocity.y = 0.0f;

							model->snapCnt[NORTH]++;

							modelUpdateSnapping(window, model);
						}
						else
						{
							object->velocity.
									y -=
									object->horzEdge.attract -
									object->position.y;
						}
					}

					if (object->position.y > object->horzEdge.prev)
						findNextNorthEdge(window, object);
				}
			}
			else if (object->edgeMask & SouthEdgeMask)
			{
				if (object->position.x <
					object->horzEdge.start
					|| object->position.x > object->horzEdge.end)
					findNextSouthEdge(window, object);

				if (object->horzEdge.snapped == FALSE ||
					objectReleaseEdge(window, model, object, SOUTH))
				{
					object->position.y += object->velocity.y;

					if (object->velocity.y > 0.0f &&
						object->position.y > object->horzEdge.attract)
					{
						if (object->position.y > object->horzEdge.next)
						{
							object->horzEdge.snapped = TRUE;
							object->position.y = object->horzEdge.next;
							object->velocity.y = 0.0f;

							model->snapCnt[SOUTH]++;

							modelUpdateSnapping(window, model);
						}
						else
						{
							object->velocity.
									y =
									object->position.y -
									object->horzEdge.attract;
						}
					}

					if (object->position.y < object->horzEdge.prev)
						findNextSouthEdge(window, object);
				}
			}
			else
				object->position.y += object->velocity.y;
		}
		else
		{
			object->position.x += object->velocity.x;
			object->position.y += object->velocity.y;
		}

		*force = fabs(object->force.x) + fabs(object->force.y);

		object->force.x = 0.0f;
		object->force.y = 0.0f;

		return fabs(object->velocity.x) + fabs(object->velocity.y);
	}
}

static int
modelStep(CompWindow * window,
		  Model * model, float friction, float k, float time)
{
	int i, j, steps, wobbly = 0;
	float velocitySum = 0.0f;
	float force, forceSum = 0.0f;

	model->steps += time / 15.0f;
	steps = floor(model->steps);
	model->steps -= steps;

	if (!steps)
		return TRUE;

	for (j = 0; j < steps; j++)
	{
		for (i = 0; i < model->numSprings; i++)
			springExertForces(&model->springs[i], k);

		for (i = 0; i < model->numObjects; i++)
		{
			velocitySum += modelStepObject(window,
										   model,
										   &model->objects[i],
										   friction, &force);
			forceSum += force;
		}
	}

	modelCalcBounds(model);

	if (velocitySum > 0.5f)
		wobbly |= WobblyVelocity;

	if (forceSum > 20.0f)
		wobbly |= WobblyForce;

	return wobbly;
}

static void
bezierPatchEvaluate(Model * model,
					float u, float v, float *patchX, float *patchY)
{
	float coeffsU[4], coeffsV[4];
	float x, y;
	int i, j;

	coeffsU[0] = (1 - u) * (1 - u) * (1 - u);
	coeffsU[1] = 3 * u * (1 - u) * (1 - u);
	coeffsU[2] = 3 * u * u * (1 - u);
	coeffsU[3] = u * u * u;

	coeffsV[0] = (1 - v) * (1 - v) * (1 - v);
	coeffsV[1] = 3 * v * (1 - v) * (1 - v);
	coeffsV[2] = 3 * v * v * (1 - v);
	coeffsV[3] = v * v * v;

	x = y = 0.0f;

	for (i = 0; i < 4; i++)
	{
		for (j = 0; j < 4; j++)
		{
			x += coeffsU[i] * coeffsV[j] *
					model->objects[j * GRID_WIDTH + i].position.x;
			y += coeffsU[i] * coeffsV[j] *
					model->objects[j * GRID_WIDTH + i].position.y;
		}
	}

	*patchX = x;
	*patchY = y;
}

static Bool wobblyEnsureModel(CompWindow * w)
{
	WOBBLY_WINDOW(w);

	if (!ww->model)
	{
		unsigned int edgeMask = 0;

		if (w->type & CompWindowTypeNormalMask)
			edgeMask =
					WestEdgeMask | EastEdgeMask | NorthEdgeMask |
					SouthEdgeMask;

		ww->model =
				createModel(WIN_X(w), WIN_Y(w), WIN_W(w), WIN_H(w), edgeMask);
		if (!ww->model)
			return FALSE;
	}

	return TRUE;
}

static float objectDistance(Object * object, float x, float y)
{
	float dx, dy;

	dx = object->position.x - x;
	dy = object->position.y - y;

	return sqrt(dx * dx + dy * dy);
}

static Object *modelFindNearestObject(Model * model, float x, float y)
{
	Object *object = &model->objects[0];
	float distance, minDistance = 0.0;
	int i;

	for (i = 0; i < model->numObjects; i++)
	{
		distance = objectDistance(&model->objects[i], x, y);
		if (i == 0 || distance < minDistance)
		{
			minDistance = distance;
			object = &model->objects[i];
		}
	}

	return object;
}

static Bool isWobblyWin(CompWindow * w)
{
	WOBBLY_WINDOW(w);

	if (ww->model)
		return TRUE;

	/* avoid offscreen windows */
	if (w->state & CompWindowStateOffscreenMask)
		return FALSE;

	/* avoid tiny windows */
	if (w->width == 1 && w->height == 1)
		return FALSE;

	/* avoid fullscreen windows */
	if (w->attrib.x <= 0 &&
		w->attrib.y <= 0 &&
		w->attrib.x + w->width >= w->screen->width &&
		w->attrib.y + w->height >= w->screen->height)
		return FALSE;

	return TRUE;
}

static void wobblyPreparePaintScreen(CompScreen * s, int msSinceLastPaint)
{
	WobblyWindow *ww;
	CompWindow *w;

	WOBBLY_SCREEN(s);

	if (ws->wobblyWindows & (WobblyInitial | WobblyVelocity))
	{
		BoxRec box;
		Point topLeft, bottomRight;
		float friction, springK;
		Model *model;

		ws->wobblyWindows = 0;
		for (w = s->windows; w; w = w->next)
		{
			ww = GET_WOBBLY_WINDOW(w, ws);

			friction = ww->friction;
			springK = ww->spring_k;

			if (ww->wobbly)
			{
				if (ww->wobbly & (WobblyInitial | WobblyVelocity))
				{
					model = ww->model;

					topLeft = model->topLeft;
					bottomRight = model->bottomRight;

					ww->wobbly =
							modelStep(w, model, friction,
									  springK,
									  (ww->
									   wobbly &
									   WobblyVelocity) ?
									  msSinceLastPaint : s->redrawTime);

					if ((ww->state & MAXIMIZE_STATE) && ww->grabbed)
						ww->wobbly |= WobblyForce;

					if (ww->wobbly)
					{
						/* snapped to more than one edge, we have to reduce
						   edge escape velocity until only one edge is snapped */
						if (ww->wobbly == WobblyForce && !ww->grabbed)
						{
							modelReduceEdgeEscapeVelocity(ww->model);
							ww->wobbly |= WobblyInitial;
						}
					}
					else
					{
						ww->model = NULL;

						if (w->attrib.x == w->serverX &&
							w->attrib.y == w->serverY)
						{
							moveWindow(w,
									   model->topLeft.x + w->output.left -
									   w->attrib.x,
									   model->topLeft.y + w->output.top -
									   w->attrib.y, TRUE, TRUE);
							syncWindowPosition(w);
						}

						ww->model = model;
					}

					if (!(s->damageMask & COMP_SCREEN_DAMAGE_ALL_MASK))
					{
						if (ww->wobbly)
						{
							if (ww->model->topLeft.x < topLeft.x)
								topLeft.x = ww->model->topLeft.x;
							if (ww->model->topLeft.y < topLeft.y)
								topLeft.y = ww->model->topLeft.y;
							if (ww->model->bottomRight.x > bottomRight.x)
								bottomRight.x = ww->model->bottomRight.x;
							if (ww->model->bottomRight.y > bottomRight.y)
								bottomRight.y = ww->model->bottomRight.y;
						}
						else
							addWindowDamage(w);

						box.x1 = topLeft.x;
						box.y1 = topLeft.y;
						box.x2 = bottomRight.x + 0.5f;
						box.y2 = bottomRight.y + 0.5f;

						box.x1 -= w->attrib.x + w->attrib.border_width;
						box.y1 -= w->attrib.y + w->attrib.border_width;
						box.x2 -= w->attrib.x + w->attrib.border_width;
						box.y2 -= w->attrib.y + w->attrib.border_width;

						addWindowDamageRect(w, &box);
					}
				}

				ws->wobblyWindows |= ww->wobbly;
			}
		}
	}

	UNWRAP(ws, s, preparePaintScreen);
	(*s->preparePaintScreen) (s, msSinceLastPaint);
	WRAP(ws, s, preparePaintScreen, wobblyPreparePaintScreen);
}

static void wobblyDonePaintScreen(CompScreen * s)
{
	WOBBLY_SCREEN(s);

	if (ws->wobblyWindows & (WobblyVelocity | WobblyInitial))
		damageScreen(s);

	UNWRAP(ws, s, donePaintScreen);
	(*s->donePaintScreen) (s);
	WRAP(ws, s, donePaintScreen, wobblyDonePaintScreen);
}

static void
wobblyAddWindowGeometry(CompWindow * w,
						CompMatrix * matrix,
						int nMatrix, Region region, Region clip)
{
	WOBBLY_WINDOW(w);
	WOBBLY_SCREEN(w->screen);

	if (ww->wobbly)
	{
		BoxPtr pClip;
		int nClip, nVertices, nIndices;
		GLushort *i;
		GLfloat *v;
		int x1, y1, x2, y2;
		float width, height;
		float deformedX, deformedY;
		int x, y, iw, ih, wx, wy;
		int vSize, it;
		int gridW, gridH;
		Bool rect = TRUE;

		for (it = 0; it < nMatrix; it++)
		{
			if (matrix[it].xy != 0.0f || matrix[it].yx != 0.0f)
			{
				rect = FALSE;
				break;
			}
		}

		wx = WIN_X(w);
		wy = WIN_Y(w);
		width = WIN_W(w);
		height = WIN_H(w);

		gridW = width / ws->opt[WOBBLY_SCREEN_OPTION_GRID_RESOLUTION].value.i;
		if (gridW < ws->opt[WOBBLY_SCREEN_OPTION_MIN_GRID_SIZE].value.i)
			gridW = ws->opt[WOBBLY_SCREEN_OPTION_MIN_GRID_SIZE].value.i;

		gridH = height /
				ws->opt[WOBBLY_SCREEN_OPTION_GRID_RESOLUTION].value.i;
		if (gridH < ws->opt[WOBBLY_SCREEN_OPTION_MIN_GRID_SIZE].value.i)
			gridH = ws->opt[WOBBLY_SCREEN_OPTION_MIN_GRID_SIZE].value.i;

		nClip = region->numRects;
		pClip = region->rects;

		w->texUnits = nMatrix;

		if (w->vCount == 0)
		{
			// reset
			w->indexCount = 0;
			w->texCoordSize = 2;
		}
		vSize = 2 + w->texUnits * w->texCoordSize;

		nVertices = w->vCount;
		nIndices = w->indexCount;

		v = w->vertices + (nVertices * vSize);
		i = w->indices + nIndices;

		while (nClip--)
		{
			x1 = pClip->x1;
			y1 = pClip->y1;
			x2 = pClip->x2;
			y2 = pClip->y2;

			iw = ((x2 - x1 - 1) / gridW) + 1;
			ih = ((y2 - y1 - 1) / gridH) + 1;

			if (nIndices + (iw * ih * 4) > w->indexSize)
			{
				if (!moreWindowIndices(w, nIndices + (iw * ih * 4)))
					return;

				i = w->indices + nIndices;
			}

			iw++;
			ih++;

			for (y = 0; y < ih - 1; y++)
			{
				for (x = 0; x < iw - 1; x++)
				{
					*i++ = nVertices + iw * (y + 1) + x;
					*i++ = nVertices + iw * (y + 1) + x + 1;
					*i++ = nVertices + iw * y + x + 1;
					*i++ = nVertices + iw * y + x;

					nIndices += 4;
				}
			}

			if (((nVertices + iw * ih) * vSize) > w->vertexSize)
			{
				if (!moreWindowVertices(w, (nVertices + iw * ih) * vSize))
					return;

				v = w->vertices + (nVertices * vSize);
			}

			for (y = y1;; y += gridH)
			{
				if (y > y2)
					y = y2;

				for (x = x1;; x += gridW)
				{
					if (x > x2)
						x = x2;

					bezierPatchEvaluate(ww->model,
										(x -
										 wx) / width,
										(y -
										 wy) / height,
										&deformedX, &deformedY);

					if (rect)
					{
						for (it = 0; it < nMatrix; it++)
						{
							*v++ = COMP_TEX_COORD_X(&matrix[it], x);
							*v++ = COMP_TEX_COORD_Y(&matrix[it], y);
						}
					}
					else
					{
						for (it = 0; it < nMatrix; it++)
						{
							*v++ = COMP_TEX_COORD_XY(&matrix[it], x, y);
							*v++ = COMP_TEX_COORD_YX(&matrix[it], x, y);
						}
					}

					*v++ = deformedX;
					*v++ = deformedY;

					nVertices++;

					if (x == x2)
						break;
				}

				if (y == y2)
					break;
			}

			pClip++;
		}

		w->vCount = nVertices;
		w->indexCount = nIndices;
	}
	else
	{
		UNWRAP(ws, w->screen, addWindowGeometry);
		(*w->screen->addWindowGeometry) (w, matrix, nMatrix, region, clip);
		WRAP(ws, w->screen, addWindowGeometry, wobblyAddWindowGeometry);
	}
}

static void wobblyDrawWindowGeometry(CompWindow * w)
{
	WOBBLY_WINDOW(w);

	if (ww->wobbly)
	{
		int texUnit = w->texUnits;
		int currentTexUnit = 0;
		int stride = 2 + texUnit * w->texCoordSize;
		GLfloat *vertices = w->vertices + (stride - 2);

		stride *= sizeof(GLfloat);

		glVertexPointer(2, GL_FLOAT, stride, vertices);

		while (texUnit--)
		{
			if (texUnit != currentTexUnit)
			{
				w->screen->clientActiveTexture(GL_TEXTURE0_ARB + texUnit);
				glEnableClientState(GL_TEXTURE_COORD_ARRAY);
				currentTexUnit = texUnit;
			}
			vertices -= 2;
			glTexCoordPointer(w->texCoordSize, GL_FLOAT, stride, vertices);
		}

		glDrawElements(GL_QUADS, w->indexCount, GL_UNSIGNED_SHORT,
					   w->indices);

		/* disable all texture coordinate arrays except 0 */
		texUnit = w->texUnits;
		if (texUnit > 1)
		{
			while (--texUnit)
			{
				(*w->screen->clientActiveTexture) (GL_TEXTURE0_ARB + texUnit);
				glDisableClientState(GL_TEXTURE_COORD_ARRAY);
			}

			(*w->screen->clientActiveTexture) (GL_TEXTURE0_ARB);
		}
	}
	else
	{
		WOBBLY_SCREEN(w->screen);

		UNWRAP(ws, w->screen, drawWindowGeometry);
		(*w->screen->drawWindowGeometry) (w);
		WRAP(ws, w->screen, drawWindowGeometry, wobblyDrawWindowGeometry);
	}
}

static Bool
wobblyPaintWindow(CompWindow * w,
				  const WindowPaintAttrib * attrib,
				  Region region, unsigned int mask)
{
	Bool status;

	WOBBLY_SCREEN(w->screen);
	WOBBLY_WINDOW(w);

	if (ww->wobbly)
	{
		if (mask & PAINT_WINDOW_SOLID_MASK)
			return FALSE;

		mask |= PAINT_WINDOW_TRANSFORMED_MASK;
	}
	UNWRAP(ws, w->screen, paintWindow);
	status = (*w->screen->paintWindow) (w, attrib, region, mask);
	WRAP(ws, w->screen, paintWindow, wobblyPaintWindow);

	return status;
}

static Bool
wobblyEnableSnapping(CompDisplay * d,
					 CompAction * action,
					 CompActionState state, CompOption * option, int nOption)
{
	CompScreen *s;
	CompWindow *w;

	WOBBLY_DISPLAY(d);

	for (s = d->screens; s; s = s->next)
	{
		for (w = s->windows; w; w = w->next)
		{
			WOBBLY_WINDOW(w);

			if (ww->grabbed && ww->model)
				modelUpdateSnapping(w, ww->model);
		}
	}

	wd->snapping = TRUE;

	return FALSE;
}

static Bool
wobblyDisableSnapping(CompDisplay * d,
					  CompAction * action,
					  CompActionState state, CompOption * option, int nOption)
{
	CompScreen *s;
	CompWindow *w;

	WOBBLY_DISPLAY(d);

	if (!wd->snapping)
		return FALSE;

	for (s = d->screens; s; s = s->next)
	{
		for (w = s->windows; w; w = w->next)
		{
			WOBBLY_WINDOW(w);

			if (ww->grabbed && ww->model)
			{
				if (modelDisableSnapping(w, ww->model))
				{
					WOBBLY_SCREEN(w->screen);

					ww->wobbly |= WobblyInitial;
					ws->wobblyWindows |= ww->wobbly;
				}
			}
		}
	}

	wd->snapping = FALSE;

	return FALSE;
}

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

	if (!wd->opt[WOBBLY_DISPLAY_OPTION_DEFAULT_SNAP].value.b)
		return wobblyEnableSnapping(d, NULL, 0, NULL, 0);
	else
		return wobblyDisableSnapping(d, NULL, 0, NULL, 0);
}

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

	if (wd->opt[WOBBLY_DISPLAY_OPTION_DEFAULT_SNAP].value.b)
		return wobblyEnableSnapping(d, NULL, 0, NULL, 0);
	else
		return wobblyDisableSnapping(d, NULL, 0, NULL, 0);
}

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

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

	w = findWindowAtDisplay(d, xid);
	if (w && isWobblyWin(w) && wobblyEnsureModel(w))
	{
		WOBBLY_SCREEN(w->screen);
		WOBBLY_WINDOW(w);

		modelSetMiddleAnchor(ww->model,
							 WIN_X(w), WIN_Y(w), WIN_W(w), WIN_H(w));
		modelAdjustObjectsForShiver(ww->model, WIN_X(w), WIN_Y(w),
									WIN_W(w), WIN_H(w));

		ww->wobbly |= WobblyInitial;
		ws->wobblyWindows |= ww->wobbly;
	}

	return FALSE;
}

static void wobblyHandleEvent(CompDisplay * d, XEvent * event)
{
	Window activeWindow = 0;
	CompWindow *w;
	CompScreen *s;

	WOBBLY_DISPLAY(d);

	switch (event->type)
	{
	case ClientMessage:
		if (event->xclient.message_type ==
			XInternAtom(d->display, "_BERYL_WOBBLY_SHIVER", 0))
		{
			CompOption o[1];

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


			wobblyShiver(d, NULL, 0, o, 1);

		}
		break;
	case PropertyNotify:
		if (event->xproperty.atom == d->winActiveAtom)
			activeWindow = d->activeWindow;
		break;
	case MapNotify:
		w = findWindowAtDisplay(d, event->xmap.window);
		if (w)
		{
			WOBBLY_WINDOW(w);

			if (ww->model)
			{
				modelInitObjects(ww->model,
								 WIN_X(w), WIN_Y(w), WIN_W(w), WIN_H(w));

				modelInitSprings(ww->model,
								 WIN_X(w), WIN_Y(w), WIN_W(w), WIN_H(w));
			}
		}
		break;
	default:
		if (event->type == d->xkbEvent)
		{
			XkbAnyEvent *xkbEvent = (XkbAnyEvent *) event;

			if (xkbEvent->xkb_type == XkbStateNotify)
			{
				XkbStateNotifyEvent *stateEvent =
						(XkbStateNotifyEvent *) event;
				unsigned int mods = 0xffffffff;

				if (wd->snapMask)
					mods = wd->snapMask;

				if ((stateEvent->mods & mods) == mods)
					wobblyEnableKeySnapping(d, NULL, 0, NULL, 0);
				else
					wobblyDisableKeySnapping(d, NULL, 0, NULL, 0);
			}
		}
		break;
	}

	UNWRAP(wd, d, handleEvent);
	(*d->handleEvent) (d, event);
	WRAP(wd, d, handleEvent, wobblyHandleEvent);

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

			if (ws->grabWindow &&
				(ws->moveWMask & ws->grabWindow->type) &&
				ws->opt[WOBBLY_SCREEN_OPTION_MAXIMIZE_EFFECT].value.b)
			{
				WOBBLY_WINDOW(ws->grabWindow);

				if (ww->state & MAXIMIZE_STATE)
				{
					WOBBLY_WINDOW(ws->grabWindow);

					if (ww->model && ww->grabbed)
					{
						int dx, dy;

						if (ww->state & CompWindowStateMaximizedHorzMask)
							dx = d->pointerX - d->lastPointerX;
						else
							dx = 0;

						if (ww->state & CompWindowStateMaximizedVertMask)
							dy = d->pointerY - d->lastPointerY;
						else
							dy = 0;

						ww->model->anchorObject->position.x += dx;
						ww->model->anchorObject->position.y += dy;

						ww->wobbly |= WobblyInitial;
						ws->wobblyWindows |= ww->wobbly;
					}
				}
			}
		}
		break;
	case PropertyNotify:
		if (event->xproperty.atom == d->winActiveAtom)
		{
			if (d->activeWindow != activeWindow)
			{
				w = findWindowAtDisplay(d, d->activeWindow);
				if (w && isWobblyWin(w))
				{
					WOBBLY_WINDOW(w);
					WOBBLY_SCREEN(w->screen);

					if ((ws->focusWMask & w->type) &&
						ws->focusEffect && wobblyEnsureModel(w))
					{
						switch (ws->focusEffect)
						{
						case WobblyEffectShiver:
							modelAdjustObjectsForShiver
									(ww->model,
									 WIN_X(w), WIN_Y(w), WIN_W(w), WIN_H(w));
						default:
							break;
						}

						ww->friction =
								ws->
								opt[WOBBLY_SCREEN_OPTION_FOCUS_FRICTION].
								value.f;
						ww->spring_k =
								ws->opt[WOBBLY_SCREEN_OPTION_FOCUS_SPRING_K].
								value.f;

						ww->wobbly |= WobblyInitial;
						ws->wobblyWindows |= ww->wobbly;
					}
				}
			}
		}
		else if (event->xproperty.atom == wd->wmHintsAtom)
		{
			w = findWindowAtDisplay(d, event->xproperty.window);
			if (w)
			{
				XWMHints *xwmh =
						XGetWMHints(w->screen->display->display, w->id);
				if (xwmh)
				{
					WOBBLY_WINDOW(w);
					WOBBLY_SCREEN(w->screen);

					if (isWobblyWin(w)
						&& (xwmh->flags & XUrgencyHint)
						&& wobblyEnsureModel(w)
						&& ws->
						opt[WOBBLY_SCREEN_OPTION_URGENT_EFFECT].value.b)
					{
						Spring *s;
						int i;
						float vf;

						for (i = 0; i < ww->model->numSprings; i++)
						{
							s = &ww->model->springs[i];
							vf = ws->
									opt
									[WOBBLY_SCREEN_OPTION_URGENT_VELOCITY].
									value.f / 30;

							if (ws->
								opt[WOBBLY_SCREEN_OPTION_URGENT_IN].value.b)
							{
								s->b->velocity.x -= s->offset.x * vf;
								s->b->velocity.y -= s->offset.y * vf;
								s->a->velocity.x += s->offset.x * vf;
								s->a->velocity.y += s->offset.y * vf;
							}
							else
							{
								s->b->velocity.x += s->offset.x * vf;
								s->b->velocity.y += s->offset.y * vf;
								s->a->velocity.x -= s->offset.x * vf;
								s->a->velocity.y -= s->offset.y * vf;
							}
						}

						if (!ws->
							opt[WOBBLY_SCREEN_OPTION_URGENT_UNIFORM].value.b)
							modelSetMiddleAnchor
									(ww->model,
									 WIN_X(w), WIN_Y(w), WIN_W(w), WIN_H(w));

						else if (ww->model->anchorObject)
							ww->model->anchorObject->immobile = FALSE;


						modelAdjustObjectsForShiver
								(ww->model, WIN_X(w),
								 WIN_Y(w), WIN_W(w), WIN_H(w));

						ww->friction =
								ws->
								opt[WOBBLY_SCREEN_OPTION_URGENT_FRICTION].
								value.f;
						ww->spring_k =
								ws->opt[WOBBLY_SCREEN_OPTION_URGENT_SPRING_K].
								value.f;

						ww->wobbly |= WobblyInitial;
						ws->wobblyWindows |= ww->wobbly;
					}
					else
					{
						ww->friction =
								ws->
								opt[WOBBLY_SCREEN_OPTION_MOVE_FRICTION].value.
								f;
						ww->spring_k =
								ws->opt[WOBBLY_SCREEN_OPTION_MOVE_SPRING_K].
								value.f;
					}
					XFree(xwmh);
				}
			}
		}
	default:
		break;
	}
}

static Bool wobblyDamageWindowRect(CompWindow * w, Bool initial, BoxPtr rect)
{
	Bool status;

	WOBBLY_SCREEN(w->screen);

	if (!initial)
	{
		WOBBLY_WINDOW(w);

		if (ww->wobbly == WobblyForce)
		{
			REGION region;

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

			region.extents.x1 = ww->model->topLeft.x;
			region.extents.y1 = ww->model->topLeft.y;
			region.extents.x2 = ww->model->bottomRight.x + 0.5f;
			region.extents.y2 = ww->model->bottomRight.y + 0.5f;

			damageScreenRegion(w->screen, &region);

			return TRUE;
		}
	}

	UNWRAP(ws, w->screen, damageWindowRect);
	status = (*w->screen->damageWindowRect) (w, initial, rect);
	WRAP(ws, w->screen, damageWindowRect, wobblyDamageWindowRect);

	if (initial)
	{
		if (isWobblyWin(w))
		{
			WOBBLY_WINDOW(w);
			WOBBLY_SCREEN(w->screen);

			if (ws->opt[WOBBLY_SCREEN_OPTION_MAXIMIZE_EFFECT].value.b)
				wobblyEnsureModel(w);

			if ((ws->mapWMask & w->type) &&
				ws->mapEffect && wobblyEnsureModel(w))
			{
				switch (ws->mapEffect)
				{
				case WobblyEffectShiver:
					modelAdjustObjectsForShiver(ww->
												model,
												WIN_X
												(w),
												WIN_Y(w), WIN_W(w), WIN_H(w));
				default:
					break;
				}

				ww->friction =
						ws->opt[WOBBLY_SCREEN_OPTION_MAP_FRICTION].value.f;
				ww->spring_k =
						ws->opt[WOBBLY_SCREEN_OPTION_MAP_SPRING_K].value.f;

				ww->wobbly |= WobblyInitial;
				ws->wobblyWindows |= ww->wobbly;
			}
		}
	}

	return status;
}

static void wobblyWindowResizeNotify(CompWindow * w, int dx, int dy,
									 int dwidth, int dheight, Bool preview)
{
	WOBBLY_SCREEN(w->screen);
	WOBBLY_WINDOW(w);

	if (!preview && ws->opt[WOBBLY_SCREEN_OPTION_MAXIMIZE_EFFECT].value.b &&
		isWobblyWin(w) && ((w->state | ww->state) & MAXIMIZE_STATE))
	{
		ww->state &= ~MAXIMIZE_STATE;
		ww->state |= w->state & MAXIMIZE_STATE;
		if (wobblyEnsureModel(w))
		{
			if (w->state & MAXIMIZE_STATE)
			{
				if (!ww->grabbed && ww->model->anchorObject)
				{
					ww->model->anchorObject->immobile = FALSE;
					ww->model->anchorObject = NULL;
				}

				modelAddEdgeAnchors(ww->model,
									WIN_X(w), WIN_Y(w), WIN_W(w), WIN_H(w));
			}
			else
			{
				modelRemoveEdgeAnchors(ww->model,
									   WIN_X(w), WIN_Y(w),
									   WIN_W(w), WIN_H(w));
				modelSetMiddleAnchor(ww->model,
									 WIN_X(w), WIN_Y(w), WIN_W(w), WIN_H(w));
			}

			modelInitSprings(ww->model,
							 WIN_X(w), WIN_Y(w), WIN_W(w), WIN_H(w));

			ww->friction =
					ws->opt[WOBBLY_SCREEN_OPTION_MAXIMIZE_FRICTION].value.f;
			ww->spring_k =
					ws->opt[WOBBLY_SCREEN_OPTION_MAXIMIZE_SPRING_K].value.f;

			ww->wobbly |= WobblyInitial;
			ws->wobblyWindows |= ww->wobbly;

			damagePendingOnScreen(w->screen);
		}
	}
	else if (!preview && ww->model)
	{
		if (!ww->wobbly)
			modelInitObjects(ww->model,
							 WIN_X(w), WIN_Y(w), WIN_W(w), WIN_H(w));

		modelInitSprings(ww->model, WIN_X(w), WIN_Y(w), WIN_W(w), WIN_H(w));
	}

	/* update grab */
	if (!preview && ww->model && ww->grabbed)
	{
		if (ww->model->anchorObject)
			ww->model->anchorObject->immobile = FALSE;

		ww->model->anchorObject = modelFindNearestObject(ww->model,
														 w->screen->display->
														 pointerX,
														 w->screen->display->
														 pointerY);

		ww->model->anchorObject->immobile = TRUE;

		modelAdjustObjectPosition(ww->model, ww->model->anchorObject,
								  WIN_X(w), WIN_Y(w), WIN_W(w), WIN_H(w));
	}

	UNWRAP(ws, w->screen, windowResizeNotify);
	(*w->screen->windowResizeNotify) (w, dx, dy, dwidth, dheight, preview);
	WRAP(ws, w->screen, windowResizeNotify, wobblyWindowResizeNotify);
}

static void
wobblyWindowMoveNotify(CompWindow * w, int dx, int dy, Bool immediate)
{
	WOBBLY_SCREEN(w->screen);
	WOBBLY_WINDOW(w);

	if (ww->model)
	{
		if (ww->grabbed && !immediate)
		{
			if (ww->state & MAXIMIZE_STATE)
			{
				int i;

				for (i = 0; i < ww->model->numObjects; i++)
				{
					if (ww->model->objects[i].immobile)
					{
						ww->model->objects[i].position.x += dx;
						ww->model->objects[i].position.y += dy;
					}
				}
			}
			else
			{
				ww->model->anchorObject->position.x += dx;
				ww->model->anchorObject->position.y += dy;
			}

			ww->friction =
					ws->opt[WOBBLY_SCREEN_OPTION_MOVE_FRICTION].value.f;
			ww->spring_k =
					ws->opt[WOBBLY_SCREEN_OPTION_MOVE_SPRING_K].value.f;

			ww->wobbly |= WobblyInitial;
			ws->wobblyWindows |= ww->wobbly;
		}
		else
		{
			modelMove(ww->model, dx, dy);

		}

	}

	UNWRAP(ws, w->screen, windowMoveNotify);
	(*w->screen->windowMoveNotify) (w, dx, dy, immediate);
	WRAP(ws, w->screen, windowMoveNotify, wobblyWindowMoveNotify);
}

static void
wobblyWindowGrabNotify(CompWindow * w,
					   int x, int y, unsigned int state, unsigned int mask)
{
	WOBBLY_SCREEN(w->screen);
	WOBBLY_DISPLAY(w->screen->display);

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

	if (ws->opt[WOBBLY_SCREEN_OPTION_MOVE_EFFECT].value.b)
	{
		if ((mask & CompWindowGrabButtonMask) &&
			(ws->moveWMask & w->type) && isWobblyWin(w))
		{
			WOBBLY_WINDOW(w);

			if (wobblyEnsureModel(w))
			{
				Spring *s;
				int i;

				if (ws->opt[WOBBLY_SCREEN_OPTION_MAXIMIZE_EFFECT].value.b)
				{
					if (w->state & MAXIMIZE_STATE)
					{
						modelAddEdgeAnchors(ww->
											model,
											WIN_X
											(w),
											WIN_Y(w), WIN_W(w), WIN_H(w));
					}
					else
					{
						modelRemoveEdgeAnchors(ww->
											   model,
											   WIN_X
											   (w),
											   WIN_Y(w), WIN_W(w), WIN_H(w));

						if (ww->model->anchorObject)
							ww->model->anchorObject->immobile = FALSE;
					}
				}
				else
				{
					if (ww->model->anchorObject)
						ww->model->anchorObject->immobile = FALSE;
				}

				ww->model->anchorObject =
						modelFindNearestObject(ww->model, x, y);
				ww->model->anchorObject->immobile = TRUE;

				ww->grabbed = TRUE;

				if (mask & CompWindowGrabMoveMask)
				{
					modelDisableSnapping(w, ww->model);
					if (wd->snapping)
						modelUpdateSnapping(w, ww->model);
				}

				if (ws->grabWMask & w->type)
				{
					for (i = 0; i < ww->model->numSprings; i++)
					{
						s = &ww->model->springs[i];

						if (s->a == ww->model->anchorObject)
						{
							s->b->velocity.x -= s->offset.x * 0.05f;
							s->b->velocity.y -= s->offset.y * 0.05f;
						}
						else if (s->b == ww->model->anchorObject)
						{
							s->a->velocity.x += s->offset.x * 0.05f;
							s->a->velocity.y += s->offset.y * 0.05f;
						}
					}

					ww->friction =
							ws->opt[WOBBLY_SCREEN_OPTION_GRAB_FRICTION].value.
							f;
					ww->spring_k =
							ws->opt[WOBBLY_SCREEN_OPTION_GRAB_SPRING_K].value.
							f;

					ww->wobbly |= WobblyInitial;
					ws->wobblyWindows |= ww->wobbly;
				}
			}
		}
	}

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

static void wobblyWindowUngrabNotify(CompWindow * w)
{
	WOBBLY_SCREEN(w->screen);
	WOBBLY_WINDOW(w);

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

	if (ww->grabbed)
	{
		if (ww->model)
		{
			if (ww->model->anchorObject)
				ww->model->anchorObject->immobile = FALSE;

			ww->model->anchorObject = NULL;

			if (ws->opt[WOBBLY_SCREEN_OPTION_MAXIMIZE_EFFECT].value.b)
			{
				if (ww->state & MAXIMIZE_STATE)
					modelAddEdgeAnchors(ww->model,
										WIN_X(w),
										WIN_Y(w), WIN_W(w), WIN_H(w));
			}

			if (ws->opt[WOBBLY_SCREEN_OPTION_USE_RELEASE].value.b)
			{
				ww->friction =
						ws->opt[WOBBLY_SCREEN_OPTION_RELEASE_FRICTION].value.
						f;
				ww->spring_k =
						ws->opt[WOBBLY_SCREEN_OPTION_RELEASE_SPRING_K].value.
						f;
			}

			ww->wobbly |= WobblyInitial;
			ws->wobblyWindows |= ww->wobbly;
		}

		ww->grabbed = FALSE;
	}

	UNWRAP(ws, w->screen, windowUngrabNotify);
	(*w->screen->windowUngrabNotify) (w);
	WRAP(ws, w->screen, windowUngrabNotify, wobblyWindowUngrabNotify);
}


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

	WOBBLY_SCREEN(s);

	if (ws->wobblyWindows)
		mask |= PAINT_SCREEN_WITH_TRANSFORMED_WINDOWS_MASK;

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

	return status;
}

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

	WOBBLY_DISPLAY(display);

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

	switch (index)
	{
	case WOBBLY_DISPLAY_OPTION_DEFAULT_SNAP:
		if (compSetBoolOption(o, value))
		{
			if (value->b)
				wobblyEnableSnapping(display, NULL, 0, NULL, 0);
			else
				wobblyDisableSnapping(display, NULL, 0, NULL, 0);
			return TRUE;
		}
		break;
	case WOBBLY_DISPLAY_OPTION_SNAP:
		if (compSetOptionList(o, value))
		{
			int i;

			wd->snapMask = 0;
			for (i = 0; i < o->value.list.nValue; i++)
			{
				int j;

				for (j = 0; j < nMods; j++)
					if (strcmp(o->value.list.value[i].s, Mods[j]) == 0)
						wd->snapMask |= ModMask[j];
			}
			return TRUE;
		}
		break;
	case WOBBLY_DISPLAY_OPTION_SHIVER:
		if (setDisplayAction(display, o, value))
			return TRUE;
		break;
	default:
		break;
	}

	return FALSE;
}

static void wobblyDisplayInitOptions(WobblyDisplay * wd)
{
	CompOption *o;
	CompOptionValue *v;

	o = &wd->opt[WOBBLY_DISPLAY_OPTION_SNAP];
	o->advanced = False;
	o->name = "snap";
	o->group = N_("Basic");
	o->subGroup = N_("Snap");
	o->displayHints = "";
	o->shortDesc = N_("Snap Windows Modifier");
	o->longDesc = N_("Press all of the selected keys while "
					 "moving Windows to cause them to Snap");
	o->type = CompOptionTypeList;
	v = malloc(sizeof(CompOptionValue));
	o->value.list.type = CompOptionTypeString;
	o->value.list.nValue = 1;
	o->value.list.value = v;
	v->s = strdup(N_("Shift"));
	o->rest.s.nString = nMods;
	o->rest.s.string = Mods;


	o = &wd->opt[WOBBLY_DISPLAY_OPTION_DEFAULT_SNAP];
	o->advanced = False;
	o->name = "default_snap";
	o->group = N_("Basic");
	o->subGroup = N_("Snap");
	o->displayHints = "";
	o->shortDesc = N_("Default Snapping to On");
	o->longDesc = N_("Reverse meaning of Snapping toggle.");
	o->type = CompOptionTypeBool;
	o->value.b = WOBBLY_DEFAULT_SNAP_DEFAULT;

	o = &wd->opt[WOBBLY_DISPLAY_OPTION_SHIVER];
	o->advanced = False;
	o->name = "shiver";
	o->group = N_("Basic");
	o->subGroup = N_("Shiver");
	o->displayHints = "";
	o->shortDesc = N_("Shiver");
	o->longDesc = N_("Make window Shiver.");
	o->type = CompOptionTypeAction;
	o->value.action.initiate = wobblyShiver;
	o->value.action.terminate = 0;
	o->value.action.bell = FALSE;
	o->value.action.edgeMask = 0;
	o->value.action.state = CompActionStateInitBell;
	o->value.action.state |= CompActionStateInitKey;
	o->value.action.state |= CompActionStateInitButton;
	o->value.action.type = 0;
}

static CompOption *wobblyGetDisplayOptions(CompDisplay * display, int *count)
{
	if (display)
	{
		WOBBLY_DISPLAY(display);

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

		wobblyDisplayInitOptions(wd);
		*count = NUM_OPTIONS(wd);
		return wd->opt;
	}
}

static Bool wobblyInitDisplay(CompPlugin * p, CompDisplay * d)
{
	WobblyDisplay *wd;

	wd = malloc(sizeof(WobblyDisplay));
	if (!wd)
		return FALSE;

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

	wd->wmHintsAtom = XInternAtom(d->display, "WM_HINTS", FALSE);

	WRAP(wd, d, handleEvent, wobblyHandleEvent);

	wd->snapping = FALSE;
	wd->snapMask = ShiftMask;

	wobblyDisplayInitOptions(wd);

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

	return TRUE;
}

static void wobblyFiniDisplay(CompPlugin * p, CompDisplay * d)
{
	WOBBLY_DISPLAY(d);

	freeScreenPrivateIndex(d, wd->screenPrivateIndex);

	UNWRAP(wd, d, handleEvent);

	free(wd);
}

static Bool wobblyInitScreen(CompPlugin * p, CompScreen * s)
{
	WobblyScreen *ws;

	WOBBLY_DISPLAY(s->display);

	int i;

	ws = malloc(sizeof(WobblyScreen));
	if (!ws)
		return FALSE;

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

	ws->wobblyWindows = FALSE;

	ws->mapEffect = WobblyEffectShiver;
	ws->focusEffect = WobblyEffectNone;

	ws->grabMask = 0;
	ws->grabWindow = NULL;

	ws->edgeDistance = WOBBLY_EDGE_DISTANCE_DEFAULT;
	ws->edgeVelocity = WOBBLY_EDGE_VELOCITY_DEFAULT;

	wobblyScreenInitOptions(ws);

	/* init the focus effect */
	for (i = 0; i < NUM_EFFECT; i++)
	{
		if (strcmp
			(ws->opt[WOBBLY_SCREEN_OPTION_FOCUS_EFFECT].value.s,
			 effectName[i]) == 0)
		{
			ws->focusEffect = effectType[i];
			break;
		}
	}


	WRAP(ws, s, preparePaintScreen, wobblyPreparePaintScreen);
	WRAP(ws, s, donePaintScreen, wobblyDonePaintScreen);
	WRAP(ws, s, paintScreen, wobblyPaintScreen);
	WRAP(ws, s, paintWindow, wobblyPaintWindow);
	WRAP(ws, s, damageWindowRect, wobblyDamageWindowRect);
	WRAP(ws, s, addWindowGeometry, wobblyAddWindowGeometry);
	WRAP(ws, s, drawWindowGeometry, wobblyDrawWindowGeometry);
	WRAP(ws, s, windowResizeNotify, wobblyWindowResizeNotify);
	WRAP(ws, s, windowMoveNotify, wobblyWindowMoveNotify);
	WRAP(ws, s, windowGrabNotify, wobblyWindowGrabNotify);
	WRAP(ws, s, windowUngrabNotify, wobblyWindowUngrabNotify);

	s->privates[wd->screenPrivateIndex].ptr = ws;

	return TRUE;
}

static void wobblyFiniScreen(CompPlugin * p, CompScreen * s)
{
	WOBBLY_SCREEN(s);

	freeWindowPrivateIndex(s, ws->windowPrivateIndex);

	free(ws->opt[WOBBLY_SCREEN_OPTION_MAP_EFFECT].value.s);
	free(ws->opt[WOBBLY_SCREEN_OPTION_FOCUS_EFFECT].value.s);

	UNWRAP(ws, s, preparePaintScreen);
	UNWRAP(ws, s, donePaintScreen);
	UNWRAP(ws, s, paintScreen);
	UNWRAP(ws, s, paintWindow);
	UNWRAP(ws, s, damageWindowRect);
	UNWRAP(ws, s, addWindowGeometry);
	UNWRAP(ws, s, drawWindowGeometry);
	UNWRAP(ws, s, windowResizeNotify);
	UNWRAP(ws, s, windowMoveNotify);
	UNWRAP(ws, s, windowGrabNotify);
	UNWRAP(ws, s, windowUngrabNotify);

	free(ws);
}

static Bool wobblyInitWindow(CompPlugin * p, CompWindow * w)
{
	WobblyWindow *ww;

	WOBBLY_SCREEN(w->screen);

	ww = malloc(sizeof(WobblyWindow));
	if (!ww)
		return FALSE;

	ww->model = 0;
	ww->wobbly = 0;
	ww->grabbed = FALSE;
	ww->state = w->state;
	ww->friction = 3.0;
	ww->spring_k = 8.0;

	w->privates[ws->windowPrivateIndex].ptr = ww;

	if (w->mapNum && ws->opt[WOBBLY_SCREEN_OPTION_MAXIMIZE_EFFECT].value.b)
	{
		if (isWobblyWin(w))
			wobblyEnsureModel(w);
	}

	return TRUE;
}

static void wobblyFiniWindow(CompPlugin * p, CompWindow * w)
{
	WOBBLY_WINDOW(w);

	if (ww->model)
	{
		free(ww->model->objects);
		free(ww->model);
	}

	free(ww);
}

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

	return TRUE;
}

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

CompPluginDep wobblyDeps[] = {
	{CompPluginRuleAfter, "decoration"}
	,
};

CompPluginFeature wobblyFeatures[] = {
	{"edgeresistance"}
};

CompPluginVTable wobblyVTable = {
	"wobbly",
	N_("Wobbly Windows"),
	N_("Use spring model for wobbly window effect"),
	wobblyInit,
	wobblyFini,
	wobblyInitDisplay,
	wobblyFiniDisplay,
	wobblyInitScreen,
	wobblyFiniScreen,
	wobblyInitWindow,
	wobblyFiniWindow,
	wobblyGetDisplayOptions,
	wobblySetDisplayOption,
	wobblyGetScreenOptions,
	wobblySetScreenOption,
	wobblyDeps,
	sizeof(wobblyDeps) / sizeof(wobblyDeps[0]),
	wobblyFeatures,
	sizeof(wobblyFeatures) / sizeof(wobblyFeatures[0]),
	BERYL_ABI_INFO,
	"beryl-plugins",
	"effects",
	0,
	0,
	True,
};

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


syntax highlighted by Code2HTML, v. 0.9.1