From 793d7bb9f470d1594f2bf4a740e0c3e1bf715383 Mon Sep 17 00:00:00 2001 From: Adrian Bonpin Date: Sun, 28 Jun 2026 07:59:39 +0800 Subject: [PATCH] feat(plugin): wire together entry point with definePlugin and SteamClient events --- plugins/decky-vault/src/index.tsx | 241 ++++++++++++++++++++++++++---- 1 file changed, 212 insertions(+), 29 deletions(-) diff --git a/plugins/decky-vault/src/index.tsx b/plugins/decky-vault/src/index.tsx index 178425e..0de5783 100644 --- a/plugins/decky-vault/src/index.tsx +++ b/plugins/decky-vault/src/index.tsx @@ -1,32 +1,215 @@ -import { DeckyVaultImportV1 } from "@deckyvault/shared" +import { useState, useEffect, useRef } from "react" +import { + PanelSection, + PanelSectionRow, + staticClasses, +} from "@decky/ui" +import { + definePlugin, +} from "@decky/api" +import { FaDatabase } from "react-icons/fa" +import MainPanel from "./components/main-panel" +import SettingsPanel from "./components/settings-panel" +import { useSettings, useSession } from "./lib/store" +import { + readAndParseMangohudLog, + clearMangohudLog, + getHardwareInfo, + getOsVersion, + getProtonVersion, + getLaunchOptions, +} from "./lib/api" -interface PluginSettings { - apiKey: string - autoRecord: boolean - exportPath: string +function Content() { + const { settings, updateSetting, loaded } = useSettings() + const { + recordingState, + session, + recentSessions, + error, + setError, + startRecording, + stopRecording, + updateSession, + addToRecent, + reset, + onGameStart, + onGameStop, + } = useSession() + const [activeTab, setActiveTab] = useState<"main" | "settings">("main") + const gameStartedUnregRef = useRef<{ unregister: () => void } | null>(null) + const gameStoppedUnregRef = useRef<{ unregister: () => void } | null>(null) + + // ── Register SteamClient game events ────────────────────────── + useEffect(() => { + try { + const startedReg = SteamClient.Apps.RegisterForGameStarted(async (appId: number) => { + let gameName = `App ${appId}` + try { + const info = await SteamClient.Apps.GetCurrentGameInfo() + if (info.appId === appId) { + gameName = info.strAppName + } + } catch { + // GetCurrentGameInfo may not be available in all contexts + } + onGameStart(appId, gameName) + }) + gameStartedUnregRef.current = startedReg + + const stoppedReg = SteamClient.Apps.RegisterForGameStopped((_appId: number) => { + onGameStop() + }) + gameStoppedUnregRef.current = stoppedReg + } catch (e) { + console.warn("[DeckyVault] SteamClient event registration failed:", e) + } + + return () => { + try { + gameStartedUnregRef.current?.unregister() + gameStoppedUnregRef.current?.unregister() + } catch { + // ignore + } + } + }, [onGameStart, onGameStop]) + + // ── Handle start recording ──────────────────────────────────── + async function handleStart() { + // Clear any previous log file + await clearMangohudLog() + startRecording() + } + + // ── Handle stop recording: parse log + read system info ──────── + async function handleStop() { + stopRecording() + + // Parse the MangoHud log + const logResult = await readAndParseMangohudLog() + if (logResult.error) { + setError(logResult.error) + // Still transition to stopped state so user can see the error + manual fields + return + } + + // Read system info in parallel + const [hwInfo, osVersion] = await Promise.all([ + getHardwareInfo(), + getOsVersion(), + ]) + + // Read Proton version + launch options if we have an app ID + let protonVersion = "" + let launchOptions = "" + if (session.appId) { + const [pv, lo] = await Promise.all([ + getProtonVersion(session.appId), + getLaunchOptions(session.appId), + ]) + protonVersion = pv + launchOptions = lo + } + + // Use settings hardware override if set, otherwise auto-detected + const hardwareSlug = settings.hardwareSlug || hwInfo.slug + + updateSession({ + fpsAvg: logResult.fpsAvg ?? null, + fpsLow: logResult.fpsLow ?? null, + fpsHigh: logResult.fpsHigh ?? null, + fpsOnePercentLow: logResult.fpsOnePercentLow ?? null, + tdpWatts: logResult.tdpWatts ?? null, + hardwareSlug, + hardwareName: hwInfo.name, + osVersion, + protonVersion, + launchOptions, + }) + } + + if (!loaded) { + return ( + + +
+ Loading... +
+
+
+ ) + } + + return ( + <> + {/* ── Tab navigation ──────────────────────────────────────── */} + +
+ + +
+
+ + {activeTab === "main" ? ( + + ) : ( + + )} + + ) } -let settings: PluginSettings = { - apiKey: "", - autoRecord: false, - exportPath: "/home/deck/Downloads", -} - -export default { - name: "DeckyVault", - content: () => { - // Main plugin UI — will be implemented in a future phase - return
DeckyVault Plugin
- }, - onSettingUpdate: (newSettings: Partial) => { - settings = { ...settings, ...newSettings } - }, - onGameSessionStart: (appId: number) => { - DeckyPlugin.log(`[DeckyVault] Game started: ${appId}`) - // Future: start MangoHud monitoring - }, - onGameSessionEnd: (appId: number) => { - DeckyPlugin.log(`[DeckyVault] Game stopped: ${appId}`) - // Future: stop monitoring, prompt export/upload - }, -} \ No newline at end of file +export default definePlugin(() => { + return { + name: "DeckyVault", + titleView:
DeckyVault
, + content: , + icon: , + alwaysRender: false, + onDismount() { + console.log("[DeckyVault] Plugin unloading") + }, + } +}) \ No newline at end of file