/* This file is part of the KDE project Copyright (C) 2001 Lubos Lunak Copyright (C) 2010 Martin Gräßlin This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "startupid.h" #include #include "klaunchsettings.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define KDE_STARTUP_ICON QLatin1String( "kmenu" ) #ifdef HAVE_XCURSOR #include #endif enum kde_startup_status_enum { StartupPre, StartupIn, StartupDone }; static kde_startup_status_enum kde_startup_status = StartupPre; static Atom kde_splash_progress; StartupId::StartupId( QWidget* parent, const char* name ) : QWidget( parent ), startup_info( KStartupInfo::CleanOnCantDetect ), startup_window( None ), blinking( true ), bouncing( false ), selection_watcher( new KSelectionWatcher( "_KDE_STARTUP_FEEDBACK", -1, this )) { setObjectName( QLatin1String( name ) ); hide(); // is QWidget only because of x11Event() if( kde_startup_status == StartupPre ) { kde_splash_progress = XInternAtom( QX11Info::display(), "_KDE_SPLASH_PROGRESS", False ); XWindowAttributes attrs; XGetWindowAttributes( QX11Info::display(), QX11Info::appRootWindow(), &attrs); XSelectInput( QX11Info::display(), QX11Info::appRootWindow(), attrs.your_event_mask | SubstructureNotifyMask); kapp->installX11EventFilter( this ); } update_timer.setSingleShot( true ); connect( &update_timer, SIGNAL(timeout()), SLOT(update_startupid())); connect( &startup_info, SIGNAL(gotNewStartup(KStartupInfoId,KStartupInfoData)), SLOT(gotNewStartup(KStartupInfoId,KStartupInfoData))); connect( &startup_info, SIGNAL(gotStartupChange(KStartupInfoId,KStartupInfoData)), SLOT(gotStartupChange(KStartupInfoId,KStartupInfoData))); connect( &startup_info, SIGNAL(gotRemoveStartup(KStartupInfoId,KStartupInfoData)), SLOT(gotRemoveStartup(KStartupInfoId))); connect( selection_watcher, SIGNAL(newOwner(Window)), SLOT(newOwner())); connect( selection_watcher, SIGNAL(lostOwner()), SLOT(lostOwner())); active_selection = ( selection_watcher->owner() != None ); } StartupId::~StartupId() { stop_startupid(); } void StartupId::configure() { startup_info.setTimeout( KLaunchSettings::timeout()); blinking = KLaunchSettings::blinking(); bouncing = KLaunchSettings::bouncing(); } void StartupId::gotNewStartup( const KStartupInfoId& id_P, const KStartupInfoData& data_P ) { if( active_selection ) return; QString icon = data_P.findIcon(); current_startup = id_P; startups[ id_P ] = icon; start_startupid( icon ); } void StartupId::gotStartupChange( const KStartupInfoId& id_P, const KStartupInfoData& data_P ) { if( active_selection ) return; if( current_startup == id_P ) { QString icon = data_P.findIcon(); if( !icon.isEmpty() && icon != startups[ current_startup ] ) { startups[ id_P ] = icon; start_startupid( icon ); } } } void StartupId::gotRemoveStartup( const KStartupInfoId& id_P ) { if( active_selection ) return; startups.remove( id_P ); if( startups.count() == 0 ) { current_startup = KStartupInfoId(); // null if( kde_startup_status == StartupIn ) start_startupid( KDE_STARTUP_ICON ); else stop_startupid(); return; } current_startup = startups.begin().key(); start_startupid( startups[ current_startup ] ); } bool StartupId::x11Event( XEvent* e ) { if( e->type == ClientMessage && e->xclient.window == QX11Info::appRootWindow() && e->xclient.message_type == kde_splash_progress ) { const char* s = e->xclient.data.b; if( strcmp( s, "desktop" ) == 0 && kde_startup_status == StartupPre ) { kde_startup_status = StartupIn; if( startups.count() == 0 ) start_startupid( KDE_STARTUP_ICON ); // 60(?) sec timeout - shouldn't be hopefully needed anyway, ksmserver should have it too QTimer::singleShot( 60000, this, SLOT(finishKDEStartup())); } else if( strcmp( s, "ready" ) == 0 && kde_startup_status < StartupDone ) QTimer::singleShot( 2000, this, SLOT(finishKDEStartup())); } return false; } void StartupId::finishKDEStartup() { kde_startup_status = StartupDone; kapp->removeX11EventFilter( this ); if( startups.count() == 0 ) stop_startupid(); } void StartupId::stop_startupid() { if( startup_window != None ) XDestroyWindow( QX11Info::display(), startup_window ); startup_window = None; if( blinking ) for( int i = 0; i < NUM_BLINKING_PIXMAPS; ++i ) pixmaps[ i ] = QPixmap(); // null update_timer.stop(); } static QPixmap scalePixmap( const QPixmap& pm, int w, int h ) { QImage scaled = pm.toImage().scaled(w, h, Qt::IgnoreAspectRatio, Qt::SmoothTransformation); if (scaled.format() != QImage::Format_ARGB32_Premultiplied && scaled.format() != QImage::Format_ARGB32) scaled = scaled.convertToFormat(QImage::Format_ARGB32_Premultiplied); QImage result(20, 20, QImage::Format_ARGB32_Premultiplied); QPainter p(&result); p.setCompositionMode(QPainter::CompositionMode_Source); p.fillRect(result.rect(), Qt::transparent); p.drawImage((20 - w) / 2, (20 - h) / 2, scaled, 0, 0, w, h); return QPixmap::fromImage(result); } // Transparent images are converted to 32bpp pixmaps, but // setting those as window background needs ARGB visual // and compositing - convert to 24bpp, at least for now. static QPixmap make24bpp( const QPixmap& p ) { QPixmap ret( p.size()); QPainter pt( &ret ); pt.drawPixmap( 0, 0, p ); pt.end(); ret.setMask( p.mask()); return ret; } void StartupId::start_startupid( const QString& icon_P ) { const QColor startup_colors[ StartupId::NUM_BLINKING_PIXMAPS ] = { Qt::black, Qt::darkGray, Qt::lightGray, Qt::white, Qt::white }; QPixmap icon_pixmap = KIconLoader::global()->loadIcon( icon_P, KIconLoader::Small, 0, KIconLoader::DefaultState, QStringList(), 0, true ); // return null pixmap if not found if( icon_pixmap.isNull()) icon_pixmap = SmallIcon( QLatin1String( "system-run" ) ); if( startup_window == None ) { XSetWindowAttributes attrs; attrs.override_redirect = True; attrs.save_under = True; // useful saveunder if possible to avoid redrawing attrs.colormap = QX11Info::appColormap(); attrs.background_pixel = WhitePixel( QX11Info::display(), QX11Info::appScreen()); attrs.border_pixel = BlackPixel( QX11Info::display(), QX11Info::appScreen()); startup_window = XCreateWindow( QX11Info::display(), DefaultRootWindow( QX11Info::display()), 0, 0, 1, 1, 0, QX11Info::appDepth(), InputOutput, static_cast< Visual* >( QX11Info::appVisual()), CWOverrideRedirect | CWSaveUnder | CWColormap | CWBackPixel | CWBorderPixel, &attrs ); XClassHint class_hint; QByteArray rname = QApplication::applicationName().toLatin1(); QByteArray rclass = QX11Info::appClass(); class_hint.res_name = rname.data(); class_hint.res_class = rclass.data(); XSetWMProperties( QX11Info::display(), startup_window, NULL, NULL, NULL, 0, NULL, NULL, &class_hint ); XChangeProperty( QX11Info::display(), winId(), XInternAtom( QX11Info::display(), "WM_WINDOW_ROLE", False ), XA_STRING, 8, PropModeReplace, (unsigned char *)"startupfeedback", strlen( "startupfeedback" )); } XResizeWindow( QX11Info::display(), startup_window, icon_pixmap.width(), icon_pixmap.height()); if( blinking ) { // no mask XShapeCombineMask( QX11Info::display(), startup_window, ShapeBounding, 0, 0, None, ShapeSet ); int window_w = icon_pixmap.width(); int window_h = icon_pixmap.height(); for( int i = 0; i < NUM_BLINKING_PIXMAPS; ++i ) { pixmaps[ i ] = QPixmap( window_w, window_h ); pixmaps[ i ].fill( startup_colors[ i ] ); QPainter p( &pixmaps[ i ] ); p.drawPixmap( 0, 0, icon_pixmap ); p.end(); } color_index = 0; } else if( bouncing ) { XResizeWindow( QX11Info::display(), startup_window, 20, 20 ); pixmaps[ 0 ] = make24bpp( scalePixmap( icon_pixmap, 16, 16 )); pixmaps[ 1 ] = make24bpp( scalePixmap( icon_pixmap, 14, 18 )); pixmaps[ 2 ] = make24bpp( scalePixmap( icon_pixmap, 12, 20 )); pixmaps[ 3 ] = make24bpp( scalePixmap( icon_pixmap, 18, 14 )); pixmaps[ 4 ] = make24bpp( scalePixmap( icon_pixmap, 20, 12 )); frame = 0; } else { icon_pixmap = make24bpp( icon_pixmap ); if( !icon_pixmap.mask().isNull() ) // set mask XShapeCombineMask( QX11Info::display(), startup_window, ShapeBounding, 0, 0, icon_pixmap.mask().handle(), ShapeSet ); else // clear mask XShapeCombineMask( QX11Info::display(), startup_window, ShapeBounding, 0, 0, None, ShapeSet ); XSetWindowBackgroundPixmap( QX11Info::display(), startup_window, icon_pixmap.handle()); XClearWindow( QX11Info::display(), startup_window ); } update_startupid(); } namespace { const int color_to_pixmap[] = { 0, 1, 2, 3, 2, 1 }; const int frame_to_yoffset[] = { -5, -1, 2, 5, 8, 10, 12, 13, 15, 15, 15, 15, 14, 12, 10, 8, 5, 2, -1, -5 }; const int frame_to_pixmap[] = { 0, 0, 0, 1, 2, 2, 1, 0, 3, 4, 4, 3, 0, 1, 2, 2, 1, 0, 0, 0 }; } void StartupId::update_startupid() { int yoffset = 0; if( blinking ) { XSetWindowBackgroundPixmap( QX11Info::display(), startup_window, pixmaps[ color_to_pixmap[ color_index ]].handle()); XClearWindow( QX11Info::display(), startup_window ); if( ++color_index >= ( sizeof( color_to_pixmap ) / sizeof( color_to_pixmap[ 0 ] ))) color_index = 0; } else if( bouncing ) { yoffset = frame_to_yoffset[ frame ]; QPixmap pixmap = pixmaps[ frame_to_pixmap[ frame ] ]; XSetWindowBackgroundPixmap( QX11Info::display(), startup_window, pixmap.handle()); XClearWindow( QX11Info::display(), startup_window ); if ( !pixmap.mask().isNull() ) // set mask XShapeCombineMask( QX11Info::display(), startup_window, ShapeBounding, 0, 0, pixmap.mask().handle(), ShapeSet ); else // clear mask XShapeCombineMask( QX11Info::display(), startup_window, ShapeBounding, 0, 0, None, ShapeSet ); if ( ++frame >= ( sizeof( frame_to_yoffset ) / sizeof( frame_to_yoffset[ 0 ] ) ) ) frame = 0; } Window dummy1, dummy2; int x, y; int dummy3, dummy4; unsigned int dummy5; if( !XQueryPointer( QX11Info::display(), QX11Info::appRootWindow(), &dummy1, &dummy2, &x, &y, &dummy3, &dummy4, &dummy5 )) { XUnmapWindow( QX11Info::display(), startup_window ); update_timer.start( 100 ); return; } QPoint c_pos( x, y ); int cursor_size = 0; #ifdef HAVE_XCURSOR cursor_size = XcursorGetDefaultSize( QX11Info::display()); #endif int X_DIFF; if( cursor_size <= 16 ) X_DIFF = 8 + 7; else if( cursor_size <= 32 ) X_DIFF = 16 + 7; else if( cursor_size <= 48 ) X_DIFF = 24 + 7; else X_DIFF = 32 + 7; int Y_DIFF = X_DIFF; XMoveWindow( QX11Info::display(), startup_window, c_pos.x() + X_DIFF, c_pos.y() + Y_DIFF + yoffset ); XMapWindow( QX11Info::display(), startup_window ); XRaiseWindow( QX11Info::display(), startup_window ); update_timer.start( bouncing ? 30 : 100 ); QApplication::flush(); } void StartupId::newOwner() { active_selection = true; } void StartupId::lostOwner() { active_selection = false; } #include "moc_startupid.cpp"