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
|
# SPDX-License-Identifier: BSD-2-Clause
|
||||||
|
|
||||||
ecm_add_qml_module(waydroidintegrationplugin URI org.kde.plasma.private.mobileshell.waydroidintegrationplugin GENERATE_PLUGIN_SOURCE)
|
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
|
target_link_libraries(waydroidintegrationplugin PRIVATE
|
||||||
Qt::Gui
|
Qt::Gui
|
||||||
|
|
|
||||||
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 "waydroidstate.h"
|
||||||
#include "waydroidintegrationplugin_debug.h"
|
#include "waydroidintegrationplugin_debug.h"
|
||||||
|
#include "waydroidshared.h"
|
||||||
|
|
||||||
#include <QClipboard>
|
#include <QClipboard>
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
|
|
@ -22,7 +23,6 @@
|
||||||
|
|
||||||
using namespace Qt::StringLiterals;
|
using namespace Qt::StringLiterals;
|
||||||
|
|
||||||
#define WAYDROID_COMMAND "waydroid"
|
|
||||||
#define MULTI_WINDOWS_PROP_KEY "persist.waydroid.multi_windows"
|
#define MULTI_WINDOWS_PROP_KEY "persist.waydroid.multi_windows"
|
||||||
#define SUSPEND_PROP_KEY "persist.waydroid.suspend"
|
#define SUSPEND_PROP_KEY "persist.waydroid.suspend"
|
||||||
#define UEVENT_PROP_KEY "persist.waydroid.uevent"
|
#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)
|
WaydroidState::WaydroidState(QObject *parent)
|
||||||
: QObject{parent}
|
: QObject{parent}
|
||||||
|
, m_applicationListModel{new WaydroidApplicationListModel{this}}
|
||||||
{
|
{
|
||||||
// Connect it-self to auto-refresh when required status has changed
|
// Connect it-self to auto-refresh when required status has changed
|
||||||
connect(this, &WaydroidState::statusChanged, this, &WaydroidState::refreshSessionInfo);
|
connect(this, &WaydroidState::statusChanged, this, &WaydroidState::refreshSessionInfo);
|
||||||
|
|
@ -335,6 +336,11 @@ QString WaydroidState::androidId() const
|
||||||
return m_androidId;
|
return m_androidId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
WaydroidApplicationListModel *WaydroidState::applicationListModel() const
|
||||||
|
{
|
||||||
|
return m_applicationListModel;
|
||||||
|
}
|
||||||
|
|
||||||
bool WaydroidState::multiWindows() const
|
bool WaydroidState::multiWindows() const
|
||||||
{
|
{
|
||||||
return m_multiWindows;
|
return m_multiWindows;
|
||||||
|
|
|
||||||
|
|
@ -6,11 +6,15 @@
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "waydroidapplicationlistmodel.h"
|
||||||
|
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
|
|
||||||
#include <qqmlregistration.h>
|
#include <qqmlregistration.h>
|
||||||
#include <qtmetamacros.h>
|
#include <qtmetamacros.h>
|
||||||
|
|
||||||
|
class WaydroidApplicationListModel;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class provides an interface to interact with the Waydroid container,
|
* This class provides an interface to interact with the Waydroid container,
|
||||||
* including session management and property configuration.
|
* 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 multiWindows READ multiWindows WRITE setMultiWindows NOTIFY multiWindowsChanged)
|
||||||
Q_PROPERTY(bool suspend READ suspend WRITE setSuspend NOTIFY suspendChanged)
|
Q_PROPERTY(bool suspend READ suspend WRITE setSuspend NOTIFY suspendChanged)
|
||||||
Q_PROPERTY(bool uevent READ uevent WRITE setUevent NOTIFY ueventChanged)
|
Q_PROPERTY(bool uevent READ uevent WRITE setUevent NOTIFY ueventChanged)
|
||||||
|
Q_PROPERTY(WaydroidApplicationListModel *applicationListModel READ applicationListModel CONSTANT)
|
||||||
|
|
||||||
public:
|
public:
|
||||||
WaydroidState(QObject *parent = nullptr);
|
WaydroidState(QObject *parent = nullptr);
|
||||||
|
|
@ -102,6 +107,8 @@ public:
|
||||||
QString androidId() const;
|
QString androidId() const;
|
||||||
QString errorTitle() const;
|
QString errorTitle() const;
|
||||||
QString errorMessage() const;
|
QString errorMessage() const;
|
||||||
|
WaydroidApplicationListModel *applicationListModel() const;
|
||||||
|
|
||||||
bool multiWindows() const;
|
bool multiWindows() const;
|
||||||
void setMultiWindows(const bool multiWindows);
|
void setMultiWindows(const bool multiWindows);
|
||||||
bool suspend() const;
|
bool suspend() const;
|
||||||
|
|
@ -129,6 +136,7 @@ private:
|
||||||
QString m_errorTitle{""};
|
QString m_errorTitle{""};
|
||||||
QString m_errorMessage{""};
|
QString m_errorMessage{""};
|
||||||
QString m_androidId{""};
|
QString m_androidId{""};
|
||||||
|
WaydroidApplicationListModel *m_applicationListModel{nullptr};
|
||||||
|
|
||||||
// Waydroid props. See https://docs.waydro.id/usage/waydroid-prop-options
|
// Waydroid props. See https://docs.waydro.id/usage/waydroid-prop-options
|
||||||
bool m_multiWindows{false};
|
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 {
|
FormCard.FormButtonDelegate {
|
||||||
id: quickSettingsButton
|
|
||||||
visible: AIP.WaydroidState.systemType === AIP.WaydroidState.Gapps
|
visible: AIP.WaydroidState.systemType === AIP.WaydroidState.Gapps
|
||||||
text: i18n("Certify my device for Google Play Protect")
|
text: i18n("Certify my device for Google Play Protect")
|
||||||
onClicked: kcm.push("WaydroidGooglePlayProtectConfigurationPage.qml")
|
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
|
// Some informations as IP address can take time to be set by Waydroid
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue