mirror of
https://github.com/SteamDeckHomebrew/decky-loader.git
synced 2026-06-17 08:47:49 +00:00
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>
This commit is contained in:
+135
-134
@@ -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');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user