27 #define KSTARTUPINFO_ALL_DEBUG
29 #warning Extra KStartupInfo debug messages enabled.
35 #include <QtGui/QWidget>
36 #include <QtCore/QBool>
41 #ifndef QT_CLEAN_NAMESPACE
42 #define QT_CLEAN_NAMESPACE
48 #include <QtCore/QTimer>
49 #include <QtGui/QActionEvent>
51 #include <qx11info_x11.h>
82 class KStartupInfo::Data
92 struct KStartupInfoId::Private
94 Private() :
id(
"" ) {}
101 struct KStartupInfoData::Private
107 void remove_pid( pid_t pid );
125 class KStartupInfo::Private
129 void startups_cleanup();
130 void startups_cleanup_no_age();
131 void got_message(
const QString& msg );
132 void window_added( WId w );
133 void slot_window_added( WId w );
135 void init(
int flags );
136 void got_startup_info(
const QString& msg_P,
bool update_only_P );
137 void got_remove_startup_info(
const QString& msg_P );
139 Data& data_P,
bool update_only_P );
146 bool find_pid( pid_t pid_P,
const QByteArray& hostname,
KStartupInfoId* id_O,
148 bool find_wclass(
const QByteArray &res_name_P,
const QByteArray &res_class_P,
150 static QByteArray get_window_hostname( WId w_P );
151 void startups_cleanup_internal(
bool age_P );
152 void clean_all_noncompliant();
180 void createConnections()
186 if( !QX11Info::display())
190 QObject::connect(
KWindowSystem::self(), SIGNAL(windowAdded(WId)), q, SLOT(slot_window_added(WId)));
192 #warning "systemTrayWindowAdded signal was remove from KWindowSystem class"
196 QObject::connect( &msgs, SIGNAL(gotMessage(
QString)), q, SLOT(got_message(
QString)));
197 cleanup =
new QTimer( q );
198 QObject::connect( cleanup, SIGNAL(
timeout()), q, SLOT(startups_cleanup()));
205 d(new Private(flags_P, this))
207 d->createConnections();
212 d(new Private(clean_on_cantdetect_P ? CleanOnCantDetect : 0, this))
214 d->createConnections();
223 void KStartupInfo::Private::got_message(
const QString& msg_P )
227 kDebug( 172 ) <<
"got:" << msg_P;
229 if( msg.startsWith( QLatin1String(
"new:") ))
230 got_startup_info( msg.mid( 4 ), false );
231 else if( msg.startsWith( QLatin1String(
"change:") ))
232 got_startup_info( msg.mid( 7 ), true );
233 else if( msg.startsWith( QLatin1String(
"remove:") ))
234 got_remove_startup_info( msg.mid( 7 ));
246 class DelayedWindowEvent
250 DelayedWindowEvent( WId w_P )
251 : QEvent( uniqueType() ), w( w_P ) {}
257 static Type uniqueType() {
return Type(QEvent::User+15); }
261 void KStartupInfo::Private::slot_window_added( WId w_P )
263 qApp->postEvent( q,
new DelayedWindowEvent( w_P ));
269 if( e_P->type() == DelayedWindowEvent::uniqueType() )
270 d->window_added( static_cast< DelayedWindowEvent* >( e_P )->w );
276 void KStartupInfo::Private::window_added( WId w_P )
280 startup_t ret = check_startup_internal( w_P, &
id, &data );
284 kDebug( 172 ) <<
"new window match";
289 if( flags & CleanOnCantDetect )
290 clean_all_noncompliant();
295 void KStartupInfo::Private::got_startup_info(
const QString& msg_P,
bool update_P )
300 KStartupInfo::Data data( msg_P );
301 new_startup_info_internal(
id, data, update_P );
304 void KStartupInfo::Private::new_startup_info_internal(
const KStartupInfoId& id_P,
305 KStartupInfo::Data& data_P,
bool update_P )
309 if( startups.contains( id_P ))
311 startups[ id_P ].update( data_P );
312 startups[ id_P ].age = 0;
313 kDebug( 172 ) <<
"updating";
315 && !( flags & AnnounceSilenceChanges ))
317 silent_startups[ id_P ] = startups[ id_P ];
318 startups.remove( id_P );
319 emit q->gotRemoveStartup( id_P, silent_startups[ id_P ] );
322 emit q->gotStartupChange( id_P, startups[ id_P ] );
325 if( silent_startups.contains( id_P ))
327 silent_startups[ id_P ].update( data_P );
328 silent_startups[ id_P ].age = 0;
329 kDebug( 172 ) <<
"updating silenced";
330 if( silent_startups[ id_P ].silent() !=
Data::Yes )
332 startups[ id_P ] = silent_startups[ id_P ];
333 silent_startups.remove( id_P );
334 q->emit gotNewStartup( id_P, startups[ id_P ] );
337 emit q->gotStartupChange( id_P, silent_startups[ id_P ] );
340 if( uninited_startups.contains( id_P ))
342 uninited_startups[ id_P ].update( data_P );
343 kDebug( 172 ) <<
"updating uninited";
346 startups[ id_P ] = uninited_startups[ id_P ];
347 uninited_startups.remove( id_P );
348 emit q->gotNewStartup( id_P, startups[ id_P ] );
356 kDebug( 172 ) <<
"adding uninited";
357 uninited_startups.insert( id_P, data_P );
359 else if( data_P.silent() !=
Data::Yes || flags & AnnounceSilenceChanges )
361 kDebug( 172 ) <<
"adding";
362 startups.insert( id_P, data_P );
363 emit q->gotNewStartup( id_P, data_P );
367 kDebug( 172 ) <<
"adding silent";
368 silent_startups.insert( id_P, data_P );
370 cleanup->start( 1000 );
373 void KStartupInfo::Private::got_remove_startup_info(
const QString& msg_P )
377 if( data.
pids().count() > 0 )
380 remove_startup_pids(
id, data );
382 remove_startup_pids( data );
385 remove_startup_info_internal(
id );
388 void KStartupInfo::Private::remove_startup_info_internal(
const KStartupInfoId& id_P )
390 if( startups.contains( id_P ))
392 kDebug( 172 ) <<
"removing";
393 emit q->gotRemoveStartup( id_P, startups[ id_P ]);
394 startups.remove( id_P );
396 else if( silent_startups.contains( id_P ))
398 kDebug( 172 ) <<
"removing silent";
399 silent_startups.remove( id_P );
401 else if( uninited_startups.contains( id_P ))
403 kDebug( 172 ) <<
"removing uninited";
404 uninited_startups.remove( id_P );
409 void KStartupInfo::Private::remove_startup_pids(
const KStartupInfoData& data_P )
412 it != startups.end();
415 if( ( *it ).hostname() != data_P.
hostname())
417 if( !( *it ).is_pid( data_P.
pids().first()))
419 remove_startup_pids( it.key(), data_P );
424 void KStartupInfo::Private::remove_startup_pids(
const KStartupInfoId& id_P,
429 if( startups.contains( id_P ))
430 data = &startups[ id_P ];
431 else if( silent_startups.contains( id_P ))
432 data = &silent_startups[ id_P ];
433 else if( uninited_startups.contains( id_P ))
434 data = &uninited_startups[ id_P ];
437 for( QList< pid_t >::ConstIterator it2 = data_P.
pids().constBegin();
438 it2 != data_P.
pids().constEnd();
440 data->d->remove_pid( *it2 );
441 if( data->pids().count() == 0 )
442 remove_startup_info_internal( id_P );
451 QString msg = QString::fromLatin1(
"new: %1 %2" )
452 .arg( id_P.d->to_text()).arg( data_P.d->to_text());
454 msg = Private::check_required_startup_fields( msg, data_P, inf.
screen());
455 kDebug( 172 ) <<
"sending " << msg;
467 QString msg = QString::fromLatin1(
"new: %1 %2" )
468 .arg( id_P.d->to_text()).arg( data_P.d->to_text());
469 msg = Private::check_required_startup_fields( msg, data_P, DefaultScreen( disp_P ));
470 #ifdef KSTARTUPINFO_ALL_DEBUG
471 kDebug( 172 ) <<
"sending " << msg;
483 if( data_P.
name().isEmpty())
491 if( data_P.
screen() == -1 )
492 ret +=
QString(
" SCREEN=%1" ).arg( screen );
502 QString msg = QString::fromLatin1(
"change: %1 %2" )
503 .arg( id_P.d->to_text()).arg( data_P.d->to_text());
504 kDebug( 172 ) <<
"sending " << msg;
516 QString msg = QString::fromLatin1(
"change: %1 %2" )
517 .arg( id_P.d->to_text()).arg( data_P.d->to_text());
518 #ifdef KSTARTUPINFO_ALL_DEBUG
519 kDebug( 172 ) <<
"sending " << msg;
533 QString msg = QString::fromLatin1(
"remove: %1" ).arg( id_P.d->to_text());
534 kDebug( 172 ) <<
"sending " << msg;
545 QString msg = QString::fromLatin1(
"remove: %1" ).arg( id_P.d->to_text());
546 #ifdef KSTARTUPINFO_ALL_DEBUG
547 kDebug( 172 ) <<
"sending " << msg;
561 QString msg = QString::fromLatin1(
"remove: %1 %2" )
562 .arg( id_P.d->to_text()).arg( data_P.d->to_text());
563 kDebug( 172 ) <<
"sending " << msg;
575 QString msg = QString::fromLatin1(
"remove: %1 %2" )
576 .arg( id_P.d->to_text()).arg( data_P.d->to_text());
577 #ifdef KSTARTUPINFO_ALL_DEBUG
578 kDebug( 172 ) <<
"sending " << msg;
591 kapp->clearStartupId();
608 else if( !qgetenv(
"DISPLAY" ).isEmpty() )
611 Display* disp = XOpenDisplay( NULL );
615 XCloseDisplay( disp );
623 auto_app_started_sending = !disable;
639 if( auto_app_started_sending )
645 bool activate =
true;
646 kapp->setStartupId( startup_id );
650 if( !startup_id.isEmpty() && startup_id !=
"0" )
676 return d->check_startup_internal( w_P, &id_O, &data_O );
681 return d->check_startup_internal( w_P, &id_O, NULL );
686 return d->check_startup_internal( w_P, NULL, &data_O );
691 return d->check_startup_internal( w_P, NULL, NULL );
697 if( startups.count() == 0 )
706 kDebug( 172 ) <<
"check_startup";
707 QByteArray
id = windowStartupId( w_P );
710 if(
id.isEmpty() ||
id ==
"0" )
712 kDebug( 172 ) <<
"ignore";
715 return find_id(
id, id_O, data_O ) ? Match : NoMatch;
718 NETWinInfo info( QX11Info::display(), w_P, QX11Info::appRootWindow(),
720 pid_t pid = info.pid();
723 QByteArray hostname = get_window_hostname( w_P );
724 if( !hostname.isEmpty()
725 && find_pid( pid, hostname, id_O, data_O ))
730 if( XGetClassHint( QX11Info::display(), w_P, &hint ) != 0 )
732 QByteArray res_name = hint.res_name;
733 QByteArray res_class = hint.res_class;
734 XFree( hint.res_name );
735 XFree( hint.res_class );
736 if( find_wclass( res_name, res_class, id_O, data_O ))
752 if( XGetTransientForHint( QX11Info::display(), static_cast< Window >( w_P ), &transient_for )
753 && static_cast< WId >( transient_for ) != QX11Info::appRootWindow()
754 && transient_for != None )
757 kDebug( 172 ) <<
"check_startup:cantdetect";
761 bool KStartupInfo::Private::find_id(
const QByteArray& id_P,
KStartupInfoId* id_O,
764 kDebug( 172 ) <<
"find_id:" << id_P;
767 if( startups.contains(
id ))
772 *data_O = startups[ id ];
773 kDebug( 172 ) <<
"check_startup_id:match";
779 bool KStartupInfo::Private::find_pid( pid_t pid_P,
const QByteArray& hostname_P,
782 kDebug( 172 ) <<
"find_pid:" << pid_P;
784 it != startups.end();
787 if( ( *it ).is_pid( pid_P ) && ( *it ).hostname() == hostname_P )
794 remove_startup_info_internal( it.key());
795 kDebug( 172 ) <<
"check_startup_pid:match";
802 bool KStartupInfo::Private::find_wclass(
const QByteArray &_res_name,
const QByteArray &_res_class,
805 QByteArray res_name = _res_name.toLower();
806 QByteArray res_class = _res_class.toLower();
807 kDebug( 172 ) <<
"find_wclass:" << res_name <<
":" << res_class;
809 it != startups.end();
812 const QByteArray wmclass = ( *it ).findWMClass();
813 if( wmclass.toLower() == res_name || wmclass.toLower() == res_class )
820 remove_startup_info_internal( it.key());
821 kDebug( 172 ) <<
"check_startup_wclass:match";
834 unsigned char *name_ret;
837 unsigned long nitems_ret = 0, after_ret = 0;
838 if( XGetWindowProperty( QX11Info::display(), w_P, net_startup_atom, 0l, 4096,
839 False,
utf8_string_atom, &type_ret, &format_ret, &nitems_ret, &after_ret, &name_ret )
843 ret =
reinterpret_cast< char*
>( name_ret );
844 if ( name_ret != NULL )
855 if( net_startup_atom ==
None )
858 utf8_string_atom = XInternAtom( QX11Info::display(),
"UTF8_STRING", False );
862 XWMHints* hints = XGetWMHints( QX11Info::display(), w_P );
863 if( hints && ( hints->flags & WindowGroupHint ) != 0 )
879 if( net_startup_atom ==
None )
882 utf8_string_atom = XInternAtom( QX11Info::display(),
"UTF8_STRING", False );
883 XChangeProperty( QX11Info::display(), w_P, net_startup_atom,
utf8_string_atom, 8,
884 PropModeReplace, reinterpret_cast< const unsigned char* >( id_P.data()), id_P.length());
888 QByteArray KStartupInfo::Private::get_window_hostname( WId w_P )
894 if( XGetWMClientMachine( QX11Info::display(), w_P, &tp ) != 0
895 && XTextPropertyToStringList( &tp, &hh, &cnt ) != 0 )
899 QByteArray hostname = hh[ 0 ];
900 XFreeStringList( hh );
903 XFreeStringList( hh );
914 QTimer::singleShot( 0,
this, SLOT(startups_cleanup_no_age()));
917 void KStartupInfo::Private::startups_cleanup_no_age()
919 startups_cleanup_internal(
false );
922 void KStartupInfo::Private::startups_cleanup()
924 if( startups.count() == 0 && silent_startups.count() == 0
925 && uninited_startups.count() == 0 )
930 startups_cleanup_internal(
true );
933 void KStartupInfo::Private::startups_cleanup_internal(
bool age_P )
936 it != startups.end();
944 if( ( *it ).age >= tout )
948 kDebug( 172 ) <<
"entry timeout:" << key.
id();
949 remove_startup_info_internal( key );
955 it != silent_startups.end();
963 if( ( *it ).age >= tout )
967 kDebug( 172 ) <<
"entry timeout:" << key.
id();
968 remove_startup_info_internal( key );
974 it != uninited_startups.end();
982 if( ( *it ).age >= tout )
986 kDebug( 172 ) <<
"entry timeout:" << key.
id();
987 remove_startup_info_internal( key );
994 void KStartupInfo::Private::clean_all_noncompliant()
997 it != startups.end();
1000 if( ( *it ).WMClass() !=
"0" )
1007 kDebug( 172 ) <<
"entry cleaning:" << key.
id();
1008 remove_startup_info_internal( key );
1017 gettimeofday( &tm, NULL );
1018 char hostname[ 256 ];
1019 hostname[ 0 ] =
'\0';
1020 if (!gethostname( hostname, 255 ))
1021 hostname[
sizeof(hostname)-1] =
'\0';
1023 unsigned long qt_x_user_time = QX11Info::appUserTime();
1025 unsigned long qt_x_user_time = 0;
1027 QByteArray
id = QString::fromLatin1(
"%1;%2;%3;%4_TIME%5" ).arg( hostname ).arg( tm.tv_sec )
1028 .arg( tm.tv_usec ).arg( getpid()).arg( qt_x_user_time ).toUtf8();
1029 kDebug( 172 ) <<
"creating: " <<
id <<
":" << (qApp ? qAppName() :
QString(
"unnamed app") );
1040 QString KStartupInfoId::Private::to_text()
const
1042 return QString::fromLatin1(
" ID=\"%1\" " ).arg(
escape_str(
id));
1048 const QString id_str = QLatin1String(
"ID=" );
1049 for( QStringList::ConstIterator it = items.begin();
1053 if( ( *it ).startsWith( id_str ))
1060 if( !id_P.isEmpty())
1063 #ifdef KSTARTUPINFO_ALL_DEBUG
1064 kDebug( 172 ) <<
"using: " << d->id;
1069 if( !startup_env.isEmpty() )
1071 d->id = startup_env;
1072 #ifdef KSTARTUPINFO_ALL_DEBUG
1073 kDebug( 172 ) <<
"reusing: " << d->id;
1094 if( !startup_env.isEmpty() )
1095 id.d->id = startup_env;
1129 return id() == id_P.
id();
1134 return !(*
this == id_P );
1140 return id() < id_P.
id();
1146 return d->id.isEmpty() || d->id ==
"0";
1153 int pos = d->id.lastIndexOf(
"_TIME" );
1157 unsigned long time =
QString( d->id.mid( pos + 5 ) ).toULong( &ok );
1158 if( !ok && d->id[ pos + 5 ] ==
'-' )
1159 time =
QString( d->id.mid( pos + 5 ) ).toLong( &ok );
1167 int pos1 = d->id.lastIndexOf(
'/' );
1170 int pos2 = d->id.lastIndexOf(
'/', pos1 - 1 );
1174 unsigned long time =
QString( d->id.mid( pos2 + 1, pos1 - pos2 - 1 ) ).toULong( &ok );
1175 if( !ok && d->id[ pos2 + 1 ] ==
'-' )
1176 time =
QString( d->id.mid( pos2 + 1, pos1 - pos2 - 1 ) ).toLong( &ok );
1185 QString KStartupInfoData::Private::to_text()
const
1189 ret += QString::fromLatin1(
" BIN=\"%1\"" ).arg(
escape_str( bin ));
1190 if( !name.isEmpty())
1191 ret += QString::fromLatin1(
" NAME=\"%1\"" ).arg(
escape_str( name ));
1192 if( !description.isEmpty())
1193 ret += QString::fromLatin1(
" DESCRIPTION=\"%1\"" ).arg(
escape_str( description ));
1194 if( !icon.isEmpty())
1195 ret += QString::fromLatin1(
" ICON=\"%1\"" ).arg( icon );
1197 ret += QString::fromLatin1(
" DESKTOP=%1" )
1203 if( !wmclass.isEmpty())
1204 ret += QString::fromLatin1(
" WMCLASS=\"%1\"" ).arg(
QString( wmclass ) );
1205 if( !hostname.isEmpty())
1206 ret += QString::fromLatin1(
" HOSTNAME=%1" ).arg(
QString( hostname ) );
1207 for( QList< pid_t >::ConstIterator it = pids.begin();
1210 ret += QString::fromLatin1(
" PID=%1" ).arg( *it );
1213 if( timestamp != ~0U )
1214 ret += QString::fromLatin1(
" TIMESTAMP=%1" ).arg( timestamp );
1216 ret += QString::fromLatin1(
" SCREEN=%1" ).arg( screen );
1217 if( xinerama != -1 )
1218 ret += QString::fromLatin1(
" XINERAMA=%1" ).arg( xinerama );
1219 if( launched_by != 0 )
1220 ret += QString::fromLatin1(
" LAUNCHED_BY=%1" ).arg( (qptrdiff)launched_by );
1221 if( !application_id.isEmpty())
1222 ret += QString::fromLatin1(
" APPLICATION_ID=\"%1\"" ).arg( application_id );
1229 const QString bin_str = QString::fromLatin1(
"BIN=" );
1230 const QString name_str = QString::fromLatin1(
"NAME=" );
1231 const QString description_str = QString::fromLatin1(
"DESCRIPTION=" );
1232 const QString icon_str = QString::fromLatin1(
"ICON=" );
1233 const QString desktop_str = QString::fromLatin1(
"DESKTOP=" );
1234 const QString wmclass_str = QString::fromLatin1(
"WMCLASS=" );
1235 const QString hostname_str = QString::fromLatin1(
"HOSTNAME=" );
1236 const QString pid_str = QString::fromLatin1(
"PID=" );
1237 const QString silent_str = QString::fromLatin1(
"SILENT=" );
1238 const QString timestamp_str = QString::fromLatin1(
"TIMESTAMP=" );
1239 const QString screen_str = QString::fromLatin1(
"SCREEN=" );
1240 const QString xinerama_str = QString::fromLatin1(
"XINERAMA=" );
1241 const QString launched_by_str = QString::fromLatin1(
"LAUNCHED_BY=" );
1242 const QString application_id_str = QString::fromLatin1(
"APPLICATION_ID=" );
1243 for( QStringList::ConstIterator it = items.begin();
1247 if( ( *it ).startsWith( bin_str ))
1249 else if( ( *it ).startsWith( name_str ))
1251 else if( ( *it ).startsWith( description_str ))
1252 d->description =
get_str( *it );
1253 else if( ( *it ).startsWith( icon_str ))
1255 else if( ( *it ).startsWith( desktop_str ))
1263 else if( ( *it ).startsWith( wmclass_str ))
1265 else if( ( *it ).startsWith( hostname_str ))
1267 else if( ( *it ).startsWith( pid_str ))
1269 else if( ( *it ).startsWith( silent_str ))
1271 else if( ( *it ).startsWith( timestamp_str ))
1273 else if( ( *it ).startsWith( screen_str ))
1275 else if( ( *it ).startsWith( xinerama_str ))
1277 else if( ( *it ).startsWith( launched_by_str ))
1278 d->launched_by = ( WId )
get_num( *it );
1279 else if( ( *it ).startsWith( application_id_str ))
1280 d->application_id =
get_str( *it );
1298 if( !data_P.
bin().isEmpty())
1299 d->bin = data_P.
bin();
1300 if( !data_P.
name().isEmpty() &&
name().isEmpty())
1301 d->name = data_P.
name();
1304 if( !data_P.
icon().isEmpty() &&
icon().isEmpty())
1305 d->icon = data_P.
icon();
1307 d->desktop = data_P.
desktop();
1308 if( !data_P.d->wmclass.isEmpty())
1309 d->wmclass = data_P.d->wmclass;
1310 if( !data_P.d->hostname.isEmpty())
1311 d->hostname = data_P.d->hostname;
1312 for( QList< pid_t >::ConstIterator it = data_P.d->pids.constBegin();
1313 it != data_P.d->pids.constEnd();
1317 d->silent = data_P.
silent();
1320 if( data_P.
screen() != -1 )
1321 d->screen = data_P.
screen();
1361 if( !
name().isEmpty())
1368 d->description = desc_P;
1373 return d->description;
1390 if( !
icon().isEmpty())
1402 d->desktop = desktop_P;
1412 d->wmclass = wmclass_P;
1419 return bin().toUtf8();
1429 if( !hostname_P.isNull())
1430 d->hostname = hostname_P;
1435 if (!gethostname( tmp, 255 ))
1436 tmp[
sizeof(tmp)-1] =
'\0';
1448 if( !d->pids.contains( pid_P ))
1449 d->pids.append( pid_P );
1452 void KStartupInfoData::Private::remove_pid( pid_t pid_P )
1454 pids.removeAll( pid_P );
1464 return d->pids.contains( pid_P );
1469 d->silent = state_P;
1479 d->timestamp = time;
1484 return d->timestamp;
1489 d->screen = _screen;
1509 d->launched_by = window;
1514 return d->launched_by;
1519 if( desktop.startsWith(
'/' ))
1530 d->application_id = desk;
1535 return d->application_id;
1541 unsigned int pos = item_P.indexOf( QLatin1Char(
'=') );
1542 return item_P.mid( pos + 1 ).toLong();
1548 unsigned int pos = item_P.indexOf( QLatin1Char(
'=') );
1549 return item_P.mid( pos + 1 ).toULong();
1555 int pos = item_P.indexOf( QLatin1Char(
'=') );
1556 if( item_P.length() > pos + 2 && item_P.at( pos + 1 ) == QLatin1Char(
'\"') )
1558 int pos2 = item_P.left( pos + 2 ).indexOf( QLatin1Char(
'\"') );
1561 return item_P.mid( pos + 2, pos2 - 2 - pos );
1563 return item_P.mid( pos + 1 );
1569 return get_str( item_P ).toUtf8();
1575 QString txt = txt_P.simplified();
1579 bool escape =
false;
1589 else if( txt[ pos ] ==
'\\' )
1591 else if( txt[ pos ] ==
'\"' )
1593 else if( txt[ pos ] ==
' ' && !in )
1609 pos < str_P.length();
1612 if( str_P[ pos ] ==
'\\'
1613 || str_P[ pos ] ==
'"' )
1615 ret += str_P[ pos ];
1620 #include "kstartupinfo.moc"