shift-shell/kcms/waydroidintegration/ui/WaydroidApplicationsPage.qml
Marco Allegretti a8e76002f4 Improve Waydroid app management
Handle unavailable Waydroid sessions more cleanly and add KCM controls for launching apps, Game Shell visibility, and compatibility overrides.
2026-04-24 09:35:28 +02:00

269 lines
9.9 KiB
QML

/*
* SPDX-FileCopyrightText: 2025 Florian RICHER <florian.richer@protonmail.com>
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQuick
import QtQuick.Layouts
import QtQuick.Controls as QQC2
import QtQuick.Dialogs
import org.kde.kirigami 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
function packagePatterns(value: string): var {
if (value === "") {
return []
}
return value.split(",")
.map(pattern => pattern.trim())
.filter(pattern => pattern !== "")
}
function wildcardRegExp(pattern: string): RegExp {
const escaped = pattern.replace(/[.+?^${}()|[\]\\]/g, "\\$&")
return new RegExp("^" + escaped.replace(/\*/g, ".*") + "$")
}
function hasExactPackage(value: string, packageName: string): bool {
return packagePatterns(value).includes(packageName)
}
function hasWildcardPackage(value: string, packageName: string): bool {
return packagePatterns(value)
.filter(pattern => pattern.includes("*"))
.some(pattern => wildcardRegExp(pattern).test(packageName))
}
function hasEffectivePackage(value: string, packageName: string): bool {
return hasExactPackage(value, packageName) || hasWildcardPackage(value, packageName)
}
function updateExactPackage(value: string, packageName: string, enabled: bool): string {
const updatedPatterns = packagePatterns(value).filter(pattern => pattern !== packageName)
if (enabled) {
updatedPatterns.push(packageName)
}
return updatedPatterns.join(",")
}
topPadding: Kirigami.Units.largeSpacing
bottomPadding: Kirigami.Units.largeSpacing
leftPadding: 0
rightPadding: 0
title: i18n("Waydroid applications")
actions: [
Kirigami.Action {
text: i18nc("@action:button", "Install APK")
icon.name: "list-add"
onTriggered: fileDialog.open()
}
]
Connections {
target: AIP.WaydroidDBusClient
function onActionFinished(message: string): void {
inlineMessage.text = message
inlineMessage.visible = true
inlineMessage.type = Kirigami.MessageType.Positive
}
function onActionFailed(error: string): void {
inlineMessage.text = error
inlineMessage.visible = true
inlineMessage.type = Kirigami.MessageType.Error
}
}
Timer {
id: autoRefreshApplicationsTimer
interval: 2000
repeat: true
running: root.visible
onTriggered: AIP.WaydroidDBusClient.refreshApplications()
}
FileDialog {
id: fileDialog
nameFilters: [ "APK files (*.apk)" ]
onAccepted: {
const url = new URL(selectedFile)
if (url.protocol !== "file:") {
inlineMessage.text = i18n("You must select a local file")
inlineMessage.visible = true
inlineMessage.type = Kirigami.MessageType.Error
} else {
AIP.WaydroidDBusClient.installApk(url.pathname)
}
}
}
ColumnLayout {
visible: AIP.WaydroidDBusClient.status === AIP.WaydroidDBusClient.Initialized
spacing: Kirigami.Units.largeSpacing
Kirigami.InlineMessage {
id: inlineMessage
Layout.fillWidth: true
visible: false
showCloseButton: true
}
Kirigami.PlaceholderMessage {
Layout.fillWidth: true
explanation: i18n("This page manages the launchers exported by Waydroid. Enable Show in Game Shell for Android apps you want listed in Game Center's Waydroid tab. Touch and Wi-Fi toggles add or remove exact package names from Waydroid's documented compatibility property lists. Wildcard rules remain in the main Waydroid properties page.")
}
FormCard.FormCard {
Repeater {
model: AIP.WaydroidDBusClient.applicationListModel
delegate: FormCard.AbstractFormDelegate {
id: appDelegate
width: ListView.view.width
background: null
contentItem: ColumnLayout {
spacing: Kirigami.Units.smallSpacing
RowLayout {
Layout.fillWidth: true
QQC2.Label {
Layout.fillWidth: true
text: model.name
elide: Text.ElideRight
}
QQC2.ToolButton {
display: QQC2.AbstractButton.IconOnly
text: i18nc("@action:button", "Launch the application")
icon.name: "media-playback-start"
onClicked: AIP.WaydroidDBusClient.launchApplication(model.id)
QQC2.ToolTip.visible: hovered
QQC2.ToolTip.text: text
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
}
QQC2.ToolButton {
display: QQC2.AbstractButton.IconOnly
text: i18nc("@action:button", "Delete the application")
icon.name: "usermenu-delete"
onClicked: AIP.WaydroidDBusClient.deleteApplication(model.id)
QQC2.ToolTip.visible: hovered
QQC2.ToolTip.text: text
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
}
}
RowLayout {
Layout.fillWidth: true
QQC2.CheckBox {
text: i18n("Show in Game Shell")
checked: AIP.WaydroidDBusClient.gameShellPackages.indexOf(model.id) !== -1
onClicked: AIP.WaydroidDBusClient.setGameShellEnabledForPackage(model.id, checked)
}
Item {
Layout.fillWidth: true
}
}
QQC2.Label {
Layout.fillWidth: true
wrapMode: Text.WordWrap
color: Kirigami.Theme.disabledTextColor
text: i18n("Adds this app to the Game Shell allowlist so it appears in Game Center under Waydroid.")
}
RowLayout {
Layout.fillWidth: true
QQC2.CheckBox {
id: fakeTouchToggle
text: i18n("Touch")
checked: root.hasEffectivePackage(AIP.WaydroidDBusClient.fakeTouch, model.id)
enabled: !root.hasWildcardPackage(AIP.WaydroidDBusClient.fakeTouch, model.id)
onClicked: {
AIP.WaydroidDBusClient.fakeTouch = root.updateExactPackage(AIP.WaydroidDBusClient.fakeTouch, model.id, checked)
}
QQC2.ToolTip.visible: hovered && !enabled
QQC2.ToolTip.text: i18n("Managed by a wildcard pattern in Waydroid properties")
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
}
QQC2.CheckBox {
id: fakeWifiToggle
text: i18n("Wi-Fi")
checked: root.hasEffectivePackage(AIP.WaydroidDBusClient.fakeWifi, model.id)
enabled: !root.hasWildcardPackage(AIP.WaydroidDBusClient.fakeWifi, model.id)
onClicked: {
AIP.WaydroidDBusClient.fakeWifi = root.updateExactPackage(AIP.WaydroidDBusClient.fakeWifi, model.id, checked)
}
QQC2.ToolTip.visible: hovered && !enabled
QQC2.ToolTip.text: i18n("Managed by a wildcard pattern in Waydroid properties")
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
}
}
QQC2.Label {
Layout.fillWidth: true
visible: !fakeTouchToggle.enabled || !fakeWifiToggle.enabled
wrapMode: Text.WordWrap
color: Kirigami.Theme.disabledTextColor
text: i18n("One or more compatibility settings for this app come from a wildcard rule. Edit the global Waydroid property to change that rule.")
}
}
}
Layout.fillWidth: true
Layout.preferredHeight: contentHeight
}
}
}
ColumnLayout {
visible: AIP.WaydroidDBusClient.status !== AIP.WaydroidDBusClient.Initialized
anchors.centerIn: parent
spacing: Kirigami.Units.largeSpacing
QQC2.Label {
text: i18n("Waydroid is unavailable")
Layout.alignment: Qt.AlignHCenter
horizontalAlignment: Text.AlignHCenter
}
PC3.Button {
text: i18n("Check again")
Layout.alignment: Qt.AlignHCenter
onClicked: AIP.WaydroidDBusClient.refreshSupportsInfo()
}
}
}