All pastes #391360 Raw Edit

Anonymous

public text v1 · immutable
·published 2007-03-12 02:32 UTC
rendered paste body
/**
*
* Beryl pager plugin
*
* pager.c
*
* Copyright (c) 2007 Atie H. <atie.at.matrix@gmail.com>
*
* 
* 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.
*
**/
/*
Pager Features:
	- pager by hsize * vsize
	- pager orientation
	- toggle to display pager - default super+alt+p
	
Pager TODO:
	- draggable pager?
	- size option for pager / resize w/ mouse if possible
	- repaint pager when hsize or vsize updated
	- manipulate vsize if =1 by dividing hsize(>=4) so 4x1 becomes 2x2
	- perspective view of pager
	- miniview of viewports
	- switch viewport by click / swap viewport by drag-out : wall's job?
	- animation for toggle pager ?
	- animation for switch/swap viewport
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>

#include <beryl.h>

#include <cairo-xlib-xrender.h>
#include <cairo.h>

#define PI 3.14159265359f

#define PAGER_TOGGLE_KEY_DEFAULT "p"
#define PAGER_TOGGLE_MODIFIERS_DEFAULT (CompAltMask | CompSuperMask)

static int displayPrivateIndex;

enum {
	PAGER_DISPLAY_OPTION_TOGGLE,
	PAGER_DISPLAY_OPTION_WIDTH,
	PAGER_DISPLAY_OPTION_HEIGHT,
	PAGER_DISPLAY_OPTION_GRAVITY,
	PAGER_DISPLAY_OPTION_OFFSET,
	PAGER_DISPLAY_OPTION_NUM
};

typedef enum _PagerGravity
{
	PagerGravityNorthWest = 0,
	PagerGravityNorthEast,
	PagerGravitySouthEast,
	PagerGravitySouthWest,
	PagerGravityNum
} PagerGravity;

#define PAGER_GRAVITY_DEFAULT PagerGravitySouthEast

static char *pagerGravities[] = {
	N_("NorthWest"),
	N_("NorthEast"),
	N_("SouthEast"),
	N_("SouthWest")
};

#define PAGER_GRAVITY_OFFSET_DEFAULT 5
#define PAGER_GRAVITY_OFFSET_MIN 0
#define PAGER_GRAVITY_OFFSET_MAX 50

#define PAGER_WIDTH_DEFAULT 300
#define PAGER_WIDTH_MIN 50
#define PAGER_WIDTH_MAX 500

#define PAGER_HEIGHT_DEFAULT 200
#define PAGER_HEIGHT_MIN 50
#define PAGER_HEIGHT_MAX 500

typedef struct _PagerCairoContext
{
	Pixmap pixmap;
	CompTexture texture;
	cairo_surface_t *surface;
	cairo_t *cr;
	int width;
	int height;
} PagerCairoContext;

typedef struct _PagerDisplay
{
	int screenPrivateIndex;
	HandleEventProc handleEvent;

	Bool togglePager;
	int pagerGravity;
	int offset;
	int pagerWidth;
	int pagerHeight;

	CompOption opt[PAGER_DISPLAY_OPTION_NUM];
} PagerDisplay;

typedef struct _PagerScreen
{
	DonePaintScreenProc donePaintScreen;
	PaintScreenProc paintScreen;
	PreparePaintScreenProc preparePaintScreen;
	PaintTransformedScreenProc paintTransformedScreen;

	float firstViewportX;
	float firstViewportY;
	float viewportWidth;
	float viewportHeight;
	float viewportBorder;

	WindowPaintAttrib mSAttribs;
	float mSzCamera;

	PagerCairoContext *pagerContext;
	PagerCairoContext *thumbContext;
} PagerScreen;

/* Helpers */
#define GET_PAGER_DISPLAY(d) \
		((PagerDisplay *) (d)->privates[displayPrivateIndex].ptr)
#define PAGER_DISPLAY(d) \
		PagerDisplay *pd = GET_PAGER_DISPLAY(d);

