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/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ä.

diff --git a/plugins/notifications/CMakeLists.txt b/plugins/notifications/CMakeLists.txt index 41c5b78f8..448be94d4 100644 --- a/plugins/notifications/CMakeLists.txt +++ b/plugins/notifications/CMakeLists.txt @@ -1,9 +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}) @@ -12,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/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..c5f956307 --- /dev/null +++ b/plugins/notifications/notificationslistener.cpp @@ -0,0 +1,161 @@ +/** + * 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" +#include "notifyingapplication.h" + +NotificationsListener::NotificationsListener(KdeConnectPlugin* aPlugin, + NotificationsDbusInterface* aDbusInterface) + : QDBusAbstractAdaptor(aPlugin), + mPlugin(aPlugin), + dbusInterface(aDbusInterface) +{ + qRegisterMetaTypeStreamOperators("NotifyingApplication"); + + 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'"); + + loadApplications(); + + connect(mPlugin->config(), SIGNAL(configChanged()), this, SLOT(loadApplications())); +} + +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"); +} + +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, + const QStringList &actions, + const QVariantMap &hints, int timeout) +{ + static int id = 0; + Q_UNUSED(actions); + + //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; + if (hints.contains("urgency")) { + bool ok; + 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", ticker); + 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..dfa37b8a9 --- /dev/null +++ b/plugins/notifications/notificationslistener.h @@ -0,0 +1,52 @@ +/** + * 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; +struct NotifyingApplication; + +class NotificationsListener : public QDBusAbstractAdaptor +{ + Q_OBJECT + Q_CLASSINFO("D-Bus Interface", "org.freedesktop.Notifications") + +public: + explicit NotificationsListener(KdeConnectPlugin* aPlugin, + NotificationsDbusInterface* aDbusInterface); + virtual ~NotificationsListener(); + +protected: + 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/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..a0e240539 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 @@ -46,8 +47,9 @@ public Q_SLOTS: virtual bool receivePackage(const NetworkPackage& np) override; virtual void connected() override; -private: +protected: NotificationsDbusInterface* notificationsDbusInterface; + NotificationsListener* notificationsListener; }; 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 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", 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/CMakeLists.txt b/tests/CMakeLists.txt index a2db3bbac..1f52153ff 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} @@ -56,3 +56,11 @@ ecm_add_test(lanlinkprovidertest.cpp ${lanlinkprovidertest_sources} TEST_NAME ecm_add_test(devicetest.cpp ${lanlinkprovidertest_sources} TEST_NAME devicetest 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/sendfiletest.cpp b/tests/sendfiletest.cpp index 28f207dd3..667c86e76 100644 --- a/tests/sendfiletest.cpp +++ b/tests/sendfiletest.cpp @@ -67,11 +67,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()); diff --git a/tests/testnotificationlistener.cpp b/tests/testnotificationlistener.cpp new file mode 100644 index 000000000..b80d8645d --- /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"), false); + + // 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"