diff options
| author | TrainDoctor <traindoctor@protonmail.com> | 2022-10-30 10:32:05 -0700 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2022-10-30 10:32:05 -0700 |
| commit | bace5143d28c42ffcc83509b7fcdf02b6cae6934 (patch) | |
| tree | 5a39a5980a84136df5a6781ba1e200d151112073 /frontend/src/toaster.tsx | |
| parent | f5fc2053847d3054d36d3348d21e7de060342698 (diff) | |
| download | decky-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.tsx | 269 |
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'); } } |
