File indexing completed on 2024-05-05 05:38:29

0001 /*
0002     SPDX-FileCopyrightText: 2008 Dmitry Suzdalev <dimsuz@gmail.com>
0003     SPDX-FileCopyrightText: 2017 David Edmundson <davidedmundson@kde.org>
0004     SPDX-FileCopyrightText: 2018-2019 Kai Uwe Broulik <kde@privat.broulik.de>
0005 
0006     SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
0007 */
0008 
0009 #include "notification.h"
0010 #include "notification_p.h"
0011 
0012 #include <QDBusArgument>
0013 #include <QDebug>
0014 #include <QImageReader>
0015 #include <QRegularExpression>
0016 #include <QXmlStreamReader>
0017 
0018 #include <KApplicationTrader>
0019 #include <KConfig>
0020 #include <KConfigGroup>
0021 #include <KService>
0022 
0023 #include "debug.h"
0024 
0025 using namespace NotificationManager;
0026 using namespace Qt::StringLiterals;
0027 
0028 Notification::Private::Private()
0029 {
0030 }
0031 
0032 Notification::Private::~Private() = default;
0033 
0034 QString Notification::Private::sanitize(const QString &text)
0035 {
0036     // replace all \ns with <br/>
0037     QString t = text;
0038 
0039     t.replace(QLatin1String("\n"), QStringLiteral("<br/>"));
0040     // Now remove all inner whitespace (\ns are already <br/>s)
0041     t = std::move(t).simplified();
0042     // Finally, check if we don't have multiple <br/>s following,
0043     // can happen for example when "\n       \n" is sent, this replaces
0044     // all <br/>s in succession with just one
0045     static const QRegularExpression brExpr(QStringLiteral("<br/>\\s*<br/>(\\s|<br/>)*"));
0046     t.replace(brExpr, QLatin1String("<br/>"));
0047     // This fancy RegExp escapes every occurrence of & since QtQuick Text will blatantly cut off
0048     // text where it finds a stray ampersand.
0049     // Only &{apos, quot, gt, lt, amp}; as well as &#123 character references will be allowed
0050     static const QRegularExpression escapeExpr(QStringLiteral("&(?!(?:apos|quot|[gl]t|amp);|#)"));
0051     t.replace(escapeExpr, QLatin1String("&amp;"));
0052 
0053     // Don't bother adding some HTML structure if the body is now empty
0054     if (t.isEmpty()) {
0055         return t;
0056     }
0057 
0058     QXmlStreamReader r(QStringLiteral("<html>") + t + QStringLiteral("</html>"));
0059     QString result;
0060     QXmlStreamWriter out(&result);
0061 
0062     const QList<QString> allowedTags = {"b", "i", "u", "img", "a", "html", "br", "table", "tr", "td"};
0063 
0064     out.writeStartDocument();
0065     while (!r.atEnd()) {
0066         r.readNext();
0067 
0068         if (r.tokenType() == QXmlStreamReader::StartElement) {
0069             const QString name = r.name().toString();
0070             if (!allowedTags.contains(name)) {
0071                 continue;
0072             }
0073             out.writeStartElement(name);
0074             if (name == QLatin1String("img")) {
0075                 auto src = r.attributes().value("src").toString();
0076                 auto alt = r.attributes().value("alt").toString();
0077 
0078                 const QUrl url(src);
0079                 if (url.isLocalFile()) {
0080                     out.writeAttribute(QStringLiteral("src"), src);
0081                 } else {
0082                     // image denied for security reasons! Do not copy the image src here!
0083                 }
0084 
0085                 out.writeAttribute(QStringLiteral("alt"), alt);
0086             }
0087             if (name == QLatin1Char('a')) {
0088                 out.writeAttribute(QStringLiteral("href"), r.attributes().value("href").toString());
0089             }
0090         }
0091 
0092         if (r.tokenType() == QXmlStreamReader::EndElement) {
0093             const QString name = r.name().toString();
0094             if (!allowedTags.contains(name)) {
0095                 continue;
0096             }
0097             out.writeEndElement();
0098         }
0099 
0100         if (r.tokenType() == QXmlStreamReader::Characters) {
0101             const auto text = r.text().toString();
0102             out.writeCharacters(text); // this auto escapes chars -> HTML entities
0103         }
0104     }
0105     out.writeEndDocument();
0106 
0107     if (r.hasError()) {
0108         qCWarning(NOTIFICATIONMANAGER) << "Notification to send to backend contains invalid XML: " << r.errorString() << "line" << r.lineNumber() << "col"
0109                                        << r.columnNumber();
0110     }
0111 
0112     // The Text.StyledText format handles only html3.2 stuff and &apos; is html4 stuff
0113     // so we need to replace it here otherwise it will not render at all.
0114     result.replace(QLatin1String("&apos;"), QChar('\''));
0115 
0116     return result;
0117 }
0118 
0119 QImage Notification::Private::decodeNotificationSpecImageHint(const QDBusArgument &arg)
0120 {
0121     int width, height, rowStride, hasAlpha, bitsPerSample, channels;
0122     QByteArray pixels;
0123     char *ptr;
0124     char *end;
0125 
0126     if (arg.currentType() != QDBusArgument::StructureType) {
0127         return QImage();
0128     }
0129     arg.beginStructure();
0130     arg >> width >> height >> rowStride >> hasAlpha >> bitsPerSample >> channels >> pixels;
0131     arg.endStructure();
0132 
0133 #define SANITY_CHECK(condition)                                                                                                                                \
0134     if (!(condition)) {                                                                                                                                        \
0135         qCWarning(NOTIFICATIONMANAGER) << "Image decoding sanity check failed on" << #condition;                                                               \
0136         return QImage();                                                                                                                                       \
0137     }
0138 
0139     SANITY_CHECK(width > 0);
0140     SANITY_CHECK(width < 2048);
0141     SANITY_CHECK(height > 0);
0142     SANITY_CHECK(height < 2048);
0143     SANITY_CHECK(rowStride > 0);
0144 
0145 #undef SANITY_CHECK
0146 
0147     auto copyLineRGB32 = [](QRgb *dst, const char *src, int width) {
0148         const char *end = src + width * 3;
0149         for (; src != end; ++dst, src += 3) {
0150             *dst = qRgb(src[0], src[1], src[2]);
0151         }
0152     };
0153 
0154     auto copyLineARGB32 = [](QRgb *dst, const char *src, int width) {
0155         const char *end = src + width * 4;
0156         for (; src != end; ++dst, src += 4) {
0157             *dst = qRgba(src[0], src[1], src[2], src[3]);
0158         }
0159     };
0160 
0161     QImage::Format format = QImage::Format_Invalid;
0162     void (*fcn)(QRgb *, const char *, int) = nullptr;
0163     if (bitsPerSample == 8) {
0164         if (channels == 4) {
0165             format = QImage::Format_ARGB32;
0166             fcn = copyLineARGB32;
0167         } else if (channels == 3) {
0168             format = QImage::Format_RGB32;
0169             fcn = copyLineRGB32;
0170         }
0171     }
0172     if (format == QImage::Format_Invalid) {
0173         qCWarning(NOTIFICATIONMANAGER) << "Unsupported image format (hasAlpha:" << hasAlpha << "bitsPerSample:" << bitsPerSample << "channels:" << channels
0174                                        << ")";
0175         return QImage();
0176     }
0177 
0178     QImage image(width, height, format);
0179     ptr = pixels.data();
0180     end = ptr + pixels.length();
0181     for (int y = 0; y < height; ++y, ptr += rowStride) {
0182         if (ptr + channels * width > end) {
0183             qCWarning(NOTIFICATIONMANAGER) << "Image data is incomplete. y:" << y << "height:" << height;
0184             break;
0185         }
0186         fcn((QRgb *)image.scanLine(y), ptr, width);
0187     }
0188 
0189     return image;
0190 }
0191 
0192 void Notification::Private::sanitizeImage(QImage &image)
0193 {
0194     if (image.isNull()) {
0195         return;
0196     }
0197 
0198     const QSize max = maximumImageSize();
0199     if (image.size().width() > max.width() || image.size().height() > max.height()) {
0200         image = image.scaled(max, Qt::KeepAspectRatio, Qt::SmoothTransformation);
0201     }
0202 }
0203 
0204 void Notification::Private::loadImagePath(const QString &path)
0205 {
0206     // image_path and appIcon should either be a URL with file scheme or the name of a themed icon.
0207     // We're lenient and also allow local paths.
0208 
0209     image = QImage(); // clear
0210     icon.clear();
0211 
0212     QUrl imageUrl;
0213     if (path.startsWith(QLatin1Char('/'))) {
0214         imageUrl = QUrl::fromLocalFile(path);
0215     } else if (path.contains(QLatin1Char('/'))) { // bad heuristic to detect a URL
0216         imageUrl = QUrl(path);
0217 
0218         if (!imageUrl.isLocalFile()) {
0219             qCDebug(NOTIFICATIONMANAGER) << "Refused to load image from" << path << "which isn't a valid local location.";
0220             return;
0221         }
0222     }
0223 
0224     if (!imageUrl.isValid()) {
0225         // try icon path instead;
0226         icon = path;
0227         return;
0228     }
0229 
0230     QImageReader reader(imageUrl.toLocalFile());
0231     reader.setAutoTransform(true);
0232 
0233     const QSize imageSize = reader.size();
0234     if (imageSize.isValid() && (imageSize.width() > maximumImageSize().width() || imageSize.height() > maximumImageSize().height())) {
0235         const QSize thumbnailSize = imageSize.scaled(maximumImageSize(), Qt::KeepAspectRatio);
0236         reader.setScaledSize(thumbnailSize);
0237     }
0238 
0239     image = reader.read();
0240 }
0241 
0242 QString Notification::Private::defaultComponentName()
0243 {
0244     // NOTE Keep in sync with KNotification
0245     return QStringLiteral("plasma_workspace");
0246 }
0247 
0248 QSize Notification::Private::maximumImageSize()
0249 {
0250     return QSize(256, 256);
0251 }
0252 
0253 KService::Ptr Notification::Private::serviceForDesktopEntry(const QString &desktopEntry)
0254 {
0255     if (desktopEntry.isEmpty()) {
0256         return {};
0257     }
0258 
0259     KService::Ptr service;
0260 
0261     if (desktopEntry.startsWith(QLatin1Char('/'))) {
0262         service = KService::serviceByDesktopPath(desktopEntry);
0263     } else {
0264         service = KService::serviceByDesktopName(desktopEntry);
0265     }
0266 
0267     if (!service) {
0268         const QString lowerDesktopEntry = desktopEntry.toLower();
0269         service = KService::serviceByDesktopName(lowerDesktopEntry);
0270     }
0271 
0272     // Try if it's a renamed flatpak
0273     if (!service) {
0274         const QString desktopId = desktopEntry + QLatin1String(".desktop");
0275 
0276         const auto services = KApplicationTrader::query([&desktopId](const KService::Ptr &app) -> bool {
0277             const QStringList renamedFrom = app->property<QStringList>(QStringLiteral("X-Flatpak-RenamedFrom"));
0278             return renamedFrom.contains(desktopId);
0279         });
0280 
0281         if (!services.isEmpty()) {
0282             service = services.first();
0283         }
0284     }
0285 
0286     // Try snap instance name.
0287     if (!service) {
0288         const auto services = KApplicationTrader::query([&desktopEntry](const KService::Ptr &app) -> bool {
0289             const QString snapInstanceName = app->property<QString>(QStringLiteral("X-SnapInstanceName"));
0290             return desktopEntry.compare(snapInstanceName, Qt::CaseInsensitive) == 0;
0291         });
0292 
0293         if (!services.isEmpty()) {
0294             service = services.first();
0295         }
0296     }
0297 
0298     return service;
0299 }
0300 
0301 void Notification::Private::setDesktopEntry(const QString &desktopEntry)
0302 {
0303     QString serviceName;
0304 
0305     configurableService = false;
0306 
0307     KService::Ptr service = serviceForDesktopEntry(desktopEntry);
0308     if (service) {
0309         this->desktopEntry = service->desktopEntryName();
0310         serviceName = service->name();
0311         applicationIconName = service->icon();
0312         configurableService = !service->noDisplay();
0313     }
0314 
0315     const bool isDefaultEvent = (notifyRcName == defaultComponentName());
0316     configurableNotifyRc = false;
0317     if (!notifyRcName.isEmpty()) {
0318         // Check whether the application actually has notifications we can configure
0319         KConfig config(notifyRcName + QStringLiteral(".notifyrc"), KConfig::NoGlobals);
0320 
0321         QStringList configSources =
0322             QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, QStringLiteral("knotifications6/%1.notifyrc").arg(notifyRcName));
0323         // Keep compatibility with KF5 applications
0324         if (configSources.isEmpty()) {
0325             configSources = QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, QStringLiteral("knotifications5/%1.notifyrc").arg(notifyRcName));
0326         }
0327         config.addConfigSources(configSources);
0328 
0329         KConfigGroup globalGroup(&config, u"Global"_s);
0330 
0331         const QString iconName = globalGroup.readEntry("IconName");
0332 
0333         // also only overwrite application icon name for non-default events (or if we don't have a service icon)
0334         if (!iconName.isEmpty() && (!isDefaultEvent || applicationIconName.isEmpty())) {
0335             applicationIconName = iconName;
0336         }
0337 
0338         const QRegularExpression regexp(QStringLiteral("^Event/([^/]*)$"));
0339         configurableNotifyRc = !config.groupList().filter(regexp).isEmpty();
0340     }
0341 
0342     // For default events we try to show the application name from the desktop entry if possible
0343     // This will have us show e.g. "Dr Konqi" instead of generic "Plasma Desktop"
0344     // The application may not send an applicationName. Use the name from the desktop entry then
0345     if ((isDefaultEvent || applicationName.isEmpty()) && !serviceName.isEmpty()) {
0346         applicationName = serviceName;
0347     }
0348 }
0349 
0350 void Notification::Private::processHints(const QVariantMap &hints)
0351 {
0352     auto end = hints.end();
0353 
0354     notifyRcName = hints.value(QStringLiteral("x-kde-appname")).toString();
0355 
0356     setDesktopEntry(hints.value(QStringLiteral("desktop-entry")).toString());
0357 
0358     // Special override for KDE Connect since the notification is sent by kdeconnectd
0359     // but actually comes from a different app on the phone
0360     const QString applicationDisplayName = hints.value(QStringLiteral("x-kde-display-appname")).toString();
0361     if (!applicationDisplayName.isEmpty()) {
0362         applicationName = applicationDisplayName;
0363     }
0364 
0365     originName = hints.value(QStringLiteral("x-kde-origin-name")).toString();
0366 
0367     eventId = hints.value(QStringLiteral("x-kde-eventId")).toString();
0368     xdgTokenAppId = hints.value(QStringLiteral("x-kde-xdgTokenAppId")).toString();
0369 
0370     bool ok;
0371     const int urgency = hints.value(QStringLiteral("urgency")).toInt(&ok); // DBus type is actually "byte"
0372     if (ok) {
0373         // FIXME use separate enum again
0374         switch (urgency) {
0375         case 0:
0376             setUrgency(Notifications::LowUrgency);
0377             break;
0378         case 1:
0379             setUrgency(Notifications::NormalUrgency);
0380             break;
0381         case 2:
0382             setUrgency(Notifications::CriticalUrgency);
0383             break;
0384         }
0385     }
0386 
0387     resident = hints.value(QStringLiteral("resident")).toBool();
0388     transient = hints.value(QStringLiteral("transient")).toBool();
0389 
0390     userActionFeedback = hints.value(QStringLiteral("x-kde-user-action-feedback")).toBool();
0391     if (userActionFeedback) {
0392         // A confirmation of an explicit user interaction is assumed to have been seen by the user.
0393         read = true;
0394     }
0395 
0396     urls = QUrl::fromStringList(hints.value(QStringLiteral("x-kde-urls")).toStringList());
0397 
0398     replyPlaceholderText = hints.value(QStringLiteral("x-kde-reply-placeholder-text")).toString();
0399     replySubmitButtonText = hints.value(QStringLiteral("x-kde-reply-submit-button-text")).toString();
0400     replySubmitButtonIconName = hints.value(QStringLiteral("x-kde-reply-submit-button-icon-name")).toString();
0401 
0402     category = hints.value(QStringLiteral("category")).toString();
0403 
0404     // Underscored hints was in use in version 1.1 of the spec but has been
0405     // replaced by dashed hints in version 1.2. We need to support it for
0406     // users of the 1.2 version of the spec.
0407     auto it = hints.find(QStringLiteral("image-data"));
0408     if (it == end) {
0409         it = hints.find(QStringLiteral("image_data"));
0410     }
0411     if (it == end) {
0412         // This hint was in use in version 1.0 of the spec but has been
0413         // replaced by "image_data" in version 1.1. We need to support it for
0414         // users of the 1.0 version of the spec.
0415         it = hints.find(QStringLiteral("icon_data"));
0416     }
0417 
0418     if (it != end) {
0419         image = decodeNotificationSpecImageHint(it->value<QDBusArgument>());
0420     }
0421 
0422     if (image.isNull()) {
0423         it = hints.find(QStringLiteral("image-path"));
0424         if (it == end) {
0425             it = hints.find(QStringLiteral("image_path"));
0426         }
0427 
0428         if (it != end) {
0429             loadImagePath(it->toString());
0430         }
0431     }
0432 
0433     sanitizeImage(image);
0434 }
0435 
0436 void Notification::Private::setUrgency(Notifications::Urgency urgency)
0437 {
0438     this->urgency = urgency;
0439 
0440     // Critical notifications must not time out
0441     // TODO should we really imply this here and not on the view side?
0442     // are there usecases for critical but can expire?
0443     // "critical updates available"?
0444     if (urgency == Notifications::CriticalUrgency) {
0445         timeout = 0;
0446     }
0447 }
0448 
0449 Notification::Notification(uint id)
0450     : d(new Private())
0451 {
0452     d->id = id;
0453     d->created = QDateTime::currentDateTimeUtc();
0454 }
0455 
0456 Notification::Notification(const Notification &other)
0457     : d(new Private(*other.d))
0458 {
0459 }
0460 
0461 Notification::Notification(Notification &&other) noexcept
0462     : d(other.d)
0463 {
0464     other.d = nullptr;
0465 }
0466 
0467 Notification &Notification::operator=(const Notification &other)
0468 {
0469     *d = *other.d;
0470     return *this;
0471 }
0472 
0473 Notification &Notification::operator=(Notification &&other) noexcept
0474 {
0475     d = other.d;
0476     other.d = nullptr;
0477     return *this;
0478 }
0479 
0480 Notification::~Notification()
0481 {
0482     delete d;
0483 }
0484 
0485 uint Notification::id() const
0486 {
0487     return d->id;
0488 }
0489 
0490 QString Notification::dBusService() const
0491 {
0492     return d->dBusService;
0493 }
0494 
0495 void Notification::setDBusService(const QString &dBusService)
0496 {
0497     d->dBusService = dBusService;
0498 }
0499 
0500 QDateTime Notification::created() const
0501 {
0502     return d->created;
0503 }
0504 
0505 void Notification::setCreated(const QDateTime &created)
0506 {
0507     d->created = created;
0508 }
0509 
0510 QDateTime Notification::updated() const
0511 {
0512     return d->updated;
0513 }
0514 
0515 void Notification::resetUpdated()
0516 {
0517     d->updated = QDateTime::currentDateTimeUtc();
0518 }
0519 
0520 bool Notification::read() const
0521 {
0522     return d->read;
0523 }
0524 
0525 void Notification::setRead(bool read)
0526 {
0527     d->read = read;
0528 }
0529 
0530 QString Notification::summary() const
0531 {
0532     return d->summary;
0533 }
0534 
0535 void Notification::setSummary(const QString &summary)
0536 {
0537     d->summary = summary;
0538 }
0539 
0540 QString Notification::body() const
0541 {
0542     return d->body;
0543 }
0544 
0545 void Notification::setBody(const QString &body)
0546 {
0547     d->rawBody = body;
0548     d->body = Private::sanitize(body.trimmed());
0549 }
0550 
0551 QString Notification::rawBody() const
0552 {
0553     return d->rawBody;
0554 }
0555 
0556 QString Notification::icon() const
0557 {
0558     return d->icon;
0559 }
0560 
0561 void Notification::setIcon(const QString &icon)
0562 {
0563     d->loadImagePath(icon);
0564     Private::sanitizeImage(d->image);
0565 }
0566 
0567 QImage Notification::image() const
0568 {
0569     return d->image;
0570 }
0571 
0572 void Notification::setImage(const QImage &image)
0573 {
0574     d->image = image;
0575 }
0576 
0577 QString Notification::desktopEntry() const
0578 {
0579     return d->desktopEntry;
0580 }
0581 
0582 void Notification::setDesktopEntry(const QString &desktopEntry)
0583 {
0584     d->setDesktopEntry(desktopEntry);
0585 }
0586 
0587 QString Notification::notifyRcName() const
0588 {
0589     return d->notifyRcName;
0590 }
0591 
0592 QString Notification::eventId() const
0593 {
0594     return d->eventId;
0595 }
0596 
0597 QString Notification::applicationName() const
0598 {
0599     return d->applicationName;
0600 }
0601 
0602 void Notification::setApplicationName(const QString &applicationName)
0603 {
0604     d->applicationName = applicationName;
0605 }
0606 
0607 QString Notification::applicationIconName() const
0608 {
0609     return d->applicationIconName;
0610 }
0611 
0612 void Notification::setApplicationIconName(const QString &applicationIconName)
0613 {
0614     d->applicationIconName = applicationIconName;
0615 }
0616 
0617 QString Notification::originName() const
0618 {
0619     return d->originName;
0620 }
0621 
0622 QStringList Notification::actionNames() const
0623 {
0624     return d->actionNames;
0625 }
0626 
0627 QStringList Notification::actionLabels() const
0628 {
0629     return d->actionLabels;
0630 }
0631 
0632 bool Notification::hasDefaultAction() const
0633 {
0634     return d->hasDefaultAction;
0635 }
0636 
0637 QString Notification::defaultActionLabel() const
0638 {
0639     return d->defaultActionLabel;
0640 }
0641 
0642 void Notification::setActions(const QStringList &actions)
0643 {
0644     if (actions.count() % 2 != 0) {
0645         qCWarning(NOTIFICATIONMANAGER) << "List of actions must contain an even number of items, tried to set actions to" << actions;
0646         return;
0647     }
0648 
0649     d->hasDefaultAction = false;
0650     d->hasConfigureAction = false;
0651     d->hasReplyAction = false;
0652 
0653     QStringList names;
0654     QStringList labels;
0655 
0656     for (int i = 0; i < actions.count(); i += 2) {
0657         const QString &name = actions.at(i);
0658         const QString &label = actions.at(i + 1);
0659 
0660         if (!d->hasDefaultAction && name == QLatin1String("default")) {
0661             d->hasDefaultAction = true;
0662             d->defaultActionLabel = label;
0663             continue;
0664         }
0665 
0666         if (!d->hasConfigureAction && name == QLatin1String("settings")) {
0667             d->hasConfigureAction = true;
0668             d->configureActionLabel = label;
0669             continue;
0670         }
0671 
0672         if (!d->hasReplyAction && name == QLatin1String("inline-reply")) {
0673             d->hasReplyAction = true;
0674             d->replyActionLabel = label;
0675             continue;
0676         }
0677 
0678         names << name;
0679         labels << label;
0680     }
0681 
0682     d->actionNames = names;
0683     d->actionLabels = labels;
0684 }
0685 
0686 QList<QUrl> Notification::urls() const
0687 {
0688     return d->urls;
0689 }
0690 
0691 void Notification::setUrls(const QList<QUrl> &urls)
0692 {
0693     d->urls = urls;
0694 }
0695 
0696 Notifications::Urgency Notification::urgency() const
0697 {
0698     return d->urgency;
0699 }
0700 
0701 bool Notification::userActionFeedback() const
0702 {
0703     return d->userActionFeedback;
0704 }
0705 
0706 int Notification::timeout() const
0707 {
0708     return d->timeout;
0709 }
0710 
0711 void Notification::setTimeout(int timeout)
0712 {
0713     d->timeout = timeout;
0714 }
0715 
0716 bool Notification::configurable() const
0717 {
0718     return d->hasConfigureAction || d->configurableNotifyRc || d->configurableService;
0719 }
0720 
0721 QString Notification::configureActionLabel() const
0722 {
0723     return d->configureActionLabel;
0724 }
0725 
0726 bool Notification::hasReplyAction() const
0727 {
0728     return d->hasReplyAction;
0729 }
0730 
0731 QString Notification::replyActionLabel() const
0732 {
0733     return d->replyActionLabel;
0734 }
0735 
0736 QString Notification::replyPlaceholderText() const
0737 {
0738     return d->replyPlaceholderText;
0739 }
0740 
0741 QString Notification::replySubmitButtonText() const
0742 {
0743     return d->replySubmitButtonText;
0744 }
0745 
0746 QString Notification::replySubmitButtonIconName() const
0747 {
0748     return d->replySubmitButtonIconName;
0749 }
0750 
0751 QString Notification::category() const
0752 {
0753     return d->category;
0754 }
0755 
0756 bool Notification::expired() const
0757 {
0758     return d->expired;
0759 }
0760 
0761 void Notification::setExpired(bool expired)
0762 {
0763     d->expired = expired;
0764 }
0765 
0766 bool Notification::dismissed() const
0767 {
0768     return d->dismissed;
0769 }
0770 
0771 void Notification::setDismissed(bool dismissed)
0772 {
0773     d->dismissed = dismissed;
0774 }
0775 
0776 bool Notification::resident() const
0777 {
0778     return d->resident;
0779 }
0780 
0781 void Notification::setResident(bool resident)
0782 {
0783     d->resident = resident;
0784 }
0785 
0786 bool Notification::transient() const
0787 {
0788     return d->transient;
0789 }
0790 
0791 void Notification::setTransient(bool transient)
0792 {
0793     d->transient = transient;
0794 }
0795 
0796 QVariantMap Notification::hints() const
0797 {
0798     return d->hints;
0799 }
0800 
0801 void Notification::setHints(const QVariantMap &hints)
0802 {
0803     d->hints = hints;
0804 }
0805 
0806 void Notification::processHints(const QVariantMap &hints)
0807 {
0808     d->processHints(hints);
0809 }