mirror of
https://github.com/SteamDeckHomebrew/decky-loader.git
synced 2026-07-10 23:11:56 +00:00
Compare commits
29 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b3a5f9659f | |||
| d444248f62 | |||
| 9570d0b0c2 | |||
| 8a90605a64 | |||
| 9d0dbbb790 | |||
| 2303023ad4 | |||
| 599279c137 | |||
| 44be6ba3dd | |||
| 5e270de990 | |||
| 52b40b7d0c | |||
| 3cb150b9fb | |||
| bac1ef5f4d | |||
| d9ab176bc7 | |||
| ff856c7148 | |||
| 62293d2316 | |||
| e00d517119 | |||
| 241aec26e8 | |||
| 491d298fd7 | |||
| 262b62ea4f | |||
| 4cde25c43e | |||
| b8bf9f343c | |||
| 4cf80595ad | |||
| 4c23549748 | |||
| 7b21e81caa | |||
| c2443ee2c5 | |||
| df52ebe7ed | |||
| b93fc8b557 | |||
| 88e7919a12 | |||
| 28c7254ef6 |
Vendored
+1
-1
@@ -120,7 +120,7 @@
|
|||||||
"dependsOn": [
|
"dependsOn": [
|
||||||
"checkforsettings"
|
"checkforsettings"
|
||||||
],
|
],
|
||||||
"command": "ssh -t ${config:deckuser}@${config:deckip} -p ${config:deckport} ${config:deckkey} 'export PATH=${config:deckdir}/.local/bin:$PATH; export PLUGIN_PATH=${config:deckdir}/homebrew/plugins; export CHOWN_PLUGIN_PATH=0; export LOG_LEVEL=DEBUG; cd ${config:deckdir}/homebrew/dev/pluginloader/backend; echo '${config:deckpass}' | poetry run sh -c \"cd ${config:deckdir}/homebrew/services; sudo -SE env \"PATH=\\$PATH\" python3 ${config:deckdir}/homebrew/dev/pluginloader/backend/main.py\"'",
|
"command": "ssh ${config:deckuser}@${config:deckip} -p ${config:deckport} ${config:deckkey} 'export PATH=${config:deckdir}/.local/bin:$PATH; export PLUGIN_PATH=${config:deckdir}/homebrew/plugins; export CHOWN_PLUGIN_PATH=0; export LOG_LEVEL=DEBUG; cd ${config:deckdir}/homebrew/dev/pluginloader/backend; echo '${config:deckpass}' | poetry run sh -c \"cd ${config:deckdir}/homebrew/services; sudo -SE env \"PATH=\\$PATH\" python3 ${config:deckdir}/homebrew/dev/pluginloader/backend/main.py\"'",
|
||||||
"problemMatcher": []
|
"problemMatcher": []
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -52,9 +52,6 @@ async def csrf_middleware(request: Request, handler: Handler):
|
|||||||
return await handler(request)
|
return await handler(request)
|
||||||
return Response(text='Forbidden', status=403)
|
return Response(text='Forbidden', status=403)
|
||||||
|
|
||||||
def create_inject_script(script: str) -> str:
|
|
||||||
return "try{if (window.deckyHasLoaded){setTimeout(() => SteamClient.Browser.RestartJSContext(), 100)}else{window.deckyHasLoaded = true;(async()=>{try{await import('http://localhost:1337/frontend/%s?v=%s')}catch(e){console.error(e)};})();}}catch(e){console.error(e)}" % (script, get_loader_version(), )
|
|
||||||
|
|
||||||
# Get the default homebrew path unless a home_path is specified. home_path argument is deprecated
|
# Get the default homebrew path unless a home_path is specified. home_path argument is deprecated
|
||||||
def get_homebrew_path() -> str:
|
def get_homebrew_path() -> str:
|
||||||
return localplatform.get_unprivileged_path()
|
return localplatform.get_unprivileged_path()
|
||||||
@@ -96,11 +93,7 @@ def get_system_pythonpaths() -> list[str]:
|
|||||||
proc = subprocess.run(["python3" if localplatform.ON_LINUX else "python", "-c", "import sys; print('\\n'.join(x for x in sys.path if x))"],
|
proc = subprocess.run(["python3" if localplatform.ON_LINUX else "python", "-c", "import sys; print('\\n'.join(x for x in sys.path if x))"],
|
||||||
# TODO make this less insane
|
# TODO make this less insane
|
||||||
capture_output=True, user=localplatform.localplatform._get_user_id() if localplatform.ON_LINUX else None, env={} if localplatform.ON_LINUX else None) # pyright: ignore [reportPrivateUsage]
|
capture_output=True, user=localplatform.localplatform._get_user_id() if localplatform.ON_LINUX else None, env={} if localplatform.ON_LINUX else None) # pyright: ignore [reportPrivateUsage]
|
||||||
|
return [x.strip() for x in proc.stdout.decode().strip().split("\n")]
|
||||||
proc.check_returncode()
|
|
||||||
|
|
||||||
versions = [x.strip() for x in proc.stdout.decode().strip().split("\n")]
|
|
||||||
return [x for x in versions if x and not x.isspace()]
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.warn(f"Failed to execute get_system_pythonpaths(): {str(e)}")
|
logger.warn(f"Failed to execute get_system_pythonpaths(): {str(e)}")
|
||||||
return []
|
return []
|
||||||
|
|||||||
@@ -95,14 +95,12 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"PluginListIndex": {
|
"PluginListIndex": {
|
||||||
"freeze": "アップデートを凍結",
|
|
||||||
"hide": "クイックアクセス: 非表示",
|
"hide": "クイックアクセス: 非表示",
|
||||||
"no_plugin": "プラグインがインストールされていません!",
|
"no_plugin": "プラグインがインストールされていません!",
|
||||||
"plugin_actions": "プラグインアクション",
|
"plugin_actions": "プラグインアクション",
|
||||||
"reinstall": "再インストール",
|
"reinstall": "再インストール",
|
||||||
"reload": "再読み込み",
|
"reload": "再読み込み",
|
||||||
"show": "クイックアクセス: 表示",
|
"show": "クイックアクセス: 表示",
|
||||||
"unfreeze": "アップデートを許可",
|
|
||||||
"uninstall": "アンインストール",
|
"uninstall": "アンインストール",
|
||||||
"update_all_other": "{{count}} 個のプラグインをアップデート",
|
"update_all_other": "{{count}} 個のプラグインをアップデート",
|
||||||
"update_to": "{{name}} を更新"
|
"update_to": "{{name}} を更新"
|
||||||
@@ -187,19 +185,9 @@
|
|||||||
"SettingsIndex": {
|
"SettingsIndex": {
|
||||||
"developer_title": "開発者",
|
"developer_title": "開発者",
|
||||||
"general_title": "一般",
|
"general_title": "一般",
|
||||||
"plugins_title": "プラグイン",
|
"plugins_title": "プラグイン"
|
||||||
"testing_title": "テスト"
|
|
||||||
},
|
},
|
||||||
"Store": {
|
"Store": {
|
||||||
"download_progress_info": {
|
|
||||||
"download_zip": "プラグインのダウンロード中",
|
|
||||||
"increment_count": "ダウンロード数の増加",
|
|
||||||
"installing_plugin": "プラグインのインストール中",
|
|
||||||
"open_zip": "zipファイルを展開中",
|
|
||||||
"parse_zip": "zipファイルの解析中",
|
|
||||||
"start": "初期化中",
|
|
||||||
"uninstalling_previous": "以前のコピーのアンインストール"
|
|
||||||
},
|
|
||||||
"store_contrib": {
|
"store_contrib": {
|
||||||
"desc": "Decky Plugin Storeに貢献したい場合は、GitHubのSteamDeckHomebrew/decky-plugin-templateリポジトリを確認してください。 開発と配布に関する情報は README で入手できます。",
|
"desc": "Decky Plugin Storeに貢献したい場合は、GitHubのSteamDeckHomebrew/decky-plugin-templateリポジトリを確認してください。 開発と配布に関する情報は README で入手できます。",
|
||||||
"label": "貢献"
|
"label": "貢献"
|
||||||
@@ -223,10 +211,6 @@
|
|||||||
"about": "概要",
|
"about": "概要",
|
||||||
"alph_asce": "アルファベット(Z to A)",
|
"alph_asce": "アルファベット(Z to A)",
|
||||||
"alph_desc": "アルファベット(A to Z)",
|
"alph_desc": "アルファベット(A to Z)",
|
||||||
"date_asce": "古い順",
|
|
||||||
"date_desc": "新しい順",
|
|
||||||
"downloads_asce": "ダウンロード数が少ない順",
|
|
||||||
"downloads_desc": "ダウンロード数が多い順",
|
|
||||||
"title": "閲覧"
|
"title": "閲覧"
|
||||||
},
|
},
|
||||||
"store_testing_cta": "Decky Loaderチームを支援するために、新しいプラグインのテストを検討してください!",
|
"store_testing_cta": "Decky Loaderチームを支援するために、新しいプラグインのテストを検討してください!",
|
||||||
@@ -247,13 +231,6 @@
|
|||||||
"testing": "テスト"
|
"testing": "テスト"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Testing": {
|
|
||||||
"download": "ダウンロード",
|
|
||||||
"error": "PRのインストールエラー",
|
|
||||||
"header": "Decky Loaderの以下のバージョンは、公開されているサードパーティのPull Requestからビルドされたものです。 Decky Loaderチームはその機能や安全性を検証しておらず、内容も古い可能性があります。",
|
|
||||||
"loading": "Pull Requestの読み込み中...",
|
|
||||||
"start_download_toast": "PR #{{id}}のダウンロード中"
|
|
||||||
},
|
|
||||||
"TitleView": {
|
"TitleView": {
|
||||||
"decky_store_desc": "Deckyストアを開く",
|
"decky_store_desc": "Deckyストアを開く",
|
||||||
"settings_desc": "Decky設定を開く"
|
"settings_desc": "Decky設定を開く"
|
||||||
|
|||||||
@@ -37,9 +37,6 @@ def get_live_reload() -> bool:
|
|||||||
def get_keep_systemd_service() -> bool:
|
def get_keep_systemd_service() -> bool:
|
||||||
return os.getenv("KEEP_SYSTEMD_SERVICE", "0") == "1"
|
return os.getenv("KEEP_SYSTEMD_SERVICE", "0") == "1"
|
||||||
|
|
||||||
def get_use_cef_close_workaround() -> bool:
|
|
||||||
return ON_LINUX and os.getenv("USE_CEF_CLOSE_WORKAROUND", "1") == "1"
|
|
||||||
|
|
||||||
def get_log_level() -> int:
|
def get_log_level() -> int:
|
||||||
return {"CRITICAL": 50, "ERROR": 40, "WARNING": 30, "INFO": 20, "DEBUG": 10}[
|
return {"CRITICAL": 50, "ERROR": 40, "WARNING": 30, "INFO": 20, "DEBUG": 10}[
|
||||||
os.getenv("LOG_LEVEL", "INFO")
|
os.getenv("LOG_LEVEL", "INFO")
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
from re import compile
|
|
||||||
from asyncio import Lock
|
|
||||||
import os, pwd, grp, sys, logging
|
import os, pwd, grp, sys, logging
|
||||||
from subprocess import call, run, DEVNULL, PIPE, STDOUT
|
from subprocess import call, run, DEVNULL, PIPE, STDOUT
|
||||||
from ..enums import UserType
|
from ..enums import UserType
|
||||||
@@ -229,39 +227,3 @@ def get_unprivileged_user() -> str:
|
|||||||
user = 'deck'
|
user = 'deck'
|
||||||
|
|
||||||
return user
|
return user
|
||||||
|
|
||||||
# Works around the CEF debugger TCP socket not closing properly when Steam restarts
|
|
||||||
# Group 1 is PID, group 2 is FD. this also filters for "steamwebhelper" in the process name.
|
|
||||||
cef_socket_lsof_regex = compile(r"^p(\d+)(?:\s|.)+csteamwebhelper(?:\s|.)+f(\d+)(?:\s|.)+TST=LISTEN")
|
|
||||||
close_cef_socket_lock = Lock()
|
|
||||||
|
|
||||||
async def close_cef_socket():
|
|
||||||
async with close_cef_socket_lock:
|
|
||||||
if _get_effective_user_id() != 0:
|
|
||||||
logger.warn("Can't close CEF socket as Decky isn't running as root.")
|
|
||||||
return
|
|
||||||
# Look for anything listening TCP on port 8080
|
|
||||||
lsof = run(["lsof", "-F", "-iTCP:8080", "-sTCP:LISTEN"], capture_output=True, text=True)
|
|
||||||
if lsof.returncode != 0 or len(lsof.stdout) < 1:
|
|
||||||
logger.error(f"lsof call failed in close_cef_socket! return code: {str(lsof.returncode)}")
|
|
||||||
return
|
|
||||||
|
|
||||||
lsof_data = cef_socket_lsof_regex.match(lsof.stdout)
|
|
||||||
|
|
||||||
if not lsof_data:
|
|
||||||
logger.error("lsof regex match failed in close_cef_socket!")
|
|
||||||
return
|
|
||||||
|
|
||||||
pid = lsof_data.group(1)
|
|
||||||
fd = lsof_data.group(2)
|
|
||||||
|
|
||||||
logger.info(f"Closing CEF socket with PID {pid} and FD {fd}")
|
|
||||||
|
|
||||||
# Use gdb to inject a close() call for the socket fd into steamwebhelper
|
|
||||||
gdb_ret = run(["gdb", "--nx", "-p", pid, "--batch", "--eval-command", f"call (int)close({fd})"], env={"LD_LIBRARY_PATH": ""})
|
|
||||||
|
|
||||||
if gdb_ret.returncode != 0:
|
|
||||||
logger.error(f"Failed to close CEF socket with gdb! return code: {str(gdb_ret.returncode)}", exc_info=True)
|
|
||||||
return
|
|
||||||
|
|
||||||
logger.info("CEF socket closed")
|
|
||||||
|
|||||||
@@ -55,7 +55,4 @@ def get_unprivileged_user() -> str:
|
|||||||
return os.getenv("UNPRIVILEGED_USER", os.getlogin())
|
return os.getenv("UNPRIVILEGED_USER", os.getlogin())
|
||||||
|
|
||||||
async def restart_webhelper() -> bool:
|
async def restart_webhelper() -> bool:
|
||||||
return True # Stubbed
|
return True # Stubbed
|
||||||
|
|
||||||
async def close_cef_socket():
|
|
||||||
return # Stubbed
|
|
||||||
@@ -7,17 +7,14 @@ from .localplatform.localplatform import (chmod, chown, service_stop, service_st
|
|||||||
get_privileged_path, restart_webhelper)
|
get_privileged_path, restart_webhelper)
|
||||||
if hasattr(sys, '_MEIPASS'):
|
if hasattr(sys, '_MEIPASS'):
|
||||||
chmod(sys._MEIPASS, 755) # type: ignore
|
chmod(sys._MEIPASS, 755) # type: ignore
|
||||||
|
|
||||||
# Full imports
|
# Full imports
|
||||||
import multiprocessing
|
|
||||||
multiprocessing.freeze_support()
|
|
||||||
from asyncio import AbstractEventLoop, CancelledError, Task, all_tasks, current_task, gather, new_event_loop, set_event_loop, sleep
|
from asyncio import AbstractEventLoop, CancelledError, Task, all_tasks, current_task, gather, new_event_loop, set_event_loop, sleep
|
||||||
from logging import basicConfig, getLogger
|
from logging import basicConfig, getLogger
|
||||||
from os import path
|
from os import path
|
||||||
from traceback import format_exc
|
from traceback import format_exc
|
||||||
from time import time
|
import multiprocessing
|
||||||
import aiohttp_cors # pyright: ignore [reportMissingTypeStubs]
|
|
||||||
|
|
||||||
|
import aiohttp_cors # pyright: ignore [reportMissingTypeStubs]
|
||||||
# Partial imports
|
# Partial imports
|
||||||
from aiohttp import client_exceptions
|
from aiohttp import client_exceptions
|
||||||
from aiohttp.web import Application, Response, Request, get, run_app, static # pyright: ignore [reportUnknownVariableType]
|
from aiohttp.web import Application, Response, Request, get, run_app, static # pyright: ignore [reportUnknownVariableType]
|
||||||
@@ -26,7 +23,7 @@ from setproctitle import getproctitle, setproctitle, setthreadtitle
|
|||||||
|
|
||||||
# local modules
|
# local modules
|
||||||
from .browser import PluginBrowser
|
from .browser import PluginBrowser
|
||||||
from .helpers import (REMOTE_DEBUGGER_UNIT, create_inject_script, csrf_middleware, get_csrf_token, get_loader_version,
|
from .helpers import (REMOTE_DEBUGGER_UNIT, csrf_middleware, get_csrf_token, get_loader_version,
|
||||||
mkdir_as_user, get_system_pythonpaths, get_effective_user_id)
|
mkdir_as_user, get_system_pythonpaths, get_effective_user_id)
|
||||||
|
|
||||||
from .injector import get_gamepadui_tab, Tab
|
from .injector import get_gamepadui_tab, Tab
|
||||||
@@ -76,9 +73,6 @@ class PluginManager:
|
|||||||
self.plugin_browser = PluginBrowser(plugin_path, self.plugin_loader.plugins, self.plugin_loader, self.settings)
|
self.plugin_browser = PluginBrowser(plugin_path, self.plugin_loader.plugins, self.plugin_loader, self.settings)
|
||||||
self.utilities = Utilities(self)
|
self.utilities = Utilities(self)
|
||||||
self.updater = Updater(self)
|
self.updater = Updater(self)
|
||||||
self.last_webhelper_exit: float = 0
|
|
||||||
self.webhelper_crash_count: int = 0
|
|
||||||
self.inject_fallback: bool = False
|
|
||||||
|
|
||||||
jinja_setup(self.web_app)
|
jinja_setup(self.web_app)
|
||||||
|
|
||||||
@@ -100,21 +94,6 @@ class PluginManager:
|
|||||||
self.cors.add(route) # pyright: ignore [reportUnknownMemberType]
|
self.cors.add(route) # pyright: ignore [reportUnknownMemberType]
|
||||||
self.web_app.add_routes([static("/static", path.join(path.dirname(__file__), 'static'))])
|
self.web_app.add_routes([static("/static", path.join(path.dirname(__file__), 'static'))])
|
||||||
|
|
||||||
async def handle_crash(self):
|
|
||||||
new_time = time()
|
|
||||||
if (new_time - self.last_webhelper_exit < 60):
|
|
||||||
self.webhelper_crash_count += 1
|
|
||||||
logger.warn(f"webhelper crashed within a minute from last crash! crash count: {self.webhelper_crash_count}")
|
|
||||||
else:
|
|
||||||
self.webhelper_crash_count = 0
|
|
||||||
self.last_webhelper_exit = new_time
|
|
||||||
|
|
||||||
# should never happen
|
|
||||||
if (self.webhelper_crash_count > 4):
|
|
||||||
await self.updater.do_shutdown()
|
|
||||||
# Give up
|
|
||||||
exit(0)
|
|
||||||
|
|
||||||
async def shutdown(self, _: Application):
|
async def shutdown(self, _: Application):
|
||||||
try:
|
try:
|
||||||
logger.info(f"Shutting down...")
|
logger.info(f"Shutting down...")
|
||||||
@@ -206,7 +185,6 @@ class PluginManager:
|
|||||||
# If this is a forceful disconnect the loop will just stop without any failure message. In this case, injector.py will handle this for us so we don't need to close the socket.
|
# If this is a forceful disconnect the loop will just stop without any failure message. In this case, injector.py will handle this for us so we don't need to close the socket.
|
||||||
# This is because of https://github.com/aio-libs/aiohttp/blob/3ee7091b40a1bc58a8d7846e7878a77640e96996/aiohttp/client_ws.py#L321
|
# This is because of https://github.com/aio-libs/aiohttp/blob/3ee7091b40a1bc58a8d7846e7878a77640e96996/aiohttp/client_ws.py#L321
|
||||||
logger.info("CEF has disconnected...")
|
logger.info("CEF has disconnected...")
|
||||||
await self.handle_crash()
|
|
||||||
# At this point the loop starts again and we connect to the freshly started Steam client once it is ready.
|
# At this point the loop starts again and we connect to the freshly started Steam client once it is ready.
|
||||||
except Exception:
|
except Exception:
|
||||||
if not self.reinject:
|
if not self.reinject:
|
||||||
@@ -214,7 +192,6 @@ class PluginManager:
|
|||||||
logger.error("Exception while reading page events " + format_exc())
|
logger.error("Exception while reading page events " + format_exc())
|
||||||
await tab.close_websocket()
|
await tab.close_websocket()
|
||||||
self.js_ctx_tab = None
|
self.js_ctx_tab = None
|
||||||
await self.handle_crash()
|
|
||||||
pass
|
pass
|
||||||
# while True:
|
# while True:
|
||||||
# await sleep(5)
|
# await sleep(5)
|
||||||
@@ -232,11 +209,7 @@ class PluginManager:
|
|||||||
await restart_webhelper()
|
await restart_webhelper()
|
||||||
await sleep(1) # To give CEF enough time to close down the websocket
|
await sleep(1) # To give CEF enough time to close down the websocket
|
||||||
return # We'll catch the next tab in the main loop
|
return # We'll catch the next tab in the main loop
|
||||||
await tab.evaluate_js(create_inject_script("index.js" if self.webhelper_crash_count < 3 else "fallback.js"), False, False, False)
|
await tab.evaluate_js("try{if (window.deckyHasLoaded){setTimeout(() => SteamClient.Browser.RestartJSContext(), 100)}else{window.deckyHasLoaded = true;(async()=>{try{await import('http://localhost:1337/frontend/index.js?v=%s')}catch(e){console.error(e)};})();}}catch(e){console.error(e)}" % (get_loader_version(), ), False, False, False)
|
||||||
if self.webhelper_crash_count > 2:
|
|
||||||
self.reinject = False
|
|
||||||
await sleep(1)
|
|
||||||
await self.updater.do_shutdown()
|
|
||||||
except:
|
except:
|
||||||
logger.info("Failed to inject JavaScript into tab\n" + format_exc())
|
logger.info("Failed to inject JavaScript into tab\n" + format_exc())
|
||||||
pass
|
pass
|
||||||
@@ -251,6 +224,9 @@ def main():
|
|||||||
# Fix windows/flask not recognising that .js means 'application/javascript'
|
# Fix windows/flask not recognising that .js means 'application/javascript'
|
||||||
import mimetypes
|
import mimetypes
|
||||||
mimetypes.add_type('application/javascript', '.js')
|
mimetypes.add_type('application/javascript', '.js')
|
||||||
|
|
||||||
|
# Required for multiprocessing support in frozen files
|
||||||
|
multiprocessing.freeze_support()
|
||||||
else:
|
else:
|
||||||
if get_effective_user_id() != 0:
|
if get_effective_user_id() != 0:
|
||||||
logger.warning(f"decky is running as an unprivileged user, this is not officially supported and may cause issues")
|
logger.warning(f"decky is running as an unprivileged user, this is not officially supported and may cause issues")
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import sys
|
|
||||||
from os import path, environ
|
from os import path, environ
|
||||||
from signal import SIG_IGN, SIGINT, SIGTERM, getsignal, signal
|
from signal import SIG_IGN, SIGINT, SIGTERM, getsignal, signal
|
||||||
from importlib.util import module_from_spec, spec_from_file_location
|
from importlib.util import module_from_spec, spec_from_file_location
|
||||||
from json import dumps, loads
|
from json import dumps, loads
|
||||||
from logging import getLogger
|
from logging import getLogger
|
||||||
|
from sys import exit, path as syspath, modules as sysmodules
|
||||||
from traceback import format_exc
|
from traceback import format_exc
|
||||||
from asyncio import (get_event_loop, new_event_loop,
|
from asyncio import (get_event_loop, new_event_loop,
|
||||||
set_event_loop)
|
set_event_loop)
|
||||||
@@ -13,7 +13,7 @@ from .messages import SocketResponseDict, SocketMessageType
|
|||||||
from ..localplatform.localsocket import LocalSocket
|
from ..localplatform.localsocket import LocalSocket
|
||||||
from ..localplatform.localplatform import setgid, setuid, get_username, get_home_path
|
from ..localplatform.localplatform import setgid, setuid, get_username, get_home_path
|
||||||
from ..enums import UserType
|
from ..enums import UserType
|
||||||
from .. import helpers, settings, injector # pyright: ignore [reportUnusedImport]
|
from .. import helpers
|
||||||
|
|
||||||
from typing import List, TypeVar, Any
|
from typing import List, TypeVar, Any
|
||||||
|
|
||||||
@@ -78,12 +78,12 @@ class SandboxedPlugin:
|
|||||||
environ["DECKY_PLUGIN_AUTHOR"] = self.author
|
environ["DECKY_PLUGIN_AUTHOR"] = self.author
|
||||||
|
|
||||||
# append the plugin's `py_modules` to the recognized python paths
|
# append the plugin's `py_modules` to the recognized python paths
|
||||||
sys.path.append(path.join(environ["DECKY_PLUGIN_DIR"], "py_modules"))
|
syspath.append(path.join(environ["DECKY_PLUGIN_DIR"], "py_modules"))
|
||||||
|
|
||||||
#TODO: FIX IN A LESS CURSED WAY
|
#TODO: FIX IN A LESS CURSED WAY
|
||||||
keys = [key for key in sys.modules if key.startswith("decky_loader.")]
|
keys = [key for key in sysmodules if key.startswith("decky_loader.")]
|
||||||
for key in keys:
|
for key in keys:
|
||||||
sys.modules[key.replace("decky_loader.", "")] = sys.modules[key]
|
sysmodules[key.replace("decky_loader.", "")] = sysmodules[key]
|
||||||
|
|
||||||
from .imports import decky
|
from .imports import decky
|
||||||
async def emit(event: str, *args: Any) -> None:
|
async def emit(event: str, *args: Any) -> None:
|
||||||
@@ -95,9 +95,9 @@ class SandboxedPlugin:
|
|||||||
# copy the docstring over so we don't have to duplicate it
|
# copy the docstring over so we don't have to duplicate it
|
||||||
emit.__doc__ = decky.emit.__doc__
|
emit.__doc__ = decky.emit.__doc__
|
||||||
decky.emit = emit
|
decky.emit = emit
|
||||||
sys.modules["decky"] = decky
|
sysmodules["decky"] = decky
|
||||||
# provided for compatibility
|
# provided for compatibility
|
||||||
sys.modules["decky_plugin"] = decky
|
sysmodules["decky_plugin"] = decky
|
||||||
|
|
||||||
spec = spec_from_file_location("_", self.file)
|
spec = spec_from_file_location("_", self.file)
|
||||||
assert spec is not None
|
assert spec is not None
|
||||||
@@ -123,7 +123,7 @@ class SandboxedPlugin:
|
|||||||
get_event_loop().create_task(socket.setup_server())
|
get_event_loop().create_task(socket.setup_server())
|
||||||
except:
|
except:
|
||||||
self.log.error("Failed to start " + self.name + "!\n" + format_exc())
|
self.log.error("Failed to start " + self.name + "!\n" + format_exc())
|
||||||
sys.exit(0)
|
exit(0)
|
||||||
try:
|
try:
|
||||||
get_event_loop().run_forever()
|
get_event_loop().run_forever()
|
||||||
except SystemExit:
|
except SystemExit:
|
||||||
@@ -180,7 +180,7 @@ class SandboxedPlugin:
|
|||||||
|
|
||||||
loop = get_event_loop()
|
loop = get_event_loop()
|
||||||
loop.call_soon_threadsafe(loop.stop)
|
loop.call_soon_threadsafe(loop.stop)
|
||||||
sys.exit(0)
|
exit(0)
|
||||||
|
|
||||||
d: SocketResponseDict = {"type": SocketMessageType.RESPONSE, "res": None, "success": True, "id": data["id"]}
|
d: SocketResponseDict = {"type": SocketMessageType.RESPONSE, "res": None, "success": True, "id": data["id"]}
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -20,8 +20,9 @@ from .browser import PluginInstallRequest, PluginInstallType
|
|||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from .main import PluginManager
|
from .main import PluginManager
|
||||||
from .injector import inject_to_tab, get_gamepadui_tab, close_old_tabs, get_tab
|
from .injector import inject_to_tab, get_gamepadui_tab, close_old_tabs, get_tab
|
||||||
|
from .localplatform.localplatform import ON_WINDOWS
|
||||||
from . import helpers
|
from . import helpers
|
||||||
from .localplatform.localplatform import ON_WINDOWS, service_stop, service_start, get_home_path, get_username, get_use_cef_close_workaround, close_cef_socket, restart_webhelper
|
from .localplatform.localplatform import service_stop, service_start, get_home_path, get_username
|
||||||
|
|
||||||
class FilePickerObj(TypedDict):
|
class FilePickerObj(TypedDict):
|
||||||
file: Path
|
file: Path
|
||||||
@@ -77,8 +78,6 @@ class Utilities:
|
|||||||
context.ws.add_route("utilities/get_tab_id", self.get_tab_id)
|
context.ws.add_route("utilities/get_tab_id", self.get_tab_id)
|
||||||
context.ws.add_route("utilities/get_user_info", self.get_user_info)
|
context.ws.add_route("utilities/get_user_info", self.get_user_info)
|
||||||
context.ws.add_route("utilities/http_request", self.http_request_legacy)
|
context.ws.add_route("utilities/http_request", self.http_request_legacy)
|
||||||
context.ws.add_route("utilities/restart_webhelper", self.restart_webhelper)
|
|
||||||
context.ws.add_route("utilities/close_cef_socket", self.close_cef_socket)
|
|
||||||
context.ws.add_route("utilities/_call_legacy_utility", self._call_legacy_utility)
|
context.ws.add_route("utilities/_call_legacy_utility", self._call_legacy_utility)
|
||||||
|
|
||||||
context.web_app.add_routes([
|
context.web_app.add_routes([
|
||||||
@@ -288,13 +287,6 @@ class Utilities:
|
|||||||
await service_stop(helpers.SSHD_UNIT)
|
await service_stop(helpers.SSHD_UNIT)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
async def close_cef_socket(self):
|
|
||||||
if get_use_cef_close_workaround():
|
|
||||||
await close_cef_socket()
|
|
||||||
|
|
||||||
async def restart_webhelper(self):
|
|
||||||
await restart_webhelper()
|
|
||||||
|
|
||||||
async def filepicker_ls(self,
|
async def filepicker_ls(self,
|
||||||
path: str | None = None,
|
path: str | None = None,
|
||||||
include_files: bool = True,
|
include_files: bool = True,
|
||||||
|
|||||||
-1
@@ -1,6 +1,5 @@
|
|||||||
[Unit]
|
[Unit]
|
||||||
Description=SteamDeck Plugin Loader
|
Description=SteamDeck Plugin Loader
|
||||||
After=network.target
|
|
||||||
[Service]
|
[Service]
|
||||||
Type=simple
|
Type=simple
|
||||||
User=root
|
User=root
|
||||||
|
|||||||
Vendored
-1
@@ -1,6 +1,5 @@
|
|||||||
[Unit]
|
[Unit]
|
||||||
Description=SteamDeck Plugin Loader
|
Description=SteamDeck Plugin Loader
|
||||||
After=network.target
|
|
||||||
[Service]
|
[Service]
|
||||||
Type=simple
|
Type=simple
|
||||||
User=root
|
User=root
|
||||||
|
|||||||
@@ -47,7 +47,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@decky/ui": "^4.7.1",
|
"@decky/ui": "^4.7.0",
|
||||||
"filesize": "^10.1.2",
|
"filesize": "^10.1.2",
|
||||||
"i18next": "^23.11.5",
|
"i18next": "^23.11.5",
|
||||||
"i18next-http-backend": "^2.5.2",
|
"i18next-http-backend": "^2.5.2",
|
||||||
|
|||||||
Generated
+5
-5
@@ -9,8 +9,8 @@ importers:
|
|||||||
.:
|
.:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@decky/ui':
|
'@decky/ui':
|
||||||
specifier: ^4.7.1
|
specifier: ^4.7.0
|
||||||
version: 4.7.1
|
version: 4.7.0
|
||||||
filesize:
|
filesize:
|
||||||
specifier: ^10.1.2
|
specifier: ^10.1.2
|
||||||
version: 10.1.2
|
version: 10.1.2
|
||||||
@@ -215,8 +215,8 @@ packages:
|
|||||||
'@decky/api@1.1.1':
|
'@decky/api@1.1.1':
|
||||||
resolution: {integrity: sha512-R5fkBRHBt5QIQY7Q0AlbVIhlIZ/nTzwBOoi8Rt4Go2fjFnoMKPInCJl6cPjXzimGwl2pyqKJgY6VnH6ar0XrHQ==}
|
resolution: {integrity: sha512-R5fkBRHBt5QIQY7Q0AlbVIhlIZ/nTzwBOoi8Rt4Go2fjFnoMKPInCJl6cPjXzimGwl2pyqKJgY6VnH6ar0XrHQ==}
|
||||||
|
|
||||||
'@decky/ui@4.7.1':
|
'@decky/ui@4.7.0':
|
||||||
resolution: {integrity: sha512-yJwBgW+J2cMDfMkmcDFtzsubhUjekFZAtCnP55QEJ/1UKGR7sLNOvDLFYi1h5PI0K4L1XYcAMKHwbYFFTzcDTA==}
|
resolution: {integrity: sha512-klNWF5tnZVqzuUgFbw+pThiZjK7gKEtwbEZAo4aAuPJSVobpl/euTx9NAxY95QPCFMDgxCo6X6ioEA2nMfHfLA==}
|
||||||
|
|
||||||
'@esbuild/aix-ppc64@0.20.2':
|
'@esbuild/aix-ppc64@0.20.2':
|
||||||
resolution: {integrity: sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g==}
|
resolution: {integrity: sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g==}
|
||||||
@@ -2289,7 +2289,7 @@ snapshots:
|
|||||||
|
|
||||||
'@decky/api@1.1.1': {}
|
'@decky/api@1.1.1': {}
|
||||||
|
|
||||||
'@decky/ui@4.7.1': {}
|
'@decky/ui@4.7.0': {}
|
||||||
|
|
||||||
'@esbuild/aix-ppc64@0.20.2':
|
'@esbuild/aix-ppc64@0.20.2':
|
||||||
optional: true
|
optional: true
|
||||||
|
|||||||
+44
-58
@@ -11,62 +11,48 @@ import { visualizer } from 'rollup-plugin-visualizer';
|
|||||||
|
|
||||||
const hiddenWarnings = ['THIS_IS_UNDEFINED', 'EVAL'];
|
const hiddenWarnings = ['THIS_IS_UNDEFINED', 'EVAL'];
|
||||||
|
|
||||||
export default defineConfig([
|
export default defineConfig({
|
||||||
// Main bundle
|
input: 'src/index.ts',
|
||||||
{
|
plugins: [
|
||||||
input: 'src/index.ts',
|
del({ targets: '../backend/decky_loader/static/*', force: true }),
|
||||||
plugins: [
|
commonjs(),
|
||||||
del({ targets: ['../backend/decky_loader/static/*', '!../backend/decky_loader/static/fallback.js'], force: true }),
|
nodeResolve({
|
||||||
commonjs(),
|
browser: true,
|
||||||
nodeResolve({
|
}),
|
||||||
browser: true,
|
externalGlobals({
|
||||||
}),
|
react: 'SP_REACT',
|
||||||
externalGlobals({
|
'react-dom': 'SP_REACTDOM',
|
||||||
react: 'SP_REACT',
|
// hack to shut up react-markdown
|
||||||
'react-dom': 'SP_REACTDOM',
|
process: '{cwd: () => {}}',
|
||||||
// hack to shut up react-markdown
|
path: '{dirname: () => {}, join: () => {}, basename: () => {}, extname: () => {}}',
|
||||||
process: '{cwd: () => {}}',
|
url: '{fileURLToPath: (f) => f}',
|
||||||
path: '{dirname: () => {}, join: () => {}, basename: () => {}, extname: () => {}}',
|
}),
|
||||||
url: '{fileURLToPath: (f) => f}',
|
typescript(),
|
||||||
}),
|
json(),
|
||||||
typescript(),
|
replace({
|
||||||
json(),
|
preventAssignment: false,
|
||||||
replace({
|
'process.env.NODE_ENV': JSON.stringify('production'),
|
||||||
preventAssignment: false,
|
}),
|
||||||
'process.env.NODE_ENV': JSON.stringify('production'),
|
image(),
|
||||||
}),
|
visualizer(),
|
||||||
image(),
|
],
|
||||||
visualizer(),
|
preserveEntrySignatures: false,
|
||||||
],
|
treeshake: {
|
||||||
preserveEntrySignatures: false,
|
// Assume all external modules have imports with side effects (the default) while allowing decky libraries to treeshake
|
||||||
treeshake: {
|
pureExternalImports: true,
|
||||||
// Assume all external modules have imports with side effects (the default) while allowing decky libraries to treeshake
|
preset: 'smallest'
|
||||||
pureExternalImports: true,
|
|
||||||
preset: 'smallest'
|
|
||||||
},
|
|
||||||
output: {
|
|
||||||
dir: '../backend/decky_loader/static',
|
|
||||||
format: 'esm',
|
|
||||||
chunkFileNames: (chunkInfo) => {
|
|
||||||
return 'chunk-[hash].js';
|
|
||||||
},
|
|
||||||
sourcemap: true,
|
|
||||||
sourcemapPathTransform: (relativeSourcePath) => relativeSourcePath.replace(/^\.\.\//, `decky://decky/loader/`),
|
|
||||||
},
|
|
||||||
onwarn: function (message, handleWarning) {
|
|
||||||
if (hiddenWarnings.some((warning) => message.code === warning)) return;
|
|
||||||
handleWarning(message);
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
// Fallback
|
output: {
|
||||||
{
|
dir: '../backend/decky_loader/static',
|
||||||
input: 'src/fallback.ts',
|
format: 'esm',
|
||||||
plugins: [
|
chunkFileNames: (chunkInfo) => {
|
||||||
typescript()
|
return 'chunk-[hash].js';
|
||||||
],
|
},
|
||||||
output: {
|
sourcemap: true,
|
||||||
file: '../backend/decky_loader/static/fallback.js',
|
sourcemapPathTransform: (relativeSourcePath) => relativeSourcePath.replace(/^\.\.\//, `decky://decky/loader/`),
|
||||||
format: 'esm',
|
},
|
||||||
}
|
onwarn: function (message, handleWarning) {
|
||||||
}
|
if (hiddenWarnings.some((warning) => message.code === warning)) return;
|
||||||
]);
|
handleWarning(message);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|||||||
@@ -94,7 +94,7 @@ const DeckyErrorBoundary: FunctionComponent<DeckyErrorBoundaryProps> = ({ error,
|
|||||||
style={{ marginRight: '5px', padding: '5px' }}
|
style={{ marginRight: '5px', padding: '5px' }}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
addLogLine('Restarting Steam...');
|
addLogLine('Restarting Steam...');
|
||||||
SteamClient.User.StartRestart(false);
|
SteamClient.User.StartRestart();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Restart Steam
|
Restart Steam
|
||||||
@@ -121,7 +121,7 @@ const DeckyErrorBoundary: FunctionComponent<DeckyErrorBoundaryProps> = ({ error,
|
|||||||
doShutdown();
|
doShutdown();
|
||||||
await sleep(5000);
|
await sleep(5000);
|
||||||
addLogLine('Restarting Steam...');
|
addLogLine('Restarting Steam...');
|
||||||
SteamClient.User.StartRestart(false);
|
SteamClient.User.StartRestart();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Disable Decky until next boot
|
Disable Decky until next boot
|
||||||
@@ -166,7 +166,7 @@ const DeckyErrorBoundary: FunctionComponent<DeckyErrorBoundaryProps> = ({ error,
|
|||||||
await sleep(2000);
|
await sleep(2000);
|
||||||
addLogLine('Restarting Steam...');
|
addLogLine('Restarting Steam...');
|
||||||
await sleep(500);
|
await sleep(500);
|
||||||
SteamClient.User.StartRestart(false);
|
SteamClient.User.StartRestart();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Uninstall {errorSource} and restart Decky
|
Uninstall {errorSource} and restart Decky
|
||||||
|
|||||||
@@ -1,128 +0,0 @@
|
|||||||
// THIS FILE MUST BE ENTIRELY SELF-CONTAINED! DO NOT USE PACKAGES!
|
|
||||||
interface Window {
|
|
||||||
FocusNavController: any;
|
|
||||||
GamepadNavTree: any;
|
|
||||||
deckyFallbackLoaded?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
(async () => {
|
|
||||||
try {
|
|
||||||
if (window.deckyFallbackLoaded) return;
|
|
||||||
window.deckyFallbackLoaded = true;
|
|
||||||
|
|
||||||
// #region utils
|
|
||||||
function sleep(ms: number) {
|
|
||||||
return new Promise((res) => setTimeout(res, ms));
|
|
||||||
}
|
|
||||||
// #endregion
|
|
||||||
|
|
||||||
// #region DeckyIcon
|
|
||||||
const fallbackIcon = `
|
|
||||||
<svg class="fallbackDeckyIcon" xmlns="http://www.w3.org/2000/svg" height="100%" width="100%" viewBox="0 0 512 456">
|
|
||||||
<g>
|
|
||||||
<path
|
|
||||||
style="fill: none;"
|
|
||||||
d="M154.33,72.51v49.79c11.78-0.17,23.48,2,34.42,6.39c10.93,4.39,20.89,10.91,29.28,19.18
|
|
||||||
c8.39,8.27,15.06,18.13,19.61,29c4.55,10.87,6.89,22.54,6.89,34.32c0,11.78-2.34,23.45-6.89,34.32
|
|
||||||
c-4.55,10.87-11.21,20.73-19.61,29c-8.39,8.27-18.35,14.79-29.28,19.18c-10.94,4.39-22.63,6.56-34.42,6.39v49.77
|
|
||||||
c36.78,0,72.05-14.61,98.05-40.62c26-26.01,40.61-61.28,40.61-98.05c0-36.78-14.61-72.05-40.61-98.05
|
|
||||||
C226.38,87.12,191.11,72.51,154.33,72.51z"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<ellipse
|
|
||||||
transform="matrix(0.982 -0.1891 0.1891 0.982 -37.1795 32.9988)"
|
|
||||||
style="fill: none;"
|
|
||||||
cx="154.33"
|
|
||||||
cy="211.33"
|
|
||||||
rx="69.33"
|
|
||||||
ry="69.33"
|
|
||||||
/>
|
|
||||||
<path style="fill: none;" d="M430,97h-52v187h52c7.18,0,13-5.82,13-13V110C443,102.82,437.18,97,430,97z" />
|
|
||||||
<path
|
|
||||||
style="fill: currentColor;"
|
|
||||||
d="M432,27h-54V0H0v361c0,52.47,42.53,95,95,95h188c52.47,0,95-42.53,95-95v-7h54c44.18,0,80-35.82,80-80V107
|
|
||||||
C512,62.82,476.18,27,432,27z M85,211.33c0-38.29,31.04-69.33,69.33-69.33c38.29,0,69.33,31.04,69.33,69.33
|
|
||||||
c0,38.29-31.04,69.33-69.33,69.33C116.04,280.67,85,249.62,85,211.33z M252.39,309.23c-26.01,26-61.28,40.62-98.05,40.62v-49.77
|
|
||||||
c11.78,0.17,23.48-2,34.42-6.39c10.93-4.39,20.89-10.91,29.28-19.18c8.39-8.27,15.06-18.13,19.61-29
|
|
||||||
c4.55-10.87,6.89-22.53,6.89-34.32c0-11.78-2.34-23.45-6.89-34.32c-4.55-10.87-11.21-20.73-19.61-29
|
|
||||||
c-8.39-8.27-18.35-14.79-29.28-19.18c-10.94-4.39-22.63-6.56-34.42-6.39V72.51c36.78,0,72.05,14.61,98.05,40.61
|
|
||||||
c26,26.01,40.61,61.28,40.61,98.05C293,247.96,278.39,283.23,252.39,309.23z M443,271c0,7.18-5.82,13-13,13h-52V97h52
|
|
||||||
c7.18,0,13,5.82,13,13V271z"
|
|
||||||
/>
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
||||||
`;
|
|
||||||
// #endregion
|
|
||||||
|
|
||||||
// #region findSP
|
|
||||||
// from @decky/ui
|
|
||||||
function getFocusNavController(): any {
|
|
||||||
return window.GamepadNavTree?.m_context?.m_controller || window.FocusNavController;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getGamepadNavigationTrees(): any {
|
|
||||||
const focusNav = getFocusNavController();
|
|
||||||
const context = focusNav.m_ActiveContext || focusNav.m_LastActiveContext;
|
|
||||||
return context?.m_rgGamepadNavigationTrees;
|
|
||||||
}
|
|
||||||
|
|
||||||
function findSP(): Window {
|
|
||||||
// old (SP as host)
|
|
||||||
if (document.title == 'SP') return window;
|
|
||||||
// new (SP as popup)
|
|
||||||
const navTrees = getGamepadNavigationTrees();
|
|
||||||
return navTrees?.find((x: any) => x.m_ID == 'root_1_').Root.Element.ownerDocument.defaultView;
|
|
||||||
}
|
|
||||||
// #endregion
|
|
||||||
|
|
||||||
const fallbackCSS = `
|
|
||||||
.fallbackContainer {
|
|
||||||
width: 100vw;
|
|
||||||
height: 100vh;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
text-align: center;
|
|
||||||
flex-direction: column;
|
|
||||||
z-index: 99999999;
|
|
||||||
pointer-events: none;
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
backdrop-filter: blur(8px) brightness(40%);
|
|
||||||
}
|
|
||||||
.fallbackDeckyIcon {
|
|
||||||
width: 96px;
|
|
||||||
height: 96px;
|
|
||||||
padding-bottom: 1rem;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
const fallbackHTML = `
|
|
||||||
<style>${fallbackCSS}</style>
|
|
||||||
${fallbackIcon}
|
|
||||||
<span class="fallbackText">
|
|
||||||
<b>A crash loop has been detected and Decky has been disabled for this boot.</b>
|
|
||||||
<br>
|
|
||||||
<i>Steam will restart in 10 seconds...</i>
|
|
||||||
</span>
|
|
||||||
`;
|
|
||||||
|
|
||||||
await sleep(4000);
|
|
||||||
|
|
||||||
const win = findSP() || window;
|
|
||||||
|
|
||||||
const container = Object.assign(document.createElement('div'), {
|
|
||||||
innerHTML: fallbackHTML,
|
|
||||||
});
|
|
||||||
container.classList.add('fallbackContainer');
|
|
||||||
|
|
||||||
win.document.body.appendChild(container);
|
|
||||||
|
|
||||||
await sleep(10000);
|
|
||||||
|
|
||||||
SteamClient.User.StartShutdown(false);
|
|
||||||
} catch (e) {
|
|
||||||
console.error('Error showing fallback!', e);
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
@@ -172,32 +172,11 @@ class PluginLoader extends Logger {
|
|||||||
.then(() => this.log('Initialized'));
|
.then(() => this.log('Initialized'));
|
||||||
}
|
}
|
||||||
|
|
||||||
private checkForSP(): boolean {
|
|
||||||
try {
|
|
||||||
return !!findSP();
|
|
||||||
} catch (e) {
|
|
||||||
this.warn('Error checking for SP tab', e);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async runCrashChecker() {
|
|
||||||
const spExists = this.checkForSP();
|
|
||||||
await sleep(5000);
|
|
||||||
if (spExists && !this.checkForSP()) {
|
|
||||||
// SP died after plugin loaded. Give up and let the loader's crash loop detection handle it.
|
|
||||||
this.error('SP died during startup. Restarting webhelper.');
|
|
||||||
await this.restartWebhelper();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private getPluginsFromBackend = DeckyBackend.callable<
|
private getPluginsFromBackend = DeckyBackend.callable<
|
||||||
[],
|
[],
|
||||||
{ name: string; version: string; load_type: PluginLoadType }[]
|
{ name: string; version: string; load_type: PluginLoadType }[]
|
||||||
>('loader/get_plugins');
|
>('loader/get_plugins');
|
||||||
|
|
||||||
private restartWebhelper = DeckyBackend.callable<[], void>('utilities/restart_webhelper');
|
|
||||||
|
|
||||||
private async loadPlugins() {
|
private async loadPlugins() {
|
||||||
let registration: any;
|
let registration: any;
|
||||||
const uiMode = await new Promise(
|
const uiMode = await new Promise(
|
||||||
@@ -213,7 +192,6 @@ class PluginLoader extends Logger {
|
|||||||
await sleep(100);
|
await sleep(100);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.runCrashChecker();
|
|
||||||
const plugins = await this.getPluginsFromBackend();
|
const plugins = await this.getPluginsFromBackend();
|
||||||
const pluginLoadPromises = [];
|
const pluginLoadPromises = [];
|
||||||
const loadStart = performance.now();
|
const loadStart = performance.now();
|
||||||
@@ -417,7 +395,6 @@ class PluginLoader extends Logger {
|
|||||||
version?: string,
|
version?: string,
|
||||||
loadType: PluginLoadType = PluginLoadType.ESMODULE_V1,
|
loadType: PluginLoadType = PluginLoadType.ESMODULE_V1,
|
||||||
) {
|
) {
|
||||||
let spExists = this.checkForSP();
|
|
||||||
try {
|
try {
|
||||||
switch (loadType) {
|
switch (loadType) {
|
||||||
case PluginLoadType.ESMODULE_V1:
|
case PluginLoadType.ESMODULE_V1:
|
||||||
@@ -465,7 +442,7 @@ class PluginLoader extends Logger {
|
|||||||
</PanelSectionRow>
|
</PanelSectionRow>
|
||||||
<PanelSectionRow>
|
<PanelSectionRow>
|
||||||
<pre style={{ overflowX: 'scroll' }}>
|
<pre style={{ overflowX: 'scroll' }}>
|
||||||
<code>{e instanceof Error ? '' + e.stack : JSON.stringify(e)}</code>
|
<code>{e instanceof Error ? e.stack : JSON.stringify(e)}</code>
|
||||||
</pre>
|
</pre>
|
||||||
</PanelSectionRow>
|
</PanelSectionRow>
|
||||||
<PanelSectionRow>
|
<PanelSectionRow>
|
||||||
@@ -497,12 +474,6 @@ class PluginLoader extends Logger {
|
|||||||
icon: <FaExclamationCircle />,
|
icon: <FaExclamationCircle />,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (spExists && !this.checkForSP()) {
|
|
||||||
// SP died after plugin loaded. Give up and let the loader's crash loop detection handle it.
|
|
||||||
this.error('SP died after loading plugin. Restarting webhelper.');
|
|
||||||
await this.restartWebhelper();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async callServerMethod(methodName: string, args = {}) {
|
async callServerMethod(methodName: string, args = {}) {
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
// import restartFix from './restart';
|
// import reloadFix from './reload';
|
||||||
import cefSocketFix from './socket';
|
import restartFix from './restart';
|
||||||
|
|
||||||
let fixes: Function[] = [];
|
let fixes: Function[] = [];
|
||||||
|
|
||||||
export function deinitSteamFixes() {
|
export function deinitSteamFixes() {
|
||||||
@@ -8,6 +7,6 @@ export function deinitSteamFixes() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function initSteamFixes() {
|
export async function initSteamFixes() {
|
||||||
fixes.push(cefSocketFix());
|
// fixes.push(await reloadFix());
|
||||||
// fixes.push(await restartFix());
|
fixes.push(await restartFix());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +0,0 @@
|
|||||||
import Logger from '../logger';
|
|
||||||
|
|
||||||
const logger = new Logger('CEFSocketFix');
|
|
||||||
|
|
||||||
const closeCEFSocket = DeckyBackend.callable<[], void>('utilities/close_cef_socket');
|
|
||||||
|
|
||||||
export default function cefSocketFix() {
|
|
||||||
const reg = window.SteamClient?.User?.RegisterForShutdownStart(async () => {
|
|
||||||
logger.log('Closing CEF socket before shutdown');
|
|
||||||
await closeCEFSocket();
|
|
||||||
});
|
|
||||||
|
|
||||||
if (reg) logger.debug('CEF shutdown handler ready');
|
|
||||||
|
|
||||||
return () => reg?.unregister();
|
|
||||||
}
|
|
||||||
@@ -1,13 +1,5 @@
|
|||||||
import type { ToastData, ToastNotification } from '@decky/api';
|
import type { ToastData, ToastNotification } from '@decky/api';
|
||||||
import {
|
import { Patch, callOriginal, findModuleExport, injectFCTrampoline, replacePatch } from '@decky/ui';
|
||||||
ErrorBoundary,
|
|
||||||
Patch,
|
|
||||||
callOriginal,
|
|
||||||
findModuleExport,
|
|
||||||
injectFCTrampoline,
|
|
||||||
replacePatch,
|
|
||||||
sleep,
|
|
||||||
} from '@decky/ui';
|
|
||||||
|
|
||||||
import Toast from './components/Toast';
|
import Toast from './components/Toast';
|
||||||
import Logger from './logger';
|
import Logger from './logger';
|
||||||
@@ -29,8 +21,6 @@ declare global {
|
|||||||
|
|
||||||
class Toaster extends Logger {
|
class Toaster extends Logger {
|
||||||
private toastPatch?: Patch;
|
private toastPatch?: Patch;
|
||||||
private markReady!: () => void;
|
|
||||||
private ready = new Promise<void>((r) => (this.markReady = r));
|
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super('Toaster');
|
super('Toaster');
|
||||||
@@ -44,16 +34,13 @@ class Toaster extends Logger {
|
|||||||
this.toastPatch = replacePatch(patchedRenderer, 'component', (args: any[]) => {
|
this.toastPatch = replacePatch(patchedRenderer, 'component', (args: any[]) => {
|
||||||
if (args?.[0]?.group?.decky || args?.[0]?.group?.notifications?.[0]?.decky) {
|
if (args?.[0]?.group?.decky || args?.[0]?.group?.notifications?.[0]?.decky) {
|
||||||
return args[0].group.notifications.map((notification: any) => (
|
return args[0].group.notifications.map((notification: any) => (
|
||||||
<ErrorBoundary>
|
<Toast toast={notification.data} newIndicator={notification.bNewIndicator} location={args?.[0]?.location} />
|
||||||
<Toast toast={notification.data} newIndicator={notification.bNewIndicator} location={args?.[0]?.location} />
|
|
||||||
</ErrorBoundary>
|
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
return callOriginal;
|
return callOriginal;
|
||||||
});
|
});
|
||||||
|
|
||||||
this.log('Initialized');
|
this.log('Initialized');
|
||||||
sleep(4000).then(this.markReady);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
toast(toast: ToastData): ToastNotification {
|
toast(toast: ToastData): ToastNotification {
|
||||||
@@ -120,7 +107,7 @@ class Toaster extends Logger {
|
|||||||
}
|
}
|
||||||
}, toast.expiration);
|
}, toast.expiration);
|
||||||
}
|
}
|
||||||
this.ready.then(() => window.NotificationStore.ProcessNotification(info, toastData, ToastType.New));
|
window.NotificationStore.ProcessNotification(info, toastData, ToastType.New);
|
||||||
return toastResult;
|
return toastResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user