diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 1e56f62..e87806c 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,219 +1,219 @@ 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) 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) + find_package(Qt5XmlPatterns 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) 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() 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) 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() 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::Core Qt5::Network SnoreToast::SnoreToastActions) + target_link_libraries(KF5Notifications PRIVATE Qt5::Network Qt5::XmlPatterns 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 index 48bed40..e36b7a0 100644 --- a/src/notifybysnore.cpp +++ b/src/notifybysnore.cpp @@ -1,189 +1,203 @@ /* 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 #include -/** - * Be sure to have a shortcut installed in Windows Start Menu by SnoreToast - * The syntax is - +/* + * 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. - */ + * 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) { - 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 - + server.listen(QString::number(qHash(qApp->applicationDirPath()))); QObject::connect(&server, &QLocalServer::newConnection, &server, [this]() { auto sock = server.nextPendingConnection(); sock->waitForReadyRead(); const QByteArray rawData = sock->readAll(); + sock->deleteLater(); 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)); + QMap map; + const auto parts = data.splitRef(QLatin1Char(';')); + for (auto &str : parts) { + const auto index = str.indexOf(QLatin1Char('=')); + map.insert(str.toString().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()) { + const auto action = map[QStringLiteral("action")].toString(); + const auto id = map[QStringLiteral("notificationId")].toString().toInt(); + KNotification *notification; + const auto it = m_notifications.constFind(id); + if (it != m_notifications.constEnd()) { notification = it.value(); } const auto snoreAction = SnoreToastActions::getAction(action.toStdWString()); qCDebug(LOG_KNOTIFICATIONS) << "The notification ID is : " << id; switch (snoreAction) { - case SnoreToastActions::Actions::Clicked : + case SnoreToastActions::Actions::Clicked: qCDebug(LOG_KNOTIFICATIONS) << " User clicked on the toast."; if (notification) { close(notification); } break; - case SnoreToastActions::Actions::Hidden : + case SnoreToastActions::Actions::Hidden: qCDebug(LOG_KNOTIFICATIONS) << "The toast got hidden."; break; - case SnoreToastActions::Actions::Dismissed : + case SnoreToastActions::Actions::Dismissed: qCDebug(LOG_KNOTIFICATIONS) << "User dismissed the toast."; break; - case SnoreToastActions::Actions::Timedout : + case SnoreToastActions::Actions::Timedout: qCDebug(LOG_KNOTIFICATIONS) << "The toast timed out."; break; - case SnoreToastActions::Actions::ButtonClicked :{ + case SnoreToastActions::Actions::ButtonClicked:{ qCDebug(LOG_KNOTIFICATIONS) << " User clicked a button on the toast."; - const auto button = map[QStringLiteral("button")]; + 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 : + 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 { + } else { arguments << qApp->applicationDisplayName(); } arguments << QStringLiteral("-m") << notification->text(); if (!notification->pixmap().isNull()) { - iconPath = iconDir.path() + QStringLiteral("/") - + QString::number(notification->id()) + QStringLiteral(".png"); + auto iconPath = QString(iconDir.path() + QLatin1Char('/') + + QString::number(notification->id()) + QStringLiteral(".png")); notification->pixmap().save(iconPath, "PNG"); - arguments << QStringLiteral("-p") << iconPath; + 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(";")); + arguments << QStringLiteral("-b") << notification->actions().join(QLatin1Char(';')); } qCDebug(LOG_KNOTIFICATIONS) << arguments; - m_notifications.insert(notification->id(), notification); proc->start(program, arguments); - - connect(proc, QOverload::of(&QProcess::finished), + + if(!proc->waitForStarted()){ + m_notifications.insert(notification->id(), notification); + finish(notification); + } + connect(proc, QOverload::of(&QProcess::finished), [=](int exitCode, QProcess::ExitStatus exitStatus){ proc->deleteLater(); + if(exitStatus == QProcess::NormalExit){ + } else{ + qDebug() << "SnoreToast crashed."; + close(notification); + } }); } void NotifyBySnore::close(KNotification *notification) { - const auto it = m_notifications.find(notification->id()); - if (it == m_notifications.end()) { + const auto it = m_notifications.constFind(notification->id()); + if (it == m_notifications.constEnd()) { 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); + QProcess::startDetached(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); } diff --git a/src/notifybysnore.h b/src/notifybysnore.h index 8ad37aa..13482b8 100644 --- a/src/notifybysnore.h +++ b/src/notifybysnore.h @@ -1,49 +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: - QMap> m_notifications; + QHash> m_notifications; QString program = QStringLiteral("SnoreToast.exe"); QLocalServer server; QTemporaryDir iconDir; }; #endif // NOTIFYBYSNORE_H