Skip to content

Conversation

@phobos665
Copy link
Contributor

@phobos665 phobos665 commented Jan 19, 2026

Overview

This PR updates the Game Shortcut logic for exporting and consuming the launch intent.

This does the following:

  • Correctly uses the game source as part of the shortcut creation so it can correctly identify what service it needs to check if installed, and launch it if installed.
  • Cleans up detection logic around consuming the launch intent
  • Maintains backwards compatibility with the old logic by giving it a default as STEAM.

Summary by cubic

Shortcuts and launch intents now work for Steam, GOG, Epic, and Custom games. Shortcuts embed the game source so they open the right game and show a clear message if the game isn’t installed.

  • New Features
    • Shortcuts: pass game_source in shortcut intents; createPinnedShortcut now accepts GameSource and includes it.
    • Intent handling: IntentLaunchManager reads game_source (defaults to STEAM, locale-safe) and builds appId as "_".
    • Launch flow: PluviaMain checks installation per source (SteamService, GOGService, EpicService, CustomGameScanner) and shows a “not installed” dialog; uses provided appId without rewriting.
    • UI plumbing: BaseAppScreen passes GameSource from LibraryItem into shortcut creation.
    • Utilities: CustomGameScanner adds isGameInstalled(appId: Int) to align with service checks.

Written for commit 5450014. Summary will update on new commits.

Summary by CodeRabbit

  • New Features

    • Multi-source launch support: Steam, GOG, Epic and Custom games recognized at launch.
    • Shortcuts/intents now carry source-aware game identifiers.
  • Improvements

    • Unified per-source installation checks and clearer non-installed messaging.
    • Improved custom-game detection, icon retrieval, and default-folder handling.
    • Consistent use of original launch identifier for config overrides and launch tracking.

…ectly. Will now look to test and fix front-end exporting.
@coderabbitai
Copy link

coderabbitai bot commented Jan 19, 2026

📝 Walkthrough

Walkthrough

Implements GameSource-aware external launches and shortcuts: intent-parsed game_source drives source-prefixed appId creation, per-source installation checks (Steam, GOG, Epic, Custom), container/config override application using original launchRequest.appId, and threads GameSource into shortcut intents and custom-game utilities.

Changes

Cohort / File(s) Summary
Launch flow & multi-source handling
app/src/main/java/app/gamenative/ui/PluviaMain.kt, app/src/main/java/app/gamenative/utils/IntentLaunchManager.kt
Add GameSource-aware appId construction (reads/validates game_source intent extra). Replace Steam-only install check with per-source checks (SteamService, GOGService, EpicService, CustomGameScanner.isGameInstalled). Use original launchRequest.appId for container/config overrides and launched-app state updates.
Custom game utilities
app/src/main/java/app/gamenative/utils/CustomGameScanner.kt
Add helpers: isGameInstalled(appId: Int), ensureDefaultFolderExists(), and findIconFileForCustomGame(context, appId) (context-aware icon lookup and container-exe fallback).
Shortcut creation & UI helpers
app/src/main/java/app/gamenative/ui/screen/library/appscreen/BaseAppScreen.kt, app/src/main/java/app/gamenative/utils/ShortcutUtils.kt
Expose getGameSource helper and thread GameSource into createPinnedShortcut; shortcut intents now include game_source extra and icon handling updated to accept the source parameter.
Imports, logging & minor API usage
app/src/main/java/app/gamenative/ui/PluviaMain.kt, app/src/main/java/app/gamenative/utils/ShortcutUtils.kt
Add Epic/GOG imports and use their isGameInstalled APIs; adjust logging messages and replace findCustomGameById usages with isGameInstalled; preserve container-override semantics but consistently reference launchRequest.appId.

Sequence Diagram

sequenceDiagram
    participant Intent as External Intent
    participant IL as IntentLaunchManager
    participant PM as PluviaMain
    participant Steam as SteamService
    participant GOG as GOGService
    participant Epic as EpicService
    participant CS as CustomGameScanner

    Intent->>IL: Launch intent (game_id, game_source?)
    IL->>IL: Extract & validate game_source (default STEAM)
    IL->>PM: Deliver LaunchRequest (appId = "<SOURCE>_<id>")

    alt source == STEAM
        PM->>Steam: SteamService.isAppInstalled(appId)
        Steam-->>PM: installed?
    else source == GOG
        PM->>GOG: Ensure credentials / init
        GOG-->>PM: ready
        PM->>GOG: GOGService.isGameInstalled(appId)
        GOG-->>PM: installed?
    else source == EPIC
        PM->>Epic: EpicService.isGameInstalled(appId)
        Epic-->>PM: installed?
    else source == CUSTOM_GAME
        PM->>CS: CustomGameScanner.isGameInstalled(appId)
        CS-->>PM: installed?
    end

    alt installed
        PM->>PM: Apply container/config overrides (use launchRequest.appId)
        PM->>PM: preLaunchApp / launch
    else not installed
        PM->>PM: Log / show not-installed message
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐰 I hopped through intents with a tiny thrum,

