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

KDE's Doxygen guidelines are available online.