mirror of
https://github.com/SteamDeckHomebrew/decky-loader.git
synced 2026-06-13 12:15:09 +03:00
Compare commits
130 Commits
v3.0.0-pre5
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 00e866c39e | |||
| 5490961056 | |||
| d4442f6b0d | |||
| 7a629f1cef | |||
| 7a7ecdac55 | |||
| 232fadd796 | |||
| acaf165219 | |||
| bef7ede91f | |||
| 511dd121bd | |||
| d31c2bf034 | |||
| b7a884f26f | |||
| a477bf6829 | |||
| 1e8bf43e5f | |||
| 259d01d7ec | |||
| a13887a13a | |||
| b97c27aac4 | |||
| 8b8a1cc4d8 | |||
| 7a283c7608 | |||
| 9f586a1b97 | |||
| 789851579b | |||
| 7ea7bc7f9b | |||
| e267ba9135 | |||
| 44bb023b80 | |||
| 86b5567d4e | |||
| 8f41eb93ef | |||
| 670ae7d8a7 | |||
| 01ec1663bc | |||
| b840d75cb8 | |||
| e22ba20d13 | |||
| bff410d98b | |||
| 7188db9877 | |||
| 02e8640ad4 | |||
| e63983dba9 | |||
| cbea1518ed | |||
| 414493eed2 | |||
| 078a9cf33d | |||
| 6e357ceecc | |||
| e4f7546f99 | |||
| 4e5468a353 | |||
| 0b73882012 | |||
| b15392b5f2 | |||
| e646168e31 | |||
| 5ec4a4645d | |||
| b47ad69557 | |||
| efac7bc397 | |||
| ce2f98aa04 | |||
| ede8a18e9b | |||
| 8db3711cab | |||
| ffa3226077 | |||
| dc0e1cfdce | |||
| b3483897e0 | |||
| 543ee3d19e | |||
| 654957cb0c | |||
| 11e5236fa3 | |||
| c1dd1c7296 | |||
| 310dd700ac | |||
| 0c727d64d2 | |||
| 054517595d | |||
| ceb10fd7cf | |||
| 8e50886c48 | |||
| 76677fa877 | |||
| f6144f9634 | |||
| 79bb62a3c4 | |||
| 83f2c94712 | |||
| 25036b065f | |||
| 6bf21bf2ad | |||
| 4b47a8abeb | |||
| 43f22329e1 | |||
| 19e5d84928 | |||
| 86dea2d7c6 | |||
| 4c95484ccb | |||
| 8d2b252e6d | |||
| dbd7488d8f | |||
| cff3ca504d | |||
| 456ccf479a | |||
| 4f05a001fb | |||
| 43aa0497f4 | |||
| 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 | |||
| c1f7ca7f20 | |||
| 6ae6f5ee67 | |||
| 016ed6e998 | |||
| 4842a599e0 | |||
| 927f912eb3 | |||
| 7c9b68c1dd | |||
| e5e75cc16e | |||
| 3656f541e6 | |||
| f2b859b409 | |||
| 93b3919325 | |||
| 7a161a5b83 | |||
| 1265672067 | |||
| b8acc849bc | |||
| 65b6883dcc | |||
| 166c7ea8a7 | |||
| ddc807340c | |||
| 7fc51c8b7d | |||
| 131f0961ff | |||
| 75aa1e4851 | |||
| dcfaf11696 | |||
| 3e50ce65a7 | |||
| adf5a76392 | |||
| e01a700669 | |||
| 4b23a800f6 | |||
| 7045cdea6b | |||
| ef0d449fb6 |
@@ -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
|
||||
|
||||
@@ -41,7 +41,7 @@ jobs:
|
||||
working-directory: ./frontend
|
||||
run: |
|
||||
npm i -g pnpm
|
||||
pnpm i --frozen-lockfile
|
||||
pnpm i --frozen-lockfile --dangerously-allow-all-builds
|
||||
|
||||
- name: Build JS Frontend 🛠️
|
||||
working-directory: ./frontend
|
||||
|
||||
@@ -43,7 +43,7 @@ jobs:
|
||||
working-directory: ./frontend
|
||||
run: |
|
||||
npm i -g pnpm
|
||||
pnpm i --frozen-lockfile
|
||||
pnpm i --frozen-lockfile --dangerously-allow-all-builds
|
||||
|
||||
- name: Build JS Frontend 🛠️
|
||||
working-directory: ./frontend
|
||||
|
||||
@@ -18,7 +18,7 @@ jobs:
|
||||
|
||||
- name: Get changed files
|
||||
id: changed-files
|
||||
uses: tj-actions/changed-files@v41.0.0
|
||||
uses: tj-actions/changed-files@531f5f7d163941f0c1c04e0ff4d8bb243ac4366f
|
||||
with:
|
||||
separator: ","
|
||||
files: |
|
||||
|
||||
@@ -16,7 +16,7 @@ jobs:
|
||||
working-directory: frontend
|
||||
run: |
|
||||
npm i -g pnpm
|
||||
pnpm i --frozen-lockfile
|
||||
pnpm i --frozen-lockfile --dangerously-allow-all-builds
|
||||
|
||||
- name: Run prettier (TypeScript)
|
||||
working-directory: frontend
|
||||
|
||||
@@ -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@v4
|
||||
with:
|
||||
tool_versions: |
|
||||
semver 3.3.0
|
||||
semver 3.4.0
|
||||
|
||||
- name: Get latest release
|
||||
uses: rez0n/actions-github-release@main
|
||||
|
||||
@@ -35,7 +35,7 @@ jobs:
|
||||
working-directory: frontend
|
||||
run: |
|
||||
npm i -g pnpm
|
||||
pnpm i --frozen-lockfile
|
||||
pnpm i --frozen-lockfile --dangerously-allow-all-builds
|
||||
|
||||
- name: Run pyright (Python)
|
||||
uses: jakebailey/pyright-action@v1
|
||||
|
||||
Vendored
+1
-1
@@ -120,7 +120,7 @@
|
||||
"dependsOn": [
|
||||
"checkforsettings"
|
||||
],
|
||||
"command": "ssh ${config:deckuser}@${config:deckip} -p ${config:deckport} ${config:deckkey} 'export PATH=${config:deckdir}/.local/bin:$PATH; export PLUGIN_PATH=${config:deckdir}/homebrew/plugins; export CHOWN_PLUGIN_PATH=0; export LOG_LEVEL=DEBUG; cd ${config:deckdir}/homebrew/dev/pluginloader/backend; echo '${config:deckpass}' | poetry run sh -c \"cd ${config:deckdir}/homebrew/services; sudo -SE env \"PATH=\\$PATH\" python3 ${config:deckdir}/homebrew/dev/pluginloader/backend/main.py\"'",
|
||||
"command": "ssh -t ${config:deckuser}@${config:deckip} -p ${config:deckport} ${config:deckkey} 'export PATH=${config:deckdir}/.local/bin:$PATH; export PLUGIN_PATH=${config:deckdir}/homebrew/plugins; export CHOWN_PLUGIN_PATH=0; export LOG_LEVEL=DEBUG; cd ${config:deckdir}/homebrew/dev/pluginloader/backend; echo '${config:deckpass}' | poetry run sh -c \"cd ${config:deckdir}/homebrew/services; sudo -SE env \"PATH=\\$PATH\" python3 ${config:deckdir}/homebrew/dev/pluginloader/backend/main.py\"'",
|
||||
"problemMatcher": []
|
||||
},
|
||||
{
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<br>
|
||||
Decky Loader
|
||||
<br>
|
||||
<a name="download button" href="https://github.com/SteamDeckHomebrew/decky-installer/releases/latest/download/decky_installer.desktop"><img src="./docs/images/download_button.svg" alt="Download decky" width="350px" style="padding-top: 15px;"></a>
|
||||
<a name="download button" href="https://github.com/SteamDeckHomebrew/decky-installer/releases/latest/download/decky_installer.desktop"><img src="./docs/images/download_button.svg" alt="Download decky" width="150px" style="padding-top: 15px;"></a>
|
||||
</h1>
|
||||
|
||||
<p align="center">
|
||||
@@ -15,9 +15,21 @@
|
||||
<a href="https://deckbrew.xyz/discord"><img src="https://img.shields.io/discord/960281551428522045?color=%235865F2&label=discord" /></a>
|
||||
<br>
|
||||
<br>
|
||||
🌐 <a href="./README.md">English</a> · <a href="./README_zh-Hans.md">简体中文</a>
|
||||
<br>
|
||||
<br>
|
||||
<!-- <img src="https://media.discordapp.net/attachments/966017112244125756/1012466063893610506/main.jpg" alt="Decky screenshot" width="80%">-->
|
||||
</p>
|
||||
|
||||
## 🩵 Backers and Sponsors
|
||||
|
||||
[Become a backer or sponsor](https://opencollective.com/steamdeckhomebrew) to support our work! Contributing to our collective effort will help Decky Loader developers cover the costs of web servers, acquire new development hardware, and more.
|
||||
|
||||
<!-- SPONSORS COMMENTED OUT UNTIL WE GET SOME SPONSORS TO AVOID BLANK SVG SPACE -->
|
||||
<a href="https://opencollective.com/steamdeckhomebrew"><img alt="Steam Deck Homebrew sponsors on Open Collective" src="https://opencollective.com/steamdeckhomebrew/sponsors.svg?button=true&avatarHeight=46&width=750"></a>
|
||||
|
||||
<a href="https://opencollective.com/steamdeckhomebrew"><img alt="Steam Deck Homebrew backers on Open Collective" src="https://opencollective.com/steamdeckhomebrew/backers.svg?button=false&avatarHeight=46&width=750"></a>
|
||||
|
||||
## 📖 About
|
||||
|
||||
Decky Loader is a homebrew plugin launcher for the Steam Deck. It can be used to [stylize your menus](https://github.com/suchmememanyskill/SDH-CssLoader), [change system sounds](https://github.com/EMERALD0874/SDH-AudioLoader), [adjust your screen saturation](https://github.com/libvibrant/vibrantDeck), [change additional system settings](https://github.com/NGnius/PowerTools), and [more](https://plugins.deckbrew.xyz/).
|
||||
@@ -40,7 +52,9 @@ For more information about Decky Loader as well as documentation and development
|
||||
- Sometimes Decky will disappear on SteamOS updates. This can easily be fixed by just re-running the installer and installing the stable branch again. If this doesn't work, try installing the prerelease instead. If that doesn't work, then [check the existing issues](https://github.com/SteamDeckHomebrew/decky-loader/issues) and if there isn't one then you can [file a new issue](https://github.com/SteamDeckHomebrew/decky-loader/issues/new?assignees=&labels=bug&template=bug_report.yml&title=%5BBUG%5D+%3Ctitle%3E).
|
||||
|
||||
## 💾 Installation
|
||||
|
||||
- This installation can be done without an admin/sudo password set.
|
||||
|
||||
1. Prepare a mouse and keyboard if possible.
|
||||
- Keyboards and mice can be connected to the Steam Deck via USB-C or Bluetooth.
|
||||
- Many Bluetooth keyboard and mouse apps are available for iOS and Android. (KDE connect is preinstalled on the steam deck)
|
||||
@@ -54,7 +68,7 @@ For more information about Decky Loader as well as documentation and development
|
||||
1. Either type your admin password or allow Decky to temporarily set your admin password to `Decky!` (this password will be removed after the installer finishes)
|
||||
1. Choose the version of Decky Loader you want to install.
|
||||
- **Latest Release**
|
||||
Intended for most users. This is the latest stable version of Decky Loader.
|
||||
Intended for most users. This is the latest stable version of Decky Loader.
|
||||
- **Latest Pre-Release**
|
||||
Intended for plugin developers. Pre-releases are unlikely to be fully stable but contain the latest changes. For more information on plugin development, please consult [the wiki page](https://wiki.deckbrew.xyz/en/loader-dev/development).
|
||||
1. Open the Return to Gaming Mode shortcut on your desktop.
|
||||
@@ -68,6 +82,7 @@ We are sorry to see you go! If you are considering uninstalling because you are
|
||||
1. Press the <img src="./docs/images/light/steam.svg#gh-dark-mode-only" height=16><img src="./docs/images/dark/steam.svg#gh-light-mode-only" height=16> button and open the Power menu.
|
||||
1. Select "Switch to Desktop".
|
||||
1. Run the installer file again, and select `uninstall decky loader`.
|
||||
|
||||
- There is also a fast uninstall for those who can use Konsole. Run `curl -L https://github.com/SteamDeckHomebrew/decky-installer/releases/latest/download/uninstall.sh | sh` and type your password when prompted.
|
||||
|
||||
## 🚀 Getting Started
|
||||
@@ -95,7 +110,7 @@ Please consult [the wiki page regarding development](https://wiki.deckbrew.xyz/e
|
||||
1. In your clone of the repository, run these commands.
|
||||
```bash
|
||||
cd frontend
|
||||
pnpm i
|
||||
pnpm i # NOTE: you may need to approve esbuild's build script with pnpm approve-builds
|
||||
pnpm run build
|
||||
```
|
||||
1. If you are modifying the UI, these commands will need to be run before deploying the changes to your Steam Deck.
|
||||
@@ -105,7 +120,7 @@ Please consult [the wiki page regarding development](https://wiki.deckbrew.xyz/e
|
||||
⚠️ If you are recieving build errors due to an out of date library, you should run this command inside of your repository.
|
||||
|
||||
```bash
|
||||
pnpm update decky-frontend-lib --latest
|
||||
pnpm update @decky/ui --latest
|
||||
```
|
||||
|
||||
Source control and deploying plugins are left to each respective contributor for the cloned repos in order to keep dependencies up to date.
|
||||
|
||||
@@ -0,0 +1,129 @@
|
||||
<h1 align="center">
|
||||
<a name="logo" href="https://deckbrew.xyz/"><img src="https://deckbrew.xyz/static/icon-45ca1f5aea376a9ad37e92db906f283e.png" alt="Deckbrew logo" width="200"></a>
|
||||
<br>
|
||||
Decky Loader
|
||||
<br>
|
||||
<a name="download button" href="https://github.com/SteamDeckHomebrew/decky-installer/releases/latest/download/decky_installer.desktop"><img src="./docs/images/download_button.svg" alt="Download decky" width="150px" style="padding-top: 15px;"></a>
|
||||
</h1>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://github.com/SteamDeckHomebrew/decky-loader/releases"><img src="https://img.shields.io/github/downloads/SteamDeckHomebrew/decky-loader/total" /></a>
|
||||
<a href="https://github.com/SteamDeckHomebrew/decky-loader/stargazers"><img src="https://img.shields.io/github/stars/SteamDeckHomebrew/decky-loader" /></a>
|
||||
<a href="https://github.com/SteamDeckHomebrew/decky-loader/commits/main"><img src="https://img.shields.io/github/last-commit/SteamDeckHomebrew/decky-loader.svg" /></a>
|
||||
<a href="https://weblate.werwolv.net/engage/decky/"><img src="https://weblate.werwolv.net/widgets/decky/-/decky/svg-badge.svg" alt="Translation status" /></a>
|
||||
<a href="https://github.com/SteamDeckHomebrew/decky-loader/blob/main/LICENSE"><img src="https://img.shields.io/github/license/SteamDeckHomebrew/decky-loader" /></a>
|
||||
<a href="https://deckbrew.xyz/discord"><img src="https://img.shields.io/discord/960281551428522045?color=%235865F2&label=discord" /></a>
|
||||
<br>
|
||||
<br>
|
||||
🌐 <a href="./README.md">English</a> · <a href="./README_zh-Hans.md">简体中文</a>
|
||||
<br>
|
||||
<br>
|
||||
<!-- <img src="https://media.discordapp.net/attachments/966017112244125756/1012466063893610506/main.jpg" alt="Decky screenshot" width="80%">-->
|
||||
</p>
|
||||
|
||||
## 🩵 赞助者
|
||||
|
||||
[成为赞助者](https://opencollective.com/steamdeckhomebrew)来支持我们的工作!向我们的集体项目捐款将帮助 Decky Loader 开发者支付网络服务器费用、购买新的开发硬件等。
|
||||
|
||||
<a href="https://opencollective.com/steamdeckhomebrew"><img alt="Steam Deck Homebrew sponsors on Open Collective" src="https://opencollective.com/steamdeckhomebrew/sponsors.svg?button=true&avatarHeight=46&width=750"></a>
|
||||
|
||||
<a href="https://opencollective.com/steamdeckhomebrew"><img alt="Steam Deck Homebrew backers on Open Collective" src="https://opencollective.com/steamdeckhomebrew/backers.svg?button=false&avatarHeight=46&width=750"></a>
|
||||
|
||||
## 📖 关于
|
||||
|
||||
Decky Loader 是一款用于 Steam Deck 的自制插件启动器。它可以用来[美化菜单界面](https://github.com/suchmememanyskill/SDH-CssLoader)、[更改系统音效](https://github.com/EMERALD0874/SDH-AudioLoader)、[调整屏幕饱和度](https://github.com/libvibrant/vibrantDeck)、[修改更多系统设置](https://github.com/NGnius/PowerTools),以及[更多功能](https://plugins.deckbrew.xyz/)。
|
||||
|
||||
有关 Decky Loader 的更多信息、文档和开发工具,请访问[我们的维基](https://wiki.deckbrew.xyz)。
|
||||
|
||||
### 🎨 功能特性
|
||||
|
||||
🧹 干净地注入和加载多个插件。
|
||||
🔒 在系统更新和重启后仍然保持安装状态。
|
||||
🔗 允许插件与启动器之间进行双向通信。
|
||||
🐍 支持从 TypeScript React 中运行 Python 函数。
|
||||
🌐 允许插件发起完全绕过 CORS 的 fetch 请求。
|
||||
|
||||
### 🤔 常见问题
|
||||
|
||||
- Syncthing 可能会占用 Steam Deck 上的 8080 端口,而 Decky Loader 需要该端口才能运行。如果您将 Syncthing 作为服务使用,请将其端口更改为其他端口。
|
||||
- 建议将 Syncthing 的端口改为 8384。
|
||||
- 如果您使用的任何软件占用了 1337 或 8080 端口,请将其端口更改为其他端口或卸载该软件。
|
||||
- 有时 Decky 会在 SteamOS 更新后消失。只需重新运行安装程序并再次安装稳定版即可轻松解决。如果这不起作用,请尝试安装预发布版。如果还是不行,请[查看现有问题](https://github.com/SteamDeckHomebrew/decky-loader/issues),如果没有相关问题,您可以[提交一个新问题](https://github.com/SteamDeckHomebrew/decky-loader/issues/new?assignees=&labels=bug&template=bug_report.yml&title=%5BBUG%5D+%3Ctitle%3E)。
|
||||
|
||||
## 💾 安装
|
||||
|
||||
- 安装过程无需设置管理员/sudo 密码。
|
||||
|
||||
1. 如果可能,请准备鼠标和键盘。
|
||||
- 键盘和鼠标可以通过 USB-C 或蓝牙连接到 Steam Deck。
|
||||
- iOS 和 Android 上有许多蓝牙键盘和鼠标应用可用。(Steam Deck 上预安装了 KDE Connect)
|
||||
- Steam Link 应用可在 [Windows](https://media.steampowered.com/steamlink/windows/latest/SteamLink.zip)、[macOS](https://apps.apple.com/us/app/steam-link/id1246969117) 和 [Linux](https://flathub.org/apps/details/com.valvesoftware.SteamLink) 上使用。它可以很好地替代远程桌面。
|
||||
- 如果您没有其他选择,可以使用右侧触控板作为鼠标,并按 <img src="./docs/images/light/steam.svg#gh-dark-mode-only" height=16><img src="./docs/images/dark/steam.svg#gh-light-mode-only" height=16>+<img src="./docs/images/light/x.svg#gh-dark-mode-only" height=16><img src="./docs/images/dark/x.svg#gh-light-mode-only" height=16> 打开屏幕键盘。
|
||||
1. 按下 <img src="./docs/images/light/steam.svg#gh-dark-mode-only" height=16><img src="./docs/images/dark/steam.svg#gh-light-mode-only" height=16> 按钮并打开电源菜单。
|
||||
1. 选择"切换到桌面模式"。
|
||||
1. 在您选择的浏览器中访问此 GitHub 页面。
|
||||
1. 下载[安装程序文件](https://github.com/SteamDeckHomebrew/decky-installer/releases/latest/download/decky_installer.desktop)。(如果使用 Firefox,文件将命名为 `decky_installer.desktop.download`,请在运行前将其重命名为 `decky_installer.desktop`)
|
||||
1. 将文件拖到桌面上,然后双击运行。
|
||||
1. 输入您的管理员密码或允许 Decky 临时将您的管理员密码设置为 `Decky!`(安装程序完成后将删除此密码)。
|
||||
1. 选择您要安装的 Decky Loader 版本。
|
||||
- **最新正式版**
|
||||
面向大多数用户。这是 Decky Loader 的最新稳定版本。
|
||||
- **最新预发布版**
|
||||
面向插件开发者。预发布版可能尚未完全稳定,但包含最新更改。有关插件开发的更多信息,请参阅[维基页面](https://wiki.deckbrew.xyz/en/loader-dev/development)。
|
||||
1. 打开桌面上的"返回游戏模式"快捷方式。
|
||||
|
||||
- 对于可以使用 Konsole 的用户,还有一种快速安装方式。运行 `curl -L https://github.com/SteamDeckHomebrew/decky-installer/releases/latest/download/install_release.sh | sh` 并在提示时输入密码。
|
||||
|
||||
### 👋 卸载
|
||||
|
||||
很抱歉看到您离开!如果您因为遇到问题而考虑卸载,请考虑[提交问题](https://github.com/SteamDeckHomebrew/decky-loader/issues)或[加入我们的 Discord](https://deckbrew.xyz/discord),以便我们帮助您和其他用户。
|
||||
|
||||
1. 按下 <img src="./docs/images/light/steam.svg#gh-dark-mode-only" height=16><img src="./docs/images/dark/steam.svg#gh-light-mode-only" height=16> 按钮并打开电源菜单。
|
||||
1. 选择"切换到桌面模式"。
|
||||
1. 再次运行安装程序文件,然后选择 `uninstall decky loader`。
|
||||
|
||||
- 对于可以使用 Konsole 的用户,还有一种快速卸载方式。运行 `curl -L https://github.com/SteamDeckHomebrew/decky-installer/releases/latest/download/uninstall.sh | sh` 并在提示时输入密码。
|
||||
|
||||
## 🚀 入门指南
|
||||
|
||||
现在您已经安装了 Decky Loader,可以开始使用插件了。每个插件由不同的开发者维护,具有各自的用途,但大多数遵循以下通用结构。
|
||||
|
||||
### 📦 插件
|
||||
|
||||
1. 按下 <img src="./docs/images/light/qam.svg#gh-dark-mode-only" height=16><img src="./docs/images/dark/qam.svg#gh-light-mode-only" height=16> 按钮并导航到 <img src="./docs/images/light/plug.svg#gh-dark-mode-only" height=16><img src="./docs/images/dark/plug.svg#gh-light-mode-only" height=16> 图标。这是 Decky 菜单,用于与插件和启动器本身交互。
|
||||
1. 选择 <img src="./docs/images/light/store.svg#gh-dark-mode-only" height=16><img src="./docs/images/dark/store.svg#gh-light-mode-only" height=16> 图标打开插件浏览器。在这里您可以查找和安装插件。
|
||||
- 您也可以在设置菜单中通过 URL 安装。我们不建议安装来自不可信来源的插件。
|
||||
1. 要安装插件,请在您想要的插件上选择"安装"按钮。您也可以从下拉菜单中选择一个版本,但不建议这样做。
|
||||
1. 要更新、卸载和重新加载插件,请导航到 Decky 菜单并选择 <img src="./docs/images/light/gear.svg#gh-dark-mode-only" height=16><img src="./docs/images/dark/gear.svg#gh-light-mode-only" height=16> 图标。
|
||||
- 请注意,卸载插件只会移除其插件文件,而不会删除它可能创建的任何其他文件。
|
||||
|
||||
### 🛠️ 插件开发
|
||||
|
||||
目前还没有完整的插件开发文档。不过,一个好的起点是[插件模板仓库](https://github.com/SteamDeckHomebrew/decky-plugin-template)。如果您有任何问题,请考虑[加入我们的 Discord](https://deckbrew.xyz/discord)。
|
||||
|
||||
### 🤝 贡献
|
||||
|
||||
有关安装 Decky Loader 开发版本的更多信息,请参阅[有关开发的维基页面](https://wiki.deckbrew.xyz/en/loader-dev/development)。您还可以通过观看[此 YouTube 教程](https://youtu.be/1IAbZte8e7E?t=112)在 Windows 或 Linux 计算机上安装 Steam Deck UI 进行测试。
|
||||
|
||||
1. 在开始您的 PR 之前,使用最新的 main 分支提交克隆仓库。
|
||||
1. 在您的仓库克隆中,运行以下命令。
|
||||
```bash
|
||||
cd frontend
|
||||
pnpm i # 注意:您可能需要使用 pnpm approve-builds 批准 esbuild 的构建脚本
|
||||
pnpm run build
|
||||
```
|
||||
1. 如果您正在修改 UI,则需要在部署更改到 Steam Deck 之前运行这些命令。
|
||||
1. 使用 VS Code 任务或 `deck.sh` 脚本将您的更改部署到 Steam Deck 以进行测试。
|
||||
1. 您将使用 Python 脚本版本测试您的更改。每次都需要构建、部署和重新加载。
|
||||
|
||||
⚠️ 如果您因库过时而收到构建错误,请在仓库内运行此命令。
|
||||
|
||||
```bash
|
||||
pnpm update @decky/ui --latest
|
||||
```
|
||||
|
||||
源代码管理和插件部署留给克隆仓库的各自贡献者处理,以保持依赖项为最新版本。
|
||||
|
||||
## 📜 鸣谢
|
||||
|
||||
插件加载器概念的最初想法基于 [marios8543 的 Steam Deck UI Inject 项目](https://github.com/marios8543/steamdeck-ui-inject)的工作。
|
||||
@@ -18,9 +18,10 @@ from enum import IntEnum
|
||||
from typing import Dict, List, TypedDict
|
||||
|
||||
# Local modules
|
||||
from .localplatform.localplatform import chown, chmod
|
||||
from .localplatform.localplatform import chown, chmod, get_chown_plugin_path
|
||||
from .loader import Loader, Plugins
|
||||
from .helpers import get_ssl_context, download_remote_binary_to_path
|
||||
from .enums import UserType
|
||||
from .settings import SettingsManager
|
||||
|
||||
logger = getLogger("Browser")
|
||||
@@ -29,6 +30,8 @@ class PluginInstallType(IntEnum):
|
||||
INSTALL = 0
|
||||
REINSTALL = 1
|
||||
UPDATE = 2
|
||||
DOWNGRADE = 3
|
||||
OVERWRITE = 4
|
||||
|
||||
class PluginInstallRequest(TypedDict):
|
||||
name: str
|
||||
@@ -58,13 +61,6 @@ class PluginBrowser:
|
||||
return False
|
||||
zip_file = ZipFile(zip)
|
||||
zip_file.extractall(self.plugin_path)
|
||||
plugin_folder = self.find_plugin_folder(name)
|
||||
assert plugin_folder is not None
|
||||
plugin_dir = path.join(self.plugin_path, plugin_folder)
|
||||
|
||||
if not chown(plugin_dir) or not chmod(plugin_dir, 555):
|
||||
logger.error(f"chown/chmod exited with a non-zero exit code")
|
||||
return False
|
||||
return True
|
||||
|
||||
async def _download_remote_binaries_for_plugin_with_name(self, pluginBasePath: str):
|
||||
@@ -73,6 +69,8 @@ class PluginBrowser:
|
||||
packageJsonPath = path.join(pluginBasePath, 'package.json')
|
||||
pluginBinPath = path.join(pluginBasePath, 'bin')
|
||||
|
||||
logger.debug(f"Checking package.json at {packageJsonPath}")
|
||||
|
||||
if access(packageJsonPath, R_OK):
|
||||
with open(packageJsonPath, "r", encoding="utf-8") as f:
|
||||
packageJson = json.load(f)
|
||||
@@ -81,6 +79,7 @@ class PluginBrowser:
|
||||
chmod(pluginBasePath, 777)
|
||||
if access(pluginBasePath, W_OK):
|
||||
if not path.exists(pluginBinPath):
|
||||
logger.debug(f"Creating bin directory at {pluginBinPath}")
|
||||
mkdir(pluginBinPath)
|
||||
if not access(pluginBinPath, W_OK):
|
||||
chmod(pluginBinPath, 777)
|
||||
@@ -91,15 +90,14 @@ class PluginBrowser:
|
||||
binName = remoteBinary["name"]
|
||||
binURL = remoteBinary["url"]
|
||||
binHash = remoteBinary["sha256hash"]
|
||||
logger.info(f"Attempting to download {binName} from {binURL}")
|
||||
if not await download_remote_binary_to_path(binURL, binHash, path.join(pluginBinPath, binName)):
|
||||
rv = False
|
||||
raise Exception(f"Error Downloading Remote Binary {binName}@{binURL} with hash {binHash} to {path.join(pluginBinPath, binName)}")
|
||||
|
||||
chown(self.plugin_path)
|
||||
chmod(pluginBasePath, 555)
|
||||
else:
|
||||
rv = True
|
||||
logger.debug(f"No Remote Binaries to Download")
|
||||
logger.info(f"No Remote Binaries to Download")
|
||||
|
||||
except Exception as e:
|
||||
rv = False
|
||||
@@ -118,6 +116,25 @@ class PluginBrowser:
|
||||
return folder
|
||||
except:
|
||||
logger.debug(f"skipping {folder}")
|
||||
|
||||
def set_plugin_dir_permissions(self, plugin_dir: str) -> bool:
|
||||
plugin_json_path = path.join(plugin_dir, 'plugin.json')
|
||||
logger.debug(f"Checking plugin.json at {plugin_json_path}")
|
||||
|
||||
root_plugin = False
|
||||
|
||||
if access(plugin_json_path, R_OK):
|
||||
with open(plugin_json_path, "r", encoding="utf-8") as f:
|
||||
plugin_json = json.load(f)
|
||||
if "flags" in plugin_json and "root" in plugin_json["flags"]:
|
||||
root_plugin = True
|
||||
|
||||
logger.debug("root_plugin %d, dir %s", root_plugin, plugin_dir)
|
||||
if get_chown_plugin_path():
|
||||
return chown(plugin_dir, UserType.EFFECTIVE_USER if root_plugin else UserType.HOST_USER, True) and chown(plugin_dir, UserType.EFFECTIVE_USER, False) and chmod(plugin_dir, 755) and chown(plugin_json_path, UserType.EFFECTIVE_USER, False) and chmod(plugin_json_path, 755)
|
||||
else:
|
||||
logger.debug("chown disabled by environment")
|
||||
return True
|
||||
|
||||
async def uninstall_plugin(self, name: str):
|
||||
if self.loader.watcher:
|
||||
@@ -133,6 +150,7 @@ class PluginBrowser:
|
||||
# plugins_snapshot = self.plugins.copy()
|
||||
# snapshot_string = pformat(plugins_snapshot)
|
||||
# logger.debug("current plugins: %s", snapshot_string)
|
||||
|
||||
if name in self.plugins:
|
||||
logger.debug("Plugin %s was found", name)
|
||||
await self.plugins[name].stop(uninstall=True)
|
||||
@@ -157,8 +175,16 @@ class PluginBrowser:
|
||||
# Will be set later in code
|
||||
res_zip = None
|
||||
|
||||
# Check if plugin is installed
|
||||
# Check if plugin was already installed before this
|
||||
isInstalled = False
|
||||
|
||||
try:
|
||||
pluginFolderPath = self.find_plugin_folder(name)
|
||||
if pluginFolderPath:
|
||||
isInstalled = True
|
||||
except:
|
||||
logger.error(f"Failed to determine if {name} is already installed, continuing anyway.")
|
||||
|
||||
# Preserve plugin order before removing plugin (uninstall alters the order and removes the plugin from the list)
|
||||
current_plugin_order = self.settings.getSetting("pluginOrder")[:]
|
||||
if self.loader.watcher:
|
||||
@@ -186,7 +212,7 @@ class PluginBrowser:
|
||||
else:
|
||||
logger.fatal(f"Could not fetch from URL. {await res.text()}")
|
||||
|
||||
await self.loader.ws.emit("loader/plugin_download_info", 80, "Store.download_progress_info.increment_count")
|
||||
await self.loader.ws.emit("loader/plugin_download_info", 70, "Store.download_progress_info.increment_count")
|
||||
storeUrl = ""
|
||||
match self.settings.getSetting("store", 0):
|
||||
case 0: storeUrl = "https://plugins.deckbrew.xyz/plugins" # default
|
||||
@@ -199,7 +225,7 @@ class PluginBrowser:
|
||||
if res.status != 200:
|
||||
logger.error(f"Server did not accept install count increment request. code: {res.status}")
|
||||
|
||||
await self.loader.ws.emit("loader/plugin_download_info", 85, "Store.download_progress_info.parse_zip")
|
||||
await self.loader.ws.emit("loader/plugin_download_info", 75, "Store.download_progress_info.parse_zip")
|
||||
if res_zip and version == "dev":
|
||||
with ZipFile(res_zip) as plugin_zip:
|
||||
plugin_json_list = [file for file in plugin_zip.namelist() if file.endswith("/plugin.json") and file.count("/") == 1]
|
||||
@@ -213,14 +239,19 @@ class PluginBrowser:
|
||||
return
|
||||
|
||||
else:
|
||||
name = sub(r"/.+$", "", plugin_json_list[0])
|
||||
|
||||
try:
|
||||
pluginFolderPath = self.find_plugin_folder(name)
|
||||
if pluginFolderPath:
|
||||
isInstalled = True
|
||||
except:
|
||||
logger.error(f"Failed to determine if {name} is already installed, continuing anyway.")
|
||||
plugin_json_file = plugin_json_list[0]
|
||||
name = sub(r"/.+$", "", plugin_json_file)
|
||||
try:
|
||||
with plugin_zip.open(plugin_json_file) as f:
|
||||
plugin_json_data = json.loads(f.read().decode('utf-8'))
|
||||
plugin_name_from_plugin_json = plugin_json_data.get('name')
|
||||
if plugin_name_from_plugin_json and plugin_name_from_plugin_json.strip():
|
||||
logger.info(f"Extracted plugin name from {plugin_json_file}: {plugin_name_from_plugin_json}")
|
||||
name = plugin_name_from_plugin_json
|
||||
else:
|
||||
logger.warning(f"Nonexistent or invalid 'name' key value in {plugin_json_file}. Falling back to extracting from path.")
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to read or parse {plugin_json_file}: {str(e)}. Falling back to extracting from path.")
|
||||
|
||||
# Check to make sure we got the file
|
||||
if res_zip is None:
|
||||
@@ -229,7 +260,7 @@ class PluginBrowser:
|
||||
|
||||
# If plugin is installed, uninstall it
|
||||
if isInstalled:
|
||||
await self.loader.ws.emit("loader/plugin_download_info", 90, "Store.download_progress_info.uninstalling_previous")
|
||||
await self.loader.ws.emit("loader/plugin_download_info", 80, "Store.download_progress_info.uninstalling_previous")
|
||||
try:
|
||||
logger.debug("Uninstalling existing plugin...")
|
||||
await self.uninstall_plugin(name)
|
||||
@@ -237,7 +268,7 @@ class PluginBrowser:
|
||||
logger.error(f"Plugin {name} could not be uninstalled.")
|
||||
|
||||
|
||||
await self.loader.ws.emit("loader/plugin_download_info", 95, "Store.download_progress_info.installing_plugin")
|
||||
await self.loader.ws.emit("loader/plugin_download_info", 90, "Store.download_progress_info.installing_plugin")
|
||||
# Install the plugin
|
||||
logger.debug("Unzipping...")
|
||||
ret = self._unzip_to_plugin_dir(res_zip, name, hash)
|
||||
@@ -245,8 +276,9 @@ class PluginBrowser:
|
||||
plugin_folder = self.find_plugin_folder(name)
|
||||
assert plugin_folder is not None
|
||||
plugin_dir = path.join(self.plugin_path, plugin_folder)
|
||||
#TODO count again from 0% to 100% quickly for this one if it does anything
|
||||
await self.loader.ws.emit("loader/plugin_download_info", 95, "Store.download_progress_info.download_remote")
|
||||
ret = await self._download_remote_binaries_for_plugin_with_name(plugin_dir)
|
||||
chown_ret = self.set_plugin_dir_permissions(plugin_dir)
|
||||
if ret:
|
||||
logger.info(f"Installed {name} (Version: {version})")
|
||||
if name in self.loader.plugins:
|
||||
@@ -259,8 +291,12 @@ class PluginBrowser:
|
||||
self.settings.setSetting("pluginOrder", current_plugin_order)
|
||||
logger.debug("Plugin %s was added to the pluginOrder setting", name)
|
||||
await self.loader.import_plugin(path.join(plugin_dir, "main.py"), plugin_folder)
|
||||
elif not chown_ret:
|
||||
logger.error("Could not chown plugin")
|
||||
return
|
||||
else:
|
||||
logger.fatal(f"Failed Downloading Remote Binaries")
|
||||
logger.error("Could not download remote binaries")
|
||||
return
|
||||
else:
|
||||
logger.fatal(f"SHA-256 Mismatch!!!! {name} (Version: {version})")
|
||||
if self.loader.watcher:
|
||||
@@ -311,4 +347,9 @@ class PluginBrowser:
|
||||
plugin_order.remove(name)
|
||||
self.settings.setSetting("pluginOrder", plugin_order)
|
||||
|
||||
disabled_plugins: List[str] = self.settings.getSetting("disabled_plugins", [])
|
||||
if name in disabled_plugins:
|
||||
disabled_plugins.remove(name)
|
||||
self.settings.setSetting("disabled_plugins", disabled_plugins)
|
||||
|
||||
logger.debug("Removed any settings for plugin %s", name)
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
from enum import IntEnum
|
||||
|
||||
class UserType(IntEnum):
|
||||
HOST_USER = 1
|
||||
EFFECTIVE_USER = 2
|
||||
ROOT = 3
|
||||
HOST_USER = 1 # usually deck
|
||||
EFFECTIVE_USER = 2 # usually root
|
||||
|
||||
class PluginLoadType(IntEnum):
|
||||
LEGACY_EVAL_IIFE = 0 # legacy, uses legacy serverAPI
|
||||
|
||||
@@ -4,7 +4,6 @@ import uuid
|
||||
import os
|
||||
import subprocess
|
||||
from hashlib import sha256
|
||||
from io import BytesIO
|
||||
import importlib.metadata
|
||||
|
||||
import certifi
|
||||
@@ -24,6 +23,7 @@ csrf_token = str(uuid.uuid4())
|
||||
ssl_ctx = ssl.create_default_context(cafile=certifi.where())
|
||||
|
||||
assets_regex = re.compile("^/plugins/.*/assets/.*")
|
||||
data_regex = re.compile("^/plugins/.*/data/.*")
|
||||
dist_regex = re.compile("^/plugins/.*/dist/.*")
|
||||
frontend_regex = re.compile("^/frontend/.*")
|
||||
logger = getLogger("Main")
|
||||
@@ -46,12 +46,16 @@ async def csrf_middleware(request: Request, handler: Handler):
|
||||
str(request.rel_url.path) == "/fetch" or \
|
||||
str(request.rel_url.path) == "/ws" 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 \
|
||||
frontend_regex.match(str(request.rel_url)):
|
||||
|
||||
return await handler(request)
|
||||
return Response(text='Forbidden', status=403)
|
||||
|
||||
def create_inject_script(script: str) -> str:
|
||||
return "try{if (window.deckyHasLoaded){setTimeout(() => SteamClient.Browser.RestartJSContext(), 100)}else{window.deckyHasLoaded = true;(async()=>{try{await import('http://localhost:1337/frontend/%s?v=%s')}catch(e){console.error(e)};})();}}catch(e){console.error(e)}" % (script, get_loader_version(), )
|
||||
|
||||
# Get the default homebrew path unless a home_path is specified. home_path argument is deprecated
|
||||
def get_homebrew_path() -> str:
|
||||
return localplatform.get_unprivileged_path()
|
||||
@@ -67,6 +71,9 @@ def get_loader_version() -> str:
|
||||
try:
|
||||
# Normalize Python-style version to conform to Decky style
|
||||
v = Version(importlib.metadata.version("decky_loader"))
|
||||
if v.major == 0 and v.minor == 0 and v.micro == 0:
|
||||
# We are probably running from source
|
||||
return "dev"
|
||||
|
||||
version_str = f'v{v.major}.{v.minor}.{v.micro}'
|
||||
|
||||
@@ -78,7 +85,7 @@ def get_loader_version() -> str:
|
||||
|
||||
return version_str
|
||||
except Exception as e:
|
||||
logger.warn(f"Failed to execute get_loader_version(): {str(e)}")
|
||||
logger.warning(f"Failed to execute get_loader_version(): {str(e)}")
|
||||
return "unknown"
|
||||
|
||||
user_agent = f"Decky/{get_loader_version()} (https://decky.xyz)"
|
||||
@@ -90,9 +97,13 @@ def get_system_pythonpaths() -> list[str]:
|
||||
proc = subprocess.run(["python3" if localplatform.ON_LINUX else "python", "-c", "import sys; print('\\n'.join(x for x in sys.path if x))"],
|
||||
# TODO make this less insane
|
||||
capture_output=True, user=localplatform.localplatform._get_user_id() if localplatform.ON_LINUX else None, env={} if localplatform.ON_LINUX else None) # pyright: ignore [reportPrivateUsage]
|
||||
return [x.strip() for x in proc.stdout.decode().strip().split("\n")]
|
||||
|
||||
proc.check_returncode()
|
||||
|
||||
versions = [x.strip() for x in proc.stdout.decode().strip().split("\n")]
|
||||
return [x for x in versions if x and not x.isspace()]
|
||||
except Exception as e:
|
||||
logger.warn(f"Failed to execute get_system_pythonpaths(): {str(e)}")
|
||||
logger.warning(f"Failed to execute get_system_pythonpaths(): {str(e)}")
|
||||
return []
|
||||
|
||||
# Download Remote Binaries to local Plugin
|
||||
@@ -102,19 +113,20 @@ async def download_remote_binary_to_path(url: str, binHash: str, path: str) -> b
|
||||
if os.access(os.path.dirname(path), os.W_OK):
|
||||
async with ClientSession() as client:
|
||||
res = await client.get(url, ssl=get_ssl_context())
|
||||
if res.status == 200:
|
||||
data = BytesIO(await res.read())
|
||||
remoteHash = sha256(data.getbuffer()).hexdigest()
|
||||
if binHash == remoteHash:
|
||||
data.seek(0)
|
||||
with open(path, 'wb') as f:
|
||||
f.write(data.getbuffer())
|
||||
rv = True
|
||||
if res.status == 200:
|
||||
logger.debug("Download attempt of URL: " + url)
|
||||
data = await res.read()
|
||||
remoteHash = sha256(data).hexdigest()
|
||||
if binHash == remoteHash:
|
||||
with open(path, 'wb') as f:
|
||||
f.write(data)
|
||||
rv = True
|
||||
else:
|
||||
raise Exception(f"Fatal Error: Hash Mismatch for remote binary {path}@{url}")
|
||||
else:
|
||||
raise Exception(f"Fatal Error: Hash Mismatch for remote binary {path}@{url}")
|
||||
else:
|
||||
rv = False
|
||||
except:
|
||||
rv = False
|
||||
except Exception as e:
|
||||
logger.error("Error during download " + str(e))
|
||||
rv = False
|
||||
|
||||
return rv
|
||||
@@ -169,7 +181,8 @@ def get_user_group_id() -> int:
|
||||
|
||||
# Get the default home path unless a user is specified
|
||||
def get_home_path(username: str | None = None) -> str:
|
||||
return localplatform.get_home_path(UserType.ROOT if username == "root" else UserType.HOST_USER)
|
||||
# TODO hardcoded root is kinda a hack
|
||||
return localplatform.get_home_path(UserType.EFFECTIVE_USER if username == "root" else UserType.HOST_USER)
|
||||
|
||||
async def is_systemd_unit_active(unit_name: str) -> bool:
|
||||
return await localplatform.service_active(unit_name)
|
||||
|
||||
@@ -46,7 +46,7 @@ class Tab:
|
||||
async for message in self.websocket:
|
||||
data = message.json()
|
||||
yield data
|
||||
logger.warn(f"The Tab {self.title} socket has been disconnected while listening for messages.")
|
||||
logger.warning(f"The Tab {self.title} socket has been disconnected while listening for messages.")
|
||||
await self.close_websocket()
|
||||
|
||||
async def _send_devtools_cmd(self, dc: Dict[str, Any], receive: bool = True):
|
||||
@@ -381,10 +381,10 @@ async def get_tabs() -> List[Tab]:
|
||||
na = True
|
||||
await sleep(5)
|
||||
except ClientOSError:
|
||||
logger.warn(f"The request to {BASE_ADDRESS}/json was reset")
|
||||
logger.warning(f"The request to {BASE_ADDRESS}/json was reset")
|
||||
await sleep(1)
|
||||
except TimeoutError:
|
||||
logger.warn(f"The request to {BASE_ADDRESS}/json timed out")
|
||||
logger.warning(f"The request to {BASE_ADDRESS}/json timed out")
|
||||
await sleep(1)
|
||||
else:
|
||||
break
|
||||
@@ -415,7 +415,7 @@ CLOSEABLE_URLS = ["about:blank", "data:text/html,%3Cbody%3E%3C%2Fbody%3E"] # Clo
|
||||
DO_NOT_CLOSE_URLS = ["Valve Steam Gamepad/default", "Valve%20Steam%20Gamepad"] # Steam Big Picture Mode tab
|
||||
|
||||
def tab_is_gamepadui(t: Tab) -> bool:
|
||||
return "https://steamloopback.host/routes/" in t.url and t.title in SHARED_CTX_NAMES
|
||||
return ("https://steamloopback.host/routes/" in t.url or "https://steamloopback.host/index.html" in t.url) and t.title in SHARED_CTX_NAMES
|
||||
|
||||
async def get_gamepadui_tab() -> Tab:
|
||||
tabs = await get_tabs()
|
||||
|
||||
@@ -8,6 +8,7 @@ from typing import Any, Tuple, Dict, cast
|
||||
|
||||
from aiohttp import web
|
||||
from os.path import exists
|
||||
from decky_loader.helpers import get_homebrew_path
|
||||
from watchdog.events import RegexMatchingEventHandler, FileSystemEvent
|
||||
from watchdog.observers import Observer
|
||||
|
||||
@@ -77,6 +78,7 @@ class Loader:
|
||||
self.live_reload = live_reload
|
||||
self.reload_queue: ReloadQueue = Queue()
|
||||
self.loop.create_task(self.handle_reloads())
|
||||
self.context: PluginManager = server_instance
|
||||
|
||||
if live_reload:
|
||||
self.observer = Observer()
|
||||
@@ -91,6 +93,7 @@ class Loader:
|
||||
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}/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)
|
||||
@@ -104,10 +107,15 @@ class Loader:
|
||||
async def enable_reload_wait(self):
|
||||
if self.live_reload:
|
||||
await sleep(10)
|
||||
if self.watcher:
|
||||
if self.watcher and self.live_reload:
|
||||
self.logger.info("Hot reload enabled")
|
||||
self.watcher.disabled = False
|
||||
|
||||
async def disable_reload(self):
|
||||
if self.watcher:
|
||||
self.watcher.disabled = True
|
||||
self.live_reload = False
|
||||
|
||||
async def handle_frontend_assets(self, request: web.Request):
|
||||
file = Path(__file__).parent.joinpath("static").joinpath(request.match_info["path"])
|
||||
return web.FileResponse(file, headers={"Cache-Control": "no-cache"})
|
||||
@@ -123,7 +131,7 @@ class Loader:
|
||||
|
||||
async def get_plugins(self):
|
||||
plugins = list(self.plugins.values())
|
||||
return [{"name": str(i), "version": i.version, "load_type": i.load_type} for i in plugins]
|
||||
return [{"name": str(i), "version": i.version, "load_type": i.load_type, "disabled": i.disabled} for i in plugins]
|
||||
|
||||
async def handle_plugin_dist(self, request: web.Request):
|
||||
plugin = self.plugins[request.match_info["plugin_name"]]
|
||||
@@ -137,6 +145,13 @@ class Loader:
|
||||
|
||||
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):
|
||||
plugin = self.plugins[request.match_info["plugin_name"]]
|
||||
|
||||
@@ -150,6 +165,10 @@ class Loader:
|
||||
await self.ws.emit(f"loader/plugin_event", {"plugin": plugin.name, "event": event, "args": args})
|
||||
|
||||
plugin = PluginWrapper(file, plugin_directory, self.plugin_path, plugin_emitted_event)
|
||||
if hasattr(self.context, "utilities") and plugin.name in await self.context.utilities.get_setting("disabled_plugins",[]):
|
||||
plugin.disabled = True
|
||||
self.plugins[plugin.name] = plugin
|
||||
return
|
||||
if plugin.name in self.plugins:
|
||||
if not "debug" in plugin.flags and refresh:
|
||||
self.logger.info(f"Plugin {plugin.name} is already loaded and has requested to not be re-loaded")
|
||||
@@ -169,7 +188,7 @@ class Loader:
|
||||
print_exc()
|
||||
|
||||
async def dispatch_plugin(self, name: str, version: str | None, load_type: int = PluginLoadType.ESMODULE_V1.value):
|
||||
await self.ws.emit("loader/import_plugin", name, version, load_type)
|
||||
await self.ws.emit("loader/import_plugin", name, version, load_type, True, 15000)
|
||||
|
||||
async def import_plugins(self):
|
||||
self.logger.info(f"import plugins from {self.plugin_path}")
|
||||
@@ -211,4 +230,4 @@ class Loader:
|
||||
async def handle_plugin_backend_reload(self, plugin_name: str):
|
||||
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))
|
||||
|
||||
@@ -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": "няма бележки за промените в тази версия",
|
||||
|
||||
@@ -52,7 +52,9 @@
|
||||
"MultiplePluginsInstallModal": {
|
||||
"confirm": "Jste si jisti, že chcete udělat následující úpravy?",
|
||||
"description": {
|
||||
"downgrade": "Downgradovat {{name}} na verzi {{version}}",
|
||||
"install": "Instalovat {{name}} {{version}}",
|
||||
"overwrite": "Přepsat {{name}} verzí {{version}}",
|
||||
"reinstall": "Přeinstalovat {{name}} {{version}}",
|
||||
"update": "Aktualizovat {{name}} na {{version}}"
|
||||
},
|
||||
@@ -61,12 +63,18 @@
|
||||
"loading": "Probíhá"
|
||||
},
|
||||
"title": {
|
||||
"downgrade_few": "Downgradovat {{count}} pluginy",
|
||||
"downgrade_one": "Downgradovat {{count}} plugin",
|
||||
"downgrade_other": "Downgradovat {{count}} pluginů",
|
||||
"install_few": "Instalovat {{count}} pluginů",
|
||||
"install_one": "Instalovat 1 plugin",
|
||||
"install_other": "Instalovat {{count}} pluginů",
|
||||
"mixed_few": "Upravit {{count}} pluginů",
|
||||
"mixed_one": "Upravit {{count}} plugin",
|
||||
"mixed_other": "Upravit {{count}} pluginů",
|
||||
"overwrite_few": "Přepsat {{count}} pluginy",
|
||||
"overwrite_one": "Přepsat {{count}} plugin",
|
||||
"overwrite_other": "Přepsat {{count}} pluginů",
|
||||
"reinstall_few": "Přeinstalovat {{count}} pluginů",
|
||||
"reinstall_one": "Přeinstalovat 1 plugin",
|
||||
"reinstall_other": "Přeinstalovat {{count}} pluginů",
|
||||
@@ -76,12 +84,22 @@
|
||||
}
|
||||
},
|
||||
"PluginCard": {
|
||||
"plugin_downgrade": "Downgrade",
|
||||
"plugin_full_access": "Tento plugin má plný přístup k vašemu Steam Decku.",
|
||||
"plugin_install": "Instalovat",
|
||||
"plugin_no_desc": "Nebyl uveden žádný popis.",
|
||||
"plugin_overwrite": "Přepsat",
|
||||
"plugin_reinstall": "Přeinstalovat",
|
||||
"plugin_update": "Aktualizovat",
|
||||
"plugin_version_label": "Verze pluginu"
|
||||
},
|
||||
"PluginInstallModal": {
|
||||
"downgrade": {
|
||||
"button_idle": "Downgrade",
|
||||
"button_processing": "Downgradování",
|
||||
"desc": "Opravdu chcete downgradovat {{artifact}} na verzi {{version}}?",
|
||||
"title": "Downgradovat {{artifact}}"
|
||||
},
|
||||
"install": {
|
||||
"button_idle": "Instalovat",
|
||||
"button_processing": "Instalování",
|
||||
@@ -89,6 +107,13 @@
|
||||
"title": "Instalovat {{artifact}}"
|
||||
},
|
||||
"no_hash": "Tento plugin nemá hash, instalujete jej na vlastní nebezpečí.",
|
||||
"not_installed": "(nenainstalováno)",
|
||||
"overwrite": {
|
||||
"button_idle": "Přepsat",
|
||||
"button_processing": "Přepisování",
|
||||
"desc": "Opravdu chcete přepsat {{artifact}} na verzi {{version}}?",
|
||||
"title": "Přepsat {{artifact}}"
|
||||
},
|
||||
"reinstall": {
|
||||
"button_idle": "Přeinstalovat",
|
||||
"button_processing": "Přeinstalování",
|
||||
@@ -98,7 +123,7 @@
|
||||
"update": {
|
||||
"button_idle": "Aktualizovat",
|
||||
"button_processing": "Aktualizování",
|
||||
"desc": "Jste si jisti, že chcete aktualizovat {{artifact}} {{version}}?",
|
||||
"desc": "Opravdu chcete aktualizovat {{artifact}} na verzi {{version}}?",
|
||||
"title": "Aktualizovat {{artifact}}"
|
||||
}
|
||||
},
|
||||
@@ -205,6 +230,16 @@
|
||||
"testing_title": "Testování"
|
||||
},
|
||||
"Store": {
|
||||
"download_progress_info": {
|
||||
"download_remote": "Stahování externích knihoven",
|
||||
"download_zip": "Stahování pluginu",
|
||||
"increment_count": "Zvyšující se počet stahování",
|
||||
"installing_plugin": "Instalování pluginu",
|
||||
"open_zip": "Otevírání ZIP souboru",
|
||||
"parse_zip": "Analýza ZIP souboru",
|
||||
"start": "Inicializace",
|
||||
"uninstalling_previous": "Odinstalování předchozí kopie"
|
||||
},
|
||||
"store_contrib": {
|
||||
"desc": "Pokud byste chtěli přispět do obchodu Decky Plugin Store, podívejte se na repozitář SteamDeckHomebrew/decky-plugin-template na GitHubu. Informace o vývoji a distribuci jsou k dispozici v README.",
|
||||
"label": "Přispívání"
|
||||
@@ -253,7 +288,11 @@
|
||||
}
|
||||
},
|
||||
"Testing": {
|
||||
"download": "Stáhnout"
|
||||
"download": "Stáhnout",
|
||||
"error": "Chyba při instalaci PR",
|
||||
"header": "Následující verze Decky Loaderu jsou vytvořeny z otevřených Pull Requestů třetích stran. Tým Decky Loaderu neověřil jejich funkčnost ani zabezpečení a mohou být zastaralé.",
|
||||
"loading": "Načítání Pull Requestů...",
|
||||
"start_download_toast": "Stahování PR #{{id}}"
|
||||
},
|
||||
"TitleView": {
|
||||
"decky_store_desc": "Otevřít obchod Decky",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -52,7 +52,9 @@
|
||||
"MultiplePluginsInstallModal": {
|
||||
"confirm": "Are you sure you want to make the following modifications?",
|
||||
"description": {
|
||||
"downgrade": "Downgrade {{name}} to {{version}}",
|
||||
"install": "Install {{name}} {{version}}",
|
||||
"overwrite": "Overwrite {{name}} with {{version}}",
|
||||
"reinstall": "Reinstall {{name}} {{version}}",
|
||||
"update": "Update {{name}} to {{version}}"
|
||||
},
|
||||
@@ -61,10 +63,14 @@
|
||||
"loading": "Working"
|
||||
},
|
||||
"title": {
|
||||
"downgrade_one": "Downgrade 1 plugin",
|
||||
"downgrade_other": "Downgrade {{count}} plugins",
|
||||
"install_one": "Install 1 plugin",
|
||||
"install_other": "Install {{count}} plugins",
|
||||
"mixed_one": "Modify {{count}} plugin",
|
||||
"mixed_other": "Modify {{count}} plugins",
|
||||
"overwrite_one": "Overwrite 1 plugin",
|
||||
"overwrite_other": "Overwrite {{count}} plugins",
|
||||
"reinstall_one": "Reinstall 1 plugin",
|
||||
"reinstall_other": "Reinstall {{count}} plugins",
|
||||
"update_one": "Update 1 plugin",
|
||||
@@ -72,12 +78,22 @@
|
||||
}
|
||||
},
|
||||
"PluginCard": {
|
||||
"plugin_downgrade": "Downgrade",
|
||||
"plugin_full_access": "This plugin has full access to your Steam Deck.",
|
||||
"plugin_install": "Install",
|
||||
"plugin_no_desc": "No description provided.",
|
||||
"plugin_overwrite": "Overwrite",
|
||||
"plugin_reinstall": "Reinstall",
|
||||
"plugin_update": "Update",
|
||||
"plugin_version_label": "Plugin Version"
|
||||
},
|
||||
"PluginInstallModal": {
|
||||
"downgrade": {
|
||||
"button_idle": "Downgrade",
|
||||
"button_processing": "Downgrading",
|
||||
"desc": "Are you sure you want to downgrade {{artifact}} to version {{version}}?",
|
||||
"title": "Downgrade {{artifact}}"
|
||||
},
|
||||
"install": {
|
||||
"button_idle": "Install",
|
||||
"button_processing": "Installing",
|
||||
@@ -85,6 +101,14 @@
|
||||
"title": "Install {{artifact}}"
|
||||
},
|
||||
"no_hash": "This plugin does not have a hash, you are installing it at your own risk.",
|
||||
"not_installed": "(not installed)",
|
||||
"disabled": "The plugin will be re-enabled after installation",
|
||||
"overwrite": {
|
||||
"button_idle": "Overwrite",
|
||||
"button_processing": "Overwriting",
|
||||
"desc": "Are you sure you want to overwrite {{artifact}} with version {{version}}?",
|
||||
"title": "Overwrite {{artifact}}"
|
||||
},
|
||||
"reinstall": {
|
||||
"button_idle": "Reinstall",
|
||||
"button_processing": "Reinstalling",
|
||||
@@ -94,7 +118,7 @@
|
||||
"update": {
|
||||
"button_idle": "Update",
|
||||
"button_processing": "Updating",
|
||||
"desc": "Are you sure you want to update {{artifact}} {{version}}?",
|
||||
"desc": "Are you sure you want to update {{artifact}} to version {{version}}?",
|
||||
"title": "Update {{artifact}}"
|
||||
}
|
||||
},
|
||||
@@ -110,10 +134,13 @@
|
||||
"uninstall": "Uninstall",
|
||||
"update_all_one": "Update 1 plugin",
|
||||
"update_all_other": "Update {{count}} plugins",
|
||||
"update_to": "Update to {{name}}"
|
||||
"update_to": "Update to {{name}}",
|
||||
"disable": "Disable",
|
||||
"enable": "Enable"
|
||||
},
|
||||
"PluginListLabel": {
|
||||
"hidden": "Hidden from the quick access menu"
|
||||
"hidden": "Hidden from the quick access menu",
|
||||
"disabled": "Plugin disabled"
|
||||
},
|
||||
"PluginLoader": {
|
||||
"decky_title": "Decky",
|
||||
@@ -129,12 +156,23 @@
|
||||
"desc": "Are you sure you want to uninstall {{name}}?",
|
||||
"title": "Uninstall {{name}}"
|
||||
},
|
||||
"plugin_disable": {
|
||||
"button": "Disable",
|
||||
"desc": "Are you sure you want to disable {{name}}?",
|
||||
"title": "Disable {{name}}",
|
||||
"error": "Error disabling {{name}}"
|
||||
},
|
||||
"plugin_enable": {
|
||||
"error": "Error enabling {{name}}"
|
||||
},
|
||||
"plugin_update_one": "Updates available for 1 plugin!",
|
||||
"plugin_update_other": "Updates available for {{count}} plugins!"
|
||||
},
|
||||
"PluginView": {
|
||||
"hidden_one": "1 plugin is hidden from this list",
|
||||
"hidden_other": "{{count}} plugins are hidden from this list"
|
||||
"hidden_other": "{{count}} plugins are hidden from this list",
|
||||
"disabled_one": "1 plugin is disabled",
|
||||
"disabled_other": "{{count}} plugins are disabled"
|
||||
},
|
||||
"RemoteDebugging": {
|
||||
"remote_cef": {
|
||||
@@ -199,6 +237,7 @@
|
||||
},
|
||||
"Store": {
|
||||
"download_progress_info": {
|
||||
"download_remote": "Downloading any external binaries",
|
||||
"download_zip": "Downloading plugin",
|
||||
"increment_count": "Incrementing download count",
|
||||
"installing_plugin": "Installing plugin",
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"label": "Canal de actualización",
|
||||
"prerelease": "Prelanzamiento",
|
||||
"stable": "Estable",
|
||||
"testing": "Pruebas"
|
||||
"testing": "En pruebas"
|
||||
}
|
||||
},
|
||||
"Developer": {
|
||||
@@ -12,15 +12,49 @@
|
||||
"disabling": "Desactivando DevTools de React",
|
||||
"enabling": "Activando DevTools de React"
|
||||
},
|
||||
"DropdownMultiselect": {
|
||||
"button": {
|
||||
"back": "Volver"
|
||||
}
|
||||
},
|
||||
"FilePickerError": {
|
||||
"errors": {
|
||||
"file_not_found": "La ruta especificada no es válida. Por favor revísala e introdúcela correctamente.",
|
||||
"perm_denied": "No tienes acceso a la ruta especificada. Por favor revisa si tu usuario (deck en Steam Deck) tiene el permiso correspondiente para acceder a la ruta/archivo especificado.",
|
||||
"unknown": "Ha ocurrido un error desconocido. El error sin procesar es:{{raw_error}}"
|
||||
}
|
||||
},
|
||||
"FilePickerIndex": {
|
||||
"file": {
|
||||
"select": "Selecciona este archivo"
|
||||
},
|
||||
"files": {
|
||||
"all_files": "Todos los archivos",
|
||||
"file_type": "Tipo de archivo",
|
||||
"show_hidden": "Mostrar archivos ocultos"
|
||||
},
|
||||
"filter": {
|
||||
"created_asce": "Creado (Más antiguo)",
|
||||
"created_desc": "Creado (Más reciente)",
|
||||
"modified_asce": "Modificado (Más antiguo)",
|
||||
"modified_desc": "Modificado (Más reciente)",
|
||||
"name_asce": "Z-A",
|
||||
"name_desc": "A-Z",
|
||||
"size_asce": "Tamaño (Más pequeño)",
|
||||
"size_desc": "Tamaño (Más grande)"
|
||||
},
|
||||
"folder": {
|
||||
"select": "Usar esta carpeta"
|
||||
"label": "Carpeta",
|
||||
"select": "Usar esta carpeta",
|
||||
"show_more": "Mostrar más archivos"
|
||||
}
|
||||
},
|
||||
"MultiplePluginsInstallModal": {
|
||||
"confirm": "¿Estás seguro de que quieres hacer las siguientes modificaciones?",
|
||||
"description": {
|
||||
"downgrade": "Downgrade {{name}} a la {{version}}",
|
||||
"install": "Instalar {{name}} {{version}}",
|
||||
"overwrite": "Sobrescribir {{name}} con {{version}}",
|
||||
"reinstall": "Reinstalar {{name}} {{version}}",
|
||||
"update": "Actualizar {{name}} a {{version}}"
|
||||
},
|
||||
@@ -29,12 +63,18 @@
|
||||
"loading": "Trabajando"
|
||||
},
|
||||
"title": {
|
||||
"downgrade_many": "Downgrade {{count}} plugins",
|
||||
"downgrade_one": "Downgrade 1 plugin",
|
||||
"downgrade_other": "Downgrade {{count}} plugins",
|
||||
"install_many": "Instalar {{count}} plugins",
|
||||
"install_one": "Instalar 1 plugin",
|
||||
"install_other": "Instalar {{count}} plugins",
|
||||
"mixed_many": "Modificar {{count}} plugins",
|
||||
"mixed_one": "Modificar 1 plugin",
|
||||
"mixed_other": "Modificar {{count}} plugins",
|
||||
"overwrite_many": "Sobrescribir {{count}} plugins",
|
||||
"overwrite_one": "Sobrescribir 1 plugin",
|
||||
"overwrite_other": "Sobrescribr {{count}} plugins",
|
||||
"reinstall_many": "Reinstalar {{count}} plugins",
|
||||
"reinstall_one": "Reinstalar 1 plugin",
|
||||
"reinstall_other": "Reinstalar {{count}} plugins",
|
||||
@@ -44,12 +84,22 @@
|
||||
}
|
||||
},
|
||||
"PluginCard": {
|
||||
"plugin_full_access": "Este plugin tiene acceso completo a su Steam Deck.",
|
||||
"plugin_downgrade": "Downgrade",
|
||||
"plugin_full_access": "Este plugin tiene acceso completo a tu Steam Deck.",
|
||||
"plugin_install": "Instalar",
|
||||
"plugin_no_desc": "No se proporcionó una descripción.",
|
||||
"plugin_no_desc": "No se ha proporcionado una descripción.",
|
||||
"plugin_overwrite": "Sobrescribir",
|
||||
"plugin_reinstall": "Reinstalar",
|
||||
"plugin_update": "Actualizar",
|
||||
"plugin_version_label": "Versión de Plugin"
|
||||
},
|
||||
"PluginInstallModal": {
|
||||
"downgrade": {
|
||||
"button_idle": "Downgrade",
|
||||
"button_processing": "Downgrading",
|
||||
"desc": "¿Estas seguro de que quieres realizar el downgrade de {{artifact}} a la versión {{version}}?",
|
||||
"title": "Downgrade {{artifact}}"
|
||||
},
|
||||
"install": {
|
||||
"button_idle": "Instalar",
|
||||
"button_processing": "Instalando",
|
||||
@@ -57,6 +107,13 @@
|
||||
"title": "Instalar {{artifact}}"
|
||||
},
|
||||
"no_hash": "Este plugin no tiene un hash, lo estás instalando bajo tu propia responsabilidad.",
|
||||
"not_installed": "(no instalado)",
|
||||
"overwrite": {
|
||||
"button_idle": "Sobrescribir",
|
||||
"button_processing": "Sobrescribiendo",
|
||||
"desc": "¿Estas seguro de que quieres sobrescribir {{artifact}} con la versión {{version}}?",
|
||||
"title": "Sobrescribir {{artifact}}"
|
||||
},
|
||||
"reinstall": {
|
||||
"button_idle": "Reinstalar",
|
||||
"button_processing": "Reinstalando",
|
||||
@@ -66,24 +123,31 @@
|
||||
"update": {
|
||||
"button_idle": "Actualizar",
|
||||
"button_processing": "Actualizando",
|
||||
"desc": "¿Estás seguro de que quieres actualizar {{artifact}} {{version}}?",
|
||||
"desc": "¿Estás seguro de que quieres actualizar {{artifact}} a la {{version}}?",
|
||||
"title": "Actualizar {{artifact}}"
|
||||
}
|
||||
},
|
||||
"PluginListIndex": {
|
||||
"freeze": "Congelar actualizaciones",
|
||||
"hide": "Acceso rápido: Esconder",
|
||||
"no_plugin": "¡No hay plugins instalados!",
|
||||
"plugin_actions": "Acciones de plugin",
|
||||
"plugin_actions": "Acciones de Plugin",
|
||||
"reinstall": "Reinstalar",
|
||||
"reload": "Recargar",
|
||||
"show": "Acceso rápido: Mostrar",
|
||||
"unfreeze": "Permitir actualizaciones",
|
||||
"uninstall": "Desinstalar",
|
||||
"update_all_many": "Actualizar {{count}} plugins",
|
||||
"update_all_one": "Actualizar 1 plugin",
|
||||
"update_all_other": "Actualizar {{count}} plugins",
|
||||
"update_to": "Actualizar a {{name}}"
|
||||
},
|
||||
"PluginListLabel": {
|
||||
"hidden": "Escondido del menú de acceso rápido"
|
||||
},
|
||||
"PluginLoader": {
|
||||
"decky_title": "Decky",
|
||||
"decky_update_available": "¡Actualización {{tag_name}} disponible!",
|
||||
"decky_update_available": "¡Actualización para {{tag_name}} disponible!",
|
||||
"error": "Error",
|
||||
"plugin_error_uninstall": "Al cargar {{name}} se ha producido una excepción como se muestra arriba. Esto suele significar que el plugin requiere una actualización para la nueva versión de SteamUI. Comprueba si hay una actualización disponible o valora eliminarlo en los ajustes de Decky, en la sección Plugins.",
|
||||
"plugin_load_error": {
|
||||
@@ -100,19 +164,19 @@
|
||||
"plugin_update_other": "¡Actualizaciones disponibles para {{count}} plugins!"
|
||||
},
|
||||
"PluginView": {
|
||||
"hidden_many": "",
|
||||
"hidden_one": "",
|
||||
"hidden_other": ""
|
||||
"hidden_many": "{{count}} plugins están escondidos de esta lista",
|
||||
"hidden_one": "1 plugin está escondido de esta lista",
|
||||
"hidden_other": "{{count}} plugins están escondidos de esta lista"
|
||||
},
|
||||
"RemoteDebugging": {
|
||||
"remote_cef": {
|
||||
"desc": "Permitir acceso no autenticado al CEF debugger a cualquier persona en su red",
|
||||
"desc": "Permitir acceso no autenticado al depurador del CEF a cualquier persona en tu red",
|
||||
"label": "Permitir depuración remota del CEF"
|
||||
}
|
||||
},
|
||||
"SettingsDeveloperIndex": {
|
||||
"cef_console": {
|
||||
"button": "Abrir consola",
|
||||
"button": "Abrir Consola",
|
||||
"desc": "Abre la consola del CEF. Solamente es útil para propósitos de depuración. Las cosas que hagas aquí pueden ser potencialmente peligrosas y solo se debería usar si eres un desarrollador de plugins, o uno te ha dirigido aquí.",
|
||||
"label": "Consola CEF"
|
||||
},
|
||||
@@ -147,6 +211,11 @@
|
||||
"developer_mode": {
|
||||
"label": "Modo desarrollador"
|
||||
},
|
||||
"notifications": {
|
||||
"decky_updates_label": "Actualización de Decky disponible",
|
||||
"header": "Notificaciones",
|
||||
"plugin_updates_label": "Actualizaciones de plugin disponibles"
|
||||
},
|
||||
"other": {
|
||||
"header": "Otros"
|
||||
},
|
||||
@@ -157,11 +226,22 @@
|
||||
"SettingsIndex": {
|
||||
"developer_title": "Desarrollador",
|
||||
"general_title": "General",
|
||||
"plugins_title": "Plugins"
|
||||
"plugins_title": "Plugins",
|
||||
"testing_title": "En pruebas"
|
||||
},
|
||||
"Store": {
|
||||
"download_progress_info": {
|
||||
"download_remote": "Descargando los binarios externos",
|
||||
"download_zip": "Descargando plugin",
|
||||
"increment_count": "Incrementando el contador de descargas",
|
||||
"installing_plugin": "Instalando plugin",
|
||||
"open_zip": "Abriendo archivo zip",
|
||||
"parse_zip": "Analizando archivo zip",
|
||||
"start": "Iniciando",
|
||||
"uninstalling_previous": "Desinstalando copia previa"
|
||||
},
|
||||
"store_contrib": {
|
||||
"desc": "Si desea contribuir a la tienda de plugins de Decky, mira el repositorio SteamDeckHomebrew/decky-plugin-template en GitHub. Hay información acerca del desarrollo y distribución en el archivo README.",
|
||||
"desc": "Si desea contribuir a la tienda de plugins de Decky, consulta el repositorio SteamDeckHomebrew/decky-plugin-template en GitHub. Hay información disponible acerca del desarrollo y distribución en el archivo README.",
|
||||
"label": "Contribuyendo"
|
||||
},
|
||||
"store_filter": {
|
||||
@@ -173,7 +253,7 @@
|
||||
},
|
||||
"store_sort": {
|
||||
"label": "Ordenar",
|
||||
"label_def": "Actualizado por última vez (Nuevos)"
|
||||
"label_def": "Actualizado por última vez (Más reciente)"
|
||||
},
|
||||
"store_source": {
|
||||
"desc": "El código fuente de los plugins está disponible en el repositiorio SteamDeckHomebrew/decky-plugin-database en GitHub.",
|
||||
@@ -183,9 +263,17 @@
|
||||
"about": "Información",
|
||||
"alph_asce": "Alfabéticamente (Z-A)",
|
||||
"alph_desc": "Alfabéticamente (A-Z)",
|
||||
"date_asce": "Más antiguo primero",
|
||||
"date_desc": "Más reciente primero",
|
||||
"downloads_asce": "Menos descargados primero",
|
||||
"downloads_desc": "Más descargados primero",
|
||||
"title": "Navegar"
|
||||
},
|
||||
"store_testing_cta": "¡Por favor considera probar plugins nuevos para ayudar al equipo de Decky Loader!"
|
||||
"store_testing_cta": "¡Por favor considera probar plugins nuevos para ayudar al equipo de Decky Loader!",
|
||||
"store_testing_warning": {
|
||||
"desc": "Puedes usar este canal de la tienda para probar versiones inestables de plugins. Recuerda compartir tu experiencia en GitHub con el fin de poder actualizar el plugin para todos los usuarios.",
|
||||
"label": "Bienvenido al canal En Pruebas de la Tienda"
|
||||
}
|
||||
},
|
||||
"StoreSelect": {
|
||||
"custom_store": {
|
||||
@@ -195,10 +283,21 @@
|
||||
"store_channel": {
|
||||
"custom": "Personalizada",
|
||||
"default": "Por defecto",
|
||||
"label": "Canál de la tienda",
|
||||
"testing": "Pruebas"
|
||||
"label": "Canal de la Tienda",
|
||||
"testing": "En pruebas"
|
||||
}
|
||||
},
|
||||
"Testing": {
|
||||
"download": "Descargar",
|
||||
"error": "Error instalando PR",
|
||||
"header": "Las siguientes versiones de Decky Loader han sido compiladas de solicitudes Pull de terceros. El equipo de Decky Loader no ha verificado su funcionalidad o seguridad, y es posible que estén desactulizadas.",
|
||||
"loading": "Cargando abrir Solicitudes de Pull...",
|
||||
"start_download_toast": "Descargando PR #{{id}}"
|
||||
},
|
||||
"TitleView": {
|
||||
"decky_store_desc": "Abrir la tienda de Decky",
|
||||
"settings_desc": "Abrir los ajustes de Decky"
|
||||
},
|
||||
"Updater": {
|
||||
"decky_updates": "Actualizaciones de Decky",
|
||||
"no_patch_notes_desc": "No hay notas de parche para esta versión",
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"BranchSelect": {
|
||||
"update_channel": {
|
||||
"label": "Canal de mise à jour",
|
||||
"prerelease": "Avant-première",
|
||||
"prerelease": "Préliminaire",
|
||||
"stable": "Stable",
|
||||
"testing": "Test"
|
||||
}
|
||||
@@ -52,21 +52,29 @@
|
||||
"MultiplePluginsInstallModal": {
|
||||
"confirm": "Êtes-vous sûr de vouloir apporter les modifications suivantes ?",
|
||||
"description": {
|
||||
"downgrade": "Rétrograder {{name}} en {{version}}",
|
||||
"install": "Installer {{name}} {{version}}",
|
||||
"overwrite": "Écraser {{name}} avec {{version}}",
|
||||
"reinstall": "Réinstaller {{name}} {{version}}",
|
||||
"update": "Mettre à jour {{name}} à {{version}}"
|
||||
"update": "Mettre à jour {{name}} en {{version}}"
|
||||
},
|
||||
"ok_button": {
|
||||
"idle": "Confirmer",
|
||||
"loading": "En cours"
|
||||
},
|
||||
"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_one": "Installer 1 plugin",
|
||||
"install_other": "Installer {{count}} plugins",
|
||||
"mixed_many": "Modifier {{count}} plugins",
|
||||
"mixed_one": "Modifier {{count}} plugin",
|
||||
"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_one": "Réinstaller 1 plugin",
|
||||
"reinstall_other": "Réinstaller {{count}} plugins",
|
||||
@@ -76,12 +84,22 @@
|
||||
}
|
||||
},
|
||||
"PluginCard": {
|
||||
"plugin_downgrade": "Rétrograder",
|
||||
"plugin_full_access": "Ce plugin a un accès complet à votre Steam Deck.",
|
||||
"plugin_install": "Installer",
|
||||
"plugin_no_desc": "Aucune description fournie.",
|
||||
"plugin_overwrite": "Écraser",
|
||||
"plugin_reinstall": "Réinstaller",
|
||||
"plugin_update": "Mettre à jour",
|
||||
"plugin_version_label": "Version du plugin"
|
||||
},
|
||||
"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": {
|
||||
"button_idle": "Installer",
|
||||
"button_processing": "Installation en cours",
|
||||
@@ -89,6 +107,13 @@
|
||||
"title": "Installer {{artifact}}"
|
||||
},
|
||||
"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": {
|
||||
"button_idle": "Réinstaller",
|
||||
"button_processing": "Réinstallation en cours",
|
||||
@@ -97,8 +122,8 @@
|
||||
},
|
||||
"update": {
|
||||
"button_idle": "Mettre à jour",
|
||||
"button_processing": "Mise à jour",
|
||||
"desc": "Êtes-vous sûr de vouloir mettre à jour {{artifact}} {{version}} ?",
|
||||
"button_processing": "Mise à jour en cours",
|
||||
"desc": "Êtes-vous sûr de vouloir mettre à jour {{artifact}} vers la version {{version}} ?",
|
||||
"title": "Mettre à jour {{artifact}}"
|
||||
}
|
||||
},
|
||||
@@ -124,7 +149,7 @@
|
||||
"decky_title": "Decky",
|
||||
"decky_update_available": "Mise à jour vers {{tag_name}} disponible !",
|
||||
"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": {
|
||||
"message": "Erreur lors du chargement du plugin {{name}}",
|
||||
"toast": "Erreur lors du chargement de {{name}}"
|
||||
@@ -153,7 +178,7 @@
|
||||
"cef_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.",
|
||||
"label": "CEF Console"
|
||||
"label": "Console CEF"
|
||||
},
|
||||
"header": "Autre",
|
||||
"react_devtools": {
|
||||
@@ -171,7 +196,7 @@
|
||||
},
|
||||
"valve_internal": {
|
||||
"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"
|
||||
}
|
||||
},
|
||||
@@ -187,9 +212,9 @@
|
||||
"label": "Mode développeur"
|
||||
},
|
||||
"notifications": {
|
||||
"decky_updates_label": "Mise à jour Decky disponible",
|
||||
"decky_updates_label": "Mise à jour de Decky disponible",
|
||||
"header": "Notifications",
|
||||
"plugin_updates_label": "Mises à jour du plugin disponibles"
|
||||
"plugin_updates_label": "Mises à jour des plugins disponibles"
|
||||
},
|
||||
"other": {
|
||||
"header": "Autre"
|
||||
@@ -202,9 +227,19 @@
|
||||
"developer_title": "Développeur",
|
||||
"general_title": "Général",
|
||||
"plugins_title": "Plugins",
|
||||
"testing_title": "Essai"
|
||||
"testing_title": "Expérimentations"
|
||||
},
|
||||
"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": {
|
||||
"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"
|
||||
@@ -237,23 +272,27 @@
|
||||
"store_testing_cta": "Pensez à tester de nouveaux plugins pour aider l'équipe Decky Loader !",
|
||||
"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.",
|
||||
"label": "Bienvenue sur la chaîne du magasin de tests"
|
||||
"label": "Bienvenue sur le canal test de la boutique"
|
||||
}
|
||||
},
|
||||
"StoreSelect": {
|
||||
"custom_store": {
|
||||
"label": "Plugin Store personnalisé",
|
||||
"label": "Magasin personnalisé",
|
||||
"url_label": "URL"
|
||||
},
|
||||
"store_channel": {
|
||||
"custom": "Personnalisé",
|
||||
"default": "Par défaut",
|
||||
"label": "Canal du Plugin Store",
|
||||
"label": "Canal magasin",
|
||||
"testing": "Test"
|
||||
}
|
||||
},
|
||||
"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": {
|
||||
"decky_store_desc": "Ouvrir le magasin Decky",
|
||||
@@ -264,7 +303,7 @@
|
||||
"no_patch_notes_desc": "pas de notes de mise à jour pour cette version",
|
||||
"patch_notes_desc": "Notes de mise à jour",
|
||||
"updates": {
|
||||
"check_button": "Chercher les mises à jour",
|
||||
"check_button": "Vérifier les mises à jour",
|
||||
"checking": "Recherche",
|
||||
"cur_version": "Version actuelle: {{ver}}",
|
||||
"install_button": "Installer la mise à jour",
|
||||
|
||||
@@ -52,7 +52,9 @@
|
||||
"MultiplePluginsInstallModal": {
|
||||
"confirm": "Sei sicuro di voler effettuare le modifiche seguenti?",
|
||||
"description": {
|
||||
"downgrade": "Declassa {{name}} a versione {{version}}",
|
||||
"install": "Installa {{name}} {{version}}",
|
||||
"overwrite": "Sovrascrive {{name}} con {{version}}",
|
||||
"reinstall": "Reinstalla {{name}} {{version}}",
|
||||
"update": "Aggiorna {{name}} alla versione {{version}}"
|
||||
},
|
||||
@@ -61,12 +63,18 @@
|
||||
"loading": "Elaboro"
|
||||
},
|
||||
"title": {
|
||||
"downgrade_many": "Declassa {{count}} plugins",
|
||||
"downgrade_one": "Declassa un plugin",
|
||||
"downgrade_other": "Declassa {{count}} plugins",
|
||||
"install_many": "Installa {{count}} plugins",
|
||||
"install_one": "Installa un plugin",
|
||||
"install_other": "Installa {{count}} plugins",
|
||||
"mixed_many": "Modifica {{count}} plugins",
|
||||
"mixed_one": "Modifica un plugin",
|
||||
"mixed_other": "Modifica {{count}} plugins",
|
||||
"overwrite_many": "Sovrascrivi {{count}} plugins",
|
||||
"overwrite_one": "Sovrascrivi un plugin",
|
||||
"overwrite_other": "Sovrascrivi {{count}} plugins",
|
||||
"reinstall_many": "Reinstalla {{count}} plugins",
|
||||
"reinstall_one": "Reinstalla un plugin",
|
||||
"reinstall_other": "Reinstalla {{count}} plugins",
|
||||
@@ -76,12 +84,22 @@
|
||||
}
|
||||
},
|
||||
"PluginCard": {
|
||||
"plugin_downgrade": "Declassa",
|
||||
"plugin_full_access": "Questo plugin ha accesso completo al tuo Steam Deck.",
|
||||
"plugin_install": "Installa",
|
||||
"plugin_no_desc": "Nessuna descrizione fornita.",
|
||||
"plugin_overwrite": "Sovrascrivi",
|
||||
"plugin_reinstall": "Reinstalla",
|
||||
"plugin_update": "Aggiorna",
|
||||
"plugin_version_label": "Versione Plugin"
|
||||
},
|
||||
"PluginInstallModal": {
|
||||
"downgrade": {
|
||||
"button_idle": "Declassa",
|
||||
"button_processing": "Declassando",
|
||||
"desc": "Sei sicuro di voler declassare {{artifact}} alla versione {{version}}?",
|
||||
"title": "Declassa {{artifact}}"
|
||||
},
|
||||
"install": {
|
||||
"button_idle": "Installa",
|
||||
"button_processing": "Installando",
|
||||
@@ -89,6 +107,13 @@
|
||||
"title": "Installa {{artifact}}"
|
||||
},
|
||||
"no_hash": "Questo plugin non ha un hash associato, lo stai installando a tuo rischio e pericolo.",
|
||||
"not_installed": "(non installato)",
|
||||
"overwrite": {
|
||||
"button_idle": "Sovrascrivi",
|
||||
"button_processing": "Sovrascrivendo",
|
||||
"desc": "Sei sicuro di voler sovrascrivere {{artifact}} con la versione {{version}}?",
|
||||
"title": "Sovrascrivi {{artifact}}"
|
||||
},
|
||||
"reinstall": {
|
||||
"button_idle": "Reinstalla",
|
||||
"button_processing": "Reinstallando",
|
||||
@@ -98,7 +123,7 @@
|
||||
"update": {
|
||||
"button_idle": "Aggiorna",
|
||||
"button_processing": "Aggiornando",
|
||||
"desc": "Sei sicuro di voler aggiornare {{artifact}} {{version}}?",
|
||||
"desc": "Sei sicuro di voler aggiornare {{artifact}} alla versione {{version}}?",
|
||||
"title": "Aggiorna {{artifact}}"
|
||||
}
|
||||
},
|
||||
@@ -206,6 +231,7 @@
|
||||
},
|
||||
"Store": {
|
||||
"download_progress_info": {
|
||||
"download_remote": "Scaricando qualunque binario esterno",
|
||||
"download_zip": "Scarico plugin",
|
||||
"increment_count": "Incremento il numero di download",
|
||||
"installing_plugin": "Installo il plugin",
|
||||
|
||||
@@ -52,7 +52,9 @@
|
||||
"MultiplePluginsInstallModal": {
|
||||
"confirm": "以下の変更を加えてもよろしいですか?",
|
||||
"description": {
|
||||
"downgrade": "ダウングレード {{name}} {{version}}",
|
||||
"install": "インストール {{name}} {{version}}",
|
||||
"overwrite": "上書き {{name}} {{version}}",
|
||||
"reinstall": "再インストール {{name}} {{version}}",
|
||||
"update": "アップデート {{name}} {{version}}"
|
||||
},
|
||||
@@ -61,19 +63,31 @@
|
||||
"loading": "作業中"
|
||||
},
|
||||
"title": {
|
||||
"downgrade_other": "{{count}} 個のプラグインをダウングレード",
|
||||
"install_other": "{{count}} 個のプラグインをインストール",
|
||||
"mixed_other": "{{count}} 個のプラグインを修正",
|
||||
"overwrite_other": "{{count}} 個のプラグインを上書き",
|
||||
"reinstall_other": "{{count}} 個のプラグインを再インストール",
|
||||
"update_other": "{{count}} 個のプラグインをアップデート"
|
||||
}
|
||||
},
|
||||
"PluginCard": {
|
||||
"plugin_downgrade": "ダウングレード",
|
||||
"plugin_full_access": "このプラグインはSteam Deckの全てのアクセス権を持ちます。",
|
||||
"plugin_install": "インストール",
|
||||
"plugin_no_desc": "説明はありません。",
|
||||
"plugin_overwrite": "上書き",
|
||||
"plugin_reinstall": "再インストール",
|
||||
"plugin_update": "アップデート",
|
||||
"plugin_version_label": "プラグインバージョン"
|
||||
},
|
||||
"PluginInstallModal": {
|
||||
"downgrade": {
|
||||
"button_idle": "ダウングレード",
|
||||
"button_processing": "ダウングレード中",
|
||||
"desc": "{{artifact}}をVer {{version}} にダウングレードしてもよろしいですか?",
|
||||
"title": "{{artifact}}をダウングレード"
|
||||
},
|
||||
"install": {
|
||||
"button_idle": "インストール",
|
||||
"button_processing": "インストール中",
|
||||
@@ -81,6 +95,13 @@
|
||||
"title": "{{artifact}} をインストール"
|
||||
},
|
||||
"no_hash": "このプラグインにはハッシュがありません。ご自身の責任でインストールしてください。",
|
||||
"not_installed": "(インストールされていません)",
|
||||
"overwrite": {
|
||||
"button_idle": "上書き",
|
||||
"button_processing": "上書き中",
|
||||
"desc": "{{artifact}}をVer {{version}} に上書きしてもよろしいですか?",
|
||||
"title": "{{artifact}}を上書き"
|
||||
},
|
||||
"reinstall": {
|
||||
"button_idle": "再インストール",
|
||||
"button_processing": "再インストール中",
|
||||
@@ -90,17 +111,19 @@
|
||||
"update": {
|
||||
"button_idle": "アップデート",
|
||||
"button_processing": "アップデート中",
|
||||
"desc": "{{artifact}} {{version}} をアップデートしてもよろしいですか?",
|
||||
"desc": "{{artifact}}をVer {{version}} にアップデートしてもよろしいですか?",
|
||||
"title": "{{artifact}} をアップデート"
|
||||
}
|
||||
},
|
||||
"PluginListIndex": {
|
||||
"freeze": "アップデートを凍結",
|
||||
"hide": "クイックアクセス: 非表示",
|
||||
"no_plugin": "プラグインがインストールされていません!",
|
||||
"plugin_actions": "プラグインアクション",
|
||||
"reinstall": "再インストール",
|
||||
"reload": "再読み込み",
|
||||
"show": "クイックアクセス: 表示",
|
||||
"unfreeze": "アップデートを許可",
|
||||
"uninstall": "アンインストール",
|
||||
"update_all_other": "{{count}} 個のプラグインをアップデート",
|
||||
"update_to": "{{name}} を更新"
|
||||
@@ -185,9 +208,20 @@
|
||||
"SettingsIndex": {
|
||||
"developer_title": "開発者",
|
||||
"general_title": "一般",
|
||||
"plugins_title": "プラグイン"
|
||||
"plugins_title": "プラグイン",
|
||||
"testing_title": "テスト"
|
||||
},
|
||||
"Store": {
|
||||
"download_progress_info": {
|
||||
"download_remote": "外部バイナリのダウンロード",
|
||||
"download_zip": "プラグインのダウンロード中",
|
||||
"increment_count": "ダウンロード進行中",
|
||||
"installing_plugin": "プラグインのインストール中",
|
||||
"open_zip": "zipファイルを展開中",
|
||||
"parse_zip": "zipファイルの解析中",
|
||||
"start": "初期化中",
|
||||
"uninstalling_previous": "以前のコピーのアンインストール"
|
||||
},
|
||||
"store_contrib": {
|
||||
"desc": "Decky Plugin Storeに貢献したい場合は、GitHubのSteamDeckHomebrew/decky-plugin-templateリポジトリを確認してください。 開発と配布に関する情報は README で入手できます。",
|
||||
"label": "貢献"
|
||||
@@ -211,6 +245,10 @@
|
||||
"about": "概要",
|
||||
"alph_asce": "アルファベット(Z to A)",
|
||||
"alph_desc": "アルファベット(A to Z)",
|
||||
"date_asce": "古い順",
|
||||
"date_desc": "新しい順",
|
||||
"downloads_asce": "ダウンロード数が少ない順",
|
||||
"downloads_desc": "ダウンロード数が多い順",
|
||||
"title": "閲覧"
|
||||
},
|
||||
"store_testing_cta": "Decky Loaderチームを支援するために、新しいプラグインのテストを検討してください!",
|
||||
@@ -231,6 +269,13 @@
|
||||
"testing": "テスト"
|
||||
}
|
||||
},
|
||||
"Testing": {
|
||||
"download": "ダウンロード",
|
||||
"error": "PRのインストールエラー",
|
||||
"header": "Decky Loaderの以下のバージョンは、公開されているサードパーティのPull Requestからビルドされたものです。 Decky Loaderチームはその機能や安全性を検証しておらず、内容も古い可能性があります。",
|
||||
"loading": "Pull Requestの読み込み中...",
|
||||
"start_download_toast": "PR #{{id}}のダウンロード中"
|
||||
},
|
||||
"TitleView": {
|
||||
"decky_store_desc": "Deckyストアを開く",
|
||||
"settings_desc": "Decky設定を開く"
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
{}
|
||||
@@ -191,6 +191,15 @@
|
||||
"testing_title": "테스트"
|
||||
},
|
||||
"Store": {
|
||||
"download_progress_info": {
|
||||
"download_zip": "플러그인 다운로드 중",
|
||||
"increment_count": "다운로드 횟수 증가",
|
||||
"installing_plugin": "플러그인 설치 중",
|
||||
"open_zip": "압축 파일 여는 중",
|
||||
"parse_zip": "압축 파일 파싱 중",
|
||||
"start": "초기화 중",
|
||||
"uninstalling_previous": "이전 복사본 설치 제거 중"
|
||||
},
|
||||
"store_contrib": {
|
||||
"desc": "Decky 플러그인 스토어에 기여하고 싶다면 SteamDeckHomebrew/decky-plugin-template Github 저장소를 확인하세요. 개발 및 배포에 대한 정보는 README에서 확인할 수 있습니다.",
|
||||
"label": "기여하기"
|
||||
@@ -239,7 +248,11 @@
|
||||
}
|
||||
},
|
||||
"Testing": {
|
||||
"download": "다운로드"
|
||||
"download": "다운로드",
|
||||
"error": "PR 설치 오류",
|
||||
"header": "다음 버전의 Decky Loader는 서드 파티 PR를 사용하여 빌드 되엇습니다. Decky Loader 팀은 해당 버전의 작동 여부와 안전성을 확인하지 않았으며 최신 버전이 아닐 수 도 있습니다.",
|
||||
"loading": "열린 PR 로딩 중...",
|
||||
"start_download_toast": "PR #{{id}} 다운로드 중"
|
||||
},
|
||||
"TitleView": {
|
||||
"decky_store_desc": "Decky 스토어 열기",
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
},
|
||||
"FilePickerIndex": {
|
||||
"file": {
|
||||
"select": "Wybierz ten plik"
|
||||
"select": "Zaznacz ten plik"
|
||||
},
|
||||
"files": {
|
||||
"all_files": "Wszystkie pliki",
|
||||
@@ -96,9 +96,9 @@
|
||||
"title": "Reinstaluj {{artifact}}"
|
||||
},
|
||||
"update": {
|
||||
"button_idle": "Aktualizacja",
|
||||
"button_idle": "Aktualizuj",
|
||||
"button_processing": "Aktualizowanie",
|
||||
"desc": "Czy na pewno chcesz zaktualizować {{artifact}} {{version}}?",
|
||||
"desc": "Czy na pewno chcesz zaktualizować {{artifact}} do wersji {{version}}?",
|
||||
"title": "Zaktualizuj {{artifact}}"
|
||||
}
|
||||
},
|
||||
@@ -122,11 +122,11 @@
|
||||
},
|
||||
"PluginLoader": {
|
||||
"decky_title": "Decky",
|
||||
"decky_update_available": "Dostępna aktualizacja do {{tag_name}}!",
|
||||
"decky_update_available": "Aktualizacja do {{tag_name}} jest dostępna!",
|
||||
"error": "Błąd",
|
||||
"plugin_error_uninstall": "Ładowanie {{name}} spowodowało wyjątek, jak pokazano powyżej. Zwykle oznacza to, że plugin wymaga aktualizacji do nowej wersji SteamUI. Sprawdź, czy aktualizacja jest obecna lub rozważ usunięcie go w ustawieniach Decky, w sekcji Pluginy.",
|
||||
"plugin_load_error": {
|
||||
"message": "Błąd ładowania plugin {{name}}",
|
||||
"message": "Błąd ładowania pluginu {{name}}",
|
||||
"toast": "Błąd ładowania {{name}}"
|
||||
},
|
||||
"plugin_uninstall": {
|
||||
@@ -205,6 +205,15 @@
|
||||
"testing_title": "Testowanie"
|
||||
},
|
||||
"Store": {
|
||||
"download_progress_info": {
|
||||
"download_zip": "Pobieranie pluginu",
|
||||
"increment_count": "Zwiększanie liczby pobrań",
|
||||
"installing_plugin": "Instalowanie pluginu",
|
||||
"open_zip": "Otwieranie pliku zip",
|
||||
"parse_zip": "Analizowanie pliku zip",
|
||||
"start": "Inicjalizacja",
|
||||
"uninstalling_previous": "Odinstalowywanie poprzednich kopii"
|
||||
},
|
||||
"store_contrib": {
|
||||
"desc": "Jeśli chcesz przyczynić się do rozwoju Decky Plugin Store, sprawdź repozytorium SteamDeckHomebrew/decky-plugin-template na GitHub. Informacje na temat rozwoju i dystrybucji są dostępne w pliku README.",
|
||||
"label": "Współtworzenie"
|
||||
@@ -236,7 +245,7 @@
|
||||
},
|
||||
"store_testing_cta": "Rozważ przetestowanie nowych pluginów, aby pomóc zespołowi Decky Loader!",
|
||||
"store_testing_warning": {
|
||||
"desc": "Możesz użyć tego kanału sklepu do testowania najnowszych wersji pluginów. Pamiętaj, aby zostawić opinię na GitHub, aby plugin mogła zostać zaktualizowana dla wszystkich użytkowników.",
|
||||
"desc": "Możesz użyć tego kanału sklepu do testowania najnowszych wersji pluginów. Pamiętaj, aby zostawić opinię na GitHub, aby plugin mógł zostać zaktualizowany dla wszystkich użytkowników.",
|
||||
"label": "Witamy w Testowym Kanale Sklepu"
|
||||
}
|
||||
},
|
||||
@@ -253,7 +262,11 @@
|
||||
}
|
||||
},
|
||||
"Testing": {
|
||||
"download": "Pobierz"
|
||||
"download": "Pobierz",
|
||||
"error": "Błąd instalowania PR",
|
||||
"header": "Następujące wersje Decky Loader są zrobione z open third-party Pull Requests. Zespół Decky Loader nie zweryfikował ich działania czy bezpieczeństwa, mogą też być nie aktualne.",
|
||||
"loading": "Ładowanie open Pull Requests...",
|
||||
"start_download_toast": "Pobieranie PR #{{id}}"
|
||||
},
|
||||
"TitleView": {
|
||||
"decky_store_desc": "Otwórz sklep Decky",
|
||||
|
||||
@@ -110,6 +110,7 @@
|
||||
"reinstall": "Reinstalar",
|
||||
"reload": "Recarregar",
|
||||
"show": "Acesso Rápido: Mostrar",
|
||||
"unfreeze": "Permitir atualizações",
|
||||
"uninstall": "Desinstalar",
|
||||
"update_all_many": "Atualizar {{count}} plugins",
|
||||
"update_all_one": "Atualizar 1 plugin",
|
||||
@@ -200,9 +201,19 @@
|
||||
"SettingsIndex": {
|
||||
"developer_title": "Desenvolvedor",
|
||||
"general_title": "Geral",
|
||||
"plugins_title": "Plugins"
|
||||
"plugins_title": "Plugins",
|
||||
"testing_title": "Testando"
|
||||
},
|
||||
"Store": {
|
||||
"download_progress_info": {
|
||||
"download_zip": "Baixando Plugin",
|
||||
"increment_count": "Aumentando contagem de downloads",
|
||||
"installing_plugin": "Instalando Plugin",
|
||||
"open_zip": "Abrindo arquivo Zip",
|
||||
"parse_zip": "Analizando Arquivo ZIP",
|
||||
"start": "Iniciando",
|
||||
"uninstalling_previous": "Desinstalando Cópias Anteriores"
|
||||
},
|
||||
"store_contrib": {
|
||||
"desc": "Se você deseja contribuir para a Loja de Plugins para o Decky, confira o repositório SteamDeckHomebrew/decky-plugin-template no GitHub. Informações sobre o desenvolvimento e distribuição estão disponíveis no README.",
|
||||
"label": "Contribuindo"
|
||||
@@ -226,6 +237,10 @@
|
||||
"about": "Sobre",
|
||||
"alph_asce": "Alfabética (Z - A)",
|
||||
"alph_desc": "Alfabética (A - Z)",
|
||||
"date_asce": "Antigos Primeiro",
|
||||
"date_desc": "Novos Primeiro",
|
||||
"downloads_asce": "Menos Baixados Primeiro",
|
||||
"downloads_desc": "Mais Baixados Primeiro",
|
||||
"title": "Navegar"
|
||||
},
|
||||
"store_testing_cta": "Por favor, considere testar os novos plugins para ajudar o time do Decky Loader!",
|
||||
@@ -246,6 +261,13 @@
|
||||
"testing": "Em Teste"
|
||||
}
|
||||
},
|
||||
"Testing": {
|
||||
"download": "Download",
|
||||
"error": "Erro ao instalar PR",
|
||||
"header": "As seguintes versões do Decky Loader são construídas a partir de Pull Requests de terceiros. A equipe do Decky Loader não verificou sua funcionalidade ou segurança, e elas podem estar desatualizadas.",
|
||||
"loading": "Carregando Pull Requests abertos...",
|
||||
"start_download_toast": "Baixando PR #{{id}}"
|
||||
},
|
||||
"TitleView": {
|
||||
"decky_store_desc": "Abrir Loja Decky",
|
||||
"settings_desc": "Abrir Definições Decky"
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
{
|
||||
"BranchSelect": {
|
||||
"update_channel": {
|
||||
"label": "Canal de actualização",
|
||||
"label": "Canal de atualização",
|
||||
"prerelease": "Pré-lançamento",
|
||||
"stable": "Estável",
|
||||
"testing": "Em teste"
|
||||
"testing": "Em testes"
|
||||
}
|
||||
},
|
||||
"Developer": {
|
||||
"5secreload": "Vai recarregar em 5 segundos",
|
||||
"disabling": "Desactivando React DevTools",
|
||||
"enabling": "Activando React DevTools"
|
||||
"5secreload": "A recarregar em 5 segundos",
|
||||
"disabling": "Desativar React DevTools",
|
||||
"enabling": "Ativar React DevTools"
|
||||
},
|
||||
"DropdownMultiselect": {
|
||||
"button": {
|
||||
@@ -19,9 +19,9 @@
|
||||
},
|
||||
"FilePickerError": {
|
||||
"errors": {
|
||||
"file_not_found": "O caminho especificado não é válido. Por favor, verifique e insira-o corretamente.",
|
||||
"perm_denied": "Não tem acesso ao diretório especificado. Por favor, verifique se o seu utilizador (deck na Steam Deck) possui as permissões correspondentes para aceder à pasta/ficheiro especificado.",
|
||||
"unknown": "Ocorreu um erro desconhecido. O erro é: {{raw_error}}"
|
||||
"file_not_found": "O caminho especificado não é válido. Por favor, verifica e insere o caminho correto.",
|
||||
"perm_denied": "Não tens acesso ao diretório especificado. Por favor, verifica se o seu utilizador (deck na Steam Deck) tem a permissão correspondente para aceder à pasta/ficheiro especificada(o).",
|
||||
"unknown": "Ocorreu um erro desconhecido. O erro bruto é: {{raw_error}}"
|
||||
}
|
||||
},
|
||||
"FilePickerIndex": {
|
||||
@@ -50,11 +50,11 @@
|
||||
}
|
||||
},
|
||||
"MultiplePluginsInstallModal": {
|
||||
"confirm": "De certeza que queres fazer as seguintes alterações?",
|
||||
"confirm": "Tens a certeza de que pretendes fazer as seguintes alterações?",
|
||||
"description": {
|
||||
"install": "Instalar {{name}} {{version}}",
|
||||
"reinstall": "Reinstalar {{name}} {{version}}",
|
||||
"update": "Actualizar {{name}} para {{version}}"
|
||||
"update": "Atualizar {{name}} para {{version}}"
|
||||
},
|
||||
"ok_button": {
|
||||
"idle": "Confirmar",
|
||||
@@ -70,15 +70,15 @@
|
||||
"reinstall_many": "Reinstalar {{count}} plugins",
|
||||
"reinstall_one": "Reinstalar 1 plugin",
|
||||
"reinstall_other": "Reinstalar {{count}} plugins",
|
||||
"update_many": "Actualizar {{count}} plugins",
|
||||
"update_one": "Actualizar 1 plugin",
|
||||
"update_other": "Actualizar {{count}} plugins"
|
||||
"update_many": "Atualizar {{count}} plugins",
|
||||
"update_one": "Atualizar 1 plugin",
|
||||
"update_other": "Atualizar {{count}} plugins"
|
||||
}
|
||||
},
|
||||
"PluginCard": {
|
||||
"plugin_full_access": "Este plugin tem acesso total à tua Steam Deck.",
|
||||
"plugin_full_access": "Este plugin tem acesso total ao teu Steam Deck.",
|
||||
"plugin_install": "Instalar",
|
||||
"plugin_no_desc": "Não tem descrição.",
|
||||
"plugin_no_desc": "Sem descrição fornecida.",
|
||||
"plugin_version_label": "Versão do plugin"
|
||||
},
|
||||
"PluginInstallModal": {
|
||||
@@ -88,7 +88,7 @@
|
||||
"desc": "De certeza que queres instalar {{artifact}} {{version}}?",
|
||||
"title": "Instalar {{artifact}}"
|
||||
},
|
||||
"no_hash": "Este plugin não tem um hash, estás a instalá-lo por tua conta e risco.",
|
||||
"no_hash": "Este plugin não tem uma hash, estás a instalá-lo por tua conta e risco.",
|
||||
"reinstall": {
|
||||
"button_idle": "Reinstalar",
|
||||
"button_processing": "Reinstalação em curso",
|
||||
@@ -96,24 +96,26 @@
|
||||
"title": "Reinstalar {{artifact}}"
|
||||
},
|
||||
"update": {
|
||||
"button_idle": "Actualizar",
|
||||
"button_processing": "Actualização em curso",
|
||||
"desc": "De certeza que queres actualizar {{artifact}} {{version}}?",
|
||||
"title": "Actualizar {{artifact}}"
|
||||
"button_idle": "Atualizar",
|
||||
"button_processing": "Atualização em curso",
|
||||
"desc": "De certeza que queres atualizar {{artifact}} {{version}}?",
|
||||
"title": "Atualizar {{artifact}}"
|
||||
}
|
||||
},
|
||||
"PluginListIndex": {
|
||||
"freeze": "Congelar atualizações",
|
||||
"hide": "Acesso rápido: Ocultar",
|
||||
"no_plugin": "Nenhum plugin instalado!",
|
||||
"plugin_actions": "Operações de plugin",
|
||||
"reinstall": "Reinstalar",
|
||||
"reload": "Recarregar",
|
||||
"show": "Acesso rápido: Mostrar",
|
||||
"unfreeze": "Permitir atualizações",
|
||||
"uninstall": "Desinstalar",
|
||||
"update_all_many": "Actualizar {{count}} plugins",
|
||||
"update_all_one": "Actualizar 1 plugin",
|
||||
"update_all_other": "Actualizar {{count}} plugins",
|
||||
"update_to": "Actualizar para {{name}}"
|
||||
"update_all_many": "Atualizar {{count}} plugins",
|
||||
"update_all_one": "Atualizar 1 plugin",
|
||||
"update_all_other": "Atualizar {{count}} plugins",
|
||||
"update_to": "Atualizar para {{name}}"
|
||||
},
|
||||
"PluginListLabel": {
|
||||
"hidden": "Oculto do menu de acesso rápido"
|
||||
@@ -122,7 +124,7 @@
|
||||
"decky_title": "Decky",
|
||||
"decky_update_available": "Está disponível uma nova versão de {{tag_name}} !",
|
||||
"error": "Erro",
|
||||
"plugin_error_uninstall": "Houve uma excepção ao carregar {{name}}, como mostrado em cima. Pode ter sido porque o plugin requere a última versão do SteamUI. Verifica se há uma actualização disponível ou desinstala o plugin nas definições do Decky.",
|
||||
"plugin_error_uninstall": "Ao carregar {{name}}, ocorreu uma exceção, como pode ser verificado acima. Pode ter sido porque o plugin requer a última versão do SteamUI. Verifica se há uma atualização disponível ou desinstala o plugin nas definições do Decky.",
|
||||
"plugin_load_error": {
|
||||
"message": "Erro ao carregar o plugin {{name}}",
|
||||
"toast": "Erro ao carregar {{name}}"
|
||||
@@ -133,7 +135,7 @@
|
||||
"title": "Desinstalar {{name}}"
|
||||
},
|
||||
"plugin_update_many": "{{count}} plugins têm actualizações disponíveis!",
|
||||
"plugin_update_one": "1 plugin tem actualizações disponíveis!",
|
||||
"plugin_update_one": "1 plugin tem atualizações disponíveis!",
|
||||
"plugin_update_other": "{{count}} plugins têm actualizações disponíveis!"
|
||||
},
|
||||
"PluginView": {
|
||||
@@ -143,34 +145,34 @@
|
||||
},
|
||||
"RemoteDebugging": {
|
||||
"remote_cef": {
|
||||
"desc": "Permitir acesso não autenticado ao debugger do CEF a qualquer pessoa na tua rede",
|
||||
"label": "Permitir debugging remoto do CEF"
|
||||
"desc": "Permitir acesso não autenticado ao depurador do CEF a qualquer pessoa na tua rede",
|
||||
"label": "Permitir Depuração Remota do CEF"
|
||||
}
|
||||
},
|
||||
"SettingsDeveloperIndex": {
|
||||
"cef_console": {
|
||||
"button": "Abrir consola",
|
||||
"desc": "Abre a consola do CEF. Só é útil para efeitos de debugging. Pode ser perigosa e só deve ser usada se és um desenvolvedor de plugins, ou se foste aqui indicado por um desenvolvedor.",
|
||||
"desc": "Abre a Consola CEF. Apenas útil para fins de depuração. O conteúdo aqui presente pode ser perigoso e só devem ser usadas se fores um desenvolvedor de plugins ou se fores direcionado para aqui por um.",
|
||||
"label": "Consola CEF"
|
||||
},
|
||||
"header": "Outros",
|
||||
"react_devtools": {
|
||||
"desc": "Permite a conecção a um computador que está a correr React DevTools. Mudar esta definição vai recarregar o Steam. Define o endereço de IP antes de activar.",
|
||||
"desc": "Permite a conexão a um computador a correr o React DevTools. Alterar esta definição irá recarregar o Steam. Define o endereço IP antes de ativar.",
|
||||
"ip_label": "IP",
|
||||
"label": "Activar React DevTools"
|
||||
"label": "Ativar React DevTools"
|
||||
},
|
||||
"third_party_plugins": {
|
||||
"button_install": "Instalar",
|
||||
"button_zip": "Navegar",
|
||||
"header": "Plugins de terceiros",
|
||||
"label_desc": "URl",
|
||||
"label_url": "Instalar plugin a partir dum URL",
|
||||
"label_zip": "Instalar plugin a partir dum ficheiro ZIP"
|
||||
"label_desc": "URL",
|
||||
"label_url": "Instalar plugin a partir de um URL",
|
||||
"label_zip": "Instalar plugin a partir de um ficheiro ZIP"
|
||||
},
|
||||
"valve_internal": {
|
||||
"desc1": "Activa o menu interno de programador da Valve.",
|
||||
"desc2": "Não toques em nada deste menu se não souberes a sua função.",
|
||||
"label": "Activar menu interno da Valve"
|
||||
"desc1": "Ativa o menu interno de programador da Valve.",
|
||||
"desc2": "Não toques em nada neste menu, a menos que saibas o que faz.",
|
||||
"label": "Cativar menu interno da Valve"
|
||||
}
|
||||
},
|
||||
"SettingsGeneralIndex": {
|
||||
@@ -193,17 +195,27 @@
|
||||
"header": "Outros"
|
||||
},
|
||||
"updates": {
|
||||
"header": "Actualizações"
|
||||
"header": "Atualizações"
|
||||
}
|
||||
},
|
||||
"SettingsIndex": {
|
||||
"developer_title": "Programador",
|
||||
"general_title": "Geral",
|
||||
"plugins_title": "Plugins"
|
||||
"plugins_title": "Plugins",
|
||||
"testing_title": "Testes"
|
||||
},
|
||||
"Store": {
|
||||
"download_progress_info": {
|
||||
"download_zip": "A transferir o plugin",
|
||||
"increment_count": "A incrementar a contagem de transferências",
|
||||
"installing_plugin": "A instalar o plugin",
|
||||
"open_zip": "A abrir o ficheiro zip",
|
||||
"parse_zip": "A processar o ficheiro zip",
|
||||
"start": "A inicializar",
|
||||
"uninstalling_previous": "A desinstalar a cópia anterior"
|
||||
},
|
||||
"store_contrib": {
|
||||
"desc": "Se queres contribuir com um novo plugin, vai ao repositório SteamDeckHomebrew/decky-plugin-template no GitHub. No README, podes encontrar mais informação sobre desenvolvimento e distribuição.",
|
||||
"desc": "Se quiseres contribuir para a Loja de Plugins do Decky, consulta o repositório SteamDeckHomebrew/decky-plugin-template no GitHub. Encontras informações sobre desenvolvimento e distribuição no README.",
|
||||
"label": "Contribuir"
|
||||
},
|
||||
"store_filter": {
|
||||
@@ -215,21 +227,25 @@
|
||||
},
|
||||
"store_sort": {
|
||||
"label": "Ordenar",
|
||||
"label_def": "Última actualização (mais recente)"
|
||||
"label_def": "Última atualização (mais recente)"
|
||||
},
|
||||
"store_source": {
|
||||
"desc": "O código fonte de cada plugin está disponível no repositório SteamDeckHomebrew/decky-plugin-database no GitHub.",
|
||||
"desc": "Todo o código-fonte dos plugins está disponível no repositório SteamDeckHomebrew/decky-plugin-database no GitHub.",
|
||||
"label": "Código fonte"
|
||||
},
|
||||
"store_tabs": {
|
||||
"about": "Sobre",
|
||||
"alph_asce": "Alfabeticamente (Z-A)",
|
||||
"alph_desc": "Alfabeticamente (A-Z)",
|
||||
"date_asce": "Mais Antigos Primeiro",
|
||||
"date_desc": "Mais Recentes Primeiro",
|
||||
"downloads_asce": "Menos Transferidos Primeiro",
|
||||
"downloads_desc": "Mais Transferidos Primeiro",
|
||||
"title": "Navegar"
|
||||
},
|
||||
"store_testing_cta": "Testa novos plugins e ajuda a equipa do Decky Loader!",
|
||||
"store_testing_warning": {
|
||||
"desc": "Pode usar esta versão da loja para testar versões experimentais de plugins. Certifique-se de deixar feedback no GitHub para que o plugin possa ser atualizado para todos os utilizadores.",
|
||||
"desc": "Podes utilizar este canal da loja para testar versões de plugins de última geração. Não te esqueças de deixar o teufeedback no GitHub para que o plugin possa ser atualizado para todos os utilizadores.",
|
||||
"label": "Bem-vindo ao Canal de Testes da Loja"
|
||||
}
|
||||
},
|
||||
@@ -240,28 +256,35 @@
|
||||
},
|
||||
"store_channel": {
|
||||
"custom": "Personalizada",
|
||||
"default": "Standard",
|
||||
"label": "Canal de loja",
|
||||
"testing": "Em teste"
|
||||
"default": "Padrão",
|
||||
"label": "Canal da loja",
|
||||
"testing": "Em testes"
|
||||
}
|
||||
},
|
||||
"Testing": {
|
||||
"download": "Transferir",
|
||||
"error": "Erro ao instalar PR",
|
||||
"header": "As seguintes versões do Decky Loader estão construidas a partir de Pull Requests de terceiros. A equipa do Decky Loader não verificou as sua funcionalidade ou segurança e as mesmas podem estar desatualizados.",
|
||||
"loading": "A carregar Pull Requests abertos...",
|
||||
"start_download_toast": "A descarregar PR #{{id}}"
|
||||
},
|
||||
"TitleView": {
|
||||
"decky_store_desc": "Abrir a Loja Decky",
|
||||
"settings_desc": "Abrir as Definições Decky"
|
||||
},
|
||||
"Updater": {
|
||||
"decky_updates": "Actualizações do Decky",
|
||||
"no_patch_notes_desc": "sem registo de alterações desta versão",
|
||||
"patch_notes_desc": "Registo de alterações",
|
||||
"decky_updates": "Atualizações do Decky",
|
||||
"no_patch_notes_desc": "sem notas de atualizações para esta versão",
|
||||
"patch_notes_desc": "Notas de atualizações",
|
||||
"updates": {
|
||||
"check_button": "Procurar actualizações",
|
||||
"checking": "Busca de actualizações em curso",
|
||||
"cur_version": "Versão actual: {{ver}}",
|
||||
"install_button": "Instalar actualização",
|
||||
"label": "Actualizações",
|
||||
"lat_version": "Actualizado: a correr {{ver}}",
|
||||
"reloading": "Recarregar",
|
||||
"updating": "Actualização em curso"
|
||||
"check_button": "Procurar atualizações",
|
||||
"checking": "A verificar atualizações",
|
||||
"cur_version": "Versão atual: {{ver}}",
|
||||
"install_button": "Instalar Atualização",
|
||||
"label": "Atualizações",
|
||||
"lat_version": "Atualizado: a executar {{ver}}",
|
||||
"reloading": "Recarregando",
|
||||
"updating": "Atualização em curso"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,316 @@
|
||||
{
|
||||
"BranchSelect": {
|
||||
"update_channel": {
|
||||
"label": "Candalul de Actualizare",
|
||||
"prerelease": "Pre-lansare",
|
||||
"stable": "Stabil",
|
||||
"testing": "Testare"
|
||||
}
|
||||
},
|
||||
"Developer": {
|
||||
"5secreload": "Reîncărcare în 5 secunde",
|
||||
"disabling": "Dezactivare React DevTools",
|
||||
"enabling": "Activare React DevTools"
|
||||
},
|
||||
"DropdownMultiselect": {
|
||||
"button": {
|
||||
"back": "Înapoi"
|
||||
}
|
||||
},
|
||||
"FilePickerError": {
|
||||
"errors": {
|
||||
"file_not_found": "Calea specificată nu este validă. Vă rugăm să o verificați și să o reintroduceți corect.",
|
||||
"perm_denied": "Nu aveți acces la fișierul specificat. Te rugăm să verifici dacă utilizatorul tău (deck pe Steam Deck) are permisiunea corespunzătoare de a accesa folderul/fișierul specific.",
|
||||
"unknown": "A apărut o eroare necunoscută. Eroarea brută este: {{raw_error}}"
|
||||
}
|
||||
},
|
||||
"FilePickerIndex": {
|
||||
"file": {
|
||||
"select": "Selectați acest fișier"
|
||||
},
|
||||
"files": {
|
||||
"all_files": "Toate Fișierele",
|
||||
"file_type": "Tip de Fișier",
|
||||
"show_hidden": "Afișați Fișierele Ascunse"
|
||||
},
|
||||
"filter": {
|
||||
"created_asce": "Create (Cele mai vechi)",
|
||||
"created_desc": "Create (Cele mai noi)",
|
||||
"modified_asce": "Modificate (Cele mai vechi)",
|
||||
"modified_desc": "Modificate (Cele mai noi)",
|
||||
"name_asce": "Z-A",
|
||||
"name_desc": "A-Z",
|
||||
"size_asce": "Dimensiune (Cea mai mică)",
|
||||
"size_desc": "Dimensiune (Cea mai mare)"
|
||||
},
|
||||
"folder": {
|
||||
"label": "Fișier",
|
||||
"select": "Folosiți acest fișier",
|
||||
"show_more": "Afișați mai multe fișiere"
|
||||
}
|
||||
},
|
||||
"MultiplePluginsInstallModal": {
|
||||
"confirm": "Sigur doriți să faceți următoarele modificări?",
|
||||
"description": {
|
||||
"downgrade": "Retrogradare {{name}} la {{version}}",
|
||||
"install": "Instalați {{name}} {{version}}",
|
||||
"overwrite": "Suprascrie {{name}} cu {{version}}",
|
||||
"reinstall": "Reinstalați {{name}} {{version}}",
|
||||
"update": "Actualizați {{name}} la {{version}}"
|
||||
},
|
||||
"ok_button": {
|
||||
"idle": "Confirmați",
|
||||
"loading": "Încărcare"
|
||||
},
|
||||
"title": {
|
||||
"downgrade_few": "Retrogradează {{count}} pluginuri",
|
||||
"downgrade_one": "Retrogradează un plugin",
|
||||
"downgrade_other": "Retrogradează {{count}} pluginuri",
|
||||
"install_few": "Instalați {{count}} pluginuri",
|
||||
"install_one": "Instalați un plugin",
|
||||
"install_other": "Instalați {{count}} pluginuri",
|
||||
"mixed_few": "Modificați {{count}} pluginuri",
|
||||
"mixed_one": "Modificați un plugin",
|
||||
"mixed_other": "Modificați {{count}} pluginuri",
|
||||
"overwrite_few": "Suprascrie {{count}} pluginuri",
|
||||
"overwrite_one": "Suprascrie un plugin",
|
||||
"overwrite_other": "Suprascrie {{count}} pluginuri",
|
||||
"reinstall_few": "Reinstalați {{count}} pluginuri",
|
||||
"reinstall_one": "Reinstalați un plugin",
|
||||
"reinstall_other": "Reinstalați {{count}} pluginuri",
|
||||
"update_few": "Actualizați {{count}} pluginuri",
|
||||
"update_one": "Actualizați un plugin",
|
||||
"update_other": "Actualizați {{count}} pluginuri"
|
||||
}
|
||||
},
|
||||
"PluginCard": {
|
||||
"plugin_downgrade": "Retrogradare",
|
||||
"plugin_full_access": "Acest plugin are acces complet la Steam Deck-ul tău.",
|
||||
"plugin_install": "Instalați",
|
||||
"plugin_no_desc": "Nici-o descriere pusă.",
|
||||
"plugin_overwrite": "Suprascriere",
|
||||
"plugin_reinstall": "Reinstalați",
|
||||
"plugin_update": "Actualizare",
|
||||
"plugin_version_label": "Versiunea Plugin"
|
||||
},
|
||||
"PluginInstallModal": {
|
||||
"downgrade": {
|
||||
"button_idle": "Retrogradare",
|
||||
"button_processing": "Retrogradare",
|
||||
"desc": "Sunteți sigur că vreți să retrogradați {{artifact}} la versiunea {{version}}?",
|
||||
"title": "Retrogradare {{artifact}}"
|
||||
},
|
||||
"install": {
|
||||
"button_idle": "Instalați",
|
||||
"button_processing": "Instalare",
|
||||
"desc": "Sigur vreți să instalați {{artifact}} {{version}}?",
|
||||
"title": "Instalați {{artifact}}"
|
||||
},
|
||||
"no_hash": "Acest plugin nu are hash, îl instalați pe propriul risc.",
|
||||
"not_installed": "(neinstalat)",
|
||||
"overwrite": {
|
||||
"button_idle": "Suprascriere",
|
||||
"button_processing": "Suprascriere",
|
||||
"desc": "Sigur vreți să suprascrii {{artifact}} cu versiunea {{version}}?",
|
||||
"title": "Suprascrie {{artifact}}"
|
||||
},
|
||||
"reinstall": {
|
||||
"button_idle": "Reinstalare",
|
||||
"button_processing": "Reinstalare",
|
||||
"desc": "Sigur vreți să reinstalați {{artifact}} {{version}}?",
|
||||
"title": "Reinstalare {{artifact}}"
|
||||
},
|
||||
"update": {
|
||||
"button_idle": "Actualizare",
|
||||
"button_processing": "Se Actualizează",
|
||||
"desc": "Sigur vreți să actualizați {{artifact}} la versiunea {{version}}?",
|
||||
"title": "Actualizare {{artifact}}"
|
||||
}
|
||||
},
|
||||
"PluginListIndex": {
|
||||
"freeze": "Pauză actualizări",
|
||||
"hide": "Acces rapid: Ascunde",
|
||||
"no_plugin": "Niciun plugin instalat!",
|
||||
"plugin_actions": "Acțiuni plugin",
|
||||
"reinstall": "Reinstalare",
|
||||
"reload": "Reîncărcare",
|
||||
"show": "Acces rapid: Afișare",
|
||||
"unfreeze": "Permiteți actualizări",
|
||||
"uninstall": "Dezinstalare",
|
||||
"update_all_few": "Actualizați {{count}} pluginuri",
|
||||
"update_all_one": "Actualizare un plugin",
|
||||
"update_all_other": "Actualizați {{count}} pluginuri",
|
||||
"update_to": "Actualizare pentru {{name}}"
|
||||
},
|
||||
"PluginListLabel": {
|
||||
"hidden": "Ascuns din meniul de acces rapid"
|
||||
},
|
||||
"PluginLoader": {
|
||||
"decky_title": "Decky",
|
||||
"decky_update_available": "Actualizare disponibilă pentru {{tag_name}}!",
|
||||
"error": "Eroare",
|
||||
"plugin_error_uninstall": "Încărcarea pluginului {{name}} a cauzat o excepție, așa cum se arată mai sus. Aceasta înseamnă de obicei că pluginul necesită o actualizare pentru noua versiune de SteamUI. Verificați dacă există o actualizare sau evaluați eliminarea acesteia în setările Decky, în secțiunea Plugin-uri.",
|
||||
"plugin_load_error": {
|
||||
"message": "Eroare la încărcarea pluginului {{name}}",
|
||||
"toast": "Eroare la încărcarea {{name}}"
|
||||
},
|
||||
"plugin_uninstall": {
|
||||
"button": "Dezinstalare",
|
||||
"desc": "Sigur vreți să dezinstalați {{name}}?",
|
||||
"title": "Dezinstalați {{name}}"
|
||||
},
|
||||
"plugin_update_few": "Actualizări disponibile pentru {{count}} plugin-uri!",
|
||||
"plugin_update_one": "Actualizare disponibilă pentru un plugin!",
|
||||
"plugin_update_other": "Actualizări disponibile pentru {{count}} plugin-uri!"
|
||||
},
|
||||
"PluginView": {
|
||||
"hidden_few": "{{count}} pluginuri sunt ascunse din această listă",
|
||||
"hidden_one": "Un plugin este ascuns din această listă",
|
||||
"hidden_other": "{{count}} pluginuri sunt ascunse din această listă"
|
||||
},
|
||||
"RemoteDebugging": {
|
||||
"remote_cef": {
|
||||
"desc": "Permiteți accesul neautentificat la debugger CEF oricui din rețeaua dumneavoastră",
|
||||
"label": "Permiteți Deugging CEF la distanță"
|
||||
}
|
||||
},
|
||||
"SettingsDeveloperIndex": {
|
||||
"cef_console": {
|
||||
"button": "Deschideți Consola",
|
||||
"desc": "Deschide consola CEF. Utilă doar în scopuri de debugging. Informațiile de aici sunt potențial periculoase și ar trebui utilizate doar dacă ești dezvoltator de plugin-uri sau dacă ești îndrumat aici de unul dintre ei.",
|
||||
"label": "Consola CEF"
|
||||
},
|
||||
"header": "Alte",
|
||||
"react_devtools": {
|
||||
"desc": "Permite conectarea la un calculator care rulează React DevTools. Modificarea acestei setări va reîncărca Steam. Setați adresa IP înainte de activare.",
|
||||
"ip_label": "IP",
|
||||
"label": "Activați React DevTools"
|
||||
},
|
||||
"third_party_plugins": {
|
||||
"button_install": "Instalați",
|
||||
"button_zip": "Răsfoiți",
|
||||
"header": "Pluginuri terțe",
|
||||
"label_desc": "URL",
|
||||
"label_url": "Instalați pluginul de la URL",
|
||||
"label_zip": "Instalați pluginul din fișierul ZIP"
|
||||
},
|
||||
"valve_internal": {
|
||||
"desc1": "Activează meniul intern pentru dezvoltatori Valve.",
|
||||
"desc2": "Nu atingeți nimic din acest meniu decât dacă știți ce face.",
|
||||
"label": "Activare Valve Internă"
|
||||
}
|
||||
},
|
||||
"SettingsGeneralIndex": {
|
||||
"about": {
|
||||
"decky_version": "Versiunea Decky",
|
||||
"header": "Despre"
|
||||
},
|
||||
"beta": {
|
||||
"header": "Participarea la beta"
|
||||
},
|
||||
"developer_mode": {
|
||||
"label": "Mod dezvoltator"
|
||||
},
|
||||
"notifications": {
|
||||
"decky_updates_label": "Actualizare Decky disponibilă",
|
||||
"header": "Notificări",
|
||||
"plugin_updates_label": "Actualizări de pluginuri disponibile"
|
||||
},
|
||||
"other": {
|
||||
"header": "Alte"
|
||||
},
|
||||
"updates": {
|
||||
"header": "Actualizări"
|
||||
}
|
||||
},
|
||||
"SettingsIndex": {
|
||||
"developer_title": "Dezvoltator",
|
||||
"general_title": "General",
|
||||
"plugins_title": "Pluginuri",
|
||||
"testing_title": "Testare"
|
||||
},
|
||||
"Store": {
|
||||
"download_progress_info": {
|
||||
"download_remote": "Descărcarea oricăror binări externe",
|
||||
"download_zip": "Se descarcă pluginul",
|
||||
"increment_count": "Se mărește numărul de descărcări",
|
||||
"installing_plugin": "Se instalează plugin",
|
||||
"open_zip": "Deschiderea fișierului zip",
|
||||
"parse_zip": "Analizarea fișierului zip",
|
||||
"start": "Inițializare",
|
||||
"uninstalling_previous": "Se dezinstalează copia anterioară"
|
||||
},
|
||||
"store_contrib": {
|
||||
"desc": "Dacă doriți să contribuiți la Magazinul de Pluginuri Decky, verificați depozitul SteamDeckHomebrew/decky-plugin-template de pe GitHub. Informații despre dezvoltare și distribuție sunt disponibile în fișierul README.",
|
||||
"label": "Contribuţii"
|
||||
},
|
||||
"store_filter": {
|
||||
"label": "Filtru",
|
||||
"label_def": "Toate"
|
||||
},
|
||||
"store_search": {
|
||||
"label": "Căutare"
|
||||
},
|
||||
"store_sort": {
|
||||
"label": "Sortează",
|
||||
"label_def": "Ultima actualizare (cele mai noi)"
|
||||
},
|
||||
"store_source": {
|
||||
"desc": "Tot codul sursă al pluginului este disponibil în depozitul SteamDeckHomebrew/decky-plugin-database de pe GitHub.",
|
||||
"label": "Cod sursă"
|
||||
},
|
||||
"store_tabs": {
|
||||
"about": "Despre",
|
||||
"alph_asce": "Alfabetic (de la Z la A)",
|
||||
"alph_desc": "Alfabetic (de la A la Z)",
|
||||
"date_asce": "Cele mai vechi primele",
|
||||
"date_desc": "Cele mai noi primele",
|
||||
"downloads_asce": "Cele mai puțin descărcate primele",
|
||||
"downloads_desc": "Cele mai descărcate primele",
|
||||
"title": "Răsfoiți"
|
||||
},
|
||||
"store_testing_cta": "Vă rugăm să luați în considerare testarea la noi plugin-uri pentru a ajuta echipa Decky Loader!",
|
||||
"store_testing_warning": {
|
||||
"desc": "Puteți folosi acest canal de magazin pentru a testa versiuni de pluginuri de ultimă generație. Asigură-te că lași feedback pe GitHub, astfel încât pluginul să poată fi actualizat pentru toți utilizatorii.",
|
||||
"label": "Bun venit la Canalul Magazinului de Testare"
|
||||
}
|
||||
},
|
||||
"StoreSelect": {
|
||||
"custom_store": {
|
||||
"label": "Magazin personalizat",
|
||||
"url_label": "URL"
|
||||
},
|
||||
"store_channel": {
|
||||
"custom": "Personalizat",
|
||||
"default": "Standard",
|
||||
"label": "Canalul Magazinului",
|
||||
"testing": "Testare"
|
||||
}
|
||||
},
|
||||
"Testing": {
|
||||
"download": "Descărcare",
|
||||
"error": "Eroare la instalarea PR-ului",
|
||||
"header": "Următoarele versiuni de Decky Loader sunt construite pe baza unor solicitări de extragere (Pull Requests) deschise de la terți. Echipa Decky Loader nu a verificat funcționalitatea sau securitatea acestora și este posibil să fie învechite.",
|
||||
"loading": "Se încarcă cererile de extragere deschise...",
|
||||
"start_download_toast": "Se descarcă PR #{{id}}"
|
||||
},
|
||||
"TitleView": {
|
||||
"decky_store_desc": "Deschide Magazinul Decky",
|
||||
"settings_desc": "Deschide Setările Decky"
|
||||
},
|
||||
"Updater": {
|
||||
"decky_updates": "Actualizări Decky",
|
||||
"no_patch_notes_desc": "Nu există note de patch pentru această versiune",
|
||||
"patch_notes_desc": "Note de patch",
|
||||
"updates": {
|
||||
"check_button": "Verificare pentru actualizări",
|
||||
"checking": "Verificare",
|
||||
"cur_version": "Versiunea curentă: {{ver}}",
|
||||
"install_button": "Instalați actualizarea",
|
||||
"label": "Actualizări",
|
||||
"lat_version": "La zi cu actualizările: rulează {{ver}}",
|
||||
"reloading": "Reîncărcare",
|
||||
"updating": "Actualizare"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -52,7 +52,9 @@
|
||||
"MultiplePluginsInstallModal": {
|
||||
"confirm": "Вы уверены, что хотите внести следующие изменения?",
|
||||
"description": {
|
||||
"downgrade": "Откатить {{name}} до {{version}}",
|
||||
"install": "Установить {{name}} {{version}}",
|
||||
"overwrite": "Заменить {{name}} на {{version}}",
|
||||
"reinstall": "Переустановить {{name}} {{version}}",
|
||||
"update": "Обновить с {{name}} на {{version}}"
|
||||
},
|
||||
@@ -61,12 +63,18 @@
|
||||
"loading": "В процессе"
|
||||
},
|
||||
"title": {
|
||||
"downgrade_few": "Откатить {{count}} плагина",
|
||||
"downgrade_many": "Откатить {{count}} плагинов",
|
||||
"downgrade_one": "Откатить {{count}} плагин",
|
||||
"install_few": "Установить {{count}} плагинов",
|
||||
"install_many": "Установить {{count}} плагинов",
|
||||
"install_one": "Установить {{count}} плагин",
|
||||
"mixed_few": "Изменить {{count}} плагинов",
|
||||
"mixed_many": "Изменить {{count}} плагинов",
|
||||
"mixed_one": "Изменить {{count}} плагин",
|
||||
"overwrite_few": "Перезаписать {{count}} плагина",
|
||||
"overwrite_many": "Перезаписать {{count}} плагинов",
|
||||
"overwrite_one": "Перезаписать {{count}} плагин",
|
||||
"reinstall_few": "Переустановить {{count}} плагинов",
|
||||
"reinstall_many": "Переустановить {{count}} плагинов",
|
||||
"reinstall_one": "Переустановить {{count}} плагин",
|
||||
@@ -76,12 +84,22 @@
|
||||
}
|
||||
},
|
||||
"PluginCard": {
|
||||
"plugin_downgrade": "Откат",
|
||||
"plugin_full_access": "Этот плагин имеет полный доступ к вашему Steam Deck.",
|
||||
"plugin_install": "Установить",
|
||||
"plugin_no_desc": "Нет описания.",
|
||||
"plugin_overwrite": "Замена",
|
||||
"plugin_reinstall": "Переустановка",
|
||||
"plugin_update": "Обновление",
|
||||
"plugin_version_label": "Версия плагина"
|
||||
},
|
||||
"PluginInstallModal": {
|
||||
"downgrade": {
|
||||
"button_idle": "Откат",
|
||||
"button_processing": "Откатывание",
|
||||
"desc": "Вы уверенны, что хотите откатить {{artifact}} до версии {{version}}?",
|
||||
"title": "Откатить {{artifact}}"
|
||||
},
|
||||
"install": {
|
||||
"button_idle": "Установить",
|
||||
"button_processing": "Установка",
|
||||
@@ -89,6 +107,13 @@
|
||||
"title": "Установить {{artifact}}"
|
||||
},
|
||||
"no_hash": "У данного плагина отсутствует хэш, устанавливайте на свой страх и риск.",
|
||||
"not_installed": "(не установлено)",
|
||||
"overwrite": {
|
||||
"button_idle": "Перезаписать",
|
||||
"button_processing": "Перезаписывание",
|
||||
"desc": "Вы уверены, что хотите перезаписать {{artifact}} версией {{version}}?",
|
||||
"title": "Перезаписать {{artifact}}"
|
||||
},
|
||||
"reinstall": {
|
||||
"button_idle": "Переустановить",
|
||||
"button_processing": "Переустановка",
|
||||
@@ -98,7 +123,7 @@
|
||||
"update": {
|
||||
"button_idle": "Обновить",
|
||||
"button_processing": "Обновление",
|
||||
"desc": "Вы уверены, что хотите обновить {{artifact}} {{version}}?",
|
||||
"desc": "Вы уверены, что хотите обновить {{artifact}} до версии {{version}}?",
|
||||
"title": "Обновить {{artifact}}"
|
||||
}
|
||||
},
|
||||
@@ -205,6 +230,16 @@
|
||||
"testing_title": "Тестирование"
|
||||
},
|
||||
"Store": {
|
||||
"download_progress_info": {
|
||||
"download_remote": "Загрузка внешних бинарных файлов",
|
||||
"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": "Помощь проекту"
|
||||
@@ -253,7 +288,11 @@
|
||||
}
|
||||
},
|
||||
"Testing": {
|
||||
"download": "Загрузить"
|
||||
"download": "Загрузить",
|
||||
"error": "Ошибка при установке PR",
|
||||
"header": "Данные версии Decky Loader созданы на основе сторонних pull requst. Команда Decky Loader не проверяла их функциональность и безопасность, и они могут быть устаревшими.",
|
||||
"loading": "Загрузка открытых pull requst'ов...",
|
||||
"start_download_toast": "Загрузка PR#{{id}}"
|
||||
},
|
||||
"TitleView": {
|
||||
"decky_store_desc": "Открыть магазин Decky",
|
||||
|
||||
@@ -8,9 +8,9 @@
|
||||
}
|
||||
},
|
||||
"Developer": {
|
||||
"5secreload": "Omladdning på 5 sekunder",
|
||||
"disabling": "Inaktivera React DevTools",
|
||||
"enabling": "Aktivera React DevTools"
|
||||
"5secreload": "Uppdaterar om 5 sekunder",
|
||||
"disabling": "Inaktiverar React DevTools",
|
||||
"enabling": "Aktiverar React DevTools"
|
||||
},
|
||||
"DropdownMultiselect": {
|
||||
"button": {
|
||||
@@ -29,19 +29,19 @@
|
||||
"select": "Välj denna fil"
|
||||
},
|
||||
"files": {
|
||||
"all_files": "Alla Filer",
|
||||
"all_files": "Alla filer",
|
||||
"file_type": "Filtyp",
|
||||
"show_hidden": "Visa dolda filer"
|
||||
},
|
||||
"filter": {
|
||||
"created_asce": "Skapad (Äldst)",
|
||||
"created_asce": "Skapad (äldst)",
|
||||
"created_desc": "Skapad (nyast)",
|
||||
"modified_asce": "Modifierad (Äldst)",
|
||||
"modified_asce": "Modifierad (äldst)",
|
||||
"modified_desc": "Modifierad (nyaste)",
|
||||
"name_asce": "Z-A",
|
||||
"name_desc": "A-Z",
|
||||
"size_asce": "Storlek (minst)",
|
||||
"size_desc": "Storlek (Störst)"
|
||||
"size_desc": "Storlek (störst)"
|
||||
},
|
||||
"folder": {
|
||||
"label": "Mapp",
|
||||
@@ -52,7 +52,9 @@
|
||||
"MultiplePluginsInstallModal": {
|
||||
"confirm": "Är du säker på att du vill göra följande ändringar?",
|
||||
"description": {
|
||||
"downgrade": "Nedgradera {{name}} till {{version}}",
|
||||
"install": "Installera {{name}} {{version}}",
|
||||
"overwrite": "Skriv över {{name}} med {{version}}",
|
||||
"reinstall": "Installera om {{name}} {{version}}",
|
||||
"update": "Uppdatera {{name}} {{version}}"
|
||||
},
|
||||
@@ -61,26 +63,245 @@
|
||||
"loading": "Arbetar"
|
||||
},
|
||||
"title": {
|
||||
"downgrade_one": "Nedgradera 1 insticksmodul",
|
||||
"downgrade_other": "Nedgradera {{count}} insticksmoduler",
|
||||
"install_one": "Install 1 tillägg",
|
||||
"install_other": "Installerar {{count}} tillägg",
|
||||
"mixed_one": "",
|
||||
"mixed_other": "",
|
||||
"reinstall_one": "",
|
||||
"reinstall_other": "",
|
||||
"update_one": "",
|
||||
"update_other": ""
|
||||
"mixed_one": "Ändra {{count}} insticksmodul",
|
||||
"mixed_other": "Ändra {{count}} insticksmoduler",
|
||||
"overwrite_one": "Skriv över 1 insticksmodul",
|
||||
"overwrite_other": "Skriv över {{count}} insticksmoduler",
|
||||
"reinstall_one": "Installera om 1 insticksmodul",
|
||||
"reinstall_other": "Installera om {{count}} insticksmoduler",
|
||||
"update_one": "Uppdatera 1 insticksmodul",
|
||||
"update_other": "Uppdatera {{count}} insticksmoduler"
|
||||
}
|
||||
},
|
||||
"PluginCard": {
|
||||
"plugin_downgrade": "Nedgradera",
|
||||
"plugin_full_access": "Denna insticksmodul har full åtkomst till din Steam Deck.",
|
||||
"plugin_install": "Installera",
|
||||
"plugin_no_desc": "Ingen beskrivning angavs.",
|
||||
"plugin_overwrite": "Skriv över",
|
||||
"plugin_reinstall": "Installera om",
|
||||
"plugin_update": "Uppdatera",
|
||||
"plugin_version_label": "Version av insticksmodul"
|
||||
},
|
||||
"PluginInstallModal": {
|
||||
"downgrade": {
|
||||
"button_idle": "Nedgradera",
|
||||
"button_processing": "Nedgraderar",
|
||||
"desc": "Är du säker på att du vill nedgradera {{artifact}} till version {{version}}?",
|
||||
"title": "Nedgradera {{artifact}}"
|
||||
},
|
||||
"install": {
|
||||
"button_idle": "Installera",
|
||||
"button_processing": "Installerar",
|
||||
"desc": "Är du säker på att du vill installera {{artifact}} {{version}}?",
|
||||
"title": "Installera {{artifact}}"
|
||||
},
|
||||
"no_hash": "Denna insticksmodul har inte någon kontrollsumma. Du installerar den på egen risk.",
|
||||
"not_installed": "(inte installerad)",
|
||||
"overwrite": {
|
||||
"button_idle": "Skriv över",
|
||||
"button_processing": "Skriver över",
|
||||
"desc": "Är du säker på att du vill skriva över {{artifact}} med version {{version}}?",
|
||||
"title": "Skriv över {{artifact}}"
|
||||
},
|
||||
"reinstall": {
|
||||
"button_idle": "Installera om",
|
||||
"button_processing": "Installerar om",
|
||||
"desc": "Är du säker på att du vill installera om {{artifact}} {{version}}?",
|
||||
"title": "Installera om {{artifact}}"
|
||||
},
|
||||
"update": {
|
||||
"button_idle": "Uppdatera",
|
||||
"button_processing": "Uppdaterar",
|
||||
"desc": "Är du säker på att du vill uppdatera {{artifact}} till version {{version}}?",
|
||||
"title": "Uppdatera {{artifact}}"
|
||||
}
|
||||
},
|
||||
"PluginListIndex": {
|
||||
"update_all_one": "",
|
||||
"update_all_other": ""
|
||||
"freeze": "Frys uppdateringar",
|
||||
"hide": "Snabbåtkomst: Dölj",
|
||||
"no_plugin": "Inga insticksmoduler installerade!",
|
||||
"plugin_actions": "Åtgärder för insticksmodul",
|
||||
"reinstall": "Installera om",
|
||||
"reload": "Uppdatera",
|
||||
"show": "Snabbåtkomst: Visa",
|
||||
"unfreeze": "Tillåt uppdateringar",
|
||||
"uninstall": "Avinstallera",
|
||||
"update_all_one": "Uppdatera 1 insticksmodul",
|
||||
"update_all_other": "Uppdatera {{count}} insticksmoduler",
|
||||
"update_to": "Uppdatera till {{name}}"
|
||||
},
|
||||
"PluginListLabel": {
|
||||
"hidden": "Dold från snabbåtkomstmenyn"
|
||||
},
|
||||
"PluginLoader": {
|
||||
"plugin_update_one": "",
|
||||
"plugin_update_other": ""
|
||||
"decky_title": "Decky",
|
||||
"decky_update_available": "Uppdatering till {{tag_name}} finns!",
|
||||
"error": "Fel",
|
||||
"plugin_error_uninstall": "Inläsning av {{name}} orsakade ett undantag som visas nedan. Detta betyder oftast att insticksmodulen kräver en uppdatering för den nya versionen av SteamUI. Kontrollera om en uppdatering finns eller fundera på att ta bort den i Decky-inställningarna, under insticksmoduler.",
|
||||
"plugin_load_error": {
|
||||
"message": "Fel vid inläsning av insticksmodulen {{name}}",
|
||||
"toast": "Fel vid inläsning {{name}}"
|
||||
},
|
||||
"plugin_uninstall": {
|
||||
"button": "Avinstallera",
|
||||
"desc": "Är du säker på att du vill avinstallera {{name}}?",
|
||||
"title": "Avinstallera {{name}}"
|
||||
},
|
||||
"plugin_update_one": "Uppdateringar tillgängliga för 1 insticksmodul!",
|
||||
"plugin_update_other": "Uppdateringar tillgängliga för {{count}} insticksmoduler!"
|
||||
},
|
||||
"PluginView": {
|
||||
"hidden_one": "",
|
||||
"hidden_other": ""
|
||||
"hidden_one": "1 insticksmodul är dold från denna lista",
|
||||
"hidden_other": "{{count}} insticksmoduler är dolda från denna lista"
|
||||
},
|
||||
"RemoteDebugging": {
|
||||
"remote_cef": {
|
||||
"desc": "Tillåt oautentiserad åtkomst till CEF-felsökaren till vem som helst i ditt nätverk",
|
||||
"label": "Tillåt fjärr-CEF-felsökning"
|
||||
}
|
||||
},
|
||||
"SettingsDeveloperIndex": {
|
||||
"cef_console": {
|
||||
"button": "Öppna konsoll",
|
||||
"desc": "Öppnar CEF-konsollen. Endast användbart för felsökningssyften. Saker här är potentiellt farliga och bör endast användas om du utvecklar insticksmoduler.",
|
||||
"label": "CEF-konsoll"
|
||||
},
|
||||
"header": "Övrigt",
|
||||
"react_devtools": {
|
||||
"desc": "Aktiverar anslutning till en dator som kör React DevTools. Ändring av denna inställning kommer att läsa om Steam. Ställ in IP-adressen innan du aktiverar.",
|
||||
"ip_label": "IP",
|
||||
"label": "Aktivera React DevTools"
|
||||
},
|
||||
"third_party_plugins": {
|
||||
"button_install": "Installera",
|
||||
"button_zip": "Bläddra",
|
||||
"header": "Insticksmoduler från tredjepart",
|
||||
"label_desc": "URL",
|
||||
"label_url": "Installera insticksmodul från URL",
|
||||
"label_zip": "Installera insticksmodul från ZIP-fil"
|
||||
},
|
||||
"valve_internal": {
|
||||
"desc1": "Aktiverar Valves interna utvecklarmeny.",
|
||||
"desc2": "Rör ingenting i denna meny såvida inte du vet vad du gör.",
|
||||
"label": "Aktivera Valves interna"
|
||||
}
|
||||
},
|
||||
"SettingsGeneralIndex": {
|
||||
"about": {
|
||||
"decky_version": "Decky-version",
|
||||
"header": "Om"
|
||||
},
|
||||
"beta": {
|
||||
"header": "Delta i betatestning"
|
||||
},
|
||||
"developer_mode": {
|
||||
"label": "Utvecklarläge"
|
||||
},
|
||||
"notifications": {
|
||||
"decky_updates_label": "Uppdatering till Decky finns tillgänglig",
|
||||
"header": "Aviseringar",
|
||||
"plugin_updates_label": "Uppdateringar till insticksmoduler tillgängliga"
|
||||
},
|
||||
"other": {
|
||||
"header": "Övrigt"
|
||||
},
|
||||
"updates": {
|
||||
"header": "Uppdateringar"
|
||||
}
|
||||
},
|
||||
"SettingsIndex": {
|
||||
"developer_title": "Utvecklare",
|
||||
"general_title": "Allmänt",
|
||||
"plugins_title": "Insticksmoduler",
|
||||
"testing_title": "Testning"
|
||||
},
|
||||
"Store": {
|
||||
"download_progress_info": {
|
||||
"download_remote": "Hämtar externa binärfiler",
|
||||
"download_zip": "Hämtar insticksmodul",
|
||||
"increment_count": "Ökar hämtningsantal",
|
||||
"installing_plugin": "Installerar insticksmodul",
|
||||
"open_zip": "Öppnar zip-fil",
|
||||
"parse_zip": "Tolkar zip-fil",
|
||||
"start": "Initierar",
|
||||
"uninstalling_previous": "Avinstallerar tidigare kopia"
|
||||
},
|
||||
"store_contrib": {
|
||||
"desc": "Om du vill bidra till Deckys insticksmoduler, titta in i förrådet SteamDeckHomebrew/decky-plugin-template på GitHub. Information om utveckling och distribution finns tillgänglig i filen README.",
|
||||
"label": "Bidra"
|
||||
},
|
||||
"store_filter": {
|
||||
"label": "Filtrera",
|
||||
"label_def": "Alla"
|
||||
},
|
||||
"store_search": {
|
||||
"label": "Sök"
|
||||
},
|
||||
"store_sort": {
|
||||
"label": "Sortera",
|
||||
"label_def": "Senast uppdaterad (Senaste)"
|
||||
},
|
||||
"store_source": {
|
||||
"desc": "All källkod för insticksmoduler finns tillgänglig i förrådet SteamDeckHomebrew/decky-plugin-database på GitHub.",
|
||||
"label": "Källkod"
|
||||
},
|
||||
"store_tabs": {
|
||||
"about": "Om",
|
||||
"alph_asce": "Alfabetisk (Z till A)",
|
||||
"alph_desc": "Alfabetisk (A till Z)",
|
||||
"date_asce": "Äldsta först",
|
||||
"date_desc": "Senaste först",
|
||||
"downloads_asce": "Minst hämtade först",
|
||||
"downloads_desc": "Mest hämtade först",
|
||||
"title": "Bläddra"
|
||||
},
|
||||
"store_testing_cta": "Överväg att testa nya insticksmoduler för att hjälpa Decky Loader-teamet!",
|
||||
"store_testing_warning": {
|
||||
"desc": "Du kan använda denna kanal för att testa de absolut senaste versionerna. Tänk på att ge återkoppling på GitHub så att insticksmodulen kan uppdateras för alla användare.",
|
||||
"label": "Välkommen till testkanalen"
|
||||
}
|
||||
},
|
||||
"StoreSelect": {
|
||||
"custom_store": {
|
||||
"label": "Anpassad affär",
|
||||
"url_label": "URL"
|
||||
},
|
||||
"store_channel": {
|
||||
"custom": "Anpassad",
|
||||
"default": "Standard",
|
||||
"label": "Affärskanal",
|
||||
"testing": "Testning"
|
||||
}
|
||||
},
|
||||
"Testing": {
|
||||
"download": "Hämta",
|
||||
"error": "Fel vid installation av PR",
|
||||
"header": "Följande versioner av Decky Loader byggs från öppnade Pull Requests från tredjepart. Decky Loader-teamet har inte verifierat deras funktionalitet eller säkerhet och de kan vara utdaterade.",
|
||||
"loading": "Läser in öppnade Pull Requests...",
|
||||
"start_download_toast": "Hämtar PR #{{id}}"
|
||||
},
|
||||
"TitleView": {
|
||||
"decky_store_desc": "Öppna Decky-affär",
|
||||
"settings_desc": "Öppna Decky-inställningar"
|
||||
},
|
||||
"Updater": {
|
||||
"decky_updates": "Decky-uppdateringar",
|
||||
"no_patch_notes_desc": "inga patch-anteckningar för denna version",
|
||||
"patch_notes_desc": "Patch-anteckningar",
|
||||
"updates": {
|
||||
"check_button": "Leta efter uppdateringar",
|
||||
"checking": "Letar",
|
||||
"cur_version": "Aktuell version: {{ver}}",
|
||||
"install_button": "Installera uppdatering",
|
||||
"label": "Uppdateringar",
|
||||
"lat_version": "Uppdaterad: kör {{ver}}",
|
||||
"reloading": "Läser om",
|
||||
"updating": "Uppdaterar"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,9 @@
|
||||
},
|
||||
"FilePickerError": {
|
||||
"errors": {
|
||||
"file_not_found": "Belirtilen yol geçerli değil. Lütfen yolu kontrol edin ve doğru şekilde yeniden girin."
|
||||
"file_not_found": "Belirtilen yol geçerli değil. Lütfen yolu kontrol edin ve doğru şekilde yeniden girin.",
|
||||
"perm_denied": "Belirtilen dizine erişim izniniz yok. Lütfen kullanıcınızın (Steam Deck'te deck) belirtilen klasöre/dosyaya erişmek için ilgili izne sahip olup olmadığını kontrol edin.",
|
||||
"unknown": "Bilinmeyen bir hata oluştu. Raw hata: {{raw_error}}"
|
||||
}
|
||||
},
|
||||
"FilePickerIndex": {
|
||||
@@ -46,7 +48,8 @@
|
||||
"confirm": "Aşağıdaki değişiklikleri yapmak istediğinizden emin misiniz?",
|
||||
"description": {
|
||||
"install": "Yükle {{name}} {{version}}",
|
||||
"reinstall": "Yeniden yükle {{name}} {{version}}"
|
||||
"reinstall": "Yeniden yükle {{name}} {{version}}",
|
||||
"update": "{{name}}'i {{version}}'ye güncelle"
|
||||
},
|
||||
"ok_button": {
|
||||
"idle": "Onayla",
|
||||
@@ -55,17 +58,18 @@
|
||||
"title": {
|
||||
"install_one": "1 eklenti yükle",
|
||||
"install_other": "{{count}} eklenti yükle",
|
||||
"mixed_one": "",
|
||||
"mixed_other": "",
|
||||
"mixed_one": "{{count}} eklentiyi değiştir",
|
||||
"mixed_other": "{{count}} eklentiyi değiştir",
|
||||
"reinstall_one": "1 eklentiyi yeniden yükle",
|
||||
"reinstall_other": "{{count}} eklentiyi yeniden yükle",
|
||||
"update_one": "",
|
||||
"update_other": ""
|
||||
"update_one": "{{count}} eklentiyi güncelle",
|
||||
"update_other": "{{count}} eklentiyi güncelle"
|
||||
}
|
||||
},
|
||||
"PluginCard": {
|
||||
"plugin_full_access": "Bu eklenti Steam Deck'inize tam erişime sahiptir.",
|
||||
"plugin_install": "Yükle",
|
||||
"plugin_no_desc": "Açıklama bulunmuyor.",
|
||||
"plugin_version_label": "Eklenti Versiyonu"
|
||||
},
|
||||
"PluginInstallModal": {
|
||||
@@ -94,6 +98,7 @@
|
||||
"no_plugin": "Yüklü eklenti yok!",
|
||||
"plugin_actions": "Eklenti İşlemleri",
|
||||
"reinstall": "Yeniden Yükle",
|
||||
"reload": "Yenile",
|
||||
"show": "Hızlı erişim: Göster",
|
||||
"unfreeze": "Güncellemelere izin ver",
|
||||
"uninstall": "Kaldır",
|
||||
|
||||
@@ -12,15 +12,49 @@
|
||||
"disabling": "Вимкнення React DevTools",
|
||||
"enabling": "Увімкнення React DevTools"
|
||||
},
|
||||
"DropdownMultiselect": {
|
||||
"button": {
|
||||
"back": "Назад"
|
||||
}
|
||||
},
|
||||
"FilePickerError": {
|
||||
"errors": {
|
||||
"file_not_found": "Вказаний шлях недійсний. Будь ласка, перевірте його та введіть правильно.",
|
||||
"perm_denied": "У вас немає доступу до вказаного каталогу. Будь ласка, перевірте, чи має ваш користувач (deck на Steam Deck) відповідні права доступу до зазначеної папки/файлу.",
|
||||
"unknown": "Сталася невідома помилка. Деталі помилки: {{raw_error}}"
|
||||
}
|
||||
},
|
||||
"FilePickerIndex": {
|
||||
"file": {
|
||||
"select": "Виберіть цей файл"
|
||||
},
|
||||
"files": {
|
||||
"all_files": "Усі файли",
|
||||
"file_type": "Тип файлу",
|
||||
"show_hidden": "Показати приховані файли"
|
||||
},
|
||||
"filter": {
|
||||
"created_asce": "Створено (спочатку найстаріші)",
|
||||
"created_desc": "Створено (спочатку найновіші)",
|
||||
"modified_asce": "Змінено (спочатку найстаріші)",
|
||||
"modified_desc": "Змінено (спочатку найновіші)",
|
||||
"name_asce": "Я-А",
|
||||
"name_desc": "А-Я",
|
||||
"size_asce": "Розмір (спочатку найменші)",
|
||||
"size_desc": "Розмір (спочатку найбільші)"
|
||||
},
|
||||
"folder": {
|
||||
"select": "Використовувати цю папку"
|
||||
"label": "Папка",
|
||||
"select": "Використовувати цю папку",
|
||||
"show_more": "Показати більше файлів"
|
||||
}
|
||||
},
|
||||
"MultiplePluginsInstallModal": {
|
||||
"confirm": "Ви впевнені, що хочете застосувати такі модифікації?",
|
||||
"description": {
|
||||
"downgrade": "Понизити {{name}} до версії {{version}}",
|
||||
"install": "Встановити {{name}} {{version}}",
|
||||
"overwrite": "Перезаписати {{name}} версією {{version}}",
|
||||
"reinstall": "Перевстановити {{name}} {{version}}",
|
||||
"update": "Оновити {{name}} до {{version}}"
|
||||
},
|
||||
@@ -29,27 +63,43 @@
|
||||
"loading": "Опрацювання"
|
||||
},
|
||||
"title": {
|
||||
"install_few": "Встановити {{count}} плагінів",
|
||||
"downgrade_few": "Понизити {{count}} плагіни",
|
||||
"downgrade_many": "Понизити {{count}} плагінів",
|
||||
"downgrade_one": "Понизити {{count}} плагін",
|
||||
"install_few": "Встановити {{count}} плагіни",
|
||||
"install_many": "Встановити {{count}} плагінів",
|
||||
"install_one": "Встановити 1 плагін",
|
||||
"mixed_few": "Модифікувати {{count}} плагінів",
|
||||
"mixed_many": "",
|
||||
"mixed_one": "Модифікувати 1 плагін",
|
||||
"reinstall_few": "Перевстановити {{count}} плагінів",
|
||||
"install_one": "Встановити {{count}} плагін",
|
||||
"mixed_few": "Модифікувати {{count}} плагіни",
|
||||
"mixed_many": "Модифікувати {{count}} плагінів",
|
||||
"mixed_one": "Модифікувати {{count}} плагін",
|
||||
"overwrite_few": "Перезаписати {{count}} плагіни",
|
||||
"overwrite_many": "Перезаписати {{count}} плагінів",
|
||||
"overwrite_one": "Перезаписати {{count}} плагін",
|
||||
"reinstall_few": "Перевстановити {{count}} плагіни",
|
||||
"reinstall_many": "Перевстановити {{count}} плагінів",
|
||||
"reinstall_one": "Перевстановити 1 плагін",
|
||||
"update_few": "Оновити {{count}} плагінів",
|
||||
"reinstall_one": "Перевстановити {{count}} плагін",
|
||||
"update_few": "Оновити {{count}} плагіни",
|
||||
"update_many": "Оновити {{count}} плагінів",
|
||||
"update_one": "Оновити 1 плагін"
|
||||
"update_one": "Оновити {{count}} плагін"
|
||||
}
|
||||
},
|
||||
"PluginCard": {
|
||||
"plugin_downgrade": "Понизити",
|
||||
"plugin_full_access": "Цей плагін має повний доступ до вашого Steam Deck.",
|
||||
"plugin_install": "Встановити",
|
||||
"plugin_no_desc": "Опис не надано.",
|
||||
"plugin_overwrite": "Перезаписати",
|
||||
"plugin_reinstall": "Перевстановити",
|
||||
"plugin_update": "Оновити",
|
||||
"plugin_version_label": "Версія плагіна"
|
||||
},
|
||||
"PluginInstallModal": {
|
||||
"downgrade": {
|
||||
"button_idle": "Понизити",
|
||||
"button_processing": "Пониження",
|
||||
"desc": "Ви впевнені, що хочете понизити {{artifact}} до версії {{version}}?",
|
||||
"title": "Понизити {{artifact}}"
|
||||
},
|
||||
"install": {
|
||||
"button_idle": "Встановити",
|
||||
"button_processing": "Встановлення",
|
||||
@@ -57,6 +107,13 @@
|
||||
"title": "Встановити {{artifact}}"
|
||||
},
|
||||
"no_hash": "Цей плагін не має хешу, ви встановлюєте його на власний ризик.",
|
||||
"not_installed": "(не встановлено)",
|
||||
"overwrite": {
|
||||
"button_idle": "Перезаписати",
|
||||
"button_processing": "Перезаписування",
|
||||
"desc": "Ви впевнені, що хочете перезаписати {{artifact}} версією {{version}}?",
|
||||
"title": "Перезаписати {{artifact}}"
|
||||
},
|
||||
"reinstall": {
|
||||
"button_idle": "Перевстановити",
|
||||
"button_processing": "Перевстановлення",
|
||||
@@ -66,21 +123,23 @@
|
||||
"update": {
|
||||
"button_idle": "Оновити",
|
||||
"button_processing": "Оновлення",
|
||||
"desc": "Ви впевнені, що хочете оновити {{artifact}} {{version}}?",
|
||||
"desc": "Ви впевнені, що хочете оновити {{artifact}} до версії {{version}}?",
|
||||
"title": "Оновити {{artifact}}"
|
||||
}
|
||||
},
|
||||
"PluginListIndex": {
|
||||
"freeze": "Заморозити оновлення",
|
||||
"hide": "Швидкий доступ: Приховати",
|
||||
"no_plugin": "Плагінів не встановлено!",
|
||||
"plugin_actions": "Дії плагінів",
|
||||
"reinstall": "Перевстановити",
|
||||
"reload": "Перезавантажити",
|
||||
"show": "Швидкий доступ: Показати",
|
||||
"unfreeze": "Дозволити оновлення",
|
||||
"uninstall": "Видалити",
|
||||
"update_all_few": "Оновити {{count}} плагінів",
|
||||
"update_all_few": "Оновити {{count}} плагіни",
|
||||
"update_all_many": "Оновити {{count}} плагінів",
|
||||
"update_all_one": "Оновити 1 плагін",
|
||||
"update_all_one": "Оновити {{count}} плагін",
|
||||
"update_to": "Оновити {{name}}"
|
||||
},
|
||||
"PluginListLabel": {
|
||||
@@ -100,9 +159,9 @@
|
||||
"desc": "Ви впевнені, що хочете видалити {{name}}?",
|
||||
"title": "Видалити {{name}}"
|
||||
},
|
||||
"plugin_update_few": "Доступне оновлення для {{count}} плагінів!",
|
||||
"plugin_update_few": "Доступне оновлення для {{count}} плагіни!",
|
||||
"plugin_update_many": "Доступне оновлення для {{count}} плагінів!",
|
||||
"plugin_update_one": "Доступне оновлення для 1 плагіна!"
|
||||
"plugin_update_one": "Доступне оновлення для {{count}} плагіна!"
|
||||
},
|
||||
"PluginView": {
|
||||
"hidden_few": "{{count}} плагінів приховано з цього списку",
|
||||
@@ -152,6 +211,11 @@
|
||||
"developer_mode": {
|
||||
"label": "Розробницький режим"
|
||||
},
|
||||
"notifications": {
|
||||
"decky_updates_label": "Доступне оновлення Decky",
|
||||
"header": "Сповіщення",
|
||||
"plugin_updates_label": "Доступні оновлення плагінів"
|
||||
},
|
||||
"other": {
|
||||
"header": "Інше"
|
||||
},
|
||||
@@ -162,9 +226,20 @@
|
||||
"SettingsIndex": {
|
||||
"developer_title": "Розробник",
|
||||
"general_title": "Загальне",
|
||||
"plugins_title": "Плагіни"
|
||||
"plugins_title": "Плагіни",
|
||||
"testing_title": "Тестування"
|
||||
},
|
||||
"Store": {
|
||||
"download_progress_info": {
|
||||
"download_remote": "Завантаження будь-яких зовнішніх бінарних файлів",
|
||||
"download_zip": "Завантаження плагіна",
|
||||
"increment_count": "Збільшення лічильника завантажень",
|
||||
"installing_plugin": "Встановлення плагіна",
|
||||
"open_zip": "Відкриття ZIP-файлу",
|
||||
"parse_zip": "Обробка ZIP-файлу",
|
||||
"start": "Ініціалізація",
|
||||
"uninstalling_previous": "Видалення попередньої копії"
|
||||
},
|
||||
"store_contrib": {
|
||||
"desc": "Якщо ви бажаєте додати щось у Decky Plugin Store, завітайте у репозиторій SteamDeckHomebrew/decky-plugin-template на GitHub. Інформація про розробку та поширення доступна у README.",
|
||||
"label": "Зробити внесок"
|
||||
@@ -188,9 +263,17 @@
|
||||
"about": "Інформація",
|
||||
"alph_asce": "За алфавітом (Z до A)",
|
||||
"alph_desc": "За алфавітом (A до Z)",
|
||||
"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": {
|
||||
@@ -204,6 +287,17 @@
|
||||
"testing": "Тестування"
|
||||
}
|
||||
},
|
||||
"Testing": {
|
||||
"download": "Завантажити",
|
||||
"error": "Помилка встановлення PR",
|
||||
"header": "Наведені нижче версії Decky Loader зібрані з відкритих сторонніх Pull Request. Команда Decky Loader не перевіряла їхню функціональність або безпеку, і вони можуть бути застарілими.",
|
||||
"loading": "Завантаження відкритих Pull Request...",
|
||||
"start_download_toast": "Завантаження PR #{{id}}"
|
||||
},
|
||||
"TitleView": {
|
||||
"decky_store_desc": "Відкрити Decky Store",
|
||||
"settings_desc": "Відкрити налаштування Decky"
|
||||
},
|
||||
"Updater": {
|
||||
"decky_updates": "Оновлення Decky",
|
||||
"no_patch_notes_desc": "Немає нотаток до цієї версії",
|
||||
|
||||
@@ -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,15 @@
|
||||
import platform, os
|
||||
|
||||
ON_WINDOWS = platform.system() == "Windows"
|
||||
ON_LINUX = not ON_WINDOWS
|
||||
ON_MAC = platform.system() == "Darwin"
|
||||
ON_LINUX = not ON_WINDOWS and not ON_MAC
|
||||
|
||||
if ON_WINDOWS:
|
||||
from .localplatformwin import *
|
||||
from . import localplatformwin as localplatform
|
||||
elif ON_MAC:
|
||||
from .localplatformmac import *
|
||||
from . import localplatformmac as localplatform
|
||||
else:
|
||||
from .localplatformlinux import *
|
||||
from . import localplatformlinux as localplatform
|
||||
@@ -37,6 +41,9 @@ def get_live_reload() -> bool:
|
||||
def get_keep_systemd_service() -> bool:
|
||||
return os.getenv("KEEP_SYSTEMD_SERVICE", "0") == "1"
|
||||
|
||||
def get_use_cef_close_workaround() -> bool:
|
||||
return ON_LINUX and os.getenv("USE_CEF_CLOSE_WORKAROUND", "1") == "1"
|
||||
|
||||
def get_log_level() -> int:
|
||||
return {"CRITICAL": 50, "ERROR": 40, "WARNING": 30, "INFO": 20, "DEBUG": 10}[
|
||||
os.getenv("LOG_LEVEL", "INFO")
|
||||
|
||||
@@ -1,9 +1,21 @@
|
||||
from re import compile
|
||||
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 = {"LD_LIBRARY_PATH": ""}) -> 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
|
||||
@@ -47,12 +59,10 @@ def chown(path : str, user : UserType = UserType.HOST_USER, recursive : bool =
|
||||
user_str = _get_user()+":"+_get_user_group()
|
||||
elif user == UserType.EFFECTIVE_USER:
|
||||
user_str = _get_effective_user()+":"+_get_effective_user_group()
|
||||
elif user == UserType.ROOT:
|
||||
user_str = "root:root"
|
||||
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:
|
||||
@@ -75,7 +85,7 @@ def chmod(path : str, permissions : int, recursive : bool = True) -> bool:
|
||||
|
||||
return True
|
||||
|
||||
def folder_owner(path : str) -> UserType|None:
|
||||
def file_owner(path : str) -> UserType|None:
|
||||
user_owner = _get_user_owner(path)
|
||||
|
||||
if (user_owner == _get_user()):
|
||||
@@ -94,48 +104,52 @@ def get_home_path(user : UserType = UserType.HOST_USER) -> str:
|
||||
user_name = _get_user()
|
||||
elif user == UserType.EFFECTIVE_USER:
|
||||
user_name = _get_effective_user()
|
||||
elif user == UserType.ROOT:
|
||||
pass
|
||||
else:
|
||||
raise Exception("Unknown User Type")
|
||||
|
||||
return pwd.getpwnam(user_name).pw_dir
|
||||
|
||||
def get_effective_username() -> str:
|
||||
return _get_effective_user()
|
||||
|
||||
def get_username() -> str:
|
||||
return _get_user()
|
||||
|
||||
def setgid(user : UserType = UserType.HOST_USER):
|
||||
user_id = 0
|
||||
|
||||
if user == UserType.HOST_USER:
|
||||
user_id = _get_user_group_id()
|
||||
elif user == UserType.ROOT:
|
||||
host_user_group_id, effective_user_group_id = _get_user_group_id(), _get_effective_user_group_id()
|
||||
if host_user_group_id == effective_user_group_id:
|
||||
pass
|
||||
elif user == UserType.HOST_USER:
|
||||
os.setgid(host_user_group_id)
|
||||
elif user == UserType.EFFECTIVE_USER:
|
||||
os.setgid(effective_user_group_id)
|
||||
else:
|
||||
raise Exception("Unknown user type")
|
||||
|
||||
os.setgid(user_id)
|
||||
|
||||
def setuid(user : UserType = UserType.HOST_USER):
|
||||
user_id = 0
|
||||
|
||||
if user == UserType.HOST_USER:
|
||||
user_id = _get_user_id()
|
||||
elif user == UserType.ROOT:
|
||||
host_user_id, effective_user_id = _get_user_id(), _get_effective_user_id()
|
||||
if host_user_id == effective_user_id:
|
||||
pass
|
||||
elif user == UserType.HOST_USER:
|
||||
os.setuid(host_user_id)
|
||||
elif user == UserType.EFFECTIVE_USER:
|
||||
os.setuid(effective_user_id)
|
||||
else:
|
||||
raise Exception("Unknown user type")
|
||||
|
||||
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"])
|
||||
logger.info("Systemd reload done.")
|
||||
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:
|
||||
@@ -144,7 +158,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:
|
||||
@@ -153,13 +167,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:
|
||||
@@ -201,7 +215,7 @@ def get_unprivileged_path() -> str:
|
||||
path = None
|
||||
|
||||
if path == None:
|
||||
logger.warn("Unprivileged path is not properly configured. Defaulting to /home/deck/homebrew")
|
||||
logger.warning("Unprivileged path is not properly configured. Defaulting to /home/deck/homebrew")
|
||||
path = "/home/deck/homebrew" # We give up
|
||||
|
||||
os.makedirs(path, exist_ok=True)
|
||||
@@ -223,7 +237,43 @@ def get_unprivileged_user() -> str:
|
||||
break
|
||||
|
||||
if user == None:
|
||||
logger.warn("Unprivileged user is not properly configured. Defaulting to 'deck'")
|
||||
logger.warning("Unprivileged user is not properly configured. Defaulting to 'deck'")
|
||||
user = 'deck'
|
||||
|
||||
return user
|
||||
|
||||
# Works around the CEF debugger TCP socket not closing properly when Steam restarts
|
||||
# Group 1 is PID, group 2 is FD. this also filters for "steamwebhelper" in the process name.
|
||||
cef_socket_lsof_regex = compile(r"^p(\d+)(?:\s|.)+csteamwebhelper(?:\s|.)+f(\d+)(?:\s|.)+TST=LISTEN")
|
||||
close_cef_socket_lock = Lock()
|
||||
|
||||
async def close_cef_socket():
|
||||
async with close_cef_socket_lock:
|
||||
if _get_effective_user_id() != 0:
|
||||
logger.warning("Can't close CEF socket as Decky isn't running as root.")
|
||||
return
|
||||
# Look for anything listening TCP on port 8080
|
||||
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(stdout.decode())
|
||||
|
||||
if not lsof_data:
|
||||
logger.error("lsof regex match failed in close_cef_socket!")
|
||||
return
|
||||
|
||||
pid = lsof_data.group(1)
|
||||
fd = lsof_data.group(2)
|
||||
|
||||
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, _, _ = await run(["gdb", "--nx", "-p", pid, "--batch", "--eval-command", f"call (int)close({fd})"])
|
||||
|
||||
if gdb_ret.returncode != 0:
|
||||
logger.error(f"Failed to close CEF socket with gdb! return code: {str(gdb_ret.returncode)}", exc_info=True)
|
||||
return
|
||||
|
||||
logger.info("CEF socket closed")
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
from ..enums import UserType
|
||||
import os, sys
|
||||
from . import localplatformlinux
|
||||
|
||||
# this should be public
|
||||
def _get_effective_user_id() -> int:
|
||||
return os.geteuid()
|
||||
|
||||
def chown(path : str, user : UserType = UserType.HOST_USER, recursive : bool = True) -> bool:
|
||||
return localplatformlinux.chown(path, user, recursive)
|
||||
|
||||
def chmod(path : str, permissions : int, recursive : bool = True) -> bool:
|
||||
return localplatformlinux.chmod(path, permissions, recursive)
|
||||
|
||||
def file_owner(path : str) -> UserType|None:
|
||||
return localplatformlinux.file_owner(path)
|
||||
|
||||
def get_home_path(user : UserType = UserType.HOST_USER) -> str:
|
||||
return localplatformlinux.get_home_path(user)
|
||||
|
||||
def setgid(user : UserType = UserType.HOST_USER):
|
||||
return localplatformlinux.setgid(user)
|
||||
|
||||
def setuid(user : UserType = UserType.HOST_USER):
|
||||
return localplatformlinux.setuid(user)
|
||||
|
||||
async def service_active(service_name : str) -> bool:
|
||||
return True # Stubbed
|
||||
|
||||
async def service_stop(service_name : str) -> bool:
|
||||
return True # Stubbed
|
||||
|
||||
async def service_start(service_name : str) -> bool:
|
||||
return True # Stubbed
|
||||
|
||||
async def service_restart(service_name : str, block : bool = True) -> bool:
|
||||
return True # Stubbed
|
||||
|
||||
def get_effective_username() -> str:
|
||||
return localplatformlinux.get_effective_username()
|
||||
|
||||
def get_username() -> str:
|
||||
return localplatformlinux.get_username()
|
||||
|
||||
def get_privileged_path() -> str:
|
||||
'''On Mac, privileged_path is equal to unprivileged_path'''
|
||||
return get_unprivileged_path()
|
||||
|
||||
def get_unprivileged_path() -> str:
|
||||
return localplatformlinux.get_unprivileged_path()
|
||||
|
||||
def get_unprivileged_user() -> str:
|
||||
return localplatformlinux.get_unprivileged_user()
|
||||
|
||||
async def restart_webhelper() -> bool:
|
||||
return await localplatformlinux.restart_webhelper()
|
||||
|
||||
async def close_cef_socket():
|
||||
return # Stubbed
|
||||
@@ -7,7 +7,7 @@ def chown(path : str, user : UserType = UserType.HOST_USER, recursive : bool =
|
||||
def chmod(path : str, permissions : int, recursive : bool = True) -> bool:
|
||||
return True # Stubbed
|
||||
|
||||
def folder_owner(path : str) -> UserType|None:
|
||||
def file_owner(path : str) -> UserType|None:
|
||||
return UserType.HOST_USER # Stubbed
|
||||
|
||||
def get_home_path(user : UserType = UserType.HOST_USER) -> str:
|
||||
@@ -28,12 +28,15 @@ 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)
|
||||
|
||||
return True # Stubbed
|
||||
|
||||
def get_effective_username() -> str:
|
||||
return os.getlogin()
|
||||
|
||||
def get_username() -> str:
|
||||
return os.getlogin()
|
||||
|
||||
@@ -55,4 +58,7 @@ def get_unprivileged_user() -> str:
|
||||
return os.getenv("UNPRIVILEGED_USER", os.getlogin())
|
||||
|
||||
async def restart_webhelper() -> bool:
|
||||
return True # Stubbed
|
||||
return True # Stubbed
|
||||
|
||||
async def close_cef_socket():
|
||||
return # Stubbed
|
||||
@@ -7,22 +7,24 @@ from .localplatform import ON_WINDOWS
|
||||
BUFFER_LIMIT = 2 ** 20 # 1 MiB
|
||||
|
||||
class UnixSocket:
|
||||
def __init__(self, on_new_message: Callable[[str], Coroutine[Any, Any, Any]]):
|
||||
def __init__(self):
|
||||
'''
|
||||
on_new_message takes 1 string argument.
|
||||
It's return value gets used, if not None, to write data to the socket.
|
||||
Method should be async
|
||||
'''
|
||||
self.socket_addr = f"/tmp/plugin_socket_{time.time()}"
|
||||
self.on_new_message = on_new_message
|
||||
self.on_new_message = None
|
||||
self.socket = None
|
||||
self.reader = None
|
||||
self.writer = None
|
||||
self.server_writer = None
|
||||
self.open_lock = asyncio.Lock()
|
||||
self.active = True
|
||||
|
||||
async def setup_server(self):
|
||||
async def setup_server(self, on_new_message: Callable[[str], Coroutine[Any, Any, Any]]):
|
||||
try:
|
||||
self.on_new_message = on_new_message
|
||||
self.socket = await asyncio.start_unix_server(self._listen_for_method_call, path=self.socket_addr, limit=BUFFER_LIMIT)
|
||||
except asyncio.CancelledError:
|
||||
await self.close_socket_connection()
|
||||
@@ -58,6 +60,8 @@ class UnixSocket:
|
||||
if self.socket:
|
||||
self.socket.close()
|
||||
await self.socket.wait_closed()
|
||||
|
||||
self.active = False
|
||||
|
||||
async def read_single_line(self) -> str|None:
|
||||
reader, _ = await self.get_socket_connection()
|
||||
@@ -81,7 +85,7 @@ class UnixSocket:
|
||||
|
||||
async def _read_single_line(self, reader: asyncio.StreamReader) -> str:
|
||||
line = bytearray()
|
||||
while True:
|
||||
while self.active:
|
||||
try:
|
||||
line.extend(await reader.readuntil())
|
||||
except asyncio.LimitOverrunError:
|
||||
@@ -91,7 +95,7 @@ class UnixSocket:
|
||||
line.extend(err.partial)
|
||||
break
|
||||
except asyncio.CancelledError:
|
||||
break
|
||||
raise
|
||||
else:
|
||||
break
|
||||
|
||||
@@ -111,7 +115,7 @@ class UnixSocket:
|
||||
|
||||
async def _listen_for_method_call(self, reader: asyncio.StreamReader, writer: asyncio.StreamWriter):
|
||||
self.server_writer = writer
|
||||
while True:
|
||||
while self.active and self.on_new_message:
|
||||
|
||||
def _(task: asyncio.Task[str|None]):
|
||||
res = task.result()
|
||||
@@ -122,18 +126,19 @@ class UnixSocket:
|
||||
asyncio.create_task(self.on_new_message(line)).add_done_callback(_)
|
||||
|
||||
class PortSocket (UnixSocket):
|
||||
def __init__(self, on_new_message: Callable[[str], Coroutine[Any, Any, Any]]):
|
||||
def __init__(self):
|
||||
'''
|
||||
on_new_message takes 1 string argument.
|
||||
It's return value gets used, if not None, to write data to the socket.
|
||||
Method should be async
|
||||
'''
|
||||
super().__init__(on_new_message)
|
||||
super().__init__()
|
||||
self.host = "127.0.0.1"
|
||||
self.port = random.sample(range(40000, 60000), 1)[0]
|
||||
|
||||
async def setup_server(self):
|
||||
async def setup_server(self, on_new_message: Callable[[str], Coroutine[Any, Any, Any]]):
|
||||
try:
|
||||
self.on_new_message = on_new_message
|
||||
self.socket = await asyncio.start_server(self._listen_for_method_call, host=self.host, port=self.port, limit=BUFFER_LIMIT)
|
||||
except asyncio.CancelledError:
|
||||
await self.close_socket_connection()
|
||||
|
||||
@@ -7,14 +7,17 @@ from .localplatform.localplatform import (chmod, chown, service_stop, service_st
|
||||
get_privileged_path, restart_webhelper)
|
||||
if hasattr(sys, '_MEIPASS'):
|
||||
chmod(sys._MEIPASS, 755) # type: ignore
|
||||
|
||||
# Full imports
|
||||
import multiprocessing
|
||||
multiprocessing.freeze_support()
|
||||
from asyncio import AbstractEventLoop, CancelledError, Task, all_tasks, current_task, gather, new_event_loop, set_event_loop, sleep
|
||||
from logging import basicConfig, getLogger
|
||||
from os import path
|
||||
from traceback import format_exc
|
||||
import multiprocessing
|
||||
|
||||
from time import time
|
||||
import aiohttp_cors # pyright: ignore [reportMissingTypeStubs]
|
||||
|
||||
# Partial imports
|
||||
from aiohttp import client_exceptions
|
||||
from aiohttp.web import Application, Response, Request, get, run_app, static # pyright: ignore [reportUnknownVariableType]
|
||||
@@ -23,7 +26,7 @@ from setproctitle import getproctitle, setproctitle, setthreadtitle
|
||||
|
||||
# local modules
|
||||
from .browser import PluginBrowser
|
||||
from .helpers import (REMOTE_DEBUGGER_UNIT, csrf_middleware, get_csrf_token, get_loader_version,
|
||||
from .helpers import (REMOTE_DEBUGGER_UNIT, create_inject_script, csrf_middleware, get_csrf_token, get_loader_version,
|
||||
mkdir_as_user, get_system_pythonpaths, get_effective_user_id)
|
||||
|
||||
from .injector import get_gamepadui_tab, Tab
|
||||
@@ -47,7 +50,7 @@ def chown_plugin_dir():
|
||||
if not path.exists(plugin_path): # For safety, create the folder before attempting to do anything with it
|
||||
mkdir_as_user(plugin_path)
|
||||
|
||||
if not chown(plugin_path, UserType.HOST_USER) or not chmod(plugin_path, 555):
|
||||
if not chown(plugin_path, UserType.EFFECTIVE_USER, False) or not chmod(plugin_path, 755, False):
|
||||
logger.error(f"chown/chmod exited with a non-zero exit code")
|
||||
|
||||
if get_chown_plugin_path() == True:
|
||||
@@ -73,6 +76,9 @@ class PluginManager:
|
||||
self.plugin_browser = PluginBrowser(plugin_path, self.plugin_loader.plugins, self.plugin_loader, self.settings)
|
||||
self.utilities = Utilities(self)
|
||||
self.updater = Updater(self)
|
||||
self.last_webhelper_exit: float = 0
|
||||
self.webhelper_crash_count: int = 0
|
||||
self.inject_fallback: bool = False
|
||||
|
||||
jinja_setup(self.web_app)
|
||||
|
||||
@@ -94,12 +100,33 @@ class PluginManager:
|
||||
self.cors.add(route) # pyright: ignore [reportUnknownMemberType]
|
||||
self.web_app.add_routes([static("/static", path.join(path.dirname(__file__), 'static'))])
|
||||
|
||||
async def handle_crash(self):
|
||||
if not self.reinject:
|
||||
return
|
||||
new_time = time()
|
||||
if (new_time - self.last_webhelper_exit < 60):
|
||||
self.webhelper_crash_count += 1
|
||||
logger.warning(f"webhelper crashed within a minute from last crash! crash count: {self.webhelper_crash_count}")
|
||||
else:
|
||||
self.webhelper_crash_count = 0
|
||||
self.last_webhelper_exit = new_time
|
||||
|
||||
# should never happen
|
||||
if (self.webhelper_crash_count > 4):
|
||||
await self.updater.do_shutdown()
|
||||
# Give up
|
||||
exit(0)
|
||||
|
||||
async def shutdown(self, _: Application):
|
||||
try:
|
||||
logger.info(f"Shutting down...")
|
||||
logger.info("Disabling reload...")
|
||||
await self.plugin_loader.disable_reload()
|
||||
logger.info("Killing plugins...")
|
||||
await self.plugin_loader.shutdown_plugins()
|
||||
await self.ws.disconnect()
|
||||
logger.info("Disconnecting from WS...")
|
||||
self.reinject = False
|
||||
await self.ws.disconnect()
|
||||
if self.js_ctx_tab:
|
||||
await self.js_ctx_tab.close_websocket()
|
||||
self.js_ctx_tab = None
|
||||
@@ -111,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.warn(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)
|
||||
@@ -185,6 +213,7 @@ class PluginManager:
|
||||
# If this is a forceful disconnect the loop will just stop without any failure message. In this case, injector.py will handle this for us so we don't need to close the socket.
|
||||
# This is because of https://github.com/aio-libs/aiohttp/blob/3ee7091b40a1bc58a8d7846e7878a77640e96996/aiohttp/client_ws.py#L321
|
||||
logger.info("CEF has disconnected...")
|
||||
await self.handle_crash()
|
||||
# At this point the loop starts again and we connect to the freshly started Steam client once it is ready.
|
||||
except Exception:
|
||||
if not self.reinject:
|
||||
@@ -192,6 +221,7 @@ class PluginManager:
|
||||
logger.error("Exception while reading page events " + format_exc())
|
||||
await tab.close_websocket()
|
||||
self.js_ctx_tab = None
|
||||
await self.handle_crash()
|
||||
pass
|
||||
# while True:
|
||||
# await sleep(5)
|
||||
@@ -207,8 +237,13 @@ class PluginManager:
|
||||
await tab.close_websocket()
|
||||
self.js_ctx_tab = None
|
||||
await restart_webhelper()
|
||||
await sleep(1) # To give CEF enough time to close down the websocket
|
||||
return # We'll catch the next tab in the main loop
|
||||
await tab.evaluate_js("try{if (window.deckyHasLoaded){setTimeout(() => SteamClient.Browser.RestartJSContext(), 100)}else{window.deckyHasLoaded = true;(async()=>{try{await import('http://localhost:1337/frontend/index.js?v=%s')}catch(e){console.error(e)};})();}}catch(e){console.error(e)}" % (get_loader_version(), ), False, False, False)
|
||||
await tab.evaluate_js(create_inject_script("index.js" if self.webhelper_crash_count < 3 else "fallback.js"), False, False, False)
|
||||
if self.webhelper_crash_count > 2:
|
||||
self.reinject = False
|
||||
await sleep(1)
|
||||
await self.updater.do_shutdown()
|
||||
except:
|
||||
logger.info("Failed to inject JavaScript into tab\n" + format_exc())
|
||||
pass
|
||||
@@ -223,9 +258,6 @@ def main():
|
||||
# Fix windows/flask not recognising that .js means 'application/javascript'
|
||||
import mimetypes
|
||||
mimetypes.add_type('application/javascript', '.js')
|
||||
|
||||
# Required for multiprocessing support in frozen files
|
||||
multiprocessing.freeze_support()
|
||||
else:
|
||||
if get_effective_user_id() != 0:
|
||||
logger.warning(f"decky is running as an unprivileged user, this is not officially supported and may cause issues")
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
from asyncio import CancelledError, Task, create_task, sleep
|
||||
from asyncio import CancelledError, Task, create_task, sleep, wait
|
||||
from json import dumps, load, loads
|
||||
from logging import getLogger
|
||||
from os import path
|
||||
from multiprocessing import Process
|
||||
from time import time
|
||||
from traceback import format_exc
|
||||
|
||||
from .sandboxed_plugin import SandboxedPlugin
|
||||
from .messages import MethodCallRequest, SocketMessageType
|
||||
from ..enums import PluginLoadType
|
||||
from ..enums import PluginLoadType, UserType
|
||||
from ..localplatform.localplatform import file_owner, chown, chmod, get_chown_plugin_path
|
||||
from ..localplatform.localsocket import LocalSocket
|
||||
from ..helpers import get_homebrew_path, mkdir_as_user
|
||||
|
||||
@@ -24,9 +27,12 @@ class PluginWrapper:
|
||||
|
||||
self.load_type = PluginLoadType.LEGACY_EVAL_IIFE.value
|
||||
|
||||
json = load(open(path.join(plugin_path, plugin_directory, "plugin.json"), "r", encoding="utf-8"))
|
||||
if path.isfile(path.join(plugin_path, plugin_directory, "package.json")):
|
||||
package_json = load(open(path.join(plugin_path, plugin_directory, "package.json"), "r", encoding="utf-8"))
|
||||
plugin_dir_path = path.join(plugin_path, plugin_directory)
|
||||
plugin_json_path = path.join(plugin_dir_path, "plugin.json")
|
||||
|
||||
json = load(open(plugin_json_path, "r", encoding="utf-8"))
|
||||
if path.isfile(path.join(plugin_dir_path, "package.json")):
|
||||
package_json = load(open(path.join(plugin_dir_path, "package.json"), "r", encoding="utf-8"))
|
||||
self.version = package_json["version"]
|
||||
if ("type" in package_json and package_json["type"] == "module"):
|
||||
self.load_type = PluginLoadType.ESMODULE_V1.value
|
||||
@@ -35,15 +41,26 @@ class PluginWrapper:
|
||||
self.author = json["author"]
|
||||
self.flags = json["flags"]
|
||||
self.api_version = json["api_version"] if "api_version" in json else 0
|
||||
self.disabled = False
|
||||
|
||||
self.passive = not path.isfile(self.file)
|
||||
|
||||
self.log = getLogger("plugin")
|
||||
|
||||
if get_chown_plugin_path():
|
||||
# ensure plugin folder ownership
|
||||
if file_owner(plugin_dir_path) != UserType.EFFECTIVE_USER:
|
||||
chown(plugin_dir_path, UserType.EFFECTIVE_USER if "root" in self.flags else UserType.HOST_USER, True)
|
||||
chown(plugin_dir_path, UserType.EFFECTIVE_USER, False)
|
||||
chmod(plugin_dir_path, 755, True)
|
||||
# fix plugin.json permissions
|
||||
if file_owner(plugin_json_path) != UserType.EFFECTIVE_USER:
|
||||
chown(plugin_json_path, UserType.EFFECTIVE_USER, False)
|
||||
chmod(plugin_json_path, 755, False)
|
||||
|
||||
self.sandboxed_plugin = SandboxedPlugin(self.name, self.passive, self.flags, self.file, self.plugin_directory, self.plugin_path, self.version, self.author, self.api_version)
|
||||
self.proc: Process | None = None
|
||||
# TODO: Maybe make LocalSocket not require on_new_message to make this cleaner
|
||||
self._socket = LocalSocket(self.sandboxed_plugin.on_new_message)
|
||||
self._socket = LocalSocket()
|
||||
self._listener_task: Task[Any]
|
||||
self._method_call_requests: Dict[str, MethodCallRequest] = {}
|
||||
|
||||
@@ -65,7 +82,7 @@ class PluginWrapper:
|
||||
return self.name
|
||||
|
||||
async def _response_listener(self):
|
||||
while True:
|
||||
while self._socket.active:
|
||||
try:
|
||||
line = await self._socket.read_single_line()
|
||||
if line != None:
|
||||
@@ -84,7 +101,7 @@ class PluginWrapper:
|
||||
async def execute_legacy_method(self, method_name: str, kwargs: Dict[Any, Any]):
|
||||
if not self.legacy_method_warning:
|
||||
self.legacy_method_warning = True
|
||||
self.log.warn(f"Plugin {self.name} is using legacy method calls. This will be removed in a future release.")
|
||||
self.log.warning(f"Plugin {self.name} is using legacy method calls. This will be removed in a future release.")
|
||||
if self.passive:
|
||||
raise RuntimeError("This plugin is passive (aka does not implement main.py)")
|
||||
|
||||
@@ -115,29 +132,43 @@ class PluginWrapper:
|
||||
return self
|
||||
|
||||
async def stop(self, uninstall: bool = False):
|
||||
self.log.info(f"Stopping plugin {self.name}")
|
||||
if self.passive:
|
||||
return
|
||||
if hasattr(self, "_socket"):
|
||||
await self._socket.write_single_line(dumps({ "stop": True, "uninstall": uninstall }, ensure_ascii=False))
|
||||
await self._socket.close_socket_connection()
|
||||
if self.proc:
|
||||
self.proc.join()
|
||||
await self.kill_if_still_running()
|
||||
if hasattr(self, "_listener_task"):
|
||||
self._listener_task.cancel()
|
||||
try:
|
||||
start_time = time()
|
||||
if self.passive:
|
||||
return
|
||||
self.log.info(f"Shutting down {self.name}")
|
||||
|
||||
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()
|
||||
|
||||
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:
|
||||
self.log.error(f"Error during shutdown for plugin {self.name}: {str(e)}\n{format_exc()}")
|
||||
|
||||
async def kill_if_still_running(self):
|
||||
time = 0
|
||||
start_time = time()
|
||||
while self.proc and self.proc.is_alive():
|
||||
await sleep(0.1)
|
||||
time += 1
|
||||
if time == 100:
|
||||
self.log.warn(f"Plugin {self.name} still alive 10 seconds after stop request! Sending SIGTERM!")
|
||||
self.terminate()
|
||||
elif time == 200:
|
||||
self.log.warn(f"Plugin {self.name} still alive 20 seconds after stop request! Sending SIGKILL!")
|
||||
elapsed_time = time() - start_time
|
||||
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():
|
||||
|
||||
@@ -1,26 +1,25 @@
|
||||
import sys
|
||||
from os import path, environ
|
||||
from signal import SIG_IGN, SIGINT, SIGTERM, getsignal, signal
|
||||
from importlib.util import module_from_spec, spec_from_file_location
|
||||
from json import dumps, loads
|
||||
from logging import getLogger
|
||||
from sys import exit, path as syspath, modules as sysmodules
|
||||
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
|
||||
from .. import settings # pyright: ignore [reportUnusedImport]
|
||||
|
||||
from typing import List, TypeVar, Any
|
||||
|
||||
DataType = TypeVar("DataType")
|
||||
|
||||
original_term_handler = getsignal(SIGTERM)
|
||||
|
||||
class SandboxedPlugin:
|
||||
def __init__(self,
|
||||
name: str,
|
||||
@@ -41,6 +40,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,21 +49,23 @@ class SandboxedPlugin:
|
||||
self._socket = socket
|
||||
|
||||
try:
|
||||
# Ignore signals meant for parent Process
|
||||
# TODO SURELY there's a better way to do this.
|
||||
signal(SIGINT, SIG_IGN)
|
||||
signal(SIGTERM, SIG_IGN)
|
||||
|
||||
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)
|
||||
|
||||
setgid(UserType.EFFECTIVE_USER if "root" in self.flags else UserType.HOST_USER)
|
||||
setuid(UserType.EFFECTIVE_USER if "root" in self.flags else UserType.HOST_USER)
|
||||
# export a bunch of environment variables to help plugin developers
|
||||
environ["HOME"] = get_home_path(UserType.ROOT if "root" in self.flags else UserType.HOST_USER)
|
||||
environ["HOME"] = get_home_path(UserType.EFFECTIVE_USER if "root" in self.flags else UserType.HOST_USER)
|
||||
environ["USER"] = "root" if "root" in self.flags else get_username()
|
||||
environ["DECKY_VERSION"] = helpers.get_loader_version()
|
||||
environ["DECKY_USER"] = get_username()
|
||||
@@ -78,12 +81,12 @@ class SandboxedPlugin:
|
||||
environ["DECKY_PLUGIN_AUTHOR"] = self.author
|
||||
|
||||
# append the plugin's `py_modules` to the recognized python paths
|
||||
syspath.append(path.join(environ["DECKY_PLUGIN_DIR"], "py_modules"))
|
||||
sys.path.append(path.join(environ["DECKY_PLUGIN_DIR"], "py_modules"))
|
||||
|
||||
#TODO: FIX IN A LESS CURSED WAY
|
||||
keys = [key for key in sysmodules if key.startswith("decky_loader.")]
|
||||
keys = [key for key in sys.modules if key.startswith("decky_loader.")]
|
||||
for key in keys:
|
||||
sysmodules[key.replace("decky_loader.", "")] = sysmodules[key]
|
||||
sys.modules[key.replace("decky_loader.", "")] = sys.modules[key]
|
||||
|
||||
from .imports import decky
|
||||
async def emit(event: str, *args: Any) -> None:
|
||||
@@ -95,9 +98,9 @@ class SandboxedPlugin:
|
||||
# copy the docstring over so we don't have to duplicate it
|
||||
emit.__doc__ = decky.emit.__doc__
|
||||
decky.emit = emit
|
||||
sysmodules["decky"] = decky
|
||||
sys.modules["decky"] = decky
|
||||
# provided for compatibility
|
||||
sysmodules["decky_plugin"] = decky
|
||||
sys.modules["decky_plugin"] = decky
|
||||
|
||||
spec = spec_from_file_location("_", self.file)
|
||||
assert spec is not None
|
||||
@@ -120,10 +123,10 @@ class SandboxedPlugin:
|
||||
get_event_loop().create_task(self.Plugin._main())
|
||||
else:
|
||||
get_event_loop().create_task(self.Plugin._main(self.Plugin))
|
||||
get_event_loop().create_task(socket.setup_server())
|
||||
get_event_loop().create_task(socket.setup_server(self.on_new_message))
|
||||
except:
|
||||
self.log.error("Failed to start " + self.name + "!\n" + format_exc())
|
||||
exit(0)
|
||||
sys.exit(0)
|
||||
try:
|
||||
get_event_loop().run_forever()
|
||||
except SystemExit:
|
||||
@@ -163,24 +166,28 @@ 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:
|
||||
# Incase the loader needs to terminate our process soon
|
||||
signal(SIGTERM, original_term_handler)
|
||||
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)
|
||||
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")
|
||||
return
|
||||
|
||||
d: SocketResponseDict = {"type": SocketMessageType.RESPONSE, "res": None, "success": True, "id": data["id"]}
|
||||
try:
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
from json import dump, load
|
||||
from os import mkdir, path, listdir, rename
|
||||
from typing import Any, Dict
|
||||
from .localplatform.localplatform import chown, folder_owner, get_chown_plugin_path
|
||||
from .localplatform.localplatform import chown, file_owner, get_chown_plugin_path
|
||||
from .enums import UserType
|
||||
|
||||
from .helpers import get_homebrew_path
|
||||
@@ -28,8 +28,8 @@ class SettingsManager:
|
||||
|
||||
|
||||
#If the owner of the settings directory is not the user, then set it as the user:
|
||||
expected_user = UserType.HOST_USER if get_chown_plugin_path() else UserType.ROOT
|
||||
if folder_owner(settings_directory) != expected_user:
|
||||
expected_user = UserType.HOST_USER if get_chown_plugin_path() else UserType.EFFECTIVE_USER
|
||||
if file_owner(settings_directory) != expected_user:
|
||||
chown(settings_directory, expected_user, False)
|
||||
|
||||
self.settings: Dict[str, Any] = {}
|
||||
|
||||
@@ -14,7 +14,6 @@ import zipfile
|
||||
from aiohttp import ClientSession
|
||||
|
||||
from . import helpers
|
||||
from .injector import get_gamepadui_tab
|
||||
from .settings import SettingsManager
|
||||
if TYPE_CHECKING:
|
||||
from .main import PluginManager
|
||||
@@ -24,6 +23,7 @@ logger = getLogger("Updater")
|
||||
|
||||
class RemoteVerAsset(TypedDict):
|
||||
name: str
|
||||
size: int
|
||||
browser_download_url: str
|
||||
class RemoteVer(TypedDict):
|
||||
tag_name: str
|
||||
@@ -96,7 +96,7 @@ class Updater:
|
||||
"current": self.localVer,
|
||||
"remote": self.remoteVer,
|
||||
"all": self.allRemoteVers,
|
||||
"updatable": self.localVer != "unknown"
|
||||
"updatable": self.localVer != "unknown" and self.localVer != "dev"
|
||||
}
|
||||
|
||||
async def check_for_updates(self):
|
||||
@@ -141,8 +141,6 @@ class Updater:
|
||||
async def download_decky_binary(self, download_url: str, version: str, is_zip: bool = False, size_in_bytes: int | None = None):
|
||||
download_filename = "PluginLoader" if ON_LINUX else "PluginLoader.exe"
|
||||
download_temp_filename = download_filename + ".new"
|
||||
tab = await get_gamepadui_tab()
|
||||
await tab.open_websocket()
|
||||
|
||||
if size_in_bytes == None:
|
||||
size_in_bytes = 26214400 # 25MiB, a reasonable overestimate (19.6MiB as of 2024/02/25)
|
||||
@@ -185,7 +183,6 @@ class Updater:
|
||||
|
||||
logger.info("Updated loader installation.")
|
||||
await self.context.ws.emit("updater/finish_download")
|
||||
await tab.close_websocket()
|
||||
await self.do_restart()
|
||||
|
||||
async def do_update(self):
|
||||
@@ -198,11 +195,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 +237,11 @@ 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")
|
||||
logger.info("Restarting loader for update.")
|
||||
await service_restart("plugin_loader", block=False)
|
||||
|
||||
async def do_shutdown(self):
|
||||
await service_stop("plugin_loader")
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
from __future__ import annotations
|
||||
from os import stat_result
|
||||
from os import path, stat_result
|
||||
import uuid
|
||||
from urllib.parse import unquote
|
||||
from json.decoder import JSONDecodeError
|
||||
@@ -8,8 +8,8 @@ import re
|
||||
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 asyncio import StreamReader, StreamWriter, sleep, start_server, gather, open_connection
|
||||
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
|
||||
|
||||
@@ -20,9 +20,8 @@ from .browser import PluginInstallRequest, PluginInstallType
|
||||
if TYPE_CHECKING:
|
||||
from .main import PluginManager
|
||||
from .injector import inject_to_tab, get_gamepadui_tab, close_old_tabs, get_tab
|
||||
from .localplatform.localplatform import ON_WINDOWS
|
||||
from . import helpers
|
||||
from .localplatform.localplatform import service_stop, service_start, get_home_path, get_username
|
||||
from .localplatform.localplatform import ON_WINDOWS, service_stop, service_start, get_home_path, get_username, get_use_cef_close_workaround, close_cef_socket, restart_webhelper
|
||||
|
||||
class FilePickerObj(TypedDict):
|
||||
file: Path
|
||||
@@ -78,7 +77,12 @@ class Utilities:
|
||||
context.ws.add_route("utilities/get_tab_id", self.get_tab_id)
|
||||
context.ws.add_route("utilities/get_user_info", self.get_user_info)
|
||||
context.ws.add_route("utilities/http_request", self.http_request_legacy)
|
||||
context.ws.add_route("utilities/restart_webhelper", self.restart_webhelper)
|
||||
context.ws.add_route("utilities/close_cef_socket", self.close_cef_socket)
|
||||
context.ws.add_route("utilities/_call_legacy_utility", self._call_legacy_utility)
|
||||
context.ws.add_route("utilities/enable_plugin", self.enable_plugin)
|
||||
context.ws.add_route("utilities/disable_plugin", self.disable_plugin)
|
||||
context.ws.add_route("utilities/set_all_plugins_disabled", self.set_all_plugins_disabled)
|
||||
|
||||
context.web_app.add_routes([
|
||||
post("/methods/{method_name}", self._handle_legacy_server_method_call)
|
||||
@@ -141,10 +145,10 @@ class Utilities:
|
||||
|
||||
# Loosely based on https://gist.github.com/mosquito/4dbfacd51e751827cda7ec9761273e95#file-proxy-py
|
||||
async def http_request(self, req: Request) -> StreamResponse:
|
||||
if req.headers.get('X-Decky-Auth', '') != helpers.get_csrf_token() and req.query.get('auth', '') != helpers.get_csrf_token():
|
||||
if req.query['auth'] != helpers.get_csrf_token():
|
||||
return Response(text='Forbidden', status=403)
|
||||
|
||||
url = req.headers["X-Decky-Fetch-URL"] if "X-Decky-Fetch-URL" in req.headers else unquote(req.query.get('fetch_url', ''))
|
||||
url = unquote(req.query['fetch_url'])
|
||||
self.logger.info(f"Preparing {req.method} request to {url}")
|
||||
|
||||
headers = dict(req.headers)
|
||||
@@ -152,14 +156,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:
|
||||
@@ -180,9 +184,27 @@ class Utilities:
|
||||
|
||||
body = await req.read() # TODO can this also be streamed?
|
||||
|
||||
async with ClientSession() as web:
|
||||
# We disable auto-decompress so that the body is completely forwarded to the
|
||||
# JS engine for it to do the decompression. Otherwise we need need to clear
|
||||
# the Content-Encoding header in the response headers, however that would
|
||||
# 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()
|
||||
|
||||
@@ -190,14 +212,12 @@ class Utilities:
|
||||
self.logger.debug(f"Starting stream for {url}")
|
||||
async for data in web_res.content.iter_any():
|
||||
await res.write(data)
|
||||
if data:
|
||||
await res.drain()
|
||||
self.logger.debug(f"Finished stream for {url}")
|
||||
return res
|
||||
|
||||
async def http_request_legacy(self, method: str, url: str, extra_opts: Any = {}):
|
||||
async def http_request_legacy(self, method: str, url: str, extra_opts: Any = {}, timeout: int | None = None):
|
||||
async with ClientSession() as web:
|
||||
res = await web.request(method, url, ssl=helpers.get_ssl_context(), **extra_opts)
|
||||
res = await web.request(method, url, ssl=helpers.get_ssl_context(), timeout=timeout, **extra_opts) # type: ignore
|
||||
text = await res.text()
|
||||
return {
|
||||
"status": res.status,
|
||||
@@ -285,6 +305,13 @@ class Utilities:
|
||||
await service_stop(helpers.SSHD_UNIT)
|
||||
return True
|
||||
|
||||
async def close_cef_socket(self):
|
||||
if get_use_cef_close_workaround():
|
||||
await close_cef_socket()
|
||||
|
||||
async def restart_webhelper(self):
|
||||
await restart_webhelper()
|
||||
|
||||
async def filepicker_ls(self,
|
||||
path: str | None = None,
|
||||
include_files: bool = True,
|
||||
@@ -366,7 +393,6 @@ class Utilities:
|
||||
"total": len(all),
|
||||
}
|
||||
|
||||
|
||||
# Based on https://stackoverflow.com/a/46422554/13174603
|
||||
def start_rdt_proxy(self, ip: str, port: int):
|
||||
async def pipe(reader: StreamReader, writer: StreamWriter):
|
||||
@@ -450,3 +476,41 @@ class Utilities:
|
||||
|
||||
async def get_tab_id(self, name: str):
|
||||
return (await get_tab(name)).id
|
||||
|
||||
async def disable_plugin(self, name: str):
|
||||
disabled_plugins: List[str] = await self.get_setting("disabled_plugins", [])
|
||||
if name not in disabled_plugins:
|
||||
disabled_plugins.append(name)
|
||||
await self.set_setting("disabled_plugins", disabled_plugins)
|
||||
|
||||
await self.context.plugin_loader.plugins[name].stop()
|
||||
await self.context.ws.emit("loader/disable_plugin", name)
|
||||
|
||||
async def enable_plugin(self, name: str):
|
||||
plugin_folder = self.context.plugin_browser.find_plugin_folder(name)
|
||||
assert plugin_folder is not None
|
||||
plugin_dir = path.join(self.context.plugin_browser.plugin_path, plugin_folder)
|
||||
|
||||
if name in self.context.plugin_loader.plugins:
|
||||
plugin = self.context.plugin_loader.plugins[name]
|
||||
if plugin.proc and plugin.proc.is_alive():
|
||||
await plugin.stop()
|
||||
self.context.plugin_loader.plugins.pop(name, None)
|
||||
await sleep(1)
|
||||
|
||||
disabled_plugins: List[str] = await self.get_setting("disabled_plugins", [])
|
||||
|
||||
if name in disabled_plugins:
|
||||
disabled_plugins.remove(name)
|
||||
await self.set_setting("disabled_plugins", disabled_plugins)
|
||||
|
||||
await self.context.plugin_loader.import_plugin(path.join(plugin_dir, "main.py"), plugin_folder)
|
||||
|
||||
async def set_all_plugins_disabled(self):
|
||||
disabled_plugins: List[str] = await self.get_setting("disabled_plugins", [])
|
||||
|
||||
for name, _ in self.context.plugin_loader.plugins.items():
|
||||
if name not in disabled_plugins:
|
||||
disabled_plugins.append(name)
|
||||
|
||||
await self.set_setting("disabled_plugins", disabled_plugins)
|
||||
@@ -7,7 +7,7 @@ from aiohttp.web import Application, WebSocketResponse, Request, Response, get
|
||||
|
||||
from enum import IntEnum
|
||||
|
||||
from typing import Callable, Coroutine, Dict, Any, cast, TypeVar
|
||||
from typing import Callable, Coroutine, Dict, Any, cast
|
||||
|
||||
from traceback import format_exc
|
||||
|
||||
@@ -29,8 +29,6 @@ class WSMessageExtra(WSMessage):
|
||||
|
||||
# see wsrouter.ts for typings
|
||||
|
||||
DataType = TypeVar("DataType")
|
||||
|
||||
Route = Callable[..., Coroutine[Any, Any, Any]]
|
||||
|
||||
class WSRouter:
|
||||
@@ -50,7 +48,7 @@ class WSRouter:
|
||||
if self.ws != None:
|
||||
await self.ws.send_json(data)
|
||||
else:
|
||||
self.logger.warn("Dropping message as there is no connected socket: %s", data)
|
||||
self.logger.warning("Dropping message as there is no connected socket: %s", data)
|
||||
|
||||
def add_route(self, name: str, route: Route):
|
||||
self.routes[name] = route
|
||||
@@ -69,9 +67,9 @@ class WSRouter:
|
||||
|
||||
if instance_id != self.instance_id:
|
||||
try:
|
||||
self.logger.warn("Ignoring %s reply from stale instance %d with args %s and response %s", route, instance_id, args, res)
|
||||
self.logger.warning("Ignoring %s reply from stale instance %d with args %s and response %s", route, instance_id, args, res)
|
||||
except:
|
||||
self.logger.warn("Ignoring %s reply from stale instance %d (failed to log event data)", route, instance_id)
|
||||
self.logger.warning("Ignoring %s reply from stale instance %d (failed to log event data)", route, instance_id)
|
||||
finally:
|
||||
return
|
||||
|
||||
|
||||
Generated
+386
-207
@@ -1,100 +1,129 @@
|
||||
# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand.
|
||||
# This file is automatically @generated by Poetry 2.1.3 and should not be changed by hand.
|
||||
|
||||
[[package]]
|
||||
name = "aiohappyeyeballs"
|
||||
version = "2.4.3"
|
||||
description = "Happy Eyeballs for asyncio"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "aiohappyeyeballs-2.4.3-py3-none-any.whl", hash = "sha256:8a7a83727b2756f394ab2895ea0765a0a8c475e3c71e98d43d76f22b4b435572"},
|
||||
{file = "aiohappyeyeballs-2.4.3.tar.gz", hash = "sha256:75cf88a15106a5002a8eb1dab212525c00d1f4c0fa96e551c9fbe6f09a621586"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aiohttp"
|
||||
version = "3.9.5"
|
||||
version = "3.10.11"
|
||||
description = "Async http client/server framework (asyncio)"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "aiohttp-3.9.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:fcde4c397f673fdec23e6b05ebf8d4751314fa7c24f93334bf1f1364c1c69ac7"},
|
||||
{file = "aiohttp-3.9.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5d6b3f1fabe465e819aed2c421a6743d8debbde79b6a8600739300630a01bf2c"},
|
||||
{file = "aiohttp-3.9.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6ae79c1bc12c34082d92bf9422764f799aee4746fd7a392db46b7fd357d4a17a"},
|
||||
{file = "aiohttp-3.9.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4d3ebb9e1316ec74277d19c5f482f98cc65a73ccd5430540d6d11682cd857430"},
|
||||
{file = "aiohttp-3.9.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:84dabd95154f43a2ea80deffec9cb44d2e301e38a0c9d331cc4aa0166fe28ae3"},
|
||||
{file = "aiohttp-3.9.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c8a02fbeca6f63cb1f0475c799679057fc9268b77075ab7cf3f1c600e81dd46b"},
|
||||
{file = "aiohttp-3.9.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c26959ca7b75ff768e2776d8055bf9582a6267e24556bb7f7bd29e677932be72"},
|
||||
{file = "aiohttp-3.9.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:714d4e5231fed4ba2762ed489b4aec07b2b9953cf4ee31e9871caac895a839c0"},
|
||||
{file = "aiohttp-3.9.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e7a6a8354f1b62e15d48e04350f13e726fa08b62c3d7b8401c0a1314f02e3558"},
|
||||
{file = "aiohttp-3.9.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:c413016880e03e69d166efb5a1a95d40f83d5a3a648d16486592c49ffb76d0db"},
|
||||
{file = "aiohttp-3.9.5-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:ff84aeb864e0fac81f676be9f4685f0527b660f1efdc40dcede3c251ef1e867f"},
|
||||
{file = "aiohttp-3.9.5-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:ad7f2919d7dac062f24d6f5fe95d401597fbb015a25771f85e692d043c9d7832"},
|
||||
{file = "aiohttp-3.9.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:702e2c7c187c1a498a4e2b03155d52658fdd6fda882d3d7fbb891a5cf108bb10"},
|
||||
{file = "aiohttp-3.9.5-cp310-cp310-win32.whl", hash = "sha256:67c3119f5ddc7261d47163ed86d760ddf0e625cd6246b4ed852e82159617b5fb"},
|
||||
{file = "aiohttp-3.9.5-cp310-cp310-win_amd64.whl", hash = "sha256:471f0ef53ccedec9995287f02caf0c068732f026455f07db3f01a46e49d76bbb"},
|
||||
{file = "aiohttp-3.9.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e0ae53e33ee7476dd3d1132f932eeb39bf6125083820049d06edcdca4381f342"},
|
||||
{file = "aiohttp-3.9.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c088c4d70d21f8ca5c0b8b5403fe84a7bc8e024161febdd4ef04575ef35d474d"},
|
||||
{file = "aiohttp-3.9.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:639d0042b7670222f33b0028de6b4e2fad6451462ce7df2af8aee37dcac55424"},
|
||||
{file = "aiohttp-3.9.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f26383adb94da5e7fb388d441bf09c61e5e35f455a3217bfd790c6b6bc64b2ee"},
|
||||
{file = "aiohttp-3.9.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:66331d00fb28dc90aa606d9a54304af76b335ae204d1836f65797d6fe27f1ca2"},
|
||||
{file = "aiohttp-3.9.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4ff550491f5492ab5ed3533e76b8567f4b37bd2995e780a1f46bca2024223233"},
|
||||
{file = "aiohttp-3.9.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f22eb3a6c1080d862befa0a89c380b4dafce29dc6cd56083f630073d102eb595"},
|
||||
{file = "aiohttp-3.9.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a81b1143d42b66ffc40a441379387076243ef7b51019204fd3ec36b9f69e77d6"},
|
||||
{file = "aiohttp-3.9.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:f64fd07515dad67f24b6ea4a66ae2876c01031de91c93075b8093f07c0a2d93d"},
|
||||
{file = "aiohttp-3.9.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:93e22add827447d2e26d67c9ac0161756007f152fdc5210277d00a85f6c92323"},
|
||||
{file = "aiohttp-3.9.5-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:55b39c8684a46e56ef8c8d24faf02de4a2b2ac60d26cee93bc595651ff545de9"},
|
||||
{file = "aiohttp-3.9.5-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4715a9b778f4293b9f8ae7a0a7cef9829f02ff8d6277a39d7f40565c737d3771"},
|
||||
{file = "aiohttp-3.9.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:afc52b8d969eff14e069a710057d15ab9ac17cd4b6753042c407dcea0e40bf75"},
|
||||
{file = "aiohttp-3.9.5-cp311-cp311-win32.whl", hash = "sha256:b3df71da99c98534be076196791adca8819761f0bf6e08e07fd7da25127150d6"},
|
||||
{file = "aiohttp-3.9.5-cp311-cp311-win_amd64.whl", hash = "sha256:88e311d98cc0bf45b62fc46c66753a83445f5ab20038bcc1b8a1cc05666f428a"},
|
||||
{file = "aiohttp-3.9.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:c7a4b7a6cf5b6eb11e109a9755fd4fda7d57395f8c575e166d363b9fc3ec4678"},
|
||||
{file = "aiohttp-3.9.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:0a158704edf0abcac8ac371fbb54044f3270bdbc93e254a82b6c82be1ef08f3c"},
|
||||
{file = "aiohttp-3.9.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d153f652a687a8e95ad367a86a61e8d53d528b0530ef382ec5aaf533140ed00f"},
|
||||
{file = "aiohttp-3.9.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:82a6a97d9771cb48ae16979c3a3a9a18b600a8505b1115cfe354dfb2054468b4"},
|
||||
{file = "aiohttp-3.9.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:60cdbd56f4cad9f69c35eaac0fbbdf1f77b0ff9456cebd4902f3dd1cf096464c"},
|
||||
{file = "aiohttp-3.9.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8676e8fd73141ded15ea586de0b7cda1542960a7b9ad89b2b06428e97125d4fa"},
|
||||
{file = "aiohttp-3.9.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:da00da442a0e31f1c69d26d224e1efd3a1ca5bcbf210978a2ca7426dfcae9f58"},
|
||||
{file = "aiohttp-3.9.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:18f634d540dd099c262e9f887c8bbacc959847cfe5da7a0e2e1cf3f14dbf2daf"},
|
||||
{file = "aiohttp-3.9.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:320e8618eda64e19d11bdb3bd04ccc0a816c17eaecb7e4945d01deee2a22f95f"},
|
||||
{file = "aiohttp-3.9.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:2faa61a904b83142747fc6a6d7ad8fccff898c849123030f8e75d5d967fd4a81"},
|
||||
{file = "aiohttp-3.9.5-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:8c64a6dc3fe5db7b1b4d2b5cb84c4f677768bdc340611eca673afb7cf416ef5a"},
|
||||
{file = "aiohttp-3.9.5-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:393c7aba2b55559ef7ab791c94b44f7482a07bf7640d17b341b79081f5e5cd1a"},
|
||||
{file = "aiohttp-3.9.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:c671dc117c2c21a1ca10c116cfcd6e3e44da7fcde37bf83b2be485ab377b25da"},
|
||||
{file = "aiohttp-3.9.5-cp312-cp312-win32.whl", hash = "sha256:5a7ee16aab26e76add4afc45e8f8206c95d1d75540f1039b84a03c3b3800dd59"},
|
||||
{file = "aiohttp-3.9.5-cp312-cp312-win_amd64.whl", hash = "sha256:5ca51eadbd67045396bc92a4345d1790b7301c14d1848feaac1d6a6c9289e888"},
|
||||
{file = "aiohttp-3.9.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:694d828b5c41255e54bc2dddb51a9f5150b4eefa9886e38b52605a05d96566e8"},
|
||||
{file = "aiohttp-3.9.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0605cc2c0088fcaae79f01c913a38611ad09ba68ff482402d3410bf59039bfb8"},
|
||||
{file = "aiohttp-3.9.5-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4558e5012ee03d2638c681e156461d37b7a113fe13970d438d95d10173d25f78"},
|
||||
{file = "aiohttp-3.9.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9dbc053ac75ccc63dc3a3cc547b98c7258ec35a215a92bd9f983e0aac95d3d5b"},
|
||||
{file = "aiohttp-3.9.5-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4109adee842b90671f1b689901b948f347325045c15f46b39797ae1bf17019de"},
|
||||
{file = "aiohttp-3.9.5-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a6ea1a5b409a85477fd8e5ee6ad8f0e40bf2844c270955e09360418cfd09abac"},
|
||||
{file = "aiohttp-3.9.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f3c2890ca8c59ee683fd09adf32321a40fe1cf164e3387799efb2acebf090c11"},
|
||||
{file = "aiohttp-3.9.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3916c8692dbd9d55c523374a3b8213e628424d19116ac4308e434dbf6d95bbdd"},
|
||||
{file = "aiohttp-3.9.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:8d1964eb7617907c792ca00b341b5ec3e01ae8c280825deadbbd678447b127e1"},
|
||||
{file = "aiohttp-3.9.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:d5ab8e1f6bee051a4bf6195e38a5c13e5e161cb7bad83d8854524798bd9fcd6e"},
|
||||
{file = "aiohttp-3.9.5-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:52c27110f3862a1afbcb2af4281fc9fdc40327fa286c4625dfee247c3ba90156"},
|
||||
{file = "aiohttp-3.9.5-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:7f64cbd44443e80094309875d4f9c71d0401e966d191c3d469cde4642bc2e031"},
|
||||
{file = "aiohttp-3.9.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8b4f72fbb66279624bfe83fd5eb6aea0022dad8eec62b71e7bf63ee1caadeafe"},
|
||||
{file = "aiohttp-3.9.5-cp38-cp38-win32.whl", hash = "sha256:6380c039ec52866c06d69b5c7aad5478b24ed11696f0e72f6b807cfb261453da"},
|
||||
{file = "aiohttp-3.9.5-cp38-cp38-win_amd64.whl", hash = "sha256:da22dab31d7180f8c3ac7c7635f3bcd53808f374f6aa333fe0b0b9e14b01f91a"},
|
||||
{file = "aiohttp-3.9.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:1732102949ff6087589408d76cd6dea656b93c896b011ecafff418c9661dc4ed"},
|
||||
{file = "aiohttp-3.9.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c6021d296318cb6f9414b48e6a439a7f5d1f665464da507e8ff640848ee2a58a"},
|
||||
{file = "aiohttp-3.9.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:239f975589a944eeb1bad26b8b140a59a3a320067fb3cd10b75c3092405a1372"},
|
||||
{file = "aiohttp-3.9.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3b7b30258348082826d274504fbc7c849959f1989d86c29bc355107accec6cfb"},
|
||||
{file = "aiohttp-3.9.5-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cd2adf5c87ff6d8b277814a28a535b59e20bfea40a101db6b3bdca7e9926bc24"},
|
||||
{file = "aiohttp-3.9.5-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e9a3d838441bebcf5cf442700e3963f58b5c33f015341f9ea86dcd7d503c07e2"},
|
||||
{file = "aiohttp-3.9.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9e3a1ae66e3d0c17cf65c08968a5ee3180c5a95920ec2731f53343fac9bad106"},
|
||||
{file = "aiohttp-3.9.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9c69e77370cce2d6df5d12b4e12bdcca60c47ba13d1cbbc8645dd005a20b738b"},
|
||||
{file = "aiohttp-3.9.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0cbf56238f4bbf49dab8c2dc2e6b1b68502b1e88d335bea59b3f5b9f4c001475"},
|
||||
{file = "aiohttp-3.9.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:d1469f228cd9ffddd396d9948b8c9cd8022b6d1bf1e40c6f25b0fb90b4f893ed"},
|
||||
{file = "aiohttp-3.9.5-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:45731330e754f5811c314901cebdf19dd776a44b31927fa4b4dbecab9e457b0c"},
|
||||
{file = "aiohttp-3.9.5-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:3fcb4046d2904378e3aeea1df51f697b0467f2aac55d232c87ba162709478c46"},
|
||||
{file = "aiohttp-3.9.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8cf142aa6c1a751fcb364158fd710b8a9be874b81889c2bd13aa8893197455e2"},
|
||||
{file = "aiohttp-3.9.5-cp39-cp39-win32.whl", hash = "sha256:7b179eea70833c8dee51ec42f3b4097bd6370892fa93f510f76762105568cf09"},
|
||||
{file = "aiohttp-3.9.5-cp39-cp39-win_amd64.whl", hash = "sha256:38d80498e2e169bc61418ff36170e0aad0cd268da8b38a17c4cf29d254a8b3f1"},
|
||||
{file = "aiohttp-3.9.5.tar.gz", hash = "sha256:edea7d15772ceeb29db4aff55e482d4bcfb6ae160ce144f2682de02f6d693551"},
|
||||
{file = "aiohttp-3.10.11-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:5077b1a5f40ffa3ba1f40d537d3bec4383988ee51fbba6b74aa8fb1bc466599e"},
|
||||
{file = "aiohttp-3.10.11-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:8d6a14a4d93b5b3c2891fca94fa9d41b2322a68194422bef0dd5ec1e57d7d298"},
|
||||
{file = "aiohttp-3.10.11-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ffbfde2443696345e23a3c597049b1dd43049bb65337837574205e7368472177"},
|
||||
{file = "aiohttp-3.10.11-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:20b3d9e416774d41813bc02fdc0663379c01817b0874b932b81c7f777f67b217"},
|
||||
{file = "aiohttp-3.10.11-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2b943011b45ee6bf74b22245c6faab736363678e910504dd7531a58c76c9015a"},
|
||||
{file = "aiohttp-3.10.11-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:48bc1d924490f0d0b3658fe5c4b081a4d56ebb58af80a6729d4bd13ea569797a"},
|
||||
{file = "aiohttp-3.10.11-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e12eb3f4b1f72aaaf6acd27d045753b18101524f72ae071ae1c91c1cd44ef115"},
|
||||
{file = "aiohttp-3.10.11-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f14ebc419a568c2eff3c1ed35f634435c24ead2fe19c07426af41e7adb68713a"},
|
||||
{file = "aiohttp-3.10.11-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:72b191cdf35a518bfc7ca87d770d30941decc5aaf897ec8b484eb5cc8c7706f3"},
|
||||
{file = "aiohttp-3.10.11-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:5ab2328a61fdc86424ee540d0aeb8b73bbcad7351fb7cf7a6546fc0bcffa0038"},
|
||||
{file = "aiohttp-3.10.11-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:aa93063d4af05c49276cf14e419550a3f45258b6b9d1f16403e777f1addf4519"},
|
||||
{file = "aiohttp-3.10.11-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:30283f9d0ce420363c24c5c2421e71a738a2155f10adbb1a11a4d4d6d2715cfc"},
|
||||
{file = "aiohttp-3.10.11-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:e5358addc8044ee49143c546d2182c15b4ac3a60be01c3209374ace05af5733d"},
|
||||
{file = "aiohttp-3.10.11-cp310-cp310-win32.whl", hash = "sha256:e1ffa713d3ea7cdcd4aea9cddccab41edf6882fa9552940344c44e59652e1120"},
|
||||
{file = "aiohttp-3.10.11-cp310-cp310-win_amd64.whl", hash = "sha256:778cbd01f18ff78b5dd23c77eb82987ee4ba23408cbed233009fd570dda7e674"},
|
||||
{file = "aiohttp-3.10.11-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:80ff08556c7f59a7972b1e8919f62e9c069c33566a6d28586771711e0eea4f07"},
|
||||
{file = "aiohttp-3.10.11-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2c8f96e9ee19f04c4914e4e7a42a60861066d3e1abf05c726f38d9d0a466e695"},
|
||||
{file = "aiohttp-3.10.11-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:fb8601394d537da9221947b5d6e62b064c9a43e88a1ecd7414d21a1a6fba9c24"},
|
||||
{file = "aiohttp-3.10.11-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2ea224cf7bc2d8856d6971cea73b1d50c9c51d36971faf1abc169a0d5f85a382"},
|
||||
{file = "aiohttp-3.10.11-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:db9503f79e12d5d80b3efd4d01312853565c05367493379df76d2674af881caa"},
|
||||
{file = "aiohttp-3.10.11-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0f449a50cc33f0384f633894d8d3cd020e3ccef81879c6e6245c3c375c448625"},
|
||||
{file = "aiohttp-3.10.11-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:82052be3e6d9e0c123499127782a01a2b224b8af8c62ab46b3f6197035ad94e9"},
|
||||
{file = "aiohttp-3.10.11-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:20063c7acf1eec550c8eb098deb5ed9e1bb0521613b03bb93644b810986027ac"},
|
||||
{file = "aiohttp-3.10.11-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:489cced07a4c11488f47aab1f00d0c572506883f877af100a38f1fedaa884c3a"},
|
||||
{file = "aiohttp-3.10.11-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:ea9b3bab329aeaa603ed3bf605f1e2a6f36496ad7e0e1aa42025f368ee2dc07b"},
|
||||
{file = "aiohttp-3.10.11-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:ca117819d8ad113413016cb29774b3f6d99ad23c220069789fc050267b786c16"},
|
||||
{file = "aiohttp-3.10.11-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:2dfb612dcbe70fb7cdcf3499e8d483079b89749c857a8f6e80263b021745c730"},
|
||||
{file = "aiohttp-3.10.11-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f9b615d3da0d60e7d53c62e22b4fd1c70f4ae5993a44687b011ea3a2e49051b8"},
|
||||
{file = "aiohttp-3.10.11-cp311-cp311-win32.whl", hash = "sha256:29103f9099b6068bbdf44d6a3d090e0a0b2be6d3c9f16a070dd9d0d910ec08f9"},
|
||||
{file = "aiohttp-3.10.11-cp311-cp311-win_amd64.whl", hash = "sha256:236b28ceb79532da85d59aa9b9bf873b364e27a0acb2ceaba475dc61cffb6f3f"},
|
||||
{file = "aiohttp-3.10.11-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:7480519f70e32bfb101d71fb9a1f330fbd291655a4c1c922232a48c458c52710"},
|
||||
{file = "aiohttp-3.10.11-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f65267266c9aeb2287a6622ee2bb39490292552f9fbf851baabc04c9f84e048d"},
|
||||
{file = "aiohttp-3.10.11-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7400a93d629a0608dc1d6c55f1e3d6e07f7375745aaa8bd7f085571e4d1cee97"},
|
||||
{file = "aiohttp-3.10.11-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f34b97e4b11b8d4eb2c3a4f975be626cc8af99ff479da7de49ac2c6d02d35725"},
|
||||
{file = "aiohttp-3.10.11-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1e7b825da878464a252ccff2958838f9caa82f32a8dbc334eb9b34a026e2c636"},
|
||||
{file = "aiohttp-3.10.11-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f9f92a344c50b9667827da308473005f34767b6a2a60d9acff56ae94f895f385"},
|
||||
{file = "aiohttp-3.10.11-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc6f1ab987a27b83c5268a17218463c2ec08dbb754195113867a27b166cd6087"},
|
||||
{file = "aiohttp-3.10.11-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1dc0f4ca54842173d03322793ebcf2c8cc2d34ae91cc762478e295d8e361e03f"},
|
||||
{file = "aiohttp-3.10.11-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7ce6a51469bfaacff146e59e7fb61c9c23006495d11cc24c514a455032bcfa03"},
|
||||
{file = "aiohttp-3.10.11-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:aad3cd91d484d065ede16f3cf15408254e2469e3f613b241a1db552c5eb7ab7d"},
|
||||
{file = "aiohttp-3.10.11-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:f4df4b8ca97f658c880fb4b90b1d1ec528315d4030af1ec763247ebfd33d8b9a"},
|
||||
{file = "aiohttp-3.10.11-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:2e4e18a0a2d03531edbc06c366954e40a3f8d2a88d2b936bbe78a0c75a3aab3e"},
|
||||
{file = "aiohttp-3.10.11-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6ce66780fa1a20e45bc753cda2a149daa6dbf1561fc1289fa0c308391c7bc0a4"},
|
||||
{file = "aiohttp-3.10.11-cp312-cp312-win32.whl", hash = "sha256:a919c8957695ea4c0e7a3e8d16494e3477b86f33067478f43106921c2fef15bb"},
|
||||
{file = "aiohttp-3.10.11-cp312-cp312-win_amd64.whl", hash = "sha256:b5e29706e6389a2283a91611c91bf24f218962717c8f3b4e528ef529d112ee27"},
|
||||
{file = "aiohttp-3.10.11-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:703938e22434d7d14ec22f9f310559331f455018389222eed132808cd8f44127"},
|
||||
{file = "aiohttp-3.10.11-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:9bc50b63648840854e00084c2b43035a62e033cb9b06d8c22b409d56eb098413"},
|
||||
{file = "aiohttp-3.10.11-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5f0463bf8b0754bc744e1feb61590706823795041e63edf30118a6f0bf577461"},
|
||||
{file = "aiohttp-3.10.11-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f6c6dec398ac5a87cb3a407b068e1106b20ef001c344e34154616183fe684288"},
|
||||
{file = "aiohttp-3.10.11-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bcaf2d79104d53d4dcf934f7ce76d3d155302d07dae24dff6c9fffd217568067"},
|
||||
{file = "aiohttp-3.10.11-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:25fd5470922091b5a9aeeb7e75be609e16b4fba81cdeaf12981393fb240dd10e"},
|
||||
{file = "aiohttp-3.10.11-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bbde2ca67230923a42161b1f408c3992ae6e0be782dca0c44cb3206bf330dee1"},
|
||||
{file = "aiohttp-3.10.11-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:249c8ff8d26a8b41a0f12f9df804e7c685ca35a207e2410adbd3e924217b9006"},
|
||||
{file = "aiohttp-3.10.11-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:878ca6a931ee8c486a8f7b432b65431d095c522cbeb34892bee5be97b3481d0f"},
|
||||
{file = "aiohttp-3.10.11-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:8663f7777ce775f0413324be0d96d9730959b2ca73d9b7e2c2c90539139cbdd6"},
|
||||
{file = "aiohttp-3.10.11-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:6cd3f10b01f0c31481fba8d302b61603a2acb37b9d30e1d14e0f5a58b7b18a31"},
|
||||
{file = "aiohttp-3.10.11-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:4e8d8aad9402d3aa02fdc5ca2fe68bcb9fdfe1f77b40b10410a94c7f408b664d"},
|
||||
{file = "aiohttp-3.10.11-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:38e3c4f80196b4f6c3a85d134a534a56f52da9cb8d8e7af1b79a32eefee73a00"},
|
||||
{file = "aiohttp-3.10.11-cp313-cp313-win32.whl", hash = "sha256:fc31820cfc3b2863c6e95e14fcf815dc7afe52480b4dc03393c4873bb5599f71"},
|
||||
{file = "aiohttp-3.10.11-cp313-cp313-win_amd64.whl", hash = "sha256:4996ff1345704ffdd6d75fb06ed175938c133425af616142e7187f28dc75f14e"},
|
||||
{file = "aiohttp-3.10.11-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:74baf1a7d948b3d640badeac333af581a367ab916b37e44cf90a0334157cdfd2"},
|
||||
{file = "aiohttp-3.10.11-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:473aebc3b871646e1940c05268d451f2543a1d209f47035b594b9d4e91ce8339"},
|
||||
{file = "aiohttp-3.10.11-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:c2f746a6968c54ab2186574e15c3f14f3e7f67aef12b761e043b33b89c5b5f95"},
|
||||
{file = "aiohttp-3.10.11-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d110cabad8360ffa0dec8f6ec60e43286e9d251e77db4763a87dcfe55b4adb92"},
|
||||
{file = "aiohttp-3.10.11-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e0099c7d5d7afff4202a0c670e5b723f7718810000b4abcbc96b064129e64bc7"},
|
||||
{file = "aiohttp-3.10.11-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0316e624b754dbbf8c872b62fe6dcb395ef20c70e59890dfa0de9eafccd2849d"},
|
||||
{file = "aiohttp-3.10.11-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5a5f7ab8baf13314e6b2485965cbacb94afff1e93466ac4d06a47a81c50f9cca"},
|
||||
{file = "aiohttp-3.10.11-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c891011e76041e6508cbfc469dd1a8ea09bc24e87e4c204e05f150c4c455a5fa"},
|
||||
{file = "aiohttp-3.10.11-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:9208299251370ee815473270c52cd3f7069ee9ed348d941d574d1457d2c73e8b"},
|
||||
{file = "aiohttp-3.10.11-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:459f0f32c8356e8125f45eeff0ecf2b1cb6db1551304972702f34cd9e6c44658"},
|
||||
{file = "aiohttp-3.10.11-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:14cdc8c1810bbd4b4b9f142eeee23cda528ae4e57ea0923551a9af4820980e39"},
|
||||
{file = "aiohttp-3.10.11-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:971aa438a29701d4b34e4943e91b5e984c3ae6ccbf80dd9efaffb01bd0b243a9"},
|
||||
{file = "aiohttp-3.10.11-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:9a309c5de392dfe0f32ee57fa43ed8fc6ddf9985425e84bd51ed66bb16bce3a7"},
|
||||
{file = "aiohttp-3.10.11-cp38-cp38-win32.whl", hash = "sha256:9ec1628180241d906a0840b38f162a3215114b14541f1a8711c368a8739a9be4"},
|
||||
{file = "aiohttp-3.10.11-cp38-cp38-win_amd64.whl", hash = "sha256:9c6e0ffd52c929f985c7258f83185d17c76d4275ad22e90aa29f38e211aacbec"},
|
||||
{file = "aiohttp-3.10.11-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:cdc493a2e5d8dc79b2df5bec9558425bcd39aff59fc949810cbd0832e294b106"},
|
||||
{file = "aiohttp-3.10.11-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b3e70f24e7d0405be2348da9d5a7836936bf3a9b4fd210f8c37e8d48bc32eca6"},
|
||||
{file = "aiohttp-3.10.11-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:968b8fb2a5eee2770eda9c7b5581587ef9b96fbdf8dcabc6b446d35ccc69df01"},
|
||||
{file = "aiohttp-3.10.11-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:deef4362af9493d1382ef86732ee2e4cbc0d7c005947bd54ad1a9a16dd59298e"},
|
||||
{file = "aiohttp-3.10.11-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:686b03196976e327412a1b094f4120778c7c4b9cff9bce8d2fdfeca386b89829"},
|
||||
{file = "aiohttp-3.10.11-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3bf6d027d9d1d34e1c2e1645f18a6498c98d634f8e373395221121f1c258ace8"},
|
||||
{file = "aiohttp-3.10.11-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:099fd126bf960f96d34a760e747a629c27fb3634da5d05c7ef4d35ef4ea519fc"},
|
||||
{file = "aiohttp-3.10.11-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c73c4d3dae0b4644bc21e3de546530531d6cdc88659cdeb6579cd627d3c206aa"},
|
||||
{file = "aiohttp-3.10.11-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:0c5580f3c51eea91559db3facd45d72e7ec970b04528b4709b1f9c2555bd6d0b"},
|
||||
{file = "aiohttp-3.10.11-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:fdf6429f0caabfd8a30c4e2eaecb547b3c340e4730ebfe25139779b9815ba138"},
|
||||
{file = "aiohttp-3.10.11-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:d97187de3c276263db3564bb9d9fad9e15b51ea10a371ffa5947a5ba93ad6777"},
|
||||
{file = "aiohttp-3.10.11-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:0acafb350cfb2eba70eb5d271f55e08bd4502ec35e964e18ad3e7d34d71f7261"},
|
||||
{file = "aiohttp-3.10.11-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:c13ed0c779911c7998a58e7848954bd4d63df3e3575f591e321b19a2aec8df9f"},
|
||||
{file = "aiohttp-3.10.11-cp39-cp39-win32.whl", hash = "sha256:22b7c540c55909140f63ab4f54ec2c20d2635c0289cdd8006da46f3327f971b9"},
|
||||
{file = "aiohttp-3.10.11-cp39-cp39-win_amd64.whl", hash = "sha256:7b26b1551e481012575dab8e3727b16fe7dd27eb2711d2e63ced7368756268fb"},
|
||||
{file = "aiohttp-3.10.11.tar.gz", hash = "sha256:9dc2b8f3dcab2e39e0fa309c8da50c3b55e6f34ab25f1a71d3288f24924d33a7"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
aiohappyeyeballs = ">=2.3.0"
|
||||
aiosignal = ">=1.1.2"
|
||||
async-timeout = {version = ">=4.0,<5.0", markers = "python_version < \"3.11\""}
|
||||
async-timeout = {version = ">=4.0,<6.0", markers = "python_version < \"3.11\""}
|
||||
attrs = ">=17.3.0"
|
||||
frozenlist = ">=1.1.1"
|
||||
multidict = ">=4.5,<7.0"
|
||||
yarl = ">=1.0,<2.0"
|
||||
yarl = ">=1.12.0,<2.0"
|
||||
|
||||
[package.extras]
|
||||
speedups = ["Brotli", "aiodns", "brotlicffi"]
|
||||
speedups = ["Brotli ; platform_python_implementation == \"CPython\"", "aiodns (>=3.2.0) ; sys_platform == \"linux\" or sys_platform == \"darwin\"", "brotlicffi ; platform_python_implementation != \"CPython\""]
|
||||
|
||||
[[package]]
|
||||
name = "aiohttp-cors"
|
||||
@@ -102,6 +131,7 @@ version = "0.7.0"
|
||||
description = "CORS support for aiohttp"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "aiohttp-cors-0.7.0.tar.gz", hash = "sha256:4d39c6d7100fd9764ed1caf8cebf0eb01bf5e3f24e2e073fda6234bc48b19f5d"},
|
||||
{file = "aiohttp_cors-0.7.0-py3-none-any.whl", hash = "sha256:0451ba59fdf6909d0e2cd21e4c0a43752bc0703d33fc78ae94d9d9321710193e"},
|
||||
@@ -116,6 +146,7 @@ version = "1.6"
|
||||
description = "jinja2 template renderer for aiohttp.web (http server for asyncio)"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "aiohttp-jinja2-1.6.tar.gz", hash = "sha256:a3a7ff5264e5bca52e8ae547bbfd0761b72495230d438d05b6c0915be619b0e2"},
|
||||
{file = "aiohttp_jinja2-1.6-py3-none-any.whl", hash = "sha256:0df405ee6ad1b58e5a068a105407dc7dcc1704544c559f1938babde954f945c7"},
|
||||
@@ -131,6 +162,7 @@ version = "1.3.1"
|
||||
description = "aiosignal: a list of registered asynchronous callbacks"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "aiosignal-1.3.1-py3-none-any.whl", hash = "sha256:f8376fb07dd1e86a584e4fcdec80b36b7f81aac666ebc724e2c090300dd83b17"},
|
||||
{file = "aiosignal-1.3.1.tar.gz", hash = "sha256:54cd96e15e1649b75d6c87526a6ff0b6c1b0dd3459f43d9ca11d48c339b68cfc"},
|
||||
@@ -145,6 +177,7 @@ version = "0.17.4"
|
||||
description = "Python graph (network) package"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
groups = ["dev"]
|
||||
files = [
|
||||
{file = "altgraph-0.17.4-py2.py3-none-any.whl", hash = "sha256:642743b4750de17e655e6711601b077bc6598dbfa3ba5fa2b2a35ce12b508dff"},
|
||||
{file = "altgraph-0.17.4.tar.gz", hash = "sha256:1b5afbb98f6c4dcadb2e2ae6ab9fa994bbb8c1d75f4fa96d340f9437ae454406"},
|
||||
@@ -156,6 +189,8 @@ version = "4.0.3"
|
||||
description = "Timeout context manager for asyncio programs"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
groups = ["main"]
|
||||
markers = "python_version == \"3.10\""
|
||||
files = [
|
||||
{file = "async-timeout-4.0.3.tar.gz", hash = "sha256:4640d96be84d82d02ed59ea2b7105a0f7b33abe8703703cd0ab0bf87c427522f"},
|
||||
{file = "async_timeout-4.0.3-py3-none-any.whl", hash = "sha256:7405140ff1230c310e51dc27b3145b9092d659ce68ff733fb0cefe3ee42be028"},
|
||||
@@ -167,6 +202,7 @@ version = "23.2.0"
|
||||
description = "Classes Without Boilerplate"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "attrs-23.2.0-py3-none-any.whl", hash = "sha256:99b87a485a5820b23b879f04c2305b44b951b502fd64be915879d77a7e8fc6f1"},
|
||||
{file = "attrs-23.2.0.tar.gz", hash = "sha256:935dc3b529c262f6cf76e50877d35a4bd3c1de194fd41f47a2b7ae8f19971f30"},
|
||||
@@ -177,8 +213,8 @@ cov = ["attrs[tests]", "coverage[toml] (>=5.3)"]
|
||||
dev = ["attrs[tests]", "pre-commit"]
|
||||
docs = ["furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier", "zope-interface"]
|
||||
tests = ["attrs[tests-no-zope]", "zope-interface"]
|
||||
tests-mypy = ["mypy (>=1.6)", "pytest-mypy-plugins"]
|
||||
tests-no-zope = ["attrs[tests-mypy]", "cloudpickle", "hypothesis", "pympler", "pytest (>=4.3.0)", "pytest-xdist[psutil]"]
|
||||
tests-mypy = ["mypy (>=1.6) ; platform_python_implementation == \"CPython\" and python_version >= \"3.8\"", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.8\""]
|
||||
tests-no-zope = ["attrs[tests-mypy]", "cloudpickle ; platform_python_implementation == \"CPython\"", "hypothesis", "pympler", "pytest (>=4.3.0)", "pytest-xdist[psutil]"]
|
||||
|
||||
[[package]]
|
||||
name = "certifi"
|
||||
@@ -186,6 +222,7 @@ version = "2024.7.4"
|
||||
description = "Python package for providing Mozilla's CA Bundle."
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "certifi-2024.7.4-py3-none-any.whl", hash = "sha256:c198e21b1289c2ab85ee4e67bb4b4ef3ead0892059901a8d5b622f24a1101e90"},
|
||||
{file = "certifi-2024.7.4.tar.gz", hash = "sha256:5a1e7645bc0ec61a09e26c36f6106dd4cf40c6db3a1fb6352b0244e7fb057c7b"},
|
||||
@@ -197,6 +234,7 @@ version = "1.4.1"
|
||||
description = "A list-like structure which implements collections.abc.MutableSequence"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "frozenlist-1.4.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:f9aa1878d1083b276b0196f2dfbe00c9b7e752475ed3b682025ff20c1c1f51ac"},
|
||||
{file = "frozenlist-1.4.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:29acab3f66f0f24674b7dc4736477bcd4bc3ad4b896f5f45379a67bce8b96868"},
|
||||
@@ -283,6 +321,7 @@ version = "3.7"
|
||||
description = "Internationalized Domain Names in Applications (IDNA)"
|
||||
optional = false
|
||||
python-versions = ">=3.5"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "idna-3.7-py3-none-any.whl", hash = "sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0"},
|
||||
{file = "idna-3.7.tar.gz", hash = "sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc"},
|
||||
@@ -290,13 +329,14 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "jinja2"
|
||||
version = "3.1.4"
|
||||
version = "3.1.6"
|
||||
description = "A very fast and expressive template engine."
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "jinja2-3.1.4-py3-none-any.whl", hash = "sha256:bc5dd2abb727a5319567b7a813e6a2e7318c39f4f487cfe6c89c6f9c7d25197d"},
|
||||
{file = "jinja2-3.1.4.tar.gz", hash = "sha256:4a3aee7acbbe7303aede8e9648d13b8bf88a429282aa6122a993f0ac800cb369"},
|
||||
{file = "jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67"},
|
||||
{file = "jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@@ -311,6 +351,8 @@ version = "1.16.3"
|
||||
description = "Mach-O header analysis and editing"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
groups = ["dev"]
|
||||
markers = "sys_platform == \"darwin\""
|
||||
files = [
|
||||
{file = "macholib-1.16.3-py2.py3-none-any.whl", hash = "sha256:0e315d7583d38b8c77e815b1ecbdbf504a8258d8b3e17b61165c6feb60d18f2c"},
|
||||
{file = "macholib-1.16.3.tar.gz", hash = "sha256:07ae9e15e8e4cd9a788013d81f5908b3609aa76f9b1421bae9c4d7606ec86a30"},
|
||||
@@ -325,6 +367,7 @@ version = "2.1.5"
|
||||
description = "Safely add untrusted strings to HTML/XML markup."
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a17a92de5231666cfbe003f0e4b9b3a7ae3afb1ec2845aadc2bacc93ff85febc"},
|
||||
{file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:72b6be590cc35924b02c78ef34b467da4ba07e4e0f0454a2c5907f473fc50ce5"},
|
||||
@@ -394,6 +437,7 @@ version = "6.0.5"
|
||||
description = "multidict implementation"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "multidict-6.0.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:228b644ae063c10e7f324ab1ab6b548bdf6f8b47f3ec234fef1093bc2735e5f9"},
|
||||
{file = "multidict-6.0.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:896ebdcf62683551312c30e20614305f53125750803b614e9e6ce74a96232604"},
|
||||
@@ -493,6 +537,7 @@ version = "1.9.1"
|
||||
description = "Node.js virtual environment builder"
|
||||
optional = false
|
||||
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7"
|
||||
groups = ["dev"]
|
||||
files = [
|
||||
{file = "nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9"},
|
||||
{file = "nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f"},
|
||||
@@ -504,6 +549,7 @@ version = "24.1"
|
||||
description = "Core utilities for Python packages"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
groups = ["main", "dev"]
|
||||
files = [
|
||||
{file = "packaging-24.1-py3-none-any.whl", hash = "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124"},
|
||||
{file = "packaging-24.1.tar.gz", hash = "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002"},
|
||||
@@ -515,38 +561,149 @@ version = "2023.2.7"
|
||||
description = "Python PE parsing module"
|
||||
optional = false
|
||||
python-versions = ">=3.6.0"
|
||||
groups = ["dev"]
|
||||
markers = "sys_platform == \"win32\""
|
||||
files = [
|
||||
{file = "pefile-2023.2.7-py3-none-any.whl", hash = "sha256:da185cd2af68c08a6cd4481f7325ed600a88f6a813bad9dea07ab3ef73d8d8d6"},
|
||||
{file = "pefile-2023.2.7.tar.gz", hash = "sha256:82e6114004b3d6911c77c3953e3838654b04511b8b66e8583db70c65998017dc"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "propcache"
|
||||
version = "0.2.0"
|
||||
description = "Accelerated property cache"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "propcache-0.2.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:c5869b8fd70b81835a6f187c5fdbe67917a04d7e52b6e7cc4e5fe39d55c39d58"},
|
||||
{file = "propcache-0.2.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:952e0d9d07609d9c5be361f33b0d6d650cd2bae393aabb11d9b719364521984b"},
|
||||
{file = "propcache-0.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:33ac8f098df0585c0b53009f039dfd913b38c1d2edafed0cedcc0c32a05aa110"},
|
||||
{file = "propcache-0.2.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:97e48e8875e6c13909c800fa344cd54cc4b2b0db1d5f911f840458a500fde2c2"},
|
||||
{file = "propcache-0.2.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:388f3217649d6d59292b722d940d4d2e1e6a7003259eb835724092a1cca0203a"},
|
||||
{file = "propcache-0.2.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f571aea50ba5623c308aa146eb650eebf7dbe0fd8c5d946e28343cb3b5aad577"},
|
||||
{file = "propcache-0.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3dfafb44f7bb35c0c06eda6b2ab4bfd58f02729e7c4045e179f9a861b07c9850"},
|
||||
{file = "propcache-0.2.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a3ebe9a75be7ab0b7da2464a77bb27febcb4fab46a34f9288f39d74833db7f61"},
|
||||
{file = "propcache-0.2.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d2f0d0f976985f85dfb5f3d685697ef769faa6b71993b46b295cdbbd6be8cc37"},
|
||||
{file = "propcache-0.2.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:a3dc1a4b165283bd865e8f8cb5f0c64c05001e0718ed06250d8cac9bec115b48"},
|
||||
{file = "propcache-0.2.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:9e0f07b42d2a50c7dd2d8675d50f7343d998c64008f1da5fef888396b7f84630"},
|
||||
{file = "propcache-0.2.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:e63e3e1e0271f374ed489ff5ee73d4b6e7c60710e1f76af5f0e1a6117cd26394"},
|
||||
{file = "propcache-0.2.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:56bb5c98f058a41bb58eead194b4db8c05b088c93d94d5161728515bd52b052b"},
|
||||
{file = "propcache-0.2.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:7665f04d0c7f26ff8bb534e1c65068409bf4687aa2534faf7104d7182debb336"},
|
||||
{file = "propcache-0.2.0-cp310-cp310-win32.whl", hash = "sha256:7cf18abf9764746b9c8704774d8b06714bcb0a63641518a3a89c7f85cc02c2ad"},
|
||||
{file = "propcache-0.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:cfac69017ef97db2438efb854edf24f5a29fd09a536ff3a992b75990720cdc99"},
|
||||
{file = "propcache-0.2.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:63f13bf09cc3336eb04a837490b8f332e0db41da66995c9fd1ba04552e516354"},
|
||||
{file = "propcache-0.2.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:608cce1da6f2672a56b24a015b42db4ac612ee709f3d29f27a00c943d9e851de"},
|
||||
{file = "propcache-0.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:466c219deee4536fbc83c08d09115249db301550625c7fef1c5563a584c9bc87"},
|
||||
{file = "propcache-0.2.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fc2db02409338bf36590aa985a461b2c96fce91f8e7e0f14c50c5fcc4f229016"},
|
||||
{file = "propcache-0.2.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a6ed8db0a556343d566a5c124ee483ae113acc9a557a807d439bcecc44e7dfbb"},
|
||||
{file = "propcache-0.2.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:91997d9cb4a325b60d4e3f20967f8eb08dfcb32b22554d5ef78e6fd1dda743a2"},
|
||||
{file = "propcache-0.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4c7dde9e533c0a49d802b4f3f218fa9ad0a1ce21f2c2eb80d5216565202acab4"},
|
||||
{file = "propcache-0.2.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffcad6c564fe6b9b8916c1aefbb37a362deebf9394bd2974e9d84232e3e08504"},
|
||||
{file = "propcache-0.2.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:97a58a28bcf63284e8b4d7b460cbee1edaab24634e82059c7b8c09e65284f178"},
|
||||
{file = "propcache-0.2.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:945db8ee295d3af9dbdbb698cce9bbc5c59b5c3fe328bbc4387f59a8a35f998d"},
|
||||
{file = "propcache-0.2.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:39e104da444a34830751715f45ef9fc537475ba21b7f1f5b0f4d71a3b60d7fe2"},
|
||||
{file = "propcache-0.2.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:c5ecca8f9bab618340c8e848d340baf68bcd8ad90a8ecd7a4524a81c1764b3db"},
|
||||
{file = "propcache-0.2.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:c436130cc779806bdf5d5fae0d848713105472b8566b75ff70048c47d3961c5b"},
|
||||
{file = "propcache-0.2.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:191db28dc6dcd29d1a3e063c3be0b40688ed76434622c53a284e5427565bbd9b"},
|
||||
{file = "propcache-0.2.0-cp311-cp311-win32.whl", hash = "sha256:5f2564ec89058ee7c7989a7b719115bdfe2a2fb8e7a4543b8d1c0cc4cf6478c1"},
|
||||
{file = "propcache-0.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:6e2e54267980349b723cff366d1e29b138b9a60fa376664a157a342689553f71"},
|
||||
{file = "propcache-0.2.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:2ee7606193fb267be4b2e3b32714f2d58cad27217638db98a60f9efb5efeccc2"},
|
||||
{file = "propcache-0.2.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:91ee8fc02ca52e24bcb77b234f22afc03288e1dafbb1f88fe24db308910c4ac7"},
|
||||
{file = "propcache-0.2.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2e900bad2a8456d00a113cad8c13343f3b1f327534e3589acc2219729237a2e8"},
|
||||
{file = "propcache-0.2.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f52a68c21363c45297aca15561812d542f8fc683c85201df0bebe209e349f793"},
|
||||
{file = "propcache-0.2.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1e41d67757ff4fbc8ef2af99b338bfb955010444b92929e9e55a6d4dcc3c4f09"},
|
||||
{file = "propcache-0.2.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a64e32f8bd94c105cc27f42d3b658902b5bcc947ece3c8fe7bc1b05982f60e89"},
|
||||
{file = "propcache-0.2.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:55346705687dbd7ef0d77883ab4f6fabc48232f587925bdaf95219bae072491e"},
|
||||
{file = "propcache-0.2.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:00181262b17e517df2cd85656fcd6b4e70946fe62cd625b9d74ac9977b64d8d9"},
|
||||
{file = "propcache-0.2.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6994984550eaf25dd7fc7bd1b700ff45c894149341725bb4edc67f0ffa94efa4"},
|
||||
{file = "propcache-0.2.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:56295eb1e5f3aecd516d91b00cfd8bf3a13991de5a479df9e27dd569ea23959c"},
|
||||
{file = "propcache-0.2.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:439e76255daa0f8151d3cb325f6dd4a3e93043e6403e6491813bcaaaa8733887"},
|
||||
{file = "propcache-0.2.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:f6475a1b2ecb310c98c28d271a30df74f9dd436ee46d09236a6b750a7599ce57"},
|
||||
{file = "propcache-0.2.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:3444cdba6628accf384e349014084b1cacd866fbb88433cd9d279d90a54e0b23"},
|
||||
{file = "propcache-0.2.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:4a9d9b4d0a9b38d1c391bb4ad24aa65f306c6f01b512e10a8a34a2dc5675d348"},
|
||||
{file = "propcache-0.2.0-cp312-cp312-win32.whl", hash = "sha256:69d3a98eebae99a420d4b28756c8ce6ea5a29291baf2dc9ff9414b42676f61d5"},
|
||||
{file = "propcache-0.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:ad9c9b99b05f163109466638bd30ada1722abb01bbb85c739c50b6dc11f92dc3"},
|
||||
{file = "propcache-0.2.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ecddc221a077a8132cf7c747d5352a15ed763b674c0448d811f408bf803d9ad7"},
|
||||
{file = "propcache-0.2.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0e53cb83fdd61cbd67202735e6a6687a7b491c8742dfc39c9e01e80354956763"},
|
||||
{file = "propcache-0.2.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:92fe151145a990c22cbccf9ae15cae8ae9eddabfc949a219c9f667877e40853d"},
|
||||
{file = "propcache-0.2.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d6a21ef516d36909931a2967621eecb256018aeb11fc48656e3257e73e2e247a"},
|
||||
{file = "propcache-0.2.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3f88a4095e913f98988f5b338c1d4d5d07dbb0b6bad19892fd447484e483ba6b"},
|
||||
{file = "propcache-0.2.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5a5b3bb545ead161be780ee85a2b54fdf7092815995661947812dde94a40f6fb"},
|
||||
{file = "propcache-0.2.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:67aeb72e0f482709991aa91345a831d0b707d16b0257e8ef88a2ad246a7280bf"},
|
||||
{file = "propcache-0.2.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3c997f8c44ec9b9b0bcbf2d422cc00a1d9b9c681f56efa6ca149a941e5560da2"},
|
||||
{file = "propcache-0.2.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:2a66df3d4992bc1d725b9aa803e8c5a66c010c65c741ad901e260ece77f58d2f"},
|
||||
{file = "propcache-0.2.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:3ebbcf2a07621f29638799828b8d8668c421bfb94c6cb04269130d8de4fb7136"},
|
||||
{file = "propcache-0.2.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:1235c01ddaa80da8235741e80815ce381c5267f96cc49b1477fdcf8c047ef325"},
|
||||
{file = "propcache-0.2.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:3947483a381259c06921612550867b37d22e1df6d6d7e8361264b6d037595f44"},
|
||||
{file = "propcache-0.2.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:d5bed7f9805cc29c780f3aee05de3262ee7ce1f47083cfe9f77471e9d6777e83"},
|
||||
{file = "propcache-0.2.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e4a91d44379f45f5e540971d41e4626dacd7f01004826a18cb048e7da7e96544"},
|
||||
{file = "propcache-0.2.0-cp313-cp313-win32.whl", hash = "sha256:f902804113e032e2cdf8c71015651c97af6418363bea8d78dc0911d56c335032"},
|
||||
{file = "propcache-0.2.0-cp313-cp313-win_amd64.whl", hash = "sha256:8f188cfcc64fb1266f4684206c9de0e80f54622c3f22a910cbd200478aeae61e"},
|
||||
{file = "propcache-0.2.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:53d1bd3f979ed529f0805dd35ddaca330f80a9a6d90bc0121d2ff398f8ed8861"},
|
||||
{file = "propcache-0.2.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:83928404adf8fb3d26793665633ea79b7361efa0287dfbd372a7e74311d51ee6"},
|
||||
{file = "propcache-0.2.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:77a86c261679ea5f3896ec060be9dc8e365788248cc1e049632a1be682442063"},
|
||||
{file = "propcache-0.2.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:218db2a3c297a3768c11a34812e63b3ac1c3234c3a086def9c0fee50d35add1f"},
|
||||
{file = "propcache-0.2.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7735e82e3498c27bcb2d17cb65d62c14f1100b71723b68362872bca7d0913d90"},
|
||||
{file = "propcache-0.2.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:20a617c776f520c3875cf4511e0d1db847a076d720714ae35ffe0df3e440be68"},
|
||||
{file = "propcache-0.2.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:67b69535c870670c9f9b14a75d28baa32221d06f6b6fa6f77a0a13c5a7b0a5b9"},
|
||||
{file = "propcache-0.2.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4569158070180c3855e9c0791c56be3ceeb192defa2cdf6a3f39e54319e56b89"},
|
||||
{file = "propcache-0.2.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:db47514ffdbd91ccdc7e6f8407aac4ee94cc871b15b577c1c324236b013ddd04"},
|
||||
{file = "propcache-0.2.0-cp38-cp38-musllinux_1_2_armv7l.whl", hash = "sha256:2a60ad3e2553a74168d275a0ef35e8c0a965448ffbc3b300ab3a5bb9956c2162"},
|
||||
{file = "propcache-0.2.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:662dd62358bdeaca0aee5761de8727cfd6861432e3bb828dc2a693aa0471a563"},
|
||||
{file = "propcache-0.2.0-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:25a1f88b471b3bc911d18b935ecb7115dff3a192b6fef46f0bfaf71ff4f12418"},
|
||||
{file = "propcache-0.2.0-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:f60f0ac7005b9f5a6091009b09a419ace1610e163fa5deaba5ce3484341840e7"},
|
||||
{file = "propcache-0.2.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:74acd6e291f885678631b7ebc85d2d4aec458dd849b8c841b57ef04047833bed"},
|
||||
{file = "propcache-0.2.0-cp38-cp38-win32.whl", hash = "sha256:d9b6ddac6408194e934002a69bcaadbc88c10b5f38fb9307779d1c629181815d"},
|
||||
{file = "propcache-0.2.0-cp38-cp38-win_amd64.whl", hash = "sha256:676135dcf3262c9c5081cc8f19ad55c8a64e3f7282a21266d05544450bffc3a5"},
|
||||
{file = "propcache-0.2.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:25c8d773a62ce0451b020c7b29a35cfbc05de8b291163a7a0f3b7904f27253e6"},
|
||||
{file = "propcache-0.2.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:375a12d7556d462dc64d70475a9ee5982465fbb3d2b364f16b86ba9135793638"},
|
||||
{file = "propcache-0.2.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1ec43d76b9677637a89d6ab86e1fef70d739217fefa208c65352ecf0282be957"},
|
||||
{file = "propcache-0.2.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f45eec587dafd4b2d41ac189c2156461ebd0c1082d2fe7013571598abb8505d1"},
|
||||
{file = "propcache-0.2.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bc092ba439d91df90aea38168e11f75c655880c12782facf5cf9c00f3d42b562"},
|
||||
{file = "propcache-0.2.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fa1076244f54bb76e65e22cb6910365779d5c3d71d1f18b275f1dfc7b0d71b4d"},
|
||||
{file = "propcache-0.2.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:682a7c79a2fbf40f5dbb1eb6bfe2cd865376deeac65acf9beb607505dced9e12"},
|
||||
{file = "propcache-0.2.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8e40876731f99b6f3c897b66b803c9e1c07a989b366c6b5b475fafd1f7ba3fb8"},
|
||||
{file = "propcache-0.2.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:363ea8cd3c5cb6679f1c2f5f1f9669587361c062e4899fce56758efa928728f8"},
|
||||
{file = "propcache-0.2.0-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:140fbf08ab3588b3468932974a9331aff43c0ab8a2ec2c608b6d7d1756dbb6cb"},
|
||||
{file = "propcache-0.2.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:e70fac33e8b4ac63dfc4c956fd7d85a0b1139adcfc0d964ce288b7c527537fea"},
|
||||
{file = "propcache-0.2.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:b33d7a286c0dc1a15f5fc864cc48ae92a846df287ceac2dd499926c3801054a6"},
|
||||
{file = "propcache-0.2.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:f6d5749fdd33d90e34c2efb174c7e236829147a2713334d708746e94c4bde40d"},
|
||||
{file = "propcache-0.2.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:22aa8f2272d81d9317ff5756bb108021a056805ce63dd3630e27d042c8092798"},
|
||||
{file = "propcache-0.2.0-cp39-cp39-win32.whl", hash = "sha256:73e4b40ea0eda421b115248d7e79b59214411109a5bc47d0d48e4c73e3b8fcf9"},
|
||||
{file = "propcache-0.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:9517d5e9e0731957468c29dbfd0f976736a0e55afaea843726e887f36fe017df"},
|
||||
{file = "propcache-0.2.0-py3-none-any.whl", hash = "sha256:2ccc28197af5313706511fab3a8b66dcd6da067a1331372c82ea1cb74285e036"},
|
||||
{file = "propcache-0.2.0.tar.gz", hash = "sha256:df81779732feb9d01e5d513fad0122efb3d53bbc75f61b2a4f29a020bc985e70"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pyinstaller"
|
||||
version = "6.8.0"
|
||||
version = "6.14.2"
|
||||
description = "PyInstaller bundles a Python application and all its dependencies into a single package."
|
||||
optional = false
|
||||
python-versions = "<3.13,>=3.8"
|
||||
python-versions = "<3.14,>=3.8"
|
||||
groups = ["dev"]
|
||||
files = [
|
||||
{file = "pyinstaller-6.8.0-py3-none-macosx_10_13_universal2.whl", hash = "sha256:5ff6bc2784c1026f8e2f04aa3760cbed41408e108a9d4cf1dd52ee8351a3f6e1"},
|
||||
{file = "pyinstaller-6.8.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:39ac424d2ee2457d2ab11a5091436e75a0cccae207d460d180aa1fcbbafdd528"},
|
||||
{file = "pyinstaller-6.8.0-py3-none-manylinux2014_i686.whl", hash = "sha256:355832a3acc7de90a255ecacd4b9f9e166a547a79c8905d49f14e3a75c1acdb9"},
|
||||
{file = "pyinstaller-6.8.0-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:6303c7a009f47e6a96ef65aed49f41e36ece8d079b9193ca92fe807403e5fe80"},
|
||||
{file = "pyinstaller-6.8.0-py3-none-manylinux2014_s390x.whl", hash = "sha256:2b71509468c811968c0b5decb5bbe85b6292ea52d7b1f26313d2aabb673fa9a5"},
|
||||
{file = "pyinstaller-6.8.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:ff31c5b99e05a4384bbe2071df67ec8b2b347640a375eae9b40218be2f1754c6"},
|
||||
{file = "pyinstaller-6.8.0-py3-none-musllinux_1_1_aarch64.whl", hash = "sha256:000c36b13fe4cd8d0d8c2bc855b1ddcf39867b5adf389e6b5ca45b25fa3e619d"},
|
||||
{file = "pyinstaller-6.8.0-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:fe0af018d7d5077180e3144ada89a4da5df8d07716eb7e9482834a56dc57a4e8"},
|
||||
{file = "pyinstaller-6.8.0-py3-none-win32.whl", hash = "sha256:d257f6645c7334cbd66f38a4fac62c3ad614cc46302b2b5d9f8cc48c563bce0e"},
|
||||
{file = "pyinstaller-6.8.0-py3-none-win_amd64.whl", hash = "sha256:81cccfa9b16699b457f4788c5cc119b50f3cd4d0db924955f15c33f2ad27a50d"},
|
||||
{file = "pyinstaller-6.8.0-py3-none-win_arm64.whl", hash = "sha256:1c3060a263758cf7f0144ab4c016097b20451b2469d468763414665db1bb743d"},
|
||||
{file = "pyinstaller-6.8.0.tar.gz", hash = "sha256:3f4b6520f4423fe19bcc2fd63ab7238851ae2bdcbc98f25bc5d2f97cc62012e9"},
|
||||
{file = "pyinstaller-6.14.2-py3-none-macosx_10_13_universal2.whl", hash = "sha256:d77d18bf5343a1afef2772393d7a489d4ec2282dee5bca549803fc0d74b78330"},
|
||||
{file = "pyinstaller-6.14.2-py3-none-manylinux2014_aarch64.whl", hash = "sha256:3fa0c391e1300a9fd7752eb1ffe2950112b88fba9d2743eee2ef218a15f4705f"},
|
||||
{file = "pyinstaller-6.14.2-py3-none-manylinux2014_i686.whl", hash = "sha256:077efb2d01d16d9c8fdda3ad52788f0fead2791c5cec9ed6ce058af7e26eb74b"},
|
||||
{file = "pyinstaller-6.14.2-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:fdd2bd020a18736806a6bd5d3c4352f1209b427a96ad6c459d88aec1d90c4f21"},
|
||||
{file = "pyinstaller-6.14.2-py3-none-manylinux2014_s390x.whl", hash = "sha256:03862c6b3cf7b16843d24b529f89cd4077cbe467883cd54ce7a81940d6da09d3"},
|
||||
{file = "pyinstaller-6.14.2-py3-none-manylinux2014_x86_64.whl", hash = "sha256:78827a21ada2a848e98671852d20d74b2955b6e2aaf2359ed13a462e1a603d84"},
|
||||
{file = "pyinstaller-6.14.2-py3-none-musllinux_1_1_aarch64.whl", hash = "sha256:185710ab1503dfdfa14c43237d394d96ac183422d588294be42531480dfa6c38"},
|
||||
{file = "pyinstaller-6.14.2-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:6c673a7e761bd4a2560cfd5dbe1ccdcfe2dff304b774e6e5242fc5afed953661"},
|
||||
{file = "pyinstaller-6.14.2-py3-none-win32.whl", hash = "sha256:1697601aa788e3a52f0b5e620b4741a34b82e6f222ec6e1318b3a1349f566bb2"},
|
||||
{file = "pyinstaller-6.14.2-py3-none-win_amd64.whl", hash = "sha256:e10e0e67288d6dcb5898a917dd1d4272aa0ff33f197ad49a0e39618009d63ed9"},
|
||||
{file = "pyinstaller-6.14.2-py3-none-win_arm64.whl", hash = "sha256:69fd11ca57e572387826afaa4a1b3d4cb74927d76f231f0308c0bd7872ca5ac1"},
|
||||
{file = "pyinstaller-6.14.2.tar.gz", hash = "sha256:142cce0719e79315f0cc26400c2e5c45d9b6b17e7e0491fee444a9f8f16f4917"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
altgraph = "*"
|
||||
macholib = {version = ">=1.8", markers = "sys_platform == \"darwin\""}
|
||||
packaging = ">=22.0"
|
||||
pefile = {version = ">=2022.5.30", markers = "sys_platform == \"win32\""}
|
||||
pyinstaller-hooks-contrib = ">=2024.6"
|
||||
pefile = {version = ">=2022.5.30,<2024.8.26 || >2024.8.26", markers = "sys_platform == \"win32\""}
|
||||
pyinstaller-hooks-contrib = ">=2025.5"
|
||||
pywin32-ctypes = {version = ">=0.2.1", markers = "sys_platform == \"win32\""}
|
||||
setuptools = ">=42.0.0"
|
||||
|
||||
@@ -556,13 +713,14 @@ hook-testing = ["execnet (>=1.5.0)", "psutil", "pytest (>=2.7.3)"]
|
||||
|
||||
[[package]]
|
||||
name = "pyinstaller-hooks-contrib"
|
||||
version = "2024.7"
|
||||
version = "2025.8"
|
||||
description = "Community maintained hooks for PyInstaller"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
python-versions = ">=3.8"
|
||||
groups = ["dev"]
|
||||
files = [
|
||||
{file = "pyinstaller_hooks_contrib-2024.7-py2.py3-none-any.whl", hash = "sha256:8bf0775771fbaf96bcd2f4dfd6f7ae6c1dd1b1efe254c7e50477b3c08e7841d8"},
|
||||
{file = "pyinstaller_hooks_contrib-2024.7.tar.gz", hash = "sha256:fd5f37dcf99bece184e40642af88be16a9b89613ecb958a8bd1136634fc9fac5"},
|
||||
{file = "pyinstaller_hooks_contrib-2025.8-py3-none-any.whl", hash = "sha256:8d0b8cfa0cb689a619294ae200497374234bd4e3994b3ace2a4442274c899064"},
|
||||
{file = "pyinstaller_hooks_contrib-2025.8.tar.gz", hash = "sha256:3402ad41dfe9b5110af134422e37fc5d421ba342c6cb980bd67cb30b7415641c"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@@ -575,6 +733,7 @@ version = "1.1.369"
|
||||
description = "Command line wrapper for pyright"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
groups = ["dev"]
|
||||
files = [
|
||||
{file = "pyright-1.1.369-py3-none-any.whl", hash = "sha256:06d5167a8d7be62523ced0265c5d2f1e022e110caf57a25d92f50fb2d07bcda0"},
|
||||
{file = "pyright-1.1.369.tar.gz", hash = "sha256:ad290710072d021e213b98cc7a2f90ae3a48609ef5b978f749346d1a47eb9af8"},
|
||||
@@ -593,6 +752,8 @@ version = "0.2.2"
|
||||
description = "A (partial) reimplementation of pywin32 using ctypes/cffi"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
groups = ["dev"]
|
||||
markers = "sys_platform == \"win32\""
|
||||
files = [
|
||||
{file = "pywin32-ctypes-0.2.2.tar.gz", hash = "sha256:3426e063bdd5fd4df74a14fa3cf80a0b42845a87e1d1e81f6549f9daec593a60"},
|
||||
{file = "pywin32_ctypes-0.2.2-py3-none-any.whl", hash = "sha256:bf490a1a709baf35d688fe0ecf980ed4de11d2b3e37b51e5442587a75d9957e7"},
|
||||
@@ -604,6 +765,7 @@ version = "1.3.3"
|
||||
description = "A Python module to customize the process title"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "setproctitle-1.3.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:897a73208da48db41e687225f355ce993167079eda1260ba5e13c4e53be7f754"},
|
||||
{file = "setproctitle-1.3.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:8c331e91a14ba4076f88c29c777ad6b58639530ed5b24b5564b5ed2fd7a95452"},
|
||||
@@ -700,18 +862,24 @@ test = ["pytest"]
|
||||
|
||||
[[package]]
|
||||
name = "setuptools"
|
||||
version = "70.1.1"
|
||||
version = "78.1.1"
|
||||
description = "Easily download, build, install, upgrade, and uninstall Python packages"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
python-versions = ">=3.9"
|
||||
groups = ["dev"]
|
||||
files = [
|
||||
{file = "setuptools-70.1.1-py3-none-any.whl", hash = "sha256:a58a8fde0541dab0419750bcc521fbdf8585f6e5cb41909df3a472ef7b81ca95"},
|
||||
{file = "setuptools-70.1.1.tar.gz", hash = "sha256:937a48c7cdb7a21eb53cd7f9b59e525503aa8abaf3584c730dc5f7a5bec3a650"},
|
||||
{file = "setuptools-78.1.1-py3-none-any.whl", hash = "sha256:c3a9c4211ff4c309edb8b8c4f1cbfa7ae324c4ba9f91ff254e3d305b9fd54561"},
|
||||
{file = "setuptools-78.1.1.tar.gz", hash = "sha256:fcc17fd9cd898242f6b4adfaca46137a9edef687f43e6f78469692a5e70d851d"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"]
|
||||
testing = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "importlib-metadata", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "jaraco.test", "mypy (==1.10.0)", "packaging (>=23.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.1)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-home (>=0.5)", "pytest-mypy", "pytest-perf", "pytest-ruff (>=0.3.2)", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"]
|
||||
check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\"", "ruff (>=0.8.0) ; sys_platform != \"cygwin\""]
|
||||
core = ["importlib_metadata (>=6) ; python_version < \"3.10\"", "jaraco.functools (>=4)", "jaraco.text (>=3.7)", "more_itertools", "more_itertools (>=8.8)", "packaging (>=24.2)", "platformdirs (>=4.2.2)", "tomli (>=2.0.1) ; python_version < \"3.11\"", "wheel (>=0.43.0)"]
|
||||
cover = ["pytest-cov"]
|
||||
doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier", "towncrier (<24.7)"]
|
||||
enabler = ["pytest-enabler (>=2.2)"]
|
||||
test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21) ; python_version >= \"3.9\" and sys_platform != \"cygwin\"", "jaraco.envs (>=2.2)", "jaraco.path (>=3.7.2)", "jaraco.test (>=5.5)", "packaging (>=24.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-home (>=0.5)", "pytest-perf ; sys_platform != \"cygwin\"", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel (>=0.44.0)"]
|
||||
type = ["importlib_metadata (>=7.0.2) ; python_version < \"3.10\"", "jaraco.develop (>=7.21) ; sys_platform != \"cygwin\"", "mypy (==1.14.*)", "pytest-mypy"]
|
||||
|
||||
[[package]]
|
||||
name = "watchdog"
|
||||
@@ -719,6 +887,7 @@ version = "4.0.1"
|
||||
description = "Filesystem events monitoring"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "watchdog-4.0.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:da2dfdaa8006eb6a71051795856bedd97e5b03e57da96f98e375682c48850645"},
|
||||
{file = "watchdog-4.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e93f451f2dfa433d97765ca2634628b789b49ba8b504fdde5837cdcf25fdb53b"},
|
||||
@@ -759,108 +928,118 @@ watchmedo = ["PyYAML (>=3.10)"]
|
||||
|
||||
[[package]]
|
||||
name = "yarl"
|
||||
version = "1.9.4"
|
||||
version = "1.15.2"
|
||||
description = "Yet another URL library"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
python-versions = ">=3.8"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "yarl-1.9.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a8c1df72eb746f4136fe9a2e72b0c9dc1da1cbd23b5372f94b5820ff8ae30e0e"},
|
||||
{file = "yarl-1.9.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a3a6ed1d525bfb91b3fc9b690c5a21bb52de28c018530ad85093cc488bee2dd2"},
|
||||
{file = "yarl-1.9.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c38c9ddb6103ceae4e4498f9c08fac9b590c5c71b0370f98714768e22ac6fa66"},
|
||||
{file = "yarl-1.9.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d9e09c9d74f4566e905a0b8fa668c58109f7624db96a2171f21747abc7524234"},
|
||||
{file = "yarl-1.9.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b8477c1ee4bd47c57d49621a062121c3023609f7a13b8a46953eb6c9716ca392"},
|
||||
{file = "yarl-1.9.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d5ff2c858f5f6a42c2a8e751100f237c5e869cbde669a724f2062d4c4ef93551"},
|
||||
{file = "yarl-1.9.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:357495293086c5b6d34ca9616a43d329317feab7917518bc97a08f9e55648455"},
|
||||
{file = "yarl-1.9.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:54525ae423d7b7a8ee81ba189f131054defdb122cde31ff17477951464c1691c"},
|
||||
{file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:801e9264d19643548651b9db361ce3287176671fb0117f96b5ac0ee1c3530d53"},
|
||||
{file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e516dc8baf7b380e6c1c26792610230f37147bb754d6426462ab115a02944385"},
|
||||
{file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:7d5aaac37d19b2904bb9dfe12cdb08c8443e7ba7d2852894ad448d4b8f442863"},
|
||||
{file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:54beabb809ffcacbd9d28ac57b0db46e42a6e341a030293fb3185c409e626b8b"},
|
||||
{file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:bac8d525a8dbc2a1507ec731d2867025d11ceadcb4dd421423a5d42c56818541"},
|
||||
{file = "yarl-1.9.4-cp310-cp310-win32.whl", hash = "sha256:7855426dfbddac81896b6e533ebefc0af2f132d4a47340cee6d22cac7190022d"},
|
||||
{file = "yarl-1.9.4-cp310-cp310-win_amd64.whl", hash = "sha256:848cd2a1df56ddbffeb375535fb62c9d1645dde33ca4d51341378b3f5954429b"},
|
||||
{file = "yarl-1.9.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:35a2b9396879ce32754bd457d31a51ff0a9d426fd9e0e3c33394bf4b9036b099"},
|
||||
{file = "yarl-1.9.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4c7d56b293cc071e82532f70adcbd8b61909eec973ae9d2d1f9b233f3d943f2c"},
|
||||
{file = "yarl-1.9.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d8a1c6c0be645c745a081c192e747c5de06e944a0d21245f4cf7c05e457c36e0"},
|
||||
{file = "yarl-1.9.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4b3c1ffe10069f655ea2d731808e76e0f452fc6c749bea04781daf18e6039525"},
|
||||
{file = "yarl-1.9.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:549d19c84c55d11687ddbd47eeb348a89df9cb30e1993f1b128f4685cd0ebbf8"},
|
||||
{file = "yarl-1.9.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a7409f968456111140c1c95301cadf071bd30a81cbd7ab829169fb9e3d72eae9"},
|
||||
{file = "yarl-1.9.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e23a6d84d9d1738dbc6e38167776107e63307dfc8ad108e580548d1f2c587f42"},
|
||||
{file = "yarl-1.9.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d8b889777de69897406c9fb0b76cdf2fd0f31267861ae7501d93003d55f54fbe"},
|
||||
{file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:03caa9507d3d3c83bca08650678e25364e1843b484f19986a527630ca376ecce"},
|
||||
{file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:4e9035df8d0880b2f1c7f5031f33f69e071dfe72ee9310cfc76f7b605958ceb9"},
|
||||
{file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:c0ec0ed476f77db9fb29bca17f0a8fcc7bc97ad4c6c1d8959c507decb22e8572"},
|
||||
{file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:ee04010f26d5102399bd17f8df8bc38dc7ccd7701dc77f4a68c5b8d733406958"},
|
||||
{file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:49a180c2e0743d5d6e0b4d1a9e5f633c62eca3f8a86ba5dd3c471060e352ca98"},
|
||||
{file = "yarl-1.9.4-cp311-cp311-win32.whl", hash = "sha256:81eb57278deb6098a5b62e88ad8281b2ba09f2f1147c4767522353eaa6260b31"},
|
||||
{file = "yarl-1.9.4-cp311-cp311-win_amd64.whl", hash = "sha256:d1d2532b340b692880261c15aee4dc94dd22ca5d61b9db9a8a361953d36410b1"},
|
||||
{file = "yarl-1.9.4-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0d2454f0aef65ea81037759be5ca9947539667eecebca092733b2eb43c965a81"},
|
||||
{file = "yarl-1.9.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:44d8ffbb9c06e5a7f529f38f53eda23e50d1ed33c6c869e01481d3fafa6b8142"},
|
||||
{file = "yarl-1.9.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:aaaea1e536f98754a6e5c56091baa1b6ce2f2700cc4a00b0d49eca8dea471074"},
|
||||
{file = "yarl-1.9.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3777ce5536d17989c91696db1d459574e9a9bd37660ea7ee4d3344579bb6f129"},
|
||||
{file = "yarl-1.9.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9fc5fc1eeb029757349ad26bbc5880557389a03fa6ada41703db5e068881e5f2"},
|
||||
{file = "yarl-1.9.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ea65804b5dc88dacd4a40279af0cdadcfe74b3e5b4c897aa0d81cf86927fee78"},
|
||||
{file = "yarl-1.9.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa102d6d280a5455ad6a0f9e6d769989638718e938a6a0a2ff3f4a7ff8c62cc4"},
|
||||
{file = "yarl-1.9.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:09efe4615ada057ba2d30df871d2f668af661e971dfeedf0c159927d48bbeff0"},
|
||||
{file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:008d3e808d03ef28542372d01057fd09168419cdc8f848efe2804f894ae03e51"},
|
||||
{file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:6f5cb257bc2ec58f437da2b37a8cd48f666db96d47b8a3115c29f316313654ff"},
|
||||
{file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:992f18e0ea248ee03b5a6e8b3b4738850ae7dbb172cc41c966462801cbf62cf7"},
|
||||
{file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:0e9d124c191d5b881060a9e5060627694c3bdd1fe24c5eecc8d5d7d0eb6faabc"},
|
||||
{file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:3986b6f41ad22988e53d5778f91855dc0399b043fc8946d4f2e68af22ee9ff10"},
|
||||
{file = "yarl-1.9.4-cp312-cp312-win32.whl", hash = "sha256:4b21516d181cd77ebd06ce160ef8cc2a5e9ad35fb1c5930882baff5ac865eee7"},
|
||||
{file = "yarl-1.9.4-cp312-cp312-win_amd64.whl", hash = "sha256:a9bd00dc3bc395a662900f33f74feb3e757429e545d831eef5bb280252631984"},
|
||||
{file = "yarl-1.9.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:63b20738b5aac74e239622d2fe30df4fca4942a86e31bf47a81a0e94c14df94f"},
|
||||
{file = "yarl-1.9.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7d7f7de27b8944f1fee2c26a88b4dabc2409d2fea7a9ed3df79b67277644e17"},
|
||||
{file = "yarl-1.9.4-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c74018551e31269d56fab81a728f683667e7c28c04e807ba08f8c9e3bba32f14"},
|
||||
{file = "yarl-1.9.4-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ca06675212f94e7a610e85ca36948bb8fc023e458dd6c63ef71abfd482481aa5"},
|
||||
{file = "yarl-1.9.4-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5aef935237d60a51a62b86249839b51345f47564208c6ee615ed2a40878dccdd"},
|
||||
{file = "yarl-1.9.4-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2b134fd795e2322b7684155b7855cc99409d10b2e408056db2b93b51a52accc7"},
|
||||
{file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d25039a474c4c72a5ad4b52495056f843a7ff07b632c1b92ea9043a3d9950f6e"},
|
||||
{file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:f7d6b36dd2e029b6bcb8a13cf19664c7b8e19ab3a58e0fefbb5b8461447ed5ec"},
|
||||
{file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:957b4774373cf6f709359e5c8c4a0af9f6d7875db657adb0feaf8d6cb3c3964c"},
|
||||
{file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:d7eeb6d22331e2fd42fce928a81c697c9ee2d51400bd1a28803965883e13cead"},
|
||||
{file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:6a962e04b8f91f8c4e5917e518d17958e3bdee71fd1d8b88cdce74dd0ebbf434"},
|
||||
{file = "yarl-1.9.4-cp37-cp37m-win32.whl", hash = "sha256:f3bc6af6e2b8f92eced34ef6a96ffb248e863af20ef4fde9448cc8c9b858b749"},
|
||||
{file = "yarl-1.9.4-cp37-cp37m-win_amd64.whl", hash = "sha256:ad4d7a90a92e528aadf4965d685c17dacff3df282db1121136c382dc0b6014d2"},
|
||||
{file = "yarl-1.9.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:ec61d826d80fc293ed46c9dd26995921e3a82146feacd952ef0757236fc137be"},
|
||||
{file = "yarl-1.9.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8be9e837ea9113676e5754b43b940b50cce76d9ed7d2461df1af39a8ee674d9f"},
|
||||
{file = "yarl-1.9.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:bef596fdaa8f26e3d66af846bbe77057237cb6e8efff8cd7cc8dff9a62278bbf"},
|
||||
{file = "yarl-1.9.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2d47552b6e52c3319fede1b60b3de120fe83bde9b7bddad11a69fb0af7db32f1"},
|
||||
{file = "yarl-1.9.4-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:84fc30f71689d7fc9168b92788abc977dc8cefa806909565fc2951d02f6b7d57"},
|
||||
{file = "yarl-1.9.4-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4aa9741085f635934f3a2583e16fcf62ba835719a8b2b28fb2917bb0537c1dfa"},
|
||||
{file = "yarl-1.9.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:206a55215e6d05dbc6c98ce598a59e6fbd0c493e2de4ea6cc2f4934d5a18d130"},
|
||||
{file = "yarl-1.9.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07574b007ee20e5c375a8fe4a0789fad26db905f9813be0f9fef5a68080de559"},
|
||||
{file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:5a2e2433eb9344a163aced6a5f6c9222c0786e5a9e9cac2c89f0b28433f56e23"},
|
||||
{file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:6ad6d10ed9b67a382b45f29ea028f92d25bc0bc1daf6c5b801b90b5aa70fb9ec"},
|
||||
{file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:6fe79f998a4052d79e1c30eeb7d6c1c1056ad33300f682465e1b4e9b5a188b78"},
|
||||
{file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:a825ec844298c791fd28ed14ed1bffc56a98d15b8c58a20e0e08c1f5f2bea1be"},
|
||||
{file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8619d6915b3b0b34420cf9b2bb6d81ef59d984cb0fde7544e9ece32b4b3043c3"},
|
||||
{file = "yarl-1.9.4-cp38-cp38-win32.whl", hash = "sha256:686a0c2f85f83463272ddffd4deb5e591c98aac1897d65e92319f729c320eece"},
|
||||
{file = "yarl-1.9.4-cp38-cp38-win_amd64.whl", hash = "sha256:a00862fb23195b6b8322f7d781b0dc1d82cb3bcac346d1e38689370cc1cc398b"},
|
||||
{file = "yarl-1.9.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:604f31d97fa493083ea21bd9b92c419012531c4e17ea6da0f65cacdcf5d0bd27"},
|
||||
{file = "yarl-1.9.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8a854227cf581330ffa2c4824d96e52ee621dd571078a252c25e3a3b3d94a1b1"},
|
||||
{file = "yarl-1.9.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ba6f52cbc7809cd8d74604cce9c14868306ae4aa0282016b641c661f981a6e91"},
|
||||
{file = "yarl-1.9.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a6327976c7c2f4ee6816eff196e25385ccc02cb81427952414a64811037bbc8b"},
|
||||
{file = "yarl-1.9.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8397a3817d7dcdd14bb266283cd1d6fc7264a48c186b986f32e86d86d35fbac5"},
|
||||
{file = "yarl-1.9.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e0381b4ce23ff92f8170080c97678040fc5b08da85e9e292292aba67fdac6c34"},
|
||||
{file = "yarl-1.9.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:23d32a2594cb5d565d358a92e151315d1b2268bc10f4610d098f96b147370136"},
|
||||
{file = "yarl-1.9.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ddb2a5c08a4eaaba605340fdee8fc08e406c56617566d9643ad8bf6852778fc7"},
|
||||
{file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:26a1dc6285e03f3cc9e839a2da83bcbf31dcb0d004c72d0730e755b33466c30e"},
|
||||
{file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:18580f672e44ce1238b82f7fb87d727c4a131f3a9d33a5e0e82b793362bf18b4"},
|
||||
{file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:29e0f83f37610f173eb7e7b5562dd71467993495e568e708d99e9d1944f561ec"},
|
||||
{file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:1f23e4fe1e8794f74b6027d7cf19dc25f8b63af1483d91d595d4a07eca1fb26c"},
|
||||
{file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:db8e58b9d79200c76956cefd14d5c90af54416ff5353c5bfd7cbe58818e26ef0"},
|
||||
{file = "yarl-1.9.4-cp39-cp39-win32.whl", hash = "sha256:c7224cab95645c7ab53791022ae77a4509472613e839dab722a72abe5a684575"},
|
||||
{file = "yarl-1.9.4-cp39-cp39-win_amd64.whl", hash = "sha256:824d6c50492add5da9374875ce72db7a0733b29c2394890aef23d533106e2b15"},
|
||||
{file = "yarl-1.9.4-py3-none-any.whl", hash = "sha256:928cecb0ef9d5a7946eb6ff58417ad2fe9375762382f1bf5c55e61645f2c43ad"},
|
||||
{file = "yarl-1.9.4.tar.gz", hash = "sha256:566db86717cf8080b99b58b083b773a908ae40f06681e87e589a976faf8246bf"},
|
||||
{file = "yarl-1.15.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e4ee8b8639070ff246ad3649294336b06db37a94bdea0d09ea491603e0be73b8"},
|
||||
{file = "yarl-1.15.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a7cf963a357c5f00cb55b1955df8bbe68d2f2f65de065160a1c26b85a1e44172"},
|
||||
{file = "yarl-1.15.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:43ebdcc120e2ca679dba01a779333a8ea76b50547b55e812b8b92818d604662c"},
|
||||
{file = "yarl-1.15.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3433da95b51a75692dcf6cc8117a31410447c75a9a8187888f02ad45c0a86c50"},
|
||||
{file = "yarl-1.15.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:38d0124fa992dbacd0c48b1b755d3ee0a9f924f427f95b0ef376556a24debf01"},
|
||||
{file = "yarl-1.15.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ded1b1803151dd0f20a8945508786d57c2f97a50289b16f2629f85433e546d47"},
|
||||
{file = "yarl-1.15.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ace4cad790f3bf872c082366c9edd7f8f8f77afe3992b134cfc810332206884f"},
|
||||
{file = "yarl-1.15.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c77494a2f2282d9bbbbcab7c227a4d1b4bb829875c96251f66fb5f3bae4fb053"},
|
||||
{file = "yarl-1.15.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:b7f227ca6db5a9fda0a2b935a2ea34a7267589ffc63c8045f0e4edb8d8dcf956"},
|
||||
{file = "yarl-1.15.2-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:31561a5b4d8dbef1559b3600b045607cf804bae040f64b5f5bca77da38084a8a"},
|
||||
{file = "yarl-1.15.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:3e52474256a7db9dcf3c5f4ca0b300fdea6c21cca0148c8891d03a025649d935"},
|
||||
{file = "yarl-1.15.2-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:0e1af74a9529a1137c67c887ed9cde62cff53aa4d84a3adbec329f9ec47a3936"},
|
||||
{file = "yarl-1.15.2-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:15c87339490100c63472a76d87fe7097a0835c705eb5ae79fd96e343473629ed"},
|
||||
{file = "yarl-1.15.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:74abb8709ea54cc483c4fb57fb17bb66f8e0f04438cff6ded322074dbd17c7ec"},
|
||||
{file = "yarl-1.15.2-cp310-cp310-win32.whl", hash = "sha256:ffd591e22b22f9cb48e472529db6a47203c41c2c5911ff0a52e85723196c0d75"},
|
||||
{file = "yarl-1.15.2-cp310-cp310-win_amd64.whl", hash = "sha256:1695497bb2a02a6de60064c9f077a4ae9c25c73624e0d43e3aa9d16d983073c2"},
|
||||
{file = "yarl-1.15.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9fcda20b2de7042cc35cf911702fa3d8311bd40055a14446c1e62403684afdc5"},
|
||||
{file = "yarl-1.15.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0545de8c688fbbf3088f9e8b801157923be4bf8e7b03e97c2ecd4dfa39e48e0e"},
|
||||
{file = "yarl-1.15.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:fbda058a9a68bec347962595f50546a8a4a34fd7b0654a7b9697917dc2bf810d"},
|
||||
{file = "yarl-1.15.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d1ac2bc069f4a458634c26b101c2341b18da85cb96afe0015990507efec2e417"},
|
||||
{file = "yarl-1.15.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cd126498171f752dd85737ab1544329a4520c53eed3997f9b08aefbafb1cc53b"},
|
||||
{file = "yarl-1.15.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3db817b4e95eb05c362e3b45dafe7144b18603e1211f4a5b36eb9522ecc62bcf"},
|
||||
{file = "yarl-1.15.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:076b1ed2ac819933895b1a000904f62d615fe4533a5cf3e052ff9a1da560575c"},
|
||||
{file = "yarl-1.15.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f8cfd847e6b9ecf9f2f2531c8427035f291ec286c0a4944b0a9fce58c6446046"},
|
||||
{file = "yarl-1.15.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:32b66be100ac5739065496c74c4b7f3015cef792c3174982809274d7e51b3e04"},
|
||||
{file = "yarl-1.15.2-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:34a2d76a1984cac04ff8b1bfc939ec9dc0914821264d4a9c8fd0ed6aa8d4cfd2"},
|
||||
{file = "yarl-1.15.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:0afad2cd484908f472c8fe2e8ef499facee54a0a6978be0e0cff67b1254fd747"},
|
||||
{file = "yarl-1.15.2-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:c68e820879ff39992c7f148113b46efcd6ec765a4865581f2902b3c43a5f4bbb"},
|
||||
{file = "yarl-1.15.2-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:98f68df80ec6ca3015186b2677c208c096d646ef37bbf8b49764ab4a38183931"},
|
||||
{file = "yarl-1.15.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3c56ec1eacd0a5d35b8a29f468659c47f4fe61b2cab948ca756c39b7617f0aa5"},
|
||||
{file = "yarl-1.15.2-cp311-cp311-win32.whl", hash = "sha256:eedc3f247ee7b3808ea07205f3e7d7879bc19ad3e6222195cd5fbf9988853e4d"},
|
||||
{file = "yarl-1.15.2-cp311-cp311-win_amd64.whl", hash = "sha256:0ccaa1bc98751fbfcf53dc8dfdb90d96e98838010fc254180dd6707a6e8bb179"},
|
||||
{file = "yarl-1.15.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:82d5161e8cb8f36ec778fd7ac4d740415d84030f5b9ef8fe4da54784a1f46c94"},
|
||||
{file = "yarl-1.15.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:fa2bea05ff0a8fb4d8124498e00e02398f06d23cdadd0fe027d84a3f7afde31e"},
|
||||
{file = "yarl-1.15.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:99e12d2bf587b44deb74e0d6170fec37adb489964dbca656ec41a7cd8f2ff178"},
|
||||
{file = "yarl-1.15.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:243fbbbf003754fe41b5bdf10ce1e7f80bcc70732b5b54222c124d6b4c2ab31c"},
|
||||
{file = "yarl-1.15.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:856b7f1a7b98a8c31823285786bd566cf06226ac4f38b3ef462f593c608a9bd6"},
|
||||
{file = "yarl-1.15.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:553dad9af802a9ad1a6525e7528152a015b85fb8dbf764ebfc755c695f488367"},
|
||||
{file = "yarl-1.15.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:30c3ff305f6e06650a761c4393666f77384f1cc6c5c0251965d6bfa5fbc88f7f"},
|
||||
{file = "yarl-1.15.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:353665775be69bbfc6d54c8d134bfc533e332149faeddd631b0bc79df0897f46"},
|
||||
{file = "yarl-1.15.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f4fe99ce44128c71233d0d72152db31ca119711dfc5f2c82385ad611d8d7f897"},
|
||||
{file = "yarl-1.15.2-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:9c1e3ff4b89cdd2e1a24c214f141e848b9e0451f08d7d4963cb4108d4d798f1f"},
|
||||
{file = "yarl-1.15.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:711bdfae4e699a6d4f371137cbe9e740dc958530cb920eb6f43ff9551e17cfbc"},
|
||||
{file = "yarl-1.15.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:4388c72174868884f76affcdd3656544c426407e0043c89b684d22fb265e04a5"},
|
||||
{file = "yarl-1.15.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:f0e1844ad47c7bd5d6fa784f1d4accc5f4168b48999303a868fe0f8597bde715"},
|
||||
{file = "yarl-1.15.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a5cafb02cf097a82d74403f7e0b6b9df3ffbfe8edf9415ea816314711764a27b"},
|
||||
{file = "yarl-1.15.2-cp312-cp312-win32.whl", hash = "sha256:156ececdf636143f508770bf8a3a0498de64da5abd890c7dbb42ca9e3b6c05b8"},
|
||||
{file = "yarl-1.15.2-cp312-cp312-win_amd64.whl", hash = "sha256:435aca062444a7f0c884861d2e3ea79883bd1cd19d0a381928b69ae1b85bc51d"},
|
||||
{file = "yarl-1.15.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:416f2e3beaeae81e2f7a45dc711258be5bdc79c940a9a270b266c0bec038fb84"},
|
||||
{file = "yarl-1.15.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:173563f3696124372831007e3d4b9821746964a95968628f7075d9231ac6bb33"},
|
||||
{file = "yarl-1.15.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:9ce2e0f6123a60bd1a7f5ae3b2c49b240c12c132847f17aa990b841a417598a2"},
|
||||
{file = "yarl-1.15.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eaea112aed589131f73d50d570a6864728bd7c0c66ef6c9154ed7b59f24da611"},
|
||||
{file = "yarl-1.15.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e4ca3b9f370f218cc2a0309542cab8d0acdfd66667e7c37d04d617012485f904"},
|
||||
{file = "yarl-1.15.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:23ec1d3c31882b2a8a69c801ef58ebf7bae2553211ebbddf04235be275a38548"},
|
||||
{file = "yarl-1.15.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75119badf45f7183e10e348edff5a76a94dc19ba9287d94001ff05e81475967b"},
|
||||
{file = "yarl-1.15.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:78e6fdc976ec966b99e4daa3812fac0274cc28cd2b24b0d92462e2e5ef90d368"},
|
||||
{file = "yarl-1.15.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:8657d3f37f781d987037f9cc20bbc8b40425fa14380c87da0cb8dfce7c92d0fb"},
|
||||
{file = "yarl-1.15.2-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:93bed8a8084544c6efe8856c362af08a23e959340c87a95687fdbe9c9f280c8b"},
|
||||
{file = "yarl-1.15.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:69d5856d526802cbda768d3e6246cd0d77450fa2a4bc2ea0ea14f0d972c2894b"},
|
||||
{file = "yarl-1.15.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:ccad2800dfdff34392448c4bf834be124f10a5bc102f254521d931c1c53c455a"},
|
||||
{file = "yarl-1.15.2-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:a880372e2e5dbb9258a4e8ff43f13888039abb9dd6d515f28611c54361bc5644"},
|
||||
{file = "yarl-1.15.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:c998d0558805860503bc3a595994895ca0f7835e00668dadc673bbf7f5fbfcbe"},
|
||||
{file = "yarl-1.15.2-cp313-cp313-win32.whl", hash = "sha256:533a28754e7f7439f217550a497bb026c54072dbe16402b183fdbca2431935a9"},
|
||||
{file = "yarl-1.15.2-cp313-cp313-win_amd64.whl", hash = "sha256:5838f2b79dc8f96fdc44077c9e4e2e33d7089b10788464609df788eb97d03aad"},
|
||||
{file = "yarl-1.15.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:fbbb63bed5fcd70cd3dd23a087cd78e4675fb5a2963b8af53f945cbbca79ae16"},
|
||||
{file = "yarl-1.15.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:e2e93b88ecc8f74074012e18d679fb2e9c746f2a56f79cd5e2b1afcf2a8a786b"},
|
||||
{file = "yarl-1.15.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:af8ff8d7dc07ce873f643de6dfbcd45dc3db2c87462e5c387267197f59e6d776"},
|
||||
{file = "yarl-1.15.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:66f629632220a4e7858b58e4857927dd01a850a4cef2fb4044c8662787165cf7"},
|
||||
{file = "yarl-1.15.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:833547179c31f9bec39b49601d282d6f0ea1633620701288934c5f66d88c3e50"},
|
||||
{file = "yarl-1.15.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2aa738e0282be54eede1e3f36b81f1e46aee7ec7602aa563e81e0e8d7b67963f"},
|
||||
{file = "yarl-1.15.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9a13a07532e8e1c4a5a3afff0ca4553da23409fad65def1b71186fb867eeae8d"},
|
||||
{file = "yarl-1.15.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c45817e3e6972109d1a2c65091504a537e257bc3c885b4e78a95baa96df6a3f8"},
|
||||
{file = "yarl-1.15.2-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:670eb11325ed3a6209339974b276811867defe52f4188fe18dc49855774fa9cf"},
|
||||
{file = "yarl-1.15.2-cp38-cp38-musllinux_1_2_armv7l.whl", hash = "sha256:d417a4f6943112fae3924bae2af7112562285848d9bcee737fc4ff7cbd450e6c"},
|
||||
{file = "yarl-1.15.2-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:bc8936d06cd53fddd4892677d65e98af514c8d78c79864f418bbf78a4a2edde4"},
|
||||
{file = "yarl-1.15.2-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:954dde77c404084c2544e572f342aef384240b3e434e06cecc71597e95fd1ce7"},
|
||||
{file = "yarl-1.15.2-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:5bc0df728e4def5e15a754521e8882ba5a5121bd6b5a3a0ff7efda5d6558ab3d"},
|
||||
{file = "yarl-1.15.2-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:b71862a652f50babab4a43a487f157d26b464b1dedbcc0afda02fd64f3809d04"},
|
||||
{file = "yarl-1.15.2-cp38-cp38-win32.whl", hash = "sha256:63eab904f8630aed5a68f2d0aeab565dcfc595dc1bf0b91b71d9ddd43dea3aea"},
|
||||
{file = "yarl-1.15.2-cp38-cp38-win_amd64.whl", hash = "sha256:2cf441c4b6e538ba0d2591574f95d3fdd33f1efafa864faa077d9636ecc0c4e9"},
|
||||
{file = "yarl-1.15.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:a32d58f4b521bb98b2c0aa9da407f8bd57ca81f34362bcb090e4a79e9924fefc"},
|
||||
{file = "yarl-1.15.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:766dcc00b943c089349d4060b935c76281f6be225e39994c2ccec3a2a36ad627"},
|
||||
{file = "yarl-1.15.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:bed1b5dbf90bad3bfc19439258c97873eab453c71d8b6869c136346acfe497e7"},
|
||||
{file = "yarl-1.15.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ed20a4bdc635f36cb19e630bfc644181dd075839b6fc84cac51c0f381ac472e2"},
|
||||
{file = "yarl-1.15.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d538df442c0d9665664ab6dd5fccd0110fa3b364914f9c85b3ef9b7b2e157980"},
|
||||
{file = "yarl-1.15.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c6cf1d92edf936ceedc7afa61b07e9d78a27b15244aa46bbcd534c7458ee1b"},
|
||||
{file = "yarl-1.15.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce44217ad99ffad8027d2fde0269ae368c86db66ea0571c62a000798d69401fb"},
|
||||
{file = "yarl-1.15.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b47a6000a7e833ebfe5886b56a31cb2ff12120b1efd4578a6fcc38df16cc77bd"},
|
||||
{file = "yarl-1.15.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:e52f77a0cd246086afde8815039f3e16f8d2be51786c0a39b57104c563c5cbb0"},
|
||||
{file = "yarl-1.15.2-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:f9ca0e6ce7774dc7830dc0cc4bb6b3eec769db667f230e7c770a628c1aa5681b"},
|
||||
{file = "yarl-1.15.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:136f9db0f53c0206db38b8cd0c985c78ded5fd596c9a86ce5c0b92afb91c3a19"},
|
||||
{file = "yarl-1.15.2-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:173866d9f7409c0fb514cf6e78952e65816600cb888c68b37b41147349fe0057"},
|
||||
{file = "yarl-1.15.2-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:6e840553c9c494a35e449a987ca2c4f8372668ee954a03a9a9685075228e5036"},
|
||||
{file = "yarl-1.15.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:458c0c65802d816a6b955cf3603186de79e8fdb46d4f19abaec4ef0a906f50a7"},
|
||||
{file = "yarl-1.15.2-cp39-cp39-win32.whl", hash = "sha256:5b48388ded01f6f2429a8c55012bdbd1c2a0c3735b3e73e221649e524c34a58d"},
|
||||
{file = "yarl-1.15.2-cp39-cp39-win_amd64.whl", hash = "sha256:81dadafb3aa124f86dc267a2168f71bbd2bfb163663661ab0038f6e4b8edb810"},
|
||||
{file = "yarl-1.15.2-py3-none-any.whl", hash = "sha256:0d3105efab7c5c091609abacad33afff33bdff0035bece164c98bcf5a85ef90a"},
|
||||
{file = "yarl-1.15.2.tar.gz", hash = "sha256:a39c36f4218a5bb668b4f06874d676d35a035ee668e6e7e3538835c703634b84"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
idna = ">=2.0"
|
||||
multidict = ">=4.0"
|
||||
propcache = ">=0.2.0"
|
||||
|
||||
[metadata]
|
||||
lock-version = "2.0"
|
||||
python-versions = ">=3.10,<3.13"
|
||||
content-hash = "81a35e932aea2fecab78f82f12cc8ab435cfcda5e1fe20acb42cb95542b4608f"
|
||||
lock-version = "2.1"
|
||||
python-versions = ">=3.10,<3.14"
|
||||
content-hash = "9a331b42c52134230384c1a7348c2856903d82d6007e06cd75eed13842aa21ea"
|
||||
|
||||
@@ -14,9 +14,9 @@ include = [
|
||||
]
|
||||
|
||||
[tool.poetry.dependencies]
|
||||
python = ">=3.10,<3.13"
|
||||
python = ">=3.10,<3.14"
|
||||
|
||||
aiohttp = "^3.9.5"
|
||||
aiohttp = "^3.10.11"
|
||||
aiohttp-jinja2 = "^1.5.1"
|
||||
aiohttp-cors = "^0.7.0"
|
||||
watchdog = "^4"
|
||||
|
||||
Vendored
+2
-66
@@ -1,67 +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
|
||||
[Service]
|
||||
Type=simple
|
||||
User=root
|
||||
Restart=always
|
||||
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
-66
@@ -1,67 +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
|
||||
[Service]
|
||||
Type=simple
|
||||
User=root
|
||||
Restart=always
|
||||
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
|
||||
+3
-1
@@ -1,10 +1,12 @@
|
||||
[Unit]
|
||||
Description=SteamDeck Plugin Loader
|
||||
After=network.target
|
||||
[Service]
|
||||
Type=simple
|
||||
User=root
|
||||
Restart=always
|
||||
TimeoutStopSec=45
|
||||
KillMode=process
|
||||
TimeoutStopSec=15
|
||||
ExecStart=${HOMEBREW_FOLDER}/services/PluginLoader
|
||||
WorkingDirectory=${HOMEBREW_FOLDER}/services
|
||||
Environment=UNPRIVILEGED_PATH=${HOMEBREW_FOLDER}
|
||||
|
||||
Vendored
+3
-1
@@ -1,10 +1,12 @@
|
||||
[Unit]
|
||||
Description=SteamDeck Plugin Loader
|
||||
After=network.target
|
||||
[Service]
|
||||
Type=simple
|
||||
User=root
|
||||
Restart=always
|
||||
TimeoutStopSec=45
|
||||
KillMode=process
|
||||
TimeoutStopSec=15
|
||||
ExecStart=${HOMEBREW_FOLDER}/services/PluginLoader
|
||||
WorkingDirectory=${HOMEBREW_FOLDER}/services
|
||||
Environment=UNPRIVILEGED_PATH=${HOMEBREW_FOLDER}
|
||||
|
||||
+24
-159
@@ -1,162 +1,27 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg id="Layer_1" xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 176.36 38">
|
||||
<!-- Generator: Adobe Illustrator 29.2.1, SVG Export Plug-In . SVG Version: 2.1.0 Build 116) -->
|
||||
<defs>
|
||||
<style>
|
||||
.st0 {
|
||||
fill: #3fafa8;
|
||||
}
|
||||
|
||||
<svg
|
||||
width="81.700577mm"
|
||||
height="24.334814mm"
|
||||
viewBox="0 0 81.700577 24.334814"
|
||||
version="1.1"
|
||||
id="svg5"
|
||||
inkscape:version="1.2.2 (b0a8486541, 2022-12-01)"
|
||||
sodipodi:docname="download.svg"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<sodipodi:namedview
|
||||
id="namedview7"
|
||||
pagecolor="#505050"
|
||||
bordercolor="#ffffff"
|
||||
borderopacity="1"
|
||||
inkscape:showpageshadow="0"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pagecheckerboard="1"
|
||||
inkscape:deskcolor="#505050"
|
||||
inkscape:document-units="mm"
|
||||
showgrid="false"
|
||||
inkscape:zoom="3.659624"
|
||||
inkscape:cx="115.44902"
|
||||
inkscape:cy="59.295709"
|
||||
inkscape:window-width="1827"
|
||||
inkscape:window-height="1233"
|
||||
inkscape:window-x="69"
|
||||
inkscape:window-y="38"
|
||||
inkscape:window-maximized="0"
|
||||
inkscape:current-layer="layer1" />
|
||||
<defs
|
||||
id="defs2">
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
id="linearGradient4494">
|
||||
<stop
|
||||
style="stop-color:#009fff;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop4490" />
|
||||
<stop
|
||||
style="stop-color:#ff1965;stop-opacity:1;"
|
||||
offset="0.79417855"
|
||||
id="stop4498" />
|
||||
<stop
|
||||
style="stop-color:#b9b500;stop-opacity:1;"
|
||||
offset="1"
|
||||
id="stop4492" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient4494"
|
||||
id="linearGradient4496"
|
||||
x1="49.131042"
|
||||
y1="118.6573"
|
||||
x2="150.29259"
|
||||
y2="138.74957"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
spreadMethod="pad"
|
||||
gradientTransform="matrix(1.0500324,0,0,1,-1.6155884,24.621921)" />
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient4494"
|
||||
id="linearGradient13802"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="matrix(1.0500324,0,0,1,-1.6155884,24.621921)"
|
||||
x1="49.131042"
|
||||
y1="118.6573"
|
||||
x2="150.29259"
|
||||
y2="138.74957"
|
||||
spreadMethod="pad" />
|
||||
.st1 {
|
||||
fill: #fff;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(-64.149712,-136.3326)">
|
||||
<rect
|
||||
style="mix-blend-mode:normal;fill:url(#linearGradient13802);fill-opacity:1;stroke:none;stroke-width:0.271121"
|
||||
id="rect111"
|
||||
width="81.700577"
|
||||
height="24.334814"
|
||||
x="64.149712"
|
||||
y="136.3326"
|
||||
ry="8.1781616" />
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-size:3.175px;fill:#000000;stroke:none;stroke-width:0.264583"
|
||||
x="66.364288"
|
||||
y="124.84658"
|
||||
id="text10382"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan10380"
|
||||
style="stroke-width:0.264583"
|
||||
x="66.364288"
|
||||
y="124.84658" /></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:15.1694px;font-family:sans-serif;-inkscape-font-specification:sans-serif;white-space:pre;inline-size:82.6483;display:inline;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.264583"
|
||||
x="67.732498"
|
||||
y="126.05277"
|
||||
id="text10440"
|
||||
transform="translate(1.088576,28.135753)"><tspan
|
||||
x="67.732498"
|
||||
y="126.05277"
|
||||
id="tspan13872">Download</tspan></text>
|
||||
<rect
|
||||
style="mix-blend-mode:normal;fill:url(#linearGradient4496);fill-opacity:1;stroke:none;stroke-width:0.271121"
|
||||
id="rect13792"
|
||||
width="81.700577"
|
||||
height="24.334814"
|
||||
x="64.149712"
|
||||
y="136.3326"
|
||||
ry="8.1781616" />
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-size:3.175px;fill:#000000;stroke:none;stroke-width:0.264583"
|
||||
x="66.364288"
|
||||
y="124.84658"
|
||||
id="text13796"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan13794"
|
||||
style="stroke-width:0.264583"
|
||||
x="66.364288"
|
||||
y="124.84658" /></text>
|
||||
<g
|
||||
aria-label="Download"
|
||||
transform="translate(1.088576,28.135753)"
|
||||
id="text13800"
|
||||
style="font-size:15.1694px;-inkscape-font-specification:sans-serif;white-space:pre;inline-size:82.6483;display:inline;fill:#ffffff;stroke-width:0.264583">
|
||||
<path
|
||||
d="m 77.880751,120.53111 q 0,2.74566 -1.501771,4.14125 -1.486601,1.38041 -4.156416,1.38041 h -3.01871 v -10.83095 h 3.337268 q 1.638295,0 2.836678,0.60678 1.198382,0.60677 1.850666,1.78999 0.652285,1.16804 0.652285,2.91252 z m -1.441093,0.0455 q 0,-2.16923 -1.077028,-3.17041 -1.061858,-1.01635 -3.01871,-1.01635 H 70.5691 v 8.49487 h 1.471432 q 4.399126,0 4.399126,-4.30811 z"
|
||||
id="path13828" />
|
||||
<path
|
||||
d="m 87.164417,121.9722 q 0,2.01753 -1.03152,3.1249 -1.01635,1.10737 -2.760831,1.10737 -1.077027,0 -1.926513,-0.48542 -0.834317,-0.50059 -1.319738,-1.4411 -0.485421,-0.95567 -0.485421,-2.30575 0,-2.01753 1.01635,-3.10972 1.01635,-1.0922 2.760831,-1.0922 1.107366,0 1.941683,0.50059 0.849486,0.48542 1.319738,1.42592 0.485421,0.92534 0.485421,2.27541 z m -6.143608,0 q 0,1.4411 0.561268,2.29058 0.576437,0.83432 1.820328,0.83432 1.228722,0 1.805159,-0.83432 0.576437,-0.84948 0.576437,-2.29058 0,-1.44109 -0.576437,-2.26024 -0.576437,-0.81914 -1.820328,-0.81914 -1.243891,0 -1.805159,0.81914 -0.561268,0.81915 -0.561268,2.26024 z"
|
||||
id="path13830" />
|
||||
<path
|
||||
d="m 94.218174,121.45644 q -0.197202,-0.62194 -0.348896,-1.21355 -0.136525,-0.60677 -0.212372,-0.9405 h -0.06068 q -0.06068,0.33373 -0.197203,0.9405 -0.136524,0.59161 -0.348896,1.22872 l -1.456262,4.56599 h -1.51694 l -2.229902,-8.1308 h 1.380415 l 1.122536,4.33845 q 0.166863,0.65229 0.318557,1.31974 0.151694,0.66745 0.212372,1.10737 h 0.06068 q 0.06068,-0.25788 0.136525,-0.63712 0.09102,-0.37923 0.197202,-0.78881 0.106186,-0.42474 0.212372,-0.75847 l 1.441093,-4.58116 h 1.456262 l 1.395585,4.58116 q 0.166864,0.51576 0.318558,1.12254 0.166863,0.60678 0.227541,1.04669 h 0.06068 q 0.04551,-0.37924 0.197202,-1.04669 0.166864,-0.66745 0.348897,-1.36525 l 1.137705,-4.33845 h 1.365246 l -2.260241,8.1308 h -1.562448 z"
|
||||
id="path13832" />
|
||||
<path
|
||||
d="m 104.8064,117.77028 q 1.45627,0 2.19957,0.71296 0.7433,0.69779 0.7433,2.27541 v 5.29412 h -1.31974 v -5.2031 q 0,-1.95685 -1.82033,-1.95685 -1.35007,0 -1.86583,0.75847 -0.51576,0.75847 -0.51576,2.18439 v 4.21709 h -1.33491 v -8.1308 h 1.07703 l 0.1972,1.10737 h 0.0759 q 0.3944,-0.63711 1.09219,-0.9405 0.69779,-0.31856 1.47143,-0.31856 z"
|
||||
id="path13834" />
|
||||
<path
|
||||
d="m 111.6023,126.05277 h -1.33491 v -11.52874 h 1.33491 z"
|
||||
id="path13836" />
|
||||
<path
|
||||
d="m 121.25003,121.9722 q 0,2.01753 -1.03152,3.1249 -1.01635,1.10737 -2.76084,1.10737 -1.07702,0 -1.92651,-0.48542 -0.83432,-0.50059 -1.31974,-1.4411 -0.48542,-0.95567 -0.48542,-2.30575 0,-2.01753 1.01635,-3.10972 1.01635,-1.0922 2.76083,-1.0922 1.10737,0 1.94169,0.50059 0.84948,0.48542 1.31973,1.42592 0.48543,0.92534 0.48543,2.27541 z m -6.14361,0 q 0,1.4411 0.56127,2.29058 0.57643,0.83432 1.82032,0.83432 1.22873,0 1.80516,-0.83432 0.57644,-0.84948 0.57644,-2.29058 0,-1.44109 -0.57644,-2.26024 -0.57643,-0.81914 -1.82033,-0.81914 -1.24389,0 -1.80515,0.81914 -0.56127,0.81915 -0.56127,2.26024 z"
|
||||
id="path13838" />
|
||||
<path
|
||||
d="m 126.43796,117.78545 q 1.4866,0 2.19956,0.65228 0.71296,0.65229 0.71296,2.07821 v 5.53683 h -0.97084 l -0.25788,-1.15287 h -0.0607 q -0.53093,0.66745 -1.12253,0.98601 -0.57644,0.31856 -1.60796,0.31856 -1.10737,0 -1.8355,-0.57644 -0.72813,-0.59161 -0.72813,-1.8355 0,-1.21355 0.95567,-1.86583 0.95567,-0.66746 2.94287,-0.72814 l 1.38041,-0.0455 v -0.48542 q 0,-1.01635 -0.43991,-1.41076 -0.43991,-0.3944 -1.24389,-0.3944 -0.63712,0 -1.21355,0.1972 -0.57644,0.18203 -1.07703,0.42474 l -0.40957,-1.00118 q 0.53092,-0.28822 1.25906,-0.48542 0.72813,-0.21237 1.51694,-0.21237 z m 0.3944,4.33845 q -1.51694,0.0607 -2.10855,0.48542 -0.57643,0.42474 -0.57643,1.19838 0,0.68262 0.40957,1.00118 0.42474,0.31856 1.07703,0.31856 1.03152,0 1.71414,-0.56127 0.68262,-0.57644 0.68262,-1.75965 v -0.72813 z"
|
||||
id="path13840" />
|
||||
<path
|
||||
d="m 134.7508,126.20447 q -1.51694,0 -2.42711,-1.04669 -0.91016,-1.06186 -0.91016,-3.15524 0,-2.09337 0.91016,-3.15523 0.92534,-1.07703 2.44228,-1.07703 0.9405,0 1.53211,0.3489 0.60677,0.34889 0.98601,0.84948 h 0.091 q -0.0152,-0.1972 -0.0607,-0.57643 -0.0303,-0.39441 -0.0303,-0.62195 v -3.24625 h 1.3349 v 11.52874 h -1.07702 l -0.19721,-1.09219 h -0.0607 q -0.36407,0.51576 -0.97084,0.87982 -0.60678,0.36407 -1.56245,0.36407 z m 0.21237,-1.10737 q 1.2894,0 1.80516,-0.69779 0.53093,-0.71296 0.53093,-2.13889 v -0.24271 q 0,-1.51694 -0.50059,-2.32092 -0.50059,-0.81914 -1.85067,-0.81914 -1.07703,0 -1.62313,0.86465 -0.53093,0.84949 -0.53093,2.29058 0,1.45626 0.53093,2.26024 0.5461,0.80398 1.6383,0.80398 z"
|
||||
id="path13842" />
|
||||
</g>
|
||||
<rect class="st0" x="0" y="0" width="176.36" height="38" rx="19" ry="19"/>
|
||||
<g>
|
||||
<path class="st1" d="M59.4,26.66v-15.77h4.92c2.76,0,4.85.63,6.25,1.9,1.4,1.27,2.11,3.2,2.11,5.79s-.76,4.47-2.29,5.92c-1.53,1.45-3.58,2.17-6.17,2.17h-4.82ZM62.01,13.13v11.28h2.09c1.83,0,3.25-.5,4.28-1.49,1.03-.99,1.54-2.43,1.54-4.31s-.49-3.21-1.46-4.12c-.98-.91-2.41-1.37-4.31-1.37h-2.13Z"/>
|
||||
<path class="st1" d="M80.12,26.92c-1.78,0-3.2-.52-4.25-1.57-1.05-1.05-1.57-2.46-1.57-4.23,0-1.8.56-3.24,1.67-4.34s2.54-1.64,4.31-1.64,3.17.54,4.2,1.62c1.03,1.08,1.54,2.47,1.54,4.18s-.54,3.18-1.62,4.31c-1.08,1.13-2.51,1.69-4.28,1.69ZM80.22,24.84c1.01,0,1.8-.35,2.35-1.04.56-.69.84-1.63.84-2.81s-.28-2.05-.84-2.74c-.56-.69-1.34-1.04-2.35-1.04s-1.81.36-2.41,1.07c-.6.71-.9,1.64-.9,2.78s.3,2.11.89,2.78,1.4,1.01,2.42,1.01Z"/>
|
||||
<path class="st1" d="M103.61,15.4l-3.32,11.26h-2.67l-2.02-7.33c-.05-.19-.09-.34-.11-.45-.02-.11-.05-.25-.08-.43h-.05c-.03.18-.06.32-.09.43s-.07.25-.12.41l-2.19,7.36h-2.64l-3.31-11.26h2.6l2.01,7.71c.04.13.07.27.09.41.02.14.05.31.08.49h.07c.04-.19.07-.36.1-.5.03-.14.07-.29.12-.43l2.29-7.68h2.43l2.05,7.72c.02.09.05.21.08.36.03.15.07.33.1.54h.08c.04-.21.07-.36.09-.47.02-.11.06-.25.1-.43l1.95-7.72h2.39Z"/>
|
||||
<path class="st1" d="M115.36,26.66h-2.55v-6.59c0-.93-.19-1.64-.56-2.13-.37-.49-.93-.73-1.66-.73-.8,0-1.45.29-1.95.86-.5.57-.75,1.29-.75,2.17v6.42h-2.56v-11.26h2.56v1.53h.04c.4-.57.91-1.01,1.55-1.33.63-.31,1.32-.47,2.06-.47,1.25,0,2.2.4,2.85,1.19.65.79.98,1.92.98,3.4v6.94Z"/>
|
||||
<path class="st1" d="M118.22,26.66V9.98h2.56v16.67h-2.56Z"/>
|
||||
<path class="st1" d="M128.95,26.92c-1.78,0-3.2-.52-4.25-1.57s-1.57-2.46-1.57-4.23c0-1.8.56-3.24,1.67-4.34,1.11-1.1,2.54-1.64,4.31-1.64s3.17.54,4.2,1.62c1.03,1.08,1.54,2.47,1.54,4.18s-.54,3.18-1.62,4.31c-1.08,1.13-2.51,1.69-4.28,1.69ZM129.05,24.84c1.01,0,1.8-.35,2.35-1.04.56-.69.84-1.63.84-2.81s-.28-2.05-.84-2.74c-.56-.69-1.34-1.04-2.35-1.04s-1.81.36-2.41,1.07c-.6.71-.9,1.64-.9,2.78s.3,2.11.89,2.78c.59.67,1.4,1.01,2.42,1.01Z"/>
|
||||
<path class="st1" d="M144.71,26.66h-2.48v-1.4h-.04c-.4.54-.88.96-1.45,1.24-.57.28-1.21.42-1.91.42-1.04,0-1.89-.3-2.56-.89-.66-.59-1-1.37-1-2.33,0-1.03.33-1.86,1-2.49.66-.63,1.62-1.01,2.85-1.15l3.12-.35v-.54c0-.7-.19-1.22-.58-1.57s-.9-.52-1.53-.52-1.15.14-1.57.42c-.43.28-.78.68-1.06,1.2l-1.91-.98c.38-.76.98-1.38,1.8-1.86s1.8-.73,2.93-.73c1.42,0,2.51.37,3.26,1.12.75.74,1.13,1.82,1.13,3.24v7.17ZM142.25,22.08v-.62l-2.72.3c-.62.07-1.08.24-1.36.52-.29.28-.43.65-.43,1.12s.16.86.49,1.16c.33.3.75.45,1.27.45.82,0,1.49-.28,1.99-.83s.76-1.25.76-2.09Z"/>
|
||||
<path class="st1" d="M155.4,25.1c-.41.6-.93,1.06-1.55,1.36-.62.31-1.33.46-2.12.46-1.51,0-2.7-.5-3.57-1.5-.87-1-1.3-2.38-1.3-4.13,0-1.89.49-3.39,1.46-4.5.97-1.11,2.27-1.66,3.89-1.66.7,0,1.34.14,1.91.42.57.28,1,.63,1.29,1.06h.04v-6.62h2.56v16.67h-2.56v-1.56h-.04ZM149.46,21.19c0,1.14.26,2.04.78,2.68.52.65,1.24.97,2.16.97s1.69-.32,2.24-.97c.56-.64.84-1.47.84-2.49v-1.29c0-.82-.27-1.51-.81-2.06s-1.24-.83-2.1-.83c-.96,0-1.72.34-2.28,1.03s-.84,1.67-.84,2.95Z"/>
|
||||
</g>
|
||||
</svg>
|
||||
<path class="st1" d="M29.96,6.28h3.98c.66,0,1.19.53,1.19,1.19v8.35h4.36c.88,0,1.33,1.07.7,1.69l-7.56,7.56c-.37.37-.98.37-1.36,0l-7.57-7.56c-.63-.63-.18-1.69.7-1.69h4.36V7.47c0-.66.53-1.19,1.19-1.19ZM44.67,24.96v5.57c0,.66-.53,1.19-1.19,1.19h-23.06c-.66,0-1.19-.53-1.19-1.19v-5.57c0-.66.53-1.19,1.19-1.19h7.29l2.44,2.44c1,1,2.61,1,3.61,0l2.44-2.44h7.29c.66,0,1.19.53,1.19,1.19ZM38.5,29.34c0-.55-.45-.99-.99-.99s-.99.45-.99.99.45.99.99.99.99-.45.99-.99ZM41.68,29.34c0-.55-.45-.99-.99-.99s-.99.45-.99.99.45.99.99.99.99-.45.99-.99Z"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 8.9 KiB After Width: | Height: | Size: 3.8 KiB |
@@ -11,45 +11,57 @@
|
||||
};
|
||||
};
|
||||
|
||||
outputs = { self, nixpkgs, flake-utils, poetry2nix }:
|
||||
flake-utils.lib.eachDefaultSystem (system:
|
||||
outputs =
|
||||
{
|
||||
self,
|
||||
nixpkgs,
|
||||
flake-utils,
|
||||
poetry2nix,
|
||||
}:
|
||||
flake-utils.lib.eachDefaultSystem (
|
||||
system:
|
||||
let
|
||||
pkgs = import nixpkgs { inherit system; };
|
||||
p2n = (poetry2nix.lib.mkPoetry2Nix { inherit pkgs; });
|
||||
in {
|
||||
devShells.default = (p2n.mkPoetryEnv {
|
||||
projectDir = self + "/backend";
|
||||
# pyinstaller fails to compile so precompiled it is
|
||||
overrides = p2n.overrides.withDefaults (final: prev: {
|
||||
pyinstaller = prev.pyinstaller.override { preferWheel = true; };
|
||||
pyright = null;
|
||||
});
|
||||
}).env.overrideAttrs (oldAttrs: {
|
||||
shellHook = ''
|
||||
PYTHONPATH=`which python`
|
||||
FILE=.vscode/settings.json
|
||||
if [ -f "$FILE" ]; then
|
||||
jq --arg pythonpath "$PYTHONPATH" '.["python.defaultInterpreterPath"] = $pythonpath' $FILE > "$FILE.tmp" && mv "$FILE.tmp" "$FILE"
|
||||
else
|
||||
echo "{\"python.defaultInterpreterPath\": \"$PYTHONPATH\"}" > "$FILE"
|
||||
fi
|
||||
'';
|
||||
UV_USE_IO_URING = 0; # work around node#48444
|
||||
buildInputs = with pkgs; [
|
||||
nodejs_22
|
||||
nodePackages.pnpm
|
||||
poetry
|
||||
jq
|
||||
# fixes local pyright not being able to see the pythonpath properly.
|
||||
(pkgs.writeShellScriptBin "pyright" ''
|
||||
${pkgs.pyright}/bin/pyright --pythonpath `which python3` "$@" '')
|
||||
(pkgs.writeShellScriptBin "pyright-langserver" ''
|
||||
${pkgs.pyright}/bin/pyright-langserver --pythonpath `which python3` "$@" '')
|
||||
(pkgs.writeShellScriptBin "pyright-python" ''
|
||||
${pkgs.pyright}/bin/pyright-python --pythonpath `which python3` "$@" '')
|
||||
(pkgs.writeShellScriptBin "pyright-python-langserver" ''
|
||||
${pkgs.pyright}/bin/pyright-python-langserver --pythonpath `which python3` "$@" '')
|
||||
];
|
||||
});
|
||||
});
|
||||
in
|
||||
{
|
||||
devShells.default =
|
||||
(p2n.mkPoetryEnv {
|
||||
projectDir = self + "/backend";
|
||||
# pyinstaller fails to compile so precompiled it is
|
||||
overrides = p2n.overrides.withDefaults (
|
||||
final: prev: {
|
||||
pyinstaller = prev.pyinstaller.override { preferWheel = true; };
|
||||
pyright = null;
|
||||
}
|
||||
);
|
||||
}).env.overrideAttrs
|
||||
(oldAttrs: {
|
||||
shellHook = ''
|
||||
PYTHONPATH=`which python`
|
||||
FILE=.vscode/settings.json
|
||||
if [ -f "$FILE" ]; then
|
||||
jq --arg pythonpath "$PYTHONPATH" '.["python.defaultInterpreterPath"] = $pythonpath' $FILE > "$FILE.tmp" && mv "$FILE.tmp" "$FILE"
|
||||
else
|
||||
echo "{\"python.defaultInterpreterPath\": \"$PYTHONPATH\"}" > "$FILE"
|
||||
fi
|
||||
'';
|
||||
UV_USE_IO_URING = 0; # work around node#48444
|
||||
nativeBuildInputs = with pkgs; [
|
||||
python311Packages.setuptools
|
||||
];
|
||||
buildInputs = with pkgs; [
|
||||
nodejs_22
|
||||
nodePackages.pnpm
|
||||
poetry
|
||||
jq
|
||||
# fixes local pyright not being able to see the pythonpath properly.
|
||||
(pkgs.writeShellScriptBin "pyright" ''${pkgs.pyright}/bin/pyright --pythonpath `which python3` "$@" '')
|
||||
(pkgs.writeShellScriptBin "pyright-langserver" ''${pkgs.pyright}/bin/pyright-langserver --pythonpath `which python3` "$@" '')
|
||||
(pkgs.writeShellScriptBin "pyright-python" ''${pkgs.pyright}/bin/pyright-python --pythonpath `which python3` "$@" '')
|
||||
(pkgs.writeShellScriptBin "pyright-python-langserver" ''${pkgs.pyright}/bin/pyright-python-langserver --pythonpath `which python3` "$@" '')
|
||||
];
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@@ -7,4 +7,4 @@ export default {
|
||||
tabWidth: 2,
|
||||
endOfLine: 'auto',
|
||||
plugins: [importSort],
|
||||
}
|
||||
};
|
||||
|
||||
+11
-10
@@ -13,15 +13,15 @@
|
||||
"localize": "i18next"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@decky/api": "^1.0.5",
|
||||
"@decky/api": "^1.1.3",
|
||||
"@rollup/plugin-commonjs": "^26.0.1",
|
||||
"@rollup/plugin-image": "^3.0.3",
|
||||
"@rollup/plugin-json": "^6.1.0",
|
||||
"@rollup/plugin-node-resolve": "^15.2.3",
|
||||
"@rollup/plugin-replace": "^5.0.7",
|
||||
"@rollup/plugin-typescript": "^11.1.6",
|
||||
"@types/react": "18.3.3",
|
||||
"@types/react-dom": "18.3.0",
|
||||
"@types/react": "19.1.1",
|
||||
"@types/react-dom": "19.1.1",
|
||||
"@types/react-file-icon": "^1.0.4",
|
||||
"@types/react-router": "5.1.20",
|
||||
"husky": "^9.0.11",
|
||||
@@ -30,9 +30,9 @@
|
||||
"inquirer": "^9.2.23",
|
||||
"prettier": "^3.3.2",
|
||||
"prettier-plugin-import-sort": "^0.0.7",
|
||||
"react": "18.3.1",
|
||||
"react-dom": "18.3.1",
|
||||
"rollup": "^4.18.0",
|
||||
"react": "19.1.1",
|
||||
"react-dom": "19.1.1",
|
||||
"rollup": "^4.22.4",
|
||||
"rollup-plugin-delete": "^2.0.0",
|
||||
"rollup-plugin-external-globals": "^0.10.0",
|
||||
"rollup-plugin-polyfill-node": "^0.13.0",
|
||||
@@ -47,12 +47,13 @@
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@decky/ui": "^4.3.1",
|
||||
"@decky/ui": "^4.11.6",
|
||||
"compare-versions": "^6.1.1",
|
||||
"filesize": "^10.1.2",
|
||||
"i18next": "^23.11.5",
|
||||
"i18next": "^25.6.0",
|
||||
"i18next-http-backend": "^2.5.2",
|
||||
"react-file-icon": "^1.5.0",
|
||||
"react-i18next": "^14.1.2",
|
||||
"react-file-icon": "^1.6.0",
|
||||
"react-i18next": "^16.0.1",
|
||||
"react-icons": "^5.2.1",
|
||||
"react-markdown": "^9.0.1",
|
||||
"remark-gfm": "^4.0.0"
|
||||
|
||||
Generated
+222
-199
@@ -9,60 +9,63 @@ importers:
|
||||
.:
|
||||
dependencies:
|
||||
'@decky/ui':
|
||||
specifier: ^4.3.1
|
||||
version: 4.3.1
|
||||
specifier: ^4.11.6
|
||||
version: 4.11.6
|
||||
compare-versions:
|
||||
specifier: ^6.1.1
|
||||
version: 6.1.1
|
||||
filesize:
|
||||
specifier: ^10.1.2
|
||||
version: 10.1.2
|
||||
i18next:
|
||||
specifier: ^23.11.5
|
||||
version: 23.11.5
|
||||
specifier: ^25.6.0
|
||||
version: 25.6.0(typescript@5.4.5)
|
||||
i18next-http-backend:
|
||||
specifier: ^2.5.2
|
||||
version: 2.5.2
|
||||
react-file-icon:
|
||||
specifier: ^1.5.0
|
||||
version: 1.5.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
specifier: ^1.6.0
|
||||
version: 1.6.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
|
||||
react-i18next:
|
||||
specifier: ^14.1.2
|
||||
version: 14.1.2(i18next@23.11.5)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
specifier: ^16.0.1
|
||||
version: 16.0.1(i18next@25.6.0(typescript@5.4.5))(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(typescript@5.4.5)
|
||||
react-icons:
|
||||
specifier: ^5.2.1
|
||||
version: 5.2.1(react@18.3.1)
|
||||
version: 5.2.1(react@19.1.1)
|
||||
react-markdown:
|
||||
specifier: ^9.0.1
|
||||
version: 9.0.1(@types/react@18.3.3)(react@18.3.1)
|
||||
version: 9.0.1(@types/react@19.1.1)(react@19.1.1)
|
||||
remark-gfm:
|
||||
specifier: ^4.0.0
|
||||
version: 4.0.0
|
||||
devDependencies:
|
||||
'@decky/api':
|
||||
specifier: ^1.0.5
|
||||
version: 1.0.5
|
||||
specifier: ^1.1.3
|
||||
version: 1.1.3
|
||||
'@rollup/plugin-commonjs':
|
||||
specifier: ^26.0.1
|
||||
version: 26.0.1(rollup@4.18.0)
|
||||
version: 26.0.1(rollup@4.22.4)
|
||||
'@rollup/plugin-image':
|
||||
specifier: ^3.0.3
|
||||
version: 3.0.3(rollup@4.18.0)
|
||||
version: 3.0.3(rollup@4.22.4)
|
||||
'@rollup/plugin-json':
|
||||
specifier: ^6.1.0
|
||||
version: 6.1.0(rollup@4.18.0)
|
||||
version: 6.1.0(rollup@4.22.4)
|
||||
'@rollup/plugin-node-resolve':
|
||||
specifier: ^15.2.3
|
||||
version: 15.2.3(rollup@4.18.0)
|
||||
version: 15.2.3(rollup@4.22.4)
|
||||
'@rollup/plugin-replace':
|
||||
specifier: ^5.0.7
|
||||
version: 5.0.7(rollup@4.18.0)
|
||||
version: 5.0.7(rollup@4.22.4)
|
||||
'@rollup/plugin-typescript':
|
||||
specifier: ^11.1.6
|
||||
version: 11.1.6(rollup@4.18.0)(tslib@2.6.3)(typescript@5.4.5)
|
||||
version: 11.1.6(rollup@4.22.4)(tslib@2.6.3)(typescript@5.4.5)
|
||||
'@types/react':
|
||||
specifier: 18.3.3
|
||||
version: 18.3.3
|
||||
specifier: 19.1.1
|
||||
version: 19.1.1
|
||||
'@types/react-dom':
|
||||
specifier: 18.3.0
|
||||
version: 18.3.0
|
||||
specifier: 19.1.1
|
||||
version: 19.1.1(@types/react@19.1.1)
|
||||
'@types/react-file-icon':
|
||||
specifier: ^1.0.4
|
||||
version: 1.0.4
|
||||
@@ -88,26 +91,26 @@ importers:
|
||||
specifier: ^0.0.7
|
||||
version: 0.0.7(prettier@3.3.2)
|
||||
react:
|
||||
specifier: 18.3.1
|
||||
version: 18.3.1
|
||||
specifier: 19.1.1
|
||||
version: 19.1.1
|
||||
react-dom:
|
||||
specifier: 18.3.1
|
||||
version: 18.3.1(react@18.3.1)
|
||||
specifier: 19.1.1
|
||||
version: 19.1.1(react@19.1.1)
|
||||
rollup:
|
||||
specifier: ^4.18.0
|
||||
version: 4.18.0
|
||||
specifier: ^4.22.4
|
||||
version: 4.22.4
|
||||
rollup-plugin-delete:
|
||||
specifier: ^2.0.0
|
||||
version: 2.0.0
|
||||
rollup-plugin-external-globals:
|
||||
specifier: ^0.10.0
|
||||
version: 0.10.0(rollup@4.18.0)
|
||||
version: 0.10.0(rollup@4.22.4)
|
||||
rollup-plugin-polyfill-node:
|
||||
specifier: ^0.13.0
|
||||
version: 0.13.0(rollup@4.18.0)
|
||||
version: 0.13.0(rollup@4.22.4)
|
||||
rollup-plugin-visualizer:
|
||||
specifier: ^5.12.0
|
||||
version: 5.12.0(rollup@4.18.0)
|
||||
version: 5.12.0(rollup@4.22.4)
|
||||
tslib:
|
||||
specifier: ^2.6.3
|
||||
version: 2.6.3
|
||||
@@ -200,6 +203,10 @@ packages:
|
||||
resolution: {integrity: sha512-UwgBRMjJP+xv857DCngvqXI3Iq6J4v0wXmwc6sapg+zyhbwmQX67LUEFrkK5tbyJ30jGuG3ZvWpBiB9LCy1kWw==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
|
||||
'@babel/runtime@7.28.4':
|
||||
resolution: {integrity: sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
|
||||
'@babel/template@7.24.7':
|
||||
resolution: {integrity: sha512-jYqfPrU9JTF0PmPy1tLYHW4Mp4KlgxJD9l2nP9fD6yT/ICi554DmrWBAEYpIelzjHf1msDP3PxJIRt/nFNfBig==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
@@ -212,11 +219,11 @@ packages:
|
||||
resolution: {integrity: sha512-XEFXSlxiG5td2EJRe8vOmRbaXVgfcBlszKujvVmWIK/UpywWljQCfzAv3RQCGujWQ1RD4YYWEAqDXfuJiy8f5Q==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
|
||||
'@decky/api@1.0.5':
|
||||
resolution: {integrity: sha512-ghIewwXzedYDq0l5eik21Rg0Fd3DFeniCIjL6M90Zc/bOH7vlKpgUGl+V/5/TvaGKkYW51hsMV6Usfy5OZFM7Q==}
|
||||
'@decky/api@1.1.3':
|
||||
resolution: {integrity: sha512-XsPCZxfxk5I1UtylIUN3qaWQI31siQbKfbLIskkI5innEatY1m4NQqBv/6hwPaO9mKMbdqYpnh5PSJDeMEOOBA==}
|
||||
|
||||
'@decky/ui@4.3.1':
|
||||
resolution: {integrity: sha512-yp3Pg4kNx3m8tA8bfvlBDI7303X6aq1X4dbCnG+0+lwSeS8vKafH3Srllw2WpvLg1RZdrjmmugdSDhFRt2fRbA==}
|
||||
'@decky/ui@4.11.6':
|
||||
resolution: {integrity: sha512-vPCr2/KODeM6DAzIL/XN2e/RY7vhebXoWoh8e0VvB5QJU59Usb1z/cIpNmqe/GEMd1P3om6DFMcpEW5v8Se95Q==}
|
||||
|
||||
'@esbuild/aix-ppc64@0.20.2':
|
||||
resolution: {integrity: sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g==}
|
||||
@@ -482,83 +489,83 @@ packages:
|
||||
rollup:
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-android-arm-eabi@4.18.0':
|
||||
resolution: {integrity: sha512-Tya6xypR10giZV1XzxmH5wr25VcZSncG0pZIjfePT0OVBvqNEurzValetGNarVrGiq66EBVAFn15iYX4w6FKgQ==}
|
||||
'@rollup/rollup-android-arm-eabi@4.22.4':
|
||||
resolution: {integrity: sha512-Fxamp4aEZnfPOcGA8KSNEohV8hX7zVHOemC8jVBoBUHu5zpJK/Eu3uJwt6BMgy9fkvzxDaurgj96F/NiLukF2w==}
|
||||
cpu: [arm]
|
||||
os: [android]
|
||||
|
||||
'@rollup/rollup-android-arm64@4.18.0':
|
||||
resolution: {integrity: sha512-avCea0RAP03lTsDhEyfy+hpfr85KfyTctMADqHVhLAF3MlIkq83CP8UfAHUssgXTYd+6er6PaAhx/QGv4L1EiA==}
|
||||
'@rollup/rollup-android-arm64@4.22.4':
|
||||
resolution: {integrity: sha512-VXoK5UMrgECLYaMuGuVTOx5kcuap1Jm8g/M83RnCHBKOqvPPmROFJGQaZhGccnsFtfXQ3XYa4/jMCJvZnbJBdA==}
|
||||
cpu: [arm64]
|
||||
os: [android]
|
||||
|
||||
'@rollup/rollup-darwin-arm64@4.18.0':
|
||||
resolution: {integrity: sha512-IWfdwU7KDSm07Ty0PuA/W2JYoZ4iTj3TUQjkVsO/6U+4I1jN5lcR71ZEvRh52sDOERdnNhhHU57UITXz5jC1/w==}
|
||||
'@rollup/rollup-darwin-arm64@4.22.4':
|
||||
resolution: {integrity: sha512-xMM9ORBqu81jyMKCDP+SZDhnX2QEVQzTcC6G18KlTQEzWK8r/oNZtKuZaCcHhnsa6fEeOBionoyl5JsAbE/36Q==}
|
||||
cpu: [arm64]
|
||||
os: [darwin]
|
||||
|
||||
'@rollup/rollup-darwin-x64@4.18.0':
|
||||
resolution: {integrity: sha512-n2LMsUz7Ynu7DoQrSQkBf8iNrjOGyPLrdSg802vk6XT3FtsgX6JbE8IHRvposskFm9SNxzkLYGSq9QdpLYpRNA==}
|
||||
'@rollup/rollup-darwin-x64@4.22.4':
|
||||
resolution: {integrity: sha512-aJJyYKQwbHuhTUrjWjxEvGnNNBCnmpHDvrb8JFDbeSH3m2XdHcxDd3jthAzvmoI8w/kSjd2y0udT+4okADsZIw==}
|
||||
cpu: [x64]
|
||||
os: [darwin]
|
||||
|
||||
'@rollup/rollup-linux-arm-gnueabihf@4.18.0':
|
||||
resolution: {integrity: sha512-C/zbRYRXFjWvz9Z4haRxcTdnkPt1BtCkz+7RtBSuNmKzMzp3ZxdM28Mpccn6pt28/UWUCTXa+b0Mx1k3g6NOMA==}
|
||||
'@rollup/rollup-linux-arm-gnueabihf@4.22.4':
|
||||
resolution: {integrity: sha512-j63YtCIRAzbO+gC2L9dWXRh5BFetsv0j0va0Wi9epXDgU/XUi5dJKo4USTttVyK7fGw2nPWK0PbAvyliz50SCQ==}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
|
||||
'@rollup/rollup-linux-arm-musleabihf@4.18.0':
|
||||
resolution: {integrity: sha512-l3m9ewPgjQSXrUMHg93vt0hYCGnrMOcUpTz6FLtbwljo2HluS4zTXFy2571YQbisTnfTKPZ01u/ukJdQTLGh9A==}
|
||||
'@rollup/rollup-linux-arm-musleabihf@4.22.4':
|
||||
resolution: {integrity: sha512-dJnWUgwWBX1YBRsuKKMOlXCzh2Wu1mlHzv20TpqEsfdZLb3WoJW2kIEsGwLkroYf24IrPAvOT/ZQ2OYMV6vlrg==}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
|
||||
'@rollup/rollup-linux-arm64-gnu@4.18.0':
|
||||
resolution: {integrity: sha512-rJ5D47d8WD7J+7STKdCUAgmQk49xuFrRi9pZkWoRD1UeSMakbcepWXPF8ycChBoAqs1pb2wzvbY6Q33WmN2ftw==}
|
||||
'@rollup/rollup-linux-arm64-gnu@4.22.4':
|
||||
resolution: {integrity: sha512-AdPRoNi3NKVLolCN/Sp4F4N1d98c4SBnHMKoLuiG6RXgoZ4sllseuGioszumnPGmPM2O7qaAX/IJdeDU8f26Aw==}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
|
||||
'@rollup/rollup-linux-arm64-musl@4.18.0':
|
||||
resolution: {integrity: sha512-be6Yx37b24ZwxQ+wOQXXLZqpq4jTckJhtGlWGZs68TgdKXJgw54lUUoFYrg6Zs/kjzAQwEwYbp8JxZVzZLRepQ==}
|
||||
'@rollup/rollup-linux-arm64-musl@4.22.4':
|
||||
resolution: {integrity: sha512-Gl0AxBtDg8uoAn5CCqQDMqAx22Wx22pjDOjBdmG0VIWX3qUBHzYmOKh8KXHL4UpogfJ14G4wk16EQogF+v8hmA==}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
|
||||
'@rollup/rollup-linux-powerpc64le-gnu@4.18.0':
|
||||
resolution: {integrity: sha512-hNVMQK+qrA9Todu9+wqrXOHxFiD5YmdEi3paj6vP02Kx1hjd2LLYR2eaN7DsEshg09+9uzWi2W18MJDlG0cxJA==}
|
||||
'@rollup/rollup-linux-powerpc64le-gnu@4.22.4':
|
||||
resolution: {integrity: sha512-3aVCK9xfWW1oGQpTsYJJPF6bfpWfhbRnhdlyhak2ZiyFLDaayz0EP5j9V1RVLAAxlmWKTDfS9wyRyY3hvhPoOg==}
|
||||
cpu: [ppc64]
|
||||
os: [linux]
|
||||
|
||||
'@rollup/rollup-linux-riscv64-gnu@4.18.0':
|
||||
resolution: {integrity: sha512-ROCM7i+m1NfdrsmvwSzoxp9HFtmKGHEqu5NNDiZWQtXLA8S5HBCkVvKAxJ8U+CVctHwV2Gb5VUaK7UAkzhDjlg==}
|
||||
'@rollup/rollup-linux-riscv64-gnu@4.22.4':
|
||||
resolution: {integrity: sha512-ePYIir6VYnhgv2C5Xe9u+ico4t8sZWXschR6fMgoPUK31yQu7hTEJb7bCqivHECwIClJfKgE7zYsh1qTP3WHUA==}
|
||||
cpu: [riscv64]
|
||||
os: [linux]
|
||||
|
||||
'@rollup/rollup-linux-s390x-gnu@4.18.0':
|
||||
resolution: {integrity: sha512-0UyyRHyDN42QL+NbqevXIIUnKA47A+45WyasO+y2bGJ1mhQrfrtXUpTxCOrfxCR4esV3/RLYyucGVPiUsO8xjg==}
|
||||
'@rollup/rollup-linux-s390x-gnu@4.22.4':
|
||||
resolution: {integrity: sha512-GqFJ9wLlbB9daxhVlrTe61vJtEY99/xB3C8e4ULVsVfflcpmR6c8UZXjtkMA6FhNONhj2eA5Tk9uAVw5orEs4Q==}
|
||||
cpu: [s390x]
|
||||
os: [linux]
|
||||
|
||||
'@rollup/rollup-linux-x64-gnu@4.18.0':
|
||||
resolution: {integrity: sha512-xuglR2rBVHA5UsI8h8UbX4VJ470PtGCf5Vpswh7p2ukaqBGFTnsfzxUBetoWBWymHMxbIG0Cmx7Y9qDZzr648w==}
|
||||
'@rollup/rollup-linux-x64-gnu@4.22.4':
|
||||
resolution: {integrity: sha512-87v0ol2sH9GE3cLQLNEy0K/R0pz1nvg76o8M5nhMR0+Q+BBGLnb35P0fVz4CQxHYXaAOhE8HhlkaZfsdUOlHwg==}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
|
||||
'@rollup/rollup-linux-x64-musl@4.18.0':
|
||||
resolution: {integrity: sha512-LKaqQL9osY/ir2geuLVvRRs+utWUNilzdE90TpyoX0eNqPzWjRm14oMEE+YLve4k/NAqCdPkGYDaDF5Sw+xBfg==}
|
||||
'@rollup/rollup-linux-x64-musl@4.22.4':
|
||||
resolution: {integrity: sha512-UV6FZMUgePDZrFjrNGIWzDo/vABebuXBhJEqrHxrGiU6HikPy0Z3LfdtciIttEUQfuDdCn8fqh7wiFJjCNwO+g==}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
|
||||
'@rollup/rollup-win32-arm64-msvc@4.18.0':
|
||||
resolution: {integrity: sha512-7J6TkZQFGo9qBKH0pk2cEVSRhJbL6MtfWxth7Y5YmZs57Pi+4x6c2dStAUvaQkHQLnEQv1jzBUW43GvZW8OFqA==}
|
||||
'@rollup/rollup-win32-arm64-msvc@4.22.4':
|
||||
resolution: {integrity: sha512-BjI+NVVEGAXjGWYHz/vv0pBqfGoUH0IGZ0cICTn7kB9PyjrATSkX+8WkguNjWoj2qSr1im/+tTGRaY+4/PdcQw==}
|
||||
cpu: [arm64]
|
||||
os: [win32]
|
||||
|
||||
'@rollup/rollup-win32-ia32-msvc@4.18.0':
|
||||
resolution: {integrity: sha512-Txjh+IxBPbkUB9+SXZMpv+b/vnTEtFyfWZgJ6iyCmt2tdx0OF5WhFowLmnh8ENGNpfUlUZkdI//4IEmhwPieNg==}
|
||||
'@rollup/rollup-win32-ia32-msvc@4.22.4':
|
||||
resolution: {integrity: sha512-SiWG/1TuUdPvYmzmYnmd3IEifzR61Tragkbx9D3+R8mzQqDBz8v+BvZNDlkiTtI9T15KYZhP0ehn3Dld4n9J5g==}
|
||||
cpu: [ia32]
|
||||
os: [win32]
|
||||
|
||||
'@rollup/rollup-win32-x64-msvc@4.18.0':
|
||||
resolution: {integrity: sha512-UOo5FdvOL0+eIVTgS4tIdbW+TtnBLWg1YBCcU2KWM7nuNwRz9bksDX1bekJJCpu25N1DVWaCwnT39dVQxzqS8g==}
|
||||
'@rollup/rollup-win32-x64-msvc@4.22.4':
|
||||
resolution: {integrity: sha512-j8pPKp53/lq9lMXN57S8cFz0MynJk8OWNuUnXct/9KCpKU7DgU3bYMJhwWmcqC0UU29p8Lr0/7KEVcaM6bf47Q==}
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
|
||||
@@ -595,11 +602,10 @@ packages:
|
||||
'@types/node@20.14.2':
|
||||
resolution: {integrity: sha512-xyu6WAMVwv6AKFLB+e/7ySZVr/0zLCzOa7rSpq6jNwpqOrUbcACDWC+53d4n2QHOnDou0fbIsg8wZu/sxrnI4Q==}
|
||||
|
||||
'@types/prop-types@15.7.12':
|
||||
resolution: {integrity: sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q==}
|
||||
|
||||
'@types/react-dom@18.3.0':
|
||||
resolution: {integrity: sha512-EhwApuTmMBmXuFOikhQLIBUn6uFg81SwLMOAUgodJF14SOBOCMdU04gDoYi0WOJJHD144TL32z4yDqCW3dnkQg==}
|
||||
'@types/react-dom@19.1.1':
|
||||
resolution: {integrity: sha512-jFf/woGTVTjUJsl2O7hcopJ1r0upqoq/vIOoCj0yLh3RIXxWcljlpuZ+vEBRXsymD1jhfeJrlyTy/S1UW+4y1w==}
|
||||
peerDependencies:
|
||||
'@types/react': ^19.0.0
|
||||
|
||||
'@types/react-file-icon@1.0.4':
|
||||
resolution: {integrity: sha512-c1mIklUDaxm9odxf8RTiy/EAxsblZliJ86EKIOAyuafP9eK3iudyn4ATv53DX6ZvgGymc7IttVNm97LTGnTiYA==}
|
||||
@@ -607,8 +613,8 @@ packages:
|
||||
'@types/react-router@5.1.20':
|
||||
resolution: {integrity: sha512-jGjmu/ZqS7FjSH6owMcD5qpq19+1RS9DeVRqfl1FeBMxTDQAGwlMWOcs52NDoXaNKyG3d1cYQFMs9rCrb88o9Q==}
|
||||
|
||||
'@types/react@18.3.3':
|
||||
resolution: {integrity: sha512-hti/R0pS0q1/xx+TsI73XIqk26eBsISZ2R0wUijXIngRK9R/e7Xw/cXVxQK7R5JjW+SV4zGcn5hXjudkN/pLIw==}
|
||||
'@types/react@19.1.1':
|
||||
resolution: {integrity: sha512-ePapxDL7qrgqSF67s0h9m412d9DbXyC1n59O2st+9rjuuamWsZuD2w55rqY12CbzsZ7uVXb5Nw0gEp9Z8MMutQ==}
|
||||
|
||||
'@types/resolve@1.20.2':
|
||||
resolution: {integrity: sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==}
|
||||
@@ -848,6 +854,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==}
|
||||
|
||||
@@ -864,8 +873,8 @@ packages:
|
||||
cross-fetch@4.0.0:
|
||||
resolution: {integrity: sha512-e4a5N8lVvuLgAWgnCrLr2PP0YyDOTHa9H/Rj54dirp61qXnNq46m82bRhNqIA5VccJtWBvPTFRV3TtvHUKPB1g==}
|
||||
|
||||
cross-spawn@7.0.3:
|
||||
resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==}
|
||||
cross-spawn@7.0.6:
|
||||
resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==}
|
||||
engines: {node: '>= 8'}
|
||||
|
||||
css-select@5.1.0:
|
||||
@@ -1181,8 +1190,16 @@ packages:
|
||||
engines: {node: '>=18.0.0 || >=20.0.0 || >=22.0.0', npm: '>=6', yarn: '>=1'}
|
||||
hasBin: true
|
||||
|
||||
i18next@23.11.5:
|
||||
resolution: {integrity: sha512-41pvpVbW9rhZPk5xjCX2TPJi2861LEig/YRhUkY+1FQ2IQPS0bKUDYnEqY8XPPbB48h1uIwLnP9iiEfuSl20CA==}
|
||||
i18next@23.16.8:
|
||||
resolution: {integrity: sha512-06r/TitrM88Mg5FdUXAKL96dJMzgqLE5dv3ryBAra4KCwD9mJ4ndOTS95ZuymIGoE+2hzfdaMak2X11/es7ZWg==}
|
||||
|
||||
i18next@25.6.0:
|
||||
resolution: {integrity: sha512-tTn8fLrwBYtnclpL5aPXK/tAYBLWVvoHM1zdfXoRNLcI+RvtMsoZRV98ePlaW3khHYKuNh/Q65W/+NVFUeIwVw==}
|
||||
peerDependencies:
|
||||
typescript: ^5
|
||||
peerDependenciesMeta:
|
||||
typescript:
|
||||
optional: true
|
||||
|
||||
iconv-lite@0.4.24:
|
||||
resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==}
|
||||
@@ -1542,8 +1559,8 @@ packages:
|
||||
micromark@4.0.0:
|
||||
resolution: {integrity: sha512-o/sd0nMof8kYff+TqcDx3VSrgBTcZpSvYcAHIfHhv5VAuNmisCxjhx6YmxS8PFEpb9z5WKWKPdzf0jM23ro3RQ==}
|
||||
|
||||
micromatch@4.0.7:
|
||||
resolution: {integrity: sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q==}
|
||||
micromatch@4.0.8:
|
||||
resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==}
|
||||
engines: {node: '>=8.6'}
|
||||
|
||||
mimic-fn@2.1.0:
|
||||
@@ -1703,29 +1720,32 @@ packages:
|
||||
quick-temp@0.1.8:
|
||||
resolution: {integrity: sha512-YsmIFfD9j2zaFwJkzI6eMG7y0lQP7YeWzgtFgNl38pGWZBSXJooZbOWwkcRot7Vt0Fg9L23pX0tqWU3VvLDsiA==}
|
||||
|
||||
react-dom@18.3.1:
|
||||
resolution: {integrity: sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==}
|
||||
react-dom@19.1.1:
|
||||
resolution: {integrity: sha512-Dlq/5LAZgF0Gaz6yiqZCf6VCcZs1ghAJyrsu84Q/GT0gV+mCxbfmKNoGRKBYMJ8IEdGPqu49YWXD02GCknEDkw==}
|
||||
peerDependencies:
|
||||
react: ^18.3.1
|
||||
react: ^19.1.1
|
||||
|
||||
react-file-icon@1.5.0:
|
||||
resolution: {integrity: sha512-6K2/nAI69CS838HOS+4S95MLXwf1neWywek1FgqcTFPTYjnM8XT7aBLz4gkjoqQKY9qPhu3A2tu+lvxhmZYY9w==}
|
||||
react-file-icon@1.6.0:
|
||||
resolution: {integrity: sha512-Ba4Qa2ya/kvhcCd4LJja77sV7JD7u1ZXcI1DUz+TII3nGmglG6QY+NZeHizThokgct3qI0glwb9eV8NqRGs5lw==}
|
||||
peerDependencies:
|
||||
react: ^18.0.0 || ^17.0.0 || ^16.2.0
|
||||
react-dom: ^18.0.0 || ^17.0.0 || ^16.2.0
|
||||
react: ^19.0.0 || ^18.0.0 || ^17.0.0 || ^16.2.0
|
||||
react-dom: ^19.0.0 || ^18.0.0 || ^17.0.0 || ^16.2.0
|
||||
|
||||
react-i18next@14.1.2:
|
||||
resolution: {integrity: sha512-FSIcJy6oauJbGEXfhUgVeLzvWBhIBIS+/9c6Lj4niwKZyGaGb4V4vUbATXSlsHJDXXB+ociNxqFNiFuV1gmoqg==}
|
||||
react-i18next@16.0.1:
|
||||
resolution: {integrity: sha512-0S//bpYEkCPjzuVmxDf9Z6+Y+ArNvpAUk7eDL4qNCZXjDh6Z9j6MZ+NThU7kMCOsmYmDCun3GYEwkiOjjZo9Ug==}
|
||||
peerDependencies:
|
||||
i18next: '>= 23.2.3'
|
||||
i18next: '>= 25.5.2'
|
||||
react: '>= 16.8.0'
|
||||
react-dom: '*'
|
||||
react-native: '*'
|
||||
typescript: ^5
|
||||
peerDependenciesMeta:
|
||||
react-dom:
|
||||
optional: true
|
||||
react-native:
|
||||
optional: true
|
||||
typescript:
|
||||
optional: true
|
||||
|
||||
react-icons@5.2.1:
|
||||
resolution: {integrity: sha512-zdbW5GstTzXaVKvGSyTaBalt7HSfuK5ovrzlpyiWHAFXndXTdd/1hdDHI4xBM1Mn7YriT6aqESucFl9kEXzrdw==}
|
||||
@@ -1741,8 +1761,8 @@ packages:
|
||||
'@types/react': '>=18'
|
||||
react: '>=18'
|
||||
|
||||
react@18.3.1:
|
||||
resolution: {integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==}
|
||||
react@19.1.1:
|
||||
resolution: {integrity: sha512-w8nqGImo45dmMIfljjMwOGtbmC/mk4CMYhWIicdSflH91J9TyCyczcPFXJzrZ/ZXcgGRFeP6BU0BEJTw6tZdfQ==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
readable-stream@2.3.8:
|
||||
@@ -1836,8 +1856,8 @@ packages:
|
||||
rollup:
|
||||
optional: true
|
||||
|
||||
rollup@4.18.0:
|
||||
resolution: {integrity: sha512-QmJz14PX3rzbJCN1SG4Xe/bAAX2a6NpCP8ab2vfu2GiUr8AQcr2nCV/oEO3yneFarB67zk8ShlIyWb2LGTb3Sg==}
|
||||
rollup@4.22.4:
|
||||
resolution: {integrity: sha512-vD8HJ5raRcWOyymsR6Z3o6+RzfEPCnVLMFJ6vRslO1jt4LO6dUo5Qnpg7y4RkZFM2DMe3WUirkI5c16onjrc6A==}
|
||||
engines: {node: '>=18.0.0', npm: '>=8.0.0'}
|
||||
hasBin: true
|
||||
|
||||
@@ -1867,8 +1887,8 @@ packages:
|
||||
safer-buffer@2.1.2:
|
||||
resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==}
|
||||
|
||||
scheduler@0.23.2:
|
||||
resolution: {integrity: sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==}
|
||||
scheduler@0.26.0:
|
||||
resolution: {integrity: sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==}
|
||||
|
||||
semver@6.3.1:
|
||||
resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==}
|
||||
@@ -2260,6 +2280,8 @@ snapshots:
|
||||
dependencies:
|
||||
regenerator-runtime: 0.14.1
|
||||
|
||||
'@babel/runtime@7.28.4': {}
|
||||
|
||||
'@babel/template@7.24.7':
|
||||
dependencies:
|
||||
'@babel/code-frame': 7.24.7
|
||||
@@ -2287,9 +2309,9 @@ snapshots:
|
||||
'@babel/helper-validator-identifier': 7.24.7
|
||||
to-fast-properties: 2.0.0
|
||||
|
||||
'@decky/api@1.0.5': {}
|
||||
'@decky/api@1.1.3': {}
|
||||
|
||||
'@decky/ui@4.3.1': {}
|
||||
'@decky/ui@4.11.6': {}
|
||||
|
||||
'@esbuild/aix-ppc64@0.20.2':
|
||||
optional: true
|
||||
@@ -2411,119 +2433,119 @@ snapshots:
|
||||
'@pkgjs/parseargs@0.11.0':
|
||||
optional: true
|
||||
|
||||
'@rollup/plugin-commonjs@26.0.1(rollup@4.18.0)':
|
||||
'@rollup/plugin-commonjs@26.0.1(rollup@4.22.4)':
|
||||
dependencies:
|
||||
'@rollup/pluginutils': 5.1.0(rollup@4.18.0)
|
||||
'@rollup/pluginutils': 5.1.0(rollup@4.22.4)
|
||||
commondir: 1.0.1
|
||||
estree-walker: 2.0.2
|
||||
glob: 10.4.1
|
||||
is-reference: 1.2.1
|
||||
magic-string: 0.30.10
|
||||
optionalDependencies:
|
||||
rollup: 4.18.0
|
||||
rollup: 4.22.4
|
||||
|
||||
'@rollup/plugin-image@3.0.3(rollup@4.18.0)':
|
||||
'@rollup/plugin-image@3.0.3(rollup@4.22.4)':
|
||||
dependencies:
|
||||
'@rollup/pluginutils': 5.1.0(rollup@4.18.0)
|
||||
'@rollup/pluginutils': 5.1.0(rollup@4.22.4)
|
||||
mini-svg-data-uri: 1.4.4
|
||||
optionalDependencies:
|
||||
rollup: 4.18.0
|
||||
rollup: 4.22.4
|
||||
|
||||
'@rollup/plugin-inject@5.0.5(rollup@4.18.0)':
|
||||
'@rollup/plugin-inject@5.0.5(rollup@4.22.4)':
|
||||
dependencies:
|
||||
'@rollup/pluginutils': 5.1.0(rollup@4.18.0)
|
||||
'@rollup/pluginutils': 5.1.0(rollup@4.22.4)
|
||||
estree-walker: 2.0.2
|
||||
magic-string: 0.30.10
|
||||
optionalDependencies:
|
||||
rollup: 4.18.0
|
||||
rollup: 4.22.4
|
||||
|
||||
'@rollup/plugin-json@6.1.0(rollup@4.18.0)':
|
||||
'@rollup/plugin-json@6.1.0(rollup@4.22.4)':
|
||||
dependencies:
|
||||
'@rollup/pluginutils': 5.1.0(rollup@4.18.0)
|
||||
'@rollup/pluginutils': 5.1.0(rollup@4.22.4)
|
||||
optionalDependencies:
|
||||
rollup: 4.18.0
|
||||
rollup: 4.22.4
|
||||
|
||||
'@rollup/plugin-node-resolve@15.2.3(rollup@4.18.0)':
|
||||
'@rollup/plugin-node-resolve@15.2.3(rollup@4.22.4)':
|
||||
dependencies:
|
||||
'@rollup/pluginutils': 5.1.0(rollup@4.18.0)
|
||||
'@rollup/pluginutils': 5.1.0(rollup@4.22.4)
|
||||
'@types/resolve': 1.20.2
|
||||
deepmerge: 4.3.1
|
||||
is-builtin-module: 3.2.1
|
||||
is-module: 1.0.0
|
||||
resolve: 1.22.8
|
||||
optionalDependencies:
|
||||
rollup: 4.18.0
|
||||
rollup: 4.22.4
|
||||
|
||||
'@rollup/plugin-replace@5.0.7(rollup@4.18.0)':
|
||||
'@rollup/plugin-replace@5.0.7(rollup@4.22.4)':
|
||||
dependencies:
|
||||
'@rollup/pluginutils': 5.1.0(rollup@4.18.0)
|
||||
'@rollup/pluginutils': 5.1.0(rollup@4.22.4)
|
||||
magic-string: 0.30.10
|
||||
optionalDependencies:
|
||||
rollup: 4.18.0
|
||||
rollup: 4.22.4
|
||||
|
||||
'@rollup/plugin-typescript@11.1.6(rollup@4.18.0)(tslib@2.6.3)(typescript@5.4.5)':
|
||||
'@rollup/plugin-typescript@11.1.6(rollup@4.22.4)(tslib@2.6.3)(typescript@5.4.5)':
|
||||
dependencies:
|
||||
'@rollup/pluginutils': 5.1.0(rollup@4.18.0)
|
||||
'@rollup/pluginutils': 5.1.0(rollup@4.22.4)
|
||||
resolve: 1.22.8
|
||||
typescript: 5.4.5
|
||||
optionalDependencies:
|
||||
rollup: 4.18.0
|
||||
rollup: 4.22.4
|
||||
tslib: 2.6.3
|
||||
|
||||
'@rollup/pluginutils@5.1.0(rollup@4.18.0)':
|
||||
'@rollup/pluginutils@5.1.0(rollup@4.22.4)':
|
||||
dependencies:
|
||||
'@types/estree': 1.0.5
|
||||
estree-walker: 2.0.2
|
||||
picomatch: 2.3.1
|
||||
optionalDependencies:
|
||||
rollup: 4.18.0
|
||||
rollup: 4.22.4
|
||||
|
||||
'@rollup/rollup-android-arm-eabi@4.18.0':
|
||||
'@rollup/rollup-android-arm-eabi@4.22.4':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-android-arm64@4.18.0':
|
||||
'@rollup/rollup-android-arm64@4.22.4':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-darwin-arm64@4.18.0':
|
||||
'@rollup/rollup-darwin-arm64@4.22.4':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-darwin-x64@4.18.0':
|
||||
'@rollup/rollup-darwin-x64@4.22.4':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-linux-arm-gnueabihf@4.18.0':
|
||||
'@rollup/rollup-linux-arm-gnueabihf@4.22.4':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-linux-arm-musleabihf@4.18.0':
|
||||
'@rollup/rollup-linux-arm-musleabihf@4.22.4':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-linux-arm64-gnu@4.18.0':
|
||||
'@rollup/rollup-linux-arm64-gnu@4.22.4':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-linux-arm64-musl@4.18.0':
|
||||
'@rollup/rollup-linux-arm64-musl@4.22.4':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-linux-powerpc64le-gnu@4.18.0':
|
||||
'@rollup/rollup-linux-powerpc64le-gnu@4.22.4':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-linux-riscv64-gnu@4.18.0':
|
||||
'@rollup/rollup-linux-riscv64-gnu@4.22.4':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-linux-s390x-gnu@4.18.0':
|
||||
'@rollup/rollup-linux-s390x-gnu@4.22.4':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-linux-x64-gnu@4.18.0':
|
||||
'@rollup/rollup-linux-x64-gnu@4.22.4':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-linux-x64-musl@4.18.0':
|
||||
'@rollup/rollup-linux-x64-musl@4.22.4':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-win32-arm64-msvc@4.18.0':
|
||||
'@rollup/rollup-win32-arm64-msvc@4.22.4':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-win32-ia32-msvc@4.18.0':
|
||||
'@rollup/rollup-win32-ia32-msvc@4.22.4':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-win32-x64-msvc@4.18.0':
|
||||
'@rollup/rollup-win32-x64-msvc@4.22.4':
|
||||
optional: true
|
||||
|
||||
'@types/debug@4.1.12':
|
||||
@@ -2561,24 +2583,21 @@ snapshots:
|
||||
dependencies:
|
||||
undici-types: 5.26.5
|
||||
|
||||
'@types/prop-types@15.7.12': {}
|
||||
|
||||
'@types/react-dom@18.3.0':
|
||||
'@types/react-dom@19.1.1(@types/react@19.1.1)':
|
||||
dependencies:
|
||||
'@types/react': 18.3.3
|
||||
'@types/react': 19.1.1
|
||||
|
||||
'@types/react-file-icon@1.0.4':
|
||||
dependencies:
|
||||
'@types/react': 18.3.3
|
||||
'@types/react': 19.1.1
|
||||
|
||||
'@types/react-router@5.1.20':
|
||||
dependencies:
|
||||
'@types/history': 4.7.11
|
||||
'@types/react': 18.3.3
|
||||
'@types/react': 19.1.1
|
||||
|
||||
'@types/react@18.3.3':
|
||||
'@types/react@19.1.1':
|
||||
dependencies:
|
||||
'@types/prop-types': 15.7.12
|
||||
csstype: 3.1.3
|
||||
|
||||
'@types/resolve@1.20.2': {}
|
||||
@@ -2816,6 +2835,8 @@ snapshots:
|
||||
|
||||
commondir@1.0.1: {}
|
||||
|
||||
compare-versions@6.1.1: {}
|
||||
|
||||
concat-map@0.0.1: {}
|
||||
|
||||
convert-source-map@2.0.0: {}
|
||||
@@ -2835,7 +2856,7 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- encoding
|
||||
|
||||
cross-spawn@7.0.3:
|
||||
cross-spawn@7.0.6:
|
||||
dependencies:
|
||||
path-key: 3.1.1
|
||||
shebang-command: 2.0.0
|
||||
@@ -3002,7 +3023,7 @@ snapshots:
|
||||
'@nodelib/fs.walk': 1.2.8
|
||||
glob-parent: 5.1.2
|
||||
merge2: 1.4.1
|
||||
micromatch: 4.0.7
|
||||
micromatch: 4.0.8
|
||||
|
||||
fastq@1.17.1:
|
||||
dependencies:
|
||||
@@ -3020,7 +3041,7 @@ snapshots:
|
||||
|
||||
foreground-child@3.2.0:
|
||||
dependencies:
|
||||
cross-spawn: 7.0.3
|
||||
cross-spawn: 7.0.6
|
||||
signal-exit: 4.1.0
|
||||
|
||||
fs-extra@11.2.0:
|
||||
@@ -3221,7 +3242,7 @@ snapshots:
|
||||
esbuild: 0.20.2
|
||||
fs-extra: 11.2.0
|
||||
gulp-sort: 2.0.0
|
||||
i18next: 23.11.5
|
||||
i18next: 23.16.8
|
||||
js-yaml: 4.1.0
|
||||
lilconfig: 3.1.2
|
||||
rsvp: 4.8.5
|
||||
@@ -3232,10 +3253,16 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
i18next@23.11.5:
|
||||
i18next@23.16.8:
|
||||
dependencies:
|
||||
'@babel/runtime': 7.24.7
|
||||
|
||||
i18next@25.6.0(typescript@5.4.5):
|
||||
dependencies:
|
||||
'@babel/runtime': 7.28.4
|
||||
optionalDependencies:
|
||||
typescript: 5.4.5
|
||||
|
||||
iconv-lite@0.4.24:
|
||||
dependencies:
|
||||
safer-buffer: 2.1.2
|
||||
@@ -3797,7 +3824,7 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
micromatch@4.0.7:
|
||||
micromatch@4.0.8:
|
||||
dependencies:
|
||||
braces: 3.0.3
|
||||
picomatch: 2.3.1
|
||||
@@ -3952,43 +3979,43 @@ snapshots:
|
||||
rimraf: 2.7.1
|
||||
underscore.string: 3.3.6
|
||||
|
||||
react-dom@18.3.1(react@18.3.1):
|
||||
react-dom@19.1.1(react@19.1.1):
|
||||
dependencies:
|
||||
loose-envify: 1.4.0
|
||||
react: 18.3.1
|
||||
scheduler: 0.23.2
|
||||
react: 19.1.1
|
||||
scheduler: 0.26.0
|
||||
|
||||
react-file-icon@1.5.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
|
||||
react-file-icon@1.6.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1):
|
||||
dependencies:
|
||||
colord: 2.9.3
|
||||
prop-types: 15.8.1
|
||||
react: 18.3.1
|
||||
react-dom: 18.3.1(react@18.3.1)
|
||||
react: 19.1.1
|
||||
react-dom: 19.1.1(react@19.1.1)
|
||||
|
||||
react-i18next@14.1.2(i18next@23.11.5)(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
|
||||
react-i18next@16.0.1(i18next@25.6.0(typescript@5.4.5))(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(typescript@5.4.5):
|
||||
dependencies:
|
||||
'@babel/runtime': 7.24.7
|
||||
'@babel/runtime': 7.28.4
|
||||
html-parse-stringify: 3.0.1
|
||||
i18next: 23.11.5
|
||||
react: 18.3.1
|
||||
i18next: 25.6.0(typescript@5.4.5)
|
||||
react: 19.1.1
|
||||
optionalDependencies:
|
||||
react-dom: 18.3.1(react@18.3.1)
|
||||
react-dom: 19.1.1(react@19.1.1)
|
||||
typescript: 5.4.5
|
||||
|
||||
react-icons@5.2.1(react@18.3.1):
|
||||
react-icons@5.2.1(react@19.1.1):
|
||||
dependencies:
|
||||
react: 18.3.1
|
||||
react: 19.1.1
|
||||
|
||||
react-is@16.13.1: {}
|
||||
|
||||
react-markdown@9.0.1(@types/react@18.3.3)(react@18.3.1):
|
||||
react-markdown@9.0.1(@types/react@19.1.1)(react@19.1.1):
|
||||
dependencies:
|
||||
'@types/hast': 3.0.4
|
||||
'@types/react': 18.3.3
|
||||
'@types/react': 19.1.1
|
||||
devlop: 1.1.0
|
||||
hast-util-to-jsx-runtime: 2.3.0
|
||||
html-url-attributes: 3.0.0
|
||||
mdast-util-to-hast: 13.2.0
|
||||
react: 18.3.1
|
||||
react: 19.1.1
|
||||
remark-parse: 11.0.0
|
||||
remark-rehype: 11.1.0
|
||||
unified: 11.0.4
|
||||
@@ -3997,9 +4024,7 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
react@18.3.1:
|
||||
dependencies:
|
||||
loose-envify: 1.4.0
|
||||
react@19.1.1: {}
|
||||
|
||||
readable-stream@2.3.8:
|
||||
dependencies:
|
||||
@@ -4092,48 +4117,48 @@ snapshots:
|
||||
dependencies:
|
||||
del: 5.1.0
|
||||
|
||||
rollup-plugin-external-globals@0.10.0(rollup@4.18.0):
|
||||
rollup-plugin-external-globals@0.10.0(rollup@4.22.4):
|
||||
dependencies:
|
||||
'@rollup/pluginutils': 5.1.0(rollup@4.18.0)
|
||||
'@rollup/pluginutils': 5.1.0(rollup@4.22.4)
|
||||
estree-walker: 3.0.3
|
||||
is-reference: 3.0.2
|
||||
magic-string: 0.30.10
|
||||
rollup: 4.18.0
|
||||
rollup: 4.22.4
|
||||
|
||||
rollup-plugin-polyfill-node@0.13.0(rollup@4.18.0):
|
||||
rollup-plugin-polyfill-node@0.13.0(rollup@4.22.4):
|
||||
dependencies:
|
||||
'@rollup/plugin-inject': 5.0.5(rollup@4.18.0)
|
||||
rollup: 4.18.0
|
||||
'@rollup/plugin-inject': 5.0.5(rollup@4.22.4)
|
||||
rollup: 4.22.4
|
||||
|
||||
rollup-plugin-visualizer@5.12.0(rollup@4.18.0):
|
||||
rollup-plugin-visualizer@5.12.0(rollup@4.22.4):
|
||||
dependencies:
|
||||
open: 8.4.2
|
||||
picomatch: 2.3.1
|
||||
source-map: 0.7.4
|
||||
yargs: 17.7.2
|
||||
optionalDependencies:
|
||||
rollup: 4.18.0
|
||||
rollup: 4.22.4
|
||||
|
||||
rollup@4.18.0:
|
||||
rollup@4.22.4:
|
||||
dependencies:
|
||||
'@types/estree': 1.0.5
|
||||
optionalDependencies:
|
||||
'@rollup/rollup-android-arm-eabi': 4.18.0
|
||||
'@rollup/rollup-android-arm64': 4.18.0
|
||||
'@rollup/rollup-darwin-arm64': 4.18.0
|
||||
'@rollup/rollup-darwin-x64': 4.18.0
|
||||
'@rollup/rollup-linux-arm-gnueabihf': 4.18.0
|
||||
'@rollup/rollup-linux-arm-musleabihf': 4.18.0
|
||||
'@rollup/rollup-linux-arm64-gnu': 4.18.0
|
||||
'@rollup/rollup-linux-arm64-musl': 4.18.0
|
||||
'@rollup/rollup-linux-powerpc64le-gnu': 4.18.0
|
||||
'@rollup/rollup-linux-riscv64-gnu': 4.18.0
|
||||
'@rollup/rollup-linux-s390x-gnu': 4.18.0
|
||||
'@rollup/rollup-linux-x64-gnu': 4.18.0
|
||||
'@rollup/rollup-linux-x64-musl': 4.18.0
|
||||
'@rollup/rollup-win32-arm64-msvc': 4.18.0
|
||||
'@rollup/rollup-win32-ia32-msvc': 4.18.0
|
||||
'@rollup/rollup-win32-x64-msvc': 4.18.0
|
||||
'@rollup/rollup-android-arm-eabi': 4.22.4
|
||||
'@rollup/rollup-android-arm64': 4.22.4
|
||||
'@rollup/rollup-darwin-arm64': 4.22.4
|
||||
'@rollup/rollup-darwin-x64': 4.22.4
|
||||
'@rollup/rollup-linux-arm-gnueabihf': 4.22.4
|
||||
'@rollup/rollup-linux-arm-musleabihf': 4.22.4
|
||||
'@rollup/rollup-linux-arm64-gnu': 4.22.4
|
||||
'@rollup/rollup-linux-arm64-musl': 4.22.4
|
||||
'@rollup/rollup-linux-powerpc64le-gnu': 4.22.4
|
||||
'@rollup/rollup-linux-riscv64-gnu': 4.22.4
|
||||
'@rollup/rollup-linux-s390x-gnu': 4.22.4
|
||||
'@rollup/rollup-linux-x64-gnu': 4.22.4
|
||||
'@rollup/rollup-linux-x64-musl': 4.22.4
|
||||
'@rollup/rollup-win32-arm64-msvc': 4.22.4
|
||||
'@rollup/rollup-win32-ia32-msvc': 4.22.4
|
||||
'@rollup/rollup-win32-x64-msvc': 4.22.4
|
||||
fsevents: 2.3.3
|
||||
|
||||
rsvp@3.2.1: {}
|
||||
@@ -4156,9 +4181,7 @@ snapshots:
|
||||
|
||||
safer-buffer@2.1.2: {}
|
||||
|
||||
scheduler@0.23.2:
|
||||
dependencies:
|
||||
loose-envify: 1.4.0
|
||||
scheduler@0.26.0: {}
|
||||
|
||||
semver@6.3.1: {}
|
||||
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
minimumReleaseAgeExclude:
|
||||
- "@decky/api"
|
||||
- "@decky/ui"
|
||||
+58
-43
@@ -11,48 +11,63 @@ import { visualizer } from 'rollup-plugin-visualizer';
|
||||
|
||||
const hiddenWarnings = ['THIS_IS_UNDEFINED', 'EVAL'];
|
||||
|
||||
export default defineConfig({
|
||||
input: 'src/index.ts',
|
||||
plugins: [
|
||||
del({ targets: '../backend/decky_loader/static/*', force: true }),
|
||||
commonjs(),
|
||||
nodeResolve({
|
||||
browser: true,
|
||||
}),
|
||||
externalGlobals({
|
||||
react: 'SP_REACT',
|
||||
'react-dom': 'SP_REACTDOM',
|
||||
// hack to shut up react-markdown
|
||||
process: '{cwd: () => {}}',
|
||||
path: '{dirname: () => {}, join: () => {}, basename: () => {}, extname: () => {}}',
|
||||
url: '{fileURLToPath: (f) => f}',
|
||||
}),
|
||||
typescript(),
|
||||
json(),
|
||||
replace({
|
||||
preventAssignment: false,
|
||||
'process.env.NODE_ENV': JSON.stringify('production'),
|
||||
}),
|
||||
image(),
|
||||
visualizer(),
|
||||
],
|
||||
preserveEntrySignatures: false,
|
||||
treeshake: {
|
||||
// Assume all external modules have imports with side effects (the default) while allowing decky libraries to treeshake
|
||||
pureExternalImports: true,
|
||||
preset: 'smallest'
|
||||
},
|
||||
output: {
|
||||
dir: '../backend/decky_loader/static',
|
||||
format: 'esm',
|
||||
chunkFileNames: (chunkInfo) => {
|
||||
return 'chunk-[hash].js';
|
||||
export default defineConfig([
|
||||
// Main bundle
|
||||
{
|
||||
input: 'src/index.ts',
|
||||
plugins: [
|
||||
del({ targets: ['../backend/decky_loader/static/*', '!../backend/decky_loader/static/fallback.js'], force: true }),
|
||||
commonjs(),
|
||||
nodeResolve({
|
||||
browser: true,
|
||||
}),
|
||||
externalGlobals({
|
||||
react: 'SP_REACT',
|
||||
'react/jsx-runtime': 'SP_JSX',
|
||||
'react-dom': 'SP_REACTDOM',
|
||||
// hack to shut up react-markdown
|
||||
process: '{cwd: () => {}}',
|
||||
path: '{dirname: () => {}, join: () => {}, basename: () => {}, extname: () => {}}',
|
||||
url: '{fileURLToPath: (f) => f}',
|
||||
}),
|
||||
typescript(),
|
||||
json(),
|
||||
replace({
|
||||
preventAssignment: false,
|
||||
'process.env.NODE_ENV': JSON.stringify('production'),
|
||||
}),
|
||||
image(),
|
||||
visualizer(),
|
||||
],
|
||||
preserveEntrySignatures: false,
|
||||
treeshake: {
|
||||
// Assume all external modules have imports with side effects (the default) while allowing decky libraries to treeshake
|
||||
pureExternalImports: true,
|
||||
preset: 'smallest'
|
||||
},
|
||||
output: {
|
||||
dir: '../backend/decky_loader/static',
|
||||
format: 'esm',
|
||||
chunkFileNames: (chunkInfo) => {
|
||||
return 'chunk-[hash].js';
|
||||
},
|
||||
sourcemap: true,
|
||||
sourcemapPathTransform: (relativeSourcePath) => relativeSourcePath.replace(/^\.\.\//, `decky://decky/loader/`),
|
||||
},
|
||||
onwarn: function (message, handleWarning) {
|
||||
if (hiddenWarnings.some((warning) => message.code === warning)) return;
|
||||
handleWarning(message);
|
||||
},
|
||||
sourcemap: true,
|
||||
sourcemapPathTransform: (relativeSourcePath) => relativeSourcePath.replace(/^\.\.\//, `decky://decky/loader/`),
|
||||
},
|
||||
onwarn: function (message, handleWarning) {
|
||||
if (hiddenWarnings.some((warning) => message.code === warning)) return;
|
||||
handleWarning(message);
|
||||
},
|
||||
});
|
||||
// Fallback
|
||||
{
|
||||
input: 'src/fallback.ts',
|
||||
plugins: [
|
||||
typescript()
|
||||
],
|
||||
output: {
|
||||
file: '../backend/decky_loader/static/fallback.js',
|
||||
format: 'esm',
|
||||
}
|
||||
}
|
||||
]);
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import { sleep } from '@decky/ui';
|
||||
import { joinClassNames, sleep } from '@decky/ui';
|
||||
import { FunctionComponent, useEffect, useReducer, useState } from 'react';
|
||||
|
||||
import { uninstallPlugin } from '../plugin';
|
||||
import { disablePlugin, uninstallPlugin } from '../plugin';
|
||||
import { VerInfo, doRestart, doShutdown } from '../updater';
|
||||
import { ValveReactErrorInfo, getLikelyErrorSourceFromValveReactError } from '../utils/errors';
|
||||
import { useSetting } from '../utils/hooks/useSetting';
|
||||
import { UpdateBranch } from './settings/pages/general/BranchSelect';
|
||||
|
||||
interface DeckyErrorBoundaryProps {
|
||||
error: ValveReactErrorInfo;
|
||||
@@ -18,6 +20,26 @@ declare global {
|
||||
}
|
||||
}
|
||||
|
||||
const classes = {
|
||||
root: 'deckyErrorBoundary',
|
||||
likelyOccurred: 'likely-occured-msg',
|
||||
panel: 'panel-section',
|
||||
panelHeader: 'panel-header',
|
||||
trace: 'trace',
|
||||
rowList: 'row-list',
|
||||
rowItem: 'row-item',
|
||||
buttonDescRow: 'button-description-row',
|
||||
flexRowWGap: 'flex-row',
|
||||
marginBottom: 'margin-bottom',
|
||||
swipePrompt: 'swipe-prompt',
|
||||
};
|
||||
|
||||
const vars = {
|
||||
scrollBarwidth: '18px',
|
||||
rootMarginLeft: '15px',
|
||||
panelXPadding: '20px',
|
||||
};
|
||||
|
||||
export const startSSH = DeckyBackend.callable('utilities/start_ssh');
|
||||
export const starrCEFForwarding = DeckyBackend.callable('utilities/allow_remote_debugging');
|
||||
|
||||
@@ -37,43 +59,155 @@ const DeckyErrorBoundary: FunctionComponent<DeckyErrorBoundaryProps> = ({ error,
|
||||
if (!shouldReportToValve) DeckyPluginLoader.errorBoundaryHook.temporarilyDisableReporting();
|
||||
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 (
|
||||
<>
|
||||
<style>
|
||||
{`
|
||||
*:has(> .deckyErrorBoundary) {
|
||||
*:has(> .${classes.root}) {
|
||||
margin-top: var(--basicui-header-height);
|
||||
overflow: scroll !important;
|
||||
background: #000;
|
||||
}
|
||||
*:has(> .${classes.root})::-webkit-scrollbar {
|
||||
display: initial !important;
|
||||
width: ${vars.scrollBarwidth};
|
||||
height: 0px;
|
||||
}
|
||||
*:has(> .${classes.root})::-webkit-scrollbar-thumb {
|
||||
background: #4349535e;
|
||||
}
|
||||
.${classes.root} {
|
||||
color: #93929e;
|
||||
font-size: 15px;
|
||||
margin: 10px 0px 40px ${vars.rootMarginLeft};
|
||||
overflow: visible;
|
||||
}
|
||||
.${classes.root} button,
|
||||
.${classes.root} select {
|
||||
border: none;
|
||||
padding: 4px 16px !important;
|
||||
background: #333;
|
||||
color: #ddd;
|
||||
font-size: 12px;
|
||||
border-radius: 3px;
|
||||
outline: none;
|
||||
height: 28px;
|
||||
}
|
||||
.${classes.panel} {
|
||||
background: #080808;
|
||||
padding: 8px ${vars.panelXPadding};
|
||||
border-radius: 3px;
|
||||
/* box-shadow: 9px 9px 20px -5px rgb(0 0 0 / 89%); */
|
||||
}
|
||||
.${classes.panelHeader} {
|
||||
font-size: 18px;
|
||||
font-weight: bolder;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
.${classes.likelyOccurred} {
|
||||
font-size: 22px;
|
||||
font-weight: bold;
|
||||
color: #588fb4;
|
||||
}
|
||||
.${classes.rowItem} {
|
||||
position: relative;
|
||||
}
|
||||
.${classes.rowItem}:not(:last-child)::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: -4.5px;
|
||||
left: 5px;
|
||||
right: 15px;
|
||||
height: 0.5px;
|
||||
background: #3c3c3c47;
|
||||
}
|
||||
.${classes.flexRowWGap},
|
||||
.${classes.buttonDescRow},
|
||||
.${classes.rowList},
|
||||
.${classes.panel} {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.${classes.rowList},
|
||||
.${classes.panel} {
|
||||
flex-direction: column;
|
||||
}
|
||||
.${classes.flexRowWGap},
|
||||
.${classes.rowList} {
|
||||
gap: 8px;
|
||||
}
|
||||
.${classes.marginBottom} {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.${classes.buttonDescRow} {
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.${classes.swipePrompt} {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
position: relative;
|
||||
font-style: italic;
|
||||
font-size: small;
|
||||
margin: 16px 0;
|
||||
}
|
||||
.${classes.swipePrompt} span {
|
||||
padding: 0 8px;
|
||||
background-color: #000;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
.${classes.swipePrompt}::before,
|
||||
.${classes.swipePrompt}::after {
|
||||
content: "";
|
||||
flex-grow: 1;
|
||||
border-bottom: 1px solid #474752;
|
||||
top: 50%;
|
||||
}
|
||||
.${classes.swipePrompt}::before {
|
||||
right: 50%;
|
||||
margin-right: 8px;
|
||||
}
|
||||
.${classes.swipePrompt}::after {
|
||||
left: 50%;
|
||||
margin-left: 8px;
|
||||
}
|
||||
`}
|
||||
</style>
|
||||
<div
|
||||
style={{
|
||||
overflow: 'auto',
|
||||
marginLeft: '15px',
|
||||
color: 'white',
|
||||
fontSize: '16px',
|
||||
userSelect: 'auto',
|
||||
backgroundColor: 'black',
|
||||
marginTop: '48px', // Incase this is a page
|
||||
}}
|
||||
className="deckyErrorBoundary"
|
||||
>
|
||||
<h1
|
||||
style={{
|
||||
fontSize: '20px',
|
||||
display: 'inline-block',
|
||||
userSelect: 'auto',
|
||||
}}
|
||||
>
|
||||
⚠️ An error occured while rendering this content.
|
||||
</h1>
|
||||
<pre style={{}}>
|
||||
<div className={classes.root}>
|
||||
<div className={classes.marginBottom}>An error occurred while rendering this content.</div>
|
||||
<pre className={joinClassNames(classes.marginBottom)} style={{ marginTop: '0px' }}>
|
||||
<code>
|
||||
{identifier && `Error Reference: ${identifier}`}
|
||||
{versionInfo?.current && `\nDecky Version: ${versionInfo.current}`}
|
||||
</code>
|
||||
</pre>
|
||||
<p>This error likely occured in {errorSource}.</p>
|
||||
<div className={joinClassNames(classes.likelyOccurred, classes.marginBottom)}>
|
||||
This error likely occurred in {errorSource}.
|
||||
</div>
|
||||
{actionLog?.length > 0 && (
|
||||
<pre>
|
||||
<code>
|
||||
@@ -83,113 +217,225 @@ const DeckyErrorBoundary: FunctionComponent<DeckyErrorBoundaryProps> = ({ error,
|
||||
</pre>
|
||||
)}
|
||||
{actionsEnabled && (
|
||||
<>
|
||||
<h3>Actions: </h3>
|
||||
<p>Use the touch screen.</p>
|
||||
<div style={{ display: 'block', marginBottom: '5px' }}>
|
||||
<button style={{ marginRight: '5px', padding: '5px' }} onClick={reset}>
|
||||
Retry
|
||||
</button>
|
||||
<button
|
||||
style={{ marginRight: '5px', padding: '5px' }}
|
||||
onClick={() => {
|
||||
addLogLine('Restarting Steam...');
|
||||
SteamClient.User.StartRestart();
|
||||
}}
|
||||
>
|
||||
Restart Steam
|
||||
</button>
|
||||
</div>
|
||||
<div style={{ display: 'block', marginBottom: '5px' }}>
|
||||
<button
|
||||
style={{ marginRight: '5px', padding: '5px' }}
|
||||
onClick={async () => {
|
||||
setActionsEnabled(false);
|
||||
addLogLine('Restarting Decky...');
|
||||
doRestart();
|
||||
await sleep(2000);
|
||||
addLogLine('Reloading UI...');
|
||||
}}
|
||||
>
|
||||
Restart Decky
|
||||
</button>
|
||||
<button
|
||||
style={{ marginRight: '5px', padding: '5px' }}
|
||||
onClick={async () => {
|
||||
setActionsEnabled(false);
|
||||
addLogLine('Stopping Decky...');
|
||||
doShutdown();
|
||||
await sleep(5000);
|
||||
addLogLine('Restarting Steam...');
|
||||
SteamClient.User.StartRestart();
|
||||
}}
|
||||
>
|
||||
Disable Decky until next boot
|
||||
</button>
|
||||
</div>
|
||||
{debugAllowed && (
|
||||
<div style={{ display: 'block', marginBottom: '5px' }}>
|
||||
<button
|
||||
style={{ marginRight: '5px', padding: '5px' }}
|
||||
onClick={async () => {
|
||||
setDebugAllowed(false);
|
||||
addLogLine('Enabling CEF debugger forwarding...');
|
||||
await starrCEFForwarding();
|
||||
addLogLine('Enabling SSH...');
|
||||
await startSSH();
|
||||
addLogLine('Ready for debugging!');
|
||||
if (window?.SystemNetworkStore?.wirelessNetworkDevice?.ip4?.addresses?.[0]?.ip) {
|
||||
const ip = ipToString(window.SystemNetworkStore.wirelessNetworkDevice.ip4.addresses[0].ip);
|
||||
addLogLine(`CEF Debugger: http://${ip}:8081`);
|
||||
addLogLine(`SSH: deck@${ip}`);
|
||||
}
|
||||
}}
|
||||
>
|
||||
Allow remote debugging and SSH until next boot
|
||||
</button>
|
||||
<div className={classes.panel}>
|
||||
<div className={classes.flexRowWGap} style={{ alignItems: 'center', marginBottom: '8px' }}>
|
||||
<div className={classes.panelHeader}>Actions</div>
|
||||
<div style={{ fontSize: 'small', fontStyle: 'italic' }}>
|
||||
Use the touch screen. Solutions are listed in the recommended order. If you are still experiencing
|
||||
issues, please post in the #loader-support channel at decky.xyz/discord.
|
||||
</div>
|
||||
)}
|
||||
{wasCausedByPlugin && (
|
||||
<div style={{ display: 'block', marginBottom: '5px' }}>
|
||||
{'\n'}
|
||||
</div>
|
||||
<div className={classes.rowList}>
|
||||
<div className={joinClassNames(classes.rowItem, classes.buttonDescRow)}>
|
||||
Retry the action or restart
|
||||
<div className={classes.flexRowWGap}>
|
||||
<button onClick={reset}>Retry</button>
|
||||
<button
|
||||
onClick={() => {
|
||||
addLogLine('Restarting Steam...');
|
||||
SteamClient.User.StartRestart(false);
|
||||
}}
|
||||
>
|
||||
Restart Steam
|
||||
</button>
|
||||
<button
|
||||
onClick={async () => {
|
||||
setActionsEnabled(false);
|
||||
addLogLine('Restarting Decky...');
|
||||
doRestart();
|
||||
await sleep(2000);
|
||||
addLogLine('Reloading UI...');
|
||||
}}
|
||||
>
|
||||
Restart Decky
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{wasCausedByPlugin && (
|
||||
<div className={joinClassNames(classes.rowItem, classes.buttonDescRow)}>
|
||||
Disable or uninstall the suspected plugin
|
||||
<div className={classes.flexRowWGap}>
|
||||
<button
|
||||
onClick={async () => {
|
||||
setActionsEnabled(false);
|
||||
addLogLine(`Disabling ${errorSource}...`);
|
||||
await disablePlugin(errorSource);
|
||||
await sleep(1000);
|
||||
addLogLine('Restarting Decky...');
|
||||
doRestart();
|
||||
await sleep(2000);
|
||||
addLogLine('Restarting Steam...');
|
||||
await sleep(500);
|
||||
SteamClient.User.StartRestart(false);
|
||||
}}
|
||||
>
|
||||
Disable {errorSource}
|
||||
</button>
|
||||
<button
|
||||
onClick={async () => {
|
||||
setActionsEnabled(false);
|
||||
addLogLine(`Uninstalling ${errorSource}...`);
|
||||
await uninstallPlugin(errorSource);
|
||||
await DeckyPluginLoader.frozenPluginsService.invalidate();
|
||||
await DeckyPluginLoader.hiddenPluginsService.invalidate();
|
||||
await sleep(1000);
|
||||
addLogLine('Restarting Decky...');
|
||||
doRestart();
|
||||
await sleep(2000);
|
||||
addLogLine('Restarting Steam...');
|
||||
await sleep(500);
|
||||
SteamClient.User.StartRestart(false);
|
||||
}}
|
||||
>
|
||||
Uninstall {errorSource}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<div className={joinClassNames(classes.rowItem, classes.buttonDescRow)}>
|
||||
Disable all plugins
|
||||
<button
|
||||
style={{ marginRight: '5px', padding: '5px' }}
|
||||
onClick={async () => {
|
||||
setActionsEnabled(false);
|
||||
addLogLine(`Uninstalling ${errorSource}...`);
|
||||
await uninstallPlugin(errorSource);
|
||||
await DeckyPluginLoader.frozenPluginsService.invalidate();
|
||||
await DeckyPluginLoader.hiddenPluginsService.invalidate();
|
||||
addLogLine(`Disabling plugins...`);
|
||||
await DeckyBackend.call('utilities/set_all_plugins_disabled');
|
||||
await sleep(1000);
|
||||
addLogLine('Restarting Decky...');
|
||||
doRestart();
|
||||
await sleep(2000);
|
||||
addLogLine('Restarting Steam...');
|
||||
await sleep(500);
|
||||
SteamClient.User.StartRestart();
|
||||
SteamClient.User.StartRestart(false);
|
||||
}}
|
||||
>
|
||||
Uninstall {errorSource} and restart Decky
|
||||
Disable All Plugins
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
{
|
||||
<div className={joinClassNames(classes.rowItem, classes.buttonDescRow)}>
|
||||
{updateProgress > -1
|
||||
? 'Update in progress... ' + updateProgress + '%'
|
||||
: updateProgress == -2
|
||||
? 'Update complete. Restarting...'
|
||||
: 'Check for Decky updates'}
|
||||
{
|
||||
<div className={classes.flexRowWGap}>
|
||||
{updateProgress == -1 && (
|
||||
<>
|
||||
<select
|
||||
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
|
||||
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>
|
||||
}
|
||||
<div className={joinClassNames(classes.rowItem, classes.buttonDescRow)}>
|
||||
Disable Decky until next boot
|
||||
<button
|
||||
onClick={async () => {
|
||||
setActionsEnabled(false);
|
||||
addLogLine('Stopping Decky...');
|
||||
doShutdown();
|
||||
await sleep(5000);
|
||||
addLogLine('Restarting Steam...');
|
||||
SteamClient.User.StartRestart(false);
|
||||
}}
|
||||
>
|
||||
Disable Decky
|
||||
</button>
|
||||
</div>
|
||||
{debugAllowed && (
|
||||
<div className={joinClassNames(classes.rowItem, classes.buttonDescRow)}>
|
||||
Enable remote debugging and SSH until next boot (for developers)
|
||||
<button
|
||||
onClick={async () => {
|
||||
setDebugAllowed(false);
|
||||
addLogLine('Enabling CEF debugger forwarding...');
|
||||
await starrCEFForwarding();
|
||||
addLogLine('Enabling SSH...');
|
||||
await startSSH();
|
||||
addLogLine('Ready for debugging!');
|
||||
if (window?.SystemNetworkStore?.wirelessNetworkDevice?.ip4?.addresses?.[0]?.ip) {
|
||||
const ip = ipToString(window.SystemNetworkStore.wirelessNetworkDevice.ip4.addresses[0].ip);
|
||||
addLogLine(`CEF Debugger: http://${ip}:8081`);
|
||||
addLogLine(`SSH: deck@${ip}`);
|
||||
}
|
||||
}}
|
||||
>
|
||||
Enable
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<pre
|
||||
style={{
|
||||
marginTop: '15px',
|
||||
opacity: 0.7,
|
||||
userSelect: 'auto',
|
||||
}}
|
||||
>
|
||||
<code>
|
||||
{error.error.stack}
|
||||
{'\n\n'}
|
||||
Component Stack:
|
||||
{error.info.componentStack}
|
||||
</code>
|
||||
</pre>
|
||||
{actionsEnabled && (
|
||||
<div className={classes.swipePrompt}>
|
||||
<span>Swipe to scroll</span>
|
||||
</div>
|
||||
)}
|
||||
<div className={classes.panel}>
|
||||
<div className={classes.panelHeader}>Trace</div>
|
||||
<pre
|
||||
style={{
|
||||
margin: `8px calc(-1 * ${vars.panelXPadding})`,
|
||||
userSelect: 'auto',
|
||||
overflowX: 'scroll',
|
||||
padding: `0px ${vars.panelXPadding}`,
|
||||
maskImage: `linear-gradient(to right, transparent, black ${vars.panelXPadding}, black calc(100% - ${vars.panelXPadding}), transparent)`,
|
||||
}}
|
||||
>
|
||||
<code>
|
||||
{error.error.stack}
|
||||
{'\n\n'}
|
||||
Component Stack:
|
||||
{error.info.componentStack}
|
||||
</code>
|
||||
</pre>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -1,37 +1,39 @@
|
||||
export default function DeckyIcon() {
|
||||
return (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 456" width="512" height="456">
|
||||
<g>
|
||||
<path
|
||||
style={{ fill: 'none' }}
|
||||
d="M154.33,72.51v49.79c11.78-0.17,23.48,2,34.42,6.39c10.93,4.39,20.89,10.91,29.28,19.18
|
||||
c8.39,8.27,15.06,18.13,19.61,29c4.55,10.87,6.89,22.54,6.89,34.32c0,11.78-2.34,23.45-6.89,34.32
|
||||
c-4.55,10.87-11.21,20.73-19.61,29c-8.39,8.27-18.35,14.79-29.28,19.18c-10.94,4.39-22.63,6.56-34.42,6.39v49.77
|
||||
c36.78,0,72.05-14.61,98.05-40.62c26-26.01,40.61-61.28,40.61-98.05c0-36.78-14.61-72.05-40.61-98.05
|
||||
C226.38,87.12,191.11,72.51,154.33,72.51z"
|
||||
/>
|
||||
import { FC, SVGAttributes } from 'react';
|
||||
|
||||
<ellipse
|
||||
transform="matrix(0.982 -0.1891 0.1891 0.982 -37.1795 32.9988)"
|
||||
style={{ fill: 'none' }}
|
||||
cx="154.33"
|
||||
cy="211.33"
|
||||
rx="69.33"
|
||||
ry="69.33"
|
||||
/>
|
||||
<path style={{ fill: 'none' }} d="M430,97h-52v187h52c7.18,0,13-5.82,13-13V110C443,102.82,437.18,97,430,97z" />
|
||||
<path
|
||||
style={{ fill: 'currentColor' }}
|
||||
d="M432,27h-54V0H0v361c0,52.47,42.53,95,95,95h188c52.47,0,95-42.53,95-95v-7h54c44.18,0,80-35.82,80-80V107
|
||||
C512,62.82,476.18,27,432,27z M85,211.33c0-38.29,31.04-69.33,69.33-69.33c38.29,0,69.33,31.04,69.33,69.33
|
||||
c0,38.29-31.04,69.33-69.33,69.33C116.04,280.67,85,249.62,85,211.33z M252.39,309.23c-26.01,26-61.28,40.62-98.05,40.62v-49.77
|
||||
c11.78,0.17,23.48-2,34.42-6.39c10.93-4.39,20.89-10.91,29.28-19.18c8.39-8.27,15.06-18.13,19.61-29
|
||||
c4.55-10.87,6.89-22.53,6.89-34.32c0-11.78-2.34-23.45-6.89-34.32c-4.55-10.87-11.21-20.73-19.61-29
|
||||
c-8.39-8.27-18.35-14.79-29.28-19.18c-10.94-4.39-22.63-6.56-34.42-6.39V72.51c36.78,0,72.05,14.61,98.05,40.61
|
||||
c26,26.01,40.61,61.28,40.61,98.05C293,247.96,278.39,283.23,252.39,309.23z M443,271c0,7.18-5.82,13-13,13h-52V97h52
|
||||
c7.18,0,13,5.82,13,13V271z"
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
const DeckyIcon: FC<SVGAttributes<SVGElement>> = (props) => (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="100%" width="100%" viewBox="0 0 512 456" {...props}>
|
||||
<g>
|
||||
<path
|
||||
style={{ fill: 'none' }}
|
||||
d="M154.33,72.51v49.79c11.78-0.17,23.48,2,34.42,6.39c10.93,4.39,20.89,10.91,29.28,19.18
|
||||
c8.39,8.27,15.06,18.13,19.61,29c4.55,10.87,6.89,22.54,6.89,34.32c0,11.78-2.34,23.45-6.89,34.32
|
||||
c-4.55,10.87-11.21,20.73-19.61,29c-8.39,8.27-18.35,14.79-29.28,19.18c-10.94,4.39-22.63,6.56-34.42,6.39v49.77
|
||||
c36.78,0,72.05-14.61,98.05-40.62c26-26.01,40.61-61.28,40.61-98.05c0-36.78-14.61-72.05-40.61-98.05
|
||||
C226.38,87.12,191.11,72.51,154.33,72.51z"
|
||||
/>
|
||||
|
||||
<ellipse
|
||||
transform="matrix(0.982 -0.1891 0.1891 0.982 -37.1795 32.9988)"
|
||||
style={{ fill: 'none' }}
|
||||
cx="154.33"
|
||||
cy="211.33"
|
||||
rx="69.33"
|
||||
ry="69.33"
|
||||
/>
|
||||
<path style={{ fill: 'none' }} d="M430,97h-52v187h52c7.18,0,13-5.82,13-13V110C443,102.82,437.18,97,430,97z" />
|
||||
<path
|
||||
style={{ fill: 'currentColor' }}
|
||||
d="M432,27h-54V0H0v361c0,52.47,42.53,95,95,95h188c52.47,0,95-42.53,95-95v-7h54c44.18,0,80-35.82,80-80V107
|
||||
C512,62.82,476.18,27,432,27z M85,211.33c0-38.29,31.04-69.33,69.33-69.33c38.29,0,69.33,31.04,69.33,69.33
|
||||
c0,38.29-31.04,69.33-69.33,69.33C116.04,280.67,85,249.62,85,211.33z M252.39,309.23c-26.01,26-61.28,40.62-98.05,40.62v-49.77
|
||||
c11.78,0.17,23.48-2,34.42-6.39c10.93-4.39,20.89-10.91,29.28-19.18c8.39-8.27,15.06-18.13,19.61-29
|
||||
c4.55-10.87,6.89-22.53,6.89-34.32c0-11.78-2.34-23.45-6.89-34.32c-4.55-10.87-11.21-20.73-19.61-29
|
||||
c-8.39-8.27-18.35-14.79-29.28-19.18c-10.94-4.39-22.63-6.56-34.42-6.39V72.51c36.78,0,72.05,14.61,98.05,40.61
|
||||
c26,26.01,40.61,61.28,40.61,98.05C293,247.96,278.39,283.23,252.39,309.23z M443,271c0,7.18-5.82,13-13,13h-52V97h52
|
||||
c7.18,0,13,5.82,13,13V271z"
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
);
|
||||
|
||||
export default DeckyIcon;
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
import { FC, ReactNode, createContext, useContext, useEffect, useState } from 'react';
|
||||
|
||||
import { DEFAULT_NOTIFICATION_SETTINGS, NotificationSettings } from '../notification-service';
|
||||
import { Plugin } from '../plugin';
|
||||
import { DisabledPlugin, Plugin } from '../plugin';
|
||||
import { PluginUpdateMapping } from '../store';
|
||||
import { VerInfo } from '../updater';
|
||||
|
||||
interface PublicDeckyState {
|
||||
plugins: Plugin[];
|
||||
disabledPlugins: DisabledPlugin[];
|
||||
installedPlugins: (Plugin | DisabledPlugin)[];
|
||||
pluginOrder: string[];
|
||||
frozenPlugins: string[];
|
||||
hiddenPlugins: string[];
|
||||
@@ -26,6 +28,8 @@ export interface UserInfo {
|
||||
|
||||
export class DeckyState {
|
||||
private _plugins: Plugin[] = [];
|
||||
private _disabledPlugins: DisabledPlugin[] = [];
|
||||
private _installedPlugins: (Plugin | DisabledPlugin)[] = [];
|
||||
private _pluginOrder: string[] = [];
|
||||
private _frozenPlugins: string[] = [];
|
||||
private _hiddenPlugins: string[] = [];
|
||||
@@ -42,6 +46,8 @@ export class DeckyState {
|
||||
publicState(): PublicDeckyState {
|
||||
return {
|
||||
plugins: this._plugins,
|
||||
disabledPlugins: this._disabledPlugins,
|
||||
installedPlugins: this._installedPlugins,
|
||||
pluginOrder: this._pluginOrder,
|
||||
frozenPlugins: this._frozenPlugins,
|
||||
hiddenPlugins: this._hiddenPlugins,
|
||||
@@ -62,6 +68,13 @@ export class DeckyState {
|
||||
|
||||
setPlugins(plugins: Plugin[]) {
|
||||
this._plugins = plugins;
|
||||
this._installedPlugins = [...plugins, ...this._disabledPlugins];
|
||||
this.notifyUpdate();
|
||||
}
|
||||
|
||||
setDisabledPlugins(disabledPlugins: DisabledPlugin[]) {
|
||||
this._disabledPlugins = disabledPlugins;
|
||||
this._installedPlugins = [...this._plugins, ...disabledPlugins];
|
||||
this.notifyUpdate();
|
||||
}
|
||||
|
||||
@@ -125,12 +138,21 @@ interface DeckyStateContext extends PublicDeckyState {
|
||||
setIsLoaderUpdating(hasUpdate: boolean): void;
|
||||
setActivePlugin(name: string): void;
|
||||
setPluginOrder(pluginOrder: string[]): void;
|
||||
setDisabledPlugins(disabled: DisabledPlugin[]): void;
|
||||
closeActivePlugin(): void;
|
||||
}
|
||||
|
||||
const DeckyStateContext = createContext<DeckyStateContext>(null as any);
|
||||
const DeckyStateContext = createContext<DeckyStateContext | null>(null);
|
||||
|
||||
export const useDeckyState = () => useContext(DeckyStateContext);
|
||||
export const useDeckyState = () => {
|
||||
const deckyState = useContext(DeckyStateContext);
|
||||
|
||||
if (deckyState === null) {
|
||||
throw new Error('useDeckyState needs a parent DeckyStateContext');
|
||||
}
|
||||
|
||||
return deckyState;
|
||||
};
|
||||
|
||||
interface Props {
|
||||
deckyState: DeckyState;
|
||||
@@ -155,6 +177,7 @@ export const DeckyStateContextProvider: FC<Props> = ({ children, deckyState }) =
|
||||
const setActivePlugin = deckyState.setActivePlugin.bind(deckyState);
|
||||
const closeActivePlugin = deckyState.closeActivePlugin.bind(deckyState);
|
||||
const setPluginOrder = deckyState.setPluginOrder.bind(deckyState);
|
||||
const setDisabledPlugins = deckyState.setDisabledPlugins.bind(deckyState);
|
||||
|
||||
return (
|
||||
<DeckyStateContext.Provider
|
||||
@@ -165,6 +188,7 @@ export const DeckyStateContextProvider: FC<Props> = ({ children, deckyState }) =
|
||||
setActivePlugin,
|
||||
closeActivePlugin,
|
||||
setPluginOrder,
|
||||
setDisabledPlugins,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
|
||||
@@ -1,57 +0,0 @@
|
||||
import type { ToastData } from '@decky/api';
|
||||
import { joinClassNames } from '@decky/ui';
|
||||
import { FC, ReactElement, useEffect, useState } from 'react';
|
||||
|
||||
import { useDeckyToasterState } from './DeckyToasterState';
|
||||
import Toast, { toastClasses } from './Toast';
|
||||
|
||||
interface DeckyToasterProps {}
|
||||
|
||||
interface RenderedToast {
|
||||
component: ReactElement;
|
||||
data: ToastData;
|
||||
}
|
||||
|
||||
const DeckyToaster: FC<DeckyToasterProps> = () => {
|
||||
const { toasts, removeToast } = useDeckyToasterState();
|
||||
const [renderedToast, setRenderedToast] = useState<RenderedToast | null>(null);
|
||||
console.log(toasts);
|
||||
if (toasts.size > 0) {
|
||||
const [activeToast] = toasts;
|
||||
if (!renderedToast || activeToast != renderedToast.data) {
|
||||
// TODO play toast soundReactElement
|
||||
console.log('rendering toast', activeToast);
|
||||
setRenderedToast({ component: <Toast key={Math.random()} toast={activeToast} />, data: activeToast });
|
||||
}
|
||||
} else {
|
||||
if (renderedToast) setRenderedToast(null);
|
||||
}
|
||||
useEffect(() => {
|
||||
// not actually node but TS is shit
|
||||
let interval: number | null;
|
||||
if (renderedToast) {
|
||||
interval = setTimeout(
|
||||
() => {
|
||||
interval = null;
|
||||
console.log('clear toast', renderedToast.data);
|
||||
removeToast(renderedToast.data);
|
||||
},
|
||||
(renderedToast.data.duration || 5e3) + 1000,
|
||||
);
|
||||
console.log('set int', interval);
|
||||
}
|
||||
return () => {
|
||||
if (interval) {
|
||||
console.log('clearing int', interval);
|
||||
clearTimeout(interval);
|
||||
}
|
||||
};
|
||||
}, [renderedToast]);
|
||||
return (
|
||||
<div className={joinClassNames('deckyToaster', toastClasses.ToastPlaceholder)}>
|
||||
{renderedToast && renderedToast.component}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default DeckyToaster;
|
||||
@@ -1,69 +0,0 @@
|
||||
import type { ToastData } from '@decky/api';
|
||||
import { FC, ReactNode, createContext, useContext, useEffect, useState } from 'react';
|
||||
|
||||
interface PublicDeckyToasterState {
|
||||
toasts: Set<ToastData>;
|
||||
}
|
||||
|
||||
export class DeckyToasterState {
|
||||
private _toasts: Set<ToastData> = new Set();
|
||||
|
||||
public eventBus = new EventTarget();
|
||||
|
||||
publicState(): PublicDeckyToasterState {
|
||||
return { toasts: this._toasts };
|
||||
}
|
||||
|
||||
addToast(toast: ToastData) {
|
||||
this._toasts.add(toast);
|
||||
this.notifyUpdate();
|
||||
}
|
||||
|
||||
removeToast(toast: ToastData) {
|
||||
this._toasts.delete(toast);
|
||||
this.notifyUpdate();
|
||||
}
|
||||
|
||||
private notifyUpdate() {
|
||||
this.eventBus.dispatchEvent(new Event('update'));
|
||||
}
|
||||
}
|
||||
|
||||
interface DeckyToasterContext extends PublicDeckyToasterState {
|
||||
addToast(toast: ToastData): void;
|
||||
removeToast(toast: ToastData): void;
|
||||
}
|
||||
|
||||
const DeckyToasterContext = createContext<DeckyToasterContext>(null as any);
|
||||
|
||||
export const useDeckyToasterState = () => useContext(DeckyToasterContext);
|
||||
|
||||
interface Props {
|
||||
deckyToasterState: DeckyToasterState;
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
export const DeckyToasterStateContextProvider: FC<Props> = ({ children, deckyToasterState }) => {
|
||||
const [publicDeckyToasterState, setPublicDeckyToasterState] = useState<PublicDeckyToasterState>({
|
||||
...deckyToasterState.publicState(),
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
function onUpdate() {
|
||||
setPublicDeckyToasterState({ ...deckyToasterState.publicState() });
|
||||
}
|
||||
|
||||
deckyToasterState.eventBus.addEventListener('update', onUpdate);
|
||||
|
||||
return () => deckyToasterState.eventBus.removeEventListener('update', onUpdate);
|
||||
}, []);
|
||||
|
||||
const addToast = deckyToasterState.addToast.bind(deckyToasterState);
|
||||
const removeToast = deckyToasterState.removeToast.bind(deckyToasterState);
|
||||
|
||||
return (
|
||||
<DeckyToasterContext.Provider value={{ ...publicDeckyToasterState, addToast, removeToast }}>
|
||||
{children}
|
||||
</DeckyToasterContext.Provider>
|
||||
);
|
||||
};
|
||||
@@ -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;
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Focusable, Navigation } from '@decky/ui';
|
||||
import { Focusable, Navigation, findClass, findClassByName } from '@decky/ui';
|
||||
import { FunctionComponent, useRef } from 'react';
|
||||
import ReactMarkdown, { Options as ReactMarkdownOptions } from 'react-markdown';
|
||||
import remarkGfm from 'remark-gfm';
|
||||
@@ -8,6 +8,9 @@ interface MarkdownProps extends ReactMarkdownOptions {
|
||||
}
|
||||
|
||||
const Markdown: FunctionComponent<MarkdownProps> = (props) => {
|
||||
const eventDetailsBodyClassName = findClassByName('EventDetailsBody') || undefined;
|
||||
const eventLinkClassName = findClass('43088', 'Link');
|
||||
|
||||
return (
|
||||
<Focusable>
|
||||
<ReactMarkdown
|
||||
@@ -25,8 +28,10 @@ const Markdown: FunctionComponent<MarkdownProps> = (props) => {
|
||||
Navigation.NavigateToExternalWeb(aRef.current!.href);
|
||||
}}
|
||||
style={{ display: 'inline' }}
|
||||
focusClassName="steam-focus"
|
||||
className={eventDetailsBodyClassName}
|
||||
>
|
||||
<a ref={aRef} {...nodeProps.node.properties}>
|
||||
<a ref={aRef} {...nodeProps.node.properties} className={eventLinkClassName}>
|
||||
{nodeProps.children}
|
||||
</a>
|
||||
</Focusable>
|
||||
|
||||
@@ -1,28 +1,37 @@
|
||||
import { ButtonItem, ErrorBoundary, Focusable, PanelSection, PanelSectionRow } from '@decky/ui';
|
||||
import { FC, useEffect, useState } from 'react';
|
||||
import { FC, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { FaEyeSlash } from 'react-icons/fa';
|
||||
import { FaBan, FaEyeSlash } from 'react-icons/fa';
|
||||
|
||||
import { Plugin } from '../plugin';
|
||||
import { useDeckyState } from './DeckyState';
|
||||
import NotificationBadge from './NotificationBadge';
|
||||
import { useQuickAccessVisible } from './QuickAccessVisibleState';
|
||||
import TitleView from './TitleView';
|
||||
|
||||
const PluginView: FC = () => {
|
||||
const { hiddenPlugins } = useDeckyState();
|
||||
const { plugins, updates, activePlugin, pluginOrder, setActivePlugin, closeActivePlugin } = useDeckyState();
|
||||
const {
|
||||
plugins,
|
||||
disabledPlugins,
|
||||
hiddenPlugins,
|
||||
updates,
|
||||
activePlugin,
|
||||
pluginOrder,
|
||||
setActivePlugin,
|
||||
closeActivePlugin,
|
||||
} = useDeckyState();
|
||||
const visible = useQuickAccessVisible();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const [pluginList, setPluginList] = useState<Plugin[]>(
|
||||
plugins.sort((a, b) => pluginOrder.indexOf(a.name) - pluginOrder.indexOf(b.name)),
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
setPluginList(plugins.sort((a, b) => pluginOrder.indexOf(a.name) - pluginOrder.indexOf(b.name)));
|
||||
const pluginList = useMemo(() => {
|
||||
console.log('updating PluginView after changes');
|
||||
}, [plugins, pluginOrder]);
|
||||
|
||||
return [...plugins]
|
||||
.sort((a, b) => pluginOrder.indexOf(a.name) - pluginOrder.indexOf(b.name))
|
||||
.filter((p) => p.content)
|
||||
.filter(({ name }) => !hiddenPlugins.includes(name));
|
||||
}, [plugins, pluginOrder, hiddenPlugins]);
|
||||
|
||||
const numberOfHidden = hiddenPlugins.filter((name) => !!plugins.find((p) => p.name === name)).length;
|
||||
|
||||
if (activePlugin) {
|
||||
return (
|
||||
@@ -43,26 +52,39 @@ const PluginView: FC = () => {
|
||||
}}
|
||||
>
|
||||
<PanelSection>
|
||||
{pluginList
|
||||
.filter((p) => p.content)
|
||||
.filter(({ name }) => !hiddenPlugins.includes(name))
|
||||
.map(({ name, icon }) => (
|
||||
<PanelSectionRow key={name}>
|
||||
<ButtonItem layout="below" onClick={() => setActivePlugin(name)}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
|
||||
{icon}
|
||||
<div>{name}</div>
|
||||
<NotificationBadge show={updates?.has(name)} style={{ top: '-5px', right: '-5px' }} />
|
||||
</div>
|
||||
</ButtonItem>
|
||||
</PanelSectionRow>
|
||||
))}
|
||||
{hiddenPlugins.length > 0 && (
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: '10px', fontSize: '0.8rem', marginTop: '10px' }}>
|
||||
<FaEyeSlash />
|
||||
<div>{t('PluginView.hidden', { count: hiddenPlugins.length })}</div>
|
||||
</div>
|
||||
)}
|
||||
{pluginList.map(({ name, icon }) => (
|
||||
<PanelSectionRow key={name}>
|
||||
<ButtonItem layout="below" onClick={() => setActivePlugin(name)}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
|
||||
{icon}
|
||||
<div>{name}</div>
|
||||
<NotificationBadge show={updates?.has(name)} style={{ top: '-5px', right: '-5px' }} />
|
||||
</div>
|
||||
</ButtonItem>
|
||||
</PanelSectionRow>
|
||||
))}
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
position: 'absolute',
|
||||
justifyContent: 'center',
|
||||
padding: '5px 0px',
|
||||
}}
|
||||
>
|
||||
{numberOfHidden > 0 && (
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: '10px', fontSize: '0.8rem' }}>
|
||||
<FaEyeSlash />
|
||||
<div>{t('PluginView.hidden', { count: numberOfHidden })}</div>
|
||||
</div>
|
||||
)}
|
||||
{disabledPlugins.length > 0 && (
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: '10px', fontSize: '0.8rem' }}>
|
||||
<FaBan />
|
||||
<div>{t('PluginView.disabled', { count: disabledPlugins.length })}</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</PanelSection>
|
||||
</div>
|
||||
</>
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { FC, ReactNode, createContext, useContext, useState } from 'react';
|
||||
import { FC, PropsWithChildren, createContext, useContext, useState } from 'react';
|
||||
|
||||
const QuickAccessVisibleState = createContext<boolean>(false);
|
||||
|
||||
export const useQuickAccessVisible = () => useContext(QuickAccessVisibleState);
|
||||
|
||||
export const QuickAccessVisibleStateProvider: FC<{ tab: any; children: ReactNode }> = ({ children, tab }) => {
|
||||
export const QuickAccessVisibleStateProvider: FC<PropsWithChildren<{ tab: any }>> = ({ children, tab }) => {
|
||||
const initial = tab.initialVisibility;
|
||||
const [visible, setVisible] = useState<boolean>(initial);
|
||||
// HACK but i can't think of a better way to do this
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { DialogButton, Focusable, Router, staticClasses } from '@decky/ui';
|
||||
import { DialogButton, Focusable, Navigation, staticClasses } from '@decky/ui';
|
||||
import { CSSProperties, FC } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { BsGearFill } from 'react-icons/bs';
|
||||
@@ -8,7 +8,6 @@ import { useDeckyState } from './DeckyState';
|
||||
|
||||
const titleStyles: CSSProperties = {
|
||||
display: 'flex',
|
||||
paddingTop: '3px',
|
||||
paddingRight: '16px',
|
||||
position: 'sticky',
|
||||
top: '0px',
|
||||
@@ -19,13 +18,13 @@ const TitleView: FC = () => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const onSettingsClick = () => {
|
||||
Router.CloseSideMenus();
|
||||
Router.Navigate('/decky/settings');
|
||||
Navigation.Navigate('/decky/settings');
|
||||
Navigation.CloseSideMenus();
|
||||
};
|
||||
|
||||
const onStoreClick = () => {
|
||||
Router.CloseSideMenus();
|
||||
Router.Navigate('/decky/store');
|
||||
Navigation.Navigate('/decky/store');
|
||||
Navigation.CloseSideMenus();
|
||||
};
|
||||
|
||||
if (activePlugin === null) {
|
||||
|
||||
@@ -1,37 +1,38 @@
|
||||
import type { ToastData } from '@decky/api';
|
||||
import { findModule, joinClassNames } from '@decky/ui';
|
||||
import { FunctionComponent } from 'react';
|
||||
import { Focusable, Navigation, findClassModule, joinClassNames } from '@decky/ui';
|
||||
import { FC, memo } from 'react';
|
||||
|
||||
import Logger from '../logger';
|
||||
|
||||
const logger = new Logger('ToastRenderer');
|
||||
|
||||
// TODO there are more of these
|
||||
export enum ToastLocation {
|
||||
/** Big Picture popup toasts */
|
||||
GAMEPADUI_POPUP = 1,
|
||||
/** QAM Notifications tab */
|
||||
GAMEPADUI_QAM = 3,
|
||||
}
|
||||
|
||||
interface ToastProps {
|
||||
toast: ToastData;
|
||||
newIndicator?: boolean;
|
||||
}
|
||||
|
||||
export const toastClasses = findModule((mod) => {
|
||||
if (typeof mod !== 'object') return false;
|
||||
interface ToastRendererProps extends ToastProps {
|
||||
location: ToastLocation;
|
||||
}
|
||||
|
||||
if (mod.ToastPlaceholder) {
|
||||
return true;
|
||||
}
|
||||
const templateClasses = findClassModule((m) => m.ShortTemplate) || {};
|
||||
|
||||
return false;
|
||||
});
|
||||
// These are memoized as they like to randomly rerender
|
||||
|
||||
const templateClasses = findModule((mod) => {
|
||||
if (typeof mod !== 'object') return false;
|
||||
|
||||
if (mod.ShortTemplate) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
const Toast: FunctionComponent<ToastProps> = ({ toast }) => {
|
||||
const GamepadUIPopupToast: FC<Omit<ToastProps, 'newIndicator'>> = memo(({ toast }) => {
|
||||
return (
|
||||
<div
|
||||
style={{ '--toast-duration': `${toast.duration}ms` } as React.CSSProperties}
|
||||
onClick={toast.onClick}
|
||||
className={joinClassNames(templateClasses.ShortTemplate, toast.className || '')}
|
||||
className={joinClassNames(templateClasses.ShortTemplate, toast.className || '', 'DeckyGamepadUIPopupToast')}
|
||||
>
|
||||
{toast.logo && <div className={templateClasses.StandardLogoDimensions}>{toast.logo}</div>}
|
||||
<div className={joinClassNames(templateClasses.Content, toast.contentClassName || '')}>
|
||||
@@ -43,6 +44,61 @@ const Toast: FunctionComponent<ToastProps> = ({ toast }) => {
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
});
|
||||
|
||||
export default Toast;
|
||||
const GamepadUIQAMToast: FC<ToastProps> = memo(({ toast, newIndicator }) => {
|
||||
// The fields aren't mismatched, the logic for these is just a bit weird.
|
||||
return (
|
||||
<Focusable
|
||||
onActivate={() => {
|
||||
toast.onClick?.();
|
||||
Navigation.CloseSideMenus();
|
||||
}}
|
||||
className={joinClassNames(
|
||||
templateClasses.StandardTemplateContainer,
|
||||
toast.className || '',
|
||||
'DeckyGamepadUIQAMToast',
|
||||
)}
|
||||
>
|
||||
<div className={templateClasses.StandardTemplate}>
|
||||
{toast.logo && <div className={templateClasses.StandardLogoDimensions}>{toast.logo}</div>}
|
||||
<div className={joinClassNames(templateClasses.Content, toast.contentClassName || '')}>
|
||||
<div className={templateClasses.Header}>
|
||||
{toast.icon && <div className={templateClasses.Icon}>{toast.icon}</div>}
|
||||
{toast.title && <div className={templateClasses.Title}>{toast.title}</div>}
|
||||
{/* timestamp should always be defined by toaster */}
|
||||
{/* TODO check how valve does this */}
|
||||
{toast.timestamp && (
|
||||
<div className={templateClasses.Timestamp}>
|
||||
{toast.timestamp.toLocaleTimeString(undefined, { timeStyle: 'short' })}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{toast.body && <div className={templateClasses.StandardNotificationDescription}>{toast.body}</div>}
|
||||
{toast.subtext && <div className={templateClasses.StandardNotificationSubText}>{toast.subtext}</div>}
|
||||
</div>
|
||||
{newIndicator && (
|
||||
<div className={templateClasses.NewIndicator}>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="50" height="50" viewBox="0 0 50 50" fill="none">
|
||||
<circle fill="currentColor" cx="25" cy="25" r="25"></circle>
|
||||
</svg>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</Focusable>
|
||||
);
|
||||
});
|
||||
|
||||
export const ToastRenderer: FC<ToastRendererProps> = memo(({ toast, location, newIndicator }) => {
|
||||
switch (location) {
|
||||
default:
|
||||
logger.warn(`Toast UI not implemented for location ${location}! Falling back to GamepadUIQAMToast.`);
|
||||
return <GamepadUIQAMToast toast={toast} newIndicator={false} />;
|
||||
case ToastLocation.GAMEPADUI_POPUP:
|
||||
return <GamepadUIPopupToast toast={toast} />;
|
||||
case ToastLocation.GAMEPADUI_QAM:
|
||||
return <GamepadUIQAMToast toast={toast} newIndicator={newIndicator} />;
|
||||
}
|
||||
});
|
||||
|
||||
export default ToastRenderer;
|
||||
|
||||
@@ -10,7 +10,7 @@ interface WithSuspenseProps {
|
||||
const WithSuspense: FunctionComponent<WithSuspenseProps> = (props) => {
|
||||
const propsCopy = { ...props };
|
||||
delete propsCopy.children;
|
||||
(props.children as ReactElement)?.props && Object.assign((props.children as ReactElement).props, propsCopy); // There is probably a better way to do this but valve does it this way so ¯\_(ツ)_/¯
|
||||
(props.children as ReactElement<any>)?.props && Object.assign((props.children as ReactElement<any>).props, propsCopy); // There is probably a better way to do this but valve does it this way so ¯\_(ツ)_/¯
|
||||
return (
|
||||
<Suspense
|
||||
fallback={
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -3,26 +3,22 @@ import { FC, useEffect, useMemo, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { FaCheck, FaDownload } from 'react-icons/fa';
|
||||
|
||||
import { InstallType } from '../../plugin';
|
||||
import { DisabledPlugin, InstallType, InstallTypeTranslationMapping } from '../../plugin';
|
||||
|
||||
interface MultiplePluginsInstallModalProps {
|
||||
requests: { name: string; version: string; hash: string; install_type: InstallType }[];
|
||||
disabledPlugins: DisabledPlugin[];
|
||||
onOK(): void | Promise<void>;
|
||||
onCancel(): void | Promise<void>;
|
||||
closeModal?(): void;
|
||||
}
|
||||
|
||||
// values are the JSON keys used in the translation file
|
||||
const InstallTypeTranslationMapping = {
|
||||
[InstallType.INSTALL]: 'install',
|
||||
[InstallType.REINSTALL]: 'reinstall',
|
||||
[InstallType.UPDATE]: 'update',
|
||||
} as const satisfies Record<InstallType, string>;
|
||||
|
||||
// IMPORTANT! Keep in sync with `t(...)` comments below
|
||||
type TitleTranslationMapping = 'mixed' | (typeof InstallTypeTranslationMapping)[InstallType];
|
||||
|
||||
const MultiplePluginsInstallModal: FC<MultiplePluginsInstallModalProps> = ({
|
||||
requests,
|
||||
disabledPlugins,
|
||||
onOK,
|
||||
onCancel,
|
||||
closeModal,
|
||||
@@ -70,6 +66,8 @@ const MultiplePluginsInstallModal: FC<MultiplePluginsInstallModalProps> = ({
|
||||
if (requests.every(({ install_type }) => install_type === InstallType.INSTALL)) return 'install';
|
||||
if (requests.every(({ install_type }) => install_type === InstallType.REINSTALL)) return 'reinstall';
|
||||
if (requests.every(({ install_type }) => install_type === InstallType.UPDATE)) return 'update';
|
||||
if (requests.every(({ install_type }) => install_type === InstallType.DOWNGRADE)) return 'downgrade';
|
||||
if (requests.every(({ install_type }) => install_type === InstallType.OVERWRITE)) return 'overwrite';
|
||||
return 'mixed';
|
||||
}, [requests]);
|
||||
|
||||
@@ -86,23 +84,45 @@ const MultiplePluginsInstallModal: FC<MultiplePluginsInstallModalProps> = ({
|
||||
onCancel={async () => {
|
||||
await onCancel();
|
||||
}}
|
||||
strTitle={<div>{t(`MultiplePluginsInstallModal.title.${installTypeGrouped}`, { count: requests.length })}</div>}
|
||||
strOKButtonText={t(`MultiplePluginsInstallModal.ok_button.${loading ? 'loading' : 'idle'}`)}
|
||||
strTitle={
|
||||
<div>
|
||||
{
|
||||
// IMPORTANT! These comments are not cosmetic and are needed for `extracttext` task to work
|
||||
// t('MultiplePluginsInstallModal.title.install', { count: n })
|
||||
// t('MultiplePluginsInstallModal.title.reinstall', { count: n })
|
||||
// t('MultiplePluginsInstallModal.title.update', { count: n })
|
||||
// t('MultiplePluginsInstallModal.title.downgrade', { count: n })
|
||||
// t('MultiplePluginsInstallModal.title.overwrite', { count: n })
|
||||
// t('MultiplePluginsInstallModal.title.mixed', { count: n })
|
||||
t(`MultiplePluginsInstallModal.title.${installTypeGrouped}`, { count: requests.length })
|
||||
}
|
||||
</div>
|
||||
}
|
||||
strOKButtonText={
|
||||
loading ? t('MultiplePluginsInstallModal.ok_button.loading') : t('MultiplePluginsInstallModal.ok_button.idle')
|
||||
}
|
||||
>
|
||||
<div>
|
||||
{t('MultiplePluginsInstallModal.confirm')}
|
||||
<ul style={{ listStyle: 'none', display: 'flex', flexDirection: 'column', gap: '4px' }}>
|
||||
{requests.map(({ name, version, install_type, hash }, i) => {
|
||||
const installTypeStr = InstallTypeTranslationMapping[install_type];
|
||||
// IMPORTANT! These comments are not cosmetic and are needed for `extracttext` task to work
|
||||
// t('MultiplePluginsInstallModal.description.install')
|
||||
// t('MultiplePluginsInstallModal.description.reinstall')
|
||||
// t('MultiplePluginsInstallModal.description.update')
|
||||
// t('MultiplePluginsInstallModal.description.downgrade')
|
||||
// t('MultiplePluginsInstallModal.description.overwrite')
|
||||
const description = t(`MultiplePluginsInstallModal.description.${installTypeStr}`, {
|
||||
name,
|
||||
version,
|
||||
});
|
||||
|
||||
const disabled = disabledPlugins.some((p) => p.name === name);
|
||||
return (
|
||||
<li key={i} style={{ display: 'flex', flexDirection: 'column' }}>
|
||||
<span>
|
||||
{description}{' '}
|
||||
{disabled ? `${description} - ${t('PluginInstallModal.disabled')}` : description}{' '}
|
||||
{(pluginsCompleted.includes(name) && <FaCheck />) || (name === pluginInProgress && <FaDownload />)}
|
||||
</span>
|
||||
{hash === 'False' && (
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
import { ConfirmModal, Spinner } from '@decky/ui';
|
||||
import { FC, useState } from 'react';
|
||||
|
||||
import { disablePlugin } from '../../plugin';
|
||||
|
||||
interface PluginDisableModalProps {
|
||||
name: string;
|
||||
title: string;
|
||||
buttonText: string;
|
||||
description: string;
|
||||
closeModal?(): void;
|
||||
}
|
||||
|
||||
const PluginDisableModal: FC<PluginDisableModalProps> = ({ name, title, buttonText, description, closeModal }) => {
|
||||
const [disabling, setDisabling] = useState<boolean>(false);
|
||||
return (
|
||||
<ConfirmModal
|
||||
closeModal={closeModal}
|
||||
onOK={async () => {
|
||||
setDisabling(true);
|
||||
await disablePlugin(name);
|
||||
closeModal?.();
|
||||
}}
|
||||
bOKDisabled={disabling}
|
||||
bCancelDisabled={disabling}
|
||||
strTitle={
|
||||
<div style={{ display: 'flex', flexDirection: 'row', alignItems: 'center', width: '100%' }}>
|
||||
{title}
|
||||
{disabling && <Spinner width="24px" height="24px" style={{ marginLeft: 'auto' }} />}
|
||||
</div>
|
||||
}
|
||||
strOKButtonText={buttonText}
|
||||
>
|
||||
{description}
|
||||
</ConfirmModal>
|
||||
);
|
||||
};
|
||||
|
||||
export default PluginDisableModal;
|
||||
@@ -2,13 +2,14 @@ import { ConfirmModal, Navigation, ProgressBarWithInfo, QuickAccessTab } from '@
|
||||
import { FC, useEffect, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import TranslationHelper, { TranslationClass } from '../../utils/TranslationHelper';
|
||||
import { InstallType, InstallTypeTranslationMapping } from '../../plugin';
|
||||
|
||||
interface PluginInstallModalProps {
|
||||
artifact: string;
|
||||
version: string;
|
||||
hash: string;
|
||||
installType: number;
|
||||
installType: InstallType;
|
||||
disabled?: boolean;
|
||||
onOK(): void;
|
||||
onCancel(): void;
|
||||
closeModal?(): void;
|
||||
@@ -19,6 +20,7 @@ const PluginInstallModal: FC<PluginInstallModalProps> = ({
|
||||
version,
|
||||
hash,
|
||||
installType,
|
||||
disabled,
|
||||
onOK,
|
||||
onCancel,
|
||||
closeModal,
|
||||
@@ -44,6 +46,12 @@ const PluginInstallModal: FC<PluginInstallModalProps> = ({
|
||||
};
|
||||
}, []);
|
||||
|
||||
const installTypeTranslationKey = InstallTypeTranslationMapping[installType];
|
||||
const description = t(`PluginInstallModal.${installTypeTranslationKey}.desc`, {
|
||||
artifact: artifact,
|
||||
version: version,
|
||||
});
|
||||
|
||||
return (
|
||||
<ConfirmModal
|
||||
bOKDisabled={loading}
|
||||
@@ -59,12 +67,15 @@ const PluginInstallModal: FC<PluginInstallModalProps> = ({
|
||||
}}
|
||||
strTitle={
|
||||
<div style={{ display: 'flex', flexDirection: 'row', alignItems: 'center', width: '100%' }}>
|
||||
<TranslationHelper
|
||||
transClass={TranslationClass.PLUGIN_INSTALL_MODAL}
|
||||
transText="title"
|
||||
i18nArgs={{ artifact: artifact }}
|
||||
installType={installType}
|
||||
/>
|
||||
{
|
||||
// IMPORTANT! These comments are not cosmetic and are needed for `extracttext` task to work
|
||||
// t('PluginInstallModal.install.title')
|
||||
// t('PluginInstallModal.reinstall.title')
|
||||
// t('PluginInstallModal.update.title')
|
||||
// t('PluginInstallModal.downgrade.title')
|
||||
// t('PluginInstallModal.overwrite.title')
|
||||
t(`PluginInstallModal.${installTypeTranslationKey}.title`, { artifact: artifact })
|
||||
}
|
||||
{loading && (
|
||||
<div style={{ marginLeft: 'auto' }}>
|
||||
<ProgressBarWithInfo
|
||||
@@ -80,33 +91,41 @@ const PluginInstallModal: FC<PluginInstallModalProps> = ({
|
||||
strOKButtonText={
|
||||
loading ? (
|
||||
<div>
|
||||
<TranslationHelper
|
||||
transClass={TranslationClass.PLUGIN_INSTALL_MODAL}
|
||||
transText="button_processing"
|
||||
installType={installType}
|
||||
/>
|
||||
{
|
||||
// IMPORTANT! These comments are not cosmetic and are needed for `extracttext` task to work
|
||||
// t('PluginInstallModal.install.button_processing')
|
||||
// t('PluginInstallModal.reinstall.button_processing')
|
||||
// t('PluginInstallModal.update.button_processing')
|
||||
// t('PluginInstallModal.downgrade.button_processing')
|
||||
// t('PluginInstallModal.overwrite.button_processing')
|
||||
t(`PluginInstallModal.${installTypeTranslationKey}.button_processing`)
|
||||
}
|
||||
</div>
|
||||
) : (
|
||||
<div>
|
||||
<TranslationHelper
|
||||
transClass={TranslationClass.PLUGIN_INSTALL_MODAL}
|
||||
transText="button_idle"
|
||||
installType={installType}
|
||||
/>
|
||||
{
|
||||
// IMPORTANT! These comments are not cosmetic and are needed for `extracttext` task to work
|
||||
// t('PluginInstallModal.install.button_idle')
|
||||
// t('PluginInstallModal.reinstall.button_idle')
|
||||
// t('PluginInstallModal.update.button_idle')
|
||||
// t('PluginInstallModal.downgrade.button_idle')
|
||||
// t('PluginInstallModal.overwrite.button_idle')
|
||||
t(`PluginInstallModal.${installTypeTranslationKey}.button_idle`)
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
>
|
||||
<div>
|
||||
<TranslationHelper
|
||||
transClass={TranslationClass.PLUGIN_INSTALL_MODAL}
|
||||
transText="desc"
|
||||
i18nArgs={{
|
||||
artifact: artifact,
|
||||
version: version,
|
||||
}}
|
||||
installType={installType}
|
||||
/>
|
||||
{
|
||||
// IMPORTANT! These comments are not cosmetic and are needed for `extracttext` task to work
|
||||
// t('PluginInstallModal.install.desc')
|
||||
// t('PluginInstallModal.reinstall.desc')
|
||||
// t('PluginInstallModal.update.desc')
|
||||
// t('PluginInstallModal.downgrade.desc')
|
||||
// t('PluginInstallModal.overwrite.desc')
|
||||
disabled ? `${description} ${t('PluginInstallModal.disabled')}` : description
|
||||
}
|
||||
</div>
|
||||
{hash == 'False' && <span style={{ color: 'red' }}>{t('PluginInstallModal.no_hash')}</span>}
|
||||
</ConfirmModal>
|
||||
|
||||
@@ -2,8 +2,10 @@ import { ConfirmModal, Spinner } from '@decky/ui';
|
||||
import { FC, useState } from 'react';
|
||||
|
||||
import { uninstallPlugin } from '../../plugin';
|
||||
import { DeckyState } from '../DeckyState';
|
||||
|
||||
interface PluginUninstallModalProps {
|
||||
deckyState: DeckyState;
|
||||
name: string;
|
||||
title: string;
|
||||
buttonText: string;
|
||||
@@ -11,7 +13,14 @@ interface PluginUninstallModalProps {
|
||||
closeModal?(): void;
|
||||
}
|
||||
|
||||
const PluginUninstallModal: FC<PluginUninstallModalProps> = ({ name, title, buttonText, description, closeModal }) => {
|
||||
const PluginUninstallModal: FC<PluginUninstallModalProps> = ({
|
||||
name,
|
||||
title,
|
||||
buttonText,
|
||||
description,
|
||||
deckyState,
|
||||
closeModal,
|
||||
}) => {
|
||||
const [uninstalling, setUninstalling] = useState<boolean>(false);
|
||||
return (
|
||||
<ConfirmModal
|
||||
@@ -19,6 +28,7 @@ const PluginUninstallModal: FC<PluginUninstallModalProps> = ({ name, title, butt
|
||||
onOK={async () => {
|
||||
setUninstalling(true);
|
||||
await uninstallPlugin(name);
|
||||
deckyState.setDisabledPlugins(deckyState.publicState().disabledPlugins.filter((d) => d.name !== name));
|
||||
// uninstalling a plugin resets the hidden setting for it server-side
|
||||
// we invalidate here so if you re-install it, you won't have an out-of-date hidden filter
|
||||
await DeckyPluginLoader.frozenPluginsService.invalidate();
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { FC, useEffect, useState } from 'react';
|
||||
import { FC, JSX, useEffect, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { IconContext } from 'react-icons';
|
||||
import { FaExclamationTriangle, FaQuestionCircle, FaUserSlash } from 'react-icons/fa';
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import library from './library';
|
||||
let patches: Function[] = [];
|
||||
// import library from './library';
|
||||
// let patches: Function[] = [];
|
||||
|
||||
export function deinitFilepickerPatches() {
|
||||
patches.forEach((unpatch) => unpatch());
|
||||
// patches.forEach((unpatch) => unpatch());
|
||||
}
|
||||
|
||||
export async function initFilepickerPatches() {
|
||||
patches.push(await library());
|
||||
// patches.push(await library());
|
||||
}
|
||||
|
||||
@@ -47,7 +47,7 @@ export default async function libraryPatch() {
|
||||
}
|
||||
|
||||
const unlisten = History.listen(() => {
|
||||
if (window.SteamClient.Apps.PromptToChangeShortcut !== patch.patchedFunction) {
|
||||
if ((window.SteamClient.Apps as any).PromptToChangeShortcut !== patch.patchedFunction) {
|
||||
rePatch();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -72,7 +72,16 @@ export default function DeveloperSettings() {
|
||||
}
|
||||
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')}
|
||||
</DialogButton>
|
||||
</Field>
|
||||
|
||||
@@ -2,11 +2,11 @@ import {
|
||||
Carousel,
|
||||
DialogButton,
|
||||
Field,
|
||||
FocusRing,
|
||||
Focusable,
|
||||
ProgressBarWithInfo,
|
||||
Spinner,
|
||||
findSP,
|
||||
gamepadDialogClasses,
|
||||
showModal,
|
||||
} from '@decky/ui';
|
||||
import { Suspense, lazy, useCallback, useEffect, useState } from 'react';
|
||||
@@ -23,9 +23,31 @@ const MarkdownRenderer = lazy(() => import('../../../Markdown'));
|
||||
function PatchNotesModal({ versionInfo, closeModal }: { versionInfo: VerInfo | null; closeModal?: () => {} }) {
|
||||
const SP = findSP();
|
||||
const { t } = useTranslation();
|
||||
|
||||
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
|
||||
fnItemRenderer={(id: number) => (
|
||||
<Focusable
|
||||
@@ -35,7 +57,9 @@ function PatchNotesModal({ versionInfo, closeModal }: { versionInfo: VerInfo | n
|
||||
overflowY: 'scroll',
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
margin: '40px',
|
||||
margin: '30px',
|
||||
padding: '0 15px',
|
||||
backgroundColor: 'rgba(37, 40, 46, 0.5)',
|
||||
}}
|
||||
>
|
||||
<div>
|
||||
@@ -57,11 +81,11 @@ function PatchNotesModal({ versionInfo, closeModal }: { versionInfo: VerInfo | n
|
||||
nItemMarginX={0}
|
||||
initialColumn={0}
|
||||
autoFocus={true}
|
||||
fnGetColumnWidth={() => SP.innerWidth}
|
||||
fnGetColumnWidth={() => SP.innerWidth - SP.innerWidth * (10 / 100)}
|
||||
name={t('Updater.decky_updates') as string}
|
||||
/>
|
||||
</FocusRing>
|
||||
</Focusable>
|
||||
</Focusable>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -139,13 +163,22 @@ export default function UpdaterSettings() {
|
||||
: t('Updater.updates.install_button')}
|
||||
</DialogButton>
|
||||
) : (
|
||||
<ProgressBarWithInfo
|
||||
layout="inline"
|
||||
bottomSeparator="none"
|
||||
nProgress={updateProgress}
|
||||
indeterminate={reloading}
|
||||
sOperationText={reloading ? t('Updater.updates.reloading') : t('Updater.updates.updating')}
|
||||
/>
|
||||
<div id="decky-hide-left">
|
||||
<style>
|
||||
{`
|
||||
#decky-hide-left .${gamepadDialogClasses.FieldLeftColumn} {
|
||||
display: none;
|
||||
}
|
||||
`}
|
||||
</style>
|
||||
<ProgressBarWithInfo
|
||||
layout="inline"
|
||||
bottomSeparator="none"
|
||||
nProgress={updateProgress}
|
||||
indeterminate={reloading}
|
||||
sOperationText={reloading ? t('Updater.updates.reloading') : t('Updater.updates.updating')}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</Field>
|
||||
{versionInfo?.remote && versionInfo?.remote?.tag_name != versionInfo?.current && (
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
import { FC } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { FaEyeSlash, FaLock } from 'react-icons/fa';
|
||||
import { FaBan, FaEyeSlash, FaLock } from 'react-icons/fa';
|
||||
|
||||
interface PluginListLabelProps {
|
||||
frozen: boolean;
|
||||
hidden: boolean;
|
||||
disabled: boolean;
|
||||
name: string;
|
||||
version?: string;
|
||||
}
|
||||
|
||||
const PluginListLabel: FC<PluginListLabelProps> = ({ name, frozen, hidden, version }) => {
|
||||
const PluginListLabel: FC<PluginListLabelProps> = ({ name, frozen, hidden, version, disabled }) => {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: '6px' }}>
|
||||
@@ -43,6 +44,20 @@ const PluginListLabel: FC<PluginListLabelProps> = ({ name, frozen, hidden, versi
|
||||
{t('PluginListLabel.hidden')}
|
||||
</div>
|
||||
)}
|
||||
{disabled && (
|
||||
<div
|
||||
style={{
|
||||
fontSize: '0.8rem',
|
||||
color: '#dcdedf',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: '10px',
|
||||
}}
|
||||
>
|
||||
<FaBan />
|
||||
{t('PluginListLabel.disabled')}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -2,9 +2,11 @@ import {
|
||||
DialogBody,
|
||||
DialogButton,
|
||||
DialogControlsSection,
|
||||
Focusable,
|
||||
GamepadEvent,
|
||||
Menu,
|
||||
MenuItem,
|
||||
NavEntryPositionPreferences,
|
||||
ReorderableEntry,
|
||||
ReorderableList,
|
||||
showContextMenu,
|
||||
@@ -13,7 +15,7 @@ import { useEffect, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { FaDownload, FaEllipsisH, FaRecycle } from 'react-icons/fa';
|
||||
|
||||
import { InstallType } from '../../../../plugin';
|
||||
import { InstallType, enablePlugin } from '../../../../plugin';
|
||||
import {
|
||||
StorePluginVersion,
|
||||
getPluginList,
|
||||
@@ -35,6 +37,7 @@ async function reinstallPlugin(pluginName: string, currentVersion?: string) {
|
||||
|
||||
type PluginTableData = PluginData & {
|
||||
name: string;
|
||||
disabled: boolean;
|
||||
frozen: boolean;
|
||||
onFreeze(): void;
|
||||
onUnfreeze(): void;
|
||||
@@ -54,24 +57,25 @@ function PluginInteractables(props: { entry: ReorderableEntry<PluginTableData> }
|
||||
return null;
|
||||
}
|
||||
|
||||
const { name, update, version, onHide, onShow, hidden, onFreeze, onUnfreeze, frozen, isDeveloper } = props.entry.data;
|
||||
const { name, update, version, onHide, onShow, hidden, onFreeze, onUnfreeze, frozen, isDeveloper, disabled } =
|
||||
props.entry.data;
|
||||
|
||||
const showCtxMenu = (e: MouseEvent | GamepadEvent) => {
|
||||
showContextMenu(
|
||||
<Menu label={t('PluginListIndex.plugin_actions')}>
|
||||
<MenuItem
|
||||
onSelected={async () => {
|
||||
try {
|
||||
await reloadPluginBackend(name);
|
||||
} catch (err) {
|
||||
console.error('Error Reloading Plugin Backend', err);
|
||||
}
|
||||
|
||||
DeckyPluginLoader.importPlugin(name, version);
|
||||
}}
|
||||
>
|
||||
{t('PluginListIndex.reload')}
|
||||
</MenuItem>
|
||||
{!disabled && (
|
||||
<MenuItem
|
||||
onSelected={async () => {
|
||||
try {
|
||||
await reloadPluginBackend(name);
|
||||
} catch (err) {
|
||||
console.error('Error Reloading Plugin Backend', err);
|
||||
}
|
||||
}}
|
||||
>
|
||||
{t('PluginListIndex.reload')}
|
||||
</MenuItem>
|
||||
)}
|
||||
<MenuItem
|
||||
onSelected={() =>
|
||||
DeckyPluginLoader.uninstallPlugin(
|
||||
@@ -84,11 +88,28 @@ function PluginInteractables(props: { entry: ReorderableEntry<PluginTableData> }
|
||||
>
|
||||
{t('PluginListIndex.uninstall')}
|
||||
</MenuItem>
|
||||
{hidden ? (
|
||||
<MenuItem onSelected={onShow}>{t('PluginListIndex.show')}</MenuItem>
|
||||
{disabled ? (
|
||||
<MenuItem onSelected={() => enablePlugin(name)}>{t('PluginListIndex.enable')}</MenuItem>
|
||||
) : (
|
||||
<MenuItem onSelected={onHide}>{t('PluginListIndex.hide')}</MenuItem>
|
||||
<MenuItem
|
||||
onSelected={() =>
|
||||
DeckyPluginLoader.disablePlugin(
|
||||
name,
|
||||
t('PluginLoader.plugin_disable.title', { name }),
|
||||
t('PluginLoader.plugin_disable.button'),
|
||||
t('PluginLoader.plugin_disable.desc', { name }),
|
||||
)
|
||||
}
|
||||
>
|
||||
{t('PluginListIndex.disable')}
|
||||
</MenuItem>
|
||||
)}
|
||||
{!disabled &&
|
||||
(hidden ? (
|
||||
<MenuItem onSelected={onShow}>{t('PluginListIndex.show')}</MenuItem>
|
||||
) : (
|
||||
<MenuItem onSelected={onHide}>{t('PluginListIndex.hide')}</MenuItem>
|
||||
))}
|
||||
{frozen ? (
|
||||
<MenuItem onSelected={onUnfreeze}>{t('PluginListIndex.unfreeze')}</MenuItem>
|
||||
) : (
|
||||
@@ -100,7 +121,7 @@ function PluginInteractables(props: { entry: ReorderableEntry<PluginTableData> }
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Focusable navEntryPreferPosition={NavEntryPositionPreferences.MAINTAIN_X} style={{ display: 'flex' }}>
|
||||
{update ? (
|
||||
<DialogButton
|
||||
style={{ height: '40px', minWidth: '60px', marginRight: '10px' }}
|
||||
@@ -139,7 +160,7 @@ function PluginInteractables(props: { entry: ReorderableEntry<PluginTableData> }
|
||||
>
|
||||
<FaEllipsisH />
|
||||
</DialogButton>
|
||||
</>
|
||||
</Focusable>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -149,16 +170,18 @@ type PluginData = {
|
||||
};
|
||||
|
||||
export default function PluginList({ isDeveloper }: { isDeveloper: boolean }) {
|
||||
const { plugins, updates, pluginOrder, setPluginOrder, frozenPlugins, hiddenPlugins } = useDeckyState();
|
||||
const { installedPlugins, disabledPlugins, updates, pluginOrder, setPluginOrder, frozenPlugins, hiddenPlugins } =
|
||||
useDeckyState();
|
||||
|
||||
const [_, setPluginOrderSetting] = useSetting<string[]>(
|
||||
'pluginOrder',
|
||||
plugins.map((plugin) => plugin.name),
|
||||
installedPlugins.map((plugin) => plugin.name),
|
||||
);
|
||||
const { t } = useTranslation();
|
||||
|
||||
useEffect(() => {
|
||||
DeckyPluginLoader.checkPluginUpdates();
|
||||
}, []);
|
||||
}, [installedPlugins, frozenPlugins]);
|
||||
|
||||
const [pluginEntries, setPluginEntries] = useState<ReorderableEntry<PluginTableData>[]>([]);
|
||||
const hiddenPluginsService = DeckyPluginLoader.hiddenPluginsService;
|
||||
@@ -166,15 +189,24 @@ export default function PluginList({ isDeveloper }: { isDeveloper: boolean }) {
|
||||
|
||||
useEffect(() => {
|
||||
setPluginEntries(
|
||||
plugins.map(({ name, version }) => {
|
||||
installedPlugins.map(({ name, version }) => {
|
||||
const frozen = frozenPlugins.includes(name);
|
||||
const hidden = hiddenPlugins.includes(name);
|
||||
|
||||
return {
|
||||
label: <PluginListLabel name={name} frozen={frozen} hidden={hidden} version={version} />,
|
||||
label: (
|
||||
<PluginListLabel
|
||||
name={name}
|
||||
frozen={frozen}
|
||||
hidden={hidden}
|
||||
version={version}
|
||||
disabled={disabledPlugins.find((p) => p.name == name) !== undefined}
|
||||
/>
|
||||
),
|
||||
position: pluginOrder.indexOf(name),
|
||||
data: {
|
||||
name,
|
||||
disabled: disabledPlugins.some((disabledPlugin) => disabledPlugin.name === name),
|
||||
frozen,
|
||||
hidden,
|
||||
isDeveloper,
|
||||
@@ -188,9 +220,9 @@ export default function PluginList({ isDeveloper }: { isDeveloper: boolean }) {
|
||||
};
|
||||
}),
|
||||
);
|
||||
}, [plugins, updates, hiddenPlugins]);
|
||||
}, [installedPlugins, updates, hiddenPlugins, disabledPlugins]);
|
||||
|
||||
if (plugins.length === 0) {
|
||||
if (installedPlugins.length === 0) {
|
||||
return (
|
||||
<div>
|
||||
<p>{t('PluginListIndex.no_plugin')}</p>
|
||||
|
||||
@@ -4,13 +4,14 @@ import {
|
||||
DialogControlsSection,
|
||||
Field,
|
||||
Focusable,
|
||||
NavEntryPositionPreferences,
|
||||
Navigation,
|
||||
ProgressBar,
|
||||
SteamSpinner,
|
||||
} from '@decky/ui';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { FaDownload, FaInfo } from 'react-icons/fa';
|
||||
import { FaDownload, FaFlask, FaInfo } from 'react-icons/fa';
|
||||
|
||||
import { setSetting } from '../../../../utils/settings';
|
||||
import { UpdateBranch } from '../general/BranchSelect';
|
||||
@@ -65,9 +66,9 @@ export default function TestingVersionList() {
|
||||
|
||||
if (testingVersions.length === 0) {
|
||||
return (
|
||||
<div>
|
||||
<DialogBody>
|
||||
<p>No open PRs found</p>
|
||||
</div>
|
||||
</DialogBody>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -79,29 +80,38 @@ export default function TestingVersionList() {
|
||||
<ul style={{ listStyleType: 'none', padding: '0' }}>
|
||||
{testingVersions.map((version) => {
|
||||
return (
|
||||
<li>
|
||||
<li key={`${version.id}_${version.name}`}>
|
||||
<Field
|
||||
label={
|
||||
<>
|
||||
{version.name} <span style={{ opacity: '50%' }}>{'#' + version.id}</span>
|
||||
{version.name}{' '}
|
||||
<span style={{ opacity: '50%', whiteSpace: 'nowrap', marginLeft: 'auto', alignSelf: 'center' }}>
|
||||
{'#' + version.id}
|
||||
</span>
|
||||
</>
|
||||
}
|
||||
>
|
||||
<Focusable style={{ height: '40px', marginLeft: 'auto', display: 'flex' }}>
|
||||
<Focusable
|
||||
style={{ height: '40px', marginLeft: 'auto', display: 'flex' }}
|
||||
navEntryPreferPosition={NavEntryPositionPreferences.MAINTAIN_X}
|
||||
>
|
||||
<DialogButton
|
||||
style={{ height: '40px', minWidth: '60px', marginRight: '10px' }}
|
||||
onClick={async () => {
|
||||
DeckyPluginLoader.toaster.toast({
|
||||
const downloadToast = DeckyPluginLoader.toaster.toast({
|
||||
title: t('Testing.start_download_toast', { id: version.id }),
|
||||
body: null,
|
||||
icon: <FaFlask />,
|
||||
});
|
||||
try {
|
||||
await downloadTestingVersion(version.id, version.head_sha);
|
||||
downloadToast.dismiss();
|
||||
} catch (e) {
|
||||
if (e instanceof Error) {
|
||||
DeckyPluginLoader.toaster.toast({
|
||||
title: t('Testing.error'),
|
||||
body: `${e.name}: ${e.message}`,
|
||||
icon: <FaFlask />,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,17 +1,40 @@
|
||||
import { ButtonItem, Dropdown, Focusable, PanelSectionRow, SingleDropdownOption, SuspensefulImage } from '@decky/ui';
|
||||
import {
|
||||
ButtonItem,
|
||||
Dropdown,
|
||||
Focusable,
|
||||
NavEntryPositionPreferences,
|
||||
PanelSectionRow,
|
||||
SingleDropdownOption,
|
||||
SuspensefulImage,
|
||||
} from '@decky/ui';
|
||||
import { CSSProperties, FC, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { FaArrowDown, FaArrowUp, FaCheck, FaDownload, FaRecycle } from 'react-icons/fa';
|
||||
|
||||
import { InstallType } from '../../plugin';
|
||||
import { StorePlugin, StorePluginVersion, requestPluginInstall } from '../../store';
|
||||
import { DisabledPlugin, InstallType, Plugin } from '../../plugin';
|
||||
import { StorePlugin, requestPluginInstall } from '../../store';
|
||||
import ExternalLink from '../ExternalLink';
|
||||
|
||||
interface PluginCardProps {
|
||||
plugin: StorePlugin;
|
||||
storePlugin: StorePlugin;
|
||||
installedPlugin: Plugin | DisabledPlugin | undefined;
|
||||
}
|
||||
|
||||
const PluginCard: FC<PluginCardProps> = ({ plugin }) => {
|
||||
const PluginCard: FC<PluginCardProps> = ({ storePlugin, installedPlugin }) => {
|
||||
const [selectedOption, setSelectedOption] = useState<number>(0);
|
||||
const root = plugin.tags.some((tag) => tag === 'root');
|
||||
const installedVersionIndex = storePlugin.versions.findIndex((version) => version.name === installedPlugin?.version);
|
||||
const installType = // This assumes index in options is inverse to update order (i.e. newer updates are first)
|
||||
installedPlugin && selectedOption < installedVersionIndex
|
||||
? InstallType.UPDATE
|
||||
: installedPlugin && selectedOption === installedVersionIndex
|
||||
? InstallType.REINSTALL
|
||||
: installedPlugin && selectedOption > installedVersionIndex
|
||||
? InstallType.DOWNGRADE
|
||||
: installedPlugin // can happen if installed version is not in store
|
||||
? InstallType.OVERWRITE
|
||||
: InstallType.INSTALL;
|
||||
|
||||
const root = storePlugin.tags.some((tag) => tag === 'root');
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
@@ -42,7 +65,7 @@ const PluginCard: FC<PluginCardProps> = ({ plugin }) => {
|
||||
height: '200px',
|
||||
objectFit: 'cover',
|
||||
}}
|
||||
src={plugin.image_url}
|
||||
src={storePlugin.image_url}
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
@@ -68,7 +91,7 @@ const PluginCard: FC<PluginCardProps> = ({ plugin }) => {
|
||||
width: '90%',
|
||||
}}
|
||||
>
|
||||
{plugin.name}
|
||||
{storePlugin.name}
|
||||
</span>
|
||||
<span
|
||||
className="deckyStoreCardAuthor"
|
||||
@@ -77,7 +100,7 @@ const PluginCard: FC<PluginCardProps> = ({ plugin }) => {
|
||||
fontSize: '1em',
|
||||
}}
|
||||
>
|
||||
{plugin.author}
|
||||
{storePlugin.author}
|
||||
</span>
|
||||
<span
|
||||
className="deckyStoreCardDescription"
|
||||
@@ -90,8 +113,8 @@ const PluginCard: FC<PluginCardProps> = ({ plugin }) => {
|
||||
display: '-webkit-box',
|
||||
}}
|
||||
>
|
||||
{plugin.description ? (
|
||||
plugin.description
|
||||
{storePlugin.description ? (
|
||||
storePlugin.description
|
||||
) : (
|
||||
<span>
|
||||
<i style={{ color: '#666' }}>{t('PluginCard.plugin_no_desc')}</i>
|
||||
@@ -108,7 +131,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,13 +141,16 @@ const PluginCard: FC<PluginCardProps> = ({ plugin }) => {
|
||||
}}
|
||||
>
|
||||
deckbrew.xyz/root
|
||||
</a>
|
||||
</ExternalLink>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="deckyStoreCardButtonRow">
|
||||
<PanelSectionRow>
|
||||
<Focusable style={{ display: 'flex', gap: '5px', padding: 0 }}>
|
||||
<Focusable
|
||||
style={{ display: 'flex', gap: '5px', padding: 0 }}
|
||||
navEntryPreferPosition={NavEntryPositionPreferences.MAINTAIN_X}
|
||||
>
|
||||
<div
|
||||
className="deckyStoreCardInstallContainer"
|
||||
style={
|
||||
@@ -140,18 +166,49 @@ const PluginCard: FC<PluginCardProps> = ({ plugin }) => {
|
||||
bottomSeparator="none"
|
||||
layout="below"
|
||||
onClick={() =>
|
||||
requestPluginInstall(plugin.name, plugin.versions[selectedOption], InstallType.INSTALL)
|
||||
requestPluginInstall(storePlugin.name, storePlugin.versions[selectedOption], installType)
|
||||
}
|
||||
>
|
||||
<span className="deckyStoreCardInstallText">{t('PluginCard.plugin_install')}</span>
|
||||
<span
|
||||
className="deckyStoreCardInstallText"
|
||||
style={{ display: 'flex', justifyContent: 'center', alignItems: 'center', gap: '5px' }}
|
||||
>
|
||||
{installType === InstallType.UPDATE ? (
|
||||
<>
|
||||
<FaArrowUp /> {t('PluginCard.plugin_update')}
|
||||
</>
|
||||
) : installType === InstallType.REINSTALL ? (
|
||||
<>
|
||||
<FaRecycle /> {t('PluginCard.plugin_reinstall')}
|
||||
</>
|
||||
) : installType === InstallType.DOWNGRADE ? (
|
||||
<>
|
||||
<FaArrowDown /> {t('PluginCard.plugin_downgrade')}
|
||||
</>
|
||||
) : installType === InstallType.OVERWRITE ? (
|
||||
<>
|
||||
<FaDownload /> {t('PluginCard.plugin_overwrite')}
|
||||
</>
|
||||
) : (
|
||||
// installType === InstallType.INSTALL (also fallback)
|
||||
<>
|
||||
<FaDownload /> {t('PluginCard.plugin_install')}
|
||||
</>
|
||||
)}
|
||||
</span>
|
||||
</ButtonItem>
|
||||
</div>
|
||||
<div className="deckyStoreCardVersionContainer" style={{ minWidth: '130px' }}>
|
||||
<Dropdown
|
||||
rgOptions={
|
||||
plugin.versions.map((version: StorePluginVersion, index) => ({
|
||||
storePlugin.versions.map((version, index) => ({
|
||||
data: index,
|
||||
label: version.name,
|
||||
label: (
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: '5px' }}>
|
||||
{version.name}
|
||||
{installedPlugin && installedVersionIndex === index ? <FaCheck /> : null}
|
||||
</div>
|
||||
),
|
||||
})) as SingleDropdownOption[]
|
||||
}
|
||||
menuLabel={t('PluginCard.plugin_version_label') as string}
|
||||
|
||||
@@ -14,6 +14,8 @@ 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 { useDeckyState } from '../DeckyState';
|
||||
import ExternalLink from '../ExternalLink';
|
||||
import PluginCard from './PluginCard';
|
||||
|
||||
const logger = new Logger('Store');
|
||||
@@ -103,6 +105,8 @@ const BrowseTab: FC<{ setPluginCount: Dispatch<SetStateAction<number | null>> }>
|
||||
})();
|
||||
}, []);
|
||||
|
||||
const { installedPlugins } = useDeckyState();
|
||||
|
||||
return (
|
||||
<>
|
||||
<style>{`
|
||||
@@ -207,7 +211,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 +219,7 @@ const BrowseTab: FC<{ setPluginCount: Dispatch<SetStateAction<number | null>> }>
|
||||
}}
|
||||
>
|
||||
decky.xyz/testing
|
||||
</a>
|
||||
</ExternalLink>
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
@@ -234,7 +238,13 @@ const BrowseTab: FC<{ setPluginCount: Dispatch<SetStateAction<number | null>> }>
|
||||
plugin.tags.some((tag: string) => tag.toLowerCase().includes(searchFieldValue.toLowerCase()))
|
||||
);
|
||||
})
|
||||
.map((plugin: StorePlugin) => <PluginCard plugin={plugin} />)
|
||||
.map((plugin: StorePlugin) => (
|
||||
<PluginCard
|
||||
key={`${plugin.id}_${plugin.name}`}
|
||||
storePlugin={plugin}
|
||||
installedPlugin={installedPlugins.find((installedPlugin) => installedPlugin.name === plugin.name)}
|
||||
/>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
@@ -269,7 +279,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 +287,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>
|
||||
|
||||
@@ -22,11 +22,9 @@ class ErrorBoundaryHook extends Logger {
|
||||
this.log('Initialized');
|
||||
window.__ERRORBOUNDARY_HOOK_INSTANCE?.deinit?.();
|
||||
window.__ERRORBOUNDARY_HOOK_INSTANCE = this;
|
||||
}
|
||||
|
||||
init() {
|
||||
// valve writes only the sanest of code
|
||||
const exp = /^\(\)=>\(.\|\|.\(new .\),.\)$/;
|
||||
const exp = /^\(\)=>\(.\|\|(?:.\(|\(.=)new .\),.\)$/;
|
||||
const initErrorReportingStore = findModuleExport(
|
||||
(e) => typeof e == 'function' && e?.toString && exp.test(e.toString()),
|
||||
);
|
||||
@@ -71,14 +69,27 @@ class ErrorBoundaryHook extends Logger {
|
||||
});
|
||||
|
||||
if (!ErrorBoundary) {
|
||||
this.error('could not find ValveErrorBoundary');
|
||||
this.error('@decky/ui could not find ErrorBoundary, skipping patch');
|
||||
return;
|
||||
}
|
||||
|
||||
this.errorBoundaryPatch = replacePatch(ErrorBoundary.prototype, 'render', function (this: any) {
|
||||
if (this.state.error) {
|
||||
if (this.state._deckyForceRerender) {
|
||||
const stateClone = { ...this.state, _deckyForceRerender: null };
|
||||
this.setState(stateClone);
|
||||
return null;
|
||||
}
|
||||
// yoinked from valve error boundary
|
||||
if (this.state.error && this.props.errorKey == this.state.lastErrorKey) {
|
||||
const store = Object.getPrototypeOf(this)?.constructor?.sm_ErrorReportingStore || errorReportingStore;
|
||||
return (
|
||||
|
||||
return void 0 !== this.props.fallback ? (
|
||||
'function' == typeof this.props.fallback ? (
|
||||
this.props.fallback(this.state.error.error)
|
||||
) : (
|
||||
this.props.fallback
|
||||
)
|
||||
) : (
|
||||
<DeckyErrorBoundary
|
||||
error={this.state.error}
|
||||
errorKey={this.props.errorKey}
|
||||
@@ -89,6 +100,10 @@ class ErrorBoundaryHook extends Logger {
|
||||
}
|
||||
return callOriginal;
|
||||
});
|
||||
// Small hack that gives us a lot more flexibility to force rerenders.
|
||||
ErrorBoundary.prototype._deckyForceRerender = function (this: any) {
|
||||
this.setState({ ...this.state, _deckyForceRerender: true });
|
||||
};
|
||||
}
|
||||
|
||||
public temporarilyDisableReporting() {
|
||||
|
||||
@@ -0,0 +1,128 @@
|
||||
// THIS FILE MUST BE ENTIRELY SELF-CONTAINED! DO NOT USE PACKAGES!
|
||||
interface Window {
|
||||
FocusNavController: any;
|
||||
GamepadNavTree: any;
|
||||
deckyFallbackLoaded?: boolean;
|
||||
}
|
||||
|
||||
(async () => {
|
||||
try {
|
||||
if (window.deckyFallbackLoaded) return;
|
||||
window.deckyFallbackLoaded = true;
|
||||
|
||||
// #region utils
|
||||
function sleep(ms: number) {
|
||||
return new Promise((res) => setTimeout(res, ms));
|
||||
}
|
||||
// #endregion
|
||||
|
||||
// #region DeckyIcon
|
||||
const fallbackIcon = `
|
||||
<svg class="fallbackDeckyIcon" xmlns="http://www.w3.org/2000/svg" height="100%" width="100%" viewBox="0 0 512 456">
|
||||
<g>
|
||||
<path
|
||||
style="fill: none;"
|
||||
d="M154.33,72.51v49.79c11.78-0.17,23.48,2,34.42,6.39c10.93,4.39,20.89,10.91,29.28,19.18
|
||||
c8.39,8.27,15.06,18.13,19.61,29c4.55,10.87,6.89,22.54,6.89,34.32c0,11.78-2.34,23.45-6.89,34.32
|
||||
c-4.55,10.87-11.21,20.73-19.61,29c-8.39,8.27-18.35,14.79-29.28,19.18c-10.94,4.39-22.63,6.56-34.42,6.39v49.77
|
||||
c36.78,0,72.05-14.61,98.05-40.62c26-26.01,40.61-61.28,40.61-98.05c0-36.78-14.61-72.05-40.61-98.05
|
||||
C226.38,87.12,191.11,72.51,154.33,72.51z"
|
||||
/>
|
||||
|
||||
<ellipse
|
||||
transform="matrix(0.982 -0.1891 0.1891 0.982 -37.1795 32.9988)"
|
||||
style="fill: none;"
|
||||
cx="154.33"
|
||||
cy="211.33"
|
||||
rx="69.33"
|
||||
ry="69.33"
|
||||
/>
|
||||
<path style="fill: none;" d="M430,97h-52v187h52c7.18,0,13-5.82,13-13V110C443,102.82,437.18,97,430,97z" />
|
||||
<path
|
||||
style="fill: currentColor;"
|
||||
d="M432,27h-54V0H0v361c0,52.47,42.53,95,95,95h188c52.47,0,95-42.53,95-95v-7h54c44.18,0,80-35.82,80-80V107
|
||||
C512,62.82,476.18,27,432,27z M85,211.33c0-38.29,31.04-69.33,69.33-69.33c38.29,0,69.33,31.04,69.33,69.33
|
||||
c0,38.29-31.04,69.33-69.33,69.33C116.04,280.67,85,249.62,85,211.33z M252.39,309.23c-26.01,26-61.28,40.62-98.05,40.62v-49.77
|
||||
c11.78,0.17,23.48-2,34.42-6.39c10.93-4.39,20.89-10.91,29.28-19.18c8.39-8.27,15.06-18.13,19.61-29
|
||||
c4.55-10.87,6.89-22.53,6.89-34.32c0-11.78-2.34-23.45-6.89-34.32c-4.55-10.87-11.21-20.73-19.61-29
|
||||
c-8.39-8.27-18.35-14.79-29.28-19.18c-10.94-4.39-22.63-6.56-34.42-6.39V72.51c36.78,0,72.05,14.61,98.05,40.61
|
||||
c26,26.01,40.61,61.28,40.61,98.05C293,247.96,278.39,283.23,252.39,309.23z M443,271c0,7.18-5.82,13-13,13h-52V97h52
|
||||
c7.18,0,13,5.82,13,13V271z"
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
`;
|
||||
// #endregion
|
||||
|
||||
// #region findSP
|
||||
// from @decky/ui
|
||||
function getFocusNavController(): any {
|
||||
return window.GamepadNavTree?.m_context?.m_controller || window.FocusNavController;
|
||||
}
|
||||
|
||||
function getGamepadNavigationTrees(): any {
|
||||
const focusNav = getFocusNavController();
|
||||
const context = focusNav.m_ActiveContext || focusNav.m_LastActiveContext;
|
||||
return context?.m_rgGamepadNavigationTrees;
|
||||
}
|
||||
|
||||
function findSP(): Window {
|
||||
// old (SP as host)
|
||||
if (document.title == 'SP') return window;
|
||||
// new (SP as popup)
|
||||
const navTrees = getGamepadNavigationTrees();
|
||||
return navTrees?.find((x: any) => x.m_ID == 'root_1_').Root.Element.ownerDocument.defaultView;
|
||||
}
|
||||
// #endregion
|
||||
|
||||
const fallbackCSS = `
|
||||
.fallbackContainer {
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
flex-direction: column;
|
||||
z-index: 99999999;
|
||||
pointer-events: none;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
backdrop-filter: blur(8px) brightness(40%);
|
||||
}
|
||||
.fallbackDeckyIcon {
|
||||
width: 96px;
|
||||
height: 96px;
|
||||
padding-bottom: 1rem;
|
||||
}
|
||||
`;
|
||||
|
||||
const fallbackHTML = `
|
||||
<style>${fallbackCSS}</style>
|
||||
${fallbackIcon}
|
||||
<span class="fallbackText">
|
||||
<b>A crash loop has been detected and Decky has been disabled for this boot.</b>
|
||||
<br>
|
||||
<i>Steam will restart in 10 seconds...</i>
|
||||
</span>
|
||||
`;
|
||||
|
||||
await sleep(4000);
|
||||
|
||||
const win = findSP() || window;
|
||||
|
||||
const container = Object.assign(document.createElement('div'), {
|
||||
innerHTML: fallbackHTML,
|
||||
});
|
||||
container.classList.add('fallbackContainer');
|
||||
|
||||
win.document.body.appendChild(container);
|
||||
|
||||
await sleep(10000);
|
||||
|
||||
SteamClient.User.StartShutdown(false);
|
||||
} catch (e) {
|
||||
console.error('Error showing fallback!', e);
|
||||
}
|
||||
})();
|
||||
+25
-9
@@ -1,21 +1,37 @@
|
||||
// Sets up DFL, then loads start.ts which starts up the loader
|
||||
interface Window {
|
||||
// Shut up TS
|
||||
SP_REACTDOM: any;
|
||||
}
|
||||
|
||||
(async () => {
|
||||
// Wait for react to definitely be loaded
|
||||
while (!window.webpackChunksteamui || window.webpackChunksteamui <= 3) {
|
||||
await new Promise((r) => setTimeout(r, 10)); // Can't use DFL sleep here.
|
||||
console.debug('[Decky:Boot] Frontend init');
|
||||
|
||||
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 SteamApp init stage 1 to finish...');
|
||||
|
||||
if (!window.SP_REACT) {
|
||||
console.debug('[Decky:Boot] Setting up React globals...');
|
||||
console.debug('[Decky:Boot] Setting up Webpack & React globals...');
|
||||
// deliberate partial import
|
||||
const DFLWebpack = await import('@decky/ui/dist/webpack');
|
||||
window.SP_REACT = DFLWebpack.findModule((m) => m.Component && m.PureComponent && m.useLayoutEffect);
|
||||
window.SP_REACTDOM = DFLWebpack.findModule((m) => m.createPortal && m.createRoot);
|
||||
window.SP_REACTDOM =
|
||||
DFLWebpack.findModule((m) => m.createPortal && m.createRoot) ||
|
||||
DFLWebpack.findModule((m) => m.createPortal && m.__DOM_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE);
|
||||
|
||||
console.debug('[Decky:Boot] Setting up JSX internals...');
|
||||
const jsxModule = DFLWebpack.findModule((m) => (m.jsx && m.jsxs) || (m.jsx && Object.keys(m).length == 1));
|
||||
if (jsxModule.jsxs) {
|
||||
window.SP_JSX = jsxModule;
|
||||
} else {
|
||||
window.SP_JSX = {
|
||||
jsx: jsxModule.jsx,
|
||||
jsxs: jsxModule.jsx,
|
||||
Fragment: window.SP_REACT.Fragment,
|
||||
};
|
||||
}
|
||||
}
|
||||
console.debug('[Decky:Boot] Setting up @decky/ui...');
|
||||
window.DFL = await import('@decky/ui');
|
||||
|
||||
+188
-70
@@ -1,32 +1,37 @@
|
||||
import { ToastNotification } from '@decky/api';
|
||||
import {
|
||||
EUIMode,
|
||||
ModalRoot,
|
||||
Navigation,
|
||||
PanelSection,
|
||||
PanelSectionRow,
|
||||
QuickAccessTab,
|
||||
Router,
|
||||
findSP,
|
||||
quickAccessMenuClasses,
|
||||
showModal,
|
||||
sleep,
|
||||
} from '@decky/ui';
|
||||
import { FC, lazy } from 'react';
|
||||
import { FaExclamationCircle, FaPlug } from 'react-icons/fa';
|
||||
import { FaDownload, FaExclamationCircle, FaPlug } from 'react-icons/fa';
|
||||
|
||||
import DeckyIcon from './components/DeckyIcon';
|
||||
import { DeckyState, DeckyStateContextProvider, UserInfo, useDeckyState } from './components/DeckyState';
|
||||
import { File, FileSelectionType } from './components/modals/filepicker';
|
||||
import { deinitFilepickerPatches, initFilepickerPatches } from './components/modals/filepicker/patches';
|
||||
import MultiplePluginsInstallModal from './components/modals/MultiplePluginsInstallModal';
|
||||
import PluginDisableModal from './components/modals/PluginDisableModal';
|
||||
import PluginInstallModal from './components/modals/PluginInstallModal';
|
||||
import PluginUninstallModal from './components/modals/PluginUninstallModal';
|
||||
import NotificationBadge from './components/NotificationBadge';
|
||||
import PluginView from './components/PluginView';
|
||||
import { useQuickAccessVisible } from './components/QuickAccessVisibleState';
|
||||
import WithSuspense from './components/WithSuspense';
|
||||
import ErrorBoundaryHook from './errorboundary-hook';
|
||||
import { FrozenPluginService } from './frozen-plugins-service';
|
||||
import { HiddenPluginsService } from './hidden-plugins-service';
|
||||
import Logger from './logger';
|
||||
import { NotificationService } from './notification-service';
|
||||
import { InstallType, Plugin, PluginLoadType } from './plugin';
|
||||
import { DisabledPlugin, InstallType, Plugin, PluginLoadType } from './plugin';
|
||||
import RouterHook from './router-hook';
|
||||
import { deinitSteamFixes, initSteamFixes } from './steamfixes';
|
||||
import { checkForPluginUpdates } from './store';
|
||||
@@ -76,16 +81,18 @@ class PluginLoader extends Logger {
|
||||
|
||||
private reloadLock: boolean = false;
|
||||
// stores a list of plugin names which requested to be reloaded
|
||||
private pluginReloadQueue: { name: string; version?: string }[] = [];
|
||||
private pluginReloadQueue: { name: string; version?: string; loadType: PluginLoadType }[] = [];
|
||||
|
||||
private loaderUpdateToast?: ToastNotification;
|
||||
private pluginUpdateToast?: ToastNotification;
|
||||
|
||||
constructor() {
|
||||
super(PluginLoader.name);
|
||||
|
||||
this.errorBoundaryHook.init();
|
||||
|
||||
DeckyBackend.addEventListener('loader/notify_updates', this.notifyUpdates.bind(this));
|
||||
DeckyBackend.addEventListener('loader/import_plugin', this.importPlugin.bind(this));
|
||||
DeckyBackend.addEventListener('loader/unload_plugin', this.unloadPlugin.bind(this));
|
||||
DeckyBackend.addEventListener('loader/disable_plugin', this.doDisablePlugin.bind(this));
|
||||
DeckyBackend.addEventListener('loader/add_plugin_install_prompt', this.addPluginInstallPrompt.bind(this));
|
||||
DeckyBackend.addEventListener(
|
||||
'loader/add_multiple_plugins_install_prompt',
|
||||
@@ -115,36 +122,16 @@ class PluginLoader extends Logger {
|
||||
<DeckyStateContextProvider deckyState={this.deckyState}>
|
||||
<FaPlug />
|
||||
<TabBadge />
|
||||
<style>
|
||||
{`
|
||||
/* fixes random overscrolling in QAM */
|
||||
.${quickAccessMenuClasses?.TabContentColumn} {
|
||||
flex-grow: 1 !important;
|
||||
margin-top: 0 !important;
|
||||
margin-bottom: 0 !important;
|
||||
justify-content: center !important;
|
||||
}
|
||||
.${quickAccessMenuClasses?.Tab} {
|
||||
flex-grow: 1 !important;
|
||||
height: unset !important;
|
||||
--decky-qam-tab-max-height: 64px; /* make things a little easier for themers */
|
||||
max-height: var(--decky-qam-tab-max-height) !important;
|
||||
}
|
||||
/* they broke the footer a while ago and forgot to update the styles LOL */
|
||||
.${quickAccessMenuClasses?.Tabs}.${quickAccessMenuClasses.TabsWithFooter} {
|
||||
margin-bottom: 0 !important;
|
||||
padding-bottom: 0 !important;
|
||||
}
|
||||
`}
|
||||
</style>
|
||||
</DeckyStateContextProvider>
|
||||
),
|
||||
});
|
||||
|
||||
this.routerHook.addRoute('/decky/store', () => (
|
||||
<WithSuspense route={true}>
|
||||
<StorePage />
|
||||
</WithSuspense>
|
||||
<DeckyStateContextProvider deckyState={this.deckyState}>
|
||||
<WithSuspense route={true}>
|
||||
<StorePage />
|
||||
</WithSuspense>
|
||||
</DeckyStateContextProvider>
|
||||
));
|
||||
this.routerHook.addRoute('/decky/settings', () => {
|
||||
return (
|
||||
@@ -164,26 +151,65 @@ 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 {
|
||||
try {
|
||||
return !!findSP();
|
||||
} catch (e) {
|
||||
this.warn('Error checking for SP tab', e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private async runCrashChecker() {
|
||||
const spExists = this.checkForSP();
|
||||
await sleep(5000);
|
||||
if (spExists && !this.checkForSP()) {
|
||||
// SP died after plugin loaded. Give up and let the loader's crash loop detection handle it.
|
||||
this.error('SP died during startup. Restarting webhelper.');
|
||||
await this.restartWebhelper();
|
||||
}
|
||||
}
|
||||
|
||||
private getPluginsFromBackend = DeckyBackend.callable<
|
||||
[],
|
||||
{ name: string; version: string; load_type: PluginLoadType }[]
|
||||
{ name: string; version: string; load_type: PluginLoadType; disabled: boolean }[]
|
||||
>('loader/get_plugins');
|
||||
|
||||
private restartWebhelper = DeckyBackend.callable<[], void>('utilities/restart_webhelper');
|
||||
|
||||
private async loadPlugins() {
|
||||
// wait for SP window to exist before loading plugins
|
||||
while (!findSP()) {
|
||||
await sleep(100);
|
||||
let registration: any;
|
||||
const uiMode = await new Promise(
|
||||
(r) =>
|
||||
(registration = SteamClient.UI.RegisterForUIModeChanged((mode: EUIMode) => {
|
||||
r(mode);
|
||||
registration.unregister();
|
||||
})),
|
||||
);
|
||||
if (uiMode == EUIMode.GamePad) {
|
||||
// wait for SP window to exist before loading plugins
|
||||
while (!findSP()) {
|
||||
await sleep(100);
|
||||
}
|
||||
}
|
||||
this.runCrashChecker();
|
||||
const plugins = await this.getPluginsFromBackend();
|
||||
const pluginLoadPromises = [];
|
||||
const disabledPlugins: DisabledPlugin[] = [];
|
||||
const loadStart = performance.now();
|
||||
for (const plugin of plugins) {
|
||||
if (!this.hasPlugin(plugin.name))
|
||||
pluginLoadPromises.push(this.importPlugin(plugin.name, plugin.version, plugin.load_type, false));
|
||||
if (plugin.disabled) {
|
||||
disabledPlugins.push({ name: plugin.name, version: plugin.version });
|
||||
this.deckyState.setDisabledPlugins(disabledPlugins);
|
||||
} else {
|
||||
if (!this.hasPlugin(plugin.name))
|
||||
pluginLoadPromises.push(this.importPlugin(plugin.name, plugin.version, plugin.load_type, false));
|
||||
}
|
||||
}
|
||||
await Promise.all(pluginLoadPromises);
|
||||
const loadEnd = performance.now();
|
||||
@@ -210,7 +236,9 @@ class PluginLoader extends Logger {
|
||||
if (versionInfo?.remote && versionInfo?.remote?.tag_name != versionInfo?.current) {
|
||||
this.deckyState.setHasLoaderUpdate(true);
|
||||
if (this.notificationService.shouldNotify('deckyUpdates')) {
|
||||
this.toaster.toast({
|
||||
this.loaderUpdateToast && this.loaderUpdateToast.dismiss();
|
||||
await this.routerHook.waitForUnlock();
|
||||
this.loaderUpdateToast = this.toaster.toast({
|
||||
title: <TranslationHelper transClass={TranslationClass.PLUGIN_LOADER} transText="decky_title" />,
|
||||
body: (
|
||||
<TranslationHelper
|
||||
@@ -219,7 +247,9 @@ class PluginLoader extends Logger {
|
||||
i18nArgs={{ tag_name: versionInfo?.remote?.tag_name }}
|
||||
/>
|
||||
),
|
||||
onClick: () => Router.Navigate('/decky/settings'),
|
||||
logo: <DeckyIcon />,
|
||||
icon: <FaDownload />,
|
||||
onClick: () => Navigation.Navigate('/decky/settings'),
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -230,7 +260,9 @@ class PluginLoader extends Logger {
|
||||
public async checkPluginUpdates() {
|
||||
const frozenPlugins = this.deckyState.publicState().frozenPlugins;
|
||||
|
||||
const updates = await checkForPluginUpdates(this.plugins.filter((p) => !frozenPlugins.includes(p.name)));
|
||||
const updates = await checkForPluginUpdates(
|
||||
this.deckyState.publicState().installedPlugins.filter((p) => !frozenPlugins.includes(p.name)),
|
||||
);
|
||||
this.deckyState.setUpdates(updates);
|
||||
return updates;
|
||||
}
|
||||
@@ -238,7 +270,8 @@ class PluginLoader extends Logger {
|
||||
public async notifyPluginUpdates() {
|
||||
const updates = await this.checkPluginUpdates();
|
||||
if (updates?.size > 0 && this.notificationService.shouldNotify('pluginUpdates')) {
|
||||
this.toaster.toast({
|
||||
this.pluginUpdateToast && this.pluginUpdateToast.dismiss();
|
||||
this.pluginUpdateToast = this.toaster.toast({
|
||||
title: <TranslationHelper transClass={TranslationClass.PLUGIN_LOADER} transText="decky_title" />,
|
||||
body: (
|
||||
<TranslationHelper
|
||||
@@ -247,7 +280,9 @@ class PluginLoader extends Logger {
|
||||
i18nArgs={{ count: updates.size }}
|
||||
/>
|
||||
),
|
||||
onClick: () => Router.Navigate('/decky/settings/plugins'),
|
||||
logo: <DeckyIcon />,
|
||||
icon: <FaDownload />,
|
||||
onClick: () => Navigation.Navigate('/decky/settings/plugins'),
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -265,6 +300,7 @@ class PluginLoader extends Logger {
|
||||
version={version}
|
||||
hash={hash}
|
||||
installType={install_type}
|
||||
disabled={this.deckyState.publicState().disabledPlugins.some((p) => p.name === artifact)}
|
||||
onOK={() => DeckyBackend.call<[string]>('utilities/confirm_plugin_install', request_id)}
|
||||
onCancel={() => DeckyBackend.call<[string]>('utilities/cancel_plugin_install', request_id)}
|
||||
/>,
|
||||
@@ -278,6 +314,7 @@ class PluginLoader extends Logger {
|
||||
showModal(
|
||||
<MultiplePluginsInstallModal
|
||||
requests={requests}
|
||||
disabledPlugins={this.deckyState.publicState().disabledPlugins}
|
||||
onOK={() => DeckyBackend.call<[string]>('utilities/confirm_plugin_install', request_id)}
|
||||
onCancel={() => DeckyBackend.call<[string]>('utilities/cancel_plugin_install', request_id)}
|
||||
/>,
|
||||
@@ -285,7 +322,19 @@ class PluginLoader extends Logger {
|
||||
}
|
||||
|
||||
public uninstallPlugin(name: string, title: string, buttonText: string, description: string) {
|
||||
showModal(<PluginUninstallModal name={name} title={title} buttonText={buttonText} description={description} />);
|
||||
showModal(
|
||||
<PluginUninstallModal
|
||||
name={name}
|
||||
title={title}
|
||||
buttonText={buttonText}
|
||||
description={description}
|
||||
deckyState={this.deckyState}
|
||||
/>,
|
||||
);
|
||||
}
|
||||
|
||||
public disablePlugin(name: string, title: string, buttonText: string, description: string) {
|
||||
showModal(<PluginDisableModal name={name} title={title} buttonText={buttonText} description={description} />);
|
||||
}
|
||||
|
||||
public hasPlugin(name: string) {
|
||||
@@ -326,11 +375,24 @@ class PluginLoader extends Logger {
|
||||
this.errorBoundaryHook.deinit();
|
||||
}
|
||||
|
||||
public unloadPlugin(name: string) {
|
||||
public doDisablePlugin(name: string) {
|
||||
const plugin = this.plugins.find((plugin) => plugin.name === name);
|
||||
if (plugin == undefined) return;
|
||||
|
||||
plugin?.onDismount?.();
|
||||
this.plugins = this.plugins.filter((p) => p !== plugin);
|
||||
this.deckyState.setDisabledPlugins([
|
||||
...this.deckyState.publicState().disabledPlugins,
|
||||
{ name: plugin.name, version: plugin.version },
|
||||
]);
|
||||
this.deckyState.setPlugins(this.plugins);
|
||||
}
|
||||
|
||||
public unloadPlugin(name: string, skipStateUpdate: boolean = false) {
|
||||
const plugin = this.plugins.find((plugin) => plugin.name === name);
|
||||
plugin?.onDismount?.();
|
||||
this.plugins = this.plugins.filter((p) => p !== plugin);
|
||||
this.deckyState.setPlugins(this.plugins);
|
||||
if (!skipStateUpdate) this.deckyState.setPlugins(this.plugins);
|
||||
}
|
||||
|
||||
public async importPlugin(
|
||||
@@ -338,10 +400,11 @@ class PluginLoader extends Logger {
|
||||
version?: string | undefined,
|
||||
loadType: PluginLoadType = PluginLoadType.ESMODULE_V1,
|
||||
useQueue: boolean = true,
|
||||
timeoutMS?: number,
|
||||
) {
|
||||
if (useQueue && this.reloadLock) {
|
||||
this.log('Reload currently in progress, adding to queue', name);
|
||||
this.pluginReloadQueue.push({ name, version: version });
|
||||
this.pluginReloadQueue.push({ name, version: version, loadType });
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -349,11 +412,13 @@ class PluginLoader extends Logger {
|
||||
if (useQueue) this.reloadLock = true;
|
||||
this.log(`Trying to load ${name}`);
|
||||
|
||||
this.unloadPlugin(name);
|
||||
this.unloadPlugin(name, true);
|
||||
const startTime = performance.now();
|
||||
await this.importReactPlugin(name, version, loadType);
|
||||
|
||||
await this.importReactPlugin(name, version, loadType, timeoutMS);
|
||||
const endTime = performance.now();
|
||||
|
||||
this.deckyState.setDisabledPlugins(this.deckyState.publicState().disabledPlugins.filter((d) => d.name !== name));
|
||||
this.deckyState.setPlugins(this.plugins);
|
||||
this.log(`Loaded ${name} in ${endTime - startTime}ms`);
|
||||
} catch (e) {
|
||||
@@ -363,7 +428,7 @@ class PluginLoader extends Logger {
|
||||
this.reloadLock = false;
|
||||
const nextPlugin = this.pluginReloadQueue.shift();
|
||||
if (nextPlugin) {
|
||||
this.importPlugin(nextPlugin.name, nextPlugin.version);
|
||||
this.importPlugin(nextPlugin.name, nextPlugin.version, nextPlugin.loadType, true, timeoutMS);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -373,27 +438,59 @@ class PluginLoader extends Logger {
|
||||
name: string,
|
||||
version?: string,
|
||||
loadType: PluginLoadType = PluginLoadType.ESMODULE_V1,
|
||||
timeoutMS?: number,
|
||||
) {
|
||||
let spExists = this.checkForSP();
|
||||
const timeoutException = new Error(
|
||||
`${name} failed to load within ${timeoutMS ? `${timeoutMS / 1000} second` : ''} time limit`,
|
||||
);
|
||||
let timeout: number | undefined;
|
||||
|
||||
try {
|
||||
switch (loadType) {
|
||||
case PluginLoadType.ESMODULE_V1:
|
||||
const plugin_exports = await import(`http://127.0.0.1:1337/plugins/${name}/dist/index.js`);
|
||||
const importJS = () => import(`http://127.0.0.1:1337/plugins/${name}/dist/index.js?t=${Date.now()}`);
|
||||
|
||||
const promise =
|
||||
timeoutMS === undefined
|
||||
? importJS()
|
||||
: Promise.race([
|
||||
importJS(),
|
||||
new Promise((_, reject) => (timeout = setTimeout(() => reject(timeoutException), timeoutMS))),
|
||||
]);
|
||||
|
||||
const plugin_exports = await promise;
|
||||
let plugin = plugin_exports.default();
|
||||
|
||||
this.plugins.push({
|
||||
...plugin,
|
||||
name: name,
|
||||
version: version,
|
||||
loadType,
|
||||
});
|
||||
break;
|
||||
|
||||
case PluginLoadType.LEGACY_EVAL_IIFE:
|
||||
let res = await fetch(`http://127.0.0.1:1337/plugins/${name}/frontend_bundle`, {
|
||||
credentials: 'include',
|
||||
headers: {
|
||||
'X-Decky-Auth': deckyAuthToken,
|
||||
},
|
||||
});
|
||||
const fetchJS = async () => {
|
||||
const controller = new AbortController();
|
||||
const { signal } = controller;
|
||||
|
||||
if (timeoutMS !== undefined) timeout = setTimeout(() => controller.abort(), timeoutMS);
|
||||
|
||||
try {
|
||||
return await fetch(`http://127.0.0.1:1337/plugins/${name}/frontend_bundle`, {
|
||||
credentials: 'include',
|
||||
headers: {
|
||||
'X-Decky-Auth': deckyAuthToken,
|
||||
},
|
||||
signal,
|
||||
});
|
||||
} catch (e: any) {
|
||||
throw 'name' in e && e.name === 'AbortError' ? timeoutException : e;
|
||||
}
|
||||
};
|
||||
|
||||
let res = await fetchJS();
|
||||
if (res.ok) {
|
||||
let plugin_export: (serverAPI: any) => Plugin = await eval(
|
||||
(await res.text()) + `\n//# sourceURL=decky://decky/legacy_plugin/${encodeURIComponent(name)}/index.js`,
|
||||
@@ -403,6 +500,7 @@ class PluginLoader extends Logger {
|
||||
...plugin,
|
||||
name: name,
|
||||
version: version,
|
||||
loadType,
|
||||
});
|
||||
} else throw new Error(`${name} frontend_bundle not OK`);
|
||||
break;
|
||||
@@ -411,6 +509,8 @@ class PluginLoader extends Logger {
|
||||
throw new Error(`${name} has no defined loadType.`);
|
||||
}
|
||||
} catch (e) {
|
||||
if (e === timeoutException) throw timeoutException;
|
||||
|
||||
this.error('Error loading plugin ' + name, e);
|
||||
const TheError: FC<{}> = () => (
|
||||
<PanelSection>
|
||||
@@ -421,7 +521,7 @@ class PluginLoader extends Logger {
|
||||
</PanelSectionRow>
|
||||
<PanelSectionRow>
|
||||
<pre style={{ overflowX: 'scroll' }}>
|
||||
<code>{e instanceof Error ? e.stack : JSON.stringify(e)}</code>
|
||||
<code>{e instanceof Error ? '' + e.stack : JSON.stringify(e)}</code>
|
||||
</pre>
|
||||
</PanelSectionRow>
|
||||
<PanelSectionRow>
|
||||
@@ -440,6 +540,7 @@ class PluginLoader extends Logger {
|
||||
version: version,
|
||||
content: <TheError />,
|
||||
icon: <FaExclamationCircle />,
|
||||
loadType,
|
||||
});
|
||||
this.toaster.toast({
|
||||
title: (
|
||||
@@ -452,6 +553,14 @@ class PluginLoader extends Logger {
|
||||
body: '' + e,
|
||||
icon: <FaExclamationCircle />,
|
||||
});
|
||||
} finally {
|
||||
if (timeout !== undefined) clearTimeout(timeout);
|
||||
}
|
||||
|
||||
if (spExists && !this.checkForSP()) {
|
||||
// SP died after plugin loaded. Give up and let the loader's crash loop detection handle it.
|
||||
this.error('SP died after loading plugin. Restarting webhelper.');
|
||||
await this.restartWebhelper();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -527,18 +636,22 @@ class PluginLoader extends Logger {
|
||||
|
||||
// Same syntax as fetch but only supports the url-based syntax and an object for headers since it's the most common usage pattern
|
||||
fetchNoCors(input: string, init?: DeckyRequestInit | undefined): Promise<Response> {
|
||||
const headers: { [name: string]: string } = {
|
||||
...(init?.headers as { [name: string]: string }),
|
||||
'X-Decky-Auth': deckyAuthToken,
|
||||
'X-Decky-Fetch-URL': input,
|
||||
const { headers: initHeaders = {}, ...restOfInit } = init || {};
|
||||
const getPrefixedHeaders = () => {
|
||||
let prefixedInitHeaders: { [name: string]: any } = {};
|
||||
for (const [key, value] of Object.entries(initHeaders)) {
|
||||
prefixedInitHeaders[`X-Decky-Header-${key}`] = value;
|
||||
}
|
||||
return prefixedInitHeaders;
|
||||
};
|
||||
const headers: { [name: string]: string } = getPrefixedHeaders();
|
||||
|
||||
if (init?.excludedHeaders) {
|
||||
headers['X-Decky-Fetch-Excluded-Headers'] = init.excludedHeaders.join(', ');
|
||||
}
|
||||
|
||||
return fetch('http://127.0.0.1:1337/fetch', {
|
||||
...init,
|
||||
return fetch(this.getExternalResourceURL(input), {
|
||||
...restOfInit,
|
||||
credentials: 'include',
|
||||
headers,
|
||||
});
|
||||
@@ -554,7 +667,6 @@ class PluginLoader extends Logger {
|
||||
method = request.method;
|
||||
delete req.method;
|
||||
}
|
||||
// this is terrible but a. we're going to redo this entire method anyway and b. it was already terrible
|
||||
try {
|
||||
const ret = await DeckyBackend.call<
|
||||
[method: string, url: string, extra_opts?: any],
|
||||
@@ -567,11 +679,11 @@ class PluginLoader extends Logger {
|
||||
}
|
||||
|
||||
initPluginBackendAPI() {
|
||||
// Things will break *very* badly if plugin code touches this outside of @decky/backend, so lets make that clear.
|
||||
// Things will break *very* badly if plugin code touches this outside of @decky/api, so lets make that clear.
|
||||
window.__DECKY_SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED_deckyLoaderAPIInit = {
|
||||
connect: (version: number, pluginName: string) => {
|
||||
if (version < 1 || version > 1) {
|
||||
throw new Error(`Plugin ${pluginName} requested unsupported backend api version ${version}.`);
|
||||
if (version < 1 || version > 2) {
|
||||
console.warn(`Plugin ${pluginName} requested unsupported api version ${version}.`);
|
||||
}
|
||||
|
||||
const eventListeners: listenerMap = new Map();
|
||||
@@ -611,7 +723,13 @@ class PluginLoader extends Logger {
|
||||
removeCssFromTab: DeckyBackend.callable<[tab: string, cssId: string]>('utilities/remove_css_from_tab'),
|
||||
routerHook: this.routerHook,
|
||||
toaster: this.toaster,
|
||||
};
|
||||
_version: 1,
|
||||
} as any;
|
||||
|
||||
if (version >= 2) {
|
||||
backendAPI._version = 2;
|
||||
backendAPI.useQuickAccessVisible = useQuickAccessVisible;
|
||||
}
|
||||
|
||||
this.debug(`${pluginName} connected to loader API.`);
|
||||
return backendAPI;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import type { JSX } from 'react';
|
||||
export enum PluginLoadType {
|
||||
LEGACY_EVAL_IIFE = 0, // legacy, uses legacy serverAPI
|
||||
ESMODULE_V1 = 1, // esmodule loading with modern @decky/backend apis
|
||||
@@ -6,6 +7,7 @@ export enum PluginLoadType {
|
||||
export interface Plugin {
|
||||
name: string;
|
||||
version?: string;
|
||||
loadType?: PluginLoadType;
|
||||
icon: JSX.Element;
|
||||
content?: JSX.Element;
|
||||
onDismount?(): void;
|
||||
@@ -13,12 +15,26 @@ export interface Plugin {
|
||||
titleView?: JSX.Element;
|
||||
}
|
||||
|
||||
export type DisabledPlugin = Pick<Plugin, 'name' | 'version'>;
|
||||
|
||||
export enum InstallType {
|
||||
INSTALL,
|
||||
REINSTALL,
|
||||
UPDATE,
|
||||
DOWNGRADE,
|
||||
OVERWRITE,
|
||||
}
|
||||
|
||||
// values are the JSON keys used in the translation file
|
||||
// IMPORTANT! keep in sync with `t(...)` comments where this is used
|
||||
export const InstallTypeTranslationMapping = {
|
||||
[InstallType.INSTALL]: 'install',
|
||||
[InstallType.REINSTALL]: 'reinstall',
|
||||
[InstallType.UPDATE]: 'update',
|
||||
[InstallType.DOWNGRADE]: 'downgrade',
|
||||
[InstallType.OVERWRITE]: 'overwrite',
|
||||
} as const satisfies Record<InstallType, string>;
|
||||
|
||||
type installPluginArgs = [
|
||||
artifact: string,
|
||||
name?: string,
|
||||
@@ -42,3 +58,5 @@ type installPluginsArgs = [
|
||||
export let installPlugins = DeckyBackend.callable<installPluginsArgs>('utilities/install_plugins');
|
||||
|
||||
export let uninstallPlugin = DeckyBackend.callable<[name: string]>('utilities/uninstall_plugin');
|
||||
export let enablePlugin = DeckyBackend.callable<[name: string]>('utilities/enable_plugin');
|
||||
export let disablePlugin = DeckyBackend.callable<[name: string]>('utilities/disable_plugin');
|
||||
|
||||
+284
-108
@@ -1,5 +1,15 @@
|
||||
import { ErrorBoundary, Focusable, Patch, afterPatch } from '@decky/ui';
|
||||
import { FC, ReactElement, ReactNode, cloneElement, createElement, memo } from 'react';
|
||||
import {
|
||||
EUIMode,
|
||||
ErrorBoundary,
|
||||
Patch,
|
||||
afterPatch,
|
||||
findInReactTree,
|
||||
findInTree,
|
||||
findModuleByExport,
|
||||
getReactRoot,
|
||||
sleep,
|
||||
} from '@decky/ui';
|
||||
import { FC, JSX, ReactElement, ReactNode, cloneElement, createElement } from 'react';
|
||||
import type { Route } from 'react-router';
|
||||
|
||||
import {
|
||||
@@ -25,13 +35,18 @@ declare global {
|
||||
const isPatched = Symbol('is patched');
|
||||
|
||||
class RouterHook extends Logger {
|
||||
private router: any;
|
||||
private memoizedRouter: any;
|
||||
private gamepadWrapper: any;
|
||||
private routerState: DeckyRouterState = new DeckyRouterState();
|
||||
private globalComponentsState: DeckyGlobalComponentsState = new DeckyGlobalComponentsState();
|
||||
private wrapperPatch: Patch;
|
||||
private routerPatch?: Patch;
|
||||
private renderedComponents: ReactElement<any>[] = [];
|
||||
private Route: any;
|
||||
private DeckyGamepadRouterWrapper = this.gamepadRouterWrapper.bind(this);
|
||||
private DeckyDesktopRouterWrapper = this.desktopRouterWrapper.bind(this);
|
||||
private DeckyGlobalComponentsWrapper = this.globalComponentsWrapper.bind(this);
|
||||
private toReplace = new Map<string, ReactNode>();
|
||||
private desktopRouterPatch?: Patch;
|
||||
private gamepadRouterPatch?: Patch;
|
||||
private modeChangeRegistration?: any;
|
||||
private patchedModes = new Set<number>();
|
||||
public routes?: any[];
|
||||
|
||||
constructor() {
|
||||
@@ -41,112 +56,272 @@ class RouterHook extends Logger {
|
||||
window.__ROUTER_HOOK_INSTANCE?.deinit?.();
|
||||
window.__ROUTER_HOOK_INSTANCE = this;
|
||||
|
||||
this.gamepadWrapper = Focusable;
|
||||
|
||||
let Route: new () => Route;
|
||||
// Used to store the new replicated routes we create to allow routes to be unpatched.
|
||||
const processList = (
|
||||
routeList: any[],
|
||||
routes: Map<string, RouterEntry> | null,
|
||||
routePatches: Map<string, Set<RoutePatch>>,
|
||||
save: boolean,
|
||||
) => {
|
||||
this.debug('Route list: ', routeList);
|
||||
if (save) this.routes = routeList;
|
||||
let routerIndex = routeList.length;
|
||||
if (routes) {
|
||||
if (!routeList[routerIndex - 1]?.length || routeList[routerIndex - 1]?.length !== routes.size) {
|
||||
if (routeList[routerIndex - 1]?.length && routeList[routerIndex - 1].length !== routes.size) routerIndex--;
|
||||
const newRouterArray: (ReactElement | JSX.Element)[] = [];
|
||||
routes.forEach(({ component, props }, path) => {
|
||||
newRouterArray.push(
|
||||
<Route path={path} {...props}>
|
||||
<ErrorBoundary>{createElement(component)}</ErrorBoundary>
|
||||
</Route>,
|
||||
);
|
||||
});
|
||||
routeList[routerIndex] = newRouterArray;
|
||||
}
|
||||
const reactRouterStackModule = findModuleByExport((e) => e == 'router-backstack', 20);
|
||||
if (reactRouterStackModule) {
|
||||
this.Route =
|
||||
Object.values(reactRouterStackModule).find(
|
||||
(e) => typeof e == 'function' && /routePath:.\.match\?\.path./.test(e.toString()),
|
||||
) ||
|
||||
Object.values(reactRouterStackModule).find(
|
||||
(e) => typeof e == 'function' && /routePath:null===\(.=.\.match\)/.test(e.toString()),
|
||||
);
|
||||
if (!this.Route) {
|
||||
this.error('Failed to find Route component');
|
||||
}
|
||||
routeList.forEach((route: Route, index: number) => {
|
||||
const replaced = toReplace.get(route?.props?.path as string);
|
||||
if (replaced) {
|
||||
routeList[index].props.children = replaced;
|
||||
toReplace.delete(route?.props?.path as string);
|
||||
}
|
||||
if (route?.props?.path && routePatches.has(route.props.path as string)) {
|
||||
toReplace.set(
|
||||
route?.props?.path as string,
|
||||
// @ts-ignore
|
||||
routeList[index].props.children,
|
||||
);
|
||||
routePatches.get(route.props.path as string)?.forEach((patch) => {
|
||||
const oType = routeList[index].props.children.type;
|
||||
routeList[index].props.children = patch({
|
||||
...routeList[index].props,
|
||||
children: {
|
||||
...cloneElement(routeList[index].props.children),
|
||||
type: routeList[index].props.children[isPatched] ? oType : (props) => createElement(oType, props),
|
||||
},
|
||||
}).children;
|
||||
routeList[index].props.children[isPatched] = true;
|
||||
});
|
||||
}
|
||||
} else {
|
||||
this.error('Failed to find router stack module');
|
||||
}
|
||||
|
||||
this.modeChangeRegistration = SteamClient.UI.RegisterForUIModeChanged((mode: EUIMode) => {
|
||||
this.debug(`UI mode changed to ${mode}`);
|
||||
if (this.patchedModes.has(mode)) return;
|
||||
this.patchedModes.add(mode);
|
||||
this.debug(`Patching router for UI mode ${mode}`);
|
||||
switch (mode) {
|
||||
case EUIMode.GamePad:
|
||||
this.debug('Patching gamepad router');
|
||||
this.patchGamepadRouter();
|
||||
break;
|
||||
// Not fully implemented yet
|
||||
// case UIMode.Desktop:
|
||||
// this.debug("Patching desktop router");
|
||||
// this.patchDesktopRouter();
|
||||
// break;
|
||||
default:
|
||||
this.warn(`Router patch not implemented for UI mode ${mode}`);
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private async patchGamepadRouter() {
|
||||
const root = getReactRoot(document.getElementById('root') as any);
|
||||
const findRouterNode = () =>
|
||||
findInReactTree(
|
||||
root,
|
||||
(node) =>
|
||||
typeof node?.pendingProps?.loggedIn == 'undefined' && node?.type?.toString().includes('Settings.Root()'),
|
||||
);
|
||||
await this.waitForUnlock();
|
||||
let routerNode = findRouterNode();
|
||||
while (!routerNode) {
|
||||
this.warn('Failed to find Router node, reattempting in 5 seconds.');
|
||||
await sleep(5000);
|
||||
await this.waitForUnlock();
|
||||
routerNode = findRouterNode();
|
||||
}
|
||||
if (routerNode) {
|
||||
// Patch the component globally
|
||||
this.gamepadRouterPatch = afterPatch(routerNode.elementType, 'type', this.handleGamepadRouterRender.bind(this));
|
||||
// Swap out the current instance
|
||||
routerNode.type = routerNode.elementType.type;
|
||||
if (routerNode?.alternate) {
|
||||
routerNode.alternate.type = routerNode.type;
|
||||
}
|
||||
// Force a full rerender via our custom error boundary
|
||||
const errorBoundaryNode = findInTree(routerNode, (e) => e?.stateNode?._deckyForceRerender, {
|
||||
walkable: ['return'],
|
||||
});
|
||||
};
|
||||
let toReplace = new Map<string, ReactNode>();
|
||||
const DeckyWrapper = ({ children }: { children: ReactElement }) => {
|
||||
const { routes, routePatches } = useDeckyRouterState();
|
||||
const mainRouteList = children.props.children[0].props.children;
|
||||
const ingameRouteList = children.props.children[1].props.children; // /appoverlay and /apprunning
|
||||
processList(mainRouteList, routes, routePatches, true);
|
||||
processList(ingameRouteList, null, routePatches, false);
|
||||
errorBoundaryNode?.stateNode?._deckyForceRerender?.();
|
||||
}
|
||||
}
|
||||
|
||||
this.debug('Rerendered routes list');
|
||||
return children;
|
||||
};
|
||||
|
||||
let renderedComponents: ReactElement[] = [];
|
||||
|
||||
const DeckyGlobalComponentsWrapper = () => {
|
||||
const { components } = useDeckyGlobalComponentsState();
|
||||
if (renderedComponents.length != components.size) {
|
||||
this.debug('Rerendering global components');
|
||||
renderedComponents = Array.from(components.values()).map((GComponent) => <GComponent />);
|
||||
// Currently unused
|
||||
// @ts-expect-error 6133
|
||||
private async patchDesktopRouter() {
|
||||
const root = getReactRoot(document.getElementById('root') as any);
|
||||
const findRouterNode = () =>
|
||||
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.');
|
||||
await sleep(5000);
|
||||
routerNode = findRouterNode();
|
||||
}
|
||||
if (routerNode) {
|
||||
// this.debug("desktop router node", routerNode);
|
||||
// Patch the component globally
|
||||
this.desktopRouterPatch = afterPatch(routerNode.elementType, 'type', this.handleDesktopRouterRender.bind(this));
|
||||
// Swap out the current instance
|
||||
routerNode.type = routerNode.elementType.type;
|
||||
if (routerNode?.alternate) {
|
||||
routerNode.alternate.type = routerNode.type;
|
||||
}
|
||||
return <>{renderedComponents}</>;
|
||||
};
|
||||
// Force a full rerender via our custom error boundary
|
||||
const errorBoundaryNode = findInTree(routerNode, (e) => e?.stateNode?._deckyForceRerender, {
|
||||
walkable: ['return'],
|
||||
});
|
||||
errorBoundaryNode?.stateNode?._deckyForceRerender?.();
|
||||
// this.debug("desktop router node", routerNode);
|
||||
// // Patch the component globally
|
||||
// this.desktopRouterPatch = afterPatch(routerNode.type.prototype, 'render', this.handleDesktopRouterRender.bind(this));
|
||||
// const stateNodeClone = { render: routerNode.stateNode.render } as any;
|
||||
// // Patch the current instance. render is readonly so we have to do this.
|
||||
// Object.assign(stateNodeClone, routerNode.stateNode);
|
||||
// Object.setPrototypeOf(stateNodeClone, Object.getPrototypeOf(routerNode.stateNode));
|
||||
// this.desktopRouterFirstInstancePatch = afterPatch(stateNodeClone, 'render', this.handleDesktopRouterRender.bind(this));
|
||||
// routerNode.stateNode = stateNodeClone;
|
||||
// // Swap out the current instance
|
||||
// if (routerNode?.alternate) {
|
||||
// routerNode.alternate.type = routerNode.type;
|
||||
// routerNode.alternate.stateNode = routerNode.stateNode;
|
||||
// }
|
||||
// routerNode.stateNode.forceUpdate();
|
||||
// Force a full rerender via our custom error boundary
|
||||
// const errorBoundaryNode = findInTree(routerNode, e => e?.stateNode?._deckyForceRerender, { walkable: ["return"] });
|
||||
// errorBoundaryNode?.stateNode?._deckyForceRerender?.();
|
||||
}
|
||||
}
|
||||
|
||||
this.wrapperPatch = afterPatch(this.gamepadWrapper, 'render', (_: any, ret: any) => {
|
||||
if (ret?.props?.children?.props?.children?.length == 5 || ret?.props?.children?.props?.children?.length == 4) {
|
||||
const idx = ret?.props?.children?.props?.children?.length == 4 ? 1 : 2;
|
||||
const potentialSettingsRootString =
|
||||
ret.props.children.props.children[idx]?.props?.children?.[0]?.type?.type?.toString() || '';
|
||||
if (potentialSettingsRootString?.includes('Settings.Root()')) {
|
||||
if (!this.router) {
|
||||
this.router = ret.props.children.props.children[idx]?.props?.children?.[0]?.type;
|
||||
this.routerPatch = afterPatch(this.router, 'type', (_: any, ret: any) => {
|
||||
if (!Route)
|
||||
Route = ret.props.children[0].props.children.find((x: any) => x.props.path == '/createaccount').type;
|
||||
const returnVal = (
|
||||
<DeckyRouterStateContextProvider deckyRouterState={this.routerState}>
|
||||
<DeckyWrapper>{ret}</DeckyWrapper>
|
||||
</DeckyRouterStateContextProvider>
|
||||
);
|
||||
return returnVal;
|
||||
});
|
||||
this.memoizedRouter = memo(this.router.type);
|
||||
this.memoizedRouter.isDeckyRouter = true;
|
||||
}
|
||||
ret.props.children.props.children.push(
|
||||
<DeckyGlobalComponentsStateContextProvider deckyGlobalComponentsState={this.globalComponentsState}>
|
||||
<DeckyGlobalComponentsWrapper />
|
||||
</DeckyGlobalComponentsStateContextProvider>,
|
||||
);
|
||||
ret.props.children.props.children[idx].props.children[0].type = this.memoizedRouter;
|
||||
}
|
||||
public async waitForUnlock() {
|
||||
try {
|
||||
while (window?.securitystore?.IsLockScreenActive?.()) {
|
||||
await sleep(500);
|
||||
}
|
||||
} catch (e) {
|
||||
this.warn('Error while checking if unlocked:', e);
|
||||
}
|
||||
}
|
||||
|
||||
public handleDesktopRouterRender(_: any, ret: any) {
|
||||
const DeckyDesktopRouterWrapper = this.DeckyDesktopRouterWrapper;
|
||||
const DeckyGlobalComponentsWrapper = this.DeckyGlobalComponentsWrapper;
|
||||
this.debug('desktop router render', ret);
|
||||
if (ret._decky) {
|
||||
return ret;
|
||||
}
|
||||
const returnVal = (
|
||||
<>
|
||||
<DeckyRouterStateContextProvider deckyRouterState={this.routerState}>
|
||||
<DeckyDesktopRouterWrapper>{ret}</DeckyDesktopRouterWrapper>
|
||||
</DeckyRouterStateContextProvider>
|
||||
<DeckyGlobalComponentsStateContextProvider deckyGlobalComponentsState={this.globalComponentsState}>
|
||||
<DeckyGlobalComponentsWrapper />
|
||||
</DeckyGlobalComponentsStateContextProvider>
|
||||
</>
|
||||
);
|
||||
(returnVal as any)._decky = true;
|
||||
return returnVal;
|
||||
}
|
||||
|
||||
public handleGamepadRouterRender(_: any, ret: any) {
|
||||
const DeckyGamepadRouterWrapper = this.DeckyGamepadRouterWrapper;
|
||||
const DeckyGlobalComponentsWrapper = this.DeckyGlobalComponentsWrapper;
|
||||
if (ret._decky) {
|
||||
return ret;
|
||||
}
|
||||
const returnVal = (
|
||||
<>
|
||||
<DeckyRouterStateContextProvider deckyRouterState={this.routerState}>
|
||||
<DeckyGamepadRouterWrapper>{ret}</DeckyGamepadRouterWrapper>
|
||||
</DeckyRouterStateContextProvider>
|
||||
<DeckyGlobalComponentsStateContextProvider deckyGlobalComponentsState={this.globalComponentsState}>
|
||||
<DeckyGlobalComponentsWrapper />
|
||||
</DeckyGlobalComponentsStateContextProvider>
|
||||
</>
|
||||
);
|
||||
(returnVal as any)._decky = true;
|
||||
return returnVal;
|
||||
}
|
||||
|
||||
private globalComponentsWrapper() {
|
||||
const { components } = useDeckyGlobalComponentsState();
|
||||
if (this.renderedComponents.length != components.size) {
|
||||
this.debug('Rerendering global components');
|
||||
this.renderedComponents = Array.from(components.values()).map((GComponent) => <GComponent />);
|
||||
}
|
||||
return <>{this.renderedComponents}</>;
|
||||
}
|
||||
|
||||
private gamepadRouterWrapper({ children }: { children: ReactElement<any> }) {
|
||||
// Used to store the new replicated routes we create to allow routes to be unpatched.
|
||||
|
||||
const { routes, routePatches } = useDeckyRouterState();
|
||||
// TODO make more redundant
|
||||
if (!children?.props?.children?.[0]?.props?.children) {
|
||||
this.debug('routerWrapper wrong component?', children);
|
||||
return children;
|
||||
}
|
||||
const mainRouteList = children.props.children[0].props.children;
|
||||
const ingameRouteList = children.props.children[1].props.children; // /appoverlay and /apprunning
|
||||
this.processList(mainRouteList, routes, routePatches, true);
|
||||
this.processList(ingameRouteList, null, routePatches, false);
|
||||
|
||||
this.debug('Rerendered gamepadui routes list');
|
||||
return children;
|
||||
}
|
||||
|
||||
private desktopRouterWrapper({ children }: { children: ReactElement<any> }) {
|
||||
// Used to store the new replicated routes we create to allow routes to be unpatched.
|
||||
this.debug('desktop router wrapper render', children);
|
||||
const { routes, routePatches } = useDeckyRouterState();
|
||||
const routeList = findInReactTree(
|
||||
children,
|
||||
(node) => node?.length > 2 && node?.find((elem: any) => elem?.props?.path == '/library/home'),
|
||||
);
|
||||
if (!routeList) {
|
||||
this.debug('routerWrapper wrong component?', children);
|
||||
return children;
|
||||
}
|
||||
const library = children.props.children[1].props.children.props;
|
||||
if (!Array.isArray(library.children)) {
|
||||
library.children = [library.children];
|
||||
}
|
||||
this.debug('library', library);
|
||||
this.processList(library.children, routes, routePatches, true);
|
||||
|
||||
this.debug('Rerendered desktop routes list');
|
||||
return children;
|
||||
}
|
||||
|
||||
private processList(
|
||||
routeList: any[],
|
||||
routes: Map<string, RouterEntry> | null,
|
||||
routePatches: Map<string, Set<RoutePatch>>,
|
||||
save: boolean,
|
||||
) {
|
||||
const Route = this.Route;
|
||||
this.debug('Route list: ', routeList);
|
||||
if (save) this.routes = routeList;
|
||||
let routerIndex = routeList.length;
|
||||
if (routes) {
|
||||
if (!routeList[routerIndex - 1]?.length || routeList[routerIndex - 1]?.length !== routes.size) {
|
||||
if (routeList[routerIndex - 1]?.length && routeList[routerIndex - 1].length !== routes.size) routerIndex--;
|
||||
const newRouterArray: (ReactElement<any> | JSX.Element)[] = [];
|
||||
routes.forEach(({ component, props }, path) => {
|
||||
newRouterArray.push(
|
||||
<Route path={path} {...props}>
|
||||
<ErrorBoundary>{createElement(component)}</ErrorBoundary>
|
||||
</Route>,
|
||||
);
|
||||
});
|
||||
routeList[routerIndex] = newRouterArray;
|
||||
}
|
||||
}
|
||||
routeList.forEach((route: Route, index: number) => {
|
||||
const replaced = this.toReplace.get(route?.props?.path as string);
|
||||
if (replaced) {
|
||||
routeList[index].props.children = replaced;
|
||||
this.toReplace.delete(route?.props?.path as string);
|
||||
}
|
||||
if (route?.props?.path && routePatches.has(route.props.path as string)) {
|
||||
this.toReplace.set(
|
||||
route?.props?.path as string,
|
||||
// @ts-ignore
|
||||
routeList[index].props.children,
|
||||
);
|
||||
routePatches.get(route.props.path as string)?.forEach((patch) => {
|
||||
const oType = routeList[index].props.children.type;
|
||||
routeList[index].props.children = patch({
|
||||
...routeList[index].props,
|
||||
children: {
|
||||
...cloneElement(routeList[index].props.children),
|
||||
type: routeList[index].props.children[isPatched] ? oType : (props) => createElement(oType, props),
|
||||
},
|
||||
}).children;
|
||||
routeList[index].props.children[isPatched] = true;
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -175,8 +350,9 @@ class RouterHook extends Logger {
|
||||
}
|
||||
|
||||
deinit() {
|
||||
this.wrapperPatch.unpatch();
|
||||
this.routerPatch?.unpatch();
|
||||
this.modeChangeRegistration?.unregister();
|
||||
this.gamepadRouterPatch?.unpatch();
|
||||
this.desktopRouterPatch?.unpatch();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
// import reloadFix from './reload';
|
||||
import restartFix from './restart';
|
||||
// import restartFix from './restart';
|
||||
import cefSocketFix from './socket';
|
||||
|
||||
let fixes: Function[] = [];
|
||||
|
||||
export function deinitSteamFixes() {
|
||||
@@ -7,6 +8,6 @@ export function deinitSteamFixes() {
|
||||
}
|
||||
|
||||
export async function initSteamFixes() {
|
||||
// fixes.push(await reloadFix());
|
||||
fixes.push(await restartFix());
|
||||
fixes.push(cefSocketFix());
|
||||
// fixes.push(await restartFix());
|
||||
}
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
import Logger from '../logger';
|
||||
|
||||
const logger = new Logger('CEFSocketFix');
|
||||
|
||||
const closeCEFSocket = DeckyBackend.callable<[], void>('utilities/close_cef_socket');
|
||||
|
||||
export default function cefSocketFix() {
|
||||
const reg = window.SteamClient?.User?.RegisterForShutdownStart(async () => {
|
||||
logger.log('Closing CEF socket before shutdown');
|
||||
await closeCEFSocket();
|
||||
});
|
||||
|
||||
if (reg) logger.debug('CEF shutdown handler ready');
|
||||
|
||||
return () => reg?.unregister();
|
||||
}
|
||||
+16
-24
@@ -1,4 +1,6 @@
|
||||
import { InstallType, Plugin, installPlugin, installPlugins } from './plugin';
|
||||
import { compare, validate } from 'compare-versions';
|
||||
|
||||
import { DisabledPlugin, InstallType, Plugin, installPlugin, installPlugins } from './plugin';
|
||||
import { getSetting, setSetting } from './utils/settings';
|
||||
|
||||
export enum Store {
|
||||
@@ -66,27 +68,6 @@ export async function getPluginList(
|
||||
await setSetting('store', Store.Default);
|
||||
store = Store.Default;
|
||||
}
|
||||
switch (+store) {
|
||||
case Store.Default:
|
||||
storeURL = 'https://plugins.deckbrew.xyz/plugins';
|
||||
break;
|
||||
case Store.Testing:
|
||||
storeURL = 'https://testing.deckbrew.xyz/plugins';
|
||||
break;
|
||||
case Store.Custom:
|
||||
storeURL = customURL;
|
||||
break;
|
||||
default:
|
||||
console.error('Somehow you ended up without a standard URL, using the default URL.');
|
||||
storeURL = 'https://plugins.deckbrew.xyz/plugins';
|
||||
break;
|
||||
return fetch(storeURL, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'X-Decky-Version': version.current,
|
||||
},
|
||||
}).then((r) => r.json());
|
||||
}
|
||||
switch (+store) {
|
||||
case Store.Default:
|
||||
storeURL = 'https://plugins.deckbrew.xyz/plugins';
|
||||
@@ -132,12 +113,23 @@ export async function requestMultiplePluginInstalls(requests: PluginInstallReque
|
||||
);
|
||||
}
|
||||
|
||||
export async function checkForPluginUpdates(plugins: Plugin[]): Promise<PluginUpdateMapping> {
|
||||
export async function checkForPluginUpdates(plugins: (Plugin | DisabledPlugin)[]): Promise<PluginUpdateMapping> {
|
||||
const serverData = await getPluginList();
|
||||
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.0';
|
||||
|
||||
if (
|
||||
remotePlugin &&
|
||||
remotePlugin.versions?.length > 0 &&
|
||||
plugin.version != remotePlugin?.versions?.[0]?.name &&
|
||||
validate(remotePlugin.versions?.[0]?.name) &&
|
||||
validate(curVer) &&
|
||||
compare(remotePlugin?.versions?.[0]?.name, curVer, '>')
|
||||
) {
|
||||
updateMap.set(plugin.name, remotePlugin.versions[0]);
|
||||
}
|
||||
}
|
||||
|
||||
+49
-77
@@ -1,5 +1,14 @@
|
||||
// TabsHook for versions after the Desktop merge
|
||||
import { ErrorBoundary, Patch, QuickAccessTab, afterPatch, findInReactTree, getReactRoot, sleep } from '@decky/ui';
|
||||
import {
|
||||
ErrorBoundary,
|
||||
Patch,
|
||||
QuickAccessTab,
|
||||
afterPatch,
|
||||
createReactTreePatcher,
|
||||
findInReactTree,
|
||||
findModuleByExport,
|
||||
getReactRoot,
|
||||
} from '@decky/ui';
|
||||
|
||||
import { QuickAccessVisibleStateProvider } from './components/QuickAccessVisibleState';
|
||||
import Logger from './logger';
|
||||
@@ -20,8 +29,8 @@ interface Tab {
|
||||
class TabsHook extends Logger {
|
||||
// private keys = 7;
|
||||
tabs: Tab[] = [];
|
||||
private qAMRoot?: any;
|
||||
private qamPatch?: Patch;
|
||||
private qamBrowserViewPatch?: Patch;
|
||||
private qamEmbeddedPatch?: Patch;
|
||||
|
||||
constructor() {
|
||||
super('TabsHook');
|
||||
@@ -32,87 +41,50 @@ class TabsHook extends Logger {
|
||||
}
|
||||
|
||||
init() {
|
||||
const tree = getReactRoot(document.getElementById('root') as any);
|
||||
let qAMRoot: any;
|
||||
const findQAMRoot = (currentNode: any, iters: number): any => {
|
||||
if (iters >= 80) {
|
||||
// currently 67
|
||||
return null;
|
||||
}
|
||||
if (
|
||||
(typeof currentNode?.memoizedProps?.visible == 'boolean' ||
|
||||
typeof currentNode?.memoizedProps?.active == 'boolean') &&
|
||||
currentNode?.type?.toString()?.includes('QuickAccessMenuBrowserView')
|
||||
) {
|
||||
this.log(`QAM root was found in ${iters} recursion cycles`);
|
||||
return currentNode;
|
||||
}
|
||||
if (currentNode.child) {
|
||||
let node = findQAMRoot(currentNode.child, iters + 1);
|
||||
if (node !== null) return node;
|
||||
}
|
||||
if (currentNode.sibling) {
|
||||
let node = findQAMRoot(currentNode.sibling, iters + 1);
|
||||
if (node !== null) return node;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
(async () => {
|
||||
qAMRoot = findQAMRoot(tree, 0);
|
||||
while (!qAMRoot) {
|
||||
this.error(
|
||||
'Failed to find QAM root node, reattempting in 5 seconds. A developer may need to increase the recursion limit.',
|
||||
);
|
||||
await sleep(5000);
|
||||
qAMRoot = findQAMRoot(tree, 0);
|
||||
}
|
||||
this.qAMRoot = qAMRoot;
|
||||
let patchedInnerQAM: any;
|
||||
this.qamPatch = afterPatch(qAMRoot.return, 'type', (_: any, ret: any) => {
|
||||
try {
|
||||
if (!qAMRoot?.child) {
|
||||
qAMRoot = findQAMRoot(tree, 0);
|
||||
this.qAMRoot = qAMRoot;
|
||||
}
|
||||
if (qAMRoot?.child && !qAMRoot?.child?.type?.decky) {
|
||||
afterPatch(qAMRoot.child, 'type', (_: any, ret: any) => {
|
||||
try {
|
||||
const qamTabsRenderer = findInReactTree(ret, (x) => x?.props?.onFocusNavDeactivated);
|
||||
if (patchedInnerQAM) {
|
||||
qamTabsRenderer.type = patchedInnerQAM;
|
||||
} else {
|
||||
afterPatch(qamTabsRenderer, 'type', (innerArgs: any, ret: any) => {
|
||||
const tabs = findInReactTree(ret, (x) => x?.props?.tabs);
|
||||
this.render(tabs.props.tabs, innerArgs[0].visible);
|
||||
return ret;
|
||||
});
|
||||
patchedInnerQAM = qamTabsRenderer.type;
|
||||
}
|
||||
} catch (e) {
|
||||
this.error('Error patching QAM inner', e);
|
||||
}
|
||||
return ret;
|
||||
});
|
||||
qAMRoot.child.type.decky = true;
|
||||
qAMRoot.child.alternate.type = qAMRoot.child.type;
|
||||
}
|
||||
} catch (e) {
|
||||
this.error('Error patching QAM', e);
|
||||
}
|
||||
const qamModule = findModuleByExport((e) => e?.type?.toString?.()?.includes('QuickAccessMenuBrowserView'));
|
||||
const qamBrowserViewRenderer = Object.values(qamModule).find((e: any) =>
|
||||
e?.type?.toString?.()?.includes('QuickAccessMenuBrowserView'),
|
||||
);
|
||||
const qamEmbeddedRenderer = Object.values(qamModule).find((e: any) =>
|
||||
e?.type?.toString?.()?.includes('QuickAccessMenuEmbedded'),
|
||||
);
|
||||
|
||||
const patchHandler = createReactTreePatcher(
|
||||
[(tree) => findInReactTree(tree, (node) => node?.props?.onFocusNavDeactivated)],
|
||||
(args, ret) => {
|
||||
const tabs = findInReactTree(ret, (x) => x?.props?.tabs);
|
||||
this.render(tabs.props.tabs, args[0].visible);
|
||||
return ret;
|
||||
});
|
||||
},
|
||||
'TabsHook',
|
||||
);
|
||||
|
||||
if (qAMRoot.return.alternate) {
|
||||
qAMRoot.return.alternate.type = qAMRoot.return.type;
|
||||
this.qamBrowserViewPatch = afterPatch(qamBrowserViewRenderer, 'type', patchHandler);
|
||||
if (qamEmbeddedRenderer) this.qamEmbeddedPatch = afterPatch(qamEmbeddedRenderer, 'type', patchHandler);
|
||||
|
||||
// Patch already rendered qam
|
||||
const root = getReactRoot(document.getElementById('root') as any);
|
||||
const qamNode =
|
||||
root &&
|
||||
findInReactTree(
|
||||
root,
|
||||
(n: any) =>
|
||||
n.elementType == qamBrowserViewRenderer ||
|
||||
(qamEmbeddedRenderer != null && n.elementType == qamEmbeddedRenderer),
|
||||
); // need elementType, because type is actually mobx wrapper
|
||||
if (qamNode) {
|
||||
console.log('patching existing qam');
|
||||
// Only affects this fiber node so we don't need to unpatch here
|
||||
qamNode.type = qamNode.elementType.type;
|
||||
if (qamNode?.alternate) {
|
||||
qamNode.alternate.type = qamNode.type;
|
||||
}
|
||||
this.log('Finished initial injection');
|
||||
})();
|
||||
}
|
||||
}
|
||||
|
||||
deinit() {
|
||||
this.qamPatch?.unpatch();
|
||||
this.qAMRoot.return.alternate.type = this.qAMRoot.return.type;
|
||||
this.qamBrowserViewPatch?.unpatch();
|
||||
this.qamEmbeddedPatch?.unpatch();
|
||||
}
|
||||
|
||||
add(tab: Tab) {
|
||||
|
||||
+86
-159
@@ -1,19 +1,16 @@
|
||||
import type { ToastData } from '@decky/api';
|
||||
import {
|
||||
Export,
|
||||
Patch,
|
||||
afterPatch,
|
||||
findClass,
|
||||
findInReactTree,
|
||||
findModuleExport,
|
||||
getReactRoot,
|
||||
sleep,
|
||||
} from '@decky/ui';
|
||||
import { ReactNode } from 'react';
|
||||
import type { ToastData, ToastNotification } from '@decky/api';
|
||||
import { ErrorBoundary, Patch, callOriginal, findModuleExport, injectFCTrampoline, replacePatch } from '@decky/ui';
|
||||
|
||||
import Toast from './components/Toast';
|
||||
import Logger from './logger';
|
||||
|
||||
// TODO export
|
||||
enum ToastType {
|
||||
New,
|
||||
Update,
|
||||
Remove,
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
__TOASTER_INSTANCE: any;
|
||||
@@ -23,176 +20,106 @@ declare global {
|
||||
}
|
||||
|
||||
class Toaster extends Logger {
|
||||
// private routerHook: RouterHook;
|
||||
// private toasterState: DeckyToasterState = new DeckyToasterState();
|
||||
private node: any;
|
||||
private rNode: any;
|
||||
private audioModule: any;
|
||||
private finishStartup?: () => void;
|
||||
private ready: Promise<void> = new Promise((res) => (this.finishStartup = res));
|
||||
private toasterPatch?: Patch;
|
||||
private toastPatch?: Patch;
|
||||
|
||||
constructor() {
|
||||
super('Toaster');
|
||||
// this.routerHook = routerHook;
|
||||
|
||||
window.__TOASTER_INSTANCE?.deinit?.();
|
||||
window.__TOASTER_INSTANCE = this;
|
||||
this.init();
|
||||
}
|
||||
|
||||
async init() {
|
||||
// this.routerHook.addGlobalComponent('DeckyToaster', () => (
|
||||
// <DeckyToasterStateContextProvider deckyToasterState={this.toasterState}>
|
||||
// <DeckyToaster />
|
||||
// </DeckyToasterStateContextProvider>
|
||||
// ));
|
||||
let instance: any;
|
||||
const tree = getReactRoot(document.getElementById('root') as any);
|
||||
const toasterClass1 = findClass('GamepadToastPlaceholder');
|
||||
const toasterClass2 = findClass('ToastPlaceholder');
|
||||
const toasterClass3 = findClass('ToastPopup');
|
||||
const toasterClass4 = findClass('GamepadToastPopup');
|
||||
const findToasterRoot = (currentNode: any, iters: number): any => {
|
||||
if (iters >= 80) {
|
||||
// currently 66
|
||||
return null;
|
||||
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[]) => {
|
||||
if (args?.[0]?.group?.decky || args?.[0]?.group?.notifications?.[0]?.decky) {
|
||||
return args[0].group.notifications.map((notification: any) => (
|
||||
<ErrorBoundary>
|
||||
<Toast toast={notification.data} newIndicator={notification.bNewIndicator} location={args?.[0]?.location} />
|
||||
</ErrorBoundary>
|
||||
));
|
||||
}
|
||||
if (
|
||||
currentNode?.memoizedProps?.className?.startsWith?.(toasterClass1) ||
|
||||
currentNode?.memoizedProps?.className?.startsWith?.(toasterClass2) ||
|
||||
currentNode?.memoizedProps?.className?.startsWith?.(toasterClass3) ||
|
||||
currentNode?.memoizedProps?.className?.startsWith?.(toasterClass4)
|
||||
) {
|
||||
this.log(`Toaster root was found in ${iters} recursion cycles`);
|
||||
return currentNode;
|
||||
}
|
||||
if (currentNode.sibling) {
|
||||
let node = findToasterRoot(currentNode.sibling, iters + 1);
|
||||
if (node !== null) return node;
|
||||
}
|
||||
if (currentNode.child) {
|
||||
let node = findToasterRoot(currentNode.child, iters + 1);
|
||||
if (node !== null) return node;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
instance = findToasterRoot(tree, 0);
|
||||
while (!instance) {
|
||||
this.warn(
|
||||
'Failed to find Toaster root node, reattempting in 5 seconds. A developer may need to increase the recursion limit.',
|
||||
);
|
||||
await sleep(5000);
|
||||
instance = findToasterRoot(tree, 0);
|
||||
}
|
||||
this.node = instance.return;
|
||||
this.rNode = findInReactTree(
|
||||
this.node.return.return,
|
||||
(node) => node?.stateNode && node.type?.InstallErrorReportingStore,
|
||||
);
|
||||
let toast: any;
|
||||
let renderedToast: ReactNode = null;
|
||||
let innerPatched: any;
|
||||
const repatch = () => {
|
||||
if (this.node && !this.node.type.decky) {
|
||||
this.toasterPatch = afterPatch(this.node, 'type', (_: any, ret: any) => {
|
||||
const inner = findInReactTree(ret.props.children, (x) => x?.props?.onDismiss);
|
||||
if (innerPatched) {
|
||||
inner.type = innerPatched;
|
||||
} else {
|
||||
afterPatch(inner, 'type', (innerArgs: any, ret: any) => {
|
||||
const currentToast = innerArgs[0]?.notification;
|
||||
if (currentToast?.decky) {
|
||||
if (currentToast == toast) {
|
||||
ret.props.children = renderedToast;
|
||||
} else {
|
||||
toast = currentToast;
|
||||
renderedToast = <Toast toast={toast.data} />;
|
||||
ret.props.children = renderedToast;
|
||||
}
|
||||
} else {
|
||||
toast = null;
|
||||
renderedToast = null;
|
||||
}
|
||||
return ret;
|
||||
});
|
||||
innerPatched = inner.type;
|
||||
}
|
||||
return ret;
|
||||
});
|
||||
this.node.type.decky = true;
|
||||
this.node.alternate.type = this.node.type;
|
||||
}
|
||||
};
|
||||
const oRender = Object.getPrototypeOf(this.rNode.stateNode).render;
|
||||
let int: number | undefined;
|
||||
this.rNode.stateNode.render = (...args: any[]) => {
|
||||
const ret = oRender.call(this.rNode.stateNode, ...args);
|
||||
if (ret && !this?.node?.return?.return) {
|
||||
int && clearInterval(int);
|
||||
int = setInterval(() => {
|
||||
const n = findToasterRoot(tree, 0);
|
||||
if (n?.return) {
|
||||
clearInterval(int);
|
||||
this.node = n.return;
|
||||
this.rNode = this.node.return;
|
||||
repatch();
|
||||
} else {
|
||||
this.error('Failed to re-grab Toaster node, trying again...');
|
||||
}
|
||||
}, 1200);
|
||||
}
|
||||
repatch();
|
||||
return ret;
|
||||
};
|
||||
|
||||
this.rNode.stateNode.shouldComponentUpdate = () => true;
|
||||
this.rNode.stateNode.forceUpdate();
|
||||
delete this.rNode.stateNode.shouldComponentUpdate;
|
||||
|
||||
this.audioModule = findModuleExport((e: Export) => e.PlayNavSound && e.RegisterCallbackOnPlaySound);
|
||||
return callOriginal;
|
||||
});
|
||||
|
||||
this.log('Initialized');
|
||||
this.finishStartup?.();
|
||||
}
|
||||
|
||||
async toast(toast: ToastData) {
|
||||
// toast.duration = toast.duration || 5e3;
|
||||
// this.toasterState.addToast(toast);
|
||||
await this.ready;
|
||||
toast(toast: ToastData): ToastNotification {
|
||||
if (toast.sound === undefined) toast.sound = 6;
|
||||
if (toast.playSound === undefined) toast.playSound = true;
|
||||
if (toast.showToast === undefined) toast.showToast = true;
|
||||
if (toast.timestamp === undefined) toast.timestamp = new Date();
|
||||
if (toast.showNewIndicator === undefined) toast.showNewIndicator = true;
|
||||
/* eType 13
|
||||
13: {
|
||||
proto: m.mu,
|
||||
fnTray: null,
|
||||
showToast: !0,
|
||||
sound: f.PN.ToastMisc,
|
||||
eFeature: l.uX
|
||||
}
|
||||
*/
|
||||
let toastData = {
|
||||
nNotificationID: window.NotificationStore.m_nNextTestNotificationID++,
|
||||
bNewIndicator: toast.showNewIndicator,
|
||||
rtCreated: Date.now(),
|
||||
eType: toast.eType || 11,
|
||||
eType: toast.eType || 31,
|
||||
eSource: 1, // Client
|
||||
nToastDurationMS: toast.duration || (toast.duration = 5e3),
|
||||
data: toast,
|
||||
decky: true,
|
||||
};
|
||||
// @ts-ignore
|
||||
toastData.data.appid = () => 0;
|
||||
if (toast.sound === undefined) toast.sound = 6;
|
||||
if (toast.playSound === undefined) toast.playSound = true;
|
||||
if (toast.showToast === undefined) toast.showToast = true;
|
||||
if (
|
||||
(window.settingsStore.settings.bDisableAllToasts && !toast.critical) ||
|
||||
(window.settingsStore.settings.bDisableToastsInGame &&
|
||||
!toast.critical &&
|
||||
window.NotificationStore.BIsUserInGame())
|
||||
)
|
||||
return;
|
||||
if (toast.playSound) this.audioModule?.PlayNavSound(toast.sound);
|
||||
if (toast.showToast) {
|
||||
window.NotificationStore.m_rgNotificationToasts.push(toastData);
|
||||
window.NotificationStore.DispatchNextToast();
|
||||
let group: any;
|
||||
function fnTray(toast: any, tray: any) {
|
||||
group = {
|
||||
eType: toast.eType,
|
||||
notifications: [toast],
|
||||
};
|
||||
tray.unshift(group);
|
||||
}
|
||||
const info = {
|
||||
showToast: toast.showToast,
|
||||
sound: toast.sound,
|
||||
playSound: toast.playSound,
|
||||
eFeature: 0,
|
||||
toastDurationMS: toastData.nToastDurationMS,
|
||||
bCritical: toast.critical,
|
||||
fnTray,
|
||||
};
|
||||
const self = this;
|
||||
let expirationTimeout: number;
|
||||
const toastResult: ToastNotification = {
|
||||
data: toast,
|
||||
dismiss() {
|
||||
// it checks against the id of notifications[0]
|
||||
try {
|
||||
expirationTimeout && clearTimeout(expirationTimeout);
|
||||
group && window.NotificationStore.RemoveGroupFromTray(group);
|
||||
} catch (e) {
|
||||
self.error('Error while dismissing toast:', e);
|
||||
}
|
||||
},
|
||||
};
|
||||
if (toast.expiration) {
|
||||
expirationTimeout = setTimeout(() => {
|
||||
try {
|
||||
group && window.NotificationStore.RemoveGroupFromTray(group);
|
||||
} catch (e) {
|
||||
this.error('Error while dismissing expired toast:', e);
|
||||
}
|
||||
}, toast.expiration);
|
||||
}
|
||||
try {
|
||||
window.NotificationStore.ProcessNotification(info, toastData, ToastType.New);
|
||||
} catch (e) {
|
||||
this.error('Error while sending toast:', e);
|
||||
}
|
||||
return toastResult;
|
||||
}
|
||||
|
||||
deinit() {
|
||||
this.toasterPatch?.unpatch();
|
||||
this.node.alternate.type = this.node.type;
|
||||
delete this.rNode.stateNode.render;
|
||||
this.ready = new Promise((res) => (this.finishStartup = res));
|
||||
// this.routerHook.removeGlobalComponent('DeckyToaster');
|
||||
this.toastPatch?.unpatch();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,11 +2,9 @@ import { FC } from 'react';
|
||||
import { Translation } from 'react-i18next';
|
||||
|
||||
import Logger from '../logger';
|
||||
import { InstallType } from '../plugin';
|
||||
|
||||
export enum TranslationClass {
|
||||
PLUGIN_LOADER = 'PluginLoader',
|
||||
PLUGIN_INSTALL_MODAL = 'PluginInstallModal',
|
||||
DEVELOPER = 'Developer',
|
||||
}
|
||||
|
||||
@@ -19,7 +17,7 @@ interface TranslationHelperProps {
|
||||
|
||||
const logger = new Logger('TranslationHelper');
|
||||
|
||||
const TranslationHelper: FC<TranslationHelperProps> = ({ transClass, transText, i18nArgs = null, installType = 0 }) => {
|
||||
const TranslationHelper: FC<TranslationHelperProps> = ({ transClass, transText, i18nArgs = null }) => {
|
||||
return (
|
||||
<Translation>
|
||||
{(t, {}) => {
|
||||
@@ -28,21 +26,6 @@ const TranslationHelper: FC<TranslationHelperProps> = ({ transClass, transText,
|
||||
return i18nArgs
|
||||
? t(TranslationClass.PLUGIN_LOADER + '.' + transText, i18nArgs)
|
||||
: t(TranslationClass.PLUGIN_LOADER + '.' + transText);
|
||||
case TranslationClass.PLUGIN_INSTALL_MODAL:
|
||||
switch (installType) {
|
||||
case InstallType.INSTALL:
|
||||
return i18nArgs
|
||||
? t(TranslationClass.PLUGIN_INSTALL_MODAL + '.install.' + transText, i18nArgs)
|
||||
: t(TranslationClass.PLUGIN_INSTALL_MODAL + '.install.' + transText);
|
||||
case InstallType.REINSTALL:
|
||||
return i18nArgs
|
||||
? t(TranslationClass.PLUGIN_INSTALL_MODAL + '.reinstall.' + transText, i18nArgs)
|
||||
: t(TranslationClass.PLUGIN_INSTALL_MODAL + '.reinstall.' + transText);
|
||||
case InstallType.UPDATE:
|
||||
return i18nArgs
|
||||
? t(TranslationClass.PLUGIN_INSTALL_MODAL + '.update.' + transText, i18nArgs)
|
||||
: t(TranslationClass.PLUGIN_INSTALL_MODAL + '.update.' + transText);
|
||||
}
|
||||
case TranslationClass.DEVELOPER:
|
||||
return i18nArgs
|
||||
? t(TranslationClass.DEVELOPER + '.' + transText, i18nArgs)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { ErrorInfo } from 'react';
|
||||
|
||||
const pluginErrorRegex = /http:\/\/localhost:1337\/plugins\/([^\/]*)\//;
|
||||
const pluginErrorRegex = /http:\/\/(?:localhost|127\.0\.0\.1):1337\/plugins\/([^\/]*)\//;
|
||||
const pluginSourceMapErrorRegex = /decky:\/\/decky\/plugin\/([^\/]*)\//;
|
||||
const legacyPluginErrorRegex = /decky:\/\/decky\/legacy_plugin\/([^\/]*)\/index.js/;
|
||||
|
||||
@@ -22,7 +22,10 @@ export function getLikelyErrorSourceFromValveError(error: ValveError): ErrorSour
|
||||
}
|
||||
|
||||
export function getLikelyErrorSourceFromValveReactError(error: ValveReactErrorInfo): ErrorSource {
|
||||
return getLikelyErrorSource(error?.error?.stack + '\n' + error.info.componentStack);
|
||||
// get the first 10 lines of the componentStack to avoid matching against the decky router wrapper for any route errors deeper in the tree
|
||||
return getLikelyErrorSource(
|
||||
error?.error?.stack + '\n' + error.info.componentStack?.split('\n').slice(0, 8).join('\n'),
|
||||
);
|
||||
}
|
||||
|
||||
export function getLikelyErrorSource(error?: string): ErrorSource {
|
||||
@@ -41,7 +44,7 @@ export function getLikelyErrorSource(error?: string): ErrorSource {
|
||||
return [decodeURIComponent(legacyPluginMatch[1]), true, false];
|
||||
}
|
||||
|
||||
if (error?.includes('http://localhost:1337/')) {
|
||||
if (error?.includes('http://localhost:1337/') || error?.includes('http://127.0.0.1:1337/')) {
|
||||
return ['the Decky frontend', false, false];
|
||||
}
|
||||
return ['Steam', false, true];
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user