diff options
Diffstat (limited to 'kwin/client.cpp')
-rw-r--r-- | kwin/client.cpp | 637 |
1 files changed, 636 insertions, 1 deletions
diff --git a/kwin/client.cpp b/kwin/client.cpp index 5f281f2ab..0d2c5cfbf 100644 --- a/kwin/client.cpp +++ b/kwin/client.cpp @@ -11,9 +11,12 @@ License. See the file "COPYING" for the exact licensing terms. #include "client.h" +#include <math.h> + #include <tqapplication.h> #include <tqpainter.h> #include <tqdatetime.h> +#include <tqimage.h> #include <kprocess.h> #include <unistd.h> #include <kstandarddirs.h> @@ -39,9 +42,25 @@ extern Time qt_x_time; extern Atom qt_window_role; extern Atom qt_sm_client_id; +// wait 200 ms before drawing shadow after move/resize +static const int SHADOW_DELAY = 200; + namespace KWinInternal { +/* TODO: Remove this once X has real translucency. + * + * A list of the regions covered by all shadows and the Clients to which they + * belong. Used to redraw shadows when a window overlapping or underlying a + * shadow is moved, resized, or hidden. + */ +struct ShadowRegion + { + TQRegion region; + Client *client; + }; +static TQValueList<ShadowRegion> shadowRegions; + /* Creating a client: @@ -100,6 +119,13 @@ Client::Client( Workspace *ws ) autoRaiseTimer = 0; shadeHoverTimer = 0; + shadowDelayTimer = new TQTimer(this); + opacityCache = &activeOpacityCache; + shadowAfterClient = NULL; + shadowWidget = NULL; + shadowMe = true; + connect(shadowDelayTimer, TQT_SIGNAL(timeout()), TQT_SLOT(drawShadow())); + // set the initial mapping state mapping_state = WithdrawnState; desk = 0; // no desktop yet @@ -145,7 +171,7 @@ Client::Client( Workspace *ws ) maxmode_restore = MaximizeRestore; cmap = None; - + frame_geometry = TQRect( 0, 0, 100, 100 ); // so that decorations don't start with size being (0,0) client_size = TQSize( 100, 100 ); custom_opacity = false; @@ -188,6 +214,8 @@ void Client::releaseWindow( bool on_shutdown ) if (!custom_opacity) setOpacity(FALSE); if (moveResizeMode) leaveMoveResize(); + removeShadow(); + drawIntersectingShadows(); finishWindowRules(); ++postpone_geometry_updates; // grab X during the release to make removing of properties, setting to withdrawn state @@ -248,6 +276,8 @@ void Client::destroyClient() StackingUpdatesBlocker blocker( workspace()); if (moveResizeMode) leaveMoveResize(); + removeShadow(); + drawIntersectingShadows(); finishWindowRules(); ++postpone_geometry_updates; setModal( false ); @@ -304,6 +334,7 @@ void Client::updateDecoration( bool check_workspace_pos, bool force ) if( do_show ) decoration->widget()->show(); updateFrameExtents(); + updateOpacityCache(); } void Client::destroyDecoration() @@ -434,6 +465,12 @@ void Client::resizeDecoration( const TQSize& s ) TQResizeEvent e( s, oldsize ); TQApplication::sendEvent( decoration->widget(), &e ); } + if (!moveResizeMode && options->shadowEnabled(isActive())) + { + // If the user is manually resizing, let Client::leaveMoveResize() + // decide when to redraw the shadow + updateOpacityCache(); + } } bool Client::noBorder() const @@ -471,6 +508,7 @@ void Client::updateShape() noborder = true; updateDecoration( true ); } + updateOpacityCache(); if ( shape() ) { XShapeCombineShape(qt_xdisplay(), frameId(), ShapeBounding, @@ -862,6 +900,16 @@ void Client::setShade( ShadeMode mode ) XMapWindow( qt_xdisplay(), wrapperId()); XMapWindow( qt_xdisplay(), window()); XDeleteProperty (qt_xdisplay(), client, atoms->net_wm_window_shade); + if (options->shadowEnabled(false)) + { + for (ClientList::ConstIterator it = transients().begin(); + it != transients().end(); ++it) + { + (*it)->removeShadow(); + (*it)->drawDelayedShadow(); + } + } + if ( isActive() ) workspace()->requestFocus( this ); } @@ -946,6 +994,589 @@ void Client::updateVisibility() } } +void Client::setShadowed(bool shadowed) +{ + bool wasShadowed; + + wasShadowed = isShadowed(); + shadowMe = options->shadowEnabled(isActive()) ? shadowed : false; + + if (shadowMe) { + if (!wasShadowed) + drawShadow(); + } + else { + if (wasShadowed) { + removeShadow(); + + if (!activeOpacityCache.isNull()) + activeOpacityCache.resize(0); + if (!inactiveOpacityCache.isNull()) + inactiveOpacityCache.resize(0); + } + } +} + +void Client::updateOpacityCache() +{ + if (!activeOpacityCache.isNull()) + activeOpacityCache.resize(0); + if (!inactiveOpacityCache.isNull()) + inactiveOpacityCache.resize(0); + + if (!moveResizeMode) { + // If the user is manually resizing, let Client::finishMoveResize() + // decide when to redraw the shadow + removeShadow(); + drawIntersectingShadows(); + if (options->shadowEnabled(isActive())) + drawDelayedShadow(); + } +} + +/*! + Redraw shadows that were previously occluding or occluded by this window, + to avoid visual glitches. + */ +void Client::drawIntersectingShadows() { + //Client *reshadowClient; + TQRegion region; + //TQPtrList<Client> reshadowClients; + TQValueList<Client *> reshadowClients; + TQValueListIterator<ShadowRegion> it; + TQValueListIterator<Client *> it2; + + if (!options->shadowEnabled(false)) + // No point in redrawing overlapping/overlapped shadows if only the + // active window has a shadow. + return; + + region = shapeBoundingRegion; + + // Generate list of Clients whose shadows need to be redrawn. That is, + // those that are currently intersecting or intersected by other windows or + // shadows. + for (it = shadowRegions.begin(); it != shadowRegions.end(); ++it) + if ((isOnAllDesktops() || (*it).client->isOnCurrentDesktop()) && + !(*it).region.intersect(region).isEmpty()) + reshadowClients.append((*it).client); + + // Redraw shadows for each of the Clients in the list generated above + for (it2 = reshadowClients.begin(); it2 != reshadowClients.end(); + ++it2) { + (*it2)->removeShadow(); + (*it2)->drawDelayedShadow(); + } +} + +/*! + Redraw shadows that are above the current window in the stacking order. + Furthermore, redraw them in the same order as they come in the stacking order + from bottom to top. + */ +void Client::drawOverlappingShadows(bool waitForMe) +{ + Client *aClient; + TQRegion region; + TQValueList<Client *> reshadowClients; + ClientList stacking_order; + ClientList::ConstIterator it; + TQValueListIterator<ShadowRegion> it2; + TQValueListIterator<Client *> it3; + + if (!options->shadowEnabled(false)) + // No point in redrawing overlapping/overlapped shadows if only the + // active window has a shadow. + return; + + region = shapeBoundingRegion; + + stacking_order = workspace()->stackingOrder(); + for (it = stacking_order.fromLast(); it != stacking_order.end(); --it) { + // Find the position of this window in the stacking order. + if ((*it) == this) + break; + } + ++it; + while (it != stacking_order.end()) { + if ((*it)->windowType() == NET::Dock) { + // This function is only interested in windows whose shadows don't + // have weird stacking rules. + ++it; + continue; + } + + // Generate list of Clients whose shadows need to be redrawn. That is, + // those that are currently overlapping or overlapped by other windows + // or shadows. The list should be in order from bottom to top in the + // stacking order. + for (it2 = shadowRegions.begin(); it2 != shadowRegions.end(); ++it2) { + if ((*it2).client == (*it)) { + if ((isOnAllDesktops() || (*it2).client->isOnCurrentDesktop()) + && !(*it2).region.intersect(region).isEmpty()) + reshadowClients.append((*it2).client); + } + } + ++it; + } + + // Redraw shadows for each of the Clients in the list generated above + for (it3 = reshadowClients.begin(); it3 != reshadowClients.end(); ++it3) { + (*it3)->removeShadow(); + if (it3 == reshadowClients.begin()) { + if (waitForMe) + (*it3)->drawShadowAfter(this); + else + (*it3)->drawDelayedShadow(); + } + else { + --it3; + aClient = (*it3); + ++it3; + (*it3)->drawShadowAfter(aClient); + } + } +} + +/*! + Draw shadow after some time has elapsed, to give recently exposed windows a + chance to repaint before a shadow gradient is drawn over them. + */ +void Client::drawDelayedShadow() +{ + shadowDelayTimer->stop(); + shadowDelayTimer->start(SHADOW_DELAY, true); +} + +/*! + Draw shadow immediately after the specified Client's shadow finishes drawing. + */ +void Client::drawShadowAfter(Client *after) +{ + shadowAfterClient = after; + connect(after, TQT_SIGNAL(shadowDrawn()), TQT_SLOT(drawShadow())); +} + +/*! + Draw a shadow under this window and XShape the shadow accordingly. + */ +void Client::drawShadow() +{ + Window shadows[2]; + XRectangle *shapes; + int i, count, ordering; + + // If we are waiting for another Client's shadow to be drawn, stop waiting now + if (shadowAfterClient != NULL) { + disconnect(shadowAfterClient, TQT_SIGNAL(shadowDrawn()), this, TQT_SLOT(drawShadow())); + shadowAfterClient = NULL; + } + + if (!isOnCurrentDesktop()) + return; + + /* Store this window's ShapeBoundingRegion even if shadows aren't drawn for + * this type of window. Otherwise, drawIntersectingShadows() won't update + * properly when this window is moved/resized/hidden/closed. + */ + shapes = XShapeGetRectangles(qt_xdisplay(), frameId(), ShapeBounding, + &count, &ordering); + if (!shapes) + // XShape extension not supported + shapeBoundingRegion = TQRegion(x(), y(), width(), height()); + else { + shapeBoundingRegion = TQRegion(); + for (i = 0; i < count; i++) { + // Translate XShaped window into a TQRegion + TQRegion shapeRectangle(shapes[i].x, shapes[i].y, shapes[i].width, + shapes[i].height); + shapeBoundingRegion += shapeRectangle; + } + if (isShade()) + // Since XResize() doesn't change a window's XShape regions, ensure that + // shapeBoundingRegion is not taller than the window's shaded height, + // or the bottom shadow will appear to be missing + shapeBoundingRegion &= TQRegion(0, 0, width(), height()); + shapeBoundingRegion.translate(x(), y()); + } + + if (!isShadowed() || hidden || isMinimized() || + maximizeMode() == MaximizeFull || + !options->shadowWindowType(windowType())) { + XFree(shapes); + + // Tell whatever Clients are listening that this Client's shadow has been drawn. + // It hasn't, but there's no sense waiting for something that won't happen. + emit shadowDrawn(); + + return; + } + + removeShadow(); + + TQMemArray<QRgb> pixelData; + TQPixmap shadowPixmap; + TQRect shadow; + TQRegion exposedRegion; + ShadowRegion shadowRegion; + int thickness, xOffset, yOffset; + + thickness = options->shadowThickness(isActive()); + xOffset = options->shadowXOffset(isActive()); + yOffset = options->shadowYOffset(isActive()); + opacityCache = active? &activeOpacityCache : &inactiveOpacityCache; + + shadow.setRect(x() - thickness + xOffset, y() - thickness + yOffset, + width() + thickness * 2, height() + thickness * 2); + shadowPixmap.resize(shadow.size()); + + // Create a fake drop-down shadow effect via blended Xwindows + shadowWidget = new TQWidget(0, 0, WStyle_Customize | WX11BypassWM); + shadowWidget->setGeometry(shadow); + XSelectInput(qt_xdisplay(), shadowWidget->winId(), + ButtonPressMask | ButtonReleaseMask | StructureNotifyMask); + shadowWidget->installEventFilter(this); + + if (!shapes) { + // XShape extension not supported + exposedRegion = getExposedRegion(shapeBoundingRegion, shadow.x(), + shadow.y(), shadow.width(), shadow.height(), thickness, + xOffset, yOffset); + shadowRegion.region = exposedRegion; + shadowRegion.client = this; + shadowRegions.append(shadowRegion); + + if (opacityCache->isNull()) + imposeRegionShadow(shadowPixmap, shapeBoundingRegion, + exposedRegion, thickness, + options->shadowOpacity(isActive())); + else + imposeCachedShadow(shadowPixmap, exposedRegion); + } + else { + TQMemArray<TQRect> exposedRects; + TQMemArray<TQRect>::Iterator it, itEnd; + XRectangle *shadowShapes; + + exposedRegion = getExposedRegion(shapeBoundingRegion, shadow.x(), + shadow.y(), shadow.width(), shadow.height(), thickness, + xOffset, yOffset); + shadowRegion.region = exposedRegion; + shadowRegion.client = this; + shadowRegions.append(shadowRegion); + + // XShape the shadow + exposedRects = exposedRegion.rects(); + i = 0; + itEnd = exposedRects.end(); + shadowShapes = new XRectangle[exposedRects.count()]; + for (it = exposedRects.begin(); it != itEnd; ++it) { + shadowShapes[i].x = (*it).x(); + shadowShapes[i].y = (*it).y(); + shadowShapes[i].width = (*it).width(); + shadowShapes[i].height = (*it).height(); + i++; + } + XShapeCombineRectangles(qt_xdisplay(), shadowWidget->winId(), + ShapeBounding, -x() + thickness - xOffset, + -y() + thickness - yOffset, shadowShapes, i, ShapeSet, + Unsorted); + delete [] shadowShapes; + + if (opacityCache->isNull()) + imposeRegionShadow(shadowPixmap, shapeBoundingRegion, + exposedRegion, thickness, + options->shadowOpacity(isActive())); + else + imposeCachedShadow(shadowPixmap, exposedRegion); + } + + XFree(shapes); + + // Set the background pixmap + //shadowPixmap.convertFromImage(shadowImage); + shadowWidget->setErasePixmap(shadowPixmap); + + // Restack shadows under this window so that shadows drawn for a newly + // focused (but not raised) window don't overlap any windows above it. + if (isDock()) { + ClientList stacking_order = workspace()->stackingOrder(); + for (ClientList::ConstIterator it = stacking_order.begin(); it != stacking_order.end(); ++it) + if ((*it)->isDesktop()) + { + ++it; + shadows[0] = (*it)->frameId(); + shadows[1] = shadowWidget->winId(); + } + } + else { + shadows[0] = frameId(); + if (shadowWidget != NULL) + shadows[1] = shadowWidget->winId(); + } + + XRestackWindows(qt_xdisplay(), shadows, 2); + + // Don't use TQWidget::show() so we don't confuse QEffects, thus causing + // broken focus. + XMapWindow(qt_xdisplay(), shadowWidget->winId()); + + // Tell whatever Clients are listening that this Client's shadow has been drawn. + emit shadowDrawn(); +} + +/*! + Remove shadow under this window. + */ +void Client::removeShadow() +{ + TQValueList<ShadowRegion>::Iterator it; + + shadowDelayTimer->stop(); + + if (shadowWidget != NULL) { + for (it = shadowRegions.begin(); it != shadowRegions.end(); ++it) + if ((*it).client == this) { + shadowRegions.remove(it); + break; + } + delete shadowWidget; + shadowWidget = NULL; + } +} + +/*! + Calculate regions in which the shadow will be visible given the window's + origin, height and width and the shadow's thickness, and X- and Y-offsets. + */ +TQRegion Client::getExposedRegion(TQRegion occludedRegion, int x, int y, int w, + int h, int thickness, int xOffset, int yOffset) +{ + TQRegion exposedRegion; + + exposedRegion = TQRegion(x, y, w, h); + exposedRegion -= occludedRegion; + + if (thickness > 0) { + // Limit exposedRegion to include only where a shadow of the specified + // thickness will be drawn + TQMemArray<TQRect> occludedRects; + TQMemArray<TQRect>::Iterator it, itEnd; + TQRegion shadowRegion; + + occludedRects = occludedRegion.rects(); + itEnd = occludedRects.end(); + for (it = occludedRects.begin(); it != itEnd; ++it) { + // Expand each of the occluded region's shape rectangles to contain + // where a shadow of the specified thickness will be drawn. Create + // a new TQRegion that contains the expanded occluded region + it->setTop(it->top() - thickness + yOffset); + it->setLeft(it->left() - thickness + xOffset); + it->setRight(it->right() + thickness + xOffset); + it->setBottom(it->bottom() + thickness + yOffset); + shadowRegion += TQRegion(*it); + } + exposedRegion -= exposedRegion - shadowRegion; + } + + return exposedRegion; +} + +/*! + Draw shadow gradient around this window using cached opacity values. + */ +void Client::imposeCachedShadow(TQPixmap &pixmap, TQRegion exposed) +{ + QRgb pixel; + double opacity; + int red, green, blue, pixelRed, pixelGreen, pixelBlue; + int subW, subH, w, h, x, y, zeroX, zeroY; + TQImage image; + TQMemArray<TQRect>::Iterator it, itEnd; + TQMemArray<TQRect> rectangles; + TQPixmap subPixmap; + Window rootWindow; + int thickness, windowX, windowY, xOffset, yOffset; + + rectangles = exposed.rects(); + rootWindow = qt_xrootwin(); + thickness = options->shadowThickness(isActive()); + windowX = this->x(); + windowY = this->y(); + xOffset = options->shadowXOffset(isActive()); + yOffset = options->shadowYOffset(isActive()); + options->shadowColour(isActive()).rgb(&red, &green, &blue); + w = pixmap.width(); + h = pixmap.height(); + + itEnd = rectangles.end(); + for (it = rectangles.begin(); it != itEnd; ++it) { + subW = (*it).width(); + subH = (*it).height(); + subPixmap = TQPixmap::grabWindow(rootWindow, (*it).x(), (*it).y(), + subW, subH); + zeroX = (*it).x() - windowX + thickness - xOffset; + zeroY = (*it).y() - windowY + thickness - yOffset; + image = subPixmap.convertToImage(); + + for (x = 0; x < subW; x++) { + for (y = 0; y < subH; y++) { + opacity = (*(opacityCache))[(zeroY + y) * w + zeroX + x]; + pixel = image.pixel(x, y); + pixelRed = qRed(pixel); + pixelGreen = qGreen(pixel); + pixelBlue = qBlue(pixel); + image.setPixel(x, y, + qRgb((int)(pixelRed + (red - pixelRed) * opacity), + (int)(pixelGreen + (green - pixelGreen) * opacity), + (int)(pixelBlue + (blue - pixelBlue) * opacity))); + } + } + + subPixmap.convertFromImage(image); + bitBlt(&pixmap, zeroX, zeroY, &subPixmap); + } +} + +/*! + Draw shadow around this window using calculated opacity values. + */ +void Client::imposeRegionShadow(TQPixmap &pixmap, TQRegion occluded, + TQRegion exposed, int thickness, double maxOpacity) +{ + register int distance, intersectCount, i, j, x, y; + QRgb pixel; + double decay, factor, opacity; + int red, green, blue, pixelRed, pixelGreen, pixelBlue; + int halfMaxIntersects, lineIntersects, maxIntersects, maxY; + int irBottom, irLeft, irRight, irTop, yIncrement; + int subW, subH, w, h, zeroX, zeroY; + TQImage image; + TQMemArray<TQRect>::Iterator it, itEnd; + TQMemArray<TQRect> rectangles; + TQPixmap subPixmap; + Window rootWindow; + int windowX, windowY, xOffset, yOffset; + + rectangles = exposed.rects(); + rootWindow = qt_xrootwin(); + windowX = this->x(); + windowY = this->y(); + xOffset = options->shadowXOffset(isActive()); + yOffset = options->shadowYOffset(isActive()); + options->shadowColour(isActive()).rgb(&red, &green, &blue); + maxIntersects = thickness * thickness * 4 + (thickness * 4) + 1; + halfMaxIntersects = maxIntersects / 2; + lineIntersects = thickness * 2 + 1; + factor = maxIntersects / maxOpacity; + decay = (lineIntersects / 0.0125 - factor) / pow((double)maxIntersects, 3.0); + w = pixmap.width(); + h = pixmap.height(); + xOffset = options->shadowXOffset(isActive()); + yOffset = options->shadowYOffset(isActive()); + + opacityCache->resize(0); + opacityCache->resize(w * h); + occluded.translate(-windowX + thickness, -windowY + thickness); + + itEnd = rectangles.end(); + for (it = rectangles.begin(); it != itEnd; ++it) { + subW = (*it).width(); + subH = (*it).height(); + subPixmap = TQPixmap::grabWindow(rootWindow, (*it).x(), (*it).y(), + subW, subH); + maxY = subH; + zeroX = (*it).x() - windowX + thickness - xOffset; + zeroY = (*it).y() - windowY + thickness - yOffset; + image = subPixmap.convertToImage(); + + intersectCount = 0; + opacity = -1; + y = 0; + yIncrement = 1; + for (x = 0; x < subW; x++) { + irLeft = zeroX + x - thickness; + irRight = zeroX + x + thickness; + + while (y != maxY) { + // horizontal row about to leave the intersect region, not + // necessarily the top row + irTop = zeroY + y - thickness * yIncrement; + // horizontal row that just came into the intersect region, + // not necessarily the bottom row + irBottom = zeroY + y + thickness * yIncrement; + + if (opacity == -1) { + // If occluded pixels caused an intersect count to be + // skipped, recount it + intersectCount = 0; + + for (j = irTop; j != irBottom; j += yIncrement) { + // irTop is not necessarily larger than irBottom and + // yIncrement isn't necessarily positive + for (i = irLeft; i <= irRight; i++) { + if (occluded.contains(TQPoint(i, j))) + intersectCount++; + } + } + } + else { + if (intersectCount < 0) + intersectCount = 0; + + for (i = irLeft; i <= irRight; i++) { + if (occluded.contains(TQPoint(i, irBottom))) + intersectCount++; + } + } + + distance = maxIntersects - intersectCount; + opacity = intersectCount / (factor + pow((double)distance, 3.0) * decay); + + (*(opacityCache))[(zeroY + y) * w + zeroX + x] = opacity; + pixel = image.pixel(x, y); + pixelRed = qRed(pixel); + pixelGreen = qGreen(pixel); + pixelBlue = qBlue(pixel); + image.setPixel(x, y, + qRgb((int)(pixelRed + (red - pixelRed) * opacity), + (int)(pixelGreen + (green - pixelGreen) * opacity), + (int)(pixelBlue + (blue - pixelBlue) * opacity))); + + for (i = irLeft; i <= irRight; i++) { + if (occluded.contains(TQPoint(i, irTop))) + intersectCount--; + } + + y += yIncrement; + } + y -= yIncrement; + + irTop += yIncrement; + for (j = irTop; j != irBottom; j += yIncrement) { + if (occluded.contains(TQPoint(irLeft, j))) + intersectCount--; + } + irRight++; + for (j = irTop; j != irBottom; j += yIncrement) { + if (occluded.contains(TQPoint(irRight, j))) + intersectCount++; + } + + yIncrement *= -1; + if (yIncrement < 0) + // Scan Y-axis bottom-up for next X-coordinate iteration + maxY = -1; + else + // Scan Y-axis top-down for next X-coordinate iteration + maxY = subH; + } + + subPixmap.convertFromImage(image); + bitBlt(&pixmap, zeroX, zeroY, &subPixmap); + } +} + /*! Sets the client window's mapping state. Possible values are WithdrawnState, IconicState, NormalState. @@ -989,6 +1620,8 @@ void Client::rawShow() XMapWindow( qt_xdisplay(), wrapper ); XMapWindow( qt_xdisplay(), client ); } + if (options->shadowEnabled(isActive())) + drawDelayedShadow(); } /*! @@ -1004,6 +1637,8 @@ void Client::rawHide() // which won't be missed, so this shouldn't be a problem. The chance the real UnmapNotify // will be missed is also very minimal, so I don't think it's needed to grab the server // here. + removeShadow(); + drawIntersectingShadows(); XSelectInput( qt_xdisplay(), wrapper, ClientWinMask ); // avoid getting UnmapNotify XUnmapWindow( qt_xdisplay(), frame ); XUnmapWindow( qt_xdisplay(), wrapper ); |