Custom error handler and some misc fixes

This commit is contained in:
AAGaming
2024-05-25 19:14:54 -04:00
parent 96cc72f2ca
commit a84a13c76d
28 changed files with 1140 additions and 1126 deletions
+1 -1
View File
@@ -41,7 +41,7 @@
"deploy"
],
"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": []
},
{
+1
View File
@@ -16,6 +16,7 @@ from .enums import UserType
from logging import getLogger
from packaging.version import Version
SSHD_UNIT = "sshd.service"
REMOTE_DEBUGGER_UNIT = "steam-web-debug-portforward.service"
# global vars
+1 -1
View File
@@ -160,7 +160,7 @@ class Loader:
self.plugins[plugin.name] = plugin.start()
self.logger.info(f"Loaded {plugin.name}")
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:
self.logger.error(f"Could not load {file}. {e}")
print_exc()
@@ -158,6 +158,7 @@ async def service_start(service_name : str) -> bool:
async def restart_webhelper() -> bool:
logger.info("Restarting steamwebhelper")
# TODO move to pkill
res = run(["killall", "-s", "SIGTERM", "steamwebhelper"], stdout=DEVNULL, stderr=DEVNULL)
return res.returncode == 0
+5 -3
View File
@@ -99,8 +99,10 @@ class PluginWrapper:
return self
def stop(self, uninstall: bool = False):
self._listener_task.cancel()
if hasattr(self, "_listener_task"):
self._listener_task.cancel()
async def _(self: PluginWrapper):
await self._socket.write_single_line(dumps({ "stop": True, "uninstall": uninstall }, ensure_ascii=False))
await self._socket.close_socket_connection()
if hasattr(self, "_socket"):
await self._socket.write_single_line(dumps({ "stop": True, "uninstall": uninstall }, ensure_ascii=False))
await self._socket.close_socket_connection()
create_task(_(self))
@@ -142,7 +142,10 @@ class SandboxedPlugin:
try:
self.log.info("Attempting to uninstall with plugin " + self.name + "'s \"_uninstall\" function.\n")
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")
else:
self.log.info("Could not find \"_uninstall\" in " + self.name + "'s main.py" + "\n")
+6 -2
View File
@@ -6,7 +6,7 @@ from os import getcwd, path, remove
from typing import TYPE_CHECKING, List, TypedDict
if TYPE_CHECKING:
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
from typing import List, TYPE_CHECKING, TypedDict
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/check_for_updates", self.check_for_updates);
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/get_testing_versions", self.get_testing_versions);
context.ws.add_route("updater/download_testing_version", self.download_testing_version);
@@ -184,8 +185,8 @@ class Updater:
logger.info("Updated loader installation.")
await self.context.ws.emit("updater/finish_download")
await self.do_restart()
await tab.close_websocket()
await self.do_restart()
async def do_update(self):
logger.debug("Starting update.")
@@ -242,6 +243,9 @@ class Updater:
async def do_restart(self):
await service_restart("plugin_loader")
async def do_shutdown(self):
await service_stop("plugin_loader")
async def get_testing_versions(self) -> List[TestingVersion]:
result: List[TestingVersion] = []
async with ClientSession() as web:
+10
View File
@@ -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/allow_remote_debugging", self.allow_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/disable_rdt", self.disable_rdt)
context.ws.add_route("utilities/enable_rdt", self.enable_rdt)
@@ -284,6 +286,14 @@ class Utilities:
await service_stop(helpers.REMOTE_DEBUGGER_UNIT)
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,
path: str | None = None,
include_files: bool = True,
+16 -15
View File
@@ -10,30 +10,31 @@
"format": "prettier -c src -w"
},
"devDependencies": {
"@decky/api": "^1.0.3",
"@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-node-resolve": "^13.3.0",
"@rollup/plugin-replace": "^4.0.0",
"@rollup/plugin-typescript": "^8.5.0",
"@types/react": "16.14.0",
"@types/react-file-icon": "^1.0.1",
"@types/react": "18.2.0",
"@types/react-file-icon": "^1.0.4",
"@types/react-router": "5.1.18",
"@types/webpack": "^5.28.1",
"@types/webpack": "^5.28.5",
"husky": "^8.0.3",
"i18next-parser": "^8.0.0",
"i18next-parser": "^8.13.0",
"import-sort-style-module": "^6.0.0",
"inquirer": "^8.2.5",
"inquirer": "^8.2.6",
"prettier": "^3.2.5",
"prettier-plugin-import-sort": "^0.0.7",
"react": "16.14.0",
"react-dom": "16.14.0",
"react": "18.2.0",
"react-dom": "18.2.0",
"rollup": "^2.79.1",
"rollup-plugin-delete": "^2.0.0",
"rollup-plugin-external-globals": "^0.6.1",
"rollup-plugin-polyfill-node": "^0.10.2",
"rollup-plugin-visualizer": "^5.9.2",
"tslib": "^2.5.3",
"rollup-plugin-visualizer": "^5.12.0",
"tslib": "^2.6.2",
"typescript": "^4.9.5"
},
"importSort": {
@@ -44,12 +45,12 @@
},
"dependencies": {
"decky-frontend-lib": "3.25.0",
"filesize": "^10.0.7",
"i18next": "^23.2.1",
"i18next-http-backend": "^2.2.1",
"react-file-icon": "^1.3.0",
"filesize": "^10.1.2",
"i18next": "^23.11.5",
"i18next-http-backend": "^2.5.2",
"react-file-icon": "^1.4.0",
"react-i18next": "^12.3.1",
"react-icons": "^4.9.0",
"react-icons": "^4.12.0",
"react-markdown": "^8.0.7",
"remark-gfm": "^3.0.1"
}
+739 -1003
View File
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 {
components: Map<string, FC>;
@@ -40,6 +40,7 @@ export const useDeckyGlobalComponentsState = () => useContext(DeckyGlobalCompone
interface Props {
deckyGlobalComponentsState: DeckyGlobalComponentsState;
children: ReactNode;
}
export const DeckyGlobalComponentsStateContextProvider: FC<Props> = ({
+2 -1
View File
@@ -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';
export interface RouterEntry {
@@ -71,6 +71,7 @@ export const useDeckyRouterState = () => useContext(DeckyRouterStateContext);
interface Props {
deckyRouterState: DeckyRouterState;
children: ReactNode;
}
export const DeckyRouterStateContextProvider: FC<Props> = ({ children, deckyRouterState }) => {
+2 -1
View File
@@ -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 { Plugin } from '../plugin';
@@ -134,6 +134,7 @@ export const useDeckyState = () => useContext(DeckyStateContext);
interface Props {
deckyState: DeckyState;
children?: ReactNode;
}
export const DeckyStateContextProvider: FC<Props> = ({ children, deckyState }) => {
+3 -2
View File
@@ -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 { ReactElement } from 'react-markdown/lib/react-markdown';
@@ -28,7 +29,7 @@ const DeckyToaster: FC<DeckyToasterProps> = () => {
}
useEffect(() => {
// not actually node but TS is shit
let interval: NodeJS.Timer | null;
let interval: NodeJS.Timeout | number | null;
if (renderedToast) {
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';
interface PublicDeckyToasterState {
+2 -1
View File
@@ -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';
interface ToastProps {
@@ -59,7 +59,7 @@ const DropdownMultiselect: FC<{
const [itemsSelected, setItemsSelected] = useState<any>(selected);
const { t } = useTranslation();
const handleItemSelect = useCallback((checked, value) => {
const handleItemSelect = useCallback((checked: boolean, value: any) => {
setItemsSelected((x: any) =>
checked ? [...x.filter((y: any) => y !== value), value] : x.filter((y: any) => y !== value),
);
@@ -60,10 +60,10 @@ const PluginInstallModal: FC<PluginInstallModalProps> = ({
strTitle={
<div>
<TranslationHelper
trans_class={TranslationClass.PLUGIN_INSTALL_MODAL}
trans_text="title"
i18n_args={{ artifact: artifact }}
install_type={installType}
transClass={TranslationClass.PLUGIN_INSTALL_MODAL}
transText="title"
i18nArgs={{ artifact: artifact }}
installType={installType}
/>
</div>
}
@@ -71,17 +71,17 @@ const PluginInstallModal: FC<PluginInstallModalProps> = ({
loading ? (
<div>
<TranslationHelper
trans_class={TranslationClass.PLUGIN_INSTALL_MODAL}
trans_text="button_processing"
install_type={installType}
transClass={TranslationClass.PLUGIN_INSTALL_MODAL}
transText="button_processing"
installType={installType}
/>
</div>
) : (
<div>
<TranslationHelper
trans_class={TranslationClass.PLUGIN_INSTALL_MODAL}
trans_text="button_idle"
install_type={installType}
transClass={TranslationClass.PLUGIN_INSTALL_MODAL}
transText="button_idle"
installType={installType}
/>
</div>
)
@@ -89,13 +89,13 @@ const PluginInstallModal: FC<PluginInstallModalProps> = ({
>
<div>
<TranslationHelper
trans_class={TranslationClass.PLUGIN_INSTALL_MODAL}
trans_text="desc"
i18n_args={{
transClass={TranslationClass.PLUGIN_INSTALL_MODAL}
transText="desc"
i18nArgs={{
artifact: artifact,
version: version,
}}
install_type={installType}
installType={installType}
/>
</div>
{loading && (
+3 -3
View File
@@ -40,11 +40,11 @@ export async function setShowValveInternal(show: boolean) {
export async function setShouldConnectToReactDevTools(enable: boolean) {
DeckyPluginLoader.toaster.toast({
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 />,
});
await sleep(5000);
+69
View File
@@ -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;
+26 -16
View File
@@ -21,6 +21,7 @@ import PluginUninstallModal from './components/modals/PluginUninstallModal';
import NotificationBadge from './components/NotificationBadge';
import PluginView from './components/PluginView';
import WithSuspense from './components/WithSuspense';
import ErrorBoundaryHook from './errorboundary-hook';
import { FrozenPluginService } from './frozen-plugins-service';
import { HiddenPluginsService } from './hidden-plugins-service';
import Logger from './logger';
@@ -61,6 +62,7 @@ const callPluginMethod = DeckyBackend.callable<[pluginName: string, method: stri
class PluginLoader extends Logger {
private plugins: Plugin[] = [];
private errorBoundaryHook: ErrorBoundaryHook = new ErrorBoundaryHook();
private tabsHook: TabsHook = new TabsHook();
private routerHook: RouterHook = new RouterHook();
public toaster: Toaster = new Toaster();
@@ -79,6 +81,8 @@ class PluginLoader extends Logger {
constructor() {
super(PluginLoader.name);
this.errorBoundaryHook.init();
DeckyBackend.addEventListener('loader/notify_updates', this.notifyUpdates.bind(this));
DeckyBackend.addEventListener('loader/import_plugin', this.importPlugin.bind(this));
DeckyBackend.addEventListener('loader/unload_plugin', this.unloadPlugin.bind(this));
@@ -185,12 +189,12 @@ class PluginLoader extends Logger {
this.deckyState.setHasLoaderUpdate(true);
if (this.notificationService.shouldNotify('deckyUpdates')) {
this.toaster.toast({
title: <TranslationHelper trans_class={TranslationClass.PLUGIN_LOADER} trans_text="decky_title" />,
title: <TranslationHelper transClass={TranslationClass.PLUGIN_LOADER} transText="decky_title" />,
body: (
<TranslationHelper
trans_class={TranslationClass.PLUGIN_LOADER}
trans_text="decky_update_available"
i18n_args={{ tag_name: versionInfo?.remote?.tag_name }}
transClass={TranslationClass.PLUGIN_LOADER}
transText="decky_update_available"
i18nArgs={{ tag_name: versionInfo?.remote?.tag_name }}
/>
),
onClick: () => Router.Navigate('/decky/settings'),
@@ -213,12 +217,12 @@ class PluginLoader extends Logger {
const updates = await this.checkPluginUpdates();
if (updates?.size > 0 && this.notificationService.shouldNotify('pluginUpdates')) {
this.toaster.toast({
title: <TranslationHelper trans_class={TranslationClass.PLUGIN_LOADER} trans_text="decky_title" />,
title: <TranslationHelper transClass={TranslationClass.PLUGIN_LOADER} transText="decky_title" />,
body: (
<TranslationHelper
trans_class={TranslationClass.PLUGIN_LOADER}
trans_text="plugin_update"
i18n_args={{ count: updates.size }}
transClass={TranslationClass.PLUGIN_LOADER}
transText="plugin_update"
i18nArgs={{ count: updates.size }}
/>
),
onClick: () => Router.Navigate('/decky/settings/plugins'),
@@ -294,6 +298,10 @@ class PluginLoader extends Logger {
this.routerHook.removeRoute('/decky/settings');
deinitSteamFixes();
deinitFilepickerPatches();
this.routerHook.deinit();
this.tabsHook.deinit();
this.toaster.deinit();
this.errorBoundaryHook.deinit();
}
public unloadPlugin(name: string) {
@@ -365,7 +373,9 @@ class PluginLoader extends Logger {
},
});
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));
this.plugins.push({
...plugin,
@@ -384,7 +394,7 @@ class PluginLoader extends Logger {
<PanelSection>
<PanelSectionRow>
<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>
</PanelSectionRow>
<PanelSectionRow>
@@ -395,9 +405,9 @@ class PluginLoader extends Logger {
<PanelSectionRow>
<div className={quickAccessMenuClasses.Text}>
<TranslationHelper
trans_class={TranslationClass.PLUGIN_LOADER}
trans_text="plugin_error_uninstall"
i18n_args={{ name: name }}
transClass={TranslationClass.PLUGIN_LOADER}
transText="plugin_error_uninstall"
i18nArgs={{ name: name }}
/>
</div>
</PanelSectionRow>
@@ -412,9 +422,9 @@ class PluginLoader extends Logger {
this.toaster.toast({
title: (
<TranslationHelper
trans_class={TranslationClass.PLUGIN_LOADER}
trans_text="plugin_load_error.toast"
i18n_args={{ name: name }}
transClass={TranslationClass.PLUGIN_LOADER}
transText="plugin_load_error.toast"
i18nArgs={{ name: name }}
/>
),
body: '' + e,
+2 -2
View File
@@ -1,5 +1,5 @@
// import reloadFix from './reload';
// import restartFix from './restart';
import restartFix from './restart';
let fixes: Function[] = [];
export function deinitSteamFixes() {
@@ -8,5 +8,5 @@ export function deinitSteamFixes() {
export async function initSteamFixes() {
// fixes.push(await reloadFix());
// fixes.push(await restartFix());
fixes.push(await restartFix());
}
-21
View File
@@ -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 () => {};
}
+4 -4
View File
@@ -1,7 +1,7 @@
import type { ToastData } from '@decky/api';
import {
Export,
Patch,
ToastData,
afterPatch,
findClass,
findInReactTree,
@@ -124,12 +124,12 @@ class Toaster extends Logger {
this.node.alternate.type = this.node.type;
}
};
const oRender = this.rNode.stateNode.__proto__.render;
let int: NodeJS.Timer | undefined;
const oRender = Object.getPrototypeOf(this.rNode.stateNode).render;
let int: NodeJS.Timeout | undefined;
this.rNode.stateNode.render = (...args: any[]) => {
const ret = oRender.call(this.rNode.stateNode, ...args);
if (ret && !this?.node?.return?.return) {
clearInterval(int);
int && clearInterval(int);
int = setInterval(() => {
const n = findToasterRoot(tree, 0);
if (n?.return) {
+1 -4
View File
@@ -25,9 +25,6 @@ export interface VerInfo {
export const doUpdate = DeckyBackend.callable('updater/do_update');
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 checkForUpdates = DeckyBackend.callable<[], VerInfo>('updater/check_for_updates');
DeckyBackend.addEventListener('updater/finish_download', async () => {
await doRestart();
});
+22 -27
View File
@@ -11,47 +11,42 @@ export enum TranslationClass {
}
interface TranslationHelperProps {
trans_class: TranslationClass;
trans_text: string;
i18n_args?: {};
install_type?: number;
transClass: TranslationClass;
transText: string;
i18nArgs?: {};
installType?: number;
}
const logger = new Logger('TranslationHelper');
const TranslationHelper: FC<TranslationHelperProps> = ({
trans_class,
trans_text,
i18n_args = null,
install_type = 0,
}) => {
const TranslationHelper: FC<TranslationHelperProps> = ({ transClass, transText, i18nArgs = null, installType = 0 }) => {
return (
<Translation>
{(t, {}) => {
switch (trans_class) {
switch (transClass) {
case TranslationClass.PLUGIN_LOADER:
return i18n_args
? t(TranslationClass.PLUGIN_LOADER + '.' + trans_text, i18n_args)
: t(TranslationClass.PLUGIN_LOADER + '.' + trans_text);
return i18nArgs
? t(TranslationClass.PLUGIN_LOADER + '.' + transText, i18nArgs)
: t(TranslationClass.PLUGIN_LOADER + '.' + transText);
case TranslationClass.PLUGIN_INSTALL_MODAL:
switch (install_type) {
switch (installType) {
case InstallType.INSTALL:
return i18n_args
? t(TranslationClass.PLUGIN_INSTALL_MODAL + '.install.' + trans_text, i18n_args)
: t(TranslationClass.PLUGIN_INSTALL_MODAL + '.install.' + trans_text);
return i18nArgs
? t(TranslationClass.PLUGIN_INSTALL_MODAL + '.install.' + transText, i18nArgs)
: t(TranslationClass.PLUGIN_INSTALL_MODAL + '.install.' + transText);
case InstallType.REINSTALL:
return i18n_args
? t(TranslationClass.PLUGIN_INSTALL_MODAL + '.reinstall.' + trans_text, i18n_args)
: t(TranslationClass.PLUGIN_INSTALL_MODAL + '.reinstall.' + trans_text);
return i18nArgs
? t(TranslationClass.PLUGIN_INSTALL_MODAL + '.reinstall.' + transText, i18nArgs)
: t(TranslationClass.PLUGIN_INSTALL_MODAL + '.reinstall.' + transText);
case InstallType.UPDATE:
return i18n_args
? t(TranslationClass.PLUGIN_INSTALL_MODAL + '.update.' + trans_text, i18n_args)
: t(TranslationClass.PLUGIN_INSTALL_MODAL + '.update.' + trans_text);
return i18nArgs
? t(TranslationClass.PLUGIN_INSTALL_MODAL + '.update.' + transText, i18nArgs)
: t(TranslationClass.PLUGIN_INSTALL_MODAL + '.update.' + transText);
}
case TranslationClass.DEVELOPER:
return i18n_args
? t(TranslationClass.DEVELOPER + '.' + trans_text, i18n_args)
: t(TranslationClass.DEVELOPER + '.' + trans_text);
return i18nArgs
? t(TranslationClass.DEVELOPER + '.' + transText, i18nArgs)
: t(TranslationClass.DEVELOPER + '.' + transText);
default:
logger.error('We should never fall in the default case!');
return '';
+1 -1
View File
@@ -1,4 +1,4 @@
#! /usr/bin/env bash
#!/usr/bin/env bash
# Usage: deckdebug.sh DECKIP:8081
# Dependencies: websocat jq curl chromium