/**
 *
 * Compiz expo plugin
 *
 * expo.c
 *
 * Copyright (c) 2006 Robert Carr <racarr@beryl-project.org>
 *
 * Authors:
 * Robert Carr <racarr@beryl-project.org>
 * Dennis Kasprzyk <onestone@beryl-project.org>
 *
 * 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.
 *
 **/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <sys/time.h>

#include <compiz.h>
#include "expo_options.h"

#include <GL/glu.h>

#define PI 3.14159265359f

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

static int displayPrivateIndex;

typedef enum
{
    DnDNone = 0,
    DnDDuring,
    DnDStart
} DnDState;

typedef enum
{
    VPUpdateNone = 0,
    VPUpdateMouseOver,
    VPUpdatePrevious
} VPUpdateMode;

typedef struct _ExpoDisplay
{
    int screenPrivateIndex;

    HandleEventProc handleEvent;

    KeyCode leftKey;
    KeyCode rightKey;
    KeyCode upKey;
    KeyCode downKey;
} ExpoDisplay;

typedef struct _ExpoScreen
{
    DonePaintScreenProc        donePaintScreen;
    PaintOutputProc            paintOutput;
    PaintScreenProc            paintScreen;
    PreparePaintScreenProc     preparePaintScreen;
    PaintTransformedOutputProc paintTransformedOutput;
    PaintWindowProc            paintWindow;
    DrawWindowProc             drawWindow;
    DamageWindowRectProc       damageWindowRect;

    /*  Used for expo zoom animation */
    float expoCam;
    Bool  expoActive;
    /* In expo mode? */
    Bool  expoMode;

    /* For expo grab */
    int grabIndex;

    GLint viewport[4];

    /* Window being dragged in expo mode */
    DnDState   dndState;
    CompWindow *dndWindow;
    
    int prevCursorX, prevCursorY;
    int newCursorX, newCursorY;

    int origVX;
    int origVY;
    int selectedVX;
    int selectedVY;

    VPUpdateMode vpUpdateMode;

    Bool anyClick;

    unsigned int clickTime;
    Bool         doubleClick;
    
} ExpoScreen;

typedef struct _xyz_tuple
{
    float x, y, z;
}
Point3d;

/* Helpers */
#define GET_EXPO_DISPLAY(d) \
    ((ExpoDisplay *) (d)->privates[displayPrivateIndex].ptr)
#define EXPO_DISPLAY(d) \
    ExpoDisplay *ed = GET_EXPO_DISPLAY(d);

#define GET_EXPO_SCREEN(s, ed) \
    ((ExpoScreen *) (s)->privates[(ed)->screenPrivateIndex].ptr)
#define EXPO_SCREEN(s) \
    ExpoScreen *es = GET_EXPO_SCREEN(s, GET_EXPO_DISPLAY(s->display))

#define sigmoid(x) (1.0f / (1.0f + exp (-5.5f * 2 * ((x) - 0.5))))
#define sigmoidProgress(x) ((sigmoid (x) - sigmoid (0)) / \
			    (sigmoid (1) - sigmoid (0)))

static void
expoMoveFocusViewport (CompScreen *s,
		       int        dx,
		       int        dy)
{
    EXPO_SCREEN (s);

    es->selectedVX += dx;
    es->selectedVY += dy;

    es->selectedVX = MIN (s->hsize - 1, es->selectedVX);
    es->selectedVX = MAX (0, es->selectedVX);
    es->selectedVY = MIN (s->vsize - 1, es->selectedVY);
    es->selectedVY = MAX (0, es->selectedVY);

    damageScreen (s);
}

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

    for (s = d->screens; s; s = s->next)
    {
	EXPO_SCREEN (s);

	if (!es->expoMode)
	    continue;

	es->expoMode = FALSE;

	if (es->dndWindow)
	    syncWindowPosition (es->dndWindow);

	if (state & CompActionStateCancel)
	    es->vpUpdateMode = VPUpdatePrevious;
	else
	    es->vpUpdateMode = VPUpdateMouseOver;

	es->dndState  = DnDNone;
	es->dndWindow = 0;

	damageScreen (s);
	focusDefaultWindow (s->display);
    }

    return TRUE;
}

