/*
 * Copyright © 2006 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>
 */

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

#include <beryl.h>

#define SHOT_INITIATE_BUTTON_DEFAULT	       Button1
#define SHOT_INITIATE_BUTTON_MODIFIERS_DEFAULT CompSuperMask | ControlMask

#define SHOT_DIR_DEFAULT "Desktop"

static int displayPrivateIndex;

#define SHOT_DISPLAY_OPTION_INITIATE 0
#define SHOT_DISPLAY_OPTION_DIR      1
#define SHOT_DISPLAY_OPTION_NUM	     2

typedef struct _ShotDisplay
{
	int screenPrivateIndex;
	HandleEventProc handleEvent;

	CompOption opt[SHOT_DISPLAY_OPTION_NUM];
} ShotDisplay;

typedef struct _ShotScreen
{
	PaintScreenProc paintScreen;
	PaintTransformedScreenProc paintTransformedScreen;
	int grabIndex;

	Bool painted;
	int x, y;

	int x1, y1, x2, y2;
	Bool grab;
} ShotScreen;

#define GET_SHOT_DISPLAY(d)				     \
    ((ShotDisplay *) (d)->privates[displayPrivateIndex].ptr)

#define SHOT_DISPLAY(d)			   \
    ShotDisplay *sd = GET_SHOT_DISPLAY (d)

#define GET_SHOT_SCREEN(s, sd)					 \
    ((ShotScreen *) (s)->privates[(sd)->screenPrivateIndex].ptr)

#define SHOT_SCREEN(s)							\
    ShotScreen *ss = GET_SHOT_SCREEN (s, GET_SHOT_DISPLAY (s->display))

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


static Bool
shotInitiate(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)
	{
		SHOT_SCREEN(s);

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

		if (!ss->grabIndex)
			ss->grabIndex = pushScreenGrab(s, None, "screenshot");

		if (state & CompActionStateInitButton)
			action->state |= CompActionStateTermButton;

		/* start selection screenshot rectangle */

		ss->x1 = ss->x2 = d->pointerX;
		ss->y1 = ss->y2 = d->pointerY;

		ss->grab = TRUE;
	}

	return TRUE;
}

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

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

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

		if (xid && s->root != xid)
			continue;

		if (ss->grabIndex)
		{
			removeScreenGrab(s, ss->grabIndex, NULL);
			ss->grabIndex = 0;

			if (ss->x1 != ss->x2 && ss->y1 != ss->y2)
			{
				REGION reg;

				reg.rects = &reg.extents;
				reg.numRects = 1;

				reg.extents.x1 = MIN(ss->x1, ss->x2) - 1;
				reg.extents.y1 = MIN(ss->y1, ss->y2) - 1;
				reg.extents.x2 = MAX(ss->x1, ss->x2) + 1;
				reg.extents.y2 = MAX(ss->y1, ss->y2) + 1;

				damageScreenRegion(s, &reg);
			}
		}
	}

	action->state &= ~(CompActionStateTermKey | CompActionStateTermButton);

	return FALSE;
}

static int shotFilter(const struct dirent *d)
{
	int number;

	if (sscanf(d->d_name, "screenshot%d.png", &number))
		return 1;

	return 0;
}

static int shotSort(const void *_a, const void *_b)
{
	struct dirent **a = (struct dirent **)_a;
	struct dirent **b = (struct dirent **)_b;
	int al = strlen((*a)->d_name);
	int bl = strlen((*b)->d_name);

	if (al == bl)
		return strcoll((*a)->d_name, (*b)->d_name);
	else
		return al - bl;
}

