Commit 147debe3 authored by Kai Uwe Broulik's avatar Kai Uwe Broulik 🍇
Browse files

[Notifications] Add ServerInfo class

It can be used to query information about the current notification server (vendor, name, version, notification spec version).
This will be used by the plasmoid and KCM to indicate when notifications are currently unavailable or provided by a
different notification service from Plasma. Also notify valid changing at runtime when ownership is lost.

Differential Revision: https://phabricator.kde.org/D25772
parent ee81c53a
......@@ -8,6 +8,7 @@ endif()
set(notificationmanager_LIB_SRCS
server.cpp
server_p.cpp
serverinfo.cpp
settings.cpp
mirroredscreenstracker.cpp
notifications.cpp
......
......@@ -23,6 +23,7 @@
#include "notifications.h"
#include "job.h"
#include "server.h"
#include "serverinfo.h"
#include "settings.h"
#include <QQmlEngine>
......@@ -40,4 +41,5 @@ void NotificationManagerPlugin::registerTypes(const char *uri)
QQmlEngine::setObjectOwnership(&Server::self(), QQmlEngine::CppOwnership);
return &Server::self();
});
qmlRegisterUncreatableType<ServerInfo>(uri, 1, 0, "ServerInfo", QStringLiteral("Can only access ServerInfo via Server"));
}
......@@ -34,6 +34,7 @@ Server::Server(QObject *parent)
: QObject(parent)
, d(new ServerPrivate(this))
{
connect(d.data(), &ServerPrivate::validChanged, this, &Server::validChanged);
connect(d.data(), &ServerPrivate::inhibitedChanged, this, [this] {
emit inhibitedChanged(inhibited());
});
......@@ -62,6 +63,11 @@ bool Server::isValid() const
return d->m_valid;
}
ServerInfo *Server::currentOwner() const
{
return d->currentOwner();
}
void Server::closeNotification(uint notificationId, CloseReason reason)
{
emit notificationRemoved(notificationId, reason);
......
......@@ -29,6 +29,7 @@ namespace NotificationManager
class Notification;
class ServerInfo;
class ServerPrivate;
/**
......@@ -40,6 +41,24 @@ class NOTIFICATIONMANAGER_EXPORT Server : public QObject
{
Q_OBJECT
/**
* Whether the notification service could be registered.
* Call @c init() to register.
*/
Q_PROPERTY(bool valid READ isValid NOTIFY validChanged)
/**
* Information about the current owner of the Notification service.
*
* This can be used to tell the user which application is currently
* owning the service in case service registration failed.
*
* This is never null, even if there is no notification service running.
*
* @since 5.18
*/
Q_PROPERTY(NotificationManager::ServerInfo *currentOwner READ currentOwner CONSTANT)
/**
* Whether notifications are currently inhibited.
*
......@@ -77,6 +96,12 @@ public:
*/
bool isValid() const;
/**
* Information about the current owner of the Notification service.
* @since 5.18
*/
ServerInfo *currentOwner() const;
/**
* Whether notifications are currently inhibited.
* @since 5.17
......@@ -134,6 +159,14 @@ public:
uint add(const Notification &notification);
Q_SIGNALS:
/**
* Emitted when the notification service validity changes,
* because it sucessfully registered the service or lost
* ownership of it.
* @since 5.18
*/
void validChanged();
/**
* Emitted when a notification was added.
* This is emitted regardless of any filtering rules or user settings.
......
......@@ -28,6 +28,7 @@
#include "notification_p.h"
#include "server.h"
#include "serverinfo.h"
#include "utils_p.h"
......@@ -47,11 +48,25 @@ ServerPrivate::ServerPrivate(QObject *parent)
{
m_inhibitionWatcher->setConnection(QDBusConnection::sessionBus());
m_inhibitionWatcher->setWatchMode(QDBusServiceWatcher::WatchForUnregistration);
connect(m_inhibitionWatcher, &QDBusServiceWatcher::serviceUnregistered, this, &ServerPrivate::onServiceUnregistered);
connect(m_inhibitionWatcher, &QDBusServiceWatcher::serviceUnregistered, this, &ServerPrivate::onInhibitionServiceUnregistered);
}
ServerPrivate::~ServerPrivate() = default;
QString ServerPrivate::notificationServiceName()
{
return QStringLiteral("org.freedesktop.Notifications");
}
ServerInfo *ServerPrivate::currentOwner() const
{
if (!m_currentOwner) {
m_currentOwner.reset(new ServerInfo());
}
return m_currentOwner.data();
}
bool ServerPrivate::init()
{
if (m_valid) {
......@@ -60,7 +75,11 @@ bool ServerPrivate::init()
new NotificationsAdaptor(this);
if (!QDBusConnection::sessionBus().registerObject(QStringLiteral("/org/freedesktop/Notifications"), this)) {
if (!m_dbusObjectValid) { // if already registered, don't fail here
m_dbusObjectValid = QDBusConnection::sessionBus().registerObject(QStringLiteral("/org/freedesktop/Notifications"), this);
}
if (!m_dbusObjectValid) {
qCWarning(NOTIFICATIONMANAGER) << "Failed to register Notification DBus object";
return false;
}
......@@ -68,20 +87,15 @@ bool ServerPrivate::init()
// Only the "dbus master" (effectively plasmashell) should be the true owner of notifications
const bool master = Utils::isDBusMaster();
const QString notificationService = QStringLiteral("org.freedesktop.Notifications");
QDBusConnectionInterface *dbusIface = QDBusConnection::sessionBus().interface();
if (!master) {
connect(dbusIface, &QDBusConnectionInterface::serviceUnregistered, this, [=](const QString &serviceName) {
if (serviceName == notificationService) {
qCDebug(NOTIFICATIONMANAGER) << "Lost ownership of" << serviceName << "service";
emit serviceOwnershipLost();
}
});
// NOTE this connects to whether the application lost ownership of given service
// This is not a wildcard listener for all unregistered services on the bus!
connect(dbusIface, &QDBusConnectionInterface::serviceUnregistered, this, &ServerPrivate::onServiceOwnershipLost, Qt::UniqueConnection);
}
auto registration = dbusIface->registerService(notificationService,
auto registration = dbusIface->registerService(notificationServiceName(),
master ? QDBusConnectionInterface::ReplaceExistingService : QDBusConnectionInterface::DontQueueService,
master ? QDBusConnectionInterface::DontAllowReplacement : QDBusConnectionInterface::AllowReplacement
);
......@@ -90,24 +104,7 @@ bool ServerPrivate::init()
return false;
}
connect(this, &ServerPrivate::inhibitedChanged, this, [this] {
// emit DBus change signal...
QDBusMessage signal = QDBusMessage::createSignal(
QStringLiteral("/org/freedesktop/Notifications"),
QStringLiteral("org.freedesktop.DBus.Properties"),
QStringLiteral("PropertiesChanged")
);
signal.setArguments({
QStringLiteral("org.freedesktop.Notifications"),
QVariantMap{ // updated
{QStringLiteral("Inhibited"), inhibited()},
},
QStringList() // invalidated
});
QDBusConnection::sessionBus().send(signal);
});
connect(this, &ServerPrivate::inhibitedChanged, this, &ServerPrivate::onInhibitedChanged, Qt::UniqueConnection);
qCDebug(NOTIFICATIONMANAGER) << "Registered Notification service on DBus";
......@@ -116,11 +113,14 @@ bool ServerPrivate::init()
if (broadcastsEnabled) {
qCDebug(NOTIFICATIONMANAGER) << "Notification server is configured to listen for broadcasts";
// NOTE Keep disconnect() call in onServiceOwnershipLost in sync if you change this!
QDBusConnection::systemBus().connect({}, {}, QStringLiteral("org.kde.BroadcastNotifications"),
QStringLiteral("Notify"), this, SLOT(onBroadcastNotification(QMap<QString,QVariant>)));
}
m_valid = true;
emit validChanged();
return true;
}
......@@ -342,7 +342,28 @@ uint ServerPrivate::Inhibit(const QString &desktop_entry, const QString &reason,
return m_highestInhibitionCookie;
}
void ServerPrivate::onServiceUnregistered(const QString &serviceName)
void ServerPrivate::onServiceOwnershipLost(const QString &serviceName)
{
if (serviceName != notificationServiceName()) {
return;
}
qCDebug(NOTIFICATIONMANAGER) << "Lost ownership of" << serviceName << "service";
disconnect(QDBusConnection::sessionBus().interface(), &QDBusConnectionInterface::serviceUnregistered,
this, &ServerPrivate::onServiceOwnershipLost);
disconnect(this, &ServerPrivate::inhibitedChanged, this, &ServerPrivate::onInhibitedChanged);
QDBusConnection::systemBus().disconnect({}, {}, QStringLiteral("org.kde.BroadcastNotifications"),
QStringLiteral("Notify"), this, SLOT(onBroadcastNotification(QMap<QString,QVariant>)));
m_valid = false;
emit validChanged();
emit serviceOwnershipLost();
}
void ServerPrivate::onInhibitionServiceUnregistered(const QString &serviceName)
{
qCDebug(NOTIFICATIONMANAGER) << "Inhibition service unregistered" << serviceName;
......@@ -356,6 +377,26 @@ void ServerPrivate::onServiceUnregistered(const QString &serviceName)
UnInhibit(cookie);
}
void ServerPrivate::onInhibitedChanged()
{
// emit DBus change signal...
QDBusMessage signal = QDBusMessage::createSignal(
QStringLiteral("/org/freedesktop/Notifications"),
QStringLiteral("org.freedesktop.DBus.Properties"),
QStringLiteral("PropertiesChanged")
);
signal.setArguments({
QStringLiteral("org.freedesktop.Notifications"),
QVariantMap{ // updated
{QStringLiteral("Inhibited"), inhibited()},
},
QStringList() // invalidated
});
QDBusConnection::sessionBus().send(signal);
}
void ServerPrivate::UnInhibit(uint cookie)
{
qCDebug(NOTIFICATIONMANAGER) << "Request release inhibition for cookie" << cookie;
......
......@@ -39,6 +39,8 @@ struct Inhibition
namespace NotificationManager
{
class ServerInfo;
class Q_DECL_HIDDEN ServerPrivate : public QObject, protected QDBusContext
{
Q_OBJECT
......@@ -71,6 +73,8 @@ Q_SIGNALS:
void NotificationClosed(uint id, uint reason);
void ActionInvoked(uint id, const QString &actionKey);
void validChanged();
void inhibitedChanged();
void externalInhibitedChanged();
......@@ -79,9 +83,14 @@ Q_SIGNALS:
void serviceOwnershipLost();
public: // stuff used by public class
friend class ServerInfo;
static QString notificationServiceName();
bool init();
uint add(const Notification &notification);
ServerInfo *currentOwner() const;
// Server only handles external application inhibitions but we still want the Inhibited property
// expose the actual inhibition state for applications to check.
void setInhibited(bool inhibited);
......@@ -97,7 +106,13 @@ private slots:
void onBroadcastNotification(const QMap<QString, QVariant> &properties);
private:
void onServiceUnregistered(const QString &serviceName);
void onServiceOwnershipLost(const QString &serviceName);
void onInhibitionServiceUnregistered(const QString &serviceName);
void onInhibitedChanged(); // emit DBus change signal
bool m_dbusObjectValid = false;
mutable QScopedPointer<ServerInfo> m_currentOwner;
QDBusServiceWatcher *m_inhibitionWatcher = nullptr;
uint m_highestInhibitionCookie = 0;
......
/*
* Copyright 2018 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/>.
*/
#include "serverinfo.h"
#include "server_p.h" // for notificationServiceName
#include "debug.h"
#include <QDBusConnection>
#include <QDBusConnectionInterface>
#include <QDBusPendingCallWatcher>
#include <QDBusServiceWatcher>
#include <QDebug>
using namespace NotificationManager;
class Q_DECL_HIDDEN ServerInfo::Private
{
public:
Private(ServerInfo *q);
~Private();
void setStatus(ServerInfo::Status status);
void setServerInformation(const QString &vendor,
const QString &name,
const QString &version,
const QString &specVersion);
void updateServerInformation();
ServerInfo *q;
ServerInfo::Status status = ServerInfo::Status::Unknown;
QString vendor;
QString name;
QString version;
QString specVersion;
};
ServerInfo::Private::Private(ServerInfo *q)
: q(q)
{
}
ServerInfo::Private::~Private() = default;
void ServerInfo::Private::setStatus(ServerInfo::Status status)
{
if (this->status != status) {
this->status = status;
emit q->statusChanged(status);
}
}
void ServerInfo::Private::setServerInformation(const QString &vendor,
const QString &name,
const QString &version,
const QString &specVersion)
{
if (this->vendor != vendor) {
this->vendor = vendor;
emit q->vendorChanged(vendor);
}
if (this->name != name) {
this->name = name;
emit q->nameChanged(name);
}
if (this->version != version) {
this->version = version;
emit q->versionChanged(version);
}
if (this->specVersion != specVersion) {
this->specVersion = specVersion;
emit q->specVersionChanged(specVersion);
}
}
void ServerInfo::Private::updateServerInformation()
{
// Check whether the service is running to avoid DBus-activating plasma_waitforname and getting stuck there.
if (!QDBusConnection::sessionBus().interface()->isServiceRegistered(ServerPrivate::notificationServiceName())) {
setStatus(ServerInfo::Status::NotRunning);
setServerInformation({}, {}, {}, {});
return;
}
QDBusMessage msg = QDBusMessage::createMethodCall(ServerPrivate::notificationServiceName(),
QStringLiteral("/org/freedesktop/Notifications"),
QStringLiteral("org.freedesktop.Notifications"),
QStringLiteral("GetServerInformation"));
auto call = QDBusConnection::sessionBus().asyncCall(msg);
auto *watcher = new QDBusPendingCallWatcher(call, q);
QObject::connect(watcher, &QDBusPendingCallWatcher::finished, q, [this](QDBusPendingCallWatcher *watcher) {
QDBusPendingReply<QString, QString, QString, QString> reply = *watcher;
watcher->deleteLater();
if (reply.isError()) {
qCWarning(NOTIFICATIONMANAGER) << "Failed to determine notification server information" << reply.error().message();
// Should this still be "Running" as technically it is?
// But if it is not even responding to this properly, who knows what it'll to with an actual notification
setStatus(Status::Unknown);
setServerInformation({}, {}, {}, {});
return;
}
const QString name = reply.argumentAt(0).toString();
const QString vendor = reply.argumentAt(1).toString();
const QString version = reply.argumentAt(2).toString();
const QString specVersion = reply.argumentAt(3).toString();
setServerInformation(vendor, name, version, specVersion);
setStatus(ServerInfo::Status::Running);
});
}
ServerInfo::ServerInfo(QObject *parent)
: QObject(parent)
, d(new Private(this))
{
auto *watcher = new QDBusServiceWatcher(ServerPrivate::notificationServiceName(),
QDBusConnection::sessionBus(),
QDBusServiceWatcher::WatchForOwnerChange, this);
connect(watcher, &QDBusServiceWatcher::serviceOwnerChanged, this, [this]() {
d->updateServerInformation();
});
d->updateServerInformation();
}
ServerInfo::~ServerInfo() = default;
ServerInfo::Status ServerInfo::status() const
{
return d->status;
}
QString ServerInfo::vendor() const
{
return d->vendor;
}
QString ServerInfo::name() const
{
return d->name;
}
QString ServerInfo::version() const
{
return d->version;
}
QString ServerInfo::specVersion() const
{
return d->specVersion;
}
/*
* Copyright 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/>.
*/
#pragma once
#include "notificationmanager_export.h"
#include <QObject>
#include <QScopedPointer>
#include <QString>
namespace NotificationManager
{
/**
* @short Information about the notification server
*
* Provides information such as vendor, name, version of the notification server.
*
* @author Kai Uwe Broulik <kde@privat.broulik.de>
**/
class NOTIFICATIONMANAGER_EXPORT ServerInfo : public QObject
{
Q_OBJECT
Q_PROPERTY(Status status READ status NOTIFY statusChanged)
Q_PROPERTY(QString vendor READ vendor NOTIFY vendorChanged)
Q_PROPERTY(QString name READ name NOTIFY nameChanged)
Q_PROPERTY(QString version READ version NOTIFY versionChanged)
Q_PROPERTY(QString specVersion READ specVersion NOTIFY specVersionChanged)
public:
explicit ServerInfo(QObject *parent = nullptr);
~ServerInfo() override;
enum class Status {
Unknown = -1,
NotRunning,
Running
};
Q_ENUM(Status)
Status status() const;
QString vendor() const;
QString name() const;
QString version() const;
QString specVersion() const;
Q_SIGNALS:
void statusChanged(Status status);
void vendorChanged(const QString &vendor);
void nameChanged(const QString &name);
void versionChanged(const QString &version);
void specVersionChanged(const QString &specVersion);
private:
class Private;
QScopedPointer<Private> d;
};
} // namespace NotificationManager
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment