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

KDE's Doxygen guidelines are available online.