diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -60,6 +60,8 @@ configure_file(config-knotifications.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-knotifications.h ) +add_subdirectory(waitforname) + add_library(KF5Notifications ${knotifications_SRCS}) generate_export_header(KF5Notifications BASE_NAME KNotifications) add_library(KF5::Notifications ALIAS KF5Notifications) diff --git a/src/knotificationmanager.cpp b/src/knotificationmanager.cpp --- a/src/knotificationmanager.cpp +++ b/src/knotificationmanager.cpp @@ -27,6 +27,7 @@ #include #include #include +#include #include #include "knotifyconfig.h" @@ -56,6 +57,7 @@ // incremental ids for notifications int notifyIdCounter; QStringList dirtyConfigCache; + bool blockNotifications; }; class KNotificationManagerSingleton @@ -127,13 +129,23 @@ } } - QDBusConnection::sessionBus().connect(QString(), + QDBusConnection sessionBus = QDBusConnection::sessionBus(); + sessionBus.connect(QString(), QStringLiteral("/Config"), QStringLiteral("org.kde.knotification"), QStringLiteral("reparseConfiguration"), this, SLOT(reparseConfiguration(QString))); + d->blockNotifications = sessionBus.interface()->isServiceRegistered("org.kde.KSplash"); + if (d->blockNotifications) { + qCDebug(LOG_KNOTIFICATIONS) << "Splash is in progress, delaying notifications"; + QDBusServiceWatcher* watcher = new QDBusServiceWatcher(this); + watcher->setConnection(sessionBus); + watcher->setWatchedServices(QStringList("org.kde.KSplash")); + watcher->setWatchMode(QDBusServiceWatcher::WatchForUnregistration); + connect(watcher, SIGNAL(serviceUnregistered(const QString&)), this, SLOT(renotify(const QString&))); + } } KNotificationManager::~KNotificationManager() @@ -212,6 +224,13 @@ int KNotificationManager::notify(KNotification *n) { + if (d->blockNotifications) { + d->notifications.insert(d->notifyIdCounter, n); + // Temporarily ref the item to prevent its untimely cleanup + n->ref(); + return d->notifyIdCounter++; + } + KNotifyConfig notifyConfig(n->appName(), n->contexts(), n->eventId()); if (d->dirtyConfigCache.contains(n->appName())) { @@ -246,6 +265,19 @@ return d->notifyIdCounter++; } +void KNotificationManager::renotify(const QString& serv) +{ + if (!d->blockNotifications) { + return; + } + d->blockNotifications = false; + + Q_FOREACH (KNotification* n, d->notifications.values()) { + notify(n); + n->deref(); + } +} + void KNotificationManager::update(KNotification *n) { QByteArray pixmapData; diff --git a/src/knotificationmanager_p.h b/src/knotificationmanager_p.h --- a/src/knotificationmanager_p.h +++ b/src/knotificationmanager_p.h @@ -68,6 +68,7 @@ void notificationActivated(int id, int action); void notifyPluginFinished(KNotification *notification); void reparseConfiguration(const QString &app); + void renotify(const QString&); private: struct Private; diff --git a/src/notifybypopup.cpp b/src/notifybypopup.cpp --- a/src/notifybypopup.cpp +++ b/src/notifybypopup.cpp @@ -185,23 +185,21 @@ #ifdef Q_WS_WIN startfdo = true; #else - if (qEnvironmentVariableIsEmpty("KDE_FULL_SESSION")) { - QDBusMessage message = QDBusMessage::createMethodCall(QStringLiteral("org.freedesktop.DBus"), - QStringLiteral("/org/freedesktop/DBus"), - QStringLiteral("org.freedesktop.DBus"), - QStringLiteral("ListActivatableNames")); - - // FIXME - this should be async - QDBusReply reply = QDBusConnection::sessionBus().call(message); - if (reply.isValid() && reply.value().contains(dbusServiceName)) { - startfdo = true; - // We need to set d->dbusServiceExists to true because dbus might be too slow - // starting the service and the first call to NotifyByPopup::notify - // might not have had the service up, by setting this to true we - // guarantee it will still go through dbus and dbus will do the correct - // thing and wait for the service to go up - d->dbusServiceExists = true; - } + QDBusMessage message = QDBusMessage::createMethodCall(QStringLiteral("org.freedesktop.DBus"), + QStringLiteral("/org/freedesktop/DBus"), + QStringLiteral("org.freedesktop.DBus"), + QStringLiteral("ListActivatableNames")); + + // FIXME - this should be async + QDBusReply reply = QDBusConnection::sessionBus().call(message); + if (reply.isValid() && reply.value().contains(dbusServiceName)) { + startfdo = true; + // We need to set d->dbusServiceExists to true because dbus might be too slow + // starting the service and the first call to NotifyByPopup::notify + // might not have had the service up, by setting this to true we + // guarantee it will still go through dbus and dbus will do the correct + // thing and wait for the service to go up + d->dbusServiceExists = true; } #endif if (startfdo) { @@ -438,9 +436,9 @@ d->dbusServiceCapCacheDirty = true; d->popupServerCapabilities.clear(); - d->notificationQueue.clear(); if (newOwner.isEmpty()) { + d->notificationQueue.clear(); d->dbusServiceExists = false; } else if (oldOwner.isEmpty()) { d->dbusServiceExists = true; diff --git a/src/waitforname/CMakeLists.txt b/src/waitforname/CMakeLists.txt new file mode 100644 --- /dev/null +++ b/src/waitforname/CMakeLists.txt @@ -0,0 +1,20 @@ + +set(knotifications_waitforname_SRCS + main.cpp + ) + +ecm_qt_declare_logging_category(knotifications_waitforname_SRCS HEADER debug_p.h IDENTIFIER LOG_KNOTIFICATIONS CATEGORY_NAME org.kde.knotifications) + +add_executable(knotifications_waitforname ${knotifications_waitforname_SRCS}) + +target_link_libraries(knotifications_waitforname + Qt5::DBus + ) + +configure_file(org.freedesktop.Notifications.service.in + ${CMAKE_CURRENT_BINARY_DIR}/org.freedesktop.Notifications.service) + +install(TARGETS knotifications_waitforname ${KDE_INSTALL_TARGETS_DEFAULT_ARGS}) +install(FILES ${CMAKE_CURRENT_BINARY_DIR}/org.freedesktop.Notifications.service + DESTINATION ${KDE_INSTALL_DBUSSERVICEDIR}) + diff --git a/src/waitforname/main.cpp b/src/waitforname/main.cpp new file mode 100644 --- /dev/null +++ b/src/waitforname/main.cpp @@ -0,0 +1,118 @@ +/* + * Copyright © 2017 Valerio Pilo + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License version 2 as + * published by the Free Software Foundation + * + * This program 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 General Public License for more details + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +#include "debug_p.h" + + +constexpr static const char dbusServiceName[] = "org.freedesktop.Notifications"; + + +class Waiter : public QCoreApplication { + Q_OBJECT +private: + constexpr static const int dbusTimeoutSec = 60; + + QString mService = dbusServiceName; + QTimer mTimeoutTimer; + +public: + Waiter(int argc, char **argv) + : QCoreApplication(argc, argv) { + setApplicationName(QStringLiteral("knotifications_waitforname")); + setApplicationVersion(QStringLiteral("1.0")); + + QCommandLineParser parser; + parser.setApplicationDescription(QStringLiteral("Waits for D-Bus registration of the Notifications service.\n" + "Prevents notifications from being processed before the desktop is ready.")); + parser.addHelpOption(); + parser.addVersionOption(); + parser.addPositionalArgument(QStringLiteral("service"), + QStringLiteral("Optionally listen for a service different than '%1'").arg(dbusServiceName), + QStringLiteral("[service]")); + parser.process(*this); + + const QStringList args = parser.positionalArguments(); + if (!args.isEmpty()) { + mService = args.at(0); + } + } + + bool waitForService() { + QDBusConnection sessionBus = QDBusConnection::sessionBus(); + + if (sessionBus.interface()->isServiceRegistered(mService)) { + qCDebug(LOG_KNOTIFICATIONS) << "WaitForName: Service" << mService << "is already registered"; + return false; + } + + qCDebug(LOG_KNOTIFICATIONS) << "WaitForName: Waiting for appearance of service" << mService << "for" << dbusTimeoutSec << "seconds"; + + QDBusServiceWatcher* watcher = new QDBusServiceWatcher(this); + watcher->setConnection(sessionBus); + watcher->setWatchMode(QDBusServiceWatcher::WatchForRegistration); + watcher->addWatchedService(mService); + connect(watcher, &QDBusServiceWatcher::serviceRegistered, + this, &Waiter::registered); + + mTimeoutTimer.setSingleShot(true); + mTimeoutTimer.setInterval(dbusTimeoutSec * 1000); + connect(&mTimeoutTimer, &QTimer::timeout, this, &Waiter::timeout); + mTimeoutTimer.start(); + + return true; + } + +private Q_SLOTS: + void registered() { + qCDebug(LOG_KNOTIFICATIONS) << "WaitForName: Service was registered after" << (dbusTimeoutSec - (mTimeoutTimer.remainingTime() / 1000)) << "seconds"; + mTimeoutTimer.stop(); + exit(0); + } + + void timeout() { + qCInfo(LOG_KNOTIFICATIONS) << "WaitForName: Service was not registered within timeout"; + exit(1); + } +}; + + +int main(int argc, char **argv) +{ + Waiter app(argc, argv); + + if (!app.waitForService()) { + return 0; + } + + return app.exec(); +} + + +#include "main.moc" diff --git a/src/waitforname/org.freedesktop.Notifications.service.in b/src/waitforname/org.freedesktop.Notifications.service.in new file mode 100644 --- /dev/null +++ b/src/waitforname/org.freedesktop.Notifications.service.in @@ -0,0 +1,3 @@ +[D-BUS Service] +Name=org.freedesktop.Notifications +Exec=@CMAKE_INSTALL_PREFIX@/bin/knotifications_waitforname org.freedesktop.Notifications