mirror of
https://github.com/SteamDeckHomebrew/decky-loader.git
synced 2026-07-02 07:49:53 +00:00
Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| dbd7488d8f | |||
| cff3ca504d | |||
| 456ccf479a | |||
| 4f05a001fb | |||
| 43aa0497f4 | |||
| 1781c19c11 | |||
| 1ef3cb8307 | |||
| 5bc4dc684d | |||
| 4cff530b52 | |||
| 2f90a4fcf7 | |||
| 24fce1e093 |
@@ -191,6 +191,15 @@
|
||||
"testing_title": "测试"
|
||||
},
|
||||
"Store": {
|
||||
"download_progress_info": {
|
||||
"download_zip": "正在下载插件",
|
||||
"increment_count": "正在计入下载次数",
|
||||
"installing_plugin": "正在安装插件",
|
||||
"open_zip": "正在打开 ZIP 文件",
|
||||
"parse_zip": "正在解析 ZIP 文件",
|
||||
"start": "正在初始化",
|
||||
"uninstalling_previous": "正在卸载之前的版本"
|
||||
},
|
||||
"store_contrib": {
|
||||
"desc": "如果你想要提交你的插件到 Decky 插件商店,请访问 GitHub 上的 SteamDeckHomebrew/decky-plugin-template 存储库。有关开发和分发插件的信息,请查看 README 文件。",
|
||||
"label": "贡献"
|
||||
@@ -239,7 +248,11 @@
|
||||
}
|
||||
},
|
||||
"Testing": {
|
||||
"download": "下载"
|
||||
"download": "下载",
|
||||
"error": "安装 PR 时出错",
|
||||
"header": "以下版本的 Decky Loader 是根据开放的第三方 Pull Request 构建的。Decky Loader 团队尚未验证这些版本的功能或安全性,且它们可能已经过期。",
|
||||
"loading": "正在加载尚未合并的 Pull Request ...",
|
||||
"start_download_toast": "正在下载 PR #{{id}}"
|
||||
},
|
||||
"TitleView": {
|
||||
"decky_store_desc": "打开 Decky 商店",
|
||||
|
||||
@@ -9,7 +9,7 @@ from traceback import format_exc
|
||||
from stat import FILE_ATTRIBUTE_HIDDEN # pyright: ignore [reportAttributeAccessIssue, reportUnknownVariableType]
|
||||
|
||||
from asyncio import StreamReader, StreamWriter, start_server, gather, open_connection
|
||||
from aiohttp import ClientSession
|
||||
from aiohttp import ClientSession, hdrs
|
||||
from aiohttp.web import Request, StreamResponse, Response, json_response, post
|
||||
from typing import TYPE_CHECKING, Callable, Coroutine, Dict, Any, List, TypedDict
|
||||
|
||||
@@ -153,14 +153,14 @@ class Utilities:
|
||||
headers["User-Agent"] = helpers.user_agent
|
||||
|
||||
for excluded_header in excluded_default_headers:
|
||||
self.logger.debug(f"Excluding default header {excluded_header}")
|
||||
if excluded_header in headers:
|
||||
self.logger.debug(f"Excluding default header {excluded_header}: {headers[excluded_header]}")
|
||||
del headers[excluded_header]
|
||||
|
||||
if "X-Decky-Fetch-Excluded-Headers" in req.headers:
|
||||
for excluded_header in req.headers["X-Decky-Fetch-Excluded-Headers"].split(", "):
|
||||
self.logger.debug(f"Excluding header {excluded_header}")
|
||||
if excluded_header in headers:
|
||||
self.logger.debug(f"Excluding header {excluded_header}: {headers[excluded_header]}")
|
||||
del headers[excluded_header]
|
||||
|
||||
for header in req.headers:
|
||||
@@ -187,7 +187,21 @@ class Utilities:
|
||||
# defeat the point of this proxy.
|
||||
async with ClientSession(auto_decompress=False) as web:
|
||||
async with web.request(req.method, url, headers=headers, data=body, ssl=helpers.get_ssl_context()) as web_res:
|
||||
res = StreamResponse(headers=web_res.headers, status=web_res.status)
|
||||
# Whenever the aiohttp_cors is used, it expects a near complete control over whatever headers are needed
|
||||
# for `aiohttp_cors.ResourceOptions`. As a server, if you delegate CORS handling to aiohttp_cors,
|
||||
# the headers below must NOT be set. Otherwise they would be overwritten by aiohttp_cors and there would be
|
||||
# logic bugs, so it was probably a smart choice to assert if the headers are present.
|
||||
#
|
||||
# However, this request handler method does not act like our own local server, it always acts like a proxy
|
||||
# where we do not have control over the response headers. For responses that do not allow CORS, we add the support
|
||||
# via aiohttp_cors. For responses that allow CORS, we have to remove the conflicting headers to allow
|
||||
# aiohttp_cors handle it for us as if there was no CORS support.
|
||||
aiohttp_cors_compatible_headers = web_res.headers.copy()
|
||||
aiohttp_cors_compatible_headers.popall(hdrs.ACCESS_CONTROL_ALLOW_ORIGIN, default=None)
|
||||
aiohttp_cors_compatible_headers.popall(hdrs.ACCESS_CONTROL_ALLOW_CREDENTIALS, default=None)
|
||||
aiohttp_cors_compatible_headers.popall(hdrs.ACCESS_CONTROL_EXPOSE_HEADERS, default=None)
|
||||
|
||||
res = StreamResponse(headers=aiohttp_cors_compatible_headers, status=web_res.status)
|
||||
if web_res.headers.get('Transfer-Encoding', '').lower() == 'chunked':
|
||||
res.enable_chunked_encoding()
|
||||
|
||||
|
||||
@@ -47,7 +47,8 @@
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@decky/ui": "^4.7.1",
|
||||
"@decky/ui": "^4.8.1",
|
||||
"compare-versions": "^6.1.1",
|
||||
"filesize": "^10.1.2",
|
||||
"i18next": "^23.11.5",
|
||||
"i18next-http-backend": "^2.5.2",
|
||||
|
||||
Generated
+13
-5
@@ -9,8 +9,11 @@ importers:
|
||||
.:
|
||||
dependencies:
|
||||
'@decky/ui':
|
||||
specifier: ^4.7.1
|
||||
version: 4.7.1
|
||||
specifier: ^4.8.1
|
||||
version: 4.8.1
|
||||
compare-versions:
|
||||
specifier: ^6.1.1
|
||||
version: 6.1.1
|
||||
filesize:
|
||||
specifier: ^10.1.2
|
||||
version: 10.1.2
|
||||
@@ -215,8 +218,8 @@ packages:
|
||||
'@decky/api@1.1.1':
|
||||
resolution: {integrity: sha512-R5fkBRHBt5QIQY7Q0AlbVIhlIZ/nTzwBOoi8Rt4Go2fjFnoMKPInCJl6cPjXzimGwl2pyqKJgY6VnH6ar0XrHQ==}
|
||||
|
||||
'@decky/ui@4.7.1':
|
||||
resolution: {integrity: sha512-yJwBgW+J2cMDfMkmcDFtzsubhUjekFZAtCnP55QEJ/1UKGR7sLNOvDLFYi1h5PI0K4L1XYcAMKHwbYFFTzcDTA==}
|
||||
'@decky/ui@4.8.1':
|
||||
resolution: {integrity: sha512-lM4jdeyHjIbxHWxDBhbk+GQvdIT50p6RW9DC+oWSWXlaNWU/iG+8aUAcnfxygFkTP43EkCgjFASsYTfB55CMXA==}
|
||||
|
||||
'@esbuild/aix-ppc64@0.20.2':
|
||||
resolution: {integrity: sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g==}
|
||||
@@ -848,6 +851,9 @@ packages:
|
||||
commondir@1.0.1:
|
||||
resolution: {integrity: sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==}
|
||||
|
||||
compare-versions@6.1.1:
|
||||
resolution: {integrity: sha512-4hm4VPpIecmlg59CHXnRDnqGplJFrbLG4aFEl5vl6cK1u76ws3LLvX7ikFnTDl5vo39sjWD6AaDPYodJp/NNHg==}
|
||||
|
||||
concat-map@0.0.1:
|
||||
resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
|
||||
|
||||
@@ -2289,7 +2295,7 @@ snapshots:
|
||||
|
||||
'@decky/api@1.1.1': {}
|
||||
|
||||
'@decky/ui@4.7.1': {}
|
||||
'@decky/ui@4.8.1': {}
|
||||
|
||||
'@esbuild/aix-ppc64@0.20.2':
|
||||
optional: true
|
||||
@@ -2816,6 +2822,8 @@ snapshots:
|
||||
|
||||
commondir@1.0.1: {}
|
||||
|
||||
compare-versions@6.1.1: {}
|
||||
|
||||
concat-map@0.0.1: {}
|
||||
|
||||
convert-source-map@2.0.0: {}
|
||||
|
||||
@@ -2,11 +2,11 @@ import {
|
||||
DialogButton,
|
||||
DialogCheckbox,
|
||||
DialogCheckboxProps,
|
||||
Export,
|
||||
Marquee,
|
||||
Menu,
|
||||
MenuItem,
|
||||
findModuleExport,
|
||||
Module,
|
||||
findModule,
|
||||
showContextMenu,
|
||||
} from '@decky/ui';
|
||||
import { FC, useCallback, useEffect, useState } from 'react';
|
||||
@@ -14,9 +14,7 @@ import { useTranslation } from 'react-i18next';
|
||||
import { FaChevronDown } from 'react-icons/fa';
|
||||
|
||||
// TODO add to dfl
|
||||
const dropDownControlButtonClass = findModuleExport((e: Export) =>
|
||||
e?.toString()?.includes('gamepaddropdown_DropDownControlButton'),
|
||||
);
|
||||
const dropDownControlButtonClasses = findModule((m: Module) => m?.DropDownControlButton && m?.['duration-app-launch']);
|
||||
|
||||
const DropdownMultiselectItem: FC<
|
||||
{
|
||||
@@ -76,7 +74,7 @@ const DropdownMultiselect: FC<{
|
||||
alignItems: 'center',
|
||||
maxWidth: '100%',
|
||||
}}
|
||||
className={dropDownControlButtonClass}
|
||||
className={dropDownControlButtonClasses?.DropDownControlButton}
|
||||
onClick={(evt) => {
|
||||
evt.preventDefault();
|
||||
showContextMenu(
|
||||
|
||||
@@ -168,8 +168,9 @@ class PluginLoader extends Logger {
|
||||
|
||||
Promise.all([this.getUserInfo(), this.updateVersion()])
|
||||
.then(() => this.loadPlugins())
|
||||
.then(() => this.checkPluginUpdates())
|
||||
.then(() => this.log('Initialized'));
|
||||
.then(() => this.log('Initialized'))
|
||||
.then(() => sleep(30000)) // Internet might not immediately be up
|
||||
.then(() => this.checkPluginUpdates());
|
||||
}
|
||||
|
||||
private checkForSP(): boolean {
|
||||
|
||||
@@ -135,7 +135,7 @@ class RouterHook extends Logger {
|
||||
private async patchDesktopRouter() {
|
||||
const root = getReactRoot(document.getElementById('root') as any);
|
||||
const findRouterNode = () =>
|
||||
findInReactTree(root, (node) => node?.elementType?.type?.toString()?.includes('bShowDesktopUIContent:'));
|
||||
findInReactTree(root, (node) => node?.elementType?.type?.toString?.()?.includes('bShowDesktopUIContent:'));
|
||||
let routerNode = findRouterNode();
|
||||
while (!routerNode) {
|
||||
this.warn('Failed to find Router node, reattempting in 5 seconds.');
|
||||
|
||||
+11
-1
@@ -1,3 +1,5 @@
|
||||
import { compare } from 'compare-versions';
|
||||
|
||||
import { InstallType, Plugin, installPlugin, installPlugins } from './plugin';
|
||||
import { getSetting, setSetting } from './utils/settings';
|
||||
|
||||
@@ -137,7 +139,15 @@ export async function checkForPluginUpdates(plugins: Plugin[]): Promise<PluginUp
|
||||
const updateMap = new Map<string, StorePluginVersion>();
|
||||
for (let plugin of plugins) {
|
||||
const remotePlugin = serverData?.find((x) => x.name == plugin.name);
|
||||
if (remotePlugin && remotePlugin.versions?.length > 0 && plugin.version != remotePlugin?.versions?.[0]?.name) {
|
||||
//FIXME: Ugly hack since plugin.version might be null during evaluation,
|
||||
//so this will set the older version possible
|
||||
const curVer = plugin.version ? plugin.version : '0.0';
|
||||
if (
|
||||
remotePlugin &&
|
||||
remotePlugin.versions?.length > 0 &&
|
||||
plugin.version != remotePlugin?.versions?.[0]?.name &&
|
||||
compare(remotePlugin?.versions?.[0]?.name, curVer, '>')
|
||||
) {
|
||||
updateMap.set(plugin.name, remotePlugin.versions[0]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,9 +41,9 @@ class TabsHook extends Logger {
|
||||
|
||||
init() {
|
||||
// TODO patch the "embedded" renderer in this module too (seems to be for VR? unsure)
|
||||
const qamModule = findModuleByExport((e) => e?.type?.toString()?.includes('QuickAccessMenuBrowserView'));
|
||||
const qamModule = findModuleByExport((e) => e?.type?.toString?.()?.includes('QuickAccessMenuBrowserView'));
|
||||
const qamRenderer = Object.values(qamModule).find((e: any) =>
|
||||
e?.type?.toString()?.includes('QuickAccessMenuBrowserView'),
|
||||
e?.type?.toString?.()?.includes('QuickAccessMenuBrowserView'),
|
||||
);
|
||||
|
||||
const patchHandler = createReactTreePatcher(
|
||||
|
||||
@@ -28,7 +28,7 @@ class Toaster extends Logger {
|
||||
window.__TOASTER_INSTANCE?.deinit?.();
|
||||
window.__TOASTER_INSTANCE = this;
|
||||
|
||||
const ValveToastRenderer = findModuleExport((e) => e?.toString()?.includes(`controller:"notification",method:`));
|
||||
const ValveToastRenderer = findModuleExport((e) => e?.toString?.()?.includes(`controller:"notification",method:`));
|
||||
// TODO find a way to undo this if possible?
|
||||
const patchedRenderer = injectFCTrampoline(ValveToastRenderer);
|
||||
this.toastPatch = replacePatch(patchedRenderer, 'component', (args: any[]) => {
|
||||
|
||||
@@ -30,7 +30,7 @@ while :; do
|
||||
if [[ $NEWTARGET != "" ]] && [[ $NEWTARGET != $TARGET ]]; then
|
||||
echo found new tab at $NEWTARGET
|
||||
TARGET=$NEWTARGET
|
||||
TARGETURL="devtools://devtools/bundled/inspector.html?remoteFrontend=true&ws=$ADDR/devtools/page/$TARGET"
|
||||
TARGETURL="http://$ADDR/devtools/inspector.html?ws=$ADDR/devtools/page/$TARGET"
|
||||
|
||||
LOCALTARGET=$(echo '{"id": 1, "method": "Target.createTarget", "params": {"background": true, "url": "'$TARGETURL'"}}
|
||||
{"id": 2, "method": "Target.closeTarget", "params": {"targetId": "'$LOCALTARGET'"}}' \
|
||||
|
||||
Reference in New Issue
Block a user