2025-07-14 10:47:21 +00:00
/ *
* 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
2026-03-07 03:08:07 +00:00
import org . kde . kirigami as Kirigami
2025-07-14 10:47:21 +00:00
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
2026-04-24 07:35:28 +00:00
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 ( "," )
}
2025-07-14 10:47:21 +00:00
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 {
2025-08-06 20:06:17 +00:00
target: AIP . WaydroidDBusClient
2025-07-14 10:47:21 +00:00
function onActionFinished ( message: string ) : void {
inlineMessage . text = message
inlineMessage . visible = true
inlineMessage . type = Kirigami . MessageType . Positive
}
2025-08-06 20:06:17 +00:00
function onActionFailed ( error: string ) : void {
2025-07-14 10:47:21 +00:00
inlineMessage . text = error
inlineMessage . visible = true
inlineMessage . type = Kirigami . MessageType . Error
}
}
2025-08-06 20:06:17 +00:00
Timer {
id: autoRefreshApplicationsTimer
interval: 2000
repeat: true
running: root . visible
onTriggered: AIP . WaydroidDBusClient . refreshApplications ( )
}
2025-07-14 10:47:21 +00:00
FileDialog {
id: fileDialog
nameFilters: [ "APK files (*.apk)" ]
onAccepted: {
const url = new URL ( selectedFile )
if ( url . protocol !== "file:" ) {
2025-12-27 13:57:22 +00:00
inlineMessage . text = i18n ( "You must select a local file" )
2025-07-14 10:47:21 +00:00
inlineMessage . visible = true
inlineMessage . type = Kirigami . MessageType . Error
} else {
2025-08-06 20:06:17 +00:00
AIP . WaydroidDBusClient . installApk ( url . pathname )
2025-07-14 10:47:21 +00:00
}
}
}
ColumnLayout {
2026-04-24 07:35:28 +00:00
visible: AIP . WaydroidDBusClient . status === AIP . WaydroidDBusClient . Initialized
2025-07-14 10:47:21 +00:00
spacing: Kirigami . Units . largeSpacing
Kirigami . InlineMessage {
id: inlineMessage
Layout.fillWidth: true
visible: false
showCloseButton: true
}
2026-04-24 07:35:28 +00:00
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." )
}
2025-07-14 10:47:21 +00:00
FormCard . FormCard {
Repeater {
2025-08-06 20:06:17 +00:00
model: AIP . WaydroidDBusClient . applicationListModel
2025-07-14 10:47:21 +00:00
delegate: FormCard . AbstractFormDelegate {
id: appDelegate
width: ListView . view . width
background: null
2026-04-24 07:35:28 +00:00
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
}
}
2025-07-14 10:47:21 +00:00
QQC2 . Label {
Layout.fillWidth: true
2026-04-24 07:35:28 +00:00
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." )
2025-07-14 10:47:21 +00:00
}
2026-04-24 07:35:28 +00:00
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
}
2025-07-14 10:47:21 +00:00
2026-04-24 07:35:28 +00:00
QQC2 . CheckBox {
id: fakeWifiToggle
text: i18n ( "Wi-Fi" )
checked: root . hasEffectivePackage ( AIP . WaydroidDBusClient . fakeWifi , model . id )
enabled: ! root . hasWildcardPackage ( AIP . WaydroidDBusClient . fakeWifi , model . id )
2025-07-14 10:47:21 +00:00
2026-04-24 07:35:28 +00:00
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." )
2025-07-14 10:47:21 +00:00
}
}
}
Layout.fillWidth: true
Layout.preferredHeight: contentHeight
}
}
}
2026-04-24 07:35:28 +00:00
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 ( )
}
}
2025-07-14 10:47:21 +00:00
}