mirror of
https://github.com/Droid-Deck/DroidDeck.git
synced 2026-10-14 19:27:09 -04:00
Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d03e62fd01 | ||
|
|
799ecfb267 |
No files matched your search
@@ -120,6 +120,7 @@ class MainActivity : ComponentActivity() {
|
||||
private var glThread by mutableStateOf(true)
|
||||
private var noGlError by mutableStateOf(true)
|
||||
private var steamDeckMode by mutableStateOf(false)
|
||||
private var returnAfterGame by mutableStateOf(true)
|
||||
private var steamRepairQueued by mutableStateOf(false)
|
||||
private var mangoapp by mutableStateOf(true)
|
||||
private var steamController by mutableStateOf(SessionPrefs.CONTROLLER_DECK)
|
||||
@@ -1267,6 +1268,7 @@ class MainActivity : ComponentActivity() {
|
||||
syncBackend = if (mode == SessionService.MODE_STEAM) SessionPrefs.syncBackendOf(fastSync, fsyncFirst, syncFallback) else null,
|
||||
steamChannel = if (mode == SessionService.MODE_STEAM) steamChannel else null,
|
||||
steamDeckMode = mode == SessionService.MODE_STEAM && steamDeckMode,
|
||||
returnAfterGame = if (mode == SessionService.MODE_STEAM) returnAfterGame else null,
|
||||
steamRepairQueued = steamRepairQueued,
|
||||
mangoapp = mangoapp,
|
||||
steamController = if (mode == SessionService.MODE_STEAM) steamController else null,
|
||||
@@ -1351,6 +1353,7 @@ class MainActivity : ComponentActivity() {
|
||||
steamChannel = SessionPrefs.steamChannel(this)
|
||||
},
|
||||
onMangoapp = { on -> SessionPrefs.setMangoapp(this, on); mangoapp = on },
|
||||
onReturnAfterGame = { on -> SessionPrefs.setReturnAfterGame(this, on); returnAfterGame = on },
|
||||
onSteamController = { id -> SessionPrefs.setSteamController(this, id); steamController = id },
|
||||
onRunSteamAtStartup = { on ->
|
||||
SessionPrefs.setRunSteamAtStartup(this, on)
|
||||
@@ -1482,6 +1485,7 @@ class MainActivity : ComponentActivity() {
|
||||
steamDeckMode = SessionPrefs.steamDeckMode(this)
|
||||
steamRepairQueued = SteamRepair.queued(this)
|
||||
mangoapp = SessionPrefs.mangoapp(this)
|
||||
returnAfterGame = SessionPrefs.returnAfterGame(this)
|
||||
steamController = SessionPrefs.steamController(this)
|
||||
runSteamAtStartup = SessionPrefs.runSteamAtStartup(this)
|
||||
hdrOn = SessionPrefs.hdr(this, mode)
|
||||
|
||||
@@ -167,6 +167,8 @@ object DeviceReport {
|
||||
k("Guest host name", SessionPrefs.guestHostname(context))
|
||||
k("DirectAudio for games", SessionPrefs.directAudio(context))
|
||||
k("Stretch 16:9 to panel", SessionPrefs.stretch16x9(context))
|
||||
k("Return to launcher after a game", SessionPrefs.returnAfterGame(context))
|
||||
k("Client audio", if (SessionPrefs.clientDirectAudio(context)) "DirectAudio" else "classic")
|
||||
k("Microphone", SessionPrefs.micEnabled(context))
|
||||
k("On-screen controls", SessionPrefs.oscMode(context))
|
||||
k("Touch mode", SessionPrefs.touchMode(context))
|
||||
|
||||
@@ -212,6 +212,16 @@ object SessionPrefs {
|
||||
// daemon (PulseAudioComponent), one step from Android. The old "clientDirectAudio" boolean
|
||||
// (which meant the relay route, one hop more) is no longer read.
|
||||
|
||||
/**
|
||||
* After a game launched from the front end (or another launcher's link) closes, the client
|
||||
* exits cleanly and the session returns there, instead of staying in Big Picture.
|
||||
*/
|
||||
fun returnAfterGame(context: Context): Boolean = prefs(context).getBoolean("returnAfterGame", true)
|
||||
|
||||
fun setReturnAfterGame(context: Context, on: Boolean) {
|
||||
prefs(context).edit().putBoolean("returnAfterGame", on).apply()
|
||||
}
|
||||
|
||||
fun stretch16x9(context: Context): Boolean = prefs(context).getBoolean("stretch16x9", false)
|
||||
|
||||
fun setStretch16x9(context: Context, on: Boolean) {
|
||||
|
||||
@@ -98,6 +98,8 @@ class SessionService : Service() {
|
||||
/** One validated game request waiting for this Steam session to reach a usable state. */
|
||||
private var pendingSteamGameId: String? = null
|
||||
private var pendingSteamGameGeneration = -1
|
||||
// Bumped to cancel a running game-exit watch (a new launch, or the session ending).
|
||||
@Volatile private var gameExitWatchToken = 0
|
||||
private val flushSteamGame = object : Runnable {
|
||||
override fun run() = flushQueuedSteamGame()
|
||||
}
|
||||
@@ -259,6 +261,8 @@ class SessionService : Service() {
|
||||
val gen = ++sessionGen
|
||||
cancelQueuedSteamGame(clearMailbox = false)
|
||||
clearSteamGameMailbox()
|
||||
if (SessionState.mode == MODE_STEAM) SessionState.steamUrl
|
||||
?.takeIf { it.startsWith(GAME_URL_PREFIX) }?.let { watchGameExit(it.removePrefix(GAME_URL_PREFIX), gen) }
|
||||
acquireLocks()
|
||||
Thread({
|
||||
// A tree the last session left behind (the app was killed or crashed, so its teardown
|
||||
@@ -1089,9 +1093,71 @@ class SessionService : Service() {
|
||||
pendingSteamGameId = null
|
||||
pendingSteamGameGeneration = -1
|
||||
SessionEvents.record("steam.game_launch_requested", mapOf("gameId" to gameId, "generation" to gen))
|
||||
watchGameExit(gameId, gen)
|
||||
Log.i(TAG, "queued Steam game $gameId for the running client")
|
||||
}
|
||||
|
||||
/**
|
||||
* A game launched from the front end (the Games tab, or another launcher's link): once it has
|
||||
* started and then been gone for [GAME_EXIT_GRACE_MS], ask the client to exit through the
|
||||
* session script's steam-stop request. The client quits cleanly, finishing its cloud sync, so
|
||||
* the session ends on its own and the activity returns to where the launch came from instead
|
||||
* of leaving the player in Big Picture. The grace covers launchers that restart the game.
|
||||
*/
|
||||
private fun watchGameExit(gameId: String, gen: Int) {
|
||||
if (!SessionPrefs.returnAfterGame(this)) return
|
||||
val token = ++gameExitWatchToken
|
||||
Thread({
|
||||
val startBy = android.os.SystemClock.elapsedRealtime() + GAME_START_TIMEOUT_MS
|
||||
var seen = false
|
||||
var goneSince = 0L
|
||||
while (token == gameExitWatchToken && gen == sessionGen && SessionState.running && !SessionState.stopRequested) {
|
||||
val now = android.os.SystemClock.elapsedRealtime()
|
||||
if (steamGameRunning()) {
|
||||
if (!seen) Log.i(TAG, "game $gameId started; returning to the launcher when it closes")
|
||||
seen = true
|
||||
goneSince = 0L
|
||||
} else if (!seen) {
|
||||
// Never started (an install, an update or a failed launch): leave the session be.
|
||||
if (now > startBy) {
|
||||
Log.i(TAG, "game $gameId did not start; not watching for its exit")
|
||||
return@Thread
|
||||
}
|
||||
} else if (goneSince == 0L) {
|
||||
goneSince = now
|
||||
} else if (now - goneSince >= GAME_EXIT_GRACE_MS) {
|
||||
mainHandler.post { returnAfterGame(gameId, gen, token) }
|
||||
return@Thread
|
||||
}
|
||||
try { Thread.sleep(GAME_EXIT_POLL_MS) } catch (e: InterruptedException) { return@Thread }
|
||||
}
|
||||
}, "game-exit-watch").start()
|
||||
}
|
||||
|
||||
private fun returnAfterGame(gameId: String, gen: Int, token: Int) {
|
||||
if (token != gameExitWatchToken || gen != sessionGen || !SessionState.running ||
|
||||
SessionState.stopRequested || SessionState.mode != MODE_STEAM || !SessionPrefs.returnAfterGame(this)) return
|
||||
val written = runCatching { File(LinuxRuntime.sessionRoot(this), "steam-stop").writeText("") }
|
||||
.onFailure { Log.w(TAG, "could not ask the client to exit after game $gameId", it) }.isSuccess
|
||||
if (!written) return
|
||||
SessionEvents.record("steam.return_after_game", mapOf("gameId" to gameId))
|
||||
Log.i(TAG, "game $gameId closed: asking the client to exit and returning to the launcher")
|
||||
}
|
||||
|
||||
/** A Steam game is running: the client runs each one under its reaper, and Proton's script
|
||||
* waits on the game with waitforexitandrun. Read from the command lines, as ComponentsManager does. */
|
||||
private fun steamGameRunning(): Boolean {
|
||||
val procs = File("/proc").listFiles() ?: return false
|
||||
for (p in procs) {
|
||||
if (!p.name.all(Char::isDigit)) continue
|
||||
val cmd = runCatching { File(p, "cmdline").readBytes() }.getOrNull() ?: continue
|
||||
if (cmd.isEmpty()) continue
|
||||
val text = String(cmd).replace('\u0000', ' ')
|
||||
if (text.contains("SteamLaunch AppId=") || text.contains("waitforexitandrun")) return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
private fun resumeSession() {
|
||||
activityVisible = true
|
||||
screenOn = (getSystemService(Context.POWER_SERVICE) as? PowerManager)?.isInteractive ?: screenOn
|
||||
@@ -1466,6 +1532,7 @@ class SessionService : Service() {
|
||||
|
||||
private fun stopSession(status: Int) {
|
||||
cancelQueuedSteamGame()
|
||||
gameExitWatchToken++
|
||||
completeNativeSleep()
|
||||
synchronized(stopLock) {
|
||||
if (!SessionState.running) return
|
||||
@@ -1773,6 +1840,12 @@ class SessionService : Service() {
|
||||
private const val GAME_LAUNCH_RETRY_MS = 250L
|
||||
private const val DOWNLOAD_POLL_MS = 2_000L
|
||||
private const val STEAM_GAME_REQUEST = "steam-game"
|
||||
private const val GAME_URL_PREFIX = "steam://rungameid/"
|
||||
/** How long a front-end launch may take to start (downloads, first-run installs) before the exit watch gives up. */
|
||||
private const val GAME_START_TIMEOUT_MS = 20 * 60_000L
|
||||
/** How long the game must stay gone before the client is asked to exit: launchers restart games. */
|
||||
private const val GAME_EXIT_GRACE_MS = 10_000L
|
||||
private const val GAME_EXIT_POLL_MS = 2_000L
|
||||
private const val NO_PAD_SWITCH = "Download/droiddeck-no-pad"
|
||||
private const val NO_UINPUT_SWITCH = "Download/droiddeck-no-uinput"
|
||||
private const val NO_DECK_PAD_SWITCH = "Download/droiddeck-no-deck-pad"
|
||||
|
||||
@@ -90,6 +90,8 @@ class ModeSettings(
|
||||
val steamChannel: String? = null,
|
||||
/** Steam only: enable the SteamOS client interface and its performance controls. */
|
||||
val steamDeckMode: Boolean = false,
|
||||
/** Steam only: a game launched from the front end returns there when it closes; null hides the row. */
|
||||
val returnAfterGame: Boolean? = null,
|
||||
val steamRepairQueued: Boolean = false,
|
||||
/** Steam, Deck mode: the QAM's performance overlay (mangoapp) is started. */
|
||||
val mangoapp: Boolean = true,
|
||||
@@ -143,6 +145,7 @@ class ModeSettingsActions(
|
||||
val onSteamRepair: () -> Unit = {},
|
||||
val onSteamDeckMode: (Boolean) -> Unit = {},
|
||||
val onMangoapp: (Boolean) -> Unit = {},
|
||||
val onReturnAfterGame: (Boolean) -> Unit = {},
|
||||
val onSteamController: (String) -> Unit = {},
|
||||
val onRunSteamAtStartup: (Boolean) -> Unit = {},
|
||||
val onWifiDiscovery: (Boolean) -> Unit = {},
|
||||
@@ -384,6 +387,11 @@ fun ModeSettingsPage(s: ModeSettings, a: ModeSettingsActions) {
|
||||
stringResource(R.string.mode_mangoapp_hint),
|
||||
s.mangoapp, onChange = a.onMangoapp,
|
||||
)
|
||||
if (s.returnAfterGame != null) ToggleRow(
|
||||
host, "returnaftergame", stringResource(R.string.mode_return_after_game),
|
||||
stringResource(R.string.mode_return_after_game_hint),
|
||||
s.returnAfterGame, onChange = a.onReturnAfterGame,
|
||||
)
|
||||
// Deck mode fixes the branch (SessionPrefs.steamChannel); the choice is for Deck mode off.
|
||||
if (s.steamDeckMode) SettingsRow(stringResource(R.string.mode_branch), stringResource(R.string.mode_branch_deck)) {}
|
||||
else ChoiceRow(
|
||||
|
||||
@@ -287,6 +287,8 @@
|
||||
<string name="mode_deck_mode_hint">La interfaz Deck de Steam y los controles de rendimiento de Quick Access. Se aplica en la próxima sesión.</string>
|
||||
<string name="mode_mangoapp">Superposición de rendimiento</string>
|
||||
<string name="mode_mangoapp_hint">Desactívalo si los juegos fallan o se quedan en negro en modo Deck. Se aplica en la próxima sesión.</string>
|
||||
<string name="mode_return_after_game">Volver a DroidDeck al cerrar un juego</string>
|
||||
<string name="mode_return_after_game_hint">Cuando se cierra un juego que iniciaste desde DroidDeck u otro lanzador, Steam se cierra correctamente (primero termina de sincronizar las partidas en la nube) y vuelves allí. Desactivado: te quedas en Steam.</string>
|
||||
<string name="mode_branch">Rama del cliente</string>
|
||||
<string name="mode_branch_deck">Beta pública de Steam Deck (requerida por el modo Deck).</string>
|
||||
<string name="mode_branch_hint">Se aplica en la próxima sesión. Steam puede actualizarse una vez.</string>
|
||||
|
||||
@@ -261,6 +261,8 @@
|
||||
<string name="mode_deck_mode_hint">Interface Steam Deck et contrôles de performance à accès rapide. Prendra effet à la prochaine session.</string>
|
||||
<string name="mode_mangoapp">Superposition de performance</string>
|
||||
<string name="mode_mangoapp_hint">Désactivez cette option si les jeux plantent ou affichent un écran noir en mode Deck. Prendra effet à la prochaine session.</string>
|
||||
<string name="mode_return_after_game">Revenir à DroidDeck après un jeu</string>
|
||||
<string name="mode_return_after_game_hint">Quand un jeu lancé depuis DroidDeck ou un autre lanceur se ferme, Steam se ferme proprement (après la synchronisation des sauvegardes cloud) et vous y revenez. Désactivé : vous restez dans Steam.</string>
|
||||
<string name="mode_branch">Branche du client</string>
|
||||
<string name="mode_branch_deck">Bêta Steam Deck (requis pour le mode Deck).</string>
|
||||
<string name="mode_branch_hint">Prendra effet à la prochaine session. Steam peut se mettre à jour une fois de lui-même.</string>
|
||||
|
||||
@@ -287,6 +287,8 @@
|
||||
<string name="mode_deck_mode_hint">Steam DeckのUIとクイックアクセスパフォーマンスコントロール。次回のセッションで有効になります。</string>
|
||||
<string name="mode_mangoapp">パフォーマンスオーバーレイ</string>
|
||||
<string name="mode_mangoapp_hint">Deckモードでゲームがクラッシュしたり画面が真っ暗になる場合は、これをオフにしてください。次回のセッションで有効になります。</string>
|
||||
<string name="mode_return_after_game">ゲーム終了後にDroidDeckへ戻る</string>
|
||||
<string name="mode_return_after_game_hint">DroidDeckや他のランチャーから起動したゲームを閉じると、Steamが正しく終了し(クラウドセーブの同期を先に完了)、起動元に戻ります。オフ:Steamに留まります。</string>
|
||||
<string name="mode_branch">クライアントブランチ</string>
|
||||
<string name="mode_branch_deck">Steam Deckベータ版(Deckモードに必要)。</string>
|
||||
<string name="mode_branch_hint">次回のセッションで有効になります。Steamが自動的に一度アップデートされる場合があります。</string>
|
||||
|
||||
@@ -287,6 +287,8 @@
|
||||
<string name="mode_deck_mode_hint">Steam Deck UI 및 빠른 액세스 성능 제어. 다음 세션에 적용됩니다.</string>
|
||||
<string name="mode_mangoapp">성능 오버레이</string>
|
||||
<string name="mode_mangoapp_hint">Deck 모드에서 게임이 충돌하거나 화면이 검게 변하면 이 옵션을 끄세요. 다음 세션에 적용됩니다.</string>
|
||||
<string name="mode_return_after_game">게임 종료 후 DroidDeck으로 돌아가기</string>
|
||||
<string name="mode_return_after_game_hint">DroidDeck 또는 다른 런처에서 실행한 게임을 닫으면 Steam이 정상적으로 종료되고(클라우드 저장 동기화를 먼저 완료) 실행한 곳으로 돌아갑니다. 끄면 Steam에 머무릅니다.</string>
|
||||
<string name="mode_branch">클라이언트 브랜치</string>
|
||||
<string name="mode_branch_deck">Steam Deck 베타(Deck 모드에 필요).</string>
|
||||
<string name="mode_branch_hint">다음 세션에 적용됩니다. Steam이 자체적으로 한 번 업데이트될 수 있습니다.</string>
|
||||
|
||||
@@ -285,6 +285,8 @@
|
||||
<string name="mode_deck_mode_hint">Интерфейс Steam Deck и управление производительностью через быстрый доступ. Вступит в силу в следующей сессии.</string>
|
||||
<string name="mode_mangoapp">Оверлей производительности</string>
|
||||
<string name="mode_mangoapp_hint">Отключите, если игры вылетают или показывают черный экран в режиме Deck. Вступит в силу в следующей сессии.</string>
|
||||
<string name="mode_return_after_game">Возвращаться в DroidDeck после игры</string>
|
||||
<string name="mode_return_after_game_hint">Когда игра, запущенная из DroidDeck или другого лаунчера, закрывается, Steam корректно завершает работу (сначала синхронизирует облачные сохранения), и вы возвращаетесь туда. Выкл.: остаётесь в Steam.</string>
|
||||
<string name="mode_branch">Ветка клиента</string>
|
||||
<string name="mode_branch_deck">Публичная бета Steam Deck (требуется для режима Deck).</string>
|
||||
<string name="mode_branch_hint">Вступит в силу в следующей сессии. Steam может обновиться самостоятельно один раз.</string>
|
||||
|
||||
@@ -287,6 +287,8 @@
|
||||
<string name="mode_deck_mode_hint">Steam 的 Deck 界面和快捷访问性能控制。将在下次会话生效。</string>
|
||||
<string name="mode_mangoapp">性能覆盖层</string>
|
||||
<string name="mode_mangoapp_hint">若游戏在 Deck 模式下崩溃或黑屏,请关闭性能覆盖层。将在下次会话生效。</string>
|
||||
<string name="mode_return_after_game">游戏结束后返回 DroidDeck</string>
|
||||
<string name="mode_return_after_game_hint">从 DroidDeck 或其他启动器启动的游戏关闭后,Steam 会正常退出(先完成云存档同步),并返回启动位置。关闭:留在 Steam 中。</string>
|
||||
<string name="mode_branch">客户端分支</string>
|
||||
<string name="mode_branch_deck">Steam Deck 公开测试版(Deck 模式所必需)。</string>
|
||||
<string name="mode_branch_hint">将在下次会话生效。Steam 可能会自行更新一次。</string>
|
||||
|
||||
@@ -287,6 +287,8 @@
|
||||
<string name="mode_deck_mode_hint">Steam 的 Deck 介面及快速存取效能控制。下次會話生效。</string>
|
||||
<string name="mode_mangoapp">效能疊加層</string>
|
||||
<string name="mode_mangoapp_hint">如果在 Deck 模式下遊戲當機或黑畫面,請關閉此項。下次會話生效。</string>
|
||||
<string name="mode_return_after_game">遊戲結束後返回 DroidDeck</string>
|
||||
<string name="mode_return_after_game_hint">從 DroidDeck 或其他啟動器啟動的遊戲關閉後,Steam 會正常退出(先完成雲端存檔同步),並返回啟動位置。關閉:留在 Steam 中。</string>
|
||||
<string name="mode_branch">用戶端分支</string>
|
||||
<string name="mode_branch_deck">Steam Deck 公開測試版(Deck 模式需要)。</string>
|
||||
<string name="mode_branch_hint">下次會話生效。Steam 可能會自行更新一次。</string>
|
||||
|
||||
@@ -287,6 +287,8 @@
|
||||
<string name="mode_deck_mode_hint">Steam Deck 介面和快速存取效能控制。下次工作階段生效。</string>
|
||||
<string name="mode_mangoapp">效能重疊層</string>
|
||||
<string name="mode_mangoapp_hint">如果 Deck 模式下遊戲崩潰或黑畫面,請關閉此項。下次工作階段生效。</string>
|
||||
<string name="mode_return_after_game">遊戲結束後返回 DroidDeck</string>
|
||||
<string name="mode_return_after_game_hint">從 DroidDeck 或其他啟動器啟動的遊戲關閉後,Steam 會正常退出(先完成雲端存檔同步),並返回啟動位置。關閉:留在 Steam 中。</string>
|
||||
<string name="mode_branch">用戶端分支</string>
|
||||
<string name="mode_branch_deck">Steam Deck 公開測試版(Deck 模式所需)。</string>
|
||||
<string name="mode_branch_hint">下次工作階段生效。Steam 可能會自行更新一次。</string>
|
||||
|
||||
@@ -297,6 +297,8 @@
|
||||
<string name="mode_deck_mode_hint">Steam\'s Deck interface and Quick Access performance controls. Applies next session.</string>
|
||||
<string name="mode_mangoapp">Performance overlay</string>
|
||||
<string name="mode_mangoapp_hint">Turn off if games crash or go black in Deck mode. Applies next session.</string>
|
||||
<string name="mode_return_after_game">Return to DroidDeck after a game</string>
|
||||
<string name="mode_return_after_game_hint">When a game you started from DroidDeck or another launcher closes, Steam exits cleanly (cloud saves finish first) and you go back there. Off: stay in Steam.</string>
|
||||
<string name="mode_branch">Client branch</string>
|
||||
<string name="mode_branch_deck">Steam Deck public beta (required by Deck mode).</string>
|
||||
<string name="mode_branch_hint">Applies next session. Steam may update itself once.</string>
|
||||
|
||||
Reference in new issue
Block a user