diff --git a/libnotificationmanager/CMakeLists.txt b/libnotificationmanager/CMakeLists.txt --- a/libnotificationmanager/CMakeLists.txt +++ b/libnotificationmanager/CMakeLists.txt @@ -14,11 +14,13 @@ notifications.cpp notification.cpp + abstractnotificationsmodel.cpp notificationsmodel.cpp notificationfilterproxymodel.cpp notificationsortproxymodel.cpp notificationgroupingproxymodel.cpp notificationgroupcollapsingproxymodel.cpp + watchednotificationsmodel.cpp jobsmodel.cpp jobsmodel_p.cpp @@ -46,6 +48,8 @@ # DBus # Notifications qt5_add_dbus_adaptor(notificationmanager_LIB_SRCS dbus/org.freedesktop.Notifications.xml server_p.h NotificationManager::ServerPrivate) +qt5_add_dbus_adaptor(notificationmanager_LIB_SRCS dbus/org.kde.notificationmanager.xml server_p.h NotificationManager::ServerPrivate) +qt5_add_dbus_interface(notificationmanager_LIB_SRCS dbus/org.freedesktop.Notifications.xml fdonotifications_interface) # JobView qt5_add_dbus_adaptor(notificationmanager_LIB_SRCS dbus/org.kde.kuiserver.xml jobsmodel_p.h NotificationManager::JobsModelPrivate) qt5_add_dbus_adaptor(notificationmanager_LIB_SRCS dbus/org.kde.JobViewServer.xml jobsmodel_p.h NotificationManager::JobsModelPrivate) diff --git a/libnotificationmanager/notificationsmodel.h b/libnotificationmanager/abstractnotificationsmodel.h copy from libnotificationmanager/notificationsmodel.h copy to libnotificationmanager/abstractnotificationsmodel.h --- a/libnotificationmanager/notificationsmodel.h +++ b/libnotificationmanager/abstractnotificationsmodel.h @@ -18,26 +18,27 @@ * License along with this library. If not, see . */ -#pragma once +#ifndef ABSTRACTNOTIFICATIONSMODEL_H +#define ABSTRACTNOTIFICATIONSMODEL_H #include #include #include +#include #include "notifications.h" +#include "notification.h" +#include "server.h" namespace NotificationManager { -class NotificationsModel : public QAbstractListModel +class Q_DECL_EXPORT AbstractNotificationsModel : public QAbstractListModel { Q_OBJECT public: - ~NotificationsModel() override; - - using Ptr = QSharedPointer; - static Ptr createNotificationsModel(); + ~AbstractNotificationsModel() override; QDateTime lastRead() const; void setLastRead(const QDateTime &lastRead); @@ -47,13 +48,15 @@ int rowCount(const QModelIndex &parent = QModelIndex()) const override; QHash roleNames() const override; - void expire(uint notificationId); - void close(uint notificationId); - void configure(uint notificationId); - void configure(const QString &desktopEntry, const QString ¬ifyRcName, const QString &eventId); - void invokeDefaultAction(uint notificationId); - void invokeAction(uint notificationId, const QString &actionName); - void reply(uint notificationId, const QString &text); + virtual void expire(uint notificationId) = 0; + virtual void close(uint notificationId) = 0; + + // Currently configure actions are not exposed in AbstractNotificationsModel to keep it very minimal + // if usecase for this comes up in future, we can revisit it. + + virtual void invokeDefaultAction(uint notificationId) = 0; + virtual void invokeAction(uint notificationId, const QString &actionName) = 0; + virtual void reply(uint notificationId, const QString &text) = 0; void startTimeout(uint notificationId); void stopTimeout(uint notificationId); @@ -63,13 +66,25 @@ signals: void lastReadChanged(); +protected: + AbstractNotificationsModel(); + void onNotificationAdded(const Notification ¬ification); + void onNotificationReplaced(uint replacedId, const Notification ¬ification); + void onNotificationRemoved(uint notificationId, Server::CloseReason reason); + + void setupNotificationTimeout(const Notification ¬ification); + const QVector& notifications(); + int rowOfNotification(uint id) const; + + private: class Private; QScopedPointer d; - NotificationsModel(); - Q_DISABLE_COPY(NotificationsModel) + Q_DISABLE_COPY(AbstractNotificationsModel) }; } // namespace NotificationManager + +#endif //ABSTRACTNOTIFICATIONSMODEL_H diff --git a/libnotificationmanager/notificationsmodel.cpp b/libnotificationmanager/abstractnotificationsmodel.cpp copy from libnotificationmanager/notificationsmodel.cpp copy to libnotificationmanager/abstractnotificationsmodel.cpp --- a/libnotificationmanager/notificationsmodel.cpp +++ b/libnotificationmanager/abstractnotificationsmodel.cpp @@ -18,8 +18,8 @@ * License along with this library. If not, see . */ -#include "notificationsmodel.h" - +#include "abstractnotificationsmodel.h" +#include "abstractnotificationsmodel_p.h" #include "debug.h" #include "server.h" @@ -43,46 +43,20 @@ using namespace NotificationManager; -class Q_DECL_HIDDEN NotificationsModel::Private -{ -public: - explicit Private(NotificationsModel *q); - ~Private(); - - void onNotificationAdded(const Notification ¬ification); - void onNotificationReplaced(uint replacedId, const Notification ¬ification); - void onNotificationRemoved(uint notificationId, Server::CloseReason reason); - - void setupNotificationTimeout(const Notification ¬ification); - - int rowOfNotification(uint id) const; - - NotificationsModel *q; - - QVector notifications; - // Fallback timeout to ensure all notifications expire eventually - // otherwise when it isn't shown to the user and doesn't expire - // an app might wait indefinitely for the notification to do so - QHash notificationTimeouts; - - QDateTime lastRead; - -}; - -NotificationsModel::Private::Private(NotificationsModel *q) +AbstractNotificationsModel::Private::Private(AbstractNotificationsModel *q) : q(q) , lastRead(QDateTime::currentDateTimeUtc()) { } -NotificationsModel::Private::~Private() +AbstractNotificationsModel::Private::~Private() { qDeleteAll(notificationTimeouts); notificationTimeouts.clear(); } -void NotificationsModel::Private::onNotificationAdded(const Notification ¬ification) +void AbstractNotificationsModel::Private::onNotificationAdded(const Notification ¬ification) { // Once we reach a certain insane number of notifications discard some old ones // as we keep pixmaps around etc @@ -104,9 +78,9 @@ q->endInsertRows(); } -void NotificationsModel::Private::onNotificationReplaced(uint replacedId, const Notification ¬ification) +void AbstractNotificationsModel::Private::onNotificationReplaced(uint replacedId, const Notification ¬ification) { - const int row = rowOfNotification(replacedId); + const int row = q->rowOfNotification(replacedId); if (row == -1) { qCWarning(NOTIFICATIONMANAGER) << "Trying to replace notification with id" << replacedId << "which doesn't exist, creating a new one. This is an application bug!"; @@ -121,9 +95,9 @@ emit q->dataChanged(idx, idx); } -void NotificationsModel::Private::onNotificationRemoved(uint removedId, Server::CloseReason reason) +void AbstractNotificationsModel::Private::onNotificationRemoved(uint removedId, Server::CloseReason reason) { - const int row = rowOfNotification(removedId); + const int row = q->rowOfNotification(removedId); if (row == -1) { return; } @@ -161,7 +135,7 @@ q->endRemoveRows(); } -void NotificationsModel::Private::setupNotificationTimeout(const Notification ¬ification) +void AbstractNotificationsModel::Private::setupNotificationTimeout(const Notification ¬ification) { if (notification.timeout() == 0) { // In case it got replaced by a persistent notification @@ -187,72 +161,41 @@ timer->start(); } -int NotificationsModel::Private::rowOfNotification(uint id) const +int AbstractNotificationsModel::rowOfNotification(uint id) const { - auto it = std::find_if(notifications.constBegin(), notifications.constEnd(), [id](const Notification &item) { + auto it = std::find_if(d->notifications.constBegin(), d->notifications.constEnd(), [id](const Notification &item) { return item.id() == id; }); - if (it == notifications.constEnd()) { + if (it == d->notifications.constEnd()) { return -1; } - return std::distance(notifications.constBegin(), it); + return std::distance(d->notifications.constBegin(), it); } -NotificationsModel::NotificationsModel() +AbstractNotificationsModel::AbstractNotificationsModel() : QAbstractListModel(nullptr) , d(new Private(this)) { - connect(&Server::self(), &Server::notificationAdded, this, [this](const Notification ¬ification) { - d->onNotificationAdded(notification); - }); - connect(&Server::self(), &Server::notificationReplaced, this, [this](uint replacedId, const Notification ¬ification) { - d->onNotificationReplaced(replacedId, notification); - }); - connect(&Server::self(), &Server::notificationRemoved, this, [this](uint removedId, Server::CloseReason reason) { - d->onNotificationRemoved(removedId, reason); - }); - connect(&Server::self(), &Server::serviceOwnershipLost, this, [this] { - // Expire all notifications as we're defunct now - const auto notifications = d->notifications; - for (const Notification ¬ification : notifications) { - if (!notification.expired()) { - d->onNotificationRemoved(notification.id(), Server::CloseReason::Expired); - } - } - }); - - Server::self().init(); } -NotificationsModel::~NotificationsModel() = default; +AbstractNotificationsModel::~AbstractNotificationsModel() = default; -NotificationsModel::Ptr NotificationsModel::createNotificationsModel() -{ - static QWeakPointer s_instance; - if (!s_instance) { - QSharedPointer ptr(new NotificationsModel()); - s_instance = ptr.toWeakRef(); - return ptr; - } - return s_instance.toStrongRef(); -} - -QDateTime NotificationsModel::lastRead() const +QDateTime AbstractNotificationsModel::lastRead() const { return d->lastRead; } -void NotificationsModel::setLastRead(const QDateTime &lastRead) +void AbstractNotificationsModel::setLastRead(const QDateTime &lastRead) { if (d->lastRead != lastRead) { d->lastRead = lastRead; emit lastReadChanged(); } } -QVariant NotificationsModel::data(const QModelIndex &index, int role) const +QVariant AbstractNotificationsModel::data(const QModelIndex &index, int role) const { if (!checkIndex(index)) { return QVariant(); @@ -322,7 +265,7 @@ return QVariant(); } -bool NotificationsModel::setData(const QModelIndex &index, const QVariant &value, int role) +bool AbstractNotificationsModel::setData(const QModelIndex &index, const QVariant &value, int role) { if (!checkIndex(index)) { return false; @@ -342,134 +285,23 @@ return false; } -int NotificationsModel::rowCount(const QModelIndex &parent) const +int AbstractNotificationsModel::rowCount(const QModelIndex &parent) const { if (parent.isValid()) { return 0; } return d->notifications.count(); } -QHash NotificationsModel::roleNames() const +QHash AbstractNotificationsModel::roleNames() const { return Utils::roleNames(); } -void NotificationsModel::expire(uint notificationId) -{ - if (d->rowOfNotification(notificationId) > -1) { - Server::self().closeNotification(notificationId, Server::CloseReason::Expired); - } -} - -void NotificationsModel::close(uint notificationId) +void AbstractNotificationsModel::startTimeout(uint notificationId) { - if (d->rowOfNotification(notificationId) > -1) { - Server::self().closeNotification(notificationId, Server::CloseReason::DismissedByUser); - } -} - -void NotificationsModel::configure(uint notificationId) -{ - const int row = d->rowOfNotification(notificationId); - if (row == -1) { - return; - } - - const Notification ¬ification = d->notifications.at(row); - - if (notification.d->hasConfigureAction) { - Server::self().invokeAction(notificationId, QStringLiteral("settings")); // FIXME make a static Notification::configureActionName() or something - return; - } - - if (!notification.desktopEntry().isEmpty() || !notification.notifyRcName().isEmpty()) { - configure(notification.desktopEntry(), notification.notifyRcName(), notification.eventId()); - return; - } - - qCWarning(NOTIFICATIONMANAGER) << "Trying to configure notification" << notificationId << "which isn't configurable"; -} - -void NotificationsModel::configure(const QString &desktopEntry, const QString ¬ifyRcName, const QString &eventId) -{ - // TODO would be nice to just have a signal but since NotificationsModel is shared, - // if we connect to this from Notifications you would get a signal in every instance - // and potentially open the config dialog multiple times. - - QStringList args; - if (!desktopEntry.isEmpty()) { - args.append(QStringLiteral("--desktop-entry")); - args.append(desktopEntry); - } - if (!notifyRcName.isEmpty()) { - args.append(QStringLiteral("--notifyrc")); - args.append(notifyRcName); - } - if (!eventId.isEmpty()) { - args.append(QStringLiteral("--event-id")); - args.append(eventId); - } - - QProcess::startDetached(QStringLiteral("kcmshell5"), { - QStringLiteral("notifications"), - QStringLiteral("--args"), - KShell::joinArgs(args) - }); -} - -void NotificationsModel::invokeDefaultAction(uint notificationId) -{ - const int row = d->rowOfNotification(notificationId); - if (row == -1) { - return; - } - - const Notification ¬ification = d->notifications.at(row); - if (!notification.hasDefaultAction()) { - qCWarning(NOTIFICATIONMANAGER) << "Trying to invoke default action on notification" << notificationId << "which doesn't have one"; - return; - } - - Server::self().invokeAction(notificationId, QStringLiteral("default")); // FIXME make a static Notification::defaultActionName() or something -} - -void NotificationsModel::invokeAction(uint notificationId, const QString &actionName) -{ - const int row = d->rowOfNotification(notificationId); - if (row == -1) { - return; - } - - const Notification ¬ification = d->notifications.at(row); - if (!notification.actionNames().contains(actionName)) { - qCWarning(NOTIFICATIONMANAGER) << "Trying to invoke action" << actionName << "on notification" << notificationId << "which it doesn't have"; - return; - } - - Server::self().invokeAction(notificationId, actionName); -} - -void NotificationsModel::reply(uint notificationId, const QString &text) -{ - const int row = d->rowOfNotification(notificationId); - if (row == -1) { - return; - } - - const Notification ¬ification = d->notifications.at(row); - if (!notification.hasReplyAction()) { - qCWarning(NOTIFICATIONMANAGER) << "Trying to reply to a notification which doesn't have a reply action"; - return; - } - - Server::self().reply(notification.dBusService(), notificationId, text); -} - -void NotificationsModel::startTimeout(uint notificationId) -{ - const int row = d->rowOfNotification(notificationId); + const int row = rowOfNotification(notificationId); if (row == -1) { return; } @@ -483,12 +315,12 @@ d->setupNotificationTimeout(notification); } -void NotificationsModel::stopTimeout(uint notificationId) +void AbstractNotificationsModel::stopTimeout(uint notificationId) { delete d->notificationTimeouts.take(notificationId); } -void NotificationsModel::clear(Notifications::ClearFlags flags) +void AbstractNotificationsModel::clear(Notifications::ClearFlags flags) { if (d->notifications.isEmpty()) { return; @@ -534,3 +366,28 @@ endRemoveRows(); } } + +void AbstractNotificationsModel::onNotificationAdded(const Notification ¬ification) +{ + d->onNotificationAdded(notification); +} + +void AbstractNotificationsModel::onNotificationReplaced(uint replacedId, const Notification ¬ification) +{ + d->onNotificationReplaced(replacedId, notification); +} + +void AbstractNotificationsModel::onNotificationRemoved(uint notificationId, Server::CloseReason reason) +{ + d->onNotificationRemoved(notificationId, reason); +} + +void AbstractNotificationsModel::setupNotificationTimeout(const Notification ¬ification) +{ + d->setupNotificationTimeout(notification); +} + +const QVector& AbstractNotificationsModel::notifications() +{ + return d->notifications; +} diff --git a/libnotificationmanager/abstractnotificationsmodel_p.h b/libnotificationmanager/abstractnotificationsmodel_p.h new file mode 100644 --- /dev/null +++ b/libnotificationmanager/abstractnotificationsmodel_p.h @@ -0,0 +1,58 @@ +/* + * Copyright 2018-2019 Kai Uwe Broulik + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) version 3, or any + * later version accepted by the membership of KDE e.V. (or its + * successor approved by the membership of KDE e.V.), which shall + * act as a proxy defined in Section 6 of version 3 of the license. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + */ + +#ifndef ABSTRACTNOTIFICATIONSMODEL_P_H +#define ABSTRACTNOTIFICATIONSMODEL_P_H + +#include "notification.h" +#include "server.h" + +#include + +namespace NotificationManager +{ + +class Q_DECL_HIDDEN AbstractNotificationsModel::Private +{ +public: + explicit Private(AbstractNotificationsModel *q); + ~Private(); + + void onNotificationAdded(const Notification ¬ification); + void onNotificationReplaced(uint replacedId, const Notification ¬ification); + void onNotificationRemoved(uint notificationId, Server::CloseReason reason); + + void setupNotificationTimeout(const Notification ¬ification); + + AbstractNotificationsModel *q; + + QVector notifications; + // Fallback timeout to ensure all notifications expire eventually + // otherwise when it isn't shown to the user and doesn't expire + // an app might wait indefinitely for the notification to do so + QHash notificationTimeouts; + + QDateTime lastRead; + +}; + +} + +#endif // ABSTRACTNOTIFICATIONSMODEL_P_H diff --git a/libnotificationmanager/dbus/org.kde.notificationmanager.xml b/libnotificationmanager/dbus/org.kde.notificationmanager.xml new file mode 100644 --- /dev/null +++ b/libnotificationmanager/dbus/org.kde.notificationmanager.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/libnotificationmanager/declarative/notificationmanagerplugin.cpp b/libnotificationmanager/declarative/notificationmanagerplugin.cpp --- a/libnotificationmanager/declarative/notificationmanagerplugin.cpp +++ b/libnotificationmanager/declarative/notificationmanagerplugin.cpp @@ -25,6 +25,7 @@ #include "server.h" #include "serverinfo.h" #include "settings.h" +#include "watchednotificationsmodel.h" #include @@ -42,4 +43,5 @@ return &Server::self(); }); qmlRegisterUncreatableType(uri, 1, 0, "ServerInfo", QStringLiteral("Can only access ServerInfo via Server")); + qmlRegisterType(uri, 1, 1, "WatchedNotificationsModel"); } diff --git a/libnotificationmanager/notification.h b/libnotificationmanager/notification.h --- a/libnotificationmanager/notification.h +++ b/libnotificationmanager/notification.h @@ -70,6 +70,11 @@ QString body() const; void setBody(const QString &body); + // This returns the raw body data as provided by the notification + // this is useful when you want to html sanitization at different + // stage then the notification server. + QString rawBody() const; + QString icon() const; void setIcon(const QString &icon); @@ -124,10 +129,15 @@ bool dismissed() const; void setDismissed(bool dismissed); + // Little bit of mess here, we want to sometime keep track of processed hints, and not process it. + QVariantMap hints() const; + void setHints(const QVariantMap &hints); + void processHints(const QVariantMap &hints); private: friend class NotificationsModel; + friend class AbstractNotificationsModel; friend class ServerPrivate; class Private; diff --git a/libnotificationmanager/notification.cpp b/libnotificationmanager/notification.cpp --- a/libnotificationmanager/notification.cpp +++ b/libnotificationmanager/notification.cpp @@ -532,9 +532,15 @@ 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; @@ -750,6 +756,16 @@ d->dismissed = dismissed; } +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); diff --git a/libnotificationmanager/notification_p.h b/libnotificationmanager/notification_p.h --- a/libnotificationmanager/notification_p.h +++ b/libnotificationmanager/notification_p.h @@ -66,6 +66,8 @@ QString summary; QString body; + // raw body text without sanitize called. + QString rawBody; // Can be theme icon name or path QString icon; QImage image; @@ -97,6 +99,7 @@ QString replySubmitButtonIconName; QList urls; + QVariantMap hints = QVariantMap(); bool userActionFeedback = false; Notifications::Urgency urgency = Notifications::NormalUrgency; diff --git a/libnotificationmanager/notificationsmodel.h b/libnotificationmanager/notificationsmodel.h --- a/libnotificationmanager/notificationsmodel.h +++ b/libnotificationmanager/notificationsmodel.h @@ -1,4 +1,5 @@ /* + * Copyright 2020 Shah Bhushan * Copyright 2018-2019 Kai Uwe Broulik * * This library is free software; you can redistribute it and/or @@ -20,56 +21,27 @@ #pragma once -#include -#include -#include +#include "abstractnotificationsmodel.h" -#include "notifications.h" +namespace NotificationManager { -namespace NotificationManager +class NotificationsModel : public AbstractNotificationsModel { - -class NotificationsModel : public QAbstractListModel -{ - Q_OBJECT - public: - ~NotificationsModel() override; - using Ptr = QSharedPointer; static Ptr createNotificationsModel(); + void expire(uint notificationId) override; + void close(uint notificationId) override; - QDateTime lastRead() const; - void setLastRead(const QDateTime &lastRead); - - QVariant data(const QModelIndex &index, int role) const override; - bool setData(const QModelIndex &index, const QVariant &value, int role) override; - int rowCount(const QModelIndex &parent = QModelIndex()) const override; - QHash roleNames() const override; + void invokeDefaultAction(uint notificationId) override; + void invokeAction(uint notificationId, const QString &actionName) override; + void reply(uint notificationId, const QString &text) override; - void expire(uint notificationId); - void close(uint notificationId); void configure(uint notificationId); void configure(const QString &desktopEntry, const QString ¬ifyRcName, const QString &eventId); - void invokeDefaultAction(uint notificationId); - void invokeAction(uint notificationId, const QString &actionName); - void reply(uint notificationId, const QString &text); - - void startTimeout(uint notificationId); - void stopTimeout(uint notificationId); - - void clear(Notifications::ClearFlags flags); - -signals: - void lastReadChanged(); private: - class Private; - QScopedPointer d; - NotificationsModel(); - Q_DISABLE_COPY(NotificationsModel) - }; -} // namespace NotificationManager +} diff --git a/libnotificationmanager/notificationsmodel.cpp b/libnotificationmanager/notificationsmodel.cpp --- a/libnotificationmanager/notificationsmodel.cpp +++ b/libnotificationmanager/notificationsmodel.cpp @@ -1,4 +1,5 @@ /* + * Copyright 2020 Shah Bhushan * Copyright 2018-2019 Kai Uwe Broulik * * This library is free software; you can redistribute it and/or @@ -19,365 +20,124 @@ */ #include "notificationsmodel.h" - -#include "debug.h" - #include "server.h" -#include "utils_p.h" - -#include "notifications.h" - -#include "notification.h" +#include "abstractnotificationsmodel_p.h" #include "notification_p.h" -#include +#include "debug.h" + #include -#include #include -#include -#include - -static const int s_notificationsLimit = 1000; - using namespace NotificationManager; -class Q_DECL_HIDDEN NotificationsModel::Private -{ -public: - explicit Private(NotificationsModel *q); - ~Private(); - - void onNotificationAdded(const Notification ¬ification); - void onNotificationReplaced(uint replacedId, const Notification ¬ification); - void onNotificationRemoved(uint notificationId, Server::CloseReason reason); - - void setupNotificationTimeout(const Notification ¬ification); - - int rowOfNotification(uint id) const; - - NotificationsModel *q; - - QVector notifications; - // Fallback timeout to ensure all notifications expire eventually - // otherwise when it isn't shown to the user and doesn't expire - // an app might wait indefinitely for the notification to do so - QHash notificationTimeouts; - - QDateTime lastRead; - -}; - -NotificationsModel::Private::Private(NotificationsModel *q) - : q(q) - , lastRead(QDateTime::currentDateTimeUtc()) -{ - -} - -NotificationsModel::Private::~Private() -{ - qDeleteAll(notificationTimeouts); - notificationTimeouts.clear(); -} - -void NotificationsModel::Private::onNotificationAdded(const Notification ¬ification) -{ - // Once we reach a certain insane number of notifications discard some old ones - // as we keep pixmaps around etc - if (notifications.count() >= s_notificationsLimit) { - const int cleanupCount = s_notificationsLimit / 2; - qCDebug(NOTIFICATIONMANAGER) << "Reached the notification limit of" << s_notificationsLimit << ", discarding the oldest" << cleanupCount << "notifications"; - q->beginRemoveRows(QModelIndex(), 0, cleanupCount - 1); - for (int i = 0 ; i < cleanupCount; ++i) { - notifications.removeAt(0); - // TODO close gracefully? - } - q->endRemoveRows(); - } - - setupNotificationTimeout(notification); - - q->beginInsertRows(QModelIndex(), notifications.count(), notifications.count()); - notifications.append(std::move(notification)); - q->endInsertRows(); -} - -void NotificationsModel::Private::onNotificationReplaced(uint replacedId, const Notification ¬ification) -{ - const int row = rowOfNotification(replacedId); - - if (row == -1) { - qCWarning(NOTIFICATIONMANAGER) << "Trying to replace notification with id" << replacedId << "which doesn't exist, creating a new one. This is an application bug!"; - onNotificationAdded(notification); - return; - } - - setupNotificationTimeout(notification); - - notifications[row] = notification; - const QModelIndex idx = q->index(row, 0); - emit q->dataChanged(idx, idx); -} - -void NotificationsModel::Private::onNotificationRemoved(uint removedId, Server::CloseReason reason) -{ - const int row = rowOfNotification(removedId); - if (row == -1) { - return; - } - - q->stopTimeout(removedId); - - // When a notification expired, keep it around in the history and mark it as such - if (reason == Server::CloseReason::Expired) { - const QModelIndex idx = q->index(row, 0); - - Notification ¬ification = notifications[row]; - notification.setExpired(true); - - // Since the notification is "closed" it cannot have any actions - // unless it is "resident" which we don't support - notification.setActions(QStringList()); - - emit q->dataChanged(idx, idx, { - Notifications::ExpiredRole, - // TODO only emit those if actually changed? - Notifications::ActionNamesRole, - Notifications::ActionLabelsRole, - Notifications::HasDefaultActionRole, - Notifications::DefaultActionLabelRole, - Notifications::ConfigurableRole - }); - - return; - } - - // Otherwise if explicitly closed by either user or app, remove it - - q->beginRemoveRows(QModelIndex(), row, row); - notifications.removeAt(row); - q->endRemoveRows(); -} - -void NotificationsModel::Private::setupNotificationTimeout(const Notification ¬ification) -{ - if (notification.timeout() == 0) { - // In case it got replaced by a persistent notification - q->stopTimeout(notification.id()); - return; - } - - QTimer *timer = notificationTimeouts.value(notification.id()); - if (!timer) { - timer = new QTimer(); - timer->setSingleShot(true); - - connect(timer, &QTimer::timeout, q, [this, timer] { - const uint id = timer->property("notificationId").toUInt(); - q->expire(id); - }); - notificationTimeouts.insert(notification.id(), timer); - } - - timer->stop(); - timer->setProperty("notificationId", notification.id()); - timer->setInterval(60000 /*1min*/ + (notification.timeout() == -1 ? 120000 /*2min, max configurable default timeout*/ : notification.timeout())); - timer->start(); -} - -int NotificationsModel::Private::rowOfNotification(uint id) const +NotificationsModel::Ptr NotificationsModel::createNotificationsModel() { - auto it = std::find_if(notifications.constBegin(), notifications.constEnd(), [id](const Notification &item) { - return item.id() == id; - }); - - if (it == notifications.constEnd()) { - return -1; + static QWeakPointer s_instance; + if (!s_instance) { + QSharedPointer ptr(new NotificationsModel()); + s_instance = ptr.toWeakRef(); + return ptr; } - - return std::distance(notifications.constBegin(), it); + return s_instance.toStrongRef(); } NotificationsModel::NotificationsModel() - : QAbstractListModel(nullptr) - , d(new Private(this)) { connect(&Server::self(), &Server::notificationAdded, this, [this](const Notification ¬ification) { - d->onNotificationAdded(notification); + onNotificationAdded(notification); }); connect(&Server::self(), &Server::notificationReplaced, this, [this](uint replacedId, const Notification ¬ification) { - d->onNotificationReplaced(replacedId, notification); + onNotificationReplaced(replacedId, notification); }); connect(&Server::self(), &Server::notificationRemoved, this, [this](uint removedId, Server::CloseReason reason) { - d->onNotificationRemoved(removedId, reason); + onNotificationRemoved(removedId, reason); }); connect(&Server::self(), &Server::serviceOwnershipLost, this, [this] { // Expire all notifications as we're defunct now - const auto notifications = d->notifications; - for (const Notification ¬ification : notifications) { + const auto notificationList = notifications(); + for (const Notification ¬ification : notificationList) { if (!notification.expired()) { - d->onNotificationRemoved(notification.id(), Server::CloseReason::Expired); + onNotificationRemoved(notification.id(), Server::CloseReason::Expired); } } }); - Server::self().init(); } -NotificationsModel::~NotificationsModel() = default; - -NotificationsModel::Ptr NotificationsModel::createNotificationsModel() +void NotificationsModel::expire(uint notificationId) { - static QWeakPointer s_instance; - if (!s_instance) { - QSharedPointer ptr(new NotificationsModel()); - s_instance = ptr.toWeakRef(); - return ptr; + if (rowOfNotification(notificationId) > -1) { + Server::self().closeNotification(notificationId, Server::CloseReason::Expired); } - return s_instance.toStrongRef(); -} - -QDateTime NotificationsModel::lastRead() const -{ - return d->lastRead; } -void NotificationsModel::setLastRead(const QDateTime &lastRead) +void NotificationsModel::close(uint notificationId) { - if (d->lastRead != lastRead) { - d->lastRead = lastRead; - emit lastReadChanged(); + if (rowOfNotification(notificationId) > -1) { + Server::self().closeNotification(notificationId, Server::CloseReason::DismissedByUser); } } -QVariant NotificationsModel::data(const QModelIndex &index, int role) const + +void NotificationsModel::invokeDefaultAction(uint notificationId) { - if (!checkIndex(index)) { - return QVariant(); + const int row = rowOfNotification(notificationId); + if (row == -1) { + return; } - const Notification ¬ification = d->notifications.at(index.row()); - - switch (role) { - case Notifications::IdRole: return notification.id(); - case Notifications::TypeRole: return Notifications::NotificationType; - - case Notifications::CreatedRole: - if (notification.created().isValid()) { - return notification.created(); - } - break; - case Notifications::UpdatedRole: - if (notification.updated().isValid()) { - return notification.updated(); - } - break; - case Notifications::SummaryRole: return notification.summary(); - case Notifications::BodyRole: return notification.body(); - case Notifications::IconNameRole: - if (notification.image().isNull()) { - return notification.icon(); - } - break; - case Notifications::ImageRole: - if (!notification.image().isNull()) { - return notification.image(); - } - break; - case Notifications::DesktopEntryRole: return notification.desktopEntry(); - case Notifications::NotifyRcNameRole: return notification.notifyRcName(); - - case Notifications::ApplicationNameRole: return notification.applicationName(); - case Notifications::ApplicationIconNameRole: return notification.applicationIconName(); - case Notifications::OriginNameRole: return notification.originName(); - - case Notifications::ActionNamesRole: return notification.actionNames(); - case Notifications::ActionLabelsRole: return notification.actionLabels(); - case Notifications::HasDefaultActionRole: return notification.hasDefaultAction(); - case Notifications::DefaultActionLabelRole: return notification.defaultActionLabel(); - - case Notifications::UrlsRole: return QVariant::fromValue(notification.urls()); - - case Notifications::UrgencyRole: return static_cast(notification.urgency()); - case Notifications::UserActionFeedbackRole: return notification.userActionFeedback(); - - case Notifications::TimeoutRole: return notification.timeout(); - - case Notifications::ClosableRole: return true; - case Notifications::ConfigurableRole: return notification.configurable(); - case Notifications::ConfigureActionLabelRole: return notification.configureActionLabel(); - - case Notifications::ExpiredRole: return notification.expired(); - case Notifications::ReadRole: return notification.read(); - - case Notifications::HasReplyActionRole: return notification.hasReplyAction(); - case Notifications::ReplyActionLabelRole: return notification.replyActionLabel(); - case Notifications::ReplyPlaceholderTextRole: return notification.replyPlaceholderText(); - case Notifications::ReplySubmitButtonTextRole: return notification.replySubmitButtonText(); - case Notifications::ReplySubmitButtonIconNameRole: return notification.replySubmitButtonIconName(); + const Notification ¬ification = notifications().at(row); + if (!notification.hasDefaultAction()) { + qCWarning(NOTIFICATIONMANAGER) << "Trying to invoke default action on notification" << notificationId << "which doesn't have one"; + return; } - return QVariant(); + Server::self().invokeAction(notificationId, QStringLiteral("default")); // FIXME make a static Notification::defaultActionName() or something } -bool NotificationsModel::setData(const QModelIndex &index, const QVariant &value, int role) +void NotificationsModel::invokeAction(uint notificationId, const QString &actionName) { - if (!checkIndex(index)) { - return false; + const int row = rowOfNotification(notificationId); + if (row == -1) { + return; } - Notification ¬ification = d->notifications[index.row()]; - - switch (role) { - case Notifications::ReadRole: - if (value.toBool() != notification.read()) { - notification.setRead(value.toBool()); - return true; - } - break; + const Notification ¬ification = notifications().at(row); + if (!notification.actionNames().contains(actionName)) { + qCWarning(NOTIFICATIONMANAGER) << "Trying to invoke action" << actionName << "on notification" << notificationId << "which it doesn't have"; + return; } - return false; + Server::self().invokeAction(notificationId, actionName); } -int NotificationsModel::rowCount(const QModelIndex &parent) const +void NotificationsModel::reply(uint notificationId, const QString &text) { - if (parent.isValid()) { - return 0; + const int row = rowOfNotification(notificationId); + if (row == -1) { + return; } - return d->notifications.count(); -} - -QHash NotificationsModel::roleNames() const -{ - return Utils::roleNames(); -} - -void NotificationsModel::expire(uint notificationId) -{ - if (d->rowOfNotification(notificationId) > -1) { - Server::self().closeNotification(notificationId, Server::CloseReason::Expired); + const Notification ¬ification = notifications().at(row); + if (!notification.hasReplyAction()) { + qCWarning(NOTIFICATIONMANAGER) << "Trying to reply to a notification which doesn't have a reply action"; + return; } -} -void NotificationsModel::close(uint notificationId) -{ - if (d->rowOfNotification(notificationId) > -1) { - Server::self().closeNotification(notificationId, Server::CloseReason::DismissedByUser); - } + Server::self().reply(notification.dBusService(), notificationId, text); } + void NotificationsModel::configure(uint notificationId) { - const int row = d->rowOfNotification(notificationId); + const int row = rowOfNotification(notificationId); if (row == -1) { return; } - const Notification ¬ification = d->notifications.at(row); + const Notification ¬ification = notifications().at(row); if (notification.d->hasConfigureAction) { Server::self().invokeAction(notificationId, QStringLiteral("settings")); // FIXME make a static Notification::configureActionName() or something @@ -418,119 +178,3 @@ KShell::joinArgs(args) }); } - -void NotificationsModel::invokeDefaultAction(uint notificationId) -{ - const int row = d->rowOfNotification(notificationId); - if (row == -1) { - return; - } - - const Notification ¬ification = d->notifications.at(row); - if (!notification.hasDefaultAction()) { - qCWarning(NOTIFICATIONMANAGER) << "Trying to invoke default action on notification" << notificationId << "which doesn't have one"; - return; - } - - Server::self().invokeAction(notificationId, QStringLiteral("default")); // FIXME make a static Notification::defaultActionName() or something -} - -void NotificationsModel::invokeAction(uint notificationId, const QString &actionName) -{ - const int row = d->rowOfNotification(notificationId); - if (row == -1) { - return; - } - - const Notification ¬ification = d->notifications.at(row); - if (!notification.actionNames().contains(actionName)) { - qCWarning(NOTIFICATIONMANAGER) << "Trying to invoke action" << actionName << "on notification" << notificationId << "which it doesn't have"; - return; - } - - Server::self().invokeAction(notificationId, actionName); -} - -void NotificationsModel::reply(uint notificationId, const QString &text) -{ - const int row = d->rowOfNotification(notificationId); - if (row == -1) { - return; - } - - const Notification ¬ification = d->notifications.at(row); - if (!notification.hasReplyAction()) { - qCWarning(NOTIFICATIONMANAGER) << "Trying to reply to a notification which doesn't have a reply action"; - return; - } - - Server::self().reply(notification.dBusService(), notificationId, text); -} - -void NotificationsModel::startTimeout(uint notificationId) -{ - const int row = d->rowOfNotification(notificationId); - if (row == -1) { - return; - } - - const Notification ¬ification = d->notifications.at(row); - - if (!notification.timeout() || notification.expired()) { - return; - } - - d->setupNotificationTimeout(notification); -} - -void NotificationsModel::stopTimeout(uint notificationId) -{ - delete d->notificationTimeouts.take(notificationId); -} - -void NotificationsModel::clear(Notifications::ClearFlags flags) -{ - if (d->notifications.isEmpty()) { - return; - } - - // Tries to remove a contiguous group if possible as the likely case is - // you have n unread notifications at the end of the list, we don't want to - // remove and signal each item individually - QVector> clearQueue; - - QPair clearRange{-1, -1}; - - for (int i = d->notifications.count() - 1; i >= 0; --i) { - const Notification ¬ification = d->notifications.at(i); - - bool clear = (flags.testFlag(Notifications::ClearExpired) && notification.expired()); - - if (clear) { - if (clearRange.second == -1) { - clearRange.second = i; - } - clearRange.first = i; - } else { - if (clearRange.first != -1) { - clearQueue.append(clearRange); - clearRange.first = -1; - clearRange.second = -1; - } - } - } - - if (clearRange.first != -1) { - clearQueue.append(clearRange); - clearRange.first = -1; - clearRange.second = -1; - } - - for (const auto &range : clearQueue) { - beginRemoveRows(QModelIndex(), range.first, range.second); - for (int i = range.second; i >= range.first; --i) { - d->notifications.removeAt(i); - } - endRemoveRows(); - } -} diff --git a/libnotificationmanager/server_p.h b/libnotificationmanager/server_p.h --- a/libnotificationmanager/server_p.h +++ b/libnotificationmanager/server_p.h @@ -22,6 +22,7 @@ #include #include +#include #include "notification.h" @@ -68,6 +69,12 @@ void UnInhibit(uint cookie); bool inhibited() const; // property getter + // Notifition watcher + void RegisterWatcher(); + void UnRegisterWatcher(); + + void InvokeAction(uint id, const QString &actionKey); + Q_SIGNALS: // DBus void NotificationClosed(uint id, uint reason); @@ -121,6 +128,7 @@ mutable QScopedPointer m_currentOwner; QDBusServiceWatcher *m_inhibitionWatcher = nullptr; + QDBusServiceWatcher *m_notificationWatchers = nullptr; uint m_highestInhibitionCookie = 0; QHash m_externalInhibitions; QHash m_inhibitionServices; diff --git a/libnotificationmanager/server_p.cpp b/libnotificationmanager/server_p.cpp --- a/libnotificationmanager/server_p.cpp +++ b/libnotificationmanager/server_p.cpp @@ -23,6 +23,7 @@ #include "debug.h" #include "notificationsadaptor.h" +#include "notificationmanageradaptor.h" #include "notification.h" #include "notification_p.h" @@ -45,10 +46,17 @@ ServerPrivate::ServerPrivate(QObject *parent) : QObject(parent) , m_inhibitionWatcher(new QDBusServiceWatcher(this)) + , m_notificationWatchers (new QDBusServiceWatcher(this)) { m_inhibitionWatcher->setConnection(QDBusConnection::sessionBus()); m_inhibitionWatcher->setWatchMode(QDBusServiceWatcher::WatchForUnregistration); connect(m_inhibitionWatcher, &QDBusServiceWatcher::serviceUnregistered, this, &ServerPrivate::onInhibitionServiceUnregistered); + + m_notificationWatchers->setConnection(QDBusConnection::sessionBus()); + m_notificationWatchers->setWatchMode(QDBusServiceWatcher::WatchForUnregistration); + connect(m_notificationWatchers, &QDBusServiceWatcher::serviceUnregistered, [=](const QString &service) { + m_notificationWatchers->removeWatchedService(service); + }); } ServerPrivate::~ServerPrivate() = default; @@ -84,6 +92,7 @@ } new NotificationsAdaptor(this); + new NotificationManagerAdaptor(this); if (!m_dbusObjectValid) { // if already registered, don't fail here m_dbusObjectValid = QDBusConnection::sessionBus().registerObject(notificationServicePath(), this); @@ -223,11 +232,48 @@ emit static_cast(parent())->notificationAdded(notification); } + // currently we dispatch all notification, this is ugly + // TODO: come up with proper authentication/user selection + for (const QString &service : m_notificationWatchers->watchedServices()) { + QDBusMessage msg = QDBusMessage::createMethodCall( + service, + QStringLiteral("/NotificationWatcher"), + QStringLiteral("org.kde.NotificationWatcher"), + QStringLiteral("Notify") + ); + msg.setArguments({ + notificationId, + notification.applicationName(), + replaces_id, + notification.applicationIconName(), + notification.summary(), + // we pass raw body data since this data goes through another sanitization + // in WatchedNotificationsModel when notification object is created. + notification.rawBody(), + notification.actionNames(), + hints, + notification.timeout() + }); + QDBusConnection::sessionBus().call(msg, QDBus::NoBlock); + } + return notificationId; } void ServerPrivate::CloseNotification(uint id) { + for (const QString &service : m_notificationWatchers->watchedServices()) { + QDBusMessage msg = QDBusMessage::createMethodCall( + service, + QStringLiteral("/NotificationWatcher"), + QStringLiteral("org.kde.NotificationWatcher"), + QStringLiteral("CloseNotification") + ); + msg.setArguments({ + id + }); + QDBusConnection::sessionBus().call(msg, QDBus::NoBlock); + } // spec says "If the notification no longer exists, an empty D-BUS Error message is sent back." static_cast(parent())->closeNotification(id, Server::CloseReason::Revoked); } @@ -482,3 +528,18 @@ emit externalInhibitedChanged(); emit externalInhibitionsChanged(); } + +void ServerPrivate::RegisterWatcher() +{ + m_notificationWatchers->addWatchedService(message().service()); +} + +void ServerPrivate::UnRegisterWatcher() +{ + m_notificationWatchers->removeWatchedService(message().service()); +} + +void ServerPrivate::InvokeAction(uint id, const QString& actionKey) +{ + ActionInvoked(id, actionKey); +} diff --git a/libnotificationmanager/watchednotificationsmodel.h b/libnotificationmanager/watchednotificationsmodel.h new file mode 100644 --- /dev/null +++ b/libnotificationmanager/watchednotificationsmodel.h @@ -0,0 +1,60 @@ +/* + * Copyright 2020 Shah Bhushan + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) version 3, or any + * later version accepted by the membership of KDE e.V. (or its + * successor approved by the membership of KDE e.V.), which shall + * act as a proxy defined in Section 6 of version 3 of the license. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + */ + +#ifndef WATCHEDNOTIFICATIONSMODEL_H +#define WATCHEDNOTIFICATIONSMODEL_H + +#include "abstractnotificationsmodel.h" + +#include "notificationmanager_export.h" + +namespace NotificationManager +{ + +class NOTIFICATIONMANAGER_EXPORT WatchedNotificationsModel : public AbstractNotificationsModel +{ + Q_OBJECT + Q_PROPERTY(bool valid READ valid NOTIFY validChanged) + +public: + explicit WatchedNotificationsModel(); + ~WatchedNotificationsModel(); + + void expire(uint notificationId) override; + void close(uint notificationId) override; + + void invokeDefaultAction(uint notificationId) override; + void invokeAction(uint notificationId, const QString &actionName) override; + void reply(uint notificationId, const QString &text) override; + bool valid(); + +signals: + void validChanged(bool valid); + +private: + class Private; + Private * const d; + Q_DISABLE_COPY(WatchedNotificationsModel) + +}; + +} + +#endif // WATCHEDNOTIFICATIONSMODEL_H diff --git a/libnotificationmanager/watchednotificationsmodel.cpp b/libnotificationmanager/watchednotificationsmodel.cpp new file mode 100644 --- /dev/null +++ b/libnotificationmanager/watchednotificationsmodel.cpp @@ -0,0 +1,178 @@ +/* + * Copyright 2020 Shah Bhushan + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) version 3, or any + * later version accepted by the membership of KDE e.V. (or its + * successor approved by the membership of KDE e.V.), which shall + * act as a proxy defined in Section 6 of version 3 of the license. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + */ + +#include "watchednotificationsmodel.h" + +#include +#include +#include +#include + +#include + +#include "fdonotifications_interface.h" + +using namespace NotificationManager; + +class WatchedNotificationsModel::Private : public QObject +{ + Q_OBJECT +public: + explicit Private(WatchedNotificationsModel* q, QObject* parent = nullptr); + ~Private(); + bool valid = false; + +public Q_SLOTS: + Q_SCRIPTABLE void Notify(uint id, const QString &app_name, uint replaces_id, const QString &app_icon, + const QString &summary, const QString &body, const QStringList &actions, + const QVariantMap &hints, int timeout); + Q_SCRIPTABLE void CloseNotification(uint id); + void NotificationClosed(uint id, uint reason); + +private: + WatchedNotificationsModel* q; + OrgFreedesktopNotificationsInterface *fdoNotificationsInterface; +}; + +WatchedNotificationsModel::Private::Private(WatchedNotificationsModel* q, QObject *parent) + : q(q) + , QObject(parent) +{ + QDBusConnection dbus = QDBusConnection::sessionBus(); + fdoNotificationsInterface = new OrgFreedesktopNotificationsInterface(QStringLiteral("org.freedesktop.Notifications"), + QStringLiteral("/org/freedesktop/Notifications"), + dbus, + this); + connect(fdoNotificationsInterface, &OrgFreedesktopNotificationsInterface::NotificationClosed, + this, &WatchedNotificationsModel::Private::NotificationClosed); + dbus.registerObject("/NotificationWatcher", QStringLiteral("org.kde.NotificationWatcher"), this, QDBusConnection::ExportScriptableSlots); + QDBusMessage msg = QDBusMessage::createMethodCall( + QStringLiteral("org.freedesktop.Notifications"), + QStringLiteral("/org/freedesktop/Notifications"), + QStringLiteral("org.kde.NotificationManager"), + QStringLiteral("RegisterWatcher") + ); + QDBusMessage reply = QDBusConnection::sessionBus().call(msg, QDBus::NoBlock); + if(reply.type() != QDBusMessage::ErrorMessage) { + valid = true; + Q_EMIT q->validChanged(valid); + } +} + +WatchedNotificationsModel::Private::~Private() +{ + QDBusMessage msg = QDBusMessage::createMethodCall( + QStringLiteral("org.freedesktop.Notifications"), + QStringLiteral("/org/freedesktop/Notifications"), + QStringLiteral("org.kde.NotificationManager"), + QStringLiteral("UnRegisterWatcher") + ); + QDBusConnection::sessionBus().call(msg, QDBus::NoBlock); +} + +void WatchedNotificationsModel::Private::Notify(uint id, const QString &app_name, uint replaces_id, const QString &app_icon, + const QString &summary, const QString &body, const QStringList &actions, + const QVariantMap &hints, int timeout) +{ + const bool wasReplaced = replaces_id > 0; + + qDebug() << summary; + qDebug() << body; + Notification notification(id); + notification.setSummary(summary); + notification.setBody(body); + notification.setApplicationName(app_name); + + notification.setActions(actions); + notification.setTimeout(timeout); + notification.setHints(hints); + notification.setIcon(app_icon); + if(wasReplaced) { + q->onNotificationReplaced(replaces_id, notification); + } else { + q->onNotificationAdded(notification); + } +} + +void WatchedNotificationsModel::Private::CloseNotification(uint id) +{ + q->onNotificationRemoved(id, Server::CloseReason::Expired); +} + +void WatchedNotificationsModel::Private::NotificationClosed(uint id, uint reason) +{ + q->onNotificationRemoved(id, static_cast(reason)); +} + +WatchedNotificationsModel::WatchedNotificationsModel() + : AbstractNotificationsModel(), + d(new Private(this, nullptr)) +{ +} + +WatchedNotificationsModel::~WatchedNotificationsModel() +{ +} + +void WatchedNotificationsModel::close(uint notificationId) +{ + onNotificationRemoved(notificationId, Server::CloseReason::DismissedByUser); +} + +void WatchedNotificationsModel::expire(uint notificationId) +{ + onNotificationRemoved(notificationId, Server::CloseReason::Expired); +} + +void WatchedNotificationsModel::invokeDefaultAction(uint notificationId) +{ + this->invokeAction(notificationId, QStringLiteral("default")); +} + +void WatchedNotificationsModel::invokeAction(uint notificationId, const QString &actionName) +{ + QDBusConnection dbus = QDBusConnection::sessionBus(); + dbus.registerObject("/NotificationWatcher", this, QDBusConnection::ExportScriptableSlots); + QDBusMessage msg = QDBusMessage::createMethodCall( + QStringLiteral("org.freedesktop.Notifications"), + QStringLiteral("/org/freedesktop/Notifications"), + QStringLiteral("org.kde.NotificationManager"), + QStringLiteral("InvokeAction") + ); + msg.setArguments({ + notificationId, + actionName + }); + QDBusConnection::sessionBus().call(msg, QDBus::NoBlock); +} + +void WatchedNotificationsModel::reply(uint notificationId, const QString &text) +{ + // todo + Q_UNUSED(notificationId) + Q_UNUSED(text) +} + +bool WatchedNotificationsModel::valid() +{ + return d->valid; +} + +#include "watchednotificationsmodel.moc"