/**
 * Beryl Opacify 
 *
 * Copyright (c) 2006 Kristian Lyngstøl <kristian@beryl-project.org>
 * Ported to Compiz and BCOP usage by Danny Baumann <maniac@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.
 *
 *
 * Opacify increases opacity on targeted windows and reduces it on
 * blocking windows, making whatever window you are targeting easily
 * visible. 
 *
 */

#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <math.h>
#include <X11/Xatom.h>
#include <X11/extensions/Xrender.h>

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

#define GET_OPACIFY_DISPLAY(d)                            \
	((OpacifyDisplay *) (d)->privates[displayPrivateIndex].ptr)
#define OPACIFY_DISPLAY(d)                                \
	OpacifyDisplay *od = GET_OPACIFY_DISPLAY (d)
#define GET_OPACIFY_SCREEN(s, od)                         \
	((OpacifyScreen *) (s)->privates[(od)->screenPrivateIndex].ptr)
#define OPACIFY_SCREEN(s)                                 \
	OpacifyScreen *os = GET_OPACIFY_SCREEN (s, GET_OPACIFY_DISPLAY (s->display))
#define GET_OPACIFY_WINDOW(w, os)                         \
	((OpacifyWindow *) (w)->privates[(os)->windowPrivateIndex].ptr)
#define OPACIFY_WINDOW(s)                                 \
	OpacifyWindow *ow = GET_OPACIFY_WINDOW(w, GET_OPACIFY_SCREEN (w->screen, GET_OPACIFY_DISPLAY (w->screen->display)))

/* Size of the Window array storing passive windows. */
#define MAX_WINDOWS 64

static int displayPrivateIndex = 0;

typedef struct _OpacifyDisplay
{
	int screenPrivateIndex;
	HandleEventProc handleEvent;
	Bool toggle;
	int active_screen;
	CompTimeoutHandle timeout_handle;
} OpacifyDisplay;

typedef struct _OpacifyScreen
{
	int windowPrivateIndex;
	PaintWindowProc paintWindow;

	CompWindow *new_active;
	Window active;
	Window passive[MAX_WINDOWS];
	Region intersect;
	unsigned short int passive_num;
	Bool just_moved;
} OpacifyScreen;

typedef struct _OpacifyWindow
{
	Bool opacified;
	int opacity;
} OpacifyWindow;

/* Core opacify functions. These do the real work. ---------------------*/

/* Sets the real opacity and damages the window if actual opacity and 
 * requested opacity differs. */
static void set_opacity(CompWindow * w, int opacity)
{
	OPACIFY_WINDOW(w);
	
	if (!ow->opacified || (w->paint.opacity != opacity))
		addWindowDamage(w);

	ow->opacified = TRUE;
	ow->opacity = opacity;

	//setWindowOpacity(w, opacity, PL_TEMP_HELLO);
}

/* Resets the Window to the original opacity if it still exists.
 */
static void reset_opacity(CompScreen * s, Window id)
{
	CompWindow *w;

	w = findWindowAtScreen(s, id);
	if (!w)
		return;

	OPACIFY_WINDOW(w);
	ow->opacified = FALSE;
	addWindowDamage(w);
	//resetWindowOpacity(w, PL_TEMP_HELLO);
}

/* Resets the opacity of windows on the passive list.
 */
static void clear_passive(CompScreen * s)
{
	OPACIFY_SCREEN(s);
	int i;

	for (i = 0; i < os->passive_num; i++)
		reset_opacity(s, os->passive[i]);
	os->passive_num = 0;
}

/* Dim an (inactive) window. Place it on the passive list and
 * update passive_num. Then change the opacity.
 */
static void dim_window(CompScreen * s, CompWindow * w)
{
	OPACIFY_SCREEN(s);
	if (os->passive_num >= MAX_WINDOWS - 1)
	{
		compLogMessage (s->display, "opacify", CompLogLevelWarn,
						"Trying to store information "
						"about too many windows, or you hit a bug.\nIf "
						"you don't have around %d windows blocking the "
						"currently targeted window, please report this.",
						MAX_WINDOWS);
		return;
	}
	os->passive[os->passive_num++] = w->id;
	set_opacity(w, MIN(OPAQUE * opacifyGetPassiveOpacity(s) / 100, w->paint.opacity));
}

/* Walk through all windows, skip until we've passed the active
 * window, skip if it's invisible, hidden or minimized, skip if
 * it's not a window type we're looking for. 
 * Dim it if it intersects. 
 *
 * Returns number of changed windows.
 */
