Compare commits

...

301 Commits

Author SHA1 Message Date
Lukas Senionis b47ad69557 Revert "fix dev on newer python (steamos 3.7)" (#758)
Builder Win / Build PluginLoader for Win (push) Has been cancelled
Builder / Build PluginLoader (push) Has been cancelled
Push Updated Plugin Stub to Template / copy-stub (push) Has been cancelled
Lint / Run linters (push) Has been cancelled
Type Check / Run type checkers (push) Has been cancelled
This reverts commit efac7bc397.
2025-03-26 02:11:36 +00:00
AAGaming efac7bc397 fix dev on newer python (steamos 3.7)
Builder Win / Build PluginLoader for Win (push) Has been cancelled
Builder / Build PluginLoader (push) Has been cancelled
Push Updated Plugin Stub to Template / copy-stub (push) Has been cancelled
Lint / Run linters (push) Has been cancelled
Type Check / Run type checkers (push) Has been cancelled
2025-03-19 15:23:29 -04:00
AAGaming ce2f98aa04 fix toasts on beta (valve forgot how to count) 2025-03-19 15:23:19 -04:00
AAGaming ede8a18e9b pin changed-files to a safe SHA
Builder Win / Build PluginLoader for Win (push) Has been cancelled
Builder / Build PluginLoader (push) Has been cancelled
Push Updated Plugin Stub to Template / copy-stub (push) Has been cancelled
Lint / Run linters (push) Has been cancelled
Type Check / Run type checkers (push) Has been cancelled
2025-03-17 07:54:16 -04:00
WerWolvTranslationBot 8db3711cab Translated using Weblate (Czech) (#753)
Builder Win / Build PluginLoader for Win (push) Has been cancelled
Builder / Build PluginLoader (push) Has been cancelled
Push Updated Plugin Stub to Template / copy-stub (push) Has been cancelled
Lint / Run linters (push) Has been cancelled
Type Check / Run type checkers (push) Has been cancelled
Currently translated at 100.0% (176 of 176 strings)

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

Co-authored-by: Meiton <michal.salati@gmail.com>
2025-02-26 10:27:28 +01:00
WerWolvTranslationBot ffa3226077 Translated using Weblate (Swedish) (#752)
Builder Win / Build PluginLoader for Win (push) Has been cancelled
Builder / Build PluginLoader (push) Has been cancelled
Push Updated Plugin Stub to Template / copy-stub (push) Has been cancelled
Lint / Run linters (push) Has been cancelled
Type Check / Run type checkers (push) Has been cancelled
Currently translated at 100.0% (176 of 176 strings)

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

Co-authored-by: Daniel Nylander <po@danielnylander.se>
2025-02-24 09:12:44 +01:00
WerWolvTranslationBot dc0e1cfdce Translations update from Weblate (#751)
Builder Win / Build PluginLoader for Win (push) Has been cancelled
Builder / Build PluginLoader (push) Has been cancelled
Push Updated Plugin Stub to Template / copy-stub (push) Has been cancelled
Lint / Run linters (push) Has been cancelled
Type Check / Run type checkers (push) Has been cancelled
* Translated using Weblate (Italian)

Currently translated at 100.0% (176 of 176 strings)

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

* Translated using Weblate (English)

Currently translated at 100.0% (176 of 176 strings)

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

* Translated using Weblate (Italian)

Currently translated at 100.0% (176 of 176 strings)

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

---------

Co-authored-by: Marco Rodolfi <marco.rodolfi@tuta.io>
2025-02-20 16:59:19 +01:00
Marco Rodolfi b3483897e0 Rename ka--DZ.json to ka-DZ.json
Builder Win / Build PluginLoader for Win (push) Waiting to run
Builder / Build PluginLoader (push) Waiting to run
Push Updated Plugin Stub to Template / copy-stub (push) Waiting to run
Lint / Run linters (push) Waiting to run
Type Check / Run type checkers (push) Waiting to run
Wrong filename in the locales folder
2025-02-19 11:10:25 +00:00
TrainDoctor 543ee3d19e Fixed up remote binary logging, download process and old incorrect error
Builder Win / Build PluginLoader for Win (push) Has been cancelled
Builder / Build PluginLoader (push) Has been cancelled
Push Updated Plugin Stub to Template / copy-stub (push) Has been cancelled
Lint / Run linters (push) Has been cancelled
Type Check / Run type checkers (push) Has been cancelled
2025-02-08 17:30:09 -08:00
WerWolvTranslationBot 654957cb0c Translations update from Weblate (#737)
* Added translation using Weblate (Georgian)

* 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 (Polish)

Currently translated at 100.0% (175 of 175 strings)

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

* Translated using Weblate (Portuguese (Portugal))

Currently translated at 100.0% (175 of 175 strings)

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

* Translated using Weblate (Portuguese (Portugal))

Currently translated at 100.0% (175 of 175 strings)

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

* Translated using Weblate (Portuguese (Portugal))

Currently translated at 100.0% (175 of 175 strings)

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

---------

Co-authored-by: NorwayFun <temuri.doghonadze@gmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: Gadzio742 <daniel.123737@gmail.com>
Co-authored-by: hyperscrawl <design.pedrofirmino@gmail.com>
Co-authored-by: Fábio Oliveira <fabio.an.oliveira@gmail.com>
Co-authored-by: hyperscrawl <hyperscrawl@users.noreply.weblate.werwolv.net>
2025-02-08 14:41:19 -08:00
TrainDoctor 11e5236fa3 Removed unused import and improved log 2025-02-08 14:33:20 -08:00
TrainDoctor c1dd1c7296 White-space formatting is one of the most incomprehensible decisions... 2025-02-08 14:23:07 -08:00
xXJSONDeruloXx 310dd700ac fix: adtl remote binary error handling
(cherry picked from commit 860b1ac835)
2025-02-08 12:32:27 -08:00
Adrian Covaci 0c727d64d2 chore(flake): Add setuptools in order to build on a minimal Nix system (#741)
Builder Win / Build PluginLoader for Win (push) Has been cancelled
Builder / Build PluginLoader (push) Has been cancelled
Push Updated Plugin Stub to Template / copy-stub (push) Has been cancelled
Lint / Run linters (push) Has been cancelled
Type Check / Run type checkers (push) Has been cancelled
2025-01-17 21:52:50 +00:00
AAGaming 054517595d fix(ErrorBoundary): use same conditions as Valve boundary for rendering
Builder Win / Build PluginLoader for Win (push) Has been cancelled
Builder / Build PluginLoader (push) Has been cancelled
Push Updated Plugin Stub to Template / copy-stub (push) Has been cancelled
Lint / Run linters (push) Has been cancelled
Type Check / Run type checkers (push) Has been cancelled
this fixes the screenshot deletion error
2025-01-03 22:53:37 -05:00
dependabot[bot] ceb10fd7cf Bump jinja2 from 3.1.4 to 3.1.5 in /backend (#738)
Builder Win / Build PluginLoader for Win (push) Has been cancelled
Builder / Build PluginLoader (push) Has been cancelled
Push Updated Plugin Stub to Template / copy-stub (push) Has been cancelled
Lint / Run linters (push) Has been cancelled
Type Check / Run type checkers (push) Has been cancelled
Bumps [jinja2](https://github.com/pallets/jinja) from 3.1.4 to 3.1.5.
- [Release notes](https://github.com/pallets/jinja/releases)
- [Changelog](https://github.com/pallets/jinja/blob/main/CHANGES.rst)
- [Commits](https://github.com/pallets/jinja/compare/3.1.4...3.1.5)

---
updated-dependencies:
- dependency-name: jinja2
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-01-02 12:12:43 -08:00
dependabot[bot] 8e50886c48 Bump cross-spawn from 7.0.3 to 7.0.6 in /frontend (#727)
Bumps [cross-spawn](https://github.com/moxystudio/node-cross-spawn) from 7.0.3 to 7.0.6.
- [Changelog](https://github.com/moxystudio/node-cross-spawn/blob/master/CHANGELOG.md)
- [Commits](https://github.com/moxystudio/node-cross-spawn/compare/v7.0.3...v7.0.6)

---
updated-dependencies:
- dependency-name: cross-spawn
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-01-02 12:06:40 -08:00
dependabot[bot] 76677fa877 Bump aiohttp from 3.10.10 to 3.10.11 in /backend (#728)
Bumps [aiohttp](https://github.com/aio-libs/aiohttp) from 3.10.10 to 3.10.11.
- [Release notes](https://github.com/aio-libs/aiohttp/releases)
- [Changelog](https://github.com/aio-libs/aiohttp/blob/master/CHANGES.rst)
- [Commits](https://github.com/aio-libs/aiohttp/compare/v3.10.10...v3.10.11)

---
updated-dependencies:
- dependency-name: aiohttp
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-01-02 11:52:44 -08:00
Álvaro Cuesta f6144f9634 feat: sync with local plugin status in store (#733)
* fix: useDeckyState proper type and safety

* refactor: plugin list

Avoids unneeded re-renders. See https://react.dev/learn/you-might-not-need-an-effect#caching-expensive-calculations

* feat: sync with local plugin status in store

Adds some QoL changes to the plugin store browser:

- Add ✓ icon to currently installed plugin version in version selector
- Change install button label depending on the install type that the
  button would trigger
- Adds icon to install button for clarity

The goal is to make it clear to the user what the current state of the
installed plugin is, and what would be the impact of installing the
selected version.

Resolves #360

* lint: prettier

* fix: add missing translations

* refactor: safer translation strings on install

Prefer using `t(...)` instead of `TranslationHelper` since it ensures
that the translation keys are not missing in the locale files when
running the `extractext` task.

By adding comments with `t(...)` calls, `i18next-parser` will generate
the strings as if they were present as literals in the code (see
https://github.com/i18next/i18next-parser#caveats).

This does _not_ suppress the warnings (since `i18next-parser` does not
have access to TS types, so it cannot infer template literals) but it at
least makes it less likely that a translation will be missed by mistake,
have typos, etc.
2025-01-02 11:38:40 -08:00
AAGaming 79bb62a3c4 update library name in readme
Builder Win / Build PluginLoader for Win (push) Has been cancelled
Builder / Build PluginLoader (push) Has been cancelled
Push Updated Plugin Stub to Template / copy-stub (push) Has been cancelled
Lint / Run linters (push) Has been cancelled
Type Check / Run type checkers (push) Has been cancelled
2024-12-18 13:04:18 -05:00
Sims 83f2c94712 Fix id keyerror on plugin uninstall (#725)
Builder Win / Build PluginLoader for Win (push) Has been cancelled
Builder / Build PluginLoader (push) Has been cancelled
Push Updated Plugin Stub to Template / copy-stub (push) Has been cancelled
Lint / Run linters (push) Has been cancelled
Type Check / Run type checkers (push) Has been cancelled
* Fix id keyerror on plugin uninstall

* This is python

* too bad im changing it anyway

---------

Co-authored-by: AAGaming <aagaming@riseup.net>
2024-12-13 22:38:33 -05:00
eXhumer 25036b065f chore: remove dead / double code (#726)
* noticed by @KP2048 in https://discord.com/channels/960281551428522045/960284311444131840/1317031315551420447

Signed-off-by: eXhumer <exhumer@exhumer.cc>
2024-12-13 22:32:50 -05:00
AAGaming 6bf21bf2ad Merge branch 'main' of github.com:SteamDeckHomebrew/decky-loader 2024-12-13 22:31:09 -05:00
AAGaming 4b47a8abeb fix(deps): update decky/ui to fix beta plugin loading issues 2024-12-13 22:31:06 -05:00
dependabot[bot] 43f22329e1 Bump micromatch from 4.0.7 to 4.0.8 in /frontend (#714)
Builder Win / Build PluginLoader for Win (push) Has been cancelled
Builder / Build PluginLoader (push) Has been cancelled
Push Updated Plugin Stub to Template / copy-stub (push) Has been cancelled
Lint / Run linters (push) Has been cancelled
Type Check / Run type checkers (push) Has been cancelled
Bumps [micromatch](https://github.com/micromatch/micromatch) from 4.0.7 to 4.0.8.
- [Release notes](https://github.com/micromatch/micromatch/releases)
- [Changelog](https://github.com/micromatch/micromatch/blob/master/CHANGELOG.md)
- [Commits](https://github.com/micromatch/micromatch/compare/4.0.7...4.0.8)

---
updated-dependencies:
- dependency-name: micromatch
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-13 13:42:50 -07:00
dependabot[bot] 19e5d84928 Bump rollup from 4.18.0 to 4.22.4 in /frontend (#711)
Bumps [rollup](https://github.com/rollup/rollup) from 4.18.0 to 4.22.4.
- [Release notes](https://github.com/rollup/rollup/releases)
- [Changelog](https://github.com/rollup/rollup/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rollup/rollup/compare/v4.18.0...v4.22.4)

---
updated-dependencies:
- dependency-name: rollup
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-13 13:42:24 -07:00
Sims 86dea2d7c6 Update aiohttp (#716) 2024-10-13 13:34:38 -07:00
Party Wumpus 4c95484ccb move check for if plugin was installed to the start of the function (#715) 2024-10-13 12:22:02 -07:00
Mitja Skuver 8d2b252e6d Fixed plugin manual zip installation getting stuck indefinitely (#706) 2024-10-13 12:20:08 -07:00
AAGaming dbd7488d8f lint
Builder Win / Build PluginLoader for Win (push) Has been cancelled
Builder / Build PluginLoader (push) Has been cancelled
Push Updated Plugin Stub to Template / copy-stub (push) Has been cancelled
Lint / Run linters (push) Has been cancelled
Type Check / Run type checkers (push) Has been cancelled
2024-10-11 15:05:07 -04:00
AAGaming cff3ca504d bump @decky/ui to fix issues on beta 2024-10-11 15:00:54 -04:00
AAGaming 456ccf479a fix dropdownmultiselect on beta 2024-10-11 14:58:37 -04:00
AAGaming 4f05a001fb fix another toString 2024-10-11 14:58:05 -04:00
AAGaming 43aa0497f4 prevent future issues where toString may not be a function (what) 2024-10-11 14:47:20 -04:00
AAGaming 1781c19c11 Fix broken checkboxes on Beta Steam client (#710)
Builder Win / Build PluginLoader for Win (push) Has been cancelled
Builder / Build PluginLoader (push) Has been cancelled
Push Updated Plugin Stub to Template / copy-stub (push) Has been cancelled
Lint / Run linters (push) Has been cancelled
Type Check / Run type checkers (push) Has been cancelled
2024-10-04 13:26:00 -04:00
Lukas Senionis 1ef3cb8307 fix(http_request): remove conflicting CORS headers (#708) 2024-10-04 13:15:58 -04:00
WerWolvTranslationBot 5bc4dc684d Translations update from Weblate (#707)
Builder Win / Build PluginLoader for Win (push) Has been cancelled
Builder / Build PluginLoader (push) Has been cancelled
Push Updated Plugin Stub to Template / copy-stub (push) Has been cancelled
Lint / Run linters (push) Has been cancelled
Type Check / Run type checkers (push) Has been cancelled
Co-authored-by: ayssia <nynaevealmearah@gmail.com>
2024-10-03 17:28:00 -04:00
AAGaming 4cff530b52 Fix missing components on Oct 2 2024 Steam Beta (#709) 2024-10-03 17:08:35 -04:00
Marco Rodolfi 2f90a4fcf7 Rebase semver parsing on main (#677)
Builder Win / Build PluginLoader for Win (push) Has been cancelled
Builder / Build PluginLoader (push) Has been cancelled
Push Updated Plugin Stub to Template / copy-stub (push) Has been cancelled
Lint / Run linters (push) Has been cancelled
Type Check / Run type checkers (push) Has been cancelled
Co-authored-by: Marco Rodolfi <marco.rodolfi.1992@gmail.com>
2024-09-17 06:21:31 -07:00
AAGaming 24fce1e093 bump @decky/ui to include findSP fix
Builder Win / Build PluginLoader for Win (push) Waiting to run
Builder / Build PluginLoader (push) Waiting to run
Push Updated Plugin Stub to Template / copy-stub (push) Waiting to run
Lint / Run linters (push) Waiting to run
Type Check / Run type checkers (push) Waiting to run
2024-09-16 19:31:04 -04:00
AAGaming 97b12972ee shut ts up
Builder Win / Build PluginLoader for Win (push) Waiting to run
Builder / Build PluginLoader (push) Waiting to run
Push Updated Plugin Stub to Template / copy-stub (push) Waiting to run
Lint / Run linters (push) Waiting to run
Type Check / Run type checkers (push) Waiting to run
2024-09-16 16:21:00 -04:00
AAGaming f69eb72df9 wait for SteamApp init stage 1 to finish before loading decky's frontend bundle
should fix the startup race condition
2024-09-16 16:17:20 -04:00
WerWolvTranslationBot 24215c0732 Translations update from Weblate (#704)
Builder Win / Build PluginLoader for Win (push) Has been cancelled
Builder / Build PluginLoader (push) Has been cancelled
Push Updated Plugin Stub to Template / copy-stub (push) Has been cancelled
Lint / Run linters (push) Has been cancelled
Type Check / Run type checkers (push) Has been cancelled
* Translated using Weblate (German)

Currently translated at 100.0% (158 of 158 strings)

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

* Translated using Weblate (Dutch)

Currently translated at 100.0% (158 of 158 strings)

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

* Translated using Weblate (Bulgarian)

Currently translated at 100.0% (158 of 158 strings)

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

---------

Co-authored-by: SecularSteve <fairfull.playing@gmail.com>
2024-09-14 23:01:47 -07:00
Sims e87ce625fb Test (#701)
Builder Win / Build PluginLoader for Win (push) Has been cancelled
Builder / Build PluginLoader (push) Has been cancelled
Push Updated Plugin Stub to Template / copy-stub (push) Has been cancelled
Lint / Run linters (push) Has been cancelled
Type Check / Run type checkers (push) Has been cancelled
2024-09-13 16:59:35 -07:00
AAGaming 1284075d02 update release systemd service
Builder Win / Build PluginLoader for Win (push) Has been cancelled
Builder / Build PluginLoader (push) Has been cancelled
Push Updated Plugin Stub to Template / copy-stub (push) Has been cancelled
Lint / Run linters (push) Has been cancelled
Type Check / Run type checkers (push) Has been cancelled
2024-09-11 22:54:16 -04:00
TrainDoctor d2c5aef58b Update release.yml 2024-09-11 19:49:39 -07:00
AAGaming caac379b08 just sleep 500ms for now to work around startup race condition 2024-09-11 21:50:48 -04:00
AAGaming c487a6e15a deprecate install scripts in repo (use decky-installer/cli instead, they're the same scripts but more up to date) 2024-09-11 21:02:41 -04:00
AAGaming 9df5f00068 drop TimeoutStopSec to 15s 2024-09-11 21:00:19 -04:00
AAGaming 508408ad5a use signals to shut down plugins instead of sending a socket message
should reduce or outright prevent shutdown stalls
2024-09-11 20:35:24 -04:00
AAGaming ef4ca204bd potentially fix startup race condition 2024-09-11 20:16:49 -04:00
AAGaming 1d7af36a2b add cache bust param to index.js of esmodule plugins
should fix hot reload
2024-09-11 19:38:58 -04:00
TrainDoctor 6b78e0ae9c Update bug_report.yml 2024-09-05 12:21:58 -07:00
TrainDoctor 1f5d5f9f1a Update bug_report.yml 2024-09-05 12:20:32 -07:00
TrainDoctor f7a47127a7 Update bug_report.yml 2024-09-05 12:19:12 -07:00
TrainDoctor 494f8dac5e Update bug_report.yml
Add new field requiring all installed plugins to be listed.
2024-09-05 12:14:33 -07:00
TrainDoctor bcc14848c5 Create plugin-info.sh
Add plugin-info script for debugging, thanks @Jaynator495!
2024-09-05 12:09:39 -07:00
AAGaming 0e40374b10 This also shouldn't have applied to stabls 2024-09-04 08:45:54 -04:00
AAGaming 81ffe11106 This shouldn't have applied to stable 2024-09-04 08:45:26 -04:00
AAGaming d06494885a fix external links softlocking the ui in testing store cta 2024-09-01 20:40:12 -04:00
Sims a6e4bcf052 Fix updater taking a long time (#696)
Replaces subprocess with asyncio.subprocess in some localplatformlinux functions and improves shutdown handling
Co-authored-by: AAGaming <aagaming@riseup.net>
2024-09-01 19:45:47 -04:00
AAGaming c1f7ca7f20 chore(backend): remove unused import in plugin.py 2024-09-01 14:18:33 -04:00
AAGaming 6ae6f5ee67 chore(backend): .warn -> .warning 2024-09-01 14:17:11 -04:00
Sims 016ed6e998 Fix shutdown timeouts (#695)
Co-authored-by: AAGaming <aagaming@riseup.net>
2024-09-01 14:15:49 -04:00
WerWolvTranslationBot 4842a599e0 Translated using Weblate (Russian) (#690)
Currently translated at 100.0% (158 of 158 strings)

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

Co-authored-by: Andrew <www.andru90@gmail.com>
2024-08-28 10:25:51 +00:00
AAGaming 927f912eb3 lint 2024-08-21 14:40:42 -04:00
AAGaming 7c9b68c1dd only grab the first 8 lines of the component stack 2024-08-20 14:55:59 -04:00
AAGaming e5e75cc16e fix oopsie breaking decky on the latest beta 2024-08-13 21:14:17 -04:00
WerWolvTranslationBot 3656f541e6 Translations update from Weblate (#681)
* Translated using Weblate (Spanish)

Currently translated at 100.0% (158 of 158 strings)

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

* Translated using Weblate (Czech)

Currently translated at 100.0% (158 of 158 strings)

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

---------

Co-authored-by: Matsu <luciariasanchez323@gmail.com>
Co-authored-by: Meiton <michal.salati@gmail.com>
2024-08-12 15:04:45 -07:00
WerWolvTranslationBot f2b859b409 Translated using Weblate (Spanish) (#678)
Currently translated at 93.0% (147 of 158 strings)

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

Co-authored-by: Matsu <luciariasanchez323@gmail.com>
2024-08-10 07:37:27 +00:00
AAGaming 93b3919325 fix reloading plugins 2024-08-08 15:26:40 -04:00
AAGaming 7a161a5b83 remove useless toast delay 2024-08-08 15:20:47 -04:00
AAGaming 1265672067 Update @decky/ui to fix non-global dfl plugins crashing the UI due to a race condition 2024-08-08 14:25:56 -04:00
Sims b8acc849bc Ensure boot after network.target (#670) 2024-08-07 16:19:37 -04:00
AAGaming 65b6883dcc handle crashloops and disable decky for the user 2024-08-07 16:14:18 -04:00
AAGaming 166c7ea8a7 Work around account switching failing to open the CEF debugger socket (#668)
* Work around account switching failing to open the CEF debugger socket

this automates lsof and gdb to force close the socket before steam finishes shutting down (from RegisterForShutdownStart)

* lint

* fix LD_LIBRARY_PATH for gdb
2024-08-06 20:25:39 -07:00
WerWolvTranslationBot ddc807340c Translated using Weblate (Japanese) (#669)
Currently translated at 100.0% (158 of 158 strings)

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

Co-authored-by: Tak-attack <tak.bts@gmail.com>
2024-08-06 08:00:19 +00:00
AAGaming 7fc51c8b7d shut down properly when using runpydeck 2024-08-05 16:47:13 -04:00
AAGaming 131f0961ff Rewrite router/tabs/toaster hooks (#661) 2024-08-05 14:07:10 -04:00
Sims 75aa1e4851 Sims/pyinstaller misuse fix (#657)
* Fix misuse of pyinstaller

* Fix breaking change

* Fix pywright import errors
2024-08-05 00:00:49 +02:00
AAGaming dcfaf11696 don't try to update when running from source 2024-08-03 14:03:56 -04:00
Lukas Senionis 3e50ce65a7 fix: fetchNoCors and http_request data handling (#664) 2024-08-03 13:24:25 -04:00
WerWolvTranslationBot adf5a76392 Translations update from Weblate (#656)
* Translated using Weblate (Korean)

Currently translated at 100.0% (158 of 158 strings)

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

* Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (158 of 158 strings)

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

* Translated using Weblate (Turkish)

Currently translated at 76.5% (121 of 158 strings)

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

---------

Co-authored-by: Sungjoon Moon <sumoon@seoulsaram.org>
Co-authored-by: Akinaton Osti <akinaton.osti@gmail.com>
Co-authored-by: Bahasnyldz <bahasnyldz@gmail.com>
2024-08-01 07:28:38 +00:00
AAGaming e01a700669 chore(deps): bump @decky/ui 2024-07-26 14:35:21 -04:00
AAGaming 4b23a800f6 fix(toaster): fixup for latest @decky/ui 2024-07-26 14:29:25 -04:00
AAGaming 7045cdea6b chore(deps): bump @decky/api, @decky/ui 2024-07-26 14:18:29 -04:00
AAGaming ef0d449fb6 feat(api): add useQuickAccessVisible to plugin api 2024-07-26 14:17:04 -04:00
AAGaming a5ccc87a46 improve treeshaking 2024-07-17 19:38:16 -04:00
AAGaming 24c3d3c8f1 remove duplicate find in ErrorBoundary 2024-07-17 19:38:06 -04:00
AAGaming 2000eaf399 bump @decky/ui to fix beta issues 2024-07-17 19:37:49 -04:00
WerWolvTranslationBot dc9f89afad Translations update from Weblate (#655)
* Translated using Weblate (Italian)

Currently translated at 93.0% (147 of 158 strings)

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

* Translated using Weblate (French)

Currently translated at 93.0% (147 of 158 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 93.0% (147 of 158 strings)

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

* Translated using Weblate (Chinese (Traditional))

Currently translated at 93.0% (147 of 158 strings)

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

* Translated using Weblate (Albanian)

Currently translated at 29.7% (47 of 158 strings)

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

* Translated using Weblate (Spanish)

Currently translated at 69.6% (110 of 158 strings)

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

* Translated using Weblate (Greek)

Currently translated at 87.9% (139 of 158 strings)

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

* Translated using Weblate (German)

Currently translated at 93.0% (147 of 158 strings)

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

* Translated using Weblate (Czech)

Currently translated at 93.0% (147 of 158 strings)

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

* Translated using Weblate (Korean)

Currently translated at 93.0% (147 of 158 strings)

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

* Translated using Weblate (Portuguese (Portugal))

Currently translated at 87.9% (139 of 158 strings)

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

* Translated using Weblate (Portuguese (Brazil))

Currently translated at 88.6% (140 of 158 strings)

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

* Translated using Weblate (Russian)

Currently translated at 93.0% (147 of 158 strings)

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

* Translated using Weblate (Ukrainian)

Currently translated at 72.1% (114 of 158 strings)

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

* Translated using Weblate (Dutch)

Currently translated at 93.0% (147 of 158 strings)

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

* Translated using Weblate (Bulgarian)

Currently translated at 85.4% (135 of 158 strings)

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

* Translated using Weblate (Polish)

Currently translated at 93.0% (147 of 158 strings)

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

* Translated using Weblate (Finnish)

Currently translated at 87.9% (139 of 158 strings)

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

* Translated using Weblate (Japanese)

Currently translated at 87.9% (139 of 158 strings)

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

* Translated using Weblate (Arabic)

Currently translated at 36.7% (58 of 158 strings)

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

* Translated using Weblate (Turkish)

Currently translated at 72.1% (114 of 158 strings)

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

* Translated using Weblate (Swedish)

Currently translated at 20.8% (33 of 158 strings)

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

* 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 (Italian)

Currently translated at 100.0% (158 of 158 strings)

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

---------

Co-authored-by: Anonymous <noreply@weblate.org>
Co-authored-by: Marco Rodolfi <marco.rodolfi@tuta.io>
2024-07-13 06:16:47 +02:00
WerWolvTranslationBot 88dfd476e5 Translations update from Weblate (#653)
Co-authored-by: elliotfontaine <elliot.fontaine@gmail.com>
Co-authored-by: Meiton <michal.salati@gmail.com>
Co-authored-by: Andrew <www.andru90@gmail.com>
Co-authored-by: Denys Dovhan <denysdovhan@gmail.com>
Co-authored-by: Eryk Pawlikowski <eryk5188@gmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
2024-07-08 18:20:08 -04:00
AAGaming d634503ffa nevermind, run i18next-parser onlyon english and let weblate handle the rest 2024-07-08 18:19:13 -04:00
AAGaming 24e1a239cc sort translations 2024-07-08 18:05:41 -04:00
AAGaming 88fed53a07 commit hook is broken for now 2024-07-08 18:05:24 -04:00
AAGaming 383225aa54 sort the same way as weblate 2024-07-08 18:05:08 -04:00
AAGaming 89c9751f04 try to resolve weblate conflicts 2024-07-08 17:58:41 -04:00
WerWolvTranslationBot bf22cdb491 Translations update from Weblate (#652)
Co-authored-by: Anonymous <noreply@weblate.org>
Co-authored-by: elliotfontaine <elliot.fontaine@gmail.com>
Co-authored-by: Meiton <michal.salati@gmail.com>
Co-authored-by: Andrew <www.andru90@gmail.com>
Co-authored-by: Denys Dovhan <denysdovhan@gmail.com>
Co-authored-by: Eryk Pawlikowski <eryk5188@gmail.com>
2024-07-08 17:56:54 -04:00
AAGaming 54b50fd25c add default values back 2024-07-08 17:50:00 -04:00
WerWolvTranslationBot 1bccfe22ef Translations update from Weblate (#645)
Co-authored-by: Anonymous <noreply@weblate.org>
Co-authored-by: elliotfontaine <elliot.fontaine@gmail.com>
Co-authored-by: Meiton <michal.salati@gmail.com>
Co-authored-by: Andrew <www.andru90@gmail.com>
Co-authored-by: Eryk Pawlikowski <eryk5188@gmail.com>
2024-07-08 17:35:57 -04:00
AAGaming 68c6eedb81 fix(i18n): fix i18next-parser configuration
i can't wait to fix the weblate conflicts this one causes
2024-07-08 17:33:09 -04:00
AAGaming 7e0ec279e7 fix plugin backend reloading 2024-07-08 17:19:05 -04:00
AAGaming 837ae838d4 Merge branch 'sims/fix-backend-socket-concurrent-use' 2024-07-08 17:15:48 -04:00
dependabot[bot] 5646168959 Bump certifi from 2024.6.2 to 2024.7.4 in /backend (#651) 2024-07-08 20:57:43 +00:00
suchmememanyskill 7043256232 Add lock to internal socket fetch function 2024-07-07 20:33:45 +02:00
AAGaming c1f01dcb21 enable aiohttp signal handler 2024-07-04 01:41:12 -04:00
AAGaming fb3de87921 use transparent spinner in WithSuspense 2024-07-04 01:34:52 -04:00
AAGaming 846995e0d3 LINTERRRRRRRRRRRRRRRRRRRR 2024-07-04 01:28:32 -04:00
AAGaming f73aced452 styling fixes for SteamSpinner 2024-07-04 01:27:22 -04:00
AAGaming a45df8566e fix plugin uninstalls 2024-07-04 01:27:13 -04:00
AAGaming 2b2a1d9e39 make the qam fix css more accurate 2024-07-03 22:49:42 -04:00
AAGaming 86df2e0fd9 inject some css into the QAM to fix overscroll issues 2024-07-03 22:38:36 -04:00
AAGaming f5eba51c52 set process/thread titles for decky and plugins
sadly requires the `setproctitle` pypi module because python doesn't have a builtin to do this :/
2024-07-03 20:43:08 -04:00
AAGaming 83972972f3 allow direnv to change vscode python path when updating 2024-07-03 20:25:37 -04:00
AAGaming 2c4edeb810 implement a shutdown routine instead of just waiting for all plugins to stop on their own 2024-07-03 20:08:30 -04:00
AAGaming b32fa7948f fix wiki link 2024-07-03 13:15:55 -04:00
AAGaming 9a098ec942 remove dead screenshot for now 2024-07-03 13:14:23 -04:00
AAGaming b378a25c8c bump download-artifact 2024-06-29 18:08:41 -04:00
AAGaming 0c96738530 Add windows stub for restart_webhelper
manually merged
closes #632
2024-06-29 17:59:32 -04:00
AAGaming 9875c4ddbc fix: upload-artifact is at v4, not v5 2024-06-29 17:54:24 -04:00
AAGaming a12d9d2bdb chore(ci): update actions to remove deprecation warnings 2024-06-29 17:53:01 -04:00
AAGaming e266258fc4 update python and OS versions in CI 2024-06-29 17:46:21 -04:00
AAGaming 725ebd5835 fix: add .bind to legacy plugin API to fix filepicker 2024-06-29 17:41:03 -04:00
WerWolvTranslationBot 58cd6e9541 Translations update from Weblate (#644)
Co-authored-by: Anonymous <noreply@weblate.org>
2024-06-29 17:29:12 -04:00
K900 5697d98862 treewide: fix package structure
The static files need to be inside the module to be installed correctly
as part of the module.
2024-06-29 16:53:57 -04:00
K900 6cd4fb5553 backend: update all dependencies to latest 2024-06-29 16:53:57 -04:00
AAGaming a3143c11a8 use node 20 in ci 2024-06-27 02:18:41 -04:00
AAGaming d71fb7935b fix plugin uninstall dialog issues 2024-06-27 01:43:01 -04:00
AAGaming 107b9abb3e fix testing our own PRs 2024-06-27 00:37:35 -04:00
AAGaming 0cfb41755a lint 2024-06-27 00:21:32 -04:00
AAGaming 2f8b5df007 bump @decky/ui 2024-06-27 00:20:18 -04:00
AAGaming 69e9f998e9 bump @decky/ui 2024-06-27 00:13:54 -04:00
AAGaming a8d55785cf fix cef debug toggle 2024-06-27 00:09:05 -04:00
AAGaming d067fe6361 add some errorboundaries 2024-06-27 00:09:00 -04:00
AAGaming c02a78ed6e fix plugins not loading on install 2024-06-27 00:08:45 -04:00
AAGaming c2f8cba4af trigger temporarilyDisableReporting in errorboundary 2024-06-27 00:08:32 -04:00
AAGaming c36f1985bd improve error checking logic 2024-06-27 00:08:13 -04:00
AAGaming fc52cf53ee don't report errors to valve if we've caused one in the last 30 seconds 2024-06-27 00:07:55 -04:00
AAGaming dcff7d146b move webpack check into index 2024-06-27 00:07:38 -04:00
AAGaming 13a38d82fd accidental lack of indent? 2024-06-27 00:07:25 -04:00
AAGaming b537968feb clean up legacy utilities for security 2024-06-27 00:07:06 -04:00
AAGaming 983fcf3014 RUN LINTER AGAINNNNNNN 2024-06-14 20:34:08 -04:00
AAGaming 61ad88db77 add doNotReportErrors 2024-06-14 20:20:15 -04:00
AAGaming 84577c8708 typescript please 2024-06-14 19:32:10 -04:00
Party Wumpus 6bd3951d31 add docstring for emit 2024-06-14 22:45:02 +00:00
AAGaming 48e79f803a update @decky/ui and @decky/api 2024-06-13 18:27:59 -04:00
AAGaming 7f421f5bd4 remove unused dep 2024-06-13 17:52:19 -04:00
AAGaming d6e71b23ef fixup prettier 2024-06-13 17:49:40 -04:00
AAGaming 54aecee64e hook up plugin events properly 2024-06-13 17:47:53 -04:00
AAGaming 822b6bcaaa fix lockfile 2024-06-13 17:08:38 -04:00
AAGaming 259aabf82f update dependencies 2024-06-13 17:00:50 -04:00
AAGaming 1de8c5915b the rest of f11e34a (from 3a83fa8) 2024-06-13 15:26:23 -04:00
WerWolvTranslationBot 4f92276147 Translations update from Weblate (#606)
* Translated using Weblate (Dutch)

Currently translated at 100.0% (147 of 147 strings)

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

* Added translation using Weblate (Arabic)

* 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 (Arabic)

Currently translated at 35.3% (52 of 147 strings)

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

* Translated using Weblate (French)

Currently translated at 100.0% (147 of 147 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (147 of 147 strings)

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

* Translated using Weblate (German)

Currently translated at 100.0% (147 of 147 strings)

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

* Translated using Weblate (Korean)

Currently translated at 100.0% (147 of 147 strings)

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

* Added translation using Weblate (Turkish)

* 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 (Turkish)

Currently translated at 77.5% (114 of 147 strings)

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

* Translated using Weblate (Chinese (Traditional))

Currently translated at 100.0% (147 of 147 strings)

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

* Added translation using Weblate (Swedish)

* 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 (Swedish)

Currently translated at 21.7% (32 of 147 strings)

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

* Translated using Weblate (Russian)

Currently translated at 100.0% (147 of 147 strings)

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

* Translated using Weblate (Czech)

Currently translated at 100.0% (147 of 147 strings)

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

* Translated using Weblate (Portuguese (Brazil))

Currently translated at 95.2% (140 of 147 strings)

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

---------

Co-authored-by: Danae Dekker <genecyll@gmail.com>
Co-authored-by: d7eeem <almutiri21@gmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: foXaCe <foxace66@gmail.com>
Co-authored-by: Qihan Xu <imhe6@outlook.com>
Co-authored-by: Benedikt Wagener <bwagener@proton.me>
Co-authored-by: Sungjoon Moon <sumoon@seoulsaram.org>
Co-authored-by: Bahasnyldz <bahasnyldz@gmail.com>
Co-authored-by: david082321 <david082321@yahoo.com.tw>
Co-authored-by: mmfa450 <mmou04faa@gmail.com>
Co-authored-by: kotovasia <super.capt2013.ya@gmail.com>
Co-authored-by: Meiton <michal.salati@gmail.com>
Co-authored-by: tobidashite <mpdeandrade3@gmail.com>
2024-06-13 15:14:24 -04:00
Party Wumpus f11e34ab25 Typing fix
linters are the light of my life
2024-06-13 15:13:58 -04:00
Sims 23944f7cbf Add new user agent (#610) 2024-06-13 15:12:45 -04:00
AAGaming e6b1950bcb fix for latest beta (backport) 2024-06-13 15:12:24 -04:00
AAGaming 0c6c7b1b06 missed a log 2024-05-27 17:22:08 -04:00
AAGaming 9c8db576f5 error boundary now properly reports steam errors 2024-05-27 17:21:27 -04:00
AAGaming a84a13c76d Custom error handler and some misc fixes 2024-05-25 19:14:54 -04:00
AAGaming 96cc72f2ca chore: fetch -> fetchNoCors 2024-05-24 23:26:39 -04:00
Party Wumpus 372771a228 plugin install progress (#614)
* Frontend progress bars

* Backend bit

* closure is stale i think so no closure for you

* Fix formatting of the progress svgs

* Reset progress bar when new plugin starts downloading
2024-05-13 14:42:55 +01:00
PartyWumpus 675b6d5ef8 fix locale mistake
from 28aca03f0d
2024-05-12 23:56:11 +01:00
Party Wumpus 97b62ac72b [needs websockets] Add development flake file (#620)
* Add development flake file

* use regular nixpkgs import

* Make vscode work and add .envrc

* add .direnv to the .gitignore file
2024-05-12 21:02:47 +01:00
AAGaming 0b1c069448 port to @decky/ui
TODO: update package.json to match once @decky/ui is on NPM
2024-05-12 15:47:08 -04:00
PartyWumpus 43b940e216 remove some type: ignore and make some specific 2024-05-05 10:17:03 +01:00
PartyWumpus 10e13571e5 version does have types, false alarm 2024-05-05 08:52:23 +01:00
AAGaming 14ea7b964f implement fetch and external resource request apis 2024-05-04 22:39:30 -04:00
AAGaming 2a22f000c1 add deckdebug.sh utility 2024-05-04 22:38:40 -04:00
WerWolvTranslationBot 63f90d884e 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-04-22 13:29:14 -04:00
Wayne Heaney a1a29616e5 Add Plugin.uninstall callback support (#555)
* Add Plugin.uninstall callback support

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

* Remove empty deck.sh
2024-04-22 13:29:13 -04:00
AAGaming 6b06bae250 fix: support new minified class names
bumps decky-frontend-lib to 3.25.0
can't stop us THAT easily :P
2024-04-22 13:18:33 -04:00
TrainDoctor 9a0a52f9e3 Update bug_report.yml 2024-04-22 13:17:30 -04:00
TrainDoctor 6f7dd26d56 Update bug_report.yml 2024-04-22 13:17:30 -04:00
WerWolvTranslationBot 28aca03f0d 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-04-22 13:17:28 -04:00
Party Wumpus f9ff518e6d attempt to add plugin events to the plugin frontend api.
unable to test right now though
2024-04-09 15:54:48 +01:00
Party Wumpus de9d2144a6 name RouteNotFoundError 2024-03-01 23:08:32 +00:00
Party Wumpus 11b743a792 Add loading bar to testing page and make downloading more robust 2024-03-01 23:07:51 +00:00
Party Wumpus 637e3c566e what if the error message was better 2024-02-22 17:37:20 +00:00
Party Wumpus 89a4a69f6d make frontend -> backend errors actually work 2024-02-22 16:38:50 +00:00
AAGaming a449181802 hook up the backend api 2024-02-21 17:39:13 -05:00
AAGaming 4696583680 fix unused imports 2024-02-21 01:42:39 -05:00
AAGaming 6d2e9365c0 more major websocket progress 2024-02-21 01:08:25 -05:00
AAGaming 61cf80f8a2 update service to reduce startup time 2024-02-20 18:51:22 -05:00
Party Wumpus 39e752e4e2 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-20 21:42:37 +00:00
Party Wumpus 992e2e2ad3 fix finding qam root node for feb 14th beta 2024-02-20 21:40:01 +00:00
Andrew Moore c2ebc78836 [Feature] Freeze updates for devs (#582) 2024-02-20 21:39:43 +00:00
eXhumer dc1697d049 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-20 21:34:16 +00:00
Party Wumpus 35f6f041c1 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-20 21:33:41 +00:00
AAGaming 7e3f9edacf clean up updater logic 2024-02-19 23:42:24 -05:00
AAGaming 22b732bab4 clean up deranged react triple-import in Updater.tsx 2024-02-19 23:34:01 -05:00
Party Wumpus 61b984bfa1 what if the error handling worked for async event listeners
the anonymous async function is made so the event handlers can each be started in parallel, just in case there's a long running function
2024-02-16 13:35:08 +00:00
Party Wumpus 867ce63f7b Add event handler 2024-02-15 22:28:36 +00:00
AAGaming ee6122b97d restart ui by killing webhelper
this cleanly reloads the ui, prevents memory leaks, and won't break the toasts
2024-02-14 17:49:52 -05:00
AAGaming 091428f683 small loader refactoring 2024-02-14 17:49:27 -05:00
AAGaming 9db3f3f20e bump dfl 2024-02-14 17:49:10 -05:00
AAGaming 37d70c31ff stop using homebrew/dev/plugins
it keeps causing me confusion between plugin versions in development
2024-02-14 17:49:00 -05:00
AAGaming ee1627a3a1 fix some broken types 2024-02-14 17:06:41 -05:00
Jozen Blue Martinez ecd8ef5998 fix(filepicker_ls): use case insensitive matching for file exts (#585) 2024-02-14 16:44:49 -05:00
Party Wumpus 8987076c5f 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-14 16:43:34 -05:00
Party Wumpus ec41c61219 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-14 16:43:30 -05:00
Party Wumpus 21c7742f9a 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-14 16:42:14 -05:00
AAGaming e8add28797 fix: use findInReactTree to find correct errorboundary for toaster
fixes toaster error on latest beta
2024-02-14 16:42:14 -05:00
AAGaming f5e902f741 fix vscode import memes 2024-02-14 16:40:48 -05:00
Beebles 063961d36a 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-21 16:49:52 -05:00
dependabot[bot] 96ce599e34 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-21 16:49:52 -05:00
AAGaming c5ea95a787 finalize api for plugin events in backend
just need frontend impl now
2023-12-31 20:29:19 -05:00
AAGaming db96121304 -post -> -dev 2023-12-31 19:13:19 -05:00
AAGaming 40c7c1b515 port updater to ws, also small refactoring 2023-12-30 21:32:23 -05:00
AAGaming 70104065e2 chore: get rid of useless old tabs hook 2023-12-30 18:06:07 -05:00
AAGaming 11a88186ba fix ci issue 2023-12-30 00:50:12 -05:00
AAGaming 6522ebf0ca Implement legacy & modern plugin method calls over WS
This version builds fine and runs all of the 14 plugins I have installed perfectly, so we're really close to having this done.
2023-12-30 00:46:59 -05:00
WerWolvTranslationBot 6042ca56b8 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-29 18:40:53 -05:00
Jan 5190765ce1 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-29 18:40:53 -05:00
AAGaming 3a38cf8074 fix(developer): add back valve internal on beta
look i was tired when writing yesterday's fix okay
2023-12-29 18:40:53 -05:00
AAGaming 4f40b97f53 fix: idiotic formatting error i should have noticed 2023-12-29 18:40:52 -05:00
AAGaming 5fd5b2f08c fix: Adjust tabs and toaster hooks to work on react 18, also half-fix Valve Internal 2023-12-29 18:40:52 -05:00
Party Wumpus 87d7e15951 bump dfl 2023-12-29 18:40:52 -05:00
Jan 98e2d1232c replace chmod implementation with os.chmod (#541) 2023-12-29 18:40:52 -05:00
Jan 6cb545c78d Check if Linux service is running before trying to start or stop it (#540)
this prevents needless prompts opening up
2023-12-29 18:40:52 -05:00
K900 41c62c3a34 refactor(backend): get version from package metadata 2023-12-29 18:34:19 -05:00
K900 31a6202da9 refactor(ci): reorganize CI to build tagged artifacts 2023-12-29 18:34:19 -05:00
K900 3565c3c9b4 feat(backend): log our version on startup 2023-12-29 18:34:19 -05:00
K900 e2ade0d731 feat(backend): enable poetry-dynamic-versioning 2023-12-29 18:34:19 -05:00
AAGaming 06690890fb fix tasks 2023-12-29 18:33:36 -05:00
marios8543 8b0d1753ef fix a couple types 2023-11-14 00:34:48 +02:00
AAGaming 70532c8d0b change default event DataType 2023-11-14 00:04:56 +02:00
AAGaming 5e1e035bc2 more progress on websockets 2023-11-14 00:04:56 +02:00
AAGaming 34d1a34b10 Migrate most of frontend callServerMethod usage over to websocket 2023-11-14 00:04:56 +02:00
AAGaming cfb6fe69e3 remove test method 2023-11-14 00:04:56 +02:00
AAGaming 1921e7ec56 JS -> Python WS now functional 2023-11-14 00:04:56 +02:00
AAGaming 05b41b3410 more progress on WS router 2023-11-14 00:03:47 +02:00
AAGaming 18d89e76fd more work on websockets 2023-11-14 00:03:47 +02:00
AAGaming 4a9b45b98e initial WSRouter implementation 2023-11-14 00:03:47 +02:00
AAGaming 8f299a90dc init 2023-11-14 00:02:14 +02:00
K900 5a633fdd82 Packaging rework (#531)
* fix: get rid of title view jank on latest beta

* Count the number of installs for each plugin (#557)

* Bump aiohttp from 3.8.4 to 3.8.5 in /backend (#558)

* fix: include Decky version in request for index.js

This avoids the If-Modified-Since logic in aiohttp and ensures Steam doesn't cache old JS,
even if the timestamps are normalized.

* fix: clean up shellcheck warnings in act runner script

* fix: gitignore settings/

* fix: ensure state directories exist when running without the installer

* feat: determine root directory correctly when running from in-tree

* fix: fix typo in CI script

* refactor: build a proper Python package with poetry

* refactor: move decky_plugin under the poetry structure

There's no need to special case it anymore, just treat it like any other Python module.

* sandboxed_plugin: better fix, attempt 2

---------

Co-authored-by: AAGaming <aagaming@riseup.net>
Co-authored-by: Party Wumpus <48649272+PartyWumpus@users.noreply.github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-11-13 23:40:37 +02:00
marios8543 8ce4a7679e Remove legacy data from pyinstaller 2023-11-01 00:04:14 +02:00
marios8543 a0920cf0d0 fix sys.modules import 2023-10-31 23:38:00 +02:00
marios8543 7565a66d90 fix emit_message mechanism 2023-10-31 23:18:31 +02:00
marios8543 e4b1efc44d run method calls asynchronously 2023-10-31 23:18:31 +02:00
marios8543 85f4604bfd fix static/lang file fetch and method call 2023-10-31 23:18:31 +02:00
marios8543 f30309d153 fix start/stop methods 2023-10-31 23:18:31 +02:00
marios8543 f48d774554 fix customtypes import 2023-10-31 23:18:31 +02:00
marios8543 268311c482 Add message emit mechanism 2023-10-31 23:18:31 +02:00
marios8543 ed0f851d4d Add act to .gitignore 2023-10-31 23:18:31 +02:00
Party Wumpus 2f4e79a40e fix typo
this is what i get for commiting to main 😔
2023-10-31 23:18:31 +02:00
Party Wumpus f508d1dfce fix logical error when no store was set 2023-10-31 23:18:30 +02:00
marios8543 8dc6f19d2b Run response_listener task 2023-10-31 23:18:23 +02:00
marios8543 321242b0d9 Experimental support for async method calls 2023-10-31 23:18:23 +02:00
marios8543 949c5e73c4 Add localplatform stuff to its own package 2023-10-31 23:17:57 +02:00
marios8543 da9217ac4a Fix type error on store.tsx 2023-10-31 23:17:57 +02:00
marios8543 39f64ca666 Drop support for legacy plugins 2023-10-31 23:17:49 +02:00
AAGaming 2391af09eb fix paths 2023-10-31 23:17:23 +02:00
AAGaming 0b01df7339 oops 2023-10-31 23:17:03 +02:00
AAGaming c69ca5e821 fix broken import 2023-10-31 23:17:03 +02:00
AAGaming b155734dcf fix ci (hopefully, because act wont work) 2023-10-31 23:17:03 +02:00
WerWolv 96ae502202 Moved locales folder and requirements.txt 2023-10-31 23:16:24 +02:00
WerWolv b373c3114b Moved main.py 2023-10-31 23:15:46 +02:00
WerWolv af6784272c Moved backend entirely into the backend folder 2023-10-31 23:15:25 +02:00
AAGaming df08f611b9 run lint and typecheck on PRs 2023-10-31 23:13:19 +02:00
AAGaming de1b24b8bc remove quotes on some types 2023-10-31 23:13:19 +02:00
AAGaming fae09596a7 make ci title consistent 2023-10-31 23:13:19 +02:00
AAGaming e8f5ce8d5a move type checking to other workflow, fix TS errors, add TSC checking 2023-10-31 23:13:19 +02:00
AAGaming 81726acd51 add pyright ci 2023-10-31 23:13:00 +02:00
AAGaming 5582457c58 move to module imports 2023-10-31 23:12:43 +02:00
marios8543 df755063c2 type hints on main,plugin,updater,utilites.localsocket 2023-10-31 23:11:37 +02:00
AAGaming 1949e9fcf1 begin adding static types to backend code 2023-10-31 23:11:08 +02:00
marios8543 28ca7b5c90 fix emit_message mechanism 2023-10-18 21:04:51 +03:00
marios8543 feabb582b2 run method calls asynchronously 2023-10-18 19:38:07 +03:00
marios8543 47e9708a20 fix static/lang file fetch and method call 2023-10-18 17:21:57 +03:00
marios8543 934b1b35ad fix start/stop methods 2023-10-18 15:34:25 +03:00
marios8543 88250b3e20 fix customtypes import 2023-10-18 14:59:05 +03:00
marios8543 d9ba637cd9 Add message emit mechanism 2023-10-18 14:45:36 +03:00
marios8543 dcee5ca4e4 Add act to .gitignore 2023-10-18 02:39:52 +03:00
marios8543 dffa82a555 fix uninstall bug 2023-10-18 02:39:15 +03:00
Party Wumpus 63f8cff341 fix typo
this is what i get for commiting to main 😔
2023-10-18 02:38:58 +03:00
Party Wumpus 836bcfbc03 fix logical error when no store was set 2023-10-18 02:37:48 +03:00
marios8543 64867369f9 Fix decky_plugin on windows CI 2023-10-18 02:36:55 +03:00
marios8543 315b2f9cda Run response_listener task 2023-10-18 00:04:14 +03:00
marios8543 07c8ddc0b2 Experimental support for async method calls 2023-10-17 23:52:18 +03:00
marios8543 36c145bb3a Add localplatform stuff to its own package 2023-10-17 23:51:57 +03:00
marios8543 19793d71e6 Fix type error on store.tsx 2023-10-17 17:11:50 +03:00
marios8543 796b8b49f4 Drop support for legacy plugins 2023-10-17 17:08:23 +03:00
marios8543 1b9d674a81 fix decky_plugin path in pyinstaller 2023-10-17 17:08:23 +03:00
AAGaming 949244e8e6 fix paths 2023-10-17 17:08:23 +03:00
AAGaming b7d4d57bc2 oops 2023-10-17 17:08:23 +03:00
AAGaming 458fa6a66c fix broken import 2023-10-17 17:08:23 +03:00
AAGaming 06fccb792f fix ci (hopefully, because act wont work) 2023-10-17 17:08:23 +03:00
AAGaming 6867feba85 speed up stupid make 2023-10-17 17:08:23 +03:00
WerWolv 45353c87c2 Moved locales folder and requirements.txt 2023-10-17 17:08:23 +03:00
WerWolv 37b8c5264f Moved main.py 2023-10-17 17:08:23 +03:00
WerWolv 5937971014 Moved backend entirely into the backend folder 2023-10-17 17:08:23 +03:00
AAGaming a351c02ac1 with, not env 2023-10-17 17:08:23 +03:00
AAGaming fc086db5e6 run lint and typecheck on PRs 2023-10-17 17:08:23 +03:00
AAGaming ca1332334d remove quotes on some types 2023-10-17 17:08:23 +03:00
AAGaming aebca54eac make ci title consistent 2023-10-17 17:08:23 +03:00
AAGaming 8fe8062950 move type checking to other workflow, fix TS errors, add TSC checking 2023-10-17 17:08:23 +03:00
AAGaming 11d731cf35 add pyright ci 2023-10-17 17:08:23 +03:00
AAGaming bf83eabe6b move to module imports 2023-10-17 17:08:23 +03:00
marios8543 a7c358844c type hints on main,plugin,updater,utilites.localsocket 2023-10-17 17:08:23 +03:00
AAGaming e2d708a6af begin adding static types to backend code 2023-10-17 17:08:23 +03:00
AAGaming 1e1e82ed71 remove useless main.py imports 2023-10-17 17:08:23 +03:00
142 changed files with 13300 additions and 7970 deletions
+1
View File
@@ -0,0 +1 @@
use flake
+31 -5
View File
@@ -42,7 +42,7 @@ body:
label: SteamOS version
# 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: "SteamOS 3.4.3 Stable"
placeholder: "SteamOS 3.5.7 Stable"
validations:
required: true
@@ -57,18 +57,44 @@ body:
validations:
required: true
- type: input
attributes:
label: Decky Loader Version
description: Specify the exact version of Decky.
placeholder: v3.0.0-pre12
validations:
required: true
- type: textarea
attributes:
label: Plugin Info
description: "Include all plugins installed including their version. Helpful script here: https://github.com/SteamDeckHomebrew/decky-loader/blob/main/scripts/plugin-info.sh"
placeholder: "If you don't want to collect this info manually you can download a helpful script linked in this item's description and place it into your home directory, chmod +x plugin-info.sh and then run it with ./plugin-info.sh"
validations:
required: true
- type: input
attributes:
label: Have you modified the read-only filesystem at any point?
description: Describe how here, if you haven't done anything you can leave this blank
placeholder: Yes, I've installed neofetch via pacman.
description: "Describe how here, if you haven't done anything you can leave this blank"
placeholder: "Yes, I've installed neofetch via pacman."
validations:
required: false
- type: textarea
attributes:
label: 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
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.
placeholder: deckylog.txt
validations:
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
+24 -16
View File
@@ -14,24 +14,28 @@ jobs:
steps:
- name: Checkout 🧰
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: Set up NodeJS 18 💎
uses: actions/setup-node@v3
- name: Set up NodeJS 20 💎
uses: actions/setup-node@v4
with:
node-version: 18
node-version: 20
- name: Set up Python 3.11.4 🐍
uses: actions/setup-python@v4
- name: Set up Python 3.11.7 🐍
uses: actions/setup-python@v5
with:
python-version: "3.11.4"
python-version: "3.11.7"
- name: Install Poetry
uses: snok/install-poetry@v1
with:
virtualenvs-create: false
- name: Install Python dependencies ⬇️
working-directory: ./backend
run: |
python -m pip install --upgrade pip
pip install pyinstaller==5.13.0
pip install -r requirements.txt
C:\Users\runneradmin\.local\bin\poetry self add "poetry-dynamic-versioning[plugin]"
C:\Users\runneradmin\.local\bin\poetry install --no-interaction
- name: Install JS dependencies ⬇️
working-directory: ./frontend
@@ -44,16 +48,20 @@ jobs:
run: pnpm run build
- 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;/plugin" --hidden-import=logging.handlers --hidden-import=sqlite3 ./backend/main.py
working-directory: ./backend
run: |
C:\Users\runneradmin\.local\bin\poetry dynamic-versioning
C:\Users\runneradmin\.local\bin\poetry run pyinstaller pyinstaller.spec
- 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;/plugin" --hidden-import=logging.handlers --hidden-import=sqlite3 ./backend/main.py
working-directory: ./backend
run: $env:DECKY_NOCONSOLE = 1; C:\Users\runneradmin\.local\bin\poetry run pyinstaller pyinstaller.spec
- name: Upload package artifact ⬆️
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: PluginLoader Win
path: |
./dist/PluginLoader.exe
./dist/PluginLoader_noconsole.exe
./backend/dist/PluginLoader.exe
./backend/dist/PluginLoader_noconsole.exe
+25 -225
View File
@@ -3,30 +3,9 @@ name: Builder
on:
push:
pull_request:
workflow_call:
# schedule:
# - cron: '0 13 * * *' # run at 1 PM UTC
workflow_dispatch:
inputs:
release:
type: choice
description: Release the asset
default: 'none'
options:
- none
- prerelease
- release
bump:
type: choice
description: Semver to bump
default: 'none'
options:
- none
- patch
- minor
- major
permissions:
contents: write
jobs:
build:
@@ -34,47 +13,31 @@ jobs:
runs-on: ubuntu-22.04
steps:
- name: Print input
run : |
echo "release: ${{ github.event.inputs.release }}\n"
echo "bump: ${{ github.event.inputs.bump }}\n"
- name: Checkout 🧰
uses: actions/checkout@v3
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set up NodeJS 18 💎
uses: actions/setup-node@v3
- name: Set up NodeJS 20 💎
uses: actions/setup-node@v4
with:
node-version: 18
node-version: 20
- name: Set up Python 3.10.6 🐍
uses: actions/setup-python@v4
- name: Set up Python 3.11.7 🐍
uses: actions/setup-python@v5
with:
python-version: "3.10.6"
python-version: "3.11.7"
- name: Upgrade SQLite 3 binary version to 3.42.0 🧑‍💻
run: >
cd /tmp &&
wget "https://www.sqlite.org/2023/sqlite-autoconf-3420000.tar.gz" &&
tar -xvzf sqlite-autoconf-3420000.tar.gz &&
cd /tmp/sqlite-autoconf-3420000 &&
./configure --prefix=/usr --disable-static CFLAGS="-g" CPPFLAGS="$CPPFLAGS -DSQLITE_ENABLE_COLUMN_METADATA=1 \
-DSQLITE_ENABLE_UNLOCK_NOTIFY -DSQLITE_ENABLE_DBSTAT_VTAB=1 -DSQLITE_ENABLE_FTS3_TOKENIZER=1 \
-DSQLITE_ENABLE_FTS3_PARENTHESIS -DSQLITE_SECURE_DELETE -DSQLITE_ENABLE_STMTVTAB -DSQLITE_MAX_VARIABLE_NUMBER=250000 \
-DSQLITE_MAX_EXPR_DEPTH=10000 -DSQLITE_ENABLE_MATH_FUNCTIONS" &&
make -j$(nproc) &&
sudo make install &&
sudo cp /usr/lib/libsqlite3.so /usr/lib/x86_64-linux-gnu/ &&
sudo cp /usr/lib/libsqlite3.so.0 /usr/lib/x86_64-linux-gnu/ &&
sudo cp /usr/lib/libsqlite3.so.0.8.6 /usr/lib/x86_64-linux-gnu/ &&
rm -r /tmp/sqlite-autoconf-3420000
- name: Install Poetry
uses: snok/install-poetry@v1
with:
virtualenvs-create: false
- name: Install Python dependencies ⬇️
working-directory: ./backend
run: |
python -m pip install --upgrade pip
pip install pyinstaller==5.13.0
pip install -r requirements.txt
poetry self add "poetry-dynamic-versioning[plugin]"
poetry install --no-interaction
- name: Install JS dependencies ⬇️
working-directory: ./frontend
@@ -87,183 +50,20 @@ jobs:
run: pnpm run build
- 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:/plugin --hidden-import=logging.handlers --hidden-import=sqlite3 ./backend/main.py
working-directory: ./backend
run: |
poetry dynamic-versioning
pyinstaller pyinstaller.spec
- name: Upload package artifact ⬆️
if: ${{ !env.ACT }}
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: PluginLoader
path: ./dist/PluginLoader
path: ./backend/dist/PluginLoader
- name: Download package artifact locally
if: ${{ env.ACT }}
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
path: ./dist/PluginLoader
release:
name: Release stable version of the package
if: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.release == 'release' }}
needs: build
runs-on: ubuntu-latest
steps:
- name: Checkout 🧰
uses: actions/checkout@v3
- name: Install semver-tool asdf
uses: asdf-vm/actions/install@v1
with:
tool_versions: |
semver 3.3.0
- name: Fetch package artifact ⬇️
uses: actions/download-artifact@v3
if: ${{ !env.ACT }}
with:
name: PluginLoader
path: dist
- name: Get latest release
uses: rez0n/actions-github-release@main
id: latest_release
with:
token: ${{ secrets.GITHUB_TOKEN }}
repository: "SteamDeckHomebrew/decky-loader"
type: "nodraft"
- name: Prepare tag ⚙️
id: ready_tag
run: |
export VERSION=${{ steps.latest_release.outputs.release }}
echo "VERS: $VERSION"
OUT="notsemver"
if [[ "$VERSION" =~ "-pre" ]]; then
printf "is prerelease, bumping to release\n"
OUT=$(semver bump release "$VERSION")
printf "OUT: ${OUT}\n"\
printf "bumping by selected type.\n"
if [[ "${{github.event.inputs.bump}}" != "none" ]]; then
OUT=$(semver bump ${{github.event.inputs.bump}} "$OUT")
printf "OUT: ${OUT}\n"
else
printf "no type selected, not bumping for release.\n"
fi
elif [[ ! "$VERSION" =~ "-pre" ]]; then
printf "previous tag is a release, bumping by selected type.\n"
if [[ "${{github.event.inputs.bump}}" != "none" ]]; then
OUT=$(semver bump ${{github.event.inputs.bump}} "$VERSION")
printf "OUT: ${OUT}\n"
else
printf "previous tag is a release, but no bump selected. Defaulting to a patch bump.\n"
OUT=$(semver bump patch "$VERSION")
printf "OUT: ${OUT}\n"
fi
fi
echo "vOUT: v$OUT"
echo tag_name=v$OUT >> $GITHUB_OUTPUT
- name: Push tag 📤
uses: rickstaa/action-create-tag@v1.3.2
if: ${{ steps.ready_tag.outputs.tag_name && github.event_name == 'workflow_dispatch' && !env.ACT }}
with:
tag: ${{ steps.ready_tag.outputs.tag_name }}
message: Pre-release ${{ steps.ready_tag.outputs.tag_name }}
- name: Release 📦
uses: softprops/action-gh-release@v1
if: ${{ github.event_name == 'workflow_dispatch' && !env.ACT }}
with:
name: Release ${{ steps.ready_tag.outputs.tag_name }}
tag_name: ${{ steps.ready_tag.outputs.tag_name }}
files: ./dist/PluginLoader
prerelease: false
generate_release_notes: true
prerelease:
name: Release the pre-release version of the package
if: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.release == 'prerelease' }}
needs: build
runs-on: ubuntu-latest
steps:
- name: Checkout 🧰
uses: actions/checkout@v3
- name: Install semver-tool asdf
uses: asdf-vm/actions/install@v1
with:
tool_versions: |
semver 3.3.0
- name: Fetch package artifact ⬇️
uses: actions/download-artifact@v3
if: ${{ !env.ACT }}
with:
name: PluginLoader
path: dist
- name: Get latest release
uses: rez0n/actions-github-release@main
id: latest_release
with:
token: ${{ secrets.GITHUB_TOKEN }}
repository: "SteamDeckHomebrew/decky-loader"
type: "nodraft"
- name: Prepare tag ⚙️
id: ready_tag
run: |
export VERSION=${{ steps.latest_release.outputs.release }}
echo "VERS: $VERSION"
OUT=""
if [[ ! "$VERSION" =~ "-pre" ]]; then
printf "pre-release from release, bumping by selected type and prerel\n"
if [[ ! ${{ github.event.inputs.bump }} == "none" ]]; then
OUT=$(semver bump ${{github.event.inputs.bump}} "$VERSION")
printf "OUT: ${OUT}\n"
else
printf "type not selected, defaulting to patch\n"
OUT=$(semver bump patch "$VERSION")
printf "OUT: ${OUT}\n"
fi
OUT="$OUT-pre"
OUT=$(semver bump prerel "$OUT")
printf "OUT: ${OUT}\n"
elif [[ "$VERSION" =~ "-pre" ]]; then
printf "pre-release to pre-release, bumping by selected type and or prerel version\n"
if [[ ! ${{ github.event.inputs.bump }} == "none" ]]; then
OUT=$(semver bump ${{github.event.inputs.bump}} "$VERSION")
printf "OUT: ${OUT}\n"
OUT="$OUT-pre"
printf "OUT: ${OUT}\n"
printf "bumping prerel\n"
OUT=$(semver bump prerel "$OUT")
printf "OUT: ${OUT}\n"
else
printf "type not selected, defaulting to new pre-release only\n"
printf "bumping prerel\n"
OUT=$(semver bump prerel "$VERSION")
printf "OUT: ${OUT}\n"
fi
fi
printf "vOUT: v${OUT}\n"
echo tag_name=v$OUT >> $GITHUB_OUTPUT
- name: Push tag 📤
uses: rickstaa/action-create-tag@v1.3.2
if: ${{ steps.ready_tag.outputs.tag_name && github.event_name == 'workflow_dispatch' && !env.ACT }}
with:
tag: ${{ steps.ready_tag.outputs.tag_name }}
message: Pre-release ${{ steps.ready_tag.outputs.tag_name }}
- name: Release 📦
uses: softprops/action-gh-release@v1
if: ${{ github.event_name == 'workflow_dispatch' && !env.ACT }}
with:
name: Prerelease ${{ steps.ready_tag.outputs.tag_name }}
tag_name: ${{ steps.ready_tag.outputs.tag_name }}
files: ./dist/PluginLoader
prerelease: true
generate_release_notes: true
path: ./backend/dist/PluginLoader
+9 -9
View File
@@ -4,7 +4,7 @@ on: push
jobs:
copy-stub:
runs-on: ubuntu-latest
runs-on: ubuntu-22.04
steps:
- name: Checkout
uses: actions/checkout@8230315d06ad95c617244d2f265d237a1682d445
@@ -14,26 +14,26 @@ jobs:
token: ${{ secrets.GITHUB_TOKEN }}
- name: Checkout
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: Get changed files
id: changed-files
uses: tj-actions/changed-files@v35.6.3
uses: tj-actions/changed-files@531f5f7d163941f0c1c04e0ff4d8bb243ac4366f
with:
separator: ","
files: |
plugin/*
backend/decky_loader/plugin/imports/decky.pyi
- name: Is stub changed
id: changed-stub
run: |
STUB_CHANGED="false"
PATHS=(plugin plugin/decky_plugin.pyi)
PATHS=(backend backend/decky_loader/plugin/imports/decky.pyi)
SHA=${{ github.sha }}
SHA_PREV=HEAD^
FILES=$(git diff $SHA_PREV..$SHA --name-only -- ${PATHS[@]} | jq -Rsc 'split("\n")[:-1] | join (",")')
if [[ "$FILES" == *"plugin/decky_plugin.pyi"* ]]; then
$STUB_CHANGED="true"
if [[ "$FILES" == *"backend/decky_loader/plugin/imports/decky.pyi"* ]]; then
STUB_CHANGED="true"
echo "Stub has changed, pushing updated stub"
else
echo "Stub has not changed, exiting."
@@ -43,12 +43,12 @@ jobs:
echo "has_changed=$STUB_CHANGED" >> $GITHUB_OUTPUT
- name: Push updated stub
if: steps.changed-stub.outputs.has_changed == true
if: github.ref == 'refs/heads/main' && steps.changed-stub.outputs.has_changed == true
uses: dmnemec/copy_file_to_another_repo_action@bbebd3da22e4a37d04dca5f782edd5201cb97083
env:
API_TOKEN_GITHUB: ${{ secrets.GITHUB_TOKEN }}
with:
source_file: 'plugin/decky_plugin.pyi'
source_file: 'backend/decky_loader/plugin/imports/decky.pyi'
destination_repo: 'SteamDeckHomebrew/decky-plugin-template'
user_email: '11465594+TrainDoctor@users.noreply.github.com'
user_name: 'TrainDoctor'
+2 -2
View File
@@ -7,10 +7,10 @@ on:
jobs:
lint:
name: Run linters
runs-on: ubuntu-20.04
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v3 # Check out the repository first.
- uses: actions/checkout@v4 # Check out the repository first.
- name: Install TypeScript dependencies
working-directory: frontend
+156
View File
@@ -0,0 +1,156 @@
name: Release
on:
workflow_dispatch:
inputs:
release:
type: choice
description: Release the asset
default: 'none'
options:
- none
- prerelease
- release
bump:
type: choice
description: Semver to bump
default: 'none'
options:
- none
- patch
- minor
- major
permissions:
contents: write
jobs:
create_tag:
name: Tag a new version of the package
runs-on: ubuntu-22.04
outputs:
tag_name: ${{ steps.ready_tag.outputs.tag_name }}
steps:
- name: Checkout 🧰
uses: actions/checkout@v4
- name: Install semver-tool asdf
uses: asdf-vm/actions/install@v3
with:
tool_versions: |
semver 3.4.0
- name: Get latest release
uses: rez0n/actions-github-release@main
id: latest_release
with:
token: ${{ secrets.GITHUB_TOKEN }}
repository: "SteamDeckHomebrew/decky-loader"
type: "nodraft"
- name: Prepare tag ⚙️
id: ready_tag
run: |
export VERSION=${{ steps.latest_release.outputs.release }}
echo "VERS: $VERSION"
if [[ ${{github.event.inputs.release}} == "release" ]]; then
OUT="notsemver"
if [[ "$VERSION" =~ "-pre" ]]; then
printf "is prerelease, bumping to release\n"
OUT=$(semver bump release "$VERSION")
printf "OUT: ${OUT}\n"\
printf "bumping by selected type.\n"
if [[ "${{github.event.inputs.bump}}" != "none" ]]; then
OUT=$(semver bump ${{github.event.inputs.bump}} "$OUT")
printf "OUT: ${OUT}\n"
else
printf "no type selected, not bumping for release.\n"
fi
elif [[ ! "$VERSION" =~ "-pre" ]]; then
printf "previous tag is a release, bumping by selected type.\n"
if [[ "${{github.event.inputs.bump}}" != "none" ]]; then
OUT=$(semver bump ${{github.event.inputs.bump}} "$VERSION")
printf "OUT: ${OUT}\n"
else
printf "previous tag is a release, but no bump selected. Defaulting to a patch bump.\n"
OUT=$(semver bump patch "$VERSION")
printf "OUT: ${OUT}\n"
fi
fi
else
OUT=""
if [[ ! "$VERSION" =~ "-pre" ]]; then
printf "pre-release from release, bumping by selected type and prerel\n"
if [[ ! ${{ github.event.inputs.bump }} == "none" ]]; then
OUT=$(semver bump ${{github.event.inputs.bump}} "$VERSION")
printf "OUT: ${OUT}\n"
else
printf "type not selected, defaulting to patch\n"
OUT=$(semver bump patch "$VERSION")
printf "OUT: ${OUT}\n"
fi
OUT="$OUT-pre"
OUT=$(semver bump prerel "$OUT")
printf "OUT: ${OUT}\n"
elif [[ "$VERSION" =~ "-pre" ]]; then
printf "pre-release to pre-release, bumping by selected type and or prerel version\n"
if [[ ! ${{ github.event.inputs.bump }} == "none" ]]; then
OUT=$(semver bump ${{github.event.inputs.bump}} "$VERSION")
printf "OUT: ${OUT}\n"
OUT="$OUT-pre"
printf "OUT: ${OUT}\n"
printf "bumping prerel\n"
OUT=$(semver bump prerel "$OUT")
printf "OUT: ${OUT}\n"
else
printf "type not selected, defaulting to new pre-release only\n"
printf "bumping prerel\n"
OUT=$(semver bump prerel "$VERSION")
printf "OUT: ${OUT}\n"
fi
fi
fi
echo "vOUT: v${OUT}"
echo tag_name=v$OUT >> $GITHUB_OUTPUT
- name: Push tag 📤
uses: rickstaa/action-create-tag@v1.3.2
if: ${{ steps.ready_tag.outputs.tag_name && !env.ACT }}
with:
tag: ${{ steps.ready_tag.outputs.tag_name }}
message: Pre-release ${{ steps.ready_tag.outputs.tag_name }}
build:
name: Build tagged artifact
uses: ./.github/workflows/build.yml
needs: [create_tag]
release:
name: Release tagged artifact
runs-on: ubuntu-22.04
needs: [create_tag, build]
steps:
- name: Fetch package artifact ⬇️
uses: actions/download-artifact@v4
with:
name: PluginLoader
- name: Pre-release 📦
if: github.event.inputs.release == 'prerelease'
uses: softprops/action-gh-release@v1
with:
name: Prerelease ${{ needs.create_tag.outputs.tag_name }}
tag_name: ${{ needs.create_tag.outputs.tag_name }}
files: ./PluginLoader
prerelease: true
generate_release_notes: true
- name: Release 📦
if: github.event.inputs.release == 'release'
uses: softprops/action-gh-release@v1
with:
name: Release ${{ needs.create_tag.outputs.tag_name }}
tag_name: ${{ needs.create_tag.outputs.tag_name }}
files: ./PluginLoader
prerelease: false
generate_release_notes: true
+20 -7
View File
@@ -7,16 +7,29 @@ on:
jobs:
typecheck:
name: Run type checkers
runs-on: ubuntu-20.04
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v2 # Check out the repository first.
- uses: actions/checkout@v4 # Check out the repository first.
- name: Install Python dependencies
- name: Set up NodeJS 20 💎
uses: actions/setup-node@v4
with:
node-version: 20
- name: Set up Python 3.11.7 🐍
uses: actions/setup-python@v5
with:
python-version: "3.11.7"
- name: Install Poetry
uses: snok/install-poetry@v1
with:
virtualenvs-create: false
- name: Install Python dependencies ⬇️
working-directory: backend
run: |
python -m pip install --upgrade pip
[ -f requirements.txt ] && pip install -r requirements.txt
run: poetry install --no-interaction
- name: Install TypeScript dependencies
working-directory: frontend
@@ -33,4 +46,4 @@ jobs:
- name: Run tsc (TypeScript)
working-directory: frontend
run: $(pnpm bin)/tsc --noEmit
run: pnpm run typecheck
+7 -4
View File
@@ -29,7 +29,7 @@ MANIFEST
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
backend/dist/
# Installer logs
pip-log.txt
@@ -126,6 +126,7 @@ venv/
ENV/
env.bak/
venv.bak/
.direnv/
# Spyder project settings
.spyderproject
@@ -152,13 +153,15 @@ dmypy.json
cython_debug/
# static files are built
backend/static
backend/decky_loader/static
# ignore settings.json
# prevents leaking login details
.vscode/settings.json
# plugins folder for local launches
plugins/*
/plugins/*
act/.directory
act/artifacts/*
act/artifacts/*
bin/act
/settings/
+1
View File
@@ -1,6 +1,7 @@
{
"deckip" : "0.0.0.0",
"deckport" : "22",
"deckuser" : "deck",
"deckpass" : "ssap",
"deckkey" : "-i ${env:HOME}/.ssh/id_rsa",
"deckdir" : "/home/deck"
+17 -5
View File
@@ -37,8 +37,11 @@
"label": "dependencies",
"type": "shell",
"group": "none",
"dependsOn": [
"deploy"
],
"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": "ssh ${config:deckuser}@${config:deckip} -p ${config:deckport} ${config:deckkey} 'python -m ensurepip --root / && python -m pip install --user --break-system-packages --upgrade poetry && cd ${config:deckdir}/homebrew/dev/pluginloader/backend && python -m poetry install'",
"problemMatcher": []
},
{
@@ -97,7 +100,7 @@
"dependsOn": [
"checkforsettings"
],
"command": "ssh deck@${config:deckip} -p ${config:deckport} ${config:deckkey} 'mkdir -p ${config:deckdir}/homebrew/dev/pluginloader && mkdir -p ${config:deckdir}/homebrew/dev/plugins'",
"command": "ssh ${config:deckuser}@${config:deckip} -p ${config:deckport} ${config:deckkey} 'mkdir -p ${config:deckdir}/homebrew/dev/pluginloader && mkdir -p ${config:deckdir}/homebrew/plugins'",
"problemMatcher": []
},
{
@@ -105,7 +108,7 @@
"detail": "Deploy dev PluginLoader to deck",
"type": "shell",
"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 --force --rsh='ssh -p ${config:deckport} ${config:deckkey}' --exclude='.git/' --exclude='.github/' --exclude='.vscode/' --exclude='frontend/' --exclude='dist/' --exclude='contrib/' --exclude='*.log' --exclude='**/__pycache__/' --exclude='.gitignore' . ${config:deckuser}@${config:deckip}:${config:deckdir}/homebrew/dev/pluginloader",
"problemMatcher": []
},
// RUN
@@ -117,7 +120,7 @@
"dependsOn": [
"checkforsettings"
],
"command": "ssh deck@${config:deckip} -p ${config:deckport} ${config:deckkey} 'export PLUGIN_PATH=${config:deckdir}/homebrew/dev/plugins; export CHOWN_PLUGIN_PATH=0; export LOG_LEVEL=DEBUG; cd ${config:deckdir}/homebrew/services; echo '${config:deckpass}' | sudo -SE python3 ${config:deckdir}/homebrew/dev/pluginloader/backend/main.py'",
"command": "ssh -t ${config:deckuser}@${config:deckip} -p ${config:deckport} ${config:deckkey} 'export PATH=${config:deckdir}/.local/bin:$PATH; export PLUGIN_PATH=${config:deckdir}/homebrew/plugins; export CHOWN_PLUGIN_PATH=0; export LOG_LEVEL=DEBUG; cd ${config:deckdir}/homebrew/dev/pluginloader/backend; echo '${config:deckpass}' | poetry run sh -c \"cd ${config:deckdir}/homebrew/services; sudo -SE env \"PATH=\\$PATH\" python3 ${config:deckdir}/homebrew/dev/pluginloader/backend/main.py\"'",
"problemMatcher": []
},
{
@@ -181,10 +184,19 @@
"buildall",
"createfolders",
"dependencies",
"deploy",
// dependencies runs deploy already
// "deploy",
"runpydeck"
],
"problemMatcher": []
},
{
"label": "act",
"type": "shell",
"group": "none",
"detail": "Build release artifact using local CI",
"command": "./act/run-act.sh release",
"problemMatcher": []
}
]
}
+3 -3
View File
@@ -15,14 +15,14 @@
<a href="https://deckbrew.xyz/discord"><img src="https://img.shields.io/discord/960281551428522045?color=%235865F2&label=discord" /></a>
<br>
<br>
<img src="https://media.discordapp.net/attachments/966017112244125756/1012466063893610506/main.jpg" alt="Decky screenshot" width="80%">
<!-- <img src="https://media.discordapp.net/attachments/966017112244125756/1012466063893610506/main.jpg" alt="Decky screenshot" width="80%">-->
</p>
## 📖 About
Decky Loader is a homebrew plugin launcher for the Steam Deck. It can be used to [stylize your menus](https://github.com/suchmememanyskill/SDH-CssLoader), [change system sounds](https://github.com/EMERALD0874/SDH-AudioLoader), [adjust your screen saturation](https://github.com/libvibrant/vibrantDeck), [change additional system settings](https://github.com/NGnius/PowerTools), and [more](https://plugins.deckbrew.xyz/).
For more information about Decky Loader as well as documentation and development tools, please visit [our wiki](https://deckbrew.xyz).
For more information about Decky Loader as well as documentation and development tools, please visit [our wiki](https://wiki.deckbrew.xyz).
### 🎨 Features
@@ -105,7 +105,7 @@ Please consult [the wiki page regarding development](https://wiki.deckbrew.xyz/e
⚠️ If you are recieving build errors due to an out of date library, you should run this command inside of your repository.
```bash
pnpm update decky-frontend-lib --latest
pnpm update @decky/ui --latest
```
Source control and deploying plugins are left to each respective contributor for the cloned repos in order to keep dependencies up to date.
+23 -20
View File
@@ -1,44 +1,47 @@
#!/bin/bash
#!/usr/bin/env bash
set -eo pipefail
type=$1
# bump=$2
oldartifactsdir="old"
parent_path=$( cd "$(dirname "${BASH_SOURCE[0]}")" ; pwd -P )
cd "$parent_path"
parent_path=$( cd "$(dirname "${BASH_SOURCE[0]}")" || exit ; pwd -P )
cd "$parent_path" || exit
artifactfolders=$(find artifacts/ -maxdepth 1 -mindepth 1 -type d)
if [[ ${#artifactfolders[@]} > 0 ]]; then
for i in ${artifactfolders[@]}; do
foldername=$(dirname $i)
subfoldername=$(basename $i)
out=$foldername/$oldartifactsdir/$subfoldername-$(date +'%s')
if [[ ! "$subfoldername" =~ "$oldartifactsdir" ]]; then
mkdir -p $out
mv $i $out
printf "Moved "${foldername}"/"${subfoldername}" to "${out}" \n"
fi
done
fi
for i in artifacts/*; do
if [[ ! -d "$i" ]]; then
continue;
fi
subfoldername=$(basename "$i")
if [[ "$subfoldername" == "$oldartifactsdir" ]]; then
continue;
fi
out=artifacts/$oldartifactsdir/$subfoldername-$(date +'%s')
mkdir -p "$out"
mv "$i" "$out"
echo "Moved artifacts/${subfoldername} to ${out}"
done
cd ..
if [[ "$type" == "release" ]]; then
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
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
printf "Release type unspecified/badly specified.\n"
printf "Options: 'release' or 'prerelease'\n"
fi
cd act/artifacts
cd act/artifacts || exit
if [[ -d "1" ]]; then
cd "1/artifact"
cd "1/artifact" || exit
cp "PluginLoader.gz__" "PluginLoader.gz"
gzip -d "PluginLoader.gz"
chmod +x PluginLoader
@@ -10,6 +10,7 @@ from hashlib import sha256
from io import BytesIO
from logging import getLogger
from os import R_OK, W_OK, path, listdir, access, mkdir
from re import sub
from shutil import rmtree
from time import time
from zipfile import ZipFile
@@ -17,11 +18,10 @@ from enum import IntEnum
from typing import Dict, List, TypedDict
# Local modules
from .localplatform import chown, chmod
from .localplatform.localplatform import chown, chmod
from .loader import Loader, Plugins
from .helpers import get_ssl_context, download_remote_binary_to_path
from .settings import SettingsManager
from .injector import get_gamepadui_tab
logger = getLogger("Browser")
@@ -29,6 +29,8 @@ class PluginInstallType(IntEnum):
INSTALL = 0
REINSTALL = 1
UPDATE = 2
DOWNGRADE = 3
OVERWRITE = 4
class PluginInstallRequest(TypedDict):
name: str
@@ -73,6 +75,8 @@ class PluginBrowser:
packageJsonPath = path.join(pluginBasePath, 'package.json')
pluginBinPath = path.join(pluginBasePath, 'bin')
logger.debug(f"Checking package.json at {packageJsonPath}")
if access(packageJsonPath, R_OK):
with open(packageJsonPath, "r", encoding="utf-8") as f:
packageJson = json.load(f)
@@ -81,6 +85,7 @@ class PluginBrowser:
chmod(pluginBasePath, 777)
if access(pluginBasePath, W_OK):
if not path.exists(pluginBinPath):
logger.debug(f"Creating bin directory at {pluginBinPath}")
mkdir(pluginBinPath)
if not access(pluginBinPath, W_OK):
chmod(pluginBinPath, 777)
@@ -91,6 +96,7 @@ class PluginBrowser:
binName = remoteBinary["name"]
binURL = remoteBinary["url"]
binHash = remoteBinary["sha256hash"]
logger.info(f"Attempting to download {binName} from {binURL}")
if not await download_remote_binary_to_path(binURL, binHash, path.join(pluginBinPath, binName)):
rv = False
raise Exception(f"Error Downloading Remote Binary {binName}@{binURL} with hash {binHash} to {path.join(pluginBinPath, binName)}")
@@ -99,7 +105,7 @@ class PluginBrowser:
chmod(pluginBasePath, 555)
else:
rv = True
logger.debug(f"No Remote Binaries to Download")
logger.info(f"No Remote Binaries to Download")
except Exception as e:
rv = False
@@ -122,7 +128,6 @@ class PluginBrowser:
async def uninstall_plugin(self, name: str):
if self.loader.watcher:
self.loader.watcher.disabled = True
tab = await get_gamepadui_tab()
plugin_folder = self.find_plugin_folder(name)
assert plugin_folder is not None
plugin_dir = path.join(self.plugin_path, plugin_folder)
@@ -130,14 +135,13 @@ class PluginBrowser:
logger.info("uninstalling " + name)
logger.info(" at dir " + plugin_dir)
logger.debug("calling frontend unload for %s" % str(name))
res = await tab.evaluate_js(f"DeckyPluginLoader.unloadPlugin('{name}')")
logger.debug("result of unload from UI: %s", res)
await self.loader.ws.emit("loader/unload_plugin", name)
# plugins_snapshot = self.plugins.copy()
# snapshot_string = pformat(plugins_snapshot)
# logger.debug("current plugins: %s", snapshot_string)
if name in self.plugins:
logger.debug("Plugin %s was found", name)
self.plugins[name].stop()
await self.plugins[name].stop(uninstall=True)
logger.debug("Plugin %s was stopped", name)
del self.plugins[name]
logger.debug("Plugin %s was removed from the dictionary", name)
@@ -149,19 +153,19 @@ class PluginBrowser:
except Exception as e:
logger.error(f"Plugin {name} in {plugin_dir} was not uninstalled")
logger.error(f"Error at {str(e)}", exc_info=e)
if self.loader.watcher:
self.loader.watcher.disabled = False
finally:
if self.loader.watcher:
self.loader.watcher.disabled = False
async def _install(self, artifact: str, name: str, version: str, hash: str):
await self.loader.ws.emit("loader/plugin_download_start", name)
await self.loader.ws.emit("loader/plugin_download_info", 5, "Store.download_progress_info.start")
# Will be set later in code
res_zip = None
# Check if plugin is installed
# Check if plugin was already installed before this
isInstalled = False
# Preserve plugin order before removing plugin (uninstall alters the order and removes the plugin from the list)
current_plugin_order = self.settings.getSetting("pluginOrder")[:]
if self.loader.watcher:
self.loader.watcher.disabled = True
try:
pluginFolderPath = self.find_plugin_folder(name)
if pluginFolderPath:
@@ -169,15 +173,25 @@ class PluginBrowser:
except:
logger.error(f"Failed to determine if {name} is already installed, continuing anyway.")
# Preserve plugin order before removing plugin (uninstall alters the order and removes the plugin from the list)
current_plugin_order = self.settings.getSetting("pluginOrder")[:]
if self.loader.watcher:
self.loader.watcher.disabled = True
# Check if the file is a local file or a URL
if artifact.startswith("file://"):
logger.info(f"Installing {name} from local ZIP file (Version: {version})")
await self.loader.ws.emit("loader/plugin_download_info", 10, "Store.download_progress_info.open_zip")
res_zip = BytesIO(open(artifact[7:], "rb").read())
else:
logger.info(f"Installing {name} from URL (Version: {version})")
await self.loader.ws.emit("loader/plugin_download_info", 10, "Store.download_progress_info.download_zip")
async with ClientSession() as client:
logger.debug(f"Fetching {artifact}")
res = await client.get(artifact, ssl=get_ssl_context())
#TODO track progress of this download in chunks like with decky updates
#TODO but squish with min 15 and max 75
if res.status == 200:
logger.debug("Got 200. Reading...")
data = await res.read()
@@ -186,6 +200,47 @@ class PluginBrowser:
else:
logger.fatal(f"Could not fetch from URL. {await res.text()}")
await self.loader.ws.emit("loader/plugin_download_info", 70, "Store.download_progress_info.increment_count")
storeUrl = ""
match self.settings.getSetting("store", 0):
case 0: storeUrl = "https://plugins.deckbrew.xyz/plugins" # default
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}")
await self.loader.ws.emit("loader/plugin_download_info", 75, "Store.download_progress_info.parse_zip")
if res_zip and version == "dev":
with ZipFile(res_zip) as plugin_zip:
plugin_json_list = [file for file in plugin_zip.namelist() if file.endswith("/plugin.json") and file.count("/") == 1]
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:
plugin_json_file = plugin_json_list[0]
name = sub(r"/.+$", "", plugin_json_file)
try:
with plugin_zip.open(plugin_json_file) as f:
plugin_json_data = json.loads(f.read().decode('utf-8'))
plugin_name_from_plugin_json = plugin_json_data.get('name')
if plugin_name_from_plugin_json and plugin_name_from_plugin_json.strip():
logger.info(f"Extracted plugin name from {plugin_json_file}: {plugin_name_from_plugin_json}")
name = plugin_name_from_plugin_json
else:
logger.warning(f"Nonexistent or invalid 'name' key value in {plugin_json_file}. Falling back to extracting from path.")
except Exception as e:
logger.error(f"Failed to read or parse {plugin_json_file}: {str(e)}. Falling back to extracting from path.")
# Check to make sure we got the file
if res_zip is None:
logger.fatal(f"Could not fetch {artifact}")
@@ -193,12 +248,15 @@ class PluginBrowser:
# If plugin is installed, uninstall it
if isInstalled:
await self.loader.ws.emit("loader/plugin_download_info", 80, "Store.download_progress_info.uninstalling_previous")
try:
logger.debug("Uninstalling existing plugin...")
await self.uninstall_plugin(name)
except:
logger.error(f"Plugin {name} could not be uninstalled.")
await self.loader.ws.emit("loader/plugin_download_info", 90, "Store.download_progress_info.installing_plugin")
# Install the plugin
logger.debug("Unzipping...")
ret = self._unzip_to_plugin_dir(res_zip, name, hash)
@@ -206,43 +264,40 @@ class PluginBrowser:
plugin_folder = self.find_plugin_folder(name)
assert plugin_folder is not None
plugin_dir = path.join(self.plugin_path, plugin_folder)
await self.loader.ws.emit("loader/plugin_download_info", 95, "Store.download_progress_info.download_remote")
ret = await self._download_remote_binaries_for_plugin_with_name(plugin_dir)
if ret:
logger.info(f"Installed {name} (Version: {version})")
if name in self.loader.plugins:
self.loader.plugins[name].stop()
await self.loader.plugins[name].stop()
self.loader.plugins.pop(name, None)
await sleep(1)
if not isInstalled:
current_plugin_order = self.settings.getSetting("pluginOrder")
current_plugin_order.append(name)
self.settings.setSetting("pluginOrder", current_plugin_order)
logger.debug("Plugin %s was added to the pluginOrder setting", name)
self.loader.import_plugin(path.join(plugin_dir, "main.py"), plugin_folder)
self.settings.setSetting("pluginOrder", current_plugin_order)
logger.debug("Plugin %s was added to the pluginOrder setting", name)
await self.loader.import_plugin(path.join(plugin_dir, "main.py"), plugin_folder)
else:
logger.fatal(f"Failed Downloading Remote Binaries")
logger.error("Could not download remote binaries")
return
else:
logger.fatal(f"SHA-256 Mismatch!!!! {name} (Version: {version})")
if self.loader.watcher:
self.loader.watcher.disabled = False
await self.loader.ws.emit("loader/plugin_download_finish", name)
async def request_plugin_install(self, artifact: str, name: str, version: str, hash: str, install_type: PluginInstallType):
request_id = str(time())
self.install_requests[request_id] = PluginInstallContext(artifact, name, version, hash)
tab = await get_gamepadui_tab()
await tab.open_websocket()
await tab.evaluate_js(f"DeckyPluginLoader.addPluginInstallPrompt('{name}', '{version}', '{request_id}', '{hash}', {install_type})")
await self.loader.ws.emit("loader/add_plugin_install_prompt", name, version, request_id, hash, install_type)
async def request_multiple_plugin_installs(self, requests: List[PluginInstallRequest]):
request_id = str(time())
self.install_requests[request_id] = [PluginInstallContext(req['artifact'], req['name'], req['version'], req['hash']) for req in requests]
js_requests_parameter = ','.join([
f"{{ name: '{req['name']}', version: '{req['version']}', hash: '{req['hash']}', install_type: {req['install_type']}}}" for req in requests
])
tab = await get_gamepadui_tab()
await tab.open_websocket()
await tab.evaluate_js(f"DeckyPluginLoader.addMultiplePluginsInstallPrompt('{request_id}', [{js_requests_parameter}])")
await self.loader.ws.emit("loader/add_multiple_plugins_install_prompt", request_id, requests)
async def confirm_plugin_install(self, request_id: str):
requestOrRequests = self.install_requests.pop(request_id)
@@ -260,16 +315,20 @@ class PluginBrowser:
Args:
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", [])
if name in hidden_plugins:
hidden_plugins.remove(name)
self.settings.setSetting("hiddenPlugins", hidden_plugins)
plugin_order = self.settings.getSetting("pluginOrder", [])
if name in plugin_order:
plugin_order.remove(name)
self.settings.setSetting("pluginOrder", plugin_order)
logger.debug("Removed any settings for plugin %s", name)
+10
View File
@@ -0,0 +1,10 @@
from enum import IntEnum
class UserType(IntEnum):
HOST_USER = 1
EFFECTIVE_USER = 2
ROOT = 3
class PluginLoadType(IntEnum):
LEGACY_EVAL_IIFE = 0 # legacy, uses legacy serverAPI
ESMODULE_V1 = 1 # esmodule loading with modern @decky/backend apis
@@ -4,16 +4,18 @@ import uuid
import os
import subprocess
from hashlib import sha256
from io import BytesIO
import importlib.metadata
import certifi
from aiohttp.web import Request, Response, middleware
from aiohttp.typedefs import Handler
from aiohttp import ClientSession
from . import localplatform
from .customtypes import UserType
from .localplatform import localplatform
from .enums import UserType
from logging import getLogger
from packaging.version import Version
SSHD_UNIT = "sshd.service"
REMOTE_DEBUGGER_UNIT = "steam-web-debug-portforward.service"
# global vars
@@ -21,6 +23,7 @@ csrf_token = str(uuid.uuid4())
ssl_ctx = ssl.create_default_context(cafile=certifi.where())
assets_regex = re.compile("^/plugins/.*/assets/.*")
dist_regex = re.compile("^/plugins/.*/dist/.*")
frontend_regex = re.compile("^/frontend/.*")
logger = getLogger("Main")
@@ -32,10 +35,25 @@ def get_csrf_token():
@middleware
async def csrf_middleware(request: Request, handler: Handler):
if str(request.method) == "OPTIONS" or request.headers.get('Authentication') == csrf_token or str(request.rel_url) == "/auth/token" or str(request.rel_url).startswith("/plugins/load_main/") or str(request.rel_url).startswith("/static/") or str(request.rel_url).startswith("/legacy/") or str(request.rel_url).startswith("/steam_resource/") or str(request.rel_url).startswith("/frontend/") or assets_regex.match(str(request.rel_url)) or frontend_regex.match(str(request.rel_url)):
if str(request.method) == "OPTIONS" or \
request.headers.get('X-Decky-Auth') == csrf_token or \
str(request.rel_url) == "/auth/token" or \
str(request.rel_url).startswith("/plugins/load_main/") or \
str(request.rel_url).startswith("/static/") or \
str(request.rel_url).startswith("/steam_resource/") or \
str(request.rel_url).startswith("/frontend/") or \
str(request.rel_url.path) == "/fetch" or \
str(request.rel_url.path) == "/ws" or \
assets_regex.match(str(request.rel_url)) or \
dist_regex.match(str(request.rel_url)) or \
frontend_regex.match(str(request.rel_url)):
return await handler(request)
return Response(text='Forbidden', status=403)
def create_inject_script(script: str) -> str:
return "try{if (window.deckyHasLoaded){setTimeout(() => SteamClient.Browser.RestartJSContext(), 100)}else{window.deckyHasLoaded = true;(async()=>{try{await import('http://localhost:1337/frontend/%s?v=%s')}catch(e){console.error(e)};})();}}catch(e){console.error(e)}" % (script, get_loader_version(), )
# Get the default homebrew path unless a home_path is specified. home_path argument is deprecated
def get_homebrew_path() -> str:
return localplatform.get_unprivileged_path()
@@ -49,22 +67,41 @@ def mkdir_as_user(path: str):
# Fetches the version of loader
def get_loader_version() -> str:
try:
with open(os.path.join(os.getcwd(), ".loader.version"), "r", encoding="utf-8") as version_file:
return version_file.readline().strip()
# Normalize Python-style version to conform to Decky style
v = Version(importlib.metadata.version("decky_loader"))
if v.major == 0 and v.minor == 0 and v.micro == 0:
# We are probably running from source
return "dev"
version_str = f'v{v.major}.{v.minor}.{v.micro}'
if v.pre:
version_str += f'-pre{v.pre[1]}'
if v.post:
version_str += f'-dev{v.post}'
return version_str
except Exception as e:
logger.warn(f"Failed to execute get_loader_version(): {str(e)}")
logger.warning(f"Failed to execute get_loader_version(): {str(e)}")
return "unknown"
user_agent = f"Decky/{get_loader_version()} (https://decky.xyz)"
# returns the appropriate system python paths
def get_system_pythonpaths() -> list[str]:
try:
# run as normal normal user if on linux to also include user python paths
proc = subprocess.run(["python3" if localplatform.ON_LINUX else "python", "-c", "import sys; print('\\n'.join(x for x in sys.path if x))"],
# TODO make this less insane
capture_output=True, user=localplatform.localplatform._get_user_id() if localplatform.ON_LINUX else None, env={} if localplatform.ON_LINUX else None) # type: ignore
return [x.strip() for x in proc.stdout.decode().strip().split("\n")]
capture_output=True, user=localplatform.localplatform._get_user_id() if localplatform.ON_LINUX else None, env={} if localplatform.ON_LINUX else None) # pyright: ignore [reportPrivateUsage]
proc.check_returncode()
versions = [x.strip() for x in proc.stdout.decode().strip().split("\n")]
return [x for x in versions if x and not x.isspace()]
except Exception as e:
logger.warn(f"Failed to execute get_system_pythonpaths(): {str(e)}")
logger.warning(f"Failed to execute get_system_pythonpaths(): {str(e)}")
return []
# Download Remote Binaries to local Plugin
@@ -74,19 +111,20 @@ async def download_remote_binary_to_path(url: str, binHash: str, path: str) -> b
if os.access(os.path.dirname(path), os.W_OK):
async with ClientSession() as client:
res = await client.get(url, ssl=get_ssl_context())
if res.status == 200:
data = BytesIO(await res.read())
remoteHash = sha256(data.getbuffer()).hexdigest()
if binHash == remoteHash:
data.seek(0)
with open(path, 'wb') as f:
f.write(data.getbuffer())
rv = True
if res.status == 200:
logger.debug("Download attempt of URL: " + url)
data = await res.read()
remoteHash = sha256(data).hexdigest()
if binHash == remoteHash:
with open(path, 'wb') as f:
f.write(data)
rv = True
else:
raise Exception(f"Fatal Error: Hash Mismatch for remote binary {path}@{url}")
else:
raise Exception(f"Fatal Error: Hash Mismatch for remote binary {path}@{url}")
else:
rv = False
except:
rv = False
except Exception as e:
logger.error("Error during download " + str(e))
rv = False
return rv
@@ -33,7 +33,7 @@ class Tab:
async def open_websocket(self):
self.client = ClientSession()
self.websocket = await self.client.ws_connect(self.ws_url) # type: ignore
self.websocket = await self.client.ws_connect(self.ws_url)
async def close_websocket(self):
if self.websocket:
@@ -46,7 +46,7 @@ class Tab:
async for message in self.websocket:
data = message.json()
yield data
logger.warn(f"The Tab {self.title} socket has been disconnected while listening for messages.")
logger.warning(f"The Tab {self.title} socket has been disconnected while listening for messages.")
await self.close_websocket()
async def _send_devtools_cmd(self, dc: Dict[str, Any], receive: bool = True):
@@ -381,10 +381,10 @@ async def get_tabs() -> List[Tab]:
na = True
await sleep(5)
except ClientOSError:
logger.warn(f"The request to {BASE_ADDRESS}/json was reset")
logger.warning(f"The request to {BASE_ADDRESS}/json was reset")
await sleep(1)
except TimeoutError:
logger.warn(f"The request to {BASE_ADDRESS}/json timed out")
logger.warning(f"The request to {BASE_ADDRESS}/json timed out")
await sleep(1)
else:
break
@@ -412,10 +412,10 @@ async def get_tab_lambda(test: Callable[[Tab], bool]) -> Tab:
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
DO_NOT_CLOSE_URL = "Valve Steam Gamepad/default" # Steam Big Picture Mode tab
DO_NOT_CLOSE_URLS = ["Valve Steam Gamepad/default", "Valve%20Steam%20Gamepad"] # Steam Big Picture Mode tab
def tab_is_gamepadui(t: Tab) -> bool:
return "https://steamloopback.host/routes/" in t.url and t.title in SHARED_CTX_NAMES
return ("https://steamloopback.host/routes/" in t.url or "https://steamloopback.host/index.html" in t.url) and t.title in SHARED_CTX_NAMES
async def get_gamepadui_tab() -> Tab:
tabs = await get_tabs()
@@ -432,7 +432,7 @@ async def inject_to_tab(tab_name: str, js: str, run_async: bool = False):
async def close_old_tabs():
tabs = await get_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"))
await t.close()
await sleep(0.5)
@@ -1,30 +1,30 @@
from __future__ import annotations
from asyncio import AbstractEventLoop, Queue, sleep
from json.decoder import JSONDecodeError
from asyncio import AbstractEventLoop, Queue, gather, sleep
from logging import getLogger
from os import listdir, path
from pathlib import Path
from traceback import print_exc
from typing import Any, Tuple
from traceback import print_exc, format_exc
from typing import Any, Tuple, Dict, cast
from aiohttp import web
from os.path import exists
from watchdog.events import RegexMatchingEventHandler, DirCreatedEvent, DirModifiedEvent, FileCreatedEvent, FileModifiedEvent # type: ignore
from watchdog.observers import Observer # type: ignore
from watchdog.events import RegexMatchingEventHandler, FileSystemEvent
from watchdog.observers import Observer
from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, List
if TYPE_CHECKING:
from .main import PluginManager
from .injector import get_tab, get_gamepadui_tab
from .plugin import PluginWrapper
from .plugin.plugin import PluginWrapper
from .wsrouter import WSRouter
from .enums import PluginLoadType
Plugins = dict[str, PluginWrapper]
ReloadQueue = Queue[Tuple[str, str, bool | None] | Tuple[str, str]]
class FileChangeHandler(RegexMatchingEventHandler):
def __init__(self, queue: ReloadQueue, plugin_path: str) -> None:
super().__init__(regexes=[r'^.*?dist\/index\.js$', r'^.*?main\.py$']) # type: ignore
super().__init__(regexes=[r'^.*?dist\/index\.js$', r'^.*?main\.py$']) # pyright: ignore [reportUnknownMemberType]
self.logger = getLogger("file-watcher")
self.plugin_path = plugin_path
self.queue = queue
@@ -37,8 +37,8 @@ class FileChangeHandler(RegexMatchingEventHandler):
if exists(path.join(self.plugin_path, plugin_dir, "plugin.json")):
self.queue.put_nowait((path.join(self.plugin_path, plugin_dir, "main.py"), plugin_dir, True))
def on_created(self, event: DirCreatedEvent | FileCreatedEvent):
src_path = event.src_path
def on_created(self, event: FileSystemEvent):
src_path = cast(str, event.src_path) #type: ignore # this is the correct type for this is in later versions of watchdog
if "__pycache__" in src_path:
return
@@ -51,8 +51,8 @@ class FileChangeHandler(RegexMatchingEventHandler):
self.logger.debug(f"file created: {src_path}")
self.maybe_reload(src_path)
def on_modified(self, event: DirModifiedEvent | FileModifiedEvent):
src_path = event.src_path
def on_modified(self, event: FileSystemEvent):
src_path = cast(str, event.src_path) # type: ignore
if "__pycache__" in src_path:
return
@@ -66,9 +66,10 @@ class FileChangeHandler(RegexMatchingEventHandler):
self.maybe_reload(src_path)
class Loader:
def __init__(self, server_instance: PluginManager, plugin_path: str, loop: AbstractEventLoop, live_reload: bool = False) -> None:
def __init__(self, server_instance: PluginManager, ws: WSRouter, plugin_path: str, loop: AbstractEventLoop, live_reload: bool = False) -> None:
self.loop = loop
self.logger = getLogger("Loader")
self.ws = ws
self.plugin_path = plugin_path
self.logger.info(f"plugin_path: {self.plugin_path}")
self.plugins: Plugins = {}
@@ -80,49 +81,60 @@ class Loader:
if live_reload:
self.observer = Observer()
self.watcher = FileChangeHandler(self.reload_queue, plugin_path)
self.observer.schedule(self.watcher, self.plugin_path, recursive=True) # type: ignore
self.observer.schedule(self.watcher, self.plugin_path, recursive=True) # pyright: ignore [reportUnknownMemberType]
self.observer.start()
self.loop.create_task(self.enable_reload_wait())
server_instance.web_app.add_routes([
web.get("/frontend/{path:.*}", self.handle_frontend_assets),
web.get("/locales/{path:.*}", self.handle_frontend_locales),
web.get("/plugins", self.get_plugins),
web.get("/plugins/{plugin_name}/frontend_bundle", self.handle_frontend_bundle),
web.post("/plugins/{plugin_name}/methods/{method_name}", self.handle_plugin_method_call),
web.get("/plugins/{plugin_name}/dist/{path:.*}", self.handle_plugin_dist),
web.get("/plugins/{plugin_name}/assets/{path:.*}", self.handle_plugin_frontend_assets),
web.post("/plugins/{plugin_name}/reload", self.handle_backend_reload_request),
# The following is legacy plugin code.
web.get("/plugins/load_main/{name}", self.load_plugin_main_view),
web.get("/plugins/plugin_resource/{name}/{path:.+}", self.handle_sub_route),
web.get("/steam_resource/{path:.+}", self.get_steam_resource)
])
server_instance.ws.add_route("loader/get_plugins", self.get_plugins)
server_instance.ws.add_route("loader/reload_plugin", self.handle_plugin_backend_reload)
server_instance.ws.add_route("loader/call_plugin_method", self.handle_plugin_method_call)
server_instance.ws.add_route("loader/call_legacy_plugin_method", self.handle_plugin_method_call_legacy)
async def shutdown_plugins(self):
await gather(*[self.plugins[plugin_name].stop() for plugin_name in self.plugins])
async def enable_reload_wait(self):
if self.live_reload:
await sleep(10)
if self.watcher:
if self.watcher and self.live_reload:
self.logger.info("Hot reload enabled")
self.watcher.disabled = False
async def handle_frontend_assets(self, request: web.Request):
file = path.join(path.dirname(__file__), "..", "static", request.match_info["path"])
async def disable_reload(self):
if self.watcher:
self.watcher.disabled = True
self.live_reload = False
async def handle_frontend_assets(self, request: web.Request):
file = Path(__file__).parent.joinpath("static").joinpath(request.match_info["path"])
return web.FileResponse(file, headers={"Cache-Control": "no-cache"})
async def handle_frontend_locales(self, request: web.Request):
req_lang = request.match_info["path"]
file = path.join(path.dirname(__file__), "..", "locales", req_lang)
file = Path(__file__).parent.joinpath("locales").joinpath(req_lang)
if exists(file):
return web.FileResponse(file, headers={"Cache-Control": "no-cache", "Content-Type": "application/json"})
else:
self.logger.info(f"Language {req_lang} not available, returning an empty dictionary")
return web.json_response(data={}, headers={"Cache-Control": "no-cache"})
async def get_plugins(self, request: web.Request):
async def get_plugins(self):
plugins = list(self.plugins.values())
return web.json_response([{"name": str(i) if not i.legacy else "$LEGACY_"+str(i), "version": i.version} for i in plugins])
return [{"name": str(i), "version": i.version, "load_type": i.load_type} for i in plugins]
async def handle_plugin_dist(self, request: web.Request):
plugin = self.plugins[request.match_info["plugin_name"]]
file = path.join(self.plugin_path, plugin.plugin_directory, "dist", request.match_info["path"])
return web.FileResponse(file, headers={"Cache-Control": "no-cache"})
async def handle_plugin_frontend_assets(self, request: web.Request):
plugin = self.plugins[request.match_info["plugin_name"]]
@@ -136,103 +148,72 @@ class Loader:
with open(path.join(self.plugin_path, plugin.plugin_directory, "dist/index.js"), "r", encoding="utf-8") as bundle:
return web.Response(text=bundle.read(), content_type="application/javascript")
def import_plugin(self, file: str, plugin_directory: str, refresh: bool | None = False, batch: bool | None = False):
async def import_plugin(self, file: str, plugin_directory: str, refresh: bool | None = False, batch: bool | None = False):
try:
plugin = PluginWrapper(file, plugin_directory, self.plugin_path)
async def plugin_emitted_event(event: str, args: Any):
self.logger.debug(f"PLUGIN EMITTED EVENT: {event} with args {args}")
await self.ws.emit(f"loader/plugin_event", {"plugin": plugin.name, "event": event, "args": args})
plugin = PluginWrapper(file, plugin_directory, self.plugin_path, plugin_emitted_event)
if plugin.name in self.plugins:
if not "debug" in plugin.flags and refresh:
self.logger.info(f"Plugin {plugin.name} is already loaded and has requested to not be re-loaded")
return
else:
self.plugins[plugin.name].stop()
await self.plugins[plugin.name].stop()
self.plugins.pop(plugin.name, None)
if plugin.passive:
self.logger.info(f"Plugin {plugin.name} is passive")
self.plugins[plugin.name] = plugin.start()
self.logger.info(f"Loaded {plugin.name}")
if not batch:
self.loop.create_task(self.dispatch_plugin(plugin.name if not plugin.legacy else "$LEGACY_" + plugin.name, plugin.version))
self.loop.create_task(self.dispatch_plugin(plugin.name, plugin.version, plugin.load_type))
except Exception as e:
self.logger.error(f"Could not load {file}. {e}")
print_exc()
async def dispatch_plugin(self, name: str, version: str | None):
gpui_tab = await get_gamepadui_tab()
await gpui_tab.evaluate_js(f"window.importDeckyPlugin('{name}', '{version}')")
async def dispatch_plugin(self, name: str, version: str | None, load_type: int = PluginLoadType.ESMODULE_V1.value):
await self.ws.emit("loader/import_plugin", name, version, load_type)
def import_plugins(self):
async def import_plugins(self):
self.logger.info(f"import plugins from {self.plugin_path}")
directories = [i for i in listdir(self.plugin_path) if path.isdir(path.join(self.plugin_path, i)) and path.isfile(path.join(self.plugin_path, i, "plugin.json"))]
for directory in directories:
self.logger.info(f"found plugin: {directory}")
self.import_plugin(path.join(self.plugin_path, directory, "main.py"), directory, False, True)
await self.import_plugin(path.join(self.plugin_path, directory, "main.py"), directory, False, True)
async def handle_reloads(self):
while True:
args = await self.reload_queue.get()
self.import_plugin(*args) # type: ignore
await self.import_plugin(*args) # pyright: ignore [reportArgumentType]
async def handle_plugin_method_call(self, request: web.Request):
res = {}
plugin = self.plugins[request.match_info["plugin_name"]]
method_name = request.match_info["method_name"]
try:
method_info = await request.json()
args: Any = method_info["args"]
except JSONDecodeError:
args = {}
async def handle_plugin_method_call_legacy(self, plugin_name: str, method_name: str, kwargs: Dict[Any, Any]):
res: Dict[Any, Any] = {}
plugin = self.plugins[plugin_name]
try:
if method_name.startswith("_"):
raise RuntimeError("Tried to call private method")
res["result"] = await plugin.execute_method(method_name, args)
raise RuntimeError(f"Plugin {plugin.name} tried to call private method {method_name}")
res["result"] = await plugin.execute_legacy_method(method_name, kwargs)
res["success"] = True
except Exception as e:
res["result"] = str(e)
res["success"] = False
return web.json_response(res)
return res
"""
The following methods are used to load legacy plugins, which are considered deprecated.
I made the choice to re-add them so that the first iteration/version of the react loader
can work as a drop-in replacement for the stable branch of the PluginLoader, so that we
can introduce it more smoothly and give people the chance to sample the new features even
without plugin support. They will be removed once legacy plugins are no longer relevant.
"""
async def load_plugin_main_view(self, request: web.Request):
plugin = self.plugins[request.match_info["name"]]
with open(path.join(self.plugin_path, plugin.plugin_directory, plugin.main_view_html), "r", encoding="utf-8") as template:
template_data = template.read()
ret = f"""
<script src="/legacy/library.js"></script>
<script>window.plugin_name = '{plugin.name}' </script>
<base href="http://127.0.0.1:1337/plugins/plugin_resource/{plugin.name}/">
{template_data}
"""
return web.Response(text=ret, content_type="text/html")
async def handle_sub_route(self, request: web.Request):
plugin = self.plugins[request.match_info["name"]]
route_path = request.match_info["path"]
self.logger.info(path)
ret = ""
file_path = path.join(self.plugin_path, plugin.plugin_directory, route_path)
with open(file_path, "r", encoding="utf-8") as resource_data:
ret = resource_data.read()
return web.Response(text=ret)
async def get_steam_resource(self, request: web.Request):
tab = await get_tab("SP")
async def handle_plugin_method_call(self, plugin_name: str, method_name: str, *args: List[Any]):
plugin = self.plugins[plugin_name]
try:
return web.Response(text=await tab.get_steam_resource(f"https://steamloopback.host/{request.match_info['path']}"), content_type="text/html")
if method_name.startswith("_"):
raise RuntimeError(f"Plugin {plugin.name} tried to call private method {method_name}")
result = await plugin.execute_method(method_name, *args)
except Exception as e:
return web.Response(text=str(e), status=400)
self.logger.error(f"Method {method_name} of plugin {plugin.name} failed with the following exception:\n{format_exc()}")
raise e # throw again to pass the error to the frontend
return result
async def handle_backend_reload_request(self, request: web.Request):
plugin_name : str = request.match_info["plugin_name"]
async def handle_plugin_backend_reload(self, plugin_name: str):
plugin = self.plugins[plugin_name]
await self.reload_queue.put((plugin.file, plugin.plugin_directory))
return web.Response(status=200)
await self.reload_queue.put((plugin.file, plugin.plugin_directory))
+156
View File
@@ -0,0 +1,156 @@
{
"BranchSelect": {
"update_channel": {
"label": "قناة التحديثات",
"prerelease": "الإصدار التجريبي",
"stable": "إصدار مستقر",
"testing": "إصدار تحت الإختبار"
}
},
"Developer": {
"5secreload": "إعادة التحميل في 5 ثواني"
},
"DropdownMultiselect": {
"button": {
"back": "الخلف"
}
},
"FilePickerIndex": {
"file": {
"select": "إختر هذا الملف"
},
"files": {
"all_files": "جميع الملفات",
"file_type": "نوع الملف",
"show_hidden": "أظهر الملفات المخفية"
},
"filter": {
"created_asce": "المنشئة (الأقدم)",
"created_desc": "المنشئة (الأحدث)",
"modified_asce": "المعدلة (الأقدم)",
"modified_desc": "المعدلة (الأحدث)",
"name_asce": "أ-ي",
"name_desc": "أ-ي",
"size_asce": "الحجم ( الأصغر)",
"size_desc": "الحجم ( الأكبر)"
},
"folder": {
"label": "المجلد",
"select": "إستخدم هذا المجلد",
"show_more": "أظهر المزيد من الملفات"
}
},
"MultiplePluginsInstallModal": {
"confirm": "هل أنت متأكد من التعديلات التالية؟",
"description": {
"install": "تنصيب {{name}} {{version}}",
"reinstall": "إعادة تنصيب {{name}} {{version}}",
"update": "تحديث {{name}} إلى {{version}}"
},
"ok_button": {
"idle": "تأكيد"
},
"title": {
"install_few": "",
"install_many": "",
"install_one": "",
"install_other": "",
"install_two": "",
"install_zero": "",
"mixed_few": "",
"mixed_many": "",
"mixed_one": "",
"mixed_other": "",
"mixed_two": "",
"mixed_zero": "",
"reinstall_few": "",
"reinstall_many": "",
"reinstall_one": "",
"reinstall_other": "",
"reinstall_two": "",
"reinstall_zero": "",
"update_few": "",
"update_many": "",
"update_one": "",
"update_other": "",
"update_two": "",
"update_zero": ""
}
},
"PluginCard": {
"plugin_full_access": "هذه الإضافة لديها الصلاحية للوصول لمحتويات Steam Deck.",
"plugin_install": "تنصيب",
"plugin_no_desc": "لا يوجد وصف متاح.",
"plugin_version_label": "رقم إصدار الإضافة"
},
"PluginInstallModal": {
"install": {
"button_idle": "تنصيب",
"button_processing": "يتم التنصيب",
"desc": "هل أنت متأكد من رغبتك في تنصيب {{artifact}} {{version}}؟",
"title": "تنصيب {{artifact}}"
},
"reinstall": {
"button_idle": "إعادة تنصيب",
"button_processing": "تتم إعادة التنصيب",
"desc": "هل أنت متأكد من رغبتك في إعادة تنصيب {{artifact}} {{version}}؟",
"title": "إعادة تنصيب {{artifact}}"
},
"update": {
"button_idle": "تحديث",
"button_processing": "يتم التحديث",
"desc": "هل أنت متأكد من رغبتك في تحديث {{artifact}} {{version}}؟",
"title": "تحديث {{artifact}}"
}
},
"PluginListIndex": {
"hide": "إخفاء من قائمة الوصول السريع",
"reinstall": "إعادة التنصيب",
"reload": "إعادة التحميل",
"show": "إظهار في قائمة الوصول السريع",
"unfreeze": "السماح بالتحديثات",
"uninstall": "إزالة التنصيب",
"update_all_few": "",
"update_all_many": "",
"update_all_one": "",
"update_all_other": "",
"update_all_two": "",
"update_all_zero": "",
"update_to": "التحديث إلى {{name}}"
},
"PluginListLabel": {
"hidden": "مخفي من قائمة الوصول السريع"
},
"PluginLoader": {
"decky_title": "Decky",
"error": "خطا",
"plugin_uninstall": {
"button": "إزالة التنصيب",
"title": "إزالة التنصيب {{name}}"
},
"plugin_update_few": "",
"plugin_update_many": "",
"plugin_update_one": "",
"plugin_update_other": "",
"plugin_update_two": "",
"plugin_update_zero": ""
},
"PluginView": {
"hidden_few": "",
"hidden_many": "",
"hidden_one": "",
"hidden_other": "",
"hidden_two": "",
"hidden_zero": ""
},
"SettingsDeveloperIndex": {
"header": "أخرى",
"react_devtools": {
"ip_label": "عنوان الشبكة"
},
"third_party_plugins": {
"button_install": "تنصيب",
"button_zip": "تصفح"
}
}
}
@@ -1,10 +1,10 @@
{
"BranchSelect": {
"update_channel": {
"stable": "Стабилен",
"testing": "Тестване",
"label": "Канал за обновления",
"prerelease": "Предварителни издания"
"prerelease": "Предварителни издания",
"stable": "Стабилен",
"testing": "Тестване"
}
},
"Developer": {
@@ -19,9 +19,9 @@
},
"FilePickerError": {
"errors": {
"unknown": "Възникна неизвестна грешка. Грешката в суров вид е: {{raw_error}}",
"file_not_found": "Посоченият път е неправилен. Проверете го и го въведете правилно.",
"perm_denied": "Нямате достъп до посочената папка. Проверете дали потребителят (deck на Steam Deck) има съответните правомощия за достъп до посочената папка/файл."
"perm_denied": "Нямате достъп до посочената папка. Проверете дали потребителят (deck на Steam Deck) има съответните правомощия за достъп до посочената папка/файл.",
"unknown": "Възникна неизвестна грешка. Грешката в суров вид е: {{raw_error}}"
}
},
"FilePickerIndex": {
@@ -45,11 +45,12 @@
},
"folder": {
"label": "Папка",
"show_more": "Показване на още файлове",
"select": "Използване на тази папка"
"select": "Използване на тази папка",
"show_more": "Показване на още файлове"
}
},
"MultiplePluginsInstallModal": {
"confirm": "Наистина ли искате да направите следните промени?",
"description": {
"install": "Инсталиране на {{name}} {{version}}",
"reinstall": "Преинсталиране на {{name}} {{version}}",
@@ -60,16 +61,15 @@
"loading": "В процес на работа"
},
"title": {
"mixed_one": "Промяна на {{count}} добавка",
"mixed_other": "Промяна на {{count}} добавки",
"update_one": "Обновяване на 1 добавка",
"update_other": "Обновяване на {{count}} добавки",
"install_one": "Инсталиране на 1 добавка",
"install_other": "Инсталиране на {{count}} добавки",
"mixed_one": "Промяна на {{count}} добавка",
"mixed_other": "Промяна на {{count}} добавки",
"reinstall_one": "Преинсталиране на 1 добавка",
"reinstall_other": "Преинсталиране на {{count}} добавки"
},
"confirm": "Наистина ли искате да направите следните промени?"
"reinstall_other": "Преинсталиране на {{count}} добавки",
"update_one": "Обновяване на 1 добавка",
"update_other": "Обновяване на {{count}} добавки"
}
},
"PluginCard": {
"plugin_full_access": "Тази добавка има пълен достъп до Вашия Steam Deck.",
@@ -80,10 +80,11 @@
"PluginInstallModal": {
"install": {
"button_idle": "Инсталиране",
"button_processing": "Инсталиране",
"desc": "Наистина ли искате да инсталирате {{artifact}} {{version}}?",
"title": "Инсталиране на {{artifact}}",
"button_processing": "Инсталиране"
"title": "Инсталиране на {{artifact}}"
},
"no_hash": "Тази добавка няма хеш. Инсталирате я на свой собствен риск.",
"reinstall": {
"button_idle": "Преинсталиране",
"button_processing": "Преинсталиране",
@@ -92,30 +93,33 @@
},
"update": {
"button_idle": "Обновяване",
"title": "Обновяване на {{artifact}}",
"button_processing": "Обновяване",
"desc": "Наистина ли искате да обновите {{artifact}} {{version}}?"
},
"no_hash": "Тази добавка няма хеш. Инсталирате я на свой собствен риск."
"desc": "Наистина ли искате да обновите {{artifact}} {{version}}?",
"title": "Обновяване на {{artifact}}"
}
},
"PluginListIndex": {
"freeze": "Замразяване на актуализациите",
"hide": "Бърз достъп: Скриване",
"no_plugin": "Няма инсталирани добавки!",
"plugin_actions": "Действия с добавката",
"reinstall": "Преинсталиране",
"uninstall": "Деинсталиране",
"update_to": "Обновяване до {{name}}",
"reload": "Презареждане",
"show": "Бърз достъп: Показване",
"unfreeze": "Разрешаване на актуализациите",
"uninstall": "Деинсталиране",
"update_all_one": "Обновяване на 1 добавка",
"update_all_other": "Обновяване на {{count}} добавки"
"update_all_other": "Обновяване на {{count}} добавки",
"update_to": "Обновяване до {{name}}"
},
"PluginListLabel": {
"hidden": "Скрито от менюто за бърз достъп"
},
"PluginLoader": {
"decky_title": "Decky",
"decky_update_available": "Има налично обновление до {{tag_name}}!",
"error": "Грешка",
"plugin_error_uninstall": "Зареждането на {{name}} предизвика грешка, както се вижда по-горе. Това обикновено означава, че добавката изисква обновяване на новата версия на SteamUI. Проверете дали има обновление или изберете да я премахнете в настройките на Decky, в раздела с добавките.",
"plugin_load_error": {
"message": "Грешка при зареждането на добавката {{name}}",
"toast": "Грешка при зареждането на {{name}}"
@@ -126,9 +130,11 @@
"title": "Деинсталиране на {{name}}"
},
"plugin_update_one": "Има налично обновление за 1 добавка!",
"plugin_update_other": "Има налични обновления за {{count}} добавки!",
"decky_update_available": "Има налично обновление до {{tag_name}}!",
"plugin_error_uninstall": "Зареждането на {{name}} предизвика грешка, както се вижда по-горе. Това обикновено означава, че добавката изисква обновяване на новата версия на SteamUI. Проверете дали има обновление или изберете да я премахнете в настройките на Decky, в раздела с добавките."
"plugin_update_other": "Има налични обновления за {{count}} добавки!"
},
"PluginView": {
"hidden_one": "1 добавка е скрита от този списък",
"hidden_other": "{{count}} добавки са скрити от този списък"
},
"RemoteDebugging": {
"remote_cef": {
@@ -139,27 +145,27 @@
"SettingsDeveloperIndex": {
"cef_console": {
"button": "Отваряне на конзолата",
"label": "Конзола на CEF",
"desc": "Отваря конзолата на CEF. Това има смисъл единствено за дебъгване. Нещата тук може да са опасни и трябва да бъдат използвани само ако Вие сте разработчик на добавка, или получавате насоки от такъв."
"desc": "Отваря конзолата на CEF. Това има смисъл единствено за дебъгване. Нещата тук може да са опасни и трябва да бъдат използвани само ако Вие сте разработчик на добавка, или получавате насоки от такъв.",
"label": "Конзола на CEF"
},
"header": "Други",
"react_devtools": {
"desc": "Включва свързването към компютър, на който работи React DevTools. Промяната на тази настройка ще презареди Steam. Задайте IP адреса преди да включите това.",
"ip_label": "IP",
"label": "Включване на React DevTools",
"desc": "Включва свързването към компютър, на който работи React DevTools. Промяната на тази настройка ще презареди Steam. Задайте IP адреса преди да включите това."
"label": "Включване на React DevTools"
},
"third_party_plugins": {
"button_install": "Инсталиране",
"button_zip": "Разглеждане",
"header": "Добавки от външен източник",
"label_desc": "Адрес",
"label_zip": "Инсталиране на добавка от файл ZIP",
"label_url": "Инсталиране на добавка от адрес в Интернет"
"label_url": "Инсталиране на добавка от адрес в Интернет",
"label_zip": "Инсталиране на добавка от файл ZIP"
},
"valve_internal": {
"desc1": "Включва вътрешното меню за разработчици на Valve.",
"desc2": "Не пипайте нищо в това меню, освен ако не знаете какво правите.",
"label": "Включване на вътрешното меню на Valve",
"desc1": "Включва вътрешното меню за разработчици на Valve."
"label": "Включване на вътрешното меню на Valve"
}
},
"SettingsGeneralIndex": {
@@ -167,6 +173,9 @@
"decky_version": "Версия на Decky",
"header": "Относно"
},
"beta": {
"header": "Участие в бета-версии"
},
"developer_mode": {
"label": "Режим за разработчици"
},
@@ -180,20 +189,27 @@
},
"updates": {
"header": "Обновления"
},
"beta": {
"header": "Участие в бета-версии"
}
},
"SettingsIndex": {
"developer_title": "Разработчик",
"general_title": "Общи",
"plugins_title": "Добавки"
"plugins_title": "Добавки",
"testing_title": "Тестване"
},
"Store": {
"download_progress_info": {
"download_zip": "Изтегляне на плъгина",
"increment_count": "Увеличаване на броя изтегляния",
"installing_plugin": "Инсталиране на плъгина",
"open_zip": "Отваряне на zip файла",
"parse_zip": "Разглеждане на zip файла",
"start": "Иницииране",
"uninstalling_previous": "Деинсталиране на предишното копие"
},
"store_contrib": {
"label": "Допринасяне",
"desc": "Ако искате да допринесете към магазина за добавки на Decky, разгледайте хранилището SteamDeckHomebrew/decky-plugin-template в GitHub. Може да намерите информация относно разработката и разпространението във файла README."
"desc": "Ако искате да допринесете към магазина за добавки на Decky, разгледайте хранилището SteamDeckHomebrew/decky-plugin-template в GitHub. Може да намерите информация относно разработката и разпространението във файла README.",
"label": "Допринасяне"
},
"store_filter": {
"label": "Филтър",
@@ -207,16 +223,24 @@
"label_def": "Последно обновление (първо най-новите)"
},
"store_source": {
"label": "Изходен код",
"desc": "Целият изходен код е наличен в хранилището SteamDeckHomebrew/decky-plugin-database в GitHub."
"desc": "Целият изходен код е наличен в хранилището SteamDeckHomebrew/decky-plugin-database в GitHub.",
"label": "Изходен код"
},
"store_tabs": {
"about": "Относно",
"alph_asce": "По азбучен ред (Я -> А)",
"alph_desc": "По азбучен ред (А -> Я)",
"date_asce": "Най-старият първи",
"date_desc": "Най-новият първи",
"downloads_asce": "Най-малко изтеглени първи",
"downloads_desc": "Най-изтеглени първи",
"title": "Разглеждане"
},
"store_testing_cta": "Помислете дали искате да тествате новите добавки, за да помогнете на екипа на Decky Loader!"
"store_testing_cta": "Помислете дали искате да тествате новите добавки, за да помогнете на екипа на Decky Loader!",
"store_testing_warning": {
"desc": "Можете да използвате този канал на магазина, за да тествате най-новите версии на плъгините. Не забравяйте да оставите обратна връзка в GitHub, за да може плъгинът да бъде актуализиран за всички потребители.",
"label": "Добре дошли в канала на магазина за тестване"
}
},
"StoreSelect": {
"custom_store": {
@@ -230,23 +254,30 @@
"testing": "Тестване"
}
},
"Testing": {
"download": "Изтегляне",
"error": "Грешка при инсталирането на PR",
"header": "Следните версии на Decky Loader са създадени от отворени заявки за изтегляне от трети страни. Екипът на Decky Loader не е проверявал тяхната функционалност или сигурност и е възможно те да са остарели.",
"loading": "Зареждане на отворени заявки за изтегляне...",
"start_download_toast": "Изтегляне на PR #{{id}}"
},
"TitleView": {
"decky_store_desc": "Отворете Decky Store",
"settings_desc": "Отворете настройките на Decky"
},
"Updater": {
"decky_updates": "Обновления на Decky",
"no_patch_notes_desc": "няма бележки за промените в тази версия",
"patch_notes_desc": "Бележки за промените",
"updates": {
"check_button": "Проверка за обновления",
"checking": "Проверяване",
"cur_version": "Текуща версия: {{ver}}",
"install_button": "Инсталиране на обновлението",
"label": "Обновления",
"lat_version": "Използвате най-новата версия: {{ver}}",
"reloading": "Презареждане",
"updating": "Обновяване",
"install_button": "Инсталиране на обновлението"
},
"no_patch_notes_desc": "няма бележки за промените в тази версия"
},
"PluginView": {
"hidden_one": "1 добавка е скрита от този списък",
"hidden_other": "{{count}} добавки са скрити от този списък"
"updating": "Обновяване"
}
}
}
@@ -8,15 +8,30 @@
}
},
"Developer": {
"5secreload": "Znovu načtení za 5 vteřin",
"disabling": "Vypínám React DevTools",
"enabling": "Zapínám React DevTools",
"5secreload": "Znovu načtení za 5 vteřin"
"enabling": "Zapínám React DevTools"
},
"DropdownMultiselect": {
"button": {
"back": "Zpět"
}
},
"FilePickerError": {
"errors": {
"file_not_found": "Zadaná cesta není platná. Zkontrolujte ji a zadejte znovu správně.",
"perm_denied": "Nemáte přístup k zadanému adresáři. Zkontrolujte, zda jako uživatel (deck na Steam Decku) máte odpovídající oprávnění pro přístup k dané složce/souboru.",
"unknown": "Nastala neznámá chyba. Nezpracovaná chyba je: {{raw_error}}"
}
},
"FilePickerIndex": {
"folder": {
"select": "Použít tuto složku",
"label": "Složka",
"show_more": "Zobrazit více souborů"
"file": {
"select": "Vybrat tento soubor"
},
"files": {
"all_files": "Všechny soubory",
"file_type": "Typ souboru",
"show_hidden": "Zobrazit skryté soubory"
},
"filter": {
"created_asce": "Vytvořeno (Nejstarší)",
@@ -28,93 +43,113 @@
"size_asce": "Velikost (Nejmenší)",
"size_desc": "Velikost (Největší)"
},
"files": {
"show_hidden": "Zobrazit skryté soubory",
"all_files": "Všechny soubory",
"file_type": "Typ souboru"
},
"file": {
"select": "Vybrat tento soubor"
}
},
"PluginView": {
"hidden_one": "1 plugin je v tomto seznamu skrytý",
"hidden_few": "{{count}} pluginů je v tomto seznamu skryto",
"hidden_other": "{{count}} pluginů je v tomto seznamu skryto"
},
"PluginListLabel": {
"hidden": "Skryto z nabídky rychlého přístupu"
},
"PluginCard": {
"plugin_full_access": "Tento plugin má plný přístup k vašemu Steam Decku.",
"plugin_install": "Instalovat",
"plugin_no_desc": "Nebyl uveden žádný popis.",
"plugin_version_label": "Verze pluginu"
},
"PluginInstallModal": {
"install": {
"button_idle": "Instalovat",
"button_processing": "Instalování",
"title": "Instalovat {{artifact}}",
"desc": "Jste si jisti, že chcete nainstalovat {{artifact}} {{version}}?"
},
"no_hash": "Tento plugin nemá hash, instalujete jej na vlastní nebezpečí.",
"reinstall": {
"button_idle": "Přeinstalovat",
"button_processing": "Přeinstalování",
"title": "Přeinstalovat {{artifact}}",
"desc": "Jste si jisti, že chcete přeinstalovat {{artifact}} {{version}}?"
},
"update": {
"button_idle": "Aktualizovat",
"button_processing": "Aktualizování",
"desc": "Jste si jisti, že chcete aktualizovat {{artifact}} {{version}}?",
"title": "Aktualizovat {{artifact}}"
"folder": {
"label": "Složka",
"select": "Použít tuto složku",
"show_more": "Zobrazit více souborů"
}
},
"MultiplePluginsInstallModal": {
"title": {
"mixed_one": "Upravit {{count}} plugin",
"mixed_few": "Upravit {{count}} pluginů",
"mixed_other": "Upravit {{count}} pluginů",
"reinstall_one": "Přeinstalovat 1 plugin",
"reinstall_few": "Přeinstalovat {{count}} pluginů",
"reinstall_other": "Přeinstalovat {{count}} pluginů",
"install_one": "Instalovat 1 plugin",
"install_few": "Instalovat {{count}} pluginů",
"install_other": "Instalovat {{count}} pluginů",
"update_one": "Aktualizovat 1 plugin",
"update_few": "Aktualizovat {{count}} pluginů",
"update_other": "Aktualizovat {{count}} pluginů"
"confirm": "Jste si jisti, že chcete udělat následující úpravy?",
"description": {
"downgrade": "Downgradovat {{name}} na verzi {{version}}",
"install": "Instalovat {{name}} {{version}}",
"overwrite": "Přepsat {{name}} verzí {{version}}",
"reinstall": "Přeinstalovat {{name}} {{version}}",
"update": "Aktualizovat {{name}} na {{version}}"
},
"ok_button": {
"idle": "Potvrdit",
"loading": "Probíhá"
},
"description": {
"install": "Instalovat {{name}} {{version}}",
"update": "Aktualizovat {{name}} na {{version}}",
"reinstall": "Přeinstalovat {{name}} {{version}}"
"title": {
"downgrade_few": "Downgradovat {{count}} pluginy",
"downgrade_one": "Downgradovat {{count}} plugin",
"downgrade_other": "Downgradovat {{count}} pluginů",
"install_few": "Instalovat {{count}} pluginů",
"install_one": "Instalovat 1 plugin",
"install_other": "Instalovat {{count}} pluginů",
"mixed_few": "Upravit {{count}} pluginů",
"mixed_one": "Upravit {{count}} plugin",
"mixed_other": "Upravit {{count}} pluginů",
"overwrite_few": "Přepsat {{count}} pluginy",
"overwrite_one": "Přepsat {{count}} plugin",
"overwrite_other": "Přepsat {{count}} pluginů",
"reinstall_few": "Přeinstalovat {{count}} pluginů",
"reinstall_one": "Přeinstalovat 1 plugin",
"reinstall_other": "Přeinstalovat {{count}} pluginů",
"update_few": "Aktualizovat {{count}} pluginů",
"update_one": "Aktualizovat 1 plugin",
"update_other": "Aktualizovat {{count}} pluginů"
}
},
"PluginCard": {
"plugin_downgrade": "Downgrade",
"plugin_full_access": "Tento plugin má plný přístup k vašemu Steam Decku.",
"plugin_install": "Instalovat",
"plugin_no_desc": "Nebyl uveden žádný popis.",
"plugin_overwrite": "Přepsat",
"plugin_reinstall": "Přeinstalovat",
"plugin_update": "Aktualizovat",
"plugin_version_label": "Verze pluginu"
},
"PluginInstallModal": {
"downgrade": {
"button_idle": "Downgrade",
"button_processing": "Downgradování",
"desc": "Opravdu chcete downgradovat {{artifact}} na verzi {{version}}?",
"title": "Downgradovat {{artifact}}"
},
"confirm": "Jste si jisti, že chcete udělat následující úpravy?"
"install": {
"button_idle": "Instalovat",
"button_processing": "Instalování",
"desc": "Jste si jisti, že chcete nainstalovat {{artifact}} {{version}}?",
"title": "Instalovat {{artifact}}"
},
"no_hash": "Tento plugin nemá hash, instalujete jej na vlastní nebezpečí.",
"not_installed": "(nenainstalováno)",
"overwrite": {
"button_idle": "Přepsat",
"button_processing": "Přepisování",
"desc": "Opravdu chcete přepsat {{artifact}} na verzi {{version}}?",
"title": "Přepsat {{artifact}}"
},
"reinstall": {
"button_idle": "Přeinstalovat",
"button_processing": "Přeinstalování",
"desc": "Jste si jisti, že chcete přeinstalovat {{artifact}} {{version}}?",
"title": "Přeinstalovat {{artifact}}"
},
"update": {
"button_idle": "Aktualizovat",
"button_processing": "Aktualizování",
"desc": "Opravdu chcete aktualizovat {{artifact}} na verzi {{version}}?",
"title": "Aktualizovat {{artifact}}"
}
},
"PluginListIndex": {
"freeze": "Pozastavit aktualizace",
"hide": "Rychlý přístup: Skrýt",
"no_plugin": "Nejsou nainstalovány žádné pluginy!",
"plugin_actions": "Akce pluginu",
"reinstall": "Přeinstalovat",
"reload": "Znovu načíst",
"uninstall": "Odinstalovat",
"update_to": "Aktualizovat na {{name}}",
"show": "Rychlý přístup: Zobrazit",
"hide": "Rychlý přístup: Skrýt",
"update_all_one": "Aktualizovat 1 plugin",
"unfreeze": "Povolit aktualizace",
"uninstall": "Odinstalovat",
"update_all_few": "Aktualizovat {{count}} pluginů",
"update_all_other": "Aktualizovat {{count}} pluginů"
"update_all_one": "Aktualizovat 1 plugin",
"update_all_other": "Aktualizovat {{count}} pluginů",
"update_to": "Aktualizovat na {{name}}"
},
"PluginListLabel": {
"hidden": "Skryto z nabídky rychlého přístupu"
},
"PluginLoader": {
"decky_title": "Decky",
"decky_update_available": "Aktualizace na {{tag_name}} dostupná!",
"error": "Chyba",
"plugin_error_uninstall": "Načítání {{name}} způsobilo chybu uvedenou výše. To obvykle znamená, že plugin vyžaduje aktualizaci SteamUI. Zkontrolujte, zda je aktualizace k dispozici, nebo zvažte odstranění pluginu v nastavení Decky v sekci Pluginy.",
"plugin_load_error": {
"message": "Chyba při načítání pluginu {{name}}",
"toast": "Chyba při načítání {{name}}"
@@ -124,16 +159,26 @@
"desc": "Opravdu chcete odinstalovat {{name}}?",
"title": "Odinstalovat {{name}}"
},
"plugin_update_one": "Je dostupná aktualizace pro 1 plugin!",
"plugin_update_few": "Jsou dostupné aktualizace pro {{count}} pluginů!",
"plugin_update_other": "Jsou dostupné aktualizace pro {{count}} pluginů!",
"plugin_error_uninstall": "Načítání {{name}} způsobilo chybu uvedenou výše. To obvykle znamená, že plugin vyžaduje aktualizaci SteamUI. Zkontrolujte, zda je aktualizace k dispozici, nebo zvažte odstranění pluginu v nastavení Decky v sekci Pluginy."
"plugin_update_one": "Je dostupná aktualizace pro 1 plugin!",
"plugin_update_other": "Jsou dostupné aktualizace pro {{count}} pluginů!"
},
"PluginView": {
"hidden_few": "{{count}} pluginů je v tomto seznamu skryto",
"hidden_one": "1 plugin je v tomto seznamu skrytý",
"hidden_other": "{{count}} pluginů je v tomto seznamu skryto"
},
"RemoteDebugging": {
"remote_cef": {
"desc": "Umožní neověřený přístup k CEF ladění komukoli ve vaší síti",
"label": "Povolit vzdálené CEF ladění"
}
},
"SettingsDeveloperIndex": {
"cef_console": {
"button": "Otevřít konzoli",
"label": "CEF konzole",
"desc": "Otevře CEF konzoli. Užitečné pouze pro účely ladění. Věci zde jsou potenciálně nebezpečné a měly by být používány pouze v případě, že jste vývojář pluginů, nebo vás sem nějaký nasměroval."
"desc": "Otevře CEF konzoli. Užitečné pouze pro účely ladění. Věci zde jsou potenciálně nebezpečné a měly by být používány pouze v případě, že jste vývojář pluginů, nebo vás sem nějaký nasměroval.",
"label": "CEF konzole"
},
"header": "Ostatní",
"react_devtools": {
@@ -155,12 +200,6 @@
"label": "Zapnout Valve Internal"
}
},
"RemoteDebugging": {
"remote_cef": {
"label": "Povolit vzdálené CEF ladění",
"desc": "Umožní neověřený přístup k CEF ladění komukoli ve vaší síti"
}
},
"SettingsGeneralIndex": {
"about": {
"decky_version": "Decky verze",
@@ -172,27 +211,38 @@
"developer_mode": {
"label": "Vývojářský režim"
},
"notifications": {
"decky_updates_label": "Dostupná aktualizace Decky",
"header": "Notifikace",
"plugin_updates_label": "Dostupná aktualizace pluginu"
},
"other": {
"header": "Ostatní"
},
"updates": {
"header": "Aktualizace"
},
"notifications": {
"decky_updates_label": "Dostupná aktualizace Decky",
"header": "Notifikace",
"plugin_updates_label": "Dostupná aktualizace pluginu"
}
},
"SettingsIndex": {
"developer_title": "Vývojář",
"general_title": "Obecné",
"plugins_title": "Pluginy"
"plugins_title": "Pluginy",
"testing_title": "Testování"
},
"Store": {
"download_progress_info": {
"download_remote": "Stahování externích knihoven",
"download_zip": "Stahování pluginu",
"increment_count": "Zvyšující se počet stahování",
"installing_plugin": "Instalování pluginu",
"open_zip": "Otevírání ZIP souboru",
"parse_zip": "Analýza ZIP souboru",
"start": "Inicializace",
"uninstalling_previous": "Odinstalování předchozí kopie"
},
"store_contrib": {
"label": "Přispívání",
"desc": "Pokud byste chtěli přispět do obchodu Decky Plugin Store, podívejte se na repozitář SteamDeckHomebrew/decky-plugin-template na GitHubu. Informace o vývoji a distribuci jsou k dispozici v README."
"desc": "Pokud byste chtěli přispět do obchodu Decky Plugin Store, podívejte se na repozitář SteamDeckHomebrew/decky-plugin-template na GitHubu. Informace o vývoji a distribuci jsou k dispozici v README.",
"label": "Přispívání"
},
"store_filter": {
"label": "Filtr",
@@ -213,6 +263,10 @@
"about": "O Decky Plugin Store",
"alph_asce": "Abecedně (Z do A)",
"alph_desc": "Abecedně (A do Z)",
"date_asce": "Nejstarší",
"date_desc": "Nejnovější",
"downloads_asce": "Nejméně stažené",
"downloads_desc": "Nejvíce stažené",
"title": "Procházet"
},
"store_testing_cta": "Zvažte prosím testování nových pluginů, pomůžete tím týmu Decky Loader!",
@@ -233,35 +287,30 @@
"testing": "Testování"
}
},
"Testing": {
"download": "Stáhnout",
"error": "Chyba při instalaci PR",
"header": "Následující verze Decky Loaderu jsou vytvořeny z otevřených Pull Requestů třetích stran. Tým Decky Loaderu neověřil jejich funkčnost ani zabezpečení a mohou být zastaralé.",
"loading": "Načítání Pull Requestů...",
"start_download_toast": "Stahování PR #{{id}}"
},
"TitleView": {
"decky_store_desc": "Otevřít obchod Decky",
"settings_desc": "Otevřít nastavení Decky"
},
"Updater": {
"decky_updates": "Aktualizace Decky",
"no_patch_notes_desc": "žádné poznámky pro tuto verzi",
"patch_notes_desc": "Poznámky k verzi",
"updates": {
"lat_version": "Aktuální: běží na verzi {{ver}}",
"reloading": "Znovu načítání",
"updating": "Aktualizování",
"check_button": "Zkontrolovat aktualizace",
"checking": "Kontrolování",
"cur_version": "Aktuální verze: {{ver}}",
"install_button": "Instalovat aktualizaci",
"label": "Aktualizace"
},
"decky_updates": "Aktualizace Decky",
"patch_notes_desc": "Poznámky k verzi",
"no_patch_notes_desc": "žádné poznámky pro tuto verzi"
},
"DropdownMultiselect": {
"button": {
"back": "Zpět"
"label": "Aktualizace",
"lat_version": "Aktuální: běží na verzi {{ver}}",
"reloading": "Znovu načítání",
"updating": "Aktualizování"
}
},
"FilePickerError": {
"errors": {
"file_not_found": "Zadaná cesta není platná. Zkontrolujte ji a zadejte znovu správně.",
"unknown": "Nastala neznámá chyba. Nezpracovaná chyba je: {{raw_error}}",
"perm_denied": "Nemáte přístup k zadanému adresáři. Zkontrolujte, zda jako uživatel (deck na Steam Decku) máte odpovídající oprávnění pro přístup k dané složce/souboru."
}
},
"TitleView": {
"settings_desc": "Otevřít nastavení Decky",
"decky_store_desc": "Otevřít obchod Decky"
}
}
+283
View File
@@ -0,0 +1,283 @@
{
"BranchSelect": {
"update_channel": {
"label": "Updatekanal",
"prerelease": "Vorabveröffentlichung",
"stable": "Standard",
"testing": "Test"
}
},
"Developer": {
"5secreload": "Neu laden in 5 Sekunden",
"disabling": "Deaktiviere React DevTools",
"enabling": "Aktiviere React DevTools"
},
"DropdownMultiselect": {
"button": {
"back": "Zurück"
}
},
"FilePickerError": {
"errors": {
"file_not_found": "Der Pfad ist ungültig. Bitte prüfen und erneut eingeben.",
"perm_denied": "Kein Zugriff auf den angegebenen Dateipfad. Bitte prüfen, ob der Nutzer (deck auf dem Steam Deck) die entsprechenden Zugriffsrechte auf den angegebenen Ordner/die angegebene Datei hat.",
"unknown": "Ein unbekannter Fehler ist aufgetreten. Die ursprüngliche Fehlermeldung ist: {{raw_error}}"
}
},
"FilePickerIndex": {
"file": {
"select": "Diese Datei auswählen"
},
"files": {
"all_files": "Alle Dateien",
"file_type": "Dateityp",
"show_hidden": "Versteckte Dateien anzeigen"
},
"filter": {
"created_asce": "Erstellt (Älteste)",
"created_desc": "Erstellt (Neuste)",
"modified_asce": "Geändert (Älteste)",
"modified_desc": "Geändert (Neuste)",
"name_asce": "Z-A",
"name_desc": "A-Z",
"size_asce": "Größe (Kleinste)",
"size_desc": "Größe (Größte)"
},
"folder": {
"label": "Ordner",
"select": "Diesen Ordner verwenden",
"show_more": "Mehr Dateien anzeigen"
}
},
"MultiplePluginsInstallModal": {
"confirm": "Bist du sicher, dass du die folgenden Änderungen vornehmen möchtest?",
"description": {
"install": "{{name}} {{version}} installieren",
"reinstall": "{{name}} {{version}} neu installieren",
"update": "{{name}} auf {{version}} aktualisieren"
},
"ok_button": {
"idle": "Bestätigen",
"loading": "An der Arbeit"
},
"title": {
"install_one": "{{count}} Plugin installieren",
"install_other": "{{count}} Plugins installieren",
"mixed_one": "{{count}} Plugin bearbeiten",
"mixed_other": "{{count}} Plugins bearbeiten",
"reinstall_one": "{{count}} Plugin neu installieren",
"reinstall_other": "{{count}} Plugins neu installieren",
"update_one": "{{count}} Plugin aktualisieren",
"update_other": "{{count}} Plugins aktualisieren"
}
},
"PluginCard": {
"plugin_full_access": "Diese Erweiterung hat uneingeschränkten Zugriff auf dein Steam Deck.",
"plugin_install": "Installieren",
"plugin_no_desc": "Keine Beschreibung angegeben.",
"plugin_version_label": "Erweiterungs Version"
},
"PluginInstallModal": {
"install": {
"button_idle": "Installieren",
"button_processing": "Wird installiert",
"desc": "Bist du dir sicher, dass du {{artifact}} {{version}} installieren willst?",
"title": "Installiere {{artifact}}"
},
"no_hash": "Diese Erweiterung besitzt keine Prüfsumme, Installation auf eigene Gefahr.",
"reinstall": {
"button_idle": "Neu installieren",
"button_processing": "Wird neu installiert",
"desc": "Bist du dir sicher, dass du {{artifact}} {{version}} neu installieren willst?",
"title": "Neu installation {{artifact}}"
},
"update": {
"button_idle": "Aktualisieren",
"button_processing": "Wird aktualisiert",
"desc": "Bist du dir sicher, dass du {{artifact}} {{version}} aktualisieren willst?",
"title": "Aktualisiere {{artifact}}"
}
},
"PluginListIndex": {
"freeze": "Updates einfrieren",
"hide": "Schnellzugriff: Ausblenden",
"no_plugin": "Keine Erweiterungen installiert!",
"plugin_actions": "Erweiterungs Aktionen",
"reinstall": "Neu installieren",
"reload": "Neu laden",
"show": "Schnellzugriff: Anzeigen",
"unfreeze": "Updates erlauben",
"uninstall": "Deinstallieren",
"update_all_one": "{{count}} Plugin aktualisieren",
"update_all_other": "{{count}} Plugins aktualisieren",
"update_to": "Aktualisieren zu {{name}}"
},
"PluginListLabel": {
"hidden": "Im Schnellzugriff-Menu ausgeblendet"
},
"PluginLoader": {
"decky_title": "Decky",
"decky_update_available": "Eine neue Version ({{tag_name}}) ist verfügbar!",
"error": "Fehler",
"plugin_error_uninstall": "Das Laden von {{name}} hat einen Fehler verursacht. Dies bedeutet normalerweise, dass die Erweiterung ein Update für die neue Version von SteamUI benötigt. Prüfe in den Decky-Einstellungen im Bereich Erweiterungen, ob ein Update vorhanden ist.",
"plugin_load_error": {
"message": "Fehler beim Laden von {{name}}",
"toast": "Fehler beim Laden von {{name}}"
},
"plugin_uninstall": {
"button": "Deinstallieren",
"desc": "Bist du dir sicher, dass du {{name}} deinstallieren willst?",
"title": "Deinstalliere {{name}}"
},
"plugin_update_one": "1 Erweiterung kann aktualisiert werden!",
"plugin_update_other": "{{count}} Erweiterungen können aktualisiert werden!"
},
"PluginView": {
"hidden_one": "{{count}} Plugin ist in dieser Liste ausgeblendet",
"hidden_other": "{{count}} Plugins sind in dieser Liste ausgeblendet"
},
"RemoteDebugging": {
"remote_cef": {
"desc": "Erlaubt jedem aus dem Neztwerk unautorisierten Zugriff auf den CEF Debugger",
"label": "Remote CEF Debugging Zugriff"
}
},
"SettingsDeveloperIndex": {
"cef_console": {
"button": "Konsole öffnen",
"desc": "Öffnet die CEF Konsole. Nur für Debugzwecke. Dinge hier sind potentiell gefährlich und sollten nur durch oder unter Anleitung von Pluginentwickler/innen geschehen.",
"label": "CEF Konsole"
},
"header": "Sonstiges",
"react_devtools": {
"desc": "Erlaubt die Verbindung mit einem anderen Rechner, auf welchem React DevTools läuft. Eine Änderung startet Steam neu. Die IP Adresse muss vor Aktivierung ausgefüllt sein.",
"ip_label": "IP",
"label": "Aktiviere React DevTools"
},
"third_party_plugins": {
"button_install": "Installieren",
"button_zip": "Durchsuchen",
"header": "Erweiterungen von Drittanbietern",
"label_desc": "URL",
"label_url": "Installiere Erweiterung via URL",
"label_zip": "Installiere Erweiterung via ZIP Datei"
},
"valve_internal": {
"desc1": "Aktiviert das Valve-interne Entwickler Menü.",
"desc2": "Fasse in diesem Menü nichts an, es sei denn, du weißt was du tust.",
"label": "Aktiviere Valve-internes Menü"
}
},
"SettingsGeneralIndex": {
"about": {
"decky_version": "Decky Version",
"header": "Über"
},
"beta": {
"header": "Beta Teilnahme"
},
"developer_mode": {
"label": "Entwickleroptionen"
},
"notifications": {
"decky_updates_label": "Decky Update verfügbar",
"header": "Benachrichtigungen",
"plugin_updates_label": "Plugin Updates verfügbar"
},
"other": {
"header": "Sonstiges"
},
"updates": {
"header": "Aktualisierungen"
}
},
"SettingsIndex": {
"developer_title": "Entwickler",
"general_title": "Allgemein",
"plugins_title": "Erweiterungen",
"testing_title": "Testen"
},
"Store": {
"download_progress_info": {
"download_zip": "Plugin herunterladen",
"increment_count": "Erhöhen der Downloadanzahl",
"installing_plugin": "Plugin installieren",
"open_zip": "Öffnen der Zip-Datei",
"parse_zip": "Parsen der Zip-Datei",
"start": "Initialisieren",
"uninstalling_previous": "Vorherige Kopie deinstallieren"
},
"store_contrib": {
"desc": "Wenn du Erweiterungen im Decky Store veröffentlichen willst, besuche die SteamDeckHomebrew/decky-plugin-template Repository auf GitHub. Informationen rund um Entwicklung und Veröffentlichung findest du in der README.",
"label": "Mitwirken"
},
"store_filter": {
"label": "Filter",
"label_def": "Alle"
},
"store_search": {
"label": "Suche"
},
"store_sort": {
"label": "Sortierung",
"label_def": "Zuletzt aktualisiert"
},
"store_source": {
"desc": "Jeder Erweiterungs Quellcode ist in der SteamDeckHomebrew/decky-plugin-database Repository auf GitHub verfügbar.",
"label": "Quellcode"
},
"store_tabs": {
"about": "Über",
"alph_asce": "Alphabetisch (Z zu A)",
"alph_desc": "Alphabetisch (A zu Z)",
"date_asce": "Älteste Zuerst",
"date_desc": "Neuste Zuerst",
"downloads_asce": "Wenigste Downloads Zuerst",
"downloads_desc": "Meiste Downloads Zuerst",
"title": "Durchstöbern"
},
"store_testing_cta": "Unterstütze das Decky Loader Team mit dem Testen von neuen Erweiterungen!",
"store_testing_warning": {
"desc": "Du kannst diesen Store Kanal nutzen, um brandneue Testversionen von Plugins auszuprobieren. Denk daran Feedback auf GitHub zu hinterlassen, sodass das Plugin für alle Nutzer verbessert werden kann.",
"label": "Willkommen zum Test Store Kanal"
}
},
"StoreSelect": {
"custom_store": {
"label": "Benutzerdefiniertes Store",
"url_label": "URL"
},
"store_channel": {
"custom": "Benutzerdefiniert",
"default": "Standard",
"label": "Store Kanal",
"testing": "Test"
}
},
"Testing": {
"download": "Download",
"error": "Fehler beim Installieren von PR",
"header": "Die folgenden Versionen von Decky Loader wurden aus offenen Pull Requests von Dritten erstellt. Das Decky Loader-Team hat ihre Funktionalität oder Sicherheit nicht überprüft, und sie können veraltet sein.",
"loading": "Offene Pull Requests laden...",
"start_download_toast": "Herunterladen von PR #{{id}}"
},
"TitleView": {
"decky_store_desc": "Decky Store Öffnen",
"settings_desc": "Decky Einstellungen Öffnen"
},
"Updater": {
"decky_updates": "Decky Aktualisierungen",
"no_patch_notes_desc": "Für diese Version gibt es keine Patchnotizen",
"patch_notes_desc": "Patchnotizen",
"updates": {
"check_button": "Auf Aktualisierungen prüfen",
"checking": "Wird überprüft",
"cur_version": "Aktualle Version: {{ver}}",
"install_button": "Aktualisierung installieren",
"label": "Aktualisierungen",
"lat_version": "{{ver}} ist die aktuellste",
"reloading": "Lade neu",
"updating": "Aktualisiere"
}
}
}
@@ -1,35 +1,9 @@
{
"SettingsDeveloperIndex": {
"react_devtools": {
"desc": "Επιτρέπει την σύνδεση με υπολογιστή που τρέχει React DevTools. Η αλλαγή αυτής της ρύθμισης θα προκαλέσει επαναφόρτωση του Steam. Ωρίστε την διεύθυνση IP πριν την ενεργοποιήσετε.",
"ip_label": "IP",
"label": "Ενεργοποίηση React DevTools"
},
"third_party_plugins": {
"button_install": "Εγκατάσταση",
"button_zip": "Περιήγηση",
"header": "Επεκτάσεις τρίτων",
"label_desc": "URL",
"label_url": "Εγκατάσταση επέκτασης απο URL",
"label_zip": "Εγκατάσταση επέκτασης από αρχείο ZIP"
},
"valve_internal": {
"desc1": "Ενεργοποιεί το μενού προγραμματιστή της Valve.",
"desc2": "Μην αγγίξετε τίποτα σε αυτό το μενού εκτός και αν ξέρετε τι κάνει.",
"label": "Ενεργοποιήση εσωτερικού μενού Valve"
},
"cef_console": {
"button": "Άνοιγμα Κονσόλας",
"desc": "Ανοίγει την Κονσόλα CEF. Χρήσιμο μόνο για εντοπισμό σφαλμάτων. Τα πράγματα εδώ είναι δυνητικά επικίνδυνα και θα πρέπει να χρησιμοποιηθεί μόνο εάν είστε προγραμματιστής επεκτάσεων, ή κατευθυνθήκατε εδώ από έναν προγραμματιστή.",
"label": "Κονσόλα CEF"
},
"header": "Άλλα"
},
"BranchSelect": {
"update_channel": {
"label": "Κανάλι ενημερώσεων",
"prerelease": "Προ-κυκλοφορία",
"stable": "Σταθερό",
"label": "Κανάλι ενημερώσεων",
"testing": "Δοκιμαστικό"
}
},
@@ -38,17 +12,76 @@
"disabling": "Γίνεται απενεργοποίηση των React DevTools",
"enabling": "Γίνεται ενεργοποίηση των React DevTools"
},
"DropdownMultiselect": {
"button": {
"back": "Πίσω"
}
},
"FilePickerError": {
"errors": {
"file_not_found": "Η καθορισμένη διαδρομή δεν είναι έγκυρη. Παρακαλούμε ελέγξτε τη και εισάγετέ τη ξανά σωστά.",
"perm_denied": "Δεν έχετε πρόσβαση στην καθορισμένη διαδρομή. Ελέγξτε εάν ο χρήστης σας (deck στο Steam Deck) έχει τα αντίστοιχα δικαιώματα πρόσβασης στον καθορισμένο φάκελο/αρχείο.",
"unknown": "Παρουσιάστηκε άγνωστο σφάλμα. Το σφάλμα είναι: {{raw_error}}"
}
},
"FilePickerIndex": {
"file": {
"select": "Επιλογή αυτού του αρχείου"
},
"files": {
"all_files": "Όλα Τα Αρχεία",
"file_type": "Τύπος Αρχείου",
"show_hidden": "Εμφάνιση Κρυφών Αρχείων"
},
"filter": {
"created_asce": "Δημιουργήθηκε (Παλαιότερο)",
"created_desc": "Δημιουργήθηκε (Νεότερο)",
"modified_asce": "Τροποποιήθηκε (Παλαιότερο)",
"modified_desc": "Τροποποιήθηκε (Νεότερο)",
"name_asce": "Z-A",
"name_desc": "A-Z",
"size_asce": "Μέγεθος (Μικρότερο)",
"size_desc": "Μέγεθος (Μεγαλύτερο)"
},
"folder": {
"label": "Φάκελος",
"select": "Χρησιμοποιήστε αυτό το φάκελο",
"show_more": "Εμφάνιση περισσότερων αρχείων"
}
},
"MultiplePluginsInstallModal": {
"confirm": "Είστε βέβαιοι ότι θέλετε να κάνετε τις ακόλουθες τροποποιήσεις;",
"description": {
"install": "Εγκατάσταση {{name}} {{version}}",
"reinstall": "Επανεγκατάσταση {{name}} {{version}}",
"update": "Ενημέρωση {{name}} to {{version}}"
},
"ok_button": {
"idle": "Επιβεβαίωση",
"loading": "Φόρτωση"
},
"title": {
"install_one": "Εγκατάσταση 1 επέκτασης",
"install_other": "Εγκατάσταση {{count}} επεκτάσεων",
"mixed_one": "Τροποποίηση 1 επέκτασης",
"mixed_other": "Τροποποίηση {{count}} επεκτάσεων",
"reinstall_one": "Επανεγκατάσταση 1 επέκτασης",
"reinstall_other": "Επανεγκατάσταση {{count}} επεκτάσεων",
"update_one": "Ενημέρωση 1 επέκτασης",
"update_other": "Ενημέρωση {{count}} επεκτάσεων"
}
},
"PluginCard": {
"plugin_no_desc": "Δεν υπάρχει περιγραφή.",
"plugin_full_access": "Αυτή η επέκταση έχει πλήρη πρόσβαση στο Steam Deck σας.",
"plugin_install": "Εγκατάσταση",
"plugin_no_desc": "Δεν υπάρχει περιγραφή.",
"plugin_version_label": "Έκδοση επέκτασης"
},
"PluginInstallModal": {
"install": {
"desc": "Σίγουρα θέλετε να εγκαταστήσετε το {{artifact}}{{version}};",
"button_idle": "Εγκατάσταση",
"button_processing": "Γίνεται εγκατάσταση",
"desc": "Σίγουρα θέλετε να εγκαταστήσετε το {{artifact}}{{version}};",
"title": "Εγκατάσταση {{artifact}}"
},
"no_hash": "Αυτή η επέκταση δεν έχει υπογραφή, την εγκαθηστάτε με δικό σας ρίσκο.",
@@ -60,22 +93,25 @@
},
"update": {
"button_idle": "Ενημέρωση",
"button_processing": "Γίνεται ενημέρωση",
"desc": "Σίγουρα θέλετε να ενημερώσετε το {{artifact}} {{version}};",
"title": "Ενημέρωση {{artifact}}",
"button_processing": "Γίνεται ενημέρωση"
"title": "Ενημέρωση {{artifact}}"
}
},
"PluginListIndex": {
"hide": "Γρήγορη πρόσβαση: Απόκρυψη",
"no_plugin": "Δεν υπάρχουν εγκατεστημένες επεκτάσεις!",
"plugin_actions": "Ενέργειες επεκτάσεων",
"reinstall": "Επανεγκατάσταση",
"reload": "Επαναφόρτωση",
"show": "Γρήγορη πρόσβαση: Εμφάνιση",
"uninstall": "Απεγκατάσταση",
"update_to": "Ενημέρωση σε {{name}}",
"update_all_one": "Ενημέρωση 1 επέκτασης",
"update_all_other": "Ενημέρωση {{count}} επεκτάσεων",
"show": "Γρήγορη πρόσβαση: Εμφάνιση",
"hide": "Γρήγορη πρόσβαση: Απόκρυψη"
"update_to": "Ενημέρωση σε {{name}}"
},
"PluginListLabel": {
"hidden": "Κρυφό στο μενού γρήγορης πρόσβασης"
},
"PluginLoader": {
"decky_title": "Decky",
@@ -94,10 +130,40 @@
"plugin_update_one": "Διαθέσιμη ενημέρωση για 1 επέκταση!",
"plugin_update_other": "Διαθέσιμες ενημερώσεις για {{count}} επεκτάσεις!"
},
"PluginView": {
"hidden_one": "1 επέκταση είναι κρυμμένη σε αυτήν τη λίστα",
"hidden_other": "{{count}} επεκτάσεις είναι κρυμμένες σε αυτήν τη λίστα"
},
"RemoteDebugging": {
"remote_cef": {
"label": "Να επιτρέπεται η απομακρυσμένη πρόσβαση στον CEF debugger",
"desc": "Να επιτρέπεται η ανεξέλεγκτη πρόσβαση στον CEF debugger σε οποιονδήποτε στο τοπικό δίκτυο"
"desc": "Να επιτρέπεται η ανεξέλεγκτη πρόσβαση στον CEF debugger σε οποιονδήποτε στο τοπικό δίκτυο",
"label": "Να επιτρέπεται η απομακρυσμένη πρόσβαση στον CEF debugger"
}
},
"SettingsDeveloperIndex": {
"cef_console": {
"button": "Άνοιγμα Κονσόλας",
"desc": "Ανοίγει την Κονσόλα CEF. Χρήσιμο μόνο για εντοπισμό σφαλμάτων. Τα πράγματα εδώ είναι δυνητικά επικίνδυνα και θα πρέπει να χρησιμοποιηθεί μόνο εάν είστε προγραμματιστής επεκτάσεων, ή κατευθυνθήκατε εδώ από έναν προγραμματιστή.",
"label": "Κονσόλα CEF"
},
"header": "Άλλα",
"react_devtools": {
"desc": "Επιτρέπει την σύνδεση με υπολογιστή που τρέχει React DevTools. Η αλλαγή αυτής της ρύθμισης θα προκαλέσει επαναφόρτωση του Steam. Ωρίστε την διεύθυνση IP πριν την ενεργοποιήσετε.",
"ip_label": "IP",
"label": "Ενεργοποίηση React DevTools"
},
"third_party_plugins": {
"button_install": "Εγκατάσταση",
"button_zip": "Περιήγηση",
"header": "Επεκτάσεις τρίτων",
"label_desc": "URL",
"label_url": "Εγκατάσταση επέκτασης απο URL",
"label_zip": "Εγκατάσταση επέκτασης από αρχείο ZIP"
},
"valve_internal": {
"desc1": "Ενεργοποιεί το μενού προγραμματιστή της Valve.",
"desc2": "Μην αγγίξετε τίποτα σε αυτό το μενού εκτός και αν ξέρετε τι κάνει.",
"label": "Ενεργοποιήση εσωτερικού μενού Valve"
}
},
"SettingsGeneralIndex": {
@@ -105,33 +171,33 @@
"decky_version": "Έκδοση Decky",
"header": "Σχετικά"
},
"beta": {
"header": "Συμμετοχή στη Beta"
},
"developer_mode": {
"label": "Λειτουργία προγραμματιστή"
},
"notifications": {
"decky_updates_label": "Διαθέσιμη ενημέρωση του Decky",
"header": "Ειδοποιήσεις",
"plugin_updates_label": "Διαθέσιμες ενημερώσεις επεκτάσεων"
},
"other": {
"header": "Άλλα"
},
"updates": {
"header": "Ενημερώσεις"
},
"beta": {
"header": "Συμμετοχή στη Beta"
},
"notifications": {
"decky_updates_label": "Διαθέσιμη ενημέρωση του Decky",
"header": "Ειδοποιήσεις",
"plugin_updates_label": "Διαθέσιμες ενημερώσεις επεκτάσεων"
}
},
"SettingsIndex": {
"plugins_title": "Επεκτάσεις",
"developer_title": "Προγραμματιστής",
"general_title": "Γενικά"
"general_title": "Γενικά",
"plugins_title": "Επεκτάσεις"
},
"Store": {
"store_contrib": {
"label": "Συνεισφέροντας",
"desc": "Αν θέλετε να συνεισφέρετε στο κατάστημα επεκτάσεων του Decky, τσεκάρετε το SteamDeckHomebrew/decky-plugin-template repository στο GitHub. Πληροφορίες σχετικά με τη δημιουργία και τη διανομή επεκτάσεων είναι διαθέσιμες στο README."
"desc": "Αν θέλετε να συνεισφέρετε στο κατάστημα επεκτάσεων του Decky, τσεκάρετε το SteamDeckHomebrew/decky-plugin-template repository στο GitHub. Πληροφορίες σχετικά με τη δημιουργία και τη διανομή επεκτάσεων είναι διαθέσιμες στο README.",
"label": "Συνεισφέροντας"
},
"store_filter": {
"label": "Φίλτρο",
@@ -172,7 +238,12 @@
"testing": "Δοκιμαστικό"
}
},
"TitleView": {
"decky_store_desc": "Άνοιγμα Καταστήματος Decky",
"settings_desc": "Άνοιγμα Ρυθμίσεων Decky"
},
"Updater": {
"decky_updates": "Ενημερώσεις Decky",
"no_patch_notes_desc": "Κανένα ενημερωτικό σημείωμα για αυτή την έκδοση",
"patch_notes_desc": "Σημειώσεις ενημέρωσης",
"updates": {
@@ -181,80 +252,9 @@
"cur_version": "Τρέχουσα έκδοση: {{ver}}",
"install_button": "Εγκατάσταση ενημέρωσης",
"label": "Ενημερώσεις",
"updating": "Γίνεται ενημέρωση",
"lat_version": "Ενημερωμένο: τρέχουσα έκδοση {{ver}}",
"reloading": "Γίνεται επαναφόρτωση"
},
"decky_updates": "Ενημερώσεις Decky"
},
"FilePickerIndex": {
"folder": {
"select": "Χρησιμοποιήστε αυτό το φάκελο",
"label": "Φάκελος",
"show_more": "Εμφάνιση περισσότερων αρχείων"
},
"filter": {
"modified_asce": "Τροποποιήθηκε (Παλαιότερο)",
"modified_desc": "Τροποποιήθηκε (Νεότερο)",
"created_desc": "Δημιουργήθηκε (Νεότερο)",
"name_asce": "Z-A",
"name_desc": "A-Z",
"created_asce": "Δημιουργήθηκε (Παλαιότερο)",
"size_asce": "Μέγεθος (Μικρότερο)",
"size_desc": "Μέγεθος (Μεγαλύτερο)"
},
"file": {
"select": "Επιλογή αυτού του αρχείου"
},
"files": {
"show_hidden": "Εμφάνιση Κρυφών Αρχείων",
"all_files": "Όλα Τα Αρχεία",
"file_type": "Τύπος Αρχείου"
}
},
"PluginView": {
"hidden_one": "1 επέκταση είναι κρυμμένη σε αυτήν τη λίστα",
"hidden_other": "{{count}} επεκτάσεις είναι κρυμμένες σε αυτήν τη λίστα"
},
"MultiplePluginsInstallModal": {
"title": {
"mixed_one": "Τροποποίηση 1 επέκτασης",
"mixed_other": "Τροποποίηση {{count}} επεκτάσεων",
"update_one": "Ενημέρωση 1 επέκτασης",
"update_other": "Ενημέρωση {{count}} επεκτάσεων",
"reinstall_one": "Επανεγκατάσταση 1 επέκτασης",
"reinstall_other": "Επανεγκατάσταση {{count}} επεκτάσεων",
"install_one": "Εγκατάσταση 1 επέκτασης",
"install_other": "Εγκατάσταση {{count}} επεκτάσεων"
},
"confirm": "Είστε βέβαιοι ότι θέλετε να κάνετε τις ακόλουθες τροποποιήσεις;",
"description": {
"reinstall": "Επανεγκατάσταση {{name}} {{version}}",
"update": "Ενημέρωση {{name}} to {{version}}",
"install": "Εγκατάσταση {{name}} {{version}}"
},
"ok_button": {
"idle": "Επιβεβαίωση",
"loading": "Φόρτωση"
}
},
"PluginListLabel": {
"hidden": "Κρυφό στο μενού γρήγορης πρόσβασης"
},
"TitleView": {
"settings_desc": "Άνοιγμα Ρυθμίσεων Decky",
"decky_store_desc": "Άνοιγμα Καταστήματος Decky"
},
"DropdownMultiselect": {
"button": {
"back": "Πίσω"
}
},
"FilePickerError": {
"errors": {
"file_not_found": "Η καθορισμένη διαδρομή δεν είναι έγκυρη. Παρακαλούμε ελέγξτε τη και εισάγετέ τη ξανά σωστά.",
"perm_denied": "Δεν έχετε πρόσβαση στην καθορισμένη διαδρομή. Ελέγξτε εάν ο χρήστης σας (deck στο Steam Deck) έχει τα αντίστοιχα δικαιώματα πρόσβασης στον καθορισμένο φάκελο/αρχείο.",
"unknown": "Παρουσιάστηκε άγνωστο σφάλμα. Το σφάλμα είναι: {{raw_error}}"
"reloading": "Γίνεται επαναφόρτωση",
"updating": "Γίνεται ενημέρωση"
}
}
}
+307
View File
@@ -0,0 +1,307 @@
{
"BranchSelect": {
"update_channel": {
"label": "Update Channel",
"prerelease": "Prerelease",
"stable": "Stable",
"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": {
"downgrade": "Downgrade {{name}} to {{version}}",
"install": "Install {{name}} {{version}}",
"overwrite": "Overwrite {{name}} with {{version}}",
"reinstall": "Reinstall {{name}} {{version}}",
"update": "Update {{name}} to {{version}}"
},
"ok_button": {
"idle": "Confirm",
"loading": "Working"
},
"title": {
"downgrade_one": "Downgrade 1 plugin",
"downgrade_other": "Downgrade {{count}} plugins",
"install_one": "Install 1 plugin",
"install_other": "Install {{count}} plugins",
"mixed_one": "Modify {{count}} plugin",
"mixed_other": "Modify {{count}} plugins",
"overwrite_one": "Overwrite 1 plugin",
"overwrite_other": "Overwrite {{count}} plugins",
"reinstall_one": "Reinstall 1 plugin",
"reinstall_other": "Reinstall {{count}} plugins",
"update_one": "Update 1 plugin",
"update_other": "Update {{count}} plugins"
}
},
"PluginCard": {
"plugin_downgrade": "Downgrade",
"plugin_full_access": "This plugin has full access to your Steam Deck.",
"plugin_install": "Install",
"plugin_no_desc": "No description provided.",
"plugin_overwrite": "Overwrite",
"plugin_reinstall": "Reinstall",
"plugin_update": "Update",
"plugin_version_label": "Plugin Version"
},
"PluginInstallModal": {
"downgrade": {
"button_idle": "Downgrade",
"button_processing": "Downgrading",
"desc": "Are you sure you want to downgrade {{artifact}} to version {{version}}?",
"title": "Downgrade {{artifact}}"
},
"install": {
"button_idle": "Install",
"button_processing": "Installing",
"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.",
"not_installed": "(not installed)",
"overwrite": {
"button_idle": "Overwrite",
"button_processing": "Overwriting",
"desc": "Are you sure you want to overwrite {{artifact}} with version {{version}}?",
"title": "Overwrite {{artifact}}"
},
"reinstall": {
"button_idle": "Reinstall",
"button_processing": "Reinstalling",
"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}} to version {{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": {
"download_progress_info": {
"download_remote": "Downloading any external binaries",
"download_zip": "Downloading plugin",
"increment_count": "Incrementing download count",
"installing_plugin": "Installing plugin",
"open_zip": "Opening zip file",
"parse_zip": "Parsing zip file",
"start": "Initializing",
"uninstalling_previous": "Uninstalling previous copy"
},
"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"
}
},
"Testing": {
"download": "Download",
"error": "Error Installing PR",
"header": "The following versions of Decky Loader are built from open third-party Pull Requests. The Decky Loader team has not verified their functionality or security, and they may be outdated.",
"loading": "Loading open Pull Requests...",
"start_download_toast": "Downloading PR #{{id}}"
},
"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"
}
}
}
@@ -1,37 +1,94 @@
{
"SettingsDeveloperIndex": {
"third_party_plugins": {
"button_install": "Instalar",
"button_zip": "Navegar",
"label_desc": "URL",
"label_url": "Instalar plugin desde URL",
"label_zip": "Instalar plugin desde archivo ZIP",
"header": "Plugins de terceros"
"BranchSelect": {
"update_channel": {
"label": "Canal de actualización",
"prerelease": "Prelanzamiento",
"stable": "Estable",
"testing": "En pruebas"
}
},
"Developer": {
"5secreload": "Recargando en 5 segundos",
"disabling": "Desactivando DevTools de React",
"enabling": "Activando DevTools de React"
},
"DropdownMultiselect": {
"button": {
"back": "Volver"
}
},
"FilePickerError": {
"errors": {
"file_not_found": "La ruta especificada no es válida. Por favor revísala e introdúcela correctamente.",
"perm_denied": "No tienes acceso a la ruta especificada. Por favor revisa si tu usuario (deck en Steam Deck) tiene el permiso correspondiente para acceder a la ruta/archivo especificado.",
"unknown": "Ha ocurrido un error desconocido. El error sin procesar es:{{raw_error}}"
}
},
"FilePickerIndex": {
"file": {
"select": "Selecciona este archivo"
},
"valve_internal": {
"desc2": "No toques nada en este menú a menos que sepas lo que haces.",
"label": "Activar menú interno de Valve",
"desc1": "Activa el menú interno de desarrollo de Valve."
"files": {
"all_files": "Todos los archivos",
"file_type": "Tipo de archivo",
"show_hidden": "Mostrar archivos ocultos"
},
"cef_console": {
"button": "Abrir consola",
"label": "Consola CEF",
"desc": "Abre la consola del CEF. Solamente es útil para propósitos de depuración. Las cosas que hagas aquí pueden ser potencialmente peligrosas y solo se debería usar si eres un desarrollador de plugins, o uno te ha dirigido aquí."
"filter": {
"created_asce": "Creado (Más antiguo)",
"created_desc": "Creado (Más reciente)",
"modified_asce": "Modificado (Más antiguo)",
"modified_desc": "Modificado (Más reciente)",
"name_asce": "Z-A",
"name_desc": "A-Z",
"size_asce": "Tamaño (Más pequeño)",
"size_desc": "Tamaño (Más grande)"
},
"react_devtools": {
"ip_label": "IP",
"label": "Activar DevTools de React",
"desc": "Permite la conexión a un ordenador ejecutando las DevTools de React. Cambiar este ajuste recargará Steam. Configura la dirección IP antes de activarlo."
"folder": {
"label": "Carpeta",
"select": "Usar esta carpeta",
"show_more": "Mostrar más archivos"
}
},
"MultiplePluginsInstallModal": {
"confirm": "¿Estás seguro de que quieres hacer las siguientes modificaciones?",
"description": {
"install": "Instalar {{name}} {{version}}",
"reinstall": "Reinstalar {{name}} {{version}}",
"update": "Actualizar {{name}} a {{version}}"
},
"header": "Otros"
"ok_button": {
"idle": "Confirmar",
"loading": "Trabajando"
},
"title": {
"install_many": "Instalar {{count}} plugins",
"install_one": "Instalar 1 plugin",
"install_other": "Instalar {{count}} plugins",
"mixed_many": "Modificar {{count}} plugins",
"mixed_one": "Modificar 1 plugin",
"mixed_other": "Modificar {{count}} plugins",
"reinstall_many": "Reinstalar {{count}} plugins",
"reinstall_one": "Reinstalar 1 plugin",
"reinstall_other": "Reinstalar {{count}} plugins",
"update_many": "Actualizar {{count}} plugins",
"update_one": "Actualizar 1 plugin",
"update_other": "Actualizar {{count}} plugins"
}
},
"PluginCard": {
"plugin_full_access": "Este plugin tiene acceso completo a tu Steam Deck.",
"plugin_install": "Instalar",
"plugin_no_desc": "No se ha proporcionado una descripción.",
"plugin_version_label": "Versión de Plugin"
},
"PluginInstallModal": {
"install": {
"button_idle": "Instalar",
"button_processing": "Instalando",
"title": "Instalar {{artifact}}",
"desc": "¿Estás seguro de que quieres instalar {{artifact}} {{version}}?"
"desc": "¿Estás seguro de que quieres instalar {{artifact}} {{version}}?",
"title": "Instalar {{artifact}}"
},
"no_hash": "Este plugin no tiene un hash, lo estás instalando bajo tu propia responsabilidad.",
"reinstall": {
"button_idle": "Reinstalar",
"button_processing": "Reinstalando",
@@ -39,179 +96,195 @@
"title": "Reinstalar {{artifact}}"
},
"update": {
"button_processing": "Actualizando",
"button_idle": "Actualizar",
"button_processing": "Actualizando",
"desc": "¿Estás seguro de que quieres actualizar {{artifact}} {{version}}?",
"title": "Actualizar {{artifact}}"
},
"no_hash": "Este plugin no tiene un hash, lo estás instalando bajo tu propia responsabilidad."
},
"Developer": {
"disabling": "Desactivando DevTools de React",
"enabling": "Activando DevTools de React",
"5secreload": "Recargando en 5 segundos"
},
"BranchSelect": {
"update_channel": {
"prerelease": "Prelanzamiento",
"stable": "Estable",
"label": "Canal de actualización",
"testing": "Pruebas"
}
},
"PluginCard": {
"plugin_full_access": "Este plugin tiene acceso completo a su Steam Deck.",
"plugin_install": "Instalar",
"plugin_version_label": "Versión de Plugin",
"plugin_no_desc": "No se proporcionó una descripción."
},
"FilePickerIndex": {
"folder": {
"select": "Usar esta carpeta"
}
},
"PluginListIndex": {
"uninstall": "Desinstalar",
"freeze": "Congelar actualizaciones",
"hide": "Acceso rápido: Esconder",
"no_plugin": "¡No hay plugins instalados!",
"plugin_actions": "Acciones de Plugin",
"reinstall": "Reinstalar",
"reload": "Recargar",
"plugin_actions": "Acciones de plugin",
"no_plugin": "¡No hay plugins instalados!",
"update_all_one": "Actualizar 1 plugin",
"show": "Acceso rápido: Mostrar",
"unfreeze": "Permitir actualizaciones",
"uninstall": "Desinstalar",
"update_all_many": "Actualizar {{count}} plugins",
"update_all_one": "Actualizar 1 plugin",
"update_all_other": "Actualizar {{count}} plugins",
"update_to": "Actualizar a {{name}}"
},
"PluginListLabel": {
"hidden": "Escondido del menú de acceso rápido"
},
"PluginLoader": {
"decky_title": "Decky",
"decky_update_available": "¡Actualización para {{tag_name}} disponible!",
"error": "Error",
"plugin_error_uninstall": "Al cargar {{name}} se ha producido una excepción como se muestra arriba. Esto suele significar que el plugin requiere una actualización para la nueva versión de SteamUI. Comprueba si hay una actualización disponible o valora eliminarlo en los ajustes de Decky, en la sección Plugins.",
"plugin_load_error": {
"message": "Se ha producido un error al cargar el plugin {{name}}",
"toast": "Se ha producido un error al cargar {{name}}"
},
"plugin_uninstall": {
"button": "Desinstalar",
"desc": "¿Estás seguro de que quieres desinstalar {{name}}?",
"title": "Desinstalar {{name}}"
},
"decky_title": "Decky",
"plugin_update_one": "¡Actualización disponible para 1 plugin!",
"plugin_update_many": "¡Actualizaciones disponibles para {{count}} plugins!",
"plugin_update_other": "¡Actualizaciones disponibles para {{count}} plugins!",
"decky_update_available": "¡Actualización {{tag_name}} disponible!",
"plugin_load_error": {
"message": "Se ha producido un error al cargar el plugin {{name}}",
"toast": "Se ha producido un error al cargar {{name}}"
},
"plugin_error_uninstall": "Al cargar {{name}} se ha producido una excepción como se muestra arriba. Esto suele significar que el plugin requiere una actualización para la nueva versión de SteamUI. Comprueba si hay una actualización disponible o valora eliminarlo en los ajustes de Decky, en la sección Plugins."
"plugin_update_one": "¡Actualización disponible para 1 plugin!",
"plugin_update_other": "¡Actualizaciones disponibles para {{count}} plugins!"
},
"PluginView": {
"hidden_many": "{{count}} plugins están escondidos de esta lista",
"hidden_one": "1 plugin está escondido de esta lista",
"hidden_other": "{{count}} plugins están escondidos de esta lista"
},
"RemoteDebugging": {
"remote_cef": {
"desc": "Permitir acceso no autenticado al CEF debugger a cualquier persona en su red",
"desc": "Permitir acceso no autenticado al depurador del CEF a cualquier persona en tu red",
"label": "Permitir depuración remota del CEF"
}
},
"SettingsDeveloperIndex": {
"cef_console": {
"button": "Abrir Consola",
"desc": "Abre la consola del CEF. Solamente es útil para propósitos de depuración. Las cosas que hagas aquí pueden ser potencialmente peligrosas y solo se debería usar si eres un desarrollador de plugins, o uno te ha dirigido aquí.",
"label": "Consola CEF"
},
"header": "Otros",
"react_devtools": {
"desc": "Permite la conexión a un ordenador ejecutando las DevTools de React. Cambiar este ajuste recargará Steam. Configura la dirección IP antes de activarlo.",
"ip_label": "IP",
"label": "Activar DevTools de React"
},
"third_party_plugins": {
"button_install": "Instalar",
"button_zip": "Navegar",
"header": "Plugins de terceros",
"label_desc": "URL",
"label_url": "Instalar plugin desde URL",
"label_zip": "Instalar plugin desde archivo ZIP"
},
"valve_internal": {
"desc1": "Activa el menú interno de desarrollo de Valve.",
"desc2": "No toques nada en este menú a menos que sepas lo que haces.",
"label": "Activar menú interno de Valve"
}
},
"SettingsGeneralIndex": {
"updates": {
"header": "Actualizaciones"
},
"about": {
"header": "Acerca de",
"decky_version": "Versión de Decky"
},
"developer_mode": {
"label": "Modo desarrollador"
"decky_version": "Versión de Decky",
"header": "Acerca de"
},
"beta": {
"header": "Participación en la beta"
},
"developer_mode": {
"label": "Modo desarrollador"
},
"notifications": {
"decky_updates_label": "Actualización de Decky disponible",
"header": "Notificaciones",
"plugin_updates_label": "Actualizaciones de plugin disponibles"
},
"other": {
"header": "Otros"
},
"updates": {
"header": "Actualizaciones"
}
},
"SettingsIndex": {
"developer_title": "Desarrollador",
"general_title": "General",
"plugins_title": "Plugins"
"plugins_title": "Plugins",
"testing_title": "En pruebas"
},
"Store": {
"download_progress_info": {
"download_zip": "Descargando plugin",
"increment_count": "Incrementando el contador de descargas",
"installing_plugin": "Instalando plugin",
"open_zip": "Abriendo archivo zip",
"parse_zip": "Analizando archivo zip",
"start": "Iniciando",
"uninstalling_previous": "Desinstalando copia previa"
},
"store_contrib": {
"desc": "Si desea contribuir a la tienda de plugins de Decky, consulta el repositorio SteamDeckHomebrew/decky-plugin-template en GitHub. Hay información disponible acerca del desarrollo y distribución en el archivo README.",
"label": "Contribuyendo"
},
"store_filter": {
"label": "Filtrar",
"label_def": "Todos"
},
"store_search": {
"label": "Buscar"
},
"store_sort": {
"label": "Ordenar",
"label_def": "Actualizado por última vez (Nuevos)"
"label_def": "Actualizado por última vez (Más reciente)"
},
"store_contrib": {
"desc": "Si desea contribuir a la tienda de plugins de Decky, mira el repositorio SteamDeckHomebrew/decky-plugin-template en GitHub. Hay información acerca del desarrollo y distribución en el archivo README.",
"label": "Contribuyendo"
},
"store_tabs": {
"about": "Información",
"title": "Navegar",
"alph_asce": "Alfabéticamente (Z-A)",
"alph_desc": "Alfabéticamente (A-Z)"
},
"store_testing_cta": "¡Por favor considera probar plugins nuevos para ayudar al equipo de Decky Loader!",
"store_source": {
"desc": "El código fuente de los plugins está disponible en el repositiorio SteamDeckHomebrew/decky-plugin-database en GitHub.",
"label": "Código fuente"
},
"store_filter": {
"label_def": "Todos",
"label": "Filtrar"
}
},
"Updater": {
"updates": {
"reloading": "Recargando",
"updating": "Actualizando",
"checking": "Buscando",
"check_button": "Buscar actualizaciones",
"install_button": "Instalar actualización",
"label": "Actualizaciones",
"lat_version": "Actualizado: ejecutando {{ver}}",
"cur_version": "Versión actual: {{ver}}"
"store_tabs": {
"about": "Información",
"alph_asce": "Alfabéticamente (Z-A)",
"alph_desc": "Alfabéticamente (A-Z)",
"date_asce": "Más antiguo primero",
"date_desc": "Más reciente primero",
"downloads_asce": "Menos descargados primero",
"downloads_desc": "Más descargados primero",
"title": "Navegar"
},
"decky_updates": "Actualizaciones de Decky",
"no_patch_notes_desc": "No hay notas de parche para esta versión",
"patch_notes_desc": "Notas de parche"
},
"MultiplePluginsInstallModal": {
"title": {
"reinstall_one": "Reinstalar 1 plugin",
"reinstall_many": "Reinstalar {{count}} plugins",
"reinstall_other": "Reinstalar {{count}} plugins",
"update_one": "Actualizar 1 plugin",
"update_many": "Actualizar {{count}} plugins",
"update_other": "Actualizar {{count}} plugins",
"mixed_one": "Modificar 1 plugin",
"mixed_many": "Modificar {{count}} plugins",
"mixed_other": "Modificar {{count}} plugins",
"install_one": "Instalar 1 plugin",
"install_many": "Instalar {{count}} plugins",
"install_other": "Instalar {{count}} plugins"
},
"ok_button": {
"idle": "Confirmar",
"loading": "Trabajando"
},
"confirm": "¿Estás seguro de que quieres hacer las siguientes modificaciones?",
"description": {
"install": "Instalar {{name}} {{version}}",
"update": "Actualizar {{name}} a {{version}}",
"reinstall": "Reinstalar {{name}} {{version}}"
"store_testing_cta": "¡Por favor considera probar plugins nuevos para ayudar al equipo de Decky Loader!",
"store_testing_warning": {
"desc": "Puedes usar este canal de la tienda para probar versiones inestables de plugins. Recuerda compartir tu experiencia en GitHub con el fin de poder actualizar el plugin para todos los usuarios.",
"label": "Bienvenido al canal En Pruebas de la Tienda"
}
},
"StoreSelect": {
"custom_store": {
"url_label": "URL",
"label": "Tienda personalizada"
"label": "Tienda personalizada",
"url_label": "URL"
},
"store_channel": {
"custom": "Personalizada",
"default": "Por defecto",
"label": "Canál de la tienda",
"testing": "Pruebas"
"label": "Canal de la Tienda",
"testing": "En pruebas"
}
},
"PluginView": {
"hidden_one": "",
"hidden_many": "",
"hidden_other": ""
"Testing": {
"download": "Descargar",
"error": "Error instalando PR",
"header": "Las siguientes versiones de Decky Loader han sido compiladas de solicitudes Pull de terceros. El equipo de Decky Loader no ha verificado su funcionalidad o seguridad, y es posible que estén desactulizadas.",
"loading": "Cargando abrir Solicitudes de Pull...",
"start_download_toast": "Descargando PR #{{id}}"
},
"TitleView": {
"decky_store_desc": "Abrir la tienda de Decky",
"settings_desc": "Abrir los ajustes de Decky"
},
"Updater": {
"decky_updates": "Actualizaciones de Decky",
"no_patch_notes_desc": "No hay notas de parche para esta versión",
"patch_notes_desc": "Notas de parche",
"updates": {
"check_button": "Buscar actualizaciones",
"checking": "Buscando",
"cur_version": "Versión actual: {{ver}}",
"install_button": "Instalar actualización",
"label": "Actualizaciones",
"lat_version": "Actualizado: ejecutando {{ver}}",
"reloading": "Recargando",
"updating": "Actualizando"
}
}
}
@@ -1,10 +1,10 @@
{
"BranchSelect": {
"update_channel": {
"label": "Päivityskanava",
"prerelease": "Esijulkaisu",
"testing": "Testiversio",
"stable": "Vakaa versio",
"label": "Päivityskanava"
"testing": "Testiversio"
}
},
"Developer": {
@@ -12,11 +12,16 @@
"disabling": "Poistetaan React DevTools käytöstä",
"enabling": "Otetaan React DevTools käyttöön"
},
"DropdownMultiselect": {
"button": {
"back": "Takaisin"
}
},
"FilePickerError": {
"errors": {
"file_not_found": "Määritetty polku ei kelpaa. Tarkista se ja kirjoita se uudelleen oikein.",
"perm_denied": "Sinulla ei ole käyttöoikeutta määritettyyn hakemistoon. Tarkista, onko käyttäjälläsi (käyttäjä 'deck' Steam Deckillä) vastaavat oikeudet käyttää määritettyä kansiota/tiedostoa.",
"unknown": "Tapahtui tuntematon virhe. Raaka virhe on: {{raw_error}}",
"file_not_found": "Määritetty polku ei kelpaa. Tarkista se ja kirjoita se uudelleen oikein."
"unknown": "Tapahtui tuntematon virhe. Raaka virhe on: {{raw_error}}"
}
},
"FilePickerIndex": {
@@ -29,14 +34,14 @@
"show_hidden": "Näytä piilotetut tiedostot"
},
"filter": {
"created_asce": "Luotu (vanhin ensin)",
"created_desc": "Luotu (uusin ensin)",
"modified_asce": "Muokattu (vanhin)",
"modified_desc": "Muokattu (uusin)",
"name_asce": "Z-A",
"name_desc": "A-Z",
"size_asce": "Koko (pienin ensin)",
"size_desc": "Koko (suurin ensin)",
"created_asce": "Luotu (vanhin ensin)"
"size_desc": "Koko (suurin ensin)"
},
"folder": {
"label": "Kansio",
@@ -47,9 +52,9 @@
"MultiplePluginsInstallModal": {
"confirm": "Haluatko varmasti tehdä seuraavat muutokset?",
"description": {
"install": "Asenna {{name}} {{version}}",
"reinstall": "Uudelleenasenna {{name}} {{version}}",
"update": "Päivitä {{name}} versioon {{version}}",
"install": "Asenna {{name}} {{version}}"
"update": "Päivitä {{name}} versioon {{version}}"
},
"ok_button": {
"idle": "Vahvista",
@@ -58,19 +63,19 @@
"title": {
"install_one": "Asenna yksi laajennus",
"install_other": "Asenna {{count}} laajennusta",
"update_one": "Päivitä yksi laajennus",
"update_other": "Päivitä {{count}} laajennusta",
"mixed_one": "Muuta yhtä laajennusta",
"mixed_other": "Muuta {{count}} laajennusta",
"reinstall_one": "Uudelleenasenna yksi laajennus",
"reinstall_other": "Uudelleenasenna {{count}} laajennusta"
"reinstall_other": "Uudelleenasenna {{count}} laajennusta",
"update_one": "Päivitä yksi laajennus",
"update_other": "Päivitä {{count}} laajennusta"
}
},
"PluginCard": {
"plugin_full_access": "Tällä laajennuksella on täysi pääsy Steam Deckkiisi.",
"plugin_install": "Asenna",
"plugin_no_desc": "Ei kuvausta.",
"plugin_version_label": "Laajennuksen versio",
"plugin_full_access": "Tällä laajennuksella on täysi pääsy Steam Deckkiisi."
"plugin_version_label": "Laajennuksen versio"
},
"PluginInstallModal": {
"install": {
@@ -93,22 +98,17 @@
"title": "Päivitä {{artifact}}"
}
},
"DropdownMultiselect": {
"button": {
"back": "Takaisin"
}
},
"PluginListIndex": {
"hide": "Pikavalikko: Piilota",
"no_plugin": "Ei asennettuja laajennuksia!",
"plugin_actions": "Laajennustoiminnot",
"reinstall": "Uudelleenasenna",
"reload": "Lataa uudelleen",
"show": "Pikavalikko: Näytä",
"uninstall": "Poista asennus",
"update_all_one": "Päivitä yksi laajennus",
"update_all_other": "Päivitä {{count}} laajennusta",
"update_to": "Päivitä versioon {{name}}",
"hide": "Pikavalikko: Piilota",
"show": "Pikavalikko: Näytä"
"update_to": "Päivitä versioon {{name}}"
},
"PluginListLabel": {
"hidden": "Piilotettu pikavalikosta"
@@ -117,6 +117,7 @@
"decky_title": "Decky",
"decky_update_available": "Päivitys versioon {{tag_name}} on saatavilla!",
"error": "Virhe",
"plugin_error_uninstall": "{{name}} lataaminen aiheutti yllä olevan poikkeuksen. Tämä tarkoittaa yleensä sitä, että laajennus vaatii päivityksen uudelle SteamUI-versiolle. Tarkista, onko päivitystä saatavilla, tai harkitse laajennuksen poistoa Decky-asetuksista, laajennukset-osiosta.",
"plugin_load_error": {
"message": "Virhe ladattaessa {{name}}-laajennusta",
"toast": "Virhe ladattaessa {{name}}"
@@ -127,8 +128,11 @@
"title": "Poista {{name}}"
},
"plugin_update_one": "Päivityksiä saatavilla yhdelle laajennukselle!",
"plugin_update_other": "Päivityksiä saatavilla {{count}} laajennukselle!",
"plugin_error_uninstall": "{{name}} lataaminen aiheutti yllä olevan poikkeuksen. Tämä tarkoittaa yleensä sitä, että laajennus vaatii päivityksen uudelle SteamUI-versiolle. Tarkista, onko päivitystä saatavilla, tai harkitse laajennuksen poistoa Decky-asetuksista, laajennukset-osiosta."
"plugin_update_other": "Päivityksiä saatavilla {{count}} laajennukselle!"
},
"PluginView": {
"hidden_one": "Yksi laajennus on piilotettu tästä luettelosta",
"hidden_other": "{{count}} laajennusta on piilotettu tästä luettelosta"
},
"RemoteDebugging": {
"remote_cef": {
@@ -153,13 +157,13 @@
"button_zip": "Selaa",
"header": "Kolmannen osapuolen laajennukset",
"label_desc": "URL-osoite",
"label_zip": "Asenna laajennus ZIP-tiedostosta",
"label_url": "Asenna laajennus URL-osoitteesta"
"label_url": "Asenna laajennus URL-osoitteesta",
"label_zip": "Asenna laajennus ZIP-tiedostosta"
},
"valve_internal": {
"desc1": "Ottaa käyttöön Valven sisäisen kehittäjävalikon.",
"desc2": "Älä koske mihinkään tässä valikossa, ellet tiedä mitä se tekee.",
"label": "Ota Valve Internal käyttöön",
"desc1": "Ottaa käyttöön Valven sisäisen kehittäjävalikon."
"label": "Ota Valve Internal käyttöön"
}
},
"SettingsGeneralIndex": {
@@ -192,8 +196,8 @@
},
"Store": {
"store_contrib": {
"label": "Osallistuminen",
"desc": "Mikäli haluat julkaista Decky Plugin Storeen, tarkista GitHubin SteamDeckHomebrew/decky-plugin-template -esimerkkitietovarasto. Tietoa kehityksestä ja jakelusta löytyy README:stä."
"desc": "Mikäli haluat julkaista Decky Plugin Storeen, tarkista GitHubin SteamDeckHomebrew/decky-plugin-template -esimerkkitietovarasto. Tietoa kehityksestä ja jakelusta löytyy README:stä.",
"label": "Osallistuminen"
},
"store_filter": {
"label": "Suodin",
@@ -218,8 +222,8 @@
},
"store_testing_cta": "Harkitse uusien lisäosien testaamista auttaaksesi Decky Loader -tiimiä!",
"store_testing_warning": {
"label": "Tervetuloa testausmyymälä-kanavalle",
"desc": "Voit käyttää tätä myymäläkanavaa testataksesi uusimpia laajennusversioita. Muista jättää palautetta GitHubissa, jotta laajennus voidaan päivittää kaikille käyttäjille."
"desc": "Voit käyttää tätä myymäläkanavaa testataksesi uusimpia laajennusversioita. Muista jättää palautetta GitHubissa, jotta laajennus voidaan päivittää kaikille käyttäjille.",
"label": "Tervetuloa testausmyymälä-kanavalle"
}
},
"StoreSelect": {
@@ -252,9 +256,5 @@
"reloading": "Uudelleenladataan",
"updating": "Päivitetään"
}
},
"PluginView": {
"hidden_one": "Yksi laajennus on piilotettu tästä luettelosta",
"hidden_other": "{{count}} laajennusta on piilotettu tästä luettelosta"
}
}
@@ -1,5 +1,161 @@
{
"BranchSelect": {
"update_channel": {
"label": "Canal de mise à jour",
"prerelease": "Avant-première",
"stable": "Stable",
"testing": "Test"
}
},
"Developer": {
"5secreload": "Rechargement dans 5 secondes",
"disabling": "Désactivation des React DevTools",
"enabling": "Activation des React DevTools"
},
"DropdownMultiselect": {
"button": {
"back": "Retour"
}
},
"FilePickerError": {
"errors": {
"file_not_found": "Le chemin spécifié n'est pas valide. Veuillez vérifier et essayer à nouveau.",
"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é.",
"unknown": "Une erreur inconnue est survenue. L'erreur est : {{raw_error}}"
}
},
"FilePickerIndex": {
"file": {
"select": "Sélectionner ce fichier"
},
"files": {
"all_files": "Tout les fichiers",
"file_type": "Type de fichier",
"show_hidden": "Afficher les fichiers cachés"
},
"filter": {
"created_asce": "Création (Plus vieux)",
"created_desc": "Création (Plus récent)",
"modified_asce": "Modifié (Plus vieux)",
"modified_desc": "Modifié (Plus récent)",
"name_asce": "Z-A",
"name_desc": "A-Z",
"size_asce": "Taille (Plus petit)",
"size_desc": "Taille (Plus grand)"
},
"folder": {
"label": "Dossier",
"select": "Utiliser ce dossier",
"show_more": "Afficher plus de fichiers"
}
},
"MultiplePluginsInstallModal": {
"confirm": "Êtes-vous sûr de vouloir apporter les modifications suivantes ?",
"description": {
"install": "Installer {{name}} {{version}}",
"reinstall": "Réinstaller {{name}} {{version}}",
"update": "Mettre à jour {{name}} à {{version}}"
},
"ok_button": {
"idle": "Confirmer",
"loading": "En cours"
},
"title": {
"install_many": "Installer {{count}} plugins",
"install_one": "Installer 1 plugin",
"install_other": "Installer {{count}} plugins",
"mixed_many": "Modifier {{count}} plugins",
"mixed_one": "Modifier {{count}} plugin",
"mixed_other": "Modifier {{count}} plugins",
"reinstall_many": "Réinstaller {{count}} plugins",
"reinstall_one": "Réinstaller 1 plugin",
"reinstall_other": "Réinstaller {{count}} plugins",
"update_many": "Mettre à jour {{count}} plugins",
"update_one": "Mettre à jour 1 plugin",
"update_other": "Mettre à jour {{count}} plugins"
}
},
"PluginCard": {
"plugin_full_access": "Ce plugin a un accès complet à votre Steam Deck.",
"plugin_install": "Installer",
"plugin_no_desc": "Aucune description fournie.",
"plugin_version_label": "Version du plugin"
},
"PluginInstallModal": {
"install": {
"button_idle": "Installer",
"button_processing": "Installation en cours",
"desc": "Êtes-vous sûr de vouloir installer {{artifact}} {{version}}?",
"title": "Installer {{artifact}}"
},
"no_hash": "Ce plugin n'a pas de somme de contrôle, vous l'installez à vos risques et périls.",
"reinstall": {
"button_idle": "Réinstaller",
"button_processing": "Réinstallation en cours",
"desc": "Êtes-vous sûr de vouloir réinstaller {{artifact}} {{version}}?",
"title": "Réinstaller {{artifact}}"
},
"update": {
"button_idle": "Mettre à jour",
"button_processing": "Mise à jour",
"desc": "Êtes-vous sûr de vouloir mettre à jour {{artifact}} {{version}}?",
"title": "Mettre à jour {{artifact}}"
}
},
"PluginListIndex": {
"freeze": "Geler les mises à jour",
"hide": "Accès rapide : Cacher",
"no_plugin": "Aucun plugin installé !",
"plugin_actions": "Plugin Actions",
"reinstall": "Réinstaller",
"reload": "Recharger",
"show": "Accès Rapide : Afficher",
"unfreeze": "Autoriser les mises à jour",
"uninstall": "Désinstaller",
"update_all_many": "Mettre à jour {{count}} plugins",
"update_all_one": "Mettre à jour 1 plugin",
"update_all_other": "Mettre à jour {{count}} plugins",
"update_to": "Mettre à jour vers {{name}}"
},
"PluginListLabel": {
"hidden": "Caché du menu d'accès rapide"
},
"PluginLoader": {
"decky_title": "Decky",
"decky_update_available": "Mise à jour vers {{tag_name}} disponible !",
"error": "Erreur",
"plugin_error_uninstall": "Allez sur {{name}} dans le menu de Decky si vous voulez désinstaller ce plugin.",
"plugin_load_error": {
"message": "Erreur lors du chargement du plugin {{name}}",
"toast": "Erreur lors du chargement de {{name}}"
},
"plugin_uninstall": {
"button": "Désinstaller",
"desc": "Êtes-vous sûr.e de vouloir désinstaller {{name}} ?",
"title": "Désinstaller {{name}}"
},
"plugin_update_many": "Mises à jour disponible pour {{count}} plugins !",
"plugin_update_one": "Mise à jour disponible pour 1 plugin !",
"plugin_update_other": "Mises à jour disponible pour {{count}} plugins !"
},
"PluginView": {
"hidden_many": "{{count}} plugins sont masqués de cette liste",
"hidden_one": "1 plugin est masqué dans cette liste",
"hidden_other": "{{count}} plugins sont masqués de cette liste"
},
"RemoteDebugging": {
"remote_cef": {
"desc": "Autoriser l'accès non authentifié au débogueur CEF à toute personne de votre réseau",
"label": "Autoriser le débogage CEF à distance"
}
},
"SettingsDeveloperIndex": {
"cef_console": {
"button": "Ouvrir la console",
"desc": "Ouvre la console CEF. Utile uniquement à des fins de débogage. Les éléments présentés ici sont potentiellement dangereux et ne doivent être utilisés que si vous êtes un développeur de plugins ou si vous êtes dirigé ici par un de ces développeurs.",
"label": "CEF Console"
},
"header": "Autre",
"react_devtools": {
"desc": "Permet la connexion à un ordinateur exécutant React DevTools. Changer ce paramètre rechargera Steam. Définissez l'adresse IP avant l'activation.",
"ip_label": "IP",
@@ -19,113 +175,6 @@
"label": "Activer Valve Internal"
}
},
"BranchSelect": {
"update_channel": {
"prerelease": "Avant-première",
"label": "Canal de mise à jour",
"stable": "Stable",
"testing": "Test"
}
},
"StoreSelect": {
"store_channel": {
"label": "Canal du Plugin Store",
"testing": "Test",
"custom": "Personnalisé",
"default": "Par défaut"
},
"custom_store": {
"label": "Plugin Store personnalisé",
"url_label": "URL"
}
},
"Updater": {
"decky_updates": "Mises à jour de Decky",
"no_patch_notes_desc": "pas de notes de mise à jour pour cette version",
"patch_notes_desc": "Notes de mise à jour",
"updates": {
"check_button": "Chercher les mises à jour",
"checking": "Recherche",
"cur_version": "Version actuelle: {{ver}}",
"install_button": "Installer la mise à jour",
"label": "Mises à jour",
"lat_version": "À jour: version {{ver}}",
"reloading": "Rechargement",
"updating": "Mise à jour en cours"
}
},
"Developer": {
"5secreload": "Rechargement dans 5 secondes",
"disabling": "Désactivation",
"enabling": "Activation"
},
"FilePickerIndex": {
"folder": {
"select": "Utiliser ce dossier"
}
},
"PluginCard": {
"plugin_full_access": "Ce plugin a un accès complet à votre Steam Deck.",
"plugin_install": "Installer",
"plugin_no_desc": "Aucune description fournie.",
"plugin_version_label": "Version du plugin"
},
"PluginInstallModal": {
"install": {
"button_idle": "Installer",
"button_processing": "Installation en cours",
"title": "Installer {{artifact}}",
"desc": "Êtes-vous sûr de vouloir installer {{artifact}} {{version}}?"
},
"no_hash": "Ce plugin n'a pas de somme de contrôle, vous l'installez à vos risques et périls.",
"reinstall": {
"button_idle": "Réinstaller",
"button_processing": "Réinstallation en cours",
"desc": "Êtes-vous sûr de vouloir réinstaller {{artifact}} {{version}}?",
"title": "Réinstaller {{artifact}}"
},
"update": {
"button_idle": "Mettre à jour",
"button_processing": "Mise à jour",
"title": "Mettre à jour {{artifact}}",
"desc": "Êtes-vous sûr de vouloir mettre à jour {{artifact}} {{version}}?"
}
},
"PluginListIndex": {
"plugin_actions": "Plugin Actions",
"reinstall": "Réinstaller",
"reload": "Recharger",
"uninstall": "Désinstaller",
"update_to": "Mettre à jour vers {{name}}",
"no_plugin": "Aucun plugin installé !",
"update_all_one": "",
"update_all_many": "",
"update_all_other": ""
},
"PluginLoader": {
"decky_title": "Decky",
"error": "Erreur",
"plugin_error_uninstall": "Allez sur {{name}} dans le menu de Decky si vous voulez désinstaller ce plugin.",
"plugin_load_error": {
"message": "Erreur lors du chargement du plugin {{name}}",
"toast": "Erreur lors du chargement de {{name}}"
},
"decky_update_available": "Mise à jour vers {{tag_name}} disponible !",
"plugin_uninstall": {
"button": "Désinstaller",
"title": "Désinstaller {{name}}",
"desc": "Êtes-vous sûr.e de vouloir désinstaller {{name}} ?"
},
"plugin_update_one": "",
"plugin_update_many": "",
"plugin_update_other": ""
},
"RemoteDebugging": {
"remote_cef": {
"desc": "Autoriser l'accès non authentifié au débogueur CEF à toute personne de votre réseau",
"label": "Autoriser le débogage CEF à distance"
}
},
"SettingsGeneralIndex": {
"about": {
"decky_version": "Version de Decky",
@@ -137,6 +186,11 @@
"developer_mode": {
"label": "Mode développeur"
},
"notifications": {
"decky_updates_label": "Mise à jour Decky disponible",
"header": "Notifications",
"plugin_updates_label": "Mises à jour du plugin disponibles"
},
"other": {
"header": "Autre"
},
@@ -147,7 +201,8 @@
"SettingsIndex": {
"developer_title": "Développeur",
"general_title": "Général",
"plugins_title": "Plugins"
"plugins_title": "Plugins",
"testing_title": "Essai"
},
"Store": {
"store_contrib": {
@@ -173,29 +228,50 @@
"about": "À propos",
"alph_asce": "Alphabétique (Z à A)",
"alph_desc": "Alphabétique (A à Z)",
"date_asce": "Plus ancien en premier",
"date_desc": "Le plus récent d'abord",
"downloads_asce": "Le moins téléchargé en premier",
"downloads_desc": "Les plus téléchargés en premier",
"title": "Explorer"
},
"store_testing_cta": "Pensez à tester de nouveaux plugins pour aider l'équipe Decky Loader !"
"store_testing_cta": "Pensez à tester de nouveaux plugins pour aider l'équipe Decky Loader !",
"store_testing_warning": {
"desc": "Vous pouvez utiliser cette chaîne de magasin pour tester des versions de plugins. Assurez-vous de laisser des commentaires sur GitHub afin que le plugin puisse être mis à jour pour tous les utilisateurs.",
"label": "Bienvenue sur la chaîne du magasin de tests"
}
},
"PluginView": {
"hidden_one": "",
"hidden_many": "",
"hidden_other": ""
"StoreSelect": {
"custom_store": {
"label": "Plugin Store personnalisé",
"url_label": "URL"
},
"store_channel": {
"custom": "Personnalisé",
"default": "Par défaut",
"label": "Canal du Plugin Store",
"testing": "Test"
}
},
"MultiplePluginsInstallModal": {
"title": {
"reinstall_one": "",
"reinstall_many": "",
"reinstall_other": "",
"install_one": "",
"install_many": "",
"install_other": "",
"mixed_one": "",
"mixed_many": "",
"mixed_other": "",
"update_one": "",
"update_many": "",
"update_other": ""
"Testing": {
"download": "Télécharger"
},
"TitleView": {
"decky_store_desc": "Ouvrir le magasin Decky",
"settings_desc": "Ouvrir les paramètres de Decky"
},
"Updater": {
"decky_updates": "Mises à jour de Decky",
"no_patch_notes_desc": "pas de notes de mise à jour pour cette version",
"patch_notes_desc": "Notes de mise à jour",
"updates": {
"check_button": "Chercher les mises à jour",
"checking": "Recherche",
"cur_version": "Version actuelle: {{ver}}",
"install_button": "Installer la mise à jour",
"label": "Mises à jour",
"lat_version": "À jour: version {{ver}}",
"reloading": "Rechargement",
"updating": "Mise à jour en cours"
}
}
}
@@ -20,8 +20,8 @@
"FilePickerError": {
"errors": {
"file_not_found": "Il percorso specificato non è valido. Controllalo e prova a reinserirlo di nuovo.",
"unknown": "È avvenuto un'errore sconosciuto. L'errore segnalato è {{raw_error}}",
"perm_denied": "Il tuo utente non ha accesso alla directory specificata. Verifica se l'utente corrente (è deck su Steam Deck di default) ha i permessi corrispondenti per accedere alla cartella/file desiderato."
"perm_denied": "Il tuo utente non ha accesso alla directory specificata. Verifica se l'utente corrente (è deck su Steam Deck di default) ha i permessi corrispondenti per accedere alla cartella/file desiderato.",
"unknown": "È avvenuto un'errore sconosciuto. L'errore segnalato è {{raw_error}}"
}
},
"FilePickerIndex": {
@@ -52,7 +52,9 @@
"MultiplePluginsInstallModal": {
"confirm": "Sei sicuro di voler effettuare le modifiche seguenti?",
"description": {
"downgrade": "Downgrada {{name}} a versione {{version}}",
"install": "Installa {{name}} {{version}}",
"overwrite": "Sovrascrive {{name}} con {{version}}",
"reinstall": "Reinstalla {{name}} {{version}}",
"update": "Aggiorna {{name}} alla versione {{version}}"
},
@@ -61,27 +63,43 @@
"loading": "Elaboro"
},
"title": {
"install_one": "Installa un plugin",
"downgrade_many": "Downgrada {{count}} plugins",
"downgrade_one": "Downgrada un plugin",
"downgrade_other": "Downgrada {{count}} plugins",
"install_many": "Installa {{count}} plugins",
"install_one": "Installa un plugin",
"install_other": "Installa {{count}} plugins",
"mixed_one": "Modifica un plugin",
"mixed_many": "Modifica {{count}} plugins",
"mixed_one": "Modifica un plugin",
"mixed_other": "Modifica {{count}} plugins",
"reinstall_one": "Reinstalla un plugin",
"overwrite_many": "Sovrascrivi {{count}} plugins",
"overwrite_one": "Sovrascrivi un plugin",
"overwrite_other": "Sovrascrivi {{count}} plugins",
"reinstall_many": "Reinstalla {{count}} plugins",
"reinstall_one": "Reinstalla un plugin",
"reinstall_other": "Reinstalla {{count}} plugins",
"update_one": "Aggiorna un plugin",
"update_many": "Aggiorna {{count}} plugins",
"update_one": "Aggiorna un plugin",
"update_other": "Aggiorna {{count}} plugins"
}
},
"PluginCard": {
"plugin_downgrade": "Downgrada",
"plugin_full_access": "Questo plugin ha accesso completo al tuo Steam Deck.",
"plugin_install": "Installa",
"plugin_no_desc": "Nessuna descrizione fornita.",
"plugin_overwrite": "Sovrascrivi",
"plugin_reinstall": "Reinstalla",
"plugin_update": "Aggiorna",
"plugin_version_label": "Versione Plugin"
},
"PluginInstallModal": {
"downgrade": {
"button_idle": "Downgrada",
"button_processing": "Downgradando",
"desc": "Sei sicuro di voler downgradare {{artifact}} alla versione {{version}}?",
"title": "Downgrada {{artifact}}"
},
"install": {
"button_idle": "Installa",
"button_processing": "Installando",
@@ -89,6 +107,13 @@
"title": "Installa {{artifact}}"
},
"no_hash": "Questo plugin non ha un hash associato, lo stai installando a tuo rischio e pericolo.",
"not_installed": "(non installato)",
"overwrite": {
"button_idle": "Sovrascrivi",
"button_processing": "Sovrascrivendo",
"desc": "Sei sicuro di voler sovrascrivere {{artifact}} con la versione {{version}}?",
"title": "Sovrascrivi {{artifact}}"
},
"reinstall": {
"button_idle": "Reinstalla",
"button_processing": "Reinstallando",
@@ -98,20 +123,22 @@
"update": {
"button_idle": "Aggiorna",
"button_processing": "Aggiornando",
"desc": "Sei sicuro di voler aggiornare {{artifact}} {{version}}?",
"desc": "Sei sicuro di voler aggiornare {{artifact}} alla versione {{version}}?",
"title": "Aggiorna {{artifact}}"
}
},
"PluginListIndex": {
"freeze": "Congela aggiornamenti",
"hide": "Accesso rapido: Nascondi",
"no_plugin": "Nessun plugin installato!",
"plugin_actions": "Operazioni sui plugins",
"reinstall": "Reinstalla",
"reload": "Ricarica",
"show": "Accesso rapido: Mostra",
"unfreeze": "Permetti aggiornamenti",
"uninstall": "Rimuovi",
"update_all_one": "Aggiorna un plugin",
"update_all_many": "Aggiorna {{count}} plugins",
"update_all_one": "Aggiorna un plugin",
"update_all_other": "Aggiorna {{count}} plugins",
"update_to": "Aggiorna a {{name}}"
},
@@ -132,13 +159,13 @@
"desc": "Sei sicuro di voler rimuovere {{name}}?",
"title": "Rimuovi {{name}}"
},
"plugin_update_one": "Aggiornamento disponibile per 1 plugin!",
"plugin_update_many": "Aggiornamenti disponibili per {{count}} plugins!",
"plugin_update_one": "Aggiornamento disponibile per 1 plugin!",
"plugin_update_other": "Aggiornamenti disponibili per {{count}} plugins!"
},
"PluginView": {
"hidden_one": "Un plugin è nascosto dalla lista",
"hidden_many": "Sono nascosti {{count}} plugin dalla lista",
"hidden_one": "Un plugin è nascosto dalla lista",
"hidden_other": "Sono nascosti {{count}} plugin dalla lista"
},
"RemoteDebugging": {
@@ -184,24 +211,35 @@
"developer_mode": {
"label": "Modalità sviluppatore"
},
"notifications": {
"decky_updates_label": "Aggiornamenti di Decky",
"header": "Notifiche",
"plugin_updates_label": "Aggiornamenti dei plugins"
},
"other": {
"header": "Altro"
},
"updates": {
"header": "Aggiornamenti"
},
"notifications": {
"header": "Notifiche",
"decky_updates_label": "Aggiornamenti di Decky",
"plugin_updates_label": "Aggiornamenti dei plugins"
}
},
"SettingsIndex": {
"developer_title": "Sviluppatore",
"general_title": "Generali",
"plugins_title": "Plugins"
"plugins_title": "Plugins",
"testing_title": "Testing"
},
"Store": {
"download_progress_info": {
"download_remote": "Scaricando qualunque binario esterno",
"download_zip": "Scarico plugin",
"increment_count": "Incremento il numero di download",
"installing_plugin": "Installo il plugin",
"open_zip": "Apro file zip",
"parse_zip": "Analizzo file zip",
"start": "Inizializzo",
"uninstalling_previous": "Rimuovo la copia precedente"
},
"store_contrib": {
"desc": "Se desideri contribuire allo store di Decky, puoi trovare un template caricato su GitHub all'indirizzo SteamDeckHomebrew/decky-plugin-template. Informazioni riguardo sviluppo e distribuzione sono disponibili nel README.",
"label": "Contribuisci"
@@ -225,12 +263,16 @@
"about": "Riguardo a",
"alph_asce": "Alfabetico (Z a A)",
"alph_desc": "Alfabetico (A a Z)",
"date_asce": "Per più vecchio",
"date_desc": "Per più recente",
"downloads_asce": "Per meno scaricato",
"downloads_desc": "Per più scaricato",
"title": "Sfoglia"
},
"store_testing_cta": "Valuta la possibilità di testare nuovi plugin per aiutare il team di Decky Loader!",
"store_testing_warning": {
"label": "Benvenuto nel Negozio di Test dei Plugins",
"desc": "Puoi usare questo canale del negozio per testare versioni di plugin sperimentali. Assicurati di lasciare un feedback su Github dopo averlo testato in modo che il plugin possa essere promosso a stabile per tutti gli altri utenti o per permettere allo sviluppatore di plugin di correggere eventuali errori."
"desc": "Puoi usare questo canale del negozio per testare versioni di plugin sperimentali. Assicurati di lasciare un feedback su Github dopo averlo testato in modo che il plugin possa essere promosso a stabile per tutti gli altri utenti o per permettere allo sviluppatore di plugin di correggere eventuali errori.",
"label": "Benvenuto nel Negozio di Test dei Plugins"
}
},
"StoreSelect": {
@@ -245,6 +287,17 @@
"testing": "In prova"
}
},
"Testing": {
"download": "Scarica",
"error": "Errore durante l'installazione della PR",
"header": "Le versioni mostrate di Decky Loader sono create da Pull Request aperte da terze parti. Il team di Decky Loader non ha verificato la loro funzionalita o sicurezza, e potrebbero essere vecchie.",
"loading": "Carico Pull Request aperte...",
"start_download_toast": "Scarico PR #{{id}}"
},
"TitleView": {
"decky_store_desc": "Apri lo store di Decky",
"settings_desc": "Apri le impostazioni di Decky"
},
"Updater": {
"decky_updates": "Aggiornamento di Decky",
"no_patch_notes_desc": "nessuna patch notes per questa versione",
@@ -259,9 +312,5 @@
"reloading": "Ricaricando",
"updating": "Aggiornando"
}
},
"TitleView": {
"settings_desc": "Apri le impostazioni di Decky",
"decky_store_desc": "Apri lo store di Decky"
}
}
+276
View File
@@ -0,0 +1,276 @@
{
"BranchSelect": {
"update_channel": {
"label": "アップデートチャンネル",
"prerelease": "プレリリース",
"stable": "安定",
"testing": "テスト"
}
},
"Developer": {
"5secreload": "5秒以内に再読み込みされます",
"disabling": "React DevToolsを無効",
"enabling": "React DevToolsを有効"
},
"DropdownMultiselect": {
"button": {
"back": "戻る"
}
},
"FilePickerError": {
"errors": {
"file_not_found": "指定されたパスは無効です。 内容をご確認の上、正しく入力し直してください。",
"perm_denied": "選択したパスへのアクセス権がありません。選択したフォルダ/ファイルのアクセス権がユーザー(Steam Deckのdeckユーザー)に合わせて正しく設定されていることを確認してください。",
"unknown": "不明なエラーが発生しました。 エラー内容は次のとおりです: {{raw_error}}"
}
},
"FilePickerIndex": {
"file": {
"select": "ファイルを選択"
},
"files": {
"all_files": "すべてのファイル",
"file_type": "ファイルタイプ",
"show_hidden": "非表示ファイルを表示する"
},
"filter": {
"created_asce": "作成日(古い順)",
"created_desc": "作成日(新しい順)",
"modified_asce": "更新日(古い順)",
"modified_desc": "更新日(新しい順)",
"name_asce": "Z-A",
"name_desc": "A-Z",
"size_asce": "サイズ(小さい順)",
"size_desc": "サイズ(大きい順)"
},
"folder": {
"label": "フォルダ",
"select": "このフォルダを使用",
"show_more": "その他のファイルを表示"
}
},
"MultiplePluginsInstallModal": {
"confirm": "以下の変更を加えてもよろしいですか?",
"description": {
"install": "インストール {{name}} {{version}}",
"reinstall": "再インストール {{name}} {{version}}",
"update": "アップデート {{name}} {{version}}"
},
"ok_button": {
"idle": "確認",
"loading": "作業中"
},
"title": {
"install_other": "{{count}} 個のプラグインをインストール",
"mixed_other": "{{count}} 個のプラグインを修正",
"reinstall_other": "{{count}} 個のプラグインを再インストール",
"update_other": "{{count}} 個のプラグインをアップデート"
}
},
"PluginCard": {
"plugin_full_access": "このプラグインはSteam Deckの全てのアクセス権を持ちます。",
"plugin_install": "インストール",
"plugin_no_desc": "説明はありません。",
"plugin_version_label": "プラグインバージョン"
},
"PluginInstallModal": {
"install": {
"button_idle": "インストール",
"button_processing": "インストール中",
"desc": "{{artifact}} {{version}} をインストールしてもよろしいですか?",
"title": "{{artifact}} をインストール"
},
"no_hash": "このプラグインにはハッシュがありません。ご自身の責任でインストールしてください。",
"reinstall": {
"button_idle": "再インストール",
"button_processing": "再インストール中",
"desc": "{{artifact}} {{version}} を再インストールしてもよろしいですか?",
"title": "{{artifact}} を再インストール"
},
"update": {
"button_idle": "アップデート",
"button_processing": "アップデート中",
"desc": "{{artifact}} {{version}} をアップデートしてもよろしいですか?",
"title": "{{artifact}} をアップデート"
}
},
"PluginListIndex": {
"freeze": "アップデートを凍結",
"hide": "クイックアクセス: 非表示",
"no_plugin": "プラグインがインストールされていません!",
"plugin_actions": "プラグインアクション",
"reinstall": "再インストール",
"reload": "再読み込み",
"show": "クイックアクセス: 表示",
"unfreeze": "アップデートを許可",
"uninstall": "アンインストール",
"update_all_other": "{{count}} 個のプラグインをアップデート",
"update_to": "{{name}} を更新"
},
"PluginListLabel": {
"hidden": "クイックアクセスメニューから表示されません"
},
"PluginLoader": {
"decky_title": "Decky",
"decky_update_available": "{{tag_name}} のアップデートが利用可能です!",
"error": "エラー",
"plugin_error_uninstall": "{{name}} プラグインを読み込む際に上記のような例外が発生しました。 これは通常、SteamUIの最新バージョンに合ったプラグインのアップデートが必要な場合に発生します。Decky設定のプラグインセクションでアップデートがあるかどうかを確認するか、アンインストールをお試しください。",
"plugin_load_error": {
"message": "プラグイン {{name}} の読み込みエラー",
"toast": "{{name}} の読み込みエラー"
},
"plugin_uninstall": {
"button": "アンインストール",
"desc": "{{name}} をアンインストールしてもよろしいですか?",
"title": "{{name}} をアンインストール"
},
"plugin_update_other": "{{count}} 個のプラグインのアップデートが利用可能です!"
},
"PluginView": {
"hidden_other": "{{count}} 個のプラグインがこのリストから非表示になっています"
},
"RemoteDebugging": {
"remote_cef": {
"desc": "ネットワーク上のすべてのユーザーにCEFデバッガへの非認証アクセスを許可します",
"label": "リモート CEF デバッグを許可する"
}
},
"SettingsDeveloperIndex": {
"cef_console": {
"button": "コンソールを開く",
"desc": "CEFコンソールを開きます。デバッグ目的でのみ使用してください。これらの項目は危険な可能性があるので、プラグイン開発者であるか、開発者のガイドに従う場合のみ使用する必要があります。",
"label": "CEFコンソール"
},
"header": "その他",
"react_devtools": {
"desc": "React DevToolsを実行しているコンピューターへの接続を有効にします。この設定を変更すると、Steam が再ロードされます。有効にする前にIPアドレスを設定してください。",
"ip_label": "IP",
"label": "React DevTools を有効化"
},
"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を有効"
}
},
"SettingsGeneralIndex": {
"about": {
"decky_version": "Deckyバージョン",
"header": "情報"
},
"beta": {
"header": "ベータ版への参加"
},
"developer_mode": {
"label": "開発者モード"
},
"notifications": {
"decky_updates_label": "Deckyのアップデートが利用可能な場合に通知",
"header": "通知",
"plugin_updates_label": "プラグインのアップデートが利用可能な場合に通知"
},
"other": {
"header": "その他"
},
"updates": {
"header": "アップデート"
}
},
"SettingsIndex": {
"developer_title": "開発者",
"general_title": "一般",
"plugins_title": "プラグイン",
"testing_title": "テスト"
},
"Store": {
"download_progress_info": {
"download_zip": "プラグインのダウンロード中",
"increment_count": "ダウンロード数の増加",
"installing_plugin": "プラグインのインストール中",
"open_zip": "zipファイルを展開中",
"parse_zip": "zipファイルの解析中",
"start": "初期化中",
"uninstalling_previous": "以前のコピーのアンインストール"
},
"store_contrib": {
"desc": "Decky Plugin Storeに貢献したい場合は、GitHubのSteamDeckHomebrew/decky-plugin-templateリポジトリを確認してください。 開発と配布に関する情報は README で入手できます。",
"label": "貢献"
},
"store_filter": {
"label": "フィルター",
"label_def": "すべて"
},
"store_search": {
"label": "検索"
},
"store_sort": {
"label": "並べ替え",
"label_def": "直近のアップデート(新しい順)"
},
"store_source": {
"desc": "すべてのプラグインのソース コードは、GitHubのSteamDeckHomebrew/decky-plugin-databaseリポジトリで入手できます。",
"label": "ソースコード"
},
"store_tabs": {
"about": "概要",
"alph_asce": "アルファベット(Z to A)",
"alph_desc": "アルファベット(A to Z)",
"date_asce": "古い順",
"date_desc": "新しい順",
"downloads_asce": "ダウンロード数が少ない順",
"downloads_desc": "ダウンロード数が多い順",
"title": "閲覧"
},
"store_testing_cta": "Decky Loaderチームを支援するために、新しいプラグインのテストを検討してください!",
"store_testing_warning": {
"desc": "このストアチャンネルを使用して、最先端のプラグイン バージョンをテストできます。 すべてのユーザーがプラグインを更新できるように、必ずGitHubにフィードバックを残してください。",
"label": "テストストア チャンネルへようこそ"
}
},
"StoreSelect": {
"custom_store": {
"label": "カスタムストア",
"url_label": "URL"
},
"store_channel": {
"custom": "カスタム",
"default": "デフォルト",
"label": "ストアチャンネル",
"testing": "テスト"
}
},
"Testing": {
"download": "ダウンロード",
"error": "PRのインストールエラー",
"header": "Decky Loaderの以下のバージョンは、公開されているサードパーティのPull Requestからビルドされたものです。 Decky Loaderチームはその機能や安全性を検証しておらず、内容も古い可能性があります。",
"loading": "Pull Requestの読み込み中...",
"start_download_toast": "PR #{{id}}のダウンロード中"
},
"TitleView": {
"decky_store_desc": "Deckyストアを開く",
"settings_desc": "Decky設定を開く"
},
"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": "アップデート中"
}
}
}
+1
View File
@@ -0,0 +1 @@
{}
@@ -2,52 +2,76 @@
"BranchSelect": {
"update_channel": {
"label": "업데이트 배포 채널",
"prerelease": "사전 출시",
"stable": "안정",
"testing": "테스트",
"prerelease": "사전 출시"
"testing": "테스트"
}
},
"Developer": {
"5secreload": "5초 내로 다시 로드 됩니다",
"disabling": "React DevTools 비활성화",
"enabling": "React DevTools 활성화",
"5secreload": "5초 내로 다시 로드 됩니다"
"enabling": "React DevTools 활성화"
},
"DropdownMultiselect": {
"button": {
"back": "뒤로"
}
},
"FilePickerError": {
"errors": {
"file_not_found": "지정된 경로가 잘못되었습니다. 확인 후에 다시 입력해 주세요.",
"perm_denied": "선택한 경로에 접근 할 수 없습니다. 선택한 폴더/파일 접근 권한이 유저(Steam Deck의 deck 유저)에 맞게 올바르게 설정 되었는지 확인하세요.",
"unknown": "알 수 없는 오류가 발생했습니다. Raw 오류: {{raw_error}}"
}
},
"FilePickerIndex": {
"folder": {
"select": "이 폴더 사용",
"label": "폴더",
"show_more": "더 많은 파일 표시"
},
"filter": {
"created_asce": "만든 날짜 (오름차순)",
"modified_asce": "수정한 날짜 (오름차순)",
"created_desc": "만든 날짜 (내림차 순)",
"name_asce": "Z-A",
"name_desc": "A-Z",
"size_asce": "크기 (오름차순)",
"modified_desc": "수정한 날짜 (내림차순)",
"size_desc": "크기 (내림차순)"
"file": {
"select": "이 파일 선택"
},
"files": {
"all_files": "모든 파일",
"show_hidden": "숨김 파일 표시",
"file_type": "파일 형식"
"file_type": "파일 형식",
"show_hidden": "숨김 파일 표시"
},
"file": {
"select": "이 파일 선택"
"filter": {
"created_asce": "만든 날짜 (오름차순)",
"created_desc": "만든 날짜 (내림차 순)",
"modified_asce": "수정한 날짜 (오름차순)",
"modified_desc": "수정한 날짜 (내림차순)",
"name_asce": "Z-A",
"name_desc": "A-Z",
"size_asce": "크기 (오름차순)",
"size_desc": "크기 (내림차순)"
},
"folder": {
"label": "폴더",
"select": "이 폴더 사용",
"show_more": "더 많은 파일 표시"
}
},
"PluginView": {
"hidden_other": "플러그인 {{count}}개 숨김"
},
"PluginListLabel": {
"hidden": "빠른 액세스 메뉴에서 숨김"
"MultiplePluginsInstallModal": {
"confirm": "해당 수정을 적용하겠습니까?",
"description": {
"install": "{{name}} {{version}} 플러그인 설치",
"reinstall": "{{name}} {{version}} 재설치",
"update": "{{name}}의 {{version}} 업데이트 설치"
},
"ok_button": {
"idle": "확인",
"loading": "작업 중"
},
"title": {
"install_other": "플러그인 {{count}}개 설치",
"mixed_other": "플러그인 {{count}}개 수정",
"reinstall_other": "플러그인 {{count}}개 재설치",
"update_other": "플러그인 {{count}}개 업데이트"
}
},
"PluginCard": {
"plugin_full_access": "이 플러그인은 Steam Deck의 모든 접근 권한을 가집니다.",
"plugin_install": "설치",
"plugin_no_desc": "플러그인 설명이 제공되지 않았습니다.",
"plugin_version_label": "플러그인 버전",
"plugin_full_access": "이 플러그인은 Steam Deck의 모든 접근 권한을 가집니다."
"plugin_version_label": "플러그인 버전"
},
"PluginInstallModal": {
"install": {
@@ -56,6 +80,7 @@
"desc": "{{artifact}} {{version}}을(를) 설치하겠습니까?",
"title": "{{artifact}} 설치"
},
"no_hash": "이 플러그인은 해시 확인을 하지 않습니다, 설치에 따른 위험은 사용자가 감수해야 합니다.",
"reinstall": {
"button_idle": "재설치",
"button_processing": "재설치 중",
@@ -65,44 +90,31 @@
"update": {
"button_idle": "업데이트",
"button_processing": "업데이트 중",
"title": "{{artifact}} 업데이트",
"desc": "{{artifact}} {{version}} 업데이트를 설치하겠습니까?"
},
"no_hash": "이 플러그인은 해시 확인을 하지 않습니다, 설치에 따른 위험은 사용자가 감수해야 합니다."
},
"MultiplePluginsInstallModal": {
"title": {
"mixed_other": "플러그인 {{count}}개 수정",
"update_other": "플러그인 {{count}}개 업데이트",
"reinstall_other": "플러그인 {{count}}개 재설치",
"install_other": "플러그인 {{count}}개 설치"
},
"ok_button": {
"idle": "확인",
"loading": "작업 중"
},
"confirm": "해당 수정을 적용하겠습니까?",
"description": {
"install": "{{name}} {{version}} 플러그인 설치",
"update": "{{name}}의 {{version}} 업데이트 설치",
"reinstall": "{{name}} {{version}} 재설치"
"desc": "{{artifact}} {{version}} 업데이트를 설치하겠습니까?",
"title": "{{artifact}} 업데이트"
}
},
"PluginListIndex": {
"freeze": "업데이트 일시 중지",
"hide": "빠른 액세스 메뉴: 숨김",
"no_plugin": "설치된 플러그인이 없습니다!",
"plugin_actions": "플러그인 동작",
"reinstall": "재설치",
"reload": "다시 로드",
"uninstall": "설치 제거",
"show": "빠른 액세스 메뉴: 표시",
"hide": "빠른 액세스 메뉴: 숨김",
"unfreeze": "업데이트 허용",
"uninstall": "설치 제거",
"update_all_other": "플러그인 {{count}}개 업데이트",
"no_plugin": "설치된 플러그인이 없습니다!",
"update_to": "{{name}}(으)로 업데이트"
},
"PluginListLabel": {
"hidden": "빠른 액세스 메뉴에서 숨김"
},
"PluginLoader": {
"decky_title": "Decky",
"decky_update_available": "{{tag_name}} 업데이트를 설치할 수 있습니다!",
"error": "오류",
"plugin_error_uninstall": "{{name}} 플러그인을 불러올 때 위와 같은 예외가 발생했습니다. 이는 보통 SteamUI 최신 버전에 맞는 플러그인 업데이트가 필요할 때 발생합니다. Decky 설정의 플러그인 섹션에서 업데이트가 있는지 확인하거나 설치 제거를 시도 해 보세요.",
"plugin_load_error": {
"message": "{{name}} 플러그인 불러오기 오류",
"toast": "{{name}} 불러오기 오류"
@@ -112,26 +124,28 @@
"desc": "{{name}}을(를) 설치 제거하겠습니까?",
"title": "{{name}} 설치 제거"
},
"plugin_update_other": "플러그인 {{count}}개를 업데이트 할 수 있습니다!",
"plugin_error_uninstall": "{{name}} 플러그인을 불러올 때 위와 같은 예외가 발생했습니다. 이는 보통 SteamUI 최신 버전에 맞는 플러그인 업데이트가 필요할 때 발생합니다. Decky 설정의 플러그인 섹션에서 업데이트가 있는지 확인하거나 설치 제거를 시도 해 보세요."
"plugin_update_other": "플러그인 {{count}}개를 업데이트 할 수 있습니다!"
},
"PluginView": {
"hidden_other": "플러그인 {{count}}개 숨김"
},
"RemoteDebugging": {
"remote_cef": {
"label": "리모트 CEF 디버그 허용",
"desc": "네트워크의 모든 사용자에게 CEF 디버거에 대한 인증되지 않은 액세스 허용"
"desc": "네트워크의 모든 사용자에게 CEF 디버거에 대한 인증되지 않은 액세스 허용",
"label": "리모트 CEF 디버그 허용"
}
},
"SettingsDeveloperIndex": {
"cef_console": {
"button": "콘솔 열기",
"label": "CEF 콘솔",
"desc": "CEF 콘솔을 엽니다. 디버그 전용입니다. 이 항목들은 위험 가능성이 있으므로 플러그인 개발자이거나 개발자의 가이드를 따를 경우에만 사용해야 합니다."
"desc": "CEF 콘솔을 엽니다. 디버그 전용입니다. 이 항목들은 위험 가능성이 있으므로 플러그인 개발자이거나 개발자의 가이드를 따를 경우에만 사용해야 합니다.",
"label": "CEF 콘솔"
},
"header": "기타",
"react_devtools": {
"desc": "React DevTools를 실행하고 있는 컴퓨터에 연결을 활성화합니다. 이 설정을 변경하면 Steam이 다시 로드됩니다. 활성화하기 전에 IP 주소를 설정하세요.",
"ip_label": "IP",
"label": "React DevTools 활성화",
"desc": "React DevTools를 실행하고 있는 컴퓨터에 연결을 활성화합니다. 이 설정을 변경하면 Steam이 다시 로드됩니다. 활성화하기 전에 IP 주소를 설정하세요."
"label": "React DevTools 활성화"
},
"third_party_plugins": {
"button_install": "설치",
@@ -143,8 +157,8 @@
},
"valve_internal": {
"desc1": "Valve 내부 개발자 메뉴를 활성화합니다.",
"label": "Valve 내부 개발자 메뉴 활성화",
"desc2": "이 메뉴의 기능을 모른다면 어떤 것도 건드리지 마세요."
"desc2": "이 메뉴의 기능을 모른다면 어떤 것도 건드리지 마세요.",
"label": "Valve 내부 개발자 메뉴 활성화"
}
},
"SettingsGeneralIndex": {
@@ -158,24 +172,34 @@
"developer_mode": {
"label": "개발자 모드"
},
"notifications": {
"decky_updates_label": "Decky 업데이트 가능",
"header": "알림",
"plugin_updates_label": "플러그인 업데이트 가능"
},
"other": {
"header": "기타"
},
"updates": {
"header": "업데이트"
},
"notifications": {
"header": "알림",
"plugin_updates_label": "플러그인 업데이트 가능",
"decky_updates_label": "Decky 업데이트 가능"
}
},
"SettingsIndex": {
"developer_title": "개발자",
"general_title": "일반",
"plugins_title": "플러그인"
"plugins_title": "플러그인",
"testing_title": "테스트"
},
"Store": {
"download_progress_info": {
"download_zip": "플러그인 다운로드 중",
"increment_count": "다운로드 횟수 증가",
"installing_plugin": "플러그인 설치 중",
"open_zip": "압축 파일 여는 중",
"parse_zip": "압축 파일 파싱 중",
"start": "초기화 중",
"uninstalling_previous": "이전 복사본 설치 제거 중"
},
"store_contrib": {
"desc": "Decky 플러그인 스토어에 기여하고 싶다면 SteamDeckHomebrew/decky-plugin-template Github 저장소를 확인하세요. 개발 및 배포에 대한 정보는 README에서 확인할 수 있습니다.",
"label": "기여하기"
@@ -199,6 +223,10 @@
"about": "정보",
"alph_asce": "알파벳순 (Z-A)",
"alph_desc": "알파벳순 (A-Z)",
"date_asce": "오래된 순",
"date_desc": "최신 순",
"downloads_asce": "다운로드 수 낮은 순",
"downloads_desc": "다운로드 많은 순",
"title": "검색"
},
"store_testing_cta": "새로운 플러그인을 테스트하여 Decky Loader 팀을 도와주세요!",
@@ -214,11 +242,22 @@
},
"store_channel": {
"custom": "사용자 지정",
"label": "스토어 배포 채널",
"default": "기본",
"label": "스토어 배포 채널",
"testing": "테스트"
}
},
"Testing": {
"download": "다운로드",
"error": "PR 설치 오류",
"header": "다음 버전의 Decky Loader는 서드 파티 PR를 사용하여 빌드 되엇습니다. Decky Loader 팀은 해당 버전의 작동 여부와 안전성을 확인하지 않았으며 최신 버전이 아닐 수 도 있습니다.",
"loading": "열린 PR 로딩 중...",
"start_download_toast": "PR #{{id}} 다운로드 중"
},
"TitleView": {
"decky_store_desc": "Decky 스토어 열기",
"settings_desc": "Decky 설정 열기"
},
"Updater": {
"decky_updates": "Decky 업데이트",
"no_patch_notes_desc": "이 버전에는 패치 노트가 없습니다",
@@ -233,21 +272,5 @@
"reloading": "다시 로드 중",
"updating": "업데이트 중"
}
},
"FilePickerError": {
"errors": {
"file_not_found": "지정된 경로가 잘못되었습니다. 확인 후에 다시 입력해 주세요.",
"unknown": "알 수 없는 오류가 발생했습니다. Raw 오류: {{raw_error}}",
"perm_denied": "선택한 경로에 접근 할 수 없습니다. 선택한 폴더/파일 접근 권한이 유저(Steam Deck의 deck 유저)에 맞게 올바르게 설정 되었는지 확인하세요."
}
},
"DropdownMultiselect": {
"button": {
"back": "뒤로"
}
},
"TitleView": {
"settings_desc": "Decky 설정 열기",
"decky_store_desc": "Decky 스토어 열기"
}
}
+283
View File
@@ -0,0 +1,283 @@
{
"BranchSelect": {
"update_channel": {
"label": "Updatekanaal",
"prerelease": "Vooruitgave",
"stable": "Stabiel",
"testing": "Testen"
}
},
"Developer": {
"5secreload": "Bezig met herstarten in 5 seconden",
"disabling": "Bezig met uitschakelen van React DevTools",
"enabling": "Bezig met inschakelen van React DevTools"
},
"DropdownMultiselect": {
"button": {
"back": "Terug"
}
},
"FilePickerError": {
"errors": {
"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.",
"unknown": "Er is een onbekende fout opgetreden. De foutmelding is: {{raw_error}}"
}
},
"FilePickerIndex": {
"file": {
"select": "Dit bestand selecteren"
},
"files": {
"all_files": "Alle bestanden",
"file_type": "Bestandstype",
"show_hidden": "Verborgen bestanden tonen"
},
"filter": {
"created_asce": "Aanmaakdatum (oudste)",
"created_desc": "Aanmaakdatum (nieuwste)",
"modified_asce": "Gewijzigd op (oudste)",
"modified_desc": "Gewijzigd op (nieuwste)",
"name_asce": "Naam (Z-A)",
"name_desc": "Naam (A-Z)",
"size_asce": "Grootte (kleinste)",
"size_desc": "Grootte (grootste)"
},
"folder": {
"label": "Map",
"select": "Deze map gebruiken",
"show_more": "Meer bestanden tonen"
}
},
"MultiplePluginsInstallModal": {
"confirm": "Weet je zeker dat je de volgende wijzigingen wilt aanbrengen?",
"description": {
"install": "Installeer {{name}} {{version}}",
"reinstall": "Installeer {{name}} {{version}} opnieuw",
"update": "Werk {{name}} bij naar {{version}}"
},
"ok_button": {
"idle": "Bevestigen",
"loading": "Bezig"
},
"title": {
"install_one": "Installeer 1 plug-in",
"install_other": "Installeer {{count}} plug-ins",
"mixed_one": "Wijzig 1 plug-in",
"mixed_other": "Wijzig {{count}} plug-ins",
"reinstall_one": "Installeer 1 plug-in opnieuw",
"reinstall_other": "Installeer {{count}} plug-ins opnieuw",
"update_one": "Werk 1 plug-in bij",
"update_other": "Werk {{count}} plug-ins bij"
}
},
"PluginCard": {
"plugin_full_access": "Deze plug-in heeft volledige toegang tot uw Steam Deck.",
"plugin_install": "Installeren",
"plugin_no_desc": "Geen beschrijving gegeven.",
"plugin_version_label": "Plug-inversie"
},
"PluginInstallModal": {
"install": {
"button_idle": "Installeren",
"button_processing": "Bezig met installeren",
"desc": "Weet je zeker dat je {{artifact}} {{version}} wilt installeren?",
"title": "Installeer {{artifact}}"
},
"no_hash": "Deze plug-in heeft geen hash, je installeert deze op eigen risico.",
"reinstall": {
"button_idle": "Opnieuw installeren",
"button_processing": "Bezig met opnieuw te installeren",
"desc": "Weet je zeker dat je {{artifact}} {{version}} opnieuw wilt installeren?",
"title": "Installeer {{artifact}} opnieuw"
},
"update": {
"button_idle": "Bijwerken",
"button_processing": "Bezig met bijwerken",
"desc": "Weet je zeker dat je {{artifact}} {{version}} wilt bijwerken?",
"title": "{{artifact}} bijwerken"
}
},
"PluginListIndex": {
"freeze": "Updates bevriezen",
"hide": "Verberg in snelle toegang",
"no_plugin": "Geen plug-ins geïnstalleerd!",
"plugin_actions": "Plug-inacties",
"reinstall": "Opnieuw installeren",
"reload": "Herstarten",
"show": "Toon in snelle toegang",
"unfreeze": "Updates toestaan",
"uninstall": "Verwijderen",
"update_all_one": "Werk 1 plug-in bij",
"update_all_other": "Werk {{count}} plug-ins bij",
"update_to": "Bijwerken naar {{name}}"
},
"PluginListLabel": {
"hidden": "Verborgen in snelle toegang"
},
"PluginLoader": {
"decky_title": "Decky",
"decky_update_available": "Update naar {{tag_name}} beschikbaar!",
"error": "Fout",
"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.",
"plugin_load_error": {
"message": "Fout bij het laden van plug-in {{name}}",
"toast": "Fout bij het laden van {{name}}"
},
"plugin_uninstall": {
"button": "Verwijderen",
"desc": "Weet je zeker dat je {{name}} wilt verwijderen?",
"title": "Verwijder {{name}}"
},
"plugin_update_one": "Updates beschikbaar voor 1 plug-in!",
"plugin_update_other": "Updates beschikbaar voor {{count}} plug-ins!"
},
"PluginView": {
"hidden_one": "1 plug-in is verborgen in deze lijst",
"hidden_other": "{{count}} plug-ins zijn verborgen in deze lijst"
},
"RemoteDebugging": {
"remote_cef": {
"desc": "Sta ongeauthenticeerde toegang tot de CEF-debugger toe aan iedereen in uw netwerk",
"label": "Externe CEF-debugging toestaan"
}
},
"SettingsDeveloperIndex": {
"cef_console": {
"button": "Console openen",
"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.",
"label": "CEF-console"
},
"header": "Overige",
"react_devtools": {
"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.",
"ip_label": "IP-adres",
"label": "React DevTools inschakelen"
},
"third_party_plugins": {
"button_install": "Installeren",
"button_zip": "Bladeren",
"header": "Plug-ins van derden",
"label_desc": "URL",
"label_url": "Installeer plug-in via een URL",
"label_zip": "Installeer plug-in via een ZIP-bestand"
},
"valve_internal": {
"desc1": "Schakelt het interne ontwikkelaarsmenu van Valve in.",
"desc2": "Pas niets in dit menu aan, tenzij je weet wat het doet.",
"label": "Valve Internal inschakelen"
}
},
"SettingsGeneralIndex": {
"about": {
"decky_version": "Decky-versie",
"header": "Over"
},
"beta": {
"header": "Beta-deelname"
},
"developer_mode": {
"label": "Ontwikkelaarsmodus"
},
"notifications": {
"decky_updates_label": "Wanneer er een Decky-update beschikbaar is",
"header": "Meldingen",
"plugin_updates_label": "Wanneer er plug-in-updates beschikbaar zijn"
},
"other": {
"header": "Overige"
},
"updates": {
"header": "Bijwerkingen"
}
},
"SettingsIndex": {
"developer_title": "Ontwikkelaar",
"general_title": "Algemeen",
"plugins_title": "Plug-ins",
"testing_title": "Testen"
},
"Store": {
"download_progress_info": {
"download_zip": "Plugin downloaden",
"increment_count": "Aantal downloads verhogen",
"installing_plugin": "Plugin installeren",
"open_zip": "Zip-bestand openen",
"parse_zip": "Zip-bestand parseren",
"start": "Initialiseren",
"uninstalling_previous": "Vorige kopie verwijderen"
},
"store_contrib": {
"desc": "Als je wilt bijdragen aan de Decky Plugin Store, kijk dan in de SteamDeckHomebrew/decky-plugin-template repository op GitHub. Informatie over ontwikkeling en distributie is beschikbaar in de README.",
"label": "Bijdragen"
},
"store_filter": {
"label": "Filter",
"label_def": "Alles"
},
"store_search": {
"label": "Zoeken"
},
"store_sort": {
"label": "Sorteren",
"label_def": "Laatst bijgewerkt (nieuwste)"
},
"store_source": {
"desc": "Alle broncode van de plug-in is beschikbaar in de SteamDeckHomebrew/decky-plugin-database-repository op GitHub.",
"label": "Broncode"
},
"store_tabs": {
"about": "Over",
"alph_asce": "Alfabetisch (Z naar A)",
"alph_desc": "Alfabetisch (A naar Z)",
"date_asce": "Oudste eerst",
"date_desc": "Nieuwste eerst",
"downloads_asce": "Minste gedownload eerst",
"downloads_desc": "Meeste gedownload eerst",
"title": "Bladeren"
},
"store_testing_cta": "Overweeg om nieuwe plug-ins te testen om het Decky Loader-team te helpen!",
"store_testing_warning": {
"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.",
"label": "Welkom bij het Testing-winkelkanaal"
}
},
"StoreSelect": {
"custom_store": {
"label": "Aangepaste winkel",
"url_label": "URL"
},
"store_channel": {
"custom": "Aangepast",
"default": "Standaard",
"label": "Winkelkanaal",
"testing": "Testen"
}
},
"Testing": {
"download": "Downloaden",
"error": "Fout bij installatie van PR",
"header": "De volgende versies van Decky Loader zijn gebouwd op basis van open Pull Requests van derden. Het Decky Loader-team heeft hun functionaliteit of veiligheid niet gecontroleerd en ze kunnen verouderd zijn.",
"loading": "Openstaande Pull Requests laden...",
"start_download_toast": "PR #{{id}} downloaden"
},
"TitleView": {
"decky_store_desc": "Decky Store openen",
"settings_desc": "Decky-instellingen openen"
},
"Updater": {
"decky_updates": "Decky-updates",
"no_patch_notes_desc": "geen patch-opmerkingen voor deze versie",
"patch_notes_desc": "Patch-opmerkingen",
"updates": {
"check_button": "Op updates controleren",
"checking": "Bezig met controleren op updates",
"cur_version": "Huidige versie: {{ver}}",
"install_button": "Bijwerken",
"label": "Bijwerkingen",
"lat_version": "Bijwerkt: versie {{ver}}",
"reloading": "Bezig met herstarten",
"updating": "Bezig met bijwerken"
}
}
}
@@ -1,16 +1,16 @@
{
"BranchSelect": {
"update_channel": {
"testing": "Testowy",
"label": "Kanał aktualizacji",
"prerelease": "Przedpremierowy",
"stable": "Stabilny",
"prerelease": "Przedpremierowy"
"testing": "Testowy"
}
},
"Developer": {
"enabling": "Włączanie React DevTools",
"5secreload": "Ponowne załadowanie za 5 sekund",
"disabling": "Wyłączanie React DevTools"
"disabling": "Wyłączanie React DevTools",
"enabling": "Włączanie React DevTools"
},
"DropdownMultiselect": {
"button": {
@@ -19,14 +19,14 @@
},
"FilePickerError": {
"errors": {
"file_not_found": "Podana ścieżka jest nieprawidłowa. Sprawdź ją i wprowadź ponownie poprawnie.",
"perm_denied": "Nie masz dostępu do podanego katalogu. Sprawdź, czy twój użytkownik (deck na Steam Deck) ma odpowiednie uprawnienia dostępu do określonego katalogu/pliku.",
"unknown": "Wystąpił nieznany błąd. Surowy błąd to {{raw_error}}",
"file_not_found": "Podana ścieżka jest nieprawidłowa. Sprawdź ją i wprowadź ponownie poprawnie."
"unknown": "Wystąpił nieznany błąd. Surowy błąd to {{raw_error}}"
}
},
"FilePickerIndex": {
"file": {
"select": "Wybierz ten plik"
"select": "Zaznacz ten plik"
},
"files": {
"all_files": "Wszystkie pliki",
@@ -50,20 +50,6 @@
}
},
"MultiplePluginsInstallModal": {
"title": {
"mixed_one": "Zmodyfikuj {{count}} plugin",
"mixed_few": "Zmodyfikuj {{count}} pluginy",
"mixed_many": "Zmodyfikuj {{count}} pluginów",
"reinstall_one": "Reinstaluj 1 plugin",
"reinstall_few": "Reinstaluj {{count}} pluginy",
"reinstall_many": "Reinstaluj {{count}} pluginów",
"install_one": "Zainstaluj 1 plugin",
"install_few": "Zainstaluj {{count}} pluginy",
"install_many": "Zainstaluj {{count}} pluginów",
"update_one": "Zaktualizuj 1 plugin",
"update_few": "Zaktualizuj {{count}} pluginy",
"update_many": "Zaktualizuj {{count}} pluginów"
},
"confirm": "Czy na pewno chcesz wprowadzić następujące modyfikacje?",
"description": {
"install": "Zainstaluj {{name}} {{version}}",
@@ -73,13 +59,27 @@
"ok_button": {
"idle": "Potwierdź",
"loading": "W toku"
},
"title": {
"install_few": "Zainstaluj {{count}} pluginy",
"install_many": "Zainstaluj {{count}} pluginów",
"install_one": "Zainstaluj 1 plugin",
"mixed_few": "Zmodyfikuj {{count}} pluginy",
"mixed_many": "Zmodyfikuj {{count}} pluginów",
"mixed_one": "Zmodyfikuj {{count}} plugin",
"reinstall_few": "Reinstaluj {{count}} pluginy",
"reinstall_many": "Reinstaluj {{count}} pluginów",
"reinstall_one": "Reinstaluj 1 plugin",
"update_few": "Zaktualizuj {{count}} pluginy",
"update_many": "Zaktualizuj {{count}} pluginów",
"update_one": "Zaktualizuj 1 plugin"
}
},
"PluginCard": {
"plugin_full_access": "Ten plugin ma pełny dostęp do twojego Steam Decka.",
"plugin_install": "Zainstaluj",
"plugin_no_desc": "Brak opisu.",
"plugin_version_label": "Wersja pluginu",
"plugin_full_access": "Ten plugin ma pełny dostęp do twojego Steam Decka."
"plugin_version_label": "Wersja pluginu"
},
"PluginInstallModal": {
"install": {
@@ -88,6 +88,7 @@
"desc": "Czy na pewno chcesz zainstalować {{artifact}} {{version}}?",
"title": "Zainstaluj {{artifact}}"
},
"no_hash": "Ten plugin nie ma hasha, instalujesz go na własne ryzyko.",
"reinstall": {
"button_idle": "Reinstaluj",
"button_processing": "Reinstalowanie",
@@ -95,51 +96,52 @@
"title": "Reinstaluj {{artifact}}"
},
"update": {
"button_idle": "Aktualizacja",
"button_idle": "Aktualizuj",
"button_processing": "Aktualizowanie",
"desc": "Czy na pewno chcesz zaktualizować {{artifact}} {{version}}?",
"desc": "Czy na pewno chcesz zaktualizować {{artifact}} do wersji {{version}}?",
"title": "Zaktualizuj {{artifact}}"
},
"no_hash": "Ten plugin nie ma hasha, instalujesz go na własne ryzyko."
}
},
"PluginListIndex": {
"freeze": "Zablokuj aktualizacje",
"hide": "Szybki dostęp: Ukryj",
"no_plugin": "Brak zainstalowanych pluginów!",
"reload": "Załaduj ponownie",
"update_all_one": "Zaktualizuj 1 plugin",
"update_all_few": "Zaktualizuj {{count}} pluginy",
"update_all_many": "Zaktualizuj {{count}} pluginów",
"plugin_actions": "Akcje pluginów",
"reinstall": "Reinstalacja",
"reload": "Załaduj ponownie",
"show": "Szybki dostęp: Pokaż",
"unfreeze": "Odblokuj aktualizacje",
"uninstall": "Odinstaluj",
"update_all_few": "Zaktualizuj {{count}} pluginy",
"update_all_many": "Zaktualizuj {{count}} pluginów",
"update_all_one": "Zaktualizuj 1 plugin",
"update_to": "Zaktualizuj do {{name}}"
},
"PluginLoader": {
"decky_title": "Decky",
"decky_update_available": "Dostępna aktualizacja do {{tag_name}}!",
"error": "Błąd",
"plugin_error_uninstall": "Ładowanie {{name}} spowodowało wyjątek, jak pokazano powyżej. Zwykle oznacza to, że plugin wymaga aktualizacji do nowej wersji SteamUI. Sprawdź, czy aktualizacja jest obecna lub rozważ usunięcie go w ustawieniach Decky, w sekcji Pluginy.",
"plugin_load_error": {
"message": "Błąd ładowania plugin {{name}}",
"toast": "Błąd ładowania {{name}}"
},
"plugin_uninstall": {
"button": "Odinstaluj",
"title": "Odinstaluj {{name}}",
"desc": "Czy na pewno chcesz odinstalować {{name}}?"
},
"plugin_update_one": "Aktualizacje dostępne dla 1 pluginu!",
"plugin_update_few": "Aktualizacje dostępne dla {{count}} pluginów!",
"plugin_update_many": "Aktualizacje dostępne dla {{count}} pluginów!"
},
"PluginListLabel": {
"hidden": "Ukryty w menu szybkiego dostępu"
},
"PluginLoader": {
"decky_title": "Decky",
"decky_update_available": "Aktualizacja do {{tag_name}} jest dostępna!",
"error": "Błąd",
"plugin_error_uninstall": "Ładowanie {{name}} spowodowało wyjątek, jak pokazano powyżej. Zwykle oznacza to, że plugin wymaga aktualizacji do nowej wersji SteamUI. Sprawdź, czy aktualizacja jest obecna lub rozważ usunięcie go w ustawieniach Decky, w sekcji Pluginy.",
"plugin_load_error": {
"message": "Błąd ładowania pluginu {{name}}",
"toast": "Błąd ładowania {{name}}"
},
"plugin_uninstall": {
"button": "Odinstaluj",
"desc": "Czy na pewno chcesz odinstalować {{name}}?",
"title": "Odinstaluj {{name}}"
},
"plugin_update_few": "Aktualizacje dostępne dla {{count}} pluginów!",
"plugin_update_many": "Aktualizacje dostępne dla {{count}} pluginów!",
"plugin_update_one": "Aktualizacje dostępne dla 1 pluginu!"
},
"PluginView": {
"hidden_one": "1 plugin jest ukryty na tej liście",
"hidden_few": "{{count}} pluginy jest ukryty na tej liście",
"hidden_many": "{{count}} pluginów jest ukryty na tej liście"
"hidden_many": "{{count}} pluginów jest ukryty na tej liście",
"hidden_one": "1 plugin jest ukryty na tej liście"
},
"RemoteDebugging": {
"remote_cef": {
@@ -174,6 +176,16 @@
}
},
"SettingsGeneralIndex": {
"about": {
"decky_version": "Wersja Decky",
"header": "Informacje"
},
"beta": {
"header": "Udział w becie"
},
"developer_mode": {
"label": "Tryb dewelopera"
},
"notifications": {
"decky_updates_label": "Dostępna aktualizacja Decky",
"header": "Powiadomienia",
@@ -184,24 +196,24 @@
},
"updates": {
"header": "Aktualizacje"
},
"about": {
"header": "Informacje",
"decky_version": "Wersja Decky"
},
"beta": {
"header": "Udział w becie"
},
"developer_mode": {
"label": "Tryb dewelopera"
}
},
"SettingsIndex": {
"developer_title": "Deweloper",
"general_title": "Ogólne",
"plugins_title": "Pluginy"
"plugins_title": "Pluginy",
"testing_title": "Testowanie"
},
"Store": {
"download_progress_info": {
"download_zip": "Pobieranie pluginu",
"increment_count": "Zwiększanie liczby pobrań",
"installing_plugin": "Instalowanie pluginu",
"open_zip": "Otwieranie pliku zip",
"parse_zip": "Analizowanie pliku zip",
"start": "Inicjalizacja",
"uninstalling_previous": "Odinstalowywanie poprzednich kopii"
},
"store_contrib": {
"desc": "Jeśli chcesz przyczynić się do rozwoju Decky Plugin Store, sprawdź repozytorium SteamDeckHomebrew/decky-plugin-template na GitHub. Informacje na temat rozwoju i dystrybucji są dostępne w pliku README.",
"label": "Współtworzenie"
@@ -222,15 +234,19 @@
"label": "Kod źródłowy"
},
"store_tabs": {
"about": "Informacje",
"alph_asce": "Alfabetycznie (od Z do A)",
"alph_desc": "Alfabetycznie (od A do Z)",
"title": "Przeglądaj",
"about": "Informacje"
"date_asce": "Od najstarszych",
"date_desc": "Od najnowszych",
"downloads_asce": "Najmniej pobrań",
"downloads_desc": "Najwięcej pobrań",
"title": "Przeglądaj"
},
"store_testing_cta": "Rozważ przetestowanie nowych pluginów, aby pomóc zespołowi Decky Loader!",
"store_testing_warning": {
"label": "Witamy w Testowym Kanale Sklepu",
"desc": "Możesz użyć tego kanału sklepu do testowania najnowszych wersji pluginów. Pamiętaj, aby zostawić opinię na GitHub, aby plugin mogła zostać zaktualizowana dla wszystkich użytkowników."
"desc": "Możesz użyć tego kanału sklepu do testowania najnowszych wersji pluginów. Pamiętaj, aby zostawić opinię na GitHub, aby plugin mógł zostać zaktualizowany dla wszystkich użytkowników.",
"label": "Witamy w Testowym Kanale Sklepu"
}
},
"StoreSelect": {
@@ -245,6 +261,17 @@
"testing": "Testowy"
}
},
"Testing": {
"download": "Pobierz",
"error": "Błąd instalowania PR",
"header": "Następujące wersje Decky Loader są zrobione z open third-party Pull Requests. Zespół Decky Loader nie zweryfikował ich działania czy bezpieczeństwa, mogą też być nie aktualne.",
"loading": "Ładowanie open Pull Requests...",
"start_download_toast": "Pobieranie PR #{{id}}"
},
"TitleView": {
"decky_store_desc": "Otwórz sklep Decky",
"settings_desc": "Otwórz ustawienia Decky"
},
"Updater": {
"decky_updates": "Aktualizacje Decky",
"no_patch_notes_desc": "Brak informacji o poprawkach dla tej wersji",
@@ -259,9 +286,5 @@
"reloading": "Ponowne ładowanie",
"updating": "Aktualizowanie"
}
},
"TitleView": {
"settings_desc": "Otwórz ustawienia Decky",
"decky_store_desc": "Otwórz sklep Decky"
}
}
@@ -1,44 +1,79 @@
{
"BranchSelect": {
"update_channel": {
"label": "Canal de Atualização",
"prerelease": "Pré-lançamento",
"stable": "Estável",
"testing": "Em Teste",
"label": "Canal de Atualização"
"testing": "Em Teste"
}
},
"Developer": {
"5secreload": "Recarregando em 5 segundos",
"enabling": "Habilitando React DevTools",
"disabling": "Desabilitando React DevTools"
"disabling": "Desabilitando React DevTools",
"enabling": "Habilitando React DevTools"
},
"DropdownMultiselect": {
"button": {
"back": "Voltar"
}
},
"FilePickerError": {
"errors": {
"file_not_found": "O caminho especificado não é válido. Por favor, confira e reinsira corretamente.",
"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.",
"unknown": "Ocorreu um erro desconhecido. O erro completo é: {{raw_error}}"
}
},
"FilePickerIndex": {
"folder": {
"select": "Use esta pasta",
"label": "Pasta",
"show_more": "Mostrar mais arquivos"
"file": {
"select": "Selecione este arquivo"
},
"files": {
"show_hidden": "Mostrar Arquivos Ocultos",
"all_files": "Todos os arquivos",
"file_type": "Formato de arquivo"
"file_type": "Formato de arquivo",
"show_hidden": "Mostrar Arquivos Ocultos"
},
"filter": {
"created_asce": "Criado (Mais antigo)",
"created_desc": "Criado (Mais recente)",
"modified_asce": "Alterado (Mais antigo)",
"modified_desc": "Alterado (Mais recente)",
"name_asce": "Z-A",
"name_desc": "A-Z",
"size_asce": "Tamanho (Menor)",
"size_desc": "Tamanho (Maior)",
"modified_desc": "Alterado (Mais recente)"
"size_desc": "Tamanho (Maior)"
},
"file": {
"select": "Selecione este arquivo"
"folder": {
"label": "Pasta",
"select": "Use esta pasta",
"show_more": "Mostrar mais arquivos"
}
},
"PluginListLabel": {
"hidden": "Oculto no menu de acesso rápido"
"MultiplePluginsInstallModal": {
"confirm": "Tem certeza que deseja fazer as seguintes modificações?",
"description": {
"install": "Instalar {{name}} {{version}}",
"reinstall": "Reinstalar {{name}} {{version}}",
"update": "Atualizar {{name}} para {{version}}"
},
"ok_button": {
"idle": "Confirmar",
"loading": "Carregando"
},
"title": {
"install_many": "Instalar {{count}} plugins",
"install_one": "Instalar 1 plugin",
"install_other": "Instalar {{count}} plugins",
"mixed_many": "Modificar {{count}} plugins",
"mixed_one": "Modificar {{count}} plugin",
"mixed_other": "Modificar {{count}} plugins",
"reinstall_many": "Reinstalar {{count}} plugins",
"reinstall_one": "Reinstalar 1 plugin",
"reinstall_other": "Reinstalar {{count}} plugins",
"update_many": "Atualizar {{count}} plugins",
"update_one": "Atualizar 1 plugin",
"update_other": "Atualizar {{count}} plugins"
}
},
"PluginCard": {
"plugin_full_access": "Este plugin tem acesso total ao seu Steam Deck.",
@@ -53,6 +88,7 @@
"desc": "Você tem certeza que deseja instalar {{artifact}} {{version}}?",
"title": "Instalar {{artifact}}"
},
"no_hash": "Este plugin não tem um hash, você o está instalando por sua conta em risco.",
"reinstall": {
"button_idle": "Reinstalar",
"button_processing": "Reinstalando",
@@ -64,51 +100,31 @@
"button_processing": "Atualizando",
"desc": "Tem certeza que voce deseja atualizar {{artifact}} {{version}}?",
"title": "Atualizar {{artifact}}"
},
"no_hash": "Este plugin não tem um hash, você o está instalando por sua conta em risco."
},
"MultiplePluginsInstallModal": {
"title": {
"mixed_one": "Modificar {{count}} plugin",
"mixed_many": "Modificar {{count}} plugins",
"mixed_other": "Modificar {{count}} plugins",
"update_one": "Atualizar 1 plugin",
"update_many": "Atualizar {{count}} plugins",
"update_other": "Atualizar {{count}} plugins",
"install_one": "Instalar 1 plugin",
"install_many": "Instalar {{count}} plugins",
"install_other": "Instalar {{count}} plugins",
"reinstall_one": "Reinstalar 1 plugin",
"reinstall_many": "Reinstalar {{count}} plugins",
"reinstall_other": "Reinstalar {{count}} plugins"
},
"ok_button": {
"idle": "Confirmar",
"loading": "Carregando"
},
"description": {
"install": "Instalar {{name}} {{version}}",
"update": "Atualizar {{name}} para {{version}}",
"reinstall": "Reinstalar {{name}} {{version}}"
},
"confirm": "Tem certeza que deseja fazer as seguintes modificações?"
}
},
"PluginListIndex": {
"freeze": "Congelar updates",
"hide": "Acesso Rápido: Ocultar",
"no_plugin": "Nenhum plugin instalado!",
"plugin_actions": "Ações do plugin",
"reinstall": "Reinstalar",
"reload": "Recarregar",
"uninstall": "Desinstalar",
"update_to": "Atualizar para {{name}}",
"show": "Acesso Rápido: Mostrar",
"update_all_one": "Atualizar 1 plugin",
"unfreeze": "Permitir atualizações",
"uninstall": "Desinstalar",
"update_all_many": "Atualizar {{count}} plugins",
"update_all_one": "Atualizar 1 plugin",
"update_all_other": "Atualizar {{count}} plugins",
"hide": "Acesso Rápido: Ocultar"
"update_to": "Atualizar para {{name}}"
},
"PluginListLabel": {
"hidden": "Oculto no menu de acesso rápido"
},
"PluginLoader": {
"decky_title": "Decky",
"decky_update_available": "Atualização para {{tag_name}} disponível!",
"error": "Erro",
"plugin_error_uninstall": "Um erro aconteceu ao carregar {{name}}, como mostrado acima. Isso normalmente significa que o plugin precisa de uma atualização para a nova versão do SteamUI. Confira se existe uma atualização ou avalie a remoção do plugin nas configurações do Decky, na sessão de plugins.",
"plugin_load_error": {
"message": "Erro ao carregar o plugin {{name}}",
"toast": "Erro ao carregar {{name}}"
@@ -118,23 +134,26 @@
"desc": "Você tem certeza que deseja desinstalar {{name}}?",
"title": "Desinstalar {{name}}"
},
"decky_update_available": "Atualização para {{tag_name}} disponível!",
"plugin_error_uninstall": "Um erro aconteceu ao carregar {{name}}, como mostrado acima. Isso normalmente significa que o plugin precisa de uma atualização para a nova versão do SteamUI. Confira se existe uma atualização ou avalie a remoção do plugin nas configurações do Decky, na sessão de plugins.",
"plugin_update_one": "Atualização disponível para 1 plugin!",
"plugin_update_many": "Atualizações disponíveis para {{count}} plugins!",
"plugin_update_one": "Atualização disponível para 1 plugin!",
"plugin_update_other": "Atualizações disponíveis para {{count}} plugins!"
},
"PluginView": {
"hidden_many": "{{count}} plugins estão ocultos nesta lista",
"hidden_one": "1 plugin está oculto nesta lista",
"hidden_other": "{{count}} plugins estão ocultos nesta lista"
},
"RemoteDebugging": {
"remote_cef": {
"label": "Permitir Depuração CEF Demota",
"desc": "Permitir acesso não autenticato ao depurador CEF para qualquer um na sua rede"
"desc": "Permitir acesso não autenticato ao depurador CEF para qualquer um na sua rede",
"label": "Permitir Depuração CEF Demota"
}
},
"SettingsDeveloperIndex": {
"cef_console": {
"button": "Abrir o Console",
"label": "Console CEF",
"desc": "Abre o Console CEF. Somente útil para fins de depuração. O material aqui é potencialmente perigoso e só deve ser usado se você for um desenvolvedor de plugin, ou direcionado até aqui por um."
"desc": "Abre o Console CEF. Somente útil para fins de depuração. O material aqui é potencialmente perigoso e só deve ser usado se você for um desenvolvedor de plugin, ou direcionado até aqui por um.",
"label": "Console CEF"
},
"header": "Outros",
"react_devtools": {
@@ -146,9 +165,9 @@
"button_install": "Instalar",
"button_zip": "Navegar",
"header": "Plugins de terceiros",
"label_desc": "URL",
"label_url": "Instalar Plugin a partir da URL",
"label_zip": "Instalar Plugin a partir de um arquivo ZIP",
"label_desc": "URL"
"label_zip": "Instalar Plugin a partir de um arquivo ZIP"
},
"valve_internal": {
"desc1": "Habilita o menu interno de desenvolvedor da Valve.",
@@ -161,33 +180,43 @@
"decky_version": "Versão do Decky",
"header": "Sobre"
},
"beta": {
"header": "Participação no Beta"
},
"developer_mode": {
"label": "Modo Deselvolvedor"
},
"notifications": {
"decky_updates_label": "Atualização do Decky disponível",
"header": "Noificações",
"plugin_updates_label": "Atualizações de Plugin disponíveis"
},
"other": {
"header": "Outros"
},
"updates": {
"header": "Atualizações"
},
"beta": {
"header": "Participação no Beta"
},
"notifications": {
"decky_updates_label": "Atualização do Decky disponível",
"header": "Noificações",
"plugin_updates_label": "Atualizações de Plugin disponíveis"
}
},
"SettingsIndex": {
"developer_title": "Desenvolvedor",
"general_title": "Geral",
"plugins_title": "Plugins"
"plugins_title": "Plugins",
"testing_title": "Testando"
},
"Store": {
"download_progress_info": {
"download_zip": "Baixando Plugin",
"increment_count": "Aumentando contagem de downloads",
"installing_plugin": "Instalando Plugin",
"open_zip": "Abrindo arquivo Zip",
"parse_zip": "Analizando Arquivo ZIP",
"start": "Iniciando",
"uninstalling_previous": "Desinstalando Cópias Anteriores"
},
"store_contrib": {
"label": "Contribuindo",
"desc": "Se você deseja contribuir para a Loja de Plugins para o Decky, confira o repositório SteamDeckHomebrew/decky-plugin-template no GitHub. Informações sobre o desenvolvimento e distribuição estão disponíveis no README."
"desc": "Se você deseja contribuir para a Loja de Plugins para o Decky, confira o repositório SteamDeckHomebrew/decky-plugin-template no GitHub. Informações sobre o desenvolvimento e distribuição estão disponíveis no README.",
"label": "Contribuindo"
},
"store_filter": {
"label": "Filtros",
@@ -206,11 +235,19 @@
},
"store_tabs": {
"about": "Sobre",
"alph_asce": "Alfabética (Z - A)",
"alph_desc": "Alfabética (A - Z)",
"title": "Navegar",
"alph_asce": "Alfabética (Z - A)"
"date_asce": "Antigos Primeiro",
"date_desc": "Novos Primeiro",
"downloads_asce": "Menos Baixados Primeiro",
"downloads_desc": "Mais Baixados Primeiro",
"title": "Navegar"
},
"store_testing_cta": "Por favor, considere testar os novos plugins para ajudar o time do Decky Loader!"
"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": {
"custom_store": {
@@ -224,7 +261,19 @@
"testing": "Em Teste"
}
},
"Testing": {
"download": "Download",
"error": "Erro ao instalar PR",
"header": "As seguintes versões do Decky Loader são construídas a partir de Pull Requests de terceiros. A equipe do Decky Loader não verificou sua funcionalidade ou segurança, e elas podem estar desatualizadas.",
"loading": "Carregando Pull Requests abertos...",
"start_download_toast": "Baixando PR #{{id}}"
},
"TitleView": {
"decky_store_desc": "Abrir Loja Decky",
"settings_desc": "Abrir Definições Decky"
},
"Updater": {
"decky_updates": "Atualizações do Decky",
"no_patch_notes_desc": "nenhuma nota de alteração para esta versão",
"patch_notes_desc": "Notas de alteração",
"updates": {
@@ -236,24 +285,6 @@
"lat_version": "Atualizado: rodando {{ver}}",
"reloading": "Recarregando",
"updating": "Atualizando"
},
"decky_updates": "Atualizações do Decky"
},
"PluginView": {
"hidden_one": "1 plugin está oculto nesta lista",
"hidden_many": "{{count}} plugins estão ocultos nesta lista",
"hidden_other": "{{count}} plugins estão ocultos nesta lista"
},
"DropdownMultiselect": {
"button": {
"back": "Voltar"
}
},
"FilePickerError": {
"errors": {
"file_not_found": "O caminho especificado não é válido. Por favor, confira e reinsira corretamente.",
"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."
}
}
}
+290
View File
@@ -0,0 +1,290 @@
{
"BranchSelect": {
"update_channel": {
"label": "Canal de atualização",
"prerelease": "Pré-lançamento",
"stable": "Estável",
"testing": "Em testes"
}
},
"Developer": {
"5secreload": "A recarregar em 5 segundos",
"disabling": "Desativar React DevTools",
"enabling": "Ativar React DevTools"
},
"DropdownMultiselect": {
"button": {
"back": "Voltar"
}
},
"FilePickerError": {
"errors": {
"file_not_found": "O caminho especificado não é válido. Por favor, verifica e insere o caminho correto.",
"perm_denied": "Não tens acesso ao diretório especificado. Por favor, verifica se o seu utilizador (deck na Steam Deck) tem a permissão correspondente para aceder à pasta/ficheiro especificada(o).",
"unknown": "Ocorreu um erro desconhecido. O erro bruto é: {{raw_error}}"
}
},
"FilePickerIndex": {
"file": {
"select": "Selecionar este ficheiro"
},
"files": {
"all_files": "Todos os ficheiros",
"file_type": "Tipo de ficheiro",
"show_hidden": "Mostrar ficheiros ocultos"
},
"filter": {
"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)",
"size_desc": "Tamanho (maior)"
},
"folder": {
"label": "Pasta",
"select": "Usar esta pasta",
"show_more": "Mostrar mais ficheiros"
}
},
"MultiplePluginsInstallModal": {
"confirm": "Tens a certeza de que pretendes fazer as seguintes alterações?",
"description": {
"install": "Instalar {{name}} {{version}}",
"reinstall": "Reinstalar {{name}} {{version}}",
"update": "Atualizar {{name}} para {{version}}"
},
"ok_button": {
"idle": "Confirmar",
"loading": "Em curso"
},
"title": {
"install_many": "Instalar {{count}} plugins",
"install_one": "Instalar 1 plugin",
"install_other": "Instalar {{count}} plugins",
"mixed_many": "Alterar {{count}} plugins",
"mixed_one": "Alterar 1 plugin",
"mixed_other": "Alterar {{count}} plugins",
"reinstall_many": "Reinstalar {{count}} plugins",
"reinstall_one": "Reinstalar 1 plugin",
"reinstall_other": "Reinstalar {{count}} plugins",
"update_many": "Atualizar {{count}} plugins",
"update_one": "Atualizar 1 plugin",
"update_other": "Atualizar {{count}} plugins"
}
},
"PluginCard": {
"plugin_full_access": "Este plugin tem acesso total ao teu Steam Deck.",
"plugin_install": "Instalar",
"plugin_no_desc": "Sem descrição fornecida.",
"plugin_version_label": "Versão do plugin"
},
"PluginInstallModal": {
"install": {
"button_idle": "Instalar",
"button_processing": "Instalação em curso",
"desc": "De certeza que queres instalar {{artifact}} {{version}}?",
"title": "Instalar {{artifact}}"
},
"no_hash": "Este plugin não tem uma hash, estás a instalá-lo por tua conta e risco.",
"reinstall": {
"button_idle": "Reinstalar",
"button_processing": "Reinstalação em curso",
"desc": "De certeza que queres reinstalar {{artifact}} {{version}}?",
"title": "Reinstalar {{artifact}}"
},
"update": {
"button_idle": "Atualizar",
"button_processing": "Atualização em curso",
"desc": "De certeza que queres atualizar {{artifact}} {{version}}?",
"title": "Atualizar {{artifact}}"
}
},
"PluginListIndex": {
"freeze": "Congelar atualizações",
"hide": "Acesso rápido: Ocultar",
"no_plugin": "Nenhum plugin instalado!",
"plugin_actions": "Operações de plugin",
"reinstall": "Reinstalar",
"reload": "Recarregar",
"show": "Acesso rápido: Mostrar",
"unfreeze": "Permitir atualizações",
"uninstall": "Desinstalar",
"update_all_many": "Atualizar {{count}} plugins",
"update_all_one": "Atualizar 1 plugin",
"update_all_other": "Atualizar {{count}} plugins",
"update_to": "Atualizar para {{name}}"
},
"PluginListLabel": {
"hidden": "Oculto do menu de acesso rápido"
},
"PluginLoader": {
"decky_title": "Decky",
"decky_update_available": "Está disponível uma nova versão de {{tag_name}} !",
"error": "Erro",
"plugin_error_uninstall": "Ao carregar {{name}}, ocorreu uma exceção, como pode ser verificado acima. Pode ter sido porque o plugin requer a última versão do SteamUI. Verifica se há uma atualização disponível ou desinstala o plugin nas definições do Decky.",
"plugin_load_error": {
"message": "Erro ao carregar o plugin {{name}}",
"toast": "Erro ao carregar {{name}}"
},
"plugin_uninstall": {
"button": "Desinstalar",
"desc": "De certeza que queres desinstalar {{name}}?",
"title": "Desinstalar {{name}}"
},
"plugin_update_many": "{{count}} plugins têm actualizações disponíveis!",
"plugin_update_one": "1 plugin tem atualizações disponíveis!",
"plugin_update_other": "{{count}} plugins têm actualizações disponíveis!"
},
"PluginView": {
"hidden_many": "{{count}} plugins estão ocultos desta lista",
"hidden_one": "1 plugin está oculto desta lista",
"hidden_other": "{{count}} plugins estão ocultos desta lista"
},
"RemoteDebugging": {
"remote_cef": {
"desc": "Permitir acesso não autenticado ao depurador do CEF a qualquer pessoa na tua rede",
"label": "Permitir Depuração Remota do CEF"
}
},
"SettingsDeveloperIndex": {
"cef_console": {
"button": "Abrir consola",
"desc": "Abre a Consola CEF. Apenas útil para fins de depuração. O conteúdo aqui presente pode ser perigoso e só devem ser usadas se fores um desenvolvedor de plugins ou se fores direcionado para aqui por um.",
"label": "Consola CEF"
},
"header": "Outros",
"react_devtools": {
"desc": "Permite a conexão a um computador a correr o React DevTools. Alterar esta definição irá recarregar o Steam. Define o endereço IP antes de ativar.",
"ip_label": "IP",
"label": "Ativar React DevTools"
},
"third_party_plugins": {
"button_install": "Instalar",
"button_zip": "Navegar",
"header": "Plugins de terceiros",
"label_desc": "URL",
"label_url": "Instalar plugin a partir de um URL",
"label_zip": "Instalar plugin a partir de um ficheiro ZIP"
},
"valve_internal": {
"desc1": "Ativa o menu interno de programador da Valve.",
"desc2": "Não toques em nada neste menu, a menos que saibas o que faz.",
"label": "Cativar menu interno da Valve"
}
},
"SettingsGeneralIndex": {
"about": {
"decky_version": "Versão do Decky",
"header": "Sobre"
},
"beta": {
"header": "Participação na versão Beta"
},
"developer_mode": {
"label": "Modo de programador"
},
"notifications": {
"decky_updates_label": "Atualização Decky disponível",
"header": "Notificações",
"plugin_updates_label": "Atualizações de plugins disponíveis"
},
"other": {
"header": "Outros"
},
"updates": {
"header": "Atualizações"
}
},
"SettingsIndex": {
"developer_title": "Programador",
"general_title": "Geral",
"plugins_title": "Plugins",
"testing_title": "Testes"
},
"Store": {
"download_progress_info": {
"download_zip": "A transferir o plugin",
"increment_count": "A incrementar a contagem de transferências",
"installing_plugin": "A instalar o plugin",
"open_zip": "A abrir o ficheiro zip",
"parse_zip": "A processar o ficheiro zip",
"start": "A inicializar",
"uninstalling_previous": "A desinstalar a cópia anterior"
},
"store_contrib": {
"desc": "Se quiseres contribuir para a Loja de Plugins do Decky, consulta o repositório SteamDeckHomebrew/decky-plugin-template no GitHub. Encontras informações sobre desenvolvimento e distribuição no README.",
"label": "Contribuir"
},
"store_filter": {
"label": "Filtro",
"label_def": "Todos"
},
"store_search": {
"label": "Procurar"
},
"store_sort": {
"label": "Ordenar",
"label_def": "Última atualização (mais recente)"
},
"store_source": {
"desc": "Todo o código-fonte dos plugins está disponível no repositório SteamDeckHomebrew/decky-plugin-database no GitHub.",
"label": "Código fonte"
},
"store_tabs": {
"about": "Sobre",
"alph_asce": "Alfabeticamente (Z-A)",
"alph_desc": "Alfabeticamente (A-Z)",
"date_asce": "Mais Antigos Primeiro",
"date_desc": "Mais Recentes Primeiro",
"downloads_asce": "Menos Transferidos Primeiro",
"downloads_desc": "Mais Transferidos Primeiro",
"title": "Navegar"
},
"store_testing_cta": "Testa novos plugins e ajuda a equipa do Decky Loader!",
"store_testing_warning": {
"desc": "Podes utilizar este canal da loja para testar versões de plugins de última geração. Não te esqueças de deixar o teufeedback no GitHub para que o plugin possa ser atualizado para todos os utilizadores.",
"label": "Bem-vindo ao Canal de Testes da Loja"
}
},
"StoreSelect": {
"custom_store": {
"label": "Loja personalizada",
"url_label": "URL"
},
"store_channel": {
"custom": "Personalizada",
"default": "Padrão",
"label": "Canal da loja",
"testing": "Em testes"
}
},
"Testing": {
"download": "Transferir",
"error": "Erro ao instalar PR",
"header": "As seguintes versões do Decky Loader estão construidas a partir de Pull Requests de terceiros. A equipa do Decky Loader não verificou as sua funcionalidade ou segurança e as mesmas podem estar desatualizados.",
"loading": "A carregar Pull Requests abertos...",
"start_download_toast": "A descarregar PR #{{id}}"
},
"TitleView": {
"decky_store_desc": "Abrir a Loja Decky",
"settings_desc": "Abrir as Definições Decky"
},
"Updater": {
"decky_updates": "Atualizações do Decky",
"no_patch_notes_desc": "sem notas de atualizações para esta versão",
"patch_notes_desc": "Notas de atualizações",
"updates": {
"check_button": "Procurar atualizações",
"checking": "A verificar atualizações",
"cur_version": "Versão atual: {{ver}}",
"install_button": "Instalar Atualização",
"label": "Atualizações",
"lat_version": "Atualizado: a executar {{ver}}",
"reloading": "Recarregando",
"updating": "Atualização em curso"
}
}
}
@@ -1,47 +1,129 @@
{
"MultiplePluginsInstallModal": {
"title": {
"update_one": "Переустановить {{count}} плагин",
"update_few": "Переустановить {{count}} плагинов",
"update_many": "Переустановить {{count}} плагинов",
"reinstall_one": "Переустановить {{count}} плагин",
"reinstall_few": "Переустановить {{count}} плагинов",
"reinstall_many": "Переустановить {{count}} плагинов",
"install_one": "Установить {{count}} плагин",
"install_few": "Установить {{count}} плагинов",
"install_many": "Установить {{count}} плагинов",
"mixed_one": "Изменить {{count}} плагин",
"mixed_few": "Изменить {{count}} плагинов",
"mixed_many": "Изменить {{count}} плагинов"
"BranchSelect": {
"update_channel": {
"label": "Канал обновлений",
"prerelease": "Предрелиз",
"stable": "Стабильный",
"testing": "Тестовый"
}
},
"Developer": {
"5secreload": "Перезагрузка через 5 секунд",
"disabling": "Выключение React DevTools",
"enabling": "Включение React DevTools"
},
"DropdownMultiselect": {
"button": {
"back": "Назад"
}
},
"FilePickerError": {
"errors": {
"file_not_found": "Указан недействительный путь. Пожалуйста, проверьте его и повторите ввод.",
"perm_denied": "У вас нет доступа к указанному каталогу.. Пожалуйста, проверьте имеет ли пользователь (deck на Steam Deck) соответствующие права доступа к указанной папке/файлу.",
"unknown": "Произошла неизвестная ошибка. Текст ошибки: {{raw_error}}"
}
},
"FilePickerIndex": {
"file": {
"select": "Выберите этот файл"
},
"files": {
"all_files": "Все файлы",
"file_type": "Тип файла",
"show_hidden": "Показать скрытые файлы"
},
"filter": {
"created_asce": "Создан (самый старый)",
"created_desc": "Создан (самый новый)",
"modified_asce": "Модифицирован (самый новый)",
"modified_desc": "Модифицирован (самый старый)",
"name_asce": "Z-A",
"name_desc": "A-Z",
"size_asce": "Размер (самый малый)",
"size_desc": "Размер (самый большой)"
},
"folder": {
"label": "Папка",
"select": "Использовать этот каталог",
"show_more": "Показать больше файлов"
}
},
"MultiplePluginsInstallModal": {
"confirm": "Вы уверены, что хотите внести следующие изменения?",
"description": {
"install": "Установить {{name}} {{version}}",
"reinstall": "Переустановить {{name}} {{version}}",
"update": "Обновить с {{name}} на {{version}}"
},
"confirm": "Вы уверены, что хотите внести следующие изменения?",
"ok_button": {
"idle": "Подтвердить",
"loading": "В процессе"
},
"title": {
"install_few": "Установить {{count}} плагинов",
"install_many": "Установить {{count}} плагинов",
"install_one": "Установить {{count}} плагин",
"mixed_few": "Изменить {{count}} плагинов",
"mixed_many": "Изменить {{count}} плагинов",
"mixed_one": "Изменить {{count}} плагин",
"reinstall_few": "Переустановить {{count}} плагинов",
"reinstall_many": "Переустановить {{count}} плагинов",
"reinstall_one": "Переустановить {{count}} плагин",
"update_few": "Переустановить {{count}} плагинов",
"update_many": "Переустановить {{count}} плагинов",
"update_one": "Переустановить {{count}} плагин"
}
},
"PluginCard": {
"plugin_full_access": "Этот плагин имеет полный доступ к вашему Steam Deck.",
"plugin_install": "Установить",
"plugin_no_desc": "Нет описания.",
"plugin_version_label": "Версия плагина"
},
"PluginInstallModal": {
"install": {
"button_idle": "Установить",
"button_processing": "Установка",
"desc": "Вы уверены, что хотите установить {{artifact}} {{version}}?",
"title": "Установить {{artifact}}"
},
"no_hash": "У данного плагина отсутствует хэш, устанавливайте на свой страх и риск.",
"reinstall": {
"button_idle": "Переустановить",
"button_processing": "Переустановка",
"desc": "Вы уверены, что хотите переустановить {{artifact}} {{version}}?",
"title": "Переустановить {{artifact}}"
},
"update": {
"button_idle": "Обновить",
"button_processing": "Обновление",
"desc": "Вы уверены, что хотите обновить {{artifact}} {{version}}?",
"title": "Обновить {{artifact}}"
}
},
"PluginListIndex": {
"update_all_one": "Обновить {{count}} плагин",
"freeze": "Остановить обновления",
"hide": "Быстрый доступ: Скрыть",
"no_plugin": "Не установлено ни одного плагина!",
"plugin_actions": "Действия с плагинами",
"reinstall": "Переустановить",
"reload": "Перезагрузить",
"show": "Быстрый доступ: Показать",
"unfreeze": "Разрешить обновления",
"uninstall": "Удалить",
"update_all_few": "Обновить {{count}} плагинов",
"update_all_many": "Обновить {{count}} плагинов",
"hide": "Быстрый доступ: Скрыть",
"reload": "Перезагрузить",
"uninstall": "Удалить",
"update_to": "Обновить на {{name}}",
"show": "Быстрый доступ: Показать",
"plugin_actions": "Действия с плагинами",
"no_plugin": "Не установлено ни одного плагина!",
"reinstall": "Переустановить"
"update_all_one": "Обновить {{count}} плагин",
"update_to": "Обновить на {{name}}"
},
"PluginListLabel": {
"hidden": "Скрыто из меню быстрого доступа"
},
"PluginLoader": {
"plugin_update_one": "Обновления доступны для {{count}} плагина!",
"plugin_update_few": "Обновления доступны для {{count}} плагинов!",
"plugin_update_many": "Обновления доступны для {{count}} плагинов!",
"decky_title": "Decky",
"decky_update_available": "Доступно обновление на {{tag_name}}!",
"error": "Ошибка",
"plugin_error_uninstall": "Загрузка {{name}} вызвала исключение, указанное выше. Обычно это означает, что плагин требует обновления для новой версии SteamUI. Проверьте наличие обновления или попробуйте его удалить в настройках Decky, в разделе Плагины.",
"plugin_load_error": {
"message": "Ошибка загрузки плагина {{name}}",
@@ -52,69 +134,14 @@
"desc": "Вы уверены, что хотите удалить {{name}}?",
"title": "Удалить {{name}}"
},
"decky_title": "Decky",
"decky_update_available": "Доступно обновление на {{tag_name}}!",
"error": "Ошибка"
"plugin_update_few": "Обновления доступны для {{count}} плагинов!",
"plugin_update_many": "Обновления доступны для {{count}} плагинов!",
"plugin_update_one": "Обновления доступны для {{count}} плагина!"
},
"PluginView": {
"hidden_one": "{{count}} плагин скрыт из списка",
"hidden_few": "{{count}} плагинов скрыт из списка",
"hidden_many": "{{count}} плагинов скрыт из списка"
},
"FilePickerIndex": {
"files": {
"show_hidden": "Показать скрытые файлы",
"all_files": "Все файлы",
"file_type": "Тип файла"
},
"filter": {
"created_asce": "Создан (самый старый)",
"modified_asce": "Модифицирован (самый новый)",
"modified_desc": "Модифицирован (самый старый)",
"size_asce": "Размер (самый малый)",
"size_desc": "Размер (самый большой)",
"name_asce": "Z-A",
"name_desc": "A-Z",
"created_desc": "Создан (самый новый)"
},
"folder": {
"label": "Папка",
"show_more": "Показать больше файлов",
"select": "Использовать этот каталог"
},
"file": {
"select": "Выберите этот файл"
}
},
"PluginCard": {
"plugin_install": "Установить",
"plugin_no_desc": "Нет описания.",
"plugin_version_label": "Версия плагина",
"plugin_full_access": "Этот плагин имеет полный доступ к вашему Steam Deck."
},
"PluginInstallModal": {
"install": {
"button_processing": "Установка",
"title": "Установить {{artifact}}",
"button_idle": "Установить",
"desc": "Вы уверены, что хотите установить {{artifact}} {{version}}?"
},
"no_hash": "У данного плагина отсутствует хэш, устанавливайте на свой страх и риск.",
"reinstall": {
"title": "Переустановить {{artifact}}",
"desc": "Вы уверены, что хотите переустановить {{artifact}} {{version}}?",
"button_idle": "Переустановить",
"button_processing": "Переустановка"
},
"update": {
"button_idle": "Обновить",
"button_processing": "Обновление",
"desc": "Вы уверены, что хотите обновить {{artifact}} {{version}}?",
"title": "Обновить {{artifact}}"
}
},
"PluginListLabel": {
"hidden": "Скрыто из меню быстрого доступа"
"hidden_many": "{{count}} плагинов скрыт из списка",
"hidden_one": "{{count}} плагин скрыт из списка"
},
"RemoteDebugging": {
"remote_cef": {
@@ -123,70 +150,70 @@
}
},
"SettingsDeveloperIndex": {
"header": "Другое",
"third_party_plugins": {
"button_install": "Установить",
"label_zip": "Установить плагин из ZIP файла",
"label_url": "Установить плагин из URL",
"button_zip": "Обзор",
"header": "Сторонние плагины",
"label_desc": "Ссылка"
},
"react_devtools": {
"ip_label": "IP",
"desc": "Позволяет подключиться к компьютеру, на котором работает React DevTools. Изменение этого параметра приведет к перезагрузке Steam. Установите IP-адрес перед включением.",
"label": "Включить React DevTools"
},
"cef_console": {
"button": "Открыть консоль",
"desc": "Открывает консоль CEF. Полезно только для целей отладки. Настройки здесь потенциально опасны и должны использоваться только в том случае, если вы являетесь разработчиком плагинов или направленны сюда одним из них.",
"label": "CEF Консоль"
},
"header": "Другое",
"react_devtools": {
"desc": "Позволяет подключиться к компьютеру, на котором работает React DevTools. Изменение этого параметра приведет к перезагрузке Steam. Установите IP-адрес перед включением.",
"ip_label": "IP",
"label": "Включить React DevTools"
},
"third_party_plugins": {
"button_install": "Установить",
"button_zip": "Обзор",
"header": "Сторонние плагины",
"label_desc": "Ссылка",
"label_url": "Установить плагин из URL",
"label_zip": "Установить плагин из ZIP файла"
},
"valve_internal": {
"desc1": "Включает внутреннее меню разработчика Valve.",
"label": "Включить Valve Internal",
"desc2": "Ничего не трогайте в этом меню, если не знаете, что оно делает."
"desc2": "Ничего не трогайте в этом меню, если не знаете, что оно делает.",
"label": "Включить Valve Internal"
}
},
"SettingsGeneralIndex": {
"about": {
"decky_version": "Версия Decky",
"header": "Информация"
},
"beta": {
"header": "Бета программа"
},
"developer_mode": {
"label": "Режим разработчика"
},
"other": {
"header": "Другое"
},
"about": {
"decky_version": "Версия Decky",
"header": "Информация"
},
"updates": {
"header": "Обновления"
},
"notifications": {
"decky_updates_label": "Обновление Decky доступно",
"header": "Уведомления",
"plugin_updates_label": "Доступны обновления плагинов"
},
"other": {
"header": "Другое"
},
"updates": {
"header": "Обновления"
}
},
"SettingsIndex": {
"developer_title": "Разработчик",
"general_title": "Общее",
"plugins_title": "Плагины",
"testing_title": "Тестирование"
},
"Store": {
"store_sort": {
"label": "Сортировка",
"label_def": "Последнее обновление(самые новые)"
"download_progress_info": {
"download_zip": "Скачивание плагина",
"increment_count": "Увеличение количества загрузок",
"installing_plugin": "Установка плагина",
"open_zip": "Открытие zip файла",
"parse_zip": "Распаковка zip файла",
"start": "Инициализация",
"uninstalling_previous": "Удаление предыдущей копии"
},
"store_source": {
"label": "Исходный код",
"desc": "Весь исходный код плагина доступен в репозитории SteamDeckHomebrew/decky-plugin-database на GitHub."
},
"store_tabs": {
"about": "Информация",
"alph_desc": "По алфавиту (A - Z)",
"title": "Обзор",
"alph_asce": "По алфавиту (Z - A)"
},
"store_testing_cta": "Пожалуйста, рассмотрите возможность тестирования новых плагинов, чтобы помочь команде Decky Loader!",
"store_contrib": {
"desc": "Если вы хотите внести свой вклад в магазин плагинов Decky, проверьте репозиторий SteamDeckHomebrew/decky-plugin-template на GitHub. Информация о разработке и распространении доступна в README.",
"label": "Помощь проекту"
@@ -198,9 +225,28 @@
"store_search": {
"label": "Поиск"
},
"store_sort": {
"label": "Сортировка",
"label_def": "Последнее обновление(самые новые)"
},
"store_source": {
"desc": "Весь исходный код плагина доступен в репозитории SteamDeckHomebrew/decky-plugin-database на GitHub.",
"label": "Исходный код"
},
"store_tabs": {
"about": "Информация",
"alph_asce": "По алфавиту (Z - A)",
"alph_desc": "По алфавиту (A - Z)",
"date_asce": "Сначала старые",
"date_desc": "Сначала новые",
"downloads_asce": "Наименее загружаемые сначала",
"downloads_desc": "Наиболее загружаемые сначала",
"title": "Обзор"
},
"store_testing_cta": "Пожалуйста, рассмотрите возможность тестирования новых плагинов, чтобы помочь команде Decky Loader!",
"store_testing_warning": {
"label": "Добро пожаловать в тестовый канал магазина",
"desc": "Вы можете использовать этот канал магазина для тестирования новейших версий плагинов. Не забудьте оставить отзыв на GitHub, чтобы плагин можно было обновить для всех пользователей."
"desc": "Вы можете использовать этот канал магазина для тестирования новейших версий плагинов. Не забудьте оставить отзыв на GitHub, чтобы плагин можно было обновить для всех пользователей.",
"label": "Добро пожаловать в тестовый канал магазина"
}
},
"StoreSelect": {
@@ -215,53 +261,30 @@
"testing": "Тестовый"
}
},
"Updater": {
"decky_updates": "Обновления Decky",
"no_patch_notes_desc": "нет примечаний к патчу для этой версии",
"updates": {
"check_button": "Проверить обновления",
"checking": "Проверка",
"cur_version": "Текущая версия: {{ver}}",
"updating": "Обновление",
"install_button": "Установить обновление",
"label": "Обновления",
"lat_version": "Обновлено: версия {{ver}}",
"reloading": "Перезагрузка"
},
"patch_notes_desc": "Примечания к патчу"
},
"FilePickerError": {
"errors": {
"perm_denied": "У вас нет доступа к указанному каталогу.. Пожалуйста, проверьте имеет ли пользователь (deck на Steam Deck) соответствующие права доступа к указанной папке/файлу.",
"file_not_found": "Указан недействительный путь. Пожалуйста, проверьте его и повторите ввод.",
"unknown": "Произошла неизвестная ошибка. Текст ошибки: {{raw_error}}"
}
},
"DropdownMultiselect": {
"button": {
"back": "Назад"
}
},
"BranchSelect": {
"update_channel": {
"prerelease": "Предрелиз",
"stable": "Стабильный",
"testing": "Тестовый",
"label": "Канал обновлений"
}
},
"Developer": {
"5secreload": "Перезагрузка через 5 секунд",
"disabling": "Выключение React DevTools",
"enabling": "Включение React DevTools"
},
"SettingsIndex": {
"developer_title": "Разработчик",
"general_title": "Общее",
"plugins_title": "Плагины"
"Testing": {
"download": "Загрузить",
"error": "Ошибка при установке PR",
"header": "Данные версии Decky Loader созданы на основе сторонних pull requst. Команда Decky Loader не проверяла их функциональность и безопасность, и они могут быть устаревшими.",
"loading": "Загрузка открытых pull requst'ов...",
"start_download_toast": "Загрузка PR#{{id}}"
},
"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": "Обновление"
}
}
}
@@ -1,22 +1,8 @@
{
"SettingsDeveloperIndex": {
"react_devtools": {
"ip_label": "IP",
"label": "Aktivizo React DevTools"
},
"third_party_plugins": {
"button_zip": "Kërko",
"header": "Shtesa të Huaj",
"button_install": "Instalo",
"label_desc": "URL",
"label_url": "Instalo Shtes Nga URL",
"label_zip": "Instalo Shtes Nga ZIP"
}
},
"BranchSelect": {
"update_channel": {
"stable": "Fiksuar",
"label": "Kanali Përditësimet"
"label": "Kanali Përditësimet",
"stable": "Fiksuar"
}
},
"FilePickerIndex": {
@@ -24,6 +10,18 @@
"select": "Përdore këtë folder"
}
},
"MultiplePluginsInstallModal": {
"title": {
"install_one": "",
"install_other": "",
"mixed_one": "",
"mixed_other": "",
"reinstall_one": "",
"reinstall_other": "",
"update_one": "",
"update_other": ""
}
},
"PluginCard": {
"plugin_install": "Instalo",
"plugin_version_label": "Versioni Shteses"
@@ -48,31 +46,49 @@
"title": "Përditëso {{artifact}}"
}
},
"PluginLoader": {
"decky_title": "Decky",
"plugin_uninstall": {
"title": "Çinstalo {{name}}",
"button": "Çinstalo",
"desc": "Je i sigurt që don ta çinstalojsh {{name}}?"
},
"error": "Gabim",
"plugin_error_uninstall": "Ju lutem shko nga {{name}} në Decky menu nëse don ta çinstalojsh këtë shtese.",
"plugin_update_one": "",
"plugin_update_other": ""
},
"PluginListIndex": {
"no_plugin": "Nuk ka shtesa të instaluar!",
"uninstall": "Çinstalo",
"update_all_one": "",
"update_all_other": ""
},
"SettingsGeneralIndex": {
"other": {
"header": "Të Tjera"
"PluginLoader": {
"decky_title": "Decky",
"error": "Gabim",
"plugin_error_uninstall": "Ju lutem shko nga {{name}} në Decky menu nëse don ta çinstalojsh këtë shtese.",
"plugin_uninstall": {
"button": "Çinstalo",
"desc": "Je i sigurt që don ta çinstalojsh {{name}}?",
"title": "Çinstalo {{name}}"
},
"plugin_update_one": "",
"plugin_update_other": ""
},
"PluginView": {
"hidden_one": "",
"hidden_other": ""
},
"SettingsDeveloperIndex": {
"react_devtools": {
"ip_label": "IP",
"label": "Aktivizo React DevTools"
},
"third_party_plugins": {
"button_install": "Instalo",
"button_zip": "Kërko",
"header": "Shtesa të Huaj",
"label_desc": "URL",
"label_url": "Instalo Shtes Nga URL",
"label_zip": "Instalo Shtes Nga ZIP"
}
},
"SettingsGeneralIndex": {
"about": {
"decky_version": "Versioni Decky"
},
"other": {
"header": "Të Tjera"
},
"updates": {
"header": "Përmirësimet"
}
@@ -82,12 +98,6 @@
"general_title": "Gjeneral"
},
"Store": {
"store_sort": {
"label": "Rendit"
},
"store_tabs": {
"title": "Kërko"
},
"store_contrib": {
"label": "Kontributi"
},
@@ -98,8 +108,14 @@
"store_search": {
"label": "Kërko"
},
"store_sort": {
"label": "Rendit"
},
"store_source": {
"label": "Kodin Burimor"
},
"store_tabs": {
"title": "Kërko"
}
},
"StoreSelect": {
@@ -111,21 +127,5 @@
"updates": {
"cur_version": "Versioni e tanishëme: {{ver}}"
}
},
"MultiplePluginsInstallModal": {
"title": {
"mixed_one": "",
"mixed_other": "",
"update_one": "",
"update_other": "",
"reinstall_one": "",
"reinstall_other": "",
"install_one": "",
"install_other": ""
}
},
"PluginView": {
"hidden_one": "",
"hidden_other": ""
}
}
+307
View File
@@ -0,0 +1,307 @@
{
"BranchSelect": {
"update_channel": {
"label": "Uppdateringskanal",
"prerelease": "Förhandsversion",
"stable": "Stabil",
"testing": "Testning"
}
},
"Developer": {
"5secreload": "Uppdaterar om 5 sekunder",
"disabling": "Inaktiverar React DevTools",
"enabling": "Aktiverar React DevTools"
},
"DropdownMultiselect": {
"button": {
"back": "Tillbaka"
}
},
"FilePickerError": {
"errors": {
"file_not_found": "Den angivna sökvägen är inte giltig. Kontrollera den och ange den korrekt igen.",
"perm_denied": "Du har inte tillgång till den angivna katalogen. Kontrollera om din användare (deck på Steam Deck) har motsvarande behörighet för att komma åt den angivna mappen/filen.",
"unknown": "Ett okänt fel har inträffat. Det råa felet är: {{raw_error}}"
}
},
"FilePickerIndex": {
"file": {
"select": "Välj denna fil"
},
"files": {
"all_files": "Alla filer",
"file_type": "Filtyp",
"show_hidden": "Visa dolda filer"
},
"filter": {
"created_asce": "Skapad (äldst)",
"created_desc": "Skapad (nyast)",
"modified_asce": "Modifierad (äldst)",
"modified_desc": "Modifierad (nyaste)",
"name_asce": "Z-A",
"name_desc": "A-Z",
"size_asce": "Storlek (minst)",
"size_desc": "Storlek (störst)"
},
"folder": {
"label": "Mapp",
"select": "Använd denna mapp",
"show_more": "Visa fler filer"
}
},
"MultiplePluginsInstallModal": {
"confirm": "Är du säker på att du vill göra följande ändringar?",
"description": {
"downgrade": "Nedgradera {{name}} till {{version}}",
"install": "Installera {{name}} {{version}}",
"overwrite": "Skriv över {{name}} med {{version}}",
"reinstall": "Installera om {{name}} {{version}}",
"update": "Uppdatera {{name}} {{version}}"
},
"ok_button": {
"idle": "Bekräfta",
"loading": "Arbetar"
},
"title": {
"downgrade_one": "Nedgradera 1 insticksmodul",
"downgrade_other": "Nedgradera {{count}} insticksmoduler",
"install_one": "Install 1 tillägg",
"install_other": "Installerar {{count}} tillägg",
"mixed_one": "Ändra {{count}} insticksmodul",
"mixed_other": "Ändra {{count}} insticksmoduler",
"overwrite_one": "Skriv över 1 insticksmodul",
"overwrite_other": "Skriv över {{count}} insticksmoduler",
"reinstall_one": "Installera om 1 insticksmodul",
"reinstall_other": "Installera om {{count}} insticksmoduler",
"update_one": "Uppdatera 1 insticksmodul",
"update_other": "Uppdatera {{count}} insticksmoduler"
}
},
"PluginCard": {
"plugin_downgrade": "Nedgradera",
"plugin_full_access": "Denna insticksmodul har full åtkomst till din Steam Deck.",
"plugin_install": "Installera",
"plugin_no_desc": "Ingen beskrivning angavs.",
"plugin_overwrite": "Skriv över",
"plugin_reinstall": "Installera om",
"plugin_update": "Uppdatera",
"plugin_version_label": "Version av insticksmodul"
},
"PluginInstallModal": {
"downgrade": {
"button_idle": "Nedgradera",
"button_processing": "Nedgraderar",
"desc": "Är du säker på att du vill nedgradera {{artifact}} till version {{version}}?",
"title": "Nedgradera {{artifact}}"
},
"install": {
"button_idle": "Installera",
"button_processing": "Installerar",
"desc": "Är du säker på att du vill installera {{artifact}} {{version}}?",
"title": "Installera {{artifact}}"
},
"no_hash": "Denna insticksmodul har inte någon kontrollsumma. Du installerar den på egen risk.",
"not_installed": "(inte installerad)",
"overwrite": {
"button_idle": "Skriv över",
"button_processing": "Skriver över",
"desc": "Är du säker på att du vill skriva över {{artifact}} med version {{version}}?",
"title": "Skriv över {{artifact}}"
},
"reinstall": {
"button_idle": "Installera om",
"button_processing": "Installerar om",
"desc": "Är du säker på att du vill installera om {{artifact}} {{version}}?",
"title": "Installera om {{artifact}}"
},
"update": {
"button_idle": "Uppdatera",
"button_processing": "Uppdaterar",
"desc": "Är du säker på att du vill uppdatera {{artifact}} till version {{version}}?",
"title": "Uppdatera {{artifact}}"
}
},
"PluginListIndex": {
"freeze": "Frys uppdateringar",
"hide": "Snabbåtkomst: Dölj",
"no_plugin": "Inga insticksmoduler installerade!",
"plugin_actions": "Åtgärder för insticksmodul",
"reinstall": "Installera om",
"reload": "Uppdatera",
"show": "Snabbåtkomst: Visa",
"unfreeze": "Tillåt uppdateringar",
"uninstall": "Avinstallera",
"update_all_one": "Uppdatera 1 insticksmodul",
"update_all_other": "Uppdatera {{count}} insticksmoduler",
"update_to": "Uppdatera till {{name}}"
},
"PluginListLabel": {
"hidden": "Dold från snabbåtkomstmenyn"
},
"PluginLoader": {
"decky_title": "Decky",
"decky_update_available": "Uppdatering till {{tag_name}} finns!",
"error": "Fel",
"plugin_error_uninstall": "Inläsning av {{name}} orsakade ett undantag som visas nedan. Detta betyder oftast att insticksmodulen kräver en uppdatering för den nya versionen av SteamUI. Kontrollera om en uppdatering finns eller fundera på att ta bort den i Decky-inställningarna, under insticksmoduler.",
"plugin_load_error": {
"message": "Fel vid inläsning av insticksmodulen {{name}}",
"toast": "Fel vid inläsning {{name}}"
},
"plugin_uninstall": {
"button": "Avinstallera",
"desc": "Är du säker på att du vill avinstallera {{name}}?",
"title": "Avinstallera {{name}}"
},
"plugin_update_one": "Uppdateringar tillgängliga för 1 insticksmodul!",
"plugin_update_other": "Uppdateringar tillgängliga för {{count}} insticksmoduler!"
},
"PluginView": {
"hidden_one": "1 insticksmodul är dold från denna lista",
"hidden_other": "{{count}} insticksmoduler är dolda från denna lista"
},
"RemoteDebugging": {
"remote_cef": {
"desc": "Tillåt oautentiserad åtkomst till CEF-felsökaren till vem som helst i ditt nätverk",
"label": "Tillåt fjärr-CEF-felsökning"
}
},
"SettingsDeveloperIndex": {
"cef_console": {
"button": "Öppna konsoll",
"desc": "Öppnar CEF-konsollen. Endast användbart för felsökningssyften. Saker här är potentiellt farliga och bör endast användas om du utvecklar insticksmoduler.",
"label": "CEF-konsoll"
},
"header": "Övrigt",
"react_devtools": {
"desc": "Aktiverar anslutning till en dator som kör React DevTools. Ändring av denna inställning kommer att läsa om Steam. Ställ in IP-adressen innan du aktiverar.",
"ip_label": "IP",
"label": "Aktivera React DevTools"
},
"third_party_plugins": {
"button_install": "Installera",
"button_zip": "Bläddra",
"header": "Insticksmoduler från tredjepart",
"label_desc": "URL",
"label_url": "Installera insticksmodul från URL",
"label_zip": "Installera insticksmodul från ZIP-fil"
},
"valve_internal": {
"desc1": "Aktiverar Valves interna utvecklarmeny.",
"desc2": "Rör ingenting i denna meny såvida inte du vet vad du gör.",
"label": "Aktivera Valves interna"
}
},
"SettingsGeneralIndex": {
"about": {
"decky_version": "Decky-version",
"header": "Om"
},
"beta": {
"header": "Delta i betatestning"
},
"developer_mode": {
"label": "Utvecklarläge"
},
"notifications": {
"decky_updates_label": "Uppdatering till Decky finns tillgänglig",
"header": "Aviseringar",
"plugin_updates_label": "Uppdateringar till insticksmoduler tillgängliga"
},
"other": {
"header": "Övrigt"
},
"updates": {
"header": "Uppdateringar"
}
},
"SettingsIndex": {
"developer_title": "Utvecklare",
"general_title": "Allmänt",
"plugins_title": "Insticksmoduler",
"testing_title": "Testning"
},
"Store": {
"download_progress_info": {
"download_remote": "Hämtar externa binärfiler",
"download_zip": "Hämtar insticksmodul",
"increment_count": "Ökar hämtningsantal",
"installing_plugin": "Installerar insticksmodul",
"open_zip": "Öppnar zip-fil",
"parse_zip": "Tolkar zip-fil",
"start": "Initierar",
"uninstalling_previous": "Avinstallerar tidigare kopia"
},
"store_contrib": {
"desc": "Om du vill bidra till Deckys insticksmoduler, titta in i förrådet SteamDeckHomebrew/decky-plugin-template på GitHub. Information om utveckling och distribution finns tillgänglig i filen README.",
"label": "Bidra"
},
"store_filter": {
"label": "Filtrera",
"label_def": "Alla"
},
"store_search": {
"label": "Sök"
},
"store_sort": {
"label": "Sortera",
"label_def": "Senast uppdaterad (Senaste)"
},
"store_source": {
"desc": "All källkod för insticksmoduler finns tillgänglig i förrådet SteamDeckHomebrew/decky-plugin-database på GitHub.",
"label": "Källkod"
},
"store_tabs": {
"about": "Om",
"alph_asce": "Alfabetisk (Z till A)",
"alph_desc": "Alfabetisk (A till Z)",
"date_asce": "Äldsta först",
"date_desc": "Senaste först",
"downloads_asce": "Minst hämtade först",
"downloads_desc": "Mest hämtade först",
"title": "Bläddra"
},
"store_testing_cta": "Överväg att testa nya insticksmoduler för att hjälpa Decky Loader-teamet!",
"store_testing_warning": {
"desc": "Du kan använda denna kanal för att testa de absolut senaste versionerna. Tänk på att ge återkoppling på GitHub så att insticksmodulen kan uppdateras för alla användare.",
"label": "Välkommen till testkanalen"
}
},
"StoreSelect": {
"custom_store": {
"label": "Anpassad affär",
"url_label": "URL"
},
"store_channel": {
"custom": "Anpassad",
"default": "Standard",
"label": "Affärskanal",
"testing": "Testning"
}
},
"Testing": {
"download": "Hämta",
"error": "Fel vid installation av PR",
"header": "Följande versioner av Decky Loader byggs från öppnade Pull Requests från tredjepart. Decky Loader-teamet har inte verifierat deras funktionalitet eller säkerhet och de kan vara utdaterade.",
"loading": "Läser in öppnade Pull Requests...",
"start_download_toast": "Hämtar PR #{{id}}"
},
"TitleView": {
"decky_store_desc": "Öppna Decky-affär",
"settings_desc": "Öppna Decky-inställningar"
},
"Updater": {
"decky_updates": "Decky-uppdateringar",
"no_patch_notes_desc": "inga patch-anteckningar för denna version",
"patch_notes_desc": "Patch-anteckningar",
"updates": {
"check_button": "Leta efter uppdateringar",
"checking": "Letar",
"cur_version": "Aktuell version: {{ver}}",
"install_button": "Installera uppdatering",
"label": "Uppdateringar",
"lat_version": "Uppdaterad: kör {{ver}}",
"reloading": "Läser om",
"updating": "Uppdaterar"
}
}
}
+234
View File
@@ -0,0 +1,234 @@
{
"BranchSelect": {
"update_channel": {
"label": "Güncelleme Kanalı",
"prerelease": "Önsürüm",
"stable": "Stabil",
"testing": "Test"
}
},
"DropdownMultiselect": {
"button": {
"back": "Geri"
}
},
"FilePickerError": {
"errors": {
"file_not_found": "Belirtilen yol geçerli değil. Lütfen yolu kontrol edin ve doğru şekilde yeniden girin.",
"perm_denied": "Belirtilen dizine erişim izniniz yok. Lütfen kullanıcınızın (Steam Deck'te deck) belirtilen klasöre/dosyaya erişmek için ilgili izne sahip olup olmadığını kontrol edin.",
"unknown": "Bilinmeyen bir hata oluştu. Raw hata: {{raw_error}}"
}
},
"FilePickerIndex": {
"file": {
"select": "Bu dosyayı seçin"
},
"files": {
"all_files": "Tüm Dosyalar",
"file_type": "Dosya Türü",
"show_hidden": "Gizli Dosyaları Göster"
},
"filter": {
"created_asce": "Oluşturuldu (En Eski)",
"created_desc": "Oluşturuldu (En Yeni)",
"modified_asce": "Değiştirildi (En Eski)",
"modified_desc": "Değiştirildi (En Yeni)",
"name_asce": "Z-A",
"name_desc": "A-Z",
"size_asce": "Boyut (En Küçük)",
"size_desc": "Boyut (En Büyük)"
},
"folder": {
"label": "Klasör",
"select": "Bu klasörü kullan",
"show_more": "Daha fazla dosya göster"
}
},
"MultiplePluginsInstallModal": {
"confirm": "Aşağıdaki değişiklikleri yapmak istediğinizden emin misiniz?",
"description": {
"install": "Yükle {{name}} {{version}}",
"reinstall": "Yeniden yükle {{name}} {{version}}",
"update": "{{name}}'i {{version}}'ye güncelle"
},
"ok_button": {
"idle": "Onayla",
"loading": "Çalışıyor"
},
"title": {
"install_one": "1 eklenti yükle",
"install_other": "{{count}} eklenti yükle",
"mixed_one": "{{count}} eklentiyi değiştir",
"mixed_other": "{{count}} eklentiyi değiştir",
"reinstall_one": "1 eklentiyi yeniden yükle",
"reinstall_other": "{{count}} eklentiyi yeniden yükle",
"update_one": "{{count}} eklentiyi güncelle",
"update_other": "{{count}} eklentiyi güncelle"
}
},
"PluginCard": {
"plugin_full_access": "Bu eklenti Steam Deck'inize tam erişime sahiptir.",
"plugin_install": "Yükle",
"plugin_no_desc": "Açıklama bulunmuyor.",
"plugin_version_label": "Eklenti Versiyonu"
},
"PluginInstallModal": {
"install": {
"button_idle": "Yükle",
"button_processing": "Yükleniyor",
"desc": "Yüklemek istediğinizden emin misiniz {{artifact}} {{version}}?",
"title": "Yükle {{artifact}}"
},
"reinstall": {
"button_idle": "Yeniden Yükle",
"button_processing": "Yeniden Yükleniyor",
"desc": "Yeniden yüklemek istediğinizden emin misiniz {{artifact}} {{version}}?",
"title": "Yeniden Yükle {{artifact}}"
},
"update": {
"button_idle": "Güncelle",
"button_processing": "Güncelleniyor",
"desc": "Güncellemek istediğinizden emin misiniz {{artifact}} {{version}}?",
"title": "Güncelle {{artifact}}"
}
},
"PluginListIndex": {
"freeze": "Güncellemeleri durdur",
"hide": "Hızlı erişim: Gizle",
"no_plugin": "Yüklü eklenti yok!",
"plugin_actions": "Eklenti İşlemleri",
"reinstall": "Yeniden Yükle",
"reload": "Yenile",
"show": "Hızlı erişim: Göster",
"unfreeze": "Güncellemelere izin ver",
"uninstall": "Kaldır",
"update_all_one": "1 eklentiyi güncelle",
"update_all_other": "{{count}} eklentiyi güncelle"
},
"PluginListLabel": {
"hidden": "Hızlı erişim menüsünden gizlenmiş"
},
"PluginLoader": {
"decky_title": "Decky",
"decky_update_available": "{{tag_name}} güncellemesi mevcut!",
"error": "Hata",
"plugin_load_error": {
"message": "{{name}} eklentisi yüklenirken bir hata oluştu",
"toast": "{{name}} yüklenirken hata oluştu"
},
"plugin_uninstall": {
"button": "Kaldır",
"desc": "{{name}} kaldırmak istediğinizden emin misiniz?",
"title": "Kaldır {{name}}"
},
"plugin_update_one": "1 eklenti için güncelleme mevcut!",
"plugin_update_other": "{{count}} eklenti için güncelleme mevcut!"
},
"PluginView": {
"hidden_one": "1 eklenti bu listeden gizlenmiştir",
"hidden_other": "{{count}} eklenti bu listeden gizlenmiştir"
},
"SettingsDeveloperIndex": {
"cef_console": {
"button": "Konsolu Aç"
},
"header": "Diğer",
"react_devtools": {
"ip_label": "IP"
},
"third_party_plugins": {
"button_install": "Yükle",
"button_zip": "Gözat",
"header": "Üçüncü Parti Eklentiler",
"label_desc": "URL",
"label_url": "URL'den Eklenti Yükle",
"label_zip": "ZIP Dosyasından Eklenti Yükle"
}
},
"SettingsGeneralIndex": {
"about": {
"decky_version": "Decky Versiyonu",
"header": "Hakkında"
},
"beta": {
"header": "Betaya katılım"
},
"developer_mode": {
"label": "Geliştirici modu"
},
"notifications": {
"decky_updates_label": "Decky güncellemesi mevcut",
"header": "Bildirimler",
"plugin_updates_label": "Eklenti güncellemesi mevcut"
},
"other": {
"header": "Diğer"
},
"updates": {
"header": "Güncellemeler"
}
},
"SettingsIndex": {
"developer_title": "Geliştirici",
"general_title": "Genel",
"plugins_title": "Eklentiler"
},
"Store": {
"store_contrib": {
"label": "Katkıda Bulunma"
},
"store_filter": {
"label": "Filtre",
"label_def": "Tümü"
},
"store_search": {
"label": "Ara"
},
"store_sort": {
"label": "Sırala",
"label_def": "Son Güncellenme (En Yeni)"
},
"store_source": {
"label": "Kaynak Kodu"
},
"store_tabs": {
"about": "Hakkında",
"alph_asce": "Alfabetik (Z'den A'ya)",
"alph_desc": "Alfabetik (A'dan Z'ye)",
"date_asce": "Önce En Eski",
"date_desc": "Önce En Yeni",
"downloads_asce": "Önce En Az İndirilen",
"downloads_desc": "Önce En Çok İndirilen",
"title": "Gözat"
}
},
"StoreSelect": {
"custom_store": {
"url_label": "URL"
},
"store_channel": {
"custom": "Özel",
"default": "Varsayılan"
}
},
"Testing": {
"download": "İndir"
},
"TitleView": {
"decky_store_desc": "Decky Mağazasını Aç",
"settings_desc": "Decky Ayarlarını Aç"
},
"Updater": {
"decky_updates": "Decky Güncellemeleri",
"no_patch_notes_desc": "bu sürüm için yama notları mevcut değil",
"patch_notes_desc": "Yama Notları",
"updates": {
"check_button": "Güncellemeleri Kontrol Et",
"checking": "Kontrol ediliyor",
"cur_version": "Mevcut Versiyon: {{ver}}",
"install_button": "Güncellemeyi Yükle",
"label": "Güncellemeler",
"updating": "Güncelleniyor"
}
}
}
@@ -1,24 +1,47 @@
{
"BranchSelect": {
"update_channel": {
"prerelease": "Передреліз",
"testing": "Тестовий",
"label": "Канал оновлень",
"stable": "Стабільний"
"prerelease": "Передреліз",
"stable": "Стабільний",
"testing": "Тестовий"
}
},
"Developer": {
"5secreload": "Перезавантаження за 5 секунд",
"enabling": "Увімкнення React DevTools",
"disabling": "Вимкнення React DevTools"
"disabling": "Вимкнення React DevTools",
"enabling": "Увімкнення React DevTools"
},
"FilePickerIndex": {
"folder": {
"select": "Використовувати цю папку"
}
},
"PluginListLabel": {
"hidden": "Приховано з меню швидкого доступу"
"MultiplePluginsInstallModal": {
"confirm": "Ви впевнені, що хочете застосувати такі модифікації?",
"description": {
"install": "Встановити {{name}} {{version}}",
"reinstall": "Перевстановити {{name}} {{version}}",
"update": "Оновити {{name}} до {{version}}"
},
"ok_button": {
"idle": "Підтвердити",
"loading": "Опрацювання"
},
"title": {
"install_few": "Встановити {{count}} плагінів",
"install_many": "Встановити {{count}} плагінів",
"install_one": "Встановити 1 плагін",
"mixed_few": "Модифікувати {{count}} плагінів",
"mixed_many": "",
"mixed_one": "Модифікувати 1 плагін",
"reinstall_few": "Перевстановити {{count}} плагінів",
"reinstall_many": "Перевстановити {{count}} плагінів",
"reinstall_one": "Перевстановити 1 плагін",
"update_few": "Оновити {{count}} плагінів",
"update_many": "Оновити {{count}} плагінів",
"update_one": "Оновити 1 плагін"
}
},
"PluginCard": {
"plugin_full_access": "Цей плагін має повний доступ до вашого Steam Deck.",
@@ -30,104 +53,92 @@
"install": {
"button_idle": "Встановити",
"button_processing": "Встановлення",
"title": "Встановити {{artifact}}",
"desc": "Ви впевнені, що хочете встановити {{artifact}} {{version}}?"
"desc": "Ви впевнені, що хочете встановити {{artifact}} {{version}}?",
"title": "Встановити {{artifact}}"
},
"no_hash": "Цей плагін не має хешу, ви встановлюєте його на власний ризик.",
"reinstall": {
"button_idle": "Перевстановити",
"button_processing": "Перевстановлення",
"desc": "Ви впевнені, що хочете перевстановити {{artifact}} {{version}}?",
"title": "Перевстановити {{artifact}}",
"button_processing": "Перевстановлення"
"title": "Перевстановити {{artifact}}"
},
"update": {
"button_idle": "Оновити",
"button_processing": "Оновлення",
"title": "Оновити {{artifact}}",
"desc": "Ви впевнені, що хочете оновити {{artifact}} {{version}}?"
},
"no_hash": "Цей плагін не має хешу, ви встановлюєте його на власний ризик."
},
"MultiplePluginsInstallModal": {
"title": {
"mixed_one": "Модифікувати 1 плагін",
"mixed_few": "Модифікувати {{count}} плагінів",
"mixed_many": "",
"reinstall_one": "Перевстановити 1 плагін",
"reinstall_few": "Перевстановити {{count}} плагінів",
"reinstall_many": "Перевстановити {{count}} плагінів",
"update_one": "Оновити 1 плагін",
"update_few": "Оновити {{count}} плагінів",
"update_many": "Оновити {{count}} плагінів",
"install_one": "Встановити 1 плагін",
"install_few": "Встановити {{count}} плагінів",
"install_many": "Встановити {{count}} плагінів"
},
"ok_button": {
"idle": "Підтвердити",
"loading": "Опрацювання"
},
"description": {
"install": "Встановити {{name}} {{version}}",
"update": "Оновити {{name}} до {{version}}",
"reinstall": "Перевстановити {{name}} {{version}}"
},
"confirm": "Ви впевнені, що хочете застосувати такі модифікації?"
"desc": "Ви впевнені, що хочете оновити {{artifact}} {{version}}?",
"title": "Оновити {{artifact}}"
}
},
"PluginListIndex": {
"hide": "Швидкий доступ: Приховати",
"no_plugin": "Плагінів не встановлено!",
"plugin_actions": "Дії плагінів",
"reinstall": "Перевстановити",
"reload": "Перезавантажити",
"update_to": "Оновити {{name}}",
"show": "Швидкий доступ: Показати",
"hide": "Швидкий доступ: Приховати",
"uninstall": "Видалити",
"update_all_one": "Оновити 1 плагін",
"update_all_few": "Оновити {{count}} плагінів",
"update_all_many": "Оновити {{count}} плагінів"
"update_all_many": "Оновити {{count}} плагінів",
"update_all_one": "Оновити 1 плагін",
"update_to": "Оновити {{name}}"
},
"PluginListLabel": {
"hidden": "Приховано з меню швидкого доступу"
},
"PluginLoader": {
"decky_title": "Decky",
"decky_update_available": "Доступне оновлення до {{tag_name}}!",
"error": "Помилка",
"plugin_error_uninstall": "Завантаження {{name}} спровокувало помилку показану вище. Зазвичай це означає, що плагін вимагає оновлення до нової версії SteamUI. Перевірте чи таке оновлення доступне або виконайте його видалення у налаштуваннях Decky, у секції Плагіни.",
"plugin_load_error": {
"message": "Помилка завантаження плагіна {{name}}",
"toast": "Помилка завантаження {{name}}"
},
"plugin_uninstall": {
"button": "Видалення",
"desc": "Ви впевнені, що хочете видалити {{name}}?",
"title": "Видалити {{name}}",
"button": "Видалення"
"title": "Видалити {{name}}"
},
"plugin_error_uninstall": "Завантаження {{name}} спровокувало помилку показану вище. Зазвичай це означає, що плагін вимагає оновлення до нової версії SteamUI. Перевірте чи таке оновлення доступне або виконайте його видалення у налаштуваннях Decky, у секції Плагіни.",
"plugin_update_one": "Доступне оновлення для 1 плагіна!",
"plugin_update_few": "Доступне оновлення для {{count}} плагінів!",
"plugin_update_many": "Доступне оновлення для {{count}} плагінів!"
"plugin_update_many": "Доступне оновлення для {{count}} плагінів!",
"plugin_update_one": "Доступне оновлення для 1 плагіна!"
},
"PluginView": {
"hidden_few": "{{count}} плагінів приховано з цього списку",
"hidden_many": "{{count}} плагінів приховано з цього списку",
"hidden_one": "{{count}} плагін приховано з цього списку"
},
"RemoteDebugging": {
"remote_cef": {
"desc": "Дозволити доступ до CEF-дебагера без аутентифікації для будь-кого у вашій мережі",
"label": "Дозволити віддалений CEF-дебагінг"
}
},
"SettingsDeveloperIndex": {
"cef_console": {
"button": "Відкрити консоль",
"label": "CEF-консоль",
"desc": "Відкрити CEF-консоль. Корисно тільки для дебагу. Ця штука потенційно небезпечна і повинна використовувати виключно якщо ви розробник плагіна, або якщо розробник спрямував вас сюди."
"desc": "Відкрити CEF-консоль. Корисно тільки для дебагу. Ця штука потенційно небезпечна і повинна використовувати виключно якщо ви розробник плагіна, або якщо розробник спрямував вас сюди.",
"label": "CEF-консоль"
},
"header": "Інше",
"react_devtools": {
"desc": "Вмикає доступ до компʼютера із запущеним React DevTools. Зміна цього налаштування перезавантажить Steam. Вкажіть IP перед увімкненням.",
"label": "Увімкнути React DevTools",
"ip_label": "IP"
"ip_label": "IP",
"label": "Увімкнути React DevTools"
},
"third_party_plugins": {
"button_install": "Встановити",
"button_zip": "Огляд",
"header": "Сторонні плагіни",
"label_desc": "URL",
"label_url": "Встановити плагін з URL",
"label_zip": "Встановити плагін з ZIP-файлу",
"button_zip": "Огляд"
"label_zip": "Встановити плагін з ZIP-файлу"
},
"valve_internal": {
"desc1": "Вмикає внутрішнє розробницьке меню Valve.",
"label": "Увімкнути Valve Internal",
"desc2": "Нічого не торкайтесь у цьому меню, якщо не розумієте, що ви робите."
"desc2": "Нічого не торкайтесь у цьому меню, якщо не розумієте, що ви робите.",
"label": "Увімкнути Valve Internal"
}
},
"SettingsGeneralIndex": {
@@ -155,8 +166,8 @@
},
"Store": {
"store_contrib": {
"label": "Зробити внесок",
"desc": "Якщо ви бажаєте додати щось у Decky Plugin Store, завітайте у репозиторій SteamDeckHomebrew/decky-plugin-template на GitHub. Інформація про розробку та поширення доступна у README."
"desc": "Якщо ви бажаєте додати щось у Decky Plugin Store, завітайте у репозиторій SteamDeckHomebrew/decky-plugin-template на GitHub. Інформація про розробку та поширення доступна у README.",
"label": "Зробити внесок"
},
"store_filter": {
"label": "Фільтр",
@@ -170,8 +181,8 @@
"label_def": "Востаннє оновлені (Найновіші)"
},
"store_source": {
"label": "Вихідний код",
"desc": "Код усіх плагінів доступний у репозиторії SteamDeckHomebrew/decky-plugin-database на GitHub."
"desc": "Код усіх плагінів доступний у репозиторії SteamDeckHomebrew/decky-plugin-database на GitHub.",
"label": "Вихідний код"
},
"store_tabs": {
"about": "Інформація",
@@ -189,8 +200,8 @@
"store_channel": {
"custom": "Власний",
"default": "За замовчуванням",
"testing": "Тестування",
"label": "Канал магазину"
"label": "Канал магазину",
"testing": "Тестування"
}
},
"Updater": {
@@ -198,25 +209,14 @@
"no_patch_notes_desc": "Немає нотаток до цієї версії",
"patch_notes_desc": "Перелік змін",
"updates": {
"check_button": "Перевірити оновлення",
"checking": "Перевірка",
"cur_version": "Поточна версія: {{ver}}",
"install_button": "Встановити оновлення",
"label": "Оновлення",
"lat_version": "Оновлено: використовується {{ver}}",
"reloading": "Перезавантаження",
"updating": "Оновлення",
"check_button": "Перевірити оновлення",
"lat_version": "Оновлено: використовується {{ver}}"
}
},
"PluginView": {
"hidden_one": "{{count}} плагін приховано з цього списку",
"hidden_few": "{{count}} плагінів приховано з цього списку",
"hidden_many": "{{count}} плагінів приховано з цього списку"
},
"RemoteDebugging": {
"remote_cef": {
"desc": "Дозволити доступ до CEF-дебагера без аутентифікації для будь-кого у вашій мережі",
"label": "Дозволити віддалений CEF-дебагінг"
"updating": "Оновлення"
}
}
}
@@ -1,10 +1,10 @@
{
"BranchSelect": {
"update_channel": {
"label": "更新通道",
"prerelease": "发布候选",
"stable": "稳定",
"testing": "测试",
"label": "更新通道"
"testing": "测试"
}
},
"Developer": {
@@ -12,11 +12,26 @@
"disabling": "正在禁用 React DevTools",
"enabling": "正在启用 React DevTools"
},
"DropdownMultiselect": {
"button": {
"back": "返回"
}
},
"FilePickerError": {
"errors": {
"file_not_found": "指定路径无效。请检查并输入正确的路径。",
"perm_denied": "你没有访问特定目录的权限。请检查你的用户(Steam Deck 中的 deck 账户)是否有权访问特定的文件夹或文件。",
"unknown": "发生了一个未知错误。原始错误为:{{raw_error}}"
}
},
"FilePickerIndex": {
"folder": {
"select": "使用这个文件",
"label": "文件夹",
"show_more": "显示更多文件"
"file": {
"select": "选择此文件"
},
"files": {
"all_files": "全部文件",
"file_type": "文件类型",
"show_hidden": "显示隐藏文件"
},
"filter": {
"created_asce": "创建日期(最旧)",
@@ -28,20 +43,35 @@
"size_asce": "大小(最小)",
"size_desc": "大小(最大)"
},
"files": {
"all_files": "全部文件",
"file_type": "文件类型",
"show_hidden": "显示隐藏文件"
"folder": {
"label": "文件",
"select": "使用这个文件夹",
"show_more": "显示更多文件"
}
},
"MultiplePluginsInstallModal": {
"confirm": "确定要进行以下修改吗?",
"description": {
"install": "安装 {{name}} {{version}}",
"reinstall": "重装 {{name}} {{version}}",
"update": "更新 {{name}} to {{version}}"
},
"file": {
"select": "选择此文件"
"ok_button": {
"idle": "确认",
"loading": "工作中"
},
"title": {
"install_other": "安装 {{count}} 个插件",
"mixed_other": "更改 {{count}} 个插件",
"reinstall_other": "重装 {{count}} 个插件",
"update_other": "更新 {{count}} 个插件"
}
},
"PluginCard": {
"plugin_full_access": "此插件可以完全访问你的 Steam Deck。",
"plugin_install": "安装",
"plugin_no_desc": "无描述提供。",
"plugin_version_label": "插件版本",
"plugin_full_access": "此插件可以完全访问你的 Steam Deck。"
"plugin_version_label": "插件版本"
},
"PluginInstallModal": {
"install": {
@@ -50,6 +80,7 @@
"desc": "你确定要安装 {{artifact}} {{version}} 吗?",
"title": "安装 {{artifact}}"
},
"no_hash": "此插件没有哈希校验值,你需要自行承担安装风险。",
"reinstall": {
"button_idle": "重新安装",
"button_processing": "正在重新安装",
@@ -61,22 +92,27 @@
"button_processing": "正在更新",
"desc": "你确定要更新 {{artifact}} {{version}} 吗?",
"title": "更新 {{artifact}}"
},
"no_hash": "此插件没有哈希校验值,你需要自行承担安装风险。"
}
},
"PluginListIndex": {
"freeze": "暂停更新",
"hide": "在快速访问菜单中隐藏",
"no_plugin": "没有安装插件!",
"plugin_actions": "插件操作",
"reinstall": "重新安装",
"reload": "重新加载",
"uninstall": "卸载",
"update_to": "更新 {{name}}",
"update_all_other": "更新 {{count}} 个插件",
"show": "在快速访问菜单中显示",
"hide": "在快速访问菜单中隐藏"
"unfreeze": "允许更新",
"uninstall": "卸载",
"update_all_other": "更新 {{count}} 个插件",
"update_to": "更新 {{name}}"
},
"PluginListLabel": {
"hidden": "在快速访问菜单中已隐藏"
},
"PluginLoader": {
"decky_title": "Decky",
"decky_update_available": "新版本 {{tag_name}} 可用!",
"error": "错误",
"plugin_error_uninstall": "加载 {{name}} 时引起了上述异常。这通常意味着插件需要更新以适应 SteamUI 的新版本。请检查插件是否有更新,或在 Decky 设置中的插件部分将其移除。",
"plugin_load_error": {
@@ -85,23 +121,31 @@
},
"plugin_uninstall": {
"button": "卸载",
"title": "卸载 {{name}}",
"desc": "你确定要卸载 {{name}} 吗?"
"desc": "你确定要卸载 {{name}} 吗?",
"title": "卸载 {{name}}"
},
"decky_update_available": "新版本 {{tag_name}} 可用!",
"plugin_update_other": "{{count}} 个插件有更新!"
},
"PluginView": {
"hidden_other": "此列表隐藏了 {{count}} 个插件"
},
"RemoteDebugging": {
"remote_cef": {
"desc": "允许你网络中的任何人无需身份验证即可访问CEF调试器",
"label": "允许远程访问CEF调试"
"desc": "允许你网络中的任何人无需身份验证即可访问 CEF 调试器",
"label": "允许 CEF 远程调试"
}
},
"SettingsDeveloperIndex": {
"cef_console": {
"button": "打开控制台",
"desc": "打开 CEF 控制台。仅在调试目的下使用。这列选项均有风险,请仅在您是插件开发者或是在插件开发者指导时访问使用。",
"label": "CEF 控制台"
},
"header": "其他",
"react_devtools": {
"desc": "允许连接到运行着 React DevTools 的计算机。更改此设置将重新加载 Steam。请在启用前设置 IP 地址。",
"ip_label": "IP",
"label": "启用 React DevTools",
"desc": "允许连接到运行着 React DevTools 的计算机,更改此设置将重新加载Steam,请在启用前设置IP地址。"
"label": "启用 React DevTools"
},
"third_party_plugins": {
"button_install": "安装",
@@ -115,13 +159,7 @@
"desc1": "启用 Valve 内部开发者菜单。",
"desc2": "除非你知道你在干什么,否则请不要修改此菜单中的任何内容。",
"label": "启用 Valve 内部开发者"
},
"cef_console": {
"button": "打开控制台",
"label": "CEF 控制台",
"desc": "打开 CEF 控制台。仅在调试目的下使用。这列选项均有风险,请仅在您是插件开发者或是在插件开发者指导时访问使用。"
},
"header": "其他"
}
},
"SettingsGeneralIndex": {
"about": {
@@ -134,27 +172,37 @@
"developer_mode": {
"label": "开发者模式"
},
"notifications": {
"decky_updates_label": "Decky 更新可用",
"header": "通知",
"plugin_updates_label": "插件更新可用"
},
"other": {
"header": "其他"
},
"updates": {
"header": "更新"
},
"notifications": {
"header": "通知",
"decky_updates_label": "Decky 更新可用",
"plugin_updates_label": "插件更新可用"
}
},
"SettingsIndex": {
"developer_title": "开发者",
"general_title": "通用",
"plugins_title": "插件"
"plugins_title": "插件",
"testing_title": "测试"
},
"Store": {
"download_progress_info": {
"download_zip": "正在下载插件",
"increment_count": "正在计入下载次数",
"installing_plugin": "正在安装插件",
"open_zip": "正在打开 ZIP 文件",
"parse_zip": "正在解析 ZIP 文件",
"start": "正在初始化",
"uninstalling_previous": "正在卸载之前的版本"
},
"store_contrib": {
"label": "贡献",
"desc": "如果你想要提交你的插件到 Decky 插件商店,请访问 GitHub 上的 SteamDeckHomebrew/decky-plugin-template 存储库,关于开发和分发的相关信息,请查看 README 文件。"
"desc": "如果你想要提交你的插件到 Decky 插件商店,请访问 GitHub 上的 SteamDeckHomebrew/decky-plugin-template 存储库。有关开发和分发插件的信息,请查看 README 文件。",
"label": "贡献"
},
"store_filter": {
"label": "过滤器",
@@ -168,13 +216,17 @@
"label_def": "最后更新 (最新)"
},
"store_source": {
"label": "源代码",
"desc": "所有插件的源代码都可以在 GitHub 上的 SteamDeckHomebrew/decky-plugin-database 存储库中获得。"
"desc": "所有插件的源代码都可从 GitHub 上的 SteamDeckHomebrew/decky-plugin-database 存储库中获得。",
"label": "源代码"
},
"store_tabs": {
"about": "关于",
"alph_asce": "字母排序 (Z 到 A)",
"alph_desc": "字母排序 (A 到 Z)",
"date_asce": "更新时间正序",
"date_desc": "更新时间倒序",
"downloads_asce": "下载量正序",
"downloads_desc": "下载量倒序",
"title": "浏览"
},
"store_testing_cta": "请考虑测试新插件以帮助 Decky Loader 团队!",
@@ -184,17 +236,28 @@
}
},
"StoreSelect": {
"store_channel": {
"default": "默认",
"label": "商店通道",
"testing": "测试",
"custom": "自定义"
},
"custom_store": {
"label": "自定义商店",
"url_label": "URL"
},
"store_channel": {
"custom": "自定义",
"default": "默认",
"label": "商店通道",
"testing": "测试"
}
},
"Testing": {
"download": "下载",
"error": "安装 PR 时出错",
"header": "以下版本的 Decky Loader 是根据开放的第三方 Pull Request 构建的。Decky Loader 团队尚未验证这些版本的功能或安全性,且它们可能已经过期。",
"loading": "正在加载尚未合并的 Pull Request ...",
"start_download_toast": "正在下载 PR #{{id}}"
},
"TitleView": {
"decky_store_desc": "打开 Decky 商店",
"settings_desc": "打开 Decky 设置"
},
"Updater": {
"decky_updates": "Decky 更新",
"no_patch_notes_desc": "此版本没有补丁说明",
@@ -209,45 +272,5 @@
"reloading": "重新加载中",
"updating": "更新中"
}
},
"MultiplePluginsInstallModal": {
"title": {
"mixed_other": "更改 {{count}} 个插件",
"update_other": "更新 {{count}} 个插件",
"reinstall_other": "重装 {{count}} 个插件",
"install_other": "安装 {{count}} 个插件"
},
"ok_button": {
"idle": "确认",
"loading": "工作中"
},
"confirm": "确定要进行以下修改吗?",
"description": {
"install": "安装 {{name}} {{version}}",
"update": "更新 {{name}} to {{version}}",
"reinstall": "重装 {{name}} {{version}}"
}
},
"PluginListLabel": {
"hidden": "在快速访问菜单中已隐藏"
},
"PluginView": {
"hidden_other": "此列表隐藏了 {{count}} 个插件"
},
"DropdownMultiselect": {
"button": {
"back": "返回"
}
},
"FilePickerError": {
"errors": {
"file_not_found": "指定路径无效。请检查并输入正确的路径。",
"unknown": "发生了一个未知错误。原始错误为:{{raw_error}}",
"perm_denied": "你没有访问特定目录的权限。请检查你的用户(Steam Deck 中的 deck 账户)有着相对应的权限以访问特定的文件夹或文件。"
}
},
"TitleView": {
"decky_store_desc": "打开 Decky 商店",
"settings_desc": "打开 Decky 设置"
}
}
@@ -1,10 +1,10 @@
{
"BranchSelect": {
"update_channel": {
"testing": "測試版",
"label": "更新頻道",
"prerelease": "預發佈",
"stable": "穩定版"
"stable": "穩定版",
"testing": "測試版"
}
},
"Developer": {
@@ -12,22 +12,19 @@
"disabling": "正在停用 React DevTools",
"enabling": "正在啟用 React DevTools"
},
"DropdownMultiselect": {
"button": {
"back": "返回"
}
},
"FilePickerError": {
"errors": {
"file_not_found": "指定路徑無效。請檢查並輸入正確路徑。",
"perm_denied": "您沒有瀏覽此目錄的權限。請檢查您的使用者(Steam Deck 中的 deck 帳號)有權限瀏覽特定的資料夾或檔案。",
"unknown": "發生未知錯誤。錯誤詳細資料:{{raw_error}}"
}
},
"FilePickerIndex": {
"folder": {
"select": "使用此資料夾",
"show_more": "顯示更多檔案",
"label": "資料夾"
},
"filter": {
"modified_asce": "修改日期(舊到新)",
"created_desc": "建立日期(新到舊)",
"modified_desc": "修改日期(新到舊)",
"name_desc": "子母排序(A到Z",
"name_asce": "子母排序(Z到A",
"size_asce": "檔案大小(小到大)",
"size_desc": "檔案大小(大到小)",
"created_asce": "建立日期(舊到新)"
},
"file": {
"select": "選擇此檔案"
},
@@ -35,21 +32,55 @@
"all_files": "所有檔案",
"file_type": "檔案類型",
"show_hidden": "顯示隱藏檔"
},
"filter": {
"created_asce": "建立日期(舊到新)",
"created_desc": "建立日期(新到舊)",
"modified_asce": "修改日期(舊到新)",
"modified_desc": "修改日期(新到舊)",
"name_asce": "子母排序(Z到A",
"name_desc": "子母排序(A到Z",
"size_asce": "檔案大小(小到大)",
"size_desc": "檔案大小(大到小)"
},
"folder": {
"label": "資料夾",
"select": "使用此資料夾",
"show_more": "顯示更多檔案"
}
},
"MultiplePluginsInstallModal": {
"confirm": "您確定要進行以下的修改嗎?",
"description": {
"install": "安裝 {{name}} {{version}}",
"reinstall": "重新安裝 {{name}} {{version}}",
"update": "更新 {{name}} 的版本到 {{version}}"
},
"ok_button": {
"idle": "確定",
"loading": "執行中"
},
"title": {
"install_other": "安裝 {{count}} 個外掛程式",
"mixed_other": "修改 {{count}} 個外掛程式",
"reinstall_other": "重新安裝 {{count}} 個外掛程式",
"update_other": "更新 {{count}} 個外掛程式"
}
},
"PluginCard": {
"plugin_full_access": "此外掛程式擁有您的 Steam Deck 的完整存取權。",
"plugin_install": "安裝",
"plugin_no_desc": "未提描述。",
"plugin_version_label": "外掛程式版本",
"plugin_full_access": "此外掛程式擁有您的 Steam Deck 的完整存取權。"
"plugin_no_desc": "未提描述。",
"plugin_version_label": "外掛程式版本"
},
"PluginInstallModal": {
"install": {
"button_idle": "安裝",
"button_processing": "正在安裝",
"title": "安裝 {{artifact}}",
"desc": "您確定要安裝 {{artifact}} {{version}} 嗎?"
"desc": "您確定要安裝 {{artifact}} {{version}} 嗎?",
"title": "安裝 {{artifact}}"
},
"no_hash": "此外掛程式沒有提供 hash 驗證,安裝可能有風險。",
"reinstall": {
"button_idle": "重新安裝",
"button_processing": "正在重新安裝",
@@ -61,22 +92,27 @@
"button_processing": "正在更新",
"desc": "您確定要更新 {{artifact}} {{version}} 嗎?",
"title": "更新 {{artifact}}"
},
"no_hash": "此外掛程式沒有提供 hash 驗證,安裝可能有風險。"
}
},
"PluginListIndex": {
"freeze": "禁止更新",
"hide": "快速存取:隱藏",
"no_plugin": "未安裝外掛程式!",
"plugin_actions": "外掛程式操作",
"uninstall": "解除安裝",
"update_to": "更新到 {{name}}",
"reinstall": "重新安裝",
"reload": "重新載入",
"show": "快速存取:顯示",
"hide": "快速存取:隱藏",
"update_all_other": "更新 {{count}} 個外掛程式"
"unfreeze": "允許更新",
"uninstall": "解除安裝",
"update_all_other": "更新 {{count}} 個外掛程式",
"update_to": "更新到 {{name}}"
},
"PluginListLabel": {
"hidden": "已從快速存取選單中移除"
},
"PluginLoader": {
"decky_title": "Decky",
"decky_update_available": "可更新至版本 {{tag_name}}",
"error": "錯誤",
"plugin_error_uninstall": "載入 {{name}} 導致上述異常。這通常意味著該外掛程式需要針對新版本的 SteamUI 進行更新。在 Decky 設定中檢查是否存在更新,或評估刪除此外掛程式。",
"plugin_load_error": {
@@ -85,12 +121,14 @@
},
"plugin_uninstall": {
"button": "解除安裝",
"title": "解除安裝 {{name}}",
"desc": "您確定要解除安裝 {{name}} 嗎?"
"desc": "您確定要解除安裝 {{name}} 嗎?",
"title": "解除安裝 {{name}}"
},
"decky_update_available": "可更新至版本 {{tag_name}}",
"plugin_update_other": "可更新 {{count}} 個外掛程式!"
},
"PluginView": {
"hidden_other": "{{count}} 個外掛程式已隱藏"
},
"RemoteDebugging": {
"remote_cef": {
"desc": "允許您的網路中的任何人未經認證地存取 CEF 偵錯器",
@@ -98,35 +136,35 @@
}
},
"SettingsDeveloperIndex": {
"third_party_plugins": {
"button_zip": "開啟",
"label_desc": "網址",
"label_url": "從網址安裝外掛程式",
"label_zip": "從 ZIP 檔案安裝外掛程式",
"button_install": "安裝",
"header": "第三方外掛程式"
},
"valve_internal": {
"desc2": "除非您知道它的作用,否則不要碰這個選單中的任何東西。",
"desc1": "啟用 Valve 內建開發人員選單。",
"label": "啟用 Valve 內建"
"cef_console": {
"button": "開啟控制台",
"desc": "開啟 CEF 控制台。僅用於偵錯。這裡的東西有潛在的風險,只有當您是一個外掛程式開發者或者被外掛程式開發者引導到這裡時,才應該使用。",
"label": "CEF 控制台"
},
"header": "其他",
"react_devtools": {
"desc": "啟用與執行 React DevTools 的電腦的連接。改變這個設定將重新載入 Steam。啟用前必須設定 IP 位址。",
"ip_label": "IP",
"label": "啟用 React DevTools"
},
"header": "其他",
"cef_console": {
"button": "開啟控制台",
"label": "CEF 控制台",
"desc": "開啟 CEF 控制台。僅用於偵錯。這裡的東西有潛在的風險,只有當您是一個外掛程式開發者或者被外掛程式開發者引導到這裡時,才應該使用。"
"third_party_plugins": {
"button_install": "安裝",
"button_zip": "瀏覽",
"header": "第三方外掛程式",
"label_desc": "網址",
"label_url": "從網址安裝外掛程式",
"label_zip": "從 ZIP 檔案安裝外掛程式"
},
"valve_internal": {
"desc1": "啟用 Valve 內建開發人員選單。",
"desc2": "除非您知道它的作用,否則不要碰這個選單中的任何東西。",
"label": "啟用 Valve 內建"
}
},
"SettingsGeneralIndex": {
"about": {
"header": "關於",
"decky_version": "Decky 版本"
"decky_version": "Decky 版本",
"header": "關於"
},
"beta": {
"header": "參與測試"
@@ -134,27 +172,28 @@
"developer_mode": {
"label": "開發人員模式"
},
"notifications": {
"decky_updates_label": "Decky 可更新",
"header": "通知",
"plugin_updates_label": "外掛程式有更新"
},
"other": {
"header": "其他"
},
"updates": {
"header": "更新"
},
"notifications": {
"decky_updates_label": "Decky 可更新",
"header": "通知",
"plugin_updates_label": "外掛程式有更新"
}
},
"SettingsIndex": {
"developer_title": "開發人員",
"general_title": "一般",
"plugins_title": "外掛程式"
"plugins_title": "外掛程式",
"testing_title": "測試"
},
"Store": {
"store_contrib": {
"label": "貢獻",
"desc": "如果您想為 Decky 外掛程式商店做貢獻,請查看 GitHub 上的 SteamDeckHomebrew/decky-plugin-template 儲存庫。README 中提供了有關開發和發佈的資訊。"
"desc": "如果您想為 Decky 外掛程式商店做貢獻,請查看 GitHub 上的 SteamDeckHomebrew/decky-plugin-template 儲存庫。README 中提供了有關開發和發佈的資訊。",
"label": "貢獻"
},
"store_filter": {
"label": "過濾",
@@ -168,16 +207,24 @@
"label_def": "最後更新 (最新)"
},
"store_source": {
"label": "原始碼",
"desc": "所有外掛程式原始碼可以在 GitHub 的 SteamDeckHomebrew/decky-plugin-database 儲存庫查看。"
"desc": "所有外掛程式原始碼可以在 GitHub 的 SteamDeckHomebrew/decky-plugin-database 儲存庫查看。",
"label": "原始碼"
},
"store_tabs": {
"about": "關於",
"alph_asce": "依字母排序 (Z 到 A)",
"alph_desc": "依字母排序 (A 到 Z)",
"date_asce": "日期舊到新",
"date_desc": "日期新到舊",
"downloads_asce": "下載量低到高",
"downloads_desc": "下載量高到低",
"title": "瀏覽"
},
"store_testing_cta": "請考慮測試新的外掛程式來幫助 Decky Loader 團隊!"
"store_testing_cta": "請考慮測試新的外掛程式來幫助 Decky Loader 團隊!",
"store_testing_warning": {
"desc": "您可以使用此商店頻道來體驗測試外掛版本。請務必在 GitHub 上留下回饋,以便為所有用戶更新該外掛程式。",
"label": "歡迎來到測試頻道"
}
},
"StoreSelect": {
"custom_store": {
@@ -191,55 +238,26 @@
"testing": "測試"
}
},
"Testing": {
"download": "下載"
},
"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": "正在重新載入",
"check_button": "檢查更新",
"cur_version": "目前版本:{{ver}}",
"updating": "正在更新"
}
},
"PluginView": {
"hidden_other": "{{count}} 個外掛程式已隱藏"
},
"PluginListLabel": {
"hidden": "已從快速存取選單中移除"
},
"MultiplePluginsInstallModal": {
"title": {
"mixed_other": "修改 {{count}} 個外掛程式",
"update_other": "更新 {{count}} 個外掛程式",
"reinstall_other": "重新安裝 {{count}} 個外掛程式",
"install_other": "安裝 {{count}} 個外掛程式"
},
"ok_button": {
"idle": "確定",
"loading": "執行中"
},
"confirm": "您確定要進行以下的修改嗎?",
"description": {
"install": "安裝 {{name}} {{version}}",
"update": "更新 {{name}} 到 {{version}}",
"reinstall": "重新安裝 {{name}} {{version}}"
}
},
"FilePickerError": {
"errors": {
"perm_denied": "您沒有瀏覽此目錄的權限。請檢查您的使用者(Steam Deck 中的 deck 帳號)有權限瀏覽特定的資料夾或檔案。",
"unknown": "發生未知錯誤。錯誤詳細資料:{{raw_error}}",
"file_not_found": "指定路徑無效。請檢查並輸入正確路徑。"
}
},
"DropdownMultiselect": {
"button": {
"back": "返回"
}
}
}
@@ -37,6 +37,9 @@ def get_live_reload() -> bool:
def get_keep_systemd_service() -> bool:
return os.getenv("KEEP_SYSTEMD_SERVICE", "0") == "1"
def get_use_cef_close_workaround() -> bool:
return ON_LINUX and os.getenv("USE_CEF_CLOSE_WORKAROUND", "1") == "1"
def get_log_level() -> int:
return {"CRITICAL": 50, "ERROR": 40, "WARNING": 30, "INFO": 20, "DEBUG": 10}[
os.getenv("LOG_LEVEL", "INFO")
@@ -1,9 +1,21 @@
from re import compile
from asyncio import Lock, create_subprocess_exec
from asyncio.subprocess import PIPE, DEVNULL, STDOUT, Process
from subprocess import call as call_sync
import os, pwd, grp, sys, logging
from subprocess import call, run, DEVNULL, PIPE, STDOUT
from .customtypes import UserType
from typing import IO, Any, Mapping
from ..enums import UserType
logger = logging.getLogger("localplatform")
# subprocess._ENV
ENV = Mapping[str, str]
ProcessIO = int | IO[Any] | None
async def run(args: list[str], stdin: ProcessIO = DEVNULL, stdout: ProcessIO = PIPE, stderr: ProcessIO = PIPE, env: ENV | None = None) -> tuple[Process, bytes | None, bytes | None]:
proc = await create_subprocess_exec(args[0], *(args[1:]), stdin=stdin, stdout=stdout, stderr=stderr, env=env)
proc_stdout, proc_stderr = await proc.communicate()
return (proc, proc_stdout, proc_stderr)
# Get the user id hosting the plugin loader
def _get_user_id() -> int:
return pwd.getpwnam(_get_user()).pw_uid
@@ -52,14 +64,28 @@ def chown(path : str, user : UserType = UserType.HOST_USER, recursive : bool =
else:
raise Exception("Unknown User Type")
result = call(["chown", "-R", user_str, path] if recursive else ["chown", user_str, path])
result = call_sync(["chown", "-R", user_str, path] if recursive else ["chown", user_str, path])
return result == 0
def chmod(path : str, permissions : int, recursive : bool = True) -> bool:
if _get_effective_user_id() != 0:
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:
user_owner = _get_user_owner(path)
@@ -115,23 +141,41 @@ def setuid(user : UserType = UserType.HOST_USER):
os.setuid(user_id)
async def service_active(service_name : str) -> bool:
res = run(["systemctl", "is-active", service_name], stdout=DEVNULL, stderr=DEVNULL)
res, _, _ = await run(["systemctl", "is-active", service_name], stdout=DEVNULL, stderr=DEVNULL)
return res.returncode == 0
async def service_restart(service_name : str) -> bool:
call(["systemctl", "daemon-reload"])
async def service_restart(service_name : str, block : bool = True) -> bool:
await run(["systemctl", "daemon-reload"])
cmd = ["systemctl", "restart", service_name]
res = run(cmd, stdout=PIPE, stderr=STDOUT)
if not block:
cmd.append("--no-block")
res, _, _ = await run(cmd, stdout=PIPE, stderr=STDOUT)
return res.returncode == 0
async def service_stop(service_name : str) -> bool:
if not await service_active(service_name):
# Service isn't running. pretend we stopped it
return True
cmd = ["systemctl", "stop", service_name]
res = run(cmd, stdout=PIPE, stderr=STDOUT)
res, _, _ = await run(cmd, stdout=PIPE, stderr=STDOUT)
return res.returncode == 0
async def service_start(service_name : str) -> bool:
if await service_active(service_name):
# Service is running. pretend we started it
return True
cmd = ["systemctl", "start", service_name]
res = run(cmd, stdout=PIPE, stderr=STDOUT)
res, _, _ = await run(cmd, stdout=PIPE, stderr=STDOUT)
return res.returncode == 0
async def restart_webhelper() -> bool:
logger.info("Restarting steamwebhelper")
# TODO move to pkill
res, _, _ = await run(["killall", "-s", "SIGTERM", "steamwebhelper"], stdout=DEVNULL, stderr=DEVNULL)
return res.returncode == 0
def get_privileged_path() -> str:
@@ -140,6 +184,8 @@ def get_privileged_path() -> str:
if path == None:
path = get_unprivileged_path()
os.makedirs(path, exist_ok=True)
return path
def _parent_dir(path : str | None) -> str | None:
@@ -159,16 +205,23 @@ def get_unprivileged_path() -> str:
if path == None:
logger.debug("Unprivileged path is not properly configured. Making something up!")
# Expected path of loader binary is /home/deck/homebrew/service/PluginLoader
path = _parent_dir(_parent_dir(os.path.realpath(sys.argv[0])))
if hasattr(sys, 'frozen'):
# Expected path of loader binary is /home/deck/homebrew/service/PluginLoader
path = _parent_dir(_parent_dir(os.path.realpath(sys.argv[0])))
else:
# Expected path of this file is $src_root/backend/src/localplatformlinux.py
path = _parent_dir(_parent_dir(_parent_dir(__file__)))
if path != None and not os.path.exists(path):
path = None
if path == None:
logger.warn("Unprivileged path is not properly configured. Defaulting to /home/deck/homebrew")
logger.warning("Unprivileged path is not properly configured. Defaulting to /home/deck/homebrew")
path = "/home/deck/homebrew" # We give up
os.makedirs(path, exist_ok=True)
return path
@@ -186,7 +239,43 @@ def get_unprivileged_user() -> str:
break
if user == None:
logger.warn("Unprivileged user is not properly configured. Defaulting to 'deck'")
logger.warning("Unprivileged user is not properly configured. Defaulting to 'deck'")
user = 'deck'
return user
# Works around the CEF debugger TCP socket not closing properly when Steam restarts
# Group 1 is PID, group 2 is FD. this also filters for "steamwebhelper" in the process name.
cef_socket_lsof_regex = compile(r"^p(\d+)(?:\s|.)+csteamwebhelper(?:\s|.)+f(\d+)(?:\s|.)+TST=LISTEN")
close_cef_socket_lock = Lock()
async def close_cef_socket():
async with close_cef_socket_lock:
if _get_effective_user_id() != 0:
logger.warning("Can't close CEF socket as Decky isn't running as root.")
return
# Look for anything listening TCP on port 8080
lsof, stdout, _ = await run(["lsof", "-F", "-iTCP:8080", "-sTCP:LISTEN"], stdout=PIPE)
if not stdout or lsof.returncode != 0 or len(stdout) < 1:
logger.error(f"lsof call failed in close_cef_socket! return code: {str(lsof.returncode)}")
return
lsof_data = cef_socket_lsof_regex.match(stdout.decode())
if not lsof_data:
logger.error("lsof regex match failed in close_cef_socket!")
return
pid = lsof_data.group(1)
fd = lsof_data.group(2)
logger.info(f"Closing CEF socket with PID {pid} and FD {fd}")
# Use gdb to inject a close() call for the socket fd into steamwebhelper
gdb_ret, _, _ = await run(["gdb", "--nx", "-p", pid, "--batch", "--eval-command", f"call (int)close({fd})"], env={"LD_LIBRARY_PATH": ""})
if gdb_ret.returncode != 0:
logger.error(f"Failed to close CEF socket with gdb! return code: {str(gdb_ret.returncode)}", exc_info=True)
return
logger.info("CEF socket closed")
@@ -1,4 +1,4 @@
from .customtypes import UserType
from ..enums import UserType
import os, sys
def chown(path : str, user : UserType = UserType.HOST_USER, recursive : bool = True) -> bool:
@@ -28,7 +28,7 @@ async def service_stop(service_name : str) -> bool:
async def service_start(service_name : str) -> bool:
return True # Stubbed
async def service_restart(service_name : str) -> bool:
async def service_restart(service_name : str, block : bool = True) -> bool:
if service_name == "plugin_loader":
sys.exit(42)
@@ -47,7 +47,15 @@ def get_unprivileged_path() -> str:
if path == None:
path = os.getenv("PRIVILEGED_PATH", os.path.join(os.path.expanduser("~"), "homebrew"))
os.makedirs(path, exist_ok=True)
return path
def get_unprivileged_user() -> str:
return os.getenv("UNPRIVILEGED_USER", os.getlogin())
async def restart_webhelper() -> bool:
return True # Stubbed
async def close_cef_socket():
return # Stubbed
@@ -1,5 +1,5 @@
import asyncio, time
from typing import Awaitable, Callable
from typing import Any, Callable, Coroutine
import random
from .localplatform import ON_WINDOWS
@@ -7,21 +7,29 @@ from .localplatform import ON_WINDOWS
BUFFER_LIMIT = 2 ** 20 # 1 MiB
class UnixSocket:
def __init__(self, on_new_message: Callable[[str], Awaitable[str|None]]):
def __init__(self):
'''
on_new_message takes 1 string argument.
It's return value gets used, if not None, to write data to the socket.
Method should be async
'''
self.socket_addr = f"/tmp/plugin_socket_{time.time()}"
self.on_new_message = on_new_message
self.on_new_message = None
self.socket = None
self.reader = None
self.writer = None
self.server_writer = None
self.open_lock = asyncio.Lock()
self.active = True
async def setup_server(self, on_new_message: Callable[[str], Coroutine[Any, Any, Any]]):
try:
self.on_new_message = on_new_message
self.socket = await asyncio.start_unix_server(self._listen_for_method_call, path=self.socket_addr, limit=BUFFER_LIMIT)
except asyncio.CancelledError:
await self.close_socket_connection()
raise
async def setup_server(self):
self.socket = await asyncio.start_unix_server(self._listen_for_method_call, path=self.socket_addr, limit=BUFFER_LIMIT)
async def _open_socket_if_not_exists(self):
if not self.reader:
retries = 0
@@ -37,10 +45,11 @@ class UnixSocket:
return True
async def get_socket_connection(self):
if not await self._open_socket_if_not_exists():
return None, None
return self.reader, self.writer
async with self.open_lock:
if not await self._open_socket_if_not_exists():
return None, None
return self.reader, self.writer
async def close_socket_connection(self):
if self.writer != None:
@@ -48,6 +57,12 @@ class UnixSocket:
self.reader = None
if self.socket:
self.socket.close()
await self.socket.wait_closed()
self.active = False
async def read_single_line(self) -> str|None:
reader, _ = await self.get_socket_connection()
@@ -70,15 +85,17 @@ class UnixSocket:
async def _read_single_line(self, reader: asyncio.StreamReader) -> str:
line = bytearray()
while True:
while self.active:
try:
line.extend(await reader.readuntil())
except asyncio.LimitOverrunError:
line.extend(await reader.read(reader._limit)) # type: ignore
line.extend(await reader.read(reader._limit)) # pyright: ignore [reportUnknownMemberType, reportUnknownArgumentType, reportAttributeAccessIssue]
continue
except asyncio.IncompleteReadError as err:
line.extend(err.partial)
break
except asyncio.CancelledError:
raise
else:
break
@@ -90,33 +107,43 @@ class UnixSocket:
writer.write(message.encode("utf-8"))
await writer.drain()
async def write_single_line_server(self, message: str):
if self.server_writer is None:
return
await self._write_single_line(self.server_writer, message)
async def _listen_for_method_call(self, reader: asyncio.StreamReader, writer: asyncio.StreamWriter):
while True:
self.server_writer = writer
while self.active and self.on_new_message:
def _(task: asyncio.Task[str|None]):
res = task.result()
if res is not None:
asyncio.create_task(self._write_single_line(writer, res))
line = await self._read_single_line(reader)
try:
res = await self.on_new_message(line)
except Exception:
return
if res != None:
await self._write_single_line(writer, res)
asyncio.create_task(self.on_new_message(line)).add_done_callback(_)
class PortSocket (UnixSocket):
def __init__(self, on_new_message: Callable[[str], Awaitable[str|None]]):
def __init__(self):
'''
on_new_message takes 1 string argument.
It's return value gets used, if not None, to write data to the socket.
Method should be async
'''
super().__init__(on_new_message)
super().__init__()
self.host = "127.0.0.1"
self.port = random.sample(range(40000, 60000), 1)[0]
async def setup_server(self):
self.socket = await asyncio.start_server(self._listen_for_method_call, host=self.host, port=self.port, limit=BUFFER_LIMIT)
async def setup_server(self, on_new_message: Callable[[str], Coroutine[Any, Any, Any]]):
try:
self.on_new_message = on_new_message
self.socket = await asyncio.start_server(self._listen_for_method_call, host=self.host, port=self.port, limit=BUFFER_LIMIT)
except asyncio.CancelledError:
await self.close_socket_connection()
raise
async def _open_socket_if_not_exists(self):
if not self.reader:
retries = 0
@@ -136,4 +163,4 @@ if ON_WINDOWS:
pass
else:
class LocalSocket (UnixSocket):
pass
pass
@@ -1,36 +1,41 @@
# Change PyInstaller files permissions
import sys
from typing import Dict
from .localplatform import (chmod, chown, service_stop, service_start,
ON_WINDOWS, get_log_level, get_live_reload,
from typing import Any, Dict
from .localplatform.localplatform import (chmod, chown, service_stop, service_start,
ON_WINDOWS, ON_LINUX, get_log_level, get_live_reload,
get_server_port, get_server_host, get_chown_plugin_path,
get_privileged_path)
get_privileged_path, restart_webhelper)
if hasattr(sys, '_MEIPASS'):
chmod(sys._MEIPASS, 755) # type: ignore
# Full imports
from asyncio import AbstractEventLoop, new_event_loop, set_event_loop, sleep
import multiprocessing
multiprocessing.freeze_support()
from asyncio import AbstractEventLoop, CancelledError, Task, all_tasks, current_task, gather, new_event_loop, set_event_loop, sleep
from logging import basicConfig, getLogger
from os import path
from traceback import format_exc
import multiprocessing
from time import time
import aiohttp_cors # pyright: ignore [reportMissingTypeStubs]
import aiohttp_cors # type: ignore
# Partial imports
from aiohttp import client_exceptions
from aiohttp.web import Application, Response, Request, get, run_app, static # type: ignore
from aiohttp.web import Application, Response, Request, get, run_app, static # pyright: ignore [reportUnknownVariableType]
from aiohttp_jinja2 import setup as jinja_setup
from setproctitle import getproctitle, setproctitle, setthreadtitle
# local modules
from .browser import PluginBrowser
from .helpers import (REMOTE_DEBUGGER_UNIT, csrf_middleware, get_csrf_token,
from .helpers import (REMOTE_DEBUGGER_UNIT, create_inject_script, csrf_middleware, get_csrf_token, get_loader_version,
mkdir_as_user, get_system_pythonpaths, get_effective_user_id)
from .injector import get_gamepadui_tab, Tab, close_old_tabs
from .injector import get_gamepadui_tab, Tab
from .loader import Loader
from .settings import SettingsManager
from .updater import Updater
from .utilities import Utilities
from .customtypes import UserType
from .enums import UserType
from .wsrouter import WSRouter
basicConfig(
@@ -54,6 +59,8 @@ if get_chown_plugin_path() == True:
class PluginManager:
def __init__(self, loop: AbstractEventLoop) -> None:
self.loop = loop
self.reinject: bool = True
self.js_ctx_tab: Tab | None = None
self.web_app = Application()
self.web_app.middlewares.append(csrf_middleware)
self.cors = aiohttp_cors.setup(self.web_app, defaults={
@@ -63,11 +70,15 @@ class PluginManager:
allow_credentials=True
)
})
self.plugin_loader = Loader(self, plugin_path, self.loop, get_live_reload())
self.ws = WSRouter(self.loop, self.web_app)
self.plugin_loader = Loader(self, self.ws, plugin_path, self.loop, get_live_reload())
self.settings = SettingsManager("loader", path.join(get_privileged_path(), "settings"))
self.plugin_browser = PluginBrowser(plugin_path, self.plugin_loader.plugins, self.plugin_loader, self.settings)
self.utilities = Utilities(self)
self.updater = Updater(self)
self.last_webhelper_exit: float = 0
self.webhelper_crash_count: int = 0
self.inject_fallback: bool = False
jinja_setup(self.web_app)
@@ -80,14 +91,70 @@ class PluginManager:
self.loop.create_task(self.load_plugins())
self.web_app.on_startup.append(startup)
self.web_app.on_shutdown.append(self.shutdown)
self.loop.set_exception_handler(self.exception_handler)
self.web_app.add_routes([get("/auth/token", self.get_auth_token)])
for route in list(self.web_app.router.routes()):
self.cors.add(route) # type: ignore
self.web_app.add_routes([static("/static", path.join(path.dirname(__file__), '..', 'static'))])
self.web_app.add_routes([static("/legacy", path.join(path.dirname(__file__), 'legacy'))])
self.cors.add(route) # pyright: ignore [reportUnknownMemberType]
self.web_app.add_routes([static("/static", path.join(path.dirname(__file__), 'static'))])
async def handle_crash(self):
if not self.reinject:
return
new_time = time()
if (new_time - self.last_webhelper_exit < 60):
self.webhelper_crash_count += 1
logger.warning(f"webhelper crashed within a minute from last crash! crash count: {self.webhelper_crash_count}")
else:
self.webhelper_crash_count = 0
self.last_webhelper_exit = new_time
# should never happen
if (self.webhelper_crash_count > 4):
await self.updater.do_shutdown()
# Give up
exit(0)
async def shutdown(self, _: Application):
try:
logger.info(f"Shutting down...")
logger.info("Disabling reload...")
await self.plugin_loader.disable_reload()
logger.info("Killing plugins...")
await self.plugin_loader.shutdown_plugins()
logger.info("Disconnecting from WS...")
self.reinject = False
await self.ws.disconnect()
if self.js_ctx_tab:
await self.js_ctx_tab.close_websocket()
self.js_ctx_tab = None
except:
logger.info("Error during shutdown:\n" + format_exc())
pass
finally:
logger.info("Cancelling tasks...")
tasks = all_tasks()
current = current_task()
async def cancel_task(task: Task[Any]):
name = task.get_coro().__qualname__
logger.debug(f"Cancelling task {name}")
try:
task.cancel()
try:
await task
except CancelledError:
pass
logger.debug(f"Task {name} finished")
except:
logger.warning(f"Failed to cancel task {name}:\n" + format_exc())
pass
if current:
tasks.remove(current)
await gather(*[cancel_task(task) for task in tasks])
logger.info("Shutdown finished.")
def exception_handler(self, loop: AbstractEventLoop, context: Dict[str, str]):
if context["message"] == "Unclosed connection":
@@ -100,18 +167,19 @@ class PluginManager:
async def load_plugins(self):
# await self.wait_for_server()
logger.debug("Loading plugins")
self.plugin_loader.import_plugins()
# await inject_to_tab("SP", "window.syncDeckyPlugins();")
await self.plugin_loader.import_plugins()
if self.settings.getSetting("pluginOrder", None) == None:
self.settings.setSetting("pluginOrder", list(self.plugin_loader.plugins.keys()))
logger.debug("Did not find pluginOrder setting, set it to default")
async def loader_reinjector(self):
while True:
while self.reinject:
tab = None
nf = False
dc = False
while not tab:
if not self.reinject:
return
try:
tab = await get_gamepadui_tab()
except (client_exceptions.ClientConnectorError, client_exceptions.ServerDisconnectedError):
@@ -127,27 +195,33 @@ class PluginManager:
if not tab:
await sleep(5)
await tab.open_websocket()
self.js_ctx_tab = tab
await tab.enable()
await self.inject_javascript(tab, True)
try:
async for msg in tab.listen_for_message():
# this gets spammed a lot
if msg.get("method", None) != "Page.navigatedWithinDocument":
logger.debug("Page event: " + str(msg.get("method", None)))
if msg.get("method", None) == "Page.domContentEventFired":
if not await tab.has_global_var("deckyHasLoaded", False):
await self.inject_javascript(tab)
if msg.get("method", None) == "Inspector.detached":
logger.info("CEF has requested that we detach.")
await tab.close_websocket()
break
if msg.get("method", None) == "Page.domContentEventFired":
if not await tab.has_global_var("deckyHasLoaded", False):
await self.inject_javascript(tab)
elif msg.get("method", None) == "Inspector.detached":
if not self.reinject:
return
logger.info("CEF has requested that we detach.")
await tab.close_websocket()
self.js_ctx_tab = None
break
# If this is a forceful disconnect the loop will just stop without any failure message. In this case, injector.py will handle this for us so we don't need to close the socket.
# This is because of https://github.com/aio-libs/aiohttp/blob/3ee7091b40a1bc58a8d7846e7878a77640e96996/aiohttp/client_ws.py#L321
logger.info("CEF has disconnected...")
await self.handle_crash()
# At this point the loop starts again and we connect to the freshly started Steam client once it is ready.
except Exception:
if not self.reinject:
return
logger.error("Exception while reading page events " + format_exc())
await tab.close_websocket()
self.js_ctx_tab = None
await self.handle_crash()
pass
# while True:
# await sleep(5)
@@ -158,35 +232,41 @@ class PluginManager:
async def inject_javascript(self, tab: Tab, first: bool=False, request: Request|None=None):
logger.info("Loading Decky frontend!")
try:
if first:
if await tab.has_global_var("deckyHasLoaded", False):
await close_old_tabs()
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)
# if first:
if ON_LINUX and await tab.has_global_var("deckyHasLoaded", False):
await tab.close_websocket()
self.js_ctx_tab = None
await restart_webhelper()
await sleep(1) # To give CEF enough time to close down the websocket
return # We'll catch the next tab in the main loop
await tab.evaluate_js(create_inject_script("index.js" if self.webhelper_crash_count < 3 else "fallback.js"), False, False, False)
if self.webhelper_crash_count > 2:
self.reinject = False
await sleep(1)
await self.updater.do_shutdown()
except:
logger.info("Failed to inject JavaScript into tab\n" + format_exc())
pass
def run(self):
return run_app(self.web_app, host=get_server_host(), port=get_server_port(), loop=self.loop, access_log=None)
run_app(self.web_app, host=get_server_host(), port=get_server_port(), loop=self.loop, access_log=None, handle_signals=True, shutdown_timeout=40)
def main():
setproctitle(f"Decky Loader {get_loader_version()} ({getproctitle()})")
setthreadtitle("Decky Loader")
if ON_WINDOWS:
# Fix windows/flask not recognising that .js means 'application/javascript'
import mimetypes
mimetypes.add_type('application/javascript', '.js')
# Required for multiprocessing support in frozen files
multiprocessing.freeze_support()
else:
if get_effective_user_id() != 0:
logger.warning(f"decky is running as an unprivileged user, this is not officially supported and may cause issues")
# Append the loader's plugin path to the recognized python paths
sys.path.append(path.join(path.dirname(__file__), "..", "plugin"))
if get_effective_user_id() != 0:
logger.warning(f"decky is running as an unprivileged user, this is not officially supported and may cause issues")
# Append the system and user python paths
sys.path.extend(get_system_pythonpaths())
logger.info(f"Starting Decky version {get_loader_version()}")
loop = new_event_loop()
set_event_loop(loop)
PluginManager(loop).run()
@@ -12,13 +12,15 @@ Some basic migration helpers are available: `migrate_any`, `migrate_settings`, `
A logging facility `logger` is available which writes to the recommended location.
"""
__version__ = '0.1.0'
__version__ = '1.0.0'
import os
import subprocess
import logging
import time
from typing import Any
"""
Constants
"""
@@ -204,6 +206,20 @@ logging.basicConfig(filename=DECKY_PLUGIN_LOG,
format='[%(asctime)s][%(levelname)s]: %(message)s',
force=True)
logger: logging.Logger = logging.getLogger()
# Also log to stdout
logger.addHandler(logging.StreamHandler())
"""The main plugin logger writing to `DECKY_PLUGIN_LOG`."""
logger.setLevel(logging.INFO)
"""
Event handling
"""
# This is overriden with an actual implementation before being passed to any plugins
# in ../sandboxed_plugin.py 's initialize function
async def emit(event: str, *args: Any) -> None:
"""
Triggers all event listeners in the frontend waiting for `event`, passing the remaining `*args` as the arguments to each listener function.
(Event listeners are set up in the frontend via the `addEventListener` function from `@decky/api`)
"""
pass
@@ -12,10 +12,12 @@ Some basic migration helpers are available: `migrate_any`, `migrate_settings`, `
A logging facility `logger` is available which writes to the recommended location.
"""
__version__ = '0.1.0'
__version__ = '1.0.0'
import logging
from typing import Any
"""
Constants
"""
@@ -171,3 +173,13 @@ Logging
logger: logging.Logger
"""The main plugin logger writing to `DECKY_PLUGIN_LOG`."""
"""
Event handling
"""
async def emit(event: str, *args: Any) -> None:
"""
Triggers all event listeners in the frontend waiting for `event`, passing the remaining `*args` as the arguments to each listener function.
(Event listeners are set up in the frontend via the `addEventListener` function from `@decky/api`)
"""
+36
View File
@@ -0,0 +1,36 @@
from typing import Any, TypedDict
from enum import IntEnum
from uuid import uuid4
from asyncio import Event
class SocketMessageType(IntEnum):
CALL = 0
RESPONSE = 1
EVENT = 2
class SocketResponseDict(TypedDict):
type: SocketMessageType
id: str
success: bool
res: Any
class MethodCallResponse:
def __init__(self, success: bool, result: Any) -> None:
self.success = success
self.result = result
class MethodCallRequest:
def __init__(self) -> None:
self.id = str(uuid4())
self.event = Event()
self.response: MethodCallResponse
def set_result(self, dc: SocketResponseDict):
self.response = MethodCallResponse(dc["success"], dc["res"])
self.event.set()
async def wait_for_result(self):
await self.event.wait()
if not self.response.success:
raise Exception(self.response.result)
return self.response.result
+162
View File
@@ -0,0 +1,162 @@
from asyncio import CancelledError, Task, create_task, sleep, wait
from json import dumps, load, loads
from logging import getLogger
from os import path
from multiprocessing import Process
from time import time
from traceback import format_exc
from .sandboxed_plugin import SandboxedPlugin
from .messages import MethodCallRequest, SocketMessageType
from ..enums import PluginLoadType
from ..localplatform.localsocket import LocalSocket
from ..helpers import get_homebrew_path, mkdir_as_user
from typing import Any, Callable, Coroutine, Dict, List
EmittedEventCallbackType = Callable[[str, Any], Coroutine[Any, Any, Any]]
class PluginWrapper:
def __init__(self, file: str, plugin_directory: str, plugin_path: str, emit_callback: EmittedEventCallbackType) -> None:
self.file = file
self.plugin_path = plugin_path
self.plugin_directory = plugin_directory
self.version = None
self.load_type = PluginLoadType.LEGACY_EVAL_IIFE.value
json = load(open(path.join(plugin_path, plugin_directory, "plugin.json"), "r", encoding="utf-8"))
if path.isfile(path.join(plugin_path, plugin_directory, "package.json")):
package_json = load(open(path.join(plugin_path, plugin_directory, "package.json"), "r", encoding="utf-8"))
self.version = package_json["version"]
if ("type" in package_json and package_json["type"] == "module"):
self.load_type = PluginLoadType.ESMODULE_V1.value
self.name = json["name"]
self.author = json["author"]
self.flags = json["flags"]
self.api_version = json["api_version"] if "api_version" in json else 0
self.passive = not path.isfile(self.file)
self.log = getLogger("plugin")
self.sandboxed_plugin = SandboxedPlugin(self.name, self.passive, self.flags, self.file, self.plugin_directory, self.plugin_path, self.version, self.author, self.api_version)
self.proc: Process | None = None
self._socket = LocalSocket()
self._listener_task: Task[Any]
self._method_call_requests: Dict[str, MethodCallRequest] = {}
self.emitted_event_callback: EmittedEventCallbackType = emit_callback
# TODO enable this after websocket release
self.legacy_method_warning = False
home = get_homebrew_path()
mkdir_as_user(path.join(home, "settings", self.plugin_directory))
# TODO maybe dont chown this?
mkdir_as_user(path.join(home, "data"))
mkdir_as_user(path.join(home, "data", self.plugin_directory))
# TODO maybe dont chown this?
mkdir_as_user(path.join(home, "logs"))
mkdir_as_user(path.join(home, "logs", self.plugin_directory))
def __str__(self) -> str:
return self.name
async def _response_listener(self):
while self._socket.active:
try:
line = await self._socket.read_single_line()
if line != None:
res = loads(line)
if res["type"] == SocketMessageType.EVENT.value:
create_task(self.emitted_event_callback(res["event"], res["args"]))
elif res["type"] == SocketMessageType.RESPONSE.value:
self._method_call_requests.pop(res["id"]).set_result(res)
except CancelledError:
self.log.info(f"Stopping response listener for {self.name}")
await self._socket.close_socket_connection()
raise
except:
pass
async def execute_legacy_method(self, method_name: str, kwargs: Dict[Any, Any]):
if not self.legacy_method_warning:
self.legacy_method_warning = True
self.log.warning(f"Plugin {self.name} is using legacy method calls. This will be removed in a future release.")
if self.passive:
raise RuntimeError("This plugin is passive (aka does not implement main.py)")
request = MethodCallRequest()
await self._socket.get_socket_connection()
await self._socket.write_single_line(dumps({ "type": SocketMessageType.CALL, "method": method_name, "args": kwargs, "id": request.id, "legacy": True }, ensure_ascii=False))
self._method_call_requests[request.id] = request
return await request.wait_for_result()
async def execute_method(self, method_name: str, *args: List[Any]):
if self.passive:
raise RuntimeError("This plugin is passive (aka does not implement main.py)")
request = MethodCallRequest()
await self._socket.get_socket_connection()
await self._socket.write_single_line(dumps({ "type": SocketMessageType.CALL, "method": method_name, "args": args, "id": request.id }, ensure_ascii=False))
self._method_call_requests[request.id] = request
return await request.wait_for_result()
def start(self):
if self.passive:
return self
self.proc = Process(target=self.sandboxed_plugin.initialize, args=[self._socket])
self.proc.start()
self._listener_task = create_task(self._response_listener())
return self
async def stop(self, uninstall: bool = False):
try:
start_time = time()
if self.passive:
return
self.log.info(f"Shutting down {self.name}")
pending: set[Task[None]] | None = None;
if uninstall:
_, pending = await wait([
create_task(self._socket.write_single_line(dumps({ "uninstall": uninstall }, ensure_ascii=False)))
], timeout=1)
self.terminate() # the plugin process will handle SIGTERM and shut down cleanly without a socket message
if hasattr(self, "_listener_task"):
self._listener_task.cancel()
await self.kill_if_still_running()
if pending:
for pending_task in pending:
pending_task.cancel()
self.log.info(f"Plugin {self.name} has been stopped in {time() - start_time:.1f}s")
except Exception as e:
self.log.error(f"Error during shutdown for plugin {self.name}: {str(e)}\n{format_exc()}")
async def kill_if_still_running(self):
start_time = time()
while self.proc and self.proc.is_alive():
elapsed_time = time() - start_time
if elapsed_time >= 5:
self.log.warning(f"Plugin {self.name} still alive 5 seconds after stop request! Sending SIGKILL!")
self.terminate(True)
await sleep(0.1)
def terminate(self, kill: bool = False):
if self.proc and self.proc.is_alive():
if kill:
self.proc.kill()
else:
self.proc.terminate()
@@ -0,0 +1,207 @@
import sys
from os import path, environ
from importlib.util import module_from_spec, spec_from_file_location
from json import dumps, loads
from logging import getLogger
from traceback import format_exc
from asyncio import (ensure_future, get_event_loop, new_event_loop,
set_event_loop)
from signal import SIGINT, SIGTERM
from setproctitle import setproctitle, setthreadtitle
from .messages import SocketResponseDict, SocketMessageType
from ..localplatform.localsocket import LocalSocket
from ..localplatform.localplatform import setgid, setuid, get_username, get_home_path, ON_LINUX
from ..enums import UserType
from .. import helpers, settings, injector # pyright: ignore [reportUnusedImport]
from typing import List, TypeVar, Any
DataType = TypeVar("DataType")
class SandboxedPlugin:
def __init__(self,
name: str,
passive: bool,
flags: List[str],
file: str,
plugin_directory: str,
plugin_path: str,
version: str|None,
author: str,
api_version: int) -> None:
self.name = name
self.passive = passive
self.flags = flags
self.file = file
self.plugin_path = plugin_path
self.plugin_directory = plugin_directory
self.version = version
self.author = author
self.api_version = api_version
self.shutdown_running = False
self.uninstalling = False
self.log = getLogger("sandboxed_plugin")
def initialize(self, socket: LocalSocket):
self._socket = socket
try:
setproctitle(f"{self.name} ({self.file})")
setthreadtitle(self.name)
loop = new_event_loop()
set_event_loop(loop)
# When running Decky manually in a terminal, ctrl-c will trigger this, so we have to handle it properly
if ON_LINUX:
loop.add_signal_handler(SIGINT, lambda: ensure_future(self.shutdown()))
loop.add_signal_handler(SIGTERM, lambda: ensure_future(self.shutdown()))
if self.passive:
return
setgid(UserType.ROOT if "root" in self.flags else UserType.HOST_USER)
setuid(UserType.ROOT if "root" in self.flags else UserType.HOST_USER)
# export a bunch of environment variables to help plugin developers
environ["HOME"] = get_home_path(UserType.ROOT if "root" in self.flags else UserType.HOST_USER)
environ["USER"] = "root" if "root" in self.flags else get_username()
environ["DECKY_VERSION"] = helpers.get_loader_version()
environ["DECKY_USER"] = get_username()
environ["DECKY_USER_HOME"] = helpers.get_home_path()
environ["DECKY_HOME"] = helpers.get_homebrew_path()
environ["DECKY_PLUGIN_SETTINGS_DIR"] = path.join(environ["DECKY_HOME"], "settings", self.plugin_directory)
environ["DECKY_PLUGIN_RUNTIME_DIR"] = path.join(environ["DECKY_HOME"], "data", self.plugin_directory)
environ["DECKY_PLUGIN_LOG_DIR"] = path.join(environ["DECKY_HOME"], "logs", self.plugin_directory)
environ["DECKY_PLUGIN_DIR"] = path.join(self.plugin_path, self.plugin_directory)
environ["DECKY_PLUGIN_NAME"] = self.name
if self.version:
environ["DECKY_PLUGIN_VERSION"] = self.version
environ["DECKY_PLUGIN_AUTHOR"] = self.author
# append the plugin's `py_modules` to the recognized python paths
sys.path.append(path.join(environ["DECKY_PLUGIN_DIR"], "py_modules"))
#TODO: FIX IN A LESS CURSED WAY
keys = [key for key in sys.modules if key.startswith("decky_loader.")]
for key in keys:
sys.modules[key.replace("decky_loader.", "")] = sys.modules[key]
from .imports import decky
async def emit(event: str, *args: Any) -> None:
await self._socket.write_single_line_server(dumps({
"type": SocketMessageType.EVENT,
"event": event,
"args": args
}))
# copy the docstring over so we don't have to duplicate it
emit.__doc__ = decky.emit.__doc__
decky.emit = emit
sys.modules["decky"] = decky
# provided for compatibility
sys.modules["decky_plugin"] = decky
spec = spec_from_file_location("_", self.file)
assert spec is not None
module = module_from_spec(spec)
assert spec.loader is not None
spec.loader.exec_module(module)
# TODO fix self weirdness once plugin.json versioning is done. need this before WS release!
if self.api_version > 0:
self.Plugin = module.Plugin()
else:
self.Plugin = module.Plugin
if hasattr(self.Plugin, "_migration"):
if self.api_version > 0:
get_event_loop().run_until_complete(self.Plugin._migration())
else:
get_event_loop().run_until_complete(self.Plugin._migration(self.Plugin))
if hasattr(self.Plugin, "_main"):
if self.api_version > 0:
get_event_loop().create_task(self.Plugin._main())
else:
get_event_loop().create_task(self.Plugin._main(self.Plugin))
get_event_loop().create_task(socket.setup_server(self.on_new_message))
except:
self.log.error("Failed to start " + self.name + "!\n" + format_exc())
sys.exit(0)
try:
get_event_loop().run_forever()
except SystemExit:
pass
except:
self.log.error("Loop exited for " + self.name + "!\n" + format_exc())
finally:
get_event_loop().close()
async def _unload(self):
try:
self.log.info("Attempting to unload with plugin " + self.name + "'s \"_unload\" function.\n")
if hasattr(self.Plugin, "_unload"):
if self.api_version > 0:
await self.Plugin._unload()
else:
await self.Plugin._unload(self.Plugin)
self.log.info("Unloaded " + self.name + "\n")
else:
self.log.info("Could not find \"_unload\" in " + self.name + "'s main.py" + "\n")
except:
self.log.error("Failed to unload " + self.name + "!\n" + format_exc())
pass
async def _uninstall(self):
try:
self.log.info("Attempting to uninstall with plugin " + self.name + "'s \"_uninstall\" function.\n")
if hasattr(self.Plugin, "_uninstall"):
if self.api_version > 0:
await self.Plugin._uninstall()
else:
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())
pass
async def shutdown(self):
if not self.shutdown_running:
self.shutdown_running = True
self.log.info(f"Calling Loader unload function for {self.name}.")
await self._unload()
if self.uninstalling:
self.log.info("Calling Loader uninstall function.")
await self._uninstall()
self.log.debug("Stopping event loop")
loop = get_event_loop()
loop.call_soon_threadsafe(loop.stop)
sys.exit(0)
async def on_new_message(self, message : str) -> str|None:
data = loads(message)
if "uninstall" in data:
self.uninstalling = data.get("uninstall")
return
d: SocketResponseDict = {"type": SocketMessageType.RESPONSE, "res": None, "success": True, "id": data["id"]}
try:
if data.get("legacy"):
if self.api_version > 0:
raise Exception("Legacy methods may not be used on api_version > 0")
# Legacy kwargs
d["res"] = await getattr(self.Plugin, data["method"])(self.Plugin, **data["args"])
else:
if self.api_version < 1 :
raise Exception("api_version 1 or newer is required to call methods with index-based arguments")
# New args
d["res"] = await getattr(self.Plugin, data["method"])(*data["args"])
except Exception as e:
d["res"] = str(e)
d["success"] = False
finally:
return dumps(d, ensure_ascii=False)
@@ -1,8 +1,8 @@
from json import dump, load
from os import mkdir, path, listdir, rename
from typing import Any, Dict
from .localplatform import chown, folder_owner, get_chown_plugin_path
from .customtypes import UserType
from .localplatform.localplatform import chown, folder_owner, get_chown_plugin_path
from .enums import UserType
from .helpers import get_homebrew_path
@@ -1,43 +1,45 @@
from __future__ import annotations
import os
import shutil
from asyncio import sleep
from json.decoder import JSONDecodeError
from logging import getLogger
import os
from os import getcwd, path, remove
from typing import TYPE_CHECKING, List, TypedDict
if TYPE_CHECKING:
from .main import PluginManager
from .localplatform import chmod, service_restart, ON_LINUX, get_keep_systemd_service, get_selinux
from .localplatform.localplatform import chmod, service_restart, service_stop, ON_LINUX, ON_WINDOWS, get_keep_systemd_service, get_selinux
import shutil
from typing import List, TYPE_CHECKING, TypedDict
import zipfile
from aiohttp import ClientSession, web
from aiohttp import ClientSession
from . import helpers
from .injector import get_gamepadui_tab
from .settings import SettingsManager
if TYPE_CHECKING:
from .main import PluginManager
logger = getLogger("Updater")
class RemoteVerAsset(TypedDict):
name: str
size: int
browser_download_url: str
class RemoteVer(TypedDict):
tag_name: str
prerelease: bool
assets: List[RemoteVerAsset]
class TestingVersion(TypedDict):
id: int
name: str
link: str
head_sha: str
class Updater:
def __init__(self, context: PluginManager) -> None:
self.context = context
self.settings = self.context.settings
# Exposes updater methods to frontend
self.updater_methods = {
"get_branch": self._get_branch,
"get_version": self.get_version,
"do_update": self.do_update,
"do_restart": self.do_restart,
"check_for_updates": self.check_for_updates
}
self.remoteVer: RemoteVer | None = None
self.allRemoteVers: List[RemoteVer] = []
self.localVer = helpers.get_loader_version()
@@ -49,27 +51,15 @@ class Updater:
logger.error("Current branch could not be determined, defaulting to \"Stable\"")
if context:
context.web_app.add_routes([
web.post("/updater/{method_name}", self._handle_server_method_call)
])
context.ws.add_route("updater/get_version_info", self.get_version_info);
context.ws.add_route("updater/check_for_updates", self.check_for_updates);
context.ws.add_route("updater/do_restart", self.do_restart);
context.ws.add_route("updater/do_shutdown", self.do_shutdown);
context.ws.add_route("updater/do_update", self.do_update);
context.ws.add_route("updater/get_testing_versions", self.get_testing_versions);
context.ws.add_route("updater/download_testing_version", self.download_testing_version);
context.loop.create_task(self.version_reloader())
async def _handle_server_method_call(self, request: web.Request):
method_name = request.match_info["method_name"]
try:
args = await request.json()
except JSONDecodeError:
args = {}
res = {}
try:
r = await self.updater_methods[method_name](**args) # type: ignore
res["result"] = r
res["success"] = True
except Exception as e:
res["result"] = str(e)
res["success"] = False
return web.json_response(res)
def get_branch(self, manager: SettingsManager):
ver = manager.getSetting("branch", -1)
logger.debug("current branch: %i" % ver)
@@ -102,19 +92,19 @@ class Updater:
url = "https://raw.githubusercontent.com/SteamDeckHomebrew/decky-loader/main/dist/plugin_loader-prerelease.service"
return str(url)
async def get_version(self):
async def get_version_info(self):
return {
"current": self.localVer,
"remote": self.remoteVer,
"all": self.allRemoteVers,
"updatable": self.localVer != "unknown"
"updatable": self.localVer != "unknown" and self.localVer != "dev"
}
async def check_for_updates(self):
logger.debug("checking for updates")
selectedBranch = self.get_branch(self.context.settings)
async with ClientSession() as web:
async with web.request("GET", "https://api.github.com/repos/SteamDeckHomebrew/decky-loader/releases", ssl=helpers.get_ssl_context()) as res:
async with web.request("GET", "https://api.github.com/repos/SteamDeckHomebrew/decky-loader/releases", headers={'X-GitHub-Api-Version': '2022-11-28'}, ssl=helpers.get_ssl_context()) as res:
remoteVersions: List[RemoteVer] = await res.json()
if selectedBranch == 0:
logger.debug("release type: release")
@@ -137,9 +127,8 @@ class Updater:
logger.error("release type: NOT FOUND")
raise ValueError("no valid branch found")
logger.info("Updated remote version information")
tab = await get_gamepadui_tab()
await tab.evaluate_js(f"window.DeckyPluginLoader.notifyUpdates()", False, True, False)
return await self.get_version()
await self.context.ws.emit("loader/notify_updates")
return await self.get_version_info()
async def version_reloader(self):
await sleep(30)
@@ -150,6 +139,56 @@ class Updater:
pass
await sleep(60 * 60 * 6) # 6 hours
async def download_decky_binary(self, download_url: str, version: str, is_zip: bool = False, size_in_bytes: int | None = None):
download_filename = "PluginLoader" if ON_LINUX else "PluginLoader.exe"
download_temp_filename = download_filename + ".new"
tab = await get_gamepadui_tab()
await tab.open_websocket()
if size_in_bytes == None:
size_in_bytes = 26214400 # 25MiB, a reasonable overestimate (19.6MiB as of 2024/02/25)
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', size_in_bytes))
if total == 0: total = 1
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(self.context.ws.emit("updater/update_download_percentage", new_progress))
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 self.context.ws.emit("updater/finish_download")
await tab.close_websocket()
await self.do_restart()
async def do_update(self):
logger.debug("Starting update.")
try:
@@ -160,12 +199,13 @@ class Updater:
version = self.remoteVer["tag_name"]
download_url = None
size_in_bytes = None
download_filename = "PluginLoader" if ON_LINUX else "PluginLoader.exe"
download_temp_filename = download_filename + ".new"
for x in self.remoteVer["assets"]:
if x["name"] == download_filename:
download_url = x["browser_download_url"]
size_in_bytes = x["size"]
break
if download_url == None:
@@ -174,8 +214,6 @@ class Updater:
service_url = self.get_service_url()
logger.debug("Retrieved service URL")
tab = await get_gamepadui_tab()
await tab.open_websocket()
async with ClientSession() as web:
if ON_LINUX and not get_keep_systemd_service():
logger.debug("Downloading systemd service")
@@ -203,36 +241,56 @@ class Updater:
os.mkdir(path.join(getcwd(), ".systemd"))
shutil.move(service_file_path, path.join(getcwd(), ".systemd")+"/plugin_loader.service")
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)
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()
await self.download_decky_binary(download_url, version, size_in_bytes=size_in_bytes)
async def do_restart(self):
await service_restart("plugin_loader")
await service_restart("plugin_loader", block=False)
async def do_shutdown(self):
await service_stop("plugin_loader")
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={'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!
artifact = jresp['artifacts'][0]
down_link = f"https://nightly.link/SteamDeckHomebrew/decky-loader/actions/artifacts/{artifact['id']}.zip"
#Then fetch it and restart itself
await self.download_decky_binary(down_link, f'PR-{pr_id}', is_zip=True, size_in_bytes=artifact.get('size_in_bytes',None))
else:
logger.error("workflow run not found", str(works))
raise Exception("Workflow run not found.")
@@ -1,14 +1,16 @@
from __future__ import annotations
from os import stat_result
import uuid
from urllib.parse import unquote
from json.decoder import JSONDecodeError
from os.path import splitext
import re
from traceback import format_exc
from stat import FILE_ATTRIBUTE_HIDDEN # type: ignore
from stat import FILE_ATTRIBUTE_HIDDEN # pyright: ignore [reportAttributeAccessIssue, reportUnknownVariableType]
from asyncio import StreamReader, StreamWriter, start_server, gather, open_connection
from aiohttp import ClientSession, web
from aiohttp import ClientSession, hdrs
from aiohttp.web import Request, StreamResponse, Response, json_response, post
from typing import TYPE_CHECKING, Callable, Coroutine, Dict, Any, List, TypedDict
from logging import getLogger
@@ -18,36 +20,31 @@ from .browser import PluginInstallRequest, PluginInstallType
if TYPE_CHECKING:
from .main import PluginManager
from .injector import inject_to_tab, get_gamepadui_tab, close_old_tabs, get_tab
from .localplatform import ON_WINDOWS
from . import helpers
from .localplatform import service_stop, service_start, get_home_path, get_username
from .localplatform.localplatform import ON_WINDOWS, service_stop, service_start, get_home_path, get_username, get_use_cef_close_workaround, close_cef_socket, restart_webhelper
class FilePickerObj(TypedDict):
file: Path
filest: stat_result
is_dir: bool
decky_header_regex = re.compile("X-Decky-(.*)")
extra_header_regex = re.compile("X-Decky-Header-(.*)")
excluded_default_headers = ["Host", "Origin", "Sec-Fetch-Site", "Sec-Fetch-Mode", "Sec-Fetch-Dest"]
class Utilities:
def __init__(self, context: PluginManager) -> None:
self.context = context
self.util_methods: Dict[str, Callable[..., Coroutine[Any, Any, Any]]] = {
self.legacy_util_methods: Dict[str, Callable[..., Coroutine[Any, Any, Any]]] = {
"ping": self.ping,
"http_request": self.http_request,
"install_plugin": self.install_plugin,
"install_plugins": self.install_plugins,
"cancel_plugin_install": self.cancel_plugin_install,
"confirm_plugin_install": self.confirm_plugin_install,
"uninstall_plugin": self.uninstall_plugin,
"http_request": self.http_request_legacy,
"execute_in_tab": self.execute_in_tab,
"inject_css_into_tab": self.inject_css_into_tab,
"remove_css_from_tab": self.remove_css_from_tab,
"allow_remote_debugging": self.allow_remote_debugging,
"disallow_remote_debugging": self.disallow_remote_debugging,
"set_setting": self.set_setting,
"get_setting": self.get_setting,
"filepicker_ls": self.filepicker_ls,
"disable_rdt": self.disable_rdt,
"enable_rdt": self.enable_rdt,
"get_tab_id": self.get_tab_id,
"get_user_info": self.get_user_info,
}
@@ -59,11 +56,40 @@ class Utilities:
self.rdt_proxy_task = None
if context:
context.ws.add_route("utilities/ping", self.ping)
context.ws.add_route("utilities/settings/get", self.get_setting)
context.ws.add_route("utilities/settings/set", self.set_setting)
context.ws.add_route("utilities/install_plugin", self.install_plugin)
context.ws.add_route("utilities/install_plugins", self.install_plugins)
context.ws.add_route("utilities/cancel_plugin_install", self.cancel_plugin_install)
context.ws.add_route("utilities/confirm_plugin_install", self.confirm_plugin_install)
context.ws.add_route("utilities/uninstall_plugin", self.uninstall_plugin)
context.ws.add_route("utilities/execute_in_tab", self.execute_in_tab)
context.ws.add_route("utilities/inject_css_into_tab", self.inject_css_into_tab)
context.ws.add_route("utilities/remove_css_from_tab", self.remove_css_from_tab)
context.ws.add_route("utilities/allow_remote_debugging", self.allow_remote_debugging)
context.ws.add_route("utilities/disallow_remote_debugging", self.disallow_remote_debugging)
context.ws.add_route("utilities/start_ssh", self.allow_remote_debugging)
context.ws.add_route("utilities/stop_ssh", self.allow_remote_debugging)
context.ws.add_route("utilities/filepicker_ls", self.filepicker_ls)
context.ws.add_route("utilities/disable_rdt", self.disable_rdt)
context.ws.add_route("utilities/enable_rdt", self.enable_rdt)
context.ws.add_route("utilities/get_tab_id", self.get_tab_id)
context.ws.add_route("utilities/get_user_info", self.get_user_info)
context.ws.add_route("utilities/http_request", self.http_request_legacy)
context.ws.add_route("utilities/restart_webhelper", self.restart_webhelper)
context.ws.add_route("utilities/close_cef_socket", self.close_cef_socket)
context.ws.add_route("utilities/_call_legacy_utility", self._call_legacy_utility)
context.web_app.add_routes([
web.post("/methods/{method_name}", self._handle_server_method_call)
post("/methods/{method_name}", self._handle_legacy_server_method_call)
])
async def _handle_server_method_call(self, request: web.Request):
for method in ('GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'HEAD'):
context.web_app.router.add_route(method, "/fetch", self.http_request)
async def _handle_legacy_server_method_call(self, request: Request) -> Response:
method_name = request.match_info["method_name"]
try:
args = await request.json()
@@ -71,13 +97,25 @@ class Utilities:
args = {}
res = {}
try:
r = await self.util_methods[method_name](**args)
r = await self.legacy_util_methods[method_name](**args)
res["result"] = r
res["success"] = True
except Exception as e:
res["result"] = str(e)
res["success"] = False
return web.json_response(res)
return json_response(res)
async def _call_legacy_utility(self, method_name: str, kwargs: Dict[Any, Any]) -> Any:
self.logger.debug(f"Calling utility {method_name} with legacy kwargs");
res: Dict[Any, Any] = {}
try:
r = await self.legacy_util_methods[method_name](**kwargs)
res["result"] = r
res["success"] = True
except Exception as e:
res["result"] = str(e)
res["success"] = False
return res
async def install_plugin(self, artifact: str="", name: str="No name", version: str="dev", hash: str="", install_type: PluginInstallType=PluginInstallType.INSTALL):
return await self.context.plugin_browser.request_plugin_install(
@@ -102,9 +140,81 @@ class Utilities:
async def uninstall_plugin(self, name: str):
return await self.context.plugin_browser.uninstall_plugin(name)
async def http_request(self, method: str="", url: str="", **kwargs: Any):
# Loosely based on https://gist.github.com/mosquito/4dbfacd51e751827cda7ec9761273e95#file-proxy-py
async def http_request(self, req: Request) -> StreamResponse:
if req.query['auth'] != helpers.get_csrf_token():
return Response(text='Forbidden', status=403)
url = unquote(req.query['fetch_url'])
self.logger.info(f"Preparing {req.method} request to {url}")
headers = dict(req.headers)
headers["User-Agent"] = helpers.user_agent
for excluded_header in excluded_default_headers:
if excluded_header in headers:
self.logger.debug(f"Excluding default header {excluded_header}: {headers[excluded_header]}")
del headers[excluded_header]
if "X-Decky-Fetch-Excluded-Headers" in req.headers:
for excluded_header in req.headers["X-Decky-Fetch-Excluded-Headers"].split(", "):
if excluded_header in headers:
self.logger.debug(f"Excluding header {excluded_header}: {headers[excluded_header]}")
del headers[excluded_header]
for header in req.headers:
match = extra_header_regex.search(header)
if match:
header_name = match.group(1)
header_value = req.headers[header]
self.logger.debug(f"Adding extra header {header_name}: {header_value}")
headers[header_name] = header_value
for header in list(headers.keys()):
match = decky_header_regex.search(header)
if match:
self.logger.debug(f"Removing decky header {header} from request")
del headers[header]
self.logger.debug(f"Final request headers: {headers}")
body = await req.read() # TODO can this also be streamed?
# We disable auto-decompress so that the body is completely forwarded to the
# JS engine for it to do the decompression. Otherwise we need need to clear
# the Content-Encoding header in the response headers, however that would
# defeat the point of this proxy.
async with ClientSession(auto_decompress=False) as web:
async with web.request(req.method, url, headers=headers, data=body, ssl=helpers.get_ssl_context()) as web_res:
# Whenever the aiohttp_cors is used, it expects a near complete control over whatever headers are needed
# for `aiohttp_cors.ResourceOptions`. As a server, if you delegate CORS handling to aiohttp_cors,
# the headers below must NOT be set. Otherwise they would be overwritten by aiohttp_cors and there would be
# logic bugs, so it was probably a smart choice to assert if the headers are present.
#
# However, this request handler method does not act like our own local server, it always acts like a proxy
# where we do not have control over the response headers. For responses that do not allow CORS, we add the support
# via aiohttp_cors. For responses that allow CORS, we have to remove the conflicting headers to allow
# aiohttp_cors handle it for us as if there was no CORS support.
aiohttp_cors_compatible_headers = web_res.headers.copy()
aiohttp_cors_compatible_headers.popall(hdrs.ACCESS_CONTROL_ALLOW_ORIGIN, default=None)
aiohttp_cors_compatible_headers.popall(hdrs.ACCESS_CONTROL_ALLOW_CREDENTIALS, default=None)
aiohttp_cors_compatible_headers.popall(hdrs.ACCESS_CONTROL_EXPOSE_HEADERS, default=None)
res = StreamResponse(headers=aiohttp_cors_compatible_headers, status=web_res.status)
if web_res.headers.get('Transfer-Encoding', '').lower() == 'chunked':
res.enable_chunked_encoding()
await res.prepare(req)
self.logger.debug(f"Starting stream for {url}")
async for data in web_res.content.iter_any():
await res.write(data)
self.logger.debug(f"Finished stream for {url}")
return res
async def http_request_legacy(self, method: str, url: str, extra_opts: Any = {}, timeout: int | None = None):
async with ClientSession() as web:
res = await web.request(method, url, ssl=helpers.get_ssl_context(), **kwargs)
res = await web.request(method, url, ssl=helpers.get_ssl_context(), timeout=timeout, **extra_opts)
text = await res.text()
return {
"status": res.status,
@@ -135,62 +245,40 @@ class Utilities:
"result": e
}
async def inject_css_into_tab(self, tab: str, style: str):
try:
css_id = str(uuid.uuid4())
async def inject_css_into_tab(self, tab: str, style: str) -> str:
css_id = str(uuid.uuid4())
result = await inject_to_tab(tab,
f"""
(function() {{
const style = document.createElement('style');
style.id = "{css_id}";
document.head.append(style);
style.textContent = `{style}`;
}})()
""", False)
result = await inject_to_tab(tab,
f"""
(function() {{
const style = document.createElement('style');
style.id = "{css_id}";
document.head.append(style);
style.textContent = `{style}`;
}})()
""", False)
assert result is not None # TODO remove this once it has proper typings
if "exceptionDetails" in result["result"]:
raise result["result"]["exceptionDetails"]
if result and "exceptionDetails" in result["result"]:
return {
"success": False,
"result": result["result"]
}
return {
"success": True,
"result": css_id
}
except Exception as e:
return {
"success": False,
"result": e
}
return css_id
async def remove_css_from_tab(self, tab: str, css_id: str):
try:
result = await inject_to_tab(tab,
f"""
(function() {{
let style = document.getElementById("{css_id}");
result = await inject_to_tab(tab,
f"""
(function() {{
let style = document.getElementById("{css_id}");
if (style.nodeName.toLowerCase() == 'style')
style.parentNode.removeChild(style);
}})()
""", False)
if (style.nodeName.toLowerCase() == 'style')
style.parentNode.removeChild(style);
}})()
""", False)
assert result
if "exceptionDetails" in result["result"]:
raise result["result"]["exceptionDetails"]
if result and "exceptionDetails" in result["result"]:
return {
"success": False,
"result": result
}
return {
"success": True
}
except Exception as e:
return {
"success": False,
"result": e
}
return
async def get_setting(self, key: str, default: Any):
return self.context.settings.getSetting(key, default)
@@ -206,13 +294,28 @@ class Utilities:
await service_stop(helpers.REMOTE_DEBUGGER_UNIT)
return True
async def start_ssh(self):
await service_start(helpers.SSHD_UNIT)
return True
async def stop_ssh(self):
await service_stop(helpers.SSHD_UNIT)
return True
async def close_cef_socket(self):
if get_use_cef_close_workaround():
await close_cef_socket()
async def restart_webhelper(self):
await restart_webhelper()
async def filepicker_ls(self,
path : str | None = None,
path: str | None = None,
include_files: bool = True,
include_folders: bool = True,
include_ext: list[str] = [],
include_ext: list[str] | None = None,
include_hidden: bool = False,
order_by: str = "name_asc",
order_by: str = "name_desc",
filter_for: str | None = None,
page: int = 1,
max: int = 1000):
@@ -237,8 +340,8 @@ class Utilities:
folders.append({"file": file, "filest": filest, "is_dir": True})
elif include_files:
# Handle requested extensions if present
if len(include_ext) == 0 or 'all_files' in include_ext \
or splitext(file.name)[1].lstrip('.') in include_ext:
if include_ext == None or len(include_ext) == 0 or 'all_files' 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:
files.append({"file": file, "filest": filest, "is_dir": False})
# Filter logic
+139
View File
@@ -0,0 +1,139 @@
from logging import getLogger
from asyncio import AbstractEventLoop
from aiohttp import WSCloseCode, WSMsgType, WSMessage
from aiohttp.web import Application, WebSocketResponse, Request, Response, get
from enum import IntEnum
from typing import Callable, Coroutine, Dict, Any, cast, TypeVar
from traceback import format_exc
from .helpers import get_csrf_token
class MessageType(IntEnum):
ERROR = -1
# Call-reply, Frontend -> Backend -> Frontend
CALL = 0
REPLY = 1
# Pub/Sub, Backend -> Frontend
EVENT = 3
# WSMessage with slightly better typings
class WSMessageExtra(WSMessage):
# TODO message typings here too
data: Any # pyright: ignore [reportIncompatibleVariableOverride]
type: WSMsgType # pyright: ignore [reportIncompatibleVariableOverride]
# see wsrouter.ts for typings
DataType = TypeVar("DataType")
Route = Callable[..., Coroutine[Any, Any, Any]]
class WSRouter:
def __init__(self, loop: AbstractEventLoop, server_instance: Application) -> None:
self.loop = loop
self.ws: WebSocketResponse | None = None
self.instance_id = 0
self.routes: Dict[str, Route] = {}
# self.subscriptions: Dict[str, Callable[[Any]]] = {}
self.logger = getLogger("WSRouter")
server_instance.add_routes([
get("/ws", self.handle)
])
async def write(self, data: Dict[str, Any]):
if self.ws != None:
await self.ws.send_json(data)
else:
self.logger.warning("Dropping message as there is no connected socket: %s", data)
def add_route(self, name: str, route: Route):
self.routes[name] = route
def remove_route(self, name: str):
del self.routes[name]
async def _call_route(self, route: str, args: ..., call_id: int):
instance_id = self.instance_id
error = None
try:
res = await self.routes[route](*args)
except Exception as err:
error = {"name":err.__class__.__name__, "message":str(err), "traceback":format_exc()}
res = None
if instance_id != self.instance_id:
try:
self.logger.warning("Ignoring %s reply from stale instance %d with args %s and response %s", route, instance_id, args, res)
except:
self.logger.warning("Ignoring %s reply from stale instance %d (failed to log event data)", route, instance_id)
finally:
return
if error:
await self.write({"type": MessageType.ERROR.value, "id": call_id, "error": error})
else:
await self.write({"type": MessageType.REPLY.value, "id": call_id, "result": res})
async def handle(self, request: Request):
# Auth is a query param as JS WebSocket doesn't support headers
if request.rel_url.query["auth"] != get_csrf_token():
return Response(text='Forbidden', status=403)
self.logger.debug('Websocket connection starting')
ws = WebSocketResponse()
await ws.prepare(request)
self.instance_id += 1
self.logger.debug('Websocket connection ready')
if self.ws != None:
try:
await self.ws.close()
except:
pass
self.ws = None
self.ws = ws
try:
async for msg in ws:
msg = cast(WSMessageExtra, msg)
if msg.type == WSMsgType.TEXT:
if msg.data == 'close':
# TODO DO NOT RELY ON THIS!
break
else:
data = msg.json()
match data["type"]:
case MessageType.CALL.value:
# do stuff with the message
if data["route"] in self.routes:
self.logger.debug(f'Started PY call {data["route"]} ID {data["id"]}')
self.loop.create_task(self._call_route(data["route"], data["args"], data["id"]))
else:
error = {"error":f'Route {data["route"]} does not exist.', "name": "RouteNotFoundError", "traceback": None}
self.loop.create_task(self.write({"type": MessageType.ERROR.value, "id": data["id"], "error": error}))
case _:
self.logger.error("Unknown message type", data)
finally:
try:
await ws.close()
self.ws = None
except:
pass
self.logger.debug('Websocket connection closed')
return ws
async def emit(self, event: str, *args: Any):
self.logger.debug(f'Firing frontend event {event} with args {args}')
await self.write({ "type": MessageType.EVENT.value, "event": event, "args": args })
async def disconnect(self):
if self.ws:
await self.ws.close(code=WSCloseCode.GOING_AWAY, message=b"Loader is shutting down")
-195
View File
@@ -1,195 +0,0 @@
{
"BranchSelect": {
"update_channel": {
"label": "Updatekanal",
"prerelease": "Vorabveröffentlichung",
"stable": "Standard",
"testing": "Test"
}
},
"Developer": {
"disabling": "Deaktiviere",
"enabling": "Aktiviere",
"5secreload": "Neu laden in 5 Sekunden"
},
"FilePickerIndex": {
"folder": {
"select": "Diesen Ordner verwenden"
}
},
"PluginCard": {
"plugin_install": "Installieren",
"plugin_no_desc": "Keine Beschreibung angegeben.",
"plugin_version_label": "Erweiterungs Version",
"plugin_full_access": "Diese Erweiterung hat uneingeschränkten Zugriff auf dein Steam Deck."
},
"PluginInstallModal": {
"install": {
"button_idle": "Installieren",
"button_processing": "Wird installiert",
"desc": "Bist du dir sicher, dass du {{artifact}} {{version}} installieren willst?",
"title": "Installiere {{artifact}}"
},
"reinstall": {
"button_idle": "Neu installieren",
"button_processing": "Wird neu installiert",
"desc": "Bist du dir sicher, dass du {{artifact}} {{version}} neu installieren willst?",
"title": "Neu installation {{artifact}}"
},
"update": {
"button_idle": "Aktualisieren",
"button_processing": "Wird aktualisiert",
"title": "Aktualisiere {{artifact}}",
"desc": "Bist du dir sicher, dass du {{artifact}} {{version}} aktualisieren willst?"
},
"no_hash": "Diese Erweiterung besitzt keine Prüfsumme, Installation auf eigene Gefahr."
},
"PluginListIndex": {
"no_plugin": "Keine Erweiterungen installiert!",
"plugin_actions": "Erweiterungs Aktionen",
"reinstall": "Neu installieren",
"reload": "Neu laden",
"uninstall": "Deinstallieren",
"update_to": "Aktualisieren zu {{name}}",
"update_all_one": "",
"update_all_other": ""
},
"PluginLoader": {
"decky_title": "Decky",
"decky_update_available": "Eine neue Version ({{tag_name}}) ist verfügbar!",
"error": "Fehler",
"plugin_load_error": {
"toast": "Fehler beim Laden von {{name}}",
"message": "Fehler beim Laden von {{name}}"
},
"plugin_uninstall": {
"button": "Deinstallieren",
"desc": "Bist du dir sicher, dass du {{name}} deinstallieren willst?",
"title": "Deinstalliere {{name}}"
},
"plugin_error_uninstall": "Das Laden von {{name}} hat einen Fehler verursacht. Dies bedeutet normalerweise, dass die Erweiterung ein Update für die neue Version von SteamUI benötigt. Prüfe in den Decky-Einstellungen im Bereich Erweiterungen, ob ein Update vorhanden ist.",
"plugin_update_one": "1 Erweiterung kann aktualisiert werden!",
"plugin_update_other": "{{count}} Erweiterungen können aktualisiert werden!"
},
"RemoteDebugging": {
"remote_cef": {
"label": "Remote CEF Debugging Zugriff",
"desc": "Erlaubt jedem aus dem Neztwerk unautorisierten Zugriff auf den CEF Debugger"
}
},
"SettingsDeveloperIndex": {
"header": "Sonstiges",
"react_devtools": {
"ip_label": "IP",
"label": "Aktiviere React DevTools",
"desc": "Erlaubt die Verbindung mit einem anderen Rechner, auf welchem React DevTools läuft. Eine Änderung startet Steam neu. Die IP Adresse muss vor Aktivierung ausgefüllt sein."
},
"third_party_plugins": {
"button_zip": "Durchsuchen",
"header": "Erweiterungen von Drittanbietern",
"label_desc": "URL",
"label_zip": "Installiere Erweiterung via ZIP Datei",
"button_install": "Installieren",
"label_url": "Installiere Erweiterung via URL"
},
"valve_internal": {
"desc2": "Fasse in diesem Menü nichts an, es sei denn, du weißt was du tust.",
"label": "Aktiviere Valve-internes Menü",
"desc1": "Aktiviert das Valve-interne Entwickler Menü."
}
},
"SettingsGeneralIndex": {
"about": {
"decky_version": "Decky Version",
"header": "Über"
},
"beta": {
"header": "Beta Teilnahme"
},
"developer_mode": {
"label": "Entwickleroptionen"
},
"other": {
"header": "Sonstiges"
},
"updates": {
"header": "Aktualisierungen"
}
},
"SettingsIndex": {
"developer_title": "Entwickler",
"general_title": "Allgemein",
"plugins_title": "Erweiterungen"
},
"Store": {
"store_contrib": {
"label": "Mitwirken",
"desc": "Wenn du Erweiterungen im Decky Store veröffentlichen willst, besuche die SteamDeckHomebrew/decky-plugin-template Repository auf GitHub. Informationen rund um Entwicklung und Veröffentlichung findest du in der README."
},
"store_filter": {
"label": "Filter",
"label_def": "Alle"
},
"store_search": {
"label": "Suche"
},
"store_sort": {
"label": "Sortierung",
"label_def": "Zuletzt aktualisiert"
},
"store_source": {
"desc": "Jeder Erweiterungs Quellcode ist in der SteamDeckHomebrew/decky-plugin-database Repository auf GitHub verfügbar.",
"label": "Quellcode"
},
"store_tabs": {
"about": "Über",
"alph_asce": "Alphabetisch (Z zu A)",
"alph_desc": "Alphabetisch (A zu Z)",
"title": "Durchstöbern"
},
"store_testing_cta": "Unterstütze das Decky Loader Team mit dem Testen von neuen Erweiterungen!"
},
"StoreSelect": {
"custom_store": {
"label": "Benutzerdefinierter Marktplatz",
"url_label": "URL"
},
"store_channel": {
"custom": "Benutzerdefiniert",
"default": "Standard",
"label": "Marktplatz Kanal",
"testing": "Test"
}
},
"Updater": {
"decky_updates": "Decky Aktualisierungen",
"patch_notes_desc": "Patchnotizen",
"updates": {
"check_button": "Auf Aktualisierungen prüfen",
"checking": "Wird überprüft",
"cur_version": "Aktualle Version: {{ver}}",
"install_button": "Aktualisierung installieren",
"label": "Aktualisierungen",
"lat_version": "{{ver}} ist die aktuellste",
"reloading": "Lade neu",
"updating": "Aktualisiere"
},
"no_patch_notes_desc": "Für diese Version gibt es keine Patchnotizen"
},
"PluginView": {
"hidden_one": "",
"hidden_other": ""
},
"MultiplePluginsInstallModal": {
"title": {
"install_one": "",
"install_other": "",
"mixed_one": "",
"mixed_other": "",
"update_one": "",
"update_other": "",
"reinstall_one": "",
"reinstall_other": ""
}
}
}
-260
View File
@@ -1,260 +0,0 @@
{
"BranchSelect": {
"update_channel": {
"label": "Update Channel",
"prerelease": "Prerelease",
"stable": "Stable",
"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": {
"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"
}
}
}
-243
View File
@@ -1,243 +0,0 @@
{
"BranchSelect": {
"update_channel": {
"prerelease": "Vooruitgave",
"stable": "Stabiel",
"label": "Update Kanaal",
"testing": "Test"
}
},
"Developer": {
"5secreload": "Herlaad in 5 seconden",
"disabling": "Uitschakelen React DevTools",
"enabling": "Inschakelen React DevTools"
},
"DropdownMultiselect": {
"button": {
"back": "Terug"
}
},
"FilePickerError": {
"errors": {
"unknown": "Een onbekende fout is opgetreden. De ruwe fout is: {{raw_error}}",
"file_not_found": "Het opgegeven pad is niet geldig. Controleer het en voer het opnieuw correct in."
}
},
"FilePickerIndex": {
"files": {
"all_files": "Alle bestanden",
"file_type": "Bestandstype",
"show_hidden": "Toon verborgen bestanden"
},
"filter": {
"created_desc": "Gecreëerd ( Nieuwste)",
"modified_asce": "Veranderd (Oudste)",
"modified_desc": "Veranderd (Nieuwste)",
"name_asce": "Z-A",
"name_desc": "A-Z",
"size_asce": "Grootte (Kleinste)",
"size_desc": "Grootte (Grootste)",
"created_asce": "Gecreëerd (Oudste)"
},
"folder": {
"label": "Map",
"select": "Gebruik deze map",
"show_more": "Toon meer bestanden"
}
},
"PluginView": {
"hidden_one": "1 plug-in is verborgen in deze lijst",
"hidden_other": "{{count}} plug-ins zijn verborgen in deze lijst"
},
"PluginListLabel": {
"hidden": "Verborgen in het snelmenu"
},
"PluginCard": {
"plugin_install": "Installeren",
"plugin_no_desc": "Geen beschrijving gegeven.",
"plugin_version_label": "Plugin Versie",
"plugin_full_access": "Deze plug-in heeft volledige toegang tot je Steam Deck."
},
"PluginInstallModal": {
"install": {
"button_idle": "Installeren",
"button_processing": "Bezig met installeren",
"title": "Installeer {{artifact}}",
"desc": "Weet je zeker dat je {{artifact}} {{version}} wilt installeren?"
},
"no_hash": "Deze plug-in heeft geen hash, u installeert deze op eigen risico.",
"reinstall": {
"button_idle": "Herinstalleren",
"button_processing": "Bezig te herinstalleren",
"desc": "Weet je zeker dat je {{artifact}} {{version}} opnieuw wilt installeren?",
"title": "Installeer {{artifact}} opnieuw"
},
"update": {
"button_idle": "Update",
"button_processing": "Bezig met updaten",
"title": "{{artifact}} bijwerken",
"desc": "Weet je zeker dat je {{artifact}} {{version}} wilt updaten?"
}
},
"MultiplePluginsInstallModal": {
"title": {
"mixed_one": "Wijzig {{count}} plug-in",
"mixed_other": "Pas {{count}} plug-ins aan",
"update_one": "1 plugin bijwerken",
"update_other": "{{count}} plug-ins bijwerken",
"install_one": "Installeer 1 plugin",
"install_other": "Installeer {{count}} plugins",
"reinstall_one": "1 plugin opnieuw installeren",
"reinstall_other": "{{count}} plugins opnieuw installeren"
},
"ok_button": {
"idle": "Bevestigen",
"loading": "Werkend"
},
"confirm": "Weet u zeker dat u de volgende wijzigingen wilt aanbrengen?",
"description": {
"install": "Installeer {{name}} {{version}}",
"update": "Update {{name}} naar {{version}}",
"reinstall": "Installeer opnieuw {{name}} {{version}}"
}
},
"PluginListIndex": {
"no_plugin": "Geen plugins geïnstalleerd!",
"plugin_actions": "Plugin Acties",
"reload": "Herladen",
"uninstall": "Verwijderen",
"update_to": "Update naar {{name}}",
"hide": "Snelle toegang: Verberg",
"update_all_one": "Update 1 plugin",
"update_all_other": "Update {{count}} plugins",
"reinstall": "Opnieuw installeren",
"show": "Snelle toegang: Toon"
},
"PluginLoader": {
"decky_title": "Decky",
"error": "Fout",
"plugin_load_error": {
"message": "Fout bij het laden van plugin {{name}}",
"toast": "Fout bij het laden van {{name}}"
},
"plugin_uninstall": {
"button": "Verwijderen",
"desc": "Weet je zeker dat je {{name}} wilt verwijderen?",
"title": "Verwijder {{name}}"
},
"plugin_update_one": "Updates beschikbaar voor 1 plugin!",
"plugin_update_other": "Updates beschikbaar voor {{count}} plugins!",
"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."
},
"RemoteDebugging": {
"remote_cef": {
"desc": "Sta ongeauthenticeerde toegang tot de CEF-foutopsporing toe aan iedereen in uw netwerk",
"label": "Externe CEF-foutopsporing toestaan"
}
},
"SettingsDeveloperIndex": {
"cef_console": {
"button": "Console openen",
"label": "CEF Bedieningsscherm",
"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."
},
"header": "Andere",
"react_devtools": {
"ip_label": "IP",
"label": "Aanzetten React DevTools",
"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."
},
"third_party_plugins": {
"header": "Plug-ins van derden",
"label_desc": "URL",
"label_url": "Installeer Plugin van URL",
"label_zip": "Installeer Plugin van Zip bestand",
"button_install": "Installeren",
"button_zip": "Bladeren"
},
"valve_internal": {
"desc1": "Schakelt het interne ontwikkelaarsmenu van Valve in.",
"desc2": "Raak niets in dit menu aan tenzij u weet wat het doet.",
"label": "Valve Internal inschakelen"
}
},
"SettingsGeneralIndex": {
"about": {
"decky_version": "Decky versie",
"header": "Over"
},
"beta": {
"header": "Beta deelname"
},
"developer_mode": {
"label": "Ontwikkelaars modus"
},
"other": {
"header": "Overige"
},
"updates": {
"header": "Nieuwe Versies"
}
},
"SettingsIndex": {
"developer_title": "Ontwikkelaar",
"general_title": "Algemeen",
"plugins_title": "Plugins"
},
"Store": {
"store_filter": {
"label": "Filter",
"label_def": "Alles"
},
"store_search": {
"label": "Zoek"
},
"store_sort": {
"label": "Sorteren",
"label_def": "Laatste Geupdate (Nieuwste)"
},
"store_source": {
"label": "Bron Code",
"desc": "Alle broncode van de plug-in is beschikbaar in de SteamDeckHomebrew/decky-plugin-database-repository op GitHub."
},
"store_tabs": {
"about": "Over",
"alph_asce": "Alfabetisch (Z naar A)",
"alph_desc": "Alfabetisch (A naar Z)",
"title": "Bladeren"
},
"store_testing_cta": "Overweeg alsjeblieft om nieuwe plug-ins te testen om het Decky Loader-team te helpen!",
"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.",
"label": "Bijdragende"
}
},
"StoreSelect": {
"custom_store": {
"label": "Aangepassingen winkel",
"url_label": "URL"
},
"store_channel": {
"custom": "Aanpassingen",
"default": "Standaard",
"label": "Winkel Kanaal",
"testing": "Testen"
}
},
"Updater": {
"patch_notes_desc": "Correctie opmerkingen",
"updates": {
"check_button": "Controleer op updates",
"checking": "Controleren",
"cur_version": "Huidige versie: {{ver}}",
"install_button": "Installeer Update",
"label": "Update",
"lat_version": "Up-to-date: loopt {{ver}}",
"reloading": "Herstarten",
"updating": "Aan het updaten"
},
"decky_updates": "Decky Nieuwe Versies",
"no_patch_notes_desc": "geen correctie-opmerkingen voor deze versie"
}
}
-222
View File
@@ -1,222 +0,0 @@
{
"FilePickerIndex": {
"folder": {
"select": "Usar esta pasta"
}
},
"PluginView": {
"hidden_one": "1 plugin está oculto desta lista",
"hidden_many": "{{count}} plugins estão ocultos desta lista",
"hidden_other": "{{count}} plugins estão ocultos desta lista"
},
"PluginCard": {
"plugin_full_access": "Este plugin tem acesso total à tua Steam Deck.",
"plugin_install": "Instalar",
"plugin_version_label": "Versão do plugin",
"plugin_no_desc": "Não tem descrição."
},
"PluginInstallModal": {
"install": {
"button_idle": "Instalar",
"button_processing": "Instalação em curso",
"title": "Instalar {{artifact}}",
"desc": "De certeza que queres instalar {{artifact}} {{version}}?"
},
"reinstall": {
"button_idle": "Reinstalar",
"button_processing": "Reinstalação em curso",
"title": "Reinstalar {{artifact}}",
"desc": "De certeza que queres reinstalar {{artifact}} {{version}}?"
},
"update": {
"button_idle": "Actualizar",
"button_processing": "Actualização em curso",
"title": "Actualizar {{artifact}}",
"desc": "De certeza que queres actualizar {{artifact}} {{version}}?"
},
"no_hash": "Este plugin não tem um hash, estás a instalá-lo por tua conta e risco."
},
"MultiplePluginsInstallModal": {
"title": {
"mixed_one": "Alterar 1 plugin",
"mixed_many": "Alterar {{count}} plugins",
"mixed_other": "Alterar {{count}} plugins",
"update_one": "Actualizar 1 plugin",
"update_many": "Actualizar {{count}} plugins",
"update_other": "Actualizar {{count}} plugins",
"reinstall_one": "Reinstalar 1 plugin",
"reinstall_many": "Reinstalar {{count}} plugins",
"reinstall_other": "Reinstalar {{count}} plugins",
"install_one": "Instalar 1 plugin",
"install_many": "Instalar {{count}} plugins",
"install_other": "Instalar {{count}} plugins"
},
"ok_button": {
"idle": "Confirmar",
"loading": "Em curso"
},
"description": {
"install": "Instalar {{name}} {{version}}",
"update": "Actualizar {{name}} para {{version}}",
"reinstall": "Reinstalar {{name}} {{version}}"
},
"confirm": "De certeza que queres fazer as seguintes alterações?"
},
"PluginListIndex": {
"no_plugin": "Nenhum plugin instalado!",
"reinstall": "Reinstalar",
"uninstall": "Desinstalar",
"update_to": "Actualizar para {{name}}",
"update_all_one": "Actualizar 1 plugin",
"update_all_many": "Actualizar {{count}} plugins",
"update_all_other": "Actualizar {{count}} plugins",
"plugin_actions": "Operações de plugin",
"reload": "Recarregar",
"show": "Acesso rápido: Mostrar",
"hide": "Acesso rápido: Ocultar"
},
"BranchSelect": {
"update_channel": {
"stable": "Estável",
"testing": "Em teste",
"label": "Canal de actualização",
"prerelease": "Pré-lançamento"
}
},
"Developer": {
"5secreload": "Vai recarregar em 5 segundos",
"disabling": "Desactivando React DevTools",
"enabling": "Activando React DevTools"
},
"PluginListLabel": {
"hidden": "Oculto do menu de acesso rápido"
},
"PluginLoader": {
"decky_title": "Decky",
"error": "Erro",
"plugin_load_error": {
"message": "Erro ao carregar o plugin {{name}}",
"toast": "Erro ao carregar {{name}}"
},
"plugin_uninstall": {
"button": "Desinstalar",
"title": "Desinstalar {{name}}",
"desc": "De certeza que queres desinstalar {{name}}?"
},
"decky_update_available": "Está disponível uma nova versão de {{tag_name}} !",
"plugin_update_one": "1 plugin tem actualizações disponíveis!",
"plugin_update_many": "{{count}} plugins têm actualizações disponíveis!",
"plugin_update_other": "{{count}} plugins têm actualizações disponíveis!",
"plugin_error_uninstall": "Houve uma excepção ao carregar {{name}}, como mostrado em cima. Pode ter sido porque o plugin requere a última versão do SteamUI. Verifica se há uma actualização disponível ou desinstala o plugin nas definições do Decky."
},
"SettingsDeveloperIndex": {
"cef_console": {
"button": "Abrir consola",
"label": "Consola CEF",
"desc": "Abre a consola do CEF. Só é útil para efeitos de debugging. Pode ser perigosa e só deve ser usada se és um desenvolvedor de plugins, ou se foste aqui indicado por um desenvolvedor."
},
"header": "Outros",
"react_devtools": {
"desc": "Permite a conecção a um computador que está a correr React DevTools. Mudar esta definição vai recarregar o Steam. Define o endereço de IP antes de activar.",
"ip_label": "IP",
"label": "Activar React DevTools"
},
"third_party_plugins": {
"button_install": "Instalar",
"button_zip": "Navegar",
"header": "Plugins de terceiros",
"label_desc": "URl",
"label_url": "Instalar plugin a partir dum URL",
"label_zip": "Instalar plugin a partir dum ficheiro ZIP"
},
"valve_internal": {
"label": "Activar menu interno da Valve",
"desc1": "Activa o menu interno de programador da Valve.",
"desc2": "Não toques em nada deste menu se não souberes a sua função."
}
},
"RemoteDebugging": {
"remote_cef": {
"desc": "Permitir acesso não autenticado ao debugger do CEF a qualquer pessoa na tua rede",
"label": "Permitir debugging remoto do CEF"
}
},
"SettingsGeneralIndex": {
"about": {
"decky_version": "Versão do Decky",
"header": "Sobre"
},
"beta": {
"header": "Participação na versão Beta"
},
"developer_mode": {
"label": "Modo de programador"
},
"other": {
"header": "Outros"
},
"updates": {
"header": "Actualizações"
}
},
"SettingsIndex": {
"developer_title": "Programador",
"general_title": "Geral",
"plugins_title": "Plugins"
},
"Store": {
"store_contrib": {
"label": "Contribuir",
"desc": "Se queres contribuir com um novo plugin, vai ao repositório SteamDeckHomebrew/decky-plugin-template no GitHub. No README, podes encontrar mais informação sobre desenvolvimento e distribuição."
},
"store_filter": {
"label": "Filtro",
"label_def": "Todos"
},
"store_search": {
"label": "Procurar"
},
"store_sort": {
"label": "Ordenar",
"label_def": "Última actualização (mais recente)"
},
"store_source": {
"label": "Código fonte",
"desc": "O código fonte de cada plugin está disponível no repositório SteamDeckHomebrew/decky-plugin-database no GitHub."
},
"store_tabs": {
"about": "Sobre",
"alph_asce": "Alfabeticamente (Z-A)",
"alph_desc": "Alfabeticamente (A-Z)",
"title": "Navegar"
},
"store_testing_cta": "Testa novos plugins e ajuda a equipa do Decky Loader!"
},
"StoreSelect": {
"custom_store": {
"url_label": "URL",
"label": "Loja personalizada"
},
"store_channel": {
"custom": "Personalizada",
"default": "Standard",
"testing": "Em teste",
"label": "Canal de loja"
}
},
"Updater": {
"decky_updates": "Actualizações do Decky",
"no_patch_notes_desc": "sem registo de alterações desta versão",
"patch_notes_desc": "Registo de alterações",
"updates": {
"check_button": "Procurar actualizações",
"checking": "Busca de actualizações em curso",
"cur_version": "Versão actual: {{ver}}",
"label": "Actualizações",
"lat_version": "Actualizado: a correr {{ver}}",
"updating": "Actualização em curso",
"reloading": "Recarregar",
"install_button": "Instalar actualização"
}
}
}
+2 -2
View File
@@ -1,4 +1,4 @@
# This file is needed to make the relative imports in src/ work properly.
# This file is needed to make the relative imports in decky_loader/ work properly.
if __name__ == "__main__":
from src.main import main
from decky_loader.main import main
main()
+1009
View File
File diff suppressed because it is too large Load Diff
+30
View File
@@ -0,0 +1,30 @@
import os
from PyInstaller.building.build_main import Analysis
from PyInstaller.building.api import EXE, PYZ
from PyInstaller.utils.hooks import copy_metadata
a = Analysis(
['main.py'],
datas=[
('decky_loader/locales', 'decky_loader/locales'),
('decky_loader/static', 'decky_loader/static'),
] + copy_metadata('decky_loader'),
hiddenimports=['logging.handlers', 'sqlite3', 'decky_plugin', 'decky'],
)
pyz = PYZ(a.pure, a.zipped_data)
noconsole = bool(os.getenv('DECKY_NOCONSOLE'))
name = "PluginLoader"
if noconsole:
name += "_noconsole"
exe = EXE(
pyz,
a.scripts,
a.binaries,
a.zipfiles,
a.datas,
name=name,
upx=True,
console=not noconsole,
)
+47
View File
@@ -0,0 +1,47 @@
[tool.poetry]
name = "decky-loader"
version = "0.0.0" # the real version will be autogenerated
description = "A plugin loader for the Steam Deck"
license = "GPLv2"
authors = []
packages = [
{include = "decky_loader"},
{include = "decky_loader/main.py"}
]
include = [
"decky_loader/locales/*",
"decky_loader/static/*"
]
[tool.poetry.dependencies]
python = ">=3.10,<3.13"
aiohttp = "^3.10.11"
aiohttp-jinja2 = "^1.5.1"
aiohttp-cors = "^0.7.0"
watchdog = "^4"
certifi = "*"
packaging = "^24"
multidict = "^6.0.5"
setproctitle = "^1.3.3"
[tool.poetry.group.dev.dependencies]
pyinstaller = "^6.8.0"
pyright = "^1.1.335"
[tool.poetry.scripts]
decky-loader = 'decky_loader.main:main'
[tool.pyright]
strict = ["*"]
[tool.poetry-dynamic-versioning]
enable = true
[tool.poetry-dynamic-versioning.substitution]
# don't replace version in decky_plugin.py
files = []
[build-system]
requires = ["poetry-core>=1.0.0", "poetry-dynamic-versioning>=1.0.0,<2.0.0"]
build-backend = "poetry_dynamic_versioning.backend"
-3
View File
@@ -1,3 +0,0 @@
{
"strict": ["*"]
}
-5
View File
@@ -1,5 +0,0 @@
aiohttp==3.8.4
aiohttp-jinja2==1.5.1
aiohttp_cors==0.7.0
watchdog==2.1.7
certifi==2023.7.22
-6
View File
@@ -1,6 +0,0 @@
from enum import Enum
class UserType(Enum):
HOST_USER = 1
EFFECTIVE_USER = 2
ROOT = 3
-84
View File
@@ -1,84 +0,0 @@
class PluginEventTarget extends EventTarget { }
method_call_ev_target = new PluginEventTarget();
window.addEventListener("message", function(evt) {
let ev = new Event(evt.data.call_id);
ev.data = evt.data.result;
method_call_ev_target.dispatchEvent(ev);
}, false);
async function call_server_method(method_name, arg_object={}) {
const token = await fetch("http://127.0.0.1:1337/auth/token").then(r => r.text());
const response = await fetch(`http://127.0.0.1:1337/methods/${method_name}`, {
method: 'POST',
credentials: "include",
headers: {
'Content-Type': 'application/json',
Authentication: token
},
body: JSON.stringify(arg_object),
});
const dta = await response.json();
if (!dta.success) throw dta.result;
return dta.result;
}
// Source: https://stackoverflow.com/a/2117523 Thanks!
function uuidv4() {
return ([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g, c =>
(c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16)
);
}
async function fetch_nocors(url, request={}) {
let args = { method: "POST", headers: {}, body: "" };
request = {...args, ...request};
request.url = url;
request.data = request.body;
delete request.body; //maintain api-compatibility with fetch
return await call_server_method("http_request", request);
}
async function call_plugin_method(method_name, arg_object={}) {
if (plugin_name == undefined)
throw new Error("Plugin methods can only be called from inside plugins (duh)");
const token = await fetch("http://127.0.0.1:1337/auth/token").then(r => r.text());
const response = await fetch(`http://127.0.0.1:1337/plugins/${plugin_name}/methods/${method_name}`, {
method: 'POST',
credentials: "include",
headers: {
'Content-Type': 'application/json',
Authentication: token
},
body: JSON.stringify({
args: arg_object,
}),
});
const dta = await response.json();
if (!dta.success) throw dta.result;
return dta.result;
}
async function execute_in_tab(tab, run_async, code) {
return await call_server_method("execute_in_tab", {
'tab': tab,
'run_async': run_async,
'code': code
});
}
async function inject_css_into_tab(tab, style) {
return await call_server_method("inject_css_into_tab", {
'tab': tab,
'style': style
});
}
async function remove_css_from_tab(tab, css_id) {
return await call_server_method("remove_css_from_tab", {
'tab': tab,
'css_id': css_id
});
}
-168
View File
@@ -1,168 +0,0 @@
import multiprocessing
from asyncio import (Lock, get_event_loop, new_event_loop,
set_event_loop, sleep)
from importlib.util import module_from_spec, spec_from_file_location
from json import dumps, load, loads
from logging import getLogger
from traceback import format_exc
from os import path, environ
from signal import SIGINT, signal
from sys import exit, path as syspath, modules as sysmodules
from typing import Any, Dict
from .localsocket import LocalSocket
from .localplatform import setgid, setuid, get_username, get_home_path
from .customtypes import UserType
from . import helpers
class PluginWrapper:
def __init__(self, file: str, plugin_directory: str, plugin_path: str) -> None:
self.file = file
self.plugin_path = plugin_path
self.plugin_directory = plugin_directory
self.method_call_lock = Lock()
self.socket: LocalSocket = LocalSocket(self._on_new_message)
self.version = None
json = load(open(path.join(plugin_path, plugin_directory, "plugin.json"), "r", encoding="utf-8"))
if path.isfile(path.join(plugin_path, plugin_directory, "package.json")):
package_json = load(open(path.join(plugin_path, plugin_directory, "package.json"), "r", encoding="utf-8"))
self.version = package_json["version"]
self.legacy = False
self.main_view_html = json["main_view_html"] if "main_view_html" in json else ""
self.tile_view_html = json["tile_view_html"] if "tile_view_html" in json else ""
self.legacy = self.main_view_html or self.tile_view_html
self.name = json["name"]
self.author = json["author"]
self.flags = json["flags"]
self.log = getLogger("plugin")
self.passive = not path.isfile(self.file)
def __str__(self) -> str:
return self.name
def _init(self):
try:
signal(SIGINT, lambda s, f: exit(0))
set_event_loop(new_event_loop())
if self.passive:
return
setgid(UserType.ROOT if "root" in self.flags else UserType.HOST_USER)
setuid(UserType.ROOT if "root" in self.flags else UserType.HOST_USER)
# export a bunch of environment variables to help plugin developers
environ["HOME"] = get_home_path(UserType.ROOT if "root" in self.flags else UserType.HOST_USER)
environ["USER"] = "root" if "root" in self.flags else get_username()
environ["DECKY_VERSION"] = helpers.get_loader_version()
environ["DECKY_USER"] = get_username()
environ["DECKY_USER_HOME"] = helpers.get_home_path()
environ["DECKY_HOME"] = helpers.get_homebrew_path()
environ["DECKY_PLUGIN_SETTINGS_DIR"] = path.join(environ["DECKY_HOME"], "settings", self.plugin_directory)
helpers.mkdir_as_user(path.join(environ["DECKY_HOME"], "settings"))
helpers.mkdir_as_user(environ["DECKY_PLUGIN_SETTINGS_DIR"])
environ["DECKY_PLUGIN_RUNTIME_DIR"] = path.join(environ["DECKY_HOME"], "data", self.plugin_directory)
helpers.mkdir_as_user(path.join(environ["DECKY_HOME"], "data"))
helpers.mkdir_as_user(environ["DECKY_PLUGIN_RUNTIME_DIR"])
environ["DECKY_PLUGIN_LOG_DIR"] = path.join(environ["DECKY_HOME"], "logs", self.plugin_directory)
helpers.mkdir_as_user(path.join(environ["DECKY_HOME"], "logs"))
helpers.mkdir_as_user(environ["DECKY_PLUGIN_LOG_DIR"])
environ["DECKY_PLUGIN_DIR"] = path.join(self.plugin_path, self.plugin_directory)
environ["DECKY_PLUGIN_NAME"] = self.name
if self.version:
environ["DECKY_PLUGIN_VERSION"] = self.version
environ["DECKY_PLUGIN_AUTHOR"] = self.author
# append the plugin's `py_modules` to the recognized python paths
syspath.append(path.join(environ["DECKY_PLUGIN_DIR"], "py_modules"))
#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)
assert spec is not None
module = module_from_spec(spec)
assert spec.loader is not None
spec.loader.exec_module(module)
self.Plugin = module.Plugin
if hasattr(self.Plugin, "_migration"):
get_event_loop().run_until_complete(self.Plugin._migration(self.Plugin))
if hasattr(self.Plugin, "_main"):
get_event_loop().create_task(self.Plugin._main(self.Plugin))
get_event_loop().create_task(self.socket.setup_server())
get_event_loop().run_forever()
except:
self.log.error("Failed to start " + self.name + "!\n" + format_exc())
exit(0)
async def _unload(self):
try:
self.log.info("Attempting to unload with plugin " + self.name + "'s \"_unload\" function.\n")
if hasattr(self.Plugin, "_unload"):
await self.Plugin._unload(self.Plugin)
self.log.info("Unloaded " + self.name + "\n")
else:
self.log.info("Could not find \"_unload\" in " + self.name + "'s main.py" + "\n")
except:
self.log.error("Failed to unload " + self.name + "!\n" + format_exc())
exit(0)
async def _on_new_message(self, message : str) -> str|None:
data = loads(message)
if "stop" in data:
self.log.info("Calling Loader unload function.")
await self._unload()
get_event_loop().stop()
while get_event_loop().is_running():
await sleep(0)
get_event_loop().close()
raise Exception("Closing message listener")
# TODO there is definitely a better way to type this
d: Dict[str, Any] = {"res": None, "success": True}
try:
d["res"] = await getattr(self.Plugin, data["method"])(self.Plugin, **data["args"])
except Exception as e:
d["res"] = str(e)
d["success"] = False
finally:
return dumps(d, ensure_ascii=False)
def start(self):
if self.passive:
return self
multiprocessing.Process(target=self._init).start()
return self
def stop(self):
if self.passive:
return
async def _(self: PluginWrapper):
await self.socket.write_single_line(dumps({ "stop": True }, ensure_ascii=False))
await self.socket.close_socket_connection()
get_event_loop().create_task(_(self))
async def execute_method(self, method_name: str, kwargs: Dict[Any, Any]):
if self.passive:
raise RuntimeError("This plugin is passive (aka does not implement main.py)")
async with self.method_call_lock:
# reader, writer =
await self.socket.get_socket_connection()
await self.socket.write_single_line(dumps({ "method": method_name, "args": kwargs }, ensure_ascii=False))
line = await self.socket.read_single_line()
if line != None:
res = loads(line)
if not res["success"]:
raise Exception(res["res"])
return res["res"]
+2 -68
View File
@@ -1,69 +1,3 @@
#!/bin/sh
[ "$UID" -eq 0 ] || exec sudo "$0" "$@"
echo "Installing Steam Deck Plugin Loader pre-release..."
USER_DIR="$(getent passwd $SUDO_USER | cut -d: -f6)"
HOMEBREW_FOLDER="${USER_DIR}/homebrew"
# Create folder structure
rm -rf "${HOMEBREW_FOLDER}/services"
sudo -u $SUDO_USER mkdir -p "${HOMEBREW_FOLDER}/services"
sudo -u $SUDO_USER mkdir -p "${HOMEBREW_FOLDER}/plugins"
touch "${USER_DIR}/.steam/steam/.cef-enable-remote-debugging"
# Download latest release and install it
RELEASE=$(curl -s 'https://api.github.com/repos/SteamDeckHomebrew/decky-loader/releases' | jq -r "first(.[] | select(.prerelease == "true"))")
VERSION=$(jq -r '.tag_name' <<< ${RELEASE} )
DOWNLOADURL=$(jq -r '.assets[].browser_download_url | select(endswith("PluginLoader"))' <<< ${RELEASE})
printf "Installing version %s...\n" "${VERSION}"
curl -L $DOWNLOADURL --output ${HOMEBREW_FOLDER}/services/PluginLoader
chmod +x ${HOMEBREW_FOLDER}/services/PluginLoader
echo $VERSION > ${HOMEBREW_FOLDER}/services/.loader.version
systemctl --user stop plugin_loader 2> /dev/null
systemctl --user disable plugin_loader 2> /dev/null
systemctl stop plugin_loader 2> /dev/null
systemctl disable plugin_loader 2> /dev/null
curl -L https://raw.githubusercontent.com/SteamDeckHomebrew/decky-loader/main/dist/plugin_loader-prerelease.service --output ${HOMEBREW_FOLDER}/services/plugin_loader-prerelease.service
cat > "${HOMEBREW_FOLDER}/services/plugin_loader-backup.service" <<- EOM
[Unit]
Description=SteamDeck Plugin Loader
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=root
Restart=always
ExecStart=${HOMEBREW_FOLDER}/services/PluginLoader
WorkingDirectory=${HOMEBREW_FOLDER}/services
KillSignal=SIGKILL
Environment=PLUGIN_PATH=${HOMEBREW_FOLDER}/plugins
Environment=LOG_LEVEL=DEBUG
[Install]
WantedBy=multi-user.target
EOM
if [[ -f "${HOMEBREW_FOLDER}/services/plugin_loader-prerelease.service" ]]; then
printf "Grabbed latest prerelease service.\n"
sed -i -e "s|\${HOMEBREW_FOLDER}|${HOMEBREW_FOLDER}|" "${HOMEBREW_FOLDER}/services/plugin_loader-prerelease.service"
cp -f "${HOMEBREW_FOLDER}/services/plugin_loader-prerelease.service" "/etc/systemd/system/plugin_loader.service"
else
printf "Could not curl latest prerelease systemd service, using built-in service as a backup!\n"
rm -f "/etc/systemd/system/plugin_loader.service"
cp "${HOMEBREW_FOLDER}/services/plugin_loader-backup.service" "/etc/systemd/system/plugin_loader.service"
fi
mkdir -p ${HOMEBREW_FOLDER}/services/.systemd
cp ${HOMEBREW_FOLDER}/services/plugin_loader-prerelease.service ${HOMEBREW_FOLDER}/services/.systemd/plugin_loader-prerelease.service
cp ${HOMEBREW_FOLDER}/services/plugin_loader-backup.service ${HOMEBREW_FOLDER}/services/.systemd/plugin_loader-backup.service
rm ${HOMEBREW_FOLDER}/services/plugin_loader-backup.service ${HOMEBREW_FOLDER}/services/plugin_loader-prerelease.service
systemctl daemon-reload
systemctl start plugin_loader
systemctl enable plugin_loader
echo This script is deprecated! Use https://github.com/SteamDeckHomebrew/decky-installer/raw/main/cli/install_prerelease.sh instead!
exit 1
+2 -68
View File
@@ -1,69 +1,3 @@
#!/bin/sh
[ "$UID" -eq 0 ] || exec sudo "$0" "$@"
echo "Installing Steam Deck Plugin Loader release..."
USER_DIR="$(getent passwd $SUDO_USER | cut -d: -f6)"
HOMEBREW_FOLDER="${USER_DIR}/homebrew"
# Create folder structure
rm -rf "${HOMEBREW_FOLDER}/services"
sudo -u $SUDO_USER mkdir -p "${HOMEBREW_FOLDER}/services"
sudo -u $SUDO_USER mkdir -p "${HOMEBREW_FOLDER}/plugins"
touch "${USER_DIR}/.steam/steam/.cef-enable-remote-debugging"
# Download latest release and install it
RELEASE=$(curl -s 'https://api.github.com/repos/SteamDeckHomebrew/decky-loader/releases' | jq -r "first(.[] | select(.prerelease == "false"))")
VERSION=$(jq -r '.tag_name' <<< ${RELEASE} )
DOWNLOADURL=$(jq -r '.assets[].browser_download_url | select(endswith("PluginLoader"))' <<< ${RELEASE})
printf "Installing version %s...\n" "${VERSION}"
curl -L $DOWNLOADURL --output ${HOMEBREW_FOLDER}/services/PluginLoader
chmod +x ${HOMEBREW_FOLDER}/services/PluginLoader
echo $VERSION > ${HOMEBREW_FOLDER}/services/.loader.version
systemctl --user stop plugin_loader 2> /dev/null
systemctl --user disable plugin_loader 2> /dev/null
systemctl stop plugin_loader 2> /dev/null
systemctl disable plugin_loader 2> /dev/null
curl -L https://raw.githubusercontent.com/SteamDeckHomebrew/decky-loader/main/dist/plugin_loader-release.service --output ${HOMEBREW_FOLDER}/services/plugin_loader-release.service
cat > "${HOMEBREW_FOLDER}/services/plugin_loader-backup.service" <<- EOM
[Unit]
Description=SteamDeck Plugin Loader
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=root
Restart=always
ExecStart=${HOMEBREW_FOLDER}/services/PluginLoader
WorkingDirectory=${HOMEBREW_FOLDER}/services
KillSignal=SIGKILL
Environment=PLUGIN_PATH=${HOMEBREW_FOLDER}/plugins
Environment=LOG_LEVEL=INFO
[Install]
WantedBy=multi-user.target
EOM
if [[ -f "${HOMEBREW_FOLDER}/services/plugin_loader-release.service" ]]; then
printf "Grabbed latest release service.\n"
sed -i -e "s|\${HOMEBREW_FOLDER}|${HOMEBREW_FOLDER}|" "${HOMEBREW_FOLDER}/services/plugin_loader-release.service"
cp -f "${HOMEBREW_FOLDER}/services/plugin_loader-release.service" "/etc/systemd/system/plugin_loader.service"
else
printf "Could not curl latest release systemd service, using built-in service as a backup!\n"
rm -f "/etc/systemd/system/plugin_loader.service"
cp "${HOMEBREW_FOLDER}/services/plugin_loader-backup.service" "/etc/systemd/system/plugin_loader.service"
fi
mkdir -p ${HOMEBREW_FOLDER}/services/.systemd
cp ${HOMEBREW_FOLDER}/services/plugin_loader-release.service ${HOMEBREW_FOLDER}/services/.systemd/plugin_loader-release.service
cp ${HOMEBREW_FOLDER}/services/plugin_loader-backup.service ${HOMEBREW_FOLDER}/services/.systemd/plugin_loader-backup.service
rm ${HOMEBREW_FOLDER}/services/plugin_loader-backup.service ${HOMEBREW_FOLDER}/services/plugin_loader-release.service
systemctl daemon-reload
systemctl start plugin_loader
systemctl enable plugin_loader
echo This script is deprecated! Use https://github.com/SteamDeckHomebrew/decky-installer/raw/main/cli/install_release.sh instead!
exit 1
+3 -3
View File
@@ -1,14 +1,14 @@
[Unit]
Description=SteamDeck Plugin Loader
After=network-online.target
Wants=network-online.target
After=network.target
[Service]
Type=simple
User=root
Restart=always
KillMode=process
TimeoutStopSec=15
ExecStart=${HOMEBREW_FOLDER}/services/PluginLoader
WorkingDirectory=${HOMEBREW_FOLDER}/services
KillSignal=SIGKILL
Environment=UNPRIVILEGED_PATH=${HOMEBREW_FOLDER}
Environment=PRIVILEGED_PATH=${HOMEBREW_FOLDER}
Environment=LOG_LEVEL=DEBUG
+3 -3
View File
@@ -1,14 +1,14 @@
[Unit]
Description=SteamDeck Plugin Loader
After=network-online.target
Wants=network-online.target
After=network.target
[Service]
Type=simple
User=root
Restart=always
KillMode=process
TimeoutStopSec=15
ExecStart=${HOMEBREW_FOLDER}/services/PluginLoader
WorkingDirectory=${HOMEBREW_FOLDER}/services
KillSignal=SIGKILL
Environment=UNPRIVILEGED_PATH=${HOMEBREW_FOLDER}
Environment=PRIVILEGED_PATH=${HOMEBREW_FOLDER}
Environment=LOG_LEVEL=INFO
Generated
+175
View File
@@ -0,0 +1,175 @@
{
"nodes": {
"flake-utils": {
"inputs": {
"systems": "systems"
},
"locked": {
"lastModified": 1710146030,
"narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"flake-utils_2": {
"inputs": {
"systems": "systems_2"
},
"locked": {
"lastModified": 1710146030,
"narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"nix-github-actions": {
"inputs": {
"nixpkgs": [
"poetry2nix",
"nixpkgs"
]
},
"locked": {
"lastModified": 1703863825,
"narHash": "sha256-rXwqjtwiGKJheXB43ybM8NwWB8rO2dSRrEqes0S7F5Y=",
"owner": "nix-community",
"repo": "nix-github-actions",
"rev": "5163432afc817cf8bd1f031418d1869e4c9d5547",
"type": "github"
},
"original": {
"owner": "nix-community",
"repo": "nix-github-actions",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1719848872,
"narHash": "sha256-H3+EC5cYuq+gQW8y0lSrrDZfH71LB4DAf+TDFyvwCNA=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "00d80d13810dbfea8ab4ed1009b09100cca86ba8",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"poetry2nix": {
"inputs": {
"flake-utils": "flake-utils_2",
"nix-github-actions": "nix-github-actions",
"nixpkgs": [
"nixpkgs"
],
"systems": "systems_3",
"treefmt-nix": "treefmt-nix"
},
"locked": {
"lastModified": 1719850884,
"narHash": "sha256-UU/lVTHFx0GpEkihoLJrMuM9DcuhZmNe3db45vshSyI=",
"owner": "nix-community",
"repo": "poetry2nix",
"rev": "42262f382c68afab1113ebd1911d0c93822d756e",
"type": "github"
},
"original": {
"owner": "nix-community",
"repo": "poetry2nix",
"type": "github"
}
},
"root": {
"inputs": {
"flake-utils": "flake-utils",
"nixpkgs": "nixpkgs",
"poetry2nix": "poetry2nix"
}
},
"systems": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
},
"systems_2": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
},
"systems_3": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"id": "systems",
"type": "indirect"
}
},
"treefmt-nix": {
"inputs": {
"nixpkgs": [
"poetry2nix",
"nixpkgs"
]
},
"locked": {
"lastModified": 1719749022,
"narHash": "sha256-ddPKHcqaKCIFSFc/cvxS14goUhCOAwsM1PbMr0ZtHMg=",
"owner": "numtide",
"repo": "treefmt-nix",
"rev": "8df5ff62195d4e67e2264df0b7f5e8c9995fd0bd",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "treefmt-nix",
"type": "github"
}
}
},
"root": "root",
"version": 7
}
+67
View File
@@ -0,0 +1,67 @@
{
description = "Decky development environment";
# pulls in the python deps from poetry
inputs = {
flake-utils.url = "github:numtide/flake-utils";
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
poetry2nix = {
url = "github:nix-community/poetry2nix";
inputs.nixpkgs.follows = "nixpkgs";
};
};
outputs =
{
self,
nixpkgs,
flake-utils,
poetry2nix,
}:
flake-utils.lib.eachDefaultSystem (
system:
let
pkgs = import nixpkgs { inherit system; };
p2n = (poetry2nix.lib.mkPoetry2Nix { inherit pkgs; });
in
{
devShells.default =
(p2n.mkPoetryEnv {
projectDir = self + "/backend";
# pyinstaller fails to compile so precompiled it is
overrides = p2n.overrides.withDefaults (
final: prev: {
pyinstaller = prev.pyinstaller.override { preferWheel = true; };
pyright = null;
}
);
}).env.overrideAttrs
(oldAttrs: {
shellHook = ''
PYTHONPATH=`which python`
FILE=.vscode/settings.json
if [ -f "$FILE" ]; then
jq --arg pythonpath "$PYTHONPATH" '.["python.defaultInterpreterPath"] = $pythonpath' $FILE > "$FILE.tmp" && mv "$FILE.tmp" "$FILE"
else
echo "{\"python.defaultInterpreterPath\": \"$PYTHONPATH\"}" > "$FILE"
fi
'';
UV_USE_IO_URING = 0; # work around node#48444
nativeBuildInputs = with pkgs; [
python311Packages.setuptools
];
buildInputs = with pkgs; [
nodejs_22
nodePackages.pnpm
poetry
jq
# fixes local pyright not being able to see the pythonpath properly.
(pkgs.writeShellScriptBin "pyright" ''${pkgs.pyright}/bin/pyright --pythonpath `which python3` "$@" '')
(pkgs.writeShellScriptBin "pyright-langserver" ''${pkgs.pyright}/bin/pyright-langserver --pythonpath `which python3` "$@" '')
(pkgs.writeShellScriptBin "pyright-python" ''${pkgs.pyright}/bin/pyright-python --pythonpath `which python3` "$@" '')
(pkgs.writeShellScriptBin "pyright-python-langserver" ''${pkgs.pyright}/bin/pyright-python-langserver --pythonpath `which python3` "$@" '')
];
});
}
);
}
+4 -3
View File
@@ -1,9 +1,10 @@
module.exports = {
import importSort from 'prettier-plugin-import-sort';
export default {
semi: true,
trailingComma: 'all',
singleQuote: true,
printWidth: 120,
tabWidth: 2,
endOfLine: 'auto',
plugins: [require('prettier-plugin-import-sort')],
};
plugins: [importSort],
}
+102 -99
View File
@@ -1,100 +1,103 @@
// import { readdir } from "fs/promises";
export default {
contextSeparator: '_',
// Key separator used in your translation keys
createOldCatalogs: false,
// Save the \_old files
defaultNamespace: 'translation',
// Default namespace used in your i18next config
defaultValue: '',
// Default value to give to keys with no value
// You may also specify a function accepting the locale, namespace, key, and value as arguments
indentation: 2,
// Indentation of the catalog files
keepRemoved: true,
// Keep keys from the catalog that are no longer in code
keySeparator: '.',
// Key separator used in your translation keys
// If you want to use plain english keys, separators such as `.` and `:` will conflict. You might want to set `keySeparator: false` and `namespaceSeparator: false`. That way, `t('Status: Loading...')` will not think that there are a namespace and three separator dots for instance.
// see below for more details
lexers: {
mjs: ['JavascriptLexer'],
js: ['JavascriptLexer'], // if you're writing jsx inside .js files, change this to JsxLexer
ts: ['JavascriptLexer'],
jsx: ['JsxLexer'],
tsx: ['JsxLexer'],
default: ['JavascriptLexer'],
},
lineEnding: 'auto',
// Control the line ending. See options at https://github.com/ryanve/eol
locales: ['en-US'],
// An array of the locales in your applications
namespaceSeparator: false,
// Namespace separator used in your translation keys
// If you want to use plain english keys, separators such as `.` and `:` will conflict. You might want to set `keySeparator: false` and `namespaceSeparator: false`. That way, `t('Status: Loading...')` will not think that there are a namespace and three separator dots for instance.
output: '../backend/locales/$LOCALE.json',
// Supports $LOCALE and $NAMESPACE injection
// Supports JSON (.json) and YAML (.yml) file formats
// Where to write the locale files relative to process.cwd()
pluralSeparator: '_',
// Plural separator used in your translation keys
// If you want to use plain english keys, separators such as `_` might conflict. You might want to set `pluralSeparator` to a different string that does not occur in your keys.
input: './src/**/*.{ts,tsx}',
// An array of globs that describe where to look for source files
// relative to the location of the configuration file
sort: true,
// Whether or not to sort the catalog. Can also be a [compareFunction](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort#parameters)
verbose: false,
// Display info about the parsing including some stats
failOnWarnings: false,
// Exit with an exit code of 1 on warnings
failOnUpdate: false,
// Exit with an exit code of 1 when translations are updated (for CI purpose)
customValueTemplate: null,
// If you wish to customize the value output the value as an object, you can set your own format.
// ${defaultValue} is the default value you set in your translation function.
// Any other custom property will be automatically extracted.
//
// Example:
// {
// message: "${defaultValue}",
// description: "${maxLength}", // t('my-key', {maxLength: 150})
// }
resetDefaultValueLocale: null,
// The locale to compare with default values to determine whether a default value has been changed.
// If this is set and a default value differs from a translation in the specified locale, all entries
// for that key across locales are reset to the default value, and existing translations are moved to
// the `_old` file.
i18nextOptions: null,
// If you wish to customize options in internally used i18next instance, you can define an object with any
// configuration property supported by i18next (https://www.i18next.com/overview/configuration-options).
// { compatibilityJSON: 'v3' } can be used to generate v3 compatible plurals.
yamlOptions: null,
// If you wish to customize options for yaml output, you can define an object here.
// Configuration options are here (https://github.com/nodeca/js-yaml#dump-object---options-).
// Example:
// {
// lineWidth: -1,
// }
}
contextSeparator: '_',
// Key separator used in your translation keys
createOldCatalogs: false,
// Save the \_old files
defaultNamespace: 'translation',
// Default namespace used in your i18next config
defaultValue: '',
// Default value to give to keys with no value
// You may also specify a function accepting the locale, namespace, key, and value as arguments
indentation: 4,
// Indentation of the catalog files. Weblate seems to like 4.
keepRemoved: true,
// Keep keys from the catalog that are no longer in code
keySeparator: '.',
// Key separator used in your translation keys
// If you want to use plain english keys, separators such as `.` and `:` will conflict. You might want to set `keySeparator: false` and `namespaceSeparator: false`. That way, `t('Status: Loading...')` will not think that there are a namespace and three separator dots for instance.
// see below for more details
lexers: {
mjs: ['JavascriptLexer'],
js: ['JavascriptLexer'], // if you're writing jsx inside .js files, change this to JsxLexer
ts: ['JavascriptLexer'],
jsx: ['JsxLexer'],
tsx: ['JsxLexer'],
default: ['JavascriptLexer'],
},
lineEnding: 'auto',
// Control the line ending. See options at https://github.com/ryanve/eol
// locales: (await readdir('../backend/decky_loader/locales/')).map(lang => lang.replace(".json", "")),
locales: ["en-US"],
// An array of the locales in your applications
namespaceSeparator: false,
// Namespace separator used in your translation keys
// If you want to use plain english keys, separators such as `.` and `:` will conflict. You might want to set `keySeparator: false` and `namespaceSeparator: false`. That way, `t('Status: Loading...')` will not think that there are a namespace and three separator dots for instance.
output: '../backend/decky_loader/locales/$LOCALE.json',
// Supports $LOCALE and $NAMESPACE injection
// Supports JSON (.json) and YAML (.yml) file formats
// Where to write the locale files relative to process.cwd()
pluralSeparator: '_',
// Plural separator used in your translation keys
// If you want to use plain english keys, separators such as `_` might conflict. You might want to set `pluralSeparator` to a different string that does not occur in your keys.
input: './src/**/*.{ts,tsx}',
// An array of globs that describe where to look for source files
// relative to the location of the configuration file
sort: (a, b) => a.localeCompare(b, undefined, { numeric: true }),
// Whether or not to sort the catalog. Can also be a [compareFunction](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort#parameters)
verbose: false,
// Display info about the parsing including some stats
failOnWarnings: false,
// Exit with an exit code of 1 on warnings
failOnUpdate: false,
// Exit with an exit code of 1 when translations are updated (for CI purpose)
customValueTemplate: null,
// If you wish to customize the value output the value as an object, you can set your own format.
// ${defaultValue} is the default value you set in your translation function.
// Any other custom property will be automatically extracted.
//
// Example:
// {
// message: "${defaultValue}",
// description: "${maxLength}", // t('my-key', {maxLength: 150})
// }
resetDefaultValueLocale: null,
// The locale to compare with default values to determine whether a default value has been changed.
// If this is set and a default value differs from a translation in the specified locale, all entries
// for that key across locales are reset to the default value, and existing translations are moved to
// the `_old` file.
i18nextOptions: null,
// If you wish to customize options in internally used i18next instance, you can define an object with any
// configuration property supported by i18next (https://www.i18next.com/overview/configuration-options).
// { compatibilityJSON: 'v3' } can be used to generate v3 compatible plurals.
yamlOptions: null,
// If you wish to customize options for yaml output, you can define an object here.
// Configuration options are here (https://github.com/nodeca/js-yaml#dump-object---options-).
// Example:
// {
// lineWidth: -1,
// }
}
+38 -34
View File
@@ -1,41 +1,44 @@
{
"name": "decky_frontend",
"version": "2.1.1",
"name": "@decky/loader-frontend",
"private": true,
"license": "GPLV2",
"type": "module",
"scripts": {
"prepare": "cd .. && husky install frontend/.husky",
"build": "rollup -c",
"watch": "rollup -c -w",
"lint": "prettier -c src",
"format": "prettier -c src -w"
"typecheck": "tsc --noEmit",
"format": "prettier -c src -w",
"localize": "i18next"
},
"devDependencies": {
"@rollup/plugin-commonjs": "^21.1.0",
"@rollup/plugin-image": "^3.0.2",
"@rollup/plugin-json": "^4.1.0",
"@rollup/plugin-node-resolve": "^13.3.0",
"@rollup/plugin-replace": "^4.0.0",
"@rollup/plugin-typescript": "^8.5.0",
"@types/react": "16.14.0",
"@types/react-file-icon": "^1.0.1",
"@types/react-router": "5.1.18",
"@types/webpack": "^5.28.1",
"husky": "^8.0.3",
"i18next-parser": "^8.0.0",
"@decky/api": "^1.1.1",
"@rollup/plugin-commonjs": "^26.0.1",
"@rollup/plugin-image": "^3.0.3",
"@rollup/plugin-json": "^6.1.0",
"@rollup/plugin-node-resolve": "^15.2.3",
"@rollup/plugin-replace": "^5.0.7",
"@rollup/plugin-typescript": "^11.1.6",
"@types/react": "18.3.3",
"@types/react-dom": "18.3.0",
"@types/react-file-icon": "^1.0.4",
"@types/react-router": "5.1.20",
"husky": "^9.0.11",
"i18next-parser": "^9.0.0",
"import-sort-style-module": "^6.0.0",
"inquirer": "^8.2.5",
"prettier": "^2.8.8",
"inquirer": "^9.2.23",
"prettier": "^3.3.2",
"prettier-plugin-import-sort": "^0.0.7",
"react": "16.14.0",
"react-dom": "16.14.0",
"rollup": "^2.79.1",
"react": "18.3.1",
"react-dom": "18.3.1",
"rollup": "^4.22.4",
"rollup-plugin-delete": "^2.0.0",
"rollup-plugin-external-globals": "^0.6.1",
"rollup-plugin-polyfill-node": "^0.10.2",
"rollup-plugin-visualizer": "^5.9.2",
"tslib": "^2.5.3",
"typescript": "^4.9.5"
"rollup-plugin-external-globals": "^0.10.0",
"rollup-plugin-polyfill-node": "^0.13.0",
"rollup-plugin-visualizer": "^5.12.0",
"tslib": "^2.6.3",
"typescript": "^5.4.5"
},
"importSort": {
".js, .jsx, .ts, .tsx": {
@@ -44,14 +47,15 @@
}
},
"dependencies": {
"decky-frontend-lib": "3.21.1",
"filesize": "^10.0.7",
"i18next": "^23.2.1",
"i18next-http-backend": "^2.2.1",
"react-file-icon": "^1.3.0",
"react-i18next": "^12.3.1",
"react-icons": "^4.9.0",
"react-markdown": "^8.0.7",
"remark-gfm": "^3.0.1"
"@decky/ui": "^4.8.3",
"compare-versions": "^6.1.1",
"filesize": "^10.1.2",
"i18next": "^23.11.5",
"i18next-http-backend": "^2.5.2",
"react-file-icon": "^1.5.0",
"react-i18next": "^14.1.2",
"react-icons": "^5.2.1",
"react-markdown": "^9.0.1",
"remark-gfm": "^4.0.0"
}
}
+3381 -3051
View File
File diff suppressed because it is too large Load Diff
+57 -36
View File
@@ -11,41 +11,62 @@ import { visualizer } from 'rollup-plugin-visualizer';
const hiddenWarnings = ['THIS_IS_UNDEFINED', 'EVAL'];
export default defineConfig({
input: 'src/index.ts',
plugins: [
del({ targets: '../backend/static/*', force: true }),
commonjs(),
nodeResolve({
browser: true,
}),
externalGlobals({
react: 'SP_REACT',
'react-dom': 'SP_REACTDOM',
// hack to shut up react-markdown
process: '{cwd: () => {}}',
path: '{dirname: () => {}, join: () => {}, basename: () => {}, extname: () => {}}',
url: '{fileURLToPath: (f) => f}',
}),
typescript(),
json(),
replace({
preventAssignment: false,
'process.env.NODE_ENV': JSON.stringify('production'),
}),
image(),
visualizer(),
],
preserveEntrySignatures: false,
output: {
dir: '../backend/static',
format: 'esm',
chunkFileNames: (chunkInfo) => {
return 'chunk-[hash].js';
export default defineConfig([
// Main bundle
{
input: 'src/index.ts',
plugins: [
del({ targets: ['../backend/decky_loader/static/*', '!../backend/decky_loader/static/fallback.js'], force: true }),
commonjs(),
nodeResolve({
browser: true,
}),
externalGlobals({
react: 'SP_REACT',
'react-dom': 'SP_REACTDOM',
// hack to shut up react-markdown
process: '{cwd: () => {}}',
path: '{dirname: () => {}, join: () => {}, basename: () => {}, extname: () => {}}',
url: '{fileURLToPath: (f) => f}',
}),
typescript(),
json(),
replace({
preventAssignment: false,
'process.env.NODE_ENV': JSON.stringify('production'),
}),
image(),
visualizer(),
],
preserveEntrySignatures: false,
treeshake: {
// Assume all external modules have imports with side effects (the default) while allowing decky libraries to treeshake
pureExternalImports: true,
preset: 'smallest'
},
output: {
dir: '../backend/decky_loader/static',
format: 'esm',
chunkFileNames: (chunkInfo) => {
return 'chunk-[hash].js';
},
sourcemap: true,
sourcemapPathTransform: (relativeSourcePath) => relativeSourcePath.replace(/^\.\.\//, `decky://decky/loader/`),
},
onwarn: function (message, handleWarning) {
if (hiddenWarnings.some((warning) => message.code === warning)) return;
handleWarning(message);
},
},
onwarn: function (message, handleWarning) {
if (hiddenWarnings.some((warning) => message.code === warning)) return;
handleWarning(message);
},
});
// Fallback
{
input: 'src/fallback.ts',
plugins: [
typescript()
],
output: {
file: '../backend/decky_loader/static/fallback.js',
format: 'esm',
}
}
]);
@@ -0,0 +1,198 @@
import { sleep } from '@decky/ui';
import { FunctionComponent, useEffect, useReducer, useState } from 'react';
import { uninstallPlugin } from '../plugin';
import { VerInfo, doRestart, doShutdown } from '../updater';
import { ValveReactErrorInfo, getLikelyErrorSourceFromValveReactError } from '../utils/errors';
interface DeckyErrorBoundaryProps {
error: ValveReactErrorInfo;
errorKey: string;
identifier: string;
reset: () => void;
}
declare global {
interface Window {
SystemNetworkStore?: any;
}
}
export const startSSH = DeckyBackend.callable('utilities/start_ssh');
export const starrCEFForwarding = DeckyBackend.callable('utilities/allow_remote_debugging');
function ipToString(ip: number) {
return [(ip >>> 24) & 255, (ip >>> 16) & 255, (ip >>> 8) & 255, (ip >>> 0) & 255].join('.');
}
// Intentionally not localized since we can't really trust React here
const DeckyErrorBoundary: FunctionComponent<DeckyErrorBoundaryProps> = ({ error, identifier, reset }) => {
const [actionLog, addLogLine] = useReducer((log: string, line: string) => (log += '\n' + line), '');
const [actionsEnabled, setActionsEnabled] = useState<boolean>(true);
const [debugAllowed, setDebugAllowed] = useState<boolean>(true);
// Intentionally doesn't use DeckyState.
const [versionInfo, setVersionInfo] = useState<VerInfo>();
const [errorSource, wasCausedByPlugin, shouldReportToValve] = getLikelyErrorSourceFromValveReactError(error);
useEffect(() => {
if (!shouldReportToValve) DeckyPluginLoader.errorBoundaryHook.temporarilyDisableReporting();
DeckyPluginLoader.updateVersion().then(setVersionInfo);
}, []);
return (
<>
<style>
{`
*:has(> .deckyErrorBoundary) {
overflow: scroll !important;
}
`}
</style>
<div
style={{
overflow: 'auto',
marginLeft: '15px',
color: 'white',
fontSize: '16px',
userSelect: 'auto',
backgroundColor: 'black',
marginTop: '48px', // Incase this is a page
}}
className="deckyErrorBoundary"
>
<h1
style={{
fontSize: '20px',
display: 'inline-block',
userSelect: 'auto',
}}
>
An error occured while rendering this content.
</h1>
<pre style={{}}>
<code>
{identifier && `Error Reference: ${identifier}`}
{versionInfo?.current && `\nDecky Version: ${versionInfo.current}`}
</code>
</pre>
<p>This error likely occured in {errorSource}.</p>
{actionLog?.length > 0 && (
<pre>
<code>
Running actions...
{actionLog}
</code>
</pre>
)}
{actionsEnabled && (
<>
<h3>Actions: </h3>
<p>Use the touch screen.</p>
<div style={{ display: 'block', marginBottom: '5px' }}>
<button style={{ marginRight: '5px', padding: '5px' }} onClick={reset}>
Retry
</button>
<button
style={{ marginRight: '5px', padding: '5px' }}
onClick={() => {
addLogLine('Restarting Steam...');
SteamClient.User.StartRestart(false);
}}
>
Restart Steam
</button>
</div>
<div style={{ display: 'block', marginBottom: '5px' }}>
<button
style={{ marginRight: '5px', padding: '5px' }}
onClick={async () => {
setActionsEnabled(false);
addLogLine('Restarting Decky...');
doRestart();
await sleep(2000);
addLogLine('Reloading UI...');
}}
>
Restart Decky
</button>
<button
style={{ marginRight: '5px', padding: '5px' }}
onClick={async () => {
setActionsEnabled(false);
addLogLine('Stopping Decky...');
doShutdown();
await sleep(5000);
addLogLine('Restarting Steam...');
SteamClient.User.StartRestart(false);
}}
>
Disable Decky until next boot
</button>
</div>
{debugAllowed && (
<div style={{ display: 'block', marginBottom: '5px' }}>
<button
style={{ marginRight: '5px', padding: '5px' }}
onClick={async () => {
setDebugAllowed(false);
addLogLine('Enabling CEF debugger forwarding...');
await starrCEFForwarding();
addLogLine('Enabling SSH...');
await startSSH();
addLogLine('Ready for debugging!');
if (window?.SystemNetworkStore?.wirelessNetworkDevice?.ip4?.addresses?.[0]?.ip) {
const ip = ipToString(window.SystemNetworkStore.wirelessNetworkDevice.ip4.addresses[0].ip);
addLogLine(`CEF Debugger: http://${ip}:8081`);
addLogLine(`SSH: deck@${ip}`);
}
}}
>
Allow remote debugging and SSH until next boot
</button>
</div>
)}
{wasCausedByPlugin && (
<div style={{ display: 'block', marginBottom: '5px' }}>
{'\n'}
<button
style={{ marginRight: '5px', padding: '5px' }}
onClick={async () => {
setActionsEnabled(false);
addLogLine(`Uninstalling ${errorSource}...`);
await uninstallPlugin(errorSource);
await DeckyPluginLoader.frozenPluginsService.invalidate();
await DeckyPluginLoader.hiddenPluginsService.invalidate();
await sleep(1000);
addLogLine('Restarting Decky...');
doRestart();
await sleep(2000);
addLogLine('Restarting Steam...');
await sleep(500);
SteamClient.User.StartRestart(false);
}}
>
Uninstall {errorSource} and restart Decky
</button>
</div>
)}
</>
)}
<pre
style={{
marginTop: '15px',
opacity: 0.7,
userSelect: 'auto',
}}
>
<code>
{error.error.stack}
{'\n\n'}
Component Stack:
{error.info.componentStack}
</code>
</pre>
</div>
</>
);
};
export default DeckyErrorBoundary;
@@ -1,4 +1,4 @@
import { FC, createContext, useContext, useEffect, useState } from 'react';
import { FC, ReactNode, createContext, useContext, useEffect, useState } from 'react';
interface PublicDeckyGlobalComponentsState {
components: Map<string, FC>;
@@ -40,6 +40,7 @@ export const useDeckyGlobalComponentsState = () => useContext(DeckyGlobalCompone
interface Props {
deckyGlobalComponentsState: DeckyGlobalComponentsState;
children: ReactNode;
}
export const DeckyGlobalComponentsStateContextProvider: FC<Props> = ({
+38 -36
View File
@@ -1,37 +1,39 @@
export default function DeckyIcon() {
return (
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 456" width="512" height="456">
<g>
<path
style={{ fill: 'none' }}
d="M154.33,72.51v49.79c11.78-0.17,23.48,2,34.42,6.39c10.93,4.39,20.89,10.91,29.28,19.18
c8.39,8.27,15.06,18.13,19.61,29c4.55,10.87,6.89,22.54,6.89,34.32c0,11.78-2.34,23.45-6.89,34.32
c-4.55,10.87-11.21,20.73-19.61,29c-8.39,8.27-18.35,14.79-29.28,19.18c-10.94,4.39-22.63,6.56-34.42,6.39v49.77
c36.78,0,72.05-14.61,98.05-40.62c26-26.01,40.61-61.28,40.61-98.05c0-36.78-14.61-72.05-40.61-98.05
C226.38,87.12,191.11,72.51,154.33,72.51z"
/>
import { FC, SVGAttributes } from 'react';
<ellipse
transform="matrix(0.982 -0.1891 0.1891 0.982 -37.1795 32.9988)"
style={{ fill: 'none' }}
cx="154.33"
cy="211.33"
rx="69.33"
ry="69.33"
/>
<path style={{ fill: 'none' }} d="M430,97h-52v187h52c7.18,0,13-5.82,13-13V110C443,102.82,437.18,97,430,97z" />
<path
style={{ fill: 'currentColor' }}
d="M432,27h-54V0H0v361c0,52.47,42.53,95,95,95h188c52.47,0,95-42.53,95-95v-7h54c44.18,0,80-35.82,80-80V107
C512,62.82,476.18,27,432,27z M85,211.33c0-38.29,31.04-69.33,69.33-69.33c38.29,0,69.33,31.04,69.33,69.33
c0,38.29-31.04,69.33-69.33,69.33C116.04,280.67,85,249.62,85,211.33z M252.39,309.23c-26.01,26-61.28,40.62-98.05,40.62v-49.77
c11.78,0.17,23.48-2,34.42-6.39c10.93-4.39,20.89-10.91,29.28-19.18c8.39-8.27,15.06-18.13,19.61-29
c4.55-10.87,6.89-22.53,6.89-34.32c0-11.78-2.34-23.45-6.89-34.32c-4.55-10.87-11.21-20.73-19.61-29
c-8.39-8.27-18.35-14.79-29.28-19.18c-10.94-4.39-22.63-6.56-34.42-6.39V72.51c36.78,0,72.05,14.61,98.05,40.61
c26,26.01,40.61,61.28,40.61,98.05C293,247.96,278.39,283.23,252.39,309.23z M443,271c0,7.18-5.82,13-13,13h-52V97h52
c7.18,0,13,5.82,13,13V271z"
/>
</g>
</svg>
);
}
const DeckyIcon: FC<SVGAttributes<SVGElement>> = (props) => (
<svg xmlns="http://www.w3.org/2000/svg" height="100%" width="100%" viewBox="0 0 512 456" {...props}>
<g>
<path
style={{ fill: 'none' }}
d="M154.33,72.51v49.79c11.78-0.17,23.48,2,34.42,6.39c10.93,4.39,20.89,10.91,29.28,19.18
c8.39,8.27,15.06,18.13,19.61,29c4.55,10.87,6.89,22.54,6.89,34.32c0,11.78-2.34,23.45-6.89,34.32
c-4.55,10.87-11.21,20.73-19.61,29c-8.39,8.27-18.35,14.79-29.28,19.18c-10.94,4.39-22.63,6.56-34.42,6.39v49.77
c36.78,0,72.05-14.61,98.05-40.62c26-26.01,40.61-61.28,40.61-98.05c0-36.78-14.61-72.05-40.61-98.05
C226.38,87.12,191.11,72.51,154.33,72.51z"
/>
<ellipse
transform="matrix(0.982 -0.1891 0.1891 0.982 -37.1795 32.9988)"
style={{ fill: 'none' }}
cx="154.33"
cy="211.33"
rx="69.33"
ry="69.33"
/>
<path style={{ fill: 'none' }} d="M430,97h-52v187h52c7.18,0,13-5.82,13-13V110C443,102.82,437.18,97,430,97z" />
<path
style={{ fill: 'currentColor' }}
d="M432,27h-54V0H0v361c0,52.47,42.53,95,95,95h188c52.47,0,95-42.53,95-95v-7h54c44.18,0,80-35.82,80-80V107
C512,62.82,476.18,27,432,27z M85,211.33c0-38.29,31.04-69.33,69.33-69.33c38.29,0,69.33,31.04,69.33,69.33
c0,38.29-31.04,69.33-69.33,69.33C116.04,280.67,85,249.62,85,211.33z M252.39,309.23c-26.01,26-61.28,40.62-98.05,40.62v-49.77
c11.78,0.17,23.48-2,34.42-6.39c10.93-4.39,20.89-10.91,29.28-19.18c8.39-8.27,15.06-18.13,19.61-29
c4.55-10.87,6.89-22.53,6.89-34.32c0-11.78-2.34-23.45-6.89-34.32c-4.55-10.87-11.21-20.73-19.61-29
c-8.39-8.27-18.35-14.79-29.28-19.18c-10.94-4.39-22.63-6.56-34.42-6.39V72.51c36.78,0,72.05,14.61,98.05,40.61
c26,26.01,40.61,61.28,40.61,98.05C293,247.96,278.39,283.23,252.39,309.23z M443,271c0,7.18-5.82,13-13,13h-52V97h52
c7.18,0,13,5.82,13,13V271z"
/>
</g>
</svg>
);
export default DeckyIcon;
+2 -1
View File
@@ -1,4 +1,4 @@
import { ComponentType, FC, createContext, useContext, useEffect, useState } from 'react';
import { ComponentType, FC, ReactNode, createContext, useContext, useEffect, useState } from 'react';
import type { RouteProps } from 'react-router';
export interface RouterEntry {
@@ -71,6 +71,7 @@ export const useDeckyRouterState = () => useContext(DeckyRouterStateContext);
interface Props {
deckyRouterState: DeckyRouterState;
children: ReactNode;
}
export const DeckyRouterStateContextProvider: FC<Props> = ({ children, deckyRouterState }) => {
+20 -3
View File
@@ -1,4 +1,4 @@
import { FC, createContext, useContext, useEffect, useState } from 'react';
import { FC, ReactNode, createContext, useContext, useEffect, useState } from 'react';
import { DEFAULT_NOTIFICATION_SETTINGS, NotificationSettings } from '../notification-service';
import { Plugin } from '../plugin';
@@ -8,6 +8,7 @@ import { VerInfo } from '../updater';
interface PublicDeckyState {
plugins: Plugin[];
pluginOrder: string[];
frozenPlugins: string[];
hiddenPlugins: string[];
activePlugin: Plugin | null;
updates: PluginUpdateMapping | null;
@@ -26,6 +27,7 @@ export interface UserInfo {
export class DeckyState {
private _plugins: Plugin[] = [];
private _pluginOrder: string[] = [];
private _frozenPlugins: string[] = [];
private _hiddenPlugins: string[] = [];
private _activePlugin: Plugin | null = null;
private _updates: PluginUpdateMapping | null = null;
@@ -41,6 +43,7 @@ export class DeckyState {
return {
plugins: this._plugins,
pluginOrder: this._pluginOrder,
frozenPlugins: this._frozenPlugins,
hiddenPlugins: this._hiddenPlugins,
activePlugin: this._activePlugin,
updates: this._updates,
@@ -67,6 +70,11 @@ export class DeckyState {
this.notifyUpdate();
}
setFrozenPlugins(frozenPlugins: string[]) {
this._frozenPlugins = frozenPlugins;
this.notifyUpdate();
}
setHiddenPlugins(hiddenPlugins: string[]) {
this._hiddenPlugins = hiddenPlugins;
this.notifyUpdate();
@@ -120,12 +128,21 @@ interface DeckyStateContext extends PublicDeckyState {
closeActivePlugin(): void;
}
const DeckyStateContext = createContext<DeckyStateContext>(null as any);
const DeckyStateContext = createContext<DeckyStateContext | null>(null);
export const useDeckyState = () => useContext(DeckyStateContext);
export const useDeckyState = () => {
const deckyState = useContext(DeckyStateContext);
if (deckyState === null) {
throw new Error('useDeckyState needs a parent DeckyStateContext');
}
return deckyState;
};
interface Props {
deckyState: DeckyState;
children?: ReactNode;
}
export const DeckyStateContextProvider: FC<Props> = ({ children, deckyState }) => {
-54
View File
@@ -1,54 +0,0 @@
import { ToastData, joinClassNames } from 'decky-frontend-lib';
import { FC, useEffect, useState } from 'react';
import { ReactElement } from 'react-markdown/lib/react-markdown';
import { useDeckyToasterState } from './DeckyToasterState';
import Toast, { toastClasses } from './Toast';
interface DeckyToasterProps {}
interface RenderedToast {
component: ReactElement;
data: ToastData;
}
const DeckyToaster: FC<DeckyToasterProps> = () => {
const { toasts, removeToast } = useDeckyToasterState();
const [renderedToast, setRenderedToast] = useState<RenderedToast | null>(null);
console.log(toasts);
if (toasts.size > 0) {
const [activeToast] = toasts;
if (!renderedToast || activeToast != renderedToast.data) {
// TODO play toast sound
console.log('rendering toast', activeToast);
setRenderedToast({ component: <Toast key={Math.random()} toast={activeToast} />, data: activeToast });
}
} else {
if (renderedToast) setRenderedToast(null);
}
useEffect(() => {
// not actually node but TS is shit
let interval: NodeJS.Timer | null;
if (renderedToast) {
interval = setTimeout(() => {
interval = null;
console.log('clear toast', renderedToast.data);
removeToast(renderedToast.data);
}, (renderedToast.data.duration || 5e3) + 1000);
console.log('set int', interval);
}
return () => {
if (interval) {
console.log('clearing int', interval);
clearTimeout(interval);
}
};
}, [renderedToast]);
return (
<div className={joinClassNames('deckyToaster', toastClasses.ToastPlaceholder)}>
{renderedToast && renderedToast.component}
</div>
);
};
export default DeckyToaster;
@@ -1,69 +0,0 @@
import { ToastData } from 'decky-frontend-lib';
import { FC, createContext, useContext, useEffect, useState } from 'react';
interface PublicDeckyToasterState {
toasts: Set<ToastData>;
}
export class DeckyToasterState {
// TODO a set would be better
private _toasts: Set<ToastData> = new Set();
public eventBus = new EventTarget();
publicState(): PublicDeckyToasterState {
return { toasts: this._toasts };
}
addToast(toast: ToastData) {
this._toasts.add(toast);
this.notifyUpdate();
}
removeToast(toast: ToastData) {
this._toasts.delete(toast);
this.notifyUpdate();
}
private notifyUpdate() {
this.eventBus.dispatchEvent(new Event('update'));
}
}
interface DeckyToasterContext extends PublicDeckyToasterState {
addToast(toast: ToastData): void;
removeToast(toast: ToastData): void;
}
const DeckyToasterContext = createContext<DeckyToasterContext>(null as any);
export const useDeckyToasterState = () => useContext(DeckyToasterContext);
interface Props {
deckyToasterState: DeckyToasterState;
}
export const DeckyToasterStateContextProvider: FC<Props> = ({ children, deckyToasterState }) => {
const [publicDeckyToasterState, setPublicDeckyToasterState] = useState<PublicDeckyToasterState>({
...deckyToasterState.publicState(),
});
useEffect(() => {
function onUpdate() {
setPublicDeckyToasterState({ ...deckyToasterState.publicState() });
}
deckyToasterState.eventBus.addEventListener('update', onUpdate);
return () => deckyToasterState.eventBus.removeEventListener('update', onUpdate);
}, []);
const addToast = deckyToasterState.addToast.bind(deckyToasterState);
const removeToast = deckyToasterState.removeToast.bind(deckyToasterState);
return (
<DeckyToasterContext.Provider value={{ ...publicDeckyToasterState, addToast, removeToast }}>
{children}
</DeckyToasterContext.Provider>
);
};
+16
View File
@@ -0,0 +1,16 @@
import { Navigation } from '@decky/ui';
import { AnchorHTMLAttributes, FC } from 'react';
const ExternalLink: FC<AnchorHTMLAttributes<HTMLAnchorElement>> = (props) => {
return (
<a
{...props}
onClick={(e) => {
e.preventDefault();
props.onClick ? props.onClick(e) : props.href && Navigation.NavigateToExternalWeb(props.href);
}}
/>
);
};
export default ExternalLink;
-11
View File
@@ -1,11 +0,0 @@
import { VFC } from 'react';
interface Props {
url: string;
}
const LegacyPlugin: VFC<Props> = ({ url }) => {
return <iframe style={{ border: 'none', width: '100%', height: '100%' }} src={url}></iframe>;
};
export default LegacyPlugin;
+3 -3
View File
@@ -1,4 +1,4 @@
import { Focusable, Navigation } from 'decky-frontend-lib';
import { Focusable, Navigation } from '@decky/ui';
import { FunctionComponent, useRef } from 'react';
import ReactMarkdown, { Options as ReactMarkdownOptions } from 'react-markdown';
import remarkGfm from 'remark-gfm';
@@ -13,8 +13,8 @@ const Markdown: FunctionComponent<MarkdownProps> = (props) => {
<ReactMarkdown
remarkPlugins={[remarkGfm]}
components={{
div: (nodeProps) => <Focusable {...nodeProps.node.properties}>{nodeProps.children}</Focusable>,
a: (nodeProps) => {
div: (nodeProps: any) => <Focusable {...nodeProps.node.properties}>{nodeProps.children}</Focusable>,
a: (nodeProps: any) => {
const aRef = useRef<HTMLAnchorElement>(null);
return (
// TODO fix focus ring
+29 -40
View File
@@ -1,46 +1,34 @@
import {
ButtonItem,
Focusable,
PanelSection,
PanelSectionRow,
joinClassNames,
scrollClasses,
staticClasses,
} from 'decky-frontend-lib';
import { VFC, useEffect, useState } from 'react';
import { ButtonItem, ErrorBoundary, Focusable, PanelSection, PanelSectionRow } from '@decky/ui';
import { FC, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { FaEyeSlash } from 'react-icons/fa';
import { Plugin } from '../plugin';
import { useDeckyState } from './DeckyState';
import NotificationBadge from './NotificationBadge';
import { useQuickAccessVisible } from './QuickAccessVisibleState';
import TitleView from './TitleView';
const PluginView: VFC = () => {
const { hiddenPlugins } = useDeckyState();
const { plugins, updates, activePlugin, pluginOrder, setActivePlugin, closeActivePlugin } = useDeckyState();
const PluginView: FC = () => {
const { plugins, hiddenPlugins, updates, activePlugin, pluginOrder, setActivePlugin, closeActivePlugin } =
useDeckyState();
const visible = useQuickAccessVisible();
const { t } = useTranslation();
const [pluginList, setPluginList] = useState<Plugin[]>(
plugins.sort((a, b) => pluginOrder.indexOf(a.name) - pluginOrder.indexOf(b.name)),
);
useEffect(() => {
setPluginList(plugins.sort((a, b) => pluginOrder.indexOf(a.name) - pluginOrder.indexOf(b.name)));
const pluginList = useMemo(() => {
console.log('updating PluginView after changes');
return [...plugins]
.sort((a, b) => pluginOrder.indexOf(a.name) - pluginOrder.indexOf(b.name))
.filter((p) => p.content)
.filter(({ name }) => !hiddenPlugins.includes(name));
}, [plugins, pluginOrder]);
if (activePlugin) {
return (
<Focusable onCancelButton={closeActivePlugin}>
<TitleView />
<div
className={joinClassNames(staticClasses.TabGroupPanel, scrollClasses.ScrollPanel, scrollClasses.ScrollY)}
style={{ height: '100%' }}
>
{(visible || activePlugin.alwaysRender) && activePlugin.content}
<div style={{ height: '100%', paddingTop: '16px' }}>
<ErrorBoundary>{(visible || activePlugin.alwaysRender) && activePlugin.content}</ErrorBoundary>
</div>
</Focusable>
);
@@ -48,22 +36,23 @@ const PluginView: VFC = () => {
return (
<>
<TitleView />
<div className={joinClassNames(staticClasses.TabGroupPanel, scrollClasses.ScrollPanel, scrollClasses.ScrollY)}>
<div
style={{
paddingTop: '16px',
}}
>
<PanelSection>
{pluginList
.filter((p) => p.content)
.filter(({ name }) => !hiddenPlugins.includes(name))
.map(({ name, icon }) => (
<PanelSectionRow key={name}>
<ButtonItem layout="below" onClick={() => setActivePlugin(name)}>
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
{icon}
<div>{name}</div>
<NotificationBadge show={updates?.has(name)} style={{ top: '-5px', right: '-5px' }} />
</div>
</ButtonItem>
</PanelSectionRow>
))}
{pluginList.map(({ name, icon }) => (
<PanelSectionRow key={name}>
<ButtonItem layout="below" onClick={() => setActivePlugin(name)}>
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
{icon}
<div>{name}</div>
<NotificationBadge show={updates?.has(name)} style={{ top: '-5px', right: '-5px' }} />
</div>
</ButtonItem>
</PanelSectionRow>
))}
{hiddenPlugins.length > 0 && (
<div style={{ display: 'flex', alignItems: 'center', gap: '10px', fontSize: '0.8rem', marginTop: '10px' }}>
<FaEyeSlash />
@@ -1,10 +1,10 @@
import { FC, createContext, useContext, useState } from 'react';
import { FC, ReactNode, createContext, useContext, useState } from 'react';
const QuickAccessVisibleState = createContext<boolean>(false);
export const useQuickAccessVisible = () => useContext(QuickAccessVisibleState);
export const QuickAccessVisibleStateProvider: FC<{ tab: any }> = ({ children, tab }) => {
export const QuickAccessVisibleStateProvider: FC<{ tab: any; children: ReactNode }> = ({ children, tab }) => {
const initial = tab.initialVisibility;
const [visible, setVisible] = useState<boolean>(initial);
// HACK but i can't think of a better way to do this
+9 -7
View File
@@ -1,5 +1,5 @@
import { DialogButton, Focusable, Router, staticClasses } from 'decky-frontend-lib';
import { CSSProperties, VFC } from 'react';
import { DialogButton, Focusable, Navigation, staticClasses } from '@decky/ui';
import { CSSProperties, FC } from 'react';
import { useTranslation } from 'react-i18next';
import { BsGearFill } from 'react-icons/bs';
import { FaArrowLeft, FaStore } from 'react-icons/fa';
@@ -10,20 +10,22 @@ const titleStyles: CSSProperties = {
display: 'flex',
paddingTop: '3px',
paddingRight: '16px',
position: 'sticky',
top: '0px',
};
const TitleView: VFC = () => {
const TitleView: FC = () => {
const { activePlugin, closeActivePlugin } = useDeckyState();
const { t } = useTranslation();
const onSettingsClick = () => {
Router.CloseSideMenus();
Router.Navigate('/decky/settings');
Navigation.Navigate('/decky/settings');
Navigation.CloseSideMenus();
};
const onStoreClick = () => {
Router.CloseSideMenus();
Router.Navigate('/decky/store');
Navigation.Navigate('/decky/store');
Navigation.CloseSideMenus();
};
if (activePlugin === null) {
+80 -23
View File
@@ -1,36 +1,38 @@
import { ToastData, findModule, joinClassNames } from 'decky-frontend-lib';
import { FunctionComponent } from 'react';
import type { ToastData } from '@decky/api';
import { Focusable, Navigation, findClassModule, joinClassNames } from '@decky/ui';
import { FC, memo } from 'react';
import Logger from '../logger';
const logger = new Logger('ToastRenderer');
// TODO there are more of these
export enum ToastLocation {
/** Big Picture popup toasts */
GAMEPADUI_POPUP = 1,
/** QAM Notifications tab */
GAMEPADUI_QAM = 3,
}
interface ToastProps {
toast: ToastData;
newIndicator?: boolean;
}
export const toastClasses = findModule((mod) => {
if (typeof mod !== 'object') return false;
interface ToastRendererProps extends ToastProps {
location: ToastLocation;
}
if (mod.ToastPlaceholder) {
return true;
}
const templateClasses = findClassModule((m) => m.ShortTemplate) || {};
return false;
});
// These are memoized as they like to randomly rerender
const templateClasses = findModule((mod) => {
if (typeof mod !== 'object') return false;
if (mod.ShortTemplate) {
return true;
}
return false;
});
const Toast: FunctionComponent<ToastProps> = ({ toast }) => {
const GamepadUIPopupToast: FC<Omit<ToastProps, 'newIndicator'>> = memo(({ toast }) => {
return (
<div
style={{ '--toast-duration': `${toast.duration}ms` } as React.CSSProperties}
onClick={toast.onClick}
className={joinClassNames(templateClasses.ShortTemplate, toast.className || '')}
className={joinClassNames(templateClasses.ShortTemplate, toast.className || '', 'DeckyGamepadUIPopupToast')}
>
{toast.logo && <div className={templateClasses.StandardLogoDimensions}>{toast.logo}</div>}
<div className={joinClassNames(templateClasses.Content, toast.contentClassName || '')}>
@@ -42,6 +44,61 @@ const Toast: FunctionComponent<ToastProps> = ({ toast }) => {
</div>
</div>
);
};
});
export default Toast;
const GamepadUIQAMToast: FC<ToastProps> = memo(({ toast, newIndicator }) => {
// The fields aren't mismatched, the logic for these is just a bit weird.
return (
<Focusable
onActivate={() => {
toast.onClick?.();
Navigation.CloseSideMenus();
}}
className={joinClassNames(
templateClasses.StandardTemplateContainer,
toast.className || '',
'DeckyGamepadUIQAMToast',
)}
>
<div className={templateClasses.StandardTemplate}>
{toast.logo && <div className={templateClasses.StandardLogoDimensions}>{toast.logo}</div>}
<div className={joinClassNames(templateClasses.Content, toast.contentClassName || '')}>
<div className={templateClasses.Header}>
{toast.icon && <div className={templateClasses.Icon}>{toast.icon}</div>}
{toast.title && <div className={templateClasses.Title}>{toast.title}</div>}
{/* timestamp should always be defined by toaster */}
{/* TODO check how valve does this */}
{toast.timestamp && (
<div className={templateClasses.Timestamp}>
{toast.timestamp.toLocaleTimeString(undefined, { timeStyle: 'short' })}
</div>
)}
</div>
{toast.body && <div className={templateClasses.StandardNotificationDescription}>{toast.body}</div>}
{toast.subtext && <div className={templateClasses.StandardNotificationSubText}>{toast.subtext}</div>}
</div>
{newIndicator && (
<div className={templateClasses.NewIndicator}>
<svg xmlns="http://www.w3.org/2000/svg" width="50" height="50" viewBox="0 0 50 50" fill="none">
<circle fill="currentColor" cx="25" cy="25" r="25"></circle>
</svg>
</div>
)}
</div>
</Focusable>
);
});
export const ToastRenderer: FC<ToastRendererProps> = memo(({ toast, location, newIndicator }) => {
switch (location) {
default:
logger.warn(`Toast UI not implemented for location ${location}! Falling back to GamepadUIQAMToast.`);
return <GamepadUIQAMToast toast={toast} newIndicator={false} />;
case ToastLocation.GAMEPADUI_POPUP:
return <GamepadUIPopupToast toast={toast} />;
case ToastLocation.GAMEPADUI_QAM:
return <GamepadUIQAMToast toast={toast} newIndicator={newIndicator} />;
}
});
export default ToastRenderer;
+2 -2
View File
@@ -1,4 +1,4 @@
import { Focusable, SteamSpinner } from 'decky-frontend-lib';
import { Focusable, SteamSpinner } from '@decky/ui';
import { FunctionComponent, ReactElement, ReactNode, Suspense } from 'react';
interface WithSuspenseProps {
@@ -26,7 +26,7 @@ const WithSuspense: FunctionComponent<WithSuspenseProps> = (props) => {
}),
}}
>
<SteamSpinner />
<SteamSpinner background="transparent" />
</Focusable>
}
>
@@ -5,21 +5,16 @@ import {
Marquee,
Menu,
MenuItem,
findModuleChild,
Module,
findModule,
showContextMenu,
} from 'decky-frontend-lib';
} from '@decky/ui';
import { FC, useCallback, useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { FaChevronDown } from 'react-icons/fa';
const dropDownControlButtonClass = findModuleChild((m) => {
if (typeof m !== 'object') return undefined;
for (const prop in m) {
if (m[prop]?.toString()?.includes('gamepaddropdown_DropDownControlButton')) {
return m[prop];
}
}
});
// TODO add to dfl
const dropDownControlButtonClasses = findModule((m: Module) => m?.DropDownControlButton && m?.['duration-app-launch']);
const DropdownMultiselectItem: FC<
{
@@ -62,7 +57,7 @@ const DropdownMultiselect: FC<{
const [itemsSelected, setItemsSelected] = useState<any>(selected);
const { t } = useTranslation();
const handleItemSelect = useCallback((checked, value) => {
const handleItemSelect = useCallback((checked: boolean, value: any) => {
setItemsSelected((x: any) =>
checked ? [...x.filter((y: any) => y !== value), value] : x.filter((y: any) => y !== value),
);
@@ -79,7 +74,7 @@ const DropdownMultiselect: FC<{
alignItems: 'center',
maxWidth: '100%',
}}
className={dropDownControlButtonClass}
className={dropDownControlButtonClasses?.DropDownControlButton}
onClick={(evt) => {
evt.preventDefault();
showContextMenu(
@@ -1,8 +1,9 @@
import { ConfirmModal, Navigation, QuickAccessTab } from 'decky-frontend-lib';
import { FC, useMemo, useState } from 'react';
import { ConfirmModal, Navigation, ProgressBarWithInfo, QuickAccessTab } from '@decky/ui';
import { FC, useEffect, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { FaCheck, FaDownload } from 'react-icons/fa';
import { InstallType } from '../../plugin';
import { InstallType, InstallTypeTranslationMapping } from '../../plugin';
interface MultiplePluginsInstallModalProps {
requests: { name: string; version: string; hash: string; install_type: InstallType }[];
@@ -11,13 +12,7 @@ interface MultiplePluginsInstallModalProps {
closeModal?(): void;
}
// values are the JSON keys used in the translation file
const InstallTypeTranslationMapping = {
[InstallType.INSTALL]: 'install',
[InstallType.REINSTALL]: 'reinstall',
[InstallType.UPDATE]: 'update',
} as const satisfies Record<InstallType, string>;
// IMPORTANT! Keep in sync with `t(...)` comments below
type TitleTranslationMapping = 'mixed' | (typeof InstallTypeTranslationMapping)[InstallType];
const MultiplePluginsInstallModal: FC<MultiplePluginsInstallModalProps> = ({
@@ -27,14 +22,50 @@ const MultiplePluginsInstallModal: FC<MultiplePluginsInstallModalProps> = ({
closeModal,
}) => {
const [loading, setLoading] = useState<boolean>(false);
const [percentage, setPercentage] = useState<number>(0);
const [pluginsCompleted, setPluginsCompleted] = useState<string[]>([]);
const [pluginInProgress, setInProgress] = useState<string | null>();
const [downloadInfo, setDownloadInfo] = useState<string | null>(null);
const { t } = useTranslation();
function updateDownloadState(percent: number, trans_text: string | undefined, trans_info: Record<string, string>) {
setPercentage(percent);
if (trans_text === undefined) {
setDownloadInfo(null);
} else {
setDownloadInfo(t(trans_text, trans_info));
}
}
function startDownload(name: string) {
setInProgress(name);
setPercentage(0);
}
function finishDownload(name: string) {
setPluginsCompleted((list) => [...list, name]);
}
useEffect(() => {
DeckyBackend.addEventListener('loader/plugin_download_info', updateDownloadState);
DeckyBackend.addEventListener('loader/plugin_download_start', startDownload);
DeckyBackend.addEventListener('loader/plugin_download_finish', finishDownload);
return () => {
DeckyBackend.removeEventListener('loader/plugin_download_info', updateDownloadState);
DeckyBackend.removeEventListener('loader/plugin_download_start', startDownload);
DeckyBackend.removeEventListener('loader/plugin_download_finish', finishDownload);
};
}, []);
// used as part of the title translation
// if we know all operations are of a specific type, we can show so in the title to make decision easier
const installTypeGrouped = useMemo((): TitleTranslationMapping => {
if (requests.every(({ install_type }) => install_type === InstallType.INSTALL)) return 'install';
if (requests.every(({ install_type }) => install_type === InstallType.REINSTALL)) return 'reinstall';
if (requests.every(({ install_type }) => install_type === InstallType.UPDATE)) return 'update';
if (requests.every(({ install_type }) => install_type === InstallType.DOWNGRADE)) return 'downgrade';
if (requests.every(({ install_type }) => install_type === InstallType.OVERWRITE)) return 'overwrite';
return 'mixed';
}, [requests]);
@@ -46,19 +77,40 @@ const MultiplePluginsInstallModal: FC<MultiplePluginsInstallModalProps> = ({
setLoading(true);
await onOK();
setTimeout(() => Navigation.OpenQuickAccessMenu(QuickAccessTab.Decky), 250);
setTimeout(() => window.DeckyPluginLoader.checkPluginUpdates(), 1000);
setTimeout(() => DeckyPluginLoader.checkPluginUpdates(), 1000);
}}
onCancel={async () => {
await onCancel();
}}
strTitle={<div>{t(`MultiplePluginsInstallModal.title.${installTypeGrouped}`, { count: requests.length })}</div>}
strOKButtonText={t(`MultiplePluginsInstallModal.ok_button.${loading ? 'loading' : 'idle'}`)}
strTitle={
<div>
{
// IMPORTANT! These comments are not cosmetic and are needed for `extracttext` task to work
// t('MultiplePluginsInstallModal.title.install', { count: n })
// t('MultiplePluginsInstallModal.title.reinstall', { count: n })
// t('MultiplePluginsInstallModal.title.update', { count: n })
// t('MultiplePluginsInstallModal.title.downgrade', { count: n })
// t('MultiplePluginsInstallModal.title.overwrite', { count: n })
// t('MultiplePluginsInstallModal.title.mixed', { count: n })
t(`MultiplePluginsInstallModal.title.${installTypeGrouped}`, { count: requests.length })
}
</div>
}
strOKButtonText={
loading ? t('MultiplePluginsInstallModal.ok_button.loading') : t('MultiplePluginsInstallModal.ok_button.idle')
}
>
<div>
{t('MultiplePluginsInstallModal.confirm')}
<ul style={{ listStyle: 'none', display: 'flex', flexDirection: 'column', gap: '4px' }}>
{requests.map(({ name, version, install_type, hash }, i) => {
const installTypeStr = InstallTypeTranslationMapping[install_type];
// IMPORTANT! These comments are not cosmetic and are needed for `extracttext` task to work
// t('MultiplePluginsInstallModal.description.install')
// t('MultiplePluginsInstallModal.description.reinstall')
// t('MultiplePluginsInstallModal.description.update')
// t('MultiplePluginsInstallModal.description.downgrade')
// t('MultiplePluginsInstallModal.description.overwrite')
const description = t(`MultiplePluginsInstallModal.description.${installTypeStr}`, {
name,
version,
@@ -66,7 +118,10 @@ const MultiplePluginsInstallModal: FC<MultiplePluginsInstallModalProps> = ({
return (
<li key={i} style={{ display: 'flex', flexDirection: 'column' }}>
<div>{description}</div>
<span>
{description}{' '}
{(pluginsCompleted.includes(name) && <FaCheck />) || (name === pluginInProgress && <FaDownload />)}
</span>
{hash === 'False' && (
<div style={{ color: 'red', paddingLeft: '10px' }}>{t('PluginInstallModal.no_hash')}</div>
)}
@@ -74,6 +129,17 @@ const MultiplePluginsInstallModal: FC<MultiplePluginsInstallModalProps> = ({
);
})}
</ul>
{/* TODO: center the progress bar and make it 80% width */}
{loading && (
<ProgressBarWithInfo
// when the key changes, react considers this a new component so resets the progress without the smoothing animation
key={pluginInProgress}
bottomSeparator="none"
focusable={false}
nProgress={percentage}
sOperationText={downloadInfo}
/>
)}
</div>
</ConfirmModal>
);
@@ -1,14 +1,14 @@
import { ConfirmModal, Navigation, QuickAccessTab } from 'decky-frontend-lib';
import { FC, useState } from 'react';
import { ConfirmModal, Navigation, ProgressBarWithInfo, QuickAccessTab } from '@decky/ui';
import { FC, useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import TranslationHelper, { TranslationClass } from '../../utils/TranslationHelper';
import { InstallType, InstallTypeTranslationMapping } from '../../plugin';
interface PluginInstallModalProps {
artifact: string;
version: string;
hash: string;
installType: number;
installType: InstallType;
onOK(): void;
onCancel(): void;
closeModal?(): void;
@@ -24,8 +24,28 @@ const PluginInstallModal: FC<PluginInstallModalProps> = ({
closeModal,
}) => {
const [loading, setLoading] = useState<boolean>(false);
const [percentage, setPercentage] = useState<number>(0);
const [downloadInfo, setDownloadInfo] = useState<string | null>(null);
const { t } = useTranslation();
function updateDownloadState(percent: number, trans_text: string | undefined, trans_info: Record<string, string>) {
setPercentage(percent);
if (trans_text === undefined) {
setDownloadInfo(null);
} else {
setDownloadInfo(t(trans_text, trans_info));
}
}
useEffect(() => {
DeckyBackend.addEventListener('loader/plugin_download_info', updateDownloadState);
return () => {
DeckyBackend.removeEventListener('loader/plugin_download_info', updateDownloadState);
};
}, []);
const installTypeTranslationKey = InstallTypeTranslationMapping[installType];
return (
<ConfirmModal
bOKDisabled={loading}
@@ -34,51 +54,75 @@ const PluginInstallModal: FC<PluginInstallModalProps> = ({
setLoading(true);
await onOK();
setTimeout(() => Navigation.OpenQuickAccessMenu(QuickAccessTab.Decky), 250);
setTimeout(() => window.DeckyPluginLoader.checkPluginUpdates(), 1000);
setTimeout(() => DeckyPluginLoader.checkPluginUpdates(), 1000);
}}
onCancel={async () => {
await onCancel();
}}
strTitle={
<div>
<TranslationHelper
trans_class={TranslationClass.PLUGIN_INSTALL_MODAL}
trans_text="title"
i18n_args={{ artifact: artifact }}
install_type={installType}
/>
<div style={{ display: 'flex', flexDirection: 'row', alignItems: 'center', width: '100%' }}>
{
// IMPORTANT! These comments are not cosmetic and are needed for `extracttext` task to work
// t('PluginInstallModal.install.title')
// t('PluginInstallModal.reinstall.title')
// t('PluginInstallModal.update.title')
// t('PluginInstallModal.downgrade.title')
// t('PluginInstallModal.overwrite.title')
t(`PluginInstallModal.${installTypeTranslationKey}.title`, { artifact: artifact })
}
{loading && (
<div style={{ marginLeft: 'auto' }}>
<ProgressBarWithInfo
layout="inline"
bottomSeparator="none"
nProgress={percentage}
sOperationText={downloadInfo}
/>
</div>
)}
</div>
}
strOKButtonText={
loading ? (
<div>
<TranslationHelper
trans_class={TranslationClass.PLUGIN_INSTALL_MODAL}
trans_text="button_processing"
install_type={installType}
/>
{
// IMPORTANT! These comments are not cosmetic and are needed for `extracttext` task to work
// t('PluginInstallModal.install.button_processing')
// t('PluginInstallModal.reinstall.button_processing')
// t('PluginInstallModal.update.button_processing')
// t('PluginInstallModal.downgrade.button_processing')
// t('PluginInstallModal.overwrite.button_processing')
t(`PluginInstallModal.${installTypeTranslationKey}.button_processing`)
}
</div>
) : (
<div>
<TranslationHelper
trans_class={TranslationClass.PLUGIN_INSTALL_MODAL}
trans_text="button_idle"
install_type={installType}
/>
{
// IMPORTANT! These comments are not cosmetic and are needed for `extracttext` task to work
// t('PluginInstallModal.install.button_idle')
// t('PluginInstallModal.reinstall.button_idle')
// t('PluginInstallModal.update.button_idle')
// t('PluginInstallModal.downgrade.button_idle')
// t('PluginInstallModal.overwrite.button_idle')
t(`PluginInstallModal.${installTypeTranslationKey}.button_idle`)
}
</div>
)
}
>
<div>
<TranslationHelper
trans_class={TranslationClass.PLUGIN_INSTALL_MODAL}
trans_text="desc"
i18n_args={{
{
// IMPORTANT! These comments are not cosmetic and are needed for `extracttext` task to work
// t('PluginInstallModal.install.desc')
// t('PluginInstallModal.reinstall.desc')
// t('PluginInstallModal.update.desc')
// t('PluginInstallModal.downgrade.desc')
// t('PluginInstallModal.overwrite.desc')
t(`PluginInstallModal.${installTypeTranslationKey}.desc`, {
artifact: artifact,
version: version,
}}
install_type={installType}
/>
})
}
</div>
{hash == 'False' && <span style={{ color: 'red' }}>{t('PluginInstallModal.no_hash')}</span>}
</ConfirmModal>
@@ -1,5 +1,7 @@
import { ConfirmModal } from 'decky-frontend-lib';
import { FC } from 'react';
import { ConfirmModal, Spinner } from '@decky/ui';
import { FC, useState } from 'react';
import { uninstallPlugin } from '../../plugin';
interface PluginUninstallModalProps {
name: string;
@@ -10,16 +12,27 @@ interface PluginUninstallModalProps {
}
const PluginUninstallModal: FC<PluginUninstallModalProps> = ({ name, title, buttonText, description, closeModal }) => {
const [uninstalling, setUninstalling] = useState<boolean>(false);
return (
<ConfirmModal
closeModal={closeModal}
onOK={async () => {
await window.DeckyPluginLoader.callServerMethod('uninstall_plugin', { name });
setUninstalling(true);
await uninstallPlugin(name);
// uninstalling a plugin resets the hidden setting for it server-side
// we invalidate here so if you re-install it, you won't have an out-of-date hidden filter
await window.DeckyPluginLoader.hiddenPluginsService.invalidate();
await DeckyPluginLoader.frozenPluginsService.invalidate();
await DeckyPluginLoader.hiddenPluginsService.invalidate();
closeModal?.();
}}
strTitle={title}
bOKDisabled={uninstalling}
bCancelDisabled={uninstalling}
strTitle={
<div style={{ display: 'flex', flexDirection: 'row', alignItems: 'center', width: '100%' }}>
{title}
{uninstalling && <Spinner width="24px" height="24px" style={{ marginLeft: 'auto' }} />}
</div>
}
strOKButtonText={buttonText}
>
{description}
@@ -10,7 +10,7 @@ import {
SteamSpinner,
TextField,
ToggleField,
} from 'decky-frontend-lib';
} from '@decky/ui';
import { filesize } from 'filesize';
import { FunctionComponent, useCallback, useEffect, useMemo, useState } from 'react';
import { DefaultExtensionType, FileIcon, defaultStyles } from 'react-file-icon';
@@ -95,29 +95,20 @@ const sortOptions = [
},
];
function getList(
path: string,
includeFiles: boolean,
includeFolders: boolean = true,
includeExt: string[] | null = null,
includeHidden: boolean = false,
orderBy: SortOptions = SortOptions.name_desc,
filterFor: RegExp | ((file: File) => boolean) | null = null,
pageNumber: number = 1,
max: number = 1000,
): Promise<{ result: FileListing | string; success: boolean }> {
return window.DeckyPluginLoader.callServerMethod('filepicker_ls', {
path,
include_files: includeFiles,
include_folders: includeFolders,
include_ext: includeExt ? includeExt : [],
include_hidden: includeHidden,
order_by: orderBy,
filter_for: filterFor,
page: pageNumber,
max: max,
});
}
const getList = DeckyBackend.callable<
[
path: string,
includeFiles?: boolean,
includeFolders?: boolean,
includeExt?: string[] | null,
includeHidden?: boolean,
orderBy?: SortOptions,
filterFor?: RegExp | ((file: File) => boolean) | null,
pageNumber?: number,
max?: number,
],
FileListing
>('utilities/filepicker_ls');
const iconStyles = {
paddingRight: '10px',
@@ -126,20 +117,20 @@ const iconStyles = {
const FilePicker: FunctionComponent<FilePickerProps> = ({
startPath,
//What are we allowing to show in the file picker
// What are we allowing to show in the file picker
includeFiles = true,
includeFolders = true,
//Parameter for specifying a specific filename match
// Parameter for specifying a specific filename match
filter = undefined,
//Filter for specific extensions as an array
// Filter for specific extensions as an array
validFileExtensions = undefined,
//Allow to override the fixed extension above
// Allow to override the fixed extension above
allowAllFiles = true,
//If we need to show hidden files and folders (both Win and Linux should work)
// If we need to show hidden files and folders (both Win and Linux should work)
defaultHidden = false, // false by default makes sense for most users
//How much files per page to show, default 1000
// How many files per page to show, default 1000
max = 1000,
//Which picking option to select by default
// Which picking option to select by default
fileSelType = FileSelectionType.FOLDER,
onSubmit,
closeModal,
@@ -190,21 +181,27 @@ const FilePicker: FunctionComponent<FilePickerProps> = ({
useEffect(() => {
(async () => {
setLoading(true);
const listing = await getList(
path,
includeFiles,
includeFolders,
selectedExts,
showHidden,
sort,
filter,
page,
max,
);
if (!listing.success) {
try {
const listing = await getList(
path,
includeFiles,
includeFolders,
selectedExts,
showHidden,
sort,
filter,
page,
max,
);
setRawError(null);
setError(FileErrorTypes.None);
setFiles(listing.files);
setLoading(false);
setListing(listing);
logger.log('reloaded', path, listing);
} catch (theError: any) {
setListing({ files: [], realpath: path, total: 0 });
setLoading(false);
const theError = listing.result as string;
switch (theError) {
case theError.match(/\[Errno\s2.*/i)?.input:
case theError.match(/\[WinError\s3.*/i)?.input:
@@ -220,14 +217,7 @@ const FilePicker: FunctionComponent<FilePickerProps> = ({
}
logger.debug(theError);
return;
} else {
setRawError(null);
setError(FileErrorTypes.None);
setFiles((listing.result as FileListing).files);
}
setLoading(false);
setListing(listing.result as FileListing);
logger.log('reloaded', path, listing);
})();
}, [error, path, includeFiles, includeFolders, showHidden, sort, selectedExts, page]);

Some files were not shown because too many files have changed in this diff Show More