Compare commits

...

5 commits

Author SHA1 Message Date
Nicolas Fella
44ccf6f000 Revert test mode 2019-03-22 22:43:28 +01:00
Nicolas Fella
d890877645 Port to QQC2 dialog 2019-03-22 22:43:28 +01:00
Nicolas Fella
5ef1b1420a Port remaining plugins 2019-03-22 22:43:28 +01:00
Nicolas Fella
f0c09e9fd3 Remove spacing 2019-03-22 22:30:09 +01:00
Nicolas Fella
e827ec4d6d [WIP] Add plugin setting to app and start port plugin KCMs to QML 2019-03-22 22:30:09 +01:00
31 changed files with 1037 additions and 39 deletions

View file

@ -13,7 +13,7 @@ find_package(PkgConfig)
if (SAILFISHOS) if (SAILFISHOS)
set(KF5_MIN_VERSION "5.31.0") set(KF5_MIN_VERSION "5.31.0")
set(QT_MIN_VERSION "5.6.0") set(QT_MIN_VERSION "5.6.0")
set(KF5_REQUIRED_COMPONENTS I18n DBusAddons CoreAddons IconThemes Config) set(KF5_REQUIRED_COMPONENTS I18n DBusAddons CoreAddons IconThemes Config Service)
set(KF5_OPTIONAL_COMPONENTS) set(KF5_OPTIONAL_COMPONENTS)
set(QCA_MIN_VERSION 2.0.0) set(QCA_MIN_VERSION 2.0.0)
pkg_search_module(SFOS REQUIRED sailfishapp) pkg_search_module(SFOS REQUIRED sailfishapp)

View file

@ -5,4 +5,7 @@
# Thoroughly inspired in kdevplatform_add_plugin # Thoroughly inspired in kdevplatform_add_plugin
function(kdeconnect_add_plugin) function(kdeconnect_add_plugin)
kcoreaddons_add_plugin(${ARGN} INSTALL_NAMESPACE kdeconnect) kcoreaddons_add_plugin(${ARGN} INSTALL_NAMESPACE kdeconnect)
if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/${ARGV0}_config.qml")
install(FILES "${ARGV0}_config.qml" DESTINATION ${DATA_INSTALL_DIR}/kdeconnect)
endif()
endfunction() endfunction()

View file

