Logo Search packages:      
Sourcecode: beryl-plugins version File versions

wobbly.c

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

Generated by  Doxygen 1.6.0   Back to index