Merge branch 'master' into sslrefactor

This commit is contained in:
Albert Vaca 2015-12-07 03:38:06 -08:00
commit 77facdbcfd
24 changed files with 1384 additions and 12 deletions

View file

@ -62,6 +62,24 @@ QVariant KdeConnectPluginConfig::get(const QString& key, const QVariant& default
return d->mConfig->value(key, defaultValue); 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) void KdeConnectPluginConfig::set(const QString& key, const QVariant& value)
{ {
d->mConfig->setValue(key, value); d->mConfig->setValue(key, value);
@ -69,6 +87,18 @@ void KdeConnectPluginConfig::set(const QString& key, const QVariant& value)
QDBusConnection::sessionBus().send(d->signal); 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() void KdeConnectPluginConfig::slotConfigChanged()
{ {
Q_EMIT configChanged(); Q_EMIT configChanged();

View file

@ -50,6 +50,12 @@ public:
*/ */
void set(const QString& key, const QVariant& value); 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 * Read a key-value pair from this config object
*/ */
@ -62,6 +68,8 @@ public:
return get(key, QVariant(defaultValue)).template value<T>(); //Important note: Awesome template syntax is awesome return get(key, QVariant(defaultValue)).template value<T>(); //Important note: Awesome template syntax is awesome
} }
QVariantList getList(const QString& key, const QVariantList& defaultValue = {});
private Q_SLOTS: private Q_SLOTS:
void slotConfigChanged(); void slotConfigChanged();

View file

@ -30,6 +30,7 @@
<summary xml:lang="ca">Connexió transparent amb els vostres dispositius</summary> <summary xml:lang="ca">Connexió transparent amb els vostres dispositius</summary>
<summary xml:lang="ca-valencia">Connexió transparent amb els vostres dispositius</summary> <summary xml:lang="ca-valencia">Connexió transparent amb els vostres dispositius</summary>
<summary xml:lang="cs">Snadné propojení vašich zařízení</summary> <summary xml:lang="cs">Snadné propojení vašich zařízení</summary>
<summary xml:lang="de">Nahtlose Verbindung zu Ihren Geräten</summary>
<summary xml:lang="en-GB">Seamless connection of your devices</summary> <summary xml:lang="en-GB">Seamless connection of your devices</summary>
<summary xml:lang="es">Conexión sin interrupciones de sus dispositivos</summary> <summary xml:lang="es">Conexión sin interrupciones de sus dispositivos</summary>
<summary xml:lang="fi">Saumaton yhteys laitteisiisi</summary> <summary xml:lang="fi">Saumaton yhteys laitteisiisi</summary>
@ -52,6 +53,7 @@
<p xml:lang="ca">El KDE Connect proporciona la integració entre el telèfon Android i el vostre escriptori.</p> <p xml:lang="ca">El KDE Connect proporciona la integració entre el telèfon Android i el vostre escriptori.</p>
<p xml:lang="ca-valencia">El KDE Connect proporciona la integració entre el telèfon Android i el vostre escriptori.</p> <p xml:lang="ca-valencia">El KDE Connect proporciona la integració entre el telèfon Android i el vostre escriptori.</p>
<p xml:lang="cs">KDE Connect poskytuje integraci mezi vaším telefonem s Androidem a vaší pracovní plochou.</p> <p xml:lang="cs">KDE Connect poskytuje integraci mezi vaším telefonem s Androidem a vaší pracovní plochou.</p>
<p xml:lang="de">KDE Connect bietet Integrationsdienste zwischen Ihrem Android-Telefon und Ihrem Desktop-Computer.</p>
<p xml:lang="en-GB">KDE Connect provides integration between your Android phone and your desktop.</p> <p xml:lang="en-GB">KDE Connect provides integration between your Android phone and your desktop.</p>
<p xml:lang="es">KDE Connect proporciona integración entre su teléfono Android y su escritorio.</p> <p xml:lang="es">KDE Connect proporciona integración entre su teléfono Android y su escritorio.</p>
<p xml:lang="fi">KDE Connect tarjoaa integraation Android-puhelimesi ja työpöytäsi välillä.</p> <p xml:lang="fi">KDE Connect tarjoaa integraation Android-puhelimesi ja työpöytäsi välillä.</p>

View file

@ -1,9 +1,11 @@
find_package(KF5 REQUIRED COMPONENTS Notifications) find_package(KF5 REQUIRED COMPONENTS Notifications KCMUtils I18n)
set(kdeconnect_notifications_SRCS set(kdeconnect_notifications_SRCS
notification.cpp notification.cpp
notificationsplugin.cpp notificationsplugin.cpp
notificationsdbusinterface.cpp notificationsdbusinterface.cpp
notificationslistener.cpp
notifyingapplication.cpp
) )
kdeconnect_add_plugin(kdeconnect_notifications JSON kdeconnect_notifications.json SOURCES ${kdeconnect_notifications_SRCS}) kdeconnect_add_plugin(kdeconnect_notifications JSON kdeconnect_notifications.json SOURCES ${kdeconnect_notifications_SRCS})
@ -12,4 +14,26 @@ target_link_libraries(kdeconnect_notifications
kdeconnectcore kdeconnectcore
Qt5::DBus Qt5::DBus
KF5::Notifications 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} )

View file

@ -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;

View file

@ -0,0 +1,113 @@
/**
* Copyright 2015 Holger Kaelberer <holger.k@elberer.de>
*
* 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 <http://www.gnu.org/licenses/>.
*/
#include "notifications_config.h"
#include "ui_notifications_config.h"
#include "notifyingapplicationmodel.h"
#include <KCModule>
#include <KPluginFactory>
K_PLUGIN_FACTORY(NotificationsConfigFactory, registerPlugin<NotificationsConfig>();)
NotificationsConfig::NotificationsConfig(QWidget *parent, const QVariantList& args)
: KdeConnectPluginKcm(parent, args, "kdeconnect_notifications_config")
, m_ui(new Ui::NotificationsConfigUi())
, appModel(new NotifyingApplicationModel)
{
qRegisterMetaTypeStreamOperators<NotifyingApplication>("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<NotifyingApplication>();
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<NotifyingApplication>(a);
config()->setList("applications", list);
KCModule::save();
Q_EMIT changed(false);
}
#include "notifications_config.moc"

View file

@ -0,0 +1,54 @@
/**
* Copyright 2015 Holger Kaelberer <holger.k@elberer.de>
*
* 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 <http://www.gnu.org/licenses/>.
*/
#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

View file

@ -0,0 +1,210 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>NotificationsConfigUi</class>
<widget class="QWidget" name="NotificationsConfigUi">
<property name="windowModality">
<enum>Qt::WindowModal</enum>
</property>
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>350</width>
<height>326</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>350</width>
<height>0</height>
</size>
</property>
<property name="windowTitle">
<string>Pause music plugin</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QGroupBox" name="groupBox_2">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="font">
<font>
<pointsize>11</pointsize>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="title">
<string>General</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QCheckBox" name="check_persistent">
<property name="font">
<font>
<weight>50</weight>
<bold>false</bold>
</font>
</property>
<property name="toolTip">
<string>Synchronize only notifications with a timeout value of 0?</string>
</property>
<property name="text">
<string>Persistent notifications only</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="check_body">
<property name="font">
<font>
<weight>50</weight>
<bold>false</bold>
</font>
</property>
<property name="toolTip">
<string>Append the notification body to the summary when synchronizing notifications?</string>
</property>
<property name="text">
<string>Include body</string>
</property>
</widget>
</item>
<item>
<widget class="QWidget" name="horizontalWidget" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QSpinBox" name="spin_urgency">
<property name="maximumSize">
<size>
<width>40</width>
<height>32</height>
</size>
</property>
<property name="font">
<font>
<weight>50</weight>
<bold>false</bold>
</font>
</property>
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Minimum urgency level of the notifications&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="maximum">
<number>2</number>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label">
<property name="font">
<font>
<weight>50</weight>
<bold>false</bold>
</font>
</property>
<property name="toolTip">
<string>Synchronize only notifications with the given urgency level.</string>
</property>
<property name="text">
<string>Minimum urgency level</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="font">
<font>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="toolTip">
<string/>
</property>
<property name="title">
<string>Applications</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QTableView" name="appList">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="font">
<font>
<weight>50</weight>
<bold>false</bold>
</font>
</property>
<property name="alternatingRowColors">
<bool>true</bool>
</property>
<property name="showGrid">
<bool>false</bool>
</property>
<property name="gridStyle">
<enum>Qt::NoPen</enum>
</property>
<property name="sortingEnabled">
<bool>true</bool>
</property>
<attribute name="horizontalHeaderVisible">
<bool>true</bool>
</attribute>
<attribute name="horizontalHeaderDefaultSectionSize">
<number>150</number>
</attribute>
<attribute name="horizontalHeaderMinimumSectionSize">
<number>20</number>
</attribute>
<attribute name="horizontalHeaderShowSortIndicator" stdset="0">
<bool>true</bool>
</attribute>
<attribute name="horizontalHeaderStretchLastSection">
<bool>true</bool>
</attribute>
<attribute name="verticalHeaderVisible">
<bool>false</bool>
</attribute>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

View file

@ -45,6 +45,7 @@ NotificationsDbusInterface::NotificationsDbusInterface(KdeConnectPlugin* plugin)
NotificationsDbusInterface::~NotificationsDbusInterface() NotificationsDbusInterface::~NotificationsDbusInterface()
{ {
qCDebug(KDECONNECT_PLUGIN_NOTIFICATION) << "Destroying NotificationsDbusInterface";
} }
void NotificationsDbusInterface::clearNotifications() void NotificationsDbusInterface::clearNotifications()
@ -60,7 +61,21 @@ QStringList NotificationsDbusInterface::activeNotifications()
void NotificationsDbusInterface::processPackage(const NetworkPackage& np) void NotificationsDbusInterface::processPackage(const NetworkPackage& np)
{ {
if (np.get<bool>("isCancel")) { if (np.get<bool>("isCancel")) {
removeNotification(np.get<QString>("id")); QString id = np.get<QString>("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<bool>("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 { } else {
//TODO: Uncoment when we are able to display app icon on plasmoid //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; qCDebug(KDECONNECT_PLUGIN_NOTIFICATION) << "removeNotification" << internalId;
if (!mInternalIdToPublicId.contains(internalId)) { if (!mInternalIdToPublicId.contains(internalId)) {
qCDebug(KDECONNECT_PLUGIN_NOTIFICATION) << "Not found"; qCDebug(KDECONNECT_PLUGIN_NOTIFICATION) << "Not found: " << internalId;
return; return;
} }

View file

@ -45,6 +45,7 @@ public:
void processPackage(const NetworkPackage& np); void processPackage(const NetworkPackage& np);
void clearNotifications(); void clearNotifications();
void dismissRequested(const QString& notification); void dismissRequested(const QString& notification);
void addNotification(Notification* noti);
public Q_SLOTS: public Q_SLOTS:
Q_SCRIPTABLE QStringList activeNotifications(); Q_SCRIPTABLE QStringList activeNotifications();
@ -54,7 +55,6 @@ Q_SIGNALS:
Q_SCRIPTABLE void notificationRemoved(const QString& publicId); Q_SCRIPTABLE void notificationRemoved(const QString& publicId);
private /*methods*/: private /*methods*/:
void addNotification(Notification* noti);
void removeNotification(const QString& internalId); void removeNotification(const QString& internalId);
QString newId(); //Generates successive identifitiers to use as public ids QString newId(); //Generates successive identifitiers to use as public ids

View file

@ -0,0 +1,161 @@
/**
* Copyright 2015 Holger Kaelberer <holger.k@elberer.de>
*
* 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 <http://www.gnu.org/licenses/>.
*/
#include <QtDBus/QDBusConnection>
#include <QtDBus/QDBusInterface>
#include <QtDebug>
#include <QLoggingCategory>
#include <core/device.h>
#include <core/kdeconnectplugin.h>
#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>("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<NotifyingApplication>();
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<NotifyingApplication>(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<int>("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 << ":" <<ticker;
NetworkPackage np(PACKAGE_TYPE_NOTIFICATION);
np.set("id", QString::number(replacesId > 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);
}

View file

@ -0,0 +1,52 @@
/**
* Copyright 2015 Holger Kaelberer <holger.k@elberer.de>
*
* 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 <http://www.gnu.org/licenses/>.
*/
#include <QtDBus/QDBusAbstractAdaptor>
#include <core/device.h>
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<QString, NotifyingApplication> 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();
};

View file

@ -21,6 +21,7 @@
#include "notificationsplugin.h" #include "notificationsplugin.h"
#include "notificationsdbusinterface.h" #include "notificationsdbusinterface.h"
#include "notificationslistener.h"
#include "notification_debug.h" #include "notification_debug.h"
#include <KPluginFactory> #include <KPluginFactory>
@ -33,6 +34,7 @@ NotificationsPlugin::NotificationsPlugin(QObject* parent, const QVariantList& ar
: KdeConnectPlugin(parent, args) : KdeConnectPlugin(parent, args)
{ {
notificationsDbusInterface = new NotificationsDbusInterface(this); notificationsDbusInterface = new NotificationsDbusInterface(this);
notificationsListener = new NotificationsListener(this, notificationsDbusInterface);
} }
void NotificationsPlugin::connected() void NotificationsPlugin::connected()
@ -44,6 +46,7 @@ void NotificationsPlugin::connected()
NotificationsPlugin::~NotificationsPlugin() NotificationsPlugin::~NotificationsPlugin()
{ {
qCDebug(KDECONNECT_PLUGIN_NOTIFICATION) << "Destroying NotificationsPlugin";
//FIXME: Qt dbus does not allow to remove an adaptor! (it causes a crash in //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 // 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 // is that disabling the plugin leaks the interface. As a mitigation we are

View file

@ -32,6 +32,7 @@
* KdeConnectPlugin at the same time (both are QObject) * KdeConnectPlugin at the same time (both are QObject)
*/ */
class NotificationsDbusInterface; class NotificationsDbusInterface;
class NotificationsListener;
class NotificationsPlugin class NotificationsPlugin
: public KdeConnectPlugin : public KdeConnectPlugin
@ -46,8 +47,9 @@ public Q_SLOTS:
virtual bool receivePackage(const NetworkPackage& np) override; virtual bool receivePackage(const NetworkPackage& np) override;
virtual void connected() override; virtual void connected() override;
private: protected:
NotificationsDbusInterface* notificationsDbusInterface; NotificationsDbusInterface* notificationsDbusInterface;
NotificationsListener* notificationsListener;
}; };

View file

@ -0,0 +1,50 @@
/**
* Copyright 2015 Holger Kaelberer <holger.k@elberer.de>
*
* 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 <http://www.gnu.org/licenses/>.
*/
#include "notifyingapplication.h"
#include <QDebug>
#include <QDataStream>
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();
}

View file

@ -0,0 +1,44 @@
/**
* Copyright 2015 Holger Kaelberer <holger.k@elberer.de>
*
* 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 <http://www.gnu.org/licenses/>.
*/
#ifndef NOTIFYINGAPPLICATION_H
#define NOTIFYINGAPPLICATION_H
#include <QRegularExpression>
#include <QList>
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

View file

@ -0,0 +1,235 @@
/**
* Copyright 2015 Holger Kaelberer <holger.k@elberer.de>
*
* 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 <http://www.gnu.org/licenses/>.
*/
#include <QString>
#include <QIcon>
#include <QDebug>
#include <KLocalizedString>
#include <algorithm>
#include "notifyingapplicationmodel.h"
//#include "modeltest.h"
NotifyingApplicationModel::NotifyingApplicationModel(QObject *parent)
: QAbstractTableModel(parent)
{
}
NotifyingApplicationModel::~NotifyingApplicationModel()
{
}
QVector<NotifyingApplication> 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();
}

View file

@ -0,0 +1,56 @@
/**
* Copyright 2015 Holger Kaelberer <holger.k@elberer.de>
*
* 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 <http://www.gnu.org/licenses/>.
*/
#ifndef NOTIFYINGAPPLICATIONMODEL_H
#define NOTIFYINGAPPLICATIONMODEL_H
#include <QAbstractTableModel>
#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<NotifyingApplication> apps();
void clearApplications();
void appendApp(const NotifyingApplication& app);
bool containsApp(const QString& name) const;
Q_SIGNALS:
void applicationsChanged();
private:
QVector<NotifyingApplication> m_apps;
};
#endif // NOTIFYINGAPPLICATIONMODEL_H

View file

@ -12,6 +12,7 @@
"Description[ca@valencia]": "Sistemes de control remot", "Description[ca@valencia]": "Sistemes de control remot",
"Description[ca]": "Sistemes de control remot", "Description[ca]": "Sistemes de control remot",
"Description[cs]": "Ovládejte vzdálené systémy", "Description[cs]": "Ovládejte vzdálené systémy",
"Description[de]": "Entfernte Systeme steuern",
"Description[en_GB]": "Control Remote systems", "Description[en_GB]": "Control Remote systems",
"Description[es]": "Controlar sistemas remotos", "Description[es]": "Controlar sistemas remotos",
"Description[fi]": "Ohjaa järjestelmiä etänä", "Description[fi]": "Ohjaa järjestelmiä etänä",
@ -38,6 +39,7 @@
"Name[ca@valencia]": "Control remot", "Name[ca@valencia]": "Control remot",
"Name[ca]": "Control remot", "Name[ca]": "Control remot",
"Name[cs]": "Dálkové ovládání", "Name[cs]": "Dálkové ovládání",
"Name[de]": "Fernsteuerung",
"Name[en_GB]": "RemoteControl", "Name[en_GB]": "RemoteControl",
"Name[es]": "Control remoto", "Name[es]": "Control remoto",
"Name[fi]": "Kauko-ohjain", "Name[fi]": "Kauko-ohjain",

View file

@ -114,11 +114,15 @@ bool SharePlugin::receivePackage(const NetworkPackage& np)
tmpFile.open(); tmpFile.open();
tmpFile.write(text.toUtf8()); tmpFile.write(text.toUtf8());
tmpFile.close(); 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")) { } else if (np.has("url")) {
QUrl url = QUrl::fromEncoded(np.get<QByteArray>("url")); QUrl url = QUrl::fromEncoded(np.get<QByteArray>("url"));
QDesktopServices::openUrl(url); QDesktopServices::openUrl(url);
Q_EMIT shareReceived(url);
} else { } else {
qCDebug(KDECONNECT_PLUGIN_SHARE) << "Error: Nothing attached!"; qCDebug(KDECONNECT_PLUGIN_SHARE) << "Error: Nothing attached!";
} }
@ -131,7 +135,7 @@ void SharePlugin::finished(KJob* job)
{ {
FileTransferJob* ftjob = qobject_cast<FileTransferJob*>(job); FileTransferJob* ftjob = qobject_cast<FileTransferJob*>(job);
if (ftjob) if (ftjob)
fileReceived(ftjob->destination()); Q_EMIT shareReceived(ftjob->destination());
qCDebug(KDECONNECT_PLUGIN_SHARE) << "File transfer finished. Success:" << (!job->error()) << (ftjob ? ftjob->destination() : QUrl()); 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() void SharePlugin::connected()
{ {
QDBusConnection::sessionBus().registerObject(dbusPath(), this, QDBusConnection::ExportAllContents); QDBusConnection::sessionBus().registerObject(dbusPath(), this, QDBusConnection::ExportScriptableContents);
} }
QString SharePlugin::dbusPath() const QString SharePlugin::dbusPath() const

View file

@ -48,7 +48,7 @@ private Q_SLOTS:
void openDestinationFolder(); void openDestinationFolder();
Q_SIGNALS: Q_SIGNALS:
void fileReceived(const QUrl& url); void shareReceived(const QUrl& url);
private: private:
void shareUrl(const QUrl& url); void shareUrl(const QUrl& url);

View file

@ -1,5 +1,5 @@
find_package(Qt5 REQUIRED COMPONENTS Test) find_package(Qt5 REQUIRED COMPONENTS Test)
find_package(KF5 REQUIRED COMPONENTS KIO) find_package(KF5 REQUIRED COMPONENTS KIO Notifications)
include_directories( include_directories(
${KDEConnectCore_BINARY_DIR} ${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(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(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)

View file

@ -67,11 +67,11 @@ class TestSendFile : public QObject
QVERIFY(plugin); QVERIFY(plugin);
plugin->metaObject()->invokeMethod(plugin, "shareUrl", Q_ARG(QString, QUrl::fromLocalFile(temp.fileName()).toString())); 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)); QVERIFY(spy.wait(2000));
QVariantList args = spy.takeFirst(); QVariantList args = spy.takeFirst();
QUrl sentFile = args.first().toUrl(); QUrl sentFile(args.first().toUrl());
QFile file(sentFile.toLocalFile()); QFile file(sentFile.toLocalFile());
QCOMPARE(file.size(), content.size()); QCOMPARE(file.size(), content.size());

View file

@ -0,0 +1,289 @@
/**
* Copyright 2015 Holger Kaelberer <holger.k@elberer.de>
*
* 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 <http://www.gnu.org/licenses/>.
*/
#include <QSocketNotifier>
#include <QApplication>
#include <QNetworkAccessManager>
#include <QTest>
#include <QTemporaryFile>
#include <QSignalSpy>
#include <QStandardPaths>
#include <KIO/AccessManager>
#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<QString, NotifyingApplication>& getApplications()
{
return applications;
}
void setApplications(const QHash<QString, NotifyingApplication>& 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<Device*>(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<bool>("generalPersistent"), false);
QCOMPARE(plugin->config()->get<bool>("generalIncludeBody"), true);
QCOMPARE(plugin->config()->get<bool>("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"