From e82a55e9582fbd6d84f3f4510b04480c35e41fe4 Mon Sep 17 00:00:00 2001 From: AAGaming Date: Mon, 26 Oct 2026 23:50:59 -0400 Subject: feat: initial VERY VERY incomplete steam frame support --- frontend/src/hooks/vr-dashboard-bar.tsx | 105 ++++++++++++++++++++++++++++++++ 1 file changed, 105 insertions(+) create mode 100644 frontend/src/hooks/vr-dashboard-bar.tsx (limited to 'frontend/src/hooks/vr-dashboard-bar.tsx') diff --git a/frontend/src/hooks/vr-dashboard-bar.tsx b/frontend/src/hooks/vr-dashboard-bar.tsx new file mode 100644 index 00000000..905aee03 --- /dev/null +++ b/frontend/src/hooks/vr-dashboard-bar.tsx @@ -0,0 +1,105 @@ +// Hook the SteamVR Hotbar +import { ErrorBoundary, Patch, afterPatch, findInReactTree, findModuleExport, getReactRoot } from '@decky/ui'; +import { ReactNode, createElement } from 'react'; + +import Logger from '../logger'; + +declare global { + interface Window { + __VR_DASHBAORD_BAR_HOOK_INSTANCE: any; + } +} + +// TODO kinda dumb +enum VRDashboardBarButtonID { + Decky = 999, +} + +export interface VRDashboardBarButton { + id: VRDashboardBarButtonID | number; + active?: boolean; + children?: ReactNode; + tooltip?: ReactNode; + popupContents?: ReactNode; + onActivate?: () => void; + refBarPopupHandle?: any; // TODO idk + onPopupVisibilityChanged?: (visible: boolean) => void; +} + +class VRDashboardBarHook extends Logger { + rightSideButtons: VRDashboardBarButton[] = []; + private rendererdRightSideButtons: ReactNode[] = []; + private barPatch?: Patch; + private barButton?: any; + + constructor() { + super('VRDashboardBarHook'); + + this.log('Initialized'); + window.__VR_DASHBAORD_BAR_HOOK_INSTANCE?.deinit?.(); + window.__VR_DASHBAORD_BAR_HOOK_INSTANCE = this; + } + + init() { + this.barButton = findModuleExport((e) => e?.render?.toString()?.includes('VRDashboardBarSmallButtonActive')); + const barComponent = findModuleExport((e) => e?.type?.toString()?.includes('vrLocation=="dashboard-bar"')); + + this.barPatch = afterPatch(barComponent, 'type', (_, ret) => { + const nodes = findInReactTree(ret, (x) => x?.length > 6); + if (!window?.securitystore?.IsLockScreenActive?.()) nodes.splice(4, 0, this.rendererdRightSideButtons); + return ret; + }); + + // Patch already rendered bar + const root = getReactRoot(document.getElementById('root') as any); + const barNode = + root && + findInReactTree( + root, + (n: any) => n.elementType == barComponent || (barComponent != null && n.elementType == barComponent), + ); // need elementType, because case where type is actually mobx wrapper + if (barNode) { + // Only affects this fiber node so we don't need to unpatch here + barNode.type = barNode.elementType.type; + if (barNode?.alternate) { + barNode.alternate.type = barNode.type; + } + } + } + + deinit() { + this.barPatch?.unpatch(); + } + + addRightSideButton(button: VRDashboardBarButton) { + this.debug('Adding RSB', button.id, 'to render array'); + this.rightSideButtons.push(button); + this.renderRightSideButtons(); + } + + removeRightSideButton(id: number) { + this.debug('Removing RSB', id); + this.rightSideButtons = this.rightSideButtons.filter((button) => button.id !== id); + this.renderRightSideButtons(); + } + + renderRightSideButtons() { + const arr = []; + for (const button of this.rightSideButtons) { + arr.push( + createElement(this.barButton, { + active: button.active, + children: {button.children}, + tooltip: button.tooltip, + popupContents: {button.popupContents}, + onActivate: button.onActivate, + refBarPopupHandle: button.refBarPopupHandle, + onPopupVisibilityChanged: button.onPopupVisibilityChanged, + }), + ); + } + this.rendererdRightSideButtons = arr; + } +} + +export default VRDashboardBarHook; -- cgit v1.2.3