/*
* Copyright © 2005 Novell, Inc.
*
* Permission to use, copy, modify, distribute, and sell this software
* and its documentation for any purpose is hereby granted without
* fee, provided that the above copyright notice appear in all copies
* and that both that copyright notice and this permission notice
* appear in supporting documentation, and that the name of
* Novell, Inc. not be used in advertising or publicity pertaining to
* distribution of the software without specific, written prior permission.
* Novell, Inc. makes no representations about the suitability of this
* software for any purpose. It is provided "as is" without express or
* implied warranty.
*
* NOVELL, INC. DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
* INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN
* NO EVENT SHALL NOVELL, INC. BE LIABLE FOR ANY SPECIAL, INDIRECT OR
* CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
* OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
* NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
* WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
*
* Authors:
* - Original zoom plugin; David Reveman <davidr@novell.com>
* - Most features beyond basic zoom;
* Kristian Lyngstol <kristian@bohemians.org>
*
* This plugin offers basic zoom, and does not require input to be disabled
* while zooming. Key features of the new version is a hopefully more generic
* interface to the basic zoom features, allowing advanced control of the
* plugin based on events such as focus changes, cursor movement, manual
* panning and similar. This plugin has also been inspired by the inputzoom.c
* plugin written for the Beryl project and copyrighted to Dennis Kasprzyk and
* Quinn Storm.
*
* Note on actual zoom process
*
* This plug-in has been refactored to use scaling instead of z-translation
* for the actual zoom operation. This presented only minor diffs, but some
* minor tasks are left to sort out as of this writing.
*
* The animation is done in preparePaintScreen, while instant movements
* are done by calling updateActualTranslate () after updating the
* translations. This causes [xyz]trans to be re-calculated. We keep track
* of each head separately; It does not make sense to zoom a window across
* multiple heads in most (or all?) cases.
*
* Note on input
*
* We can not redirect input yet, but this plugin offers two fundamentally
* different approaches to achieve input:
*
* Making sure the real mouse is always at the correct place. This is
* the traditional way we did input zoom in Beryl. This also means that
* whenever you move the mouse, we have to move the zoomed area, and that
* if we are working on a window that's in the upper right corner, the
* mouse has to be in the upper right corner too. This is probably the
* techincally best solution, as it is fairly simple, but it has the obvious
* weakness that mouse movement equals panning, so you can't keep a single
* window fully visible and interact with it without parts of it going
* off screen.
*
* The second method involves hiding the real cursor and showing a "fake"
* one. This involves using XFixes to first get hold of the actual cursor
* (ie: normal pointer, resize-pointer, grab-pointer, etc) and XFixes to
* hide the original. We then convert the real mouse coordinates to their
* corresponding values zoomed, and draw the cursor. This gives a splendid
* effect and is fairly close to what we will be able to do with input
* redirection, but has a few weaknesses, the biggest being use of
* XFixes which tends to bug up (Loose the cursor entirely, even when
* we aren't hiding it, happens on firefox-loading cursors for instance).
* The second, and rather minor weakness is that the sensitivity is rather
* ... crazy. If you zoom in so your 1024-wide screen is showing 102 pixels,
* you only have to move the mouse 1/10th of the distance you normally would
* to move it across the visible area.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <sys/time.h>
#include <time.h>
#include <compiz.h>
static CompMetadata zoomMetadata;
static int displayPrivateIndex;
typedef enum _ZdOpt
{
DOPT_INITIATE = 0,
DOPT_IN,
DOPT_OUT,
DOPT_SPECIFIC_1,
DOPT_SPECIFIC_2,
DOPT_SPECIFIC_3,
DOPT_SPECIFIC_LEVEL_1,
DOPT_SPECIFIC_LEVEL_2,
DOPT_SPECIFIC_LEVEL_3,
DOPT_SPECIFIC_TARGET_FOCUS,
DOPT_PAN_LEFT,
DOPT_PAN_RIGHT,
DOPT_PAN_UP,
DOPT_PAN_DOWN,
DOPT_FIT_TO_WINDOW,
DOPT_CENTER_MOUSE,
DOPT_FIT_TO_ZOOM,
DOPT_ENSURE_VISIBILITY,
DOPT_SET_ZOOM_AREA,
DOPT_LOCK_ZOOM,
DOPT_NUM
} ZoomDisplayOptions;
typedef enum _ZsOpt
{
SOPT_FOLLOW_FOCUS = 0,
SOPT_SPEED,
SOPT_TIMESTEP,
SOPT_ZOOM_FACTOR,
SOPT_FILTER_LINEAR,
SOPT_SYNC_MOUSE,
SOPT_POLL_INTERVAL,
SOPT_FOCUS_DELAY,
SOPT_PAN_FACTOR,
SOPT_FOCUS_FIT_WINDOW,
SOPT_ALLWAYS_FOCUS_FIT_WINDOW,
SOPT_SCALE_MOUSE,
SOPT_HIDE_ORIGINAL_MOUSE,
SOPT_RESTRAIN_MOUSE,
SOPT_RESTRAIN_MARGIN,
SOPT_MOUSE_PAN,
SOPT_NUM
} ZoomScreenOptions;
typedef struct _CursorTexture
{
Bool isSet;
GLuint texture;
CompScreen *screen;
int width;
int height;
int hotX;
int hotY;
} CursorTexture;
typedef struct _ZoomDisplay {
int screenPrivateIndex;
HandleEventProc handleEvent;
Bool fixesSupported;
int fixesEventBase;
int fixesErrorBase;
Bool canHideCursor;
CompOption opt[DOPT_NUM];
} ZoomDisplay;
/* Stores an actual zoom-setup. This can later be used to store/restore zoom
* areas on the fly.
*
* [xy]Translate and newZoom are target values, and [xy]Translate always ranges
* from -0.5 to 0.5.
*
* currentZoom is actual zoomed value
*
* real[XY]Translate are the currently used values in the same range as
* [xy]Translate, and [xy]trans is adjusted for the zoom level in place.
* [xyz]trans should never be modified except in updateActualTranslates()
*
* viewport is a mask of the viewport, or ~0 for "any".
*/
typedef struct _ZoomArea {
int output;
unsigned long int viewport;
GLfloat currentZoom;
GLfloat newZoom;
GLfloat xVelocity;
GLfloat yVelocity;
GLfloat zVelocity;
GLfloat xTranslate;
GLfloat yTranslate;
GLfloat realXTranslate;
GLfloat realYTranslate;
GLfloat xtrans;
GLfloat ytrans;
Bool locked;
} ZoomArea;
typedef struct _ZoomScreen {
PreparePaintScreenProc preparePaintScreen;
DonePaintScreenProc donePaintScreen;
PaintOutputProc paintOutput;
CompOption opt[SOPT_NUM];
CompTimeoutHandle mouseIntervalTimeoutHandle;
ZoomArea *zooms;
int nZooms;
int mouseX;
int mouseY;
unsigned long int grabbed;
time_t lastChange;
CursorTexture cursor;
Bool cursorInfoSelected;
Bool cursorHidden;
} ZoomScreen;
/* These prototypes must be pre-defined since they cross-refference eachother
* and thus makes it impossible to order them in a fashion that avoids this.
*/
static void updateMousePosition (CompScreen *s);
static void syncCenterToMouse (CompScreen *s);
static Bool updateMouseInterval (void *vs);
static void cursorZoomActive (CompScreen *s);
static void cursorZoomInactive (CompScreen *s);
static void drawCursor (CompScreen *s, CompOutput *output, const CompTransform
*transform);
static void restrainCursor (CompScreen *s, int out);
static Bool fetchMousePosition (CompScreen *s);
#define GET_ZOOM_DISPLAY(d) \
((ZoomDisplay *) (d)->privates[displayPrivateIndex].ptr)
#define ZOOM_DISPLAY(d) \
ZoomDisplay *zd = GET_ZOOM_DISPLAY (d)
#define GET_ZOOM_SCREEN(s, zd) \
((ZoomScreen *) (s)->privates[(zd)->screenPrivateIndex].ptr)
#define ZOOM_SCREEN(s) \
ZoomScreen *zs = GET_ZOOM_SCREEN (s, GET_ZOOM_DISPLAY (s->display))
#define NUM_OPTIONS(s) (sizeof ((s)->opt) / sizeof (CompOption))
/* Checks if a specific screen grab exist. DO NOT USE THIS.
* This is a temporary fix that WILL be removed asap.
* See comments in drawCursor.
*/
static inline Bool
dontuseScreengrabExist (CompScreen * s, char * grab)
{
int i;
for (i = 0; i < s->maxGrab; i++)
if (s->grabs[i].active && !strcmp(s->grabs[i].name, grab))
return TRUE;
return FALSE;
}
/* Check if the output is valid
*/
static inline Bool
outputIsZoomArea (CompScreen *s, int out)
{
ZOOM_SCREEN (s);
if (out < 0 || out >= zs->nZooms)
return FALSE;
return TRUE;
}
/* Check if zoom is active on the output specified */
static inline Bool
isActive (CompScreen *s, int out)
{
ZOOM_SCREEN (s);
if (!outputIsZoomArea (s, out))
return FALSE;
if (zs->grabbed & (1 << zs->zooms[out].output))
return TRUE;
return FALSE;
}
/* Check if we are zoomed out and not going anywhere
* (similar to isActive but based on actual zoom, not grab)
*/
static inline Bool
isZoomed (CompScreen *s, int out)
{
ZOOM_SCREEN (s);
if (!outputIsZoomArea (s, out))
return FALSE;
if (zs->zooms[out].currentZoom != 1.0f || zs->zooms[out].newZoom != 1.0f)
return TRUE;
if (zs->zooms[out].zVelocity != 0.0f)
return TRUE;
return FALSE;
}
/* Update/set translations based on zoom level and real translate.
*/
static void
updateActualTranslates (ZoomArea *za)
{
za->xtrans = -za->realXTranslate * (1.0f - za->currentZoom);
za->ytrans = za->realYTranslate * (1.0f - za->currentZoom);
}
/* Returns true if the head in question is currently moving.
* Since we don't always bother resetting everything when
* canceling zoom, we check for the condition of being completly
* zoomed out and not zooming in/out first.
*/
static Bool
isInMovement (CompScreen *s, int out)
{
ZOOM_SCREEN (s);
if (zs->zooms[out].currentZoom == 1.0f &&
zs->zooms[out].newZoom == 1.0f &&
zs->zooms[out].zVelocity == 0.0f)
return FALSE;
if (zs->zooms[out].currentZoom != zs->zooms[out].newZoom ||
zs->zooms[out].xVelocity || zs->zooms[out].yVelocity ||
zs->zooms[out].zVelocity)
return TRUE;
if (zs->zooms[out].xTranslate != zs->zooms[out].realXTranslate ||
zs->zooms[out].yTranslate != zs->zooms[out].realYTranslate)
return TRUE;
return FALSE;
}
/* Set the initial values of a zoom area.
*/
static void
initialiseZoomArea (ZoomArea *za, int out)
{
za->output = out;
za->currentZoom = 1.0f;
za->newZoom = 1.0f;
za->xVelocity = 0.0f;
za->yVelocity = 0.0f;
za->zVelocity = 0.0f;
za->xTranslate = 0.0f;
za->yTranslate = 0.0f;
za->realXTranslate = 0.0f;
za->realYTranslate = 0.0f;
za->viewport = ~0;
za->locked = FALSE;
updateActualTranslates (za);
}
/* Adjust the velocity in the z-direction.
*/
static void
adjustZoomVelocity (CompScreen *s, int out, float chunk)
{
ZOOM_SCREEN (s);
float d, adjust, amount;
d = (zs->zooms[out].newZoom - zs->zooms[out].currentZoom) * 75.0f;
adjust = d * 0.002f;
amount = fabs (d);
if (amount < 1.0f)
amount = 1.0f;
else if (amount > 5.0f)
amount = 5.0f;
zs->zooms[out].zVelocity =
(amount * zs->zooms[out].zVelocity + adjust) / (amount + 1.0f);
if (fabs (d) < 0.1f && fabs (zs->zooms[out].zVelocity) < 0.005f)
{
zs->zooms[out].currentZoom = zs->zooms[out].newZoom;
zs->zooms[out].zVelocity = 0.0f;
}
else
{
zs->zooms[out].currentZoom += (zs->zooms[out].zVelocity * chunk) /
s->redrawTime;
}
}
/* Adjust the X/Y velocity based on target translation and real translation.
*/
static void
adjustXYVelocity (CompScreen *s, int out, float chunk)
{
ZOOM_SCREEN (s);
float xdiff, ydiff;
float xadjust, yadjust;
float xamount, yamount;
zs->zooms[out].xVelocity /= 1.25f;
zs->zooms[out].yVelocity /= 1.25f;
xdiff =
(zs->zooms[out].xTranslate - zs->zooms[out].realXTranslate) * 75.0f;
ydiff =
(zs->zooms[out].yTranslate - zs->zooms[out].realYTranslate) * 75.0f;
xadjust = xdiff * 0.002f;
yadjust = ydiff * 0.002f;
xamount = fabs (xdiff);
yamount = fabs (ydiff);
if (xamount < 1.0f) xamount = 1.0f;
else if (xamount > 5.0) xamount = 5.0f;
if (yamount < 1.0f) yamount = 1.0f;
else if (yamount > 5.0) yamount = 5.0f;
zs->zooms[out].xVelocity =
(xamount * zs->zooms[out].xVelocity + xadjust) / (xamount + 1.0f);
zs->zooms[out].yVelocity =
(yamount * zs->zooms[out].yVelocity + yadjust) / (yamount + 1.0f);
if ((fabs(xdiff) < 0.1f && fabs (zs->zooms[out].xVelocity) < 0.005f) &&
(fabs(ydiff) < 0.1f && fabs (zs->zooms[out].yVelocity) < 0.005f))
{
zs->zooms[out].realXTranslate = zs->zooms[out].xTranslate;
zs->zooms[out].realYTranslate = zs->zooms[out].yTranslate;
zs->zooms[out].xVelocity = 0.0f;
zs->zooms[out].yVelocity = 0.0f;
return;
}
zs->zooms[out].realXTranslate +=
(zs->zooms[out].xVelocity * chunk) / s->redrawTime;
zs->zooms[out].realYTranslate +=
(zs->zooms[out].yVelocity * chunk) / s->redrawTime;
}
/* Animate the movement (if any) in preperation of a paint screen.
*/
static void
zoomPreparePaintScreen (CompScreen *s,
int msSinceLastPaint)
{
ZOOM_SCREEN (s);
if (zs->grabbed)
{
int steps;
float amount, chunk;
amount = msSinceLastPaint * 0.05f * zs->opt[SOPT_SPEED].value.f;
steps = amount / (0.5f * zs->opt[SOPT_TIMESTEP].value.f);
if (!steps) steps = 1;
chunk = amount / (float) steps;
while (steps--)
{
int out;
for (out = 0; out < zs->nZooms; out++)
{
if (!isInMovement (s, out) || !isActive (s, out))
continue;
adjustXYVelocity (s, out, chunk);
adjustZoomVelocity (s, out, chunk);
updateActualTranslates (&zs->zooms[out]);
if (!isZoomed (s, out))
{
zs->zooms[out].xVelocity = zs->zooms[out].yVelocity = 0.0f;
zs->grabbed &= ~(1 << zs->zooms[out].output);
}
}
}
if (zs->opt[SOPT_SYNC_MOUSE].value.b)
syncCenterToMouse (s);
}
UNWRAP (zs, s, preparePaintScreen);
(*s->preparePaintScreen) (s, msSinceLastPaint);
WRAP (zs, s, preparePaintScreen, zoomPreparePaintScreen);
}
/* Damage screen if we're still moving.
*/
static void
zoomDonePaintScreen (CompScreen *s)
{
ZOOM_SCREEN (s);
if (zs->grabbed)
{
int out;
for (out = 0; out < zs->nZooms; out++)
{
if (isInMovement (s, out) && isActive (s,out))
{
damageScreen (s);
break;
}
}
}
UNWRAP (zs, s, donePaintScreen);
(*s->donePaintScreen) (s);
WRAP (zs, s, donePaintScreen, zoomDonePaintScreen);
}
/* Apply the zoom if we are grabbed.
* Make sure to use the correct filter.
*/
static Bool
zoomPaintOutput (CompScreen *s,
const ScreenPaintAttrib *sAttrib,
const CompTransform *transform,
Region region,
CompOutput *output,
unsigned int mask)
{
Bool status;
int out = output->id;
ZOOM_SCREEN (s);
if (isActive (s, out))
{
ScreenPaintAttrib sa = *sAttrib;
int saveFilter;
CompTransform zTransform = *transform;
mask &= ~PAINT_SCREEN_REGION_MASK;
mask |= PAINT_SCREEN_CLEAR_MASK;
matrixScale (&zTransform,
1.0f / zs->zooms[out].currentZoom,
1.0f / zs->zooms[out].currentZoom,
1.0f);
matrixTranslate (&zTransform,
zs->zooms[out].xtrans,
zs->zooms[out].ytrans,
0);
mask |= PAINT_SCREEN_TRANSFORMED_MASK;
saveFilter = s->filter[SCREEN_TRANS_FILTER];
if (zs->opt[SOPT_FILTER_LINEAR].value.b)
s->filter[SCREEN_TRANS_FILTER] = COMP_TEXTURE_FILTER_GOOD;
else
s->filter[SCREEN_TRANS_FILTER] = COMP_TEXTURE_FILTER_FAST;
UNWRAP (zs, s, paintOutput);
status = (*s->paintOutput) (s, &sa, &zTransform, region, output, mask);
WRAP (zs, s, paintOutput, zoomPaintOutput);
drawCursor (s, output, transform);
s->filter[SCREEN_TRANS_FILTER] = saveFilter;
}
else
{
UNWRAP (zs, s, paintOutput);
status = (*s->paintOutput) (s, sAttrib, transform, region, output,
mask);
WRAP (zs, s, paintOutput, zoomPaintOutput);
}
return status;
}
/* Makes sure we're not attempting to translate too far.
* We are restricted to 0.5 to not go beyond the end
* of the screen/head.
*/
static inline void
constrainZoomTranslate (CompScreen *s)
{
ZOOM_SCREEN (s);
int out;
for (out = 0; out < zs->nZooms; out++)
{
if (zs->zooms[out].xTranslate > 0.5f)
zs->zooms[out].xTranslate = 0.5f;
else if (zs->zooms[out].xTranslate < -0.5f)
zs->zooms[out].xTranslate = -0.5f;
if (zs->zooms[out].yTranslate > 0.5f)
zs->zooms[out].yTranslate = 0.5f;
else if (zs->zooms[out].yTranslate < -0.5f)
zs->zooms[out].yTranslate = -0.5f;
}
}
/* Functions for adjusting the zoomed area.
* These are the core of the zoom plugin; Anything wanting
* to adjust the zoomed area must use setCenter or setZoomArea
* and setScale or frontends to them.
*/
/* Sets the center of the zoom area to X,Y.
* We have to be able to warp the pointer here: If we are moved by
* anything except mouse movement, we have to sync the
* mouse pointer. This is to allow input, and is NOT necesarry
* when input redirection is available to us or if we're cheating
* and using a scaled mouse cursor to immitate IR.
* The center is not the center of the screen. This is the target-center;
* that is, it's the point that's the same regardless of zoom level.
*/
static void
setCenter (CompScreen *s, int x, int y, Bool instant)
{
ZOOM_SCREEN(s);
int out = outputDeviceForPoint (s, x,y);
CompOutput *o = &s->outputDev[out];
if (zs->zooms[out].locked)
return;
zs->zooms[out].xTranslate = (float)
((x - o->region.extents.x1) - o->width / 2) / (o->width);
zs->zooms[out].yTranslate = (float)
((y - o->region.extents.y1) - o->height / 2) / (o->height);
if (instant)
{
zs->zooms[out].realXTranslate = zs->zooms[out].xTranslate;
zs->zooms[out].realYTranslate = zs->zooms[out].yTranslate;
zs->zooms[out].yVelocity = 0.0f;
zs->zooms[out].xVelocity = 0.0f;
updateActualTranslates (&zs->zooms[out]);
}
if (zs->opt[SOPT_MOUSE_PAN].value.b)
restrainCursor (s, out);
}
/*
* Zooms the area described.
* The math could probably be cleaned up, but should be correct now.
*/
static void
setZoomArea (CompScreen *s, int x, int y, int width, int height, Bool instant)
{
ZOOM_SCREEN (s);
int out = outputDeviceForGeometry (s, x, y, width, height, 0);
CompOutput *o = &s->outputDev[out];
if (zs->zooms[out].newZoom == 1.0f)
return;
if (zs->zooms[out].locked)
return;
zs->zooms[out].xTranslate =
(float) -((o->width/2) - (x + (width/2) - o->region.extents.x1))
/ (o->width);
zs->zooms[out].xTranslate /= (1.0f - zs->zooms[out].newZoom);
zs->zooms[out].yTranslate =
(float) -((o->height/2) - (y + (height/2) - o->region.extents.y1))
/ (o->height);
zs->zooms[out].yTranslate /= (1.0f - zs->zooms[out].newZoom);
constrainZoomTranslate (s);
if (instant)
{
zs->zooms[out].realXTranslate = zs->zooms[out].xTranslate;
zs->zooms[out].realYTranslate = zs->zooms[out].yTranslate;
updateActualTranslates (&zs->zooms[out]);
}
if (zs->opt[SOPT_MOUSE_PAN].value.b)
restrainCursor (s, out);
}
/* Moves the zoom area to the window specified
*/
static void
zoomAreaToWindow (CompWindow *w)
{
int left = w->serverX - w->input.left;
int width = w->width + w->input.left + w->input.right;
int top = w->serverY - w->input.top;
int height = w->height + w->input.top + w->input.bottom;
setZoomArea (w->screen, left, top, width, height, FALSE);
}
/* Pans the zoomed area vertically/horisontaly by
* value * zs->panFactor
* TODO: Fix output.
*/
static void
panZoom (CompScreen *s, int xvalue, int yvalue)
{
ZOOM_SCREEN (s);
int out;
for (out = 0; out < zs->nZooms; out++)
{
zs->zooms[out].xTranslate +=
zs->opt[SOPT_PAN_FACTOR].value.f * xvalue *
zs->zooms[out].currentZoom;
zs->zooms[out].yTranslate +=
zs->opt[SOPT_PAN_FACTOR].value.f * yvalue *
zs->zooms[out].currentZoom;
}
constrainZoomTranslate (s);
}
/* Sets the zoom (or scale) level. Takes two float's
* as argument for legacy reasons mainly; it uses the
* larger of the two, allowing us to easily scale to
* fit an area. FIXME: This shouldn't happen here.
*/
static void
setScale (CompScreen *s, int out, float x, float y)
{
float value = x > y ? x : y;
ZOOM_SCREEN(s);
if (zs->zooms[out].locked)
return;
if (value >= 1.0f)
{
value = 1.0f;
}
else
{
if (!zs->grabbed)
{
zs->mouseIntervalTimeoutHandle =
compAddTimeout (zs->opt[SOPT_POLL_INTERVAL].value.i,
updateMouseInterval, s);
}
zs->grabbed |= (1 << zs->zooms[out].output);
cursorZoomActive (s);
}
if (value == 1.0f)
{
zs->zooms[out].xTranslate = 0.0f;
zs->zooms[out].yTranslate = 0.0f;
cursorZoomInactive (s);
}
zs->zooms[out].newZoom = value;
damageScreen(s);
}
/* Mouse code...
* This takes care of keeping the mouse in sync with the zoomed area and
* vice versa. This is necesarry since we don't have input redirection (yet).
* They are easily disabled. We can also use a scaled mouse cursor. See
* heading for description.
*/
/* Syncs the center, based on translations, back to the mouse.
* This should be called when doing non-IR zooming and moving the zoom
* area based on events other than mouse movement.
*/
static void
syncCenterToMouse (CompScreen *s)
{
ZOOM_SCREEN(s);
int x, y;
int out = outputDeviceForPoint (s, zs->mouseX, zs->mouseY);
CompOutput *o = &s->outputDev[out];
if (!isInMovement (s, out))
return;
x = (int) ((zs->zooms[out].realXTranslate * s->width) +
(o->width / 2) + o->region.extents.x1);
y = (int) ((zs->zooms[out].realYTranslate * s->height) +
(o->height / 2) + o->region.extents.y1);
if ((x != zs->mouseX || y != zs->mouseY)
&& zs->grabbed && zs->zooms[out].newZoom != 1.0f)
{
warpPointer (s, x - pointerX , y - pointerY );
zs->mouseX = x;
zs->mouseY = y;
}
}
/* Convert the point X,Y to where it would be when zoomed.
*/
static void
convertToZoomed (CompScreen *s, int out, int x, int y, int *resultX, int *resultY)
{
ZOOM_SCREEN (s);
CompOutput *o = &s->outputDev[out];
ZoomArea *za = &zs->zooms[out];
x -= o->region.extents.x1;
y -= o->region.extents.y1;
*resultX = x - (za->realXTranslate *
(1.0f - za->currentZoom) * o->width) - o->width/2;
*resultX /= za->currentZoom;
*resultX += o->width/2;
*resultX += o->region.extents.x1;
*resultY = y - (za->realYTranslate *
(1.0f - za->currentZoom) * o->height) - o->height/2;
*resultY /= za->currentZoom;
*resultY += o->height/2;
*resultY += o->region.extents.y1;
}
/* Same but use targeted translation, not real */
static void
convertToZoomedTarget (CompScreen *s,
int out,
int x,
int y,
int *resultX,
int *resultY)
{
ZOOM_SCREEN (s);
CompOutput *o = &s->outputDev[out];
ZoomArea *za = &zs->zooms[out];
x -= o->region.extents.x1;
y -= o->region.extents.y1;
*resultX = x - (za->xTranslate *
(1.0f - za->newZoom) * o->width) - o->width/2;
*resultX /= za->newZoom;
*resultX += o->width/2;
*resultX += o->region.extents.x1;
*resultY = y - (za->yTranslate *
(1.0f - za->newZoom) * o->height) - o->height/2;
*resultY /= za->newZoom;
*resultY += o->height/2;
*resultY += o->region.extents.y1;
}
/* Make sure the given point + margin is visible;
* Translate to make it visible if necesarry.
* Returns false if the point isn't on a actively zoomed head
* or the area is locked.
*/
static Bool
ensureVisibility (CompScreen *s, int x, int y, int margin)
{
int zoomX, zoomY;
int out;
CompOutput *o;
ZOOM_SCREEN (s);
out = outputDeviceForPoint (s, x, y);
if (!isActive (s, out))
return FALSE;
o = &s->outputDev[out];
convertToZoomedTarget (s, out, x, y, &zoomX, &zoomY);
ZoomArea *za = &zs->zooms[out];
if (za->locked)
return FALSE;
#define FACTOR (za->newZoom / (1.0f - za->newZoom))
if (zoomX + margin > o->region.extents.x2)
za->xTranslate +=
(FACTOR * (float) (zoomX + margin - o->region.extents.x2)) /
(float) o->width;
else if (zoomX - margin < o->region.extents.x1)
za->xTranslate +=
(FACTOR * (float) (zoomX - margin - o->region.extents.x1)) /
(float) o->width;
if (zoomY + margin > o->region.extents.y2)
za->yTranslate +=
(FACTOR * (float) (zoomY + margin - o->region.extents.y2)) /
(float) o->height;
else if (zoomY - margin < o->region.extents.y1)
za->yTranslate +=
(FACTOR * (float) (zoomY - margin - o->region.extents.y1)) /
(float) o->height;
#undef FACTOR
constrainZoomTranslate (s);
return TRUE;
}
/* Ensures that the cursor is visible on the given head.
* Note that we check if currentZoom is 1.0f, because that
* often means that mouseX and mouseY is not up-to-date (since
* the polling timer just started).
*/
static void
restrainCursor (CompScreen *s, int out)
{
int x,y;
int diffX = 0, diffY = 0;
CompOutput *o = &s->outputDev[out];
ZOOM_SCREEN (s);
float z = zs->zooms[out].newZoom;
int margin = zs->opt[SOPT_RESTRAIN_MARGIN].value.i;
if (zs->zooms[out].currentZoom == 1.0f)
fetchMousePosition (s);
convertToZoomedTarget (s, out, zs->mouseX, zs->mouseY, &x, &y);
if (x > o->region.extents.x2 - margin)
diffX = x - o->region.extents.x2 + margin;
else if (x < o->region.extents.x1 + margin)
diffX = x - o->region.extents.x1 - margin;
if (y > o->region.extents.y2 - margin)
diffY = y - o->region.extents.y2 + margin;
else if (y < o->region.extents.y1 + margin)
diffY = y - o->region.extents.y1 - margin;
if (abs(diffX)*z > 0 || abs(diffY)*z > 0)
warpPointer (s,
(int) (zs->mouseX - pointerX) - (int) ((float)diffX * z),
(int) (zs->mouseY - pointerY) - (int) ((float)diffY * z));
}
/* Check if the cursor is still visible.
* We also make sure to activate/deactivate cursor scaling here
* so we turn on/off the pointer if it moves from one head to another.
* FIXME: Detect an actual output change instead of spamming.
* FIXME: The second ensureVisibility (sync with restrain).
*/
static void
cursorMoved (CompScreen *s)
{
ZOOM_SCREEN (s);
int out;
out = outputDeviceForPoint (s, zs->mouseX, zs->mouseY);
if (isActive (s, out))
{
if (zs->opt[SOPT_RESTRAIN_MOUSE].value.b)
restrainCursor (s, out);
if (zs->opt[SOPT_MOUSE_PAN].value.b)
{
ensureVisibility (s,
zs->mouseX,
zs->mouseY,
zs->opt[SOPT_RESTRAIN_MARGIN].value.i);
/* ensureVisibility (s,
zs->mouseX + zs->cursor.width/2,
zs->mouseY + zs->cursor.height/2,
zs->opt[SOPT_RESTRAIN_MARGIN].value.i);
*/
}
cursorZoomActive (s);
}
else
{
cursorZoomInactive (s);
}
}
/* Updates zs->mouse[XY], returns TRUE if it changed and
* is within the bounds of the current screen.
*/
static Bool
fetchMousePosition (CompScreen *s)
{
Window root_return;
Window child_return;
int rootX, rootY;
int winX, winY;
unsigned int maskReturn;
XQueryPointer (s->display->display, s->root,
&root_return, &child_return,
&rootX, &rootY, &winX, &winY, &maskReturn);
if (rootX > s->width || rootY > s->height || s->root != root_return)
return FALSE;
ZOOM_SCREEN (s);
if ((rootX != zs->mouseX || rootY != zs->mouseY))
{
zs->lastChange = time(NULL);
zs->mouseX = rootX;
zs->mouseY = rootY;
return TRUE;
}
return FALSE;
}
/* Update the mouse position.
* Based on the zoom engine in use, we will have to move the zoom area.
* This might have to be added to a timer.
*/
static void
updateMousePosition (CompScreen *s)
{
ZOOM_SCREEN(s);
if (fetchMousePosition (s))
{
int out = outputDeviceForPoint (s, zs->mouseX, zs->mouseY);
if (zs->opt[SOPT_SYNC_MOUSE].value.b && !isInMovement (s, out))
setCenter (s, zs->mouseX, zs->mouseY, TRUE);
cursorMoved (s);
damageScreen (s);
}
}
/* Timeout handler to poll the mouse. Returns false (and thereby does not get
* re-added to the queue) when zoom is not active.
*/
static Bool
updateMouseInterval (void *vs)
{
CompScreen *s = vs;
ZOOM_SCREEN (s);
updateMousePosition(s);
if (!zs->grabbed)
{
zs->mouseIntervalTimeoutHandle = FALSE;
cursorMoved (s);
return FALSE;
}
return TRUE;
}
/* Free a cursor
*/
static void
freeCursor (CursorTexture * cursor)
{
if (!cursor->isSet)
return;
makeScreenCurrent (cursor->screen);
cursor->isSet = FALSE;
glDeleteTextures (1, &cursor->texture);
cursor->texture = 0;
}
/* Translate into place and draw the scaled cursor.
*/
static void
drawCursor (CompScreen *s, CompOutput *output, const CompTransform *transform)
{
ZOOM_SCREEN (s);
int out = output->id;
if (zs->cursor.isSet)
{
CompTransform sTransform = *transform;
int ax, ay, x, y;
/* This is a hack because these transformations are wrong when
* we're working exposed. Expo is capable of telling where the
* real mouse is despite zoom, so we don't have to disable the
* zoom. We do, however, have to show the original pointer.
*/
if (dontuseScreengrabExist (s, "expo"))
{
cursorZoomInactive (s);
return;
}
transformToScreenSpace (s, output, -DEFAULT_Z_CAMERA, &sTransform);
convertToZoomed (s, out, zs->mouseX, zs->mouseY, &ax, &ay);
glPushMatrix ();
glLoadMatrixf (sTransform.m);
glTranslatef ((float) ax, (float) ay, 0.0f);
glScalef (1.0f / zs->zooms[out].currentZoom,
1.0f / zs->zooms[out].currentZoom,
1.0f);
x = -zs->cursor.hotX;
y = -zs->cursor.hotY;
glEnable (GL_BLEND);
glBindTexture (GL_TEXTURE_RECTANGLE_ARB, zs->cursor.texture);
glEnable (GL_TEXTURE_RECTANGLE_ARB);
glBegin (GL_QUADS);
glTexCoord2d (0, 0);
glVertex2f (x, y);
glTexCoord2d (0, zs->cursor.height);
glVertex2f (x, y + zs->cursor.height);
glTexCoord2d (zs->cursor.width, zs->cursor.height);
glVertex2f (x + zs->cursor.width, y + zs->cursor.height);
glTexCoord2d (zs->cursor.width, 0);
glVertex2f (x + zs->cursor.width, y);
glEnd ();
glDisable (GL_BLEND);
glBindTexture (GL_TEXTURE_RECTANGLE_ARB, 0);
glDisable (GL_TEXTURE_RECTANGLE_ARB);
glPopMatrix ();
}
}
/* Create (if necessarry) a texture to store the cursor,
* fetch the cursor with XFixes. Store it.
*/
static void
zoomUpdateCursor (CompScreen * s, CursorTexture * cursor)
{
Display * dpy = s->display->display;
if (!cursor->isSet)
{
cursor->isSet = TRUE;
cursor->screen = s;
makeScreenCurrent (s);
glEnable (GL_TEXTURE_RECTANGLE_ARB);
glGenTextures (1, &cursor->texture);
glBindTexture (GL_TEXTURE_RECTANGLE_ARB, cursor->texture);
glTexParameteri (GL_TEXTURE_RECTANGLE_ARB,
GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri (GL_TEXTURE_RECTANGLE_ARB,
GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri (GL_TEXTURE_RECTANGLE_ARB,
GL_TEXTURE_WRAP_S, GL_CLAMP);
glTexParameteri (GL_TEXTURE_RECTANGLE_ARB,
GL_TEXTURE_WRAP_T, GL_CLAMP);
} else {
makeScreenCurrent (cursor->screen);
glEnable (GL_TEXTURE_RECTANGLE_ARB);
}
XFixesCursorImage *ci = XFixesGetCursorImage(dpy);
cursor->width = ci->width;
cursor->height = ci->height;
cursor->hotX = ci->xhot;
cursor->hotY = ci->yhot;
unsigned char *pixels = malloc(ci->width * ci->height * 4);
int i;
if (!pixels)
{
XFree (ci);
return;
}
for (i = 0; i < ci->width * ci->height; i++)
{
unsigned long pix = ci->pixels[i];
pixels[i * 4] = pix & 0xff;
pixels[(i * 4) + 1] = (pix >> 8) & 0xff;
pixels[(i * 4) + 2] = (pix >> 16) & 0xff;
pixels[(i * 4) + 3] = (pix >> 24) & 0xff;
}
glBindTexture (GL_TEXTURE_RECTANGLE_ARB, cursor->texture);
glTexImage2D (GL_TEXTURE_RECTANGLE_ARB, 0, GL_RGBA, cursor->width,
cursor->height, 0, GL_BGRA, GL_UNSIGNED_BYTE, pixels);
glBindTexture (GL_TEXTURE_RECTANGLE_ARB, 0);
glDisable (GL_TEXTURE_RECTANGLE_ARB);
XFree (ci);
free (pixels);
}
/* We are no longer zooming the cursor, so display it.
*/
static void
cursorZoomInactive (CompScreen *s)
{
ZOOM_DISPLAY (s->display);
if (!zd->fixesSupported)
return;
ZOOM_SCREEN (s);
if (zs->cursorInfoSelected)
{
zs->cursorInfoSelected = FALSE;
XFixesSelectCursorInput (s->display->display, s->root, 0);
}
if (zs->cursor.isSet)
{
freeCursor (&zs->cursor);
}
if (zs->cursorHidden)
{
zs->cursorHidden = FALSE;
XFixesShowCursor (s->display->display, s->root);
}
}
/* Cursor zoom is active: We need to hide the original,
* register for Cursor notifies and display the new one.
* This can be called multiple times, not just on initial
* activation.
*/
static void
cursorZoomActive (CompScreen *s)
{
ZOOM_DISPLAY (s->display);
if (!zd->fixesSupported)
return;
ZOOM_SCREEN (s);
if (!zs->opt[SOPT_SCALE_MOUSE].value.b)
return;
if (!zs->cursorInfoSelected)
{
zs->cursorInfoSelected = TRUE;
XFixesSelectCursorInput (s->display->display, s->root,
XFixesDisplayCursorNotifyMask);
zoomUpdateCursor (s, &zs->cursor);
}
if (zd->canHideCursor && !zs->cursorHidden &&
zs->opt[SOPT_HIDE_ORIGINAL_MOUSE].value.b)
{
zs->cursorHidden = TRUE;
XFixesHideCursor (s->display->display, s->root);
}
}
/* Set the zoom area
* This is an interface for scripting.
* int32:x1: left x coordiante
* int32:y1: top y coordinate
* int32:x2: right x
* int32:y2: bottom y
* x2 and y2 can be omitted to assume x1==x2+1 y1==y2+1
* boolean:scale: True if we should modify the zoom level, false to just
* adjust the movement/translation.
* boolean:restrain: True to warp the pointer so it's visible.
*/
static Bool
setZoomAreaAction (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)
{
int x1, y1, x2, y2, out;
Bool scale, restrain;
x1 = getIntOptionNamed (option, nOption, "x1", -1);
y1 = getIntOptionNamed (option, nOption, "y1", -1);
x2 = getIntOptionNamed (option, nOption, "x2", -1);
y2 = getIntOptionNamed (option, nOption, "y2", -1);
scale = getBoolOptionNamed (option, nOption, "scale", FALSE);
restrain = getBoolOptionNamed (option, nOption, "restrain", FALSE);
if (x1 < 0 || y1 < 0)
return FALSE;
if (x2 < 0)
x2 = x1 + 1;
if (y2 < 0)
y2 = y1 + 1;
out = outputDeviceForPoint (s, x1, y1);
#define WIDTH (x2 - x1)
#define HEIGHT (y2 - y1)
setZoomArea (s, x1, y1, WIDTH, HEIGHT, FALSE);
CompOutput *o = &s->outputDev[out];
if (scale && WIDTH && HEIGHT)
setScale (s, out, (float) WIDTH/o->width,
(float) HEIGHT/o->height);
#undef WIDTH
#undef HEIGHT
if (restrain)
restrainCursor (s, out);
}
return TRUE;
}
/* Ensure visibility of an area defined by x1->x2/y1->y2
* int:x1: left X coordinate
* int:x2: right X Coordinate
* int:y1: top Y coordinate
* int:y2: bottom Y coordinate
* bool:scale: zoom out if necesarry to ensure visibility
* bool:restrain: Restrain the mouse cursor
* int:margin: The margin to use (default: 0)
* if x2/y2 is omitted, it is ignored.
*/
static Bool
ensureVisibilityAction (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)
{
int x1, y1, x2, y2, margin, out;
Bool scale, restrain;
x1 = getIntOptionNamed (option, nOption, "x1", -1);
y1 = getIntOptionNamed (option, nOption, "y1", -1);
x2 = getIntOptionNamed (option, nOption, "x2", -1);
y2 = getIntOptionNamed (option, nOption, "y2", -1);
margin = getBoolOptionNamed (option, nOption, "margin", 0);
scale = getBoolOptionNamed (option, nOption, "scale", FALSE);
restrain = getBoolOptionNamed (option, nOption, "restrain", FALSE);
if (x1 < 0 || y1 < 0)
return FALSE;
if (x2 < 0)
y2 = y1 + 1;
out = outputDeviceForPoint (s, x1, y1);
ensureVisibility (s, x1, y1, margin);
if (x2 >= 0 && y2 >= 0)
ensureVisibility (s, x2, y2, margin);
CompOutput *o = &s->outputDev[out];
#define WIDTH (x2 - x1)
#define HEIGHT (y2 - y1)
if (scale && WIDTH && HEIGHT)
setScale (s, out, (float) WIDTH/o->width,
(float) HEIGHT/o->height);
#undef WIDTH
#undef HEIGHT
if (restrain)
restrainCursor (s, out);
}
return TRUE;
}
/* Zoom in to the area pointed to by the mouse.
*/
static Bool
zoomIn (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)
{
ZOOM_SCREEN (s);
int out = outputDeviceForPoint (s, pointerX, pointerY);
if (zs->opt[SOPT_SYNC_MOUSE].value.b && !isInMovement (s, out))
setCenter (s, pointerX, pointerY, TRUE);
setScale (s, out,
zs->zooms[out].newZoom/zs->opt[SOPT_ZOOM_FACTOR].value.f,
-1.0f);
}
return TRUE;
}
/* Locks down the current zoom area
*/
static Bool
lockZoomAction (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)
{
ZOOM_SCREEN (s);
int out = outputDeviceForPoint (s, pointerX, pointerY);
zs->zooms[out].locked = !zs->zooms[out].locked;
}
return TRUE;
}
/* Zoom to a specific level.
* taget defines the target zoom level.
* First set the scale level and mark the display as grabbed internally (to
* catch the FocusIn event). Either target the focused window or the mouse,
* depending on settings.
* FIXME: A bit of a mess...
*/
static Bool
zoomSpecific (CompDisplay *d,
CompAction *action,
CompActionState state,
CompOption *option,
int nOption,
float target)
{
CompScreen *s;
Window xid;
xid = getIntOptionNamed (option, nOption, "root", 0);
s = findScreenAtDisplay (d, xid);
if (s)
{
int x, y;
ZOOM_DISPLAY (d);
ZOOM_SCREEN (s);
int out = outputDeviceForPoint (s, pointerX, pointerY);
if (target == 1.0f && zs->zooms[out].newZoom == 1.0f)
return FALSE;
if (otherScreenGrabExist (s, 0))
return FALSE;
setScale (s, out, target, target);
CompWindow *w;
w = findWindowAtDisplay(d, d->activeWindow);
if (zd->opt[DOPT_SPECIFIC_TARGET_FOCUS].value.b
&& w && w->screen->root == s->root)
{
zoomAreaToWindow (w);
}
else
{
x = getIntOptionNamed (option, nOption, "x", 0);
y = getIntOptionNamed (option, nOption, "y", 0);
setCenter (s, x, y, FALSE);
}
}
return TRUE;
}
static Bool
zoomSpecific1 (CompDisplay *d,
CompAction *action,
CompActionState state,
CompOption *option,
int nOption)
{
ZOOM_DISPLAY (d);
return zoomSpecific (d, action, state, option, nOption,
zd->opt[DOPT_SPECIFIC_LEVEL_1].value.f);
}
static Bool
zoomSpecific2 (CompDisplay *d,
CompAction *action,
CompActionState state,
CompOption *option,
int nOption)
{
ZOOM_DISPLAY (d);
return zoomSpecific (d, action, state, option, nOption,
zd->opt[DOPT_SPECIFIC_LEVEL_2].value.f);
}
static Bool
zoomSpecific3 (CompDisplay *d,
CompAction *action,
CompActionState state,
CompOption *option,
int nOption)
{
ZOOM_DISPLAY (d);
return zoomSpecific (d, action, state, option, nOption,
zd->opt[DOPT_SPECIFIC_LEVEL_3].value.f);
}
/* Zooms to fit the active window to the screen without cutting
* it off and targets it.
*/
static Bool
zoomToWindow (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)
return TRUE;
CompWindow *w;
w = findWindowAtDisplay (d, d->activeWindow);
if (!w || w->screen->root != s->root)
return TRUE;
int width = w->width + w->input.left + w->input.right;
int height = w->height + w->input.top + w->input.bottom;
int out = outputDeviceForWindow (w);
CompOutput *o = &s->outputDev[out];
setScale (s, out, (float) width/o->width, (float) height/o->height);
zoomAreaToWindow (w);
return TRUE;
}
static Bool
zoomPanLeft (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)
return TRUE;
panZoom (s, -1, 0);
return TRUE;
}
static Bool
zoomPanRight (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)
return TRUE;
panZoom (s, 1, 0);
return TRUE;
}
static Bool
zoomPanUp (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)
return TRUE;
panZoom (s, 0, -1);
return TRUE;
}
static Bool
zoomPanDown (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)
return TRUE;
panZoom (s, 0, 1);
return TRUE;
}
/* Centers the mouse based on zoom level and translation.
*/
static Bool
zoomCenterMouse (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)
return TRUE;
ZOOM_SCREEN (s);
int out = outputDeviceForPoint (s, pointerX, pointerY);
warpPointer (s,
(int) (s->outputDev[out].width/2 +
s->outputDev[out].region.extents.x1 - pointerX)
+ ((float) s->outputDev[out].width *
-zs->zooms[out].xtrans),
(int) (s->outputDev[out].height/2 +
s->outputDev[out].region.extents.y1 - pointerY)
+ ((float) s->outputDev[out].height *
zs->zooms[out].ytrans));
return TRUE;
}
/* Resize a window to fit the zoomed area.
* This could probably do with some moving-stuff too.
* IE: Move the zoom area afterwards. And ensure
* the window isn't resized off-screen.
*/
static Bool
zoomFitWindowToZoom (CompDisplay *d,
CompAction *action,
CompActionState state,
CompOption *option,
int nOption)
{
CompScreen *s;
XWindowChanges xwc;
CompWindow * w;
w = findWindowAtDisplay (d, d->activeWindow);
if (!w)
return TRUE;
s = w->screen;
int out = outputDeviceForWindow (w);
ZOOM_SCREEN (s);
xwc.x = w->serverX;
xwc.y = w->serverY;
xwc.width = (int) (s->outputDev[out].width *
zs->zooms[out].currentZoom -
(int) ((w->input.left + w->input.right)));
xwc.height = (int) (s->outputDev[out].height *
zs->zooms[out].currentZoom -
(int) ((w->input.top + w->input.bottom)));
sendSyncRequest (w);
configureXWindow (w, (unsigned int) CWWidth | CWHeight, &xwc);
return TRUE;
}
static Bool
zoomInitiate (CompDisplay *d,
CompAction *action,
CompActionState state,
CompOption *option,
int nOption)
{
zoomIn (d, action, state, option, nOption);
if (state & CompActionStateInitKey)
action->state |= CompActionStateTermKey;
if (state & CompActionStateInitButton)
action->state |= CompActionStateTermButton;
return TRUE;
}
static Bool
zoomOut (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)
{
ZOOM_SCREEN (s);
int out = outputDeviceForPoint (s, pointerX, pointerY);
setScale (s, out,
zs->zooms[out].newZoom *
zs->opt[SOPT_ZOOM_FACTOR].value.f,
-1.0f);
}
return TRUE;
}
static Bool
zoomTerminate (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)
{
ZOOM_SCREEN (s);
if (xid && s->root != xid)
continue;
int out = outputDeviceForPoint (s, pointerX, pointerY);
if (zs->grabbed)
{
zs->zooms[out].newZoom = 1.0f;
damageScreen (s);
}
}
action->state &= ~(CompActionStateTermKey | CompActionStateTermButton);
return FALSE;
}
/* Focus-track related event handeling.
* The lastMapped is a hack to ensure that newly mapped windows are
* caught even if the grab that (possibly) triggered them affected
* the mode. Windows created by a keybind (like creating a terminal
* on a keybind) tends to trigger FocusIn events with mode other than
* Normal. This works around this problem.
* FIXME: Cleanup.
* TODO: Avoid maximized windows.
*/
static void
focusTrack (CompDisplay *d,
XEvent *event)
{
CompWindow *w;
int out;
static Window lastMapped = 0;
if (event->type == MapNotify)
{
lastMapped = event->xmap.window;
return;
}
else if (event->type != FocusIn)
return;
if ((event->xfocus.mode != NotifyNormal)
&& (lastMapped != event->xfocus.window))
return;
lastMapped = 0;
w = findWindowAtDisplay(d, event->xfocus.window);
if (w == NULL || w->id == d->activeWindow)
return;
ZOOM_SCREEN (w->screen);
if (time(NULL) - zs->lastChange < zs->opt[SOPT_FOCUS_DELAY].value.i ||
!zs->opt[SOPT_FOLLOW_FOCUS].value.b)
return;
out = outputDeviceForWindow (w);
if (!isActive (w->screen, out) &&
!zs->opt[SOPT_ALLWAYS_FOCUS_FIT_WINDOW].value.b)
return;
if (zs->opt[SOPT_FOCUS_FIT_WINDOW].value.b)
{
int width = w->width + w->input.left + w->input.right;
int height = w->height + w->input.top + w->input.bottom;
setScale (w->screen, out,
(float) width / w->screen->outputDev[out].width,
(float) height/w->screen->outputDev[out].height);
}
zoomAreaToWindow (w);
}
/* Event handler. Pass focus-related events on and handle
* XFixes events.
*/
static void
zoomHandleEvent (CompDisplay *d,
XEvent *event)
{
ZOOM_DISPLAY(d);
CompScreen *s;
switch (event->type) {
case FocusIn:
case MapNotify:
focusTrack (d, event);
break;
default:
if (event->type == zd->fixesEventBase + XFixesCursorNotify)
{
XFixesCursorNotifyEvent *cev = (XFixesCursorNotifyEvent *)
event;
s = findScreenAtDisplay (d, cev->window);
if (s)
{
ZOOM_SCREEN(s);
if (zs->cursor.isSet)
zoomUpdateCursor (s, &zs->cursor);
}
}
break;
}
UNWRAP (zd, d, handleEvent);
(*d->handleEvent) (d, event);
WRAP (zd, d, handleEvent, zoomHandleEvent);
}
/* Settings etc, boring stuff */
static const CompMetadataOptionInfo zoomDisplayOptionInfo[] = {
{ "initiate", "action", 0, zoomInitiate, zoomTerminate },
{ "zoom_in", "action", 0, zoomIn, 0 },
{ "zoom_out", "action", 0, zoomOut, 0 },
{ "zoom_specific_1", "action", 0, zoomSpecific1, 0 },
{ "zoom_specific_2", "action", 0, zoomSpecific2, 0 },
{ "zoom_specific_3", "action", 0, zoomSpecific3, 0 },
{ "zoom_spec1", "float", "<min>0.1</min><max>1.0</max><default>1.0</default>", 0, 0 },
{ "zoom_spec2", "float", "<min>0.1</min><max>1.0</max><default>0.5</default>", 0, 0 },
{ "zoom_spec3", "float", "<min>0.1</min><max>1.0</max><default>0.2</default>", 0, 0 },
{ "spec_target_focus", "bool", "<default>true</default>", 0, 0 },
{ "pan_left", "action", 0, zoomPanLeft, 0 },
{ "pan_right", "action", 0, zoomPanRight, 0 },
{ "pan_up", "action", 0, zoomPanUp, 0 },
{ "pan_down", "action", 0, zoomPanDown, 0 },
{ "fit_to_window", "action", 0, zoomToWindow, 0 },
{ "center_mouse", "action", 0, zoomCenterMouse, 0 },
{ "fit_to_zoom", "action", 0, zoomFitWindowToZoom, 0 },
{ "ensure_visibility", "action", 0, ensureVisibilityAction, 0},
{ "set_zoom_area", "action", 0, setZoomAreaAction, 0},
{ "lock_zoom", "action", 0, lockZoomAction, 0},
};
static const CompMetadataOptionInfo zoomScreenOptionInfo[] = {
{ "follow_focus", "bool", 0, 0, 0 },
{ "speed", "float", "<min>0.01</min>", 0, 0 },
{ "timestep", "float", "<min>0.1</min>", 0, 0 },
{ "zoom_factor", "float", "<min>1.01</min>", 0, 0 },
{ "filter_linear", "bool", 0, 0, 0 },
{ "sync_mouse", "bool", 0, 0, 0 },
{ "mouse_poll_interval", "int", "<min>1</min>", 0, 0 },
{ "follow_focus_delay", "int", "<min>0</min>", 0, 0 },
{ "pan_factor", "float", "<min>0.001</min><default>0.1</default>", 0, 0 },
{ "focus_fit_window", "bool", "<default>false</default>", 0, 0 },
{ "allways_focus_fit_window", "bool", "<default>false</default>", 0, 0 },
{ "scale_mouse", "bool", "<default>false</default>", 0, 0 },
{ "hide_original_mouse", "bool", "<default>false</default>", 0, 0 },
{ "restrain_mouse", "bool", "<default>false</default>", 0, 0 },
{ "restrain_margin", "int", "<default>5</default>", 0, 0 },
{ "mouse_pan", "bool", "<default>false</default>", 0, 0 }
};
static CompOption *
zoomGetScreenOptions (CompPlugin *plugin,
CompScreen *screen,
int *count)
{
ZOOM_SCREEN (screen);
*count = NUM_OPTIONS (zs);
return zs->opt;
}
static Bool
zoomSetScreenOption (CompPlugin *plugin,
CompScreen *screen,
char *name,
CompOptionValue *value)
{
CompOption *o;
int index;
ZOOM_SCREEN (screen);
o = compFindOption (zs->opt, NUM_OPTIONS (zs), name, &index);
if (!o)
return FALSE;
return compSetScreenOption (screen, o, value);
}
static CompOption *
zoomGetDisplayOptions (CompPlugin *plugin,
CompDisplay *display,
int *count)
{
ZOOM_DISPLAY (display);
*count = NUM_OPTIONS (zd);
return zd->opt;
}
static Bool
zoomSetDisplayOption (CompPlugin *plugin,
CompDisplay *display,
char *name,
CompOptionValue *value)
{
CompOption *o;
int index;
ZOOM_DISPLAY (display);
o = compFindOption (zd->opt, NUM_OPTIONS (zd), name, &index);
if (!o)
return FALSE;
return compSetDisplayOption (display, o, value);
}
static Bool
zoomInitDisplay (CompPlugin *p,
CompDisplay *d)
{
ZoomDisplay *zd;
zd = malloc (sizeof (ZoomDisplay));
if (!zd)
return FALSE;
if (!compInitDisplayOptionsFromMetadata (d,
&zoomMetadata,
zoomDisplayOptionInfo,
zd->opt,
DOPT_NUM))
{
free (zd);
return FALSE;
}
zd->screenPrivateIndex = allocateScreenPrivateIndex (d);
if (zd->screenPrivateIndex < 0)
{
compFiniDisplayOptions (d, zd->opt, DOPT_NUM);
free (zd);
return FALSE;
}
zd->fixesSupported = XFixesQueryExtension(d->display, &zd->fixesEventBase,
&zd->fixesErrorBase);
int minor, major;
XFixesQueryVersion(d->display, &major, &minor);
if (major >= 4)
zd->canHideCursor = TRUE;
else
zd->canHideCursor = FALSE;
WRAP (zd, d, handleEvent, zoomHandleEvent);
d->privates[displayPrivateIndex].ptr = zd;
return TRUE;
}
static void
zoomFiniDisplay (CompPlugin *p,
CompDisplay *d)
{
ZOOM_DISPLAY (d);
freeScreenPrivateIndex (d, zd->screenPrivateIndex);
UNWRAP (zd, d, handleEvent);
compFiniDisplayOptions (d, zd->opt, DOPT_NUM);
free (zd);
}
static Bool
zoomInitScreen (CompPlugin *p,
CompScreen *s)
{
ZoomScreen *zs;
int i;
ZOOM_DISPLAY (s->display);
zs = malloc (sizeof (ZoomScreen));
if (!zs)
return FALSE;
if (!compInitScreenOptionsFromMetadata (s,
&zoomMetadata,
zoomScreenOptionInfo,
zs->opt,
SOPT_NUM))
{
free (zs);
return FALSE;
}
zs->nZooms = s->nOutputDev;
zs->zooms = malloc (sizeof (ZoomArea) * zs->nZooms);
for (i = 0; i < zs->nZooms; i ++ )
{
/* zs->grabbed is a mask ... Thus this limit */
if (i > sizeof (long int) * 8)
break;
initialiseZoomArea (&zs->zooms[i], i);
}
zs->grabbed = 0;
zs->mouseX = -1;
zs->mouseY = -1;
zs->cursorInfoSelected = FALSE;
zs->cursor.isSet = FALSE;
zs->cursorHidden = FALSE;
WRAP (zs, s, preparePaintScreen, zoomPreparePaintScreen);
WRAP (zs, s, donePaintScreen, zoomDonePaintScreen);
WRAP (zs, s, paintOutput, zoomPaintOutput);
s->privates[zd->screenPrivateIndex].ptr = zs;
return TRUE;
}
static void
zoomFiniScreen (CompPlugin *p,
CompScreen *s)
{
ZOOM_SCREEN (s);
if (zs->mouseIntervalTimeoutHandle)
compRemoveTimeout (zs->mouseIntervalTimeoutHandle);
UNWRAP (zs, s, preparePaintScreen);
UNWRAP (zs, s, donePaintScreen);
UNWRAP (zs, s, paintOutput);
compFiniScreenOptions (s, zs->opt, SOPT_NUM);
free (zs);
}
static Bool
zoomInit (CompPlugin *p)
{
if (!compInitPluginMetadataFromInfo (&zoomMetadata,
p->vTable->name,
zoomDisplayOptionInfo,
DOPT_NUM,
zoomScreenOptionInfo,
SOPT_NUM))
return FALSE;
displayPrivateIndex = allocateDisplayPrivateIndex ();
if (displayPrivateIndex < 0)
{
compFiniMetadata (&zoomMetadata);
return FALSE;
}
compAddMetadataFromFile (&zoomMetadata, p->vTable->name);
return TRUE;
}
static void
zoomFini (CompPlugin *p)
{
freeDisplayPrivateIndex (displayPrivateIndex);
compFiniMetadata (&zoomMetadata);
}
static int
zoomGetVersion (CompPlugin *plugin,
int version)
{
return ABIVERSION;
}
static CompMetadata *
zoomGetMetadata (CompPlugin *plugin)
{
return &zoomMetadata;
}
CompPluginVTable zoomVTable = {
"ezoom",
zoomGetVersion,
zoomGetMetadata,
zoomInit,
zoomFini,
zoomInitDisplay,
zoomFiniDisplay,
zoomInitScreen,
zoomFiniScreen,
0, /* InitWindow */
0, /* FiniWindow */
zoomGetDisplayOptions,
zoomSetDisplayOption,
zoomGetScreenOptions,
zoomSetScreenOption
};
CompPluginVTable *
getCompPluginInfo (void)
{
return &zoomVTable;
}
syntax highlighted by Code2HTML, v. 0.9.1