summaryrefslogtreecommitdiff
path: root/frontend/src/toaster.tsx
diff options
context:
space:
mode:
authorTrainDoctor <traindoctor@protonmail.com>2022-10-30 10:32:05 -0700
committerGitHub <noreply@github.com>2022-10-30 10:32:05 -0700
commitbace5143d28c42ffcc83509b7fcdf02b6cae6934 (patch)
tree5a39a5980a84136df5a6781ba1e200d151112073 /frontend/src/toaster.tsx
parentf5fc2053847d3054d36d3348d21e7de060342698 (diff)
downloaddecky-loader-bace5143d28c42ffcc83509b7fcdf02b6cae6934.tar.gz
decky-loader-bace5143d28c42ffcc83509b7fcdf02b6cae6934.zip
Merge Tabs and Injection Fixes, bring back native Valve toaster (#238)
* Bring back component patch-based tabshook * better injection point * finally fix dumb loading error * fix QAM injection breaking after lock * shut up typescript * fix lock screen focusing issues * Bring back the Valve toaster! * Add support for stable steamos * fix focus bug on lock screen but actually * oops: remove extra console log * shut up typescript again * better fix for lockscreen bug * better probably * actually fix focus issues (WTF) Co-authored-by: AAGaming <aa@mail.catvibers.me>
Diffstat (limited to 'frontend/src/toaster.tsx')
-rw-r--r--frontend/src/toaster.tsx269
1 files changed, 135 insertions, 134 deletions
diff --git a/frontend/src/toaster.tsx b/frontend/src/toaster.tsx
index 94b08d70..728bbdb8 100644
--- a/frontend/src/toaster.tsx
+++ b/frontend/src/toaster.tsx
@@ -1,10 +1,8 @@
-import { Patch, ToastData, sleep } from 'decky-frontend-lib';
+import { Patch, ToastData, afterPatch, findInReactTree, sleep } from 'decky-frontend-lib';
+import { ReactNode } from 'react';
-import DeckyToaster from './components/DeckyToaster';
-import { DeckyToasterState, DeckyToasterStateContextProvider } from './components/DeckyToasterState';
import Toast from './components/Toast';
import Logger from './logger';
-import RouterHook from './router-hook';
declare global {
interface Window {
@@ -14,16 +12,18 @@ declare global {
}
class Toaster extends Logger {
- private instanceRetPatch?: Patch;
- private routerHook: RouterHook;
- private toasterState: DeckyToasterState = new DeckyToasterState();
+ // private routerHook: RouterHook;
+ // private toasterState: DeckyToasterState = new DeckyToasterState();
private node: any;
+ private rNode: any;
private settingsModule: any;
- private ready: boolean = false;
+ private finishStartup?: () => void;
+ private ready: Promise<void> = new Promise((res) => (this.finishStartup = res));
+ private toasterPatch?: Patch;
- constructor(routerHook: RouterHook) {
+ constructor() {
super('Toaster');
- this.routerHook = routerHook;
+ // this.routerHook = routerHook;
window.__TOASTER_INSTANCE?.deinit?.();
window.__TOASTER_INSTANCE = this;
@@ -31,135 +31,136 @@ class Toaster extends Logger {
}
async init() {
- this.routerHook.addGlobalComponent('DeckyToaster', () => (
- <DeckyToasterStateContextProvider deckyToasterState={this.toasterState}>
- <DeckyToaster />
- </DeckyToasterStateContextProvider>
- ));
- // let instance: any;
- // while (true) {
- // instance = findInReactTree(
- // (document.getElementById('root') as any)._reactRootContainer._internalRoot.current,
- // (x) => x?.memoizedProps?.className?.startsWith?.('toastmanager_ToastPlaceholder'),
- // );
- // if (instance) break;
- // this.debug('finding instance');
- // await sleep(2000);
- // }
- // // const windowManager = findModuleChild((m) => {
- // // if (typeof m !== 'object') return false;
- // // for (let prop in m) {
- // // if (m[prop]?.prototype?.GetRenderElement) return m[prop];
- // // }
- // // return false;
- // // });
- // this.node = instance.return.return;
- // let toast: any;
- // let renderedToast: ReactNode = null;
- // console.log(instance, this.node);
- // // replacePatch(window.SteamClient.BrowserView, "Destroy", (args: any[]) => {
- // // console.debug("destroy", args)
- // // return callOriginal;
- // // })
- // // let node = this.node.child.updateQueue.lastEffect;
- // // while (node.next && !node.deckyPatched) {
- // // node = node.next;
- // // if (node.deps[1] == "notificationtoasts") {
- // // console.log("Deleting destroy");
- // // node.deckyPatched = true;
- // // node.create = () => {console.debug("VVVVVVVVVVV")};
- // // node.destroy = () => {console.debug("AAAAAAAAAAAAAAAAaaaaaaaaaaaaaaa")};
- // // }
- // // }
- // this.node.stateNode.render = (...args: any[]) => {
- // const ret = this.node.stateNode.__proto__.render.call(this.node.stateNode, ...args);
- // console.log('toast', ret);
- // if (ret) {
- // console.log(ret)
- // // this.instanceRetPatch = replacePatch(ret, 'type', (innerArgs: any) => {
- // // console.log("inner toast", innerArgs)
- // // // @ts-ignore
- // // const oldEffect = window.SP_REACT.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.ReactCurrentDispatcher.current.useEffect;
- // // // @ts-ignore
- // // window.SP_REACT.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.ReactCurrentDispatcher.current.useEffect = (effect, deps) => {
- // // console.log(effect, deps)
- // // if (deps?.[1] == "notificationtoasts") {
- // // console.log("run")
- // // effect();
- // // }
- // // return oldEffect(effect, deps);
- // // }
- // // const ret = this.instanceRetPatch?.original(...args);
- // // console.log("inner ret", ret)
- // // // @ts-ignore
- // // window.SP_REACT.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.ReactCurrentDispatcher.current.useEffect = oldEffect;
- // // return ret
- // // });
- // }
- // // console.log("toast ret", ret)
- // // if (ret?.props?.children[1]?.children?.props) {
- // // const currentToast = ret.props.children[1].children.props.notification;
- // // if (currentToast?.decky) {
- // // if (currentToast == toast) {
- // // ret.props.children[1].children = renderedToast;
- // // } else {
- // // toast = currentToast;
- // // renderedToast = <Toast toast={toast} />;
- // // ret.props.children[1].children = renderedToast;
- // // }
- // // } else {
- // // toast = null;
- // // renderedToast = null;
- // // }
- // // }
- // // return ret;
- // // });
- // // }
- // return ret;
- // };
- // this.settingsModule = findModuleChild((m) => {
- // if (typeof m !== 'object') return undefined;
- // for (let prop in m) {
- // if (typeof m[prop]?.settings && m[prop]?.communityPreferences) return m[prop];
- // }
- // });
- // // const idx = FocusNavController.m_ActiveContext.m_rgGamepadNavigationTrees.findIndex((x: any) => x.m_ID == "ToastContainer");
- // // if (idx > -1) {
- // // FocusNavController.m_ActiveContext.m_rgGamepadNavigationTrees.splice(idx, 1)
- // // }
- // this.node.stateNode.forceUpdate();
- // this.node.stateNode.shouldComponentUpdate = () => {
- // return false;
- // };
- // this.log('Initialized');
- // this.ready = true;
+ // this.routerHook.addGlobalComponent('DeckyToaster', () => (
+ // <DeckyToasterStateContextProvider deckyToasterState={this.toasterState}>
+ // <DeckyToaster />
+ // </DeckyToasterStateContextProvider>
+ // ));
+ let instance: any;
+ const tree = (document.getElementById('root') as any)._reactRootContainer._internalRoot.current;
+ const findToasterRoot = (currentNode: any, iters: number): any => {
+ if (iters >= 50) {
+ // currently 40
+ return null;
+ }
+ if (currentNode?.memoizedProps?.className?.startsWith?.('toastmanager_ToastPlaceholder')) {
+ this.log(`Toaster root was found in ${iters} recursion cycles`);
+ return currentNode;
+ }
+ if (currentNode.sibling) {
+ let node = findToasterRoot(currentNode.sibling, iters + 1);
+ if (node !== null) return node;
+ }
+ if (currentNode.child) {
+ let node = findToasterRoot(currentNode.child, iters + 1);
+ if (node !== null) return node;
+ }
+ return null;
+ };
+ instance = findToasterRoot(tree, 0);
+ while (!instance) {
+ this.error(
+ 'Failed to find Toaster root node, reattempting in 5 seconds. A developer may need to increase the recursion limit.',
+ );
+ await sleep(5000);
+ instance = findToasterRoot(tree, 0);
+ }
+ this.node = instance.return;
+ this.rNode = this.node.return;
+ let toast: any;
+ let renderedToast: ReactNode = null;
+ let innerPatched: any;
+ const repatch = () => {
+ if (this.node && !this.node.type.decky) {
+ this.toasterPatch = afterPatch(this.node, 'type', (_: any, ret: any) => {
+ const inner = findInReactTree(ret.props.children, (x) => x?.props?.onDismiss);
+ if (innerPatched) {
+ inner.type = innerPatched;
+ } else {
+ afterPatch(inner, 'type', (innerArgs: any, ret: any) => {
+ const currentToast = innerArgs[0]?.notification;
+ if (currentToast?.decky) {
+ if (currentToast == toast) {
+ ret.props.children = renderedToast;
+ } else {
+ toast = currentToast;
+ renderedToast = <Toast toast={toast.data} />;
+ ret.props.children = renderedToast;
+ }
+ } else {
+ toast = null;
+ renderedToast = null;
+ }
+ return ret;
+ });
+ innerPatched = inner.type;
+ }
+ return ret;
+ });
+ this.node.type.decky = true;
+ this.node.alternate.type = this.node.type;
+ }
+ };
+ const oRender = this.rNode.stateNode.__proto__.render;
+ let int: NodeJS.Timer | undefined;
+ this.rNode.stateNode.render = (...args: any[]) => {
+ const ret = oRender.call(this.rNode.stateNode, ...args);
+ if (ret && !this?.node?.return?.return) {
+ clearInterval(int);
+ int = setInterval(() => {
+ const n = findToasterRoot(tree, 0);
+ if (n?.return) {
+ clearInterval(int);
+ this.node = n.return;
+ this.rNode = this.node.return;
+ repatch();
+ } else {
+ this.error('Failed to re-grab Toaster node, trying again...');
+ }
+ }, 1200);
+ }
+ repatch();
+ return ret;
+ };
+
+ this.rNode.stateNode.shouldComponentUpdate = () => true;
+ this.rNode.stateNode.forceUpdate();
+ delete this.rNode.stateNode.shouldComponentUpdate;
+
+ this.log('Initialized');
+ this.finishStartup?.();
}
- toast(toast: ToastData) {
- toast.duration = toast.duration || 5e3;
- this.toasterState.addToast(toast);
- // const settings = this.settingsModule?.settings;
- // let toastData = {
- // nNotificationID: window.NotificationStore.m_nNextTestNotificationID++,
- // rtCreated: Date.now(),
- // eType: 15,
- // nToastDurationMS: toast.duration || 5e3,
- // data: toast,
- // decky: true,
- // };
- // // @ts-ignore
- // toastData.data.appid = () => 0;
- // if (
- // (settings?.bDisableAllToasts && !toast.critical) ||
- // (settings?.bDisableToastsInGame && !toast.critical && window.NotificationStore.BIsUserInGame())
- // )
- // return;
- // window.NotificationStore.m_rgNotificationToasts.push(toastData);
- // window.NotificationStore.DispatchNextToast();
+ async toast(toast: ToastData) {
+ // toast.duration = toast.duration || 5e3;
+ // this.toasterState.addToast(toast);
+ await this.ready;
+ const settings = this.settingsModule?.settings;
+ let toastData = {
+ nNotificationID: window.NotificationStore.m_nNextTestNotificationID++,
+ rtCreated: Date.now(),
+ eType: 15,
+ nToastDurationMS: toast.duration || (toast.duration = 5e3),
+ data: toast,
+ decky: true,
+ };
+ // @ts-ignore
+ toastData.data.appid = () => 0;
+ if (
+ (settings?.bDisableAllToasts && !toast.critical) ||
+ (settings?.bDisableToastsInGame && !toast.critical && window.NotificationStore.BIsUserInGame())
+ )
+ return;
+ window.NotificationStore.m_rgNotificationToasts.push(toastData);
+ window.NotificationStore.DispatchNextToast();
}
deinit() {
- this.routerHook.removeGlobalComponent('DeckyToaster');
+ this.toasterPatch?.unpatch();
+ this.node.alternate.type = this.node.type;
+ delete this.rNode.stateNode.render;
+ this.ready = new Promise((res) => (this.finishStartup = res));
+ // this.routerHook.removeGlobalComponent('DeckyToaster');
}
}