mirror of
https://github.com/SteamDeckHomebrew/decky-loader.git
synced 2026-07-11 12:42:01 +00:00
Merge branch 'main' into i18n
This commit is contained in:
@@ -1,36 +0,0 @@
|
|||||||
---
|
|
||||||
name: Bug report
|
|
||||||
about: Create a report to help us improve
|
|
||||||
title: ''
|
|
||||||
labels: bug
|
|
||||||
assignees: ''
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**Description**
|
|
||||||
[A clear and concise description of what the bug is.]
|
|
||||||
|
|
||||||
**To Reproduce**
|
|
||||||
Steps to reproduce the behavior:
|
|
||||||
1. Go to '...'
|
|
||||||
2. Click on '....'
|
|
||||||
3. Scroll down to '....'
|
|
||||||
4. See error
|
|
||||||
|
|
||||||
**Expected behavior**
|
|
||||||
[A clear and concise description of what you expected to happen.]
|
|
||||||
|
|
||||||
**Screenshots**
|
|
||||||
[If applicable, add screenshots to help explain your problem.]
|
|
||||||
|
|
||||||
**Version information**
|
|
||||||
- SteamOS Version: ``[Run ``uname -a`` and place the output here. Leave the single quotations outside.]``
|
|
||||||
- Selected Update Channel: [Stable, Beta or Preview.]
|
|
||||||
|
|
||||||
**Logs**
|
|
||||||
[Please reboot your deck (if possible) when attempting to recreate the issue, then run
|
|
||||||
``cd ~ && journalctl -b0 -u plugin_loader.service > backendlog.txt``. This will save the log file to ``~`` aka ``/home/deck``. Please upload the file here in place of this textblock.]
|
|
||||||
|
|
||||||
**Additional context**
|
|
||||||
Have you modified the read-only filesystem at any point?
|
|
||||||
[Yes or No.]
|
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
name: Bug report
|
||||||
|
description: File a bug/issue
|
||||||
|
title: "[BUG] <title>"
|
||||||
|
labels: [bug]
|
||||||
|
body:
|
||||||
|
- type: checkboxes
|
||||||
|
id: low-effort-checks
|
||||||
|
attributes:
|
||||||
|
label: Please confirm
|
||||||
|
description: Issues without all checks may be ignored/closed.
|
||||||
|
options:
|
||||||
|
- label: I have searched existing issues
|
||||||
|
- label: This issue is not a duplicate of an existing one
|
||||||
|
- label: I have checked the [common issues section in the readme file](https://github.com/SteamDeckHomebrew/decky-loader#-common-issues)
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: Bug Report Description
|
||||||
|
description: A clear and concise description of what the bug is and if possible, the steps you used to get to the bug. If appropriate, include screenshots or videos.
|
||||||
|
placeholder: |
|
||||||
|
When I try to use ...
|
||||||
|
|
||||||
|
Steps to reproduce the behavior:
|
||||||
|
1. Go to '...'
|
||||||
|
2. Click on '....'
|
||||||
|
3. Scroll down to '....'
|
||||||
|
4. See error
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: Expected Behaviour
|
||||||
|
description: A brief description of the expected behavior.
|
||||||
|
placeholder: It should be ...
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: input
|
||||||
|
attributes:
|
||||||
|
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"
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: dropdown
|
||||||
|
attributes:
|
||||||
|
label: Selected Update Channel
|
||||||
|
description: Which branch of Decky are you on?
|
||||||
|
multiple: false
|
||||||
|
options:
|
||||||
|
- Stable
|
||||||
|
- Prerelease
|
||||||
|
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.
|
||||||
|
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
|
||||||
|
placeholder: deckylog.txt
|
||||||
|
validations:
|
||||||
|
required: false
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
blank_issues_enabled: false
|
||||||
|
contact_links:
|
||||||
|
- name: Steam Deck Homebrew Discord Server
|
||||||
|
url: https://discord.gg/ZU74G2NJzk
|
||||||
|
about: Please ask and answer questions here.
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
name: Feature request
|
||||||
|
description: Request a new feature (NOT A PLUGIN)
|
||||||
|
title: "[Request] <title>"
|
||||||
|
labels: [feature request]
|
||||||
|
body:
|
||||||
|
- type: checkboxes
|
||||||
|
id: low-effort-checks
|
||||||
|
attributes:
|
||||||
|
label: Please confirm
|
||||||
|
description: Issues without all checks may be ignored/closed.
|
||||||
|
options:
|
||||||
|
- label: I have searched existing issues
|
||||||
|
- label: This issue is not a duplicate of an existing one
|
||||||
|
- label: This is not a request for a plugin
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: Feature Request Description
|
||||||
|
description: A clear and concise description of what the new feature.
|
||||||
|
placeholder: |
|
||||||
|
Decky plugins should be sortable in the quick access menu
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: Further Description
|
||||||
|
description: A further explanation of the feature. If appropriate, include screenshots or videos.
|
||||||
|
placeholder: |
|
||||||
|
This would help make the UI clearer and easier to use as there is less clutter in the QAM.
|
||||||
|
It would also make it faster to access plugins that are used more.
|
||||||
|
|
||||||
|
This could be implemented by adding ...
|
||||||
|
validations:
|
||||||
|
required: false
|
||||||
@@ -31,7 +31,7 @@ permissions:
|
|||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
name: Build PluginLoader
|
name: Build PluginLoader
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-20.04
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Print input
|
- name: Print input
|
||||||
|
|||||||
@@ -2,6 +2,8 @@
|
|||||||
<a name="logo" href="https://deckbrew.xyz/"><img src="https://deckbrew.xyz/logo.png" alt="Deckbrew logo" width="200"></a>
|
<a name="logo" href="https://deckbrew.xyz/"><img src="https://deckbrew.xyz/logo.png" alt="Deckbrew logo" width="200"></a>
|
||||||
<br>
|
<br>
|
||||||
Decky Loader
|
Decky Loader
|
||||||
|
<br>
|
||||||
|
<a name="logo" href="https://github.com/SteamDeckHomebrew/decky-installer/releases/latest/download/decky_installer.desktop"><img src="./docs/images/download_button.png" alt="Download decky" width="350"></a>
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
@@ -36,11 +38,7 @@ For more information about Decky Loader as well as documentation and development
|
|||||||
- If you are using any software that uses port 1337 or 8080, please change its port to something else or uninstall it.
|
- If you are using any software that uses port 1337 or 8080, please change its port to something else or uninstall it.
|
||||||
|
|
||||||
## 💾 Installation
|
## 💾 Installation
|
||||||
|
- This installation can be done without an admin/sudo password set.
|
||||||
1. Press the <img src="./docs/images/light/steam.svg#gh-dark-mode-only" height=16><img src="./docs/images/dark/steam.svg#gh-light-mode-only" height=16> button and open the Settings menu.
|
|
||||||
1. Navigate to the System menu and scroll to the System Settings. Toggle "Enable Developer Mode" so it is enabled.
|
|
||||||
1. Navigate to the Developer menu and scroll to Miscellaneous. Toggle "CEF Remote Debugging" so it is enabled.
|
|
||||||
1. Select "Restart Now" to apply your changes.
|
|
||||||
1. Prepare a mouse and keyboard if possible.
|
1. Prepare a mouse and keyboard if possible.
|
||||||
- Keyboards and mice can be connected to the Steam Deck via USB-C or Bluetooth.
|
- Keyboards and mice can be connected to the Steam Deck via USB-C or Bluetooth.
|
||||||
- Many Bluetooth keyboard and mouse apps are available for iOS and Android.
|
- Many Bluetooth keyboard and mouse apps are available for iOS and Android.
|
||||||
@@ -48,24 +46,27 @@ For more information about Decky Loader as well as documentation and development
|
|||||||
- If you have no other options, use the right trackpad as a mouse and press <img src="./docs/images/light/steam.svg#gh-dark-mode-only" height=16><img src="./docs/images/dark/steam.svg#gh-light-mode-only" height=16>+<img src="./docs/images/light/x.svg#gh-dark-mode-only" height=16><img src="./docs/images/dark/x.svg#gh-light-mode-only" height=16> to open the on-screen keyboard as needed.
|
- If you have no other options, use the right trackpad as a mouse and press <img src="./docs/images/light/steam.svg#gh-dark-mode-only" height=16><img src="./docs/images/dark/steam.svg#gh-light-mode-only" height=16>+<img src="./docs/images/light/x.svg#gh-dark-mode-only" height=16><img src="./docs/images/dark/x.svg#gh-light-mode-only" height=16> to open the on-screen keyboard as needed.
|
||||||
1. Press the <img src="./docs/images/light/steam.svg#gh-dark-mode-only" height=16><img src="./docs/images/dark/steam.svg#gh-light-mode-only" height=16> button and open the Power menu.
|
1. Press the <img src="./docs/images/light/steam.svg#gh-dark-mode-only" height=16><img src="./docs/images/dark/steam.svg#gh-light-mode-only" height=16> button and open the Power menu.
|
||||||
1. Select "Switch to Desktop".
|
1. Select "Switch to Desktop".
|
||||||
1. Open the Konsole app and enter the command `passwd`. You can skip this step if you have already created a sudo password using this command. ([YouTube Guide](https://www.youtube.com/watch?v=1vOMYGj22rQ))
|
1. Navigate to this Github page on a browser of your choice.
|
||||||
1. You will be prompted to create a password. Your text will not be visible. After you press enter, you will need to type your password again to confirm.
|
1. Press the 'Download' button at the top of the page.
|
||||||
1. Choose the version of Decky Loader you want to install and paste the following command into the Konsole app.
|
1. Run the downloaded file by clicking on it in Dolphin (the file manager).
|
||||||
|
1. Either type your admin password or allow Decky to temporarily set your password to `Decky!`
|
||||||
|
1. Choose the version of Decky Loader you want to install.
|
||||||
- **Latest Release**
|
- **Latest Release**
|
||||||
Intended for most users. This is the latest stable version of Decky Loader.
|
Intended for most users. This is the latest stable version of Decky Loader.
|
||||||
`curl -L https://github.com/SteamDeckHomebrew/decky-loader/raw/main/dist/install_release.sh | sh`
|
|
||||||
- **Latest Pre-Release**
|
- **Latest Pre-Release**
|
||||||
Intended for plugin developers. Pre-releases are unlikely to be fully stable but contain the latest changes. For more information on plugin development, please consult [the wiki page](https://deckbrew.xyz/en/loader-dev/development).
|
Intended for plugin developers. Pre-releases are unlikely to be fully stable but contain the latest changes. For more information on plugin development, please consult [the wiki page](https://deckbrew.xyz/en/loader-dev/development).
|
||||||
`curl -L https://github.com/SteamDeckHomebrew/decky-loader/raw/main/dist/install_prerelease.sh | sh`
|
|
||||||
1. Open the Return to Gaming Mode shortcut on your desktop.
|
1. Open the Return to Gaming Mode shortcut on your desktop.
|
||||||
|
|
||||||
|
- There is also a fast install for those who can use Konsole. Run `curl -L https://github.com/SteamDeckHomebrew/decky-loader/raw/main/dist/install_release.sh | sh` and type your password when prompted.
|
||||||
|
|
||||||
### 👋 Uninstallation
|
### 👋 Uninstallation
|
||||||
|
|
||||||
We are sorry to see you go! If you are considering uninstalling because you are having issues, please consider [opening an issue](https://github.com/SteamDeckHomebrew/decky-loader/issues) or [joining our Discord](https://discord.gg/ZU74G2NJzk) so we can help you and other users.
|
We are sorry to see you go! If you are considering uninstalling because you are having issues, please consider [opening an issue](https://github.com/SteamDeckHomebrew/decky-loader/issues) or [joining our Discord](https://discord.gg/ZU74G2NJzk) so we can help you and other users.
|
||||||
|
|
||||||
1. Press the <img src="./docs/images/light/steam.svg#gh-dark-mode-only" height=16><img src="./docs/images/dark/steam.svg#gh-light-mode-only" height=16> button and open the Power menu.
|
1. Press the <img src="./docs/images/light/steam.svg#gh-dark-mode-only" height=16><img src="./docs/images/dark/steam.svg#gh-light-mode-only" height=16> button and open the Power menu.
|
||||||
1. Select "Switch to Desktop".
|
1. Select "Switch to Desktop".
|
||||||
1. Open the Konsole app and run `curl -L https://github.com/SteamDeckHomebrew/decky-loader/raw/main/dist/uninstall.sh | sh`.
|
1. Run the installer file again, and select `uninstall decky loader`
|
||||||
|
- There is also a fast uninstall for those who can use Konsole. Run `curl -L https://github.com/SteamDeckHomebrew/decky-loader/raw/main/dist/uninstall.sh | sh` and type your password when prompted.
|
||||||
|
|
||||||
## 🚀 Getting Started
|
## 🚀 Getting Started
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -55,7 +55,7 @@ class PluginBrowser:
|
|||||||
pluginBinPath = path.join(pluginBasePath, 'bin')
|
pluginBinPath = path.join(pluginBasePath, 'bin')
|
||||||
|
|
||||||
if access(packageJsonPath, R_OK):
|
if access(packageJsonPath, R_OK):
|
||||||
with open(packageJsonPath, 'r') as f:
|
with open(packageJsonPath, "r", encoding="utf-8") as f:
|
||||||
packageJson = json.load(f)
|
packageJson = json.load(f)
|
||||||
if "remote_binary" in packageJson and len(packageJson["remote_binary"]) > 0:
|
if "remote_binary" in packageJson and len(packageJson["remote_binary"]) > 0:
|
||||||
# create bin directory if needed.
|
# create bin directory if needed.
|
||||||
@@ -93,7 +93,7 @@ class PluginBrowser:
|
|||||||
def find_plugin_folder(self, name):
|
def find_plugin_folder(self, name):
|
||||||
for folder in listdir(self.plugin_path):
|
for folder in listdir(self.plugin_path):
|
||||||
try:
|
try:
|
||||||
with open(path.join(self.plugin_path, folder, 'plugin.json'), 'r') as f:
|
with open(path.join(self.plugin_path, folder, 'plugin.json'), "r", encoding="utf-8") as f:
|
||||||
plugin = json.load(f)
|
plugin = json.load(f)
|
||||||
|
|
||||||
if plugin['name'] == name:
|
if plugin['name'] == name:
|
||||||
|
|||||||
+1
-1
@@ -396,7 +396,7 @@ async def get_tab_lambda(test) -> Tab:
|
|||||||
|
|
||||||
async def get_gamepadui_tab() -> Tab:
|
async def get_gamepadui_tab() -> Tab:
|
||||||
tabs = await get_tabs()
|
tabs = await get_tabs()
|
||||||
tab = next((i for i in tabs if ("https://steamloopback.host/routes/" in i.url and (i.title == "Steam" or i.title == "SP"))), None)
|
tab = next((i for i in tabs if ("https://steamloopback.host/routes/" in i.url and (i.title == "Steam Shared Context presented by Valve™" or i.title == "Steam" or i.title == "SP"))), None)
|
||||||
if not tab:
|
if not tab:
|
||||||
raise ValueError(f"GamepadUI Tab not found")
|
raise ValueError(f"GamepadUI Tab not found")
|
||||||
return tab
|
return tab
|
||||||
|
|||||||
+3
-3
@@ -118,7 +118,7 @@ class Loader:
|
|||||||
def handle_frontend_bundle(self, request):
|
def handle_frontend_bundle(self, request):
|
||||||
plugin = self.plugins[request.match_info["plugin_name"]]
|
plugin = self.plugins[request.match_info["plugin_name"]]
|
||||||
|
|
||||||
with open(path.join(self.plugin_path, plugin.plugin_directory, "dist/index.js"), 'r') as bundle:
|
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")
|
return web.Response(text=bundle.read(), content_type="application/javascript")
|
||||||
|
|
||||||
def import_plugin(self, file, plugin_directory, refresh=False, batch=False):
|
def import_plugin(self, file, plugin_directory, refresh=False, batch=False):
|
||||||
@@ -186,7 +186,7 @@ class Loader:
|
|||||||
"""
|
"""
|
||||||
async def load_plugin_main_view(self, request):
|
async def load_plugin_main_view(self, request):
|
||||||
plugin = self.plugins[request.match_info["name"]]
|
plugin = self.plugins[request.match_info["name"]]
|
||||||
with open(path.join(self.plugin_path, plugin.plugin_directory, plugin.main_view_html), 'r') as template:
|
with open(path.join(self.plugin_path, plugin.plugin_directory, plugin.main_view_html), "r", encoding="utf-8") as template:
|
||||||
template_data = template.read()
|
template_data = template.read()
|
||||||
ret = f"""
|
ret = f"""
|
||||||
<script src="/legacy/library.js"></script>
|
<script src="/legacy/library.js"></script>
|
||||||
@@ -202,7 +202,7 @@ class Loader:
|
|||||||
self.logger.info(path)
|
self.logger.info(path)
|
||||||
ret = ""
|
ret = ""
|
||||||
file_path = path.join(self.plugin_path, plugin.plugin_directory, route_path)
|
file_path = path.join(self.plugin_path, plugin.plugin_directory, route_path)
|
||||||
with open(file_path, 'r') as resource_data:
|
with open(file_path, "r", encoding="utf-8") as resource_data:
|
||||||
ret = resource_data.read()
|
ret = resource_data.read()
|
||||||
|
|
||||||
return web.Response(text=ret)
|
return web.Response(text=ret)
|
||||||
|
|||||||
+9
-9
@@ -168,15 +168,15 @@ class PluginManager:
|
|||||||
async def inject_javascript(self, tab: Tab, first=False, request=None):
|
async def inject_javascript(self, tab: Tab, first=False, request=None):
|
||||||
logger.info("Loading Decky frontend!")
|
logger.info("Loading Decky frontend!")
|
||||||
try:
|
try:
|
||||||
# if first:
|
if first:
|
||||||
# if await tab.has_global_var("deckyHasLoaded", False):
|
if await tab.has_global_var("deckyHasLoaded", False):
|
||||||
# tabs = await get_tabs()
|
tabs = await get_tabs()
|
||||||
# for t in tabs:
|
for t in tabs:
|
||||||
# if t.title != "Steam" and t.title != "SP":
|
if not t.title or (t.title != "Steam" and t.title != "SP"):
|
||||||
# logger.debug("Closing tab: " + getattr(t, "title", "Untitled"))
|
logger.debug("Closing tab: " + getattr(t, "title", "Untitled"))
|
||||||
# await t.close()
|
await t.close()
|
||||||
# await sleep(0.5)
|
await sleep(0.5)
|
||||||
await tab.evaluate_js("try{if (window.deckyHasLoaded){setTimeout(() => SteamClient.User.StartRestart(), 100)}else{window.deckyHasLoaded = true;(async()=>{try{while(!window.SP_REACT){await new Promise(r => setTimeout(r, 10))};await import('http://localhost:1337/frontend/index.js')}catch(e){console.error(e)};})();}}catch(e){console.error(e)}", False, False, False)
|
await tab.evaluate_js("try{if (window.deckyHasLoaded){setTimeout(() => 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)
|
||||||
except:
|
except:
|
||||||
logger.info("Failed to inject JavaScript into tab\n" + format_exc())
|
logger.info("Failed to inject JavaScript into tab\n" + format_exc())
|
||||||
pass
|
pass
|
||||||
|
|||||||
+5
-5
@@ -27,9 +27,9 @@ class PluginWrapper:
|
|||||||
|
|
||||||
self.version = None
|
self.version = None
|
||||||
|
|
||||||
json = load(open(path.join(plugin_path, plugin_directory, "plugin.json"), "r"))
|
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")):
|
if path.isfile(path.join(plugin_path, plugin_directory, "package.json")):
|
||||||
package_json = load(open(path.join(plugin_path, plugin_directory, "package.json"), "r"))
|
package_json = load(open(path.join(plugin_path, plugin_directory, "package.json"), "r", encoding="utf-8"))
|
||||||
self.version = package_json["version"]
|
self.version = package_json["version"]
|
||||||
|
|
||||||
|
|
||||||
@@ -112,7 +112,7 @@ class PluginWrapper:
|
|||||||
d["res"] = str(e)
|
d["res"] = str(e)
|
||||||
d["success"] = False
|
d["success"] = False
|
||||||
finally:
|
finally:
|
||||||
writer.write((dumps(d)+"\n").encode("utf-8"))
|
writer.write((dumps(d, ensure_ascii=False)+"\n").encode("utf-8"))
|
||||||
await writer.drain()
|
await writer.drain()
|
||||||
|
|
||||||
async def _open_socket_if_not_exists(self):
|
async def _open_socket_if_not_exists(self):
|
||||||
@@ -140,7 +140,7 @@ class PluginWrapper:
|
|||||||
return
|
return
|
||||||
async def _(self):
|
async def _(self):
|
||||||
if await self._open_socket_if_not_exists():
|
if await self._open_socket_if_not_exists():
|
||||||
self.writer.write((dumps({"stop": True})+"\n").encode("utf-8"))
|
self.writer.write((dumps({ "stop": True }, ensure_ascii=False)+"\n").encode("utf-8"))
|
||||||
await self.writer.drain()
|
await self.writer.drain()
|
||||||
self.writer.close()
|
self.writer.close()
|
||||||
get_event_loop().create_task(_(self))
|
get_event_loop().create_task(_(self))
|
||||||
@@ -151,7 +151,7 @@ class PluginWrapper:
|
|||||||
async with self.method_call_lock:
|
async with self.method_call_lock:
|
||||||
if await self._open_socket_if_not_exists():
|
if await self._open_socket_if_not_exists():
|
||||||
self.writer.write(
|
self.writer.write(
|
||||||
(dumps({"method": method_name, "args": kwargs})+"\n").encode("utf-8"))
|
(dumps({ "method": method_name, "args": kwargs }, ensure_ascii=False) + "\n").encode("utf-8"))
|
||||||
await self.writer.drain()
|
await self.writer.drain()
|
||||||
line = bytearray()
|
line = bytearray()
|
||||||
while True:
|
while True:
|
||||||
|
|||||||
+4
-4
@@ -35,22 +35,22 @@ class SettingsManager:
|
|||||||
self.settings = {}
|
self.settings = {}
|
||||||
|
|
||||||
try:
|
try:
|
||||||
open(self.path, "x")
|
open(self.path, "x", encoding="utf-8")
|
||||||
except FileExistsError as e:
|
except FileExistsError as e:
|
||||||
self.read()
|
self.read()
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def read(self):
|
def read(self):
|
||||||
try:
|
try:
|
||||||
with open(self.path, "r") as file:
|
with open(self.path, "r", encoding="utf-8") as file:
|
||||||
self.settings = load(file)
|
self.settings = load(file)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(e)
|
print(e)
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def commit(self):
|
def commit(self):
|
||||||
with open(self.path, "w+") as file:
|
with open(self.path, "w+", encoding="utf-8") as file:
|
||||||
dump(self.settings, file, indent=4)
|
dump(self.settings, file, indent=4, ensure_ascii=False)
|
||||||
|
|
||||||
def getSetting(self, key, default):
|
def getSetting(self, key, default):
|
||||||
return self.settings.get(key, default)
|
return self.settings.get(key, default)
|
||||||
|
|||||||
+4
-4
@@ -32,7 +32,7 @@ class Updater:
|
|||||||
self.allRemoteVers = None
|
self.allRemoteVers = None
|
||||||
try:
|
try:
|
||||||
logger.info(getcwd())
|
logger.info(getcwd())
|
||||||
with open(path.join(getcwd(), ".loader.version"), 'r') as version_file:
|
with open(path.join(getcwd(), ".loader.version"), "r", encoding="utf-8") as version_file:
|
||||||
self.localVer = version_file.readline().replace("\n", "")
|
self.localVer = version_file.readline().replace("\n", "")
|
||||||
except:
|
except:
|
||||||
self.localVer = False
|
self.localVer = False
|
||||||
@@ -159,10 +159,10 @@ class Updater:
|
|||||||
out.write(data)
|
out.write(data)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error at %s", exc_info=e)
|
logger.error(f"Error at %s", exc_info=e)
|
||||||
with open(path.join(getcwd(), "plugin_loader.service"), 'r') as service_file:
|
with open(path.join(getcwd(), "plugin_loader.service"), "r", encoding="utf-8") as service_file:
|
||||||
service_data = service_file.read()
|
service_data = service_file.read()
|
||||||
service_data = service_data.replace("${HOMEBREW_FOLDER}", "/home/"+helpers.get_user()+"/homebrew")
|
service_data = service_data.replace("${HOMEBREW_FOLDER}", "/home/"+helpers.get_user()+"/homebrew")
|
||||||
with open(path.join(getcwd(), "plugin_loader.service"), 'w') as service_file:
|
with open(path.join(getcwd(), "plugin_loader.service"), "w", encoding="utf-8") as service_file:
|
||||||
service_file.write(service_data)
|
service_file.write(service_data)
|
||||||
|
|
||||||
logger.debug("Saved service file")
|
logger.debug("Saved service file")
|
||||||
@@ -191,7 +191,7 @@ class Updater:
|
|||||||
self.context.loop.create_task(tab.evaluate_js(f"window.DeckyUpdater.updateProgress({new_progress})", False, False, False))
|
self.context.loop.create_task(tab.evaluate_js(f"window.DeckyUpdater.updateProgress({new_progress})", False, False, False))
|
||||||
progress = new_progress
|
progress = new_progress
|
||||||
|
|
||||||
with open(path.join(getcwd(), ".loader.version"), "w") as out:
|
with open(path.join(getcwd(), ".loader.version"), "w", encoding="utf-8") as out:
|
||||||
out.write(version)
|
out.write(version)
|
||||||
|
|
||||||
call(['chmod', '+x', path.join(getcwd(), "PluginLoader")])
|
call(['chmod', '+x', path.join(getcwd(), "PluginLoader")])
|
||||||
|
|||||||
Vendored
+4
-1
@@ -11,10 +11,12 @@ HOMEBREW_FOLDER="${USER_DIR}/homebrew"
|
|||||||
rm -rf "${HOMEBREW_FOLDER}/services"
|
rm -rf "${HOMEBREW_FOLDER}/services"
|
||||||
sudo -u $SUDO_USER mkdir -p "${HOMEBREW_FOLDER}/services"
|
sudo -u $SUDO_USER mkdir -p "${HOMEBREW_FOLDER}/services"
|
||||||
sudo -u $SUDO_USER mkdir -p "${HOMEBREW_FOLDER}/plugins"
|
sudo -u $SUDO_USER mkdir -p "${HOMEBREW_FOLDER}/plugins"
|
||||||
|
touch "${USER_DIR}/.steam/steam/.cef-enable-remote-debugging"
|
||||||
|
|
||||||
# Download latest release and install it
|
# Download latest release and install it
|
||||||
RELEASE=$(curl -s 'https://api.github.com/repos/SteamDeckHomebrew/decky-loader/releases' | jq -r "first(.[] | select(.prerelease == "true"))")
|
RELEASE=$(curl -s 'https://api.github.com/repos/SteamDeckHomebrew/decky-loader/releases' | jq -r "first(.[] | select(.prerelease == "true"))")
|
||||||
read VERSION DOWNLOADURL < <(echo $(jq -r '.tag_name, .assets[].browser_download_url' <<< ${RELEASE}))
|
VERSION=$(jq -r '.tag_name' <<< ${RELEASE} )
|
||||||
|
DOWNLOADURL=$(jq -r '.assets[].browser_download_url | select(endswith("PluginLoader"))' <<< ${RELEASE})
|
||||||
|
|
||||||
printf "Installing version %s...\n" "${VERSION}"
|
printf "Installing version %s...\n" "${VERSION}"
|
||||||
curl -L $DOWNLOADURL --output ${HOMEBREW_FOLDER}/services/PluginLoader
|
curl -L $DOWNLOADURL --output ${HOMEBREW_FOLDER}/services/PluginLoader
|
||||||
@@ -40,6 +42,7 @@ User=root
|
|||||||
Restart=always
|
Restart=always
|
||||||
ExecStart=${HOMEBREW_FOLDER}/services/PluginLoader
|
ExecStart=${HOMEBREW_FOLDER}/services/PluginLoader
|
||||||
WorkingDirectory=${HOMEBREW_FOLDER}/services
|
WorkingDirectory=${HOMEBREW_FOLDER}/services
|
||||||
|
KillSignal=SIGKILL
|
||||||
Environment=PLUGIN_PATH=${HOMEBREW_FOLDER}/plugins
|
Environment=PLUGIN_PATH=${HOMEBREW_FOLDER}/plugins
|
||||||
Environment=LOG_LEVEL=DEBUG
|
Environment=LOG_LEVEL=DEBUG
|
||||||
[Install]
|
[Install]
|
||||||
|
|||||||
Vendored
+4
-1
@@ -11,10 +11,12 @@ HOMEBREW_FOLDER="${USER_DIR}/homebrew"
|
|||||||
rm -rf "${HOMEBREW_FOLDER}/services"
|
rm -rf "${HOMEBREW_FOLDER}/services"
|
||||||
sudo -u $SUDO_USER mkdir -p "${HOMEBREW_FOLDER}/services"
|
sudo -u $SUDO_USER mkdir -p "${HOMEBREW_FOLDER}/services"
|
||||||
sudo -u $SUDO_USER mkdir -p "${HOMEBREW_FOLDER}/plugins"
|
sudo -u $SUDO_USER mkdir -p "${HOMEBREW_FOLDER}/plugins"
|
||||||
|
touch "${USER_DIR}/.steam/steam/.cef-enable-remote-debugging"
|
||||||
|
|
||||||
# Download latest release and install it
|
# Download latest release and install it
|
||||||
RELEASE=$(curl -s 'https://api.github.com/repos/SteamDeckHomebrew/decky-loader/releases' | jq -r "first(.[] | select(.prerelease == "false"))")
|
RELEASE=$(curl -s 'https://api.github.com/repos/SteamDeckHomebrew/decky-loader/releases' | jq -r "first(.[] | select(.prerelease == "false"))")
|
||||||
read VERSION DOWNLOADURL < <(echo $(jq -r '.tag_name, .assets[].browser_download_url' <<< ${RELEASE}))
|
VERSION=$(jq -r '.tag_name' <<< ${RELEASE} )
|
||||||
|
DOWNLOADURL=$(jq -r '.assets[].browser_download_url | select(endswith("PluginLoader"))' <<< ${RELEASE})
|
||||||
|
|
||||||
printf "Installing version %s...\n" "${VERSION}"
|
printf "Installing version %s...\n" "${VERSION}"
|
||||||
curl -L $DOWNLOADURL --output ${HOMEBREW_FOLDER}/services/PluginLoader
|
curl -L $DOWNLOADURL --output ${HOMEBREW_FOLDER}/services/PluginLoader
|
||||||
@@ -40,6 +42,7 @@ User=root
|
|||||||
Restart=always
|
Restart=always
|
||||||
ExecStart=${HOMEBREW_FOLDER}/services/PluginLoader
|
ExecStart=${HOMEBREW_FOLDER}/services/PluginLoader
|
||||||
WorkingDirectory=${HOMEBREW_FOLDER}/services
|
WorkingDirectory=${HOMEBREW_FOLDER}/services
|
||||||
|
KillSignal=SIGKILL
|
||||||
Environment=PLUGIN_PATH=${HOMEBREW_FOLDER}/plugins
|
Environment=PLUGIN_PATH=${HOMEBREW_FOLDER}/plugins
|
||||||
Environment=LOG_LEVEL=INFO
|
Environment=LOG_LEVEL=INFO
|
||||||
[Install]
|
[Install]
|
||||||
|
|||||||
+2
-1
@@ -8,7 +8,8 @@ User=root
|
|||||||
Restart=always
|
Restart=always
|
||||||
ExecStart=${HOMEBREW_FOLDER}/services/PluginLoader
|
ExecStart=${HOMEBREW_FOLDER}/services/PluginLoader
|
||||||
WorkingDirectory=${HOMEBREW_FOLDER}/services
|
WorkingDirectory=${HOMEBREW_FOLDER}/services
|
||||||
|
KillSignal=SIGKILL
|
||||||
Environment=PLUGIN_PATH=${HOMEBREW_FOLDER}/plugins
|
Environment=PLUGIN_PATH=${HOMEBREW_FOLDER}/plugins
|
||||||
Environment=LOG_LEVEL=DEBUG
|
Environment=LOG_LEVEL=DEBUG
|
||||||
[Install]
|
[Install]
|
||||||
WantedBy=multi-user.target
|
WantedBy=multi-user.target
|
||||||
|
|||||||
Vendored
+2
-1
@@ -8,7 +8,8 @@ User=root
|
|||||||
Restart=always
|
Restart=always
|
||||||
ExecStart=${HOMEBREW_FOLDER}/services/PluginLoader
|
ExecStart=${HOMEBREW_FOLDER}/services/PluginLoader
|
||||||
WorkingDirectory=${HOMEBREW_FOLDER}/services
|
WorkingDirectory=${HOMEBREW_FOLDER}/services
|
||||||
|
KillSignal=SIGKILL
|
||||||
Environment=PLUGIN_PATH=${HOMEBREW_FOLDER}/plugins
|
Environment=PLUGIN_PATH=${HOMEBREW_FOLDER}/plugins
|
||||||
Environment=LOG_LEVEL=INFO
|
Environment=LOG_LEVEL=INFO
|
||||||
[Install]
|
[Install]
|
||||||
WantedBy=multi-user.target
|
WantedBy=multi-user.target
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 11 KiB |
@@ -41,7 +41,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"decky-frontend-lib": "^3.7.14",
|
"decky-frontend-lib": "^3.18.4",
|
||||||
"i18next": "^22.0.6",
|
"i18next": "^22.0.6",
|
||||||
"i18next-fs-backend": "^2.0.0",
|
"i18next-fs-backend": "^2.0.0",
|
||||||
"react-file-icon": "^1.2.0",
|
"react-file-icon": "^1.2.0",
|
||||||
|
|||||||
Generated
+4
-5
@@ -10,7 +10,7 @@ specifiers:
|
|||||||
'@types/react-file-icon': ^1.0.1
|
'@types/react-file-icon': ^1.0.1
|
||||||
'@types/react-router': 5.1.18
|
'@types/react-router': 5.1.18
|
||||||
'@types/webpack': ^5.28.0
|
'@types/webpack': ^5.28.0
|
||||||
decky-frontend-lib: ^3.7.14
|
decky-frontend-lib: ^3.18.4
|
||||||
husky: ^8.0.1
|
husky: ^8.0.1
|
||||||
i18next: ^22.0.6
|
i18next: ^22.0.6
|
||||||
i18next-fs-backend: ^2.0.0
|
i18next-fs-backend: ^2.0.0
|
||||||
@@ -33,7 +33,7 @@ specifiers:
|
|||||||
typescript: ^4.7.4
|
typescript: ^4.7.4
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
decky-frontend-lib: 3.7.14
|
decky-frontend-lib: 3.18.4
|
||||||
i18next: 22.0.6
|
i18next: 22.0.6
|
||||||
i18next-fs-backend: 2.0.0
|
i18next-fs-backend: 2.0.0
|
||||||
react-file-icon: 1.2.0_wcqkhtmu7mswc6yz4uyexck3ty
|
react-file-icon: 1.2.0_wcqkhtmu7mswc6yz4uyexck3ty
|
||||||
@@ -1082,9 +1082,8 @@ packages:
|
|||||||
dependencies:
|
dependencies:
|
||||||
ms: 2.1.2
|
ms: 2.1.2
|
||||||
|
|
||||||
/decky-frontend-lib/3.7.14:
|
/decky-frontend-lib/3.18.4:
|
||||||
resolution:
|
resolution: {integrity: sha512-i3TAe3RJtT1TK0rJgW9Ek5jxMWZRCYLDvqHDylGVieUvuyI7c8X+cogz30pP4cqeGOaA1d/MxBEbhlpD3JhVvg==}
|
||||||
{ integrity: sha512-ShAoP3VqiwkJYukDBHsOF9fk7wYw0VaKpHw6j9WdzLxwZwBcg0J7QBNIFYP3nfA0UgEwSJVEg/22kONiplipmA== }
|
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/decode-named-character-reference/1.0.2:
|
/decode-named-character-reference/1.0.2:
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { ConfirmModal, QuickAccessTab, Router, Spinner, staticClasses } from 'decky-frontend-lib';
|
import { ConfirmModal, Navigation, QuickAccessTab, Spinner, staticClasses } from 'decky-frontend-lib';
|
||||||
import { FC, useState } from 'react';
|
import { FC, useState } from 'react';
|
||||||
|
|
||||||
interface PluginInstallModalProps {
|
interface PluginInstallModalProps {
|
||||||
@@ -20,7 +20,7 @@ const PluginInstallModal: FC<PluginInstallModalProps> = ({ artifact, version, ha
|
|||||||
onOK={async () => {
|
onOK={async () => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
await onOK();
|
await onOK();
|
||||||
setTimeout(() => Router.OpenQuickAccessMenu(QuickAccessTab.Decky), 250);
|
setTimeout(() => Navigation.OpenQuickAccessMenu(QuickAccessTab.Decky), 250);
|
||||||
setTimeout(() => window.DeckyPluginLoader.checkPluginUpdates(), 1000);
|
setTimeout(() => window.DeckyPluginLoader.checkPluginUpdates(), 1000);
|
||||||
}}
|
}}
|
||||||
onCancel={async () => {
|
onCancel={async () => {
|
||||||
|
|||||||
@@ -1,4 +1,8 @@
|
|||||||
import { Patch, findModuleChild, replacePatch } from 'decky-frontend-lib';
|
import { Patch, findModuleChild, replacePatch, sleep } from 'decky-frontend-lib';
|
||||||
|
|
||||||
|
import Logger from '../../../../logger';
|
||||||
|
|
||||||
|
const logger = new Logger('LibraryPatch');
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface Window {
|
interface Window {
|
||||||
@@ -10,36 +14,44 @@ declare global {
|
|||||||
let patch: Patch;
|
let patch: Patch;
|
||||||
|
|
||||||
function rePatch() {
|
function rePatch() {
|
||||||
// If you patch anything on SteamClient within the first few seconds of the client having loaded it will get redefined for some reason, so repatch any of these changes that occur within the first 20s of the last patch
|
// If you patch anything on SteamClient within the first few seconds of the client having loaded it will get redefined for some reason, so repatch any of these changes that occur with History.listen or an interval
|
||||||
patch = replacePatch(window.SteamClient.Apps, 'PromptToChangeShortcut', async ([appid]: number[]) => {
|
patch = replacePatch(window.SteamClient.Apps, 'PromptToChangeShortcut', async ([appid]: number[]) => {
|
||||||
try {
|
try {
|
||||||
const details = window.appDetailsStore.GetAppDetails(appid);
|
const details = window.appDetailsStore.GetAppDetails(appid);
|
||||||
console.log(details);
|
logger.debug('game details', details);
|
||||||
// strShortcutStartDir
|
// strShortcutStartDir
|
||||||
const file = await window.DeckyPluginLoader.openFilePicker(details.strShortcutStartDir.replaceAll('"', ''));
|
const file = await window.DeckyPluginLoader.openFilePicker(details.strShortcutStartDir.replaceAll('"', ''));
|
||||||
console.log('user selected', file);
|
logger.debug('user selected', file);
|
||||||
window.SteamClient.Apps.SetShortcutExe(appid, JSON.stringify(file.path));
|
window.SteamClient.Apps.SetShortcutExe(appid, JSON.stringify(file.path));
|
||||||
const pathArr = file.path.split('/');
|
const pathArr = file.path.split('/');
|
||||||
pathArr.pop();
|
pathArr.pop();
|
||||||
const folder = pathArr.join('/');
|
const folder = pathArr.join('/');
|
||||||
window.SteamClient.Apps.SetShortcutStartDir(appid, JSON.stringify(folder));
|
window.SteamClient.Apps.SetShortcutStartDir(appid, JSON.stringify(folder));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e);
|
logger.error(e);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO type and add to frontend-lib
|
|
||||||
const History = findModuleChild((m) => {
|
|
||||||
if (typeof m !== 'object') return undefined;
|
|
||||||
for (let prop in m) {
|
|
||||||
if (m[prop]?.m_history) return m[prop].m_history;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
export default async function libraryPatch() {
|
export default async function libraryPatch() {
|
||||||
try {
|
try {
|
||||||
rePatch();
|
rePatch();
|
||||||
|
// TODO type and add to frontend-lib
|
||||||
|
let History: any;
|
||||||
|
|
||||||
|
while (!History) {
|
||||||
|
History = findModuleChild((m) => {
|
||||||
|
if (typeof m !== 'object') return undefined;
|
||||||
|
for (let prop in m) {
|
||||||
|
if (m[prop]?.m_history) return m[prop].m_history;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (!History) {
|
||||||
|
logger.debug('Waiting 5s for history to become available.');
|
||||||
|
await sleep(5000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const unlisten = History.listen(() => {
|
const unlisten = History.listen(() => {
|
||||||
if (window.SteamClient.Apps.PromptToChangeShortcut !== patch.patchedFunction) {
|
if (window.SteamClient.Apps.PromptToChangeShortcut !== patch.patchedFunction) {
|
||||||
rePatch();
|
rePatch();
|
||||||
@@ -47,11 +59,11 @@ export default async function libraryPatch() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
patch.unpatch();
|
|
||||||
unlisten();
|
unlisten();
|
||||||
|
patch.unpatch();
|
||||||
};
|
};
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('Error patching library file picker', e);
|
logger.error('Error patching library file picker', e);
|
||||||
}
|
}
|
||||||
return () => {};
|
return () => {};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ import {
|
|||||||
DialogButton,
|
DialogButton,
|
||||||
Dropdown,
|
Dropdown,
|
||||||
Focusable,
|
Focusable,
|
||||||
|
Navigation,
|
||||||
QuickAccessTab,
|
QuickAccessTab,
|
||||||
Router,
|
|
||||||
SingleDropdownOption,
|
SingleDropdownOption,
|
||||||
SuspensefulImage,
|
SuspensefulImage,
|
||||||
joinClassNames,
|
joinClassNames,
|
||||||
@@ -41,8 +41,8 @@ const PluginCard: FC<PluginCardProps> = ({ plugin }) => {
|
|||||||
}}
|
}}
|
||||||
onCancel={(_: CustomEvent) => {
|
onCancel={(_: CustomEvent) => {
|
||||||
if (containerRef.current!.querySelectorAll('* :focus').length === 0) {
|
if (containerRef.current!.querySelectorAll('* :focus').length === 0) {
|
||||||
Router.NavigateBackOrOpenMenu();
|
Navigation.NavigateBack();
|
||||||
setTimeout(() => Router.OpenQuickAccessMenu(QuickAccessTab.Decky), 1000);
|
setTimeout(() => Navigation.OpenQuickAccessMenu(QuickAccessTab.Decky), 1000);
|
||||||
} else {
|
} else {
|
||||||
containerRef.current!.focus();
|
containerRef.current!.focus();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import 'i18n.ts';
|
import 'i18n.ts';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
Navigation,
|
||||||
ReactRouter,
|
ReactRouter,
|
||||||
Router,
|
Router,
|
||||||
fakeRenderComponent,
|
fakeRenderComponent,
|
||||||
@@ -28,13 +29,20 @@ const logger = new Logger('DeveloperMode');
|
|||||||
|
|
||||||
let removeSettingsObserver: () => void = () => {};
|
let removeSettingsObserver: () => void = () => {};
|
||||||
|
|
||||||
export function setShowValveInternal(show: boolean) {
|
export async function setShowValveInternal(show: boolean) {
|
||||||
const settingsMod = findModuleChild((m) => {
|
let settingsMod: any;
|
||||||
if (typeof m !== 'object') return undefined;
|
while (!settingsMod) {
|
||||||
for (let prop in m) {
|
settingsMod = findModuleChild((m) => {
|
||||||
if (typeof m[prop]?.settings?.bIsValveEmail !== 'undefined') return m[prop];
|
if (typeof m !== 'object') return undefined;
|
||||||
|
for (let prop in m) {
|
||||||
|
if (typeof m[prop]?.settings?.bIsValveEmail !== 'undefined') return m[prop];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (!settingsMod) {
|
||||||
|
logger.debug('[ValveInternal] waiting for settingsMod');
|
||||||
|
await sleep(1000);
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
if (show) {
|
if (show) {
|
||||||
removeSettingsObserver = settingsMod[
|
removeSettingsObserver = settingsMod[
|
||||||
@@ -76,13 +84,14 @@ export async function startup() {
|
|||||||
window.DFL = {
|
window.DFL = {
|
||||||
findModuleChild,
|
findModuleChild,
|
||||||
findModule,
|
findModule,
|
||||||
|
Navigation,
|
||||||
|
Router,
|
||||||
|
ReactRouter,
|
||||||
ReactUtils: {
|
ReactUtils: {
|
||||||
fakeRenderComponent,
|
fakeRenderComponent,
|
||||||
findInReactTree,
|
findInReactTree,
|
||||||
findInTree,
|
findInTree,
|
||||||
},
|
},
|
||||||
Router,
|
|
||||||
ReactRouter,
|
|
||||||
classes: {
|
classes: {
|
||||||
scrollClasses,
|
scrollClasses,
|
||||||
staticClasses,
|
staticClasses,
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import WithSuspense from './components/WithSuspense';
|
|||||||
import Logger from './logger';
|
import Logger from './logger';
|
||||||
import { Plugin } from './plugin';
|
import { Plugin } from './plugin';
|
||||||
import RouterHook from './router-hook';
|
import RouterHook from './router-hook';
|
||||||
|
import { deinitSteamFixes, initSteamFixes } from './steamfixes';
|
||||||
import { checkForUpdates } from './store';
|
import { checkForUpdates } from './store';
|
||||||
import TabsHook from './tabs-hook';
|
import TabsHook from './tabs-hook';
|
||||||
import OldTabsHook from './tabs-hook.old';
|
import OldTabsHook from './tabs-hook.old';
|
||||||
@@ -95,6 +96,8 @@ class PluginLoader extends Logger {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
initSteamFixes();
|
||||||
|
|
||||||
initFilepickerPatches();
|
initFilepickerPatches();
|
||||||
|
|
||||||
this.updateVersion();
|
this.updateVersion();
|
||||||
@@ -188,6 +191,7 @@ class PluginLoader extends Logger {
|
|||||||
public deinit() {
|
public deinit() {
|
||||||
this.routerHook.removeRoute('/decky/store');
|
this.routerHook.removeRoute('/decky/store');
|
||||||
this.routerHook.removeRoute('/decky/settings');
|
this.routerHook.removeRoute('/decky/settings');
|
||||||
|
deinitSteamFixes();
|
||||||
deinitFilepickerPatches();
|
deinitFilepickerPatches();
|
||||||
this.focusWorkaroundPatch?.unpatch();
|
this.focusWorkaroundPatch?.unpatch();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
## What's this?
|
||||||
|
|
||||||
|
`steamfixes` contains various fixes and workaround for things Valve has broken that cause Decky issues.
|
||||||
|
|
||||||
|
## Current fixes:
|
||||||
|
|
||||||
|
- StartRestart() -> StartShutdown(false) override:
|
||||||
|
|
||||||
|
StartRestart() breaks CEF debugging, StartShutdown(false) doesn't. We can safely replace StartRestart() with StartShutdown(false) as gamescope-session will automatically restart the steam client anyway if it shuts down, bypassing the broken restart codepath. Added 12/29/2022
|
||||||
|
|
||||||
|
- ExecuteSteamURL UI reload fix:
|
||||||
|
|
||||||
|
Starting sometime in November 2022, Valve broke reloading the Steam UI pages via location.reload, as it won't properly start the UI. We can manually trigger UI startup if we detect no active input contexts by calling `SteamClient.URL.ExecuteSteamURL("steam://open/settings/")` Added 12/29/2022
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
import reloadFix from './reload';
|
||||||
|
import restartFix from './restart';
|
||||||
|
let fixes: Function[] = [];
|
||||||
|
|
||||||
|
export function deinitSteamFixes() {
|
||||||
|
fixes.forEach((deinit) => deinit());
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function initSteamFixes() {
|
||||||
|
fixes.push(reloadFix());
|
||||||
|
fixes.push(await restartFix());
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
import Logger from '../logger';
|
||||||
|
|
||||||
|
const logger = new Logger('ReloadSteamFix');
|
||||||
|
|
||||||
|
export default function reloadFix() {
|
||||||
|
// Hack to unbreak the ui when reloading it
|
||||||
|
if (window.FocusNavController?.m_rgAllContexts?.length == 0) {
|
||||||
|
SteamClient.URL.ExecuteSteamURL('steam://open/settings');
|
||||||
|
logger.log('Applied UI reload fix.');
|
||||||
|
}
|
||||||
|
|
||||||
|
// This steamfix does not need to deinit.
|
||||||
|
return () => {};
|
||||||
|
}
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
import { Patch, findModuleChild, replacePatch, sleep } from 'decky-frontend-lib';
|
||||||
|
|
||||||
|
import Logger from '../logger';
|
||||||
|
|
||||||
|
const logger = new Logger('RestartSteamFix');
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface Window {
|
||||||
|
SteamClient: any;
|
||||||
|
appDetailsStore: any;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let patch: Patch;
|
||||||
|
|
||||||
|
function rePatch() {
|
||||||
|
// If you patch anything on SteamClient within the first few seconds of the client having loaded it will get redefined for some reason, so repatch any of these changes that occur with History.listen or an interval
|
||||||
|
patch = replacePatch(window.SteamClient.User, 'StartRestart', () => SteamClient.User.StartShutdown(false));
|
||||||
|
}
|
||||||
|
|
||||||
|
export default async function restartFix() {
|
||||||
|
try {
|
||||||
|
rePatch();
|
||||||
|
// TODO type and add to frontend-lib
|
||||||
|
let History: any;
|
||||||
|
|
||||||
|
while (!History) {
|
||||||
|
History = findModuleChild((m) => {
|
||||||
|
if (typeof m !== 'object') return undefined;
|
||||||
|
for (let prop in m) {
|
||||||
|
if (m[prop]?.m_history) return m[prop].m_history;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (!History) {
|
||||||
|
logger.debug('Waiting 5s for history to become available.');
|
||||||
|
await sleep(5000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function repatchIfNeeded() {
|
||||||
|
if (window.SteamClient.User.StartRestart !== patch.patchedFunction) {
|
||||||
|
rePatch();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const unlisten = History.listen(repatchIfNeeded);
|
||||||
|
|
||||||
|
// Just in case
|
||||||
|
setTimeout(repatchIfNeeded, 5000);
|
||||||
|
setTimeout(repatchIfNeeded, 10000);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
unlisten();
|
||||||
|
patch.unpatch();
|
||||||
|
};
|
||||||
|
} catch (e) {
|
||||||
|
logger.error('Error patching StartRestart', e);
|
||||||
|
}
|
||||||
|
return () => {};
|
||||||
|
}
|
||||||
@@ -10,6 +10,7 @@ export enum Store {
|
|||||||
export interface StorePluginVersion {
|
export interface StorePluginVersion {
|
||||||
name: string;
|
name: string;
|
||||||
hash: string;
|
hash: string;
|
||||||
|
artifact: string | undefined | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface StorePlugin {
|
export interface StorePlugin {
|
||||||
@@ -73,9 +74,11 @@ export async function installFromURL(url: string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function requestPluginInstall(plugin: string, selectedVer: StorePluginVersion) {
|
export async function requestPluginInstall(plugin: string, selectedVer: StorePluginVersion) {
|
||||||
|
const artifactUrl =
|
||||||
|
selectedVer.artifact ?? `https://cdn.tzatzikiweeb.moe/file/steam-deck-homebrew/versions/${selectedVer.hash}.zip`;
|
||||||
await window.DeckyPluginLoader.callServerMethod('install_plugin', {
|
await window.DeckyPluginLoader.callServerMethod('install_plugin', {
|
||||||
name: plugin,
|
name: plugin,
|
||||||
artifact: `https://cdn.tzatzikiweeb.moe/file/steam-deck-homebrew/versions/${selectedVer.hash}.zip`,
|
artifact: artifactUrl,
|
||||||
version: selectedVer.name,
|
version: selectedVer.name,
|
||||||
hash: selectedVer.hash,
|
hash: selectedVer.hash,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import Logger from './logger';
|
|||||||
declare global {
|
declare global {
|
||||||
interface Window {
|
interface Window {
|
||||||
__TABS_HOOK_INSTANCE: any;
|
__TABS_HOOK_INSTANCE: any;
|
||||||
securitystore: any;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -23,7 +22,6 @@ class TabsHook extends Logger {
|
|||||||
tabs: Tab[] = [];
|
tabs: Tab[] = [];
|
||||||
private qAMRoot?: any;
|
private qAMRoot?: any;
|
||||||
private qamPatch?: Patch;
|
private qamPatch?: Patch;
|
||||||
private unsubscribeSecurity?: () => void;
|
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super('TabsHook');
|
super('TabsHook');
|
||||||
@@ -114,7 +112,6 @@ class TabsHook extends Logger {
|
|||||||
deinit() {
|
deinit() {
|
||||||
this.qamPatch?.unpatch();
|
this.qamPatch?.unpatch();
|
||||||
this.qAMRoot.return.alternate.type = this.qAMRoot.return.type;
|
this.qAMRoot.return.alternate.type = this.qAMRoot.return.type;
|
||||||
this.unsubscribeSecurity?.();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
add(tab: Tab) {
|
add(tab: Tab) {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Patch, ToastData, afterPatch, findInReactTree, sleep } from 'decky-frontend-lib';
|
import { Module, Patch, ToastData, afterPatch, findInReactTree, findModuleChild, sleep } from 'decky-frontend-lib';
|
||||||
import { ReactNode } from 'react';
|
import { ReactNode } from 'react';
|
||||||
|
|
||||||
import Toast from './components/Toast';
|
import Toast from './components/Toast';
|
||||||
@@ -7,6 +7,7 @@ import Logger from './logger';
|
|||||||
declare global {
|
declare global {
|
||||||
interface Window {
|
interface Window {
|
||||||
__TOASTER_INSTANCE: any;
|
__TOASTER_INSTANCE: any;
|
||||||
|
settingsStore: any;
|
||||||
NotificationStore: any;
|
NotificationStore: any;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -16,7 +17,7 @@ class Toaster extends Logger {
|
|||||||
// private toasterState: DeckyToasterState = new DeckyToasterState();
|
// private toasterState: DeckyToasterState = new DeckyToasterState();
|
||||||
private node: any;
|
private node: any;
|
||||||
private rNode: any;
|
private rNode: any;
|
||||||
private settingsModule: any;
|
private audioModule: any;
|
||||||
private finishStartup?: () => void;
|
private finishStartup?: () => void;
|
||||||
private ready: Promise<void> = new Promise((res) => (this.finishStartup = res));
|
private ready: Promise<void> = new Promise((res) => (this.finishStartup = res));
|
||||||
private toasterPatch?: Patch;
|
private toasterPatch?: Patch;
|
||||||
@@ -127,6 +128,17 @@ class Toaster extends Logger {
|
|||||||
this.rNode.stateNode.forceUpdate();
|
this.rNode.stateNode.forceUpdate();
|
||||||
delete this.rNode.stateNode.shouldComponentUpdate;
|
delete this.rNode.stateNode.shouldComponentUpdate;
|
||||||
|
|
||||||
|
this.audioModule = findModuleChild((m: Module) => {
|
||||||
|
if (typeof m !== 'object') return undefined;
|
||||||
|
for (let prop in m) {
|
||||||
|
try {
|
||||||
|
if (m[prop].PlayNavSound && m[prop].RegisterCallbackOnPlaySound) return m[prop];
|
||||||
|
} catch {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
this.log('Initialized');
|
this.log('Initialized');
|
||||||
this.finishStartup?.();
|
this.finishStartup?.();
|
||||||
}
|
}
|
||||||
@@ -135,24 +147,31 @@ class Toaster extends Logger {
|
|||||||
// toast.duration = toast.duration || 5e3;
|
// toast.duration = toast.duration || 5e3;
|
||||||
// this.toasterState.addToast(toast);
|
// this.toasterState.addToast(toast);
|
||||||
await this.ready;
|
await this.ready;
|
||||||
const settings = this.settingsModule?.settings;
|
|
||||||
let toastData = {
|
let toastData = {
|
||||||
nNotificationID: window.NotificationStore.m_nNextTestNotificationID++,
|
nNotificationID: window.NotificationStore.m_nNextTestNotificationID++,
|
||||||
rtCreated: Date.now(),
|
rtCreated: Date.now(),
|
||||||
eType: 15,
|
eType: toast.eType || 11,
|
||||||
nToastDurationMS: toast.duration || (toast.duration = 5e3),
|
nToastDurationMS: toast.duration || (toast.duration = 5e3),
|
||||||
data: toast,
|
data: toast,
|
||||||
decky: true,
|
decky: true,
|
||||||
};
|
};
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
toastData.data.appid = () => 0;
|
toastData.data.appid = () => 0;
|
||||||
|
if (toast.sound === undefined) toast.sound = 6;
|
||||||
|
if (toast.playSound === undefined) toast.playSound = true;
|
||||||
|
if (toast.showToast === undefined) toast.showToast = true;
|
||||||
if (
|
if (
|
||||||
(settings?.bDisableAllToasts && !toast.critical) ||
|
(window.settingsStore.settings.bDisableAllToasts && !toast.critical) ||
|
||||||
(settings?.bDisableToastsInGame && !toast.critical && window.NotificationStore.BIsUserInGame())
|
(window.settingsStore.settings.bDisableToastsInGame &&
|
||||||
|
!toast.critical &&
|
||||||
|
window.NotificationStore.BIsUserInGame())
|
||||||
)
|
)
|
||||||
return;
|
return;
|
||||||
window.NotificationStore.m_rgNotificationToasts.push(toastData);
|
if (toast.playSound) this.audioModule?.PlayNavSound(toast.sound);
|
||||||
window.NotificationStore.DispatchNextToast();
|
if (toast.showToast) {
|
||||||
|
window.NotificationStore.m_rgNotificationToasts.push(toastData);
|
||||||
|
window.NotificationStore.DispatchNextToast();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
deinit() {
|
deinit() {
|
||||||
|
|||||||
Reference in New Issue
Block a user