summaryrefslogtreecommitdiff
path: root/frontend/src/hooks/vr-dashboard-bar.tsx
blob: 905aee03ea76a7ee0bd446ad8f898313a8f9c9fe (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
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: <ErrorBoundary>{button.children}</ErrorBoundary>,
          tooltip: button.tooltip,
          popupContents: <ErrorBoundary>{button.popupContents}</ErrorBoundary>,
          onActivate: button.onActivate,
          refBarPopupHandle: button.refBarPopupHandle,
          onPopupVisibilityChanged: button.onPopupVisibilityChanged,
        }),
      );
    }
    this.rendererdRightSideButtons = arr;
  }
}

export default VRDashboardBarHook;