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