From d57c80f34eabb906f4af99b3873db1be8cb48fdc Mon Sep 17 00:00:00 2001 From: l10n daemon script Date: Thu, 3 Dec 2015 07:32:23 +0000 Subject: [PATCH 1/7] SVN_SILENT made messages (after extraction) --- kdeconnect.appdata.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/kdeconnect.appdata.xml b/kdeconnect.appdata.xml index b3460921f..af820afd0 100644 --- a/kdeconnect.appdata.xml +++ b/kdeconnect.appdata.xml @@ -30,6 +30,7 @@ Connexió transparent amb els vostres dispositius Connexió transparent amb els vostres dispositius Snadné propojení vašich zařízení + Nahtlose Verbindung zu Ihren Geräten Seamless connection of your devices Conexión sin interrupciones de sus dispositivos Saumaton yhteys laitteisiisi @@ -52,6 +53,7 @@

El KDE Connect proporciona la integració entre el telèfon Android i el vostre escriptori.

El KDE Connect proporciona la integració entre el telèfon Android i el vostre escriptori.

KDE Connect poskytuje integraci mezi vaším telefonem s Androidem a vaší pracovní plochou.

+

KDE Connect bietet Integrationsdienste zwischen Ihrem Android-Telefon und Ihrem Desktop-Computer.

KDE Connect provides integration between your Android phone and your desktop.

KDE Connect proporciona integración entre su teléfono Android y su escritorio.

KDE Connect tarjoaa integraation Android-puhelimesi ja työpöytäsi välillä.

From 2ca1d9720e4341176ba1f83cf4a94752a8892660 Mon Sep 17 00:00:00 2001 From: l10n daemon script Date: Thu, 3 Dec 2015 09:40:02 +0000 Subject: [PATCH 2/7] SVN_SILENT made messages (.desktop file) - always resolve ours In case of conflict in i18n, keep the version of the branch "ours" To resolve a particular conflict, "git checkout --ours path/to/file.desktop" --- plugins/remotecontrol/kdeconnect_remotecontrol.json | 2 ++ 1 file changed, 2 insertions(+) diff --git a/plugins/remotecontrol/kdeconnect_remotecontrol.json b/plugins/remotecontrol/kdeconnect_remotecontrol.json index d008f0e0a..a3eeddfda 100644 --- a/plugins/remotecontrol/kdeconnect_remotecontrol.json +++ b/plugins/remotecontrol/kdeconnect_remotecontrol.json @@ -12,6 +12,7 @@ "Description[ca@valencia]": "Sistemes de control remot", "Description[ca]": "Sistemes de control remot", "Description[cs]": "Ovládejte vzdálené systémy", + "Description[de]": "Entfernte Systeme steuern", "Description[en_GB]": "Control Remote systems", "Description[es]": "Controlar sistemas remotos", "Description[fi]": "Ohjaa järjestelmiä etänä", @@ -38,6 +39,7 @@ "Name[ca@valencia]": "Control remot", "Name[ca]": "Control remot", "Name[cs]": "Dálkové ovládání", + "Name[de]": "Fernsteuerung", "Name[en_GB]": "RemoteControl", "Name[es]": "Control remoto", "Name[fi]": "Kauko-ohjain", From 2a74eb68f883bd17731aec98cdaaa87ab6d849a6 Mon Sep 17 00:00:00 2001 From: Holger Kaelberer Date: Thu, 10 Sep 2015 16:36:03 +0200 Subject: [PATCH 3/7] notifications: add initial support for desktop-to-xxx notifications Eavesdrop on the Notify call of the org.freedesktop.Notifications dbus-interface, proxy all caught notifications to our peer device and track them in the internal notifications-list. Also fix "cancel" requests from peer devices, by cutting of kdeconnect-android's id-prefix. --- plugins/notifications/CMakeLists.txt | 1 + .../notificationsdbusinterface.cpp | 19 +++- .../notificationsdbusinterface.h | 2 +- .../notifications/notificationslistener.cpp | 101 ++++++++++++++++++ plugins/notifications/notificationslistener.h | 46 ++++++++ plugins/notifications/notificationsplugin.cpp | 3 + plugins/notifications/notificationsplugin.h | 2 + 7 files changed, 171 insertions(+), 3 deletions(-) create mode 100644 plugins/notifications/notificationslistener.cpp create mode 100644 plugins/notifications/notificationslistener.h diff --git a/plugins/notifications/CMakeLists.txt b/plugins/notifications/CMakeLists.txt index 41c5b78f8..fbb459eec 100644 --- a/plugins/notifications/CMakeLists.txt +++ b/plugins/notifications/CMakeLists.txt @@ -4,6 +4,7 @@ set(kdeconnect_notifications_SRCS notification.cpp notificationsplugin.cpp notificationsdbusinterface.cpp + notificationslistener.cpp ) kdeconnect_add_plugin(kdeconnect_notifications JSON kdeconnect_notifications.json SOURCES ${kdeconnect_notifications_SRCS}) diff --git a/plugins/notifications/notificationsdbusinterface.cpp b/plugins/notifications/notificationsdbusinterface.cpp index 3c504c646..4e2c7e0ea 100644 --- a/plugins/notifications/notificationsdbusinterface.cpp +++ b/plugins/notifications/notificationsdbusinterface.cpp @@ -45,6 +45,7 @@ NotificationsDbusInterface::NotificationsDbusInterface(KdeConnectPlugin* plugin) NotificationsDbusInterface::~NotificationsDbusInterface() { + qCDebug(KDECONNECT_PLUGIN_NOTIFICATION) << "Destroying NotificationsDbusInterface"; } void NotificationsDbusInterface::clearNotifications() @@ -60,7 +61,21 @@ QStringList NotificationsDbusInterface::activeNotifications() void NotificationsDbusInterface::processPackage(const NetworkPackage& np) { if (np.get("isCancel")) { - removeNotification(np.get("id")); + QString id = np.get("id"); + // cut off kdeconnect-android's prefix if there: + if (id.startsWith("org.kde.kdeconnect_tp::")) + id = id.mid(id.indexOf("::") + 2); + removeNotification(id); + } else if (np.get("isRequest")) { + for (const auto& n: mNotifications) { + NetworkPackage np(PACKAGE_TYPE_NOTIFICATION); + np.set("id", n->internalId()); + np.set("appName", n->appName()); + np.set("ticker", n->ticker()); + np.set("isClearable", n->dismissable()); + np.set("requestAnswer", true); + mPlugin->sendPackage(np); + } } else { //TODO: Uncoment when we are able to display app icon on plasmoid @@ -114,7 +129,7 @@ void NotificationsDbusInterface::removeNotification(const QString& internalId) qCDebug(KDECONNECT_PLUGIN_NOTIFICATION) << "removeNotification" << internalId; if (!mInternalIdToPublicId.contains(internalId)) { - qCDebug(KDECONNECT_PLUGIN_NOTIFICATION) << "Not found"; + qCDebug(KDECONNECT_PLUGIN_NOTIFICATION) << "Not found: " << internalId; return; } diff --git a/plugins/notifications/notificationsdbusinterface.h b/plugins/notifications/notificationsdbusinterface.h index 21d5a6afe..fd36a1088 100644 --- a/plugins/notifications/notificationsdbusinterface.h +++ b/plugins/notifications/notificationsdbusinterface.h @@ -45,6 +45,7 @@ public: void processPackage(const NetworkPackage& np); void clearNotifications(); void dismissRequested(const QString& notification); + void addNotification(Notification* noti); public Q_SLOTS: Q_SCRIPTABLE QStringList activeNotifications(); @@ -54,7 +55,6 @@ Q_SIGNALS: Q_SCRIPTABLE void notificationRemoved(const QString& publicId); private /*methods*/: - void addNotification(Notification* noti); void removeNotification(const QString& internalId); QString newId(); //Generates successive identifitiers to use as public ids diff --git a/plugins/notifications/notificationslistener.cpp b/plugins/notifications/notificationslistener.cpp new file mode 100644 index 000000000..88ad98bc1 --- /dev/null +++ b/plugins/notifications/notificationslistener.cpp @@ -0,0 +1,101 @@ +/** + * Copyright 2015 Holger Kaelberer + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * 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 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 +#include +#include +#include + +#include +#include + +#include "notificationslistener.h" +#include "notificationsplugin.h" +#include "notification_debug.h" +#include "notificationsdbusinterface.h" + +NotificationsListener::NotificationsListener(KdeConnectPlugin* aPlugin, + NotificationsDbusInterface* aDbusInterface) + : QDBusAbstractAdaptor(aPlugin), + mPlugin(aPlugin), + dbusInterface(aDbusInterface) +{ + bool ret = QDBusConnection::sessionBus() + .registerObject("/org/freedesktop/Notifications", + this, + QDBusConnection::ExportScriptableContents); + if (!ret) + qCWarning(KDECONNECT_PLUGIN_NOTIFICATION) + << "Error registering notifications listener for device" + << mPlugin->device()->name() << ":" + << QDBusConnection::sessionBus().lastError(); + else + qCDebug(KDECONNECT_PLUGIN_NOTIFICATION) + << "Registered notifications listener for device" + << mPlugin->device()->name(); + + QDBusInterface iface("org.freedesktop.DBus", "/org/freedesktop/DBus", + "org.freedesktop.DBus"); + iface.call("AddMatch", + "interface='org.freedesktop.Notifications',member='Notify',type='method_call',eavesdrop='true'"); +} + +NotificationsListener::~NotificationsListener() +{ + qCDebug(KDECONNECT_PLUGIN_NOTIFICATION) << "Destroying NotificationsListener"; + QDBusInterface iface("org.freedesktop.DBus", "/org/freedesktop/DBus", + "org.freedesktop.DBus"); + QDBusMessage res = iface.call("RemoveMatch", + "interface='org.freedesktop.Notifications',member='Notify',type='method_call',eavesdrop='true'"); + QDBusConnection::sessionBus().unregisterObject("/org/freedesktop/Notifications"); +} + +uint NotificationsListener::Notify(const QString &appName, uint replacesId, + const QString &appIcon, + const QString &summary, const QString &body, + const QStringList &actions, + const QVariantMap &hints, int timeout) +{ + static int id = 0; + qCDebug(KDECONNECT_PLUGIN_NOTIFICATION) << "Got notification appName=" << appName << "replacesId=" << replacesId << "appIcon=" << appIcon << "summary=" << summary << "body=" << body << "actions=" << actions << "hints=" << hints << "timeout=" << timeout; + Q_UNUSED(hints); + Q_UNUSED(actions); + Q_UNUSED(appIcon); + Q_UNUSED(body); + + // skip our own notifications + if (appName == QLatin1String("KDE Connect")) + return 0; + + NetworkPackage np(PACKAGE_TYPE_NOTIFICATION); + np.set("id", QString::number(replacesId > 0 ? replacesId : ++id)); + np.set("appName", appName); + np.set("ticker", summary); + np.set("isClearable", timeout == 0); // KNotifications are persistent if + // timeout == 0, for other notifications + // clearability is pointless + + Notification *notification = new Notification(np, QString(), this); + dbusInterface->addNotification(notification); + + mPlugin->sendPackage(np); + + return (replacesId > 0 ? replacesId : id); +} diff --git a/plugins/notifications/notificationslistener.h b/plugins/notifications/notificationslistener.h new file mode 100644 index 000000000..7887b5981 --- /dev/null +++ b/plugins/notifications/notificationslistener.h @@ -0,0 +1,46 @@ +/** + * Copyright 2015 Holger Kaelberer + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * 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 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 +#include + +class KdeConnectPlugin; +class NotificationsDbusInterface; +class Notification; + +class NotificationsListener : public QDBusAbstractAdaptor +{ + Q_OBJECT + Q_CLASSINFO("D-Bus Interface", "org.freedesktop.Notifications") + +public: + explicit NotificationsListener(KdeConnectPlugin* aPlugin, + NotificationsDbusInterface* aDbusInterface); + virtual ~NotificationsListener(); + +private: + KdeConnectPlugin* mPlugin; + NotificationsDbusInterface* dbusInterface; + +public Q_SLOTS: + Q_SCRIPTABLE uint Notify(const QString&, uint, const QString&, + const QString&, const QString&, + const QStringList&, const QVariantMap&, int); +}; diff --git a/plugins/notifications/notificationsplugin.cpp b/plugins/notifications/notificationsplugin.cpp index badb5d340..f47f5d8dc 100644 --- a/plugins/notifications/notificationsplugin.cpp +++ b/plugins/notifications/notificationsplugin.cpp @@ -21,6 +21,7 @@ #include "notificationsplugin.h" #include "notificationsdbusinterface.h" +#include "notificationslistener.h" #include "notification_debug.h" #include @@ -33,6 +34,7 @@ NotificationsPlugin::NotificationsPlugin(QObject* parent, const QVariantList& ar : KdeConnectPlugin(parent, args) { notificationsDbusInterface = new NotificationsDbusInterface(this); + notificationsListener = new NotificationsListener(this, notificationsDbusInterface); } void NotificationsPlugin::connected() @@ -44,6 +46,7 @@ void NotificationsPlugin::connected() NotificationsPlugin::~NotificationsPlugin() { + qCDebug(KDECONNECT_PLUGIN_NOTIFICATION) << "Destroying NotificationsPlugin"; //FIXME: Qt dbus does not allow to remove an adaptor! (it causes a crash in // the next dbus access to its parent). The implication of not deleting this // is that disabling the plugin leaks the interface. As a mitigation we are diff --git a/plugins/notifications/notificationsplugin.h b/plugins/notifications/notificationsplugin.h index 7cc67c5f4..599c89c71 100644 --- a/plugins/notifications/notificationsplugin.h +++ b/plugins/notifications/notificationsplugin.h @@ -32,6 +32,7 @@ * KdeConnectPlugin at the same time (both are QObject) */ class NotificationsDbusInterface; +class NotificationsListener; class NotificationsPlugin : public KdeConnectPlugin @@ -48,6 +49,7 @@ public Q_SLOTS: private: NotificationsDbusInterface* notificationsDbusInterface; + NotificationsListener* notificationsListener; }; From f072a0c615115ecce0c0ab9ec8a83ae7ab0b7622 Mon Sep 17 00:00:00 2001 From: Holger Kaelberer Date: Sat, 5 Dec 2015 23:11:57 +0100 Subject: [PATCH 4/7] notifications: allow to configure how to sync desktop notifications Added kcm ui with global and app-specific config options. Global options affect all notifications: - Persistent only? --> Sync only notifications with timeout == 0? - Include body? --> Add body string to summary when syncing? - Minimum urgency Per-application options affect notifications sent by a specific application: - Sync it at all? - If yes, allow to define a "blacklist" pattern (QRegularExpression) to define which notifications should *not* be synced. Applications are maintained in the per-device config and added when seen the first time by the notification listener. From that moment on they can be configured in the kcm ui. --- core/kdeconnectpluginconfig.cpp | 30 +++ core/kdeconnectpluginconfig.h | 8 + plugins/notifications/CMakeLists.txt | 25 +- .../kdeconnect_notifications_config.desktop | 10 + .../notifications/notifications_config.cpp | 113 +++++++++ plugins/notifications/notifications_config.h | 54 ++++ plugins/notifications/notifications_config.ui | 210 ++++++++++++++++ .../notifications/notificationslistener.cpp | 69 ++++- plugins/notifications/notificationslistener.h | 6 + .../notifications/notifyingapplication.cpp | 50 ++++ plugins/notifications/notifyingapplication.h | 44 ++++ .../notifyingapplicationmodel.cpp | 235 ++++++++++++++++++ .../notifications/notifyingapplicationmodel.h | 56 +++++ 13 files changed, 904 insertions(+), 6 deletions(-) create mode 100644 plugins/notifications/kdeconnect_notifications_config.desktop create mode 100644 plugins/notifications/notifications_config.cpp create mode 100644 plugins/notifications/notifications_config.h create mode 100644 plugins/notifications/notifications_config.ui create mode 100644 plugins/notifications/notifyingapplication.cpp create mode 100644 plugins/notifications/notifyingapplication.h create mode 100644 plugins/notifications/notifyingapplicationmodel.cpp create mode 100644 plugins/notifications/notifyingapplicationmodel.h diff --git a/core/kdeconnectpluginconfig.cpp b/core/kdeconnectpluginconfig.cpp index 980235c96..7c11e3c68 100644 --- a/core/kdeconnectpluginconfig.cpp +++ b/core/kdeconnectpluginconfig.cpp @@ -62,6 +62,24 @@ QVariant KdeConnectPluginConfig::get(const QString& key, const QVariant& default return d->mConfig->value(key, defaultValue); } +QVariantList KdeConnectPluginConfig::getList(const QString& key, + const QVariantList& defaultValue) +{ + QVariantList list; + d->mConfig->sync(); // note: need sync() to get recent changes signalled from other process + int size = d->mConfig->beginReadArray(key); + if (size < 1) { + d->mConfig->endArray(); + return defaultValue; + } + for (int i = 0; i < size; ++i) { + d->mConfig->setArrayIndex(i); + list << d->mConfig->value("value"); + } + d->mConfig->endArray(); + return list; +} + void KdeConnectPluginConfig::set(const QString& key, const QVariant& value) { d->mConfig->setValue(key, value); @@ -69,6 +87,18 @@ void KdeConnectPluginConfig::set(const QString& key, const QVariant& value) QDBusConnection::sessionBus().send(d->signal); } +void KdeConnectPluginConfig::setList(const QString& key, const QVariantList& list) +{ + d->mConfig->beginWriteArray(key); + for (int i = 0; i < list.size(); ++i) { + d->mConfig->setArrayIndex(i); + d->mConfig->setValue("value", list.at(i)); + } + d->mConfig->endArray(); + d->mConfig->sync(); + QDBusConnection::sessionBus().send(d->signal); +} + void KdeConnectPluginConfig::slotConfigChanged() { Q_EMIT configChanged(); diff --git a/core/kdeconnectpluginconfig.h b/core/kdeconnectpluginconfig.h index 09bf68eaa..a3fcf18ee 100644 --- a/core/kdeconnectpluginconfig.h +++ b/core/kdeconnectpluginconfig.h @@ -50,6 +50,12 @@ public: */ void set(const QString& key, const QVariant& value); + /** + * Store a list of values in this config object under the array name + * specified in key. + */ + void setList(const QString& key, const QVariantList& list); + /** * Read a key-value pair from this config object */ @@ -62,6 +68,8 @@ public: return get(key, QVariant(defaultValue)).template value(); //Important note: Awesome template syntax is awesome } + QVariantList getList(const QString& key, const QVariantList& defaultValue = {}); + private Q_SLOTS: void slotConfigChanged(); diff --git a/plugins/notifications/CMakeLists.txt b/plugins/notifications/CMakeLists.txt index fbb459eec..448be94d4 100644 --- a/plugins/notifications/CMakeLists.txt +++ b/plugins/notifications/CMakeLists.txt @@ -1,10 +1,11 @@ -find_package(KF5 REQUIRED COMPONENTS Notifications) +find_package(KF5 REQUIRED COMPONENTS Notifications KCMUtils I18n) set(kdeconnect_notifications_SRCS notification.cpp notificationsplugin.cpp notificationsdbusinterface.cpp notificationslistener.cpp + notifyingapplication.cpp ) kdeconnect_add_plugin(kdeconnect_notifications JSON kdeconnect_notifications.json SOURCES ${kdeconnect_notifications_SRCS}) @@ -13,4 +14,26 @@ target_link_libraries(kdeconnect_notifications kdeconnectcore Qt5::DBus KF5::Notifications + KF5::I18n ) + +####################################### +# Config + +set( kdeconnect_notifications_config_SRCS + notifications_config.cpp + notifyingapplication.cpp + notifyingapplicationmodel.cpp +) +ki18n_wrap_ui( kdeconnect_notifications_config_SRCS notifications_config.ui ) + +add_library(kdeconnect_notifications_config MODULE ${kdeconnect_notifications_config_SRCS} ) +target_link_libraries( kdeconnect_notifications_config + kdeconnectcore + kdeconnectpluginkcm + KF5::I18n + KF5::KCMUtils +) + +install( TARGETS kdeconnect_notifications_config DESTINATION ${PLUGIN_INSTALL_DIR} ) +install( FILES kdeconnect_notifications_config.desktop DESTINATION ${SERVICES_INSTALL_DIR} ) diff --git a/plugins/notifications/kdeconnect_notifications_config.desktop b/plugins/notifications/kdeconnect_notifications_config.desktop new file mode 100644 index 000000000..6535bc7aa --- /dev/null +++ b/plugins/notifications/kdeconnect_notifications_config.desktop @@ -0,0 +1,10 @@ +[Desktop Entry] +Type=Service +X-KDE-ServiceTypes=KCModule + +X-KDE-Library=kdeconnect_notifications_config +X-KDE-ParentComponents=kdeconnect_notifications + +Name=Notification synchronization plugin settings + +Categories=Qt;KDE;X-KDE-settings-kdeconnect; diff --git a/plugins/notifications/notifications_config.cpp b/plugins/notifications/notifications_config.cpp new file mode 100644 index 000000000..5086d9fca --- /dev/null +++ b/plugins/notifications/notifications_config.cpp @@ -0,0 +1,113 @@ +/** + * Copyright 2015 Holger Kaelberer + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * 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 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 "notifications_config.h" +#include "ui_notifications_config.h" +#include "notifyingapplicationmodel.h" + +#include +#include + +K_PLUGIN_FACTORY(NotificationsConfigFactory, registerPlugin();) + +NotificationsConfig::NotificationsConfig(QWidget *parent, const QVariantList& args) + : KdeConnectPluginKcm(parent, args, "kdeconnect_notifications_config") + , m_ui(new Ui::NotificationsConfigUi()) + , appModel(new NotifyingApplicationModel) +{ + qRegisterMetaTypeStreamOperators("NotifyingApplication"); + + m_ui->setupUi(this); + m_ui->appList->setIconSize(QSize(32,32)); + + m_ui->appList->setModel(appModel); + + m_ui->appList->horizontalHeader()->setSectionResizeMode(0, QHeaderView::QHeaderView::Fixed); + m_ui->appList->horizontalHeader()->setSectionResizeMode(1, QHeaderView::QHeaderView::Stretch); + m_ui->appList->horizontalHeader()->setSectionResizeMode(2, QHeaderView::QHeaderView::Stretch); + for (int i = 0; i < 3; i++) + m_ui->appList->resizeColumnToContents(i); + + connect(m_ui->appList->horizontalHeader(), SIGNAL(sortIndicatorChanged(int,Qt::SortOrder)), + m_ui->appList, SLOT(sortByColumn(int))); + + connect(m_ui->check_persistent, SIGNAL(toggled(bool)), this, SLOT(changed())); + connect(m_ui->spin_urgency, SIGNAL(editingFinished()), this, SLOT(changed())); + connect(m_ui->check_body, SIGNAL(toggled(bool)), this, SLOT(changed())); + + connect(appModel, SIGNAL(applicationsChanged()), this, SLOT(changed())); + + connect(config(), SIGNAL(configChanged()), this, SLOT(loadApplications())); +} + +NotificationsConfig::~NotificationsConfig() +{ + delete m_ui; +} + +void NotificationsConfig::defaults() +{ + KCModule::defaults(); + m_ui->check_persistent->setChecked(false); + m_ui->spin_urgency->setValue(0); + Q_EMIT changed(true); +} + +void NotificationsConfig::loadApplications() +{ + appModel->clearApplications(); + QVariantList list = config()->getList("applications"); + for (const auto& a: list) { + NotifyingApplication app = a.value(); + if (!appModel->containsApp(app.name)) { + appModel->appendApp(app); + } + } +} + +void NotificationsConfig::load() +{ + KCModule::load(); + bool persistent = config()->get("generalPersistent", false); + m_ui->check_persistent->setChecked(persistent); + bool body = config()->get("generalIncludeBody", true); + m_ui->check_body->setChecked(body); + int urgency = config()->get("generalUrgency", 0); + m_ui->spin_urgency->setValue(urgency); + + loadApplications(); + Q_EMIT changed(false); +} + +void NotificationsConfig::save() +{ + config()->set("generalPersistent", m_ui->check_persistent->isChecked()); + config()->set("generalIncludeBody", m_ui->check_body->isChecked()); + config()->set("generalUrgency", m_ui->spin_urgency->value()); + + QVariantList list; + for (const auto& a: appModel->apps()) + list << QVariant::fromValue(a); + config()->setList("applications", list); + KCModule::save(); + Q_EMIT changed(false); +} + +#include "notifications_config.moc" diff --git a/plugins/notifications/notifications_config.h b/plugins/notifications/notifications_config.h new file mode 100644 index 000000000..fedbfc6c8 --- /dev/null +++ b/plugins/notifications/notifications_config.h @@ -0,0 +1,54 @@ +/** + * Copyright 2015 Holger Kaelberer + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * 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 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 NOTIFICATIONS_CONFIG_H +#define NOTIFICATIONS_CONFIG_H + +#include "kcmplugin/kdeconnectpluginkcm.h" + +namespace Ui { + class NotificationsConfigUi; +} + +class NotifyingApplicationModel; + +class NotificationsConfig + : public KdeConnectPluginKcm +{ + Q_OBJECT +public: + NotificationsConfig(QWidget *parent, const QVariantList&); + virtual ~NotificationsConfig(); + +public Q_SLOTS: + virtual void save() override; + virtual void load() override; + virtual void defaults() override; + +private Q_SLOTS: + void loadApplications(); + +private: + Ui::NotificationsConfigUi* m_ui; + NotifyingApplicationModel* appModel; + +}; + +#endif diff --git a/plugins/notifications/notifications_config.ui b/plugins/notifications/notifications_config.ui new file mode 100644 index 000000000..3f3d8a471 --- /dev/null +++ b/plugins/notifications/notifications_config.ui @@ -0,0 +1,210 @@ + + + NotificationsConfigUi + + + Qt::WindowModal + + + + 0 + 0 + 350 + 326 + + + + + 0 + 0 + + + + + 350 + 0 + + + + Pause music plugin + + + + + + + 0 + 0 + + + + + 11 + 75 + true + + + + General + + + + + + + 50 + false + + + + Synchronize only notifications with a timeout value of 0? + + + Persistent notifications only + + + + + + + + 50 + false + + + + Append the notification body to the summary when synchronizing notifications? + + + Include body + + + + + + + + 0 + 0 + + + + + + + + 40 + 32 + + + + + 50 + false + + + + <html><head/><body><p>Minimum urgency level of the notifications</p></body></html> + + + 2 + + + + + + + + 50 + false + + + + Synchronize only notifications with the given urgency level. + + + Minimum urgency level + + + + + + + + + + + + + + 0 + 0 + + + + + 75 + true + + + + + + + Applications + + + + + + + 0 + 0 + + + + + 50 + false + + + + true + + + false + + + Qt::NoPen + + + true + + + true + + + 150 + + + 20 + + + true + + + true + + + false + + + + + + + + + + + diff --git a/plugins/notifications/notificationslistener.cpp b/plugins/notifications/notificationslistener.cpp index 88ad98bc1..7e643af3c 100644 --- a/plugins/notifications/notificationslistener.cpp +++ b/plugins/notifications/notificationslistener.cpp @@ -30,6 +30,7 @@ #include "notificationsplugin.h" #include "notification_debug.h" #include "notificationsdbusinterface.h" +#include "notifyingapplication.h" NotificationsListener::NotificationsListener(KdeConnectPlugin* aPlugin, NotificationsDbusInterface* aDbusInterface) @@ -37,6 +38,8 @@ NotificationsListener::NotificationsListener(KdeConnectPlugin* aPlugin, mPlugin(aPlugin), dbusInterface(aDbusInterface) { + qRegisterMetaTypeStreamOperators("NotifyingApplication"); + bool ret = QDBusConnection::sessionBus() .registerObject("/org/freedesktop/Notifications", this, @@ -55,6 +58,10 @@ NotificationsListener::NotificationsListener(KdeConnectPlugin* aPlugin, "org.freedesktop.DBus"); iface.call("AddMatch", "interface='org.freedesktop.Notifications',member='Notify',type='method_call',eavesdrop='true'"); + + loadApplications(); + + connect(mPlugin->config(), SIGNAL(configChanged()), this, SLOT(loadApplications())); } NotificationsListener::~NotificationsListener() @@ -67,6 +74,18 @@ NotificationsListener::~NotificationsListener() QDBusConnection::sessionBus().unregisterObject("/org/freedesktop/Notifications"); } +void NotificationsListener::loadApplications() +{ + applications.clear(); + QVariantList list = mPlugin->config()->getList("applications"); + for (const auto& a: list) { + NotifyingApplication app = a.value(); + if (!applications.contains(app.name)) + applications.insert(app.name, app); + } + //qCDebug(KDECONNECT_PLUGIN_NOTIFICATION) << "Loaded" << applications.size() << " applications"; +} + uint NotificationsListener::Notify(const QString &appName, uint replacesId, const QString &appIcon, const QString &summary, const QString &body, @@ -74,20 +93,60 @@ uint NotificationsListener::Notify(const QString &appName, uint replacesId, const QVariantMap &hints, int timeout) { static int id = 0; - qCDebug(KDECONNECT_PLUGIN_NOTIFICATION) << "Got notification appName=" << appName << "replacesId=" << replacesId << "appIcon=" << appIcon << "summary=" << summary << "body=" << body << "actions=" << actions << "hints=" << hints << "timeout=" << timeout; - Q_UNUSED(hints); Q_UNUSED(actions); - Q_UNUSED(appIcon); - Q_UNUSED(body); + + //qCDebug(KDECONNECT_PLUGIN_NOTIFICATION) << "Got notification appName=" << appName << "replacesId=" << replacesId << "appIcon=" << appIcon << "summary=" << summary << "body=" << body << "actions=" << actions << "hints=" << hints << "timeout=" << timeout; // skip our own notifications if (appName == QLatin1String("KDE Connect")) return 0; + NotifyingApplication app; + if (!applications.contains(appName)) { + // new application -> add to config + app.name = appName; + app.icon = appIcon; + app.active = true; + app.blacklistExpression = QRegularExpression(); + applications.insert(app.name, app); + // update config: + QVariantList list; + for (const auto& a: applications.values()) + list << QVariant::fromValue(a); + mPlugin->config()->setList("applications", list); + //qCDebug(KDECONNECT_PLUGIN_NOTIFICATION) << "Added new application to config:" << app; + } else + app = applications.value(appName); + + if (!app.active) + return 0; + + if (timeout > 0 && mPlugin->config()->get("generalPersistent", false)) + return 0; + + int urgency = -1; + bool ok; + if (hints.contains("urgency")) + urgency = hints["urgency"].toInt(&ok); + if (!ok) + urgency = -1; + if (urgency > -1 && urgency < mPlugin->config()->get("generalUrgency", 0)) + return 0; + + QString ticker = summary; + if (!body.isEmpty() && mPlugin->config()->get("generalIncludeBody", true)) + ticker += QLatin1String(": ") + body; + + if (app.blacklistExpression.isValid() && + !app.blacklistExpression.pattern().isEmpty() && + app.blacklistExpression.match(ticker).hasMatch()) + return 0; + + //qCDebug(KDECONNECT_PLUGIN_NOTIFICATION) << "Sending notification from" << appName << ":" < 0 ? replacesId : ++id)); np.set("appName", appName); - np.set("ticker", summary); + np.set("ticker", ticker); np.set("isClearable", timeout == 0); // KNotifications are persistent if // timeout == 0, for other notifications // clearability is pointless diff --git a/plugins/notifications/notificationslistener.h b/plugins/notifications/notificationslistener.h index 7887b5981..bb37ead06 100644 --- a/plugins/notifications/notificationslistener.h +++ b/plugins/notifications/notificationslistener.h @@ -24,6 +24,7 @@ class KdeConnectPlugin; class NotificationsDbusInterface; class Notification; +class NotifyingApplication; class NotificationsListener : public QDBusAbstractAdaptor { @@ -38,9 +39,14 @@ public: private: KdeConnectPlugin* mPlugin; NotificationsDbusInterface* dbusInterface; + QHash applications; public Q_SLOTS: Q_SCRIPTABLE uint Notify(const QString&, uint, const QString&, const QString&, const QString&, const QStringList&, const QVariantMap&, int); + +private Q_SLOTS: + void loadApplications(); + }; diff --git a/plugins/notifications/notifyingapplication.cpp b/plugins/notifications/notifyingapplication.cpp new file mode 100644 index 000000000..dc1c3c155 --- /dev/null +++ b/plugins/notifications/notifyingapplication.cpp @@ -0,0 +1,50 @@ +/** + * Copyright 2015 Holger Kaelberer + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * 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 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 "notifyingapplication.h" + +#include +#include + +QDataStream &operator<<(QDataStream &out, const NotifyingApplication &app) +{ + out << app.name << app.icon << app.active << app.blacklistExpression.pattern(); + return out; +} + +QDataStream &operator>>(QDataStream &in, NotifyingApplication &app) +{ + QString pattern; + in >> app.name; + in >> app.icon; + in >> app.active; + in >> pattern; + app.blacklistExpression.setPattern(pattern); + return in; +} + +QDebug operator<<(QDebug dbg, const NotifyingApplication& a) { + dbg.nospace() << "{ name=" << a.name + << ", icon=" << a.icon + << ", active=" << a.active + << ", blacklistExpression =" << a.blacklistExpression + << " }"; + return dbg.space(); +} diff --git a/plugins/notifications/notifyingapplication.h b/plugins/notifications/notifyingapplication.h new file mode 100644 index 000000000..97421aa87 --- /dev/null +++ b/plugins/notifications/notifyingapplication.h @@ -0,0 +1,44 @@ +/** + * Copyright 2015 Holger Kaelberer + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * 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 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 NOTIFYINGAPPLICATION_H +#define NOTIFYINGAPPLICATION_H + +#include +#include + +struct NotifyingApplication { + QString name; + QString icon; + bool active; + QRegularExpression blacklistExpression; + + bool operator==(const NotifyingApplication& other) const { + return (name == other.name); + } +}; + +Q_DECLARE_METATYPE(NotifyingApplication); + +QDataStream &operator<<(QDataStream &out, const NotifyingApplication &app); +QDataStream &operator>>(QDataStream &in, NotifyingApplication &app); +QDebug operator<<(QDebug dbg, const NotifyingApplication &a); + +#endif //NOTIFYINGAPPLICATION_H diff --git a/plugins/notifications/notifyingapplicationmodel.cpp b/plugins/notifications/notifyingapplicationmodel.cpp new file mode 100644 index 000000000..eadd46a61 --- /dev/null +++ b/plugins/notifications/notifyingapplicationmodel.cpp @@ -0,0 +1,235 @@ +/** + * Copyright 2015 Holger Kaelberer + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * 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 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 +#include +#include +#include +#include + +#include "notifyingapplicationmodel.h" +//#include "modeltest.h" + +NotifyingApplicationModel::NotifyingApplicationModel(QObject *parent) + : QAbstractTableModel(parent) +{ +} + +NotifyingApplicationModel::~NotifyingApplicationModel() +{ +} + +QVector NotifyingApplicationModel::apps() +{ + return m_apps; +} + +void NotifyingApplicationModel::appendApp(const NotifyingApplication& app) +{ + if (app.name.isEmpty() || apps().contains(app)) + return; + beginInsertRows(QModelIndex(), m_apps.size(), m_apps.size()); + m_apps.append(app); + endInsertRows(); +} + +bool NotifyingApplicationModel::containsApp(const QString& name) const +{ + for (const auto& a: m_apps) + if (a.name == name) + return true; + return false; +} + +Qt::ItemFlags NotifyingApplicationModel::flags(const QModelIndex& index) const +{ + Qt::ItemFlags flags = Qt::ItemIsEnabled; + if (index.isValid() && index.row() >= 0 && index.row() < m_apps.size() && + index.column() < 3) + { + if (index.column() == 0) + flags |= Qt::ItemIsEditable | Qt::ItemIsUserCheckable; + else if (index.column() == 2) { + if (m_apps[index.row()].active) + flags |= Qt::ItemIsEditable; + else + flags ^= Qt::ItemIsEnabled; + } + else if (index.column() == 1) { + if (!m_apps[index.row()].active) + flags ^= Qt::ItemIsEnabled; + } + } + return flags; +} + +void NotifyingApplicationModel::clearApplications() +{ + if (!m_apps.isEmpty()) { + beginRemoveRows(QModelIndex(), 0, m_apps.size() - 1); + m_apps.clear(); + endRemoveRows(); + } +} + +QVariant NotifyingApplicationModel::data(const QModelIndex& index, int role) const +{ + Q_UNUSED(role); + if (!index.isValid() + || index.row() < 0 + || index.row() >= m_apps.size() + || index.column() > 3) + { + return QVariant(); + } + + switch (role) { + case Qt::TextAlignmentRole: { + if (index.column() == 0) + return int(Qt::AlignCenter | Qt::AlignVCenter ); + else + return int(Qt::AlignLeft | Qt::AlignVCenter ); + break; + } + case Qt::DisplayRole: { + if (index.column() == 1) + return m_apps[index.row()].name; + else if (index.column() == 0) + return QVariant();//m_apps[index.row()].active; + else if (index.column() == 2) + return m_apps[index.row()].blacklistExpression.pattern(); + else + return QVariant(); + break; + } + case Qt::DecorationRole: { + if (index.column() == 1) + return QIcon::fromTheme(m_apps[index.row()].icon, QIcon::fromTheme("application-x-executable")); + else + return QVariant(); + break; + } + case Qt::EditRole: { + if (index.column() == 0) + return m_apps[index.row()].active ? Qt::Checked : Qt::Unchecked; + else if (index.column() == 2) + return m_apps[index.row()].blacklistExpression.pattern(); + else + return QVariant(); + break; + } + case Qt::CheckStateRole: { + if (index.column() == 0) + return m_apps[index.row()].active ? Qt::Checked : Qt::Unchecked; + else + return QVariant(); + break; + } + } + return QVariant(); +} + +bool NotifyingApplicationModel::setData(const QModelIndex &index, const QVariant &value, int role) +{ + if (!index.isValid() || + (index.column() != 0 && index.column() != 2) || + index.row() < 0 || index.row() >= m_apps.size()) + return false; + + bool res = false; + QModelIndex bottomRight = createIndex(index.row(), index.column()); + switch (role) { + case Qt::CheckStateRole: { + if (index.column() == 0) { + m_apps[index.row()].active = ((Qt::CheckState)value.toInt() == Qt::Checked); + bottomRight = createIndex(index.row(), index.column() + 1); + res = true; + } + break; + } + case Qt::EditRole: { + if (index.column() == 2) { + m_apps[index.row()].blacklistExpression.setPattern(value.toString()); + res = true; + } + } + } + if (res) { + Q_EMIT dataChanged(index, bottomRight); + Q_EMIT applicationsChanged(); // -> notify config that we need to save + } + return res; +} + +void NotifyingApplicationModel::sort(int column, Qt::SortOrder order) +{ + if (column != 1) + return; + + if (order == Qt::AscendingOrder) + std::sort(m_apps.begin(), m_apps.end(), + [](const NotifyingApplication& a, const NotifyingApplication& b) { + return (a.name.compare(b.name, Qt::CaseInsensitive) < 1); + }); + else + std::sort(m_apps.begin(), m_apps.end(), + [](const NotifyingApplication& a, const NotifyingApplication& b) { + return (b.name.compare(a.name, Qt::CaseInsensitive) < 1); + }); + Q_EMIT dataChanged(createIndex(0, 0), createIndex(m_apps.size(), 2)); +} + +QVariant NotifyingApplicationModel::headerData(int section, Qt::Orientation /*orientation*/, + int role) const +{ + switch (role) { + case Qt::DisplayRole: { + if (section == 1) + return i18n("Name"); + else if (section == 0) + return QVariant(); //i18n("Sync"); + else + return i18n("Blacklisted"); + } + case Qt::ToolTipRole: { + if (section == 1) + return i18n("Name of a notifying application."); + else if (section == 0) + return i18n("Synchronize notifications of an application?"); + else + return i18n("Regular expression defining which notifications should not be sent.\nThis pattern is applied to the summary and, if selected above, the body of notifications."); + } + } + return QVariant(); +} + +int NotifyingApplicationModel::columnCount(const QModelIndex&) const +{ + return 3; +} + +int NotifyingApplicationModel::rowCount(const QModelIndex& parent) const +{ + if(parent.isValid()) { + //Return size 0 if we are a child because this is not a tree + return 0; + } + return m_apps.size(); +} diff --git a/plugins/notifications/notifyingapplicationmodel.h b/plugins/notifications/notifyingapplicationmodel.h new file mode 100644 index 000000000..efeda1951 --- /dev/null +++ b/plugins/notifications/notifyingapplicationmodel.h @@ -0,0 +1,56 @@ +/** + * Copyright 2015 Holger Kaelberer + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * 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 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 NOTIFYINGAPPLICATIONMODEL_H +#define NOTIFYINGAPPLICATIONMODEL_H + +#include + +#include "notifyingapplication.h" + +class NotifyingApplicationModel: public QAbstractTableModel +{ + Q_OBJECT + +public: + explicit NotifyingApplicationModel(QObject *parent = nullptr); + virtual ~NotifyingApplicationModel(); + + virtual QVariant data(const QModelIndex& index, int role) const override; + virtual bool setData(const QModelIndex &index, const QVariant &value, int role) override; + virtual int rowCount(const QModelIndex& parent = QModelIndex()) const override; + virtual int columnCount(const QModelIndex& parent = QModelIndex()) const override; + virtual Qt::ItemFlags flags(const QModelIndex & index) const override; + virtual QVariant headerData(int section, Qt::Orientation orientation, int role) const override; + virtual void sort(int column, Qt::SortOrder order = Qt::AscendingOrder) override; + + QVector apps(); + void clearApplications(); + void appendApp(const NotifyingApplication& app); + bool containsApp(const QString& name) const; + +Q_SIGNALS: + void applicationsChanged(); + +private: + QVector m_apps; +}; + +#endif // NOTIFYINGAPPLICATIONMODEL_H From 4fc39601dd6b94bb9797e087980dc0015f19aec4 Mon Sep 17 00:00:00 2001 From: Holger Kaelberer Date: Sat, 5 Dec 2015 23:13:34 +0100 Subject: [PATCH 5/7] tests: add test-case for NotificationsListener --- plugins/notifications/notificationslistener.h | 2 +- plugins/notifications/notificationsplugin.h | 2 +- tests/CMakeLists.txt | 10 +- tests/testnotificationlistener.cpp | 289 ++++++++++++++++++ 4 files changed, 300 insertions(+), 3 deletions(-) create mode 100644 tests/testnotificationlistener.cpp diff --git a/plugins/notifications/notificationslistener.h b/plugins/notifications/notificationslistener.h index bb37ead06..b3d2b4604 100644 --- a/plugins/notifications/notificationslistener.h +++ b/plugins/notifications/notificationslistener.h @@ -36,7 +36,7 @@ public: NotificationsDbusInterface* aDbusInterface); virtual ~NotificationsListener(); -private: +protected: KdeConnectPlugin* mPlugin; NotificationsDbusInterface* dbusInterface; QHash applications; diff --git a/plugins/notifications/notificationsplugin.h b/plugins/notifications/notificationsplugin.h index 599c89c71..a0e240539 100644 --- a/plugins/notifications/notificationsplugin.h +++ b/plugins/notifications/notificationsplugin.h @@ -47,7 +47,7 @@ public Q_SLOTS: virtual bool receivePackage(const NetworkPackage& np) override; virtual void connected() override; -private: +protected: NotificationsDbusInterface* notificationsDbusInterface; NotificationsListener* notificationsListener; diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index d585d2b45..66aeb5f3f 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -1,5 +1,5 @@ find_package(Qt5 REQUIRED COMPONENTS Test) -find_package(KF5 REQUIRED COMPONENTS KIO) +find_package(KF5 REQUIRED COMPONENTS KIO Notifications) include_directories( ${KDEConnectCore_BINARY_DIR} @@ -19,3 +19,11 @@ ecm_add_test(sendfiletest.cpp LINK_LIBRARIES ${kdeconnect_libraries}) ecm_add_test(networkpackagetests.cpp LINK_LIBRARIES ${kdeconnect_libraries}) ecm_add_test(testsocketlinereader.cpp ../core/backends/lan/socketlinereader.cpp TEST_NAME testsocketlinereader LINK_LIBRARIES ${kdeconnect_libraries}) ecm_add_test(downloadjobtest.cpp ../core/backends/lan/downloadjob.cpp TEST_NAME downloadjobtest LINK_LIBRARIES ${kdeconnect_libraries}) +ecm_add_test(testnotificationlistener.cpp + ../plugins/notifications/notificationslistener.cpp + ../plugins/notifications/notificationsplugin.cpp + ../plugins/notifications/notification.cpp + ../plugins/notifications/notifyingapplication.cpp + ../plugins/notifications/notificationsdbusinterface.cpp + TEST_NAME testnotificationlistener + LINK_LIBRARIES ${kdeconnect_libraries} Qt5::DBus KF5::Notifications) diff --git a/tests/testnotificationlistener.cpp b/tests/testnotificationlistener.cpp new file mode 100644 index 000000000..6219d2779 --- /dev/null +++ b/tests/testnotificationlistener.cpp @@ -0,0 +1,289 @@ +/** + * Copyright 2015 Holger Kaelberer + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * 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 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 +#include +#include +#include +#include +#include +#include + +#include + +#include "core/daemon.h" +#include "core/device.h" +#include "core/kdeconnectplugin.h" +#include "kdeconnect-version.h" +#include "plugins/notifications/notificationsplugin.h" +#include "plugins/notifications/notificationslistener.h" +#include "plugins/notifications/notificationsdbusinterface.h" +#include "plugins/notifications/notifyingapplication.h" + +// Tweaked NotificationsPlugin for testing +class TestNotificationsPlugin : public NotificationsPlugin +{ + Q_OBJECT +public: + explicit TestNotificationsPlugin(QObject *parent, const QVariantList &args) + : NotificationsPlugin(parent, args) + { + // make notificationPosted() inspectable: + connect(notificationsDbusInterface, &NotificationsDbusInterface::notificationPosted, + this, &TestNotificationsPlugin::notificationPosted); + } + + virtual ~TestNotificationsPlugin() {}; + + // allow to access notificationsListener for testing: + NotificationsListener* getNotificationsListener() const + { + return notificationsListener; + } + + void setNotificationsListener(NotificationsListener* value) + { + notificationsListener = value; + } + + NotificationsDbusInterface* getNotificationsDbusInterface() const + { + return notificationsDbusInterface; + } + +Q_SIGNALS: + void notificationPosted(const QString& publicId); +}; + +// Tweaked NotificationsListener for testing: +class TestedNotificationsListener: public NotificationsListener +{ + +public: + explicit TestedNotificationsListener(KdeConnectPlugin* aPlugin, + NotificationsDbusInterface* aDbusInterface) + : NotificationsListener(aPlugin, aDbusInterface) + {} + + virtual ~TestedNotificationsListener() + {} + + QHash& getApplications() + { + return applications; + } + + void setApplications(const QHash& value) + { + applications = value; + } + +}; + +class TestNotificationListener : public QObject +{ + Q_OBJECT + public: + TestNotificationListener() + : plugin(nullptr) + { + QStandardPaths::setTestModeEnabled(true); + } + + private Q_SLOTS: + void testNotify(); + + private: + TestNotificationsPlugin* plugin; +}; + +void TestNotificationListener::testNotify() +{ + // + // set things up: + // + + QString dId("testid"); + Device *d = new Device(nullptr, dId); // not setting any parent or we will double free the dbusInterface + plugin = new TestNotificationsPlugin(this, + QVariantList({ QVariant::fromValue(d), + "notifications_plugin", + {"kdeconnect.notification"}})); + QVERIFY(plugin->getNotificationsListener()); + delete plugin->getNotificationsListener(); + + // inject our tweaked NotificationsListener: + TestedNotificationsListener* listener = new TestedNotificationsListener(plugin, plugin->getNotificationsDbusInterface()); + QVERIFY(listener); + plugin->setNotificationsListener(listener); + QCOMPARE(listener, plugin->getNotificationsListener()); + + // make sure config is default: + plugin->config()->set("generalPersistent", false); + plugin->config()->set("generalIncludeBody", true); + plugin->config()->set("generalUrgency", 0); + QCOMPARE(plugin->config()->get("generalPersistent"), false); + QCOMPARE(plugin->config()->get("generalIncludeBody"), true); + QCOMPARE(plugin->config()->get("generalUrgency"), 0); + + // applications are modified directly: + listener->getApplications().clear(); + QCOMPARE(listener->getApplications().count(), 0); + + // + // Go !!! + // + + uint replacesId = 99; + uint retId; + int notificationId = 0; + QSignalSpy spy(plugin, &TestNotificationsPlugin::notificationPosted); + + // regular Notify call that is synchronized ... + retId = listener->Notify("testApp", replacesId, "some-icon", "summary", "body", {}, {{}}, 0); + // ... should return replacesId, + QCOMPARE(retId, replacesId); + // ... trigger a notificationPosted signal with incremented id + QCOMPARE(spy.count(), 1); + QCOMPARE(spy.takeFirst().at(0).toString(), QString::number(++notificationId)); + // ... and create a new application internally that is initialized correctly: + QCOMPARE(listener->getApplications().count(), 1); + QVERIFY(listener->getApplications().contains("testApp")); + QVERIFY(listener->getApplications()["testApp"].active); + QCOMPARE(listener->getApplications()["testApp"].name, QStringLiteral("testApp")); + QVERIFY(listener->getApplications()["testApp"].blacklistExpression.pattern().isEmpty()); + QCOMPARE(listener->getApplications()["testApp"].name, QStringLiteral("testApp")); + QCOMPARE(listener->getApplications()["testApp"].icon, QStringLiteral("some-icon")); + + // another one, with other timeout and urgency values: + retId = listener->Notify("testApp2", replacesId+1, "some-icon2", "summary2", "body2", {}, {{"urgency", 2}}, 10); + QCOMPARE(retId, replacesId+1); + QCOMPARE(spy.count(), 1); + QCOMPARE(spy.takeFirst().at(0).toString(), QString::number(++notificationId)); + QCOMPARE(listener->getApplications().count(), 2); + QVERIFY(listener->getApplications().contains("testApp2")); + QVERIFY(listener->getApplications().contains("testApp")); + + // if persistent-only is set, timeouts > 0 are not synced: + plugin->config()->set("generalPersistent", true); + retId = listener->Notify("testApp", replacesId, "some-icon", "summary", "body", {}, {{}}, 1); + QCOMPARE(retId, 0U); + QCOMPARE(spy.count(), 0); + retId = listener->Notify("testApp2", replacesId, "some-icon2", "summary2", "body2", {}, {{}}, 3); + QCOMPARE(retId, 0U); + QCOMPARE(spy.count(), 0); + // but timeout == 0 is + retId = listener->Notify("testApp", replacesId, "some-icon", "summary", "body", {}, {{}}, 0); + QCOMPARE(retId, replacesId); + QCOMPARE(spy.count(), 1); + QCOMPARE(spy.takeFirst().at(0).toString(), QString::number(++notificationId)); + plugin->config()->set("generalPersistent", false); + + // if min-urgency is set, lower urgency levels are not synced: + plugin->config()->set("generalUrgency", 1); + retId = listener->Notify("testApp", replacesId, "some-icon", "summary", "body", {}, {{"urgency", 0}}, 0); + QCOMPARE(retId, 0U); + QCOMPARE(spy.count(), 0); + // equal urgency is + retId = listener->Notify("testApp", replacesId, "some-icon", "summary", "body", {}, {{"urgency", 1}}, 0); + QCOMPARE(retId, replacesId); + QCOMPARE(spy.count(), 1); + QCOMPARE(spy.takeFirst().at(0).toString(), QString::number(++notificationId)); + // higher urgency as well + retId = listener->Notify("testApp", replacesId, "some-icon", "summary", "body", {}, {{"urgency", 2}}, 0); + QCOMPARE(retId, replacesId); + QCOMPARE(spy.count(), 1); + QCOMPARE(spy.takeFirst().at(0).toString(), QString::number(++notificationId)); + plugin->config()->set("generalUrgency", 0); + + // notifications for a deactivated application are not synced: + QVERIFY(listener->getApplications().contains("testApp")); + listener->getApplications()["testApp"].active = false; + QVERIFY(!listener->getApplications()["testApp"].active); + retId = listener->Notify("testApp", replacesId, "some-icon", "summary", "body", {}, {{"urgency", 0}}, 0); + QCOMPARE(retId, 0U); + QCOMPARE(spy.count(), 0); + // others are still: + retId = listener->Notify("testApp2", replacesId+1, "some-icon2", "summary2", "body2", {}, {{}}, 0); + QCOMPARE(retId, replacesId+1); + QCOMPARE(spy.count(), 1); + QCOMPARE(spy.takeFirst().at(0).toString(), QString::number(++notificationId)); + // back to normal: + listener->getApplications()["testApp"].active = true; + QVERIFY(listener->getApplications()["testApp"].active); + retId = listener->Notify("testApp", replacesId, "some-icon", "summary", "body", {}, {{}}, 0); + QCOMPARE(retId, replacesId); + QCOMPARE(spy.count(), 1); + QCOMPARE(spy.takeFirst().at(0).toString(), QString::number(++notificationId)); + + // notifications with blacklisted subjects are not synced: + QVERIFY(listener->getApplications().contains("testApp")); + listener->getApplications()["testApp"].blacklistExpression.setPattern("black[12]|foo(bar|baz)"); + retId = listener->Notify("testApp", replacesId, "some-icon", "summary black1", "body", {}, {{}}, 0); + QCOMPARE(retId, 0U); + QCOMPARE(spy.count(), 0); + retId = listener->Notify("testApp", replacesId, "some-icon", "summary foobar", "body", {}, {{}}, 0); + QCOMPARE(retId, 0U); + QCOMPARE(spy.count(), 0); + // other subjects are synced: + retId = listener->Notify("testApp", replacesId, "some-icon", "summary foo", "body", {}, {{}}, 0); + QCOMPARE(retId, replacesId); + QCOMPARE(spy.count(), 1); + QCOMPARE(spy.takeFirst().at(0).toString(), QString::number(++notificationId)); + retId = listener->Notify("testApp", replacesId, "some-icon", "summary black3", "body", {}, {{}}, 0); + QCOMPARE(retId, replacesId); + QCOMPARE(spy.count(), 1); + QCOMPARE(spy.takeFirst().at(0).toString(), QString::number(++notificationId)); + // also body is checked by blacklist if requested: + plugin->config()->set("generalIncludeBody", true); + retId = listener->Notify("testApp", replacesId, "some-icon", "summary", "body black1", {}, {{}}, 0); + QCOMPARE(retId, 0U); + QCOMPARE(spy.count(), 0); + retId = listener->Notify("testApp", replacesId, "some-icon", "summary", "body foobaz", {}, {{}}, 0); + QCOMPARE(retId, 0U); + QCOMPARE(spy.count(), 0); + // body does not matter if inclusion was not requested: + plugin->config()->set("generalIncludeBody", false); + retId = listener->Notify("testApp", replacesId, "some-icon", "summary", "body black1", {}, {{}}, 0); + QCOMPARE(retId, replacesId); + QCOMPARE(spy.count(), 1); + QCOMPARE(spy.takeFirst().at(0).toString(), QString::number(++notificationId)); + retId = listener->Notify("testApp", replacesId, "some-icon", "summary", "body foobaz", {}, {{}}, 0); + QCOMPARE(retId, replacesId); + QCOMPARE(spy.count(), 1); + QCOMPARE(spy.takeFirst().at(0).toString(), QString::number(++notificationId)); + + // back to normal: + listener->getApplications()["testApp"].blacklistExpression.setPattern(""); + plugin->config()->set("generalIncludeBody", true); + retId = listener->Notify("testApp", replacesId, "some-icon", "summary", "body", {}, {{}}, 0); + QCOMPARE(retId, replacesId); + QCOMPARE(spy.count(), 1); + QCOMPARE(spy.takeFirst().at(0).toString(), QString::number(++notificationId)); + retId = listener->Notify("testApp2", replacesId, "some-icon2", "summary2", "body2", {}, {{}}, 0); + QCOMPARE(retId, replacesId); + QCOMPARE(spy.count(), 1); + QCOMPARE(spy.takeFirst().at(0).toString(), QString::number(++notificationId)); +} + + +QTEST_GUILESS_MAIN(TestNotificationListener); + +#include "testnotificationlistener.moc" From 70f55d0ebcd67fb64c24450170b88386c2c70494 Mon Sep 17 00:00:00 2001 From: Aleix Pol Date: Mon, 7 Dec 2015 03:21:35 +0100 Subject: [PATCH 6/7] Fix compilation and a couple of warnings CCMAIL: holger.k@elberer.de --- plugins/notifications/notificationslistener.cpp | 9 +++++---- plugins/notifications/notificationslistener.h | 2 +- tests/testnotificationlistener.cpp | 2 +- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/plugins/notifications/notificationslistener.cpp b/plugins/notifications/notificationslistener.cpp index 7e643af3c..c5f956307 100644 --- a/plugins/notifications/notificationslistener.cpp +++ b/plugins/notifications/notificationslistener.cpp @@ -125,11 +125,12 @@ uint NotificationsListener::Notify(const QString &appName, uint replacesId, return 0; int urgency = -1; - bool ok; - if (hints.contains("urgency")) + if (hints.contains("urgency")) { + bool ok; urgency = hints["urgency"].toInt(&ok); - if (!ok) - urgency = -1; + if (!ok) + urgency = -1; + } if (urgency > -1 && urgency < mPlugin->config()->get("generalUrgency", 0)) return 0; diff --git a/plugins/notifications/notificationslistener.h b/plugins/notifications/notificationslistener.h index b3d2b4604..dfa37b8a9 100644 --- a/plugins/notifications/notificationslistener.h +++ b/plugins/notifications/notificationslistener.h @@ -24,7 +24,7 @@ class KdeConnectPlugin; class NotificationsDbusInterface; class Notification; -class NotifyingApplication; +struct NotifyingApplication; class NotificationsListener : public QDBusAbstractAdaptor { diff --git a/tests/testnotificationlistener.cpp b/tests/testnotificationlistener.cpp index 6219d2779..b80d8645d 100644 --- a/tests/testnotificationlistener.cpp +++ b/tests/testnotificationlistener.cpp @@ -141,7 +141,7 @@ void TestNotificationListener::testNotify() plugin->config()->set("generalUrgency", 0); QCOMPARE(plugin->config()->get("generalPersistent"), false); QCOMPARE(plugin->config()->get("generalIncludeBody"), true); - QCOMPARE(plugin->config()->get("generalUrgency"), 0); + QCOMPARE(plugin->config()->get("generalUrgency"), false); // applications are modified directly: listener->getApplications().clear(); From 42110549f48994fa7b6070660ef4fc99f862a400 Mon Sep 17 00:00:00 2001 From: Aleix Pol Date: Mon, 7 Dec 2015 03:27:40 +0100 Subject: [PATCH 7/7] Improve SharePlugin share notification method Make the share received notification explicitly internal. Fix runtime warning about how QUrl cannot be exported. Notify about all shares, not exclusively transferred files. --- plugins/share/shareplugin.cpp | 10 +++++++--- plugins/share/shareplugin.h | 2 +- tests/sendfiletest.cpp | 4 ++-- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/plugins/share/shareplugin.cpp b/plugins/share/shareplugin.cpp index 44e52e2ca..ecefba4c7 100644 --- a/plugins/share/shareplugin.cpp +++ b/plugins/share/shareplugin.cpp @@ -114,11 +114,15 @@ bool SharePlugin::receivePackage(const NetworkPackage& np) tmpFile.open(); tmpFile.write(text.toUtf8()); tmpFile.close(); - QDesktopServices::openUrl(QUrl::fromLocalFile(tmpFile.fileName())); + + const QUrl url = QUrl::fromLocalFile(tmpFile.fileName()); + Q_EMIT shareReceived(url); + QDesktopServices::openUrl(url); } } else if (np.has("url")) { QUrl url = QUrl::fromEncoded(np.get("url")); QDesktopServices::openUrl(url); + Q_EMIT shareReceived(url); } else { qCDebug(KDECONNECT_PLUGIN_SHARE) << "Error: Nothing attached!"; } @@ -131,7 +135,7 @@ void SharePlugin::finished(KJob* job) { FileTransferJob* ftjob = qobject_cast(job); if (ftjob) - fileReceived(ftjob->destination()); + Q_EMIT shareReceived(ftjob->destination()); qCDebug(KDECONNECT_PLUGIN_SHARE) << "File transfer finished. Success:" << (!job->error()) << (ftjob ? ftjob->destination() : QUrl()); } @@ -156,7 +160,7 @@ void SharePlugin::shareUrl(const QUrl& url) void SharePlugin::connected() { - QDBusConnection::sessionBus().registerObject(dbusPath(), this, QDBusConnection::ExportAllContents); + QDBusConnection::sessionBus().registerObject(dbusPath(), this, QDBusConnection::ExportScriptableContents); } QString SharePlugin::dbusPath() const diff --git a/plugins/share/shareplugin.h b/plugins/share/shareplugin.h index 13b012b64..4149e21d1 100644 --- a/plugins/share/shareplugin.h +++ b/plugins/share/shareplugin.h @@ -48,7 +48,7 @@ private Q_SLOTS: void openDestinationFolder(); Q_SIGNALS: - void fileReceived(const QUrl& url); + void shareReceived(const QUrl& url); private: void shareUrl(const QUrl& url); diff --git a/tests/sendfiletest.cpp b/tests/sendfiletest.cpp index 015818b36..4a400181a 100644 --- a/tests/sendfiletest.cpp +++ b/tests/sendfiletest.cpp @@ -97,11 +97,11 @@ class TestSendFile : public QObject QVERIFY(plugin); plugin->metaObject()->invokeMethod(plugin, "shareUrl", Q_ARG(QString, QUrl::fromLocalFile(temp.fileName()).toString())); - QSignalSpy spy(plugin, SIGNAL(fileReceived(QUrl))); + QSignalSpy spy(plugin, SIGNAL(shareReceived(QUrl))); QVERIFY(spy.wait(2000)); QVariantList args = spy.takeFirst(); - QUrl sentFile = args.first().toUrl(); + QUrl sentFile(args.first().toUrl()); QFile file(sentFile.toLocalFile()); QCOMPARE(file.size(), content.size());