Steam, GOG, Epic, Custom — each gets its drum.
Shortcuts carry source, icons found with glee,
Launches check each home before they set free. 🥕

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Working Game Shortcuts for all game sources' accurately captures the main objective of the PR, which is to enable shortcuts to work across multiple game sources (Steam, GOG, Custom, and Epic).

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

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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.

Comment on lines 18 to 52
@@ -40,7 +42,14 @@ object IntentLaunchManager {
return null
}

val appId = "${GameSource.STEAM.name}_$gameId"
// Get Game Source for launch intent
var gameSource = intent.getStringExtra(EXTRA_GAME_SOURCE)?.uppercase()
val isValidGameSource = GameSource.entries.any { it.name == gameSource }
if (!isValidGameSource) {
gameSource = GameSource.STEAM.name
}

val appId = "${gameSource}_$gameId"
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Intent Manager will now correctly parse the gameSource.

Comment on lines 621 to +628

// Helper function to check if game is installed to match pattern of GOG & Steam Service
fun isGameInstalled(appId: Int): Boolean {
val isInstalled = findCustomGameById(appId) != null

return isInstalled
}

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Added isGameInstalled helper function to match the pattern of GOG and Steam services.

Comment on lines 212 to 244
@@ -235,27 +241,13 @@ fun PluviaMain(
return@let
}

// If it's a custom game, update the appId to use CUSTOM_GAME format
val finalAppId = if (customGamePath != null && !isSteamInstalled) {
"${GameSource.CUSTOM_GAME.name}_$gameId"
} else {
launchRequest.appId
}

// Update launchRequest with the correct appId if it was changed
val updatedLaunchRequest = if (finalAppId != launchRequest.appId) {
launchRequest.copy(appId = finalAppId)
} else {
launchRequest
}

