diff --git a/app/qml/DevicePage.qml b/app/qml/DevicePage.qml --- a/app/qml/DevicePage.qml +++ b/app/qml/DevicePage.qml @@ -99,6 +99,13 @@ } } + PluginItem { + label: i18n("Run command") + interfaceFactory: RemoteCommandsDbusInterfaceFactory + component: "qrc:/qml/runcommand.qml" + pluginName: "remotecommands" + } + Item { Layout.fillHeight: true } } } 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,53 @@ +/* + * 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(key) + reserveSpaceForIcon: false + } + } + +} + diff --git a/app/resources.qrc b/app/resources.qrc --- a/app/resources.qrc +++ b/app/resources.qrc @@ -8,5 +8,6 @@ qml/PluginItem.qml qml/DevicePage.qml qml/FindDevicesPage.qml + qml/runcommand.qml 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 ) diff --git a/interfaces/remotecommandsmodel.h b/interfaces/remotecommandsmodel.h new file mode 100644 --- /dev/null +++ b/interfaces/remotecommandsmodel.h @@ -0,0 +1,72 @@ +/** + * 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 "interfaces/dbusinterfaces.h" + +struct Command { + QString key; + QString name; + QString command; +}; + +class KDECONNECTINTERFACES_EXPORT RemoteCommandsModel + : public QAbstractListModel +{ + Q_OBJECT + Q_PROPERTY(QString deviceId READ deviceId WRITE setDeviceId NOTIFY deviceIdChanged) + +public: + enum ModelRoles { + 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; + + QHash roleNames() const override; + +private Q_SLOTS: + void refreshCommandList(); + void clearCommands(); + +Q_SIGNALS: + void deviceIdChanged(const QString& value); + void rowsChanged(); + +private: + RemoteCommandsDbusInterface* m_dbusInterface; + QVector 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,154 @@ +/** + * 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 "remotecommandsmodel.h" +#include "interfaces_debug.h" + +#include +#include + +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(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 RemoteCommandsDbusInterface(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; + } + + const auto cmds = QJsonDocument::fromJson(m_dbusInterface->commands()).object(); + + beginResetModel(); + + for (auto it = cmds.constBegin(), itEnd = cmds.constEnd(); it!=itEnd; ++it) { + const QJsonObject cont = it->toObject(); + Command* command = new 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 RemoteCommandsModel::data(const QModelIndex& index, int role) const +{ + if (!index.isValid() + || index.row() < 0 + || index.row() >= m_commandList.count()) + { + return QVariant(); + } + + if (!m_dbusInterface || !m_dbusInterface->isValid()) { + return QVariant(); + } + + Command* 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 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 QObject* createDeviceDbusInterface(const QVariant& deviceId) { @@ -84,17 +85,24 @@ return new DBusAsyncResponse(); } +QObject* createRemoteCommandsInterface(const QVariant& deviceId) +{ + return new RemoteCommandsDbusInterface(deviceId.toString()); +} + void KdeConnectDeclarativePlugin::registerTypes(const char* uri) { 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, "RemoteCommandsDbusInterface", 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("DBusResponseFactory") - , new ObjectFactory(engine, createDBusResponse)); - + , new ObjectFactory(engine, createDBusResponse)); + engine->rootContext()->setContextProperty(QStringLiteral("DBusResponseWaiter") , DBusResponseWaiter::instance()); + + engine->rootContext()->setContextProperty(QStringLiteral("RemoteCommandsDbusInterfaceFactory") + , new ObjectFactory(engine, createRemoteCommandsInterface)); } diff --git a/plugins/remotecommands/remotecommandsplugin.h b/plugins/remotecommands/remotecommandsplugin.h --- a/plugins/remotecommands/remotecommandsplugin.h +++ b/plugins/remotecommands/remotecommandsplugin.h @@ -36,13 +36,19 @@ Q_OBJECT Q_CLASSINFO("D-Bus Interface", "org.kde.kdeconnect.device.remotecommands") Q_PROPERTY(QByteArray 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; Q_SCRIPTABLE void triggerCommand(const QString& key); + Q_SCRIPTABLE void editCommands(); + QByteArray commands() const { return m_commands; } + QString deviceId() const { return device()->id(); } + bool canAddCommand() const { return m_canAddCommand; } bool receivePacket(const NetworkPacket& np) override; void connected() override; @@ -55,6 +61,7 @@ void setCommands(const QByteArray& commands); QByteArray m_commands; + bool m_canAddCommand; }; #endif diff --git a/plugins/remotecommands/remotecommandsplugin.cpp b/plugins/remotecommands/remotecommandsplugin.cpp --- a/plugins/remotecommands/remotecommandsplugin.cpp +++ b/plugins/remotecommands/remotecommandsplugin.cpp @@ -42,14 +42,16 @@ RemoteCommandsPlugin::RemoteCommandsPlugin(QObject* parent, const QVariantList& args) : KdeConnectPlugin(parent, args) , m_commands("{}") + , m_canAddCommand(false) { } RemoteCommandsPlugin::~RemoteCommandsPlugin() = default; bool RemoteCommandsPlugin::receivePacket(const NetworkPacket& np) { if (np.has(QStringLiteral("commandList"))) { + m_canAddCommand = np.get(QStringLiteral("canAddCommand")); setCommands(np.get(QStringLiteral("commandList"))); return true; } @@ -82,4 +84,10 @@ sendPacket(np); } +void RemoteCommandsPlugin::editCommands() +{ + NetworkPacket np(PACKET_TYPE_RUNCOMMAND_REQUEST, {{ "setup", true }}); + sendPacket(np); +} + #include "remotecommandsplugin.moc"