waydroidintegrationplugin: Make async and add error checking

Fixes: https://invent.kde.org/plasma/plasma-mobile/-/issues/494
This commit is contained in:
Devin Lin 2025-12-14 21:10:54 -05:00
parent aa070c5985
commit c7e961ca85
4 changed files with 320 additions and 166 deletions

View file

@ -73,6 +73,12 @@ void WaydroidApplicationDBusClient::updateName()
connect(watcher, &QDBusPendingCallWatcher::finished, this, [this](auto watcher) { connect(watcher, &QDBusPendingCallWatcher::finished, this, [this](auto watcher) {
QDBusPendingReply<QString> reply = *watcher; QDBusPendingReply<QString> reply = *watcher;
if (!reply.isValid()) {
qDebug() << "WaydroidApplicationDBusClient: Failed to fetch name:" << reply.error().message();
watcher->deleteLater();
return;
}
const auto name = reply.argumentAt<0>(); const auto name = reply.argumentAt<0>();
if (m_name != name) { if (m_name != name) {
@ -90,11 +96,17 @@ void WaydroidApplicationDBusClient::updatePackageName()
return; return;
} }
auto reply = m_interface->name(); auto reply = m_interface->packageName();
auto watcher = new QDBusPendingCallWatcher(reply, this); auto watcher = new QDBusPendingCallWatcher(reply, this);
connect(watcher, &QDBusPendingCallWatcher::finished, this, [this](auto watcher) { connect(watcher, &QDBusPendingCallWatcher::finished, this, [this](auto watcher) {
QDBusPendingReply<QString> reply = *watcher; QDBusPendingReply<QString> reply = *watcher;
if (!reply.isValid()) {
qDebug() << "WaydroidApplicationDBusClient: Failed to fetch packageName:" << reply.error().message();
watcher->deleteLater();
return;
}
const auto packageName = reply.argumentAt<0>(); const auto packageName = reply.argumentAt<0>();
if (m_packageName != packageName) { if (m_packageName != packageName) {

View file

@ -83,6 +83,12 @@ void WaydroidDBusClient::initializeApplicationListModel()
connect(watcher, &QDBusPendingCallWatcher::finished, this, [this](auto watcher) { connect(watcher, &QDBusPendingCallWatcher::finished, this, [this](auto watcher) {
QDBusPendingReply<QList<QDBusObjectPath>> reply = *watcher; QDBusPendingReply<QList<QDBusObjectPath>> reply = *watcher;
if (!reply.isValid()) {
qDebug() << "WaydroidDBusClient: Failed to fetch applications:" << reply.error().message();
watcher->deleteLater();
return;
}
const auto applications = reply.argumentAt<0>(); const auto applications = reply.argumentAt<0>();
m_applicationListModel->initializeApplications(applications); m_applicationListModel->initializeApplications(applications);
@ -90,6 +96,8 @@ void WaydroidDBusClient::initializeApplicationListModel()
// Connect applicationListModel signals only when applications is synced // Connect applicationListModel signals only when applications is synced
connect(m_interface, &OrgKdePlasmashellWaydroidInterface::applicationAdded, m_applicationListModel, &WaydroidApplicationListModel::addApplication); connect(m_interface, &OrgKdePlasmashellWaydroidInterface::applicationAdded, m_applicationListModel, &WaydroidApplicationListModel::addApplication);
connect(m_interface, &OrgKdePlasmashellWaydroidInterface::applicationRemoved, m_applicationListModel, &WaydroidApplicationListModel::removeApplication); connect(m_interface, &OrgKdePlasmashellWaydroidInterface::applicationRemoved, m_applicationListModel, &WaydroidApplicationListModel::removeApplication);
watcher->deleteLater();
}); });
} }
@ -161,9 +169,9 @@ QCoro::Task<void> WaydroidDBusClient::setUeventTask(const bool uevent)
co_await pendingReply; co_await pendingReply;
} }
QCoro::QmlTask WaydroidDBusClient::setUevent(const bool multiWindows) QCoro::QmlTask WaydroidDBusClient::setUevent(const bool uevent)
{ {
return setUeventTask(multiWindows); return setUeventTask(uevent);
} }
QCoro::Task<void> WaydroidDBusClient::refreshSessionInfoTask() QCoro::Task<void> WaydroidDBusClient::refreshSessionInfoTask()
@ -277,6 +285,12 @@ void WaydroidDBusClient::updateStatus()
connect(watcher, &QDBusPendingCallWatcher::finished, this, [this](auto watcher) { connect(watcher, &QDBusPendingCallWatcher::finished, this, [this](auto watcher) {
QDBusPendingReply<int> reply = *watcher; QDBusPendingReply<int> reply = *watcher;
if (!reply.isValid()) {
qDebug() << "WaydroidDBusClient: Failed to fetch status:" << reply.error().message();
watcher->deleteLater();
return;
}
const auto status = static_cast<Status>(reply.argumentAt<0>()); const auto status = static_cast<Status>(reply.argumentAt<0>());
if (m_status != status) { if (m_status != status) {
@ -295,6 +309,12 @@ void WaydroidDBusClient::updateSessionStatus()
connect(watcher, &QDBusPendingCallWatcher::finished, this, [this](auto watcher) { connect(watcher, &QDBusPendingCallWatcher::finished, this, [this](auto watcher) {
QDBusPendingReply<int> reply = *watcher; QDBusPendingReply<int> reply = *watcher;
if (!reply.isValid()) {
qDebug() << "WaydroidDBusClient: Failed to fetch sessionStatus:" << reply.error().message();
watcher->deleteLater();
return;
}
const auto sessionStatus = static_cast<SessionStatus>(reply.argumentAt<0>()); const auto sessionStatus = static_cast<SessionStatus>(reply.argumentAt<0>());
if (m_sessionStatus != sessionStatus) { if (m_sessionStatus != sessionStatus) {
@ -313,6 +333,12 @@ void WaydroidDBusClient::updateSystemType()
connect(watcher, &QDBusPendingCallWatcher::finished, this, [this](auto watcher) { connect(watcher, &QDBusPendingCallWatcher::finished, this, [this](auto watcher) {
QDBusPendingReply<int> reply = *watcher; QDBusPendingReply<int> reply = *watcher;
if (!reply.isValid()) {
qDebug() << "WaydroidDBusClient: Failed to fetch systemType:" << reply.error().message();
watcher->deleteLater();
return;
}
const auto systemType = static_cast<SystemType>(reply.argumentAt<0>()); const auto systemType = static_cast<SystemType>(reply.argumentAt<0>());
if (m_systemType != systemType) { if (m_systemType != systemType) {
@ -331,6 +357,12 @@ void WaydroidDBusClient::updateIpAddress()
connect(watcher, &QDBusPendingCallWatcher::finished, this, [this](auto watcher) { connect(watcher, &QDBusPendingCallWatcher::finished, this, [this](auto watcher) {
QDBusPendingReply<QString> reply = *watcher; QDBusPendingReply<QString> reply = *watcher;
if (!reply.isValid()) {
qDebug() << "WaydroidDBusClient: Failed to fetch ipAddress:" << reply.error().message();
watcher->deleteLater();
return;
}
const auto ipAddress = reply.argumentAt<0>(); const auto ipAddress = reply.argumentAt<0>();
if (m_ipAddress != ipAddress) { if (m_ipAddress != ipAddress) {
@ -349,6 +381,12 @@ void WaydroidDBusClient::updateAndroidId()
connect(watcher, &QDBusPendingCallWatcher::finished, this, [this](auto watcher) { connect(watcher, &QDBusPendingCallWatcher::finished, this, [this](auto watcher) {
QDBusPendingReply<QString> reply = *watcher; QDBusPendingReply<QString> reply = *watcher;
if (!reply.isValid()) {
qDebug() << "WaydroidDBusClient: Failed to fetch androidId:" << reply.error().message();
watcher->deleteLater();
return;
}
const auto androidId = reply.argumentAt<0>(); const auto androidId = reply.argumentAt<0>();
if (m_androidId != androidId) { if (m_androidId != androidId) {
@ -367,6 +405,12 @@ void WaydroidDBusClient::updateMultiWindows()
connect(watcher, &QDBusPendingCallWatcher::finished, this, [this](auto watcher) { connect(watcher, &QDBusPendingCallWatcher::finished, this, [this](auto watcher) {
QDBusPendingReply<bool> reply = *watcher; QDBusPendingReply<bool> reply = *watcher;
if (!reply.isValid()) {
qDebug() << "WaydroidDBusClient: Failed to fetch multiWindows:" << reply.error().message();
watcher->deleteLater();
return;
}
const auto multiWindows = reply.argumentAt<0>(); const auto multiWindows = reply.argumentAt<0>();
if (m_multiWindows != multiWindows) { if (m_multiWindows != multiWindows) {
@ -385,6 +429,12 @@ void WaydroidDBusClient::updateSuspend()
connect(watcher, &QDBusPendingCallWatcher::finished, this, [this](auto watcher) { connect(watcher, &QDBusPendingCallWatcher::finished, this, [this](auto watcher) {
QDBusPendingReply<bool> reply = *watcher; QDBusPendingReply<bool> reply = *watcher;
if (!reply.isValid()) {
qDebug() << "WaydroidDBusClient: Failed to fetch suspend:" << reply.error().message();
watcher->deleteLater();
return;
}
const auto suspend = reply.argumentAt<0>(); const auto suspend = reply.argumentAt<0>();
if (m_suspend != suspend) { if (m_suspend != suspend) {
@ -403,6 +453,12 @@ void WaydroidDBusClient::updateUevent()
connect(watcher, &QDBusPendingCallWatcher::finished, this, [this](auto watcher) { connect(watcher, &QDBusPendingCallWatcher::finished, this, [this](auto watcher) {
QDBusPendingReply<bool> reply = *watcher; QDBusPendingReply<bool> reply = *watcher;
if (!reply.isValid()) {
qDebug() << "WaydroidDBusClient: Failed to fetch uevent:" << reply.error().message();
watcher->deleteLater();
return;
}
const auto uevent = reply.argumentAt<0>(); const auto uevent = reply.argumentAt<0>();
if (m_uevent != uevent) { if (m_uevent != uevent) {
@ -417,4 +473,4 @@ void WaydroidDBusClient::updateUevent()
void WaydroidDBusClient::copyToClipboard(const QString text) void WaydroidDBusClient::copyToClipboard(const QString text)
{ {
qGuiApp->clipboard()->setText(text); qGuiApp->clipboard()->setText(text);
} }

View file

@ -10,9 +10,11 @@
#include "waydroidintegrationplugin_debug.h" #include "waydroidintegrationplugin_debug.h"
#include "waydroidshared.h" #include "waydroidshared.h"
#include <QCoroProcess>
#include <QDBusConnection> #include <QDBusConnection>
#include <QDir> #include <QDir>
#include <QLoggingCategory> #include <QLoggingCategory>
#include <QPointer>
#include <QProcess> #include <QProcess>
#include <QRegularExpression> #include <QRegularExpression>
#include <QStandardPaths> #include <QStandardPaths>
@ -31,7 +33,7 @@ using namespace Qt::StringLiterals;
#define UEVENT_PROP_KEY "persist.waydroid.uevent" #define UEVENT_PROP_KEY "persist.waydroid.uevent"
static const QRegularExpression sessionRegExp(u"Session:\\s*(\\w+)"_s); 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 ipAddressRegExp(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);
WaydroidDBusObject::WaydroidDBusObject(QObject *parent) WaydroidDBusObject::WaydroidDBusObject(QObject *parent)
@ -41,19 +43,21 @@ WaydroidDBusObject::WaydroidDBusObject(QObject *parent)
void WaydroidDBusObject::registerObject() void WaydroidDBusObject::registerObject()
{ {
if (!m_dbusInitialized) { if (m_dbusInitialized) {
new WaydroidAdaptor{this}; return;
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();
} }
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) void WaydroidDBusObject::initialize(const int systemType, const int romType, const bool forced)
@ -99,7 +103,6 @@ void WaydroidDBusObject::initialize(const int systemType, const int romType, con
writeAction.setTimeout(3600000); // HACK: 1 hour to wait installation writeAction.setTimeout(3600000); // HACK: 1 hour to wait installation
KAuth::ExecuteJob *job = writeAction.execute(); KAuth::ExecuteJob *job = writeAction.execute();
job->start();
connect(job, &KAuth::ExecuteJob::newData, this, [this](const QVariantMap &data) { connect(job, &KAuth::ExecuteJob::newData, this, [this](const QVariantMap &data) {
QString log = data.value("log", "").toString(); QString log = data.value("log", "").toString();
@ -123,6 +126,8 @@ void WaydroidDBusObject::initialize(const int systemType, const int romType, con
Q_EMIT statusChanged(); Q_EMIT statusChanged();
}); });
job->start();
} }
void WaydroidDBusObject::startSession() void WaydroidDBusObject::startSession()
@ -137,10 +142,10 @@ void WaydroidDBusObject::startSession()
const QStringList arguments{u"session"_s, u"start"_s}; const QStringList arguments{u"session"_s, u"start"_s};
auto *process = new QProcess(this); auto *process = new QProcess(this);
process->start(WAYDROID_COMMAND, arguments);
connect(process, &QProcess::finished, this, [this, process](int exitCode, QProcess::ExitStatus exitStatus) { connect(process, &QProcess::finished, this, [this, process](int exitCode, QProcess::ExitStatus exitStatus) {
Q_UNUSED(exitStatus); Q_UNUSED(exitStatus);
process->deleteLater();
if (exitCode == 0) { if (exitCode == 0) {
return; return;
@ -157,6 +162,8 @@ void WaydroidDBusObject::startSession()
qCWarning(WAYDROIDINTEGRATIONPLUGIN) << "Failed to start the Waydroid session: " << errorString; qCWarning(WAYDROIDINTEGRATIONPLUGIN) << "Failed to start the Waydroid session: " << errorString;
}); });
process->start(WAYDROID_COMMAND, arguments);
checkSessionStarting(10); checkSessionStarting(10);
} }
@ -168,16 +175,22 @@ void WaydroidDBusObject::stopSession()
const QStringList arguments{u"session"_s, u"stop"_s}; const QStringList arguments{u"session"_s, u"stop"_s};
QProcess *process = new QProcess(this); auto *process = new QProcess(this);
process->start(WAYDROID_COMMAND, arguments);
process->waitForFinished(); connect(process, &QProcess::finished, this, [this, process](int exitCode, QProcess::ExitStatus exitStatus) {
Q_UNUSED(exitStatus);
process->deleteLater();
if (exitCode == 0) {
qCWarning(WAYDROIDINTEGRATIONPLUGIN) << "Failed to stop the Waydroid session: " << process->readAllStandardError();
return;
}
if (process->exitCode() == 0) {
m_sessionStatus = SessionStopped; m_sessionStatus = SessionStopped;
Q_EMIT sessionStatusChanged(); Q_EMIT sessionStatusChanged();
} else { });
qCWarning(WAYDROIDINTEGRATIONPLUGIN) << "Failed to stop the Waydroid session: " << process->readAllStandardError();
} process->start(WAYDROID_COMMAND, arguments);
} }
void WaydroidDBusObject::resetWaydroid() void WaydroidDBusObject::resetWaydroid()
@ -200,7 +213,6 @@ void WaydroidDBusObject::resetWaydroid()
writeAction.setArguments(args); writeAction.setArguments(args);
KAuth::ExecuteJob *job = writeAction.execute(); KAuth::ExecuteJob *job = writeAction.execute();
job->start();
connect(job, &KAuth::ExecuteJob::finished, this, [this](KJob *job, auto) { connect(job, &KAuth::ExecuteJob::finished, this, [this](KJob *job, auto) {
removeWaydroidApplications(); removeWaydroidApplications();
@ -216,6 +228,8 @@ void WaydroidDBusObject::resetWaydroid()
Q_EMIT statusChanged(); Q_EMIT statusChanged();
}); });
job->start();
} }
void WaydroidDBusObject::installApk(const QString apkFile) void WaydroidDBusObject::installApk(const QString apkFile)
@ -223,7 +237,6 @@ void WaydroidDBusObject::installApk(const QString apkFile)
const QStringList arguments{u"app"_s, u"install"_s, apkFile}; const QStringList arguments{u"app"_s, u"install"_s, apkFile};
QProcess *process = new QProcess(this); QProcess *process = new QProcess(this);
process->start(WAYDROID_COMMAND, arguments);
connect(process, &QProcess::finished, this, [this, apkFile, process](int exitCode, QProcess::ExitStatus exitStatus) { connect(process, &QProcess::finished, this, [this, apkFile, process](int exitCode, QProcess::ExitStatus exitStatus) {
if (exitCode == 0 && exitStatus == QProcess::NormalExit) { if (exitCode == 0 && exitStatus == QProcess::NormalExit) {
@ -233,6 +246,8 @@ void WaydroidDBusObject::installApk(const QString apkFile)
qCWarning(WAYDROIDINTEGRATIONPLUGIN) << "Error occurred during installation of " << apkFile << ": " << process->readAllStandardError(); qCWarning(WAYDROIDINTEGRATIONPLUGIN) << "Error occurred during installation of " << apkFile << ": " << process->readAllStandardError();
} }
}); });
process->start(WAYDROID_COMMAND, arguments);
} }
void WaydroidDBusObject::deleteApplication(const QString appId) void WaydroidDBusObject::deleteApplication(const QString appId)
@ -240,7 +255,6 @@ void WaydroidDBusObject::deleteApplication(const QString appId)
const QStringList arguments{u"app"_s, u"remove"_s, appId}; const QStringList arguments{u"app"_s, u"remove"_s, appId};
QProcess *process = new QProcess(this); QProcess *process = new QProcess(this);
process->start(WAYDROID_COMMAND, arguments);
connect(process, &QProcess::finished, this, [this, appId, process](int exitCode, QProcess::ExitStatus exitStatus) { connect(process, &QProcess::finished, this, [this, appId, process](int exitCode, QProcess::ExitStatus exitStatus) {
Q_UNUSED(exitCode); Q_UNUSED(exitCode);
@ -256,6 +270,8 @@ void WaydroidDBusObject::deleteApplication(const QString appId)
qCWarning(WAYDROIDINTEGRATIONPLUGIN) << "Error occurred during uninstallation of " << appId << ": " << errorLog; qCWarning(WAYDROIDINTEGRATIONPLUGIN) << "Error occurred during uninstallation of " << appId << ": " << errorLog;
} }
}); });
process->start(WAYDROID_COMMAND, arguments);
} }
int WaydroidDBusObject::status() const int WaydroidDBusObject::status() const
@ -296,10 +312,17 @@ void WaydroidDBusObject::setMultiWindows(const bool multiWindows)
const QString value = multiWindows ? "true" : "false"; const QString value = multiWindows ? "true" : "false";
if (writePropValue(MULTI_WINDOWS_PROP_KEY, value)) { // Run coroutine asynchronously with QPointer to safely handle 'this'
m_multiWindows = multiWindows; auto coro = [](WaydroidDBusObject *self, QString value, bool multiWindows) -> QCoro::Task<void> {
Q_EMIT multiWindowsChanged(); QPointer<WaydroidDBusObject> guard(self);
} if (co_await self->writePropValue(MULTI_WINDOWS_PROP_KEY, value)) {
if (guard) {
self->m_multiWindows = multiWindows;
Q_EMIT self->multiWindowsChanged();
}
}
};
coro(this, value, multiWindows);
} }
bool WaydroidDBusObject::suspend() const bool WaydroidDBusObject::suspend() const
@ -315,10 +338,16 @@ void WaydroidDBusObject::setSuspend(const bool suspend)
const QString value = suspend ? "true" : "false"; const QString value = suspend ? "true" : "false";
if (writePropValue(SUSPEND_PROP_KEY, value)) { auto coro = [](WaydroidDBusObject *self, QString value, bool suspend) -> QCoro::Task<void> {
m_suspend = suspend; QPointer<WaydroidDBusObject> guard(self);
Q_EMIT suspendChanged(); if (co_await self->writePropValue(SUSPEND_PROP_KEY, value)) {
} if (guard) {
self->m_suspend = suspend;
Q_EMIT self->suspendChanged();
}
}
};
coro(this, value, suspend);
} }
bool WaydroidDBusObject::uevent() const bool WaydroidDBusObject::uevent() const
@ -334,10 +363,16 @@ void WaydroidDBusObject::setUevent(const bool uevent)
const QString value = uevent ? "true" : "false"; const QString value = uevent ? "true" : "false";
if (writePropValue(UEVENT_PROP_KEY, value)) { auto coro = [](WaydroidDBusObject *self, QString value, bool uevent) -> QCoro::Task<void> {
m_uevent = uevent; QPointer<WaydroidDBusObject> guard(self);
Q_EMIT ueventChanged(); if (co_await self->writePropValue(UEVENT_PROP_KEY, value)) {
} if (guard) {
self->m_uevent = uevent;
Q_EMIT self->ueventChanged();
}
}
};
coro(this, value, uevent);
} }
QList<QDBusObjectPath> WaydroidDBusObject::applications() const QList<QDBusObjectPath> WaydroidDBusObject::applications() const
@ -353,24 +388,30 @@ void WaydroidDBusObject::refreshSupportsInfo()
{ {
const QStringList arguments{u"-h"_s}; const QStringList arguments{u"-h"_s};
auto process = QProcess(this); auto process = new QProcess(this);
process.start(WAYDROID_COMMAND, arguments); process->start(WAYDROID_COMMAND, arguments);
process.waitForFinished();
const int exitCode = process.exitCode(); connect(process, &QProcess::finished, this, [this, process](int exitCode, QProcess::ExitStatus exitStatus) {
if (exitCode != 0) { Q_UNUSED(exitCode)
m_status = NotSupported; Q_UNUSED(exitStatus)
Q_EMIT statusChanged();
return;
}
const QString output = fetchSessionInfo(); process->deleteLater();
if (!output.contains("WayDroid is not initialized")) {
m_status = Initialized; if (process->exitCode() != 0) {
} else { m_status = NotSupported;
m_status = NotInitialized; Q_EMIT statusChanged();
} return;
Q_EMIT statusChanged(); }
fetchSessionInfo().then([this](const QString &output) {
if (!output.contains("WayDroid is not initialized")) {
m_status = Initialized;
} else {
m_status = NotInitialized;
}
Q_EMIT statusChanged();
});
});
} }
void WaydroidDBusObject::refreshInstallationInfo() void WaydroidDBusObject::refreshInstallationInfo()
@ -406,35 +447,49 @@ void WaydroidDBusObject::refreshSessionInfo()
return; return;
} }
const QString output = fetchSessionInfo(); auto coro = [](WaydroidDBusObject *self) -> QCoro::Task<void> {
QPointer<WaydroidDBusObject> guard(self);
const QString output = co_await self->fetchSessionInfo();
const QString sessionMatchResult = extractRegExp(output, sessionRegExp); if (!guard) {
SessionStatus newSessionStatus; co_return;
}
if (!sessionMatchResult.isEmpty()) { const QString sessionMatchResult = self->extractRegExp(output, sessionRegExp);
newSessionStatus = sessionMatchResult.contains("RUNNING") ? SessionRunning : SessionStopped; SessionStatus newSessionStatus;
} else {
newSessionStatus = SessionStopped;
}
if (m_sessionStatus != newSessionStatus) { if (!sessionMatchResult.isEmpty()) {
m_sessionStatus = newSessionStatus; newSessionStatus = sessionMatchResult.contains("RUNNING") ? SessionRunning : SessionStopped;
Q_EMIT sessionStatusChanged(); } else {
} newSessionStatus = SessionStopped;
}
m_ipAddress = extractRegExp(output, ipAdressRegExp); if (self->m_sessionStatus != newSessionStatus) {
Q_EMIT ipAddressChanged(); self->m_sessionStatus = newSessionStatus;
Q_EMIT self->sessionStatusChanged();
}
const QString newIpAddress = self->extractRegExp(output, ipAddressRegExp);
if (self->m_ipAddress != newIpAddress) {
self->m_ipAddress = self->extractRegExp(output, ipAddressRegExp);
Q_EMIT self->ipAddressChanged();
}
};
coro(this);
} }
QString WaydroidDBusObject::fetchSessionInfo() QCoro::Task<QString> WaydroidDBusObject::fetchSessionInfo()
{ {
const QStringList arguments{u"status"_s}; const QStringList arguments{u"status"_s};
auto process = QProcess(this); auto *basicProcess = new QProcess(this);
process.start(WAYDROID_COMMAND, arguments); auto process = qCoro(basicProcess);
process.waitForFinished(); co_await process.start(WAYDROID_COMMAND, arguments);
co_await process.waitForFinished();
return process.readAllStandardOutput(); const QString output = basicProcess->readAllStandardOutput();
basicProcess->deleteLater();
co_return output;
} }
void WaydroidDBusObject::refreshAndroidId() void WaydroidDBusObject::refreshAndroidId()
@ -447,7 +502,6 @@ void WaydroidDBusObject::refreshAndroidId()
writeAction.setHelperId(u"org.kde.plasma.mobileshell.waydroidhelper"_s); writeAction.setHelperId(u"org.kde.plasma.mobileshell.waydroidhelper"_s);
KAuth::ExecuteJob *job = writeAction.execute(); KAuth::ExecuteJob *job = writeAction.execute();
job->start();
connect(job, &KAuth::ExecuteJob::finished, this, [this](KJob *job, auto) { connect(job, &KAuth::ExecuteJob::finished, this, [this](KJob *job, auto) {
KAuth::ExecuteJob *executeJob = dynamic_cast<KAuth::ExecuteJob *>(job); KAuth::ExecuteJob *executeJob = dynamic_cast<KAuth::ExecuteJob *>(job);
@ -464,54 +518,61 @@ void WaydroidDBusObject::refreshAndroidId()
Q_EMIT androidIdChanged(); Q_EMIT androidIdChanged();
}); });
job->start();
} }
void WaydroidDBusObject::refreshPropsInfo() QCoro::Task<void> WaydroidDBusObject::refreshPropsInfo()
{ {
if (m_sessionStatus != SessionRunning) { if (m_sessionStatus != SessionRunning) {
return; co_return;
} }
const QString multiWindowsPropValue = fetchPropValue(MULTI_WINDOWS_PROP_KEY, "false"); const QString multiWindowsPropValue = co_await fetchPropValue(MULTI_WINDOWS_PROP_KEY, "false");
m_multiWindows = multiWindowsPropValue == "true"; m_multiWindows = multiWindowsPropValue == "true";
Q_EMIT multiWindowsChanged(); Q_EMIT multiWindowsChanged();
const QString suspendPropValue = fetchPropValue(SUSPEND_PROP_KEY, "true"); const QString suspendPropValue = co_await fetchPropValue(SUSPEND_PROP_KEY, "true");
m_suspend = suspendPropValue == "true"; m_suspend = suspendPropValue == "true";
Q_EMIT suspendChanged(); Q_EMIT suspendChanged();
const QString ueventPropValue = fetchPropValue(UEVENT_PROP_KEY, "false"); const QString ueventPropValue = co_await fetchPropValue(UEVENT_PROP_KEY, "false");
m_uevent = ueventPropValue == "true"; m_uevent = ueventPropValue == "true";
Q_EMIT ueventChanged(); Q_EMIT ueventChanged();
} }
QString WaydroidDBusObject::fetchPropValue(const QString key, const QString defaultValue) QCoro::Task<QString> WaydroidDBusObject::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};
QProcess *process = new QProcess(this); auto *basicProcess = new QProcess(this);
process->start(WAYDROID_COMMAND, arguments); auto process = qCoro(basicProcess);
process->waitForFinished(); co_await process.start(WAYDROID_COMMAND, arguments);
co_await process.waitForFinished();
const QString commandOutput = process->readAllStandardOutput(); const QString commandOutput = basicProcess->readAllStandardOutput();
basicProcess->deleteLater();
const QString value = commandOutput.split("\n").first().trimmed(); const QString value = commandOutput.split("\n").first().trimmed();
if (value.isEmpty()) { if (value.isEmpty()) {
return defaultValue; co_return defaultValue;
} }
return value; co_return value;
} }
bool WaydroidDBusObject::writePropValue(const QString key, const QString value) QCoro::Task<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};
auto process = QProcess(this); auto *basicProcess = new QProcess(this);
process.start(WAYDROID_COMMAND, arguments); auto process = qCoro(basicProcess);
process.waitForFinished(); co_await process.start(WAYDROID_COMMAND, arguments);
co_await process.waitForFinished();
return process.exitCode() == 0; const bool success = basicProcess->exitCode() == 0;
basicProcess->deleteLater();
co_return success;
} }
QString WaydroidDBusObject::extractRegExp(const QString text, const QRegularExpression regExp) const QString WaydroidDBusObject::extractRegExp(const QString text, const QRegularExpression regExp) const
@ -531,21 +592,32 @@ void WaydroidDBusObject::checkSessionStarting(const int limit, const int tried)
return; return;
} }
const QString output = fetchSessionInfo(); auto coro = [](WaydroidDBusObject *self, int limit, int tried) -> QCoro::Task<void> {
const QString sessionMatchResult = extractRegExp(output, sessionRegExp); QPointer<WaydroidDBusObject> guard(self);
const QString output = co_await self->fetchSessionInfo();
if (sessionMatchResult.contains("RUNNING")) { if (!guard) {
m_sessionStatus = SessionRunning; co_return;
Q_EMIT sessionStatusChanged(); }
} else if (tried == limit) {
m_sessionStatus = SessionStopped; const QString sessionMatchResult = self->extractRegExp(output, sessionRegExp);
Q_EMIT sessionStatusChanged();
qCWarning(WAYDROIDINTEGRATIONPLUGIN) << "Failed to start the session after " << tried << " tries"; if (sessionMatchResult.contains("RUNNING")) {
} else { self->m_sessionStatus = SessionRunning;
QTimer::singleShot(500, [this, tried, limit]() { Q_EMIT self->sessionStatusChanged();
checkSessionStarting(limit, tried + 1); } else if (tried == limit) {
}); self->m_sessionStatus = SessionStopped;
} Q_EMIT self->sessionStatusChanged();
qCWarning(WAYDROIDINTEGRATIONPLUGIN) << "Failed to start the session after " << tried << " tries";
} else {
QTimer::singleShot(500, self, [self, tried, limit]() {
if (self) {
self->checkSessionStarting(limit, tried + 1);
}
});
}
};
coro(this, limit, tried);
} }
QString WaydroidDBusObject::desktopFileDirectory() QString WaydroidDBusObject::desktopFileDirectory()
@ -608,74 +680,87 @@ void WaydroidDBusObject::refreshApplications()
return; return;
} }
const QString output = fetchApplicationsList(); auto coro = [](WaydroidDBusObject *self) -> QCoro::Task<void> {
if (output.isEmpty()) { QPointer<WaydroidDBusObject> guard(self);
return; const QString output = co_await self->fetchApplicationsList();
}
QTextStream inFile(const_cast<QString *>(&output), QIODevice::ReadOnly); if (!guard) {
const auto newApplications = WaydroidApplicationDBusObject::parseApplicationsFromWaydroidLog(inFile); co_return;
}
// Create a map of existing applications by package name for efficient lookup if (output.isEmpty()) {
QMap<QString, int> existingAppMap; co_return;
for (int i = 0; i < m_applicationObjects.size(); ++i) { }
const auto &application = m_applicationObjects[i];
existingAppMap.insert(application->packageName(), i);
}
QList<WaydroidApplicationDBusObject::Ptr> toInsert; QTextStream inFile(const_cast<QString *>(&output), QIODevice::ReadOnly);
const auto newApplications = WaydroidApplicationDBusObject::parseApplicationsFromWaydroidLog(inFile);
// Check which applications need to be added or are already present // Create a map of existing applications by package name for efficient lookup
for (const auto &application : newApplications) { QMap<QString, int> existingAppMap;
if (!application->name().isEmpty() && !application->packageName().isEmpty()) { for (int i = 0; i < self->m_applicationObjects.size(); ++i) {
auto it = existingAppMap.find(application->packageName()); const auto &application = self->m_applicationObjects[i];
if (it != existingAppMap.end()) { existingAppMap.insert(application->packageName(), i);
// Application already exists, remove from map to mark as kept }
existingAppMap.erase(it);
} else { QList<WaydroidApplicationDBusObject::Ptr> toInsert;
// Application needs to be inserted
toInsert.append(application); // 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 // Remove applications that are no longer present
QList<int> toRemove; QList<int> toRemove;
for (const int index : existingAppMap.values()) { for (const int index : existingAppMap.values()) {
toRemove.append(index); toRemove.append(index);
} }
std::sort(toRemove.begin(), toRemove.end()); std::sort(toRemove.begin(), toRemove.end());
// Remove indices from end to start to avoid index shifting // Remove indices from end to start to avoid index shifting
for (int i = toRemove.size() - 1; i >= 0; --i) { for (int i = toRemove.size() - 1; i >= 0; --i) {
int ind = toRemove[i]; int ind = toRemove[i];
const auto application = m_applicationObjects[ind]; const auto application = self->m_applicationObjects[ind];
m_applicationObjects.removeAt(ind); self->m_applicationObjects.removeAt(ind);
Q_EMIT applicationRemoved(application->objectPath()); Q_EMIT self->applicationRemoved(application->objectPath());
application->unregisterObject(); application->unregisterObject();
} }
// Add new applications and register them // Add new applications and register them
for (const auto &application : toInsert) { for (const auto &application : toInsert) {
application->registerObject(); application->registerObject();
m_applicationObjects.append(application); self->m_applicationObjects.append(application);
Q_EMIT applicationAdded(application->objectPath()); Q_EMIT self->applicationAdded(application->objectPath());
} }
};
coro(this);
} }
QString WaydroidDBusObject::fetchApplicationsList() QCoro::Task<QString> WaydroidDBusObject::fetchApplicationsList()
{ {
const QStringList arguments{u"app"_s, u"list"_s}; const QStringList arguments{u"app"_s, u"list"_s};
auto process = QProcess(this); auto *basicProcess = new QProcess(this);
process.start(WAYDROID_COMMAND, arguments); auto process = qCoro(basicProcess);
process.waitForFinished(); co_await process.start(WAYDROID_COMMAND, arguments);
co_await process.waitForFinished();
if (process.exitCode() != 0) { if (basicProcess->exitCode() != 0) {
qCWarning(WAYDROIDINTEGRATIONPLUGIN) << "Failed to fetch applications list: " << process.readAllStandardError(); qCWarning(WAYDROIDINTEGRATIONPLUGIN) << "Failed to fetch applications list: " << basicProcess->readAllStandardError();
return QString{}; basicProcess->deleteLater();
co_return QString{};
} }
return process.readAllStandardOutput(); const QString output = basicProcess->readAllStandardOutput();
basicProcess->deleteLater();
co_return output;
} }

View file

@ -8,6 +8,7 @@
#include "waydroidapplicationdbusobject.h" #include "waydroidapplicationdbusobject.h"
#include <QCoroTask>
#include <QDBusObjectPath> #include <QDBusObjectPath>
#include <QList> #include <QList>
#include <QObject> #include <QObject>
@ -145,7 +146,7 @@ private:
void refreshSupportsInfo(); void refreshSupportsInfo();
void refreshInstallationInfo(); void refreshInstallationInfo();
void refreshPropsInfo(); QCoro::Task<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
@ -153,7 +154,7 @@ private:
* *
* @return A QString containing the output of the Waydroid session status command. * @return A QString containing the output of the Waydroid session status command.
*/ */
QString fetchSessionInfo(); QCoro::Task<QString> fetchSessionInfo();
/** /**
* @brief Executes the command to retrieve the value of a specified property from the Waydroid container. * @brief Executes the command to retrieve the value of a specified property from the Waydroid container.
@ -162,7 +163,7 @@ private:
* @param defaultValue The default value to return if the property is not found or empty. * @param defaultValue The default value to return if the property is not found or empty.
* @return A QString containing the property value, or the defaultValue if not found. * @return A QString containing the property value, or the defaultValue if not found.
*/ */
QString fetchPropValue(const QString key, const QString defaultValue); QCoro::Task<QString> fetchPropValue(const QString key, const QString defaultValue);
/** /**
* @brief Executes the command to writes a value to a specified property in the Waydroid container. * @brief Executes the command to writes a value to a specified property in the Waydroid container.
@ -171,7 +172,7 @@ private:
* @param value The value to write to the property. * @param value The value to write to the property.
* @return A boolean indicating whether the write operation was successful. * @return A boolean indicating whether the write operation was successful.
*/ */
bool writePropValue(const QString key, const QString value); QCoro::Task<bool> writePropValue(const QString key, const QString value);
/** /**
* @brief Extracts text from a string using a regular expression pattern. * @brief Extracts text from a string using a regular expression pattern.
@ -202,6 +203,6 @@ private:
QString desktopFileDirectory(); QString desktopFileDirectory();
bool removeWaydroidApplications(); bool removeWaydroidApplications();
QString fetchApplicationsList(); QCoro::Task<QString> fetchApplicationsList();
QList<WaydroidApplicationDBusObject::Ptr> m_applicationObjects; QList<WaydroidApplicationDBusObject::Ptr> m_applicationObjects;
}; };