diff options
| author | AAGaming <aa@mail.catvibers.me> | 2022-10-24 19:14:56 -0400 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2022-10-24 16:14:56 -0700 |
| commit | 84c3b039c385ad872bb0f22eba7a3d2cd4a5ea10 (patch) | |
| tree | 20b13066c6256cc6ca1beac085094c7964226a37 /backend | |
| parent | 2e6b3834da357c7e81821ce60bad36f54dd9fa6e (diff) | |
| download | decky-loader-84c3b039c385ad872bb0f22eba7a3d2cd4a5ea10.tar.gz decky-loader-84c3b039c385ad872bb0f22eba7a3d2cd4a5ea10.zip | |
preview 10/21/2022 fixes (#234)
* initial fixes: everything working except toasts and patch notes
* tabshook changes, disable toaster for now
* prettier
* oops
* implement custom toaster because I am tired of Valve's shit
also fix QAM not injecting sometimes
* remove extra logging
* add findSP, fix patch notes, fix vscode screwup
* fix patch notes
* show error when plugin frontends fail to load
* add get_tab_lambda
* add css and has_element helpers to Tab
* small modals fixup
* Don't forceUpdate QuickAccess on stable
* add routes prop used to get tabs component
* add more dev utils to DFL global
Diffstat (limited to 'backend')
| -rw-r--r-- | backend/browser.py | 6 | ||||
| -rw-r--r-- | backend/injector.py | 142 | ||||
| -rw-r--r-- | backend/loader.py | 5 | ||||
| -rw-r--r-- | backend/main.py | 54 | ||||
| -rw-r--r-- | backend/updater.py | 6 | ||||
| -rw-r--r-- | backend/utilities.py | 6 |
6 files changed, 169 insertions, 50 deletions
diff --git a/backend/browser.py b/backend/browser.py index 3007ae18..69f82bdb 100644 --- a/backend/browser.py +++ b/backend/browser.py @@ -16,7 +16,7 @@ from zipfile import ZipFile # Local modules from helpers import get_ssl_context, get_user, get_user_group, download_remote_binary_to_path -from injector import get_tab, inject_to_tab +from injector import get_gamepadui_tab logger = getLogger("Browser") @@ -104,7 +104,7 @@ class PluginBrowser: async def uninstall_plugin(self, name): if self.loader.watcher: self.loader.watcher.disabled = True - tab = await get_tab("SP") + tab = await get_gamepadui_tab() try: logger.info("uninstalling " + name) logger.info(" at dir " + self.find_plugin_folder(name)) @@ -172,7 +172,7 @@ class PluginBrowser: async def request_plugin_install(self, artifact, name, version, hash): request_id = str(time()) self.install_requests[request_id] = PluginInstallContext(artifact, name, version, hash) - tab = await get_tab("SP") + tab = await get_gamepadui_tab() await tab.open_websocket() await tab.evaluate_js(f"DeckyPluginLoader.addPluginInstallPrompt('{name}', '{version}', '{request_id}', '{hash}')") diff --git a/backend/injector.py b/backend/injector.py index de914d17..c0d9cbad 100644 --- a/backend/injector.py +++ b/backend/injector.py @@ -3,6 +3,7 @@ from asyncio import sleep from logging import getLogger from traceback import format_exc +from typing import List from aiohttp import ClientSession from aiohttp.client_exceptions import ClientConnectorError @@ -18,6 +19,7 @@ class Tab: def __init__(self, res) -> None: self.title = res["title"] self.id = res["id"] + self.url = res["url"] self.ws_url = res["webSocketDebuggerUrl"] self.websocket = None @@ -64,6 +66,26 @@ class Tab: await self.close_websocket() return res + async def has_global_var(self, var_name, manage_socket=True): + res = await self.evaluate_js(f"window['{var_name}'] !== null && window['{var_name}'] !== undefined", False, manage_socket) + + if not "result" in res or not "result" in res["result"] or not "value" in res["result"]["result"]: + return False + + return res["result"]["result"]["value"] + + async def close(self, manage_socket=True): + if manage_socket: + await self.open_websocket() + + res = await self._send_devtools_cmd({ + "method": "Page.close", + }, False) + + if manage_socket: + await self.close_websocket() + return res + async def enable(self): """ Enables page domain notifications. @@ -80,6 +102,18 @@ class Tab: "method": "Page.disable", }, False) + async def refresh(self): + if manage_socket: + await self.open_websocket() + + await self._send_devtools_cmd({ + "method": "Page.reload", + }, False) + + if manage_socket: + await self.close_websocket() + + return async def reload_and_evaluate(self, js, manage_socket=True): """ Reloads the current tab, with JS to run on load via debugger @@ -227,6 +261,71 @@ class Tab: if manage_socket: await self.close_websocket() + async def has_element(self, element_name, manage_socket=True): + res = await self.evaluate_js(f"document.getElementById('{element_name}') != null", False, manage_socket) + + if not "result" in res or not "result" in res["result"] or not "value" in res["result"]["result"]: + return False + + return res["result"]["result"]["value"] + + async def inject_css(self, style, manage_socket=True): + try: + css_id = str(uuid.uuid4()) + + result = await self.evaluate_js( + f""" + (function() {{ + const style = document.createElement('style'); + style.id = "{css_id}"; + document.head.append(style); + style.textContent = `{style}`; + }})() + """, False, manage_socket) + + if "exceptionDetails" in result["result"]: + return { + "success": False, + "result": result["result"] + } + + return { + "success": True, + "result": css_id + } + except Exception as e: + return { + "success": False, + "result": e + } + + async def remove_css(self, css_id, manage_socket=True): + try: + result = await self.evaluate_js( + f""" + (function() {{ + let style = document.getElementById("{css_id}"); + + if (style.nodeName.toLowerCase() == 'style') + style.parentNode.removeChild(style); + }})() + """, False, manage_socket) + + if "exceptionDetails" in result["result"]: + return { + "success": False, + "result": result + } + + return { + "success": True + } + except Exception as e: + return { + "success": False, + "result": e + } + async def get_steam_resource(self, url): res = await self.evaluate_js(f'(async function test() {{ return await (await fetch("{url}")).text() }})()', True) return res["result"]["result"]["value"] @@ -235,7 +334,7 @@ class Tab: return self.title -async def get_tabs(): +async def get_tabs() -> List[Tab]: async with ClientSession() as web: res = {} @@ -257,41 +356,28 @@ async def get_tabs(): raise Exception(f"/json did not return 200. {await res.text()}") -async def get_tab(tab_name): +async def get_tab(tab_name) -> Tab: tabs = await get_tabs() tab = next((i for i in tabs if i.title == tab_name), None) if not tab: raise ValueError(f"Tab {tab_name} not found") return tab +async def get_tab_lambda(test) -> Tab: + tabs = await get_tabs() + tab = next((i for i in tabs if test(i)), None) + if not tab: + raise ValueError(f"Tab not found by lambda") + return tab + +async def get_gamepadui_tab() -> Tab: + tabs = await get_tabs() + tab = next((i for i in tabs if ("https://steamloopback.host/routes/" in i.url and (i.title == "Steam" or i.title == "SP"))), None) + if not tab: + raise ValueError(f"GamepadUI Tab not found") + return tab async def inject_to_tab(tab_name, js, run_async=False): tab = await get_tab(tab_name) return await tab.evaluate_js(js, run_async) - - -async def tab_has_global_var(tab_name, var_name): - try: - tab = await get_tab(tab_name) - except ValueError: - return False - res = await tab.evaluate_js(f"window['{var_name}'] !== null && window['{var_name}'] !== undefined", False) - - if not "result" in res or not "result" in res["result"] or not "value" in res["result"]["result"]: - return False - - return res["result"]["result"]["value"] - - -async def tab_has_element(tab_name, element_name): - try: - tab = await get_tab(tab_name) - except ValueError: - return False - res = await tab.evaluate_js(f"document.getElementById('{element_name}') != null", False) - - if not "result" in res or not "result" in res["result"] or not "value" in res["result"]["result"]: - return False - - return res["result"]["result"]["value"] diff --git a/backend/loader.py b/backend/loader.py index b4559180..8c48c7ae 100644 --- a/backend/loader.py +++ b/backend/loader.py @@ -15,7 +15,7 @@ try: except UnsupportedLibc: from watchdog.observers.fsevents import FSEventsObserver as Observer -from injector import get_tab, inject_to_tab +from injector import get_tab, get_gamepadui_tab from plugin import PluginWrapper @@ -141,7 +141,8 @@ class Loader: print_exc() async def dispatch_plugin(self, name, version): - await inject_to_tab("SP", f"window.importDeckyPlugin('{name}', '{version}')") + gpui_tab = await get_gamepadui_tab() + await gpui_tab.evaluate_js(f"window.importDeckyPlugin('{name}', '{version}')") def import_plugins(self): self.logger.info(f"import plugins from {self.plugin_path}") diff --git a/backend/main.py b/backend/main.py index c3f43078..563755ca 100644 --- a/backend/main.py +++ b/backend/main.py @@ -12,7 +12,7 @@ from traceback import format_exc import aiohttp_cors # Partial imports -from aiohttp import ClientSession +from aiohttp import ClientSession, client_exceptions from aiohttp.web import Application, Response, get, run_app, static from aiohttp_jinja2 import setup as jinja_setup @@ -22,7 +22,7 @@ from helpers import (REMOTE_DEBUGGER_UNIT, csrf_middleware, get_csrf_token, get_home_path, get_homebrew_path, get_user, get_user_group, set_user, set_user_group, stop_systemd_unit) -from injector import inject_to_tab, tab_has_global_var +from injector import get_gamepadui_tab, Tab, get_tabs from loader import Loader from settings import SettingsManager from updater import Updater @@ -118,17 +118,49 @@ class PluginManager: # await inject_to_tab("SP", "window.syncDeckyPlugins();") async def loader_reinjector(self): - await sleep(2) - await self.inject_javascript() while True: - await sleep(5) - if not await tab_has_global_var("SP", "deckyHasLoaded"): - logger.info("Plugin loader isn't present in Steam anymore, reinjecting...") - await self.inject_javascript() - - async def inject_javascript(self, request=None): + tab = None + while not tab: + try: + tab = await get_gamepadui_tab() + except client_exceptions.ClientConnectorError or client_exceptions.ServerDisconnectedError: + logger.debug("Couldn't connect to debugger, waiting 5 seconds.") + pass + except ValueError: + logger.debug("Couldn't find GamepadUI tab, waiting 5 seconds") + pass + if not tab: + await sleep(5) + await tab.open_websocket() + await tab.enable() + await self.inject_javascript(tab, True) + async for msg in tab.listen_for_message(): + logger.debug("Page event: " + str(msg.get("method", None))) + if msg.get("method", None) == "Page.domContentEventFired": + if not await tab.has_global_var("deckyHasLoaded", False): + await self.inject_javascript(tab) + if msg.get("method", None) == "Inspector.detached": + logger.info("Steam is exiting...") + await tab.close_websocket() + break + # while True: + # await sleep(5) + # if not await tab.has_global_var("deckyHasLoaded", False): + # logger.info("Plugin loader isn't present in Steam anymore, reinjecting...") + # await self.inject_javascript(tab) + + async def inject_javascript(self, tab: Tab, first=False, request=None): + logger.info("Loading Decky frontend!") try: - await inject_to_tab("SP", "try{if (window.deckyHasLoaded) location.reload();window.deckyHasLoaded = true;(async()=>{while(!window.SP_REACT){await new Promise(r => setTimeout(r, 10))};await import('http://localhost:1337/frontend/index.js')})();}catch(e){console.error(e)}", True) + if first: + if await tab.has_global_var("deckyHasLoaded", False): + tabs = await get_tabs() + for t in tabs: + if t.title != "Steam" and t.title != "SP": + logger.debug("Closing tab: " + getattr(t, "title", "Untitled")) + await t.close() + await sleep(0.5) + await tab.evaluate_js("try{if (window.deckyHasLoaded){setTimeout(() => location.reload(), 1000)}window.deckyHasLoaded = true;(async()=>{while(!window.SP_REACT){await new Promise(r => setTimeout(r, 10))};await import('http://localhost:1337/frontend/index.js')})();}catch(e){console.error(e)}", False, False, False) except: logger.info("Failed to inject JavaScript into tab\n" + format_exc()) pass diff --git a/backend/updater.py b/backend/updater.py index ed1520ab..a209f103 100644 --- a/backend/updater.py +++ b/backend/updater.py @@ -9,7 +9,7 @@ from subprocess import call from aiohttp import ClientSession, web import helpers -from injector import get_tab, inject_to_tab +from injector import get_gamepadui_tab, inject_to_tab from settings import SettingsManager logger = getLogger("Updater") @@ -108,7 +108,7 @@ class Updater: logger.error("release type: NOT FOUND") raise ValueError("no valid branch found") logger.info("Updated remote version information") - tab = await get_tab("SP") + tab = await get_gamepadui_tab() await tab.evaluate_js(f"window.DeckyPluginLoader.notifyUpdates()", False, True, False) return await self.get_version() @@ -125,7 +125,7 @@ class Updater: version = self.remoteVer["tag_name"] download_url = self.remoteVer["assets"][0]["browser_download_url"] - tab = await get_tab("SP") + tab = await get_gamepadui_tab() await tab.open_websocket() async with ClientSession() as web: async with web.request("GET", download_url, ssl=helpers.get_ssl_context(), allow_redirects=True) as res: diff --git a/backend/utilities.py b/backend/utilities.py index 8d899c0d..8351f3e0 100644 --- a/backend/utilities.py +++ b/backend/utilities.py @@ -7,7 +7,7 @@ from asyncio import sleep, start_server, gather, open_connection from aiohttp import ClientSession, web from logging import getLogger -from injector import inject_to_tab, get_tab +from injector import inject_to_tab, get_gamepadui_tab import helpers import subprocess @@ -248,7 +248,7 @@ class Utilities: self.start_rdt_proxy(ip, 8097) script = "if(!window.deckyHasConnectedRDT){window.deckyHasConnectedRDT=true;\n" + await res.text() + "\n}" self.logger.info("Connected to React DevTools, loading script") - tab = await get_tab("SP") + tab = await get_gamepadui_tab() # RDT needs to load before React itself to work. result = await tab.reload_and_evaluate(script) self.logger.info(result) @@ -259,7 +259,7 @@ class Utilities: async def disable_rdt(self): self.logger.info("Disabling React DevTools") - tab = await get_tab("SP") + tab = await get_gamepadui_tab() self.rdt_script_id = None await tab.evaluate_js("location.reload();", False, True, False) self.logger.info("React DevTools disabled") |
