/***************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2006 Lubos Lunak You can Freely distribute this program under the GNU General Public License. See the file "COPYING" for the exact licensing terms. ******************************************************************/ #include "kwineffects.h" #include "kwinglutils.h" #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_XRENDER #include #endif namespace KWin { void WindowPrePaintData::setTranslucent() { mask |= Effect::PAINT_WINDOW_TRANSLUCENT; mask &= ~Effect::PAINT_WINDOW_OPAQUE; clip = QRegion(); // cannot clip, will be transparent } void WindowPrePaintData::setTransformed() { mask |= Effect::PAINT_WINDOW_TRANSFORMED; } WindowPaintData::WindowPaintData( EffectWindow* w ) : opacity( w->opacity()) , contents_opacity( 1.0 ) , decoration_opacity( 1.0 ) , xScale( 1 ) , yScale( 1 ) , xTranslate( 0 ) , yTranslate( 0 ) , saturation( 1 ) , brightness( 1 ) , shader( NULL ) { quads = w->buildQuads(); } ScreenPaintData::ScreenPaintData() : xScale( 1 ) , yScale( 1 ) , xTranslate( 0 ) , yTranslate( 0 ) { } //**************************************** // Effect //**************************************** Effect::Effect() { } Effect::~Effect() { } void Effect::windowUserMovedResized( EffectWindow* , bool, bool ) { } void Effect::windowOpacityChanged( EffectWindow*, double ) { } void Effect::windowAdded( EffectWindow* ) { } void Effect::windowClosed( EffectWindow* ) { } void Effect::windowDeleted( EffectWindow* ) { } void Effect::windowActivated( EffectWindow* ) { } void Effect::windowMinimized( EffectWindow* ) { } void Effect::windowUnminimized( EffectWindow* ) { } void Effect::windowInputMouseEvent( Window, QEvent* ) { } void Effect::grabbedKeyboardEvent( QKeyEvent* ) { } void Effect::desktopChanged( int ) { } void Effect::windowDamaged( EffectWindow*, const QRect& ) { } void Effect::windowGeometryShapeChanged( EffectWindow*, const QRect& ) { } void Effect::tabBoxAdded( int ) { } void Effect::tabBoxClosed() { } void Effect::tabBoxUpdated() { } bool Effect::borderActivated( ElectricBorder ) { return false; } void Effect::mouseChanged( const QPoint&, const QPoint&, Qt::MouseButtons, Qt::KeyboardModifiers ) { } void Effect::prePaintScreen( ScreenPrePaintData& data, int time ) { effects->prePaintScreen( data, time ); } void Effect::paintScreen( int mask, QRegion region, ScreenPaintData& data ) { effects->paintScreen( mask, region, data ); } void Effect::postPaintScreen() { effects->postPaintScreen(); } void Effect::prePaintWindow( EffectWindow* w, WindowPrePaintData& data, int time ) { effects->prePaintWindow( w, data, time ); } void Effect::paintWindow( EffectWindow* w, int mask, QRegion region, WindowPaintData& data ) { effects->paintWindow( w, mask, region, data ); } void Effect::postPaintWindow( EffectWindow* w ) { effects->postPaintWindow( w ); } void Effect::drawWindow( EffectWindow* w, int mask, QRegion region, WindowPaintData& data ) { effects->drawWindow( w, mask, region, data ); } QRect Effect::transformWindowDamage( EffectWindow* w, const QRect& r ) { return effects->transformWindowDamage( w, r ); } void Effect::setPositionTransformations( WindowPaintData& data, QRect& region, EffectWindow* w, const QRect& r, Qt::AspectRatioMode aspect ) { QSize size = w->size(); size.scale( r.size(), aspect ); data.xScale = size.width() / double( w->width()); data.yScale = size.height() / double( w->height()); int width = int( w->width() * data.xScale ); int height = int( w->height() * data.yScale ); int x = r.x() + ( r.width() - width ) / 2; int y = r.y() + ( r.height() - height ) / 2; region = QRect( x, y, width, height ); data.xTranslate = x - w->x(); data.yTranslate = y - w->y(); } int Effect::displayWidth() { return KWin::displayWidth(); } int Effect::displayHeight() { return KWin::displayHeight(); } QPoint Effect::cursorPos() { return effects->cursorPos(); } //**************************************** // EffectsHandler //**************************************** EffectsHandler::EffectsHandler(CompositingType type) : current_paint_screen( 0 ) , current_paint_window( 0 ) , current_draw_window( 0 ) , current_transform( 0 ) , compositing_type( type ) { if( compositing_type == NoCompositing ) return; KWin::effects = this; } EffectsHandler::~EffectsHandler() { // All effects should already be unloaded by Impl dtor assert( loaded_effects.count() == 0 ); } QRect EffectsHandler::transformWindowDamage( EffectWindow* w, const QRect& r ) { if( current_transform < loaded_effects.size()) { QRect rr = loaded_effects[current_transform++].second->transformWindowDamage( w, r ); --current_transform; return rr; } else return r; } Window EffectsHandler::createInputWindow( Effect* e, const QRect& r, const QCursor& cursor ) { return createInputWindow( e, r.x(), r.y(), r.width(), r.height(), cursor ); } Window EffectsHandler::createFullScreenInputWindow( Effect* e, const QCursor& cursor ) { return createInputWindow( e, 0, 0, displayWidth(), displayHeight(), cursor ); } CompositingType EffectsHandler::compositingType() const { return compositing_type; } void EffectsHandler::sendReloadMessage( const QString& effectname ) { QDBusMessage message = QDBusMessage::createMethodCall("org.kde.kwin", "/KWin", "org.kde.KWin", "reloadEffect"); message << QString("kwin4_effect_" + effectname); QDBusConnection::sessionBus().send(message); } KConfigGroup EffectsHandler::effectConfig( const QString& effectname ) { KSharedConfig::Ptr kwinconfig = KSharedConfig::openConfig( "kwinrc", KConfig::CascadeConfig ); return kwinconfig->group( "Effect-" + effectname ); } bool EffectsHandler::paintText( const QString& text, const QPoint& center, int maxwidth, const QColor& color, const QFont& font ) { QPainter p; // Calculate size of the text QFontMetrics fm( font ); QString painttext = fm.elidedText( text, Qt::ElideRight, maxwidth ); QRect textrect = fm.boundingRect( painttext ); // Create temporary QPixmap where the text will be drawn onto QPixmap textPixmap( textrect.width(), textrect.height()); textPixmap.fill( Qt::transparent ); // Draw the text p.begin( &textPixmap ); p.setFont( font ); p.setRenderHint( QPainter::TextAntialiasing ); p.setPen( color ); p.drawText( -textrect.topLeft(), painttext ); p.end(); // Area covered by text QRect area( center.x() - textrect.width() / 2, center.y() - textrect.height() / 2, textrect.width(), textrect.height() ); #ifdef HAVE_OPENGL if( effects->compositingType() == OpenGLCompositing ) { GLTexture textTexture( textPixmap, GL_TEXTURE_RECTANGLE_ARB ); glPushAttrib( GL_CURRENT_BIT | GL_ENABLE_BIT | GL_TEXTURE_BIT ); glEnable( GL_BLEND ); glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); textTexture.bind(); const float verts[ 4 * 2 ] = { area.x(), area.y(), area.x(), area.y() + area.height(), area.x() + area.width(), area.y() + area.height(), area.x() + area.width(), area.y() }; const float texcoords[ 4 * 2 ] = { 0, textPixmap.height(), 0, 0, textPixmap.width(), 0, textPixmap.width(), textPixmap.height() }; renderGLGeometry( 4, verts, texcoords ); textTexture.unbind(); glPopAttrib(); return true; } #endif #ifdef HAVE_XRENDER if( effects->compositingType() == XRenderCompositing ) { static XRenderPictFormat* alphaFormat = 0; if( !alphaFormat) alphaFormat = XRenderFindStandardFormat( display(), PictStandardARGB32 ); Picture textPicture; textPicture = XRenderCreatePicture( display(), textPixmap.handle(), alphaFormat, 0, NULL ); XRenderComposite( display(), textPixmap.depth() == 32 ? PictOpOver : PictOpSrc, textPicture, None, effects->xrenderBufferPicture(), 0, 0, 0, 0, area.x(), area.y(), area.width(), area.height()); XRenderFreePicture( display(), textPicture ); return true; } #endif return false; } bool EffectsHandler::paintTextWithBackground( const QString& text, const QPoint& center, int maxwidth, const QColor& color, const QColor& bgcolor, const QFont& font ) { // Calculate size of the text QFontMetrics fm( font ); QString painttext = fm.elidedText( text, Qt::ElideRight, maxwidth ); QRect textrect = fm.boundingRect( painttext ); // Area covered by text QRect area( center.x() - textrect.width() / 2, center.y() - textrect.height() / 2, textrect.width(), textrect.height() ); #ifdef HAVE_OPENGL if( effects->compositingType() == OpenGLCompositing ) { glColor4f( bgcolor.redF(), bgcolor.greenF(), bgcolor.blueF(), bgcolor.alphaF() ); renderRoundBox( area.adjusted( -8, -3, 8, 3 ), 5 ); return paintText( text, center, maxwidth, color, font ); } #endif // TODO: render at least a simple background rect in XRender mode return false; } EffectsHandler* effects = 0; //**************************************** // EffectWindow //**************************************** EffectWindow::EffectWindow() { } EffectWindow::~EffectWindow() { } bool EffectWindow::isOnCurrentDesktop() const { return isOnDesktop( effects->currentDesktop()); } bool EffectWindow::isOnDesktop( int d ) const { return desktop() == d || isOnAllDesktops(); } bool EffectWindow::hasDecoration() const { return contentsRect() != QRect( 0, 0, width(), height()); } //**************************************** // EffectWindowGroup //**************************************** EffectWindowGroup::~EffectWindowGroup() { } /*************************************************************** WindowQuad ***************************************************************/ WindowQuad WindowQuad::makeSubQuad( double x1, double y1, double x2, double y2 ) const { assert( x1 < x2 && y1 < y2 && x1 >= left() && x2 <= right() && y1 >= top() && y2 <= bottom()); #ifndef NDEBUG if( isTransformed()) kFatal( 1212 ) << "Splitting quads is allowed only in pre-paint calls!" ; #endif WindowQuad ret( *this ); // vertices are clockwise starting from topleft ret.verts[ 0 ].px = x1; ret.verts[ 3 ].px = x1; ret.verts[ 1 ].px = x2; ret.verts[ 2 ].px = x2; ret.verts[ 0 ].py = y1; ret.verts[ 1 ].py = y1; ret.verts[ 2 ].py = y2; ret.verts[ 3 ].py = y2; // original x/y are supposed to be the same, no transforming is done here ret.verts[ 0 ].ox = x1; ret.verts[ 3 ].ox = x1; ret.verts[ 1 ].ox = x2; ret.verts[ 2 ].ox = x2; ret.verts[ 0 ].oy = y1; ret.verts[ 1 ].oy = y1; ret.verts[ 2 ].oy = y2; ret.verts[ 3 ].oy = y2; double my_tleft = verts[ 0 ].tx; double my_tright = verts[ 2 ].tx; double my_ttop = verts[ 0 ].ty; double my_tbottom = verts[ 2 ].ty; double tleft = ( x1 - left()) / ( right() - left()) * ( my_tright - my_tleft ) + my_tleft; double tright = ( x2 - left()) / ( right() - left()) * ( my_tright - my_tleft ) + my_tleft; double ttop = ( y1 - top()) / ( bottom() - top()) * ( my_tbottom - my_ttop ) + my_ttop; double tbottom = ( y2 - top()) / ( bottom() - top()) * ( my_tbottom - my_ttop ) + my_ttop; ret.verts[ 0 ].tx = tleft; ret.verts[ 3 ].tx = tleft; ret.verts[ 1 ].tx = tright; ret.verts[ 2 ].tx = tright; ret.verts[ 0 ].ty = ttop; ret.verts[ 1 ].ty = ttop; ret.verts[ 2 ].ty = tbottom; ret.verts[ 3 ].ty = tbottom; return ret; } bool WindowQuad::smoothNeeded() const { // smoothing is needed if the width or height of the quad does not match the original size double width = verts[ 1 ].ox - verts[ 0 ].ox; double height = verts[ 2 ].oy - verts[ 1 ].oy; return( verts[ 1 ].px - verts[ 0 ].px != width || verts[ 2 ].px - verts[ 3 ].px != width || verts[ 2 ].py - verts[ 1 ].py != height || verts[ 3 ].py - verts[ 0 ].py != height ); } /*************************************************************** WindowQuadList ***************************************************************/ WindowQuadList WindowQuadList::splitAtX( double x ) const { WindowQuadList ret; foreach( WindowQuad quad, *this ) { #ifndef NDEBUG if( quad.isTransformed()) kFatal( 1212 ) << "Splitting quads is allowed only in pre-paint calls!" ; #endif bool wholeleft = true; bool wholeright = true; for( int i = 0; i < 4; ++i ) { if( quad[ i ].x() < x ) wholeright = false; if( quad[ i ].x() >= x ) wholeleft = false; } if( wholeleft || wholeright ) // is whole in one split part { ret.append( quad ); continue; } ret.append( quad.makeSubQuad( quad.left(), quad.top(), x, quad.bottom())); ret.append( quad.makeSubQuad( x, quad.top(), quad.right(), quad.bottom())); } return ret; } WindowQuadList WindowQuadList::splitAtY( double y ) const { WindowQuadList ret; foreach( WindowQuad quad, *this ) { #ifndef NDEBUG if( quad.isTransformed()) kFatal( 1212 ) << "Splitting quads is allowed only in pre-paint calls!" ; #endif bool wholetop = true; bool wholebottom = true; for( int i = 0; i < 4; ++i ) { if( quad[ i ].y() < y ) wholebottom = false; if( quad[ i ].y() >= y ) wholetop = false; } if( wholetop || wholebottom ) // is whole in one split part { ret.append( quad ); continue; } ret.append( quad.makeSubQuad( quad.left(), quad.top(), quad.right(), y )); ret.append( quad.makeSubQuad( quad.left(), y, quad.right(), quad.bottom())); } return ret; } WindowQuadList WindowQuadList::makeGrid( int maxquadsize ) const { if( empty()) return *this; // find the bounding rectangle double left = first().left(); double right = first().right(); double top = first().top(); double bottom = first().bottom(); foreach( WindowQuad quad, *this ) { #ifndef NDEBUG if( quad.isTransformed()) kFatal( 1212 ) << "Splitting quads is allowed only in pre-paint calls!" ; #endif left = qMin( left, quad.left()); right = qMax( right, quad.right()); top = qMin( top, quad.top()); bottom = qMax( bottom, quad.bottom()); } WindowQuadList ret; for( double x = left; x < right; x += maxquadsize ) { for( double y = top; y < bottom; y += maxquadsize ) { foreach( WindowQuad quad, *this ) { if( QRectF( QPointF( quad.left(), quad.top()), QPointF( quad.right(), quad.bottom())) .intersects( QRectF( x, y, maxquadsize, maxquadsize ))) { ret.append( quad.makeSubQuad( qMax( x, quad.left()), qMax( y, quad.top()), qMin( quad.right(), x + maxquadsize ), qMin( quad.bottom(), y + maxquadsize ))); } } } } return ret; } void WindowQuadList::makeArrays( float** vertices, float** texcoords ) const { *vertices = new float[ count() * 4 * 2 ]; *texcoords = new float[ count() * 4 * 2 ]; float* vpos = *vertices; float* tpos = *texcoords; for( int i = 0; i < count(); ++i ) for( int j = 0; j < 4; ++j ) { *vpos++ = at( i )[ j ].x(); *vpos++ = at( i )[ j ].y(); *tpos++ = at( i )[ j ].tx; *tpos++ = at( i )[ j ].ty; } } WindowQuadList WindowQuadList::select( WindowQuadType type ) const { foreach( WindowQuad q, *this ) { if( q.type != type ) // something else than ones to select, make a copy and filter { WindowQuadList ret; foreach( WindowQuad q, *this ) { if( q.type == type ) ret.append( q ); } return ret; } } return *this; // nothing to filter out } WindowQuadList WindowQuadList::filterOut( WindowQuadType type ) const { foreach( WindowQuad q, *this ) { if( q.type == type ) // something to filter out, make a copy and filter { WindowQuadList ret; foreach( WindowQuad q, *this ) { if( q.type != type ) ret.append( q ); } return ret; } } return *this; // nothing to filter out } bool WindowQuadList::smoothNeeded() const { foreach( WindowQuad q, *this ) if( q.smoothNeeded()) return true; return false; } } // namespace