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

KDE's Doxygen guidelines are available online.