static Bool
expoExpo (CompDisplay     *d,
	  CompAction      *action,
	  CompActionState state,
	  CompOption      *option,
	  int             nOption)
{
    CompScreen *s;
    Window     xid;

    xid = getIntOptionNamed (option, nOption, "root", 0);
    s   = findScreenAtDisplay (d, xid);

    if (s)
    {
    	EXPO_SCREEN (s);

	if (otherScreenGrabExist (s, "expo", 0))
	    return FALSE;

	if (!es->expoMode)
	{
	    if (!es->grabIndex)
		es->grabIndex = pushScreenGrab (s, None, "expo");

	    es->expoMode = TRUE;
	    es->anyClick = FALSE;
    	    es->dndState  = DnDNone;
    	    es->dndWindow = None;

	    es->selectedVX = es->origVX = s->x;
	    es->selectedVY = es->origVY = s->y;

	    damageScreen (s);
	}
	else
	{
	    expoTermExpo (d, action, state, option, nOption);
	}

	return TRUE;
    }

    return FALSE;
}

static void
expoFinishWindowMovement (CompWindow *w)
{
    CompScreen *s = w->screen;

    EXPO_SCREEN (s);

    syncWindowPosition (w);
    (*s->windowUngrabNotify) (w);

    moveScreenViewport (s, s->x - es->selectedVX,
    			s->y - es->selectedVY, TRUE);

    /* update saved window attributes in case we moved the
       window to a new viewport */
    if (w->saveMask & CWX)
    {
	w->saveWc.x = w->saveWc.x % s->width;
	if (w->saveWc.x < 0)
	    w->saveWc.x += s->width;
    }
    if (w->saveMask & CWY)
    {
	w->saveWc.y = w->saveWc.y % s->height;
	if (w->saveWc.y < 0)
	    w->saveWc.y += s->height;
    }

    /* update window attibutes to make sure a
       moved maximized window is properly snapped
       to the work area */
    if (w->state & MAXIMIZE_STATE)
    {
    	int lastOutput;
	int centerX, centerY;

	/* make sure we snap to the correct output */
	lastOutput = s->currentOutputDev;
	centerX = (WIN_X (w) + WIN_W (w) / 2) % s->width;
	if (centerX < 0)
	    centerX += s->width;
	centerY = (WIN_Y (w) + WIN_H (w) / 2) % s->height;
	if (centerY < 0)
	    centerY += s->height;

	s->currentOutputDev = outputDeviceForPoint (s, centerX, centerY);

	updateWindowAttributes (w, CompStackingUpdateModeNone);

	s->currentOutputDev = lastOutput;
    }
}

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

    switch (event->type)
    {
    case KeyPress:
	s = findScreenAtDisplay (d, event->xkey.root);

	if (s)
	{
	    EXPO_SCREEN (s);

	    if (es->expoMode)
	    {
		if (event->xkey.keycode == ed->leftKey)
		    expoMoveFocusViewport (s, -1, 0);
		else if (event->xkey.keycode == ed->rightKey)
		    expoMoveFocusViewport (s, 1, 0);
		else if (event->xkey.keycode == ed->upKey)
		    expoMoveFocusViewport (s, 0, -1);
		else if (event->xkey.keycode == ed->downKey)
		    expoMoveFocusViewport (s, 0, 1);
	    }
	}

	break;

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

	if (s)
	{
	    EXPO_SCREEN (s);

	    if (es->expoMode)
	    {
		switch (event->xbutton.button) {
		case Button1:
		    es->dndState = DnDStart;
		    if (es->clickTime == 0)
		    {
			es->clickTime = event->xbutton.time;
		    }
		    else if (event->xbutton.time - es->clickTime <=
			     expoGetDoubleClickTime (d))
		    {
			es->doubleClick = TRUE;
		    }
		    else
		    {
			es->clickTime   = event->xbutton.time;
			es->doubleClick = FALSE;
		    }
		    break;
		case Button3:
		    {
    			CompAction *action;
			
    			action = expoGetExpo (d);
    			expoTermExpo (d, action, 0, NULL, 0);
			es->anyClick = TRUE;
		    }
		    break;
		case Button4:
		    {
			int newX = es->selectedVX - 1;
			int newY = es->selectedVY;

			if (newX < 0)
			{
			    newX = s->hsize - 1;
			    newY = newY - 1;
			    if (newY < 0)
				newY = s->vsize - 1;
			}

			expoMoveFocusViewport (s, newX - es->selectedVX,
					       newY - es->selectedVY);
		    }
		    break;
		case Button5:
		    {
			int newX = es->selectedVX + 1;
			int newY = es->selectedVY;

			if (newX >= s->hsize)
			{
			    newX = 0;
			    newY = newY + 1;
			    if (newY >= s->vsize)
				newY = 0;
			}

			expoMoveFocusViewport (s, newX - es->selectedVX,
					       newY - es->selectedVY);
		    }
		    break;
		default:
    		    es->anyClick = TRUE;
		    break;
		}
    		damageScreen (s);
	    }
	}
	break;

    case ButtonRelease:
	s = findScreenAtDisplay (d, event->xbutton.root);

	if (s)
	{
	    EXPO_SCREEN (s);

	    if (es->dndState == DnDDuring || es->dndState == DnDStart)
	    {
		if (es->dndWindow)
		    expoFinishWindowMovement (es->dndWindow);

		es->dndState = DnDNone;
		es->dndWindow = NULL;
	    }
	    
	    if (es->expoMode)
	    {
		if (event->xbutton.button == Button1)
		{
		    if (event->xbutton.time - es->clickTime >
			     expoGetDoubleClickTime (d))
		    {
			es->clickTime   = 0;
			es->doubleClick = FALSE;
		    }
		    else if (es->doubleClick)
		    {
			CompAction *action;
			
			es->clickTime   = 0;
			es->doubleClick = FALSE;
			
    			action = expoGetExpo (d);
    			expoTermExpo (d, action, 0, NULL, 0);
			es->anyClick = TRUE;
		    }
		    break;
		}
	    }
	}
	break;
    }

    UNWRAP (ed, d, handleEvent);
    (*d->handleEvent) (d, event);
    WRAP (ed, d, handleEvent, expoHandleEvent);
}

