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