feat(plugin): wire together entry point with definePlugin and SteamClient events
This commit is contained in:
@@ -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)
|
||||
}
|
||||
|
||||
let settings: PluginSettings = {
|
||||
apiKey: "",
|
||||
autoRecord: false,
|
||||
exportPath: "/home/deck/Downloads",
|
||||
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()
|
||||
}
|
||||
|
||||
export default {
|
||||
// ── 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}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default definePlugin(() => {
|
||||
return {
|
||||
name: "DeckyVault",
|
||||
content: () => {
|
||||
// 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
|
||||
titleView: <div className={staticClasses.Title}>DeckyVault</div>,
|
||||
content: <Content />,
|
||||
icon: <FaDatabase />,
|
||||
alwaysRender: false,
|
||||
onDismount() {
|
||||
console.log("[DeckyVault] Plugin unloading")
|
||||
},
|
||||
}
|
||||
})
|
||||
Reference in New Issue
Block a user