#define GET_PAGER_SCREEN(s, pd) \
		((PagerScreen *) (s)->privates[(pd)->screenPrivateIndex].ptr)
#define PAGER_SCREEN(s) \
		PagerScreen *ps = GET_PAGER_SCREEN(s, GET_PAGER_DISPLAY(s->display));

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

static void pagerDrawPager(CompScreen *s)
{
	PAGER_SCREEN(s);

	cairo_t * cr = ps->pagerContext->cr;

	cairo_save(cr);
	cairo_set_operator(cr, CAIRO_OPERATOR_CLEAR);
	cairo_paint(cr);
	cairo_restore(cr);
	cairo_set_operator(cr, CAIRO_OPERATOR_OVER);
	cairo_save(cr);

	int hsize = s->hsize;
	int vsize = s->vsize;

	float border = 10.0f;
	float outline = 2.0f;

	float width = (float) ps->pagerContext->width;
	width -= outline;
	float height = (float) ps->pagerContext->height;
	height -= outline;

	cairo_pattern_t *pattern;

	cairo_translate(cr, outline/2.0f, outline/2.0f);

	// set the pattern for the pager's background
	pattern = cairo_pattern_create_linear(0, 0, width, height);
	cairo_pattern_add_color_stop_rgba(pattern, 0.0f, 0.80, 0.80, 0.90 ,0.85);
	cairo_pattern_add_color_stop_rgba(pattern, 0.65f, 0.95, 0.95, 1.0f, 0.85);
	cairo_pattern_add_color_stop_rgba(pattern, 0.85f, 0.85, 0.85, 0.85 ,0.85);
	cairo_set_source(cr, pattern);

	// draw the border's shape
	cairo_arc(cr, border, border, border, PI, 1.5f*PI);
	cairo_arc(cr, border+width-2*border, border, border, 1.5f*PI, 2.0*PI);
	cairo_arc(cr, width-border, height-border, border, 0,  PI/2.0f);
	cairo_arc(cr, border, height-border, border,  PI/2.0f, PI);
	cairo_close_path(cr);

	// apply pattern to pager
	cairo_fill_preserve(cr);

	// draw an outline
	cairo_set_line_width(cr, outline);
	cairo_set_source_rgba(cr, 0.2,0.2,0.2,0.85);
	cairo_stroke(cr);

	cairo_pattern_destroy(pattern);

	float viewportWidth  = floor((width  - border*(s->hsize+1))/hsize);
	float viewportHeight = floor((height - border*(s->vsize+1))/vsize);
	ps->viewportWidth = viewportWidth;
	ps->viewportHeight = viewportHeight;
	ps->viewportBorder = border;

	cairo_translate(cr, border, border);

	int i, j;
	float vpX, vpY;

	for (i = 0; i < hsize; i++)
	{
		for (j = 0; j < vsize; j++)
		{
			vpX = i*(viewportWidth+border);
			vpY = j*(viewportHeight+border);

			// this cuts a hole into our background
			cairo_set_operator(cr, CAIRO_OPERATOR_OVER);
			cairo_set_source_rgba(cr, 0.0, 0.0, 0.0, 1.0);
			cairo_rectangle(cr, vpX, vpY, viewportWidth, viewportHeight);
			cairo_fill_preserve(cr);
			cairo_set_operator(cr, CAIRO_OPERATOR_XOR);
			cairo_fill(cr);
		}
	}
	
	cairo_restore(cr);

}

