From d81bb130385114389728f849d0ab8cccf62b90d1 Mon Sep 17 00:00:00 2001 From: xXJsonDeruloXx Date: Fri, 20 Mar 2026 17:32:52 -0400 Subject: Add Steam UI for prefix-managed integration --- src/components/InstalledGamesSection.tsx | 158 ++++++++++++++++++++----------- 1 file changed, 101 insertions(+), 57 deletions(-) (limited to 'src/components/InstalledGamesSection.tsx') diff --git a/src/components/InstalledGamesSection.tsx b/src/components/InstalledGamesSection.tsx index 71278d7..eb750c8 100644 --- a/src/components/InstalledGamesSection.tsx +++ b/src/components/InstalledGamesSection.tsx @@ -1,122 +1,166 @@ -import { useState, useEffect } from "react"; -import { PanelSection, PanelSectionRow, ButtonItem, DropdownItem, ConfirmModal, showModal } from "@decky/ui"; -import { listInstalledGames, logError } from "../api"; +import { useEffect, useState } from "react"; +import { + ButtonItem, + ConfirmModal, + DropdownItem, + PanelSection, + PanelSectionRow, + showModal, +} from "@decky/ui"; +import { cleanupManagedGame, listInstalledGames, logError } from "../api"; import { safeAsyncOperation } from "../utils"; -import { STYLES } from "../utils/constants"; import { GameInfo } from "../types/index"; +import { STYLES } from "../utils/constants"; + +const DEFAULT_LAUNCH_COMMAND = 'OPTISCALER_PROXY=winmm ~/fgmod/fgmod %COMMAND%'; -export function InstalledGamesSection() { +interface InstalledGamesSectionProps { + isAvailable: boolean; +} + +export function InstalledGamesSection({ isAvailable }: InstalledGamesSectionProps) { const [games, setGames] = useState([]); const [selectedGame, setSelectedGame] = useState(null); - const [result, setResult] = useState(''); + const [result, setResult] = useState(""); + const [loadingGames, setLoadingGames] = useState(false); + const [enabling, setEnabling] = useState(false); + const [disabling, setDisabling] = useState(false); useEffect(() => { + if (!isAvailable) return; + + let cancelled = false; + const fetchGames = async () => { - const response = await safeAsyncOperation( - async () => await listInstalledGames(), - 'fetchGames' - ); - - if (response?.status === "success") { + setLoadingGames(true); + const response = await safeAsyncOperation(async () => await listInstalledGames(), "InstalledGamesSection.fetchGames"); + + if (cancelled || !response) { + setLoadingGames(false); + return; + } + + if (response.status === "success") { const sortedGames = [...response.games] - .map(game => ({ + .map((game) => ({ ...game, - appid: parseInt(game.appid, 10), + appid: parseInt(String(game.appid), 10), })) .sort((a, b) => a.name.toLowerCase().localeCompare(b.name.toLowerCase())); setGames(sortedGames); - } else if (response) { - logError('fetchGames: ' + JSON.stringify(response)); - console.error('fetchGames: ' + JSON.stringify(response)); + } else { + logError(`InstalledGamesSection.fetchGames: ${JSON.stringify(response)}`); } + + setLoadingGames(false); }; - + fetchGames(); - }, []); - const handlePatchClick = async () => { + return () => { + cancelled = true; + }; + }, [isAvailable]); + + const handleEnable = async () => { if (!selectedGame) return; - // Show confirmation modal showModal( - { + setEnabling(true); try { - await SteamClient.Apps.SetAppLaunchOptions(selectedGame.appid, '~/fgmod/fgmod %COMMAND%'); - setResult(`✓ Frame generation enabled for ${selectedGame.name}. Launch the game, enable DLSS in graphics settings, then press Insert to access OptiScaler options.`); + await SteamClient.Apps.SetAppLaunchOptions(selectedGame.appid, DEFAULT_LAUNCH_COMMAND); + setResult(`✓ Enabled prefix-managed OptiScaler for ${selectedGame.name}. Launch the game, enable DLSS if needed, then press Insert for the OptiScaler menu.`); } catch (error) { - logError('handlePatchClick: ' + String(error)); - setResult(error instanceof Error ? `Error: ${error.message}` : 'Error enabling frame generation'); + logError(`InstalledGamesSection.handleEnable: ${String(error)}`); + setResult(error instanceof Error ? `Error: ${error.message}` : "Error enabling prefix-managed OptiScaler"); + } finally { + setEnabling(false); } }} /> ); }; - const handleUnpatchClick = async () => { + const handleDisable = async () => { if (!selectedGame) return; + setDisabling(true); try { - await SteamClient.Apps.SetAppLaunchOptions(selectedGame.appid, '~/fgmod/fgmod-uninstaller.sh %COMMAND%'); - setResult(`✓ Frame generation will be disabled on next launch of ${selectedGame.name}.`); + const cleanupResult = await cleanupManagedGame(String(selectedGame.appid)); + if (cleanupResult?.status !== "success") { + setResult(`Error: ${cleanupResult?.message || cleanupResult?.output || "Failed to clean managed compatdata prefix"}`); + return; + } + + await SteamClient.Apps.SetAppLaunchOptions(selectedGame.appid, ""); + setResult(`✓ Cleared launch options and cleaned the managed compatdata prefix for ${selectedGame.name}.`); } catch (error) { - logError('handleUnpatchClick: ' + String(error)); - setResult(error instanceof Error ? `Error: ${error.message}` : 'Error disabling frame generation'); + logError(`InstalledGamesSection.handleDisable: ${String(error)}`); + setResult(error instanceof Error ? `Error: ${error.message}` : "Error disabling prefix-managed OptiScaler"); + } finally { + setDisabling(false); } }; + if (!isAvailable) return null; + return ( - + ({ + rgOptions={games.map((game) => ({ data: game.appid, - label: game.name + label: game.name, }))} selectedOption={selectedGame?.appid} onChange={(option) => { - const game = games.find(g => g.appid === option.data); + const game = games.find((entry) => entry.appid === option.data); setSelectedGame(game || null); - setResult(''); + setResult(""); }} - strDefaultLabel="Choose a game" - menuLabel="Installed Games" + strDefaultLabel={loadingGames ? "Loading installed games..." : "Choose a game"} + menuLabel="Installed Steam games" + disabled={loadingGames || games.length === 0} /> + +
+ Enable writes the launch option automatically. Disable clears launch options and removes staged files from the selected game's compatdata prefix. +
+
+ {result ? ( -
- {result.includes('Error') ? '❌' : '✅'} {result} +
+ {result.startsWith("Error") ? "❌" : "✅"} {result}
) : null} - + {selectedGame ? ( <> - - Enable Frame Generation + + {enabling ? "Enabling..." : "Enable for selected game"} - - Disable Frame Generation + + {disabling ? "Cleaning..." : "Disable and clean selected game"} -- cgit v1.2.3