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