static void
shotPaintOutline(CompScreen * s, const ScreenPaintAttrib * sa, 
				 int output, Bool transformed)
{
	int x1 = 0, x2 = 0, y1 = 0, y2 = 0;

	SHOT_SCREEN(s);

	x1 = MIN(ss->x1, ss->x2);
	y1 = MIN(ss->y1, ss->y2);
	x2 = MAX(ss->x1, ss->x2);
	y2 = MAX(ss->y1, ss->y2);

	glPushMatrix();

	if (transformed)
	{
		glLoadIdentity();
		(s->applyScreenTransform) (s, sa, output);
	}

	prepareXCoords(s, output,
				   transformed ? -sa->zTranslate : -DEFAULT_Z_CAMERA);
	glDisableClientState(GL_TEXTURE_COORD_ARRAY);
	glEnable(GL_BLEND);

	glColor4us(0x2fff, 0x2fff, 0x4fff, 0x4fff);
	glRecti(x1, y2, x2, y1);

	/* This section draws the outline */
	glColor4us(0x2fff, 0x2fff, 0x4fff, 0x9fff);
	glLineWidth(2.0);
	glBegin(GL_LINE_LOOP);
	glVertex2i(x1, y1);
	glVertex2i(x2, y1);
	glVertex2i(x2, y2);
	glVertex2i(x1, y2);
	glEnd();

	/* Clean up */
	glColor4usv(defaultColor);
	glDisable(GL_BLEND);
	glEnableClientState(GL_TEXTURE_COORD_ARRAY);
	glPopMatrix();
}

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

	SHOT_SCREEN(s);

	ss->painted = FALSE;
	ss->x = s->x;
	ss->y = s->y;

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

	if (status && ss->grab && !ss->painted)
	{
		if (ss->grabIndex)
		{
			shotPaintOutline(s, sAttrib, output, FALSE);
		}
		else
		{
			int x1, x2, y1, y2;

			x1 = MIN(ss->x1, ss->x2);
			y1 = MIN(ss->y1, ss->y2);
			x2 = MAX(ss->x1, ss->x2);
			y2 = MAX(ss->y1, ss->y2);

			int w = x2 - x1;
			int h = y2 - y1;

			SHOT_DISPLAY(s->display);

			if (w && h)
			{
				GLubyte *buffer;
				char *dir = sd->opt[SHOT_DISPLAY_OPTION_DIR].value.s;

				buffer = malloc(sizeof(GLubyte) * w * h * 4);
				if (buffer)
				{
					struct dirent **namelist;
					int n;

					glReadPixels(x1, s->height - y2, w,
								 h, GL_RGBA,
								 GL_UNSIGNED_BYTE, (GLvoid *) buffer);

					n = scandir(dir, &namelist, shotFilter, shotSort);
					if (n >= 0)
					{
						char name[256];
						int number = 0;

						if (n > 0)
							sscanf(namelist
								   [n -
									1]->d_name, "screenshot%d.png", &number);

						number++;

						if (n)
							free(namelist);
						sprintf(name, "screenshot%d.png", number);

						if (!writeImageToFile(s->display, dir, name, "png",
											  w, h, buffer))
						{
							fprintf(stderr, "%s: failed to write "
									"screenshot image", getProgramName());
						}
					}
					else
					{
						perror(dir);
					}

					free(buffer);
				}
			}

			ss->grab = FALSE;
		}
	}

	return status;
}

static void
shotPaintTransformedScreen(CompScreen * s, const ScreenPaintAttrib * sa,
						   Region region, int output, unsigned int mask)
{
	SHOT_SCREEN(s);

	UNWRAP(ss, s, paintTransformedScreen);
	(*s->paintTransformedScreen) (s, sa, region, output, mask);
	WRAP(ss, s, paintTransformedScreen, shotPaintTransformedScreen);

	if (ss->grab && ss->grabIndex && (ss->x == s->x) && (ss->y == s->y)) {
		ss->painted = TRUE;
		shotPaintOutline(s, sa, output, TRUE);
	}
}

static void shotHandleMotionEvent(CompScreen * s, int xRoot, int yRoot)
{
	SHOT_SCREEN(s);

	/* update screenshot rectangle size */

	if (ss->grabIndex)
	{
		REGION reg;

		reg.rects = &reg.extents;
		reg.numRects = 1;

		reg.extents.x1 = MIN(ss->x1, ss->x2) - 1;
		reg.extents.y1 = MIN(ss->y1, ss->y2) - 1;
		reg.extents.x2 = MAX(ss->x1, ss->x2) + 1;
		reg.extents.y2 = MAX(ss->y1, ss->y2) + 1;

		damageScreenRegion(s, &reg);

		ss->x2 = xRoot;
		ss->y2 = yRoot;

		reg.extents.x1 = MIN(ss->x1, ss->x2) - 1;
		reg.extents.y1 = MIN(ss->y1, ss->y2) - 1;
		reg.extents.x2 = MAX(ss->x1, ss->x2) + 1;
		reg.extents.y2 = MAX(ss->y1, ss->y2) + 1;

		damageScreenRegion(s, &reg);

		damageScreen(s);
	}
}

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

	SHOT_DISPLAY(d);

	switch (event->type)
	{
	case MotionNotify:
		s = findScreenAtDisplay(d, event->xmotion.root);
		if (s)
			shotHandleMotionEvent(s, d->pointerX, d->pointerY);
		break;
	case EnterNotify:
	case LeaveNotify:
		s = findScreenAtDisplay(d, event->xcrossing.root);
		if (s)
			shotHandleMotionEvent(s, d->pointerX, d->pointerY);
	default:
		break;
	}

	UNWRAP(sd, d, handleEvent);
	(*d->handleEvent) (d, event);
	WRAP(sd, d, handleEvent, shotHandleEvent);
}

