// 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;