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 { character references will be allowed 0049 static const QRegularExpression escapeExpr(QStringLiteral("&(?!(?:apos|quot|[gl]t|amp);|#)")); 0050 t.replace(escapeExpr, QLatin1String("&")); 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 ' is html4 stuff 0112 // so we need to replace it here otherwise it will not render at all. 0113 result.replace(QLatin1String("'"), 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 }