From b27b625921212648748f056cd704dda0267106c5 Mon Sep 17 00:00:00 2001
From: MIkhail Kozlov <mishakozlov74@gmail.com>
Date: Tue, 30 May 2023 23:53:48 -0700
Subject: Refactor TabsHook (#458)

---
 frontend/src/tabs-hook.tsx | 133 ++++++++++++++++++++-------------------------
 1 file changed, 60 insertions(+), 73 deletions(-)

(limited to 'frontend/src/tabs-hook.tsx')

diff --git a/frontend/src/tabs-hook.tsx b/frontend/src/tabs-hook.tsx
index 08206c14..5d708fcb 100644
--- a/frontend/src/tabs-hook.tsx
+++ b/frontend/src/tabs-hook.tsx
@@ -1,5 +1,14 @@
 // TabsHook for versions after the Desktop merge
-import { Patch, QuickAccessTab, afterPatch, findInReactTree, sleep } from 'decky-frontend-lib';
+import {
+  Patch,
+  QuickAccessTab,
+  afterPatch,
+  findInReactTree,
+  findSP,
+  gamepadUIClasses,
+  getReactInstance,
+  sleep,
+} from 'decky-frontend-lib';
 
 import { QuickAccessVisibleStateProvider } from './components/QuickAccessVisibleState';
 import Logger from './logger';
@@ -31,82 +40,60 @@ class TabsHook extends Logger {
     window.__TABS_HOOK_INSTANCE = this;
   }
 
-  init() {
-    const tree = (document.getElementById('root') as any)._reactRootContainer._internalRoot.current;
-    let qAMRoot: any;
-    const findQAMRoot = (currentNode: any, iters: number): any => {
-      if (iters >= 65) {
-        // currently 45
-        return null;
-      }
-      if (
-        typeof currentNode?.memoizedProps?.visible == 'boolean' &&
-        currentNode?.type?.toString()?.includes('QuickAccessMenuBrowserView')
-      ) {
-        this.log(`QAM root was found in ${iters} recursion cycles`);
-        return currentNode;
-      }
-      if (currentNode.child) {
-        let node = findQAMRoot(currentNode.child, iters + 1);
-        if (node !== null) return node;
-      }
-      if (currentNode.sibling) {
-        let node = findQAMRoot(currentNode.sibling, iters + 1);
-        if (node !== null) return node;
-      }
-      return null;
-    };
-    (async () => {
-      qAMRoot = findQAMRoot(tree, 0);
-      while (!qAMRoot) {
-        this.error(
-          'Failed to find QAM root node, reattempting in 5 seconds. A developer may need to increase the recursion limit.',
-        );
-        await sleep(5000);
-        qAMRoot = findQAMRoot(tree, 0);
-      }
-      this.qAMRoot = qAMRoot;
-      let patchedInnerQAM: any;
-      this.qamPatch = afterPatch(qAMRoot.return, 'type', (_: any, ret: any) => {
-        try {
-          if (!qAMRoot?.child) {
-            qAMRoot = findQAMRoot(tree, 0);
-            this.qAMRoot = qAMRoot;
-          }
-          if (qAMRoot?.child && !qAMRoot?.child?.type?.decky) {
-            afterPatch(qAMRoot.child, 'type', (_: any, ret: any) => {
-              try {
-                const qamTabsRenderer = findInReactTree(ret, (x) => x?.props?.onFocusNavDeactivated);
-                if (patchedInnerQAM) {
-                  qamTabsRenderer.type = patchedInnerQAM;
-                } else {
-                  afterPatch(qamTabsRenderer, 'type', (innerArgs: any, ret: any) => {
-                    const tabs = findInReactTree(ret, (x) => x?.props?.tabs);
-                    this.render(tabs.props.tabs, innerArgs[0].visible);
-                    return ret;
-                  });
-                  patchedInnerQAM = qamTabsRenderer.type;
-                }
-              } catch (e) {
-                this.error('Error patching QAM inner', e);
+  async init() {
+    this.qAMRoot = await this.getQAMRoot();
+
+    let patchedInnerQAM: any;
+    this.qamPatch = afterPatch(this.qAMRoot.return, 'type', (_: any, ret: any) => {
+      try {
+        if (this.qAMRoot?.child && !this.qAMRoot?.child?.type?.decky) {
+          afterPatch(this.qAMRoot.child, 'type', (_: any, ret: any) => {
+            try {
+              const qamTabsRenderer = findInReactTree(ret, (x) => x?.props?.onFocusNavDeactivated);
+              if (patchedInnerQAM) {
+                qamTabsRenderer.type = patchedInnerQAM;
+              } else {
+                afterPatch(qamTabsRenderer, 'type', (innerArgs: any, ret: any) => {
+                  const tabs = findInReactTree(ret, (x) => x?.props?.tabs);
+                  this.render(tabs.props.tabs, innerArgs[0].visible);
+                  return ret;
+                });
+                patchedInnerQAM = qamTabsRenderer.type;
               }
-              return ret;
-            });
-            qAMRoot.child.type.decky = true;
-            qAMRoot.child.alternate.type = qAMRoot.child.type;
-          }
-        } catch (e) {
-          this.error('Error patching QAM', e);
+            } catch (e) {
+              this.error('Error patching QAM inner', e);
+            }
+            return ret;
+          });
+          this.qAMRoot.child.type.decky = true;
+          this.qAMRoot.child.alternate.type = this.qAMRoot.child.type;
         }
+      } catch (e) {
+        this.error('Error patching QAM', e);
+      }
 
-        return ret;
-      });
+      return ret;
+    });
 
-      if (qAMRoot.return.alternate) {
-        qAMRoot.return.alternate.type = qAMRoot.return.type;
-      }
-      this.log('Finished initial injection');
-    })();
+    if (this.qAMRoot.return.alternate) {
+      this.qAMRoot.return.alternate.type = this.qAMRoot.return.type;
+    }
+    this.log('Finished initial injection');
+  }
+
+  async getQAMRoot() {
+    while (!findSP()) {
+      await sleep(50);
+    }
+
+    const parentNode = findSP().document.querySelector(`.${gamepadUIClasses.BasicUiRoot}`);
+    if (!parentNode) return null;
+
+    return findInReactTree(
+      getReactInstance(parentNode),
+      (n) =>
+        typeof n.memoizedProps?.visible !== 'undefined' && n.type?.toString()?.includes('QuickAccessMenuBrowserView'),
+    );
   }
 
   deinit() {
-- 
cgit v1.2.3

