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-FileCopyrightText: 2025 Florian RICHER <florian.richer@protonmail.com>
|
||||||
# SPDX-License-Identifier: BSD-2-Clause
|
# 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)
|
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
|
target_link_libraries(waydroidintegrationplugin PRIVATE
|
||||||
|
Qt::DBus
|
||||||
Qt::Gui
|
Qt::Gui
|
||||||
Qt::Qml
|
Qt::Qml
|
||||||
Qt::Quick
|
Qt::Quick
|
||||||
|
|
@ -12,6 +53,7 @@ target_link_libraries(waydroidintegrationplugin PRIVATE
|
||||||
KF6::ConfigCore
|
KF6::ConfigCore
|
||||||
KF6::I18n
|
KF6::I18n
|
||||||
QCoro::Core
|
QCoro::Core
|
||||||
|
QCoro::DBus
|
||||||
QCoro::Qml
|
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 "waydroidapplicationlistmodel.h"
|
||||||
#include "waydroidintegrationplugin_debug.h"
|
#include "waydroidapplicationdbusclient.h"
|
||||||
#include "waydroidshared.h"
|
#include "waydroiddbusclient.h"
|
||||||
|
|
||||||
#include <QLoggingCategory>
|
|
||||||
#include <QProcess>
|
|
||||||
#include <QStringLiteral>
|
|
||||||
|
|
||||||
#include <KLocalizedString>
|
#include <KLocalizedString>
|
||||||
|
|
||||||
using namespace Qt::StringLiterals;
|
using namespace Qt::StringLiterals;
|
||||||
using namespace std::chrono_literals;
|
using namespace std::chrono_literals;
|
||||||
|
|
||||||
WaydroidApplicationListModel::WaydroidApplicationListModel(WaydroidState *parent)
|
WaydroidApplicationListModel::WaydroidApplicationListModel(WaydroidDBusClient *parent)
|
||||||
: QAbstractListModel{parent}
|
: QAbstractListModel{parent}
|
||||||
, m_waydroidState{parent}
|
, m_waydroidDBusClient{parent}
|
||||||
, m_refreshTimer{new QTimer{this}}
|
|
||||||
{
|
{
|
||||||
// Waydroid does not return all installed applications immediately, so we need to refresh regularly.
|
|
||||||
m_refreshTimer->setInterval(1s);
|
|
||||||
m_refreshTimer->setSingleShot(false);
|
|
||||||
m_refreshTimer->start();
|
|
||||||
|
|
||||||
connect(m_refreshTimer, &QTimer::timeout, this, &WaydroidApplicationListModel::refreshApplications);
|
|
||||||
connect(parent, &WaydroidState::sessionStatusChanged, this, &WaydroidApplicationListModel::refreshApplications);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
WaydroidApplicationListModel::~WaydroidApplicationListModel() = default;
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
qCDebug(WAYDROIDINTEGRATIONPLUGIN) << "Reload waydroid apps";
|
beginResetModel();
|
||||||
|
for (const QDBusObjectPath &applicationObjectPath : applicationObjectPaths) {
|
||||||
QMap<QString, int> appIdMap; // <packageName, index>
|
auto client = std::make_shared<WaydroidApplicationDBusClient>(applicationObjectPath, this);
|
||||||
for (int i = 0; i < m_applications.size(); ++i) {
|
m_applications.append(client);
|
||||||
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();
|
|
||||||
}
|
}
|
||||||
|
endResetModel();
|
||||||
}
|
}
|
||||||
|
|
||||||
void WaydroidApplicationListModel::refreshApplications()
|
void WaydroidApplicationListModel::addApplication(const QDBusObjectPath &objectPath)
|
||||||
{
|
{
|
||||||
QList<WaydroidApplication::Ptr> applications;
|
beginInsertRows({}, m_applications.size(), m_applications.size());
|
||||||
|
auto client = std::make_shared<WaydroidApplicationDBusClient>(objectPath, this);
|
||||||
QStringList arguments = {u"app"_s, u"list"_s};
|
connect(client.get(), &WaydroidApplicationDBusClient::nameChanged, this, [this, objectPath] {
|
||||||
|
updateApplication(objectPath, {Qt::DisplayRole, DelegateRole, NameRole});
|
||||||
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);
|
|
||||||
});
|
});
|
||||||
|
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
|
QHash<int, QByteArray> WaydroidApplicationListModel::roleNames() const
|
||||||
|
|
@ -135,7 +91,7 @@ QVariant WaydroidApplicationListModel::data(const QModelIndex &index, int role)
|
||||||
return QVariant();
|
return QVariant();
|
||||||
}
|
}
|
||||||
|
|
||||||
WaydroidApplication::Ptr app = m_applications.at(index.row());
|
WaydroidApplicationDBusClient::Ptr app = m_applications.at(index.row());
|
||||||
|
|
||||||
switch (role) {
|
switch (role) {
|
||||||
case Qt::DisplayRole:
|
case Qt::DisplayRole:
|
||||||
|
|
@ -158,43 +114,3 @@ int WaydroidApplicationListModel::rowCount(const QModelIndex &parent) const
|
||||||
|
|
||||||
return m_applications.count();
|
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
|
#pragma once
|
||||||
|
|
||||||
#include "waydroidapplication.h"
|
#include "waydroidapplicationdbusclient.h"
|
||||||
#include "waydroidstate.h"
|
|
||||||
|
|
||||||
#include <QAbstractListModel>
|
#include <QAbstractListModel>
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
#include <QTimer>
|
#include <QTimer>
|
||||||
|
|
||||||
class WaydroidState;
|
class WaydroidDBusClient;
|
||||||
|
|
||||||
class WaydroidApplicationListModel : public QAbstractListModel
|
class WaydroidApplicationListModel : public QAbstractListModel
|
||||||
{
|
{
|
||||||
|
|
@ -26,25 +25,23 @@ public:
|
||||||
IdRole
|
IdRole
|
||||||
};
|
};
|
||||||
|
|
||||||
WaydroidApplicationListModel(WaydroidState *parent = nullptr);
|
explicit WaydroidApplicationListModel(WaydroidDBusClient *parent = nullptr);
|
||||||
~WaydroidApplicationListModel() override;
|
~WaydroidApplicationListModel() override;
|
||||||
|
|
||||||
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||||
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
|
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
|
||||||
QHash<int, QByteArray> roleNames() const override;
|
QHash<int, QByteArray> roleNames() const override;
|
||||||
|
|
||||||
Q_INVOKABLE void installApk(const QString apkFile);
|
void initializeApplications(const QList<QDBusObjectPath> &applicationObjectPaths);
|
||||||
Q_INVOKABLE void deleteApplication(const QString appId);
|
|
||||||
|
|
||||||
Q_SIGNALS:
|
public Q_SLOTS:
|
||||||
void actionFinished(const QString message);
|
void addApplication(const QDBusObjectPath &objectPath);
|
||||||
void errorOccurred(const QString message);
|
void removeApplication(const QDBusObjectPath &objectPath);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
WaydroidState *m_waydroidState{nullptr};
|
WaydroidDBusClient *m_waydroidDBusClient{nullptr};
|
||||||
QList<WaydroidApplication::Ptr> m_applications;
|
QList<WaydroidApplicationDBusClient::Ptr> m_applications;
|
||||||
QTimer *m_refreshTimer{nullptr};
|
QTimer *m_refreshTimer{nullptr};
|
||||||
|
|
||||||
void loadApplications(const QList<WaydroidApplication::Ptr> applications);
|
void updateApplication(const QDBusObjectPath &objectPath, const QList<int> &roles);
|
||||||
void refreshApplications();
|
|
||||||
};
|
};
|
||||||
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
|
* 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 "waydroidintegrationplugin_debug.h"
|
||||||
#include "waydroidshared.h"
|
#include "waydroidshared.h"
|
||||||
|
|
||||||
#include <QClipboard>
|
#include <QDBusConnection>
|
||||||
#include <QCoroProcess>
|
|
||||||
#include <QDebug>
|
|
||||||
#include <QDir>
|
#include <QDir>
|
||||||
#include <QGuiApplication>
|
#include <QLoggingCategory>
|
||||||
#include <QProcess>
|
#include <QProcess>
|
||||||
#include <QRegularExpression>
|
#include <QRegularExpression>
|
||||||
#include <QStandardPaths>
|
#include <QStandardPaths>
|
||||||
#include <QTimer>
|
#include <QTimer>
|
||||||
#include <QtLogging>
|
|
||||||
|
|
||||||
#include <KAuth/Action>
|
|
||||||
#include <KAuth/ExecuteJob>
|
#include <KAuth/ExecuteJob>
|
||||||
#include <KConfigGroup>
|
#include <KConfigGroup>
|
||||||
#include <KDesktopFile>
|
#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 ipAdressRegExp(u"IP address:\\s*(\\d+\\.\\d+\\.\\d+\\.\\d+)"_s);
|
||||||
static const QRegularExpression systemOtaRegExp(u"system_ota\\s*=\\s*(\\S+)"_s);
|
static const QRegularExpression systemOtaRegExp(u"system_ota\\s*=\\s*(\\S+)"_s);
|
||||||
|
|
||||||
WaydroidState::WaydroidState(QObject *parent)
|
WaydroidDBusObject::WaydroidDBusObject(QObject *parent)
|
||||||
: 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);
|
QProcess *process = new QProcess(this);
|
||||||
process->start(WAYDROID_COMMAND, arguments);
|
process->start(WAYDROID_COMMAND, arguments);
|
||||||
process->waitForFinished();
|
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) {
|
if (exitCode != 0) {
|
||||||
m_status = NotSupported;
|
m_status = NotSupported;
|
||||||
Q_EMIT statusChanged();
|
Q_EMIT statusChanged();
|
||||||
|
|
@ -72,7 +373,7 @@ void WaydroidState::refreshSupportsInfo()
|
||||||
Q_EMIT statusChanged();
|
Q_EMIT statusChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
void WaydroidState::refreshInstallationInfo()
|
void WaydroidDBusObject::refreshInstallationInfo()
|
||||||
{
|
{
|
||||||
if (m_status != Initialized) {
|
if (m_status != Initialized) {
|
||||||
return;
|
return;
|
||||||
|
|
@ -99,7 +400,7 @@ void WaydroidState::refreshInstallationInfo()
|
||||||
Q_EMIT systemTypeChanged();
|
Q_EMIT systemTypeChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
void WaydroidState::refreshSessionInfo()
|
void WaydroidDBusObject::refreshSessionInfo()
|
||||||
{
|
{
|
||||||
if (m_status != Initialized) {
|
if (m_status != Initialized) {
|
||||||
return;
|
return;
|
||||||
|
|
@ -108,7 +409,7 @@ void WaydroidState::refreshSessionInfo()
|
||||||
const QString output = fetchSessionInfo();
|
const QString output = fetchSessionInfo();
|
||||||
|
|
||||||
const QString sessionMatchResult = extractRegExp(output, sessionRegExp);
|
const QString sessionMatchResult = extractRegExp(output, sessionRegExp);
|
||||||
WaydroidState::SessionStatus newSessionStatus;
|
SessionStatus newSessionStatus;
|
||||||
|
|
||||||
if (!sessionMatchResult.isEmpty()) {
|
if (!sessionMatchResult.isEmpty()) {
|
||||||
newSessionStatus = sessionMatchResult.contains("RUNNING") ? SessionRunning : SessionStopped;
|
newSessionStatus = sessionMatchResult.contains("RUNNING") ? SessionRunning : SessionStopped;
|
||||||
|
|
@ -125,7 +426,18 @@ void WaydroidState::refreshSessionInfo()
|
||||||
Q_EMIT ipAddressChanged();
|
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) {
|
if (m_status != Initialized) {
|
||||||
return;
|
return;
|
||||||
|
|
@ -150,7 +462,7 @@ void WaydroidState::refreshAndroidId()
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void WaydroidState::refreshPropsInfo()
|
void WaydroidDBusObject::refreshPropsInfo()
|
||||||
{
|
{
|
||||||
if (m_sessionStatus != SessionRunning) {
|
if (m_sessionStatus != SessionRunning) {
|
||||||
return;
|
return;
|
||||||
|
|
@ -169,322 +481,7 @@ void WaydroidState::refreshPropsInfo()
|
||||||
Q_EMIT ueventChanged();
|
Q_EMIT ueventChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
void WaydroidState::resetError()
|
QString WaydroidDBusObject::fetchPropValue(const QString key, const QString defaultValue)
|
||||||
{
|
|
||||||
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)
|
|
||||||
{
|
{
|
||||||
const QStringList arguments{u"prop"_s, u"get"_s, key};
|
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;
|
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};
|
const QStringList arguments{u"prop"_s, u"set"_s, key, value};
|
||||||
|
|
||||||
QProcess *process = new QProcess(this);
|
auto process = QProcess(this);
|
||||||
process->start(WAYDROID_COMMAND, arguments);
|
process.start(WAYDROID_COMMAND, arguments);
|
||||||
process->waitForFinished();
|
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);
|
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) {
|
if (m_sessionStatus != SessionStarting) {
|
||||||
return;
|
return;
|
||||||
|
|
@ -547,7 +544,7 @@ void WaydroidState::checkSessionStarting(const int limit, const int tried)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QString WaydroidState::desktopFileDirectory()
|
QString WaydroidDBusObject::desktopFileDirectory()
|
||||||
{
|
{
|
||||||
auto dir = []() -> QString {
|
auto dir = []() -> QString {
|
||||||
if (KSandbox::isFlatpak()) {
|
if (KSandbox::isFlatpak()) {
|
||||||
|
|
@ -561,7 +558,7 @@ QString WaydroidState::desktopFileDirectory()
|
||||||
return dir;
|
return dir;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool WaydroidState::removeWaydroidApplications()
|
bool WaydroidDBusObject::removeWaydroidApplications()
|
||||||
{
|
{
|
||||||
const QDir appsDir(desktopFileDirectory());
|
const QDir appsDir(desktopFileDirectory());
|
||||||
const auto fileInfos = appsDir.entryInfoList(QDir::Files);
|
const auto fileInfos = appsDir.entryInfoList(QDir::Files);
|
||||||
|
|
@ -595,3 +592,86 @@ bool WaydroidState::removeWaydroidApplications()
|
||||||
|
|
||||||
return allFileRemoved;
|
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
|
#pragma once
|
||||||
|
|
||||||
#include "waydroidapplicationlistmodel.h"
|
#include "waydroidapplicationdbusobject.h"
|
||||||
|
|
||||||
#include <QCoroCore>
|
#include <QDBusObjectPath>
|
||||||
#include <QCoroQmlTask>
|
#include <QList>
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
|
#include <QString>
|
||||||
|
|
||||||
#include <qqmlregistration.h>
|
#include <qqmlregistration.h>
|
||||||
#include <qtmetamacros.h>
|
|
||||||
|
|
||||||
class WaydroidApplicationListModel;
|
class WaydroidApplicationDBusObject;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class provides an interface to interact with the Waydroid container,
|
* This class provides an interface to interact with the Waydroid container,
|
||||||
|
|
@ -23,26 +23,15 @@ class WaydroidApplicationListModel;
|
||||||
*
|
*
|
||||||
* @author Florian RICHER <florian.richer@protonmail.com>
|
* @author Florian RICHER <florian.richer@protonmail.com>
|
||||||
*/
|
*/
|
||||||
class WaydroidState : public QObject
|
class WaydroidDBusObject : public QObject
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
QML_ELEMENT
|
QML_ELEMENT
|
||||||
QML_SINGLETON
|
QML_SINGLETON
|
||||||
|
Q_CLASSINFO("D-Bus Interface", "org.kde.plasmashell.Waydroid")
|
||||||
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)
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
WaydroidState(QObject *parent = nullptr);
|
explicit WaydroidDBusObject(QObject *parent = nullptr);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @enum Status
|
* @enum Status
|
||||||
|
|
@ -92,68 +81,72 @@ public:
|
||||||
};
|
};
|
||||||
Q_ENUM(RomType)
|
Q_ENUM(RomType)
|
||||||
|
|
||||||
Q_INVOKABLE void refreshSupportsInfo();
|
// called by QML
|
||||||
Q_INVOKABLE void refreshInstallationInfo();
|
Q_INVOKABLE void registerObject();
|
||||||
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);
|
|
||||||
|
|
||||||
Q_SIGNALS:
|
Q_SIGNALS:
|
||||||
void statusChanged();
|
Q_SCRIPTABLE void statusChanged();
|
||||||
// download and total is in MB and speed in Kbps
|
// download and total is in MB and speed in Kbps
|
||||||
void downloadStatusChanged(float downloaded, float total, float speed);
|
Q_SCRIPTABLE void downloadStatusChanged(double downloaded, double total, double speed);
|
||||||
void sessionStatusChanged();
|
Q_SCRIPTABLE void sessionStatusChanged();
|
||||||
void systemTypeChanged();
|
Q_SCRIPTABLE void systemTypeChanged();
|
||||||
void ipAddressChanged();
|
Q_SCRIPTABLE void ipAddressChanged();
|
||||||
void multiWindowsChanged();
|
Q_SCRIPTABLE void androidIdChanged();
|
||||||
void suspendChanged();
|
Q_SCRIPTABLE void multiWindowsChanged();
|
||||||
void ueventChanged();
|
Q_SCRIPTABLE void suspendChanged();
|
||||||
void errorTitleChanged();
|
Q_SCRIPTABLE void ueventChanged();
|
||||||
void errorMessageChanged();
|
|
||||||
void androidIdChanged();
|
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:
|
private:
|
||||||
|
bool m_dbusInitialized{false};
|
||||||
Status m_status{NotInitialized};
|
Status m_status{NotInitialized};
|
||||||
SessionStatus m_sessionStatus{SessionStopped};
|
SessionStatus m_sessionStatus{SessionStopped};
|
||||||
SystemType m_systemType{SystemType::UnknownSystemType};
|
SystemType m_systemType{UnknownSystemType};
|
||||||
QString m_ipAddress{""};
|
QString m_ipAddress{""};
|
||||||
QString m_errorTitle{""};
|
|
||||||
QString m_errorMessage{""};
|
|
||||||
QString m_androidId{""};
|
QString m_androidId{""};
|
||||||
WaydroidApplicationListModel *m_applicationListModel{nullptr};
|
|
||||||
|
|
||||||
// Waydroid props. See https://docs.waydro.id/usage/waydroid-prop-options
|
// Waydroid props. See https://docs.waydro.id/usage/waydroid-prop-options
|
||||||
bool m_multiWindows{false};
|
bool m_multiWindows{false};
|
||||||
bool m_suspend{false};
|
bool m_suspend{false};
|
||||||
bool m_uevent{false};
|
bool m_uevent{false};
|
||||||
|
|
||||||
|
void refreshSupportsInfo();
|
||||||
|
void refreshInstallationInfo();
|
||||||
|
void refreshPropsInfo();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Executes the command to retrieve the current session status and related
|
* @brief Executes the command to retrieve the current session status and related
|
||||||
* information from Waydroid.
|
* information from Waydroid.
|
||||||
|
|
@ -208,4 +201,7 @@ private:
|
||||||
|
|
||||||
QString desktopFileDirectory();
|
QString desktopFileDirectory();
|
||||||
bool removeWaydroidApplications();
|
bool removeWaydroidApplications();
|
||||||
|
|
||||||
|
QString fetchApplicationsList();
|
||||||
|
QList<WaydroidApplicationDBusObject::Ptr> m_applicationObjects;
|
||||||
};
|
};
|
||||||
|
|
@ -34,7 +34,7 @@ KCM.SimpleKCM {
|
||||||
]
|
]
|
||||||
|
|
||||||
Connections {
|
Connections {
|
||||||
target: AIP.WaydroidState.applicationListModel
|
target: AIP.WaydroidDBusClient
|
||||||
|
|
||||||
function onActionFinished(message: string): void {
|
function onActionFinished(message: string): void {
|
||||||
inlineMessage.text = message
|
inlineMessage.text = message
|
||||||
|
|
@ -42,13 +42,21 @@ KCM.SimpleKCM {
|
||||||
inlineMessage.type = Kirigami.MessageType.Positive
|
inlineMessage.type = Kirigami.MessageType.Positive
|
||||||
}
|
}
|
||||||
|
|
||||||
function onErrorOccurred(error: string): void {
|
function onActionFailed(error: string): void {
|
||||||
inlineMessage.text = error
|
inlineMessage.text = error
|
||||||
inlineMessage.visible = true
|
inlineMessage.visible = true
|
||||||
inlineMessage.type = Kirigami.MessageType.Error
|
inlineMessage.type = Kirigami.MessageType.Error
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Timer {
|
||||||
|
id: autoRefreshApplicationsTimer
|
||||||
|
interval: 2000
|
||||||
|
repeat: true
|
||||||
|
running: root.visible
|
||||||
|
onTriggered: AIP.WaydroidDBusClient.refreshApplications()
|
||||||
|
}
|
||||||
|
|
||||||
FileDialog {
|
FileDialog {
|
||||||
id: fileDialog
|
id: fileDialog
|
||||||
nameFilters: [ "APK files (*.apk)" ]
|
nameFilters: [ "APK files (*.apk)" ]
|
||||||
|
|
@ -60,7 +68,7 @@ KCM.SimpleKCM {
|
||||||
inlineMessage.visible = true
|
inlineMessage.visible = true
|
||||||
inlineMessage.type = Kirigami.MessageType.Error
|
inlineMessage.type = Kirigami.MessageType.Error
|
||||||
} else {
|
} else {
|
||||||
AIP.WaydroidState.applicationListModel.installApk(url.pathname)
|
AIP.WaydroidDBusClient.installApk(url.pathname)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -79,7 +87,7 @@ KCM.SimpleKCM {
|
||||||
|
|
||||||
FormCard.FormCard {
|
FormCard.FormCard {
|
||||||
Repeater {
|
Repeater {
|
||||||
model: AIP.WaydroidState.applicationListModel
|
model: AIP.WaydroidDBusClient.applicationListModel
|
||||||
|
|
||||||
delegate: FormCard.AbstractFormDelegate {
|
delegate: FormCard.AbstractFormDelegate {
|
||||||
id: appDelegate
|
id: appDelegate
|
||||||
|
|
@ -99,7 +107,7 @@ KCM.SimpleKCM {
|
||||||
text: i18nc("@action:button", "Delete the application")
|
text: i18nc("@action:button", "Delete the application")
|
||||||
icon.name: "usermenu-delete"
|
icon.name: "usermenu-delete"
|
||||||
|
|
||||||
onClicked: AIP.WaydroidState.applicationListModel.deleteApplication(model.id)
|
onClicked: AIP.WaydroidDBusClient.deleteApplication(model.id)
|
||||||
|
|
||||||
QQC2.ToolTip.visible: hovered
|
QQC2.ToolTip.visible: hovered
|
||||||
QQC2.ToolTip.text: text
|
QQC2.ToolTip.text: text
|
||||||
|
|
|
||||||
|
|
@ -22,12 +22,12 @@ ColumnLayout {
|
||||||
FormCard.FormCard {
|
FormCard.FormCard {
|
||||||
FormCard.FormTextDelegate {
|
FormCard.FormTextDelegate {
|
||||||
text: i18n("IP address")
|
text: i18n("IP address")
|
||||||
description: AIP.WaydroidState.ipAddress
|
description: AIP.WaydroidDBusClient.ipAddress
|
||||||
trailing: PC3.Button {
|
trailing: PC3.Button {
|
||||||
visible: AIP.WaydroidState.ipAddress !== ""
|
visible: AIP.WaydroidDBusClient.ipAddress !== ""
|
||||||
text: i18n("Copy")
|
text: i18n("Copy")
|
||||||
icon.name: 'edit-copy-symbolic'
|
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 {
|
trailing: PC3.Button {
|
||||||
text: i18n("Stop session")
|
text: i18n("Stop session")
|
||||||
onClicked: AIP.WaydroidState.stopSessionQml()
|
onClicked: AIP.WaydroidDBusClient.stopSession()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
FormCard.FormButtonDelegate {
|
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")
|
text: i18n("Certify my device for Google Play Protect")
|
||||||
onClicked: kcm.push("WaydroidGooglePlayProtectConfigurationPage.qml")
|
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.")
|
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
|
standardButtons: Kirigami.Dialog.Ok | Kirigami.Dialog.Cancel
|
||||||
|
|
||||||
onAccepted: AIP.WaydroidState.resetWaydroidQml()
|
onAccepted: AIP.WaydroidDBusClient.resetWaydroid()
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -74,7 +74,7 @@ ColumnLayout {
|
||||||
interval: 2000
|
interval: 2000
|
||||||
repeat: true
|
repeat: true
|
||||||
running: root.visible
|
running: root.visible
|
||||||
onTriggered: AIP.WaydroidState.refreshSessionInfo()
|
onTriggered: AIP.WaydroidDBusClient.refreshSessionInfo()
|
||||||
}
|
}
|
||||||
|
|
||||||
FormCard.FormHeader {
|
FormCard.FormHeader {
|
||||||
|
|
@ -96,7 +96,7 @@ ColumnLayout {
|
||||||
}
|
}
|
||||||
|
|
||||||
Connections {
|
Connections {
|
||||||
target: AIP.WaydroidState
|
target: AIP.WaydroidDBusClient
|
||||||
|
|
||||||
function onSessionStatusChanged() {
|
function onSessionStatusChanged() {
|
||||||
infoMessage.visible = false
|
infoMessage.visible = false
|
||||||
|
|
@ -108,9 +108,9 @@ ColumnLayout {
|
||||||
id: multiWindows
|
id: multiWindows
|
||||||
text: i18n("Multi Windows")
|
text: i18n("Multi Windows")
|
||||||
description: i18n("Enables/Disables window integration with the desktop")
|
description: i18n("Enables/Disables window integration with the desktop")
|
||||||
checked: AIP.WaydroidState.multiWindows
|
checked: AIP.WaydroidDBusClient.multiWindows
|
||||||
onToggled: {
|
onToggled: {
|
||||||
AIP.WaydroidState.multiWindows = checked
|
AIP.WaydroidDBusClient.multiWindows = checked
|
||||||
infoMessage.visible = true
|
infoMessage.visible = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -121,9 +121,9 @@ ColumnLayout {
|
||||||
id: suspend
|
id: suspend
|
||||||
text: i18n("Suspend")
|
text: i18n("Suspend")
|
||||||
description: i18n("Let the Waydroid container sleep (after the display timeout) when no apps are active")
|
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: {
|
onToggled: {
|
||||||
AIP.WaydroidState.suspend = checked
|
AIP.WaydroidDBusClient.suspend = checked
|
||||||
infoMessage.visible = true
|
infoMessage.visible = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -134,9 +134,9 @@ ColumnLayout {
|
||||||
id: uevent
|
id: uevent
|
||||||
text: i18n("UEvent")
|
text: i18n("UEvent")
|
||||||
description: i18n("Allow android direct access to hotplugged devices")
|
description: i18n("Allow android direct access to hotplugged devices")
|
||||||
checked: AIP.WaydroidState.uevent
|
checked: AIP.WaydroidDBusClient.uevent
|
||||||
onToggled: {
|
onToggled: {
|
||||||
AIP.WaydroidState.uevent = checked
|
AIP.WaydroidDBusClient.uevent = checked
|
||||||
infoMessage.visible = true
|
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")
|
title: i18n("Google Play Protect configuration")
|
||||||
|
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
if (AIP.WaydroidState.androidId === "") {
|
if (AIP.WaydroidDBusClient.androidId === "") {
|
||||||
AIP.WaydroidState.refreshAndroidId()
|
AIP.WaydroidDBusClient.refreshAndroidId()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
WaydroidLoader {
|
WaydroidLoader {
|
||||||
visible: AIP.WaydroidState.androidId === ""
|
visible: AIP.WaydroidDBusClient.androidId === ""
|
||||||
text: i18n("We fetching your Android ID.\nIt can take a few seconds.")
|
text: i18n("We fetching your Android ID.\nIt can take a few seconds.")
|
||||||
}
|
}
|
||||||
|
|
||||||
ColumnLayout {
|
ColumnLayout {
|
||||||
visible: AIP.WaydroidState.androidId !== ""
|
visible: AIP.WaydroidDBusClient.androidId !== ""
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
anchors.left: parent
|
anchors.left: parent
|
||||||
anchors.leftMargin: Kirigami.Units.largeSpacing
|
anchors.leftMargin: Kirigami.Units.largeSpacing
|
||||||
|
|
@ -52,7 +52,7 @@ KCM.SimpleKCM {
|
||||||
icon.name: 'edit-copy-symbolic'
|
icon.name: 'edit-copy-symbolic'
|
||||||
Layout.alignment: Qt.AlignHCenter
|
Layout.alignment: Qt.AlignHCenter
|
||||||
onClicked: {
|
onClicked: {
|
||||||
AIP.WaydroidState.copyToClipboard(AIP.WaydroidState.androidId)
|
AIP.WaydroidDBusClient.copyToClipboard(AIP.WaydroidDBusClient.androidId)
|
||||||
Qt.openUrlExternally("https://www.google.com/android/uncertified")
|
Qt.openUrlExternally("https://www.google.com/android/uncertified")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,8 +22,8 @@ ColumnLayout {
|
||||||
text: i18n("System type")
|
text: i18n("System type")
|
||||||
|
|
||||||
model: [
|
model: [
|
||||||
{"name": "Vanilla", "value": AIP.WaydroidState.Vanilla},
|
{"name": "Vanilla", "value": AIP.WaydroidDBusClient.Vanilla},
|
||||||
{"name": "GAPPS", "value": AIP.WaydroidState.Gapps}
|
{"name": "GAPPS", "value": AIP.WaydroidDBusClient.Gapps}
|
||||||
]
|
]
|
||||||
|
|
||||||
textRole: "name"
|
textRole: "name"
|
||||||
|
|
@ -36,6 +36,6 @@ ColumnLayout {
|
||||||
Layout.alignment: Qt.AlignHCenter
|
Layout.alignment: Qt.AlignHCenter
|
||||||
enabled: systemType.currentValue !== undefined
|
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
|
rightPadding: 0
|
||||||
|
|
||||||
ColumnLayout {
|
ColumnLayout {
|
||||||
visible: AIP.WaydroidState.errorTitle === "" && AIP.WaydroidState.status == AIP.WaydroidState.NotSupported
|
visible: AIP.WaydroidDBusClient.status === AIP.WaydroidDBusClient.NotSupported
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
spacing: Kirigami.Units.largeSpacing
|
spacing: Kirigami.Units.largeSpacing
|
||||||
|
|
||||||
|
|
@ -38,21 +38,21 @@ KCM.SimpleKCM {
|
||||||
PC3.Button {
|
PC3.Button {
|
||||||
text: i18n("Check installation")
|
text: i18n("Check installation")
|
||||||
Layout.alignment: Qt.AlignHCenter
|
Layout.alignment: Qt.AlignHCenter
|
||||||
onClicked: AIP.WaydroidState.refreshSupportsInfo()
|
onClicked: AIP.WaydroidDBusClient.refreshSupportsInfo()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
WaydroidInitialConfigurationForm {
|
WaydroidInitialConfigurationForm {
|
||||||
visible: AIP.WaydroidState.errorTitle === "" && AIP.WaydroidState.status == AIP.WaydroidState.NotInitialized
|
visible: AIP.WaydroidDBusClient.status === AIP.WaydroidDBusClient.NotInitialized
|
||||||
}
|
}
|
||||||
|
|
||||||
WaydroidDownloadStatus {
|
WaydroidDownloadStatus {
|
||||||
id: downloadStatus
|
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.")
|
text: i18n("Downloading Android and vendor images.\nIt can take a few minutes.")
|
||||||
|
|
||||||
Connections {
|
Connections {
|
||||||
target: AIP.WaydroidState
|
target: AIP.WaydroidDBusClient
|
||||||
|
|
||||||
function onDownloadStatusChanged(downloaded, total, speed) {
|
function onDownloadStatusChanged(downloaded, total, speed) {
|
||||||
downloadStatus.downloaded = downloaded
|
downloadStatus.downloaded = downloaded
|
||||||
|
|
@ -63,12 +63,12 @@ KCM.SimpleKCM {
|
||||||
}
|
}
|
||||||
|
|
||||||
WaydroidLoader {
|
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.")
|
text: i18n("Waydroid is resetting.\nIt can take a few seconds.")
|
||||||
}
|
}
|
||||||
|
|
||||||
ColumnLayout {
|
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
|
anchors.centerIn: parent
|
||||||
spacing: Kirigami.Units.largeSpacing
|
spacing: Kirigami.Units.largeSpacing
|
||||||
|
|
||||||
|
|
@ -81,46 +81,24 @@ KCM.SimpleKCM {
|
||||||
PC3.Button {
|
PC3.Button {
|
||||||
text: i18n("Start the session")
|
text: i18n("Start the session")
|
||||||
Layout.alignment: Qt.AlignHCenter
|
Layout.alignment: Qt.AlignHCenter
|
||||||
onClicked: AIP.WaydroidState.startSessionQml()
|
onClicked: AIP.WaydroidDBusClient.startSession()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
WaydroidLoader {
|
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.")
|
text: i18n("Waydroid session is starting.\nIt can take a few seconds.")
|
||||||
}
|
}
|
||||||
|
|
||||||
WaydroidConfigurationForm {
|
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 {
|
Connections {
|
||||||
visible: AIP.WaydroidState.errorTitle !== ""
|
target: AIP.WaydroidDBusClient
|
||||||
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 {
|
function onErrorOccurred(title, message) {
|
||||||
text: AIP.WaydroidState.errorTitle
|
kcm.push("WaydroidErrorPage.qml", { title, message })
|
||||||
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()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -9,18 +9,36 @@ import org.kde.plasma.private.mobileshell.waydroidintegrationplugin as AIP
|
||||||
|
|
||||||
QS.QuickSetting {
|
QS.QuickSetting {
|
||||||
text: i18nc("@action:button", "Waydroid")
|
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"
|
icon: "folder-android-symbolic"
|
||||||
settingsCommand: "plasma-open-settings kcm_waydroidintegration"
|
settingsCommand: "plasma-open-settings kcm_waydroidintegration"
|
||||||
|
|
||||||
enabled: AIP.WaydroidState.sessionStatus === AIP.WaydroidState.SessionRunning
|
available: AIP.WaydroidDBusClient.status !== AIP.WaydroidDBusClient.NotSupported
|
||||||
available: AIP.WaydroidState.status === AIP.WaydroidState.Initialized
|
enabled: AIP.WaydroidDBusClient.sessionStatus === AIP.WaydroidDBusClient.SessionRunning
|
||||||
|
|
||||||
|
Component.onCompleted: {
|
||||||
|
AIP.WaydroidDBusObject.registerObject()
|
||||||
|
}
|
||||||
|
|
||||||
function toggle(): void {
|
function toggle(): void {
|
||||||
if (AIP.WaydroidState.sessionStatus === AIP.WaydroidState.SessionRunning) {
|
if (AIP.WaydroidDBusClient.status !== AIP.WaydroidDBusClient.Initialized) {
|
||||||
AIP.WaydroidState.stopSessionQml()
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (AIP.WaydroidDBusClient.sessionStatus === AIP.WaydroidDBusClient.SessionRunning) {
|
||||||
|
AIP.WaydroidDBusClient.stopSession()
|
||||||
} else {
|
} 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