/**************************************************************************** $Id$ Copyright (C) 2001-2003 Lubos Lunak Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ****************************************************************************/ // kdDebug() can't be turned off in tdeinit #if 0 #define KSTARTUPINFO_ALL_DEBUG #warning Extra KStartupInfo debug messages enabled. #endif #include #include "config.h" #ifdef Q_WS_X11 //#ifdef Q_WS_X11 // FIXME(E): Re-implement in a less X11 specific way #include #ifdef HAVE_CONFIG_H #include #endif // need to resolve INT32(tqglobal.h)<>INT32(Xlibint.h) conflict #ifndef QT_CLEAN_NAMESPACE #define QT_CLEAN_NAMESPACE #endif #include "kstartupinfo.h" #include #include #include #include #ifdef Q_WS_X11 #include #endif #include #include #include #ifdef Q_WS_X11 #include #include #include #endif static const char* const NET_STARTUP_MSG = "_NET_STARTUP_INFO"; static const char* const NET_STARTUP_WINDOW = "_NET_STARTUP_ID"; // DESKTOP_STARTUP_ID is used also in kinit/wrapper.c , // tdesu in both tdelibs and tdebase and who knows where else static const char* const NET_STARTUP_ENV = "DESKTOP_STARTUP_ID"; static bool auto_app_started_sending = true; static long get_num( const TQString& item_P ); static unsigned long get_unum( const TQString& item_P ); static TQString get_str( const TQString& item_P ); static TQCString get_cstr( const TQString& item_P ); static TQStringList get_fields( const TQString& txt_P ); static TQString escape_str( const TQString& str_P ); static Atom utf8_string_atom = None; class KStartupInfo::Data : public KStartupInfoData { public: Data() : KStartupInfoData(), age(0) {} // just because it's in a QMap Data( const TQString& txt_P ) : KStartupInfoData( txt_P ), age( 0 ) {} unsigned int age; }; struct KStartupInfoPrivate { public: TQMap< KStartupInfoId, KStartupInfo::Data > startups; // contains silenced ASN's only if !AnnounceSilencedChanges TQMap< KStartupInfoId, KStartupInfo::Data > silent_startups; // contains ASN's that had change: but no new: yet TQMap< KStartupInfoId, KStartupInfo::Data > uninited_startups; #ifdef Q_WS_X11 KWinModule* wm_module; KXMessages msgs; #endif TQTimer* cleanup; int flags; KStartupInfoPrivate( int flags_P ) : #ifdef Q_WS_X11 msgs( NET_STARTUP_MSG, NULL, false ), #endif flags( flags_P ) {} }; KStartupInfo::KStartupInfo( int flags_P, TQObject* parent_P, const char* name_P ) : TQObject( parent_P, name_P ), timeout( 60 ), d( NULL ) { init( flags_P ); } KStartupInfo::KStartupInfo( bool clean_on_cantdetect_P, TQObject* parent_P, const char* name_P ) : TQObject( parent_P, name_P ), timeout( 60 ), d( NULL ) { init( clean_on_cantdetect_P ? CleanOnCantDetect : 0 ); } void KStartupInfo::init( int flags_P ) { // d == NULL means "disabled" if( !KApplication::kApplication()) return; if( !KApplication::kApplication()->getDisplay()) return; d = new KStartupInfoPrivate( flags_P ); #ifdef Q_WS_X11 if( !( d->flags & DisableKWinModule )) { d->wm_module = new KWinModule( this ); connect( d->wm_module, TQT_SIGNAL( windowAdded( WId )), TQT_SLOT( slot_window_added( WId ))); connect( d->wm_module, TQT_SIGNAL( systemTrayWindowAdded( WId )), TQT_SLOT( slot_window_added( WId ))); } else d->wm_module = NULL; connect( &d->msgs, TQT_SIGNAL( gotMessage( const TQString& )), TQT_SLOT( got_message( const TQString& ))); #endif d->cleanup = new TQTimer( this, "cleanup" ); connect( d->cleanup, TQT_SIGNAL( timeout()), TQT_SLOT( startups_cleanup())); } KStartupInfo::~KStartupInfo() { delete d; } void KStartupInfo::got_message( const TQString& msg_P ) { // TODO do something with SCREEN= ? kdDebug( 172 ) << "got:" << msg_P << endl; TQString msg = msg_P.stripWhiteSpace(); if( msg.startsWith( "new:" )) // must match length below got_startup_info( msg.mid( 4 ), false ); else if( msg.startsWith( "change:" )) // must match length below got_startup_info( msg.mid( 7 ), true ); else if( msg.startsWith( "remove:" )) // must match length below got_remove_startup_info( msg.mid( 7 )); } // if the application stops responding for a while, KWinModule may get // the information about the already mapped window before KXMessages // actually gets the info about the started application (depends // on their order in X11 event filter in KApplication) // simply delay info from KWinModule a bit // SELI??? namespace { class DelayedWindowEvent : public TQCustomEvent { public: DelayedWindowEvent( WId w_P ) : TQCustomEvent( TQEvent::User + 15 ), w( w_P ) {} Window w; }; } void KStartupInfo::slot_window_added( WId w_P ) { kapp->postEvent( this, new DelayedWindowEvent( w_P )); } void KStartupInfo::customEvent( TQCustomEvent* e_P ) { if( e_P->type() == TQEvent::User + 15 ) window_added( static_cast< DelayedWindowEvent* >( e_P )->w ); else TQObject::customEvent( e_P ); } void KStartupInfo::window_added( WId w_P ) { KStartupInfoId id; KStartupInfoData data; startup_t ret = check_startup_internal( w_P, &id, &data ); switch( ret ) { case Match: kdDebug( 172 ) << "new window match" << endl; break; case NoMatch: break; // nothing case CantDetect: if( d->flags & CleanOnCantDetect ) clean_all_noncompliant(); break; } } void KStartupInfo::got_startup_info( const TQString& msg_P, bool update_P ) { KStartupInfoId id( msg_P ); if( id.none()) return; KStartupInfo::Data data( msg_P ); new_startup_info_internal( id, data, update_P ); } void KStartupInfo::new_startup_info_internal( const KStartupInfoId& id_P, Data& data_P, bool update_P ) { if( d == NULL ) return; if( id_P.none()) return; if( d->startups.contains( id_P )) { // already reported, update d->startups[ id_P ].update( data_P ); d->startups[ id_P ].age = 0; // CHECKME kdDebug( 172 ) << "updating" << endl; if( d->startups[ id_P ].silent() == Data::Yes && !( d->flags & AnnounceSilenceChanges )) { d->silent_startups[ id_P ] = d->startups[ id_P ]; d->startups.remove( id_P ); emit gotRemoveStartup( id_P, d->silent_startups[ id_P ] ); return; } emit gotStartupChange( id_P, d->startups[ id_P ] ); return; } if( d->silent_startups.contains( id_P )) { // already reported, update d->silent_startups[ id_P ].update( data_P ); d->silent_startups[ id_P ].age = 0; // CHECKME kdDebug( 172 ) << "updating silenced" << endl; if( d->silent_startups[ id_P ].silent() != Data::Yes ) { d->startups[ id_P ] = d->silent_startups[ id_P ]; d->silent_startups.remove( id_P ); emit gotNewStartup( id_P, d->startups[ id_P ] ); return; } emit gotStartupChange( id_P, d->silent_startups[ id_P ] ); return; } if( d->uninited_startups.contains( id_P )) { d->uninited_startups[ id_P ].update( data_P ); kdDebug( 172 ) << "updating uninited" << endl; if( !update_P ) // uninited finally got new: { d->startups[ id_P ] = d->uninited_startups[ id_P ]; d->uninited_startups.remove( id_P ); emit gotNewStartup( id_P, d->startups[ id_P ] ); return; } // no change announce, it's still uninited return; } if( update_P ) // change: without any new: first { kdDebug( 172 ) << "adding uninited" << endl; d->uninited_startups.insert( id_P, data_P ); } else if( data_P.silent() != Data::Yes || d->flags & AnnounceSilenceChanges ) { kdDebug( 172 ) << "adding" << endl; d->startups.insert( id_P, data_P ); emit gotNewStartup( id_P, data_P ); } else // new silenced, and silent shouldn't be announced { kdDebug( 172 ) << "adding silent" << endl; d->silent_startups.insert( id_P, data_P ); } d->cleanup->start( 1000 ); // 1 sec } void KStartupInfo::got_remove_startup_info( const TQString& msg_P ) { KStartupInfoId id( msg_P ); KStartupInfoData data( msg_P ); if( data.pids().count() > 0 ) { if( !id.none()) remove_startup_pids( id, data ); else remove_startup_pids( data ); return; } remove_startup_info_internal( id ); } void KStartupInfo::remove_startup_info_internal( const KStartupInfoId& id_P ) { if( d == NULL ) return; if( d->startups.contains( id_P )) { kdDebug( 172 ) << "removing" << endl; emit gotRemoveStartup( id_P, d->startups[ id_P ]); d->startups.remove( id_P ); } else if( d->silent_startups.contains( id_P )) { kdDebug( 172 ) << "removing silent" << endl; d->silent_startups.remove( id_P ); } else if( d->uninited_startups.contains( id_P )) { kdDebug( 172 ) << "removing uninited" << endl; d->uninited_startups.remove( id_P ); } return; } void KStartupInfo::remove_startup_pids( const KStartupInfoData& data_P ) { // first find the matching info if( d == NULL ) return; for( TQMap< KStartupInfoId, Data >::Iterator it = d->startups.begin(); it != d->startups.end(); ++it ) { if( ( *it ).hostname() != data_P.hostname()) continue; if( !( *it ).is_pid( data_P.pids().first())) continue; // not the matching info remove_startup_pids( it.key(), data_P ); break; } } void KStartupInfo::remove_startup_pids( const KStartupInfoId& id_P, const KStartupInfoData& data_P ) { if( d == NULL ) return; kdFatal( data_P.pids().count() == 0, 172 ); Data* data = NULL; if( d->startups.contains( id_P )) data = &d->startups[ id_P ]; else if( d->silent_startups.contains( id_P )) data = &d->silent_startups[ id_P ]; else if( d->uninited_startups.contains( id_P )) data = &d->uninited_startups[ id_P ]; else return; for( TQValueList< pid_t >::ConstIterator it2 = data_P.pids().begin(); it2 != data_P.pids().end(); ++it2 ) data->remove_pid( *it2 ); // remove all pids from the info if( data->pids().count() == 0 ) // all pids removed -> remove info remove_startup_info_internal( id_P ); } bool KStartupInfo::sendStartup( const KStartupInfoId& id_P, const KStartupInfoData& data_P ) { if( id_P.none()) return false; KXMessages msgs; TQString msg = TQString::tqfromLatin1( "new: %1 %2" ) .arg( id_P.to_text()).arg( data_P.to_text()); msg = check_required_startup_fields( msg, data_P, qt_xscreen()); kdDebug( 172 ) << "sending " << msg << endl; msgs.broadcastMessage( NET_STARTUP_MSG, msg, -1, false ); return true; } bool KStartupInfo::sendStartupX( Display* disp_P, const KStartupInfoId& id_P, const KStartupInfoData& data_P ) { if( id_P.none()) return false; TQString msg = TQString::tqfromLatin1( "new: %1 %2" ) .arg( id_P.to_text()).arg( data_P.to_text()); msg = check_required_startup_fields( msg, data_P, DefaultScreen( disp_P )); #ifdef KSTARTUPINFO_ALL_DEBUG kdDebug( 172 ) << "sending " << msg << endl; #endif return KXMessages::broadcastMessageX( disp_P, NET_STARTUP_MSG, msg, -1, false ); } TQString KStartupInfo::check_required_startup_fields( const TQString& msg, const KStartupInfoData& data_P, int screen ) { TQString ret = msg; if( data_P.name().isEmpty()) { // kdWarning( 172 ) << "NAME not specified in initial startup message" << endl; TQString name = data_P.bin(); if( name.isEmpty()) name = "UNKNOWN"; ret += TQString( " NAME=\"%1\"" ).arg( escape_str( name )); } if( data_P.screen() == -1 ) // add automatically if needed ret += TQString( " SCREEN=%1" ).arg( screen ); return ret; } bool KStartupInfo::sendChange( const KStartupInfoId& id_P, const KStartupInfoData& data_P ) { if( id_P.none()) return false; KXMessages msgs; TQString msg = TQString::tqfromLatin1( "change: %1 %2" ) .arg( id_P.to_text()).arg( data_P.to_text()); kdDebug( 172 ) << "sending " << msg << endl; msgs.broadcastMessage( NET_STARTUP_MSG, msg, -1, false ); return true; } bool KStartupInfo::sendChangeX( Display* disp_P, const KStartupInfoId& id_P, const KStartupInfoData& data_P ) { if( id_P.none()) return false; TQString msg = TQString::tqfromLatin1( "change: %1 %2" ) .arg( id_P.to_text()).arg( data_P.to_text()); #ifdef KSTARTUPINFO_ALL_DEBUG kdDebug( 172 ) << "sending " << msg << endl; #endif return KXMessages::broadcastMessageX( disp_P, NET_STARTUP_MSG, msg, -1, false ); } bool KStartupInfo::sendFinish( const KStartupInfoId& id_P ) { if( id_P.none()) return false; KXMessages msgs; TQString msg = TQString::tqfromLatin1( "remove: %1" ).arg( id_P.to_text()); kdDebug( 172 ) << "sending " << msg << endl; msgs.broadcastMessage( NET_STARTUP_MSG, msg, -1, false ); return true; } bool KStartupInfo::sendFinishX( Display* disp_P, const KStartupInfoId& id_P ) { if( id_P.none()) return false; TQString msg = TQString::tqfromLatin1( "remove: %1" ).arg( id_P.to_text()); #ifdef KSTARTUPINFO_ALL_DEBUG kdDebug( 172 ) << "sending " << msg << endl; #endif return KXMessages::broadcastMessageX( disp_P, NET_STARTUP_MSG, msg, -1, false ); } bool KStartupInfo::sendFinish( const KStartupInfoId& id_P, const KStartupInfoData& data_P ) { // if( id_P.none()) // id may be none, the pids and hostname matter then // return false; KXMessages msgs; TQString msg = TQString::tqfromLatin1( "remove: %1 %2" ) .arg( id_P.to_text()).arg( data_P.to_text()); kdDebug( 172 ) << "sending " << msg << endl; msgs.broadcastMessage( NET_STARTUP_MSG, msg, -1, false ); return true; } bool KStartupInfo::sendFinishX( Display* disp_P, const KStartupInfoId& id_P, const KStartupInfoData& data_P ) { // if( id_P.none()) // id may be none, the pids and hostname matter then // return false; TQString msg = TQString::tqfromLatin1( "remove: %1 %2" ) .arg( id_P.to_text()).arg( data_P.to_text()); #ifdef KSTARTUPINFO_ALL_DEBUG kdDebug( 172 ) << "sending " << msg << endl; #endif return KXMessages::broadcastMessageX( disp_P, NET_STARTUP_MSG, msg, -1, false ); } void KStartupInfo::appStarted() { if( kapp != NULL ) // KApplication constructor unsets the env. variable appStarted( kapp->startupId()); else appStarted( KStartupInfo::currentStartupIdEnv().id()); } void KStartupInfo::appStarted( const TQCString& startup_id ) { KStartupInfoId id; id.initId( startup_id ); if( id.none()) return; if( kapp != NULL ) KStartupInfo::sendFinish( id ); else if( getenv( "DISPLAY" ) != NULL ) // don't rely on qt_xdisplay() { #ifdef Q_WS_X11 Display* disp = XOpenDisplay( NULL ); if( disp != NULL ) { KStartupInfo::sendFinishX( disp, id ); XCloseDisplay( disp ); } #endif } } void KStartupInfo::disableAutoAppStartedSending( bool disable ) { auto_app_started_sending = !disable; } void KStartupInfo::silenceStartup( bool silence ) { KStartupInfoId id; id.initId( kapp->startupId()); if( id.none()) return; KStartupInfoData data; data.setSilent( silence ? KStartupInfoData::Yes : KStartupInfoData::No ); sendChange( id, data ); } void KStartupInfo::handleAutoAppStartedSending() { if( auto_app_started_sending ) appStarted(); } void KStartupInfo::setNewStartupId( TQWidget* window, const TQCString& startup_id ) { bool activate = true; kapp->setStartupId( startup_id ); if( window != NULL ) { if( !startup_id.isEmpty() && startup_id != "0" ) { NETRootInfo i( qt_xdisplay(), NET::Supported ); if( i.isSupported( NET::WM2StartupId )) { KStartupInfo::setWindowStartupId( window->winId(), startup_id ); activate = false; // WM will take care of it } } if( activate ) { KWin::setOnDesktop( window->winId(), KWin::currentDesktop()); // This is not very nice, but there's no way how to get any // usable timestamp without ASN, so force activating the window. // And even with ASN, it's not possible to get the timestamp here, // so if the WM doesn't have support for ASN, it can't be used either. KWin::forceActiveWindow( window->winId()); } } KStartupInfo::handleAutoAppStartedSending(); } KStartupInfo::startup_t KStartupInfo::checkStartup( WId w_P, KStartupInfoId& id_O, KStartupInfoData& data_O ) { return check_startup_internal( w_P, &id_O, &data_O ); } KStartupInfo::startup_t KStartupInfo::checkStartup( WId w_P, KStartupInfoId& id_O ) { return check_startup_internal( w_P, &id_O, NULL ); } KStartupInfo::startup_t KStartupInfo::checkStartup( WId w_P, KStartupInfoData& data_O ) { return check_startup_internal( w_P, NULL, &data_O ); } KStartupInfo::startup_t KStartupInfo::checkStartup( WId w_P ) { return check_startup_internal( w_P, NULL, NULL ); } KStartupInfo::startup_t KStartupInfo::check_startup_internal( WId w_P, KStartupInfoId* id_O, KStartupInfoData* data_O ) { if( d == NULL ) return NoMatch; if( d->startups.count() == 0 ) return NoMatch; // no startups // Strategy: // // Is this a compliant app ? // - Yes - test for match // - No - Is this a NET_WM compliant app ? // - Yes - test for pid match // - No - test for WM_CLASS match kdDebug( 172 ) << "check_startup" << endl; TQCString id = windowStartupId( w_P ); if( !id.isNull()) { if( id.isEmpty() || id == "0" ) // means ignore this window { kdDebug( 172 ) << "ignore" << endl; return NoMatch; } return find_id( id, id_O, data_O ) ? Match : NoMatch; } #ifdef Q_WS_X11 NETWinInfo info( qt_xdisplay(), w_P, qt_xrootwin(), NET::WMWindowType | NET::WMPid | NET::WMState ); pid_t pid = info.pid(); if( pid > 0 ) { TQCString hostname = get_window_hostname( w_P ); if( !hostname.isEmpty() && find_pid( pid, hostname, id_O, data_O )) return Match; // try XClass matching , this PID stuff sucks :( } XClassHint hint; if( XGetClassHint( qt_xdisplay(), w_P, &hint ) != 0 ) { // We managed to read the class hint TQCString res_name = hint.res_name; TQCString res_class = hint.res_class; XFree( hint.res_name ); XFree( hint.res_class ); if( find_wclass( res_name, res_class, id_O, data_O )) return Match; } // ignore NET::Tool and other special window types, if they can't be matched NET::WindowType type = info.windowType( NET::NormalMask | NET::DesktopMask | NET::DockMask | NET::ToolbarMask | NET::MenuMask | NET::DialogMask | NET::OverrideMask | NET::TopMenuMask | NET::UtilityMask | NET::SplashMask ); if( type != NET::Normal && type != NET::Override && type != NET::Unknown && type != NET::Dialog && type != NET::Utility ) // && type != NET::Dock ) why did I put this here? return NoMatch; // lets see if this is a transient Window transient_for; if( XGetTransientForHint( qt_xdisplay(), static_cast< Window >( w_P ), &transient_for ) && static_cast< WId >( transient_for ) != qt_xrootwin() && transient_for != None ) return NoMatch; #endif kdDebug( 172 ) << "check_startup:cantdetect" << endl; return CantDetect; } bool KStartupInfo::find_id( const TQCString& id_P, KStartupInfoId* id_O, KStartupInfoData* data_O ) { if( d == NULL ) return false; kdDebug( 172 ) << "find_id:" << id_P << endl; KStartupInfoId id; id.initId( id_P ); if( d->startups.contains( id )) { if( id_O != NULL ) *id_O = id; if( data_O != NULL ) *data_O = d->startups[ id ]; kdDebug( 172 ) << "check_startup_id:match" << endl; return true; } return false; } bool KStartupInfo::find_pid( pid_t pid_P, const TQCString& hostname_P, KStartupInfoId* id_O, KStartupInfoData* data_O ) { if( d == NULL ) return false; kdDebug( 172 ) << "find_pid:" << pid_P << endl; for( TQMap< KStartupInfoId, Data >::Iterator it = d->startups.begin(); it != d->startups.end(); ++it ) { if( ( *it ).is_pid( pid_P ) && ( *it ).hostname() == hostname_P ) { // Found it ! if( id_O != NULL ) *id_O = it.key(); if( data_O != NULL ) *data_O = *it; // non-compliant, remove on first match remove_startup_info_internal( it.key()); kdDebug( 172 ) << "check_startup_pid:match" << endl; return true; } } return false; } bool KStartupInfo::find_wclass( TQCString res_name, TQCString res_class, KStartupInfoId* id_O, KStartupInfoData* data_O ) { if( d == NULL ) return false; res_name = res_name.lower(); res_class = res_class.lower(); kdDebug( 172 ) << "find_wclass:" << res_name << ":" << res_class << endl; for( TQMap< KStartupInfoId, Data >::Iterator it = d->startups.begin(); it != d->startups.end(); ++it ) { const TQCString wmclass = ( *it ).findWMClass(); if( wmclass.lower() == res_name || wmclass.lower() == res_class ) { // Found it ! if( id_O != NULL ) *id_O = it.key(); if( data_O != NULL ) *data_O = *it; // non-compliant, remove on first match remove_startup_info_internal( it.key()); kdDebug( 172 ) << "check_startup_wclass:match" << endl; return true; } } return false; } #ifdef Q_WS_X11 static Atom net_startup_atom = None; static TQCString read_startup_id_property( WId w_P ) { TQCString ret; unsigned char *name_ret; Atom type_ret; int format_ret; unsigned long nitems_ret = 0, after_ret = 0; if( XGetWindowProperty( qt_xdisplay(), w_P, net_startup_atom, 0l, 4096, False, utf8_string_atom, &type_ret, &format_ret, &nitems_ret, &after_ret, &name_ret ) == Success ) { if( type_ret == utf8_string_atom && format_ret == 8 && name_ret != NULL ) ret = reinterpret_cast< char* >( name_ret ); if ( name_ret != NULL ) XFree( name_ret ); } return ret; } #endif TQCString KStartupInfo::windowStartupId( WId w_P ) { #ifdef Q_WS_X11 if( net_startup_atom == None ) net_startup_atom = XInternAtom( qt_xdisplay(), NET_STARTUP_WINDOW, False ); if( utf8_string_atom == None ) utf8_string_atom = XInternAtom( qt_xdisplay(), "UTF8_STRING", False ); TQCString ret = read_startup_id_property( w_P ); if( ret.isEmpty()) { // retry with window group leader, as the spec says XWMHints* hints = XGetWMHints( qt_xdisplay(), w_P ); if( hints && ( hints->flags & WindowGroupHint ) != 0 ) ret = read_startup_id_property( hints->window_group ); if( hints ) XFree( hints ); } return ret; #else return TQCString(); #endif } void KStartupInfo::setWindowStartupId( WId w_P, const TQCString& id_P ) { #ifdef Q_WS_X11 if( id_P.isNull()) return; if( net_startup_atom == None ) net_startup_atom = XInternAtom( qt_xdisplay(), NET_STARTUP_WINDOW, False ); if( utf8_string_atom == None ) utf8_string_atom = XInternAtom( qt_xdisplay(), "UTF8_STRING", False ); XChangeProperty( qt_xdisplay(), w_P, net_startup_atom, utf8_string_atom, 8, PropModeReplace, reinterpret_cast< unsigned char* >( const_cast(id_P).data()), id_P.length()); #endif } TQCString KStartupInfo::get_window_hostname( WId w_P ) { #ifdef Q_WS_X11 XTextProperty tp; char** hh; int cnt; if( XGetWMClientMachine( qt_xdisplay(), w_P, &tp ) != 0 && XTextPropertyToStringList( &tp, &hh, &cnt ) != 0 ) { if( cnt == 1 ) { TQCString hostname = hh[ 0 ]; XFreeStringList( hh ); return hostname; } XFreeStringList( hh ); } #endif // no hostname return TQCString(); } void KStartupInfo::setTimeout( unsigned int secs_P ) { timeout = secs_P; // schedule removing entries that are older than the new timeout TQTimer::singleShot( 0, this, TQT_SLOT( startups_cleanup_no_age())); } void KStartupInfo::startups_cleanup_no_age() { startups_cleanup_internal( false ); } void KStartupInfo::startups_cleanup() { if( d == NULL ) return; if( d->startups.count() == 0 && d->silent_startups.count() == 0 && d->uninited_startups.count() == 0 ) { d->cleanup->stop(); return; } startups_cleanup_internal( true ); } void KStartupInfo::startups_cleanup_internal( bool age_P ) { if( d == NULL ) return; for( TQMap< KStartupInfoId, Data >::Iterator it = d->startups.begin(); it != d->startups.end(); ) { if( age_P ) ( *it ).age++; unsigned int tout = timeout; if( ( *it ).silent() == Data::Yes ) // TODO tout *= 20; if( ( *it ).age >= tout ) { const KStartupInfoId& key = it.key(); ++it; kdDebug( 172 ) << "startups entry timeout:" << key.id() << endl; remove_startup_info_internal( key ); } else ++it; } for( TQMap< KStartupInfoId, Data >::Iterator it = d->silent_startups.begin(); it != d->silent_startups.end(); ) { if( age_P ) ( *it ).age++; unsigned int tout = timeout; if( ( *it ).silent() == Data::Yes ) // TODO tout *= 20; if( ( *it ).age >= tout ) { const KStartupInfoId& key = it.key(); ++it; kdDebug( 172 ) << "silent entry timeout:" << key.id() << endl; remove_startup_info_internal( key ); } else ++it; } for( TQMap< KStartupInfoId, Data >::Iterator it = d->uninited_startups.begin(); it != d->uninited_startups.end(); ) { if( age_P ) ( *it ).age++; unsigned int tout = timeout; if( ( *it ).silent() == Data::Yes ) // TODO tout *= 20; if( ( *it ).age >= tout ) { const KStartupInfoId& key = it.key(); ++it; kdDebug( 172 ) << "uninited entry timeout:" << key.id() << endl; remove_startup_info_internal( key ); } else ++it; } } void KStartupInfo::clean_all_noncompliant() { if( d == NULL ) return; for( TQMap< KStartupInfoId, Data >::Iterator it = d->startups.begin(); it != d->startups.end(); ) { if( ( *it ).WMClass() != "0" ) { ++it; continue; } const KStartupInfoId& key = it.key(); ++it; kdDebug( 172 ) << "entry cleaning:" << key.id() << endl; remove_startup_info_internal( key ); } } TQCString KStartupInfo::createNewStartupId() { // Assign a unique id, use hostname+time+pid, that should be 200% unique. // Also append the user timestamp (for focus stealing prevention). struct timeval tm; gettimeofday( &tm, NULL ); char hostname[ 256 ]; hostname[ 0 ] = '\0'; if (!gethostname( hostname, 255 )) hostname[sizeof(hostname)-1] = '\0'; TQCString id = TQString(TQString( "%1;%2;%3;%4_TIME%5" ).arg( hostname ).arg( tm.tv_sec ) .arg( tm.tv_usec ).arg( getpid()).arg( GET_QT_X_USER_TIME() )).utf8(); kdDebug( 172 ) << "creating: " << id << ":" << tqAppName() << endl; return id; } struct KStartupInfoIdPrivate { KStartupInfoIdPrivate() : id( "" ) {} TQCString id; // id }; const TQCString& KStartupInfoId::id() const { return d->id; } TQString KStartupInfoId::to_text() const { return TQString::tqfromLatin1( " ID=\"%1\" " ).arg( escape_str( id())); } KStartupInfoId::KStartupInfoId( const TQString& txt_P ) { d = new KStartupInfoIdPrivate; TQStringList items = get_fields( txt_P ); const TQString id_str = TQString::tqfromLatin1( "ID=" ); for( TQStringList::Iterator it = items.begin(); it != items.end(); ++it ) { if( ( *it ).startsWith( id_str )) d->id = get_cstr( *it ); } } void KStartupInfoId::initId( const TQCString& id_P ) { if( !id_P.isEmpty()) { d->id = id_P; #ifdef KSTARTUPINFO_ALL_DEBUG kdDebug( 172 ) << "using: " << d->id << endl; #endif return; } const char* startup_env = getenv( NET_STARTUP_ENV ); if( startup_env != NULL && *startup_env != '\0' ) { // already has id d->id = startup_env; #ifdef KSTARTUPINFO_ALL_DEBUG kdDebug( 172 ) << "reusing: " << d->id << endl; #endif return; } d->id = KStartupInfo::createNewStartupId(); } bool KStartupInfoId::setupStartupEnv() const { if( id().isEmpty()) { unsetenv( NET_STARTUP_ENV ); return false; } return setenv( NET_STARTUP_ENV, id(), true ) == 0; } KStartupInfoId KStartupInfo::currentStartupIdEnv() { const char* startup_env = getenv( NET_STARTUP_ENV ); KStartupInfoId id; if( startup_env != NULL && *startup_env != '\0' ) id.d->id = startup_env; else id.d->id = "0"; return id; } void KStartupInfo::resetStartupEnv() { unsetenv( NET_STARTUP_ENV ); } KStartupInfoId::KStartupInfoId() { d = new KStartupInfoIdPrivate; } KStartupInfoId::~KStartupInfoId() { delete d; } KStartupInfoId::KStartupInfoId( const KStartupInfoId& id_P ) { d = new KStartupInfoIdPrivate( *id_P.d ); } KStartupInfoId& KStartupInfoId::operator=( const KStartupInfoId& id_P ) { if( &id_P == this ) return *this; delete d; d = new KStartupInfoIdPrivate( *id_P.d ); return *this; } bool KStartupInfoId::operator==( const KStartupInfoId& id_P ) const { return id() == id_P.id(); } bool KStartupInfoId::operator!=( const KStartupInfoId& id_P ) const { return !(*this == id_P ); } // needed for QMap bool KStartupInfoId::operator<( const KStartupInfoId& id_P ) const { return id() < id_P.id(); } bool KStartupInfoId::none() const { return d->id.isEmpty() || d->id == "0"; } unsigned long KStartupInfoId::timestamp() const { if( none()) return 0; int pos = d->id.findRev( "_TIME" ); if( pos >= 0 ) { bool ok; unsigned long time = d->id.mid( pos + 5 ).toULong( &ok ); if( !ok && d->id[ pos + 5 ] == '-' ) // try if it's as a negative signed number perhaps time = d->id.mid( pos + 5 ).toLong( &ok ); if( ok ) return time; } // libstartup-notification style : // snprintf (s, len, "%s/%s/%lu/%d-%d-%s", // canonicalized_launcher, canonicalized_launchee, (unsigned long) timestamp, // (int) getpid (), (int) sequence_number, hostbuf); int pos1 = d->id.findRev( '/' ); if( pos1 > 0 ) { int pos2 = d->id.findRev( '/', pos1 - 1 ); if( pos2 >= 0 ) { bool ok; unsigned long time = d->id.mid( pos2 + 1, pos1 - pos2 - 1 ).toULong( &ok ); if( !ok && d->id[ pos2 + 1 ] == '-' ) // try if it's as a negative signed number perhaps time = d->id.mid( pos2 + 1, pos1 - pos2 - 1 ).toLong( &ok ); if( ok ) return time; } } // bah ... old KStartupInfo or a problem return 0; } struct KStartupInfoDataPrivate { KStartupInfoDataPrivate() : desktop( 0 ), wmclass( "" ), hostname( "" ), silent( KStartupInfoData::Unknown ), timestamp( -1U ), screen( -1 ), xinerama( -1 ), launched_by( 0 ) {} TQString bin; TQString name; TQString description; TQString icon; int desktop; TQValueList< pid_t > pids; TQCString wmclass; TQCString hostname; KStartupInfoData::TriState silent; unsigned long timestamp; int screen; int xinerama; WId launched_by; }; TQString KStartupInfoData::to_text() const { TQString ret = ""; if( !d->bin.isEmpty()) ret += TQString::tqfromLatin1( " BIN=\"%1\"" ).arg( escape_str( d->bin )); if( !d->name.isEmpty()) ret += TQString::tqfromLatin1( " NAME=\"%1\"" ).arg( escape_str( d->name )); if( !d->description.isEmpty()) ret += TQString::tqfromLatin1( " DESCRIPTION=\"%1\"" ).arg( escape_str( d->description )); if( !d->icon.isEmpty()) ret += TQString::tqfromLatin1( " ICON=%1" ).arg( d->icon ); if( d->desktop != 0 ) ret += TQString::tqfromLatin1( " DESKTOP=%1" ) .arg( d->desktop == NET::OnAllDesktops ? NET::OnAllDesktops : d->desktop - 1 ); // spec counts from 0 if( !d->wmclass.isEmpty()) ret += TQString::tqfromLatin1( " WMCLASS=\"%1\"" ).arg( QString(d->wmclass) ); if( !d->hostname.isEmpty()) ret += TQString::tqfromLatin1( " HOSTNAME=%1" ).arg( QString(d->hostname) ); for( TQValueList< pid_t >::ConstIterator it = d->pids.begin(); it != d->pids.end(); ++it ) ret += TQString::tqfromLatin1( " PID=%1" ).arg( *it ); if( d->silent != Unknown ) ret += TQString::tqfromLatin1( " SILENT=%1" ).arg( d->silent == Yes ? 1 : 0 ); if( d->timestamp != -1U ) ret += TQString::tqfromLatin1( " TIMESTAMP=%1" ).arg( d->timestamp ); if( d->screen != -1 ) ret += TQString::tqfromLatin1( " SCREEN=%1" ).arg( d->screen ); if( d->xinerama != -1 ) ret += TQString::tqfromLatin1( " XINERAMA=%1" ).arg( d->xinerama ); if( d->launched_by != 0 ) ret += TQString::tqfromLatin1( " LAUNCHED_BY=%1" ).arg( d->launched_by ); return ret; } KStartupInfoData::KStartupInfoData( const TQString& txt_P ) { d = new KStartupInfoDataPrivate; TQStringList items = get_fields( txt_P ); const TQString bin_str = TQString::tqfromLatin1( "BIN=" ); const TQString name_str = TQString::tqfromLatin1( "NAME=" ); const TQString description_str = TQString::tqfromLatin1( "DESCRIPTION=" ); const TQString icon_str = TQString::tqfromLatin1( "ICON=" ); const TQString desktop_str = TQString::tqfromLatin1( "DESKTOP=" ); const TQString wmclass_str = TQString::tqfromLatin1( "WMCLASS=" ); const TQString hostname_str = TQString::tqfromLatin1( "HOSTNAME=" ); // SELI nonstd const TQString pid_str = TQString::tqfromLatin1( "PID=" ); // SELI nonstd const TQString silent_str = TQString::tqfromLatin1( "SILENT=" ); const TQString timestamp_str = TQString::tqfromLatin1( "TIMESTAMP=" ); const TQString screen_str = TQString::tqfromLatin1( "SCREEN=" ); const TQString xinerama_str = TQString::tqfromLatin1( "XINERAMA=" ); const TQString launched_by_str = TQString::tqfromLatin1( "LAUNCHED_BY=" ); for( TQStringList::Iterator it = items.begin(); it != items.end(); ++it ) { if( ( *it ).startsWith( bin_str )) d->bin = get_str( *it ); else if( ( *it ).startsWith( name_str )) d->name = get_str( *it ); else if( ( *it ).startsWith( description_str )) d->description = get_str( *it ); else if( ( *it ).startsWith( icon_str )) d->icon = get_str( *it ); else if( ( *it ).startsWith( desktop_str )) { d->desktop = get_num( *it ); if( d->desktop != NET::OnAllDesktops ) ++d->desktop; // spec counts from 0 } else if( ( *it ).startsWith( wmclass_str )) d->wmclass = get_cstr( *it ); else if( ( *it ).startsWith( hostname_str )) d->hostname = get_cstr( *it ); else if( ( *it ).startsWith( pid_str )) addPid( get_num( *it )); else if( ( *it ).startsWith( silent_str )) d->silent = get_num( *it ) != 0 ? Yes : No; else if( ( *it ).startsWith( timestamp_str )) d->timestamp = get_unum( *it ); else if( ( *it ).startsWith( screen_str )) d->screen = get_num( *it ); else if( ( *it ).startsWith( xinerama_str )) d->xinerama = get_num( *it ); else if( ( *it ).startsWith( launched_by_str )) d->launched_by = get_num( *it ); } } KStartupInfoData::KStartupInfoData( const KStartupInfoData& data ) { d = new KStartupInfoDataPrivate( *data.d ); } KStartupInfoData& KStartupInfoData::operator=( const KStartupInfoData& data ) { if( &data == this ) return *this; delete d; d = new KStartupInfoDataPrivate( *data.d ); return *this; } void KStartupInfoData::update( const KStartupInfoData& data_P ) { if( !data_P.bin().isEmpty()) d->bin = data_P.bin(); if( !data_P.name().isEmpty() && name().isEmpty()) // don't overwrite d->name = data_P.name(); if( !data_P.description().isEmpty() && description().isEmpty()) // don't overwrite d->description = data_P.description(); if( !data_P.icon().isEmpty() && icon().isEmpty()) // don't overwrite d->icon = data_P.icon(); if( data_P.desktop() != 0 && desktop() == 0 ) // don't overwrite d->desktop = data_P.desktop(); if( !data_P.d->wmclass.isEmpty()) d->wmclass = data_P.d->wmclass; if( !data_P.d->hostname.isEmpty()) d->hostname = data_P.d->hostname; for( TQValueList< pid_t >::ConstIterator it = data_P.d->pids.begin(); it != data_P.d->pids.end(); ++it ) addPid( *it ); if( data_P.silent() != Unknown ) d->silent = data_P.silent(); if( data_P.timestamp() != -1U && timestamp() == -1U ) // don't overwrite d->timestamp = data_P.timestamp(); if( data_P.screen() != -1 ) d->screen = data_P.screen(); if( data_P.xinerama() != -1 && xinerama() != -1 ) // don't overwrite d->xinerama = data_P.xinerama(); if( data_P.launchedBy() != 0 && launchedBy() != 0 ) // don't overwrite d->launched_by = data_P.launchedBy(); } KStartupInfoData::KStartupInfoData() { d = new KStartupInfoDataPrivate; } KStartupInfoData::~KStartupInfoData() { delete d; } void KStartupInfoData::setBin( const TQString& bin_P ) { d->bin = bin_P; } const TQString& KStartupInfoData::bin() const { return d->bin; } void KStartupInfoData::setName( const TQString& name_P ) { d->name = name_P; } const TQString& KStartupInfoData::name() const { return d->name; } const TQString& KStartupInfoData::findName() const { if( !name().isEmpty()) return name(); return bin(); } void KStartupInfoData::setDescription( const TQString& desc_P ) { d->description = desc_P; } const TQString& KStartupInfoData::description() const { return d->description; } const TQString& KStartupInfoData::findDescription() const { if( !description().isEmpty()) return description(); return name(); } void KStartupInfoData::setIcon( const TQString& icon_P ) { d->icon = icon_P; } const TQString& KStartupInfoData::findIcon() const { if( !icon().isEmpty()) return icon(); return bin(); } const TQString& KStartupInfoData::icon() const { return d->icon; } void KStartupInfoData::setDesktop( int desktop_P ) { d->desktop = desktop_P; } int KStartupInfoData::desktop() const { return d->desktop; } void KStartupInfoData::setWMClass( const TQCString& wmclass_P ) { d->wmclass = wmclass_P; } const TQCString KStartupInfoData::findWMClass() const { if( !WMClass().isEmpty() && WMClass() != "0" ) return WMClass(); return bin().utf8(); } const TQCString& KStartupInfoData::WMClass() const { return d->wmclass; } void KStartupInfoData::setHostname( const TQCString& hostname_P ) { if( !hostname_P.isNull()) d->hostname = hostname_P; else { char tmp[ 256 ]; tmp[ 0 ] = '\0'; if (!gethostname( tmp, 255 )) tmp[sizeof(tmp)-1] = '\0'; d->hostname = tmp; } } const TQCString& KStartupInfoData::hostname() const { return d->hostname; } void KStartupInfoData::addPid( pid_t pid_P ) { if( !d->pids.contains( pid_P )) d->pids.append( pid_P ); } void KStartupInfoData::remove_pid( pid_t pid_P ) { d->pids.remove( pid_P ); } const TQValueList< pid_t >& KStartupInfoData::pids() const { return d->pids; } bool KStartupInfoData::is_pid( pid_t pid_P ) const { return d->pids.contains( pid_P ); } void KStartupInfoData::setSilent( TriState state_P ) { d->silent = state_P; } KStartupInfoData::TriState KStartupInfoData::silent() const { return d->silent; } void KStartupInfoData::setTimestamp( unsigned long time ) { d->timestamp = time; } unsigned long KStartupInfoData::timestamp() const { return d->timestamp; } void KStartupInfoData::setScreen( int screen ) { d->screen = screen; } int KStartupInfoData::screen() const { return d->screen; } void KStartupInfoData::setXinerama( int xinerama ) { d->xinerama = xinerama; } int KStartupInfoData::xinerama() const { return d->xinerama; } void KStartupInfoData::setLaunchedBy( WId window ) { d->launched_by = window; } WId KStartupInfoData::launchedBy() const { return d->launched_by; } static long get_num( const TQString& item_P ) { unsigned int pos = item_P.find( '=' ); return item_P.mid( pos + 1 ).toLong(); } static unsigned long get_unum( const TQString& item_P ) { unsigned int pos = item_P.find( '=' ); return item_P.mid( pos + 1 ).toULong(); } static TQString get_str( const TQString& item_P ) { unsigned int pos = item_P.find( '=' ); if( item_P.length() > pos + 2 && item_P[ pos + 1 ] == (QChar)'\"' ) { int pos2 = item_P.left( pos + 2 ).find( '\"' ); if( pos2 < 0 ) return TQString::null; // 01234 return item_P.mid( pos + 2, pos2 - 2 - pos ); // A="C" } return item_P.mid( pos + 1 ); } static TQCString get_cstr( const TQString& item_P ) { return get_str( item_P ).utf8(); } static TQStringList get_fields( const TQString& txt_P ) { TQString txt = txt_P.simplifyWhiteSpace(); TQStringList ret; TQString item = ""; bool in = false; bool escape = false; for( unsigned int pos = 0; pos < txt.length(); ++pos ) { if( escape ) { item += txt[ pos ]; escape = false; } else if( txt[ pos ] == '\\' ) escape = true; else if( txt[ pos ] == '\"' ) in = !in; else if( txt[ pos ] == ' ' && !in ) { ret.append( item ); item = ""; } else item += txt[ pos ]; } ret.append( item ); return ret; } static TQString escape_str( const TQString& str_P ) { TQString ret = ""; for( unsigned int pos = 0; pos < str_P.length(); ++pos ) { if( str_P[ pos ] == (QChar)'\\' || str_P[ pos ] == (QChar)'"' ) ret += '\\'; ret += str_P[ pos ]; } return ret; } #include "kstartupinfo.moc" #endif