# kdecore Library API Documentation

Overview | Class Hierarchy | Classes | Classes (annotated) | Members | Source Files

# kstartupinfo.cpp

    00001 /****************************************************************************
    00002
    00003  $Id: kstartupinfo.cpp,v 1.32.2.5 2003/02/25 08:05:12 lunakl Exp $
    00004
    00005  Copyright (C) 2001 Lubos Lunak        <l.lunak@kde.org>
    00006
    00007 Permission is hereby granted, free of charge, to any person obtaining a
    00008 copy of this software and associated documentation files (the "Software"),
    00009 to deal in the Software without restriction, including without limitation
    00010 the rights to use, copy, modify, merge, publish, distribute, sublicense,
    00011 and/or sell copies of the Software, and to permit persons to whom the
    00012 Software is furnished to do so, subject to the following conditions:
    00013
    00014 The above copyright notice and this permission notice shall be included in
    00015 all copies or substantial portions of the Software.
    00016
    00017 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    00018 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    00019 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
    00020 THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    00021 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
    00022 FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
    00023 DEALINGS IN THE SOFTWARE.
    00024
    00025 ****************************************************************************/
    00026
    00027 // kdDebug() can't be turned off in kdeinit
    00028 #if 0
    00029 #define KSTARTUPINFO_ALL_DEBUG
    00030 #endif
    00031
    00032 #include <qwidget.h>
    00033 #ifdef Q_WS_X11 // FIXME(E): Re-implement in a less X11 specific way
    00034 #include <qglobal.h>
    00035 #ifdef HAVE_CONFIG_H
    00036 #include <config.h>
    00037 #endif
    00038
    00039 // need to resolve INT32(qglobal.h)<>INT32(Xlibint.h) conflict
    00040 #ifndef QT_CLEAN_NAMESPACE
    00041 #define QT_CLEAN_NAMESPACE
    00042 #endif
    00043
    00044 #include "kstartupinfo.h"
    00045
    00046 #include <unistd.h>
    00047 #include <sys/time.h>
    00048 #include <stdlib.h>
    00049 #include <qtimer.h>
    00050 #include <netwm.h>
    00051 #include <kdebug.h>
    00052 #include <kapplication.h>
    00053 #include <signal.h>
    00054 #include <kwinmodule.h>
    00055 #include <kxmessages.h>
    00056 #ifndef KDE_USE_FINAL
    00057 #include <X11/Xlibint.h> // cannot be included multiple times
    00058 #endif
    00059
    00060 #undef Data // crappy X11
    00061
    00062 #ifndef None  // CHECKME
    00063 #define None 0
    00064 #endif
    00065
    00066 static const char* const KDE_STARTUP_INFO = "_KDE_STARTUP_INFO";
    00067 static const char* const KDE_STARTUP_ID = "_KDE_STARTUP_ID";
    00068 // KDE_STARTUP_ENV is used also in kinit/wrapper.c
    00069 static const char* const KDE_STARTUP_ENV = "KDE_STARTUP_ENV";
    00070
    00071 // TODO these two are for backward compatibility with KDE2.x
    00072 static const char* const KDE_STARTUP_INFO_2 = "KDE_STARTUP_INFO";
    00073 static const char* const KDE_STARTUP_ID_2 = "KDE_STARTUP_ID";
    00074
    00075 static int get_num( const QString& item_P );
    00076 static QString get_str( const QString& item_P );
    00077 static QCString get_cstr( const QString& item_P );
    00078 static QStringList get_fields( const QString& txt_P );
    00079 static QString escape_str( const QString& str_P );
    00080
    00081 class KStartupInfo::Data
    00082     : public KStartupInfoData
    00083     {
    00084     public:
    00085         Data() {}; // just because it's in a QMap
    00086         Data( const QString& txt_P )
    00087             : KStartupInfoData( txt_P ), age( 0 ) {};
    00088         unsigned int age;
    00089     };
    00090
    00091 struct KStartupInfoPrivate
    00092     {
    00093     public:
    00094         QMap< KStartupInfoId, KStartupInfo::Data > startups;
    00095         // contains silenced ASN's only if !AnnounceSilencedChanges
    00096         QMap< KStartupInfoId, KStartupInfo::Data > silent_startups;
    00097         KWinModule* wm_module;
    00098         KXMessages msgs;
    00099         KXMessages msgs_2;
    00100         QTimer* cleanup;
    00101         int flags;
    00102         KStartupInfoPrivate( int flags_P )
    00103             : msgs( KDE_STARTUP_INFO ), msgs_2( KDE_STARTUP_INFO_2 ),
    00104               flags( flags_P ) {}
    00105     };
    00106
    00107 KStartupInfo::KStartupInfo( int flags_P, QObject* parent_P, const char* name_P )
    00108     : QObject( parent_P, name_P ),
    00109         timeout( 60 ), d( NULL )
    00110     {
    00111     init( flags_P );
    00112     }
    00113
    00114 KStartupInfo::KStartupInfo( bool clean_on_cantdetect_P, QObject* parent_P, const char* name_P )
    00115     : QObject( parent_P, name_P ),
    00116         timeout( 60 ), d( NULL )
    00117     {
    00118     init( clean_on_cantdetect_P ? CleanOnCantDetect : 0 );
    00119     }
    00120
    00121 void KStartupInfo::init( int flags_P )
    00122     {
    00123     // d == NULL means "disabled"
    00124     if (!KApplication::kApplication()) return;
    00125     if (!KApplication::kApplication()->getDisplay()) return;
    00126
    00127     d = new KStartupInfoPrivate( flags_P );
    00128     if( !( d->flags & DisableKWinModule ))
    00129         {
    00130         d->wm_module = new KWinModule( this );
    00131         connect( d->wm_module, SIGNAL( windowAdded( WId )), SLOT( slot_window_added( WId )));
    00132         connect( d->wm_module, SIGNAL( systemTrayWindowAdded( WId )), SLOT( slot_window_added( WId )));
    00133         }
    00134     else
    00135         d->wm_module = NULL;
    00136     connect( &d->msgs, SIGNAL( gotMessage( const QString& )), SLOT( got_message( const QString& )));
    00137     connect( &d->msgs_2, SIGNAL( gotMessage( const QString& )), SLOT( got_message( const QString& )));
    00138     d->cleanup = new QTimer( this );
    00139     connect( d->cleanup, SIGNAL( timeout()), SLOT( startups_cleanup()));
    00140     }
    00141
    00142 KStartupInfo::~KStartupInfo()
    00143     {
    00144     delete d;
    00145     }
    00146
    00147 void KStartupInfo::got_message( const QString& msg_P )
    00148     {
    00149     kdDebug( 172 ) << "got:" << msg_P << endl;
    00150     QString msg = msg_P.stripWhiteSpace();
    00151     if( msg.startsWith( "new:" )) // must match length below
    00152         got_startup_info( msg.mid( 4 ), false );
    00153     else if( msg.startsWith( "change:" )) // must match length below
    00154         got_startup_info( msg.mid( 7 ), true );
    00155     else if( msg.startsWith( "remove:" )) // must match length below
    00156         got_remove_startup_info( msg.mid( 7 ));
    00157     }
    00158
    00159 // if the application stops responding for a while, KWinModule may get
    00160 // the information about the already mapped window before KXMessages
    00161 // actually gets the info about the started application (depends
    00162 // on their order in X11 event filter in KApplication)
    00163 // simply delay info from KWinModule a bit
    00164 namespace
    00165 {
    00166 class DelayedWindowEvent
    00167     : public QCustomEvent
    00168     {
    00169     public:
    00170         DelayedWindowEvent( WId w_P )
    00171             : QCustomEvent( QEvent::User + 15 ), w( w_P ) {}
    00172         Window w;
    00173     };
    00174 }
    00175
    00176 void KStartupInfo::slot_window_added( WId w_P )
    00177     {
    00178     kapp->postEvent( this, new DelayedWindowEvent( w_P ));
    00179     }
    00180
    00181 void KStartupInfo::customEvent( QCustomEvent* e_P )
    00182     {
    00183     if( e_P->type() == QEvent::User + 15 )
    00184         window_added( static_cast< DelayedWindowEvent* >( e_P )->w );
    00185     else
    00186         QObject::customEvent( e_P );
    00187     }
    00188
    00189 void KStartupInfo::window_added( WId w_P )
    00190     {
    00191     KStartupInfoId id;
    00192     KStartupInfoData data;
    00193     startup_t ret = check_startup_internal( w_P, &id, &data, false );
    00194     switch( ret )
    00195         {
    00196         case Match:
    00197             kdDebug( 172 ) << "new window match" << endl;
    00198             // HACK
    00199             // with KDE's spec, first matching window ends ASN
    00200             // but when silenced, ASN shouldn't be ended
    00201             // TODO this shouldn't be needed with the new spec?
    00202             if( data.silent() != KStartupInfoData::Yes )
    00203                 remove_startup_info_internal( id );
    00204           break;
    00205         case NoMatch:
    00206           break; // nothing
    00207         case CantDetect:
    00208             if( d->flags & CleanOnCantDetect )
    00209                 clean_all_noncompliant();
    00210           break;
    00211         }
    00212     }
    00213
    00214 void KStartupInfo::got_startup_info( const QString& msg_P, bool update_only_P )
    00215     {
    00216     KStartupInfoId id( msg_P );
    00217     if( id.none())
    00218         return;
    00219     KStartupInfo::Data data( msg_P );
    00220     new_startup_info_internal( id, data, update_only_P );
    00221     }
    00222
    00223 void KStartupInfo::new_startup_info_internal( const KStartupInfoId& id_P,
    00224     Data& data_P, bool update_only_P )
    00225     {
    00226     if (!d) return;
    00227     if( id_P.none())
    00228         return;
    00229     if( d->startups.contains( id_P ))
    00230         { // already reported, update
    00231         d->startups[ id_P ].update( data_P );
    00232         d->startups[ id_P ].age = 0; // CHECKME
    00233         kdDebug( 172 ) << "updating" << endl;
    00234         if( d->startups[ id_P ].silent() == Data::Yes
    00235             && !( d->flags & AnnounceSilenceChanges ))
    00236             {
    00237             d->silent_startups[ id_P ] = d->startups[ id_P ];
    00238             d->startups.remove( id_P );
    00239             emit gotRemoveStartup( id_P, d->silent_startups[ id_P ] );
    00240             return;
    00241             }
    00242         emit gotStartupChange( id_P, d->startups[ id_P ] );
    00243         return;
    00244         }
    00245     if( d->silent_startups.contains( id_P ))
    00246         { // already reported, update
    00247         d->silent_startups[ id_P ].update( data_P );
    00248         d->silent_startups[ id_P ].age = 0; // CHECKME
    00249         kdDebug( 172 ) << "updating silenced" << endl;
    00250         if( d->silent_startups[ id_P ].silent() != Data::Yes )
    00251             {
    00252             d->startups[ id_P ] = d->silent_startups[ id_P ];
    00253             d->silent_startups.remove( id_P );
    00254             emit gotNewStartup( id_P, d->startups[ id_P ] );
    00255             return;
    00256             }
    00257         emit gotStartupChange( id_P, d->startups[ id_P ] );
    00258         return;
    00259         }
    00260     if( update_only_P )
    00261         return;
    00262     if( data_P.silent() != Data::Yes || d->flags & AnnounceSilenceChanges )
    00263         {
    00264         kdDebug( 172 ) << "adding" << endl;
    00265         d->startups.insert( id_P, data_P );
    00266         emit gotNewStartup( id_P, data_P );
    00267         }
    00268     else // new silenced, and silent shouldn't be announced
    00269         {
    00270         kdDebug( 172 ) << "adding silent" << endl;
    00271         d->silent_startups.insert( id_P, data_P );
    00272         }
    00273     d->cleanup->start( 1000 ); // 1 sec
    00274     }
    00275
    00276 void KStartupInfo::got_remove_startup_info( const QString& msg_P )
    00277     {
    00278     KStartupInfoId id( msg_P );
    00279     KStartupInfoData data( msg_P );
    00280     if( data.pids().count() > 0 )
    00281         {
    00282         if( !id.none())
    00283             remove_startup_pids( id, data );
    00284         else
    00285             remove_startup_pids( data );
    00286         return;
    00287         }
    00288     remove_startup_info_internal( id );
    00289     }
    00290
    00291 void KStartupInfo::remove_startup_info_internal( const KStartupInfoId& id_P )
    00292     {
    00293     if (!d) return;
    00294     if( d->startups.contains( id_P ))
    00295         {
    00296         kdDebug( 172 ) << "removing" << endl;
    00297         emit gotRemoveStartup( id_P, d->startups[ id_P ]);
    00298         d->startups.remove( id_P );
    00299         }
    00300     else if( d->silent_startups.contains( id_P ))
    00301         {
    00302         kdDebug( 172 ) << "removing silent" << endl;
    00303         d->silent_startups.remove( id_P );
    00304         }
    00305     return;
    00306     }
    00307
    00308 void KStartupInfo::remove_startup_pids( const KStartupInfoData& data_P )
    00309     { // first find the matching info
    00310     if (!d) return;
    00311     for( QMap< KStartupInfoId, Data >::Iterator it = d->startups.begin();
    00312          it != d->startups.end();
    00313          ++it )
    00314         {
    00315         if( ( *it ).hostname() != data_P.hostname())
    00316             continue;
    00317         if( !( *it ).is_pid( data_P.pids().first()))
    00318             continue; // not the matching info
    00319         remove_startup_pids( it.key(), data_P );
    00320         break;
    00321         }
    00322     }
    00323
    00324 void KStartupInfo::remove_startup_pids( const KStartupInfoId& id_P,
    00325     const KStartupInfoData& data_P )
    00326     {
    00327     if (!d) return;
    00328     kdFatal( data_P.pids().count() == 0, 172 );
    00329     Data* data = NULL;
    00330     if( d->startups.contains( id_P ))
    00331         data = &d->startups[ id_P ];
    00332     else if( d->silent_startups.contains( id_P ))
    00333         data = &d->silent_startups[ id_P ];
    00334     else
    00335         return;
    00336     for( QValueList< pid_t >::ConstIterator it2 = data_P.pids().begin();
    00337          it2 != data_P.pids().end();
    00338          ++it2 )
    00339         data->remove_pid( *it2 ); // remove all pids from the info
    00340     if( data->pids().count() == 0 ) // all pids removed -> remove info
    00341         remove_startup_info_internal( id_P );
    00342     }
    00343
    00344 bool KStartupInfo::sendStartup( const KStartupInfoId& id_P, const KStartupInfoData& data_P )
    00345     {
    00346     if( id_P.none())
    00347         return false;
    00348     KXMessages msgs;
    00349     QString msg = QString::fromLatin1( "new: %1 %2" )
    00350         .arg( id_P.to_text()).arg( data_P.to_text());
    00351     kdDebug( 172 ) << "sending " << msg << endl;
    00352     msgs.broadcastMessage( KDE_STARTUP_INFO, msg );
    00353     return true;
    00354     }
    00355
    00356 bool KStartupInfo::sendStartupX( Display* disp_P, const KStartupInfoId& id_P,
    00357     const KStartupInfoData& data_P )
    00358     {
    00359     if( id_P.none())
    00360         return false;
    00361     QString msg = QString::fromLatin1( "new: %1 %2" )
    00362         .arg( id_P.to_text()).arg( data_P.to_text());
    00363 #ifdef KSTARTUPINFO_ALL_DEBUG
    00364     kdDebug( 172 ) << "sending " << msg << endl;
    00365 #endif
    00366     return KXMessages::broadcastMessageX( disp_P, KDE_STARTUP_INFO, msg );
    00367     }
    00368
    00369 bool KStartupInfo::sendChange( const KStartupInfoId& id_P, const KStartupInfoData& data_P )
    00370     {
    00371     if( id_P.none())
    00372         return false;
    00373     KXMessages msgs;
    00374     QString msg = QString::fromLatin1( "change: %1 %2" )
    00375         .arg( id_P.to_text()).arg( data_P.to_text());
    00376     kdDebug( 172 ) << "sending " << msg << endl;
    00377     msgs.broadcastMessage( KDE_STARTUP_INFO, msg );
    00378     return true;
    00379     }
    00380
    00381 bool KStartupInfo::sendChangeX( Display* disp_P, const KStartupInfoId& id_P,
    00382     const KStartupInfoData& data_P )
    00383     {
    00384     if( id_P.none())
    00385         return false;
    00386     QString msg = QString::fromLatin1( "change: %1 %2" )
    00387         .arg( id_P.to_text()).arg( data_P.to_text());
    00388 #ifdef KSTARTUPINFO_ALL_DEBUG
    00389     kdDebug( 172 ) << "sending " << msg << endl;
    00390 #endif
    00391     return KXMessages::broadcastMessageX( disp_P, KDE_STARTUP_INFO, msg );
    00392     }
    00393
    00394 bool KStartupInfo::sendFinish( const KStartupInfoId& id_P )
    00395     {
    00396     if( id_P.none())
    00397         return false;
    00398     KXMessages msgs;
    00399     QString msg = QString::fromLatin1( "remove: %1" ).arg( id_P.to_text());
    00400     kdDebug( 172 ) << "sending " << msg << endl;
    00401     msgs.broadcastMessage( KDE_STARTUP_INFO, msg );
    00402     return true;
    00403     }
    00404
    00405 bool KStartupInfo::sendFinishX( Display* disp_P, const KStartupInfoId& id_P )
    00406     {
    00407     if( id_P.none())
    00408         return false;
    00409     QString msg = QString::fromLatin1( "remove: %1" ).arg( id_P.to_text());
    00410 #ifdef KSTARTUPINFO_ALL_DEBUG
    00411     kdDebug( 172 ) << "sending " << msg << endl;
    00412 #endif
    00413     return KXMessages::broadcastMessageX( disp_P, KDE_STARTUP_INFO, msg );
    00414     }
    00415
    00416 bool KStartupInfo::sendFinish( const KStartupInfoId& id_P, const KStartupInfoData& data_P )
    00417     {
    00418 //    if( id_P.none()) // id may be none, the pids and hostname matter then
    00419 //        return false;
    00420     KXMessages msgs;
    00421     QString msg = QString::fromLatin1( "remove: %1 %2" )
    00422         .arg( id_P.to_text()).arg( data_P.to_text());
    00423     kdDebug( 172 ) << "sending " << msg << endl;
    00424     msgs.broadcastMessage( KDE_STARTUP_INFO, msg );
    00425     return true;
    00426     }
    00427
    00428 bool KStartupInfo::sendFinishX( Display* disp_P, const KStartupInfoId& id_P,
    00429     const KStartupInfoData& data_P )
    00430     {
    00431 //    if( id_P.none()) // id may be none, the pids and hostname matter then
    00432 //        return false;
    00433     QString msg = QString::fromLatin1( "remove: %1 %2" )
    00434         .arg( id_P.to_text()).arg( data_P.to_text());
    00435 #ifdef KSTARTUPINFO_ALL_DEBUG
    00436     kdDebug( 172 ) << "sending " << msg << endl;
    00437 #endif
    00438     return KXMessages::broadcastMessageX( disp_P, KDE_STARTUP_INFO, msg );
    00439     }
    00440
    00441 void KStartupInfo::appStarted()
    00442     {
    00443     if( kapp != NULL ) // KApplication constructor unsets the env. variable
    00444         {
    00445         KStartupInfoId id;
    00446         id.initId( kapp->startupId());
    00447         if( !id.none())
    00448             KStartupInfo::sendFinish( id );
    00449         }
    00450     else if( getenv( "DISPLAY" ) != NULL ) // don't rely on qt_xdisplay()
    00451         {
    00452         KStartupInfoId id = KStartupInfo::currentStartupIdEnv();
    00453         if( !id.none())
    00454             {
    00455             Display* disp = XOpenDisplay( NULL );
    00456             if( disp != NULL )
    00457                 {
    00458                 KStartupInfo::sendFinishX( disp, id );
    00459                 XCloseDisplay( disp );
    00460                 }
    00461             }
    00462         }
    00463     }
    00464
    00465 KStartupInfo::startup_t KStartupInfo::checkStartup( WId w_P, KStartupInfoId& id_O,
    00466     KStartupInfoData& data_O )
    00467     {
    00468     return check_startup_internal( w_P, &id_O, &data_O, true );
    00469     }
    00470
    00471 KStartupInfo::startup_t KStartupInfo::checkStartup( WId w_P, KStartupInfoId& id_O )
    00472     {
    00473     return check_startup_internal( w_P, &id_O, NULL, true );
    00474     }
    00475
    00476 KStartupInfo::startup_t KStartupInfo::checkStartup( WId w_P, KStartupInfoData& data_O )
    00477     {
    00478     return check_startup_internal( w_P, NULL, &data_O, true );
    00479     }
    00480
    00481 KStartupInfo::startup_t KStartupInfo::checkStartup( WId w_P )
    00482     {
    00483     return check_startup_internal( w_P, NULL, NULL, true );
    00484     }
    00485
    00486 KStartupInfo::startup_t KStartupInfo::check_startup_internal( WId w_P, KStartupInfoId* id_O,
    00487     KStartupInfoData* data_O, bool remove_P )
    00488     {
    00489     if (!d) return NoMatch;
    00490     if( d->startups.count() == 0 )
    00491         return NoMatch; // no startups
    00492     NETWinInfo info( qt_xdisplay(),  w_P, qt_xrootwin(),
    00493         NET::WMWindowType | NET::WMPid | NET::WMState );
    00494     // ignore NET::Tool and other special window types
    00495     if( info.windowType() != NET::Normal
    00496         && info.windowType() != NET::Override
    00497         && info.windowType() != NET::Unknown
    00498         && info.windowType() != NET::Dialog
    00499         && info.windowType() != NET::Dock )
    00500         return NoMatch;
    00501     // lets see if this is a transient
    00502     Window transient_for;
    00503     if( XGetTransientForHint( qt_xdisplay(), static_cast< Window >( w_P ), &transient_for )
    00504         && static_cast< WId >( transient_for ) != qt_xrootwin()
    00505         && transient_for != None )
    00506         return NoMatch;
    00507     // Strategy:
    00508     //
    00509     // Is this a compliant app ?
    00510     //  - Yes - test for match
    00511     //  - No - Is this a NET_WM compliant app ?
    00512     //           - Yes - test for pid match
    00513     //           - No - test for WM_CLASS match
    00514     kdDebug( 172 ) << "check_startup" << endl;
    00515     QCString id = windowStartupId( w_P );
    00516     if( !id.isNull())
    00517         {
    00518         if( id.isEmpty() || id == "0" ) // means ignore this window
    00519             {
    00520             kdDebug( 172 ) << "ignore" << endl;
    00521             return NoMatch;
    00522             }
    00523         return find_id( id, id_O, data_O, remove_P ) ? Match : NoMatch;
    00524         }
    00525     // _NET_WM_PID apps are also considered compliant
    00526     pid_t pid = info.pid();
    00527     if( pid > 0 )
    00528         {
    00529         QCString hostname = get_window_hostname( w_P );
    00530         if( !hostname.isEmpty()
    00531             && find_pid( pid, hostname, id_O, data_O, remove_P ))
    00532             return Match;
    00533         // try XClass matching , this PID stuff sucks :(
    00534         }
    00535     // Hard - this app is not even NET_WM compliant
    00536     XClassHint hint;
    00537     if( XGetClassHint( qt_xdisplay(), w_P, &hint ) != 0 )
    00538         { // We managed to read the class hint
    00539         if( find_wclass( hint.res_name, hint.res_class, id_O, data_O, remove_P ))
    00540             return Match;
    00541         }
    00542     kdDebug( 172 ) << "check_startup:cantdetect" << endl;
    00543     return CantDetect;
    00544     }
    00545
    00546 bool KStartupInfo::find_id( const QCString& id_P, KStartupInfoId* id_O,
    00547     KStartupInfoData* data_O, bool remove_P )
    00548     {
    00549     if (!d) return false;
    00550     kdDebug( 172 ) << "find_id:" << id_P << endl;
    00551     KStartupInfoId id;
    00552     id.initId( id_P );
    00553     if( d->startups.contains( id ))
    00554         {
    00555         if( id_O != NULL )
    00556             *id_O = id;
    00557         if( data_O != NULL )
    00558             *data_O = d->startups[ id ];
    00559         if( remove_P
    00560             && d->startups[ id ].silent() != Data::Yes ) // HACK
    00561             d->startups.remove( id );
    00562         kdDebug( 172 ) << "check_startup_id:match" << endl;
    00563         return true;
    00564         }
    00565     return false;
    00566     }
    00567
    00568 bool KStartupInfo::find_pid( pid_t pid_P, const QCString& hostname_P,
    00569     KStartupInfoId* id_O, KStartupInfoData* data_O, bool remove_P )
    00570     {
    00571     if (!d) return false;
    00572     kdDebug( 172 ) << "find_pid:" << pid_P << endl;
    00573     for( QMap< KStartupInfoId, Data >::Iterator it = d->startups.begin();
    00574          it != d->startups.end();
    00575          ++it )
    00576         {
    00577         if( ( *it ).is_pid( pid_P ) && ( *it ).hostname() == hostname_P )
    00578             { // Found it !
    00579             if( id_O != NULL )
    00580                 *id_O = it.key();
    00581             if( data_O != NULL )
    00582                 *data_O = *it;
    00583             if( remove_P
    00584                 && ( *it ).silent() != Data::Yes ) // HACK
    00585                 d->startups.remove( it );
    00586             kdDebug( 172 ) << "check_startup_pid:match" << endl;
    00587             return true;
    00588             }
    00589         }
    00590     return false;
    00591     }
    00592
    00593 bool KStartupInfo::find_wclass( QCString res_name, QCString res_class,
    00594     KStartupInfoId* id_O, KStartupInfoData* data_O, bool remove_P )
    00595     {
    00596     if (!d) return false;
    00597     res_name = res_name.lower();
    00598     res_class = res_class.lower();
    00599     kdDebug( 172 ) << "find_wclass:" << res_name << ":" << res_class << endl;
    00600     for( QMap< KStartupInfoId, Data >::Iterator it = d->startups.begin();
    00601          it != d->startups.end();
    00602          ++it )
    00603         {
    00604         const QCString wmclass = ( *it ).findWMClass();
    00605         if( wmclass.lower() == res_name || wmclass.lower() == res_class )
    00606             { // Found it !
    00607             if( id_O != NULL )
    00608                 *id_O = it.key();
    00609             if( data_O != NULL )
    00610                 *data_O = *it;
    00611             if( remove_P
    00612                 && ( *it ).silent() != Data::Yes ) // HACK
    00613                 d->startups.remove( it );
    00614             kdDebug( 172 ) << "check_startup_wclass:match" << endl;
    00615             return true;
    00616             }
    00617         }
    00618     return false;
    00619     }
    00620
    00621 static Atom kde_startup_atom = None;
    00622 static Atom kde_startup_atom_2 = None;
    00623
    00624 QCString KStartupInfo::windowStartupId( WId w_P )
    00625     {
    00626     if( kde_startup_atom == None )
    00627         kde_startup_atom = XInternAtom( qt_xdisplay(), KDE_STARTUP_ID, False );
    00628     if( kde_startup_atom_2 == None )
    00629         kde_startup_atom_2 = XInternAtom( qt_xdisplay(), KDE_STARTUP_ID_2, False );
    00630     unsigned char *name_ret;
    00631     QCString ret;
    00632     Atom type_ret;
    00633     int format_ret;
    00634     unsigned long nitems_ret = 0, after_ret = 0;
    00635     if( XGetWindowProperty( qt_xdisplay(), w_P, kde_startup_atom, 0l, (long) BUFSIZE,
    00636             False, XA_STRING, &type_ret, &format_ret, &nitems_ret, &after_ret, &name_ret )
    00637             == Success )
    00638         {
    00639         if( type_ret == XA_STRING && format_ret == 8 && name_ret != NULL )
    00640             ret = reinterpret_cast< char* >( name_ret );
    00641         if ( name_ret != NULL )
    00642             XFree( name_ret );
    00643         }
    00644     if( ret.isNull() && XGetWindowProperty( qt_xdisplay(), w_P, kde_startup_atom_2, 0l, (long) BUFSIZE,
    00645             False, XA_STRING, &type_ret, &format_ret, &nitems_ret, &after_ret, &name_ret )
    00646             == Success )
    00647         {
    00648         if( type_ret == XA_STRING && format_ret == 8 && name_ret != NULL )
    00649             ret = reinterpret_cast< char* >( name_ret );
    00650         if ( name_ret != NULL )
    00651             XFree( name_ret );
    00652         }
    00653     return ret;
    00654     }
    00655
    00656 void KStartupInfo::setWindowStartupId( WId w_P, const QCString& id_P )
    00657     {
    00658     if( id_P.isNull())
    00659         return;
    00660     if( kde_startup_atom == None )
    00661         kde_startup_atom = XInternAtom( qt_xdisplay(), KDE_STARTUP_ID, False );
    00662     XChangeProperty( qt_xdisplay(), w_P, kde_startup_atom, XA_STRING, 8,
    00663         PropModeReplace, reinterpret_cast< unsigned char* >( id_P.data()), id_P.length());
    00664     }
    00665
    00666 QCString KStartupInfo::get_window_hostname( WId w_P )
    00667     {
    00668     XTextProperty tp;
    00669     char** hh;
    00670     int cnt;
    00671     if( XGetWMClientMachine( qt_xdisplay(), w_P, &tp ) != 0
    00672         && XTextPropertyToStringList( &tp, &hh, &cnt ) != 0 )
    00673         {
    00674         if( cnt == 1 )
    00675             {
    00676             QCString hostname = hh[ 0 ];
    00677             XFreeStringList( hh );
    00678             return hostname;
    00679             }
    00680         XFreeStringList( hh );
    00681         }
    00682     // no hostname
    00683     return QCString();
    00684     }
    00685
    00686 void KStartupInfo::setTimeout( unsigned int secs_P )
    00687     {
    00688     timeout = secs_P;
    00689  // schedule removing entries that are older than the new timeout
    00690     QTimer::singleShot( 0, this, SLOT( startups_cleanup_no_age()));
    00691     }
    00692
    00693 void KStartupInfo::startups_cleanup_no_age()
    00694     {
    00695     startups_cleanup_internal( false );
    00696     }
    00697
    00698 void KStartupInfo::startups_cleanup()
    00699     {
    00700     if (!d) return;
    00701     if( d->startups.count() == 0 && d->silent_startups.count() == 0 )
    00702         {
    00703         d->cleanup->stop();
    00704         return;
    00705         }
    00706     startups_cleanup_internal( true );
    00707     }
    00708
    00709 void KStartupInfo::startups_cleanup_internal( bool age_P )
    00710     {
    00711     if (!d) return;
    00712     for( QMap< KStartupInfoId, Data >::Iterator it = d->startups.begin();
    00713          it != d->startups.end();
    00714          )
    00715         {
    00716         if( age_P )
    00717             ( *it ).age++;
    00718         int tout = timeout;
    00719         if( ( *it ).silent() == Data::Yes ) // TODO
    00720             tout *= 20;
    00721         if( ( *it ).age >= timeout )
    00722             {
    00723             const KStartupInfoId& key = it.key();
    00724             ++it;
    00725             kdDebug( 172 ) << "entry timeout:" << key.id() << endl;
    00726             remove_startup_info_internal( key );
    00727             }
    00728         else
    00729             ++it;
    00730         }
    00731     for( QMap< KStartupInfoId, Data >::Iterator it = d->silent_startups.begin();
    00732          it != d->silent_startups.end();
    00733          )
    00734         {
    00735         if( age_P )
    00736             ( *it ).age++;
    00737         int tout = timeout;
    00738         if( ( *it ).silent() == Data::Yes ) // TODO
    00739             tout *= 20;
    00740         if( ( *it ).age >= timeout )
    00741             {
    00742             const KStartupInfoId& key = it.key();
    00743             ++it;
    00744             kdDebug( 172 ) << "entry timeout:" << key.id() << endl;
    00745             remove_startup_info_internal( key );
    00746             }
    00747         else
    00748             ++it;
    00749         }
    00750     }
    00751
    00752 void KStartupInfo::clean_all_noncompliant()
    00753     {
    00754     if (!d) return;
    00755     for( QMap< KStartupInfoId, Data >::Iterator it = d->startups.begin();
    00756          it != d->startups.end();
    00757          )
    00758         {
    00759         if( ( *it ).WMClass() != "0" )
    00760             {
    00761             ++it;
    00762             continue;
    00763             }
    00764         const KStartupInfoId& key = it.key();
    00765         ++it;
    00766         kdDebug( 172 ) << "entry cleaning:" << key.id() << endl;
    00767         remove_startup_info_internal( key );
    00768         }
    00769     }
    00770
    00771 struct KStartupInfoIdPrivate
    00772     {
    00773     KStartupInfoIdPrivate() : id( "" ) {};
    00774     QCString id; // id
    00775     };
    00776
    00777 const QCString& KStartupInfoId::id() const
    00778     {
    00779     return d->id;
    00780     }
    00781
    00782
    00783 QString KStartupInfoId::to_text() const
    00784     {
    00785     return QString::fromLatin1( " ID=\"%1\" " ).arg( escape_str( id()));
    00786     }
    00787
    00788 KStartupInfoId::KStartupInfoId( const QString& txt_P )
    00789     {
    00790     d = new KStartupInfoIdPrivate;
    00791     QStringList items = get_fields( txt_P );
    00792     const QString id_str = QString::fromLatin1( "ID=" );
    00793     for( QStringList::Iterator it = items.begin();
    00794          it != items.end();
    00795          ++it )
    00796         {
    00797         if( ( *it ).startsWith( id_str ))
    00798             d->id = get_cstr( *it );
    00799         }
    00800     }
    00801
    00802 void KStartupInfoId::initId( const QCString& id_P )
    00803     {
    00804     if( !id_P.isEmpty())
    00805         {
    00806         d->id = id_P;
    00807 #ifdef KSTARTUPINFO_ALL_DEBUG
    00808         kdDebug( 172 ) << "using: " << d->id << endl;
    00809 #endif
    00810         return;
    00811         }
    00812     const char* startup_env = getenv( KDE_STARTUP_ENV );
    00813     if( startup_env != NULL && *startup_env != '\0' )
    00814         { // already has id
    00815         d->id = startup_env;
    00816 #ifdef KSTARTUPINFO_ALL_DEBUG
    00817         kdDebug( 172 ) << "reusing: " << d->id << endl;
    00818 #endif
    00819         return;
    00820         }
    00821     // assign a unique id  CHECKME
    00822     // use hostname+time+pid, that should be 200% unique
    00823     // hmm, probably something 99.9% unique and much shorter would be enough
    00824     struct timeval tm;
    00825     gettimeofday( &tm, NULL );
    00826     char hostname[ 256 ];
    00827     hostname[ 0 ] = '\0';
    00828     gethostname( hostname, 255 );
    00829     d->id = QString( "%1;%2;%3;%4" ).arg( hostname ).arg( tm.tv_sec )
    00830         .arg( tm.tv_usec ).arg( getpid()).latin1();
    00831 #ifdef KSTARTUPINFO_ALL_DEBUG
    00832     kdDebug( 172 ) << "creating: " << d->id << endl;
    00833 #endif
    00834     }
    00835
    00836 bool KStartupInfoId::setupStartupEnv() const
    00837     {
    00838     if( id().isEmpty())
    00839         {
    00840         unsetenv( KDE_STARTUP_ENV );
    00841         return false;
    00842         }
    00843     return setenv( KDE_STARTUP_ENV, id(), true ) == 0;
    00844     }
    00845
    00846 KStartupInfoId KStartupInfo::currentStartupIdEnv()
    00847     {
    00848     const char* startup_env = getenv( KDE_STARTUP_ENV );
    00849     KStartupInfoId id;
    00850     if( startup_env != NULL && *startup_env != '\0' )
    00851         id.d->id = startup_env;
    00852     else
    00853         id.d->id = "0";
    00854     return id;
    00855     }
    00856
    00857 void KStartupInfo::resetStartupEnv()
    00858     {
    00859     unsetenv( KDE_STARTUP_ENV );
    00860     }
    00861
    00862 KStartupInfoId::KStartupInfoId()
    00863     {
    00864     d = new KStartupInfoIdPrivate;
    00865     }
    00866
    00867 KStartupInfoId::~KStartupInfoId()
    00868     {
    00869     delete d;
    00870     }
    00871
    00872 KStartupInfoId::KStartupInfoId( const KStartupInfoId& id_P )
    00873     {
    00874     d = new KStartupInfoIdPrivate( *id_P.d );
    00875     }
    00876
    00877 KStartupInfoId& KStartupInfoId::operator=( const KStartupInfoId& id_P )
    00878     {
    00879     if( &id_P == this )
    00880         return *this;
    00881     delete d;
    00882     d = new KStartupInfoIdPrivate( *id_P.d );
    00883     return *this;
    00884     }
    00885
    00886 bool KStartupInfoId::operator==( const KStartupInfoId& id_P ) const
    00887     {
    00888     return id() == id_P.id();
    00889     }
    00890
    00891 bool KStartupInfoId::operator!=( const KStartupInfoId& id_P ) const
    00892     {
    00893     return !(*this == id_P );
    00894     }
    00895
    00896 // needed for QMap
    00897 bool KStartupInfoId::operator<( const KStartupInfoId& id_P ) const
    00898     {
    00899     return id() < id_P.id();
    00900     }
    00901
    00902 bool KStartupInfoId::none() const
    00903     {
    00904     return d->id.isEmpty() || d->id == "0";
    00905     }
    00906
    00907 struct KStartupInfoDataPrivate
    00908     {
    00909     KStartupInfoDataPrivate() : desktop( 0 ), wmclass( "" ), hostname( "" ),
    00910         silent( KStartupInfoData::Unknown ) {};
    00911     QString bin;
    00912     QString name;
    00913     QString icon;
    00914     int desktop;
    00915     QValueList< pid_t > pids;
    00916     QCString wmclass;
    00917     QCString hostname;
    00918     KStartupInfoData::TriState silent;
    00919     };
    00920
    00921 QString KStartupInfoData::to_text() const
    00922     {
    00923     QString ret = "";
    00924     if( !d->bin.isEmpty())
    00925         ret += QString::fromLatin1( " BIN=\"%1\"" ).arg( escape_str( d->bin ));
    00926     if( !d->name.isEmpty())
    00927         ret += QString::fromLatin1( " NAME=\"%1\"" ).arg( escape_str( d->name ));
    00928     if( !d->icon.isEmpty())
    00929         ret += QString::fromLatin1( " ICON=%1" ).arg( d->icon );
    00930     if( d->desktop != 0 )
    00931         ret += QString::fromLatin1( " DESKTOP=%1" ).arg( d->desktop );
    00932     if( !d->wmclass.isEmpty())
    00933         ret += QString::fromLatin1( " WMCLASS=%1" ).arg( d->wmclass );
    00934     if( !d->hostname.isEmpty())
    00935         ret += QString::fromLatin1( " HOSTNAME=%1" ).arg( d->hostname );
    00936     for( QValueList< pid_t >::ConstIterator it = d->pids.begin();
    00937          it != d->pids.end();
    00938          ++it )
    00939         ret += QString::fromLatin1( " PID=%1" ).arg( *it );
    00940     if( d->silent != Unknown )
    00941         ret += QString::fromLatin1( " SILENT=%1" ).arg( d->silent == Yes ? 1 : 0 );
    00942     return ret;
    00943     }
    00944
    00945 KStartupInfoData::KStartupInfoData( const QString& txt_P )
    00946     {
    00947     d = new KStartupInfoDataPrivate;
    00948     QStringList items = get_fields( txt_P );
    00949     const QString bin_str = QString::fromLatin1( "BIN=" );
    00950     const QString name_str = QString::fromLatin1( "NAME=" );
    00951     const QString icon_str = QString::fromLatin1( "ICON=" );
    00952     const QString desktop_str = QString::fromLatin1( "DESKTOP=" );
    00953     const QString wmclass_str = QString::fromLatin1( "WMCLASS=" );
    00954     const QString hostname_str = QString::fromLatin1( "HOSTNAME=" );
    00955     const QString pid_str = QString::fromLatin1( "PID=" );
    00956     const QString silent_str = QString::fromLatin1( "SILENT=" );
    00957     for( QStringList::Iterator it = items.begin();
    00958          it != items.end();
    00959          ++it )
    00960         {
    00961         if( ( *it ).startsWith( bin_str ))
    00962             d->bin = get_str( *it );
    00963         else if( ( *it ).startsWith( name_str ))
    00964             d->name = get_str( *it );
    00965         else if( ( *it ).startsWith( icon_str ))
    00966             d->icon = get_str( *it );
    00967         else if( ( *it ).startsWith( desktop_str ))
    00968             d->desktop = get_num( *it );
    00969         else if( ( *it ).startsWith( wmclass_str ))
    00970             d->wmclass = get_cstr( *it );
    00971         else if( ( *it ).startsWith( hostname_str ))
    00972             d->hostname = get_cstr( *it );
    00973         else if( ( *it ).startsWith( pid_str ))
    00974             addPid( get_num( *it ));
    00975         else if( ( *it ).startsWith( silent_str ))
    00976             d->silent = get_num( *it ) != 0 ? Yes : No;
    00977         }
    00978     }
    00979
    00980 KStartupInfoData::KStartupInfoData( const KStartupInfoData& data )
    00981 {
    00982     d = new KStartupInfoDataPrivate( *data.d );
    00983 }
    00984
    00985 KStartupInfoData& KStartupInfoData::operator=( const KStartupInfoData& data )
    00986 {
    00987     if( &data == this )
    00988         return *this;
    00989     delete d;
    00990     d = new KStartupInfoDataPrivate( *data.d );
    00991     return *this;
    00992 }
    00993
    00994 void KStartupInfoData::update( const KStartupInfoData& data_P )
    00995     {
    00996     if( !data_P.bin().isEmpty())
    00997         d->bin = data_P.bin();
    00998     if( !data_P.name().isEmpty() && name().isEmpty()) // don't overwrite
    00999         d->name = data_P.name();
    01000     if( !data_P.icon().isEmpty() && icon().isEmpty()) // don't overwrite
    01001         d->icon = data_P.icon();
    01002     if( data_P.desktop() != 0 && desktop() == 0 ) // don't overwrite
    01003         d->desktop = data_P.desktop();
    01004     if( !data_P.d->wmclass.isEmpty())
    01005         d->wmclass = data_P.d->wmclass;
    01006     if( !data_P.d->hostname.isEmpty())
    01007         d->hostname = data_P.d->hostname;
    01008     for( QValueList< pid_t >::ConstIterator it = data_P.d->pids.begin();
    01009          it != data_P.d->pids.end();
    01010          ++it )
    01011         addPid( *it );
    01012     if( data_P.silent() != Unknown )
    01013         d->silent = data_P.silent();
    01014     }
    01015
    01016 KStartupInfoData::KStartupInfoData()
    01017 {
    01018     d = new KStartupInfoDataPrivate;
    01019 }
    01020
    01021 KStartupInfoData::~KStartupInfoData()
    01022 {
    01023     delete d;
    01024 }
    01025
    01026 void KStartupInfoData::setBin( const QString& bin_P )
    01027     {
    01028     d->bin = bin_P;
    01029     }
    01030
    01031 const QString& KStartupInfoData::bin() const
    01032     {
    01033     return d->bin;
    01034     }
    01035
    01036 void KStartupInfoData::setName( const QString& name_P )
    01037     {
    01038     d->name = name_P;
    01039     }
    01040
    01041 const QString& KStartupInfoData::findName() const
    01042     {
    01043     if( !name().isEmpty())
    01044         return name();
    01045     return bin();
    01046     }
    01047
    01048 const QString& KStartupInfoData::name() const
    01049     {
    01050     return d->name;
    01051     }
    01052
    01053 void KStartupInfoData::setIcon( const QString& icon_P )
    01054     {
    01055     d->icon = icon_P;
    01056     }
    01057
    01058 const QString& KStartupInfoData::findIcon() const
    01059     {
    01060     if( !icon().isEmpty())
    01061         return icon();
    01062     return bin();
    01063     }
    01064
    01065 const QString& KStartupInfoData::icon() const
    01066     {
    01067     return d->icon;
    01068     }
    01069
    01070 void KStartupInfoData::setDesktop( int desktop_P )
    01071     {
    01072     d->desktop = desktop_P;
    01073     }
    01074
    01075 int KStartupInfoData::desktop() const
    01076     {
    01077     return d->desktop;
    01078     }
    01079
    01080 void KStartupInfoData::setWMClass( const QCString& wmclass_P )
    01081     {
    01082     d->wmclass = wmclass_P;
    01083     }
    01084
    01085 const QCString KStartupInfoData::findWMClass() const
    01086     {
    01087     if( !WMClass().isEmpty() && WMClass() != "0" )
    01088         return WMClass();
    01089     return bin().latin1(); // CHECKME
    01090     }
    01091
    01092 const QCString& KStartupInfoData::WMClass() const
    01093     {
    01094     return d->wmclass;
    01095     }
    01096
    01097 void KStartupInfoData::setHostname( const QCString& hostname_P )
    01098     {
    01099     if( !hostname_P.isNull())
    01100         d->hostname = hostname_P;
    01101     else
    01102         {
    01103         char tmp[ 256 ];
    01104         tmp[ 0 ] = '\0';
    01105         gethostname( tmp, 255 );
    01106         d->hostname = tmp;
    01107         }
    01108     }
    01109
    01110 const QCString& KStartupInfoData::hostname() const
    01111     {
    01112     return d->hostname;
    01113     }
    01114
    01115 void KStartupInfoData::addPid( pid_t pid_P )
    01116     {
    01117     if( !d->pids.contains( pid_P ))
    01118         d->pids.append( pid_P );
    01119     }
    01120
    01121 void KStartupInfoData::remove_pid( pid_t pid_P )
    01122     {
    01123     d->pids.remove( pid_P );
    01124     }
    01125
    01126 const QValueList< pid_t >& KStartupInfoData::pids() const
    01127     {
    01128     return d->pids;
    01129     }
    01130
    01131 bool KStartupInfoData::is_pid( pid_t pid_P ) const
    01132     {
    01133     return d->pids.contains( pid_P );
    01134     }
    01135
    01136 void KStartupInfoData::setSilent( TriState state_P )
    01137     {
    01138     d->silent = state_P;
    01139     }
    01140
    01141 KStartupInfoData::TriState KStartupInfoData::silent() const
    01142     {
    01143     return d->silent;
    01144     }
    01145
    01146 static
    01147 int get_num( const QString& item_P )
    01148     {
    01149     unsigned int pos = item_P.find( '=' );
    01150     return item_P.mid( pos + 1 ).toInt();
    01151     }
    01152
    01153 static
    01154 QString get_str( const QString& item_P )
    01155     {
    01156     unsigned int pos = item_P.find( '=' );
    01157     if( item_P.length() > pos + 2 && item_P[ pos + 1 ] == '\"' )
    01158         {
    01159         int pos2 = item_P.left( pos + 2 ).find( '\"' );
    01160         if( pos2 < 0 )
    01161             return QString::null;                      // 01234
    01162         return item_P.mid( pos + 2, pos2 - 2 - pos );  // A="C"
    01163         }
    01164     return item_P.mid( pos + 1 );
    01165     }
    01166
    01167 static
    01168 QCString get_cstr( const QString& item_P )
    01169     {
    01170     return get_str( item_P ).latin1(); // CHECKME
    01171     }
    01172
    01173 static
    01174 QStringList get_fields( const QString& txt_P )
    01175     {
    01176     QString txt = txt_P.simplifyWhiteSpace();
    01177     QStringList ret;
    01178     QString item = "";
    01179     bool in = false;
    01180     bool escape = false;
    01181     for( unsigned int pos = 0;
    01182          pos < txt.length();
    01183          ++pos )
    01184         {
    01185         if( escape )
    01186             {
    01187             item += txt[ pos ];
    01188             escape = false;
    01189             }
    01190         else if( txt[ pos ] == '\\' )
    01191             escape = true;
    01192         else if( txt[ pos ] == '\"' )
    01193             in = !in;
    01194         else if( txt[ pos ] == ' ' && !in )
    01195             {
    01196             ret.append( item );
    01197             item = "";
    01198             }
    01199         else
    01200             item += txt[ pos ];
    01201         }
    01202     ret.append( item );
    01203     return ret;
    01204     }
    01205
    01206 static QString escape_str( const QString& str_P )
    01207     {
    01208     QString ret = "";
    01209     for( unsigned int pos = 0;
    01210          pos < str_P.length();
    01211          ++pos )
    01212         {
    01213         if( str_P[ pos ] == '\\'
    01214             || str_P[ pos ] == '"' )
    01215             ret += '\\';
    01216         ret += str_P[ pos ];
    01217         }
    01218     return ret;
    01219     }
    01220
    01221 #undef None
    01222
    01223 #include "kstartupinfo.moc"
    01224 #endif

This file is part of the documentation for kdelibs Version 3.1.5.
Documentation copyright © 1996-2002 the KDE developers.
Generated on Wed Jan 28 12:46:57 2004 by doxygen 1.3.4 written by Dimitri van Heesch, © 1997-2001