diff --git a/libnotificationmanager/jobsmodel.h b/libnotificationmanager/jobsmodel.h --- a/libnotificationmanager/jobsmodel.h +++ b/libnotificationmanager/jobsmodel.h @@ -84,6 +84,9 @@ void clear(Notifications::ClearFlags flags); +signals: + void serviceOwnershipLost(); + private: JobsModel(); Q_DISABLE_COPY(JobsModel) diff --git a/libnotificationmanager/jobsmodel.cpp b/libnotificationmanager/jobsmodel.cpp --- a/libnotificationmanager/jobsmodel.cpp +++ b/libnotificationmanager/jobsmodel.cpp @@ -63,6 +63,8 @@ const QModelIndex idx = index(row, 0); emit dataChanged(idx, idx, roles); }); + + connect(d, &JobsModelPrivate::serviceOwnershipLost, this, &JobsModel::serviceOwnershipLost); } JobsModel::~JobsModel() = default; diff --git a/libnotificationmanager/jobsmodel_p.h b/libnotificationmanager/jobsmodel_p.h --- a/libnotificationmanager/jobsmodel_p.h +++ b/libnotificationmanager/jobsmodel_p.h @@ -66,6 +66,8 @@ void jobViewChanged(int row, Job *job, const QVector &roles); + void serviceOwnershipLost(); + // DBus // kuiserver void jobUrlsChanged(const QStringList &urls); diff --git a/libnotificationmanager/jobsmodel_p.cpp b/libnotificationmanager/jobsmodel_p.cpp --- a/libnotificationmanager/jobsmodel_p.cpp +++ b/libnotificationmanager/jobsmodel_p.cpp @@ -145,14 +145,51 @@ return false; } - if (sessionBus.registerService(QStringLiteral("org.kde.JobViewServer"))) { + // Only the "dbus master" (effectively plasmashell) should be the true owner of job progress reporting + const bool master = Utils::isDBusMaster(); + const auto queueOptions = master ? QDBusConnectionInterface::ReplaceExistingService : QDBusConnectionInterface::DontQueueService; + const auto replacementOptions = master ? QDBusConnectionInterface::DontAllowReplacement : QDBusConnectionInterface::AllowReplacement; + + const QString jobViewServerService = QStringLiteral("org.kde.JobViewServer"); + const QString kuiserverService = QStringLiteral("org.kde.kuiserver"); + + QDBusConnectionInterface *dbusIface = QDBusConnection::sessionBus().interface(); + + if (!master) { + connect(dbusIface, &QDBusConnectionInterface::serviceUnregistered, this, [=](const QString &serviceName) { + // Close all running jobs as we're defunct now + if (serviceName == jobViewServerService || serviceName == kuiserverService) { + qCDebug(NOTIFICATIONMANAGER) << "Lost ownership of" << serviceName << "service"; + + const auto pendingJobs = m_pendingJobViews; + for (Job *job : pendingJobs) { + remove(job); + } + + const auto jobs = m_jobViews; + for (Job *job : jobs) { + // We can keep the finished ones as they're non-interactive anyway + if (job->state() != Notifications::JobStateStopped) { + remove(job); + } + } + + m_valid = false; + emit serviceOwnershipLost(); + } + }); + } + + auto registration = dbusIface->registerService(jobViewServerService, queueOptions, replacementOptions); + if (registration.value() == QDBusConnectionInterface::ServiceRegistered) { qCDebug(NOTIFICATIONMANAGER) << "Registered JobViewServer service on DBus"; } else { qCWarning(NOTIFICATIONMANAGER) << "Failed to register JobViewServer service on DBus, is kuiserver running?"; return false; } - if (!sessionBus.registerService(QStringLiteral("org.kde.kuiserver"))) { + registration = dbusIface->registerService(kuiserverService, queueOptions, replacementOptions); + if (registration.value() != QDBusConnectionInterface::ServiceRegistered) { qCWarning(NOTIFICATIONMANAGER) << "Failed to register org.kde.kuiserver service on DBus, is kuiserver running?"; return false; } diff --git a/libnotificationmanager/notificationsmodel.cpp b/libnotificationmanager/notificationsmodel.cpp --- a/libnotificationmanager/notificationsmodel.cpp +++ b/libnotificationmanager/notificationsmodel.cpp @@ -212,6 +212,15 @@ 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(); } diff --git a/libnotificationmanager/server.h b/libnotificationmanager/server.h --- a/libnotificationmanager/server.h +++ b/libnotificationmanager/server.h @@ -143,6 +143,11 @@ */ void inhibitionApplicationsChanged(); + /** + * Emitted when the ownership of the Notification DBus Service is lost. + */ + void serviceOwnershipLost(); + private: explicit Server(QObject *parent = nullptr); Q_DISABLE_COPY(Server) diff --git a/libnotificationmanager/server.cpp b/libnotificationmanager/server.cpp --- a/libnotificationmanager/server.cpp +++ b/libnotificationmanager/server.cpp @@ -39,6 +39,7 @@ }); connect(d.data(), &ServerPrivate::inhibitionAdded, this, &Server::inhibitionApplicationsChanged); connect(d.data(), &ServerPrivate::inhibitionRemoved, this, &Server::inhibitionApplicationsChanged); + connect(d.data(), &ServerPrivate::serviceOwnershipLost, this, &Server::serviceOwnershipLost); } Server::~Server() = default; diff --git a/libnotificationmanager/server_p.h b/libnotificationmanager/server_p.h --- a/libnotificationmanager/server_p.h +++ b/libnotificationmanager/server_p.h @@ -74,6 +74,7 @@ void inhibitedChanged(); void inhibitionAdded(); void inhibitionRemoved(); + void serviceOwnershipLost(); public: // stuff used by public class bool init(); diff --git a/libnotificationmanager/server_p.cpp b/libnotificationmanager/server_p.cpp --- a/libnotificationmanager/server_p.cpp +++ b/libnotificationmanager/server_p.cpp @@ -65,7 +65,27 @@ return false; } - if (!QDBusConnection::sessionBus().registerService(QStringLiteral("org.freedesktop.Notifications"))) { + // 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(); + } + }); + } + + auto registration = dbusIface->registerService(notificationService, + master ? QDBusConnectionInterface::ReplaceExistingService : QDBusConnectionInterface::DontQueueService, + master ? QDBusConnectionInterface::DontAllowReplacement : QDBusConnectionInterface::AllowReplacement + ); + if (registration.value() != QDBusConnectionInterface::ServiceRegistered) { qCWarning(NOTIFICATIONMANAGER) << "Failed to register Notification service on DBus"; return false; } diff --git a/libnotificationmanager/utils.cpp b/libnotificationmanager/utils.cpp --- a/libnotificationmanager/utils.cpp +++ b/libnotificationmanager/utils.cpp @@ -22,6 +22,7 @@ #include #include +#include #include #include #include @@ -93,3 +94,8 @@ } return resolvedIdx; } + +bool Utils::isDBusMaster() +{ + return qApp->property("_plasma_dbus_master").toBool(); +} diff --git a/libnotificationmanager/utils_p.h b/libnotificationmanager/utils_p.h --- a/libnotificationmanager/utils_p.h +++ b/libnotificationmanager/utils_p.h @@ -38,6 +38,8 @@ QModelIndex mapToModel(const QModelIndex &idx, const QAbstractItemModel *sourceModel); +bool isDBusMaster(); + } // namespace Utils } // namespace NotificationManager diff --git a/shell/main.cpp b/shell/main.cpp --- a/shell/main.cpp +++ b/shell/main.cpp @@ -171,6 +171,9 @@ } else { cliOptions.showHelp(1); } + } else { + // Tells libnotificationmanager that we're the only true application that may own notification and job progress services + qApp->setProperty("_plasma_dbus_master", true); } if (cliOptions.isSet(replaceOption)) {