mirror of
https://github.com/SteamDeckHomebrew/decky-loader.git
synced 2026-07-12 21:01:54 +00:00
Custom error handler and some misc fixes
This commit is contained in:
Vendored
+1
-1
@@ -41,7 +41,7 @@
|
|||||||
"deploy"
|
"deploy"
|
||||||
],
|
],
|
||||||
"detail": "Check for local runs, create a plugins folder",
|
"detail": "Check for local runs, create a plugins folder",
|
||||||
"command": "ssh ${config:deckuser}@${config:deckip} -p ${config:deckport} ${config:deckkey} 'python -m ensurepip && python -m pip install --user --upgrade poetry && cd ${config:deckdir}/homebrew/dev/pluginloader/backend && python -m poetry install'",
|
"command": "ssh ${config:deckuser}@${config:deckip} -p ${config:deckport} ${config:deckkey} 'python -m ensurepip --root / && python -m pip install --user --break-system-packages --upgrade poetry && cd ${config:deckdir}/homebrew/dev/pluginloader/backend && python -m poetry install'",
|
||||||
"problemMatcher": []
|
"problemMatcher": []
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ from .enums import UserType
|
|||||||
from logging import getLogger
|
from logging import getLogger
|
||||||
from packaging.version import Version
|
from packaging.version import Version
|
||||||
|
|
||||||
|
SSHD_UNIT = "sshd.service"
|
||||||
REMOTE_DEBUGGER_UNIT = "steam-web-debug-portforward.service"
|
REMOTE_DEBUGGER_UNIT = "steam-web-debug-portforward.service"
|
||||||
|
|
||||||
# global vars
|
# global vars
|
||||||
|
|||||||
@@ -160,7 +160,7 @@ class Loader:
|
|||||||
self.plugins[plugin.name] = plugin.start()
|
self.plugins[plugin.name] = plugin.start()
|
||||||
self.logger.info(f"Loaded {plugin.name}")
|
self.logger.info(f"Loaded {plugin.name}")
|
||||||
if not batch:
|
if not batch:
|
||||||
self.loop.create_task(self.dispatch_plugin(plugin.name, plugin.version))
|
self.loop.create_task(self.dispatch_plugin(plugin.name, plugin.version, plugin.load_type))
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.error(f"Could not load {file}. {e}")
|
self.logger.error(f"Could not load {file}. {e}")
|
||||||
print_exc()
|
print_exc()
|
||||||
|
|||||||
@@ -158,6 +158,7 @@ async def service_start(service_name : str) -> bool:
|
|||||||
|
|
||||||
async def restart_webhelper() -> bool:
|
async def restart_webhelper() -> bool:
|
||||||
logger.info("Restarting steamwebhelper")
|
logger.info("Restarting steamwebhelper")
|
||||||
|
# TODO move to pkill
|
||||||
res = run(["killall", "-s", "SIGTERM", "steamwebhelper"], stdout=DEVNULL, stderr=DEVNULL)
|
res = run(["killall", "-s", "SIGTERM", "steamwebhelper"], stdout=DEVNULL, stderr=DEVNULL)
|
||||||
return res.returncode == 0
|
return res.returncode == 0
|
||||||
|
|
||||||
|
|||||||
@@ -99,8 +99,10 @@ class PluginWrapper:
|
|||||||
return self
|
return self
|
||||||
|
|
||||||
def stop(self, uninstall: bool = False):
|
def stop(self, uninstall: bool = False):
|
||||||
self._listener_task.cancel()
|
if hasattr(self, "_listener_task"):
|
||||||
|
self._listener_task.cancel()
|
||||||
async def _(self: PluginWrapper):
|
async def _(self: PluginWrapper):
|
||||||
await self._socket.write_single_line(dumps({ "stop": True, "uninstall": uninstall }, ensure_ascii=False))
|
if hasattr(self, "_socket"):
|
||||||
await self._socket.close_socket_connection()
|
await self._socket.write_single_line(dumps({ "stop": True, "uninstall": uninstall }, ensure_ascii=False))
|
||||||
|
await self._socket.close_socket_connection()
|
||||||
create_task(_(self))
|
create_task(_(self))
|
||||||
@@ -142,7 +142,10 @@ class SandboxedPlugin:
|
|||||||
try:
|
try:
|
||||||
self.log.info("Attempting to uninstall with plugin " + self.name + "'s \"_uninstall\" function.\n")
|
self.log.info("Attempting to uninstall with plugin " + self.name + "'s \"_uninstall\" function.\n")
|
||||||
if hasattr(self.Plugin, "_uninstall"):
|
if hasattr(self.Plugin, "_uninstall"):
|
||||||
await self.Plugin._uninstall(self.Plugin)
|
if self.api_version > 0:
|
||||||
|
await self.Plugin._uninstall()
|
||||||
|
else:
|
||||||
|
await self.Plugin._uninstall(self.Plugin)
|
||||||
self.log.info("Uninstalled " + self.name + "\n")
|
self.log.info("Uninstalled " + self.name + "\n")
|
||||||
else:
|
else:
|
||||||
self.log.info("Could not find \"_uninstall\" in " + self.name + "'s main.py" + "\n")
|
self.log.info("Could not find \"_uninstall\" in " + self.name + "'s main.py" + "\n")
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ from os import getcwd, path, remove
|
|||||||
from typing import TYPE_CHECKING, List, TypedDict
|
from typing import TYPE_CHECKING, List, TypedDict
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from .main import PluginManager
|
from .main import PluginManager
|
||||||
from .localplatform.localplatform import chmod, service_restart, ON_LINUX, ON_WINDOWS, get_keep_systemd_service, get_selinux
|
from .localplatform.localplatform import chmod, service_restart, service_stop, ON_LINUX, ON_WINDOWS, get_keep_systemd_service, get_selinux
|
||||||
import shutil
|
import shutil
|
||||||
from typing import List, TYPE_CHECKING, TypedDict
|
from typing import List, TYPE_CHECKING, TypedDict
|
||||||
import zipfile
|
import zipfile
|
||||||
@@ -53,6 +53,7 @@ class Updater:
|
|||||||
context.ws.add_route("updater/get_version_info", self.get_version_info);
|
context.ws.add_route("updater/get_version_info", self.get_version_info);
|
||||||
context.ws.add_route("updater/check_for_updates", self.check_for_updates);
|
context.ws.add_route("updater/check_for_updates", self.check_for_updates);
|
||||||
context.ws.add_route("updater/do_restart", self.do_restart);
|
context.ws.add_route("updater/do_restart", self.do_restart);
|
||||||
|
context.ws.add_route("updater/do_shutdown", self.do_shutdown);
|
||||||
context.ws.add_route("updater/do_update", self.do_update);
|
context.ws.add_route("updater/do_update", self.do_update);
|
||||||
context.ws.add_route("updater/get_testing_versions", self.get_testing_versions);
|
context.ws.add_route("updater/get_testing_versions", self.get_testing_versions);
|
||||||
context.ws.add_route("updater/download_testing_version", self.download_testing_version);
|
context.ws.add_route("updater/download_testing_version", self.download_testing_version);
|
||||||
@@ -184,8 +185,8 @@ class Updater:
|
|||||||
|
|
||||||
logger.info("Updated loader installation.")
|
logger.info("Updated loader installation.")
|
||||||
await self.context.ws.emit("updater/finish_download")
|
await self.context.ws.emit("updater/finish_download")
|
||||||
await self.do_restart()
|
|
||||||
await tab.close_websocket()
|
await tab.close_websocket()
|
||||||
|
await self.do_restart()
|
||||||
|
|
||||||
async def do_update(self):
|
async def do_update(self):
|
||||||
logger.debug("Starting update.")
|
logger.debug("Starting update.")
|
||||||
@@ -242,6 +243,9 @@ class Updater:
|
|||||||
async def do_restart(self):
|
async def do_restart(self):
|
||||||
await service_restart("plugin_loader")
|
await service_restart("plugin_loader")
|
||||||
|
|
||||||
|
async def do_shutdown(self):
|
||||||
|
await service_stop("plugin_loader")
|
||||||
|
|
||||||
async def get_testing_versions(self) -> List[TestingVersion]:
|
async def get_testing_versions(self) -> List[TestingVersion]:
|
||||||
result: List[TestingVersion] = []
|
result: List[TestingVersion] = []
|
||||||
async with ClientSession() as web:
|
async with ClientSession() as web:
|
||||||
|
|||||||
@@ -79,6 +79,8 @@ class Utilities:
|
|||||||
context.ws.add_route("utilities/remove_css_from_tab", self.remove_css_from_tab)
|
context.ws.add_route("utilities/remove_css_from_tab", self.remove_css_from_tab)
|
||||||
context.ws.add_route("utilities/allow_remote_debugging", self.allow_remote_debugging)
|
context.ws.add_route("utilities/allow_remote_debugging", self.allow_remote_debugging)
|
||||||
context.ws.add_route("utilities/disallow_remote_debugging", self.disallow_remote_debugging)
|
context.ws.add_route("utilities/disallow_remote_debugging", self.disallow_remote_debugging)
|
||||||
|
context.ws.add_route("utilities/start_ssh", self.allow_remote_debugging)
|
||||||
|
context.ws.add_route("utilities/stop_ssh", self.allow_remote_debugging)
|
||||||
context.ws.add_route("utilities/filepicker_ls", self.filepicker_ls)
|
context.ws.add_route("utilities/filepicker_ls", self.filepicker_ls)
|
||||||
context.ws.add_route("utilities/disable_rdt", self.disable_rdt)
|
context.ws.add_route("utilities/disable_rdt", self.disable_rdt)
|
||||||
context.ws.add_route("utilities/enable_rdt", self.enable_rdt)
|
context.ws.add_route("utilities/enable_rdt", self.enable_rdt)
|
||||||
@@ -284,6 +286,14 @@ class Utilities:
|
|||||||
await service_stop(helpers.REMOTE_DEBUGGER_UNIT)
|
await service_stop(helpers.REMOTE_DEBUGGER_UNIT)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
async def start_ssh(self):
|
||||||
|
await service_start(helpers.SSHD_UNIT)
|
||||||
|
return True
|
||||||
|
|
||||||
|
async def stop_ssh(self):
|
||||||
|
await service_stop(helpers.SSHD_UNIT)
|
||||||
|
return True
|
||||||
|
|
||||||
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,
|
||||||
|
|||||||
+16
-15
@@ -10,30 +10,31 @@
|
|||||||
"format": "prettier -c src -w"
|
"format": "prettier -c src -w"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@decky/api": "^1.0.3",
|
||||||
"@rollup/plugin-commonjs": "^21.1.0",
|
"@rollup/plugin-commonjs": "^21.1.0",
|
||||||
"@rollup/plugin-image": "^3.0.2",
|
"@rollup/plugin-image": "^3.0.3",
|
||||||
"@rollup/plugin-json": "^4.1.0",
|
"@rollup/plugin-json": "^4.1.0",
|
||||||
"@rollup/plugin-node-resolve": "^13.3.0",
|
"@rollup/plugin-node-resolve": "^13.3.0",
|
||||||
"@rollup/plugin-replace": "^4.0.0",
|
"@rollup/plugin-replace": "^4.0.0",
|
||||||
"@rollup/plugin-typescript": "^8.5.0",
|
"@rollup/plugin-typescript": "^8.5.0",
|
||||||
"@types/react": "16.14.0",
|
"@types/react": "18.2.0",
|
||||||
"@types/react-file-icon": "^1.0.1",
|
"@types/react-file-icon": "^1.0.4",
|
||||||
"@types/react-router": "5.1.18",
|
"@types/react-router": "5.1.18",
|
||||||
"@types/webpack": "^5.28.1",
|
"@types/webpack": "^5.28.5",
|
||||||
"husky": "^8.0.3",
|
"husky": "^8.0.3",
|
||||||
"i18next-parser": "^8.0.0",
|
"i18next-parser": "^8.13.0",
|
||||||
"import-sort-style-module": "^6.0.0",
|
"import-sort-style-module": "^6.0.0",
|
||||||
"inquirer": "^8.2.5",
|
"inquirer": "^8.2.6",
|
||||||
"prettier": "^3.2.5",
|
"prettier": "^3.2.5",
|
||||||
"prettier-plugin-import-sort": "^0.0.7",
|
"prettier-plugin-import-sort": "^0.0.7",
|
||||||
"react": "16.14.0",
|
"react": "18.2.0",
|
||||||
"react-dom": "16.14.0",
|
"react-dom": "18.2.0",
|
||||||
"rollup": "^2.79.1",
|
"rollup": "^2.79.1",
|
||||||
"rollup-plugin-delete": "^2.0.0",
|
"rollup-plugin-delete": "^2.0.0",
|
||||||
"rollup-plugin-external-globals": "^0.6.1",
|
"rollup-plugin-external-globals": "^0.6.1",
|
||||||
"rollup-plugin-polyfill-node": "^0.10.2",
|
"rollup-plugin-polyfill-node": "^0.10.2",
|
||||||
"rollup-plugin-visualizer": "^5.9.2",
|
"rollup-plugin-visualizer": "^5.12.0",
|
||||||
"tslib": "^2.5.3",
|
"tslib": "^2.6.2",
|
||||||
"typescript": "^4.9.5"
|
"typescript": "^4.9.5"
|
||||||
},
|
},
|
||||||
"importSort": {
|
"importSort": {
|
||||||
@@ -44,12 +45,12 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"decky-frontend-lib": "3.25.0",
|
"decky-frontend-lib": "3.25.0",
|
||||||
"filesize": "^10.0.7",
|
"filesize": "^10.1.2",
|
||||||
"i18next": "^23.2.1",
|
"i18next": "^23.11.5",
|
||||||
"i18next-http-backend": "^2.2.1",
|
"i18next-http-backend": "^2.5.2",
|
||||||
"react-file-icon": "^1.3.0",
|
"react-file-icon": "^1.4.0",
|
||||||
"react-i18next": "^12.3.1",
|
"react-i18next": "^12.3.1",
|
||||||
"react-icons": "^4.9.0",
|
"react-icons": "^4.12.0",
|
||||||
"react-markdown": "^8.0.7",
|
"react-markdown": "^8.0.7",
|
||||||
"remark-gfm": "^3.0.1"
|
"remark-gfm": "^3.0.1"
|
||||||
}
|
}
|
||||||
|
|||||||
Generated
+739
-1003
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,201 @@
|
|||||||
|
import { sleep } from '@decky/ui';
|
||||||
|
import { ErrorInfo, FunctionComponent, useReducer, useState } from 'react';
|
||||||
|
|
||||||
|
import { uninstallPlugin } from '../plugin';
|
||||||
|
import { doRestart, doShutdown } from '../updater';
|
||||||
|
|
||||||
|
interface ReactErrorInfo {
|
||||||
|
error: Error;
|
||||||
|
info: ErrorInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DeckyErrorBoundaryProps {
|
||||||
|
error: ReactErrorInfo;
|
||||||
|
errorKey: string;
|
||||||
|
reset: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface Window {
|
||||||
|
SystemNetworkStore?: any;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const pluginErrorRegex = /\(http:\/\/localhost:1337\/plugins\/(.*)\//;
|
||||||
|
const pluginSourceMapErrorRegex = /\(decky:\/\/decky\/plugin\/(.*)\//;
|
||||||
|
const legacyPluginErrorRegex = /\(decky:\/\/decky\/legacy_plugin\/(.*)\/index.js/;
|
||||||
|
|
||||||
|
function getLikelyErrorSource(error: ReactErrorInfo): [source: string, wasPlugin: boolean] {
|
||||||
|
const pluginMatch = error.error.stack?.match(pluginErrorRegex);
|
||||||
|
if (pluginMatch) {
|
||||||
|
return [decodeURIComponent(pluginMatch[1]), true];
|
||||||
|
}
|
||||||
|
|
||||||
|
const pluginMatchViaMap = error.error.stack?.match(pluginSourceMapErrorRegex);
|
||||||
|
if (pluginMatchViaMap) {
|
||||||
|
return [decodeURIComponent(pluginMatchViaMap[1]), true];
|
||||||
|
}
|
||||||
|
|
||||||
|
const legacyPluginMatch = error.error.stack?.match(legacyPluginErrorRegex);
|
||||||
|
if (legacyPluginMatch) {
|
||||||
|
return [decodeURIComponent(legacyPluginMatch[1]), true];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error.error.stack?.includes('http://localhost:1337/')) {
|
||||||
|
return ['the Decky frontend', false];
|
||||||
|
}
|
||||||
|
return ['Steam', false];
|
||||||
|
}
|
||||||
|
|
||||||
|
export const startSSH = DeckyBackend.callable('utilities/start_ssh');
|
||||||
|
export const starrCEFForwarding = DeckyBackend.callable('utilities/allow_remote_debugging');
|
||||||
|
|
||||||
|
function ipToString(ip: number) {
|
||||||
|
return [(ip >>> 24) & 255, (ip >>> 16) & 255, (ip >>> 8) & 255, (ip >>> 0) & 255].join('.');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Intentionally not localized since we can't really trust React here
|
||||||
|
const DeckyErrorBoundary: FunctionComponent<DeckyErrorBoundaryProps> = ({ error, reset }) => {
|
||||||
|
const [actionLog, addLogLine] = useReducer((log: string, line: string) => (log += '\n' + line), '');
|
||||||
|
const [actionsEnabled, setActionsEnabled] = useState<boolean>(true);
|
||||||
|
const [debugAllowed, setDebugAllowed] = useState<boolean>(true);
|
||||||
|
const [errorSource, wasCausedByPlugin] = getLikelyErrorSource(error);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
overflow: 'scroll',
|
||||||
|
marginLeft: '15px',
|
||||||
|
color: 'white',
|
||||||
|
fontSize: '16px',
|
||||||
|
userSelect: 'auto',
|
||||||
|
backgroundColor: 'black',
|
||||||
|
marginTop: '48px', // Incase this is a page
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<h1
|
||||||
|
style={{
|
||||||
|
fontSize: '20px',
|
||||||
|
display: 'inline-block',
|
||||||
|
marginTop: '15px',
|
||||||
|
userSelect: 'auto',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
⚠️ An error occured rendering this content.
|
||||||
|
</h1>
|
||||||
|
<p>This error likely occured in {getLikelyErrorSource(error)}.</p>
|
||||||
|
{actionLog?.length > 0 && (
|
||||||
|
<pre>
|
||||||
|
<code>
|
||||||
|
Running actions...
|
||||||
|
{actionLog}
|
||||||
|
</code>
|
||||||
|
</pre>
|
||||||
|
)}
|
||||||
|
{actionsEnabled && (
|
||||||
|
<>
|
||||||
|
<h3>Actions: </h3>
|
||||||
|
<p>Use the touch screen.</p>
|
||||||
|
<div style={{ display: 'block', marginBottom: '5px' }}>
|
||||||
|
<button style={{ marginRight: '5px', padding: '5px' }} onClick={reset}>
|
||||||
|
Retry
|
||||||
|
</button>
|
||||||
|
<button style={{ marginRight: '5px', padding: '5px' }} onClick={() => SteamClient.User.StartRestart()}>
|
||||||
|
Restart Steam
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div style={{ display: 'block', marginBottom: '5px' }}>
|
||||||
|
<button
|
||||||
|
style={{ marginRight: '5px', padding: '5px' }}
|
||||||
|
onClick={async () => {
|
||||||
|
setActionsEnabled(false);
|
||||||
|
addLogLine('Restarting Decky...');
|
||||||
|
doRestart();
|
||||||
|
await sleep(2000);
|
||||||
|
addLogLine('Reloading UI...');
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Restart Decky
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
style={{ marginRight: '5px', padding: '5px' }}
|
||||||
|
onClick={async () => {
|
||||||
|
setActionsEnabled(false);
|
||||||
|
addLogLine('Stopping Decky...');
|
||||||
|
doShutdown();
|
||||||
|
await sleep(5000);
|
||||||
|
addLogLine('Restarting Steam...');
|
||||||
|
SteamClient.User.StartRestart();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Disable Decky until next boot
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
{debugAllowed && (
|
||||||
|
<div style={{ display: 'block', marginBottom: '5px' }}>
|
||||||
|
<button
|
||||||
|
style={{ marginRight: '5px', padding: '5px' }}
|
||||||
|
onClick={async () => {
|
||||||
|
setDebugAllowed(false);
|
||||||
|
addLogLine('Enabling CEF debugger forwarding...');
|
||||||
|
await starrCEFForwarding();
|
||||||
|
addLogLine('Enabling SSH...');
|
||||||
|
await startSSH();
|
||||||
|
addLogLine('Ready for debugging!');
|
||||||
|
if (window?.SystemNetworkStore?.wirelessNetworkDevice?.ip4?.addresses?.[0]?.ip) {
|
||||||
|
const ip = ipToString(window.SystemNetworkStore.wirelessNetworkDevice.ip4.addresses[0].ip);
|
||||||
|
addLogLine(`CEF Debugger: http://${ip}:8081`);
|
||||||
|
addLogLine(`SSH: deck@${ip}`);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Allow remote debugging and SSH until next boot
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{wasCausedByPlugin && (
|
||||||
|
<div style={{ display: 'block', marginBottom: '5px' }}>
|
||||||
|
{'\n'}
|
||||||
|
<button
|
||||||
|
style={{ marginRight: '5px', padding: '5px' }}
|
||||||
|
onClick={async () => {
|
||||||
|
setActionsEnabled(false);
|
||||||
|
addLogLine(`Uninstalling ${errorSource}...`);
|
||||||
|
await uninstallPlugin(errorSource);
|
||||||
|
await DeckyPluginLoader.frozenPluginsService.invalidate();
|
||||||
|
await DeckyPluginLoader.hiddenPluginsService.invalidate();
|
||||||
|
await sleep(1000);
|
||||||
|
addLogLine('Restarting Decky...');
|
||||||
|
doRestart();
|
||||||
|
await sleep(2000);
|
||||||
|
addLogLine('Restarting Steam...');
|
||||||
|
await sleep(500);
|
||||||
|
SteamClient.User.StartRestart();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Uninstall {errorSource} and restart Decky
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<pre
|
||||||
|
style={{
|
||||||
|
marginTop: '15px',
|
||||||
|
opacity: 0.7,
|
||||||
|
userSelect: 'auto',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<code>
|
||||||
|
{error.error.stack}
|
||||||
|
{'\n\n'}
|
||||||
|
Component Stack:
|
||||||
|
{error.info.componentStack}
|
||||||
|
</code>
|
||||||
|
</pre>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DeckyErrorBoundary;
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { FC, createContext, useContext, useEffect, useState } from 'react';
|
import { FC, ReactNode, createContext, useContext, useEffect, useState } from 'react';
|
||||||
|
|
||||||
interface PublicDeckyGlobalComponentsState {
|
interface PublicDeckyGlobalComponentsState {
|
||||||
components: Map<string, FC>;
|
components: Map<string, FC>;
|
||||||
@@ -40,6 +40,7 @@ export const useDeckyGlobalComponentsState = () => useContext(DeckyGlobalCompone
|
|||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
deckyGlobalComponentsState: DeckyGlobalComponentsState;
|
deckyGlobalComponentsState: DeckyGlobalComponentsState;
|
||||||
|
children: ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const DeckyGlobalComponentsStateContextProvider: FC<Props> = ({
|
export const DeckyGlobalComponentsStateContextProvider: FC<Props> = ({
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { ComponentType, FC, createContext, useContext, useEffect, useState } from 'react';
|
import { ComponentType, FC, ReactNode, createContext, useContext, useEffect, useState } from 'react';
|
||||||
import type { RouteProps } from 'react-router';
|
import type { RouteProps } from 'react-router';
|
||||||
|
|
||||||
export interface RouterEntry {
|
export interface RouterEntry {
|
||||||
@@ -71,6 +71,7 @@ export const useDeckyRouterState = () => useContext(DeckyRouterStateContext);
|
|||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
deckyRouterState: DeckyRouterState;
|
deckyRouterState: DeckyRouterState;
|
||||||
|
children: ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const DeckyRouterStateContextProvider: FC<Props> = ({ children, deckyRouterState }) => {
|
export const DeckyRouterStateContextProvider: FC<Props> = ({ children, deckyRouterState }) => {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { FC, createContext, useContext, useEffect, useState } from 'react';
|
import { FC, ReactNode, createContext, useContext, useEffect, useState } from 'react';
|
||||||
|
|
||||||
import { DEFAULT_NOTIFICATION_SETTINGS, NotificationSettings } from '../notification-service';
|
import { DEFAULT_NOTIFICATION_SETTINGS, NotificationSettings } from '../notification-service';
|
||||||
import { Plugin } from '../plugin';
|
import { Plugin } from '../plugin';
|
||||||
@@ -134,6 +134,7 @@ export const useDeckyState = () => useContext(DeckyStateContext);
|
|||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
deckyState: DeckyState;
|
deckyState: DeckyState;
|
||||||
|
children?: ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const DeckyStateContextProvider: FC<Props> = ({ children, deckyState }) => {
|
export const DeckyStateContextProvider: FC<Props> = ({ children, deckyState }) => {
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { ToastData, joinClassNames } from '@decky/ui';
|
import type { ToastData } from '@decky/api';
|
||||||
|
import { joinClassNames } from '@decky/ui';
|
||||||
import { FC, useEffect, useState } from 'react';
|
import { FC, useEffect, useState } from 'react';
|
||||||
import { ReactElement } from 'react-markdown/lib/react-markdown';
|
import { ReactElement } from 'react-markdown/lib/react-markdown';
|
||||||
|
|
||||||
@@ -28,7 +29,7 @@ const DeckyToaster: FC<DeckyToasterProps> = () => {
|
|||||||
}
|
}
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// not actually node but TS is shit
|
// not actually node but TS is shit
|
||||||
let interval: NodeJS.Timer | null;
|
let interval: NodeJS.Timeout | number | null;
|
||||||
if (renderedToast) {
|
if (renderedToast) {
|
||||||
interval = setTimeout(
|
interval = setTimeout(
|
||||||
() => {
|
() => {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { ToastData } from '@decky/ui';
|
import type { ToastData } from '@decky/api';
|
||||||
import { FC, createContext, useContext, useEffect, useState } from 'react';
|
import { FC, createContext, useContext, useEffect, useState } from 'react';
|
||||||
|
|
||||||
interface PublicDeckyToasterState {
|
interface PublicDeckyToasterState {
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { ToastData, findModule, joinClassNames } from '@decky/ui';
|
import type { ToastData } from '@decky/api';
|
||||||
|
import { findModule, joinClassNames } from '@decky/ui';
|
||||||
import { FunctionComponent } from 'react';
|
import { FunctionComponent } from 'react';
|
||||||
|
|
||||||
interface ToastProps {
|
interface ToastProps {
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ const DropdownMultiselect: FC<{
|
|||||||
const [itemsSelected, setItemsSelected] = useState<any>(selected);
|
const [itemsSelected, setItemsSelected] = useState<any>(selected);
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const handleItemSelect = useCallback((checked, value) => {
|
const handleItemSelect = useCallback((checked: boolean, value: any) => {
|
||||||
setItemsSelected((x: any) =>
|
setItemsSelected((x: any) =>
|
||||||
checked ? [...x.filter((y: any) => y !== value), value] : x.filter((y: any) => y !== value),
|
checked ? [...x.filter((y: any) => y !== value), value] : x.filter((y: any) => y !== value),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -60,10 +60,10 @@ const PluginInstallModal: FC<PluginInstallModalProps> = ({
|
|||||||
strTitle={
|
strTitle={
|
||||||
<div>
|
<div>
|
||||||
<TranslationHelper
|
<TranslationHelper
|
||||||
trans_class={TranslationClass.PLUGIN_INSTALL_MODAL}
|
transClass={TranslationClass.PLUGIN_INSTALL_MODAL}
|
||||||
trans_text="title"
|
transText="title"
|
||||||
i18n_args={{ artifact: artifact }}
|
i18nArgs={{ artifact: artifact }}
|
||||||
install_type={installType}
|
installType={installType}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
@@ -71,17 +71,17 @@ const PluginInstallModal: FC<PluginInstallModalProps> = ({
|
|||||||
loading ? (
|
loading ? (
|
||||||
<div>
|
<div>
|
||||||
<TranslationHelper
|
<TranslationHelper
|
||||||
trans_class={TranslationClass.PLUGIN_INSTALL_MODAL}
|
transClass={TranslationClass.PLUGIN_INSTALL_MODAL}
|
||||||
trans_text="button_processing"
|
transText="button_processing"
|
||||||
install_type={installType}
|
installType={installType}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div>
|
<div>
|
||||||
<TranslationHelper
|
<TranslationHelper
|
||||||
trans_class={TranslationClass.PLUGIN_INSTALL_MODAL}
|
transClass={TranslationClass.PLUGIN_INSTALL_MODAL}
|
||||||
trans_text="button_idle"
|
transText="button_idle"
|
||||||
install_type={installType}
|
installType={installType}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
@@ -89,13 +89,13 @@ const PluginInstallModal: FC<PluginInstallModalProps> = ({
|
|||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
<TranslationHelper
|
<TranslationHelper
|
||||||
trans_class={TranslationClass.PLUGIN_INSTALL_MODAL}
|
transClass={TranslationClass.PLUGIN_INSTALL_MODAL}
|
||||||
trans_text="desc"
|
transText="desc"
|
||||||
i18n_args={{
|
i18nArgs={{
|
||||||
artifact: artifact,
|
artifact: artifact,
|
||||||
version: version,
|
version: version,
|
||||||
}}
|
}}
|
||||||
install_type={installType}
|
installType={installType}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{loading && (
|
{loading && (
|
||||||
|
|||||||
@@ -40,11 +40,11 @@ export async function setShowValveInternal(show: boolean) {
|
|||||||
export async function setShouldConnectToReactDevTools(enable: boolean) {
|
export async function setShouldConnectToReactDevTools(enable: boolean) {
|
||||||
DeckyPluginLoader.toaster.toast({
|
DeckyPluginLoader.toaster.toast({
|
||||||
title: enable ? (
|
title: enable ? (
|
||||||
<TranslationHelper trans_class={TranslationClass.DEVELOPER} trans_text={'enabling'} />
|
<TranslationHelper transClass={TranslationClass.DEVELOPER} transText={'enabling'} />
|
||||||
) : (
|
) : (
|
||||||
<TranslationHelper trans_class={TranslationClass.DEVELOPER} trans_text={'disabling'} />
|
<TranslationHelper transClass={TranslationClass.DEVELOPER} transText={'disabling'} />
|
||||||
),
|
),
|
||||||
body: <TranslationHelper trans_class={TranslationClass.DEVELOPER} trans_text={'5secreload'} />,
|
body: <TranslationHelper transClass={TranslationClass.DEVELOPER} transText={'5secreload'} />,
|
||||||
icon: <FaReact />,
|
icon: <FaReact />,
|
||||||
});
|
});
|
||||||
await sleep(5000);
|
await sleep(5000);
|
||||||
|
|||||||
@@ -0,0 +1,69 @@
|
|||||||
|
import { Patch, callOriginal, findModuleExport, replacePatch } from '@decky/ui';
|
||||||
|
|
||||||
|
import DeckyErrorBoundary from './components/DeckyErrorBoundary';
|
||||||
|
import Logger from './logger';
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface Window {
|
||||||
|
__ERRORBOUNDARY_HOOK_INSTANCE: any;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ErrorBoundaryHook extends Logger {
|
||||||
|
private errorBoundaryPatch?: Patch;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super('ErrorBoundaryHook');
|
||||||
|
|
||||||
|
this.log('Initialized');
|
||||||
|
window.__ERRORBOUNDARY_HOOK_INSTANCE?.deinit?.();
|
||||||
|
window.__ERRORBOUNDARY_HOOK_INSTANCE = this;
|
||||||
|
}
|
||||||
|
|
||||||
|
init() {
|
||||||
|
// valve writes only the sanest of code
|
||||||
|
const exp = /^\(\)=>\(.\|\|.\(new .\),.\)$/;
|
||||||
|
const initErrorReportingStore = findModuleExport(
|
||||||
|
(e) => typeof e == 'function' && e?.toString && exp.test(e.toString()),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!initErrorReportingStore) {
|
||||||
|
this.error('could not find initErrorReportingStore! error boundary hook disabled!');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// will replace the existing one for us seemingly? doesnt matter anyway lol
|
||||||
|
const errorReportingStore = initErrorReportingStore();
|
||||||
|
|
||||||
|
// NUH UH.
|
||||||
|
Object.defineProperty(Object.getPrototypeOf(errorReportingStore), 'reporting_enabled', {
|
||||||
|
get: () => false,
|
||||||
|
});
|
||||||
|
errorReportingStore.m_bEnabled = false;
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
// window.errorStore = errorReportingStore;
|
||||||
|
|
||||||
|
const ValveErrorBoundary = findModuleExport(
|
||||||
|
(e) => e.InstallErrorReportingStore && e?.prototype?.Reset && e?.prototype?.componentDidCatch,
|
||||||
|
);
|
||||||
|
if (!ValveErrorBoundary) {
|
||||||
|
this.error('could not find ValveErrorBoundary');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.errorBoundaryPatch = replacePatch(ValveErrorBoundary.prototype, 'render', function (this: any) {
|
||||||
|
if (this.state.error) {
|
||||||
|
return (
|
||||||
|
<DeckyErrorBoundary error={this.state.error} errorKey={this.state.errorKey} reset={() => this.Reset()} />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return callOriginal;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
deinit() {
|
||||||
|
this.errorBoundaryPatch?.unpatch();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ErrorBoundaryHook;
|
||||||
@@ -21,6 +21,7 @@ import PluginUninstallModal from './components/modals/PluginUninstallModal';
|
|||||||
import NotificationBadge from './components/NotificationBadge';
|
import NotificationBadge from './components/NotificationBadge';
|
||||||
import PluginView from './components/PluginView';
|
import PluginView from './components/PluginView';
|
||||||
import WithSuspense from './components/WithSuspense';
|
import WithSuspense from './components/WithSuspense';
|
||||||
|
import ErrorBoundaryHook from './errorboundary-hook';
|
||||||
import { FrozenPluginService } from './frozen-plugins-service';
|
import { FrozenPluginService } from './frozen-plugins-service';
|
||||||
import { HiddenPluginsService } from './hidden-plugins-service';
|
import { HiddenPluginsService } from './hidden-plugins-service';
|
||||||
import Logger from './logger';
|
import Logger from './logger';
|
||||||
@@ -61,6 +62,7 @@ const callPluginMethod = DeckyBackend.callable<[pluginName: string, method: stri
|
|||||||
|
|
||||||
class PluginLoader extends Logger {
|
class PluginLoader extends Logger {
|
||||||
private plugins: Plugin[] = [];
|
private plugins: Plugin[] = [];
|
||||||
|
private errorBoundaryHook: ErrorBoundaryHook = new ErrorBoundaryHook();
|
||||||
private tabsHook: TabsHook = new TabsHook();
|
private tabsHook: TabsHook = new TabsHook();
|
||||||
private routerHook: RouterHook = new RouterHook();
|
private routerHook: RouterHook = new RouterHook();
|
||||||
public toaster: Toaster = new Toaster();
|
public toaster: Toaster = new Toaster();
|
||||||
@@ -79,6 +81,8 @@ class PluginLoader extends Logger {
|
|||||||
constructor() {
|
constructor() {
|
||||||
super(PluginLoader.name);
|
super(PluginLoader.name);
|
||||||
|
|
||||||
|
this.errorBoundaryHook.init();
|
||||||
|
|
||||||
DeckyBackend.addEventListener('loader/notify_updates', this.notifyUpdates.bind(this));
|
DeckyBackend.addEventListener('loader/notify_updates', this.notifyUpdates.bind(this));
|
||||||
DeckyBackend.addEventListener('loader/import_plugin', this.importPlugin.bind(this));
|
DeckyBackend.addEventListener('loader/import_plugin', this.importPlugin.bind(this));
|
||||||
DeckyBackend.addEventListener('loader/unload_plugin', this.unloadPlugin.bind(this));
|
DeckyBackend.addEventListener('loader/unload_plugin', this.unloadPlugin.bind(this));
|
||||||
@@ -185,12 +189,12 @@ class PluginLoader extends Logger {
|
|||||||
this.deckyState.setHasLoaderUpdate(true);
|
this.deckyState.setHasLoaderUpdate(true);
|
||||||
if (this.notificationService.shouldNotify('deckyUpdates')) {
|
if (this.notificationService.shouldNotify('deckyUpdates')) {
|
||||||
this.toaster.toast({
|
this.toaster.toast({
|
||||||
title: <TranslationHelper trans_class={TranslationClass.PLUGIN_LOADER} trans_text="decky_title" />,
|
title: <TranslationHelper transClass={TranslationClass.PLUGIN_LOADER} transText="decky_title" />,
|
||||||
body: (
|
body: (
|
||||||
<TranslationHelper
|
<TranslationHelper
|
||||||
trans_class={TranslationClass.PLUGIN_LOADER}
|
transClass={TranslationClass.PLUGIN_LOADER}
|
||||||
trans_text="decky_update_available"
|
transText="decky_update_available"
|
||||||
i18n_args={{ tag_name: versionInfo?.remote?.tag_name }}
|
i18nArgs={{ tag_name: versionInfo?.remote?.tag_name }}
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
onClick: () => Router.Navigate('/decky/settings'),
|
onClick: () => Router.Navigate('/decky/settings'),
|
||||||
@@ -213,12 +217,12 @@ class PluginLoader extends Logger {
|
|||||||
const updates = await this.checkPluginUpdates();
|
const updates = await this.checkPluginUpdates();
|
||||||
if (updates?.size > 0 && this.notificationService.shouldNotify('pluginUpdates')) {
|
if (updates?.size > 0 && this.notificationService.shouldNotify('pluginUpdates')) {
|
||||||
this.toaster.toast({
|
this.toaster.toast({
|
||||||
title: <TranslationHelper trans_class={TranslationClass.PLUGIN_LOADER} trans_text="decky_title" />,
|
title: <TranslationHelper transClass={TranslationClass.PLUGIN_LOADER} transText="decky_title" />,
|
||||||
body: (
|
body: (
|
||||||
<TranslationHelper
|
<TranslationHelper
|
||||||
trans_class={TranslationClass.PLUGIN_LOADER}
|
transClass={TranslationClass.PLUGIN_LOADER}
|
||||||
trans_text="plugin_update"
|
transText="plugin_update"
|
||||||
i18n_args={{ count: updates.size }}
|
i18nArgs={{ count: updates.size }}
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
onClick: () => Router.Navigate('/decky/settings/plugins'),
|
onClick: () => Router.Navigate('/decky/settings/plugins'),
|
||||||
@@ -294,6 +298,10 @@ class PluginLoader extends Logger {
|
|||||||
this.routerHook.removeRoute('/decky/settings');
|
this.routerHook.removeRoute('/decky/settings');
|
||||||
deinitSteamFixes();
|
deinitSteamFixes();
|
||||||
deinitFilepickerPatches();
|
deinitFilepickerPatches();
|
||||||
|
this.routerHook.deinit();
|
||||||
|
this.tabsHook.deinit();
|
||||||
|
this.toaster.deinit();
|
||||||
|
this.errorBoundaryHook.deinit();
|
||||||
}
|
}
|
||||||
|
|
||||||
public unloadPlugin(name: string) {
|
public unloadPlugin(name: string) {
|
||||||
@@ -365,7 +373,9 @@ class PluginLoader extends Logger {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
let plugin_export: (serverAPI: any) => Plugin = await eval(await res.text());
|
let plugin_export: (serverAPI: any) => Plugin = await eval(
|
||||||
|
(await res.text()) + `\n//# sourceURL=decky://decky/legacy_plugin/${encodeURIComponent(name)}/index.js`,
|
||||||
|
);
|
||||||
let plugin = plugin_export(this.createLegacyPluginAPI(name));
|
let plugin = plugin_export(this.createLegacyPluginAPI(name));
|
||||||
this.plugins.push({
|
this.plugins.push({
|
||||||
...plugin,
|
...plugin,
|
||||||
@@ -384,7 +394,7 @@ class PluginLoader extends Logger {
|
|||||||
<PanelSection>
|
<PanelSection>
|
||||||
<PanelSectionRow>
|
<PanelSectionRow>
|
||||||
<div className={quickAccessMenuClasses.FriendsTitle} style={{ display: 'flex', justifyContent: 'center' }}>
|
<div className={quickAccessMenuClasses.FriendsTitle} style={{ display: 'flex', justifyContent: 'center' }}>
|
||||||
<TranslationHelper trans_class={TranslationClass.PLUGIN_LOADER} trans_text="error" />
|
<TranslationHelper transClass={TranslationClass.PLUGIN_LOADER} transText="error" />
|
||||||
</div>
|
</div>
|
||||||
</PanelSectionRow>
|
</PanelSectionRow>
|
||||||
<PanelSectionRow>
|
<PanelSectionRow>
|
||||||
@@ -395,9 +405,9 @@ class PluginLoader extends Logger {
|
|||||||
<PanelSectionRow>
|
<PanelSectionRow>
|
||||||
<div className={quickAccessMenuClasses.Text}>
|
<div className={quickAccessMenuClasses.Text}>
|
||||||
<TranslationHelper
|
<TranslationHelper
|
||||||
trans_class={TranslationClass.PLUGIN_LOADER}
|
transClass={TranslationClass.PLUGIN_LOADER}
|
||||||
trans_text="plugin_error_uninstall"
|
transText="plugin_error_uninstall"
|
||||||
i18n_args={{ name: name }}
|
i18nArgs={{ name: name }}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</PanelSectionRow>
|
</PanelSectionRow>
|
||||||
@@ -412,9 +422,9 @@ class PluginLoader extends Logger {
|
|||||||
this.toaster.toast({
|
this.toaster.toast({
|
||||||
title: (
|
title: (
|
||||||
<TranslationHelper
|
<TranslationHelper
|
||||||
trans_class={TranslationClass.PLUGIN_LOADER}
|
transClass={TranslationClass.PLUGIN_LOADER}
|
||||||
trans_text="plugin_load_error.toast"
|
transText="plugin_load_error.toast"
|
||||||
i18n_args={{ name: name }}
|
i18nArgs={{ name: name }}
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
body: '' + e,
|
body: '' + e,
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// import reloadFix from './reload';
|
// import reloadFix from './reload';
|
||||||
// import restartFix from './restart';
|
import restartFix from './restart';
|
||||||
let fixes: Function[] = [];
|
let fixes: Function[] = [];
|
||||||
|
|
||||||
export function deinitSteamFixes() {
|
export function deinitSteamFixes() {
|
||||||
@@ -8,5 +8,5 @@ export function deinitSteamFixes() {
|
|||||||
|
|
||||||
export async function initSteamFixes() {
|
export async function initSteamFixes() {
|
||||||
// fixes.push(await reloadFix());
|
// fixes.push(await reloadFix());
|
||||||
// fixes.push(await restartFix());
|
fixes.push(await restartFix());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,21 +0,0 @@
|
|||||||
import { getFocusNavController, sleep } from '@decky/ui';
|
|
||||||
|
|
||||||
import Logger from '../logger';
|
|
||||||
|
|
||||||
const logger = new Logger('ReloadSteamFix');
|
|
||||||
|
|
||||||
declare global {
|
|
||||||
var GamepadNavTree: any;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default async function reloadFix() {
|
|
||||||
// Hack to unbreak the ui when reloading it
|
|
||||||
await sleep(4000);
|
|
||||||
if (getFocusNavController()?.m_rgAllContexts?.length == 0) {
|
|
||||||
SteamClient.URL.ExecuteSteamURL('steam://open/settings');
|
|
||||||
logger.log('Applied UI reload fix.');
|
|
||||||
}
|
|
||||||
|
|
||||||
// This steamfix does not need to deinit.
|
|
||||||
return () => {};
|
|
||||||
}
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
|
import type { ToastData } from '@decky/api';
|
||||||
import {
|
import {
|
||||||
Export,
|
Export,
|
||||||
Patch,
|
Patch,
|
||||||
ToastData,
|
|
||||||
afterPatch,
|
afterPatch,
|
||||||
findClass,
|
findClass,
|
||||||
findInReactTree,
|
findInReactTree,
|
||||||
@@ -124,12 +124,12 @@ class Toaster extends Logger {
|
|||||||
this.node.alternate.type = this.node.type;
|
this.node.alternate.type = this.node.type;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const oRender = this.rNode.stateNode.__proto__.render;
|
const oRender = Object.getPrototypeOf(this.rNode.stateNode).render;
|
||||||
let int: NodeJS.Timer | undefined;
|
let int: NodeJS.Timeout | undefined;
|
||||||
this.rNode.stateNode.render = (...args: any[]) => {
|
this.rNode.stateNode.render = (...args: any[]) => {
|
||||||
const ret = oRender.call(this.rNode.stateNode, ...args);
|
const ret = oRender.call(this.rNode.stateNode, ...args);
|
||||||
if (ret && !this?.node?.return?.return) {
|
if (ret && !this?.node?.return?.return) {
|
||||||
clearInterval(int);
|
int && clearInterval(int);
|
||||||
int = setInterval(() => {
|
int = setInterval(() => {
|
||||||
const n = findToasterRoot(tree, 0);
|
const n = findToasterRoot(tree, 0);
|
||||||
if (n?.return) {
|
if (n?.return) {
|
||||||
|
|||||||
@@ -25,9 +25,6 @@ export interface VerInfo {
|
|||||||
|
|
||||||
export const doUpdate = DeckyBackend.callable('updater/do_update');
|
export const doUpdate = DeckyBackend.callable('updater/do_update');
|
||||||
export const doRestart = DeckyBackend.callable('updater/do_restart');
|
export const doRestart = DeckyBackend.callable('updater/do_restart');
|
||||||
|
export const doShutdown = DeckyBackend.callable('updater/do_shutdown');
|
||||||
export const getVersionInfo = DeckyBackend.callable<[], VerInfo>('updater/get_version_info');
|
export const getVersionInfo = DeckyBackend.callable<[], VerInfo>('updater/get_version_info');
|
||||||
export const checkForUpdates = DeckyBackend.callable<[], VerInfo>('updater/check_for_updates');
|
export const checkForUpdates = DeckyBackend.callable<[], VerInfo>('updater/check_for_updates');
|
||||||
|
|
||||||
DeckyBackend.addEventListener('updater/finish_download', async () => {
|
|
||||||
await doRestart();
|
|
||||||
});
|
|
||||||
|
|||||||
@@ -11,47 +11,42 @@ export enum TranslationClass {
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface TranslationHelperProps {
|
interface TranslationHelperProps {
|
||||||
trans_class: TranslationClass;
|
transClass: TranslationClass;
|
||||||
trans_text: string;
|
transText: string;
|
||||||
i18n_args?: {};
|
i18nArgs?: {};
|
||||||
install_type?: number;
|
installType?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
const logger = new Logger('TranslationHelper');
|
const logger = new Logger('TranslationHelper');
|
||||||
|
|
||||||
const TranslationHelper: FC<TranslationHelperProps> = ({
|
const TranslationHelper: FC<TranslationHelperProps> = ({ transClass, transText, i18nArgs = null, installType = 0 }) => {
|
||||||
trans_class,
|
|
||||||
trans_text,
|
|
||||||
i18n_args = null,
|
|
||||||
install_type = 0,
|
|
||||||
}) => {
|
|
||||||
return (
|
return (
|
||||||
<Translation>
|
<Translation>
|
||||||
{(t, {}) => {
|
{(t, {}) => {
|
||||||
switch (trans_class) {
|
switch (transClass) {
|
||||||
case TranslationClass.PLUGIN_LOADER:
|
case TranslationClass.PLUGIN_LOADER:
|
||||||
return i18n_args
|
return i18nArgs
|
||||||
? t(TranslationClass.PLUGIN_LOADER + '.' + trans_text, i18n_args)
|
? t(TranslationClass.PLUGIN_LOADER + '.' + transText, i18nArgs)
|
||||||
: t(TranslationClass.PLUGIN_LOADER + '.' + trans_text);
|
: t(TranslationClass.PLUGIN_LOADER + '.' + transText);
|
||||||
case TranslationClass.PLUGIN_INSTALL_MODAL:
|
case TranslationClass.PLUGIN_INSTALL_MODAL:
|
||||||
switch (install_type) {
|
switch (installType) {
|
||||||
case InstallType.INSTALL:
|
case InstallType.INSTALL:
|
||||||
return i18n_args
|
return i18nArgs
|
||||||
? t(TranslationClass.PLUGIN_INSTALL_MODAL + '.install.' + trans_text, i18n_args)
|
? t(TranslationClass.PLUGIN_INSTALL_MODAL + '.install.' + transText, i18nArgs)
|
||||||
: t(TranslationClass.PLUGIN_INSTALL_MODAL + '.install.' + trans_text);
|
: t(TranslationClass.PLUGIN_INSTALL_MODAL + '.install.' + transText);
|
||||||
case InstallType.REINSTALL:
|
case InstallType.REINSTALL:
|
||||||
return i18n_args
|
return i18nArgs
|
||||||
? t(TranslationClass.PLUGIN_INSTALL_MODAL + '.reinstall.' + trans_text, i18n_args)
|
? t(TranslationClass.PLUGIN_INSTALL_MODAL + '.reinstall.' + transText, i18nArgs)
|
||||||
: t(TranslationClass.PLUGIN_INSTALL_MODAL + '.reinstall.' + trans_text);
|
: t(TranslationClass.PLUGIN_INSTALL_MODAL + '.reinstall.' + transText);
|
||||||
case InstallType.UPDATE:
|
case InstallType.UPDATE:
|
||||||
return i18n_args
|
return i18nArgs
|
||||||
? t(TranslationClass.PLUGIN_INSTALL_MODAL + '.update.' + trans_text, i18n_args)
|
? t(TranslationClass.PLUGIN_INSTALL_MODAL + '.update.' + transText, i18nArgs)
|
||||||
: t(TranslationClass.PLUGIN_INSTALL_MODAL + '.update.' + trans_text);
|
: t(TranslationClass.PLUGIN_INSTALL_MODAL + '.update.' + transText);
|
||||||
}
|
}
|
||||||
case TranslationClass.DEVELOPER:
|
case TranslationClass.DEVELOPER:
|
||||||
return i18n_args
|
return i18nArgs
|
||||||
? t(TranslationClass.DEVELOPER + '.' + trans_text, i18n_args)
|
? t(TranslationClass.DEVELOPER + '.' + transText, i18nArgs)
|
||||||
: t(TranslationClass.DEVELOPER + '.' + trans_text);
|
: t(TranslationClass.DEVELOPER + '.' + transText);
|
||||||
default:
|
default:
|
||||||
logger.error('We should never fall in the default case!');
|
logger.error('We should never fall in the default case!');
|
||||||
return '';
|
return '';
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
#! /usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
# Usage: deckdebug.sh DECKIP:8081
|
# Usage: deckdebug.sh DECKIP:8081
|
||||||
# Dependencies: websocat jq curl chromium
|
# Dependencies: websocat jq curl chromium
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user