diff --git a/CMakeLists.txt b/CMakeLists.txt index 0b61dbe..966a798 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,151 +1,151 @@ cmake_minimum_required(VERSION 3.5) set(KF5_VERSION "5.59.0") # handled by release scripts set(KF5_DEP_VERSION "5.58.0") # handled by release scripts project(KNotifications VERSION ${KF5_VERSION}) # ECM setup include(FeatureSummary) find_package(ECM 5.58.0 NO_MODULE) set_package_properties(ECM PROPERTIES TYPE REQUIRED DESCRIPTION "Extra CMake Modules." URL "https://projects.kde.org/projects/kdesupport/extra-cmake-modules") feature_summary(WHAT REQUIRED_PACKAGES_NOT_FOUND FATAL_ON_MISSING_REQUIRED_PACKAGES) set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH} ${ECM_KDE_MODULE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules) include(GenerateExportHeader) include(ECMSetupVersion) include(ECMGenerateHeaders) include(ECMQtDeclareLoggingCategory) include(ECMPoQmTools) include(ECMAddQch) option(BUILD_QCH "Build API documentation in QCH format (for e.g. Qt Assistant, Qt Creator & KDevelop)" OFF) add_feature_info(QCH ${BUILD_QCH} "API documentation in QCH format (for e.g. Qt Assistant, Qt Creator & KDevelop)") ecm_setup_version(PROJECT VARIABLE_PREFIX KNOTIFICATIONS VERSION_HEADER "${CMAKE_CURRENT_BINARY_DIR}/knotifications_version.h" PACKAGE_VERSION_FILE "${CMAKE_CURRENT_BINARY_DIR}/KF5NotificationsConfigVersion.cmake" SOVERSION 5) # Dependencies set(REQUIRED_QT_VERSION 5.10.0) set(CMAKE_AUTORCC TRUE) find_package(Qt5 ${REQUIRED_QT_VERSION} CONFIG REQUIRED Widgets) if (ANDROID) find_package(Qt5 ${REQUIRED_QT_VERSION} CONFIG REQUIRED AndroidExtras) find_package(Gradle REQUIRED) else () find_package(Qt5 ${REQUIRED_QT_VERSION} CONFIG REQUIRED DBus) endif() find_package(Qt5 ${REQUIRED_QT_VERSION} QUIET OPTIONAL_COMPONENTS TextToSpeech) set_package_properties(Qt5TextToSpeech PROPERTIES DESCRIPTION "Qt text to speech module" TYPE OPTIONAL PURPOSE "Required to build text to speech notification support") if (Qt5TextToSpeech_FOUND) add_definitions(-DHAVE_SPEECH) endif() include(KDEInstallDirs) include(KDEFrameworkCompilerSettings NO_POLICY_SCOPE) include(KDECMakeSettings) if (NOT APPLE AND NOT WIN32) find_package(X11) endif() set(HAVE_X11 ${X11_FOUND}) set(HAVE_XTEST ${X11_XTest_FOUND}) if(X11_FOUND) find_package(Qt5X11Extras ${REQUIRED_QT_VERSION} REQUIRED NO_MODULE) endif() if(APPLE) find_package(Qt5MacExtras ${REQUIRED_QT_VERSION} REQUIRED NO_MODULE) 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) if (NOT ANDROID) 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 () find_package(Phonon4Qt5 4.6.60 NO_MODULE) set_package_properties(Phonon4Qt5 PROPERTIES DESCRIPTION "Qt-based audio library" PURPOSE "Needed to build audio notification support when Canberra isn't available") # This is REQUIRED since you cannot tell CMake "either one of those two optional ones are required" set_package_properties(Phonon4Qt5 PROPERTIES TYPE REQUIRED) if (Phonon4Qt5_FOUND) add_definitions(-DHAVE_PHONON4QT5) - endif() endif() endif() +endif() remove_definitions(-DQT_NO_CAST_FROM_BYTEARRAY) #add_definitions(-DQT_NO_FOREACH) if (IS_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/po") ecm_install_po_files_as_qm(po) endif() if (NOT APPLE) # QtMac::setBadgeLabelText is deprecated add_definitions(-DQT_DISABLE_DEPRECATED_BEFORE=0x050d00) endif() add_subdirectory(src) if (BUILD_TESTING) add_subdirectory(tests) add_subdirectory(autotests) endif() # create a Config.cmake and a ConfigVersion.cmake file and install them set(CMAKECONFIG_INSTALL_DIR "${KDE_INSTALL_CMAKEPACKAGEDIR}/KF5Notifications") if (BUILD_QCH) ecm_install_qch_export( TARGETS KF5Notifications_QCH FILE KF5NotificationsQchTargets.cmake DESTINATION "${CMAKECONFIG_INSTALL_DIR}" COMPONENT Devel ) set(PACKAGE_INCLUDE_QCHTARGETS "include(\"\${CMAKE_CURRENT_LIST_DIR}/KF5NotificationsQchTargets.cmake\")") endif() include(CMakePackageConfigHelpers) set(HAVE_DBUS FALSE) if (TARGET Qt5::DBus) set(HAVE_DBUS TRUE) endif() configure_package_config_file( "${CMAKE_CURRENT_SOURCE_DIR}/KF5NotificationsConfig.cmake.in" "${CMAKE_CURRENT_BINARY_DIR}/KF5NotificationsConfig.cmake" PATH_VARS KDE_INSTALL_DBUSINTERFACEDIR INSTALL_DESTINATION ${CMAKECONFIG_INSTALL_DIR} ) install(FILES "${CMAKE_CURRENT_BINARY_DIR}/KF5NotificationsConfig.cmake" "${CMAKE_CURRENT_BINARY_DIR}/KF5NotificationsConfigVersion.cmake" DESTINATION "${CMAKECONFIG_INSTALL_DIR}" COMPONENT Devel ) install(EXPORT KF5NotificationsTargets DESTINATION "${CMAKECONFIG_INSTALL_DIR}" FILE KF5NotificationsTargets.cmake NAMESPACE KF5:: ) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/knotifications_version.h DESTINATION ${KDE_INSTALL_INCLUDEDIR_KF5} COMPONENT Devel ) install(FILES knotifications.categories DESTINATION ${KDE_INSTALL_CONFDIR}) feature_summary(WHAT ALL INCLUDE_QUIET_PACKAGES FATAL_ON_MISSING_REQUIRED_PACKAGES) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index f9d3cb2..5da5e8c 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,210 +1,215 @@ 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) + add_subdirectory(android) + list(APPEND knotifications_SRCS notifybyandroid.cpp knotifications.qrc) endif() -# if (WIN32) -# add_subdirectory(snore-ftw) -# list(APPEND knotifications_SRCS snoretoast.cpp) -#endif (WIN32) +# so the main stuff to handle now is -- 1. SnoreToast folder +# -- 2. notifybysnore.cpp + +#if (WIN32) +# if (MSVC) +# add_subdirectory(snoretoast) +# list(APPEND knotifications_SRCS notifybysnore.cpp) +# endif () +#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 (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/knotification.cpp b/src/knotification.cpp index e19442b..a0c7903 100644 --- a/src/knotification.cpp +++ b/src/knotification.cpp @@ -1,579 +1,583 @@ /* This file is part of the KDE libraries Copyright (C) 2005-2006 Olivier Goffart Copyright (C) 2013-2014 Martin Klapetek code from KNotify/KNotifyClient Copyright (c) 1997 Christian Esken (esken@kde.org) 2000 Charles Samuels (charles@kde.org) 2000 Stefan Schimanski (1Stein@gmx.de) 2000 Matthias Ettrich (ettrich@kde.org) 2000 Waldo Bastian 2000-2003 Carsten Pfeiffer 2005 Allan Sandfeld Jensen This library 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 library 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 Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "knotification.h" #include "knotificationmanager_p.h" #include #include #include #include #include #include #include #include #include #include #include struct Q_DECL_HIDDEN KNotification::Private { QString eventId; int id; int ref; QWidget *widget; QString title; QString text; QString iconName; QString defaultAction; QStringList actions; QPixmap pixmap; ContextList contexts; NotificationFlags flags; QString componentName; QList urls; KNotification::Urgency urgency; QVariantMap hints; QTimer updateTimer; bool needUpdate; Private() : id(-1), ref(0), widget(nullptr), urgency(KNotification::DefaultUrgency), needUpdate(false) {} /** * recursive function that raise the widget. @p w * * @see raiseWidget() */ static void raiseWidget(QWidget *w); }; KNotification::KNotification(const QString &eventId, QWidget *parent, const NotificationFlags &flags) : QObject(parent), d(new Private) { d->eventId = eventId; d->flags = flags; setWidget(parent); connect(&d->updateTimer, &QTimer::timeout, this, &KNotification::update); d->updateTimer.setSingleShot(true); d->updateTimer.setInterval(100); } KNotification::KNotification( const QString &eventId, const NotificationFlags &flags, QObject *parent) : QObject(parent), d(new Private) { d->eventId = eventId; d->flags = flags; connect(&d->updateTimer, &QTimer::timeout, this, &KNotification::update); d->updateTimer.setSingleShot(true); d->updateTimer.setInterval(100); d->widget = nullptr; } KNotification::~KNotification() { if (d->id >= 0) { KNotificationManager::self()->close(d->id); } delete d; } QString KNotification::eventId() const { return d->eventId; } QString KNotification::title() const { return d->title; } QString KNotification::text() const { return d->text; } QWidget *KNotification::widget() const { return d->widget; } void KNotification::setWidget(QWidget *wid) { d->widget = wid; // setParent(wid); if (wid && d->flags & CloseWhenWidgetActivated) { wid->installEventFilter(this); } } void KNotification::setTitle(const QString &title) { if (title == d->title) { return; } d->needUpdate = true; d->title = title; if (d->id >= 0) { d->updateTimer.start(); } } void KNotification::setText(const QString &text) { if (text == d->text) { return; } d->needUpdate = true; d->text = text; if (d->id >= 0) { d->updateTimer.start(); } } void KNotification::setIconName(const QString &icon) { if (icon == d->iconName) { return; } d->needUpdate = true; d->iconName = icon; if (d->id >= 0) { d->updateTimer.start(); } } QString KNotification::iconName() const { return d->iconName; } QPixmap KNotification::pixmap() const { return d->pixmap; } void KNotification::setPixmap(const QPixmap &pix) { d->needUpdate = true; d->pixmap = pix; if (d->id >= 0) { d->updateTimer.start(); } } QStringList KNotification::actions() const { return d->actions; } void KNotification::setActions(const QStringList &as) { if (as == d->actions) { return; } d->needUpdate = true; d->actions = as; if (d->id >= 0) { d->updateTimer.start(); } } void KNotification::setDefaultAction(const QString &defaultAction) { if (defaultAction == d->defaultAction) { return; } d->needUpdate = true; d->defaultAction = defaultAction; if (d->id >= 0) { d->updateTimer.start(); } } QString KNotification::defaultAction() const { return d->defaultAction; } KNotification::ContextList KNotification::contexts() const { return d->contexts; } void KNotification::setContexts(const KNotification::ContextList &contexts) { d->contexts = contexts; } void KNotification::addContext(const KNotification::Context &context) { d->contexts << context; } void KNotification::addContext(const QString &context_key, const QString &context_value) { d->contexts << qMakePair(context_key, context_value); } KNotification::NotificationFlags KNotification::flags() const { return d->flags; } void KNotification::setFlags(const NotificationFlags &flags) { if (d->flags == flags) { return; } d->needUpdate = true; d->flags = flags; if (d->id >= 0) { d->updateTimer.start(); } } void KNotification::setComponentName(const QString &c) { d->componentName = c; } QList KNotification::urls() const { return d->urls; } void KNotification::setUrls(const QList &urls) { if (d->urls == urls) { return; } d->needUpdate = true; d->urls = urls; if (d->id >= 0) { d->updateTimer.start(); } } KNotification::Urgency KNotification::urgency() const { return d->urgency; } void KNotification::setUrgency(Urgency urgency) { if (d->urgency == urgency) { return; } d->needUpdate = true; d->urgency = urgency; if (d->id >= 0) { d->updateTimer.start(); } } void KNotification::activate(unsigned int action) { switch (action) { case 0: emit activated(); break; case 1: emit action1Activated(); break; case 2: emit action2Activated(); break; case 3: emit action3Activated(); break; } // emitting activated() makes the Manager close all the active plugins // which will deref() the KNotification object, which will result // in closing the notification emit activated(action); } void KNotification::close() { if (d->id >= 0) { KNotificationManager::self()->close(d->id); } if (d->id == -1) { d->id = -2; emit closed(); deleteLater(); } } void KNotification::raiseWidget() { if (!d->widget) { return; } Private::raiseWidget(d->widget); } void KNotification::Private::raiseWidget(QWidget *w) { //TODO this function is far from finished. if (w->isTopLevel()) { w->raise(); KWindowSystem::activateWindow(w->winId()); #if defined(Q_OS_MACOS) w->activateWindow(); #endif } else { QWidget *pw = w->parentWidget(); raiseWidget(pw); if (QTabWidget *tab_widget = qobject_cast(pw)) { tab_widget->setCurrentIndex(tab_widget->indexOf(w)); } } } static QString defaultComponentName() { -#ifndef Q_OS_ANDROID - return QStringLiteral("plasma_workspace"); -#else +#ifdef Q_OS_ANDROID return QStringLiteral("android_defaults"); +#else +#ifdef Q_OS_WIN + return QStringLiteral("win32_defaults"); +#else + return QStringLiteral("plasma_workspace"); +#endif #endif } KNotification *KNotification::event(const QString &eventid, const QString &title, const QString &text, const QPixmap &pixmap, QWidget *widget, const NotificationFlags &flags, const QString &componentName) { KNotification *notify = new KNotification(eventid, widget, flags); notify->setTitle(title); notify->setText(text); notify->setPixmap(pixmap); notify->setComponentName((flags & DefaultEvent) ? defaultComponentName() : componentName); QTimer::singleShot(0, notify, &KNotification::sendEvent); return notify; } KNotification *KNotification::event(const QString &eventid, const QString &text, const QPixmap &pixmap, QWidget *widget, const NotificationFlags &flags, const QString &componentName) { return event(eventid, QString(), text, pixmap, widget, flags, componentName); } KNotification *KNotification::event(StandardEvent eventid, const QString &title, const QString &text, const QPixmap &pixmap, QWidget *widget, const NotificationFlags &flags) { return event(standardEventToEventId(eventid), title, text, pixmap, widget, flags | DefaultEvent); } KNotification *KNotification::event(StandardEvent eventid, const QString &text, const QPixmap &pixmap, QWidget *widget, const NotificationFlags &flags) { return event(eventid, QString(), text, pixmap, widget, flags); } KNotification *KNotification::event(const QString &eventid, const QString &title, const QString &text, const QString &iconName, QWidget *widget, const NotificationFlags &flags, const QString &componentName) { KNotification *notify = new KNotification(eventid, widget, flags); notify->setTitle(title); notify->setText(text); notify->setIconName(iconName); notify->setComponentName((flags & DefaultEvent) ? defaultComponentName() : componentName); QTimer::singleShot(0, notify, &KNotification::sendEvent); return notify; } KNotification *KNotification::event(StandardEvent eventid, const QString &title, const QString &text, const QString &iconName, QWidget *widget, const NotificationFlags &flags) { return event(standardEventToEventId(eventid), title, text, iconName, widget, flags | DefaultEvent); } KNotification* KNotification::event(StandardEvent eventid, const QString &title, const QString &text, QWidget *widget, const NotificationFlags &flags) { return event(standardEventToEventId(eventid), title, text, standardEventToIconName(eventid), widget, flags | DefaultEvent); } void KNotification::ref() { d->ref++; } void KNotification::deref() { Q_ASSERT(d->ref > 0); d->ref--; if (d->ref == 0) { d->id = -1; close(); } } void KNotification::beep(const QString &reason, QWidget *widget) { event(QStringLiteral("beep"), reason, QPixmap(), widget, CloseOnTimeout | DefaultEvent); } void KNotification::sendEvent() { d->needUpdate = false; if (d->id == -1) { d->id = KNotificationManager::self()->notify(this); } else if (d->id >= 0) { KNotificationManager::self()->reemit(this); } } int KNotification::id() { if (!d) { return -1; } return d->id; } QString KNotification::appName() const { QString appname; if (d->flags & DefaultEvent) { appname = defaultComponentName(); } else if (!d->componentName.isEmpty()) { appname = d->componentName; } else { appname = QCoreApplication::applicationName(); } return appname; } void KNotification::update() { if (d->needUpdate) { KNotificationManager::self()->update(this); } } bool KNotification::eventFilter(QObject *watched, QEvent *event) { if (watched == d->widget) { if (event->type() == QEvent::WindowActivate) { if (d->flags & CloseWhenWidgetActivated) { QTimer::singleShot(500, this, &KNotification::close); } } } return false; } QString KNotification::standardEventToEventId(KNotification::StandardEvent event) { QString eventId; switch (event) { case Warning: eventId = QStringLiteral("warning"); break; case Error: eventId = QStringLiteral("fatalerror"); break; case Catastrophe: eventId = QStringLiteral("catastrophe"); break; case Notification: // fall through default: eventId = QStringLiteral("notification"); break; } return eventId; } QString KNotification::standardEventToIconName(KNotification::StandardEvent event) { QString iconName; switch (event) { case Warning: iconName = QStringLiteral("dialog-warning"); break; case Error: iconName = QStringLiteral("dialog-error"); break; case Catastrophe: iconName = QStringLiteral("dialog-error"); break; case Notification: // fall through default: iconName = QStringLiteral("dialog-information"); break; } return iconName; } void KNotification::setHint(const QString &hint, const QVariant &value) { if (value == d->hints.value(hint)) { return; } d->needUpdate = true; d->hints[hint] = value; if (d->id >= 0) { d->updateTimer.start(); } } QVariantMap KNotification::hints() const { return d->hints; } diff --git a/src/knotificationmanager.cpp b/src/knotificationmanager.cpp index a22b8e6..68fa05f 100644 --- a/src/knotificationmanager.cpp +++ b/src/knotificationmanager.cpp @@ -1,368 +1,378 @@ /* This file is part of the KDE libraries Copyright (C) 2005 Olivier Goffart Copyright (C) 2013-2015 Martin Klapetek Copyright (C) 2017 Eike Hein This library 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 library 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 Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "knotificationmanager_p.h" #include "knotification.h" #include #include #include #include #include #include #include #ifdef QT_DBUS_LIB #include #include #endif #include "knotifyconfig.h" #include "knotificationplugin.h" #include "notifybylogfile.h" #include "notifybytaskbar.h" #include "notifybyexecute.h" -#ifndef Q_OS_ANDROID -#include "notifybypopup.h" -#include "notifybyportal.h" + +#ifdef Q_OS_ANDROID + #include "notifybyandroid.h" #else -#include "notifybyandroid.h" + #ifdef Q_OS_WIN + #include "notifybysnore.h" + #else + #include "notifybypopup.h" + #include "notifybyportal.h" + #endif #endif + #include "debug_p.h" #if defined(HAVE_CANBERRA) #include "notifybyaudio_canberra.h" #elif defined(HAVE_PHONON4QT5) #include "notifybyaudio_phonon.h" #endif #ifdef HAVE_SPEECH #include "notifybytts.h" #endif typedef QHash Dict; struct Q_DECL_HIDDEN KNotificationManager::Private { QHash notifications; QHash notifyPlugins; // incremental ids for notifications int notifyIdCounter; QStringList dirtyConfigCache; bool inSandbox = false; bool portalDBusServiceExists = false; }; class KNotificationManagerSingleton { public: KNotificationManager instance; }; Q_GLOBAL_STATIC(KNotificationManagerSingleton, s_self) KNotificationManager *KNotificationManager::self() { return &s_self()->instance; } KNotificationManager::KNotificationManager() : d(new Private) { d->notifyIdCounter = 0; qDeleteAll(d->notifyPlugins); d->notifyPlugins.clear(); if (!qEnvironmentVariableIsEmpty("XDG_RUNTIME_DIR")) { const QByteArray runtimeDir = qgetenv("XDG_RUNTIME_DIR"); if (!runtimeDir.isEmpty()) { d->inSandbox = QFileInfo::exists(QFile::decodeName(runtimeDir) + QLatin1String("/flatpak-info")); } } else if (qEnvironmentVariableIsSet("SNAP")) { d->inSandbox = true; } #ifdef QT_DBUS_LIB if (d->inSandbox) { QDBusConnectionInterface *interface = QDBusConnection::sessionBus().interface(); d->portalDBusServiceExists = interface->isServiceRegistered(QStringLiteral("org.freedesktop.portal.Desktop")); } QDBusConnection::sessionBus().connect(QString(), QStringLiteral("/Config"), QStringLiteral("org.kde.knotification"), QStringLiteral("reparseConfiguration"), this, SLOT(reparseConfiguration(QString))); #endif } KNotificationManager::~KNotificationManager() { delete d; } KNotificationPlugin *KNotificationManager::pluginForAction(const QString &action) { KNotificationPlugin *plugin = d->notifyPlugins.value(action); // We already loaded a plugin for this action. if (plugin) { return plugin; } auto addPlugin = [this](KNotificationPlugin *plugin) { d->notifyPlugins[plugin->optionName()] = plugin; connect(plugin, &KNotificationPlugin::finished, this, &KNotificationManager::notifyPluginFinished); connect(plugin, &KNotificationPlugin::actionInvoked, this, &KNotificationManager::notificationActivated); }; // Load plugin. // 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 (d->inSandbox && d->portalDBusServiceExists) { - plugin = new NotifyByPortal(this); - } else { - plugin = new NotifyByPopup(this); - } -#else - plugin = new NotifyByAndroid(this); -#endif - + #ifdef Q_OS_ANDROID + plugin = new NotifyByAndroid(this); + #else + #ifdef Q_OS_WIN + plugin = new NotifyBySnore(this); + #else + if (d->inSandbox && d->portalDBusServiceExists) { + plugin = new NotifyByPortal(this); + } + else { + plugin = new NotifyByPopup(this); + } + #endif + #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); -#endif + #if defined(HAVE_PHONON4QT5) || defined(HAVE_CANBERRA) + plugin = new NotifyByAudio(this); + addPlugin(plugin); + #endif } else if (action == QLatin1String("Execute")) { plugin = new NotifyByExecute(this); addPlugin(plugin); } else if (action == QLatin1String("Logfile")) { plugin = new NotifyByLogfile(this); addPlugin(plugin); } else if (action == QLatin1String("TTS")) { #ifdef HAVE_SPEECH plugin = new NotifyByTTS(this); addPlugin(plugin); #endif } else { bool pluginFound = false; QList plugins = KPluginLoader::instantiatePlugins(QStringLiteral("knotification/notifyplugins"), [&action, &pluginFound](const KPluginMetaData &data) { // KPluginLoader::instantiatePlugins loops over the plugins it // found and calls this function to determine whether to // instantiate them. We use a `pluginFound` var outside the // lambda to break out of the loop once we got a match. // The reason we can't just use KPluginLoader::findPlugins, // loop over the meta data and instantiate only one plugin // is because the X-KDE-KNotification-OptionName field is // optional (see TODO note below) and the matching plugin // may be among the plugins which don't have it. if (pluginFound) { return false; } const QJsonObject &rawData = data.rawData(); // This field is new-ish and optional. If it's not set we always // instantiate the plugin, unless we already got a match. // TODO KF6: Require X-KDE-KNotification-OptionName be set and // reject plugins without it. if (rawData.contains(QStringLiteral("X-KDE-KNotification-OptionName"))) { if (rawData.value(QStringLiteral("X-KDE-KNotification-OptionName")) == action) { pluginFound = true; } else { return false; } } return true; }, this); for (QObject *pluginObj : qAsConst(plugins)) { KNotificationPlugin *notifyPlugin = qobject_cast(pluginObj); if (notifyPlugin) { // We try to avoid unnecessary instantiations (see above), but // when they happen keep the resulting plugins around. addPlugin(notifyPlugin); // Get ready to return the plugin we got asked for. if (notifyPlugin->optionName() == action) { plugin = notifyPlugin; } } else { // Not our/valid plugin, so delete the created object. pluginObj->deleteLater(); } } } return plugin; } void KNotificationManager::notifyPluginFinished(KNotification *notification) { if (!notification || !d->notifications.contains(notification->id())) { return; } notification->deref(); } void KNotificationManager::notificationActivated(int id, int action) { if (d->notifications.contains(id)) { qCDebug(LOG_KNOTIFICATIONS) << id << " " << action; KNotification *n = d->notifications[id]; n->activate(action); close(id); } } void KNotificationManager::notificationClosed() { KNotification *notification = qobject_cast(sender()); if (!notification) { return; } // We cannot do d->notifications.find(notification->id()); here because the // notification->id() is -1 or -2 at this point, so we need to look for value for (auto iter = d->notifications.begin(); iter != d->notifications.end(); ++iter) { if (iter.value() == notification) { d->notifications.erase(iter); break; } } } void KNotificationManager::close(int id, bool force) { if (force || d->notifications.contains(id)) { KNotification *n = d->notifications.value(id); qCDebug(LOG_KNOTIFICATIONS) << "Closing notification" << id; // Find plugins that are actually acting on this notification // call close() only on those, otherwise each KNotificationPlugin::close() // will call finish() which may close-and-delete the KNotification object // before it finishes calling close on all the other plugins. // For example: Action=Popup is a single actions but there is 5 loaded // plugins, calling close() on the second would already close-and-delete // the notification KNotifyConfig notifyConfig(n->appName(), n->contexts(), n->eventId()); QString notifyActions = notifyConfig.readEntry(QStringLiteral("Action")); const auto listActions = notifyActions.split(QLatin1Char('|')); for (const QString &action : listActions) { if (!d->notifyPlugins.contains(action)) { qCDebug(LOG_KNOTIFICATIONS) << "No plugin for action" << action; continue; } d->notifyPlugins[action]->close(n); } } } int KNotificationManager::notify(KNotification *n) { KNotifyConfig notifyConfig(n->appName(), n->contexts(), n->eventId()); if (d->dirtyConfigCache.contains(n->appName())) { notifyConfig.reparseSingleConfiguration(n->appName()); d->dirtyConfigCache.removeOne(n->appName()); } const QString notifyActions = notifyConfig.readEntry(QStringLiteral("Action")); if (notifyActions.isEmpty() || notifyActions == QLatin1String("None")) { // this will cause KNotification closing itself fast n->ref(); n->deref(); return -1; } d->notifications.insert(d->notifyIdCounter, n); // TODO KF6 d-pointer KNotifyConfig and add this there if (n->urgency() == KNotification::DefaultUrgency) { const QString urgency = notifyConfig.readEntry(QStringLiteral("Urgency")); if (urgency == QLatin1String("Low")) { n->setUrgency(KNotification::LowUrgency); } else if (urgency == QLatin1String("Normal")) { n->setUrgency(KNotification::NormalUrgency); } else if (urgency == QLatin1String("High")) { n->setUrgency(KNotification::HighUrgency); } else if (urgency == QLatin1String("Critical")) { n->setUrgency(KNotification::CriticalUrgency); } } const auto actionsList = notifyActions.split(QLatin1Char('|')); for (const QString &action : actionsList) { KNotificationPlugin *notifyPlugin = pluginForAction(action); if (!notifyPlugin) { qCDebug(LOG_KNOTIFICATIONS) << "No plugin for action" << action; continue; } n->ref(); qCDebug(LOG_KNOTIFICATIONS) << "Calling notify on" << notifyPlugin->optionName(); notifyPlugin->notify(n, ¬ifyConfig); } connect(n, &KNotification::closed, this, &KNotificationManager::notificationClosed); return d->notifyIdCounter++; } void KNotificationManager::update(KNotification *n) { KNotifyConfig notifyConfig(n->appName(), n->contexts(), n->eventId()); for (KNotificationPlugin *p : qAsConst(d->notifyPlugins)) { p->update(n, ¬ifyConfig); } } void KNotificationManager::reemit(KNotification *n) { notify(n); } void KNotificationManager::reparseConfiguration(const QString &app) { if (!d->dirtyConfigCache.contains(app)) { d->dirtyConfigCache << app; } } #include "moc_knotificationmanager_p.cpp" diff --git a/src/notifybysnore.cpp b/src/notifybysnore.cpp new file mode 100644 index 0000000..b5d73f1 --- /dev/null +++ b/src/notifybysnore.cpp @@ -0,0 +1,82 @@ +/* + 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 + +static NotifyBySnore *s_instance = nullptr; + +NotifyBySnore::NotifyBySnore(QObject* parent) : + KNotificationPlugin(parent) +{ + s_instance = this; +} + +NotifyBySnore::~NotifyBySnore() +{ + s_instance = nullptr; +} + +QString NotifyBySnore::optionName() +{ + return QStringLiteral("Toast"); +} + +void NotifyBySnore::notify(KNotification *notification, KNotifyConfig *config) +{ + +} +/* +basically, we have every notifiaction packaged as a JSON-ish object. +Need to look into what's this "id" thingy. +Then we also have a config + Not sure what's that, but we sure get this from notifs in KDEConnect. +Here in this plugin we gotta define our own + notify + close + notificationFinished + notificationActionInvoked + +aaaand we should be done :) +*/ +void NotifyBySnore::notifyDeferred(KNotification* notification) +{ + +} + +void NotifyBySnore::close(KNotification* notification) +{ + // KNotificationPlugin::close(notification); +} + +void NotifyBySnore::notificationFinished(int id) +{ +} + +void NotifyBySnore::notificationActionInvoked(int id, int action) +{ + qCDebug(LOG_KNOTIFICATIONS) << id << action; + emit actionInvoked(id, action); +} diff --git a/src/notifybysnore.h b/src/notifybysnore.h new file mode 100644 index 0000000..c57155c --- /dev/null +++ b/src/notifybysnore.h @@ -0,0 +1,42 @@ +/* + 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 + +/** 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; +// do I need something to point to the exe ??? +}; + +#endif // NOTIFYBYSNORE_H