arts Library API Documentation

knotify.cpp

00001 /*
00002    Copyright (c) 1997 Christian Esken (esken@kde.org)
00003                  2000 Charles Samuels (charles@kde.org)
00004                  2000 Stefan Schimanski (1Stein@gmx.de)
00005                  2000 Matthias Ettrich (ettrich@kde.org)
00006                  2000 Waldo Bastian <bastian@kde.org>
00007                  2000-2003 Carsten Pfeiffer <pfeiffer@kde.org>
00008          2004 Allan Sandfeld Jensen <kde@carewolf.com>
00009 
00010    This program is free software; you can redistribute it and/or modify
00011    it under the terms of the GNU General Public License as published by
00012    the Free Software Foundation; either version 2, or (at your option)
00013    any later version.
00014 
00015    This program is distributed in the hope that it will be useful,
00016    but WITHOUT ANY WARRANTY; without even the implied warranty of
00017    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00018    GNU General Public License for more details.
00019 
00020    You should have received a copy of the GNU General Public License
00021    along with this program; if not, write to the Free Software
00022    Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
00023 */
00024 
00025 // C headers
00026 #include <fcntl.h>
00027 #include <sys/types.h>
00028 #include <sys/stat.h>
00029 
00030 // QT headers
00031 #include <qfile.h>
00032 #include <qfileinfo.h>
00033 #include <qiomanager.h>
00034 #include <qstringlist.h>
00035 #include <qtextstream.h>
00036 
00037 // KDE headers
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 // Yes, it's ugly to put this here, but this facilitates the cautious startup
00083 // procedure.
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     // initialize application
00104     if ( !KUniqueApplication::start() ) {
00105         kdDebug() << "Running knotify found" << endl;
00106         return 0;
00107     }
00108 
00109     KUniqueApplication app;
00110     app.disableSessionManagement();
00111 
00112     // start notify service
00113     KNotify notify( true );
00114 
00115     app.dcopClient()->setDefaultObject( "Notify" );
00116     app.dcopClient()->setDaemonMode( true );
00117     // kdDebug() << "knotify starting" << endl;
00118 
00119     int ret = app.exec();
00120     return ret;
00121 }
00122 }// end extern "C"
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     // load external player settings
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     // try to locate a suitable player if none is configured
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     // load default volume
00174     d->volume = kc->readNumEntry( "Volume", 100 );
00175 }
00176 
00177 
00178 void KNotify::reconfigure()
00179 {
00180     kapp->config()->reparseConfiguration();
00181     loadConfig();
00182 
00183     // clear loaded config files
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     // kdDebug() << "event=" << event << " fromApp=" << fromApp << " text=" << text << " sound=" << sound <<
00210     //    " file=" << file << " present=" << present << " level=" << level <<  " winId=" << winId << " eventId=" << eventId << endl;
00211     if( d->inStartup ) {
00212         d->startupEvents += "(" + event + ":" + fromApp + ")";
00213     }
00214 
00215     QString commandline;
00216 
00217     // check for valid events
00218     if ( !event.isEmpty() ) {
00219 
00220         // get config file
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         // get event presentation
00246         if ( present==-1 )
00247             present = configFile->readNumEntry( "presentation", -1 );
00248         if ( present==-1 )
00249             present = eventsFile->readNumEntry( "default_presentation", 0 );
00250 
00251         // get sound file name
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         // get log file name
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         // get default event level
00270         if( present & KNotifyClient::Messagebox )
00271             level = eventsFile->readNumEntry( "level", 0 );
00272 
00273         // get command line
00274         if (present & KNotifyClient::Execute ) {
00275             commandline = configFile->readPathEntry( "commandline" );
00276             if ( commandline.isEmpty() )
00277                 commandline = eventsFile->readPathEntry( "default_commandline" );
00278         }
00279     }
00280 
00281     // emit event
00282     if ( present & KNotifyClient::Sound ) // && QFile(sound).isReadable()
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 ) // && QFile(file).isWritable()
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     // get file name
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     // kdDebug() << "KNotify::notifyBySound - trying to play file " << soundFile << endl;
00336 
00337     if (!external) {
00338         //If we disabled audio, just return,
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         // use an external player to play the sound
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; // Skip
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     // ignore empty messages
00388     if ( text.isEmpty() )
00389         return false;
00390 
00391     // display message box for specified event level
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     // kdDebug() << "executing command '" << command << "'" << endl;
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; // fallback
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     // ignore empty messages
00456     if ( text.isEmpty() )
00457         return true;
00458 
00459     // open file in append mode
00460     QFile logFile(file);
00461     if ( !logFile.open(IO_WriteOnly | IO_Append) )
00462         return false;
00463 
00464     // append msg
00465     QTextStream strm( &logFile );
00466     strm << "- KNotify " << QDateTime::currentDateTime().toString() << ": ";
00467     strm << text << endl;
00468 
00469     // close file
00470     logFile.close();
00471     return true;
00472 }
00473 
00474 bool KNotify::notifyByStderr(const QString &text)
00475 {
00476     // ignore empty messages
00477     if ( text.isEmpty() )
00478         return true;
00479 
00480     // open stderr for output
00481     QTextStream strm( stderr, IO_WriteOnly );
00482 
00483     // output msg
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 ) // may be "playing" even if there's an error
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         // kdDebug() << "notifyByPassivePopup: appName=" << appName << " sender=" << senderId << endl;
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                 // kdDebug( ) << "found " << obj << endl;
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                         // kdDebug() << "SUCCESS, found getWinID(): type='" << QString(replyType)
00576                         //      << "' senderWinId=" << senderWinId << endl;
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 // vim: sw=4 sts=4 ts=8 et
KDE Logo
This file is part of the documentation for arts Library Version 3.4.2.
Documentation copyright © 1996-2004 the KDE developers.
Generated on Tue Sep 13 04:03:37 2005 by doxygen 1.4.4 written by Dimitri van Heesch, © 1997-2003