@ -72,6 +72,15 @@ Kirigami.Page
onTriggered: { onTriggered: {
deviceView.currentDevice.pluginCall("ping", "sendPing"); deviceView.currentDevice.pluginCall("ping", "sendPing");
} }
},
Kirigami.Action {
text: i18n("Plugin settings")
onTriggered: {
pageStack.push(
deviceComp,
{device: currentDevice.id()}
);
}
} }
] ]
@ -191,6 +200,11 @@ Kirigami.Page
} }
} }
Component {
id: deviceComp
PluginSettings {}
}
FileDialog { FileDialog {
id: fileDialog id: fileDialog
title: i18n("Please choose a file") title: i18n("Please choose a file")

View file

@ -0,0 +1,45 @@
/*
* Copyright 2019 Nicolas Fella <nicolas.fella@gmx.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/>.
*/
import QtQuick 2.2
import QtQuick.Controls 2.2
import org.kde.kirigami 2.0 as Kirigami
Kirigami.Page
{
id: root
property string configFile
property string device
actions.main: loader.item.action
Loader {
anchors.fill: parent
id: loader
Component.onCompleted: {
setSource(configFile, {
device: root.device
})
}
}
}

View file

@ -0,0 +1,78 @@
/*
* Copyright 2019 Nicolas Fella <nicolas.fella@gmx.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/>.
*/
import QtQuick 2.2
import QtQuick.Controls 2.2
import QtQuick.Layouts 1.1
import org.kde.kirigami 2.0 as Kirigami
import org.kde.kdeconnect 1.0
Kirigami.ScrollablePage
{
id: root
title: i18n("Plugin settings")
property string device
ListView {
id: sinkList
Component {
id: pluginInfo
PluginInfoPage {}
}
anchors.fill: parent
model: PluginModel {
deviceId: device
}
delegate: Kirigami.SwipeListItem {
width: parent.width
enabled: true
supportsMouseEvents: false
CheckBox {
checked: isChecked
text: name
onClicked: {
isChecked = checked
}
}
actions: [
Kirigami.Action {
icon.name: "settings-configure"
visible: configSource != ""
onTriggered: {
pageStack.push(pluginInfo, {
title: name,
configFile: configSource,
device: root.device
})
}
}
]
}
}
}

View file

@ -10,5 +10,7 @@
<file>qml/FindDevicesPage.qml</file> <file>qml/FindDevicesPage.qml</file>
<file>qml/runcommand.qml</file> <file>qml/runcommand.qml</file>
<file>qml/volume.qml</file> <file>qml/volume.qml</file>
<file>qml/PluginSettings.qml</file>
<file>qml/PluginInfoPage.qml</file>
</qresource> </qresource>
</RCC> </RCC>

View file

@ -24,6 +24,7 @@
#include <QSettings> #include <QSettings>
#include <QDBusMessage> #include <QDBusMessage>
#include <QDBusConnection> #include <QDBusConnection>
#include <QDebug>
#include "kdeconnectconfig.h" #include "kdeconnectconfig.h"
@ -34,6 +35,12 @@ struct KdeConnectPluginConfigPrivate
QDBusMessage m_signal; QDBusMessage m_signal;
}; };
KdeConnectPluginConfig::KdeConnectPluginConfig()
: d(new KdeConnectPluginConfigPrivate())
{
}
KdeConnectPluginConfig::KdeConnectPluginConfig(const QString& deviceId, const QString& pluginName) KdeConnectPluginConfig::KdeConnectPluginConfig(const QString& deviceId, const QString& pluginName)
: d(new KdeConnectPluginConfigPrivate()) : d(new KdeConnectPluginConfigPrivate())
{ {
@ -42,8 +49,9 @@ KdeConnectPluginConfig::KdeConnectPluginConfig(const QString& deviceId, const QS
d->m_config = new QSettings(d->m_configDir.absoluteFilePath(QStringLiteral("config")), QSettings::IniFormat); d->m_config = new QSettings(d->m_configDir.absoluteFilePath(QStringLiteral("config")), QSettings::IniFormat);
d->m_signal = QDBusMessage::createSignal("/kdeconnect/"+deviceId+"/"+pluginName, QStringLiteral("org.kde.kdeconnect.config"), QStringLiteral("configChanged")); d->m_signal = QDBusMessage::createSignal("/kdeconnect/" + deviceId + "/" + pluginName, QStringLiteral("org.kde.kdeconnect.config"), QStringLiteral("configChanged"));
QDBusConnection::sessionBus().connect(QLatin1String(""), "/kdeconnect/"+deviceId+"/"+pluginName, QStringLiteral("org.kde.kdeconnect.config"), QStringLiteral("configChanged"), this, SLOT(slotConfigChanged())); QDBusConnection::sessionBus().connect(QLatin1String(""), "/kdeconnect/" + deviceId + "/" + pluginName, QStringLiteral("org.kde.kdeconnect.config"), QStringLiteral("configChanged"), this, SLOT(slotConfigChanged()));
} }
KdeConnectPluginConfig::~KdeConnectPluginConfig() KdeConnectPluginConfig::~KdeConnectPluginConfig()
@ -51,10 +59,44 @@ KdeConnectPluginConfig::~KdeConnectPluginConfig()
delete d->m_config; delete d->m_config;
} }
QVariant KdeConnectPluginConfig::get(const QString& key, const QVariant& defaultValue) QString KdeConnectPluginConfig::getString(const QString& key, const QString& defaultValue)
{ {
if (!d->m_config) {
loadConfig();
}
d->m_config->sync(); d->m_config->sync();
return d->m_config->value(key, defaultValue); return d->m_config->value(key, defaultValue).toString();
}
bool KdeConnectPluginConfig::getBool(const QString& key, const bool defaultValue)
{
if (!d->m_config) {
loadConfig();
}
d->m_config->sync();
return d->m_config->value(key, defaultValue).toBool();
}
int KdeConnectPluginConfig::getInt(const QString& key, const int defaultValue)
{
if (!d->m_config) {
loadConfig();
}
d->m_config->sync();
return d->m_config->value(key, defaultValue).toInt();
}
QByteArray KdeConnectPluginConfig::getByteArray(const QString& key, const QByteArray defaultValue)
{
if (!d->m_config) {
loadConfig();
}
d->m_config->sync();
return d->m_config->value(key, defaultValue).toByteArray();
} }
QVariantList KdeConnectPluginConfig::getList(const QString& key, QVariantList KdeConnectPluginConfig::getList(const QString& key,
@ -98,3 +140,47 @@ void KdeConnectPluginConfig::slotConfigChanged()
{ {
Q_EMIT configChanged(); Q_EMIT configChanged();
} }
void KdeConnectPluginConfig::setDeviceId(const QString& deviceId)
{
if (deviceId != m_deviceId) {
m_deviceId = deviceId;
}
if (!m_deviceId.isEmpty() && !m_pluginName.isEmpty()) {
loadConfig();
}
}
QString KdeConnectPluginConfig::deviceId()
{
return m_deviceId;
}
void KdeConnectPluginConfig::setPluginName(const QString& pluginName)
{
if (pluginName != m_pluginName) {
m_pluginName = pluginName;
}
if (!m_deviceId.isEmpty() && !m_pluginName.isEmpty()) {
loadConfig();
}
}
QString KdeConnectPluginConfig::pluginName()
{
return m_pluginName;
}
void KdeConnectPluginConfig::loadConfig()
{
d->m_configDir = KdeConnectConfig::instance()->pluginConfigDir(m_deviceId, m_pluginName);
QDir().mkpath(d->m_configDir.path());
d->m_config = new QSettings(d->m_configDir.absoluteFilePath(QStringLiteral("config")), QSettings::IniFormat);
d->m_signal = QDBusMessage::createSignal("/kdeconnect/" + m_deviceId + "/" + m_pluginName, QStringLiteral("org.kde.kdeconnect.config"), QStringLiteral("configChanged"));
QDBusConnection::sessionBus().connect(QLatin1String(""), "/kdeconnect/" + m_deviceId + "/" + m_pluginName, QStringLiteral("org.kde.kdeconnect.config"), QStringLiteral("configChanged"), this, SLOT(slotConfigChanged()));
Q_EMIT configChanged();
}

View file

@ -27,7 +27,7 @@
#include <QStringList> #include <QStringList>
#include <QVariant> #include <QVariant>
#include "kdeconnectcore_export.h" #include "core/kdeconnectcore_export.h"
struct KdeConnectPluginConfigPrivate; struct KdeConnectPluginConfigPrivate;
@ -35,14 +35,18 @@ class KDECONNECTCORE_EXPORT KdeConnectPluginConfig : public QObject
{ {
Q_OBJECT Q_OBJECT
Q_PROPERTY(QString deviceId READ deviceId WRITE setDeviceId NOTIFY configChanged)
Q_PROPERTY(QString pluginName READ pluginName WRITE setPluginName NOTIFY configChanged)
public: public:
KdeConnectPluginConfig();
KdeConnectPluginConfig(const QString& deviceId, const QString& pluginName); KdeConnectPluginConfig(const QString& deviceId, const QString& pluginName);
~KdeConnectPluginConfig() override; ~KdeConnectPluginConfig() override;
/** /**
* Store a key-value pair in this config object * Store a key-value pair in this config object
*/ */
void set(const QString& key, const QVariant& value); Q_SCRIPTABLE void set(const QString& key, const QVariant& value);
/** /**
* Store a list of values in this config object under the array name * Store a list of values in this config object under the array name
@ -53,17 +57,19 @@ public:
/** /**
* Read a key-value pair from this config object * Read a key-value pair from this config object
*/ */
QVariant get(const QString& key, const QVariant& defaultValue); Q_SCRIPTABLE QString getString(const QString& key, const QString& defaultValue);
Q_SCRIPTABLE bool getBool(const QString& key, const bool defaultValue);
/** Q_SCRIPTABLE int getInt(const QString& key, const int defaultValue);
* Convenience method that will convert the QVariant to whatever type for you Q_SCRIPTABLE QByteArray getByteArray(const QString& key, const QByteArray defaultValue);
*/
template<typename T> T get(const QString& key, const T& defaultValue = {}) {
return get(key, QVariant(defaultValue)).template value<T>(); //Important note: Awesome template syntax is awesome
}
QVariantList getList(const QString& key, const QVariantList& defaultValue = {}); QVariantList getList(const QString& key, const QVariantList& defaultValue = {});
QString deviceId();
void setDeviceId(const QString& deviceId);
QString pluginName();
void setPluginName(const QString& pluginName);
private Q_SLOTS: private Q_SLOTS:
void slotConfigChanged(); void slotConfigChanged();
@ -71,7 +77,11 @@ Q_SIGNALS:
void configChanged(); void configChanged();
private: private:
void loadConfig();
QScopedPointer<KdeConnectPluginConfigPrivate> d; QScopedPointer<KdeConnectPluginConfigPrivate> d;
QString m_deviceId;
QString m_pluginName;
}; };
#endif #endif

View file

@ -24,6 +24,7 @@ add_library(kdeconnectdeclarativeplugin SHARED ${kdeconnectdeclarativeplugin_SRC
target_link_libraries(kdeconnectdeclarativeplugin target_link_libraries(kdeconnectdeclarativeplugin
Qt5::Qml Qt5::Qml
kdeconnectinterfaces kdeconnectinterfaces
kdeconnectcore
) )
install(TARGETS kdeconnectdeclarativeplugin DESTINATION ${QML_INSTALL_DIR}/org/kde/kdeconnect) install(TARGETS kdeconnectdeclarativeplugin DESTINATION ${QML_INSTALL_DIR}/org/kde/kdeconnect)
install(FILES qmldir ${qml_SRC} DESTINATION ${QML_INSTALL_DIR}/org/kde/kdeconnect) install(FILES qmldir ${qml_SRC} DESTINATION ${QML_INSTALL_DIR}/org/kde/kdeconnect)

View file

@ -33,6 +33,9 @@
#include "interfaces/notificationsmodel.h" #include "interfaces/notificationsmodel.h"
#include <remotecommandsmodel.h> #include <remotecommandsmodel.h>
#include <remotesinksmodel.h> #include <remotesinksmodel.h>
#include <pluginmodel.h>
#include "core/kdeconnectpluginconfig.h"
#include "interfaces/commandsmodel.h"
QObject* createDeviceDbusInterface(const QVariant& deviceId) QObject* createDeviceDbusInterface(const QVariant& deviceId)
{ {
@ -109,6 +112,9 @@ void KdeConnectDeclarativePlugin::registerTypes(const char* uri)
qmlRegisterType<DBusAsyncResponse>(uri, 1, 0, "DBusAsyncResponse"); qmlRegisterType<DBusAsyncResponse>(uri, 1, 0, "DBusAsyncResponse");
qmlRegisterType<DevicesSortProxyModel>(uri, 1, 0, "DevicesSortProxyModel"); qmlRegisterType<DevicesSortProxyModel>(uri, 1, 0, "DevicesSortProxyModel");
qmlRegisterType<RemoteSinksModel>(uri, 1, 0, "RemoteSinksModel"); qmlRegisterType<RemoteSinksModel>(uri, 1, 0, "RemoteSinksModel");
qmlRegisterType<PluginModel>(uri, 1, 0, "PluginModel");
qmlRegisterType<KdeConnectPluginConfig>(uri, 1, 0, "KdeConnectPluginConfig");
qmlRegisterType<CommandsModel>(uri, 1, 0, "CommandsModel");
qmlRegisterUncreatableType<MprisDbusInterface>(uri, 1, 0, "MprisDbusInterface", QStringLiteral("You're not supposed to instantiate interfaces")); qmlRegisterUncreatableType<MprisDbusInterface>(uri, 1, 0, "MprisDbusInterface", QStringLiteral("You're not supposed to instantiate interfaces"));
qmlRegisterUncreatableType<LockDeviceDbusInterface>(uri, 1, 0, "LockDeviceDbusInterface", QStringLiteral("You're not supposed to instantiate interfaces")); qmlRegisterUncreatableType<LockDeviceDbusInterface>(uri, 1, 0, "LockDeviceDbusInterface", QStringLiteral("You're not supposed to instantiate interfaces"));
qmlRegisterUncreatableType<FindMyPhoneDeviceDbusInterface>(uri, 1, 0, "FindMyPhoneDbusInterface", QStringLiteral("You're not supposed to instantiate interfaces")); qmlRegisterUncreatableType<FindMyPhoneDeviceDbusInterface>(uri, 1, 0, "FindMyPhoneDbusInterface", QStringLiteral("You're not supposed to instantiate interfaces"));

View file

@ -20,6 +20,8 @@ set(libkdeconnect_SRC
conversationmessage.cpp conversationmessage.cpp
remotecommandsmodel.cpp remotecommandsmodel.cpp
remotesinksmodel.cpp remotesinksmodel.cpp
pluginmodel.cpp
commandsmodel.cpp
# modeltest.cpp # modeltest.cpp
) )
@ -34,6 +36,7 @@ set(libkdeconnect_HEADERS
conversationmessage.h conversationmessage.h
dbusinterfaces.h dbusinterfaces.h
remotecommandsmodel.h remotecommandsmodel.h
pluginmodel.h
${CMAKE_CURRENT_BINARY_DIR}/kdeconnectinterfaces_export.h ${CMAKE_CURRENT_BINARY_DIR}/kdeconnectinterfaces_export.h
) )
@ -67,9 +70,12 @@ generate_export_header(kdeconnectinterfaces EXPORT_FILE_NAME ${CMAKE_CURRENT_BIN
target_link_libraries(kdeconnectinterfaces target_link_libraries(kdeconnectinterfaces
LINK_PUBLIC LINK_PUBLIC
Qt5::DBus Qt5::DBus
KF5::Service
LINK_PRIVATE LINK_PRIVATE
kdeconnectcore
KF5::ConfigCore KF5::ConfigCore
KF5::I18n KF5::I18n
KF5::CoreAddons
) )
configure_file(KDEConnectConfig.cmake.in ${CMAKE_BINARY_DIR}/interfaces/KDEConnectConfig.cmake @ONLY) configure_file(KDEConnectConfig.cmake.in ${CMAKE_BINARY_DIR}/interfaces/KDEConnectConfig.cmake @ONLY)

View file

@ -0,0 +1,157 @@
/**
* Copyright 2019 Nicolas Fella <nicolas.fella@gmx.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 "commandsmodel.h"
#include <QDebug>
#include <QJsonObject>
#include <QJsonDocument>
#include <QUuid>
#include <dbushelper.h>
CommandsModel::CommandsModel(QObject* parent)
: QAbstractListModel(parent)
, m_config()
{
m_config.setPluginName("kdeconnect_runcommand");
connect(this, &QAbstractItemModel::rowsInserted,
this, &CommandsModel::rowsChanged);
connect(this, &QAbstractItemModel::rowsRemoved,
this, &CommandsModel::rowsChanged);
}
QHash<int, QByteArray> CommandsModel::roleNames() const
{
//Role names for QML
QHash<int, QByteArray> names = QAbstractItemModel::roleNames();
names.insert(KeyRole, "key");
names.insert(NameRole, "name");
names.insert(CommandRole, "command");
return names;
}
CommandsModel::~CommandsModel()
{
}
QString CommandsModel::deviceId() const
{
return m_deviceId;
}
void CommandsModel::setDeviceId(const QString& deviceId)
{
m_deviceId = deviceId;
m_config.setDeviceId(deviceId);
refreshCommandList();
Q_EMIT deviceIdChanged(deviceId);
}
void CommandsModel::refreshCommandList()
{
const auto cmds = QJsonDocument::fromJson(m_config.getByteArray("commands", QByteArray())).object();
beginResetModel();
m_commandList.clear();
for (auto it = cmds.constBegin(), itEnd = cmds.constEnd(); it!=itEnd; ++it) {
const QJsonObject cont = it->toObject();
CommandEntry command;
command.key = it.key();
command.name = cont.value(QStringLiteral("name")).toString();
command.command = cont.value(QStringLiteral("command")).toString();
m_commandList.append(command);
}
endResetModel();
}
QVariant CommandsModel::data(const QModelIndex& index, int role) const
{
if (!index.isValid()
|| index.row() < 0
|| index.row() >= m_commandList.count())
{
return QVariant();
}
CommandEntry command = m_commandList[index.row()];
switch (role) {
case KeyRole:
return command.key;
case NameRole:
return command.name;
case CommandRole:
return command.command;
default:
return QVariant();
}
}
int CommandsModel::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_commandList.count();
}
void CommandsModel::removeCommand(int index)
{
beginRemoveRows(QModelIndex(), index, index);
m_commandList.remove(index);
endRemoveRows();
saveCommands();
}
void CommandsModel::saveCommands()
{
QJsonObject jsonConfig;
for (const CommandEntry &command : m_commandList) {
QJsonObject entry;
entry[QStringLiteral("name")] = command.name;
entry[QStringLiteral("command")] = command.command;
jsonConfig[command.key] = entry;
}
QJsonDocument document;
document.setObject(jsonConfig);
m_config.set(QStringLiteral("commands"), document.toJson(QJsonDocument::Compact));
}
void CommandsModel::addCommand(const QString& name, const QString& command)
{
CommandEntry entry;
QString key = QUuid::createUuid().toString();
DbusHelper::filterNonExportableCharacters(key);
entry.key = key;
entry.name = name;
entry.command = command;
beginInsertRows(QModelIndex(), m_commandList.size(), m_commandList.size());
m_commandList.append(entry);
endInsertRows();
saveCommands();
}

View file

@ -0,0 +1,77 @@
/**
* Copyright 2019 Nicolas Fella <nicolas.fella@gmx.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 COMMANDSMODEL_H
#define COMMANDSMODEL_H
#include <QAbstractListModel>
#include "interfaces/kdeconnectinterfaces_export.h"
#include "core/kdeconnectpluginconfig.h"
struct CommandEntry {
QString key;
QString name;
QString command;
};
class KDECONNECTINTERFACES_EXPORT CommandsModel
: public QAbstractListModel
{
Q_OBJECT
Q_PROPERTY(QString deviceId READ deviceId WRITE setDeviceId NOTIFY deviceIdChanged)
public:
enum ModelRoles {
KeyRole,
NameRole,
CommandRole
};
explicit CommandsModel(QObject* parent = nullptr);
~CommandsModel() override;
QString deviceId() const;
void setDeviceId(const QString& deviceId);
QVariant data(const QModelIndex& index, int role) const override;
int rowCount(const QModelIndex& parent = QModelIndex()) const override;
QHash<int, QByteArray> roleNames() const override;
Q_SCRIPTABLE void removeCommand(int index);
Q_SCRIPTABLE void addCommand(const QString& name, const QString& command);
private Q_SLOTS:
void refreshCommandList();
Q_SIGNALS:
void deviceIdChanged(const QString& value);
void rowsChanged();
private:
void saveCommands();
QVector<CommandEntry> m_commandList;
QString m_deviceId;
KdeConnectPluginConfig m_config;
};
#endif // DEVICESMODEL_H

133
interfaces/pluginmodel.cpp Normal file
View file

@ -0,0 +1,133 @@
/**
* Copyright 2019 Nicolas Fella <nicolas.fella@gmx.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 "pluginmodel.h"
#include <QDebug>
#include <KPluginMetaData>
PluginModel::PluginModel(QObject *parent)
: QAbstractListModel(parent)
{
connect(this, &QAbstractItemModel::rowsInserted,
this, &PluginModel::rowsChanged);
connect(this, &QAbstractItemModel::rowsRemoved,
this, &PluginModel::rowsChanged);
beginResetModel();
m_plugins = KPluginInfo::fromMetaData(KPluginLoader::findPlugins(QStringLiteral("kdeconnect/")));
endResetModel();
}
PluginModel::~PluginModel()
{
}
void PluginModel::setDeviceId(const QString& deviceId)
{
if (deviceId == m_deviceId)
return;
m_deviceId = deviceId;
DeviceDbusInterface *device = new DeviceDbusInterface(m_deviceId);
m_config = KSharedConfig::openConfig(device->pluginsConfigFile());
}
QVariant PluginModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid()) {
return QVariant();
}
const KPluginInfo &pluginEntry = m_plugins[index.row()];
switch (role) {
case CheckedRole:
return m_config->group("Plugins").readEntry(QStringLiteral("%1Enabled").arg(pluginEntry.pluginName()), QStringLiteral("false")) == QStringLiteral("true");
case NameRole:
return pluginEntry.name();
case IconRole:
return pluginEntry.icon();
case IdRole:
return pluginEntry.pluginName();
case ConfigSourceRole:
{
const QString configFile = QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("kdeconnect/%1_config.qml").arg(pluginEntry.pluginName()));
if (configFile.isEmpty())
return QUrl();
return QUrl::fromLocalFile(configFile);
}
default:
return QVariant();
}
}
QHash<int, QByteArray> PluginModel::roleNames() const
{
QHash<int, QByteArray> roles = QAbstractItemModel::roleNames();
roles[NameRole] = "name";
roles[CheckedRole] = "isChecked";
roles[IconRole] = "iconName";
roles[IdRole] = "pluginId";
roles[ConfigSourceRole] = "configSource";
return roles;
}
bool PluginModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
if (!index.isValid()) {
return false;
}
bool ret = false;
if (role == CheckedRole) {
const KPluginInfo &pluginEntry = m_plugins[index.row()];
m_config->group("Plugins").writeEntry(QStringLiteral("%1Enabled").arg(pluginEntry.pluginName()), value);
ret = true;
}
m_config->sync();
if (ret) {
Q_EMIT dataChanged(index, index);
}
return ret;
}
int PluginModel::rowCount(const QModelIndex &parent) const
{
if (parent.isValid()) {
return 0;
}
return m_plugins.size();
}
QString PluginModel::deviceId()
{
return m_deviceId;
}

74
interfaces/pluginmodel.h Normal file
View file

@ -0,0 +1,74 @@
/**
* Copyright 2019 Nicolas Fella <nicolas.fella@gmx.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 PLUGINMODEL
#define PLUGINMODEL
#include <QAbstractListModel>
#include <KPluginInfo>
#include "interfaces/dbusinterfaces.h"
#include <KSharedConfig>
class KDECONNECTINTERFACES_EXPORT PluginModel
: public QAbstractListModel
{
Q_OBJECT
Q_PROPERTY(QString deviceId READ deviceId WRITE setDeviceId CONSTANT)
public:
enum ExtraRoles {
NameRole,
CheckedRole,
IconRole,
IdRole,
ConfigSourceRole
};
Q_ENUM(ExtraRoles)
explicit PluginModel(QObject *parent = nullptr);
~PluginModel() override;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override;
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
QHash<int, QByteArray> roleNames() const override;
void setDeviceId(const QString& deviceId);
QString deviceId();
Q_SIGNALS:
void deviceIdChanged(const QString& value);
void rowsChanged();
private:
QList<KPluginInfo> m_plugins;
QString m_deviceId;
KSharedConfigPtr m_config;
};
#endif // KPLUGINSELECTOR_P_H

View file

@ -72,7 +72,7 @@ void FindThisDeviceConfig::load()
{ {
KCModule::load(); KCModule::load();
const QString ringTone = config()->get<QString>(QStringLiteral("ringtone"), defaultSound()); const QString ringTone = config()->getString(QStringLiteral("ringtone"), defaultSound());
m_ui->soundFileRequester->setText(ringTone); m_ui->soundFileRequester->setText(ringTone);
Q_EMIT changed(false); Q_EMIT changed(false);

View file

@ -51,7 +51,7 @@ bool FindThisDevicePlugin::receivePacket(const NetworkPacket& np)
{ {
Q_UNUSED(np); Q_UNUSED(np);
const QString soundFilename = config()->get<QString>(QStringLiteral("ringtone"), defaultSound()); const QString soundFilename = config()->getString(QStringLiteral("ringtone"), defaultSound());
QUrl soundURL; QUrl soundURL;
#ifdef Q_OS_WIN #ifdef Q_OS_WIN

View file

@ -0,0 +1,52 @@
import QtQuick 2.2
import QtQuick.Controls 2.2
import QtQuick.Layouts 1.1
import org.kde.kirigami 2.5 as Kirigami
import Qt.labs.platform 1.1
import org.kde.kdeconnect 1.0
Kirigami.FormLayout {
property string device
property var action: Kirigami.Action {
icon.name: "dialog-ok"
text: i18n("Apply")
onTriggered: config.set("ringtone", path.text)
}
FileDialog {
id: fileDialog
currentFile: path.text
onAccepted: {
path.text = currentFile.toString().replace("file://", "")
}
}
KdeConnectPluginConfig {
id: config
deviceId: device
pluginName: "kdeconnect_findthisdevice"
onConfigChanged: {
path.text = get("ringtone", StandardPaths.writableLocation(StandardPaths.DownloadsLocation).toString().replace("file://", ""))
}
}
RowLayout {
Kirigami.FormData.label: i18n("Sound to play:")
TextField {
id: path
}
Button {
icon.name: "document-open"
onClicked: {
fileDialog.open()
}
}
}
}

View file

@ -0,0 +1,46 @@
import QtQuick 2.2
import QtQuick.Controls 2.2
import QtQuick.Layouts 1.1
import org.kde.kirigami 2.5 as Kirigami
import org.kde.kdeconnect 1.0
Kirigami.FormLayout {
property string device
KdeConnectPluginConfig {
id: config
deviceId: device
pluginName: "kdeconnect_pausemusic"
}
Component.onCompleted: {
talking.checked = config.get("conditionTalking", false)
mute.checked = config.get("actionMute", false)
pause.checked = config.get("actionPause", true)
}
RadioButton {
text: i18n("Pause as soon as phone rings")
checked: !talking.checked
}
RadioButton {
id: talking
onCheckedChanged: config.set("conditionTalking", checked)
text: i18n("Pause only while talking")
}
CheckBox {
id: pause
text: i18n("Pause media players")
onClicked: config.set("actionPause", checked)
}
CheckBox {
id: mute
text: i18n("Mute system sound")
onClicked: config.set("actionMute", checked)
}
}

View file

@ -55,12 +55,12 @@ void PauseMusicConfig::defaults()
void PauseMusicConfig::load() void PauseMusicConfig::load()
{ {
KCModule::load(); KCModule::load();
bool talking = config()->get(QStringLiteral("conditionTalking"), false); bool talking = config()->getBool(QStringLiteral("conditionTalking"), false);
m_ui->rad_talking->setChecked(talking); m_ui->rad_talking->setChecked(talking);
m_ui->rad_ringing->setChecked(!talking); m_ui->rad_ringing->setChecked(!talking);
bool pause = config()->get(QStringLiteral("actionPause"), true); bool pause = config()->getBool(QStringLiteral("actionPause"), true);
bool mute = config()->get(QStringLiteral("actionMute"), false); bool mute = config()->getBool(QStringLiteral("actionMute"), false);
m_ui->check_pause->setChecked(pause); m_ui->check_pause->setChecked(pause);
m_ui->check_mute->setChecked(mute); m_ui->check_mute->setChecked(mute);

View file

@ -44,7 +44,7 @@ PauseMusicPlugin::PauseMusicPlugin(QObject* parent, const QVariantList& args)
bool PauseMusicPlugin::receivePacket(const NetworkPacket& np) bool PauseMusicPlugin::receivePacket(const NetworkPacket& np)
{ {
bool pauseOnlyWhenTalking = config()->get(QStringLiteral("conditionTalking"), false); bool pauseOnlyWhenTalking = config()->getBool(QStringLiteral("conditionTalking"), false);
if (pauseOnlyWhenTalking) { if (pauseOnlyWhenTalking) {
if (np.get<QString>(QStringLiteral("event")) != QLatin1String("talking")) { if (np.get<QString>(QStringLiteral("event")) != QLatin1String("talking")) {
@ -58,8 +58,8 @@ bool PauseMusicPlugin::receivePacket(const NetworkPacket& np)
bool pauseConditionFulfilled = !np.get<bool>(QStringLiteral("isCancel")); bool pauseConditionFulfilled = !np.get<bool>(QStringLiteral("isCancel"));
bool pause = config()->get(QStringLiteral("actionPause"), true); bool pause = config()->getBool(QStringLiteral("actionPause"), true);
bool mute = config()->get(QStringLiteral("actionMute"), false); bool mute = config()->getBool(QStringLiteral("actionMute"), false);
if (pauseConditionFulfilled) { if (pauseConditionFulfilled) {

View file

@ -0,0 +1,105 @@
import QtQuick 2.2
import QtQuick.Controls 2.5
import org.kde.kirigami 2.4 as Kirigami
import org.kde.kdeconnect 1.0
ListView {
Component.onCompleted: {
root.leftPadding = 0
root.rightPadding = 0
root.topPadding = 0
root.bottomPadding = 0
}
property string device
property var action: Kirigami.Action {
icon.name: "list-add"
text: i18n("Add command")
onTriggered: addDialog.open()
}
model: CommandsModel {
id: commandModel
deviceId: device
}
delegate: Kirigami.SwipeListItem {
width: parent.width
enabled: true
Label {
text: i18n("%1 <br> <i>%2</i>", name, command)
}
actions: [
Kirigami.Action {
icon.name: "delete"
onTriggered: commandModel.removeCommand(index)
}
]
}
Dialog {
id: addDialog
title: "Add command"
standardButtons: Dialog.Save | Dialog.Cancel
Kirigami.FormLayout {
TextField {
id: nameField
Kirigami.FormData.label: i18n("Name:")
}
TextField {
id: commandField
Kirigami.FormData.label: i18n("Command:")
}
ComboBox {
Kirigami.FormData.label: i18n("Sample commands:")
textRole: "name"
model: ListModel {
id: sampleCommands
ListElement {
name: "Sample command"
command: ""
}
ListElement {
name: "Suspend"
command: "systemctl suspend"
}
ListElement {
name: "Maximum Brightness"
command: "qdbus org.kde.Solid.PowerManagement /org/kde/Solid/PowerManagement/Actions/BrightnessControl org.kde.Solid.PowerManagement.Actions.BrightnessControl.setBrightness `qdbus org.kde.Solid.PowerManagement /org/kde/Solid/PowerManagement/Actions/BrightnessControl org.kde.Solid.PowerManagement.Actions.BrightnessControl.brightnessMax`"
}
ListElement {
name: "Lock Screen"
command: "loginctl lock-session"
}
ListElement {
name: "Unlock Screen"
command: "loginctl unlock-session"
}
ListElement {
name: "Close All Vaults"
command: "qdbus org.kde.kded5 /modules/plasmavault closeAllVaults"
}
ListElement {
name: "Forcefully Close All Vaults"
command: "qdbus org.kde.kded5 /modules/plasmavault forceCloseAllVaults"
}
}
onActivated: {
if (index > 0) {
nameField.text = sampleCommands.get(index).name
commandField.text = sampleCommands.get(index).command
}
}
}
}
onAccepted: commandModel.addCommand(nameField.text, commandField.text)
}
}

View file

@ -93,7 +93,7 @@ void RunCommandConfig::load()
{ {
KCModule::load(); KCModule::load();
QJsonDocument jsonDocument = QJsonDocument::fromJson(config()->get<QByteArray>(QStringLiteral("commands"), "{}")); QJsonDocument jsonDocument = QJsonDocument::fromJson(config()->getByteArray(QStringLiteral("commands"), "{}"));
QJsonObject jsonConfig = jsonDocument.object(); QJsonObject jsonConfig = jsonDocument.object();
const QStringList keys = jsonConfig.keys(); const QStringList keys = jsonConfig.keys();
for (const QString& key : keys) { for (const QString& key : keys) {

View file

@ -68,7 +68,7 @@ bool RunCommandPlugin::receivePacket(const NetworkPacket& np)
} }
if (np.has(QStringLiteral("key"))) { if (np.has(QStringLiteral("key"))) {
QJsonDocument commandsDocument = QJsonDocument::fromJson(config()->get<QByteArray>(QStringLiteral("commands"), "{}")); QJsonDocument commandsDocument = QJsonDocument::fromJson(config()->getByteArray(QStringLiteral("commands"), "{}"));
QJsonObject commands = commandsDocument.object(); QJsonObject commands = commandsDocument.object();
QString key = np.get<QString>(QStringLiteral("key")); QString key = np.get<QString>(QStringLiteral("key"));
QJsonValue value = commands[key]; QJsonValue value = commands[key];
@ -94,7 +94,7 @@ void RunCommandPlugin::connected()
void RunCommandPlugin::sendConfig() void RunCommandPlugin::sendConfig()
{ {
QString commands = config()->get<QString>(QStringLiteral("commands"),QStringLiteral("{}")); QString commands = config()->getString(QStringLiteral("commands"),QStringLiteral("{}"));
NetworkPacket np(PACKET_TYPE_RUNCOMMAND, {{"commandList", commands}}); NetworkPacket np(PACKET_TYPE_RUNCOMMAND, {{"commandList", commands}});
#if KCMUTILS_VERSION >= QT_VERSION_CHECK(5, 45, 0) #if KCMUTILS_VERSION >= QT_VERSION_CHECK(5, 45, 0)

View file

@ -0,0 +1,50 @@
import QtQuick 2.2
import QtQuick.Controls 2.2
import QtQuick.Layouts 1.1
import org.kde.kirigami 2.5 as Kirigami
import org.kde.kdeconnect 1.0
Kirigami.FormLayout {
property string device
KdeConnectPluginConfig {
id: config
deviceId: device
pluginName: "kdeconnect_sendnotifications"
}
Component.onCompleted: {
persistent.checked = config.getBool("generalPersistent", false)
includeBody.checked = config.getBool("generalIncludeBody", true)
includeIcon.checked = config.getBool("generalSynchronizeIcons", true)
urgency.value = config.getInt("generalUrgency", 0)
}
CheckBox {
id: persistent
text: i18n("Persistent notifications only")
onClicked: config.set("generalPersistent", checked)
}
CheckBox {
id: includeBody
text: i18n("Include body")
onClicked: config.set("generalIncludeBody", checked)
}
CheckBox {
id: includeIcon
text: i18n("Include icon")
onClicked: config.set("generalSynchronizeIcons", checked)
}
SpinBox {
id: urgency
Kirigami.FormData.label: i18n("Minimum urgency level:")
from: 0
to: 2
onValueModified: config.set("generalUrgency", value)
}
}

View file

@ -217,7 +217,7 @@ uint NotificationsListener::Notify(const QString& appName, uint replacesId,
if (!app.active) if (!app.active)
return 0; return 0;
if (timeout > 0 && m_plugin->config()->get(QStringLiteral("generalPersistent"), false)) if (timeout > 0 && m_plugin->config()->getBool(QStringLiteral("generalPersistent"), false))
return 0; return 0;
int urgency = -1; int urgency = -1;
@ -227,11 +227,11 @@ uint NotificationsListener::Notify(const QString& appName, uint replacesId,
if (!ok) if (!ok)
urgency = -1; urgency = -1;
} }
if (urgency > -1 && urgency < m_plugin->config()->get<int>(QStringLiteral("generalUrgency"), 0)) if (urgency > -1 && urgency < m_plugin->config()->getInt(QStringLiteral("generalUrgency"), 0))
return 0; return 0;
QString ticker = summary; QString ticker = summary;
if (!body.isEmpty() && m_plugin->config()->get(QStringLiteral("generalIncludeBody"), true)) if (!body.isEmpty() && m_plugin->config()->getBool(QStringLiteral("generalIncludeBody"), true))
ticker += QStringLiteral(": ") + body; ticker += QStringLiteral(": ") + body;
if (app.blacklistExpression.isValid() && if (app.blacklistExpression.isValid() &&
@ -250,7 +250,7 @@ uint NotificationsListener::Notify(const QString& appName, uint replacesId,
// clearability is pointless // clearability is pointless
// sync any icon data? // sync any icon data?
if (m_plugin->config()->get(QStringLiteral("generalSynchronizeIcons"), true)) { if (m_plugin->config()->getBool(QStringLiteral("generalSynchronizeIcons"), true)) {
QSharedPointer<QIODevice> iconSource; QSharedPointer<QIODevice> iconSource;
// try different image sources according to priorities in notifications- // try different image sources according to priorities in notifications-
// spec version 1.2: // spec version 1.2:

View file

@ -88,13 +88,13 @@ void SendNotificationsConfig::loadApplications()
void SendNotificationsConfig::load() void SendNotificationsConfig::load()
{ {
KCModule::load(); KCModule::load();
bool persistent = config()->get(QStringLiteral("generalPersistent"), false); bool persistent = config()->getBool(QStringLiteral("generalPersistent"), false);
m_ui->check_persistent->setChecked(persistent); m_ui->check_persistent->setChecked(persistent);
bool body = config()->get(QStringLiteral("generalIncludeBody"), true); bool body = config()->getBool(QStringLiteral("generalIncludeBody"), true);
m_ui->check_body->setChecked(body); m_ui->check_body->setChecked(body);
bool icons = config()->get(QStringLiteral("generalSynchronizeIcons"), true); bool icons = config()->getBool(QStringLiteral("generalSynchronizeIcons"), true);
m_ui->check_icons->setChecked(icons); m_ui->check_icons->setChecked(icons);
int urgency = config()->get(QStringLiteral("generalUrgency"), 0); int urgency = config()->getInt(QStringLiteral("generalUrgency"), 0);
m_ui->spin_urgency->setValue(urgency); m_ui->spin_urgency->setValue(urgency);
loadApplications(); loadApplications();

View file

@ -0,0 +1,53 @@
import QtQuick 2.2
import QtQuick.Controls 2.2
import QtQuick.Layouts 1.1
import org.kde.kirigami 2.5 as Kirigami
import Qt.labs.platform 1.1
import org.kde.kdeconnect 1.0
Kirigami.FormLayout {
property string device
property var action: Kirigami.Action {
icon.name: "dialog-ok"
text: i18n("Apply")
onTriggered: config.set("incoming_path", path.text)
}
FolderDialog {
id: folderDialog
currentFolder: path.text
onAccepted: {
path.text = currentFolder.toString().replace("file://", "")
}
}
KdeConnectPluginConfig {
id: config
deviceId: device
pluginName: "kdeconnect_share"
onConfigChanged: {
path.text = get("incoming_path", StandardPaths.writableLocation(StandardPaths.DownloadsLocation).toString().replace("file://", ""))
}
}
RowLayout {
Kirigami.FormData.label: i18n("Save files in:")
TextField {
id: path
}
Button {
icon.name: "document-open"
onClicked: folderDialog.open()
}
}
Label {
text: "%1 in the path will be replaced with the specific device name"
}
}

View file

@ -59,7 +59,7 @@ void ShareConfig::load()
KCModule::load(); KCModule::load();
const auto standardPath = QStandardPaths::writableLocation(QStandardPaths::DownloadLocation); const auto standardPath = QStandardPaths::writableLocation(QStandardPaths::DownloadLocation);
m_ui->kurlrequester->setText(config()->get<QString>(QStringLiteral("incoming_path"), standardPath)); m_ui->kurlrequester->setText(config()->getString(QStringLiteral("incoming_path"), standardPath));
Q_EMIT changed(false); Q_EMIT changed(false);
} }

View file

@ -49,7 +49,7 @@ SharePlugin::SharePlugin(QObject* parent, const QVariantList& args)
QUrl SharePlugin::destinationDir() const QUrl SharePlugin::destinationDir() const
{ {
const QString defaultDownloadPath = QStandardPaths::writableLocation(QStandardPaths::DownloadLocation); const QString defaultDownloadPath = QStandardPaths::writableLocation(QStandardPaths::DownloadLocation);
QUrl dir = QUrl::fromLocalFile(config()->get<QString>(QStringLiteral("incoming_path"), defaultDownloadPath)); QUrl dir = QUrl::fromLocalFile(config()->getString(QStringLiteral("incoming_path"), defaultDownloadPath));
if (dir.path().contains(QLatin1String("%1"))) { if (dir.path().contains(QLatin1String("%1"))) {
dir.setPath(dir.path().arg(device()->name())); dir.setPath(dir.path().arg(device()->name()));

View file

@ -204,9 +204,9 @@ void TestNotificationListener::testNotify()
plugin->config()->set(QStringLiteral("generalPersistent"), false); plugin->config()->set(QStringLiteral("generalPersistent"), false);
plugin->config()->set(QStringLiteral("generalIncludeBody"), true); plugin->config()->set(QStringLiteral("generalIncludeBody"), true);
plugin->config()->set(QStringLiteral("generalUrgency"), 0); plugin->config()->set(QStringLiteral("generalUrgency"), 0);
QCOMPARE(plugin->config()->get<bool>("generalPersistent"), false); QCOMPARE(plugin->config()->getBool("generalPersistent", false), false);
QCOMPARE(plugin->config()->get<bool>("generalIncludeBody"), true); QCOMPARE(plugin->config()->getBool("generalIncludeBody", true), true);
QCOMPARE(plugin->config()->get<bool>("generalUrgency"), false); QCOMPARE(plugin->config()->getBool("generalUrgency", false), false);
// applications are modified directly: // applications are modified directly:
listener->getApplications().clear(); listener->getApplications().clear();