Skip to content

fix: don't block local launch when remote runs a non-Steam shortcut#1396

Open
ben-pearson wants to merge 1 commit into
utkarshdalal:masterfrom
ben-pearson:fix/play-anyway-non-steam-remote
Open

fix: don't block local launch when remote runs a non-Steam shortcut#1396
ben-pearson wants to merge 1 commit into
utkarshdalal:masterfrom
ben-pearson:fix/play-anyway-non-steam-remote

Conversation

@ben-pearson
Copy link
Copy Markdown
Contributor

@ben-pearson ben-pearson commented May 7, 2026

Summary

Tapping Play on a Steam game in GameNative while a non-Steam shortcut (e.g. Chrome on a Steam Deck) was running on another signed-in device showed the "App Running" conflict dialog, but tapping Play Anyway couldn't actually kick a non-Steam process — leaving the user stuck. Pre-#1306 the local launch would have proceeded normally in this scenario.

This patch restricts the conflict dialog to cases where the remote app is something we recognise (and could meaningfully kick).

Bonus improvement: the dialog now shows the actual remote game name ("Cyberpunk 2077 is currently running on another device") instead of the generic "another game" label, when we have the app in the local DB.

Root cause

Steam reports session conflicts via two callbacks: PlayingSessionStateCallback (in-game) and LoggedInElsewhere on LoggedOff (forced kick). Both populate _isPlayingBlocked and emit SteamEvent.PlayingBlocked whenever Steam says the session is blocked.

Non-Steam shortcuts added to a Steam library (Chrome, Discord, etc.) get synthetic high-range app IDs. They pass an appID != 0 check but aren't in our local app DB and can't be kicked via kickPlayingSession — Steam has no game session to stop. The dialog therefore appeared with a generic "another game" label, and Play Anyway no-op'd.

The same logic also runs as a pre-launch check in PluviaMain.preLaunchApp via localPersona.gameAppID — synthetic shortcut IDs got through there too.

Changes

  • PluviaMain.preLaunchApp: gate the pre-launch conflict dialog on SteamService.getAppInfoOf(currentPlaying) != null. Unknown apps fall through to a normal local launch.
  • SteamService.onPlayingSessionState: same DB-lookup gate, plus pass the resolved app name through the PlayingBlocked event so the in-game dialog can display the actual game name.
  • SteamService.onLoggedOff LoggedInElsewhere branch: emits PlayingBlocked(remoteAppName = null) since that callback doesn't carry remote-app info — the dialog falls back to the existing "another game" label.
  • SteamEvent.PlayingBlocked: changed from data object to data class carrying remoteAppName: String?.
  • XServerScreen: stores the resolved name in a rememberSaveable, uses it in the dialog message, falls back to R.string.main_app_running_unknown_game when null.

+45 / -28 across 4 files. No new strings, no new public API, no test changes.

Test plan

  • Non-Steam shortcut on remote (e.g. Chrome on Steam Deck) → tap Play on a game in GameNative → no dialog, local game launches normally
  • Real Steam game on remote → tap Play on a different game in GameNative → dialog appears showing the actual remote game name
  • Wine game already running locally, then start a real Steam game on remote → in-game dialog appears with the remote game name; Play Anyway kicks the remote successfully
  • Wine game already running locally, then start a non-Steam shortcut on remote → no in-game dialog interrupts the running game

Demo

image image

Known trade-offs

  • Newly purchased / family-shared Steam games not yet synced into the local app DB will be silently suppressed (no dialog, local launch proceeds). Acceptable: rare and self-healing once the DB sync runs.
  • The pre-existing "another game" fallback string in PluviaMain is now unreachable. Removed.

Summary by cubic

Stop blocking local Play when the remote session is a non-Steam shortcut. If a real Steam game is running remotely, the dialog now shows that game’s actual name.

  • Bug Fixes
    • Show the conflict dialog only when the remote app exists in the local DB; otherwise launch locally without prompting.
    • Apply this gate in PluviaMain.preLaunchApp and SteamService.onPlayingSessionState.
    • Send the remote app name via SteamEvent.PlayingBlocked; LoggedInElsewhere emits remoteAppName = null so the dialog uses the generic label.
    • XServerScreen displays the resolved name, saves it across recompositions, and clears it on dismiss.

Written for commit 6d56308. Summary will update on new commits.

