/************************************************************************* * Copyright (C) 2014 by Hugo Pereira Da Costa * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * *************************************************************************/ #include "breezeshadowhelper.h" #include "breezeshadowhelper.moc" #include "breeze.h" #include "breezehelper.h" #include "breezepropertynames.h" #include "breezestyleconfigdata.h" #include #include #include #include #include #include #include #include #if BREEZE_HAVE_X11 #include #endif namespace Breeze { const char ShadowHelper::netWMShadowAtomName[] ="_KDE_NET_WM_SHADOW"; //_____________________________________________________ ShadowHelper::ShadowHelper( QObject* parent, Helper& helper ): QObject( parent ), _helper( helper ), _supported( checkSupported() ) #if BREEZE_HAVE_X11 ,_gc( 0 ), _atom( 0 ) #endif {} //_______________________________________________________ ShadowHelper::~ShadowHelper( void ) { #if BREEZE_HAVE_X11 if( Helper::isX11() ) { foreach( const quint32& value, _pixmaps ) xcb_free_pixmap( Helper::connection(), value ); } #endif } //______________________________________________ void ShadowHelper::reset( void ) { #if BREEZE_HAVE_X11 if( Helper::isX11() ) { foreach( const quint32& value, _pixmaps ) xcb_free_pixmap( Helper::connection(), value ); } #endif _pixmaps.clear(); _shadowTiles = TileSet(); } //_______________________________________________________ bool ShadowHelper::registerWidget( QWidget* widget, bool force ) { // do nothing if not supported if( !_supported ) return false; // make sure widget is not already registered if( _widgets.contains( widget ) ) return false; // check if widget qualifies if( !( force || acceptWidget( widget ) ) ) { return false; } // store in map and add destroy signal connection widget->removeEventFilter( this ); widget->installEventFilter( this ); _widgets.insert( widget, 0 ); /* need to install shadow directly when widget "created" state is already set since WinID changed is never called when this is the case */ if( widget->testAttribute(Qt::WA_WState_Created) && installX11Shadows( widget ) ) { _widgets.insert( widget, widget->winId() ); } connect( widget, SIGNAL(destroyed(QObject*)), SLOT(objectDeleted(QObject*)) ); return true; } //_______________________________________________________ void ShadowHelper::unregisterWidget( QWidget* widget ) { if( _widgets.remove( widget ) ) { uninstallX11Shadows( widget ); } } //_______________________________________________________ bool ShadowHelper::eventFilter( QObject* object, QEvent* event ) { // check event type if( event->type() != QEvent::WinIdChange ) return false; // cast widget QWidget* widget( static_cast( object ) ); // install shadows and update winId if( installX11Shadows( widget ) ) { _widgets.insert( widget, widget->winId() ); } return false; } //_______________________________________________________ TileSet ShadowHelper::shadowTiles( void ) { if( !_shadowTiles.isValid() ) { const QPalette palette( QApplication::palette() ); const QColor shadowColor( palette.color( QPalette::Shadow ) ); // pixmap QPixmap pixmap = _helper.highDpiPixmap( Metrics::Shadow_Size*2 ); pixmap.fill( Qt::transparent ); // gradient auto gradientStopColor = [](QColor color, qreal alpha) { color.setAlphaF(alpha); return color; }; QRadialGradient radialGradient( Metrics::Shadow_Size, Metrics::Shadow_Size, Metrics::Shadow_Size); radialGradient.setColorAt(0.0, gradientStopColor( shadowColor, 0.35 ) ); radialGradient.setColorAt(0.25, gradientStopColor( shadowColor, 0.25 ) ); radialGradient.setColorAt(0.5, gradientStopColor( shadowColor, 0.13 ) ); radialGradient.setColorAt(0.75, gradientStopColor( shadowColor, 0.04 ) ); radialGradient.setColorAt(1.0, gradientStopColor( shadowColor, 0.0 ) ); // render QPainter painter( &pixmap ); painter.setRenderHint( QPainter::Antialiasing ); painter.setCompositionMode(QPainter::CompositionMode_Source); painter.setPen( Qt::NoPen ); const QRectF rect( QPoint( 0, 0 ), pixmap.size()/_helper.devicePixelRatio( pixmap ) ); painter.fillRect( rect, radialGradient ); painter.end(); // create tiles from pixmap _shadowTiles = TileSet( pixmap, Metrics::Shadow_Size, Metrics::Shadow_Size, 1, 1 ); } return _shadowTiles; } //_______________________________________________________ void ShadowHelper::objectDeleted( QObject* object ) { _widgets.remove( static_cast( object ) ); } //_______________________________________________________ bool ShadowHelper::checkSupported( void ) const { // create atom #if BREEZE_HAVE_X11 // make sure we are on X11 if( !Helper::isX11() ) return false; // create atom xcb_atom_t netSupportedAtom( _helper.createAtom( "_NET_SUPPORTED" ) ); if( !netSupportedAtom ) return false; // store connection locally xcb_connection_t* connection( Helper::connection() ); // get property const quint32 maxLength = std::string().max_size(); xcb_get_property_cookie_t cookie( xcb_get_property( connection, 0, QX11Info::appRootWindow(), netSupportedAtom, XCB_ATOM_ATOM, 0, (maxLength+3) / 4 ) ); ScopedPointer reply( xcb_get_property_reply( connection, cookie, nullptr ) ); if( !reply ) return false; // get reply length and data const int count( xcb_get_property_value_length( reply.data() )/sizeof( xcb_atom_t ) ); xcb_atom_t *atoms = reinterpret_cast( xcb_get_property_value( reply.data() ) ); bool found( false ); for( int i = 0; i < count && !found; ++i ) { // get atom name and print xcb_atom_t atom( atoms[i] ); xcb_get_atom_name_cookie_t cookie( xcb_get_atom_name( connection, atom ) ); ScopedPointer reply( xcb_get_atom_name_reply( connection, cookie, 0 ) ); if( !reply ) continue; // get name and compare const QString name( QByteArray( xcb_get_atom_name_name( reply.data() ), xcb_get_atom_name_name_length( reply.data() ) ) ); if( strcmp( netWMShadowAtomName, xcb_get_atom_name_name( reply.data() ) ) == 0 ) found = true; } return found; #else return false; #endif } //_______________________________________________________ bool ShadowHelper::isMenu( QWidget* widget ) const { return qobject_cast( widget ); } //_______________________________________________________ bool ShadowHelper::isToolTip( QWidget* widget ) const { return widget->inherits( "QTipLabel" ) || (widget->windowFlags() & Qt::WindowType_Mask) == Qt::ToolTip; } //_______________________________________________________ bool ShadowHelper::isDockWidget( QWidget* widget ) const { return qobject_cast( widget ); } //_______________________________________________________ bool ShadowHelper::isToolBar( QWidget* widget ) const { return qobject_cast( widget ); } //_______________________________________________________ bool ShadowHelper::acceptWidget( QWidget* widget ) const { // flags if( widget->property( PropertyNames::netWMSkipShadow ).toBool() ) return false; if( widget->property( PropertyNames::netWMForceShadow ).toBool() ) return true; // menus if( isMenu( widget ) ) return true; // combobox dropdown lists if( widget->inherits( "QComboBoxPrivateContainer" ) ) return true; // tooltips if( isToolTip( widget ) && !widget->inherits( "Plasma::ToolTip" ) ) { return true; } // detached widgets if( isDockWidget( widget ) || isToolBar( widget ) ) { return true; } // reject return false; } //______________________________________________ const QVector& ShadowHelper::createPixmapHandles( void ) { /** shadow atom and property specification available at http://community.kde.org/KWin/Shadow */ // create atom #if BREEZE_HAVE_X11 if( !_atom && Helper::isX11() ) _atom = _helper.createAtom( QLatin1String( netWMShadowAtomName ) ); #endif shadowTiles(); // make sure size is valid if( _pixmaps.empty() && _shadowTiles.isValid() ) { _pixmaps.append( createPixmap( _shadowTiles.pixmap( 1 ) ) ); _pixmaps.append( createPixmap( _shadowTiles.pixmap( 2 ) ) ); _pixmaps.append( createPixmap( _shadowTiles.pixmap( 5 ) ) ); _pixmaps.append( createPixmap( _shadowTiles.pixmap( 8 ) ) ); _pixmaps.append( createPixmap( _shadowTiles.pixmap( 7 ) ) ); _pixmaps.append( createPixmap( _shadowTiles.pixmap( 6 ) ) ); _pixmaps.append( createPixmap( _shadowTiles.pixmap( 3 ) ) ); _pixmaps.append( createPixmap( _shadowTiles.pixmap( 0 ) ) ); } // return relevant list of pixmap handles return _pixmaps; } //______________________________________________ quint32 ShadowHelper::createPixmap( const QPixmap& source ) { // do nothing for invalid pixmaps if( source.isNull() ) return 0; if( !Helper::isX11() ) return 0; /* in some cases, pixmap handle is invalid. This is the case notably when Qt uses to RasterEngine. In this case, we create an X11 Pixmap explicitly and draw the source pixmap on it. */ #if BREEZE_HAVE_X11 const int width( source.width() ); const int height( source.height() ); // create X11 pixmap xcb_pixmap_t pixmap = xcb_generate_id( Helper::connection() ); xcb_create_pixmap( Helper::connection(), 32, pixmap, QX11Info::appRootWindow(), width, height ); // create gc if( !_gc ) { _gc = xcb_generate_id( Helper::connection() ); xcb_create_gc( Helper::connection(), _gc, pixmap, 0, 0x0 ); } // create image from QPixmap and assign to pixmap QImage image( source.toImage() ); xcb_put_image( Helper::connection(), XCB_IMAGE_FORMAT_Z_PIXMAP, pixmap, _gc, image.width(), image.height(), 0, 0, 0, 32, image.byteCount(), image.constBits()); return pixmap; #else return 0; #endif } //_______________________________________________________ bool ShadowHelper::installX11Shadows( QWidget* widget ) { // do nothing if not supported if( !_supported ) return false; // check widget and shadow if( !widget ) return false; if( !Helper::isX11() ) return false; #if BREEZE_HAVE_X11 #ifndef QT_NO_XRENDER /* From bespin code. Supposibly prevent playing with some 'pseudo-widgets' that have winId matching some other -random- window */ if( !(widget->testAttribute(Qt::WA_WState_Created) || widget->internalWinId() )) { return false; } // create pixmap handles if needed const QVector& pixmaps( createPixmapHandles() ); if( pixmaps.size() != numPixmaps ) return false; // create data // add pixmap handles QVector data; foreach( const quint32& value, pixmaps ) { data.append( value ); } // get devicePixelRatio // for testing purposes only const qreal devicePixelRatio( _helper.devicePixelRatio( _shadowTiles.pixmap( 0 ) ) ); // define shadows padding int size( Metrics::Shadow_Size - Metrics::Shadow_Overlap ); int topSize = ( size - Metrics::Shadow_Offset ) * devicePixelRatio; int bottomSize = size * devicePixelRatio; int rightSize(0); int leftSize(0); switch( StyleConfigData::lightSource() ) { case StyleConfigData::LS_TOPLEFT: leftSize = (size - Metrics::Shadow_Offset) * devicePixelRatio; rightSize = size * devicePixelRatio; break; case StyleConfigData::LS_TOPRIGHT: rightSize = (size - Metrics::Shadow_Offset) * devicePixelRatio; leftSize = size * devicePixelRatio; break; case StyleConfigData::LS_TOP: leftSize = (size - Metrics::Shadow_Offset/2) * devicePixelRatio; rightSize = (size - Metrics::Shadow_Offset/2) * devicePixelRatio; break; } if( widget->inherits( "QBalloonTip" ) ) { // balloon tip needs special margins to deal with the arrow int top = 0; int bottom = 0; widget->getContentsMargins( nullptr, &top, nullptr, &bottom ); // also need to decrement default size further due to extra hard coded round corner size -= 2 * devicePixelRatio; // it seems arrow can be either to the top or the bottom. Adjust margins accordingly if( top > bottom ) topSize -= (top - bottom); else bottomSize -= (bottom - top ); } // assign to data and xcb property data << topSize << rightSize << bottomSize << leftSize; xcb_change_property( Helper::connection(), XCB_PROP_MODE_REPLACE, widget->winId(), _atom, XCB_ATOM_CARDINAL, 32, data.size(), data.constData() ); xcb_flush( Helper::connection() ); return true; #endif #endif return false; } //_______________________________________________________ void ShadowHelper::uninstallX11Shadows( QWidget* widget ) const { #if BREEZE_HAVE_X11 if( !_supported ) return; if( !Helper::isX11() ) return; if( !( widget && widget->testAttribute(Qt::WA_WState_Created) ) ) return; xcb_delete_property( Helper::connection(), widget->winId(), _atom); #else Q_UNUSED( widget ) #endif } }