File indexing completed on 2024-04-28 16:54:35

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