static void pagerDrawThumb(CompScreen *s)
{
	PAGER_SCREEN(s);

	cairo_t * cr = ps->thumbContext->cr;

	cairo_save(cr);
	cairo_set_operator(cr, CAIRO_OPERATOR_CLEAR);
	cairo_paint(cr);
	cairo_restore(cr);
	cairo_set_operator(cr, CAIRO_OPERATOR_OVER);
	cairo_save(cr);

	float border = 10.0f;
	float outline = 2.0f;

	float width  = (float) ps->thumbContext->width;
	float height = (float) ps->thumbContext->height;

	ps->viewportWidth = width;
	ps->viewportHeight = height;
	ps->viewportBorder = border;
	
	width -= outline;
	height -= outline;

	cairo_translate(cr, outline/2.0f, outline/2.0f);
	cairo_pattern_t *pattern;

	pattern = cairo_pattern_create_linear(0, 0, width, height);
	cairo_pattern_add_color_stop_rgba(pattern, 0.0f, 0.2, 0.2, 0.2 ,0.35);
	cairo_pattern_add_color_stop_rgba(pattern, 1.0f, 0.25, 0.25, 0.25, 0.25);

	// apply the patter for thumb background
	cairo_set_source(cr, pattern);
	cairo_rectangle(cr, 0, 0, width, height);
	cairo_fill_preserve(cr);

	cairo_set_line_width(cr, outline);
	cairo_set_source_rgba(cr, 0.2, 0.2, 0.2, 0.65);
	cairo_stroke(cr);

	cairo_pattern_destroy(pattern);

	cairo_restore(cr);

}

static void pagerSetupCairoContext(CompScreen *s, PagerCairoContext *context)
{
	XRenderPictFormat * format;
	Screen * screen;

	screen = ScreenOfDisplay(s->display->display, s->screenNum);

	int width, height;
	width = context->width;
	height = context->height;

	initTexture(s, &context->texture);

	format = XRenderFindStandardFormat(s->display->display,
									PictStandardARGB32);

	context->pixmap = XCreatePixmap(s->display->display, s->root, 
								width, height, 32);

	if (!bindPixmapToTexture(s, &context->texture, context->pixmap, 
						width, height, 32))
		printf("Couldn't create cairo context for pager\n");

	context->surface = 
		cairo_xlib_surface_create_with_xrender_format(s->display->display,
													context->pixmap,
													screen, format,
													width, height);
	context->cr = cairo_create(context->surface);
	
	cairo_t *cr = context->cr;
	cairo_save(cr);
	cairo_set_operator(cr, CAIRO_OPERATOR_CLEAR);
	cairo_paint(cr);
	cairo_restore(cr);
	cairo_set_operator(cr, CAIRO_OPERATOR_OVER);
}

static void pagerDestroyCairoContext(CompScreen *s, PagerCairoContext *context)
{
	if (context->cr)
		cairo_destroy(context->cr);

	if (context->surface)
		cairo_surface_destroy(context->surface);

	finiTexture(s, &context->texture);

	if (context->pixmap)
		XFreePixmap(s->display->display, context->pixmap);
}

