Commit 2357e6f2 authored by Bhushan Shah's avatar Bhushan Shah 📱

[libnotificationmanager] introduce the WatchedNotificationsModel

Summary: This allows one to subscribe to notifications from notification server.

Test Plan:
tested using very simple QML

```
import QtQuick 2.0
import org.kde.notificationmanager 1.1 as Notifications

...
Notifications.NotificationWatchedModel {
    id: model
}

```

Reviewers: #plasma, broulik, davidedmundson

Reviewed By: #plasma, broulik

Subscribers: nicolasfella, plasma-devel

Tags: #plasma

Differential Revision: https://phabricator.kde.org/D28509
parent 63deb6a0
......@@ -14,11 +14,13 @@ set(notificationmanager_LIB_SRCS
notifications.cpp
notification.cpp
abstractnotificationsmodel.cpp
notificationsmodel.cpp
notificationfilterproxymodel.cpp
notificationsortproxymodel.cpp
notificationgroupingproxymodel.cpp
notificationgroupcollapsingproxymodel.cpp
watchednotificationsmodel.cpp
jobsmodel.cpp
jobsmodel_p.cpp
......@@ -47,6 +49,8 @@ kconfig_add_kcfg_files(notificationmanager_LIB_SRCS kcfg/behaviorsettings.kcfgc
# 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)
......
This diff is collapsed.
/*
* Copyright 2018-2019 Kai Uwe Broulik <kde@privat.broulik.de>
*
* 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 <http://www.gnu.org/licenses/>.
*/
#ifndef ABSTRACTNOTIFICATIONSMODEL_H
#define ABSTRACTNOTIFICATIONSMODEL_H
#include <QAbstractListModel>
#include <QScopedPointer>
#include <QSharedPointer>
#include <QDateTime>
#include "notifications.h"
#include "notification.h"
#include "server.h"
namespace NotificationManager
{
class Q_DECL_EXPORT AbstractNotificationsModel : public QAbstractListModel
{
Q_OBJECT
public:
~AbstractNotificationsModel() 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<int, QByteArray> roleNames() const override;
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);
void clear(Notifications::ClearFlags flags);
signals:
void lastReadChanged();
protected:
AbstractNotificationsModel();
void onNotificationAdded(const Notification &notification);
void onNotificationReplaced(uint replacedId, const Notification &notification);
void onNotificationRemoved(uint notificationId, Server::CloseReason reason);
void setupNotificationTimeout(const Notification &notification);
const QVector<Notification>& notifications();
int rowOfNotification(uint id) const;
private:
class Private;
QScopedPointer<Private> d;
Q_DISABLE_COPY(AbstractNotificationsModel)
};
} // namespace NotificationManager
#endif //ABSTRACTNOTIFICATIONSMODEL_H
/*
* Copyright 2018-2019 Kai Uwe Broulik <kde@privat.broulik.de>
*
* 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 <http://www.gnu.org/licenses/>.
*/
#ifndef ABSTRACTNOTIFICATIONSMODEL_P_H
#define ABSTRACTNOTIFICATIONSMODEL_P_H
#include "notification.h"
#include "server.h"
#include <QDateTime>
namespace NotificationManager
{
class Q_DECL_HIDDEN AbstractNotificationsModel::Private
{
public:
explicit Private(AbstractNotificationsModel *q);
~Private();
void onNotificationAdded(const Notification &notification);
void onNotificationReplaced(uint replacedId, const Notification &notification);
void onNotificationRemoved(uint notificationId, Server::CloseReason reason);
void setupNotificationTimeout(const Notification &notification);
AbstractNotificationsModel *q;
QVector<Notification> 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<uint /*notificationId*/, QTimer*> notificationTimeouts;
QDateTime lastRead;
};
}
#endif // ABSTRACTNOTIFICATIONSMODEL_P_H
<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN" "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
<node>
<interface name="org.kde.NotificationManager">
<method name="RegisterWatcher"/>
<method name="UnRegisterWatcher"/>
<method name="InvokeAction">
<arg name="id" type="u" direction="in"/>
<arg name="action_key" type="s" direction="in"/>
</method>
</interface>
</node>
......@@ -25,6 +25,7 @@
#include "server.h"
#include "serverinfo.h"
#include "settings.h"
#include "watchednotificationsmodel.h"
#include <QQmlEngine>
......@@ -42,4 +43,5 @@ void NotificationManagerPlugin::registerTypes(const char *uri)
return &Server::self();
});
qmlRegisterUncreatableType<ServerInfo>(uri, 1, 0, "ServerInfo", QStringLiteral("Can only access ServerInfo via Server"));
qmlRegisterType<WatchedNotificationsModel>(uri, 1, 1, "WatchedNotificationsModel");
}
......@@ -532,9 +532,15 @@ QString Notification::body() const
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 @@ void Notification::setDismissed(bool dismissed)
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);
......
......@@ -70,6 +70,11 @@ public:
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 @@ public:
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;
......
......@@ -66,6 +66,8 @@ public:
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 @@ public:
QString replySubmitButtonIconName;
QList<QUrl> urls;
QVariantMap hints = QVariantMap();
bool userActionFeedback = false;
Notifications::Urgency urgency = Notifications::NormalUrgency;
......
/*
* Copyright 2020 Shah Bhushan <bshah@kde.org>
* Copyright 2018-2019 Kai Uwe Broulik <kde@privat.broulik.de>
*
* This library is free software; you can redistribute it and/or
......@@ -20,56 +21,27 @@
#pragma once
#include <QAbstractListModel>
#include <QScopedPointer>
#include <QSharedPointer>
#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<NotificationsModel>;
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<int, QByteArray> 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 &notifyRcName, 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<Private> d;
NotificationsModel();
Q_DISABLE_COPY(NotificationsModel)
};
} // namespace NotificationManager
}
......@@ -23,6 +23,7 @@
#include "debug.h"
#include "notificationsadaptor.h"
#include "notificationmanageradaptor.h"
#include "notification.h"
#include "notification_p.h"
......@@ -45,10 +46,17 @@ using namespace NotificationManager;
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 @@ bool ServerPrivate::init()
}
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 @@ uint ServerPrivate::Notify(const QString &app_name, uint replaces_id, const QStr
emit static_cast<Server*>(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<Server*>(parent())->closeNotification(id, Server::CloseReason::Revoked);
}
......@@ -482,3 +528,18 @@ void ServerPrivate::clearExternalInhibitions()
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);
}
......@@ -22,6 +22,7 @@
#include <QObject>
#include <QDBusContext>
#include <QStringList>
#include "notification.h"
......@@ -68,6 +69,12 @@ public:
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 @@ private:
mutable QScopedPointer<ServerInfo> m_currentOwner;
QDBusServiceWatcher *m_inhibitionWatcher = nullptr;
QDBusServiceWatcher *m_notificationWatchers = nullptr;
uint m_highestInhibitionCookie = 0;
QHash<uint /*cookie*/, Inhibition> m_externalInhibitions;
QHash<uint /*cookie*/, QString> m_inhibitionServices;
......
/*
* Copyright 2020 Shah Bhushan <bshah@kde.org>
*
* 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 <http://www.gnu.org/licenses/>.
*/
#include "watchednotificationsmodel.h"
#include <QDBusMetaType>
#include <QDBusConnection>
#include <QDBusServiceWatcher>
#include <QDBusConnectionInterface>
#include <QDebug>
#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<Server::CloseReason>(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)
{