diff --git a/CMakeLists.txt b/CMakeLists.txt
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -66,26 +66,42 @@
if(APPLE)
find_package(Qt5MacExtras ${REQUIRED_QT_VERSION} REQUIRED NO_MODULE)
endif()
+if (ANDROID)
+ find_package(Qt5 ${REQUIRED_QT_VERSION} CONFIG REQUIRED AndroidExtras)
+ find_package(Gradle REQUIRED)
+endif()
+if (WIN32)
+ find_package(LibSnoreToast REQUIRED)
+ set_package_properties(LibSnoreToast PROPERTIES TYPE REQUIRED
+ PURPOSE "for the Windows Toast Notifications"
+ DESCRIPTION "A command line application, capable of creating
+ Windows Toast notifications on Windows (>=)8 "
+ )
+ find_package(Qt5Network REQUIRED)
+endif()
find_package(KF5WindowSystem ${KF5_DEP_VERSION} REQUIRED)
find_package(KF5Config ${KF5_DEP_VERSION} REQUIRED)
find_package(KF5Codecs ${KF5_DEP_VERSION} REQUIRED)
find_package(KF5CoreAddons ${KF5_DEP_VERSION} REQUIRED)
-find_package(Canberra)
-set_package_properties(Canberra PROPERTIES DESCRIPTION "Library for generating event sounds"
- PURPOSE "Needed to build audio notification support"
- URL "http://0pointer.de/lennart/projects/libcanberra"
- TYPE OPTIONAL)
-if (Canberra_FOUND)
- add_definitions(-DHAVE_CANBERRA)
-elseif(NOT ANDROID)
- # This is REQUIRED since you cannot tell CMake "either one of those two optional ones are required"
- find_package(Phonon4Qt5 4.6.60 NO_MODULE REQUIRED)
- set_package_properties(Phonon4Qt5 PROPERTIES
- DESCRIPTION "Qt-based audio library"
- PURPOSE "Needed to build audio notification support when Canberra isn't available")
+if (NOT WIN32)
+ find_package(Qt5 ${REQUIRED_QT_VERSION} CONFIG REQUIRED DBus)
+ find_package(Canberra)
+ set_package_properties(Canberra PROPERTIES DESCRIPTION "Library for generating event sounds"
+ PURPOSE "Needed to build audio notification support"
+ URL "http://0pointer.de/lennart/projects/libcanberra"
+ TYPE OPTIONAL)
+ if (Canberra_FOUND)
+ add_definitions(-DHAVE_CANBERRA)
+ else()
+ # This is REQUIRED since you cannot tell CMake "either one of those two optional ones are required"
+ find_package(Phonon4Qt5 4.6.60 NO_MODULE REQUIRED)
+ set_package_properties(Phonon4Qt5 PROPERTIES
+ DESCRIPTION "Qt-based audio library"
+ PURPOSE "Needed to build audio notification support when Canberra isn't available")
add_definitions(-DHAVE_PHONON4QT5)
+ endif()
endif()
remove_definitions(-DQT_NO_CAST_FROM_BYTEARRAY)
@@ -119,6 +135,11 @@
set(HAVE_DBUS FALSE)
if (TARGET Qt5::DBus)
+ find_package(dbusmenu-qt5 CONFIG)
+ set_package_properties(dbusmenu-qt5 PROPERTIES DESCRIPTION "DBusMenuQt"
+ URL "https://launchpad.net/libdbusmenu-qt" TYPE OPTIONAL
+ PURPOSE "Support for notification area menus via the DBusMenu protocol")
+
set(HAVE_DBUS TRUE)
endif()
configure_package_config_file(
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -39,6 +39,10 @@
list(APPEND knotifications_SRCS notifybyandroid.cpp knotifications.qrc)
endif()
+if (WIN32)
+ 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)
@@ -54,27 +58,25 @@
notifybytts.cpp)
endif()
-find_package(dbusmenu-qt5 CONFIG)
-set_package_properties(dbusmenu-qt5 PROPERTIES DESCRIPTION "DBusMenuQt"
- URL "https://launchpad.net/libdbusmenu-qt" TYPE OPTIONAL
- PURPOSE "Support for notification area menus via the DBusMenu protocol")
-if (dbusmenu-qt5_FOUND)
- message("dbusmenu-qt5_FOUND")
- set(HAVE_DBUSMENUQT 1)
- include_directories(${dbusmenu-qt5_INCLUDE_DIRS})
-else()
- set(HAVE_DBUSMENUQT 0)
-endif()
-
if (TARGET Qt5::DBus)
- qt5_add_dbus_adaptor(knotifications_SRCS org.kde.StatusNotifierItem.xml
- kstatusnotifieritemdbus_p.h KStatusNotifierItemDBus)
+ if (dbusmenu-qt5_FOUND)
+ message("dbusmenu-qt5_FOUND")
+ set(HAVE_DBUSMENUQT 1)
+ include_directories(${dbusmenu-qt5_INCLUDE_DIRS})
+ else()
+ set(HAVE_DBUSMENUQT 0)
+ endif()
+
+ if (TARGET Qt5::DBus)
+ qt5_add_dbus_adaptor(knotifications_SRCS org.kde.StatusNotifierItem.xml
+ kstatusnotifieritemdbus_p.h KStatusNotifierItemDBus)
- set(statusnotifierwatcher_xml org.kde.StatusNotifierWatcher.xml)
- qt5_add_dbus_interface(knotifications_SRCS ${statusnotifierwatcher_xml} statusnotifierwatcher_interface)
- set(notifications_xml org.freedesktop.Notifications.xml)
- qt5_add_dbus_interface(knotifications_SRCS ${notifications_xml} notifications_interface)
+ set(statusnotifierwatcher_xml org.kde.StatusNotifierWatcher.xml)
+ qt5_add_dbus_interface(knotifications_SRCS ${statusnotifierwatcher_xml} statusnotifierwatcher_interface)
+ set(notifications_xml org.freedesktop.Notifications.xml)
+ qt5_add_dbus_interface(knotifications_SRCS ${notifications_xml} notifications_interface)
+ endif()
endif()
configure_file(config-knotifications.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-knotifications.h )
@@ -97,6 +99,9 @@
KF5::WindowSystem
KF5::Codecs
)
+if (TARGET SnoreToast::SnoreToastActions)
+ target_link_libraries(KF5Notifications PRIVATE 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
@@ -372,10 +372,10 @@
static QString defaultComponentName()
{
-#ifndef Q_OS_ANDROID
- return QStringLiteral("plasma_workspace");
-#else
+#if defined(Q_OS_ANDROID)
return QStringLiteral("android_defaults");
+#else
+ return QStringLiteral("plasma_workspace");
#endif
}
@@ -573,3 +573,5 @@
return d->hints;
}
+}
+
diff --git a/src/knotificationmanager.cpp b/src/knotificationmanager.cpp
--- a/src/knotificationmanager.cpp
+++ b/src/knotificationmanager.cpp
@@ -37,11 +37,14 @@
#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"
@@ -88,10 +91,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"));
}
@@ -130,24 +133,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);
@@ -354,4 +358,4 @@
}
}
-#include "moc_knotificationmanager_p.cpp"
+#include "moc_knotificationmanager_p.cpp"
\ No newline at end of file
diff --git a/src/knotifications.qrc b/src/knotifications.qrc
--- a/src/knotifications.qrc
+++ b/src/knotifications.qrc
@@ -1,5 +1,6 @@
android_defaults.notifyrc
+ win32_defaults.notifyrc
diff --git a/src/notifybysnore.h b/src/notifybysnore.h
new file mode 100644
--- /dev/null
+++ b/src/notifybysnore.h
@@ -0,0 +1,49 @@
+/*
+ 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:
+ QHash> m_notifications;
+ QString m_program = QStringLiteral("SnoreToast.exe");
+ QLocalServer m_server;
+ QTemporaryDir m_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,201 @@
+/*
+ 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
+
+/*
+ * On Windows a shortcut to your app is needed to be installed in the Start Menu
+ * (and subsequently, registered with the OS) in order to show notifications.
+ * Since KNotifications is a library, an app using it can't (feasibly) be properly
+ * registered with the OS. It is possible we could come up with some complicated solution
+ * which would require every KNotification-using app to do some special and probably
+ * difficult to understand change to support Windows. Or we can have SnoreToast.exe
+ * take care of all that nonsense for us.
+ * Note that, up to this point, there have been no special
+ * KNotifications changes to the generic application codebase to make this work,
+ * just some tweaks to the Craft blueprint and packaging script
+ * to pull in SnoreToast and trigger shortcut building respectively.
+ * Be sure to have a shortcut installed in Windows Start Menu by SnoreToast.
+ *
+ * So the location doesn't matter, but it's only possible to register the internal COM server in an executable.
+ * We could make it a static lib and link it in all KDE applications,
+ * but to make the action center integration work, we would need to also compile a class
+ * into the executable using a compile time uuid.
+ *
+ * The used header is meant to help with parsing the response.
+ * The cmake target for LibSnoreToast is a INTERFACE lib, it only provides the include path.
+ *
+ *
+ * Trigger the shortcut installation during the installation of your app; syntax for shortcut installation 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 folder.
+ * For example, check out Craft Blueprint for Quassel-IRC or KDE Connect.
+*/
+
+NotifyBySnore::NotifyBySnore(QObject* parent) :
+ KNotificationPlugin(parent)
+{
+ m_server.listen(QString::number(qHash(qApp->applicationDirPath())));
+ connect(&m_server, &QLocalServer::newConnection, &m_server, [this]() {
+ auto sock = m_server.nextPendingConnection();
+ sock->waitForReadyRead();
+ const QByteArray rawData = sock->readAll();
+ sock->deleteLater();
+ const QString data =
+ QString::fromWCharArray(reinterpret_cast(rawData.constData()),
+ rawData.size() / sizeof(wchar_t));
+ QMap map;
+ const auto parts = data.splitRef(QLatin1Char(';'));
+ for (auto &str : parts) {
+ const auto index = str.indexOf(QLatin1Char('='));
+ map.insert(str.mid(0, index).toString(), str.mid(index + 1));
+ }
+ const auto action = map[QStringLiteral("action")].toString();
+ const auto id = map[QStringLiteral("notificationId")].toInt();
+ KNotification *notification;
+ const auto it = m_notifications.constFind(id);
+ if (it != m_notifications.constEnd()) {
+ notification = it.value();
+ }
+ else {
+ qCDebug(LOG_KNOTIFICATIONS) << "Notification not found!";
+ return;
+ }
+
+ // MSVC2019 has issues with QString::toStdWString()
+ // Qstring::toStdWString() doesn't work with MSVC2019 yet. If it gets fixed
+ // in future, feel free to change the implementation below for lesser LOC.
+ std::wstring waction(action.size(), 0);
+ action.toWCharArray(const_cast(waction.data()));
+ const auto snoreAction = SnoreToastActions::getAction(waction);
+
+ 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")].toString();
+ 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()
+{
+ m_server.close();
+}
+
+void NotifyBySnore::notify(KNotification *notification, KNotifyConfig *config)
+{
+ QProcess *proc = new QProcess();
+ QStringList arguments;
+
+ arguments << QStringLiteral("-t");
+ if (!notification->title().isEmpty()) {
+ arguments << notification->title();
+ } else {
+ arguments << qApp->applicationDisplayName();
+ }
+ arguments << QStringLiteral("-m") << notification->text();
+ if (!notification->pixmap().isNull()) {
+ auto iconPath = QString(m_iconDir.path() + QLatin1Char('/')
+ + 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") << m_server.fullServerName();
+
+ if (!notification->actions().isEmpty()) {
+ arguments << QStringLiteral("-b") << notification->actions().join(QLatin1Char(';'));
+ }
+ qCDebug(LOG_KNOTIFICATIONS) << arguments;
+ proc->start(m_program, arguments);
+ m_notifications.insert(notification->id(), notification);
+ connect(proc, QOverload::of(&QProcess::finished),
+ [=](int exitCode, QProcess::ExitStatus exitStatus){
+ proc->deleteLater();
+ if (exitStatus != QProcess::NormalExit) {
+ qCDebug(LOG_KNOTIFICATIONS) << "SnoreToast crashed while trying to show a notification.";
+ close(notification);
+ }
+ QFile::remove(QString(m_iconDir.path() + QLatin1Char('/')
+ + QString::number(notification->id()) + QStringLiteral(".png")));
+ });
+}
+
+void NotifyBySnore::close(KNotification *notification)
+{
+ if (m_notifications.constFind(notification->id()) == m_notifications.constEnd()) {
+ return;
+ }
+ qCDebug(LOG_KNOTIFICATIONS) << "SnoreToast closing notification with ID: " << notification->id();
+ QStringList arguments;
+ arguments << QStringLiteral("-close") << QString::number(notification->id())
+ << QStringLiteral("-appID") << qApp->applicationName();
+ QProcess::startDetached(m_program, arguments);
+ if (notification) {
+ finish(notification);
+ }
+ m_notifications.remove(notification->id());
+}
+
+void NotifyBySnore::update(KNotification *notification, KNotifyConfig *config)
+{
+ close(notification);
+ notify(notification, config);
+}