static void pagerDrawCairoTextureOnScreen(CompScreen *s,
										int output, Region region)
{
	PAGER_SCREEN(s);
	PAGER_DISPLAY(s->display);

	glPushMatrix();
	prepareXCoords(s, output, -DEFAULT_Z_CAMERA);
	glDisableClientState(GL_TEXTURE_COORD_ARRAY);
	glEnable(GL_BLEND);

	float border = 10.0f;

	float width = (float) ps->pagerContext->width;
	float height = (float) ps->pagerContext->height;

	float topleftx = 0;
	float toplefty = 0;
	
	//?FIXME somehow need to read display options here again
	pd->offset = pd->opt[PAGER_DISPLAY_OPTION_OFFSET].value.i;
	pd->pagerWidth = pd->opt[PAGER_DISPLAY_OPTION_WIDTH].value.i;
	pd->pagerHeight = pd->opt[PAGER_DISPLAY_OPTION_HEIGHT].value.i;
	//FIXME, gravity doesn't the value from settings at init
	pd->pagerGravity = PagerGravitySouthEast;
	
	
	switch (pd->pagerGravity)  
	{
	case PagerGravityNorthWest:
		topleftx = pd->offset;
		toplefty = pd->offset;
		break;
	case PagerGravityNorthEast:
		topleftx = s->width - pd->pagerWidth - pd->offset; 
		toplefty = pd->offset;
		break;
	case PagerGravitySouthEast:
		topleftx = s->width - pd->pagerWidth - pd->offset;
		toplefty = s->height - pd->pagerHeight - pd->offset;
		break;
	case PagerGravitySouthWest:
		topleftx = pd->offset;
		toplefty = s->height - pd->pagerHeight - pd->offset;
		break;
	default:
		break;
	}

	//draw pager
	enableTexture(s, &ps->pagerContext->texture, COMP_TEXTURE_FILTER_GOOD);

	CompMatrix matrix = ps->pagerContext->texture.matrix;
	matrix.x0 -= topleftx * matrix.xx;
	matrix.y0 -= toplefty * matrix.yy;

	BOX box;
	box.x1 = topleftx;
	box.x2 = box.x1 + width;
	box.y1 = toplefty;
	box.y2 = box.y1 + height;

	glBegin(GL_QUADS);
	glTexCoord2f(COMP_TEX_COORD_X(&matrix, box.x1), COMP_TEX_COORD_Y(&matrix, box.y2));
	glVertex2i(box.x1, box.y2);
	glTexCoord2f(COMP_TEX_COORD_X(&matrix, box.x2), COMP_TEX_COORD_Y(&matrix, box.y2));
	glVertex2i(box.x2, box.y2);
	glTexCoord2f(COMP_TEX_COORD_X(&matrix, box.x2), COMP_TEX_COORD_Y(&matrix, box.y1));
	glVertex2i(box.x2, box.y1);
	glTexCoord2f(COMP_TEX_COORD_X(&matrix, box.x1), COMP_TEX_COORD_Y(&matrix, box.y1));
	glVertex2i(box.x1, box.y1);     
	glEnd();

	disableTexture(s, &ps->pagerContext->texture);

	//draw thumb
	width = (float) ps->thumbContext->width;
	height = (float) ps->thumbContext->height;

	int i,j;

	enableTexture(s, &ps->thumbContext->texture, COMP_TEXTURE_FILTER_GOOD);
	glBegin(GL_QUADS);
	for (i = 0; i < s->hsize; i++)
	{
		for (j = 0; j < s->vsize; j++)
		{
			box.x1 = i*(width+border);
			box.x1 += topleftx + border;
			box.x2 = box.x1 + width;
			box.y1 = j*(height+border);
			box.y1 += toplefty + border;
			box.y2 = box.y1 + height;

			matrix = ps->thumbContext->texture.matrix;
			matrix.x0 -= box.x1 * matrix.xx;
			matrix.y0 -= box.y1 * matrix.yy;

			glTexCoord2f(COMP_TEX_COORD_X(&matrix, box.x1), COMP_TEX_COORD_Y(&matrix, box.y2));
			glVertex2i(box.x1, box.y2);
			glTexCoord2f(COMP_TEX_COORD_X(&matrix, box.x2), COMP_TEX_COORD_Y(&matrix, box.y2));
			glVertex2i(box.x2, box.y2);
			glTexCoord2f(COMP_TEX_COORD_X(&matrix, box.x2), COMP_TEX_COORD_Y(&matrix, box.y1));
			glVertex2i(box.x2, box.y1);
			glTexCoord2f(COMP_TEX_COORD_X(&matrix, box.x1), COMP_TEX_COORD_Y(&matrix, box.y1));
			glVertex2i(box.x1, box.y1);
		}
	}
	glEnd();
	disableTexture(s, &ps->thumbContext->texture);

	glDisable(GL_BLEND);
	glEnableClientState(GL_TEXTURE_COORD_ARRAY);
	glPopMatrix();
	screenTexEnvMode(s, GL_REPLACE);
	glColor4usv(defaultColor);
}

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

	PAGER_SCREEN(s);
	PAGER_DISPLAY(s->display);

	UNWRAP(ps, s, paintScreen);
	status = (*s->paintScreen) (s, sAttrib, transform, region, output, mask);
	WRAP(ps, s, paintScreen, pagerPaintScreen);

	if (pd->togglePager)
	{
		pagerDrawCairoTextureOnScreen(s, output, region);
	
		int i,j;
		int origVX = s->x;
		int origVY = s->y;

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

		for (j = 0; j < s->vsize; j++)
		{
			for (i = 0; i < s->hsize; i++)
			{
				float mx = ps->firstViewportX + 
						(i * (ps->viewportWidth + ps->viewportBorder));
				float my = ps->firstViewportY + 
						(i * (ps->viewportHeight + ps->viewportBorder));
				float mw = ps->viewportWidth;
				float mh = ps->viewportHeight;

				ps->mSAttribs.xTranslate = mx / s->outputDev[output].width;
				ps->mSAttribs.yTranslate = -my / s->outputDev[output].height;

				ps->mSAttribs.xScale = mw / s->width;
				ps->mSAttribs.yScale = mh / s->height;
/* FIXME
				(*s->paintTransformedScreen) (s, sAttrib, transform, region,
										output, mask | PAINT_SCREEN_TRANSFORMED_MASK);
*/
				moveScreenViewport(s, -1, 0, FALSE);
			}
			moveScreenViewport(s, 0, -1, FALSE);
		}
		moveScreenViewport(s, -origVX, -origVY, FALSE);
	}
	
	return status;
}

