You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
798 lines
21 KiB
798 lines
21 KiB
/* |
|
SPDX-FileCopyrightText: 2008 Dmitry Suzdalev <dimsuz@gmail.com> |
|
SPDX-FileCopyrightText: 2017 David Edmundson <davidedmundson@kde.org> |
|
SPDX-FileCopyrightText: 2018-2019 Kai Uwe Broulik <kde@privat.broulik.de> |
|
|
|
SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL |
|
*/ |
|
|
|
#include "notification.h" |
|
#include "notification_p.h" |
|
|
|
#include <QDBusArgument> |
|
#include <QDebug> |
|
#include <QImageReader> |
|
#include <QRegularExpression> |
|
#include <QXmlStreamReader> |
|
|
|
#include <KApplicationTrader> |
|
#include <KConfig> |
|
#include <KConfigGroup> |
|
#include <KService> |
|
|
|
#include "debug.h" |
|
|
|
using namespace NotificationManager; |
|
|
|
Notification::Private::Private() |
|
{ |
|
} |
|
|
|
Notification::Private::~Private() = default; |
|
|
|
QString Notification::Private::sanitize(const QString &text) |
|
{ |
|
// replace all \ns with <br/> |
|
QString t = text; |
|
|
|
t.replace(QLatin1String("\n"), QStringLiteral("<br/>")); |
|
// Now remove all inner whitespace (\ns are already <br/>s) |
|
t = t.simplified(); |
|
// Finally, check if we don't have multiple <br/>s following, |
|
// can happen for example when "\n \n" is sent, this replaces |
|
// all <br/>s in succession with just one |
|
static const QRegularExpression brExpr(QStringLiteral("<br/>\\s*<br/>(\\s|<br/>)*")); |
|
t.replace(brExpr, QLatin1String("<br/>")); |
|
// This fancy RegExp escapes every occurrence of & since QtQuick Text will blatantly cut off |
|
// text where it finds a stray ampersand. |
|
// Only &{apos, quot, gt, lt, amp}; as well as { character references will be allowed |
|
static const QRegularExpression escapeExpr(QStringLiteral("&(?!(?:apos|quot|[gl]t|amp);|#)")); |
|
t.replace(escapeExpr, QLatin1String("&")); |
|
|
|
// Don't bother adding some HTML structure if the body is now empty |
|
if (t.isEmpty()) { |
|
return t; |
|
} |
|
|
|
QXmlStreamReader r(QStringLiteral("<html>") + t + QStringLiteral("</html>")); |
|
QString result; |
|
QXmlStreamWriter out(&result); |
|
|
|
const QVector<QString> allowedTags = {"b", "i", "u", "img", "a", "html", "br", "table", "tr", "td"}; |
|
|
|
out.writeStartDocument(); |
|
while (!r.atEnd()) { |
|
r.readNext(); |
|
|
|
if (r.tokenType() == QXmlStreamReader::StartElement) { |
|
const QString name = r.name().toString(); |
|
if (!allowedTags.contains(name)) { |
|
continue; |
|
} |
|
out.writeStartElement(name); |
|
if (name == QLatin1String("img")) { |
|
auto src = r.attributes().value("src").toString(); |
|
auto alt = r.attributes().value("alt").toString(); |
|
|
|
const QUrl url(src); |
|
if (url.isLocalFile()) { |
|
out.writeAttribute(QStringLiteral("src"), src); |
|
} else { |
|
// image denied for security reasons! Do not copy the image src here! |
|
} |
|
|
|
out.writeAttribute(QStringLiteral("alt"), alt); |
|
} |
|
if (name == QLatin1Char('a')) { |
|
out.writeAttribute(QStringLiteral("href"), r.attributes().value("href").toString()); |
|
} |
|
} |
|
|
|
if (r.tokenType() == QXmlStreamReader::EndElement) { |
|
const QString name = r.name().toString(); |
|
if (!allowedTags.contains(name)) { |
|
continue; |
|
} |
|
out.writeEndElement(); |
|
} |
|
|
|
if (r.tokenType() == QXmlStreamReader::Characters) { |
|
const auto text = r.text().toString(); |
|
out.writeCharacters(text); // this auto escapes chars -> HTML entities |
|
} |
|
} |
|
out.writeEndDocument(); |
|
|
|
if (r.hasError()) { |
|
qCWarning(NOTIFICATIONMANAGER) << "Notification to send to backend contains invalid XML: " << r.errorString() << "line" << r.lineNumber() << "col" |
|
<< r.columnNumber(); |
|
} |
|
|
|
// The Text.StyledText format handles only html3.2 stuff and ' is html4 stuff |
|
// so we need to replace it here otherwise it will not render at all. |
|
result.replace(QLatin1String("'"), QChar('\'')); |
|
|
|
return result; |
|
} |
|
|
|
QImage Notification::Private::decodeNotificationSpecImageHint(const QDBusArgument &arg) |
|
{ |
|
int width, height, rowStride, hasAlpha, bitsPerSample, channels; |
|
QByteArray pixels; |
|
char *ptr; |
|
char *end; |
|
|
|
arg.beginStructure(); |
|
arg >> width >> height >> rowStride >> hasAlpha >> bitsPerSample >> channels >> pixels; |
|
arg.endStructure(); |
|
|
|
#define SANITY_CHECK(condition) \ |
|
if (!(condition)) { \ |
|
qCWarning(NOTIFICATIONMANAGER) << "Image decoding sanity check failed on" << #condition; \ |
|
return QImage(); \ |
|
} |
|
|
|
SANITY_CHECK(width > 0); |
|
SANITY_CHECK(width < 2048); |
|
SANITY_CHECK(height > 0); |
|
SANITY_CHECK(height < 2048); |
|
SANITY_CHECK(rowStride > 0); |
|
|
|
#undef SANITY_CHECK |
|
|
|
auto copyLineRGB32 = [](QRgb *dst, const char *src, int width) { |
|
const char *end = src + width * 3; |
|
for (; src != end; ++dst, src += 3) { |
|
*dst = qRgb(src[0], src[1], src[2]); |
|
} |
|
}; |
|
|
|
auto copyLineARGB32 = [](QRgb *dst, const char *src, int width) { |
|
const char *end = src + width * 4; |
|
for (; src != end; ++dst, src += 4) { |
|
*dst = qRgba(src[0], src[1], src[2], src[3]); |
|
} |
|
}; |
|
|
|
QImage::Format format = QImage::Format_Invalid; |
|
void (*fcn)(QRgb *, const char *, int) = nullptr; |
|
if (bitsPerSample == 8) { |
|
if (channels == 4) { |
|
format = QImage::Format_ARGB32; |
|
fcn = copyLineARGB32; |
|
} else if (channels == 3) { |
|
format = QImage::Format_RGB32; |
|
fcn = copyLineRGB32; |
|
} |
|
} |
|
if (format == QImage::Format_Invalid) { |
|
qCWarning(NOTIFICATIONMANAGER) << "Unsupported image format (hasAlpha:" << hasAlpha << "bitsPerSample:" << bitsPerSample << "channels:" << channels |
|
<< ")"; |
|
return QImage(); |
|
} |
|
|
|
QImage image(width, height, format); |
|
ptr = pixels.data(); |
|
end = ptr + pixels.length(); |
|
for (int y = 0; y < height; ++y, ptr += rowStride) { |
|
if (ptr + channels * width > end) { |
|
qCWarning(NOTIFICATIONMANAGER) << "Image data is incomplete. y:" << y << "height:" << height; |
|
break; |
|
} |
|
fcn((QRgb *)image.scanLine(y), ptr, width); |
|
} |
|
|
|
return image; |
|
} |
|
|
|
void Notification::Private::sanitizeImage(QImage &image) |
|
{ |
|
if (image.isNull()) { |
|
return; |
|
} |
|
|
|
const QSize max = maximumImageSize(); |
|
if (image.size().width() > max.width() || image.size().height() > max.height()) { |
|
image = image.scaled(max, Qt::KeepAspectRatio, Qt::SmoothTransformation); |
|
} |
|
} |
|
|
|
void Notification::Private::loadImagePath(const QString &path) |
|
{ |
|
// image_path and appIcon should either be a URL with file scheme or the name of a themed icon. |
|
// We're lenient and also allow local paths. |
|
|
|
image = QImage(); // clear |
|
icon.clear(); |
|
|
|
QUrl imageUrl; |
|
if (path.startsWith(QLatin1Char('/'))) { |
|
imageUrl = QUrl::fromLocalFile(path); |
|
} else if (path.contains(QLatin1Char('/'))) { // bad heuristic to detect a URL |
|
imageUrl = QUrl(path); |
|
|
|
if (!imageUrl.isLocalFile()) { |
|
qCDebug(NOTIFICATIONMANAGER) << "Refused to load image from" << path << "which isn't a valid local location."; |
|
return; |
|
} |
|
} |
|
|
|
if (!imageUrl.isValid()) { |
|
// try icon path instead; |
|
icon = path; |
|
return; |
|
} |
|
|
|
QImageReader reader(imageUrl.toLocalFile()); |
|
reader.setAutoTransform(true); |
|
|
|
const QSize imageSize = reader.size(); |
|
if (imageSize.isValid() && (imageSize.width() > maximumImageSize().width() || imageSize.height() > maximumImageSize().height())) { |
|
const QSize thumbnailSize = imageSize.scaled(maximumImageSize(), Qt::KeepAspectRatio); |
|
reader.setScaledSize(thumbnailSize); |
|
} |
|
|
|
image = reader.read(); |
|
} |
|
|
|
QString Notification::Private::defaultComponentName() |
|
{ |
|
// NOTE Keep in sync with KNotification |
|
return QStringLiteral("plasma_workspace"); |
|
} |
|
|
|
QSize Notification::Private::maximumImageSize() |
|
{ |
|
return QSize(256, 256); |
|
} |
|
|
|
KService::Ptr Notification::Private::serviceForDesktopEntry(const QString &desktopEntry) |
|
{ |
|
if (desktopEntry.isEmpty()) { |
|
return {}; |
|
} |
|
|
|
KService::Ptr service; |
|
|
|
if (desktopEntry.startsWith(QLatin1Char('/'))) { |
|
service = KService::serviceByDesktopPath(desktopEntry); |
|
} else { |
|
service = KService::serviceByDesktopName(desktopEntry); |
|
} |
|
|
|
if (!service) { |
|
const QString lowerDesktopEntry = desktopEntry.toLower(); |
|
service = KService::serviceByDesktopName(lowerDesktopEntry); |
|
} |
|
|
|
// Try if it's a renamed flatpak |
|
if (!service) { |
|
const QString desktopId = desktopEntry + QLatin1String(".desktop"); |
|
|
|
const auto services = KApplicationTrader::query([&desktopId](const KService::Ptr &app) -> bool { |
|
const QString renamedFrom = app->property(QStringLiteral("X-Flatpak-RenamedFrom"), QVariant::String).toString(); |
|
|
|
if (renamedFrom.isEmpty()) { |
|
return false; |
|
} |
|
|
|
const auto names = renamedFrom.split(QChar(';')); |
|
for (const QString &name : names) { |
|
if (name == desktopId) { |
|
return true; |
|
} |
|
} |
|
return false; |
|
}); |
|
|
|
if (!services.isEmpty()) { |
|
service = services.first(); |
|
} |
|
} |
|
|
|
return service; |
|
} |
|
|
|
void Notification::Private::setDesktopEntry(const QString &desktopEntry) |
|
{ |
|
QString serviceName; |
|
|
|
configurableService = false; |
|
|
|
KService::Ptr service = serviceForDesktopEntry(desktopEntry); |
|
if (service) { |
|
this->desktopEntry = service->desktopEntryName(); |
|
serviceName = service->name(); |
|
applicationIconName = service->icon(); |
|
configurableService = !service->noDisplay(); |
|
} |
|
|
|
const bool isDefaultEvent = (notifyRcName == defaultComponentName()); |
|
configurableNotifyRc = false; |
|
if (!notifyRcName.isEmpty()) { |
|
// Check whether the application actually has notifications we can configure |
|
KConfig config(notifyRcName + QStringLiteral(".notifyrc"), KConfig::NoGlobals); |
|
config.addConfigSources( |
|
QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, QStringLiteral("knotifications5/") + notifyRcName + QStringLiteral(".notifyrc"))); |
|
|
|
KConfigGroup globalGroup(&config, "Global"); |
|
|
|
const QString iconName = globalGroup.readEntry("IconName"); |
|
|
|
// also only overwrite application icon name for non-default events (or if we don't have a service icon) |
|
if (!iconName.isEmpty() && (!isDefaultEvent || applicationIconName.isEmpty())) { |
|
applicationIconName = iconName; |
|
} |
|
|
|
const QRegularExpression regexp(QStringLiteral("^Event/([^/]*)$")); |
|
configurableNotifyRc = !config.groupList().filter(regexp).isEmpty(); |
|
} |
|
|
|
// For default events we try to show the application name from the desktop entry if possible |
|
// This will have us show e.g. "Dr Konqi" instead of generic "Plasma Desktop" |
|
// The application may not send an applicationName. Use the name from the desktop entry then |
|
if ((isDefaultEvent || applicationName.isEmpty()) && !serviceName.isEmpty()) { |
|
applicationName = serviceName; |
|
} |
|
} |
|
|
|
void Notification::Private::processHints(const QVariantMap &hints) |
|
{ |
|
auto end = hints.end(); |
|
|
|
notifyRcName = hints.value(QStringLiteral("x-kde-appname")).toString(); |
|
|
|
setDesktopEntry(hints.value(QStringLiteral("desktop-entry")).toString()); |
|
|
|
// Special override for KDE Connect since the notification is sent by kdeconnectd |
|
// but actually comes from a different app on the phone |
|
const QString applicationDisplayName = hints.value(QStringLiteral("x-kde-display-appname")).toString(); |
|
if (!applicationDisplayName.isEmpty()) { |
|
applicationName = applicationDisplayName; |
|
} |
|
|
|
originName = hints.value(QStringLiteral("x-kde-origin-name")).toString(); |
|
|
|
eventId = hints.value(QStringLiteral("x-kde-eventId")).toString(); |
|
xdgTokenAppId = hints.value(QStringLiteral("x-kde-xdgTokenAppId")).toString(); |
|
|
|
bool ok; |
|
const int urgency = hints.value(QStringLiteral("urgency")).toInt(&ok); // DBus type is actually "byte" |
|
if (ok) { |
|
// FIXME use separate enum again |
|
switch (urgency) { |
|
case 0: |
|
setUrgency(Notifications::LowUrgency); |
|
break; |
|
case 1: |
|
setUrgency(Notifications::NormalUrgency); |
|
break; |
|
case 2: |
|
setUrgency(Notifications::CriticalUrgency); |
|
break; |
|
} |
|
} |
|
|
|
resident = hints.value(QStringLiteral("resident")).toBool(); |
|
transient = hints.value(QStringLiteral("transient")).toBool(); |
|
|
|
userActionFeedback = hints.value(QStringLiteral("x-kde-user-action-feedback")).toBool(); |
|
if (userActionFeedback) { |
|
// A confirmation of an explicit user interaction is assumed to have been seen by the user. |
|
read = true; |
|
} |
|
|
|
urls = QUrl::fromStringList(hints.value(QStringLiteral("x-kde-urls")).toStringList()); |
|
|
|
replyPlaceholderText = hints.value(QStringLiteral("x-kde-reply-placeholder-text")).toString(); |
|
replySubmitButtonText = hints.value(QStringLiteral("x-kde-reply-submit-button-text")).toString(); |
|
replySubmitButtonIconName = hints.value(QStringLiteral("x-kde-reply-submit-button-icon-name")).toString(); |
|
|
|
category = hints.value(QStringLiteral("category")).toString(); |
|
|
|
// Underscored hints was in use in version 1.1 of the spec but has been |
|
// replaced by dashed hints in version 1.2. We need to support it for |
|
// users of the 1.2 version of the spec. |
|
auto it = hints.find(QStringLiteral("image-data")); |
|
if (it == end) { |
|
it = hints.find(QStringLiteral("image_data")); |
|
} |
|
if (it == end) { |
|
// This hint was in use in version 1.0 of the spec but has been |
|
// replaced by "image_data" in version 1.1. We need to support it for |
|
// users of the 1.0 version of the spec. |
|
it = hints.find(QStringLiteral("icon_data")); |
|
} |
|
|
|
if (it != end) { |
|
image = decodeNotificationSpecImageHint(it->value<QDBusArgument>()); |
|
} |
|
|
|
if (image.isNull()) { |
|
it = hints.find(QStringLiteral("image-path")); |
|
if (it == end) { |
|
it = hints.find(QStringLiteral("image_path")); |
|
} |
|
|
|
if (it != end) { |
|
loadImagePath(it->toString()); |
|
} |
|
} |
|
|
|
sanitizeImage(image); |
|
} |
|
|
|
void Notification::Private::setUrgency(Notifications::Urgency urgency) |
|
{ |
|
this->urgency = urgency; |
|
|
|
// Critical notifications must not time out |
|
// TODO should we really imply this here and not on the view side? |
|
// are there usecases for critical but can expire? |
|
// "critical updates available"? |
|
if (urgency == Notifications::CriticalUrgency) { |
|
timeout = 0; |
|
} |
|
} |
|
|
|
Notification::Notification(uint id) |
|
: d(new Private()) |
|
{ |
|
d->id = id; |
|
d->created = QDateTime::currentDateTimeUtc(); |
|
} |
|
|
|
Notification::Notification(const Notification &other) |
|
: d(new Private(*other.d)) |
|
{ |
|
} |
|
|
|
Notification::Notification(Notification &&other) noexcept |
|
: d(other.d) |
|
{ |
|
other.d = nullptr; |
|
} |
|
|
|
Notification &Notification::operator=(const Notification &other) |
|
{ |
|
*d = *other.d; |
|
return *this; |
|
} |
|
|
|
Notification &Notification::operator=(Notification &&other) noexcept |
|
{ |
|
d = other.d; |
|
other.d = nullptr; |
|
return *this; |
|
} |
|
|
|
Notification::~Notification() |
|
{ |
|
delete d; |
|
} |
|
|
|
uint Notification::id() const |
|
{ |
|
return d->id; |
|
} |
|
|
|
QString Notification::dBusService() const |
|
{ |
|
return d->dBusService; |
|
} |
|
|
|
void Notification::setDBusService(const QString &dBusService) |
|
{ |
|
d->dBusService = dBusService; |
|
} |
|
|
|
QDateTime Notification::created() const |
|
{ |
|
return d->created; |
|
} |
|
|
|
void Notification::setCreated(const QDateTime &created) |
|
{ |
|
d->created = created; |
|
} |
|
|
|
QDateTime Notification::updated() const |
|
{ |
|
return d->updated; |
|
} |
|
|
|
void Notification::resetUpdated() |
|
{ |
|
d->updated = QDateTime::currentDateTimeUtc(); |
|
} |
|
|
|
bool Notification::read() const |
|
{ |
|
return d->read; |
|
} |
|
|
|
void Notification::setRead(bool read) |
|
{ |
|
d->read = read; |
|
} |
|
|
|
QString Notification::summary() const |
|
{ |
|
return d->summary; |
|
} |
|
|
|
void Notification::setSummary(const QString &summary) |
|
{ |
|
d->summary = summary; |
|
} |
|
|
|
QString Notification::body() const |
|
{ |
|
return d->body; |
|
} |
|
|
|
void Notification::setBody(const QString &body) |
|
{ |
|
d->rawBody = body; |
|
d->body = Private::sanitize(body.trimmed()); |
|
} |
|
|
|
QString Notification::rawBody() const |
|
{ |
|
return d->rawBody; |
|
} |
|
|
|
QString Notification::icon() const |
|
{ |
|
return d->icon; |
|
} |
|
|
|
void Notification::setIcon(const QString &icon) |
|
{ |
|
d->loadImagePath(icon); |
|
Private::sanitizeImage(d->image); |
|
} |
|
|
|
QImage Notification::image() const |
|
{ |
|
return d->image; |
|
} |
|
|
|
void Notification::setImage(const QImage &image) |
|
{ |
|
d->image = image; |
|
} |
|
|
|
QString Notification::desktopEntry() const |
|
{ |
|
return d->desktopEntry; |
|
} |
|
|
|
void Notification::setDesktopEntry(const QString &desktopEntry) |
|
{ |
|
d->setDesktopEntry(desktopEntry); |
|
} |
|
|
|
QString Notification::notifyRcName() const |
|
{ |
|
return d->notifyRcName; |
|
} |
|
|
|
QString Notification::eventId() const |
|
{ |
|
return d->eventId; |
|
} |
|
|
|
QString Notification::applicationName() const |
|
{ |
|
return d->applicationName; |
|
} |
|
|
|
void Notification::setApplicationName(const QString &applicationName) |
|
{ |
|
d->applicationName = applicationName; |
|
} |
|
|
|
QString Notification::applicationIconName() const |
|
{ |
|
return d->applicationIconName; |
|
} |
|
|
|
void Notification::setApplicationIconName(const QString &applicationIconName) |
|
{ |
|
d->applicationIconName = applicationIconName; |
|
} |
|
|
|
QString Notification::originName() const |
|
{ |
|
return d->originName; |
|
} |
|
|
|
QStringList Notification::actionNames() const |
|
{ |
|
return d->actionNames; |
|
} |
|
|
|
QStringList Notification::actionLabels() const |
|
{ |
|
return d->actionLabels; |
|
} |
|
|
|
bool Notification::hasDefaultAction() const |
|
{ |
|
return d->hasDefaultAction; |
|
} |
|
|
|
QString Notification::defaultActionLabel() const |
|
{ |
|
return d->defaultActionLabel; |
|
} |
|
|
|
void Notification::setActions(const QStringList &actions) |
|
{ |
|
if (actions.count() % 2 != 0) { |
|
qCWarning(NOTIFICATIONMANAGER) << "List of actions must contain an even number of items, tried to set actions to" << actions; |
|
return; |
|
} |
|
|
|
d->hasDefaultAction = false; |
|
d->hasConfigureAction = false; |
|
d->hasReplyAction = false; |
|
|
|
QStringList names; |
|
QStringList labels; |
|
|
|
for (int i = 0; i < actions.count(); i += 2) { |
|
const QString &name = actions.at(i); |
|
const QString &label = actions.at(i + 1); |
|
|
|
if (!d->hasDefaultAction && name == QLatin1String("default")) { |
|
d->hasDefaultAction = true; |
|
d->defaultActionLabel = label; |
|
continue; |
|
} |
|
|
|
if (!d->hasConfigureAction && name == QLatin1String("settings")) { |
|
d->hasConfigureAction = true; |
|
d->configureActionLabel = label; |
|
continue; |
|
} |
|
|
|
if (!d->hasReplyAction && name == QLatin1String("inline-reply")) { |
|
d->hasReplyAction = true; |
|
d->replyActionLabel = label; |
|
continue; |
|
} |
|
|
|
names << name; |
|
labels << label; |
|
} |
|
|
|
d->actionNames = names; |
|
d->actionLabels = labels; |
|
} |
|
|
|
QList<QUrl> Notification::urls() const |
|
{ |
|
return d->urls; |
|
} |
|
|
|
void Notification::setUrls(const QList<QUrl> &urls) |
|
{ |
|
d->urls = urls; |
|
} |
|
|
|
Notifications::Urgency Notification::urgency() const |
|
{ |
|
return d->urgency; |
|
} |
|
|
|
bool Notification::userActionFeedback() const |
|
{ |
|
return d->userActionFeedback; |
|
} |
|
|
|
int Notification::timeout() const |
|
{ |
|
return d->timeout; |
|
} |
|
|
|
void Notification::setTimeout(int timeout) |
|
{ |
|
d->timeout = timeout; |
|
} |
|
|
|
bool Notification::configurable() const |
|
{ |
|
return d->hasConfigureAction || d->configurableNotifyRc || d->configurableService; |
|
} |
|
|
|
QString Notification::configureActionLabel() const |
|
{ |
|
return d->configureActionLabel; |
|
} |
|
|
|
bool Notification::hasReplyAction() const |
|
{ |
|
return d->hasReplyAction; |
|
} |
|
|
|
QString Notification::replyActionLabel() const |
|
{ |
|
return d->replyActionLabel; |
|
} |
|
|
|
QString Notification::replyPlaceholderText() const |
|
{ |
|
return d->replyPlaceholderText; |
|
} |
|
|
|
QString Notification::replySubmitButtonText() const |
|
{ |
|
return d->replySubmitButtonText; |
|
} |
|
|
|
QString Notification::replySubmitButtonIconName() const |
|
{ |
|
return d->replySubmitButtonIconName; |
|
} |
|
|
|
QString Notification::category() const |
|
{ |
|
return d->category; |
|
} |
|
|
|
bool Notification::expired() const |
|
{ |
|
return d->expired; |
|
} |
|
|
|
void Notification::setExpired(bool expired) |
|
{ |
|
d->expired = expired; |
|
} |
|
|
|
bool Notification::dismissed() const |
|
{ |
|
return d->dismissed; |
|
} |
|
|
|
void Notification::setDismissed(bool dismissed) |
|
{ |
|
d->dismissed = dismissed; |
|
} |
|
|
|
bool Notification::resident() const |
|
{ |
|
return d->resident; |
|
} |
|
|
|
void Notification::setResident(bool resident) |
|
{ |
|
d->resident = resident; |
|
} |
|
|
|
bool Notification::transient() const |
|
{ |
|
return d->transient; |
|
} |
|
|
|
void Notification::setTransient(bool transient) |
|
{ |
|
d->transient = transient; |
|
} |
|
|
|
QVariantMap Notification::hints() const |
|
{ |
|
return d->hints; |
|
} |
|
|
|
void Notification::setHints(const QVariantMap &hints) |
|
{ |
|
d->hints = hints; |
|
} |
|
|
|
void Notification::processHints(const QVariantMap &hints) |
|
{ |
|
d->processHints(hints); |
|
}
|
|
|