static void
invertTransformedVertex (CompScreen              *s,
			 const ScreenPaintAttrib *sAttrib,
			 const CompTransform     *transform,
			 CompOutput              *output,
			 int                     vertex[2])
{
    CompTransform sTransform = *transform;
    GLdouble p1[3], p2[3], v[3], alpha;
    GLdouble mvm[16], pm[16];
    int      i;

    EXPO_SCREEN (s);

    (*s->applyScreenTransform) (s, sAttrib, output, &sTransform);
    transformToScreenSpace (s, output, -sAttrib->zTranslate, &sTransform);

    glGetIntegerv (GL_VIEWPORT, es->viewport);

    for (i = 0; i < 16; i++)
    {
	mvm[i] = sTransform.m[i];
	pm[i] = s->projection[i];
    }

    gluUnProject (vertex[0], s->height - vertex[1], 0, mvm, pm,
		  es->viewport, &p1[0], &p1[1], &p1[2]);
    gluUnProject (vertex[0], s->height - vertex[1], -1.0, mvm, pm,
		  es->viewport, &p2[0], &p2[1], &p2[2]);

    for (i = 0; i < 3; i++)
	v[i] = p1[i] - p2[i];

    alpha = -p1[2] / v[2];

    vertex[0] = ceil (p1[0] + (alpha * v[0]));
    vertex[1] = ceil (p1[1] + (alpha * v[1]));
}

static Bool
expoDamageWindowRect (CompWindow *w,
		      Bool       initial,
		      BoxPtr     rect)
{
    Bool status;
    EXPO_SCREEN (w->screen);

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

    if (es->expoCam > 0.0f)
	damageScreen (w->screen);

    return status;
}

static void
expoPaintScreen (CompScreen   *s,
		 CompOutput   *outputs,
		 int          numOutputs,
		 unsigned int mask)
{
    EXPO_SCREEN (s);

    if (es->expoCam > 0.0 && numOutputs > 1 &&
        expoGetMultioutputMode (s->display) == MultioutputModeOneBigWall)
    {
	outputs = &s->fullscreenOutput;
	numOutputs = 1;
    }

    UNWRAP (es, s, paintScreen);
    (*s->paintScreen) (s, outputs, numOutputs, mask);
    WRAP (es, s, paintScreen, expoPaintScreen);
}

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

    EXPO_SCREEN (s);

    if (es->expoCam > 0.0)
	mask |= PAINT_SCREEN_TRANSFORMED_MASK | PAINT_SCREEN_CLEAR_MASK;

    UNWRAP (es, s, paintOutput);
    status = (*s->paintOutput) (s, sAttrib, transform, region, output, mask);
    WRAP (es, s, paintOutput, expoPaintOutput);

    return status;
}

static void
expoPreparePaintScreen (CompScreen *s,
			int        ms)
{
    EXPO_SCREEN (s);

    float val = ((float) ms / 1000.0) / expoGetZoomTime (s->display);

    if (es->expoMode)
	es->expoCam = MIN (1.0, es->expoCam + val);
    else
	es->expoCam = MAX (0.0, es->expoCam - val);

    UNWRAP (es, s, preparePaintScreen);
    (*s->preparePaintScreen) (s, ms);
    WRAP (es, s, preparePaintScreen, expoPreparePaintScreen);
}

static void
expoPaintWall (CompScreen              *s,
	       const ScreenPaintAttrib *sAttrib,
	       const CompTransform     *transform,
	       Region                  region,
	       CompOutput              *output,
	       unsigned int            mask,
	       Bool                    reflection)
{
    EXPO_SCREEN (s);

    CompTransform sTransformW, sTransform = *transform;
    int           i, j;
    int           oldFilter = s->display->textureFilter;

    int origVX = s->x;
    int origVY = s->y;

    float sx = (float)s->width / output->width;
    float sy = (float)s->height / output->height;
    float biasZ;
    float oScale, rotation = 0.0f, progress;
    float aspectX = 1.0f, aspectY = 1.0f;
    float camX, camY, camZ;

    /* amount of gap between viewports */
    const float gapY = 0.01f * es->expoCam; 
    const float gapX = 0.01f * s->height / s->width * es->expoCam;

    /* Zoom animation stuff */
    /* camera position for the selected viewport */
    Point3d vpCamPos   = { 0, 0, 0 };

    /* camera position during expo mode */
    Point3d expoCamPos = { 0, 0, 0 };

    vpCamPos.x = ((s->x * sx) + 0.5 +
		 (output->region.extents.x1 / output->width)) -
		 (s->hsize * 0.5 * sx) + gapX * (s->x);
    vpCamPos.y = -((s->y * sy) + 0.5 +
		 ( output->region.extents.y1 / output->height)) +
		 (s->vsize * 0.5 * sy) - gapY * (s->y);
    vpCamPos.z = 0;

    if (expoGetRotate (s->display) || expoGetReflection (s->display))
	biasZ = MAX (s->hsize * sx, s->vsize * sy) *
		(0.15 + expoGetDistance (s->display));
    else
	biasZ = MAX (s->hsize * sx, s->vsize * sy) *
		expoGetDistance (s->display);

    expoCamPos.x = gapX * (s->hsize - 1) * 0.5;
    expoCamPos.y = -gapY * (s->vsize - 1) * 0.5;
    expoCamPos.z = -DEFAULT_Z_CAMERA + DEFAULT_Z_CAMERA *
		   (MAX (s->hsize + (s->hsize - 1) * gapX,
			 s->vsize + (s->vsize - 1) * gapY) + biasZ);

    progress = sigmoidProgress (es->expoCam);

    /* interpolate between vpCamPos and expoCamPos */
    camX = vpCamPos.x * (1 - progress) + expoCamPos.x * progress;
    camY = vpCamPos.y * (1 - progress) + expoCamPos.y * progress;
    camZ = vpCamPos.z * (1 - progress) + expoCamPos.z * progress;

    if (s->hsize > s->vsize)
    {
	aspectY = (float) s->hsize / (float) s->vsize;
	aspectY -= 1.0;
	aspectY *= -expoGetAspectRatio (s->display) + 1.0;
	aspectY *= progress;
	aspectY += 1.0;
    }
    else
    {
	aspectX = (float) s->vsize / (float) s->hsize;
	aspectX -= 1.0;
	aspectX *= -expoGetAspectRatio (s->display) + 1.0;
	aspectX *= progress;
	aspectX += 1.0;
    }

    /* End of Zoom animation stuff */

    moveScreenViewport (s, s->x, s->y, FALSE);

    if (expoGetRotate (s->display))
    {
	if (expoGetExpoAnimation (s->display) == ExpoAnimationZoom)
	    rotation = 10.0 * sigmoidProgress (es->expoCam);
	else
	    rotation = 10.0 * es->expoCam;
    }

    if (expoGetMipmaps (s->display))
	s->display->textureFilter = GL_LINEAR_MIPMAP_LINEAR;

    /* ALL TRANSFORMATION ARE EXECUTED FROM BOTTOM TO TOP */

    oScale = 1 / (1 + ((MAX (sx,sy) - 1) * progress));

    matrixScale (&sTransform, oScale, oScale, 1.0);

    if (reflection)
    {
	float scaleFactor = expoGetScaleFactor (s->display);

	matrixTranslate (&sTransform, 0.0, -s->vsize * sy * aspectY, 0.0);
	matrixScale (&sTransform, 1.0, -1.0, 1.0);
	matrixTranslate (&sTransform, 0.0,
			 - (1 - scaleFactor) / 2 * s->vsize * sy * aspectY,
			 0.0);
	matrixScale (&sTransform, 1.0, scaleFactor, 1.0);
	glCullFace (GL_FRONT);
    }

    /* zoom out */
    matrixTranslate (&sTransform, -camX, -camY, -camZ - DEFAULT_Z_CAMERA);

    /* rotate */
    matrixRotate (&sTransform, rotation, 0.0f, 1.0f, 0.0f);
    matrixScale (&sTransform, aspectX, aspectY, 1.0);

    /* translate expo to center */
    matrixTranslate (&sTransform, s->hsize * sx * -0.5,
		     s->vsize * sy * 0.5, 0.0f);
    sTransformW = sTransform;

    /* revert prepareXCoords region shift. Now all screens display the same */
    matrixTranslate (&sTransform, 0.5f, -0.5f, DEFAULT_Z_CAMERA);

    es->expoActive = TRUE;

    for (j = 0; j < s->vsize; j++)
    {

	CompTransform  sTransform2 = sTransform;
	CompTransform  sTransform3;

	for (i = 0; i < s->hsize; i++)
	{
	    if (expoGetExpoAnimation (s->display) == ExpoAnimationVortex)
		matrixRotate (&sTransform2, 360 * es->expoCam, 0.0f, 1.0f,
			      2.0f * es->expoCam);

	    sTransform3 = sTransform2;
	    matrixTranslate (&sTransform3,
			     output->region.extents.x1 / output->width,
			     -output->region.extents.y1 / output->height, 0.0);

	    paintTransformedOutput (s, sAttrib, &sTransform3, &s->region,
				    output, mask);

	    if (!reflection)
	    {
	    	int cursor[2] = { pointerX, pointerY };

    		invertTransformedVertex (s, sAttrib, &sTransform3,
					 output, cursor);

		if ((cursor[0] > 0) && (cursor[0] < s->width) &&
	   	    (cursor[1] > 0) && (cursor[1] < s->height))
		{
		    es->newCursorX = i * s->width + cursor[0];
		    es->newCursorY = j * s->height + cursor[1];

		    if (es->anyClick || es->dndState != DnDNone)
		    {
			/* Used to save last viewport interaction was in */
		    	es->selectedVX = i;
	    		es->selectedVY = j;
			es->anyClick = FALSE;
		    }
		}
	    }

	    /* not sure this will work with different resolutions */
	    matrixTranslate (&sTransform2, ((1.0 * sx) + gapX), 0.0f, 0.0);
	    moveScreenViewport (s, -1, 0, FALSE);
	}

	/* not sure this will work with different resolutions */
	matrixTranslate (&sTransform, 0, - ((1.0 * sy) + gapY), 0.0f);
	moveScreenViewport (s, 0, -1, FALSE);
    }

    if (reflection)
    {
	glPushMatrix();
	glLoadMatrixf (sTransformW.m);
	glEnable (GL_BLEND);

	glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
	glBegin (GL_QUADS);
	glColor4f (0.0, 0.0, 0.0, 1.0);
	glVertex2f (0.0, 0.0);
	glColor4f (0.0, 0.0, 0.0, 0.5);
	glVertex2f (0.0, -s->vsize * (1.0 * sy + gapY));
	glVertex2f (s->hsize * sx * (1.0 + gapX),
		    -s->vsize * sy * (1.0 + gapY));
	glColor4f (0.0, 0.0, 0.0, 1.0);
	glVertex2f (s->hsize * sx * (1.0 + gapX), 0.0);
	glEnd ();
	glCullFace (GL_BACK);

	glLoadIdentity ();
	glTranslatef (0.0, 0.0, -DEFAULT_Z_CAMERA);

	if (expoGetGroundSize (s->display) > 0.0)
	{
	    glBegin (GL_QUADS);
	    glColor4usv (expoGetGroundColor1 (s->display));
	    glVertex2f (-0.5, -0.5);
	    glVertex2f (0.5, -0.5);
	    glColor4usv (expoGetGroundColor2 (s->display));
	    glVertex2f (0.5, -0.5 + expoGetGroundSize (s->display));
	    glVertex2f (-0.5, -0.5 + expoGetGroundSize (s->display));
	    glEnd ();
	}

	glColor4usv (defaultColor);

	glBlendFunc (GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
	glDisable (GL_BLEND);
	glPopMatrix ();
    }

    es->expoActive = FALSE;

    moveScreenViewport (s, -origVX, -origVY, FALSE);

    s->filter[SCREEN_TRANS_FILTER] = oldFilter;
    s->display->textureFilter = oldFilter;
}

static void
expoPaintTransformedOutput (CompScreen              *s,
			    const ScreenPaintAttrib *sAttrib,
			    const CompTransform     *transform,
			    Region                  region,
			    CompOutput              *output,
			    unsigned int            mask)
{
    EXPO_SCREEN (s);

    UNWRAP (es, s, paintTransformedOutput);

    es->expoActive = FALSE;

    if (es->expoCam > 0)
	mask |= PAINT_SCREEN_CLEAR_MASK;

    if (es->expoCam <= 0 || (es->expoCam < 1.0 && es->expoCam > 0.0 &&
	expoGetExpoAnimation (s->display) != ExpoAnimationZoom))
    {
	(*s->paintTransformedOutput) (s, sAttrib, transform, region,
				      output, mask);
    }
    else
	clearScreenOutput (s, output, GL_COLOR_BUFFER_BIT);

    mask &= ~PAINT_SCREEN_CLEAR_MASK;

    if (es->expoCam > 0.0)
    {
	if (expoGetReflection (s->display))
	    expoPaintWall (s, sAttrib, transform, region, output, mask, TRUE);

	expoPaintWall (s, sAttrib, transform, region, output, mask, FALSE);
    }

    WRAP (es, s, paintTransformedOutput, expoPaintTransformedOutput);
}

static Bool
expoDrawWindow (CompWindow           *w,
		const CompTransform  *transform,
		const FragmentAttrib *fragment,
		Region	             region,
		unsigned int	     mask)
{
    Bool       status;
    CompScreen *s = w->screen;

    EXPO_SCREEN (s);

    if (es->expoCam > 0.0)
    {
	FragmentAttrib fA = *fragment;
	ExpoExpoAnimationEnum expoAnimation;

	expoAnimation = expoGetExpoAnimation (s->display);

	if (es->expoActive)
	{
	    if (expoAnimation != ExpoAnimationZoom)
		fA.opacity = fragment->opacity * es->expoCam;

	    if (w->wmType & CompWindowTypeDockMask &&
		expoGetHideDocks (s->display))
	    {
		if (expoAnimation == ExpoAnimationZoom &&
		    (s->x == es->selectedVX && s->y == es->selectedVY))
		{
		    fA.opacity = fragment->opacity *
				 (1 - sigmoidProgress (es->expoCam));
		}
		else
		    fA.opacity = 0;
	    }

	    if (s->x != es->selectedVX || s->y != es->selectedVY)
	    {
		fA.brightness = fragment->brightness * .75;
	    }
	}
	else
	{
	    if (expoAnimation == ExpoAnimationZoom)
		fA.brightness = 0;
	    else
		fA.brightness = fragment->brightness *
				(1 - sigmoidProgress (es->expoCam));
	}

    	UNWRAP (es, s, drawWindow);
	status = (*s->drawWindow) (w, transform, &fA, region, mask);
	WRAP (es, s, drawWindow, expoDrawWindow);
    }
    else
    {
    	UNWRAP (es, s, drawWindow);
	status = (*s->drawWindow) (w, transform, fragment, region, mask);
	WRAP (es, s, drawWindow, expoDrawWindow);
    }

    return status;
}

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

    EXPO_SCREEN (s);

    if (es->expoActive)
    {
	float opacity = 1.0;
	Bool  hideDocks;

	ExpoExpoAnimationEnum expoAnimation;

	expoAnimation = expoGetExpoAnimation (s->display);

	hideDocks = expoGetHideDocks (s->display);

	if (es->expoCam > 0.0 && es->expoCam < 1.0 &&
	    expoAnimation != ExpoAnimationZoom)
	    mask |= PAINT_WINDOW_TRANSLUCENT_MASK;

	if (es->expoCam > 0.0 && hideDocks &&
	    w->wmType & CompWindowTypeDockMask)
	    mask |= PAINT_WINDOW_TRANSLUCENT_MASK;
	
	if (expoAnimation != ExpoAnimationZoom)
	    opacity = attrib->opacity * es->expoCam;

	if (w->wmType & CompWindowTypeDockMask &&
		expoGetHideDocks (s->display))
	{
	    if (expoAnimation == ExpoAnimationZoom &&
		(s->x == es->selectedVX && s->y == es->selectedVY))
	    {
		opacity = attrib->opacity *
			  (1 - sigmoidProgress (es->expoCam));
	    }
	    else
		opacity = 0;

	    if (opacity <= 0)
		mask |= PAINT_WINDOW_NO_CORE_INSTANCE_MASK;
	}
    }

    UNWRAP (es, s, paintWindow);
    status = (*s->paintWindow) (w, attrib, transform, region, mask);
    WRAP (es, s, paintWindow, expoPaintWindow);

    return status;
}

static void
expoDonePaintScreen (CompScreen * s)
{
    EXPO_SCREEN (s);

    switch (es->vpUpdateMode) {
    case VPUpdateMouseOver:
    	moveScreenViewport (s, s->x - es->selectedVX, 
			    s->y - es->selectedVY, TRUE);
	focusDefaultWindow (s->display);
	es->vpUpdateMode = VPUpdateNone;
	break;
    case VPUpdatePrevious:
	moveScreenViewport (s, s->x - es->origVX, s->y - es->origVY, TRUE);
	focusDefaultWindow (s->display);
	es->vpUpdateMode = VPUpdateNone;
	break;
    default:
	break;
    }

    if ((es->expoCam > 0.0f && es->expoCam < 1.0f) || es->dndState != DnDNone)
	damageScreen (s);

    if (es->grabIndex && es->expoCam <= 0.0f && !es->expoMode)
    {
	removeScreenGrab (s, es->grabIndex, 0);
	es->grabIndex = 0;
    }

    UNWRAP (es, s, donePaintScreen);
    (*s->donePaintScreen) (s);
    WRAP (es, s, donePaintScreen, expoDonePaintScreen);

    switch (es->dndState) {
    case DnDDuring:
	{
	    int dx = es->newCursorX - es->prevCursorX;
	    int dy = es->newCursorY - es->prevCursorY;

	    if (es->dndWindow)
		moveWindow (es->dndWindow, dx, dy, TRUE,
			    expoGetExpoImmediateMove (s->display));

	    es->prevCursorX = es->newCursorX;
	    es->prevCursorY = es->newCursorY;

	    damageScreen (s);
	}
	break;

    case DnDStart:
	{
	    int        origView  = s->x;
	    int        origViewY = s->y;
	    CompWindow *w;

	    moveScreenViewport (s, s->x, s->y, FALSE);

	    for (w = s->reverseWindows; w; w = w->prev)
	    {
		Bool inWindow;
		int xOffset, yOffset;

		if (w->destroyed)
		    continue;

		if (!w->shaded)
		{
		    if (w->attrib.map_state != IsViewable || !w->damaged)
			continue;
		}

		if (!(w->type & (CompWindowTypeNormalMask |
				 CompWindowTypeFullscreenMask)))
		    continue;

		xOffset = s->hsize * s->width;
		yOffset = s->vsize * s->height;

		inWindow = ((es->newCursorX >= WIN_X (w)) &&
			    (es->newCursorX <= WIN_X (w) + WIN_W (w))) ||
		           ((es->newCursorX >= (WIN_X (w) + xOffset)) &&
			    (es->newCursorX <= (WIN_X (w) + WIN_W (w) +
						xOffset)));

		inWindow &= ((es->newCursorY >= WIN_Y (w)) &&
			     (es->newCursorY <= WIN_Y (w) + WIN_H (w))) ||
		            ((es->newCursorY >= (WIN_Y (w) + yOffset)) &&
		    	     (es->newCursorY <= (WIN_Y (w) + WIN_H (w) +
						 yOffset)));

		if (!inWindow)
		    continue;

		es->dndState  = DnDDuring;
		es->dndWindow = w;

		(*s->windowGrabNotify) (w, es->newCursorX, es->newCursorY,
					0, CompWindowGrabMoveMask |
					CompWindowGrabButtonMask);
		break;
	    }

	    if (w)
	    {
		raiseWindow (es->dndWindow);
		moveInputFocusToWindow (es->dndWindow);
	    }
	    else
	    {
		/* no window was hovered */
		es->dndState = DnDNone;
	    }
    
	    moveScreenViewport (s, -origView, -origViewY, FALSE);

	    es->prevCursorX = es->newCursorX;
	    es->prevCursorY = es->newCursorY;
	}
	break;
    default:
	break;
    }
}

