File indexing completed on 2024-04-14 14:29:51

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