Compare commits

...

12 Commits

Author SHA1 Message Date
AAGaming bc146b0349 typescript stinks 2025-10-15 00:22:54 -04:00
AAGaming 0c56f1b689 react 19 support 2025-10-15 00:14:50 -04:00
AAGaming 86b5567d4e dfl bump to fix DialogHeader component (#800)
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-08-20 15:46:17 -04:00
AAGaming 8f41eb93ef Merge commit from fork
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 incorrect permissions on plugin directories

* chown plugin dirs too

* fix the stupid

* cleanup useless comments
2025-07-28 20:58:59 -04:00
AAGaming 670ae7d8a7 OOPS
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-07-13 02:42:16 -04:00
AAGaming 01ec1663bc Merge branch 'main' of github.com:SteamDeckHomebrew/decky-loader 2025-07-13 02:37:34 -04:00
AAGaming b840d75cb8 certified pnpm moment 2025-07-13 02:37:03 -04:00
AAGaming e22ba20d13 Fix ModalRoot component on beta (#792) 2025-07-13 02:33:22 -04:00
AAGaming bff410d98b chore(readme): update to reflect dumb new pnpm change 2025-07-13 02:26:51 -04:00
AAGaming 7188db9877 feat(dev): make deckdebug.sh configurable
so i can use it on windows :3
2025-07-13 02:20:42 -04:00
AAGaming 02e8640ad4 bump @decky/ui to fix ConfirmModal component (#789)
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: shadow <81448108+shdwmtr@users.noreply.github.com>
2025-07-08 23:30:47 -04:00
AAGaming e63983dba9 Fix updater reload freezing (#786)
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-07-01 15:24:52 -04:00
30 changed files with 242 additions and 205 deletions
+1 -1
View File
@@ -41,7 +41,7 @@ jobs:
working-directory: ./frontend
run: |
npm i -g pnpm
pnpm i --frozen-lockfile
pnpm i --frozen-lockfile --dangerously-allow-all-builds
- name: Build JS Frontend 🛠️
working-directory: ./frontend
+1 -1
View File
@@ -43,7 +43,7 @@ jobs:
working-directory: ./frontend
run: |
npm i -g pnpm
pnpm i --frozen-lockfile
pnpm i --frozen-lockfile --dangerously-allow-all-builds
- name: Build JS Frontend 🛠️
working-directory: ./frontend
+1 -1
View File
@@ -16,7 +16,7 @@ jobs:
working-directory: frontend
run: |
npm i -g pnpm
pnpm i --frozen-lockfile
pnpm i --frozen-lockfile --dangerously-allow-all-builds
- name: Run prettier (TypeScript)
working-directory: frontend
+1 -1
View File
@@ -35,7 +35,7 @@ jobs:
working-directory: frontend
run: |
npm i -g pnpm
pnpm i --frozen-lockfile
pnpm i --frozen-lockfile --dangerously-allow-all-builds
- name: Run pyright (Python)
uses: jakebailey/pyright-action@v1
+1 -1
View File
@@ -95,7 +95,7 @@ Please consult [the wiki page regarding development](https://wiki.deckbrew.xyz/e
1. In your clone of the repository, run these commands.
```bash
cd frontend
pnpm i
pnpm i # NOTE: you may need to approve esbuild's build script with pnpm approve-builds
pnpm run build
```
1. If you are modifying the UI, these commands will need to be run before deploying the changes to your Steam Deck.
+25 -10
View File
@@ -18,9 +18,10 @@ from enum import IntEnum
from typing import Dict, List, TypedDict
# Local modules
from .localplatform.localplatform import chown, chmod
from .localplatform.localplatform import chown, chmod, get_chown_plugin_path
from .loader import Loader, Plugins
from .helpers import get_ssl_context, download_remote_binary_to_path
from .enums import UserType
from .settings import SettingsManager
logger = getLogger("Browser")
@@ -60,13 +61,6 @@ class PluginBrowser:
return False
zip_file = ZipFile(zip)
zip_file.extractall(self.plugin_path)
plugin_folder = self.find_plugin_folder(name)
assert plugin_folder is not None
plugin_dir = path.join(self.plugin_path, plugin_folder)
if not chown(plugin_dir) or not chmod(plugin_dir, 555):
logger.error(f"chown/chmod exited with a non-zero exit code")
return False
return True
async def _download_remote_binaries_for_plugin_with_name(self, pluginBasePath: str):
@@ -101,8 +95,6 @@ class PluginBrowser:
rv = False
raise Exception(f"Error Downloading Remote Binary {binName}@{binURL} with hash {binHash} to {path.join(pluginBinPath, binName)}")
chown(self.plugin_path)
chmod(pluginBasePath, 555)
else:
rv = True
logger.info(f"No Remote Binaries to Download")
@@ -124,6 +116,25 @@ class PluginBrowser:
return folder
except:
logger.debug(f"skipping {folder}")
def set_plugin_dir_permissions(self, plugin_dir: str) -> bool:
plugin_json_path = path.join(plugin_dir, 'plugin.json')
logger.debug(f"Checking plugin.json at {plugin_json_path}")
root_plugin = False
if access(plugin_json_path, R_OK):
with open(plugin_json_path, "r", encoding="utf-8") as f:
plugin_json = json.load(f)
if "flags" in plugin_json and "root" in plugin_json["flags"]:
root_plugin = True
logger.debug("root_plugin %d, dir %s", root_plugin, plugin_dir)
if get_chown_plugin_path():
return chown(plugin_dir, UserType.EFFECTIVE_USER if root_plugin else UserType.HOST_USER, True) and chown(plugin_dir, UserType.EFFECTIVE_USER, False) and chmod(plugin_dir, 755) and chown(plugin_json_path, UserType.EFFECTIVE_USER, False) and chmod(plugin_json_path, 755)
else:
logger.debug("chown disabled by environment")
return True
async def uninstall_plugin(self, name: str):
if self.loader.watcher:
@@ -266,6 +277,7 @@ class PluginBrowser:
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)
chown_ret = self.set_plugin_dir_permissions(plugin_dir)
if ret:
logger.info(f"Installed {name} (Version: {version})")
if name in self.loader.plugins:
@@ -278,6 +290,9 @@ class PluginBrowser:
self.settings.setSetting("pluginOrder", current_plugin_order)
logger.debug("Plugin %s was added to the pluginOrder setting", name)
await self.loader.import_plugin(path.join(plugin_dir, "main.py"), plugin_folder)
elif not chown_ret:
logger.error("Could not chown plugin")
return
else:
logger.error("Could not download remote binaries")
return
+2 -3
View File
@@ -1,9 +1,8 @@
from enum import IntEnum
class UserType(IntEnum):
HOST_USER = 1
EFFECTIVE_USER = 2
ROOT = 3
HOST_USER = 1 # usually deck
EFFECTIVE_USER = 2 # usually root
class PluginLoadType(IntEnum):
LEGACY_EVAL_IIFE = 0 # legacy, uses legacy serverAPI
+2 -1
View File
@@ -181,7 +181,8 @@ def get_user_group_id() -> int:
# Get the default home path unless a user is specified
def get_home_path(username: str | None = None) -> str:
return localplatform.get_home_path(UserType.ROOT if username == "root" else UserType.HOST_USER)
# TODO hardcoded root is kinda a hack
return localplatform.get_home_path(UserType.EFFECTIVE_USER if username == "root" else UserType.HOST_USER)
async def is_systemd_unit_active(unit_name: str) -> bool:
return await localplatform.service_active(unit_name)
@@ -11,7 +11,7 @@ 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]:
async def run(args: list[str], stdin: ProcessIO = DEVNULL, stdout: ProcessIO = PIPE, stderr: ProcessIO = PIPE, env: ENV | None = {"LD_LIBRARY_PATH": ""}) -> tuple[Process, bytes | None, bytes | None]:
proc = await create_subprocess_exec(args[0], *(args[1:]), stdin=stdin, stdout=stdout, stderr=stderr, env=env)
proc_stdout, proc_stderr = await proc.communicate()
return (proc, proc_stdout, proc_stderr)
@@ -59,8 +59,6 @@ def chown(path : str, user : UserType = UserType.HOST_USER, recursive : bool =
user_str = _get_user()+":"+_get_user_group()
elif user == UserType.EFFECTIVE_USER:
user_str = _get_effective_user()+":"+_get_effective_user_group()
elif user == UserType.ROOT:
user_str = "root:root"
else:
raise Exception("Unknown User Type")
@@ -87,7 +85,7 @@ def chmod(path : str, permissions : int, recursive : bool = True) -> bool:
return True
def folder_owner(path : str) -> UserType|None:
def file_owner(path : str) -> UserType|None:
user_owner = _get_user_owner(path)
if (user_owner == _get_user()):
@@ -106,13 +104,14 @@ def get_home_path(user : UserType = UserType.HOST_USER) -> str:
user_name = _get_user()
elif user == UserType.EFFECTIVE_USER:
user_name = _get_effective_user()
elif user == UserType.ROOT:
pass
else:
raise Exception("Unknown User Type")
return pwd.getpwnam(user_name).pw_dir
def get_effective_username() -> str:
return _get_effective_user()
def get_username() -> str:
return _get_user()
@@ -121,8 +120,8 @@ def setgid(user : UserType = UserType.HOST_USER):
if user == UserType.HOST_USER:
user_id = _get_user_group_id()
elif user == UserType.ROOT:
pass
elif user == UserType.EFFECTIVE_USER:
pass # we already are
else:
raise Exception("Unknown user type")
@@ -133,8 +132,8 @@ def setuid(user : UserType = UserType.HOST_USER):
if user == UserType.HOST_USER:
user_id = _get_user_id()
elif user == UserType.ROOT:
pass
elif user == UserType.EFFECTIVE_USER:
pass # we already are
else:
raise Exception("Unknown user type")
@@ -146,6 +145,7 @@ async def service_active(service_name : str) -> bool:
async def service_restart(service_name : str, block : bool = True) -> bool:
await run(["systemctl", "daemon-reload"])
logger.info("Systemd reload done.")
cmd = ["systemctl", "restart", service_name]
if not block:
@@ -272,7 +272,7 @@ async def close_cef_socket():
logger.info(f"Closing CEF socket with PID {pid} and FD {fd}")
# Use gdb to inject a close() call for the socket fd into steamwebhelper
gdb_ret, _, _ = await run(["gdb", "--nx", "-p", pid, "--batch", "--eval-command", f"call (int)close({fd})"], env={"LD_LIBRARY_PATH": ""})
gdb_ret, _, _ = await run(["gdb", "--nx", "-p", pid, "--batch", "--eval-command", f"call (int)close({fd})"])
if gdb_ret.returncode != 0:
logger.error(f"Failed to close CEF socket with gdb! return code: {str(gdb_ret.returncode)}", exc_info=True)
@@ -7,7 +7,7 @@ def chown(path : str, user : UserType = UserType.HOST_USER, recursive : bool =
def chmod(path : str, permissions : int, recursive : bool = True) -> bool:
return True # Stubbed
def folder_owner(path : str) -> UserType|None:
def file_owner(path : str) -> UserType|None:
return UserType.HOST_USER # Stubbed
def get_home_path(user : UserType = UserType.HOST_USER) -> str:
@@ -34,6 +34,9 @@ async def service_restart(service_name : str, block : bool = True) -> bool:
return True # Stubbed
def get_effective_username() -> str:
return os.getlogin()
def get_username() -> str:
return os.getlogin()
+1 -1
View File
@@ -50,7 +50,7 @@ def chown_plugin_dir():
if not path.exists(plugin_path): # For safety, create the folder before attempting to do anything with it
mkdir_as_user(plugin_path)
if not chown(plugin_path, UserType.HOST_USER) or not chmod(plugin_path, 555):
if not chown(plugin_path, UserType.EFFECTIVE_USER, False) or not chmod(plugin_path, 755, False):
logger.error(f"chown/chmod exited with a non-zero exit code")
if get_chown_plugin_path() == True:
+19 -4
View File
@@ -8,7 +8,8 @@ from traceback import format_exc
from .sandboxed_plugin import SandboxedPlugin
from .messages import MethodCallRequest, SocketMessageType
from ..enums import PluginLoadType
from ..enums import PluginLoadType, UserType
from ..localplatform.localplatform import file_owner, chown, chmod, get_chown_plugin_path
from ..localplatform.localsocket import LocalSocket
from ..helpers import get_homebrew_path, mkdir_as_user
@@ -26,9 +27,12 @@ class PluginWrapper:
self.load_type = PluginLoadType.LEGACY_EVAL_IIFE.value
json = load(open(path.join(plugin_path, plugin_directory, "plugin.json"), "r", encoding="utf-8"))
if path.isfile(path.join(plugin_path, plugin_directory, "package.json")):
package_json = load(open(path.join(plugin_path, plugin_directory, "package.json"), "r", encoding="utf-8"))
plugin_dir_path = path.join(plugin_path, plugin_directory)
plugin_json_path = path.join(plugin_dir_path, "plugin.json")
json = load(open(plugin_json_path, "r", encoding="utf-8"))
if path.isfile(path.join(plugin_dir_path, "package.json")):
package_json = load(open(path.join(plugin_dir_path, "package.json"), "r", encoding="utf-8"))
self.version = package_json["version"]
if ("type" in package_json and package_json["type"] == "module"):
self.load_type = PluginLoadType.ESMODULE_V1.value
@@ -42,6 +46,17 @@ class PluginWrapper:
self.log = getLogger("plugin")
if get_chown_plugin_path():
# ensure plugin folder ownership
if file_owner(plugin_dir_path) != UserType.EFFECTIVE_USER:
chown(plugin_dir_path, UserType.EFFECTIVE_USER if "root" in self.flags else UserType.HOST_USER, True)
chown(plugin_dir_path, UserType.EFFECTIVE_USER, False)
chmod(plugin_dir_path, 755, True)
# fix plugin.json permissions
if file_owner(plugin_json_path) != UserType.EFFECTIVE_USER:
chown(plugin_json_path, UserType.EFFECTIVE_USER, False)
chmod(plugin_json_path, 755, False)
self.sandboxed_plugin = SandboxedPlugin(self.name, self.passive, self.flags, self.file, self.plugin_directory, self.plugin_path, self.version, self.author, self.api_version)
self.proc: Process | None = None
self._socket = LocalSocket()
@@ -13,7 +13,7 @@ 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 .. import helpers
from typing import List, TypeVar, Any
@@ -61,10 +61,10 @@ class SandboxedPlugin:
if self.passive:
return
setgid(UserType.ROOT if "root" in self.flags else UserType.HOST_USER)
setuid(UserType.ROOT if "root" in self.flags else UserType.HOST_USER)
setgid(UserType.EFFECTIVE_USER if "root" in self.flags else UserType.HOST_USER)
setuid(UserType.EFFECTIVE_USER if "root" in self.flags else UserType.HOST_USER)
# export a bunch of environment variables to help plugin developers
environ["HOME"] = get_home_path(UserType.ROOT if "root" in self.flags else UserType.HOST_USER)
environ["HOME"] = get_home_path(UserType.EFFECTIVE_USER if "root" in self.flags else UserType.HOST_USER)
environ["USER"] = "root" if "root" in self.flags else get_username()
environ["DECKY_VERSION"] = helpers.get_loader_version()
environ["DECKY_USER"] = get_username()
+3 -3
View File
@@ -1,7 +1,7 @@
from json import dump, load
from os import mkdir, path, listdir, rename
from typing import Any, Dict
from .localplatform.localplatform import chown, folder_owner, get_chown_plugin_path
from .localplatform.localplatform import chown, file_owner, get_chown_plugin_path
from .enums import UserType
from .helpers import get_homebrew_path
@@ -28,8 +28,8 @@ class SettingsManager:
#If the owner of the settings directory is not the user, then set it as the user:
expected_user = UserType.HOST_USER if get_chown_plugin_path() else UserType.ROOT
if folder_owner(settings_directory) != expected_user:
expected_user = UserType.HOST_USER if get_chown_plugin_path() else UserType.EFFECTIVE_USER
if file_owner(settings_directory) != expected_user:
chown(settings_directory, expected_user, False)
self.settings: Dict[str, Any] = {}
+1 -4
View File
@@ -14,7 +14,6 @@ import zipfile
from aiohttp import ClientSession
from . import helpers
from .injector import get_gamepadui_tab
from .settings import SettingsManager
if TYPE_CHECKING:
from .main import PluginManager
@@ -142,8 +141,6 @@ class Updater:
async def download_decky_binary(self, download_url: str, version: str, is_zip: bool = False, size_in_bytes: int | None = None):
download_filename = "PluginLoader" if ON_LINUX else "PluginLoader.exe"
download_temp_filename = download_filename + ".new"
tab = await get_gamepadui_tab()
await tab.open_websocket()
if size_in_bytes == None:
size_in_bytes = 26214400 # 25MiB, a reasonable overestimate (19.6MiB as of 2024/02/25)
@@ -186,7 +183,6 @@ class Updater:
logger.info("Updated loader installation.")
await self.context.ws.emit("updater/finish_download")
await tab.close_websocket()
await self.do_restart()
async def do_update(self):
@@ -244,6 +240,7 @@ class Updater:
await self.download_decky_binary(download_url, version, size_in_bytes=size_in_bytes)
async def do_restart(self):
logger.info("Restarting loader for update.")
await service_restart("plugin_loader", block=False)
async def do_shutdown(self):
+1 -3
View File
@@ -7,7 +7,7 @@ from aiohttp.web import Application, WebSocketResponse, Request, Response, get
from enum import IntEnum
from typing import Callable, Coroutine, Dict, Any, cast, TypeVar
from typing import Callable, Coroutine, Dict, Any, cast
from traceback import format_exc
@@ -29,8 +29,6 @@ class WSMessageExtra(WSMessage):
# see wsrouter.ts for typings
DataType = TypeVar("DataType")
Route = Callable[..., Coroutine[Any, Any, Any]]
class WSRouter:
+24 -24
View File
@@ -1,4 +1,4 @@
# This file is automatically @generated by Poetry 2.1.1 and should not be changed by hand.
# This file is automatically @generated by Poetry 2.1.3 and should not be changed by hand.
[[package]]
name = "aiohappyeyeballs"
@@ -190,7 +190,7 @@ description = "Timeout context manager for asyncio programs"
optional = false
python-versions = ">=3.7"
groups = ["main"]
markers = "python_version < \"3.11\""
markers = "python_version == \"3.10\""
files = [
{file = "async-timeout-4.0.3.tar.gz", hash = "sha256:4640d96be84d82d02ed59ea2b7105a0f7b33abe8703703cd0ab0bf87c427522f"},
{file = "async_timeout-4.0.3-py3-none-any.whl", hash = "sha256:7405140ff1230c310e51dc27b3145b9092d659ce68ff733fb0cefe3ee42be028"},
@@ -678,32 +678,32 @@ files = [
[[package]]
name = "pyinstaller"
version = "6.8.0"
version = "6.14.2"
description = "PyInstaller bundles a Python application and all its dependencies into a single package."
optional = false
python-versions = "<3.13,>=3.8"
python-versions = "<3.14,>=3.8"
groups = ["dev"]
files = [
{file = "pyinstaller-6.8.0-py3-none-macosx_10_13_universal2.whl", hash = "sha256:5ff6bc2784c1026f8e2f04aa3760cbed41408e108a9d4cf1dd52ee8351a3f6e1"},
{file = "pyinstaller-6.8.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:39ac424d2ee2457d2ab11a5091436e75a0cccae207d460d180aa1fcbbafdd528"},
{file = "pyinstaller-6.8.0-py3-none-manylinux2014_i686.whl", hash = "sha256:355832a3acc7de90a255ecacd4b9f9e166a547a79c8905d49f14e3a75c1acdb9"},
{file = "pyinstaller-6.8.0-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:6303c7a009f47e6a96ef65aed49f41e36ece8d079b9193ca92fe807403e5fe80"},
{file = "pyinstaller-6.8.0-py3-none-manylinux2014_s390x.whl", hash = "sha256:2b71509468c811968c0b5decb5bbe85b6292ea52d7b1f26313d2aabb673fa9a5"},
{file = "pyinstaller-6.8.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:ff31c5b99e05a4384bbe2071df67ec8b2b347640a375eae9b40218be2f1754c6"},
{file = "pyinstaller-6.8.0-py3-none-musllinux_1_1_aarch64.whl", hash = "sha256:000c36b13fe4cd8d0d8c2bc855b1ddcf39867b5adf389e6b5ca45b25fa3e619d"},
{file = "pyinstaller-6.8.0-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:fe0af018d7d5077180e3144ada89a4da5df8d07716eb7e9482834a56dc57a4e8"},
{file = "pyinstaller-6.8.0-py3-none-win32.whl", hash = "sha256:d257f6645c7334cbd66f38a4fac62c3ad614cc46302b2b5d9f8cc48c563bce0e"},
{file = "pyinstaller-6.8.0-py3-none-win_amd64.whl", hash = "sha256:81cccfa9b16699b457f4788c5cc119b50f3cd4d0db924955f15c33f2ad27a50d"},
{file = "pyinstaller-6.8.0-py3-none-win_arm64.whl", hash = "sha256:1c3060a263758cf7f0144ab4c016097b20451b2469d468763414665db1bb743d"},
{file = "pyinstaller-6.8.0.tar.gz", hash = "sha256:3f4b6520f4423fe19bcc2fd63ab7238851ae2bdcbc98f25bc5d2f97cc62012e9"},
{file = "pyinstaller-6.14.2-py3-none-macosx_10_13_universal2.whl", hash = "sha256:d77d18bf5343a1afef2772393d7a489d4ec2282dee5bca549803fc0d74b78330"},
{file = "pyinstaller-6.14.2-py3-none-manylinux2014_aarch64.whl", hash = "sha256:3fa0c391e1300a9fd7752eb1ffe2950112b88fba9d2743eee2ef218a15f4705f"},
{file = "pyinstaller-6.14.2-py3-none-manylinux2014_i686.whl", hash = "sha256:077efb2d01d16d9c8fdda3ad52788f0fead2791c5cec9ed6ce058af7e26eb74b"},
{file = "pyinstaller-6.14.2-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:fdd2bd020a18736806a6bd5d3c4352f1209b427a96ad6c459d88aec1d90c4f21"},
{file = "pyinstaller-6.14.2-py3-none-manylinux2014_s390x.whl", hash = "sha256:03862c6b3cf7b16843d24b529f89cd4077cbe467883cd54ce7a81940d6da09d3"},
{file = "pyinstaller-6.14.2-py3-none-manylinux2014_x86_64.whl", hash = "sha256:78827a21ada2a848e98671852d20d74b2955b6e2aaf2359ed13a462e1a603d84"},
{file = "pyinstaller-6.14.2-py3-none-musllinux_1_1_aarch64.whl", hash = "sha256:185710ab1503dfdfa14c43237d394d96ac183422d588294be42531480dfa6c38"},
{file = "pyinstaller-6.14.2-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:6c673a7e761bd4a2560cfd5dbe1ccdcfe2dff304b774e6e5242fc5afed953661"},
{file = "pyinstaller-6.14.2-py3-none-win32.whl", hash = "sha256:1697601aa788e3a52f0b5e620b4741a34b82e6f222ec6e1318b3a1349f566bb2"},
{file = "pyinstaller-6.14.2-py3-none-win_amd64.whl", hash = "sha256:e10e0e67288d6dcb5898a917dd1d4272aa0ff33f197ad49a0e39618009d63ed9"},
{file = "pyinstaller-6.14.2-py3-none-win_arm64.whl", hash = "sha256:69fd11ca57e572387826afaa4a1b3d4cb74927d76f231f0308c0bd7872ca5ac1"},
{file = "pyinstaller-6.14.2.tar.gz", hash = "sha256:142cce0719e79315f0cc26400c2e5c45d9b6b17e7e0491fee444a9f8f16f4917"},
]
[package.dependencies]
altgraph = "*"
macholib = {version = ">=1.8", markers = "sys_platform == \"darwin\""}
packaging = ">=22.0"
pefile = {version = ">=2022.5.30", markers = "sys_platform == \"win32\""}
pyinstaller-hooks-contrib = ">=2024.6"
pefile = {version = ">=2022.5.30,<2024.8.26 || >2024.8.26", markers = "sys_platform == \"win32\""}
pyinstaller-hooks-contrib = ">=2025.5"
pywin32-ctypes = {version = ">=0.2.1", markers = "sys_platform == \"win32\""}
setuptools = ">=42.0.0"
@@ -713,14 +713,14 @@ hook-testing = ["execnet (>=1.5.0)", "psutil", "pytest (>=2.7.3)"]
[[package]]
name = "pyinstaller-hooks-contrib"
version = "2024.7"
version = "2025.8"
description = "Community maintained hooks for PyInstaller"
optional = false
python-versions = ">=3.7"
python-versions = ">=3.8"
groups = ["dev"]
files = [
{file = "pyinstaller_hooks_contrib-2024.7-py2.py3-none-any.whl", hash = "sha256:8bf0775771fbaf96bcd2f4dfd6f7ae6c1dd1b1efe254c7e50477b3c08e7841d8"},
{file = "pyinstaller_hooks_contrib-2024.7.tar.gz", hash = "sha256:fd5f37dcf99bece184e40642af88be16a9b89613ecb958a8bd1136634fc9fac5"},
{file = "pyinstaller_hooks_contrib-2025.8-py3-none-any.whl", hash = "sha256:8d0b8cfa0cb689a619294ae200497374234bd4e3994b3ace2a4442274c899064"},
{file = "pyinstaller_hooks_contrib-2025.8.tar.gz", hash = "sha256:3402ad41dfe9b5110af134422e37fc5d421ba342c6cb980bd67cb30b7415641c"},
]
[package.dependencies]
@@ -1041,5 +1041,5 @@ propcache = ">=0.2.0"
[metadata]
lock-version = "2.1"
python-versions = ">=3.10,<3.13"
content-hash = "3c9488709e61f3aa21ab47ceb9c677927ce770d8e1e327602a1a6afb09164475"
python-versions = ">=3.10,<3.14"
content-hash = "9a331b42c52134230384c1a7348c2856903d82d6007e06cd75eed13842aa21ea"
+1 -1
View File
@@ -14,7 +14,7 @@ include = [
]
[tool.poetry.dependencies]
python = ">=3.10,<3.13"
python = ">=3.10,<3.14"
aiohttp = "^3.10.11"
aiohttp-jinja2 = "^1.5.1"
+8 -8
View File
@@ -20,8 +20,8 @@
"@rollup/plugin-node-resolve": "^15.2.3",
"@rollup/plugin-replace": "^5.0.7",
"@rollup/plugin-typescript": "^11.1.6",
"@types/react": "18.3.3",
"@types/react-dom": "18.3.0",
"@types/react": "19.1.1",
"@types/react-dom": "19.1.1",
"@types/react-file-icon": "^1.0.4",
"@types/react-router": "5.1.20",
"husky": "^9.0.11",
@@ -30,8 +30,8 @@
"inquirer": "^9.2.23",
"prettier": "^3.3.2",
"prettier-plugin-import-sort": "^0.0.7",
"react": "18.3.1",
"react-dom": "18.3.1",
"react": "19.1.1",
"react-dom": "19.1.1",
"rollup": "^4.22.4",
"rollup-plugin-delete": "^2.0.0",
"rollup-plugin-external-globals": "^0.10.0",
@@ -47,13 +47,13 @@
}
},
"dependencies": {
"@decky/ui": "^4.10.2",
"@decky/ui": "^4.11.0",
"compare-versions": "^6.1.1",
"filesize": "^10.1.2",
"i18next": "^23.11.5",
"i18next": "^25.6.0",
"i18next-http-backend": "^2.5.2",
"react-file-icon": "^1.5.0",
"react-i18next": "^14.1.2",
"react-file-icon": "^1.6.0",
"react-i18next": "^16.0.1",
"react-icons": "^5.2.1",
"react-markdown": "^9.0.1",
"remark-gfm": "^4.0.0"
+92 -77
View File
@@ -9,8 +9,8 @@ importers:
.:
dependencies:
'@decky/ui':
specifier: ^4.10.2
version: 4.10.2
specifier: ^4.11.0
version: 4.11.0
compare-versions:
specifier: ^6.1.1
version: 6.1.1
@@ -18,23 +18,23 @@ importers:
specifier: ^10.1.2
version: 10.1.2
i18next:
specifier: ^23.11.5
version: 23.11.5
specifier: ^25.6.0
version: 25.6.0(typescript@5.4.5)
i18next-http-backend:
specifier: ^2.5.2
version: 2.5.2
react-file-icon:
specifier: ^1.5.0
version: 1.5.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
specifier: ^1.6.0
version: 1.6.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
react-i18next:
specifier: ^14.1.2
version: 14.1.2(i18next@23.11.5)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
specifier: ^16.0.1
version: 16.0.1(i18next@25.6.0(typescript@5.4.5))(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(typescript@5.4.5)
react-icons:
specifier: ^5.2.1
version: 5.2.1(react@18.3.1)
version: 5.2.1(react@19.1.1)
react-markdown:
specifier: ^9.0.1
version: 9.0.1(@types/react@18.3.3)(react@18.3.1)
version: 9.0.1(@types/react@19.1.1)(react@19.1.1)
remark-gfm:
specifier: ^4.0.0
version: 4.0.0
@@ -61,11 +61,11 @@ importers:
specifier: ^11.1.6
version: 11.1.6(rollup@4.22.4)(tslib@2.6.3)(typescript@5.4.5)
'@types/react':
specifier: 18.3.3
version: 18.3.3
specifier: 19.1.1
version: 19.1.1
'@types/react-dom':
specifier: 18.3.0
version: 18.3.0
specifier: 19.1.1
version: 19.1.1(@types/react@19.1.1)
'@types/react-file-icon':
specifier: ^1.0.4
version: 1.0.4
@@ -91,11 +91,11 @@ importers:
specifier: ^0.0.7
version: 0.0.7(prettier@3.3.2)
react:
specifier: 18.3.1
version: 18.3.1
specifier: 19.1.1
version: 19.1.1
react-dom:
specifier: 18.3.1
version: 18.3.1(react@18.3.1)
specifier: 19.1.1
version: 19.1.1(react@19.1.1)
rollup:
specifier: ^4.22.4
version: 4.22.4
@@ -203,6 +203,10 @@ packages:
resolution: {integrity: sha512-UwgBRMjJP+xv857DCngvqXI3Iq6J4v0wXmwc6sapg+zyhbwmQX67LUEFrkK5tbyJ30jGuG3ZvWpBiB9LCy1kWw==}
engines: {node: '>=6.9.0'}
'@babel/runtime@7.28.4':
resolution: {integrity: sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==}
engines: {node: '>=6.9.0'}
'@babel/template@7.24.7':
resolution: {integrity: sha512-jYqfPrU9JTF0PmPy1tLYHW4Mp4KlgxJD9l2nP9fD6yT/ICi554DmrWBAEYpIelzjHf1msDP3PxJIRt/nFNfBig==}
engines: {node: '>=6.9.0'}
@@ -218,8 +222,8 @@ packages:
'@decky/api@1.1.1':
resolution: {integrity: sha512-R5fkBRHBt5QIQY7Q0AlbVIhlIZ/nTzwBOoi8Rt4Go2fjFnoMKPInCJl6cPjXzimGwl2pyqKJgY6VnH6ar0XrHQ==}
'@decky/ui@4.10.2':
resolution: {integrity: sha512-dfY/OEI/rhG4d3Tvx4Y3TLmBrJ+nAm2RTbkoOJ3VAglql3Lu7RY2ixeDFbs21ZWWzWh/C+9dAQKjQ4lx4d1f2g==}
'@decky/ui@4.11.0':
resolution: {integrity: sha512-l9PstFC+S8FE8M2kIM78L8cYW4vzJ/ZD30II0huarHLcCsKM4Q+rbmEnbWjlJ1/KLmGXVRXBdAbyD4X/FzfxnQ==}
'@esbuild/aix-ppc64@0.20.2':
resolution: {integrity: sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g==}
@@ -598,11 +602,10 @@ packages:
'@types/node@20.14.2':
resolution: {integrity: sha512-xyu6WAMVwv6AKFLB+e/7ySZVr/0zLCzOa7rSpq6jNwpqOrUbcACDWC+53d4n2QHOnDou0fbIsg8wZu/sxrnI4Q==}
'@types/prop-types@15.7.12':
resolution: {integrity: sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q==}
'@types/react-dom@18.3.0':
resolution: {integrity: sha512-EhwApuTmMBmXuFOikhQLIBUn6uFg81SwLMOAUgodJF14SOBOCMdU04gDoYi0WOJJHD144TL32z4yDqCW3dnkQg==}
'@types/react-dom@19.1.1':
resolution: {integrity: sha512-jFf/woGTVTjUJsl2O7hcopJ1r0upqoq/vIOoCj0yLh3RIXxWcljlpuZ+vEBRXsymD1jhfeJrlyTy/S1UW+4y1w==}
peerDependencies:
'@types/react': ^19.0.0
'@types/react-file-icon@1.0.4':
resolution: {integrity: sha512-c1mIklUDaxm9odxf8RTiy/EAxsblZliJ86EKIOAyuafP9eK3iudyn4ATv53DX6ZvgGymc7IttVNm97LTGnTiYA==}
@@ -610,8 +613,8 @@ packages:
'@types/react-router@5.1.20':
resolution: {integrity: sha512-jGjmu/ZqS7FjSH6owMcD5qpq19+1RS9DeVRqfl1FeBMxTDQAGwlMWOcs52NDoXaNKyG3d1cYQFMs9rCrb88o9Q==}
'@types/react@18.3.3':
resolution: {integrity: sha512-hti/R0pS0q1/xx+TsI73XIqk26eBsISZ2R0wUijXIngRK9R/e7Xw/cXVxQK7R5JjW+SV4zGcn5hXjudkN/pLIw==}
'@types/react@19.1.1':
resolution: {integrity: sha512-ePapxDL7qrgqSF67s0h9m412d9DbXyC1n59O2st+9rjuuamWsZuD2w55rqY12CbzsZ7uVXb5Nw0gEp9Z8MMutQ==}
'@types/resolve@1.20.2':
resolution: {integrity: sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==}
@@ -1187,8 +1190,16 @@ packages:
engines: {node: '>=18.0.0 || >=20.0.0 || >=22.0.0', npm: '>=6', yarn: '>=1'}
hasBin: true
i18next@23.11.5:
resolution: {integrity: sha512-41pvpVbW9rhZPk5xjCX2TPJi2861LEig/YRhUkY+1FQ2IQPS0bKUDYnEqY8XPPbB48h1uIwLnP9iiEfuSl20CA==}
i18next@23.16.8:
resolution: {integrity: sha512-06r/TitrM88Mg5FdUXAKL96dJMzgqLE5dv3ryBAra4KCwD9mJ4ndOTS95ZuymIGoE+2hzfdaMak2X11/es7ZWg==}
i18next@25.6.0:
resolution: {integrity: sha512-tTn8fLrwBYtnclpL5aPXK/tAYBLWVvoHM1zdfXoRNLcI+RvtMsoZRV98ePlaW3khHYKuNh/Q65W/+NVFUeIwVw==}
peerDependencies:
typescript: ^5
peerDependenciesMeta:
typescript:
optional: true
iconv-lite@0.4.24:
resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==}
@@ -1709,29 +1720,32 @@ packages:
quick-temp@0.1.8:
resolution: {integrity: sha512-YsmIFfD9j2zaFwJkzI6eMG7y0lQP7YeWzgtFgNl38pGWZBSXJooZbOWwkcRot7Vt0Fg9L23pX0tqWU3VvLDsiA==}
react-dom@18.3.1:
resolution: {integrity: sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==}
react-dom@19.1.1:
resolution: {integrity: sha512-Dlq/5LAZgF0Gaz6yiqZCf6VCcZs1ghAJyrsu84Q/GT0gV+mCxbfmKNoGRKBYMJ8IEdGPqu49YWXD02GCknEDkw==}
peerDependencies:
react: ^18.3.1
react: ^19.1.1
react-file-icon@1.5.0:
resolution: {integrity: sha512-6K2/nAI69CS838HOS+4S95MLXwf1neWywek1FgqcTFPTYjnM8XT7aBLz4gkjoqQKY9qPhu3A2tu+lvxhmZYY9w==}
react-file-icon@1.6.0:
resolution: {integrity: sha512-Ba4Qa2ya/kvhcCd4LJja77sV7JD7u1ZXcI1DUz+TII3nGmglG6QY+NZeHizThokgct3qI0glwb9eV8NqRGs5lw==}
peerDependencies:
react: ^18.0.0 || ^17.0.0 || ^16.2.0
react-dom: ^18.0.0 || ^17.0.0 || ^16.2.0
react: ^19.0.0 || ^18.0.0 || ^17.0.0 || ^16.2.0
react-dom: ^19.0.0 || ^18.0.0 || ^17.0.0 || ^16.2.0
react-i18next@14.1.2:
resolution: {integrity: sha512-FSIcJy6oauJbGEXfhUgVeLzvWBhIBIS+/9c6Lj4niwKZyGaGb4V4vUbATXSlsHJDXXB+ociNxqFNiFuV1gmoqg==}
react-i18next@16.0.1:
resolution: {integrity: sha512-0S//bpYEkCPjzuVmxDf9Z6+Y+ArNvpAUk7eDL4qNCZXjDh6Z9j6MZ+NThU7kMCOsmYmDCun3GYEwkiOjjZo9Ug==}
peerDependencies:
i18next: '>= 23.2.3'
i18next: '>= 25.5.2'
react: '>= 16.8.0'
react-dom: '*'
react-native: '*'
typescript: ^5
peerDependenciesMeta:
react-dom:
optional: true
react-native:
optional: true
typescript:
optional: true
react-icons@5.2.1:
resolution: {integrity: sha512-zdbW5GstTzXaVKvGSyTaBalt7HSfuK5ovrzlpyiWHAFXndXTdd/1hdDHI4xBM1Mn7YriT6aqESucFl9kEXzrdw==}
@@ -1747,8 +1761,8 @@ packages:
'@types/react': '>=18'
react: '>=18'
react@18.3.1:
resolution: {integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==}
react@19.1.1:
resolution: {integrity: sha512-w8nqGImo45dmMIfljjMwOGtbmC/mk4CMYhWIicdSflH91J9TyCyczcPFXJzrZ/ZXcgGRFeP6BU0BEJTw6tZdfQ==}
engines: {node: '>=0.10.0'}
readable-stream@2.3.8:
@@ -1873,8 +1887,8 @@ packages:
safer-buffer@2.1.2:
resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==}
scheduler@0.23.2:
resolution: {integrity: sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==}
scheduler@0.26.0:
resolution: {integrity: sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==}
semver@6.3.1:
resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==}
@@ -2266,6 +2280,8 @@ snapshots:
dependencies:
regenerator-runtime: 0.14.1
'@babel/runtime@7.28.4': {}
'@babel/template@7.24.7':
dependencies:
'@babel/code-frame': 7.24.7
@@ -2295,7 +2311,7 @@ snapshots:
'@decky/api@1.1.1': {}
'@decky/ui@4.10.2': {}
'@decky/ui@4.11.0': {}
'@esbuild/aix-ppc64@0.20.2':
optional: true
@@ -2567,24 +2583,21 @@ snapshots:
dependencies:
undici-types: 5.26.5
'@types/prop-types@15.7.12': {}
'@types/react-dom@18.3.0':
'@types/react-dom@19.1.1(@types/react@19.1.1)':
dependencies:
'@types/react': 18.3.3
'@types/react': 19.1.1
'@types/react-file-icon@1.0.4':
dependencies:
'@types/react': 18.3.3
'@types/react': 19.1.1
'@types/react-router@5.1.20':
dependencies:
'@types/history': 4.7.11
'@types/react': 18.3.3
'@types/react': 19.1.1
'@types/react@18.3.3':
'@types/react@19.1.1':
dependencies:
'@types/prop-types': 15.7.12
csstype: 3.1.3
'@types/resolve@1.20.2': {}
@@ -3229,7 +3242,7 @@ snapshots:
esbuild: 0.20.2
fs-extra: 11.2.0
gulp-sort: 2.0.0
i18next: 23.11.5
i18next: 23.16.8
js-yaml: 4.1.0
lilconfig: 3.1.2
rsvp: 4.8.5
@@ -3240,10 +3253,16 @@ snapshots:
transitivePeerDependencies:
- supports-color
i18next@23.11.5:
i18next@23.16.8:
dependencies:
'@babel/runtime': 7.24.7
i18next@25.6.0(typescript@5.4.5):
dependencies:
'@babel/runtime': 7.28.4
optionalDependencies:
typescript: 5.4.5
iconv-lite@0.4.24:
dependencies:
safer-buffer: 2.1.2
@@ -3960,43 +3979,43 @@ snapshots:
rimraf: 2.7.1
underscore.string: 3.3.6
react-dom@18.3.1(react@18.3.1):
react-dom@19.1.1(react@19.1.1):
dependencies:
loose-envify: 1.4.0
react: 18.3.1
scheduler: 0.23.2
react: 19.1.1
scheduler: 0.26.0
react-file-icon@1.5.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
react-file-icon@1.6.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1):
dependencies:
colord: 2.9.3
prop-types: 15.8.1
react: 18.3.1
react-dom: 18.3.1(react@18.3.1)
react: 19.1.1
react-dom: 19.1.1(react@19.1.1)
react-i18next@14.1.2(i18next@23.11.5)(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
react-i18next@16.0.1(i18next@25.6.0(typescript@5.4.5))(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(typescript@5.4.5):
dependencies:
'@babel/runtime': 7.24.7
'@babel/runtime': 7.28.4
html-parse-stringify: 3.0.1
i18next: 23.11.5
react: 18.3.1
i18next: 25.6.0(typescript@5.4.5)
react: 19.1.1
optionalDependencies:
react-dom: 18.3.1(react@18.3.1)
react-dom: 19.1.1(react@19.1.1)
typescript: 5.4.5
react-icons@5.2.1(react@18.3.1):
react-icons@5.2.1(react@19.1.1):
dependencies:
react: 18.3.1
react: 19.1.1
react-is@16.13.1: {}
react-markdown@9.0.1(@types/react@18.3.3)(react@18.3.1):
react-markdown@9.0.1(@types/react@19.1.1)(react@19.1.1):
dependencies:
'@types/hast': 3.0.4
'@types/react': 18.3.3
'@types/react': 19.1.1
devlop: 1.1.0
hast-util-to-jsx-runtime: 2.3.0
html-url-attributes: 3.0.0
mdast-util-to-hast: 13.2.0
react: 18.3.1
react: 19.1.1
remark-parse: 11.0.0
remark-rehype: 11.1.0
unified: 11.0.4
@@ -4005,9 +4024,7 @@ snapshots:
transitivePeerDependencies:
- supports-color
react@18.3.1:
dependencies:
loose-envify: 1.4.0
react@19.1.1: {}
readable-stream@2.3.8:
dependencies:
@@ -4164,9 +4181,7 @@ snapshots:
safer-buffer@2.1.2: {}
scheduler@0.23.2:
dependencies:
loose-envify: 1.4.0
scheduler@0.26.0: {}
semver@6.3.1: {}
+1
View File
@@ -23,6 +23,7 @@ export default defineConfig([
}),
externalGlobals({
react: 'SP_REACT',
'react/jsx-runtime': 'SP_JSX',
'react-dom': 'SP_REACTDOM',
// hack to shut up react-markdown
process: '{cwd: () => {}}',
@@ -1,10 +1,10 @@
import { FC, ReactNode, createContext, useContext, useState } from 'react';
import { FC, PropsWithChildren, createContext, useContext, useState } from 'react';
const QuickAccessVisibleState = createContext<boolean>(false);
export const useQuickAccessVisible = () => useContext(QuickAccessVisibleState);
export const QuickAccessVisibleStateProvider: FC<{ tab: any; children: ReactNode }> = ({ children, tab }) => {
export const QuickAccessVisibleStateProvider: FC<PropsWithChildren<{ tab: any }>> = ({ children, tab }) => {
const initial = tab.initialVisibility;
const [visible, setVisible] = useState<boolean>(initial);
// HACK but i can't think of a better way to do this
+1 -1
View File
@@ -10,7 +10,7 @@ interface WithSuspenseProps {
const WithSuspense: FunctionComponent<WithSuspenseProps> = (props) => {
const propsCopy = { ...props };
delete propsCopy.children;
(props.children as ReactElement)?.props && Object.assign((props.children as ReactElement).props, propsCopy); // There is probably a better way to do this but valve does it this way so ¯\_(ツ)_/¯
(props.children as ReactElement<any>)?.props && Object.assign((props.children as ReactElement<any>).props, propsCopy); // There is probably a better way to do this but valve does it this way so ¯\_(ツ)_/¯
return (
<Suspense
fallback={
@@ -1,4 +1,4 @@
import { FC, useEffect, useState } from 'react';
import { FC, JSX, useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { IconContext } from 'react-icons';
import { FaExclamationTriangle, FaQuestionCircle, FaUserSlash } from 'react-icons/fa';
+13 -5
View File
@@ -1,8 +1,4 @@
// Sets up DFL, then loads start.ts which starts up the loader
interface Window {
// Shut up TS
SP_REACTDOM: any;
}
(async () => {
console.debug('[Decky:Boot] Frontend init');
@@ -21,7 +17,19 @@ interface Window {
// deliberate partial import
const DFLWebpack = await import('@decky/ui/dist/webpack');
window.SP_REACT = DFLWebpack.findModule((m) => m.Component && m.PureComponent && m.useLayoutEffect);
window.SP_REACTDOM = DFLWebpack.findModule((m) => m.createPortal && m.createRoot);
window.SP_REACTDOM =
DFLWebpack.findModule((m) => m.createPortal && m.createRoot) ||
DFLWebpack.findModule((m) => m.createPortal && m.__DOM_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE);
console.debug('[Decky:Boot] Setting up JSX internals...');
const jsx = DFLWebpack.findModule((m) => m.jsx && Object.keys(m).length == 1)?.jsx;
if (jsx) {
window.SP_JSX = {
jsx,
jsxs: jsx,
Fragment: window.SP_REACT.Fragment,
};
}
}
console.debug('[Decky:Boot] Setting up @decky/ui...');
window.DFL = await import('@decky/ui');
-22
View File
@@ -120,28 +120,6 @@ class PluginLoader extends Logger {
<DeckyStateContextProvider deckyState={this.deckyState}>
<FaPlug />
<TabBadge />
<style>
{`
/* fixes random overscrolling in QAM */
.${quickAccessMenuClasses?.TabContentColumn} {
flex-grow: 1 !important;
margin-top: 0 !important;
margin-bottom: 0 !important;
justify-content: center !important;
}
.${quickAccessMenuClasses?.Tab} {
flex-grow: 1 !important;
height: unset !important;
--decky-qam-tab-max-height: 64px; /* make things a little easier for themers */
max-height: var(--decky-qam-tab-max-height) !important;
}
/* they broke the footer a while ago and forgot to update the styles LOL */
.${quickAccessMenuClasses?.Tabs}.${quickAccessMenuClasses.TabsWithFooter} {
margin-bottom: 0 !important;
padding-bottom: 0 !important;
}
`}
</style>
</DeckyStateContextProvider>
),
});
+1
View File
@@ -1,3 +1,4 @@
import type { JSX } from 'react';
export enum PluginLoadType {
LEGACY_EVAL_IIFE = 0, // legacy, uses legacy serverAPI
ESMODULE_V1 = 1, // esmodule loading with modern @decky/backend apis
+5 -5
View File
@@ -9,7 +9,7 @@ import {
getReactRoot,
sleep,
} from '@decky/ui';
import { FC, ReactElement, ReactNode, cloneElement, createElement } from 'react';
import { FC, JSX, ReactElement, ReactNode, cloneElement, createElement } from 'react';
import type { Route } from 'react-router';
import {
@@ -37,7 +37,7 @@ const isPatched = Symbol('is patched');
class RouterHook extends Logger {
private routerState: DeckyRouterState = new DeckyRouterState();
private globalComponentsState: DeckyGlobalComponentsState = new DeckyGlobalComponentsState();
private renderedComponents: ReactElement[] = [];
private renderedComponents: ReactElement<any>[] = [];
private Route: any;
private DeckyGamepadRouterWrapper = this.gamepadRouterWrapper.bind(this);
private DeckyDesktopRouterWrapper = this.desktopRouterWrapper.bind(this);
@@ -233,7 +233,7 @@ class RouterHook extends Logger {
return <>{this.renderedComponents}</>;
}
private gamepadRouterWrapper({ children }: { children: ReactElement }) {
private gamepadRouterWrapper({ children }: { children: ReactElement<any> }) {
// Used to store the new replicated routes we create to allow routes to be unpatched.
const { routes, routePatches } = useDeckyRouterState();
@@ -251,7 +251,7 @@ class RouterHook extends Logger {
return children;
}
private desktopRouterWrapper({ children }: { children: ReactElement }) {
private desktopRouterWrapper({ children }: { children: ReactElement<any> }) {
// Used to store the new replicated routes we create to allow routes to be unpatched.
this.debug('desktop router wrapper render', children);
const { routes, routePatches } = useDeckyRouterState();
@@ -287,7 +287,7 @@ class RouterHook extends Logger {
if (routes) {
if (!routeList[routerIndex - 1]?.length || routeList[routerIndex - 1]?.length !== routes.size) {
if (routeList[routerIndex - 1]?.length && routeList[routerIndex - 1].length !== routes.size) routerIndex--;
const newRouterArray: (ReactElement | JSX.Element)[] = [];
const newRouterArray: (ReactElement<any> | JSX.Element)[] = [];
routes.forEach(({ component, props }, path) => {
newRouterArray.push(
<Route path={path} {...props}>
+3 -4
View File
@@ -2,9 +2,7 @@
"compilerOptions": {
"module": "ESNext",
"target": "ES2021",
"jsx": "react",
"jsxFactory": "window.SP_REACT.createElement",
"jsxFragmentFactory": "window.SP_REACT.Fragment",
"jsx": "react-jsx",
"declaration": false,
"moduleResolution": "node",
"noUnusedLocals": true,
@@ -15,7 +13,8 @@
"noImplicitAny": true,
"strict": true,
"allowSyntheticDefaultImports": true,
"resolveJsonModule": true
"resolveJsonModule": true,
"skipLibCheck": true
},
"include": ["src", "index.d.ts"],
"exclude": ["node_modules"]
+12 -5
View File
@@ -20,7 +20,10 @@ if [ -z "$INSIDE_NIX_RANDOMSTRING" ] && command -v nix &> /dev/null; then
exit $?
fi
required_dependencies=(websocat jq curl chromium)
[[ -f "$HOME/.config/deckdebug/config.sh" ]] && source "$HOME/.config/deckdebug/config.sh"
CHROMIUM="${CHROMIUM:-chromium}"
required_dependencies=(websocat jq curl $CHROMIUM)
# Check if the dependencies are installed
for cmd in "${required_dependencies[@]}"; do
@@ -30,7 +33,7 @@ for cmd in "${required_dependencies[@]}"; do
fi
done
chromium --remote-debugging-port=9222 &
$CHROMIUM --remote-debugging-port=9222 &
sleep 2
ADDR=$1
@@ -49,10 +52,14 @@ while :; do
TARGET=$NEWTARGET
TARGETURL="http://$ADDR/devtools/inspector.html?ws=$ADDR/devtools/page/$TARGET"
LOCALTARGET=$(echo '{"id": 1, "method": "Target.createTarget", "params": {"background": true, "url": "'$TARGETURL'"}}
echo '{"id": 1, "method": "Target.createTarget", "params": {"background": true, "url": "'$TARGETURL'"}}
{"id": 2, "method": "Target.closeTarget", "params": {"targetId": "'$LOCALTARGET'"}}' \
| websocat ws://$LOCAL/devtools/page/$LOCALTARGET \
| jq -r '.result.targetId')
| websocat -t ws://$LOCAL/devtools/page/$LOCALTARGET
sleep 2
LOCALTARGETS=$(curl -s http://$LOCAL/json/list)
LOCALTARGET=$(jq -r '.[] | select(.title | startswith("DevTools")) | .id' <<< "$LOCALTARGETS")
echo started devtools at $LOCALTARGET
fi