mirror of
https://github.com/SteamDeckHomebrew/decky-loader.git
synced 2026-07-01 15:29:54 +00:00
Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e6cc4bba5c | |||
| 1199c080bc | |||
| 414e0da2f3 | |||
| cb9b888dc6 | |||
| f3ab0f5989 | |||
| e132aba0f8 | |||
| 0d0e57e35a |
@@ -8,7 +8,8 @@
|
|||||||
3. Scroll the sidebar all the way down and click on `Developer`
|
3. Scroll the sidebar all the way down and click on `Developer`
|
||||||
4. Under Miscellaneous, enable `CEF Remote Debugging`
|
4. Under Miscellaneous, enable `CEF Remote Debugging`
|
||||||
5. Click on the `STEAM` button and select `Power` -> `Switch to Desktop`
|
5. Click on the `STEAM` button and select `Power` -> `Switch to Desktop`
|
||||||
6. Open a terminal and paste the following command into it:
|
6. Make sure you have a password set with the "passwd" command in terminal to install it ([YouTube Guide](https://www.youtube.com/watch?v=1vOMYGj22rQ)).
|
||||||
|
7. Open a terminal and paste the following command into it:
|
||||||
- For users:
|
- For users:
|
||||||
- `curl -L https://github.com/SteamDeckHomebrew/PluginLoader/raw/main/dist/install_release.sh | sh`
|
- `curl -L https://github.com/SteamDeckHomebrew/PluginLoader/raw/main/dist/install_release.sh | sh`
|
||||||
- For developers:
|
- For developers:
|
||||||
|
|||||||
Vendored
+1
@@ -40,6 +40,7 @@ Restart=always
|
|||||||
ExecStart=/home/deck/homebrew/services/PluginLoader
|
ExecStart=/home/deck/homebrew/services/PluginLoader
|
||||||
WorkingDirectory=/home/deck/homebrew/services
|
WorkingDirectory=/home/deck/homebrew/services
|
||||||
|
|
||||||
|
Environment=STORE_URL=https://plugins.deckbrew.xyz
|
||||||
Environment=PLUGIN_PATH=/home/deck/homebrew/plugins
|
Environment=PLUGIN_PATH=/home/deck/homebrew/plugins
|
||||||
|
|
||||||
[Install]
|
[Install]
|
||||||
|
|||||||
Vendored
+1
@@ -30,6 +30,7 @@ User=root
|
|||||||
Restart=always
|
Restart=always
|
||||||
ExecStart=/home/deck/homebrew/services/PluginLoader
|
ExecStart=/home/deck/homebrew/services/PluginLoader
|
||||||
WorkingDirectory=/home/deck/homebrew/services
|
WorkingDirectory=/home/deck/homebrew/services
|
||||||
|
Environment=STORE_URL=https://plugins.deckbrew.xyz
|
||||||
Environment=PLUGIN_PATH=/home/deck/homebrew/plugins
|
Environment=PLUGIN_PATH=/home/deck/homebrew/plugins
|
||||||
[Install]
|
[Install]
|
||||||
WantedBy=multi-user.target
|
WantedBy=multi-user.target
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
from injector import get_tab
|
from injector import get_tab, inject_to_tab
|
||||||
from logging import getLogger
|
from logging import getLogger
|
||||||
from os import path, rename
|
from os import path, rename
|
||||||
from shutil import rmtree
|
from shutil import rmtree
|
||||||
@@ -26,7 +26,7 @@ class PluginBrowser:
|
|||||||
|
|
||||||
server_instance.add_routes([
|
server_instance.add_routes([
|
||||||
web.post("/browser/install_plugin", self.install_plugin),
|
web.post("/browser/install_plugin", self.install_plugin),
|
||||||
web.get("/browser/iframe", self.redirect_to_store)
|
web.get("/browser/redirect", self.redirect_to_store),
|
||||||
])
|
])
|
||||||
|
|
||||||
def _unzip_to_plugin_dir(self, zip, name, hash):
|
def _unzip_to_plugin_dir(self, zip, name, hash):
|
||||||
|
|||||||
+11
-11
@@ -6,6 +6,8 @@ from asyncio import Queue
|
|||||||
from os import path, listdir
|
from os import path, listdir
|
||||||
from logging import getLogger
|
from logging import getLogger
|
||||||
from time import time
|
from time import time
|
||||||
|
from genericpath import exists
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
from injector import get_tabs, get_tab
|
from injector import get_tabs, get_tab
|
||||||
from plugin import PluginWrapper
|
from plugin import PluginWrapper
|
||||||
@@ -18,6 +20,12 @@ class FileChangeHandler(FileSystemEventHandler):
|
|||||||
self.plugin_path = plugin_path
|
self.plugin_path = plugin_path
|
||||||
self.queue = queue
|
self.queue = queue
|
||||||
|
|
||||||
|
def maybe_reload(self, src_path):
|
||||||
|
plugin_dir = Path(path.relpath(src_path, self.plugin_path)).parts[0]
|
||||||
|
self.logger.info(path.join(self.plugin_path, plugin_dir, "plugin.json"))
|
||||||
|
if exists(path.join(self.plugin_path, plugin_dir, "plugin.json")):
|
||||||
|
self.queue.put_nowait((path.join(self.plugin_path, plugin_dir, "main.py"), plugin_dir, True))
|
||||||
|
|
||||||
def on_created(self, event):
|
def on_created(self, event):
|
||||||
src_path = event.src_path
|
src_path = event.src_path
|
||||||
if "__pycache__" in src_path:
|
if "__pycache__" in src_path:
|
||||||
@@ -27,13 +35,8 @@ class FileChangeHandler(FileSystemEventHandler):
|
|||||||
if path.isdir(src_path):
|
if path.isdir(src_path):
|
||||||
return
|
return
|
||||||
|
|
||||||
# get the directory name of the plugin so that we can find its "main.py" and reload it; the
|
|
||||||
# file that changed is not necessarily the one that needs to be reloaded
|
|
||||||
self.logger.debug(f"file created: {src_path}")
|
self.logger.debug(f"file created: {src_path}")
|
||||||
rel_path = path.relpath(src_path, path.commonprefix([self.plugin_path, src_path]))
|
self.maybe_reload(src_path)
|
||||||
plugin_dir = path.split(rel_path)[0]
|
|
||||||
main_file_path = path.join(self.plugin_path, plugin_dir, "main.py")
|
|
||||||
self.queue.put_nowait((main_file_path, plugin_dir, True))
|
|
||||||
|
|
||||||
def on_modified(self, event):
|
def on_modified(self, event):
|
||||||
src_path = event.src_path
|
src_path = event.src_path
|
||||||
@@ -44,11 +47,8 @@ class FileChangeHandler(FileSystemEventHandler):
|
|||||||
if path.isdir(src_path):
|
if path.isdir(src_path):
|
||||||
return
|
return
|
||||||
|
|
||||||
# get the directory name of the plugin so that we can find its "main.py" and reload it; the
|
|
||||||
# file that changed is not necessarily the one that needs to be reloaded
|
|
||||||
self.logger.debug(f"file modified: {src_path}")
|
self.logger.debug(f"file modified: {src_path}")
|
||||||
plugin_dir = path.split(path.relpath(src_path, path.commonprefix([self.plugin_path, src_path])))[0]
|
self.maybe_reload(src_path)
|
||||||
self.queue.put_nowait((path.join(self.plugin_path, plugin_dir, "main.py"), plugin_dir, True))
|
|
||||||
|
|
||||||
class Loader:
|
class Loader:
|
||||||
def __init__(self, server_instance, plugin_path, loop, live_reload=False) -> None:
|
def __init__(self, server_instance, plugin_path, loop, live_reload=False) -> None:
|
||||||
@@ -121,7 +121,7 @@ class Loader:
|
|||||||
return await self.callsigns[callsign].execute_method(method_name, kwargs)
|
return await self.callsigns[callsign].execute_method(method_name, kwargs)
|
||||||
|
|
||||||
async def get_steam_resource(self, request):
|
async def get_steam_resource(self, request):
|
||||||
tab = (await get_tabs())[0]
|
tab = await get_tab("QuickAccess")
|
||||||
try:
|
try:
|
||||||
return web.Response(text=await tab.get_steam_resource(f"https://steamloopback.host/{request.match_info['path']}"), content_type="text/html")
|
return web.Response(text=await tab.get_steam_resource(f"https://steamloopback.host/{request.match_info['path']}"), content_type="text/html")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ CONFIG = {
|
|||||||
"server_port": int(getenv("SERVER_PORT", "1337")),
|
"server_port": int(getenv("SERVER_PORT", "1337")),
|
||||||
"live_reload": getenv("LIVE_RELOAD", "1") == "1",
|
"live_reload": getenv("LIVE_RELOAD", "1") == "1",
|
||||||
"log_level": {"CRITICAL": 50, "ERROR": 40, "WARNING":30, "INFO": 20, "DEBUG": 10}[getenv("LOG_LEVEL", "INFO")],
|
"log_level": {"CRITICAL": 50, "ERROR": 40, "WARNING":30, "INFO": 20, "DEBUG": 10}[getenv("LOG_LEVEL", "INFO")],
|
||||||
"store_url": getenv("STORE_URL", "https://plugins.deckbrew.xyz"),
|
"store_url": getenv("STORE_URL", "https://beta.deckbrew.xyz"),
|
||||||
"log_base_events": getenv("LOG_BASE_EVENTS", "0")=="1"
|
"log_base_events": getenv("LOG_BASE_EVENTS", "0")=="1"
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -96,7 +96,7 @@ class PluginManager:
|
|||||||
"id": 1,
|
"id": 1,
|
||||||
"method": "Runtime.evaluate",
|
"method": "Runtime.evaluate",
|
||||||
"params": {
|
"params": {
|
||||||
"expression": f"resolveMethodCall({call_id}, {r})",
|
"expression": f"resolveMethodCall('{call_id}', {r})",
|
||||||
"userGesture": True
|
"userGesture": True
|
||||||
}
|
}
|
||||||
}, receive=False)
|
}, receive=False)
|
||||||
@@ -141,4 +141,4 @@ class PluginManager:
|
|||||||
return run_app(self.web_app, host=CONFIG["server_host"], port=CONFIG["server_port"], loop=self.loop, access_log=None)
|
return run_app(self.web_app, host=CONFIG["server_host"], port=CONFIG["server_port"], loop=self.loop, access_log=None)
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
PluginManager().run()
|
PluginManager().run()
|
||||||
|
|||||||
@@ -53,8 +53,22 @@ function addPluginInstallPrompt(artifact, version, request_id) {
|
|||||||
</svg>
|
</svg>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
const SHOP_ICON = `
|
||||||
|
<button
|
||||||
|
class="DialogButton _DialogLayout Secondary basicdialog_Button_1Ievp Focusable"
|
||||||
|
style="width: auto; padding-left: 10px; padding-right: 10px; margin-right: 1rem; margin-left: auto; padding-top: 3px;"
|
||||||
|
id="open_shop_button"
|
||||||
|
>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-bag-fill" viewBox="0 0 16 16">
|
||||||
|
<path d="M8 1a2.5 2.5 0 0 1 2.5 2.5V4h-5v-.5A2.5 2.5 0 0 1 8 1zm3.5 3v-.5a3.5 3.5 0 1 0-7 0V4H1v10a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V4h-3.5z"/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
`
|
||||||
|
|
||||||
function createTitle(text) {
|
function createTitle(text) {
|
||||||
return `<div id="plugin_title" class="quickaccessmenu_Title_34nl5">${text}</div>`;
|
return `
|
||||||
|
<div class="quickaccessmenu_Title_34nl5"><div id="plugin_title">${text}</div>${SHOP_ICON}</div>
|
||||||
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function createPluginList() {
|
function createPluginList() {
|
||||||
@@ -79,11 +93,20 @@ function addPluginInstallPrompt(artifact, version, request_id) {
|
|||||||
inject();
|
inject();
|
||||||
document.getElementById("plugin_title").onclick = function() {
|
document.getElementById("plugin_title").onclick = function() {
|
||||||
reloadIframe();
|
reloadIframe();
|
||||||
document.getElementById("plugin_title").innerText = "Plugins";
|
document.getElementById("plugin_title").innerText = `Plugins`;
|
||||||
|
document.getElementById("open_shop_button").style.display = 'block';
|
||||||
|
}
|
||||||
|
document.getElementById("open_shop_button").onclick = function(ev) {
|
||||||
|
console.debug(JSON.stringify({
|
||||||
|
"id": 1,
|
||||||
|
"method": "open_plugin_store",
|
||||||
|
"args": {}
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
window.onmessage = function(ev) {
|
window.onmessage = function(ev) {
|
||||||
let title = ev.data;
|
let title = ev.data;
|
||||||
if (title.startsWith("PLUGIN_LOADER__")) {
|
if (title.startsWith("PLUGIN_LOADER__")) {
|
||||||
|
document.getElementById("open_shop_button").style.display = 'none';
|
||||||
document.getElementById("plugin_title").innerHTML = `
|
document.getElementById("plugin_title").innerHTML = `
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-arrow-left-square-fill" viewBox="0 0 16 16">
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-arrow-left-square-fill" viewBox="0 0 16 16">
|
||||||
<path d="M16 14a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V2a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v12zm-4.5-6.5H5.707l2.147-2.146a.5.5 0 1 0-.708-.708l-3 3a.5.5 0 0 0 0 .708l3 3a.5.5 0 0 0 .708-.708L5.707 8.5H11.5a.5.5 0 0 0 0-1z"/>
|
<path d="M16 14a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V2a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v12zm-4.5-6.5H5.707l2.147-2.146a.5.5 0 1 0-.708-.708l-3 3a.5.5 0 0 0 0 .708l3 3a.5.5 0 0 0 .708-.708L5.707 8.5H11.5a.5.5 0 0 0 0-1z"/>
|
||||||
|
|||||||
@@ -11,7 +11,8 @@ class Utilities:
|
|||||||
"confirm_plugin_install": self.confirm_plugin_install,
|
"confirm_plugin_install": self.confirm_plugin_install,
|
||||||
"execute_in_tab": self.execute_in_tab,
|
"execute_in_tab": self.execute_in_tab,
|
||||||
"inject_css_into_tab": self.inject_css_into_tab,
|
"inject_css_into_tab": self.inject_css_into_tab,
|
||||||
"remove_css_from_tab": self.remove_css_from_tab
|
"remove_css_from_tab": self.remove_css_from_tab,
|
||||||
|
"open_plugin_store": self.open_plugin_store
|
||||||
}
|
}
|
||||||
|
|
||||||
async def confirm_plugin_install(self, request_id):
|
async def confirm_plugin_install(self, request_id):
|
||||||
@@ -104,3 +105,16 @@ class Utilities:
|
|||||||
"success": False,
|
"success": False,
|
||||||
"result": e
|
"result": e
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async def open_plugin_store(self):
|
||||||
|
await inject_to_tab("SP", """
|
||||||
|
(function() {
|
||||||
|
wpRequire = webpackJsonp.push([[], { get_require: (mod, _exports, wpRequire) => mod.exports = wpRequire }, [["get_require"]]]);
|
||||||
|
const all = () => Object.keys(wpRequire.c).map((x) => wpRequire.c[x].exports).filter((x) => x);
|
||||||
|
router = all().map(m => {
|
||||||
|
if (typeof m !== "object") return undefined;
|
||||||
|
for (let prop in m) { if (m[prop]?.Navigate) return m[prop]}
|
||||||
|
}).find(x => x)
|
||||||
|
router.NavigateToExternalWeb("http://127.0.0.1:1337/browser/redirect")
|
||||||
|
})();
|
||||||
|
""")
|
||||||
Reference in New Issue
Block a user