[WIP] Add plugin setting to app and start port plugin KCMs to QML

This commit is contained in:
Nicolas Fella 2019-02-06 17:27:17 +01:00
parent 3dd4701378
commit e827ec4d6d
16 changed files with 542 additions and 8 deletions

View file

@ -13,7 +13,7 @@ find_package(PkgConfig)
if (SAILFISHOS)
set(KF5_MIN_VERSION "5.31.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(QCA_MIN_VERSION 2.0.0)
pkg_search_module(SFOS REQUIRED sailfishapp)

View file

@ -5,4 +5,7 @@
# Thoroughly inspired in kdevplatform_add_plugin
function(kdeconnect_add_plugin)
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()

View file

@ -72,6 +72,15 @@ Kirigami.Page
onTriggered: {
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 {
id: fileDialog
title: i18n("Please choose a file")

View file

@ -0,0 +1,49 @@
/*
* 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.ScrollablePage
{
id: root
property string configFile
property string device
Loader {
id: loader
Component.onCompleted: {
setSource(configFile, {
device: root.device
})
}
}
actions.main: Kirigami.Action {
icon.name: "dialog-ok"
text: i18n("Apply")
onTriggered: loader.item.apply()
}
}

View file

@ -0,0 +1,79 @@
/*
* 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
spacing: Kirigami.Units.smallSpacing
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/runcommand.qml</file>
<file>qml/volume.qml</file>
<file>qml/PluginSettings.qml</file>
<file>qml/PluginInfoPage.qml</file>
</qresource>
</RCC>

View file

@ -71,9 +71,9 @@ Daemon::Daemon(QObject* parent, bool testMode)
qCDebug(KDECONNECT_CORE) << "KdeConnect daemon starting";
//Load backends
if (testMode)
d->m_linkProviders.insert(new LoopbackLinkProvider());
if (testMode){}
else {
d->m_linkProviders.insert(new LoopbackLinkProvider());
d->m_linkProviders.insert(new LanLinkProvider());
#ifdef KDECONNECT_BLUETOOTH
d->m_linkProviders.insert(new BluetoothLinkProvider());

View file

@ -24,6 +24,7 @@
#include <QSettings>
#include <QDBusMessage>
#include <QDBusConnection>
#include <QDebug>
#include "kdeconnectconfig.h"
@ -34,6 +35,12 @@ struct KdeConnectPluginConfigPrivate
QDBusMessage m_signal;
};
KdeConnectPluginConfig::KdeConnectPluginConfig()
: d(new KdeConnectPluginConfigPrivate())
{
}
KdeConnectPluginConfig::KdeConnectPluginConfig(const QString& deviceId, const QString& pluginName)
: 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_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()));
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()));
}
KdeConnectPluginConfig::~KdeConnectPluginConfig()
@ -53,6 +61,10 @@ KdeConnectPluginConfig::~KdeConnectPluginConfig()
QVariant KdeConnectPluginConfig::get(const QString& key, const QVariant& defaultValue)
{
if (!d->m_config) {
loadConfig();
}
d->m_config->sync();
return d->m_config->value(key, defaultValue);
}
@ -98,3 +110,47 @@ void KdeConnectPluginConfig::slotConfigChanged()
{
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 <QVariant>
#include "kdeconnectcore_export.h"
#include "core/kdeconnectcore_export.h"
struct KdeConnectPluginConfigPrivate;
@ -35,14 +35,18 @@ class KDECONNECTCORE_EXPORT KdeConnectPluginConfig : public QObject
{
Q_OBJECT
Q_PROPERTY(QString deviceId READ deviceId WRITE setDeviceId NOTIFY configChanged)
Q_PROPERTY(QString pluginName READ pluginName WRITE setPluginName NOTIFY configChanged)
public:
KdeConnectPluginConfig();
KdeConnectPluginConfig(const QString& deviceId, const QString& pluginName);
~KdeConnectPluginConfig() override;
/**
* 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
@ -53,7 +57,7 @@ public:
/**
* Read a key-value pair from this config object
*/
QVariant get(const QString& key, const QVariant& defaultValue);
Q_SCRIPTABLE QVariant get(const QString& key, const QVariant& defaultValue);
/**
* Convenience method that will convert the QVariant to whatever type for you
@ -64,6 +68,12 @@ public:
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:
void slotConfigChanged();
@ -71,7 +81,11 @@ Q_SIGNALS:
void configChanged();
private:
void loadConfig();
QScopedPointer<KdeConnectPluginConfigPrivate> d;
QString m_deviceId;
QString m_pluginName;
};
#endif

View file

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

View file

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

View file

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

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

@ -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 Qt.labs.platform 1.1
import org.kde.kdeconnect 1.0
Kirigami.FormLayout {
property string device
function apply() {
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,51 @@
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
function apply() {
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"
}
}