From e3fe4ed94ecd916b5c7985512c417e70dfdd03ed Mon Sep 17 00:00:00 2001 From: The412Banner Date: Wed, 23 Sep 2026 13:14:36 -0400 Subject: [PATCH 1/6] Display shape: Exactly this panel, for 4:3 and 3:2 handhelds The panel's shape was never allowed narrower than 16:9 (the foldable guard), so on a 4:3 panel such as the Retroid Pocket's both choices gave 16:9 with bars. A third choice, Exactly this panel, presents the panel's own aspect with no floor; the other two are unchanged and foldables still default to 16:9. Offered in the mode settings and the in-session menu. --- .../java/com/steamdeck/launcher/SessionActivity.kt | 9 +++++++-- .../com/steamdeck/launcher/session/SessionPrefs.kt | 12 ++++++++++-- .../com/steamdeck/launcher/ui/ModeSettingsDialog.kt | 4 ++-- .../java/com/steamdeck/launcher/ui/SessionOverlay.kt | 2 +- 4 files changed, 20 insertions(+), 7 deletions(-) diff --git a/app/src/main/java/com/steamdeck/launcher/SessionActivity.kt b/app/src/main/java/com/steamdeck/launcher/SessionActivity.kt index eea0110f9..bf8af866a 100644 --- a/app/src/main/java/com/steamdeck/launcher/SessionActivity.kt +++ b/app/src/main/java/com/steamdeck/launcher/SessionActivity.kt @@ -444,8 +444,13 @@ class SessionActivity : ComponentActivity(), SurfaceHolder.Callback { // Wider than 16:9 is fine - games and the client cope with a phone's 20:9 - so the // panel's aspect is kept above that, unless the user pinned 16:9 for a foldable, and the // compositor letterboxes onto a squarer panel. - val aspect = if (SessionPrefs.shapeMode(this) == SessionPrefs.SHAPE_WIDE) 16f / 9f - else maxOf(panelW / panelH, 16f / 9f) + // "Exactly this panel" drops that floor, for a 4:3 or 3:2 handheld whose games should + // fill it. + val aspect = when (SessionPrefs.shapeMode(this)) { + SessionPrefs.SHAPE_WIDE -> 16f / 9f + SessionPrefs.SHAPE_EXACT -> panelW / panelH + else -> maxOf(panelW / panelH, 16f / 9f) + } // 720 tall at most by default, client and desktop alike: the client's CEF is the heaviest // thing in the session, and pixels above that cost frames for nothing anyone can see on a // handheld panel. The mode's settings (the cog beside Play / Desktop) can change diff --git a/app/src/main/java/com/steamdeck/launcher/session/SessionPrefs.kt b/app/src/main/java/com/steamdeck/launcher/session/SessionPrefs.kt index bb9191790..d04fdc225 100644 --- a/app/src/main/java/com/steamdeck/launcher/session/SessionPrefs.kt +++ b/app/src/main/java/com/steamdeck/launcher/session/SessionPrefs.kt @@ -35,10 +35,18 @@ object SessionPrefs { const val SHAPE_AUTO = "auto" const val SHAPE_WIDE = "16:9" + const val SHAPE_EXACT = "exact" + + /** The choices the settings offer, in order. */ + val shapeChoices = listOf( + SHAPE_AUTO to "The panel's shape (16:9 or wider)", + SHAPE_EXACT to "Exactly this panel (4:3, 3:2…)", + SHAPE_WIDE to "16:9 with bars", + ) /** - * The shape of the display the session presents: the panel's own (never narrower than 16:9) - * or a fixed 16:9. A foldable defaults to 16:9, which sits with modest bars on either of its + * The shape of the display the session presents: the panel's own (never narrower than 16:9), + * exactly the panel's (a 4:3 or 3:2 handheld, drawn edge to edge), or a fixed 16:9. A foldable defaults to 16:9, which sits with modest bars on either of its * panels; the panel's own shape would fit one and leave a strip on the other, and gamescope's * display cannot change size once the session is up. */ diff --git a/app/src/main/java/com/steamdeck/launcher/ui/ModeSettingsDialog.kt b/app/src/main/java/com/steamdeck/launcher/ui/ModeSettingsDialog.kt index 9f486328e..0aa6e0786 100644 --- a/app/src/main/java/com/steamdeck/launcher/ui/ModeSettingsDialog.kt +++ b/app/src/main/java/com/steamdeck/launcher/ui/ModeSettingsDialog.kt @@ -96,8 +96,8 @@ fun ModeSettingsPage(s: ModeSettings, a: ModeSettingsActions) { onPick = a.onResolution, ) ChoiceRow( - host, "shape", "Shape", "Adds bars to preserve 16:9.", - listOf("auto" to "The panel's shape", "16:9" to "16:9 with bars"), s.shapeMode, onPick = a.onShape, + host, "shape", "Shape", "The panel's shape stays at 16:9 or wider; pick Exactly this panel for a 4:3 or 3:2 screen.", + com.steamdeck.launcher.session.SessionPrefs.shapeChoices, s.shapeMode, onPick = a.onShape, ) } SettingsGroup("HDR") { diff --git a/app/src/main/java/com/steamdeck/launcher/ui/SessionOverlay.kt b/app/src/main/java/com/steamdeck/launcher/ui/SessionOverlay.kt index d4adfc912..8c1eadd7d 100644 --- a/app/src/main/java/com/steamdeck/launcher/ui/SessionOverlay.kt +++ b/app/src/main/java/com/steamdeck/launcher/ui/SessionOverlay.kt @@ -261,7 +261,7 @@ fun SessionDrawer(open: Boolean, a: DrawerActions) { SettingsGroup("Next session") { ChoiceRow( host, "shape", "Display shape", null, - listOf(SessionPrefs.SHAPE_AUTO to "The panel's shape", SessionPrefs.SHAPE_WIDE to "16:9 with bars"), a.shapeMode, onPick = a.onShape, + SessionPrefs.shapeChoices, a.shapeMode, onPick = a.onShape, ) if (a.steam) ChoiceRow( host, "fex", "FEX preset", null, From 92df14bb6e1768bc51f6ea3ed357fcaef66cf68d Mon Sep 17 00:00:00 2001 From: The412Banner Date: Wed, 23 Sep 2026 13:18:15 -0400 Subject: [PATCH 2/6] Custom resolution per mode, as in Bannerlator MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Resolution gains Custom…: a width × height box (320×240 to 3840×2160, evened) with one-tap presets for 4:3 (960×720, 1024×768, 1280×960), 16:10 (1280×800) and 16:9. A custom size replaces the cap and the shape for that mode - the Shape row greys out and says so - and the compositor fits it to the panel with bars where the shapes differ. Choosing any cap clears it. The device report lists it for both modes. --- .../com/steamdeck/launcher/MainActivity.kt | 5 +- .../com/steamdeck/launcher/SessionActivity.kt | 2 + .../steamdeck/launcher/core/DeviceReport.kt | 2 + .../launcher/session/SessionPrefs.kt | 21 +++++ .../launcher/ui/ModeSettingsDialog.kt | 83 +++++++++++++++++-- 5 files changed, 107 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/com/steamdeck/launcher/MainActivity.kt b/app/src/main/java/com/steamdeck/launcher/MainActivity.kt index 74cfd98f0..29f791b33 100644 --- a/app/src/main/java/com/steamdeck/launcher/MainActivity.kt +++ b/app/src/main/java/com/steamdeck/launcher/MainActivity.kt @@ -149,6 +149,7 @@ class MainActivity : ComponentActivity() { // The mode whose settings dialog is open, with what it shows; refreshed by openModeSettings(). private var settingsMode by mutableStateOf(null) private var resolutionCap by mutableStateOf(1080) + private var customResolution by mutableStateOf?>(null) private var fexPreset by mutableStateOf("") private var steamChannel by mutableStateOf("publicbeta") private var theme by mutableStateOf("paper") @@ -414,7 +415,7 @@ class MainActivity : ComponentActivity() { private fun ModeSettingsHost(mode: String) { ModeSettingsPage( ModeSettings( - mode = mode, resolutionCap = resolutionCap, shapeMode = shapeMode, + mode = mode, resolutionCap = resolutionCap, customResolution = customResolution, shapeMode = shapeMode, hdr = hdrOn, hdrReason = hdrReason, linuxRows = linuxRows, linuxSelected = if (mode == SessionService.MODE_STEAM) linuxSteam else linuxDesktop, @@ -436,6 +437,7 @@ class MainActivity : ComponentActivity() { ), ModeSettingsActions( onResolution = { cap -> SessionPrefs.setResolutionCap(this, mode, cap); resolutionCap = cap }, + onCustomResolution = { size -> SessionPrefs.setCustomResolution(this, mode, size); customResolution = size }, onShape = { shape -> SessionPrefs.setShapeMode(this, shape); shapeMode = shape }, onHdr = { on -> SessionPrefs.setHdr(this, mode, on); hdrOn = on }, onSelectLinux = { id -> SessionPrefs.setLinuxDriver(this, mode, id); refreshDrivers() }, @@ -530,6 +532,7 @@ class MainActivity : ComponentActivity() { showApps = false refreshDrivers() resolutionCap = SessionPrefs.resolutionCap(this, mode) + customResolution = SessionPrefs.customResolution(this, mode) fexPreset = SessionPrefs.fexPreset(this) steamChannel = SessionPrefs.steamChannel(this) addedGamesDirs = SessionPrefs.addedGamesDirs(this) diff --git a/app/src/main/java/com/steamdeck/launcher/SessionActivity.kt b/app/src/main/java/com/steamdeck/launcher/SessionActivity.kt index bf8af866a..c7adb4dd7 100644 --- a/app/src/main/java/com/steamdeck/launcher/SessionActivity.kt +++ b/app/src/main/java/com/steamdeck/launcher/SessionActivity.kt @@ -456,6 +456,8 @@ class SessionActivity : ComponentActivity(), SurfaceHolder.Callback { // handheld panel. The mode's settings (the cog beside Play / Desktop) can change // the cap or lift it to the panel. val mode = SessionPrefs.prefMode(intent.getStringExtra(SessionService.EXTRA_MODE) ?: SessionService.MODE_STEAM) + // A custom resolution is taken as given; the compositor fits it to the panel. + SessionPrefs.customResolution(this, mode)?.let { return it } val cap = SessionPrefs.resolutionCap(this, mode) val height = (if (cap <= 0) panelH else minOf(panelH, cap.toFloat())).toInt() val width = (height * aspect).toInt() diff --git a/app/src/main/java/com/steamdeck/launcher/core/DeviceReport.kt b/app/src/main/java/com/steamdeck/launcher/core/DeviceReport.kt index 9c6c08300..50b1edacb 100644 --- a/app/src/main/java/com/steamdeck/launcher/core/DeviceReport.kt +++ b/app/src/main/java/com/steamdeck/launcher/core/DeviceReport.kt @@ -109,6 +109,8 @@ object DeviceReport { k("Session output", SessionState.outputSize?.let { "${it.first}x${it.second}" }) k("Session refresh", String.format(Locale.US, "%.2f Hz", SessionState.refreshHz)) k("Shape setting", SessionPrefs.shapeMode(context)) + k("Custom resolution (Steam)", SessionPrefs.customResolution(context, SessionService.MODE_STEAM)?.let { "${it.first}x${it.second}" } ?: "off") + k("Custom resolution (Desktop)", SessionPrefs.customResolution(context, SessionService.MODE_DESKTOP)?.let { "${it.first}x${it.second}" } ?: "off") k("Foldable", context.packageManager.hasSystemFeature("android.hardware.sensor.hinge_angle")) h("Drivers") diff --git a/app/src/main/java/com/steamdeck/launcher/session/SessionPrefs.kt b/app/src/main/java/com/steamdeck/launcher/session/SessionPrefs.kt index d04fdc225..17a399e93 100644 --- a/app/src/main/java/com/steamdeck/launcher/session/SessionPrefs.kt +++ b/app/src/main/java/com/steamdeck/launcher/session/SessionPrefs.kt @@ -308,6 +308,27 @@ object SessionPrefs { prefs(context).edit().putInt("resolutionCap.$mode", cap).apply() } + /** + * A fixed size for the session's display, per mode, or null. When set it replaces both the + * cap and the shape: the compositor fits it to the panel with bars where the shapes differ. + */ + fun customResolution(context: Context, mode: String): Pair? = + parseResolution(prefs(context).getString("customRes.$mode", null)) + + fun setCustomResolution(context: Context, mode: String, size: Pair?) { + prefs(context).edit().putString("customRes.$mode", size?.let { "${it.first}x${it.second}" }).apply() + } + + /** "1024x768" (or ×, or *) to an even size inside 320x240..3840x2160; anything else is null. */ + fun parseResolution(text: String?): Pair? { + val parts = text?.trim()?.split('x', 'X', '×', '*')?.map { it.trim() } ?: return null + if (parts.size != 2) return null + val w = parts[0].toIntOrNull() ?: return null + val h = parts[1].toIntOrNull() ?: return null + if (w !in 320..3840 || h !in 240..2160) return null + return Pair(w and 1.inv(), h and 1.inv()) + } + /** * What the desktop shell composites with: pixman (software, the default - the Adreno stand-in * is not a DRM render node, so labwc's gbm allocator cannot use it), or gles2 / vulkan for a diff --git a/app/src/main/java/com/steamdeck/launcher/ui/ModeSettingsDialog.kt b/app/src/main/java/com/steamdeck/launcher/ui/ModeSettingsDialog.kt index 0aa6e0786..2a3e60ab2 100644 --- a/app/src/main/java/com/steamdeck/launcher/ui/ModeSettingsDialog.kt +++ b/app/src/main/java/com/steamdeck/launcher/ui/ModeSettingsDialog.kt @@ -6,6 +6,10 @@ import androidx.compose.foundation.layout.size import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp @@ -18,6 +22,8 @@ class DriverRow(val id: String, val name: String, val detail: String, val remova class ModeSettings( val mode: String, val resolutionCap: Int, + /** A fixed session size, or null for the cap and shape. */ + val customResolution: Pair? = null, val shapeMode: String, val hdr: Boolean, val hdrReason: String?, @@ -50,6 +56,8 @@ class AddedGameRow(val folderPath: String, val folderName: String, val exePath: class ModeSettingsActions( val onResolution: (Int) -> Unit, + /** Null clears it. */ + val onCustomResolution: (Pair?) -> Unit = {}, val onShape: (String) -> Unit, val onHdr: (Boolean) -> Unit, val onSelectLinux: (String) -> Unit, @@ -88,16 +96,25 @@ fun ModeSettingsPage(s: ModeSettings, a: ModeSettingsActions) { ) { SettingsGroup("Display") { val default = SessionPrefs.defaultResolutionCap(s.mode) + var editCustom by remember { mutableStateOf(false) } + val custom = s.customResolution ChoiceRow( host, "res", "Resolution", "Applies next session.", listOf(720 to "Up to 720p", 900 to "Up to 900p", 1080 to "Up to 1080p", 0 to "The panel's own") - .map { (cap, label) -> cap to (if (cap == default) "$label - the default" else label) }, - s.resolutionCap, note = "720p can improve menu responsiveness.", - onPick = a.onResolution, + .map { (cap, label) -> cap to (if (cap == default) "$label - the default" else label) } + + (CUSTOM to (custom?.let { "Custom · ${it.first}×${it.second}" } ?: "Custom…")), + if (custom != null) CUSTOM else s.resolutionCap, note = "720p can improve menu responsiveness.", + onPick = { v -> if (v == CUSTOM) editCustom = true else { a.onCustomResolution(null); a.onResolution(v) } }, ) ChoiceRow( - host, "shape", "Shape", "The panel's shape stays at 16:9 or wider; pick Exactly this panel for a 4:3 or 3:2 screen.", - com.steamdeck.launcher.session.SessionPrefs.shapeChoices, s.shapeMode, onPick = a.onShape, + host, "shape", "Shape", + if (custom != null) "Set by the custom resolution." else "The panel's shape stays at 16:9 or wider; pick Exactly this panel for a 4:3 or 3:2 screen.", + com.steamdeck.launcher.session.SessionPrefs.shapeChoices, s.shapeMode, enabled = custom == null, onPick = a.onShape, + ) + if (editCustom) CustomResolutionDialog( + initial = custom, + onSave = { size -> editCustom = false; a.onCustomResolution(size) }, + onDismiss = { editCustom = false }, ) } SettingsGroup("HDR") { @@ -267,3 +284,59 @@ private fun DriverRowMenu( } } } + +/** The Resolution menu's "Custom…" entry. */ +private const val CUSTOM = -1 + +/** Width × height for the session, with the common handheld shapes one tap away. */ +@OptIn(androidx.compose.foundation.layout.ExperimentalLayoutApi::class) +@Composable +private fun CustomResolutionDialog(initial: Pair?, onSave: (Pair) -> Unit, onDismiss: () -> Unit) { + var w by remember { mutableStateOf(initial?.first?.toString() ?: "") } + var h by remember { mutableStateOf(initial?.second?.toString() ?: "") } + val parsed = SessionPrefs.parseResolution("${w}x$h") + androidx.compose.material3.AlertDialog( + onDismissRequest = onDismiss, + title = { Text("Custom resolution") }, + text = { + androidx.compose.foundation.layout.Column { + Text( + "The session's display size. It replaces the cap and the shape; a size that does not match the panel's shape gets bars.", + fontSize = 13.sp, color = MaterialTheme.colorScheme.onSurfaceVariant, + ) + androidx.compose.foundation.layout.Row( + verticalAlignment = androidx.compose.ui.Alignment.CenterVertically, + modifier = Modifier.padding(top = 12.dp), + ) { + val numbers = androidx.compose.foundation.text.KeyboardOptions(keyboardType = androidx.compose.ui.text.input.KeyboardType.Number) + androidx.compose.material3.OutlinedTextField( + w, { v -> w = v.filter(Char::isDigit).take(4) }, label = { Text("Width") }, + singleLine = true, keyboardOptions = numbers, modifier = Modifier.weight(1f), + ) + Text("×", fontSize = 18.sp, modifier = Modifier.padding(horizontal = 10.dp)) + androidx.compose.material3.OutlinedTextField( + h, { v -> h = v.filter(Char::isDigit).take(4) }, label = { Text("Height") }, + singleLine = true, keyboardOptions = numbers, modifier = Modifier.weight(1f), + ) + } + androidx.compose.foundation.layout.FlowRow( + horizontalArrangement = androidx.compose.foundation.layout.Arrangement.spacedBy(6.dp), + modifier = Modifier.padding(top = 10.dp), + ) { + for ((pw, ph, tag) in listOf(Triple(960, 720, "4:3"), Triple(1024, 768, "4:3"), Triple(1280, 960, "4:3"), Triple(1280, 800, "16:10"), Triple(1152, 648, "16:9"), Triple(1280, 720, "16:9"))) { + androidx.compose.material3.AssistChip( + onClick = { w = pw.toString(); h = ph.toString() }, + label = { Text("$pw×$ph · $tag", fontSize = 12.sp) }, + ) + } + } + if (parsed == null && (w.isNotEmpty() || h.isNotEmpty())) Text( + "Between 320×240 and 3840×2160.", fontSize = 12.sp, color = MaterialTheme.colorScheme.error, + modifier = Modifier.padding(top = 6.dp), + ) + } + }, + confirmButton = { androidx.compose.material3.TextButton(enabled = parsed != null, onClick = { parsed?.let(onSave) }) { Text("Use") } }, + dismissButton = { androidx.compose.material3.TextButton(onClick = onDismiss) { Text("Cancel") } }, + ) +} From e54010e9b95c247ba3448067c8318703478d9d2d Mon Sep 17 00:00:00 2001 From: The412Banner Date: Wed, 23 Sep 2026 13:30:27 -0400 Subject: [PATCH 3/6] Progress log: onboarding, Desktop & apps page, 4:3 display, PR #11 audio --- docs/progress-log.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/docs/progress-log.md b/docs/progress-log.md index cd0d2027b..ac6c75266 100644 --- a/docs/progress-log.md +++ b/docs/progress-log.md @@ -6,6 +6,25 @@ the timeline, then lessons and backlog. Companion to the README (what the app *d --- +## 2026-09-23 (afternoon) - main `90c7574`: first-run onboarding, Desktop & apps page, 4:3 display + +State: **main = `90c7574`** (PR #14 merge), main build run 35895701784. No tag, no release; next version is still open. **Nothing below is device-tested** except the audio default (Kurt, AYN Thor). + +- **PR #11 → `f79dc2d`** (`fix/directaudio-default`): the Steam client's audio is back on the 0.1.5 classic sink by default (`module-aaudio-classic-sink.so`, byte-identical to 0.1.5's arm64 module); DirectAudio is a choice in the Steam cog ("Steam client audio", pref `clientDirectAudio`, default off). Games keep DirectAudio on by default; mic on by default, RECORD_AUDIO asked once. Old armhf sink out of the bundle; the bundle re-unpacks when its contents change (stamp = BUNDLE_STAMP + CRC32 of pulseaudio.tzst). Relay logs a heartbeat line; Setup › Session logs › Share latest logs. Kurt on the Thor: classic "works perfectly". +- **PR #12 → `0ddb0be`** (`feat/play-installs-runtime`): + - `586c9c2` Play and Desktop UI are enabled with no runtime; the session's loading screen downloads, checks and unpacks it ("downloading the Linux runtime · 332 of 791 MB"), then starts the session. A failure ends on the loading screen with the reason. + - `f437957` Desktop does the same: the runtime if missing, then the `desktop` package from `desktop.json` (426 MB), then LXQt. Play, Desktop UI and Desktop all give the non-Adreno warning before any download (`MainActivity.startSession`). + - `cbd3bc9` Desktop & apps is a page in the pane (pageKey `apps`), not a dialog: a rail-style dropdown per tier with an installed/total pill (Native ARM64 open), per-package Install/Remove, and the installing package's step line and bar in its own row. Leaving the page does not stop an install. +- **PR #14 → `90c7574`** (`fix/exact-panel-shape`), from a user report on a 4:3 handheld (RP Nova) - "the panel's shape" still gave 16:9: + - Cause: `SessionActivity` sized the display at `maxOf(panel aspect, 16:9)` (the foldable guard), so on a 4:3 panel both choices were 16:9 with bars. + - `9c47095` Shape › **Exactly this panel (4:3, 3:2…)** - the panel's own aspect, no floor. The other two unchanged; foldables still default to 16:9. In the cog and the in-session menu (`SessionPrefs.shapeChoices`). + - `ad2f9f8` Resolution › **Custom…** per mode (`customRes.`, 320×240-3840×2160, evened) with 4:3 / 16:10 / 16:9 presets. It replaces the cap and the shape (Shape greys out); picking a cap clears it; the device report lists it. Not in the in-session menu. + - Note: the branch was cut from `cbd3bc9`, not `0ddb0be` - same tree. +- Staged: `SteamDeck-play-installs-runtime-586c9c2.apk` (`21460acd…`), `…-f437957.apk` (`174e74a6…`), `SteamDeck-apps-page-cbd3bc9.apk` (`c2cfbebe…`), `SteamDeck-custom-res-ad2f9f8.apk` (`d02ee5ce…`, = main's content). HTML preview of the onboarding flow: `/sdcard/Download/SteamDeck-onboarding-preview.html`. +- CI note: a branch push does not build (`build.yml` = main + PRs); dispatch it with `gh workflow run build.yml --ref `. A newer dispatch on the same branch cancels the older run. +- Open: Kurt's PR #13 "Add Steam second-screen controls" (`steamdeck-second-screen`) - not reviewed; now needs to merge onto `90c7574`. +- **Next:** (1) fresh-install pass: uninstall, install, press Desktop first (runtime + desktop, ~1.2 GB), then Play with no download; (2) the Desktop & apps page on device (dropdowns, in-row bar, Back mid-install); (3) the Nova reporter tests Exactly this panel / Custom 960×720; (4) PR #13. + ## 2026-09-23 - branch `feat/armada-and-lineage` - Steam Deck mode: `-steamdeck` only; SteamOS helper stubs staged from the apk under `/usr/bin` and `/usr/bin/steamos-polkit-helpers` (the missing `steamos-update` there was the "Update Error" dialog; `jupiter-dock-updater --check` answers 7 so no dock firmware row); Deck mode defaults the `steamdeck_publicbeta` channel (on `publicbeta` every start reinstalled the client and lost the launch URL); client branch row in the Steam cog. From 8e1db35f98d81ff08a865f12e55bba6076c21608 Mon Sep 17 00:00:00 2001 From: The412Banner Date: Wed, 23 Sep 2026 13:38:35 -0400 Subject: [PATCH 4/6] Play on a fresh install: the session screen no longer closes before it can install the runtime SessionActivity finished at once when no runtime was installed - a guard from before the loading screen could install it - so Play (and Desktop) opened a screen that closed before anything happened. It now logs and carries on to installThenStart. --- .../main/java/com/steamdeck/launcher/SessionActivity.kt | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/com/steamdeck/launcher/SessionActivity.kt b/app/src/main/java/com/steamdeck/launcher/SessionActivity.kt index c7adb4dd7..5611fb6e3 100644 --- a/app/src/main/java/com/steamdeck/launcher/SessionActivity.kt +++ b/app/src/main/java/com/steamdeck/launcher/SessionActivity.kt @@ -116,11 +116,9 @@ class SessionActivity : ComponentActivity(), SurfaceHolder.Callback { // PulseAudio are media playback); they are never forwarded to the guest. volumeControlStream = android.media.AudioManager.STREAM_MUSIC - if (!LinuxRuntime.isInstalled(this)) { - Log.e(TAG, "the Linux runtime is not installed") - finish() - return - } + // No runtime is not a reason to leave: the loading screen installs it (installThenStart, + // below) and the session starts when it is in. + if (!LinuxRuntime.isInstalled(this)) Log.i(TAG, "the Linux runtime is not installed; the loading screen installs it") val root = FrameLayout(this) surfaceView = SurfaceView(this) From cd677f0125a822d92d2362281290eca7a8695693 Mon Sep 17 00:00:00 2001 From: xXJsonDeruloXx Date: Wed, 23 Sep 2026 14:08:13 -0400 Subject: [PATCH 5/6] Add Android home app integration --- app/src/main/AndroidManifest.xml | 18 ++++ .../com/steamdeck/launcher/HomeActivity.kt | 27 ++++++ .../java/com/steamdeck/launcher/HomeApp.kt | 95 +++++++++++++++++++ .../com/steamdeck/launcher/MainActivity.kt | 36 +++++++ .../com/steamdeck/launcher/SessionActivity.kt | 64 ++++++++++--- .../launcher/session/SessionService.kt | 1 + .../steamdeck/launcher/ui/FrontEndScreen.kt | 53 +++++++++-- .../steamdeck/launcher/ui/SessionOverlay.kt | 48 +++++++++- .../steamdeck/launcher/ui/SettingsWidgets.kt | 6 +- 9 files changed, 326 insertions(+), 22 deletions(-) create mode 100644 app/src/main/java/com/steamdeck/launcher/HomeActivity.kt create mode 100644 app/src/main/java/com/steamdeck/launcher/HomeApp.kt diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 887478f9c..362a87d12 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -54,6 +54,23 @@ + + + + + + + + +