if (updatedLaunchRequest.containerConfig != null) {
if (launchRequest.containerConfig != null) {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Now correctly parses and identifies the game, and this logic is also cleaned up so that it's extensible for future purposes.

Copy link
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.

2 issues found across 5 files

Prompt for AI agents (all issues)

Check if these issues are valid — if so, understand the root cause of each and fix them.


<file name="app/src/main/java/app/gamenative/utils/IntentLaunchManager.kt">

<violation number="1" location="app/src/main/java/app/gamenative/utils/IntentLaunchManager.kt:46">
P2: Use locale-independent uppercasing when normalizing `game_source`; `uppercase()` is locale-sensitive and can mis-normalize values (e.g., Turkish locale), causing valid sources to be rejected and defaulted to STEAM.</violation>
</file>

<file name="app/src/main/java/app/gamenative/ui/PluviaMain.kt">

<violation number="1" location="app/src/main/java/app/gamenative/ui/PluviaMain.kt:215">
P2: Legacy GOG shortcuts without a `GOG_` prefix are treated as STEAM, so valid GOG installs will be rejected as “not installed” and never launched.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
app/src/main/java/app/gamenative/ui/PluviaMain.kt (2)

145-180: Missing GOG installation check in ExternalGameLaunch handler.

This handler checks Steam and Custom game installation but doesn't check GOG. A GOG game launched via this path would incorrectly fall through with isSteamInstalled = false and customGamePath = null, leading to the wrong finalAppId or launch failure.

The OnLogonEnded handler at lines 214-226 correctly checks all three sources. This handler should follow the same pattern.

Suggested fix
 is MainViewModel.MainUiEvent.ExternalGameLaunch -> {
     Timber.i("[PluviaMain]: Received ExternalGameLaunch UI event for app ${event.appId}")

     // Extract game ID from appId (format: "STEAM_<id>" or "CUSTOM_GAME_<id>")
     val gameId = ContainerUtils.extractGameIdFromContainerId(event.appId)
+    val gameSource = ContainerUtils.extractGameSourceFromContainerId(event.appId)

-    // First check if it's a Steam game and if it's installed
-    val isSteamInstalled = SteamService.isAppInstalled(gameId)
-
-    // If not installed as Steam game, check if it's a custom game
-    val customGamePath = if (!isSteamInstalled) {
-        CustomGameScanner.findCustomGameById(gameId)
-    } else {
-        null
+    val isInstalled = when (gameSource) {
+        GameSource.STEAM -> SteamService.isAppInstalled(gameId)
+        GameSource.GOG -> GOGService.isGameInstalled(gameId.toString())
+        GameSource.CUSTOM_GAME -> CustomGameScanner.isGameInstalled(gameId)
     }

-    // Determine the final appId to use
-    val finalAppId = if (customGamePath != null && !isSteamInstalled) {
-        "${GameSource.CUSTOM_GAME.name}_$gameId"
-    } else {
-        event.appId
+    if (!isInstalled) {
+        // Handle not installed case similar to OnLogonEnded
+        // Show error dialog and return
+        return@collect
     }

+    val finalAppId = event.appId
+
     Timber.i("[PluviaMain]: Using appId: $finalAppId ...")

228-241: App name lookup incorrectly uses Steam service for all game sources.

Line 229 uses SteamService.getAppInfoOf(gameId) regardless of gameSource. For GOG and Custom games, this returns null, falling back to the generic "App ${launchRequest.appId}" message.

To improve UX, use source-appropriate lookups:

val appName = when (gameSource) {
    GameSource.STEAM -> SteamService.getAppInfoOf(gameId)?.name
    GameSource.GOG -> GOGService.getGOGGameOf(gameId)?.title
    GameSource.CUSTOM_GAME -> CustomGameScanner.findCustomGameById(gameId)?.let { File(it).name }
} ?: "App ${launchRequest.appId}"

Note: The custom game case falls back to using the folder name as the game name, since there is no dedicated game name retrieval method in CustomGameScanner.

♻️ Duplicate comments (1)
app/src/main/java/app/gamenative/ui/PluviaMain.kt (1)

212-226: Multi-source installation check looks good, minor API inconsistency noted.

The when expression properly handles all three game sources. Note the type inconsistency: GOGService.isGameInstalled takes a String (line 220) while SteamService.isAppInstalled and CustomGameScanner.isGameInstalled take Int. Consider aligning the APIs for consistency.

Regarding legacy GOG shortcuts defaulting to STEAM: since GOG shortcut creation is new in this PR, there shouldn't be legacy GOG shortcuts to worry about.

🧹 Nitpick comments (1)
app/src/main/java/app/gamenative/ui/PluviaMain.kt (1)

397-404: Consider using the imported GOGService instead of fully qualified names.

GOGService is imported at line 54, but lines 397-404 use the fully qualified app.gamenative.service.gog.GOGService. This is inconsistent with line 220 which uses the short form.

Suggested simplification
-            if (app.gamenative.service.gog.GOGService.hasStoredCredentials(context) &&
-                !app.gamenative.service.gog.GOGService.isRunning
-            ) {
+            if (GOGService.hasStoredCredentials(context) && !GOGService.isRunning) {
                 Timber.tag("GOG").d("[PluviaMain]: Starting GOGService for logged-in user")
-                app.gamenative.service.gog.GOGService.start(context)
+                GOGService.start(context)
             } else {
-                Timber.tag("GOG").d("GOG SERVICE Not going to start: ${app.gamenative.service.gog.GOGService.isRunning}")
+                Timber.tag("GOG").d("GOG SERVICE Not going to start: ${GOGService.isRunning}")
             }

@phobos665
Copy link
Contributor Author

Will update this once EGS if merged in and we can get this one in too.

Comment on lines +153 to +156
protected fun getGameSource(libraryItem: LibraryItem): GameSource {
return libraryItem.gameSource
}

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Only change here, rest is formatting changes from kotlin's formatter.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@app/src/main/java/app/gamenative/ui/PluviaMain.kt`:
- Around line 214-226: The when expression in PluviaMain.kt that computes
isInstalled for gameSource omits GameSource.EPIC; add a GameSource.EPIC branch
to the same when block (alongside GameSource.STEAM, GOG, CUSTOM_GAME) and call
the appropriate Epic installation check (e.g.,
EpicService.isGameInstalled(gameId.toString()) or
EpicService.isAppInstalled(gameId) depending on the EpicService API), and add
any required import for EpicService so EPIC cases are handled explicitly.
🧹 Nitpick comments (2)
app/src/main/java/app/gamenative/ui/screen/library/appscreen/BaseAppScreen.kt (1)

153-156: Consider adding KDoc comment for consistency.

The surrounding helper methods (getGameName, getGameId, getIconUrl) all have KDoc comments describing their purpose. Adding one here would maintain consistency.

📝 Suggested documentation
+    /**
+     * Get the game source (Steam, GOG, Custom, etc.) for shortcuts
+     */
     protected fun getGameSource(libraryItem: LibraryItem): GameSource {
         return libraryItem.gameSource
     }
app/src/main/java/app/gamenative/ui/PluviaMain.kt (1)

228-240: App name lookup should be source-aware for better UX.

When gameSource is GOG or CUSTOM_GAME, SteamService.getAppInfoOf(gameId) returns null, causing the error dialog to display "App GOG_12345" or "App CUSTOM_GAME_5" instead of the actual game name.

Consider adding source-aware name resolution:

♻️ Proposed source-aware name lookup
 if (!isInstalled) {
-    val appName = SteamService.getAppInfoOf(gameId)?.name ?: "App ${launchRequest.appId}"
+    val appName = when (gameSource) {
+        GameSource.STEAM -> SteamService.getAppInfoOf(gameId)?.name
+        GameSource.GOG -> GOGService.getGOGGameOf(gameId.toString())?.title
+        GameSource.CUSTOM_GAME -> CustomGameScanner.findCustomGameById(gameId)?.let { File(it).name }
+    } ?: "App ${launchRequest.appId}"
     Timber.tag("IntentLaunch").w("Game not installed: $appName (${launchRequest.appId})")

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.

1 participant