/* * 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 */ #include #include #include #include #include #include #define CLONE_INITIATE_BUTTON_DEFAULT Button1 #define CLONE_INITIATE_BUTTON_MODIFIERS_DEFAULT (CompSuperMask | ShiftMask) static int displayPrivateIndex; #define CLONE_DISPLAY_OPTION_INITIATE 0 #define CLONE_DISPLAY_OPTION_NUM 1 typedef struct _CloneDisplay { int screenPrivateIndex; HandleEventProc handleEvent; CompOption opt[CLONE_DISPLAY_OPTION_NUM]; } CloneDisplay; typedef struct _CloneClone { int src; int dst; Region region; Window input; } CloneClone; typedef struct _CloneScreen { PreparePaintScreenProc preparePaintScreen; DonePaintScreenProc donePaintScreen; PaintScreenProc paintScreen; PaintWindowProc paintWindow; OutputChangeNotifyProc outputChangeNotify; int grabIndex; Bool grab; float offset; Bool transformed; CloneClone *clone; int nClone; int x, y; int grabbedOutput; int src, dst; } CloneScreen; #define GET_CLONE_DISPLAY(d) \ ((CloneDisplay *) (d)->privates[displayPrivateIndex].ptr) #define CLONE_DISPLAY(d) \ CloneDisplay *cd = GET_CLONE_DISPLAY (d) #define GET_CLONE_SCREEN(s, cd) \ ((CloneScreen *) (s)->privates[(cd)->screenPrivateIndex].ptr) #define CLONE_SCREEN(s) \ CloneScreen *cs = GET_CLONE_SCREEN (s, GET_CLONE_DISPLAY (s->display)) #define NUM_OPTIONS(d) (sizeof ((d)->opt) / sizeof (CompOption)) static void cloneRemove(CompScreen * s, int i) { CloneClone *clone; CLONE_SCREEN(s); clone = malloc(sizeof(CloneClone) * (cs->nClone - 1)); if (clone) { int j, k = 0; for (j = 0; j < cs->nClone; j++) if (j != i) memcpy(&clone[k++], &cs->clone[j], sizeof(CloneClone)); XDestroyRegion(cs->clone[i].region); XDestroyWindow(s->display->display, cs->clone[i].input); free(cs->clone); cs->clone = clone; cs->nClone--; } } static void cloneFinish(CompScreen * s) { CloneClone *clone; int i; CLONE_SCREEN(s); cs->grab = FALSE; if (cs->src != cs->dst) { clone = NULL; /* check if we should replace current clone */ for (i = 0; i < cs->nClone; i++) { if (cs->clone[i].dst == cs->dst) { clone = &cs->clone[i]; break; } } /* no existing clone for this destination, we must allocate one */ if (!clone) { Region region; region = XCreateRegion(); if (region) { clone = realloc(cs->clone, sizeof(CloneClone) * (cs->nClone + 1)); if (clone) { XSetWindowAttributes attr; int x, y; cs->clone = clone; clone = &cs->clone[cs->nClone++]; clone->region = region; attr.override_redirect = TRUE; x = s->outputDev[cs->dst].region.extents.x1; y = s->outputDev[cs->dst].region.extents.y1; clone->input = XCreateWindow(s->display-> display, s->root, x, y, s->outputDev[cs-> dst]. width, s->outputDev[cs-> dst]. height, 0, 0, InputOnly, CopyFromParent, CWOverrideRedirect, &attr); XMapRaised(s->display->display, clone->input); } else { XDestroyRegion(region); } } } if (clone) { clone->src = cs->src; clone->dst = cs->dst; } } if (cs->grabbedOutput != cs->dst) { /* remove clone */ for (i = 0; i < cs->nClone; i++) { if (cs->clone[i].dst == cs->grabbedOutput) { cloneRemove(s, i); break; } } } } static void clonePreparePaintScreen(CompScreen * s, int msSinceLastPaint) { int i; CLONE_SCREEN(s); if (cs->grab) { if (cs->grabIndex) { cs->offset -= msSinceLastPaint * 0.005f; if (cs->offset < 0.0f) cs->offset = 0.0f; } else { cs->offset += msSinceLastPaint * 0.005f; if (cs->offset >= 1.0f) { cs->offset = 1.0f; cloneFinish(s); } } damageScreen(s); } UNWRAP(cs, s, preparePaintScreen); (*s->preparePaintScreen) (s, msSinceLastPaint); WRAP(cs, s, preparePaintScreen, clonePreparePaintScreen); for (i = 0; i < cs->nClone; i++) { CompOutput *src = &s->outputDev[cs->clone[i].src]; CompOutput *dst = &s->outputDev[cs->clone[i].dst]; int dx, dy; dx = dst->region.extents.x1 - src->region.extents.x1; dy = dst->region.extents.y1 - src->region.extents.y1; if (s->damageMask & COMP_SCREEN_DAMAGE_REGION_MASK) { if (src->width != dst->width || src->height != dst->height) { XSubtractRegion(&dst->region, getEmptyRegion(), cs->clone[i].region); XUnionRegion(s->damage, cs->clone[i].region, s->damage); XSubtractRegion(&src->region, getEmptyRegion(), cs->clone[i].region); } else { XSubtractRegion(s->damage, &dst->region, cs->clone[i].region); XOffsetRegion(cs->clone[i].region, dx, dy); XUnionRegion(s->damage, cs->clone[i].region, s->damage); XSubtractRegion(s->damage, &src->region, cs->clone[i].region); XOffsetRegion(cs->clone[i].region, -dx, -dy); } } else { XSubtractRegion(&src->region, getEmptyRegion(), cs->clone[i].region); } } } static void cloneDonePaintScreen(CompScreen * s) { CLONE_SCREEN(s); if (cs->grab && cs->offset) damageScreen(s); UNWRAP(cs, s, donePaintScreen); (*s->donePaintScreen) (s); WRAP(cs, s, donePaintScreen, cloneDonePaintScreen); } static Bool clonePaintScreen(CompScreen * s, const ScreenPaintAttrib * sAttrib, Region region, int output, unsigned int mask) { Bool status; int i, dst = output; CLONE_SCREEN(s); if (!cs->grab || cs->grabbedOutput != output) { for (i = 0; i < cs->nClone; i++) { if (cs->clone[i].dst == output) { region = cs->clone[i].region; dst = cs->clone[i].src; if (s->outputDev[dst].width != s->outputDev[output].width || s->outputDev[dst].height != s->outputDev[output].height) cs->transformed = TRUE; else cs->transformed = FALSE; break; } } } UNWRAP(cs, s, paintScreen); status = (*s->paintScreen) (s, sAttrib, region, dst, mask); WRAP(cs, s, paintScreen, clonePaintScreen); if (cs->grab) { CompWindow *w; GLenum filter; float zoom1, zoom2x, zoom2y, x1, y1, x2, y2; float zoomX, zoomY; int dx, dy; zoom1 = 160.0f / s->outputDev[cs->src].height; x1 = cs->x - (s->outputDev[cs->src].region.extents.x1 * zoom1); y1 = cs->y - (s->outputDev[cs->src].region.extents.y1 * zoom1); x1 -= (s->outputDev[cs->src].width * zoom1) / 2; y1 -= (s->outputDev[cs->src].height * zoom1) / 2; if (cs->grabIndex) { x2 = s->outputDev[cs->grabbedOutput].region. extents.x1 - s->outputDev[cs->src].region.extents.x1; y2 = s->outputDev[cs->grabbedOutput].region. extents.y1 - s->outputDev[cs->src].region.extents.y1; zoom2x = (float)s->outputDev[cs->grabbedOutput].width / s->outputDev[cs->src].width; zoom2y = (float)s->outputDev[cs->grabbedOutput]. height / s->outputDev[cs->src].height; } else { x2 = s->outputDev[cs->dst].region.extents.x1 - s->outputDev[cs->src].region.extents.x1; y2 = s->outputDev[cs->dst].region.extents.y1 - s->outputDev[cs->src].region.extents.y1; zoom2x = (float)s->outputDev[cs->dst].width / s->outputDev[cs->src].width; zoom2y = (float)s->outputDev[cs->dst].height / s->outputDev[cs->src].height; } /* XXX: hmm.. why do I need this.. */ if (x2 < 0.0f) x2 *= zoom2x; if (y2 < 0.0f) y2 *= zoom2y; dx = x1 * (1.0f - cs->offset) + x2 * cs->offset; dy = y1 * (1.0f - cs->offset) + y2 * cs->offset; zoomX = zoom1 * (1.0f - cs->offset) + zoom2x * cs->offset; zoomY = zoom1 * (1.0f - cs->offset) + zoom2y * cs->offset; glPushMatrix(); glTranslatef(-0.5f, -0.5f, -DEFAULT_Z_CAMERA); glScalef(1.0f / s->outputDev[output].width, -1.0f / s->outputDev[output].height, 1.0f); glTranslatef(dx - s->outputDev[output].region.extents.x1, dy - s->outputDev[output].region.extents.y2, 0.0f); glScalef(zoomX, zoomY, 1.0f); filter = s->display->textureFilter; if (cs->offset == 0.0f) s->display->textureFilter = GL_LINEAR_MIPMAP_LINEAR; for (w = s->windows; w; w = w->next) { if (w->destroyed) continue; if (!w->shaded) { if (w->attrib.map_state != IsViewable || !w->damaged) continue; } (*s->paintWindow) (w, &w->paint, &s->outputDev[cs->src].region, PAINT_WINDOW_ON_TRANSFORMED_SCREEN_MASK); } s->display->textureFilter = filter; glPopMatrix(); } return status; } static Bool clonePaintWindow(CompWindow * w, const WindowPaintAttrib * attrib, Region region, unsigned int mask) { Bool status; CLONE_SCREEN(w->screen); if (cs->nClone && cs->transformed) mask |= PAINT_WINDOW_ON_TRANSFORMED_SCREEN_MASK; UNWRAP(cs, w->screen, paintWindow); status = (*w->screen->paintWindow) (w, attrib, region, mask); WRAP(cs, w->screen, paintWindow, clonePaintWindow); return status; } static Bool cloneInitiate(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 i; CLONE_SCREEN(s); if (cs->grab || otherScreenGrabExist(s, "clone", 0)) return FALSE; if (!cs->grabIndex) cs->grabIndex = pushScreenGrab(s, None, "clone"); cs->grab = TRUE; cs->x = getIntOptionNamed(option, nOption, "x", 0); cs->y = getIntOptionNamed(option, nOption, "y", 0); cs->src = cs->grabbedOutput = outputDeviceForPoint(s, cs->x, cs->y); /* trace source */ i = 0; while (i < cs->nClone) { if (cs->clone[i].dst == cs->src) { cs->src = cs->clone[i].src; i = 0; } else { i++; } } if (state & CompActionStateInitButton) action->state |= CompActionStateTermButton; } return FALSE; } static Bool cloneTerminate(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) { CLONE_SCREEN(s); if (xid && s->root != xid) continue; if (cs->grabIndex) { int x, y; removeScreenGrab(s, cs->grabIndex, NULL); cs->grabIndex = 0; x = getIntOptionNamed(option, nOption, "x", 0); y = getIntOptionNamed(option, nOption, "y", 0); cs->dst = outputDeviceForPoint(s, x, y); damageScreen(s); } } action->state &= ~(CompActionStateTermKey | CompActionStateTermButton); return FALSE; } static void cloneSetStrutsForCloneWindow(CompScreen * s, CloneClone * clone) { CompOutput *output = &s->outputDev[clone->dst]; XRectangle *rect = NULL; CompStruts *struts; CompWindow *w; w = findWindowAtScreen(s, clone->input); if (!w) return; struts = malloc(sizeof(CompStruts)); if (!struts) return; if (w->struts) free(w->struts); struts->left.x = 0; struts->left.y = 0; struts->left.width = 0; struts->left.height = s->height; struts->right.x = s->width; struts->right.y = 0; struts->right.width = 0; struts->right.height = s->height; struts->top.x = 0; struts->top.y = 0; struts->top.width = s->width; struts->top.height = 0; struts->bottom.x = 0; struts->bottom.y = s->height; struts->bottom.width = s->width; struts->bottom.height = 0; /* create struts relative to a screen edge that this output is next to */ if (output->region.extents.x1 == 0) rect = &struts->left; else if (output->region.extents.x2 == s->width) rect = &struts->right; else if (output->region.extents.y1 == 0) rect = &struts->top; else if (output->region.extents.y2 == s->height) rect = &struts->bottom; if (rect) { rect->x = output->region.extents.x1; rect->y = output->region.extents.y1; rect->width = output->width; rect->height = output->height; } w->struts = struts; } static void cloneHandleMotionEvent(CompScreen * s, int xRoot, int yRoot) { CLONE_SCREEN(s); if (cs->grabIndex) { cs->x = xRoot; cs->y = yRoot; damageScreen(s); } } static void cloneHandleEvent(CompDisplay * d, XEvent * event) { CompScreen *s; CLONE_DISPLAY(d); switch (event->type) { case MotionNotify: s = findScreenAtDisplay(d, event->xmotion.root); if (s) cloneHandleMotionEvent(s, d->pointerX, d->pointerY); break; case EnterNotify: case LeaveNotify: s = findScreenAtDisplay(d, event->xcrossing.root); if (s) cloneHandleMotionEvent(s, d->pointerX, d->pointerY); default: break; } UNWRAP(cd, d, handleEvent); (*d->handleEvent) (d, event); WRAP(cd, d, handleEvent, cloneHandleEvent); switch (event->type) { case CreateNotify: s = findScreenAtDisplay(d, event->xcreatewindow.parent); if (s) { int i; CLONE_SCREEN(s); for (i = 0; i < cs->nClone; i++) if (event->xcreatewindow.window == cs->clone[i].input) cloneSetStrutsForCloneWindow(s, &cs->clone[i]); } default: break; } } static void cloneOutputChangeNotify(CompScreen * s) { int i; CLONE_SCREEN(s); /* remove clones with destination or source that doesn't exist */ for (i = 0; i < cs->nClone; i++) { if (cs->clone[i].dst >= s->nOutputDev || cs->clone[i].src >= s->nOutputDev) { cloneRemove(s, i); i = 0; continue; } } UNWRAP(cs, s, outputChangeNotify); (*s->outputChangeNotify) (s); WRAP(cs, s, outputChangeNotify, cloneOutputChangeNotify); } static void cloneDisplayInitOptions(CloneDisplay * cd) { CompOption *o; o = &cd->opt[CLONE_DISPLAY_OPTION_INITIATE]; o->advanced = False; o->name = "initiate"; o->group = N_("Misc. settings"); o->subGroup = N_(""); o->displayHints = ""; o->shortDesc = N_("Initiate Clone Selection"); o->longDesc = N_("Initiate Clone selection."); o->type = CompOptionTypeAction; o->value.action.initiate = cloneInitiate; o->value.action.terminate = cloneTerminate; o->value.action.bell = FALSE; o->value.action.edgeMask = 0; o->value.action.state = CompActionStateInitButton; o->value.action.type = CompBindingTypeButton; o->value.action.button.modifiers = CLONE_INITIATE_BUTTON_MODIFIERS_DEFAULT; o->value.action.button.button = CLONE_INITIATE_BUTTON_DEFAULT; } static CompOption *cloneGetDisplayOptions(CompDisplay * display, int *count) { if (display) { CLONE_DISPLAY(display); *count = NUM_OPTIONS(cd); return cd->opt; } else { CloneDisplay *cd = malloc(sizeof(CloneDisplay)); cloneDisplayInitOptions(cd); *count = NUM_OPTIONS(cd); return cd->opt; } } static Bool cloneSetDisplayOption(CompDisplay * display, char *name, CompOptionValue * value) { CompOption *o; int index; CLONE_DISPLAY(display); o = compFindOption(cd->opt, NUM_OPTIONS(cd), name, &index); if (!o) return FALSE; switch (index) { case CLONE_DISPLAY_OPTION_INITIATE: if (setDisplayAction(display, o, value)) return TRUE; default: break; } return FALSE; } static Bool cloneInitDisplay(CompPlugin * p, CompDisplay * d) { CloneDisplay *cd; cd = malloc(sizeof(CloneDisplay)); if (!cd) return FALSE; cd->screenPrivateIndex = allocateScreenPrivateIndex(d); if (cd->screenPrivateIndex < 0) { free(cd); return FALSE; } cloneDisplayInitOptions(cd); WRAP(cd, d, handleEvent, cloneHandleEvent); d->privates[displayPrivateIndex].ptr = cd; return TRUE; } static void cloneFiniDisplay(CompPlugin * p, CompDisplay * d) { CLONE_DISPLAY(d); freeScreenPrivateIndex(d, cd->screenPrivateIndex); UNWRAP(cd, d, handleEvent); free(cd); } static Bool cloneInitScreen(CompPlugin * p, CompScreen * s) { CloneScreen *cs; CLONE_DISPLAY(s->display); cs = malloc(sizeof(CloneScreen)); if (!cs) return FALSE; cs->grabIndex = 0; cs->grab = FALSE; cs->offset = 1.0f; cs->transformed = FALSE; cs->nClone = 0; cs->clone = NULL; cs->src = 0; addScreenAction(s, &cd->opt[CLONE_DISPLAY_OPTION_INITIATE].value.action); WRAP(cs, s, preparePaintScreen, clonePreparePaintScreen); WRAP(cs, s, donePaintScreen, cloneDonePaintScreen); WRAP(cs, s, paintScreen, clonePaintScreen); WRAP(cs, s, paintWindow, clonePaintWindow); WRAP(cs, s, outputChangeNotify, cloneOutputChangeNotify); s->privates[cd->screenPrivateIndex].ptr = cs; return TRUE; } static void cloneFiniScreen(CompPlugin * p, CompScreen * s) { CLONE_SCREEN(s); UNWRAP(cs, s, preparePaintScreen); UNWRAP(cs, s, donePaintScreen); UNWRAP(cs, s, paintScreen); UNWRAP(cs, s, paintWindow); UNWRAP(cs, s, outputChangeNotify); free(cs); } static Bool cloneInit(CompPlugin * p) { displayPrivateIndex = allocateDisplayPrivateIndex(); if (displayPrivateIndex < 0) return FALSE; return TRUE; } static void cloneFini(CompPlugin * p) { if (displayPrivateIndex >= 0) freeDisplayPrivateIndex(displayPrivateIndex); } CompPluginVTable cloneVTable = { "clone", N_("Clone Output"), N_("Output clone handler"), cloneInit, cloneFini, cloneInitDisplay, cloneFiniDisplay, cloneInitScreen, cloneFiniScreen, 0, /* InitWindow */ 0, /* FiniWindow */ cloneGetDisplayOptions, cloneSetDisplayOption, 0, /* GetScreenOptions */ 0, /* SetScreenOption */ 0, /* Deps */ 0, /* nDeps */ 0, /* Features */ 0, /* nFeatures */ BERYL_ABI_INFO, "beryl-plugins", "desktop", 0, 0, False, }; CompPluginVTable *getCompPluginInfo(void) { return &cloneVTable; }