Summary by CodeRabbit

  • Bug Fixes
    • Fixed Steam session conflict detection logic to properly identify blocking games
    • Improved error handling when reading Steam profile information during account conflicts
    • Conflict notifications now display the actual remote game name instead of generic "unknown game" text
    • Enhanced prompt messaging when multiple accounts attempt simultaneous gaming sessions

Tapping Play on a Steam game while a non-Steam shortcut (e.g. Chrome on
a Steam Deck) was running on another device showed the "App Running"
conflict dialog, but Play Anyway couldn't kick a non-Steam process so
the user got stuck. Pre PR utkarshdalal#1306 the local game would launch normally
in this scenario.

Filter both trigger paths so the dialog only appears when the remote app
is in our local DB:

- PluviaMain.preLaunchApp: gate the pre-launch dialog on
  SteamService.getAppInfoOf returning non-null. Non-Steam shortcuts
  report synthetic IDs that aren't kickable, so let the local launch
  proceed silently.
- SteamService.onPlayingSessionState: same gate, plus pass the resolved
  remote app name through SteamEvent.PlayingBlocked so the in-game
  dialog can show it instead of the generic "another game" label.

SteamEvent.PlayingBlocked changes from data object to data class
carrying remoteAppName: String? (null on LoggedInElsewhere, which
doesn't tell us what app caused the kick).
@ben-pearson ben-pearson requested a review from utkarshdalal as a code owner May 7, 2026 23:06
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 7, 2026

Review Change Stack

📝 Walkthrough

Walkthrough

The PR threads remote app name through the Steam "playing blocked" conflict event pipeline: PlayingBlocked event now carries an optional remoteAppName, SteamService identifies and emits it, PluviaMain adds launch-time validation gating, and XServerScreen captures and displays the name in the conflict dialog.

Changes

Remote App Name in Playing Blocked Event

Layer / File(s) Summary
Event Contract
app/src/main/java/app/gamenative/events/SteamEvent.kt
PlayingBlocked changed from parameterless data object to data class PlayingBlocked(val remoteAppName: String?).
Service Event Emission
app/src/main/java/app/gamenative/service/SteamService.kt
onLoggedOff emits PlayingBlocked(remoteAppName = null) when xEnvironment is active; onPlayingSessionState resolves playingAppID to local AppInfo and emits PlayingBlocked with the app's name when conflict is detected.
Launch Flow Validation
app/src/main/java/app/gamenative/ui/PluviaMain.kt
preLaunchApp adds early recognition gate for Steam apps, restructures persona read and prompt computation in a try-catch, and only attempts current-playing-app checks for Steam-sourced launches.
UI State and Display
app/src/main/java/app/gamenative/ui/screen/xserver/XServerScreen.kt
Adds playingBlockedRemoteName state, updates onPlayingBlocked handler to store the remote app name, displays it in the conflict dialog with fallback text, and clears state on both primary and cancel dismissals.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • utkarshdalal/GameNative#1306: Main PR extends the same Steam "playing blocked" session-conflict feature by adding remoteAppName to PlayingBlocked and updating SteamService, launch flow, and UI to capture and display it.
  • utkarshdalal/GameNative#286: Both PRs modify PluviaMain.preLaunchApp's launch logic, with this PR refining Steam-specific prompt validation and recognition gating.
  • utkarshdalal/GameNative#470: Both PRs change PluviaMain.preLaunchApp to gate the Steam "another game running" conflict check to Steam-sourced apps.

Suggested reviewers

  • phobos665

Poem

🐰 A rabbit hops through Steam's conflicting game flow,
Now tracking which app blocked the stage to show,
From backend through service to screen it does flow,
The remote app's name, no longer "unknown," we know! 🎮

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main change: preventing false conflict dialogs when a non-Steam shortcut runs remotely, allowing local launches to proceed normally.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description check ✅ Passed The pull request includes a comprehensive description with clear problem statement, root cause analysis, detailed changes, test plan, and demo screenshots.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No issues found across 4 files

Comment on lines +3715 to +3718
val knownApp = callback.playingAppID
.takeIf { it != 0 }
?.let { getAppInfoOf(it) }
?: return
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getAppInfoOf(it) <- there will be circumstances where it's not 0 and it's not an app that is in our DB (or our DB has not synced for some reason).
Is this function safe to call in that case? What would happen? Could lead to a crash.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants