/**
 *
 * Beryl fade to desktop plugin
 *
 * fadedesktop.c
 *
 * Copyright (c) 2006 Robert Carr <racarr@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 <stdlib.h>
#include <string.h>
#include <beryl.h>
#include <beryl_ipcs.h>

#include <math.h>

#include <X11/Xlib.h>
#include <X11/Xatom.h>
#include <X11/Xproto.h>

#define FD_INITIATE_KEY_DEFAULT "F6"
#define FD_INITIATE_MODIFIERS_DEFAULT CompSuperMask
#define FD_INITIATE_EDGE_DEFAULT 0

#define FD_SPEED_DEFAULT 20
#define FD_SPEED_MIN     1
#define FD_SPEED_MAX     100


#define FD_DISPLAY_OPTION_INITIATE 0
#define FD_DISPLAY_OPTION_NUM 1

#define FD_SCREEN_OPTION_SPEED 0
#define FD_SCREEN_OPTION_NUM 1



typedef struct _FadeDesktopDisplay
{
	int screenPrivateIndex;
	CompOption opt[FD_DISPLAY_OPTION_NUM];
} FadeDesktopDisplay;

typedef struct _FadeDesktopScreen
{
	int windowPrivateIndex;

	PreparePaintScreenProc preparePaintScreen;
	DonePaintScreenProc donePaintScreen;
	
	CompOption opt[FD_SCREEN_OPTION_NUM];

	Bool showingDesktop;
	Bool reverting;
} FadeDesktopScreen;

typedef struct _FadeDesktopWindow
{
	Bool isHidden;
} FadeDesktopWindow;



#define GET_FADEDESKTOP_DISPLAY(d)				     \
    ((FadeDesktopDisplay *) (d)->privates[displayPrivateIndex].ptr)

#define FD_DISPLAY(d)			   \
    FadeDesktopDisplay *fd = GET_FADEDESKTOP_DISPLAY (d)

#define GET_FADEDESKTOP_SCREEN(s, fd)					 \
    ((FadeDesktopScreen *) (s)->privates[(fd)->screenPrivateIndex].ptr)

#define FD_SCREEN(s)							\
    FadeDesktopScreen *fs = GET_FADEDESKTOP_SCREEN (s, GET_FADEDESKTOP_DISPLAY (s->display))

#define GET_FADEDESKTOP_WINDOW(w, fs)					 \
    ((FadeDesktopWindow *) (w)->privates[(fs)->windowPrivateIndex].ptr)

#define FD_WINDOW(w)							\
    FadeDesktopWindow *fw = GET_FADEDESKTOP_WINDOW(w, \
					GET_FADEDESKTOP_SCREEN (w->screen, GET_FADEDESKTOP_DISPLAY (w->screen->display)))


static int displayPrivateIndex;


static Bool isFDWin(CompWindow *w)
{
	//w->screen->focusWindow ?
	if (w->state & CompWindowStateOffscreenMask)
		return FALSE;

    if (w->wmType & (CompWindowTypeDesktopMask | CompWindowTypeDockMask))
		return FALSE;

	if (w->type & (CompWindowTypeNormalMask | CompWindowTypeDialogMask ) )
	{
		return TRUE;
	}
	return FALSE;
}

static Bool
fadeDesktopTerminate(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)
	{
		FD_SCREEN(s);
		fs->reverting = TRUE;
		CompWindow *w;
		for (w=s->windows; w; w=w->next)
		{
			FD_WINDOW(w);
			if (fw->isHidden && (w->state & CompWindowStateOffscreenMask))
			{
				moveWindowOnscreen(w);
			}
			fw->isHidden = FALSE;
		}
		
	}

	return TRUE;

}


static Bool 
fadeDesktopInitiate(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)
	{
		FD_SCREEN(s);
		
		if (fs->showingDesktop)
		{
			fs->showingDesktop = FALSE;
			fadeDesktopTerminate(d, action, state, option,  nOption);
			return TRUE;
		}
		else if (fs->reverting)
		{
			fs->reverting = FALSE;
		}
		fs->showingDesktop = TRUE;

		unsigned long data = 0;
		XChangeProperty(s->display->display, s->root,
					s->display->showingDesktopAtom,
					XA_CARDINAL, 32, PropModeReplace,
					(unsigned char *)&data, 1);
		
	}
	return TRUE;
	
}

static void 
fadeDesktopPreparePaintScreen(CompScreen *s, int msSinceLastPaint)
{
	FD_SCREEN(s);
	#define SPEED ((fs->opt[FD_SCREEN_OPTION_SPEED].value.i ? fs->opt[FD_SCREEN_OPTION_SPEED].value.i : 1))
	
	int numWins=0;
	int numReverts=0;

	CompWindow *w;
	for (w=s->windows; w; w=w->next)
	{
		if (isFDWin(w))
		{
			if (fs->showingDesktop)
			{
				if (w->paint.opacity > OPAQUE/SPEED)
				{
					setWindowOpacity(w,w->paint.opacity-OPAQUE/SPEED,PL_TEMP_HELLO);
				}
				else if (w->paint.opacity != 0)
				{
					FD_WINDOW(w);

					moveWindowOffscreen(w);
					fw->isHidden = TRUE;
				}
			}

			if (fs->reverting)
			{
				if (w->paint.opacity + OPAQUE/SPEED <= w->opacity)
				{
					setWindowOpacity(w,w->paint.opacity + OPAQUE/SPEED,PL_TEMP_HELLO);
				}
				else 
				{
					resetWindowOpacity(w, PL_TEMP_HELLO);
					numReverts++;
				}
				
			}
			numWins++;
			
			
	
		}
	}
	if (numWins == numReverts)
	{
		fs->reverting = FALSE;
		unsigned long data = 0;
		XChangeProperty(s->display->display, s->root,
					s->display->showingDesktopAtom,
					XA_CARDINAL, 32, PropModeReplace,
					(unsigned char *)&data, 1);
	}

	UNWRAP(fs,s,preparePaintScreen);
	(*s->preparePaintScreen) (s,msSinceLastPaint);
	WRAP(fs,s,preparePaintScreen,fadeDesktopPreparePaintScreen);


}

static void fadeDesktopDonePaintScreen(CompScreen *s)
{
	FD_SCREEN(s);
	if (fs->showingDesktop || fs->reverting)
	{
		damageScreen(s);
	}
	UNWRAP(fs,s,donePaintScreen);
	(*s->donePaintScreen)(s);
	WRAP(fs,s,donePaintScreen,fadeDesktopDonePaintScreen);
}

static void
fadeDesktopDisplayInitOptions(FadeDesktopDisplay *fd)
{
	CompOption *o;
	o = &fd->opt[FD_DISPLAY_OPTION_INITIATE];
	o->advanced = False;
	o->name = "initiate";
	o->group = N_("Misc. Options");
	o->subGroup = N_("");
	o->displayHints = "";
	o->shortDesc = N_("Initiate Fade to Desktop");
	o->longDesc = N_("Initiate Fade to Desktop mode.");
	o->type = CompOptionTypeAction;
	o->value.action.initiate= fadeDesktopInitiate;
	o->value.action.terminate = fadeDesktopTerminate;
	o->value.action.bell = FALSE;
	o->value.action.edgeMask = FD_INITIATE_EDGE_DEFAULT;
	o->value.action.type = CompBindingTypeKey;
	o->value.action.state = CompActionStateInitKey;
	o->value.action.state |= CompActionStateInitEdge;
	o->value.action.key.modifiers = FD_INITIATE_MODIFIERS_DEFAULT;
	o->value.action.key.keysym = XStringToKeysym(FD_INITIATE_KEY_DEFAULT);
}

static void fadeDesktopScreenInitOptions(FadeDesktopScreen * fs)
{
	CompOption *o;
	o = &fs->opt[FD_SCREEN_OPTION_SPEED];
	o->advanced = False;
	o->name = "speed";
	o->group = N_("Misc. Options");
	o->subGroup = N_("");
	o->displayHints = "";
	o->shortDesc = N_("Time to Fade");
	o->longDesc = N_("Time to Fade.");
	o->type = CompOptionTypeInt;
	o->value.i = FD_SPEED_DEFAULT;
	o->rest.i.min = FD_SPEED_MIN;
	o->rest.i.max = FD_SPEED_MAX;
}

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


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

static Bool fadeDesktopInitDisplay(CompPlugin *p, CompDisplay *d)
{
	FadeDesktopDisplay *fd;
	fd = malloc(sizeof(FadeDesktopDisplay));
	if (!fd)
		return FALSE;
	fd->screenPrivateIndex = allocateScreenPrivateIndex(d);
	if (fd->screenPrivateIndex < 0)
	{
		free(fd);
		return FALSE;
	}
	fadeDesktopDisplayInitOptions(fd);
	
	d->privates[displayPrivateIndex].ptr = fd;
	return TRUE;
}

static void fadeDesktopFiniDisplay(CompPlugin *p, CompDisplay *d)
{
	FD_DISPLAY(d);
	freeScreenPrivateIndex(d, fd->screenPrivateIndex);
	free(fd);
}

static Bool fadeDesktopInitScreen(CompPlugin *p, CompScreen *s)
{
	FadeDesktopScreen *fs;
	FD_DISPLAY(s->display);

	fs = malloc(sizeof(FadeDesktopScreen));
	if (!fs)
		return FALSE;

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

	fadeDesktopScreenInitOptions(fs);

	fs->showingDesktop = FALSE;
	
	addScreenAction(s, &fd->opt[FD_DISPLAY_OPTION_INITIATE].value.action);
	
	WRAP(fs,s,preparePaintScreen,fadeDesktopPreparePaintScreen);
	WRAP(fs,s,donePaintScreen,fadeDesktopDonePaintScreen);

	s->privates[fd->screenPrivateIndex].ptr = fs;

	return TRUE;
	
}

static void fadeDesktopFiniScreen(CompPlugin *p, CompScreen *s)
{
	FD_SCREEN(s);
	FD_DISPLAY(s->display);
	
	UNWRAP(fs,s,preparePaintScreen);
	UNWRAP(fs,s,donePaintScreen);
	
	removeScreenAction(s, &fd->opt[FD_DISPLAY_OPTION_INITIATE].value.action);

	freeWindowPrivateIndex(s, fs->windowPrivateIndex);
	
	free (fs);
}

static Bool fadeDesktopInitWindow(CompPlugin *p, CompWindow *w)
{
	FadeDesktopWindow *fw;
	FD_SCREEN(w->screen);

	fw = malloc(sizeof(FadeDesktopWindow));
	if (!fw)
		return FALSE;

	fw->isHidden = FALSE;
	
	w->privates[fs->windowPrivateIndex].ptr = fw;

	return TRUE;
	
}

static void fadeDesktopFiniWindow(CompPlugin *p, CompWindow *w)
{
	FD_WINDOW(w);
	FD_SCREEN(w->screen);
	
	free (fw);
}


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

static CompOption *fadeDesktopGetDisplayOptions(CompDisplay *display, int *count)
{
	if (display)
	{
		FD_DISPLAY(display);

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

		fadeDesktopDisplayInitOptions(fd);
		*count = NUM_OPTIONS(fd);
		return fd->opt;
	}
}

static Bool 
fadeDesktopSetDisplayOption(CompDisplay *display, char * name, CompOptionValue *value)
{

	FD_DISPLAY(display);
	CompOption *o;
	int index;
	o = compFindOption(fd->opt,FD_DISPLAY_OPTION_NUM,name,&index);

	if (!o)
		return FALSE;
	
	switch (index)
	{
		case FD_DISPLAY_OPTION_INITIATE:
			if (setDisplayAction(display,o,value))
				return TRUE;	
		default:
			break;
	}
	return FALSE;
}

static CompOption *fadeDesktopGetScreenOptions(CompScreen * screen,
											   int *count)
{
	if (screen)
	{
		FD_SCREEN(screen);

		*count = FD_SCREEN_OPTION_NUM;
		return fs->opt;
	}
	else
	{
		FadeDesktopScreen *fs = malloc(sizeof(FadeDesktopScreen));

		fadeDesktopScreenInitOptions(fs);
		*count = FD_SCREEN_OPTION_NUM;
		return fs->opt;
	}
}

static Bool 
fadeDesktopSetScreenOption(CompScreen *screen, char *name, CompOptionValue *value)
{
	CompOption *o;
	int index;
	FD_SCREEN(screen);
	o = compFindOption(fs->opt,FD_SCREEN_OPTION_NUM,name,&index);
	
	if (!o)
		return FALSE;

	switch (index)
	{
		case FD_SCREEN_OPTION_SPEED:
		if (compSetIntOption(o,value))
		{
			return TRUE;
		}
		break;
	}
	return FALSE;
}

CompPluginFeature fdFeatures[] = {
	{"showdesktop"}
	,
};

static CompPluginVTable fadeDesktopVTable = {
	"fadeDesktop",
	N_("Fade to Desktop"),
	N_("Easily access your desktop"),
	fadeDesktopInit,
	fadeDesktopFini,
	fadeDesktopInitDisplay,
	fadeDesktopFiniDisplay,
	fadeDesktopInitScreen,
	fadeDesktopFiniScreen,
	fadeDesktopInitWindow,
	fadeDesktopFiniWindow,
	fadeDesktopGetDisplayOptions,
	fadeDesktopSetDisplayOption,
	fadeDesktopGetScreenOptions,
	fadeDesktopSetScreenOption,
	0,
	0,
	fdFeatures,
	sizeof(fdFeatures) / sizeof(fdFeatures[0]),
	BERYL_ABI_INFO,
	"beryl-plugins",
	"desktop",
	0,
	0,
	True,
};

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


syntax highlighted by Code2HTML, v. 0.9.1