1/*
2 SPDX-FileCopyrightText: 2001-2003 Lubos Lunak <l.lunak@kde.org>
3
4 SPDX-License-Identifier: MIT
5*/
6
7// qDebug() can't be turned off in kdeinit
8#if 0
9#define KSTARTUPINFO_ALL_DEBUG
10#ifdef __GNUC__
11#warning Extra KStartupInfo debug messages enabled.
12#endif
13#endif
14
15#ifdef QT_NO_CAST_FROM_ASCII
16#undef QT_NO_CAST_FROM_ASCII
17#endif
18
19#include "kstartupinfo.h"
20#include "kwindowsystem_debug.h"
21
22#include <QDateTime>
23
24// need to resolve INT32(qglobal.h)<>INT32(Xlibint.h) conflict
25#ifndef QT_CLEAN_NAMESPACE
26#define QT_CLEAN_NAMESPACE
27#endif
28
29#include <QTimer>
30#include <netwm.h>
31#include <stdlib.h>
32#include <sys/time.h>
33#include <unistd.h>
34
35#include <QCoreApplication>
36#include <QDebug>
37#include <QStandardPaths>
38#include <X11/Xlib.h>
39#include <fixx11h.h>
40#include <kwindowsystem.h>
41#include <kx11extras.h>
42#include <kxmessages.h>
43#include <private/qtx11extras_p.h>
44#include <signal.h>
45
46static const char NET_STARTUP_MSG[] = "_NET_STARTUP_INFO";
47
48// DESKTOP_STARTUP_ID is used also in kinit/wrapper.c ,
49// kdesu in both kdelibs and kdebase and who knows where else
50static const char NET_STARTUP_ENV[] = "DESKTOP_STARTUP_ID";
51
52static QByteArray s_startup_id;
53
54static long get_num(const QString &item_P);
55static QString get_str(const QString &item_P);
56static QByteArray get_cstr(const QString &item_P);
57static QStringList get_fields(const QString &txt_P);
58static QString escape_str(const QString &str_P);
59
60class Q_DECL_HIDDEN KStartupInfo::Data : public KStartupInfoData
61{
62public:
63 Data()
64 : age(0)
65 {
66 } // just because it's in a QMap
67 Data(const QString &txt_P)
68 : KStartupInfoData(txt_P)
69 , age(0)
70 {
71 }
72 unsigned int age;
73};
74
75struct Q_DECL_HIDDEN KStartupInfoId::Private {
76 Private()
77 : id("")
78 {
79 }
80
81 QString to_text() const;
82
83 QByteArray id; // id
84};
85
86struct Q_DECL_HIDDEN KStartupInfoData::Private {
87 Private()
88 : desktop(0)
89 , wmclass("")
90 , hostname("")
91 , silent(KStartupInfoData::Unknown)
92 , screen(-1)
93 , xinerama(-1)
94 {
95 }
96
97 QString to_text() const;
98 void remove_pid(pid_t pid);
99
100 QString bin;
101 QString name;
102 QString description;
103 QString icon;
104 int desktop;
105 QList<pid_t> pids;
106 QByteArray wmclass;
107 QByteArray hostname;
108 KStartupInfoData::TriState silent;
109 int screen;
110 int xinerama;
111 QString application_id;
112};
113
114class Q_DECL_HIDDEN KStartupInfo::Private
115{
116public:
117 // private slots
118 void startups_cleanup();
119 void startups_cleanup_no_age();
120 void got_message(const QString &msg);
121 void window_added(WId w);
122 void slot_window_added(WId w);
123
124 void init(int flags);
125 void got_startup_info(const QString &msg_P, bool update_only_P);
126 void got_remove_startup_info(const QString &msg_P);
127 void new_startup_info_internal(const KStartupInfoId &id_P, Data &data_P, bool update_only_P);
128 void removeAllStartupInfoInternal(const KStartupInfoId &id_P);
129 /**
130 * Emits the gotRemoveStartup signal and erases the @p it from the startups map.
131 * @returns Iterator to next item in the startups map.
132 **/
133 QMap<KStartupInfoId, Data>::iterator removeStartupInfoInternal(QMap<KStartupInfoId, Data>::iterator it);
134 void remove_startup_pids(const KStartupInfoId &id, const KStartupInfoData &data);
135 void remove_startup_pids(const KStartupInfoData &data);
136 startup_t check_startup_internal(WId w, KStartupInfoId *id, KStartupInfoData *data);
137 bool find_id(const QByteArray &id_P, KStartupInfoId *id_O, KStartupInfoData *data_O);
138 bool find_pid(pid_t pid_P, const QByteArray &hostname, KStartupInfoId *id_O, KStartupInfoData *data_O);
139 bool find_wclass(const QByteArray &res_name_P, const QByteArray &res_class_P, KStartupInfoId *id_O, KStartupInfoData *data_O);
140 void startups_cleanup_internal(bool age_P);
141 void clean_all_noncompliant();
142 static QString check_required_startup_fields(const QString &msg, const KStartupInfoData &data, int screen);
143 static void setWindowStartupId(WId w_P, const QByteArray &id_P);
144
145 KStartupInfo *q;
146 unsigned int timeout;
147 QMap<KStartupInfoId, KStartupInfo::Data> startups;
148 // contains silenced ASN's only if !AnnounceSilencedChanges
149 QMap<KStartupInfoId, KStartupInfo::Data> silent_startups;
150 // contains ASN's that had change: but no new: yet
151 QMap<KStartupInfoId, KStartupInfo::Data> uninited_startups;
152 KXMessages msgs;
153 QTimer *cleanup;
154 int flags;
155
156 Private(int flags_P, KStartupInfo *qq)
157 : q(qq)
158 , timeout(60)
159 , msgs(NET_STARTUP_MSG)
160 , cleanup(nullptr)
161 , flags(flags_P)
162 {
163 }
164
165 void createConnections()
166 {
167 // d == nullptr means "disabled"
168 if (!QX11Info::isPlatformX11() || !QX11Info::display()) {
169 return;
170 }
171
172 if (!(flags & DisableKWinModule)) {
173 QObject::connect(sender: KX11Extras::self(), SIGNAL(windowAdded(WId)), receiver: q, SLOT(slot_window_added(WId)));
174 }
175 QObject::connect(sender: &msgs, SIGNAL(gotMessage(QString)), receiver: q, SLOT(got_message(QString)));
176 cleanup = new QTimer(q);
177 QObject::connect(sender: cleanup, SIGNAL(timeout()), receiver: q, SLOT(startups_cleanup()));
178 }
179};
180
181KStartupInfo::KStartupInfo(int flags_P, QObject *parent_P)
182 : QObject(parent_P)
183 , d(new Private(flags_P, this))
184{
185 d->createConnections();
186}
187
188KStartupInfo::~KStartupInfo()
189{
190 delete d;
191}
192
193void KStartupInfo::Private::got_message(const QString &msg_P)
194{
195 // TODO do something with SCREEN= ?
196 // qCDebug(LOG_KWINDOWSYSTEM) << "got:" << msg_P;
197 QString msg = msg_P.trimmed();
198 if (msg.startsWith(s: QLatin1String("new:"))) { // must match length below
199 got_startup_info(msg_P: msg.mid(position: 4), update_only_P: false);
200 } else if (msg.startsWith(s: QLatin1String("change:"))) { // must match length below
201 got_startup_info(msg_P: msg.mid(position: 7), update_only_P: true);
202 } else if (msg.startsWith(s: QLatin1String("remove:"))) { // must match length below
203 got_remove_startup_info(msg_P: msg.mid(position: 7));
204 }
205}
206
207// if the application stops responding for a while, KWindowSystem may get
208// the information about the already mapped window before KXMessages
209// actually gets the info about the started application (depends
210// on their order in the native x11 event filter)
211// simply delay info from KWindowSystem a bit
212// SELI???
213namespace
214{
215class DelayedWindowEvent : public QEvent
216{
217public:
218 DelayedWindowEvent(WId w_P)
219 : QEvent(uniqueType())
220 , w(w_P)
221 {
222 }
223 Window w;
224 static Type uniqueType()
225 {
226 return Type(QEvent::User + 15);
227 }
228};
229}
230
231void KStartupInfo::Private::slot_window_added(WId w_P)
232{
233 qApp->postEvent(receiver: q, event: new DelayedWindowEvent(w_P));
234}
235
236void KStartupInfo::customEvent(QEvent *e_P)
237{
238 if (e_P->type() == DelayedWindowEvent::uniqueType()) {
239 d->window_added(w: static_cast<DelayedWindowEvent *>(e_P)->w);
240 } else
241 QObject::customEvent(event: e_P);
242}
243
244void KStartupInfo::Private::window_added(WId w_P)
245{
246 KStartupInfoId id;
247 KStartupInfoData data;
248 startup_t ret = check_startup_internal(w: w_P, id: &id, data: &data);
249 switch (ret) {
250 case Match:
251 // qCDebug(LOG_KWINDOWSYSTEM) << "new window match";
252 break;
253 case NoMatch:
254 break; // nothing
255 case CantDetect:
256 if (flags & CleanOnCantDetect) {
257 clean_all_noncompliant();
258 }
259 break;
260 }
261}
262
263void KStartupInfo::Private::got_startup_info(const QString &msg_P, bool update_P)
264{
265 KStartupInfoId id(msg_P);
266 if (id.isNull()) {
267 return;
268 }
269 KStartupInfo::Data data(msg_P);
270 new_startup_info_internal(id_P: id, data_P&: data, update_only_P: update_P);
271}
272
273void KStartupInfo::Private::new_startup_info_internal(const KStartupInfoId &id_P, KStartupInfo::Data &data_P, bool update_P)
274{
275 if (id_P.isNull()) {
276 return;
277 }
278 if (startups.contains(key: id_P)) {
279 // already reported, update
280 startups[id_P].update(data: data_P);
281 startups[id_P].age = 0; // CHECKME
282 // qCDebug(LOG_KWINDOWSYSTEM) << "updating";
283 if (startups[id_P].silent() == KStartupInfo::Data::Yes && !(flags & AnnounceSilenceChanges)) {
284 silent_startups[id_P] = startups[id_P];
285 startups.remove(key: id_P);
286 Q_EMIT q->gotRemoveStartup(id: id_P, data: silent_startups[id_P]);
287 return;
288 }
289 Q_EMIT q->gotStartupChange(id: id_P, data: startups[id_P]);
290 return;
291 }
292 if (silent_startups.contains(key: id_P)) {
293 // already reported, update
294 silent_startups[id_P].update(data: data_P);
295 silent_startups[id_P].age = 0; // CHECKME
296 // qCDebug(LOG_KWINDOWSYSTEM) << "updating silenced";
297 if (silent_startups[id_P].silent() != Data::Yes) {
298 startups[id_P] = silent_startups[id_P];
299 silent_startups.remove(key: id_P);
300 q->Q_EMIT gotNewStartup(id: id_P, data: startups[id_P]);
301 return;
302 }
303 Q_EMIT q->gotStartupChange(id: id_P, data: silent_startups[id_P]);
304 return;
305 }
306 if (uninited_startups.contains(key: id_P)) {
307 uninited_startups[id_P].update(data: data_P);
308 // qCDebug(LOG_KWINDOWSYSTEM) << "updating uninited";
309 if (!update_P) { // uninited finally got new:
310 startups[id_P] = uninited_startups[id_P];
311 uninited_startups.remove(key: id_P);
312 Q_EMIT q->gotNewStartup(id: id_P, data: startups[id_P]);
313 return;
314 }
315 // no change announce, it's still uninited
316 return;
317 }
318 if (update_P) { // change: without any new: first
319 // qCDebug(LOG_KWINDOWSYSTEM) << "adding uninited";
320 uninited_startups.insert(key: id_P, value: data_P);
321 } else if (data_P.silent() != Data::Yes || flags & AnnounceSilenceChanges) {
322 // qCDebug(LOG_KWINDOWSYSTEM) << "adding";
323 startups.insert(key: id_P, value: data_P);
324 Q_EMIT q->gotNewStartup(id: id_P, data: data_P);
325 } else { // new silenced, and silent shouldn't be announced
326 // qCDebug(LOG_KWINDOWSYSTEM) << "adding silent";
327 silent_startups.insert(key: id_P, value: data_P);
328 }
329 cleanup->start(msec: 1000); // 1 sec
330}
331
332void KStartupInfo::Private::got_remove_startup_info(const QString &msg_P)
333{
334 KStartupInfoId id(msg_P);
335 KStartupInfoData data(msg_P);
336 if (!data.pids().isEmpty()) {
337 if (!id.isNull()) {
338 remove_startup_pids(id, data);
339 } else {
340 remove_startup_pids(data);
341 }
342 return;
343 }
344 removeAllStartupInfoInternal(id_P: id);
345}
346
347void KStartupInfo::Private::removeAllStartupInfoInternal(const KStartupInfoId &id_P)
348{
349 auto it = startups.find(key: id_P);
350 if (it != startups.end()) {
351 // qCDebug(LOG_KWINDOWSYSTEM) << "removing";
352 Q_EMIT q->gotRemoveStartup(id: it.key(), data: it.value());
353 startups.erase(it);
354 return;
355 }
356 it = silent_startups.find(key: id_P);
357 if (it != silent_startups.end()) {
358 silent_startups.erase(it);
359 return;
360 }
361 it = uninited_startups.find(key: id_P);
362 if (it != uninited_startups.end()) {
363 uninited_startups.erase(it);
364 }
365}
366
367QMap<KStartupInfoId, KStartupInfo::Data>::iterator KStartupInfo::Private::removeStartupInfoInternal(QMap<KStartupInfoId, Data>::iterator it)
368{
369 Q_EMIT q->gotRemoveStartup(id: it.key(), data: it.value());
370 return startups.erase(it);
371}
372
373void KStartupInfo::Private::remove_startup_pids(const KStartupInfoData &data_P)
374{
375 // first find the matching info
376 for (QMap<KStartupInfoId, KStartupInfo::Data>::Iterator it = startups.begin(); it != startups.end(); ++it) {
377 if ((*it).hostname() != data_P.hostname()) {
378 continue;
379 }
380 if (!(*it).is_pid(pid: data_P.pids().first())) {
381 continue; // not the matching info
382 }
383 remove_startup_pids(id: it.key(), data: data_P);
384 break;
385 }
386}
387
388void KStartupInfo::Private::remove_startup_pids(const KStartupInfoId &id_P, const KStartupInfoData &data_P)
389{
390 if (data_P.pids().isEmpty()) {
391 qFatal(msg: "data_P.pids().isEmpty()");
392 }
393 Data *data = nullptr;
394 if (startups.contains(key: id_P)) {
395 data = &startups[id_P];
396 } else if (silent_startups.contains(key: id_P)) {
397 data = &silent_startups[id_P];
398 } else if (uninited_startups.contains(key: id_P)) {
399 data = &uninited_startups[id_P];
400 } else {
401 return;
402 }
403 const auto pids = data_P.pids();
404 for (auto pid : pids) {
405 data->d->remove_pid(pid); // remove all pids from the info
406 }
407 if (data->pids().isEmpty()) { // all pids removed -> remove info
408 removeAllStartupInfoInternal(id_P);
409 }
410}
411
412bool KStartupInfo::sendStartup(const KStartupInfoId &id_P, const KStartupInfoData &data_P)
413{
414 if (id_P.isNull()) {
415 return false;
416 }
417 return sendStartupXcb(conn: QX11Info::connection(), screen: QX11Info::appScreen(), id: id_P, data: data_P);
418}
419
420bool KStartupInfo::sendStartupXcb(xcb_connection_t *conn, int screen, const KStartupInfoId &id_P, const KStartupInfoData &data_P)
421{
422 if (id_P.isNull()) {
423 return false;
424 }
425 QString msg = QStringLiteral("new: %1 %2").arg(args: id_P.d->to_text(), args: data_P.d->to_text());
426 msg = Private::check_required_startup_fields(msg, data: data_P, screen);
427#ifdef KSTARTUPINFO_ALL_DEBUG
428 qCDebug(LOG_KWINDOWSYSTEM) << "sending " << msg;
429#endif
430 return KXMessages::broadcastMessageX(c: conn, msg_type: NET_STARTUP_MSG, message: msg, screenNumber: screen);
431}
432
433QString KStartupInfo::Private::check_required_startup_fields(const QString &msg, const KStartupInfoData &data_P, int screen)
434{
435 QString ret = msg;
436 if (data_P.name().isEmpty()) {
437 // qWarning() << "NAME not specified in initial startup message";
438 QString name = data_P.bin();
439 if (name.isEmpty()) {
440 name = QStringLiteral("UNKNOWN");
441 }
442 ret += QStringLiteral(" NAME=\"%1\"").arg(a: escape_str(str_P: name));
443 }
444 if (data_P.screen() == -1) { // add automatically if needed
445 ret += QStringLiteral(" SCREEN=%1").arg(a: screen);
446 }
447 return ret;
448}
449
450bool KStartupInfo::sendChange(const KStartupInfoId &id_P, const KStartupInfoData &data_P)
451{
452 if (id_P.isNull()) {
453 return false;
454 }
455 return sendChangeXcb(conn: QX11Info::connection(), screen: QX11Info::appScreen(), id: id_P, data: data_P);
456}
457
458bool KStartupInfo::sendChangeXcb(xcb_connection_t *conn, int screen, const KStartupInfoId &id_P, const KStartupInfoData &data_P)
459{
460 if (id_P.isNull()) {
461 return false;
462 }
463 QString msg = QStringLiteral("change: %1 %2").arg(args: id_P.d->to_text(), args: data_P.d->to_text());
464#ifdef KSTARTUPINFO_ALL_DEBUG
465 qCDebug(LOG_KWINDOWSYSTEM) << "sending " << msg;
466#endif
467 return KXMessages::broadcastMessageX(c: conn, msg_type: NET_STARTUP_MSG, message: msg, screenNumber: screen);
468}
469
470bool KStartupInfo::sendFinish(const KStartupInfoId &id_P)
471{
472 if (id_P.isNull()) {
473 return false;
474 }
475 return sendFinishXcb(conn: QX11Info::connection(), screen: QX11Info::appScreen(), id: id_P);
476}
477
478bool KStartupInfo::sendFinishXcb(xcb_connection_t *conn, int screen, const KStartupInfoId &id_P)
479{
480 if (id_P.isNull()) {
481 return false;
482 }
483 QString msg = QStringLiteral("remove: %1").arg(a: id_P.d->to_text());
484#ifdef KSTARTUPINFO_ALL_DEBUG
485 qCDebug(LOG_KWINDOWSYSTEM) << "sending " << msg;
486#endif
487 return KXMessages::broadcastMessageX(c: conn, msg_type: NET_STARTUP_MSG, message: msg, screenNumber: screen);
488}
489
490bool KStartupInfo::sendFinish(const KStartupInfoId &id_P, const KStartupInfoData &data_P)
491{
492 // if( id_P.isNull()) // id may be null, the pids and hostname matter then
493 // return false;
494 return sendFinishXcb(conn: QX11Info::connection(), screen: QX11Info::appScreen(), id: id_P, data: data_P);
495}
496
497bool KStartupInfo::sendFinishXcb(xcb_connection_t *conn, int screen, const KStartupInfoId &id_P, const KStartupInfoData &data_P)
498{
499 // if( id_P.isNull()) // id may be null, the pids and hostname matter then
500 // return false;
501 QString msg = QStringLiteral("remove: %1 %2").arg(args: id_P.d->to_text(), args: data_P.d->to_text());
502#ifdef KSTARTUPINFO_ALL_DEBUG
503 qCDebug(LOG_KWINDOWSYSTEM) << "sending " << msg;
504#endif
505 return KXMessages::broadcastMessageX(c: conn, msg_type: NET_STARTUP_MSG, message: msg, screenNumber: screen);
506}
507
508void KStartupInfo::appStarted()
509{
510 QByteArray startupId = s_startup_id;
511
512 if (startupId.isEmpty()) {
513 startupId = QX11Info::nextStartupId();
514 }
515
516 appStarted(startup_id: startupId);
517 setStartupId("0"); // reset the id, no longer valid (must use clearStartupId() to avoid infinite loop)
518}
519
520void KStartupInfo::appStarted(const QByteArray &startup_id)
521{
522 KStartupInfoId id;
523 id.initId(id: startup_id);
524 if (id.isNull()) {
525 return;
526 }
527 if (QX11Info::isPlatformX11() && !qEnvironmentVariableIsEmpty(varName: "DISPLAY")) { // don't rely on QX11Info::display()
528 KStartupInfo::sendFinish(id_P: id);
529 }
530}
531
532void KStartupInfo::setStartupId(const QByteArray &startup_id)
533{
534 if (startup_id == s_startup_id) {
535 return;
536 }
537 if (startup_id.isEmpty()) {
538 s_startup_id = "0";
539 } else {
540 s_startup_id = startup_id;
541 if (QX11Info::isPlatformX11()) {
542 KStartupInfoId id;
543 id.initId(id: startup_id);
544 long timestamp = id.timestamp();
545 if (timestamp != 0) {
546 if (QX11Info::appUserTime() == 0 || NET::timestampCompare(time1: timestamp, time2: QX11Info::appUserTime()) > 0) { // time > appUserTime
547 QX11Info::setAppUserTime(timestamp);
548 }
549 if (QX11Info::appTime() == 0 || NET::timestampCompare(time1: timestamp, time2: QX11Info::appTime()) > 0) { // time > appTime
550 QX11Info::setAppTime(timestamp);
551 }
552 }
553 }
554 }
555}
556
557void KStartupInfo::setNewStartupId(QWindow *window, const QByteArray &startup_id)
558{
559 Q_ASSERT(window);
560 setStartupId(startup_id);
561 bool activate = true;
562 if (window != nullptr && QX11Info::isPlatformX11()) {
563 if (!startup_id.isEmpty() && startup_id != "0") {
564 NETRootInfo i(QX11Info::connection(), NET::Supported);
565 if (i.isSupported(property: NET::WM2StartupId)) {
566 KStartupInfo::Private::setWindowStartupId(w_P: window->winId(), id_P: startup_id);
567 activate = false; // WM will take care of it
568 }
569 }
570 if (activate) {
571 KX11Extras::setOnDesktop(win: window->winId(), desktop: KX11Extras::currentDesktop());
572 // This is not very nice, but there's no way how to get any
573 // usable timestamp without ASN, so force activating the window.
574 // And even with ASN, it's not possible to get the timestamp here,
575 // so if the WM doesn't have support for ASN, it can't be used either.
576 KX11Extras::forceActiveWindow(win: window->winId());
577 }
578 }
579}
580
581KStartupInfo::startup_t KStartupInfo::checkStartup(WId w_P, KStartupInfoId &id_O, KStartupInfoData &data_O)
582{
583 return d->check_startup_internal(w: w_P, id: &id_O, data: &data_O);
584}
585
586KStartupInfo::startup_t KStartupInfo::checkStartup(WId w_P, KStartupInfoId &id_O)
587{
588 return d->check_startup_internal(w: w_P, id: &id_O, data: nullptr);
589}
590
591KStartupInfo::startup_t KStartupInfo::checkStartup(WId w_P, KStartupInfoData &data_O)
592{
593 return d->check_startup_internal(w: w_P, id: nullptr, data: &data_O);
594}
595
596KStartupInfo::startup_t KStartupInfo::checkStartup(WId w_P)
597{
598 return d->check_startup_internal(w: w_P, id: nullptr, data: nullptr);
599}
600
601KStartupInfo::startup_t KStartupInfo::Private::check_startup_internal(WId w_P, KStartupInfoId *id_O, KStartupInfoData *data_O)
602{
603 if (startups.isEmpty()) {
604 return NoMatch; // no startups
605 }
606 // Strategy:
607 //
608 // Is this a compliant app ?
609 // - Yes - test for match
610 // - No - Is this a NET_WM compliant app ?
611 // - Yes - test for pid match
612 // - No - test for WM_CLASS match
613 qCDebug(LOG_KWINDOWSYSTEM) << "check_startup";
614 QByteArray id = windowStartupId(w: w_P);
615 if (!id.isNull()) {
616 if (id.isEmpty() || id == "0") { // means ignore this window
617 qCDebug(LOG_KWINDOWSYSTEM) << "ignore";
618 return NoMatch;
619 }
620 return find_id(id_P: id, id_O, data_O) ? Match : NoMatch;
621 }
622 if (!QX11Info::isPlatformX11()) {
623 qCDebug(LOG_KWINDOWSYSTEM) << "check_startup:cantdetect";
624 return CantDetect;
625 }
626 NETWinInfo info(QX11Info::connection(),
627 w_P,
628 QX11Info::appRootWindow(),
629 NET::WMWindowType | NET::WMPid | NET::WMState,
630 NET::WM2WindowClass | NET::WM2ClientMachine | NET::WM2TransientFor);
631 pid_t pid = info.pid();
632 if (pid > 0) {
633 QByteArray hostname = info.clientMachine();
634 if (!hostname.isEmpty() && find_pid(pid_P: pid, hostname, id_O, data_O)) {
635 return Match;
636 }
637 // try XClass matching , this PID stuff sucks :(
638 }
639 if (find_wclass(res_name_P: info.windowClassName(), res_class_P: info.windowClassClass(), id_O, data_O)) {
640 return Match;
641 }
642 // ignore NET::Tool and other special window types, if they can't be matched
643 NET::WindowType type = info.windowType(supported_types: NET::NormalMask | NET::DesktopMask | NET::DockMask | NET::ToolbarMask | NET::MenuMask | NET::DialogMask
644 | NET::OverrideMask | NET::TopMenuMask | NET::UtilityMask | NET::SplashMask);
645 if (type != NET::Normal && type != NET::Override && type != NET::Unknown && type != NET::Dialog && type != NET::Utility)
646 // && type != NET::Dock ) why did I put this here?
647 {
648 return NoMatch;
649 }
650 // lets see if this is a transient
651 xcb_window_t transient_for = info.transientFor();
652 if (transient_for != QX11Info::appRootWindow() && transient_for != XCB_WINDOW_NONE) {
653 return NoMatch;
654 }
655 qCDebug(LOG_KWINDOWSYSTEM) << "check_startup:cantdetect";
656 return CantDetect;
657}
658
659bool KStartupInfo::Private::find_id(const QByteArray &id_P, KStartupInfoId *id_O, KStartupInfoData *data_O)
660{
661 // qCDebug(LOG_KWINDOWSYSTEM) << "find_id:" << id_P;
662 KStartupInfoId id;
663 id.initId(id: id_P);
664 if (startups.contains(key: id)) {
665 if (id_O != nullptr) {
666 *id_O = id;
667 }
668 if (data_O != nullptr) {
669 *data_O = startups[id];
670 }
671 // qCDebug(LOG_KWINDOWSYSTEM) << "check_startup_id:match";
672 return true;
673 }
674 return false;
675}
676
677bool KStartupInfo::Private::find_pid(pid_t pid_P, const QByteArray &hostname_P, KStartupInfoId *id_O, KStartupInfoData *data_O)
678{
679 // qCDebug(LOG_KWINDOWSYSTEM) << "find_pid:" << pid_P;
680 for (QMap<KStartupInfoId, KStartupInfo::Data>::Iterator it = startups.begin(); it != startups.end(); ++it) {
681 if ((*it).is_pid(pid: pid_P) && (*it).hostname() == hostname_P) {
682 // Found it !
683 if (id_O != nullptr) {
684 *id_O = it.key();
685 }
686 if (data_O != nullptr) {
687 *data_O = *it;
688 }
689 // non-compliant, remove on first match
690 removeStartupInfoInternal(it);
691 // qCDebug(LOG_KWINDOWSYSTEM) << "check_startup_pid:match";
692 return true;
693 }
694 }
695 return false;
696}
697
698bool KStartupInfo::Private::find_wclass(const QByteArray &_res_name, const QByteArray &_res_class, KStartupInfoId *id_O, KStartupInfoData *data_O)
699{
700 QByteArray res_name = _res_name.toLower();
701 QByteArray res_class = _res_class.toLower();
702 // qCDebug(LOG_KWINDOWSYSTEM) << "find_wclass:" << res_name << ":" << res_class;
703 for (QMap<KStartupInfoId, Data>::Iterator it = startups.begin(); it != startups.end(); ++it) {
704 const QByteArray wmclass = (*it).findWMClass();
705 if (wmclass.toLower() == res_name || wmclass.toLower() == res_class) {
706 // Found it !
707 if (id_O != nullptr) {
708 *id_O = it.key();
709 }
710 if (data_O != nullptr) {
711 *data_O = *it;
712 }
713 // non-compliant, remove on first match
714 removeStartupInfoInternal(it);
715 // qCDebug(LOG_KWINDOWSYSTEM) << "check_startup_wclass:match";
716 return true;
717 }
718 }
719 return false;
720}
721
722QByteArray KStartupInfo::windowStartupId(WId w_P)
723{
724 if (!QX11Info::isPlatformX11()) {
725 return QByteArray();
726 }
727 NETWinInfo info(QX11Info::connection(), w_P, QX11Info::appRootWindow(), NET::Properties(), NET::WM2StartupId | NET::WM2GroupLeader);
728 QByteArray ret = info.startupId();
729 if (ret.isEmpty() && info.groupLeader() != XCB_WINDOW_NONE) {
730 // retry with window group leader, as the spec says
731 NETWinInfo groupLeaderInfo(QX11Info::connection(), info.groupLeader(), QX11Info::appRootWindow(), NET::Properties(), NET::Properties2());
732 ret = groupLeaderInfo.startupId();
733 }
734 return ret;
735}
736
737void KStartupInfo::Private::setWindowStartupId(WId w_P, const QByteArray &id_P)
738{
739 if (!QX11Info::isPlatformX11()) {
740 return;
741 }
742 if (id_P.isNull()) {
743 return;
744 }
745 NETWinInfo info(QX11Info::connection(), w_P, QX11Info::appRootWindow(), NET::Properties(), NET::Properties2());
746 info.setStartupId(id_P.constData());
747}
748
749void KStartupInfo::setTimeout(unsigned int secs_P)
750{
751 d->timeout = secs_P;
752 // schedule removing entries that are older than the new timeout
753 QTimer::singleShot(msec: 0, receiver: this, SLOT(startups_cleanup_no_age()));
754}
755
756void KStartupInfo::Private::startups_cleanup_no_age()
757{
758 startups_cleanup_internal(age_P: false);
759}
760
761void KStartupInfo::Private::startups_cleanup()
762{
763 if (startups.isEmpty() && silent_startups.isEmpty() && uninited_startups.isEmpty()) {
764 cleanup->stop();
765 return;
766 }
767 startups_cleanup_internal(age_P: true);
768}
769
770void KStartupInfo::Private::startups_cleanup_internal(bool age_P)
771{
772 auto checkCleanup = [this, age_P](QMap<KStartupInfoId, KStartupInfo::Data> &s, bool doEmit) {
773 auto it = s.begin();
774 while (it != s.end()) {
775 if (age_P) {
776 (*it).age++;
777 }
778 unsigned int tout = timeout;
779 if ((*it).silent() == KStartupInfo::Data::Yes) {
780 // give kdesu time to get a password
781 tout *= 20;
782 }
783 const QByteArray timeoutEnvVariable = qgetenv(varName: "KSTARTUPINFO_TIMEOUT");
784 if (!timeoutEnvVariable.isNull()) {
785 tout = timeoutEnvVariable.toUInt();
786 }
787 if ((*it).age >= tout) {
788 if (doEmit) {
789 Q_EMIT q->gotRemoveStartup(id: it.key(), data: it.value());
790 }
791 it = s.erase(it);
792 } else {
793 ++it;
794 }
795 }
796 };
797 checkCleanup(startups, true);
798 checkCleanup(silent_startups, false);
799 checkCleanup(uninited_startups, false);
800}
801
802void KStartupInfo::Private::clean_all_noncompliant()
803{
804 for (QMap<KStartupInfoId, KStartupInfo::Data>::Iterator it = startups.begin(); it != startups.end();) {
805 if ((*it).WMClass() != "0") {
806 ++it;
807 continue;
808 }
809 it = removeStartupInfoInternal(it);
810 }
811}
812
813QByteArray KStartupInfo::createNewStartupId()
814{
815 quint32 timestamp = 0;
816 if (QX11Info::isPlatformX11()) {
817 timestamp = QX11Info::getTimestamp();
818 }
819 return KStartupInfo::createNewStartupIdForTimestamp(timestamp);
820}
821
822QByteArray KStartupInfo::createNewStartupIdForTimestamp(quint32 timestamp)
823{
824 // Assign a unique id, use hostname+time+pid, that should be 200% unique.
825 // Also append the user timestamp (for focus stealing prevention).
826 struct timeval tm;
827 gettimeofday(tv: &tm, tz: nullptr);
828 char hostname[256];
829 hostname[0] = '\0';
830 if (!gethostname(name: hostname, len: 255)) {
831 hostname[sizeof(hostname) - 1] = '\0';
832 }
833 QByteArray id = QStringLiteral("%1;%2;%3;%4_TIME%5").arg(a: hostname).arg(a: tm.tv_sec).arg(a: tm.tv_usec).arg(a: getpid()).arg(a: timestamp).toUtf8();
834 // qCDebug(LOG_KWINDOWSYSTEM) << "creating: " << id << ":" << (qApp ? qAppName() : QString("unnamed app") /* e.g. kdeinit */);
835 return id;
836}
837
838const QByteArray &KStartupInfoId::id() const
839{
840 return d->id;
841}
842
843QString KStartupInfoId::Private::to_text() const
844{
845 return QStringLiteral(" ID=\"%1\" ").arg(a: escape_str(str_P: id));
846}
847
848KStartupInfoId::KStartupInfoId(const QString &txt_P)
849 : d(new Private)
850{
851 const QStringList items = get_fields(txt_P);
852 for (QStringList::ConstIterator it = items.begin(); it != items.end(); ++it) {
853 if ((*it).startsWith(s: QLatin1String("ID="))) {
854 d->id = get_cstr(item_P: *it);
855 }
856 }
857}
858
859void KStartupInfoId::initId(const QByteArray &id_P)
860{
861 if (!id_P.isEmpty()) {
862 d->id = id_P;
863#ifdef KSTARTUPINFO_ALL_DEBUG
864 qCDebug(LOG_KWINDOWSYSTEM) << "using: " << d->id;
865#endif
866 return;
867 }
868 const QByteArray startup_env = qgetenv(varName: NET_STARTUP_ENV);
869 if (!startup_env.isEmpty()) {
870 // already has id
871 d->id = startup_env;
872#ifdef KSTARTUPINFO_ALL_DEBUG
873 qCDebug(LOG_KWINDOWSYSTEM) << "reusing: " << d->id;
874#endif
875 return;
876 }
877 d->id = KStartupInfo::createNewStartupId();
878}
879
880bool KStartupInfoId::setupStartupEnv() const
881{
882 if (isNull()) {
883 qunsetenv(varName: NET_STARTUP_ENV);
884 return false;
885 }
886 return !qputenv(varName: NET_STARTUP_ENV, value: id()) == 0;
887}
888
889void KStartupInfo::resetStartupEnv()
890{
891 qunsetenv(varName: NET_STARTUP_ENV);
892}
893
894KStartupInfoId::KStartupInfoId()
895 : d(new Private)
896{
897}
898
899KStartupInfoId::~KStartupInfoId()
900{
901 delete d;
902}
903
904KStartupInfoId::KStartupInfoId(const KStartupInfoId &id_P)
905 : d(new Private(*id_P.d))
906{
907}
908
909KStartupInfoId &KStartupInfoId::operator=(const KStartupInfoId &id_P)
910{
911 if (&id_P == this) {
912 return *this;
913 }
914 *d = *id_P.d;
915 return *this;
916}
917
918bool KStartupInfoId::operator==(const KStartupInfoId &id_P) const
919{
920 return id() == id_P.id();
921}
922
923bool KStartupInfoId::operator!=(const KStartupInfoId &id_P) const
924{
925 return !(*this == id_P);
926}
927
928// needed for QMap
929bool KStartupInfoId::operator<(const KStartupInfoId &id_P) const
930{
931 return id() < id_P.id();
932}
933
934bool KStartupInfoId::isNull() const
935{
936 return d->id.isEmpty() || d->id == "0";
937}
938
939unsigned long KStartupInfoId::timestamp() const
940{
941 if (isNull()) {
942 return 0;
943 }
944 // As per the spec, the ID must contain the _TIME followed by the timestamp
945 int pos = d->id.lastIndexOf(bv: "_TIME");
946 if (pos >= 0) {
947 bool ok;
948 unsigned long time = QString(d->id.mid(index: pos + 5)).toULong(ok: &ok);
949 if (!ok && d->id[pos + 5] == '-') { // try if it's as a negative signed number perhaps
950 time = QString(d->id.mid(index: pos + 5)).toLong(ok: &ok);
951 }
952 if (ok) {
953 return time;
954 }
955 }
956 return 0;
957}
958
959QString KStartupInfoData::Private::to_text() const
960{
961 QString ret;
962 // prepare some space which should be always enough.
963 // No need to squeze at the end, as the result is only used as intermediate string
964 ret.reserve(asize: 256);
965 if (!bin.isEmpty()) {
966 ret += QStringLiteral(" BIN=\"%1\"").arg(a: escape_str(str_P: bin));
967 }
968 if (!name.isEmpty()) {
969 ret += QStringLiteral(" NAME=\"%1\"").arg(a: escape_str(str_P: name));
970 }
971 if (!description.isEmpty()) {
972 ret += QStringLiteral(" DESCRIPTION=\"%1\"").arg(a: escape_str(str_P: description));
973 }
974 if (!icon.isEmpty()) {
975 ret += QStringLiteral(" ICON=\"%1\"").arg(a: icon);
976 }
977 if (desktop != 0) {
978 ret += QStringLiteral(" DESKTOP=%1").arg(a: desktop == NET::OnAllDesktops ? NET::OnAllDesktops : desktop - 1); // spec counts from 0
979 }
980 if (!wmclass.isEmpty()) {
981 ret += QStringLiteral(" WMCLASS=\"%1\"").arg(a: QString(wmclass));
982 }
983 if (!hostname.isEmpty()) {
984 ret += QStringLiteral(" HOSTNAME=%1").arg(a: QString(hostname));
985 }
986 for (QList<pid_t>::ConstIterator it = pids.begin(); it != pids.end(); ++it) {
987 ret += QStringLiteral(" PID=%1").arg(a: *it);
988 }
989 if (silent != KStartupInfoData::Unknown) {
990 ret += QStringLiteral(" SILENT=%1").arg(a: silent == KStartupInfoData::Yes ? 1 : 0);
991 }
992 if (screen != -1) {
993 ret += QStringLiteral(" SCREEN=%1").arg(a: screen);
994 }
995 if (xinerama != -1) {
996 ret += QStringLiteral(" XINERAMA=%1").arg(a: xinerama);
997 }
998 if (!application_id.isEmpty()) {
999 ret += QStringLiteral(" APPLICATION_ID=\"%1\"").arg(a: application_id);
1000 }
1001 return ret;
1002}
1003
1004KStartupInfoData::KStartupInfoData(const QString &txt_P)
1005 : d(new Private)
1006{
1007 const QStringList items = get_fields(txt_P);
1008 for (QStringList::ConstIterator it = items.begin(); it != items.end(); ++it) {
1009 if ((*it).startsWith(s: QLatin1String("BIN="))) {
1010 d->bin = get_str(item_P: *it);
1011 } else if ((*it).startsWith(s: QLatin1String("NAME="))) {
1012 d->name = get_str(item_P: *it);
1013 } else if ((*it).startsWith(s: QLatin1String("DESCRIPTION="))) {
1014 d->description = get_str(item_P: *it);
1015 } else if ((*it).startsWith(s: QLatin1String("ICON="))) {
1016 d->icon = get_str(item_P: *it);
1017 } else if ((*it).startsWith(s: QLatin1String("DESKTOP="))) {
1018 d->desktop = get_num(item_P: *it);
1019 if (d->desktop != NET::OnAllDesktops) {
1020 ++d->desktop; // spec counts from 0
1021 }
1022 } else if ((*it).startsWith(s: QLatin1String("WMCLASS="))) {
1023 d->wmclass = get_cstr(item_P: *it);
1024 } else if ((*it).startsWith(s: QLatin1String("HOSTNAME="))) { // added to version 1 (2014)
1025 d->hostname = get_cstr(item_P: *it);
1026 } else if ((*it).startsWith(s: QLatin1String("PID="))) { // added to version 1 (2014)
1027 addPid(pid: get_num(item_P: *it));
1028 } else if ((*it).startsWith(s: QLatin1String("SILENT="))) {
1029 d->silent = get_num(item_P: *it) != 0 ? Yes : No;
1030 } else if ((*it).startsWith(s: QLatin1String("SCREEN="))) {
1031 d->screen = get_num(item_P: *it);
1032 } else if ((*it).startsWith(s: QLatin1String("XINERAMA="))) {
1033 d->xinerama = get_num(item_P: *it);
1034 } else if ((*it).startsWith(s: QLatin1String("APPLICATION_ID="))) {
1035 d->application_id = get_str(item_P: *it);
1036 }
1037 }
1038}
1039
1040KStartupInfoData::KStartupInfoData(const KStartupInfoData &data)
1041 : d(new Private(*data.d))
1042{
1043}
1044
1045KStartupInfoData &KStartupInfoData::operator=(const KStartupInfoData &data)
1046{
1047 if (&data == this) {
1048 return *this;
1049 }
1050 *d = *data.d;
1051 return *this;
1052}
1053
1054void KStartupInfoData::update(const KStartupInfoData &data_P)
1055{
1056 if (!data_P.bin().isEmpty()) {
1057 d->bin = data_P.bin();
1058 }
1059 if (!data_P.name().isEmpty() && name().isEmpty()) { // don't overwrite
1060 d->name = data_P.name();
1061 }
1062 if (!data_P.description().isEmpty() && description().isEmpty()) { // don't overwrite
1063 d->description = data_P.description();
1064 }
1065 if (!data_P.icon().isEmpty() && icon().isEmpty()) { // don't overwrite
1066 d->icon = data_P.icon();
1067 }
1068 if (data_P.desktop() != 0 && desktop() == 0) { // don't overwrite
1069 d->desktop = data_P.desktop();
1070 }
1071 if (!data_P.d->wmclass.isEmpty()) {
1072 d->wmclass = data_P.d->wmclass;
1073 }
1074 if (!data_P.d->hostname.isEmpty()) {
1075 d->hostname = data_P.d->hostname;
1076 }
1077 for (QList<pid_t>::ConstIterator it = data_P.d->pids.constBegin(); it != data_P.d->pids.constEnd(); ++it) {
1078 addPid(pid: *it);
1079 }
1080 if (data_P.silent() != Unknown) {
1081 d->silent = data_P.silent();
1082 }
1083 if (data_P.screen() != -1) {
1084 d->screen = data_P.screen();
1085 }
1086 if (data_P.xinerama() != -1 && xinerama() != -1) { // don't overwrite
1087 d->xinerama = data_P.xinerama();
1088 }
1089 if (!data_P.applicationId().isEmpty() && applicationId().isEmpty()) { // don't overwrite
1090 d->application_id = data_P.applicationId();
1091 }
1092}
1093
1094KStartupInfoData::KStartupInfoData()
1095 : d(new Private)
1096{
1097}
1098
1099KStartupInfoData::~KStartupInfoData()
1100{
1101 delete d;
1102}
1103
1104void KStartupInfoData::setBin(const QString &bin_P)
1105{
1106 d->bin = bin_P;
1107}
1108
1109const QString &KStartupInfoData::bin() const
1110{
1111 return d->bin;
1112}
1113
1114void KStartupInfoData::setName(const QString &name_P)
1115{
1116 d->name = name_P;
1117}
1118
1119const QString &KStartupInfoData::name() const
1120{
1121 return d->name;
1122}
1123
1124const QString &KStartupInfoData::findName() const
1125{
1126 if (!name().isEmpty()) {
1127 return name();
1128 }
1129 return bin();
1130}
1131
1132void KStartupInfoData::setDescription(const QString &desc_P)
1133{
1134 d->description = desc_P;
1135}
1136
1137const QString &KStartupInfoData::description() const
1138{
1139 return d->description;
1140}
1141
1142const QString &KStartupInfoData::findDescription() const
1143{
1144 if (!description().isEmpty()) {
1145 return description();
1146 }
1147 return name();
1148}
1149
1150void KStartupInfoData::setIcon(const QString &icon_P)
1151{
1152 d->icon = icon_P;
1153}
1154
1155const QString &KStartupInfoData::findIcon() const
1156{
1157 if (!icon().isEmpty()) {
1158 return icon();
1159 }
1160 return bin();
1161}
1162
1163const QString &KStartupInfoData::icon() const
1164{
1165 return d->icon;
1166}
1167
1168void KStartupInfoData::setDesktop(int desktop_P)
1169{
1170 d->desktop = desktop_P;
1171}
1172
1173int KStartupInfoData::desktop() const
1174{
1175 return d->desktop;
1176}
1177
1178void KStartupInfoData::setWMClass(const QByteArray &wmclass_P)
1179{
1180 d->wmclass = wmclass_P;
1181}
1182
1183const QByteArray KStartupInfoData::findWMClass() const
1184{
1185 if (!WMClass().isEmpty() && WMClass() != "0") {
1186 return WMClass();
1187 }
1188 return bin().toUtf8();
1189}
1190
1191QByteArray KStartupInfoData::WMClass() const
1192{
1193 return d->wmclass;
1194}
1195
1196void KStartupInfoData::setHostname(const QByteArray &hostname_P)
1197{
1198 if (!hostname_P.isNull()) {
1199 d->hostname = hostname_P;
1200 } else {
1201 char tmp[256];
1202 tmp[0] = '\0';
1203 if (!gethostname(name: tmp, len: 255)) {
1204 tmp[sizeof(tmp) - 1] = '\0';
1205 }
1206 d->hostname = tmp;
1207 }
1208}
1209
1210QByteArray KStartupInfoData::hostname() const
1211{
1212 return d->hostname;
1213}
1214
1215void KStartupInfoData::addPid(pid_t pid_P)
1216{
1217 if (!d->pids.contains(t: pid_P)) {
1218 d->pids.append(t: pid_P);
1219 }
1220}
1221
1222void KStartupInfoData::Private::remove_pid(pid_t pid_P)
1223{
1224 pids.removeAll(t: pid_P);
1225}
1226
1227QList<pid_t> KStartupInfoData::pids() const
1228{
1229 return d->pids;
1230}
1231
1232bool KStartupInfoData::is_pid(pid_t pid_P) const
1233{
1234 return d->pids.contains(t: pid_P);
1235}
1236
1237void KStartupInfoData::setSilent(TriState state_P)
1238{
1239 d->silent = state_P;
1240}
1241
1242KStartupInfoData::TriState KStartupInfoData::silent() const
1243{
1244 return d->silent;
1245}
1246
1247void KStartupInfoData::setScreen(int _screen)
1248{
1249 d->screen = _screen;
1250}
1251
1252int KStartupInfoData::screen() const
1253{
1254 return d->screen;
1255}
1256
1257void KStartupInfoData::setXinerama(int xinerama)
1258{
1259 d->xinerama = xinerama;
1260}
1261
1262int KStartupInfoData::xinerama() const
1263{
1264 return d->xinerama;
1265}
1266
1267void KStartupInfoData::setApplicationId(const QString &desktop)
1268{
1269 if (desktop.startsWith(c: QLatin1Char('/'))) {
1270 d->application_id = desktop;
1271 return;
1272 }
1273 // the spec requires this is always a full path, in order for everyone to be able to find it
1274 QString desk = QStandardPaths::locate(type: QStandardPaths::ApplicationsLocation, fileName: desktop);
1275 if (desk.isEmpty()) {
1276 return;
1277 }
1278 d->application_id = desk;
1279}
1280
1281QString KStartupInfoData::applicationId() const
1282{
1283 return d->application_id;
1284}
1285
1286static long get_num(const QString &item_P)
1287{
1288 unsigned int pos = item_P.indexOf(c: QLatin1Char('='));
1289 return item_P.mid(position: pos + 1).toLong();
1290}
1291
1292static QString get_str(const QString &item_P)
1293{
1294 int pos = item_P.indexOf(c: QLatin1Char('='));
1295 return item_P.mid(position: pos + 1);
1296}
1297
1298static QByteArray get_cstr(const QString &item_P)
1299{
1300 return get_str(item_P).toUtf8();
1301}
1302
1303static QStringList get_fields(const QString &txt_P)
1304{
1305 QString txt = txt_P.simplified();
1306 QStringList ret;
1307 QString item;
1308 bool in = false;
1309 bool escape = false;
1310 for (int pos = 0; pos < txt.length(); ++pos) {
1311 if (escape) {
1312 item += txt[pos];
1313 escape = false;
1314 } else if (txt[pos] == QLatin1Char('\\')) {
1315 escape = true;
1316 } else if (txt[pos] == QLatin1Char('\"')) {
1317 in = !in;
1318 } else if (txt[pos] == QLatin1Char(' ') && !in) {
1319 ret.append(t: item);
1320 item = QString();
1321 } else {
1322 item += txt[pos];
1323 }
1324 }
1325 ret.append(t: item);
1326 return ret;
1327}
1328
1329static QString escape_str(const QString &str_P)
1330{
1331 QString ret;
1332 // prepare some space which should be always enough.
1333 // No need to squeze at the end, as the result is only used as intermediate string
1334 ret.reserve(asize: str_P.size() * 2);
1335 for (int pos = 0; pos < str_P.length(); ++pos) {
1336 if (str_P[pos] == QLatin1Char('\\') || str_P[pos] == QLatin1Char('"')) {
1337 ret += QLatin1Char('\\');
1338 }
1339 ret += str_P[pos];
1340 }
1341 return ret;
1342}
1343
1344#include "moc_kstartupinfo.cpp"
1345

source code of kwindowsystem/src/kstartupinfo.cpp