static Bool
expoInitDisplay (CompPlugin  *p,
		 CompDisplay *d)
{
    ExpoDisplay *ed;

    ed = malloc (sizeof (ExpoDisplay));

    if (!ed)
	return FALSE;

    ed->screenPrivateIndex = allocateScreenPrivateIndex (d);

    if (ed->screenPrivateIndex < 0)
    {
	free (ed);
	return FALSE;
    }

    expoSetExpoInitiate (d, expoExpo);
    expoSetExpoTerminate (d, expoTermExpo);

    ed->leftKey  = XKeysymToKeycode (d->display, XStringToKeysym ("Left"));
    ed->rightKey = XKeysymToKeycode (d->display, XStringToKeysym ("Right"));
    ed->upKey    = XKeysymToKeycode (d->display, XStringToKeysym ("Up"));
    ed->downKey  = XKeysymToKeycode (d->display, XStringToKeysym ("Down"));

    WRAP (ed, d, handleEvent, expoHandleEvent);
    d->privates[displayPrivateIndex].ptr = ed;

    return TRUE;
}

static void
expoFiniDisplay (CompPlugin  *p,
		 CompDisplay *d)
{
    EXPO_DISPLAY (d);

    UNWRAP (ed, d, handleEvent);

    freeScreenPrivateIndex (d, ed->screenPrivateIndex);
    free (ed);
}

