File indexing completed on 2024-12-08 03:43:50
0001 /* 0002 SPDX-FileCopyrightText: 2001-2003 Lubos Lunak <l.lunak@kde.org> 0003 0004 SPDX-License-Identifier: MIT 0005 */ 0006 0007 // qDebug() can't be turned off in kdeinit 0008 #if 0 0009 #define KSTARTUPINFO_ALL_DEBUG 0010 #ifdef __GNUC__ 0011 #warning Extra KStartupInfo debug messages enabled. 0012 #endif 0013 #endif 0014 0015 #ifdef QT_NO_CAST_FROM_ASCII 0016 #undef QT_NO_CAST_FROM_ASCII 0017 #endif 0018 0019 #include "kstartupinfo.h" 0020 #include "kwindowsystem_debug.h" 0021 0022 #include <QDateTime> 0023 0024 // need to resolve INT32(qglobal.h)<>INT32(Xlibint.h) conflict 0025 #ifndef QT_CLEAN_NAMESPACE 0026 #define QT_CLEAN_NAMESPACE 0027 #endif 0028 0029 #include <QTimer> 0030 #include <netwm.h> 0031 #include <stdlib.h> 0032 #include <sys/time.h> 0033 #include <unistd.h> 0034 0035 #include <QCoreApplication> 0036 #include <QDebug> 0037 #include <QStandardPaths> 0038 #include <X11/Xlib.h> 0039 #include <fixx11h.h> 0040 #include <kwindowsystem.h> 0041 #include <kx11extras.h> 0042 #include <kxmessages.h> 0043 #include <private/qtx11extras_p.h> 0044 #include <signal.h> 0045 0046 static const char NET_STARTUP_MSG[] = "_NET_STARTUP_INFO"; 0047 0048 // DESKTOP_STARTUP_ID is used also in kinit/wrapper.c , 0049 // kdesu in both kdelibs and kdebase and who knows where else 0050 static const char NET_STARTUP_ENV[] = "DESKTOP_STARTUP_ID"; 0051 0052 static QByteArray s_startup_id; 0053 0054 static long get_num(const QString &item_P); 0055 static QString get_str(const QString &item_P); 0056 static QByteArray get_cstr(const QString &item_P); 0057 static QStringList get_fields(const QString &txt_P); 0058 static QString escape_str(const QString &str_P); 0059 0060 class Q_DECL_HIDDEN KStartupInfo::Data : public KStartupInfoData 0061 { 0062 public: 0063 Data() 0064 : age(0) 0065 { 0066 } // just because it's in a QMap 0067 Data(const QString &txt_P) 0068 : KStartupInfoData(txt_P) 0069 , age(0) 0070 { 0071 } 0072 unsigned int age; 0073 }; 0074 0075 struct Q_DECL_HIDDEN KStartupInfoId::Private { 0076 Private() 0077 : id("") 0078 { 0079 } 0080 0081 QString to_text() const; 0082 0083 QByteArray id; // id 0084 }; 0085 0086 struct Q_DECL_HIDDEN KStartupInfoData::Private { 0087 Private() 0088 : desktop(0) 0089 , wmclass("") 0090 , hostname("") 0091 , silent(KStartupInfoData::Unknown) 0092 , screen(-1) 0093 , xinerama(-1) 0094 { 0095 } 0096 0097 QString to_text() const; 0098 void remove_pid(pid_t pid); 0099 0100 QString bin; 0101 QString name; 0102 QString description; 0103 QString icon; 0104 int desktop; 0105 QList<pid_t> pids; 0106 QByteArray wmclass; 0107 QByteArray hostname; 0108 KStartupInfoData::TriState silent; 0109 int screen; 0110 int xinerama; 0111 QString application_id; 0112 }; 0113 0114 class Q_DECL_HIDDEN KStartupInfo::Private 0115 { 0116 public: 0117 // private slots 0118 void startups_cleanup(); 0119 void startups_cleanup_no_age(); 0120 void got_message(const QString &msg); 0121 void window_added(WId w); 0122 void slot_window_added(WId w); 0123 0124 void init(int flags); 0125 void got_startup_info(const QString &msg_P, bool update_only_P); 0126 void got_remove_startup_info(const QString &msg_P); 0127 void new_startup_info_internal(const KStartupInfoId &id_P, Data &data_P, bool update_only_P); 0128 void removeAllStartupInfoInternal(const KStartupInfoId &id_P); 0129 /** 0130 * Emits the gotRemoveStartup signal and erases the @p it from the startups map. 0131 * @returns Iterator to next item in the startups map. 0132 **/ 0133 QMap<KStartupInfoId, Data>::iterator removeStartupInfoInternal(QMap<KStartupInfoId, Data>::iterator it); 0134 void remove_startup_pids(const KStartupInfoId &id, const KStartupInfoData &data); 0135 void remove_startup_pids(const KStartupInfoData &data); 0136 startup_t check_startup_internal(WId w, KStartupInfoId *id, KStartupInfoData *data); 0137 bool find_id(const QByteArray &id_P, KStartupInfoId *id_O, KStartupInfoData *data_O); 0138 bool find_pid(pid_t pid_P, const QByteArray &hostname, KStartupInfoId *id_O, KStartupInfoData *data_O); 0139 bool find_wclass(const QByteArray &res_name_P, const QByteArray &res_class_P, KStartupInfoId *id_O, KStartupInfoData *data_O); 0140 void startups_cleanup_internal(bool age_P); 0141 void clean_all_noncompliant(); 0142 static QString check_required_startup_fields(const QString &msg, const KStartupInfoData &data, int screen); 0143 static void setWindowStartupId(WId w_P, const QByteArray &id_P); 0144 0145 KStartupInfo *q; 0146 unsigned int timeout; 0147 QMap<KStartupInfoId, KStartupInfo::Data> startups; 0148 // contains silenced ASN's only if !AnnounceSilencedChanges 0149 QMap<KStartupInfoId, KStartupInfo::Data> silent_startups; 0150 // contains ASN's that had change: but no new: yet 0151 QMap<KStartupInfoId, KStartupInfo::Data> uninited_startups; 0152 KXMessages msgs; 0153 QTimer *cleanup; 0154 int flags; 0155 0156 Private(int flags_P, KStartupInfo *qq) 0157 : q(qq) 0158 , timeout(60) 0159 , msgs(NET_STARTUP_MSG) 0160 , cleanup(nullptr) 0161 , flags(flags_P) 0162 { 0163 } 0164 0165 void createConnections() 0166 { 0167 // d == nullptr means "disabled" 0168 if (!QX11Info::isPlatformX11() || !QX11Info::display()) { 0169 return; 0170 } 0171 0172 if (!(flags & DisableKWinModule)) { 0173 QObject::connect(KX11Extras::self(), SIGNAL(windowAdded(WId)), q, SLOT(slot_window_added(WId))); 0174 } 0175 QObject::connect(&msgs, SIGNAL(gotMessage(QString)), q, SLOT(got_message(QString))); 0176 cleanup = new QTimer(q); 0177 QObject::connect(cleanup, SIGNAL(timeout()), q, SLOT(startups_cleanup())); 0178 } 0179 }; 0180 0181 KStartupInfo::KStartupInfo(int flags_P, QObject *parent_P) 0182 : QObject(parent_P) 0183 , d(new Private(flags_P, this)) 0184 { 0185 d->createConnections(); 0186 } 0187 0188 KStartupInfo::~KStartupInfo() 0189 { 0190 delete d; 0191 } 0192 0193 void KStartupInfo::Private::got_message(const QString &msg_P) 0194 { 0195 // TODO do something with SCREEN= ? 0196 // qCDebug(LOG_KWINDOWSYSTEM) << "got:" << msg_P; 0197 QString msg = msg_P.trimmed(); 0198 if (msg.startsWith(QLatin1String("new:"))) { // must match length below 0199 got_startup_info(msg.mid(4), false); 0200 } else if (msg.startsWith(QLatin1String("change:"))) { // must match length below 0201 got_startup_info(msg.mid(7), true); 0202 } else if (msg.startsWith(QLatin1String("remove:"))) { // must match length below 0203 got_remove_startup_info(msg.mid(7)); 0204 } 0205 } 0206 0207 // if the application stops responding for a while, KWindowSystem may get 0208 // the information about the already mapped window before KXMessages 0209 // actually gets the info about the started application (depends 0210 // on their order in the native x11 event filter) 0211 // simply delay info from KWindowSystem a bit 0212 // SELI??? 0213 namespace 0214 { 0215 class DelayedWindowEvent : public QEvent 0216 { 0217 public: 0218 DelayedWindowEvent(WId w_P) 0219 : QEvent(uniqueType()) 0220 , w(w_P) 0221 { 0222 } 0223 Window w; 0224 static Type uniqueType() 0225 { 0226 return Type(QEvent::User + 15); 0227 } 0228 }; 0229 } 0230 0231 void KStartupInfo::Private::slot_window_added(WId w_P) 0232 { 0233 qApp->postEvent(q, new DelayedWindowEvent(w_P)); 0234 } 0235 0236 void KStartupInfo::customEvent(QEvent *e_P) 0237 { 0238 if (e_P->type() == DelayedWindowEvent::uniqueType()) { 0239 d->window_added(static_cast<DelayedWindowEvent *>(e_P)->w); 0240 } else 0241 QObject::customEvent(e_P); 0242 } 0243 0244 void KStartupInfo::Private::window_added(WId w_P) 0245 { 0246 KStartupInfoId id; 0247 KStartupInfoData data; 0248 startup_t ret = check_startup_internal(w_P, &id, &data); 0249 switch (ret) { 0250 case Match: 0251 // qCDebug(LOG_KWINDOWSYSTEM) << "new window match"; 0252 break; 0253 case NoMatch: 0254 break; // nothing 0255 case CantDetect: 0256 if (flags & CleanOnCantDetect) { 0257 clean_all_noncompliant(); 0258 } 0259 break; 0260 } 0261 } 0262 0263 void KStartupInfo::Private::got_startup_info(const QString &msg_P, bool update_P) 0264 { 0265 KStartupInfoId id(msg_P); 0266 if (id.isNull()) { 0267 return; 0268 } 0269 KStartupInfo::Data data(msg_P); 0270 new_startup_info_internal(id, data, update_P); 0271 } 0272 0273 void KStartupInfo::Private::new_startup_info_internal(const KStartupInfoId &id_P, KStartupInfo::Data &data_P, bool update_P) 0274 { 0275 if (id_P.isNull()) { 0276 return; 0277 } 0278 if (startups.contains(id_P)) { 0279 // already reported, update 0280 startups[id_P].update(data_P); 0281 startups[id_P].age = 0; // CHECKME 0282 // qCDebug(LOG_KWINDOWSYSTEM) << "updating"; 0283 if (startups[id_P].silent() == KStartupInfo::Data::Yes && !(flags & AnnounceSilenceChanges)) { 0284 silent_startups[id_P] = startups[id_P]; 0285 startups.remove(id_P); 0286 Q_EMIT q->gotRemoveStartup(id_P, silent_startups[id_P]); 0287 return; 0288 } 0289 Q_EMIT q->gotStartupChange(id_P, startups[id_P]); 0290 return; 0291 } 0292 if (silent_startups.contains(id_P)) { 0293 // already reported, update 0294 silent_startups[id_P].update(data_P); 0295 silent_startups[id_P].age = 0; // CHECKME 0296 // qCDebug(LOG_KWINDOWSYSTEM) << "updating silenced"; 0297 if (silent_startups[id_P].silent() != Data::Yes) { 0298 startups[id_P] = silent_startups[id_P]; 0299 silent_startups.remove(id_P); 0300 q->Q_EMIT gotNewStartup(id_P, startups[id_P]); 0301 return; 0302 } 0303 Q_EMIT q->gotStartupChange(id_P, silent_startups[id_P]); 0304 return; 0305 } 0306 if (uninited_startups.contains(id_P)) { 0307 uninited_startups[id_P].update(data_P); 0308 // qCDebug(LOG_KWINDOWSYSTEM) << "updating uninited"; 0309 if (!update_P) { // uninited finally got new: 0310 startups[id_P] = uninited_startups[id_P]; 0311 uninited_startups.remove(id_P); 0312 Q_EMIT q->gotNewStartup(id_P, startups[id_P]); 0313 return; 0314 } 0315 // no change announce, it's still uninited 0316 return; 0317 } 0318 if (update_P) { // change: without any new: first 0319 // qCDebug(LOG_KWINDOWSYSTEM) << "adding uninited"; 0320 uninited_startups.insert(id_P, data_P); 0321 } else if (data_P.silent() != Data::Yes || flags & AnnounceSilenceChanges) { 0322 // qCDebug(LOG_KWINDOWSYSTEM) << "adding"; 0323 startups.insert(id_P, data_P); 0324 Q_EMIT q->gotNewStartup(id_P, data_P); 0325 } else { // new silenced, and silent shouldn't be announced 0326 // qCDebug(LOG_KWINDOWSYSTEM) << "adding silent"; 0327 silent_startups.insert(id_P, data_P); 0328 } 0329 cleanup->start(1000); // 1 sec 0330 } 0331 0332 void KStartupInfo::Private::got_remove_startup_info(const QString &msg_P) 0333 { 0334 KStartupInfoId id(msg_P); 0335 KStartupInfoData data(msg_P); 0336 if (!data.pids().isEmpty()) { 0337 if (!id.isNull()) { 0338 remove_startup_pids(id, data); 0339 } else { 0340 remove_startup_pids(data); 0341 } 0342 return; 0343 } 0344 removeAllStartupInfoInternal(id); 0345 } 0346 0347 void KStartupInfo::Private::removeAllStartupInfoInternal(const KStartupInfoId &id_P) 0348 { 0349 auto it = startups.find(id_P); 0350 if (it != startups.end()) { 0351 // qCDebug(LOG_KWINDOWSYSTEM) << "removing"; 0352 Q_EMIT q->gotRemoveStartup(it.key(), it.value()); 0353 startups.erase(it); 0354 return; 0355 } 0356 it = silent_startups.find(id_P); 0357 if (it != silent_startups.end()) { 0358 silent_startups.erase(it); 0359 return; 0360 } 0361 it = uninited_startups.find(id_P); 0362 if (it != uninited_startups.end()) { 0363 uninited_startups.erase(it); 0364 } 0365 } 0366 0367 QMap<KStartupInfoId, KStartupInfo::Data>::iterator KStartupInfo::Private::removeStartupInfoInternal(QMap<KStartupInfoId, Data>::iterator it) 0368 { 0369 Q_EMIT q->gotRemoveStartup(it.key(), it.value()); 0370 return startups.erase(it); 0371 } 0372 0373 void KStartupInfo::Private::remove_startup_pids(const KStartupInfoData &data_P) 0374 { 0375 // first find the matching info 0376 for (QMap<KStartupInfoId, KStartupInfo::Data>::Iterator it = startups.begin(); it != startups.end(); ++it) { 0377 if ((*it).hostname() != data_P.hostname()) { 0378 continue; 0379 } 0380 if (!(*it).is_pid(data_P.pids().first())) { 0381 continue; // not the matching info 0382 } 0383 remove_startup_pids(it.key(), data_P); 0384 break; 0385 } 0386 } 0387 0388 void KStartupInfo::Private::remove_startup_pids(const KStartupInfoId &id_P, const KStartupInfoData &data_P) 0389 { 0390 if (data_P.pids().isEmpty()) { 0391 qFatal("data_P.pids().isEmpty()"); 0392 } 0393 Data *data = nullptr; 0394 if (startups.contains(id_P)) { 0395 data = &startups[id_P]; 0396 } else if (silent_startups.contains(id_P)) { 0397 data = &silent_startups[id_P]; 0398 } else if (uninited_startups.contains(id_P)) { 0399 data = &uninited_startups[id_P]; 0400 } else { 0401 return; 0402 } 0403 const auto pids = data_P.pids(); 0404 for (auto pid : pids) { 0405 data->d->remove_pid(pid); // remove all pids from the info 0406 } 0407 if (data->pids().isEmpty()) { // all pids removed -> remove info 0408 removeAllStartupInfoInternal(id_P); 0409 } 0410 } 0411 0412 bool KStartupInfo::sendStartup(const KStartupInfoId &id_P, const KStartupInfoData &data_P) 0413 { 0414 if (id_P.isNull()) { 0415 return false; 0416 } 0417 return sendStartupXcb(QX11Info::connection(), QX11Info::appScreen(), id_P, data_P); 0418 } 0419 0420 bool KStartupInfo::sendStartupXcb(xcb_connection_t *conn, int screen, const KStartupInfoId &id_P, const KStartupInfoData &data_P) 0421 { 0422 if (id_P.isNull()) { 0423 return false; 0424 } 0425 QString msg = QStringLiteral("new: %1 %2").arg(id_P.d->to_text(), data_P.d->to_text()); 0426 msg = Private::check_required_startup_fields(msg, data_P, screen); 0427 #ifdef KSTARTUPINFO_ALL_DEBUG 0428 qCDebug(LOG_KWINDOWSYSTEM) << "sending " << msg; 0429 #endif 0430 return KXMessages::broadcastMessageX(conn, NET_STARTUP_MSG, msg, screen); 0431 } 0432 0433 QString KStartupInfo::Private::check_required_startup_fields(const QString &msg, const KStartupInfoData &data_P, int screen) 0434 { 0435 QString ret = msg; 0436 if (data_P.name().isEmpty()) { 0437 // qWarning() << "NAME not specified in initial startup message"; 0438 QString name = data_P.bin(); 0439 if (name.isEmpty()) { 0440 name = QStringLiteral("UNKNOWN"); 0441 } 0442 ret += QStringLiteral(" NAME=\"%1\"").arg(escape_str(name)); 0443 } 0444 if (data_P.screen() == -1) { // add automatically if needed 0445 ret += QStringLiteral(" SCREEN=%1").arg(screen); 0446 } 0447 return ret; 0448 } 0449 0450 bool KStartupInfo::sendChange(const KStartupInfoId &id_P, const KStartupInfoData &data_P) 0451 { 0452 if (id_P.isNull()) { 0453 return false; 0454 } 0455 return sendChangeXcb(QX11Info::connection(), QX11Info::appScreen(), id_P, data_P); 0456 } 0457 0458 bool KStartupInfo::sendChangeXcb(xcb_connection_t *conn, int screen, const KStartupInfoId &id_P, const KStartupInfoData &data_P) 0459 { 0460 if (id_P.isNull()) { 0461 return false; 0462 } 0463 QString msg = QStringLiteral("change: %1 %2").arg(id_P.d->to_text(), data_P.d->to_text()); 0464 #ifdef KSTARTUPINFO_ALL_DEBUG 0465 qCDebug(LOG_KWINDOWSYSTEM) << "sending " << msg; 0466 #endif 0467 return KXMessages::broadcastMessageX(conn, NET_STARTUP_MSG, msg, screen); 0468 } 0469 0470 bool KStartupInfo::sendFinish(const KStartupInfoId &id_P) 0471 { 0472 if (id_P.isNull()) { 0473 return false; 0474 } 0475 return sendFinishXcb(QX11Info::connection(), QX11Info::appScreen(), id_P); 0476 } 0477 0478 bool KStartupInfo::sendFinishXcb(xcb_connection_t *conn, int screen, const KStartupInfoId &id_P) 0479 { 0480 if (id_P.isNull()) { 0481 return false; 0482 } 0483 QString msg = QStringLiteral("remove: %1").arg(id_P.d->to_text()); 0484 #ifdef KSTARTUPINFO_ALL_DEBUG 0485 qCDebug(LOG_KWINDOWSYSTEM) << "sending " << msg; 0486 #endif 0487 return KXMessages::broadcastMessageX(conn, NET_STARTUP_MSG, msg, screen); 0488 } 0489 0490 bool KStartupInfo::sendFinish(const KStartupInfoId &id_P, const KStartupInfoData &data_P) 0491 { 0492 // if( id_P.isNull()) // id may be null, the pids and hostname matter then 0493 // return false; 0494 return sendFinishXcb(QX11Info::connection(), QX11Info::appScreen(), id_P, data_P); 0495 } 0496 0497 bool KStartupInfo::sendFinishXcb(xcb_connection_t *conn, int screen, const KStartupInfoId &id_P, const KStartupInfoData &data_P) 0498 { 0499 // if( id_P.isNull()) // id may be null, the pids and hostname matter then 0500 // return false; 0501 QString msg = QStringLiteral("remove: %1 %2").arg(id_P.d->to_text(), data_P.d->to_text()); 0502 #ifdef KSTARTUPINFO_ALL_DEBUG 0503 qCDebug(LOG_KWINDOWSYSTEM) << "sending " << msg; 0504 #endif 0505 return KXMessages::broadcastMessageX(conn, NET_STARTUP_MSG, msg, screen); 0506 } 0507 0508 void KStartupInfo::appStarted() 0509 { 0510 QByteArray startupId = s_startup_id; 0511 0512 if (startupId.isEmpty()) { 0513 startupId = QX11Info::nextStartupId(); 0514 } 0515 0516 appStarted(startupId); 0517 setStartupId("0"); // reset the id, no longer valid (must use clearStartupId() to avoid infinite loop) 0518 } 0519 0520 void KStartupInfo::appStarted(const QByteArray &startup_id) 0521 { 0522 KStartupInfoId id; 0523 id.initId(startup_id); 0524 if (id.isNull()) { 0525 return; 0526 } 0527 if (QX11Info::isPlatformX11() && !qEnvironmentVariableIsEmpty("DISPLAY")) { // don't rely on QX11Info::display() 0528 KStartupInfo::sendFinish(id); 0529 } 0530 } 0531 0532 void KStartupInfo::setStartupId(const QByteArray &startup_id) 0533 { 0534 if (startup_id == s_startup_id) { 0535 return; 0536 } 0537 if (startup_id.isEmpty()) { 0538 s_startup_id = "0"; 0539 } else { 0540 s_startup_id = startup_id; 0541 if (QX11Info::isPlatformX11()) { 0542 KStartupInfoId id; 0543 id.initId(startup_id); 0544 long timestamp = id.timestamp(); 0545 if (timestamp != 0) { 0546 if (QX11Info::appUserTime() == 0 || NET::timestampCompare(timestamp, QX11Info::appUserTime()) > 0) { // time > appUserTime 0547 QX11Info::setAppUserTime(timestamp); 0548 } 0549 if (QX11Info::appTime() == 0 || NET::timestampCompare(timestamp, QX11Info::appTime()) > 0) { // time > appTime 0550 QX11Info::setAppTime(timestamp); 0551 } 0552 } 0553 } 0554 } 0555 } 0556 0557 void KStartupInfo::setNewStartupId(QWindow *window, const QByteArray &startup_id) 0558 { 0559 Q_ASSERT(window); 0560 setStartupId(startup_id); 0561 bool activate = true; 0562 if (window != nullptr && QX11Info::isPlatformX11()) { 0563 if (!startup_id.isEmpty() && startup_id != "0") { 0564 NETRootInfo i(QX11Info::connection(), NET::Supported); 0565 if (i.isSupported(NET::WM2StartupId)) { 0566 KStartupInfo::Private::setWindowStartupId(window->winId(), startup_id); 0567 activate = false; // WM will take care of it 0568 } 0569 } 0570 if (activate) { 0571 KX11Extras::setOnDesktop(window->winId(), KX11Extras::currentDesktop()); 0572 // This is not very nice, but there's no way how to get any 0573 // usable timestamp without ASN, so force activating the window. 0574 // And even with ASN, it's not possible to get the timestamp here, 0575 // so if the WM doesn't have support for ASN, it can't be used either. 0576 KX11Extras::forceActiveWindow(window->winId()); 0577 } 0578 } 0579 } 0580 0581 KStartupInfo::startup_t KStartupInfo::checkStartup(WId w_P, KStartupInfoId &id_O, KStartupInfoData &data_O) 0582 { 0583 return d->check_startup_internal(w_P, &id_O, &data_O); 0584 } 0585 0586 KStartupInfo::startup_t KStartupInfo::checkStartup(WId w_P, KStartupInfoId &id_O) 0587 { 0588 return d->check_startup_internal(w_P, &id_O, nullptr); 0589 } 0590 0591 KStartupInfo::startup_t KStartupInfo::checkStartup(WId w_P, KStartupInfoData &data_O) 0592 { 0593 return d->check_startup_internal(w_P, nullptr, &data_O); 0594 } 0595 0596 KStartupInfo::startup_t KStartupInfo::checkStartup(WId w_P) 0597 { 0598 return d->check_startup_internal(w_P, nullptr, nullptr); 0599 } 0600 0601 KStartupInfo::startup_t KStartupInfo::Private::check_startup_internal(WId w_P, KStartupInfoId *id_O, KStartupInfoData *data_O) 0602 { 0603 if (startups.isEmpty()) { 0604 return NoMatch; // no startups 0605 } 0606 // Strategy: 0607 // 0608 // Is this a compliant app ? 0609 // - Yes - test for match 0610 // - No - Is this a NET_WM compliant app ? 0611 // - Yes - test for pid match 0612 // - No - test for WM_CLASS match 0613 qCDebug(LOG_KWINDOWSYSTEM) << "check_startup"; 0614 QByteArray id = windowStartupId(w_P); 0615 if (!id.isNull()) { 0616 if (id.isEmpty() || id == "0") { // means ignore this window 0617 qCDebug(LOG_KWINDOWSYSTEM) << "ignore"; 0618 return NoMatch; 0619 } 0620 return find_id(id, id_O, data_O) ? Match : NoMatch; 0621 } 0622 if (!QX11Info::isPlatformX11()) { 0623 qCDebug(LOG_KWINDOWSYSTEM) << "check_startup:cantdetect"; 0624 return CantDetect; 0625 } 0626 NETWinInfo info(QX11Info::connection(), 0627 w_P, 0628 QX11Info::appRootWindow(), 0629 NET::WMWindowType | NET::WMPid | NET::WMState, 0630 NET::WM2WindowClass | NET::WM2ClientMachine | NET::WM2TransientFor); 0631 pid_t pid = info.pid(); 0632 if (pid > 0) { 0633 QByteArray hostname = info.clientMachine(); 0634 if (!hostname.isEmpty() && find_pid(pid, hostname, id_O, data_O)) { 0635 return Match; 0636 } 0637 // try XClass matching , this PID stuff sucks :( 0638 } 0639 if (find_wclass(info.windowClassName(), info.windowClassClass(), id_O, data_O)) { 0640 return Match; 0641 } 0642 // ignore NET::Tool and other special window types, if they can't be matched 0643 NET::WindowType type = info.windowType(NET::NormalMask | NET::DesktopMask | NET::DockMask | NET::ToolbarMask | NET::MenuMask | NET::DialogMask 0644 | NET::OverrideMask | NET::TopMenuMask | NET::UtilityMask | NET::SplashMask); 0645 if (type != NET::Normal && type != NET::Override && type != NET::Unknown && type != NET::Dialog && type != NET::Utility) 0646 // && type != NET::Dock ) why did I put this here? 0647 { 0648 return NoMatch; 0649 } 0650 // lets see if this is a transient 0651 xcb_window_t transient_for = info.transientFor(); 0652 if (transient_for != QX11Info::appRootWindow() && transient_for != XCB_WINDOW_NONE) { 0653 return NoMatch; 0654 } 0655 qCDebug(LOG_KWINDOWSYSTEM) << "check_startup:cantdetect"; 0656 return CantDetect; 0657 } 0658 0659 bool KStartupInfo::Private::find_id(const QByteArray &id_P, KStartupInfoId *id_O, KStartupInfoData *data_O) 0660 { 0661 // qCDebug(LOG_KWINDOWSYSTEM) << "find_id:" << id_P; 0662 KStartupInfoId id; 0663 id.initId(id_P); 0664 if (startups.contains(id)) { 0665 if (id_O != nullptr) { 0666 *id_O = id; 0667 } 0668 if (data_O != nullptr) { 0669 *data_O = startups[id]; 0670 } 0671 // qCDebug(LOG_KWINDOWSYSTEM) << "check_startup_id:match"; 0672 return true; 0673 } 0674 return false; 0675 } 0676 0677 bool KStartupInfo::Private::find_pid(pid_t pid_P, const QByteArray &hostname_P, KStartupInfoId *id_O, KStartupInfoData *data_O) 0678 { 0679 // qCDebug(LOG_KWINDOWSYSTEM) << "find_pid:" << pid_P; 0680 for (QMap<KStartupInfoId, KStartupInfo::Data>::Iterator it = startups.begin(); it != startups.end(); ++it) { 0681 if ((*it).is_pid(pid_P) && (*it).hostname() == hostname_P) { 0682 // Found it ! 0683 if (id_O != nullptr) { 0684 *id_O = it.key(); 0685 } 0686 if (data_O != nullptr) { 0687 *data_O = *it; 0688 } 0689 // non-compliant, remove on first match 0690 removeStartupInfoInternal(it); 0691 // qCDebug(LOG_KWINDOWSYSTEM) << "check_startup_pid:match"; 0692 return true; 0693 } 0694 } 0695 return false; 0696 } 0697 0698 bool KStartupInfo::Private::find_wclass(const QByteArray &_res_name, const QByteArray &_res_class, KStartupInfoId *id_O, KStartupInfoData *data_O) 0699 { 0700 QByteArray res_name = _res_name.toLower(); 0701 QByteArray res_class = _res_class.toLower(); 0702 // qCDebug(LOG_KWINDOWSYSTEM) << "find_wclass:" << res_name << ":" << res_class; 0703 for (QMap<KStartupInfoId, Data>::Iterator it = startups.begin(); it != startups.end(); ++it) { 0704 const QByteArray wmclass = (*it).findWMClass(); 0705 if (wmclass.toLower() == res_name || wmclass.toLower() == res_class) { 0706 // Found it ! 0707 if (id_O != nullptr) { 0708 *id_O = it.key(); 0709 } 0710 if (data_O != nullptr) { 0711 *data_O = *it; 0712 } 0713 // non-compliant, remove on first match 0714 removeStartupInfoInternal(it); 0715 // qCDebug(LOG_KWINDOWSYSTEM) << "check_startup_wclass:match"; 0716 return true; 0717 } 0718 } 0719 return false; 0720 } 0721 0722 QByteArray KStartupInfo::windowStartupId(WId w_P) 0723 { 0724 if (!QX11Info::isPlatformX11()) { 0725 return QByteArray(); 0726 } 0727 NETWinInfo info(QX11Info::connection(), w_P, QX11Info::appRootWindow(), NET::Properties(), NET::WM2StartupId | NET::WM2GroupLeader); 0728 QByteArray ret = info.startupId(); 0729 if (ret.isEmpty() && info.groupLeader() != XCB_WINDOW_NONE) { 0730 // retry with window group leader, as the spec says 0731 NETWinInfo groupLeaderInfo(QX11Info::connection(), info.groupLeader(), QX11Info::appRootWindow(), NET::Properties(), NET::Properties2()); 0732 ret = groupLeaderInfo.startupId(); 0733 } 0734 return ret; 0735 } 0736 0737 void KStartupInfo::Private::setWindowStartupId(WId w_P, const QByteArray &id_P) 0738 { 0739 if (!QX11Info::isPlatformX11()) { 0740 return; 0741 } 0742 if (id_P.isNull()) { 0743 return; 0744 } 0745 NETWinInfo info(QX11Info::connection(), w_P, QX11Info::appRootWindow(), NET::Properties(), NET::Properties2()); 0746 info.setStartupId(id_P.constData()); 0747 } 0748 0749 void KStartupInfo::setTimeout(unsigned int secs_P) 0750 { 0751 d->timeout = secs_P; 0752 // schedule removing entries that are older than the new timeout 0753 QTimer::singleShot(0, this, SLOT(startups_cleanup_no_age())); 0754 } 0755 0756 void KStartupInfo::Private::startups_cleanup_no_age() 0757 { 0758 startups_cleanup_internal(false); 0759 } 0760 0761 void KStartupInfo::Private::startups_cleanup() 0762 { 0763 if (startups.isEmpty() && silent_startups.isEmpty() && uninited_startups.isEmpty()) { 0764 cleanup->stop(); 0765 return; 0766 } 0767 startups_cleanup_internal(true); 0768 } 0769 0770 void KStartupInfo::Private::startups_cleanup_internal(bool age_P) 0771 { 0772 auto checkCleanup = [this, age_P](QMap<KStartupInfoId, KStartupInfo::Data> &s, bool doEmit) { 0773 auto it = s.begin(); 0774 while (it != s.end()) { 0775 if (age_P) { 0776 (*it).age++; 0777 } 0778 unsigned int tout = timeout; 0779 if ((*it).silent() == KStartupInfo::Data::Yes) { 0780 // give kdesu time to get a password 0781 tout *= 20; 0782 } 0783 const QByteArray timeoutEnvVariable = qgetenv("KSTARTUPINFO_TIMEOUT"); 0784 if (!timeoutEnvVariable.isNull()) { 0785 tout = timeoutEnvVariable.toUInt(); 0786 } 0787 if ((*it).age >= tout) { 0788 if (doEmit) { 0789 Q_EMIT q->gotRemoveStartup(it.key(), it.value()); 0790 } 0791 it = s.erase(it); 0792 } else { 0793 ++it; 0794 } 0795 } 0796 }; 0797 checkCleanup(startups, true); 0798 checkCleanup(silent_startups, false); 0799 checkCleanup(uninited_startups, false); 0800 } 0801 0802 void KStartupInfo::Private::clean_all_noncompliant() 0803 { 0804 for (QMap<KStartupInfoId, KStartupInfo::Data>::Iterator it = startups.begin(); it != startups.end();) { 0805 if ((*it).WMClass() != "0") { 0806 ++it; 0807 continue; 0808 } 0809 it = removeStartupInfoInternal(it); 0810 } 0811 } 0812 0813 QByteArray KStartupInfo::createNewStartupId() 0814 { 0815 quint32 timestamp = 0; 0816 if (QX11Info::isPlatformX11()) { 0817 timestamp = QX11Info::getTimestamp(); 0818 } 0819 return KStartupInfo::createNewStartupIdForTimestamp(timestamp); 0820 } 0821 0822 QByteArray KStartupInfo::createNewStartupIdForTimestamp(quint32 timestamp) 0823 { 0824 // Assign a unique id, use hostname+time+pid, that should be 200% unique. 0825 // Also append the user timestamp (for focus stealing prevention). 0826 struct timeval tm; 0827 gettimeofday(&tm, nullptr); 0828 char hostname[256]; 0829 hostname[0] = '\0'; 0830 if (!gethostname(hostname, 255)) { 0831 hostname[sizeof(hostname) - 1] = '\0'; 0832 } 0833 QByteArray id = QStringLiteral("%1;%2;%3;%4_TIME%5").arg(hostname).arg(tm.tv_sec).arg(tm.tv_usec).arg(getpid()).arg(timestamp).toUtf8(); 0834 // qCDebug(LOG_KWINDOWSYSTEM) << "creating: " << id << ":" << (qApp ? qAppName() : QString("unnamed app") /* e.g. kdeinit */); 0835 return id; 0836 } 0837 0838 const QByteArray &KStartupInfoId::id() const 0839 { 0840 return d->id; 0841 } 0842 0843 QString KStartupInfoId::Private::to_text() const 0844 { 0845 return QStringLiteral(" ID=\"%1\" ").arg(escape_str(id)); 0846 } 0847 0848 KStartupInfoId::KStartupInfoId(const QString &txt_P) 0849 : d(new Private) 0850 { 0851 const QStringList items = get_fields(txt_P); 0852 for (QStringList::ConstIterator it = items.begin(); it != items.end(); ++it) { 0853 if ((*it).startsWith(QLatin1String("ID="))) { 0854 d->id = get_cstr(*it); 0855 } 0856 } 0857 } 0858 0859 void KStartupInfoId::initId(const QByteArray &id_P) 0860 { 0861 if (!id_P.isEmpty()) { 0862 d->id = id_P; 0863 #ifdef KSTARTUPINFO_ALL_DEBUG 0864 qCDebug(LOG_KWINDOWSYSTEM) << "using: " << d->id; 0865 #endif 0866 return; 0867 } 0868 const QByteArray startup_env = qgetenv(NET_STARTUP_ENV); 0869 if (!startup_env.isEmpty()) { 0870 // already has id 0871 d->id = startup_env; 0872 #ifdef KSTARTUPINFO_ALL_DEBUG 0873 qCDebug(LOG_KWINDOWSYSTEM) << "reusing: " << d->id; 0874 #endif 0875 return; 0876 } 0877 d->id = KStartupInfo::createNewStartupId(); 0878 } 0879 0880 bool KStartupInfoId::setupStartupEnv() const 0881 { 0882 if (isNull()) { 0883 qunsetenv(NET_STARTUP_ENV); 0884 return false; 0885 } 0886 return !qputenv(NET_STARTUP_ENV, id()) == 0; 0887 } 0888 0889 void KStartupInfo::resetStartupEnv() 0890 { 0891 qunsetenv(NET_STARTUP_ENV); 0892 } 0893 0894 KStartupInfoId::KStartupInfoId() 0895 : d(new Private) 0896 { 0897 } 0898 0899 KStartupInfoId::~KStartupInfoId() 0900 { 0901 delete d; 0902 } 0903 0904 KStartupInfoId::KStartupInfoId(const KStartupInfoId &id_P) 0905 : d(new Private(*id_P.d)) 0906 { 0907 } 0908 0909 KStartupInfoId &KStartupInfoId::operator=(const KStartupInfoId &id_P) 0910 { 0911 if (&id_P == this) { 0912 return *this; 0913 } 0914 *d = *id_P.d; 0915 return *this; 0916 } 0917 0918 bool KStartupInfoId::operator==(const KStartupInfoId &id_P) const 0919 { 0920 return id() == id_P.id(); 0921 } 0922 0923 bool KStartupInfoId::operator!=(const KStartupInfoId &id_P) const 0924 { 0925 return !(*this == id_P); 0926 } 0927 0928 // needed for QMap 0929 bool KStartupInfoId::operator<(const KStartupInfoId &id_P) const 0930 { 0931 return id() < id_P.id(); 0932 } 0933 0934 bool KStartupInfoId::isNull() const 0935 { 0936 return d->id.isEmpty() || d->id == "0"; 0937 } 0938 0939 unsigned long KStartupInfoId::timestamp() const 0940 { 0941 if (isNull()) { 0942 return 0; 0943 } 0944 // As per the spec, the ID must contain the _TIME followed by the timestamp 0945 int pos = d->id.lastIndexOf("_TIME"); 0946 if (pos >= 0) { 0947 bool ok; 0948 unsigned long time = QString(d->id.mid(pos + 5)).toULong(&ok); 0949 if (!ok && d->id[pos + 5] == '-') { // try if it's as a negative signed number perhaps 0950 time = QString(d->id.mid(pos + 5)).toLong(&ok); 0951 } 0952 if (ok) { 0953 return time; 0954 } 0955 } 0956 return 0; 0957 } 0958 0959 QString KStartupInfoData::Private::to_text() const 0960 { 0961 QString ret; 0962 // prepare some space which should be always enough. 0963 // No need to squeze at the end, as the result is only used as intermediate string 0964 ret.reserve(256); 0965 if (!bin.isEmpty()) { 0966 ret += QStringLiteral(" BIN=\"%1\"").arg(escape_str(bin)); 0967 } 0968 if (!name.isEmpty()) { 0969 ret += QStringLiteral(" NAME=\"%1\"").arg(escape_str(name)); 0970 } 0971 if (!description.isEmpty()) { 0972 ret += QStringLiteral(" DESCRIPTION=\"%1\"").arg(escape_str(description)); 0973 } 0974 if (!icon.isEmpty()) { 0975 ret += QStringLiteral(" ICON=\"%1\"").arg(icon); 0976 } 0977 if (desktop != 0) { 0978 ret += QStringLiteral(" DESKTOP=%1").arg(desktop == NET::OnAllDesktops ? NET::OnAllDesktops : desktop - 1); // spec counts from 0 0979 } 0980 if (!wmclass.isEmpty()) { 0981 ret += QStringLiteral(" WMCLASS=\"%1\"").arg(QString(wmclass)); 0982 } 0983 if (!hostname.isEmpty()) { 0984 ret += QStringLiteral(" HOSTNAME=%1").arg(QString(hostname)); 0985 } 0986 for (QList<pid_t>::ConstIterator it = pids.begin(); it != pids.end(); ++it) { 0987 ret += QStringLiteral(" PID=%1").arg(*it); 0988 } 0989 if (silent != KStartupInfoData::Unknown) { 0990 ret += QStringLiteral(" SILENT=%1").arg(silent == KStartupInfoData::Yes ? 1 : 0); 0991 } 0992 if (screen != -1) { 0993 ret += QStringLiteral(" SCREEN=%1").arg(screen); 0994 } 0995 if (xinerama != -1) { 0996 ret += QStringLiteral(" XINERAMA=%1").arg(xinerama); 0997 } 0998 if (!application_id.isEmpty()) { 0999 ret += QStringLiteral(" APPLICATION_ID=\"%1\"").arg(application_id); 1000 } 1001 return ret; 1002 } 1003 1004 KStartupInfoData::KStartupInfoData(const QString &txt_P) 1005 : d(new Private) 1006 { 1007 const QStringList items = get_fields(txt_P); 1008 for (QStringList::ConstIterator it = items.begin(); it != items.end(); ++it) { 1009 if ((*it).startsWith(QLatin1String("BIN="))) { 1010 d->bin = get_str(*it); 1011 } else if ((*it).startsWith(QLatin1String("NAME="))) { 1012 d->name = get_str(*it); 1013 } else if ((*it).startsWith(QLatin1String("DESCRIPTION="))) { 1014 d->description = get_str(*it); 1015 } else if ((*it).startsWith(QLatin1String("ICON="))) { 1016 d->icon = get_str(*it); 1017 } else if ((*it).startsWith(QLatin1String("DESKTOP="))) { 1018 d->desktop = get_num(*it); 1019 if (d->desktop != NET::OnAllDesktops) { 1020 ++d->desktop; // spec counts from 0 1021 } 1022 } else if ((*it).startsWith(QLatin1String("WMCLASS="))) { 1023 d->wmclass = get_cstr(*it); 1024 } else if ((*it).startsWith(QLatin1String("HOSTNAME="))) { // added to version 1 (2014) 1025 d->hostname = get_cstr(*it); 1026 } else if ((*it).startsWith(QLatin1String("PID="))) { // added to version 1 (2014) 1027 addPid(get_num(*it)); 1028 } else if ((*it).startsWith(QLatin1String("SILENT="))) { 1029 d->silent = get_num(*it) != 0 ? Yes : No; 1030 } else if ((*it).startsWith(QLatin1String("SCREEN="))) { 1031 d->screen = get_num(*it); 1032 } else if ((*it).startsWith(QLatin1String("XINERAMA="))) { 1033 d->xinerama = get_num(*it); 1034 } else if ((*it).startsWith(QLatin1String("APPLICATION_ID="))) { 1035 d->application_id = get_str(*it); 1036 } 1037 } 1038 } 1039 1040 KStartupInfoData::KStartupInfoData(const KStartupInfoData &data) 1041 : d(new Private(*data.d)) 1042 { 1043 } 1044 1045 KStartupInfoData &KStartupInfoData::operator=(const KStartupInfoData &data) 1046 { 1047 if (&data == this) { 1048 return *this; 1049 } 1050 *d = *data.d; 1051 return *this; 1052 } 1053 1054 void KStartupInfoData::update(const KStartupInfoData &data_P) 1055 { 1056 if (!data_P.bin().isEmpty()) { 1057 d->bin = data_P.bin(); 1058 } 1059 if (!data_P.name().isEmpty() && name().isEmpty()) { // don't overwrite 1060 d->name = data_P.name(); 1061 } 1062 if (!data_P.description().isEmpty() && description().isEmpty()) { // don't overwrite 1063 d->description = data_P.description(); 1064 } 1065 if (!data_P.icon().isEmpty() && icon().isEmpty()) { // don't overwrite 1066 d->icon = data_P.icon(); 1067 } 1068 if (data_P.desktop() != 0 && desktop() == 0) { // don't overwrite 1069 d->desktop = data_P.desktop(); 1070 } 1071 if (!data_P.d->wmclass.isEmpty()) { 1072 d->wmclass = data_P.d->wmclass; 1073 } 1074 if (!data_P.d->hostname.isEmpty()) { 1075 d->hostname = data_P.d->hostname; 1076 } 1077 for (QList<pid_t>::ConstIterator it = data_P.d->pids.constBegin(); it != data_P.d->pids.constEnd(); ++it) { 1078 addPid(*it); 1079 } 1080 if (data_P.silent() != Unknown) { 1081 d->silent = data_P.silent(); 1082 } 1083 if (data_P.screen() != -1) { 1084 d->screen = data_P.screen(); 1085 } 1086 if (data_P.xinerama() != -1 && xinerama() != -1) { // don't overwrite 1087 d->xinerama = data_P.xinerama(); 1088 } 1089 if (!data_P.applicationId().isEmpty() && applicationId().isEmpty()) { // don't overwrite 1090 d->application_id = data_P.applicationId(); 1091 } 1092 } 1093 1094 KStartupInfoData::KStartupInfoData() 1095 : d(new Private) 1096 { 1097 } 1098 1099 KStartupInfoData::~KStartupInfoData() 1100 { 1101 delete d; 1102 } 1103 1104 void KStartupInfoData::setBin(const QString &bin_P) 1105 { 1106 d->bin = bin_P; 1107 } 1108 1109 const QString &KStartupInfoData::bin() const 1110 { 1111 return d->bin; 1112 } 1113 1114 void KStartupInfoData::setName(const QString &name_P) 1115 { 1116 d->name = name_P; 1117 } 1118 1119 const QString &KStartupInfoData::name() const 1120 { 1121 return d->name; 1122 } 1123 1124 const QString &KStartupInfoData::findName() const 1125 { 1126 if (!name().isEmpty()) { 1127 return name(); 1128 } 1129 return bin(); 1130 } 1131 1132 void KStartupInfoData::setDescription(const QString &desc_P) 1133 { 1134 d->description = desc_P; 1135 } 1136 1137 const QString &KStartupInfoData::description() const 1138 { 1139 return d->description; 1140 } 1141 1142 const QString &KStartupInfoData::findDescription() const 1143 { 1144 if (!description().isEmpty()) { 1145 return description(); 1146 } 1147 return name(); 1148 } 1149 1150 void KStartupInfoData::setIcon(const QString &icon_P) 1151 { 1152 d->icon = icon_P; 1153 } 1154 1155 const QString &KStartupInfoData::findIcon() const 1156 { 1157 if (!icon().isEmpty()) { 1158 return icon(); 1159 } 1160 return bin(); 1161 } 1162 1163 const QString &KStartupInfoData::icon() const 1164 { 1165 return d->icon; 1166 } 1167 1168 void KStartupInfoData::setDesktop(int desktop_P) 1169 { 1170 d->desktop = desktop_P; 1171 } 1172 1173 int KStartupInfoData::desktop() const 1174 { 1175 return d->desktop; 1176 } 1177 1178 void KStartupInfoData::setWMClass(const QByteArray &wmclass_P) 1179 { 1180 d->wmclass = wmclass_P; 1181 } 1182 1183 const QByteArray KStartupInfoData::findWMClass() const 1184 { 1185 if (!WMClass().isEmpty() && WMClass() != "0") { 1186 return WMClass(); 1187 } 1188 return bin().toUtf8(); 1189 } 1190 1191 QByteArray KStartupInfoData::WMClass() const 1192 { 1193 return d->wmclass; 1194 } 1195 1196 void KStartupInfoData::setHostname(const QByteArray &hostname_P) 1197 { 1198 if (!hostname_P.isNull()) { 1199 d->hostname = hostname_P; 1200 } else { 1201 char tmp[256]; 1202 tmp[0] = '\0'; 1203 if (!gethostname(tmp, 255)) { 1204 tmp[sizeof(tmp) - 1] = '\0'; 1205 } 1206 d->hostname = tmp; 1207 } 1208 } 1209 1210 QByteArray KStartupInfoData::hostname() const 1211 { 1212 return d->hostname; 1213 } 1214 1215 void KStartupInfoData::addPid(pid_t pid_P) 1216 { 1217 if (!d->pids.contains(pid_P)) { 1218 d->pids.append(pid_P); 1219 } 1220 } 1221 1222 void KStartupInfoData::Private::remove_pid(pid_t pid_P) 1223 { 1224 pids.removeAll(pid_P); 1225 } 1226 1227 QList<pid_t> KStartupInfoData::pids() const 1228 { 1229 return d->pids; 1230 } 1231 1232 bool KStartupInfoData::is_pid(pid_t pid_P) const 1233 { 1234 return d->pids.contains(pid_P); 1235 } 1236 1237 void KStartupInfoData::setSilent(TriState state_P) 1238 { 1239 d->silent = state_P; 1240 } 1241 1242 KStartupInfoData::TriState KStartupInfoData::silent() const 1243 { 1244 return d->silent; 1245 } 1246 1247 void KStartupInfoData::setScreen(int _screen) 1248 { 1249 d->screen = _screen; 1250 } 1251 1252 int KStartupInfoData::screen() const 1253 { 1254 return d->screen; 1255 } 1256 1257 void KStartupInfoData::setXinerama(int xinerama) 1258 { 1259 d->xinerama = xinerama; 1260 } 1261 1262 int KStartupInfoData::xinerama() const 1263 { 1264 return d->xinerama; 1265 } 1266 1267 void KStartupInfoData::setApplicationId(const QString &desktop) 1268 { 1269 if (desktop.startsWith(QLatin1Char('/'))) { 1270 d->application_id = desktop; 1271 return; 1272 } 1273 // the spec requires this is always a full path, in order for everyone to be able to find it 1274 QString desk = QStandardPaths::locate(QStandardPaths::ApplicationsLocation, desktop); 1275 if (desk.isEmpty()) { 1276 return; 1277 } 1278 d->application_id = desk; 1279 } 1280 1281 QString KStartupInfoData::applicationId() const 1282 { 1283 return d->application_id; 1284 } 1285 1286 static long get_num(const QString &item_P) 1287 { 1288 unsigned int pos = item_P.indexOf(QLatin1Char('=')); 1289 return item_P.mid(pos + 1).toLong(); 1290 } 1291 1292 static QString get_str(const QString &item_P) 1293 { 1294 int pos = item_P.indexOf(QLatin1Char('=')); 1295 return item_P.mid(pos + 1); 1296 } 1297 1298 static QByteArray get_cstr(const QString &item_P) 1299 { 1300 return get_str(item_P).toUtf8(); 1301 } 1302 1303 static QStringList get_fields(const QString &txt_P) 1304 { 1305 QString txt = txt_P.simplified(); 1306 QStringList ret; 1307 QString item; 1308 bool in = false; 1309 bool escape = false; 1310 for (int pos = 0; pos < txt.length(); ++pos) { 1311 if (escape) { 1312 item += txt[pos]; 1313 escape = false; 1314 } else if (txt[pos] == QLatin1Char('\\')) { 1315 escape = true; 1316 } else if (txt[pos] == QLatin1Char('\"')) { 1317 in = !in; 1318 } else if (txt[pos] == QLatin1Char(' ') && !in) { 1319 ret.append(item); 1320 item = QString(); 1321 } else { 1322 item += txt[pos]; 1323 } 1324 } 1325 ret.append(item); 1326 return ret; 1327 } 1328 1329 static QString escape_str(const QString &str_P) 1330 { 1331 QString ret; 1332 // prepare some space which should be always enough. 1333 // No need to squeze at the end, as the result is only used as intermediate string 1334 ret.reserve(str_P.size() * 2); 1335 for (int pos = 0; pos < str_P.length(); ++pos) { 1336 if (str_P[pos] == QLatin1Char('\\') || str_P[pos] == QLatin1Char('"')) { 1337 ret += QLatin1Char('\\'); 1338 } 1339 ret += str_P[pos]; 1340 } 1341 return ret; 1342 } 1343 1344 #include "moc_kstartupinfo.cpp"