mirror of
https://invent.kde.org/marcoa/shift-shell.git
synced 2026-04-29 15:03:09 +00:00
kcm: Implement Waydroid Applications
This commit is contained in:
parent
93912960d7
commit
de7b63572f
10 changed files with 503 additions and 4 deletions
|
|
@ -2,7 +2,7 @@
|
|||
# SPDX-License-Identifier: BSD-2-Clause
|
||||
|
||||
ecm_add_qml_module(waydroidintegrationplugin URI org.kde.plasma.private.mobileshell.waydroidintegrationplugin GENERATE_PLUGIN_SOURCE)
|
||||
target_sources(waydroidintegrationplugin PRIVATE waydroidstate.cpp)
|
||||
target_sources(waydroidintegrationplugin PRIVATE waydroidstate.cpp waydroidapplication.cpp waydroidapplicationlistmodel.cpp)
|
||||
|
||||
target_link_libraries(waydroidintegrationplugin PRIVATE
|
||||
Qt::Gui
|
||||
|
|
@ -22,4 +22,4 @@ ecm_qt_declare_logging_category(waydroidintegrationplugin
|
|||
DESCRIPTION "Plugin to add Waydroid Integration on Plasma"
|
||||
)
|
||||
|
||||
add_subdirectory(kauth)
|
||||
add_subdirectory(kauth)
|
||||
|
|
|
|||
69
components/waydroidintegrationplugin/waydroidapplication.cpp
Normal file
69
components/waydroidintegrationplugin/waydroidapplication.cpp
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2025 Florian RICHER <florian.richer@protonmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
|
||||
#include <waydroidapplication.h>
|
||||
|
||||
#include <QRegularExpression>
|
||||
#include <QStringLiteral>
|
||||
|
||||
using namespace Qt::StringLiterals;
|
||||
|
||||
static const QRegularExpression nameRegExp(u"^Name:\\s*(\\S+)"_s);
|
||||
static const QRegularExpression packageNameRegExp(u"^packageName:\\s*(\\S+)"_s);
|
||||
|
||||
WaydroidApplication::WaydroidApplication(QObject *parent)
|
||||
: QObject{parent}
|
||||
{
|
||||
// Nothing
|
||||
}
|
||||
|
||||
WaydroidApplication::Ptr WaydroidApplication::fromWaydroidLog(QTextStream &inFile)
|
||||
{
|
||||
WaydroidApplication::Ptr app;
|
||||
|
||||
const QString line = inFile.readLine();
|
||||
const QRegularExpressionMatch nameMatch = nameRegExp.match(line);
|
||||
|
||||
if (!nameMatch.hasMatch() || nameMatch.lastCapturedIndex() == 0) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
app = std::make_shared<WaydroidApplication>();
|
||||
app->m_name = nameMatch.captured(nameMatch.lastCapturedIndex());
|
||||
|
||||
qint64 oldPos = inFile.pos();
|
||||
while (!inFile.atEnd()) {
|
||||
const QString line = inFile.readLine();
|
||||
if (line.trimmed().isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const QRegularExpressionMatch nameMatch = nameRegExp.match(line);
|
||||
if (nameMatch.hasMatch()) {
|
||||
inFile.seek(oldPos); // Revert file cursor position for the next Application parsing
|
||||
return app;
|
||||
}
|
||||
|
||||
const QRegularExpressionMatch packageNameMatch = packageNameRegExp.match(line);
|
||||
if (packageNameMatch.hasMatch() && packageNameMatch.lastCapturedIndex() > 0) {
|
||||
app->m_packageName = packageNameMatch.captured(packageNameMatch.lastCapturedIndex());
|
||||
}
|
||||
|
||||
oldPos = inFile.pos();
|
||||
}
|
||||
|
||||
return app;
|
||||
}
|
||||
|
||||
QString WaydroidApplication::name() const
|
||||
{
|
||||
return m_name;
|
||||
}
|
||||
|
||||
QString WaydroidApplication::packageName() const
|
||||
{
|
||||
return m_packageName;
|
||||
}
|
||||
39
components/waydroidintegrationplugin/waydroidapplication.h
Normal file
39
components/waydroidintegrationplugin/waydroidapplication.h
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2025 Florian RICHER <florian.richer@protonmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QDir>
|
||||
#include <QObject>
|
||||
#include <qtmetamacros.h>
|
||||
|
||||
class WaydroidApplication : public QObject, public std::enable_shared_from_this<WaydroidApplication>
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(QString name READ name CONSTANT)
|
||||
Q_PROPERTY(QString packageName READ packageName CONSTANT)
|
||||
|
||||
public:
|
||||
typedef std::shared_ptr<WaydroidApplication> Ptr;
|
||||
|
||||
WaydroidApplication(QObject *parent = nullptr);
|
||||
|
||||
/**
|
||||
* @brief Read from "waydroid app list" command output log through QTextStream.
|
||||
* The QTextStream cursor must be set to the first line of the application.
|
||||
* The first line begin with "Name:".
|
||||
*
|
||||
* @param inFile The QTextStream used to read line by line the Waydroid logs.
|
||||
*/
|
||||
static WaydroidApplication::Ptr fromWaydroidLog(QTextStream &inFile);
|
||||
|
||||
QString name() const;
|
||||
QString packageName() const;
|
||||
|
||||
private:
|
||||
QString m_name;
|
||||
QString m_packageName;
|
||||
};
|
||||
|
|
@ -0,0 +1,198 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2025 Florian RICHER <florian.richer@protonmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
|
||||
#include "waydroidapplicationlistmodel.h"
|
||||
#include "waydroidintegrationplugin_debug.h"
|
||||
#include "waydroidshared.h"
|
||||
|
||||
#include <QLoggingCategory>
|
||||
#include <QProcess>
|
||||
#include <QStringLiteral>
|
||||
|
||||
#include <KLocalizedString>
|
||||
|
||||
using namespace Qt::StringLiterals;
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
WaydroidApplicationListModel::WaydroidApplicationListModel(WaydroidState *parent)
|
||||
: QAbstractListModel{parent}
|
||||
, m_waydroidState{parent}
|
||||
, m_refreshTimer{new QTimer{this}}
|
||||
{
|
||||
// Waydroid does not return all installed applications immediately, so we need to refresh regularly.
|
||||
m_refreshTimer->setInterval(1s);
|
||||
m_refreshTimer->setSingleShot(false);
|
||||
m_refreshTimer->start();
|
||||
|
||||
connect(m_refreshTimer, &QTimer::timeout, this, &WaydroidApplicationListModel::refreshApplications);
|
||||
connect(parent, &WaydroidState::sessionStatusChanged, this, &WaydroidApplicationListModel::refreshApplications);
|
||||
}
|
||||
|
||||
WaydroidApplicationListModel::~WaydroidApplicationListModel() = default;
|
||||
|
||||
void WaydroidApplicationListModel::refreshApplications()
|
||||
{
|
||||
if (m_waydroidState->sessionStatus() != WaydroidState::SessionRunning) {
|
||||
return;
|
||||
}
|
||||
|
||||
qCDebug(WAYDROIDINTEGRATIONPLUGIN) << "Reload waydroid apps";
|
||||
|
||||
QMap<QString, int> appIdMap; // <packageName, index>
|
||||
for (int i = 0; i < m_applications.size(); ++i) {
|
||||
const auto &application = m_applications[i];
|
||||
appIdMap.insert(application->packageName(), i);
|
||||
}
|
||||
|
||||
QList<WaydroidApplication::Ptr> currentApps = queryApplications();
|
||||
QList<WaydroidApplication::Ptr> toInsert;
|
||||
|
||||
for (const WaydroidApplication::Ptr &application : currentApps) {
|
||||
auto it = appIdMap.find(application->packageName());
|
||||
if (it != appIdMap.end()) {
|
||||
// Application already in m_applications
|
||||
appIdMap.erase(it);
|
||||
} else {
|
||||
// Application needs to be inserted into m_applications
|
||||
toInsert.append(std::move(application));
|
||||
}
|
||||
}
|
||||
|
||||
QList<int> toRemove;
|
||||
for (int index : appIdMap.values()) {
|
||||
toRemove.append(index);
|
||||
}
|
||||
|
||||
std::sort(toRemove.begin(), toRemove.end());
|
||||
|
||||
// Remove indices first, from end to start to avoid indicies changing
|
||||
for (int i = toRemove.size() - 1; i >= 0; --i) {
|
||||
int ind = toRemove[i];
|
||||
|
||||
beginRemoveRows({}, ind, ind);
|
||||
m_applications.removeAt(ind);
|
||||
endRemoveRows();
|
||||
}
|
||||
|
||||
// Append new elements
|
||||
for (const WaydroidApplication::Ptr &application : toInsert) {
|
||||
beginInsertRows({}, m_applications.size(), m_applications.size());
|
||||
m_applications.append(application);
|
||||
endInsertRows();
|
||||
}
|
||||
}
|
||||
|
||||
QList<WaydroidApplication::Ptr> WaydroidApplicationListModel::queryApplications() const
|
||||
{
|
||||
QList<WaydroidApplication::Ptr> applications;
|
||||
|
||||
QStringList arguments = {u"app"_s, u"list"_s};
|
||||
|
||||
QProcess *process = new QProcess(m_waydroidState);
|
||||
process->start(WAYDROID_COMMAND, arguments);
|
||||
process->waitForFinished();
|
||||
|
||||
if (process->exitCode() != 0) {
|
||||
qCWarning(WAYDROIDINTEGRATIONPLUGIN) << "Failed to run waydroid app list command: " << process->readAllStandardError();
|
||||
return applications;
|
||||
}
|
||||
|
||||
const QByteArray data = process->readAllStandardOutput();
|
||||
if (data.isEmpty()) {
|
||||
qCWarning(WAYDROIDINTEGRATIONPLUGIN) << "Empty data: " << process->readAllStandardError();
|
||||
return applications;
|
||||
}
|
||||
|
||||
qCDebug(WAYDROIDINTEGRATIONPLUGIN) << "Waydroid output: " << data;
|
||||
QTextStream output = QTextStream(data);
|
||||
|
||||
while (!output.atEnd()) {
|
||||
const WaydroidApplication::Ptr app = WaydroidApplication::fromWaydroidLog(output);
|
||||
if (app == nullptr) {
|
||||
qCWarning(WAYDROIDINTEGRATIONPLUGIN) << "Failed to fetch the application: Maybe wrong QTextStream cursor position.";
|
||||
break;
|
||||
}
|
||||
|
||||
qCDebug(WAYDROIDINTEGRATIONPLUGIN) << "Waydroid application found: " << app.get()->name() << " (" << app.get()->packageName() << ")";
|
||||
applications.append(app);
|
||||
}
|
||||
return applications;
|
||||
}
|
||||
|
||||
QHash<int, QByteArray> WaydroidApplicationListModel::roleNames() const
|
||||
{
|
||||
return {{DelegateRole, QByteArrayLiteral("delegate")}, {NameRole, QByteArrayLiteral("name")}, {IdRole, QByteArrayLiteral("id")}};
|
||||
}
|
||||
|
||||
QVariant WaydroidApplicationListModel::data(const QModelIndex &index, int role) const
|
||||
{
|
||||
if (!index.isValid() || index.row() >= m_applications.count()) {
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
WaydroidApplication::Ptr app = m_applications.at(index.row());
|
||||
|
||||
switch (role) {
|
||||
case Qt::DisplayRole:
|
||||
case DelegateRole:
|
||||
return QVariant::fromValue(app.get());
|
||||
case NameRole:
|
||||
return app->name();
|
||||
case IdRole:
|
||||
return app->packageName();
|
||||
default:
|
||||
return QVariant();
|
||||
}
|
||||
}
|
||||
|
||||
int WaydroidApplicationListModel::rowCount(const QModelIndex &parent) const
|
||||
{
|
||||
if (parent.isValid()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return m_applications.count();
|
||||
}
|
||||
|
||||
void WaydroidApplicationListModel::installApk(const QString apkFile)
|
||||
{
|
||||
const QStringList arguments{u"app"_s, u"install"_s, apkFile};
|
||||
|
||||
QProcess *process = new QProcess(this);
|
||||
process->start(WAYDROID_COMMAND, arguments);
|
||||
|
||||
connect(process, &QProcess::finished, this, [this, apkFile, process](int exitCode, QProcess::ExitStatus exitStatus) {
|
||||
if (exitCode == 0 && exitStatus == QProcess::NormalExit) {
|
||||
Q_EMIT actionFinished(i18n("Application has been installed"));
|
||||
} else {
|
||||
Q_EMIT errorOccurred(i18n("Installation Failed"));
|
||||
qCWarning(WAYDROIDINTEGRATIONPLUGIN) << "Error occured during installation of " << apkFile << ": " << process->readAllStandardError();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void WaydroidApplicationListModel::deleteApplication(const QString appId)
|
||||
{
|
||||
const QStringList arguments{u"app"_s, u"remove"_s, appId};
|
||||
|
||||
QProcess *process = new QProcess(this);
|
||||
process->start(WAYDROID_COMMAND, arguments);
|
||||
|
||||
connect(process, &QProcess::finished, this, [this, appId, process](int exitCode, QProcess::ExitStatus exitStatus) {
|
||||
Q_UNUSED(exitCode);
|
||||
Q_UNUSED(exitStatus);
|
||||
|
||||
const QByteArray errorLog = process->readAllStandardError();
|
||||
|
||||
// "waydroid app remove" send log on stderr but keep exitCode to 0
|
||||
if (errorLog.isEmpty()) {
|
||||
Q_EMIT actionFinished(i18n("Application has been deleted"));
|
||||
} else {
|
||||
Q_EMIT errorOccurred(i18n("Application uninstall failed"));
|
||||
qCWarning(WAYDROIDINTEGRATIONPLUGIN) << "Error occured during uninstallation of " << appId << ": " << errorLog;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2025 Florian RICHER <florian.richer@protonmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "waydroidapplication.h"
|
||||
#include "waydroidstate.h"
|
||||
|
||||
#include <QAbstractListModel>
|
||||
#include <QObject>
|
||||
#include <QTimer>
|
||||
|
||||
class WaydroidState;
|
||||
|
||||
class WaydroidApplicationListModel : public QAbstractListModel
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
enum Roles {
|
||||
DelegateRole = Qt::UserRole + 1,
|
||||
NameRole,
|
||||
IdRole
|
||||
};
|
||||
|
||||
WaydroidApplicationListModel(WaydroidState *parent = nullptr);
|
||||
~WaydroidApplicationListModel() override;
|
||||
|
||||
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
|
||||
QHash<int, QByteArray> roleNames() const override;
|
||||
|
||||
Q_INVOKABLE void installApk(const QString apkFile);
|
||||
Q_INVOKABLE void deleteApplication(const QString appId);
|
||||
|
||||
Q_SIGNALS:
|
||||
void actionFinished(const QString message);
|
||||
void errorOccurred(const QString message);
|
||||
|
||||
private:
|
||||
WaydroidState *m_waydroidState{nullptr};
|
||||
QList<WaydroidApplication::Ptr> m_applications;
|
||||
QTimer *m_refreshTimer{nullptr};
|
||||
|
||||
void refreshApplications();
|
||||
QList<WaydroidApplication::Ptr> queryApplications() const;
|
||||
};
|
||||
9
components/waydroidintegrationplugin/waydroidshared.h
Normal file
9
components/waydroidintegrationplugin/waydroidshared.h
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2025 Florian RICHER <florian.richer@protonmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#define WAYDROID_COMMAND "waydroid"
|
||||
|
|
@ -6,6 +6,7 @@
|
|||
|
||||
#include "waydroidstate.h"
|
||||
#include "waydroidintegrationplugin_debug.h"
|
||||
#include "waydroidshared.h"
|
||||
|
||||
#include <QClipboard>
|
||||
#include <QDebug>
|
||||
|
|
@ -22,7 +23,6 @@
|
|||
|
||||
using namespace Qt::StringLiterals;
|
||||
|
||||
#define WAYDROID_COMMAND "waydroid"
|
||||
#define MULTI_WINDOWS_PROP_KEY "persist.waydroid.multi_windows"
|
||||
#define SUSPEND_PROP_KEY "persist.waydroid.suspend"
|
||||
#define UEVENT_PROP_KEY "persist.waydroid.uevent"
|
||||
|
|
@ -33,6 +33,7 @@ static const QRegularExpression systemOtaRegExp(u"system_ota\\s*=\\s*(\\S+)"_s);
|
|||
|
||||
WaydroidState::WaydroidState(QObject *parent)
|
||||
: QObject{parent}
|
||||
, m_applicationListModel{new WaydroidApplicationListModel{this}}
|
||||
{
|
||||
// Connect it-self to auto-refresh when required status has changed
|
||||
connect(this, &WaydroidState::statusChanged, this, &WaydroidState::refreshSessionInfo);
|
||||
|
|
@ -335,6 +336,11 @@ QString WaydroidState::androidId() const
|
|||
return m_androidId;
|
||||
}
|
||||
|
||||
WaydroidApplicationListModel *WaydroidState::applicationListModel() const
|
||||
{
|
||||
return m_applicationListModel;
|
||||
}
|
||||
|
||||
bool WaydroidState::multiWindows() const
|
||||
{
|
||||
return m_multiWindows;
|
||||
|
|
|
|||
|
|
@ -6,11 +6,15 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include "waydroidapplicationlistmodel.h"
|
||||
|
||||
#include <QObject>
|
||||
|
||||
#include <qqmlregistration.h>
|
||||
#include <qtmetamacros.h>
|
||||
|
||||
class WaydroidApplicationListModel;
|
||||
|
||||
/**
|
||||
* This class provides an interface to interact with the Waydroid container,
|
||||
* including session management and property configuration.
|
||||
|
|
@ -33,6 +37,7 @@ class WaydroidState : public QObject
|
|||
Q_PROPERTY(bool multiWindows READ multiWindows WRITE setMultiWindows NOTIFY multiWindowsChanged)
|
||||
Q_PROPERTY(bool suspend READ suspend WRITE setSuspend NOTIFY suspendChanged)
|
||||
Q_PROPERTY(bool uevent READ uevent WRITE setUevent NOTIFY ueventChanged)
|
||||
Q_PROPERTY(WaydroidApplicationListModel *applicationListModel READ applicationListModel CONSTANT)
|
||||
|
||||
public:
|
||||
WaydroidState(QObject *parent = nullptr);
|
||||
|
|
@ -102,6 +107,8 @@ public:
|
|||
QString androidId() const;
|
||||
QString errorTitle() const;
|
||||
QString errorMessage() const;
|
||||
WaydroidApplicationListModel *applicationListModel() const;
|
||||
|
||||
bool multiWindows() const;
|
||||
void setMultiWindows(const bool multiWindows);
|
||||
bool suspend() const;
|
||||
|
|
@ -129,6 +136,7 @@ private:
|
|||
QString m_errorTitle{""};
|
||||
QString m_errorMessage{""};
|
||||
QString m_androidId{""};
|
||||
WaydroidApplicationListModel *m_applicationListModel{nullptr};
|
||||
|
||||
// Waydroid props. See https://docs.waydro.id/usage/waydroid-prop-options
|
||||
bool m_multiWindows{false};
|
||||
|
|
|
|||
116
kcms/waydroidintegration/ui/WaydroidApplicationsPage.qml
Normal file
116
kcms/waydroidintegration/ui/WaydroidApplicationsPage.qml
Normal file
|
|
@ -0,0 +1,116 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2025 Florian RICHER <florian.richer@protonmail.com>
|
||||
* SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
*/
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Controls as QQC2
|
||||
import QtQuick.Dialogs
|
||||
|
||||
import org.kde.kirigami 2.19 as Kirigami
|
||||
import org.kde.kcmutils as KCM
|
||||
import org.kde.kirigamiaddons.formcard 1.0 as FormCard
|
||||
import org.kde.plasma.components 3.0 as PC3
|
||||
import org.kde.plasma.private.mobileshell.waydroidintegrationplugin as AIP
|
||||
|
||||
KCM.SimpleKCM {
|
||||
id: root
|
||||
|
||||
topPadding: Kirigami.Units.largeSpacing
|
||||
bottomPadding: Kirigami.Units.largeSpacing
|
||||
leftPadding: 0
|
||||
rightPadding: 0
|
||||
|
||||
title: i18n("Waydroid applications")
|
||||
|
||||
actions: [
|
||||
Kirigami.Action {
|
||||
text: i18nc("@action:button", "Install APK")
|
||||
icon.name: "list-add"
|
||||
|
||||
onTriggered: fileDialog.open()
|
||||
}
|
||||
]
|
||||
|
||||
Connections {
|
||||
target: AIP.WaydroidState.applicationListModel
|
||||
|
||||
function onActionFinished(message: string): void {
|
||||
inlineMessage.text = message
|
||||
inlineMessage.visible = true
|
||||
inlineMessage.type = Kirigami.MessageType.Positive
|
||||
}
|
||||
|
||||
function onErrorOccurred(error: string): void {
|
||||
inlineMessage.text = error
|
||||
inlineMessage.visible = true
|
||||
inlineMessage.type = Kirigami.MessageType.Error
|
||||
}
|
||||
}
|
||||
|
||||
FileDialog {
|
||||
id: fileDialog
|
||||
nameFilters: [ "APK files (*.apk)" ]
|
||||
|
||||
onAccepted: {
|
||||
const url = new URL(selectedFile)
|
||||
if (url.protocol !== "file:") {
|
||||
inlineMessage.text = i18n("You must selected local file")
|
||||
inlineMessage.visible = true
|
||||
inlineMessage.type = Kirigami.MessageType.Error
|
||||
} else {
|
||||
AIP.WaydroidState.applicationListModel.installApk(url.pathname)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
spacing: Kirigami.Units.largeSpacing
|
||||
|
||||
Kirigami.InlineMessage {
|
||||
id: inlineMessage
|
||||
|
||||
Layout.fillWidth: true
|
||||
|
||||
visible: false
|
||||
showCloseButton: true
|
||||
}
|
||||
|
||||
FormCard.FormCard {
|
||||
Repeater {
|
||||
model: AIP.WaydroidState.applicationListModel
|
||||
|
||||
delegate: FormCard.AbstractFormDelegate {
|
||||
id: appDelegate
|
||||
|
||||
width: ListView.view.width
|
||||
|
||||
background: null
|
||||
contentItem: RowLayout {
|
||||
QQC2.Label {
|
||||
Layout.fillWidth: true
|
||||
text: model.name
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
QQC2.ToolButton {
|
||||
display: QQC2.AbstractButton.IconOnly
|
||||
text: i18nc("@action:button", "Delete the application")
|
||||
icon.name: "usermenu-delete"
|
||||
|
||||
onClicked: AIP.WaydroidState.applicationListModel.deleteApplication(model.id)
|
||||
|
||||
QQC2.ToolTip.visible: hovered
|
||||
QQC2.ToolTip.text: text
|
||||
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: contentHeight
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -42,11 +42,15 @@ ColumnLayout {
|
|||
}
|
||||
|
||||
FormCard.FormButtonDelegate {
|
||||
id: quickSettingsButton
|
||||
visible: AIP.WaydroidState.systemType === AIP.WaydroidState.Gapps
|
||||
text: i18n("Certify my device for Google Play Protect")
|
||||
onClicked: kcm.push("WaydroidGooglePlayProtectConfigurationPage.qml")
|
||||
}
|
||||
|
||||
FormCard.FormButtonDelegate {
|
||||
text: i18n("Installed applications")
|
||||
onClicked: kcm.push("WaydroidApplicationsPage.qml")
|
||||
}
|
||||
}
|
||||
|
||||
// Some informations as IP address can take time to be set by Waydroid
|
||||
|
|
|
|||
Loading…
Reference in a new issue