mirror of
https://invent.kde.org/marcoa/shift-shell.git
synced 2026-04-26 14:23:09 +00:00
kcm: Implement minimal Waydroid support
I prefer create MR now to avoid biggest merge request. It add minimalist implementation: - First configuration of Waydroid - Stop / Start Waydroid session - Configure properties of Waydroid - Display current ip of Waydroid | Not installed | First initialization | initializing | SessionNotStarted | SessionStarting | SessionStarted | | ------ | ------ | ------ | ------ | ------ | ------ | |  |  |  |  |  |  | Linked to https://invent.kde.org/teams/plasma-mobile/issues/-/issues/307 **Note for Reviewer**: In my local environment, i need to add manually the KAuth files otherwise polkit not recognize the implementation. ``` sudo cp ~/kde/usr/share/dbus-1/system-services/org.kde.plasma.mobileshell.waydroidhelper.service /usr/share/dbus-1/system-services/ sudo cp ~/kde/usr/share/dbus-1/system.d/org.kde.plasma.mobileshell.waydroidhelper.conf /usr/share/dbus-1/system.d/ sudo cp ~/kde/usr/share/polkit-1/actions/org.kde.plasma.mobileshell.waydroidhelper.policy /usr/share/polkit-1/actions/ ```
This commit is contained in:
parent
036b633f16
commit
14839c4389
19 changed files with 1100 additions and 0 deletions
|
|
@ -5,6 +5,7 @@ Dependencies:
|
||||||
- 'on': ['@all']
|
- 'on': ['@all']
|
||||||
'require':
|
'require':
|
||||||
'frameworks/extra-cmake-modules': '@latest-kf6'
|
'frameworks/extra-cmake-modules': '@latest-kf6'
|
||||||
|
'frameworks/kauth': '@latest-kf6'
|
||||||
'frameworks/kconfig': '@latest-kf6'
|
'frameworks/kconfig': '@latest-kf6'
|
||||||
'frameworks/kcoreaddons': '@latest-kf6'
|
'frameworks/kcoreaddons': '@latest-kf6'
|
||||||
'frameworks/ki18n': '@latest-kf6'
|
'frameworks/ki18n': '@latest-kf6'
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,7 @@ include(ECMFindQmlModule)
|
||||||
include(ECMGenerateQmlTypes)
|
include(ECMGenerateQmlTypes)
|
||||||
include(ECMConfiguredInstall)
|
include(ECMConfiguredInstall)
|
||||||
include(ECMFindQmlModule)
|
include(ECMFindQmlModule)
|
||||||
|
include(ECMQtDeclareLoggingCategory)
|
||||||
include(GenerateExportHeader)
|
include(GenerateExportHeader)
|
||||||
include(KDEGitCommitHooks)
|
include(KDEGitCommitHooks)
|
||||||
include(KDEClangFormat)
|
include(KDEClangFormat)
|
||||||
|
|
@ -119,6 +120,12 @@ set_package_properties(KWinDBusInterface PROPERTIES DESCRIPTION "KWin DBus inter
|
||||||
PURPOSE "Needed for virtual keyboard toggle button"
|
PURPOSE "Needed for virtual keyboard toggle button"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
find_package(KF6 ${KF6_MIN_VERSION} REQUIRED COMPONENTS Auth)
|
||||||
|
set_package_properties(KF6::AuthCore PROPERTIES
|
||||||
|
TYPE REQUIRED
|
||||||
|
PURPOSE "Allows Plasma Mobile to configure Waydroid"
|
||||||
|
)
|
||||||
|
|
||||||
include(CheckIncludeFiles)
|
include(CheckIncludeFiles)
|
||||||
|
|
||||||
ecm_find_qmlmodule(org.kde.pipewire 0.1)
|
ecm_find_qmlmodule(org.kde.pipewire 0.1)
|
||||||
|
|
|
||||||
9
LICENSES/BSD-2-Clause.txt
Normal file
9
LICENSES/BSD-2-Clause.txt
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
Copyright (c) <year> <owner>
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
||||||
|
|
||||||
|
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
||||||
|
|
||||||
|
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
# SPDX-FileCopyrightText: 2017-2021 Marco Martin <mart@kde.org>
|
# SPDX-FileCopyrightText: 2017-2021 Marco Martin <mart@kde.org>
|
||||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
add_subdirectory(waydroidintegrationplugin)
|
||||||
add_subdirectory(hapticsplugin)
|
add_subdirectory(hapticsplugin)
|
||||||
add_subdirectory(mmplugin)
|
add_subdirectory(mmplugin)
|
||||||
add_subdirectory(mobileshell)
|
add_subdirectory(mobileshell)
|
||||||
|
|
|
||||||
25
components/waydroidintegrationplugin/CMakeLists.txt
Normal file
25
components/waydroidintegrationplugin/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
# SPDX-FileCopyrightText: 2025 Florian RICHER <florian.richer@protonmail.com>
|
||||||
|
# SPDX-License-Identifier: BSD-2-Clause
|
||||||
|
|
||||||
|
ecm_add_qml_module(waydroidintegrationplugin URI org.kde.plasma.private.mobileshell.waydroidintegrationplugin GENERATE_PLUGIN_SOURCE)
|
||||||
|
target_sources(waydroidintegrationplugin PRIVATE waydroidstate.cpp)
|
||||||
|
|
||||||
|
target_link_libraries(waydroidintegrationplugin PRIVATE
|
||||||
|
Qt::Gui
|
||||||
|
Qt::Qml
|
||||||
|
Qt::Quick
|
||||||
|
KF6::AuthCore
|
||||||
|
KF6::I18n
|
||||||
|
)
|
||||||
|
|
||||||
|
ecm_finalize_qml_module(waydroidintegrationplugin)
|
||||||
|
|
||||||
|
ecm_qt_declare_logging_category(waydroidintegrationplugin
|
||||||
|
HEADER waydroidintegrationplugin_debug.h
|
||||||
|
IDENTIFIER WAYDROIDINTEGRATIONPLUGIN
|
||||||
|
DEFAULT_SEVERITY Warning
|
||||||
|
CATEGORY_NAME org.kde.plasma.private.mobileshell.waydroidintegrationplugin
|
||||||
|
DESCRIPTION "Plugin to add Waydroid Integration on Plasma"
|
||||||
|
)
|
||||||
|
|
||||||
|
add_subdirectory(kauth)
|
||||||
26
components/waydroidintegrationplugin/kauth/CMakeLists.txt
Normal file
26
components/waydroidintegrationplugin/kauth/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
# SPDX-FileCopyrightText: 2025 Florian RICHER <florian.richer@protonmail.com>
|
||||||
|
# SPDX-License-Identifier: BSD-2-Clause
|
||||||
|
|
||||||
|
add_executable(waydroidhelper)
|
||||||
|
target_sources(waydroidhelper PRIVATE waydroidhelper.cpp)
|
||||||
|
|
||||||
|
target_link_libraries(waydroidhelper
|
||||||
|
Qt6::Core
|
||||||
|
KF6::AuthCore
|
||||||
|
KF6::CoreAddons
|
||||||
|
)
|
||||||
|
|
||||||
|
install(TARGETS waydroidhelper DESTINATION ${KAUTH_HELPER_INSTALL_DIR})
|
||||||
|
|
||||||
|
find_package(KF6Auth NO_MODULE)
|
||||||
|
|
||||||
|
kauth_install_helper_files(waydroidhelper org.kde.plasma.mobileshell.waydroidhelper root)
|
||||||
|
kauth_install_actions(org.kde.plasma.mobileshell.waydroidhelper waydroidhelper.actions)
|
||||||
|
|
||||||
|
ecm_qt_declare_logging_category(waydroidhelper
|
||||||
|
HEADER waydroidhelper_debug.h
|
||||||
|
IDENTIFIER WAYDROIDHELPER
|
||||||
|
DEFAULT_SEVERITY Warning
|
||||||
|
CATEGORY_NAME org.kde.plasma.mobileshell.waydroidhelper
|
||||||
|
DESCRIPTION "Helper for Waydroid for some actions need root access"
|
||||||
|
)
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
# SPDX-FileCopyrightText: 2025 Florian RICHER <florian.richer@protonmail.com>
|
||||||
|
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
[Domain]
|
||||||
|
Name=Waydroid Management
|
||||||
|
Icon=color
|
||||||
|
|
||||||
|
[org.kde.plasma.mobileshell.waydroidhelper.initialize]
|
||||||
|
Name=Initialize Waydroid
|
||||||
|
Description=Allow initialization of Waydroid
|
||||||
|
Policy=yes
|
||||||
|
PolicyInactive=yes
|
||||||
|
Persistence=session
|
||||||
|
|
@ -0,0 +1,61 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2025 Florian RICHER <florian.richer@protonmail.com>
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "waydroidhelper_debug.h"
|
||||||
|
|
||||||
|
#include <KAuth/ActionReply>
|
||||||
|
#include <KAuth/HelperSupport>
|
||||||
|
|
||||||
|
#include <QDebug>
|
||||||
|
#include <QFile>
|
||||||
|
#include <QFileInfo>
|
||||||
|
#include <QLoggingCategory>
|
||||||
|
#include <QObject>
|
||||||
|
#include <QProcess>
|
||||||
|
|
||||||
|
using namespace Qt::StringLiterals;
|
||||||
|
|
||||||
|
#define WAYDROID_COMMAND "waydroid"
|
||||||
|
|
||||||
|
class WaydroidHelper : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
public Q_SLOTS:
|
||||||
|
KAuth::ActionReply initialize(const QVariantMap &args);
|
||||||
|
};
|
||||||
|
|
||||||
|
KAuth::ActionReply WaydroidHelper::initialize(const QVariantMap &args)
|
||||||
|
{
|
||||||
|
const QString systemType = args.value("systemType"_L1).toString();
|
||||||
|
const QString romType = args.value("romType"_L1).toString();
|
||||||
|
const bool forced = args.value("forced"_L1, false).toBool();
|
||||||
|
|
||||||
|
QStringList arguments{u"init"_s, u"-s"_s, systemType, u"-r"_s, romType};
|
||||||
|
if (forced) {
|
||||||
|
arguments << "-f";
|
||||||
|
}
|
||||||
|
|
||||||
|
QProcess *process = new QProcess(this);
|
||||||
|
process->start(WAYDROID_COMMAND, arguments);
|
||||||
|
process->waitForFinished();
|
||||||
|
|
||||||
|
if (process->exitCode() == 0) {
|
||||||
|
return KAuth::ActionReply::SuccessReply();
|
||||||
|
} else {
|
||||||
|
QByteArray errorData = process->readAllStandardError();
|
||||||
|
QString errorString = QString::fromUtf8(errorData);
|
||||||
|
|
||||||
|
qCWarning(WAYDROIDHELPER) << "Failed to initialize Waydroid: " << errorString;
|
||||||
|
|
||||||
|
KAuth::ActionReply reply = KAuth::ActionReply::HelperErrorReply();
|
||||||
|
reply.setErrorDescription(errorString);
|
||||||
|
return reply;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
KAUTH_HELPER_MAIN("org.kde.plasma.mobileshell.waydroidhelper", WaydroidHelper)
|
||||||
|
|
||||||
|
#include "waydroidhelper.moc"
|
||||||
399
components/waydroidintegrationplugin/waydroidstate.cpp
Normal file
399
components/waydroidintegrationplugin/waydroidstate.cpp
Normal file
|
|
@ -0,0 +1,399 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2025 Florian RICHER <florian.richer@protonmail.com>
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "waydroidstate.h"
|
||||||
|
#include "waydroidintegrationplugin_debug.h"
|
||||||
|
|
||||||
|
#include <QClipboard>
|
||||||
|
#include <QDebug>
|
||||||
|
#include <QGuiApplication>
|
||||||
|
#include <QProcess>
|
||||||
|
#include <QRegularExpression>
|
||||||
|
#include <QTimer>
|
||||||
|
#include <QtLogging>
|
||||||
|
|
||||||
|
#include <KAuth/Action>
|
||||||
|
#include <KAuth/ExecuteJob>
|
||||||
|
#include <KLocalizedString>
|
||||||
|
|
||||||
|
using namespace Qt::StringLiterals;
|
||||||
|
|
||||||
|
#define WAYDROID_COMMAND "waydroid"
|
||||||
|
#define MULTI_WINDOWS_PROP_KEY "persist.waydroid.multi_windows"
|
||||||
|
#define SUSPEND_PROP_KEY "persist.waydroid.suspend"
|
||||||
|
#define UEVENT_PROP_KEY "persist.waydroid.uevent"
|
||||||
|
|
||||||
|
static const QRegularExpression sessionRegExp(u"Session:\\s*(\\w+)"_s);
|
||||||
|
static const QRegularExpression ipAdressRegExp(u"IP address:\\s*(\\d+\\.\\d+\\.\\d+\\.\\d+)"_s);
|
||||||
|
|
||||||
|
WaydroidState::WaydroidState(QObject *parent)
|
||||||
|
: QObject{parent}
|
||||||
|
{
|
||||||
|
// Connect it-self to auto-refresh when required status has changed
|
||||||
|
connect(this, &WaydroidState::statusChanged, this, &WaydroidState::refreshSessionInfo);
|
||||||
|
connect(this, &WaydroidState::sessionStatusChanged, this, &WaydroidState::refreshPropsInfo);
|
||||||
|
|
||||||
|
refreshSupportsInfo();
|
||||||
|
}
|
||||||
|
|
||||||
|
void WaydroidState::refreshSupportsInfo()
|
||||||
|
{
|
||||||
|
const QStringList arguments{u"-h"_s};
|
||||||
|
|
||||||
|
QProcess *process = new QProcess(this);
|
||||||
|
process->start(WAYDROID_COMMAND, arguments);
|
||||||
|
process->waitForFinished();
|
||||||
|
|
||||||
|
const int exitCode = process->exitCode();
|
||||||
|
if (exitCode != 0) {
|
||||||
|
m_status = NotSupported;
|
||||||
|
Q_EMIT statusChanged();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const QString output = fetchSessionInfo();
|
||||||
|
if (!output.contains("WayDroid is not initialized")) {
|
||||||
|
m_status = Initialized;
|
||||||
|
} else {
|
||||||
|
m_status = NotInitialized;
|
||||||
|
}
|
||||||
|
Q_EMIT statusChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
void WaydroidState::refreshSessionInfo()
|
||||||
|
{
|
||||||
|
if (m_status != Initialized) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const QString output = fetchSessionInfo();
|
||||||
|
|
||||||
|
const QString sessionMatchResult = extractRegExp(output, sessionRegExp);
|
||||||
|
WaydroidState::SessionStatus newSessionStatus;
|
||||||
|
|
||||||
|
if (!sessionMatchResult.isEmpty()) {
|
||||||
|
newSessionStatus = sessionMatchResult.contains("RUNNING") ? SessionRunning : SessionStopped;
|
||||||
|
} else {
|
||||||
|
newSessionStatus = SessionStopped;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_sessionStatus != newSessionStatus) {
|
||||||
|
m_sessionStatus = newSessionStatus;
|
||||||
|
Q_EMIT sessionStatusChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
m_ipAddress = extractRegExp(output, ipAdressRegExp);
|
||||||
|
Q_EMIT ipAddressChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
void WaydroidState::refreshPropsInfo()
|
||||||
|
{
|
||||||
|
if (m_sessionStatus != SessionRunning) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const QString multiWindowsPropValue = fetchPropValue(MULTI_WINDOWS_PROP_KEY, "false");
|
||||||
|
m_multiWindows = multiWindowsPropValue == "true";
|
||||||
|
Q_EMIT multiWindowsChanged();
|
||||||
|
|
||||||
|
const QString suspendPropValue = fetchPropValue(SUSPEND_PROP_KEY, "true");
|
||||||
|
m_suspend = suspendPropValue == "true";
|
||||||
|
Q_EMIT suspendChanged();
|
||||||
|
|
||||||
|
const QString ueventPropValue = fetchPropValue(UEVENT_PROP_KEY, "false");
|
||||||
|
m_uevent = ueventPropValue == "true";
|
||||||
|
Q_EMIT ueventChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
void WaydroidState::resetError()
|
||||||
|
{
|
||||||
|
m_errorTitle = "";
|
||||||
|
Q_EMIT errorTitleChanged();
|
||||||
|
|
||||||
|
if (m_errorMessage != "") {
|
||||||
|
m_errorMessage = "";
|
||||||
|
Q_EMIT errorMessageChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void WaydroidState::initialize(const SystemType systemType, const RomType romType, const bool forced)
|
||||||
|
{
|
||||||
|
if (m_status == Initializing) {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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::finished, this, [this](KJob *job, auto) {
|
||||||
|
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();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void WaydroidState::startSession()
|
||||||
|
{
|
||||||
|
if (m_sessionStatus == SessionStarting || m_sessionStatus == SessionRunning) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_sessionStatus = SessionStarting;
|
||||||
|
Q_EMIT sessionStatusChanged();
|
||||||
|
|
||||||
|
const QStringList arguments{u"session"_s, u"start"_s};
|
||||||
|
|
||||||
|
// Don't wait for result because the command is blocking
|
||||||
|
QProcess *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);
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
void WaydroidState::stopSession()
|
||||||
|
{
|
||||||
|
if (m_sessionStatus == SessionStopped) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const QStringList arguments{u"session"_s, u"stop"_s};
|
||||||
|
|
||||||
|
QProcess *process = new QProcess(this);
|
||||||
|
process->start(WAYDROID_COMMAND, arguments);
|
||||||
|
process->waitForFinished();
|
||||||
|
|
||||||
|
if (process->exitCode() == 0) {
|
||||||
|
m_sessionStatus = SessionStopped;
|
||||||
|
Q_EMIT sessionStatusChanged();
|
||||||
|
} else {
|
||||||
|
qCWarning(WAYDROIDINTEGRATIONPLUGIN) << "Failed to stop the Waydroid session: " << process->readAllStandardError();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void WaydroidState::copyToClipboard(const QString text)
|
||||||
|
{
|
||||||
|
qGuiApp->clipboard()->setText(text);
|
||||||
|
}
|
||||||
|
|
||||||
|
WaydroidState::Status WaydroidState::status() const
|
||||||
|
{
|
||||||
|
return m_status;
|
||||||
|
}
|
||||||
|
|
||||||
|
WaydroidState::SessionStatus WaydroidState::sessionStatus() const
|
||||||
|
{
|
||||||
|
return m_sessionStatus;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString WaydroidState::ipAddress() const
|
||||||
|
{
|
||||||
|
return m_ipAddress;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString WaydroidState::errorTitle() const
|
||||||
|
{
|
||||||
|
return m_errorTitle;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString WaydroidState::errorMessage() const
|
||||||
|
{
|
||||||
|
return m_errorMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
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};
|
||||||
|
|
||||||
|
QProcess *process = new QProcess(this);
|
||||||
|
process->start(WAYDROID_COMMAND, arguments);
|
||||||
|
process->waitForFinished();
|
||||||
|
|
||||||
|
const QString commandOutput = process->readAllStandardOutput();
|
||||||
|
const QString value = commandOutput.split("\n").first().trimmed();
|
||||||
|
|
||||||
|
if (value.isEmpty()) {
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool WaydroidState::writePropValue(const QString key, const QString value)
|
||||||
|
{
|
||||||
|
const QStringList arguments{u"prop"_s, u"set"_s, key, value};
|
||||||
|
|
||||||
|
QProcess *process = new QProcess(this);
|
||||||
|
process->start(WAYDROID_COMMAND, arguments);
|
||||||
|
process->waitForFinished();
|
||||||
|
|
||||||
|
return process->exitCode() == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString WaydroidState::extractRegExp(const QString text, const QRegularExpression regExp) const
|
||||||
|
{
|
||||||
|
const QRegularExpressionMatch match = regExp.match(text);
|
||||||
|
|
||||||
|
if (match.hasMatch() && match.lastCapturedIndex() > 0) {
|
||||||
|
return match.captured(match.lastCapturedIndex());
|
||||||
|
} else {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void WaydroidState::checkSessionStarting(const int limit, const int tried)
|
||||||
|
{
|
||||||
|
if (m_sessionStatus != SessionStarting) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const QString output = fetchSessionInfo();
|
||||||
|
const QString sessionMatchResult = extractRegExp(output, sessionRegExp);
|
||||||
|
|
||||||
|
if (sessionMatchResult.contains("RUNNING")) {
|
||||||
|
m_sessionStatus = SessionRunning;
|
||||||
|
Q_EMIT sessionStatusChanged();
|
||||||
|
} else if (tried == limit) {
|
||||||
|
m_sessionStatus = SessionStopped;
|
||||||
|
Q_EMIT sessionStatusChanged();
|
||||||
|
qCWarning(WAYDROIDINTEGRATIONPLUGIN) << "Failed to start the session after " << tried << " tries";
|
||||||
|
} else {
|
||||||
|
QTimer::singleShot(500, [this, tried, limit]() {
|
||||||
|
checkSessionStarting(limit, tried + 1);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
178
components/waydroidintegrationplugin/waydroidstate.h
Normal file
178
components/waydroidintegrationplugin/waydroidstate.h
Normal file
|
|
@ -0,0 +1,178 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2025 Florian RICHER <florian.richer@protonmail.com>
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
|
||||||
|
#include <qqmlregistration.h>
|
||||||
|
#include <qtmetamacros.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class provides an interface to interact with the Waydroid container,
|
||||||
|
* including session management and property configuration.
|
||||||
|
*
|
||||||
|
* @author Florian RICHER <florian.richer@protonmail.com>
|
||||||
|
*/
|
||||||
|
class WaydroidState : 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(QString ipAddress READ ipAddress NOTIFY ipAddressChanged)
|
||||||
|
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)
|
||||||
|
|
||||||
|
public:
|
||||||
|
WaydroidState(QObject *parent = nullptr);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @enum Status
|
||||||
|
* @brief Defines the possible installation statuses of the Waydroid service.
|
||||||
|
*/
|
||||||
|
enum Status {
|
||||||
|
NotSupported = 0,
|
||||||
|
NotInitialized,
|
||||||
|
Initializing,
|
||||||
|
Initialized
|
||||||
|
};
|
||||||
|
Q_ENUM(Status)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @enum SessionStatus
|
||||||
|
* @brief Defines the possible states of a Waydroid session.
|
||||||
|
*/
|
||||||
|
enum SessionStatus {
|
||||||
|
SessionStopped = 0,
|
||||||
|
SessionStarting,
|
||||||
|
SessionRunning
|
||||||
|
};
|
||||||
|
Q_ENUM(SessionStatus)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @enum SystemType
|
||||||
|
* @brief Defines the types of Android systems supported by Waydroid.
|
||||||
|
*/
|
||||||
|
enum SystemType {
|
||||||
|
Vanilla = 0, ///< Vanilla Android system.
|
||||||
|
Foss, ///< Free and Open Source Software variant.
|
||||||
|
Gapps ///< Variant with Google Apps included.
|
||||||
|
};
|
||||||
|
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 = 0, ///< LineageOS ROM.
|
||||||
|
Bliss ///< Bliss ROM.
|
||||||
|
};
|
||||||
|
Q_ENUM(RomType)
|
||||||
|
|
||||||
|
Q_INVOKABLE void refreshSupportsInfo();
|
||||||
|
Q_INVOKABLE void refreshSessionInfo();
|
||||||
|
Q_INVOKABLE void refreshPropsInfo();
|
||||||
|
Q_INVOKABLE void resetError();
|
||||||
|
Q_INVOKABLE void initialize(const SystemType systemType, const RomType romType, const bool forced = false);
|
||||||
|
Q_INVOKABLE void startSession();
|
||||||
|
Q_INVOKABLE void stopSession();
|
||||||
|
Q_INVOKABLE void copyToClipboard(const QString text);
|
||||||
|
|
||||||
|
Status status() const;
|
||||||
|
SessionStatus sessionStatus() const;
|
||||||
|
QString ipAddress() const;
|
||||||
|
QString errorTitle() const;
|
||||||
|
QString errorMessage() 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:
|
||||||
|
void statusChanged();
|
||||||
|
void sessionStatusChanged();
|
||||||
|
void ipAddressChanged();
|
||||||
|
void multiWindowsChanged();
|
||||||
|
void suspendChanged();
|
||||||
|
void ueventChanged();
|
||||||
|
void errorTitleChanged();
|
||||||
|
void errorMessageChanged();
|
||||||
|
|
||||||
|
private:
|
||||||
|
Status m_status{NotInitialized};
|
||||||
|
SessionStatus m_sessionStatus{SessionStopped};
|
||||||
|
QString m_ipAddress{""};
|
||||||
|
QString m_errorTitle{""};
|
||||||
|
QString m_errorMessage{""};
|
||||||
|
|
||||||
|
// Waydroid props. See https://docs.waydro.id/usage/waydroid-prop-options
|
||||||
|
bool m_multiWindows{false};
|
||||||
|
bool m_suspend{false};
|
||||||
|
bool m_uevent{false};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Executes the command to retrieve the current session status and related
|
||||||
|
* information from Waydroid.
|
||||||
|
*
|
||||||
|
* @return A QString containing the output of the Waydroid session status command.
|
||||||
|
*/
|
||||||
|
QString fetchSessionInfo();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Executes the command to retrieve the value of a specified property from the Waydroid container.
|
||||||
|
*
|
||||||
|
* @param key The key of the property to fetch.
|
||||||
|
* @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.
|
||||||
|
*/
|
||||||
|
QString fetchPropValue(const QString key, const QString defaultValue);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Executes the command to writes a value to a specified property in the Waydroid container.
|
||||||
|
*
|
||||||
|
* @param key The key of the property to set.
|
||||||
|
* @param value The value to write to the property.
|
||||||
|
* @return A boolean indicating whether the write operation was successful.
|
||||||
|
*/
|
||||||
|
bool writePropValue(const QString key, const QString value);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Extracts text from a string using a regular expression pattern.
|
||||||
|
*
|
||||||
|
* @param text The text to search within.
|
||||||
|
* @param regExp The regular expression pattern to use for extraction.
|
||||||
|
* @return A QString containing the extracted text if a match is found; otherwise, an empty string.
|
||||||
|
*/
|
||||||
|
QString extractRegExp(const QString text, const QRegularExpression regExp) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Checks every 500ms if the session has started.
|
||||||
|
*
|
||||||
|
* This function periodically checks whether a session has started. If the session starts,
|
||||||
|
* it emits a "Running" signal. If the check count reaches the specified limit without
|
||||||
|
* the session starting, it emits a "Stopped" signal and logs a warning message.
|
||||||
|
*
|
||||||
|
* @param limit The maximum number of attempts to check for session start before stopping.
|
||||||
|
* @param tried The current number of attempts made to check for session start (defaults to 0).
|
||||||
|
*
|
||||||
|
* @todo Investigate using DBus for a cleaner implementation, potentially using the method:
|
||||||
|
* id.waydro.Container /ContainerManager id.waydro.ContainerManager.Start(a{ss} session).
|
||||||
|
* This would require duplicating the session start command logic from:
|
||||||
|
* https://github.com/waydroid/waydroid/blob/2c41162d8bfef5bf83333a6ce4834af0c3c2b535/tools/actions/session_manager.py#L31
|
||||||
|
*/
|
||||||
|
void checkSessionStarting(const int limit, const int tried = 0);
|
||||||
|
};
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
# SPDX-FileCopyrightText: 2022 Devin Lin <devin@kde.org>
|
# SPDX-FileCopyrightText: 2022 Devin Lin <devin@kde.org>
|
||||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
add_subdirectory(waydroidintegration)
|
||||||
add_subdirectory(mobileshell)
|
add_subdirectory(mobileshell)
|
||||||
add_subdirectory(info)
|
add_subdirectory(info)
|
||||||
add_subdirectory(time)
|
add_subdirectory(time)
|
||||||
|
|
|
||||||
14
kcms/waydroidintegration/CMakeLists.txt
Normal file
14
kcms/waydroidintegration/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
# SPDX-FileCopyrightText: 2025 Florian RICHER <florian.richer@protonmail.com>
|
||||||
|
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
kcmutils_add_qml_kcm(kcm_waydroidintegration SOURCES waydroidintegration.cpp)
|
||||||
|
|
||||||
|
target_link_libraries(kcm_waydroidintegration PRIVATE
|
||||||
|
Qt::Core
|
||||||
|
KF6::CoreAddons
|
||||||
|
KF6::KCMUtils
|
||||||
|
KF6::I18n
|
||||||
|
KF6::KCMUtilsQuick
|
||||||
|
Plasma::Plasma
|
||||||
|
Plasma::PlasmaQuick
|
||||||
|
)
|
||||||
8
kcms/waydroidintegration/Messages.sh
Normal file
8
kcms/waydroidintegration/Messages.sh
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
#! /usr/bin/env bash
|
||||||
|
|
||||||
|
# SPDX-FileCopyrightText: 2025 Florian RICHER <florian.richer@protonmail.com>
|
||||||
|
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
$XGETTEXT `find . -name \*.js -o -name \*.qml -o -name \*.cpp` -o $podir/kcm_waydroidintegration.pot
|
||||||
|
|
||||||
|
|
||||||
25
kcms/waydroidintegration/kcm_waydroidintegration.json
Normal file
25
kcms/waydroidintegration/kcm_waydroidintegration.json
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
{
|
||||||
|
"KPlugin": {
|
||||||
|
"Authors": [
|
||||||
|
{
|
||||||
|
"Email": "florian.richer@protonmail.com",
|
||||||
|
"Name": "Florian RICHER"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"Category": "Waydroid Integration",
|
||||||
|
"Description": "Waydroid configuration",
|
||||||
|
"EnabledByDefault": true,
|
||||||
|
"FormFactors": [
|
||||||
|
"desktop",
|
||||||
|
"handset",
|
||||||
|
"tablet",
|
||||||
|
"mediacenter"
|
||||||
|
],
|
||||||
|
"Icon": "folder-android-symbolic",
|
||||||
|
"License": "GPL",
|
||||||
|
"Name": "Waydroid Integration",
|
||||||
|
"Website": "https://plasma-mobile.org/"
|
||||||
|
},
|
||||||
|
"X-KDE-Keywords": "waydroid",
|
||||||
|
"X-KDE-System-Settings-Parent-Category": "system-administration"
|
||||||
|
}
|
||||||
117
kcms/waydroidintegration/ui/WaydroidConfigurationForm.qml
Normal file
117
kcms/waydroidintegration/ui/WaydroidConfigurationForm.qml
Normal file
|
|
@ -0,0 +1,117 @@
|
||||||
|
/*
|
||||||
|
* 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.kirigamiaddons.formcard 1.0 as FormCard
|
||||||
|
import org.kde.plasma.components 3.0 as PC3
|
||||||
|
import org.kde.plasma.private.mobileshell.waydroidintegrationplugin as AIP
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
FormCard.FormHeader {
|
||||||
|
title: i18n("General information")
|
||||||
|
}
|
||||||
|
|
||||||
|
FormCard.FormCard {
|
||||||
|
FormCard.FormTextDelegate {
|
||||||
|
text: i18n("IP address")
|
||||||
|
description: AIP.WaydroidState.ipAddress
|
||||||
|
trailing: PC3.Button {
|
||||||
|
text: i18n('Copy')
|
||||||
|
icon.name: 'edit-copy-symbolic'
|
||||||
|
onClicked: AIP.WaydroidState.copyToClipboard(AIP.WaydroidState.ipAddress)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FormCard.FormTextDelegate {
|
||||||
|
text: i18n("Waydroid status")
|
||||||
|
description: i18n("Running")
|
||||||
|
|
||||||
|
trailing: PC3.Button {
|
||||||
|
text: i18n("Stop session")
|
||||||
|
onClicked: AIP.WaydroidState.stopSession()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Some informations as IP address can take time to be set by Waydroid
|
||||||
|
Timer {
|
||||||
|
id: autoRefreshSessionTimer
|
||||||
|
interval: 2000
|
||||||
|
repeat: true
|
||||||
|
running: root.visible
|
||||||
|
onTriggered: AIP.WaydroidState.refreshSessionInfo()
|
||||||
|
}
|
||||||
|
|
||||||
|
FormCard.FormHeader {
|
||||||
|
title: i18n("Waydroid properties")
|
||||||
|
}
|
||||||
|
|
||||||
|
FormCard.FormCard {
|
||||||
|
id: infoMessage
|
||||||
|
visible: false
|
||||||
|
|
||||||
|
Kirigami.Theme.inherit: false
|
||||||
|
Kirigami.Theme.backgroundColor: root.Kirigami.Theme.neutralBackgroundColor
|
||||||
|
|
||||||
|
FormCard.FormTextDelegate {
|
||||||
|
text: i18n("May require restarting the waydroid session to apply")
|
||||||
|
textItem.wrapMode: Text.WordWrap
|
||||||
|
icon.name: "dialog-warning"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: AIP.WaydroidState
|
||||||
|
|
||||||
|
function onSessionStatusChanged() {
|
||||||
|
infoMessage.visible = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FormCard.FormCard {
|
||||||
|
FormCard.FormSwitchDelegate {
|
||||||
|
id: multiWindows
|
||||||
|
text: i18n("Multi Windows")
|
||||||
|
description: i18n("Enables/Disables window integration with the desktop")
|
||||||
|
checked: AIP.WaydroidState.multiWindows
|
||||||
|
onToggled: {
|
||||||
|
AIP.WaydroidState.multiWindows = checked
|
||||||
|
infoMessage.visible = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FormCard.FormDelegateSeparator { above: multiWindows; below: suspend }
|
||||||
|
|
||||||
|
FormCard.FormSwitchDelegate {
|
||||||
|
id: suspend
|
||||||
|
text: i18n("Suspend")
|
||||||
|
description: i18n("Let the Waydroid container sleep (after the display timeout) when no apps are active")
|
||||||
|
checked: AIP.WaydroidState.suspend
|
||||||
|
onToggled: {
|
||||||
|
AIP.WaydroidState.suspend = checked
|
||||||
|
infoMessage.visible = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FormCard.FormDelegateSeparator { above: suspend; below: uevent }
|
||||||
|
|
||||||
|
FormCard.FormSwitchDelegate {
|
||||||
|
id: uevent
|
||||||
|
text: i18n("UEvent")
|
||||||
|
description: i18n("Allow android direct access to hotplugged devices")
|
||||||
|
checked: AIP.WaydroidState.uevent
|
||||||
|
onToggled: {
|
||||||
|
AIP.WaydroidState.uevent = checked
|
||||||
|
infoMessage.visible = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,42 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2025 Florian RICHER <florian.richer@protonmail.com>
|
||||||
|
* SPDX-License-Identifier: LGPL-2.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
import QtQuick.Layouts 1.15
|
||||||
|
import QtQuick.Controls 2.15 as QQC2
|
||||||
|
|
||||||
|
import org.kde.kirigami 2.19 as Kirigami
|
||||||
|
import org.kde.kirigamiaddons.formcard 1.0 as FormCard
|
||||||
|
import org.kde.plasma.components 3.0 as PC3
|
||||||
|
import org.kde.plasma.private.mobileshell.waydroidintegrationplugin as AIP
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
FormCard.FormHeader {
|
||||||
|
title: i18n("Initial configuration")
|
||||||
|
}
|
||||||
|
|
||||||
|
FormCard.FormCard {
|
||||||
|
FormCard.FormComboBoxDelegate {
|
||||||
|
id: systemType
|
||||||
|
text: i18n("System type")
|
||||||
|
|
||||||
|
model: [
|
||||||
|
{"name": "Vanilla", "value": AIP.WaydroidState.Vanilla},
|
||||||
|
{"name": "FOSS", "value": AIP.WaydroidState.Foss},
|
||||||
|
{"name": "GAPPS", "value": AIP.WaydroidState.Gapps}
|
||||||
|
]
|
||||||
|
|
||||||
|
textRole: "name"
|
||||||
|
valueRole: "value"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PC3.Button {
|
||||||
|
text: i18n("Configure waydroid")
|
||||||
|
Layout.alignment: Qt.AlignHCenter
|
||||||
|
enabled: systemType.currentValue !== undefined
|
||||||
|
|
||||||
|
onClicked: AIP.WaydroidState.initialize(systemType.currentValue, AIP.WaydroidState.Lineage)
|
||||||
|
}
|
||||||
|
}
|
||||||
34
kcms/waydroidintegration/ui/WaydroidLoader.qml
Normal file
34
kcms/waydroidintegration/ui/WaydroidLoader.qml
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
/*
|
||||||
|
* 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 text
|
||||||
|
|
||||||
|
anchors.centerIn: parent
|
||||||
|
spacing: Kirigami.Units.largeSpacing
|
||||||
|
|
||||||
|
PC3.BusyIndicator {
|
||||||
|
Layout.alignment: Qt.AlignHCenter
|
||||||
|
implicitHeight: Kirigami.Units.iconSizes.huge
|
||||||
|
implicitWidth: Kirigami.Units.iconSizes.huge
|
||||||
|
|
||||||
|
Kirigami.Theme.inherit: false
|
||||||
|
Kirigami.Theme.colorSet: Kirigami.Theme.Complementary
|
||||||
|
}
|
||||||
|
|
||||||
|
QQC2.Label {
|
||||||
|
text: root.text
|
||||||
|
horizontalAlignment: Text.AlignHCenter
|
||||||
|
Layout.alignment: Qt.AlignHCenter
|
||||||
|
}
|
||||||
|
}
|
||||||
110
kcms/waydroidintegration/ui/main.qml
Normal file
110
kcms/waydroidintegration/ui/main.qml
Normal file
|
|
@ -0,0 +1,110 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2025 Florian RICHER <florian.richer@protonmail.com>
|
||||||
|
* SPDX-License-Identifier: LGPL-2.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
import QtQuick 2.15
|
||||||
|
import QtQuick.Layouts 1.15
|
||||||
|
import QtQuick.Controls 2.15 as QQC2
|
||||||
|
|
||||||
|
import org.kde.kirigami 2.19 as Kirigami
|
||||||
|
import org.kde.kcmutils as KCM
|
||||||
|
import org.kde.kirigamiaddons.formcard 1.0 as FormCard
|
||||||
|
import org.kde.plasma.components 3.0 as PC3
|
||||||
|
import org.kde.plasma.private.mobileshell.waydroidintegrationplugin as AIP
|
||||||
|
|
||||||
|
KCM.SimpleKCM {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
title: i18n("Waydroid Integration")
|
||||||
|
|
||||||
|
topPadding: 0
|
||||||
|
bottomPadding: 0
|
||||||
|
leftPadding: 0
|
||||||
|
rightPadding: 0
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
visible: AIP.WaydroidState.errorTitle === "" && AIP.WaydroidState.status == AIP.WaydroidState.NotSupported
|
||||||
|
anchors.centerIn: parent
|
||||||
|
spacing: Kirigami.Units.largeSpacing
|
||||||
|
|
||||||
|
QQC2.Label {
|
||||||
|
text: i18n("Waydroid is not installed")
|
||||||
|
Layout.alignment: Qt.AlignHCenter
|
||||||
|
horizontalAlignment: Text.AlignHCenter
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
PC3.Button {
|
||||||
|
text: i18n("Check installation")
|
||||||
|
Layout.alignment: Qt.AlignHCenter
|
||||||
|
onClicked: AIP.WaydroidState.refreshSupportsInfo()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
WaydroidInitialConfigurationForm {
|
||||||
|
visible: AIP.WaydroidState.errorTitle === "" && AIP.WaydroidState.status == AIP.WaydroidState.NotInitialized
|
||||||
|
}
|
||||||
|
|
||||||
|
WaydroidLoader {
|
||||||
|
visible: AIP.WaydroidState.errorTitle === "" && AIP.WaydroidState.status == AIP.WaydroidState.Initializing
|
||||||
|
text: i18n("Waydroid is initializing.\nIt can take a few minutes.")
|
||||||
|
}
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
visible: AIP.WaydroidState.errorTitle === "" && AIP.WaydroidState.status == AIP.WaydroidState.Initialized && AIP.WaydroidState.sessionStatus == AIP.WaydroidState.SessionStopped
|
||||||
|
anchors.centerIn: parent
|
||||||
|
spacing: Kirigami.Units.largeSpacing
|
||||||
|
|
||||||
|
QQC2.Label {
|
||||||
|
text: i18n("The Waydroid session is not running.")
|
||||||
|
Layout.alignment: Qt.AlignHCenter
|
||||||
|
horizontalAlignment: Text.AlignHCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
PC3.Button {
|
||||||
|
text: i18n("Start the session")
|
||||||
|
Layout.alignment: Qt.AlignHCenter
|
||||||
|
onClicked: AIP.WaydroidState.startSession()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
WaydroidLoader {
|
||||||
|
visible: AIP.WaydroidState.errorTitle === "" && AIP.WaydroidState.status == AIP.WaydroidState.Initialized && AIP.WaydroidState.sessionStatus == AIP.WaydroidState.SessionStarting
|
||||||
|
text: i18n("Waydroid session is starting.\nIt can take a few seconds.")
|
||||||
|
}
|
||||||
|
|
||||||
|
WaydroidConfigurationForm {
|
||||||
|
visible: AIP.WaydroidState.errorTitle === "" && AIP.WaydroidState.status == AIP.WaydroidState.Initialized && AIP.WaydroidState.sessionStatus == AIP.WaydroidState.SessionRunning
|
||||||
|
}
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
visible: AIP.WaydroidState.errorTitle !== ""
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
anchors.left: parent
|
||||||
|
anchors.leftMargin: Kirigami.Units.largeSpacing
|
||||||
|
anchors.right: parent
|
||||||
|
anchors.rightMargin: Kirigami.Units.largeSpacing
|
||||||
|
spacing: Kirigami.Units.largeSpacing
|
||||||
|
|
||||||
|
QQC2.Label {
|
||||||
|
text: AIP.WaydroidState.errorTitle
|
||||||
|
Layout.alignment: Qt.AlignHCenter
|
||||||
|
horizontalAlignment: Text.AlignHCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
QQC2.TextArea {
|
||||||
|
visible: AIP.WaydroidState.errorMessage !== ""
|
||||||
|
text: AIP.WaydroidState.errorMessage
|
||||||
|
readOnly: true
|
||||||
|
wrapMode: TextEdit.Wrap
|
||||||
|
Layout.fillWidth: true
|
||||||
|
}
|
||||||
|
|
||||||
|
PC3.Button {
|
||||||
|
text: i18n("Go back")
|
||||||
|
Layout.alignment: Qt.AlignHCenter
|
||||||
|
onClicked: AIP.WaydroidState.resetError()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
29
kcms/waydroidintegration/waydroidintegration.cpp
Normal file
29
kcms/waydroidintegration/waydroidintegration.cpp
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
/**
|
||||||
|
* SPDX-FileCopyrightText: 2025 Florian RICHER <florian.richer@protonmail.com>
|
||||||
|
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <KPluginFactory>
|
||||||
|
|
||||||
|
#include <KConfigGroup>
|
||||||
|
#include <KQuickManagedConfigModule>
|
||||||
|
#include <KSharedConfig>
|
||||||
|
|
||||||
|
class KCMWaydroidIntegration : public KQuickManagedConfigModule
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
KCMWaydroidIntegration(QObject *parent, const KPluginMetaData &data)
|
||||||
|
: KQuickManagedConfigModule(parent, data)
|
||||||
|
{
|
||||||
|
setButtons({});
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
KSharedConfig::Ptr m_config;
|
||||||
|
};
|
||||||
|
|
||||||
|
K_PLUGIN_CLASS_WITH_JSON(KCMWaydroidIntegration, "kcm_waydroidintegration.json")
|
||||||
|
|
||||||
|
#include "waydroidintegration.moc"
|
||||||
Loading…
Reference in a new issue