Compare commits

...

40 Commits

Author SHA1 Message Date
AAGaming 5053a52f32 backport webhelper restart logic from websocket 2024-05-29 21:14:22 -04:00
AAGaming 5bfc53231d shut up ts 2024-05-29 21:04:36 -04:00
AAGaming 2b4e3318ca fix latest beta
VALVEEEEE
2024-05-29 21:01:49 -04:00
WerWolvTranslationBot b84dcd99ad Translations update from Weblate (#588)
* Translated using Weblate (Japanese)

Currently translated at 94.5% (139 of 147 strings)

Translation: Decky/Decky
Translate-URL: https://weblate.werwolv.net/projects/decky/decky/ja/

* Translated using Weblate (Polish)

Currently translated at 100.0% (147 of 147 strings)

Translation: Decky/Decky
Translate-URL: https://weblate.werwolv.net/projects/decky/decky/pl/

---------

Co-authored-by: Tak-attack <tak.bts@gmail.com>
Co-authored-by: Eryk Pawlikowski <eryk5188@gmail.com>
2024-03-17 11:31:02 -07:00
Wayne Heaney 34fb7bb538 Add Plugin.uninstall callback support (#555)
* Add Plugin.uninstall callback support

https://github.com/SteamDeckHomebrew/decky-loader/issues/536

* Remove empty deck.sh
2024-03-13 23:59:22 +01:00
AAGaming 4a7e9a5f3d fix: support new minified class names
bumps decky-frontend-lib to 3.25.0
can't stop us THAT easily :P
2024-03-09 17:21:36 -05:00
TrainDoctor 8e8e6a2bd1 Update bug_report.yml 2024-02-23 16:19:20 -08:00
TrainDoctor 55a95e04b5 Update bug_report.yml 2024-02-23 16:18:31 -08:00
WerWolvTranslationBot 49d1e33c14 Translations update from Weblate (#587)
* Added translation using Weblate (Japanese)

* Update translation files

Updated by "Remove blank strings" hook in Weblate.

Translation: Decky/Decky
Translate-URL: https://weblate.werwolv.net/projects/decky/decky/

* Update translation files

Updated by "Remove blank strings" hook in Weblate.

Translation: Decky/Decky
Translate-URL: https://weblate.werwolv.net/projects/decky/decky/

* Update translation files

Updated by "Remove blank strings" hook in Weblate.

Translation: Decky/Decky
Translate-URL: https://weblate.werwolv.net/projects/decky/decky/

* Update translation files

Updated by "Remove blank strings" hook in Weblate.

Translation: Decky/Decky
Translate-URL: https://weblate.werwolv.net/projects/decky/decky/

* Update translation files

Updated by "Remove blank strings" hook in Weblate.

Translation: Decky/Decky
Translate-URL: https://weblate.werwolv.net/projects/decky/decky/

* Update translation files

Updated by "Remove blank strings" hook in Weblate.

Translation: Decky/Decky
Translate-URL: https://weblate.werwolv.net/projects/decky/decky/

* Update translation files

Updated by "Remove blank strings" hook in Weblate.

Translation: Decky/Decky
Translate-URL: https://weblate.werwolv.net/projects/decky/decky/

* Update translation files

Updated by "Remove blank strings" hook in Weblate.

Translation: Decky/Decky
Translate-URL: https://weblate.werwolv.net/projects/decky/decky/

* Translated using Weblate (Japanese)

Currently translated at 82.7% (115 of 139 strings)

Translation: Decky/Decky
Translate-URL: https://weblate.werwolv.net/projects/decky/decky/ja/

* Translated using Weblate (Japanese)

Currently translated at 89.2% (124 of 139 strings)

Translation: Decky/Decky
Translate-URL: https://weblate.werwolv.net/projects/decky/decky/ja/

* Translated using Weblate (Japanese)

Currently translated at 100.0% (139 of 139 strings)

Translation: Decky/Decky
Translate-URL: https://weblate.werwolv.net/projects/decky/decky/ja/

* Translated using Weblate (Portuguese (Portugal))

Currently translated at 100.0% (139 of 139 strings)

Translation: Decky/Decky
Translate-URL: https://weblate.werwolv.net/projects/decky/decky/pt_PT/

* Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (139 of 139 strings)

Translation: Decky/Decky
Translate-URL: https://weblate.werwolv.net/projects/decky/decky/pt_BR/

* Translated using Weblate (French)

Currently translated at 91.3% (127 of 139 strings)

Translation: Decky/Decky
Translate-URL: https://weblate.werwolv.net/projects/decky/decky/fr/

* Translated using Weblate (Dutch)

Currently translated at 100.0% (139 of 139 strings)

Translation: Decky/Decky
Translate-URL: https://weblate.werwolv.net/projects/decky/decky/nl/

* Translated using Weblate (English)

Currently translated at 100.0% (147 of 147 strings)

Translation: Decky/Decky
Translate-URL: https://weblate.werwolv.net/projects/decky/decky/en/

* Translated using Weblate (Italian)

Currently translated at 100.0% (147 of 147 strings)

Translation: Decky/Decky
Translate-URL: https://weblate.werwolv.net/projects/decky/decky/it/

---------

Co-authored-by: Tak-attack <tak.bts@gmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: Fábio Oliveira <fabio.an.oliveira@gmail.com>
Co-authored-by: Andrew Moore <andrewm.finewolf@gmail.com>
Co-authored-by: Danae Dekker <genecyll@gmail.com>
Co-authored-by: Marco Rodolfi <marco.rodolfi@tuta.io>
2024-02-22 20:27:39 +01:00
Party Wumpus 922d0c4153 Appease prettier
i must have done a great deal of harm in a past life to deserve this mistreatment by formatting tools. why do they hate me.
2024-02-15 12:15:05 +00:00
Party Wumpus ecf480059b fix finding qam root node for feb 14th beta 2024-02-15 12:09:21 +00:00
Andrew Moore 7d6b8805df [Feature] Freeze updates for devs (#582) 2024-02-14 20:45:55 -08:00
eXhumer 0dce3a8cbe Get plugin name for development ZIP during installation (#578)
* fix: get plugin name for dev builds from ZIP (SteamDeckHomebrew/decky-loader#527)

Signed-off-by: eXhumer <exhumer1@protonmail.com>
2024-02-14 20:17:26 -08:00
Party Wumpus 9503d5cee0 Testing PRs from within decky (#496)
* git no work so manually uploading files :(

* argh i wish git was working

* ok next time i'll make git work

* Update updater.py

* git please work next time this took ages without you

* fix me locales

* Update updater.py

* Update en-US.json

* Update updater.py

* Update updater.py

* i wish my python LSP stuff was working

* fix it

* Update updater.py

* Update updater.py

* Only show testing branch as an option if it is already selected

* Initial implementation for fetching the open PRs. Still need testing and a token to complete this.

* Wrong filter capitalization

* Fix a couple of typos in the python backend updater.

* Fix typos pt 3

* This should be the last one

* Prepend the PR version number with PR- to make it clearer that's the PR number.

* Update prettier to the latest version otherwise it will never be happy with the formatting.

* fix merge mistake

* fix pyright errors & type hint most new code

* fix strict pyright errors...

* not sure why my local linter didn't catch this

* Reimplement the logic between PR and artifact build to limit API calls

* Fix pyright errors

* use nightly.link for downloads

* remove accidental dollar sign

* fix various logical errors. the code actually works now.

* set branch to testing when user downloads a testing version

---------

Co-authored-by: Marco Rodolfi <marco.rodolfi@tuta.io>
2024-02-14 18:32:58 -08:00
Jozen Blue Martinez 435dfa7884 fix(filepicker_ls): use case insensitive matching for file exts (#585) 2024-02-10 11:34:16 -08:00
Party Wumpus 2500b748ce Revert "Call plugin unload function after stopping event loop (#539)" (#584)
This reverts commit 39f4f2870b , because functions (seemingly) don't run after the event loop closes, so the unload function is never actually run.
2024-02-09 20:33:47 +00:00
Party Wumpus fd4ed811be Refactor plugin store and add sorting by downloads and release date (#547)
* untested first commit

* fix types & names

* comment out built in sorting for now

* rerun search when sort changes

* fix ts complaints

* use prettier

* stop switch-case fall through

* move spinner

* use locale instead of hardcoded string

* fix typo

* add sorting by downloads & try using the data field in the dropdown for data

* fix typing error

* fix asc/desc in dropdown

* fix asc/desc again. asc = smaller one go first aaaaa

* I don't think i know what ascending means maybe

* use props instead of children, like a normal component
2024-02-07 17:38:08 +00:00
Party Wumpus 3e4c255c5b Specify catthehacker/ubuntu:act-22.04 as container for act
Fixes an issue where act wouldn't use the correct container and so couldn't find a compatible python version, so it would fail to build.
2024-02-06 19:49:57 +00:00
AAGaming 62e3128d64 fix: bump dfl to fix error on latest steam beta 2024-02-03 00:33:39 -05:00
AAGaming 7f2caa3ea9 fix: use findInReactTree to find correct errorboundary for toaster
fixes toaster error on latest beta
2024-02-03 00:33:00 -05:00
AAGaming 6b4a56c7dc fix the tasks 2024-02-03 00:32:32 -05:00
WerWolvTranslationBot 647f3fe8ed Translations update from Weblate (#580)
* Added translation using Weblate (Japanese)

* Update translation files

Updated by "Remove blank strings" hook in Weblate.

Translation: Decky/Decky
Translate-URL: https://weblate.werwolv.net/projects/decky/decky/

* Update translation files

Updated by "Remove blank strings" hook in Weblate.

Translation: Decky/Decky
Translate-URL: https://weblate.werwolv.net/projects/decky/decky/

* Update translation files

Updated by "Remove blank strings" hook in Weblate.

Translation: Decky/Decky
Translate-URL: https://weblate.werwolv.net/projects/decky/decky/

* Update translation files

Updated by "Remove blank strings" hook in Weblate.

Translation: Decky/Decky
Translate-URL: https://weblate.werwolv.net/projects/decky/decky/

* Update translation files

Updated by "Remove blank strings" hook in Weblate.

Translation: Decky/Decky
Translate-URL: https://weblate.werwolv.net/projects/decky/decky/

* Update translation files

Updated by "Remove blank strings" hook in Weblate.

Translation: Decky/Decky
Translate-URL: https://weblate.werwolv.net/projects/decky/decky/

* Update translation files

Updated by "Remove blank strings" hook in Weblate.

Translation: Decky/Decky
Translate-URL: https://weblate.werwolv.net/projects/decky/decky/

* Update translation files

Updated by "Remove blank strings" hook in Weblate.

Translation: Decky/Decky
Translate-URL: https://weblate.werwolv.net/projects/decky/decky/

* Translated using Weblate (Japanese)

Currently translated at 82.7% (115 of 139 strings)

Translation: Decky/Decky
Translate-URL: https://weblate.werwolv.net/projects/decky/decky/ja/

* Translated using Weblate (Japanese)

Currently translated at 89.2% (124 of 139 strings)

Translation: Decky/Decky
Translate-URL: https://weblate.werwolv.net/projects/decky/decky/ja/

* Translated using Weblate (Japanese)

Currently translated at 100.0% (139 of 139 strings)

Translation: Decky/Decky
Translate-URL: https://weblate.werwolv.net/projects/decky/decky/ja/

* Translated using Weblate (Portuguese (Portugal))

Currently translated at 100.0% (139 of 139 strings)

Translation: Decky/Decky
Translate-URL: https://weblate.werwolv.net/projects/decky/decky/pt_PT/

* Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (139 of 139 strings)

Translation: Decky/Decky
Translate-URL: https://weblate.werwolv.net/projects/decky/decky/pt_BR/

---------

Co-authored-by: Tak-attack <tak.bts@gmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: Fábio Oliveira <fabio.an.oliveira@gmail.com>
2024-01-25 16:03:02 -08:00
Marco Rodolfi 3146ebf85f [Bugfix] Toaster changed name again (#581)
Add another name placeholder for getting the toaster out of the HTML tree. Thanks to @eXhumer for the fix.
2024-01-25 17:21:11 +01:00
dependabot[bot] 9295e4b038 Bump aiohttp from 3.8.5 to 3.9.0 in /backend (#577) 2024-01-23 19:49:13 +00:00
Beebles f9a07da3cc fix: Fix on Chromium 109 beta (#576)
* Add new user agent to do not close tabs list

* fix: bump DFL to fix chromium 109 beta

---------

Co-authored-by: Sims <38142618+suchmememanyskill@users.noreply.github.com>
2024-01-19 18:54:56 -08:00
dependabot[bot] 12a99b8b06 Bump tj-actions/changed-files to 41.0.0 in /.github/workflows (#575)
Bumps [tj-actions/changed-files](https://github.com/tj-actions/changed-files) from 35.6.3 to 41.0.0.
- [Release notes](https://github.com/tj-actions/changed-files/releases)
- [Changelog](https://github.com/tj-actions/changed-files/blob/main/HISTORY.md)
- [Commits](https://github.com/tj-actions/changed-files/compare/v35.6.3...v41.0.0)

---
updated-dependencies:
- dependency-name: tj-actions/changed-files
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-02 16:42:39 -08:00
WerWolvTranslationBot e3d72b6082 Translations update from Weblate (#553)
* Added translation using Weblate (Japanese)

* Update translation files

Updated by "Remove blank strings" hook in Weblate.

Translation: Decky/Decky
Translate-URL: https://weblate.werwolv.net/projects/decky/decky/

* Update translation files

Updated by "Remove blank strings" hook in Weblate.

Translation: Decky/Decky
Translate-URL: https://weblate.werwolv.net/projects/decky/decky/

* Update translation files

Updated by "Remove blank strings" hook in Weblate.

Translation: Decky/Decky
Translate-URL: https://weblate.werwolv.net/projects/decky/decky/

* Update translation files

Updated by "Remove blank strings" hook in Weblate.

Translation: Decky/Decky
Translate-URL: https://weblate.werwolv.net/projects/decky/decky/

* Update translation files

Updated by "Remove blank strings" hook in Weblate.

Translation: Decky/Decky
Translate-URL: https://weblate.werwolv.net/projects/decky/decky/

* Update translation files

Updated by "Remove blank strings" hook in Weblate.

Translation: Decky/Decky
Translate-URL: https://weblate.werwolv.net/projects/decky/decky/

* Update translation files

Updated by "Remove blank strings" hook in Weblate.

Translation: Decky/Decky
Translate-URL: https://weblate.werwolv.net/projects/decky/decky/

* Update translation files

Updated by "Remove blank strings" hook in Weblate.

Translation: Decky/Decky
Translate-URL: https://weblate.werwolv.net/projects/decky/decky/

* Translated using Weblate (Japanese)

Currently translated at 82.7% (115 of 139 strings)

Translation: Decky/Decky
Translate-URL: https://weblate.werwolv.net/projects/decky/decky/ja/

* Translated using Weblate (Japanese)

Currently translated at 89.2% (124 of 139 strings)

Translation: Decky/Decky
Translate-URL: https://weblate.werwolv.net/projects/decky/decky/ja/

* Translated using Weblate (Japanese)

Currently translated at 100.0% (139 of 139 strings)

Translation: Decky/Decky
Translate-URL: https://weblate.werwolv.net/projects/decky/decky/ja/

* Translated using Weblate (Portuguese (Portugal))

Currently translated at 100.0% (139 of 139 strings)

Translation: Decky/Decky
Translate-URL: https://weblate.werwolv.net/projects/decky/decky/pt_PT/

* Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (139 of 139 strings)

Translation: Decky/Decky
Translate-URL: https://weblate.werwolv.net/projects/decky/decky/pt_BR/

---------

Co-authored-by: Tak-attack <tak.bts@gmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: Fábio Oliveira <fabio.an.oliveira@gmail.com>
2023-12-15 18:12:57 -08:00
Jan 39f4f2870b Call plugin unload function after stopping event loop (#539)
This can prevent race conditions where unload is clearing data but main is still working with it
2023-12-15 18:07:54 -08:00
AAGaming 3489fd7d69 fix(developer): add back valve internal on beta
look i was tired when writing yesterday's fix okay
2023-12-13 22:06:22 -05:00
AAGaming e21a5d5890 fix: idiotic formatting error i should have noticed 2023-12-12 22:23:07 -05:00
AAGaming 80a00a0d35 fix: Adjust tabs and toaster hooks to work on react 18, also half-fix Valve Internal 2023-12-12 22:21:25 -05:00
Party Wumpus 91186da979 bump dfl 2023-11-24 15:08:29 -08:00
Jan 7c3ae9b62b replace chmod implementation with os.chmod (#541) 2023-11-11 20:56:32 +00:00
Jan 75ad98a7b2 Check if Linux service is running before trying to start or stop it (#540)
this prevents needless prompts opening up
2023-11-11 20:50:23 +00:00
dependabot[bot] 479a16c655 Bump aiohttp from 3.8.4 to 3.8.5 in /backend (#558) 2023-11-10 21:01:48 +00:00
Party Wumpus 8f26fdec2d Count the number of installs for each plugin (#557) 2023-11-10 17:19:01 +00:00
AAGaming 29d651bed6 fix: get rid of title view jank on latest beta 2023-11-09 15:35:32 -05:00
marios8543 44e6f03b06 Fix logging.handlers import and improve plugin modules 2023-10-31 17:04:48 +02:00
marios8543 d00506d141 fix decky imports from plugins 2023-10-27 00:42:10 +03:00
marios8543 ffe9cd8afe revert decky_plugin pyinstaller to previous version and fix sys path 2023-10-26 23:25:00 +03:00
45 changed files with 1432 additions and 609 deletions
+12 -2
View File
@@ -42,7 +42,7 @@ body:
label: SteamOS version label: SteamOS version
# description: Can be found with `uname -a` # description: Can be found with `uname -a`
# placeholder: "Linux steamdeck 5.13.0-valve36-1-neptune #1 SMP PREEMPT Mon, 19 Dec 2022 23:39:41 +0000 x86_64 GNU/Linux" # placeholder: "Linux steamdeck 5.13.0-valve36-1-neptune #1 SMP PREEMPT Mon, 19 Dec 2022 23:39:41 +0000 x86_64 GNU/Linux"
placeholder: "SteamOS 3.4.3 Stable" placeholder: "SteamOS 3.5.7 Stable"
validations: validations:
required: true required: true
@@ -67,8 +67,18 @@ body:
- type: textarea - type: textarea
attributes: attributes:
label: Logs 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 placeholder: deckylog.txt
validations: validations:
required: true required: true
- type: textarea
attributes:
label: Frontend Logs
description: Please copy from your deck ~/.steam/steam/logs/cef_log.txt and ~/.steam/steam/logs/cef_log.previous.txt. Make sure to scrub your Steam username as it may appear in these logs.
placeholder: cef_log.txt
validations:
required: true
+2 -2
View File
@@ -44,10 +44,10 @@ jobs:
run: pnpm run build run: pnpm run build
- name: Build Python Backend 🛠️ - name: Build Python Backend 🛠️
run: pyinstaller --noconfirm --onefile --name "PluginLoader" --add-data "./backend/static;/static" --add-data "./backend/locales;/locales" --add-data "./backend/src/legacy;/src/legacy" --add-data "./plugin/*;/" --hidden-import=sqlite3 ./backend/main.py run: pyinstaller --noconfirm --onefile --name "PluginLoader" --add-data "./backend/static;/static" --add-data "./backend/locales;/locales" --add-data "./backend/src/legacy;/src/legacy" --add-data "./plugin;/plugin" --hidden-import=logging.handlers --hidden-import=sqlite3 ./backend/main.py
- name: Build Python Backend (noconsole) 🛠️ - name: Build Python Backend (noconsole) 🛠️
run: pyinstaller --noconfirm --noconsole --onefile --name "PluginLoader_noconsole" --add-data "./backend/static;/static" --add-data "./backend/locales;/locales" --add-data "./backend/src/legacy;/src/legacy" --add-data "./plugin/*;/" --hidden-import=sqlite3 ./backend/main.py run: pyinstaller --noconfirm --noconsole --onefile --name "PluginLoader_noconsole" --add-data "./backend/static;/static" --add-data "./backend/locales;/locales" --add-data "./backend/src/legacy;/src/legacy" --add-data "./plugin;/plugin" --hidden-import=logging.handlers --hidden-import=sqlite3 ./backend/main.py
- name: Upload package artifact ⬆️ - name: Upload package artifact ⬆️
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v3
+1 -1
View File
@@ -87,7 +87,7 @@ jobs:
run: pnpm run build run: pnpm run build
- name: Build Python Backend 🛠️ - name: Build Python Backend 🛠️
run: pyinstaller --noconfirm --onefile --name "PluginLoader" --add-data ./backend/static:/static --add-data ./backend/locales:/locales --add-data ./backend/src/legacy:/src/legacy --add-data ./plugin/*:/ --hidden-import=sqlite3 ./backend/main.py run: pyinstaller --noconfirm --onefile --name "PluginLoader" --add-data ./backend/static:/static --add-data ./backend/locales:/locales --add-data ./backend/src/legacy:/src/legacy --add-data ./plugin:/plugin --hidden-import=logging.handlers --hidden-import=sqlite3 ./backend/main.py
- name: Upload package artifact ⬆️ - name: Upload package artifact ⬆️
if: ${{ !env.ACT }} if: ${{ !env.ACT }}
+1 -1
View File
@@ -18,7 +18,7 @@ jobs:
- name: Get changed files - name: Get changed files
id: changed-files id: changed-files
uses: tj-actions/changed-files@v35.6.3 uses: tj-actions/changed-files@v41.0.0
with: with:
separator: "," separator: ","
files: | files: |
+2 -2
View File
@@ -38,7 +38,7 @@
"type": "shell", "type": "shell",
"group": "none", "group": "none",
"detail": "Check for local runs, create a plugins folder", "detail": "Check for local runs, create a plugins folder",
"command": "rsync -azp --rsh='ssh -p ${config:deckport} ${config:deckkey}' requirements.txt deck@${config:deckip}:${config:deckdir}/homebrew/dev/pluginloader/requirements.txt && ssh deck@${config:deckip} -p ${config:deckport} ${config:deckkey} 'python -m ensurepip && python -m pip install --upgrade pip && python -m pip install --upgrade setuptools && python -m pip install -r ${config:deckdir}/homebrew/dev/pluginloader/requirements.txt'", "command": "rsync -azp --rsh='ssh -p ${config:deckport} ${config:deckkey}' backend/requirements.txt deck@${config:deckip}:${config:deckdir}/homebrew/dev/pluginloader/backend/requirements.txt && ssh deck@${config:deckip} -p ${config:deckport} ${config:deckkey} 'python -m ensurepip && python -m pip install --upgrade --break-system-packages pip && python -m pip install --break-system-packages --upgrade setuptools && python -m pip install --break-system-packages -r ${config:deckdir}/homebrew/dev/pluginloader/backend/requirements.txt'",
"problemMatcher": [] "problemMatcher": []
}, },
{ {
@@ -105,7 +105,7 @@
"detail": "Deploy dev PluginLoader to deck", "detail": "Deploy dev PluginLoader to deck",
"type": "shell", "type": "shell",
"group": "none", "group": "none",
"command": "rsync -azp --delete --rsh='ssh -p ${config:deckport} ${config:deckkey}' --exclude='.git/' --exclude='.github/' --exclude='.vscode/' --exclude='frontend/' --exclude='dist/' --exclude='contrib/' --exclude='*.log' --exclude='requirements.txt' --exclude='backend/__pycache__/' --exclude='.gitignore' . deck@${config:deckip}:${config:deckdir}/homebrew/dev/pluginloader", "command": "rsync -azp --delete --rsh='ssh -p ${config:deckport} ${config:deckkey}' --exclude='.git/' --exclude='.github/' --exclude='.vscode/' --exclude='frontend/' --exclude='dist/' --exclude='contrib/' --exclude='*.log' --exclude='requirements.txt' --exclude='**/__pycache__/' --exclude='.gitignore' . deck@${config:deckip}:${config:deckdir}/homebrew/dev/pluginloader",
"problemMatcher": [] "problemMatcher": []
}, },
// RUN // RUN
+2 -2
View File
@@ -26,10 +26,10 @@ cd ..
if [[ "$type" == "release" ]]; then if [[ "$type" == "release" ]]; then
printf "release!\n" printf "release!\n"
act workflow_dispatch -e act/release.json --artifact-server-path act/artifacts --container-architecture linux/amd64 act workflow_dispatch -e act/release.json --artifact-server-path act/artifacts --container-architecture linux/amd64 --platform ubuntu-22.04=catthehacker/ubuntu:act-22.04
elif [[ "$type" == "prerelease" ]]; then elif [[ "$type" == "prerelease" ]]; then
printf "prerelease!\n" printf "prerelease!\n"
act workflow_dispatch -e act/prerelease.json --artifact-server-path act/artifacts --container-architecture linux/amd64 act workflow_dispatch -e act/prerelease.json --artifact-server-path act/artifacts --container-architecture linux/amd64 --platform ubuntu-22.04=catthehacker/ubuntu:act-22.04
else else
printf "Release type unspecified/badly specified.\n" printf "Release type unspecified/badly specified.\n"
printf "Options: 'release' or 'prerelease'\n" printf "Options: 'release' or 'prerelease'\n"
+267 -257
View File
@@ -1,260 +1,270 @@
{ {
"BranchSelect": { "BranchSelect": {
"update_channel": { "update_channel": {
"label": "Update Channel", "label": "Update Channel",
"prerelease": "Prerelease", "prerelease": "Prerelease",
"stable": "Stable", "stable": "Stable",
"testing": "Testing" "testing": "Testing"
}
},
"Developer": {
"5secreload": "Reloading in 5 seconds",
"disabling": "Disabling React DevTools",
"enabling": "Enabling React DevTools"
},
"DropdownMultiselect": {
"button": {
"back": "Back"
}
},
"FilePickerError": {
"errors": {
"file_not_found": "The path specified is not valid. Please check it and reenter it correctly.",
"perm_denied": "You do not have access to the specified directory. Please check if your user (deck on Steam Deck) has the corresponding permission to access the specified folder/file.",
"unknown": "An unknown error occurred. The raw error is: {{raw_error}}"
}
},
"FilePickerIndex": {
"file": {
"select": "Select this file"
},
"files": {
"all_files": "All Files",
"file_type": "File Type",
"show_hidden": "Show Hidden Files"
},
"filter": {
"created_asce": "Created (Oldest)",
"created_desc": "Created (Newest)",
"modified_asce": "Modified (Oldest)",
"modified_desc": "Modified (Newest)",
"name_asce": "Z-A",
"name_desc": "A-Z",
"size_asce": "Size (Smallest)",
"size_desc": "Size (Largest)"
},
"folder": {
"label": "Folder",
"select": "Use this folder",
"show_more": "Show more files"
}
},
"MultiplePluginsInstallModal": {
"confirm": "Are you sure you want to make the following modifications?",
"description": {
"install": "Install {{name}} {{version}}",
"reinstall": "Reinstall {{name}} {{version}}",
"update": "Update {{name}} to {{version}}"
},
"ok_button": {
"idle": "Confirm",
"loading": "Working"
},
"title": {
"install_one": "Install 1 plugin",
"install_other": "Install {{count}} plugins",
"mixed_one": "Modify {{count}} plugin",
"mixed_other": "Modify {{count}} plugins",
"reinstall_one": "Reinstall 1 plugin",
"reinstall_other": "Reinstall {{count}} plugins",
"update_one": "Update 1 plugin",
"update_other": "Update {{count}} plugins"
}
},
"PluginCard": {
"plugin_full_access": "This plugin has full access to your Steam Deck.",
"plugin_install": "Install",
"plugin_no_desc": "No description provided.",
"plugin_version_label": "Plugin Version"
},
"PluginInstallModal": {
"install": {
"button_idle": "Install",
"button_processing": "Installing",
"desc": "Are you sure you want to install {{artifact}} {{version}}?",
"title": "Install {{artifact}}"
},
"no_hash": "This plugin does not have a hash, you are installing it at your own risk.",
"reinstall": {
"button_idle": "Reinstall",
"button_processing": "Reinstalling",
"desc": "Are you sure you want to reinstall {{artifact}} {{version}}?",
"title": "Reinstall {{artifact}}"
},
"update": {
"button_idle": "Update",
"button_processing": "Updating",
"desc": "Are you sure you want to update {{artifact}} {{version}}?",
"title": "Update {{artifact}}"
}
},
"PluginListIndex": {
"freeze": "Freeze updates",
"hide": "Quick access: Hide",
"no_plugin": "No plugins installed!",
"plugin_actions": "Plugin Actions",
"reinstall": "Reinstall",
"reload": "Reload",
"show": "Quick access: Show",
"unfreeze": "Allow updates",
"uninstall": "Uninstall",
"update_all_one": "Update 1 plugin",
"update_all_other": "Update {{count}} plugins",
"update_to": "Update to {{name}}"
},
"PluginListLabel": {
"hidden": "Hidden from the quick access menu"
},
"PluginLoader": {
"decky_title": "Decky",
"decky_update_available": "Update to {{tag_name}} available!",
"error": "Error",
"plugin_error_uninstall": "Loading {{name}} caused an exception as shown above. This usually means that the plugin requires an update for the new version of SteamUI. Check if an update is present or evaluate its removal in the Decky settings, in the Plugins section.",
"plugin_load_error": {
"message": "Error loading plugin {{name}}",
"toast": "Error loading {{name}}"
},
"plugin_uninstall": {
"button": "Uninstall",
"desc": "Are you sure you want to uninstall {{name}}?",
"title": "Uninstall {{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"
},
"RemoteDebugging": {
"remote_cef": {
"desc": "Allow unauthenticated access to the CEF debugger to anyone in your network",
"label": "Allow Remote CEF Debugging"
}
},
"SettingsDeveloperIndex": {
"cef_console": {
"button": "Open Console",
"desc": "Opens the CEF Console. Only useful for debugging purposes. Stuff here is potentially dangerous and should only be used if you are a plugin dev, or are directed here by one.",
"label": "CEF Console"
},
"header": "Other",
"react_devtools": {
"desc": "Enables connection to a computer running React DevTools. Changing this setting will reload Steam. Set the IP address before enabling.",
"ip_label": "IP",
"label": "Enable React DevTools"
},
"third_party_plugins": {
"button_install": "Install",
"button_zip": "Browse",
"header": "Third-Party Plugins",
"label_desc": "URL",
"label_url": "Install Plugin from URL",
"label_zip": "Install Plugin from ZIP File"
},
"valve_internal": {
"desc1": "Enables the Valve internal developer menu.",
"desc2": "Do not touch anything in this menu unless you know what it does.",
"label": "Enable Valve Internal"
}
},
"SettingsGeneralIndex": {
"about": {
"decky_version": "Decky Version",
"header": "About"
},
"beta": {
"header": "Beta participation"
},
"developer_mode": {
"label": "Developer mode"
},
"notifications": {
"decky_updates_label": "Decky update available",
"header": "Notifications",
"plugin_updates_label": "Plugin updates available"
},
"other": {
"header": "Other"
},
"updates": {
"header": "Updates"
}
},
"SettingsIndex": {
"developer_title": "Developer",
"general_title": "General",
"plugins_title": "Plugins",
"testing_title": "Testing"
},
"Store": {
"store_contrib": {
"desc": "If you would like to contribute to the Decky Plugin Store, check the SteamDeckHomebrew/decky-plugin-template repository on GitHub. Information on development and distribution is available in the README.",
"label": "Contributing"
},
"store_filter": {
"label": "Filter",
"label_def": "All"
},
"store_search": {
"label": "Search"
},
"store_sort": {
"label": "Sort",
"label_def": "Last Updated (Newest)"
},
"store_source": {
"desc": "All plugin source code is available on SteamDeckHomebrew/decky-plugin-database repository on GitHub.",
"label": "Source Code"
},
"store_tabs": {
"about": "About",
"alph_asce": "Alphabetical (Z to A)",
"alph_desc": "Alphabetical (A to Z)",
"date_asce": "Oldest First",
"date_desc": "Newest First",
"downloads_asce": "Least Downloaded First",
"downloads_desc": "Most Downloaded First",
"title": "Browse"
},
"store_testing_cta": "Please consider testing new plugins to help the Decky Loader team!",
"store_testing_warning": {
"desc": "You can use this store channel to test bleeding-edge plugin versions. Be sure to leave feedback on GitHub so the plugin can be updated for all users.",
"label": "Welcome to the Testing Store Channel"
}
},
"StoreSelect": {
"custom_store": {
"label": "Custom Store",
"url_label": "URL"
},
"store_channel": {
"custom": "Custom",
"default": "Default",
"label": "Store Channel",
"testing": "Testing"
}
},
"TitleView": {
"decky_store_desc": "Open Decky Store",
"settings_desc": "Open Decky Settings"
},
"Updater": {
"decky_updates": "Decky Updates",
"no_patch_notes_desc": "no patch notes for this version",
"patch_notes_desc": "Patch Notes",
"updates": {
"check_button": "Check For Updates",
"checking": "Checking",
"cur_version": "Current version: {{ver}}",
"install_button": "Install Update",
"label": "Updates",
"lat_version": "Up to date: running {{ver}}",
"reloading": "Reloading",
"updating": "Updating"
}
},
"Testing": {
"download": "Download"
} }
},
"Developer": {
"5secreload": "Reloading in 5 seconds",
"disabling": "Disabling React DevTools",
"enabling": "Enabling React DevTools"
},
"DropdownMultiselect": {
"button": {
"back": "Back"
}
},
"FilePickerError": {
"errors": {
"file_not_found": "The path specified is not valid. Please check it and reenter it correctly.",
"perm_denied": "You do not have access to the specified directory. Please check if your user (deck on Steam Deck) has the corresponding permission to access the specified folder/file.",
"unknown": "An unknown error occurred. The raw error is: {{raw_error}}"
}
},
"FilePickerIndex": {
"file": {
"select": "Select this file"
},
"files": {
"all_files": "All Files",
"file_type": "File Type",
"show_hidden": "Show Hidden Files"
},
"filter": {
"created_asce": "Created (Oldest)",
"created_desc": "Created (Newest)",
"modified_asce": "Modified (Oldest)",
"modified_desc": "Modified (Newest)",
"name_asce": "Z-A",
"name_desc": "A-Z",
"size_asce": "Size (Smallest)",
"size_desc": "Size (Largest)"
},
"folder": {
"label": "Folder",
"select": "Use this folder",
"show_more": "Show more files"
}
},
"MultiplePluginsInstallModal": {
"confirm": "Are you sure you want to make the following modifications?",
"description": {
"install": "Install {{name}} {{version}}",
"reinstall": "Reinstall {{name}} {{version}}",
"update": "Update {{name}} to {{version}}"
},
"ok_button": {
"idle": "Confirm",
"loading": "Working"
},
"title": {
"install_one": "Install 1 plugin",
"install_other": "Install {{count}} plugins",
"mixed_one": "Modify {{count}} plugin",
"mixed_other": "Modify {{count}} plugins",
"reinstall_one": "Reinstall 1 plugin",
"reinstall_other": "Reinstall {{count}} plugins",
"update_one": "Update 1 plugin",
"update_other": "Update {{count}} plugins"
}
},
"PluginCard": {
"plugin_full_access": "This plugin has full access to your Steam Deck.",
"plugin_install": "Install",
"plugin_no_desc": "No description provided.",
"plugin_version_label": "Plugin Version"
},
"PluginInstallModal": {
"install": {
"button_idle": "Install",
"button_processing": "Installing",
"desc": "Are you sure you want to install {{artifact}} {{version}}?",
"title": "Install {{artifact}}"
},
"no_hash": "This plugin does not have a hash, you are installing it at your own risk.",
"reinstall": {
"button_idle": "Reinstall",
"button_processing": "Reinstalling",
"desc": "Are you sure you want to reinstall {{artifact}} {{version}}?",
"title": "Reinstall {{artifact}}"
},
"update": {
"button_idle": "Update",
"button_processing": "Updating",
"desc": "Are you sure you want to update {{artifact}} {{version}}?",
"title": "Update {{artifact}}"
}
},
"PluginListIndex": {
"hide": "Quick access: Hide",
"no_plugin": "No plugins installed!",
"plugin_actions": "Plugin Actions",
"reinstall": "Reinstall",
"reload": "Reload",
"show": "Quick access: Show",
"uninstall": "Uninstall",
"update_all_one": "Update 1 plugin",
"update_all_other": "Update {{count}} plugins",
"update_to": "Update to {{name}}"
},
"PluginListLabel": {
"hidden": "Hidden from the quick access menu"
},
"PluginLoader": {
"decky_title": "Decky",
"decky_update_available": "Update to {{tag_name}} available!",
"error": "Error",
"plugin_error_uninstall": "Loading {{name}} caused an exception as shown above. This usually means that the plugin requires an update for the new version of SteamUI. Check if an update is present or evaluate its removal in the Decky settings, in the Plugins section.",
"plugin_load_error": {
"message": "Error loading plugin {{name}}",
"toast": "Error loading {{name}}"
},
"plugin_uninstall": {
"button": "Uninstall",
"desc": "Are you sure you want to uninstall {{name}}?",
"title": "Uninstall {{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"
},
"RemoteDebugging": {
"remote_cef": {
"desc": "Allow unauthenticated access to the CEF debugger to anyone in your network",
"label": "Allow Remote CEF Debugging"
}
},
"SettingsDeveloperIndex": {
"cef_console": {
"button": "Open Console",
"desc": "Opens the CEF Console. Only useful for debugging purposes. Stuff here is potentially dangerous and should only be used if you are a plugin dev, or are directed here by one.",
"label": "CEF Console"
},
"header": "Other",
"react_devtools": {
"desc": "Enables connection to a computer running React DevTools. Changing this setting will reload Steam. Set the IP address before enabling.",
"ip_label": "IP",
"label": "Enable React DevTools"
},
"third_party_plugins": {
"button_install": "Install",
"button_zip": "Browse",
"header": "Third-Party Plugins",
"label_desc": "URL",
"label_url": "Install Plugin from URL",
"label_zip": "Install Plugin from ZIP File"
},
"valve_internal": {
"desc1": "Enables the Valve internal developer menu.",
"desc2": "Do not touch anything in this menu unless you know what it does.",
"label": "Enable Valve Internal"
}
},
"SettingsGeneralIndex": {
"about": {
"decky_version": "Decky Version",
"header": "About"
},
"beta": {
"header": "Beta participation"
},
"developer_mode": {
"label": "Developer mode"
},
"notifications": {
"decky_updates_label": "Decky update available",
"header": "Notifications",
"plugin_updates_label": "Plugin updates available"
},
"other": {
"header": "Other"
},
"updates": {
"header": "Updates"
}
},
"SettingsIndex": {
"developer_title": "Developer",
"general_title": "General",
"plugins_title": "Plugins"
},
"Store": {
"store_contrib": {
"desc": "If you would like to contribute to the Decky Plugin Store, check the SteamDeckHomebrew/decky-plugin-template repository on GitHub. Information on development and distribution is available in the README.",
"label": "Contributing"
},
"store_filter": {
"label": "Filter",
"label_def": "All"
},
"store_search": {
"label": "Search"
},
"store_sort": {
"label": "Sort",
"label_def": "Last Updated (Newest)"
},
"store_source": {
"desc": "All plugin source code is available on SteamDeckHomebrew/decky-plugin-database repository on GitHub.",
"label": "Source Code"
},
"store_tabs": {
"about": "About",
"alph_asce": "Alphabetical (Z to A)",
"alph_desc": "Alphabetical (A to Z)",
"title": "Browse"
},
"store_testing_cta": "Please consider testing new plugins to help the Decky Loader team!",
"store_testing_warning": {
"desc": "You can use this store channel to test bleeding-edge plugin versions. Be sure to leave feedback on GitHub so the plugin can be updated for all users.",
"label": "Welcome to the Testing Store Channel"
}
},
"StoreSelect": {
"custom_store": {
"label": "Custom Store",
"url_label": "URL"
},
"store_channel": {
"custom": "Custom",
"default": "Default",
"label": "Store Channel",
"testing": "Testing"
}
},
"TitleView": {
"decky_store_desc": "Open Decky Store",
"settings_desc": "Open Decky Settings"
},
"Updater": {
"decky_updates": "Decky Updates",
"no_patch_notes_desc": "no patch notes for this version",
"patch_notes_desc": "Patch Notes",
"updates": {
"check_button": "Check For Updates",
"checking": "Checking",
"cur_version": "Current version: {{ver}}",
"install_button": "Install Update",
"label": "Updates",
"lat_version": "Up to date: running {{ver}}",
"reloading": "Reloading",
"updating": "Updating"
}
}
} }
+68 -21
View File
@@ -56,12 +56,32 @@
}, },
"Developer": { "Developer": {
"5secreload": "Rechargement dans 5 secondes", "5secreload": "Rechargement dans 5 secondes",
"disabling": "Désactivation", "disabling": "Désactivation des React DevTools",
"enabling": "Activation" "enabling": "Activation des React DevTools"
}, },
"FilePickerIndex": { "FilePickerIndex": {
"folder": { "folder": {
"select": "Utiliser ce dossier" "select": "Utiliser ce dossier",
"label": "Dossier",
"show_more": "Afficher plus de fichiers"
},
"files": {
"show_hidden": "Afficher les fichiers cachés",
"all_files": "Tout les fichiers",
"file_type": "Type de fichier"
},
"file": {
"select": "Sélectionner ce fichier"
},
"filter": {
"created_desc": "Création (Plus récent)",
"modified_asce": "Modifié (Plus vieux)",
"modified_desc": "Modifié (Plus récent)",
"created_asce": "Création (Plus vieux)",
"name_asce": "Z-A",
"name_desc": "A-Z",
"size_asce": "Taille (Plus petit)",
"size_desc": "Taille (Plus grand)"
} }
}, },
"PluginCard": { "PluginCard": {
@@ -98,9 +118,11 @@
"uninstall": "Désinstaller", "uninstall": "Désinstaller",
"update_to": "Mettre à jour vers {{name}}", "update_to": "Mettre à jour vers {{name}}",
"no_plugin": "Aucun plugin installé !", "no_plugin": "Aucun plugin installé !",
"update_all_one": "", "update_all_one": "Mettre à jour 1 plugin",
"update_all_many": "", "update_all_many": "Mettre à jour {{count}} plugins",
"update_all_other": "" "update_all_other": "Mettre à jour {{count}} plugins",
"show": "Accès Rapide : Afficher",
"hide": "Accès rapide : Cacher"
}, },
"PluginLoader": { "PluginLoader": {
"decky_title": "Decky", "decky_title": "Decky",
@@ -116,9 +138,9 @@
"title": "Désinstaller {{name}}", "title": "Désinstaller {{name}}",
"desc": "Êtes-vous sûr.e de vouloir désinstaller {{name}} ?" "desc": "Êtes-vous sûr.e de vouloir désinstaller {{name}} ?"
}, },
"plugin_update_one": "", "plugin_update_one": "Mise à jour disponible pour 1 plugin !",
"plugin_update_many": "", "plugin_update_many": "Mises à jour disponible pour {{count}} plugins !",
"plugin_update_other": "" "plugin_update_other": "Mises à jour disponible pour {{count}} plugins !"
}, },
"RemoteDebugging": { "RemoteDebugging": {
"remote_cef": { "remote_cef": {
@@ -184,18 +206,43 @@
}, },
"MultiplePluginsInstallModal": { "MultiplePluginsInstallModal": {
"title": { "title": {
"reinstall_one": "", "reinstall_one": "Réinstaller 1 plugin",
"reinstall_many": "", "reinstall_many": "Réinstaller {{count}} plugins",
"reinstall_other": "", "reinstall_other": "Réinstaller {{count}} plugins",
"install_one": "", "install_one": "Installer 1 plugin",
"install_many": "", "install_many": "Installer {{count}} plugins",
"install_other": "", "install_other": "Installer {{count}} plugins",
"mixed_one": "", "mixed_one": "Modifier {{count}} plugin",
"mixed_many": "", "mixed_many": "Modifier {{count}} plugins",
"mixed_other": "", "mixed_other": "Modifier {{count}} plugins",
"update_one": "", "update_one": "Mettre à jour 1 plugin",
"update_many": "", "update_many": "Mettre à jour {{count}} plugins",
"update_other": "" "update_other": "Mettre à jour {{count}} plugins"
},
"confirm": "Êtes-vous sûr de vouloir apporter les modifications suivantes ?",
"description": {
"install": "Installer {{name}} {{version}}",
"update": "Mettre à jour {{name}} à {{version}}",
"reinstall": "Réinstaller {{name}} {{version}}"
},
"ok_button": {
"idle": "Confirmer",
"loading": "En cours"
}
},
"PluginListLabel": {
"hidden": "Caché du menu d'accès rapide"
},
"FilePickerError": {
"errors": {
"perm_denied": "Vous n'avez pas accès au dossier spécifié. Veuillez vérifier que votre utilisateur (deck sur un Steam Deck) possède les permissions requises pour accéder au dossier/fichier spécifié.",
"file_not_found": "Le chemin spécifié n'est pas valide. Veuillez vérifier et essayer à nouveau.",
"unknown": "Une erreur inconnue est survenue. L'erreur est : {{raw_error}}"
}
},
"DropdownMultiselect": {
"button": {
"back": "Retour"
} }
} }
} }
+13 -3
View File
@@ -113,7 +113,9 @@
"update_all_one": "Aggiorna un plugin", "update_all_one": "Aggiorna un plugin",
"update_all_many": "Aggiorna {{count}} plugins", "update_all_many": "Aggiorna {{count}} plugins",
"update_all_other": "Aggiorna {{count}} plugins", "update_all_other": "Aggiorna {{count}} plugins",
"update_to": "Aggiorna a {{name}}" "update_to": "Aggiorna a {{name}}",
"unfreeze": "Permetti aggiornamenti",
"freeze": "Congela aggiornamenti"
}, },
"PluginListLabel": { "PluginListLabel": {
"hidden": "Nascosto dal menu di accesso rapido" "hidden": "Nascosto dal menu di accesso rapido"
@@ -199,7 +201,8 @@
"SettingsIndex": { "SettingsIndex": {
"developer_title": "Sviluppatore", "developer_title": "Sviluppatore",
"general_title": "Generali", "general_title": "Generali",
"plugins_title": "Plugins" "plugins_title": "Plugins",
"testing_title": "Testing"
}, },
"Store": { "Store": {
"store_contrib": { "store_contrib": {
@@ -225,7 +228,11 @@
"about": "Riguardo a", "about": "Riguardo a",
"alph_asce": "Alfabetico (Z a A)", "alph_asce": "Alfabetico (Z a A)",
"alph_desc": "Alfabetico (A a Z)", "alph_desc": "Alfabetico (A a Z)",
"title": "Sfoglia" "title": "Sfoglia",
"date_desc": "Per più recente",
"date_asce": "Per più vecchio",
"downloads_desc": "Per più scaricato",
"downloads_asce": "Per meno scaricato"
}, },
"store_testing_cta": "Valuta la possibilità di testare nuovi plugin per aiutare il team di Decky Loader!", "store_testing_cta": "Valuta la possibilità di testare nuovi plugin per aiutare il team di Decky Loader!",
"store_testing_warning": { "store_testing_warning": {
@@ -263,5 +270,8 @@
"TitleView": { "TitleView": {
"settings_desc": "Apri le impostazioni di Decky", "settings_desc": "Apri le impostazioni di Decky",
"decky_store_desc": "Apri lo store di Decky" "decky_store_desc": "Apri lo store di Decky"
},
"Testing": {
"download": "Scarica"
} }
} }
+253
View File
@@ -0,0 +1,253 @@
{
"BranchSelect": {
"update_channel": {
"stable": "安定",
"testing": "テスト",
"label": "アップデートチャンネル",
"prerelease": "プレリリース"
}
},
"DropdownMultiselect": {
"button": {
"back": "戻る"
}
},
"FilePickerIndex": {
"file": {
"select": "ファイルを選択"
},
"files": {
"all_files": "すべてのファイル",
"file_type": "ファイルタイプ",
"show_hidden": "非表示ファイルを表示する"
},
"filter": {
"name_asce": "Z-A",
"name_desc": "A-Z",
"size_asce": "サイズ(小さい順)",
"size_desc": "サイズ(大きい順)",
"created_asce": "作成日(古い順)",
"created_desc": "作成日(新しい順)",
"modified_asce": "更新日(古い順)",
"modified_desc": "更新日(新しい順)"
},
"folder": {
"label": "フォルダ",
"select": "このフォルダを使用",
"show_more": "その他のファイルを表示"
}
},
"MultiplePluginsInstallModal": {
"description": {
"install": "インストール {{name}} {{version}}",
"reinstall": "再インストール {{name}} {{version}}",
"update": "アップデート {{name}} {{version}}"
},
"ok_button": {
"idle": "確認",
"loading": "作業中"
},
"title": {
"install_other": "{{count}} 個のプラグインをインストール",
"mixed_other": "{{count}} 個のプラグインを修正",
"update_other": "{{count}} 個のプラグインをアップデート",
"reinstall_other": "{{count}} 個のプラグインを再インストール"
},
"confirm": "以下の変更を加えてもよろしいですか?"
},
"Developer": {
"enabling": "React DevToolsを有効",
"disabling": "React DevToolsを無効",
"5secreload": "5秒以内に再読み込みされます"
},
"PluginInstallModal": {
"install": {
"button_idle": "インストール",
"title": "{{artifact}} をインストール",
"button_processing": "インストール中",
"desc": "{{artifact}} {{version}} をインストールしてもよろしいですか?"
},
"no_hash": "このプラグインにはハッシュがありません。ご自身の責任でインストールしてください。",
"reinstall": {
"button_idle": "再インストール",
"button_processing": "再インストール中",
"desc": "{{artifact}} {{version}} を再インストールしてもよろしいですか?",
"title": "{{artifact}} を再インストール"
},
"update": {
"button_idle": "アップデート",
"title": "{{artifact}} をアップデート",
"desc": "{{artifact}} {{version}} をアップデートしてもよろしいですか?",
"button_processing": "アップデート中"
}
},
"PluginListIndex": {
"hide": "クイックアクセス: 非表示",
"no_plugin": "プラグインがインストールされていません!",
"reinstall": "再インストール",
"reload": "再読み込み",
"uninstall": "アンインストール",
"plugin_actions": "プラグインアクション",
"update_all_other": "{{count}} 個のプラグインをアップデート",
"show": "クイックアクセス: 表示",
"update_to": "{{name}} を更新"
},
"PluginListLabel": {
"hidden": "クイックアクセスメニューから表示されません"
},
"PluginLoader": {
"error": "エラー",
"plugin_load_error": {
"message": "プラグイン {{name}} の読み込みエラー",
"toast": "{{name}} の読み込みエラー"
},
"plugin_uninstall": {
"button": "アンインストール",
"desc": "{{name}} をアンインストールしてもよろしいですか?",
"title": "{{name}} をアンインストール"
},
"decky_title": "Decky",
"decky_update_available": "{{tag_name}} のアップデートが利用可能です!",
"plugin_update_other": "{{count}} 個のプラグインのアップデートが利用可能です!",
"plugin_error_uninstall": "{{name}} プラグインを読み込む際に上記のような例外が発生しました。 これは通常、SteamUIの最新バージョンに合ったプラグインのアップデートが必要な場合に発生します。Decky設定のプラグインセクションでアップデートがあるかどうかを確認するか、アンインストールをお試しください。"
},
"SettingsDeveloperIndex": {
"cef_console": {
"button": "コンソールを開く",
"label": "CEFコンソール",
"desc": "CEFコンソールを開きます。デバッグ目的でのみ使用してください。これらの項目は危険な可能性があるので、プラグイン開発者であるか、開発者のガイドに従う場合のみ使用する必要があります。"
},
"react_devtools": {
"ip_label": "IP",
"label": "React DevTools を有効化",
"desc": "React DevToolsを実行しているコンピューターへの接続を有効にします。この設定を変更すると、Steam が再ロードされます。有効にする前にIPアドレスを設定してください。"
},
"third_party_plugins": {
"button_install": "インストール",
"button_zip": "開く",
"header": "サードパーティプラグイン",
"label_desc": "URL",
"label_url": "URLからプラグインをインストール",
"label_zip": "ZIPファイルからプラグインをインストール"
},
"valve_internal": {
"desc1": "Valveの内部開発者メニューを有効にします。",
"desc2": "このメニューの機能が分からない場合、このメニューには触れないでください。",
"label": "Valve Internalを有効"
},
"header": "その他"
},
"PluginView": {
"hidden_other": "{{count}} 個のプラグインがこのリストから非表示になっています"
},
"SettingsGeneralIndex": {
"about": {
"decky_version": "Deckyバージョン",
"header": "情報"
},
"developer_mode": {
"label": "開発者モード"
},
"notifications": {
"header": "通知",
"plugin_updates_label": "プラグインのアップデートが利用可能な場合に通知",
"decky_updates_label": "Deckyのアップデートが利用可能な場合に通知"
},
"beta": {
"header": "ベータ版への参加"
},
"other": {
"header": "その他"
},
"updates": {
"header": "アップデート"
}
},
"SettingsIndex": {
"developer_title": "開発者",
"plugins_title": "プラグイン",
"general_title": "一般"
},
"Store": {
"store_filter": {
"label": "フィルター",
"label_def": "すべて"
},
"store_search": {
"label": "検索"
},
"store_sort": {
"label": "並べ替え",
"label_def": "直近のアップデート(新しい順)"
},
"store_source": {
"desc": "すべてのプラグインのソース コードは、GitHubのSteamDeckHomebrew/decky-plugin-databaseリポジトリで入手できます。",
"label": "ソースコード"
},
"store_tabs": {
"alph_asce": "アルファベット(Z to A)",
"alph_desc": "アルファベット(A to Z)",
"title": "閲覧",
"about": "概要"
},
"store_testing_warning": {
"label": "テストストア チャンネルへようこそ",
"desc": "このストアチャンネルを使用して、最先端のプラグイン バージョンをテストできます。 すべてのユーザーがプラグインを更新できるように、必ずGitHubにフィードバックを残してください。"
},
"store_contrib": {
"desc": "Decky Plugin Storeに貢献したい場合は、GitHubのSteamDeckHomebrew/decky-plugin-templateリポジトリを確認してください。 開発と配布に関する情報は README で入手できます。",
"label": "貢献"
},
"store_testing_cta": "Decky Loaderチームを支援するために、新しいプラグインのテストを検討してください!"
},
"StoreSelect": {
"custom_store": {
"label": "カスタムストア",
"url_label": "URL"
},
"store_channel": {
"custom": "カスタム",
"default": "デフォルト",
"label": "ストアチャンネル",
"testing": "テスト"
}
},
"TitleView": {
"decky_store_desc": "Deckyストアを開く",
"settings_desc": "Decky設定を開く"
},
"Updater": {
"decky_updates": "Deckyアップデート",
"no_patch_notes_desc": "このバージョンのパッチノートはありません",
"patch_notes_desc": "パッチノート",
"updates": {
"check_button": "アップデートを確認",
"checking": "確認中",
"cur_version": "現在のバージョン: {{ver}}",
"install_button": "アップデートをインストール",
"label": "アップデート",
"lat_version": "アップデート: {{ver}} を実行中",
"reloading": "再読み込み中",
"updating": "アップデート中"
}
},
"FilePickerError": {
"errors": {
"file_not_found": "指定されたパスは無効です。 内容をご確認の上、正しく入力し直してください。",
"unknown": "不明なエラーが発生しました。 エラー内容は次のとおりです: {{raw_error}}",
"perm_denied": "選択したパスへのアクセス権がありません。選択したフォルダ/ファイルのアクセス権がユーザー(Steam Deckのdeckユーザー)に合わせて正しく設定されていることを確認してください。"
}
},
"PluginCard": {
"plugin_version_label": "プラグインバージョン",
"plugin_no_desc": "説明はありません。",
"plugin_full_access": "このプラグインはSteam Deckの全てのアクセス権を持ちます。",
"plugin_install": "インストール"
},
"RemoteDebugging": {
"remote_cef": {
"label": "リモート CEF デバッグを許可する",
"desc": "ネットワーク上のすべてのユーザーにCEFデバッガへの非認証アクセスを許可します"
}
}
}
+105 -88
View File
@@ -1,16 +1,16 @@
{ {
"BranchSelect": { "BranchSelect": {
"update_channel": { "update_channel": {
"prerelease": "Vooruitgave", "prerelease": "Prerelease",
"stable": "Stabiel", "stable": "Stabiel",
"label": "Update Kanaal", "label": "Updatekanaal",
"testing": "Test" "testing": "Testing"
} }
}, },
"Developer": { "Developer": {
"5secreload": "Herlaad in 5 seconden", "5secreload": "Bezig met herstarten in 5 seconden",
"disabling": "Uitschakelen React DevTools", "disabling": "Bezig met uitschakelen van React DevTools",
"enabling": "Inschakelen React DevTools" "enabling": "Bezig met inschakelen van React DevTools"
}, },
"DropdownMultiselect": { "DropdownMultiselect": {
"button": { "button": {
@@ -19,30 +19,34 @@
}, },
"FilePickerError": { "FilePickerError": {
"errors": { "errors": {
"unknown": "Een onbekende fout is opgetreden. De ruwe fout is: {{raw_error}}", "unknown": "Er is een onbekende fout opgetreden. De foutmelding is: {{raw_error}}",
"file_not_found": "Het opgegeven pad is niet geldig. Controleer het en voer het opnieuw correct in." "file_not_found": "Het opgegeven pad is niet geldig. Controleer het en voer het opnieuw correct in.",
"perm_denied": "U heeft geen toegang tot de opgegeven map. Controleer of uw gebruiker (deck op Steam Deck) de juiste permissies heeft om toegang te krijgen tot de opgegeven map/het opgegeven bestand."
} }
}, },
"FilePickerIndex": { "FilePickerIndex": {
"files": { "files": {
"all_files": "Alle bestanden", "all_files": "Alle bestanden",
"file_type": "Bestandstype", "file_type": "Bestandstype",
"show_hidden": "Toon verborgen bestanden" "show_hidden": "Verborgen bestanden tonen"
}, },
"filter": { "filter": {
"created_desc": "Gecreëerd ( Nieuwste)", "created_desc": "Aanmaakdatum (nieuwste)",
"modified_asce": "Veranderd (Oudste)", "modified_asce": "Gewijzigd op (oudste)",
"modified_desc": "Veranderd (Nieuwste)", "modified_desc": "Gewijzigd op (nieuwste)",
"name_asce": "Z-A", "name_asce": "Naam (Z-A)",
"name_desc": "A-Z", "name_desc": "Naam (A-Z)",
"size_asce": "Grootte (Kleinste)", "size_asce": "Grootte (kleinste)",
"size_desc": "Grootte (Grootste)", "size_desc": "Grootte (grootste)",
"created_asce": "Gecreëerd (Oudste)" "created_asce": "Aanmaakdatum (oudste)"
}, },
"folder": { "folder": {
"label": "Map", "label": "Map",
"select": "Gebruik deze map", "select": "Deze map gebruiken",
"show_more": "Toon meer bestanden" "show_more": "Meer bestanden tonen"
},
"file": {
"select": "Dit bestand selecteren"
} }
}, },
"PluginView": { "PluginView": {
@@ -50,13 +54,13 @@
"hidden_other": "{{count}} plug-ins zijn verborgen in deze lijst" "hidden_other": "{{count}} plug-ins zijn verborgen in deze lijst"
}, },
"PluginListLabel": { "PluginListLabel": {
"hidden": "Verborgen in het snelmenu" "hidden": "Verborgen in snelle toegang"
}, },
"PluginCard": { "PluginCard": {
"plugin_install": "Installeren", "plugin_install": "Installeren",
"plugin_no_desc": "Geen beschrijving gegeven.", "plugin_no_desc": "Geen beschrijving gegeven.",
"plugin_version_label": "Plugin Versie", "plugin_version_label": "Plug-inversie",
"plugin_full_access": "Deze plug-in heeft volledige toegang tot je Steam Deck." "plugin_full_access": "Deze plug-in heeft volledige toegang tot uw Steam Deck."
}, },
"PluginInstallModal": { "PluginInstallModal": {
"install": { "install": {
@@ -65,59 +69,59 @@
"title": "Installeer {{artifact}}", "title": "Installeer {{artifact}}",
"desc": "Weet je zeker dat je {{artifact}} {{version}} wilt installeren?" "desc": "Weet je zeker dat je {{artifact}} {{version}} wilt installeren?"
}, },
"no_hash": "Deze plug-in heeft geen hash, u installeert deze op eigen risico.", "no_hash": "Deze plug-in heeft geen hash, je installeert deze op eigen risico.",
"reinstall": { "reinstall": {
"button_idle": "Herinstalleren", "button_idle": "Opnieuw installeren",
"button_processing": "Bezig te herinstalleren", "button_processing": "Bezig met opnieuw te installeren",
"desc": "Weet je zeker dat je {{artifact}} {{version}} opnieuw wilt installeren?", "desc": "Weet je zeker dat je {{artifact}} {{version}} opnieuw wilt installeren?",
"title": "Installeer {{artifact}} opnieuw" "title": "Installeer {{artifact}} opnieuw"
}, },
"update": { "update": {
"button_idle": "Update", "button_idle": "Bijwerken",
"button_processing": "Bezig met updaten", "button_processing": "Bezig met bijwerken",
"title": "{{artifact}} bijwerken", "title": "{{artifact}} bijwerken",
"desc": "Weet je zeker dat je {{artifact}} {{version}} wilt updaten?" "desc": "Weet je zeker dat je {{artifact}} {{version}} wilt bijwerken?"
} }
}, },
"MultiplePluginsInstallModal": { "MultiplePluginsInstallModal": {
"title": { "title": {
"mixed_one": "Wijzig {{count}} plug-in", "mixed_one": "Wijzig 1 plug-in",
"mixed_other": "Pas {{count}} plug-ins aan", "mixed_other": "Wijzig {{count}} plug-ins",
"update_one": "1 plugin bijwerken", "update_one": "Werk 1 plug-in bij",
"update_other": "{{count}} plug-ins bijwerken", "update_other": "Werk {{count}} plug-ins bij",
"install_one": "Installeer 1 plugin", "install_one": "Installeer 1 plug-in",
"install_other": "Installeer {{count}} plugins", "install_other": "Installeer {{count}} plug-ins",
"reinstall_one": "1 plugin opnieuw installeren", "reinstall_one": "Installeer 1 plug-in opnieuw",
"reinstall_other": "{{count}} plugins opnieuw installeren" "reinstall_other": "Installeer {{count}} plug-ins opnieuw"
}, },
"ok_button": { "ok_button": {
"idle": "Bevestigen", "idle": "Bevestigen",
"loading": "Werkend" "loading": "Bezig"
}, },
"confirm": "Weet u zeker dat u de volgende wijzigingen wilt aanbrengen?", "confirm": "Weet je zeker dat je de volgende wijzigingen wilt aanbrengen?",
"description": { "description": {
"install": "Installeer {{name}} {{version}}", "install": "Installeer {{name}} {{version}}",
"update": "Update {{name}} naar {{version}}", "update": "Werk {{name}} bij naar {{version}}",
"reinstall": "Installeer opnieuw {{name}} {{version}}" "reinstall": "Installeer {{name}} {{version}} opnieuw"
} }
}, },
"PluginListIndex": { "PluginListIndex": {
"no_plugin": "Geen plugins geïnstalleerd!", "no_plugin": "Geen plug-ins geïnstalleerd!",
"plugin_actions": "Plugin Acties", "plugin_actions": "Plug-inacties",
"reload": "Herladen", "reload": "Herstarten",
"uninstall": "Verwijderen", "uninstall": "Verwijderen",
"update_to": "Update naar {{name}}", "update_to": "Bijwerken naar {{name}}",
"hide": "Snelle toegang: Verberg", "hide": "Verberg in snelle toegang",
"update_all_one": "Update 1 plugin", "update_all_one": "Werk 1 plug-in bij",
"update_all_other": "Update {{count}} plugins", "update_all_other": "Werk {{count}} plug-ins bij",
"reinstall": "Opnieuw installeren", "reinstall": "Opnieuw installeren",
"show": "Snelle toegang: Toon" "show": "Toon in snelle toegang"
}, },
"PluginLoader": { "PluginLoader": {
"decky_title": "Decky", "decky_title": "Decky",
"error": "Fout", "error": "Fout",
"plugin_load_error": { "plugin_load_error": {
"message": "Fout bij het laden van plugin {{name}}", "message": "Fout bij het laden van plug-in {{name}}",
"toast": "Fout bij het laden van {{name}}" "toast": "Fout bij het laden van {{name}}"
}, },
"plugin_uninstall": { "plugin_uninstall": {
@@ -125,65 +129,70 @@
"desc": "Weet je zeker dat je {{name}} wilt verwijderen?", "desc": "Weet je zeker dat je {{name}} wilt verwijderen?",
"title": "Verwijder {{name}}" "title": "Verwijder {{name}}"
}, },
"plugin_update_one": "Updates beschikbaar voor 1 plugin!", "plugin_update_one": "Updates beschikbaar voor 1 plug-in!",
"plugin_update_other": "Updates beschikbaar voor {{count}} plugins!", "plugin_update_other": "Updates beschikbaar voor {{count}} plug-ins!",
"decky_update_available": "Update naar {{tag_name}} beschikbaar!", "decky_update_available": "Update naar {{tag_name}} beschikbaar!",
"plugin_error_uninstall": "Het laden van {{name}} veroorzaakte een uitzondering zoals hierboven weergegeven. Dit betekent meestal dat de plug-in een update vereist voor de nieuwe versie van SteamUI. Controleer of er een update aanwezig is of evalueer de verwijdering ervan in de Decky-instellingen, in het gedeelte Plug-ins." "plugin_error_uninstall": "Het laden van {{name}} veroorzaakte een fout zoals hierboven weergegeven. Dit betekent meestal dat de plug-in moet worden bijgewerkt voor de nieuwe versie van SteamUI. Controleer of er een update aanwezig is of evalueer de verwijdering ervan in de Decky-instellingen, in het gedeelte Plug-ins."
}, },
"RemoteDebugging": { "RemoteDebugging": {
"remote_cef": { "remote_cef": {
"desc": "Sta ongeauthenticeerde toegang tot de CEF-foutopsporing toe aan iedereen in uw netwerk", "desc": "Sta ongeauthenticeerde toegang tot de CEF-debugger toe aan iedereen in uw netwerk",
"label": "Externe CEF-foutopsporing toestaan" "label": "Externe CEF-debugging toestaan"
} }
}, },
"SettingsDeveloperIndex": { "SettingsDeveloperIndex": {
"cef_console": { "cef_console": {
"button": "Console openen", "button": "Console openen",
"label": "CEF Bedieningsscherm", "label": "CEF-console",
"desc": "Opent de CEF-console. Alleen nuttig voor foutopsporingsdoeleinden. Dingen hier zijn potentieel gevaarlijk en mogen alleen worden gebruikt als u een ontwikkelaar van plug-ins bent, of hier door een ontwikkelaar naartoe wordt geleid." "desc": "Opent de CEF-console. Alleen nuttig voor foutopsporingsdoeleinden. Dingen hier zijn potentieel gevaarlijk en mogen alleen worden gebruikt als je een ontwikkelaar van plug-ins bent, of hier door een ontwikkelaar naartoe wordt geleid."
}, },
"header": "Andere", "header": "Overige",
"react_devtools": { "react_devtools": {
"ip_label": "IP", "ip_label": "IP-adres",
"label": "Aanzetten React DevTools", "label": "React DevTools inschakelen",
"desc": "Maakt verbinding met een computer met React DevTools mogelijk. Als je deze instelling wijzigt, wordt Steam opnieuw geladen. Stel het IP-adres in voordat u het inschakelt." "desc": "Maakt verbinding met een computer met React DevTools mogelijk. Als je deze instelling wijzigt, wordt Steam opnieuw geladen. Stel het IP-adres in voordat je het inschakelt."
}, },
"third_party_plugins": { "third_party_plugins": {
"header": "Plug-ins van derden", "header": "Plug-ins van derden",
"label_desc": "URL", "label_desc": "URL",
"label_url": "Installeer Plugin van URL", "label_url": "Installeer plug-in via een URL",
"label_zip": "Installeer Plugin van Zip bestand", "label_zip": "Installeer plug-in via een ZIP-bestand",
"button_install": "Installeren", "button_install": "Installeren",
"button_zip": "Bladeren" "button_zip": "Bladeren"
}, },
"valve_internal": { "valve_internal": {
"desc1": "Schakelt het interne ontwikkelaarsmenu van Valve in.", "desc1": "Schakelt het interne ontwikkelaarsmenu van Valve in.",
"desc2": "Raak niets in dit menu aan tenzij u weet wat het doet.", "desc2": "Pas niets in dit menu aan, tenzij je weet wat het doet.",
"label": "Valve Internal inschakelen" "label": "Valve Internal inschakelen"
} }
}, },
"SettingsGeneralIndex": { "SettingsGeneralIndex": {
"about": { "about": {
"decky_version": "Decky versie", "decky_version": "Decky-versie",
"header": "Over" "header": "Over"
}, },
"beta": { "beta": {
"header": "Beta deelname" "header": "Beta-deelname"
}, },
"developer_mode": { "developer_mode": {
"label": "Ontwikkelaars modus" "label": "Ontwikkelaarsmodus"
}, },
"other": { "other": {
"header": "Overige" "header": "Overige"
}, },
"updates": { "updates": {
"header": "Nieuwe Versies" "header": "Updates"
},
"notifications": {
"decky_updates_label": "Wanneer er een Decky-update beschikbaar is",
"header": "Meldingen",
"plugin_updates_label": "Wanneer er plug-in-updates beschikbaar zijn"
} }
}, },
"SettingsIndex": { "SettingsIndex": {
"developer_title": "Ontwikkelaar", "developer_title": "Ontwikkelaar",
"general_title": "Algemeen", "general_title": "Algemeen",
"plugins_title": "Plugins" "plugins_title": "Plug-ins"
}, },
"Store": { "Store": {
"store_filter": { "store_filter": {
@@ -191,14 +200,14 @@
"label_def": "Alles" "label_def": "Alles"
}, },
"store_search": { "store_search": {
"label": "Zoek" "label": "Zoeken"
}, },
"store_sort": { "store_sort": {
"label": "Sorteren", "label": "Sorteren",
"label_def": "Laatste Geupdate (Nieuwste)" "label_def": "Laatst bijgewerkt (nieuwste)"
}, },
"store_source": { "store_source": {
"label": "Bron Code", "label": "Broncode",
"desc": "Alle broncode van de plug-in is beschikbaar in de SteamDeckHomebrew/decky-plugin-database-repository op GitHub." "desc": "Alle broncode van de plug-in is beschikbaar in de SteamDeckHomebrew/decky-plugin-database-repository op GitHub."
}, },
"store_tabs": { "store_tabs": {
@@ -207,37 +216,45 @@
"alph_desc": "Alfabetisch (A naar Z)", "alph_desc": "Alfabetisch (A naar Z)",
"title": "Bladeren" "title": "Bladeren"
}, },
"store_testing_cta": "Overweeg alsjeblieft om nieuwe plug-ins te testen om het Decky Loader-team te helpen!", "store_testing_cta": "Overweeg om nieuwe plug-ins te testen om het Decky Loader-team te helpen!",
"store_contrib": { "store_contrib": {
"desc": "Als je wilt bijdragen aan de Decky Plugin winkel, kijk dan in de SteamDeckHomebrew/decky-plugin-template repository op GitHub. Informatie over ontwikkeling en distributie is beschikbaar in de README.", "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": "Bijdragende" "label": "Bijdragen"
},
"store_testing_warning": {
"label": "Welkom bij het Testing-winkelkanaal",
"desc": "Je kunt dit winkelkanaal gebruiken om nog in ontwikkeling zijnde plug-inversies te testen. Zorg ervoor dat je feedback geeft op GitHub, zodat de plug-in voor alle gebruikers kan worden bijgewerkt."
} }
}, },
"StoreSelect": { "StoreSelect": {
"custom_store": { "custom_store": {
"label": "Aangepassingen winkel", "label": "Aangepaste winkel",
"url_label": "URL" "url_label": "URL"
}, },
"store_channel": { "store_channel": {
"custom": "Aanpassingen", "custom": "Aangepast",
"default": "Standaard", "default": "Standaard",
"label": "Winkel Kanaal", "label": "Winkelkanaal",
"testing": "Testen" "testing": "Testing"
} }
}, },
"Updater": { "Updater": {
"patch_notes_desc": "Correctie opmerkingen", "patch_notes_desc": "Patch-opmerkingen",
"updates": { "updates": {
"check_button": "Controleer op updates", "check_button": "Op updates controleren",
"checking": "Controleren", "checking": "Bezig met controleren op updates",
"cur_version": "Huidige versie: {{ver}}", "cur_version": "Huidige versie: {{ver}}",
"install_button": "Installeer Update", "install_button": "Bijwerken",
"label": "Update", "label": "Updates",
"lat_version": "Up-to-date: loopt {{ver}}", "lat_version": "Bijwerkt: versie {{ver}}",
"reloading": "Herstarten", "reloading": "Bezig met herstarten",
"updating": "Aan het updaten" "updating": "Bezig met bijwerken"
}, },
"decky_updates": "Decky Nieuwe Versies", "decky_updates": "Decky-updates",
"no_patch_notes_desc": "geen correctie-opmerkingen voor deze versie" "no_patch_notes_desc": "geen patch-opmerkingen voor deze versie"
},
"TitleView": {
"decky_store_desc": "Decky Store openen",
"settings_desc": "Decky-instellingen openen"
} }
} }
+13 -3
View File
@@ -113,7 +113,9 @@
"reinstall": "Reinstalacja", "reinstall": "Reinstalacja",
"show": "Szybki dostęp: Pokaż", "show": "Szybki dostęp: Pokaż",
"uninstall": "Odinstaluj", "uninstall": "Odinstaluj",
"update_to": "Zaktualizuj do {{name}}" "update_to": "Zaktualizuj do {{name}}",
"unfreeze": "Odblokuj aktualizacje",
"freeze": "Zablokuj aktualizacje"
}, },
"PluginLoader": { "PluginLoader": {
"decky_title": "Decky", "decky_title": "Decky",
@@ -199,7 +201,8 @@
"SettingsIndex": { "SettingsIndex": {
"developer_title": "Deweloper", "developer_title": "Deweloper",
"general_title": "Ogólne", "general_title": "Ogólne",
"plugins_title": "Pluginy" "plugins_title": "Pluginy",
"testing_title": "Testowanie"
}, },
"Store": { "Store": {
"store_contrib": { "store_contrib": {
@@ -225,7 +228,11 @@
"alph_asce": "Alfabetycznie (od Z do A)", "alph_asce": "Alfabetycznie (od Z do A)",
"alph_desc": "Alfabetycznie (od A do Z)", "alph_desc": "Alfabetycznie (od A do Z)",
"title": "Przeglądaj", "title": "Przeglądaj",
"about": "Informacje" "about": "Informacje",
"date_desc": "Od najnowszych",
"downloads_desc": "Najwięcej pobrań",
"downloads_asce": "Najmniej pobrań",
"date_asce": "Od najstarszych"
}, },
"store_testing_cta": "Rozważ przetestowanie nowych pluginów, aby pomóc zespołowi Decky Loader!", "store_testing_cta": "Rozważ przetestowanie nowych pluginów, aby pomóc zespołowi Decky Loader!",
"store_testing_warning": { "store_testing_warning": {
@@ -263,5 +270,8 @@
"TitleView": { "TitleView": {
"settings_desc": "Otwórz ustawienia Decky", "settings_desc": "Otwórz ustawienia Decky",
"decky_store_desc": "Otwórz sklep Decky" "decky_store_desc": "Otwórz sklep Decky"
},
"Testing": {
"download": "Pobierz"
} }
} }
+9 -1
View File
@@ -210,7 +210,11 @@
"title": "Navegar", "title": "Navegar",
"alph_asce": "Alfabética (Z - A)" "alph_asce": "Alfabética (Z - A)"
}, },
"store_testing_cta": "Por favor, considere testar os novos plugins para ajudar o time do Decky Loader!" "store_testing_cta": "Por favor, considere testar os novos plugins para ajudar o time do Decky Loader!",
"store_testing_warning": {
"desc": "Você pode usar este canal da loja para testar versões avançadas do plugin. Certifique-se de deixar feedback no GitHub para que o plugin possa ser atualizado para todos os usuários.",
"label": "Bem-vindo ao Canal de Testes da Loja"
}
}, },
"StoreSelect": { "StoreSelect": {
"custom_store": { "custom_store": {
@@ -255,5 +259,9 @@
"unknown": "Ocorreu um erro desconhecido. O erro completo é: {{raw_error}}", "unknown": "Ocorreu um erro desconhecido. O erro completo é: {{raw_error}}",
"perm_denied": "Você não tem acesso à este diretório. Por favor, verifiquei se seu usuário (deck no Steam Deck) tem as permissões necessárias para acessar este arquivo/pasta." "perm_denied": "Você não tem acesso à este diretório. Por favor, verifiquei se seu usuário (deck no Steam Deck) tem as permissões necessárias para acessar este arquivo/pasta."
} }
},
"TitleView": {
"decky_store_desc": "Abrir Loja Decky",
"settings_desc": "Abrir Definições Decky"
} }
} }
+47 -2
View File
@@ -1,7 +1,27 @@
{ {
"FilePickerIndex": { "FilePickerIndex": {
"folder": { "folder": {
"select": "Usar esta pasta" "select": "Usar esta pasta",
"label": "Pasta",
"show_more": "Mostrar mais ficheiros"
},
"file": {
"select": "Selecionar este ficheiro"
},
"filter": {
"size_desc": "Tamanho (maior)",
"created_asce": "Criado (mais antigo)",
"created_desc": "Criado (mais recente)",
"modified_asce": "Modificado (mais antigo)",
"modified_desc": "Modificado (mais recente)",
"name_asce": "Z-A",
"name_desc": "A-Z",
"size_asce": "Tamanho (mais pequeno)"
},
"files": {
"file_type": "Tipo de ficheiro",
"show_hidden": "Mostrar ficheiros ocultos",
"all_files": "Todos os ficheiros"
} }
}, },
"PluginView": { "PluginView": {
@@ -157,6 +177,11 @@
}, },
"updates": { "updates": {
"header": "Actualizações" "header": "Actualizações"
},
"notifications": {
"decky_updates_label": "Atualização Decky disponível",
"header": "Notificações",
"plugin_updates_label": "Atualizações de plugins disponíveis"
} }
}, },
"SettingsIndex": { "SettingsIndex": {
@@ -190,7 +215,11 @@
"alph_desc": "Alfabeticamente (A-Z)", "alph_desc": "Alfabeticamente (A-Z)",
"title": "Navegar" "title": "Navegar"
}, },
"store_testing_cta": "Testa novos plugins e ajuda a equipa do Decky Loader!" "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.",
"label": "Bem-vindo ao Canal de Testes da Loja"
}
}, },
"StoreSelect": { "StoreSelect": {
"custom_store": { "custom_store": {
@@ -218,5 +247,21 @@
"reloading": "Recarregar", "reloading": "Recarregar",
"install_button": "Instalar actualização" "install_button": "Instalar actualização"
} }
},
"FilePickerError": {
"errors": {
"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, verifique e insira-o corretamente."
}
},
"TitleView": {
"decky_store_desc": "Abrir a Loja Decky",
"settings_desc": "Abrir as Definições Decky"
},
"DropdownMultiselect": {
"button": {
"back": "Voltar"
}
} }
} }
+1 -1
View File
@@ -1,4 +1,4 @@
aiohttp==3.8.4 aiohttp==3.9.0
aiohttp-jinja2==1.5.1 aiohttp-jinja2==1.5.1
aiohttp_cors==0.7.0 aiohttp_cors==0.7.0
watchdog==2.1.7 watchdog==2.1.7
+41 -8
View File
@@ -10,6 +10,7 @@ from hashlib import sha256
from io import BytesIO from io import BytesIO
from logging import getLogger from logging import getLogger
from os import R_OK, W_OK, path, listdir, access, mkdir from os import R_OK, W_OK, path, listdir, access, mkdir
from re import sub
from shutil import rmtree from shutil import rmtree
from time import time from time import time
from zipfile import ZipFile from zipfile import ZipFile
@@ -137,7 +138,7 @@ class PluginBrowser:
# logger.debug("current plugins: %s", snapshot_string) # logger.debug("current plugins: %s", snapshot_string)
if name in self.plugins: if name in self.plugins:
logger.debug("Plugin %s was found", name) logger.debug("Plugin %s was found", name)
self.plugins[name].stop() self.plugins[name].stop(uninstall=True)
logger.debug("Plugin %s was stopped", name) logger.debug("Plugin %s was stopped", name)
del self.plugins[name] del self.plugins[name]
logger.debug("Plugin %s was removed from the dictionary", name) logger.debug("Plugin %s was removed from the dictionary", name)
@@ -162,12 +163,6 @@ class PluginBrowser:
current_plugin_order = self.settings.getSetting("pluginOrder")[:] current_plugin_order = self.settings.getSetting("pluginOrder")[:]
if self.loader.watcher: if self.loader.watcher:
self.loader.watcher.disabled = True self.loader.watcher.disabled = True
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.")
# Check if the file is a local file or a URL # Check if the file is a local file or a URL
if artifact.startswith("file://"): if artifact.startswith("file://"):
@@ -186,6 +181,40 @@ class PluginBrowser:
else: else:
logger.fatal(f"Could not fetch from URL. {await res.text()}") logger.fatal(f"Could not fetch from URL. {await res.text()}")
storeUrl = ""
match self.settings.getSetting("store", 0):
case 0: storeUrl = "https://plugins.deckbrew.xyz/plugins" # default
case 1: storeUrl = "https://testing.deckbrew.xyz/plugins" # testing
case 2: storeUrl = self.settings.getSetting("store-url", "https://plugins.deckbrew.xyz/plugins") # custom
case _: storeUrl = "https://plugins.deckbrew.xyz/plugins"
logger.info(f"Incrementing installs for {name} from URL {storeUrl} (version {version})")
async with ClientSession() as client:
res = await client.post(storeUrl+f"/{name}/versions/{version}/increment?isUpdate={isInstalled}", ssl=get_ssl_context())
if res.status != 200:
logger.error(f"Server did not accept install count increment request. code: {res.status}")
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]
if len(plugin_json_list) == 0:
logger.fatal("No plugin.json found in plugin ZIP")
return
elif len(plugin_json_list) > 1:
logger.fatal("Multiple plugin.json found in plugin ZIP")
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.")
# Check to make sure we got the file # Check to make sure we got the file
if res_zip is None: if res_zip is None:
logger.fatal(f"Could not fetch {artifact}") logger.fatal(f"Could not fetch {artifact}")
@@ -260,12 +289,16 @@ class PluginBrowser:
Args: Args:
name (string): The name of the plugin name (string): The name of the plugin
""" """
frozen_plugins = self.settings.getSetting("frozenPlugins", [])
if name in frozen_plugins:
frozen_plugins.remove(name)
self.settings.setSetting("frozenPlugins", frozen_plugins)
hidden_plugins = self.settings.getSetting("hiddenPlugins", []) hidden_plugins = self.settings.getSetting("hiddenPlugins", [])
if name in hidden_plugins: if name in hidden_plugins:
hidden_plugins.remove(name) hidden_plugins.remove(name)
self.settings.setSetting("hiddenPlugins", hidden_plugins) self.settings.setSetting("hiddenPlugins", hidden_plugins)
plugin_order = self.settings.getSetting("pluginOrder", []) plugin_order = self.settings.getSetting("pluginOrder", [])
if name in plugin_order: if name in plugin_order:
+2 -2
View File
@@ -412,7 +412,7 @@ async def get_tab_lambda(test: Callable[[Tab], bool]) -> Tab:
SHARED_CTX_NAMES = ["SharedJSContext", "Steam Shared Context presented by Valve™", "Steam", "SP"] SHARED_CTX_NAMES = ["SharedJSContext", "Steam Shared Context presented by Valve™", "Steam", "SP"]
CLOSEABLE_URLS = ["about:blank", "data:text/html,%3Cbody%3E%3C%2Fbody%3E"] # Closing anything other than these *really* likes to crash Steam CLOSEABLE_URLS = ["about:blank", "data:text/html,%3Cbody%3E%3C%2Fbody%3E"] # Closing anything other than these *really* likes to crash Steam
DO_NOT_CLOSE_URL = "Valve Steam Gamepad/default" # Steam Big Picture Mode tab DO_NOT_CLOSE_URLS = ["Valve Steam Gamepad/default", "Valve%20Steam%20Gamepad/default"] # Steam Big Picture Mode tab
def tab_is_gamepadui(t: Tab) -> bool: 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 and t.title in SHARED_CTX_NAMES
@@ -432,7 +432,7 @@ async def inject_to_tab(tab_name: str, js: str, run_async: bool = False):
async def close_old_tabs(): async def close_old_tabs():
tabs = await get_tabs() tabs = await get_tabs()
for t in tabs: for t in tabs:
if not t.title or (t.title not in SHARED_CTX_NAMES and any(url in t.url for url in CLOSEABLE_URLS) and DO_NOT_CLOSE_URL not in t.url): if not t.title or (t.title not in SHARED_CTX_NAMES and any(url in t.url for url in CLOSEABLE_URLS) and not any(url in t.url for url in DO_NOT_CLOSE_URLS)):
logger.debug("Closing tab: " + getattr(t, "title", "Untitled")) logger.debug("Closing tab: " + getattr(t, "title", "Untitled"))
await t.close() await t.close()
await sleep(0.5) await sleep(0.5)
+30 -2
View File
@@ -58,8 +58,22 @@ def chown(path : str, user : UserType = UserType.HOST_USER, recursive : bool =
def chmod(path : str, permissions : int, recursive : bool = True) -> bool: def chmod(path : str, permissions : int, recursive : bool = True) -> bool:
if _get_effective_user_id() != 0: if _get_effective_user_id() != 0:
return True return True
result = call(["chmod", "-R", str(permissions), path] if recursive else ["chmod", str(permissions), path])
return result == 0 try:
octal_permissions = int(str(permissions), 8)
if recursive:
for root, dirs, files in os.walk(path):
for d in dirs:
os.chmod(os.path.join(root, d), octal_permissions)
for d in files:
os.chmod(os.path.join(root, d), octal_permissions)
os.chmod(path, octal_permissions)
except:
return False
return True
def folder_owner(path : str) -> UserType|None: def folder_owner(path : str) -> UserType|None:
user_owner = _get_user_owner(path) user_owner = _get_user_owner(path)
@@ -125,15 +139,29 @@ async def service_restart(service_name : str) -> bool:
return res.returncode == 0 return res.returncode == 0
async def service_stop(service_name : str) -> bool: async def service_stop(service_name : str) -> bool:
if not await service_active(service_name):
# Service isn't running. pretend we stopped it
return True
cmd = ["systemctl", "stop", service_name] cmd = ["systemctl", "stop", service_name]
res = run(cmd, stdout=PIPE, stderr=STDOUT) res = run(cmd, stdout=PIPE, stderr=STDOUT)
return res.returncode == 0 return res.returncode == 0
async def service_start(service_name : str) -> bool: async def service_start(service_name : str) -> bool:
if await service_active(service_name):
# Service is running. pretend we started it
return True
cmd = ["systemctl", "start", service_name] cmd = ["systemctl", "start", service_name]
res = run(cmd, stdout=PIPE, stderr=STDOUT) res = run(cmd, stdout=PIPE, stderr=STDOUT)
return res.returncode == 0 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)
return res.returncode == 0
def get_privileged_path() -> str: def get_privileged_path() -> str:
path = os.getenv("PRIVILEGED_PATH") path = os.getenv("PRIVILEGED_PATH")
+7 -7
View File
@@ -2,9 +2,9 @@
import sys import sys
from typing import Dict from typing import Dict
from .localplatform import (chmod, chown, service_stop, service_start, from .localplatform import (chmod, chown, service_stop, service_start,
ON_WINDOWS, get_log_level, get_live_reload, ON_WINDOWS, ON_LINUX, get_log_level, get_live_reload,
get_server_port, get_server_host, get_chown_plugin_path, get_server_port, get_server_host, get_chown_plugin_path,
get_privileged_path) get_privileged_path, restart_webhelper)
if hasattr(sys, '_MEIPASS'): if hasattr(sys, '_MEIPASS'):
chmod(sys._MEIPASS, 755) # type: ignore chmod(sys._MEIPASS, 755) # type: ignore
# Full imports # Full imports
@@ -158,10 +158,10 @@ class PluginManager:
async def inject_javascript(self, tab: Tab, first: bool=False, request: Request|None=None): async def inject_javascript(self, tab: Tab, first: bool=False, request: Request|None=None):
logger.info("Loading Decky frontend!") logger.info("Loading Decky frontend!")
try: try:
if first: # if first:
if await tab.has_global_var("deckyHasLoaded", False): if ON_LINUX and await tab.has_global_var("deckyHasLoaded", False):
await close_old_tabs() await restart_webhelper()
await tab.evaluate_js("try{if (window.deckyHasLoaded){setTimeout(() => location.reload(), 100)}else{window.deckyHasLoaded = true;(async()=>{try{while(!window.SP_REACT){await new Promise(r => setTimeout(r, 10))};await import('http://localhost:1337/frontend/index.js')}catch(e){console.error(e)};})();}}catch(e){console.error(e)}", False, False, False) await tab.evaluate_js("try{if (window.deckyHasLoaded){setTimeout(() => SteamClient.Browser.RestartJSContext(), 100)}else{window.deckyHasLoaded = true;(async()=>{try{while(!window.webpackChunksteamui){await new Promise(r => setTimeout(r, 10))};await import('http://localhost:1337/frontend/index.js')}catch(e){console.error(e)};})();}}catch(e){console.error(e)}", False, False, False)
except: except:
logger.info("Failed to inject JavaScript into tab\n" + format_exc()) logger.info("Failed to inject JavaScript into tab\n" + format_exc())
pass pass
@@ -182,7 +182,7 @@ def main():
logger.warning(f"decky is running as an unprivileged user, this is not officially supported and may cause issues") logger.warning(f"decky is running as an unprivileged user, this is not officially supported and may cause issues")
# Append the loader's plugin path to the recognized python paths # Append the loader's plugin path to the recognized python paths
sys.path.append(path.join(path.dirname(__file__), "plugin")) sys.path.append(path.join(path.dirname(__file__), "..", "plugin"))
# Append the system and user python paths # Append the system and user python paths
sys.path.extend(get_system_pythonpaths()) sys.path.extend(get_system_pythonpaths())
+26 -4
View File
@@ -7,7 +7,7 @@ from logging import getLogger
from traceback import format_exc from traceback import format_exc
from os import path, environ from os import path, environ
from signal import SIGINT, signal from signal import SIGINT, signal
from sys import exit, path as syspath from sys import exit, path as syspath, modules as sysmodules
from typing import Any, Dict from typing import Any, Dict
from .localsocket import LocalSocket from .localsocket import LocalSocket
from .localplatform import setgid, setuid, get_username, get_home_path from .localplatform import setgid, setuid, get_username, get_home_path
@@ -79,6 +79,11 @@ class PluginWrapper:
# append the plugin's `py_modules` to the recognized python paths # append the plugin's `py_modules` to the recognized python paths
syspath.append(path.join(environ["DECKY_PLUGIN_DIR"], "py_modules")) syspath.append(path.join(environ["DECKY_PLUGIN_DIR"], "py_modules"))
#TODO: FIX IN A LESS CURSED WAY
keys = [key.replace("src.", "") for key in sysmodules if key.startswith("src.")]
for key in keys:
sysmodules[key] = sysmodules["src"].__dict__[key]
spec = spec_from_file_location("_", self.file) spec = spec_from_file_location("_", self.file)
assert spec is not None assert spec is not None
module = module_from_spec(spec) module = module_from_spec(spec)
@@ -108,12 +113,29 @@ class PluginWrapper:
self.log.error("Failed to unload " + self.name + "!\n" + format_exc()) self.log.error("Failed to unload " + self.name + "!\n" + format_exc())
exit(0) exit(0)
async def _uninstall(self):
try:
self.log.info("Attempting to uninstall with plugin " + self.name + "'s \"_uninstall\" function.\n")
if hasattr(self.Plugin, "_uninstall"):
await self.Plugin._uninstall(self.Plugin)
self.log.info("Uninstalled " + self.name + "\n")
else:
self.log.info("Could not find \"_uninstall\" in " + self.name + "'s main.py" + "\n")
except:
self.log.error("Failed to uninstall " + self.name + "!\n" + format_exc())
exit(0)
async def _on_new_message(self, message : str) -> str|None: async def _on_new_message(self, message : str) -> str|None:
data = loads(message) data = loads(message)
if "stop" in data: if "stop" in data:
self.log.info("Calling Loader unload function.") self.log.info("Calling Loader unload function.")
await self._unload() await self._unload()
if data.get('uninstall'):
self.log.info("Calling Loader uninstall function.")
await self._uninstall()
get_event_loop().stop() get_event_loop().stop()
while get_event_loop().is_running(): while get_event_loop().is_running():
await sleep(0) await sleep(0)
@@ -136,12 +158,12 @@ class PluginWrapper:
multiprocessing.Process(target=self._init).start() multiprocessing.Process(target=self._init).start()
return self return self
def stop(self): def stop(self, uninstall: bool = False):
if self.passive: if self.passive:
return return
async def _(self: PluginWrapper): async def _(self: PluginWrapper):
await self.socket.write_single_line(dumps({ "stop": True }, ensure_ascii=False)) await self.socket.write_single_line(dumps({ "stop": True, "uninstall": uninstall }, ensure_ascii=False))
await self.socket.close_socket_connection() await self.socket.close_socket_connection()
get_event_loop().create_task(_(self)) get_event_loop().create_task(_(self))
@@ -150,7 +172,7 @@ class PluginWrapper:
if self.passive: if self.passive:
raise RuntimeError("This plugin is passive (aka does not implement main.py)") raise RuntimeError("This plugin is passive (aka does not implement main.py)")
async with self.method_call_lock: async with self.method_call_lock:
# reader, writer = # reader, writer =
await self.socket.get_socket_connection() await self.socket.get_socket_connection()
await self.socket.write_single_line(dumps({ "method": method_name, "args": kwargs }, ensure_ascii=False)) await self.socket.write_single_line(dumps({ "method": method_name, "args": kwargs }, ensure_ascii=False))
+114 -40
View File
@@ -1,20 +1,29 @@
from __future__ import annotations from __future__ import annotations
import os
import shutil
from asyncio import sleep from asyncio import sleep
from json.decoder import JSONDecodeError from json.decoder import JSONDecodeError
from logging import getLogger from logging import getLogger
import os
from os import getcwd, path, remove from os import getcwd, path, remove
from typing import TYPE_CHECKING, List, TypedDict import shutil
if TYPE_CHECKING: from typing import List, TYPE_CHECKING, TypedDict
from .main import PluginManager import zipfile
from .localplatform import chmod, service_restart, ON_LINUX, get_keep_systemd_service, get_selinux
from aiohttp import ClientSession, web from aiohttp import ClientSession, web
from . import helpers from . import helpers
from .injector import get_gamepadui_tab from .injector import get_gamepadui_tab
from .localplatform import (
ON_LINUX,
ON_WINDOWS,
chmod,
get_keep_systemd_service,
get_selinux,
service_restart,
)
from .settings import SettingsManager from .settings import SettingsManager
if TYPE_CHECKING:
from .main import PluginManager
logger = getLogger("Updater") logger = getLogger("Updater")
@@ -25,6 +34,12 @@ class RemoteVer(TypedDict):
tag_name: str tag_name: str
prerelease: bool prerelease: bool
assets: List[RemoteVerAsset] assets: List[RemoteVerAsset]
class TestingVersion(TypedDict):
id: int
name: str
link: str
head_sha: str
class Updater: class Updater:
def __init__(self, context: PluginManager) -> None: def __init__(self, context: PluginManager) -> None:
@@ -36,7 +51,9 @@ class Updater:
"get_version": self.get_version, "get_version": self.get_version,
"do_update": self.do_update, "do_update": self.do_update,
"do_restart": self.do_restart, "do_restart": self.do_restart,
"check_for_updates": self.check_for_updates "check_for_updates": self.check_for_updates,
"get_testing_versions": self.get_testing_versions,
"download_testing_version": self.download_testing_version
} }
self.remoteVer: RemoteVer | None = None self.remoteVer: RemoteVer | None = None
self.allRemoteVers: List[RemoteVer] = [] self.allRemoteVers: List[RemoteVer] = []
@@ -150,6 +167,53 @@ class Updater:
pass pass
await sleep(60 * 60 * 6) # 6 hours await sleep(60 * 60 * 6) # 6 hours
async def download_decky_binary(self, download_url: str, version: str, is_zip: bool = False):
download_filename = "PluginLoader" if ON_LINUX else "PluginLoader.exe"
download_temp_filename = download_filename + ".new"
tab = await get_gamepadui_tab()
await tab.open_websocket()
async with ClientSession() as web:
logger.debug("Downloading binary")
async with web.request("GET", download_url, ssl=helpers.get_ssl_context(), allow_redirects=True) as res:
total = int(res.headers.get('content-length', 0))
with open(path.join(getcwd(), download_temp_filename), "wb") as out:
progress = 0
raw = 0
async for c in res.content.iter_chunked(512):
out.write(c)
if total != 0:
raw += len(c)
new_progress = round((raw / total) * 100)
if progress != new_progress:
self.context.loop.create_task(tab.evaluate_js(f"window.DeckyUpdater.updateProgress({new_progress})", False, False, False))
progress = new_progress
with open(path.join(getcwd(), ".loader.version"), "w", encoding="utf-8") as out:
out.write(version)
if ON_LINUX:
remove(path.join(getcwd(), download_filename))
if (is_zip):
with zipfile.ZipFile(path.join(getcwd(), download_temp_filename), 'r') as file:
file.getinfo(download_filename).filename = download_filename + ".unzipped"
file.extract(download_filename)
remove(path.join(getcwd(), download_temp_filename))
shutil.move(path.join(getcwd(), download_filename + ".unzipped"), path.join(getcwd(), download_filename))
else:
shutil.move(path.join(getcwd(), download_temp_filename), path.join(getcwd(), download_filename))
chmod(path.join(getcwd(), download_filename), 777, False)
if get_selinux():
from asyncio.subprocess import create_subprocess_exec
process = await create_subprocess_exec("chcon", "-t", "bin_t", path.join(getcwd(), download_filename))
logger.info(f"Setting the executable flag with chcon returned {await process.wait()}")
logger.info("Updated loader installation.")
await tab.evaluate_js("window.DeckyUpdater.finish()", False, False)
await self.do_restart()
await tab.close_websocket()
async def do_update(self): async def do_update(self):
logger.debug("Starting update.") logger.debug("Starting update.")
try: try:
@@ -161,7 +225,6 @@ class Updater:
version = self.remoteVer["tag_name"] version = self.remoteVer["tag_name"]
download_url = None download_url = None
download_filename = "PluginLoader" if ON_LINUX else "PluginLoader.exe" download_filename = "PluginLoader" if ON_LINUX else "PluginLoader.exe"
download_temp_filename = download_filename + ".new"
for x in self.remoteVer["assets"]: for x in self.remoteVer["assets"]:
if x["name"] == download_filename: if x["name"] == download_filename:
@@ -174,8 +237,6 @@ class Updater:
service_url = self.get_service_url() service_url = self.get_service_url()
logger.debug("Retrieved service URL") logger.debug("Retrieved service URL")
tab = await get_gamepadui_tab()
await tab.open_websocket()
async with ClientSession() as web: async with ClientSession() as web:
if ON_LINUX and not get_keep_systemd_service(): if ON_LINUX and not get_keep_systemd_service():
logger.debug("Downloading systemd service") logger.debug("Downloading systemd service")
@@ -203,36 +264,49 @@ class Updater:
os.mkdir(path.join(getcwd(), ".systemd")) os.mkdir(path.join(getcwd(), ".systemd"))
shutil.move(service_file_path, path.join(getcwd(), ".systemd")+"/plugin_loader.service") shutil.move(service_file_path, path.join(getcwd(), ".systemd")+"/plugin_loader.service")
logger.debug("Downloading binary") await self.download_decky_binary(download_url, version)
async with web.request("GET", download_url, ssl=helpers.get_ssl_context(), allow_redirects=True) as res:
total = int(res.headers.get('content-length', 0))
with open(path.join(getcwd(), download_temp_filename), "wb") as out:
progress = 0
raw = 0
async for c in res.content.iter_chunked(512):
out.write(c)
raw += len(c)
new_progress = round((raw / total) * 100)
if progress != new_progress:
self.context.loop.create_task(tab.evaluate_js(f"window.DeckyUpdater.updateProgress({new_progress})", False, False, False))
progress = new_progress
with open(path.join(getcwd(), ".loader.version"), "w", encoding="utf-8") as out:
out.write(version)
if ON_LINUX:
remove(path.join(getcwd(), download_filename))
shutil.move(path.join(getcwd(), download_temp_filename), path.join(getcwd(), download_filename))
chmod(path.join(getcwd(), download_filename), 777, False)
if get_selinux():
from asyncio.subprocess import create_subprocess_exec
process = await create_subprocess_exec("chcon", "-t", "bin_t", path.join(getcwd(), download_filename))
logger.info(f"Setting the executable flag with chcon returned {await process.wait()}")
logger.info("Updated loader installation.")
await tab.evaluate_js("window.DeckyUpdater.finish()", False, False)
await self.do_restart()
await tab.close_websocket()
async def do_restart(self): async def do_restart(self):
await service_restart("plugin_loader") await service_restart("plugin_loader")
async def get_testing_versions(self) -> List[TestingVersion]:
result: List[TestingVersion] = []
async with ClientSession() as web:
async with web.request("GET", "https://api.github.com/repos/SteamDeckHomebrew/decky-loader/pulls",
headers={'X-GitHub-Api-Version': '2022-11-28'}, params={'state':'open'}, ssl=helpers.get_ssl_context()) as res:
open_prs = await res.json()
for pr in open_prs:
result.append({
"id": int(pr['number']),
"name": pr['title'],
"link": pr['html_url'],
"head_sha": pr['head']['sha'],
})
return result
async def download_testing_version(self, pr_id: int, sha_id: str):
down_id = ''
#Get all the associated workflow run for the given sha_id code hash
async with ClientSession() as web:
async with web.request("GET", "https://api.github.com/repos/SteamDeckHomebrew/decky-loader/actions/runs",
headers={'X-GitHub-Api-Version': '2022-11-28'}, params={'event':'pull_request', 'head_sha': sha_id}, ssl=helpers.get_ssl_context()) as res:
works = await res.json()
#Iterate over the workflow_run to get the two builds if they exists
for work in works['workflow_runs']:
if ON_WINDOWS and work['name'] == 'Builder Win':
down_id=work['id']
break
elif ON_LINUX and work['name'] == 'Builder':
down_id=work['id']
break
if down_id != '':
async with ClientSession() as web:
async with web.request("GET", f"https://api.github.com/repos/SteamDeckHomebrew/decky-loader/actions/runs/{down_id}/artifacts",
headers={'X-GitHub-Api-Version': '2022-11-28'}, ssl=helpers.get_ssl_context()) as res:
jresp = await res.json()
#If the request found at least one artifact to download...
if int(jresp['total_count']) != 0:
# this assumes that the artifact we want is the first one!
down_link = f"https://nightly.link/SteamDeckHomebrew/decky-loader/actions/artifacts/{jresp['artifacts'][0]['id']}.zip"
#Then fetch it and restart itself
await self.download_decky_binary(down_link, f'PR-{pr_id}' , True)
+2 -2
View File
@@ -238,7 +238,7 @@ class Utilities:
elif include_files: elif include_files:
# Handle requested extensions if present # Handle requested extensions if present
if len(include_ext) == 0 or 'all_files' in include_ext \ if len(include_ext) == 0 or 'all_files' in include_ext \
or splitext(file.name)[1].lstrip('.') in include_ext: or splitext(file.name)[1].lstrip('.').upper() in (ext.upper() for ext in include_ext):
if (is_hidden and include_hidden) or not is_hidden: if (is_hidden and include_hidden) or not is_hidden:
files.append({"file": file, "filest": filest, "is_dir": False}) files.append({"file": file, "filest": filest, "is_dir": False})
# Filter logic # Filter logic
@@ -360,7 +360,7 @@ class Utilities:
tab = await get_gamepadui_tab() tab = await get_gamepadui_tab()
self.rdt_script_id = None self.rdt_script_id = None
await close_old_tabs() await close_old_tabs()
await tab.evaluate_js("location.reload();", False, True, False) await tab.evaluate_js("SteamClient.Browser.RestartJSContext();", False, True, False)
self.logger.info("React DevTools disabled") self.logger.info("React DevTools disabled")
async def get_user_info(self) -> Dict[str, str]: async def get_user_info(self) -> Dict[str, str]:
+2 -2
View File
@@ -25,7 +25,7 @@
"i18next-parser": "^8.0.0", "i18next-parser": "^8.0.0",
"import-sort-style-module": "^6.0.0", "import-sort-style-module": "^6.0.0",
"inquirer": "^8.2.5", "inquirer": "^8.2.5",
"prettier": "^2.8.8", "prettier": "^3.2.5",
"prettier-plugin-import-sort": "^0.0.7", "prettier-plugin-import-sort": "^0.0.7",
"react": "16.14.0", "react": "16.14.0",
"react-dom": "16.14.0", "react-dom": "16.14.0",
@@ -44,7 +44,7 @@
} }
}, },
"dependencies": { "dependencies": {
"decky-frontend-lib": "3.21.1", "decky-frontend-lib": "3.25.0",
"filesize": "^10.0.7", "filesize": "^10.0.7",
"i18next": "^23.2.1", "i18next": "^23.2.1",
"i18next-http-backend": "^2.2.1", "i18next-http-backend": "^2.2.1",
+15 -15
View File
@@ -6,8 +6,8 @@ settings:
dependencies: dependencies:
decky-frontend-lib: decky-frontend-lib:
specifier: 3.21.1 specifier: 3.25.0
version: 3.21.1 version: 3.25.0
filesize: filesize:
specifier: ^10.0.7 specifier: ^10.0.7
version: 10.0.7 version: 10.0.7
@@ -77,11 +77,11 @@ devDependencies:
specifier: ^8.2.5 specifier: ^8.2.5
version: 8.2.5 version: 8.2.5
prettier: prettier:
specifier: ^2.8.8 specifier: ^3.2.5
version: 2.8.8 version: 3.2.5
prettier-plugin-import-sort: prettier-plugin-import-sort:
specifier: ^0.0.7 specifier: ^0.0.7
version: 0.0.7(prettier@2.8.8) version: 0.0.7(prettier@3.2.5)
react: react:
specifier: 16.14.0 specifier: 16.14.0
version: 16.14.0 version: 16.14.0
@@ -1482,8 +1482,8 @@ packages:
dependencies: dependencies:
ms: 2.1.2 ms: 2.1.2
/decky-frontend-lib@3.21.1: /decky-frontend-lib@3.25.0:
resolution: {integrity: sha512-30605ET9qqZ6St6I9WmMmLGgSrTIdMwo7xy85+lRaF1miUd2icOGEJjwnbVcZDdkal+1fJ3tNEDXlchVfG4TrA==} resolution: {integrity: sha512-2lBoHS2AIRmuluq/bGdHBz+uyToQE7k3K/vDq1MQbDZ4eC+8CGDuh2T8yZOj3D0yjGP2MdikNNAWPA9Z5l2qDg==}
dev: false dev: false
/decode-named-character-reference@1.0.2: /decode-named-character-reference@1.0.2:
@@ -1852,8 +1852,8 @@ packages:
resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==}
dev: true dev: true
/fsevents@2.3.2: /fsevents@2.3.3:
resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==} resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
os: [darwin] os: [darwin]
requiresBuild: true requiresBuild: true
@@ -3108,7 +3108,7 @@ packages:
engines: {node: '>=8.6'} engines: {node: '>=8.6'}
dev: true dev: true
/prettier-plugin-import-sort@0.0.7(prettier@2.8.8): /prettier-plugin-import-sort@0.0.7(prettier@3.2.5):
resolution: {integrity: sha512-O0KlUSq+lwvh+UiN3wZDT6wWkf7TNxTVv2/XXE5KqpRNbFJq3nRg2ftzBYFFO8QGpdWIrOB0uCTCtFjIxmVKQw==} resolution: {integrity: sha512-O0KlUSq+lwvh+UiN3wZDT6wWkf7TNxTVv2/XXE5KqpRNbFJq3nRg2ftzBYFFO8QGpdWIrOB0uCTCtFjIxmVKQw==}
peerDependencies: peerDependencies:
prettier: '>= 2.0' prettier: '>= 2.0'
@@ -3117,14 +3117,14 @@ packages:
import-sort-config: 6.0.0 import-sort-config: 6.0.0
import-sort-parser-babylon: 6.0.0 import-sort-parser-babylon: 6.0.0
import-sort-parser-typescript: 6.0.0 import-sort-parser-typescript: 6.0.0
prettier: 2.8.8 prettier: 3.2.5
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
dev: true dev: true
/prettier@2.8.8: /prettier@3.2.5:
resolution: {integrity: sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==} resolution: {integrity: sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==}
engines: {node: '>=10.13.0'} engines: {node: '>=14'}
hasBin: true hasBin: true
dev: true dev: true
@@ -3477,7 +3477,7 @@ packages:
engines: {node: '>=10.0.0'} engines: {node: '>=10.0.0'}
hasBin: true hasBin: true
optionalDependencies: optionalDependencies:
fsevents: 2.3.2 fsevents: 2.3.3
dev: true dev: true
/rsvp@3.2.1: /rsvp@3.2.1:
+8
View File
@@ -8,6 +8,7 @@ import { VerInfo } from '../updater';
interface PublicDeckyState { interface PublicDeckyState {
plugins: Plugin[]; plugins: Plugin[];
pluginOrder: string[]; pluginOrder: string[];
frozenPlugins: string[];
hiddenPlugins: string[]; hiddenPlugins: string[];
activePlugin: Plugin | null; activePlugin: Plugin | null;
updates: PluginUpdateMapping | null; updates: PluginUpdateMapping | null;
@@ -26,6 +27,7 @@ export interface UserInfo {
export class DeckyState { export class DeckyState {
private _plugins: Plugin[] = []; private _plugins: Plugin[] = [];
private _pluginOrder: string[] = []; private _pluginOrder: string[] = [];
private _frozenPlugins: string[] = [];
private _hiddenPlugins: string[] = []; private _hiddenPlugins: string[] = [];
private _activePlugin: Plugin | null = null; private _activePlugin: Plugin | null = null;
private _updates: PluginUpdateMapping | null = null; private _updates: PluginUpdateMapping | null = null;
@@ -41,6 +43,7 @@ export class DeckyState {
return { return {
plugins: this._plugins, plugins: this._plugins,
pluginOrder: this._pluginOrder, pluginOrder: this._pluginOrder,
frozenPlugins: this._frozenPlugins,
hiddenPlugins: this._hiddenPlugins, hiddenPlugins: this._hiddenPlugins,
activePlugin: this._activePlugin, activePlugin: this._activePlugin,
updates: this._updates, updates: this._updates,
@@ -67,6 +70,11 @@ export class DeckyState {
this.notifyUpdate(); this.notifyUpdate();
} }
setFrozenPlugins(frozenPlugins: string[]) {
this._frozenPlugins = frozenPlugins;
this.notifyUpdate();
}
setHiddenPlugins(hiddenPlugins: string[]) { setHiddenPlugins(hiddenPlugins: string[]) {
this._hiddenPlugins = hiddenPlugins; this._hiddenPlugins = hiddenPlugins;
this.notifyUpdate(); this.notifyUpdate();
+8 -5
View File
@@ -30,11 +30,14 @@ const DeckyToaster: FC<DeckyToasterProps> = () => {
// not actually node but TS is shit // not actually node but TS is shit
let interval: NodeJS.Timer | null; let interval: NodeJS.Timer | null;
if (renderedToast) { if (renderedToast) {
interval = setTimeout(() => { interval = setTimeout(
interval = null; () => {
console.log('clear toast', renderedToast.data); interval = null;
removeToast(renderedToast.data); console.log('clear toast', renderedToast.data);
}, (renderedToast.data.duration || 5e3) + 1000); removeToast(renderedToast.data);
},
(renderedToast.data.duration || 5e3) + 1000,
);
console.log('set int', interval); console.log('set int', interval);
} }
return () => { return () => {
+7 -14
View File
@@ -1,12 +1,4 @@
import { import { ButtonItem, Focusable, PanelSection, PanelSectionRow } from 'decky-frontend-lib';
ButtonItem,
Focusable,
PanelSection,
PanelSectionRow,
joinClassNames,
scrollClasses,
staticClasses,
} from 'decky-frontend-lib';
import { VFC, useEffect, useState } from 'react'; import { VFC, useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { FaEyeSlash } from 'react-icons/fa'; import { FaEyeSlash } from 'react-icons/fa';
@@ -36,10 +28,7 @@ const PluginView: VFC = () => {
return ( return (
<Focusable onCancelButton={closeActivePlugin}> <Focusable onCancelButton={closeActivePlugin}>
<TitleView /> <TitleView />
<div <div style={{ height: '100%', paddingTop: '16px' }}>
className={joinClassNames(staticClasses.TabGroupPanel, scrollClasses.ScrollPanel, scrollClasses.ScrollY)}
style={{ height: '100%' }}
>
{(visible || activePlugin.alwaysRender) && activePlugin.content} {(visible || activePlugin.alwaysRender) && activePlugin.content}
</div> </div>
</Focusable> </Focusable>
@@ -48,7 +37,11 @@ const PluginView: VFC = () => {
return ( return (
<> <>
<TitleView /> <TitleView />
<div className={joinClassNames(staticClasses.TabGroupPanel, scrollClasses.ScrollPanel, scrollClasses.ScrollY)}> <div
style={{
paddingTop: '16px',
}}
>
<PanelSection> <PanelSection>
{pluginList {pluginList
.filter((p) => p.content) .filter((p) => p.content)
+2
View File
@@ -10,6 +10,8 @@ const titleStyles: CSSProperties = {
display: 'flex', display: 'flex',
paddingTop: '3px', paddingTop: '3px',
paddingRight: '16px', paddingRight: '16px',
position: 'sticky',
top: '0px',
}; };
const TitleView: VFC = () => { const TitleView: VFC = () => {
@@ -15,8 +15,9 @@ const PluginUninstallModal: FC<PluginUninstallModalProps> = ({ name, title, butt
closeModal={closeModal} closeModal={closeModal}
onOK={async () => { onOK={async () => {
await window.DeckyPluginLoader.callServerMethod('uninstall_plugin', { name }); await window.DeckyPluginLoader.callServerMethod('uninstall_plugin', { name });
// uninstalling a plugin resets the hidden setting for it server-side // uninstalling a plugin resets the frozen and 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 // we invalidate here so if you re-install it, you won't have an out-of-date hidden filter
await window.DeckyPluginLoader.frozenPluginsService.invalidate();
await window.DeckyPluginLoader.hiddenPluginsService.invalidate(); await window.DeckyPluginLoader.hiddenPluginsService.invalidate();
}} }}
strTitle={title} strTitle={title}
+14 -2
View File
@@ -1,7 +1,7 @@
import { SidebarNavigation } from 'decky-frontend-lib'; import { SidebarNavigation } from 'decky-frontend-lib';
import { lazy } from 'react'; import { lazy } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { FaCode, FaPlug } from 'react-icons/fa'; import { FaCode, FaFlask, FaPlug } from 'react-icons/fa';
import { useSetting } from '../../utils/hooks/useSetting'; import { useSetting } from '../../utils/hooks/useSetting';
import DeckyIcon from '../DeckyIcon'; import DeckyIcon from '../DeckyIcon';
@@ -10,6 +10,7 @@ import GeneralSettings from './pages/general';
import PluginList from './pages/plugin_list'; import PluginList from './pages/plugin_list';
const DeveloperSettings = lazy(() => import('./pages/developer')); const DeveloperSettings = lazy(() => import('./pages/developer'));
const TestingMenu = lazy(() => import('./pages/testing'));
export default function SettingsPage() { export default function SettingsPage() {
const [isDeveloper, setIsDeveloper] = useSetting<boolean>('developer.enabled', false); const [isDeveloper, setIsDeveloper] = useSetting<boolean>('developer.enabled', false);
@@ -24,7 +25,7 @@ export default function SettingsPage() {
}, },
{ {
title: t('SettingsIndex.plugins_title'), title: t('SettingsIndex.plugins_title'),
content: <PluginList />, content: <PluginList isDeveloper={isDeveloper} />,
route: '/decky/settings/plugins', route: '/decky/settings/plugins',
icon: <FaPlug />, icon: <FaPlug />,
}, },
@@ -39,6 +40,17 @@ export default function SettingsPage() {
icon: <FaCode />, icon: <FaCode />,
visible: isDeveloper, visible: isDeveloper,
}, },
{
title: t('SettingsIndex.testing_title'),
content: (
<WithSuspense>
<TestingMenu />
</WithSuspense>
),
route: '/decky/settings/testing',
icon: <FaFlask />,
visible: isDeveloper,
},
]; ];
return <SidebarNavigation pages={pages} />; return <SidebarNavigation pages={pages} />;
@@ -8,10 +8,15 @@ import { useSetting } from '../../../../utils/hooks/useSetting';
const logger = new Logger('BranchSelect'); const logger = new Logger('BranchSelect');
enum UpdateBranch { export enum UpdateBranch {
Stable,
Prerelease,
Testing,
}
enum LessUpdateBranch {
Stable, Stable,
Prerelease, Prerelease,
// Testing,
} }
const BranchSelect: FunctionComponent<{}> = () => { const BranchSelect: FunctionComponent<{}> = () => {
@@ -24,11 +29,11 @@ const BranchSelect: FunctionComponent<{}> = () => {
const [selectedBranch, setSelectedBranch] = useSetting<UpdateBranch>('branch', UpdateBranch.Stable); const [selectedBranch, setSelectedBranch] = useSetting<UpdateBranch>('branch', UpdateBranch.Stable);
return ( return (
// Returns numerical values from 0 to 2 (with current branch setup as of 8/28/22) // Returns numerical values from 0 to 2 (with current branch setup as of 6/16/23)
// 0 being stable, 1 being pre-release and 2 being nightly // 0 being stable, 1 being pre-release and 2 being testing (not a branch!)
<Field label={t('BranchSelect.update_channel.label')} childrenContainerWidth={'fixed'}> <Field label={t('BranchSelect.update_channel.label')} childrenContainerWidth={'fixed'}>
<Dropdown <Dropdown
rgOptions={Object.values(UpdateBranch) rgOptions={Object.values(selectedBranch == UpdateBranch.Testing ? UpdateBranch : LessUpdateBranch)
.filter((branch) => typeof branch == 'number') .filter((branch) => typeof branch == 'number')
.map((branch) => ({ .map((branch) => ({
label: tBranches[branch as number], label: tBranches[branch as number],
@@ -135,8 +135,8 @@ export default function UpdaterSettings() {
{checkingForUpdates {checkingForUpdates
? t('Updater.updates.checking') ? t('Updater.updates.checking')
: !versionInfo?.remote || versionInfo?.remote?.tag_name == versionInfo?.current : !versionInfo?.remote || versionInfo?.remote?.tag_name == versionInfo?.current
? t('Updater.updates.check_button') ? t('Updater.updates.check_button')
: t('Updater.updates.install_button')} : t('Updater.updates.install_button')}
</DialogButton> </DialogButton>
) : ( ) : (
<ProgressBarWithInfo <ProgressBarWithInfo
@@ -1,18 +1,34 @@
import { FC } from 'react'; import { FC } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { FaEyeSlash } from 'react-icons/fa'; import { FaEyeSlash, FaLock } from 'react-icons/fa';
interface PluginListLabelProps { interface PluginListLabelProps {
frozen: boolean;
hidden: boolean; hidden: boolean;
name: string; name: string;
version?: string; version?: string;
} }
const PluginListLabel: FC<PluginListLabelProps> = ({ name, hidden, version }) => { const PluginListLabel: FC<PluginListLabelProps> = ({ name, frozen, hidden, version }) => {
const { t } = useTranslation(); const { t } = useTranslation();
return ( return (
<div style={{ display: 'flex', flexDirection: 'column', gap: '6px' }}> <div style={{ display: 'flex', flexDirection: 'column', gap: '6px' }}>
<div>{version ? `${name} - ${version}` : name}</div> <div>
{name}
{version && (
<>
{' - '}
<span style={{ color: frozen ? '#67707b' : 'inherit' }}>
{frozen && (
<>
<FaLock />{' '}
</>
)}
{version}
</span>
</>
)}
</div>
{hidden && ( {hidden && (
<div <div
style={{ style={{
@@ -33,7 +33,16 @@ async function reinstallPlugin(pluginName: string, currentVersion?: string) {
} }
} }
type PluginTableData = PluginData & { name: string; hidden: boolean; onHide(): void; onShow(): void }; type PluginTableData = PluginData & {
name: string;
frozen: boolean;
onFreeze(): void;
onUnfreeze(): void;
hidden: boolean;
onHide(): void;
onShow(): void;
isDeveloper: boolean;
};
function PluginInteractables(props: { entry: ReorderableEntry<PluginTableData> }) { function PluginInteractables(props: { entry: ReorderableEntry<PluginTableData> }) {
const { t } = useTranslation(); const { t } = useTranslation();
@@ -43,7 +52,7 @@ function PluginInteractables(props: { entry: ReorderableEntry<PluginTableData> }
return null; return null;
} }
const { name, update, version, onHide, onShow, hidden } = props.entry.data; const { name, update, version, onHide, onShow, hidden, onFreeze, onUnfreeze, frozen, isDeveloper } = props.entry.data;
const showCtxMenu = (e: MouseEvent | GamepadEvent) => { const showCtxMenu = (e: MouseEvent | GamepadEvent) => {
showContextMenu( showContextMenu(
@@ -84,6 +93,11 @@ function PluginInteractables(props: { entry: ReorderableEntry<PluginTableData> }
) : ( ) : (
<MenuItem onSelected={onHide}>{t('PluginListIndex.hide')}</MenuItem> <MenuItem onSelected={onHide}>{t('PluginListIndex.hide')}</MenuItem>
)} )}
{frozen ? (
<MenuItem onSelected={onUnfreeze}>{t('PluginListIndex.unfreeze')}</MenuItem>
) : (
isDeveloper && <MenuItem onSelected={onFreeze}>{t('PluginListIndex.freeze')}</MenuItem>
)}
</Menu>, </Menu>,
e.currentTarget ?? window, e.currentTarget ?? window,
); );
@@ -138,8 +152,8 @@ type PluginData = {
version?: string; version?: string;
}; };
export default function PluginList() { export default function PluginList({ isDeveloper }: { isDeveloper: boolean }) {
const { plugins, updates, pluginOrder, setPluginOrder, hiddenPlugins } = useDeckyState(); const { plugins, updates, pluginOrder, setPluginOrder, frozenPlugins, hiddenPlugins } = useDeckyState();
const [_, setPluginOrderSetting] = useSetting<string[]>( const [_, setPluginOrderSetting] = useSetting<string[]>(
'pluginOrder', 'pluginOrder',
plugins.map((plugin) => plugin.name), plugins.map((plugin) => plugin.name),
@@ -151,21 +165,27 @@ export default function PluginList() {
}, []); }, []);
const [pluginEntries, setPluginEntries] = useState<ReorderableEntry<PluginTableData>[]>([]); const [pluginEntries, setPluginEntries] = useState<ReorderableEntry<PluginTableData>[]>([]);
const frozenPluginsService = window.DeckyPluginLoader.frozenPluginsService;
const hiddenPluginsService = window.DeckyPluginLoader.hiddenPluginsService; const hiddenPluginsService = window.DeckyPluginLoader.hiddenPluginsService;
useEffect(() => { useEffect(() => {
setPluginEntries( setPluginEntries(
plugins.map(({ name, version }) => { plugins.map(({ name, version }) => {
const frozen = frozenPlugins.includes(name);
const hidden = hiddenPlugins.includes(name); const hidden = hiddenPlugins.includes(name);
return { return {
label: <PluginListLabel name={name} hidden={hidden} version={version} />, label: <PluginListLabel name={name} frozen={frozen} hidden={hidden} version={version} />,
position: pluginOrder.indexOf(name), position: pluginOrder.indexOf(name),
data: { data: {
name, name,
frozen,
hidden, hidden,
isDeveloper,
version, version,
update: updates?.get(name), update: updates?.get(name),
onFreeze: () => frozenPluginsService.update([...frozenPlugins, name]),
onUnfreeze: () => frozenPluginsService.update(frozenPlugins.filter((pluginName) => name !== pluginName)),
onHide: () => hiddenPluginsService.update([...hiddenPlugins, name]), onHide: () => hiddenPluginsService.update([...hiddenPlugins, name]),
onShow: () => hiddenPluginsService.update(hiddenPlugins.filter((pluginName) => name !== pluginName)), onShow: () => hiddenPluginsService.update(hiddenPlugins.filter((pluginName) => name !== pluginName)),
}, },
@@ -0,0 +1,87 @@
import { DialogBody, DialogButton, DialogControlsSection, Focusable, Navigation } from 'decky-frontend-lib';
import { useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { FaDownload, FaInfo } from 'react-icons/fa';
import { callUpdaterMethod } from '../../../../updater';
import { setSetting } from '../../../../utils/settings';
import { UpdateBranch } from '../general/BranchSelect';
interface TestingVersion {
id: number;
name: string;
link: string;
head_sha: string;
}
export default function TestingVersionList() {
const { t } = useTranslation();
const [testingVersions, setTestingVersions] = useState<TestingVersion[]>([]);
useEffect(() => {
(async () => {
setTestingVersions((await callUpdaterMethod('get_testing_versions')).result);
})();
}, []);
if (testingVersions.length === 0) {
return (
<div>
<p>No open PRs found</p>
</div>
);
}
return (
<DialogBody>
<DialogControlsSection>
<ul style={{ listStyleType: 'none', padding: '0' }}>
{testingVersions.map((version) => {
return (
<li style={{ display: 'flex', flexDirection: 'row', alignItems: 'center', paddingBottom: '10px' }}>
<span>
{version.name} <span style={{ opacity: '50%' }}>{'#' + version.id}</span>
</span>
<Focusable style={{ height: '40px', marginLeft: 'auto', display: 'flex' }}>
<DialogButton
style={{ height: '40px', minWidth: '60px', marginRight: '10px' }}
onClick={() => {
callUpdaterMethod('download_testing_version', { pr_id: version.id, sha_id: version.head_sha });
setSetting('branch', UpdateBranch.Testing);
}}
>
<div
style={{
display: 'flex',
minWidth: '150px',
justifyContent: 'space-between',
alignItems: 'center',
}}
>
{t('Testing.download')}
<FaDownload style={{ paddingLeft: '1rem' }} />
</div>
</DialogButton>
<DialogButton
style={{
height: '40px',
width: '40px',
padding: '10px 12px',
minWidth: '40px',
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
}}
onClick={() => Navigation.NavigateToExternalWeb(version.link)}
>
<FaInfo />
</DialogButton>
</Focusable>
</li>
);
})}
</ul>
</DialogControlsSection>
</DialogBody>
);
}
+72 -65
View File
@@ -8,20 +8,19 @@ import {
TextField, TextField,
findModule, findModule,
} from 'decky-frontend-lib'; } from 'decky-frontend-lib';
import { FC, useEffect, useMemo, useState } from 'react'; import { Dispatch, FC, SetStateAction, useEffect, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import logo from '../../../assets/plugin_store.png'; import logo from '../../../assets/plugin_store.png';
import Logger from '../../logger'; import Logger from '../../logger';
import { Store, StorePlugin, getPluginList, getStore } from '../../store'; import { SortDirections, SortOptions, Store, StorePlugin, getPluginList, getStore } from '../../store';
import PluginCard from './PluginCard'; import PluginCard from './PluginCard';
const logger = new Logger('Store'); const logger = new Logger('Store');
const StorePage: FC<{}> = () => { const StorePage: FC<{}> = () => {
const [currentTabRoute, setCurrentTabRoute] = useState<string>('browse'); const [currentTabRoute, setCurrentTabRoute] = useState<string>('browse');
const [data, setData] = useState<StorePlugin[] | null>(null); const [pluginCount, setPluginCount] = useState<number | null>(null);
const [isTesting, setIsTesting] = useState<boolean>(false);
const { TabCount } = findModule((m) => { const { TabCount } = findModule((m) => {
if (m?.TabCount && m?.TabTitle) return true; if (m?.TabCount && m?.TabTitle) return true;
return false; return false;
@@ -29,17 +28,6 @@ const StorePage: FC<{}> = () => {
const { t } = useTranslation(); const { t } = useTranslation();
useEffect(() => {
(async () => {
const res = await getPluginList();
logger.log('got data!', res);
setData(res);
const storeRes = await getStore();
logger.log(`store is ${storeRes}, isTesting is ${storeRes === Store.Testing}`);
setIsTesting(storeRes === Store.Testing);
})();
}, []);
return ( return (
<> <>
<div <div
@@ -49,52 +37,71 @@ const StorePage: FC<{}> = () => {
background: '#0005', background: '#0005',
}} }}
> >
{!data ? ( <Tabs
<div style={{ height: '100%' }}> activeTab={currentTabRoute}
<SteamSpinner /> onShowTab={(tabId: string) => {
</div> setCurrentTabRoute(tabId);
) : ( }}
<Tabs tabs={[
activeTab={currentTabRoute} {
onShowTab={(tabId: string) => { title: t('Store.store_tabs.title'),
setCurrentTabRoute(tabId); content: <BrowseTab setPluginCount={setPluginCount} />,
}} id: 'browse',
tabs={[ renderTabAddon: () => <span className={TabCount}>{pluginCount}</span>,
{ },
title: t('Store.store_tabs.title'), {
content: <BrowseTab children={{ data: data, isTesting: isTesting }} />, title: t('Store.store_tabs.about'),
id: 'browse', content: <AboutTab />,
renderTabAddon: () => <span className={TabCount}>{data.length}</span>, id: 'about',
}, },
{ ]}
title: t('Store.store_tabs.about'), />
content: <AboutTab />,
id: 'about',
},
]}
/>
)}
</div> </div>
</> </>
); );
}; };
const BrowseTab: FC<{ children: { data: StorePlugin[]; isTesting: boolean } }> = (data) => { const BrowseTab: FC<{ setPluginCount: Dispatch<SetStateAction<number | null>> }> = ({ setPluginCount }) => {
const { t } = useTranslation(); const { t } = useTranslation();
const sortOptions = useMemo( const dropdownSortOptions = useMemo(
(): DropdownOption[] => [ (): DropdownOption[] => [
{ data: 1, label: t('Store.store_tabs.alph_desc') }, // ascending and descending order are the wrong way around for the alphabetical sort
{ data: 2, label: t('Store.store_tabs.alph_asce') }, // this is because it was initially done incorrectly for i18n and 'fixing' it would
// make all the translations incorrect
{ data: [SortOptions.name, SortDirections.ascending], label: t('Store.store_tabs.alph_desc') },
{ data: [SortOptions.name, SortDirections.descending], label: t('Store.store_tabs.alph_asce') },
{ data: [SortOptions.date, SortDirections.ascending], label: t('Store.store_tabs.date_asce') },
{ data: [SortOptions.date, SortDirections.descending], label: t('Store.store_tabs.date_desc') },
{ data: [SortOptions.downloads, SortDirections.descending], label: t('Store.store_tabs.downloads_desc') },
{ data: [SortOptions.downloads, SortDirections.ascending], label: t('Store.store_tabs.downloads_asce') },
], ],
[], [],
); );
// const filterOptions = useMemo((): DropdownOption[] => [{ data: 1, label: 'All' }], []); // const filterOptions = useMemo((): DropdownOption[] => [{ data: 1, label: 'All' }], []);
const [selectedSort, setSort] = useState<[SortOptions, SortDirections]>(dropdownSortOptions[0].data);
const [selectedSort, setSort] = useState<number>(sortOptions[0].data);
// const [selectedFilter, setFilter] = useState<number>(filterOptions[0].data); // const [selectedFilter, setFilter] = useState<number>(filterOptions[0].data);
const [searchFieldValue, setSearchValue] = useState<string>(''); const [searchFieldValue, setSearchValue] = useState<string>('');
const [pluginList, setPluginList] = useState<StorePlugin[] | null>(null);
const [isTesting, setIsTesting] = useState<boolean>(false);
useEffect(() => {
(async () => {
const res = await getPluginList(selectedSort[0], selectedSort[1]);
logger.log('got data!', res);
setPluginList(res);
setPluginCount(res.length);
})();
}, [selectedSort]);
useEffect(() => {
(async () => {
const storeRes = await getStore();
logger.log(`store is ${storeRes}, isTesting is ${storeRes === Store.Testing}`);
setIsTesting(storeRes === Store.Testing);
})();
}, []);
return ( return (
<> <>
@@ -117,7 +124,7 @@ const BrowseTab: FC<{ children: { data: StorePlugin[]; isTesting: boolean } }> =
<span className="DialogLabel">{t("Store.store_sort.label")}</span> <span className="DialogLabel">{t("Store.store_sort.label")}</span>
<Dropdown <Dropdown
menuLabel={t("Store.store_sort.label") as string} menuLabel={t("Store.store_sort.label") as string}
rgOptions={sortOptions} rgOptions={dropdownSortOptions}
strDefaultLabel={t("Store.store_sort.label_def") as string} strDefaultLabel={t("Store.store_sort.label_def") as string}
selectedOption={selectedSort} selectedOption={selectedSort}
onChange={(e) => setSort(e.data)} onChange={(e) => setSort(e.data)}
@@ -163,7 +170,7 @@ const BrowseTab: FC<{ children: { data: StorePlugin[]; isTesting: boolean } }> =
<span className="DialogLabel">{t('Store.store_sort.label')}</span> <span className="DialogLabel">{t('Store.store_sort.label')}</span>
<Dropdown <Dropdown
menuLabel={t('Store.store_sort.label') as string} menuLabel={t('Store.store_sort.label') as string}
rgOptions={sortOptions} rgOptions={dropdownSortOptions}
strDefaultLabel={t('Store.store_sort.label_def') as string} strDefaultLabel={t('Store.store_sort.label_def') as string}
selectedOption={selectedSort} selectedOption={selectedSort}
onChange={(e) => setSort(e.data)} onChange={(e) => setSort(e.data)}
@@ -182,7 +189,7 @@ const BrowseTab: FC<{ children: { data: StorePlugin[]; isTesting: boolean } }> =
</div> </div>
</Focusable> </Focusable>
</div> </div>
{data.children.isTesting && ( {isTesting && (
<div <div
style={{ style={{
alignItems: 'center', alignItems: 'center',
@@ -213,22 +220,22 @@ const BrowseTab: FC<{ children: { data: StorePlugin[]; isTesting: boolean } }> =
</div> </div>
)} )}
<div> <div>
{data.children.data {!pluginList ? (
.filter((plugin: StorePlugin) => { <div style={{ height: '100%' }}>
return ( <SteamSpinner />
plugin.name.toLowerCase().includes(searchFieldValue.toLowerCase()) || </div>
plugin.description.toLowerCase().includes(searchFieldValue.toLowerCase()) || ) : (
plugin.author.toLowerCase().includes(searchFieldValue.toLowerCase()) || pluginList
plugin.tags.some((tag: string) => tag.toLowerCase().includes(searchFieldValue.toLowerCase())) .filter((plugin: StorePlugin) => {
); return (
}) plugin.name.toLowerCase().includes(searchFieldValue.toLowerCase()) ||
.sort((a, b) => { plugin.description.toLowerCase().includes(searchFieldValue.toLowerCase()) ||
if (selectedSort % 2 === 1) return a.name.localeCompare(b.name); plugin.author.toLowerCase().includes(searchFieldValue.toLowerCase()) ||
else return b.name.localeCompare(a.name); plugin.tags.some((tag: string) => tag.toLowerCase().includes(searchFieldValue.toLowerCase()))
}) );
.map((plugin: StorePlugin) => ( })
<PluginCard plugin={plugin} /> .map((plugin: StorePlugin) => <PluginCard plugin={plugin} />)
))} )}
</div> </div>
</> </>
); );
+17 -19
View File
@@ -1,4 +1,4 @@
import { findModuleChild, sleep } from 'decky-frontend-lib'; import { sleep } from 'decky-frontend-lib';
import { FaReact } from 'react-icons/fa'; import { FaReact } from 'react-icons/fa';
import Logger from './logger'; import Logger from './logger';
@@ -9,32 +9,30 @@ const logger = new Logger('DeveloperMode');
let removeSettingsObserver: () => void = () => {}; let removeSettingsObserver: () => void = () => {};
export async function setShowValveInternal(show: boolean) { declare global {
let settingsMod: any; interface Window {
while (!settingsMod) { settingsStore: any;
settingsMod = findModuleChild((m) => {
if (typeof m !== 'object') return undefined;
for (let prop in m) {
if (typeof m[prop]?.settings?.bIsValveEmail !== 'undefined') return m[prop];
}
});
if (!settingsMod) {
logger.debug('[ValveInternal] waiting for settingsMod');
await sleep(1000);
}
} }
}
export async function setShowValveInternal(show: boolean) {
if (show) { if (show) {
removeSettingsObserver = settingsMod[ const mobx =
Object.getOwnPropertySymbols(settingsMod).find((x) => x.toString() == 'Symbol(mobx administration)') as any window.settingsStore[
].observe((e: any) => { Object.getOwnPropertySymbols(window.settingsStore).find(
(x) => x.toString() == 'Symbol(mobx administration)',
) as any
];
removeSettingsObserver = (mobx.observe_ || mobx.observe).call(mobx, (e: any) => {
e.newValue.bIsValveEmail = true; e.newValue.bIsValveEmail = true;
}); });
settingsMod.m_Settings.bIsValveEmail = true;
window.settingsStore.m_Settings.bIsValveEmail = true;
logger.log('Enabled Valve Internal menu'); logger.log('Enabled Valve Internal menu');
} else { } else {
removeSettingsObserver(); removeSettingsObserver();
settingsMod.m_Settings.bIsValveEmail = false; window.settingsStore.m_Settings.bIsValveEmail = false;
logger.log('Disabled Valve Internal menu'); logger.log('Disabled Valve Internal menu');
} }
} }
+49
View File
@@ -0,0 +1,49 @@
import { DeckyState } from './components/DeckyState';
import { PluginUpdateMapping } from './store';
import { getSetting, setSetting } from './utils/settings';
/**
* A Service class for managing the state and actions related to the frozen plugins feature.
*
* It's mostly responsible for sending setting updates to the server and keeping the local state in sync.
*/
export class FrozenPluginService {
constructor(private deckyState: DeckyState) {}
init() {
getSetting<string[]>('frozenPlugins', []).then((frozenPlugins) => {
this.deckyState.setFrozenPlugins(frozenPlugins);
});
}
/**
* Sends the new frozen plugins list to the server and persists it locally in the decky state
*
* @param frozenPlugins The new list of frozen plugins
*/
async update(frozenPlugins: string[]) {
await setSetting('frozenPlugins', frozenPlugins);
this.deckyState.setFrozenPlugins(frozenPlugins);
// Remove pending updates for frozen plugins
const updates = this.deckyState.publicState().updates;
if (updates) {
const filteredUpdates = new Map() as PluginUpdateMapping;
updates.forEach((v, k) => {
if (!frozenPlugins.includes(k)) {
filteredUpdates.set(k, v);
}
});
this.deckyState.setUpdates(filteredUpdates);
}
}
/**
* Refreshes the state of frozen plugins in the local state
*/
async invalidate() {
this.deckyState.setFrozenPlugins(await getSetting('frozenPlugins', []));
}
}
+11
View File
@@ -1,5 +1,16 @@
// Sets up DFL, then loads start.ts which starts up the loader // Sets up DFL, then loads start.ts which starts up the loader
interface Window {
SP_REACTDOM: any;
}
(async () => { (async () => {
if (!window.SP_REACT) {
console.debug('Setting up React globals...');
// deliberate partial import
const DFLWebpack = await import('decky-frontend-lib/dist/webpack');
// TODO move these finds to dfl in v4
window.SP_REACT = DFLWebpack.findModule((m) => m.Component && m.PureComponent && m.useLayoutEffect);
window.SP_REACTDOM = DFLWebpack.findModule((m) => m.createPortal && m.createRoot);
}
console.debug('Setting up decky-frontend-lib...'); console.debug('Setting up decky-frontend-lib...');
window.DFL = await import('decky-frontend-lib'); window.DFL = await import('decky-frontend-lib');
await import('./start'); await import('./start');
+6 -1
View File
@@ -22,6 +22,7 @@ import PluginUninstallModal from './components/modals/PluginUninstallModal';
import NotificationBadge from './components/NotificationBadge'; import NotificationBadge from './components/NotificationBadge';
import PluginView from './components/PluginView'; import PluginView from './components/PluginView';
import WithSuspense from './components/WithSuspense'; import WithSuspense from './components/WithSuspense';
import { FrozenPluginService } from './frozen-plugins-service';
import { HiddenPluginsService } from './hidden-plugins-service'; import { HiddenPluginsService } from './hidden-plugins-service';
import Logger from './logger'; import Logger from './logger';
import { NotificationService } from './notification-service'; import { NotificationService } from './notification-service';
@@ -49,6 +50,7 @@ class PluginLoader extends Logger {
public toaster: Toaster = new Toaster(); public toaster: Toaster = new Toaster();
private deckyState: DeckyState = new DeckyState(); private deckyState: DeckyState = new DeckyState();
public frozenPluginsService = new FrozenPluginService(this.deckyState);
public hiddenPluginsService = new HiddenPluginsService(this.deckyState); public hiddenPluginsService = new HiddenPluginsService(this.deckyState);
public notificationService = new NotificationService(this.deckyState); public notificationService = new NotificationService(this.deckyState);
@@ -144,7 +146,9 @@ class PluginLoader extends Logger {
} }
public async checkPluginUpdates() { public async checkPluginUpdates() {
const updates = await checkForUpdates(this.plugins); const frozenPlugins = this.deckyState.publicState().frozenPlugins;
const updates = await checkForUpdates(this.plugins.filter((p) => !frozenPlugins.includes(p.name)));
this.deckyState.setUpdates(updates); this.deckyState.setUpdates(updates);
return updates; return updates;
} }
@@ -224,6 +228,7 @@ class PluginLoader extends Logger {
this.deckyState.setPluginOrder(pluginOrder); this.deckyState.setPluginOrder(pluginOrder);
}); });
this.frozenPluginsService.init();
this.hiddenPluginsService.init(); this.hiddenPluginsService.init();
this.notificationService.init(); this.notificationService.init();
} }
+2 -2
View File
@@ -1,4 +1,4 @@
import reloadFix from './reload'; // import reloadFix from './reload';
import restartFix from './restart'; import restartFix from './restart';
let fixes: Function[] = []; let fixes: Function[] = [];
@@ -7,6 +7,6 @@ export function deinitSteamFixes() {
} }
export async function initSteamFixes() { export async function initSteamFixes() {
fixes.push(await reloadFix()); // fixes.push(await reloadFix());
fixes.push(await restartFix()); fixes.push(await restartFix());
} }
+22 -2
View File
@@ -7,6 +7,17 @@ export enum Store {
Custom, Custom,
} }
export enum SortOptions {
name = 'name',
date = 'date',
downloads = 'downloads',
}
export enum SortDirections {
ascending = 'asc',
descending = 'desc',
}
export interface StorePluginVersion { export interface StorePluginVersion {
name: string; name: string;
hash: string; hash: string;
@@ -36,11 +47,20 @@ export async function getStore(): Promise<Store> {
return await getSetting<Store>('store', Store.Default); return await getSetting<Store>('store', Store.Default);
} }
export async function getPluginList(): Promise<StorePlugin[]> { export async function getPluginList(
sort_by: SortOptions | null = null,
sort_direction: SortDirections | null = null,
): Promise<StorePlugin[]> {
let version = await window.DeckyPluginLoader.updateVersion(); let version = await window.DeckyPluginLoader.updateVersion();
let store = await getSetting<Store | null>('store', null); let store = await getSetting<Store | null>('store', null);
let customURL = await getSetting<string>('store-url', 'https://plugins.deckbrew.xyz/plugins'); let customURL = await getSetting<string>('store-url', 'https://plugins.deckbrew.xyz/plugins');
let query: URLSearchParams | string = new URLSearchParams();
sort_by && query.set('sort_by', sort_by);
sort_direction && query.set('sort_direction', sort_direction);
query = '?' + String(query);
let storeURL; let storeURL;
if (store === null) { if (store === null) {
console.log('Could not get store, using Default.'); console.log('Could not get store, using Default.');
@@ -62,7 +82,7 @@ export async function getPluginList(): Promise<StorePlugin[]> {
storeURL = 'https://plugins.deckbrew.xyz/plugins'; storeURL = 'https://plugins.deckbrew.xyz/plugins';
break; break;
} }
return fetch(storeURL, { return fetch(storeURL + query, {
method: 'GET', method: 'GET',
headers: { headers: {
'X-Decky-Version': version.current, 'X-Decky-Version': version.current,
+2 -2
View File
@@ -1,5 +1,5 @@
// TabsHook for versions before the Desktop merge // TabsHook for versions before the Desktop merge
import { Patch, afterPatch, sleep } from 'decky-frontend-lib'; import { Patch, afterPatch, getReactRoot, sleep } from 'decky-frontend-lib';
import { memo } from 'react'; import { memo } from 'react';
import NewTabsHook from './tabs-hook'; import NewTabsHook from './tabs-hook';
@@ -35,7 +35,7 @@ class TabsHook extends NewTabsHook {
init() { init() {
const self = this; const self = this;
const tree = (document.getElementById('root') as any)._reactRootContainer._internalRoot.current; const tree = getReactRoot(document.getElementById('root') as any);
let scrollRoot: any; let scrollRoot: any;
async function findScrollRoot(currentNode: any, iters: number): Promise<any> { async function findScrollRoot(currentNode: any, iters: number): Promise<any> {
if (iters >= 30) { if (iters >= 30) {
+6 -5
View File
@@ -1,5 +1,5 @@
// TabsHook for versions after the Desktop merge // TabsHook for versions after the Desktop merge
import { Patch, QuickAccessTab, afterPatch, findInReactTree, sleep } from 'decky-frontend-lib'; import { Patch, QuickAccessTab, afterPatch, findInReactTree, getReactRoot, sleep } from 'decky-frontend-lib';
import { QuickAccessVisibleStateProvider } from './components/QuickAccessVisibleState'; import { QuickAccessVisibleStateProvider } from './components/QuickAccessVisibleState';
import Logger from './logger'; import Logger from './logger';
@@ -32,15 +32,16 @@ class TabsHook extends Logger {
} }
init() { init() {
const tree = (document.getElementById('root') as any)._reactRootContainer._internalRoot.current; const tree = getReactRoot(document.getElementById('root') as any);
let qAMRoot: any; let qAMRoot: any;
const findQAMRoot = (currentNode: any, iters: number): any => { const findQAMRoot = (currentNode: any, iters: number): any => {
if (iters >= 65) { if (iters >= 80) {
// currently 45 // currently 67
return null; return null;
} }
if ( if (
typeof currentNode?.memoizedProps?.visible == 'boolean' && (typeof currentNode?.memoizedProps?.visible == 'boolean' ||
typeof currentNode?.memoizedProps?.active == 'boolean') &&
currentNode?.type?.toString()?.includes('QuickAccessMenuBrowserView') currentNode?.type?.toString()?.includes('QuickAccessMenuBrowserView')
) { ) {
this.log(`QAM root was found in ${iters} recursion cycles`); this.log(`QAM root was found in ${iters} recursion cycles`);
+26 -8
View File
@@ -1,4 +1,14 @@
import { Module, Patch, ToastData, afterPatch, findInReactTree, findModuleChild, sleep } from 'decky-frontend-lib'; import {
Module,
Patch,
ToastData,
afterPatch,
findClass,
findInReactTree,
findModuleChild,
getReactRoot,
sleep,
} from 'decky-frontend-lib';
import { ReactNode } from 'react'; import { ReactNode } from 'react';
import Toast from './components/Toast'; import Toast from './components/Toast';
@@ -38,16 +48,21 @@ class Toaster extends Logger {
// </DeckyToasterStateContextProvider> // </DeckyToasterStateContextProvider>
// )); // ));
let instance: any; let instance: any;
const tree = (document.getElementById('root') as any)._reactRootContainer._internalRoot.current; 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 => { const findToasterRoot = (currentNode: any, iters: number): any => {
if (iters >= 65) { if (iters >= 80) {
// currently 65 // currently 66
return null; return null;
} }
if ( if (
currentNode?.memoizedProps?.className?.startsWith?.('gamepadtoasts_GamepadToastPlaceholder') || currentNode?.memoizedProps?.className?.startsWith?.(toasterClass1) ||
currentNode?.memoizedProps?.className?.startsWith?.('toastmanager_ToastPlaceholder') || currentNode?.memoizedProps?.className?.startsWith?.(toasterClass2) ||
currentNode?.memoizedProps?.className?.startsWith?.('toastmanager_ToastPopup') currentNode?.memoizedProps?.className?.startsWith?.(toasterClass3) ||
currentNode?.memoizedProps?.className?.startsWith?.(toasterClass4)
) { ) {
this.log(`Toaster root was found in ${iters} recursion cycles`); this.log(`Toaster root was found in ${iters} recursion cycles`);
return currentNode; return currentNode;
@@ -71,7 +86,10 @@ class Toaster extends Logger {
instance = findToasterRoot(tree, 0); instance = findToasterRoot(tree, 0);
} }
this.node = instance.return; this.node = instance.return;
this.rNode = this.node.return; this.rNode = findInReactTree(
this.node.return.return,
(node) => node?.stateNode && node.type?.InstallErrorReportingStore,
);
let toast: any; let toast: any;
let renderedToast: ReactNode = null; let renderedToast: ReactNode = null;
let innerPatched: any; let innerPatched: any;