waydroid: Implement reset action

This commit is contained in:
Florian RICHER 2025-07-28 19:37:19 +02:00
parent b5f1cb2eea
commit 8210205780
7 changed files with 169 additions and 3 deletions

View file

@ -9,6 +9,7 @@ target_link_libraries(waydroidintegrationplugin PRIVATE
Qt::Qml
Qt::Quick
KF6::AuthCore
KF6::ConfigCore
KF6::I18n
QCoro::Core
QCoro::Qml

View file

@ -104,3 +104,10 @@ Description[zh_CN]=允许获取 Waydroid 容器的 Android ID 以激活谷歌服
Policy=yes
PolicyInactive=yes
Persistence=session
[org.kde.plasma.mobileshell.waydroidhelper.reset]
Name=Reset Waydroid
Description=Allow delete waydroid folder
Policy=yes
PolicyInactive=yes
Persistence=session

View file

@ -10,6 +10,7 @@
#include <KAuth/HelperSupport>
#include <QDebug>
#include <QDir>
#include <QFile>
#include <QFileInfo>
#include <QLoggingCategory>
@ -20,6 +21,8 @@
using namespace Qt::StringLiterals;
#define WAYDROID_COMMAND "waydroid"
#define WAYDROID_FOLDER "/var/lib/waydroid"
#define WAYDROID_USER_FOLDER "/.local/share/waydroid/"
// Extract current downloaded size, total_size and speed.
// Example of log: "[Downloading] 62.19 MB/1197.24 MB 96740.75 kbps(approx.)"
@ -31,6 +34,10 @@ class WaydroidHelper : public QObject
public Q_SLOTS:
KAuth::ActionReply initialize(const QVariantMap &args);
KAuth::ActionReply getandroidid(const QVariantMap &args);
KAuth::ActionReply reset(const QVariantMap &args);
private:
bool removeDir(const QString dirPath);
};
KAuth::ActionReply WaydroidHelper::initialize(const QVariantMap &args)
@ -121,6 +128,34 @@ KAuth::ActionReply WaydroidHelper::getandroidid(const QVariantMap &args)
}
}
KAuth::ActionReply WaydroidHelper::reset(const QVariantMap &args)
{
const QString homeDir = args.value("homeDir"_L1).toString();
if (!removeDir(WAYDROID_FOLDER)) {
qCWarning(WAYDROIDHELPER) << "Failed to remove Waydroid directory";
return KAuth::ActionReply::HelperErrorReply();
}
if (!removeDir(homeDir % WAYDROID_USER_FOLDER)) {
qCWarning(WAYDROIDHELPER) << "Failed to remove user Waydroid directory";
return KAuth::ActionReply::HelperErrorReply();
}
return KAuth::ActionReply::SuccessReply();
}
bool WaydroidHelper::removeDir(const QString dirPath)
{
qCWarning(WAYDROIDHELPER) << "Removing " << dirPath;
QDir qDir(dirPath);
if (!qDir.exists()) {
return true; // Ignore if directory not exists
}
return qDir.removeRecursively();
}
KAUTH_HELPER_MAIN("org.kde.plasma.mobileshell.waydroidhelper", WaydroidHelper)
#include "waydroidhelper.moc"
#include "waydroidhelper.moc"

View file

@ -15,12 +15,16 @@
#include <QGuiApplication>
#include <QProcess>
#include <QRegularExpression>
#include <QStandardPaths>
#include <QTimer>
#include <QtLogging>
#include <KAuth/Action>
#include <KAuth/ExecuteJob>
#include <KConfigGroup>
#include <KDesktopFile>
#include <KLocalizedString>
#include <KSandbox>
using namespace Qt::StringLiterals;
@ -328,6 +332,50 @@ 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;
@ -498,3 +546,52 @@ void WaydroidState::checkSessionStarting(const int limit, const int tried)
});
}
}
QString WaydroidState::desktopFileDirectory()
{
auto dir = []() -> QString {
if (KSandbox::isFlatpak()) {
return qEnvironmentVariable("HOME") % u"/.local/share/applications/";
}
return QStandardPaths::writableLocation(QStandardPaths::ApplicationsLocation);
}();
QDir(dir).mkpath(QStringLiteral("."));
return dir;
}
bool WaydroidState::removeWaydroidApplications()
{
const QDir appsDir(desktopFileDirectory());
const auto fileInfos = appsDir.entryInfoList(QDir::Files);
if (fileInfos.length() < 1) {
return false;
}
bool allFileRemoved = true;
for (const auto &fileInfo : fileInfos) {
if (fileInfo.fileName().contains(QStringView(u".desktop"))) {
const KDesktopFile desktopFile(fileInfo.filePath());
const KConfigGroup configGroup = desktopFile.desktopGroup();
if (!configGroup.hasKey(u"Categories"_s)) {
continue;
}
const auto categories = configGroup.readEntry(u"Categories"_s);
if (!categories.contains(u"X-WayDroid-App"_s)) {
continue;
}
QFile file(fileInfo.filePath());
if (!file.remove()) {
allFileRemoved &= false;
qCWarning(WAYDROIDINTEGRATIONPLUGIN) << "Failed to remove: " << desktopFile.name();
}
}
}
return allFileRemoved;
}

View file

@ -52,7 +52,8 @@ public:
NotSupported = 0,
NotInitialized,
Initializing,
Initialized
Initialized,
Resetting
};
Q_ENUM(Status)
@ -103,7 +104,9 @@ public:
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;
@ -202,4 +205,7 @@ private:
* https://github.com/waydroid/waydroid/blob/2c41162d8bfef5bf83333a6ce4834af0c3c2b535/tools/actions/session_manager.py#L31
*/
void checkSessionStarting(const int limit, const int tried = 0);
QString desktopFileDirectory();
bool removeWaydroidApplications();
};

View file

@ -51,6 +51,21 @@ ColumnLayout {
text: i18n("Installed applications")
onClicked: kcm.push("WaydroidApplicationsPage.qml")
}
FormCard.FormButtonDelegate {
text: i18n("Reset waydroid")
onClicked: confirmDialog.open()
}
Kirigami.PromptDialog {
id: confirmDialog
title: i18nc("@title:window", "Confirm Waydroid Reset")
subtitle: i18n("Are you sure you want to reset Waydroid ? This is a destructive action, and will wipe all user data.")
standardButtons: Kirigami.Dialog.Ok | Kirigami.Dialog.Cancel
onAccepted: AIP.WaydroidState.resetWaydroidQml()
}
}
// Some informations as IP address can take time to be set by Waydroid

View file

@ -62,6 +62,11 @@ KCM.SimpleKCM {
}
}
WaydroidLoader {
visible: AIP.WaydroidState.errorTitle === "" && AIP.WaydroidState.status == AIP.WaydroidState.Resetting
text: i18n("Waydroid is resetting.\nIt can take a few seconds.")
}
ColumnLayout {
visible: AIP.WaydroidState.errorTitle === "" && AIP.WaydroidState.status == AIP.WaydroidState.Initialized && AIP.WaydroidState.sessionStatus == AIP.WaydroidState.SessionStopped
anchors.centerIn: parent