feat(plugin): wire together entry point with definePlugin and SteamClient events

This commit is contained in:
2026-06-28 07:59:39 +08:00
parent 82fbd86fb0
commit 793d7bb9f4
+212 -29
View File
@@ -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 { function Content() {
apiKey: string const { settings, updateSetting, loaded } = useSettings()
autoRecord: boolean const {
exportPath: string 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 (
<PanelSection title="DeckyVault">
<PanelSectionRow>
<div className={staticClasses.Text} style={{ padding: "16px", textAlign: "center" }}>
Loading...
</div>
</PanelSectionRow>
</PanelSection>
)
}
return (
<>
{/* ── Tab navigation ──────────────────────────────────────── */}
<PanelSectionRow>
<div style={{ display: "flex", gap: "0", marginBottom: "8px" }}>
<button
onClick={() => setActiveTab("main")}
style={{
flex: 1,
padding: "8px",
background: activeTab === "main" ? "rgba(255,255,255,0.15)" : "transparent",
border: "none",
color: activeTab === "main" ? "#fff" : "rgba(255,255,255,0.5)",
cursor: "pointer",
borderRadius: "4px 0 0 4px",
}}
>
Record
</button>
<button
onClick={() => setActiveTab("settings")}
style={{
flex: 1,
padding: "8px",
background: activeTab === "settings" ? "rgba(255,255,255,0.15)" : "transparent",
border: "none",
color: activeTab === "settings" ? "#fff" : "rgba(255,255,255,0.5)",
cursor: "pointer",
borderRadius: "0 4px 4px 0",
}}
>
Settings
</button>
</div>
</PanelSectionRow>
{activeTab === "main" ? (
<MainPanel
recordingState={recordingState}
session={session}
recentSessions={recentSessions}
error={error}
settings={settings}
onStart={handleStart}
onStop={handleStop}
onUpdateSession={updateSession}
onAddToRecent={addToRecent}
onReset={reset}
setError={setError}
/>
) : (
<SettingsPanel
settings={settings}
onUpdateSetting={updateSetting}
/>
)}
</>
)
} }
let settings: PluginSettings = { export default definePlugin(() => {
apiKey: "", return {
autoRecord: false, name: "DeckyVault",
exportPath: "/home/deck/Downloads", titleView: <div className={staticClasses.Title}>DeckyVault</div>,
} content: <Content />,
icon: <FaDatabase />,
export default { alwaysRender: false,
name: "DeckyVault", onDismount() {
content: () => { console.log("[DeckyVault] Plugin unloading")
// Main plugin UI — will be implemented in a future phase },
return <div>DeckyVault Plugin</div> }
}, })
onSettingUpdate: (newSettings: Partial<PluginSettings>) => {
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
},
}