Plasma-workspace

notification.cpp
1/*
2 SPDX-FileCopyrightText: 2008 Dmitry Suzdalev <dimsuz@gmail.com>
3 SPDX-FileCopyrightText: 2017 David Edmundson <davidedmundson@kde.org>
4 SPDX-FileCopyrightText: 2018-2019 Kai Uwe Broulik <kde@privat.broulik.de>
5
6 SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
7*/
8
9#include "notification.h"
10#include "notification_p.h"
11
12#include <algorithm>
13
14#include <QDBusArgument>
15#include <QDebug>
16#include <QImageReader>
17#include <QRegularExpression>
18#include <QXmlStreamReader>
19
20#include <KApplicationTrader>
21#include <KConfig>
22#include <KConfigGroup>
23#include <KService>
24
25#include "debug.h"
26
27using namespace NotificationManager;
28using namespace Qt::StringLiterals;
29
30QCache<uint, QImage> Notification::Private::s_imageCache = QCache<uint, QImage>{};
31
32Notification::Private::Private()
33{
34}
35
36Notification::Private::~Private()
37{
38 // The image cache is cleared by AbstractNotificationsModel::pendingRemovalTimer
39}
40
41QString Notification::Private::sanitize(const QString &text)
42{
43 // replace all \ns with <br/>
44 QString t = text;
45
46 t.replace(QLatin1String("\n"), QStringLiteral("<br/>"));
47 // Now remove all inner whitespace (\ns are already <br/>s)
48 t = std::move(t).simplified();
49 // Replace <br></br> with just <br/> in case someone is mad enough to send that
50 static const QRegularExpression brOpenCloseExpr(QStringLiteral("<br>\\s*</br>"));
51 t.replace(brOpenCloseExpr, QLatin1String("<br/>"));
52 // Replace single <br> with <br/> as QXmlStreamReader requires it
53 t.replace(QStringLiteral("<br>"), QLatin1String("<br/>"));
54 // Finally, check if we don't have multiple <br/>s following,
55 // can happen for example when "\n \n" is sent, this replaces
56 // all <br/>s in succession with just one. Also remove extra whitespace after any newline
57 static const QRegularExpression brExpr(QStringLiteral("<br/>(\\s|<br/>)+"));
58 t.replace(brExpr, QLatin1String("<br/>"));
59 // This fancy RegExp escapes every occurrence of & since QtQuick Text will blatantly cut off
60 // text where it finds a stray ampersand.
61 // Only &{apos, quot, gt, lt, amp}; as well as &#123 character references will be allowed
62 static const QRegularExpression escapeExpr(QStringLiteral("&(?!(?:apos|quot|[gl]t|amp);|#)"));
63 t.replace(escapeExpr, QLatin1String("&amp;"));
64
65 // If the < or > is not followed or preceded by text or "/", escape it.
66 // This nightmarish RegExp matches the < or >, then captures whatever is after/before it.
67 // We then replace the match with the corresponding symbol, and add the captured bit back.
68 static const QRegularExpression ltExpr(QStringLiteral("<([^\\/,a-zA-Z])"));
69 t.replace(ltExpr, QLatin1String("&lt;\\1"));
70 // Check for " in case the item is something like <img src="bla">
71 static const QRegularExpression gtExpr(QStringLiteral("([^\\/,a-zA-Z,\"])>"));
72 t.replace(gtExpr, QLatin1String("\\1&gt;"));
73
74 // Don't bother adding some HTML structure if the body is now empty
75 if (t.isEmpty()) {
76 return t;
77 }
78
79 t = u"<html>" + std::move(t) + u"</html>";
81 QString result;
82 QXmlStreamWriter out(&result);
83
84 static constexpr std::array<QStringView, 10> allowedTags{u"b", u"i", u"u", u"img", u"a", u"html", u"br", u"table", u"tr", u"td"};
85
86 out.writeStartDocument();
87 while (!r.atEnd()) {
88 r.readNext();
89
90 if (r.tokenType() == QXmlStreamReader::StartElement) {
91 const QStringView name = r.name();
92 if (std::ranges::find(allowedTags, name) == allowedTags.end()) {
93 continue;
94 }
95 out.writeStartElement(name);
96 if (name == QLatin1String("img")) {
97 const QString src = r.attributes().value("src").toString();
98 const QStringView alt = r.attributes().value("alt");
99
100 const QUrl url(src);
101 if (url.isLocalFile()) {
102 out.writeAttribute(QStringLiteral("src"), src);
103 } else {
104 // image denied for security reasons! Do not copy the image src here!
105 }
106
107 out.writeAttribute(u"alt", alt);
108 }
109 if (name == QLatin1Char('a')) {
110 out.writeAttribute(u"href", r.attributes().value("href"));
111 }
112 }
113
114 if (r.tokenType() == QXmlStreamReader::EndElement) {
115 const QStringView name = r.name();
116 if (std::ranges::find(allowedTags, name) == allowedTags.end()) {
117 continue;
118 }
119 out.writeEndElement();
120 }
121
122 if (r.tokenType() == QXmlStreamReader::Characters) {
123 out.writeCharacters(r.text()); // this auto escapes chars -> HTML entities
124 }
125 }
126 out.writeEndDocument();
127
128 if (r.hasError()) {
129 qCWarning(NOTIFICATIONMANAGER) << "Notification to send to backend contains invalid XML: " << r.errorString() << "line" << r.lineNumber() << "col"
130 << r.columnNumber();
131 }
132
133 // The Text.StyledText format handles only html3.2 stuff and &apos; is html4 stuff
134 // so we need to replace it here otherwise it will not render at all.
135 result.replace(QLatin1String("&apos;"), QChar(u'\''));
136
137 return result;
138}
139
140QImage Notification::Private::decodeNotificationSpecImageHint(const QDBusArgument &arg)
141{
142 int width, height, rowStride, hasAlpha, bitsPerSample, channels;
143 QByteArray pixels;
144 char *ptr;
145 char *end;
146
148 return QImage();
149 }
150 arg.beginStructure();
151 arg >> width >> height >> rowStride >> hasAlpha >> bitsPerSample >> channels >> pixels;
152 arg.endStructure();
153
154#define SANITY_CHECK(condition) \
155 if (!(condition)) { \
156 qCWarning(NOTIFICATIONMANAGER) << "Image decoding sanity check failed on" << #condition; \
157 return QImage(); \
158 }
159
160 SANITY_CHECK(width > 0);
161 SANITY_CHECK(width < 2048);
162 SANITY_CHECK(height > 0);
163 SANITY_CHECK(height < 2048);
164 SANITY_CHECK(rowStride > 0);
165
166#undef SANITY_CHECK
167
168 auto copyLineRGB32 = [](QRgb *dst, const char *src, int width) {
169 const char *end = src + width * 3;
170 for (; src != end; ++dst, src += 3) {
171 *dst = qRgb(src[0], src[1], src[2]);
172 }
173 };
174
175 auto copyLineARGB32 = [](QRgb *dst, const char *src, int width) {
176 const char *end = src + width * 4;
177 for (; src != end; ++dst, src += 4) {
178 *dst = qRgba(src[0], src[1], src[2], src[3]);
179 }
180 };
181
183 void (*fcn)(QRgb *, const char *, int) = nullptr;
184 if (bitsPerSample == 8) {
185 if (channels == 4) {
186 format = QImage::Format_ARGB32;
187 fcn = copyLineARGB32;
188 } else if (channels == 3) {
189 format = QImage::Format_RGB32;
190 fcn = copyLineRGB32;
191 }
192 }
193 if (format == QImage::Format_Invalid) {
194 qCWarning(NOTIFICATIONMANAGER) << "Unsupported image format (hasAlpha:" << hasAlpha << "bitsPerSample:" << bitsPerSample << "channels:" << channels
195 << ")";
196 return QImage();
197 }
198
199 QImage image(width, height, format);
200 ptr = pixels.data();
201 end = ptr + pixels.length();
202 for (int y = 0; y < height; ++y, ptr += rowStride) {
203 if (ptr + channels * width > end) {
204 qCWarning(NOTIFICATIONMANAGER) << "Image data is incomplete. y:" << y << "height:" << height;
205 break;
206 }
207 fcn((QRgb *)image.scanLine(y), ptr, width);
208 }
209
210 return image;
211}
212
213void Notification::Private::sanitizeImage(QImage &image)
214{
215 if (image.isNull()) {
216 return;
217 }
218
219 const QSize max = maximumImageSize();
220 if (image.size().width() > max.width() || image.size().height() > max.height()) {
222 }
223}
224
225void Notification::Private::loadImagePath(const QString &path)
226{
227 // image_path and appIcon should either be a URL with file scheme or the name of a themed icon.
228 // We're lenient and also allow local paths.
229
230 s_imageCache.remove(id); // clear
231 icon.clear();
232
233 QUrl imageUrl;
234 if (path.startsWith(QLatin1Char('/'))) {
235 imageUrl = QUrl::fromLocalFile(path);
236 } else if (path.contains(QLatin1Char('/'))) { // bad heuristic to detect a URL
237 imageUrl = QUrl(path);
238
239 if (!imageUrl.isLocalFile()) {
240 qCDebug(NOTIFICATIONMANAGER) << "Refused to load image from" << path << "which isn't a valid local location.";
241 return;
242 }
243 }
244
245 if (!imageUrl.isValid()) {
246 // try icon path instead;
247 icon = path;
248 return;
249 }
250
251 QImageReader reader(imageUrl.toLocalFile());
252 reader.setAutoTransform(true);
253
254 if (QSize imageSize = reader.size(); imageSize.isValid()) {
255 if (imageSize.width() > maximumImageSize().width() || imageSize.height() > maximumImageSize().height()) {
256 imageSize = imageSize.scaled(maximumImageSize(), Qt::KeepAspectRatio);
257 reader.setScaledSize(imageSize);
258 }
259 s_imageCache.insert(id, new QImage(reader.read()), imageSize.width() * imageSize.height());
260 }
261}
262
263QString Notification::Private::defaultComponentName()
264{
265 // NOTE Keep in sync with KNotification
266 return QStringLiteral("plasma_workspace");
267}
268
269constexpr QSize Notification::Private::maximumImageSize()
270{
271 return QSize(256, 256);
272}
273
274KService::Ptr Notification::Private::serviceForDesktopEntry(const QString &desktopEntry)
275{
276 if (desktopEntry.isEmpty()) {
277 return {};
278 }
279
280 KService::Ptr service;
281
282 if (desktopEntry.startsWith(QLatin1Char('/'))) {
283 service = KService::serviceByDesktopPath(desktopEntry);
284 } else {
285 service = KService::serviceByDesktopName(desktopEntry);
286 }
287
288 if (!service) {
289 const QString lowerDesktopEntry = desktopEntry.toLower();
290 service = KService::serviceByDesktopName(lowerDesktopEntry);
291 }
292
293 // Try if it's a renamed flatpak
294 if (!service) {
295 const QString desktopId = desktopEntry + QLatin1String(".desktop");
296
297 const auto services = KApplicationTrader::query([&desktopId](const KService::Ptr &app) -> bool {
298 const QStringList renamedFrom = app->property<QStringList>(QStringLiteral("X-Flatpak-RenamedFrom"));
299 return renamedFrom.contains(desktopId);
300 });
301
302 if (!services.isEmpty()) {
303 service = services.first();
304 }
305 }
306
307 // Try snap instance name.
308 if (!service) {
309 const auto services = KApplicationTrader::query([&desktopEntry](const KService::Ptr &app) -> bool {
310 const QString snapInstanceName = app->property<QString>(QStringLiteral("X-SnapInstanceName"));
311 return desktopEntry.compare(snapInstanceName, Qt::CaseInsensitive) == 0;
312 });
313
314 if (!services.isEmpty()) {
315 service = services.first();
316 }
317 }
318
319 return service;
320}
321
322void Notification::Private::setDesktopEntry(const QString &desktopEntry)
323{
324 QString serviceName;
325
326 configurableService = false;
327
328 KService::Ptr service = serviceForDesktopEntry(desktopEntry);
329 if (service) {
330 this->desktopEntry = service->desktopEntryName();
331 serviceName = service->name();
332 applicationIconName = service->icon();
333 configurableService = !service->noDisplay();
334 }
335
336 const bool isDefaultEvent = (notifyRcName == defaultComponentName());
337 configurableNotifyRc = false;
338 if (!notifyRcName.isEmpty()) {
339 // Check whether the application actually has notifications we can configure
340 KConfig config(notifyRcName + QStringLiteral(".notifyrc"), KConfig::NoGlobals);
341
342 QStringList configSources =
343 QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, QStringLiteral("knotifications6/%1.notifyrc").arg(notifyRcName));
344 // Keep compatibility with KF5 applications
345 if (configSources.isEmpty()) {
346 configSources = QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, QStringLiteral("knotifications5/%1.notifyrc").arg(notifyRcName));
347 }
348 // `QStandardPaths` follows the order of precedence given by `$XDG_DATA_DIRS
349 // (more priority goest first), but for `addConfigSources() it is the opposite
350 std::reverse(configSources.begin(), configSources.end());
351 config.addConfigSources(configSources);
352
353 KConfigGroup globalGroup(&config, u"Global"_s);
354
355 const QString iconName = globalGroup.readEntry("IconName");
356
357 // also only overwrite application icon name for non-default events (or if we don't have a service icon)
358 if (!iconName.isEmpty() && (!isDefaultEvent || applicationIconName.isEmpty())) {
359 applicationIconName = iconName;
360 }
361
362 const QRegularExpression regexp(QStringLiteral("^Event/([^/]*)$"));
363 configurableNotifyRc = !config.groupList().filter(regexp).isEmpty();
364 }
365
366 // For default events we try to show the application name from the desktop entry if possible
367 // This will have us show e.g. "Dr Konqi" instead of generic "Plasma Desktop"
368 // The application may not send an applicationName. Use the name from the desktop entry then
369 if ((isDefaultEvent || applicationName.isEmpty()) && !serviceName.isEmpty()) {
370 applicationName = serviceName;
371 }
372}
373
374void Notification::Private::processHints(const QVariantMap &hints)
375{
376 auto end = hints.end();
377
378 notifyRcName = hints.value(QStringLiteral("x-kde-appname")).toString();
379
380 setDesktopEntry(hints.value(QStringLiteral("desktop-entry")).toString());
381
382 // Special override for KDE Connect since the notification is sent by kdeconnectd
383 // but actually comes from a different app on the phone
384 const QString applicationDisplayName = hints.value(QStringLiteral("x-kde-display-appname")).toString();
385 if (!applicationDisplayName.isEmpty()) {
386 applicationName = applicationDisplayName;
387 }
388
389 originName = hints.value(QStringLiteral("x-kde-origin-name")).toString();
390
391 eventId = hints.value(QStringLiteral("x-kde-eventId")).toString();
392 xdgTokenAppId = hints.value(QStringLiteral("x-kde-xdgTokenAppId")).toString();
393
394 bool ok;
395 const int urgency = hints.value(QStringLiteral("urgency")).toInt(&ok); // DBus type is actually "byte"
396 if (ok) {
397 // FIXME use separate enum again
398 switch (urgency) {
399 case 0:
400 setUrgency(Notifications::LowUrgency);
401 break;
402 case 1:
404 break;
405 case 2:
406 setUrgency(Notifications::CriticalUrgency);
407 break;
408 }
409 }
410
411 resident = hints.value(QStringLiteral("resident")).toBool();
412 transient = hints.value(QStringLiteral("transient")).toBool();
413
414 userActionFeedback = hints.value(QStringLiteral("x-kde-user-action-feedback")).toBool();
415 if (userActionFeedback) {
416 // A confirmation of an explicit user interaction is assumed to have been seen by the user.
417 read = true;
418 }
419
420 urls = QUrl::fromStringList(hints.value(QStringLiteral("x-kde-urls")).toStringList());
421
422 replyPlaceholderText = hints.value(QStringLiteral("x-kde-reply-placeholder-text")).toString();
423 replySubmitButtonText = hints.value(QStringLiteral("x-kde-reply-submit-button-text")).toString();
424 replySubmitButtonIconName = hints.value(QStringLiteral("x-kde-reply-submit-button-icon-name")).toString();
425
426 category = hints.value(QStringLiteral("category")).toString();
427
428 // Underscored hints was in use in version 1.1 of the spec but has been
429 // replaced by dashed hints in version 1.2. We need to support it for
430 // users of the 1.2 version of the spec.
431 auto it = hints.find(QStringLiteral("image-data"));
432 if (it == end) {
433 it = hints.find(QStringLiteral("image_data"));
434 }
435 if (it == end) {
436 // This hint was in use in version 1.0 of the spec but has been
437 // replaced by "image_data" in version 1.1. We need to support it for
438 // users of the 1.0 version of the spec.
439 it = hints.find(QStringLiteral("icon_data"));
440 }
441
442 QImage *image = nullptr;
443 if (it != end) {
444 QImage imageFromHint(decodeNotificationSpecImageHint(it->value<QDBusArgument>()));
445 if (!imageFromHint.isNull()) {
446 Q_ASSERT_X(imageFromHint.width() > 0 && imageFromHint.height() > 0,
447 Q_FUNC_INFO,
448 qPrintable(QStringLiteral("%1 %2").arg(QString::number(imageFromHint.width()), QString::number(imageFromHint.height()))));
449 sanitizeImage(imageFromHint);
450 image = new QImage(imageFromHint);
451 s_imageCache.insert(id, image, imageFromHint.width() * imageFromHint.height());
452 }
453 }
454
455 if (!image) {
456 it = hints.find(QStringLiteral("image-path"));
457 if (it == end) {
458 it = hints.find(QStringLiteral("image_path"));
459 }
460
461 if (it != end) {
462 loadImagePath(it->toString());
463 }
464 }
465
466 this->hints = hints;
467}
468
469void Notification::Private::setUrgency(Notifications::Urgency urgency)
470{
471 this->urgency = urgency;
472
473 // Critical notifications must not time out
474 // TODO should we really imply this here and not on the view side?
475 // are there usecases for critical but can expire?
476 // "critical updates available"?
477 if (urgency == Notifications::CriticalUrgency) {
478 timeout = 0;
479 }
480}
481
482Notification::Notification(uint id)
483 : d(new Private())
484{
485 d->id = id;
486 d->created = QDateTime::currentDateTimeUtc();
487}
488
489Notification::Notification(const Notification &other)
490 : d(new Private(*other.d))
491{
492}
493
494Notification::Notification(Notification &&other) noexcept
495 : d(other.d)
496{
497 other.d = nullptr;
498}
499
500Notification &Notification::operator=(const Notification &other)
501{
502 *d = *other.d;
503 return *this;
504}
505
506Notification &Notification::operator=(Notification &&other) noexcept
507{
508 d = other.d;
509 other.d = nullptr;
510 return *this;
511}
512
513Notification::~Notification()
514{
515 delete d;
516}
517
518uint Notification::id() const
519{
520 return d->id;
521}
522
523QString Notification::dBusService() const
524{
525 return d->dBusService;
526}
527
528void Notification::setDBusService(const QString &dBusService)
529{
530 d->dBusService = dBusService;
531}
532
533QDateTime Notification::created() const
534{
535 return d->created;
536}
537
538void Notification::setCreated(const QDateTime &created)
539{
540 d->created = created;
541}
542
543QDateTime Notification::updated() const
544{
545 return d->updated;
546}
547
548void Notification::resetUpdated()
549{
550 d->updated = QDateTime::currentDateTimeUtc();
551}
552
553bool Notification::read() const
554{
555 return d->read;
556}
557
558void Notification::setRead(bool read)
559{
560 d->read = read;
561}
562
563QString Notification::summary() const
564{
565 return d->summary;
566}
567
568void Notification::setSummary(const QString &summary)
569{
570 d->summary = summary;
571}
572
573QString Notification::body() const
574{
575 return d->body;
576}
577
578void Notification::setBody(const QString &body)
579{
580 d->rawBody = body;
581 d->body = Private::sanitize(body.trimmed());
582}
583
584QString Notification::rawBody() const
585{
586 return d->rawBody;
587}
588
589QString Notification::icon() const
590{
591 return d->icon;
592}
593
594void Notification::setIcon(const QString &icon)
595{
596 d->loadImagePath(icon);
597}
598
599QImage Notification::image() const
600{
601 if (d->s_imageCache.contains(d->id)) {
602 return *d->s_imageCache.object(d->id);
603 }
604 return QImage();
605}
606
607void Notification::setImage(const QImage &image)
608{
609 d->s_imageCache.insert(d->id, new QImage(image));
610}
611
612QString Notification::desktopEntry() const
613{
614 return d->desktopEntry;
615}
616
617void Notification::setDesktopEntry(const QString &desktopEntry)
618{
619 d->setDesktopEntry(desktopEntry);
620}
621
622QString Notification::notifyRcName() const
623{
624 return d->notifyRcName;
625}
626
627QString Notification::eventId() const
628{
629 return d->eventId;
630}
631
632QString Notification::applicationName() const
633{
634 return d->applicationName;
635}
636
637void Notification::setApplicationName(const QString &applicationName)
638{
639 d->applicationName = applicationName;
640}
641
642QString Notification::applicationIconName() const
643{
644 return d->applicationIconName;
645}
646
647void Notification::setApplicationIconName(const QString &applicationIconName)
648{
649 d->applicationIconName = applicationIconName;
650}
651
652QString Notification::originName() const
653{
654 return d->originName;
655}
656
657QStringList Notification::actionNames() const
658{
659 return d->actionNames;
660}
661
662QStringList Notification::actionLabels() const
663{
664 return d->actionLabels;
665}
666
667bool Notification::hasDefaultAction() const
668{
669 return d->hasDefaultAction;
670}
671
672QString Notification::defaultActionLabel() const
673{
674 // Most apps don't expect the default action be visible to the user.
675 // For KDE apps we can assume KNotification does something reasonable.
676 if (!d->notifyRcName.isEmpty() && d->notifyRcName != Private::defaultComponentName()) {
677 return d->defaultActionLabel;
678 } else {
679 return QString(); // Let the UI pick a sensible default.
680 }
681}
682
683void Notification::setActions(const QStringList &actions)
684{
685 if (actions.count() % 2 != 0) {
686 qCWarning(NOTIFICATIONMANAGER) << "List of actions must contain an even number of items, tried to set actions to" << actions;
687 return;
688 }
689
690 d->hasDefaultAction = false;
691 d->hasConfigureAction = false;
692 d->hasReplyAction = false;
693
694 QStringList names;
695 QStringList labels;
696
697 for (int i = 0; i < actions.count(); i += 2) {
698 const QString &name = actions.at(i);
699 const QString &label = actions.at(i + 1);
700
701 if (!d->hasDefaultAction && name == QLatin1String("default")) {
702 d->hasDefaultAction = true;
703 d->defaultActionLabel = label;
704 continue;
705 }
706
707 if (!d->hasConfigureAction && name == QLatin1String("settings")) {
708 d->hasConfigureAction = true;
709 d->configureActionLabel = label;
710 continue;
711 }
712
713 if (!d->hasReplyAction && name == QLatin1String("inline-reply")) {
714 d->hasReplyAction = true;
715 d->replyActionLabel = label;
716 continue;
717 }
718
719 names << name;
720 labels << label;
721 }
722
723 d->actionNames = names;
724 d->actionLabels = labels;
725}
726
727QList<QUrl> Notification::urls() const
728{
729 return d->urls;
730}
731
732void Notification::setUrls(const QList<QUrl> &urls)
733{
734 d->urls = urls;
735}
736
737Notifications::Urgency Notification::urgency() const
738{
739 return d->urgency;
740}
741
742bool Notification::userActionFeedback() const
743{
744 return d->userActionFeedback;
745}
746
747int Notification::timeout() const
748{
749 return d->timeout;
750}
751
752void Notification::setTimeout(int timeout)
753{
754 d->timeout = timeout;
755}
756
757bool Notification::configurable() const
758{
759 return d->hasConfigureAction || d->configurableNotifyRc || d->configurableService;
760}
761
762QString Notification::configureActionLabel() const
763{
764 return d->configureActionLabel;
765}
766
767bool Notification::hasReplyAction() const
768{
769 return d->hasReplyAction;
770}
771
772QString Notification::replyActionLabel() const
773{
774 return d->replyActionLabel;
775}
776
777QString Notification::replyPlaceholderText() const
778{
779 return d->replyPlaceholderText;
780}
781
782QString Notification::replySubmitButtonText() const
783{
784 return d->replySubmitButtonText;
785}
786
787QString Notification::replySubmitButtonIconName() const
788{
789 return d->replySubmitButtonIconName;
790}
791
792QString Notification::category() const
793{
794 return d->category;
795}
796
797bool Notification::expired() const
798{
799 return d->expired;
800}
801
802void Notification::setExpired(bool expired)
803{
804 d->expired = expired;
805}
806
807bool Notification::dismissed() const
808{
809 return d->dismissed;
810}
811
812void Notification::setDismissed(bool dismissed)
813{
814 d->dismissed = dismissed;
815}
816
817bool Notification::resident() const
818{
819 return d->resident;
820}
821
822void Notification::setResident(bool resident)
823{
824 d->resident = resident;
825}
826
827bool Notification::transient() const
828{
829 return d->transient;
830}
831
832void Notification::setTransient(bool transient)
833{
834 d->transient = transient;
835}
836
837QVariantMap Notification::hints() const
838{
839 return d->hints;
840}
841
842void Notification::setHints(const QVariantMap &hints)
843{
844 d->hints = hints;
845}
846
847void Notification::processHints(const QVariantMap &hints)
848{
849 d->processHints(hints);
850}
851
852bool Notification::wasAddedDuringInhibition() const
853{
854 return d->wasAddedDuringInhibition;
855}
856
857void Notification::setWasAddedDuringInhibition(bool value)
858{
859 d->wasAddedDuringInhibition = value;
860}
static Ptr serviceByDesktopName(const QString &_name)
QExplicitlySharedDataPointer< KService > Ptr
static Ptr serviceByDesktopPath(const QString &_path)
Urgency
The notification urgency.
@ LowUrgency
The notification has low urgency, it is not important and may not be shown or added to a history.
@ NormalUrgency
The notification has normal urgency. This is also the default if no urgecny is supplied.
list< Action > actions
char * toString(const EngineQuery &query)
KSERVICE_EXPORT KService::List query(FilterFunc filterFunc)
QVariant read(const QByteArray &data, int versionOverride=0)
QString path(const QString &relativePath)
void transient(const QString &message, const QString &title)
QString name(StandardAction id)
Category category(StandardShortcut id)
QString label(StandardShortcut id)
const QList< QKeySequence > & end()
QDateTime currentDateTimeUtc()
void beginStructure()
ElementType currentType() const const
void endStructure()
bool isNull() const const
QImage scaled(const QSize &size, Qt::AspectRatioMode aspectRatioMode, Qt::TransformationMode transformMode) const const
QSize size() const const
int width() const const
iterator begin()
iterator end()
bool isEmpty() const const
int height() const const
bool isValid() const const
int width() const const
QStringList locateAll(StandardLocation type, const QString &fileName, LocateOptions options)
int compare(QLatin1StringView s1, const QString &s2, Qt::CaseSensitivity cs)
bool contains(QChar ch, Qt::CaseSensitivity cs) const const
bool isEmpty() const const
QString number(double n, char format, int precision)
QString & replace(QChar before, QChar after, Qt::CaseSensitivity cs)
QString simplified() const const
bool startsWith(QChar c, Qt::CaseSensitivity cs) const const
QString toLower() const const
bool contains(QLatin1StringView str, Qt::CaseSensitivity cs) const const
KeepAspectRatio
CaseInsensitive
SmoothTransformation
QUrl fromLocalFile(const QString &localFile)
QList< QUrl > fromStringList(const QStringList &urls, ParsingMode mode)
bool isLocalFile() const const
bool isValid() const const
QString toLocalFile() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2025 The KDE developers.
Generated on Fri May 2 2025 11:59:36 by doxygen 1.13.2 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.