static int passive_windows(CompScreen * s, Region a_region)
{
	CompWindow *w;

	OPACIFY_SCREEN(s);
	Bool flag = FALSE;
	int i = 0;

	for (w = s->windows; w; w = w->next)
	{
		if (w->id == os->active)
		{
			flag = TRUE;
			continue;
		}
		if (!flag)
			continue;
		if (!matchEval(opacifyGetWindowMatch(s), w))
			continue;
		if (w->invisible || w->hidden || w->minimized)
			continue;
		XIntersectRegion(w->region, a_region, os->intersect);
		if (!XEmptyRegion(os->intersect))
		{
			dim_window(s, w);
			i++;
		}
	}
	return i;
}

/* Check if we switched active window, reset the old passive windows
 * if we did. If we have an active window and switched: reset that too.
 * If we have a window (w is true), update the active id and
 * passive list. just_moved is to make sure we recalculate opacity after
 * moving. We can't reset before moving because if we're using a delay
 * and the window being moved is not the active but overlapping, it will
 * be reset, which would conflict with move's opacity change. 
 * FIXME: A more final solution should be to use IPCS to signal 
 * which window is being moved. 
 */
static void opacify_handle_enter(CompScreen * s, CompWindow * w)
{
	OPACIFY_SCREEN(s);
	int num;

	if (otherScreenGrabExist(s, 0))
	{
		if (!otherScreenGrabExist(s, "move", 0))
		{
			os->just_moved = True;
			return;
		}
		clear_passive(s);
		reset_opacity(s, os->active);
		os->active = 0;
		return;
	}
	if (!w || os->active != w->id || os->just_moved)
	{
		os->just_moved = False;
		clear_passive(s);
		reset_opacity(s, os->active);
		os->active = 0;
	}
	if (!w)
		return;
	if (w->id != os->active && !w->shaded &&
	    matchEval(opacifyGetWindowMatch(s), w))
	{
		os->active = w->id;
		num = passive_windows(s, w->region);
		if (num || opacifyGetOnlyIfBlock(s))
			set_opacity(w, MAX(OPAQUE * opacifyGetActiveOpacity(s) / 100, w->paint.opacity));
	}
}

/* Check if we are on the same screen. We only want opacify active on
 * one screen, so if we are on a diffrent screen, we reset the old one.
 * Returns True if the screen has switched.
 */
static Bool check_screen_switch(CompScreen *s)
{
	OPACIFY_DISPLAY(s->display);
	if (od->active_screen == s->screenNum)
		return False;
	CompScreen * tmp;
	for (tmp = s->display->screens; tmp; tmp = tmp->next)
	{
		OPACIFY_SCREEN(tmp);
		clear_passive(tmp);
		reset_opacity(tmp, os->active);
		os->active = 0;
	}
	od->active_screen = s->screenNum;
	return True;
}

/* Decides what to do after a timeout occured. 
 * Either we reset the opacity because we just toggled,
 * or we handle the event.
 */
static Bool handle_timeout(void *data)
{
	CompScreen *s = (CompScreen *) data;

	OPACIFY_SCREEN(s);
	OPACIFY_DISPLAY(s->display);

	od->timeout_handle = 0;
	check_screen_switch(s);
	if (!od->toggle)
	{
		clear_passive(s);
		reset_opacity(s, os->active);
		os->active = 0;
	}
	opacify_handle_enter(s, os->new_active);

	return FALSE;
}

/* Checks whether we should delay or not.
 * Returns true if immediate execution.
 */
static inline Bool check_delay(CompScreen *s)
{
	CompDisplay * d = s->display;
	OPACIFY_SCREEN(s);

	if (opacifyGetFocusInstant(s) && os->new_active && 
	    (os->new_active->id == d->activeWindow))
		 return True;
	if (!opacifyGetTimeout(d))
	     return True;
	if (!os->new_active || (os->new_active->id == s->root))
		return False;
	if (os->new_active->type & (CompWindowTypeDesktopMask | CompWindowTypeDockMask))
		return False;
	if (opacifyGetNoDelayChange(s) && os->passive_num)
		return True;
	return False;


}

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

	if (ow->opacified)
	{
		WindowPaintAttrib wAttrib = *attrib;
		wAttrib.opacity = ow->opacity;

		UNWRAP(os, s, paintWindow);
		status = (*s->paintWindow) (w, &wAttrib, transform, region, mask);
		WRAP(os, s, paintWindow, opacifyPaintWindow);
	}
	else
	{
		UNWRAP(os, s, paintWindow);
		status = (*s->paintWindow) (w, attrib, transform, region, mask);
		WRAP(os, s, paintWindow, opacifyPaintWindow);
	}

	return status;
}

/* Takes the inital event. 
 * If we were configured, recalculate the opacify-windows if 
 * it was our window. 
 * If a window was entered: call upon handle_timeout after od->timeout 
 * micro seconds, or directly if od->timeout is 0 (no delay).
 *
 * FIXME: In the perfect world, toggle-resetting is done in the action
 * handler that does the actual toggling.
 */
