mirror of
https://github.com/SteamDeckHomebrew/decky-loader.git
synced 2026-06-17 08:47:49 +00:00
Merge branch 'main' into i18n
This commit is contained in:
@@ -41,7 +41,7 @@
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"decky-frontend-lib": "^3.7.14",
|
||||
"decky-frontend-lib": "^3.18.4",
|
||||
"i18next": "^22.0.6",
|
||||
"i18next-fs-backend": "^2.0.0",
|
||||
"react-file-icon": "^1.2.0",
|
||||
|
||||
Generated
+4
-5
@@ -10,7 +10,7 @@ specifiers:
|
||||
'@types/react-file-icon': ^1.0.1
|
||||
'@types/react-router': 5.1.18
|
||||
'@types/webpack': ^5.28.0
|
||||
decky-frontend-lib: ^3.7.14
|
||||
decky-frontend-lib: ^3.18.4
|
||||
husky: ^8.0.1
|
||||
i18next: ^22.0.6
|
||||
i18next-fs-backend: ^2.0.0
|
||||
@@ -33,7 +33,7 @@ specifiers:
|
||||
typescript: ^4.7.4
|
||||
|
||||
dependencies:
|
||||
decky-frontend-lib: 3.7.14
|
||||
decky-frontend-lib: 3.18.4
|
||||
i18next: 22.0.6
|
||||
i18next-fs-backend: 2.0.0
|
||||
react-file-icon: 1.2.0_wcqkhtmu7mswc6yz4uyexck3ty
|
||||
@@ -1082,9 +1082,8 @@ packages:
|
||||
dependencies:
|
||||
ms: 2.1.2
|
||||
|
||||
/decky-frontend-lib/3.7.14:
|
||||
resolution:
|
||||
{ integrity: sha512-ShAoP3VqiwkJYukDBHsOF9fk7wYw0VaKpHw6j9WdzLxwZwBcg0J7QBNIFYP3nfA0UgEwSJVEg/22kONiplipmA== }
|
||||
/decky-frontend-lib/3.18.4:
|
||||
resolution: {integrity: sha512-i3TAe3RJtT1TK0rJgW9Ek5jxMWZRCYLDvqHDylGVieUvuyI7c8X+cogz30pP4cqeGOaA1d/MxBEbhlpD3JhVvg==}
|
||||
dev: false
|
||||
|
||||
/decode-named-character-reference/1.0.2:
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ConfirmModal, QuickAccessTab, Router, Spinner, staticClasses } from 'decky-frontend-lib';
|
||||
import { ConfirmModal, Navigation, QuickAccessTab, Spinner, staticClasses } from 'decky-frontend-lib';
|
||||
import { FC, useState } from 'react';
|
||||
|
||||
interface PluginInstallModalProps {
|
||||
@@ -20,7 +20,7 @@ const PluginInstallModal: FC<PluginInstallModalProps> = ({ artifact, version, ha
|
||||
onOK={async () => {
|
||||
setLoading(true);
|
||||
await onOK();
|
||||
setTimeout(() => Router.OpenQuickAccessMenu(QuickAccessTab.Decky), 250);
|
||||
setTimeout(() => Navigation.OpenQuickAccessMenu(QuickAccessTab.Decky), 250);
|
||||
setTimeout(() => window.DeckyPluginLoader.checkPluginUpdates(), 1000);
|
||||
}}
|
||||
onCancel={async () => {
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
import { Patch, findModuleChild, replacePatch } from 'decky-frontend-lib';
|
||||
import { Patch, findModuleChild, replacePatch, sleep } from 'decky-frontend-lib';
|
||||
|
||||
import Logger from '../../../../logger';
|
||||
|
||||
const logger = new Logger('LibraryPatch');
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
@@ -10,36 +14,44 @@ declare global {
|
||||
let patch: Patch;
|
||||
|
||||
function rePatch() {
|
||||
// If you patch anything on SteamClient within the first few seconds of the client having loaded it will get redefined for some reason, so repatch any of these changes that occur within the first 20s of the last patch
|
||||
// If you patch anything on SteamClient within the first few seconds of the client having loaded it will get redefined for some reason, so repatch any of these changes that occur with History.listen or an interval
|
||||
patch = replacePatch(window.SteamClient.Apps, 'PromptToChangeShortcut', async ([appid]: number[]) => {
|
||||
try {
|
||||
const details = window.appDetailsStore.GetAppDetails(appid);
|
||||
console.log(details);
|
||||
logger.debug('game details', details);
|
||||
// strShortcutStartDir
|
||||
const file = await window.DeckyPluginLoader.openFilePicker(details.strShortcutStartDir.replaceAll('"', ''));
|
||||
console.log('user selected', file);
|
||||
logger.debug('user selected', file);
|
||||
window.SteamClient.Apps.SetShortcutExe(appid, JSON.stringify(file.path));
|
||||
const pathArr = file.path.split('/');
|
||||
pathArr.pop();
|
||||
const folder = pathArr.join('/');
|
||||
window.SteamClient.Apps.SetShortcutStartDir(appid, JSON.stringify(folder));
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
logger.error(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// TODO type and add to frontend-lib
|
||||
const History = findModuleChild((m) => {
|
||||
if (typeof m !== 'object') return undefined;
|
||||
for (let prop in m) {
|
||||
if (m[prop]?.m_history) return m[prop].m_history;
|
||||
}
|
||||
});
|
||||
|
||||
export default async function libraryPatch() {
|
||||
try {
|
||||
rePatch();
|
||||
// TODO type and add to frontend-lib
|
||||
let History: any;
|
||||
|
||||
while (!History) {
|
||||
History = findModuleChild((m) => {
|
||||
if (typeof m !== 'object') return undefined;
|
||||
for (let prop in m) {
|
||||
if (m[prop]?.m_history) return m[prop].m_history;
|
||||
}
|
||||
});
|
||||
if (!History) {
|
||||
logger.debug('Waiting 5s for history to become available.');
|
||||
await sleep(5000);
|
||||
}
|
||||
}
|
||||
|
||||
const unlisten = History.listen(() => {
|
||||
if (window.SteamClient.Apps.PromptToChangeShortcut !== patch.patchedFunction) {
|
||||
rePatch();
|
||||
@@ -47,11 +59,11 @@ export default async function libraryPatch() {
|
||||
});
|
||||
|
||||
return () => {
|
||||
patch.unpatch();
|
||||
unlisten();
|
||||
patch.unpatch();
|
||||
};
|
||||
} catch (e) {
|
||||
console.error('Error patching library file picker', e);
|
||||
logger.error('Error patching library file picker', e);
|
||||
}
|
||||
return () => {};
|
||||
}
|
||||
|
||||
@@ -2,8 +2,8 @@ import {
|
||||
DialogButton,
|
||||
Dropdown,
|
||||
Focusable,
|
||||
Navigation,
|
||||
QuickAccessTab,
|
||||
Router,
|
||||
SingleDropdownOption,
|
||||
SuspensefulImage,
|
||||
joinClassNames,
|
||||
@@ -41,8 +41,8 @@ const PluginCard: FC<PluginCardProps> = ({ plugin }) => {
|
||||
}}
|
||||
onCancel={(_: CustomEvent) => {
|
||||
if (containerRef.current!.querySelectorAll('* :focus').length === 0) {
|
||||
Router.NavigateBackOrOpenMenu();
|
||||
setTimeout(() => Router.OpenQuickAccessMenu(QuickAccessTab.Decky), 1000);
|
||||
Navigation.NavigateBack();
|
||||
setTimeout(() => Navigation.OpenQuickAccessMenu(QuickAccessTab.Decky), 1000);
|
||||
} else {
|
||||
containerRef.current!.focus();
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import 'i18n.ts';
|
||||
|
||||
import {
|
||||
Navigation,
|
||||
ReactRouter,
|
||||
Router,
|
||||
fakeRenderComponent,
|
||||
@@ -28,13 +29,20 @@ const logger = new Logger('DeveloperMode');
|
||||
|
||||
let removeSettingsObserver: () => void = () => {};
|
||||
|
||||
export function setShowValveInternal(show: boolean) {
|
||||
const settingsMod = findModuleChild((m) => {
|
||||
if (typeof m !== 'object') return undefined;
|
||||
for (let prop in m) {
|
||||
if (typeof m[prop]?.settings?.bIsValveEmail !== 'undefined') return m[prop];
|
||||
export async function setShowValveInternal(show: boolean) {
|
||||
let settingsMod: any;
|
||||
while (!settingsMod) {
|
||||
settingsMod = findModuleChild((m) => {
|
||||
if (typeof m !== 'object') return undefined;
|
||||
for (let prop in m) {
|
||||
if (typeof m[prop]?.settings?.bIsValveEmail !== 'undefined') return m[prop];
|
||||
}
|
||||
});
|
||||
if (!settingsMod) {
|
||||
logger.debug('[ValveInternal] waiting for settingsMod');
|
||||
await sleep(1000);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (show) {
|
||||
removeSettingsObserver = settingsMod[
|
||||
@@ -76,13 +84,14 @@ export async function startup() {
|
||||
window.DFL = {
|
||||
findModuleChild,
|
||||
findModule,
|
||||
Navigation,
|
||||
Router,
|
||||
ReactRouter,
|
||||
ReactUtils: {
|
||||
fakeRenderComponent,
|
||||
findInReactTree,
|
||||
findInTree,
|
||||
},
|
||||
Router,
|
||||
ReactRouter,
|
||||
classes: {
|
||||
scrollClasses,
|
||||
staticClasses,
|
||||
|
||||
@@ -22,6 +22,7 @@ import WithSuspense from './components/WithSuspense';
|
||||
import Logger from './logger';
|
||||
import { Plugin } from './plugin';
|
||||
import RouterHook from './router-hook';
|
||||
import { deinitSteamFixes, initSteamFixes } from './steamfixes';
|
||||
import { checkForUpdates } from './store';
|
||||
import TabsHook from './tabs-hook';
|
||||
import OldTabsHook from './tabs-hook.old';
|
||||
@@ -95,6 +96,8 @@ class PluginLoader extends Logger {
|
||||
);
|
||||
});
|
||||
|
||||
initSteamFixes();
|
||||
|
||||
initFilepickerPatches();
|
||||
|
||||
this.updateVersion();
|
||||
@@ -188,6 +191,7 @@ class PluginLoader extends Logger {
|
||||
public deinit() {
|
||||
this.routerHook.removeRoute('/decky/store');
|
||||
this.routerHook.removeRoute('/decky/settings');
|
||||
deinitSteamFixes();
|
||||
deinitFilepickerPatches();
|
||||
this.focusWorkaroundPatch?.unpatch();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
## What's this?
|
||||
|
||||
`steamfixes` contains various fixes and workaround for things Valve has broken that cause Decky issues.
|
||||
|
||||
## Current fixes:
|
||||
|
||||
- StartRestart() -> StartShutdown(false) override:
|
||||
|
||||
StartRestart() breaks CEF debugging, StartShutdown(false) doesn't. We can safely replace StartRestart() with StartShutdown(false) as gamescope-session will automatically restart the steam client anyway if it shuts down, bypassing the broken restart codepath. Added 12/29/2022
|
||||
|
||||
- ExecuteSteamURL UI reload fix:
|
||||
|
||||
Starting sometime in November 2022, Valve broke reloading the Steam UI pages via location.reload, as it won't properly start the UI. We can manually trigger UI startup if we detect no active input contexts by calling `SteamClient.URL.ExecuteSteamURL("steam://open/settings/")` Added 12/29/2022
|
||||
@@ -0,0 +1,12 @@
|
||||
import reloadFix from './reload';
|
||||
import restartFix from './restart';
|
||||
let fixes: Function[] = [];
|
||||
|
||||
export function deinitSteamFixes() {
|
||||
fixes.forEach((deinit) => deinit());
|
||||
}
|
||||
|
||||
export async function initSteamFixes() {
|
||||
fixes.push(reloadFix());
|
||||
fixes.push(await restartFix());
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
import Logger from '../logger';
|
||||
|
||||
const logger = new Logger('ReloadSteamFix');
|
||||
|
||||
export default function reloadFix() {
|
||||
// Hack to unbreak the ui when reloading it
|
||||
if (window.FocusNavController?.m_rgAllContexts?.length == 0) {
|
||||
SteamClient.URL.ExecuteSteamURL('steam://open/settings');
|
||||
logger.log('Applied UI reload fix.');
|
||||
}
|
||||
|
||||
// This steamfix does not need to deinit.
|
||||
return () => {};
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
import { Patch, findModuleChild, replacePatch, sleep } from 'decky-frontend-lib';
|
||||
|
||||
import Logger from '../logger';
|
||||
|
||||
const logger = new Logger('RestartSteamFix');
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
SteamClient: any;
|
||||
appDetailsStore: any;
|
||||
}
|
||||
}
|
||||
|
||||
let patch: Patch;
|
||||
|
||||
function rePatch() {
|
||||
// If you patch anything on SteamClient within the first few seconds of the client having loaded it will get redefined for some reason, so repatch any of these changes that occur with History.listen or an interval
|
||||
patch = replacePatch(window.SteamClient.User, 'StartRestart', () => SteamClient.User.StartShutdown(false));
|
||||
}
|
||||
|
||||
export default async function restartFix() {
|
||||
try {
|
||||
rePatch();
|
||||
// TODO type and add to frontend-lib
|
||||
let History: any;
|
||||
|
||||
while (!History) {
|
||||
History = findModuleChild((m) => {
|
||||
if (typeof m !== 'object') return undefined;
|
||||
for (let prop in m) {
|
||||
if (m[prop]?.m_history) return m[prop].m_history;
|
||||
}
|
||||
});
|
||||
if (!History) {
|
||||
logger.debug('Waiting 5s for history to become available.');
|
||||
await sleep(5000);
|
||||
}
|
||||
}
|
||||
|
||||
function repatchIfNeeded() {
|
||||
if (window.SteamClient.User.StartRestart !== patch.patchedFunction) {
|
||||
rePatch();
|
||||
}
|
||||
}
|
||||
|
||||
const unlisten = History.listen(repatchIfNeeded);
|
||||
|
||||
// Just in case
|
||||
setTimeout(repatchIfNeeded, 5000);
|
||||
setTimeout(repatchIfNeeded, 10000);
|
||||
|
||||
return () => {
|
||||
unlisten();
|
||||
patch.unpatch();
|
||||
};
|
||||
} catch (e) {
|
||||
logger.error('Error patching StartRestart', e);
|
||||
}
|
||||
return () => {};
|
||||
}
|
||||
@@ -10,6 +10,7 @@ export enum Store {
|
||||
export interface StorePluginVersion {
|
||||
name: string;
|
||||
hash: string;
|
||||
artifact: string | undefined | null;
|
||||
}
|
||||
|
||||
export interface StorePlugin {
|
||||
@@ -73,9 +74,11 @@ export async function installFromURL(url: string) {
|
||||
}
|
||||
|
||||
export async function requestPluginInstall(plugin: string, selectedVer: StorePluginVersion) {
|
||||
const artifactUrl =
|
||||
selectedVer.artifact ?? `https://cdn.tzatzikiweeb.moe/file/steam-deck-homebrew/versions/${selectedVer.hash}.zip`;
|
||||
await window.DeckyPluginLoader.callServerMethod('install_plugin', {
|
||||
name: plugin,
|
||||
artifact: `https://cdn.tzatzikiweeb.moe/file/steam-deck-homebrew/versions/${selectedVer.hash}.zip`,
|
||||
artifact: artifactUrl,
|
||||
version: selectedVer.name,
|
||||
hash: selectedVer.hash,
|
||||
});
|
||||
|
||||
@@ -7,7 +7,6 @@ import Logger from './logger';
|
||||
declare global {
|
||||
interface Window {
|
||||
__TABS_HOOK_INSTANCE: any;
|
||||
securitystore: any;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,7 +22,6 @@ class TabsHook extends Logger {
|
||||
tabs: Tab[] = [];
|
||||
private qAMRoot?: any;
|
||||
private qamPatch?: Patch;
|
||||
private unsubscribeSecurity?: () => void;
|
||||
|
||||
constructor() {
|
||||
super('TabsHook');
|
||||
@@ -114,7 +112,6 @@ class TabsHook extends Logger {
|
||||
deinit() {
|
||||
this.qamPatch?.unpatch();
|
||||
this.qAMRoot.return.alternate.type = this.qAMRoot.return.type;
|
||||
this.unsubscribeSecurity?.();
|
||||
}
|
||||
|
||||
add(tab: Tab) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Patch, ToastData, afterPatch, findInReactTree, sleep } from 'decky-frontend-lib';
|
||||
import { Module, Patch, ToastData, afterPatch, findInReactTree, findModuleChild, sleep } from 'decky-frontend-lib';
|
||||
import { ReactNode } from 'react';
|
||||
|
||||
import Toast from './components/Toast';
|
||||
@@ -7,6 +7,7 @@ import Logger from './logger';
|
||||
declare global {
|
||||
interface Window {
|
||||
__TOASTER_INSTANCE: any;
|
||||
settingsStore: any;
|
||||
NotificationStore: any;
|
||||
}
|
||||
}
|
||||
@@ -16,7 +17,7 @@ class Toaster extends Logger {
|
||||
// private toasterState: DeckyToasterState = new DeckyToasterState();
|
||||
private node: any;
|
||||
private rNode: any;
|
||||
private settingsModule: any;
|
||||
private audioModule: any;
|
||||
private finishStartup?: () => void;
|
||||
private ready: Promise<void> = new Promise((res) => (this.finishStartup = res));
|
||||
private toasterPatch?: Patch;
|
||||
@@ -127,6 +128,17 @@ class Toaster extends Logger {
|
||||
this.rNode.stateNode.forceUpdate();
|
||||
delete this.rNode.stateNode.shouldComponentUpdate;
|
||||
|
||||
this.audioModule = findModuleChild((m: Module) => {
|
||||
if (typeof m !== 'object') return undefined;
|
||||
for (let prop in m) {
|
||||
try {
|
||||
if (m[prop].PlayNavSound && m[prop].RegisterCallbackOnPlaySound) return m[prop];
|
||||
} catch {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
this.log('Initialized');
|
||||
this.finishStartup?.();
|
||||
}
|
||||
@@ -135,24 +147,31 @@ class Toaster extends Logger {
|
||||
// toast.duration = toast.duration || 5e3;
|
||||
// this.toasterState.addToast(toast);
|
||||
await this.ready;
|
||||
const settings = this.settingsModule?.settings;
|
||||
let toastData = {
|
||||
nNotificationID: window.NotificationStore.m_nNextTestNotificationID++,
|
||||
rtCreated: Date.now(),
|
||||
eType: 15,
|
||||
eType: toast.eType || 11,
|
||||
nToastDurationMS: toast.duration || (toast.duration = 5e3),
|
||||
data: toast,
|
||||
decky: true,
|
||||
};
|
||||
// @ts-ignore
|
||||
toastData.data.appid = () => 0;
|
||||
if (toast.sound === undefined) toast.sound = 6;
|
||||
if (toast.playSound === undefined) toast.playSound = true;
|
||||
if (toast.showToast === undefined) toast.showToast = true;
|
||||
if (
|
||||
(settings?.bDisableAllToasts && !toast.critical) ||
|
||||
(settings?.bDisableToastsInGame && !toast.critical && window.NotificationStore.BIsUserInGame())
|
||||
(window.settingsStore.settings.bDisableAllToasts && !toast.critical) ||
|
||||
(window.settingsStore.settings.bDisableToastsInGame &&
|
||||
!toast.critical &&
|
||||
window.NotificationStore.BIsUserInGame())
|
||||
)
|
||||
return;
|
||||
window.NotificationStore.m_rgNotificationToasts.push(toastData);
|
||||
window.NotificationStore.DispatchNextToast();
|
||||
if (toast.playSound) this.audioModule?.PlayNavSound(toast.sound);
|
||||
if (toast.showToast) {
|
||||
window.NotificationStore.m_rgNotificationToasts.push(toastData);
|
||||
window.NotificationStore.DispatchNextToast();
|
||||
}
|
||||
}
|
||||
|
||||
deinit() {
|
||||
|
||||
Reference in New Issue
Block a user