00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024
00025 #include <fcntl.h>
00026 #include <sys/types.h>
00027 #include <sys/stat.h>
00028
00029
00030 #include <connect.h>
00031 #include <dispatcher.h>
00032 #include <flowsystem.h>
00033 #include <soundserver.h>
00034
00035
00036 #include <qfile.h>
00037 #include <qfileinfo.h>
00038 #include <qiomanager.h>
00039 #include <qstringlist.h>
00040 #include <qtextstream.h>
00041
00042
00043 #include <dcopclient.h>
00044 #include <kaboutdata.h>
00045 #include <kartsdispatcher.h>
00046 #include <kartsserver.h>
00047 #include <kcmdlineargs.h>
00048 #include <kconfig.h>
00049 #include <kdebug.h>
00050 #include <kglobal.h>
00051 #include <klocale.h>
00052 #include <kmessagebox.h>
00053 #include <kpassivepopup.h>
00054 #include <kiconloader.h>
00055 #include <kmacroexpander.h>
00056 #include <kplayobjectfactory.h>
00057 #include <kaudiomanagerplay.h>
00058 #include <kprocess.h>
00059 #include <kstandarddirs.h>
00060 #include <kuniqueapplication.h>
00061 #include <kwin.h>
00062
00063 #include "knotify.h"
00064 #include "knotify.moc"
00065
00066 class KNotifyPrivate
00067 {
00068 public:
00069 KConfig* globalEvents;
00070 KConfig* globalConfig;
00071 QMap<QString, KConfig*> events;
00072 QMap<QString, KConfig*> configs;
00073 QString externalPlayer;
00074 KProcess *externalPlayerProc;
00075
00076 QPtrList<KDE::PlayObject> playObjects;
00077 QMap<KDE::PlayObject*,int> playObjectEventMap;
00078 int externalPlayerEventId;
00079
00080 bool useExternal;
00081 bool useArts;
00082 int volume;
00083 QTimer *playTimer;
00084 KAudioManagerPlay *audioManager;
00085 bool inStartup;
00086 QString startupEvents;
00087 };
00088
00089
00090
00091 KArtsServer *soundServer = 0;
00092
00093 extern "C"{
00094
00095 KDE_EXPORT int kdemain(int argc, char **argv)
00096 {
00097 KAboutData aboutdata("knotify", I18N_NOOP("KNotify"),
00098 "3.0", I18N_NOOP("KDE Notification Server"),
00099 KAboutData::License_GPL, "(C) 1997-2003, KDE Developers");
00100 aboutdata.addAuthor("Carsten Pfeiffer",I18N_NOOP("Current Maintainer"),"pfeiffer@kde.org");
00101 aboutdata.addAuthor("Christian Esken",0,"esken@kde.org");
00102 aboutdata.addAuthor("Stefan Westerfeld",I18N_NOOP("Sound support"),"stefan@space.twc.de");
00103 aboutdata.addAuthor("Charles Samuels",I18N_NOOP("Previous Maintainer"),"charles@kde.org");
00104
00105 KCmdLineArgs::init( argc, argv, &aboutdata );
00106 KUniqueApplication::addCmdLineOptions();
00107
00108
00109
00110 if ( !KUniqueApplication::start() ) {
00111 kdDebug() << "Running knotify found" << endl;
00112 return 0;
00113 }
00114
00115 KUniqueApplication app;
00116 app.disableSessionManagement();
00117
00118
00119
00120
00121
00122
00123
00124
00125
00126
00127 KConfigGroup config( KGlobal::config(), "StartProgress" );
00128 KConfig artsKCMConfig( "kcmartsrc" );
00129 artsKCMConfig.setGroup( "Arts" );
00130 bool useArts = artsKCMConfig.readBoolEntry( "StartServer", true );
00131 if (useArts)
00132 useArts = config.readBoolEntry( "Use Arts", useArts );
00133 bool ok = config.readBoolEntry( "Arts Init", true );
00134
00135 if ( useArts && !ok )
00136 {
00137 if ( KMessageBox::questionYesNo(
00138 0L,
00139 i18n("During the previous startup, KNotify crashed while creating "
00140 "Arts::Dispatcher. Do you want to try again or disable "
00141 "aRts sound output?\n\n"
00142 "If you choose to disable aRts output now, you can re-enable "
00143 "it later or select an alternate sound player "
00144 "in the System Notifications control panel."),
00145 i18n("KNotify Problem"),
00146 i18n("&Try Again"),
00147 i18n("D&isable aRts Output"),
00148 "KNotifyStartProgress",
00149 0
00150 )
00151 == KMessageBox::No )
00152 {
00153 useArts = false;
00154 }
00155 }
00156
00157
00158 config.writeEntry( "Arts Init", false );
00159 config.writeEntry( "Use Arts", useArts );
00160 config.sync();
00161
00162 KArtsDispatcher *dispatcher = 0;
00163 if ( useArts )
00164 {
00165 dispatcher = new KArtsDispatcher;
00166 soundServer = new KArtsServer;
00167 }
00168
00169
00170 config.writeEntry("Arts Init", useArts );
00171 config.sync();
00172
00173 ok = config.readBoolEntry( "KNotify Init", true );
00174 if ( useArts && !ok )
00175 {
00176 if ( KMessageBox::questionYesNo(
00177 0L,
00178 i18n("During the previous startup, KNotify crashed while instantiating "
00179 "KNotify. Do you want to try again or disable "
00180 "aRts sound output?\n\n"
00181 "If you choose to disable aRts output now, you can re-enable "
00182 "it later or select an alternate sound player "
00183 "in the System Notifications control panel."),
00184 i18n("KNotify Problem"),
00185 i18n("&Try Again"),
00186 i18n("D&isable aRts Output"),
00187 "KNotifyStartProgress",
00188 0
00189 )
00190 == KMessageBox::No )
00191 {
00192 useArts = false;
00193 delete soundServer;
00194 soundServer = 0L;
00195 delete dispatcher;
00196 dispatcher = 0L;
00197 }
00198 }
00199
00200
00201 config.writeEntry( "KNotify Init", false );
00202 config.writeEntry( "Use Arts", useArts );
00203 config.sync();
00204
00205
00206 KNotify *notify = new KNotify( useArts );
00207
00208 config.writeEntry( "KNotify Init", true );
00209 config.sync();
00210
00211 app.dcopClient()->setDefaultObject( "Notify" );
00212 app.dcopClient()->setDaemonMode( true );
00213
00214
00215 int ret = app.exec();
00216 delete notify;
00217 delete soundServer;
00218 delete dispatcher;
00219 return ret;
00220 }
00221 }
00222
00223 KNotify::KNotify( bool useArts )
00224 : QObject(), DCOPObject("Notify")
00225 {
00226 d = new KNotifyPrivate;
00227 d->globalEvents = new KConfig("knotify/eventsrc", true, false, "data");
00228 d->globalConfig = new KConfig("knotify.eventsrc", true, false);
00229 d->externalPlayerProc = 0;
00230 d->useArts = useArts;
00231 d->playObjects.setAutoDelete(true);
00232 d->audioManager = 0;
00233 d->inStartup = true;
00234 if( useArts )
00235 {
00236 connect( soundServer, SIGNAL( restartedServer() ), this, SLOT( restartedArtsd() ) );
00237 restartedArtsd();
00238 }
00239
00240 d->volume = 100;
00241
00242 d->playTimer = 0;
00243
00244 loadConfig();
00245 }
00246
00247 KNotify::~KNotify()
00248 {
00249 reconfigure();
00250
00251 d->playObjects.clear();
00252
00253 delete d->globalEvents;
00254 delete d->globalConfig;
00255 delete d->externalPlayerProc;
00256 delete d->audioManager;
00257 delete d;
00258 }
00259
00260
00261 void KNotify::loadConfig() {
00262
00263 KConfig *kc = KGlobal::config();
00264 kc->setGroup("Misc");
00265 d->useExternal = kc->readBoolEntry( "Use external player", false );
00266 d->externalPlayer = kc->readPathEntry("External player");
00267
00268
00269 if ( d->externalPlayer.isEmpty() ) {
00270 QStringList players;
00271 players << "wavplay" << "aplay" << "auplay";
00272 QStringList::Iterator it = players.begin();
00273 while ( d->externalPlayer.isEmpty() && it != players.end() ) {
00274 d->externalPlayer = KStandardDirs::findExe( *it );
00275 ++it;
00276 }
00277 }
00278
00279
00280 d->volume = kc->readNumEntry( "Volume", 100 );
00281 }
00282
00283
00284 void KNotify::reconfigure()
00285 {
00286 kapp->config()->reparseConfiguration();
00287 loadConfig();
00288
00289
00290 d->globalConfig->reparseConfiguration();
00291 for ( QMapIterator<QString,KConfig*> it = d->configs.begin(); it != d->configs.end(); ++it )
00292 delete it.data();
00293 d->configs.clear();
00294 }
00295
00296
00297 void KNotify::notify(const QString &event, const QString &fromApp,
00298 const QString &text, QString sound, QString file,
00299 int present, int level)
00300 {
00301 notify( event, fromApp, text, sound, file, present, level, 0, 1 );
00302 }
00303
00304 void KNotify::notify(const QString &event, const QString &fromApp,
00305 const QString &text, QString sound, QString file,
00306 int present, int level, int winId)
00307 {
00308 notify( event, fromApp, text, sound, file, present, level, winId, 1 );
00309 }
00310
00311 void KNotify::notify(const QString &event, const QString &fromApp,
00312 const QString &text, QString sound, QString file,
00313 int present, int level, int winId, int eventId )
00314 {
00315
00316
00317 if( d->inStartup ) {
00318 d->startupEvents += "(" + event + ":" + fromApp + ")";
00319 }
00320
00321 QString commandline;
00322
00323
00324 if ( !event.isEmpty() ) {
00325
00326
00327 KConfig *eventsFile;
00328 KConfig *configFile;
00329 if ( d->events.contains( fromApp ) ) {
00330 eventsFile = d->events[fromApp];
00331 } else {
00332 eventsFile=new KConfig(locate("data", fromApp+"/eventsrc"),true,false);
00333 d->events.insert( fromApp, eventsFile );
00334 }
00335 if ( d->configs.contains( fromApp) ) {
00336 configFile = d->configs[fromApp];
00337 } else {
00338 configFile=new KConfig(fromApp+".eventsrc",true,false);
00339 d->configs.insert( fromApp, configFile );
00340 }
00341
00342 if ( !eventsFile->hasGroup( event ) && isGlobal(event) )
00343 {
00344 eventsFile = d->globalEvents;
00345 configFile = d->globalConfig;
00346 }
00347
00348 eventsFile->setGroup( event );
00349 configFile->setGroup( event );
00350
00351
00352 if ( present==-1 )
00353 present = configFile->readNumEntry( "presentation", -1 );
00354 if ( present==-1 )
00355 present = eventsFile->readNumEntry( "default_presentation", 0 );
00356
00357
00358 if( present & KNotifyClient::Sound ) {
00359 QString theSound = configFile->readPathEntry( "soundfile" );
00360 if ( theSound.isEmpty() )
00361 theSound = eventsFile->readPathEntry( "default_sound" );
00362 if ( !theSound.isEmpty() )
00363 sound = theSound;
00364 }
00365
00366
00367 if( present & KNotifyClient::Logfile ) {
00368 QString theFile = configFile->readPathEntry( "logfile" );
00369 if ( theFile.isEmpty() )
00370 theFile = eventsFile->readPathEntry( "default_logfile" );
00371 if ( !theFile.isEmpty() )
00372 file = theFile;
00373 }
00374
00375
00376 if( present & KNotifyClient::Messagebox )
00377 level = eventsFile->readNumEntry( "level", 0 );
00378
00379
00380 if (present & KNotifyClient::Execute ) {
00381 commandline = configFile->readPathEntry( "commandline" );
00382 if ( commandline.isEmpty() )
00383 commandline = eventsFile->readPathEntry( "default_commandline" );
00384 }
00385 }
00386
00387
00388 if ( present & KNotifyClient::Sound )
00389 notifyBySound( sound, fromApp, eventId );
00390
00391 if ( present & KNotifyClient::PassivePopup )
00392 notifyByPassivePopup( text, fromApp, checkWinId( fromApp, winId ));
00393
00394 else if ( present & KNotifyClient::Messagebox )
00395 notifyByMessagebox( text, level, checkWinId( fromApp, winId ));
00396
00397 if ( present & KNotifyClient::Logfile )
00398 notifyByLogfile( text, file );
00399
00400 if ( present & KNotifyClient::Stderr )
00401 notifyByStderr( text );
00402
00403 if ( present & KNotifyClient::Execute )
00404 notifyByExecute( commandline, event, fromApp, text, winId, eventId );
00405
00406 if ( present & KNotifyClient::Taskbar )
00407 notifyByTaskbar( checkWinId( fromApp, winId ));
00408
00409 QByteArray qbd;
00410 QDataStream ds(qbd, IO_WriteOnly);
00411 ds << event << fromApp << text << sound << file << present << level
00412 << winId << eventId;
00413 emitDCOPSignal("notifySignal(QString,QString,QString,QString,QString,int,int,int,int)", qbd);
00414
00415 }
00416
00417
00418 bool KNotify::notifyBySound( const QString &sound, const QString &appname, int eventId )
00419 {
00420 if (sound.isEmpty()) {
00421 soundFinished( eventId, NoSoundFile );
00422 return false;
00423 }
00424
00425 bool external = d->useExternal && !d->externalPlayer.isEmpty();
00426
00427 QString soundFile(sound);
00428 if ( QFileInfo(sound).isRelative() )
00429 {
00430 QString search = QString("%1/sounds/%2").arg(appname).arg(sound);
00431 soundFile = KGlobal::instance()->dirs()->findResource("data", search);
00432 if ( soundFile.isEmpty() )
00433 soundFile = locate( "sound", sound );
00434 }
00435 if ( soundFile.isEmpty() || isPlaying( soundFile ) )
00436 {
00437 soundFinished( eventId, soundFile.isEmpty() ? NoSoundFile : FileAlreadyPlaying );
00438 return false;
00439 }
00440
00441
00442
00443
00444 if (!external) {
00445
00446
00447 if (!d->useArts)
00448 {
00449 soundFinished( eventId, NoSoundSupport );
00450 return false;
00451 }
00452
00453
00454 while( d->playObjects.count()>5 )
00455 abortFirstPlayObject();
00456
00457 KDE::PlayObjectFactory factory(soundServer->server());
00458 if( d->audioManager )
00459 factory.setAudioManagerPlay( d->audioManager );
00460 KURL soundURL;
00461 soundURL.setPath(soundFile);
00462 KDE::PlayObject *playObject = factory.createPlayObject(soundURL, false);
00463
00464 if (playObject->isNull())
00465 {
00466 soundFinished( eventId, NoSoundSupport );
00467 delete playObject;
00468 return false;
00469 }
00470
00471 if ( d->volume != 100 )
00472 {
00473
00474
00475 Arts::StereoVolumeControl volumeControl = Arts::DynamicCast(soundServer->server().createObject("Arts::StereoVolumeControl"));
00476 Arts::PlayObject player = playObject->object();
00477 Arts::Synth_AMAN_PLAY ap = d->audioManager->amanPlay();
00478 if( ! volumeControl.isNull() && ! player.isNull() && ! ap.isNull() )
00479 {
00480 volumeControl.scaleFactor( d->volume/100.0 );
00481
00482 ap.stop();
00483 player._node()->stop();
00484 Arts::disconnect( player, "left", ap, "left" );
00485 Arts::disconnect( player, "right", ap, "right" );
00486
00487 ap.start();
00488 volumeControl.start();
00489 player._node()->start();
00490
00491 Arts::connect(player,"left",volumeControl,"inleft");
00492 Arts::connect(player,"right",volumeControl,"inright");
00493
00494 Arts::connect(volumeControl,"outleft",ap,"left");
00495 Arts::connect(volumeControl,"outright",ap,"right");
00496
00497 player._addChild( volumeControl, "volume" );
00498 }
00499 }
00500
00501 playObject->play();
00502 d->playObjects.append( playObject );
00503 d->playObjectEventMap.insert( playObject, eventId );
00504
00505 if ( !d->playTimer )
00506 {
00507 d->playTimer = new QTimer( this );
00508 connect( d->playTimer, SIGNAL( timeout() ), SLOT( playTimeout() ) );
00509 }
00510 if ( !d->playTimer->isActive() )
00511 d->playTimer->start( 1000 );
00512
00513 return true;
00514
00515 } else if(!d->externalPlayer.isEmpty()) {
00516
00517 KProcess *proc = d->externalPlayerProc;
00518 if (!proc)
00519 {
00520 proc = d->externalPlayerProc = new KProcess;
00521 connect( proc, SIGNAL( processExited( KProcess * )),
00522 SLOT( slotPlayerProcessExited( KProcess * )));
00523 }
00524 if (proc->isRunning())
00525 {
00526 soundFinished( eventId, PlayerBusy );
00527 return false;
00528 }
00529 proc->clearArguments();
00530 (*proc) << d->externalPlayer << QFile::encodeName( soundFile );
00531 d->externalPlayerEventId = eventId;
00532 proc->start(KProcess::NotifyOnExit);
00533 return true;
00534 }
00535
00536 soundFinished( eventId, Unknown );
00537 return false;
00538 }
00539
00540 bool KNotify::notifyByMessagebox(const QString &text, int level, WId winId)
00541 {
00542
00543 if ( text.isEmpty() )
00544 return false;
00545
00546
00547 switch( level ) {
00548 default:
00549 case KNotifyClient::Notification:
00550 KMessageBox::informationWId( winId, text, i18n("Notification"), 0, false );
00551 break;
00552 case KNotifyClient::Warning:
00553 KMessageBox::sorryWId( winId, text, i18n("Warning"), false );
00554 break;
00555 case KNotifyClient::Error:
00556 KMessageBox::errorWId( winId, text, i18n("Error"), false );
00557 break;
00558 case KNotifyClient::Catastrophe:
00559 KMessageBox::errorWId( winId, text, i18n("Catastrophe!"), false );
00560 break;
00561 }
00562
00563 return true;
00564 }
00565
00566 bool KNotify::notifyByPassivePopup( const QString &text,
00567 const QString &appName,
00568 WId senderWinId )
00569 {
00570 KIconLoader iconLoader( appName );
00571 if ( d->events.find( appName ) != d->events.end() ) {
00572 KConfigGroup config( d->events[ appName ], "!Global!" );
00573 QString iconName = config.readEntry( "IconName", appName );
00574 QPixmap icon = iconLoader.loadIcon( iconName, KIcon::Small );
00575 QString title = config.readEntry( "Comment", appName );
00576 KPassivePopup::message(title, text, icon, senderWinId);
00577 } else
00578 kdError() << "No events for app " << appName << "defined!" <<endl;
00579
00580 return true;
00581 }
00582
00583 bool KNotify::notifyByExecute(const QString &command, const QString& event,
00584 const QString& fromApp, const QString& text,
00585 int winId, int eventId) {
00586 if (!command.isEmpty()) {
00587
00588 QMap<QChar,QString> subst;
00589 subst.insert( 'e', event );
00590 subst.insert( 'a', fromApp );
00591 subst.insert( 's', text );
00592 subst.insert( 'w', QString::number( winId ));
00593 subst.insert( 'i', QString::number( eventId ));
00594 QString execLine = KMacroExpander::expandMacrosShellQuote( command, subst );
00595 if ( execLine.isEmpty() )
00596 execLine = command;
00597
00598 KProcess p;
00599 p.setUseShell(true);
00600 p << execLine;
00601 p.start(KProcess::DontCare);
00602 return true;
00603 }
00604 return false;
00605 }
00606
00607
00608 bool KNotify::notifyByLogfile(const QString &text, const QString &file)
00609 {
00610
00611 if ( text.isEmpty() )
00612 return true;
00613
00614
00615 QFile logFile(file);
00616 if ( !logFile.open(IO_WriteOnly | IO_Append) )
00617 return false;
00618
00619
00620 QTextStream strm( &logFile );
00621 strm << "- KNotify " << QDateTime::currentDateTime().toString() << ": ";
00622 strm << text << endl;
00623
00624
00625 logFile.close();
00626 return true;
00627 }
00628
00629 bool KNotify::notifyByStderr(const QString &text)
00630 {
00631
00632 if ( text.isEmpty() )
00633 return true;
00634
00635
00636 QTextStream strm( stderr, IO_WriteOnly );
00637
00638
00639 strm << "KNotify " << QDateTime::currentDateTime().toString() << ": ";
00640 strm << text << endl;
00641
00642 return true;
00643 }
00644
00645 bool KNotify::notifyByTaskbar( WId win )
00646 {
00647 if( win == 0 )
00648 return false;
00649 KWin::demandAttention( win );
00650 return true;
00651 }
00652
00653 bool KNotify::isGlobal(const QString &eventname)
00654 {
00655 return d->globalEvents->hasGroup( eventname );
00656 }
00657
00658 void KNotify::setVolume( int volume )
00659 {
00660 if ( volume<0 ) volume=0;
00661 if ( volume>=100 ) volume=100;
00662 d->volume = volume;
00663 }
00664
00665 void KNotify::playTimeout()
00666 {
00667 for ( QPtrListIterator< KDE::PlayObject > it(d->playObjects); *it;)
00668 {
00669 QPtrListIterator< KDE::PlayObject > current = it;
00670 ++it;
00671 if ( (*current)->state() != Arts::posPlaying )
00672 {
00673 QMap<KDE::PlayObject*,int>::Iterator eit = d->playObjectEventMap.find( *current );
00674 if ( eit != d->playObjectEventMap.end() )
00675 {
00676 soundFinished( *eit, PlayedOK );
00677 d->playObjectEventMap.remove( eit );
00678 }
00679 d->playObjects.remove( current );
00680 }
00681 }
00682 if ( !d->playObjects.count() )
00683 d->playTimer->stop();
00684 }
00685
00686 bool KNotify::isPlaying( const QString& soundFile ) const
00687 {
00688 for ( QPtrListIterator< KDE::PlayObject > it(d->playObjects); *it; ++it)
00689 {
00690 if ( (*it)->mediaName() == soundFile )
00691 return true;
00692 }
00693
00694 return false;
00695 }
00696
00697 void KNotify::slotPlayerProcessExited( KProcess *proc )
00698 {
00699 soundFinished( d->externalPlayerEventId,
00700 (proc->normalExit() && proc->exitStatus() == 0) ? PlayedOK : Unknown );
00701 }
00702
00703 void KNotify::abortFirstPlayObject()
00704 {
00705 QMap<KDE::PlayObject*,int>::Iterator it = d->playObjectEventMap.find( d->playObjects.getFirst() );
00706 if ( it != d->playObjectEventMap.end() )
00707 {
00708 soundFinished( it.data(), Aborted );
00709 d->playObjectEventMap.remove( it );
00710 }
00711 d->playObjects.removeFirst();
00712 }
00713
00714 void KNotify::soundFinished( int eventId, PlayingFinishedStatus reason )
00715 {
00716 QByteArray data;
00717 QDataStream stream( data, IO_WriteOnly );
00718 stream << eventId << (int) reason;
00719
00720 DCOPClient::mainClient()->emitDCOPSignal( "KNotify", "playingFinished(int,int)", data );
00721 }
00722
00723 WId KNotify::checkWinId( const QString &appName, WId senderWinId )
00724 {
00725 if ( senderWinId == 0 )
00726 {
00727 QCString senderId = kapp->dcopClient()->senderId();
00728 QCString compare = (appName + "-mainwindow").latin1();
00729 int len = compare.length();
00730
00731
00732 QCStringList objs = kapp->dcopClient()->remoteObjects( senderId );
00733 for (QCStringList::ConstIterator it = objs.begin(); it != objs.end(); it++ ) {
00734 QCString obj( *it );
00735 if ( obj.left(len) == compare) {
00736
00737 QCString replyType;
00738 QByteArray data, replyData;
00739
00740 if ( kapp->dcopClient()->call(senderId, obj, "getWinID()", data, replyType, replyData) ) {
00741 QDataStream answer(replyData, IO_ReadOnly);
00742 if (replyType == "int") {
00743 answer >> senderWinId;
00744
00745
00746 }
00747 }
00748 }
00749 }
00750 }
00751 return senderWinId;
00752 }
00753
00754 void KNotify::restartedArtsd()
00755 {
00756 delete d->audioManager;
00757 d->audioManager = new KAudioManagerPlay( soundServer );
00758 d->audioManager->setTitle( i18n( "KDE System Notifications" ) );
00759 d->audioManager->setAutoRestoreID( "KNotify Aman Play" );
00760 }
00761
00762 void KNotify::sessionReady()
00763 {
00764 if( d->inStartup && !d->startupEvents.isEmpty())
00765 kdDebug() << "There were knotify events while startup:" << d->startupEvents << endl;
00766 d->inStartup = false;
00767 }
00768
00769