KWindowSystem

kstartupinfo.cpp
1 /*
2  SPDX-FileCopyrightText: 2001-2003 Lubos Lunak <[email protected]>
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 #include "netwm_def.h"
22 
23 #if KWINDOWSYSTEM_BUILD_DEPRECATED_SINCE(5, 62)
24 #include <QWidget>
25 #endif
26 #include <QDateTime>
27 
28 #include <config-kwindowsystem.h> // KWINDOWSYSTEM_HAVE_X11
29 
30 // need to resolve INT32(qglobal.h)<>INT32(Xlibint.h) conflict
31 #ifndef QT_CLEAN_NAMESPACE
32 #define QT_CLEAN_NAMESPACE
33 #endif
34 
35 #ifndef Q_OS_WIN
36 #include <sys/time.h>
37 #include <unistd.h>
38 #else
39 #include <process.h>
40 #include <winsock2.h>
41 #endif
42 #include <QTimer>
43 #include <stdlib.h>
44 #if KWINDOWSYSTEM_HAVE_X11
45 #include <netwm.h>
46 #include <qx11info_x11.h>
47 #endif
48 #include <QCoreApplication>
49 #include <QDebug>
50 #include <QStandardPaths>
51 #include <signal.h>
52 #if KWINDOWSYSTEM_HAVE_X11
53 #include <X11/Xlib.h>
54 #include <fixx11h.h>
55 #include <kwindowsystem.h>
56 #include <kxmessages.h>
57 #endif
58 
59 static const char NET_STARTUP_MSG[] = "_NET_STARTUP_INFO";
60 static const char NET_STARTUP_WINDOW[] = "_NET_STARTUP_ID";
61 // DESKTOP_STARTUP_ID is used also in kinit/wrapper.c ,
62 // kdesu in both kdelibs and kdebase and who knows where else
63 static const char NET_STARTUP_ENV[] = "DESKTOP_STARTUP_ID";
64 
65 static QByteArray s_startup_id;
66 
67 static long get_num(const QString &item_P);
68 static QString get_str(const QString &item_P);
69 static QByteArray get_cstr(const QString &item_P);
70 static QStringList get_fields(const QString &txt_P);
71 static QString escape_str(const QString &str_P);
72 
73 class Q_DECL_HIDDEN KStartupInfo::Data : public KStartupInfoData
74 {
75 public:
76  Data()
77  : age(0)
78  {
79  } // just because it's in a QMap
80  Data(const QString &txt_P)
81  : KStartupInfoData(txt_P)
82  , age(0)
83  {
84  }
85  unsigned int age;
86 };
87 
88 struct Q_DECL_HIDDEN KStartupInfoId::Private {
89  Private()
90  : id("")
91  {
92  }
93 
94  QString to_text() const;
95 
96  QByteArray id; // id
97 };
98 
99 struct Q_DECL_HIDDEN KStartupInfoData::Private {
100  Private()
101  : desktop(0)
102  , wmclass("")
103  , hostname("")
104  , silent(KStartupInfoData::Unknown)
105  , screen(-1)
106  , xinerama(-1)
107  {
108  }
109 
110  QString to_text() const;
111  void remove_pid(pid_t pid);
112 
113  QString bin;
114  QString name;
115  QString description;
116  QString icon;
117  int desktop;
118  QList<pid_t> pids;
119  QByteArray wmclass;
121  KStartupInfoData::TriState silent;
122  int screen;
123  int xinerama;
124 #if KWINDOWSYSTEM_BUILD_DEPRECATED_SINCE(5, 69)
125  WId launched_by = 0;
126 #endif
127  QString application_id;
128 };
129 
130 class Q_DECL_HIDDEN KStartupInfo::Private
131 {
132 public:
133  // private slots
134  void startups_cleanup();
135  void startups_cleanup_no_age();
136  void got_message(const QString &msg);
137  void window_added(WId w);
138  void slot_window_added(WId w);
139 
140  void init(int flags);
141  void got_startup_info(const QString &msg_P, bool update_only_P);
142  void got_remove_startup_info(const QString &msg_P);
143  void new_startup_info_internal(const KStartupInfoId &id_P, Data &data_P, bool update_only_P);
144  void removeAllStartupInfoInternal(const KStartupInfoId &id_P);
145  /**
146  * Emits the gotRemoveStartup signal and erases the @p it from the startups map.
147  * @returns Iterator to next item in the startups map.
148  **/
150  void remove_startup_pids(const KStartupInfoId &id, const KStartupInfoData &data);
151  void remove_startup_pids(const KStartupInfoData &data);
152  startup_t check_startup_internal(WId w, KStartupInfoId *id, KStartupInfoData *data);
153  bool find_id(const QByteArray &id_P, KStartupInfoId *id_O, KStartupInfoData *data_O);
154  bool find_pid(pid_t pid_P, const QByteArray &hostname, KStartupInfoId *id_O, KStartupInfoData *data_O);
155  bool find_wclass(const QByteArray &res_name_P, const QByteArray &res_class_P, KStartupInfoId *id_O, KStartupInfoData *data_O);
156  void startups_cleanup_internal(bool age_P);
157  void clean_all_noncompliant();
158  static QString check_required_startup_fields(const QString &msg, const KStartupInfoData &data, int screen);
159 
160  KStartupInfo *q;
161  unsigned int timeout;
163  // contains silenced ASN's only if !AnnounceSilencedChanges
165  // contains ASN's that had change: but no new: yet
167 #if KWINDOWSYSTEM_HAVE_X11
168  KXMessages msgs;
169 #endif
170  QTimer *cleanup;
171  int flags;
172 
173  Private(int flags_P, KStartupInfo *qq)
174  : q(qq)
175  , timeout(60)
176 #if KWINDOWSYSTEM_HAVE_X11
177  , msgs(NET_STARTUP_MSG)
178 #endif
179  , cleanup(nullptr)
180  , flags(flags_P)
181  {
182  }
183 
184  void createConnections()
185  {
186 #if KWINDOWSYSTEM_HAVE_X11
187  // d == nullptr means "disabled"
188  if (!QX11Info::isPlatformX11() || !QX11Info::display()) {
189  return;
190  }
191 
192  if (!(flags & DisableKWinModule)) {
193  QObject::connect(KWindowSystem::self(), SIGNAL(windowAdded(WId)), q, SLOT(slot_window_added(WId)));
194 #ifdef __GNUC__
195 #warning "systemTrayWindowAdded signal was remove from KWindowSystem class"
196 #endif
197  // QObject::connect( KWindowSystem::self(), SIGNAL(systemTrayWindowAdded(WId)), q, SLOT(slot_window_added(WId)));
198  }
199  QObject::connect(&msgs, SIGNAL(gotMessage(QString)), q, SLOT(got_message(QString)));
200  cleanup = new QTimer(q);
201  QObject::connect(cleanup, SIGNAL(timeout()), q, SLOT(startups_cleanup()));
202 #endif
203  }
204 };
205 
206 KStartupInfo::KStartupInfo(int flags_P, QObject *parent_P)
207  : QObject(parent_P)
208  , d(new Private(flags_P, this))
209 {
210  d->createConnections();
211 }
212 
213 #if KWINDOWSYSTEM_BUILD_DEPRECATED_SINCE(5, 0)
214 KStartupInfo::KStartupInfo(bool clean_on_cantdetect_P, QObject *parent_P)
215  : QObject(parent_P)
216  , d(new Private(clean_on_cantdetect_P ? CleanOnCantDetect : 0, this))
217 {
218  d->createConnections();
219 }
220 #endif
221 
222 KStartupInfo::~KStartupInfo()
223 {
224  delete d;
225 }
226 
227 void KStartupInfo::Private::got_message(const QString &msg_P)
228 {
229 #if KWINDOWSYSTEM_HAVE_X11
230  // TODO do something with SCREEN= ?
231  // qCDebug(LOG_KWINDOWSYSTEM) << "got:" << msg_P;
232  QString msg = msg_P.trimmed();
233  if (msg.startsWith(QLatin1String("new:"))) { // must match length below
234  got_startup_info(msg.mid(4), false);
235  } else if (msg.startsWith(QLatin1String("change:"))) { // must match length below
236  got_startup_info(msg.mid(7), true);
237  } else if (msg.startsWith(QLatin1String("remove:"))) { // must match length below
238  got_remove_startup_info(msg.mid(7));
239  }
240 #else
241  Q_UNUSED(msg_P)
242 #endif
243 }
244 
245 // if the application stops responding for a while, KWindowSystem may get
246 // the information about the already mapped window before KXMessages
247 // actually gets the info about the started application (depends
248 // on their order in the native x11 event filter)
249 // simply delay info from KWindowSystem a bit
250 // SELI???
251 namespace
252 {
253 class DelayedWindowEvent : public QEvent
254 {
255 public:
256  DelayedWindowEvent(WId w_P)
257  : QEvent(uniqueType())
258  , w(w_P)
259  {
260  }
261 #if KWINDOWSYSTEM_HAVE_X11
262  Window w;
263 #else
264  WId w;
265 #endif
266  static Type uniqueType()
267  {
268  return Type(QEvent::User + 15);
269  }
270 };
271 }
272 
273 void KStartupInfo::Private::slot_window_added(WId w_P)
274 {
275  qApp->postEvent(q, new DelayedWindowEvent(w_P));
276 }
277 
278 void KStartupInfo::customEvent(QEvent *e_P)
279 {
280 #if KWINDOWSYSTEM_HAVE_X11
281  if (e_P->type() == DelayedWindowEvent::uniqueType()) {
282  d->window_added(static_cast<DelayedWindowEvent *>(e_P)->w);
283  } else
284 #endif
286 }
287 
288 void KStartupInfo::Private::window_added(WId w_P)
289 {
290  KStartupInfoId id;
291  KStartupInfoData data;
292  startup_t ret = check_startup_internal(w_P, &id, &data);
293  switch (ret) {
294  case Match:
295  // qCDebug(LOG_KWINDOWSYSTEM) << "new window match";
296  break;
297  case NoMatch:
298  break; // nothing
299  case CantDetect:
300  if (flags & CleanOnCantDetect) {
301  clean_all_noncompliant();
302  }
303  break;
304  }
305 }
306 
307 void KStartupInfo::Private::got_startup_info(const QString &msg_P, bool update_P)
308 {
309  KStartupInfoId id(msg_P);
310  if (id.isNull()) {
311  return;
312  }
313  KStartupInfo::Data data(msg_P);
314  new_startup_info_internal(id, data, update_P);
315 }
316 
317 void KStartupInfo::Private::new_startup_info_internal(const KStartupInfoId &id_P, KStartupInfo::Data &data_P, bool update_P)
318 {
319  if (id_P.isNull()) {
320  return;
321  }
322  if (startups.contains(id_P)) {
323  // already reported, update
324  startups[id_P].update(data_P);
325  startups[id_P].age = 0; // CHECKME
326  // qCDebug(LOG_KWINDOWSYSTEM) << "updating";
327  if (startups[id_P].silent() == KStartupInfo::Data::Yes && !(flags & AnnounceSilenceChanges)) {
328  silent_startups[id_P] = startups[id_P];
329  startups.remove(id_P);
330  Q_EMIT q->gotRemoveStartup(id_P, silent_startups[id_P]);
331  return;
332  }
333  Q_EMIT q->gotStartupChange(id_P, startups[id_P]);
334  return;
335  }
336  if (silent_startups.contains(id_P)) {
337  // already reported, update
338  silent_startups[id_P].update(data_P);
339  silent_startups[id_P].age = 0; // CHECKME
340  // qCDebug(LOG_KWINDOWSYSTEM) << "updating silenced";
341  if (silent_startups[id_P].silent() != Data::Yes) {
342  startups[id_P] = silent_startups[id_P];
343  silent_startups.remove(id_P);
344  q->Q_EMIT gotNewStartup(id_P, startups[id_P]);
345  return;
346  }
347  Q_EMIT q->gotStartupChange(id_P, silent_startups[id_P]);
348  return;
349  }
350  if (uninited_startups.contains(id_P)) {
351  uninited_startups[id_P].update(data_P);
352  // qCDebug(LOG_KWINDOWSYSTEM) << "updating uninited";
353  if (!update_P) { // uninited finally got new:
354  startups[id_P] = uninited_startups[id_P];
355  uninited_startups.remove(id_P);
356  Q_EMIT q->gotNewStartup(id_P, startups[id_P]);
357  return;
358  }
359  // no change announce, it's still uninited
360  return;
361  }
362  if (update_P) { // change: without any new: first
363  // qCDebug(LOG_KWINDOWSYSTEM) << "adding uninited";
364  uninited_startups.insert(id_P, data_P);
365  } else if (data_P.silent() != Data::Yes || flags & AnnounceSilenceChanges) {
366  // qCDebug(LOG_KWINDOWSYSTEM) << "adding";
367  startups.insert(id_P, data_P);
368  Q_EMIT q->gotNewStartup(id_P, data_P);
369  } else { // new silenced, and silent shouldn't be announced
370  // qCDebug(LOG_KWINDOWSYSTEM) << "adding silent";
371  silent_startups.insert(id_P, data_P);
372  }
373  cleanup->start(1000); // 1 sec
374 }
375 
376 void KStartupInfo::Private::got_remove_startup_info(const QString &msg_P)
377 {
378  KStartupInfoId id(msg_P);
379  KStartupInfoData data(msg_P);
380  if (!data.pids().isEmpty()) {
381  if (!id.isNull()) {
382  remove_startup_pids(id, data);
383  } else {
384  remove_startup_pids(data);
385  }
386  return;
387  }
388  removeAllStartupInfoInternal(id);
389 }
390 
391 void KStartupInfo::Private::removeAllStartupInfoInternal(const KStartupInfoId &id_P)
392 {
393  auto it = startups.find(id_P);
394  if (it != startups.end()) {
395  // qCDebug(LOG_KWINDOWSYSTEM) << "removing";
396  Q_EMIT q->gotRemoveStartup(it.key(), it.value());
397  startups.erase(it);
398  return;
399  }
400  it = silent_startups.find(id_P);
401  if (it != silent_startups.end()) {
402  silent_startups.erase(it);
403  return;
404  }
405  it = uninited_startups.find(id_P);
406  if (it != uninited_startups.end()) {
407  uninited_startups.erase(it);
408  }
409 }
410 
411 QMap<KStartupInfoId, KStartupInfo::Data>::iterator KStartupInfo::Private::removeStartupInfoInternal(QMap<KStartupInfoId, Data>::iterator it)
412 {
413  Q_EMIT q->gotRemoveStartup(it.key(), it.value());
414  return startups.erase(it);
415 }
416 
417 void KStartupInfo::Private::remove_startup_pids(const KStartupInfoData &data_P)
418 {
419  // first find the matching info
420  for (QMap<KStartupInfoId, KStartupInfo::Data>::Iterator it = startups.begin(); it != startups.end(); ++it) {
421  if ((*it).hostname() != data_P.hostname()) {
422  continue;
423  }
424  if (!(*it).is_pid(data_P.pids().first())) {
425  continue; // not the matching info
426  }
427  remove_startup_pids(it.key(), data_P);
428  break;
429  }
430 }
431 
432 void KStartupInfo::Private::remove_startup_pids(const KStartupInfoId &id_P, const KStartupInfoData &data_P)
433 {
434  if (data_P.pids().isEmpty()) {
435  qFatal("data_P.pids().isEmpty()");
436  }
437  Data *data = nullptr;
438  if (startups.contains(id_P)) {
439  data = &startups[id_P];
440  } else if (silent_startups.contains(id_P)) {
441  data = &silent_startups[id_P];
442  } else if (uninited_startups.contains(id_P)) {
443  data = &uninited_startups[id_P];
444  } else {
445  return;
446  }
447  const auto pids = data_P.pids();
448  for (auto pid : pids) {
449  data->d->remove_pid(pid); // remove all pids from the info
450  }
451  if (data->pids().isEmpty()) { // all pids removed -> remove info
452  removeAllStartupInfoInternal(id_P);
453  }
454 }
455 
457 {
458  if (id_P.isNull()) {
459  return false;
460  }
461 #if KWINDOWSYSTEM_HAVE_X11
462  return sendStartupXcb(QX11Info::connection(), QX11Info::appScreen(), id_P, data_P);
463 #else
464  Q_UNUSED(data_P)
465 #endif
466  return true;
467 }
468 
469 #if KWINDOWSYSTEM_BUILD_DEPRECATED_SINCE(5, 18)
470 bool KStartupInfo::sendStartupX(Display *disp_P, const KStartupInfoId &id_P, const KStartupInfoData &data_P)
471 {
472  if (id_P.isNull()) {
473  return false;
474  }
475 #if KWINDOWSYSTEM_HAVE_X11
476  QString msg = QStringLiteral("new: %1 %2").arg(id_P.d->to_text(), data_P.d->to_text());
477  msg = Private::check_required_startup_fields(msg, data_P, DefaultScreen(disp_P));
478 #ifdef KSTARTUPINFO_ALL_DEBUG
479  qCDebug(LOG_KWINDOWSYSTEM) << "sending " << msg;
480 #endif
481  return KXMessages::broadcastMessageX(disp_P, NET_STARTUP_MSG, msg);
482 #else
483  Q_UNUSED(disp_P)
484  Q_UNUSED(data_P)
485  return true;
486 #endif
487 }
488 #endif
489 
490 bool KStartupInfo::sendStartupXcb(xcb_connection_t *conn, int screen, const KStartupInfoId &id_P, const KStartupInfoData &data_P)
491 {
492  if (id_P.isNull()) {
493  return false;
494  }
495 #if KWINDOWSYSTEM_HAVE_X11
496  QString msg = QStringLiteral("new: %1 %2").arg(id_P.d->to_text(), data_P.d->to_text());
497  msg = Private::check_required_startup_fields(msg, data_P, screen);
498 #ifdef KSTARTUPINFO_ALL_DEBUG
499  qCDebug(LOG_KWINDOWSYSTEM) << "sending " << msg;
500 #endif
501  return KXMessages::broadcastMessageX(conn, NET_STARTUP_MSG, msg, screen);
502 #else
503  Q_UNUSED(conn)
504  Q_UNUSED(screen)
505  Q_UNUSED(data_P)
506  return true;
507 #endif
508 }
509 
510 QString KStartupInfo::Private::check_required_startup_fields(const QString &msg, const KStartupInfoData &data_P, int screen)
511 {
512  QString ret = msg;
513  if (data_P.name().isEmpty()) {
514  // qWarning() << "NAME not specified in initial startup message";
515  QString name = data_P.bin();
516  if (name.isEmpty()) {
517  name = QStringLiteral("UNKNOWN");
518  }
519  ret += QStringLiteral(" NAME=\"%1\"").arg(escape_str(name));
520  }
521  if (data_P.screen() == -1) { // add automatically if needed
522  ret += QStringLiteral(" SCREEN=%1").arg(screen);
523  }
524  return ret;
525 }
526 
528 {
529  if (id_P.isNull()) {
530  return false;
531  }
532 #if KWINDOWSYSTEM_HAVE_X11
533  return sendChangeXcb(QX11Info::connection(), QX11Info::appScreen(), id_P, data_P);
534 #else
535  Q_UNUSED(data_P)
536 #endif
537  return true;
538 }
539 
540 #if KWINDOWSYSTEM_BUILD_DEPRECATED_SINCE(5, 18)
541 bool KStartupInfo::sendChangeX(Display *disp_P, const KStartupInfoId &id_P, const KStartupInfoData &data_P)
542 {
543  if (id_P.isNull()) {
544  return false;
545  }
546 #if KWINDOWSYSTEM_HAVE_X11
547  QString msg = QStringLiteral("change: %1 %2").arg(id_P.d->to_text(), data_P.d->to_text());
548 #ifdef KSTARTUPINFO_ALL_DEBUG
549  qCDebug(LOG_KWINDOWSYSTEM) << "sending " << msg;
550 #endif
551  return KXMessages::broadcastMessageX(disp_P, NET_STARTUP_MSG, msg);
552 #else
553  Q_UNUSED(disp_P)
554  Q_UNUSED(data_P)
555  return true;
556 #endif
557 }
558 #endif
559 
560 bool KStartupInfo::sendChangeXcb(xcb_connection_t *conn, int screen, const KStartupInfoId &id_P, const KStartupInfoData &data_P)
561 {
562  if (id_P.isNull()) {
563  return false;
564  }
565 #if KWINDOWSYSTEM_HAVE_X11
566  QString msg = QStringLiteral("change: %1 %2").arg(id_P.d->to_text(), data_P.d->to_text());
567 #ifdef KSTARTUPINFO_ALL_DEBUG
568  qCDebug(LOG_KWINDOWSYSTEM) << "sending " << msg;
569 #endif
570  return KXMessages::broadcastMessageX(conn, NET_STARTUP_MSG, msg, screen);
571 #else
572  Q_UNUSED(conn)
573  Q_UNUSED(screen)
574  Q_UNUSED(data_P)
575  return true;
576 #endif
577 }
578 
580 {
581  if (id_P.isNull()) {
582  return false;
583  }
584 #if KWINDOWSYSTEM_HAVE_X11
585  return sendFinishXcb(QX11Info::connection(), QX11Info::appScreen(), id_P);
586 #endif
587  return true;
588 }
589 
590 #if KWINDOWSYSTEM_BUILD_DEPRECATED_SINCE(5, 18)
591 bool KStartupInfo::sendFinishX(Display *disp_P, const KStartupInfoId &id_P)
592 {
593  if (id_P.isNull()) {
594  return false;
595  }
596 #if KWINDOWSYSTEM_HAVE_X11
597  QString msg = QStringLiteral("remove: %1").arg(id_P.d->to_text());
598 #ifdef KSTARTUPINFO_ALL_DEBUG
599  qCDebug(LOG_KWINDOWSYSTEM) << "sending " << msg;
600 #endif
601  return KXMessages::broadcastMessageX(disp_P, NET_STARTUP_MSG, msg);
602 #else
603  Q_UNUSED(disp_P)
604  return true;
605 #endif
606 }
607 #endif
608 
609 bool KStartupInfo::sendFinishXcb(xcb_connection_t *conn, int screen, const KStartupInfoId &id_P)
610 {
611  if (id_P.isNull()) {
612  return false;
613  }
614 #if KWINDOWSYSTEM_HAVE_X11
615  QString msg = QStringLiteral("remove: %1").arg(id_P.d->to_text());
616 #ifdef KSTARTUPINFO_ALL_DEBUG
617  qCDebug(LOG_KWINDOWSYSTEM) << "sending " << msg;
618 #endif
619  return KXMessages::broadcastMessageX(conn, NET_STARTUP_MSG, msg, screen);
620 #else
621  Q_UNUSED(conn)
622  Q_UNUSED(screen)
623  return true;
624 #endif
625 }
626 
628 {
629 // if( id_P.isNull()) // id may be null, the pids and hostname matter then
630 // return false;
631 #if KWINDOWSYSTEM_HAVE_X11
632  return sendFinishXcb(QX11Info::connection(), QX11Info::appScreen(), id_P, data_P);
633 #else
634  Q_UNUSED(id_P)
635  Q_UNUSED(data_P)
636 #endif
637  return true;
638 }
639 
640 #if KWINDOWSYSTEM_BUILD_DEPRECATED_SINCE(5, 18)
641 bool KStartupInfo::sendFinishX(Display *disp_P, const KStartupInfoId &id_P, const KStartupInfoData &data_P)
642 {
643 // if( id_P.isNull()) // id may be null, the pids and hostname matter then
644 // return false;
645 #if KWINDOWSYSTEM_HAVE_X11
646  QString msg = QStringLiteral("remove: %1 %2").arg(id_P.d->to_text(), data_P.d->to_text());
647 #ifdef KSTARTUPINFO_ALL_DEBUG
648  qCDebug(LOG_KWINDOWSYSTEM) << "sending " << msg;
649 #endif
650  return KXMessages::broadcastMessageX(disp_P, NET_STARTUP_MSG, msg);
651 #else
652  Q_UNUSED(disp_P)
653  Q_UNUSED(id_P)
654  Q_UNUSED(data_P)
655  return true;
656 #endif
657 }
658 #endif
659 
660 bool KStartupInfo::sendFinishXcb(xcb_connection_t *conn, int screen, const KStartupInfoId &id_P, const KStartupInfoData &data_P)
661 {
662 // if( id_P.isNull()) // id may be null, the pids and hostname matter then
663 // return false;
664 #if KWINDOWSYSTEM_HAVE_X11
665  QString msg = QStringLiteral("remove: %1 %2").arg(id_P.d->to_text(), data_P.d->to_text());
666 #ifdef KSTARTUPINFO_ALL_DEBUG
667  qCDebug(LOG_KWINDOWSYSTEM) << "sending " << msg;
668 #endif
669  return KXMessages::broadcastMessageX(conn, NET_STARTUP_MSG, msg, screen);
670 #else
671  Q_UNUSED(conn)
672  Q_UNUSED(screen)
673  Q_UNUSED(id_P)
674  Q_UNUSED(data_P)
675  return true;
676 #endif
677 }
678 
680 {
682  setStartupId("0"); // reset the id, no longer valid (must use clearStartupId() to avoid infinite loop)
683 }
684 
685 void KStartupInfo::appStarted(const QByteArray &startup_id)
686 {
687  KStartupInfoId id;
688  id.initId(startup_id);
689  if (id.isNull()) {
690  return;
691  }
692 #if KWINDOWSYSTEM_HAVE_X11
693  if (QX11Info::isPlatformX11() && !qEnvironmentVariableIsEmpty("DISPLAY")) { // don't rely on QX11Info::display()
695  }
696 #endif
697 }
698 
700 {
701  KStartupInfoId id;
702  id.initId(startupId());
703  if (id.isNull()) {
704  return;
705  }
706  KStartupInfoData data;
707  data.setSilent(silence ? KStartupInfoData::Yes : KStartupInfoData::No);
708  sendChange(id, data);
709 }
710 
712 {
713  if (s_startup_id.isEmpty()) {
715  resetStartupEnv();
716  s_startup_id = id.id();
717  }
718 
719  return s_startup_id;
720 }
721 
722 void KStartupInfo::setStartupId(const QByteArray &startup_id)
723 {
724  if (startup_id == startupId()) {
725  return;
726  }
727  if (startup_id.isEmpty()) {
728  s_startup_id = "0";
729  } else {
730  s_startup_id = startup_id;
731 #if KWINDOWSYSTEM_HAVE_X11
732  if (QX11Info::isPlatformX11()) {
733  KStartupInfoId id;
734  id.initId(startup_id);
735  long timestamp = id.timestamp();
736  if (timestamp != 0) {
737  if (QX11Info::appUserTime() == 0 || NET::timestampCompare(timestamp, QX11Info::appUserTime()) > 0) { // time > appUserTime
738  QX11Info::setAppUserTime(timestamp);
739  }
740  if (QX11Info::appTime() == 0 || NET::timestampCompare(timestamp, QX11Info::appTime()) > 0) { // time > appTime
741  QX11Info::setAppTime(timestamp);
742  }
743  }
744  }
745 #endif
746  }
747 }
748 
749 #if KWINDOWSYSTEM_BUILD_DEPRECATED_SINCE(5, 62)
750 void KStartupInfo::setNewStartupId(QWidget *window, const QByteArray &startup_id)
751 {
752  // Set the WA_NativeWindow attribute to force the creation of the QWindow.
753  // Without this QWidget::windowHandle() returns 0.
754  window->setAttribute(Qt::WA_NativeWindow, true);
755  setNewStartupId(window->window()->windowHandle(), startup_id);
756 }
757 #endif
758 
759 void KStartupInfo::setNewStartupId(QWindow *window, const QByteArray &startup_id)
760 {
761  Q_ASSERT(window);
762  setStartupId(startup_id);
763 #if KWINDOWSYSTEM_HAVE_X11
764  bool activate = true;
765  if (window != nullptr && QX11Info::isPlatformX11()) {
766  if (!startup_id.isEmpty() && startup_id != "0") {
767  NETRootInfo i(QX11Info::connection(), NET::Supported);
768  if (i.isSupported(NET::WM2StartupId)) {
769  KStartupInfo::setWindowStartupId(window->winId(), startup_id);
770  activate = false; // WM will take care of it
771  }
772  }
773  if (activate) {
775  // This is not very nice, but there's no way how to get any
776  // usable timestamp without ASN, so force activating the window.
777  // And even with ASN, it's not possible to get the timestamp here,
778  // so if the WM doesn't have support for ASN, it can't be used either.
780  }
781  }
782 #else
783  Q_UNUSED(window)
784 #endif
785 }
786 
788 {
789  return d->check_startup_internal(w_P, &id_O, &data_O);
790 }
791 
793 {
794  return d->check_startup_internal(w_P, &id_O, nullptr);
795 }
796 
798 {
799  return d->check_startup_internal(w_P, nullptr, &data_O);
800 }
801 
803 {
804  return d->check_startup_internal(w_P, nullptr, nullptr);
805 }
806 
807 KStartupInfo::startup_t KStartupInfo::Private::check_startup_internal(WId w_P, KStartupInfoId *id_O, KStartupInfoData *data_O)
808 {
809  if (startups.isEmpty()) {
810  return NoMatch; // no startups
811  }
812  // Strategy:
813  //
814  // Is this a compliant app ?
815  // - Yes - test for match
816  // - No - Is this a NET_WM compliant app ?
817  // - Yes - test for pid match
818  // - No - test for WM_CLASS match
819  qCDebug(LOG_KWINDOWSYSTEM) << "check_startup";
820  QByteArray id = windowStartupId(w_P);
821  if (!id.isNull()) {
822  if (id.isEmpty() || id == "0") { // means ignore this window
823  qCDebug(LOG_KWINDOWSYSTEM) << "ignore";
824  return NoMatch;
825  }
826  return find_id(id, id_O, data_O) ? Match : NoMatch;
827  }
828 #if KWINDOWSYSTEM_HAVE_X11
829  if (!QX11Info::isPlatformX11()) {
830  qCDebug(LOG_KWINDOWSYSTEM) << "check_startup:cantdetect";
831  return CantDetect;
832  }
833  NETWinInfo info(QX11Info::connection(),
834  w_P,
835  QX11Info::appRootWindow(),
836  NET::WMWindowType | NET::WMPid | NET::WMState,
837  NET::WM2WindowClass | NET::WM2ClientMachine | NET::WM2TransientFor);
838  pid_t pid = info.pid();
839  if (pid > 0) {
840  QByteArray hostname = info.clientMachine();
841  if (!hostname.isEmpty() && find_pid(pid, hostname, id_O, data_O)) {
842  return Match;
843  }
844  // try XClass matching , this PID stuff sucks :(
845  }
846  if (find_wclass(info.windowClassName(), info.windowClassClass(), id_O, data_O)) {
847  return Match;
848  }
849  // ignore NET::Tool and other special window types, if they can't be matched
852  if (type != NET::Normal && type != NET::Override && type != NET::Unknown && type != NET::Dialog && type != NET::Utility)
853  // && type != NET::Dock ) why did I put this here?
854  {
855  return NoMatch;
856  }
857  // lets see if this is a transient
858  xcb_window_t transient_for = info.transientFor();
859  if (transient_for != QX11Info::appRootWindow() && transient_for != XCB_WINDOW_NONE) {
860  return NoMatch;
861  }
862 #endif
863  qCDebug(LOG_KWINDOWSYSTEM) << "check_startup:cantdetect";
864  return CantDetect;
865 }
866 
867 bool KStartupInfo::Private::find_id(const QByteArray &id_P, KStartupInfoId *id_O, KStartupInfoData *data_O)
868 {
869  // qCDebug(LOG_KWINDOWSYSTEM) << "find_id:" << id_P;
870  KStartupInfoId id;
871  id.initId(id_P);
872  if (startups.contains(id)) {
873  if (id_O != nullptr) {
874  *id_O = id;
875  }
876  if (data_O != nullptr) {
877  *data_O = startups[id];
878  }
879  // qCDebug(LOG_KWINDOWSYSTEM) << "check_startup_id:match";
880  return true;
881  }
882  return false;
883 }
884 
885 bool KStartupInfo::Private::find_pid(pid_t pid_P, const QByteArray &hostname_P, KStartupInfoId *id_O, KStartupInfoData *data_O)
886 {
887  // qCDebug(LOG_KWINDOWSYSTEM) << "find_pid:" << pid_P;
888  for (QMap<KStartupInfoId, KStartupInfo::Data>::Iterator it = startups.begin(); it != startups.end(); ++it) {
889  if ((*it).is_pid(pid_P) && (*it).hostname() == hostname_P) {
890  // Found it !
891  if (id_O != nullptr) {
892  *id_O = it.key();
893  }
894  if (data_O != nullptr) {
895  *data_O = *it;
896  }
897  // non-compliant, remove on first match
898  removeStartupInfoInternal(it);
899  // qCDebug(LOG_KWINDOWSYSTEM) << "check_startup_pid:match";
900  return true;
901  }
902  }
903  return false;
904 }
905 
906 bool KStartupInfo::Private::find_wclass(const QByteArray &_res_name, const QByteArray &_res_class, KStartupInfoId *id_O, KStartupInfoData *data_O)
907 {
908  QByteArray res_name = _res_name.toLower();
909  QByteArray res_class = _res_class.toLower();
910  // qCDebug(LOG_KWINDOWSYSTEM) << "find_wclass:" << res_name << ":" << res_class;
911  for (QMap<KStartupInfoId, Data>::Iterator it = startups.begin(); it != startups.end(); ++it) {
912  const QByteArray wmclass = (*it).findWMClass();
913  if (wmclass.toLower() == res_name || wmclass.toLower() == res_class) {
914  // Found it !
915  if (id_O != nullptr) {
916  *id_O = it.key();
917  }
918  if (data_O != nullptr) {
919  *data_O = *it;
920  }
921  // non-compliant, remove on first match
922  removeStartupInfoInternal(it);
923  // qCDebug(LOG_KWINDOWSYSTEM) << "check_startup_wclass:match";
924  return true;
925  }
926  }
927  return false;
928 }
929 
931 {
932 #if KWINDOWSYSTEM_HAVE_X11
933  if (!QX11Info::isPlatformX11()) {
934  return QByteArray();
935  }
936  NETWinInfo info(QX11Info::connection(), w_P, QX11Info::appRootWindow(), NET::Properties(), NET::WM2StartupId | NET::WM2GroupLeader);
937  QByteArray ret = info.startupId();
938  if (ret.isEmpty() && info.groupLeader() != XCB_WINDOW_NONE) {
939  // retry with window group leader, as the spec says
940  NETWinInfo groupLeaderInfo(QX11Info::connection(), info.groupLeader(), QX11Info::appRootWindow(), NET::Properties(), NET::Properties2());
941  ret = groupLeaderInfo.startupId();
942  }
943  return ret;
944 #else
945  Q_UNUSED(w_P)
946  return QByteArray();
947 #endif
948 }
949 
951 {
952 #if KWINDOWSYSTEM_HAVE_X11
953  if (!QX11Info::isPlatformX11()) {
954  return;
955  }
956  if (id_P.isNull()) {
957  return;
958  }
959  NETWinInfo info(QX11Info::connection(), w_P, QX11Info::appRootWindow(), NET::Properties(), NET::Properties2());
960  info.setStartupId(id_P.constData());
961 #else
962  Q_UNUSED(w_P)
963  Q_UNUSED(id_P)
964 #endif
965 }
966 
967 void KStartupInfo::setTimeout(unsigned int secs_P)
968 {
969  d->timeout = secs_P;
970  // schedule removing entries that are older than the new timeout
971  QTimer::singleShot(0, this, SLOT(startups_cleanup_no_age()));
972 }
973 
974 void KStartupInfo::Private::startups_cleanup_no_age()
975 {
976  startups_cleanup_internal(false);
977 }
978 
979 void KStartupInfo::Private::startups_cleanup()
980 {
981  if (startups.isEmpty() && silent_startups.isEmpty() && uninited_startups.isEmpty()) {
982  cleanup->stop();
983  return;
984  }
985  startups_cleanup_internal(true);
986 }
987 
988 void KStartupInfo::Private::startups_cleanup_internal(bool age_P)
989 {
990  auto checkCleanup = [this, age_P](QMap<KStartupInfoId, KStartupInfo::Data> &s, bool doEmit) {
991  auto it = s.begin();
992  while (it != s.end()) {
993  if (age_P) {
994  (*it).age++;
995  }
996  unsigned int tout = timeout;
997  if ((*it).silent() == KStartupInfo::Data::Yes) {
998  // give kdesu time to get a password
999  tout *= 20;
1000  }
1001  const QByteArray timeoutEnvVariable = qgetenv("KSTARTUPINFO_TIMEOUT");
1002  if (!timeoutEnvVariable.isNull()) {
1003  tout = timeoutEnvVariable.toUInt();
1004  }
1005  if ((*it).age >= tout) {
1006  if (doEmit) {
1007  Q_EMIT q->gotRemoveStartup(it.key(), it.value());
1008  }
1009  it = s.erase(it);
1010  } else {
1011  ++it;
1012  }
1013  }
1014  };
1015  checkCleanup(startups, true);
1016  checkCleanup(silent_startups, false);
1017  checkCleanup(uninited_startups, false);
1018 }
1019 
1020 void KStartupInfo::Private::clean_all_noncompliant()
1021 {
1022  for (QMap<KStartupInfoId, KStartupInfo::Data>::Iterator it = startups.begin(); it != startups.end();) {
1023  if ((*it).WMClass() != "0") {
1024  ++it;
1025  continue;
1026  }
1027  it = removeStartupInfoInternal(it);
1028  }
1029 }
1030 
1032 {
1033  quint32 timestamp = 0;
1034 #if KWINDOWSYSTEM_HAVE_X11
1035  if (QX11Info::isPlatformX11()) {
1036  timestamp = QX11Info::getTimestamp();
1037  }
1038 #endif
1040 }
1041 
1043 {
1044  // Assign a unique id, use hostname+time+pid, that should be 200% unique.
1045  // Also append the user timestamp (for focus stealing prevention).
1046  struct timeval tm;
1047 #ifdef Q_OS_WIN
1048  // on windows only msecs accuracy instead of usecs like with gettimeofday
1049  // XXX: use Win API to get better accuracy
1050  qint64 msecsSinceEpoch = QDateTime::currentMSecsSinceEpoch();
1051  tm.tv_sec = msecsSinceEpoch / 1000;
1052  tm.tv_usec = (msecsSinceEpoch % 1000) * 1000;
1053 #else
1054  gettimeofday(&tm, nullptr);
1055 #endif
1056  char hostname[256];
1057  hostname[0] = '\0';
1058  if (!gethostname(hostname, 255)) {
1059  hostname[sizeof(hostname) - 1] = '\0';
1060  }
1061  QByteArray id = QStringLiteral("%1;%2;%3;%4_TIME%5").arg(hostname).arg(tm.tv_sec).arg(tm.tv_usec).arg(getpid()).arg(timestamp).toUtf8();
1062  // qCDebug(LOG_KWINDOWSYSTEM) << "creating: " << id << ":" << (qApp ? qAppName() : QString("unnamed app") /* e.g. kdeinit */);
1063  return id;
1064 }
1065 
1067 {
1068  return d->id;
1069 }
1070 
1071 QString KStartupInfoId::Private::to_text() const
1072 {
1073  return QStringLiteral(" ID=\"%1\" ").arg(escape_str(id));
1074 }
1075 
1077  : d(new Private)
1078 {
1079  const QStringList items = get_fields(txt_P);
1080  for (QStringList::ConstIterator it = items.begin(); it != items.end(); ++it) {
1081  if ((*it).startsWith(QLatin1String("ID="))) {
1082  d->id = get_cstr(*it);
1083  }
1084  }
1085 }
1086 
1088 {
1089  if (!id_P.isEmpty()) {
1090  d->id = id_P;
1091 #ifdef KSTARTUPINFO_ALL_DEBUG
1092  qCDebug(LOG_KWINDOWSYSTEM) << "using: " << d->id;
1093 #endif
1094  return;
1095  }
1096  const QByteArray startup_env = qgetenv(NET_STARTUP_ENV);
1097  if (!startup_env.isEmpty()) {
1098  // already has id
1099  d->id = startup_env;
1100 #ifdef KSTARTUPINFO_ALL_DEBUG
1101  qCDebug(LOG_KWINDOWSYSTEM) << "reusing: " << d->id;
1102 #endif
1103  return;
1104  }
1106 }
1107 
1109 {
1110  if (isNull()) {
1111  qunsetenv(NET_STARTUP_ENV);
1112  return false;
1113  }
1114  return !qputenv(NET_STARTUP_ENV, id()) == 0;
1115 }
1116 
1118 {
1119  const QByteArray startup_env = qgetenv(NET_STARTUP_ENV);
1120  KStartupInfoId id;
1121  if (!startup_env.isEmpty()) {
1122  id.d->id = startup_env;
1123  } else {
1124  id.d->id = "0";
1125  }
1126  return id;
1127 }
1128 
1130 {
1131  qunsetenv(NET_STARTUP_ENV);
1132 }
1133 
1135  : d(new Private)
1136 {
1137 }
1138 
1139 KStartupInfoId::~KStartupInfoId()
1140 {
1141  delete d;
1142 }
1143 
1145  : d(new Private(*id_P.d))
1146 {
1147 }
1148 
1149 KStartupInfoId &KStartupInfoId::operator=(const KStartupInfoId &id_P)
1150 {
1151  if (&id_P == this) {
1152  return *this;
1153  }
1154  *d = *id_P.d;
1155  return *this;
1156 }
1157 
1159 {
1160  return id() == id_P.id();
1161 }
1162 
1164 {
1165  return !(*this == id_P);
1166 }
1167 
1168 // needed for QMap
1169 bool KStartupInfoId::operator<(const KStartupInfoId &id_P) const
1170 {
1171  return id() < id_P.id();
1172 }
1173 
1175 {
1176  return d->id.isEmpty() || d->id == "0";
1177 }
1178 
1179 unsigned long KStartupInfoId::timestamp() const
1180 {
1181  if (isNull()) {
1182  return 0;
1183  }
1184  // As per the spec, the ID must contain the _TIME followed by the timestamp
1185  int pos = d->id.lastIndexOf("_TIME");
1186  if (pos >= 0) {
1187  bool ok;
1188  unsigned long time = QString(d->id.mid(pos + 5)).toULong(&ok);
1189  if (!ok && d->id[pos + 5] == '-') { // try if it's as a negative signed number perhaps
1190  time = QString(d->id.mid(pos + 5)).toLong(&ok);
1191  }
1192  if (ok) {
1193  return time;
1194  }
1195  }
1196  return 0;
1197 }
1198 
1199 QString KStartupInfoData::Private::to_text() const
1200 {
1201  QString ret;
1202  // prepare some space which should be always enough.
1203  // No need to squeze at the end, as the result is only used as intermediate string
1204  ret.reserve(256);
1205  if (!bin.isEmpty()) {
1206  ret += QStringLiteral(" BIN=\"%1\"").arg(escape_str(bin));
1207  }
1208  if (!name.isEmpty()) {
1209  ret += QStringLiteral(" NAME=\"%1\"").arg(escape_str(name));
1210  }
1211  if (!description.isEmpty()) {
1212  ret += QStringLiteral(" DESCRIPTION=\"%1\"").arg(escape_str(description));
1213  }
1214  if (!icon.isEmpty()) {
1215  ret += QStringLiteral(" ICON=\"%1\"").arg(icon);
1216  }
1217  if (desktop != 0) {
1218  ret += QStringLiteral(" DESKTOP=%1").arg(desktop == NET::OnAllDesktops ? NET::OnAllDesktops : desktop - 1); // spec counts from 0
1219  }
1220  if (!wmclass.isEmpty()) {
1221  ret += QStringLiteral(" WMCLASS=\"%1\"").arg(QString(wmclass));
1222  }
1223  if (!hostname.isEmpty()) {
1224  ret += QStringLiteral(" HOSTNAME=%1").arg(QString(hostname));
1225  }
1226  for (QList<pid_t>::ConstIterator it = pids.begin(); it != pids.end(); ++it) {
1227  ret += QStringLiteral(" PID=%1").arg(*it);
1228  }
1229  if (silent != KStartupInfoData::Unknown) {
1230  ret += QStringLiteral(" SILENT=%1").arg(silent == KStartupInfoData::Yes ? 1 : 0);
1231  }
1232  if (screen != -1) {
1233  ret += QStringLiteral(" SCREEN=%1").arg(screen);
1234  }
1235  if (xinerama != -1) {
1236  ret += QStringLiteral(" XINERAMA=%1").arg(xinerama);
1237  }
1238 #if KWINDOWSYSTEM_BUILD_DEPRECATED_SINCE(5, 69)
1239  if (launched_by != 0) {
1240  ret += QStringLiteral(" LAUNCHED_BY=%1").arg((qptrdiff)launched_by);
1241  }
1242 #endif
1243  if (!application_id.isEmpty()) {
1244  ret += QStringLiteral(" APPLICATION_ID=\"%1\"").arg(application_id);
1245  }
1246  return ret;
1247 }
1248 
1250  : d(new Private)
1251 {
1252  const QStringList items = get_fields(txt_P);
1253  for (QStringList::ConstIterator it = items.begin(); it != items.end(); ++it) {
1254  if ((*it).startsWith(QLatin1String("BIN="))) {
1255  d->bin = get_str(*it);
1256  } else if ((*it).startsWith(QLatin1String("NAME="))) {
1257  d->name = get_str(*it);
1258  } else if ((*it).startsWith(QLatin1String("DESCRIPTION="))) {
1259  d->description = get_str(*it);
1260  } else if ((*it).startsWith(QLatin1String("ICON="))) {
1261  d->icon = get_str(*it);
1262  } else if ((*it).startsWith(QLatin1String("DESKTOP="))) {
1263  d->desktop = get_num(*it);
1264  if (d->desktop != NET::OnAllDesktops) {
1265  ++d->desktop; // spec counts from 0
1266  }
1267  } else if ((*it).startsWith(QLatin1String("WMCLASS="))) {
1268  d->wmclass = get_cstr(*it);
1269  } else if ((*it).startsWith(QLatin1String("HOSTNAME="))) { // added to version 1 (2014)
1270  d->hostname = get_cstr(*it);
1271  } else if ((*it).startsWith(QLatin1String("PID="))) { // added to version 1 (2014)
1272  addPid(get_num(*it));
1273  } else if ((*it).startsWith(QLatin1String("SILENT="))) {
1274  d->silent = get_num(*it) != 0 ? Yes : No;
1275  } else if ((*it).startsWith(QLatin1String("SCREEN="))) {
1276  d->screen = get_num(*it);
1277  } else if ((*it).startsWith(QLatin1String("XINERAMA="))) {
1278  d->xinerama = get_num(*it);
1279 #if KWINDOWSYSTEM_BUILD_DEPRECATED_SINCE(5, 69)
1280  } else if ((*it).startsWith(QLatin1String("LAUNCHED_BY="))) {
1281  d->launched_by = (WId)get_num(*it);
1282 #endif
1283  } else if ((*it).startsWith(QLatin1String("APPLICATION_ID="))) {
1284  d->application_id = get_str(*it);
1285  }
1286  }
1287 }
1288 
1290  : d(new Private(*data.d))
1291 {
1292 }
1293 
1294 KStartupInfoData &KStartupInfoData::operator=(const KStartupInfoData &data)
1295 {
1296  if (&data == this) {
1297  return *this;
1298  }
1299  *d = *data.d;
1300  return *this;
1301 }
1302 
1304 {
1305  if (!data_P.bin().isEmpty()) {
1306  d->bin = data_P.bin();
1307  }
1308  if (!data_P.name().isEmpty() && name().isEmpty()) { // don't overwrite
1309  d->name = data_P.name();
1310  }
1311  if (!data_P.description().isEmpty() && description().isEmpty()) { // don't overwrite
1312  d->description = data_P.description();
1313  }
1314  if (!data_P.icon().isEmpty() && icon().isEmpty()) { // don't overwrite
1315  d->icon = data_P.icon();
1316  }
1317  if (data_P.desktop() != 0 && desktop() == 0) { // don't overwrite
1318  d->desktop = data_P.desktop();
1319  }
1320  if (!data_P.d->wmclass.isEmpty()) {
1321  d->wmclass = data_P.d->wmclass;
1322  }
1323  if (!data_P.d->hostname.isEmpty()) {
1324  d->hostname = data_P.d->hostname;
1325  }
1326  for (QList<pid_t>::ConstIterator it = data_P.d->pids.constBegin(); it != data_P.d->pids.constEnd(); ++it) {
1327  addPid(*it);
1328  }
1329  if (data_P.silent() != Unknown) {
1330  d->silent = data_P.silent();
1331  }
1332  if (data_P.screen() != -1) {
1333  d->screen = data_P.screen();
1334  }
1335  if (data_P.xinerama() != -1 && xinerama() != -1) { // don't overwrite
1336  d->xinerama = data_P.xinerama();
1337  }
1338 #if KWINDOWSYSTEM_BUILD_DEPRECATED_SINCE(5, 69)
1339  if (data_P.launchedBy() != 0 && launchedBy() != 0) { // don't overwrite
1340  d->launched_by = data_P.launchedBy();
1341  }
1342 #endif
1343  if (!data_P.applicationId().isEmpty() && applicationId().isEmpty()) { // don't overwrite
1344  d->application_id = data_P.applicationId();
1345  }
1346 }
1347 
1349  : d(new Private)
1350 {
1351 }
1352 
1353 KStartupInfoData::~KStartupInfoData()
1354 {
1355  delete d;
1356 }
1357 
1359 {
1360  d->bin = bin_P;
1361 }
1362 
1364 {
1365  return d->bin;
1366 }
1367 
1369 {
1370  d->name = name_P;
1371 }
1372 
1374 {
1375  return d->name;
1376 }
1377 
1379 {
1380  if (!name().isEmpty()) {
1381  return name();
1382  }
1383  return bin();
1384 }
1385 
1387 {
1388  d->description = desc_P;
1389 }
1390 
1392 {
1393  return d->description;
1394 }
1395 
1397 {
1398  if (!description().isEmpty()) {
1399  return description();
1400  }
1401  return name();
1402 }
1403 
1405 {
1406  d->icon = icon_P;
1407 }
1408 
1410 {
1411  if (!icon().isEmpty()) {
1412  return icon();
1413  }
1414  return bin();
1415 }
1416 
1418 {
1419  return d->icon;
1420 }
1421 
1422 void KStartupInfoData::setDesktop(int desktop_P)
1423 {
1424  d->desktop = desktop_P;
1425 }
1426 
1428 {
1429  return d->desktop;
1430 }
1431 
1433 {
1434  d->wmclass = wmclass_P;
1435 }
1436 
1438 {
1439  if (!WMClass().isEmpty() && WMClass() != "0") {
1440  return WMClass();
1441  }
1442  return bin().toUtf8();
1443 }
1444 
1446 {
1447  return d->wmclass;
1448 }
1449 
1451 {
1452  if (!hostname_P.isNull()) {
1453  d->hostname = hostname_P;
1454  } else {
1455  char tmp[256];
1456  tmp[0] = '\0';
1457  if (!gethostname(tmp, 255)) {
1458  tmp[sizeof(tmp) - 1] = '\0';
1459  }
1460  d->hostname = tmp;
1461  }
1462 }
1463 
1465 {
1466  return d->hostname;
1467 }
1468 
1469 void KStartupInfoData::addPid(pid_t pid_P)
1470 {
1471  if (!d->pids.contains(pid_P)) {
1472  d->pids.append(pid_P);
1473  }
1474 }
1475 
1476 void KStartupInfoData::Private::remove_pid(pid_t pid_P)
1477 {
1478  pids.removeAll(pid_P);
1479 }
1480 
1482 {
1483  return d->pids;
1484 }
1485 
1486 bool KStartupInfoData::is_pid(pid_t pid_P) const
1487 {
1488  return d->pids.contains(pid_P);
1489 }
1490 
1491 void KStartupInfoData::setSilent(TriState state_P)
1492 {
1493  d->silent = state_P;
1494 }
1495 
1496 KStartupInfoData::TriState KStartupInfoData::silent() const
1497 {
1498  return d->silent;
1499 }
1500 
1502 {
1503  d->screen = _screen;
1504 }
1505 
1507 {
1508  return d->screen;
1509 }
1510 
1512 {
1513  d->xinerama = xinerama;
1514 }
1515 
1517 {
1518  return d->xinerama;
1519 }
1520 
1521 #if KWINDOWSYSTEM_BUILD_DEPRECATED_SINCE(5, 69)
1523 {
1524  d->launched_by = window;
1525 }
1526 
1528 {
1529  return d->launched_by;
1530 }
1531 #endif
1532 
1534 {
1535  if (desktop.startsWith(QLatin1Char('/'))) {
1536  d->application_id = desktop;
1537  return;
1538  }
1539  // the spec requires this is always a full path, in order for everyone to be able to find it
1541  if (desk.isEmpty()) {
1543  }
1544  if (desk.isEmpty()) {
1545  return;
1546  }
1547  d->application_id = desk;
1548 }
1549 
1551 {
1552  return d->application_id;
1553 }
1554 
1555 static long get_num(const QString &item_P)
1556 {
1557  unsigned int pos = item_P.indexOf(QLatin1Char('='));
1558  return item_P.mid(pos + 1).toLong();
1559 }
1560 
1561 static QString get_str(const QString &item_P)
1562 {
1563  int pos = item_P.indexOf(QLatin1Char('='));
1564  return item_P.mid(pos + 1);
1565 }
1566 
1567 static QByteArray get_cstr(const QString &item_P)
1568 {
1569  return get_str(item_P).toUtf8();
1570 }
1571 
1572 static QStringList get_fields(const QString &txt_P)
1573 {
1574  QString txt = txt_P.simplified();
1575  QStringList ret;
1576  QString item;
1577  bool in = false;
1578  bool escape = false;
1579  for (int pos = 0; pos < txt.length(); ++pos) {
1580  if (escape) {
1581  item += txt[pos];
1582  escape = false;
1583  } else if (txt[pos] == QLatin1Char('\\')) {
1584  escape = true;
1585  } else if (txt[pos] == QLatin1Char('\"')) {
1586  in = !in;
1587  } else if (txt[pos] == QLatin1Char(' ') && !in) {
1588  ret.append(item);
1589  item = QString();
1590  } else {
1591  item += txt[pos];
1592  }
1593  }
1594  ret.append(item);
1595  return ret;
1596 }
1597 
1598 static QString escape_str(const QString &str_P)
1599 {
1600  QString ret;
1601  // prepare some space which should be always enough.
1602  // No need to squeze at the end, as the result is only used as intermediate string
1603  ret.reserve(str_P.size() * 2);
1604  for (int pos = 0; pos < str_P.length(); ++pos) {
1605  if (str_P[pos] == QLatin1Char('\\') || str_P[pos] == QLatin1Char('"')) {
1606  ret += QLatin1Char('\\');
1607  }
1608  ret += str_P[pos];
1609  }
1610  return ret;
1611 }
1612 
1613 #include "moc_kstartupinfo.cpp"
const QString & findIcon() const
Returns the icon of the startup notification, and if it&#39;s not available, tries to get it from the bin...
static void resetStartupEnv()
Unsets the startup notification environment variable.
int indexOf(QChar ch, int from, Qt::CaseSensitivity cs) const const
indicates that this is a normal, top-level window
Definition: netwm_def.h:365
static bool sendStartupX(Display *dpy, const KStartupInfoId &id, const KStartupInfoData &data)
Like sendStartup , uses dpy instead of qt_x11display() for sending the info.
const char * clientMachine() const
Returns the client machine for the window (i.e.
Definition: netwm.cpp:4815
QEvent::Type type() const const
uint toUInt(bool *ok, int base) const const
const QString & description() const
Returns the name of the startup notification, or empty if not available.
QMap::iterator erase(QMap::iterator pos)
QString name(const QVariant &location)
bool contains(const Key &key) const const
const QString & findDescription() const
Returns the description of the startup notification.
const QByteArray findWMClass() const
Returns the WM_CLASS value for the startup notification, or binary name if not available.
Class for manipulating the application startup notification.
Definition: kstartupinfo.h:51
QByteArray toLower() const const
Sending string messages to other applications using the X Client Messages.
Definition: kxmessages.h:32
QWidget * window() const const
xcb_window_t groupLeader() const
Returns the leader window for the group the window is in, if any.
Definition: netwm.cpp:4770
static void setOnDesktop(WId win, int desktop)
Moves window win to desktop desktop.
void update(const KStartupInfoData &data)
Updates the notification data from the given data.
static bool sendFinishX(Display *dpy, const KStartupInfoId &id)
Like sendFinish , uses dpy instead of qt_x11display() for sending the info.
KStartupInfo(int flags, QObject *parent=nullptr)
Creates an instance that will receive the startup notifications.
void setTimeout(unsigned int secs)
Sets the timeout for notifications, after this timeout a notification is removed. ...
bool isNull() const const
int size() const const
const char * windowClassName() const
Returns the name component of the window class for the window (i.e.
Definition: netwm.cpp:4805
bool isEmpty() const const
QString simplified() const const
void setAttribute(Qt::WidgetAttribute attribute, bool on)
Class representing an identification of application startup notification.
Definition: kstartupinfo.h:447
QByteArray WMClass() const
Returns the WM_CLASS value for the startup notification, or empty if not available.
static bool sendFinishXcb(xcb_connection_t *conn, int screen, const KStartupInfoId &id)
Like sendFinish , uses conn instead of QX11Info::connection() for sending the info.
indicates that the window did not define a window type.
Definition: netwm_def.h:361
bool isNull() const
Checks whether the identifier is valid.
static void forceActiveWindow(WId win, long time=0)
Sets window win to be the active window.
void setBin(const QString &bin)
Sets the binary name of the application (e.g. &#39;kcontrol&#39;).
static bool sendStartupXcb(xcb_connection_t *conn, int screen, const KStartupInfoId &id, const KStartupInfoData &data)
Like sendStartup , uses conn instead of QX11Info::connection() for sending the info.
Common API for root window properties/protocols.
Definition: netwm.h:40
void setName(const QString &name)
Sets the name for the notification (e.g. &#39;Control Center&#39;).
startup_t checkStartup(WId w)
Checks if the given windows matches any existing startup notification.
QFlags< Property2 > Properties2
Stores a combination of Property2 values.
Definition: netwm_def.h:824
void setXinerama(int xinerama)
Sets the Xinerama screen for the startup notification ( i.e.
static void setWindowStartupId(WId window, const QByteArray &id)
Sets the startup notification window property on the given window.
static QByteArray startupId()
Returns the app startup notification identifier for this running application.
static void setStartupId(const QByteArray &startup_id)
Sets a new value for the application startup notification window property for newly created toplevel ...
int pid() const
Returns the process id for the client window.
Definition: netwm.cpp:4727
int desktop() const
Returns the desktop for the startup notification.
qint64 currentMSecsSinceEpoch()
static bool sendChange(const KStartupInfoId &id, const KStartupInfoData &data)
Sends given notification data about started application with the given startup identification.
static int timestampCompare(unsigned long time1, unsigned long time2)
Compares two X timestamps, taking into account wrapping and 64bit architectures.
Definition: netwm.cpp:4938
QFlags< Property > Properties
Stores a combination of Property values.
Definition: netwm_def.h:751
static int currentDesktop()
Returns the current virtual desktop.
static void setNewStartupId(QWindow *window, const QByteArray &startup_id)
Use this function if the application got a request with startup notification from outside (for exampl...
QString applicationId() const
The .desktop file used to initiate this startup notification, or empty.
bool operator!=(const KStartupInfoId &id) const
Overloaded operator.
void append(const T &value)
void setDescription(const QString &descr)
Sets the description for the notification (e.g. &#39;Launching Control Center&#39;).
static bool sendStartup(const KStartupInfoId &id, const KStartupInfoData &data)
Sends given notification data about started application with the given startup identification.
QByteArray hostname() const
Returns the hostname for the startup notification.
void gotStartupChange(const KStartupInfoId &id, const KStartupInfoData &data)
Emitted when a startup notification changes.
Class representing data about an application startup notification.
Definition: kstartupinfo.h:527
WindowType
Window type.
Definition: netwm_def.h:357
void setIcon(const QString &icon)
Sets the icon for the startup notification (e.g. &#39;kcontrol&#39;).
int screen() const
The X11 screen on which the startup notification is happening, -1 if unknown.
bool isEmpty() const const
const QString & name() const
Returns the name of the startup notification, or empty if not available.
bool isEmpty() const const
void setSilent(TriState state)
Sets whether the visual feedback for this startup notification should be silenced (temporarily suspen...
int removeAll(const T &value)
QString trimmed() const const
const char * constData() const const
bool startsWith(const QString &s, Qt::CaseSensitivity cs) const const
int xinerama() const
The Xinerama screen for the startup notification, -1 if unknown.
void gotRemoveStartup(const KStartupInfoId &id, const KStartupInfoData &data)
Emitted when a startup notification is removed (either because it was detected that the application i...
void setHostname(const QByteArray &hostname=QByteArray())
Sets the hostname on which the application is starting.
T & first()
void gotNewStartup(const KStartupInfoId &id, const KStartupInfoData &data)
Emitted when a new startup notification is created (i.e. a new application is being started)...
QMap::iterator end()
QMap::iterator begin()
const QString & findName() const
Returns the name of the startup notification.
bool isSupported(NET::Property property) const
Returns true if the given property is supported by the window manager.
Definition: netwm.cpp:2346
void setApplicationId(const QString &desktop)
Sets the .desktop file that was used to initiate the startup notification.
QList< pid_t > pids() const
Returns all PIDs for the startup notification.
static QByteArray windowStartupId(WId w)
Returns startup notification identification of the given window.
QList::iterator end()
unsigned long timestamp() const
Return the user timestamp for the startup notification, or 0 if no timestamp is set.
static bool sendFinish(const KStartupInfoId &id)
Ends startup notification with the given identification.
bool setupStartupEnv() const
Sets the startup notification environment variable to this identification.
void stop()
QCA_EXPORT void init()
static KWindowSystem * self()
Access to the singleton instance.
QWindow * windowHandle() const const
const char * startupId() const
Returns the startup notification id of the window.
Definition: netwm.cpp:4737
bool is_pid(pid_t pid) const
Checks whether the given pid is in the list of PIDs for startup notification.
long toLong(bool *ok, int base) const const
WA_NativeWindow
virtual void customEvent(QEvent *event)
if(recurs()&&!first)
const QByteArray & id() const
Returns the notification identifier as string.
WId launchedBy() const
The toplevel window of the application that caused this startup notification, 0 if unknown...
void setWMClass(const QByteArray &wmclass)
Sets a WM_CLASS value for the startup notification, it may be used for increasing the chance that the...
QString mid(int position, int n) const const
static KStartupInfoId currentStartupIdEnv()
Returns the current startup notification identification for the current startup notification environm...
static QByteArray createNewStartupIdForTimestamp(quint32 timestamp)
Creates and returns new startup id with timestamp as user timestamp part.
void setScreen(int screen)
Sets the X11 screen on which the startup notification should happen.
const QString & bin() const
Returns the binary name of the starting application.
static void appStarted()
Manual notification that the application has started.
static QByteArray createNewStartupId()
Creates and returns new startup id.
NETWORKMANAGERQT_EXPORT QString hostname()
static bool sendChangeXcb(xcb_connection_t *conn, int screen, const KStartupInfoId &id, const KStartupInfoData &data)
Like sendChange , uses conn instead of QX11Info::connection() for sending the info.
static bool sendChangeX(Display *dpy, const KStartupInfoId &id, const KStartupInfoData &data)
Like sendChange , uses dpy instead of qt_x11display() for sending the info.
bool operator==(const KStartupInfoId &id) const
Overloaded operator.
QString arg(qlonglong a, int fieldWidth, int base, QChar fillChar) const const
void setStartupId(const char *startup_id)
Sets the startup notification id id on the window.
Definition: netwm.cpp:3290
typedef ConstIterator
indicates that this is a dialog window
Definition: netwm_def.h:388
int length() const const
void reserve(int size)
void start(int msec)
Common API for application window properties/protocols.
Definition: netwm.h:944
QMap::iterator insert(const Key &key, const T &value)
bool isEmpty() const const
indicates a utility window
Definition: netwm_def.h:403
const QString & icon() const
Returns the icon of the startup notification, or empty if not available.
static void silenceStartup(bool silence)
If your application shows temporarily some window during its startup, for example a dialog...
QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
const char * windowClassClass() const
Returns the class component of the window class for the window (i.e.
Definition: netwm.cpp:4800
void setDesktop(int desktop)
Sets the desktop for the startup notification (i.e. the desktop on which the starting application sho...
void initId(const QByteArray &id="")
Initializes this object with the given identification ( which may be also "0" for no notification )...
KStartupInfoId()
Creates an empty identification.
QTextStream & bin(QTextStream &stream)
WindowType windowType(WindowTypes supported_types) const
Returns the window type for this client (see the NET base class documentation for a description of th...
Definition: netwm.cpp:4682
void addPid(pid_t pid)
Adds a PID to the list of processes that belong to the startup notification.
QMap::iterator find(const Key &key)
QList::iterator begin()
TriState silent() const
Return the silence status for the startup notification.
WId winId() const const
xcb_window_t transientFor() const
Returns the WM_TRANSIENT_FOR property for the window, i.e.
Definition: netwm.cpp:4765
static bool broadcastMessageX(Display *disp, const char *msg_type, const QString &message, int screen=-1)
Broadcasts the given message with the given message type.
Definition: kxmessages.cpp:220
Q_EMITQ_EMIT
QString locate(QStandardPaths::StandardLocation type, const QString &fileName, QStandardPaths::LocateOptions options)
void setLaunchedBy(WId window)
Sets the toplevel window of the application that caused this startup notification.
int remove(const Key &key)
KStartupInfoData()
Constructor.
QByteArray toUtf8() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2021 The KDE developers.
Generated on Thu Sep 23 2021 22:41:28 by doxygen 1.8.11 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.