static Bool
expoInitScreen (CompPlugin *p,
		CompScreen *s)
{
    ExpoScreen *es;

    EXPO_DISPLAY (s->display);

    es = malloc (sizeof (ExpoScreen));

    if (!es)
	return FALSE;

    es->anyClick  = FALSE;
    es->vpUpdateMode = VPUpdateNone;

    es->selectedVX = es->origVX = s->x;
    es->selectedVY = es->origVY = s->y;

    es->grabIndex = 0;

    es->expoCam  = 0.0f;
    es->expoMode = 0;

    es->dndState  = DnDNone;
    es->dndWindow = NULL;

    es->clickTime   = 0;
    es->doubleClick = FALSE;

    WRAP (es, s, paintOutput, expoPaintOutput);
    WRAP (es, s, paintScreen, expoPaintScreen);
    WRAP (es, s, donePaintScreen, expoDonePaintScreen);
    WRAP (es, s, paintTransformedOutput, expoPaintTransformedOutput);
    WRAP (es, s, preparePaintScreen, expoPreparePaintScreen);
    WRAP (es, s, drawWindow, expoDrawWindow);
    WRAP (es, s, damageWindowRect, expoDamageWindowRect);
    WRAP (es, s, paintWindow, expoPaintWindow);

    s->privates[ed->screenPrivateIndex].ptr = es;

    return TRUE;
}

