diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -39,6 +39,17 @@
list(APPEND knotifications_SRCS notifybyandroid.cpp knotifications.qrc)
endif()
+if (WIN32)
+ find_package(LibSnoreToast REQUIRED)
+ set_package_properties(LibSnoreToast PROPERTIES TYPE REQUIRED
+ PURPOSE "Enable support for the Windows Toast Notifications with SnoreToast back-end for KNotifications"
+ DESCRIPTION "A command line application which is capable of creating Windows Toast notifications on Windows 8 or later."
+ )
+ find_package(Qt5Core REQUIRED)
+ find_package(Qt5Network REQUIRED)
+ list(APPEND knotifications_SRCS notifybysnore.cpp)
+ endif ()
+
ecm_qt_declare_logging_category(knotifications_SRCS HEADER debug_p.h IDENTIFIER LOG_KNOTIFICATIONS CATEGORY_NAME org.kde.knotifications)
if (Canberra_FOUND)
@@ -97,6 +108,9 @@
KF5::WindowSystem
KF5::Codecs
)
+if (TARGET SnoreToast::SnoreToastActions)
+ target_link_libraries(KF5Notifications PRIVATE Qt5::Core Qt5::Network SnoreToast::SnoreToastActions)
+endif ()
if (Phonon4Qt5_FOUND)
target_link_libraries(KF5Notifications PRIVATE
diff --git a/src/knotification.cpp b/src/knotification.cpp
--- a/src/knotification.cpp
+++ b/src/knotification.cpp
@@ -376,10 +376,12 @@
static QString defaultComponentName()
{
-#ifndef Q_OS_ANDROID
- return QStringLiteral("plasma_workspace");
-#else
+#if defined(Q_OS_ANDROID)
return QStringLiteral("android_defaults");
+#elif defined(Q_OS_WIN)
+ return QStringLiteral("win32_defaults");
+#else
+ return QStringLiteral("plasma_workspace");
#endif
}
diff --git a/src/knotificationmanager.cpp b/src/knotificationmanager.cpp
--- a/src/knotificationmanager.cpp
+++ b/src/knotificationmanager.cpp
@@ -40,12 +40,16 @@
#include "notifybylogfile.h"
#include "notifybytaskbar.h"
#include "notifybyexecute.h"
-#ifndef Q_OS_ANDROID
+
+#if defined(Q_OS_ANDROID)
+#include "notifybyandroid.h"
+#elif defined(Q_OS_WIN)
+#include "notifybysnore.h"
+#else
#include "notifybypopup.h"
#include "notifybyportal.h"
-#else
-#include "notifybyandroid.h"
#endif
+
#include "debug_p.h"
#if defined(HAVE_CANBERRA)
@@ -91,10 +95,10 @@
d->notifyPlugins.clear();
#ifdef QT_DBUS_LIB
- const bool inSandbox = QFileInfo::exists(QLatin1String("/.flatpak-info")) || qEnvironmentVariableIsSet("SNAP");
+ const bool inSandbox = QFileInfo::exists(QLatin1String("/.flatpak-info")) || qEnvironmentVariableIsSet("SNAP");
- if (inSandbox) {
- QDBusConnectionInterface *interface = QDBusConnection::sessionBus().interface();
+ if (inSandbox) {
+ QDBusConnectionInterface *interface = QDBusConnection::sessionBus().interface();
d->portalDBusServiceExists = interface->isServiceRegistered(QStringLiteral("org.freedesktop.portal.Desktop"));
}
@@ -133,24 +137,25 @@
// We have a series of built-ins up first, and fall back to trying
// to instantiate an externally supplied plugin.
if (action == QLatin1String("Popup")) {
-#ifndef Q_OS_ANDROID
+#if defined(Q_OS_ANDROID)
+ plugin = new NotifyByAndroid(this);
+#elif defined(Q_OS_WIN)
+ plugin = new NotifyBySnore(this);
+#else
if (d->portalDBusServiceExists) {
plugin = new NotifyByPortal(this);
} else {
plugin = new NotifyByPopup(this);
- }
-#else
- plugin = new NotifyByAndroid(this);
+ }
#endif
-
addPlugin(plugin);
} else if (action == QLatin1String("Taskbar")) {
plugin = new NotifyByTaskbar(this);
addPlugin(plugin);
} else if (action == QLatin1String("Sound")) {
#if defined(HAVE_PHONON4QT5) || defined(HAVE_CANBERRA)
- plugin = new NotifyByAudio(this);
- addPlugin(plugin);
+ plugin = new NotifyByAudio(this);
+ addPlugin(plugin);
#endif
} else if (action == QLatin1String("Execute")) {
plugin = new NotifyByExecute(this);
diff --git a/src/notifybyportal.h b/src/notifybyportal.h
--- a/src/notifybyportal.h
+++ b/src/notifybyportal.h
@@ -21,44 +21,348 @@
License along with this library. If not, see .
*/
-#ifndef NOTIFYBYPORTAL_H
-#define NOTIFYBYPORTAL_H
+#include "notifybyportal.h"
-#include "knotificationplugin.h"
+#include "knotifyconfig.h"
+#include "knotification.h"
+#include "debug_p.h"
-#include
-#include
-#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
-class KNotification;
-class NotifyByPortalPrivate;
+#include
+static const char portalDbusServiceName[] = "org.freedesktop.portal.Desktop";
+static const char portalDbusInterfaceName[] = "org.freedesktop.portal.Notification";
+static const char portalDbusPath[] = "/org/freedesktop/portal/desktop";
-class NotifyByPortal : public KNotificationPlugin
-{
- Q_OBJECT
+class NotifyByPortalPrivate {
public:
- explicit NotifyByPortal(QObject *parent = nullptr);
- ~NotifyByPortal() override;
+ struct PortalIcon {
+ QString str;
+ QDBusVariant data;
+ };
+
+ NotifyByPortalPrivate(NotifyByPortal *parent) : dbusServiceExists(false), q(parent) {}
+
+ /**
+ * Sends notification to DBus "org.freedesktop.notifications" interface.
+ * @param id knotify-sid identifier of notification
+ * @param config notification data
+ * @param update If true, will request the DBus service to update
+ the notification with new data from \c notification
+ * Otherwise will put new notification on screen
+ * @return true for success or false if there was an error.
+ */
+ bool sendNotificationToPortal(KNotification *notification, const KNotifyConfig &config);
+
+ /**
+ * Sends request to close Notification with id to DBus "org.freedesktop.notifications" interface
+ * @param id knotify-side notification ID to close
+ */
- QString optionName() override { return QStringLiteral("Popup"); }
- void notify(KNotification *notification, KNotifyConfig *notifyConfig) override;
- void close(KNotification *notification) override;
- void update(KNotification *notification, KNotifyConfig *config) override;
+ void closePortalNotification(KNotification *notification);
+ /**
+ * Find the caption and the icon name of the application
+ */
-private Q_SLOTS:
+ void getAppCaptionAndIconName(const KNotifyConfig &config, QString *appCaption, QString *iconName);
- // slot to catch appearance or disappearance of org.freedesktop.Desktop DBus service
- void onServiceOwnerChanged(const QString &, const QString &, const QString &);
+ /**
+ * Specifies if DBus Notifications interface exists on session bus
+ */
+ bool dbusServiceExists;
- void onPortalNotificationActionInvoked(const QString &, const QString &, const QVariantList &);
+ /*
+ * As we communicate with the notification server over dbus
+ * we use only ids, this is for fast KNotifications lookup
+ */
+ QHash> portalNotifications;
-private:
- // TODO KF6, replace current public notify/update
- void notify(KNotification *notification, const KNotifyConfig ¬ifyConfig);
- void update(KNotification *notification, const KNotifyConfig ¬ifyConfig);
+ /*
+ * Holds the id that will be assigned to the next notification source
+ * that will be created
+ */
+ uint nextId;
- NotifyByPortalPrivate * const d;
+ NotifyByPortal * const q;
};
-#endif
+QDBusArgument &operator<<(QDBusArgument &argument, const NotifyByPortalPrivate::PortalIcon &icon)
+{
+ argument.beginStructure();
+ argument << icon.str << icon.data;
+ argument.endStructure();
+ return argument;
+}
+
+const QDBusArgument &operator>>(const QDBusArgument &argument, NotifyByPortalPrivate::PortalIcon &icon)
+{
+ argument.beginStructure();
+ argument >> icon.str >> icon.data;
+ argument.endStructure();
+ return argument;
+}
+
+Q_DECLARE_METATYPE(NotifyByPortalPrivate::PortalIcon)
+
+//---------------------------------------------------------------------------------------
+
+NotifyByPortal::NotifyByPortal(QObject *parent)
+ : KNotificationPlugin(parent),
+ d(new NotifyByPortalPrivate(this))
+{
+ // check if service already exists on plugin instantiation
+ QDBusConnectionInterface *interface = QDBusConnection::sessionBus().interface();
+ d->dbusServiceExists = interface && interface->isServiceRegistered(QString::fromLatin1(portalDbusServiceName));
+
+ if (d->dbusServiceExists) {
+ onServiceOwnerChanged(QString::fromLatin1(portalDbusServiceName), QString(), QStringLiteral("_")); //connect signals
+ }
+
+ // to catch register/unregister events from service in runtime
+ QDBusServiceWatcher *watcher = new QDBusServiceWatcher(this);
+ watcher->setConnection(QDBusConnection::sessionBus());
+ watcher->setWatchMode(QDBusServiceWatcher::WatchForOwnerChange);
+ watcher->addWatchedService(QString::fromLatin1(portalDbusServiceName));
+ connect(watcher,&QDBusServiceWatcher::serviceOwnerChanged, this, &NotifyByPortal::onServiceOwnerChanged);
+}
+
+
+NotifyByPortal::~NotifyByPortal()
+{
+ delete d;
+}
+
+void NotifyByPortal::notify(KNotification *notification, KNotifyConfig *notifyConfig)
+{
+ notify(notification, *notifyConfig);
+}
+
+void NotifyByPortal::notify(KNotification *notification, const KNotifyConfig ¬ifyConfig)
+{
+ if (d->portalNotifications.contains(notification->id())) {
+ // notification is already on the screen, do nothing
+ finish(notification);
+ return;
+ }
+
+ // check if Notifications DBus service exists on bus, use it if it does
+ if (d->dbusServiceExists) {
+ if (!d->sendNotificationToPortal(notification, notifyConfig)) {
+ finish(notification); //an error occurred.
+ }
+ }
+}
+
+void NotifyByPortal::close(KNotification *notification)
+{
+ if (d->dbusServiceExists) {
+ d->closePortalNotification(notification);
+ }
+}
+
+void NotifyByPortal::update(KNotification *notification, KNotifyConfig *notifyConfig)
+{
+ // TODO not supported by portals
+ Q_UNUSED(notification);
+ Q_UNUSED(notifyConfig);
+}
+
+void NotifyByPortal::onServiceOwnerChanged(const QString &serviceName, const QString &oldOwner, const QString &newOwner)
+{
+ Q_UNUSED(serviceName);
+ // close all notifications we currently hold reference to
+ for (KNotification *n : qAsConst(d->portalNotifications)) {
+ if (n) {
+ emit finished(n);
+ }
+ }
+
+ d->portalNotifications.clear();
+
+ if (newOwner.isEmpty()) {
+ d->dbusServiceExists = false;
+ } else if (oldOwner.isEmpty()) {
+ d->dbusServiceExists = true;
+ d->nextId = 1;
+
+ // connect to action invocation signals
+ bool connected = QDBusConnection::sessionBus().connect(QString(), // from any service
+ QString::fromLatin1(portalDbusPath),
+ QString::fromLatin1(portalDbusInterfaceName),
+ QStringLiteral("ActionInvoked"),
+ this,
+ SLOT(onPortalNotificationActionInvoked(QString,QString,QVariantList)));
+ if (!connected) {
+ qCWarning(LOG_KNOTIFICATIONS) << "warning: failed to connect to ActionInvoked dbus signal";
+ }
+ }
+}
+
+void NotifyByPortal::onPortalNotificationActionInvoked(const QString &id, const QString &action, const QVariantList ¶meter)
+{
+ Q_UNUSED(parameter);
+
+ auto iter = d->portalNotifications.find(id.toUInt());
+ if (iter == d->portalNotifications.end()) {
+ return;
+ }
+
+ KNotification *n = *iter;
+ if (n) {
+ emit actionInvoked(n->id(), action.toUInt());
+ } else {
+ d->portalNotifications.erase(iter);
+ }
+}
+
+void NotifyByPortalPrivate::getAppCaptionAndIconName(const KNotifyConfig ¬ifyConfig, QString *appCaption, QString *iconName)
+{
+ KConfigGroup globalgroup(&(*notifyConfig.eventsfile), QStringLiteral("Global"));
+ *appCaption = globalgroup.readEntry("Name", globalgroup.readEntry("Comment", notifyConfig.appname));
+
+ KConfigGroup eventGroup(&(*notifyConfig.eventsfile), QStringLiteral("Event/%1").arg(notifyConfig.eventid));
+ if (eventGroup.hasKey("IconName")) {
+ *iconName = eventGroup.readEntry("IconName", notifyConfig.appname);
+ } else {
+ *iconName = globalgroup.readEntry("IconName", notifyConfig.appname);
+ }
+}
+
+bool NotifyByPortalPrivate::sendNotificationToPortal(KNotification *notification, const KNotifyConfig ¬ifyConfig_nocheck)
+{
+ QDBusMessage dbusNotificationMessage;
+ dbusNotificationMessage = QDBusMessage::createMethodCall(QString::fromLatin1(portalDbusServiceName),
+ QString::fromLatin1(portalDbusPath),
+ QString::fromLatin1(portalDbusInterfaceName),
+ QStringLiteral("AddNotification"));
+
+ QVariantList args;
+ // Will be used only with xdg-desktop-portal
+ QVariantMap portalArgs;
+
+ QString appCaption;
+ QString iconName;
+ getAppCaptionAndIconName(notifyConfig_nocheck, &appCaption, &iconName);
+
+ //did the user override the icon name?
+ if (!notification->iconName().isEmpty()) {
+ iconName = notification->iconName();
+ }
+
+ QString title = notification->title().isEmpty() ? appCaption : notification->title();
+ QString text = notification->text();
+
+ if (!notification->defaultAction().isEmpty()) {
+ portalArgs.insert(QStringLiteral("default-action"), notification->defaultAction());
+ portalArgs.insert(QStringLiteral("default-action-target"), QStringLiteral("0"));
+ }
+
+ QString priority;
+ switch (notification->urgency()) {
+ case KNotification::DefaultUrgency:
+ break;
+ case KNotification::LowUrgency:
+ priority = QStringLiteral("low");
+ break;
+ case KNotification::NormalUrgency:
+ priority = QStringLiteral("normal");
+ break;
+ case KNotification::HighUrgency:
+ priority = QStringLiteral("high");
+ break;
+ case KNotification::CriticalUrgency:
+ priority = QStringLiteral("urgent");
+ break;
+ }
+
+ if (!priority.isEmpty()) {
+ portalArgs.insert(QStringLiteral("priority"), priority);
+ }
+
+ // galago spec defines action list to be list like
+ // (act_id1, action1, act_id2, action2, ...)
+ //
+ // assign id's to actions like it's done in fillPopup() method
+ // (i.e. starting from 1)
+ QList buttons;
+ buttons.reserve(notification->actions().count());
+
+ int actId = 0;
+ const auto listActions = notification->actions();
+ for (const QString &actionName : listActions) {
+ actId++;
+ QVariantMap button = {
+ {QStringLiteral("action"), QString::number(actId)},
+ {QStringLiteral("label"), actionName}
+ };
+ buttons << button;
+ }
+
+ qDBusRegisterMetaType >();
+ qDBusRegisterMetaType();
+
+ if (!notification->pixmap().isNull()) {
+ QByteArray pixmapData;
+ QBuffer buffer(&pixmapData);
+ buffer.open(QIODevice::WriteOnly);
+ notification->pixmap().save(&buffer, "PNG");
+ buffer.close();
+
+ PortalIcon icon;
+ icon.str = QStringLiteral("bytes");
+ icon.data.setVariant(pixmapData);
+ portalArgs.insert(QStringLiteral("icon"), QVariant::fromValue(icon));
+ } else {
+ // Use this for now for backwards compatibility, we can as well set the variant to be (sv) where the
+ // string is keyword "themed" and the variant is an array of strings with icon names
+ portalArgs.insert(QStringLiteral("icon"), iconName);
+ }
+
+ portalArgs.insert(QStringLiteral("title"), title);
+ portalArgs.insert(QStringLiteral("body"), text);
+ portalArgs.insert(QStringLiteral("buttons"), QVariant::fromValue >(buttons));
+
+ args.append(QString::number(nextId));
+ args.append(portalArgs);
+
+ dbusNotificationMessage.setArguments(args);
+
+ QDBusPendingCall notificationCall = QDBusConnection::sessionBus().asyncCall(dbusNotificationMessage, -1);
+
+ // If we are in sandbox we don't need to wait for returned notification id
+ portalNotifications.insert(nextId++, notification);
+
+ return true;
+}
+
+void NotifyByPortalPrivate::closePortalNotification(KNotification *notification)
+{
+ uint id = portalNotifications.key(notification, 0);
+
+ qCDebug(LOG_KNOTIFICATIONS) << "ID: " << id;
+
+ if (id == 0) {
+ qCDebug(LOG_KNOTIFICATIONS) << "not found dbus id to close" << notification->id();
+ return;
+ }
+
+ QDBusMessage m = QDBusMessage::createMethodCall(QString::fromLatin1(portalDbusServiceName),
+ QString::fromLatin1(portalDbusPath),
+ QString::fromLatin1(portalDbusInterfaceName),
+ QStringLiteral("RemoveNotification"));
+ m.setArguments({QString::number(id)});
+
+ // send(..) does not block
+ bool queued = QDBusConnection::sessionBus().send(m);
+ if (!queued) {
+ qCWarning(LOG_KNOTIFICATIONS) << "Failed to queue dbus message for closing a notification";
+ }
+}
diff --git a/src/notifybysnore.h b/src/notifybysnore.h
new file mode 100644
--- /dev/null
+++ b/src/notifybysnore.h
@@ -0,0 +1,50 @@
+/*
+ Copyright (C) 2019 Piyush Aggarwal
+
+ This program is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Library General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or (at your
+ option) any later version.
+
+ 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 Library General Public
+ License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+*/
+
+#ifndef NOTIFYBYSNORE_H
+#define NOTIFYBYSNORE_H
+
+#include "knotificationplugin.h"
+
+#include
+#include
+#include
+#include
+#include
+
+
+/** Windows notification backend - inspired by Android notification backend. */
+class NotifyBySnore : public KNotificationPlugin
+{
+ Q_OBJECT
+
+public:
+ explicit NotifyBySnore(QObject *parent = nullptr);
+ ~NotifyBySnore() override;
+
+ QString optionName() override { return QStringLiteral("Popup"); }
+ void notify(KNotification *notification, KNotifyConfig *config) override;
+ void close(KNotification * notification) override;
+ void update(KNotification *notification, KNotifyConfig *config) override;
+private:
+ QMap> m_notifications;
+ QString program = QStringLiteral("SnoreToast.exe");
+ QLocalServer *server;
+ QTemporaryDir *iconDir;
+};
+
+#endif // NOTIFYBYSNORE_H
diff --git a/src/notifybysnore.cpp b/src/notifybysnore.cpp
new file mode 100644
--- /dev/null
+++ b/src/notifybysnore.cpp
@@ -0,0 +1,189 @@
+/*
+ Copyright (C) 2019 Piyush Aggarwal
+
+ This program is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Library General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or (at your
+ option) any later version.
+
+ 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 Library General Public
+ License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+*/
+
+#include "notifybysnore.h"
+#include "knotification.h"
+#include "knotifyconfig.h"
+#include "debug_p.h"
+
+#include
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+
+/**
+ * Be sure to have a shortcut installed in Windows Start Menu by SnoreToast
+ * The syntax is -
+ * ./SnoreToast.exe -install
+ *
+ * appID : use as-is from your app's QCoreApplication::applicationName() when installing the shortcut.
+ * NOTE: Install the shortcut in Windows Start Menu.
+ */
+
+NotifyBySnore::NotifyBySnore(QObject* parent) :
+ KNotificationPlugin(parent)
+{
+ server.listen(QString::fromStdString(QCryptographicHash::hash(qApp->applicationDirPath().toUtf8(), \
+ QCryptographicHash::Md5 ).toHex().toStdString()).left(5));
+ // ^ can be increased from 5 to N for lesser collisions
+
+ QObject::connect(&server, &QLocalServer::newConnection, &server, [this]() {
+ auto sock = server.nextPendingConnection();
+ sock->waitForReadyRead();
+ const QByteArray rawData = sock->readAll();
+ sock->close();
+ const QString data =
+ QString::fromWCharArray(reinterpret_cast(rawData.constData()),
+ rawData.size() / sizeof(wchar_t));
+ QMap map;
+ for (const auto &str : data.split(QStringLiteral(";"))) {
+ const auto index = str.indexOf(QStringLiteral("="));
+ map.insert(str.mid(0, index), str.mid(index + 1));
+ }
+ const auto action = map[QStringLiteral("action")];
+ const auto id = map[QStringLiteral("notificationId")].toInt();
+ KNotification *notification = nullptr;
+ const auto it = m_notifications.find(id);
+ if (it != m_notifications.end()) {
+ notification = it.value();
+ }
+ const auto snoreAction = SnoreToastActions::getAction(action.toStdWString());
+ qCDebug(LOG_KNOTIFICATIONS) << "The notification ID is : " << id;
+ switch (snoreAction) {
+ case SnoreToastActions::Actions::Clicked :
+ qCDebug(LOG_KNOTIFICATIONS) << " User clicked on the toast.";
+ if (notification) {
+ close(notification);
+ }
+ break;
+ case SnoreToastActions::Actions::Hidden :
+ qCDebug(LOG_KNOTIFICATIONS) << "The toast got hidden.";
+ break;
+ case SnoreToastActions::Actions::Dismissed :
+ qCDebug(LOG_KNOTIFICATIONS) << "User dismissed the toast.";
+ break;
+ case SnoreToastActions::Actions::Timedout :
+ qCDebug(LOG_KNOTIFICATIONS) << "The toast timed out.";
+ break;
+ case SnoreToastActions::Actions::ButtonClicked :{
+ qCDebug(LOG_KNOTIFICATIONS) << " User clicked a button on the toast.";
+ const auto button = map[QStringLiteral("button")];
+ QStringList s = m_notifications.value(id)->actions();
+ int actionNum = s.indexOf(button) + 1; // QStringList starts with index 0 but not actions
+ emit actionInvoked(id, actionNum);
+ break;}
+ case SnoreToastActions::Actions::TextEntered :
+ qCDebug(LOG_KNOTIFICATIONS) << " User entered some text in the toast.";
+ break;
+ default:
+ qCDebug(LOG_KNOTIFICATIONS) << "Unexpected behaviour with the toast.";
+ if (notification) {
+ close(notification);
+ }
+ break;
+ }
+ });
+}
+
+NotifyBySnore::~NotifyBySnore()
+{
+ server.close();
+ iconDir.remove();
+}
+
+void NotifyBySnore::notify(KNotification *notification, KNotifyConfig *config)
+{
+ if (m_notifications.constFind(notification->id()) != m_notifications.constEnd()) {
+ qCDebug(LOG_KNOTIFICATIONS) << "Duplicate notification with ID: " << notification->id() << " ignored.";
+ return;
+ }
+ QProcess *proc = new QProcess();
+ QStringList arguments;
+ QString iconPath;
+
+ arguments << QStringLiteral("-t");
+ if (!notification->title().isEmpty()) {
+ arguments << notification->title();
+ }
+ else {
+ arguments << qApp->applicationDisplayName();
+ }
+ arguments << QStringLiteral("-m") << notification->text();
+ if (!notification->pixmap().isNull()) {
+ iconPath = iconDir.path() + QStringLiteral("/")
+ + QString::number(notification->id()) + QStringLiteral(".png");
+ notification->pixmap().save(iconPath, "PNG");
+ arguments << QStringLiteral("-p") << iconPath;
+ }
+ arguments << QStringLiteral("-appID") << qApp->applicationName()
+ << QStringLiteral("-id") << QString::number(notification->id())
+ << QStringLiteral("-pipename") << server.fullServerName();
+
+ if (!notification->actions().isEmpty()) {
+ arguments << QStringLiteral("-b") << notification->actions().join(QStringLiteral(";"));
+ }
+ qCDebug(LOG_KNOTIFICATIONS) << arguments;
+
+ m_notifications.insert(notification->id(), notification);
+ proc->start(program, arguments);
+
+ connect(proc, QOverload::of(&QProcess::finished),
+ [=](int exitCode, QProcess::ExitStatus exitStatus){
+ proc->deleteLater();
+ });
+}
+
+void NotifyBySnore::close(KNotification *notification)
+{
+ const auto it = m_notifications.find(notification->id());
+ if (it == m_notifications.end()) {
+ return;
+ }
+
+ qCDebug(LOG_KNOTIFICATIONS) << "SnoreToast closing notification with ID: " << notification->id();
+
+ QProcess *proc = new QProcess();
+ QStringList arguments;
+ arguments << QStringLiteral("-close") << QString::number(notification->id())
+ << QStringLiteral("-appID") << qApp->applicationName();
+ proc->start(program, arguments);
+ if (it.value()) {
+ finish(it.value());
+ }
+ m_notifications.erase(it);
+ connect(proc, QOverload::of(&QProcess::finished),
+ [=](int exitCode, QProcess::ExitStatus exitStatus){
+ proc->deleteLater();
+ delete proc;
+ });
+}
+
+void NotifyBySnore::update(KNotification *notification, KNotifyConfig *config)
+{
+ close(notification);
+ notify(notification, config);
+}