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 this->hints = hints;
458}
459
460void Notification::Private::setUrgency(Notifications::Urgency urgency)
461{
462 this->urgency = urgency;
463
464 // Critical notifications must not time out
465 // TODO should we really imply this here and not on the view side?
466 // are there usecases for critical but can expire?
467 // "critical updates available"?
468 if (urgency == Notifications::CriticalUrgency) {
469 timeout = 0;
470 }
471}
472
473Notification::Notification(uint id)
474 : d(new Private())
475{
476 d->id = id;
477 d->created = QDateTime::currentDateTimeUtc();
478}
479
480Notification::Notification(const Notification &other)
481 : d(new Private(*other.d))
482{
483}
484
485Notification::Notification(Notification &&other) noexcept
486 : d(other.d)
487{
488 other.d = nullptr;
489}
490
491Notification &Notification::operator=(const Notification &other)
492{
493 *d = *other.d;
494 return *this;
495}
496
497Notification &Notification::operator=(Notification &&other) noexcept
498{
499 d = other.d;
500 other.d = nullptr;
501 return *this;
502}
503
504Notification::~Notification()
505{
506 delete d;
507}
508
509uint Notification::id() const
510{
511 return d->id;
512}
513
514QString Notification::dBusService() const
515{
516 return d->dBusService;
517}
518
519void Notification::setDBusService(const QString &dBusService)
520{
521 d->dBusService = dBusService;
522}
523
524QDateTime Notification::created() const
525{
526 return d->created;
527}
528
529void Notification::setCreated(const QDateTime &created)
530{
531 d->created = created;
532}
533
534QDateTime Notification::updated() const
535{
536 return d->updated;
537}
538
539void Notification::resetUpdated()
540{
541 d->updated = QDateTime::currentDateTimeUtc();
542}
543
544bool Notification::read() const
545{
546 return d->read;
547}
548
549void Notification::setRead(bool read)
550{
551 d->read = read;
552}
553
554QString Notification::summary() const
555{
556 return d->summary;
557}
558
559void Notification::setSummary(const QString &summary)
560{
561 d->summary = summary;
562}
563
564QString Notification::body() const
565{
566 return d->body;
567}
568
569void Notification::setBody(const QString &body)
570{
571 d->rawBody = body;
572 d->body = Private::sanitize(body.trimmed());
573}
574
575QString Notification::rawBody() const
576{
577 return d->rawBody;
578}
579
580QString Notification::icon() const
581{
582 return d->icon;
583}
584
585void Notification::setIcon(const QString &icon)
586{
587 d->loadImagePath(icon);
588}
589
590QImage Notification::image() const
591{
592 if (d->s_imageCache.contains(d->id)) {
593 return *d->s_imageCache.object(d->id);
594 }
595 return QImage();
596}
597
598void Notification::setImage(const QImage &image)
599{
600 d->s_imageCache.insert(d->id, new QImage(image));
601}
602
603QString Notification::desktopEntry() const
604{
605 return d->desktopEntry;
606}
607
608void Notification::setDesktopEntry(const QString &desktopEntry)
609{
610 d->setDesktopEntry(desktopEntry);
611}
612
613QString Notification::notifyRcName() const
614{
615 return d->notifyRcName;
616}
617
618QString Notification::eventId() const
619{
620 return d->eventId;
621}
622
623QString Notification::applicationName() const
624{
625 return d->applicationName;
626}
627
628void Notification::setApplicationName(const QString &applicationName)
629{
630 d->applicationName = applicationName;
631}
632
633QString Notification::applicationIconName() const
634{
635 return d->applicationIconName;
636}
637
638void Notification::setApplicationIconName(const QString &applicationIconName)
639{
640 d->applicationIconName = applicationIconName;
641}
642
643QString Notification::originName() const
644{
645 return d->originName;
646}
647
648QStringList Notification::actionNames() const
649{
650 return d->actionNames;
651}
652
653QStringList Notification::actionLabels() const
654{
655 return d->actionLabels;
656}
657
658bool Notification::hasDefaultAction() const
659{
660 return d->hasDefaultAction;
661}
662
663QString Notification::defaultActionLabel() const
664{
665 return d->defaultActionLabel;
666}
667
668void Notification::setActions(const QStringList &actions)
669{
670 if (actions.count() % 2 != 0) {
671 qCWarning(NOTIFICATIONMANAGER) << "List of actions must contain an even number of items, tried to set actions to" << actions;
672 return;
673 }
674
675 d->hasDefaultAction = false;
676 d->hasConfigureAction = false;
677 d->hasReplyAction = false;
678
679 QStringList names;
680 QStringList labels;
681
682 for (int i = 0; i < actions.count(); i += 2) {
683 const QString &name = actions.at(i);
684 const QString &label = actions.at(i + 1);
685
686 if (!d->hasDefaultAction && name == QLatin1String("default")) {
687 d->hasDefaultAction = true;
688 d->defaultActionLabel = label;
689 continue;
690 }
691
692 if (!d->hasConfigureAction && name == QLatin1String("settings")) {
693 d->hasConfigureAction = true;
694 d->configureActionLabel = label;
695 continue;
696 }
697
698 if (!d->hasReplyAction && name == QLatin1String("inline-reply")) {
699 d->hasReplyAction = true;
700 d->replyActionLabel = label;
701 continue;
702 }
703
704 names << name;
705 labels << label;
706 }
707
708 d->actionNames = names;
709 d->actionLabels = labels;
710}
711
712QList<QUrl> Notification::urls() const
713{
714 return d->urls;
715}
716
717void Notification::setUrls(const QList<QUrl> &urls)
718{
719 d->urls = urls;
720}
721
722Notifications::Urgency Notification::urgency() const
723{
724 return d->urgency;
725}
726
727bool Notification::userActionFeedback() const
728{
729 return d->userActionFeedback;
730}
731
732int Notification::timeout() const
733{
734 return d->timeout;
735}
736
737void Notification::setTimeout(int timeout)
738{
739 d->timeout = timeout;
740}
741
742bool Notification::configurable() const
743{
744 return d->hasConfigureAction || d->configurableNotifyRc || d->configurableService;
745}
746
747QString Notification::configureActionLabel() const
748{
749 return d->configureActionLabel;
750}
751
752bool Notification::hasReplyAction() const
753{
754 return d->hasReplyAction;
755}
756
757QString Notification::replyActionLabel() const
758{
759 return d->replyActionLabel;
760}
761
762QString Notification::replyPlaceholderText() const
763{
764 return d->replyPlaceholderText;
765}
766
767QString Notification::replySubmitButtonText() const
768{
769 return d->replySubmitButtonText;
770}
771
772QString Notification::replySubmitButtonIconName() const
773{
774 return d->replySubmitButtonIconName;
775}
776
777QString Notification::category() const
778{
779 return d->category;
780}
781
782bool Notification::expired() const
783{
784 return d->expired;
785}
786
787void Notification::setExpired(bool expired)
788{
789 d->expired = expired;
790}
791
792bool Notification::dismissed() const
793{
794 return d->dismissed;
795}
796
797void Notification::setDismissed(bool dismissed)
798{
799 d->dismissed = dismissed;
800}
801
802bool Notification::resident() const
803{
804 return d->resident;
805}
806
807void Notification::setResident(bool resident)
808{
809 d->resident = resident;
810}
811
812bool Notification::transient() const
813{
814 return d->transient;
815}
816
817void Notification::setTransient(bool transient)
818{
819 d->transient = transient;
820}
821
822QVariantMap Notification::hints() const
823{
824 return d->hints;
825}
826
827void Notification::setHints(const QVariantMap &hints)
828{
829 d->hints = hints;
830}
831
832void Notification::processHints(const QVariantMap &hints)
833{
834 d->processHints(hints);
835}
836
837bool Notification::wasAddedDuringInhibition() const
838{
839 return d->wasAddedDuringInhibition;
840}
841
842void Notification::setWasAddedDuringInhibition(bool value)
843{
844 d->wasAddedDuringInhibition = value;
845}
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 Mar 28 2025 11:53:53 by doxygen 1.13.2 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.