/*
* 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 = ®.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, ®);
}
}
}
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 = ®.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, ®);
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, ®);
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