diff --git a/app/src/main/java/com/steamdeck/launcher/MainActivity.kt b/app/src/main/java/com/steamdeck/launcher/MainActivity.kt index f1f069ed5..fd4c7b22c 100644 --- a/app/src/main/java/com/steamdeck/launcher/MainActivity.kt +++ b/app/src/main/java/com/steamdeck/launcher/MainActivity.kt @@ -251,7 +251,7 @@ class MainActivity : ComponentActivity() { showNonAdreno?.let { release -> ConfirmDialog( title = "Not an Adreno GPU", - text = "The runtime draws with Turnip, an Adreno driver. On ${com.steamdeck.launcher.core.DeviceSupport.gpuName()} the compositor gets no usable Vulkan device and a session comes up as sound over a black screen. The download is ${"%.0f".format(release.size / 1e6)} MB.", + text = "Turnip supports Adreno GPUs. On ${com.steamdeck.launcher.core.DeviceSupport.gpuName()}, Steam may show a black screen. Download: ${"%.0f".format(release.size / 1e6)} MB.", confirm = "Install anyway", onConfirm = { showNonAdreno = null; install(release) }, onDismiss = { showNonAdreno = null }, diff --git a/app/src/main/java/com/steamdeck/launcher/core/FexPreset.kt b/app/src/main/java/com/steamdeck/launcher/core/FexPreset.kt index 00a99c6d6..bbdb1a750 100644 --- a/app/src/main/java/com/steamdeck/launcher/core/FexPreset.kt +++ b/app/src/main/java/com/steamdeck/launcher/core/FexPreset.kt @@ -19,19 +19,19 @@ object FexPreset { ) val all: List = listOf( - Preset("", "FEX defaults", "No variables set; what every session ran on until now.", emptyList()), - Preset("STABILITY", "Stability", "Every TSO emulation on, single-block: the slowest and the surest.", tso(1, 1, 1, 1) + listOf("FEX_X87REDUCEDPRECISION=0", "FEX_MULTIBLOCK=0")), - Preset("COMPATIBILITY", "Compatibility", "Every TSO emulation on, multiblock.", tso(1, 1, 1, 1) + listOf("FEX_X87REDUCEDPRECISION=0", "FEX_MULTIBLOCK=1")), - Preset("INTERMEDIATE", "Intermediate", "Main and half-barrier TSO on, vector and memcpy off, reduced x87.", tso(1, 0, 0, 1) + listOf("FEX_X87REDUCEDPRECISION=1", "FEX_MULTIBLOCK=1")), - Preset("PERFORMANCE", "Performance", "All TSO off, reduced x87, multiblock.", tso(0, 0, 0, 0) + listOf("FEX_X87REDUCEDPRECISION=1", "FEX_MULTIBLOCK=1")), - Preset("PERFORMANCE_TSO", "Performance + TSO", "Performance with the main store ordering kept on, for the many games that need it.", tso(1, 0, 0, 0) + listOf("FEX_X87REDUCEDPRECISION=1", "FEX_MULTIBLOCK=1")), - Preset("EXTREME", "Extreme", "Performance plus no self-modifying-code checks and FEX's block caches retuned. JIT-heavy titles and DRM can break.", + Preset("", "FEX defaults", "Uses FEX defaults.", emptyList()), + Preset("STABILITY", "Stability", "Safest, slowest settings.", tso(1, 1, 1, 1) + listOf("FEX_X87REDUCEDPRECISION=0", "FEX_MULTIBLOCK=0")), + Preset("COMPATIBILITY", "Compatibility", "Full TSO with multiblock.", tso(1, 1, 1, 1) + listOf("FEX_X87REDUCEDPRECISION=0", "FEX_MULTIBLOCK=1")), + Preset("INTERMEDIATE", "Intermediate", "Balanced TSO with reduced x87 precision.", tso(1, 0, 0, 1) + listOf("FEX_X87REDUCEDPRECISION=1", "FEX_MULTIBLOCK=1")), + Preset("PERFORMANCE", "Performance", "All TSO disabled; multiblock.", tso(0, 0, 0, 0) + listOf("FEX_X87REDUCEDPRECISION=1", "FEX_MULTIBLOCK=1")), + Preset("PERFORMANCE_TSO", "Performance + TSO", "Performance settings with main TSO enabled.", tso(1, 0, 0, 0) + listOf("FEX_X87REDUCEDPRECISION=1", "FEX_MULTIBLOCK=1")), + Preset("EXTREME", "Extreme", "Disables self-modifying-code checks. JIT games and DRM may break.", tso(0, 0, 0, 0) + listOf("FEX_X87REDUCEDPRECISION=1", "FEX_MULTIBLOCK=1", "FEX_SMCCHECKS=none", "FEX_DISABLEL2CACHE=1", "FEX_DYNAMICL1CACHE=1", "FEX_DYNAMICL1CACHEINCREASECOUNTHEURISTIC=250", "FEX_DYNAMICL1CACHEDECREASECOUNTHEURISTIC=50")), - Preset("EXTREME_TSO", "Extreme + TSO", "Extreme with the main store ordering kept on.", + Preset("EXTREME_TSO", "Extreme + TSO", "Extreme settings with main TSO enabled.", tso(1, 0, 0, 0) + listOf("FEX_X87REDUCEDPRECISION=1", "FEX_MULTIBLOCK=1", "FEX_SMCCHECKS=none", "FEX_DISABLEL2CACHE=1", "FEX_DYNAMICL1CACHE=1", "FEX_DYNAMICL1CACHEINCREASECOUNTHEURISTIC=250", "FEX_DYNAMICL1CACHEDECREASECOUNTHEURISTIC=50")), - Preset("EXTREME_GN", "Extreme-gn", "Performance plus the small TSC scale and volatile-metadata hints; SMC checks stay on. Try before Extreme.", + Preset("EXTREME_GN", "Extreme-gn", "Performance with TSC and metadata hints. Try before Extreme.", tso(0, 0, 0, 0) + listOf("FEX_X87REDUCEDPRECISION=1", "FEX_MULTIBLOCK=1", "FEX_SMALLTSCSCALE=1", "FEX_VOLATILEMETADATA=1")), - Preset("DENUVO", "Denuvo", "Performance with full SMC checks and the hypervisor bit hidden, for titles whose anti-tamper looks for either.", + Preset("DENUVO", "Denuvo", "Performance with full code checks and the hypervisor bit hidden.", tso(0, 0, 0, 0) + listOf("FEX_X87REDUCEDPRECISION=1", "FEX_MULTIBLOCK=1", "FEX_SMCCHECKS=full", "FEX_HIDEHYPERVISORBIT=1")), ) diff --git a/app/src/main/java/com/steamdeck/launcher/ui/DesktopAppsDialog.kt b/app/src/main/java/com/steamdeck/launcher/ui/DesktopAppsDialog.kt index 983778121..74a2834e2 100644 --- a/app/src/main/java/com/steamdeck/launcher/ui/DesktopAppsDialog.kt +++ b/app/src/main/java/com/steamdeck/launcher/ui/DesktopAppsDialog.kt @@ -49,11 +49,7 @@ fun DesktopAppsDialog( when { rows == null -> Text("Could not reach the package catalog", color = MaterialTheme.colorScheme.onSurfaceVariant) rows.isEmpty() -> Text("Loading the catalog…", color = MaterialTheme.colorScheme.onSurfaceVariant) - else -> Text( - " starts an emulator fullscreen under gamescope, with the GPU - on the desktop, Vulkan programs cannot draw. " + - "ROMs are at /root/ROMs, the phone at /root/Storage.", - style = MaterialTheme.typography.bodySmall, color = MaterialTheme.colorScheme.onSurfaceVariant, - ) + else -> Unit } var lastTier = 0 for (row in rows ?: emptyList()) { @@ -61,9 +57,9 @@ fun DesktopAppsDialog( lastTier = row.tier Text( when (row.tier) { - 1 -> "Native for this hardware" - 2 -> "Upstream ARM64 builds - experimental on a phone" - else -> "Built from source - experimental" + 1 -> "Native ARM64" + 2 -> "ARM64 · experimental" + else -> "Source build · experimental" }, style = MaterialTheme.typography.labelSmall, color = MaterialTheme.colorScheme.primary, @@ -74,8 +70,8 @@ fun DesktopAppsDialog( Column(modifier = Modifier.weight(1f)) { Text(row.name, style = MaterialTheme.typography.bodyMedium) Text( - (if (row.installed != null) "installed ${row.installed}" else "${row.version} · ${row.size}") + - (if (row.notes.isNotEmpty()) " - ${row.notes}" else ""), + (if (row.installed != null) "Installed ${row.installed}" else "${row.version} · ${row.size}") + + (if (row.notes.isNotEmpty()) " · ${row.notes}" else ""), style = MaterialTheme.typography.bodySmall, color = MaterialTheme.colorScheme.onSurfaceVariant, ) diff --git a/app/src/main/java/com/steamdeck/launcher/ui/FrontEndScreen.kt b/app/src/main/java/com/steamdeck/launcher/ui/FrontEndScreen.kt index 0041abf8d..52d2d3eb5 100644 --- a/app/src/main/java/com/steamdeck/launcher/ui/FrontEndScreen.kt +++ b/app/src/main/java/com/steamdeck/launcher/ui/FrontEndScreen.kt @@ -266,16 +266,13 @@ private fun Rail( Spacer(Modifier.width(10.dp)) Column { Text("SteamDeck", fontSize = 18.sp, fontWeight = FontWeight.Bold, color = colors.onBackground) - Text( - when { - s.busy -> if (s.percent >= 0) "${s.stage} ${s.percent}%" else s.stage - !s.ready -> "runtime not installed" - s.available != null && s.available != s.installed -> "runtime ${s.installed} · ${s.available} available" - s.running != null -> "${s.running} in the background" - else -> "runtime ${s.installed ?: "?"}" - }, - fontSize = 11.sp, color = colors.onSurfaceVariant, maxLines = 1, overflow = TextOverflow.Ellipsis, - ) + val status = when { + s.busy -> if (s.percent >= 0) "${s.stage} ${s.percent}%" else s.stage + !s.ready -> "runtime not installed" + s.available != null && s.available != s.installed -> "runtime update available" + else -> null + } + if (status != null) Text(status, fontSize = 11.sp, color = colors.onSurfaceVariant, maxLines = 1, overflow = TextOverflow.Ellipsis) } } AnimatedVisibility(s.busy, enter = expandVertically() + fadeIn(), exit = shrinkVertically() + fadeOut()) { @@ -319,7 +316,7 @@ private fun Rail( NavItem("Steam", "steam", selected == "steam", caret = openSteam, count = s.steamGames.size, register = register, unregister = unregister) { onSelect("steam") } Sub(openSteam) { for ((i, g) in s.steamGames.withIndex()) NavItem(g.name, "app:${g.appId}", selected == "app:${g.appId}", small = true, i = i, register = register, unregister = unregister) { onSelect("app:${g.appId}") } - if (s.steamGames.isEmpty()) NavItem("no games installed", "x", false, small = true, muted = true, register = register, unregister = unregister) {} + if (s.steamGames.isEmpty()) NavItem("No games", "x", false, small = true, muted = true, register = register, unregister = unregister) {} NavItem("Settings", "settings:steam", selected == "settings:steam", small = true, tiny = true, muted = true, i = s.steamGames.size, register = register, unregister = unregister) { a.onSteamSettings() } } NavItem("Desktop", "desktop", selected == "desktop", caret = openDesktop, count = s.emulators.count { it.installed }, register = register, unregister = unregister) { onSelect("desktop") } @@ -330,12 +327,12 @@ private fun Rail( Sub(openEmu == key) { for ((j, g) in e.games.withIndex()) NavItem(g.name, "rom:${e.id}:$j", selected == "rom:${e.id}:$j", small = true, tiny = true, i = j, register = register, unregister = unregister) { onSelect("rom:${e.id}:$j") } if (e.games.isEmpty()) NavItem( - if (e.id == "retroarch") "browses its own games" else if (s.romsDir == null) "choose a ROMs folder" else "nothing for ${e.system} in ROMs", + if (e.id == "retroarch") "Browse games in RetroArch" else if (s.romsDir == null) "Choose ROMs folder" else "No ${e.system} games", "x", false, small = true, tiny = true, muted = true, register = register, unregister = unregister, ) { if (e.id != "retroarch") a.onRoms() } } } - if (s.emulators.none { it.installed }) NavItem("install emulators under Desktop & apps", "x", false, small = true, muted = true, register = register, unregister = unregister) { a.onApps() } + if (s.emulators.none { it.installed }) NavItem("Install emulators", "x", false, small = true, muted = true, register = register, unregister = unregister) { a.onApps() } NavItem("Settings", "settings:lxqt", selected == "settings:lxqt", small = true, tiny = true, muted = true, i = s.emulators.count { it.installed }, register = register, unregister = unregister) { a.onDesktopSettings() } } Spacer(Modifier.height(6.dp)) @@ -371,9 +368,9 @@ private fun Rail( } } Box { - item("Start offline", when { s.offlineAccount == null -> "sign in first"; s.offline -> "on"; else -> "off" }, 7, null) { if (s.offlineAccount != null) menus.open = "offline" } + item("Offline", when { s.offlineAccount == null -> "sign in first"; s.offline -> "on"; else -> "off" }, 7, null) { if (s.offlineAccount != null) menus.open = "offline" } AnchoredMenu( - menus.open == "offline", onDismiss = { if (menus.open == "offline") menus.open = null }, title = "Start offline", + menus.open == "offline", onDismiss = { if (menus.open == "offline") menus.open = null }, title = "Offline", ) { MenuItem("On", checked = s.offline) { if (!s.offline) a.onOffline(); menus.open = null } MenuItem("Off", checked = !s.offline) { if (s.offline) a.onOffline(); menus.open = null } @@ -428,7 +425,7 @@ private fun RunningTile(name: String, onResume: () -> Unit) { Spacer(Modifier.width(8.dp)) Column(modifier = Modifier.weight(1f)) { Text(name, fontSize = 13.sp, fontWeight = FontWeight.SemiBold, color = colors.onBackground, maxLines = 1, overflow = TextOverflow.Ellipsis) - Text("running · tap to go back", fontSize = 11.sp, color = colors.onSurfaceVariant) + Text("Resume", fontSize = 11.sp, color = colors.onSurfaceVariant) } } } @@ -591,17 +588,17 @@ private fun Content(s: FrontEndState, selected: String, a: FrontEndActions, modi Rise(0) { Eyebrow("Desktop") } Rise(3) { Actions { - PrimaryButton(if (s.desktopInstalled) "Desktop" else "Install the desktop first", enabled = s.ready && !s.busy && s.desktopInstalled, onClick = a.onDesktop) + PrimaryButton("Desktop", enabled = s.ready && !s.busy && s.desktopInstalled, onClick = a.onDesktop) SecondaryButton("Desktop & apps", onClick = a.onApps) Cog(a.onDesktopSettings) } } Rise(4) { SectionTitle("Emulators", "${s.emulators.count { it.installed }} installed · ${s.emulators.count { !it.installed }} available") } Rise(5) { - Text("? Why emulators?", fontSize = 12.sp, color = colors.onSurfaceVariant, modifier = Modifier.padding(bottom = 6.dp).clip(Shape10).clickable(onClick = a.onEmulatorHelp).padding(horizontal = 6.dp, vertical = 4.dp)) + Text("Why?", fontSize = 12.sp, color = colors.onSurfaceVariant, modifier = Modifier.padding(bottom = 6.dp).clip(Shape10).clickable(onClick = a.onEmulatorHelp).padding(horizontal = 6.dp, vertical = 4.dp)) } Rise(6, Modifier.weight(1f).fillMaxWidth()) { - ArtGrid(s.emulators.map { e -> Tile(e.name, if (e.installed) (if (e.id == "retroarch") "browses its own games" else "${e.games.size} game${if (e.games.size == 1) "" else "s"}") else "not installed", null, "emu:${e.id}", e.iconRes, dim = !e.installed) { if (e.installed) a.onEmulator(e) else a.onApps() } }) + ArtGrid(s.emulators.map { e -> Tile(e.name, if (e.installed) (if (e.id == "retroarch") null else "${e.games.size} game${if (e.games.size == 1) "" else "s"}") else "Not installed", null, "emu:${e.id}", e.iconRes, dim = !e.installed) { if (e.installed) a.onEmulator(e) else a.onApps() } }) } } selected.startsWith("emu:") -> { @@ -619,9 +616,9 @@ private fun Content(s: FrontEndState, selected: String, a: FrontEndActions, modi Rise(4) { SectionTitle("Games", e.games.size.toString()) } if (e.games.isEmpty()) Rise(5) { Note( - if (s.romsDir == null) "Choose a ROMs folder first (left, or the button above)." - else if (e.id == "retroarch") "RetroArch loads its games itself: open it and browse to root › ROMs." - else "Put ${e.system} games in ROMs/${e.system.substringBefore(' ')} (or the ROMs folder itself); the list rebuilds when this screen opens.", + if (s.romsDir == null) "Choose a ROMs folder." + else if (e.id == "retroarch") "Browse to /root/ROMs in RetroArch." + else "Add ${e.system} games to ROMs/${e.system.substringBefore(' ')}.", ) } else Rise(5, Modifier.weight(1f).fillMaxWidth()) { @@ -658,12 +655,12 @@ private fun Content(s: FrontEndState, selected: String, a: FrontEndActions, modi } } } - else -> Note("Pick something on the left.") + else -> Note("Select an item.") } } } -private class Tile(val title: String, val sub: String, val art: File?, val key: String, val iconRes: Int? = null, val dim: Boolean = false, val onClick: () -> Unit) +private class Tile(val title: String, val sub: String?, val art: File?, val key: String, val iconRes: Int? = null, val dim: Boolean = false, val onClick: () -> Unit) @Composable @@ -826,7 +823,7 @@ private fun GameTile(t: Tile, wide: Boolean, square: Boolean, src: MutableIntera } Column(modifier = Modifier.padding(horizontal = 6.dp, vertical = 5.dp)) { Text(t.title, fontSize = 10.sp, fontWeight = FontWeight.SemiBold, color = colors.onBackground, maxLines = 1, overflow = TextOverflow.Ellipsis) - Text(t.sub, fontSize = 8.sp, color = colors.onSurfaceVariant, maxLines = 1, overflow = TextOverflow.Ellipsis) + if (t.sub != null) Text(t.sub, fontSize = 8.sp, color = colors.onSurfaceVariant, maxLines = 1, overflow = TextOverflow.Ellipsis) } } } diff --git a/app/src/main/java/com/steamdeck/launcher/ui/MainScreen.kt b/app/src/main/java/com/steamdeck/launcher/ui/MainScreen.kt index 54debba25..84eebda60 100644 --- a/app/src/main/java/com/steamdeck/launcher/ui/MainScreen.kt +++ b/app/src/main/java/com/steamdeck/launcher/ui/MainScreen.kt @@ -1,279 +1,24 @@ package com.steamdeck.launcher.ui -import androidx.compose.foundation.Image -import androidx.compose.foundation.background -import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.BoxWithConstraints -import androidx.compose.foundation.layout.PaddingValues -import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.FlowRow -import androidx.compose.foundation.layout.ExperimentalLayoutApi import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.layout.systemBarsPadding -import androidx.compose.foundation.layout.width -import androidx.compose.foundation.layout.widthIn -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.Settings -import androidx.compose.material3.Icon import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll import androidx.compose.material3.AlertDialog -import androidx.compose.material3.Button -import androidx.compose.material3.LinearProgressIndicator import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.OutlinedButton import androidx.compose.material3.Text import androidx.compose.material3.TextButton import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp -import com.steamdeck.launcher.R - -class MainUiState( - val installed: String?, - val ready: Boolean, - val available: String?, - val availableSize: String?, - val busy: Boolean, - val stage: String, - val percent: Int, - val failed: Boolean, - val frameGenLabel: String, - val desktopInstalled: Boolean, - val offlineAccount: String?, - val offline: Boolean, - val romsDir: String?, - val logsEnabled: Boolean, - val emulators: List> = emptyList(), -) - -@OptIn(ExperimentalLayoutApi::class) -@Composable -fun MainScreen( - state: MainUiState, - onPlay: () -> Unit, - onDesktop: () -> Unit, - onApps: () -> Unit, - onRuntime: () -> Unit, - onFrameGen: () -> Unit, - onProtons: () -> Unit, - onSteamSettings: () -> Unit, - onDesktopSettings: () -> Unit, - onPerformance: () -> Unit, - onOffline: () -> Unit, - onRoms: () -> Unit, - onFiles: () -> Unit, - onLaunchEmulator: (path: String) -> Unit, - onEmulatorHelp: () -> Unit, - onLogs: () -> Unit, - onCredits: () -> Unit, -) { - BoxWithConstraints( - modifier = Modifier - .fillMaxSize() - .background(MaterialTheme.colorScheme.background) - .systemBarsPadding(), - contentAlignment = Alignment.Center, - ) { - val designHeight = 480.dp - val k = (maxHeight / designHeight).coerceIn(0.55f, 1f) - val colors = MaterialTheme.colorScheme - - Column( - horizontalAlignment = Alignment.CenterHorizontally, - modifier = Modifier - .widthIn(max = 420.dp * k) - .fillMaxWidth() - .verticalScroll(rememberScrollState()) - .padding(horizontal = 24.dp * k, vertical = 12.dp * k), - ) { - Image( - painter = painterResource(R.drawable.logo), - contentDescription = null, - modifier = Modifier.size(72.dp * k), - ) - Spacer(Modifier.height(6.dp * k)) - Text("SteamDeck", fontSize = 22.sp * k, color = colors.onBackground) - Spacer(Modifier.height(14.dp * k)) - - val status = when { - state.busy -> "Working…" - !state.ready -> "Linux runtime not installed" - else -> "Ready" - } - Text(status, fontSize = 15.sp * k, color = colors.onBackground) - val detail = when { - state.busy -> if (state.percent >= 0) "${state.stage} ${state.percent}%" else state.stage - state.failed -> "Install failed. Nothing changed." - !state.ready -> state.available?.let { "$it · ${state.availableSize} download, one time" } - ?: "Could not reach the runtime catalog" - state.available != null && state.available != state.installed -> - "Runtime ${state.installed ?: "?"} installed · ${state.available} available" - else -> null - } - if (detail != null) Text( - detail, fontSize = 12.sp * k, color = colors.onSurfaceVariant, textAlign = TextAlign.Center, - modifier = Modifier.padding(top = 3.dp * k), - ) - if (state.busy) { - Spacer(Modifier.height(6.dp * k)) - if (state.percent >= 0) { - LinearProgressIndicator(progress = { state.percent / 100f }, modifier = Modifier.fillMaxWidth()) - } else { - LinearProgressIndicator(modifier = Modifier.fillMaxWidth()) - } - } - Spacer(Modifier.height(14.dp * k)) - - val buttonHeight = 40.dp * k - Row(horizontalArrangement = Arrangement.spacedBy(6.dp * k), modifier = Modifier.fillMaxWidth()) { - Button( - onClick = onPlay, enabled = state.ready && !state.busy, - modifier = Modifier.weight(1f).height(buttonHeight), - ) { Text("Play", fontSize = 14.sp * k) } - CogButton(k, onSteamSettings) - } - Row(horizontalArrangement = Arrangement.spacedBy(6.dp * k), modifier = Modifier.fillMaxWidth().padding(top = 5.dp * k)) { - OutlinedButton( - onClick = onDesktop, enabled = state.ready && !state.busy && state.desktopInstalled, - modifier = Modifier.weight(1f).height(buttonHeight), - ) { Text("Desktop", fontSize = 13.sp * k, maxLines = 1, overflow = TextOverflow.Ellipsis) } - CogButton(k, onDesktopSettings) - } - FlowRow( - horizontalArrangement = Arrangement.spacedBy(4.dp * k), - verticalArrangement = Arrangement.spacedBy(4.dp * k), - modifier = Modifier.fillMaxWidth().padding(top = 5.dp * k), - ) { - OutlinedButton( - onClick = onRoms, - contentPadding = PaddingValues(horizontal = 10.dp * k, vertical = 0.dp), - modifier = Modifier.height(30.dp * k), - ) { - Text( - "ROMs: " + (state.romsDir?.substringAfterLast('/')?.ifEmpty { state.romsDir } ?: "choose folder"), - fontSize = 11.sp * k, maxLines = 1, overflow = TextOverflow.Ellipsis, - ) - } - for ((label, path) in state.emulators) { - OutlinedButton( - onClick = { onLaunchEmulator(path) }, enabled = state.ready && !state.busy, - contentPadding = PaddingValues(horizontal = 10.dp * k, vertical = 0.dp), - modifier = Modifier.height(30.dp * k), - ) { Text(label, fontSize = 11.sp * k, maxLines = 1) } - } - OutlinedButton( - onClick = onEmulatorHelp, - contentPadding = PaddingValues(0.dp), - modifier = Modifier.width(30.dp * k).height(30.dp * k), - ) { Text("?", fontSize = 12.sp * k) } - } - OutlinedButton( - onClick = onOffline, enabled = state.offlineAccount != null, - modifier = Modifier.fillMaxWidth().padding(top = 5.dp * k).height(buttonHeight), - ) { - Text( - when { - state.offlineAccount == null -> "Start offline" - state.offline -> "Start offline: on · ${state.offlineAccount}" - else -> "Start offline: off" - }, - fontSize = 13.sp * k, - ) - } - Spacer(Modifier.height(8.dp * k)) - - val runtimeLabel = when { - state.busy -> "Working…" - !state.ready -> "Install Linux runtime" - state.available != null && state.available != state.installed -> "Update Linux runtime" - else -> "Remove Linux runtime" - } - val tileGap = Arrangement.spacedBy(6.dp * k) - Row(horizontalArrangement = tileGap, modifier = Modifier.fillMaxWidth()) { - MenuTile(runtimeLabel, null, k, Modifier.weight(1f), enabled = !state.busy, onClick = onRuntime) - MenuTile("Desktop & apps", null, k, Modifier.weight(1f), enabled = state.ready && !state.busy, onClick = onApps) - } - Spacer(Modifier.height(6.dp * k)) - Row(horizontalArrangement = tileGap, modifier = Modifier.fillMaxWidth()) { - MenuTile("Frame generation", state.frameGenLabel, k, Modifier.weight(1f), onClick = onFrameGen) - MenuTile("Compatibility tools", null, k, Modifier.weight(1f), onClick = onProtons) - } - Spacer(Modifier.height(6.dp * k)) - Row(horizontalArrangement = tileGap, modifier = Modifier.fillMaxWidth()) { - MenuTile("Performance", null, k, Modifier.weight(1f), onClick = onPerformance) - MenuTile("Files", null, k, Modifier.weight(1f), onClick = onFiles) - } - Spacer(Modifier.height(6.dp * k)) - Row(modifier = Modifier.fillMaxWidth()) { - MenuTile( - "Session logs", if (state.logsEnabled) "on · Download/SteamDeck" else "off", - k, Modifier.weight(1f), onClick = onLogs, - ) - } - Spacer(Modifier.height(10.dp * k)) - Text( - "by The412Banner and maxjivi05 · credits", - fontSize = 11.sp * k, color = colors.onSurfaceVariant, - modifier = Modifier.clickable(onClick = onCredits).padding(4.dp), - ) - } - } -} - -@Composable -private fun CogButton(k: Float, onClick: () -> Unit) { - OutlinedButton( - onClick = onClick, - modifier = Modifier.width(44.dp * k).height(40.dp * k), - contentPadding = PaddingValues(0.dp), - ) { Icon(Icons.Filled.Settings, contentDescription = "Settings", modifier = Modifier.size(18.dp * k)) } -} - -@Composable -private fun MenuTile( - title: String, - value: String?, - k: Float, - modifier: Modifier, - enabled: Boolean = true, - onClick: () -> Unit, -) { - OutlinedButton( - onClick = onClick, enabled = enabled, - modifier = modifier.height(if (value != null) 52.dp * k else 44.dp * k), - contentPadding = PaddingValues(horizontal = 8.dp * k, vertical = 4.dp * k), - ) { - Column(horizontalAlignment = Alignment.CenterHorizontally) { - Text(title, fontSize = 12.sp * k, maxLines = 1, overflow = TextOverflow.Ellipsis, textAlign = TextAlign.Center) - if (value != null) { - Text( - value, fontSize = 10.sp * k, maxLines = 1, overflow = TextOverflow.Ellipsis, - color = MaterialTheme.colorScheme.onSurfaceVariant, textAlign = TextAlign.Center, - ) - } - } - } -} @Composable fun EmulatorHelpDialog(onDismiss: () -> Unit) { AlertDialog( onDismissRequest = onDismiss, title = { Text("Emulators") }, - text = { Text("Place games in the ROMs folder. In RPCS3, install firmware from File > Install Firmware.") }, + text = { Text("Put games in /root/ROMs. Install RPCS3 firmware from File > Install Firmware.") }, confirmButton = { TextButton(onClick = onDismiss) { Text("OK") } }, ) } @@ -296,14 +41,14 @@ fun CreditsDialog(onDismiss: () -> Unit) { title = { Text("Credits") }, text = { Column(modifier = Modifier.verticalScroll(rememberScrollState())) { - Text("The412Banner - the app, the Wayland compositor, the runtime and the Steam session.", style = MaterialTheme.typography.bodyMedium) + Text("The412Banner: app, compositor, runtime, and Steam session.", style = MaterialTheme.typography.bodyMedium) Spacer(Modifier.height(10.dp)) - Text("maxjivi05 (Max) - the gamescope runtime this is built on: the proot session, the session shim, the fake-evdev interposer and the controller work, from WinNative.", style = MaterialTheme.typography.bodyMedium) + Text("maxjivi05: gamescope runtime and controller support, based on WinNative.", style = MaterialTheme.typography.bodyMedium) Spacer(Modifier.height(10.dp)) - Text("Winlator (brunodev85) - the audio stack, the gamepad model and the shape of a session's host-side components; WinNative and Bannerlator are both Winlator lineage.", style = MaterialTheme.typography.bodyMedium) + Text("Winlator (brunodev85): audio, gamepad, and session components.", style = MaterialTheme.typography.bodyMedium) Spacer(Modifier.height(10.dp)) Text( - "GPL-3.0. Valve, Steam, Steam Deck and Proton are trademarks of Valve Corporation; this project is not affiliated with Valve. gamescope, Mesa, Turnip, Xwayland, PulseAudio, proot and Arch Linux ARM are their authors' own.", + "GPL-3.0. Steam, Steam Deck, and Proton are Valve trademarks. Not affiliated with Valve. Third-party software remains under its authors' licenses.", style = MaterialTheme.typography.bodySmall, color = MaterialTheme.colorScheme.onSurfaceVariant, ) 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 39123b955..ad02feb5f 100644 --- a/app/src/main/java/com/steamdeck/launcher/ui/ModeSettingsDialog.kt +++ b/app/src/main/java/com/steamdeck/launcher/ui/ModeSettingsDialog.kt @@ -1,8 +1,6 @@ package com.steamdeck.launcher.ui import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.material3.MaterialTheme @@ -62,25 +60,22 @@ class ModeSettingsActions( fun ModeSettingsPage(s: ModeSettings, a: ModeSettingsActions) { val steam = s.mode == SessionService.MODE_STEAM val host = rememberMenuHost() - val colors = MaterialTheme.colorScheme SettingsPage( host, - eyebrow = if (steam) "Steam · settings" else "Desktop · settings", title = if (steam) "Steam session" else "Desktop session", - lede = "Each value opens where it is. Most take effect at the next session start; the ones that need the app closed say so.", onBack = a.onDismiss, ) { SettingsGroup("Display") { val default = SessionPrefs.defaultResolutionCap(s.mode) ChoiceRow( - host, "res", "Resolution", "Takes effect at the next session: gamescope sizes its display once, when it starts.", + 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 keeps the client's menus responsive; above 1080p costs frames for nothing a handheld can show.", + s.resolutionCap, note = "720p can improve menu responsiveness.", onPick = a.onResolution, ) ChoiceRow( - host, "shape", "Shape", "16:9 is for a foldable: bars on either panel instead of a squashed picture.", + 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, ) } @@ -88,52 +83,52 @@ fun ModeSettingsPage(s: ModeSettings, a: ModeSettingsActions) { ToggleRow( host, "hdr", "HDR10 output", s.hdrReason?.let { "Not available: $it." } - ?: "Decided once when the compositor starts: a change applies after the app is fully closed and opened again.", + ?: "Restart the app to apply.", checked = s.hdr && s.hdrReason == null, enabled = s.hdrReason == null, onChange = a.onHdr, ) } SettingsGroup("Drivers") { DriverRowMenu( host, "rt", "Runtime driver", - "What " + (if (steam) "the Steam client and its games" else "the desktop's programs") + " render with inside the runtime. Applies at the next session start.", - s.linuxRows, s.linuxSelected, importLabel = "Import a \"-Linux\" Turnip zip…", + (if (steam) "Used by Steam and games." else "Used by desktop apps.") + " Applies next session.", + s.linuxRows, s.linuxSelected, importLabel = "Import Turnip zip…", onSelect = a.onSelectLinux, onRemove = a.onRemoveLinux, onImport = a.onImportLinux, ) DriverRowMenu( host, "panel", "Display driver", - "What the app's compositor puts frames on the screen with; shared by both modes. Applies after the app is fully closed and opened again.", + "Used by the compositor in both modes. Restart the app to apply.", s.androidRows, s.androidSelected, importLabel = "Import an AdrenoTools zip…", onSelect = a.onSelectAndroid, onRemove = a.onRemoveAndroid, onImport = a.onImportAndroid, ) } SettingsGroup(if (steam) "Touch & controls" else "Touch") { ChoiceRow( - host, "touch", "Touch", "How a finger drives the pointer. Also in the session's drawer.", + host, "touch", "Touch", null, listOf("auto" to "Auto", "touchpad" to "Touchpad", "direct" to "Direct"), s.touchMode, - note = "Auto: touchpad on the desktop, direct in Steam. Touchpad: drag moves, tap clicks. Direct: the pointer jumps under the finger.", + note = "Auto uses touchpad on desktop and direct input in Steam. Touchpad: drag to move, tap to click.", onPick = a.onTouch, ) if (steam && s.oscMode != null) ChoiceRow( - host, "osc", "On-screen controls", "The virtual pad drawn over a game.", + host, "osc", "On-screen controls", null, listOf( SessionPrefs.OSC_AUTO to "Auto", SessionPrefs.OSC_ALWAYS to "Always", SessionPrefs.OSC_STEAM_QAM to "Steam + QAM", SessionPrefs.OSC_NEVER to "Never", ), s.oscMode, - note = "Auto shows the full pad when no controller is attached. Steam + QAM shows only those two buttons.", onPick = a.onOsc, + note = "Auto shows all controls without a controller. Steam + QAM shows only those buttons.", onPick = a.onOsc, ) } if (steam && s.fexPreset != null) SettingsGroup("Games") { ChoiceRow( - host, "fex", "FEX preset", "How FEX translates the x86 games the client launches. Applies to the next game launch. Also in the session's drawer.", + host, "fex", "FEX preset", "Applies on next game launch.", FexPreset.all.map { it.id to it.label }, s.fexPreset, note = FexPreset.byId(s.fexPreset).detail, onPick = a.onFexPreset, ) } if (steam && s.directAudio != null && s.mic != null) SettingsGroup("Audio") { - ToggleRow(host, "da", "DirectAudio for games", "Games play straight to the device, bypassing PulseAudio: lower latency. Off = PulseAudio for everything.", s.directAudio, onChange = a.onDirectAudio) - ToggleRow(host, "mic", "Microphone", "The device's microphone for voice chat, as the client's input device. Asks for the permission once.", s.mic, onChange = a.onMic) + ToggleRow(host, "da", "DirectAudio for games", "Bypasses PulseAudio for lower latency.", s.directAudio, onChange = a.onDirectAudio) + ToggleRow(host, "mic", "Microphone", "Uses the device microphone for voice chat.", s.mic, onChange = a.onMic) } if (steam && s.gameStorage != null) SettingsGroup("Game storage") { val custom = s.gameStorage.isNotEmpty() && s.gameStorage != "off" && s.storageOptions.none { it.second == s.gameStorage } @@ -146,14 +141,14 @@ fun ModeSettingsPage(s: ModeSettings, a: ModeSettingsActions) { val open = host.open == "storage" SettingsRow( "Second library", - "With a second place registered, every Install in Steam asks which drive, Settings › Storage lists both, and Steam moves games between them. Applies at the next session start.", + "Adds a library location in Steam. Applies next session.", highlighted = open, ) { androidx.compose.foundation.layout.Box { ValueChip(options.firstOrNull { it.first == s.gameStorage }?.second?.substringBefore(" -") ?: "-", open) { host.open = if (open) null else "storage" } AnchoredMenu( open, onDismiss = { if (host.open == "storage") host.open = null }, title = "Second library", - note = "An SD card and shared storage go through Android's file layer: a game that streams big assets from there can stall. Keep such games internal.", + note = "Games that stream assets from SD or shared storage may stutter. Keep them internal.", ) { for ((path, label) in options) MenuItem(label, checked = path == s.gameStorage) { a.onGameStorage(path, if (path.isEmpty() || path == "off") "" else label.substringBefore(" ·")) @@ -166,13 +161,11 @@ fun ModeSettingsPage(s: ModeSettings, a: ModeSettingsActions) { } if (!steam && s.renderer != null) SettingsGroup("Renderer") { ChoiceRow( - host, "renderer", "Desktop renderer", "How labwc composites the desktop. pixman is software and works everywhere.", + host, "renderer", "Desktop renderer", "Composites the desktop.", listOf("pixman" to "pixman - software", "gles2" to "gles2", "vulkan" to "vulkan"), s.renderer, - note = "gles2 and vulkan need a real DRM render node, which the Adreno stand-in is not on most devices.", onPick = a.onRenderer, + note = "GLES2 and Vulkan require a DRM render node, unavailable on most devices.", onPick = a.onRenderer, ) } - Spacer(Modifier.height(8.dp)) - Text("Values are saved as they are picked; there is nothing to confirm.", fontSize = 11.5.sp, color = colors.onSurfaceVariant, modifier = Modifier.padding(top = 10.dp)) } } diff --git a/app/src/main/java/com/steamdeck/launcher/ui/PerformanceDialog.kt b/app/src/main/java/com/steamdeck/launcher/ui/PerformanceDialog.kt index 4bb6c09ea..5decb0782 100644 --- a/app/src/main/java/com/steamdeck/launcher/ui/PerformanceDialog.kt +++ b/app/src/main/java/com/steamdeck/launcher/ui/PerformanceDialog.kt @@ -49,64 +49,64 @@ fun PerformancePage( val colors = MaterialTheme.colorScheme val coreItems = cores.map { it.core to it.label } SettingsPage( - host, eyebrow = "Setup · performance", title = "Performance", - lede = "Which cores the client and its games may run on, and the fixes for a device the runtime does not sit well on. All apply at the next session start.", + host, title = "Performance", + lede = "CPU cores and session fixes. Applies next session.", onBack = onDismiss, ) { SettingsGroup("Steam client cores") { ToggleRow( host, "override", "Override Steam's own core choice", - "Steam pins its interface to a subset of cores it picks - on one device 5 of 8, leaving out the fastest - which suits a running game and makes the menus sluggish when the client is all there is. On: the client, its UI helper and gamescope are pinned to the cores below instead, re-applied every few seconds.", + "Pins Steam, its UI helper, and gamescope to the cores below. Reapplied every few seconds.", clientOverride, onChange = onClientOverride, ) MultiRow( - host, "clientCores", "Client cores", if (clientOverride) "Every core ticked is the usual fix." else "Turn the override on to choose.", - coreItems, clientCores, enabled = clientOverride, note = "Tick or untick as many as you like; the menu stays open.", onToggle = onClientCore, + host, "clientCores", "Client cores", if (clientOverride) "Choose cores for Steam." else "Enable the override to choose.", + coreItems, clientCores, enabled = clientOverride, onToggle = onClientCore, ) } SettingsGroup("Game cores") { MultiRow( host, "gameCores", "Game cores", - "Applied by exec'ing the game through taskset, so every thread inherits the mask. Every core ticked sends nothing - that is what the scheduler does unaided. Untick the small cores to keep a heavy game off them.", - coreItems, gameCores, note = "Tick or untick as many as you like; the menu stays open.", onToggle = onGameCore, + "Choose cores for games. All selected lets Android schedule across every core.", + coreItems, gameCores, onToggle = onGameCore, ) } SettingsGroup("Client interface") { ToggleRow( host, "glthread", "Threaded GL", - "The client's menus are drawn Chromium → ANGLE → Zink → Turnip, and that chain is what limits them. mesa_glthread marshals GL off the calling thread, the shape of this bottleneck. On by default; none of these four is device-proven yet.", + "May improve Steam menu responsiveness. Device impact is unverified.", glThread, onChange = onGlThread, ) ToggleRow( host, "zink", "Zink: lazy descriptors", - "Lazy descriptor updates is the mode Zink recommends on drivers without descriptor buffers. On by default.", + "Recommended for drivers without descriptor buffers.", zinkLazy, onChange = onZinkLazy, ) ToggleRow( host, "noglerror", "Skip GL error checks", - "MESA_NO_ERROR: the driver stops validating every GL call. On by default.", + "Disables per-call GL validation.", noGlError, onChange = onNoGlError, ) ToggleRow( host, "deck", "Steam Deck mode", - "Runs the client as SteamOS runs its own session (-steamdeck -steamos3), the shape Valve tunes Big Picture for. Off by default: the client then expects Deck hardware that is not here, and that cost is untested.", + "Uses SteamOS launch flags. Untested on non-Deck hardware.", steamDeckMode, onChange = onSteamDeckMode, ) } SettingsGroup("Session fixes") { ToggleRow( host, "sysmem", "Turnip: sysmem rendering", - "Renders without the GPU's tile memory (TU_DEBUG=sysmem). A fix for an Adreno 8xx that shows corruption, and required on a 710/720/722 (set on its own there). Not a speed setting: elsewhere it COSTS frames - leave it off unless the picture is wrong.", + "Required on Adreno 710/720/722. May fix corruption on other Adreno GPUs, but can reduce performance.", tuSysmem, onChange = onTuSysmem, ) ToggleRow( host, "xalia", "Skip Steam's xalia helper", - "xalia is a Windows program Proton starts for gamepad navigation in Windows programs. On some devices its system calls are refused in a way it cannot cope with and the session dies seconds after Big Picture appears. Turn on if a session will not stay up.", + "Disables Proton's gamepad navigation helper. Try if sessions crash at startup.", noXalia, onChange = onNoXalia, ) ToggleRow( host, "seccomp", "Run proot without seccomp", - "proot normally lets most system calls run untraced, which is most of its speed. Some kernels handle that badly and refuse calls that plainly exist (\"Function not implemented\" in the log). This traces everything: slower, but correct.", + "May fix missing syscall errors on some kernels, but can reduce performance.", prootNoSeccomp, onChange = onProotNoSeccomp, ) } diff --git a/app/src/main/java/com/steamdeck/launcher/ui/ProtonDialog.kt b/app/src/main/java/com/steamdeck/launcher/ui/ProtonDialog.kt index be400cab6..dd5b34e73 100644 --- a/app/src/main/java/com/steamdeck/launcher/ui/ProtonDialog.kt +++ b/app/src/main/java/com/steamdeck/launcher/ui/ProtonDialog.kt @@ -31,7 +31,7 @@ fun ProtonDialog( text = { Column { Text( - "Native ARM64 Proton builds beside the ARM64 Proton Valve ships. Installed when a session starts - a large download - and then chosen per game in Steam under Properties → Compatibility.", + "Adds ARM64 Proton builds. Downloads at next session start; select per game in Steam > Properties > Compatibility.", style = MaterialTheme.typography.bodySmall, color = MaterialTheme.colorScheme.onSurfaceVariant, ) diff --git a/app/src/main/java/com/steamdeck/launcher/ui/RomsDialog.kt b/app/src/main/java/com/steamdeck/launcher/ui/RomsDialog.kt index 329771598..d41f69afb 100644 --- a/app/src/main/java/com/steamdeck/launcher/ui/RomsDialog.kt +++ b/app/src/main/java/com/steamdeck/launcher/ui/RomsDialog.kt @@ -19,20 +19,18 @@ fun RomsDialog(path: String?, onChoose: () -> Unit, onClear: () -> Unit, onDismi text = { Column { Text( - "A folder on this device that every session shows as ROMs in the home folder " + - "(/root/ROMs) - open it from any emulator's file dialog. All of internal " + - "storage is there too, as Storage.", + "This folder appears as /root/ROMs in each session. Internal storage is available at /root/Storage.", style = MaterialTheme.typography.bodyMedium, ) Spacer(Modifier.height(10.dp)) Text( - path ?: "No folder chosen yet.", + path ?: "No folder chosen", style = MaterialTheme.typography.bodySmall, color = if (path != null) MaterialTheme.colorScheme.onSurface else MaterialTheme.colorScheme.onSurfaceVariant, ) Spacer(Modifier.height(6.dp)) Text( - "Takes effect at the next session. A folder on an SD card works too.", + "Applies next session. SD cards are supported.", style = MaterialTheme.typography.bodySmall, color = MaterialTheme.colorScheme.onSurfaceVariant, ) diff --git a/app/src/main/java/com/steamdeck/launcher/ui/SettingsWidgets.kt b/app/src/main/java/com/steamdeck/launcher/ui/SettingsWidgets.kt index 3898b1b85..261f97f2a 100644 --- a/app/src/main/java/com/steamdeck/launcher/ui/SettingsWidgets.kt +++ b/app/src/main/java/com/steamdeck/launcher/ui/SettingsWidgets.kt @@ -265,7 +265,14 @@ fun ActionRow(label: String, hint: String?, button: String, onClick: () -> Unit) } @Composable -fun SettingsPage(host: MenuHost, eyebrow: String, title: String, lede: String, onBack: () -> Unit, content: @Composable ColumnScope.() -> Unit) { +fun SettingsPage( + host: MenuHost, + title: String, + onBack: () -> Unit, + eyebrow: String? = null, + lede: String? = null, + content: @Composable ColumnScope.() -> Unit, +) { val colors = MaterialTheme.colorScheme val dim by animateFloatAsState(if (host.open != null) 0.6f else 1f, Motion.tw(220), label = "pageDim") Column(modifier = Modifier.fillMaxSize().padding(horizontal = 22.dp, vertical = 18.dp)) { @@ -275,12 +282,14 @@ fun SettingsPage(host: MenuHost, eyebrow: String, title: String, lede: String, o "‹ Back", fontSize = 12.sp, fontWeight = FontWeight.SemiBold, color = colors.onSurfaceVariant, modifier = Modifier.clip(RoundedCornerShape(8.dp)).clickable(onClick = onBack).padding(horizontal = 6.dp, vertical = 4.dp), ) - Spacer(Modifier.width(10.dp)) - Eyebrow(eyebrow) + if (eyebrow != null) { + Spacer(Modifier.width(10.dp)) + Eyebrow(eyebrow) + } } } Rise(1) { Title(title) } - Rise(2) { Lede(lede) } + if (lede != null) Rise(2) { Lede(lede) } Rise(3, Modifier.weight(1f).fillMaxWidth()) { Column(modifier = Modifier.fillMaxSize().graphicsLayer { alpha = dim }.verticalScroll(rememberScrollState()).padding(bottom = 24.dp)) { content() } } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 8a1a7225e..c4cd58ae5 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1,51 +1,14 @@ SteamDeck - Valve\'s native ARM64 Steam client, under gamescope - Play - Ready - Working… - Linux runtime not installed - Install Linux runtime - Update Linux runtime - Remove Linux runtime - Remove - This deletes the runtime, the Steam client inside it, and every game installed there. - Runtime %1$s installed - Runtime %1$s installed · %2$s available - %1$s · %2$s download, one time - Could not reach the runtime catalog - Install failed - nothing was changed - Starting the session… - Steam is loading - %1$s elapsed · still working - Downloading the Steam client update · %1$d%% - Downloading the Steam client · %1$s - The first run downloads the Steam client from Valve and then its update - a few minutes on a good connection. - The screen stays black until the client draws its first frame; nothing is stuck while this clock is moving. - You can leave. Steam keeps running in the background and the notification brings you back. - A controller shows up in Steam as an Xbox 360 pad. Without one, the on-screen pad appears. - Games you install go through Valve\'s own ARM64 Proton; the client fetches it the first time it is needed. + First run downloads Steam. This may take a few minutes. + Steam keeps running in the background. + Your controller appears as an Xbox 360 pad. Otherwise, use the on-screen controls. - The session ended (%1$d)\n%2$s Session - Keeps Steam running while the app is in the background - Steam is running - leave this notification alone + Keeps Steam running in the background + Steam is running Stop session - Frame generation: %1$s Frame generation - Extra frames are generated between the real ones on the way to the screen, so a game running at 30 fps looks like 60. Win-FG is built in. LSFG uses the shaders from your own copy of Lossless Scaling - install it from the Steam client in this app first. Takes effect immediately, even mid-game. - Performance HUD - On-screen controls: %1$s - Display shape: %1$s · next session - panel - 16:9 - Send to background - auto - always - never - by The412Banner and maxjivi05 · credits - Credits - SteamDeck\n\nThe412Banner - the app, the Wayland compositor, the runtime and the Steam session.\n\nmaxjivi05 (Max) - the gamescope runtime this is built on: the proot session, the session shim, the fake-evdev interposer and the controller work, from WinNative.\n\nGPL-3.0. Valve, Steam, Steam Deck and Proton are trademarks of Valve Corporation; this project is not affiliated with Valve. gamescope, Mesa, Turnip, Xwayland, PulseAudio, proot and Arch Linux ARM are their authors\' own.