mirror of
https://invent.kde.org/marcoa/shift-shell.git
synced 2026-04-26 14:23:09 +00:00
waydroid: Migrate to DBus backend implementations
This commit is contained in:
parent
6f862e24c3
commit
7ebb4aa37c
20 changed files with 1622 additions and 744 deletions
|
|
@ -1,10 +1,51 @@
|
|||
# SPDX-FileCopyrightText: 2025 Florian RICHER <florian.richer@protonmail.com>
|
||||
# SPDX-License-Identifier: BSD-2-Clause
|
||||
|
||||
set(waydroidintegrationplugin_SRCS
|
||||
waydroidapplicationdbusobject.cpp
|
||||
waydroidapplicationdbusclient.cpp
|
||||
waydroidapplicationlistmodel.cpp
|
||||
waydroiddbusclient.cpp
|
||||
waydroiddbusobject.cpp
|
||||
)
|
||||
|
||||
qt_generate_dbus_interface(
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/waydroiddbusobject.h
|
||||
org.kde.plasmashell.Waydroid.xml
|
||||
OPTIONS -s -m -P
|
||||
)
|
||||
qt_generate_dbus_interface(
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/waydroidapplicationdbusobject.h
|
||||
org.kde.plasmashell.WaydroidApplication.xml
|
||||
OPTIONS -s -m -P
|
||||
)
|
||||
qt_add_dbus_adaptor(waydroidintegrationplugin_SRCS
|
||||
${CMAKE_CURRENT_BINARY_DIR}/org.kde.plasmashell.Waydroid.xml
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/waydroiddbusobject.h WaydroidDBusObject
|
||||
)
|
||||
qt_add_dbus_adaptor(waydroidintegrationplugin_SRCS
|
||||
${CMAKE_CURRENT_BINARY_DIR}/org.kde.plasmashell.WaydroidApplication.xml
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/waydroidapplicationdbusobject.h WaydroidApplicationDBusObject
|
||||
)
|
||||
qt_add_dbus_interface(waydroidintegrationplugin_SRCS
|
||||
${CMAKE_CURRENT_BINARY_DIR}/org.kde.plasmashell.Waydroid.xml
|
||||
plasmashellwaydroidinterface
|
||||
)
|
||||
qt_add_dbus_interface(waydroidintegrationplugin_SRCS
|
||||
${CMAKE_CURRENT_BINARY_DIR}/org.kde.plasmashell.WaydroidApplication.xml
|
||||
plasmashellwaydroidapplicationinterface
|
||||
)
|
||||
install(FILES
|
||||
${CMAKE_CURRENT_BINARY_DIR}/org.kde.plasmashell.Waydroid.xml
|
||||
${CMAKE_CURRENT_BINARY_DIR}/org.kde.plasmashell.WaydroidApplication.xml
|
||||
DESTINATION ${KDE_INSTALL_DBUSINTERFACEDIR}
|
||||
)
|
||||
|
||||
ecm_add_qml_module(waydroidintegrationplugin URI org.kde.plasma.private.mobileshell.waydroidintegrationplugin GENERATE_PLUGIN_SOURCE)
|
||||
target_sources(waydroidintegrationplugin PRIVATE waydroidstate.cpp waydroidapplication.cpp waydroidapplicationlistmodel.cpp)
|
||||
target_sources(waydroidintegrationplugin PRIVATE ${waydroidintegrationplugin_SRCS} ${RESOURCES})
|
||||
|
||||
target_link_libraries(waydroidintegrationplugin PRIVATE
|
||||
Qt::DBus
|
||||
Qt::Gui
|
||||
Qt::Qml
|
||||
Qt::Quick
|
||||
|
|
@ -12,6 +53,7 @@ target_link_libraries(waydroidintegrationplugin PRIVATE
|
|||
KF6::ConfigCore
|
||||
KF6::I18n
|
||||
QCoro::Core
|
||||
QCoro::DBus
|
||||
QCoro::Qml
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,69 +0,0 @@
|
|||
/*
|
||||
* 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;
|
||||
}
|
||||
|
|
@ -1,39 +0,0 @@
|
|||
/*
|
||||
* 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,103 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2025 Florian RICHER <florian.richer@protonmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
|
||||
#include "waydroidapplicationdbusclient.h"
|
||||
|
||||
#include <QDBusConnection>
|
||||
|
||||
using namespace Qt::StringLiterals;
|
||||
|
||||
WaydroidApplicationDBusClient::WaydroidApplicationDBusClient(const QDBusObjectPath &objectPath, QObject *parent)
|
||||
: QObject{parent}
|
||||
, m_objectPath{objectPath}
|
||||
, m_interface{new OrgKdePlasmashellWaydroidApplicationInterface{u"org.kde.plasmashell"_s, objectPath.path(), QDBusConnection::sessionBus(), this}}
|
||||
, m_watcher{new QDBusServiceWatcher{u"org.kde.plasmashell"_s, QDBusConnection::sessionBus(), QDBusServiceWatcher::WatchForOwnerChange, this}}
|
||||
{
|
||||
// Check if the service is already running
|
||||
if (QDBusConnection::sessionBus().interface()->isServiceRegistered(u"org.kde.plasmashell"_s)) {
|
||||
m_connected = true;
|
||||
if (m_interface->isValid()) {
|
||||
connectSignals();
|
||||
}
|
||||
}
|
||||
|
||||
connect(m_watcher, &QDBusServiceWatcher::serviceOwnerChanged, this, [this](const QString &service, const QString &oldOwner, const QString &newOwner) {
|
||||
if (service == u"org.kde.plasmashell"_s) {
|
||||
if (newOwner.isEmpty()) {
|
||||
// Service stopped
|
||||
m_connected = false;
|
||||
} else if (oldOwner.isEmpty()) {
|
||||
// Service started
|
||||
m_connected = true;
|
||||
if (m_interface->isValid()) {
|
||||
connectSignals();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void WaydroidApplicationDBusClient::connectSignals()
|
||||
{
|
||||
// Initialize properties
|
||||
updateName();
|
||||
updatePackageName();
|
||||
}
|
||||
|
||||
QString WaydroidApplicationDBusClient::name() const
|
||||
{
|
||||
return m_name;
|
||||
}
|
||||
|
||||
QString WaydroidApplicationDBusClient::packageName() const
|
||||
{
|
||||
return m_packageName;
|
||||
}
|
||||
|
||||
QDBusObjectPath WaydroidApplicationDBusClient::objectPath() const
|
||||
{
|
||||
return m_objectPath;
|
||||
}
|
||||
|
||||
void WaydroidApplicationDBusClient::updateName()
|
||||
{
|
||||
if (!m_connected) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto reply = m_interface->name();
|
||||
auto watcher = new QDBusPendingCallWatcher(reply, this);
|
||||
|
||||
connect(watcher, &QDBusPendingCallWatcher::finished, this, [this](auto watcher) {
|
||||
QDBusPendingReply<QString> reply = *watcher;
|
||||
const auto name = reply.argumentAt<0>();
|
||||
|
||||
if (m_name != name) {
|
||||
m_name = name;
|
||||
Q_EMIT nameChanged();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void WaydroidApplicationDBusClient::updatePackageName()
|
||||
{
|
||||
if (!m_connected) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto reply = m_interface->name();
|
||||
auto watcher = new QDBusPendingCallWatcher(reply, this);
|
||||
|
||||
connect(watcher, &QDBusPendingCallWatcher::finished, this, [this](auto watcher) {
|
||||
QDBusPendingReply<QString> reply = *watcher;
|
||||
const auto packageName = reply.argumentAt<0>();
|
||||
|
||||
if (m_packageName != packageName) {
|
||||
m_packageName = packageName;
|
||||
Q_EMIT packageNameChanged();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2025 Florian RICHER <florian.richer@protonmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "plasmashellwaydroidapplicationinterface.h"
|
||||
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
|
||||
#include <qqmlregistration.h>
|
||||
|
||||
/**
|
||||
* This class provides a DBus client interface for individual Waydroid application.
|
||||
* It connects to WaydroidApplicationDBusObject instances via DBus.
|
||||
*
|
||||
* @author Florian RICHER <florian.richer@protonmail.com>
|
||||
*/
|
||||
class WaydroidApplicationDBusClient : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
QML_UNCREATABLE("")
|
||||
QML_ELEMENT
|
||||
|
||||
Q_PROPERTY(QString name READ name NOTIFY nameChanged)
|
||||
Q_PROPERTY(QString packageName READ packageName NOTIFY packageNameChanged)
|
||||
|
||||
public:
|
||||
typedef std::shared_ptr<WaydroidApplicationDBusClient> Ptr;
|
||||
|
||||
explicit WaydroidApplicationDBusClient(const QDBusObjectPath &objectPath, QObject *parent = nullptr);
|
||||
|
||||
QString name() const;
|
||||
QString packageName() const;
|
||||
QDBusObjectPath objectPath() const;
|
||||
|
||||
private Q_SLOTS:
|
||||
void updateName();
|
||||
void updatePackageName();
|
||||
|
||||
Q_SIGNALS:
|
||||
void nameChanged();
|
||||
void packageNameChanged();
|
||||
|
||||
private:
|
||||
OrgKdePlasmashellWaydroidApplicationInterface *m_interface;
|
||||
QDBusServiceWatcher *m_watcher;
|
||||
|
||||
QString m_packageName;
|
||||
QString m_name;
|
||||
QDBusObjectPath m_objectPath;
|
||||
bool m_connected{false};
|
||||
|
||||
void connectSignals();
|
||||
};
|
||||
|
|
@ -0,0 +1,110 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2025 Florian RICHER <florian.richer@protonmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
|
||||
#include "waydroidapplicationdbusobject.h"
|
||||
#include "waydroidapplicationadaptor.h"
|
||||
#include "waydroidintegrationplugin_debug.h"
|
||||
|
||||
#include <QDBusConnection>
|
||||
#include <QLoggingCategory>
|
||||
#include <QRegularExpression>
|
||||
|
||||
using namespace Qt::StringLiterals;
|
||||
|
||||
static const QRegularExpression nameRegExp(u"^Name:\\s*(\\S+)"_s);
|
||||
static const QRegularExpression packageNameRegExp(u"^packageName:\\s*(\\S+)"_s);
|
||||
|
||||
WaydroidApplicationDBusObject::WaydroidApplicationDBusObject(QObject *parent)
|
||||
: QObject{parent}
|
||||
{
|
||||
}
|
||||
|
||||
void WaydroidApplicationDBusObject::registerObject()
|
||||
{
|
||||
if (!m_dbusInitialized) {
|
||||
new WaydroidApplicationAdaptor{this};
|
||||
QString sanitizedPackageName = m_packageName;
|
||||
sanitizedPackageName.replace(".", "_");
|
||||
const QString objectPath = u"/WaydroidApplication/%1"_s.arg(sanitizedPackageName);
|
||||
QDBusConnection::sessionBus().registerObject(objectPath, this);
|
||||
m_objectPath = QDBusObjectPath(objectPath);
|
||||
}
|
||||
}
|
||||
|
||||
void WaydroidApplicationDBusObject::unregisterObject()
|
||||
{
|
||||
if (m_dbusInitialized) {
|
||||
QDBusConnection::sessionBus().unregisterObject(m_objectPath.path());
|
||||
m_dbusInitialized = false;
|
||||
}
|
||||
}
|
||||
|
||||
QDBusObjectPath WaydroidApplicationDBusObject::objectPath() const
|
||||
{
|
||||
return m_objectPath;
|
||||
}
|
||||
|
||||
WaydroidApplicationDBusObject::Ptr WaydroidApplicationDBusObject::parseApplicationFromWaydroidLog(QTextStream &inFile)
|
||||
{
|
||||
const QString line = inFile.readLine();
|
||||
const QRegularExpressionMatch nameMatch = nameRegExp.match(line);
|
||||
|
||||
if (!nameMatch.hasMatch() || nameMatch.lastCapturedIndex() == 0) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto app = std::make_shared<WaydroidApplicationDBusObject>();
|
||||
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;
|
||||
}
|
||||
|
||||
QList<WaydroidApplicationDBusObject::Ptr> WaydroidApplicationDBusObject::parseApplicationsFromWaydroidLog(QTextStream &inFile)
|
||||
{
|
||||
QList<Ptr> applications;
|
||||
while (!inFile.atEnd()) {
|
||||
const auto app = parseApplicationFromWaydroidLog(inFile);
|
||||
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;
|
||||
}
|
||||
|
||||
QString WaydroidApplicationDBusObject::name() const
|
||||
{
|
||||
return m_name;
|
||||
}
|
||||
|
||||
QString WaydroidApplicationDBusObject::packageName() const
|
||||
{
|
||||
return m_packageName;
|
||||
}
|
||||
|
|
@ -0,0 +1,64 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2025 Florian RICHER <florian.richer@protonmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QDBusObjectPath>
|
||||
#include <QList>
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
|
||||
#include <qqmlregistration.h>
|
||||
|
||||
/**
|
||||
* This class provides a DBus interface for individual Waydroid applications.
|
||||
* Each application gets its own DBus object registered at a unique path.
|
||||
*
|
||||
* @author Florian RICHER <florian.richer@protonmail.com>
|
||||
*/
|
||||
class WaydroidApplicationDBusObject : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
QML_ELEMENT
|
||||
Q_CLASSINFO("D-Bus Interface", "org.kde.plasmashell.WaydroidApplication")
|
||||
|
||||
public:
|
||||
typedef std::shared_ptr<WaydroidApplicationDBusObject> Ptr;
|
||||
|
||||
explicit WaydroidApplicationDBusObject(QObject *parent = nullptr);
|
||||
|
||||
void registerObject();
|
||||
void unregisterObject();
|
||||
[[nodiscard]] QDBusObjectPath objectPath() const;
|
||||
|
||||
/**
|
||||
* @brief Read one application 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.
|
||||
* @return One parsed application DBus object, or std::nullopt if parsing failed
|
||||
*/
|
||||
static Ptr parseApplicationFromWaydroidLog(QTextStream &inFile);
|
||||
|
||||
/**
|
||||
* @brief Read all applications from "waydroid app list" command output log through QTextStream.
|
||||
*
|
||||
* @param inFile The QTextStream used to read line by line the Waydroid logs.
|
||||
* @return All parsed application DBus objects
|
||||
*/
|
||||
static QList<Ptr> parseApplicationsFromWaydroidLog(QTextStream &inFile);
|
||||
|
||||
public Q_SLOTS:
|
||||
Q_SCRIPTABLE QString name() const;
|
||||
Q_SCRIPTABLE QString packageName() const;
|
||||
|
||||
private:
|
||||
bool m_dbusInitialized{false};
|
||||
QDBusObjectPath m_objectPath;
|
||||
QString m_name;
|
||||
QString m_packageName;
|
||||
};
|
||||
|
|
@ -5,123 +5,79 @@
|
|||
*/
|
||||
|
||||
#include "waydroidapplicationlistmodel.h"
|
||||
#include "waydroidintegrationplugin_debug.h"
|
||||
#include "waydroidshared.h"
|
||||
|
||||
#include <QLoggingCategory>
|
||||
#include <QProcess>
|
||||
#include <QStringLiteral>
|
||||
#include "waydroidapplicationdbusclient.h"
|
||||
#include "waydroiddbusclient.h"
|
||||
|
||||
#include <KLocalizedString>
|
||||
|
||||
using namespace Qt::StringLiterals;
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
WaydroidApplicationListModel::WaydroidApplicationListModel(WaydroidState *parent)
|
||||
WaydroidApplicationListModel::WaydroidApplicationListModel(WaydroidDBusClient *parent)
|
||||
: QAbstractListModel{parent}
|
||||
, m_waydroidState{parent}
|
||||
, m_refreshTimer{new QTimer{this}}
|
||||
, m_waydroidDBusClient{parent}
|
||||
{
|
||||
// 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::loadApplications(const QList<WaydroidApplication::Ptr> applications)
|
||||
void WaydroidApplicationListModel::initializeApplications(const QList<QDBusObjectPath> &applicationObjectPaths)
|
||||
{
|
||||
if (m_waydroidState->sessionStatus() != WaydroidState::SessionRunning) {
|
||||
if (!m_applications.isEmpty()) {
|
||||
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> toInsert;
|
||||
|
||||
for (const WaydroidApplication::Ptr &application : applications) {
|
||||
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 indices 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();
|
||||
beginResetModel();
|
||||
for (const QDBusObjectPath &applicationObjectPath : applicationObjectPaths) {
|
||||
auto client = std::make_shared<WaydroidApplicationDBusClient>(applicationObjectPath, this);
|
||||
m_applications.append(client);
|
||||
}
|
||||
endResetModel();
|
||||
}
|
||||
|
||||
void WaydroidApplicationListModel::refreshApplications()
|
||||
void WaydroidApplicationListModel::addApplication(const QDBusObjectPath &objectPath)
|
||||
{
|
||||
QList<WaydroidApplication::Ptr> applications;
|
||||
|
||||
QStringList arguments = {u"app"_s, u"list"_s};
|
||||
|
||||
QProcess *process = new QProcess(m_waydroidState);
|
||||
process->start(WAYDROID_COMMAND, arguments);
|
||||
|
||||
connect(process, &QProcess::finished, this, [this, process](int exitCode, QProcess::ExitStatus exitStatus) {
|
||||
if (exitCode != 0 || exitStatus == QProcess::ExitStatus::CrashExit) {
|
||||
qCWarning(WAYDROIDINTEGRATIONPLUGIN) << "Failed to run waydroid app list command: " << process->readAllStandardError();
|
||||
return;
|
||||
}
|
||||
|
||||
const QByteArray data = process->readAllStandardOutput();
|
||||
if (data.isEmpty()) {
|
||||
qCWarning(WAYDROIDINTEGRATIONPLUGIN) << "Empty data: " << process->readAllStandardError();
|
||||
return;
|
||||
}
|
||||
|
||||
qCDebug(WAYDROIDINTEGRATIONPLUGIN) << "Waydroid output: " << data;
|
||||
QTextStream output = QTextStream(data);
|
||||
|
||||
QList<WaydroidApplication::Ptr> applications;
|
||||
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);
|
||||
}
|
||||
|
||||
loadApplications(applications);
|
||||
beginInsertRows({}, m_applications.size(), m_applications.size());
|
||||
auto client = std::make_shared<WaydroidApplicationDBusClient>(objectPath, this);
|
||||
connect(client.get(), &WaydroidApplicationDBusClient::nameChanged, this, [this, objectPath] {
|
||||
updateApplication(objectPath, {Qt::DisplayRole, DelegateRole, NameRole});
|
||||
});
|
||||
connect(client.get(), &WaydroidApplicationDBusClient::packageNameChanged, this, [this, objectPath] {
|
||||
updateApplication(objectPath, {Qt::DisplayRole, DelegateRole, IdRole});
|
||||
});
|
||||
m_applications.append(client);
|
||||
endInsertRows();
|
||||
}
|
||||
|
||||
void WaydroidApplicationListModel::updateApplication(const QDBusObjectPath &objectPath, const QList<int> &roles)
|
||||
{
|
||||
const auto it = std::ranges::find_if(m_applications, [objectPath](auto app) {
|
||||
return app->objectPath() == objectPath;
|
||||
});
|
||||
|
||||
if (it == m_applications.end()) {
|
||||
return;
|
||||
}
|
||||
|
||||
int ind = std::distance(m_applications.begin(), it);
|
||||
QModelIndex index = createIndex(ind, 0);
|
||||
Q_EMIT dataChanged(index, index, roles);
|
||||
}
|
||||
|
||||
void WaydroidApplicationListModel::removeApplication(const QDBusObjectPath &objectPath)
|
||||
{
|
||||
const auto it = std::ranges::find_if(m_applications, [objectPath](auto app) {
|
||||
return app->objectPath() == objectPath;
|
||||
});
|
||||
|
||||
if (it == m_applications.end()) {
|
||||
return;
|
||||
}
|
||||
|
||||
int ind = std::distance(m_applications.begin(), it);
|
||||
beginRemoveRows({}, ind, ind);
|
||||
m_applications.erase(it);
|
||||
endRemoveRows();
|
||||
}
|
||||
|
||||
QHash<int, QByteArray> WaydroidApplicationListModel::roleNames() const
|
||||
|
|
@ -135,7 +91,7 @@ QVariant WaydroidApplicationListModel::data(const QModelIndex &index, int role)
|
|||
return QVariant();
|
||||
}
|
||||
|
||||
WaydroidApplication::Ptr app = m_applications.at(index.row());
|
||||
WaydroidApplicationDBusClient::Ptr app = m_applications.at(index.row());
|
||||
|
||||
switch (role) {
|
||||
case Qt::DisplayRole:
|
||||
|
|
@ -158,43 +114,3 @@ int WaydroidApplicationListModel::rowCount(const QModelIndex &parent) const
|
|||
|
||||
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 occurred 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 occurred during uninstallation of " << appId << ": " << errorLog;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
@ -6,14 +6,13 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include "waydroidapplication.h"
|
||||
#include "waydroidstate.h"
|
||||
#include "waydroidapplicationdbusclient.h"
|
||||
|
||||
#include <QAbstractListModel>
|
||||
#include <QObject>
|
||||
#include <QTimer>
|
||||
|
||||
class WaydroidState;
|
||||
class WaydroidDBusClient;
|
||||
|
||||
class WaydroidApplicationListModel : public QAbstractListModel
|
||||
{
|
||||
|
|
@ -26,25 +25,23 @@ public:
|
|||
IdRole
|
||||
};
|
||||
|
||||
WaydroidApplicationListModel(WaydroidState *parent = nullptr);
|
||||
explicit WaydroidApplicationListModel(WaydroidDBusClient *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);
|
||||
void initializeApplications(const QList<QDBusObjectPath> &applicationObjectPaths);
|
||||
|
||||
Q_SIGNALS:
|
||||
void actionFinished(const QString message);
|
||||
void errorOccurred(const QString message);
|
||||
public Q_SLOTS:
|
||||
void addApplication(const QDBusObjectPath &objectPath);
|
||||
void removeApplication(const QDBusObjectPath &objectPath);
|
||||
|
||||
private:
|
||||
WaydroidState *m_waydroidState{nullptr};
|
||||
QList<WaydroidApplication::Ptr> m_applications;
|
||||
WaydroidDBusClient *m_waydroidDBusClient{nullptr};
|
||||
QList<WaydroidApplicationDBusClient::Ptr> m_applications;
|
||||
QTimer *m_refreshTimer{nullptr};
|
||||
|
||||
void loadApplications(const QList<WaydroidApplication::Ptr> applications);
|
||||
void refreshApplications();
|
||||
void updateApplication(const QDBusObjectPath &objectPath, const QList<int> &roles);
|
||||
};
|
||||
404
components/waydroidintegrationplugin/waydroiddbusclient.cpp
Normal file
404
components/waydroidintegrationplugin/waydroiddbusclient.cpp
Normal file
|
|
@ -0,0 +1,404 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2025 Florian RICHER <florian.richer@protonmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
|
||||
#include "waydroiddbusclient.h"
|
||||
|
||||
#include <QClipboard>
|
||||
#include <QCoroDBusPendingReply>
|
||||
#include <QGuiApplication>
|
||||
|
||||
using namespace Qt::StringLiterals;
|
||||
|
||||
WaydroidDBusClient::WaydroidDBusClient(QObject *parent)
|
||||
: QObject{parent}
|
||||
, m_interface{new OrgKdePlasmashellWaydroidInterface{u"org.kde.plasmashell"_s, u"/Waydroid"_s, QDBusConnection::sessionBus(), this}}
|
||||
, m_watcher{new QDBusServiceWatcher{u"org.kde.plasmashell"_s, QDBusConnection::sessionBus(), QDBusServiceWatcher::WatchForOwnerChange, this}}
|
||||
, m_applicationListModel{new WaydroidApplicationListModel{this}}
|
||||
{
|
||||
// Check if the service is already running
|
||||
if (QDBusConnection::sessionBus().interface()->isServiceRegistered(u"org.kde.plasmashell"_s)) {
|
||||
m_connected = true;
|
||||
if (m_interface->isValid()) {
|
||||
connectSignals();
|
||||
}
|
||||
}
|
||||
|
||||
connect(m_watcher, &QDBusServiceWatcher::serviceOwnerChanged, this, [this](const QString &service, const QString &oldOwner, const QString &newOwner) {
|
||||
if (service == u"org.kde.plasmashell"_s) {
|
||||
if (newOwner.isEmpty()) {
|
||||
// Service stopped
|
||||
m_connected = false;
|
||||
} else if (oldOwner.isEmpty()) {
|
||||
// Service started
|
||||
m_connected = true;
|
||||
if (m_interface->isValid()) {
|
||||
connectSignals();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void WaydroidDBusClient::connectSignals()
|
||||
{
|
||||
connect(m_interface, &OrgKdePlasmashellWaydroidInterface::statusChanged, this, &WaydroidDBusClient::updateStatus);
|
||||
connect(m_interface, &OrgKdePlasmashellWaydroidInterface::downloadStatusChanged, this, [this](double downloaded, double total, double speed) {
|
||||
Q_EMIT downloadStatusChanged(downloaded, total, speed);
|
||||
});
|
||||
connect(m_interface, &OrgKdePlasmashellWaydroidInterface::sessionStatusChanged, this, &WaydroidDBusClient::updateSessionStatus);
|
||||
connect(m_interface, &OrgKdePlasmashellWaydroidInterface::systemTypeChanged, this, &WaydroidDBusClient::updateSystemType);
|
||||
connect(m_interface, &OrgKdePlasmashellWaydroidInterface::ipAddressChanged, this, &WaydroidDBusClient::updateIpAddress);
|
||||
connect(m_interface, &OrgKdePlasmashellWaydroidInterface::androidIdChanged, this, &WaydroidDBusClient::updateAndroidId);
|
||||
connect(m_interface, &OrgKdePlasmashellWaydroidInterface::multiWindowsChanged, this, &WaydroidDBusClient::updateMultiWindows);
|
||||
connect(m_interface, &OrgKdePlasmashellWaydroidInterface::suspendChanged, this, &WaydroidDBusClient::updateSuspend);
|
||||
connect(m_interface, &OrgKdePlasmashellWaydroidInterface::ueventChanged, this, &WaydroidDBusClient::updateUevent);
|
||||
connect(m_interface, &OrgKdePlasmashellWaydroidInterface::actionFinished, this, [this](const QString message) {
|
||||
Q_EMIT actionFinished(message);
|
||||
});
|
||||
connect(m_interface, &OrgKdePlasmashellWaydroidInterface::actionFailed, this, [this](const QString message) {
|
||||
Q_EMIT actionFailed(message);
|
||||
});
|
||||
connect(m_interface, &OrgKdePlasmashellWaydroidInterface::errorOccurred, this, [this](const QString title, const QString message) {
|
||||
Q_EMIT errorOccurred(title, message);
|
||||
});
|
||||
|
||||
initializeApplicationListModel();
|
||||
updateStatus();
|
||||
updateSessionStatus();
|
||||
updateSystemType();
|
||||
updateIpAddress();
|
||||
updateAndroidId();
|
||||
updateMultiWindows();
|
||||
updateSuspend();
|
||||
updateUevent();
|
||||
}
|
||||
|
||||
void WaydroidDBusClient::initializeApplicationListModel()
|
||||
{
|
||||
auto reply = m_interface->applications();
|
||||
auto watcher = new QDBusPendingCallWatcher(reply, this);
|
||||
|
||||
connect(watcher, &QDBusPendingCallWatcher::finished, this, [this](auto watcher) {
|
||||
QDBusPendingReply<QList<QDBusObjectPath>> reply = *watcher;
|
||||
const auto applications = reply.argumentAt<0>();
|
||||
|
||||
m_applicationListModel->initializeApplications(applications);
|
||||
|
||||
// Connect applicationListModel signals only when applications is synced
|
||||
connect(m_interface, &OrgKdePlasmashellWaydroidInterface::applicationAdded, m_applicationListModel, &WaydroidApplicationListModel::addApplication);
|
||||
connect(m_interface, &OrgKdePlasmashellWaydroidInterface::applicationRemoved, m_applicationListModel, &WaydroidApplicationListModel::removeApplication);
|
||||
});
|
||||
}
|
||||
|
||||
WaydroidDBusClient::Status WaydroidDBusClient::status() const
|
||||
{
|
||||
return m_status;
|
||||
}
|
||||
|
||||
WaydroidDBusClient::SessionStatus WaydroidDBusClient::sessionStatus() const
|
||||
{
|
||||
return m_sessionStatus;
|
||||
}
|
||||
|
||||
WaydroidDBusClient::SystemType WaydroidDBusClient::systemType() const
|
||||
{
|
||||
return m_systemType;
|
||||
}
|
||||
|
||||
QString WaydroidDBusClient::ipAddress() const
|
||||
{
|
||||
return m_ipAddress;
|
||||
}
|
||||
|
||||
QString WaydroidDBusClient::androidId() const
|
||||
{
|
||||
return m_androidId;
|
||||
}
|
||||
|
||||
WaydroidApplicationListModel *WaydroidDBusClient::applicationListModel() const
|
||||
{
|
||||
return m_applicationListModel;
|
||||
}
|
||||
|
||||
QCoro::Task<void> WaydroidDBusClient::setMultiWindowsTask(const bool multiWindows)
|
||||
{
|
||||
auto pendingReply = m_interface->setMultiWindows(multiWindows);
|
||||
co_await pendingReply;
|
||||
}
|
||||
|
||||
QCoro::QmlTask WaydroidDBusClient::setMultiWindows(const bool multiWindows)
|
||||
{
|
||||
return setMultiWindowsTask(multiWindows);
|
||||
}
|
||||
|
||||
bool WaydroidDBusClient::multiWindows() const
|
||||
{
|
||||
return m_multiWindows;
|
||||
}
|
||||
|
||||
QCoro::Task<void> WaydroidDBusClient::setSuspendTask(const bool suspend)
|
||||
{
|
||||
auto pendingReply = m_interface->setSuspend(suspend);
|
||||
co_await pendingReply;
|
||||
}
|
||||
|
||||
QCoro::QmlTask WaydroidDBusClient::setSuspend(const bool suspend)
|
||||
{
|
||||
return setSuspendTask(suspend);
|
||||
}
|
||||
|
||||
bool WaydroidDBusClient::suspend() const
|
||||
{
|
||||
return m_suspend;
|
||||
}
|
||||
|
||||
QCoro::Task<void> WaydroidDBusClient::setUeventTask(const bool uevent)
|
||||
{
|
||||
auto pendingReply = m_interface->setUevent(uevent);
|
||||
co_await pendingReply;
|
||||
}
|
||||
|
||||
QCoro::QmlTask WaydroidDBusClient::setUevent(const bool multiWindows)
|
||||
{
|
||||
return setUeventTask(multiWindows);
|
||||
}
|
||||
|
||||
QCoro::Task<void> WaydroidDBusClient::refreshSessionInfoTask()
|
||||
{
|
||||
auto pendingReply = m_interface->refreshSessionInfo();
|
||||
co_await pendingReply;
|
||||
}
|
||||
|
||||
QCoro::QmlTask WaydroidDBusClient::refreshSessionInfo()
|
||||
{
|
||||
return refreshSessionInfoTask();
|
||||
}
|
||||
|
||||
QCoro::Task<void> WaydroidDBusClient::refreshAndroidIdTask()
|
||||
{
|
||||
auto pendingReply = m_interface->refreshAndroidId();
|
||||
co_await pendingReply;
|
||||
}
|
||||
|
||||
QCoro::QmlTask WaydroidDBusClient::refreshAndroidId()
|
||||
{
|
||||
return refreshAndroidIdTask();
|
||||
}
|
||||
|
||||
QCoro::Task<void> WaydroidDBusClient::refreshApplicationsTask()
|
||||
{
|
||||
auto pendingReply = m_interface->refreshApplications();
|
||||
co_await pendingReply;
|
||||
}
|
||||
|
||||
QCoro::QmlTask WaydroidDBusClient::refreshApplications()
|
||||
{
|
||||
return refreshApplicationsTask();
|
||||
}
|
||||
|
||||
bool WaydroidDBusClient::uevent() const
|
||||
{
|
||||
return m_uevent;
|
||||
}
|
||||
|
||||
QCoro::Task<void> WaydroidDBusClient::initializeTask(const SystemType systemType, const RomType romType, const bool forced)
|
||||
{
|
||||
auto pendingReply = m_interface->initialize(systemType, romType, forced);
|
||||
co_await pendingReply;
|
||||
}
|
||||
|
||||
QCoro::QmlTask WaydroidDBusClient::initialize(const SystemType systemType, const RomType romType, const bool forced)
|
||||
{
|
||||
return initializeTask(systemType, romType, forced);
|
||||
}
|
||||
|
||||
QCoro::Task<void> WaydroidDBusClient::startSessionTask()
|
||||
{
|
||||
auto pendingReply = m_interface->startSession();
|
||||
co_await pendingReply;
|
||||
}
|
||||
|
||||
QCoro::QmlTask WaydroidDBusClient::startSession()
|
||||
{
|
||||
return startSessionTask();
|
||||
}
|
||||
|
||||
QCoro::Task<void> WaydroidDBusClient::stopSessionTask()
|
||||
{
|
||||
auto pendingReply = m_interface->stopSession();
|
||||
co_await pendingReply;
|
||||
}
|
||||
|
||||
QCoro::QmlTask WaydroidDBusClient::stopSession()
|
||||
{
|
||||
return stopSessionTask();
|
||||
}
|
||||
|
||||
QCoro::Task<void> WaydroidDBusClient::resetWaydroidTask()
|
||||
{
|
||||
auto pendingReply = m_interface->resetWaydroid();
|
||||
co_await pendingReply;
|
||||
}
|
||||
|
||||
QCoro::QmlTask WaydroidDBusClient::resetWaydroid()
|
||||
{
|
||||
return resetWaydroidTask();
|
||||
}
|
||||
|
||||
QCoro::Task<void> WaydroidDBusClient::installApkTask(const QString apkFile)
|
||||
{
|
||||
auto pendingReply = m_interface->installApk(apkFile);
|
||||
co_await pendingReply;
|
||||
}
|
||||
|
||||
QCoro::QmlTask WaydroidDBusClient::installApk(const QString apkFile)
|
||||
{
|
||||
return installApkTask(apkFile);
|
||||
}
|
||||
|
||||
QCoro::Task<void> WaydroidDBusClient::deleteApplicationTask(const QString appId)
|
||||
{
|
||||
auto pendingReply = m_interface->deleteApplication(appId);
|
||||
co_await pendingReply;
|
||||
}
|
||||
|
||||
QCoro::QmlTask WaydroidDBusClient::deleteApplication(const QString appId)
|
||||
{
|
||||
return deleteApplicationTask(appId);
|
||||
}
|
||||
|
||||
void WaydroidDBusClient::updateStatus()
|
||||
{
|
||||
auto reply = m_interface->status();
|
||||
auto watcher = new QDBusPendingCallWatcher(reply, this);
|
||||
|
||||
connect(watcher, &QDBusPendingCallWatcher::finished, this, [this](auto watcher) {
|
||||
QDBusPendingReply<int> reply = *watcher;
|
||||
const auto status = static_cast<Status>(reply.argumentAt<0>());
|
||||
|
||||
if (m_status != status) {
|
||||
m_status = status;
|
||||
Q_EMIT statusChanged();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void WaydroidDBusClient::updateSessionStatus()
|
||||
{
|
||||
auto reply = m_interface->sessionStatus();
|
||||
auto watcher = new QDBusPendingCallWatcher(reply, this);
|
||||
|
||||
connect(watcher, &QDBusPendingCallWatcher::finished, this, [this](auto watcher) {
|
||||
QDBusPendingReply<int> reply = *watcher;
|
||||
const auto sessionStatus = static_cast<SessionStatus>(reply.argumentAt<0>());
|
||||
|
||||
if (m_sessionStatus != sessionStatus) {
|
||||
m_sessionStatus = sessionStatus;
|
||||
Q_EMIT sessionStatusChanged();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void WaydroidDBusClient::updateSystemType()
|
||||
{
|
||||
auto reply = m_interface->systemType();
|
||||
auto watcher = new QDBusPendingCallWatcher(reply, this);
|
||||
|
||||
connect(watcher, &QDBusPendingCallWatcher::finished, this, [this](auto watcher) {
|
||||
QDBusPendingReply<int> reply = *watcher;
|
||||
const auto systemType = static_cast<SystemType>(reply.argumentAt<0>());
|
||||
|
||||
if (m_systemType != systemType) {
|
||||
m_systemType = systemType;
|
||||
Q_EMIT systemTypeChanged();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void WaydroidDBusClient::updateIpAddress()
|
||||
{
|
||||
auto reply = m_interface->ipAddress();
|
||||
auto watcher = new QDBusPendingCallWatcher(reply, this);
|
||||
|
||||
connect(watcher, &QDBusPendingCallWatcher::finished, this, [this](auto watcher) {
|
||||
QDBusPendingReply<QString> reply = *watcher;
|
||||
const auto ipAddress = reply.argumentAt<0>();
|
||||
|
||||
if (m_ipAddress != ipAddress) {
|
||||
m_ipAddress = ipAddress;
|
||||
Q_EMIT ipAddressChanged();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void WaydroidDBusClient::updateAndroidId()
|
||||
{
|
||||
auto reply = m_interface->androidId();
|
||||
auto watcher = new QDBusPendingCallWatcher(reply, this);
|
||||
|
||||
connect(watcher, &QDBusPendingCallWatcher::finished, this, [this](auto watcher) {
|
||||
QDBusPendingReply<QString> reply = *watcher;
|
||||
const auto androidId = reply.argumentAt<0>();
|
||||
|
||||
if (m_androidId != androidId) {
|
||||
m_androidId = androidId;
|
||||
Q_EMIT androidIdChanged();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void WaydroidDBusClient::updateMultiWindows()
|
||||
{
|
||||
auto reply = m_interface->multiWindows();
|
||||
auto watcher = new QDBusPendingCallWatcher(reply, this);
|
||||
|
||||
connect(watcher, &QDBusPendingCallWatcher::finished, this, [this](auto watcher) {
|
||||
QDBusPendingReply<bool> reply = *watcher;
|
||||
const auto multiWindows = reply.argumentAt<0>();
|
||||
|
||||
if (m_multiWindows != multiWindows) {
|
||||
m_multiWindows = multiWindows;
|
||||
Q_EMIT multiWindowsChanged();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void WaydroidDBusClient::updateSuspend()
|
||||
{
|
||||
auto reply = m_interface->suspend();
|
||||
auto watcher = new QDBusPendingCallWatcher(reply, this);
|
||||
|
||||
connect(watcher, &QDBusPendingCallWatcher::finished, this, [this](auto watcher) {
|
||||
QDBusPendingReply<bool> reply = *watcher;
|
||||
const auto suspend = reply.argumentAt<0>();
|
||||
|
||||
if (m_suspend != suspend) {
|
||||
m_suspend = suspend;
|
||||
Q_EMIT suspendChanged();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void WaydroidDBusClient::updateUevent()
|
||||
{
|
||||
auto reply = m_interface->uevent();
|
||||
auto watcher = new QDBusPendingCallWatcher(reply, this);
|
||||
|
||||
connect(watcher, &QDBusPendingCallWatcher::finished, this, [this](auto watcher) {
|
||||
QDBusPendingReply<bool> reply = *watcher;
|
||||
const auto uevent = reply.argumentAt<0>();
|
||||
|
||||
if (m_uevent != uevent) {
|
||||
m_uevent = uevent;
|
||||
Q_EMIT ueventChanged();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void WaydroidDBusClient::copyToClipboard(const QString text)
|
||||
{
|
||||
qGuiApp->clipboard()->setText(text);
|
||||
}
|
||||
173
components/waydroidintegrationplugin/waydroiddbusclient.h
Normal file
173
components/waydroidintegrationplugin/waydroiddbusclient.h
Normal file
|
|
@ -0,0 +1,173 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2025 Florian RICHER <florian.richer@protonmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "plasmashellwaydroidinterface.h"
|
||||
#include "waydroidapplicationlistmodel.h"
|
||||
#include "waydroiddbusobject.h"
|
||||
|
||||
#include <QCoroCore>
|
||||
#include <QCoroQmlTask>
|
||||
#include <QDBusServiceWatcher>
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
|
||||
#include <qqmlregistration.h>
|
||||
|
||||
class WaydroidDBusClient : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
QML_ELEMENT
|
||||
QML_SINGLETON
|
||||
|
||||
Q_PROPERTY(Status status READ status NOTIFY statusChanged)
|
||||
Q_PROPERTY(SessionStatus sessionStatus READ sessionStatus NOTIFY sessionStatusChanged)
|
||||
Q_PROPERTY(SystemType systemType READ systemType NOTIFY systemTypeChanged)
|
||||
Q_PROPERTY(QString ipAddress READ ipAddress NOTIFY ipAddressChanged)
|
||||
Q_PROPERTY(QString androidId READ androidId NOTIFY androidIdChanged)
|
||||
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:
|
||||
explicit WaydroidDBusClient(QObject *parent = nullptr);
|
||||
|
||||
/**
|
||||
* @enum Status
|
||||
* @brief Defines the possible installation statuses of the Waydroid service.
|
||||
*/
|
||||
enum Status {
|
||||
NotSupported = WaydroidDBusObject::NotSupported,
|
||||
NotInitialized = WaydroidDBusObject::NotInitialized,
|
||||
Initializing = WaydroidDBusObject::Initializing,
|
||||
Initialized = WaydroidDBusObject::Initialized,
|
||||
Resetting = WaydroidDBusObject::Resetting,
|
||||
};
|
||||
Q_ENUM(Status)
|
||||
|
||||
/**
|
||||
* @enum SessionStatus
|
||||
* @brief Defines the possible states of a Waydroid session.
|
||||
*/
|
||||
enum SessionStatus {
|
||||
SessionStopped = WaydroidDBusObject::SessionStopped,
|
||||
SessionStarting = WaydroidDBusObject::SessionStarting,
|
||||
SessionRunning = WaydroidDBusObject::SessionRunning,
|
||||
};
|
||||
Q_ENUM(SessionStatus)
|
||||
|
||||
/**
|
||||
* @enum SystemType
|
||||
* @brief Defines the types of Android systems supported by Waydroid.
|
||||
*/
|
||||
enum SystemType {
|
||||
Vanilla = WaydroidDBusObject::Vanilla, ///< Vanilla Android system.
|
||||
Foss = WaydroidDBusObject::Foss, ///< Free and Open Source Software variant.
|
||||
Gapps = WaydroidDBusObject::Gapps, ///< Variant with Google Apps included.
|
||||
UnknownSystemType = WaydroidDBusObject::UnknownSystemType
|
||||
};
|
||||
Q_ENUM(SystemType)
|
||||
|
||||
/**
|
||||
* @enum RomType
|
||||
* @brief Defines the types of ROMs supported by Waydroid.
|
||||
*
|
||||
* @todo Add OTA ROM with custom system url and vendor url
|
||||
*/
|
||||
enum RomType {
|
||||
Lineage = WaydroidDBusObject::Lineage, ///< LineageOS ROM.
|
||||
Bliss = WaydroidDBusObject::Bliss ///< Bliss ROM.
|
||||
};
|
||||
Q_ENUM(RomType)
|
||||
|
||||
[[nodiscard]] Status status() const;
|
||||
[[nodiscard]] SessionStatus sessionStatus() const;
|
||||
[[nodiscard]] SystemType systemType() const;
|
||||
[[nodiscard]] QString ipAddress() const;
|
||||
[[nodiscard]] QString androidId() const;
|
||||
[[nodiscard]] WaydroidApplicationListModel *applicationListModel() const;
|
||||
|
||||
[[nodiscard]] bool multiWindows() const;
|
||||
QCoro::QmlTask setMultiWindows(const bool multiWindows);
|
||||
[[nodiscard]] bool suspend() const;
|
||||
QCoro::QmlTask setSuspend(const bool suspend);
|
||||
[[nodiscard]] bool uevent() const;
|
||||
QCoro::QmlTask setUevent(const bool uevent);
|
||||
|
||||
Q_INVOKABLE QCoro::QmlTask initialize(const SystemType systemType, const RomType romType, const bool forced = false);
|
||||
Q_INVOKABLE QCoro::QmlTask startSession();
|
||||
Q_INVOKABLE QCoro::QmlTask stopSession();
|
||||
Q_INVOKABLE QCoro::QmlTask resetWaydroid();
|
||||
Q_INVOKABLE QCoro::QmlTask installApk(const QString apkFile);
|
||||
Q_INVOKABLE QCoro::QmlTask deleteApplication(const QString appId);
|
||||
Q_INVOKABLE QCoro::QmlTask refreshSessionInfo();
|
||||
Q_INVOKABLE QCoro::QmlTask refreshAndroidId();
|
||||
Q_INVOKABLE QCoro::QmlTask refreshApplications();
|
||||
|
||||
Q_INVOKABLE void copyToClipboard(const QString text);
|
||||
|
||||
Q_SIGNALS:
|
||||
void statusChanged();
|
||||
// download and total is in MB and speed in Kbps
|
||||
void downloadStatusChanged(double downloaded, double total, double speed);
|
||||
void sessionStatusChanged();
|
||||
void systemTypeChanged();
|
||||
void ipAddressChanged();
|
||||
void androidIdChanged();
|
||||
void multiWindowsChanged();
|
||||
void suspendChanged();
|
||||
void ueventChanged();
|
||||
|
||||
void actionFinished(const QString message);
|
||||
void actionFailed(const QString message);
|
||||
void errorOccurred(const QString title, const QString message);
|
||||
|
||||
private Q_SLOTS:
|
||||
void updateStatus();
|
||||
void updateSessionStatus();
|
||||
void updateSystemType();
|
||||
void updateIpAddress();
|
||||
void updateAndroidId();
|
||||
void updateMultiWindows();
|
||||
void updateSuspend();
|
||||
void updateUevent();
|
||||
|
||||
private:
|
||||
OrgKdePlasmashellWaydroidInterface *m_interface;
|
||||
QDBusServiceWatcher *m_watcher;
|
||||
|
||||
Status m_status{NotInitialized};
|
||||
SessionStatus m_sessionStatus{SessionStopped};
|
||||
SystemType m_systemType{UnknownSystemType};
|
||||
QString m_ipAddress{""};
|
||||
QString m_androidId{""};
|
||||
WaydroidApplicationListModel *m_applicationListModel{nullptr};
|
||||
|
||||
// Waydroid props. See https://docs.waydro.id/usage/waydroid-prop-options
|
||||
bool m_multiWindows{false};
|
||||
bool m_suspend{false};
|
||||
bool m_uevent{false};
|
||||
|
||||
bool m_connected{false};
|
||||
|
||||
void connectSignals();
|
||||
void initializeApplicationListModel();
|
||||
|
||||
QCoro::Task<void> initializeTask(const SystemType systemType, const RomType romType, const bool forced = false);
|
||||
QCoro::Task<void> startSessionTask();
|
||||
QCoro::Task<void> stopSessionTask();
|
||||
QCoro::Task<void> resetWaydroidTask();
|
||||
QCoro::Task<void> installApkTask(const QString apkFile);
|
||||
QCoro::Task<void> deleteApplicationTask(const QString appId);
|
||||
QCoro::Task<void> setMultiWindowsTask(const bool multiWindows);
|
||||
QCoro::Task<void> setSuspendTask(const bool suspend);
|
||||
QCoro::Task<void> setUeventTask(const bool uevent);
|
||||
QCoro::Task<void> refreshSessionInfoTask();
|
||||
QCoro::Task<void> refreshAndroidIdTask();
|
||||
QCoro::Task<void> refreshApplicationsTask();
|
||||
};
|
||||
|
|
@ -4,22 +4,20 @@
|
|||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
|
||||
#include "waydroidstate.h"
|
||||
#include "waydroiddbusobject.h"
|
||||
#include "waydroidadaptor.h"
|
||||
#include "waydroidapplicationdbusobject.h"
|
||||
#include "waydroidintegrationplugin_debug.h"
|
||||
#include "waydroidshared.h"
|
||||
|
||||
#include <QClipboard>
|
||||
#include <QCoroProcess>
|
||||
#include <QDebug>
|
||||
#include <QDBusConnection>
|
||||
#include <QDir>
|
||||
#include <QGuiApplication>
|
||||
#include <QLoggingCategory>
|
||||
#include <QProcess>
|
||||
#include <QRegularExpression>
|
||||
#include <QStandardPaths>
|
||||
#include <QTimer>
|
||||
#include <QtLogging>
|
||||
|
||||
#include <KAuth/Action>
|
||||
#include <KAuth/ExecuteJob>
|
||||
#include <KConfigGroup>
|
||||
#include <KDesktopFile>
|
||||
|
|
@ -36,27 +34,330 @@ static const QRegularExpression sessionRegExp(u"Session:\\s*(\\w+)"_s);
|
|||
static const QRegularExpression ipAdressRegExp(u"IP address:\\s*(\\d+\\.\\d+\\.\\d+\\.\\d+)"_s);
|
||||
static const QRegularExpression systemOtaRegExp(u"system_ota\\s*=\\s*(\\S+)"_s);
|
||||
|
||||
WaydroidState::WaydroidState(QObject *parent)
|
||||
WaydroidDBusObject::WaydroidDBusObject(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);
|
||||
connect(this, &WaydroidState::statusChanged, this, &WaydroidState::refreshInstallationInfo);
|
||||
connect(this, &WaydroidState::sessionStatusChanged, this, &WaydroidState::refreshPropsInfo);
|
||||
|
||||
refreshSupportsInfo();
|
||||
}
|
||||
|
||||
void WaydroidState::refreshSupportsInfo()
|
||||
void WaydroidDBusObject::registerObject()
|
||||
{
|
||||
const QStringList arguments{u"-h"_s};
|
||||
if (!m_dbusInitialized) {
|
||||
new WaydroidAdaptor{this};
|
||||
QDBusConnection::sessionBus().registerObject(u"/Waydroid"_s, this);
|
||||
m_dbusInitialized = true;
|
||||
|
||||
// Connect it-self to auto-refresh when required status has changed
|
||||
connect(this, &WaydroidDBusObject::statusChanged, this, &WaydroidDBusObject::refreshSessionInfo);
|
||||
connect(this, &WaydroidDBusObject::statusChanged, this, &WaydroidDBusObject::refreshInstallationInfo);
|
||||
connect(this, &WaydroidDBusObject::sessionStatusChanged, this, &WaydroidDBusObject::refreshPropsInfo);
|
||||
connect(this, &WaydroidDBusObject::sessionStatusChanged, this, &WaydroidDBusObject::refreshApplications);
|
||||
|
||||
refreshSupportsInfo();
|
||||
}
|
||||
}
|
||||
|
||||
void WaydroidDBusObject::initialize(const int systemType, const int romType, const bool forced)
|
||||
{
|
||||
if (m_status == Initializing) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_status = Initializing;
|
||||
Q_EMIT statusChanged();
|
||||
|
||||
QString systemTypeArg;
|
||||
switch (systemType) {
|
||||
case Vanilla:
|
||||
systemTypeArg = "VANILLA";
|
||||
break;
|
||||
case Foss:
|
||||
systemTypeArg = "FOSS";
|
||||
break;
|
||||
case Gapps:
|
||||
systemTypeArg = "GAPPS";
|
||||
break;
|
||||
default:
|
||||
systemTypeArg = "VANILLA";
|
||||
break;
|
||||
}
|
||||
|
||||
QString romTypeArg;
|
||||
switch (romType) {
|
||||
case Lineage:
|
||||
romTypeArg = "lineage";
|
||||
break;
|
||||
case Bliss:
|
||||
romTypeArg = "bliss";
|
||||
break;
|
||||
}
|
||||
|
||||
const QVariantMap args = {{u"systemType"_s, systemTypeArg}, {u"romType"_s, romTypeArg}, {u"forced"_s, forced}};
|
||||
|
||||
KAuth::Action writeAction(u"org.kde.plasma.mobileshell.waydroidhelper.initialize"_s);
|
||||
writeAction.setHelperId(u"org.kde.plasma.mobileshell.waydroidhelper"_s);
|
||||
writeAction.setArguments(args);
|
||||
writeAction.setTimeout(3600000); // HACK: 1 hour to wait installation
|
||||
|
||||
KAuth::ExecuteJob *job = writeAction.execute();
|
||||
job->start();
|
||||
|
||||
connect(job, &KAuth::ExecuteJob::newData, this, [this](const QVariantMap &data) {
|
||||
QString log = data.value("log", "").toString();
|
||||
float downloaded = data.value("downloaded", 0.0).toFloat();
|
||||
float total = data.value("total", 0.0).toFloat();
|
||||
float speed = data.value("speed", 0.0).toFloat();
|
||||
|
||||
qCDebug(WAYDROIDINTEGRATIONPLUGIN) << "log: " << log;
|
||||
Q_EMIT downloadStatusChanged(downloaded, total, speed);
|
||||
});
|
||||
|
||||
connect(job, &KAuth::ExecuteJob::finished, this, [this](KJob *job, auto) {
|
||||
if (job->error() == 0) {
|
||||
m_status = Initialized;
|
||||
} else {
|
||||
Q_EMIT errorOccurred(i18n("Failed to initialize Waydroid."), job->errorString());
|
||||
|
||||
m_status = NotInitialized;
|
||||
qCWarning(WAYDROIDINTEGRATIONPLUGIN) << "KAuth returned an error code:" << job->error() << " message: " << job->errorString();
|
||||
}
|
||||
|
||||
Q_EMIT statusChanged();
|
||||
});
|
||||
}
|
||||
|
||||
void WaydroidDBusObject::startSession()
|
||||
{
|
||||
if (m_sessionStatus == SessionStarting || m_sessionStatus == SessionRunning) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_sessionStatus = SessionStarting;
|
||||
Q_EMIT sessionStatusChanged();
|
||||
|
||||
const QStringList arguments{u"session"_s, u"start"_s};
|
||||
|
||||
auto *process = new QProcess(this);
|
||||
process->start(WAYDROID_COMMAND, arguments);
|
||||
|
||||
connect(process, &QProcess::finished, this, [this, process](int exitCode, QProcess::ExitStatus exitStatus) {
|
||||
Q_UNUSED(exitStatus);
|
||||
|
||||
if (exitCode == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_sessionStatus = SessionStopped;
|
||||
Q_EMIT sessionStatusChanged();
|
||||
|
||||
QByteArray errorData = process->readAllStandardError();
|
||||
QString errorString = QString::fromUtf8(errorData);
|
||||
|
||||
Q_EMIT errorOccurred(i18n("Failed to start the Waydroid session."), errorString);
|
||||
|
||||
qCWarning(WAYDROIDINTEGRATIONPLUGIN) << "Failed to start the Waydroid session: " << errorString;
|
||||
});
|
||||
|
||||
checkSessionStarting(10);
|
||||
}
|
||||
|
||||
void WaydroidDBusObject::stopSession()
|
||||
{
|
||||
if (m_sessionStatus == SessionStopped) {
|
||||
return;
|
||||
}
|
||||
|
||||
const QStringList arguments{u"session"_s, u"stop"_s};
|
||||
|
||||
QProcess *process = new QProcess(this);
|
||||
process->start(WAYDROID_COMMAND, arguments);
|
||||
process->waitForFinished();
|
||||
|
||||
const int exitCode = process->exitCode();
|
||||
if (process->exitCode() == 0) {
|
||||
m_sessionStatus = SessionStopped;
|
||||
Q_EMIT sessionStatusChanged();
|
||||
} else {
|
||||
qCWarning(WAYDROIDINTEGRATIONPLUGIN) << "Failed to stop the Waydroid session: " << process->readAllStandardError();
|
||||
}
|
||||
}
|
||||
|
||||
void WaydroidDBusObject::resetWaydroid()
|
||||
{
|
||||
if (m_status != Initialized || m_sessionStatus == SessionStarting) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_status = Resetting;
|
||||
Q_EMIT statusChanged();
|
||||
|
||||
if (m_sessionStatus == SessionRunning) {
|
||||
stopSession();
|
||||
}
|
||||
|
||||
const QVariantMap args = {{u"homeDir"_s, QDir::homePath()}};
|
||||
|
||||
KAuth::Action writeAction(u"org.kde.plasma.mobileshell.waydroidhelper.reset"_s);
|
||||
writeAction.setHelperId(u"org.kde.plasma.mobileshell.waydroidhelper"_s);
|
||||
writeAction.setArguments(args);
|
||||
|
||||
KAuth::ExecuteJob *job = writeAction.execute();
|
||||
job->start();
|
||||
|
||||
connect(job, &KAuth::ExecuteJob::finished, this, [this](KJob *job, auto) {
|
||||
removeWaydroidApplications();
|
||||
|
||||
if (job->error() == 0) {
|
||||
m_status = NotInitialized;
|
||||
} else {
|
||||
Q_EMIT errorOccurred(i18n("Failed to reset Waydroid."), "");
|
||||
|
||||
m_status = Initialized;
|
||||
qCWarning(WAYDROIDINTEGRATIONPLUGIN) << "KAuth returned an error code:" << job->error() << " message: " << job->errorString();
|
||||
}
|
||||
|
||||
Q_EMIT statusChanged();
|
||||
});
|
||||
}
|
||||
|
||||
void WaydroidDBusObject::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 actionFailed(i18n("Installation Failed"));
|
||||
qCWarning(WAYDROIDINTEGRATIONPLUGIN) << "Error occurred during installation of " << apkFile << ": " << process->readAllStandardError();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void WaydroidDBusObject::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 actionFailed(i18n("Application uninstall failed"));
|
||||
qCWarning(WAYDROIDINTEGRATIONPLUGIN) << "Error occurred during uninstallation of " << appId << ": " << errorLog;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
int WaydroidDBusObject::status() const
|
||||
{
|
||||
return m_status;
|
||||
}
|
||||
|
||||
int WaydroidDBusObject::sessionStatus() const
|
||||
{
|
||||
return m_sessionStatus;
|
||||
}
|
||||
|
||||
int WaydroidDBusObject::systemType() const
|
||||
{
|
||||
return m_systemType;
|
||||
}
|
||||
|
||||
QString WaydroidDBusObject::ipAddress() const
|
||||
{
|
||||
return m_ipAddress;
|
||||
}
|
||||
|
||||
QString WaydroidDBusObject::androidId() const
|
||||
{
|
||||
return m_androidId;
|
||||
}
|
||||
|
||||
bool WaydroidDBusObject::multiWindows() const
|
||||
{
|
||||
return m_multiWindows;
|
||||
}
|
||||
|
||||
void WaydroidDBusObject::setMultiWindows(const bool multiWindows)
|
||||
{
|
||||
if (m_multiWindows == multiWindows) {
|
||||
return;
|
||||
}
|
||||
|
||||
const QString value = multiWindows ? "true" : "false";
|
||||
|
||||
if (writePropValue(MULTI_WINDOWS_PROP_KEY, value)) {
|
||||
m_multiWindows = multiWindows;
|
||||
Q_EMIT multiWindowsChanged();
|
||||
}
|
||||
}
|
||||
|
||||
bool WaydroidDBusObject::suspend() const
|
||||
{
|
||||
return m_suspend;
|
||||
}
|
||||
|
||||
void WaydroidDBusObject::setSuspend(const bool suspend)
|
||||
{
|
||||
if (m_suspend == suspend) {
|
||||
return;
|
||||
}
|
||||
|
||||
const QString value = suspend ? "true" : "false";
|
||||
|
||||
if (writePropValue(SUSPEND_PROP_KEY, value)) {
|
||||
m_suspend = suspend;
|
||||
Q_EMIT suspendChanged();
|
||||
}
|
||||
}
|
||||
|
||||
bool WaydroidDBusObject::uevent() const
|
||||
{
|
||||
return m_uevent;
|
||||
}
|
||||
|
||||
void WaydroidDBusObject::setUevent(const bool uevent)
|
||||
{
|
||||
if (m_uevent == uevent) {
|
||||
return;
|
||||
}
|
||||
|
||||
const QString value = uevent ? "true" : "false";
|
||||
|
||||
if (writePropValue(UEVENT_PROP_KEY, value)) {
|
||||
m_uevent = uevent;
|
||||
Q_EMIT ueventChanged();
|
||||
}
|
||||
}
|
||||
|
||||
QList<QDBusObjectPath> WaydroidDBusObject::applications() const
|
||||
{
|
||||
QList<QDBusObjectPath> paths;
|
||||
for (const auto &app : m_applicationObjects) {
|
||||
paths.push_back(app->objectPath());
|
||||
}
|
||||
return paths;
|
||||
}
|
||||
|
||||
void WaydroidDBusObject::refreshSupportsInfo()
|
||||
{
|
||||
const QStringList arguments{u"-h"_s};
|
||||
|
||||
auto process = QProcess(this);
|
||||
process.start(WAYDROID_COMMAND, arguments);
|
||||
process.waitForFinished();
|
||||
|
||||
const int exitCode = process.exitCode();
|
||||
if (exitCode != 0) {
|
||||
m_status = NotSupported;
|
||||
Q_EMIT statusChanged();
|
||||
|
|
@ -72,7 +373,7 @@ void WaydroidState::refreshSupportsInfo()
|
|||
Q_EMIT statusChanged();
|
||||
}
|
||||
|
||||
void WaydroidState::refreshInstallationInfo()
|
||||
void WaydroidDBusObject::refreshInstallationInfo()
|
||||
{
|
||||
if (m_status != Initialized) {
|
||||
return;
|
||||
|
|
@ -99,7 +400,7 @@ void WaydroidState::refreshInstallationInfo()
|
|||
Q_EMIT systemTypeChanged();
|
||||
}
|
||||
|
||||
void WaydroidState::refreshSessionInfo()
|
||||
void WaydroidDBusObject::refreshSessionInfo()
|
||||
{
|
||||
if (m_status != Initialized) {
|
||||
return;
|
||||
|
|
@ -108,7 +409,7 @@ void WaydroidState::refreshSessionInfo()
|
|||
const QString output = fetchSessionInfo();
|
||||
|
||||
const QString sessionMatchResult = extractRegExp(output, sessionRegExp);
|
||||
WaydroidState::SessionStatus newSessionStatus;
|
||||
SessionStatus newSessionStatus;
|
||||
|
||||
if (!sessionMatchResult.isEmpty()) {
|
||||
newSessionStatus = sessionMatchResult.contains("RUNNING") ? SessionRunning : SessionStopped;
|
||||
|
|
@ -125,7 +426,18 @@ void WaydroidState::refreshSessionInfo()
|
|||
Q_EMIT ipAddressChanged();
|
||||
}
|
||||
|
||||
void WaydroidState::refreshAndroidId()
|
||||
QString WaydroidDBusObject::fetchSessionInfo()
|
||||
{
|
||||
const QStringList arguments{u"status"_s};
|
||||
|
||||
auto process = QProcess(this);
|
||||
process.start(WAYDROID_COMMAND, arguments);
|
||||
process.waitForFinished();
|
||||
|
||||
return process.readAllStandardOutput();
|
||||
}
|
||||
|
||||
void WaydroidDBusObject::refreshAndroidId()
|
||||
{
|
||||
if (m_status != Initialized) {
|
||||
return;
|
||||
|
|
@ -150,7 +462,7 @@ void WaydroidState::refreshAndroidId()
|
|||
});
|
||||
}
|
||||
|
||||
void WaydroidState::refreshPropsInfo()
|
||||
void WaydroidDBusObject::refreshPropsInfo()
|
||||
{
|
||||
if (m_sessionStatus != SessionRunning) {
|
||||
return;
|
||||
|
|
@ -169,322 +481,7 @@ void WaydroidState::refreshPropsInfo()
|
|||
Q_EMIT ueventChanged();
|
||||
}
|
||||
|
||||
void WaydroidState::resetError()
|
||||
{
|
||||
m_errorTitle = "";
|
||||
Q_EMIT errorTitleChanged();
|
||||
|
||||
if (m_errorMessage != "") {
|
||||
m_errorMessage = "";
|
||||
Q_EMIT errorMessageChanged();
|
||||
}
|
||||
}
|
||||
|
||||
QCoro::QmlTask WaydroidState::initializeQml(const SystemType systemType, const RomType romType, const bool forced)
|
||||
{
|
||||
return initialize(systemType, romType, forced);
|
||||
}
|
||||
|
||||
QCoro::Task<void> WaydroidState::initialize(const SystemType systemType, const RomType romType, const bool forced)
|
||||
{
|
||||
if (m_status == Initializing) {
|
||||
co_return;
|
||||
}
|
||||
|
||||
m_status = Initializing;
|
||||
Q_EMIT statusChanged();
|
||||
|
||||
QString systemTypeArg;
|
||||
switch (systemType) {
|
||||
case SystemType::Vanilla:
|
||||
systemTypeArg = "VANILLA";
|
||||
break;
|
||||
case SystemType::Foss:
|
||||
systemTypeArg = "FOSS";
|
||||
break;
|
||||
case SystemType::Gapps:
|
||||
systemTypeArg = "GAPPS";
|
||||
break;
|
||||
default:
|
||||
systemTypeArg = "VANILLA";
|
||||
break;
|
||||
}
|
||||
|
||||
QString romTypeArg;
|
||||
switch (romType) {
|
||||
case RomType::Lineage:
|
||||
romTypeArg = "lineage";
|
||||
break;
|
||||
case RomType::Bliss:
|
||||
romTypeArg = "bliss";
|
||||
break;
|
||||
}
|
||||
|
||||
const QVariantMap args = {{u"systemType"_s, systemTypeArg}, {u"romType"_s, romTypeArg}, {u"forced"_s, forced}};
|
||||
|
||||
KAuth::Action writeAction(u"org.kde.plasma.mobileshell.waydroidhelper.initialize"_s);
|
||||
writeAction.setHelperId(u"org.kde.plasma.mobileshell.waydroidhelper"_s);
|
||||
writeAction.setArguments(args);
|
||||
writeAction.setTimeout(3600000); // HACK: 1 hour to wait installation
|
||||
|
||||
KAuth::ExecuteJob *job = writeAction.execute();
|
||||
job->start();
|
||||
|
||||
connect(job, &KAuth::ExecuteJob::newData, this, [this](const QVariantMap &data) {
|
||||
QString log = data.value("log", "").toString();
|
||||
float downloaded = data.value("downloaded", 0.0).toFloat();
|
||||
float total = data.value("total", 0.0).toFloat();
|
||||
float speed = data.value("speed", 0.0).toFloat();
|
||||
|
||||
qCDebug(WAYDROIDINTEGRATIONPLUGIN) << "log: " << log;
|
||||
Q_EMIT downloadStatusChanged(downloaded, total, speed);
|
||||
});
|
||||
|
||||
co_await qCoro(job, &KAuth::ExecuteJob::finished);
|
||||
|
||||
if (job->error() == 0) {
|
||||
m_status = Initialized;
|
||||
} else {
|
||||
m_errorTitle = i18n("Failed to initialize Waydroid.");
|
||||
Q_EMIT errorTitleChanged();
|
||||
m_errorMessage = job->errorString();
|
||||
Q_EMIT errorMessageChanged();
|
||||
|
||||
m_status = NotInitialized;
|
||||
qCWarning(WAYDROIDINTEGRATIONPLUGIN) << "KAuth returned an error code:" << job->error() << " message: " << job->errorString();
|
||||
}
|
||||
|
||||
Q_EMIT statusChanged();
|
||||
}
|
||||
|
||||
QCoro::QmlTask WaydroidState::startSessionQml()
|
||||
{
|
||||
return startSession();
|
||||
}
|
||||
|
||||
QCoro::Task<void> WaydroidState::startSession()
|
||||
{
|
||||
if (m_sessionStatus == SessionStarting || m_sessionStatus == SessionRunning) {
|
||||
co_return;
|
||||
}
|
||||
|
||||
m_sessionStatus = SessionStarting;
|
||||
Q_EMIT sessionStatusChanged();
|
||||
|
||||
const QStringList arguments{u"session"_s, u"start"_s};
|
||||
|
||||
QProcess *basicProcess = new QProcess(this);
|
||||
auto process = qCoro(basicProcess);
|
||||
co_await process.start(WAYDROID_COMMAND, arguments);
|
||||
|
||||
connect(basicProcess, &QProcess::finished, this, [this, basicProcess](int exitCode, QProcess::ExitStatus exitStatus) {
|
||||
Q_UNUSED(exitStatus);
|
||||
|
||||
if (exitCode == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_sessionStatus = SessionStopped;
|
||||
Q_EMIT sessionStatusChanged();
|
||||
|
||||
QByteArray errorData = basicProcess->readAllStandardError();
|
||||
QString errorString = QString::fromUtf8(errorData);
|
||||
|
||||
m_errorTitle = i18n("Failed to start the Waydroid session.");
|
||||
Q_EMIT errorTitleChanged();
|
||||
m_errorMessage = errorString;
|
||||
Q_EMIT errorMessageChanged();
|
||||
|
||||
qCWarning(WAYDROIDINTEGRATIONPLUGIN) << "Failed to start the Waydroid session: " << errorString;
|
||||
});
|
||||
|
||||
checkSessionStarting(10);
|
||||
}
|
||||
|
||||
QCoro::QmlTask WaydroidState::stopSessionQml()
|
||||
{
|
||||
return stopSession();
|
||||
}
|
||||
|
||||
QCoro::Task<void> WaydroidState::stopSession()
|
||||
{
|
||||
if (m_sessionStatus == SessionStopped) {
|
||||
co_return;
|
||||
}
|
||||
|
||||
const QStringList arguments{u"session"_s, u"stop"_s};
|
||||
|
||||
QProcess basicProcess = QProcess(this);
|
||||
auto process = qCoro(basicProcess);
|
||||
co_await process.start(WAYDROID_COMMAND, arguments);
|
||||
co_await process.waitForFinished();
|
||||
|
||||
if (basicProcess.exitCode() == 0) {
|
||||
m_sessionStatus = SessionStopped;
|
||||
Q_EMIT sessionStatusChanged();
|
||||
} else {
|
||||
qCWarning(WAYDROIDINTEGRATIONPLUGIN) << "Failed to stop the Waydroid session: " << basicProcess.readAllStandardError();
|
||||
}
|
||||
}
|
||||
|
||||
void WaydroidState::copyToClipboard(const QString text)
|
||||
{
|
||||
qGuiApp->clipboard()->setText(text);
|
||||
}
|
||||
|
||||
QCoro::QmlTask WaydroidState::resetWaydroidQml()
|
||||
{
|
||||
return resetWaydroid();
|
||||
}
|
||||
|
||||
QCoro::Task<void> WaydroidState::resetWaydroid()
|
||||
{
|
||||
if (m_status != Initialized || m_sessionStatus == SessionStarting) {
|
||||
co_return;
|
||||
}
|
||||
|
||||
m_status = Resetting;
|
||||
Q_EMIT statusChanged();
|
||||
|
||||
if (m_sessionStatus == SessionRunning) {
|
||||
co_await stopSession();
|
||||
}
|
||||
|
||||
const QVariantMap args = {{u"homeDir"_s, QDir::homePath()}};
|
||||
|
||||
KAuth::Action writeAction(u"org.kde.plasma.mobileshell.waydroidhelper.reset"_s);
|
||||
writeAction.setHelperId(u"org.kde.plasma.mobileshell.waydroidhelper"_s);
|
||||
writeAction.setArguments(args);
|
||||
|
||||
KAuth::ExecuteJob *job = writeAction.execute();
|
||||
job->start();
|
||||
|
||||
co_await qCoro(job, &KAuth::ExecuteJob::finished);
|
||||
|
||||
removeWaydroidApplications();
|
||||
|
||||
if (job->error() == 0) {
|
||||
m_status = NotInitialized;
|
||||
} else {
|
||||
m_errorTitle = i18n("Failed to reset Waydroid.");
|
||||
Q_EMIT errorTitleChanged();
|
||||
|
||||
m_status = Initialized;
|
||||
qCWarning(WAYDROIDINTEGRATIONPLUGIN) << "KAuth returned an error code:" << job->error() << " message: " << job->errorString();
|
||||
}
|
||||
|
||||
Q_EMIT statusChanged();
|
||||
}
|
||||
|
||||
WaydroidState::Status WaydroidState::status() const
|
||||
{
|
||||
return m_status;
|
||||
}
|
||||
|
||||
WaydroidState::SessionStatus WaydroidState::sessionStatus() const
|
||||
{
|
||||
return m_sessionStatus;
|
||||
}
|
||||
|
||||
WaydroidState::SystemType WaydroidState::systemType() const
|
||||
{
|
||||
return m_systemType;
|
||||
}
|
||||
|
||||
QString WaydroidState::ipAddress() const
|
||||
{
|
||||
return m_ipAddress;
|
||||
}
|
||||
|
||||
QString WaydroidState::errorTitle() const
|
||||
{
|
||||
return m_errorTitle;
|
||||
}
|
||||
|
||||
QString WaydroidState::errorMessage() const
|
||||
{
|
||||
return m_errorMessage;
|
||||
}
|
||||
|
||||
QString WaydroidState::androidId() const
|
||||
{
|
||||
return m_androidId;
|
||||
}
|
||||
|
||||
WaydroidApplicationListModel *WaydroidState::applicationListModel() const
|
||||
{
|
||||
return m_applicationListModel;
|
||||
}
|
||||
|
||||
bool WaydroidState::multiWindows() const
|
||||
{
|
||||
return m_multiWindows;
|
||||
}
|
||||
|
||||
void WaydroidState::setMultiWindows(const bool multiWindows)
|
||||
{
|
||||
if (m_multiWindows == multiWindows) {
|
||||
return;
|
||||
}
|
||||
|
||||
const QString value = multiWindows ? "true" : "false";
|
||||
|
||||
if (writePropValue(MULTI_WINDOWS_PROP_KEY, value)) {
|
||||
m_multiWindows = multiWindows;
|
||||
Q_EMIT multiWindowsChanged();
|
||||
}
|
||||
}
|
||||
|
||||
bool WaydroidState::suspend() const
|
||||
{
|
||||
return m_suspend;
|
||||
}
|
||||
|
||||
void WaydroidState::setSuspend(const bool suspend)
|
||||
{
|
||||
if (m_suspend == suspend) {
|
||||
return;
|
||||
}
|
||||
|
||||
const QString value = suspend ? "true" : "false";
|
||||
|
||||
if (writePropValue(SUSPEND_PROP_KEY, value)) {
|
||||
m_suspend = suspend;
|
||||
Q_EMIT suspendChanged();
|
||||
}
|
||||
}
|
||||
|
||||
bool WaydroidState::uevent() const
|
||||
{
|
||||
return m_uevent;
|
||||
}
|
||||
|
||||
void WaydroidState::setUevent(const bool uevent)
|
||||
{
|
||||
if (m_uevent == uevent) {
|
||||
return;
|
||||
}
|
||||
|
||||
const QString value = uevent ? "true" : "false";
|
||||
|
||||
if (writePropValue(UEVENT_PROP_KEY, value)) {
|
||||
m_uevent = uevent;
|
||||
Q_EMIT ueventChanged();
|
||||
}
|
||||
}
|
||||
|
||||
QString WaydroidState::fetchSessionInfo()
|
||||
{
|
||||
const QStringList arguments{u"status"_s};
|
||||
|
||||
QProcess *process = new QProcess(this);
|
||||
process->start(WAYDROID_COMMAND, arguments);
|
||||
process->waitForFinished();
|
||||
|
||||
return process->readAllStandardOutput();
|
||||
}
|
||||
|
||||
QString WaydroidState::fetchPropValue(const QString key, const QString defaultValue)
|
||||
QString WaydroidDBusObject::fetchPropValue(const QString key, const QString defaultValue)
|
||||
{
|
||||
const QStringList arguments{u"prop"_s, u"get"_s, key};
|
||||
|
||||
|
|
@ -502,18 +499,18 @@ QString WaydroidState::fetchPropValue(const QString key, const QString defaultVa
|
|||
return value;
|
||||
}
|
||||
|
||||
bool WaydroidState::writePropValue(const QString key, const QString value)
|
||||
bool WaydroidDBusObject::writePropValue(const QString key, const QString value)
|
||||
{
|
||||
const QStringList arguments{u"prop"_s, u"set"_s, key, value};
|
||||
|
||||
QProcess *process = new QProcess(this);
|
||||
process->start(WAYDROID_COMMAND, arguments);
|
||||
process->waitForFinished();
|
||||
auto process = QProcess(this);
|
||||
process.start(WAYDROID_COMMAND, arguments);
|
||||
process.waitForFinished();
|
||||
|
||||
return process->exitCode() == 0;
|
||||
return process.exitCode() == 0;
|
||||
}
|
||||
|
||||
QString WaydroidState::extractRegExp(const QString text, const QRegularExpression regExp) const
|
||||
QString WaydroidDBusObject::extractRegExp(const QString text, const QRegularExpression regExp) const
|
||||
{
|
||||
const QRegularExpressionMatch match = regExp.match(text);
|
||||
|
||||
|
|
@ -524,7 +521,7 @@ QString WaydroidState::extractRegExp(const QString text, const QRegularExpressio
|
|||
}
|
||||
}
|
||||
|
||||
void WaydroidState::checkSessionStarting(const int limit, const int tried)
|
||||
void WaydroidDBusObject::checkSessionStarting(const int limit, const int tried)
|
||||
{
|
||||
if (m_sessionStatus != SessionStarting) {
|
||||
return;
|
||||
|
|
@ -547,7 +544,7 @@ void WaydroidState::checkSessionStarting(const int limit, const int tried)
|
|||
}
|
||||
}
|
||||
|
||||
QString WaydroidState::desktopFileDirectory()
|
||||
QString WaydroidDBusObject::desktopFileDirectory()
|
||||
{
|
||||
auto dir = []() -> QString {
|
||||
if (KSandbox::isFlatpak()) {
|
||||
|
|
@ -561,7 +558,7 @@ QString WaydroidState::desktopFileDirectory()
|
|||
return dir;
|
||||
}
|
||||
|
||||
bool WaydroidState::removeWaydroidApplications()
|
||||
bool WaydroidDBusObject::removeWaydroidApplications()
|
||||
{
|
||||
const QDir appsDir(desktopFileDirectory());
|
||||
const auto fileInfos = appsDir.entryInfoList(QDir::Files);
|
||||
|
|
@ -595,3 +592,86 @@ bool WaydroidState::removeWaydroidApplications()
|
|||
|
||||
return allFileRemoved;
|
||||
}
|
||||
|
||||
void WaydroidDBusObject::refreshApplications()
|
||||
{
|
||||
if (m_sessionStatus != SessionRunning) {
|
||||
// Clear existing applications when session is not running
|
||||
for (const auto &appObject : m_applicationObjects) {
|
||||
appObject->unregisterObject();
|
||||
}
|
||||
m_applicationObjects.clear();
|
||||
return;
|
||||
}
|
||||
|
||||
const QString output = fetchApplicationsList();
|
||||
if (output.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
QTextStream inFile(const_cast<QString *>(&output), QIODevice::ReadOnly);
|
||||
const auto newApplications = WaydroidApplicationDBusObject::parseApplicationsFromWaydroidLog(inFile);
|
||||
|
||||
// Create a map of existing applications by package name for efficient lookup
|
||||
QMap<QString, int> existingAppMap;
|
||||
for (int i = 0; i < m_applicationObjects.size(); ++i) {
|
||||
const auto &application = m_applicationObjects[i];
|
||||
existingAppMap.insert(application->packageName(), i);
|
||||
}
|
||||
|
||||
QList<WaydroidApplicationDBusObject::Ptr> toInsert;
|
||||
|
||||
// Check which applications need to be added or are already present
|
||||
for (const auto &application : newApplications) {
|
||||
if (!application->name().isEmpty() && !application->packageName().isEmpty()) {
|
||||
auto it = existingAppMap.find(application->packageName());
|
||||
if (it != existingAppMap.end()) {
|
||||
// Application already exists, remove from map to mark as kept
|
||||
existingAppMap.erase(it);
|
||||
} else {
|
||||
// Application needs to be inserted
|
||||
toInsert.append(application);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Remove applications that are no longer present
|
||||
QList<int> toRemove;
|
||||
for (const int index : existingAppMap.values()) {
|
||||
toRemove.append(index);
|
||||
}
|
||||
|
||||
std::sort(toRemove.begin(), toRemove.end());
|
||||
|
||||
// Remove indices from end to start to avoid index shifting
|
||||
for (int i = toRemove.size() - 1; i >= 0; --i) {
|
||||
int ind = toRemove[i];
|
||||
const auto application = m_applicationObjects[ind];
|
||||
m_applicationObjects.removeAt(ind);
|
||||
Q_EMIT applicationRemoved(application->objectPath());
|
||||
application->unregisterObject();
|
||||
}
|
||||
|
||||
// Add new applications and register them
|
||||
for (const auto &application : toInsert) {
|
||||
application->registerObject();
|
||||
m_applicationObjects.append(application);
|
||||
Q_EMIT applicationAdded(application->objectPath());
|
||||
}
|
||||
}
|
||||
|
||||
QString WaydroidDBusObject::fetchApplicationsList()
|
||||
{
|
||||
const QStringList arguments{u"app"_s, u"list"_s};
|
||||
|
||||
auto process = QProcess(this);
|
||||
process.start(WAYDROID_COMMAND, arguments);
|
||||
process.waitForFinished();
|
||||
|
||||
if (process.exitCode() != 0) {
|
||||
qCWarning(WAYDROIDINTEGRATIONPLUGIN) << "Failed to fetch applications list: " << process.readAllStandardError();
|
||||
return QString{};
|
||||
}
|
||||
|
||||
return process.readAllStandardOutput();
|
||||
}
|
||||
|
|
@ -6,16 +6,16 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include "waydroidapplicationlistmodel.h"
|
||||
#include "waydroidapplicationdbusobject.h"
|
||||
|
||||
#include <QCoroCore>
|
||||
#include <QCoroQmlTask>
|
||||
#include <QDBusObjectPath>
|
||||
#include <QList>
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
|
||||
#include <qqmlregistration.h>
|
||||
#include <qtmetamacros.h>
|
||||
|
||||
class WaydroidApplicationListModel;
|
||||
class WaydroidApplicationDBusObject;
|
||||
|
||||
/**
|
||||
* This class provides an interface to interact with the Waydroid container,
|
||||
|
|
@ -23,26 +23,15 @@ class WaydroidApplicationListModel;
|
|||
*
|
||||
* @author Florian RICHER <florian.richer@protonmail.com>
|
||||
*/
|
||||
class WaydroidState : public QObject
|
||||
class WaydroidDBusObject : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
QML_ELEMENT
|
||||
QML_SINGLETON
|
||||
|
||||
Q_PROPERTY(Status status READ status NOTIFY statusChanged)
|
||||
Q_PROPERTY(SessionStatus sessionStatus READ sessionStatus NOTIFY sessionStatusChanged)
|
||||
Q_PROPERTY(SystemType systemType READ systemType NOTIFY systemTypeChanged)
|
||||
Q_PROPERTY(QString ipAddress READ ipAddress NOTIFY ipAddressChanged)
|
||||
Q_PROPERTY(QString androidId READ androidId NOTIFY androidIdChanged)
|
||||
Q_PROPERTY(QString errorTitle READ errorTitle NOTIFY errorTitleChanged)
|
||||
Q_PROPERTY(QString errorMessage READ errorMessage NOTIFY errorMessageChanged)
|
||||
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)
|
||||
Q_CLASSINFO("D-Bus Interface", "org.kde.plasmashell.Waydroid")
|
||||
|
||||
public:
|
||||
WaydroidState(QObject *parent = nullptr);
|
||||
explicit WaydroidDBusObject(QObject *parent = nullptr);
|
||||
|
||||
/**
|
||||
* @enum Status
|
||||
|
|
@ -92,68 +81,72 @@ public:
|
|||
};
|
||||
Q_ENUM(RomType)
|
||||
|
||||
Q_INVOKABLE void refreshSupportsInfo();
|
||||
Q_INVOKABLE void refreshInstallationInfo();
|
||||
Q_INVOKABLE void refreshSessionInfo();
|
||||
Q_INVOKABLE void refreshAndroidId();
|
||||
Q_INVOKABLE void refreshPropsInfo();
|
||||
Q_INVOKABLE void resetError();
|
||||
Q_INVOKABLE QCoro::QmlTask initializeQml(const SystemType systemType, const RomType romType, const bool forced = false);
|
||||
QCoro::Task<void> initialize(const SystemType systemType, const RomType romType, const bool forced = false);
|
||||
Q_INVOKABLE QCoro::QmlTask startSessionQml();
|
||||
QCoro::Task<void> startSession();
|
||||
Q_INVOKABLE QCoro::QmlTask stopSessionQml();
|
||||
QCoro::Task<void> stopSession();
|
||||
Q_INVOKABLE QCoro::QmlTask resetWaydroidQml();
|
||||
QCoro::Task<void> resetWaydroid();
|
||||
|
||||
Q_INVOKABLE void copyToClipboard(const QString text);
|
||||
|
||||
Status status() const;
|
||||
SessionStatus sessionStatus() const;
|
||||
SystemType systemType() const;
|
||||
QString ipAddress() const;
|
||||
QString androidId() const;
|
||||
QString errorTitle() const;
|
||||
QString errorMessage() const;
|
||||
WaydroidApplicationListModel *applicationListModel() const;
|
||||
|
||||
bool multiWindows() const;
|
||||
void setMultiWindows(const bool multiWindows);
|
||||
bool suspend() const;
|
||||
void setSuspend(const bool suspend);
|
||||
bool uevent() const;
|
||||
void setUevent(const bool uevent);
|
||||
// called by QML
|
||||
Q_INVOKABLE void registerObject();
|
||||
|
||||
Q_SIGNALS:
|
||||
void statusChanged();
|
||||
Q_SCRIPTABLE void statusChanged();
|
||||
// download and total is in MB and speed in Kbps
|
||||
void downloadStatusChanged(float downloaded, float total, float speed);
|
||||
void sessionStatusChanged();
|
||||
void systemTypeChanged();
|
||||
void ipAddressChanged();
|
||||
void multiWindowsChanged();
|
||||
void suspendChanged();
|
||||
void ueventChanged();
|
||||
void errorTitleChanged();
|
||||
void errorMessageChanged();
|
||||
void androidIdChanged();
|
||||
Q_SCRIPTABLE void downloadStatusChanged(double downloaded, double total, double speed);
|
||||
Q_SCRIPTABLE void sessionStatusChanged();
|
||||
Q_SCRIPTABLE void systemTypeChanged();
|
||||
Q_SCRIPTABLE void ipAddressChanged();
|
||||
Q_SCRIPTABLE void androidIdChanged();
|
||||
Q_SCRIPTABLE void multiWindowsChanged();
|
||||
Q_SCRIPTABLE void suspendChanged();
|
||||
Q_SCRIPTABLE void ueventChanged();
|
||||
|
||||
Q_SCRIPTABLE void applicationAdded(QDBusObjectPath path);
|
||||
Q_SCRIPTABLE void applicationRemoved(QDBusObjectPath path);
|
||||
|
||||
// Use to display banner
|
||||
Q_SCRIPTABLE void actionFinished(const QString message);
|
||||
Q_SCRIPTABLE void actionFailed(const QString message);
|
||||
|
||||
// General error
|
||||
Q_SCRIPTABLE void errorOccurred(const QString title, const QString message);
|
||||
|
||||
public Q_SLOTS:
|
||||
Q_SCRIPTABLE int status() const;
|
||||
Q_SCRIPTABLE int sessionStatus() const;
|
||||
Q_SCRIPTABLE int systemType() const;
|
||||
Q_SCRIPTABLE QString ipAddress() const;
|
||||
Q_SCRIPTABLE QString androidId() const;
|
||||
Q_SCRIPTABLE bool multiWindows() const;
|
||||
Q_SCRIPTABLE void setMultiWindows(const bool multiWindows);
|
||||
Q_SCRIPTABLE bool suspend() const;
|
||||
Q_SCRIPTABLE void setSuspend(const bool suspend);
|
||||
Q_SCRIPTABLE bool uevent() const;
|
||||
Q_SCRIPTABLE void setUevent(const bool uevent);
|
||||
Q_SCRIPTABLE QList<QDBusObjectPath> applications() const;
|
||||
|
||||
Q_SCRIPTABLE void initialize(const int systemType, const int romType, const bool forced = false);
|
||||
Q_SCRIPTABLE void startSession();
|
||||
Q_SCRIPTABLE void stopSession();
|
||||
Q_SCRIPTABLE void resetWaydroid();
|
||||
Q_SCRIPTABLE void installApk(const QString apkFile);
|
||||
Q_SCRIPTABLE void deleteApplication(const QString appId);
|
||||
Q_SCRIPTABLE void refreshSessionInfo();
|
||||
Q_SCRIPTABLE void refreshAndroidId();
|
||||
Q_SCRIPTABLE void refreshApplications();
|
||||
|
||||
private:
|
||||
bool m_dbusInitialized{false};
|
||||
Status m_status{NotInitialized};
|
||||
SessionStatus m_sessionStatus{SessionStopped};
|
||||
SystemType m_systemType{SystemType::UnknownSystemType};
|
||||
SystemType m_systemType{UnknownSystemType};
|
||||
QString m_ipAddress{""};
|
||||
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};
|
||||
bool m_suspend{false};
|
||||
bool m_uevent{false};
|
||||
|
||||
void refreshSupportsInfo();
|
||||
void refreshInstallationInfo();
|
||||
void refreshPropsInfo();
|
||||
|
||||
/**
|
||||
* @brief Executes the command to retrieve the current session status and related
|
||||
* information from Waydroid.
|
||||
|
|
@ -208,4 +201,7 @@ private:
|
|||
|
||||
QString desktopFileDirectory();
|
||||
bool removeWaydroidApplications();
|
||||
|
||||
QString fetchApplicationsList();
|
||||
QList<WaydroidApplicationDBusObject::Ptr> m_applicationObjects;
|
||||
};
|
||||
|
|
@ -34,7 +34,7 @@ KCM.SimpleKCM {
|
|||
]
|
||||
|
||||
Connections {
|
||||
target: AIP.WaydroidState.applicationListModel
|
||||
target: AIP.WaydroidDBusClient
|
||||
|
||||
function onActionFinished(message: string): void {
|
||||
inlineMessage.text = message
|
||||
|
|
@ -42,13 +42,21 @@ KCM.SimpleKCM {
|
|||
inlineMessage.type = Kirigami.MessageType.Positive
|
||||
}
|
||||
|
||||
function onErrorOccurred(error: string): void {
|
||||
function onActionFailed(error: string): void {
|
||||
inlineMessage.text = error
|
||||
inlineMessage.visible = true
|
||||
inlineMessage.type = Kirigami.MessageType.Error
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: autoRefreshApplicationsTimer
|
||||
interval: 2000
|
||||
repeat: true
|
||||
running: root.visible
|
||||
onTriggered: AIP.WaydroidDBusClient.refreshApplications()
|
||||
}
|
||||
|
||||
FileDialog {
|
||||
id: fileDialog
|
||||
nameFilters: [ "APK files (*.apk)" ]
|
||||
|
|
@ -60,7 +68,7 @@ KCM.SimpleKCM {
|
|||
inlineMessage.visible = true
|
||||
inlineMessage.type = Kirigami.MessageType.Error
|
||||
} else {
|
||||
AIP.WaydroidState.applicationListModel.installApk(url.pathname)
|
||||
AIP.WaydroidDBusClient.installApk(url.pathname)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -79,7 +87,7 @@ KCM.SimpleKCM {
|
|||
|
||||
FormCard.FormCard {
|
||||
Repeater {
|
||||
model: AIP.WaydroidState.applicationListModel
|
||||
model: AIP.WaydroidDBusClient.applicationListModel
|
||||
|
||||
delegate: FormCard.AbstractFormDelegate {
|
||||
id: appDelegate
|
||||
|
|
@ -99,7 +107,7 @@ KCM.SimpleKCM {
|
|||
text: i18nc("@action:button", "Delete the application")
|
||||
icon.name: "usermenu-delete"
|
||||
|
||||
onClicked: AIP.WaydroidState.applicationListModel.deleteApplication(model.id)
|
||||
onClicked: AIP.WaydroidDBusClient.deleteApplication(model.id)
|
||||
|
||||
QQC2.ToolTip.visible: hovered
|
||||
QQC2.ToolTip.text: text
|
||||
|
|
|
|||
|
|
@ -22,12 +22,12 @@ ColumnLayout {
|
|||
FormCard.FormCard {
|
||||
FormCard.FormTextDelegate {
|
||||
text: i18n("IP address")
|
||||
description: AIP.WaydroidState.ipAddress
|
||||
description: AIP.WaydroidDBusClient.ipAddress
|
||||
trailing: PC3.Button {
|
||||
visible: AIP.WaydroidState.ipAddress !== ""
|
||||
visible: AIP.WaydroidDBusClient.ipAddress !== ""
|
||||
text: i18n("Copy")
|
||||
icon.name: 'edit-copy-symbolic'
|
||||
onClicked: AIP.WaydroidState.copyToClipboard(AIP.WaydroidState.ipAddress)
|
||||
onClicked: AIP.WaydroidDBusClient.copyToClipboard(AIP.WaydroidDBusClient.ipAddress)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -37,12 +37,12 @@ ColumnLayout {
|
|||
|
||||
trailing: PC3.Button {
|
||||
text: i18n("Stop session")
|
||||
onClicked: AIP.WaydroidState.stopSessionQml()
|
||||
onClicked: AIP.WaydroidDBusClient.stopSession()
|
||||
}
|
||||
}
|
||||
|
||||
FormCard.FormButtonDelegate {
|
||||
visible: AIP.WaydroidState.systemType === AIP.WaydroidState.Gapps
|
||||
visible: AIP.WaydroidDBusClient.systemType === AIP.WaydroidDBusClient.Gapps
|
||||
text: i18n("Certify my device for Google Play Protect")
|
||||
onClicked: kcm.push("WaydroidGooglePlayProtectConfigurationPage.qml")
|
||||
}
|
||||
|
|
@ -63,7 +63,7 @@ ColumnLayout {
|
|||
subtitle: i18n("Are you sure you want to reset Waydroid ? This is a destructive action, and will wipe all user data.")
|
||||
standardButtons: Kirigami.Dialog.Ok | Kirigami.Dialog.Cancel
|
||||
|
||||
onAccepted: AIP.WaydroidState.resetWaydroidQml()
|
||||
onAccepted: AIP.WaydroidDBusClient.resetWaydroid()
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -74,7 +74,7 @@ ColumnLayout {
|
|||
interval: 2000
|
||||
repeat: true
|
||||
running: root.visible
|
||||
onTriggered: AIP.WaydroidState.refreshSessionInfo()
|
||||
onTriggered: AIP.WaydroidDBusClient.refreshSessionInfo()
|
||||
}
|
||||
|
||||
FormCard.FormHeader {
|
||||
|
|
@ -96,7 +96,7 @@ ColumnLayout {
|
|||
}
|
||||
|
||||
Connections {
|
||||
target: AIP.WaydroidState
|
||||
target: AIP.WaydroidDBusClient
|
||||
|
||||
function onSessionStatusChanged() {
|
||||
infoMessage.visible = false
|
||||
|
|
@ -108,9 +108,9 @@ ColumnLayout {
|
|||
id: multiWindows
|
||||
text: i18n("Multi Windows")
|
||||
description: i18n("Enables/Disables window integration with the desktop")
|
||||
checked: AIP.WaydroidState.multiWindows
|
||||
checked: AIP.WaydroidDBusClient.multiWindows
|
||||
onToggled: {
|
||||
AIP.WaydroidState.multiWindows = checked
|
||||
AIP.WaydroidDBusClient.multiWindows = checked
|
||||
infoMessage.visible = true
|
||||
}
|
||||
}
|
||||
|
|
@ -121,9 +121,9 @@ ColumnLayout {
|
|||
id: suspend
|
||||
text: i18n("Suspend")
|
||||
description: i18n("Let the Waydroid container sleep (after the display timeout) when no apps are active")
|
||||
checked: AIP.WaydroidState.suspend
|
||||
checked: AIP.WaydroidDBusClient.suspend
|
||||
onToggled: {
|
||||
AIP.WaydroidState.suspend = checked
|
||||
AIP.WaydroidDBusClient.suspend = checked
|
||||
infoMessage.visible = true
|
||||
}
|
||||
}
|
||||
|
|
@ -134,9 +134,9 @@ ColumnLayout {
|
|||
id: uevent
|
||||
text: i18n("UEvent")
|
||||
description: i18n("Allow android direct access to hotplugged devices")
|
||||
checked: AIP.WaydroidState.uevent
|
||||
checked: AIP.WaydroidDBusClient.uevent
|
||||
onToggled: {
|
||||
AIP.WaydroidState.uevent = checked
|
||||
AIP.WaydroidDBusClient.uevent = checked
|
||||
infoMessage.visible = true
|
||||
}
|
||||
}
|
||||
|
|
|
|||
39
kcms/waydroidintegration/ui/WaydroidErrorPage.qml
Normal file
39
kcms/waydroidintegration/ui/WaydroidErrorPage.qml
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2025 Florian RICHER <florian.richer@protonmail.com>
|
||||
* SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
*/
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Layouts 1.15
|
||||
import QtQuick.Controls 2.15 as QQC2
|
||||
|
||||
import org.kde.kirigami 2.19 as Kirigami
|
||||
import org.kde.plasma.components 3.0 as PC3
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
|
||||
property string title
|
||||
property string message
|
||||
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.left: parent
|
||||
anchors.leftMargin: Kirigami.Units.largeSpacing
|
||||
anchors.right: parent
|
||||
anchors.rightMargin: Kirigami.Units.largeSpacing
|
||||
spacing: Kirigami.Units.largeSpacing
|
||||
|
||||
QQC2.Label {
|
||||
text: title
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
}
|
||||
|
||||
QQC2.TextArea {
|
||||
visible: message !== ""
|
||||
text: message
|
||||
readOnly: true
|
||||
wrapMode: TextEdit.Wrap
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
}
|
||||
|
|
@ -23,18 +23,18 @@ KCM.SimpleKCM {
|
|||
title: i18n("Google Play Protect configuration")
|
||||
|
||||
Component.onCompleted: {
|
||||
if (AIP.WaydroidState.androidId === "") {
|
||||
AIP.WaydroidState.refreshAndroidId()
|
||||
if (AIP.WaydroidDBusClient.androidId === "") {
|
||||
AIP.WaydroidDBusClient.refreshAndroidId()
|
||||
}
|
||||
}
|
||||
|
||||
WaydroidLoader {
|
||||
visible: AIP.WaydroidState.androidId === ""
|
||||
visible: AIP.WaydroidDBusClient.androidId === ""
|
||||
text: i18n("We fetching your Android ID.\nIt can take a few seconds.")
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
visible: AIP.WaydroidState.androidId !== ""
|
||||
visible: AIP.WaydroidDBusClient.androidId !== ""
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.left: parent
|
||||
anchors.leftMargin: Kirigami.Units.largeSpacing
|
||||
|
|
@ -52,7 +52,7 @@ KCM.SimpleKCM {
|
|||
icon.name: 'edit-copy-symbolic'
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
onClicked: {
|
||||
AIP.WaydroidState.copyToClipboard(AIP.WaydroidState.androidId)
|
||||
AIP.WaydroidDBusClient.copyToClipboard(AIP.WaydroidDBusClient.androidId)
|
||||
Qt.openUrlExternally("https://www.google.com/android/uncertified")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,8 +22,8 @@ ColumnLayout {
|
|||
text: i18n("System type")
|
||||
|
||||
model: [
|
||||
{"name": "Vanilla", "value": AIP.WaydroidState.Vanilla},
|
||||
{"name": "GAPPS", "value": AIP.WaydroidState.Gapps}
|
||||
{"name": "Vanilla", "value": AIP.WaydroidDBusClient.Vanilla},
|
||||
{"name": "GAPPS", "value": AIP.WaydroidDBusClient.Gapps}
|
||||
]
|
||||
|
||||
textRole: "name"
|
||||
|
|
@ -36,6 +36,6 @@ ColumnLayout {
|
|||
Layout.alignment: Qt.AlignHCenter
|
||||
enabled: systemType.currentValue !== undefined
|
||||
|
||||
onClicked: AIP.WaydroidState.initializeQml(systemType.currentValue, AIP.WaydroidState.Lineage)
|
||||
onClicked: AIP.WaydroidDBusClient.initialize(systemType.currentValue, AIP.WaydroidDBusClient.Lineage)
|
||||
}
|
||||
}
|
||||
|
|
@ -24,7 +24,7 @@ KCM.SimpleKCM {
|
|||
rightPadding: 0
|
||||
|
||||
ColumnLayout {
|
||||
visible: AIP.WaydroidState.errorTitle === "" && AIP.WaydroidState.status == AIP.WaydroidState.NotSupported
|
||||
visible: AIP.WaydroidDBusClient.status === AIP.WaydroidDBusClient.NotSupported
|
||||
anchors.centerIn: parent
|
||||
spacing: Kirigami.Units.largeSpacing
|
||||
|
||||
|
|
@ -38,21 +38,21 @@ KCM.SimpleKCM {
|
|||
PC3.Button {
|
||||
text: i18n("Check installation")
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
onClicked: AIP.WaydroidState.refreshSupportsInfo()
|
||||
onClicked: AIP.WaydroidDBusClient.refreshSupportsInfo()
|
||||
}
|
||||
}
|
||||
|
||||
WaydroidInitialConfigurationForm {
|
||||
visible: AIP.WaydroidState.errorTitle === "" && AIP.WaydroidState.status == AIP.WaydroidState.NotInitialized
|
||||
visible: AIP.WaydroidDBusClient.status === AIP.WaydroidDBusClient.NotInitialized
|
||||
}
|
||||
|
||||
WaydroidDownloadStatus {
|
||||
id: downloadStatus
|
||||
visible: AIP.WaydroidState.errorTitle === "" && AIP.WaydroidState.status == AIP.WaydroidState.Initializing
|
||||
visible: AIP.WaydroidDBusClient.status === AIP.WaydroidDBusClient.Initializing
|
||||
text: i18n("Downloading Android and vendor images.\nIt can take a few minutes.")
|
||||
|
||||
Connections {
|
||||
target: AIP.WaydroidState
|
||||
target: AIP.WaydroidDBusClient
|
||||
|
||||
function onDownloadStatusChanged(downloaded, total, speed) {
|
||||
downloadStatus.downloaded = downloaded
|
||||
|
|
@ -63,12 +63,12 @@ KCM.SimpleKCM {
|
|||
}
|
||||
|
||||
WaydroidLoader {
|
||||
visible: AIP.WaydroidState.errorTitle === "" && AIP.WaydroidState.status == AIP.WaydroidState.Resetting
|
||||
visible: AIP.WaydroidDBusClient.status === AIP.WaydroidDBusClient.Resetting
|
||||
text: i18n("Waydroid is resetting.\nIt can take a few seconds.")
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
visible: AIP.WaydroidState.errorTitle === "" && AIP.WaydroidState.status == AIP.WaydroidState.Initialized && AIP.WaydroidState.sessionStatus == AIP.WaydroidState.SessionStopped
|
||||
visible: AIP.WaydroidDBusClient.status === AIP.WaydroidDBusClient.Initialized && AIP.WaydroidDBusClient.sessionStatus === AIP.WaydroidDBusClient.SessionStopped
|
||||
anchors.centerIn: parent
|
||||
spacing: Kirigami.Units.largeSpacing
|
||||
|
||||
|
|
@ -81,46 +81,24 @@ KCM.SimpleKCM {
|
|||
PC3.Button {
|
||||
text: i18n("Start the session")
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
onClicked: AIP.WaydroidState.startSessionQml()
|
||||
onClicked: AIP.WaydroidDBusClient.startSession()
|
||||
}
|
||||
}
|
||||
|
||||
WaydroidLoader {
|
||||
visible: AIP.WaydroidState.errorTitle === "" && AIP.WaydroidState.status == AIP.WaydroidState.Initialized && AIP.WaydroidState.sessionStatus == AIP.WaydroidState.SessionStarting
|
||||
visible: AIP.WaydroidDBusClient.status === AIP.WaydroidDBusClient.Initialized && AIP.WaydroidDBusClient.sessionStatus === AIP.WaydroidDBusClient.SessionStarting
|
||||
text: i18n("Waydroid session is starting.\nIt can take a few seconds.")
|
||||
}
|
||||
|
||||
WaydroidConfigurationForm {
|
||||
visible: AIP.WaydroidState.errorTitle === "" && AIP.WaydroidState.status == AIP.WaydroidState.Initialized && AIP.WaydroidState.sessionStatus == AIP.WaydroidState.SessionRunning
|
||||
visible: AIP.WaydroidDBusClient.status === AIP.WaydroidDBusClient.Initialized && AIP.WaydroidDBusClient.sessionStatus === AIP.WaydroidDBusClient.SessionRunning
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
visible: AIP.WaydroidState.errorTitle !== ""
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.left: parent
|
||||
anchors.leftMargin: Kirigami.Units.largeSpacing
|
||||
anchors.right: parent
|
||||
anchors.rightMargin: Kirigami.Units.largeSpacing
|
||||
spacing: Kirigami.Units.largeSpacing
|
||||
Connections {
|
||||
target: AIP.WaydroidDBusClient
|
||||
|
||||
QQC2.Label {
|
||||
text: AIP.WaydroidState.errorTitle
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
}
|
||||
|
||||
QQC2.TextArea {
|
||||
visible: AIP.WaydroidState.errorMessage !== ""
|
||||
text: AIP.WaydroidState.errorMessage
|
||||
readOnly: true
|
||||
wrapMode: TextEdit.Wrap
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
PC3.Button {
|
||||
text: i18n("Go back")
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
onClicked: AIP.WaydroidState.resetError()
|
||||
function onErrorOccurred(title, message) {
|
||||
kcm.push("WaydroidErrorPage.qml", { title, message })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -9,18 +9,36 @@ import org.kde.plasma.private.mobileshell.waydroidintegrationplugin as AIP
|
|||
|
||||
QS.QuickSetting {
|
||||
text: i18nc("@action:button", "Waydroid")
|
||||
status: AIP.WaydroidState.sessionStatus === AIP.WaydroidState.SessionRunning ? i18nc("@info:status", "Running") : i18nc("@info:status", "Stopped")
|
||||
status: statusText()
|
||||
icon: "folder-android-symbolic"
|
||||
settingsCommand: "plasma-open-settings kcm_waydroidintegration"
|
||||
|
||||
enabled: AIP.WaydroidState.sessionStatus === AIP.WaydroidState.SessionRunning
|
||||
available: AIP.WaydroidState.status === AIP.WaydroidState.Initialized
|
||||
available: AIP.WaydroidDBusClient.status !== AIP.WaydroidDBusClient.NotSupported
|
||||
enabled: AIP.WaydroidDBusClient.sessionStatus === AIP.WaydroidDBusClient.SessionRunning
|
||||
|
||||
Component.onCompleted: {
|
||||
AIP.WaydroidDBusObject.registerObject()
|
||||
}
|
||||
|
||||
function toggle(): void {
|
||||
if (AIP.WaydroidState.sessionStatus === AIP.WaydroidState.SessionRunning) {
|
||||
AIP.WaydroidState.stopSessionQml()
|
||||
if (AIP.WaydroidDBusClient.status !== AIP.WaydroidDBusClient.Initialized) {
|
||||
return
|
||||
}
|
||||
|
||||
if (AIP.WaydroidDBusClient.sessionStatus === AIP.WaydroidDBusClient.SessionRunning) {
|
||||
AIP.WaydroidDBusClient.stopSession()
|
||||
} else {
|
||||
AIP.WaydroidState.startSessionQml()
|
||||
AIP.WaydroidDBusClient.startSession()
|
||||
}
|
||||
}
|
||||
|
||||
function statusText(): string {
|
||||
if (AIP.WaydroidDBusClient.status !== AIP.WaydroidDBusClient.Initialized) {
|
||||
return i18nc("@info:status", "Not initialized")
|
||||
} else if (AIP.WaydroidDBusClient.sessionStatus === AIP.WaydroidDBusClient.SessionRunning) {
|
||||
return i18nc("@info:status", "Running")
|
||||
} else {
|
||||
return i18nc("@info:status", "Stopped")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue