diff -Naur kdebase-3.5.8.orig/kwin/KWinInterface.h kdebase-3.5.8/kwin/KWinInterface.h --- kdebase-3.5.8.orig/kwin/KWinInterface.h 2005-09-10 10:26:03.000000000 +0200 +++ kdebase-3.5.8/kwin/KWinInterface.h 2007-11-21 07:02:51.000000000 +0100 @@ -22,6 +22,9 @@ virtual void nextDesktop() = 0; virtual void previousDesktop() = 0; virtual void circulateDesktopApplications() = 0; + virtual void updateOverlappingShadows(unsigned long window) = 0; + virtual void setShadowed(unsigned long window, bool shadowed) = 0; + // kompmgr stuff virtual void startKompmgr() = 0; virtual void stopKompmgr() = 0; diff -Naur kdebase-3.5.8.orig/kwin/activation.cpp kdebase-3.5.8/kwin/activation.cpp --- kdebase-3.5.8.orig/kwin/activation.cpp 2007-11-21 07:02:36.000000000 +0100 +++ kdebase-3.5.8/kwin/activation.cpp 2007-11-21 07:02:51.000000000 +0100 @@ -227,6 +227,13 @@ active_client->setActive( false, !c || !c->isModal() || c != active_client->transientFor() ); } active_client = c; + if (set_active_client_recursion == 1) + { + // Only unset next_active_client if activateClient() wasn't called by + // Client::setActive() to set the active window to null before + // activating another window. + next_active_client = NULL; + } Q_ASSERT( c == NULL || c->isActive()); if( active_client != NULL ) last_active_client = active_client; @@ -324,6 +331,7 @@ Client* modal = c->findModal(); if( modal != NULL && modal != c ) { + next_active_client = modal; if( !modal->isOnDesktop( c->desktop())) { modal->setDesktop( c->desktop()); @@ -351,11 +359,14 @@ c->setActive( true ); focusToNull(); } + if( c->wantsInput()) + next_active_client = c; flags &= ~ActivityFocus; handled = false; // no point, can't get clicks } if( !c->isShown( true )) // shouldn't happen, call activateClient() if needed { + next_active_client = c; kdWarning( 1212 ) << "takeActivity: not shown" << endl; return; } @@ -856,7 +867,45 @@ updateShadowSize(); if ( active ) + { Notify::raise( Notify::Activate ); + if (options->shadowEnabled(true)) + { + if (options->shadowEnabled(false)) + { + // Wait for inactive shadow to expose occluded windows and give + // them a chance to redraw before painting the active shadow + removeShadow(); + drawDelayedShadow(); + if (!isDesktop() && + this != workspace()->topClientOnDesktop(desktop())) + // If the newly activated window's isn't the desktop, wait + // for its shadow to draw, then redraw any shadows + // overlapping it. + drawOverlappingShadows(true); + } + else + drawShadow(); + } + } + else + { + removeShadow(); + + if (options->shadowEnabled(false)) + if (this == workspace()->topClientOnDesktop(desktop())) + { + /* If the newly deactivated window is the top client on the + * desktop, then the newly activated window is below it; ensure + * that the deactivated window's shadow draws after the + * activated window's shadow. + */ + if ((shadowAfterClient = workspace()->activeClient())) + drawShadowAfter(shadowAfterClient); + } + else + drawDelayedShadow(); + } if( !active ) cancelAutoRaise(); diff -Naur kdebase-3.5.8.orig/kwin/client.cpp kdebase-3.5.8/kwin/client.cpp --- kdebase-3.5.8.orig/kwin/client.cpp 2007-11-21 07:02:36.000000000 +0100 +++ kdebase-3.5.8/kwin/client.cpp 2007-11-21 07:02:51.000000000 +0100 @@ -11,9 +11,12 @@ #include "client.h" +#include + #include #include #include +#include #include #include #include @@ -39,9 +42,25 @@ 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 + { + QRegion region; + Client *client; + }; +static QValueList shadowRegions; + /* Creating a client: @@ -100,6 +119,13 @@ autoRaiseTimer = 0; shadeHoverTimer = 0; + shadowDelayTimer = new QTimer(this); + opacityCache = &activeOpacityCache; + shadowAfterClient = NULL; + shadowWidget = NULL; + shadowMe = true; + connect(shadowDelayTimer, SIGNAL(timeout()), SLOT(drawShadow())); + // set the initial mapping state mapping_state = WithdrawnState; desk = 0; // no desktop yet @@ -145,7 +171,7 @@ maxmode_restore = MaximizeRestore; cmap = None; - + frame_geometry = QRect( 0, 0, 100, 100 ); // so that decorations don't start with size being (0,0) client_size = QSize( 100, 100 ); custom_opacity = false; @@ -188,6 +214,8 @@ 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 @@ StackingUpdatesBlocker blocker( workspace()); if (moveResizeMode) leaveMoveResize(); + removeShadow(); + drawIntersectingShadows(); finishWindowRules(); ++postpone_geometry_updates; setModal( false ); @@ -304,6 +334,7 @@ if( do_show ) decoration->widget()->show(); updateFrameExtents(); + updateOpacityCache(); } void Client::destroyDecoration() @@ -434,6 +465,12 @@ QResizeEvent e( s, oldsize ); QApplication::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 @@ noborder = true; updateDecoration( true ); } + updateOpacityCache(); if ( shape() ) { XShapeCombineShape(qt_xdisplay(), frameId(), ShapeBounding, @@ -862,6 +900,16 @@ 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::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; + QRegion region; + //QPtrList reshadowClients; + QValueList reshadowClients; + QValueListIterator it; + QValueListIterator 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; + QRegion region; + QValueList reshadowClients; + ClientList stacking_order; + ClientList::ConstIterator it; + QValueListIterator it2; + QValueListIterator 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, SIGNAL(shadowDrawn()), 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, SIGNAL(shadowDrawn()), this, 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 = QRegion(x(), y(), width(), height()); + else { + shapeBoundingRegion = QRegion(); + for (i = 0; i < count; i++) { + // Translate XShaped window into a QRegion + QRegion 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 &= QRegion(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(); + + QMemArray pixelData; + QPixmap shadowPixmap; + QRect shadow; + QRegion 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 QWidget(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 { + QMemArray exposedRects; + QMemArray::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 QWidget::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() +{ + QValueList::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. + */ +QRegion Client::getExposedRegion(QRegion occludedRegion, int x, int y, int w, + int h, int thickness, int xOffset, int yOffset) +{ + QRegion exposedRegion; + + exposedRegion = QRegion(x, y, w, h); + exposedRegion -= occludedRegion; + + if (thickness > 0) { + // Limit exposedRegion to include only where a shadow of the specified + // thickness will be drawn + QMemArray occludedRects; + QMemArray::Iterator it, itEnd; + QRegion 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 QRegion 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 += QRegion(*it); + } + exposedRegion -= exposedRegion - shadowRegion; + } + + return exposedRegion; +} + +/*! + Draw shadow gradient around this window using cached opacity values. + */ +void Client::imposeCachedShadow(QPixmap &pixmap, QRegion exposed) +{ + QRgb pixel; + double opacity; + int red, green, blue, pixelRed, pixelGreen, pixelBlue; + int subW, subH, w, h, x, y, zeroX, zeroY; + QImage image; + QMemArray::Iterator it, itEnd; + QMemArray rectangles; + QPixmap 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 = QPixmap::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(QPixmap &pixmap, QRegion occluded, + QRegion 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; + QImage image; + QMemArray::Iterator it, itEnd; + QMemArray rectangles; + QPixmap 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 = QPixmap::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(QPoint(i, j))) + intersectCount++; + } + } + } + else { + if (intersectCount < 0) + intersectCount = 0; + + for (i = irLeft; i <= irRight; i++) { + if (occluded.contains(QPoint(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(QPoint(i, irTop))) + intersectCount--; + } + + y += yIncrement; + } + y -= yIncrement; + + irTop += yIncrement; + for (j = irTop; j != irBottom; j += yIncrement) { + if (occluded.contains(QPoint(irLeft, j))) + intersectCount--; + } + irRight++; + for (j = irTop; j != irBottom; j += yIncrement) { + if (occluded.contains(QPoint(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 @@ XMapWindow( qt_xdisplay(), wrapper ); XMapWindow( qt_xdisplay(), client ); } + if (options->shadowEnabled(isActive())) + drawDelayedShadow(); } /*! @@ -1004,6 +1637,8 @@ // 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 ); diff -Naur kdebase-3.5.8.orig/kwin/client.h kdebase-3.5.8/kwin/client.h --- kdebase-3.5.8.orig/kwin/client.h 2007-11-21 07:02:36.000000000 +0100 +++ kdebase-3.5.8/kwin/client.h 2007-11-21 07:02:51.000000000 +0100 @@ -200,6 +200,19 @@ void updateDecoration( bool check_workspace_pos, bool force = false ); void checkBorderSizes(); + // drop shadow + bool isShadowed() const; + void setShadowed(bool shadowed); + Window shadowId() const; + // Aieee, a friend function! Unpleasant, yes, but it's needed by + // raiseClient() to redraw a window's shadow when it is active prior to + // being raised. + friend void Workspace::raiseClient(Client *); + // Wouldn't you know it, friend functions breed. This one's needed to + // enable a DCOP function that causes all shadows obscuring a changed + // window to be redrawn. + friend void Workspace::updateOverlappingShadows(WId); + // shape extensions bool shape() const; void updateShape(); @@ -312,6 +325,8 @@ void autoRaise(); void shadeHover(); void shortcutActivated(); + void updateOpacityCache(); + private: friend class Bridge; // FRAME @@ -348,12 +363,29 @@ bool buttonReleaseEvent( Window w, int button, int state, int x, int y, int x_root, int y_root ); bool motionNotifyEvent( Window w, int state, int x, int y, int x_root, int y_root ); + // drop shadows + void drawIntersectingShadows(); + void drawOverlappingShadows(bool waitForMe); + QRegion getExposedRegion(QRegion occludedRegion, int x, int y, + int w, int h, int thickness, int xOffset, int yOffset); + void imposeCachedShadow(QPixmap &pixmap, QRegion exposed); + void imposeRegionShadow(QPixmap &pixmap, QRegion occluded, + QRegion exposed, int thickness, double maxOpacity = 0.75); + void processDecorationButtonPress( int button, int state, int x, int y, int x_root, int y_root ); private slots: void pingTimeout(); void processKillerExited(); void demandAttentionKNotify(); + void drawShadow(); + void drawShadowAfter(Client *after); + void drawDelayedShadow(); + void removeShadow(); + + signals: + void shadowDrawn(); + private: // ICCCM 4.1.3.1, 4.1.4 , NETWM 2.5.1 @@ -531,6 +563,16 @@ bool pending_geometry_update; bool shade_geometry_change; int border_left, border_right, border_top, border_bottom; + + Client* shadowAfterClient; + QWidget* shadowWidget; + QMemArray activeOpacityCache; + QMemArray inactiveOpacityCache; + QMemArray* opacityCache; + QRegion shapeBoundingRegion; + QTimer* shadowDelayTimer; + bool shadowMe; + QRegion _mask; static bool check_active_modal; // see Client::checkActiveModal() KShortcut _shortcut; @@ -880,6 +922,16 @@ plainResize( s.width(), s.height(), force ); } +inline bool Client::isShadowed() const + { + return shadowMe; + } + +inline Window Client::shadowId() const + { + return shadowWidget != NULL ? shadowWidget->winId() : None; + } + inline void Client::resizeWithChecks( const QSize& s, ForceGeometry_t force ) { resizeWithChecks( s.width(), s.height(), force ); diff -Naur kdebase-3.5.8.orig/kwin/events.cpp kdebase-3.5.8/kwin/events.cpp --- kdebase-3.5.8.orig/kwin/events.cpp 2007-10-08 11:51:32.000000000 +0200 +++ kdebase-3.5.8/kwin/events.cpp 2007-11-21 07:02:51.000000000 +0100 @@ -1077,6 +1077,217 @@ // for the decoration window cannot be (easily) intercepted as X11 events bool Client::eventFilter( QObject* o, QEvent* e ) { + if (o == shadowWidget) + { + if (e->type() == QEvent::MouseButtonRelease) + { + int buttonMask, buttonPressed, x, y, x_root, y_root; + unsigned int mask; + QMouseEvent *qe = (QMouseEvent *)e; + Window inner_window, parent_window, pointer_window, root_window; + XButtonEvent xe; + + removeShadow(); + switch (qe->button()) + { + case Qt::MidButton: + buttonMask = Button2Mask; + buttonPressed = Button2; + break; + case Qt::RightButton: + buttonMask = Button3Mask; + buttonPressed = Button3; + break; + default: + buttonMask = Button1Mask; + buttonPressed = Button1; + break; + } + + // find the window under the cursor that should receive the + // simulated events + root_window = qt_xrootwin(); + XQueryPointer(qt_xdisplay(), root_window, &root_window, + &pointer_window, &x_root, &y_root, &x, &y, &mask); + + if (pointer_window != None) + { + // Save the child window immediately under the window + // decoration, if any. This is so that we can send an event to + // the immediate descendant of a window's window decoration, + // which causes KWin to refocus windows properly + parent_window = pointer_window; + XQueryPointer(qt_xdisplay(), parent_window, &root_window, + &pointer_window, &x_root, &y_root, &x, &y, &mask); + inner_window = pointer_window; + + while (pointer_window != None) + { + // Recursively query for the child window under the pointer, + // using the returned child window as the parent window for + // the subsequent query. When no child window is left, we've + // found the child that will receive the simulated event + parent_window = pointer_window; + XQueryPointer(qt_xdisplay(), parent_window, &root_window, + &pointer_window, &x_root, &y_root, &x, &y, &mask); + } + pointer_window = parent_window; + } + else + inner_window = None; + + // simulate a mouse button press + xe.type = ButtonPress; + xe.display = qt_xdisplay(); + xe.root = qt_xrootwin(); + xe.subwindow = None; + xe.time = CurrentTime; + xe.x = x; + xe.y = y; + xe.x_root = x_root; + xe.y_root = y_root; + xe.state = 0; + xe.button = buttonPressed; + xe.same_screen = True; + if (inner_window != None && inner_window != pointer_window) + { + xe.window = inner_window; + XSendEvent(qt_xdisplay(), inner_window, True, ButtonPressMask, + (XEvent *)&xe); + } + xe.window = pointer_window; + XSendEvent(qt_xdisplay(), pointer_window, True, ButtonPressMask, + (XEvent *)&xe); + + // simulate a mouse button release + xe.type = ButtonRelease; + xe.display = qt_xdisplay(); + xe.root = qt_xrootwin(); + xe.subwindow = None; + xe.time = CurrentTime; + xe.x = x; + xe.y = y; + xe.x_root = x_root; + xe.y_root = y_root; + xe.state = buttonMask; + xe.button = buttonPressed; + xe.same_screen = True; + if (inner_window != None && inner_window != pointer_window) + { + xe.window = inner_window; + XSendEvent(qt_xdisplay(), inner_window, True, ButtonReleaseMask, + (XEvent *)&xe); + } + xe.window = pointer_window; + XSendEvent(qt_xdisplay(), pointer_window, True, ButtonReleaseMask, + (XEvent *)&xe); + + drawDelayedShadow(); + + return true; + } + else if (e->type() == QEvent::Wheel) + { + int x, y, x_root, y_root; + unsigned int buttonMask, buttonPressed, mask; + QWheelEvent *wheelEvent = (QWheelEvent *)e; + Window inner_window, parent_window, pointer_window, + root_window; + XButtonEvent xe; + + removeShadow(); + + // state and button parameters passed to XSendEvent depend on the + // direction in which the mouse wheel was rolled + buttonMask = wheelEvent->delta() > 0 ? Button4Mask : Button5Mask; + buttonPressed = wheelEvent->delta() > 0 ? Button4 : Button5; + + // find the window under the cursor that should receive the + // simulated events + root_window = qt_xrootwin(); + XQueryPointer(qt_xdisplay(), root_window, &root_window, + &pointer_window, &x_root, &y_root, &x, &y, &mask); + + if (pointer_window != None) + { + // Save the child window immediately under the window + // decoration, if any. This is so that we can send an event to + // the immediate descendant of a window's window decoration, + // which causes KWin to refocus windows properly + parent_window = pointer_window; + XQueryPointer(qt_xdisplay(), parent_window, &root_window, + &pointer_window, &x_root, &y_root, &x, &y, &mask); + inner_window = pointer_window; + + while (pointer_window != None) + { + // Recursively query for the child window under the pointer, + // using the returned child window as the parent window for + // the subsequent query. When no child window is left, we've + // found the child that will receive the simulated event + parent_window = pointer_window; + XQueryPointer(qt_xdisplay(), parent_window, &root_window, + &pointer_window, &x_root, &y_root, &x, &y, &mask); + } + pointer_window = parent_window; + } + else + inner_window = None; + + // simulate a mouse button press + xe.type = ButtonPress; + xe.display = qt_xdisplay(); + xe.root = qt_xrootwin(); + xe.subwindow = None; + xe.time = CurrentTime; + xe.x = x; + xe.y = y; + xe.x_root = x_root; + xe.y_root = y_root; + xe.state = 0; + xe.same_screen = True; + if (inner_window != None && inner_window != pointer_window) + { + xe.button = buttonPressed; + xe.window = inner_window; + XSendEvent(qt_xdisplay(), inner_window, True, ButtonPressMask, + (XEvent *)&xe); + } + xe.button = buttonPressed; + xe.window = pointer_window; + XSendEvent(qt_xdisplay(), pointer_window, True, ButtonPressMask, + (XEvent *)&xe); + + // simulate a mouse button release + xe.type = ButtonRelease; + xe.display = qt_xdisplay(); + xe.root = qt_xrootwin(); + xe.subwindow = None; + xe.time = CurrentTime; + xe.x = x; + xe.y = y; + xe.x_root = x_root; + xe.y_root = y_root; + xe.same_screen = True; + if (inner_window != None && inner_window != pointer_window) + { + xe.window = inner_window; + xe.state = buttonMask; + xe.button = buttonPressed; + XSendEvent(qt_xdisplay(), inner_window, True, ButtonReleaseMask, + (XEvent *)&xe); + } + xe.state = buttonMask; + xe.button = buttonPressed; + xe.window = pointer_window; + XSendEvent(qt_xdisplay(), pointer_window, True, ButtonReleaseMask, + (XEvent *)&xe); + + drawDelayedShadow(); + + return true; + } + } if( decoration == NULL || o != decoration->widget()) return false; diff -Naur kdebase-3.5.8.orig/kwin/geometry.cpp kdebase-3.5.8/kwin/geometry.cpp --- kdebase-3.5.8.orig/kwin/geometry.cpp 2007-11-21 07:02:36.000000000 +0100 +++ kdebase-3.5.8/kwin/geometry.cpp 2007-11-21 07:02:51.000000000 +0100 @@ -1028,6 +1028,15 @@ rect.moveLeft( area.right() - 5 ); } } + if (!moveResizeMode && options->shadowEnabled(isActive())) + { + // If the user is manually resizing, let Client::leaveMoveResize() + // decide when to redraw the shadow + removeShadow(); + drawIntersectingShadows(); + if (options->shadowEnabled(isActive())) + drawDelayedShadow(); + } } /*! @@ -2286,6 +2295,7 @@ } if ( maximizeMode() != MaximizeRestore ) resetMaximize(); + removeShadow(); moveResizeMode = true; workspace()->setClientIsMoving(this); initialMoveResizeGeom = moveResizeGeom = geometry(); @@ -2354,6 +2364,11 @@ moveResizeMode = false; delete eater; eater = 0; + if (options->shadowEnabled(isActive())) + { + drawIntersectingShadows(); + updateOpacityCache(); + } } // This function checks if it actually makes sense to perform a restricted move/resize. diff -Naur kdebase-3.5.8.orig/kwin/kcmkwin/kwindecoration/kwindecoration.cpp kdebase-3.5.8/kwin/kcmkwin/kwindecoration/kwindecoration.cpp --- kdebase-3.5.8.orig/kwin/kcmkwin/kwindecoration/kwindecoration.cpp 2006-01-19 18:01:03.000000000 +0100 +++ kdebase-3.5.8/kwin/kcmkwin/kwindecoration/kwindecoration.cpp 2007-11-21 07:02:51.000000000 +0100 @@ -28,6 +28,8 @@ */ #include +#include + #include #include #include @@ -39,8 +41,10 @@ #include #include #include +#include #include +#include #include #include #include @@ -153,6 +157,164 @@ preview->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); tabWidget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Maximum); + // Page 3 (Window Shadows) + QHBox *inactiveShadowColourHBox, *shadowColourHBox; + QHBox *inactiveShadowOpacityHBox, *shadowOpacityHBox; + QHBox *inactiveShadowXOffsetHBox, *shadowXOffsetHBox; + QHBox *inactiveShadowYOffsetHBox, *shadowYOffsetHBox; + QHBox *inactiveShadowThicknessHBox, *shadowThicknessHBox; + QLabel *inactiveShadowColourLabel, *shadowColourLabel; + QLabel *inactiveShadowOpacityLabel, *shadowOpacityLabel; + QLabel *inactiveShadowXOffsetLabel, *shadowXOffsetLabel; + QLabel *inactiveShadowYOffsetLabel, *shadowYOffsetLabel; + QLabel *inactiveShadowThicknessLabel, *shadowThicknessLabel; + + shadowPage = new QVBox(tabWidget); + shadowPage->setSpacing(KDialog::spacingHint()); + shadowPage->setMargin(KDialog::marginHint()); + + cbWindowShadow = new QCheckBox( + i18n("&Draw a drop shadow under windows"), shadowPage); + QWhatsThis::add(cbWindowShadow, + i18n("Enabling this checkbox will allow you to choose a kind of " + "drop shadow to draw under each window.")); + + activeShadowSettings = new QGroupBox(1, Qt::Horizontal, + i18n("Active Window Shadow"), shadowPage); + inactiveShadowSettings = new QGroupBox(1, Qt::Horizontal, + i18n("Inactive Window Shadows"), shadowPage); + whichShadowSettings = new QGroupBox(3, Qt::Horizontal, + i18n("Draw Shadow Under Normal Windows And..."), shadowPage); + + cbShadowDocks = new QCheckBox(i18n("Docks and &panels"), + whichShadowSettings); + connect(cbShadowDocks, SIGNAL(toggled(bool)), + SLOT(slotSelectionChanged())); + cbShadowOverrides = new QCheckBox(i18n("O&verride windows"), + whichShadowSettings); + connect(cbShadowOverrides, SIGNAL(toggled(bool)), + SLOT(slotSelectionChanged())); + cbShadowTopMenus = new QCheckBox(i18n("&Top menu"), + whichShadowSettings); + connect(cbShadowTopMenus, SIGNAL(toggled(bool)), + SLOT(slotSelectionChanged())); + cbInactiveShadow = new QCheckBox( + i18n("Draw shadow under &inactive windows"), inactiveShadowSettings); + connect(cbInactiveShadow, SIGNAL(toggled(bool)), + SLOT(slotSelectionChanged())); + + shadowColourHBox = new QHBox(activeShadowSettings); + shadowColourHBox->setSpacing(KDialog::spacingHint()); + shadowColourLabel = new QLabel(i18n("Colour:"), shadowColourHBox); + shadowColourButton = new KColorButton(shadowColourHBox); + connect(shadowColourButton, SIGNAL(changed(const QColor &)), SLOT(slotSelectionChanged())); + + inactiveShadowColourHBox = new QHBox(inactiveShadowSettings); + inactiveShadowColourHBox->setSpacing(KDialog::spacingHint()); + inactiveShadowColourLabel = new QLabel(i18n("Colour:"), inactiveShadowColourHBox); + inactiveShadowColourButton = new KColorButton(inactiveShadowColourHBox); + connect(inactiveShadowColourButton, SIGNAL(changed(const QColor &)), SLOT(slotSelectionChanged())); + + shadowOpacityHBox = new QHBox(activeShadowSettings); + shadowOpacityHBox->setSpacing(KDialog::spacingHint()); + shadowOpacityLabel = new QLabel(i18n("Maximum opacity:"), shadowOpacityHBox); + shadowOpacitySlider = new QSlider(1, 100, 10, 50, Qt::Horizontal, + shadowOpacityHBox); + shadowOpacitySlider->setTickmarks(QSlider::Below); + shadowOpacitySlider->setTickInterval(10); + shadowOpacitySpinBox = new QSpinBox(1, 100, 1, shadowOpacityHBox); + shadowOpacitySpinBox->setSuffix(" %"); + connect(shadowOpacitySlider, SIGNAL(valueChanged(int)), shadowOpacitySpinBox, + SLOT(setValue(int))); + connect(shadowOpacitySpinBox, SIGNAL(valueChanged(int)), shadowOpacitySlider, + SLOT(setValue(int))); + connect(shadowOpacitySlider, SIGNAL(valueChanged(int)), + SLOT(slotSelectionChanged())); + + inactiveShadowOpacityHBox = new QHBox(inactiveShadowSettings); + inactiveShadowOpacityHBox->setSpacing(KDialog::spacingHint()); + inactiveShadowOpacityLabel = new QLabel(i18n("Maximum opacity:"), + inactiveShadowOpacityHBox); + inactiveShadowOpacitySlider = new QSlider(1, 100, 10, 50, Qt::Horizontal, + inactiveShadowOpacityHBox); + inactiveShadowOpacitySlider->setTickmarks(QSlider::Below); + inactiveShadowOpacitySlider->setTickInterval(10); + inactiveShadowOpacitySpinBox = new QSpinBox(1, 100, 1, + inactiveShadowOpacityHBox); + inactiveShadowOpacitySpinBox->setSuffix(" %"); + connect(inactiveShadowOpacitySlider, SIGNAL(valueChanged(int)), + inactiveShadowOpacitySpinBox, + SLOT(setValue(int))); + connect(inactiveShadowOpacitySpinBox, SIGNAL(valueChanged(int)), + inactiveShadowOpacitySlider, + SLOT(setValue(int))); + connect(inactiveShadowOpacitySlider, SIGNAL(valueChanged(int)), + SLOT(slotSelectionChanged())); + + shadowXOffsetHBox = new QHBox(activeShadowSettings); + shadowXOffsetHBox->setSpacing(KDialog::spacingHint()); + shadowXOffsetLabel = new QLabel( + i18n("Offset rightward (may be negative):"), + shadowXOffsetHBox); + shadowXOffsetSpinBox = new QSpinBox(-1024, 1024, 1, shadowXOffsetHBox); + shadowXOffsetSpinBox->setSuffix(i18n(" pixels")); + connect(shadowXOffsetSpinBox, SIGNAL(valueChanged(int)), + SLOT(slotSelectionChanged())); + + inactiveShadowXOffsetHBox = new QHBox(inactiveShadowSettings); + inactiveShadowXOffsetHBox->setSpacing(KDialog::spacingHint()); + inactiveShadowXOffsetLabel = new QLabel( + i18n("Offset rightward (may be negative):"), + inactiveShadowXOffsetHBox); + inactiveShadowXOffsetSpinBox = new QSpinBox(-1024, 1024, 1, + inactiveShadowXOffsetHBox); + inactiveShadowXOffsetSpinBox->setSuffix(i18n(" pixels")); + connect(inactiveShadowXOffsetSpinBox, SIGNAL(valueChanged(int)), + SLOT(slotSelectionChanged())); + + shadowYOffsetHBox = new QHBox(activeShadowSettings); + shadowYOffsetHBox->setSpacing(KDialog::spacingHint()); + shadowYOffsetLabel = new QLabel( + i18n("Offset downward (may be negative):"), + shadowYOffsetHBox); + shadowYOffsetSpinBox = new QSpinBox(-1024, 1024, 1, shadowYOffsetHBox); + shadowYOffsetSpinBox->setSuffix(i18n(" pixels")); + connect(shadowYOffsetSpinBox, SIGNAL(valueChanged(int)), + SLOT(slotSelectionChanged())); + + inactiveShadowYOffsetHBox = new QHBox(inactiveShadowSettings); + inactiveShadowYOffsetHBox->setSpacing(KDialog::spacingHint()); + inactiveShadowYOffsetLabel = new QLabel( + i18n("Offset downward (may be negative):"), + inactiveShadowYOffsetHBox); + inactiveShadowYOffsetSpinBox = new QSpinBox(-1024, 1024, 1, + inactiveShadowYOffsetHBox); + inactiveShadowYOffsetSpinBox->setSuffix(i18n(" pixels")); + connect(inactiveShadowYOffsetSpinBox, SIGNAL(valueChanged(int)), + SLOT(slotSelectionChanged())); + + shadowThicknessHBox = new QHBox(activeShadowSettings); + shadowThicknessHBox->setSpacing(KDialog::spacingHint()); + shadowThicknessLabel = new QLabel( + i18n("Thickness to either side of window:"), + shadowThicknessHBox); + shadowThicknessSpinBox = new QSpinBox(1, 100, 1, + shadowThicknessHBox); + shadowThicknessSpinBox->setSuffix(i18n(" pixels")); + connect(shadowThicknessSpinBox, SIGNAL(valueChanged(int)), + SLOT(slotSelectionChanged())); + + inactiveShadowThicknessHBox = new QHBox(inactiveShadowSettings); + inactiveShadowThicknessHBox->setSpacing(KDialog::spacingHint()); + inactiveShadowThicknessLabel = new QLabel( + i18n("Thickness to either side of window:"), + inactiveShadowThicknessHBox); + inactiveShadowThicknessSpinBox = new QSpinBox(1, 100, 1, + inactiveShadowThicknessHBox); + inactiveShadowThicknessSpinBox->setSuffix(i18n(" pixels")); + connect(inactiveShadowThicknessSpinBox, SIGNAL(valueChanged(int)), + SLOT(slotSelectionChanged())); + // Load all installed decorations into memory // Set up the decoration lists and other UI settings findDecorations(); @@ -162,6 +324,7 @@ tabWidget->insertTab( pluginPage, i18n("&Window Decoration") ); tabWidget->insertTab( buttonPage, i18n("&Buttons") ); + tabWidget->insertTab( shadowPage, i18n("&Shadows") ); connect( buttonPositionWidget, SIGNAL(changed()), this, SLOT(slotButtonsChanged()) ); // update preview etc. connect( buttonPositionWidget, SIGNAL(changed()), this, SLOT(slotSelectionChanged()) ); // emit changed()... @@ -171,7 +334,12 @@ connect( cbUseCustomButtonPositions, SIGNAL(clicked()), SLOT(slotSelectionChanged()) ); connect(cbUseCustomButtonPositions, SIGNAL(toggled(bool)), buttonPositionWidget, SLOT(setEnabled(bool))); connect(cbUseCustomButtonPositions, SIGNAL(toggled(bool)), this, SLOT(slotButtonsChanged()) ); + connect(cbWindowShadow, SIGNAL(toggled(bool)), activeShadowSettings, SLOT(setEnabled(bool))); + connect(cbWindowShadow, SIGNAL(toggled(bool)), inactiveShadowSettings, SLOT(setEnabled(bool))); + connect(cbWindowShadow, SIGNAL(toggled(bool)), whichShadowSettings, SLOT(setEnabled(bool))); + connect( cbShowToolTips, SIGNAL(clicked()), SLOT(slotSelectionChanged()) ); + connect( cbWindowShadow, SIGNAL(clicked()), SLOT(slotSelectionChanged()) ); connect( cBorder, SIGNAL( activated( int )), SLOT( slotBorderChanged( int ))); // connect( cbUseMiniWindows, SIGNAL(clicked()), SLOT(slotSelectionChanged()) ); @@ -465,6 +633,28 @@ border_size = BorderNormal; checkSupportedBorderSizes(); + // Shadows tab + // =========== + bool shadowEnabled = conf->readBoolEntry("ShadowEnabled", false); + cbWindowShadow->setChecked(shadowEnabled); + activeShadowSettings->setEnabled(shadowEnabled); + inactiveShadowSettings->setEnabled(shadowEnabled); + whichShadowSettings->setEnabled(shadowEnabled); + shadowColourButton->setColor(conf->readColorEntry("ShadowColour", &Qt::black)); + shadowOpacitySlider->setValue((int)ceil(conf->readDoubleNumEntry("ShadowOpacity", 0.70) * 100)); + shadowXOffsetSpinBox->setValue(conf->readNumEntry("ShadowXOffset", 0)); + shadowYOffsetSpinBox->setValue(conf->readNumEntry("ShadowYOffset", 10)); + cbShadowDocks->setChecked(conf->readBoolEntry("ShadowDocks", false)); + cbShadowOverrides->setChecked(conf->readBoolEntry("ShadowOverrides", false)); + cbShadowTopMenus->setChecked(conf->readBoolEntry("ShadowTopMenus", false)); + shadowThicknessSpinBox->setValue(conf->readNumEntry("ShadowThickness", 10)); + cbInactiveShadow->setChecked(conf->readBoolEntry("InactiveShadowEnabled", false)); + inactiveShadowColourButton->setColor(conf->readColorEntry("InactiveShadowColour", &Qt::black)); + inactiveShadowOpacitySlider->setValue((int)ceil(conf->readDoubleNumEntry("InactiveShadowOpacity", 0.70) * 100)); + inactiveShadowXOffsetSpinBox->setValue(conf->readNumEntry("InactiveShadowXOffset", 0)); + inactiveShadowYOffsetSpinBox->setValue(conf->readNumEntry("InactiveShadowYOffset", 5)); + inactiveShadowThicknessSpinBox->setValue(conf->readNumEntry("InactiveShadowThickness", 5)); + emit KCModule::changed(false); } @@ -489,6 +679,27 @@ conf->writeEntry("ButtonsOnRight", buttonPositionWidget->buttonsRight() ); conf->writeEntry("BorderSize", border_size ); + // Shadow settings + conf->writeEntry("ShadowEnabled", cbWindowShadow->isChecked()); + conf->writeEntry("ShadowColour", shadowColourButton->color()); + conf->writeEntry("ShadowOpacity", shadowOpacitySlider->value() / 100.0); + conf->writeEntry("ShadowXOffset", shadowXOffsetSpinBox->value()); + conf->writeEntry("ShadowYOffset", shadowYOffsetSpinBox->value()); + conf->writeEntry("ShadowThickness", shadowThicknessSpinBox->value()); + conf->writeEntry("ShadowDocks", cbShadowDocks->isChecked()); + conf->writeEntry("ShadowOverrides", cbShadowOverrides->isChecked()); + conf->writeEntry("ShadowTopMenus", cbShadowTopMenus->isChecked()); + conf->writeEntry("InactiveShadowEnabled", cbInactiveShadow->isChecked()); + conf->writeEntry("InactiveShadowColour", inactiveShadowColourButton->color()); + conf->writeEntry("InactiveShadowOpacity", + inactiveShadowOpacitySlider->value() / 100.0); + conf->writeEntry("InactiveShadowXOffset", + inactiveShadowXOffsetSpinBox->value()); + conf->writeEntry("InactiveShadowYOffset", + inactiveShadowYOffsetSpinBox->value()); + conf->writeEntry("InactiveShadowThickness", + inactiveShadowThicknessSpinBox->value()); + oldLibraryName = currentLibraryName; currentLibraryName = libName; @@ -541,6 +752,7 @@ cbUseCustomButtonPositions->setChecked( false ); buttonPositionWidget->setEnabled( false ); cbShowToolTips->setChecked( true ); + cbWindowShadow->setChecked( false ); // cbUseMiniWindows->setChecked( false); // Don't set default for now // decorationList->setSelected( @@ -552,6 +764,21 @@ border_size = BorderNormal; checkSupportedBorderSizes(); + shadowColourButton->setColor(Qt::black); + shadowOpacitySlider->setValue(70); + shadowXOffsetSpinBox->setValue(0); + shadowYOffsetSpinBox->setValue(10); + shadowThicknessSpinBox->setValue(10); + cbShadowDocks->setChecked(false); + cbShadowOverrides->setChecked(false); + cbShadowTopMenus->setChecked(false); + cbInactiveShadow->setChecked(false); + inactiveShadowColourButton->setColor(Qt::black); + inactiveShadowOpacitySlider->setValue(70); + inactiveShadowXOffsetSpinBox->setValue(0); + inactiveShadowYOffsetSpinBox->setValue(5); + inactiveShadowThicknessSpinBox->setValue(5); + // Set plugin defaults emit pluginDefaults(); } diff -Naur kdebase-3.5.8.orig/kwin/kcmkwin/kwindecoration/kwindecoration.h kdebase-3.5.8/kwin/kcmkwin/kwindecoration/kwindecoration.h --- kdebase-3.5.8.orig/kwin/kcmkwin/kwindecoration/kwindecoration.h 2006-01-19 18:01:03.000000000 +0100 +++ kdebase-3.5.8/kwin/kcmkwin/kwindecoration/kwindecoration.h 2007-11-21 07:02:51.000000000 +0100 @@ -127,6 +127,19 @@ // Page 2 ButtonPositionWidget *buttonPositionWidget; QVBox* buttonPage; + + // Page 3 + QVBox *shadowPage; + KColorButton *inactiveShadowColourButton, *shadowColourButton; + QCheckBox *cbShadowDocks, *cbShadowOverrides, *cbShadowTopMenus; + QCheckBox *cbInactiveShadow, *cbWindowShadow; + QGroupBox *activeShadowSettings, *inactiveShadowSettings; + QGroupBox *whichShadowSettings; + QSlider *inactiveShadowOpacitySlider, *shadowOpacitySlider; + QSpinBox *inactiveShadowOpacitySpinBox, *shadowOpacitySpinBox; + QSpinBox *inactiveShadowXOffsetSpinBox, *shadowXOffsetSpinBox; + QSpinBox *inactiveShadowYOffsetSpinBox, *shadowYOffsetSpinBox; + QSpinBox *inactiveShadowThicknessSpinBox, *shadowThicknessSpinBox; }; diff -Naur kdebase-3.5.8.orig/kwin/layers.cpp kdebase-3.5.8/kwin/layers.cpp --- kdebase-3.5.8.orig/kwin/layers.cpp 2007-01-15 12:32:14.000000000 +0100 +++ kdebase-3.5.8/kwin/layers.cpp 2007-11-21 07:02:51.000000000 +0100 @@ -134,37 +134,89 @@ // when passig pointers around. // restack the windows according to the stacking order +#if 0 Window* new_stack = new Window[ stacking_order.count() + 2 ]; int pos = 0; +#endif + NET::WindowType t; + Window shadow; + Window *dock_shadow_stack, *window_stack; + int i, numDocks, pos, topmenu_space_pos; + + dock_shadow_stack = new Window[ stacking_order.count() * 2 ]; + window_stack = new Window[ stacking_order.count() * 2 + 2 ]; + i = 0; + pos = 0; + topmenu_space_pos = 1; // not 0, that's supportWindow !!! + // Stack all windows under the support window. The support window is // not used for anything (besides the NETWM property), and it's not shown, // but it was lowered after kwin startup. Stacking all clients below // it ensures that no client will be ever shown above override-redirect // windows (e.g. popups). +#if 0 new_stack[ pos++ ] = supportWindow->winId(); int topmenu_space_pos = 1; // not 0, that's supportWindow !!! +#endif + window_stack[pos++] = supportWindow->winId(); for( ClientList::ConstIterator it = stacking_order.fromLast(); it != stacking_order.end(); --it ) { +#if 0 new_stack[ pos++ ] = (*it)->frameId(); if( (*it)->belongsToLayer() >= DockLayer ) topmenu_space_pos = pos; - } +#endif + t = (*it)->windowType(); + switch (t) + { + case NET::Dock: + window_stack[pos++] = (*it)->frameId(); + if ((shadow = (*it)->shadowId()) != None) + dock_shadow_stack[i++] = shadow; + break; + case NET::Desktop: + numDocks = i; + for (i = 0; i < numDocks; i++) + // Shadows for dock windows go just above the desktop + window_stack[pos++] = dock_shadow_stack[i]; + window_stack[pos++] = (*it)->frameId(); + break; + case NET::TopMenu: + topmenu_space_pos = pos; + // fall through + default: + window_stack[pos++] = (*it)->frameId(); + if ((shadow = (*it)->shadowId()) != None) + // If the current window also has a shadow, place it + // immediately under the current window + window_stack[pos++] = shadow; + } + } if( topmenu_space != NULL ) { // make sure the topmenu space is below all topmenus, fullscreens, etc. for( int i = pos; i > topmenu_space_pos; --i ) +#if 0 new_stack[ i ] = new_stack[ i - 1 ]; new_stack[ topmenu_space_pos ] = topmenu_space->winId(); +#endif + window_stack[ i ] = window_stack[ i - 1 ]; + window_stack[ topmenu_space_pos ] = topmenu_space->winId(); ++pos; } // TODO isn't it too inefficient to restart always all clients? // TODO don't restack not visible windows? assert( new_stack[ 0 ] = supportWindow->winId()); +#if 0 XRestackWindows(qt_xdisplay(), new_stack, pos); delete [] new_stack; +#endif + XRestackWindows(qt_xdisplay(), window_stack, pos); + delete [] dock_shadow_stack; + delete [] window_stack; if ( propagate_new_clients ) { @@ -342,6 +394,11 @@ unconstrained_stacking_order.remove( c ); unconstrained_stacking_order.append( c ); + if (options->shadowEnabled(c->isActive())) + { + c->removeShadow(); + c->drawDelayedShadow(); + } if( !c->isSpecialWindow()) { diff -Naur kdebase-3.5.8.orig/kwin/lib/kdecoration.h kdebase-3.5.8/kwin/lib/kdecoration.h --- kdebase-3.5.8.orig/kwin/lib/kdecoration.h 2005-09-10 10:26:02.000000000 +0200 +++ kdebase-3.5.8/kwin/lib/kdecoration.h 2007-11-21 07:02:51.000000000 +0100 @@ -97,6 +97,7 @@ LowerOp, FullScreenOp, NoBorderOp, + ShadowOp, NoOp, SetupWindowShortcutOp, ApplicationRulesOp ///< @since 3.5 @@ -116,7 +117,7 @@ ColorHandle, ///< The color for the resize handle NUM_COLORS }; - + /** * These flags specify which settings changed when rereading settings. * Each setting in class KDecorationOptions specifies its matching flag. @@ -130,7 +131,7 @@ SettingTooltips = 1 << 4, ///< The tooltip setting was changed SettingBorder = 1 << 5 ///< The border size setting was changed }; - + /** * Border size. KDecorationOptions::preferredBorderSize() returns * one of these values. @@ -261,7 +262,7 @@ * The changed flags for this setting is SettingTooltips. */ bool showTooltips() const; - + /** * The preferred border size selected by the user, e.g. for accessibility * reasons, or when using high resolution displays. It's up to the decoration @@ -322,9 +323,9 @@ * Destroys the KDecoration. */ virtual ~KDecoration(); - + // requests from decoration - + /** * Returns the KDecorationOptions object, which is used to access * configuration settings for the decoration. @@ -417,7 +418,7 @@ * to support older code). For a description of all window types, * see the definition of the NET::WindowType type. Note that * some window types never have decorated windows. - * + * * An example of usage: * @code * const unsigned long supported_types = NET::NormalMask | NET::DesktopMask @@ -671,7 +672,7 @@ /** * This function is called to reset the decoration on settings changes. * It is usually invoked by calling KDecorationFactory::resetDecorations(). - * + * * @param changed Specifies which settings were changed, given by the SettingXXX masks */ virtual void reset( unsigned long changed ); diff -Naur kdebase-3.5.8.orig/kwin/manage.cpp kdebase-3.5.8/kwin/manage.cpp --- kdebase-3.5.8.orig/kwin/manage.cpp 2007-11-21 07:02:36.000000000 +0100 +++ kdebase-3.5.8/kwin/manage.cpp 2007-11-21 07:02:51.000000000 +0100 @@ -356,6 +356,7 @@ setSkipTaskbar( session->skipTaskbar, true ); setSkipPager( session->skipPager ); setShade( session->shaded ? ShadeNormal : ShadeNone ); + setShadowed( session->shadowed ); if( session->maximized != MaximizeRestore ) { maximize( (MaximizeMode) session->maximized ); diff -Naur kdebase-3.5.8.orig/kwin/options.cpp kdebase-3.5.8/kwin/options.cpp --- kdebase-3.5.8.orig/kwin/options.cpp 2007-11-21 07:02:36.000000000 +0100 +++ kdebase-3.5.8/kwin/options.cpp 2007-11-21 07:02:51.000000000 +0100 @@ -202,7 +202,24 @@ if (resetKompmgr) config->writeEntry("ResetKompmgr",FALSE); - + // window drop shadows + config->setGroup("Style"); + shadow_colour = config->readColorEntry("ShadowColour", &Qt::black); + shadow_docks = config->readBoolEntry("ShadowDocks", false); + shadow_overrides = config->readBoolEntry("ShadowOverrides", false); + shadow_topMenus = config->readBoolEntry("ShadowTopMenus", false); + shadow_inactive_colour = config->readColorEntry("InactiveShadowColour", &Qt::black); + shadow_inactive_enabled = config->readBoolEntry("InactiveShadowEnabled", false); + shadow_inactive_opacity = config->readDoubleNumEntry("InactiveShadowOpacity", 0.70); + shadow_inactive_thickness = config->readNumEntry("InactiveShadowThickness", 5); + shadow_inactive_x_offset = config->readNumEntry("InactiveShadowXOffset", 0); + shadow_inactive_y_offset = config->readNumEntry("InactiveShadowYOffset", 5); + shadow_enabled = config->readBoolEntry("ShadowEnabled", false); + shadow_opacity = config->readDoubleNumEntry("ShadowOpacity", 0.70); + shadow_thickness = config->readNumEntry("ShadowThickness", 10); + shadow_x_offset = config->readNumEntry("ShadowXOffset", 0); + shadow_y_offset = config->readNumEntry("ShadowYOffset", 10); + // Read button tooltip animation effect from kdeglobals // Since we want to allow users to enable window decoration tooltips @@ -252,6 +269,8 @@ return HMaximizeOp; else if (name == "Lower") return LowerOp; + else if (name == "Shadow") + return ShadowOp; return NoOp; } @@ -294,6 +313,69 @@ return show_geometry_tip; } +QColor &Options::shadowColour(bool active) + { + return active ? shadow_colour : shadow_inactive_colour; + } + +bool Options::shadowWindowType(NET::WindowType t) + { + bool retval; + + switch (t) + { + case NET::Dialog: + case NET::Normal: + retval = true; + break; + case NET::Desktop: + case NET::Menu: + case NET::Toolbar: + retval = false; + break; + case NET::Dock: + retval = shadow_docks; + break; + case NET::Override: + retval = shadow_overrides; + break; + case NET::TopMenu: + retval = shadow_topMenus; + break; + default: + retval = false; + break; + } + + return retval; + } + +bool Options::shadowEnabled(bool active) + { + return active ? shadow_enabled : + (shadow_enabled && shadow_inactive_enabled); + } + +double Options::shadowOpacity(bool active) + { + return active ? shadow_opacity : shadow_inactive_opacity; + } + +int Options::shadowThickness(bool active) + { + return active ? shadow_thickness : shadow_inactive_thickness; + } + +int Options::shadowXOffset(bool active) + { + return active ? shadow_x_offset : shadow_inactive_x_offset; + } + +int Options::shadowYOffset(bool active) + { + return active ? shadow_y_offset : shadow_inactive_y_offset; + } + int Options::electricBorders() { return electric_borders; diff -Naur kdebase-3.5.8.orig/kwin/options.h kdebase-3.5.8/kwin/options.h --- kdebase-3.5.8.orig/kwin/options.h 2007-11-21 07:02:36.000000000 +0100 +++ kdebase-3.5.8/kwin/options.h 2007-11-21 07:02:51.000000000 +0100 @@ -268,6 +268,45 @@ */ bool showGeometryTip(); + /** + * @returns A QColor representing the colour that window drop shadows should + * be. + */ + QColor &shadowColour(bool active=true); + + /** + * @returns true if shadows should be drawn around windows of the + * specified type + */ + bool shadowWindowType(NET::WindowType t); + + /** + * @returns true if window shadows should be drawn + */ + bool shadowEnabled(bool active=true); + + /** + * @returns Window shadow's opacity between 0.01 and 1.00. + */ + double shadowOpacity(bool active=true); + + /** + * @returns How thick a shadow should be to either side of of a window. + */ + int shadowThickness(bool active=true); + + /** + * @returns Number of pixels along the X-axis by which to offset window + * shadows. + */ + int shadowXOffset(bool active=true); + + /** + * @returns Number of pixels along the Y-axis by which to offset window + * shadows. + */ + int shadowYOffset(bool active=true); + enum { ElectricDisabled = 0, ElectricMoveOnly = 1, ElectricAlways = 2 }; /** * @returns true if electric borders are enabled. With electric borders @@ -336,6 +375,21 @@ bool show_geometry_tip; bool topmenus; bool desktop_topmenu; + QColor shadow_colour; + QColor shadow_inactive_colour; + bool shadow_docks; + bool shadow_overrides; + bool shadow_topMenus; + bool shadow_inactive_enabled; + bool shadow_enabled; + double shadow_inactive_opacity; + double shadow_opacity; + int shadow_inactive_thickness; + int shadow_thickness; + int shadow_inactive_x_offset; + int shadow_x_offset; + int shadow_inactive_y_offset; + int shadow_y_offset; // List of window classes for which not to use focus stealing prevention QStringList ignoreFocusStealingClasses; diff -Naur kdebase-3.5.8.orig/kwin/sm.cpp kdebase-3.5.8/kwin/sm.cpp --- kdebase-3.5.8.orig/kwin/sm.cpp 2007-05-14 09:55:48.000000000 +0200 +++ kdebase-3.5.8/kwin/sm.cpp 2007-11-21 07:02:51.000000000 +0100 @@ -106,6 +106,7 @@ // the config entry is called "sticky" for back. comp. reasons config->writeEntry( QString("sticky")+n, c->isOnAllDesktops() ); config->writeEntry( QString("shaded")+n, c->isShade() ); + config->writeEntry( QString("shadowed")+n, c->isShadowed() ); // the config entry is called "staysOnTop" for back. comp. reasons config->writeEntry( QString("staysOnTop")+n, c->keepAbove() ); config->writeEntry( QString("keepBelow")+n, c->keepBelow() ); @@ -172,6 +173,7 @@ info->minimized = config->readBoolEntry( QString("iconified")+n, FALSE ); info->onAllDesktops = config->readBoolEntry( QString("sticky")+n, FALSE ); info->shaded = config->readBoolEntry( QString("shaded")+n, FALSE ); + info->shadowed = config->readBoolEntry( QString("shadowed")+n, TRUE ); info->keepAbove = config->readBoolEntry( QString("staysOnTop")+n, FALSE ); info->keepBelow = config->readBoolEntry( QString("keepBelow")+n, FALSE ); info->skipTaskbar = config->readBoolEntry( QString("skipTaskbar")+n, FALSE ); diff -Naur kdebase-3.5.8.orig/kwin/sm.h kdebase-3.5.8/kwin/sm.h --- kdebase-3.5.8.orig/kwin/sm.h 2005-09-10 10:26:03.000000000 +0200 +++ kdebase-3.5.8/kwin/sm.h 2007-11-21 07:02:51.000000000 +0100 @@ -39,6 +39,7 @@ bool minimized; bool onAllDesktops; bool shaded; + bool shadowed; bool keepAbove; bool keepBelow; bool skipTaskbar; diff -Naur kdebase-3.5.8.orig/kwin/useractions.cpp kdebase-3.5.8/kwin/useractions.cpp --- kdebase-3.5.8.orig/kwin/useractions.cpp 2007-11-21 07:02:36.000000000 +0100 +++ kdebase-3.5.8/kwin/useractions.cpp 2007-11-21 07:02:51.000000000 +0100 @@ -65,6 +65,7 @@ advanced_popup->insertItem( SmallIconSet( "window_fullscreen" ), i18n("&Fullscreen")+'\t'+keys->shortcut("Window Fullscreen").seq(0).toString(), Options::FullScreenOp ); advanced_popup->insertItem( i18n("&No Border")+'\t'+keys->shortcut("Window No Border").seq(0).toString(), Options::NoBorderOp ); + advanced_popup->insertItem( i18n("Shad&ow"), Options::ShadowOp ); advanced_popup->insertItem( SmallIconSet("key_bindings"), i18n("Window &Shortcut...")+'\t'+keys->shortcut("Setup Window Shortcut").seq(0).toString(), Options::SetupWindowShortcutOp ); advanced_popup->insertItem( SmallIconSet( "wizard" ), i18n("&Special Window Settings..."), Options::WindowRulesOp ); @@ -172,6 +173,10 @@ advanced_popup->setItemEnabled( Options::FullScreenOp, active_popup_client->userCanSetFullScreen() ); advanced_popup->setItemChecked( Options::NoBorderOp, active_popup_client->noBorder() ); advanced_popup->setItemEnabled( Options::NoBorderOp, active_popup_client->userCanSetNoBorder() ); + + advanced_popup->setItemEnabled( Options::ShadowOp, (options->shadowWindowType(active_popup_client->windowType()) && options->shadowEnabled(active_popup_client->isActive())) ); + advanced_popup->setItemChecked( Options::ShadowOp, active_popup_client->isShadowed() ); + popup->setItemEnabled( Options::MinimizeOp, active_popup_client->isMinimizable() ); popup->setItemEnabled( Options::CloseOp, active_popup_client->isCloseable() ); if (options->useTranslucency) @@ -398,6 +403,9 @@ case Options::ShadeOp: c->performMouseCommand( Options::MouseShade, QCursor::pos()); break; + case Options::ShadowOp: + c->setShadowed( !c->isShadowed() ); + break; case Options::OnAllDesktopsOp: c->setOnAllDesktops( !c->isOnAllDesktops() ); break; diff -Naur kdebase-3.5.8.orig/kwin/workspace.cpp kdebase-3.5.8/kwin/workspace.cpp --- kdebase-3.5.8.orig/kwin/workspace.cpp 2007-11-21 07:02:36.000000000 +0100 +++ kdebase-3.5.8/kwin/workspace.cpp 2007-11-21 07:02:51.000000000 +0100 @@ -79,6 +79,7 @@ rules_updates_disabled( false ), active_client (0), last_active_client (0), + next_active_client (0), most_recently_raised (0), movingClient(0), pending_take_activity ( NULL ), @@ -685,6 +686,24 @@ } } +void Workspace::updateOverlappingShadows(unsigned long window) + { + Client *client; + + if ((client = findClient(WindowMatchPredicate((WId)window)))) + // Redraw overlapping shadows without waiting for the specified window + // to redraw its own shadow + client->drawOverlappingShadows(false); + } + +void Workspace::setShadowed(unsigned long window, bool shadowed) + { + Client *client; + + if ((client = findClient(WindowMatchPredicate((WId)window)))) + client->setShadowed(shadowed); + } + void Workspace::updateCurrentTopMenu() { if( !managingTopMenus()) diff -Naur kdebase-3.5.8.orig/kwin/workspace.h kdebase-3.5.8/kwin/workspace.h --- kdebase-3.5.8.orig/kwin/workspace.h 2007-11-21 07:02:36.000000000 +0100 +++ kdebase-3.5.8/kwin/workspace.h 2007-11-21 07:02:51.000000000 +0100 @@ -230,6 +230,8 @@ void unclutterDesktop(); void doNotManage(QString); bool setCurrentDesktop( int new_desktop ); + void updateOverlappingShadows(WId window); + void setShadowed(WId window, bool shadowed); void nextDesktop(); void previousDesktop(); void circulateDesktopApplications(); @@ -517,6 +519,7 @@ Client* active_client; Client* last_active_client; + Client* next_active_client; // will be active after active_client deactivates Client* most_recently_raised; // used _only_ by raiseOrLowerClient() Client* movingClient; Client* pending_take_activity; @@ -703,7 +706,15 @@ inline Client* Workspace::activeClient() const { - return active_client; + // next_active_client is a kludge for drop shadows. If a window that is + // activated is not also raised (i.e. when focus follows mouse), then the + // newly activated window and its shadow won't cover visual artifacts that + // might exist in the inactive window's shadow. We work around this by + // (re)drawing the inactive window's shadow after the active window's shadow + // is drawn, but to do that the inactive window needs to know which window + // will become active next. next_active_client is a Client pointer for that + // purpose. + return next_active_client != NULL ? next_active_client : active_client; } inline Client* Workspace::mostRecentlyActivatedClient() const