diff --git a/app/qml/DevicePage.qml b/app/qml/DevicePage.qml --- a/app/qml/DevicePage.qml +++ b/app/qml/DevicePage.qml @@ -65,7 +65,13 @@ Layout.fillWidth: true PluginItem { - label: i18n("Multimedia control") + label: i18n("Run command") + interfaceFactory: DeviceRemotecommandsDbusInterfaceFactory + component: "qrc:/qml/runcommand.qml" + pluginName: "remotecommands" + } + PluginItem { + label: i18n("Multimedia") interfaceFactory: MprisDbusInterfaceFactory component: "qrc:/qml/mpris.qml" pluginName: "mprisremote" diff --git a/app/qml/runcommand.qml b/app/qml/runcommand.qml new file mode 100644 --- /dev/null +++ b/app/qml/runcommand.qml @@ -0,0 +1,52 @@ +/* + * Copyright 2018 Nicolas Fella + * + * 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 . + */ + +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.Page +{ + id: root + title: i18n("Run command") + property QtObject pluginInterface + + actions.main: Kirigami.Action { + icon.name: "document-edit" + text: i18n("Edit commands") + onTriggered: pluginInterface.editCommands() + } + + ListView { + anchors.fill: parent + model: RemotecommandsModel { + deviceId: pluginInterface.deviceId + } + delegate: Kirigami.BasicListItem { + width: ListView.view.width + label: name + "\n" + command + onClicked: pluginInterface.triggerCommand(id) + } + } + +} + diff --git a/app/resources.qrc b/app/resources.qrc --- a/app/resources.qrc +++ b/app/resources.qrc @@ -4,6 +4,7 @@ qml/mpris.qml qml/mousepad.qml qml/presentationRemote.qml + qml/runcommand.qml qml/PluginItem.qml qml/DevicePage.qml diff --git a/cli/kdeconnect-cli.cpp b/cli/kdeconnect-cli.cpp --- a/cli/kdeconnect-cli.cpp +++ b/cli/kdeconnect-cli.cpp @@ -41,7 +41,7 @@ QStringLiteral("kdeconnect-cli"), QStringLiteral(KDECONNECT_VERSION_STRING), i18n("KDE Connect CLI tool"), - KAboutLicense::GPL, + KAboutLicense::GPL, i18n("(C) 2015 Aleix Pol Gonzalez")); KAboutData::setApplicationData(about); @@ -251,14 +251,15 @@ << ": " << idx.data(NotificationsModel::NameModelRole).toString() << endl; } } else if(parser.isSet(QStringLiteral("list-commands"))) { - RemoteCommandsDbusInterface iface(device); - const auto cmds = QJsonDocument::fromJson(iface.commands()).object(); - for (auto it = cmds.constBegin(), itEnd = cmds.constEnd(); it!=itEnd; ++it) { - const QJsonObject cont = it->toObject(); - QTextStream(stdout) << it.key() << ": " << cont.value(QStringLiteral("name")).toString() << ": " << cont.value(QStringLiteral("command")).toString() << endl; + DeviceRemotecommandsDbusInterface iface(device); + QStringList commandIds = iface.commands(); + + for (const QString& id : commandIds) { + RemotecommandDbusInterface command(device, id, &app); + QTextStream(stdout) << id << ": " << command.name() << ": " << command.command() << endl; } } else if(parser.isSet(QStringLiteral("execute-command"))) { - RemoteCommandsDbusInterface iface(device); + DeviceRemotecommandsDbusInterface iface(device); blockOnReply(iface.triggerCommand(parser.value(QStringLiteral("execute-command")))); } else if(parser.isSet(QStringLiteral("encryption-info"))) { DeviceDbusInterface dev(device); diff --git a/interfaces/CMakeLists.txt b/interfaces/CMakeLists.txt --- a/interfaces/CMakeLists.txt +++ b/interfaces/CMakeLists.txt @@ -18,6 +18,7 @@ notificationsmodel.cpp devicessortproxymodel.cpp conversationmessage.cpp + remotecommandsmodel.cpp # modeltest.cpp ) @@ -31,6 +32,7 @@ notificationsmodel.h conversationmessage.h dbusinterfaces.h + remotecommandsmodel.h ${CMAKE_CURRENT_BINARY_DIR}/kdeconnectinterfaces_export.h ) @@ -44,7 +46,8 @@ geninterface(${CMAKE_SOURCE_DIR}/plugins/mprisremote/mprisremoteplugin.h mprisremoteinterface) geninterface(${CMAKE_SOURCE_DIR}/plugins/remotecontrol/remotecontrolplugin.h remotecontrolinterface) geninterface(${CMAKE_SOURCE_DIR}/plugins/lockdevice/lockdeviceplugin.h lockdeviceinterface) -geninterface(${CMAKE_SOURCE_DIR}/plugins/remotecommands/remotecommandsplugin.h remotecommandsinterface) +geninterface(${CMAKE_SOURCE_DIR}/plugins/remotecommands/remotecommand.h remotecommandinterface) +geninterface(${CMAKE_SOURCE_DIR}/plugins/remotecommands/remotecommandsdbusinterface.h deviceremotecommandsinterface) geninterface(${CMAKE_SOURCE_DIR}/plugins/remotekeyboard/remotekeyboardplugin.h remotekeyboardinterface) geninterface(${CMAKE_SOURCE_DIR}/plugins/telephony/telephonyplugin.h telephonyinterface) geninterface(${CMAKE_SOURCE_DIR}/plugins/telephony/conversationsdbusinterface.h conversationsinterface) diff --git a/interfaces/dbusinterfaces.h b/interfaces/dbusinterfaces.h --- a/interfaces/dbusinterfaces.h +++ b/interfaces/dbusinterfaces.h @@ -33,7 +33,8 @@ #include "interfaces/mprisremoteinterface.h" #include "interfaces/remotecontrolinterface.h" #include "interfaces/lockdeviceinterface.h" -#include "interfaces/remotecommandsinterface.h" +#include "interfaces/remotecommandinterface.h" +#include "interfaces/deviceremotecommandsinterface.h" #include "interfaces/remotekeyboardinterface.h" #include "interfaces/telephonyinterface.h" #include "interfaces/conversationsinterface.h" @@ -189,13 +190,26 @@ ~FindMyPhoneDeviceDbusInterface() override; }; -class KDECONNECTINTERFACES_EXPORT RemoteCommandsDbusInterface +class KDECONNECTINTERFACES_EXPORT DeviceRemotecommandsDbusInterface : public OrgKdeKdeconnectDeviceRemotecommandsInterface { Q_OBJECT public: - explicit RemoteCommandsDbusInterface(const QString& deviceId, QObject* parent = nullptr); - ~RemoteCommandsDbusInterface() override; + explicit DeviceRemotecommandsDbusInterface(const QString& deviceId, QObject* parent = nullptr); + ~DeviceRemotecommandsDbusInterface() override; +}; + +class KDECONNECTINTERFACES_EXPORT RemotecommandDbusInterface + : public OrgKdeKdeconnectDeviceRemotecommandsRemotecommandInterface +{ + Q_OBJECT +public: + RemotecommandDbusInterface(const QString& deviceId, const QString& commandId, QObject* parent = nullptr); + ~RemotecommandDbusInterface() override; + + QString commandId() { return id; } +private: + const QString id; }; class KDECONNECTINTERFACES_EXPORT RemoteKeyboardDbusInterface diff --git a/interfaces/dbusinterfaces.cpp b/interfaces/dbusinterfaces.cpp --- a/interfaces/dbusinterfaces.cpp +++ b/interfaces/dbusinterfaces.cpp @@ -161,12 +161,23 @@ { } -RemoteCommandsDbusInterface::RemoteCommandsDbusInterface(const QString& deviceId, QObject* parent): - OrgKdeKdeconnectDeviceRemotecommandsInterface(DaemonDbusInterface::activatedService(), "/modules/kdeconnect/devices/" + deviceId + "/remotecommands", QDBusConnection::sessionBus(), parent) +DeviceRemotecommandsDbusInterface::DeviceRemotecommandsDbusInterface(const QString& id, QObject* parent) + : OrgKdeKdeconnectDeviceRemotecommandsInterface(DaemonDbusInterface::activatedService(), "/modules/kdeconnect/devices/"+id, QDBusConnection::sessionBus(), parent) { + } -RemoteCommandsDbusInterface::~RemoteCommandsDbusInterface() = default; +DeviceRemotecommandsDbusInterface::~DeviceRemotecommandsDbusInterface() = default; + +RemotecommandDbusInterface::RemotecommandDbusInterface(const QString& deviceId, const QString& commandId, QObject* parent) + : OrgKdeKdeconnectDeviceRemotecommandsRemotecommandInterface(DaemonDbusInterface::activatedService(), "/modules/kdeconnect/devices/"+deviceId+"/remotecommands/"+commandId, QDBusConnection::sessionBus(), parent) + , id(commandId) +{ + +} + +RemotecommandDbusInterface::~RemotecommandDbusInterface() = default; + RemoteKeyboardDbusInterface::RemoteKeyboardDbusInterface(const QString& deviceId, QObject* parent): OrgKdeKdeconnectDeviceRemotekeyboardInterface(DaemonDbusInterface::activatedService(), "/modules/kdeconnect/devices/" + deviceId + "/remotekeyboard", QDBusConnection::sessionBus(), parent) diff --git a/interfaces/remotecommandsmodel.h b/interfaces/remotecommandsmodel.h new file mode 100644 --- /dev/null +++ b/interfaces/remotecommandsmodel.h @@ -0,0 +1,75 @@ +/** + * Copyright 2018 Nicolas Fella + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef REMOTECOMMANDSMODEL_H +#define REMOTECOMMANDSMODEL_H + +#include +#include +#include +#include + +#include "interfaces/dbusinterfaces.h" + +class KDECONNECTINTERFACES_EXPORT RemotecommandsModel + : public QAbstractListModel +{ + Q_OBJECT + Q_PROPERTY(QString deviceId READ deviceId WRITE setDeviceId NOTIFY deviceIdChanged) + Q_PROPERTY(int count READ rowCount NOTIFY rowsChanged) + +public: + enum ModelRoles { + IdRole, + KeyRole, + NameRole, + CommandRole + }; + + explicit RemotecommandsModel(QObject* parent = nullptr); + ~RemotecommandsModel() 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; + + RemotecommandDbusInterface* getCommand(const QModelIndex& index) const; + + QHash roleNames() const override; + +private Q_SLOTS: + void refreshCommandList(); + void receivedCommands(QDBusPendingCallWatcher* watcher); + void clearCommands(); + +Q_SIGNALS: + void deviceIdChanged(const QString& value); + void rowsChanged(); + +private: + DeviceRemotecommandsDbusInterface* m_dbusInterface; + QList m_commandList; + QString m_deviceId; +}; + +#endif // DEVICESMODEL_H + diff --git a/interfaces/remotecommandsmodel.cpp b/interfaces/remotecommandsmodel.cpp new file mode 100644 --- /dev/null +++ b/interfaces/remotecommandsmodel.cpp @@ -0,0 +1,193 @@ +/** + * Copyright 2013 Albert Vaca + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "remotecommandsmodel.h" +#include "interfaces_debug.h" + +#include +#include + +#include +#include + +//#include "modeltest.h" + +RemotecommandsModel::RemotecommandsModel(QObject* parent) + : QAbstractListModel(parent) + , m_dbusInterface(nullptr) +{ + + connect(this, &QAbstractItemModel::rowsInserted, + this, &RemotecommandsModel::rowsChanged); + connect(this, &QAbstractItemModel::rowsRemoved, + this, &RemotecommandsModel::rowsChanged); + + QDBusServiceWatcher* watcher = new QDBusServiceWatcher(DaemonDbusInterface::activatedService(), + QDBusConnection::sessionBus(), QDBusServiceWatcher::WatchForOwnerChange, this); + connect(watcher, &QDBusServiceWatcher::serviceRegistered, this, &RemotecommandsModel::refreshCommandList); + connect(watcher, &QDBusServiceWatcher::serviceUnregistered, this, &RemotecommandsModel::clearCommands); +} + +QHash RemotecommandsModel::roleNames() const +{ + //Role names for QML + QHash names = QAbstractItemModel::roleNames(); + names.insert(IdRole, "id"); + names.insert(KeyRole, "key"); + names.insert(NameRole, "name"); + names.insert(CommandRole, "command"); + return names; +} + +RemotecommandsModel::~RemotecommandsModel() +{ +} + +QString RemotecommandsModel::deviceId() const +{ + return m_deviceId; +} + +void RemotecommandsModel::setDeviceId(const QString& deviceId) +{ + m_deviceId = deviceId; + + if (m_dbusInterface) { + delete m_dbusInterface; + } + + m_dbusInterface = new DeviceRemotecommandsDbusInterface(deviceId, this); + + connect(m_dbusInterface, &OrgKdeKdeconnectDeviceRemotecommandsInterface::commandsChanged, + this, &RemotecommandsModel::refreshCommandList); + + refreshCommandList(); + + Q_EMIT deviceIdChanged(deviceId); +} + +void RemotecommandsModel::refreshCommandList() +{ + if (!m_dbusInterface) { + return; + } + + clearCommands(); + + if (!m_dbusInterface->isValid()) { + qCWarning(KDECONNECT_INTERFACES) << "dbus interface not valid"; + return; + } + + QDBusPendingReply pendingNotificationIds = m_dbusInterface->commands(); + QDBusPendingCallWatcher* watcher = new QDBusPendingCallWatcher(pendingNotificationIds, this); + + QObject::connect(watcher, &QDBusPendingCallWatcher::finished, + this, &RemotecommandsModel::receivedCommands); +} + +void RemotecommandsModel::receivedCommands(QDBusPendingCallWatcher* watcher) +{ + watcher->deleteLater(); + clearCommands(); + QDBusPendingReply pendingNotificationIds = *watcher; + + if (pendingNotificationIds.isError()) { + qCWarning(KDECONNECT_INTERFACES) << pendingNotificationIds.error(); + return; + } + + const QStringList commandIds = pendingNotificationIds.value(); + if (commandIds.isEmpty()) { + return; + } + + beginInsertRows(QModelIndex(), 0, commandIds.size() - 1); + for (const QString& commandId : commandIds) { + RemotecommandDbusInterface* dbusInterface = new RemotecommandDbusInterface(m_deviceId, commandId, this); + m_commandList.append(dbusInterface); + } + endInsertRows(); +} + +QVariant RemotecommandsModel::data(const QModelIndex& index, int role) const +{ + if (!index.isValid() + || index.row() < 0 + || index.row() >= m_commandList.count() + || !m_commandList[index.row()]->isValid()) + { + return QVariant(); + } + + if (!m_dbusInterface || !m_dbusInterface->isValid()) { + return QVariant(); + } + + RemotecommandDbusInterface* command = m_commandList[index.row()]; + + //FIXME: This function gets called lots of times, producing lots of dbus calls. Add a cache? + switch (role) { + case IdRole: + return command->commandId(); + case KeyRole: + return command->key(); + case NameRole: + return command->name(); + case CommandRole: + return command->command(); + default: + return QVariant(); + } +} + +RemotecommandDbusInterface* RemotecommandsModel::getCommand(const QModelIndex& index) const +{ + if (!index.isValid()) { + return nullptr; + } + + int row = index.row(); + if (row < 0 || row >= m_commandList.size()) { + return nullptr; + } + + return m_commandList[row]; +} + +int RemotecommandsModel::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 RemotecommandsModel::clearCommands() +{ + if (!m_commandList.isEmpty()) { + beginRemoveRows(QModelIndex(), 0, m_commandList.size() - 1); + qDeleteAll(m_commandList); + m_commandList.clear(); + endRemoveRows(); + } +} diff --git a/plasmoid/declarativeplugin/kdeconnectdeclarativeplugin.cpp b/plasmoid/declarativeplugin/kdeconnectdeclarativeplugin.cpp --- a/plasmoid/declarativeplugin/kdeconnectdeclarativeplugin.cpp +++ b/plasmoid/declarativeplugin/kdeconnectdeclarativeplugin.cpp @@ -32,6 +32,7 @@ #include "interfaces/devicessortproxymodel.h" #include "interfaces/devicesmodel.h" #include "interfaces/notificationsmodel.h" +#include "interfaces/remotecommandsmodel.h" QObject* createDeviceDbusInterface(const QVariant& deviceId) { @@ -79,6 +80,11 @@ return new TelephonyDbusInterface(deviceId.toString()); } +QObject* createRemotecommandsInterface(const QVariant& deviceId) +{ + return new DeviceRemotecommandsDbusInterface(deviceId.toString()); +} + QObject* createDBusResponse() { return new DBusAsyncResponse(); @@ -88,13 +94,15 @@ { qmlRegisterType(uri, 1, 0, "DevicesModel"); qmlRegisterType(uri, 1, 0, "NotificationsModel"); + qmlRegisterType(uri, 1, 0, "RemotecommandsModel"); qmlRegisterType(uri, 1, 0, "DBusAsyncResponse"); qmlRegisterType(uri, 1, 0, "DevicesSortProxyModel"); qmlRegisterUncreatableType(uri, 1, 0, "MprisDbusInterface", QStringLiteral("You're not supposed to instantiate interfacess")); qmlRegisterUncreatableType(uri, 1, 0, "LockDeviceDbusInterface", QStringLiteral("You're not supposed to instantiate interfacess")); qmlRegisterUncreatableType(uri, 1, 0, "FindMyPhoneDbusInterface", QStringLiteral("You're not supposed to instantiate interfacess")); qmlRegisterUncreatableType(uri, 1, 0, "RemoteKeyboardDbusInterface", QStringLiteral("You're not supposed to instantiate interfacess")); qmlRegisterUncreatableType(uri, 1, 0, "DeviceDbusInterface", QStringLiteral("You're not supposed to instantiate interfacess")); + qmlRegisterUncreatableType(uri, 1, 0, "DeviceRemotecommandsDbusInterface", QStringLiteral("You're not supposed to instantiate interfacess")); qmlRegisterSingletonType(uri, 1, 0, "DaemonDbusInterface", [](QQmlEngine*, QJSEngine*) -> QObject* { return new DaemonDbusInterface; @@ -105,13 +113,13 @@ void KdeConnectDeclarativePlugin::initializeEngine(QQmlEngine* engine, const char* uri) { QQmlExtensionPlugin::initializeEngine(engine, uri); - + engine->rootContext()->setContextProperty(QStringLiteral("DeviceDbusInterfaceFactory") , new ObjectFactory(engine, createDeviceDbusInterface)); - + engine->rootContext()->setContextProperty(QStringLiteral("DeviceBatteryDbusInterfaceFactory") , new ObjectFactory(engine, createDeviceBatteryDbusInterface)); - + engine->rootContext()->setContextProperty(QStringLiteral("FindMyPhoneDbusInterfaceFactory") , new ObjectFactory(engine, createFindMyPhoneInterface)); @@ -132,10 +140,13 @@ engine->rootContext()->setContextProperty(QStringLiteral("TelephonyDbusInterfaceFactory") , new ObjectFactory(engine, createTelephonyInterface)); - + + engine->rootContext()->setContextProperty(QStringLiteral("DeviceRemotecommandsDbusInterfaceFactory") + , new ObjectFactory(engine, createRemotecommandsInterface)); + engine->rootContext()->setContextProperty(QStringLiteral("DBusResponseFactory") - , new ObjectFactory(engine, createDBusResponse)); - + , new ObjectFactory(engine, createDBusResponse)); + engine->rootContext()->setContextProperty(QStringLiteral("DBusResponseWaiter") , DBusResponseWaiter::instance()); } diff --git a/plugins/remotecommands/CMakeLists.txt b/plugins/remotecommands/CMakeLists.txt --- a/plugins/remotecommands/CMakeLists.txt +++ b/plugins/remotecommands/CMakeLists.txt @@ -1,5 +1,8 @@ kdeconnect_add_plugin(kdeconnect_remotecommands JSON kdeconnect_remotecommands.json - SOURCES remotecommandsplugin.cpp + SOURCES + remotecommandsplugin.cpp + remotecommandsdbusinterface.cpp + remotecommand.cpp ) target_link_libraries(kdeconnect_remotecommands diff --git a/plugins/remotecommands/remotecommandsplugin.h b/plugins/remotecommands/remotecommand.h copy from plugins/remotecommands/remotecommandsplugin.h copy to plugins/remotecommands/remotecommand.h --- a/plugins/remotecommands/remotecommandsplugin.h +++ b/plugins/remotecommands/remotecommand.h @@ -1,5 +1,5 @@ /** - * Copyright 2016 Aleix Pol Gonzalez + * Copyright 2018 Nicolas Fella * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -18,43 +18,29 @@ * along with this program. If not, see . */ -#ifndef REMOTECOMMANDSPLUGIN_H -#define REMOTECOMMANDSPLUGIN_H +#pragma once #include -#include -#include -#include -#include -#include -#include - -class Q_DECL_EXPORT RemoteCommandsPlugin - : public KdeConnectPlugin +class Remotecommand + : public QObject { Q_OBJECT - Q_CLASSINFO("D-Bus Interface", "org.kde.kdeconnect.device.remotecommands") - Q_PROPERTY(QByteArray commands READ commands NOTIFY commandsChanged) + Q_CLASSINFO("D-Bus Interface", "org.kde.kdeconnect.device.remotecommands.remotecommand") + Q_PROPERTY(QString key READ key CONSTANT) + Q_PROPERTY(QString name READ name CONSTANT) + Q_PROPERTY(QString command READ command CONSTANT) public: - explicit RemoteCommandsPlugin(QObject* parent, const QVariantList& args); - ~RemoteCommandsPlugin() override; - - Q_SCRIPTABLE void triggerCommand(const QString& key); - QByteArray commands() const { return m_commands; } + Remotecommand(QString key, QString name, QString command, QObject* parent); - bool receivePacket(const NetworkPacket& np) override; - void connected() override; - QString dbusPath() const override; - -Q_SIGNALS: - void commandsChanged(const QByteArray& commands); + QString key(); + QString name(); + QString command(); private: - void setCommands(const QByteArray& commands); + QString m_key; + QString m_name; + QString m_command; - QByteArray m_commands; }; - -#endif diff --git a/plugins/remotecommands/remotecommand.cpp b/plugins/remotecommands/remotecommand.cpp new file mode 100644 --- /dev/null +++ b/plugins/remotecommands/remotecommand.cpp @@ -0,0 +1,44 @@ +/** + * Copyright 2018 Nicolas Fella + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "remotecommand.h" + +Remotecommand::Remotecommand(QString key, QString name, QString command, QObject* parent) + : QObject(parent) + , m_key(key) + , m_name(name) + , m_command(command) +{} + +QString Remotecommand::key() +{ + return m_key; +} + +QString Remotecommand::name() +{ + return m_name; +} + +QString Remotecommand::command() +{ + return m_command; +} + diff --git a/plugins/remotecommands/remotecommandsplugin.h b/plugins/remotecommands/remotecommandsdbusinterface.h copy from plugins/remotecommands/remotecommandsplugin.h copy to plugins/remotecommands/remotecommandsdbusinterface.h --- a/plugins/remotecommands/remotecommandsplugin.h +++ b/plugins/remotecommands/remotecommandsdbusinterface.h @@ -1,5 +1,5 @@ /** - * Copyright 2016 Aleix Pol Gonzalez + * Copyright 2018 Nicolas Fella * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -18,43 +18,42 @@ * along with this program. If not, see . */ -#ifndef REMOTECOMMANDSPLUGIN_H -#define REMOTECOMMANDSPLUGIN_H +#pragma once -#include +#include +#include "remotecommandsplugin.h" -#include -#include -#include -#include -#include -#include +class Remotecommand; -class Q_DECL_EXPORT RemoteCommandsPlugin - : public KdeConnectPlugin +class RemoteCommandsDbusInterface + : public QDBusAbstractAdaptor { Q_OBJECT Q_CLASSINFO("D-Bus Interface", "org.kde.kdeconnect.device.remotecommands") - Q_PROPERTY(QByteArray commands READ commands NOTIFY commandsChanged) + Q_PROPERTY(QStringList commands READ commands NOTIFY commandsChanged) + Q_PROPERTY(QString deviceId READ deviceId CONSTANT) + Q_PROPERTY(bool canAddCommand READ canAddCommand CONSTANT) public: - explicit RemoteCommandsPlugin(QObject* parent, const QVariantList& args); - ~RemoteCommandsPlugin() override; + explicit RemoteCommandsDbusInterface(RemoteCommandsPlugin* plugin); + ~RemoteCommandsDbusInterface() override; - Q_SCRIPTABLE void triggerCommand(const QString& key); - QByteArray commands() const { return m_commands; } + Q_SCRIPTABLE QStringList commands(); + Q_SCRIPTABLE void triggerCommand(const QString& id); + Q_SCRIPTABLE void editCommands(); - bool receivePacket(const NetworkPacket& np) override; - void connected() override; - QString dbusPath() const override; + QString deviceId(); + bool canAddCommand(); + + bool receivePacket(const NetworkPacket& np); Q_SIGNALS: - void commandsChanged(const QByteArray& commands); + Q_SCRIPTABLE void commandsChanged(); private: - void setCommands(const QByteArray& commands); - - QByteArray m_commands; + const Device* m_device; + RemoteCommandsPlugin* m_plugin; + QHash m_commands; + int m_lastId; + bool m_canAddCommand; }; - -#endif diff --git a/plugins/remotecommands/remotecommandsdbusinterface.cpp b/plugins/remotecommands/remotecommandsdbusinterface.cpp new file mode 100644 --- /dev/null +++ b/plugins/remotecommands/remotecommandsdbusinterface.cpp @@ -0,0 +1,101 @@ +/** + * Copyright 2018 Nicolas Fella + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "remotecommandsdbusinterface.h" + +#include +#include +#include +#include + +#include "remotecommand.h" +#include "remotecommandsplugin.h" +#include "core/networkpacket.h" + +#define PACKET_TYPE_RUNCOMMAND_REQUEST QLatin1String("kdeconnect.runcommand.request") + +RemoteCommandsDbusInterface::RemoteCommandsDbusInterface(RemoteCommandsPlugin* plugin) + : QDBusAbstractAdaptor(const_cast(plugin->device())) + , m_device(plugin->device()) + , m_plugin(plugin) + , m_commands() + , m_lastId(0) + , m_canAddCommand(false) +{ +} + +RemoteCommandsDbusInterface::~RemoteCommandsDbusInterface() +{ +} + + +QStringList RemoteCommandsDbusInterface::commands() +{ + return m_commands.keys(); +} + +QString RemoteCommandsDbusInterface::deviceId() +{ + return m_device->id(); +} + +bool RemoteCommandsDbusInterface::canAddCommand() +{ + return m_canAddCommand; +} + + +bool RemoteCommandsDbusInterface::receivePacket(const NetworkPacket& np) +{ + if (np.has(QStringLiteral("commandList"))) { + + m_canAddCommand = np.get(QStringLiteral("canAddCommand")); + + qDeleteAll(m_commands); + m_commands.clear(); + QJsonDocument commands = QJsonDocument::fromJson(np.get(QStringLiteral("commandList"))); + + for (QString& key : commands.object().keys()) { + const QJsonObject entry = commands[key].toObject(); + + // key contains characters that are not suitable for DBus paths, create a public id instead + QString id = QString::number(m_lastId++); + + Remotecommand* command = new Remotecommand(key, entry[QStringLiteral("name")].toString(), entry[QStringLiteral("command")].toString(), this); + m_commands.insert(id, command); + QDBusConnection::sessionBus().registerObject(m_device->dbusPath()+"/remotecommands/" + id, command, QDBusConnection::ExportScriptableContents); + } + + } + return true; +} + +void RemoteCommandsDbusInterface::triggerCommand(const QString& id) +{ + NetworkPacket np(PACKET_TYPE_RUNCOMMAND_REQUEST, {{ "key", m_commands[id]->key() }}); + m_plugin->sendPacket(np); +} + + +void RemoteCommandsDbusInterface::editCommands() +{ + NetworkPacket np(PACKET_TYPE_RUNCOMMAND_REQUEST, {{ "setup", true }}); + m_plugin->sendPacket(np); +} diff --git a/plugins/remotecommands/remotecommandsplugin.h b/plugins/remotecommands/remotecommandsplugin.h --- a/plugins/remotecommands/remotecommandsplugin.h +++ b/plugins/remotecommands/remotecommandsplugin.h @@ -24,37 +24,28 @@ #include #include -#include -#include -#include -#include -#include + +#include + +class RemoteCommandsDbusInterface; + +Q_DECLARE_LOGGING_CATEGORY(KDECONNECT_PLUGIN_REMOTECOMMANDS) class Q_DECL_EXPORT RemoteCommandsPlugin : public KdeConnectPlugin { Q_OBJECT - Q_CLASSINFO("D-Bus Interface", "org.kde.kdeconnect.device.remotecommands") - Q_PROPERTY(QByteArray commands READ commands NOTIFY commandsChanged) public: explicit RemoteCommandsPlugin(QObject* parent, const QVariantList& args); ~RemoteCommandsPlugin() override; - Q_SCRIPTABLE void triggerCommand(const QString& key); - QByteArray commands() const { return m_commands; } - bool receivePacket(const NetworkPacket& np) override; void connected() override; QString dbusPath() const override; -Q_SIGNALS: - void commandsChanged(const QByteArray& commands); - private: - void setCommands(const QByteArray& commands); - - QByteArray m_commands; + RemoteCommandsDbusInterface* m_iface; }; #endif diff --git a/plugins/remotecommands/remotecommandsplugin.cpp b/plugins/remotecommands/remotecommandsplugin.cpp --- a/plugins/remotecommands/remotecommandsplugin.cpp +++ b/plugins/remotecommands/remotecommandsplugin.cpp @@ -23,15 +23,12 @@ #include #include -#include -#include #include -#include #include -#include #include #include +#include "remotecommandsdbusinterface.h" #define PACKET_TYPE_RUNCOMMAND_REQUEST QLatin1String("kdeconnect.runcommand.request") @@ -41,20 +38,15 @@ RemoteCommandsPlugin::RemoteCommandsPlugin(QObject* parent, const QVariantList& args) : KdeConnectPlugin(parent, args) - , m_commands("{}") + , m_iface(new RemoteCommandsDbusInterface(this)) { } RemoteCommandsPlugin::~RemoteCommandsPlugin() = default; bool RemoteCommandsPlugin::receivePacket(const NetworkPacket& np) { - if (np.has(QStringLiteral("commandList"))) { - setCommands(np.get(QStringLiteral("commandList"))); - return true; - } - - return false; + return m_iface->receivePacket(np); } void RemoteCommandsPlugin::connected() @@ -68,18 +60,4 @@ return "/modules/kdeconnect/devices/" + device()->id() + "/remotecommands"; } -void RemoteCommandsPlugin::setCommands(const QByteArray& cmds) -{ - if (m_commands != cmds) { - m_commands = cmds; - Q_EMIT commandsChanged(m_commands); - } -} - -void RemoteCommandsPlugin::triggerCommand(const QString& key) -{ - NetworkPacket np(PACKET_TYPE_RUNCOMMAND_REQUEST, {{ "key", key }}); - sendPacket(np); -} - #include "remotecommandsplugin.moc"