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 {
|
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
|
|
||||||
},
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user