diff --git a/app/src/main/java/com/droiddeck/launcher/MainActivity.kt b/app/src/main/java/com/droiddeck/launcher/MainActivity.kt index abd38b87c..f9d5aedb0 100644 --- a/app/src/main/java/com/droiddeck/launcher/MainActivity.kt +++ b/app/src/main/java/com/droiddeck/launcher/MainActivity.kt @@ -144,6 +144,7 @@ class MainActivity : ComponentActivity() { private var directAudio by mutableStateOf(false) private var clientDirectAudio by mutableStateOf(false) private var forceFullscreen by mutableStateOf(true) + private var stretch16x9 by mutableStateOf(false) private var launcherFullscreen by mutableStateOf(true) private var storeEnabled by mutableStateOf(false) private var appImagesEnabled by mutableStateOf(false) @@ -961,6 +962,7 @@ class MainActivity : ComponentActivity() { directAudio = if (mode == SessionService.MODE_STEAM) directAudio else null, clientDirectAudio = clientDirectAudio, forceFullscreen = if (mode == SessionService.MODE_STEAM) forceFullscreen else null, + stretch16x9 = if (mode == SessionService.MODE_STEAM) stretch16x9 else null, mic = if (mode == SessionService.MODE_STEAM) mic else null, renderer = if (mode == SessionService.MODE_DESKTOP) renderer else null, gameStorage = if (mode == SessionService.MODE_STEAM) gameStorage else null, @@ -998,6 +1000,7 @@ class MainActivity : ComponentActivity() { onDirectAudio = { on -> SessionPrefs.setDirectAudio(this, on); directAudio = on }, onClientDirectAudio = { on -> SessionPrefs.setClientDirectAudio(this, on); clientDirectAudio = on }, onForceFullscreen = { on -> SessionPrefs.setForceFullscreen(this, on); forceFullscreen = on }, + onStretch16x9 = { on -> SessionPrefs.setStretch16x9(this, on); stretch16x9 = on }, onMic = { on -> SessionPrefs.setMicEnabled(this, on) mic = on @@ -1126,6 +1129,7 @@ class MainActivity : ComponentActivity() { directAudio = SessionPrefs.directAudio(this) clientDirectAudio = SessionPrefs.clientDirectAudio(this) forceFullscreen = SessionPrefs.forceFullscreen(this) + stretch16x9 = SessionPrefs.stretch16x9(this) mic = SessionPrefs.micEnabled(this) renderer = SessionPrefs.desktopRenderer(this) gameStorage = SessionPrefs.gameStorage(this) diff --git a/app/src/main/java/com/droiddeck/launcher/core/DeviceReport.kt b/app/src/main/java/com/droiddeck/launcher/core/DeviceReport.kt index 457e49cf1..1baa51413 100644 --- a/app/src/main/java/com/droiddeck/launcher/core/DeviceReport.kt +++ b/app/src/main/java/com/droiddeck/launcher/core/DeviceReport.kt @@ -159,6 +159,7 @@ object DeviceReport { k("Guest host name", SessionPrefs.guestHostname(context)) k("DirectAudio for games", SessionPrefs.directAudio(context)) k("Stretch games to fill", SessionPrefs.forceFullscreen(context)) + k("Stretch 16:9 to panel", SessionPrefs.stretch16x9(context)) k("Client audio", if (SessionPrefs.clientDirectAudio(context)) "DirectAudio" else "classic") k("Microphone", SessionPrefs.micEnabled(context)) k("On-screen controls", SessionPrefs.oscMode(context)) diff --git a/app/src/main/java/com/droiddeck/launcher/session/SessionPrefs.kt b/app/src/main/java/com/droiddeck/launcher/session/SessionPrefs.kt index c99943851..b7fcb3555 100644 --- a/app/src/main/java/com/droiddeck/launcher/session/SessionPrefs.kt +++ b/app/src/main/java/com/droiddeck/launcher/session/SessionPrefs.kt @@ -184,6 +184,12 @@ object SessionPrefs { writeForceFullscreenFlag(context) } + fun stretch16x9(context: Context): Boolean = prefs(context).getBoolean("stretch16x9", false) + + fun setStretch16x9(context: Context, on: Boolean) { + prefs(context).edit().putBoolean("stretch16x9", on).apply() + } + /** * The same choice as a file the running session watches, so the drawer can change it live: * the session hands every change to gamescope, which reads GAMESCOPE_FORCE_WINDOWS_FULLSCREEN diff --git a/app/src/main/java/com/droiddeck/launcher/session/SessionService.kt b/app/src/main/java/com/droiddeck/launcher/session/SessionService.kt index bb4a7dd89..413cc386e 100644 --- a/app/src/main/java/com/droiddeck/launcher/session/SessionService.kt +++ b/app/src/main/java/com/droiddeck/launcher/session/SessionService.kt @@ -540,6 +540,7 @@ class SessionService : Service() { if (steamHere) guest.add("BL_STEAM_CHANNEL=" + SessionPrefs.steamChannel(this)) if (SessionState.mode == MODE_STEAM) { guest.add("BL_GAMESCOPE_FORCE_FULLSCREEN=" + (if (SessionPrefs.forceFullscreen(this)) "1" else "0")) + guest.add("BL_GAMESCOPE_STRETCH_16X9=" + (if (SessionPrefs.stretch16x9(this)) "1" else "0")) SessionPrefs.writeForceFullscreenFlag(this) } // Proton's own gate for its xalia helper (its `proton` script reads this, and sets diff --git a/app/src/main/java/com/droiddeck/launcher/ui/ModeSettingsDialog.kt b/app/src/main/java/com/droiddeck/launcher/ui/ModeSettingsDialog.kt index 43494d9b8..7a0b1a174 100644 --- a/app/src/main/java/com/droiddeck/launcher/ui/ModeSettingsDialog.kt +++ b/app/src/main/java/com/droiddeck/launcher/ui/ModeSettingsDialog.kt @@ -70,6 +70,7 @@ class ModeSettings( val fexPreset: String? = null, /** Steam only: games are stretched to fill the screen (null = not a Steam page). */ val forceFullscreen: Boolean? = null, + val stretch16x9: Boolean? = null, /** Steam only: the client branch forced on the command line. */ val steamChannel: String? = null, /** Steam only: enable the SteamOS client interface and its performance controls. */ @@ -121,6 +122,7 @@ class ModeSettingsActions( val onPickGameStorageFolder: () -> Unit = {}, val onFexPreset: (String) -> Unit = {}, val onForceFullscreen: (Boolean) -> Unit = {}, + val onStretch16x9: (Boolean) -> Unit = {}, val onSteamChannel: (String) -> Unit = {}, val onSteamDeckMode: (Boolean) -> Unit = {}, val onMangoapp: (Boolean) -> Unit = {}, @@ -376,6 +378,11 @@ fun ModeSettingsPage(s: ModeSettings, a: ModeSettingsActions) { "Keeps games that resize their own window (FlatOut) full screen. Turn it off if a game shows up small in a corner (Quake 3). Applies next session.", s.forceFullscreen, onChange = a.onForceFullscreen, ) + if (s.stretch16x9 != null) ToggleRow( + host, "stretch169", "Stretch 16:9 games to a narrower screen", + "On a screen squarer than 16:9 (a foldable's inner panel), games render at 16:9 and are stretched to fill it: no bars, slightly wider-looking. Applies next session.", + s.stretch16x9, onChange = a.onStretch16x9, + ) } if (steam && s.directAudio != null && s.mic != null) SettingsGroup("Audio") { ToggleRow(host, "da", "DirectAudio for games", "Bypasses PulseAudio for lower latency in games.", s.directAudio, onChange = a.onDirectAudio) diff --git a/tools/linuxfs/overlay/usr/local/bin/bannerlator-session b/tools/linuxfs/overlay/usr/local/bin/bannerlator-session index 89c35ce3e..1f55e3089 100755 --- a/tools/linuxfs/overlay/usr/local/bin/bannerlator-session +++ b/tools/linuxfs/overlay/usr/local/bin/bannerlator-session @@ -140,7 +140,16 @@ if [ -z "${BL_INSIDE:-}" ]; then fi width=${BL_WIDTH:-1280} height=${BL_HEIGHT:-720} - args=(--backend wayland --expose-wayland -f -W "$width" -H "$height" -w "$width" -h "$height") + nested=$width + if [ "$mode" = steam ] && [ "${BL_GAMESCOPE_STRETCH_16X9:-0}" = 1 ]; then + wide=$(( (height * 16 + 4) / 9 / 2 * 2 )) + if [ "$wide" -gt "$width" ]; then + nested=$wide + echo "== gamescope: games at ${nested}x${height} stretched to the ${width}x${height} output" + fi + fi + args=(--backend wayland --expose-wayland -f -W "$width" -H "$height" -w "$nested" -h "$height") + [ "$nested" != "$width" ] && args+=(-S stretch) # -r is what gamescope advertises as the refresh rate, and what a game reads as the display's. # Left unset it defaults to 60, so a 120 Hz panel offers only 60 Hz in game settings. The FPS # limiter wins when it is on (it is a deliberate cap); otherwise use the panel's own rate.