static void pagerPaintTransformedScreen(CompScreen * s,
									const ScreenPaintAttrib * sAttrib,
									const CompTransform *transform,
									Region region, int output, unsigned int mask)
{
	PAGER_SCREEN(s);
	
	CompTransform sTransform = *transform;

	//move each screen to the correct output position
	matrixTranslate(&sTransform, -s->outputDev[output].region.extents.x1 /
				s->outputDev[output].width,
				s->outputDev[output].region.extents.y1 /
				s->outputDev[output].height, 0.0f);
	matrixTranslate(&sTransform, 0.0f, 0.0f, -DEFAULT_Z_CAMERA);

	matrixTranslate(&sTransform, ps->mSAttribs.xTranslate, ps->mSAttribs.yTranslate, ps->mSzCamera);

	// move orgin to top left
	matrixTranslate(&sTransform, -0.5f, 0.5f, 0.0f);

	matrixScale(&sTransform, ps->mSAttribs.xScale, ps->mSAttribs.yScale, 1.0);

	// revert prepareXCoords region shift. Now all screens display the same
	matrixTranslate(&sTransform, 0.5f, 0.5f, DEFAULT_Z_CAMERA);
	matrixTranslate(&sTransform, s->outputDev[output].region.extents.x1 /
				s->outputDev[output].width,
				-s->outputDev[output].region.extents.y2 /
				s->outputDev[output].height, 0.0f);

	UNWRAP(ps, s, paintTransformedScreen);
	(*s->paintTransformedScreen) (s, sAttrib, &sTransform, &s->region, output, mask);
	WRAP(ps, s, paintTransformedScreen, pagerPaintTransformedScreen);
}

static void pagerHandleEvent(CompDisplay * d, XEvent * event)
{
	PAGER_DISPLAY(d);

	UNWRAP(pd, d, handleEvent);
	(*d->handleEvent) (d, event);
	WRAP(pd, d, handleEvent, pagerHandleEvent);
}

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

	PAGER_DISPLAY(d);
	pd->togglePager = !pd->togglePager;

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

	return TRUE;
}

