mirror of
https://github.com/Droid-Deck/DroidDeck.git
synced 2026-10-14 19:27:09 -04:00
Compare commits
11
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1f3ce2e782 | ||
|
|
54523548cf | ||
|
|
966118e4bd | ||
|
|
444421fcec | ||
|
|
51f4c74d8c | ||
|
|
9a9a7daee8 | ||
|
|
e8cd001229 | ||
|
|
1af57b3ee7 | ||
|
|
4dc1141e5e | ||
|
|
af78a840f0 | ||
|
|
8a859ad6b9 |
No files matched your search
@@ -0,0 +1,61 @@
|
||||
name: Build KDE Plasma package
|
||||
|
||||
# The Plasma shell package (tools/plasma): Arch Linux ARM's KWin and Plasma, closed over their
|
||||
# dependencies less what the runtime and the desktop package already carry, installed over both in
|
||||
# a smoke test on an arm64 runner, and published as a release asset that catalog/shells.json names.
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- 'tools/plasma/**'
|
||||
- '.github/workflows/build-plasma.yml'
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
tag:
|
||||
description: 'Release tag to publish as (e.g. plasma-r1); empty = artifact only'
|
||||
required: false
|
||||
default: ''
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-24.04-arm
|
||||
timeout-minutes: 120
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Build
|
||||
run: bash tools/plasma/build.sh out
|
||||
- name: Smoke test over the runtime and the desktop
|
||||
run: bash tools/plasma/smoke-test.sh out/plasma.tar.zst
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: plasma
|
||||
path: |
|
||||
out/plasma.tar.zst
|
||||
out/plasma.sha256
|
||||
out/plasma.packages.txt
|
||||
- name: Catalog entry
|
||||
run: |
|
||||
set -euo pipefail
|
||||
tag=${{ inputs.tag || 'plasma-rN' }}
|
||||
size=$(stat -c %s out/plasma.tar.zst)
|
||||
sha=$(cat out/plasma.sha256)
|
||||
{
|
||||
echo "### catalog/shells.json entry"
|
||||
echo '```json'
|
||||
printf '{ "id": "plasma", "name": "KDE Plasma", "tier": 1, "version": "%s", "kind": "tar",\n' "${tag#plasma-}"
|
||||
printf ' "url": "https://github.com/%s/releases/download/%s/plasma.tar.zst",\n' "$GITHUB_REPOSITORY" "$tag"
|
||||
printf ' "sha256": "%s", "size": %s,\n' "$sha" "$size"
|
||||
printf ' "notes": "KWin and plasmashell over the desktop package; Dolphin, Konsole, System Settings" }\n'
|
||||
echo '```'
|
||||
} >> "$GITHUB_STEP_SUMMARY"
|
||||
- name: Publish
|
||||
if: github.event_name == 'workflow_dispatch' && inputs.tag != ''
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
cd out
|
||||
gh release create "${{ inputs.tag }}" --title "${{ inputs.tag }}" --notes "KDE Plasma for the desktop: tools/plasma/seeds.txt over Arch Linux ARM, less the runtime's and the desktop package's own packages (Qt and KDE libraries shipped again). Extracts over the desktop package. sha256 $(cat plasma.sha256)" plasma.tar.zst plasma.sha256 plasma.packages.txt \
|
||||
|| gh release upload "${{ inputs.tag }}" plasma.tar.zst plasma.sha256 plasma.packages.txt --clobber
|
||||
@@ -213,6 +213,8 @@ jobs:
|
||||
install -Dm644 tools/linuxfs/desktop/droiddeck-desktop "$D/usr/local/bin/droiddeck-desktop"
|
||||
install -Dm644 tools/linuxfs/desktop/droiddeck-gpu "$D/usr/local/bin/droiddeck-gpu"
|
||||
install -Dm644 tools/linuxfs/desktop/droiddeck-desktop-gpu "$D/usr/local/bin/droiddeck-desktop-gpu"
|
||||
install -Dm644 tools/linuxfs/desktop/droiddeck-plasma "$D/usr/local/bin/droiddeck-plasma"
|
||||
install -Dm644 tools/linuxfs/desktop/droiddeck-plasma-session "$D/usr/local/bin/droiddeck-plasma-session"
|
||||
install -Dm644 tools/linuxfs/desktop/autostart "$D/etc/xdg/labwc/autostart"
|
||||
install -Dm644 tools/linuxfs/desktop/rc.xml "$D/etc/xdg/labwc/rc.xml"
|
||||
install -Dm644 tools/linuxfs/desktop/panel.conf "$D/etc/xdg/lxqt/panel.conf"
|
||||
|
||||
@@ -190,6 +190,7 @@ class MainActivity : ComponentActivity() {
|
||||
private var oscMode by mutableStateOf(SessionPrefs.OSC_AUTO)
|
||||
private var backActionsInverted by mutableStateOf(false)
|
||||
private var renderer by mutableStateOf("vulkan")
|
||||
private var desktopShell by mutableStateOf(SessionPrefs.SHELL_LXQT)
|
||||
private var gameStorage by mutableStateOf("")
|
||||
private var storageOptions by mutableStateOf<List<Pair<String, String>>>(emptyList())
|
||||
private val pickGameStorage = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { r ->
|
||||
@@ -779,6 +780,7 @@ class MainActivity : ComponentActivity() {
|
||||
forceFullscreen = if (mode == SessionService.MODE_STEAM) forceFullscreen else null,
|
||||
mic = if (mode == SessionService.MODE_STEAM) mic else null,
|
||||
renderer = if (mode == SessionService.MODE_DESKTOP) renderer else null,
|
||||
shell = if (mode == SessionService.MODE_DESKTOP) desktopShell else null,
|
||||
gameStorage = if (mode == SessionService.MODE_STEAM) gameStorage else null,
|
||||
storageOptions = storageOptions,
|
||||
fexPreset = if (mode == SessionService.MODE_STEAM) fexPreset else null,
|
||||
@@ -828,6 +830,7 @@ class MainActivity : ComponentActivity() {
|
||||
}
|
||||
},
|
||||
onRenderer = { r -> SessionPrefs.setDesktopRenderer(this, r); renderer = r },
|
||||
onShell = { shell -> SessionPrefs.setDesktopShell(this, shell); desktopShell = shell },
|
||||
onGameStorage = { path, label -> setGameStorage(path, label) },
|
||||
onPickGameStorageFolder = {
|
||||
pickGameStorage.launch(InAppFilePicker.buildDirIntent(this, "Choose the game storage folder", gameStorage.ifEmpty { null }))
|
||||
@@ -932,6 +935,7 @@ class MainActivity : ComponentActivity() {
|
||||
forceFullscreen = SessionPrefs.forceFullscreen(this)
|
||||
mic = SessionPrefs.micEnabled(this)
|
||||
renderer = SessionPrefs.desktopRenderer(this)
|
||||
desktopShell = SessionPrefs.desktopShell(this)
|
||||
gameStorage = SessionPrefs.gameStorage(this)
|
||||
storageOptions = GameStorage.options(this).map { it.label to it.path }
|
||||
if (mode == SessionService.MODE_STEAM) {
|
||||
|
||||
@@ -241,7 +241,10 @@ class SessionActivity : ComponentActivity(), SurfaceHolder.Callback {
|
||||
!com.droiddeck.launcher.runtime.DesktopCatalog.desktopInstalled(this)
|
||||
val needProton = (intent.getStringExtra(SessionService.EXTRA_MODE) ?: SessionService.MODE_STEAM) == SessionService.MODE_STEAM &&
|
||||
com.droiddeck.launcher.runtime.DesktopCatalog.protonSeedNeeded(this)
|
||||
if (needRuntime || needDesktop || needProton) installThenStart(needRuntime, needDesktop, needProton)
|
||||
val needPlasma = intent.getStringExtra(SessionService.EXTRA_MODE) == SessionService.MODE_DESKTOP &&
|
||||
SessionPrefs.desktopShell(this) == SessionPrefs.SHELL_PLASMA &&
|
||||
!com.droiddeck.launcher.runtime.DesktopCatalog.plasmaInstalled(this)
|
||||
if (needRuntime || needDesktop || needProton || needPlasma) installThenStart(needRuntime, needDesktop, needProton, needPlasma)
|
||||
}
|
||||
hud = PerfHud(this)
|
||||
hud.onPresentingWindowChanged = {
|
||||
@@ -562,16 +565,16 @@ class SessionActivity : ComponentActivity(), SurfaceHolder.Callback {
|
||||
* dialog, download or client restart. Not being able to fetch it stops nothing: the session
|
||||
* then asks the client for it after sign-in, as it always has.
|
||||
*/
|
||||
private fun installThenStart(runtime: Boolean, desktop: Boolean, proton: Boolean = false) {
|
||||
private fun installThenStart(runtime: Boolean, desktop: Boolean, proton: Boolean = false, plasma: Boolean = false) {
|
||||
if (SessionState.stopRequested) return
|
||||
installingRuntime = true
|
||||
SessionState.installing = if (runtime) "runtime" else if (desktop) "desktop" else "proton"
|
||||
SessionState.installing = if (runtime) "runtime" else if (desktop || plasma) "desktop" else "proton"
|
||||
SessionEvents.transition(
|
||||
SessionPhase.INSTALLING_RUNTIME,
|
||||
"${SessionState.installing}.installing",
|
||||
mapOf("component" to SessionState.installing),
|
||||
)
|
||||
loading.step = if (runtime) "downloading the Linux runtime" else if (desktop) "downloading the desktop"
|
||||
loading.step = if (runtime) "downloading the Linux runtime" else if (desktop || plasma) "downloading the desktop"
|
||||
else "downloading Proton Experimental (ARM64)"
|
||||
loading.percent = -1
|
||||
Thread({
|
||||
@@ -587,6 +590,14 @@ class SessionActivity : ComponentActivity(), SurfaceHolder.Callback {
|
||||
problem = installDesktop()
|
||||
if (problem == null) SessionEvents.record("desktop.ready") else failedComponent = "desktop"
|
||||
}
|
||||
// KDE Plasma over the desktop. Not having it stops nothing: the desktop starts on LXQt.
|
||||
if (problem == null && plasma && !SessionState.stopRequested) {
|
||||
SessionState.installing = "desktop"
|
||||
SessionEvents.transition(SessionPhase.INSTALLING_RUNTIME, "plasma.installing", mapOf("component" to "plasma"))
|
||||
val plasmaProblem = installPlasma()
|
||||
if (plasmaProblem == null) SessionEvents.record("plasma.ready")
|
||||
else SessionEvents.record("plasma.skipped", mapOf("reason" to plasmaProblem))
|
||||
}
|
||||
if (problem == null && proton && !SessionState.stopRequested) {
|
||||
SessionState.installing = "proton"
|
||||
SessionEvents.transition(SessionPhase.INSTALLING_RUNTIME, "proton.installing", mapOf("component" to "proton"))
|
||||
@@ -660,6 +671,15 @@ class SessionActivity : ComponentActivity(), SurfaceHolder.Callback {
|
||||
return problem?.let { "The desktop did not install ($it). Check the connection and press Desktop again." }
|
||||
}
|
||||
|
||||
/** Null when KDE Plasma is in, else why not; the desktop then starts on LXQt. */
|
||||
private fun installPlasma(): String? {
|
||||
uiHandler.post { loading.percent = -1; loading.step = "downloading the desktop (KDE Plasma)" }
|
||||
val catalog = com.droiddeck.launcher.runtime.DesktopCatalog
|
||||
val entry = (catalog.fetch(catalog.SHELLS_URL) ?: return "catalog unreachable")
|
||||
.firstOrNull { it.id == catalog.PLASMA_ID } ?: return "not published yet"
|
||||
return catalog.install(this, entry, progressFor("the desktop (KDE Plasma)", entry.size / 1_000_000))
|
||||
}
|
||||
|
||||
/** Null when the ARM64 Proton is in, else why not; the session goes on either way. */
|
||||
private fun installProton(): String? {
|
||||
uiHandler.post { loading.percent = -1; loading.step = "downloading Proton Experimental (ARM64)" }
|
||||
|
||||
@@ -52,7 +52,7 @@ object DeviceReport {
|
||||
b.append("DroidDeck session report\n")
|
||||
b.append("========================\n")
|
||||
k("Written", SimpleDateFormat("yyyy-MM-dd HH:mm:ss zzz", Locale.US).format(Date()))
|
||||
k("Session mode", when (mode) { SessionService.MODE_DESKTOP -> "desktop (labwc/LXQt)"; SessionService.MODE_RUN -> "a program under gamescope"; else -> "Steam client (gamescope)" })
|
||||
k("Session mode", when (mode) { SessionService.MODE_DESKTOP -> if (SessionPrefs.desktopShell(context) == SessionPrefs.SHELL_PLASMA) "desktop (KWin/KDE Plasma)" else "desktop (labwc/LXQt)"; SessionService.MODE_RUN -> "a program under gamescope"; else -> "Steam client (gamescope)" })
|
||||
|
||||
h("App")
|
||||
runCatching {
|
||||
@@ -133,6 +133,7 @@ object DeviceReport {
|
||||
k("Installed version", LinuxRuntimeInstaller.installedVersion(context))
|
||||
k("Runtime ready", LinuxRuntime.isInstalled(context))
|
||||
k("Desktop installed", DesktopCatalog.desktopInstalled(context))
|
||||
k("KDE Plasma installed", DesktopCatalog.plasmaInstalled(context))
|
||||
k("Runtime root", LinuxRuntime.rootDir(context).path)
|
||||
|
||||
h("Settings in effect")
|
||||
|
||||
@@ -25,6 +25,12 @@ object DesktopCatalog {
|
||||
*/
|
||||
const val STEAM_SEED_URL = "https://raw.githubusercontent.com/The412Banner/winlator-contents/main/steam-seed.json"
|
||||
const val PROTON_SEED_ID = "proton-arm64"
|
||||
/**
|
||||
* Desktop shells other than LXQt, built in this repo (tools/plasma, build-plasma.yml) and
|
||||
* installed over the desktop package when one is chosen in the Desktop settings.
|
||||
*/
|
||||
const val SHELLS_URL = "https://raw.githubusercontent.com/Droid-Deck/DroidDeck/main/catalog/shells.json"
|
||||
const val PLASMA_ID = "plasma"
|
||||
|
||||
class Entry(
|
||||
val id: String, val name: String, val tier: Int, val version: String, val kind: String,
|
||||
@@ -78,6 +84,10 @@ object DesktopCatalog {
|
||||
fun desktopInstalled(context: Context): Boolean =
|
||||
File(LinuxRuntime.rootDir(context), "usr/bin/labwc").isFile
|
||||
|
||||
/** KDE Plasma's package is in (droiddeck-desktop tests the same file). */
|
||||
fun plasmaInstalled(context: Context): Boolean =
|
||||
File(LinuxRuntime.rootDir(context), "usr/bin/kwin_wayland").isFile
|
||||
|
||||
/** Downloads, verifies and installs one package. Returns null on success, else a message. */
|
||||
fun install(context: Context, entry: Entry, listener: LinuxRuntimeInstaller.ProgressListener?): String? {
|
||||
val root = LinuxRuntime.rootDir(context)
|
||||
|
||||
@@ -65,6 +65,10 @@ object SessionFiles {
|
||||
// Games and emulators from the menu, full screen in a gamescope of their own.
|
||||
"usr/local/bin/droiddeck-gpu" to "usr/local/bin/droiddeck-gpu",
|
||||
"usr/local/bin/droiddeck-desktop-gpu" to "usr/local/bin/droiddeck-desktop-gpu",
|
||||
// KDE Plasma's launcher and the session it runs in KWin; used only when the plasma
|
||||
// package is in and the Desktop settings pick it (droiddeck-desktop checks both).
|
||||
"usr/local/bin/droiddeck-plasma" to "usr/local/bin/droiddeck-plasma",
|
||||
"usr/local/bin/droiddeck-plasma-session" to "usr/local/bin/droiddeck-plasma-session",
|
||||
"etc/xdg/labwc/autostart" to "etc/xdg/labwc/autostart",
|
||||
"etc/xdg/labwc/rc.xml" to "etc/xdg/labwc/rc.xml",
|
||||
"etc/xdg/lxqt/panel.conf" to "etc/xdg/lxqt/panel.conf",
|
||||
|
||||
@@ -419,6 +419,20 @@ object SessionPrefs {
|
||||
java.io.File(com.droiddeck.launcher.runtime.LinuxRuntime.rootDir(context), "root/.droiddeck-renderer-failed").delete()
|
||||
}
|
||||
|
||||
/**
|
||||
* The desktop's shell: SHELL_LXQT (labwc, the desktop package) or SHELL_PLASMA (KWin, the plasma
|
||||
* package over it, fetched at the next Desktop start when it is not in yet).
|
||||
*/
|
||||
fun desktopShell(context: Context): String =
|
||||
prefs(context).getString("desktopShell", SHELL_LXQT)?.takeIf { it == SHELL_PLASMA } ?: SHELL_LXQT
|
||||
|
||||
fun setDesktopShell(context: Context, shell: String) {
|
||||
prefs(context).edit().putString("desktopShell", shell).apply()
|
||||
}
|
||||
|
||||
const val SHELL_LXQT = "lxqt"
|
||||
const val SHELL_PLASMA = "plasma"
|
||||
|
||||
/**
|
||||
* HDR10 output for MODE_STEAM / MODE_DESKTOP. Off by default. Honoured only when the panel
|
||||
* lists HDR10 (HdrSupport), and decided when the compositor starts, which is once per app
|
||||
|
||||
@@ -302,6 +302,8 @@ class SessionService : Service() {
|
||||
val override = File(Environment.getExternalStorageDirectory(), "Download/droiddeck-wlr-renderer")
|
||||
.takeIf { it.isFile }?.let { FileUtils.readString(it)?.trim() }
|
||||
guest.add("BL_WLR_RENDERER=" + (override?.takeIf { it.isNotEmpty() } ?: SessionPrefs.desktopRenderer(this)))
|
||||
// LXQt on labwc or KDE Plasma on KWin; droiddeck-desktop falls back to LXQt without Plasma.
|
||||
guest.add("BL_DESKTOP_SHELL=" + SessionPrefs.desktopShell(this))
|
||||
}
|
||||
// The user's own games, for the runtime's shortcuts writer to put in the client's library
|
||||
// before the client starts (see frontend/AddedGames and bannerlator-steam-shortcuts).
|
||||
|
||||
@@ -61,6 +61,8 @@ class ModeSettings(
|
||||
val clientDirectAudio: Boolean = false,
|
||||
val mic: Boolean?,
|
||||
val renderer: String?,
|
||||
/** Desktop only: SessionPrefs.SHELL_LXQT or SHELL_PLASMA. */
|
||||
val shell: String? = null,
|
||||
val gameStorage: String? = null,
|
||||
val storageOptions: List<Pair<String, String>> = emptyList(),
|
||||
val fexPreset: String? = null,
|
||||
@@ -118,6 +120,7 @@ class ModeSettingsActions(
|
||||
val onClientDirectAudio: (Boolean) -> Unit = {},
|
||||
val onMic: (Boolean) -> Unit,
|
||||
val onRenderer: (String) -> Unit,
|
||||
val onShell: (String) -> Unit = {},
|
||||
val onGameStorage: (path: String, label: String) -> Unit = { _, _ -> },
|
||||
val onPickGameStorageFolder: () -> Unit = {},
|
||||
val onFexPreset: (String) -> Unit = {},
|
||||
@@ -423,7 +426,17 @@ fun ModeSettingsPage(s: ModeSettings, a: ModeSettingsActions) {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!steam && s.renderer != null) SettingsGroup("Renderer") {
|
||||
if (!steam && s.shell != null) SettingsGroup("Shell") {
|
||||
ChoiceRow(
|
||||
host, "shell", "Desktop shell", "Applies at the next start.",
|
||||
listOf(SessionPrefs.SHELL_LXQT to "LXQt", SessionPrefs.SHELL_PLASMA to "KDE Plasma"), s.shell,
|
||||
note = "KDE Plasma downloads the first time it starts and composites in software, so games " +
|
||||
"and emulators from its menu open full screen on the GPU. If it cannot start, the desktop comes up on LXQt.",
|
||||
onPick = a.onShell,
|
||||
)
|
||||
}
|
||||
// labwc's renderer; KDE Plasma's KWin always composites in software here.
|
||||
if (!steam && s.renderer != null && s.shell != SessionPrefs.SHELL_PLASMA) SettingsGroup("Renderer") {
|
||||
ChoiceRow(
|
||||
host, "renderer", "Desktop renderer", "Composites the desktop.",
|
||||
listOf("vulkan" to "vulkan - GPU", "gles2" to "gles2 - GPU (experimental)", "pixman" to "pixman - software"), s.renderer,
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"category": "droiddeck-shells",
|
||||
"note": "Desktop shells the app installs over the desktop package when one is chosen in the Desktop settings. Built by .github/workflows/build-plasma.yml, whose run summary prints the entry to paste here.",
|
||||
"packages": []
|
||||
}
|
||||
@@ -116,6 +116,8 @@ docker run --rm --platform linux/amd64 \
|
||||
install -Dm644 tools/linuxfs/desktop/droiddeck-desktop "$d/usr/local/bin/droiddeck-desktop"
|
||||
install -Dm644 tools/linuxfs/desktop/droiddeck-gpu "$d/usr/local/bin/droiddeck-gpu"
|
||||
install -Dm644 tools/linuxfs/desktop/droiddeck-desktop-gpu "$d/usr/local/bin/droiddeck-desktop-gpu"
|
||||
install -Dm644 tools/linuxfs/desktop/droiddeck-plasma "$d/usr/local/bin/droiddeck-plasma"
|
||||
install -Dm644 tools/linuxfs/desktop/droiddeck-plasma-session "$d/usr/local/bin/droiddeck-plasma-session"
|
||||
install -Dm644 tools/linuxfs/desktop/autostart "$d/etc/xdg/labwc/autostart"
|
||||
install -Dm644 tools/linuxfs/desktop/rc.xml "$d/etc/xdg/labwc/rc.xml"
|
||||
install -Dm644 tools/linuxfs/desktop/panel.conf "$d/etc/xdg/lxqt/panel.conf"
|
||||
|
||||
@@ -130,6 +130,17 @@ python3 /usr/local/bin/bannerlator-pad-defaults 2>&1 | sed 's/^/[pads] /'
|
||||
gpu_entries() { python3 /usr/local/bin/droiddeck-desktop-gpu 2>&1 | sed 's/^/[gpu] /'; }
|
||||
gpu_entries
|
||||
|
||||
# KDE Plasma picked in the Desktop settings (BL_DESKTOP_SHELL): KWin takes labwc's place. It
|
||||
# composites in software, so the menu's entries go full screen on the GPU as they do on pixman.
|
||||
if [ "${BL_DESKTOP_SHELL:-lxqt}" = plasma ]; then
|
||||
if [ -x /usr/bin/kwin_wayland ] && [ -x /usr/local/bin/droiddeck-plasma ]; then
|
||||
use_renderer pixman
|
||||
gpu_entries
|
||||
exec /usr/local/bin/droiddeck-plasma
|
||||
fi
|
||||
echo "== droiddeck-desktop: KDE Plasma is not installed; starting LXQt"
|
||||
fi
|
||||
|
||||
# ~/.droiddeck-desktop-debug: labwc's own debug log (views and layer surfaces mapping) too. The GPU
|
||||
# renderers are experimental and always log in full, so a desktop that does not come up says why.
|
||||
DEBUG=; { [ -f "$HOME/.droiddeck-desktop-debug" ] || [ "$WLR_RENDERER" != pixman ]; } && DEBUG=-d
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
#!/bin/bash
|
||||
# KDE Plasma as the desktop: KWin nested as a client of the app's compositor (its Wayland backend,
|
||||
# chosen because WAYLAND_DISPLAY names the app's socket), with Plasma's session on it and Xwayland
|
||||
# for the X11 programs. Started by droiddeck-desktop when the Desktop settings pick KDE Plasma and
|
||||
# the plasma package is in; what droiddeck-desktop prepares (the log, fonts, the menu's game and
|
||||
# emulator entries, controller defaults) is done by then. If KWin does not come up, the desktop
|
||||
# starts on LXQt instead and the log says why.
|
||||
# Staged by the app on every launch, like droiddeck-desktop.
|
||||
set -u
|
||||
export XDG_SESSION_TYPE=wayland XDG_CURRENT_DESKTOP=KDE XDG_SESSION_DESKTOP=KDE
|
||||
export KDE_FULL_SESSION=true KDE_SESSION_VERSION=6
|
||||
# Plasma's own platform theme (plasma-integration), not LXQt's.
|
||||
unset QT_QPA_PLATFORMTHEME WLR_RENDERER WLR_BACKENDS
|
||||
export QT_QPA_PLATFORM="wayland;xcb"
|
||||
# KWin composites in software (QPainter): its OpenGL path allocates through gbm on a DRM render node,
|
||||
# which the KGSL stand-in is not - the same wall stock wlroots hit. QPainter hands programs shm
|
||||
# buffers only, so games and emulators from the menu open full screen on the GPU through
|
||||
# droiddeck-gpu, as on LXQt's pixman. BL_KWIN_COMPOSE (O2, Q) overrides for experiments.
|
||||
export KWIN_COMPOSE=${BL_KWIN_COMPOSE:-Q}
|
||||
echo "== droiddeck-plasma: KWin ($KWIN_COMPOSE) nested on $WAYLAND_DISPLAY at ${BL_WIDTH:-1280}x${BL_HEIGHT:-720}"
|
||||
|
||||
# Settings Plasma reads at start, written every session: no splash, no systemd boot (there is no
|
||||
# systemd user instance in proot), no screen locker (it needs PAM and a greeter), no wallet
|
||||
# prompts, no file indexer eating the cores a game needs.
|
||||
kset() { kwriteconfig6 --file "$1" --group "$2" --key "$3" "$4" 2>/dev/null; }
|
||||
kset ksplashrc KSplash Engine none
|
||||
kset ksplashrc KSplash Theme None
|
||||
kset startkderc General systemdBoot false
|
||||
kset kscreenlockerrc Daemon Autolock false
|
||||
kset kscreenlockerrc Daemon LockOnResume false
|
||||
kset kwalletrc Wallet Enabled false
|
||||
kset baloofilerc "Basic Settings" "Indexing-Enabled" false
|
||||
# The nested output is the app's surface: no scaling, the app already sized it.
|
||||
kset kwinrc Xwayland Scale 1
|
||||
|
||||
SOCKET=wayland-1
|
||||
rm -f "$XDG_RUNTIME_DIR/$SOCKET" "$XDG_RUNTIME_DIR/$SOCKET.lock"
|
||||
run_kwin() {
|
||||
local args=(--socket "$SOCKET" --width "${BL_WIDTH:-1280}" --height "${BL_HEIGHT:-720}" --scale 1
|
||||
--xwayland --no-lockscreen --exit-with-session /usr/local/bin/droiddeck-plasma-session)
|
||||
# A session bus: kded, plasmashell and the portal all live on it.
|
||||
if command -v dbus-run-session >/dev/null 2>&1; then
|
||||
dbus-run-session -- kwin_wayland "${args[@]}"
|
||||
else
|
||||
kwin_wayland "${args[@]}"
|
||||
fi
|
||||
}
|
||||
kwin_answers() { WAYLAND_DISPLAY=$SOCKET timeout 3 wayland-info >/dev/null 2>&1; }
|
||||
pids_named() {
|
||||
local p
|
||||
for p in /proc/[0-9]*; do
|
||||
[ "$(cat "$p/comm" 2>/dev/null)" = "$1" ] && echo "${p#/proc/}"
|
||||
done
|
||||
}
|
||||
fall_back() {
|
||||
echo "== droiddeck-plasma: $1; starting LXQt instead"
|
||||
for prog in plasmashell kded6 ksmserver kwin_wayland Xwayland; do kill -KILL $(pids_named "$prog") 2>/dev/null; done
|
||||
find "$XDG_RUNTIME_DIR" -mindepth 1 -maxdepth 1 ! -name 'wayland-0*' ! -name 'keymap.xkb' -exec rm -rf {} + 2>/dev/null
|
||||
BL_DESKTOP_SHELL=lxqt exec /usr/local/bin/droiddeck-desktop
|
||||
}
|
||||
|
||||
started=$(date +%s)
|
||||
run_kwin &
|
||||
kwin_job=$!
|
||||
answered=
|
||||
while kill -0 "$kwin_job" 2>/dev/null; do
|
||||
if kwin_answers; then answered=1; break; fi
|
||||
[ $(( $(date +%s) - started )) -ge 30 ] && break
|
||||
sleep 1
|
||||
done
|
||||
if [ -z "$answered" ]; then
|
||||
if kill -0 "$kwin_job" 2>/dev/null; then
|
||||
kill -TERM $(pids_named kwin_wayland) 2>/dev/null
|
||||
sleep 2
|
||||
fall_back "KWin did not answer in 30 s"
|
||||
fi
|
||||
wait "$kwin_job"
|
||||
fall_back "KWin exited at start (exit $?)"
|
||||
fi
|
||||
echo "== droiddeck-plasma: KWin is up after $(( $(date +%s) - started )) s"
|
||||
wait "$kwin_job"
|
||||
@@ -0,0 +1,25 @@
|
||||
#!/bin/bash
|
||||
# What runs inside KWin for the Plasma desktop (droiddeck-plasma's --exit-with-session): the Steam
|
||||
# client if the desktop was asked for with Steam in it, then Plasma's own session. KWin, and with it
|
||||
# the desktop, ends when this does.
|
||||
# The desktop was asked for with Steam in it (the front end's "Steam Desktop UI", Big Picture's
|
||||
# "Switch to Desktop"): the client's desktop UI, as LXQt's autostart opens it.
|
||||
if [ "${BL_DESKTOP_STEAM:-0}" = 1 ]; then
|
||||
/usr/local/bin/bannerlator-steam-launch desktop 2>&1 | sed 's/^/[steam] /' &
|
||||
fi
|
||||
# The desktop's wallpaper once, on the first Plasma start; after that it is the user's to change.
|
||||
if [ ! -f "$HOME/.config/.droiddeck-plasma-wallpaper" ] && [ -f /usr/share/backgrounds/steamdeck.png ]; then
|
||||
( set -o pipefail; sleep 8
|
||||
plasma-apply-wallpaperimage /usr/share/backgrounds/steamdeck.png 2>&1 | sed 's/^/[wallpaper] /' \
|
||||
&& touch "$HOME/.config/.droiddeck-plasma-wallpaper" ) &
|
||||
fi
|
||||
for session in /usr/lib/startplasma-waylandsession /usr/libexec/startplasma-waylandsession; do
|
||||
if [ -x "$session" ]; then
|
||||
echo "== droiddeck-plasma: $session"
|
||||
"$session" 2>&1 | sed 's/^/[plasma] /'
|
||||
exit "${PIPESTATUS[0]}"
|
||||
fi
|
||||
done
|
||||
echo "== droiddeck-plasma: no startplasma-waylandsession; plasmashell on its own"
|
||||
plasmashell 2>&1 | sed 's/^/[plasmashell] /'
|
||||
exit "${PIPESTATUS[0]}"
|
||||
Executable
+50
@@ -0,0 +1,50 @@
|
||||
#!/bin/bash
|
||||
# Builds the KDE Plasma shell package: the closure of seeds.txt over Arch Linux ARM, as a zstd
|
||||
# tarball that extracts over the rootfs, with the desktop package (LXQt) already in it.
|
||||
#
|
||||
# The closure and extraction are winlator-contents' own desktop/build-pkg.sh, pinned below, so this
|
||||
# package is made exactly like the desktop one. What it leaves out is the runtime's packages (as the
|
||||
# desktop does) plus the desktop package's - except Qt and the KDE libraries, which are shipped
|
||||
# again: KWin and plasmashell use Qt's private API and must match the Qt they were built against.
|
||||
#
|
||||
# tools/plasma/build.sh <out dir>
|
||||
#
|
||||
# Needs curl, git, tar, zstd, python3.
|
||||
set -euo pipefail
|
||||
out=${1:?out dir}
|
||||
here=$(cd "$(dirname "$0")" && pwd)
|
||||
. "$here/release.env"
|
||||
work=$(mktemp -d)
|
||||
mkdir -p "$out"
|
||||
|
||||
git init -q "$work/contents"
|
||||
git -C "$work/contents" fetch -q --depth 1 https://github.com/The412Banner/winlator-contents.git "$CONTENTS_REF"
|
||||
git -C "$work/contents" checkout -q FETCH_HEAD
|
||||
|
||||
curl -fsSL --retry 6 --retry-delay 5 --retry-all-errors -o "$work/desktop.packages.txt" "$DESKTOP_PACKAGES_URL"
|
||||
|
||||
# Package names from the desktop's file list (name-pkgver-pkgrel-arch.pkg.tar.xz); the ones Plasma
|
||||
# must bring in its own build are not counted as present.
|
||||
python3 - "$work/desktop.packages.txt" > "$work/desktop-names.txt" <<'PY'
|
||||
import re, sys
|
||||
keep = re.compile(r"^(qt6-|layer-shell-qt|kf6-|kwayland|plasma|breeze|kde|libplasma|kirigami|kpipewire|kscreenlocker|kglobalacc)")
|
||||
for line in open(sys.argv[1]):
|
||||
line = line.strip()
|
||||
if not line: continue
|
||||
name = line.rsplit("-", 3)[0]
|
||||
if not keep.match(name): print(name)
|
||||
PY
|
||||
cat "$work/contents/desktop/base-packages.txt" "$work/desktop-names.txt" | sort -u > "$work/present.txt"
|
||||
echo "== plasma: $(wc -l < "$work/present.txt") packages counted as present (runtime + desktop, less Qt/KDE)"
|
||||
|
||||
bash "$work/contents/desktop/build-pkg.sh" plasma "$here/seeds.txt" "$work/present.txt" "$out"
|
||||
|
||||
# What this package replaces from the desktop's: printed so a Qt bump is never a surprise.
|
||||
python3 - "$work/desktop.packages.txt" "$out/plasma.packages.txt" <<'PY'
|
||||
import sys
|
||||
def names(p): return {l.strip().rsplit("-", 3)[0]: l.strip() for l in open(p) if l.strip()}
|
||||
d, p = names(sys.argv[1]), names(sys.argv[2])
|
||||
for n in sorted(set(d) & set(p)):
|
||||
if d[n] != p[n]: print("== replaces", d[n], "->", p[n])
|
||||
PY
|
||||
rm -rf "$work"
|
||||
@@ -0,0 +1,9 @@
|
||||
# What the Plasma package is built on and tested against.
|
||||
# winlator-contents at the commit whose desktop/build-pkg.sh and base-packages.txt are used.
|
||||
CONTENTS_REF=843e9c7dcdf52260993ee9f23c33a2e17acb5e20
|
||||
DESKTOP_PACKAGES_URL=https://github.com/The412Banner/winlator-contents/releases/download/steamdeck-desktop-r1/desktop.packages.txt
|
||||
# The runtime and desktop package the smoke test installs it over (linuxfs.json, desktop.json).
|
||||
RUNTIME_URL=https://github.com/The412Banner/winlator-contents/releases/download/linuxfs-r9/linuxfs.tar.zst
|
||||
RUNTIME_SHA256=d3240c1531531615b0dd06b6ad56e92db9015a8641eb18c3a613835ae45992e7
|
||||
DESKTOP_URL=https://github.com/The412Banner/winlator-contents/releases/download/steamdeck-desktop-r1/desktop.tar.zst
|
||||
DESKTOP_SHA256=c2bb860b3b1688287451e2495be5db313ec07f8c8af501dfac2768a9f417bc86
|
||||
@@ -0,0 +1,15 @@
|
||||
# KDE Plasma as a second desktop shell: KWin nested as a client of the app's compositor, with
|
||||
# plasmashell's panel and desktop on it. Installed over the desktop package (LXQt), which keeps
|
||||
# Xwayland, Firefox, mpv and the launchers; this only adds the shell and its everyday apps.
|
||||
# One package name per line; the closure over Arch Linux ARM is taken at build time.
|
||||
kwin
|
||||
plasma-workspace
|
||||
plasma-desktop
|
||||
plasma-pa
|
||||
breeze
|
||||
breeze-icons
|
||||
kde-cli-tools
|
||||
xdg-desktop-portal-kde
|
||||
systemsettings
|
||||
dolphin
|
||||
konsole
|
||||
Executable
+79
@@ -0,0 +1,79 @@
|
||||
#!/bin/bash
|
||||
# Installs the Plasma package the way the app does - runtime, then the desktop package, then this
|
||||
# one, each extracted over the last - and checks that its programs load in that rootfs: every
|
||||
# library they link resolves (with the runtime's glibc), and KWin and plasmashell start far enough
|
||||
# to print their versions. Runs on an arm64 host; needs sudo for chroot.
|
||||
#
|
||||
# tools/plasma/smoke-test.sh <plasma.tar.zst>
|
||||
set -euo pipefail
|
||||
# On Actions, failures also go out as annotations, which can be read back without the log.
|
||||
annotate() { [ -n "${GITHUB_ACTIONS:-}" ] && echo "::error title=plasma smoke test::$*"; echo "$*"; }
|
||||
notice() { [ -n "${GITHUB_ACTIONS:-}" ] && echo "::notice title=plasma smoke test::$*"; echo "$*"; }
|
||||
trap 'annotate "stopped at line $LINENO: $BASH_COMMAND"' ERR
|
||||
pkg=$(realpath "${1:?plasma.tar.zst}")
|
||||
here=$(cd "$(dirname "$0")" && pwd)
|
||||
. "$here/release.env"
|
||||
work=$(mktemp -d)
|
||||
root=$work/root
|
||||
mkdir -p "$root"
|
||||
|
||||
fetch() { # url sha256 out
|
||||
curl -fsSL --retry 6 --retry-delay 5 --retry-all-errors -o "$3" "$1"
|
||||
echo "$2 $3" | sha256sum -c -
|
||||
}
|
||||
fetch "$RUNTIME_URL" "$RUNTIME_SHA256" "$work/runtime.tar.zst"
|
||||
fetch "$DESKTOP_URL" "$DESKTOP_SHA256" "$work/desktop.tar.zst"
|
||||
for t in "$work/runtime.tar.zst" "$work/desktop.tar.zst" "$pkg"; do
|
||||
sudo tar --zstd -xf "$t" -C "$root"
|
||||
done
|
||||
rm -f "$work/runtime.tar.zst" "$work/desktop.tar.zst"
|
||||
|
||||
fail=0
|
||||
for bin in usr/bin/kwin_wayland usr/bin/plasmashell usr/bin/dolphin usr/bin/konsole usr/bin/systemsettings; do
|
||||
[ -e "$root/$bin" ] || { annotate "MISSING /$bin"; fail=1; }
|
||||
done
|
||||
ls "$root"/usr/lib/startplasma-waylandsession "$root"/usr/lib*/libexec/startplasma-waylandsession 2>/dev/null \
|
||||
|| echo "note: no startplasma-waylandsession; the launcher starts plasmashell itself"
|
||||
|
||||
# Every ELF the package added, checked with the rootfs's own loader.
|
||||
tar --zstd -tf "$pkg" | sed -n 's#^\./##; /^usr\/\(bin\|lib\)\/.*[^/]$/p' > "$work/files.txt"
|
||||
: > "$work/unresolved.txt"
|
||||
while read -r f; do
|
||||
[ -f "$root/$f" ] && [ ! -L "$root/$f" ] || continue
|
||||
head -c 4 "$root/$f" | grep -q $'\x7fELF' || continue
|
||||
sudo chroot "$root" /usr/bin/ldd "/$f" 2>&1 | grep -E "not found" | sed "s#^#/$f: #" >> "$work/unresolved.txt" || true
|
||||
done < "$work/files.txt"
|
||||
# What the desktop starts must resolve completely (ldd follows every library they load). Other
|
||||
# helpers and plugins that need an optional dependency the package does not bring (Python
|
||||
# bindings, PackageKit, input methods...) only fail to load, as on any Arch install without those
|
||||
# extras; they are counted, not fatal.
|
||||
core='^/usr/(bin/(kwin_wayland|kwin_wayland_wrapper|plasmashell|ksmserver|kded6|krunner|dolphin|konsole|systemsettings|plasma_session)|lib/(startplasma-waylandsession|xdg-desktop-portal-kde|kactivitymanagerd|polkit-kde-authentication-agent-1|DiscoverNotifier)):'
|
||||
if [ -s "$work/unresolved.txt" ]; then
|
||||
echo "== unresolved libraries or symbol versions:"
|
||||
sort -u "$work/unresolved.txt" | head -80
|
||||
if grep -qE "$core" "$work/unresolved.txt"; then
|
||||
annotate "unresolved in the desktop's own programs: $(grep -E "$core" "$work/unresolved.txt" | sort -u | head -12 | tr -s ' \t' ' ' | tr '\n' ';')"
|
||||
fail=1
|
||||
fi
|
||||
notice "optional helpers and plugins missing a dependency: $(grep -vE "$core" "$work/unresolved.txt" | cut -d: -f1 | sort -u | wc -l)"
|
||||
else
|
||||
echo "== every library in the package resolves in the runtime"
|
||||
fi
|
||||
|
||||
# KWin must start far enough to answer. plasmashell sets up its scene before it reads its arguments
|
||||
# and cannot without a compositor, so it is held to its libraries (above) and its answer only noted.
|
||||
# A fresh proc (never a bind of the host's /dev or /proc: the rootfs is deleted at the end).
|
||||
sudo mount -t proc proc "$root/proc"
|
||||
sudo mkdir -p "$root/tmp/xdg" && sudo chmod 700 "$root/tmp/xdg"
|
||||
for prog in kwin_wayland plasmashell; do
|
||||
rc=0
|
||||
out=$(sudo chroot "$root" /usr/bin/env -i PATH=/usr/bin HOME=/root XDG_RUNTIME_DIR=/tmp/xdg QT_QPA_PLATFORM=offscreen "/usr/bin/$prog" --version 2>&1) || rc=$?
|
||||
echo "$prog --version (exit $rc): $out"
|
||||
if [ "$rc" -ne 0 ]; then
|
||||
if [ "$prog" = kwin_wayland ]; then annotate "FAILED: $prog --version exit $rc: $(echo "$out" | tail -3 | tr '\n' ' ')"; fail=1
|
||||
else notice "$prog --version exit $rc: $(echo "$out" | tail -3 | tr '\n' ' ')"; fi
|
||||
fi
|
||||
done
|
||||
sudo umount "$root/proc"
|
||||
mountpoint -q "$root/proc" || sudo rm -rf "$work"
|
||||
exit $fail
|
||||
Reference in new issue
Block a user