Play installs the Linux runtime on a fresh install, on the loading screen

Play and Desktop UI are enabled without a runtime. The session's loading screen downloads and
unpacks it first, reporting on its usual step line and bar ("downloading the Linux runtime · 332
of 791 MB", then checking, then unpacking), and starts the session when it is in. A failure ends
on the loading screen with the reason. Non-Adreno devices still get the warning before any
download. Setup > Linux runtime is unchanged.
This commit is contained in:
The412Banner committed 2026-09-23 12:48:49 -04:00
1 parent 1e1160006c
commit 2b5aae9100
3 files changed
+51 -4

No files matched your search

@@ -203,7 +203,12 @@ class MainActivity : ComponentActivity() {
theme = theme,
),
FrontEndActions(
onPlay = { startActivity(Intent(this, SessionActivity::class.java)) },
onPlay = {
// No runtime and not an Adreno: the same warning Setup gives, before any download.
val warn = installed == null && !com.steamdeck.launcher.core.DeviceSupport.adreno()
if (warn && available != null) showNonAdreno = available
else startActivity(Intent(this, SessionActivity::class.java))
},
onPlayDesktopUi = {
startActivity(Intent(this, SessionActivity::class.java)
.putExtra(SessionService.EXTRA_STEAM_UI, "desktop"))
@@ -140,6 +140,7 @@ class SessionActivity : ComponentActivity(), SurfaceHolder.Callback {
surfaceView.pointerIcon = noCursor
loading = LoadingState(this)
if (!SessionState.running && com.steamdeck.launcher.runtime.LinuxRuntimeInstaller.installedVersion(this) == null) installRuntimeThenStart()
hud = PerfHud(this)
hud.onPresentingWindowChanged = {
if (FrameGen.engine(this) != FrameGen.ENGINE_OFF) CompositorHost.rearmFrameGen { applyFrameGen() }
@@ -254,7 +255,47 @@ class SessionActivity : ComponentActivity(), SurfaceHolder.Callback {
// ── Compositor ──────────────────────────────────────────────────────────────────────────
/** True while the loading screen is installing the Linux runtime; the session waits for it. */
@Volatile private var installingRuntime = false
/**
* First Play on a fresh install: the runtime is downloaded and unpacked here, on the loading
* screen's own line and bar, and the session starts when it is in. Nothing else changes.
*/
private fun installRuntimeThenStart() {
installingRuntime = true
loading.step = "downloading the Linux runtime"
loading.percent = -1
Thread({
val release = com.steamdeck.launcher.runtime.LinuxRuntimeInstaller.fetchRelease()
val mb = release?.let { it.size / 1_000_000 } ?: 0L
val ok = release != null && com.steamdeck.launcher.runtime.LinuxRuntimeInstaller.install(this, release) { stage, p ->
uiHandler.post {
loading.percent = p
loading.step = when (stage) {
"Downloading" -> if (p >= 0 && mb > 0) "downloading the Linux runtime · ${p * mb / 100} of $mb MB" else "downloading the Linux runtime"
"Verifying" -> "checking the Linux runtime"
else -> "unpacking the Linux runtime"
}
}
}
uiHandler.post {
installingRuntime = false
if (!ok) {
loading.showEnded(if (release == null) "Could not reach the runtime catalog. Check the connection and press Play again."
else "The Linux runtime did not install. Check the connection and press Play again.")
return@post
}
loading.percent = -1
loading.step = "Starting the session…"
// The surface may have come and gone while the download ran; start on the live one.
if (surfaceView.holder.surface?.isValid == true) surfaceCreated(surfaceView.holder)
}
}, "runtime-install").start()
}
override fun surfaceCreated(holder: SurfaceHolder) {
if (installingRuntime) return
val runtimeDir = File(filesDir, ".wayland-rt").apply { mkdirs() }
// The compositor hands this keymap to wl_keyboard clients, which is how the guest reads
// the evdev codes we inject.
@@ -423,7 +464,7 @@ class SessionActivity : ComponentActivity(), SurfaceHolder.Callback {
override fun run() {
if (!watching) return
if (loading.visible && !loading.ended) {
loading.update(this@SessionActivity, SessionState.logFile)
if (!installingRuntime) loading.update(this@SessionActivity, SessionState.logFile)
if (ticks++ % 2 == 0) loading.tick()
}
handler.postDelayed(this, 500)
@@ -551,8 +551,9 @@ private fun Content(s: FrontEndState, selected: String, a: FrontEndActions, modi
Rise(0) { Eyebrow("Steam") }
Rise(3) {
Actions {
PrimaryButton("Play", enabled = s.ready && !s.busy, onClick = a.onPlay)
SecondaryButton("Desktop UI", enabled = s.ready && !s.busy, onClick = a.onPlayDesktopUi)
// Enabled without a runtime: the session's loading screen installs it first.
PrimaryButton("Play", enabled = !s.busy, onClick = a.onPlay)
SecondaryButton("Desktop UI", enabled = !s.busy, onClick = a.onPlayDesktopUi)
Cog(a.onSteamSettings)
}
}