static void opacifyHandleEvent(CompDisplay * d, XEvent * event)
{
	CompScreen *s;
	CompWindow *w = NULL;

	OPACIFY_DISPLAY(d);

	UNWRAP(od, d, handleEvent);
	(*d->handleEvent) (d, event);
	WRAP(od, d, handleEvent, opacifyHandleEvent);

	if (!od->toggle && !opacifyGetToggleReset(d))
		return;

	switch (event->type)
	{
	case EnterNotify:
		s = findScreenAtDisplay(d, event->xcrossing.root);
		if (s)
		{
			OPACIFY_SCREEN(s);
			if (!od->toggle && !os->active)
				return;
			os->new_active = findTopLevelWindowAtScreen(s, event->xcrossing.window);
			if (od->timeout_handle)
				compRemoveTimeout(od->timeout_handle);
			if (check_delay(s))
				handle_timeout(s);
			else
				od->timeout_handle =
						compAddTimeout(opacifyGetTimeout(d), handle_timeout, s);
		}
		break;
	case ConfigureNotify:
		s = findScreenAtDisplay(d, event->xconfigure.event);
		if (s)
		{
			OPACIFY_SCREEN(s);
			if (os->active != event->xconfigure.window)
				break;
			clear_passive(s);
			if (os->active)
				w = findWindowAtScreen(s, os->active);
			if (w)
				passive_windows(s, w->region);
		}
		break;
	default:
		break;
	}
}


/* Configuration, initialization, boring stuff. ----------------------- */

static Bool opacifyInitWindow(CompPlugin * p, CompWindow * w)
{
	OPACIFY_SCREEN(w->screen);
	OpacifyWindow *ow = (OpacifyWindow *) calloc(1, sizeof(OpacifyWindow));

	ow->opacified = FALSE;
	
	w->privates[os->windowPrivateIndex].ptr = ow;

	return TRUE;
}

static void opacifyFiniWindow(CompPlugin * p, CompWindow * w)
{
	OPACIFY_WINDOW(w);

	free(ow);
}

static void opacifyFiniScreen(CompPlugin * p, CompScreen * s)
{
	OPACIFY_SCREEN(s);

	UNWRAP(os, s, paintWindow);

	XDestroyRegion(os->intersect);
	free(os);
}

static Bool opacifyInitScreen(CompPlugin * p, CompScreen * s)
{
	OPACIFY_DISPLAY(s->display);
	OpacifyScreen *os = (OpacifyScreen *) calloc(1, sizeof(OpacifyScreen));

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

	WRAP(os, s, paintWindow, opacifyPaintWindow);

	s->privates[od->screenPrivateIndex].ptr = os;
	os->intersect = XCreateRegion();
	os->just_moved = False;
	
	return TRUE;
}

static Bool opacify_toggle(CompDisplay * d, CompAction * ac,
						   CompActionState state, CompOption * option,
						   int nOption)
{
	OPACIFY_DISPLAY(d);
	od->toggle = !od->toggle;
	return TRUE;
}

static void opacifyDisplayOptionChanged(CompDisplay *d, CompOption *opt, OpacifyDisplayOptions num)
{
	OPACIFY_DISPLAY(d);
	
	switch (num)
	{
		case OpacifyDisplayOptionInitToggle:
			od->toggle = opt->value.b;
			break;
		default:
			break;
	}
}

static Bool opacifyInitDisplay(CompPlugin * p, CompDisplay * d)
{
	OpacifyDisplay *od = (OpacifyDisplay *) malloc(sizeof(OpacifyDisplay));

	if (!od)
	    return FALSE;

	od->screenPrivateIndex = allocateScreenPrivateIndex(d);
	if (od->screenPrivateIndex < 0)
	{
		free(od);
		return FALSE;
	}
	d->privates[displayPrivateIndex].ptr = od;
	od->active_screen = d->screens->screenNum;
	od->toggle = TRUE;

	opacifySetToggleInitiate (d, opacify_toggle);	
	opacifySetInitToggleNotify(d, opacifyDisplayOptionChanged);

	WRAP(od, d, handleEvent, opacifyHandleEvent);
	return TRUE;
}

static void opacifyFiniDisplay(CompPlugin * p, CompDisplay * d)
{
	OPACIFY_DISPLAY(d);
	UNWRAP(od, d, handleEvent);
	if (od->timeout_handle)
		compRemoveTimeout(od->timeout_handle);
	freeScreenPrivateIndex(d, od->screenPrivateIndex);
	free(od);
}

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

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

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

CompPluginVTable opacifyVTable = {
	"opacify",
	opacifyGetVersion,
	0,
	opacifyInit,
	opacifyFini,
	opacifyInitDisplay,
	opacifyFiniDisplay,
	opacifyInitScreen,
	opacifyFiniScreen,
	opacifyInitWindow,
	opacifyFiniWindow,
	0,
	0,
	0,
	0
};

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


syntax highlighted by Code2HTML, v. 0.9.1