Warning, file /frameworks/kwindowsystem/src/kstartupinfo.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).
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"