static void pagerDisplayInitOptions(PagerDisplay *pd)
{
	CompOption *o;

	o = &pd->opt[PAGER_DISPLAY_OPTION_TOGGLE];
	o->advanced = False;
	o->name = "toggle_display";
	o->group = N_("Bindings");
	o->subGroup = N_("");
	o->displayHints = "";
	o->shortDesc = N_("Toggle Pager");
	o->longDesc = N_("Toggle to display pager.");
	o->type = CompOptionTypeAction;
	o->value.action.initiate = pagerToggle;
	o->value.action.terminate = 0;
	o->value.action.bell = 0;
	o->value.action.edgeMask = 0;
	o->value.action.state = CompActionStateInitKey;
	o->value.action.type = CompBindingTypeKey;
	o->value.action.key.modifiers = PAGER_TOGGLE_MODIFIERS_DEFAULT;
	o->value.action.key.keysym = XStringToKeysym(PAGER_TOGGLE_KEY_DEFAULT);

	o = &pd->opt[PAGER_DISPLAY_OPTION_WIDTH];
	o->advanced = False;
	o->name = "pager_width";
	o->group = N_("Pager");
	o->subGroup = N_("");
	o->displayHints = "";
	o->shortDesc = N_("Pager Width");
	o->longDesc = N_("Pager Width.");
	o->type = CompOptionTypeInt;
	o->value.i = PAGER_WIDTH_DEFAULT;
	o->rest.i.min = PAGER_WIDTH_MIN;
	o->rest.i.max = PAGER_WIDTH_MAX;

	o = &pd->opt[PAGER_DISPLAY_OPTION_HEIGHT];
	o->advanced = False;
	o->name = "pager_height";
	o->group = N_("Pager");
	o->subGroup = N_("");
	o->displayHints = "";
	o->shortDesc = N_("Pager Height");
	o->longDesc = N_("Pager Height.");
	o->type = CompOptionTypeInt;
	o->value.i = PAGER_HEIGHT_DEFAULT;
	o->rest.i.min = PAGER_HEIGHT_MIN;
	o->rest.i.max = PAGER_HEIGHT_MAX;

	o = &pd->opt[PAGER_DISPLAY_OPTION_GRAVITY];
	o->advanced = False;
	o->name = "pager_gravity";
	o->group = N_("Pager");
	o->subGroup = N_("");
	o->displayHints = "";
	o->shortDesc = N_("Pager Gravity");
	o->longDesc = N_("Orientation of pager.");
	o->type = CompOptionTypeString;
	o->value.s = strdup(pagerGravities[PAGER_GRAVITY_DEFAULT]);
	o->rest.s.string = pagerGravities;
	o->rest.s.nString = PagerGravityNum;

	o = &pd->opt[PAGER_DISPLAY_OPTION_OFFSET];
	o->advanced = False;
	o->name = "offset";
	o->group = N_("Pager");
	o->subGroup = N_("");
	o->displayHints = "";
	o->shortDesc = N_("Offset");
	o->longDesc = N_("Offset from corner.");
	o->type = CompOptionTypeInt;
	o->value.i = PAGER_GRAVITY_OFFSET_DEFAULT;
	o->rest.i.min = PAGER_GRAVITY_OFFSET_MIN;
	o->rest.i.max = PAGER_GRAVITY_OFFSET_MAX;

}

static CompOption *pagerGetDisplayOptions(CompDisplay * d, int *count)
{
	if (d)
	{
		PAGER_DISPLAY(d);
		*count = NUM_OPTIONS(pd);
		return pd->opt;
	}
	else
	{
		PagerDisplay *pd = malloc(sizeof(PagerDisplay));

		pagerDisplayInitOptions(pd);
		*count = NUM_OPTIONS(pd);
		return pd->opt;
	}
}

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

	PAGER_DISPLAY(d);

	o = compFindOption(pd->opt, NUM_OPTIONS(pd), name, &index);

	if (!o)
		return FALSE;
	
	switch (index)
	{
		case PAGER_DISPLAY_OPTION_TOGGLE:
			if (setDisplayAction(d, o, value))
				return TRUE;
			break;
		case PAGER_DISPLAY_OPTION_WIDTH:
			if (compSetIntOption(o, value))
			{
				pd->pagerWidth = o->value.i;
				return TRUE;
			}
			break;
		case PAGER_DISPLAY_OPTION_HEIGHT:
			if (compSetIntOption(o, value))
			{
				pd->pagerHeight = o->value.i;
				return TRUE;
			}
			break;
		case PAGER_DISPLAY_OPTION_GRAVITY:
			if (compSetStringOption(o, value))
			{
				int i;
				for (i = 0; i < PagerGravityNum; i++)
				{
					if (strcmp(o->value.s, pagerGravities[i]) == 0)
					{
						pd->pagerGravity = i;
						return TRUE;
					}
				}
			}
			break;
		case PAGER_DISPLAY_OPTION_OFFSET:
			if (compSetIntOption(o, value))
			{
				pd->offset = o->value.i;
				return TRUE;
			}
			break;
		default:
			break;
	}
	return FALSE;
}

