diff --git a/discover/qml/ApplicationPage.qml b/discover/qml/ApplicationPage.qml --- a/discover/qml/ApplicationPage.qml +++ b/discover/qml/ApplicationPage.qml @@ -242,6 +242,20 @@ Layout.bottomMargin: Kirigami.Units.largeSpacing } + Repeater { + model: application.objects + delegate: Loader { + property QtObject resource: appInfo.application + source: modelData + } + } + + Item { + height: addonsButton.height + width: 5 + } + + // Details/metadata Rectangle { color: Kirigami.Theme.linkColor Layout.fillWidth: true diff --git a/libdiscover/backends/CMakeLists.txt b/libdiscover/backends/CMakeLists.txt --- a/libdiscover/backends/CMakeLists.txt +++ b/libdiscover/backends/CMakeLists.txt @@ -28,7 +28,7 @@ message(WARNING "BUILD_FlatpakBackend enabled but Flatpak=${FLATPAK_FOUND} or AppStreamQt=${AppStreamQt_FOUND} not found") endif() -option(BUILD_SnapBackend "Build Snap support. Still a proof of concept" "OFF") +option(BUILD_SnapBackend "Build Snap support." "ON") if(BUILD_SnapBackend) find_package(Snapd) set_package_properties(Snapd PROPERTIES diff --git a/libdiscover/backends/SnapBackend/CMakeLists.txt b/libdiscover/backends/SnapBackend/CMakeLists.txt --- a/libdiscover/backends/SnapBackend/CMakeLists.txt +++ b/libdiscover/backends/SnapBackend/CMakeLists.txt @@ -1,6 +1,6 @@ add_subdirectory(libsnapclient) -add_library(snap-backend MODULE SnapResource.cpp SnapBackend.cpp SnapReviewsBackend.cpp SnapTransaction.cpp) +add_library(snap-backend MODULE SnapResource.cpp SnapBackend.cpp SnapReviewsBackend.cpp SnapTransaction.cpp snapui.qrc) target_link_libraries(snap-backend Qt5::Core KF5::CoreAddons KF5::ConfigCore Discover::Common Snapd::Core) install(TARGETS snap-backend DESTINATION ${PLUGIN_INSTALL_DIR}/discover) diff --git a/libdiscover/backends/SnapBackend/SnapResource.h b/libdiscover/backends/SnapBackend/SnapResource.h --- a/libdiscover/backends/SnapBackend/SnapResource.h +++ b/libdiscover/backends/SnapBackend/SnapResource.h @@ -27,10 +27,12 @@ #include class SnapBackend; +class QAbstractItemModel; class SnapResource : public AbstractResource { Q_OBJECT +Q_PROPERTY(QStringList objects MEMBER m_objects CONSTANT) public: explicit SnapResource(QSharedPointer snap, AbstractResource::State state, SnapBackend* parent); ~SnapResource() override = default; @@ -62,12 +64,15 @@ QDate releaseDate() const override; + Q_SCRIPTABLE QAbstractItemModel* plugs(QObject* parents); + public: void gotIcon(); AbstractResource::State m_state; QSharedPointer m_snap; mutable QVariant m_icon; + const QStringList m_objects; }; #endif // SNAPRESOURCE_H diff --git a/libdiscover/backends/SnapBackend/SnapResource.cpp b/libdiscover/backends/SnapBackend/SnapResource.cpp --- a/libdiscover/backends/SnapBackend/SnapResource.cpp +++ b/libdiscover/backends/SnapBackend/SnapResource.cpp @@ -24,11 +24,56 @@ #include #include #include +#include +#include +#include +#include -SnapResource::SnapResource(QSharedPointer snap, AbstractResource::State state, SnapBackend* parent) - : AbstractResource(parent) +QDebug operator<<(QDebug debug, const QSnapdPlug& plug) +{ + QDebugStateSaver saver(debug); + debug.nospace() << "QSnapdPlug("; + debug.nospace() << "name:" << plug.name() << ','; + debug.nospace() << "snap:" << plug.snap() << ','; + debug.nospace() << "label:" << plug.label() << ','; + debug.nospace() << "interface:" << plug.interface() << ','; + debug.nospace() << "connectionCount:" << plug.connectionCount(); + debug.nospace() << ')'; + return debug; +} + +QDebug operator<<(QDebug debug, const QSnapdSlot& slot) +{ + QDebugStateSaver saver(debug); + debug.nospace() << "QSnapdSlot("; + debug.nospace() << "name:" << slot.name() << ','; + debug.nospace() << "label:" << slot.label() << ','; + debug.nospace() << "snap:" << slot.snap() << ','; + debug.nospace() << "interface:" << slot.interface() << ','; + debug.nospace() << "connectionCount:" << slot.connectionCount(); + debug.nospace() << ')'; + return debug; +} + +QDebug operator<<(QDebug debug, const QSnapdPlug* plug) +{ + QDebugStateSaver saver(debug); + debug.nospace() << "*" << *plug; + return debug; +} + +QDebug operator<<(QDebug debug, const QSnapdSlot* slot) +{ + QDebugStateSaver saver(debug); + debug.nospace() << "*" << *slot; + return debug; +} + +SnapResource::SnapResource(QSharedPointer snap, AbstractResource::State state, SnapBackend* backend) + : AbstractResource(backend) , m_state(state) , m_snap(snap) + , m_objects({ QStringLiteral("qrc:/snapui/PermissionsButton.qml") }) { setObjectName(snap->name()); } @@ -192,3 +237,91 @@ { return {}; } + +class PlugsModel : public QStandardItemModel +{ +public: + enum Roles { + PlugNameRole = Qt::UserRole + 1, + SlotSnapRole, + SlotNameRole + }; + + PlugsModel(QSnapdSnap* snap, SnapBackend* backend, QObject* parent) + : QStandardItemModel(parent) + , m_snap(snap) + , m_backend(backend) + { + setItemRoleNames(roleNames().unite( + { {Qt::CheckStateRole, "checked"} } + )); + + auto req = backend->client()->getInterfaces(); + req->runSync(); + + QHash> slotsForInterface; + for (int i = 0; islotCount(); ++i) { + const auto slot = req->slot(i); + slot->setParent(this); + slotsForInterface[slot->interface()].append(slot); + + } + + for (int i = 0; iplugCount(); ++i) { + const QScopedPointer plug(req->plug(i)); + if (plug->snap() == m_snap->name()) { + for (auto slot: slotsForInterface[plug->interface()]) { + auto item = new QStandardItem; + if (plug->label().isEmpty()) + item->setText(plug->name()); + else + item->setText(i18n("%1 - %2", plug->name(), plug->label())); + + item->setCheckable(true); + item->setCheckState(plug->connectionCount()>0 ? Qt::Checked : Qt::Unchecked); + item->setData(plug->name(), PlugNameRole); + item->setData(slot->snap(), SlotSnapRole); + item->setData(slot->name(), SlotNameRole); + appendRow(item); + } + } + } + } + +private: + bool setData(const QModelIndex & index, const QVariant & value, int role) override { + if (role != Qt::CheckStateRole) + return QStandardItemModel::setData(index, value, role); + + auto item = itemFromIndex(index); + Q_ASSERT(item); + const QString plugName = item->data(PlugNameRole).toString(); + const QString slotSnap = item->data(SlotSnapRole).toString(); + const QString slotName = item->data(SlotNameRole).toString(); + + QSnapdRequest* req; + + if (item->checkState() == Qt::Checked) { + req = m_backend->client()->connectInterface(m_snap->name(), plugName, slotSnap, slotName); + } else { + req = m_backend->client()->disconnectInterface(m_snap->name(), plugName, slotSnap, slotName); + } + req->runSync(); + if (req->error()) { + qWarning() << "snapd error" << req->errorString(); + } + return req->error() == QSnapdRequest::NoError; + } + + QSnapdSnap* const m_snap; + SnapBackend* const m_backend; +}; + +QAbstractItemModel* SnapResource::plugs(QObject* p) +{ + if (!isInstalled()) + return new QStandardItemModel(p); + + + return new PlugsModel(m_snap.data(), qobject_cast(parent()), p); +} diff --git a/libdiscover/backends/SnapBackend/snapui.qrc b/libdiscover/backends/SnapBackend/snapui.qrc new file mode 100644 --- /dev/null +++ b/libdiscover/backends/SnapBackend/snapui.qrc @@ -0,0 +1,6 @@ + + + + snapui/PermissionsButton.qml + + diff --git a/libdiscover/backends/SnapBackend/snapui/PermissionsButton.qml b/libdiscover/backends/SnapBackend/snapui/PermissionsButton.qml new file mode 100644 --- /dev/null +++ b/libdiscover/backends/SnapBackend/snapui/PermissionsButton.qml @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2018 Aleix Pol Gonzalez + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library/Lesser General Public License + * version 2, or (at your option) any later version, as published by the + * Free Software Foundation + * + * 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 Library/Lesser General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +import QtQuick 2.1 +import QtQuick.Controls 2.1 +import org.kde.kirigami 2.1 as Kirigami + +Button +{ + id: root + text: i18n("Configure permissions...") + + visible: resource.isInstalled + onClicked: overlay.open() + Popup { + id: overlay + parent: applicationWindow().overlay + bottomPadding: Kirigami.Units.largeSpacing + topPadding: Kirigami.Units.largeSpacing + + x: (parent.width - width)/2 + y: (parent.height - height)/2 + width: parent.width * 1/3 + height: Math.min(view.contentHeight + bottomPadding + topPadding, parent.height * 4/5) + + ListView { + id: view + anchors.fill: parent + header: Kirigami.Heading { + text: i18n ("Permissions for %1", resource.name) + } + model: resource.plugs(root) + delegate: CheckDelegate { + id: delegate + width: parent.width + text: model.display + checked: model.checked + onClicked: { + model.checked = delegate.checked + } + } + } + } +}