mirror of
https://github.com/SteamDeckHomebrew/decky-loader.git
synced 2026-07-01 15:29:54 +00:00
Compare commits
27 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1781c19c11 | |||
| 1ef3cb8307 | |||
| 5bc4dc684d | |||
| 4cff530b52 | |||
| 2f90a4fcf7 | |||
| 24fce1e093 | |||
| 97b12972ee | |||
| f69eb72df9 | |||
| 24215c0732 | |||
| e87ce625fb | |||
| 1284075d02 | |||
| d2c5aef58b | |||
| caac379b08 | |||
| c487a6e15a | |||
| 9df5f00068 | |||
| 508408ad5a | |||
| ef4ca204bd | |||
| 1d7af36a2b | |||
| 6b78e0ae9c | |||
| 1f5d5f9f1a | |||
| f7a47127a7 | |||
| 494f8dac5e | |||
| bcc14848c5 | |||
| 0e40374b10 | |||
| 81ffe11106 | |||
| d06494885a | |||
| a6e4bcf052 |
@@ -57,18 +57,34 @@ body:
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: input
|
||||
attributes:
|
||||
label: Decky Loader Version
|
||||
description: Specify the exact version of Decky.
|
||||
placeholder: v3.0.0-pre12
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Plugin Info
|
||||
description: "Include all plugins installed including their version. Helpful script here: https://github.com/SteamDeckHomebrew/decky-loader/blob/main/scripts/plugin-info.sh"
|
||||
placeholder: "If you don't want to collect this info manually you can download a helpful script linked in this item's description and place it into your home directory, chmod +x plugin-info.sh and then run it with ./plugin-info.sh"
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: input
|
||||
attributes:
|
||||
label: Have you modified the read-only filesystem at any point?
|
||||
description: Describe how here, if you haven't done anything you can leave this blank
|
||||
placeholder: Yes, I've installed neofetch via pacman.
|
||||
description: "Describe how here, if you haven't done anything you can leave this blank"
|
||||
placeholder: "Yes, I've installed neofetch via pacman."
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Backend Logs
|
||||
description: Please reboot your deck (if possible) when attempting to recreate the issue, then run ``cd ~ && journalctl -b0 -u plugin_loader.service > deckylog.txt``. This will save the log file to ``~`` aka ``/home/deck``. Please upload the file here
|
||||
description: Please reboot your deck (if possible) when attempting to recreate the issue, then run ``cd ~ && journalctl -b0 -u plugin_loader.service > deckylog.txt``. This will save the log file to ``~`` aka ``/home/deck``. Please upload the file here.
|
||||
placeholder: deckylog.txt
|
||||
validations:
|
||||
required: true
|
||||
|
||||
@@ -36,10 +36,10 @@ jobs:
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install semver-tool asdf
|
||||
uses: asdf-vm/actions/install@v1
|
||||
uses: asdf-vm/actions/install@v3
|
||||
with:
|
||||
tool_versions: |
|
||||
semver 3.3.0
|
||||
semver 3.4.0
|
||||
|
||||
- name: Get latest release
|
||||
uses: rez0n/actions-github-release@main
|
||||
|
||||
@@ -99,12 +99,14 @@
|
||||
}
|
||||
},
|
||||
"PluginListIndex": {
|
||||
"freeze": "Замразяване на актуализациите",
|
||||
"hide": "Бърз достъп: Скриване",
|
||||
"no_plugin": "Няма инсталирани добавки!",
|
||||
"plugin_actions": "Действия с добавката",
|
||||
"reinstall": "Преинсталиране",
|
||||
"reload": "Презареждане",
|
||||
"show": "Бърз достъп: Показване",
|
||||
"unfreeze": "Разрешаване на актуализациите",
|
||||
"uninstall": "Деинсталиране",
|
||||
"update_all_one": "Обновяване на 1 добавка",
|
||||
"update_all_other": "Обновяване на {{count}} добавки",
|
||||
@@ -192,9 +194,19 @@
|
||||
"SettingsIndex": {
|
||||
"developer_title": "Разработчик",
|
||||
"general_title": "Общи",
|
||||
"plugins_title": "Добавки"
|
||||
"plugins_title": "Добавки",
|
||||
"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, разгледайте хранилището SteamDeckHomebrew/decky-plugin-template в GitHub. Може да намерите информация относно разработката и разпространението във файла README.",
|
||||
"label": "Допринасяне"
|
||||
@@ -218,9 +230,17 @@
|
||||
"about": "Относно",
|
||||
"alph_asce": "По азбучен ред (Я -> А)",
|
||||
"alph_desc": "По азбучен ред (А -> Я)",
|
||||
"date_asce": "Най-старият първи",
|
||||
"date_desc": "Най-новият първи",
|
||||
"downloads_asce": "Най-малко изтеглени първи",
|
||||
"downloads_desc": "Най-изтеглени първи",
|
||||
"title": "Разглеждане"
|
||||
},
|
||||
"store_testing_cta": "Помислете дали искате да тествате новите добавки, за да помогнете на екипа на Decky Loader!"
|
||||
"store_testing_cta": "Помислете дали искате да тествате новите добавки, за да помогнете на екипа на Decky Loader!",
|
||||
"store_testing_warning": {
|
||||
"desc": "Можете да използвате този канал на магазина, за да тествате най-новите версии на плъгините. Не забравяйте да оставите обратна връзка в GitHub, за да може плъгинът да бъде актуализиран за всички потребители.",
|
||||
"label": "Добре дошли в канала на магазина за тестване"
|
||||
}
|
||||
},
|
||||
"StoreSelect": {
|
||||
"custom_store": {
|
||||
@@ -234,6 +254,17 @@
|
||||
"testing": "Тестване"
|
||||
}
|
||||
},
|
||||
"Testing": {
|
||||
"download": "Изтегляне",
|
||||
"error": "Грешка при инсталирането на PR",
|
||||
"header": "Следните версии на Decky Loader са създадени от отворени заявки за изтегляне от трети страни. Екипът на Decky Loader не е проверявал тяхната функционалност или сигурност и е възможно те да са остарели.",
|
||||
"loading": "Зареждане на отворени заявки за изтегляне...",
|
||||
"start_download_toast": "Изтегляне на PR #{{id}}"
|
||||
},
|
||||
"TitleView": {
|
||||
"decky_store_desc": "Отворете Decky Store",
|
||||
"settings_desc": "Отворете настройките на Decky"
|
||||
},
|
||||
"Updater": {
|
||||
"decky_updates": "Обновления на Decky",
|
||||
"no_patch_notes_desc": "няма бележки за промените в тази версия",
|
||||
|
||||
@@ -198,6 +198,15 @@
|
||||
"testing_title": "Testen"
|
||||
},
|
||||
"Store": {
|
||||
"download_progress_info": {
|
||||
"download_zip": "Plugin herunterladen",
|
||||
"increment_count": "Erhöhen der Downloadanzahl",
|
||||
"installing_plugin": "Plugin installieren",
|
||||
"open_zip": "Öffnen der Zip-Datei",
|
||||
"parse_zip": "Parsen der Zip-Datei",
|
||||
"start": "Initialisieren",
|
||||
"uninstalling_previous": "Vorherige Kopie deinstallieren"
|
||||
},
|
||||
"store_contrib": {
|
||||
"desc": "Wenn du Erweiterungen im Decky Store veröffentlichen willst, besuche die SteamDeckHomebrew/decky-plugin-template Repository auf GitHub. Informationen rund um Entwicklung und Veröffentlichung findest du in der README.",
|
||||
"label": "Mitwirken"
|
||||
@@ -246,7 +255,11 @@
|
||||
}
|
||||
},
|
||||
"Testing": {
|
||||
"download": "Download"
|
||||
"download": "Download",
|
||||
"error": "Fehler beim Installieren von PR",
|
||||
"header": "Die folgenden Versionen von Decky Loader wurden aus offenen Pull Requests von Dritten erstellt. Das Decky Loader-Team hat ihre Funktionalität oder Sicherheit nicht überprüft, und sie können veraltet sein.",
|
||||
"loading": "Offene Pull Requests laden...",
|
||||
"start_download_toast": "Herunterladen von PR #{{id}}"
|
||||
},
|
||||
"TitleView": {
|
||||
"decky_store_desc": "Decky Store Öffnen",
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
"BranchSelect": {
|
||||
"update_channel": {
|
||||
"label": "Updatekanaal",
|
||||
"prerelease": "Prerelease",
|
||||
"prerelease": "Vooruitgave",
|
||||
"stable": "Stabiel",
|
||||
"testing": "Testing"
|
||||
"testing": "Testen"
|
||||
}
|
||||
},
|
||||
"Developer": {
|
||||
@@ -188,7 +188,7 @@
|
||||
"header": "Overige"
|
||||
},
|
||||
"updates": {
|
||||
"header": "Updates"
|
||||
"header": "Bijwerkingen"
|
||||
}
|
||||
},
|
||||
"SettingsIndex": {
|
||||
@@ -198,6 +198,15 @@
|
||||
"testing_title": "Testen"
|
||||
},
|
||||
"Store": {
|
||||
"download_progress_info": {
|
||||
"download_zip": "Plugin downloaden",
|
||||
"increment_count": "Aantal downloads verhogen",
|
||||
"installing_plugin": "Plugin installeren",
|
||||
"open_zip": "Zip-bestand openen",
|
||||
"parse_zip": "Zip-bestand parseren",
|
||||
"start": "Initialiseren",
|
||||
"uninstalling_previous": "Vorige kopie verwijderen"
|
||||
},
|
||||
"store_contrib": {
|
||||
"desc": "Als je wilt bijdragen aan de Decky Plugin Store, kijk dan in de SteamDeckHomebrew/decky-plugin-template repository op GitHub. Informatie over ontwikkeling en distributie is beschikbaar in de README.",
|
||||
"label": "Bijdragen"
|
||||
@@ -242,11 +251,15 @@
|
||||
"custom": "Aangepast",
|
||||
"default": "Standaard",
|
||||
"label": "Winkelkanaal",
|
||||
"testing": "Testing"
|
||||
"testing": "Testen"
|
||||
}
|
||||
},
|
||||
"Testing": {
|
||||
"download": "Downloaden"
|
||||
"download": "Downloaden",
|
||||
"error": "Fout bij installatie van PR",
|
||||
"header": "De volgende versies van Decky Loader zijn gebouwd op basis van open Pull Requests van derden. Het Decky Loader-team heeft hun functionaliteit of veiligheid niet gecontroleerd en ze kunnen verouderd zijn.",
|
||||
"loading": "Openstaande Pull Requests laden...",
|
||||
"start_download_toast": "PR #{{id}} downloaden"
|
||||
},
|
||||
"TitleView": {
|
||||
"decky_store_desc": "Decky Store openen",
|
||||
@@ -261,7 +274,7 @@
|
||||
"checking": "Bezig met controleren op updates",
|
||||
"cur_version": "Huidige versie: {{ver}}",
|
||||
"install_button": "Bijwerken",
|
||||
"label": "Updates",
|
||||
"label": "Bijwerkingen",
|
||||
"lat_version": "Bijwerkt: versie {{ver}}",
|
||||
"reloading": "Bezig met herstarten",
|
||||
"updating": "Bezig met bijwerken"
|
||||
|
||||
@@ -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 商店",
|
||||
|
||||
@@ -1,11 +1,21 @@
|
||||
from re import compile
|
||||
from asyncio import Lock
|
||||
from asyncio import Lock, create_subprocess_exec
|
||||
from asyncio.subprocess import PIPE, DEVNULL, STDOUT, Process
|
||||
from subprocess import call as call_sync
|
||||
import os, pwd, grp, sys, logging
|
||||
from subprocess import call, run, DEVNULL, PIPE, STDOUT
|
||||
from typing import IO, Any, Mapping
|
||||
from ..enums import UserType
|
||||
|
||||
logger = logging.getLogger("localplatform")
|
||||
|
||||
# subprocess._ENV
|
||||
ENV = Mapping[str, str]
|
||||
ProcessIO = int | IO[Any] | None
|
||||
async def run(args: list[str], stdin: ProcessIO = DEVNULL, stdout: ProcessIO = PIPE, stderr: ProcessIO = PIPE, env: ENV | None = None) -> tuple[Process, bytes | None, bytes | None]:
|
||||
proc = await create_subprocess_exec(args[0], *(args[1:]), stdin=stdin, stdout=stdout, stderr=stderr, env=env)
|
||||
proc_stdout, proc_stderr = await proc.communicate()
|
||||
return (proc, proc_stdout, proc_stderr)
|
||||
|
||||
# Get the user id hosting the plugin loader
|
||||
def _get_user_id() -> int:
|
||||
return pwd.getpwnam(_get_user()).pw_uid
|
||||
@@ -54,7 +64,7 @@ def chown(path : str, user : UserType = UserType.HOST_USER, recursive : bool =
|
||||
else:
|
||||
raise Exception("Unknown User Type")
|
||||
|
||||
result = call(["chown", "-R", user_str, path] if recursive else ["chown", user_str, path])
|
||||
result = call_sync(["chown", "-R", user_str, path] if recursive else ["chown", user_str, path])
|
||||
return result == 0
|
||||
|
||||
def chmod(path : str, permissions : int, recursive : bool = True) -> bool:
|
||||
@@ -131,13 +141,17 @@ def setuid(user : UserType = UserType.HOST_USER):
|
||||
os.setuid(user_id)
|
||||
|
||||
async def service_active(service_name : str) -> bool:
|
||||
res = run(["systemctl", "is-active", service_name], stdout=DEVNULL, stderr=DEVNULL)
|
||||
res, _, _ = await run(["systemctl", "is-active", service_name], stdout=DEVNULL, stderr=DEVNULL)
|
||||
return res.returncode == 0
|
||||
|
||||
async def service_restart(service_name : str) -> bool:
|
||||
call(["systemctl", "daemon-reload"])
|
||||
async def service_restart(service_name : str, block : bool = True) -> bool:
|
||||
await run(["systemctl", "daemon-reload"])
|
||||
cmd = ["systemctl", "restart", service_name]
|
||||
res = run(cmd, stdout=PIPE, stderr=STDOUT)
|
||||
|
||||
if not block:
|
||||
cmd.append("--no-block")
|
||||
|
||||
res, _, _ = await run(cmd, stdout=PIPE, stderr=STDOUT)
|
||||
return res.returncode == 0
|
||||
|
||||
async def service_stop(service_name : str) -> bool:
|
||||
@@ -146,7 +160,7 @@ async def service_stop(service_name : str) -> bool:
|
||||
return True
|
||||
|
||||
cmd = ["systemctl", "stop", service_name]
|
||||
res = run(cmd, stdout=PIPE, stderr=STDOUT)
|
||||
res, _, _ = await run(cmd, stdout=PIPE, stderr=STDOUT)
|
||||
return res.returncode == 0
|
||||
|
||||
async def service_start(service_name : str) -> bool:
|
||||
@@ -155,13 +169,13 @@ async def service_start(service_name : str) -> bool:
|
||||
return True
|
||||
|
||||
cmd = ["systemctl", "start", service_name]
|
||||
res = run(cmd, stdout=PIPE, stderr=STDOUT)
|
||||
res, _, _ = await run(cmd, stdout=PIPE, stderr=STDOUT)
|
||||
return res.returncode == 0
|
||||
|
||||
async def restart_webhelper() -> bool:
|
||||
logger.info("Restarting steamwebhelper")
|
||||
# TODO move to pkill
|
||||
res = run(["killall", "-s", "SIGTERM", "steamwebhelper"], stdout=DEVNULL, stderr=DEVNULL)
|
||||
res, _, _ = await run(["killall", "-s", "SIGTERM", "steamwebhelper"], stdout=DEVNULL, stderr=DEVNULL)
|
||||
return res.returncode == 0
|
||||
|
||||
def get_privileged_path() -> str:
|
||||
@@ -241,12 +255,12 @@ async def close_cef_socket():
|
||||
logger.warning("Can't close CEF socket as Decky isn't running as root.")
|
||||
return
|
||||
# Look for anything listening TCP on port 8080
|
||||
lsof = run(["lsof", "-F", "-iTCP:8080", "-sTCP:LISTEN"], capture_output=True, text=True)
|
||||
if lsof.returncode != 0 or len(lsof.stdout) < 1:
|
||||
lsof, stdout, _ = await run(["lsof", "-F", "-iTCP:8080", "-sTCP:LISTEN"], stdout=PIPE)
|
||||
if not stdout or lsof.returncode != 0 or len(stdout) < 1:
|
||||
logger.error(f"lsof call failed in close_cef_socket! return code: {str(lsof.returncode)}")
|
||||
return
|
||||
|
||||
lsof_data = cef_socket_lsof_regex.match(lsof.stdout)
|
||||
lsof_data = cef_socket_lsof_regex.match(stdout.decode())
|
||||
|
||||
if not lsof_data:
|
||||
logger.error("lsof regex match failed in close_cef_socket!")
|
||||
@@ -258,7 +272,7 @@ async def close_cef_socket():
|
||||
logger.info(f"Closing CEF socket with PID {pid} and FD {fd}")
|
||||
|
||||
# Use gdb to inject a close() call for the socket fd into steamwebhelper
|
||||
gdb_ret = run(["gdb", "--nx", "-p", pid, "--batch", "--eval-command", f"call (int)close({fd})"], env={"LD_LIBRARY_PATH": ""})
|
||||
gdb_ret, _, _ = await run(["gdb", "--nx", "-p", pid, "--batch", "--eval-command", f"call (int)close({fd})"], env={"LD_LIBRARY_PATH": ""})
|
||||
|
||||
if gdb_ret.returncode != 0:
|
||||
logger.error(f"Failed to close CEF socket with gdb! return code: {str(gdb_ret.returncode)}", exc_info=True)
|
||||
|
||||
@@ -28,7 +28,7 @@ async def service_stop(service_name : str) -> bool:
|
||||
async def service_start(service_name : str) -> bool:
|
||||
return True # Stubbed
|
||||
|
||||
async def service_restart(service_name : str) -> bool:
|
||||
async def service_restart(service_name : str, block : bool = True) -> bool:
|
||||
if service_name == "plugin_loader":
|
||||
sys.exit(42)
|
||||
|
||||
|
||||
@@ -138,16 +138,17 @@ class PluginManager:
|
||||
tasks = all_tasks()
|
||||
current = current_task()
|
||||
async def cancel_task(task: Task[Any]):
|
||||
logger.debug(f"Cancelling task {task}")
|
||||
name = task.get_coro().__qualname__
|
||||
logger.debug(f"Cancelling task {name}")
|
||||
try:
|
||||
task.cancel()
|
||||
try:
|
||||
await task
|
||||
except CancelledError:
|
||||
pass
|
||||
logger.debug(f"Task {task} finished")
|
||||
logger.debug(f"Task {name} finished")
|
||||
except:
|
||||
logger.warning(f"Failed to cancel task {task}:\n" + format_exc())
|
||||
logger.warning(f"Failed to cancel task {name}:\n" + format_exc())
|
||||
pass
|
||||
if current:
|
||||
tasks.remove(current)
|
||||
|
||||
@@ -120,18 +120,25 @@ class PluginWrapper:
|
||||
start_time = time()
|
||||
if self.passive:
|
||||
return
|
||||
self.log.info(f"Shutting down {self.name}")
|
||||
|
||||
_, pending = await wait([
|
||||
create_task(self._socket.write_single_line(dumps({ "stop": True, "uninstall": uninstall }, ensure_ascii=False)))
|
||||
], timeout=1)
|
||||
pending: set[Task[None]] | None = None;
|
||||
|
||||
if uninstall:
|
||||
_, pending = await wait([
|
||||
create_task(self._socket.write_single_line(dumps({ "uninstall": uninstall }, ensure_ascii=False)))
|
||||
], timeout=1)
|
||||
|
||||
self.terminate() # the plugin process will handle SIGTERM and shut down cleanly without a socket message
|
||||
|
||||
if hasattr(self, "_listener_task"):
|
||||
self._listener_task.cancel()
|
||||
|
||||
await self.kill_if_still_running()
|
||||
|
||||
for pending_task in pending:
|
||||
pending_task.cancel()
|
||||
if pending:
|
||||
for pending_task in pending:
|
||||
pending_task.cancel()
|
||||
|
||||
self.log.info(f"Plugin {self.name} has been stopped in {time() - start_time:.1f}s")
|
||||
except Exception as e:
|
||||
@@ -139,18 +146,14 @@ class PluginWrapper:
|
||||
|
||||
async def kill_if_still_running(self):
|
||||
start_time = time()
|
||||
sigtermed = False
|
||||
while self.proc and self.proc.is_alive():
|
||||
elapsed_time = time() - start_time
|
||||
if elapsed_time >= 5 and not sigtermed:
|
||||
sigtermed = True
|
||||
self.log.warning(f"Plugin {self.name} still alive 5 seconds after stop request! Sending SIGTERM!")
|
||||
self.terminate()
|
||||
elif elapsed_time >= 10:
|
||||
self.log.warning(f"Plugin {self.name} still alive 10 seconds after stop request! Sending SIGKILL!")
|
||||
if elapsed_time >= 5:
|
||||
self.log.warning(f"Plugin {self.name} still alive 5 seconds after stop request! Sending SIGKILL!")
|
||||
self.terminate(True)
|
||||
await sleep(0.1)
|
||||
|
||||
|
||||
def terminate(self, kill: bool = False):
|
||||
if self.proc and self.proc.is_alive():
|
||||
if kill:
|
||||
|
||||
@@ -4,13 +4,14 @@ from importlib.util import module_from_spec, spec_from_file_location
|
||||
from json import dumps, loads
|
||||
from logging import getLogger
|
||||
from traceback import format_exc
|
||||
from asyncio import (get_event_loop, new_event_loop,
|
||||
from asyncio import (ensure_future, get_event_loop, new_event_loop,
|
||||
set_event_loop)
|
||||
from signal import SIGINT, SIGTERM
|
||||
from setproctitle import setproctitle, setthreadtitle
|
||||
|
||||
from .messages import SocketResponseDict, SocketMessageType
|
||||
from ..localplatform.localsocket import LocalSocket
|
||||
from ..localplatform.localplatform import setgid, setuid, get_username, get_home_path
|
||||
from ..localplatform.localplatform import setgid, setuid, get_username, get_home_path, ON_LINUX
|
||||
from ..enums import UserType
|
||||
from .. import helpers, settings, injector # pyright: ignore [reportUnusedImport]
|
||||
|
||||
@@ -38,6 +39,8 @@ class SandboxedPlugin:
|
||||
self.version = version
|
||||
self.author = author
|
||||
self.api_version = api_version
|
||||
self.shutdown_running = False
|
||||
self.uninstalling = False
|
||||
|
||||
self.log = getLogger("sandboxed_plugin")
|
||||
|
||||
@@ -48,9 +51,16 @@ class SandboxedPlugin:
|
||||
setproctitle(f"{self.name} ({self.file})")
|
||||
setthreadtitle(self.name)
|
||||
|
||||
set_event_loop(new_event_loop())
|
||||
loop = new_event_loop()
|
||||
set_event_loop(loop)
|
||||
# When running Decky manually in a terminal, ctrl-c will trigger this, so we have to handle it properly
|
||||
if ON_LINUX:
|
||||
loop.add_signal_handler(SIGINT, lambda: ensure_future(self.shutdown()))
|
||||
loop.add_signal_handler(SIGTERM, lambda: ensure_future(self.shutdown()))
|
||||
|
||||
if self.passive:
|
||||
return
|
||||
|
||||
setgid(UserType.ROOT if "root" in self.flags else UserType.HOST_USER)
|
||||
setuid(UserType.ROOT if "root" in self.flags else UserType.HOST_USER)
|
||||
# export a bunch of environment variables to help plugin developers
|
||||
@@ -155,22 +165,27 @@ class SandboxedPlugin:
|
||||
self.log.error("Failed to uninstall " + self.name + "!\n" + format_exc())
|
||||
pass
|
||||
|
||||
async def on_new_message(self, message : str) -> str|None:
|
||||
data = loads(message)
|
||||
|
||||
if "stop" in data:
|
||||
async def shutdown(self):
|
||||
if not self.shutdown_running:
|
||||
self.shutdown_running = True
|
||||
self.log.info(f"Calling Loader unload function for {self.name}.")
|
||||
await self._unload()
|
||||
|
||||
if data.get('uninstall'):
|
||||
if self.uninstalling:
|
||||
self.log.info("Calling Loader uninstall function.")
|
||||
await self._uninstall()
|
||||
|
||||
self.log.debug("Stopping event loop")
|
||||
self.log.debug("Stopping event loop")
|
||||
|
||||
loop = get_event_loop()
|
||||
loop.call_soon_threadsafe(loop.stop)
|
||||
sys.exit(0)
|
||||
loop = get_event_loop()
|
||||
loop.call_soon_threadsafe(loop.stop)
|
||||
sys.exit(0)
|
||||
|
||||
async def on_new_message(self, message : str) -> str|None:
|
||||
data = loads(message)
|
||||
|
||||
if "uninstall" in data:
|
||||
self.uninstalling = data.get("uninstall")
|
||||
|
||||
d: SocketResponseDict = {"type": SocketMessageType.RESPONSE, "res": None, "success": True, "id": data["id"]}
|
||||
try:
|
||||
|
||||
@@ -24,6 +24,7 @@ logger = getLogger("Updater")
|
||||
|
||||
class RemoteVerAsset(TypedDict):
|
||||
name: str
|
||||
size: int
|
||||
browser_download_url: str
|
||||
class RemoteVer(TypedDict):
|
||||
tag_name: str
|
||||
@@ -198,11 +199,13 @@ class Updater:
|
||||
|
||||
version = self.remoteVer["tag_name"]
|
||||
download_url = None
|
||||
size_in_bytes = None
|
||||
download_filename = "PluginLoader" if ON_LINUX else "PluginLoader.exe"
|
||||
|
||||
for x in self.remoteVer["assets"]:
|
||||
if x["name"] == download_filename:
|
||||
download_url = x["browser_download_url"]
|
||||
size_in_bytes = x["size"]
|
||||
break
|
||||
|
||||
if download_url == None:
|
||||
@@ -238,10 +241,10 @@ class Updater:
|
||||
os.mkdir(path.join(getcwd(), ".systemd"))
|
||||
shutil.move(service_file_path, path.join(getcwd(), ".systemd")+"/plugin_loader.service")
|
||||
|
||||
await self.download_decky_binary(download_url, version)
|
||||
await self.download_decky_binary(download_url, version, size_in_bytes=size_in_bytes)
|
||||
|
||||
async def do_restart(self):
|
||||
await service_restart("plugin_loader")
|
||||
await service_restart("plugin_loader", block=False)
|
||||
|
||||
async def do_shutdown(self):
|
||||
await service_stop("plugin_loader")
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
Vendored
+2
-69
@@ -1,70 +1,3 @@
|
||||
#!/bin/sh
|
||||
|
||||
[ "$UID" -eq 0 ] || exec sudo "$0" "$@"
|
||||
|
||||
echo "Installing Steam Deck Plugin Loader pre-release..."
|
||||
|
||||
USER_DIR="$(getent passwd $SUDO_USER | cut -d: -f6)"
|
||||
HOMEBREW_FOLDER="${USER_DIR}/homebrew"
|
||||
|
||||
# Create folder structure
|
||||
rm -rf "${HOMEBREW_FOLDER}/services"
|
||||
sudo -u $SUDO_USER mkdir -p "${HOMEBREW_FOLDER}/services"
|
||||
sudo -u $SUDO_USER mkdir -p "${HOMEBREW_FOLDER}/plugins"
|
||||
touch "${USER_DIR}/.steam/steam/.cef-enable-remote-debugging"
|
||||
|
||||
# Download latest release and install it
|
||||
RELEASE=$(curl -s 'https://api.github.com/repos/SteamDeckHomebrew/decky-loader/releases' | jq -r "first(.[] | select(.prerelease == "true"))")
|
||||
VERSION=$(jq -r '.tag_name' <<< ${RELEASE} )
|
||||
DOWNLOADURL=$(jq -r '.assets[].browser_download_url | select(endswith("PluginLoader"))' <<< ${RELEASE})
|
||||
|
||||
printf "Installing version %s...\n" "${VERSION}"
|
||||
curl -L $DOWNLOADURL --output ${HOMEBREW_FOLDER}/services/PluginLoader
|
||||
chmod +x ${HOMEBREW_FOLDER}/services/PluginLoader
|
||||
echo $VERSION > ${HOMEBREW_FOLDER}/services/.loader.version
|
||||
|
||||
systemctl --user stop plugin_loader 2> /dev/null
|
||||
systemctl --user disable plugin_loader 2> /dev/null
|
||||
|
||||
systemctl stop plugin_loader 2> /dev/null
|
||||
systemctl disable plugin_loader 2> /dev/null
|
||||
|
||||
curl -L https://raw.githubusercontent.com/SteamDeckHomebrew/decky-loader/main/dist/plugin_loader-prerelease.service --output ${HOMEBREW_FOLDER}/services/plugin_loader-prerelease.service
|
||||
|
||||
cat > "${HOMEBREW_FOLDER}/services/plugin_loader-backup.service" <<- EOM
|
||||
[Unit]
|
||||
Description=SteamDeck Plugin Loader
|
||||
After=network.target
|
||||
[Service]
|
||||
Type=simple
|
||||
User=root
|
||||
Restart=always
|
||||
KillMode=process
|
||||
TimeoutStopSec=45
|
||||
ExecStart=${HOMEBREW_FOLDER}/services/PluginLoader
|
||||
WorkingDirectory=${HOMEBREW_FOLDER}/services
|
||||
Environment=UNPRIVILEGED_PATH=${HOMEBREW_FOLDER}
|
||||
Environment=PRIVILEGED_PATH=${HOMEBREW_FOLDER}
|
||||
Environment=LOG_LEVEL=DEBUG
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
EOM
|
||||
|
||||
if [[ -f "${HOMEBREW_FOLDER}/services/plugin_loader-prerelease.service" ]]; then
|
||||
printf "Grabbed latest prerelease service.\n"
|
||||
sed -i -e "s|\${HOMEBREW_FOLDER}|${HOMEBREW_FOLDER}|" "${HOMEBREW_FOLDER}/services/plugin_loader-prerelease.service"
|
||||
cp -f "${HOMEBREW_FOLDER}/services/plugin_loader-prerelease.service" "/etc/systemd/system/plugin_loader.service"
|
||||
else
|
||||
printf "Could not curl latest prerelease systemd service, using built-in service as a backup!\n"
|
||||
rm -f "/etc/systemd/system/plugin_loader.service"
|
||||
cp "${HOMEBREW_FOLDER}/services/plugin_loader-backup.service" "/etc/systemd/system/plugin_loader.service"
|
||||
fi
|
||||
|
||||
mkdir -p ${HOMEBREW_FOLDER}/services/.systemd
|
||||
cp ${HOMEBREW_FOLDER}/services/plugin_loader-prerelease.service ${HOMEBREW_FOLDER}/services/.systemd/plugin_loader-prerelease.service
|
||||
cp ${HOMEBREW_FOLDER}/services/plugin_loader-backup.service ${HOMEBREW_FOLDER}/services/.systemd/plugin_loader-backup.service
|
||||
rm ${HOMEBREW_FOLDER}/services/plugin_loader-backup.service ${HOMEBREW_FOLDER}/services/plugin_loader-prerelease.service
|
||||
|
||||
systemctl daemon-reload
|
||||
systemctl start plugin_loader
|
||||
systemctl enable plugin_loader
|
||||
echo This script is deprecated! Use https://github.com/SteamDeckHomebrew/decky-installer/raw/main/cli/install_prerelease.sh instead!
|
||||
exit 1
|
||||
Vendored
+2
-69
@@ -1,70 +1,3 @@
|
||||
#!/bin/sh
|
||||
|
||||
[ "$UID" -eq 0 ] || exec sudo "$0" "$@"
|
||||
|
||||
echo "Installing Steam Deck Plugin Loader release..."
|
||||
|
||||
USER_DIR="$(getent passwd $SUDO_USER | cut -d: -f6)"
|
||||
HOMEBREW_FOLDER="${USER_DIR}/homebrew"
|
||||
|
||||
# Create folder structure
|
||||
rm -rf "${HOMEBREW_FOLDER}/services"
|
||||
sudo -u $SUDO_USER mkdir -p "${HOMEBREW_FOLDER}/services"
|
||||
sudo -u $SUDO_USER mkdir -p "${HOMEBREW_FOLDER}/plugins"
|
||||
touch "${USER_DIR}/.steam/steam/.cef-enable-remote-debugging"
|
||||
|
||||
# Download latest release and install it
|
||||
RELEASE=$(curl -s 'https://api.github.com/repos/SteamDeckHomebrew/decky-loader/releases' | jq -r "first(.[] | select(.prerelease == "false"))")
|
||||
VERSION=$(jq -r '.tag_name' <<< ${RELEASE} )
|
||||
DOWNLOADURL=$(jq -r '.assets[].browser_download_url | select(endswith("PluginLoader"))' <<< ${RELEASE})
|
||||
|
||||
printf "Installing version %s...\n" "${VERSION}"
|
||||
curl -L $DOWNLOADURL --output ${HOMEBREW_FOLDER}/services/PluginLoader
|
||||
chmod +x ${HOMEBREW_FOLDER}/services/PluginLoader
|
||||
echo $VERSION > ${HOMEBREW_FOLDER}/services/.loader.version
|
||||
|
||||
systemctl --user stop plugin_loader 2> /dev/null
|
||||
systemctl --user disable plugin_loader 2> /dev/null
|
||||
|
||||
systemctl stop plugin_loader 2> /dev/null
|
||||
systemctl disable plugin_loader 2> /dev/null
|
||||
|
||||
curl -L https://raw.githubusercontent.com/SteamDeckHomebrew/decky-loader/main/dist/plugin_loader-release.service --output ${HOMEBREW_FOLDER}/services/plugin_loader-release.service
|
||||
|
||||
cat > "${HOMEBREW_FOLDER}/services/plugin_loader-backup.service" <<- EOM
|
||||
[Unit]
|
||||
Description=SteamDeck Plugin Loader
|
||||
After=network.target
|
||||
[Service]
|
||||
Type=simple
|
||||
User=root
|
||||
Restart=always
|
||||
KillMode=process
|
||||
TimeoutStopSec=45
|
||||
ExecStart=${HOMEBREW_FOLDER}/services/PluginLoader
|
||||
WorkingDirectory=${HOMEBREW_FOLDER}/services
|
||||
Environment=UNPRIVILEGED_PATH=${HOMEBREW_FOLDER}
|
||||
Environment=PRIVILEGED_PATH=${HOMEBREW_FOLDER}
|
||||
Environment=LOG_LEVEL=INFO
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
EOM
|
||||
|
||||
if [[ -f "${HOMEBREW_FOLDER}/services/plugin_loader-release.service" ]]; then
|
||||
printf "Grabbed latest release service.\n"
|
||||
sed -i -e "s|\${HOMEBREW_FOLDER}|${HOMEBREW_FOLDER}|" "${HOMEBREW_FOLDER}/services/plugin_loader-release.service"
|
||||
cp -f "${HOMEBREW_FOLDER}/services/plugin_loader-release.service" "/etc/systemd/system/plugin_loader.service"
|
||||
else
|
||||
printf "Could not curl latest release systemd service, using built-in service as a backup!\n"
|
||||
rm -f "/etc/systemd/system/plugin_loader.service"
|
||||
cp "${HOMEBREW_FOLDER}/services/plugin_loader-backup.service" "/etc/systemd/system/plugin_loader.service"
|
||||
fi
|
||||
|
||||
mkdir -p ${HOMEBREW_FOLDER}/services/.systemd
|
||||
cp ${HOMEBREW_FOLDER}/services/plugin_loader-release.service ${HOMEBREW_FOLDER}/services/.systemd/plugin_loader-release.service
|
||||
cp ${HOMEBREW_FOLDER}/services/plugin_loader-backup.service ${HOMEBREW_FOLDER}/services/.systemd/plugin_loader-backup.service
|
||||
rm ${HOMEBREW_FOLDER}/services/plugin_loader-backup.service ${HOMEBREW_FOLDER}/services/plugin_loader-release.service
|
||||
|
||||
systemctl daemon-reload
|
||||
systemctl start plugin_loader
|
||||
systemctl enable plugin_loader
|
||||
echo This script is deprecated! Use https://github.com/SteamDeckHomebrew/decky-installer/raw/main/cli/install_release.sh instead!
|
||||
exit 1
|
||||
+1
-1
@@ -6,7 +6,7 @@ Type=simple
|
||||
User=root
|
||||
Restart=always
|
||||
KillMode=process
|
||||
TimeoutStopSec=45
|
||||
TimeoutStopSec=15
|
||||
ExecStart=${HOMEBREW_FOLDER}/services/PluginLoader
|
||||
WorkingDirectory=${HOMEBREW_FOLDER}/services
|
||||
Environment=UNPRIVILEGED_PATH=${HOMEBREW_FOLDER}
|
||||
|
||||
Vendored
+1
-1
@@ -6,7 +6,7 @@ Type=simple
|
||||
User=root
|
||||
Restart=always
|
||||
KillMode=process
|
||||
TimeoutStopSec=45
|
||||
TimeoutStopSec=15
|
||||
ExecStart=${HOMEBREW_FOLDER}/services/PluginLoader
|
||||
WorkingDirectory=${HOMEBREW_FOLDER}/services
|
||||
Environment=UNPRIVILEGED_PATH=${HOMEBREW_FOLDER}
|
||||
|
||||
@@ -47,7 +47,8 @@
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@decky/ui": "^4.7.1",
|
||||
"@decky/ui": "^4.7.4",
|
||||
"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.7.4
|
||||
version: 4.7.4
|
||||
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.7.4':
|
||||
resolution: {integrity: sha512-ziCP3akLJVYG5FFoS0ao9MYEYJ09g44FP4xMmOUFe8SFRjC9BqBvbJBF0+OUOKgp2C7SJ0rNPSIFm4RwCUvoug==}
|
||||
|
||||
'@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.7.4': {}
|
||||
|
||||
'@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: {}
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
import { Navigation } from '@decky/ui';
|
||||
import { AnchorHTMLAttributes, FC } from 'react';
|
||||
|
||||
const ExternalLink: FC<AnchorHTMLAttributes<HTMLAnchorElement>> = (props) => {
|
||||
return (
|
||||
<a
|
||||
{...props}
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
props.onClick ? props.onClick(e) : props.href && Navigation.NavigateToExternalWeb(props.href);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default ExternalLink;
|
||||
@@ -4,6 +4,7 @@ import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { InstallType } from '../../plugin';
|
||||
import { StorePlugin, StorePluginVersion, requestPluginInstall } from '../../store';
|
||||
import ExternalLink from '../ExternalLink';
|
||||
|
||||
interface PluginCardProps {
|
||||
plugin: StorePlugin;
|
||||
@@ -108,7 +109,7 @@ const PluginCard: FC<PluginCardProps> = ({ plugin }) => {
|
||||
}}
|
||||
>
|
||||
<i>{t('PluginCard.plugin_full_access')}</i>{' '}
|
||||
<a
|
||||
<ExternalLink
|
||||
className="deckyStoreCardDescriptionRootLink"
|
||||
href="https://deckbrew.xyz/root"
|
||||
target="_blank"
|
||||
@@ -118,7 +119,7 @@ const PluginCard: FC<PluginCardProps> = ({ plugin }) => {
|
||||
}}
|
||||
>
|
||||
deckbrew.xyz/root
|
||||
</a>
|
||||
</ExternalLink>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -14,6 +14,7 @@ import { useTranslation } from 'react-i18next';
|
||||
import logo from '../../../assets/plugin_store.png';
|
||||
import Logger from '../../logger';
|
||||
import { SortDirections, SortOptions, Store, StorePlugin, getPluginList, getStore } from '../../store';
|
||||
import ExternalLink from '../ExternalLink';
|
||||
import PluginCard from './PluginCard';
|
||||
|
||||
const logger = new Logger('Store');
|
||||
@@ -207,7 +208,7 @@ const BrowseTab: FC<{ setPluginCount: Dispatch<SetStateAction<number | null>> }>
|
||||
<h2 style={{ margin: 0 }}>{t('Store.store_testing_warning.label')}</h2>
|
||||
<span>
|
||||
{`${t('Store.store_testing_warning.desc')} `}
|
||||
<a
|
||||
<ExternalLink
|
||||
href="https://decky.xyz/testing"
|
||||
target="_blank"
|
||||
style={{
|
||||
@@ -215,7 +216,7 @@ const BrowseTab: FC<{ setPluginCount: Dispatch<SetStateAction<number | null>> }>
|
||||
}}
|
||||
>
|
||||
decky.xyz/testing
|
||||
</a>
|
||||
</ExternalLink>
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
@@ -269,7 +270,7 @@ const AboutTab: FC<{}> = () => {
|
||||
<span className="deckyStoreAboutHeader">Testing</span>
|
||||
<span>
|
||||
{t('Store.store_testing_cta')}{' '}
|
||||
<a
|
||||
<ExternalLink
|
||||
href="https://decky.xyz/testing"
|
||||
target="_blank"
|
||||
style={{
|
||||
@@ -277,7 +278,7 @@ const AboutTab: FC<{}> = () => {
|
||||
}}
|
||||
>
|
||||
decky.xyz/testing
|
||||
</a>
|
||||
</ExternalLink>
|
||||
</span>
|
||||
<span className="deckyStoreAboutHeader">{t('Store.store_contrib.label')}</span>
|
||||
<span>{t('Store.store_contrib.desc')}</span>
|
||||
|
||||
+8
-15
@@ -5,23 +5,16 @@ interface Window {
|
||||
}
|
||||
|
||||
(async () => {
|
||||
// Wait for main webpack chunks to definitely be loaded
|
||||
console.time('[Decky:Boot] Waiting for main Webpack chunks...');
|
||||
while (!window.webpackChunksteamui || window.webpackChunksteamui.length < 5) {
|
||||
await new Promise((r) => setTimeout(r, 10)); // Can't use DFL sleep here.
|
||||
}
|
||||
console.timeEnd('[Decky:Boot] Waiting for main Webpack chunks...');
|
||||
console.debug('[Decky:Boot] Frontend init');
|
||||
|
||||
// Wait for the React root to be mounted
|
||||
console.time('[Decky:Boot] Waiting for React root mount...');
|
||||
let root;
|
||||
while (
|
||||
!(root = document.getElementById('root')) ||
|
||||
!(root as any)[Object.keys(root).find((k) => k.startsWith('__reactContainer$')) as string]
|
||||
) {
|
||||
await new Promise((r) => setTimeout(r, 10)); // Can't use DFL sleep here.
|
||||
console.time('[Decky:Boot] Waiting for SteamApp init stage 1 to finish...');
|
||||
|
||||
// @ts-expect-error TODO type BFinishedInitStageOne in @decky/ui
|
||||
while (!window.App?.BFinishedInitStageOne()) {
|
||||
await new Promise((r) => setTimeout(r, 0)); // Can't use DFL sleep here.
|
||||
}
|
||||
console.timeEnd('[Decky:Boot] Waiting for React root mount...');
|
||||
|
||||
console.timeEnd('[Decky:Boot] Waiting for SteamApp init stage 1 to finish...');
|
||||
|
||||
if (!window.SP_REACT) {
|
||||
console.debug('[Decky:Boot] Setting up Webpack & React globals...');
|
||||
|
||||
@@ -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 {
|
||||
@@ -421,7 +422,7 @@ class PluginLoader extends Logger {
|
||||
try {
|
||||
switch (loadType) {
|
||||
case PluginLoadType.ESMODULE_V1:
|
||||
const plugin_exports = await import(`http://127.0.0.1:1337/plugins/${name}/dist/index.js`);
|
||||
const plugin_exports = await import(`http://127.0.0.1:1337/plugins/${name}/dist/index.js?t=${Date.now()}`);
|
||||
let plugin = plugin_exports.default();
|
||||
|
||||
this.plugins.push({
|
||||
|
||||
+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]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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'"}}' \
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
#!/bin/bash
|
||||
# Adapted from a script provided by Jaynator495.
|
||||
# Make sure to place in home directory, chmod +x plugin-info.sh and then run with ./plugin-info.sh
|
||||
# Define the directory to scan
|
||||
directory_to_scan="~/homebrew/plugins"
|
||||
|
||||
# Loop through each subdirectory (one level deep)
|
||||
for dir in "$directory_to_scan"/*/; do
|
||||
# Check if package.json exists in the subdirectory
|
||||
if [ -f "${dir}package.json" ]; then
|
||||
# Extract name and version from the package.json file using jq
|
||||
name=$(jq -r '.name' "${dir}package.json")
|
||||
version=$(jq -r '.version' "${dir}package.json")
|
||||
|
||||
# Output the name and version
|
||||
echo "Directory: ${dir}"
|
||||
echo "Package Name: $name"
|
||||
echo "Version: $version"
|
||||
echo "-----------------------------"
|
||||
fi
|
||||
done
|
||||
Reference in New Issue
Block a user