Merge branch 'main' into aa/fix-updater-reload

This commit is contained in:
AAGaming
2025-07-01 14:49:48 -04:00
committed by GitHub
20 changed files with 1139 additions and 790 deletions
+2
View File
@@ -23,6 +23,7 @@ csrf_token = str(uuid.uuid4())
ssl_ctx = ssl.create_default_context(cafile=certifi.where()) ssl_ctx = ssl.create_default_context(cafile=certifi.where())
assets_regex = re.compile("^/plugins/.*/assets/.*") assets_regex = re.compile("^/plugins/.*/assets/.*")
data_regex = re.compile("^/plugins/.*/data/.*")
dist_regex = re.compile("^/plugins/.*/dist/.*") dist_regex = re.compile("^/plugins/.*/dist/.*")
frontend_regex = re.compile("^/frontend/.*") frontend_regex = re.compile("^/frontend/.*")
logger = getLogger("Main") logger = getLogger("Main")
@@ -45,6 +46,7 @@ async def csrf_middleware(request: Request, handler: Handler):
str(request.rel_url.path) == "/fetch" or \ str(request.rel_url.path) == "/fetch" or \
str(request.rel_url.path) == "/ws" or \ str(request.rel_url.path) == "/ws" or \
assets_regex.match(str(request.rel_url)) or \ assets_regex.match(str(request.rel_url)) or \
data_regex.match(str(request.rel_url)) or \
dist_regex.match(str(request.rel_url)) or \ dist_regex.match(str(request.rel_url)) or \
frontend_regex.match(str(request.rel_url)): frontend_regex.match(str(request.rel_url)):
+10 -1
View File
@@ -8,6 +8,7 @@ from typing import Any, Tuple, Dict, cast
from aiohttp import web from aiohttp import web
from os.path import exists from os.path import exists
from decky_loader.helpers import get_homebrew_path
from watchdog.events import RegexMatchingEventHandler, FileSystemEvent from watchdog.events import RegexMatchingEventHandler, FileSystemEvent
from watchdog.observers import Observer from watchdog.observers import Observer
@@ -91,6 +92,7 @@ class Loader:
web.get("/plugins/{plugin_name}/frontend_bundle", self.handle_frontend_bundle), web.get("/plugins/{plugin_name}/frontend_bundle", self.handle_frontend_bundle),
web.get("/plugins/{plugin_name}/dist/{path:.*}", self.handle_plugin_dist), web.get("/plugins/{plugin_name}/dist/{path:.*}", self.handle_plugin_dist),
web.get("/plugins/{plugin_name}/assets/{path:.*}", self.handle_plugin_frontend_assets), web.get("/plugins/{plugin_name}/assets/{path:.*}", self.handle_plugin_frontend_assets),
web.get("/plugins/{plugin_name}/data/{path:.*}", self.handle_plugin_frontend_assets_from_data),
]) ])
server_instance.ws.add_route("loader/get_plugins", self.get_plugins) server_instance.ws.add_route("loader/get_plugins", self.get_plugins)
@@ -142,6 +144,13 @@ class Loader:
return web.FileResponse(file, headers={"Cache-Control": "no-cache"}) return web.FileResponse(file, headers={"Cache-Control": "no-cache"})
async def handle_plugin_frontend_assets_from_data(self, request: web.Request):
plugin = self.plugins[request.match_info["plugin_name"]]
home = get_homebrew_path()
file = path.join(home, "data", plugin.plugin_directory, request.match_info["path"])
return web.FileResponse(file, headers={"Cache-Control": "no-cache"})
async def handle_frontend_bundle(self, request: web.Request): async def handle_frontend_bundle(self, request: web.Request):
plugin = self.plugins[request.match_info["plugin_name"]] plugin = self.plugins[request.match_info["plugin_name"]]
@@ -216,4 +225,4 @@ class Loader:
async def handle_plugin_backend_reload(self, plugin_name: str): async def handle_plugin_backend_reload(self, plugin_name: str):
plugin = self.plugins[plugin_name] plugin = self.plugins[plugin_name]
await self.reload_queue.put((plugin.file, plugin.plugin_directory)) await self.reload_queue.put((plugin.file, plugin.plugin_directory))
+54 -15
View File
@@ -2,7 +2,7 @@
"BranchSelect": { "BranchSelect": {
"update_channel": { "update_channel": {
"label": "Canal de mise à jour", "label": "Canal de mise à jour",
"prerelease": "Avant-première", "prerelease": "Préliminaire",
"stable": "Stable", "stable": "Stable",
"testing": "Test" "testing": "Test"
} }
@@ -52,21 +52,29 @@
"MultiplePluginsInstallModal": { "MultiplePluginsInstallModal": {
"confirm": "Êtes-vous sûr de vouloir apporter les modifications suivantes ?", "confirm": "Êtes-vous sûr de vouloir apporter les modifications suivantes ?",
"description": { "description": {
"downgrade": "Rétrograder {{name}} en {{version}}",
"install": "Installer {{name}} {{version}}", "install": "Installer {{name}} {{version}}",
"overwrite": "Écraser {{name}} avec {{version}}",
"reinstall": "Réinstaller {{name}} {{version}}", "reinstall": "Réinstaller {{name}} {{version}}",
"update": "Mettre à jour {{name}} à {{version}}" "update": "Mettre à jour {{name}} en {{version}}"
}, },
"ok_button": { "ok_button": {
"idle": "Confirmer", "idle": "Confirmer",
"loading": "En cours" "loading": "En cours"
}, },
"title": { "title": {
"downgrade_many": "Rétrograder {{count}} plugins",
"downgrade_one": "Rétrograder 1 plugin",
"downgrade_other": "Rétrograder {{count}} plugins",
"install_many": "Installer {{count}} plugins", "install_many": "Installer {{count}} plugins",
"install_one": "Installer 1 plugin", "install_one": "Installer 1 plugin",
"install_other": "Installer {{count}} plugins", "install_other": "Installer {{count}} plugins",
"mixed_many": "Modifier {{count}} plugins", "mixed_many": "Modifier {{count}} plugins",
"mixed_one": "Modifier {{count}} plugin", "mixed_one": "Modifier {{count}} plugin",
"mixed_other": "Modifier {{count}} plugins", "mixed_other": "Modifier {{count}} plugins",
"overwrite_many": "Écraser {{count}} plugins",
"overwrite_one": "Écraser 1 plugin",
"overwrite_other": "Écraser {{count}} plugins",
"reinstall_many": "Réinstaller {{count}} plugins", "reinstall_many": "Réinstaller {{count}} plugins",
"reinstall_one": "Réinstaller 1 plugin", "reinstall_one": "Réinstaller 1 plugin",
"reinstall_other": "Réinstaller {{count}} plugins", "reinstall_other": "Réinstaller {{count}} plugins",
@@ -76,12 +84,22 @@
} }
}, },
"PluginCard": { "PluginCard": {
"plugin_downgrade": "Rétrograder",
"plugin_full_access": "Ce plugin a un accès complet à votre Steam Deck.", "plugin_full_access": "Ce plugin a un accès complet à votre Steam Deck.",
"plugin_install": "Installer", "plugin_install": "Installer",
"plugin_no_desc": "Aucune description fournie.", "plugin_no_desc": "Aucune description fournie.",
"plugin_overwrite": "Écraser",
"plugin_reinstall": "Réinstaller",
"plugin_update": "Mettre à jour",
"plugin_version_label": "Version du plugin" "plugin_version_label": "Version du plugin"
}, },
"PluginInstallModal": { "PluginInstallModal": {
"downgrade": {
"button_idle": "Rétrograder",
"button_processing": "Rétrogradation",
"desc": "Êtes-vous sûr de vouloir rétrograder {{artifact}} vers la version {{version}} ?",
"title": "Rétrograder {{artifact}}"
},
"install": { "install": {
"button_idle": "Installer", "button_idle": "Installer",
"button_processing": "Installation en cours", "button_processing": "Installation en cours",
@@ -89,6 +107,13 @@
"title": "Installer {{artifact}}" "title": "Installer {{artifact}}"
}, },
"no_hash": "Ce plugin n'a pas de somme de contrôle, vous l'installez à vos risques et périls.", "no_hash": "Ce plugin n'a pas de somme de contrôle, vous l'installez à vos risques et périls.",
"not_installed": "(non installé)",
"overwrite": {
"button_idle": "Écraser",
"button_processing": "Écrasement",
"desc": "Êtes-vous sûr de vouloir remplacer {{artifact}} par la version {{version}} ?",
"title": "Écraser {{artifact}}"
},
"reinstall": { "reinstall": {
"button_idle": "Réinstaller", "button_idle": "Réinstaller",
"button_processing": "Réinstallation en cours", "button_processing": "Réinstallation en cours",
@@ -97,8 +122,8 @@
}, },
"update": { "update": {
"button_idle": "Mettre à jour", "button_idle": "Mettre à jour",
"button_processing": "Mise à jour", "button_processing": "Mise à jour en cours",
"desc": "Êtes-vous sûr de vouloir mettre à jour {{artifact}} {{version}}?", "desc": "Êtes-vous sûr de vouloir mettre à jour {{artifact}} vers la version {{version}}?",
"title": "Mettre à jour {{artifact}}" "title": "Mettre à jour {{artifact}}"
} }
}, },
@@ -124,7 +149,7 @@
"decky_title": "Decky", "decky_title": "Decky",
"decky_update_available": "Mise à jour vers {{tag_name}} disponible !", "decky_update_available": "Mise à jour vers {{tag_name}} disponible !",
"error": "Erreur", "error": "Erreur",
"plugin_error_uninstall": "Allez sur {{name}} dans le menu de Decky si vous voulez désinstaller ce plugin.", "plugin_error_uninstall": "Le chargement de {{name}} a provoqué une exception comme indiqué ci-dessus. Cela signifie généralement que le plugin nécessite une mise à jour pour la nouvelle version de SteamUI. Vérifiez si une mise à jour est présente ou évaluez sa suppression dans les paramètres de Decky, dans la section Plugins.",
"plugin_load_error": { "plugin_load_error": {
"message": "Erreur lors du chargement du plugin {{name}}", "message": "Erreur lors du chargement du plugin {{name}}",
"toast": "Erreur lors du chargement de {{name}}" "toast": "Erreur lors du chargement de {{name}}"
@@ -153,7 +178,7 @@
"cef_console": { "cef_console": {
"button": "Ouvrir la console", "button": "Ouvrir la console",
"desc": "Ouvre la console CEF. Utile uniquement à des fins de débogage. Les éléments présentés ici sont potentiellement dangereux et ne doivent être utilisés que si vous êtes un développeur de plugins ou si vous êtes dirigé ici par un de ces développeurs.", "desc": "Ouvre la console CEF. Utile uniquement à des fins de débogage. Les éléments présentés ici sont potentiellement dangereux et ne doivent être utilisés que si vous êtes un développeur de plugins ou si vous êtes dirigé ici par un de ces développeurs.",
"label": "CEF Console" "label": "Console CEF"
}, },
"header": "Autre", "header": "Autre",
"react_devtools": { "react_devtools": {
@@ -171,7 +196,7 @@
}, },
"valve_internal": { "valve_internal": {
"desc1": "Active le menu développeur interne de Valve.", "desc1": "Active le menu développeur interne de Valve.",
"desc2": "Ne touchez à rien dans ce menu à moins que vous ne sachiez ce qu'il fait.", "desc2": "Ne touchez à rien dans ce menu à moins que vous ne sachiez ce que ça fait.",
"label": "Activer Valve Internal" "label": "Activer Valve Internal"
} }
}, },
@@ -187,9 +212,9 @@
"label": "Mode développeur" "label": "Mode développeur"
}, },
"notifications": { "notifications": {
"decky_updates_label": "Mise à jour Decky disponible", "decky_updates_label": "Mise à jour de Decky disponible",
"header": "Notifications", "header": "Notifications",
"plugin_updates_label": "Mises à jour du plugin disponibles" "plugin_updates_label": "Mises à jour des plugins disponibles"
}, },
"other": { "other": {
"header": "Autre" "header": "Autre"
@@ -202,9 +227,19 @@
"developer_title": "Développeur", "developer_title": "Développeur",
"general_title": "Général", "general_title": "Général",
"plugins_title": "Plugins", "plugins_title": "Plugins",
"testing_title": "Essai" "testing_title": "Expérimentations"
}, },
"Store": { "Store": {
"download_progress_info": {
"download_remote": "Téléchargement des binaires externes",
"download_zip": "Téléchargement du plugin",
"increment_count": "Incrémentation du nombre de téléchargements",
"installing_plugin": "Installation du plugin",
"open_zip": "Ouverture du fichier zip",
"parse_zip": "Analyse du fichier zip",
"start": "Initialisation",
"uninstalling_previous": "Désinstallation de la copie précédente"
},
"store_contrib": { "store_contrib": {
"desc": "Si vous souhaitez contribuer au Decky Plugin Store, consultez le dépôt SteamDeckHomebrew/decky-plugin-template sur GitHub. Des informations sur le développement et la distribution sont disponibles dans le fichier README.", "desc": "Si vous souhaitez contribuer au Decky Plugin Store, consultez le dépôt SteamDeckHomebrew/decky-plugin-template sur GitHub. Des informations sur le développement et la distribution sont disponibles dans le fichier README.",
"label": "Contributions" "label": "Contributions"
@@ -237,23 +272,27 @@
"store_testing_cta": "Pensez à tester de nouveaux plugins pour aider l'équipe Decky Loader !", "store_testing_cta": "Pensez à tester de nouveaux plugins pour aider l'équipe Decky Loader !",
"store_testing_warning": { "store_testing_warning": {
"desc": "Vous pouvez utiliser cette chaîne de magasin pour tester des versions de plugins. Assurez-vous de laisser des commentaires sur GitHub afin que le plugin puisse être mis à jour pour tous les utilisateurs.", "desc": "Vous pouvez utiliser cette chaîne de magasin pour tester des versions de plugins. Assurez-vous de laisser des commentaires sur GitHub afin que le plugin puisse être mis à jour pour tous les utilisateurs.",
"label": "Bienvenue sur la chaîne du magasin de tests" "label": "Bienvenue sur le canal test de la boutique"
} }
}, },
"StoreSelect": { "StoreSelect": {
"custom_store": { "custom_store": {
"label": "Plugin Store personnalisé", "label": "Magasin personnalisé",
"url_label": "URL" "url_label": "URL"
}, },
"store_channel": { "store_channel": {
"custom": "Personnalisé", "custom": "Personnalisé",
"default": "Par défaut", "default": "Par défaut",
"label": "Canal du Plugin Store", "label": "Canal magasin",
"testing": "Test" "testing": "Test"
} }
}, },
"Testing": { "Testing": {
"download": "Télécharger" "download": "Télécharger",
"error": "Erreur d'installation de la PR",
"header": "Les versions suivantes de Decky Loader sont construites à partir de Pull Requests ouvertes par des tiers. L'équipe de Decky Loader n'a pas vérifié leur fonctionnalité ou leur sécurité, et elles peuvent être obsolètes.",
"loading": "Chargement des Pull Requests ouvertes...",
"start_download_toast": "Téléchargement de la PR #{{id}}"
}, },
"TitleView": { "TitleView": {
"decky_store_desc": "Ouvrir le magasin Decky", "decky_store_desc": "Ouvrir le magasin Decky",
@@ -264,7 +303,7 @@
"no_patch_notes_desc": "pas de notes de mise à jour pour cette version", "no_patch_notes_desc": "pas de notes de mise à jour pour cette version",
"patch_notes_desc": "Notes de mise à jour", "patch_notes_desc": "Notes de mise à jour",
"updates": { "updates": {
"check_button": "Chercher les mises à jour", "check_button": "Vérifier les mises à jour",
"checking": "Recherche", "checking": "Recherche",
"cur_version": "Version actuelle: {{ver}}", "cur_version": "Version actuelle: {{ver}}",
"install_button": "Installer la mise à jour", "install_button": "Installer la mise à jour",
+23 -1
View File
@@ -52,7 +52,9 @@
"MultiplePluginsInstallModal": { "MultiplePluginsInstallModal": {
"confirm": "以下の変更を加えてもよろしいですか?", "confirm": "以下の変更を加えてもよろしいですか?",
"description": { "description": {
"downgrade": "ダウングレード {{name}} {{version}}",
"install": "インストール {{name}} {{version}}", "install": "インストール {{name}} {{version}}",
"overwrite": "上書き {{name}} {{version}}",
"reinstall": "再インストール {{name}} {{version}}", "reinstall": "再インストール {{name}} {{version}}",
"update": "アップデート {{name}} {{version}}" "update": "アップデート {{name}} {{version}}"
}, },
@@ -61,19 +63,31 @@
"loading": "作業中" "loading": "作業中"
}, },
"title": { "title": {
"downgrade_other": "{{count}} 個のプラグインをダウングレード",
"install_other": "{{count}} 個のプラグインをインストール", "install_other": "{{count}} 個のプラグインをインストール",
"mixed_other": "{{count}} 個のプラグインを修正", "mixed_other": "{{count}} 個のプラグインを修正",
"overwrite_other": "{{count}} 個のプラグインを上書き",
"reinstall_other": "{{count}} 個のプラグインを再インストール", "reinstall_other": "{{count}} 個のプラグインを再インストール",
"update_other": "{{count}} 個のプラグインをアップデート" "update_other": "{{count}} 個のプラグインをアップデート"
} }
}, },
"PluginCard": { "PluginCard": {
"plugin_downgrade": "ダウングレード",
"plugin_full_access": "このプラグインはSteam Deckの全てのアクセス権を持ちます。", "plugin_full_access": "このプラグインはSteam Deckの全てのアクセス権を持ちます。",
"plugin_install": "インストール", "plugin_install": "インストール",
"plugin_no_desc": "説明はありません。", "plugin_no_desc": "説明はありません。",
"plugin_overwrite": "上書き",
"plugin_reinstall": "再インストール",
"plugin_update": "アップデート",
"plugin_version_label": "プラグインバージョン" "plugin_version_label": "プラグインバージョン"
}, },
"PluginInstallModal": { "PluginInstallModal": {
"downgrade": {
"button_idle": "ダウングレード",
"button_processing": "ダウングレード中",
"desc": "{{artifact}}をVer {{version}} にダウングレードしてもよろしいですか?",
"title": "{{artifact}}をダウングレード"
},
"install": { "install": {
"button_idle": "インストール", "button_idle": "インストール",
"button_processing": "インストール中", "button_processing": "インストール中",
@@ -81,6 +95,13 @@
"title": "{{artifact}} をインストール" "title": "{{artifact}} をインストール"
}, },
"no_hash": "このプラグインにはハッシュがありません。ご自身の責任でインストールしてください。", "no_hash": "このプラグインにはハッシュがありません。ご自身の責任でインストールしてください。",
"not_installed": "(インストールされていません)",
"overwrite": {
"button_idle": "上書き",
"button_processing": "上書き中",
"desc": "{{artifact}}をVer {{version}} に上書きしてもよろしいですか?",
"title": "{{artifact}}を上書き"
},
"reinstall": { "reinstall": {
"button_idle": "再インストール", "button_idle": "再インストール",
"button_processing": "再インストール中", "button_processing": "再インストール中",
@@ -90,7 +111,7 @@
"update": { "update": {
"button_idle": "アップデート", "button_idle": "アップデート",
"button_processing": "アップデート中", "button_processing": "アップデート中",
"desc": "{{artifact}} {{version}} アップデートしてもよろしいですか?", "desc": "{{artifact}}をVer {{version}} アップデートしてもよろしいですか?",
"title": "{{artifact}} をアップデート" "title": "{{artifact}} をアップデート"
} }
}, },
@@ -192,6 +213,7 @@
}, },
"Store": { "Store": {
"download_progress_info": { "download_progress_info": {
"download_remote": "外部バイナリのダウンロード",
"download_zip": "プラグインのダウンロード中", "download_zip": "プラグインのダウンロード中",
"increment_count": "ダウンロード数の増加", "increment_count": "ダウンロード数の増加",
"installing_plugin": "プラグインのインストール中", "installing_plugin": "プラグインのインストール中",
+15 -1
View File
@@ -52,7 +52,9 @@
"MultiplePluginsInstallModal": { "MultiplePluginsInstallModal": {
"confirm": "Вы уверены, что хотите внести следующие изменения?", "confirm": "Вы уверены, что хотите внести следующие изменения?",
"description": { "description": {
"downgrade": "Откатить {{name}} до {{version}}",
"install": "Установить {{name}} {{version}}", "install": "Установить {{name}} {{version}}",
"overwrite": "Заменить {{name}} на {{version}}",
"reinstall": "Переустановить {{name}} {{version}}", "reinstall": "Переустановить {{name}} {{version}}",
"update": "Обновить с {{name}} на {{version}}" "update": "Обновить с {{name}} на {{version}}"
}, },
@@ -61,6 +63,9 @@
"loading": "В процессе" "loading": "В процессе"
}, },
"title": { "title": {
"downgrade_few": "Откатить {{count}} плагинов",
"downgrade_many": "Откатить {{count}} плагинов",
"downgrade_one": "Откатить 1 плагин",
"install_few": "Установить {{count}} плагинов", "install_few": "Установить {{count}} плагинов",
"install_many": "Установить {{count}} плагинов", "install_many": "Установить {{count}} плагинов",
"install_one": "Установить {{count}} плагин", "install_one": "Установить {{count}} плагин",
@@ -76,12 +81,21 @@
} }
}, },
"PluginCard": { "PluginCard": {
"plugin_downgrade": "Откат",
"plugin_full_access": "Этот плагин имеет полный доступ к вашему Steam Deck.", "plugin_full_access": "Этот плагин имеет полный доступ к вашему Steam Deck.",
"plugin_install": "Установить", "plugin_install": "Установить",
"plugin_no_desc": "Нет описания.", "plugin_no_desc": "Нет описания.",
"plugin_overwrite": "Замена",
"plugin_reinstall": "Переустановка",
"plugin_update": "Обновление",
"plugin_version_label": "Версия плагина" "plugin_version_label": "Версия плагина"
}, },
"PluginInstallModal": { "PluginInstallModal": {
"downgrade": {
"button_idle": "Откат",
"desc": "Вы уверенны, что хотите откатить {{artifact}} до версии {{version}}?",
"title": "Откатить {{artifact}}"
},
"install": { "install": {
"button_idle": "Установить", "button_idle": "Установить",
"button_processing": "Установка", "button_processing": "Установка",
@@ -98,7 +112,7 @@
"update": { "update": {
"button_idle": "Обновить", "button_idle": "Обновить",
"button_processing": "Обновление", "button_processing": "Обновление",
"desc": "Вы уверены, что хотите обновить {{artifact}} {{version}}?", "desc": "Вы уверены, что хотите обновить {{artifact}} до версии {{version}}?",
"title": "Обновить {{artifact}}" "title": "Обновить {{artifact}}"
} }
}, },
+751 -732
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -14,7 +14,7 @@ include = [
] ]
[tool.poetry.dependencies] [tool.poetry.dependencies]
python = ">=3.10,<3.14" python = ">=3.10,<3.13"
aiohttp = "^3.10.11" aiohttp = "^3.10.11"
aiohttp-jinja2 = "^1.5.1" aiohttp-jinja2 = "^1.5.1"
+1 -1
View File
@@ -7,4 +7,4 @@ export default {
tabWidth: 2, tabWidth: 2,
endOfLine: 'auto', endOfLine: 'auto',
plugins: [importSort], plugins: [importSort],
} };
+1 -1
View File
@@ -47,7 +47,7 @@
} }
}, },
"dependencies": { "dependencies": {
"@decky/ui": "^4.8.3", "@decky/ui": "^4.10.2",
"compare-versions": "^6.1.1", "compare-versions": "^6.1.1",
"filesize": "^10.1.2", "filesize": "^10.1.2",
"i18next": "^23.11.5", "i18next": "^23.11.5",
+5 -5
View File
@@ -9,8 +9,8 @@ importers:
.: .:
dependencies: dependencies:
'@decky/ui': '@decky/ui':
specifier: ^4.8.3 specifier: ^4.10.2
version: 4.8.3 version: 4.10.2
compare-versions: compare-versions:
specifier: ^6.1.1 specifier: ^6.1.1
version: 6.1.1 version: 6.1.1
@@ -218,8 +218,8 @@ packages:
'@decky/api@1.1.1': '@decky/api@1.1.1':
resolution: {integrity: sha512-R5fkBRHBt5QIQY7Q0AlbVIhlIZ/nTzwBOoi8Rt4Go2fjFnoMKPInCJl6cPjXzimGwl2pyqKJgY6VnH6ar0XrHQ==} resolution: {integrity: sha512-R5fkBRHBt5QIQY7Q0AlbVIhlIZ/nTzwBOoi8Rt4Go2fjFnoMKPInCJl6cPjXzimGwl2pyqKJgY6VnH6ar0XrHQ==}
'@decky/ui@4.8.3': '@decky/ui@4.10.2':
resolution: {integrity: sha512-Y1KciazgvKqMEVBGrWFCTGOqgVi5sHbcQNoCZRMbPpcI0U3j7udl6mkfe/NBa16oRDZ03ljS41SmrAgKAAt/pA==} resolution: {integrity: sha512-dfY/OEI/rhG4d3Tvx4Y3TLmBrJ+nAm2RTbkoOJ3VAglql3Lu7RY2ixeDFbs21ZWWzWh/C+9dAQKjQ4lx4d1f2g==}
'@esbuild/aix-ppc64@0.20.2': '@esbuild/aix-ppc64@0.20.2':
resolution: {integrity: sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g==} resolution: {integrity: sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g==}
@@ -2295,7 +2295,7 @@ snapshots:
'@decky/api@1.1.1': {} '@decky/api@1.1.1': {}
'@decky/ui@4.8.3': {} '@decky/ui@4.10.2': {}
'@esbuild/aix-ppc64@0.20.2': '@esbuild/aix-ppc64@0.20.2':
optional: true optional: true
@@ -4,6 +4,8 @@ import { FunctionComponent, useEffect, useReducer, useState } from 'react';
import { uninstallPlugin } from '../plugin'; import { uninstallPlugin } from '../plugin';
import { VerInfo, doRestart, doShutdown } from '../updater'; import { VerInfo, doRestart, doShutdown } from '../updater';
import { ValveReactErrorInfo, getLikelyErrorSourceFromValveReactError } from '../utils/errors'; import { ValveReactErrorInfo, getLikelyErrorSourceFromValveReactError } from '../utils/errors';
import { useSetting } from '../utils/hooks/useSetting';
import { UpdateBranch } from './settings/pages/general/BranchSelect';
interface DeckyErrorBoundaryProps { interface DeckyErrorBoundaryProps {
error: ValveReactErrorInfo; error: ValveReactErrorInfo;
@@ -37,6 +39,27 @@ const DeckyErrorBoundary: FunctionComponent<DeckyErrorBoundaryProps> = ({ error,
if (!shouldReportToValve) DeckyPluginLoader.errorBoundaryHook.temporarilyDisableReporting(); if (!shouldReportToValve) DeckyPluginLoader.errorBoundaryHook.temporarilyDisableReporting();
DeckyPluginLoader.updateVersion().then(setVersionInfo); DeckyPluginLoader.updateVersion().then(setVersionInfo);
}, []); }, []);
const [selectedBranch, setSelectedBranch] = useSetting<UpdateBranch>('branch', UpdateBranch.Stable);
const [isChecking, setIsChecking] = useState<boolean>(false);
const [updateProgress, setUpdateProgress] = useState<number>(-1);
const [versionToUpdateTo, setSetVersionToUpdateTo] = useState<string>('');
useEffect(() => {
const a = DeckyBackend.addEventListener('updater/update_download_percentage', (percentage) => {
setUpdateProgress(percentage);
});
const b = DeckyBackend.addEventListener('updater/finish_download', () => {
setUpdateProgress(-2);
});
return () => {
DeckyBackend.removeEventListener('updater/update_download_percentage', a);
DeckyBackend.removeEventListener('updater/finish_download', b);
};
}, []);
return ( return (
<> <>
<style> <style>
@@ -149,6 +172,65 @@ const DeckyErrorBoundary: FunctionComponent<DeckyErrorBoundaryProps> = ({ error,
</button> </button>
</div> </div>
)} )}
{
<div style={{ display: 'block', marginBottom: '5px' }}>
{updateProgress > -1
? 'Update in progress... ' + updateProgress + '%'
: updateProgress == -2
? 'Update complete. Restarting...'
: 'Changing your Decky Loader branch and/or \n checking for updates might help!\n'}
{updateProgress == -1 && (
<div style={{ height: '30px' }}>
<select
style={{ height: '100%' }}
onChange={async (e) => {
const branch = parseInt(e.target.value);
setSelectedBranch(branch);
setSetVersionToUpdateTo('');
}}
>
<option value="0" selected={selectedBranch == UpdateBranch.Stable}>
Stable
</option>
<option value="1" selected={selectedBranch == UpdateBranch.Prerelease}>
Pre-Release
</option>
<option value="2" selected={selectedBranch == UpdateBranch.Testing}>
Testing
</option>
</select>
<button
style={{ height: '100%' }}
disabled={updateProgress != -1 || isChecking}
onClick={async () => {
if (versionToUpdateTo == '') {
setIsChecking(true);
const versionInfo = (await DeckyBackend.callable(
'updater/check_for_updates',
)()) as unknown as VerInfo;
setIsChecking(false);
if (versionInfo?.remote && versionInfo?.remote?.tag_name != versionInfo?.current) {
setSetVersionToUpdateTo(versionInfo.remote.tag_name);
} else {
setSetVersionToUpdateTo('');
}
} else {
DeckyBackend.callable('updater/do_update')();
setUpdateProgress(0);
}
}}
>
{' '}
{isChecking
? 'Checking for updates...'
: versionToUpdateTo != ''
? 'Update to ' + versionToUpdateTo
: 'Check for updates'}
</button>
</div>
)}
</div>
}
{wasCausedByPlugin && ( {wasCausedByPlugin && (
<div style={{ display: 'block', marginBottom: '5px' }}> <div style={{ display: 'block', marginBottom: '5px' }}>
{'\n'} {'\n'}
+7 -2
View File
@@ -1,4 +1,4 @@
import { Focusable, Navigation } from '@decky/ui'; import { Focusable, Navigation, findClass, findClassByName } from '@decky/ui';
import { FunctionComponent, useRef } from 'react'; import { FunctionComponent, useRef } from 'react';
import ReactMarkdown, { Options as ReactMarkdownOptions } from 'react-markdown'; import ReactMarkdown, { Options as ReactMarkdownOptions } from 'react-markdown';
import remarkGfm from 'remark-gfm'; import remarkGfm from 'remark-gfm';
@@ -8,6 +8,9 @@ interface MarkdownProps extends ReactMarkdownOptions {
} }
const Markdown: FunctionComponent<MarkdownProps> = (props) => { const Markdown: FunctionComponent<MarkdownProps> = (props) => {
const eventDetailsBodyClassName = findClassByName('EventDetailsBody') || undefined;
const eventLinkClassName = findClass('43088', 'Link');
return ( return (
<Focusable> <Focusable>
<ReactMarkdown <ReactMarkdown
@@ -25,8 +28,10 @@ const Markdown: FunctionComponent<MarkdownProps> = (props) => {
Navigation.NavigateToExternalWeb(aRef.current!.href); Navigation.NavigateToExternalWeb(aRef.current!.href);
}} }}
style={{ display: 'inline' }} style={{ display: 'inline' }}
focusClassName="steam-focus"
className={eventDetailsBodyClassName}
> >
<a ref={aRef} {...nodeProps.node.properties}> <a ref={aRef} {...nodeProps.node.properties} className={eventLinkClassName}>
{nodeProps.children} {nodeProps.children}
</a> </a>
</Focusable> </Focusable>
@@ -47,7 +47,7 @@ export default async function libraryPatch() {
} }
const unlisten = History.listen(() => { const unlisten = History.listen(() => {
if (window.SteamClient.Apps.PromptToChangeShortcut !== patch.patchedFunction) { if ((window.SteamClient.Apps as any).PromptToChangeShortcut !== patch.patchedFunction) {
rePatch(); rePatch();
} }
}); });
@@ -72,7 +72,16 @@ export default function DeveloperSettings() {
} }
icon={<FaLink style={{ display: 'block' }} />} icon={<FaLink style={{ display: 'block' }} />}
> >
<DialogButton disabled={pluginURL.length == 0} onClick={() => installFromURL(pluginURL)}> <DialogButton
disabled={pluginURL.length == 0}
onClick={() => {
if (/^https?:\/\//.test(pluginURL)) {
installFromURL(pluginURL);
} else {
installFromURL('https://' + pluginURL);
}
}}
>
{t('SettingsDeveloperIndex.third_party_plugins.button_install')} {t('SettingsDeveloperIndex.third_party_plugins.button_install')}
</DialogButton> </DialogButton>
</Field> </Field>
@@ -1,14 +1,4 @@
import { import { Carousel, DialogButton, Field, Focusable, ProgressBarWithInfo, Spinner, findSP, showModal } from '@decky/ui';
Carousel,
DialogButton,
Field,
FocusRing,
Focusable,
ProgressBarWithInfo,
Spinner,
findSP,
showModal,
} from '@decky/ui';
import { Suspense, lazy, useCallback, useEffect, useState } from 'react'; import { Suspense, lazy, useCallback, useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { FaExclamation } from 'react-icons/fa'; import { FaExclamation } from 'react-icons/fa';
@@ -23,9 +13,31 @@ const MarkdownRenderer = lazy(() => import('../../../Markdown'));
function PatchNotesModal({ versionInfo, closeModal }: { versionInfo: VerInfo | null; closeModal?: () => {} }) { function PatchNotesModal({ versionInfo, closeModal }: { versionInfo: VerInfo | null; closeModal?: () => {} }) {
const SP = findSP(); const SP = findSP();
const { t } = useTranslation(); const { t } = useTranslation();
return ( return (
<Focusable onCancelButton={closeModal}> <>
<FocusRing> <style>
{`
.steam-focus {
outline-offset: 3px;
outline: 2px solid rgba(255, 255, 255, 0.6);
animation: pulseOutline 1.2s infinite ease-in-out;
}
@keyframes pulseOutline {
0% {
outline: 2px solid rgba(255, 255, 255, 0.6);
}
50% {
outline: 2px solid rgba(255, 255, 255, 1);
}
100% {
outline: 2px solid rgba(255, 255, 255, 0.6);
}
}`}
</style>
<Focusable onCancelButton={closeModal}>
<Carousel <Carousel
fnItemRenderer={(id: number) => ( fnItemRenderer={(id: number) => (
<Focusable <Focusable
@@ -35,7 +47,9 @@ function PatchNotesModal({ versionInfo, closeModal }: { versionInfo: VerInfo | n
overflowY: 'scroll', overflowY: 'scroll',
display: 'flex', display: 'flex',
justifyContent: 'center', justifyContent: 'center',
margin: '40px', margin: '30px',
padding: '0 15px',
backgroundColor: 'rgba(37, 40, 46, 0.5)',
}} }}
> >
<div> <div>
@@ -57,11 +71,11 @@ function PatchNotesModal({ versionInfo, closeModal }: { versionInfo: VerInfo | n
nItemMarginX={0} nItemMarginX={0}
initialColumn={0} initialColumn={0}
autoFocus={true} autoFocus={true}
fnGetColumnWidth={() => SP.innerWidth} fnGetColumnWidth={() => SP.innerWidth - SP.innerWidth * (10 / 100)}
name={t('Updater.decky_updates') as string} name={t('Updater.decky_updates') as string}
/> />
</FocusRing> </Focusable>
</Focusable> </>
); );
} }
+4 -3
View File
@@ -1,5 +1,6 @@
import { ToastNotification } from '@decky/api'; import { ToastNotification } from '@decky/api';
import { import {
EUIMode,
ModalRoot, ModalRoot,
Navigation, Navigation,
PanelSection, PanelSection,
@@ -30,7 +31,7 @@ import { HiddenPluginsService } from './hidden-plugins-service';
import Logger from './logger'; import Logger from './logger';
import { NotificationService } from './notification-service'; import { NotificationService } from './notification-service';
import { InstallType, Plugin, PluginLoadType } from './plugin'; import { InstallType, Plugin, PluginLoadType } from './plugin';
import RouterHook, { UIMode } from './router-hook'; import RouterHook from './router-hook';
import { deinitSteamFixes, initSteamFixes } from './steamfixes'; import { deinitSteamFixes, initSteamFixes } from './steamfixes';
import { checkForPluginUpdates } from './store'; import { checkForPluginUpdates } from './store';
import TabsHook from './tabs-hook'; import TabsHook from './tabs-hook';
@@ -205,12 +206,12 @@ class PluginLoader extends Logger {
let registration: any; let registration: any;
const uiMode = await new Promise( const uiMode = await new Promise(
(r) => (r) =>
(registration = SteamClient.UI.RegisterForUIModeChanged((mode: UIMode) => { (registration = SteamClient.UI.RegisterForUIModeChanged((mode: EUIMode) => {
r(mode); r(mode);
registration.unregister(); registration.unregister();
})), })),
); );
if (uiMode == UIMode.BigPicture) { if (uiMode == EUIMode.GamePad) {
// wait for SP window to exist before loading plugins // wait for SP window to exist before loading plugins
while (!findSP()) { while (!findSP()) {
await sleep(100); await sleep(100);
+3 -7
View File
@@ -1,4 +1,5 @@
import { import {
EUIMode,
ErrorBoundary, ErrorBoundary,
Patch, Patch,
afterPatch, afterPatch,
@@ -31,11 +32,6 @@ declare global {
} }
} }
export enum UIMode {
BigPicture = 4,
Desktop = 7,
}
const isPatched = Symbol('is patched'); const isPatched = Symbol('is patched');
class RouterHook extends Logger { class RouterHook extends Logger {
@@ -76,13 +72,13 @@ class RouterHook extends Logger {
this.error('Failed to find router stack module'); this.error('Failed to find router stack module');
} }
this.modeChangeRegistration = SteamClient.UI.RegisterForUIModeChanged((mode: UIMode) => { this.modeChangeRegistration = SteamClient.UI.RegisterForUIModeChanged((mode: EUIMode) => {
this.debug(`UI mode changed to ${mode}`); this.debug(`UI mode changed to ${mode}`);
if (this.patchedModes.has(mode)) return; if (this.patchedModes.has(mode)) return;
this.patchedModes.add(mode); this.patchedModes.add(mode);
this.debug(`Patching router for UI mode ${mode}`); this.debug(`Patching router for UI mode ${mode}`);
switch (mode) { switch (mode) {
case UIMode.BigPicture: case EUIMode.GamePad:
this.debug('Patching gamepad router'); this.debug('Patching gamepad router');
this.patchGamepadRouter(); this.patchGamepadRouter();
break; break;
Regular → Executable
+18 -1
View File
@@ -2,6 +2,13 @@
# Usage: deckdebug.sh DECKIP:8081 # Usage: deckdebug.sh DECKIP:8081
# Dependencies: websocat jq curl chromium # Dependencies: websocat jq curl chromium
if [ "$#" -ne 1 ]; then
echo "Error: Missing or incorrect argument." >&2
echo "Usage: deckdebug.sh DECKIP:8081" >&2
exit 1
fi
# https://jackson.dev/post/a-portable-nix-shell-shebang/ # https://jackson.dev/post/a-portable-nix-shell-shebang/
if [ -z "$INSIDE_NIX_RANDOMSTRING" ] && command -v nix &> /dev/null; then if [ -z "$INSIDE_NIX_RANDOMSTRING" ] && command -v nix &> /dev/null; then
# If the user has nix, relaunch in nix shell with dependencies added # If the user has nix, relaunch in nix shell with dependencies added
@@ -13,6 +20,16 @@ if [ -z "$INSIDE_NIX_RANDOMSTRING" ] && command -v nix &> /dev/null; then
exit $? exit $?
fi fi
required_dependencies=(websocat jq curl chromium)
# Check if the dependencies are installed
for cmd in "${required_dependencies[@]}"; do
if ! command -v "$cmd" &> /dev/null; then
echo "Error: '$cmd' is not installed. Please install it and try again." >&2
exit 1
fi
done
chromium --remote-debugging-port=9222 & chromium --remote-debugging-port=9222 &
sleep 2 sleep 2
@@ -41,4 +58,4 @@ while :; do
fi fi
sleep 5 sleep 5
done done
Regular → Executable
View File
+120
View File
@@ -0,0 +1,120 @@
#!/usr/bin/env bash
# ./script/task.sh: Run a VSCode task from tasks.json including its dependencies.
#
# Usage: ./scripts/task.sh TASK_LABEL
#
# This script looks for .vscode/tasks.json in your workspace folder (or current directory)
# and executes the command associated with the given task label.
#
# It also handles the "dependsOn" field recursively.
#
# Requirements: jq sed
# https://jackson.dev/post/a-portable-nix-shell-shebang/
if [ -z "$INSIDE_NIX_RANDOMSTRING" ] && command -v nix &> /dev/null; then
# If the user has nix, relaunch in nix shell with dependencies added
INSIDE_NIX_RANDOMSTRING=1 nix shell \
nixpkgs#jq \
nixpkgs#gnused \
--command "$0" "$@"
exit $?
fi
required_dependencies=(jq sed)
# Check if the dependencies are installed
for cmd in "${required_dependencies[@]}"; do
if ! command -v "$cmd" &> /dev/null; then
echo "Error: '$cmd' is not installed. Please install it and try again." >&2
exit 1
fi
done
set -euo pipefail
# Use WORKSPACE_FOLDER if set; otherwise, assume current directory.
WORKSPACE_FOLDER="${WORKSPACE_FOLDER:-$(pwd)}"
TASKS_FILE="$WORKSPACE_FOLDER/.vscode/tasks.json"
if [ ! -f "$TASKS_FILE" ]; then
echo "Error: tasks.json not found at $TASKS_FILE" >&2
exit 1
fi
if [ $# -lt 1 ]; then
echo "Usage: $0 TASK_LABEL" >&2
exit 1
fi
# Remove comment lines (lines starting with //) from the tasks file to be compliant with the JSON format.
TASKS_JSON=$(sed '/^[[:space:]]*\/\//d' "$TASKS_FILE")
TASK_LABEL="$1"
shift
# run_task recursively looks up the task by label,
# runs any dependencies first, then executes its command.
run_task() {
local label="$1"
echo "Looking up task: $label"
# Get the task object from the cleaned JSON.
local task
task=$(echo "$TASKS_JSON" | jq --arg label "$label" -r '.tasks[] | select(.label == $label)')
if [ -z "$task" ]; then
echo "Error: Task with label '$label' not found in $TASKS_FILE" >&2
exit 1
fi
# If the task has dependencies, run them first.
local depends
depends=$(echo "$task" | jq -r '.dependsOn? // empty')
if [ -n "$depends" ] && [ "$depends" != "null" ]; then
# "dependsOn" can be an array or a string.
if echo "$depends" | jq -e 'if type=="array" then . else empty end' >/dev/null; then
for dep in $(echo "$depends" | jq -r '.[]'); do
run_task "$dep"
done
else
run_task "$depends"
fi
fi
# Check if the task has either a command or script.
local has_command has_script
has_command=$(echo "$task" | jq -r 'has("command")')
has_script=$(echo "$task" | jq -r 'has("script")')
if [[ "$has_command" != "true" && "$has_script" != "true" ]]; then
echo "Task '$label' has no command or script; skipping execution."
return
fi
# Determine the command to run:
local cmd=""
if echo "$task" | jq 'has("command")' | grep -q "true"; then
cmd=$(echo "$task" | jq -r '.command')
elif echo "$task" | jq 'has("script")' | grep -q "true"; then
local script
script=$(echo "$task" | jq -r '.script')
local path
path=$(echo "$task" | jq -r '.path // empty')
if [ -n "$path" ]; then
cmd="cd $path && npm run $script"
else
cmd="npm run $script"
fi
else
echo "Error: Task '$label' does not have a command or script." >&2
exit 1
fi
# Substitute ${workspaceFolder} with the actual folder path.
cmd="${cmd//\$\{workspaceFolder\}/$WORKSPACE_FOLDER}"
echo "Running task '$label': $cmd"
# Run the task in a subshell so that directory changes don't persist.
( eval "$cmd" )
}
run_task "$TASK_LABEL"