diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,210 +1,210 @@ -if (Phonon4Qt5_FOUND) - include_directories(${PHONON_INCLUDE_DIR}) -endif() -if (CANBERRA_FOUND) - include_directories(${CANBERRA_INCLUDE_DIRS}) -endif() - -ecm_create_qm_loader(knotifications_QM_LOADER knotifications5_qt) - -set(knotifications_SRCS - - knotification.cpp - knotificationmanager.cpp - kpassivepopup.cpp - - knotifyconfig.cpp - knotificationplugin.cpp - notifybypopupgrowl.cpp - notifybyexecute.cpp - notifybylogfile.cpp - notifybytaskbar.cpp - ${knotifications_QM_LOADER} -) - -if (TARGET Qt5::DBus) - list(APPEND knotifications_SRCS - kstatusnotifieritem.cpp - kstatusnotifieritemdbus_p.cpp - knotificationrestrictions.cpp - - imageconverter.cpp #needed to marshal images for sending over dbus by NotifyByPopup - notifybypopup.cpp - notifybyportal.cpp - ) -endif() - -if (ANDROID) - add_subdirectory(android) - 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) - set(knotifications_SRCS ${knotifications_SRCS} - notifybyaudio_canberra.cpp) -elseif (Phonon4Qt5_FOUND) - set(knotifications_SRCS ${knotifications_SRCS} - notifybyaudio_phonon.cpp) -endif() - -if (Qt5TextToSpeech_FOUND) - set(knotifications_SRCS ${knotifications_SRCS} - notifybytts.cpp) -endif() - -if (TARGET Qt5::DBus) - 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) - endif() -endif() - -configure_file(config-knotifications.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-knotifications.h ) - -add_library(KF5Notifications ${knotifications_SRCS}) -generate_export_header(KF5Notifications BASE_NAME KNotifications) -add_library(KF5::Notifications ALIAS KF5Notifications) - -target_include_directories(KF5Notifications INTERFACE "$") - -target_link_libraries(KF5Notifications PUBLIC - Qt5::Widgets -) -if (TARGET Qt5::DBus) - target_link_libraries(KF5Notifications PUBLIC Qt5::DBus) -endif() -target_link_libraries(KF5Notifications PRIVATE - KF5::CoreAddons - KF5::ConfigCore - 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 - ${PHONON_LIBRARIES}) -endif() - -if (Canberra_FOUND) - target_link_libraries(KF5Notifications PRIVATE - Canberra::Canberra) -endif() - -if (Qt5TextToSpeech_FOUND) - target_link_libraries(KF5Notifications PRIVATE Qt5::TextToSpeech) -endif() - -if(X11_FOUND) - target_link_libraries(KF5Notifications PRIVATE ${X11_X11_LIB} Qt5::X11Extras) -endif() - -if(APPLE) - target_link_libraries(KF5Notifications PRIVATE Qt5::MacExtras) -endif() - -if(X11_XTest_FOUND) - target_link_libraries(KF5Notifications PRIVATE ${X11_XTest_LIB}) -endif() - -if(HAVE_DBUSMENUQT) - target_link_libraries(KF5Notifications PRIVATE dbusmenu-qt5) -endif() - -if (ANDROID) - target_link_libraries(KF5Notifications PRIVATE Qt5::AndroidExtras) -endif() - -set_target_properties(KF5Notifications PROPERTIES VERSION ${KNOTIFICATIONS_VERSION_STRING} - SOVERSION ${KNOTIFICATIONS_SOVERSION} - EXPORT_NAME Notifications -) - -ecm_generate_headers(KNotifications_HEADERS - HEADER_NAMES - KNotification - KPassivePopup - KStatusNotifierItem - KNotificationRestrictions - KNotificationPlugin - KNotifyConfig - - REQUIRED_HEADERS KNotifications_HEADERS -) - -install(TARGETS KF5Notifications EXPORT KF5NotificationsTargets ${KF5_INSTALL_TARGETS_DEFAULT_ARGS}) - -install(FILES - ${CMAKE_CURRENT_BINARY_DIR}/knotifications_export.h - ${KNotifications_HEADERS} - DESTINATION ${KDE_INSTALL_INCLUDEDIR_KF5}/KNotifications COMPONENT Devel -) - -if(BUILD_QCH) - ecm_add_qch( - KF5Notifications_QCH - NAME KNotifications - BASE_NAME KF5Notifications - VERSION ${KF5_VERSION} - ORG_DOMAIN org.kde - SOURCES # using only public headers, to cover only public API - ${KNotifications_HEADERS} - MD_MAINPAGE "${CMAKE_SOURCE_DIR}/README.md" - IMAGE_DIRS "${CMAKE_SOURCE_DIR}/docs/pics" - LINK_QCHS - Qt5Widgets_QCH - BLANK_MACROS - KNOTIFICATIONS_EXPORT - KNOTIFICATIONS_DEPRECATED - KNOTIFICATIONS_DEPRECATED_EXPORT - TAGFILE_INSTALL_DESTINATION ${KDE_INSTALL_QTQCHDIR} - QCH_INSTALL_DESTINATION ${KDE_INSTALL_QTQCHDIR} - COMPONENT Devel - ) -endif() - -if (TARGET Qt5::DBus) - install(FILES - org.kde.StatusNotifierItem.xml - DESTINATION ${KDE_INSTALL_DBUSINTERFACEDIR} - RENAME kf5_org.kde.StatusNotifierItem.xml) - - install(FILES - org.kde.StatusNotifierWatcher.xml - DESTINATION ${KDE_INSTALL_DBUSINTERFACEDIR} - RENAME kf5_org.kde.StatusNotifierWatcher.xml) -endif() - -install(FILES - knotificationplugin.desktop - DESTINATION ${KDE_INSTALL_KSERVICETYPES5DIR}) - -include(ECMGeneratePriFile) -ecm_generate_pri_file(BASE_NAME KNotifications LIB_NAME KF5Notifications DEPS "widgets" FILENAME_VAR PRI_FILENAME INCLUDE_INSTALL_DIR ${KDE_INSTALL_INCLUDEDIR_KF5}/KNotifications) -install(FILES ${PRI_FILENAME} DESTINATION ${ECM_MKSPECS_INSTALL_DIR}) - -if (ANDROID) - install(FILES KF5Notifications-android-dependencies.xml DESTINATION ${KDE_INSTALL_LIBDIR}) -endif() +if (Phonon4Qt5_FOUND) + include_directories(${PHONON_INCLUDE_DIR}) +endif() +if (CANBERRA_FOUND) + include_directories(${CANBERRA_INCLUDE_DIRS}) +endif() + +ecm_create_qm_loader(knotifications_QM_LOADER knotifications5_qt) + +set(knotifications_SRCS + + knotification.cpp + knotificationmanager.cpp + kpassivepopup.cpp + + knotifyconfig.cpp + knotificationplugin.cpp + notifybypopupgrowl.cpp + notifybyexecute.cpp + notifybylogfile.cpp + notifybytaskbar.cpp + ${knotifications_QM_LOADER} +) + +if (TARGET Qt5::DBus) + list(APPEND knotifications_SRCS + kstatusnotifieritem.cpp + kstatusnotifieritemdbus_p.cpp + knotificationrestrictions.cpp + + imageconverter.cpp #needed to marshal images for sending over dbus by NotifyByPopup + notifybypopup.cpp + notifybyportal.cpp + ) +endif() + +if (ANDROID) + add_subdirectory(android) + 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) + set(knotifications_SRCS ${knotifications_SRCS} + notifybyaudio_canberra.cpp) +elseif (Phonon4Qt5_FOUND) + set(knotifications_SRCS ${knotifications_SRCS} + notifybyaudio_phonon.cpp) +endif() + +if (Qt5TextToSpeech_FOUND) + set(knotifications_SRCS ${knotifications_SRCS} + notifybytts.cpp) +endif() + +if (TARGET Qt5::DBus) + 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) + endif() +endif() + +configure_file(config-knotifications.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-knotifications.h ) + +add_library(KF5Notifications ${knotifications_SRCS}) +generate_export_header(KF5Notifications BASE_NAME KNotifications) +add_library(KF5::Notifications ALIAS KF5Notifications) + +target_include_directories(KF5Notifications INTERFACE "$") + +target_link_libraries(KF5Notifications PUBLIC + Qt5::Widgets +) +if (TARGET Qt5::DBus) + target_link_libraries(KF5Notifications PUBLIC Qt5::DBus) +endif() +target_link_libraries(KF5Notifications PRIVATE + KF5::CoreAddons + KF5::ConfigCore + 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 + ${PHONON_LIBRARIES}) +endif() + +if (Canberra_FOUND) + target_link_libraries(KF5Notifications PRIVATE + Canberra::Canberra) +endif() + +if (Qt5TextToSpeech_FOUND) + target_link_libraries(KF5Notifications PRIVATE Qt5::TextToSpeech) +endif() + +if(X11_FOUND) + target_link_libraries(KF5Notifications PRIVATE ${X11_X11_LIB} Qt5::X11Extras) +endif() + +if(APPLE) + target_link_libraries(KF5Notifications PRIVATE Qt5::MacExtras) +endif() + +if(X11_XTest_FOUND) + target_link_libraries(KF5Notifications PRIVATE ${X11_XTest_LIB}) +endif() + +if(HAVE_DBUSMENUQT) + target_link_libraries(KF5Notifications PRIVATE dbusmenu-qt5) +endif() + +if (ANDROID) + target_link_libraries(KF5Notifications PRIVATE Qt5::AndroidExtras) +endif() + +set_target_properties(KF5Notifications PROPERTIES VERSION ${KNOTIFICATIONS_VERSION_STRING} + SOVERSION ${KNOTIFICATIONS_SOVERSION} + EXPORT_NAME Notifications +) + +ecm_generate_headers(KNotifications_HEADERS + HEADER_NAMES + KNotification + KPassivePopup + KStatusNotifierItem + KNotificationRestrictions + KNotificationPlugin + KNotifyConfig + + REQUIRED_HEADERS KNotifications_HEADERS +) + +install(TARGETS KF5Notifications EXPORT KF5NotificationsTargets ${KF5_INSTALL_TARGETS_DEFAULT_ARGS}) + +install(FILES + ${CMAKE_CURRENT_BINARY_DIR}/knotifications_export.h + ${KNotifications_HEADERS} + DESTINATION ${KDE_INSTALL_INCLUDEDIR_KF5}/KNotifications COMPONENT Devel +) + +if(BUILD_QCH) + ecm_add_qch( + KF5Notifications_QCH + NAME KNotifications + BASE_NAME KF5Notifications + VERSION ${KF5_VERSION} + ORG_DOMAIN org.kde + SOURCES # using only public headers, to cover only public API + ${KNotifications_HEADERS} + MD_MAINPAGE "${CMAKE_SOURCE_DIR}/README.md" + IMAGE_DIRS "${CMAKE_SOURCE_DIR}/docs/pics" + LINK_QCHS + Qt5Widgets_QCH + BLANK_MACROS + KNOTIFICATIONS_EXPORT + KNOTIFICATIONS_DEPRECATED + KNOTIFICATIONS_DEPRECATED_EXPORT + TAGFILE_INSTALL_DESTINATION ${KDE_INSTALL_QTQCHDIR} + QCH_INSTALL_DESTINATION ${KDE_INSTALL_QTQCHDIR} + COMPONENT Devel + ) +endif() + +if (TARGET Qt5::DBus) + install(FILES + org.kde.StatusNotifierItem.xml + DESTINATION ${KDE_INSTALL_DBUSINTERFACEDIR} + RENAME kf5_org.kde.StatusNotifierItem.xml) + + install(FILES + org.kde.StatusNotifierWatcher.xml + DESTINATION ${KDE_INSTALL_DBUSINTERFACEDIR} + RENAME kf5_org.kde.StatusNotifierWatcher.xml) +endif() + +install(FILES + knotificationplugin.desktop + DESTINATION ${KDE_INSTALL_KSERVICETYPES5DIR}) + +include(ECMGeneratePriFile) +ecm_generate_pri_file(BASE_NAME KNotifications LIB_NAME KF5Notifications DEPS "widgets" FILENAME_VAR PRI_FILENAME INCLUDE_INSTALL_DIR ${KDE_INSTALL_INCLUDEDIR_KF5}/KNotifications) +install(FILES ${PRI_FILENAME} DESTINATION ${ECM_MKSPECS_INSTALL_DIR}) + +if (ANDROID) + install(FILES KF5Notifications-android-dependencies.xml DESTINATION ${KDE_INSTALL_LIBDIR}) +endif() diff --git a/src/notifybysnore.cpp b/src/notifybysnore.cpp --- a/src/notifybysnore.cpp +++ b/src/notifybysnore.cpp @@ -1,201 +1,206 @@ -/* - 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); -} +/* + 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(); + const QString iconPath = m_iconDir.path() + QLatin1Char('/') + + QString::number(notification->id()) + QStringLiteral(".png"); + if (!notification->pixmap().isNull()) { + notification->pixmap().save(iconPath, "PNG"); + arguments << QStringLiteral("-p") << iconPath; + } else if (!qApp->windowIcon().isNull()){ + QIcon app_icon = qApp->windowIcon(); + // We limit the icon size to 1024x1024 as it is the highest supported by Windows + QPixmap pixmap = app_icon.pixmap(app_icon.actualSize(QSize(1024, 1024))); + 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(iconPath); + }); +} + +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); +}