File indexing completed on 2024-04-21 03:59:27

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"