static Bool pagerInitScreen(CompPlugin * p, CompScreen * s)
{
	PagerScreen *ps;

	PAGER_DISPLAY(s->display);

	ps = malloc(sizeof(PagerScreen));

	if (!ps)
		return FALSE;

	addScreenAction(s, &pd->opt[PAGER_DISPLAY_OPTION_TOGGLE].value.action);

	WRAP(ps, s, paintScreen, pagerPaintScreen);
	
	s->privates[pd->screenPrivateIndex].ptr = ps;

	ps->viewportWidth = 0.0f;
	ps->viewportHeight = 0.0f;
	ps->viewportBorder = 0.0f;
		
	ps->pagerContext = (PagerCairoContext*) malloc(sizeof(PagerCairoContext));
	ps->pagerContext->width = pd->opt[PAGER_DISPLAY_OPTION_WIDTH].value.i;
	ps->pagerContext->height = pd->opt[PAGER_DISPLAY_OPTION_HEIGHT].value.i;

	pagerSetupCairoContext(s, ps->pagerContext);
	pagerDrawPager(s);

	float border = 10.0f;

	ps->thumbContext = (PagerCairoContext*) malloc(sizeof(PagerCairoContext));
	ps->thumbContext->width = (int) (pd->opt[PAGER_DISPLAY_OPTION_WIDTH].value.i - 
							border*(s->hsize+1)) / s->hsize;
	ps->thumbContext->height = (int) (pd->opt[PAGER_DISPLAY_OPTION_HEIGHT].value.i - 
							border*(s->vsize+1)) / s->vsize;
	pagerSetupCairoContext(s, ps->thumbContext);
	pagerDrawThumb(s);

	return TRUE;
}

static void pagerFiniScreen(CompPlugin * p, CompScreen * s)
{
	PAGER_SCREEN(s);
	PAGER_DISPLAY(s->display);

	pagerDestroyCairoContext(s, ps->pagerContext);
	pagerDestroyCairoContext(s, ps->thumbContext);

	removeScreenAction(s, &pd->opt[PAGER_DISPLAY_OPTION_TOGGLE].value.action);

	UNWRAP(ps, s, paintScreen); 

	free(ps);
}

static Bool pagerInitDisplay(CompPlugin * p, CompDisplay * d)
{
	PagerDisplay *pd;

	pd = malloc(sizeof(PagerDisplay));
	if (!pd)
		return FALSE;

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

	pagerDisplayInitOptions(pd);
	pd->togglePager = TRUE;

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

	return TRUE;
}

static void pagerFiniDisplay(CompPlugin * p, CompDisplay * d)
{
	PAGER_DISPLAY(d);

	UNWRAP(pd, d, handleEvent);

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

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

	if (displayPrivateIndex < 0)
		return FALSE;
	
	return TRUE;
}

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

CompPluginVTable pagerVTable = {
	"pager",
	N_("Pager"),
	N_("Beryl pager plugin"),
	pagerInit,
	pagerFini,
	pagerInitDisplay,
	pagerFiniDisplay,
	pagerInitScreen,
	pagerFiniScreen,
	0,	/* pagerInitWindow */
	0,	/* pagerFiniWindow */
	pagerGetDisplayOptions,
	pagerSetDisplayOption,
	0,	/* pagerGetScreenOptions */
	0,	/* pagerSetScreenOption */
	0,	/* updateLibraryFunction */
	0,	/* deps */
	0,  /* nDeps */
	0,	/* features */
	0,	/* nFeatures */
	BERYL_ABI_INFO,	
	"beryl-plugins-unsupported",	/* gettext domain */
	"desktop",	/* category */
	0,	/* groupDesc */
	0,	/*nGroupDesc */
	False,	/* defaultEnabled */
};

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