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
00026 #include <fcntl.h>
00027 #include <sys/types.h>
00028 #include <sys/stat.h>
00029
00030
00031 #include <qfile.h>
00032 #include <qfileinfo.h>
00033 #include <qiomanager.h>
00034 #include <qstringlist.h>
00035 #include <qtextstream.h>
00036
00037
00038 #include <dcopclient.h>
00039 #include <kaboutdata.h>
00040 #include <kartsdispatcher.h>
00041 #include <kartsserver.h>
00042 #include <kcmdlineargs.h>
00043 #include <kconfig.h>
00044 #include <kdebug.h>
00045 #include <kglobal.h>
00046 #include <klocale.h>
00047 #include <kmessagebox.h>
00048 #include <kpassivepopup.h>
00049 #include <kiconloader.h>
00050 #include <kmacroexpander.h>
00051 #include <kplayobjectfactory.h>
00052 #include <kaudiomanagerplay.h>
00053 #include <kprocess.h>
00054 #include <kstandarddirs.h>
00055 #include <kuniqueapplication.h>
00056 #include <kwin.h>
00057
00058 #include "knotify.h"
00059 #include "knotify.moc"
00060
00061 class KNotifyPrivate
00062 {
00063 public:
00064 KConfig* globalEvents;
00065 KConfig* globalConfig;
00066 QMap<QString, KConfig*> events;
00067 QMap<QString, KConfig*> configs;
00068 QString externalPlayer;
00069 KProcess *externalPlayerProc;
00070
00071 QMap<KDE::Multimedia::SimplePlayer*,int> playObjectEventMap;
00072 int externalPlayerEventId;
00073
00074 bool useExternal;
00075 bool useKDEMM;
00076 int volume;
00077 QTimer *playTimer;
00078 bool inStartup;
00079 QString startupEvents;
00080 };
00081
00082
00083
00084 KArtsServer *soundServer = 0;
00085
00086 extern "C"{
00087
00088 KDE_EXPORT int kdemain(int argc, char **argv)
00089 {
00090 KAboutData aboutdata("knotify", I18N_NOOP("KNotify"),
00091 "4.0", I18N_NOOP("KDE Notification Server"),
00092 KAboutData::License_GPL, "(C) 1997-2003, KDE Developers");
00093 aboutdata.addAuthor("Carsten Pfeiffer",I18N_NOOP("Current Maintainer"),"pfeiffer@kde.org");
00094 aboutdata.addAuthor("Christian Esken",0,"esken@kde.org");
00095 aboutdata.addAuthor("Stefan Westerfeld",I18N_NOOP("Sound support"),"stefan@space.twc.de");
00096 aboutdata.addAuthor("Charles Samuels",I18N_NOOP("Previous Maintainer"),"charles@kde.org");
00097 aboutdata.addAuthor("Allan Sandfeld Jensen",I18N_NOOP("Conversion to KDEMM"),"kde@carewolf.com");
00098
00099 KCmdLineArgs::init( argc, argv, &aboutdata );
00100 KUniqueApplication::addCmdLineOptions();
00101
00102
00103
00104 if ( !KUniqueApplication::start() ) {
00105 kdDebug() << "Running knotify found" << endl;
00106 return 0;
00107 }
00108
00109 KUniqueApplication app;
00110 app.disableSessionManagement();
00111
00112
00113 KNotify notify( true );
00114
00115 app.dcopClient()->setDefaultObject( "Notify" );
00116 app.dcopClient()->setDaemonMode( true );
00117
00118
00119 int ret = app.exec();
00120 return ret;
00121 }
00122 }
00123
00124 KNotify::KNotify( bool useKDEMM )
00125 : QObject(), DCOPObject("Notify")
00126 {
00127 d = new KNotifyPrivate;
00128 d->globalEvents = new KConfig("knotify/eventsrc", true, false, "data");
00129 d->globalConfig = new KConfig("knotify.eventsrc", true, false);
00130 d->externalPlayerProc = 0;
00131 d->useKDEMM = useKDEMM;
00132
00133 d->inStartup = 0;
00134 d->volume = 100;
00135
00136 d->playTimer = 0;
00137
00138 loadConfig();
00139
00140 connect ( this, SIGNAL(deletePlayObject(KDE::Multimedia::SimplePlayer*)),
00141 SLOT(objectDeleter(KDE::Multimedia::SimplePlayer*)) );
00142 }
00143
00144 KNotify::~KNotify()
00145 {
00146 reconfigure();
00147
00148 delete d->globalEvents;
00149 delete d->globalConfig;
00150 delete d->externalPlayerProc;
00151 delete d;
00152 }
00153
00154
00155 void KNotify::loadConfig() {
00156
00157 KConfig *kc = KGlobal::config();
00158 kc->setGroup("Misc");
00159 d->useExternal = kc->readBoolEntry( "Use external player", false );
00160 d->externalPlayer = kc->readPathEntry("External player");
00161
00162
00163 if ( d->externalPlayer.isEmpty() ) {
00164 QStringList players;
00165 players << "wavplay" << "aplay" << "auplay" << "artsplay" << "akodeplay";
00166 QStringList::Iterator it = players.begin();
00167 while ( d->externalPlayer.isEmpty() && it != players.end() ) {
00168 d->externalPlayer = KStandardDirs::findExe( *it );
00169 ++it;
00170 }
00171 }
00172
00173
00174 d->volume = kc->readNumEntry( "Volume", 100 );
00175 }
00176
00177
00178 void KNotify::reconfigure()
00179 {
00180 kapp->config()->reparseConfiguration();
00181 loadConfig();
00182
00183
00184 d->globalConfig->reparseConfiguration();
00185 for ( QMapIterator<QString,KConfig*> it = d->configs.begin(); it != d->configs.end(); ++it )
00186 delete it.data();
00187 d->configs.clear();
00188 }
00189
00190
00191 void KNotify::notify(const QString &event, const QString &fromApp,
00192 const QString &text, QString sound, QString file,
00193 int present, int level)
00194 {
00195 notify( event, fromApp, text, sound, file, present, level, 0, 1 );
00196 }
00197
00198 void KNotify::notify(const QString &event, const QString &fromApp,
00199 const QString &text, QString sound, QString file,
00200 int present, int level, int winId)
00201 {
00202 notify( event, fromApp, text, sound, file, present, level, winId, 1 );
00203 }
00204
00205 void KNotify::notify(const QString &event, const QString &fromApp,
00206 const QString &text, QString sound, QString file,
00207 int present, int level, int winId, int eventId )
00208 {
00209
00210
00211 if( d->inStartup ) {
00212 d->startupEvents += "(" + event + ":" + fromApp + ")";
00213 }
00214
00215 QString commandline;
00216
00217
00218 if ( !event.isEmpty() ) {
00219
00220
00221 KConfig *eventsFile;
00222 KConfig *configFile;
00223 if ( d->events.contains( fromApp ) ) {
00224 eventsFile = d->events[fromApp];
00225 } else {
00226 eventsFile=new KConfig(locate("data", fromApp+"/eventsrc"),true,false);
00227 d->events.insert( fromApp, eventsFile );
00228 }
00229 if ( d->configs.contains( fromApp) ) {
00230 configFile = d->configs[fromApp];
00231 } else {
00232 configFile=new KConfig(fromApp+".eventsrc",true,false);
00233 d->configs.insert( fromApp, configFile );
00234 }
00235
00236 if ( !eventsFile->hasGroup( event ) && isGlobal(event) )
00237 {
00238 eventsFile = d->globalEvents;
00239 configFile = d->globalConfig;
00240 }
00241
00242 eventsFile->setGroup( event );
00243 configFile->setGroup( event );
00244
00245
00246 if ( present==-1 )
00247 present = configFile->readNumEntry( "presentation", -1 );
00248 if ( present==-1 )
00249 present = eventsFile->readNumEntry( "default_presentation", 0 );
00250
00251
00252 if( present & KNotifyClient::Sound ) {
00253 QString theSound = configFile->readPathEntry( "soundfile" );
00254 if ( theSound.isEmpty() )
00255 theSound = eventsFile->readPathEntry( "default_sound" );
00256 if ( !theSound.isEmpty() )
00257 sound = theSound;
00258 }
00259
00260
00261 if( present & KNotifyClient::Logfile ) {
00262 QString theFile = configFile->readPathEntry( "logfile" );
00263 if ( theFile.isEmpty() )
00264 theFile = eventsFile->readPathEntry( "default_logfile" );
00265 if ( !theFile.isEmpty() )
00266 file = theFile;
00267 }
00268
00269
00270 if( present & KNotifyClient::Messagebox )
00271 level = eventsFile->readNumEntry( "level", 0 );
00272
00273
00274 if (present & KNotifyClient::Execute ) {
00275 commandline = configFile->readPathEntry( "commandline" );
00276 if ( commandline.isEmpty() )
00277 commandline = eventsFile->readPathEntry( "default_commandline" );
00278 }
00279 }
00280
00281
00282 if ( present & KNotifyClient::Sound )
00283 notifyBySound( sound, fromApp, eventId );
00284
00285 if ( present & KNotifyClient::Execute )
00286 notifyByExecute( commandline, event, fromApp, text, winId, eventId );
00287
00288 if ( present & KNotifyClient::Logfile )
00289 notifyByLogfile( text, file );
00290
00291 if ( present & KNotifyClient::Stderr )
00292 notifyByStderr( text );
00293
00294 if ( present & KNotifyClient::Taskbar )
00295 notifyByTaskbar( checkWinId( fromApp, winId ));
00296
00297 if ( present & KNotifyClient::PassivePopup )
00298 notifyByPassivePopup( text, fromApp, checkWinId( fromApp, winId ));
00299 else if ( present & KNotifyClient::Messagebox )
00300 notifyByMessagebox( text, level, checkWinId( fromApp, winId ));
00301
00302 QByteArray qbd;
00303 QDataStream ds(qbd, IO_WriteOnly);
00304 ds << event << fromApp << text << sound << file << present << level
00305 << winId << eventId;
00306 emitDCOPSignal("notifySignal(QString,QString,QString,QString,QString,int,int,int,int)", qbd);
00307
00308 }
00309
00310
00311 bool KNotify::notifyBySound( const QString &sound, const QString &appname, int eventId )
00312 {
00313 if (sound.isEmpty()) {
00314 soundFinished( eventId, NoSoundFile );
00315 return false;
00316 }
00317
00318 bool external = d->useExternal && !d->externalPlayer.isEmpty();
00319
00320 QString soundFile(sound);
00321 if ( QFileInfo(sound).isRelative() )
00322 {
00323 QString search = QString("%1/sounds/%2").arg(appname).arg(sound);
00324 soundFile = KGlobal::instance()->dirs()->findResource("data", search);
00325 if ( soundFile.isEmpty() )
00326 soundFile = locate( "sound", sound );
00327 }
00328 if ( soundFile.isEmpty() )
00329 {
00330 soundFinished( eventId, NoSoundFile );
00331 return false;
00332 }
00333
00334
00335
00336
00337 if (!external) {
00338
00339 if (!d->useKDEMM)
00340 {
00341 soundFinished( eventId, NoSoundSupport );
00342 return false;
00343 }
00344
00345 KURL soundURL;
00346 soundURL.setPath(soundFile);
00347 KDE::Multimedia::SimplePlayer* playObject = new KDE::Multimedia::SimplePlayer( this );
00348 d->playObjectEventMap.insert( playObject, eventId );
00349 playObject->play( soundURL );
00350
00351 if ( !d->playTimer )
00352 {
00353 d->playTimer = new QTimer( this );
00354 connect( d->playTimer, SIGNAL( timeout() ), SLOT( playTimeout() ) );
00355 }
00356 if ( !d->playTimer->isActive() )
00357 d->playTimer->start( 1000 );
00358 return playObject->isPlaying();
00359
00360 } else if(!d->externalPlayer.isEmpty()) {
00361
00362 KProcess *proc = d->externalPlayerProc;
00363 if (!proc)
00364 {
00365 proc = d->externalPlayerProc = new KProcess;
00366 connect( proc, SIGNAL( processExited( KProcess * )),
00367 SLOT( slotPlayerProcessExited( KProcess * )));
00368 }
00369 if (proc->isRunning())
00370 {
00371 soundFinished( eventId, PlayerBusy );
00372 return false;
00373 }
00374 proc->clearArguments();
00375 (*proc) << d->externalPlayer << QFile::encodeName( soundFile );
00376 d->externalPlayerEventId = eventId;
00377 proc->start(KProcess::NotifyOnExit);
00378 return true;
00379 }
00380
00381 soundFinished( eventId, Unknown );
00382 return false;
00383 }
00384
00385 bool KNotify::notifyByMessagebox(const QString &text, int level, WId winId)
00386 {
00387
00388 if ( text.isEmpty() )
00389 return false;
00390
00391
00392 switch( level ) {
00393 default:
00394 case KNotifyClient::Notification:
00395 KMessageBox::informationWId( winId, text, i18n("Notification"), 0, false );
00396 break;
00397 case KNotifyClient::Warning:
00398 KMessageBox::sorryWId( winId, text, i18n("Warning"), false );
00399 break;
00400 case KNotifyClient::Error:
00401 KMessageBox::errorWId( winId, text, i18n("Error"), false );
00402 break;
00403 case KNotifyClient::Catastrophe:
00404 KMessageBox::errorWId( winId, text, i18n("Catastrophe!"), false );
00405 break;
00406 }
00407
00408 return true;
00409 }
00410
00411 bool KNotify::notifyByPassivePopup( const QString &text,
00412 const QString &appName,
00413 WId senderWinId )
00414 {
00415 KIconLoader iconLoader( appName );
00416 if ( d->events.find( appName ) != d->events.end() ) {
00417 KConfigGroup config( d->events[ appName ], "!Global!" );
00418 QString iconName = config.readEntry( "IconName", appName );
00419 QPixmap icon = iconLoader.loadIcon( iconName, KIcon::Small );
00420 QString title = config.readEntry( "Comment", appName );
00421 KPassivePopup::message(title, text, icon, senderWinId);
00422 } else
00423 kdError() << "No events for app " << appName << "defined!" <<endl;
00424
00425 return true;
00426 }
00427
00428 bool KNotify::notifyByExecute(const QString &command, const QString& event,
00429 const QString& fromApp, const QString& text,
00430 int winId, int eventId) {
00431 if (!command.isEmpty()) {
00432
00433 QMap<QChar,QString> subst;
00434 subst.insert( 'e', event );
00435 subst.insert( 'a', fromApp );
00436 subst.insert( 's', text );
00437 subst.insert( 'w', QString::number( winId ));
00438 subst.insert( 'i', QString::number( eventId ));
00439 QString execLine = KMacroExpander::expandMacrosShellQuote( command, subst );
00440 if ( execLine.isEmpty() )
00441 execLine = command;
00442
00443 KProcess p;
00444 p.setUseShell(true);
00445 p << execLine;
00446 p.start(KProcess::DontCare);
00447 return true;
00448 }
00449 return false;
00450 }
00451
00452
00453 bool KNotify::notifyByLogfile(const QString &text, const QString &file)
00454 {
00455
00456 if ( text.isEmpty() )
00457 return true;
00458
00459
00460 QFile logFile(file);
00461 if ( !logFile.open(IO_WriteOnly | IO_Append) )
00462 return false;
00463
00464
00465 QTextStream strm( &logFile );
00466 strm << "- KNotify " << QDateTime::currentDateTime().toString() << ": ";
00467 strm << text << endl;
00468
00469
00470 logFile.close();
00471 return true;
00472 }
00473
00474 bool KNotify::notifyByStderr(const QString &text)
00475 {
00476
00477 if ( text.isEmpty() )
00478 return true;
00479
00480
00481 QTextStream strm( stderr, IO_WriteOnly );
00482
00483
00484 strm << "KNotify " << QDateTime::currentDateTime().toString() << ": ";
00485 strm << text << endl;
00486
00487 return true;
00488 }
00489
00490 bool KNotify::notifyByTaskbar( WId win )
00491 {
00492 if( win == 0 )
00493 return false;
00494 KWin::demandAttention( win );
00495 return true;
00496 }
00497
00498 bool KNotify::isGlobal(const QString &eventname)
00499 {
00500 return d->globalEvents->hasGroup( eventname );
00501 }
00502
00503 void KNotify::setVolume( int volume )
00504 {
00505 if ( volume<0 ) volume=0;
00506 if ( volume>=100 ) volume=100;
00507 d->volume = volume;
00508 }
00509
00510 void KNotify::slotPlayerProcessExited( KProcess *proc )
00511 {
00512 soundFinished( d->externalPlayerEventId,
00513 (proc->normalExit() && proc->exitStatus() == 0) ? PlayedOK : Unknown );
00514 }
00515
00516
00517 void KNotify::playTimeout()
00518 {
00519 qDebug("KNotify::playTimeout");
00520 for( QMap< KDE::Multimedia::SimplePlayer*, int >::Iterator it = d->playObjectEventMap.begin();
00521 it != d->playObjectEventMap.end();
00522 )
00523 {
00524 QMap< KDE::Multimedia::SimplePlayer*, int >::Iterator current = it;
00525 ++it;
00526 KDE::Multimedia::SimplePlayer* playObject = current.key();
00527 if ( !playObject->isPlaying() || playObject->totalTime() <= 0 )
00528 {
00529 soundFinished( *current, PlayedOK );
00530 d->playObjectEventMap.remove( current );
00531 disconnect( playObject, SIGNAL( finished() ) );
00532 playObject->stop();
00533 emit deletePlayObject(playObject);
00534 }
00535 }
00536 if ( !d->playObjectEventMap.count() )
00537 d->playTimer->stop();
00538 }
00539
00540 void KNotify::objectDeleter( KDE::Multimedia::SimplePlayer *playObject )
00541 {
00542 delete playObject;
00543 }
00544
00545 void KNotify::soundFinished( int eventId, PlayingFinishedStatus reason )
00546 {
00547 QByteArray data;
00548 QDataStream stream( data, IO_WriteOnly );
00549 stream << eventId << (int) reason;
00550
00551 DCOPClient::mainClient()->emitDCOPSignal( "KNotify", "playingFinished(int,int)", data );
00552 }
00553
00554 WId KNotify::checkWinId( const QString &appName, WId senderWinId )
00555 {
00556 if ( senderWinId == 0 )
00557 {
00558 QCString senderId = kapp->dcopClient()->senderId();
00559 QCString compare = (appName + "-mainwindow").latin1();
00560 int len = compare.length();
00561
00562
00563 QCStringList objs = kapp->dcopClient()->remoteObjects( senderId );
00564 for (QCStringList::ConstIterator it = objs.begin(); it != objs.end(); it++ ) {
00565 QCString obj( *it );
00566 if ( obj.left(len) == compare) {
00567
00568 QCString replyType;
00569 QByteArray data, replyData;
00570
00571 if ( kapp->dcopClient()->call(senderId, obj, "getWinID()", data, replyType, replyData) ) {
00572 QDataStream answer(replyData, IO_ReadOnly);
00573 if (replyType == "int") {
00574 answer >> senderWinId;
00575
00576
00577 }
00578 }
00579 }
00580 }
00581 }
00582 return senderWinId;
00583 }
00584
00585 void KNotify::sessionReady()
00586 {
00587 if( d->inStartup && !d->startupEvents.isEmpty())
00588 kdDebug() << "There were knotify events while startup:" << d->startupEvents << endl;
00589 d->inStartup = false;
00590 }
00591
00592