/* * Copyright © 2005 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 * Reworked by Nigel Cunningham * Fixes to outline painting and constraining * code by Danny Baumann * * Parts of this code are taken from metacity and written by * Eliah Newren, Rob Adams and others */ #include #include #include #include #include #define ResizeUpMask (1L << 0) #define ResizeDownMask (1L << 1) #define ResizeLeftMask (1L << 2) #define ResizeRightMask (1L << 3) #define RESIZE_INITIATE_BUTTON_DEFAULT Button2 #define RESIZE_INITIATE_BUTTON_MODIFIERS_DEFAULT CompAltMask #define RESIZE_OPACITY_DEFAULT 100 #define RESIZE_OPACITY_MIN 1 #define RESIZE_OPACITY_MAX 100 #define RESIZE_OPACIFY_NON_OPAQUE_DEFAULT FALSE #define RESIZE_OPACITY_DEFAULT 100 #define RESIZE_OPACITY_MIN 1 #define RESIZE_OPACITY_MAX 100 #define RESIZE_OPACIFY_MIN_OPACITY_DEFAULT 80 #define RESIZE_OPACIFY_MIN_OPACITY_MIN 1 #define RESIZE_OPACIFY_MIN_OPACITY_MAX 100 #define RESIZE_SYNC_WINDOW_DEFAULT FALSE #define RESIZE_WARP_POINTER_DEFAULT FALSE #define RESIZE_INITIATE_KEY_DEFAULT "F8" #define RESIZE_INITIATE_KEY_MODIFIERS_DEFAULT CompAltMask struct _ResizeKeys { char *name; int dx; int dy; unsigned int warpMask; unsigned int resizeMask; } rKeys[] = { {"Left", -1, 0, ResizeLeftMask | ResizeRightMask, ResizeLeftMask}, {"Right", 1, 0, ResizeLeftMask | ResizeRightMask, ResizeRightMask}, {"Up", 0, -1, ResizeUpMask | ResizeDownMask, ResizeUpMask}, {"Down", 0, 1, ResizeUpMask | ResizeDownMask, ResizeDownMask} }; #define NUM_KEYS (sizeof (rKeys) / sizeof (rKeys[0])) #define MIN_KEY_WIDTH_INC 24 #define MIN_KEY_HEIGHT_INC 24 #define RESIZE_DISPLAY_OPTION_INITIATE 0 #define RESIZE_DISPLAY_OPTION_OPACITY 1 #define RESIZE_DISPLAY_OPTION_OPACIFY_MIN_OPACITY 2 #define RESIZE_DISPLAY_OPTION_SYNC_WINDOW 3 #define RESIZE_DISPLAY_OPTION_WARP_POINTER 4 #define RESIZE_DISPLAY_OPTION_MODE 5 #define RESIZE_DISPLAY_OPTION_BORDER_COLOR 6 #define RESIZE_DISPLAY_OPTION_FILL_COLOR 7 #define RESIZE_DISPLAY_OPTION_NUM 8 static int displayPrivateIndex; /* Order is important. In code, we check for > Stretch. */ typedef enum _ResizeMode { ResizeModeNormal, ResizeModeStretch, ResizeModeOutline, ResizeModeFilled, } ResizeMode; char *resizeModes[] = { N_("Normal"), N_("Stretch"), N_("Outline"), N_("Filled Outline") }; #define RESIZE_MODE_DEFAULT ResizeModeNormal #define NUM_RESIZE_MODES 4 /* Ignore the NumLock and CapsLock keys. */ #define WARP_IGNORE_MASK (~(LockMask | Mod2Mask)) typedef struct _ResizeDisplay { CompOption opt[RESIZE_DISPLAY_OPTION_NUM]; int screenPrivateIndex; HandleEventProc handleEvent; CompWindow *w; XWindowAttributes savedAttrib; int releaseButton; unsigned int mask; int width; int height; KeyCode key[NUM_KEYS]; int lastWidth; int lastHeight; int currentWidth; int currentHeight; int currentX; int currentY; int xdelta, ydelta; /* Offset when resize started */ int dx_to_apply, dy_to_apply, width_to_apply, height_to_apply; int right_edge, bottom_edge; GLushort resizeOpacity; GLushort opacifyMinOpacity; int resizeMode; Bool ungrabPending; GLushort border[4]; GLushort fill[4]; } ResizeDisplay; typedef struct _ResizeScreen { int grabIndex; Bool painted; int x, y; PaintWindowProc paintWindow; PreparePaintScreenProc preparePaintScreen; PaintScreenProc paintScreen; PaintTransformedScreenProc paintTransformedScreen; DonePaintScreenProc donePaintScreen; Cursor leftCursor; Cursor rightCursor; Cursor upCursor; Cursor upLeftCursor; Cursor upRightCursor; Cursor downCursor; Cursor downLeftCursor; Cursor downRightCursor; Cursor middleCursor; Cursor cursor[NUM_KEYS]; } ResizeScreen; #define GET_RESIZE_DISPLAY(d) \ ((ResizeDisplay *) (d)->privates[displayPrivateIndex].ptr) #define RESIZE_DISPLAY(d) \ ResizeDisplay *rd = GET_RESIZE_DISPLAY (d) #define GET_RESIZE_SCREEN(s) \ ((ResizeScreen *) (s)->privates[(GET_RESIZE_DISPLAY(s->display))->screenPrivateIndex].ptr) #define RESIZE_SCREEN(s) ResizeScreen *rs = GET_RESIZE_SCREEN(s) #define NUM_OPTIONS(d) (sizeof ((d)->opt) / sizeof (CompOption)) static void resizeUpdateWindowRealSize(CompDisplay * d, int move_only) { RESIZE_DISPLAY(d); XWindowChanges xwc; unsigned int xwcm = 0; if (!rd->w || rd->w->syncWait) return; if (move_only) { moveWindow(rd->w, rd->currentX - rd->w->attrib.x, rd->currentY - rd->w->attrib.y, TRUE, TRUE); syncWindowPosition(rd->w); } else { if (rd->currentX != rd->w->serverX) xwcm |= CWX; if (rd->currentY != rd->w->serverY) xwcm |= CWY; if (rd->currentWidth != rd->w->serverWidth) xwcm |= CWWidth; if (rd->currentHeight != rd->w->serverHeight) xwcm |= CWHeight; xwc.x = rd->currentX; xwc.y = rd->currentY; xwc.width = rd->currentWidth; xwc.height = rd->currentHeight; if (rd->resizeMode == ResizeModeNormal && rd->opt[RESIZE_DISPLAY_OPTION_SYNC_WINDOW].value.b) sendSyncRequest(rd->w); configureXWindow(rd->w, xwcm, &xwc); } } #define CLAMP(v, min, max) ((v) <= (min) ? (min) : (v) >= (max) ? (max) : (v)) static void resizeConstrainMinMax(CompWindow * w, int width, int height, int *newWidth, int *newHeight) { const XSizeHints *hints = &w->sizeHints; int min_width = 0; int min_height = 0; int max_width = MAXSHORT; int max_height = MAXSHORT; if ((hints->flags & PBaseSize) && (hints->flags & PMinSize)) { min_width = hints->min_width; min_height = hints->min_height; } else if (hints->flags & PBaseSize) { min_width = hints->base_width; min_height = hints->base_height; } else if (hints->flags & PMinSize) { min_width = hints->min_width; min_height = hints->min_height; } if (hints->flags & PMaxSize) { max_width = hints->max_width; max_height = hints->max_height; } /* clamp width and height to min and max values */ width = CLAMP(width, min_width, max_width); height = CLAMP(height, min_height, max_height); *newWidth = width; *newHeight = height; } static void resizeConstrainResizeIncrement(CompWindow * w, int width, int height, int *newWidth, int *newHeight) { int baseWidth, baseHeight; const XSizeHints *hints = &w->sizeHints; if (hints->flags & PResizeInc) { if (hints->flags & PBaseSize) { baseWidth = hints->base_width; baseHeight = hints->base_height; } else { baseWidth = 0; baseHeight = 0; } if ((width - baseWidth) % hints->width_inc) width = baseWidth + (((width - baseWidth) / hints->width_inc) * hints->width_inc); if ((height - baseHeight) % hints->height_inc) height = baseHeight + (((height - baseHeight) / hints->height_inc) * hints->height_inc); } *newWidth = width; *newHeight = height; } /* this code has its origin in metacity's boxes.c - thanks! */ static void resizeFindLinePointClosestToPoint (double x1, double y1, double x2, double y2, double px, double py, double *valx, double *valy) { /* I'll use the shorthand rx, ry for the return values, valx & valy. * Now, we need (rx,ry) to be on the line between (x1,y1) and (x2,y2). * For that to happen, we first need the slope of the line from (x1,y1) * to (rx,ry) must match the slope of (x1,y1) to (x2,y2), i.e.: * (ry-y1) (y2-y1) * ------- = ------- * (rx-x1) (x2-x1) * If x1==x2, though, this gives divide by zero errors, so we want to * rewrite the equation by multiplying both sides by (rx-x1)*(x2-x1): * (ry-y1)(x2-x1) = (y2-y1)(rx-x1) * This is a valid requirement even when x1==x2 (when x1==x2, this latter * equation will basically just mean that rx must be equal to both x1 and * x2) * * The other requirement that we have is that the line from (rx,ry) to * (px,py) must be perpendicular to the line from (x1,y1) to (x2,y2). So * we just need to get a vector in the direction of each line, take the * dot product of the two, and ensure that the result is 0: * (rx-px)*(x2-x1) + (ry-py)*(y2-y1) = 0. * * This gives us two equations and two unknowns: * * (ry-y1)(x2-x1) = (y2-y1)(rx-x1) * (rx-px)*(x2-x1) + (ry-py)*(y2-y1) = 0. * * This particular pair of equations is always solvable so long as * (x1,y1) and (x2,y2) are not the same point (and note that anyone who * calls this function that way is braindead because it means that they * really didn't specify a line after all). However, the caller should * be careful to avoid making (x1,y1) and (x2,y2) too close (e.g. like * 10^{-8} apart in each coordinate), otherwise roundoff error could * cause issues. Solving these equations by hand (or using Maple(TM) or * Mathematica(TM) or whatever) results in slightly messy expressions, * but that's all the below few lines do. */ double diffx, diffy, den; diffx = x2 - x1; diffy = y2 - y1; den = diffx * diffx + diffy * diffy; *valx = (py * diffx * diffy + px * diffx * diffx + y2 * x1 * diffy - y1 * x2 * diffy) / den; *valy = (px * diffx * diffy + py * diffy * diffy + x2 * y1 * diffx - x1 * y2 * diffx) / den; } /* this function is heavily inspired by metacity's constrain_aspect_ratio - thanks another time! */ static Bool resizeConstrainAspectRatio(CompWindow * w, int width, int height, int *retWidth, int *retHeight) { RESIZE_DISPLAY(w->screen->display); const XSizeHints *hints = &w->sizeHints; double minRatio, maxRatio; int newWidth, newHeight; double altWidth, altHeight; double bestWidth, bestHeight; int tolerance = 1; Bool resizeLeftRight = FALSE; Bool resizeUpDown = FALSE; newWidth = width; newHeight = height; if (hints->flags & PAspect) { minRatio = hints->min_aspect.x / (double) hints->min_aspect.y; maxRatio = hints->max_aspect.x / (double) hints->max_aspect.y; if (minRatio > maxRatio) return FALSE; resizeLeftRight = (rd->mask == (rd->mask & ResizeLeftMask)) || (rd->mask == (rd->mask & ResizeRightMask)); resizeUpDown = (rd->mask == (rd->mask & ResizeUpMask)) || (rd->mask == (rd->mask & ResizeDownMask)); if (resizeLeftRight || resizeUpDown) tolerance = 2; /* return if constraint already is satisfied */ if (((width - (height * minRatio)) > (-minRatio * tolerance)) && ((width - (height * maxRatio)) < (maxRatio * tolerance))) { return FALSE; } if (resizeLeftRight) newHeight = CLAMP (newHeight, newWidth / maxRatio, newWidth / minRatio); else if (resizeUpDown) newWidth = CLAMP (newWidth, newHeight * minRatio, newHeight * maxRatio); else { altWidth = CLAMP (newWidth, newHeight * minRatio, newHeight * maxRatio); altHeight = CLAMP (newHeight, newWidth / maxRatio, newWidth / minRatio); resizeFindLinePointClosestToPoint(altWidth, newHeight, newWidth, altHeight, newWidth, newHeight, &bestWidth, &bestHeight); newWidth = bestWidth; newHeight = bestHeight; } } *retWidth = newWidth; *retHeight = newHeight; return TRUE; } #undef CLAMP static void resizeApply(CompDisplay * d) { int move_only = 0, cw, ch; RESIZE_DISPLAY(d); if ((!rd->dx_to_apply && !rd->dy_to_apply) || rd->w->syncWait) return; /* Move window by delta if resizing from left/top */ if (rd->mask & ResizeLeftMask && rd->width_to_apply != rd->currentWidth) rd->currentX += rd->dx_to_apply; if (rd->mask & ResizeUpMask && rd->height_to_apply != rd->currentHeight) rd->currentY += rd->dy_to_apply; cw = rd->currentWidth = rd->width_to_apply; ch = rd->currentHeight = rd->height_to_apply; rd->dx_to_apply = rd->dy_to_apply = 0; constrainNewWindowSize(rd->w, rd->currentWidth, rd->currentHeight, &cw, &ch); if (rd->mask & ResizeLeftMask) rd->currentX = rd->right_edge - cw; if (rd->mask & ResizeUpMask) rd->currentY = rd->bottom_edge - ch; rd->currentWidth = cw; rd->currentHeight = ch; switch (rd->resizeMode) { case ResizeModeStretch: move_only = 1; case ResizeModeNormal: resizeUpdateWindowRealSize(d, move_only); } if (rd->resizeMode != ResizeModeNormal) resizeWindowPreview (rd->w, rd->currentX, rd->currentY, rd->currentWidth, rd->currentHeight); } static void resizeUpdateWindowSize(CompDisplay * d, int dx, int dy) { int w, h, new_height, new_width; RESIZE_DISPLAY(d); if (!rd->w) return; if (rd->resizeMode != ResizeModeNormal) damageScreen(rd->w->screen); if (rd->w->state & CompWindowStateMaximizedVertMask) rd->currentHeight = rd->w->serverHeight; if (rd->w->state & CompWindowStateMaximizedHorzMask) rd->currentWidth = rd->w->serverWidth; if (rd->w->state & CompWindowStateMaximizedVertMask && rd->w->state & CompWindowStateMaximizedHorzMask) return; w = rd->currentWidth; h = rd->currentHeight; /* Ensure we don't push the window if constraints stop it being * resized. */ if (!(rd->mask & (ResizeLeftMask | ResizeRightMask))) dx = 0; if (!(rd->mask & (ResizeUpMask | ResizeDownMask))) dy = 0; /* Apply the width/height modifications */ if (rd->mask & ResizeLeftMask) w -= dx; else if (rd->mask & ResizeRightMask) w += dx; if (rd->mask & ResizeUpMask) h -= dy; else if (rd->mask & ResizeDownMask) h += dy; /* Apply constraints */ resizeConstrainResizeIncrement(rd->w, w, h, &w, &h); new_width = w; new_height = h; resizeConstrainAspectRatio(rd->w, w, h, &new_width, &new_height); resizeConstrainMinMax(rd->w, new_width, new_height, &new_width, &new_height); if ((w != new_width) || (h != new_height)) { /* if the resizing hit constraints, move the mouse pointer to the new border to avoid desynchronization */ int pointerAdjustX = 0, pointerAdjustY = 0; if (rd->mask & ResizeRightMask) pointerAdjustX = new_width - w; else if (rd->mask & ResizeLeftMask) pointerAdjustX = w - new_width; if (rd->mask & ResizeDownMask) pointerAdjustY = new_height - h; else if (rd->mask & ResizeUpMask) pointerAdjustY = h - new_height; warpPointer(d, pointerAdjustX, pointerAdjustY); } dx -= new_width - w; dy -= new_height - h; /* Avoid annoying jiggling if we try to move 1 pixel */ if (abs(dx) < 2) dx = 0; if (abs(dy) < 2) dy = 0; /* If no resizing, drop out now. */ if (!dx && !dy) return; rd->dx_to_apply = dx; rd->dy_to_apply = dy; rd->width_to_apply = new_width; rd->height_to_apply = new_height; resizeApply(d); } static Bool resizeInitiate(CompDisplay * d, CompAction * action, CompActionState state, CompOption * option, int nOption) { CompWindow *w; ResizeScreen *rs; Window xid; unsigned int mods; unsigned int mask; int x, y; int button; RESIZE_DISPLAY(d); xid = getIntOptionNamed(option, nOption, "window", 0); w = findWindowAtDisplay(d, xid); if (!w) return FALSE; rs = GET_RESIZE_SCREEN(w->screen); rd->dx_to_apply = rd->dy_to_apply = 0; mods = getIntOptionNamed(option, nOption, "modifiers", 0); x = getIntOptionNamed(option, nOption, "x", w->attrib.x + (w->width / 2)); y = getIntOptionNamed(option, nOption, "y", w->attrib.y + (w->height / 2)); button = getIntOptionNamed(option, nOption, "button", -1); mask = getIntOptionNamed(option, nOption, "direction", 0); /* Initiate the resize in the direction suggested by the * quarter of the window the mouse is in, eg drag in top left * will resize up and to the left. Keyboard resize starts out * with the cursor in the middle of the window and then starts * resizing the edge corresponding to the next key press. */ if (state & CompActionStateInitKey) mask = 0; else if (!mask) { mask |= ((x - w->attrib.x) < (w->width / 2)) ? ResizeLeftMask : ResizeRightMask; mask |= ((y - w->attrib.y) < (w->height / 2)) ? ResizeUpMask : ResizeDownMask; } if (otherScreenGrabExist(w->screen, "resize", 0)) return FALSE; if (rd->w) return FALSE; if (w->type & (CompWindowTypeDesktopMask | CompWindowTypeDockMask | CompWindowTypeFullscreenMask)) return FALSE; if (w->attrib.override_redirect) return FALSE; if (state & CompActionStateInitButton) action->state |= CompActionStateTermButton; if (w->shaded) mask &= ~(ResizeUpMask | ResizeDownMask); rd->w = w; rd->mask = mask; rd->currentWidth = rd->width = w->serverWidth; rd->currentHeight = rd->height = w->serverHeight; rd->currentX = w->attrib.x; rd->currentY = w->attrib.y; rd->savedAttrib = w->attrib; rd->right_edge = rd->currentX + rd->currentWidth; rd->bottom_edge = rd->currentY + rd->currentHeight; rd->xdelta = rd->ydelta = 0; if (rd->resizeMode != ResizeModeNormal) rd->lastWidth = rd->lastHeight = 0.0f; else addWindowDamage(rd->w); d->lastPointerX = x; d->lastPointerY = y; if (!rs->grabIndex) { Cursor cursor; if (state & CompActionStateInitKey) cursor = rs->middleCursor; else if (mask & ResizeLeftMask) { if (mask & ResizeDownMask) cursor = rs->downLeftCursor; else if (mask & ResizeUpMask) cursor = rs->upLeftCursor; else cursor = rs->leftCursor; } else if (mask & ResizeRightMask) { if (mask & ResizeDownMask) cursor = rs->downRightCursor; else if (mask & ResizeUpMask) cursor = rs->upRightCursor; else cursor = rs->rightCursor; } else if (mask & ResizeUpMask) cursor = rs->upCursor; else cursor = rs->downCursor; rs->grabIndex = pushScreenGrab(w->screen, cursor, "resize"); } if (rs->grabIndex) { int mods_sought = virtualToRealModMask(d, rd-> opt [RESIZE_DISPLAY_OPTION_INITIATE]. value.action.button.modifiers); int button_sought = rd->opt[RESIZE_DISPLAY_OPTION_INITIATE].value.action. button.button; rd->releaseButton = button; /* FIXME: we use CompWindowGrabButtonMask only for the normal mode to prevent wobbling for the other modes this is fine for now, but may break the API (documentation would be nice...) */ Bool wobbleDesired = (rd->resizeMode == ResizeModeNormal); wobbleDesired |= (!(mask & ~(ResizeUpMask | ResizeDownMask))) && (w->state & CompWindowStateMaximizedVertMask); wobbleDesired |= (!(mask & ~(ResizeLeftMask | ResizeRightMask))) && (w->state & CompWindowStateMaximizedHorzMask); (w->screen->windowGrabNotify) (w, x, y, state, CompWindowGrabResizeMask | (wobbleDesired ? CompWindowGrabButtonMask : 0)); if (state & CompActionStateInitKey) { x = w->attrib.x + (w->width / 2); y = w->attrib.y + (w->height / 2); warpPointer(d, x - d->pointerX, y - d->pointerY); } else if ((mods & WARP_IGNORE_MASK) == mods_sought && button == button_sought && rd->opt[RESIZE_DISPLAY_OPTION_WARP_POINTER].value.b) { /* May warp if initiated with mouse combo */ x = w->attrib.x - w->input.left + ((mask & ResizeLeftMask) ? 0 : rd->width + w->input.left + w->input.right); y = w->attrib.y - w->input.top + ((mask & ResizeUpMask) ? 0 : rd->height + w->input.top + w->input.bottom); warpPointer(d, x - d->pointerX, y - d->pointerY); } rd->xdelta = (x - rd->currentX); if (rd->mask & ResizeRightMask) rd->xdelta = rd->width - rd->xdelta; rd->ydelta = (y - rd->currentY); if (rd->mask & ResizeDownMask) rd->ydelta = rd->height - rd->ydelta; } return FALSE; } static Bool resizeTerminate(CompDisplay * d, CompAction * action, CompActionState state, CompOption * option, int nOption) { RESIZE_DISPLAY(d); ResizeScreen *rs; action->state &= ~(CompActionStateTermKey | CompActionStateTermButton); if (!rd->w) return FALSE; rs = GET_RESIZE_SCREEN(rd->w->screen); if (state & CompActionStateCancel) { XWindowChanges xwc; sendSyncRequest(rd->w); xwc.x = rd->savedAttrib.x; xwc.y = rd->savedAttrib.y; xwc.width = rd->savedAttrib.width; xwc.height = rd->savedAttrib.height; if ((xwc.x != rd->w->serverX) || (xwc.y != rd->w->serverY) || (xwc.width != rd->w->serverWidth) || (xwc.height != rd->w->serverHeight)) { rd->ungrabPending = TRUE; } else { (rd->w->screen->windowUngrabNotify) (rd->w); } configureXWindow(rd->w, CWX | CWY | CWWidth | CWHeight, &xwc); } else if (rd->resizeMode != ResizeModeNormal && (rd->currentX != rd->w->serverX || rd->currentY != rd->w->serverY || rd->currentWidth != rd->w->serverWidth || rd->currentHeight != rd->w->serverHeight)) { rd->ungrabPending = TRUE; resizeUpdateWindowRealSize(d, 0); } else { syncWindowPosition(rd->w); (rd->w->screen->windowUngrabNotify) (rd->w); } if (rs->grabIndex) { removeScreenGrab(rd->w->screen, rs->grabIndex, NULL); rs->grabIndex = 0; } addWindowDamage(rd->w); if (rd->resizeMode != ResizeModeNormal) damageScreen(rd->w->screen); if (!rd->ungrabPending) rd->w = NULL; rd->releaseButton = 0; return FALSE; } static void resizeHandleKeyEvent(CompScreen * s, KeyCode keycode) { RESIZE_SCREEN(s); RESIZE_DISPLAY(s->display); if (rs->grabIndex && rd->w) { CompWindow *w; int i, widthInc, heightInc; w = rd->w; widthInc = w->sizeHints.width_inc; heightInc = w->sizeHints.height_inc; /* if the resize increment hint is smaller than our minimum amount, then resize by the next multiple of the hint value large than our minimum */ if (widthInc < MIN_KEY_WIDTH_INC) widthInc = ((MIN_KEY_WIDTH_INC / widthInc) + 1) * widthInc; if (heightInc < MIN_KEY_HEIGHT_INC) heightInc = ((MIN_KEY_HEIGHT_INC / heightInc) + 1) * heightInc; for (i = 0; i < NUM_KEYS; i++) { if (keycode != rd->key[i]) continue; if (rd->mask & rKeys[i].warpMask) XWarpPointer(s->display->display, None, None, 0, 0, 0, 0, rKeys[i].dx * widthInc, rKeys[i].dy * heightInc); else { int x, y, left, top, width, height; left = w->attrib.x - w->input.left; top = w->attrib.y - w->input.top; width = w->input.left + w->serverWidth + w->input.right; height = w->input.top + w->serverHeight + w->input.bottom; x = left + width * (rKeys[i].dx + 1) / 2; y = top + height * (rKeys[i].dy + 1) / 2; rd->mask = rKeys[i].resizeMask; /* recalculate delta */ rd->xdelta = (x - rd->currentX); if (rd->mask & ResizeRightMask) rd->xdelta = rd->width - rd->xdelta; rd->ydelta = (y - rd->currentY); if (rd->mask & ResizeDownMask) rd->ydelta = rd->height - rd->ydelta; warpPointer(s->display, x - s->display->pointerX, y - s->display->pointerY); updateScreenGrab(s, rs->grabIndex, rs->cursor[i]); } break; } } } static void resizeHandleMotionEvent(CompScreen * s, int xRoot, int yRoot) { int pointerDx, pointerDy; RESIZE_SCREEN(s); RESIZE_DISPLAY(s->display); if (!rs->grabIndex) return; if (rd->mask == 0 && rd->w) { rd->mask = ((xRoot - rd->w->attrib.x) < (rd->w->width / 2)) ? ResizeLeftMask : ResizeRightMask; rd->mask |= ((yRoot - rd->w->attrib.y) < (rd->w->height / 2)) ? ResizeUpMask : ResizeDownMask; } pointerDx = xRoot - rd->currentX; pointerDy = yRoot - rd->currentY; if (rd->mask & ResizeRightMask) pointerDx -= (rd->currentWidth - rd->xdelta); if (rd->mask & ResizeLeftMask) pointerDx -= rd->xdelta; if (rd->mask & ResizeDownMask) pointerDy -= (rd->currentHeight - rd->ydelta); if (rd->mask & ResizeUpMask) pointerDy -= rd->ydelta; /* Any motion? */ if (!pointerDx && !pointerDy) return; resizeUpdateWindowSize(s->display, pointerDx, pointerDy); } static void resizeHandleMoveResizeMessage(CompDisplay * d, XEvent * event) { CompWindow *w; CompOption o[6]; CompAction *action; RESIZE_DISPLAY(d); if (event->xclient.data.l[2] > WmMoveResizeSizeLeft && event->xclient.data.l[2] != WmMoveResizeSizeKeyboard) return; w = findWindowAtDisplay(d, event->xclient.window); if (!w) return; action = &rd->opt[RESIZE_DISPLAY_OPTION_INITIATE].value.action; o[0].type = CompOptionTypeInt; o[0].name = "window"; o[0].value.i = event->xclient.window; if (event->xclient.data.l[2] == WmMoveResizeSizeKeyboard) { o[1].type = CompOptionTypeInt; o[1].name = "button"; o[1].value.i = 0; resizeInitiate(d, action, CompActionStateInitKey, o, 2); } else { static unsigned int mask[] = { ResizeUpMask | ResizeLeftMask, ResizeUpMask, ResizeUpMask | ResizeRightMask, ResizeRightMask, ResizeDownMask | ResizeRightMask, ResizeDownMask, ResizeDownMask | ResizeLeftMask, ResizeLeftMask, }; unsigned int mods; Window root, child; int xRoot, yRoot, i; XQueryPointer(d->display, w->screen->root, &root, &child, &xRoot, &yRoot, &i, &i, &mods); /* TODO: not only button 1 */ if (mods & Button1Mask) { o[1].type = CompOptionTypeInt; o[1].name = "modifiers"; o[1].value.i = mods; o[2].type = CompOptionTypeInt; o[2].name = "x"; o[2].value.i = event->xclient.data.l[0]; o[3].type = CompOptionTypeInt; o[3].name = "y"; o[3].value.i = event->xclient.data.l[1]; o[4].type = CompOptionTypeInt; o[4].name = "direction"; o[4].value.i = mask[event->xclient.data.l[2]]; o[5].type = CompOptionTypeInt; o[5].name = "button"; o[5].value.i = event->xclient.data.l[3] ? event->xclient.data.l[3] : -1; resizeInitiate(d, action, CompActionStateInitButton, o, 6); resizeHandleMotionEvent(w->screen, xRoot, yRoot); } } } static void resizeHandleEvent(CompDisplay * d, XEvent * event) { CompScreen *s; RESIZE_DISPLAY(d); switch (event->type) { case KeyPress: s = findScreenAtDisplay(d, event->xkey.root); if (s) resizeHandleKeyEvent(s, event->xkey.keycode); break; case KeyRelease: break; case ButtonPress: s = findScreenAtDisplay(d, event->xbutton.root); if (s) { RESIZE_SCREEN(s); if (rs->grabIndex) { resizeTerminate(d, &rd-> opt [RESIZE_DISPLAY_OPTION_INITIATE]. value.action, 0, NULL, 0); } } break; case ButtonRelease: { CompAction *action = &rd->opt[RESIZE_DISPLAY_OPTION_INITIATE].value.action; if (action->state & CompActionStateTermButton && (rd->releaseButton == -1 || event->xbutton.button == rd->releaseButton)) resizeTerminate(d, action, CompActionStateTermButton, NULL, 0); } break; case MotionNotify: s = findScreenAtDisplay(d, event->xmotion.root); if (s) resizeHandleMotionEvent(s, d->pointerX, d->pointerY); break; case EnterNotify: case LeaveNotify: s = findScreenAtDisplay(d, event->xcrossing.root); if (s) resizeHandleMotionEvent(s, d->pointerX, d->pointerY); break; case ClientMessage: if (event->xclient.message_type == d->wmMoveResizeAtom) resizeHandleMoveResizeMessage(d, event); break; case DestroyNotify: if (rd->w && rd->w->id == event->xdestroywindow.window) { CompAction *action = &rd->opt[RESIZE_DISPLAY_OPTION_INITIATE].value.action; resizeTerminate(d, action, 0, NULL, 0); } break; case UnmapNotify: if (rd->w && rd->w->id == event->xunmap.window) { CompAction *action = &rd->opt[RESIZE_DISPLAY_OPTION_INITIATE].value.action; resizeTerminate(d, action, 0, NULL, 0); } break; default: if (event->type == d->syncEvent + XSyncAlarmNotify && rd->w) { XSyncAlarmNotifyEvent *sa; sa = (XSyncAlarmNotifyEvent *) event; if (rd->w->syncAlarm == sa->alarm) resizeApply(d); } break; } UNWRAP(rd, d, handleEvent); (*d->handleEvent) (d, event); WRAP(rd, d, handleEvent, resizeHandleEvent); switch (event->type) { case ConfigureNotify: if (rd->ungrabPending) { if (rd->w && (rd->w->id == event->xconfigure.window)) { (rd->w->screen->windowUngrabNotify) (rd->w); syncWindowPosition(rd->w); rd->w = NULL; } rd->ungrabPending = FALSE; } break; default: break; } } static void resizePreparePaintScreen(CompScreen * s, int msSinceLastPaint) { RESIZE_SCREEN(s); RESIZE_DISPLAY(s->display); if (rd->w && rd->resizeMode > ResizeModeNormal && rs->grabIndex && (rd->lastWidth != rd->currentWidth || rd->lastHeight != rd->currentHeight)) { damageScreen(s); } UNWRAP(rs, s, preparePaintScreen); (*s->preparePaintScreen) (s, msSinceLastPaint); WRAP(rs, s, preparePaintScreen, resizePreparePaintScreen); } static Bool resizePaintWindow(CompWindow * w, const WindowPaintAttrib * attrib, Region region, unsigned int mask) { WindowPaintAttrib sAttrib; CompScreen *s = w->screen; Bool status; RESIZE_SCREEN(s); RESIZE_DISPLAY(s->display); if ((rs->grabIndex || rd->ungrabPending) && (rd->w == w)) { sAttrib = *attrib; if (rd->resizeMode != ResizeModeNormal) mask |= PAINT_WINDOW_TRANSFORMED_MASK; if (rd->resizeMode == ResizeModeStretch) { sAttrib.xScale = (float)(rd->currentWidth) / (float)(rd->w->attrib.width); sAttrib.yScale = (float)(rd->currentHeight) / (float)(rd->w->attrib.height); } if (rd->resizeOpacity != OPAQUE && sAttrib.opacity >= rd->opacifyMinOpacity) { sAttrib.opacity = (sAttrib.opacity * rd->resizeOpacity) >> 16; } attrib = &sAttrib; } UNWRAP(rs, s, paintWindow); status = (*s->paintWindow) (w, attrib, region, mask); WRAP(rs, s, paintWindow, resizePaintWindow); return status; } static void resizePaintOutline(CompScreen * s, const ScreenPaintAttrib * sa, int output, Bool transformed) { int x1 = 0, x2 = 0, y1 = 0, y2 = 0; RESIZE_SCREEN(s); RESIZE_DISPLAY(s->display); if (rd->resizeMode > ResizeModeStretch && rd->w && !(rd->w->state & MAXIMIZE_STATE) && rs->grabIndex) { x1 = rd->currentX - rd->w->input.left; x2 = rd->currentX + rd->currentWidth + rd->w->input.right; y1 = rd->currentY - rd->w->input.top; y2 = rd->currentY + (rd->w->shaded ? rd->w->height : rd->currentHeight) + rd->w->input.bottom; 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); if (rd->resizeMode == ResizeModeFilled) { glColor4usv(rd->opt[RESIZE_DISPLAY_OPTION_FILL_COLOR].value.c); glRecti(x1, y2, x2, y1); } /* This section draws the outline */ glColor4usv(rd->opt[RESIZE_DISPLAY_OPTION_BORDER_COLOR].value.c); 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 resizePaintScreen(CompScreen * s, const ScreenPaintAttrib * sAttrib, Region region, int output, unsigned int mask) { Bool status = FALSE; RESIZE_SCREEN(s); RESIZE_DISPLAY(s->display); rs->painted = FALSE; rs->x = s->x; rs->y = s->y; if (rd->w && rs->grabIndex && rd->resizeMode == ResizeModeStretch) mask |= PAINT_SCREEN_WITH_TRANSFORMED_WINDOWS_MASK; UNWRAP(rs, s, paintScreen); status = (*s->paintScreen) (s, sAttrib, region, output, mask); WRAP(rs, s, paintScreen, resizePaintScreen); if (status && !rs->painted) resizePaintOutline(s, sAttrib, output, FALSE); return status; } static void resizePaintTransformedScreen(CompScreen * s, const ScreenPaintAttrib * sa, Region region, int output, unsigned int mask) { RESIZE_SCREEN(s); UNWRAP(rs, s, paintTransformedScreen); (*s->paintTransformedScreen) (s, sa, region, output, mask); WRAP(rs, s, paintTransformedScreen, resizePaintTransformedScreen); if ((rs->x == s->x) && (rs->y == s->y)) { rs->painted = TRUE; resizePaintOutline(s, sa, output, TRUE); } } static void resizeDonePaintScreen(CompScreen * s) { RESIZE_SCREEN(s); RESIZE_DISPLAY(s->display); if (rd->resizeMode != ResizeModeNormal && rd->w && rs->grabIndex) { rd->lastWidth = rd->currentWidth; rd->lastHeight = rd->currentHeight; } UNWRAP(rs, s, donePaintScreen); (*s->donePaintScreen) (s); WRAP(rs, s, donePaintScreen, resizeDonePaintScreen); } static Bool resizeSetDisplayOption(CompDisplay * display, char *name, CompOptionValue * value) { CompOption *o; int index; RESIZE_DISPLAY(display); o = compFindOption(rd->opt, NUM_OPTIONS(rd), name, &index); if (!o) return FALSE; switch (index) { case RESIZE_DISPLAY_OPTION_INITIATE: if (setDisplayAction(display, o, value)) return TRUE; break; case RESIZE_DISPLAY_OPTION_OPACITY: if (compSetIntOption(o, value)) { rd->resizeOpacity = (o->value.i * OPAQUE) / 100; return TRUE; } break; case RESIZE_DISPLAY_OPTION_OPACIFY_MIN_OPACITY: if (compSetIntOption(o, value)) { rd->opacifyMinOpacity = (o->value.i * OPAQUE) / 100; return TRUE; } break; case RESIZE_DISPLAY_OPTION_SYNC_WINDOW: case RESIZE_DISPLAY_OPTION_WARP_POINTER: if (compSetBoolOption(o, value)) return TRUE; break; case RESIZE_DISPLAY_OPTION_MODE: if (compSetStringOption(o, value)) { int i; for (i = 0; i < o->rest.s.nString; i++) if (strcmp(resizeModes[i], o->value.s) == 0) rd->resizeMode = (ResizeMode) i; return TRUE; } break; case RESIZE_DISPLAY_OPTION_BORDER_COLOR: case RESIZE_DISPLAY_OPTION_FILL_COLOR: if (compSetColorOption(o, value)) return TRUE; break; default: break; } return FALSE; } static void resizeDisplayInitOptions(ResizeDisplay * rd) { CompOption *o; o = &rd->opt[RESIZE_DISPLAY_OPTION_INITIATE]; o->advanced = False; o->name = "initiate"; o->group = N_("Binding"); o->subGroup = N_(""); o->displayHints = ""; o->shortDesc = N_("Initiate Window Resize"); o->longDesc = N_("Start Resizing a Window."); o->type = CompOptionTypeAction; o->value.action.initiate = resizeInitiate; o->value.action.terminate = resizeTerminate; 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 = RESIZE_INITIATE_BUTTON_MODIFIERS_DEFAULT; o->value.action.button.button = RESIZE_INITIATE_BUTTON_DEFAULT; o->value.action.type |= CompBindingTypeKey; o->value.action.state |= CompActionStateInitKey; o->value.action.key.modifiers = RESIZE_INITIATE_KEY_MODIFIERS_DEFAULT; o->value.action.key.keysym = XStringToKeysym(RESIZE_INITIATE_KEY_DEFAULT); o = &rd->opt[RESIZE_DISPLAY_OPTION_WARP_POINTER]; o->advanced = False; o->name = "warp_pointer"; o->group = N_("Behaviour"); o->subGroup = N_(""); o->displayHints = ""; o->shortDesc = N_ ("Warp Pointer When Starting a Resize With Mouse Initiate Combo."); o->longDesc = N_("If this is set, the Pointer will be Warped to the " "closest corner when you start a Resize."); o->type = CompOptionTypeBool; o->value.b = RESIZE_WARP_POINTER_DEFAULT; o = &rd->opt[RESIZE_DISPLAY_OPTION_OPACITY]; o->advanced = False; o->name = "opacity"; o->group = N_("Appearance"); o->subGroup = N_("Opacity"); o->displayHints = ""; o->shortDesc = N_("Opacity"); o->longDesc = N_("Opacity level of resizing windows."); o->type = CompOptionTypeInt; o->value.i = RESIZE_OPACITY_DEFAULT; o->rest.i.min = RESIZE_OPACITY_MIN; o->rest.i.max = RESIZE_OPACITY_MAX; o = &rd->opt[RESIZE_DISPLAY_OPTION_OPACIFY_MIN_OPACITY]; o->advanced = False; o->name = "opacify_min_opacity"; o->group = N_("Appearance"); o->subGroup = N_("Opacity"); o->displayHints = ""; o->shortDesc = N_("Minimum Opacity for Opacify"); o->longDesc = N_ ("Opacify only Windows whose Opacity is higher than this value."); o->type = CompOptionTypeInt; o->value.i = RESIZE_OPACIFY_MIN_OPACITY_DEFAULT; o->rest.i.min = RESIZE_OPACIFY_MIN_OPACITY_MIN; o->rest.i.max = RESIZE_OPACIFY_MIN_OPACITY_MAX; o = &rd->opt[RESIZE_DISPLAY_OPTION_SYNC_WINDOW]; o->advanced = False; o->name = "sync_window"; o->group = N_("Behaviour"); o->subGroup = N_(""); o->displayHints = ""; o->shortDesc = N_("Repaints the Window on Each Resize Step"); o->longDesc = N_("If this is set to true the Window will Repaint itself " "during Resize, which may cause some lag."); o->type = CompOptionTypeBool; o->value.b = RESIZE_SYNC_WINDOW_DEFAULT; o = &rd->opt[RESIZE_DISPLAY_OPTION_MODE]; o->advanced = False; o->name = "resize_mode"; o->group = N_("Behaviour"); o->subGroup = N_(""); o->displayHints = ""; o->shortDesc = N_("Resize Display Mode"); o->longDesc = N_ ("Select between 'Normal', 'Stretched Texture', " "'Outline and Filled' outline modes."); o->type = CompOptionTypeString; o->value.s = strdup(resizeModes[RESIZE_MODE_DEFAULT]); o->rest.s.string = resizeModes; o->rest.s.nString = NUM_RESIZE_MODES; o = &rd->opt[RESIZE_DISPLAY_OPTION_BORDER_COLOR]; o->advanced = False; o->name = "border_color"; o->group = N_("Appearance"); o->subGroup = N_("Outline Mode"); o->displayHints = ""; o->shortDesc = N_("Outline Color"); o->longDesc = N_("Outline Color for Outline and Filled Outline modes."); o->type = CompOptionTypeColor; o->value.c[0] = 0x2fff; o->value.c[1] = 0x2fff; o->value.c[2] = 0x4fff; o->value.c[3] = 0x9fff; o = &rd->opt[RESIZE_DISPLAY_OPTION_FILL_COLOR]; o->advanced = False; o->name = "fill_color"; o->group = N_("Appearance"); o->subGroup = N_("Outline Mode"); o->displayHints = ""; o->shortDesc = N_("Fill Color"); o->longDesc = N_("Fill Color for Filled Outline mode."); o->type = CompOptionTypeColor; o->value.c[0] = 0x2fff; o->value.c[1] = 0x2fff; o->value.c[2] = 0x4fff; o->value.c[3] = 0x4fff; } static CompOption *resizeGetDisplayOptions(CompDisplay * display, int *count) { if (display) { RESIZE_DISPLAY(display); *count = NUM_OPTIONS(rd); return rd->opt; } else { ResizeDisplay *rd = malloc(sizeof(ResizeDisplay)); resizeDisplayInitOptions(rd); *count = NUM_OPTIONS(rd); return rd->opt; } } static Bool resizeInitDisplay(CompPlugin * p, CompDisplay * d) { ResizeDisplay *rd; int i; rd = malloc(sizeof(ResizeDisplay)); if (!rd) return FALSE; rd->screenPrivateIndex = allocateScreenPrivateIndex(d); if (rd->screenPrivateIndex < 0) { free(rd); return FALSE; } rd->resizeMode = ResizeModeNormal; rd->resizeOpacity = (RESIZE_OPACITY_DEFAULT * OPAQUE) / 100; rd->opacifyMinOpacity = (RESIZE_OPACIFY_MIN_OPACITY_DEFAULT * OPAQUE) / 100; resizeDisplayInitOptions(rd); rd->w = 0; rd->ungrabPending = FALSE; rd->releaseButton = 0; for (i = 0; i < NUM_KEYS; i++) rd->key[i] = XKeysymToKeycode(d->display, XStringToKeysym(rKeys[i].name)); WRAP(rd, d, handleEvent, resizeHandleEvent); d->privates[displayPrivateIndex].ptr = rd; return TRUE; } static void resizeFiniDisplay(CompPlugin * p, CompDisplay * d) { RESIZE_DISPLAY(d); freeScreenPrivateIndex(d, rd->screenPrivateIndex); UNWRAP(rd, d, handleEvent); free(rd); } static Bool resizeInitScreen(CompPlugin * p, CompScreen * s) { ResizeScreen *rs; RESIZE_DISPLAY(s->display); rs = malloc(sizeof(ResizeScreen)); if (!rs) return FALSE; rs->grabIndex = 0; rs->leftCursor = XCreateFontCursor(s->display->display, XC_left_side); rs->rightCursor = XCreateFontCursor(s->display->display, XC_right_side); rs->upCursor = XCreateFontCursor(s->display->display, XC_top_side); rs->upLeftCursor = XCreateFontCursor(s->display->display, XC_top_left_corner); rs->upRightCursor = XCreateFontCursor(s->display->display, XC_top_right_corner); rs->downCursor = XCreateFontCursor(s->display->display, XC_bottom_side); rs->downLeftCursor = XCreateFontCursor(s->display->display, XC_bottom_left_corner); rs->downRightCursor = XCreateFontCursor(s->display->display, XC_bottom_right_corner); rs->middleCursor = XCreateFontCursor(s->display->display, XC_fleur); rs->cursor[0] = rs->leftCursor; rs->cursor[1] = rs->rightCursor; rs->cursor[2] = rs->upCursor; rs->cursor[3] = rs->downCursor; addScreenAction(s, &rd->opt[RESIZE_DISPLAY_OPTION_INITIATE].value.action); WRAP(rs, s, paintWindow, resizePaintWindow); WRAP(rs, s, preparePaintScreen, resizePreparePaintScreen); WRAP(rs, s, paintScreen, resizePaintScreen); WRAP(rs, s, paintTransformedScreen, resizePaintTransformedScreen); WRAP(rs, s, donePaintScreen, resizeDonePaintScreen); s->privates[rd->screenPrivateIndex].ptr = rs; return TRUE; } static void resizeFiniScreen(CompPlugin * p, CompScreen * s) { RESIZE_SCREEN(s); RESIZE_DISPLAY(s->display); UNWRAP(rs, s, paintWindow); UNWRAP(rs, s, preparePaintScreen); UNWRAP(rs, s, paintScreen); UNWRAP(rs, s, paintTransformedScreen); UNWRAP(rs, s, donePaintScreen); removeScreenAction(s, &rd->opt[RESIZE_DISPLAY_OPTION_INITIATE].value.action); XFreeCursor(s->display->display, rs->leftCursor); XFreeCursor(s->display->display, rs->rightCursor); XFreeCursor(s->display->display, rs->upCursor); XFreeCursor(s->display->display, rs->upLeftCursor); XFreeCursor(s->display->display, rs->upRightCursor); XFreeCursor(s->display->display, rs->downCursor); XFreeCursor(s->display->display, rs->downLeftCursor); XFreeCursor(s->display->display, rs->downRightCursor); XFreeCursor(s->display->display, rs->middleCursor); free(rs); } static Bool resizeInit(CompPlugin * p) { displayPrivateIndex = allocateDisplayPrivateIndex(); if (displayPrivateIndex < 0) return FALSE; return TRUE; } static void resizeFini(CompPlugin * p) { if (displayPrivateIndex >= 0) freeDisplayPrivateIndex(displayPrivateIndex); } CompPluginFeature resizeFeatures[] = { {"resize"} }; CompPluginVTable resizeVTable = { "resize", N_("Resize Window"), N_("Resize window"), resizeInit, resizeFini, resizeInitDisplay, resizeFiniDisplay, resizeInitScreen, resizeFiniScreen, 0, /* InitWindow */ 0, /* FiniWindow */ resizeGetDisplayOptions, resizeSetDisplayOption, 0, /* GetScreenOptions */ 0, /* SetScreenOption */ NULL, 0, resizeFeatures, sizeof(resizeFeatures) / sizeof(resizeFeatures[0]), BERYL_ABI_INFO, "beryl-plugins", "wm", 0, 0, True, }; CompPluginVTable *getCompPluginInfo(void) { return &resizeVTable; }