static void
expoFiniScreen (CompPlugin *p,
		CompScreen *s)
{
    EXPO_SCREEN (s);

    if (es->grabIndex)
    {
	removeScreenGrab (s, es->grabIndex, 0);
	es->grabIndex = 0;
    }

    UNWRAP (es, s, paintOutput);
    UNWRAP (es, s, paintScreen);
    UNWRAP (es, s, donePaintScreen);
    UNWRAP (es, s, paintTransformedOutput);
    UNWRAP (es, s, preparePaintScreen);
    UNWRAP (es, s, drawWindow);
    UNWRAP (es, s, damageWindowRect);
    UNWRAP (es, s, paintWindow);

    free (es);
}

static Bool
expoInit (CompPlugin * p)
{
    displayPrivateIndex = allocateDisplayPrivateIndex();

    if (displayPrivateIndex < 0)
	return FALSE;

    return TRUE;
}

static void
expoFini (CompPlugin * p)
{
    freeDisplayPrivateIndex (displayPrivateIndex);
}

static int
expoGetVersion (CompPlugin *p,
		int        version)
{
    return ABIVERSION;
}

CompPluginVTable expoVTable = {
    "expo",
    expoGetVersion,
    0,
    expoInit,
    expoFini,
    expoInitDisplay,
    expoFiniDisplay,
    expoInitScreen,
    expoFiniScreen,
    NULL,
    NULL,
    NULL,
    NULL,
    NULL,
    NULL
};

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


syntax highlighted by Code2HTML, v. 0.9.1