static void shotDisplayInitOptions(ShotDisplay * sd)
{
	CompOption *o;

	o = &sd->opt[SHOT_DISPLAY_OPTION_INITIATE];
	o->advanced = False;
	o->name = "initiate";
	o->group = N_("Basic");
	o->subGroup = N_("");
	o->displayHints = "";
	o->shortDesc = N_("Initiate Screenshot");
	o->longDesc = N_("Initiate Rectangular Screenshot.");
	o->type = CompOptionTypeAction;
	o->value.action.initiate = shotInitiate;
	o->value.action.terminate = shotTerminate;
	o->value.action.bell = FALSE;
	o->value.action.edgeMask = 0;
	o->value.action.type = CompBindingTypeButton;
	o->value.action.state = CompActionStateInitButton;
	o->value.action.button.modifiers = SHOT_INITIATE_BUTTON_MODIFIERS_DEFAULT;
	o->value.action.button.button = SHOT_INITIATE_BUTTON_DEFAULT;

	o = &sd->opt[SHOT_DISPLAY_OPTION_DIR];
	o->advanced = False;
	o->name = "directory";
	o->group = N_("Basic");
	o->subGroup = N_("");
	o->displayHints = "file;directory;";
	o->shortDesc = N_("Saving Directory");
	o->longDesc = N_("Put Screenshot images in this Directory.");
	o->type = CompOptionTypeString;
	o->value.s = strdup(SHOT_DIR_DEFAULT);
	o->rest.s.string = 0;
	o->rest.s.nString = 0;
}

static CompOption *shotGetDisplayOptions(CompDisplay * display, int *count)
{
	if (display)
	{
		SHOT_DISPLAY(display);

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

		shotDisplayInitOptions(sd);
		*count = NUM_OPTIONS(sd);
		return sd->opt;
	}
}

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

	SHOT_DISPLAY(display);

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

	switch (index)
	{
	case SHOT_DISPLAY_OPTION_INITIATE:
		if (setDisplayAction(display, o, value))
			return TRUE;
		break;
	case SHOT_DISPLAY_OPTION_DIR:
		if (compSetStringOption(o, value))
			return TRUE;
	default:
		break;
	}

	return FALSE;
}

static Bool shotInitDisplay(CompPlugin * p, CompDisplay * d)
{
	ShotDisplay *sd;

	sd = malloc(sizeof(ShotDisplay));
	if (!sd)
		return FALSE;

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

	WRAP(sd, d, handleEvent, shotHandleEvent);

	shotDisplayInitOptions(sd);

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

	return TRUE;
}

static void shotFiniDisplay(CompPlugin * p, CompDisplay * d)
{
	SHOT_DISPLAY(d);

	freeScreenPrivateIndex(d, sd->screenPrivateIndex);

	UNWRAP(sd, d, handleEvent);

	free(sd);
}

static Bool shotInitScreen(CompPlugin * p, CompScreen * s)
{
	ShotScreen *ss;

	SHOT_DISPLAY(s->display);

	ss = malloc(sizeof(ShotScreen));
	if (!ss)
		return FALSE;

	ss->grabIndex = 0;
	ss->grab = FALSE;

	addScreenAction(s, &sd->opt[SHOT_DISPLAY_OPTION_INITIATE].value.action);

	WRAP(ss, s, paintScreen, shotPaintScreen);
	WRAP(ss, s, paintTransformedScreen, shotPaintTransformedScreen);

	s->privates[sd->screenPrivateIndex].ptr = ss;

	return TRUE;
}

static void shotFiniScreen(CompPlugin * p, CompScreen * s)
{
	SHOT_SCREEN(s);
	SHOT_DISPLAY(s->display);

	UNWRAP(ss, s, paintScreen);
	UNWRAP(ss, s, paintTransformedScreen);

	removeScreenAction(s,
					   &sd->opt[SHOT_DISPLAY_OPTION_INITIATE].value.action);

	free(ss);
}

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

	return TRUE;
}

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

CompPluginDep shotDeps[] = {
	{CompPluginRuleAfterCategory, "imageformat"}
	,
};

static CompPluginVTable shotVTable = {
	"screenshot",
	N_("Screenshot"),
	N_("Screenshot plugin"),
	shotInit,
	shotFini,
	shotInitDisplay,
	shotFiniDisplay,
	shotInitScreen,
	shotFiniScreen,
	0,							/* InitWindow */
	0,							/* FiniWindow */
	shotGetDisplayOptions,
	shotSetDisplayOption,
	0,							/* GetScreenOptions */
	0,							/* SetScreenOption */
	shotDeps,
	sizeof(shotDeps) / sizeof(shotDeps[0]),
	0,
	0,
	BERYL_ABI_INFO,
	"beryl-plugins",
	"misc",
	0,
	0,
	False,
};

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


syntax highlighted by Code2HTML, v. 0.9.1