import { useState, useEffect } from "react"; import { PanelSection, PanelSectionRow, ButtonItem, DropdownItem } from "@decky/ui"; import { definePlugin, callable } from "@decky/api"; import { RiAiGenerate } from "react-icons/ri"; const runInstallFGMod = callable< [], { status: string; message?: string; output?: string } >("run_install_fgmod"); const runUninstallFGMod = callable< [], { status: string; message?: string; output?: string } >("run_uninstall_fgmod"); const checkFGModPath = callable< [], { exists: boolean } >("check_fgmod_path"); const listInstalledGames = callable< [], { status: string; games: { appid: string; name: string }[] } >("list_installed_games"); const logError = callable<[string], void>("log_error"); function FGModInstallerSection() { const [installing, setInstalling] = useState(false); const [uninstalling, setUninstalling] = useState(false); const [installResult, setInstallResult] = useState<{ status: string; output?: string; message?: string; } | null>(null); const [uninstallResult, setUninstallResult] = useState<{ status: string; output?: string; message?: string; } | null>(null); const [pathExists, setPathExists] = useState(null); useEffect(() => { const checkPath = async () => { try { const result = await checkFGModPath(); setPathExists(result.exists); } catch (e) { logError('useEffect -> checkPath' + String(e)); console.error(e); } }; checkPath(); // Initial check const intervalId = setInterval(checkPath, 3000); // Check every 3 seconds return () => clearInterval(intervalId); // Cleanup interval on component unmount }, []); useEffect(() => { if (installResult) { const timer = setTimeout(() => { setInstallResult(null); }, 5000); return () => clearTimeout(timer); } return () => {}; // Ensure a cleanup function is always returned }, [installResult]); useEffect(() => { if (uninstallResult) { const timer = setTimeout(() => { setUninstallResult(null); }, 5000); return () => clearTimeout(timer); } return () => {}; }, [uninstallResult]); const handleInstallClick = async () => { try { setInstalling(true); const result = await runInstallFGMod(); setInstalling(false); setInstallResult(result); } catch (e) { logError('handleInstallClick: ' + String(e)); console.error(e) } }; const handleUninstallClick = async () => { try { setUninstalling(true); const result = await runUninstallFGMod(); setUninstalling(false); setUninstallResult(result); } catch (e) { logError('handleUninstallClick' + String(e)); console.error(e) } }; return ( {pathExists !== null ? (
{pathExists ? "Mod Is Installed" : "Mod Not Installed"}
) : null} {pathExists === false ? ( {installing ? "Installing..." : "Install FG Mod"} ) : null} {pathExists === true ? ( {uninstalling ? "Uninstalling..." : "Uninstall FG Mod"} ) : null} {installResult ? (
Status:{" "} {installResult.status === "success" ? "Success" : "Error"}
{installResult.output ? ( <> Output:
{installResult.output}
) : null} {installResult.message ? ( <> Error: {installResult.message} ) : null}
) : null} {uninstallResult ? (
Status:{" "} {uninstallResult.status === "success" ? "Success" : "Error"}
{uninstallResult.output ? ( <> Output:
{uninstallResult.output}
) : null} {uninstallResult.message ? ( <> Error: {uninstallResult.message} ) : null}
) : null}
Install the mod above, then select and patch a game.
); } function InstalledGamesSection() { const [games, setGames] = useState<{ appid: number; name: string }[]>([]); const [selectedGame, setSelectedGame] = useState<{ appid: number; name: string } | null>(null); const [result, setResult] = useState(''); useEffect(() => { const fetchGames = async () => { try { const response = await listInstalledGames(); if (response.status === "success") { const sortedGames = [...response.games] .map(game => ({ ...game, appid: parseInt(game.appid, 10), })) .sort((a, b) => a.name.toLowerCase().localeCompare(b.name.toLowerCase())); setGames(sortedGames); } else { logError('fetchGames: ' + JSON.stringify(response)); console.error('fetchGames: ' + JSON.stringify(response)); } } catch (error) { logError("Error fetching games:" + String(error)); console.error("Error fetching games:", String(error)); } }; fetchGames(); }, []); const handlePatchClick = async () => { if (!selectedGame) return; try { await SteamClient.Apps.SetAppLaunchOptions(selectedGame.appid, '~/fgmod/fgmod %COMMAND%'); setResult(`Launch options set for ${selectedGame.name}. You can now select DLSS in the game's menu.`); } catch (error) { logError('handlePatchClick: ' + String(error)); setResult(error instanceof Error ? `Error setting launch options: ${error.message}` : 'Error setting launch options'); } }; const handleUnpatchClick = async () => { if (!selectedGame) return; try { await SteamClient.Apps.SetAppLaunchOptions(selectedGame.appid, '~/fgmod/fgmod-uninstaller.sh %COMMAND%'); setResult(`DLSS mods will uninstall on next launch of ${selectedGame.name}.`); } catch (error) { logError('handleUnpatchClick: ' + String(error)); setResult(error instanceof Error ? `Error clearing launch options: ${error.message}` : 'Error clearing launch options'); } }; return ( ({ data: game.appid, label: game.name }))} selectedOption={selectedGame?.appid} onChange={(option) => { const game = games.find(g => g.appid === option.data); setSelectedGame(game || null); setResult(''); }} strDefaultLabel="Select a game..." menuLabel="Installed Games" /> {result ? (
{result}
) : null} {selectedGame ? ( <> Patch Unpatch ) : null}
); } export default definePlugin(() => ({ name: "Framegen Plugin", titleView:
Decky Framegen
, alwaysRender: true, content: ( <> ), icon: , onDismount() { console.log("Framegen Plugin unmounted"); }, }));